From d6ae4f6cf3184ce1e160e616e6f207798ac42e24 Mon Sep 17 00:00:00 2001 From: Kortin Zhou <50259158+kortin99@users.noreply.github.com> Date: Sun, 21 Jul 2024 06:46:57 +0800 Subject: [PATCH 0001/4355] feat(snippets): add support for kebab-case in snippets tmLanguage syntax --- .../json/syntaxes/snippets.tmLanguage.json | 18 +++++++-------- .../editor/contrib/snippet/browser/snippet.md | 2 +- .../contrib/snippet/browser/snippetParser.ts | 23 +++++++++++++++++++ .../test/browser/snippetParser.test.ts | 9 ++++++++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/extensions/json/syntaxes/snippets.tmLanguage.json b/extensions/json/syntaxes/snippets.tmLanguage.json index 289bc18f8c6..8b63c7e44c8 100644 --- a/extensions/json/syntaxes/snippets.tmLanguage.json +++ b/extensions/json/syntaxes/snippets.tmLanguage.json @@ -46,7 +46,7 @@ "name": "constant.character.escape.json.comments.snippets" }, "bnf_any": { - "match": "(?:\\}|((?:(?:(?:(?:(?:(?:((?:(\\$)([0-9]+)))|((?:(?:(\\$)(\\{))([0-9]+)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)((?:(\\/)((?:(?:(?:(?:(\\\\)(\\\\\\/))|(?:(\\\\\\\\\\\\)(\\\\\\/)))|[^\\/\\n])+))(\\/)(((?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((? x.toLowerCase()) + .join('-'); + } + private _toPascalCase(value: string): string { const match = value.match(/[a-z0-9]+/gi); if (!match) { diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts index bbf24168409..e99222a6a6b 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts @@ -668,6 +668,15 @@ suite('SnippetParser', () => { assert.strictEqual(new FormatString(1, 'camelcase').resolve('snake_AndCamelCase'), 'snakeAndCamelCase'); assert.strictEqual(new FormatString(1, 'camelcase').resolve('kebab-AndCamelCase'), 'kebabAndCamelCase'); assert.strictEqual(new FormatString(1, 'camelcase').resolve('_JustCamelCase'), 'justCamelCase'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('barFoo'), 'bar-foo'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('BarFoo'), 'bar-foo'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('ABarFoo'), 'a-bar-foo'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('bar42Foo'), 'bar42-foo'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('snake_AndPascalCase'), 'snake-and-pascal-case'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('kebab-AndCamelCase'), 'kebab-and-camel-case'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('_justPascalCase'), 'just-pascal-case'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('__UPCASE__'), 'upcase'); + assert.strictEqual(new FormatString(1, 'kebabcase').resolve('__BAR_FOO__'), 'bar-foo'); assert.strictEqual(new FormatString(1, 'notKnown').resolve('input'), 'input'); // if From 507a50ee34ff4a47012aa45503ddd0d4c10b4b77 Mon Sep 17 00:00:00 2001 From: Joseph Riddle Date: Mon, 30 Dec 2024 21:05:07 -0700 Subject: [PATCH 0002/4355] Add snakecase to snippets grammar --- .../json/syntaxes/snippets.tmLanguage.json | 18 +++++++++--------- .../editor/contrib/snippet/browser/snippet.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/json/syntaxes/snippets.tmLanguage.json b/extensions/json/syntaxes/snippets.tmLanguage.json index 289bc18f8c6..9822e724a96 100644 --- a/extensions/json/syntaxes/snippets.tmLanguage.json +++ b/extensions/json/syntaxes/snippets.tmLanguage.json @@ -46,7 +46,7 @@ "name": "constant.character.escape.json.comments.snippets" }, "bnf_any": { - "match": "(?:\\}|((?:(?:(?:(?:(?:(?:((?:(\\$)([0-9]+)))|((?:(?:(\\$)(\\{))([0-9]+)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)((?:(\\/)((?:(?:(?:(?:(\\\\)(\\\\\\/))|(?:(\\\\\\\\\\\\)(\\\\\\/)))|[^\\/\\n])+))(\\/)(((?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((? Date: Mon, 30 Dec 2024 21:05:47 -0700 Subject: [PATCH 0003/4355] Implement snakecase snippet transformer function --- src/vs/editor/contrib/snippet/browser/snippetParser.ts | 10 ++++++++++ .../contrib/snippet/test/browser/snippetParser.test.ts | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/vs/editor/contrib/snippet/browser/snippetParser.ts b/src/vs/editor/contrib/snippet/browser/snippetParser.ts index a5f1c16c285..5c5ec007ca2 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetParser.ts @@ -387,6 +387,8 @@ export class FormatString extends Marker { return !value ? '' : this._toPascalCase(value); } else if (this.shorthandName === 'camelcase') { return !value ? '' : this._toCamelCase(value); + } else if (this.shorthandName === 'snakecase') { + return !value ? '' : this._toSnakeCase(value); } else if (Boolean(value) && typeof this.ifValue === 'string') { return this.ifValue; } else if (!Boolean(value) && typeof this.elseValue === 'string') { @@ -421,6 +423,14 @@ export class FormatString extends Marker { .join(''); } + private _toSnakeCase(value: string): string { + const match = value.match(/[a-z0-9]+/gi); + if (!match) { + return value; + } + return match.map(word => word.toLowerCase()).join('_'); + } + toTextmateString(): string { let value = '${'; value += this.index; diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts index 31fda916089..f615ed219ac 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts @@ -668,6 +668,11 @@ suite('SnippetParser', () => { assert.strictEqual(new FormatString(1, 'camelcase').resolve('snake_AndCamelCase'), 'snakeAndCamelCase'); assert.strictEqual(new FormatString(1, 'camelcase').resolve('kebab-AndCamelCase'), 'kebabAndCamelCase'); assert.strictEqual(new FormatString(1, 'camelcase').resolve('_JustCamelCase'), 'justCamelCase'); + assert.strictEqual(new FormatString(1, 'snakecase').resolve('bar-foo'), 'bar_foo'); + assert.strictEqual(new FormatString(1, 'snakecase').resolve('bar-42-foo'), 'bar_42_foo'); + assert.strictEqual(new FormatString(1, 'snakecase').resolve('snake_AndPascalCase'), 'snake_and_pascal_case'); + assert.strictEqual(new FormatString(1, 'snakecase').resolve('kebab-AndPascalCase'), 'kebab_and_pascal_case'); + assert.strictEqual(new FormatString(1, 'snakecase').resolve('_justPascalCase'), '_just_pascal_case'); assert.strictEqual(new FormatString(1, 'notKnown').resolve('input'), 'input'); // if From 1f6b00b9ea0b57922b8f8f91a187595f8f6d4d8a Mon Sep 17 00:00:00 2001 From: Joseph Riddle Date: Mon, 30 Dec 2024 21:48:28 -0700 Subject: [PATCH 0004/4355] Fix logic for snippets _toSnakeCase --- src/vs/editor/contrib/snippet/browser/snippetParser.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/snippet/browser/snippetParser.ts b/src/vs/editor/contrib/snippet/browser/snippetParser.ts index 5c5ec007ca2..1d86fadf42b 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetParser.ts @@ -424,11 +424,9 @@ export class FormatString extends Marker { } private _toSnakeCase(value: string): string { - const match = value.match(/[a-z0-9]+/gi); - if (!match) { - return value; - } - return match.map(word => word.toLowerCase()).join('_'); + return value.replace(/([a-z])([A-Z])/g, '$1_$2') + .replace(/[\s\-]+/g, '_') + .toLowerCase(); } toTextmateString(): string { From 20e65fd2ff3753918d34968d260b5ed1a071980d Mon Sep 17 00:00:00 2001 From: BartolHrg <78815047+BartolHrg@users.noreply.github.com> Date: Tue, 15 Jul 2025 18:59:01 +0200 Subject: [PATCH 0005/4355] fix copy with multiple cursors and empty selections --- src/vs/editor/common/viewModel/viewModelImpl.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 32bfed905ef..5f20b579cbd 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -963,17 +963,6 @@ export class ViewModel extends Disposable implements IViewModel { if (!emptySelectionClipboard) { return ''; } - - const modelLineNumbers = modelRanges.map((r) => r.startLineNumber); - - let result = ''; - for (let i = 0; i < modelLineNumbers.length; i++) { - if (i > 0 && modelLineNumbers[i - 1] === modelLineNumbers[i]) { - continue; - } - result += this.model.getLineContent(modelLineNumbers[i]) + newLineCharacter; - } - return result; } if (hasEmptyRange && emptySelectionClipboard) { @@ -984,7 +973,7 @@ export class ViewModel extends Disposable implements IViewModel { const modelLineNumber = modelRange.startLineNumber; if (modelRange.isEmpty()) { if (modelLineNumber !== prevModelLineNumber) { - result.push(this.model.getLineContent(modelLineNumber)); + result.push(this.model.getLineContent(modelLineNumber) + newLineCharacter); } } else { result.push(this.model.getValueInRange(modelRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined)); From 2d2f91445d19985640bd6ad68197067011ff8108 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 7 Aug 2025 14:35:46 -0700 Subject: [PATCH 0006/4355] Make activationEventsGenerator a real generator Every time I add a new `activationEventsGenerator`, I wish it supported using a real generator --- .../common/implicitActivationEvents.ts | 4 ++-- src/vs/platform/extensions/common/extensionValidator.ts | 2 +- src/vs/platform/extensions/common/extensions.ts | 2 +- src/vs/workbench/api/browser/viewsExtensionPoint.ts | 4 ++-- .../api/test/common/extHostExtensionActivator.test.ts | 2 +- .../contrib/chat/browser/chatOutputItemRenderer.ts | 4 ++-- .../contrib/chat/browser/chatParticipant.contribution.ts | 4 ++-- .../contrib/chat/browser/chatSessions.contribution.ts | 4 ++-- src/vs/workbench/contrib/chat/common/languageModels.ts | 4 ++-- .../chat/common/tools/languageModelToolsContribution.ts | 4 ++-- .../contrib/customEditor/common/extensionPoint.ts | 4 ++-- src/vs/workbench/contrib/debug/common/debugVisualizers.ts | 4 ++-- src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts | 4 ++-- .../contrib/notebook/browser/notebookExtensionPoint.ts | 8 ++++---- .../contrib/tasks/common/taskDefinitionRegistry.ts | 4 ++-- src/vs/workbench/contrib/terminal/common/terminal.ts | 4 ++-- .../quickFix/browser/terminalQuickFixService.ts | 4 ++-- .../browser/gettingStartedExtensionPoint.ts | 4 ++-- .../services/actions/common/menusExtensionPoint.ts | 4 ++-- .../authentication/browser/authenticationService.ts | 4 ++-- .../test/common/extensionDescriptionRegistry.test.ts | 2 +- .../workbench/services/language/common/languageService.ts | 4 ++-- 22 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts index b8a52b5e9aa..7fdeb16a04b 100644 --- a/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts +++ b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from '../../../base/common/errors.js'; import { ExtensionIdentifier, IExtensionDescription } from '../../extensions/common/extensions.js'; export interface IActivationEventsGenerator { - (contributions: T[], result: { push(item: string): void }): void; + (contributions: readonly T[]): Iterable; } export class ImplicitActivationEventsImpl { @@ -73,7 +73,7 @@ export class ImplicitActivationEventsImpl { const contrib = (desc.contributes as any)[extPointName]; const contribArr = Array.isArray(contrib) ? contrib : [contrib]; try { - generator(contribArr, activationEvents); + activationEvents.push(...generator(contribArr)); } catch (err) { onUnexpectedError(err); } diff --git a/src/vs/platform/extensions/common/extensionValidator.ts b/src/vs/platform/extensions/common/extensionValidator.ts index 87a9288104d..0683f680cb7 100644 --- a/src/vs/platform/extensions/common/extensionValidator.ts +++ b/src/vs/platform/extensions/common/extensionValidator.ts @@ -417,7 +417,7 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers return true; } -function isStringArray(arr: string[]): boolean { +function isStringArray(arr: readonly string[]): boolean { if (!Array.isArray(arr)) { return false; } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index bf4bb318095..107e54a8dc2 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -299,7 +299,7 @@ export interface IRelaxedExtensionManifest { icon?: string; categories?: string[]; keywords?: string[]; - activationEvents?: string[]; + activationEvents?: readonly string[]; extensionDependencies?: string[]; extensionPack?: string[]; extensionKind?: ExtensionKind | ExtensionKind[]; diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index fdf354d1f54..c98aada78a2 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -254,12 +254,12 @@ const viewsExtensionPoint: IExtensionPoint = ExtensionsR extensionPoint: 'views', deps: [viewsContainersExtensionPoint], jsonSchema: viewsContribution, - activationEventsGenerator: (viewExtensionPointTypeArray, result) => { + activationEventsGenerator: function* (viewExtensionPointTypeArray) { for (const viewExtensionPointType of viewExtensionPointTypeArray) { for (const viewDescriptors of Object.values(viewExtensionPointType)) { for (const viewDescriptor of viewDescriptors) { if (viewDescriptor.id) { - result.push(`onView:${viewDescriptor.id}`); + yield `onView:${viewDescriptor.id}`; } } } diff --git a/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts b/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts index d7a3c6f68e8..36ad00f99db 100644 --- a/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts +++ b/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts @@ -279,7 +279,7 @@ suite('ExtensionsActivator', () => { const basicActivationEventsReader: IActivationEventsReader = { readActivationEvents: (extensionDescription: IExtensionDescription): string[] => { - return extensionDescription.activationEvents ?? []; + return extensionDescription.activationEvents?.slice() ?? []; } }; diff --git a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts index ecf3fea2687..b6f7ee9ffb6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts @@ -178,9 +178,9 @@ interface IChatOutputRendererContribution { const chatOutputRenderContributionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatOutputRenderers', - activationEventsGenerator: (contributions: IChatOutputRendererContribution[], result) => { + activationEventsGenerator: function* (contributions: readonly IChatOutputRendererContribution[]) { for (const contrib of contributions) { - result.push(`onChatOutputRenderer:${contrib.viewType}`); + yield `onChatOutputRenderer:${contrib.viewType}`; } }, jsonSchema: { diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 42d8560ece4..aee5354bb5c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -203,9 +203,9 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi } } }, - activationEventsGenerator: (contributions: IRawChatParticipantContribution[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contributions: readonly IRawChatParticipantContribution[]) { for (const contrib of contributions) { - result.push(`onChatParticipant:${contrib.id}`); + yield `onChatParticipant:${contrib.id}`; } }, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index a9cb6a93f7f..69120bd3549 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -58,9 +58,9 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint { + activationEventsGenerator: function* (contribs) { for (const contrib of contribs) { - results.push(`onChatSession:${contrib.type}`); + yield `onChatSession:${contrib.type}`; } } }); diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index d4678314284..b325f18c721 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -290,9 +290,9 @@ export const languageModelExtensionPoint = ExtensionsRegistry.registerExtensionP } ] }, - activationEventsGenerator: (contribs: IUserFriendlyLanguageModel[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly IUserFriendlyLanguageModel[]) { for (const contrib of contribs) { - result.push(`onLanguageModelChat:${contrib.vendor}`); + yield `onLanguageModelChat:${contrib.vendor}`; } } }); diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts index ae6788c7a7e..c86f7b719f9 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts @@ -39,9 +39,9 @@ export interface IRawToolContribution { const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'languageModelTools', - activationEventsGenerator: (contributions: IRawToolContribution[], result) => { + activationEventsGenerator: function* (contributions: readonly IRawToolContribution[]) { for (const contrib of contributions) { - result.push(`onLanguageModelTool:${contrib.name}`); + yield `onLanguageModelTool:${contrib.name}`; } }, jsonSchema: { diff --git a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index d2e06733cfb..68f480d0f55 100644 --- a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -96,11 +96,11 @@ export const customEditorsExtensionPoint = ExtensionsRegistry.registerExtensionP extensionPoint: 'customEditors', deps: [languagesExtPoint], jsonSchema: CustomEditorsContribution, - activationEventsGenerator: (contribs: ICustomEditorsExtensionPoint[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly ICustomEditorsExtensionPoint[]) { for (const contrib of contribs) { const viewType = contrib[Fields.viewType]; if (viewType) { - result.push(`onCustomEditor:${viewType}`); + yield `onCustomEditor:${viewType}`; } } }, diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index c607fbe166b..f3ae72a2703 100644 --- a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -291,10 +291,10 @@ const visualizersExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ id required: ['id', 'when'] } }, - activationEventsGenerator: (contribs, result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs) { for (const contrib of contribs) { if (contrib.id) { - result.push(`onDebugVisualizer:${contrib.id}`); + yield `onDebugVisualizer:${contrib.id}`; } } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts index e1af22069c9..fb06e524c45 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts @@ -221,10 +221,10 @@ export const mcpServerSchema: IJSONSchema = { export const mcpContributionPoint: IExtensionPointDescriptor = { extensionPoint: 'mcpServerDefinitionProviders', - activationEventsGenerator(contribs, result) { + activationEventsGenerator: function* (contribs) { for (const contrib of contribs) { if (contrib.id) { - result.push(mcpActivationEvent(contrib.id)); + yield mcpActivationEvent(contrib.id); } } }, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts index 352dea887c8..6de1d23298e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExtensionPoint.ts @@ -245,10 +245,10 @@ const notebookPreloadContribution: IJSONSchema = { export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'notebooks', jsonSchema: notebookProviderContribution, - activationEventsGenerator: (contribs: INotebookEditorContribution[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly INotebookEditorContribution[]) { for (const contrib of contribs) { if (contrib.type) { - result.push(`onNotebookSerializer:${contrib.type}`); + yield `onNotebookSerializer:${contrib.type}`; } } } @@ -257,10 +257,10 @@ export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'notebookRenderer', jsonSchema: notebookRendererContribution, - activationEventsGenerator: (contribs: INotebookRendererContribution[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly INotebookRendererContribution[]) { for (const contrib of contribs) { if (contrib.id) { - result.push(`onRenderer:${contrib.id}`); + yield `onRenderer:${contrib.id}`; } } } diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index b44773c7dce..1605209c7f1 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -83,10 +83,10 @@ namespace Configuration { const taskDefinitionsExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'taskDefinitions', - activationEventsGenerator: (contributions: Configuration.ITaskDefinition[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contributions: readonly Configuration.ITaskDefinition[]) { for (const task of contributions) { if (task.type) { - result.push(`onTaskType:${task.type}`); + yield `onTaskType:${task.type}`; } } }, diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 36d19fac59c..d5f17f977ed 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -634,10 +634,10 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ export const terminalContributionsDescriptor: IExtensionPointDescriptor = { extensionPoint: 'terminal', defaultExtensionKind: ['workspace'], - activationEventsGenerator: (contribs: ITerminalContributions[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly ITerminalContributions[]) { for (const contrib of contribs) { for (const profileContrib of (contrib.profiles ?? [])) { - result.push(`onTerminalProfile:${profileContrib.id}`); + yield `onTerminalProfile:${profileContrib.id}`; } } }, diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts index 4a095d55e76..f3fed767041 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminalQuickFixService.ts @@ -94,9 +94,9 @@ export class TerminalQuickFixService implements ITerminalQuickFixService { const quickFixExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'terminalQuickFixes', defaultExtensionKind: ['workspace'], - activationEventsGenerator: (terminalQuickFixes: ITerminalCommandSelector[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (terminalQuickFixes: readonly ITerminalCommandSelector[]) { for (const quickFixContrib of terminalQuickFixes ?? []) { - result.push(`onTerminalQuickFixRequest:${quickFixContrib.id}`); + yield `onTerminalQuickFixRequest:${quickFixContrib.id}`; } }, jsonSchema: { diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedExtensionPoint.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedExtensionPoint.ts index 3f1c09885f9..297598efec2 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedExtensionPoint.ts @@ -218,10 +218,10 @@ export const walkthroughsExtensionPoint = ExtensionsRegistry.registerExtensionPo } } }, - activationEventsGenerator: (walkthroughContributions, result) => { + activationEventsGenerator: function* (walkthroughContributions) { for (const walkthroughContribution of walkthroughContributions) { if (walkthroughContribution.id) { - result.push(`onWalkthrough:${walkthroughContribution.id}`); + yield `onWalkthrough:${walkthroughContribution.id}`; } } } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 9d8ac1dd3e9..30d7fe62529 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -805,10 +805,10 @@ const _commandRegistrations = new DisposableStore(); export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'commands', jsonSchema: schema.commandsContribution, - activationEventsGenerator: (contribs: schema.IUserFriendlyCommand[], result: { push(item: string): void }) => { + activationEventsGenerator: function* (contribs: readonly schema.IUserFriendlyCommand[]) { for (const contrib of contribs) { if (contrib.command) { - result.push(`onCommand:${contrib.command}`); + yield `onCommand:${contrib.command}`; } } } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index fc0dac3399c..15e117de20a 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -81,10 +81,10 @@ const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint { + activationEventsGenerator: function* (authenticationProviders) { for (const authenticationProvider of authenticationProviders) { if (authenticationProvider.id) { - result.push(`onAuthenticationRequest:${authenticationProvider.id}`); + yield `onAuthenticationRequest:${authenticationProvider.id}`; } } } diff --git a/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts index 24348eb2260..ebc8563afc4 100644 --- a/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts +++ b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts @@ -20,7 +20,7 @@ suite('ExtensionDescriptionRegistry', () => { const basicActivationEventsReader: IActivationEventsReader = { readActivationEvents: (extensionDescription: IExtensionDescription): string[] => { - return extensionDescription.activationEvents ?? []; + return extensionDescription.activationEvents?.slice() ?? []; } }; diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index 1ab9a920338..f25eaf50de3 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -112,10 +112,10 @@ export const languagesExtPoint: IExtensionPoint = } } }, - activationEventsGenerator: (languageContributions, result) => { + activationEventsGenerator: function* (languageContributions) { for (const languageContribution of languageContributions) { if (languageContribution.id && languageContribution.configuration) { - result.push(`onLanguage:${languageContribution.id}`); + yield `onLanguage:${languageContribution.id}`; } } } From 01b144481d168b122a3f702e3f66afde3033e582 Mon Sep 17 00:00:00 2001 From: Andrew Howson Date: Fri, 8 Aug 2025 14:18:38 +0100 Subject: [PATCH 0007/4355] When suggest box is too tall, ensure max height and position use larger of above and below spaces --- src/vs/editor/contrib/suggest/browser/suggestWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 59f517623a2..fac1fb1de21 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -873,7 +873,7 @@ export class SuggestWidget implements IDisposable { } const forceRenderingAboveRequiredSpace = 150; - if (height > maxHeightBelow || (this._forceRenderingAbove && availableSpaceAbove > forceRenderingAboveRequiredSpace)) { + if ((height > maxHeightBelow && maxHeightAbove > maxHeightBelow) || (this._forceRenderingAbove && availableSpaceAbove > forceRenderingAboveRequiredSpace)) { this._contentWidget.setPreference(ContentWidgetPositionPreference.ABOVE); this.element.enableSashes(true, true, false, false); maxHeight = maxHeightAbove; From bb5ad40be998d27237fbb4f1c9dcc04343ad267c Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 20 Aug 2025 10:57:48 +0200 Subject: [PATCH 0008/4355] Treat ellipsis character as search wildcard --- src/vs/base/common/strings.ts | 2 +- src/vs/base/test/common/fuzzyScorer.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index a9957139b7b..db284a0a24a 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -193,7 +193,7 @@ export function convertSimple2RegExpPattern(pattern: string): string { } export function stripWildcards(pattern: string): string { - return pattern.replace(/\*/g, ''); + return pattern.replace(/[\*…]/g, ''); } export interface RegExpOptions { diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index a0905fec5e9..81a3773baa7 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -206,6 +206,18 @@ suite('Fuzzy Scorer', () => { assert.strictEqual(pathRes.descriptionMatch[0].start, 1); assert.strictEqual(pathRes.descriptionMatch[0].end, 4); + // Ellipsis Match + const ellipsisRes = scoreItem(resource, '…me/path/someFile123.txt', true, ResourceAccessor); + assert.ok(ellipsisRes.score); + assert.ok(pathRes.descriptionMatch); + assert.ok(pathRes.labelMatch); + assert.strictEqual(pathRes.labelMatch.length, 1); + assert.strictEqual(pathRes.labelMatch[0].start, 8); + assert.strictEqual(pathRes.labelMatch[0].end, 11); + assert.strictEqual(pathRes.descriptionMatch.length, 1); + assert.strictEqual(pathRes.descriptionMatch[0].start, 1); + assert.strictEqual(pathRes.descriptionMatch[0].end, 4); + // No Match const noRes = scoreItem(resource, '987', true, ResourceAccessor); assert.ok(!noRes.score); @@ -1128,6 +1140,7 @@ suite('Fuzzy Scorer', () => { test('prepareQuery', () => { assert.strictEqual(prepareQuery(' f*a ').normalized, 'fa'); + assert.strictEqual(prepareQuery(' f…a ').normalized, 'fa'); assert.strictEqual(prepareQuery('model Tester.ts').original, 'model Tester.ts'); assert.strictEqual(prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); assert.strictEqual(prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); From 1f74c2d49eb3f38495d66fd7bfa4f0566873a482 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 25 Aug 2025 13:53:39 +0200 Subject: [PATCH 0009/4355] hygiene --- src/vs/base/common/strings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index db284a0a24a..ca0d0ed6b2f 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -193,7 +193,7 @@ export function convertSimple2RegExpPattern(pattern: string): string { } export function stripWildcards(pattern: string): string { - return pattern.replace(/[\*…]/g, ''); + return pattern.replace(/[\*\u2026]/g, ''); } export interface RegExpOptions { From 6a45a1ad8354fc9efd024094a1a66d2c2d49a2b2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 28 Aug 2025 16:54:08 +0200 Subject: [PATCH 0010/4355] new prompt parser --- src/vs/base/common/yaml.ts | 829 +++++++++++++ src/vs/base/test/common/yaml.test.ts | 1083 +++++++++++++++++ .../promptSyntax/service/newPromptsParser.ts | 94 ++ .../promptSyntax/service/promptsService.ts | 10 +- .../service/promptsServiceImpl.ts | 4 +- 5 files changed, 2016 insertions(+), 4 deletions(-) create mode 100644 src/vs/base/common/yaml.ts create mode 100644 src/vs/base/test/common/yaml.test.ts create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts diff --git a/src/vs/base/common/yaml.ts b/src/vs/base/common/yaml.ts new file mode 100644 index 00000000000..b6b977e5897 --- /dev/null +++ b/src/vs/base/common/yaml.ts @@ -0,0 +1,829 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Parses a simplified YAML-like input from an iterable of strings (lines). + * Supports objects, arrays, primitive types (string, number, boolean, null). + * Tracks positions for error reporting and node locations. + * + * Limitations: + * - No multi-line strings or block literals + * - No anchors or references + * - No complex types (dates, binary) + * - No special handling for escape sequences in strings + * - Indentation must be consistent (spaces only, no tabs) + * + * @param input Iterable of strings representing lines of the YAML-like input + * @param errors Array to collect parsing errors + * @param options Parsing options + * @returns The parsed representation (ObjectNode, ArrayNode, or primitive node) + */ +export function parse(input: Iterable, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined { + const lines = Array.from(input); + const parser = new YamlParser(lines, errors, options); + return parser.parse(); +} + +export interface YamlParseError { + readonly message: string; + readonly start: Position; + readonly end: Position; + readonly code: string; +} + +export interface ParseOptions { + readonly allowDuplicateKeys?: boolean; +} + +export interface Position { + readonly line: number; + readonly character: number; +} + +export interface YamlStringNode { + readonly type: 'string'; + readonly value: string; + readonly start: Position; + readonly end: Position; +} + +export interface YamlNumberNode { + readonly type: 'number'; + readonly value: number; + readonly start: Position; + readonly end: Position; +} + +export interface YamlBooleanNode { + readonly type: 'boolean'; + readonly value: boolean; + readonly start: Position; + readonly end: Position; +} + +export interface YamlNullNode { + readonly type: 'null'; + readonly value: null; + readonly start: Position; + readonly end: Position; +} + +export interface YamlObjectNode { + readonly type: 'object'; + readonly properties: { key: YamlStringNode; value: YamlNode }[]; + readonly start: Position; + readonly end: Position; +} + +export interface YamlArrayNode { + readonly type: 'array'; + readonly items: YamlNode[]; + readonly start: Position; + readonly end: Position; +} + +export type YamlNode = YamlStringNode | YamlNumberNode | YamlBooleanNode | YamlNullNode | YamlObjectNode | YamlArrayNode; + +// Helper functions for position and node creation +function createPosition(line: number, character: number): Position { + return { line, character }; +} + +// Specialized node creation functions using a more concise approach +function createStringNode(value: string, start: Position, end: Position): YamlStringNode { + return { type: 'string', value, start, end }; +} + +function createNumberNode(value: number, start: Position, end: Position): YamlNumberNode { + return { type: 'number', value, start, end }; +} + +function createBooleanNode(value: boolean, start: Position, end: Position): YamlBooleanNode { + return { type: 'boolean', value, start, end }; +} + +function createNullNode(start: Position, end: Position): YamlNullNode { + return { type: 'null', value: null, start, end }; +} + +function createObjectNode(properties: { key: YamlStringNode; value: YamlNode }[], start: Position, end: Position): YamlObjectNode { + return { type: 'object', start, end, properties }; +} + +function createArrayNode(items: YamlNode[], start: Position, end: Position): YamlArrayNode { + return { type: 'array', start, end, items }; +} + +// Utility functions for parsing +function isWhitespace(char: string): boolean { + return char === ' ' || char === '\t'; +} + +// Simplified number validation using regex +function isValidNumber(value: string): boolean { + return /^-?\d*\.?\d+$/.test(value); +} + +// Lexer/Tokenizer for YAML content +class YamlLexer { + private lines: string[]; + private currentLine: number = 0; + private currentChar: number = 0; + + constructor(lines: string[]) { + this.lines = lines; + } + + getCurrentPosition(): Position { + return createPosition(this.currentLine, this.currentChar); + } + + getCurrentLineNumber(): number { + return this.currentLine; + } + + getCurrentCharNumber(): number { + return this.currentChar; + } + + getCurrentLineText(): string { + return this.currentLine < this.lines.length ? this.lines[this.currentLine] : ''; + } + + savePosition(): { line: number; char: number } { + return { line: this.currentLine, char: this.currentChar }; + } + + restorePosition(pos: { line: number; char: number }): void { + this.currentLine = pos.line; + this.currentChar = pos.char; + } + + isAtEnd(): boolean { + return this.currentLine >= this.lines.length; + } + + getCurrentChar(): string { + if (this.isAtEnd() || this.currentChar >= this.lines[this.currentLine].length) { + return ''; + } + return this.lines[this.currentLine][this.currentChar]; + } + + peek(offset: number = 1): string { + const newChar = this.currentChar + offset; + if (this.currentLine >= this.lines.length || newChar >= this.lines[this.currentLine].length) { + return ''; + } + return this.lines[this.currentLine][newChar]; + } + + advance(): string { + const char = this.getCurrentChar(); + if (this.currentChar >= this.lines[this.currentLine].length && this.currentLine < this.lines.length - 1) { + this.currentLine++; + this.currentChar = 0; + } else { + this.currentChar++; + } + return char; + } + + advanceLine(): void { + this.currentLine++; + this.currentChar = 0; + } + + skipWhitespace(): void { + while (!this.isAtEnd() && this.currentChar < this.lines[this.currentLine].length && isWhitespace(this.getCurrentChar())) { + this.advance(); + } + } + + skipToEndOfLine(): void { + this.currentChar = this.lines[this.currentLine].length; + } + + getIndentation(): number { + if (this.isAtEnd()) { + return 0; + } + let indent = 0; + for (let i = 0; i < this.lines[this.currentLine].length; i++) { + if (this.lines[this.currentLine][i] === ' ') { + indent++; + } else if (this.lines[this.currentLine][i] === '\t') { + indent += 4; // Treat tab as 4 spaces + } else { + break; + } + } + return indent; + } + + moveToNextNonEmptyLine(): void { + while (this.currentLine < this.lines.length) { + // First check current line from current position + if (this.currentChar < this.lines[this.currentLine].length) { + const remainingLine = this.lines[this.currentLine].substring(this.currentChar).trim(); + if (remainingLine.length > 0 && !remainingLine.startsWith('#')) { + this.skipWhitespace(); + return; + } + } + + // Move to next line and check from beginning + this.currentLine++; + this.currentChar = 0; + + if (this.currentLine < this.lines.length) { + const line = this.lines[this.currentLine].trim(); + if (line.length > 0 && !line.startsWith('#')) { + this.skipWhitespace(); + return; + } + } + } + } +} + +// Parser class for handling YAML parsing +class YamlParser { + private lexer: YamlLexer; + private errors: YamlParseError[]; + private options: ParseOptions; + + constructor(lines: string[], errors: YamlParseError[], options: ParseOptions) { + this.lexer = new YamlLexer(lines); + this.errors = errors; + this.options = options; + } + + addError(message: string, code: string, start: Position, end: Position): void { + this.errors.push({ message, code, start, end }); + } + + parseValue(expectedIndent?: number): YamlNode { + this.lexer.skipWhitespace(); + + if (this.lexer.isAtEnd()) { + const pos = this.lexer.getCurrentPosition(); + return createStringNode('', pos, pos); + } + + const char = this.lexer.getCurrentChar(); + + // Handle quoted strings + if (char === '"' || char === `'`) { + return this.parseQuotedString(char); + } + + // Handle inline arrays + if (char === '[') { + return this.parseInlineArray(); + } + + // Handle inline objects + if (char === '{') { + return this.parseInlineObject(); + } + + // Handle unquoted values + return this.parseUnquotedValue(); + } + + parseQuotedString(quote: string): YamlNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip opening quote + + let value = ''; + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) { + value += this.lexer.advance(); + } + + if (this.lexer.getCurrentChar() === quote) { + this.lexer.advance(); // Skip closing quote + } + + const end = this.lexer.getCurrentPosition(); + return createStringNode(value, start, end); + } + + parseUnquotedValue(): YamlNode { + const start = this.lexer.getCurrentPosition(); + let value = ''; + let endPos = start; + + // Helper function to check for value terminators + const isTerminator = (char: string): boolean => + char === '#' || char === ',' || char === ']' || char === '}'; + + // Handle opening quote that might not be closed + const firstChar = this.lexer.getCurrentChar(); + if (firstChar === '"' || firstChar === `'`) { + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + + // Continue until we find closing quote or terminator + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + const char = this.lexer.getCurrentChar(); + + if (char === firstChar || isTerminator(char)) { + break; + } + + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + } + } else { + // Regular unquoted value + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + const char = this.lexer.getCurrentChar(); + + if (isTerminator(char)) { + break; + } + + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + } + } + + value = value.trim(); + + // Adjust end position for trimmed value + if (value.length === 0) { + endPos = start; + } else { + endPos = createPosition(start.line, start.character + value.length); + } + + // Return appropriate node type based on value + return this.createValueNode(value, start, endPos); + } + + private createValueNode(value: string, start: Position, end: Position): YamlNode { + if (value === '') { + return createStringNode('', start, start); + } + + // Boolean values + if (value === 'true') { + return createBooleanNode(true, start, end); + } + if (value === 'false') { + return createBooleanNode(false, start, end); + } + + // Null values + if (value === 'null' || value === '~') { + return createNullNode(start, end); + } + + // Number values + const numberValue = Number(value); + if (!isNaN(numberValue) && isFinite(numberValue) && isValidNumber(value)) { + return createNumberNode(numberValue, start, end); + } + + // Default to string + return createStringNode(value, start, end); + } + + parseInlineArray(): YamlArrayNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip '[' + + const items: YamlNode[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.skipWhitespace(); + + // Handle end of array + if (this.lexer.getCurrentChar() === ']') { + this.lexer.advance(); + break; + } + + // Handle end of line - continue to next line for multi-line arrays + if (this.lexer.getCurrentChar() === '') { + this.lexer.advanceLine(); + continue; + } + + // Parse array item + const item = this.parseValue(); + items.push(item); + + this.lexer.skipWhitespace(); + + // Handle comma separator + if (this.lexer.getCurrentChar() === ',') { + this.lexer.advance(); + } + } + + const end = this.lexer.getCurrentPosition(); + return createArrayNode(items, start, end); + } + + parseInlineObject(): YamlObjectNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip '{' + + const properties: { key: YamlStringNode; value: YamlNode }[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.skipWhitespace(); + + // Handle end of object + if (this.lexer.getCurrentChar() === '}') { + this.lexer.advance(); + break; + } + + // Parse key - read until colon + const keyStart = this.lexer.getCurrentPosition(); + let keyValue = ''; + + // Handle quoted keys + if (this.lexer.getCurrentChar() === '"' || this.lexer.getCurrentChar() === `'`) { + const quote = this.lexer.getCurrentChar(); + this.lexer.advance(); // Skip opening quote + + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) { + keyValue += this.lexer.advance(); + } + + if (this.lexer.getCurrentChar() === quote) { + this.lexer.advance(); // Skip closing quote + } + } else { + // Handle unquoted keys - read until colon + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') { + keyValue += this.lexer.advance(); + } + } + + keyValue = keyValue.trim(); + const keyEnd = this.lexer.getCurrentPosition(); + const key = createStringNode(keyValue, keyStart, keyEnd); + + this.lexer.skipWhitespace(); + + // Expect colon + if (this.lexer.getCurrentChar() === ':') { + this.lexer.advance(); + } + + this.lexer.skipWhitespace(); + + // Parse value + const value = this.parseValue(); + + properties.push({ key, value }); + + this.lexer.skipWhitespace(); + + // Handle comma separator + if (this.lexer.getCurrentChar() === ',') { + this.lexer.advance(); + } + } + + const end = this.lexer.getCurrentPosition(); + return createObjectNode(properties, start, end); + } + + parseBlockArray(baseIndent: number): YamlArrayNode { + const start = this.lexer.getCurrentPosition(); + const items: YamlNode[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.moveToNextNonEmptyLine(); + + if (this.lexer.isAtEnd()) { + break; + } + + const currentIndent = this.lexer.getIndentation(); + + // If indentation is less than expected, we're done with this array + if (currentIndent < baseIndent) { + break; + } + + this.lexer.skipWhitespace(); + + // Check for array item marker + if (this.lexer.getCurrentChar() === '-') { + this.lexer.advance(); // Skip '-' + this.lexer.skipWhitespace(); + + const itemStart = this.lexer.getCurrentPosition(); + + // Check if this is a nested structure + if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') { + // Empty item - check if next lines form a nested structure + this.lexer.advanceLine(); + + if (!this.lexer.isAtEnd()) { + const nextIndent = this.lexer.getIndentation(); + + if (nextIndent > currentIndent) { + // Check if the next line starts with a dash (nested array) or has properties (nested object) + this.lexer.skipWhitespace(); + if (this.lexer.getCurrentChar() === '-') { + // It's a nested array + const nestedArray = this.parseBlockArray(nextIndent); + items.push(nestedArray); + } else { + // Check if it looks like an object property (has a colon) + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) { + // It's a nested object + const nestedObject = this.parseBlockObject(nextIndent, this.lexer.getCurrentCharNumber()); + items.push(nestedObject); + } else { + // Not a nested structure, create empty string + items.push(createStringNode('', itemStart, itemStart)); + } + } + } else { + // No nested content, empty item + items.push(createStringNode('', itemStart, itemStart)); + } + } else { + // End of input, empty item + items.push(createStringNode('', itemStart, itemStart)); + } + } else { + // Parse the item value + // Check if this is a multi-line object by looking for a colon and checking next lines + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + // Check if there's a colon on this line (indicating object properties) + const hasColon = remainingLine.includes(':'); + + if (hasColon) { + // Any line with a colon should be treated as an object + // Parse as an object with the current item's indentation as the base + const item = this.parseBlockObject(itemStart.character, itemStart.character); + items.push(item); + } else { + // No colon, parse as regular value + const item = this.parseValue(); + items.push(item); + + // Skip to end of line + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') { + this.lexer.advance(); + } + this.lexer.advanceLine(); + } + } + } else { + // No dash found at expected indent level, break + break; + } + } + + // Calculate end position based on the last item + let end = start; + if (items.length > 0) { + const lastItem = items[items.length - 1]; + end = lastItem.end; + } else { + // If no items, end is right after the start + end = createPosition(start.line, start.character + 1); + } + + return createArrayNode(items, start, end); + } + + parseBlockObject(baseIndent: number, baseCharPosition?: number): YamlObjectNode { + const start = this.lexer.getCurrentPosition(); + const properties: { key: YamlStringNode; value: YamlNode }[] = []; + const localKeysSeen = new Set(); + + // For parsing from current position (inline object parsing) + const fromCurrentPosition = baseCharPosition !== undefined; + let firstIteration = true; + + while (!this.lexer.isAtEnd()) { + if (!firstIteration || !fromCurrentPosition) { + this.lexer.moveToNextNonEmptyLine(); + } + firstIteration = false; + + if (this.lexer.isAtEnd()) { + break; + } + + const currentIndent = this.lexer.getIndentation(); + + if (fromCurrentPosition) { + // For current position parsing, check character position alignment + this.lexer.skipWhitespace(); + const currentCharPosition = this.lexer.getCurrentCharNumber(); + + if (currentCharPosition < baseCharPosition) { + break; + } + } else { + // For normal block parsing, check indentation level + if (currentIndent < baseIndent) { + break; + } + + // Check for incorrect indentation + if (currentIndent > baseIndent) { + const lineStart = createPosition(this.lexer.getCurrentLineNumber(), 0); + const lineEnd = createPosition(this.lexer.getCurrentLineNumber(), this.lexer.getCurrentLineText().length); + this.addError('Unexpected indentation', 'indentation', lineStart, lineEnd); + + // Try to recover by treating it as a property anyway + this.lexer.skipWhitespace(); + } else { + this.lexer.skipWhitespace(); + } + } + + // Parse key + const keyStart = this.lexer.getCurrentPosition(); + let keyValue = ''; + + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') { + keyValue += this.lexer.advance(); + } + + keyValue = keyValue.trim(); + const keyEnd = this.lexer.getCurrentPosition(); + const key = createStringNode(keyValue, keyStart, keyEnd); + + // Check for duplicate keys + if (!this.options.allowDuplicateKeys && localKeysSeen.has(keyValue)) { + this.addError(`Duplicate key '${keyValue}'`, 'duplicateKey', keyStart, keyEnd); + } + localKeysSeen.add(keyValue); + + // Expect colon + if (this.lexer.getCurrentChar() === ':') { + this.lexer.advance(); + } + + this.lexer.skipWhitespace(); + + // Determine if value is on same line or next line(s) + let value: YamlNode; + const valueStart = this.lexer.getCurrentPosition(); + + if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') { + // Value is on next line(s) or empty + this.lexer.advanceLine(); + + // Check next line for nested content + if (!this.lexer.isAtEnd()) { + const nextIndent = this.lexer.getIndentation(); + + if (nextIndent > currentIndent) { + // Nested content - determine if it's an object or array + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + value = this.parseBlockArray(nextIndent); + } else { + value = this.parseBlockObject(nextIndent); + } + } else if (!fromCurrentPosition && nextIndent === currentIndent) { + // Same indentation level - check if it's an array item + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + value = this.parseBlockArray(currentIndent); + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + // Value is on the same line + value = this.parseValue(); + + // Skip any remaining content on this line (comments, etc.) + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') { + if (isWhitespace(this.lexer.getCurrentChar())) { + this.lexer.advance(); + } else { + break; + } + } + + // Skip to end of line if we hit a comment + if (this.lexer.getCurrentChar() === '#') { + this.lexer.skipToEndOfLine(); + } + + // Move to next line for next iteration + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() === '') { + this.lexer.advanceLine(); + } + } + + properties.push({ key, value }); + } + + // Calculate the end position based on the last property + let end = start; + if (properties.length > 0) { + const lastProperty = properties[properties.length - 1]; + end = lastProperty.value.end; + } + + return createObjectNode(properties, start, end); + } + + parse(): YamlNode | undefined { + if (this.lexer.isAtEnd()) { + return undefined; + } + + this.lexer.moveToNextNonEmptyLine(); + + if (this.lexer.isAtEnd()) { + return undefined; + } + + // Determine the root structure type + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + // Check if this is an array item or a negative number + // Look at the character after the dash + const nextChar = this.lexer.peek(); + if (nextChar === ' ' || nextChar === '\t' || nextChar === '' || nextChar === '#') { + // It's an array item (dash followed by whitespace/end/comment) + return this.parseBlockArray(0); + } else { + // It's likely a negative number or other value, treat as single value + return this.parseValue(); + } + } else if (this.lexer.getCurrentChar() === '[') { + // Root is an inline array + return this.parseInlineArray(); + } else if (this.lexer.getCurrentChar() === '{') { + // Root is an inline object + return this.parseInlineObject(); + } else { + // Check if this looks like a key-value pair by looking for a colon + // For single values, there shouldn't be a colon + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + // Check if there's a colon that's not inside quotes + let hasColon = false; + let inQuotes = false; + let quoteChar = ''; + + for (let i = 0; i < remainingLine.length; i++) { + const char = remainingLine[i]; + + if (!inQuotes && (char === '"' || char === `'`)) { + inQuotes = true; + quoteChar = char; + } else if (inQuotes && char === quoteChar) { + inQuotes = false; + quoteChar = ''; + } else if (!inQuotes && char === ':') { + hasColon = true; + break; + } else if (!inQuotes && char === '#') { + // Comment starts, stop looking + break; + } + } + + if (hasColon) { + // Root is an object + return this.parseBlockObject(0); + } else { + // Root is a single value + return this.parseValue(); + } + } + } +} + + diff --git a/src/vs/base/test/common/yaml.test.ts b/src/vs/base/test/common/yaml.test.ts new file mode 100644 index 00000000000..49ddbd2596f --- /dev/null +++ b/src/vs/base/test/common/yaml.test.ts @@ -0,0 +1,1083 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { deepStrictEqual, strictEqual, ok } from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { parse, ParseOptions, YamlParseError, Position, YamlNode } from '../../common/yaml.js'; + + +function assertValidParse(input: string[], expected: YamlNode, expectedErrors: YamlParseError[], options?: ParseOptions): void { + const errors: YamlParseError[] = []; + const actual1 = parse(input, errors, options); + deepStrictEqual(actual1, expected); + deepStrictEqual(errors, expectedErrors); +} + +function pos(line: number, character: number): Position { + return { line, character }; +} + +suite('YAML Parser', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('scalars', () => { + + test('numbers', () => { + assertValidParse(['1'], { type: 'number', start: pos(0, 0), end: pos(0, 1), value: 1 }, []); + assertValidParse(['1.234'], { type: 'number', start: pos(0, 0), end: pos(0, 5), value: 1.234 }, []); + assertValidParse(['-42'], { type: 'number', start: pos(0, 0), end: pos(0, 3), value: -42 }, []); + }); + + test('boolean', () => { + assertValidParse(['true'], { type: 'boolean', start: pos(0, 0), end: pos(0, 4), value: true }, []); + assertValidParse(['false'], { type: 'boolean', start: pos(0, 0), end: pos(0, 5), value: false }, []); + }); + + test('null', () => { + assertValidParse(['null'], { type: 'null', start: pos(0, 0), end: pos(0, 4), value: null }, []); + assertValidParse(['~'], { type: 'null', start: pos(0, 0), end: pos(0, 1), value: null }, []); + }); + + test('string', () => { + assertValidParse(['A Developer'], { type: 'string', start: pos(0, 0), end: pos(0, 11), value: 'A Developer' }, []); + assertValidParse(['\'A Developer\''], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); + assertValidParse(['"A Developer"'], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); + }); + }); + + suite('objects', () => { + + test('simple properties', () => { + assertValidParse(['name: John Doe'], { + type: 'object', start: pos(0, 0), end: pos(0, 14), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 14), value: 'John Doe' } + } + ] + }, []); + assertValidParse(['age: 30'], { + type: 'object', start: pos(0, 0), end: pos(0, 7), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'age' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 7), value: 30 } + } + ] + }, []); + assertValidParse(['active: true'], { + type: 'object', start: pos(0, 0), end: pos(0, 12), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'active' }, + value: { type: 'boolean', start: pos(0, 8), end: pos(0, 12), value: true } + } + ] + }, []); + assertValidParse(['value: null'], { + type: 'object', start: pos(0, 0), end: pos(0, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'value' }, + value: { type: 'null', start: pos(0, 7), end: pos(0, 11), value: null } + } + ] + }, []); + }); + + test('multiple properties', () => { + assertValidParse( + [ + 'name: John Doe', + 'age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 7), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 14), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'age' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 7), value: 30 } + } + ] + }, + [] + ); + }); + + test('nested object', () => { + assertValidParse( + [ + 'person:', + ' name: John Doe', + ' age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'person' }, + value: { + type: 'object', start: pos(1, 2), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 16), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + } + ] + } + } + ] + + }, + [] + ); + }); + + + test('nested objects with address', () => { + assertValidParse( + [ + 'person:', + ' name: John Doe', + ' age: 30', + ' address:', + ' street: 123 Main St', + ' city: Example City' + ], + { + type: 'object', start: pos(0, 0), end: pos(5, 22), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'person' }, + value: { + type: 'object', start: pos(1, 2), end: pos(5, 22), + properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 16), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + }, + { + key: { type: 'string', start: pos(3, 2), end: pos(3, 9), value: 'address' }, + value: { + type: 'object', start: pos(4, 4), end: pos(5, 22), properties: [ + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 10), value: 'street' }, + value: { type: 'string', start: pos(4, 12), end: pos(4, 23), value: '123 Main St' } + }, + { + key: { type: 'string', start: pos(5, 4), end: pos(5, 8), value: 'city' }, + value: { type: 'string', start: pos(5, 10), end: pos(5, 22), value: 'Example City' } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('properties without space after colon', () => { + assertValidParse( + ['name:John'], + { + type: 'object', start: pos(0, 0), end: pos(0, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 9), value: 'John' } + } + ] + }, + [] + ); + + // Test mixed: some properties with space, some without + assertValidParse( + [ + 'config:', + ' database:', + ' host:localhost', + ' port: 5432', + ' credentials:', + ' username:admin', + ' password: secret123' + ], + { + type: 'object', start: pos(0, 0), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'config' }, + value: { + type: 'object', start: pos(1, 2), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 10), value: 'database' }, + value: { + type: 'object', start: pos(2, 4), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 8), value: 'host' }, + value: { type: 'string', start: pos(2, 9), end: pos(2, 18), value: 'localhost' } + }, + { + key: { type: 'string', start: pos(3, 4), end: pos(3, 8), value: 'port' }, + value: { type: 'number', start: pos(3, 10), end: pos(3, 14), value: 5432 } + }, + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 15), value: 'credentials' }, + value: { + type: 'object', start: pos(5, 6), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(5, 6), end: pos(5, 14), value: 'username' }, + value: { type: 'string', start: pos(5, 15), end: pos(5, 20), value: 'admin' } + }, + { + key: { type: 'string', start: pos(6, 6), end: pos(6, 14), value: 'password' }, + value: { type: 'string', start: pos(6, 16), end: pos(6, 25), value: 'secret123' } + } + ] + } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('inline objects', () => { + assertValidParse( + ['{name: John, age: 30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 21), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 5), value: 'name' }, + value: { type: 'string', start: pos(0, 7), end: pos(0, 11), value: 'John' } + }, + { + key: { type: 'string', start: pos(0, 13), end: pos(0, 16), value: 'age' }, + value: { type: 'number', start: pos(0, 18), end: pos(0, 20), value: 30 } + } + ] + }, + [] + ); + + // Test with different data types + assertValidParse( + ['{active: true, score: 85.5, role: null}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 39), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 7), value: 'active' }, + value: { type: 'boolean', start: pos(0, 9), end: pos(0, 13), value: true } + }, + { + key: { type: 'string', start: pos(0, 15), end: pos(0, 20), value: 'score' }, + value: { type: 'number', start: pos(0, 22), end: pos(0, 26), value: 85.5 } + }, + { + key: { type: 'string', start: pos(0, 28), end: pos(0, 32), value: 'role' }, + value: { type: 'null', start: pos(0, 34), end: pos(0, 38), value: null } + } + ] + }, + [] + ); + + // Test empty inline object + assertValidParse( + ['{}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 2), properties: [] + }, + [] + ); + + // Test inline object with quoted keys and values + assertValidParse( + ['{"name": "John Doe", "age": 30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 31), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 7), value: 'name' }, + value: { type: 'string', start: pos(0, 9), end: pos(0, 19), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(0, 21), end: pos(0, 26), value: 'age' }, + value: { type: 'number', start: pos(0, 28), end: pos(0, 30), value: 30 } + } + ] + }, + [] + ); + + // Test inline object without spaces + assertValidParse( + ['{name:John,age:30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 18), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 5), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 10), value: 'John' } + }, + { + key: { type: 'string', start: pos(0, 11), end: pos(0, 14), value: 'age' }, + value: { type: 'number', start: pos(0, 15), end: pos(0, 17), value: 30 } + } + ] + }, + [] + ); + }); + + test('special characters in values', () => { + // Test values with special characters + assertValidParse( + [`key: value with \t special chars`], + { + type: 'object', start: pos(0, 0), end: pos(0, 31), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 31), value: `value with \t special chars` } + } + ] + }, + [] + ); + }); + + test('various whitespace types', () => { + // Test different types of whitespace + assertValidParse( + [`key:\t \t \t value`], + { + type: 'object', start: pos(0, 0), end: pos(0, 15), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 10), end: pos(0, 15), value: 'value' } + } + ] + }, + [] + ); + }); + }); + + suite('arrays', () => { + + + test('arrays', () => { + assertValidParse( + [ + '- Boston Red Sox', + '- Detroit Tigers', + '- New York Yankees' + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 18), items: [ + { type: 'string', start: pos(0, 2), end: pos(0, 16), value: 'Boston Red Sox' }, + { type: 'string', start: pos(1, 2), end: pos(1, 16), value: 'Detroit Tigers' }, + { type: 'string', start: pos(2, 2), end: pos(2, 18), value: 'New York Yankees' } + ] + + }, + [] + ); + }); + + + test('inline arrays', () => { + assertValidParse( + ['[Apple, Banana, Cherry]'], + { + type: 'array', start: pos(0, 0), end: pos(0, 23), items: [ + { type: 'string', start: pos(0, 1), end: pos(0, 6), value: 'Apple' }, + { type: 'string', start: pos(0, 8), end: pos(0, 14), value: 'Banana' }, + { type: 'string', start: pos(0, 16), end: pos(0, 22), value: 'Cherry' } + ] + + }, + [] + ); + }); + + test('multi-line inline arrays', () => { + assertValidParse( + [ + '[', + ' geen, ', + ' yello, red]' + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 15), items: [ + { type: 'string', start: pos(1, 4), end: pos(1, 8), value: 'geen' }, + { type: 'string', start: pos(2, 4), end: pos(2, 9), value: 'yello' }, + { type: 'string', start: pos(2, 11), end: pos(2, 14), value: 'red' } + ] + }, + [] + ); + }); + + test('arrays of arrays', () => { + assertValidParse( + [ + '-', + ' - Apple', + ' - Banana', + ' - Cherry' + ], + { + type: 'array', start: pos(0, 0), end: pos(3, 10), items: [ + { + type: 'array', start: pos(1, 2), end: pos(3, 10), items: [ + { type: 'string', start: pos(1, 4), end: pos(1, 9), value: 'Apple' }, + { type: 'string', start: pos(2, 4), end: pos(2, 10), value: 'Banana' }, + { type: 'string', start: pos(3, 4), end: pos(3, 10), value: 'Cherry' } + ] + } + ] + }, + [] + ); + }); + + test('inline arrays of inline arrays', () => { + assertValidParse( + [ + '[', + ' [ee], [ff, gg]', + ']', + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 1), items: [ + { + type: 'array', start: pos(1, 2), end: pos(1, 6), items: [ + { type: 'string', start: pos(1, 3), end: pos(1, 5), value: 'ee' }, + ], + }, + { + type: 'array', start: pos(1, 8), end: pos(1, 16), items: [ + { type: 'string', start: pos(1, 9), end: pos(1, 11), value: 'ff' }, + { type: 'string', start: pos(1, 13), end: pos(1, 15), value: 'gg' }, + ], + } + ] + }, + [] + ); + }); + + test('object with array containing single object', () => { + assertValidParse( + [ + 'items:', + '- name: John', + ' age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'items' }, + value: { + type: 'array', start: pos(1, 0), end: pos(2, 9), items: [ + { + type: 'object', start: pos(1, 2), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 12), value: 'John' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + } + ] + } + ] + } + } + ] + }, + [] + ); + }); + + test('arrays of objects', () => { + assertValidParse( + [ + '-', + ' name: one', + '- name: two', + '-', + ' name: three' + ], + { + type: 'array', start: pos(0, 0), end: pos(4, 13), items: [ + { + type: 'object', start: pos(1, 2), end: pos(1, 11), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 11), value: 'one' } + } + ] + }, + { + type: 'object', start: pos(2, 2), end: pos(2, 11), properties: [ + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 6), value: 'name' }, + value: { type: 'string', start: pos(2, 8), end: pos(2, 11), value: 'two' } + } + ] + }, + { + type: 'object', start: pos(4, 2), end: pos(4, 13), properties: [ + { + key: { type: 'string', start: pos(4, 2), end: pos(4, 6), value: 'name' }, + value: { type: 'string', start: pos(4, 8), end: pos(4, 13), value: 'three' } + } + ] + } + ] + }, + [] + ); + }); + }); + + suite('complex structures', () => { + + test('array of objects', () => { + assertValidParse( + [ + 'products:', + ' - name: Laptop', + ' price: 999.99', + ' in_stock: true', + ' - name: Mouse', + ' price: 25.50', + ' in_stock: false' + ], + { + type: 'object', start: pos(0, 0), end: pos(6, 19), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 8), value: 'products' }, + value: { + type: 'array', start: pos(1, 2), end: pos(6, 19), items: [ + { + type: 'object', start: pos(1, 4), end: pos(3, 18), properties: [ + { + key: { type: 'string', start: pos(1, 4), end: pos(1, 8), value: 'name' }, + value: { type: 'string', start: pos(1, 10), end: pos(1, 16), value: 'Laptop' } + }, + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 9), value: 'price' }, + value: { type: 'number', start: pos(2, 11), end: pos(2, 17), value: 999.99 } + }, + { + key: { type: 'string', start: pos(3, 4), end: pos(3, 12), value: 'in_stock' }, + value: { type: 'boolean', start: pos(3, 14), end: pos(3, 18), value: true } + } + ] + }, + { + type: 'object', start: pos(4, 4), end: pos(6, 19), properties: [ + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 8), value: 'name' }, + value: { type: 'string', start: pos(4, 10), end: pos(4, 15), value: 'Mouse' } + }, + { + key: { type: 'string', start: pos(5, 4), end: pos(5, 9), value: 'price' }, + value: { type: 'number', start: pos(5, 11), end: pos(5, 16), value: 25.50 } + }, + { + key: { type: 'string', start: pos(6, 4), end: pos(6, 12), value: 'in_stock' }, + value: { type: 'boolean', start: pos(6, 14), end: pos(6, 19), value: false } + } + ] + } + ] + } + } + ] + }, + [] + ); + }); + + test('inline array mixed primitives', () => { + assertValidParse( + ['vals: [1, true, null, "str"]'], + { + type: 'object', start: pos(0, 0), end: pos(0, 28), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'vals' }, + value: { + type: 'array', start: pos(0, 6), end: pos(0, 28), items: [ + { type: 'number', start: pos(0, 7), end: pos(0, 8), value: 1 }, + { type: 'boolean', start: pos(0, 10), end: pos(0, 14), value: true }, + { type: 'null', start: pos(0, 16), end: pos(0, 20), value: null }, + { type: 'string', start: pos(0, 22), end: pos(0, 27), value: 'str' } + ] + } + } + ] + }, + [] + ); + }); + + test('mixed inline structures', () => { + assertValidParse( + ['config: {env: "prod", settings: [true, 42], debug: false}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 57), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'config' }, + value: { + type: 'object', start: pos(0, 8), end: pos(0, 57), properties: [ + { + key: { type: 'string', start: pos(0, 9), end: pos(0, 12), value: 'env' }, + value: { type: 'string', start: pos(0, 14), end: pos(0, 20), value: 'prod' } + }, + { + key: { type: 'string', start: pos(0, 22), end: pos(0, 30), value: 'settings' }, + value: { + type: 'array', start: pos(0, 32), end: pos(0, 42), items: [ + { type: 'boolean', start: pos(0, 33), end: pos(0, 37), value: true }, + { type: 'number', start: pos(0, 39), end: pos(0, 41), value: 42 } + ] + } + }, + { + key: { type: 'string', start: pos(0, 44), end: pos(0, 49), value: 'debug' }, + value: { type: 'boolean', start: pos(0, 51), end: pos(0, 56), value: false } + } + ] + } + } + ] + }, + [] + ); + }); + + test('with comments', () => { + assertValidParse( + [ + `# This is a comment`, + 'name: John Doe # inline comment', + 'age: 30' + ], + { + type: 'object', start: pos(1, 0), end: pos(2, 7), properties: [ + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'name' }, + value: { type: 'string', start: pos(1, 6), end: pos(1, 14), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 3), value: 'age' }, + value: { type: 'number', start: pos(2, 5), end: pos(2, 7), value: 30 } + } + ] + }, + [] + ); + }); + }); + + suite('edge cases and error handling', () => { + + + // Edge cases + test('duplicate keys error', () => { + assertValidParse( + [ + 'key: 1', + 'key: 2' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 6), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'key' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 6), value: 2 } + } + ] + }, + [ + { + message: "Duplicate key 'key'", + code: 'duplicateKey', + start: pos(1, 0), + end: pos(1, 3) + } + ] + ); + }); + + test('duplicate keys allowed with option', () => { + assertValidParse( + [ + 'key: 1', + 'key: 2' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 6), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'key' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 6), value: 2 } + } + ] + }, + [], + { allowDuplicateKeys: true } + ); + }); + + test('unexpected indentation error with recovery', () => { + // Parser reports error but still captures the over-indented property. + assertValidParse( + [ + 'key: 1', + ' stray: value' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 16), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 4), end: pos(1, 9), value: 'stray' }, + value: { type: 'string', start: pos(1, 11), end: pos(1, 16), value: 'value' } + } + ] + }, + [ + { + message: 'Unexpected indentation', + code: 'indentation', + start: pos(1, 0), + end: pos(1, 16) + } + ] + ); + }); + + test('empty values and inline empty array', () => { + assertValidParse( + [ + 'empty:', + 'array: []' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'empty' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 6), value: '' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 5), value: 'array' }, + value: { type: 'array', start: pos(1, 7), end: pos(1, 9), items: [] } + } + ] + }, + [] + ); + }); + + + + test('nested empty objects', () => { + // Parser should create nodes for both parent and child, with child having empty string value. + assertValidParse( + [ + 'parent:', + ' child:' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 8), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'parent' }, + value: { + type: 'object', start: pos(1, 2), end: pos(1, 8), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 7), value: 'child' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 8), value: '' } + } + ] + } + } + ] + }, + [] + ); + }); + + test('empty object with only colons', () => { + // Test object with empty values + assertValidParse( + ["key1:", "key2:", "key3:"], + { + type: 'object', start: pos(0, 0), end: pos(2, 5), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'key1' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 5), value: '' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'key2' }, + value: { type: 'string', start: pos(1, 5), end: pos(1, 5), value: '' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 4), value: 'key3' }, + value: { type: 'string', start: pos(2, 5), end: pos(2, 5), value: '' } + } + ] + }, + [] + ); + }); + + test('large input performance', () => { + // Test that large inputs are handled efficiently + const input = Array.from({ length: 1000 }, (_, i) => `key${i}: value${i}`); + const expectedProperties = Array.from({ length: 1000 }, (_, i) => ({ + key: { type: 'string' as const, start: pos(i, 0), end: pos(i, `key${i}`.length), value: `key${i}` }, + value: { type: 'string' as const, start: pos(i, `key${i}: `.length), end: pos(i, `key${i}: value${i}`.length), value: `value${i}` } + })); + + const start = Date.now(); + assertValidParse( + input, + { + type: 'object', + start: pos(0, 0), + end: pos(999, 'key999: value999'.length), + properties: expectedProperties + }, + [] + ); + const duration = Date.now() - start; + + ok(duration < 100, `Parsing took ${duration}ms, expected < 100ms`); + }); + + test('deeply nested structure performance', () => { + // Test that deeply nested structures are handled efficiently + const lines = []; + for (let i = 0; i < 50; i++) { + const indent = ' '.repeat(i); + lines.push(`${indent}level${i}:`); + } + lines.push(' '.repeat(50) + 'deepValue: reached'); + + const start = Date.now(); + const errors: YamlParseError[] = []; + const result = parse(lines, errors); + const duration = Date.now() - start; + + ok(result); + strictEqual(result.type, 'object'); + strictEqual(errors.length, 0); + ok(duration < 100, `Parsing took ${duration}ms, expected < 100ms`); + }); + + test('malformed array with position issues', () => { + // Test malformed arrays that might cause position advancement issues + assertValidParse( + [ + "key: [", + "", + "", + "", + "" + ], + { + type: 'object', start: pos(0, 0), end: pos(5, 0), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'array', start: pos(0, 5), end: pos(5, 0), items: [] } + } + ] + }, + [] + ); + }); + + test('self-referential like structure', () => { + // Test structures that might appear self-referential + assertValidParse( + [ + "a:", + " b:", + " a:", + " b:", + " value: test" + ], + { + type: 'object', start: pos(0, 0), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 1), value: 'a' }, + value: { + type: 'object', start: pos(1, 2), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 3), value: 'b' }, + value: { + type: 'object', start: pos(2, 4), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 5), value: 'a' }, + value: { + type: 'object', start: pos(3, 6), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(3, 6), end: pos(3, 7), value: 'b' }, + value: { + type: 'object', start: pos(4, 8), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(4, 8), end: pos(4, 13), value: 'value' }, + value: { type: 'string', start: pos(4, 15), end: pos(4, 19), value: 'test' } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('array with empty lines', () => { + // Test arrays spanning multiple lines with empty lines + assertValidParse( + ["arr: [", "", "item1,", "", "item2", "", "]"], + { + type: 'object', start: pos(0, 0), end: pos(6, 1), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'arr' }, + value: { + type: 'array', start: pos(0, 5), end: pos(6, 1), items: [ + { type: 'string', start: pos(2, 0), end: pos(2, 5), value: 'item1' }, + { type: 'string', start: pos(4, 0), end: pos(4, 5), value: 'item2' } + ] + } + } + ] + }, + [] + ); + }); + + test('whitespace advancement robustness', () => { + // Test that whitespace advancement works correctly + assertValidParse( + [`key: value`], + { + type: 'object', start: pos(0, 0), end: pos(0, 15), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 10), end: pos(0, 15), value: 'value' } + } + ] + }, + [] + ); + }); + + + test('missing end quote in string values', () => { + // Test unclosed double quote - parser treats it as bare string with quote included + assertValidParse( + ['name: "John'], + { + type: 'object', start: pos(0, 0), end: pos(0, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 11), value: 'John' } + } + ] + }, + [] + ); + + // Test unclosed single quote - parser treats it as bare string with quote included + assertValidParse( + ['description: \'Hello world'], + { + type: 'object', start: pos(0, 0), end: pos(0, 25), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 11), value: 'description' }, + value: { type: 'string', start: pos(0, 13), end: pos(0, 25), value: 'Hello world' } + } + ] + }, + [] + ); + + // Test unclosed quote in multi-line context + assertValidParse( + [ + 'data: "incomplete', + 'next: value' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'data' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 17), value: 'incomplete' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'next' }, + value: { type: 'string', start: pos(1, 6), end: pos(1, 11), value: 'value' } + } + ] + }, + [] + ); + + // Test properly quoted strings for comparison + assertValidParse( + ['name: "John"'], + { + type: 'object', start: pos(0, 0), end: pos(0, 12), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 12), value: 'John' } + } + ] + }, + [] + ); + }); + + + }); + +}); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts new file mode 100644 index 00000000000..15c2ec6a24b --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { splitLines } from '../../../../../../base/common/strings.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { parse, YamlNode, YamlParseError } from '../../../../../../base/common/yaml.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { IPromptParserResult } from './promptsService.js'; + +export class NewPromptsParser { + constructor( + private readonly modelService: IModelService, + private readonly fileService: IFileService, + ) { + // TODO + } + + public async parse(uri: URI): Promise { + const content = await this.getContents(uri); + if (!content) { + return; + } + const lines = splitLines(content); + if (lines.length === 0) { + return createResult(uri, undefined, []); + } + let header: PromptHeader | undefined = undefined; + let body: { references: URI[] } | undefined = undefined; + let bodyStart = 0; + if (lines[0] === '---') { + let headerEnd = lines.indexOf('---', 1); + if (headerEnd === -1) { + headerEnd = lines.length; + bodyStart = lines.length; + } else { + bodyStart = headerEnd + 1; + } + header = this.parseHeader(lines.slice(1, headerEnd !== -1 ? headerEnd : lines.length)); + } + if (bodyStart < lines.length) { + body = this.parseBody(lines.slice(bodyStart)); + } + return createResult(uri, header, body?.references ?? []); + } + + private parseBody(lines: string[]): { references: URI[] } { + const references: URI[] = []; + for (const line of lines) { + const match = line.match(/\[(.+?)\]\((.+?)\)/); + if (match) { + const [, _text, uri] = match; + references.push(URI.file(uri)); + } + } + return { references }; + } + + private parseHeader(lines: string[]): PromptHeader { + const errors: YamlParseError[] = []; + const node = parse(lines, errors); + return new PromptHeader(node, errors); + } + + private async getContents(uri: URI): Promise { + const model = this.modelService.getModel(uri); + if (model) { + return model.getValue(); + } + const content = await this.fileService.readFile(uri); + if (content) { + return content.value.toString(); + } + return undefined; + } +} + +function createResult(uri: URI, header: PromptHeader | undefined, references: URI[]): IPromptParserResult { + return { + uri, + header, + references, + metadata: null + }; +} + +export class PromptHeader { + constructor(public readonly node: YamlNode | undefined, public readonly errors: YamlParseError[]) { + + } +} + diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 577a9e4d1df..21709075896 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -13,7 +13,7 @@ import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { ITopError } from '../parsers/types.js'; +import { YamlNode, YamlParseError } from '../../../../../../base/common/yaml.js'; /** * Provides prompt services. @@ -217,6 +217,12 @@ export interface IChatPromptSlashCommand { export interface IPromptParserResult { readonly uri: URI; readonly metadata: TMetadata | null; - readonly topError: ITopError | undefined; readonly references: readonly URI[]; + readonly header?: IPromptHeader; } + +export interface IPromptHeader { + readonly node: YamlNode | undefined; + readonly errors: YamlParseError[]; +} + diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 662aa6673b6..746e9d52b75 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -270,8 +270,8 @@ export class PromptsService extends Disposable implements IPromptsService { return { uri: parser.uri, metadata: parser.metadata, - topError: parser.topError, - references: parser.references.map(ref => ref.uri) + references: parser.references.map(ref => ref.uri), + header: undefined }; } finally { parser?.dispose(); From f999ab90e129f11afae946b258b79c8173252a0a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 28 Aug 2025 17:05:21 +0200 Subject: [PATCH 0011/4355] fix tests --- .../common/promptSyntax/service/promptsService.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index f8db1ba18f0..7bba2c0cfbc 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -679,7 +679,7 @@ suite('PromptsService', () => { tools: ['my-tool1'], mode: 'agent', }, - topError: undefined, + header: undefined, references: [file3, file4] }); @@ -690,7 +690,7 @@ suite('PromptsService', () => { promptType: PromptsType.prompt, mode: 'edit', }, - topError: undefined, + header: undefined, references: [nonExistingFolder, yetAnotherFile] }); @@ -702,7 +702,7 @@ suite('PromptsService', () => { description: 'Another file description.', applyTo: '**/*.tsx', }, - topError: undefined, + header: undefined, references: [someOtherFolder, someOtherFolderFile] }); @@ -713,7 +713,7 @@ suite('PromptsService', () => { promptType: PromptsType.instructions, description: 'File 4 splendid description.', }, - topError: undefined, + header: undefined, references: [ URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-existing/file.prompt.md'), URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-prompt-file.md'), From dc2284595a2856657d037743af769f5223f1cc96 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 29 Aug 2025 09:46:02 -0700 Subject: [PATCH 0012/4355] Avoid extra HTML re-parse when rendering markdown For #263896 Uses the dom fragment from dompurify instead of converting to a string and then reparsing back to dom --- src/vs/base/browser/domSanitize.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index fe392c36452..084a5d1edf2 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -5,6 +5,7 @@ import { DisposableStore, IDisposable, toDisposable } from '../common/lifecycle.js'; import { Schemas } from '../common/network.js'; +import { reset } from './dom.js'; import dompurify from './dompurify/dompurify.js'; @@ -207,9 +208,6 @@ export interface DomSanitizerConfig { const defaultDomPurifyConfig = Object.freeze({ ALLOWED_TAGS: [...basicMarkupHtmlTags], ALLOWED_ATTR: [...defaultAllowedAttrs], - RETURN_DOM: false, - RETURN_DOM_FRAGMENT: false, - RETURN_TRUSTED_TYPE: true, // We sanitize the src/href attributes later if needed ALLOW_UNKNOWN_PROTOCOLS: true, } satisfies dompurify.Config); @@ -223,6 +221,12 @@ const defaultDomPurifyConfig = Object.freeze({ * @returns A sanitized string of html. */ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): TrustedHTML { + return doSanitizeHtml(untrusted, config, 'trusted'); +} + +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom'): DocumentFragment; +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'trusted'): TrustedHTML; +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom' | 'trusted'): TrustedHTML | DocumentFragment { const store = new DisposableStore(); try { const resolvedConfig: dompurify.Config = { ...defaultDomPurifyConfig }; @@ -286,10 +290,17 @@ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): Tr })); } - return dompurify.sanitize(untrusted, { - ...resolvedConfig, - RETURN_TRUSTED_TYPE: true - }); + if (outputType === 'dom') { + return dompurify.sanitize(untrusted, { + ...resolvedConfig, + RETURN_DOM_FRAGMENT: true + }); + } else { + return dompurify.sanitize(untrusted, { + ...resolvedConfig, + RETURN_TRUSTED_TYPE: true + }); + } } finally { store.dispose(); } @@ -349,5 +360,6 @@ export function convertTagToPlaintext(element: Element): DocumentFragment { * Sanitizes the given `value` and reset the given `node` with it. */ export function safeSetInnerHtml(node: HTMLElement, untrusted: string, config?: DomSanitizerConfig): void { - node.innerHTML = sanitizeHtml(untrusted, config) as any; + const fragment = doSanitizeHtml(untrusted, config, 'dom'); + reset(node, fragment); } From 02e2e9b79ca9415be98503719f99b24b19ca3aad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 29 Aug 2025 09:51:37 -0700 Subject: [PATCH 0013/4355] Return trusted type from renderMarkdownDocument --- .../workbench/contrib/extensions/browser/extensionEditor.ts | 2 +- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 4 ++-- src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts | 2 +- src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts | 1 + .../browser/gettingStartedDetailsRenderer.ts | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index d864fa25d1f..6d1c0ff6195 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -771,7 +771,7 @@ export class ExtensionEditor extends EditorPane { return this.renderBody(content); } - private renderBody(body: string): string { + private renderBody(body: TrustedHTML): string { const nonce = generateUuid(); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 5fa2ec286e5..bc64ac4f07d 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -215,7 +215,7 @@ export async function renderMarkdownDocument( languageService: ILanguageService, options?: IRenderMarkdownDocumentOptions, token: CancellationToken = CancellationToken.None, -): Promise { +): Promise { const m = new marked.Marked( MarkedHighlight.markedHighlight({ async: true, @@ -238,7 +238,7 @@ export async function renderMarkdownDocument( ); const raw = await raceCancellationError(m.parse(text, { async: true }), token ?? CancellationToken.None); - return sanitize(raw, options?.sanitizerConfig) as any as string; + return sanitize(raw, options?.sanitizerConfig); } namespace MarkedHighlight { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index e152fb0d30f..6436865013b 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -531,7 +531,7 @@ export class McpServerEditor extends EditorPane { return this.renderBody(content); } - private renderBody(body: string): string { + private renderBody(body: TrustedHTML): string { const nonce = generateUuid(); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index ad12dd9e486..699045fc6fd 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -280,6 +280,7 @@ export class ReleaseNotesManager extends Disposable { // Remove HTML comment markers around table of contents navigation const processedContent = content + .toString() .replace(//gi, ''); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts index 01b1bc3672b..f550eb63611 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts @@ -21,7 +21,7 @@ import { gettingStartedContentRegistry } from '../common/gettingStartedContent.j export class GettingStartedDetailsRenderer { - private mdCache = new ResourceMap(); + private mdCache = new ResourceMap(); private svgCache = new ResourceMap(); constructor( @@ -233,7 +233,7 @@ export class GettingStartedDetailsRenderer { return assertReturnsDefined(this.svgCache.get(path)); } - private async readAndCacheStepMarkdown(path: URI, base: URI): Promise { + private async readAndCacheStepMarkdown(path: URI, base: URI): Promise { if (!this.mdCache.has(path)) { const contents = await this.readContentsOfPath(path); const markdownContents = await renderMarkdownDocument(transformUris(contents, base), this.extensionService, this.languageService, { From d9ee885f62c78a9fccee719752e9a54c4139b781 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 29 Aug 2025 10:20:41 -0700 Subject: [PATCH 0014/4355] Fix chat rendering tests too The call to `normalize` makes sure neighboring text nodes are merged. This matches the old behavior when `innerHtml` was used --- src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index 408e8ffa5bd..6e56855f3d3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -100,6 +100,7 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { // In some cases, the renderer can return text that is not inside a

, // but our CSS expects text to be in a

for margin to be applied properly. // So just normalize it. + result.element.normalize(); const lastChild = result.element.lastChild; if (lastChild?.nodeType === Node.TEXT_NODE && lastChild.textContent?.trim()) { lastChild.replaceWith($('p', undefined, lastChild.textContent)); From 5a0814a67d8120e525b198f400c5e6e2ef0c2897 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Fri, 29 Aug 2025 23:30:45 +0200 Subject: [PATCH 0015/4355] fix: memory leak in inline hints --- .../contrib/inlayHints/browser/inlayHintsController.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 8970884331e..3d641751c5b 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -8,7 +8,7 @@ import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { disposableTimeout, RunOnceScheduler } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; -import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { IRange } from '../../../../base/common/range.js'; import { assertType } from '../../../../base/common/types.js'; @@ -206,12 +206,13 @@ export class InlayHintsController implements IEditorContribution { let cts: CancellationTokenSource | undefined; const watchedProviders = new Set(); + this._sessionDisposables.add(model.onWillDispose(() => cts?.cancel())); + const inlayHintsDisposable = this._sessionDisposables.add(new MutableDisposable()) const scheduler = new RunOnceScheduler(async () => { const t1 = Date.now(); cts?.dispose(true); cts = new CancellationTokenSource(); - const listener = model.onWillDispose(() => cts?.cancel()); try { const myToken = cts.token; @@ -233,17 +234,14 @@ export class InlayHintsController implements IEditorContribution { })); } } - - this._sessionDisposables.add(inlayHints); + inlayHintsDisposable.value = inlayHints; this._updateHintsDecorators(inlayHints.ranges, inlayHints.items); this._cacheHintsForFastRestore(model); } catch (err) { onUnexpectedError(err); - } finally { cts.dispose(); - listener.dispose(); } }, this._debounceInfo.get(model)); From b46d5828f0d1c23509f9f415a41183da9ccb49a2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 31 Aug 2025 15:51:19 +0200 Subject: [PATCH 0016/4355] tool references --- .../api/common/extHostTypeConverters.ts | 20 ++++++++++++++++--- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/common/chatAgents.ts | 8 +++++++- .../contrib/chat/common/chatModel.ts | 4 ++-- .../contrib/chat/common/chatModes.ts | 1 + .../chat/common/chatVariableEntries.ts | 2 ++ ...ode.proposed.chatParticipantAdditions.d.ts | 12 ++++++++++- 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index d8fb60663c8..7c825d2711b 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -39,10 +39,10 @@ import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from '../ import { ProgressLocation as MainProgressLocation } from '../../../platform/progress/common/progress.js'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; -import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; +import { IChatAgentRequest, IChatAgentResult, IChatModeInstructions } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatMultiDiffData, IChatPrepareToolInvocationPart, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatThinkingPart, IChatToolInvocationSerialized, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; -import { IChatRequestVariableEntry, isImageVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, isImageVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { IToolResult, IToolResultInputOutputDetails, IToolResultOutputDetails, ToolDataSource } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; @@ -3123,7 +3123,7 @@ export namespace ChatAgentRequest { tools, model, editedFileEvents: request.editedFileEvents, - modeInstructions: request.modeInstructions, + modeInstructions: request.modeInstructions ? ChatModeInstructions.to(request.modeInstructions) : undefined, }; if (!isProposedApiEnabled(extension, 'chatParticipantPrivate')) { @@ -3147,6 +3147,15 @@ export namespace ChatAgentRequest { } } +export namespace ChatModeInstructions { + export function to(instructions: IChatModeInstructions): vscode.ChatModeInstructions { + return { + content: instructions.content, + toolReferences: instructions.toolReferences?.map(ref => ChatLanguageModelToolReference.to(ref)) + }; + } +} + export namespace ChatRequestDraft { export function to(request: IChatRequestDraft): vscode.ChatRequestDraft { return { @@ -3222,11 +3231,16 @@ export namespace ChatPromptReference { })]; }).filter(([, d]) => d.length > 0)); } + let toolReferences; + if (isPromptFileVariableEntry(variable) || isPromptTextVariableEntry(variable)) { + toolReferences = variable.toolReferences?.map(ref => ChatLanguageModelToolReference.to(ref)); + } return { id: variable.id, name: variable.name, range: variable.range && [variable.range.start, variable.range.endExclusive], + toolReferences, value, modelDescription: variable.modelDescription, }; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 37b4ce5606e..8f1eb3dae26 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -317,7 +317,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return { kind: this.currentModeKind, isBuiltin: mode.isBuiltin, - instructions: mode.body?.get(), + instructions: mode.body ? { content: mode.body.get() } : undefined, modeId: modeId, applyCodeBlockSuggestionId: undefined, }; diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 1b6d4b41bc4..e2b5137ce3e 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -28,6 +28,7 @@ import { ChatContextKeys } from './chatContextKeys.js'; import { IChatAgentEditedFileEvent, IChatProgressHistoryResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from './chatModel.js'; import { IRawChatCommandContribution } from './chatParticipantContribTypes.js'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from './chatService.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; //#region agent service, commands etc @@ -146,10 +147,15 @@ export interface IChatAgentRequest { rejectedConfirmationData?: any[]; userSelectedModelId?: string; userSelectedTools?: UserSelectedTools; - modeInstructions?: string; + modeInstructions?: IChatModeInstructions; editedFileEvents?: IChatAgentEditedFileEvent[]; } +export interface IChatModeInstructions { + content: string; + readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; +} + export interface IChatQuestion { readonly prompt: string; readonly participant?: string; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index af5fbeb0eb0..f79a0c167e7 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -22,7 +22,7 @@ import { TextEdit } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, IChatModeInstructions, reviveSerializedAgent } from './chatAgents.js'; import { migrateLegacyTerminalToolSpecificData } from './chat.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; @@ -214,7 +214,7 @@ const defaultChatResponseModelChangeReason: ChatResponseModelChangeReason = { re export interface IChatRequestModeInfo { kind: ChatModeKind | undefined; // is undefined in case of modeId == 'apply' isBuiltin: boolean; - instructions: string | undefined; + instructions: IChatModeInstructions | undefined; modeId: 'ask' | 'agent' | 'edit' | 'custom' | 'applyCodeBlock' | undefined; applyCodeBlockSuggestionId: EditSuggestionId | undefined; } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 72fb4d23a41..ed6577e08bb 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -212,6 +212,7 @@ export interface IChatMode { readonly customTools?: IObservable; readonly model?: IObservable; readonly body?: IObservable; + readonly toolReferences?: IObservable; readonly uri?: IObservable; } diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 3338b55f25c..052ff326db6 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -188,6 +188,7 @@ export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry readonly originLabel?: string; readonly modelDescription: string; readonly automaticallyAdded: boolean; + readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; } export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry { @@ -196,6 +197,7 @@ export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry readonly settingId?: string; readonly modelDescription: string; readonly automaticallyAdded: boolean; + readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; } export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index a07171f5111..4788f2f0ae8 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -590,6 +590,11 @@ declare module 'vscode' { * TODO Needed for now to drive the variableName-type reference, but probably both of these should go away in the future. */ readonly name: string; + + /** + * The list of tools were referenced in the value of the reference + */ + readonly toolReferences?: readonly ChatLanguageModelToolReference[]; } export interface ChatResultFeedback { @@ -631,6 +636,11 @@ declare module 'vscode' { } export interface ChatRequest { - modeInstructions?: string; + modeInstructions?: ChatModeInstructions; + } + + export interface ChatModeInstructions { + readonly content: string; + readonly toolReferences?: readonly ChatLanguageModelToolReference[]; } } From c647c1cfa2c2a4b1bbf12cb4db413fe05a0d9044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Sun, 31 Aug 2025 16:45:49 +0100 Subject: [PATCH 0017/4355] Fix localization for terminal.hoverHighlightBackground --- .../workbench/contrib/terminal/common/terminalColorRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 267cb5ef998..88a5ef4f699 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -65,7 +65,7 @@ export const TERMINAL_FIND_MATCH_BACKGROUND_COLOR = registerColor('terminal.find hcDark: null, hcLight: '#0F4A85' }, nls.localize('terminal.findMatchBackground', 'Color of the current search match in the terminal. The color must not be opaque so as not to hide underlying terminal content.'), true); -export const TERMINAL_HOVER_HIGHLIGHT_BACKGROUND_COLOR = registerColor('terminal.hoverHighlightBackground', transparent(editorHoverHighlight, 0.5), nls.localize('terminal.findMatchHighlightBorder', 'Border color of the other search matches in the terminal.')); +export const TERMINAL_HOVER_HIGHLIGHT_BACKGROUND_COLOR = registerColor('terminal.hoverHighlightBackground', transparent(editorHoverHighlight, 0.5), nls.localize('terminal.hoverHighlightBackground', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.')); export const TERMINAL_FIND_MATCH_BORDER_COLOR = registerColor('terminal.findMatchBorder', { dark: null, light: null, From 5eec5726c27317ae736317948fb24a1e2bd172f5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 1 Sep 2025 12:56:39 +0200 Subject: [PATCH 0018/4355] add variable references to prompt parser --- .../chat/browser/actions/chatContext.ts | 26 ++------- .../contrib/chat/browser/chatInputPart.ts | 11 +++- .../contrib/chat/common/chatModes.ts | 21 +++++++- .../chat/common/chatVariableEntries.ts | 53 ++++++++++++++++++- .../promptSyntax/parsers/basePromptParser.ts | 29 ++++++++-- .../chat/common/promptSyntax/parsers/types.ts | 5 ++ .../promptSyntax/service/promptsService.ts | 7 ++- .../service/promptsServiceImpl.ts | 5 +- .../chat/test/common/chatModeService.test.ts | 18 ++++--- 9 files changed, 136 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 3f3371d1df5..00d29bf8e00 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -23,8 +23,8 @@ import { FileEditorInput } from '../../../files/browser/editors/fileEditorInput. import { NotebookEditorInput } from '../../../notebook/common/notebookEditorInput.js'; import { IChatContextPickService, IChatContextValueItem, IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPicker } from '../chatContextPickService.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; -import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IImageVariableEntry, OmittedState, toToolSetVariableEntry, toToolVariableEntry } from '../../common/chatVariableEntries.js'; +import { ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { IChatWidget } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; @@ -77,14 +77,14 @@ class ToolsContextPickerPick implements IChatContextPickerItem { toolInfo: ToolDataSource.classify(entry.source), label: entry.referenceName, description: entry.description, - asAttachment: (): IChatRequestToolSetEntry => this._asToolSetAttachment(entry) + asAttachment: (): IChatRequestToolSetEntry => toToolSetVariableEntry(entry) }); } else { items.push({ toolInfo: ToolDataSource.classify(entry.source), label: entry.toolReferenceName ?? entry.displayName, description: entry.userDescription ?? entry.modelDescription, - asAttachment: (): IChatRequestToolEntry => this._asToolAttachment(entry) + asAttachment: (): IChatRequestToolEntry => toToolVariableEntry(entry) }); } } @@ -118,25 +118,7 @@ class ToolsContextPickerPick implements IChatContextPickerItem { }; } - private _asToolAttachment(entry: IToolData): IChatRequestToolEntry { - return { - kind: 'tool', - id: entry.id, - icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined, - name: entry.displayName, - value: undefined, - }; - } - private _asToolSetAttachment(entry: ToolSet): IChatRequestToolSetEntry { - return { - kind: 'toolset', - id: entry.id, - icon: entry.icon, - name: entry.referenceName, - value: Array.from(entry.getTools()).map(t => this._asToolAttachment(t)), - }; - } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 8f1eb3dae26..52d6cc3a1c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -81,11 +81,12 @@ import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlem import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry, toToolReferences } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; +import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; @@ -314,10 +315,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const mode = this._currentModeObservable.get(); const modeId: 'ask' | 'agent' | 'edit' | 'custom' | undefined = mode.isBuiltin ? this.currentModeKind : 'custom'; + const instructions = mode.body ? { + content: mode.body.get(), + toolReferences: toToolReferences(this.languageModelToolsService, mode.variableReferences?.get() ?? [], mode.body.get()), + } : undefined; + return { kind: this.currentModeKind, isBuiltin: mode.isBuiltin, - instructions: mode.body ? { content: mode.body.get() } : undefined, + instructions: instructions, modeId: modeId, applyCodeBlockSuggestionId: undefined, }; @@ -392,6 +398,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IChatEntitlementService private readonly entitlementService: IChatEntitlementService, @IChatModeService private readonly chatModeService: IChatModeService, @IPromptsService private readonly promptsService: IPromptsService, + @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, ) { super(); this._onDidLoadInputState = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index ed6577e08bb..faeb960fe29 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -8,6 +8,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IObservable, ISettableObservable, observableValue, transaction } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; +import { IRange } from '../../../../editor/common/core/range.js'; import { localize } from '../../../../nls.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -97,7 +98,8 @@ export class ChatModeService extends Disposable implements IChatModeService { description: cachedMode.description, tools: cachedMode.customTools, model: cachedMode.model, - body: cachedMode.body || '' + body: cachedMode.body || '', + variableReferences: cachedMode.variableReferences || [], }; const instance = new CustomChatMode(customChatMode); this._customModeInstances.set(uri.toString(), instance); @@ -199,6 +201,7 @@ export interface IChatModeData { readonly customTools?: readonly string[]; readonly model?: string; readonly body?: string; + readonly variableReferences?: readonly IVariableReference[]; readonly uri?: URI; } @@ -212,9 +215,13 @@ export interface IChatMode { readonly customTools?: IObservable; readonly model?: IObservable; readonly body?: IObservable; - readonly toolReferences?: IObservable; + readonly variableReferences?: IObservable; readonly uri?: IObservable; +} +export interface IVariableReference { + readonly name: string; + readonly range: IRange; } function isCachedChatModeData(data: unknown): data is IChatModeData { @@ -229,6 +236,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { (mode.description === undefined || typeof mode.description === 'string') && (mode.customTools === undefined || Array.isArray(mode.customTools)) && (mode.body === undefined || typeof mode.body === 'string') && + (mode.variableReferences === undefined || Array.isArray(mode.variableReferences)) && (mode.model === undefined || typeof mode.model === 'string') && (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)); } @@ -237,6 +245,7 @@ export class CustomChatMode implements IChatMode { private readonly _descriptionObservable: ISettableObservable; private readonly _customToolsObservable: ISettableObservable; private readonly _bodyObservable: ISettableObservable; + private readonly _variableReferencesObservable: ISettableObservable; private readonly _uriObservable: ISettableObservable; private readonly _modelObservable: ISettableObservable; @@ -263,6 +272,10 @@ export class CustomChatMode implements IChatMode { return this._bodyObservable; } + get variableReferences(): IObservable { + return this._variableReferencesObservable; + } + get uri(): IObservable { return this._uriObservable; } @@ -282,6 +295,7 @@ export class CustomChatMode implements IChatMode { this._customToolsObservable = observableValue('customTools', customChatMode.tools); this._modelObservable = observableValue('model', customChatMode.model); this._bodyObservable = observableValue('body', customChatMode.body); + this._variableReferencesObservable = observableValue('variableReferences', customChatMode.variableReferences); this._uriObservable = observableValue('uri', customChatMode.uri); } @@ -295,6 +309,7 @@ export class CustomChatMode implements IChatMode { this._customToolsObservable.set(newData.tools, tx); this._modelObservable.set(newData.model, tx); this._bodyObservable.set(newData.body, tx); + this._variableReferencesObservable.set(newData.variableReferences, tx); this._uriObservable.set(newData.uri, tx); }); } @@ -308,6 +323,7 @@ export class CustomChatMode implements IChatMode { customTools: this.customTools.get(), model: this.model.get(), body: this.body.get(), + variableReferences: this.variableReferences.get(), uri: this.uri.get() }; } @@ -361,3 +377,4 @@ export function isBuiltinChatMode(mode: IChatMode): boolean { mode.id === ChatMode.Edit.id || mode.id === ChatMode.Agent.id; } + diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 052ff326db6..2990167ca36 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -7,14 +7,17 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { basename } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { IRange } from '../../../../editor/common/core/range.js'; +import { IRange, Range } from '../../../../editor/common/core/range.js'; import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; +import { PositionOffsetTransformer } from '../../../../editor/common/core/text/positionToOffset.js'; import { isLocation, Location, SymbolKind } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { MarkerSeverity, IMarker } from '../../../../platform/markers/common/markers.js'; import { ISCMHistoryItem } from '../../scm/common/history.js'; +import { IVariableReference } from './chatModes.js'; import { IChatContentReference } from './chatService.js'; import { IChatRequestVariableValue } from './chatVariables.js'; +import { ILanguageModelToolsService, IToolData, ToolSet } from './languageModelToolsService.js'; interface IBaseChatRequestVariableEntry { @@ -327,6 +330,28 @@ export function toFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileE }; } +export function toToolVariableEntry(entry: IToolData, range?: IOffsetRange): IChatRequestToolEntry { + return { + kind: 'tool', + id: entry.id, + icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined, + name: entry.displayName, + value: undefined, + range + }; +} + +export function toToolSetVariableEntry(entry: ToolSet, range?: IOffsetRange): IChatRequestToolSetEntry { + return { + kind: 'toolset', + id: entry.id, + icon: entry.icon, + name: entry.referenceName, + value: Array.from(entry.getTools()).map(t => toToolVariableEntry(t)), + range + }; +} + export class ChatRequestVariableSet { private _ids = new Set(); private _entries: IChatRequestVariableEntry[] = []; @@ -370,3 +395,29 @@ export class ChatRequestVariableSet { return this._entries.length; } } + +export function toToolReferences(toolService: ILanguageModelToolsService, references: readonly IVariableReference[], body: string): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] { + const toolsOrToolSetByName = new Map(); + for (const toolSet of toolService.toolSets.get()) { + toolsOrToolSetByName.set(toolSet.referenceName, toolSet); + } + for (const tool of toolService.getTools()) { + toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool); + } + + const transformer = new PositionOffsetTransformer(body); + + const result: (IChatRequestToolEntry | IChatRequestToolSetEntry)[] = []; + for (const ref of references) { + const toolOrToolSet = toolsOrToolSetByName.get(ref.name); + if (toolOrToolSet) { + const range = transformer.getOffsetRange(Range.lift(ref.range)); + if (toolOrToolSet instanceof ToolSet) { + result.push(toToolSetVariableEntry(toolOrToolSet, range)); + } else { + result.push(toToolVariableEntry(toolOrToolSet, range)); + } + } + } + return result; +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts index e564b80129b..dd509381642 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts @@ -18,9 +18,9 @@ import { Emitter } from '../../../../../../base/common/event.js'; import { DeferredPromise } from '../../../../../../base/common/async.js'; import { InstructionsHeader } from './promptHeader/instructionsHeader.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { PromptVariableWithData } from '../codecs/tokens/promptVariable.js'; +import { PromptVariable, PromptVariableWithData } from '../codecs/tokens/promptVariable.js'; import type { IPromptContentsProvider } from '../contentProviders/types.js'; -import type { TPromptReference, ITopError } from './types.js'; +import type { TPromptReference, ITopError, TVariableReference } from './types.js'; import { type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { assert, assertNever } from '../../../../../../base/common/assert.js'; import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; @@ -81,6 +81,8 @@ export class BasePromptParser */ private readonly _references: TPromptReference[] = []; + private readonly _variableReferences: TVariableReference[] = []; + /** * Reference to the prompt header object that holds metadata associated * with the prompt. @@ -326,11 +328,15 @@ export class BasePromptParser // try to convert a prompt variable with data token into a file reference if (token instanceof PromptVariableWithData) { try { - this.handleLinkToken(FileReference.from(token)); + if (token.name === 'file') { + this.handleLinkToken(FileReference.from(token)); + } } catch (error) { // the `FileReference.from` call might throw if the `PromptVariableWithData` token // can not be converted into a valid `#file` reference, hence we ignore the error } + } else if (token instanceof PromptVariable) { + this.handleVariableToken(token); } // note! the `isURL` is a simple check and needs to be improved to truly @@ -401,6 +407,15 @@ export class BasePromptParser return this; } + private handleVariableToken(token: PromptVariable): this { + + this._variableReferences.push({ name: token.name, range: token.range }); + + this._onUpdate.fire(); + + return this; + } + /** * Handle the `stream` end event. * @@ -435,6 +450,7 @@ export class BasePromptParser this._references.length = 0; + this._variableReferences.length = 0; } /** @@ -478,6 +494,13 @@ export class BasePromptParser return [...this._references]; } + /** + * Get a list of variable references of the prompt. + */ + public get variableReferences(): readonly TVariableReference[] { + return [...this._variableReferences]; + } + /** * Valid metadata records defined in the prompt header. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts index 0f83da2bf61..926e4fd4bd7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts @@ -109,3 +109,8 @@ export interface IPromptFileReference extends IPromptReferenceBase { * List of all known prompt reference types. */ export type TPromptReference = IPromptFileReference; + +export type TVariableReference = { + readonly name: string; + readonly range: IRange; +}; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 577a9e4d1df..1a2b4214cf3 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -13,7 +13,7 @@ import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { ITopError } from '../parsers/types.js'; +import { ITopError, TVariableReference } from '../parsers/types.js'; /** * Provides prompt services. @@ -103,6 +103,11 @@ export interface ICustomChatMode { * Contents of the custom chat mode file body. */ readonly body: string; + + /** + * References to variables without a type in the mode body. These could be tools or toolsets` + */ + readonly variableReferences: readonly TVariableReference[]; } /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 662aa6673b6..f08d13e5c9e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -241,13 +241,14 @@ export class PromptsService extends Disposable implements IPromptsService { const body = await parser.getBody(); const name = getCleanPromptName(uri); + const variableReferences = parser.variableReferences; const metadata = parser.metadata; if (metadata?.promptType !== PromptsType.mode) { - return { uri, name, body }; + return { uri, name, body, variableReferences }; } const { description, model, tools } = metadata; - return { uri, name, description, model, tools, body }; + return { uri, name, description, model, tools, body, variableReferences }; } finally { parser?.dispose(); } diff --git a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts index dece3f98050..09d2629d456 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts @@ -110,7 +110,8 @@ suite('ChatModeService', () => { name: 'Test Mode', description: 'A test custom mode', tools: ['tool1', 'tool2'], - body: 'Custom mode body' + body: 'Custom mode body', + variableReferences: [] }; promptsService.setCustomModes([customMode]); @@ -143,7 +144,8 @@ suite('ChatModeService', () => { name: 'Test Mode', description: 'A test custom mode', tools: [], - body: 'Custom mode body' + body: 'Custom mode body', + variableReferences: [] }; promptsService.setCustomModes([customMode]); @@ -160,7 +162,8 @@ suite('ChatModeService', () => { name: 'Findable Mode', description: 'A findable custom mode', tools: [], - body: 'Findable mode body' + body: 'Findable mode body', + variableReferences: [] }; promptsService.setCustomModes([customMode]); @@ -183,7 +186,8 @@ suite('ChatModeService', () => { description: 'Initial description', tools: ['tool1'], body: 'Initial body', - model: 'gpt-4' + model: 'gpt-4', + variableReferences: [] }; promptsService.setCustomModes([initialMode]); @@ -224,7 +228,8 @@ suite('ChatModeService', () => { name: 'Mode 1', description: 'First mode', tools: [], - body: 'Mode 1 body' + body: 'Mode 1 body', + variableReferences: [] }; const mode2: ICustomChatMode = { @@ -232,7 +237,8 @@ suite('ChatModeService', () => { name: 'Mode 2', description: 'Second mode', tools: [], - body: 'Mode 2 body' + body: 'Mode 2 body', + variableReferences: [] }; // Add both modes From 5c0659b8e37bdd9dc420f48710fb90d21b091ad2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 1 Sep 2025 19:59:40 +0200 Subject: [PATCH 0019/4355] evolve --- .../api/common/extHostTypeConverters.ts | 14 +-- .../contrib/chat/browser/chatInputPart.ts | 14 ++- .../contrib/chat/common/chatAgents.ts | 9 +- .../contrib/chat/common/chatModel.ts | 11 ++- .../contrib/chat/common/chatModes.ts | 39 +++++++- .../chat/common/chatVariableEntries.ts | 31 +------ .../promptSyntax/parsers/basePromptParser.ts | 5 +- .../chat/common/promptSyntax/parsers/types.ts | 2 +- .../promptSyntax/service/promptsService.ts | 7 +- .../service/promptsServiceImpl.ts | 11 ++- .../chat/test/common/mockChatModeService.ts | 5 ++ .../service/promptsService.test.ts | 89 ++++++++++++++++++- ...ode.proposed.chatParticipantAdditions.d.ts | 8 +- 13 files changed, 170 insertions(+), 75 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 7c825d2711b..3dc0c63377e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -39,7 +39,7 @@ import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from '../ import { ProgressLocation as MainProgressLocation } from '../../../platform/progress/common/progress.js'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; -import { IChatAgentRequest, IChatAgentResult, IChatModeInstructions } from '../../contrib/chat/common/chatAgents.js'; +import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatMultiDiffData, IChatPrepareToolInvocationPart, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatThinkingPart, IChatToolInvocationSerialized, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; import { IChatRequestVariableEntry, isImageVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; @@ -3123,7 +3123,8 @@ export namespace ChatAgentRequest { tools, model, editedFileEvents: request.editedFileEvents, - modeInstructions: request.modeInstructions ? ChatModeInstructions.to(request.modeInstructions) : undefined, + modeInstructions: request.modeInstructions?.content, + modeInstructionsToolReferences: request.modeInstructions?.toolReferences?.map(ChatLanguageModelToolReference.to), }; if (!isProposedApiEnabled(extension, 'chatParticipantPrivate')) { @@ -3147,15 +3148,6 @@ export namespace ChatAgentRequest { } } -export namespace ChatModeInstructions { - export function to(instructions: IChatModeInstructions): vscode.ChatModeInstructions { - return { - content: instructions.content, - toolReferences: instructions.toolReferences?.map(ref => ChatLanguageModelToolReference.to(ref)) - }; - } -} - export namespace ChatRequestDraft { export function to(request: IChatRequestDraft): vscode.ChatRequestDraft { return { diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 52d6cc3a1c1..4eca0d45be4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -81,12 +81,11 @@ import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlem import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry, toToolReferences } from '../common/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; -import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; @@ -315,15 +314,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const mode = this._currentModeObservable.get(); const modeId: 'ask' | 'agent' | 'edit' | 'custom' | undefined = mode.isBuiltin ? this.currentModeKind : 'custom'; - const instructions = mode.body ? { - content: mode.body.get(), - toolReferences: toToolReferences(this.languageModelToolsService, mode.variableReferences?.get() ?? [], mode.body.get()), - } : undefined; - return { kind: this.currentModeKind, isBuiltin: mode.isBuiltin, - instructions: instructions, + instructions: { + content: mode.body?.get(), + toolReferences: this.chatModeService.toToolReferences(mode) + }, modeId: modeId, applyCodeBlockSuggestionId: undefined, }; @@ -398,7 +395,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IChatEntitlementService private readonly entitlementService: IChatEntitlementService, @IChatModeService private readonly chatModeService: IChatModeService, @IPromptsService private readonly promptsService: IPromptsService, - @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, ) { super(); this._onDidLoadInputState = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index e2b5137ce3e..51fe5440bd7 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -25,10 +25,9 @@ import { IProductService } from '../../../../platform/product/common/productServ import { asJson, IRequestService } from '../../../../platform/request/common/request.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ChatContextKeys } from './chatContextKeys.js'; -import { IChatAgentEditedFileEvent, IChatProgressHistoryResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from './chatModel.js'; +import { IChatAgentEditedFileEvent, IChatProgressHistoryResponseContent, IChatRequestModeInstructions, IChatRequestVariableData, ISerializableChatAgentData } from './chatModel.js'; import { IRawChatCommandContribution } from './chatParticipantContribTypes.js'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from './chatService.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; //#region agent service, commands etc @@ -147,14 +146,10 @@ export interface IChatAgentRequest { rejectedConfirmationData?: any[]; userSelectedModelId?: string; userSelectedTools?: UserSelectedTools; - modeInstructions?: IChatModeInstructions; + modeInstructions?: IChatRequestModeInstructions; editedFileEvents?: IChatAgentEditedFileEvent[]; } -export interface IChatModeInstructions { - content: string; - readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; -} export interface IChatQuestion { readonly prompt: string; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index bccd2b560a9..ed72c07398d 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -22,12 +22,12 @@ import { TextEdit } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, IChatModeInstructions, reviveSerializedAgent } from './chatAgents.js'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { migrateLegacyTerminalToolSpecificData } from './chat.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; -import { IChatRequestVariableEntry } from './chatVariableEntries.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; @@ -214,11 +214,16 @@ const defaultChatResponseModelChangeReason: ChatResponseModelChangeReason = { re export interface IChatRequestModeInfo { kind: ChatModeKind | undefined; // is undefined in case of modeId == 'apply' isBuiltin: boolean; - instructions: IChatModeInstructions | undefined; + instructions: IChatRequestModeInstructions | undefined; modeId: 'ask' | 'agent' | 'edit' | 'custom' | 'applyCodeBlock' | undefined; applyCodeBlockSuggestionId: EditSuggestionId | undefined; } +export interface IChatRequestModeInstructions { + readonly content: string | undefined; + readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; +} + export interface IChatRequestModelParameters { session: ChatModel; message: IParsedChatRequest; diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index faeb960fe29..47eee756913 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IObservable, ISettableObservable, observableValue, transaction } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; -import { IRange } from '../../../../editor/common/core/range.js'; +import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { localize } from '../../../../nls.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -16,7 +16,9 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry, toToolSetVariableEntry, toToolVariableEntry } from './chatVariableEntries.js'; import { ChatModeKind } from './constants.js'; +import { ILanguageModelToolsService, IToolData, ToolSet } from './languageModelToolsService.js'; import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); @@ -28,6 +30,8 @@ export interface IChatModeService { getModes(): { builtin: readonly IChatMode[]; custom: readonly IChatMode[] }; findModeById(id: string): IChatMode | undefined; findModeByName(name: string): IChatMode | undefined; + + toToolReferences(mode: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined; } export class ChatModeService extends Disposable implements IChatModeService { @@ -46,7 +50,8 @@ export class ChatModeService extends Disposable implements IChatModeService { @IChatAgentService private readonly chatAgentService: IChatAgentService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService private readonly logService: ILogService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService, ) { super(); @@ -191,6 +196,34 @@ export class ChatModeService extends Disposable implements IChatModeService { private getCustomModes(): IChatMode[] { return this.chatAgentService.hasToolsAgent ? Array.from(this._customModeInstances.values()) : []; } + + public toToolReferences({ body, variableReferences }: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined { + if (!body || !variableReferences) { + return undefined; + } + + const toolsOrToolSetByName = new Map(); + for (const toolSet of this.toolService.toolSets.get()) { + toolsOrToolSetByName.set(toolSet.referenceName, toolSet); + } + for (const tool of this.toolService.getTools()) { + toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool); + } + + const result: (IChatRequestToolEntry | IChatRequestToolSetEntry)[] = []; + for (const ref of variableReferences.get()) { + const toolOrToolSet = toolsOrToolSetByName.get(ref.name); + if (toolOrToolSet) { + if (toolOrToolSet instanceof ToolSet) { + result.push(toToolSetVariableEntry(toolOrToolSet, ref.range)); + } else { + result.push(toToolVariableEntry(toolOrToolSet, ref.range)); + } + } + } + return result; + } + } export interface IChatModeData { @@ -221,7 +254,7 @@ export interface IChatMode { export interface IVariableReference { readonly name: string; - readonly range: IRange; + readonly range: IOffsetRange; } function isCachedChatModeData(data: unknown): data is IChatModeData { diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 2990167ca36..3247b4c5cb5 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -7,17 +7,15 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { basename } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { IRange, Range } from '../../../../editor/common/core/range.js'; +import { IRange } from '../../../../editor/common/core/range.js'; import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; -import { PositionOffsetTransformer } from '../../../../editor/common/core/text/positionToOffset.js'; import { isLocation, Location, SymbolKind } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { MarkerSeverity, IMarker } from '../../../../platform/markers/common/markers.js'; import { ISCMHistoryItem } from '../../scm/common/history.js'; -import { IVariableReference } from './chatModes.js'; import { IChatContentReference } from './chatService.js'; import { IChatRequestVariableValue } from './chatVariables.js'; -import { ILanguageModelToolsService, IToolData, ToolSet } from './languageModelToolsService.js'; +import { IToolData, ToolSet } from './languageModelToolsService.js'; interface IBaseChatRequestVariableEntry { @@ -396,28 +394,3 @@ export class ChatRequestVariableSet { } } -export function toToolReferences(toolService: ILanguageModelToolsService, references: readonly IVariableReference[], body: string): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] { - const toolsOrToolSetByName = new Map(); - for (const toolSet of toolService.toolSets.get()) { - toolsOrToolSetByName.set(toolSet.referenceName, toolSet); - } - for (const tool of toolService.getTools()) { - toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool); - } - - const transformer = new PositionOffsetTransformer(body); - - const result: (IChatRequestToolEntry | IChatRequestToolSetEntry)[] = []; - for (const ref of references) { - const toolOrToolSet = toolsOrToolSetByName.get(ref.name); - if (toolOrToolSet) { - const range = transformer.getOffsetRange(Range.lift(ref.range)); - if (toolOrToolSet instanceof ToolSet) { - result.push(toToolSetVariableEntry(toolOrToolSet, range)); - } else { - result.push(toToolVariableEntry(toolOrToolSet, range)); - } - } - } - return result; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts index dd509381642..d272d80d192 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts @@ -77,10 +77,13 @@ export class BasePromptParser private receivedTokens: BaseToken[] = []; /** - * List of file references in the current branch of the file reference tree. + * List of file references in the prompt file. */ private readonly _references: TPromptReference[] = []; + /** + * List of variable references in the prompt file. + */ private readonly _variableReferences: TVariableReference[] = []; /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts index 926e4fd4bd7..0a3d693c61d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts @@ -112,5 +112,5 @@ export type TPromptReference = IPromptFileReference; export type TVariableReference = { readonly name: string; - readonly range: IRange; + readonly range: Range; }; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 1a2b4214cf3..0b326d60d0d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -13,7 +13,8 @@ import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { ITopError, TVariableReference } from '../parsers/types.js'; +import { ITopError } from '../parsers/types.js'; +import { IVariableReference } from '../../chatModes.js'; /** * Provides prompt services. @@ -105,9 +106,9 @@ export interface ICustomChatMode { readonly body: string; /** - * References to variables without a type in the mode body. These could be tools or toolsets` + * References to variables without a type in the mode body. These could be tools or toolsets. */ - readonly variableReferences: readonly TVariableReference[]; + readonly variableReferences: readonly IVariableReference[]; } /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index f08d13e5c9e..ed08e228e69 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -26,6 +26,7 @@ import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileL import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { PromptsConfig } from '../config/config.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { PositionOffsetTransformer } from '../../../../../../editor/common/core/text/positionToOffset.js'; /** * Provides prompt services. @@ -240,8 +241,16 @@ export class PromptsService extends Disposable implements IPromptsService { } const body = await parser.getBody(); + const nHeaderLines = parser.header?.range.endLineNumber ?? 0; + const transformer = new PositionOffsetTransformer(body); + const variableReferences = parser.variableReferences.map(ref => { + return { + name: ref.name, + range: transformer.getOffsetRange(ref.range.delta(-nHeaderLines)) + }; + }).sort((a, b) => b.range.start - a.range.start); // in reverse order + const name = getCleanPromptName(uri); - const variableReferences = parser.variableReferences; const metadata = parser.metadata; if (metadata?.promptType !== PromptsType.mode) { diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts index 300b1f202fc..516fc15d77c 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts @@ -6,6 +6,7 @@ import { Event } from '../../../../../base/common/event.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; +import { IChatRequestToolEntry, IChatRequestToolSetEntry } from '../../common/chatVariableEntries.js'; export class MockChatModeService implements IChatModeService { readonly _serviceBrand: undefined; @@ -25,4 +26,8 @@ export class MockChatModeService implements IChatModeService { findModeByName(name: string): IChatMode | undefined { return this._modes.builtin.find(mode => mode.name === name) ?? this._modes.custom.find(mode => mode.name === name); } + + toToolReferences(mode: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined { + return []; + } } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index f8db1ba18f0..871926fc5ee 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -28,7 +28,7 @@ import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../.. import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; -import { IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; +import { ICustomChatMode, IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; import { MockFilesystem } from '../testUtils/mockFilesystem.js'; import { ILabelService } from '../../../../../../../platform/label/common/label.js'; import { ComputeAutomaticInstructions } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; @@ -1101,4 +1101,91 @@ suite('PromptsService', () => { ); }); }); + + suite('getCustomChatModes', () => { + teardown(() => { + sinon.restore(); + }); + + + test('returns custom chat modes', async () => { + const rootFolderName = 'custom-modes'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + sinon.stub(service, 'listPromptFiles') + .returns(Promise.resolve([ + // local instructions + { + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'), + storage: 'local', + type: PromptsType.mode, + }, + { + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), + storage: 'local', + type: PromptsType.instructions, + }, + + ])); + + // mock current workspace file structure + await (instaService.createInstance(MockFilesystem, + [{ + name: rootFolderName, + children: [ + { + name: '.github/chatmodes', + children: [ + { + name: 'mode1.instructions.md', + contents: [ + '---', + 'description: \'Mode file 1.\'', + 'tools: [ tool1, tool2 ]', + '---', + 'Do it with #tool1', + ], + }, + { + name: 'mode2.instructions.md', + contents: [ + 'First use #tool2\nThen use #tool1', + ], + } + ], + + }, + ], + }])).mock(); + + const result = await service.getCustomChatModes(CancellationToken.None); + const expected: ICustomChatMode[] = [ + { + name: 'mode1', + description: 'Mode file 1.', + tools: ['tool1', 'tool2'], + body: 'Do it with #tool1', + variableReferences: [{ name: 'tool1', range: { start: 11, endExclusive: 17 } }], + model: undefined, + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'), + }, + { + name: 'mode2', + description: undefined, + tools: undefined, + body: 'First use #tool2\nThen use #tool1', + variableReferences: [{ name: 'tool1', range: { start: 26, endExclusive: 32 } }, { name: 'tool2', range: { start: 10, endExclusive: 16 } }], + model: undefined, + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), + } + ]; + + assert.deepEqual( + expected, + result, + 'Must get custom chat modes.', + ); + }); + }); }); diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 4788f2f0ae8..d2c0767e6dd 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -636,11 +636,7 @@ declare module 'vscode' { } export interface ChatRequest { - modeInstructions?: ChatModeInstructions; - } - - export interface ChatModeInstructions { - readonly content: string; - readonly toolReferences?: readonly ChatLanguageModelToolReference[]; + modeInstructions?: string; + modeInstructionsToolReferences?: readonly ChatLanguageModelToolReference[]; } } From a944d77700a97eaf5c6d819eec9db59564ccdfcc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 1 Sep 2025 20:00:58 +0200 Subject: [PATCH 0020/4355] update --- .../test/common/promptSyntax/service/promptsService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 871926fc5ee..4a8ed3a13ef 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -1108,7 +1108,7 @@ suite('PromptsService', () => { }); - test('returns custom chat modes', async () => { + test('body with tool references', async () => { const rootFolderName = 'custom-modes'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); From 6de5cd3b2494ea11b46f0c33626302614f41f8c6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 1 Sep 2025 23:04:51 +0200 Subject: [PATCH 0021/4355] add tool references to prompt file variables --- .../chat/browser/chatAttachmentWidgets.ts | 4 +- .../contrib/chat/browser/chatInputPart.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 3 +- .../chat/browser/languageModelToolsService.ts | 26 ++++++++++ .../contrib/chat/common/chatModel.ts | 4 +- .../contrib/chat/common/chatModes.ts | 32 +----------- .../chat/common/chatVariableEntries.ts | 14 ++++-- .../chat/common/languageModelToolsService.ts | 3 ++ .../computeAutomaticInstructions.ts | 2 +- .../promptSyntax/parsers/basePromptParser.ts | 11 +++++ .../promptSyntax/service/promptsService.ts | 3 +- .../service/promptsServiceImpl.ts | 11 ++++- .../chat/test/common/mockChatModeService.ts | 5 -- .../common/mockLanguageModelToolsService.ts | 6 +++ .../service/promptsService.test.ts | 49 +++++++++++++------ 15 files changed, 113 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index a98a21aa456..63138596157 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -50,7 +50,7 @@ import { CellUri } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; import { getHistoryItemEditorTitle, getHistoryItemHoverContent } from '../../scm/browser/util.js'; import { IChatContentReference } from '../common/chatService.js'; -import { IChatRequestPasteVariableEntry, IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind } from '../common/chatVariableEntries.js'; +import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; @@ -593,7 +593,7 @@ export class PromptTextAttachmentWidget extends AbstractChatAttachmentWidget { export class ToolSetOrToolItemAttachmentWidget extends AbstractChatAttachmentWidget { constructor( - attachment: IChatRequestToolSetEntry | IChatRequestToolEntry, + attachment: ChatRequestToolReferenceEntry, currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 4eca0d45be4..e79854a9839 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -86,6 +86,7 @@ import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; +import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; @@ -319,7 +320,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge isBuiltin: mode.isBuiltin, instructions: { content: mode.body?.get(), - toolReferences: this.chatModeService.toToolReferences(mode) + toolReferences: mode.variableReferences ? this.toolService.toToolReferences(mode.variableReferences.get()) : undefined }, modeId: modeId, applyCodeBlockSuggestionId: undefined, @@ -395,6 +396,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IChatEntitlementService private readonly entitlementService: IChatEntitlementService, @IChatModeService private readonly chatModeService: IChatModeService, @IPromptsService private readonly promptsService: IPromptsService, + @ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService, ) { super(); this._onDidLoadInputState = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d52802d6106..3e12417a350 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2141,7 +2141,8 @@ export class ChatWidget extends Disposable implements IChatWidget { parseResult = await this.promptsService.resolvePromptSlashCommand(agentSlashPromptPart.slashPromptCommand, CancellationToken.None); if (parseResult) { // add the prompt file to the context, but not sticky - requestInput.attachedContext.insertFirst(toPromptFileVariableEntry(parseResult.uri, PromptFileVariableKind.PromptFile, undefined, true)); + const toolReferences = this.toolsService.toToolReferences(parseResult.variableReferences); + requestInput.attachedContext.insertFirst(toPromptFileVariableEntry(parseResult.uri, PromptFileVariableKind.PromptFile, undefined, true, toolReferences)); // remove the slash command from the input requestInput.input = this.parsedInput.parts.filter(part => !(part instanceof ChatRequestSlashPromptPart)).map(part => part.text).join('').trim(); diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index abfb95b5906..7821b1c24ad 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -32,8 +32,10 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatModel } from '../common/chatModel.js'; +import { IVariableReference } from '../common/chatModes.js'; import { ChatToolInvocation } from '../common/chatProgressTypes/chatToolInvocation.js'; import { ConfirmedReason, IChatService, ToolConfirmKind } from '../common/chatService.js'; +import { ChatRequestToolReferenceEntry, toToolSetVariableEntry, toToolVariableEntry } from '../common/chatVariableEntries.js'; import { ChatConfiguration } from '../common/constants.js'; import { CountTokensCallback, createToolSchemaUri, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultInputOutputDetails, stringifyPromptTsxPart, ToolDataSource, ToolSet, IToolAndToolSetEnablementMap } from '../common/languageModelToolsService.js'; import { getToolConfirmationAlert } from './chatAccessibilityProvider.js'; @@ -554,6 +556,30 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return result; } + public toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] { + const toolsOrToolSetByName = new Map(); + for (const toolSet of this.toolSets.get()) { + toolsOrToolSetByName.set(toolSet.referenceName, toolSet); + } + for (const tool of this.getTools()) { + toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool); + } + + const result: ChatRequestToolReferenceEntry[] = []; + for (const ref of variableReferences) { + const toolOrToolSet = toolsOrToolSetByName.get(ref.name); + if (toolOrToolSet) { + if (toolOrToolSet instanceof ToolSet) { + result.push(toToolSetVariableEntry(toolOrToolSet, ref.range)); + } else { + result.push(toToolVariableEntry(toolOrToolSet, ref.range)); + } + } + } + return result; + } + + private readonly _toolSets = new ObservableSet(); readonly toolSets: IObservable> = this._toolSets.observable; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index ed72c07398d..f2c98500b8d 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -27,7 +27,7 @@ import { migrateLegacyTerminalToolSpecificData } from './chat.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; +import { IChatRequestVariableEntry, ChatRequestToolReferenceEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; @@ -221,7 +221,7 @@ export interface IChatRequestModeInfo { export interface IChatRequestModeInstructions { readonly content: string | undefined; - readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; + readonly toolReferences?: readonly ChatRequestToolReferenceEntry[]; } export interface IChatRequestModelParameters { diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 47eee756913..e020b2d8fe5 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -16,9 +16,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry, toToolSetVariableEntry, toToolVariableEntry } from './chatVariableEntries.js'; import { ChatModeKind } from './constants.js'; -import { ILanguageModelToolsService, IToolData, ToolSet } from './languageModelToolsService.js'; import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); @@ -30,8 +28,6 @@ export interface IChatModeService { getModes(): { builtin: readonly IChatMode[]; custom: readonly IChatMode[] }; findModeById(id: string): IChatMode | undefined; findModeByName(name: string): IChatMode | undefined; - - toToolReferences(mode: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined; } export class ChatModeService extends Disposable implements IChatModeService { @@ -50,8 +46,7 @@ export class ChatModeService extends Disposable implements IChatModeService { @IChatAgentService private readonly chatAgentService: IChatAgentService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService private readonly logService: ILogService, - @IStorageService private readonly storageService: IStorageService, - @ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService, + @IStorageService private readonly storageService: IStorageService ) { super(); @@ -197,32 +192,7 @@ export class ChatModeService extends Disposable implements IChatModeService { return this.chatAgentService.hasToolsAgent ? Array.from(this._customModeInstances.values()) : []; } - public toToolReferences({ body, variableReferences }: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined { - if (!body || !variableReferences) { - return undefined; - } - - const toolsOrToolSetByName = new Map(); - for (const toolSet of this.toolService.toolSets.get()) { - toolsOrToolSetByName.set(toolSet.referenceName, toolSet); - } - for (const tool of this.toolService.getTools()) { - toolsOrToolSetByName.set(tool.toolReferenceName ?? tool.displayName, tool); - } - const result: (IChatRequestToolEntry | IChatRequestToolSetEntry)[] = []; - for (const ref of variableReferences.get()) { - const toolOrToolSet = toolsOrToolSetByName.get(ref.name); - if (toolOrToolSet) { - if (toolOrToolSet instanceof ToolSet) { - result.push(toToolSetVariableEntry(toolOrToolSet, ref.range)); - } else { - result.push(toToolVariableEntry(toolOrToolSet, ref.range)); - } - } - } - return result; - } } diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 3247b4c5cb5..64960e01c2e 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -63,6 +63,8 @@ export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry readonly value: IChatRequestToolEntry[]; } +export type ChatRequestToolReferenceEntry = IChatRequestToolEntry | IChatRequestToolSetEntry; + export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry { readonly kind: 'implicit'; readonly isFile: true; @@ -189,7 +191,7 @@ export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry readonly originLabel?: string; readonly modelDescription: string; readonly automaticallyAdded: boolean; - readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; + readonly toolReferences?: readonly ChatRequestToolReferenceEntry[]; } export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry { @@ -198,7 +200,7 @@ export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry readonly settingId?: string; readonly modelDescription: string; readonly automaticallyAdded: boolean; - readonly toolReferences?: readonly (IChatRequestToolEntry | IChatRequestToolSetEntry)[]; + readonly toolReferences?: readonly ChatRequestToolReferenceEntry[]; } export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry { @@ -294,7 +296,7 @@ export enum PromptFileVariableKind { * @param uri A resource URI that points to a prompt instructions file. * @param kind The kind of the prompt file variable entry. */ -export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false): IPromptFileVariableEntry { +export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptFileVariableEntry { // `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on return { id: `${kind}__${uri.toString()}`, @@ -304,18 +306,20 @@ export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind modelDescription: 'Prompt instructions file', isRoot: kind !== PromptFileVariableKind.InstructionReference, originLabel, + toolReferences, automaticallyAdded }; } -export function toPromptTextVariableEntry(content: string, automaticallyAdded = false): IPromptTextVariableEntry { +export function toPromptTextVariableEntry(content: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptTextVariableEntry { return { id: `vscode.prompt.instructions.text`, name: `prompt:instructionsList`, value: content, kind: 'promptText', modelDescription: 'Prompt instructions list', - automaticallyAdded + automaticallyAdded, + toolReferences }; } diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index a2e7176fc26..c21b7c7beec 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -21,7 +21,9 @@ import { ContextKeyExpression } from '../../../../platform/contextkey/common/con import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IProgress } from '../../../../platform/progress/common/progress.js'; +import { IVariableReference } from './chatModes.js'; import { IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, type IChatTerminalToolInvocationData } from './chatService.js'; +import { ChatRequestToolReferenceEntry } from './chatVariableEntries.js'; import { LanguageModelPartAudience } from './languageModels.js'; import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js'; @@ -321,6 +323,7 @@ export interface ILanguageModelToolsService { cancelToolCallsForRequest(requestId: string): void; toToolEnablementMap(toolOrToolSetNames: Set): Record; toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap; + toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[]; readonly toolSets: IObservable>; getToolSet(id: string): ToolSet | undefined; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index f3d74c49749..9a6bccbdbb9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -248,7 +248,7 @@ export class ComputeAutomaticInstructions { while (next) { const result = await this._parseInstructionsFile(next, token); const refsToCheck: { resource: URI }[] = []; - for (const ref of result.references) { + for (const ref of result.fileReferences) { if (!seen.has(ref) && (isPromptOrInstructionsFile(ref) || this._workspaceService.getWorkspaceFolder(ref) !== undefined)) { // only add references that are either prompt or instruction files or are part of the workspace refsToCheck.push({ resource: ref }); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts index d272d80d192..9fe72885d03 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts @@ -120,6 +120,17 @@ export class BasePromptParser return BaseToken.render(tokens); } + /** + * Get the full contents of the prompt, including the header + */ + public async getFullContent(): Promise { + const decoder = new LinesDecoder( + await this.promptContentsProvider.contents, + ); + const tokens = await decoder.consumeAll(); + return BaseToken.render(tokens); + } + /** * The event is fired when lines or their content change. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 0b326d60d0d..ba6751b7685 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -224,5 +224,6 @@ export interface IPromptParserResult { readonly uri: URI; readonly metadata: TMetadata | null; readonly topError: ITopError | undefined; - readonly references: readonly URI[]; + readonly fileReferences: readonly URI[]; + readonly variableReferences: readonly IVariableReference[]; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index ed08e228e69..1c4a3cd7949 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -276,12 +276,21 @@ export class PromptsService extends Disposable implements IPromptsService { if (!completed) { throw new Error(localize('promptParser.notCompleted', "Prompt parser for {0} did not complete.", uri.toString())); } + const fullContent = await parser.getFullContent(); + const transformer = new PositionOffsetTransformer(fullContent); + const variableReferences = parser.variableReferences.map(ref => { + return { + name: ref.name, + range: transformer.getOffsetRange(ref.range) + }; + }).sort((a, b) => b.range.start - a.range.start); // in reverse order // make a copy, to avoid leaking the parser instance return { uri: parser.uri, metadata: parser.metadata, topError: parser.topError, - references: parser.references.map(ref => ref.uri) + variableReferences, + fileReferences: parser.references.map(ref => ref.uri) }; } finally { parser?.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts index 516fc15d77c..300b1f202fc 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts @@ -6,7 +6,6 @@ import { Event } from '../../../../../base/common/event.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; -import { IChatRequestToolEntry, IChatRequestToolSetEntry } from '../../common/chatVariableEntries.js'; export class MockChatModeService implements IChatModeService { readonly _serviceBrand: undefined; @@ -26,8 +25,4 @@ export class MockChatModeService implements IChatModeService { findModeByName(name: string): IChatMode | undefined { return this._modes.builtin.find(mode => mode.name === name) ?? this._modes.custom.find(mode => mode.name === name); } - - toToolReferences(mode: IChatMode): (IChatRequestToolEntry | IChatRequestToolSetEntry)[] | undefined { - return []; - } } diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index 3a3f97975a2..2d33f4a1169 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -8,6 +8,8 @@ import { Event } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { constObservable, IObservable } from '../../../../../base/common/observable.js'; import { IProgressStep } from '../../../../../platform/progress/common/progress.js'; +import { IVariableReference } from '../../common/chatModes.js'; +import { ChatRequestToolReferenceEntry } from '../../common/chatVariableEntries.js'; import { CountTokensCallback, ILanguageModelToolsService, IToolAndToolSetEnablementMap, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolSet } from '../../common/languageModelToolsService.js'; export class MockLanguageModelToolsService implements ILanguageModelToolsService { @@ -87,4 +89,8 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap { throw new Error('Method not implemented.'); } + + toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] { + throw new Error('Method not implemented.'); + } } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 4a8ed3a13ef..25a21544126 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -28,7 +28,7 @@ import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../.. import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; -import { ICustomChatMode, IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; +import { ICustomChatMode, IPromptParserResult, IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; import { MockFilesystem } from '../testUtils/mockFilesystem.js'; import { ILabelService } from '../../../../../../../platform/label/common/label.js'; import { ComputeAutomaticInstructions } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; @@ -587,6 +587,9 @@ suite('PromptsService', () => { '## Files', '\t- this file #file:folder1/file3.prompt.md ', '\t- also this [file4.prompt.md](./folder1/some-other-folder/file4.prompt.md) please!', + '## Vars', + '\t- #my-tool', + '\t- #my-other-tool', ' ', ], }, @@ -671,7 +674,7 @@ suite('PromptsService', () => { const result1 = await service.parse(rootFileUri, PromptsType.prompt, CancellationToken.None); - assert.deepStrictEqual(result1, { + assert.deepEqual(result1, { uri: rootFileUri, metadata: { promptType: PromptsType.prompt, @@ -680,22 +683,38 @@ suite('PromptsService', () => { mode: 'agent', }, topError: undefined, - references: [file3, file4] - }); + fileReferences: [file3, file4], + variableReferences: [ + { + name: "my-other-tool", + range: { + endExclusive: 265, + start: 251 + } + }, + { + name: "my-tool", + range: { + start: 239, + endExclusive: 247, + } + }] + } satisfies IPromptParserResult); const result2 = await service.parse(file3, PromptsType.prompt, CancellationToken.None); - assert.deepStrictEqual(result2, { + assert.deepEqual(result2, { uri: file3, metadata: { promptType: PromptsType.prompt, mode: 'edit', }, topError: undefined, - references: [nonExistingFolder, yetAnotherFile] - }); + fileReferences: [nonExistingFolder, yetAnotherFile], + variableReferences: [] + } satisfies IPromptParserResult); const result3 = await service.parse(yetAnotherFile, PromptsType.instructions, CancellationToken.None); - assert.deepStrictEqual(result3, { + assert.deepEqual(result3, { uri: yetAnotherFile, metadata: { promptType: PromptsType.instructions, @@ -703,23 +722,25 @@ suite('PromptsService', () => { applyTo: '**/*.tsx', }, topError: undefined, - references: [someOtherFolder, someOtherFolderFile] - }); + fileReferences: [someOtherFolder, someOtherFolderFile], + variableReferences: [] + } satisfies IPromptParserResult); const result4 = await service.parse(file4, PromptsType.instructions, CancellationToken.None); - assert.deepStrictEqual(result4, { + assert.deepEqual(result4, { uri: file4, metadata: { promptType: PromptsType.instructions, description: 'File 4 splendid description.', }, topError: undefined, - references: [ + fileReferences: [ URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-existing/file.prompt.md'), URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-prompt-file.md'), URI.joinPath(rootFolderUri, '/folder1/'), - ] - }); + ], + variableReferences: [] + } satisfies IPromptParserResult); }); }); From c3019defc393ccdd218fadffd755a0d742477d89 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 1 Sep 2025 23:12:12 +0200 Subject: [PATCH 0022/4355] polish --- src/vs/workbench/contrib/chat/common/chatAgents.ts | 1 - src/vs/workbench/contrib/chat/common/chatModel.ts | 2 +- src/vs/workbench/contrib/chat/common/chatModes.ts | 4 ---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 51fe5440bd7..338aa38304f 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -150,7 +150,6 @@ export interface IChatAgentRequest { editedFileEvents?: IChatAgentEditedFileEvent[]; } - export interface IChatQuestion { readonly prompt: string; readonly participant?: string; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index f2c98500b8d..8cea05b568c 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -221,7 +221,7 @@ export interface IChatRequestModeInfo { export interface IChatRequestModeInstructions { readonly content: string | undefined; - readonly toolReferences?: readonly ChatRequestToolReferenceEntry[]; + readonly toolReferences: readonly ChatRequestToolReferenceEntry[] | undefined; } export interface IChatRequestModelParameters { diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index e020b2d8fe5..40b84d443a1 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -191,9 +191,6 @@ export class ChatModeService extends Disposable implements IChatModeService { private getCustomModes(): IChatMode[] { return this.chatAgentService.hasToolsAgent ? Array.from(this._customModeInstances.values()) : []; } - - - } export interface IChatModeData { @@ -380,4 +377,3 @@ export function isBuiltinChatMode(mode: IChatMode): boolean { mode.id === ChatMode.Edit.id || mode.id === ChatMode.Agent.id; } - From 78b5e4c4dd6e1115090e8dfac55191b7deb52507 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Tue, 2 Sep 2025 08:56:39 -0700 Subject: [PATCH 0023/4355] Renamed 'chat history' to 'history' --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d52802d6106..8de25679dcb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -982,7 +982,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const container = dom.append(historyRoot, $('.chat-welcome-history')); const header = dom.append(container, $('.chat-welcome-history-header')); const headerTitle = dom.append(header, $('.chat-welcome-history-header-title')); - headerTitle.textContent = localize('chat.history.title', 'Chat History'); + headerTitle.textContent = localize('chat.history.title', 'History'); const headerActions = dom.append(header, $('.chat-welcome-history-header-actions')); const items = await this.chatService.getHistory(); From c2415d8d6d64bae54755bbcb436174f5b1743a13 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Wed, 3 Sep 2025 22:25:46 +0200 Subject: [PATCH 0024/4355] fix: memory leak in notebook text model --- .../contrib/notebook/common/model/notebookTextModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index bdbae0e7d4f..f9337475dd1 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -274,7 +274,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel }; this._register(_modelService.onModelAdded(e => maybeUpdateCellTextModel(e))); - this._pauseableEmitter = new NotebookEventEmitter({ + this._pauseableEmitter = this._register(new NotebookEventEmitter({ merge: (events: NotebookTextModelChangedEvent[]) => { const first = events[0]; @@ -292,7 +292,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return { rawEvents, versionId, endSelectionState, synchronous }; } - }); + })); this._register(this._pauseableEmitter.event(e => { if (e.rawEvents.length) { From dfd0766160c0d6de474d12e075aca12b11ebd51b Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 4 Sep 2025 12:52:05 +0200 Subject: [PATCH 0025/4355] tsc --- .../editor/contrib/inlayHints/browser/inlayHintsController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 3d641751c5b..facfd1accc7 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -8,7 +8,7 @@ import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { disposableTimeout, RunOnceScheduler } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; -import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { IRange } from '../../../../base/common/range.js'; import { assertType } from '../../../../base/common/types.js'; @@ -207,7 +207,7 @@ export class InlayHintsController implements IEditorContribution { const watchedProviders = new Set(); this._sessionDisposables.add(model.onWillDispose(() => cts?.cancel())); - const inlayHintsDisposable = this._sessionDisposables.add(new MutableDisposable()) + const inlayHintsDisposable = this._sessionDisposables.add(new MutableDisposable()); const scheduler = new RunOnceScheduler(async () => { const t1 = Date.now(); From fb54e0aa4b2aaf21e3350d3a8e021707133805f0 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 4 Sep 2025 13:31:17 +0200 Subject: [PATCH 0026/4355] try another solution --- .../browser/inlayHintsController.ts | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index facfd1accc7..2f6ca728846 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -94,8 +94,45 @@ const enum RenderMode { Invisible } + + +/** + * Mix of CancellationTokenSource, DisposableStore and MutableDisposable + */ +class CancellationStore implements IDisposable { + private _store: DisposableStore; + private _tokenSource: CancellationTokenSource; + + + constructor() { + this._store = new DisposableStore(); + this._tokenSource = new CancellationTokenSource(); + + } + + dispose() { + this._store.dispose(); + this._tokenSource.dispose(true); + + } + + reset() { + this._tokenSource.dispose(true); + this._store.dispose(); + this._tokenSource = new CancellationTokenSource(); + this._store = new DisposableStore(); + + return { + store: this._store, + token: this._tokenSource.token + } + } +} + + // --- controller + export class InlayHintsController implements IEditorContribution { static readonly ID: string = 'editor.contrib.InlayHints'; @@ -207,18 +244,18 @@ export class InlayHintsController implements IEditorContribution { const watchedProviders = new Set(); this._sessionDisposables.add(model.onWillDispose(() => cts?.cancel())); - const inlayHintsDisposable = this._sessionDisposables.add(new MutableDisposable()); + + const cancellationStore = this._sessionDisposables.add(new CancellationStore()); + const scheduler = new RunOnceScheduler(async () => { const t1 = Date.now(); - cts?.dispose(true); - cts = new CancellationTokenSource(); + const { store, token } = cancellationStore.reset(); try { - const myToken = cts.token; - const inlayHints = await InlayHintsFragments.create(this._languageFeaturesService.inlayHintsProvider, model, this._getHintsRanges(), myToken); + const inlayHints = await InlayHintsFragments.create(this._languageFeaturesService.inlayHintsProvider, model, this._getHintsRanges(), token); scheduler.delay = this._debounceInfo.update(model, Date.now() - t1); - if (myToken.isCancellationRequested) { + if (token.isCancellationRequested) { inlayHints.dispose(); return; } @@ -227,27 +264,23 @@ export class InlayHintsController implements IEditorContribution { for (const provider of inlayHints.provider) { if (typeof provider.onDidChangeInlayHints === 'function' && !watchedProviders.has(provider)) { watchedProviders.add(provider); - this._sessionDisposables.add(provider.onDidChangeInlayHints(() => { + store.add(provider.onDidChangeInlayHints(() => { if (!scheduler.isScheduled()) { // ignore event when request is already scheduled scheduler.schedule(); } })); } } - inlayHintsDisposable.value = inlayHints; + store.add(inlayHints); this._updateHintsDecorators(inlayHints.ranges, inlayHints.items); this._cacheHintsForFastRestore(model); } catch (err) { onUnexpectedError(err); - } finally { - cts.dispose(); } - }, this._debounceInfo.get(model)); this._sessionDisposables.add(scheduler); - this._sessionDisposables.add(toDisposable(() => cts?.dispose(true))); scheduler.schedule(0); this._sessionDisposables.add(this._editor.onDidScrollChange((e) => { From c16430a375b0f0c47df252fce4bd7a5ea0330cc2 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 4 Sep 2025 13:36:18 +0200 Subject: [PATCH 0027/4355] lint --- .../contrib/inlayHints/browser/inlayHintsController.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 2f6ca728846..68b429ec3c8 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -100,6 +100,7 @@ const enum RenderMode { * Mix of CancellationTokenSource, DisposableStore and MutableDisposable */ class CancellationStore implements IDisposable { + // eslint-disable-next-line local/code-no-potentially-unsafe-disposables private _store: DisposableStore; private _tokenSource: CancellationTokenSource; @@ -107,13 +108,11 @@ class CancellationStore implements IDisposable { constructor() { this._store = new DisposableStore(); this._tokenSource = new CancellationTokenSource(); - } dispose() { this._store.dispose(); this._tokenSource.dispose(true); - } reset() { @@ -125,7 +124,7 @@ class CancellationStore implements IDisposable { return { store: this._store, token: this._tokenSource.token - } + }; } } From 68a4c23b6fb968449b896691b8bca83a9169837a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:10:03 -0700 Subject: [PATCH 0028/4355] Remove unused export --- .../contrib/terminal/terminalContribChatExports.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminal/terminalContribChatExports.ts diff --git a/src/vs/workbench/contrib/terminal/terminalContribChatExports.ts b/src/vs/workbench/contrib/terminal/terminalContribChatExports.ts deleted file mode 100644 index edd22bbfdc5..00000000000 --- a/src/vs/workbench/contrib/terminal/terminalContribChatExports.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// HACK: Export chat parts as it's only partially encapsulated within the contrib. This file only -// exists because including it into terminalContribExports would cause a circular dependency on -// startup -export { TerminalChatContextKeys } from '../terminalContrib/chat/browser/terminalChat.js'; -export { TerminalChatController } from '../terminalContrib/chat/browser/terminalChatController.js'; From c738ec6c40099671c763e1ee7023e321589cf815 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Sep 2025 10:25:46 +0200 Subject: [PATCH 0029/4355] run oss tool (#265291) * run oss tool * update distro --- cli/ThirdPartyNotices.txt | 16 ++++++++-------- package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index c7f8ef0b2a4..00fd53fd890 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -4390,7 +4390,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- js-sys 0.3.69 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/js-sys Copyright (c) 2014 Alex Crichton @@ -10536,7 +10536,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen +https://github.com/wasm-bindgen/wasm-bindgen Copyright (c) 2014 Alex Crichton @@ -10568,7 +10568,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-backend 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/backend Copyright (c) 2014 Alex Crichton @@ -10600,7 +10600,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-futures 0.4.42 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/futures Copyright (c) 2014 Alex Crichton @@ -10632,7 +10632,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-macro 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro Copyright (c) 2014 Alex Crichton @@ -10664,7 +10664,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-macro-support 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support Copyright (c) 2014 Alex Crichton @@ -10696,7 +10696,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-shared 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared Copyright (c) 2014 Alex Crichton @@ -10758,7 +10758,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- web-sys 0.3.69 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/web-sys Copyright (c) 2014 Alex Crichton diff --git a/package.json b/package.json index b6ca424aa3d..1a8c39e157a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.104.0", - "distro": "ac1cb85a260685046f3a9961d551ab6901dd7cb6", + "distro": "5a3bb643226c440998c203fc2ae156db00c96fb0", "author": { "name": "Microsoft Corporation" }, @@ -237,4 +237,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From 68a8b4b9f7f8bac86992882feab4548d0975640f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Sep 2025 11:06:40 +0200 Subject: [PATCH 0030/4355] bump version (#265295) --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52d750a75c3..6b62732e7b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-oss-dev", - "version": "1.104.0", + "version": "1.105.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.104.0", + "version": "1.105.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1a8c39e157a..dab01582365 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.104.0", + "version": "1.105.0", "distro": "5a3bb643226c440998c203fc2ae156db00c96fb0", "author": { "name": "Microsoft Corporation" @@ -237,4 +237,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} From dc7adbd355a6e999afedc6425a5e9693b49a200b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Sep 2025 12:16:45 +0200 Subject: [PATCH 0031/4355] update exp configs on refetch (#265309) --- .../services/configuration/browser/configurationService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index fa5dadc0880..ed214a7c914 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -1351,6 +1351,7 @@ class ConfigurationDefaultOverridesContribution extends Disposable implements IW super(); this.updateDefaults().then(() => { + this._register(workbenchAssignmentService.onDidRefetchAssignments(() => this.processExperimentalSettings(this.autoExperimentalSettings, true))); if (ASSIGNMENT_REFETCH_INTERVAL !== 0) { this._register(this.scheduleProcessingAutoExperimentalSettings(ASSIGNMENT_REFETCH_INTERVAL)); } From 6c5f088d178528c254f3caa24b000036845f3071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 5 Sep 2025 12:30:38 +0200 Subject: [PATCH 0032/4355] allow skipping linux snap (#265311) * allow skipping linux snap * fix ci --- .../linux/product-build-linux.yml | 32 +++++++++++-------- build/azure-pipelines/product-build.yml | 9 ++++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index aa1aadc5685..8a0d1c99c29 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -5,6 +5,9 @@ parameters: type: string - name: VSCODE_CIBUILD type: boolean + - name: VSCODE_BUILD_LINUX_SNAP + type: boolean + default: false - name: VSCODE_RUN_ELECTRON_TESTS type: boolean default: false @@ -302,23 +305,24 @@ steps: command: login displayName: Login to Container Registry - - script: | - set -e - npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" - sudo -E docker run -e VSCODE_ARCH -e VSCODE_QUALITY -v $(pwd):/work -w /work vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 /bin/bash -c "./build/azure-pipelines/linux/build-snap.sh" + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_SNAP, true) }}: + - script: | + set -e + npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" + sudo -E docker run -e VSCODE_ARCH -e VSCODE_QUALITY -v $(pwd):/work -w /work vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 /bin/bash -c "./build/azure-pipelines/linux/build-snap.sh" - SNAP_ROOT="$(pwd)/.build/linux/snap/$(VSCODE_ARCH)" - SNAP_EXTRACTED_PATH=$(find $SNAP_ROOT -maxdepth 1 -type d -name 'code-*') - SNAP_PATH=$(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') + SNAP_ROOT="$(pwd)/.build/linux/snap/$(VSCODE_ARCH)" + SNAP_EXTRACTED_PATH=$(find $SNAP_ROOT -maxdepth 1 -type d -name 'code-*') + SNAP_PATH=$(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') - # SBOM tool doesn't like recursive symlinks - sudo find $SNAP_EXTRACTED_PATH -type l -delete + # SBOM tool doesn't like recursive symlinks + sudo find $SNAP_EXTRACTED_PATH -type l -delete - echo "##vso[task.setvariable variable=SNAP_EXTRACTED_PATH]$SNAP_EXTRACTED_PATH" - echo "##vso[task.setvariable variable=SNAP_PATH]$SNAP_PATH" - env: - VSCODE_ARCH: $(VSCODE_ARCH) - displayName: Build snap package + echo "##vso[task.setvariable variable=SNAP_EXTRACTED_PATH]$SNAP_EXTRACTED_PATH" + echo "##vso[task.setvariable variable=SNAP_PATH]$SNAP_PATH" + env: + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Build snap package - task: UseDotNet@2 inputs: diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 6908f3e72a9..886d102928a 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -41,6 +41,10 @@ parameters: displayName: "🎯 Linux x64" type: boolean default: true + - name: VSCODE_BUILD_LINUX_SNAP + displayName: "🎯 Linux x64 Snap" + type: boolean + default: true - name: VSCODE_BUILD_LINUX_ARM64 displayName: "🎯 Linux arm64" type: boolean @@ -105,7 +109,7 @@ variables: - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX - value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }} + value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_SNAP, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_ALPINE value: ${{ or(eq(parameters.VSCODE_BUILD_ALPINE, true), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_MACOS @@ -479,7 +483,7 @@ extends: VSCODE_TEST_ARTIFACT_NAME: remote VSCODE_RUN_REMOTE_TESTS: true - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_SNAP, true))) }}: - job: Linuxx64 timeoutInMinutes: 90 variables: @@ -495,6 +499,7 @@ extends: VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_BUILD_LINUX_SNAP: ${{ parameters.VSCODE_BUILD_LINUX_SNAP }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - job: LinuxArmhf From c5866c144be0dbe123e5de52d7a7470bdf429ab3 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 5 Sep 2025 12:50:35 +0200 Subject: [PATCH 0033/4355] Fire event on assignment context updates (#265314) fire onDidRefetchAssignments when assignment context updates --- .../assignment/common/assignmentService.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/assignment/common/assignmentService.ts b/src/vs/workbench/services/assignment/common/assignmentService.ts index e32b1fbc73a..4a3d96b11d9 100644 --- a/src/vs/workbench/services/assignment/common/assignmentService.ts +++ b/src/vs/workbench/services/assignment/common/assignmentService.ts @@ -51,7 +51,10 @@ class MementoKeyValueStorage implements IKeyValueStorage { } } -class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry { +class WorkbenchAssignmentServiceTelemetry extends Disposable implements IExperimentationTelemetry { + + private readonly _onDidUpdateAssignmentContext = this._register(new Emitter()); + readonly onDidUpdateAssignmentContext = this._onDidUpdateAssignmentContext.event; private _lastAssignmentContext: string | undefined; get assignmentContext(): string[] | undefined { @@ -61,12 +64,15 @@ class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry { constructor( private readonly telemetryService: ITelemetryService, private readonly productService: IProductService - ) { } + ) { + super(); + } // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } setSharedProperty(name: string, value: string): void { if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { this._lastAssignmentContext = value; + this._onDidUpdateAssignmentContext.fire(); } this.telemetryService.setExperimentProperty(name, value); @@ -127,7 +133,9 @@ export class WorkbenchAssignmentService extends Disposable implements IAssignmen this.tasClient = this.setupTASClient(); } - this.telemetry = new WorkbenchAssignmentServiceTelemetry(telemetryService, productService); + this.telemetry = this._register(new WorkbenchAssignmentServiceTelemetry(telemetryService, productService)); + this._register(this.telemetry.onDidUpdateAssignmentContext(() => this._onDidRefetchAssignments.fire())); + this.keyValueStorage = new MementoKeyValueStorage(new Memento('experiment.service.memento', storageService)); // For development purposes, configure the delay until tas local tas treatment ovverrides are available @@ -225,7 +233,6 @@ export class WorkbenchAssignmentService extends Disposable implements IAssignmen await tasClient.initializePromise; tasClient.initialFetch.then(() => { this.networkInitialized = true; - this._onDidRefetchAssignments.fire(); }); return tasClient; @@ -242,7 +249,6 @@ export class WorkbenchAssignmentService extends Disposable implements IAssignmen // Refresh the assignments await tasClient.getTreatmentVariableAsync('vscode', 'refresh', false); - this._onDidRefetchAssignments.fire(); } async getCurrentExperiments(): Promise { From 81641444635d1a63dd83472169b979c14de517a1 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Fri, 5 Sep 2025 11:51:21 +0100 Subject: [PATCH 0034/4355] updated codicons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 90184 -> 90136 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 9076b3a707f55bca502c2d6f90a6ae1d5a6cf37d..3738243fd543be6ff2b2f3055f0347f42366139f 100644 GIT binary patch delta 1828 zcmY+EdrTWg9>-_=TEBKN-WS-ejlFo6I>sURMFKI@*aWbNO+tVKmk?f|gch5ClOTkH zA&&F-BPo)TKca{}RJFNNl~a0YbVWI&Mp0Bn)~SD-a=EIidQnwHrznl8idI$Iu}v#= zHT#&|z@6JBhkY3%8UTRS<>fil40JZ>t72(Tam*k6M=C;c#T=^vc;AVXK`0R4x zh!Ma*_VS%N$*0o68-M#hC#0$yry5T;-fw)`#5DCbEi^set=v7e`~BVj_8EK&zQ6iD zYp!SxH^-a5Y;m>3Th>~hz4G2GkNiIWus`EpYn8XgTCcZ0Y<<%Db3hmH2gU;z10Mz+ z2A&4x!Kz?ea5T6Od^@-id=z}%W@{U7yR%2L=h~hx+Clqp`+R$@!=CC0bc}Z7I)3V` z=$!1#b>0gpLv^8OXd;vjeHeNi2H{Njc2`|jXV+TSqrH~B;l0_t8xat3MOGrWBi}@; zqP}Q4dZW9pd$RkL_ zdT3>6edzlm?MITsvf-iO^$}*oH!?hu8ToSLmr=`Ta`f{t^;mdpX6)M7{c&pi!O`lY zv7J9s}ZhC5ZZTg#d zT|61j#UCWpiED|wiOrdYnb=Hr=I-&H{sV@o$EhuIDheiI`=Hy!Z$~^^lQ%*d zjg_X)nQgc#ibn~Hmh&no#Wp=FFs4dv4PMbuz)&bigPv-rz#*;ha#{<4YE`(+Txnti zR=-Ul#$P5Xwty<|TYYbB+EbMfLlVruT0rvhgvcoJcv-LDgAOR{;5p>M<3(mzr`IjK z1UWb6F^UN25P4eF6d{(CF^s}a)XKyXl|rRfkqYG?JwTF!33<6%jFnM@NVyDET87qO zBAFepm1{|jRJKBnO5xx`n$+Y;GM~>#B#2)nDx=D7J4(Reh)`HL>(&utOZr+6RgvnTFR@`L5O@{4;-_4;*)Q~vH3J!rmW>^!C zicBUeVKV&>jcG0Rk~zcn26VSohH4A>iHjlDLOZa5E^rC}>*_hbvA&+6StB$-hTXwn z914(tmLiw4p7nZ~AnS--z2aiB)7)_3DF@h3p>hN_ zmm3^nF5n9t_{xbkfiR3&9UgWvQCma twb}%)l6T5g4O%islDSYQp3AK#5{VB;Or^p|@{?jC^PhbDQZ3x({0%33wzmKP delta 2009 zcmX|BZA=?S8lKs;cfGcEZSQ(_4Y&qu@GcxNoWC2ufU)^97)qc(a2x`rA(Z1gghUbM zD}>SrHO+}eQB-kJ6kVdbQ|=PAQHwaFi>j!HiWEhsoD@}2bw5r~#IGnqIj+4~((7pV zo!R%Doq68peP*t1sUK~rKdIFJ(Nod|Kw$(x%lXTVUe$}g7vH_S=rC;o0QW{mCMFlg zPS^m9Jh=RzL{%pg{P^dW4k_&~T`B#%^j~F~GH=;r*|oC0a=Cn^{G0Mup~InTp>IR~ zuJBh3R;*U+SGHEJR&G}5s~%Rp4kyC1;hW*jYG?Ji>W%6b5iF7!2}XJ%i;+8#?Z}I$ zChClaqN(V7^jdT)`hE0u4O3HBvsm+wT378?wQuTdb+dI>>+aV#r0aX@=j-n`SQ`8d z%MJG%eu&Ajcx*Vf6kCsN$Mza+jW-*gG{u_+nl_tW$1CH5@mulln{Cal%{Q8#w5VD_ zEs2)(mPhZ!-&=0QS{sj)92q!rtqrv0wT-v^rQO^<)c*VS-S@-qf7QWs^mI&h-0FDQ znb%p?nd)5Zs_a_tNu%{@@7_+h6TOM$#Ph`dG3~L3$--njxqZCu`0DW= zd)9lNo-m$Bo%poZ-h00HX`j3AXx~iVjlMUj%v31#X==A$?jPu1?SIh!!v{qlJUdq{l zc{Ht`j!*w~`uW-5*~Hmf=R)V!&acfh&P>l-PixX8=|p-m{fAj%wrqB7&O0|eH#4_7 z_xaqP=9&5U{ObJEg?DWWs|ybniN#A7KN z^7iuHN39=?u6S2&eq8hM<|XV>=w77opQqEAfCB>X0>EWMo^(h)p%n5S9~w9$59uj} zB2P*Ihn+BsLN-J(hvbx;-he;gFNM-KAzb?%rBYKHVJe5MR|SJTvP@fodZR`~A~!`b zT87aYRH~o_iXrj~iW-2&)~GNWdzY1InNC}b7L5vr9K^M>#ON?+0RZ6W1<(MkXq2IV z3y@+3=95TK0V0S_$pw901b_@k2#9EdW(Fcy#n2<5j}V5&M{Ai{QUMd@tQHdnH9C_n zYzZ1P6iUP?S5}y5HRQ@L+UWJ=z?=;8aFmXAE0`?S{7hV{#SN6+Xel?~5F>SgYM+_U z;smY0iY8N${}4q2a4+k3dqwF(Fcav35!itXvFH(bC*tmaN)hoW+DQikMN$kXxLg=X zxKMAy7#Q4PDeW;n!LrHZ-}G8&qI5-c5hd~lQbqF`E$Z|}BLxXP%wSV0EV<3IJ3BjL zq*gMai3F}O@&c>X$Dqz&(&GeeU^Mq`eCL+ZKm7!Ypb@Yj8x$i*b_Vi@Gz3BLxI@tE zf}}SD-3O%ET?m^)3Wx!Z^6(PrSQ8~duZz+(d?p$RQWtB>v#|%JU1qGbAkzghotI^-BmTFCY)wakQF@?F^q^YFh{UWW6s6V{R}h-4;8E~h9uMFKU}&P3>ljW~LaNgetWP5;&tZ zV>pBmvycR)#)$(ri8B;(D-u^J24=x*U@;e>&A&$~%3>c)4?nQN8Rk#3ZsCSCplCdC9z*)PTCtxPl zB(jEPttGC5FxSHFay|>Irzo62+RsQw4`*-z2dP0|GYieyln!Tey&^JEY> Date: Fri, 5 Sep 2025 13:14:35 +0200 Subject: [PATCH 0035/4355] files - update to latest `@parcel/watcher` (#265144) --- extensions/package-lock.json | 307 +---------------------------------- extensions/package.json | 2 +- package-lock.json | 302 +--------------------------------- package.json | 2 +- remote/package-lock.json | 302 +--------------------------------- remote/package.json | 2 +- 6 files changed, 27 insertions(+), 890 deletions(-) diff --git a/extensions/package-lock.json b/extensions/package-lock.json index 0eaa987965d..483e907fca4 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -13,7 +13,7 @@ "typescript": "^5.9.2" }, "devDependencies": { - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "esbuild": "0.25.0", "vscode-grammar-updater": "^1.1.0" } @@ -445,13 +445,13 @@ }, "node_modules/@parcel/watcher": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "resolved": "git+ssh://git@github.com/parcel-bundler/watcher.git#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", + "integrity": "sha512-Z0lk8pM5vwuOJU6pfheRXHrOpQYIIEnVl/z8DY6370D4+ZnrOTvFa5BUdf3pGxahT5ILbPWwQSm2Wthy4q1OTg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" @@ -459,294 +459,6 @@ "engines": { "node": ">= 10.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" @@ -790,16 +502,13 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/esbuild": { diff --git a/extensions/package.json b/extensions/package.json index f0d7f2818de..f436a5ecaca 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -10,7 +10,7 @@ "postinstall": "node ./postinstall.mjs" }, "devDependencies": { - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "esbuild": "0.25.0", "vscode-grammar-updater": "^1.1.0" }, diff --git a/package-lock.json b/package-lock.json index 6b62732e7b0..3c4b665d397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", @@ -1631,12 +1631,12 @@ }, "node_modules/@parcel/watcher": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "resolved": "git+ssh://git@github.com/parcel-bundler/watcher.git#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", + "integrity": "sha512-Z0lk8pM5vwuOJU6pfheRXHrOpQYIIEnVl/z8DY6370D4+ZnrOTvFa5BUdf3pGxahT5ILbPWwQSm2Wthy4q1OTg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" @@ -1644,298 +1644,11 @@ "engines": { "node": ">= 10.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -6375,9 +6088,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { "node": ">=8" } diff --git a/package.json b/package.json index dab01582365..1814d94c119 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index 7a9d0455cc3..58b4fe9b098 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.33.0", @@ -91,12 +91,12 @@ }, "node_modules/@parcel/watcher": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "resolved": "git+ssh://git@github.com/parcel-bundler/watcher.git#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", + "integrity": "sha512-Z0lk8pM5vwuOJU6pfheRXHrOpQYIIEnVl/z8DY6370D4+ZnrOTvFa5BUdf3pGxahT5ILbPWwQSm2Wthy4q1OTg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" @@ -104,298 +104,11 @@ "engines": { "node": ">= 10.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@tootallnate/once": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-3.0.0.tgz", @@ -769,9 +482,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { "node": ">=8" } diff --git a/remote/package.json b/remote/package.json index 1a2b555cf19..6ec99ceb2ed 100644 --- a/remote/package.json +++ b/remote/package.json @@ -5,7 +5,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", + "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.33.0", From 9ec3bb84c9b9200d8c64c298779fc414a56a9845 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 13:14:52 +0200 Subject: [PATCH 0036/4355] cli - add `--transient` to troubleshooting section (#265145) --- src/vs/platform/environment/node/argv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index a8d76158a73..2777fc28c0e 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -168,7 +168,7 @@ export const OPTIONS: OptionDescriptions> = { 'skip-welcome': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, - 'transient': { type: 'boolean' }, + 'transient': { type: 'boolean', cat: 't', description: localize('transient', "Run with temporary data and extension directories, as if launched for the first time.") }, 'use-inmemory-secretstorage': { type: 'boolean', deprecates: ['disable-keytar'] }, 'password-store': { type: 'string' }, 'disable-workspace-trust': { type: 'boolean' }, From e910fd5e1d0217cb61776296be22d8d2e28f4a6b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 13:15:02 +0200 Subject: [PATCH 0037/4355] smoke - use `workbench.action.clearEditorHistoryWithoutConfirm` consistently (#265146) --- test/automation/src/quickaccess.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index d417a4a2c29..2c7c4054252 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -7,7 +7,6 @@ import { Editors } from './editors'; import { Code } from './code'; import { QuickInput } from './quickinput'; import { basename, isAbsolute } from 'path'; -import { Quality } from './application'; enum QuickAccessKind { Files = 1, @@ -23,11 +22,7 @@ export class QuickAccess { // make sure the file quick access is not "polluted" // with entries from the editor history when opening - if (this.code.quality !== Quality.Stable) { - await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); - } else { - await this.runCommand('workbench.action.clearEditorHistory'); - } + await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); const PollingStrategy = { Stop: true, From 0c72238b49537eaedad023157476b39d6f0e210b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 13:15:11 +0200 Subject: [PATCH 0038/4355] aux window - support more CSS variables by setting them, properly (#265280) --- src/vs/base/browser/domStylesheets.ts | 3 ++ src/vs/workbench/browser/layout.ts | 3 +- src/vs/workbench/browser/media/style.css | 1 + .../debug/browser/statusbarColorProvider.ts | 14 +++++---- .../contrib/sash/browser/sash.contribution.ts | 8 ++--- src/vs/workbench/contrib/sash/browser/sash.ts | 30 +++++++++++-------- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/vs/base/browser/domStylesheets.ts b/src/vs/base/browser/domStylesheets.ts index 13ddb7b545a..c0ad9c9eabe 100644 --- a/src/vs/base/browser/domStylesheets.ts +++ b/src/vs/base/browser/domStylesheets.ts @@ -62,6 +62,9 @@ export function createStyleSheet(container: HTMLElement = mainWindow.document.he if (container === mainWindow.document.head) { const globalStylesheetClones = new Set(); globalStylesheets.set(style, globalStylesheetClones); + if (disposableStore) { + disposableStore.add(toDisposable(() => globalStylesheets.delete(style))); + } for (const { window: targetWindow, disposables } of getWindows()) { if (targetWindow === mainWindow) { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 1a58e3b61b4..b6d4255606a 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js'; import { Event, Emitter } from '../../base/common/event.js'; -import { EventType, addDisposableListener, getClientArea, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, isActiveDocument, getWindow, getWindowId, getActiveElement, Dimension } from '../../base/browser/dom.js'; +import { EventType, addDisposableListener, getClientArea, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, isActiveDocument, getWindow, getWindowId, getActiveElement, Dimension } from '../../base/browser/dom.js'; import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from '../../base/browser/browser.js'; import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from '../../base/common/platform.js'; import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from '../common/editor.js'; @@ -1634,7 +1634,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi ); this.logService.trace(`Layout#layout, height: ${this._mainContainerDimension.height}, width: ${this._mainContainerDimension.width}`); - position(this.mainContainer, 0, 0, 0, 0, 'relative'); size(this.mainContainer, this._mainContainerDimension.width, this._mainContainerDimension.height); // Layout the grid widget diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 4a83f3b2d90..36b4f0a2236 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -49,6 +49,7 @@ body { font-size: 13px; line-height: 1.4em; position: relative; + inset: 0; z-index: 1; overflow: hidden; color: var(--vscode-foreground); diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index ebc3963e61a..48f8f422308 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -12,7 +12,7 @@ import { STATUS_BAR_FOREGROUND, STATUS_BAR_BORDER, COMMAND_CENTER_BACKGROUND } f import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { IStatusbarService } from '../../../services/statusbar/browser/statusbar.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; +import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; // colors for theming @@ -45,6 +45,8 @@ export class StatusBarColorProvider implements IWorkbenchContribution { private readonly disposables = new DisposableStore(); private disposable: IDisposable | undefined; + private readonly styleSheet = createStyleSheet(); + private set enabled(enabled: boolean) { if (enabled === !!this.disposable) { return; @@ -67,7 +69,6 @@ export class StatusBarColorProvider implements IWorkbenchContribution { @IDebugService private readonly debugService: IDebugService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStatusbarService private readonly statusbarService: IStatusbarService, - @ILayoutService private readonly layoutService: ILayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { this.debugService.onDidChangeState(this.update, this, this.disposables); @@ -90,11 +91,12 @@ export class StatusBarColorProvider implements IWorkbenchContribution { } const isInCommandCenter = debugConfig.toolBarLocation === 'commandCenter'; - this.layoutService.mainContainer.style.setProperty(asCssVariableName(COMMAND_CENTER_BACKGROUND), isInCommandCenter && isInDebugMode - ? asCssVariable(COMMAND_CENTER_DEBUGGING_BACKGROUND) - : '' - ); + this.styleSheet.textContent = isInCommandCenter && isInDebugMode ? ` + .monaco-workbench { + ${asCssVariableName(COMMAND_CENTER_BACKGROUND)}: ${asCssVariable(COMMAND_CENTER_DEBUGGING_BACKGROUND)}; + } + ` : ''; } dispose(): void { diff --git a/src/vs/workbench/contrib/sash/browser/sash.contribution.ts b/src/vs/workbench/contrib/sash/browser/sash.contribution.ts index a7679a8dc65..f976b0ebf01 100644 --- a/src/vs/workbench/contrib/sash/browser/sash.contribution.ts +++ b/src/vs/workbench/contrib/sash/browser/sash.contribution.ts @@ -3,18 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isIOS } from '../../../../base/common/platform.js'; import { localize } from '../../../../nls.js'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { workbenchConfigurationNodeBase } from '../../../common/configuration.js'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { SashSettingsController } from './sash.js'; -import { isIOS } from '../../../../base/common/platform.js'; // Sash size contribution -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SashSettingsController, LifecyclePhase.Restored); +registerWorkbenchContribution2(SashSettingsController.ID, SashSettingsController, WorkbenchPhase.AfterRestored); // Sash size configuration contribution Registry.as(ConfigurationExtensions.Configuration) diff --git a/src/vs/workbench/contrib/sash/browser/sash.ts b/src/vs/workbench/contrib/sash/browser/sash.ts index eaf2d23cdbe..57c60dfd022 100644 --- a/src/vs/workbench/contrib/sash/browser/sash.ts +++ b/src/vs/workbench/contrib/sash/browser/sash.ts @@ -6,28 +6,31 @@ import { clamp } from '../../../../base/common/numbers.js'; import { setGlobalSashSize, setGlobalHoverDelay } from '../../../../base/browser/ui/sash/sash.js'; import { Event } from '../../../../base/common/event.js'; -import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; +import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; export const minSize = 1; export const maxSize = 20; // see also https://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications -export class SashSettingsController implements IWorkbenchContribution, IDisposable { +export class SashSettingsController extends Disposable implements IWorkbenchContribution { - private readonly disposables = new DisposableStore(); + static readonly ID = 'workbench.contrib.sash'; + + private readonly styleSheet = createStyleSheet(); constructor( @IConfigurationService private readonly configurationService: IConfigurationService, - @ILayoutService private readonly layoutService: ILayoutService ) { + super(); + const onDidChangeSize = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('workbench.sash.size')); - onDidChangeSize(this.onDidChangeSize, this, this.disposables); + onDidChangeSize(this.onDidChangeSize, this, this._store); this.onDidChangeSize(); const onDidChangeHoverDelay = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('workbench.sash.hoverDelay')); - onDidChangeHoverDelay(this.onDidChangeHoverDelay, this, this.disposables); + onDidChangeHoverDelay(this.onDidChangeHoverDelay, this, this._store); this.onDidChangeHoverDelay(); } @@ -36,16 +39,17 @@ export class SashSettingsController implements IWorkbenchContribution, IDisposab const size = clamp(configuredSize, 4, 20); const hoverSize = clamp(configuredSize, 1, 8); - this.layoutService.mainContainer.style.setProperty('--vscode-sash-size', size + 'px'); - this.layoutService.mainContainer.style.setProperty('--vscode-sash-hover-size', hoverSize + 'px'); + this.styleSheet.textContent = ` + .monaco-workbench { + --vscode-sash-size: ${size}px; + --vscode-sash-hover-size: ${hoverSize}px; + } + `; + setGlobalSashSize(size); } private onDidChangeHoverDelay(): void { setGlobalHoverDelay(this.configurationService.getValue('workbench.sash.hoverDelay')); } - - dispose(): void { - this.disposables.dispose(); - } } From 8c6fe4843b3a3d41ecc0132c3faf5f0210ea69cf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 13:15:21 +0200 Subject: [PATCH 0039/4355] chat - improve actions in chat title menu and aux windows (#265288) --- .../actions/browser/actionViewItemService.ts | 2 +- src/vs/platform/actions/browser/toolbar.ts | 6 +-- .../chat/browser/actions/chatActions.ts | 38 ++++++++++++++++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/actions/browser/actionViewItemService.ts b/src/vs/platform/actions/browser/actionViewItemService.ts index 8622b166195..19ffaa0afc2 100644 --- a/src/vs/platform/actions/browser/actionViewItemService.ts +++ b/src/vs/platform/actions/browser/actionViewItemService.ts @@ -17,7 +17,7 @@ export const IActionViewItemService = createDecorator('I export interface IActionViewItemFactory { - (action: IAction, options: IActionViewItemOptions, instaService: IInstantiationService): IActionViewItem | undefined; + (action: IAction, options: IActionViewItemOptions, instantiationService: IInstantiationService, windowId: number): IActionViewItem | undefined; } export interface IActionViewItemService { diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index 59aab31d9c8..a167319371c 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -343,7 +343,7 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar { @ICommandService commandService: ICommandService, @ITelemetryService telemetryService: ITelemetryService, @IActionViewItemService actionViewService: IActionViewItemService, - @IInstantiationService instaService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(container, { resetMenu: menuId, @@ -353,11 +353,11 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar { if (!provider) { provider = options?.actionViewItemProvider; } - const viewItem = provider?.(action, opts, instaService); + const viewItem = provider?.(action, opts, instantiationService, getWindow(container).vscodeWindowId); if (viewItem) { return viewItem; } - return createActionViewItem(instaService, action, opts); + return createActionViewItem(instantiationService, action, opts); } }, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 2dfed0d2842..dafc44b217f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -75,6 +75,7 @@ import { clearChatEditor } from './chatClear.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; +import { mainWindow } from '../../../../../base/browser/window.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -956,7 +957,7 @@ export function registerChatActions() { } }); - registerAction2(class OpenChatEditorAction extends Action2 { + registerAction2(class NewChatEditorAction extends Action2 { constructor() { super({ id: `workbench.action.openChat`, @@ -968,6 +969,11 @@ export function registerChatActions() { weight: KeybindingWeight.WorkbenchContrib + 1, primary: KeyMod.CtrlCmd | KeyCode.KeyN, when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatEditor) + }, + menu: { + id: MenuId.ChatTitleBarMenu, + group: 'b_new', + order: 0 } }); } @@ -978,6 +984,28 @@ export function registerChatActions() { } }); + registerAction2(class NewChatWindowAction extends Action2 { + constructor() { + super({ + id: `workbench.action.newChatWindow`, + title: localize2('interactiveSession.newChatWindow', "New Chat Window"), + f1: true, + category: CHAT_CATEGORY, + precondition: ChatContextKeys.enabled, + menu: { + id: MenuId.ChatTitleBarMenu, + group: 'b_new', + order: 1 + } + }); + } + + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } } satisfies IChatEditorOptions }, AUX_WINDOW_GROUP); + } + }); + registerAction2(class OpenChatEditorInNewWindowAction extends Action2 { constructor() { super({ @@ -1553,12 +1581,11 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben constructor( @IActionViewItemService actionViewItemService: IActionViewItemService, - @IInstantiationService instantiationService: IInstantiationService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, ) { super(); - const disposable = actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatTitleBarMenu, (action, options) => { + const disposable = actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatTitleBarMenu, (action, options, instantiationService, windowId) => { if (!(action instanceof SubmenuItemAction)) { return undefined; } @@ -1574,8 +1601,9 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben const signedOut = chatEntitlementService.entitlement === ChatEntitlement.Unknown; const free = chatEntitlementService.entitlement === ChatEntitlement.Free; - let primaryActionId = TOGGLE_CHAT_ACTION_ID; - let primaryActionTitle = localize('toggleChat', "Toggle Chat"); + const isAuxiliaryWindow = windowId !== mainWindow.vscodeWindowId; + let primaryActionId = isAuxiliaryWindow ? CHAT_OPEN_ACTION_ID : TOGGLE_CHAT_ACTION_ID; + let primaryActionTitle = isAuxiliaryWindow ? localize('openChat', "Open Chat") : localize('toggleChat', "Toggle Chat"); let primaryActionIcon = Codicon.chatSparkle; if (chatSentiment.installed && !chatSentiment.disabled) { if (signedOut) { From fa6188bc91d2b0f16ce980b62f7fcbbfe100c1ff Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 5 Sep 2025 13:23:19 +0200 Subject: [PATCH 0040/4355] Rename editor.inlineSuggest.experimental.triggerCommandOnProviderChange to editor.inlineSuggest.triggerCommandOnProviderChange (#265322) --- src/vs/editor/common/config/editorOptions.ts | 18 +++++++++--------- .../browser/model/inlineCompletionsModel.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 7082fe6b5e8..b705da66ecd 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4388,6 +4388,11 @@ export interface IInlineSuggestOptions { enabled?: boolean; }; + /** + * @internal + */ + triggerCommandOnProviderChange?: boolean; + /** * @internal */ @@ -4397,11 +4402,6 @@ export interface IInlineSuggestOptions { */ suppressInlineSuggestions?: string; - /** - * @internal - */ - triggerCommandOnProviderChange?: boolean; - showOnSuggestConflict?: 'always' | 'never' | 'whenSuggestListIsIncomplete'; }; } @@ -4435,9 +4435,9 @@ class InlineEditorSuggest extends BaseEditorOption new Set(v.experimental.suppressInlineSuggestions.split(','))); this._inlineEditsEnabled = inlineSuggest.map(v => !!v.edits.enabled); this._inlineEditsShowCollapsedEnabled = inlineSuggest.map(s => s.edits.showCollapsed); - this._triggerCommandOnProviderChange = inlineSuggest.map(s => s.experimental.triggerCommandOnProviderChange); + this._triggerCommandOnProviderChange = inlineSuggest.map(s => s.triggerCommandOnProviderChange); this._minShowDelay = inlineSuggest.map(s => s.minShowDelay); this._showOnSuggestConflict = inlineSuggest.map(s => s.experimental.showOnSuggestConflict); From 8843e3df74af33503e589afcfca9385ffbf45322 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 15:03:21 +0200 Subject: [PATCH 0041/4355] chat - support `--profile` with chat sub command (#265330) --- src/vs/code/electron-main/main.ts | 3 +++ src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7e83508c821..fb06a689254 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -523,6 +523,9 @@ class CodeMain { } else if (args.chat['reuse-window']) { // Apply `--reuse-window` flag to the main arguments args['reuse-window'] = true; + } else if (args.chat['profile']) { + // Apply `--profile` flag to the main arguments + args['profile'] = args.chat['profile']; } else { // Unless we are started with specific instructions about // new windows or reusing existing ones, always take the diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index b4e206a3e30..e7b08b8f887 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -31,6 +31,7 @@ export interface NativeParsedArgs { maximize?: boolean; 'reuse-window'?: boolean; 'new-window'?: boolean; + profile?: string; help?: boolean; }; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 2777fc28c0e..17d0583cce3 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -58,6 +58,7 @@ export const OPTIONS: OptionDescriptions> = { 'maximize': { type: 'boolean', cat: 'o', description: localize('chatMaximize', "Maximize the chat session view.") }, 'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindowForChat', "Force to use the last active window for the chat session.") }, 'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindowForChat', "Force to open an empty window for the chat session.") }, + 'profile': { type: 'string', 'cat': 'o', args: 'profileName', description: localize('profileName', "Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created.") }, 'help': { type: 'boolean', alias: 'h', description: localize('help', "Print usage.") } } }, From e44b0af9cc7a37367cbaa2bf3692abfa37628768 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Sep 2025 15:41:01 +0200 Subject: [PATCH 0042/4355] remove scheduler in favour of onDidRefetchAssignments event (#265340) --- .../browser/configurationService.ts | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index ed214a7c914..84bab05f850 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -7,8 +7,8 @@ import { URI } from '../../../../base/common/uri.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { equals } from '../../../../base/common/objects.js'; -import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; -import { Queue, Barrier, Promises, Delayer, RunOnceScheduler } from '../../../../base/common/async.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { Queue, Barrier, Promises, Delayer } from '../../../../base/common/async.js'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from '../../../../platform/configuration/common/configurationModels.js'; @@ -47,7 +47,6 @@ import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/e import { workbenchConfigurationNodeBase } from '../../../common/configuration.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { runWhenWindowIdle } from '../../../../base/browser/dom.js'; -import { ASSIGNMENT_REFETCH_INTERVAL } from '../../../../platform/assignment/common/assignment.js'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { const isDefaultProfile = userDataProfile.isDefault || userDataProfile.useDefaultFlags?.settings; @@ -1351,9 +1350,8 @@ class ConfigurationDefaultOverridesContribution extends Disposable implements IW super(); this.updateDefaults().then(() => { - this._register(workbenchAssignmentService.onDidRefetchAssignments(() => this.processExperimentalSettings(this.autoExperimentalSettings, true))); - if (ASSIGNMENT_REFETCH_INTERVAL !== 0) { - this._register(this.scheduleProcessingAutoExperimentalSettings(ASSIGNMENT_REFETCH_INTERVAL)); + if (!this._store.isDisposed) { + this._register(workbenchAssignmentService.onDidRefetchAssignments(() => this.processExperimentalSettings(this.autoExperimentalSettings, true))); } }); @@ -1361,21 +1359,6 @@ class ConfigurationDefaultOverridesContribution extends Disposable implements IW this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties }) => this.processExperimentalSettings(properties, false))); } - private scheduleProcessingAutoExperimentalSettings(interval: number): IDisposable { - const processAutoExperimentalSettingsScheduler = new RunOnceScheduler(async () => { - try { - if (this.autoExperimentalSettings.size) { - await this.processExperimentalSettings(this.autoExperimentalSettings, true); - } - } finally { - processAutoExperimentalSettingsScheduler.schedule(); - } - }, interval); - - processAutoExperimentalSettingsScheduler.schedule(); - return processAutoExperimentalSettingsScheduler; - } - private async updateDefaults(): Promise { this.logService.trace('ConfigurationService#updateDefaults: begin'); try { From 388785d3f7d2951fdd1f78b3accee973f27df6da Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 5 Sep 2025 15:57:59 +0200 Subject: [PATCH 0043/4355] Fixes https://github.com/microsoft/vscode-internalbacklog/issues/5808 (#265343) --- .../browser/helpers/documentWithAnnotatedEdits.ts | 5 +++-- .../browser/telemetry/editSourceTrackingImpl.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts b/src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts index 9eb47fd7803..966c63f9ae3 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts @@ -103,9 +103,9 @@ export abstract class EditSourceBase { case 'inlineCompletionAccept': { const type = 'type' in data ? data.type : undefined; if ('$nes' in data && data.$nes) { - return this._cache.get(new InlineSuggestEditSource('nes', data.$extensionId ?? '', type)); + return this._cache.get(new InlineSuggestEditSource('nes', data.$extensionId ?? '', data.$providerId ?? '', type)); } - return this._cache.get(new InlineSuggestEditSource('completion', data.$extensionId ?? '', type)); + return this._cache.get(new InlineSuggestEditSource('completion', data.$extensionId ?? '', data.$providerId ?? '', type)); } case 'snippet': return this._cache.get(new IdeEditSource('suggest')); @@ -141,6 +141,7 @@ export class InlineSuggestEditSource extends EditSourceBase { constructor( public readonly kind: 'completion' | 'nes', public readonly extensionId: string, + public readonly providerId: string, public readonly type: 'word' | 'line' | undefined, ) { super(); } diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts index 5025e348259..af4680f1728 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts @@ -270,9 +270,12 @@ class TrackedDocumentInfo extends Disposable { getTelemetryData(ranges: readonly TrackedEdit[]) { const getEditCategory = (source: EditSource) => { if (source.category === 'ai' && source.kind === 'nes') { return 'nes'; } + if (source.category === 'ai' && source.kind === 'completion' && source.extensionId === 'github.copilot') { return 'inlineCompletionsCopilot'; } - if (source.category === 'ai' && source.kind === 'completion' && source.extensionId === 'github.copilot-chat') { return 'inlineCompletionsNES'; } + if (source.category === 'ai' && source.kind === 'completion' && source.extensionId === 'github.copilot-chat' && source.providerId === 'completions') { return 'inlineCompletionsCopilot'; } + if (source.category === 'ai' && source.kind === 'completion' && source.extensionId === 'github.copilot-chat' && source.providerId === 'nes') { return 'inlineCompletionsNES'; } if (source.category === 'ai' && source.kind === 'completion') { return 'inlineCompletionsOther'; } + if (source.category === 'ai') { return 'otherAI'; } if (source.category === 'user') { return 'user'; } if (source.category === 'ide') { return 'ide'; } From 67eb75414b30401ac261031f7e47f7685a9a97bd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 5 Sep 2025 15:59:28 +0200 Subject: [PATCH 0044/4355] chat - improve entitlement and sentiment handling (#265344) --- .../browser/chatParticipant.contribution.ts | 12 ++++-------- .../contrib/chat/browser/chatSessions.ts | 19 +++++-------------- .../chat/common/chatEntitlementService.ts | 18 +++++++++--------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 5c107f03499..1bccbd54590 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -66,14 +66,10 @@ const chatViewDescriptor: IViewDescriptor[] = [{ }, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), when: ContextKeyExpr.or( - ContextKeyExpr.and( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.disabled.negate() // do not pretend a working Chat view if extension is explicitly disabled - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.installed, - ChatContextKeys.Setup.disabled.negate() // do not pretend a working Chat view if extension is explicitly disabled - ), + ContextKeyExpr.or( + ChatContextKeys.Setup.hidden, + ChatContextKeys.Setup.disabled + )?.negate(), ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid ) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts index 50dc5561ba1..5ae9187ad9b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.ts @@ -82,6 +82,7 @@ import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRend import { allowedChatMarkdownHtmlTags } from './chatMarkdownRenderer.js'; import product from '../../../../platform/product/common/product.js'; import { truncate } from '../../../../base/common/strings.js'; +import { IChatEntitlementService } from '../common/chatEntitlementService.js'; export const VIEWLET_ID = 'workbench.view.chat.sessions'; @@ -272,7 +273,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService ) { super(); @@ -318,19 +319,9 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi return; } - const copilotEnabledExpr = ContextKeyExpr.or( - ContextKeyExpr.and( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.disabled.negate() - ), - ContextKeyExpr.and( - ChatContextKeys.Setup.installed, - ChatContextKeys.Setup.disabled.negate() - )); - - const isCopilotEnabled = this.contextKeyService.contextMatchesRules(copilotEnabledExpr); - if (!isCopilotEnabled) { - return; + + if (this.chatEntitlementService.sentiment.hidden || this.chatEntitlementService.sentiment.disabled) { + return; // do not register container as AI features are hidden or disabled } Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( diff --git a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts index b412ae1b932..1c5284f9388 100644 --- a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts @@ -1013,7 +1013,7 @@ export class ChatEntitlementContext extends Disposable { private updateBarrier: Barrier | undefined = undefined; constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @ILogService private readonly logService: ILogService, @@ -1101,9 +1101,11 @@ export class ChatEntitlementContext extends Disposable { update(context: { hidden: false }): Promise; // legacy UI state from before we had a setting to hide, keep around to still support users who used this update(context: { later: boolean }): Promise; update(context: { entitlement: ChatEntitlement; organisations: string[] | undefined; sku: string | undefined }): Promise; - update(context: { installed?: boolean; disabled?: boolean; untrusted?: boolean; hidden?: false; later?: boolean; entitlement?: ChatEntitlement; organisations?: string[]; sku?: string }): Promise { + async update(context: { installed?: boolean; disabled?: boolean; untrusted?: boolean; hidden?: false; later?: boolean; entitlement?: ChatEntitlement; organisations?: string[]; sku?: string }): Promise { this.logService.trace(`[chat entitlement context] update(): ${JSON.stringify(context)}`); + const oldState = JSON.stringify(this._state); + if (typeof context.installed === 'boolean' && typeof context.disabled === 'boolean' && typeof context.untrusted === 'boolean') { this._state.installed = context.installed; this._state.disabled = context.disabled; @@ -1134,6 +1136,10 @@ export class ChatEntitlementContext extends Disposable { } } + if (oldState === JSON.stringify(this._state)) { + return; // state did not change + } + this.storageService.store(ChatEntitlementContext.CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY, { ...this._state, later: undefined // do not persist this across restarts for now @@ -1160,13 +1166,7 @@ export class ChatEntitlementContext extends Disposable { this.businessContextKey.set(state.entitlement === ChatEntitlement.Business); this.enterpriseContextKey.set(state.entitlement === ChatEntitlement.Enterprise); - const organisations = this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.organisations.key); - const currentSet = new Set(organisations); - const newSet = new Set(state.organisations); - if (currentSet.size !== newSet.size || !Array.from(currentSet).every(org => newSet.has(org))) { - this.organisationsContextKey.set(state.organisations); - } - + this.organisationsContextKey.set(state.organisations); this.isInternalContextKey.set(Boolean(state.organisations?.some(org => org === 'github' || org === 'microsoft'))); this.skuContextKey.set(state.sku); From e194cd953a6cfaff397d8ab87507fce5254866fa Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Fri, 5 Sep 2025 15:13:44 +0100 Subject: [PATCH 0045/4355] fix: update chat request bubble hover background transparency --- src/vs/workbench/contrib/chat/common/chatColors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index 3f75c88baaa..43eca32a105 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -58,7 +58,7 @@ export const chatRequestCodeBorder = registerColor('chat.requestCodeBorder', { d export const chatRequestBubbleBackground = registerColor('chat.requestBubbleBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hcDark: null, hcLight: null }, localize('chat.requestBubbleBackground', "Background color of the chat request bubble."), true); -export const chatRequestBubbleHoverBackground = registerColor('chat.requestBubbleHoverBackground', { dark: editorSelectionBackground, light: editorSelectionBackground, hcDark: null, hcLight: null }, localize('chat.requestBubbleHoverBackground', 'Background color of the chat request bubble on hover.'), true); +export const chatRequestBubbleHoverBackground = registerColor('chat.requestBubbleHoverBackground', { dark: transparent(editorSelectionBackground, 0.6), light: transparent(editorSelectionBackground, 0.6), hcDark: null, hcLight: null }, localize('chat.requestBubbleHoverBackground', 'Background color of the chat request bubble on hover.'), true); export const chatCheckpointSeparator = registerColor('chat.checkpointSeparator', { dark: '#585858', light: '#a9a9a9', hcDark: '#a9a9a9', hcLight: '#a5a5a5' }, From d569cb15ffa9b495a09b92e60e86fb6bdb262f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Garc=C3=ADa?= Date: Fri, 5 Sep 2025 17:51:46 +0200 Subject: [PATCH 0046/4355] mcp: fix elicitation email validator (#265326) --- src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts index ae35a192222..aefebec23ba 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts @@ -272,7 +272,7 @@ export class McpElicitationService implements IMcpElicitationService { private _validateStringFormat(value: string, format: string): { isValid: boolean; message?: string } { switch (format) { case 'email': - return !value.includes('@') + return value.includes('@') ? { isValid: true } : { isValid: false, message: localize('mcp.elicit.validation.email', 'Please enter a valid email address') }; case 'uri': From 054be9e6cccf87b9b82945bd90e6e1dc408c5f21 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 5 Sep 2025 12:06:18 -0400 Subject: [PATCH 0047/4355] do not recreate output monitor for foreground terminals (#265354) fix #265238 --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 c4f96354d72..abfbe4ef0f7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -519,7 +519,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } this._logService.debug(`RunInTerminalTool: Using \`${strategy.type}\` execute strategy for command \`${command}\``); store.add(strategy.onDidCreateStartMarker(startMarker => { - outputMonitor = store.add(this._instantiationService.createInstance(OutputMonitor, { instance: toolTerminal.instance, sessionId: invocation.context!.sessionId, getOutput: (marker?: IXtermMarker) => getOutput(toolTerminal.instance, marker ?? startMarker) }, undefined, invocation.context!, token, command)); + if (!outputMonitor) { + outputMonitor = store.add(this._instantiationService.createInstance(OutputMonitor, { instance: toolTerminal.instance, sessionId: invocation.context!.sessionId, getOutput: (marker?: IXtermMarker) => getOutput(toolTerminal.instance, marker ?? startMarker) }, undefined, invocation.context!, token, command)); + } })); const executeResult = await strategy.execute(command, token); // Reset user input state after command execution completes From f73f90d7931a46a2b7c4a815572111877495ee33 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 5 Sep 2025 09:15:22 -0700 Subject: [PATCH 0048/4355] debug: bump js-debug to 1.104 (#265361) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index a0719353135..129d714dd25 100644 --- a/product.json +++ b/product.json @@ -52,8 +52,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.102.0", - "sha256": "0e8ed27ba2d707bcfb008e89e490c2d287d9537d84893b0792a4ee418274fa0b", + "version": "1.104.0", + "sha256": "856db934294bd8b78769dd91c86904c7e35e356bb05b223a9e4d8eb38cb17ae3", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 4407c1e0b3d4cbe7ea2ae1899677445976b7830e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 5 Sep 2025 09:54:02 -0700 Subject: [PATCH 0049/4355] no progressive rendering for existing parts in incomplete chat response (#265239) * no progressive rendering for existing parts in incomplete chat response * resolve comments. --- .../contrib/chat/browser/chatListRenderer.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index c29779d1974..bfe67d9e53e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1112,6 +1112,17 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer 0 && element.contentUpdateTimings?.lastWordCount === 0) { + /** + * None of the content parts in the ongoing response have been rendered yet, + * so we should render all existing parts without animation. + */ + return { + numWordsToRender: Number.MAX_SAFE_INTEGER, + rate: Number.MAX_SAFE_INTEGER + }; + } + const renderData = element.renderData ?? { lastRenderTime: 0, renderedWordCount: 0 }; const rate = this.getProgressiveRenderRate(element); From 71b461ab86725332849783d7d2aa0093789df77c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 5 Sep 2025 07:55:35 -1000 Subject: [PATCH 0050/4355] Support PKCE for GitHub Auth (#265381) Fixes https://github.com/microsoft/vscode/issues/264795 --- extensions/github-authentication/src/flows.ts | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index 6e37dfe5dfd..d9d286bcbbc 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -9,6 +9,7 @@ import { Log } from './common/logger'; import { Config } from './config'; import { UriEventHandler } from './github'; import { fetching } from './node/fetch'; +import { crypto } from './node/crypto'; import { LoopbackAuthServer } from './node/authServer'; import { promiseFromEvent } from './common/utils'; import { isHostedGitHubEnterprise } from './common/env'; @@ -112,11 +113,44 @@ interface IFlow { trigger(options: IFlowTriggerOptions): Promise; } +/** + * Generates a cryptographically secure random string for PKCE code verifier. + * @param length The length of the string to generate + * @returns A random hex string + */ +function generateRandomString(length: number): string { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + return Array.from(array) + .map(b => b.toString(16).padStart(2, '0')) + .join('') + .substring(0, length); +} + +/** + * Generates a PKCE code challenge from a code verifier using SHA-256. + * @param codeVerifier The code verifier string + * @returns A base64url-encoded SHA-256 hash of the code verifier + */ +async function generateCodeChallenge(codeVerifier: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(codeVerifier); + const digest = await crypto.subtle.digest('SHA-256', data); + + // Base64url encode the digest + const base64String = btoa(String.fromCharCode(...new Uint8Array(digest))); + return base64String + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + async function exchangeCodeForToken( logger: Log, endpointUri: Uri, redirectUri: Uri, code: string, + codeVerifier: string, enterpriseUri?: Uri ): Promise { logger.info('Exchanging code for token...'); @@ -130,7 +164,8 @@ async function exchangeCodeForToken( ['code', code], ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], - ['client_secret', clientSecret] + ['client_secret', clientSecret], + ['code_verifier', codeVerifier] ]); if (enterpriseUri) { body.append('github_enterprise', enterpriseUri.toString(true)); @@ -199,13 +234,19 @@ class UrlHandlerFlow implements IFlow { }), cancellable: true }, async (_, token) => { + // Generate PKCE parameters + const codeVerifier = generateRandomString(64); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const promise = uriHandler.waitForCode(logger, scopes, nonce, token); const searchParams = new URLSearchParams([ ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], ['scope', scopes], - ['state', encodeURIComponent(callbackUri.toString(true))] + ['state', encodeURIComponent(callbackUri.toString(true))], + ['code_challenge', codeChallenge], + ['code_challenge_method', 'S256'] ]); if (existingLogin) { searchParams.append('login', existingLogin); @@ -236,7 +277,7 @@ class UrlHandlerFlow implements IFlow { ? Uri.parse(`${proxyEndpoints.github}login/oauth/access_token`) : baseUri.with({ path: '/login/oauth/access_token' }); - const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, enterpriseUri); + const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, codeVerifier, enterpriseUri); return accessToken; }); } @@ -283,10 +324,16 @@ class LocalServerFlow implements IFlow { }), cancellable: true }, async (_, token) => { + // Generate PKCE parameters + const codeVerifier = generateRandomString(64); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const searchParams = new URLSearchParams([ ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], ['scope', scopes], + ['code_challenge', codeChallenge], + ['code_challenge_method', 'S256'] ]); if (existingLogin) { searchParams.append('login', existingLogin); @@ -329,6 +376,7 @@ class LocalServerFlow implements IFlow { baseUri.with({ path: '/login/oauth/access_token' }), redirectUri, codeToExchange, + codeVerifier, enterpriseUri); return accessToken; }); From dc09aa2694e19fb28c49020854c63d6bee626d1d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 5 Sep 2025 20:26:26 +0200 Subject: [PATCH 0051/4355] use throttler (#265389) --- .../configuration/browser/configurationService.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 84bab05f850..548ad0850dd 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from '../../../../base/common/event.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { equals } from '../../../../base/common/objects.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { Queue, Barrier, Promises, Delayer } from '../../../../base/common/async.js'; +import { Queue, Barrier, Promises, Delayer, Throttler } from '../../../../base/common/async.js'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; import { ConfigurationModel, ConfigurationChangeEvent, mergeChanges } from '../../../../platform/configuration/common/configurationModels.js'; @@ -1340,6 +1340,7 @@ class ConfigurationDefaultOverridesContribution extends Disposable implements IW private readonly processedExperimentalSettings = new Set(); private readonly autoExperimentalSettings = new Set(); private readonly configurationRegistry = Registry.as(Extensions.Configuration); + private readonly throttler = this._register(new Throttler()); constructor( @IWorkbenchAssignmentService private readonly workbenchAssignmentService: IWorkbenchAssignmentService, @@ -1349,11 +1350,8 @@ class ConfigurationDefaultOverridesContribution extends Disposable implements IW ) { super(); - this.updateDefaults().then(() => { - if (!this._store.isDisposed) { - this._register(workbenchAssignmentService.onDidRefetchAssignments(() => this.processExperimentalSettings(this.autoExperimentalSettings, true))); - } - }); + this.throttler.queue(() => this.updateDefaults()); + this._register(workbenchAssignmentService.onDidRefetchAssignments(() => this.throttler.queue(() => this.processExperimentalSettings(this.autoExperimentalSettings, true)))); // When configuration is updated make sure to apply experimental configuration overrides this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties }) => this.processExperimentalSettings(properties, false))); From cd8186fd3e9b4c2cab396aec7069d27286049af6 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 5 Sep 2025 14:42:00 -0400 Subject: [PATCH 0052/4355] set height for confirmation button so it doesn't grow (#265390) fix #265382 --- .../chatContentParts/media/chatConfirmationWidget.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index bed3e288ccb..dfd646ba74a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -12,6 +12,12 @@ align-items: center; } +.chat-confirmation-widget .monaco-text-button { + padding: 0 12px; + min-height: 2em; + box-sizing: border-box; +} + .chat-confirmation-widget:not(:last-child) { margin-bottom: 16px; } From 5938733a106d6fe94bcaa77e9b8d6dda3b63be45 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:56:01 +0200 Subject: [PATCH 0053/4355] Chat - switch to using `em` for chat panel font size (#265398) --- .../contrib/chat/browser/chatLayoutService.ts | 22 +++++-------------- .../contrib/chat/browser/chatWidget.ts | 17 ++++++++------ .../contrib/chat/common/chatLayoutService.ts | 11 +--------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatLayoutService.ts b/src/vs/workbench/contrib/chat/browser/chatLayoutService.ts index 9ef97886dc3..f14a3b67a93 100644 --- a/src/vs/workbench/contrib/chat/browser/chatLayoutService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatLayoutService.ts @@ -7,37 +7,25 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { derived, IObservable } from '../../../../base/common/observable.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { ChatFontSize, IChatLayoutService } from '../common/chatLayoutService.js'; +import { IChatLayoutService } from '../common/chatLayoutService.js'; + +const FONT_SIZE = 13; export class ChatLayoutService extends Disposable implements IChatLayoutService { declare readonly _serviceBrand: undefined; - private readonly _fontSizeDefault = 13; - readonly fontFamily: IObservable; - readonly fontSize: IObservable; + readonly fontSize: IObservable; constructor(@IConfigurationService configurationService: IConfigurationService) { super(); const chatFontFamily = observableConfigValue('chat.fontFamily', 'default', configurationService); - const chatFontSize = observableConfigValue('chat.fontSize', this._fontSizeDefault, configurationService); - this.fontFamily = derived(reader => { const fontFamily = chatFontFamily.read(reader); return fontFamily === 'default' ? null : fontFamily; }); - this.fontSize = derived(reader => { - const fontSize = chatFontSize.read(reader); - return { - xs: Math.round(fontSize * (11 / this._fontSizeDefault)), - s: Math.round(fontSize * (12 / this._fontSizeDefault)), - m: Math.round(fontSize * (13 / this._fontSizeDefault)), - l: Math.round(fontSize * (14 / this._fontSizeDefault)), - xl: Math.round(fontSize * (16 / this._fontSizeDefault)), - xxl: Math.round(fontSize * (20 / this._fontSizeDefault)), - }; - }); + this.fontSize = observableConfigValue('chat.fontSize', FONT_SIZE, configurationService); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8bf6f708d0a..7c9becab9c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -723,18 +723,21 @@ export class ChatWidget extends Disposable implements IChatWidget { this.scrollToEnd(); })); + // Font size variables + this.container.style.setProperty('--vscode-chat-font-size-body-xs', '0.846em' /* 11px */); + this.container.style.setProperty('--vscode-chat-font-size-body-s', '0.923em' /* 12px */); + this.container.style.setProperty('--vscode-chat-font-size-body-m', '1em' /* 13px */); + this.container.style.setProperty('--vscode-chat-font-size-body-l', '1.077em' /* 14px */); + this.container.style.setProperty('--vscode-chat-font-size-body-xl', '1.231em' /* 16px */); + this.container.style.setProperty('--vscode-chat-font-size-body-xxl', '1.538em' /* 20px */); + + // Update the font family and size this._register(autorun(reader => { const fontFamily = this.chatLayoutService.fontFamily.read(reader); const fontSize = this.chatLayoutService.fontSize.read(reader); this.container.style.setProperty('--vscode-chat-font-family', fontFamily); - - this.container.style.setProperty('--vscode-chat-font-size-body-xs', `${fontSize.xs}px`); - this.container.style.setProperty('--vscode-chat-font-size-body-s', `${fontSize.s}px`); - this.container.style.setProperty('--vscode-chat-font-size-body-m', `${fontSize.m}px`); - this.container.style.setProperty('--vscode-chat-font-size-body-l', `${fontSize.l}px`); - this.container.style.setProperty('--vscode-chat-font-size-body-xl', `${fontSize.xl}px`); - this.container.style.setProperty('--vscode-chat-font-size-body-xxl', `${fontSize.xxl}px`); + this.container.style.fontSize = `${fontSize}px`; this.tree.rerender(); })); diff --git a/src/vs/workbench/contrib/chat/common/chatLayoutService.ts b/src/vs/workbench/contrib/chat/common/chatLayoutService.ts index ea0f4b22fd6..62c5bff443c 100644 --- a/src/vs/workbench/contrib/chat/common/chatLayoutService.ts +++ b/src/vs/workbench/contrib/chat/common/chatLayoutService.ts @@ -12,14 +12,5 @@ export interface IChatLayoutService { readonly _serviceBrand: undefined; readonly fontFamily: IObservable; - readonly fontSize: IObservable; -} - -export interface ChatFontSize { - readonly xs: number; - readonly s: number; - readonly m: number; - readonly l: number; - readonly xl: number; - readonly xxl: number; + readonly fontSize: IObservable; } From 3bd57dd59fbc46f0f962800a4d3e3aca2ca53977 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Fri, 5 Sep 2025 21:17:01 +0200 Subject: [PATCH 0054/4355] chore: add vscode-copilot-issues to endgame notebooks (#265329) --- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 201c0c3c5e0..ef3188d2c48 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"August 2025\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"August 2025\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index c39de9b210a..55dacefaffc 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-evaluation\n\n$MILESTONE=milestone:\"August 2025\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-evaluation repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"August 2025\"\n\n$MINE=assignee:@me" }, { "kind": 1, From a86f0c916b2f434fbe252bff4680f90ebacd24b9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 5 Sep 2025 22:31:54 +0200 Subject: [PATCH 0055/4355] update --- .../promptSyntax/service/newPromptsParser.ts | 207 ++++++++++++------ .../service/promptsServiceImpl.ts | 15 ++ .../service/newPromptsParser.test.ts | 48 ++++ 3 files changed, 204 insertions(+), 66 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index c6d368c54dd..3f21c87cb43 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -3,98 +3,173 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { splitLines } from '../../../../../../base/common/strings.js'; +import { Iterable } from '../../../../../../base/common/iterator.js'; +import { dirname, resolvePath } from '../../../../../../base/common/resources.js'; +import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; import { parse, YamlNode, YamlParseError } from '../../../../../../base/common/yaml.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { IVariableReference } from '../../chatModes.js'; -import { IPromptParserResult } from './promptsService.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { chatVariableLeader } from '../../chatParserTypes.js'; export class NewPromptsParser { - constructor( - private readonly modelService: IModelService, - private readonly fileService: IFileService, - ) { - // TODO + constructor() { } - public async parse(uri: URI): Promise { - const content = await this.getContents(uri); - if (!content) { - return; - } - const lines = splitLines(content); - if (lines.length === 0) { - return createResult(uri, undefined, [], []); + public parse(uri: URI, content: string): ParsedPromptFile { + const linesWithEOL = splitLinesIncludeSeparators(content); + if (linesWithEOL.length === 0) { + return new ParsedPromptFile(uri, undefined, undefined); } let header: PromptHeader | undefined = undefined; - - let body: { fileReferences: URI[]; variableReferences: IVariableReference[] } | undefined = undefined; - let bodyStart = 0; - if (lines[0] === '---') { - let headerEnd = lines.indexOf('---', 1); - if (headerEnd === -1) { - headerEnd = lines.length; - bodyStart = lines.length; + let body: PromptBody | undefined = undefined; + let bodyStartLine = 0; + if (linesWithEOL[0].match(/^---[\s\r\n]*$/)) { + let headerEndLine = linesWithEOL.findIndex((line, index) => index > 0 && line.match(/^---[\s\r\n]*$/)); + if (headerEndLine === -1) { + headerEndLine = linesWithEOL.length; + bodyStartLine = linesWithEOL.length; } else { - bodyStart = headerEnd + 1; + bodyStartLine = headerEndLine + 1; } - header = this.parseHeader(lines.slice(1, headerEnd !== -1 ? headerEnd : lines.length)); + // range starts on the line after the ---, and ends at the beginning of the line that has the closing --- + const range = new Range(2, 1, headerEndLine + 1, 1); + header = new PromptHeader(range, linesWithEOL); } - if (bodyStart < lines.length) { - body = this.parseBody(lines.slice(bodyStart)); - } else { - body = { fileReferences: [], variableReferences: [] }; + if (bodyStartLine < linesWithEOL.length) { + // range starts on the line after the ---, and ends at the beginning of line after the last line + const range = new Range(bodyStartLine + 1, 1, linesWithEOL.length + 1, 1); + body = new PromptBody(range, linesWithEOL, uri); } - return createResult(uri, header, body.fileReferences, body.variableReferences); + return new ParsedPromptFile(uri, header, body); } +} + + +export class ParsedPromptFile { + constructor(public readonly uri: URI, public readonly header?: PromptHeader, public readonly body?: PromptBody) { + } +} + +interface ParsedHeader { + readonly node: YamlNode | undefined; + readonly errors: YamlParseError[]; + readonly attributes: IHeaderAttribute[]; +} - private parseBody(lines: string[]): { fileReferences: URI[]; variableReferences: IVariableReference[] } { - const fileReferences: URI[] = []; - const variableReferences: IVariableReference[] = []; - for (const line of lines) { - const match = line.match(/\[(.+?)\]\((.+?)\)/); - if (match) { - const [, _text, uri] = match; - fileReferences.push(URI.file(uri)); +export class PromptHeader { + private _parsed: ParsedHeader | undefined; + + constructor(public readonly range: Range, private readonly linesWithEOL: string[]) { + } + + public getParsedHeader(): ParsedHeader { + if (this._parsed === undefined) { + const errors: YamlParseError[] = []; + const lines = Iterable.map(Iterable.slice(this.linesWithEOL, this.range.startLineNumber - 1, this.range.endLineNumber - 1), line => line.replace(/[\r\n]+$/, '')); + const node = parse(lines, errors); + const attributes = []; + if (node?.type === 'object') { + for (const property of node.properties) { + attributes.push({ + key: property.key.value, + range: new Range(this.range.startLineNumber + property.key.start.line, property.key.start.character + 1, this.range.startLineNumber + property.value.end.line, property.value.end.character + 1) + }); + } } + this._parsed = { node, attributes, errors }; } - return { fileReferences, variableReferences }; + return this._parsed; + } + + public get attributes(): IHeaderAttribute[] { + return this.getParsedHeader().attributes; + } +} +interface IHeaderAttribute { + readonly range: Range; + readonly key: string; +} + + +interface ParsedBody { + readonly fileReferences: readonly IBodyFileReference[]; + readonly variableReferences: readonly IBodyVariableReference[]; +} + +export class PromptBody { + private _parsed: ParsedBody | undefined; + + constructor(public readonly range: Range, private readonly linesWithEOL: string[], public readonly uri: URI) { + } + + public get fileReferences(): readonly IBodyFileReference[] { + return this.getParsedBody().fileReferences; } - private parseHeader(lines: string[]): PromptHeader { - const errors: YamlParseError[] = []; - const node = parse(lines, errors); - return new PromptHeader(node, errors); + public get variableReferences(): readonly IBodyVariableReference[] { + return this.getParsedBody().variableReferences; } - private async getContents(uri: URI): Promise { - const model = this.modelService.getModel(uri); - if (model) { - return model.getValue(); + private getParsedBody(): ParsedBody { + if (this._parsed === undefined) { + const fileReferences: IBodyFileReference[] = []; + const variableReferences: IBodyVariableReference[] = []; + for (let i = this.range.startLineNumber - 1; i < this.range.endLineNumber - 1; i++) { + const line = this.linesWithEOL[i]; + const linkMatch = line.matchAll(/\[(.+?)\]\((.+?)\)/g); + for (const match of linkMatch) { + const linkEndOffset = match.index + match[0].length - 1; // before the parenthesis + const linkStartOffset = match.index + match[0].length - match[2].length - 1; + const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); + fileReferences.push({ content: match[2], range }); + } + const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]*)`, 'g'); + const matches = line.matchAll(reg); + for (const match of matches) { + const varType = match[1]; + if (varType) { + if (varType === 'file:') { + const linkStartOffset = match.index + match[0].length - match[2].length; + const linkEndOffset = match.index + match[0].length; + const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); + fileReferences.push({ content: match[2], range }); + } + } else { + const contentStartOffset = match.index + 1; // after the # + const contentEndOffset = match.index + match[0].length; + const range = new Range(i + 1, contentStartOffset + 1, i + 1, contentEndOffset + 1); + variableReferences.push({ content: match[2], range }); + } + } + } + this._parsed = { fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)), variableReferences }; } - const content = await this.fileService.readFile(uri); - if (content) { - return content.value.toString(); + return this._parsed; + } + + public resolveFilePath(path: string): URI | undefined { + try { + if (path.startsWith('/')) { + return URI.file(path); + } else if (path.match(/^[a-zA-Z]:\\/)) { + return URI.parse(path); + } else { + return resolvePath(dirname(this.uri), path); + } + } catch { + return undefined; } - return undefined; } } -function createResult(uri: URI, header: PromptHeader | undefined, fileReferences: URI[], variableReferences: IVariableReference[]): IPromptParserResult { - return { - uri, - header, - fileReferences, - variableReferences, - metadata: null - }; +interface IBodyFileReference { + content: string; + range: Range; } -export class PromptHeader { - constructor(public readonly node: YamlNode | undefined, public readonly errors: YamlParseError[]) { - - } +interface IBodyVariableReference { + content: string; + range: Range; } + diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 14eb712360e..45b976408d9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -27,6 +27,8 @@ import { ILanguageService } from '../../../../../../editor/common/languages/lang import { PromptsConfig } from '../config/config.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { PositionOffsetTransformer } from '../../../../../../editor/common/core/text/positionToOffset.js'; +import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; /** * Provides prompt services. @@ -62,6 +64,7 @@ export class PromptsService extends Disposable implements IPromptsService { @IUserDataProfileService private readonly userDataService: IUserDataProfileService, @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IFileService private readonly fileService: IFileService, ) { super(); @@ -302,6 +305,18 @@ export class PromptsService extends Disposable implements IPromptsService { parser?.dispose(); } } + + public async parseNew(uri: URI): Promise { + let content: string | undefined; + const model = this.modelService.getModel(uri); + if (model) { + content = model.getValue(); + } else { + const fileContent = await this.fileService.readFile(uri); + content = fileContent.value.toString(); + } + return new NewPromptsParser().parse(uri, content); + } } export function getPromptCommandName(path: string): string { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts new file mode 100644 index 00000000000..1281fcfeef9 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; + +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { Range } from '../../../../../../../editor/common/core/range.js'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPromptsParser.js'; + +suite('NewPromptsParser', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + + test('provides cached parser instance', async () => { + const uri = URI.parse('file:///test/prompt1.md'); + const content = [ + /* 01 */"---", + /* 02 */`description: "Agent mode test"`, + /* 03 */"mode: agent", + /* 04 */"tools: ['tool1', 'tool2']", + /* 05 */"---", + /* 06 */"This is a builtin agent mode test.", + /* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).", + ].join('\n'); + const result = new NewPromptsParser().parse(uri, content); + assert.deepEqual(result.uri, uri); + assert.deepEqual(result.header?.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 5, endColumn: 1 }); + assert.deepEqual(result.header?.attributes, [ + { key: 'description', range: new Range(2, 1, 2, 31) }, + { key: 'mode', range: new Range(3, 1, 3, 12) }, + { key: 'tools', range: new Range(4, 1, 4, 26) }, + ]); + + + assert.deepEqual(result.body?.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.deepEqual(result.body?.fileReferences, [ + { range: new Range(7, 39, 7, 54), content: './reference1.md' }, + { range: new Range(7, 80, 7, 95), content: './reference2.md' } + ]); + assert.deepEqual(result.body?.variableReferences, [ + { range: new Range(7, 12, 7, 17), content: 'tool1' } + ]); + }); + +}); From 7da0b1bd76114ec56037714da5784a6300b7583f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 5 Sep 2025 14:02:28 -0700 Subject: [PATCH 0056/4355] handle contentUpdateTimings npe (#265416) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index bfe67d9e53e..61af34a86cb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1112,7 +1112,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer 0 && element.contentUpdateTimings?.lastWordCount === 0) { + if (!element.isComplete && element.response.value.length > 0 && (element.contentUpdateTimings ? element.contentUpdateTimings.lastWordCount : 0) === 0) { /** * None of the content parts in the ongoing response have been rendered yet, * so we should render all existing parts without animation. From f67462f31aa43c8d79bd5345d7d9e7d96991f6cf Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 5 Sep 2025 14:30:51 -0700 Subject: [PATCH 0057/4355] Update build TS versions --- package-lock.json | 70 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52d750a75c3..961d781f9a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250827", + "typescript": "^6.0.0-dev.20250905", "typescript-eslint": "^8.39.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -2756,9 +2756,9 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-AdSaD57/ZXM4UEyuzfqt4daiGeN2y8CFogwAKNjyQV6muQWi5QCmuLNkEbQk7Teg33lCeycrHCTjz9aPgbrJIw==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-IzPzhumNsWsIg4Kmt0y+0b2BBtsvD17rDmKj78yNeU3AsuA6xignQ5eDkFtRmLdGPVZwa8Yg5zPcJRFln98Ocw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2768,19 +2768,19 @@ "node": ">=20.6.0" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20250827.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250904.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250904.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20250904.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250904.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20250904.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250904.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20250904.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-lTgoQK7wkbkb/gxNn6ZcdpBtEh+tBgXJQbl7feMP/BdpNvFJNFKW6DN+1wlXAJj47GYfMvmCN+vk571a9oO4cw==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-1rt4DhERW1VM4OwWYVIrCp1k1S4kpZAxzbCnprNinVJInhHexY2K0FFD9IGXKWSRANHg/OmJRQYTEoDKM6pqNw==", "cpu": [ "arm64" ], @@ -2795,9 +2795,9 @@ } }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-xPyU3x81DdhAiuh+cqLYK2733+mQuRqMaHCrdwL8s1dy+swjqnwLq9e685Kj00QndOBXm0y21EQ32uH1okv7Ig==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-d2DMQnsXAkZDDk9bU/FhY/D74tbMAkboIGb+hq7kIIgOVcxOswhwLFZ/ajW/9NTesktz8Z14t40Ber+/Pny25A==", "cpu": [ "x64" ], @@ -2812,9 +2812,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-vvkrXO9HvkWMzKlvdM3y9oV3TEupfD/IWttFuTiKBa2oUSxAo9sptHYkPi43756f1jrVmagCn5PsFWJNO4TA5Q==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-YyfTK1SGmfeDJv6G3vSmVxjM914Xio7O57NzRKOyEQnmBT5tdXTzeWgkjrUh1jE8wCUu0f0ZZ+xDTwgys+E2ug==", "cpu": [ "arm" ], @@ -2829,9 +2829,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-pUKOi7wwNtRCBY/mGtSRAf/6DwOz+5XbFeifWuiFCxPvFEJrbUaF6naAh2d1bJVOCCwiZCCBGMRy708eoclvBw==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-+fv13RDSk+7wFYY846q5ig7X6G07JT7wbajk6p4rELXTIfS1c6gRHGhODETCfFVaPziP4IlvqyinNP8F8wc9uQ==", "cpu": [ "arm64" ], @@ -2846,9 +2846,9 @@ } }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-W4VRgMd+JdiC6+9S8ai/PqV5dwRfv/U77Es7yfWCR2RsKBloZQXQzwN9bbxErbo3LlGznw/7QZWBJRrMrQuwVg==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-BjWJI42cUUilIyQHZpQQeSjC/Ifj/UaIf4oj6lRHDcg5qgLHWe5bAUxuNjE6i7wi+TTN9YxUvBDkMAcm/hI8wg==", "cpu": [ "x64" ], @@ -2863,9 +2863,9 @@ } }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-vfdlEP+zAHYvPZnyz2HB1FTTjPxYLYl1A30JrEBpod7rqPZU04rah3ykcI6+7BRPehClHBCeoTMC7Bn0hi/CeA==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-rPv/mVaneZTuFESk/zDg3dFiZjpdipVMcLaF10Ns1fIyWdZ0ja79Ufm1eCFbk8KFNEX2dEx+vFEvD9n4bhEneg==", "cpu": [ "arm64" ], @@ -2880,9 +2880,9 @@ } }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-fuU+XxEbUzqsMEO41HK+oUd5vu4mkP7zi0UIs1HOl9N0h548GaC5MVHTk7ErrvNiRSY8ffNiCxhiy32YjpJD1Q==", + "version": "7.0.0-dev.20250904.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250904.1.tgz", + "integrity": "sha512-+twwqKYEv5UdZX5FRaBo0bDQgw/uPQjU3hqaqaO0Dhp1Ou8Ce4oi5hgwauB1j29JwBbvOi9/yoEcjsjT2Wsaxw==", "cpu": [ "x64" ], @@ -17553,9 +17553,9 @@ "dev": true }, "node_modules/typescript": { - "version": "6.0.0-dev.20250827", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20250827.tgz", - "integrity": "sha512-TNrtA9r9AMgInuUMAV5hVTQWClBfvG+HWhIanl7ZO8GWSwjzL9mRgauvwzMfXpdwoAR1h+XC5zSj9WbbGGOtxg==", + "version": "6.0.0-dev.20250905", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20250905.tgz", + "integrity": "sha512-3DUoON5ibUBadEPyd3Ot2K0S9c3R33DEguXdVKqfL+4tYKwHa5v3OIBdkqP0KQFRS7gqA+hhwPgdBHxFb0LSgQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index b6ca424aa3d..c74005171e4 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250827", + "typescript": "^6.0.0-dev.20250905", "typescript-eslint": "^8.39.0", "util": "^0.12.4", "webpack": "^5.94.0", From ceaaba2eb04ee73b0dda7165c9b2db1b4d096efc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 5 Sep 2025 14:41:05 -0700 Subject: [PATCH 0058/4355] Suppress moduleResolution error for now Opened #265420 to track proper fix --- src/tsconfig.monaco.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 95952c1c2ee..669abb9c332 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -10,6 +10,7 @@ "paths": {}, "module": "amd", "moduleResolution": "node", + "ignoreDeprecations": "6.0", "removeComments": false, "preserveConstEnums": true, "target": "ES2022", From c67f8834a708435034f19e3311ea97579f8bf7b1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 5 Sep 2025 14:48:25 -0700 Subject: [PATCH 0059/4355] Adding a few more ignoreDeprecations --- extensions/markdown-language-features/notebook/tsconfig.json | 1 + extensions/microsoft-authentication/tsconfig.json | 1 + 2 files changed, 2 insertions(+) diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index 426411ea857..48033ed968a 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "./dist/", "jsx": "react", "moduleResolution": "Node", + "ignoreDeprecations": "6.0", "allowSyntheticDefaultImports": true, "module": "es2020", "lib": [ diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 59947e2d995..5dd64b21dd9 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -4,6 +4,7 @@ "baseUrl": ".", "module": "commonjs", "moduleResolution": "node", + "ignoreDeprecations": "6.0", "noFallthroughCasesInSwitch": true, "noUnusedLocals": false, "outDir": "dist", From 46433f1a7575a0585acad10660c62a5b339d30eb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 5 Sep 2025 14:52:25 -0700 Subject: [PATCH 0060/4355] Fix ms auth compile error --- .eslint-plugin-local/index.js | 9 ++++++++- extensions/microsoft-authentication/src/node/flows.ts | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.eslint-plugin-local/index.js b/.eslint-plugin-local/index.js index 3646c8c4157..4f34ba08012 100644 --- a/.eslint-plugin-local/index.js +++ b/.eslint-plugin-local/index.js @@ -6,7 +6,14 @@ const glob = require('glob'); const path = require('path'); -require('ts-node').register({ experimentalResolver: true, transpileOnly: true }); +require('ts-node').register({ + experimentalResolver: true, + transpileOnly: true, + compilerOptions: { + "moduleResolution": "node", + "ignoreDeprecations": "6.0", + } +}); // Re-export all .ts files as rules /** @type {Record} */ diff --git a/extensions/microsoft-authentication/src/node/flows.ts b/extensions/microsoft-authentication/src/node/flows.ts index 2457f69cba0..71c3af56917 100644 --- a/extensions/microsoft-authentication/src/node/flows.ts +++ b/extensions/microsoft-authentication/src/node/flows.ts @@ -64,7 +64,6 @@ class DefaultLoopbackFlow implements IMsalFlow { prompt: loginHint ? undefined : 'select_account', windowHandle, claims, - redirectUri }); } } @@ -92,7 +91,6 @@ class UrlHandlerFlow implements IMsalFlow { prompt: loginHint ? undefined : 'select_account', windowHandle, claims, - redirectUri }); } } From 752c99b6f3046862ff71fa2b5c589e0e06751205 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 5 Sep 2025 16:03:22 -0700 Subject: [PATCH 0061/4355] tools: re-apply confirm response based on promise (#264791) This re-applies #263894 and fixes #264023 by removing a surprising behavior in the DeferredPromise where calling complete() with additional values will update the settled `deferred.value` without affecting the original promise (which can of course only resolve once.) --- src/vs/base/common/async.ts | 8 ++++++++ src/vs/base/test/common/async.test.ts | 16 ++++++++++++++++ .../chatProgressTypes/chatToolInvocation.ts | 11 ++++------- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 9f2962017f8..50656fa1035 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1745,6 +1745,10 @@ export class DeferredPromise { } public complete(value: T) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.completeCallback(value); this.outcome = { outcome: DeferredOutcome.Resolved, value }; @@ -1753,6 +1757,10 @@ export class DeferredPromise { } public error(err: unknown) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.errorCallback(err); this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index aacd7918ef0..c0048417319 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -1210,6 +1210,22 @@ suite('Async', () => { assert.strictEqual((await deferred.p.catch(e => e)).name, 'Canceled'); assert.strictEqual(deferred.isRejected, true); }); + + test('retains the original settled value', async () => { + const deferred = new async.DeferredPromise(); + assert.strictEqual(deferred.isResolved, false); + assert.strictEqual(deferred.value, undefined); + + deferred.complete(42); + assert.strictEqual(await deferred.p, 42); + assert.strictEqual(deferred.value, 42); + assert.strictEqual(deferred.isResolved, true); + + deferred.complete(-1); + assert.strictEqual(await deferred.p, 42); + assert.strictEqual(deferred.value, 42); + assert.strictEqual(deferred.isResolved, true); + }); }); suite('Promises.settled', () => { diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 7a41c23cede..0a04cdd65af 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -29,9 +29,8 @@ export class ChatToolInvocation implements IChatToolInvocation { return this._confirmDeferred; } - private _isConfirmed: ConfirmedReason | undefined; public get isConfirmed(): ConfirmedReason | undefined { - return this._isConfirmed; + return this._confirmDeferred.value; } private _resultDetails: IToolResult['toolResultDetails'] | undefined; @@ -65,12 +64,10 @@ export class ChatToolInvocation implements IChatToolInvocation { if (!this._confirmationMessages) { // No confirmation needed - this._isConfirmed = { type: ToolConfirmKind.ConfirmationNotNeeded }; - this._confirmDeferred.complete(this._isConfirmed); + this._confirmDeferred.complete({ type: ToolConfirmKind.ConfirmationNotNeeded }); } - this._confirmDeferred.p.then(confirmed => { - this._isConfirmed = confirmed; + this._confirmDeferred.p.then(() => { this._confirmationMessages = undefined; }); @@ -107,7 +104,7 @@ export class ChatToolInvocation implements IChatToolInvocation { invocationMessage: this.invocationMessage, pastTenseMessage: this.pastTenseMessage, originMessage: this.originMessage, - isConfirmed: this._isConfirmed, + isConfirmed: this._confirmDeferred.value, isComplete: true, source: this.source, resultDetails: isToolResultOutputDetails(this._resultDetails) From a2f5b4a308803584f0105df1559971c664db5972 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 5 Sep 2025 21:39:36 -0700 Subject: [PATCH 0062/4355] Make sure all text nodes in chat markdown are wrapped in elements (#264049) For #264043 I'm pretty sure the tests where showing bugs where top level text nodes weren't being replaces --- .../contrib/chat/browser/chatMarkdownRenderer.ts | 11 ++++++----- .../ChatMarkdownRenderer_html_comments.0.snap | 4 ++-- .../ChatMarkdownRenderer_invalid_HTML.0.snap | 2 +- ...rkdownRenderer_invalid_HTML_with_attributes.0.snap | 2 +- ...rkdownRenderer_mixed_valid_and_invalid_HTML.0.snap | 8 ++++---- .../ChatMarkdownRenderer_self-closing_elements.0.snap | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index 6e56855f3d3..69a6c324d84 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -97,13 +97,14 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { : markdown; const result = super.render(mdWithBody, options, outElement); - // In some cases, the renderer can return text that is not inside a

, - // but our CSS expects text to be in a

for margin to be applied properly. + // In some cases, the renderer can return top level text nodes but our CSS expects + // all text to be in a

for margin to be applied properly. // So just normalize it. result.element.normalize(); - const lastChild = result.element.lastChild; - if (lastChild?.nodeType === Node.TEXT_NODE && lastChild.textContent?.trim()) { - lastChild.replaceWith($('p', undefined, lastChild.textContent)); + for (const child of result.element.childNodes) { + if (child.nodeType === Node.TEXT_NODE && child.textContent?.trim()) { + child.replaceWith($('p', undefined, child.textContent)); + } } return this.attachCustomHover(result); } diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_html_comments.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_html_comments.0.snap index 52024ad2927..8766316dffd 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_html_comments.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_html_comments.0.snap @@ -1,3 +1,3 @@ -

+

-<!-- comment1 <div></div> -->

content

<!-- comment2 -->

\ No newline at end of file +<!-- comment1 <div></div> -->

content

<!-- comment2 -->

\ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap index 7e1911ec47b..2476252b21e 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap @@ -1,3 +1,3 @@
-

1<canvas>2</canvas>

<details>3</details>4

\ No newline at end of file +

1<canvas>2</canvas>

<details>3</details>4

\ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap index da57db4deea..e0de51dfb77 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap @@ -1,3 +1,3 @@
-

1

<details id="id1" style="display: none">2<details id="my id 2">3</details></details>4

\ No newline at end of file +

1

<details id="id1" style="display: none">2<details id="my id 2">3</details></details>4

\ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_mixed_valid_and_invalid_HTML.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_mixed_valid_and_invalid_HTML.0.snap index f3db82a0999..a9b0bed220b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_mixed_valid_and_invalid_HTML.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_mixed_valid_and_invalid_HTML.0.snap @@ -1,11 +1,11 @@
-

heading

+

heading

<details> -

    +

    • <details>1</details>
    • hi
    • -
    +

</details> -

<canvas>canvas here</canvas>

<details></details>

\ No newline at end of file +

<canvas>canvas here</canvas>

<details></details>

\ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap index 01a9119248e..2a77a1cc216 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap @@ -1,3 +1,3 @@
-

<area>



<input value="test" type="text">

\ No newline at end of file +

<area>



<input value="test" type="text">

\ No newline at end of file From dfc6a2ab333032954dc62e85a51fef2d0793c2bb Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Sat, 6 Sep 2025 00:49:27 -0700 Subject: [PATCH 0063/4355] remove old protection for multiple clicks (#265414) Fixes #265034 Original Issue #41363 no longer repros --- src/vs/base/browser/ui/dropdown/dropdown.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 4bb319918f3..9476e374949 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -61,9 +61,8 @@ export class BaseDropdown extends ActionRunner { for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) { this._register(addDisposableListener(this._label, event, e => { - if (isMouseEvent(e) && (e.detail > 1 || e.button !== 0)) { + if (isMouseEvent(e) && e.button !== 0) { // prevent right click trigger to allow separate context menu (https://github.com/microsoft/vscode/issues/151064) - // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363) return; } From c8f1c4eca9321223ffec32ea984e93d3601e4e23 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 6 Sep 2025 13:28:15 +0200 Subject: [PATCH 0064/4355] Revert "smoke - use `workbench.action.clearEditorHistoryWithoutConfirm` consistently" (#265471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "smoke - use `workbench.action.clearEditorHistoryWithoutConfirm` consi…" This reverts commit e910fd5e1d0217cb61776296be22d8d2e28f4a6b. --- test/automation/src/quickaccess.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index 2c7c4054252..d417a4a2c29 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -7,6 +7,7 @@ import { Editors } from './editors'; import { Code } from './code'; import { QuickInput } from './quickinput'; import { basename, isAbsolute } from 'path'; +import { Quality } from './application'; enum QuickAccessKind { Files = 1, @@ -22,7 +23,11 @@ export class QuickAccess { // make sure the file quick access is not "polluted" // with entries from the editor history when opening - await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); + if (this.code.quality !== Quality.Stable) { + await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); + } else { + await this.runCommand('workbench.action.clearEditorHistory'); + } const PollingStrategy = { Stop: true, From 2c9358044b68bfeb5ac0d84695b8266c86e34639 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sat, 6 Sep 2025 16:39:05 +0200 Subject: [PATCH 0065/4355] Chat - move variable declaration into CSS (#265480) --- .../workbench/contrib/chat/browser/chatWidget.ts | 8 -------- .../workbench/contrib/chat/browser/media/chat.css | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 7c9becab9c1..fd0fbffc771 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -723,14 +723,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.scrollToEnd(); })); - // Font size variables - this.container.style.setProperty('--vscode-chat-font-size-body-xs', '0.846em' /* 11px */); - this.container.style.setProperty('--vscode-chat-font-size-body-s', '0.923em' /* 12px */); - this.container.style.setProperty('--vscode-chat-font-size-body-m', '1em' /* 13px */); - this.container.style.setProperty('--vscode-chat-font-size-body-l', '1.077em' /* 14px */); - this.container.style.setProperty('--vscode-chat-font-size-body-xl', '1.231em' /* 16px */); - this.container.style.setProperty('--vscode-chat-font-size-body-xxl', '1.538em' /* 20px */); - // Update the font family and size this._register(autorun(reader => { const fontFamily = this.chatLayoutService.fontFamily.read(reader); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 766d459ef2f..0932407dd7a 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -3,6 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.interactive-session { + /* 11px when base font is 13px */ + --vscode-chat-font-size-body-xs: 0.846em; + /* 12px when base font is 13px */ + --vscode-chat-font-size-body-s: 0.923em; + /* 13px when base font is 13px */ + --vscode-chat-font-size-body-m: 1em; + /* 14px when base font is 13px */ + --vscode-chat-font-size-body-l: 1.077em; + /* 16px when base font is 13px */ + --vscode-chat-font-size-body-xl: 1.231em; + /* 20px when base font is 13px */ + --vscode-chat-font-size-body-xxl: 1.538em; +} + .interactive-session { max-width: 950px; margin: auto; From ece215ea9b72e9dc7819a76d414f972c87113fb8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 6 Sep 2025 15:26:32 -0700 Subject: [PATCH 0066/4355] Don't create chat sessions as "editor" location (#265516) This means inline chat. I got into a state where a chat session was loaded in the panel instead of a chat editor and the location caused issues --- .../api/browser/mainThreadChatAgents2.ts | 4 +- .../api/common/extHostChatAgents2.ts | 2 +- .../api/common/extHostChatSessions.ts | 4 +- .../api/common/extHostTypeConverters.ts | 8 +- .../browser/mainThreadChatSessions.test.ts | 2 +- .../browser/actions/chatAccessibilityHelp.ts | 2 +- .../chat/browser/actions/chatActions.ts | 14 ++-- .../browser/actions/chatContextActions.ts | 6 +- .../browser/actions/chatExecuteActions.ts | 20 ++--- .../chat/browser/actions/chatMoveActions.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 20 ++--- .../browser/chatEditing/chatEditingActions.ts | 2 +- .../chatEditingCodeEditorIntegration.ts | 18 ++--- .../chatEditing/chatEditingServiceImpl.ts | 2 +- .../browser/chatEditing/chatEditingSession.ts | 4 +- .../chatEditingNotebookEditorIntegration.ts | 4 +- .../contrib/chat/browser/chatEditor.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 6 +- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 8 +- .../browser/chatParticipant.contribution.ts | 4 +- .../contrib/chat/browser/chatQuick.ts | 14 ++-- .../chat/browser/chatSessions.contribution.ts | 2 +- .../contrib/chat/browser/chatSessions.ts | 66 +++++++-------- .../contrib/chat/browser/chatSetup.ts | 18 ++--- .../contrib/chat/browser/chatViewPane.ts | 14 ++-- .../browser/contrib/chatImplicitContext.ts | 4 +- .../browser/contrib/chatInputCompletions.ts | 4 +- .../modelPicker/modePickerActionItem.ts | 4 +- .../viewsWelcome/chatViewWelcomeController.ts | 2 +- .../contrib/chat/common/chatAgents.ts | 2 +- .../contrib/chat/common/chatModel.ts | 12 +-- .../contrib/chat/common/chatRequestParser.ts | 4 +- .../contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 14 ++-- .../chat/common/chatWidgetHistoryService.ts | 2 +- .../contrib/chat/common/constants.ts | 16 ++-- .../actions/voiceChatActions.ts | 14 ++-- .../test/browser/chatEditingService.test.ts | 32 ++++---- .../chat/test/common/chatModel.test.ts | 8 +- .../test/common/chatRequestParser.test.ts | 4 +- .../chat/test/common/chatService.test.ts | 26 +++--- .../chat/test/common/voiceChatService.test.ts | 2 +- .../emptyTextEditorHint.ts | 38 ++++----- .../browser/inlineChatController.ts | 38 ++++----- .../browser/inlineChatCurrentLine.ts | 54 ++++++------- .../browser/inlineChatSessionServiceImpl.ts | 8 +- .../test/browser/inlineChatController.test.ts | 24 +++--- .../test/browser/inlineChatSession.test.ts | 80 +++++++++---------- .../mcp/browser/openPanelChatAndGetWidget.ts | 6 +- .../chat/notebook.chat.contribution.ts | 12 +-- .../controller/chat/notebookChatController.ts | 12 +-- .../browser/commandsQuickAccess.ts | 10 +-- .../tasks/browser/abstractTaskService.ts | 22 ++--- .../browser/tools/monitoring/outputMonitor.ts | 20 ++--- 55 files changed, 366 insertions(+), 360 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 2a95d75c278..e82c4365e5b 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -9,10 +9,10 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { Disposable, DisposableMap, IDisposable } from '../../../base/common/lifecycle.js'; import { revive } from '../../../base/common/marshalling.js'; +import { Schemas } from '../../../base/common/network.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; -import { Schemas } from '../../../base/common/network.js'; import { Position } from '../../../editor/common/core/position.js'; import { Range } from '../../../editor/common/core/range.js'; import { getWordAtText } from '../../../editor/common/core/wordHelper.js'; @@ -209,7 +209,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA metadata: revive(metadata), slashCommands: [], disambiguation: [], - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask, ChatModeKind.Agent, ChatModeKind.Edit], }, impl); diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index cac7ceb7a7d..e05fb91450a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -495,7 +495,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS // in-place converting for location-data let location: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined; - if (request.locationData?.type === ChatAgentLocation.Editor) { + if (request.locationData?.type === ChatAgentLocation.TextEditor) { // editor data const document = this._documents.getDocument(request.locationData.document); location = new extHostTypes.ChatRequestEditorData(document, typeConvert.Selection.to(request.locationData.selection), typeConvert.Range.to(request.locationData.wholeRange)); diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 9259b3a2750..c7dbfe89330 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -7,6 +7,7 @@ import type * as vscode from 'vscode'; import { coalesce } from '../../../base/common/arrays.js'; import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; +import { revive } from '../../../base/common/marshalling.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../platform/log/common/log.js'; @@ -21,7 +22,6 @@ import { ExtHostLanguageModels } from './extHostLanguageModels.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import * as typeConvert from './extHostTypeConverters.js'; import * as extHostTypes from './extHostTypes.js'; -import { revive } from '../../../base/common/marshalling.js'; class ExtHostChatSession { private _stream: ChatAgentResponseStream; @@ -246,7 +246,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio agentId: id, message: '', variables: { variables: [] }, - location: ChatAgentLocation.Panel, + location: ChatAgentLocation.Chat, }, { $handleProgressChunk: (requestId, chunks) => { return this._proxy.$handleProgressChunk(handle, id, requestId, chunks); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index d3e2be32c6f..4059635412c 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3166,8 +3166,8 @@ export namespace ChatLocation { switch (loc) { case ChatAgentLocation.Notebook: return types.ChatLocation.Notebook; case ChatAgentLocation.Terminal: return types.ChatLocation.Terminal; - case ChatAgentLocation.Panel: return types.ChatLocation.Panel; - case ChatAgentLocation.Editor: return types.ChatLocation.Editor; + case ChatAgentLocation.Chat: return types.ChatLocation.Panel; + case ChatAgentLocation.TextEditor: return types.ChatLocation.Editor; } } @@ -3175,8 +3175,8 @@ export namespace ChatLocation { switch (loc) { case types.ChatLocation.Notebook: return ChatAgentLocation.Notebook; case types.ChatLocation.Terminal: return ChatAgentLocation.Terminal; - case types.ChatLocation.Panel: return ChatAgentLocation.Panel; - case types.ChatLocation.Editor: return ChatAgentLocation.Editor; + case types.ChatLocation.Panel: return ChatAgentLocation.Chat; + case types.ChatLocation.Editor: return ChatAgentLocation.TextEditor; } } } diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 219fa4f73df..0fcbee571b3 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -380,7 +380,7 @@ suite('MainThreadChatSessions', function () { requestId: 'test-request', agentId: 'test-agent', message: 'my prompt', - location: ChatAgentLocation.Panel, + location: ChatAgentLocation.Chat, variables: { variables: [] } }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9c59084fbaf..5680cc20354 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -22,7 +22,7 @@ export class PanelChatAccessibilityHelp implements IAccessibleViewImplementation readonly priority = 107; readonly name = 'panelChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.inQuickChat.negate(), ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask), ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); + readonly when = ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), ChatContextKeys.inQuickChat.negate(), ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask), ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'panelChat'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index dafc44b217f..9f6e4d853aa 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isAncestorOfActiveElement } from '../../../../../base/browser/dom.js'; +import { mainWindow } from '../../../../../base/browser/window.js'; import { toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js'; import { coalesce } from '../../../../../base/common/arrays.js'; import { timeout } from '../../../../../base/common/async.js'; @@ -21,6 +22,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; import { Position } from '../../../../../editor/common/core/position.js'; +import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; import { SuggestController } from '../../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../../nls.js'; import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js'; @@ -48,13 +50,14 @@ import { GroupDirection, IEditorGroupsService } from '../../../../services/edito import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHostService } from '../../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { IPreferencesService } from '../../../../services/preferences/common/preferences.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { EXTENSIONS_CATEGORY, IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; import { IChatAgentResult, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { ChatEntitlement, IChatEntitlementService } from '../../common/chatEntitlementService.js'; +import { IChatResponseModel } from '../../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; @@ -63,6 +66,7 @@ import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; +import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; @@ -72,10 +76,6 @@ import { VIEWLET_ID } from '../chatSessions.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; -import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; -import { IChatResponseModel } from '../../common/chatModel.js'; -import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; -import { mainWindow } from '../../../../../base/browser/window.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -303,14 +303,14 @@ abstract class OpenChatGlobalAction extends Action2 { } async function waitForDefaultAgent(chatAgentService: IChatAgentService, mode: ChatModeKind): Promise { - const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel, mode); + const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode); if (defaultAgent) { return; } await Promise.race([ Event.toPromise(Event.filter(chatAgentService.onDidChangeAgents, () => { - const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel, mode); + const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode); return Boolean(defaultAgent); })), timeout(60_000).then(() => { throw new Error('Timed out waiting for default agent'); }) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 67b319c3c1e..6f0f67dc931 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -65,7 +65,7 @@ async function withChatView(accessor: ServicesAccessor): Promise(ConfigurationExtensions.Configuration); @@ -795,7 +795,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { detail: nls.localize('clear', "Start a new chat"), sortText: 'z2_clear', executeImmediately: true, - locations: [ChatAgentLocation.Panel] + locations: [ChatAgentLocation.Chat] }, async () => { commandService.executeCommand(ACTION_ID_NEW_CHAT); })); @@ -805,7 +805,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { sortText: `z3_${SAVE_TO_PROMPT_SLASH_COMMAND_NAME}`, executeImmediately: true, silent: true, - locations: [ChatAgentLocation.Panel] + locations: [ChatAgentLocation.Chat] }, async () => { const { lastFocusedWidget } = chatWidgetService; assertDefined( @@ -820,10 +820,10 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { detail: '', sortText: 'z1_help', executeImmediately: true, - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask] }, async (prompt, progress) => { - const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); + const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); const agents = chatAgentService.getAgents(); // Report prefix @@ -839,7 +839,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { // Report agent list const agentText = (await Promise.all(agents .filter(a => !a.isDefault && !a.isCore) - .filter(a => a.locations.includes(ChatAgentLocation.Panel)) + .filter(a => a.locations.includes(ChatAgentLocation.Chat)) .map(async a => { const description = a.description ? `- ${a.description}` : ''; const agentMarkdown = instantiationService.invokeFunction(accessor => agentToMarkdown(a, true, accessor)); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index a2efcd696f3..426f0db6071 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -64,7 +64,7 @@ export function getEditingSessionContext(accessor: ServicesAccessor, args: any[] const chatEditingService = accessor.get(IChatEditingService); let chatWidget = context ? chatWidgetService.getWidgetBySessionId(context.sessionId) : undefined; if (!chatWidget) { - chatWidget = chatWidgetService.lastFocusedWidget ?? chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel).find(w => w.supportsChangingModes); + chatWidget = chatWidgetService.lastFocusedWidget ?? chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat).find(w => w.supportsChangingModes); } if (!chatWidget?.viewModel) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index b2f4fa3b9d4..9e17289b839 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -14,32 +14,32 @@ import { themeColorFromId } from '../../../../../base/common/themables.js'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../../editor/browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../editor/browser/observableCodeEditor.js'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js'; -import { RenderOptions, LineSource, renderLines } from '../../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; -import { diffAddDecoration, diffWholeLineAddDecoration, diffDeleteDecoration } from '../../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; +import { LineSource, renderLines, RenderOptions } from '../../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; import { EditorOption, IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; -import { LineRange } from '../../../../../editor/common/core/ranges/lineRange.js'; import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; +import { LineRange } from '../../../../../editor/common/core/ranges/lineRange.js'; import { Selection } from '../../../../../editor/common/core/selection.js'; import { IDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; import { DetailedLineRangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; +import { IEditorDecorationsCollection } from '../../../../../editor/common/editorCommon.js'; import { IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../../editor/common/model.js'; import { ModelDecorationOptions } from '../../../../../editor/common/model/textModel.js'; +import { InlineDecoration, InlineDecorationType } from '../../../../../editor/common/viewModel/inlineDecorations.js'; import { localize } from '../../../../../nls.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; -import { MenuWorkbenchToolBar, HiddenItemStrategy } from '../../../../../platform/actions/browser/toolbar.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { TextEditorSelectionRevealType } from '../../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { overviewRulerModifiedForeground, minimapGutterModifiedBackground, overviewRulerAddedForeground, minimapGutterAddedBackground, overviewRulerDeletedForeground, minimapGutterDeletedBackground } from '../../../scm/common/quickDiff.js'; +import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../../scm/common/quickDiff.js'; import { IChatAgentService } from '../../common/chatAgents.js'; import { IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; -import { isTextDiffEditorForEntry } from './chatEditing.js'; -import { IEditorDecorationsCollection } from '../../../../../editor/common/editorCommon.js'; import { ChatAgentLocation } from '../../common/constants.js'; -import { InlineDecoration, InlineDecorationType } from '../../../../../editor/common/viewModel/inlineDecorations.js'; +import { isTextDiffEditorForEntry } from './chatEditing.js'; export interface IDocumentDiff2 extends IDocumentDiff { @@ -617,7 +617,7 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito // Use the 'show' argument to control the diff state if provided if (show !== undefined ? show : !isDiffEditor) { // Open DIFF editor - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)?.fullName; + const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; const diffEditor = await this._editorService.openEditor({ original: { resource: this._entry.originalURI }, modified: { resource: this._entry.modifiedURI }, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index a641ffe876d..fab2b15fec6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -430,7 +430,7 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider } const isModified = this._modifiedUris.get().some(e => e.toString() === uri.toString()); if (isModified) { - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)?.fullName; + const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; return { weight: 1000, letter: Codicon.diffModified, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 4527f5b1639..463302e8b69 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -521,9 +521,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio get applyCodeBlockSuggestionId() { return responseModel.request?.modeInfo?.applyCodeBlockSuggestionId; } get feature(): string { - if (responseModel.session.initialLocation === ChatAgentLocation.Panel) { + if (responseModel.session.initialLocation === ChatAgentLocation.Chat) { return 'sideBarChat'; - } else if (responseModel.session.initialLocation === ChatAgentLocation.Editor) { + } else if (responseModel.session.initialLocation === ChatAgentLocation.TextEditor) { return 'inlineChat'; } return responseModel.session.initialLocation; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts index 8b59981514a..2b6f1f1c40c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts @@ -7,8 +7,8 @@ import { Disposable, IDisposable, toDisposable } from '../../../../../../base/co import { autorun, debouncedObservable, IObservable, ISettableObservable, observableFromEvent, observableValue } from '../../../../../../base/common/observable.js'; import { basename } from '../../../../../../base/common/resources.js'; import { assertType } from '../../../../../../base/common/types.js'; -import { LineRange } from '../../../../../../editor/common/core/ranges/lineRange.js'; import { Range } from '../../../../../../editor/common/core/range.js'; +import { LineRange } from '../../../../../../editor/common/core/ranges/lineRange.js'; import { nullDocumentDiff } from '../../../../../../editor/common/diff/documentDiffProvider.js'; import { PrefixSumComputer } from '../../../../../../editor/common/model/prefixSumComputer.js'; import { localize } from '../../../../../../nls.js'; @@ -655,7 +655,7 @@ class ChatEditingNotebookEditorWidgetIntegration extends Disposable implements I } async toggleDiff(_change: IModifiedFileEntryChangeHunk | undefined, _show?: boolean): Promise { - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)?.fullName; + const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; const diffInput: IResourceDiffEditorInput = { original: { resource: this._entry.originalURI }, modified: { resource: this._entry.modifiedURI }, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 6fc3cd0233f..e424e1ae8cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -75,7 +75,7 @@ export class ChatEditor extends EditorPane { this._widget = this._register( scopedInstantiationService.createInstance( ChatWidget, - ChatAgentLocation.Panel, + ChatAgentLocation.Chat, undefined, { autoScroll: mode => mode !== ChatModeKind.Ask, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index cd4fe962c3e..4e7ea4b684b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -173,12 +173,12 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override async resolve(): Promise { if (this.resource.scheme === Schemas.vscodeChatSession) { - this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Editor, CancellationToken.None); + this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (typeof this.sessionId === 'string') { this.model = await this.chatService.getOrRestoreSession(this.sessionId) - ?? this.chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None); + ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); } else if (!this.options.target) { - this.model = this.chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None); + this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 16bc7d55578..2e927b7a08c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1237,7 +1237,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge hoverDelegate, hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu actionViewItemProvider: (action, options) => { - if (this.location === ChatAgentLocation.Panel || this.location === ChatAgentLocation.Editor) { + if (this.location === ChatAgentLocation.Chat || this.location === ChatAgentLocation.TextEditor) { if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID || action.id === ChatEditingSessionSubmitAction.ID) && action instanceof MenuItemAction) { const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, { ...options, menuAsChild: false }); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 61af34a86cb..c6a71c24de6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -14,8 +14,8 @@ import { IListElementRenderDetails, IListVirtualDelegate } from '../../../../bas import { ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; import { IAction } from '../../../../base/common/actions.js'; import { coalesce, distinct } from '../../../../base/common/arrays.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { findLast } from '../../../../base/common/arraysFind.js'; +import { Codicon } from '../../../../base/common/codicons.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { canceledName } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; @@ -52,9 +52,9 @@ import { IChatAgentMetadata } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatConfirmation, IChatContentReference, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatPullRequestContent, IChatMultiDiffData, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatElicitationRequest, IChatThinkingPart } from '../common/chatService.js'; -import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatConfirmation, IChatContentReference, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMultiDiffData, IChatPullRequestContent, IChatTask, IChatTaskSerialized, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop } from '../common/chatService.js'; import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; +import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { getNWords } from '../common/chatWordCounter.js'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; @@ -556,7 +556,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { // Only fire for chat view instance - if (widget.location === ChatAgentLocation.Panel && + if (widget.location === ChatAgentLocation.Chat && typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { @@ -404,7 +404,7 @@ class LocalChatSessionsProvider extends Disposable implements IChatSessionItemPr })); // Check for existing chat widgets and register listeners - const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel) + const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) .filter(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); existingWidgets.forEach(widget => { @@ -511,7 +511,7 @@ class LocalChatSessionsProvider extends Disposable implements IChatSessionItemPr }); // Add chat view instance - const chatWidget = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel) + const chatWidget = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) .find(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; const widgetSession: ILocalChatSessionItem & ChatSessionItemWithProvider = { diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index cf875434caa..edac5cc0c7a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './media/chatSetup.css'; import { $ } from '../../../../base/browser/dom.js'; import { IButton } from '../../../../base/browser/ui/button/button.js'; import { Dialog, DialogContentsAlignment } from '../../../../base/browser/ui/dialog/dialog.js'; @@ -57,6 +56,7 @@ import { nullExtensionDescription } from '../../../services/extensions/common/ex import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../chat/common/languageModelToolsService.js'; import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; @@ -73,8 +73,8 @@ import { ILanguageModelsService } from '../common/languageModels.js'; import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './actions/chatActions.js'; import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; +import './media/chatSetup.css'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; -import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -112,7 +112,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let id: string; let description = ChatMode.Ask.description.get(); switch (location) { - case ChatAgentLocation.Panel: + case ChatAgentLocation.Chat: if (mode === ChatModeKind.Ask) { id = 'setup.chat'; } else if (mode === ChatModeKind.Edit) { @@ -126,7 +126,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { case ChatAgentLocation.Terminal: id = 'setup.terminal'; break; - case ChatAgentLocation.Editor: + case ChatAgentLocation.TextEditor: id = 'setup.editor'; break; case ChatAgentLocation.Notebook: @@ -145,15 +145,15 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { const disposables = new DisposableStore(); // Register VSCode agent - const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Panel, undefined, context, controller); + const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, undefined, context, controller); disposables.add(vscodeDisposable); // Register workspace agent - const { disposable: workspaceDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.workspace', 'workspace', false, localize2('workspaceAgentDescription', "Ask about your workspace").value, ChatAgentLocation.Panel, undefined, context, controller); + const { disposable: workspaceDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.workspace', 'workspace', false, localize2('workspaceAgentDescription', "Ask about your workspace").value, ChatAgentLocation.Chat, undefined, context, controller); disposables.add(workspaceDisposable); // Register terminal agent - const { disposable: terminalDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.terminal.agent', 'terminal', false, localize2('terminalAgentDescription', "Ask how to do something in the terminal").value, ChatAgentLocation.Panel, undefined, context, controller); + const { disposable: terminalDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.terminal.agent', 'terminal', false, localize2('terminalAgentDescription', "Ask how to do something in the terminal").value, ChatAgentLocation.Chat, undefined, context, controller); disposables.add(terminalDisposable); // Register tools @@ -857,7 +857,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr // Panel Agents const panelAgentDisposables = disposables.add(new DisposableStore()); for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) { - const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Panel, mode, context, controller); + const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller); panelAgentDisposables.add(disposable); panelAgentDisposables.add(agent.onUnresolvableError(() => { const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); @@ -876,7 +876,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr // Inline Agents disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Editor, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.TextEditor, undefined, context, controller).disposable); } // Built-In Agent + Tool (unless installed, signed-in and enabled) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 495670bb30e..3ad7f758cfb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -32,9 +32,9 @@ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatModel } from '../common/chatModel.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; import { IChatService } from '../common/chatService.js'; -import { IChatSessionsService, IChatSessionsExtensionPoint } from '../common/chatSessionsService.js'; -import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; +import { IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; +import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { ChatWidget, IChatViewState } from './chatWidget.js'; import { ChatViewWelcomeController, IViewWelcomeDelegate } from './viewsWelcome/chatViewWelcomeController.js'; @@ -56,7 +56,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { private _restoringSession: Promise | undefined; constructor( - private readonly chatOptions: { location: ChatAgentLocation.Panel }, + private readonly chatOptions: { location: ChatAgentLocation.Chat }, options: IViewPaneOptions, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -80,7 +80,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.memento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID, this.storageService); this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState; - if (this.chatOptions.location === ChatAgentLocation.Panel && !this.viewState.hasMigratedCurrentSession) { + if (this.chatOptions.location === ChatAgentLocation.Chat && !this.viewState.hasMigratedCurrentSession) { const editsMemento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID + `-edits`, this.storageService); const lastEditsState = editsMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState; if (lastEditsState.sessionId) { @@ -197,7 +197,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { { viewId: this.id }, { autoScroll: mode => mode !== ChatModeKind.Ask, - renderFollowups: this.chatOptions.location === ChatAgentLocation.Panel, + renderFollowups: this.chatOptions.location === ChatAgentLocation.Chat, supportsFileReferences: true, rendererOptions: { renderTextEditsAsSummary: (uri) => { @@ -207,7 +207,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { progressMessageAtBottomOfResponse: mode => mode !== ChatModeKind.Ask, }, editorOverflowWidgetsDomNode: editorOverflowNode, - enableImplicitContext: this.chatOptions.location === ChatAgentLocation.Panel, + enableImplicitContext: this.chatOptions.location === ChatAgentLocation.Chat, enableWorkingSet: 'explicit', supportsChangingModes: true, }, @@ -266,7 +266,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } } - const newModel = await (URI.isUri(sessionId) ? this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Panel, CancellationToken.None) : this.chatService.getOrRestoreSession(sessionId)); + const newModel = await (URI.isUri(sessionId) ? this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Chat, CancellationToken.None) : this.chatService.getOrRestoreSession(sessionId)); await this.updateModel(newModel, viewState); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 7ec2881066c..32bde44a062 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -19,8 +19,8 @@ import { EditorsOrder } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { getNotebookEditorFromEditorPane, INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; -import { IChatRequestImplicitVariableEntry, IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatService } from '../../common/chatService.js'; +import { IChatRequestImplicitVariableEntry, IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js'; import { getPromptsTypeForLanguageId } from '../../common/promptSyntax/promptTypes.js'; @@ -221,7 +221,7 @@ export class ChatImplicitContextContribution extends Disposable implements IWork const isPromptFile = languageId && getPromptsTypeForLanguageId(languageId) !== undefined; - const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Editor)]; + const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.TextEditor)]; for (const widget of widgets) { if (!widget.input.implicitContext) { continue; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index f31d26404f5..29560be4f85 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -494,7 +494,7 @@ class AgentCompletions extends Disposable { } const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (widget?.location !== ChatAgentLocation.Panel || widget.input.currentModeKind !== ChatModeKind.Ask) { + if (widget?.location !== ChatAgentLocation.Chat || widget.input.currentModeKind !== ChatModeKind.Ask) { return; } @@ -805,7 +805,7 @@ class BuiltinDynamicCompletions extends Disposable { return; } - if (widget.location === ChatAgentLocation.Editor) { + if (widget.location === ChatAgentLocation.TextEditor) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts index aaee91c3782..c6d1973f689 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts @@ -46,7 +46,7 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { class: undefined, enabled: true, checked: currentMode.id === mode.id, - tooltip: chatAgentService.getDefaultAgent(ChatAgentLocation.Panel, mode.kind)?.description ?? action.tooltip, + tooltip: chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode.kind)?.description ?? action.tooltip, run: async () => { const result = await commandService.executeCommand(ToggleAgentModeActionId, { modeId: mode.id } satisfies IToggleChatModeArgs); this.renderLabel(this.element!); @@ -62,7 +62,7 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { class: undefined, enabled: true, checked: currentMode.id === mode.id, - tooltip: mode.description.get() ?? chatAgentService.getDefaultAgent(ChatAgentLocation.Panel, mode.kind)?.description ?? action.tooltip, + tooltip: mode.description.get() ?? chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode.kind)?.description ?? action.tooltip, run: async () => { const result = await commandService.executeCommand(ToggleAgentModeActionId, { modeId: mode.id } satisfies IToggleChatModeArgs); this.renderLabel(this.element!); diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index acada1bb56f..cfcb9cf8d1e 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -217,7 +217,7 @@ export class ChatViewWelcomePart extends Disposable { }); if (!this.chatWidgetService.lastFocusedWidget) { - const widgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel); + const widgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat); if (widgets.length) { widgets[0].setInput(prompt.prompt); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 338aa38304f..45a8b1d2109 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -321,7 +321,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { this._extensionAgentRegistered.set(extensionAgentRegistered); if (toolsAgentRegistered !== this._hasToolsAgent) { this._hasToolsAgent = toolsAgentRegistered; - this._onDidChangeAgents.fire(this.getDefaultAgent(ChatAgentLocation.Panel, ChatModeKind.Agent)); + this._onDidChangeAgents.fire(this.getDefaultAgent(ChatAgentLocation.Chat, ChatModeKind.Agent)); } } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 8cea05b568c..cf07583a8fc 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { asArray } from '../../../../base/common/arrays.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString, MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -19,18 +20,17 @@ import { generateUuid } from '../../../../base/common/uuid.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { TextEdit } from '../../../../editor/common/languages.js'; +import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { CellUri, ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { migrateLegacyTerminalToolSpecificData } from './chat.js'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; -import { IChatRequestVariableEntry, ChatRequestToolReferenceEntry } from './chatVariableEntries.js'; +import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; -import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; -import { BugIndicatingError } from '../../../../base/common/errors.js'; export const CHAT_ATTACHABLE_IMAGE_MIME_TYPES: Record = { @@ -1206,7 +1206,7 @@ function normalizeOldFields(raw: ISerializableChatDataIn): void { } if ((raw.initialLocation as any) === 'editing-session') { - raw.initialLocation = ChatAgentLocation.Panel; + raw.initialLocation = ChatAgentLocation.Chat; } } @@ -1370,7 +1370,7 @@ export class ChatModel extends Disposable implements IChatModel { } private get _defaultAgent() { - return this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel, ChatModeKind.Ask); + return this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, ChatModeKind.Ask); } get requesterUsername(): string { diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 197f911ae7f..0bcdcf5b046 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; +import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { IChatAgentData, IChatAgentService } from './chatAgents.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from './chatParserTypes.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; @@ -32,7 +32,7 @@ export class ChatRequestParser { @IPromptsService private readonly promptsService: IPromptsService, ) { } - parseChatRequest(sessionId: string, message: string, location: ChatAgentLocation = ChatAgentLocation.Panel, context?: IChatParserContext): IParsedChatRequest { + parseChatRequest(sessionId: string, message: string, location: ChatAgentLocation = ChatAgentLocation.Chat, context?: IChatParserContext): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls const toolsByName = new Map(); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 631574b3cac..f9270b91e95 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -622,7 +622,7 @@ export interface IChatSendRequestData extends IChatSendRequestResponseState { } export interface IChatEditorLocationData { - type: ChatAgentLocation.Editor; + type: ChatAgentLocation.TextEditor; document: URI; selection: ISelection; wholeRange: IRange; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 432547d1883..d88ad4cc6a6 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -180,7 +180,7 @@ export class ChatService extends Disposable implements IChatService { private saveState(): void { const liveChats = Array.from(this._sessionModels.values()) .filter(session => - this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Panel || session.initialLocation === ChatAgentLocation.Editor)); + this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.TextEditor)); if (this.useFileStorage) { this._chatSessionStore.storeSessions(liveChats); @@ -496,7 +496,7 @@ export class ChatService extends Disposable implements IChatService { private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken): ChatModel { const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, location); - if (location === ChatAgentLocation.Panel) { + if (location === ChatAgentLocation.Chat) { model.startEditingSession(isGlobalEditingSession); } @@ -517,7 +517,7 @@ export class ChatService extends Disposable implements IChatService { async activateDefaultAgent(location: ChatAgentLocation): Promise { await this.extensionService.whenInstalledExtensionsRegistered(); - const defaultAgentData = this.chatAgentService.getContributedDefaultAgent(location) ?? this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel); + const defaultAgentData = this.chatAgentService.getContributedDefaultAgent(location) ?? this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Chat); if (!defaultAgentData) { throw new ErrorNoTelemetry('No default agent contributed'); } @@ -561,7 +561,7 @@ export class ChatService extends Disposable implements IChatService { return undefined; } - const session = this._startSession(sessionData, sessionData.initialLocation ?? ChatAgentLocation.Panel, true, CancellationToken.None); + const session = this._startSession(sessionData, sessionData.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); const isTransferred = this.transferredSessionData?.sessionId === sessionId; if (isTransferred) { @@ -610,7 +610,7 @@ export class ChatService extends Disposable implements IChatService { } loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined { - return this._startSession(data, data.initialLocation ?? ChatAgentLocation.Panel, true, CancellationToken.None); + return this._startSession(data, data.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); } async loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { @@ -1159,7 +1159,7 @@ export class ChatService extends Disposable implements IChatService { message: promptTextResult.message, command: request.response.slashCommand?.name, variables: updateRanges(request.variableData, promptTextResult.diff), // TODO bit of a hack - location: ChatAgentLocation.Panel, + location: ChatAgentLocation.Chat, editedFileEvents: request.editedFileEvents, }; history.push({ request: historyRequest, response: toChatHistoryContent(request.response.response.value), result: request.response.result ?? {} }); @@ -1245,7 +1245,7 @@ export class ChatService extends Disposable implements IChatService { throw new Error(`Unknown session: ${sessionId}`); } - if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Panel || model.initialLocation === ChatAgentLocation.Editor)) { + if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.TextEditor)) { if (this.useFileStorage) { // Always preserve sessions that have custom titles, even if empty if (model.getRequests().length === 0 && !model.customTitle) { diff --git a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts index df0e73f0732..be1e5284771 100644 --- a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts +++ b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts @@ -77,7 +77,7 @@ export class ChatWidgetHistoryService implements IChatWidgetHistoryService { private getKey(location: ChatAgentLocation): string { // Preserve history for panel by continuing to use the same old provider id. Use the location as a key for other chat locations. - return location === ChatAgentLocation.Panel ? CHAT_PROVIDER_ID : location; + return location === ChatAgentLocation.Chat ? CHAT_PROVIDER_ID : location; } saveHistory(location: ChatAgentLocation, history: IChatHistoryEntry[]): void { diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 6e0b3a85246..51799dcd353 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -57,21 +57,27 @@ export enum ThinkingDisplayMode { export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook' | 'editing-session'; export enum ChatAgentLocation { - Panel = 'panel', + /** + * This is chat, whether it's in the sidebar, a chat editor, or quick chat. + */ + Chat = 'panel', Terminal = 'terminal', Notebook = 'notebook', - Editor = 'editor', + /** + * TextEditor means inline chat in a text editor. + */ + TextEditor = 'editor', } export namespace ChatAgentLocation { export function fromRaw(value: RawChatParticipantLocation | string): ChatAgentLocation { switch (value) { - case 'panel': return ChatAgentLocation.Panel; + case 'panel': return ChatAgentLocation.Chat; case 'terminal': return ChatAgentLocation.Terminal; case 'notebook': return ChatAgentLocation.Notebook; - case 'editor': return ChatAgentLocation.Editor; + case 'editor': return ChatAgentLocation.TextEditor; } - return ChatAgentLocation.Panel; + return ChatAgentLocation.Chat; } } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index af6b481cd42..5e865dbf3b5 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -150,7 +150,7 @@ class VoiceChatSessionControllerFactory { let context: VoiceChatSessionContext; if (layoutService.hasFocus(Parts.EDITOR_PART)) { - context = chatWidget.location === ChatAgentLocation.Panel ? 'editor' : 'inline'; + context = chatWidget.location === ChatAgentLocation.Chat ? 'editor' : 'inline'; } else if ( [Parts.SIDEBAR_PART, Parts.PANEL_PART, Parts.AUXILIARYBAR_PART, Parts.TITLEBAR_PART, Parts.STATUSBAR_PART, Parts.BANNER_PART, Parts.ACTIVITYBAR_PART].some(part => layoutService.hasFocus(part)) ) { @@ -540,13 +540,13 @@ const primaryVoiceActionMenu = (when: ContextKeyExpression | undefined) => { return [ { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), when), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), when), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), when), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat).negate(), when), group: 'navigation', order: 2 } @@ -686,7 +686,7 @@ class ChatSynthesizerSessionController { const chatWidgetService = accessor.get(IChatWidgetService); const contextKeyService = accessor.get(IContextKeyService); let chatWidget = chatWidgetService.getWidgetBySessionId(response.session.sessionId); - if (chatWidget?.location === ChatAgentLocation.Editor) { + if (chatWidget?.location === ChatAgentLocation.TextEditor) { // workaround for https://github.com/microsoft/vscode/issues/212785 chatWidget = chatWidgetService.lastFocusedWidget; } @@ -1010,7 +1010,7 @@ export class StopReadChatItemAloud extends Action2 { //#region Keyword Recognition function supportsKeywordActivation(configurationService: IConfigurationService, speechService: ISpeechService, chatAgentService: IChatAgentService): boolean { - if (!speechService.hasSpeechProvider || !chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + if (!speechService.hasSpeechProvider || !chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)) { return false; } @@ -1056,7 +1056,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe })); const onDidAddDefaultAgent = this._register(this.chatAgentService.onDidChangeAgents(() => { - if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)) { this.updateConfiguration(); this.handleKeywordActivation(); @@ -1075,7 +1075,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe } private updateConfiguration(): void { - if (!this.speechService.hasSpeechProvider || !this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + if (!this.speechService.hasSpeechProvider || !this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)) { return; // these settings require a speech and chat provider } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts index d08381a05d9..7c13b19fd7e 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts @@ -12,7 +12,11 @@ import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { mock } from '../../../../../base/test/common/mock.js'; import { assertThrowsAsync, ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; +import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; +import { TextEdit } from '../../../../../editor/common/languages.js'; +import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; @@ -21,14 +25,20 @@ import { IWorkbenchAssignmentService } from '../../../../services/assignment/com import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js'; import { nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestWorkerService } from '../../../inlineChat/test/browser/testWorkerService.js'; +import { IMcpService } from '../../../mcp/common/mcpTypes.js'; +import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; import { NotebookTextModel } from '../../../notebook/common/model/notebookTextModel.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; import { ChatEditingService } from '../../browser/chatEditing/chatEditingServiceImpl.js'; +import { ChatSessionsService } from '../../browser/chatSessions.contribution.js'; import { ChatAgentService, IChatAgentData, IChatAgentImplementation, IChatAgentService } from '../../common/chatAgents.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { ChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatService } from '../../common/chatServiceImpl.js'; +import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { ChatTransferService, IChatTransferService } from '../../common/chatTransferService.js'; import { IChatVariablesService } from '../../common/chatVariables.js'; @@ -36,16 +46,6 @@ import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { ILanguageModelsService } from '../../common/languageModels.js'; import { NullLanguageModelsService } from '../common/languageModels.js'; import { MockChatVariablesService } from '../common/mockChatVariables.js'; -import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; -import { TestWorkerService } from '../../../inlineChat/test/browser/testWorkerService.js'; -import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; -import { Position } from '../../../../../editor/common/core/position.js'; -import { ChatModel } from '../../common/chatModel.js'; -import { TextEdit } from '../../../../../editor/common/languages.js'; -import { IMcpService } from '../../../mcp/common/mcpTypes.js'; -import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; -import { IChatSessionsService } from '../../common/chatSessionsService.js'; -import { ChatSessionsService } from '../../browser/chatSessions.contribution.js'; function getAgentData(id: string): IChatAgentData { return { @@ -56,7 +56,7 @@ function getAgentData(id: string): IChatAgentData { extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -137,7 +137,7 @@ suite('ChatEditingService', function () { test('create session', async function () { assert.ok(editingService); - const model = chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None); + const model = chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); const session = await editingService.createEditingSession(model, true); assert.strictEqual(session.chatSessionId, model.sessionId); @@ -157,7 +157,7 @@ suite('ChatEditingService', function () { const uri = URI.from({ scheme: 'test', path: 'HelloWorld' }); - const model = chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None); + const model = chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); const session = await model.editingSessionObs?.promise; if (!session) { assert.fail('session not created'); @@ -214,7 +214,7 @@ suite('ChatEditingService', function () { const uri = URI.from({ scheme: 'test', path: 'abc\n' }); - const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = store.add(chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); const session = await model.editingSessionObs?.promise; assertType(session, 'session not created'); @@ -245,7 +245,7 @@ suite('ChatEditingService', function () { const uri = URI.from({ scheme: 'test', path: 'abc\n' }); - const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = store.add(chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); const session = await model.editingSessionObs?.promise; assertType(session, 'session not created'); @@ -276,7 +276,7 @@ suite('ChatEditingService', function () { const uri = URI.from({ scheme: 'test', path: 'abc\n' }); - const model = store.add(chatService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = store.add(chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); const session = await model.editingSessionObs?.promise; assertType(session, 'session not created'); diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 78d9ee972af..c4d2b48ed6f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -40,7 +40,7 @@ suite('ChatModel', () => { }); test('removeRequest', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Panel)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); const text = 'hello'; model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -52,8 +52,8 @@ suite('ChatModel', () => { }); test('adoptRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Editor)); - const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Panel)); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.TextEditor)); + const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -76,7 +76,7 @@ suite('ChatModel', () => { }); test('addCompleteRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Panel)); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0, undefined, undefined, undefined, undefined, undefined, undefined, true); diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 7e1e932fef3..de488fa9e60 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -18,7 +18,7 @@ import { ChatRequestParser } from '../../common/chatRequestParser.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IChatVariablesService } from '../../common/chatVariables.js'; -import { ChatModeKind, ChatAgentLocation } from '../../common/constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { MockChatService } from './mockChatService.js'; @@ -233,7 +233,7 @@ suite('ChatRequestParser', () => { // }); const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { - return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData; + return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData; }; test('agent with subcommand after text', async () => { diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 4ff54b3d87f..e5158911944 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -30,6 +30,8 @@ import { IExtensionService, nullExtensionDescription } from '../../../../service import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { mock, TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { IMcpService } from '../../../mcp/common/mcpTypes.js'; +import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; import { ChatAgentService, IChatAgent, IChatAgentData, IChatAgentImplementation, IChatAgentService } from '../../common/chatAgents.js'; import { IChatEditingService, IChatEditingSession } from '../../common/chatEditingService.js'; import { IChatModel, ISerializableChatData } from '../../common/chatModel.js'; @@ -40,8 +42,6 @@ import { IChatVariablesService } from '../../common/chatVariables.js'; import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { MockChatService } from './mockChatService.js'; import { MockChatVariablesService } from './mockChatVariables.js'; -import { IMcpService } from '../../../mcp/common/mcpTypes.js'; -import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { @@ -52,7 +52,7 @@ const chatAgentWithUsedContext: IChatAgent = { publisherDisplayName: '', extensionPublisherId: '', extensionDisplayName: '', - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -87,7 +87,7 @@ const chatAgentWithMarkdown: IChatAgent = { publisherDisplayName: '', extensionPublisherId: '', extensionDisplayName: '', - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -110,7 +110,7 @@ function getAgentData(id: string): IChatAgentData { extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', - locations: [ChatAgentLocation.Panel], + locations: [ChatAgentLocation.Chat], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -167,10 +167,10 @@ suite('ChatService', () => { test('retrieveSession', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const session1 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const session1 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); session1.addRequest({ parts: [], text: 'request 1' }, { variables: [] }, 0); - const session2 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const session2 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }, 0); storageService.flush(); @@ -184,7 +184,7 @@ suite('ChatService', () => { test('addCompleteRequest', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const model = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await testService.addCompleteRequest(model.sessionId, 'test request', undefined, 0, { message: 'test response' }); @@ -196,7 +196,7 @@ suite('ChatService', () => { test('sendRequest fails', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const model = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -219,7 +219,7 @@ suite('ChatService', () => { testDisposables.add(chatAgentService.registerAgentImplementation('agent2', historyLengthAgent)); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const model = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); // Send a request to default agent const response = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'defaultAgent' }); @@ -248,7 +248,7 @@ suite('ChatService', () => { chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' } }); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const model = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await assertSnapshot(toSnapshotExportData(model)); @@ -274,7 +274,7 @@ suite('ChatService', () => { { // serapate block to not leak variables in outer scope const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); @@ -303,7 +303,7 @@ suite('ChatService', () => { { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts index 3e567b3c794..f9182eaa062 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts @@ -31,7 +31,7 @@ suite('VoiceChat', () => { extensionPublisher = ''; extensionDisplayName = ''; extensionPublisherId = ''; - locations: ChatAgentLocation[] = [ChatAgentLocation.Panel]; + locations: ChatAgentLocation[] = [ChatAgentLocation.Chat]; modes = [ChatModeKind.Ask]; public readonly name: string; constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 58487b1242e..43e756c8a22 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -3,35 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './emptyTextEditorHint.css'; import { $, addDisposableListener, getActiveWindow } from '../../../../../base/browser/dom.js'; +import { IContentActionHandler, renderFormattedText } from '../../../../../base/browser/formattedTextRenderer.js'; +import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; +import { status } from '../../../../../base/browser/ui/aria/aria.js'; +import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js'; +import { Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../base/common/network.js'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../../../editor/browser/editorBrowser.js'; +import { EditorContributionInstantiation, registerEditorContribution } from '../../../../../editor/browser/editorExtensions.js'; +import { ConfigurationChangedEvent, EditorOption } from '../../../../../editor/common/config/editorOptions.js'; +import { Position } from '../../../../../editor/common/core/position.js'; +import { IEditorContribution } from '../../../../../editor/common/editorCommon.js'; +import { PLAINTEXT_LANGUAGE_ID } from '../../../../../editor/common/languages/modesRegistry.js'; import { localize } from '../../../../../nls.js'; -import { ChangeLanguageAction } from '../../../../browser/parts/editor/editorStatus.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { PLAINTEXT_LANGUAGE_ID } from '../../../../../editor/common/languages/modesRegistry.js'; -import { IEditorContribution } from '../../../../../editor/common/editorCommon.js'; -import { Schemas } from '../../../../../base/common/network.js'; -import { Event } from '../../../../../base/common/event.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { ConfigurationChangedEvent, EditorOption } from '../../../../../editor/common/config/editorOptions.js'; -import { EditorContributionInstantiation, registerEditorContribution } from '../../../../../editor/browser/editorExtensions.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; -import { IContentActionHandler, renderFormattedText } from '../../../../../base/browser/formattedTextRenderer.js'; -import { IInlineChatSessionService } from '../../../inlineChat/browser/inlineChatSessionService.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; -import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js'; -import { status } from '../../../../../base/browser/ui/aria/aria.js'; -import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; +import { ChangeLanguageAction } from '../../../../browser/parts/editor/editorStatus.js'; import { LOG_MODE_ID, OUTPUT_MODE_ID } from '../../../../services/output/common/output.js'; import { SEARCH_RESULT_LANGUAGE_ID } from '../../../../services/search/common/search.js'; +import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { IChatAgentService } from '../../../chat/common/chatAgents.js'; -import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; -import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; import { ChatAgentLocation } from '../../../chat/common/constants.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { Position } from '../../../../../editor/common/core/position.js'; +import { IInlineChatSessionService } from '../../../inlineChat/browser/inlineChatSessionService.js'; +import './emptyTextEditorHint.css'; export const emptyTextEditorHintSetting = 'workbench.editor.empty.hint'; export class EmptyTextEditorHintContribution extends Disposable implements IEditorContribution { @@ -110,7 +110,7 @@ export class EmptyTextEditorHintContribution extends Disposable implements IEdit return false; } - const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); + const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)); const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID; return hasEditorAgents || shouldRenderDefaultHint; } @@ -200,7 +200,7 @@ class EmptyTextEditorHintContentWidget extends Disposable implements IContentWid } private getHint() { - const hasInlineChatProvider = this.chatAgentService.getActivatedAgents().filter(candidate => candidate.locations.includes(ChatAgentLocation.Editor)).length > 0; + const hasInlineChatProvider = this.chatAgentService.getActivatedAgents().filter(candidate => candidate.locations.includes(ChatAgentLocation.TextEditor)).length > 0; const hintHandler: IContentActionHandler = { disposables: this._store, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 374beb58ae8..bfd9f1201bc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -31,22 +31,34 @@ import { TextEdit, VersionedExtensionId } from '../../../../editor/common/langua import { IValidEditOperation } from '../../../../editor/common/model.js'; import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js'; +import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; import { MessageController } from '../../../../editor/contrib/message/browser/messageController.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; +import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showChatView } from '../../chat/browser/chat.js'; +import { IChatAttachmentResolveService } from '../../chat/browser/chatAttachmentResolveService.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; +import { ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; -import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { IChatService } from '../../chat/common/chatService.js'; +import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; +import { ChatAgentLocation } from '../../chat/common/constants.js'; +import { INotebookEditor } from '../../notebook/browser/notebookBrowser.js'; +import { isNotebookContainingCellEditor as isNotebookWithCellEditor } from '../../notebook/browser/notebookEditor.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; +import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; +import { INotebookService } from '../../notebook/common/notebookService.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; @@ -54,18 +66,6 @@ import { InlineChatError } from './inlineChatSessionServiceImpl.js'; import { HunkAction, IEditObserver, IInlineChatMetadata, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; -import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; -import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; -import { IChatAttachmentResolveService } from '../../chat/browser/chatAttachmentResolveService.js'; -import { INotebookService } from '../../notebook/common/notebookService.js'; -import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; -import { INotebookEditor } from '../../notebook/browser/notebookBrowser.js'; -import { isNotebookContainingCellEditor as isNotebookWithCellEditor } from '../../notebook/browser/notebookEditor.js'; -import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -228,12 +228,12 @@ export class InlineChatController1 implements IEditorContribution { this._ui = new Lazy(() => { const location: IChatWidgetLocationOptions = { - location: ChatAgentLocation.Editor, + location: ChatAgentLocation.TextEditor, resolveData: () => { assertType(this._editor.hasModel()); assertType(this._session); return { - type: ChatAgentLocation.Editor, + type: ChatAgentLocation.TextEditor, selection: this._editor.getSelection(), document: this._session.textModelN.uri, wholeRange: this._session?.wholeRange.trackedInitialRange, @@ -1261,12 +1261,12 @@ export class InlineChatController2 implements IEditorContribution { const location: IChatWidgetLocationOptions = { - location: ChatAgentLocation.Editor, + location: ChatAgentLocation.TextEditor, resolveData: () => { assertType(this._editor.hasModel()); return { - type: ChatAgentLocation.Editor, + type: ChatAgentLocation.TextEditor, selection: this._editor.getSelection(), document: this._editor.getModel().uri, wholeRange: this._editor.getSelection(), @@ -1532,7 +1532,7 @@ export async function reviewEdits(accessor: ServicesAccessor, editor: ICodeEdito const chatService = accessor.get(IChatService); const uri = editor.getModel().uri; - const chatModel = chatService.startSession(ChatAgentLocation.Editor, token, false); + const chatModel = chatService.startSession(ChatAgentLocation.TextEditor, token, false); chatModel.startEditingSession(true); @@ -1585,7 +1585,7 @@ export async function reviewNotebookEdits(accessor: ServicesAccessor, uri: URI, const chatService = accessor.get(IChatService); const notebookService = accessor.get(INotebookService); const isNotebook = notebookService.hasSupportedNotebooks(uri); - const chatModel = chatService.startSession(ChatAgentLocation.Editor, token, false); + const chatModel = chatService.startSession(ChatAgentLocation.TextEditor, token, false); chatModel.startEditingSession(true); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index f68cf896d94..1911661d49b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -3,44 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { stringValue } from '../../../../base/browser/cssValue.js'; +import { createStyleSheet2 } from '../../../../base/browser/domStylesheets.js'; +import { IMouseEvent } from '../../../../base/browser/mouseEvent.js'; +import { toAction } from '../../../../base/common/actions.js'; +import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, derived, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; -import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { InlineChatController } from './inlineChatController.js'; -import { ACTION_START, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; import { EditOperation } from '../../../../editor/common/core/editOperation.js'; -import { Range } from '../../../../editor/common/core/range.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; -import { AbstractInline1ChatAction } from './inlineChatActions.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; -import { URI } from '../../../../base/common/uri.js'; -import { isEqual } from '../../../../base/common/resources.js'; import { StandardTokenType } from '../../../../editor/common/encodedTokenAttributes.js'; -import { autorun, derived, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; -import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import './media/inlineChat.css'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; -import { IChatAgentService } from '../../chat/common/chatAgents.js'; +import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; +import { IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { IMarkerDecorationsService } from '../../../../editor/common/services/markerDecorations.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { toAction } from '../../../../base/common/actions.js'; -import { IMouseEvent } from '../../../../base/browser/mouseEvent.js'; +import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; -import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; -import { createStyleSheet2 } from '../../../../base/browser/domStylesheets.js'; -import { stringValue } from '../../../../base/browser/cssValue.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; +import { IChatAgentService } from '../../chat/common/chatAgents.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/promptTypes.js'; import { MODE_FILE_EXTENSION } from '../../chat/common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/promptTypes.js'; +import { ACTION_START, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; +import { AbstractInline1ChatAction } from './inlineChatActions.js'; +import { InlineChatController } from './inlineChatController.js'; +import './media/inlineChat.css'; /** * Set of language IDs where inline chat hints should not be shown. @@ -286,7 +286,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont return; } - const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)?.name ?? localize('defaultTitle', "Chat"); + const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)?.name ?? localize('defaultTitle', "Chat"); const { position, isEol, isWhitespace, kb, model } = showData; const inlineClassName: string[] = ['a' /*HACK but sorts as we want*/, 'inline-chat-hint', 'inline-chat-hint-text']; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 2ebe866eed1..cd9a06250e1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -108,7 +108,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { async createSession(editor: IActiveCodeEditor, options: { headless?: boolean; wholeRange?: Range; session?: Session }, token: CancellationToken): Promise { - const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor); + const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor); if (!agent) { this._logService.trace('[IE] NO agent found'); @@ -123,7 +123,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const store = new DisposableStore(); this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`); - const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.Editor, token); + const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.TextEditor, token); if (!chatModel) { this._logService.trace('[IE] NO chatModel found'); return undefined; @@ -348,7 +348,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._onWillStartSession.fire(editor as IActiveCodeEditor); - const chatModel = this._chatService.startSession(ChatAgentLocation.Panel, token, false); + const chatModel = this._chatService.startSession(ChatAgentLocation.Chat, token, false); const editingSession = await chatModel.editingSessionObs?.promise!; const widget = this._chatWidgetService.getWidgetBySessionId(chatModel.sessionId); @@ -431,7 +431,7 @@ export class InlineChatEnabler { this._ctxHasProvider2 = CTX_INLINE_CHAT_HAS_AGENT2.bindTo(contextKeyService); this._ctxPossible = CTX_INLINE_CHAT_POSSIBLE.bindTo(contextKeyService); - const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); + const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)); const inlineChat2Obs = observableConfigValue(InlineChatConfigKeys.EnableV2, false, configService); this._store.add(autorun(r => { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 56a41c07483..1de902e778c 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -51,24 +51,30 @@ import { TestContextService, TestExtensionService } from '../../../../test/commo import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatInputBoxContentProvider } from '../../../chat/browser/chatEdinputInputContentProvider.js'; +import { ChatLayoutService } from '../../../chat/browser/chatLayoutService.js'; import { ChatVariablesService } from '../../../chat/browser/chatVariables.js'; import { ChatWidgetService } from '../../../chat/browser/chatWidget.js'; import { ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from '../../../chat/common/chatAgents.js'; import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js'; import { IChatEntitlementService } from '../../../chat/common/chatEntitlementService.js'; +import { IChatLayoutService } from '../../../chat/common/chatLayoutService.js'; import { IChatModeService } from '../../../chat/common/chatModes.js'; import { IChatProgress, IChatService } from '../../../chat/common/chatService.js'; import { ChatService } from '../../../chat/common/chatServiceImpl.js'; import { ChatSlashCommandService, IChatSlashCommandService } from '../../../chat/common/chatSlashCommands.js'; +import { ChatTransferService, IChatTransferService } from '../../../chat/common/chatTransferService.js'; import { IChatVariablesService } from '../../../chat/common/chatVariables.js'; import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../../../chat/common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatModeKind } from '../../../chat/common/constants.js'; import { ILanguageModelsService, LanguageModelsService } from '../../../chat/common/languageModels.js'; import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js'; +import { PromptsType } from '../../../chat/common/promptSyntax/promptTypes.js'; import { IPromptPath, IPromptsService } from '../../../chat/common/promptSyntax/service/promptsService.js'; import { MockChatModeService } from '../../../chat/test/common/mockChatModeService.js'; import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js'; +import { IMcpService } from '../../../mcp/common/mcpTypes.js'; +import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; import { INotebookEditorService } from '../../../notebook/browser/services/notebookEditorService.js'; import { RerunAction } from '../../browser/inlineChatActions.js'; import { InlineChatController1, State } from '../../browser/inlineChatController.js'; @@ -76,12 +82,6 @@ import { IInlineChatSessionService } from '../../browser/inlineChatSessionServic import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl.js'; import { CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatConfigKeys, InlineChatResponseType } from '../../common/inlineChat.js'; import { TestWorkerService } from './testWorkerService.js'; -import { PromptsType } from '../../../chat/common/promptSyntax/promptTypes.js'; -import { ChatTransferService, IChatTransferService } from '../../../chat/common/chatTransferService.js'; -import { IMcpService } from '../../../mcp/common/mcpTypes.js'; -import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; -import { IChatLayoutService } from '../../../chat/common/chatLayoutService.js'; -import { ChatLayoutService } from '../../../chat/browser/chatLayoutService.js'; suite('InlineChatController', function () { @@ -94,7 +94,7 @@ suite('InlineChatController', function () { // id: 'testEditorAgent', name: 'testEditorAgent', isDefault: true, - locations: [ChatAgentLocation.Editor], + locations: [ChatAgentLocation.TextEditor], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -611,7 +611,7 @@ suite('InlineChatController', function () { assert.strictEqual(model.getValue(), 'eins\nHello\nWorld\nHello Again\nHello World\n'); - const targetModel = chatService.startSession(ChatAgentLocation.Editor, CancellationToken.None)!; + const targetModel = chatService.startSession(ChatAgentLocation.TextEditor, CancellationToken.None)!; store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { @@ -659,7 +659,7 @@ suite('InlineChatController', function () { assert.strictEqual(model.getValue(), 'zwei\neins\nHello\nWorld\nHello Again\nHello World\n'); - const targetModel = chatService.startSession(ChatAgentLocation.Editor, CancellationToken.None)!; + const targetModel = chatService.startSession(ChatAgentLocation.TextEditor, CancellationToken.None)!; store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { @@ -722,7 +722,7 @@ suite('InlineChatController', function () { assertType(request); const p2 = Event.toPromise(onDidInvoke.event); const p3 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); - chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.Editor }); + chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.TextEditor }); await p2; assert.strictEqual(await p3, undefined); @@ -759,7 +759,7 @@ suite('InlineChatController', function () { const request = ctrl.chatWidget.viewModel?.model.getRequests().at(-1); assertType(request); const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); - chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.Editor }); + chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.TextEditor }); assert.strictEqual(await p2, undefined); @@ -864,7 +864,7 @@ suite('InlineChatController', function () { const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None); assertType(newSession); - await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }))?.responseCreatedPromise; + await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.TextEditor }))?.responseCreatedPromise; assert.strictEqual(newSession.chatModel.requestInProgress, true); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index cb810cd5d7f..c6a79830343 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -3,17 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { IObservable, constObservable } from '../../../../../base/common/observable.js'; +import { assertType } from '../../../../../base/common/types.js'; import { mock } from '../../../../../base/test/common/mock.js'; +import { assertSnapshot } from '../../../../../base/test/common/snapshot.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { TestDiffProviderFactoryService } from '../../../../../editor/test/browser/diff/testDiffProviderFactoryService.js'; import { IActiveCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { IDiffProviderFactoryService } from '../../../../../editor/browser/widget/diffEditor/diffProviderFactoryService.js'; +import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; +import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { ITextModel } from '../../../../../editor/common/model.js'; +import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; +import { TestDiffProviderFactoryService } from '../../../../../editor/test/browser/diff/testDiffProviderFactoryService.js'; +import { TestCommandService } from '../../../../../editor/test/browser/editorTestServices.js'; import { instantiateTestCodeEditor } from '../../../../../editor/test/browser/testCodeEditor.js'; +import { IAccessibleViewService } from '../../../../../platform/accessibility/browser/accessibleView.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -21,55 +31,45 @@ import { SyncDescriptor } from '../../../../../platform/instantiation/common/des import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; -import { IEditorProgressService, IProgressRunner } from '../../../../../platform/progress/common/progress.js'; -import { IViewDescriptorService } from '../../../../common/views.js'; -import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; -import { IChatAccessibilityService, IChatWidgetService } from '../../../chat/browser/chat.js'; -import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js'; -import { HunkState } from '../../browser/inlineChatSession.js'; -import { IInlineChatSessionService } from '../../browser/inlineChatSessionService.js'; -import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl.js'; -import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; -import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { assertType } from '../../../../../base/common/types.js'; -import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; -import { Position } from '../../../../../editor/common/core/position.js'; -import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; -import { TestWorkerService } from './testWorkerService.js'; -import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js'; +import { IEditorProgressService, IProgressRunner } from '../../../../../platform/progress/common/progress.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IViewDescriptorService } from '../../../../common/views.js'; +import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js'; +import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js'; +import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestContextService, TestExtensionService } from '../../../../test/common/workbenchTestServices.js'; +import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; +import { IChatAccessibilityService, IChatWidgetService } from '../../../chat/browser/chat.js'; +import { ChatSessionsService } from '../../../chat/browser/chatSessions.contribution.js'; +import { ChatVariablesService } from '../../../chat/browser/chatVariables.js'; import { ChatWidgetService } from '../../../chat/browser/chatWidget.js'; +import { ChatAgentService, IChatAgentService } from '../../../chat/common/chatAgents.js'; +import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js'; +import { IChatRequestModel } from '../../../chat/common/chatModel.js'; import { IChatService } from '../../../chat/common/chatService.js'; import { ChatService } from '../../../chat/common/chatServiceImpl.js'; -import { IChatSlashCommandService, ChatSlashCommandService } from '../../../chat/common/chatSlashCommands.js'; +import { IChatSessionsService } from '../../../chat/common/chatSessionsService.js'; +import { ChatSlashCommandService, IChatSlashCommandService } from '../../../chat/common/chatSlashCommands.js'; +import { ChatTransferService, IChatTransferService } from '../../../chat/common/chatTransferService.js'; import { IChatVariablesService } from '../../../chat/common/chatVariables.js'; -import { IChatWidgetHistoryService, ChatWidgetHistoryService } from '../../../chat/common/chatWidgetHistoryService.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { TestExtensionService, TestContextService } from '../../../../test/common/workbenchTestServices.js'; -import { IChatAgentService, ChatAgentService } from '../../../chat/common/chatAgents.js'; -import { ChatVariablesService } from '../../../chat/browser/chatVariables.js'; -import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { TestCommandService } from '../../../../../editor/test/browser/editorTestServices.js'; -import { IAccessibleViewService } from '../../../../../platform/accessibility/browser/accessibleView.js'; -import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js'; -import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js'; -import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js'; -import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js'; -import { IChatRequestModel } from '../../../chat/common/chatModel.js'; -import { assertSnapshot } from '../../../../../base/test/common/snapshot.js'; -import { IObservable, constObservable } from '../../../../../base/common/observable.js'; -import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js'; +import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js'; +import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../../../chat/common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatModeKind } from '../../../chat/common/constants.js'; -import { ChatTransferService, IChatTransferService } from '../../../chat/common/chatTransferService.js'; -import { NullLanguageModelsService } from '../../../chat/test/common/languageModels.js'; import { ILanguageModelsService } from '../../../chat/common/languageModels.js'; +import { ILanguageModelToolsService } from '../../../chat/common/languageModelToolsService.js'; +import { NullLanguageModelsService } from '../../../chat/test/common/languageModels.js'; +import { MockLanguageModelToolsService } from '../../../chat/test/common/mockLanguageModelToolsService.js'; import { IMcpService } from '../../../mcp/common/mcpTypes.js'; import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; -import { IChatSessionsService } from '../../../chat/common/chatSessionsService.js'; -import { ChatSessionsService } from '../../../chat/browser/chatSessions.contribution.js'; +import { HunkState } from '../../browser/inlineChatSession.js'; +import { IInlineChatSessionService } from '../../browser/inlineChatSessionService.js'; +import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl.js'; +import { TestWorkerService } from './testWorkerService.js'; suite('InlineChatSession', function () { @@ -152,7 +152,7 @@ suite('InlineChatSession', function () { id: 'testAgent', name: 'testAgent', isDefault: true, - locations: [ChatAgentLocation.Editor], + locations: [ChatAgentLocation.TextEditor], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], diff --git a/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts index 96b7562386e..7b6dad43a4e 100644 --- a/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts +++ b/src/vs/workbench/contrib/mcp/browser/openPanelChatAndGetWidget.ts @@ -6,18 +6,18 @@ import { raceTimeout } from '../../../../base/common/async.js'; import { Event } from '../../../../base/common/event.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { IChatWidgetService, IChatWidget, ChatViewId } from '../../chat/browser/chat.js'; +import { ChatViewId, IChatWidget, IChatWidgetService } from '../../chat/browser/chat.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; export async function openPanelChatAndGetWidget(viewsService: IViewsService, chatService: IChatWidgetService): Promise { await viewsService.openView(ChatViewId, true); - const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Panel); + const widgets = chatService.getWidgetsByLocations(ChatAgentLocation.Chat); if (widgets.length) { return widgets[0]; } - const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Panel)); + const eventPromise = Event.toPromise(Event.filter(chatService.onDidAddWidget, e => e.location === ChatAgentLocation.Chat)); return await raceTimeout( eventPromise, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index e1801d2ce4f..6fbfafefd69 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; import { codiconsLibrary } from '../../../../../../base/common/codiconsLibrary.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../../base/common/network.js'; @@ -20,25 +21,24 @@ import { ServicesAccessor } from '../../../../../../platform/instantiation/commo import { IQuickInputService, IQuickPickItem } from '../../../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../common/contributions.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; +import { IViewsService } from '../../../../../services/views/common/viewsService.js'; import { IChatWidget, IChatWidgetService, showChatView } from '../../../../chat/browser/chat.js'; +import { IChatContextPicker, IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../../../chat/browser/chatContextPickService.js'; import { ChatDynamicVariableModel } from '../../../../chat/browser/contrib/chatDynamicVariables.js'; import { computeCompletionRanges } from '../../../../chat/browser/contrib/chatInputCompletions.js'; import { IChatAgentService } from '../../../../chat/common/chatAgents.js'; -import { ChatAgentLocation } from '../../../../chat/common/constants.js'; import { ChatContextKeys } from '../../../../chat/common/chatContextKeys.js'; import { chatVariableLeader } from '../../../../chat/common/chatParserTypes.js'; +import { ChatAgentLocation } from '../../../../chat/common/constants.js'; import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT, NOTEBOOK_CELL_OUTPUT_MIMETYPE } from '../../../common/notebookContextKeys.js'; import { INotebookKernelService } from '../../../common/notebookKernelService.js'; +import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../contrib/chat/notebookChatUtils.js'; import { getNotebookEditorFromEditorPane, ICellOutputViewModel, INotebookEditor } from '../../notebookBrowser.js'; import * as icons from '../../notebookIcons.js'; import { getOutputViewModelFromId } from '../cellOutputActions.js'; import { INotebookOutputActionContext, NOTEBOOK_ACTIONS_CATEGORY } from '../coreActions.js'; import './cellChatActions.js'; import { CTX_NOTEBOOK_CHAT_HAS_AGENT } from './notebookChatContext.js'; -import { IViewsService } from '../../../../../services/views/common/viewsService.js'; -import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../contrib/chat/notebookChatUtils.js'; -import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker } from '../../../../chat/browser/chatContextPickService.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; const NotebookKernelVariableKey = 'kernelVariable'; @@ -375,7 +375,7 @@ registerAction2(class CopyCellOutputAction extends Action2 { const chatWidgetService = accessor.get(IChatWidgetService); let widget = chatWidgetService.lastFocusedWidget; if (!widget) { - const widgets = chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel); + const widgets = chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat); if (widgets.length === 0) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 4933f29c079..c4b027c1fb7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -29,19 +29,19 @@ import { localize } from '../../../../../../nls.js'; import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; -import { ChatAgentLocation } from '../../../../chat/common/constants.js'; import { ChatModel, IChatModel } from '../../../../chat/common/chatModel.js'; import { IChatService } from '../../../../chat/common/chatService.js'; import { countWords } from '../../../../chat/common/chatWordCounter.js'; +import { ChatAgentLocation } from '../../../../chat/common/constants.js'; import { ProgressingEditsOptions } from '../../../../inlineChat/browser/inlineChatStrategies.js'; import { InlineChatWidget } from '../../../../inlineChat/browser/inlineChatWidget.js'; import { asProgressiveEdit, performAsyncTextEdit } from '../../../../inlineChat/browser/utils.js'; -import { insertCell, runDeleteAction } from '../cellOperations.js'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js'; -import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from '../../notebookBrowser.js'; -import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; import { CellKind } from '../../../common/notebookCommon.js'; import { INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from '../../notebookBrowser.js'; +import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; +import { insertCell, runDeleteAction } from '../cellOperations.js'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js'; class NotebookChatWidget extends Disposable implements INotebookViewZone { set afterModelPosition(afterModelPosition: number) { @@ -501,7 +501,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private async _startSession(token: CancellationToken) { if (!this._model.value) { - this._model.value = this._chatService.startSession(ChatAgentLocation.Editor, token); + this._model.value = this._chatService.startSession(ChatAgentLocation.TextEditor, token); if (!this._model.value) { throw new Error('Failed to start chat session'); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 2de14ff31ee..f2565eaec58 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -30,16 +30,16 @@ import { IQuickInputService, IQuickPickSeparator } from '../../../../platform/qu import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkbenchQuickAccessConfiguration } from '../../../browser/quickaccess.js'; -import { CHAT_OPEN_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; -import { ASK_QUICK_QUESTION_ACTION_ID } from '../../chat/browser/actions/chatQuickInputActions.js'; -import { IChatAgentService } from '../../chat/common/chatAgents.js'; -import { ChatAgentLocation } from '../../chat/common/constants.js'; import { CommandInformationResult, IAiRelatedInformationService, RelatedInformationType } from '../../../services/aiRelatedInformation/common/aiRelatedInformation.js'; import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { createKeybindingCommandQuery } from '../../../services/preferences/browser/keybindingsEditorModel.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; +import { CHAT_OPEN_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; +import { ASK_QUICK_QUESTION_ACTION_ID } from '../../chat/browser/actions/chatQuickInputActions.js'; +import { IChatAgentService } from '../../chat/common/chatAgents.js'; +import { ChatAgentLocation } from '../../chat/common/constants.js'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -174,7 +174,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce }); } - const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); + const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); if (defaultAgent) { additionalPicks.push({ label: localize('askXInChat', "Ask {0}: {1}", defaultAgent.fullName, filter), diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 631d969a198..a004d486efb 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -27,8 +27,8 @@ import { IMarkerData, IMarkerService } from '../../../../platform/markers/common import { IProgressOptions, IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { INamedProblemMatcher, ProblemMatcherRegistry } from '../common/problemMatcher.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { INamedProblemMatcher, ProblemMatcherRegistry } from '../common/problemMatcher.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; @@ -37,9 +37,9 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, WorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; -import { Markers } from '../../markers/common/markers.js'; import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { Markers } from '../../markers/common/markers.js'; import { IOutputChannel, IOutputService } from '../../../services/output/common/output.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; @@ -47,7 +47,7 @@ import { ITextFileService } from '../../../services/textfile/common/textfiles.js import { ITerminalGroupService, ITerminalService } from '../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../terminal/common/terminal.js'; -import { ConfiguringTask, ContributedTask, CustomTask, ExecutionEngine, InMemoryTask, ITaskEvent, ITaskIdentifier, ITaskSet, JsonSchemaVersion, KeyedTaskIdentifier, RuntimeType, Task, TASK_RUNNING_STATE, TaskDefinition, TaskGroup, TaskRunSource, TaskSettingId, TaskSorter, TaskSourceKind, TasksSchemaProperties, USER_TASKS_GROUP_KEY, TaskEventKind, InstancePolicy, RerunAllRunningTasksCommandId } from '../common/tasks.js'; +import { ConfiguringTask, ContributedTask, CustomTask, ExecutionEngine, InMemoryTask, InstancePolicy, ITaskEvent, ITaskIdentifier, ITaskSet, JsonSchemaVersion, KeyedTaskIdentifier, RerunAllRunningTasksCommandId, RuntimeType, Task, TASK_RUNNING_STATE, TaskDefinition, TaskEventKind, TaskGroup, TaskRunSource, TaskSettingId, TaskSorter, TaskSourceKind, TasksSchemaProperties, USER_TASKS_GROUP_KEY } from '../common/tasks.js'; import { CustomExecutionSupportedContext, ICustomizationProperties, IProblemMatcherRunOptions, ITaskFilter, ITaskProvider, ITaskService, IWorkspaceFolderTaskResult, ProcessExecutionSupportedContext, ServerlessWebContext, ShellExecutionSupportedContext, TaskCommandsRegistered, TaskExecutionSupportedContext, TasksAvailableContext } from '../common/taskService.js'; import { ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers, VerifiedTask } from '../common/taskSystem.js'; import { getTemplates as getTaskTemplates } from '../common/taskTemplates.js'; @@ -60,8 +60,10 @@ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickInput import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { TaskDefinitionRegistry } from '../common/taskDefinitionRegistry.js'; +import { getActiveElement } from '../../../../base/browser/dom.js'; import { raceTimeout } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { isCancellationError } from '../../../../base/common/errors.js'; import { toFormattedString } from '../../../../base/common/jsonFormatter.js'; import { Schemas } from '../../../../base/common/network.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -75,20 +77,18 @@ import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from import { VirtualWorkspaceContext } from '../../../common/contextkeys.js'; import { EditorResourceAccessor, SaveReason } from '../../../common/editor.js'; import { IViewDescriptorService } from '../../../common/views.js'; -import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { configureTaskIcon, isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, QUICKOPEN_SKIP_CONFIG, TaskQuickPick } from './taskQuickPick.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { ILifecycleService, ShutdownReason, StartupKind } from '../../../services/lifecycle/common/lifecycle.js'; import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IPathService } from '../../../services/path/common/pathService.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; -import { isCancellationError } from '../../../../base/common/errors.js'; -import { IChatService } from '../../chat/common/chatService.js'; -import { ChatAgentLocation, ChatModeKind } from '../../chat/common/constants.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; import { CHAT_OPEN_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; import { IChatAgentService } from '../../chat/common/chatAgents.js'; -import { getActiveElement } from '../../../../base/browser/dom.js'; +import { IChatService } from '../../chat/common/chatService.js'; +import { ChatAgentLocation, ChatModeKind } from '../../chat/common/constants.js'; +import { configureTaskIcon, isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, QUICKOPEN_SKIP_CONFIG, TaskQuickPick } from './taskQuickPick.js'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; @@ -702,7 +702,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (userRequested) { this._outputService.showChannel(this._outputChannel.id, true); } else { - const chatEnabled = this._chatService.isEnabled(ChatAgentLocation.Panel); + const chatEnabled = this._chatService.isEnabled(ChatAgentLocation.Chat); const actions = []; if (chatEnabled && errorMessage) { const beforeJSONregex = /^(.*?)\s*\{[\s\S]*$/; @@ -714,7 +714,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer : `\`${message}\`\n\`\`\`json${errorMessage}\`\`\``; - const defaultAgent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); + const defaultAgent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); const providerName = defaultAgent?.fullName; if (providerName) { actions.push({ diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index b0fd2f0d22a..d4d8d7de181 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -3,28 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { IMarker as XtermMarker } from '@xterm/xterm'; +import { IAction } from '../../../../../../../base/common/actions.js'; import { timeout } from '../../../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../../../../base/common/event.js'; import { MarkdownString } from '../../../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../../../base/common/lifecycle.js'; +import { isObject, isString } from '../../../../../../../base/common/types.js'; import { localize } from '../../../../../../../nls.js'; import { ExtensionIdentifier } from '../../../../../../../platform/extensions/common/extensions.js'; +import { IChatWidgetService } from '../../../../../chat/browser/chat.js'; import { ChatElicitationRequestPart } from '../../../../../chat/browser/chatElicitationRequestPart.js'; import { ChatModel } from '../../../../../chat/common/chatModel.js'; import { IChatService } from '../../../../../chat/common/chatService.js'; -import { ILanguageModelsService, ChatMessageRole } from '../../../../../chat/common/languageModels.js'; +import { ChatAgentLocation } from '../../../../../chat/common/constants.js'; +import { ChatMessageRole, ILanguageModelsService } from '../../../../../chat/common/languageModels.js'; import { IToolInvocationContext } from '../../../../../chat/common/languageModelToolsService.js'; import { ITaskService } from '../../../../../tasks/common/taskService.js'; -import { IPollingResult, OutputMonitorState, IExecution, IConfirmationPrompt, PollingConsts } from './types.js'; -import { getTextResponseFromStream } from './utils.js'; -import { IChatWidgetService } from '../../../../../chat/browser/chat.js'; -import { ChatAgentLocation } from '../../../../../chat/common/constants.js'; -import { isObject, isString } from '../../../../../../../base/common/types.js'; -import { ILinkLocation } from '../../taskHelpers.js'; -import { IAction } from '../../../../../../../base/common/actions.js'; -import type { IMarker as XtermMarker } from '@xterm/xterm'; import { detectsInputRequiredPattern } from '../../executeStrategy/executeStrategy.js'; +import { ILinkLocation } from '../../taskHelpers.js'; +import { IConfirmationPrompt, IExecution, IPollingResult, OutputMonitorState, PollingConsts } from './types.js'; +import { getTextResponseFromStream } from './utils.js'; export interface IOutputMonitor extends Disposable { readonly pollingResult: IPollingResult & { pollDurationMs: number } | undefined; @@ -428,7 +428,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if (!confirmationPrompt?.options.length) { return undefined; } - const model = this._chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel)[0]?.input.currentLanguageModel; + const model = this._chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat)[0]?.input.currentLanguageModel; if (!model) { return undefined; } From 3c409616d519c03d70039f1329f7ff2001b56f0f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 6 Sep 2025 16:31:09 -0700 Subject: [PATCH 0067/4355] Rename TextEditor location to EditorInline (#265517) * EditorInline * comment --- src/vs/workbench/api/common/extHostChatAgents2.ts | 2 +- src/vs/workbench/api/common/extHostTypeConverters.ts | 4 ++-- .../chat/browser/actions/chatExecuteActions.ts | 2 +- .../chat/browser/chatEditing/chatEditingSession.ts | 2 +- .../workbench/contrib/chat/browser/chatInputPart.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 4 ++-- .../chat/browser/contrib/chatImplicitContext.ts | 2 +- .../chat/browser/contrib/chatInputCompletions.ts | 2 +- src/vs/workbench/contrib/chat/common/chatService.ts | 2 +- .../workbench/contrib/chat/common/chatServiceImpl.ts | 4 ++-- src/vs/workbench/contrib/chat/common/constants.ts | 7 ++++--- .../electron-browser/actions/voiceChatActions.ts | 2 +- .../contrib/chat/test/common/chatModel.test.ts | 2 +- .../emptyTextEditorHint/emptyTextEditorHint.ts | 4 ++-- .../inlineChat/browser/inlineChatController.ts | 12 ++++++------ .../inlineChat/browser/inlineChatCurrentLine.ts | 2 +- .../browser/inlineChatSessionServiceImpl.ts | 6 +++--- .../test/browser/inlineChatController.test.ts | 12 ++++++------ .../test/browser/inlineChatSession.test.ts | 2 +- .../controller/chat/notebookChatController.ts | 2 +- 20 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index e05fb91450a..a8e1a259907 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -495,7 +495,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS // in-place converting for location-data let location: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined; - if (request.locationData?.type === ChatAgentLocation.TextEditor) { + if (request.locationData?.type === ChatAgentLocation.EditorInline) { // editor data const document = this._documents.getDocument(request.locationData.document); location = new extHostTypes.ChatRequestEditorData(document, typeConvert.Selection.to(request.locationData.selection), typeConvert.Range.to(request.locationData.wholeRange)); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4059635412c..4255ae1ba0f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3167,7 +3167,7 @@ export namespace ChatLocation { case ChatAgentLocation.Notebook: return types.ChatLocation.Notebook; case ChatAgentLocation.Terminal: return types.ChatLocation.Terminal; case ChatAgentLocation.Chat: return types.ChatLocation.Panel; - case ChatAgentLocation.TextEditor: return types.ChatLocation.Editor; + case ChatAgentLocation.EditorInline: return types.ChatLocation.Editor; } } @@ -3176,7 +3176,7 @@ export namespace ChatLocation { case types.ChatLocation.Notebook: return ChatAgentLocation.Notebook; case types.ChatLocation.Terminal: return ChatAgentLocation.Terminal; case types.ChatLocation.Panel: return ChatAgentLocation.Chat; - case types.ChatLocation.Editor: return ChatAgentLocation.TextEditor; + case types.ChatLocation.Editor: return ChatAgentLocation.EditorInline; } } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 7b1932b7df7..262713c22f8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -339,7 +339,7 @@ class OpenModelPickerAction extends Action2 { ChatContextKeys.languageModelsAreUserSelectable, ContextKeyExpr.or( ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat), - ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.TextEditor), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditorInline), ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Notebook), ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Terminal)) ) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 463302e8b69..267571f8e44 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -523,7 +523,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio get feature(): string { if (responseModel.session.initialLocation === ChatAgentLocation.Chat) { return 'sideBarChat'; - } else if (responseModel.session.initialLocation === ChatAgentLocation.TextEditor) { + } else if (responseModel.session.initialLocation === ChatAgentLocation.EditorInline) { return 'inlineChat'; } return responseModel.session.initialLocation; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 2e927b7a08c..6c48ad6ac06 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1237,7 +1237,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge hoverDelegate, hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu actionViewItemProvider: (action, options) => { - if (this.location === ChatAgentLocation.Chat || this.location === ChatAgentLocation.TextEditor) { + if (this.location === ChatAgentLocation.Chat || this.location === ChatAgentLocation.EditorInline) { if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID || action.id === ChatEditingSessionSubmitAction.ID) && action instanceof MenuItemAction) { const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, { ...options, menuAsChild: false }); diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index edac5cc0c7a..969c2f0a208 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -126,7 +126,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { case ChatAgentLocation.Terminal: id = 'setup.terminal'; break; - case ChatAgentLocation.TextEditor: + case ChatAgentLocation.EditorInline: id = 'setup.editor'; break; case ChatAgentLocation.Notebook: @@ -876,7 +876,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr // Inline Agents disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.TextEditor, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable); } // Built-In Agent + Tool (unless installed, signed-in and enabled) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 32bde44a062..b2f895f09bc 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -221,7 +221,7 @@ export class ChatImplicitContextContribution extends Disposable implements IWork const isPromptFile = languageId && getPromptsTypeForLanguageId(languageId) !== undefined; - const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.TextEditor)]; + const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.EditorInline)]; for (const widget of widgets) { if (!widget.input.implicitContext) { continue; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 29560be4f85..fd013253cf7 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -805,7 +805,7 @@ class BuiltinDynamicCompletions extends Disposable { return; } - if (widget.location === ChatAgentLocation.TextEditor) { + if (widget.location === ChatAgentLocation.EditorInline) { return; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index f9270b91e95..83623894a1b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -622,7 +622,7 @@ export interface IChatSendRequestData extends IChatSendRequestResponseState { } export interface IChatEditorLocationData { - type: ChatAgentLocation.TextEditor; + type: ChatAgentLocation.EditorInline; document: URI; selection: ISelection; wholeRange: IRange; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index d88ad4cc6a6..fbf866193bc 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -180,7 +180,7 @@ export class ChatService extends Disposable implements IChatService { private saveState(): void { const liveChats = Array.from(this._sessionModels.values()) .filter(session => - this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.TextEditor)); + this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline)); if (this.useFileStorage) { this._chatSessionStore.storeSessions(liveChats); @@ -1245,7 +1245,7 @@ export class ChatService extends Disposable implements IChatService { throw new Error(`Unknown session: ${sessionId}`); } - if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.TextEditor)) { + if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { if (this.useFileStorage) { // Always preserve sessions that have custom titles, even if empty if (model.getRequests().length === 0 && !model.customTitle) { diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 51799dcd353..02e93df759a 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -59,14 +59,15 @@ export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook' | 'ed export enum ChatAgentLocation { /** * This is chat, whether it's in the sidebar, a chat editor, or quick chat. + * Leaving the values alone as they are in stored data so we don't have to normalize them. */ Chat = 'panel', Terminal = 'terminal', Notebook = 'notebook', /** - * TextEditor means inline chat in a text editor. + * EditorInline means inline chat in a text editor. */ - TextEditor = 'editor', + EditorInline = 'editor', } export namespace ChatAgentLocation { @@ -75,7 +76,7 @@ export namespace ChatAgentLocation { case 'panel': return ChatAgentLocation.Chat; case 'terminal': return ChatAgentLocation.Terminal; case 'notebook': return ChatAgentLocation.Notebook; - case 'editor': return ChatAgentLocation.TextEditor; + case 'editor': return ChatAgentLocation.EditorInline; } return ChatAgentLocation.Chat; } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index 5e865dbf3b5..2b4dde3220c 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -686,7 +686,7 @@ class ChatSynthesizerSessionController { const chatWidgetService = accessor.get(IChatWidgetService); const contextKeyService = accessor.get(IContextKeyService); let chatWidget = chatWidgetService.getWidgetBySessionId(response.session.sessionId); - if (chatWidget?.location === ChatAgentLocation.TextEditor) { + if (chatWidget?.location === ChatAgentLocation.EditorInline) { // workaround for https://github.com/microsoft/vscode/issues/212785 chatWidget = chatWidgetService.lastFocusedWidget; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index c4d2b48ed6f..2fffcc57371 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -52,7 +52,7 @@ suite('ChatModel', () => { }); test('adoptRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.TextEditor)); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.EditorInline)); const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); const text = 'hello'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 43e756c8a22..3771cb6a36e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -110,7 +110,7 @@ export class EmptyTextEditorHintContribution extends Disposable implements IEdit return false; } - const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)); + const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline)); const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID; return hasEditorAgents || shouldRenderDefaultHint; } @@ -200,7 +200,7 @@ class EmptyTextEditorHintContentWidget extends Disposable implements IContentWid } private getHint() { - const hasInlineChatProvider = this.chatAgentService.getActivatedAgents().filter(candidate => candidate.locations.includes(ChatAgentLocation.TextEditor)).length > 0; + const hasInlineChatProvider = this.chatAgentService.getActivatedAgents().filter(candidate => candidate.locations.includes(ChatAgentLocation.EditorInline)).length > 0; const hintHandler: IContentActionHandler = { disposables: this._store, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index bfd9f1201bc..18696e2d01d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -228,12 +228,12 @@ export class InlineChatController1 implements IEditorContribution { this._ui = new Lazy(() => { const location: IChatWidgetLocationOptions = { - location: ChatAgentLocation.TextEditor, + location: ChatAgentLocation.EditorInline, resolveData: () => { assertType(this._editor.hasModel()); assertType(this._session); return { - type: ChatAgentLocation.TextEditor, + type: ChatAgentLocation.EditorInline, selection: this._editor.getSelection(), document: this._session.textModelN.uri, wholeRange: this._session?.wholeRange.trackedInitialRange, @@ -1261,12 +1261,12 @@ export class InlineChatController2 implements IEditorContribution { const location: IChatWidgetLocationOptions = { - location: ChatAgentLocation.TextEditor, + location: ChatAgentLocation.EditorInline, resolveData: () => { assertType(this._editor.hasModel()); return { - type: ChatAgentLocation.TextEditor, + type: ChatAgentLocation.EditorInline, selection: this._editor.getSelection(), document: this._editor.getModel().uri, wholeRange: this._editor.getSelection(), @@ -1532,7 +1532,7 @@ export async function reviewEdits(accessor: ServicesAccessor, editor: ICodeEdito const chatService = accessor.get(IChatService); const uri = editor.getModel().uri; - const chatModel = chatService.startSession(ChatAgentLocation.TextEditor, token, false); + const chatModel = chatService.startSession(ChatAgentLocation.EditorInline, token, false); chatModel.startEditingSession(true); @@ -1585,7 +1585,7 @@ export async function reviewNotebookEdits(accessor: ServicesAccessor, uri: URI, const chatService = accessor.get(IChatService); const notebookService = accessor.get(INotebookService); const isNotebook = notebookService.hasSupportedNotebooks(uri); - const chatModel = chatService.startSession(ChatAgentLocation.TextEditor, token, false); + const chatModel = chatService.startSession(ChatAgentLocation.EditorInline, token, false); chatModel.startEditingSession(true); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 1911661d49b..dc0266c2baf 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -286,7 +286,7 @@ export class InlineChatHintsController extends Disposable implements IEditorCont return; } - const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)?.name ?? localize('defaultTitle', "Chat"); + const agentName = chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline)?.name ?? localize('defaultTitle', "Chat"); const { position, isEol, isWhitespace, kb, model } = showData; const inlineClassName: string[] = ['a' /*HACK but sorts as we want*/, 'inline-chat-hint', 'inline-chat-hint-text']; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index cd9a06250e1..9f00ec5f076 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -108,7 +108,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { async createSession(editor: IActiveCodeEditor, options: { headless?: boolean; wholeRange?: Range; session?: Session }, token: CancellationToken): Promise { - const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor); + const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline); if (!agent) { this._logService.trace('[IE] NO agent found'); @@ -123,7 +123,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const store = new DisposableStore(); this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`); - const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.TextEditor, token); + const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.EditorInline, token); if (!chatModel) { this._logService.trace('[IE] NO chatModel found'); return undefined; @@ -431,7 +431,7 @@ export class InlineChatEnabler { this._ctxHasProvider2 = CTX_INLINE_CHAT_HAS_AGENT2.bindTo(contextKeyService); this._ctxPossible = CTX_INLINE_CHAT_POSSIBLE.bindTo(contextKeyService); - const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.TextEditor)); + const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline)); const inlineChat2Obs = observableConfigValue(InlineChatConfigKeys.EnableV2, false, configService); this._store.add(autorun(r => { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 1de902e778c..73f1202ff19 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -94,7 +94,7 @@ suite('InlineChatController', function () { // id: 'testEditorAgent', name: 'testEditorAgent', isDefault: true, - locations: [ChatAgentLocation.TextEditor], + locations: [ChatAgentLocation.EditorInline], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], @@ -611,7 +611,7 @@ suite('InlineChatController', function () { assert.strictEqual(model.getValue(), 'eins\nHello\nWorld\nHello Again\nHello World\n'); - const targetModel = chatService.startSession(ChatAgentLocation.TextEditor, CancellationToken.None)!; + const targetModel = chatService.startSession(ChatAgentLocation.EditorInline, CancellationToken.None)!; store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { @@ -659,7 +659,7 @@ suite('InlineChatController', function () { assert.strictEqual(model.getValue(), 'zwei\neins\nHello\nWorld\nHello Again\nHello World\n'); - const targetModel = chatService.startSession(ChatAgentLocation.TextEditor, CancellationToken.None)!; + const targetModel = chatService.startSession(ChatAgentLocation.EditorInline, CancellationToken.None)!; store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { @@ -722,7 +722,7 @@ suite('InlineChatController', function () { assertType(request); const p2 = Event.toPromise(onDidInvoke.event); const p3 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); - chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.TextEditor }); + chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.EditorInline }); await p2; assert.strictEqual(await p3, undefined); @@ -759,7 +759,7 @@ suite('InlineChatController', function () { const request = ctrl.chatWidget.viewModel?.model.getRequests().at(-1); assertType(request); const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); - chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.TextEditor }); + chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.EditorInline }); assert.strictEqual(await p2, undefined); @@ -864,7 +864,7 @@ suite('InlineChatController', function () { const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None); assertType(newSession); - await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.TextEditor }))?.responseCreatedPromise; + await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.EditorInline }))?.responseCreatedPromise; assert.strictEqual(newSession.chatModel.requestInProgress, true); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index c6a79830343..7355112eab7 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -152,7 +152,7 @@ suite('InlineChatSession', function () { id: 'testAgent', name: 'testAgent', isDefault: true, - locations: [ChatAgentLocation.TextEditor], + locations: [ChatAgentLocation.EditorInline], modes: [ChatModeKind.Ask], metadata: {}, slashCommands: [], diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index c4b027c1fb7..205d040c15b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -501,7 +501,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private async _startSession(token: CancellationToken) { if (!this._model.value) { - this._model.value = this._chatService.startSession(ChatAgentLocation.TextEditor, token); + this._model.value = this._chatService.startSession(ChatAgentLocation.EditorInline, token); if (!this._model.value) { throw new Error('Failed to start chat session'); From 368b72b9dd475dd10ceab7226073a44710f3420a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 7 Sep 2025 15:22:24 -0700 Subject: [PATCH 0068/4355] Get rid of obsolete 'useFileStorage' chat setting (#265593) * Get rid of obsolete 'useFileStorage' chat setting * Fix ChatService test, use real IFileService * Don't require writeFile with valid readable * Restore these files to avoid hitting CODEOWNERS --- .../test/browser/mainThreadEditors.test.ts | 22 +- .../contrib/chat/browser/chat.contribution.ts | 6 - .../contrib/chat/common/chatServiceImpl.ts | 266 ++-------- .../contrib/chat/common/constants.ts | 1 - .../chat/test/common/chatService.test.ts | 39 +- .../electron-browser/fetchPageTool.test.ts | 2 +- .../debug/test/browser/mockDebugModel.ts | 3 +- .../files/test/browser/editorAutoSave.test.ts | 26 +- .../test/browser/explorerFindProvider.test.ts | 32 +- .../files/test/browser/explorerModel.test.ts | 10 +- .../files/test/browser/explorerView.test.ts | 3 +- .../browser/textFileEditorTracker.test.ts | 36 +- .../browser/relauncher.contribution.ts | 6 - .../gettingStartedMarkdownRenderer.test.ts | 3 +- .../test/browser/extensionService.test.ts | 4 +- .../test/browser/workbenchTestServices.ts | 481 ++++++------------ .../test/common/workbenchTestServices.ts | 296 ++++++++++- .../electron-browser/workbenchTestServices.ts | 58 +-- 18 files changed, 607 insertions(+), 687 deletions(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 6571174e65d..976470f14b8 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -14,12 +14,18 @@ import { ICodeEditorService } from '../../../../editor/browser/services/codeEdit import { EditOperation } from '../../../../editor/common/core/editOperation.js'; import { Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js'; import { ITextSnapshot } from '../../../../editor/common/model.js'; import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; +import { LanguageService } from '../../../../editor/common/services/languageService.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { ModelService } from '../../../../editor/common/services/modelService.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; import { TestCodeEditorService } from '../../../../editor/test/browser/editorTestServices.js'; +import { TestLanguageConfigurationService } from '../../../../editor/test/common/modes/testLanguageConfigurationService.js'; +import { TestTreeSitterLibraryService } from '../../../../editor/test/common/services/testTreeSitterLibraryService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -39,9 +45,6 @@ import { UndoRedoService } from '../../../../platform/undoRedo/common/undoRedoSe import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { UriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentityService.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { MainThreadBulkEdits } from '../../browser/mainThreadBulkEdits.js'; -import { IWorkspaceTextEditDto } from '../../common/extHost.protocol.js'; -import { SingleProxyRPCProtocol } from '../common/testRPCProtocol.js'; import { BulkEditService } from '../../../contrib/bulkEdit/browser/bulkEditService.js'; import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -53,14 +56,11 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { ICopyOperation, ICreateFileOperation, ICreateOperation, IDeleteOperation, IMoveOperation, IWorkingCopyFileService } from '../../../services/workingCopy/common/workingCopyFileService.js'; import { IWorkingCopyService } from '../../../services/workingCopy/common/workingCopyService.js'; -import { TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestFileService, TestLifecycleService, TestWorkingCopyService } from '../../../test/browser/workbenchTestServices.js'; -import { TestContextService, TestTextResourcePropertiesService } from '../../../test/common/workbenchTestServices.js'; -import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { LanguageService } from '../../../../editor/common/services/languageService.js'; -import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js'; -import { TestLanguageConfigurationService } from '../../../../editor/test/common/modes/testLanguageConfigurationService.js'; -import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import { TestTreeSitterLibraryService } from '../../../../editor/test/common/services/testTreeSitterLibraryService.js'; +import { TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestWorkingCopyService } from '../../../test/browser/workbenchTestServices.js'; +import { TestContextService, TestFileService, TestTextResourcePropertiesService } from '../../../test/common/workbenchTestServices.js'; +import { MainThreadBulkEdits } from '../../browser/mainThreadBulkEdits.js'; +import { IWorkspaceTextEditDto } from '../../common/extHost.protocol.js'; +import { SingleProxyRPCProtocol } from '../common/testRPCProtocol.js'; suite('MainThreadEditors', () => { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 6ef6b15e90d..96a34887389 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -400,12 +400,6 @@ configurationRegistry.registerConfiguration({ mode: 'startup' } }, - [ChatConfiguration.UseFileStorage]: { - type: 'boolean', - description: nls.localize('chat.useFileStorage', "Enables storing chat sessions on disk instead of in the storage service. Enabling this does a one-time per-workspace migration of existing sessions to the new format."), - default: true, - tags: ['experimental'], - }, [ChatConfiguration.Edits2Enabled]: { type: 'boolean', description: nls.localize('chat.edits2Enabled', "Enable the new Edits mode that is based on tool-calling. When this is enabled, models that don't support tool-calling are unavailable for Edits mode."), diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index fbf866193bc..b74899a7271 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -5,7 +5,6 @@ import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; -import { memoize } from '../../../../base/common/decorators.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { ErrorNoTelemetry } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; @@ -44,12 +43,10 @@ import { ILanguageModelToolsService } from './languageModelToolsService.js'; const serializedChatKey = 'interactive.sessions'; -const globalChatKey = 'chat.workspaceTransfer'; +const TransferredGlobalChatKey = 'chat.workspaceTransfer'; const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60; -const maxPersistedSessions = 25; - class CancellableRequest implements IDisposable { constructor( public readonly cancellationTokenSource: CancellationTokenSource, @@ -78,9 +75,6 @@ export class ChatService extends Disposable implements IChatService { private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; - /** Just for empty windows, need to enforce that a chat was deleted, even though other windows still have it */ - private _deletedChatIds = new Set(); - private _transferredSessionData: IChatTransferredSessionData | undefined; public get transferredSessionData(): IChatTransferredSessionData | undefined { return this._transferredSessionData; @@ -101,11 +95,6 @@ export class ChatService extends Disposable implements IChatService { readonly requestInProgressObs: IObservable; - @memoize - private get useFileStorage(): boolean { - return this.configurationService.getValue(ChatConfiguration.UseFileStorage); - } - public get edits2Enabled(): boolean { return this.configurationService.getValue(ChatConfiguration.Edits2Enabled); } @@ -157,13 +146,11 @@ export class ChatService extends Disposable implements IChatService { } this._chatSessionStore = this._register(this.instantiationService.createInstance(ChatSessionStore)); - if (this.useFileStorage) { - this._chatSessionStore.migrateDataIfNeeded(() => this._persistedSessions); + this._chatSessionStore.migrateDataIfNeeded(() => this._persistedSessions); - // When using file storage, populate _persistedSessions with session metadata from the index - // This ensures that getPersistedSessionTitle() can find titles for inactive sessions - this.initializePersistedSessionsFromFileStorage(); - } + // When using file storage, populate _persistedSessions with session metadata from the index + // This ensures that getPersistedSessionTitle() can find titles for inactive sessions + this.initializePersistedSessionsFromFileStorage(); this._register(storageService.onWillSaveState(() => this.saveState())); @@ -182,87 +169,7 @@ export class ChatService extends Disposable implements IChatService { .filter(session => this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline)); - if (this.useFileStorage) { - this._chatSessionStore.storeSessions(liveChats); - } else { - if (this.isEmptyWindow) { - this.syncEmptyWindowChats(liveChats); - } else { - let allSessions: (ChatModel | ISerializableChatData)[] = liveChats; - allSessions = allSessions.concat( - Object.values(this._persistedSessions) - .filter(session => !this._sessionModels.has(session.sessionId)) - .filter(session => session.requests.length)); - allSessions.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); - - allSessions = allSessions.slice(0, maxPersistedSessions); - - if (allSessions.length) { - this.trace('onWillSaveState', `Persisting ${allSessions.length} sessions`); - } - - const serialized = JSON.stringify(allSessions); - - if (allSessions.length) { - this.trace('onWillSaveState', `Persisting ${serialized.length} chars`); - } - - this.storageService.store(serializedChatKey, serialized, StorageScope.WORKSPACE, StorageTarget.MACHINE); - } - - } - - this._deletedChatIds.clear(); - } - - private syncEmptyWindowChats(thisWindowChats: ChatModel[]): void { - // Note- an unavoidable race condition exists here. If there are multiple empty windows open, and the user quits the application, then the focused - // window may lose active chats, because all windows are reading and writing to storageService at the same time. This can't be fixed without some - // kind of locking, but in reality, the focused window will likely have run `saveState` at some point, like on a window focus change, and it will - // generally be fine. - const sessionData = this.storageService.get(serializedChatKey, StorageScope.APPLICATION, ''); - - const originalPersistedSessions = this._persistedSessions; - let persistedSessions: ISerializableChatsData; - if (sessionData) { - persistedSessions = this.deserializeChats(sessionData); - const countsForLog = Object.keys(persistedSessions).length; - if (countsForLog > 0) { - this.trace('constructor', `Restored ${countsForLog} persisted sessions`); - } - } else { - persistedSessions = {}; - } - - this._deletedChatIds.forEach(id => delete persistedSessions[id]); - - // Has the chat in this window been updated, and then closed? Overwrite the old persisted chats. - Object.values(originalPersistedSessions).forEach(session => { - const persistedSession = persistedSessions[session.sessionId]; - if (persistedSession && session.requests.length > persistedSession.requests.length) { - // We will add a 'modified date' at some point, but comparing the number of requests is good enough - persistedSessions[session.sessionId] = session; - } else if (!persistedSession && session.isNew) { - // This session was created in this window, and hasn't been persisted yet - session.isNew = false; - persistedSessions[session.sessionId] = session; - } - }); - - this._persistedSessions = persistedSessions; - - // Add this window's active chat models to the set to persist. - // Having the same session open in two empty windows at the same time can lead to data loss, this is acceptable - const allSessions: Record = { ...this._persistedSessions }; - for (const chat of thisWindowChats) { - allSessions[chat.sessionId] = chat; - } - - let sessionsList = Object.values(allSessions); - sessionsList.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); - sessionsList = sessionsList.slice(0, maxPersistedSessions); - const data = JSON.stringify(sessionsList); - this.storageService.store(serializedChatKey, data, StorageScope.APPLICATION, StorageTarget.MACHINE); + this._chatSessionStore.storeSessions(liveChats); } notifyUserAction(action: IChatUserActionEvent): void { @@ -282,38 +189,10 @@ export class ChatService extends Disposable implements IChatService { model.setCustomTitle(title); } - // Also update the persisted session data - if (this.useFileStorage) { - // Update the title in the file storage - await this._chatSessionStore.setSessionTitle(sessionId, title); - // Trigger immediate save to ensure consistency - await this.saveState(); - } else { - // Update the in-memory storage - if (this._persistedSessions[sessionId]) { - this._persistedSessions[sessionId].customTitle = title; - } else { - // Create a minimal placeholder entry with the title - // The full session data will be merged later when the session is activated or saved - this._persistedSessions[sessionId] = { - version: 3, - sessionId: sessionId, - customTitle: title, - creationDate: Date.now(), - lastMessageDate: Date.now(), - isImported: false, - initialLocation: undefined, - requests: [], - requesterUsername: '', - responderUsername: '', - requesterAvatarIconUri: undefined, - responderAvatarIconUri: undefined, - }; - } - - // Trigger immediate save to ensure the title is persisted - await this.saveState(); - } + // Update the title in the file storage + await this._chatSessionStore.setSessionTitle(sessionId, title); + // Trigger immediate save to ensure consistency + this.saveState(); } private trace(method: string, message?: string): void { @@ -361,7 +240,7 @@ export class ChatService extends Disposable implements IChatService { } private getTransferredSessionData(): IChatTransfer2 | undefined { - const data: IChatTransfer2[] = this.storageService.getObject(globalChatKey, StorageScope.PROFILE, []); + const data: IChatTransfer2[] = this.storageService.getObject(TransferredGlobalChatKey, StorageScope.PROFILE, []); const workspaceUri = this.workspaceContextService.getWorkspace().folders[0]?.uri; if (!workspaceUri) { return; @@ -373,7 +252,7 @@ export class ChatService extends Disposable implements IChatService { const transferred = data.find(item => URI.revive(item.toWorkspace).toString() === thisWorkspace && (currentTime - item.timestampInMilliseconds < SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS)); // Keep data that isn't for the current workspace and that hasn't expired yet const filtered = data.filter(item => URI.revive(item.toWorkspace).toString() !== thisWorkspace && (currentTime - item.timestampInMilliseconds < SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS)); - this.storageService.store(globalChatKey, JSON.stringify(filtered), StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store(TransferredGlobalChatKey, JSON.stringify(filtered), StorageScope.PROFILE, StorageTarget.MACHINE); return transferred; } @@ -413,44 +292,6 @@ export class ChatService extends Disposable implements IChatService { * Imported chat sessions are also excluded from the result. */ async getHistory(): Promise { - if (this.useFileStorage) { - const liveSessionItems = Array.from(this._sessionModels.values()) - .filter(session => !session.isImported) - .map(session => { - const title = session.title || localize('newChat', "New Chat"); - return { - sessionId: session.sessionId, - title, - lastMessageDate: session.lastMessageDate, - isActive: true, - } satisfies IChatDetail; - }); - - const index = await this._chatSessionStore.getIndex(); - const entries = Object.values(index) - .filter(entry => !this._sessionModels.has(entry.sessionId) && !entry.isImported && !entry.isEmpty) - .map((entry): IChatDetail => ({ - ...entry, - isActive: this._sessionModels.has(entry.sessionId), - })); - return [...liveSessionItems, ...entries]; - } - - const persistedSessions = Object.values(this._persistedSessions) - .filter(session => session.requests.length > 0) - .filter(session => !this._sessionModels.has(session.sessionId)); - - const persistedSessionItems = persistedSessions - .filter(session => !session.isImported) - .map(session => { - const title = session.customTitle ?? ChatModel.getDefaultTitle(session.requests); - return { - sessionId: session.sessionId, - title, - lastMessageDate: session.lastMessageDate, - isActive: false, - } satisfies IChatDetail; - }); const liveSessionItems = Array.from(this._sessionModels.values()) .filter(session => !session.isImported) .map(session => { @@ -462,31 +303,23 @@ export class ChatService extends Disposable implements IChatService { isActive: true, } satisfies IChatDetail; }); - return [...liveSessionItems, ...persistedSessionItems]; + + const index = await this._chatSessionStore.getIndex(); + const entries = Object.values(index) + .filter(entry => !this._sessionModels.has(entry.sessionId) && !entry.isImported && !entry.isEmpty) + .map((entry): IChatDetail => ({ + ...entry, + isActive: this._sessionModels.has(entry.sessionId), + })); + return [...liveSessionItems, ...entries]; } async removeHistoryEntry(sessionId: string): Promise { - if (this.useFileStorage) { - await this._chatSessionStore.deleteSession(sessionId); - return; - } - - if (this._persistedSessions[sessionId]) { - this._deletedChatIds.add(sessionId); - delete this._persistedSessions[sessionId]; - this.saveState(); - } + await this._chatSessionStore.deleteSession(sessionId); } async clearAllHistoryEntries(): Promise { - if (this.useFileStorage) { - await this._chatSessionStore.clearAllSessions(); - return; - } - - Object.values(this._persistedSessions).forEach(session => this._deletedChatIds.add(session.sessionId)); - this._persistedSessions = {}; - this.saveState(); + await this._chatSessionStore.clearAllSessions(); } startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true): ChatModel { @@ -551,7 +384,7 @@ export class ChatService extends Disposable implements IChatService { } let sessionData: ISerializableChatData | undefined; - if (!this.useFileStorage || this.transferredSessionData?.sessionId === sessionId) { + if (this.transferredSessionData?.sessionId === sessionId) { sessionData = revive(this._persistedSessions[sessionId]); } else { sessionData = revive(await this._chatSessionStore.readSession(sessionId)); @@ -591,18 +424,16 @@ export class ChatService extends Disposable implements IChatService { return title; } - // If using file storage and not found in memory, try to read directly from file storage index + // Try to read directly from file storage index // This handles the case where getName() is called before initialization completes - if (this.useFileStorage) { - // Access the internal synchronous index method via reflection - // This is a workaround for the timing issue where initialization hasn't completed - const internalGetIndex = (this._chatSessionStore as any).internalGetIndex; - if (typeof internalGetIndex === 'function') { - const indexData = internalGetIndex.call(this._chatSessionStore); - const metadata = indexData.entries[sessionId]; - if (metadata && metadata.title) { - return metadata.title; - } + // Access the internal synchronous index method via reflection + // This is a workaround for the timing issue where initialization hasn't completed + const internalGetIndex = (this._chatSessionStore as any).internalGetIndex; + if (typeof internalGetIndex === 'function') { + const indexData = internalGetIndex.call(this._chatSessionStore); + const metadata = indexData.entries[sessionId]; + if (metadata && metadata.title) { + return metadata.title; } } @@ -1246,24 +1077,11 @@ export class ChatService extends Disposable implements IChatService { } if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { - if (this.useFileStorage) { - // Always preserve sessions that have custom titles, even if empty - if (model.getRequests().length === 0 && !model.customTitle) { - await this._chatSessionStore.deleteSession(sessionId); - } else { - await this._chatSessionStore.storeSessions([model]); - } + // Always preserve sessions that have custom titles, even if empty + if (model.getRequests().length === 0 && !model.customTitle) { + await this._chatSessionStore.deleteSession(sessionId); } else { - // Always preserve sessions that have custom titles, even if empty - if (model.getRequests().length === 0 && !model.customTitle) { - delete this._persistedSessions[sessionId]; - } else { - // Turn all the real objects into actual JSON, otherwise, calling 'revive' may fail when it tries to - // assign values to properties that are getters- microsoft/vscode-copilot-release#1233 - const sessionData: ISerializableChatData = JSON.parse(JSON.stringify(model)); - sessionData.isNew = true; - this._persistedSessions[sessionId] = sessionData; - } + await this._chatSessionStore.storeSessions([model]); } } @@ -1275,11 +1093,7 @@ export class ChatService extends Disposable implements IChatService { } public hasSessions(): boolean { - if (this.useFileStorage) { - return this._chatSessionStore.hasSessions(); - } else { - return Object.values(this._persistedSessions).length > 0; - } + return this._chatSessionStore.hasSessions(); } transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { @@ -1288,7 +1102,7 @@ export class ChatService extends Disposable implements IChatService { throw new Error(`Failed to transfer session. Unknown session ID: ${transferredSessionData.sessionId}`); } - const existingRaw: IChatTransfer2[] = this.storageService.getObject(globalChatKey, StorageScope.PROFILE, []); + const existingRaw: IChatTransfer2[] = this.storageService.getObject(TransferredGlobalChatKey, StorageScope.PROFILE, []); existingRaw.push({ chat: model.toJSON(), timestampInMilliseconds: Date.now(), @@ -1298,7 +1112,7 @@ export class ChatService extends Disposable implements IChatService { mode: transferredSessionData.mode, }); - this.storageService.store(globalChatKey, JSON.stringify(existingRaw), StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store(TransferredGlobalChatKey, JSON.stringify(existingRaw), StorageScope.PROFILE, StorageTarget.MACHINE); this.chatTransferService.addWorkspaceToTransferred(toWorkspace); this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 02e93df759a..2ad22fa60fd 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -6,7 +6,6 @@ import { Schemas } from '../../../../base/common/network.js'; export enum ChatConfiguration { - UseFileStorage = 'chat.useFileStorage', AgentEnabled = 'chat.agent.enabled', Edits2Enabled = 'chat.edits2.enabled', ExtensionToolsEnabled = 'chat.extensionTools.enabled', diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index e5158911944..553b733a182 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -16,6 +16,7 @@ import { IConfigurationService } from '../../../../../platform/configuration/com import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; @@ -23,13 +24,14 @@ import { ILogService, NullLogService } from '../../../../../platform/log/common/ import { IStorageService } from '../../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; +import { IUserDataProfilesService } from '../../../../../platform/userDataProfile/common/userDataProfile.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js'; import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js'; import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { mock, TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { InMemoryTestFileService, mock, TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { IMcpService } from '../../../mcp/common/mcpTypes.js'; import { TestMcpService } from '../../../mcp/test/common/testMcpService.js'; import { ChatAgentService, IChatAgent, IChatAgentData, IChatAgentImplementation, IChatAgentService } from '../../common/chatAgents.js'; @@ -121,8 +123,8 @@ function getAgentData(id: string): IChatAgentData { suite('ChatService', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - let storageService: IStorageService; let instantiationService: TestInstantiationService; + let testFileService: InMemoryTestFileService; let chatAgentService: IChatAgentService; @@ -132,7 +134,7 @@ suite('ChatService', () => { [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()], [IMcpService, new TestMcpService()], ))); - instantiationService.stub(IStorageService, storageService = testDisposables.add(new TestStorageService())); + instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IExtensionService, new TestExtensionService()); @@ -144,12 +146,21 @@ suite('ChatService', () => { instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IEnvironmentService, { workspaceStorageHome: URI.file('/test/path/to/workspaceStorage') }); instantiationService.stub(ILifecycleService, { onWillShutdown: Event.None }); + instantiationService.stub(IUserDataProfilesService, { + defaultProfile: { + globalStorageHome: URI.file('/test/path/to/globalStorage') + } + } as any); instantiationService.stub(IChatEditingService, new class extends mock() { override startOrContinueGlobalEditingSession(): Promise { return Promise.resolve(Disposable.None as IChatEditingSession); } }); + // Configure test file service with tracking and in-memory storage + testFileService = testDisposables.add(new InMemoryTestFileService()); + instantiationService.stub(IFileService, testFileService); + chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, chatAgentService); @@ -173,10 +184,30 @@ suite('ChatService', () => { const session2 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }, 0); - storageService.flush(); + // Clear sessions to trigger persistence to file service + await testService.clearSession(session1.sessionId); + await testService.clearSession(session2.sessionId); + + // Verify that sessions were written to the file service + assert.strictEqual(testFileService.writeOperations.length, 2, 'Should have written 2 sessions to file service'); + + const session1WriteOp = testFileService.writeOperations.find((op: { resource: URI; content: string }) => + op.content.includes('request 1')); + const session2WriteOp = testFileService.writeOperations.find((op: { resource: URI; content: string }) => + op.content.includes('request 2')); + + assert.ok(session1WriteOp, 'Session 1 should have been written to file service'); + assert.ok(session2WriteOp, 'Session 2 should have been written to file service'); + + // Create a new service instance to simulate app restart const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); + + // Retrieve sessions and verify they're loaded from file service const retrieved1 = testDisposables.add((await testService2.getOrRestoreSession(session1.sessionId))!); const retrieved2 = testDisposables.add((await testService2.getOrRestoreSession(session2.sessionId))!); + + assert.ok(retrieved1, 'Should retrieve session 1'); + assert.ok(retrieved2, 'Should retrieve session 2'); assert.deepStrictEqual(retrieved1.getRequests()[0]?.message.text, 'request 1'); assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); }); diff --git a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts index 90db75f15a2..618e4ed1362 100644 --- a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { IFileContent, IReadFileOptions } from '../../../../../platform/files/common/files.js'; import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { FetchWebPageTool } from '../../electron-browser/tools/fetchPageTool.js'; -import { TestFileService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestFileService } from '../../../../test/common/workbenchTestServices.js'; class TestWebContentExtractorService implements IWebContentExtractorService { _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts index 1779cb14166..169e6dcd401 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts @@ -6,10 +6,9 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; +import { TestFileService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { DebugModel } from '../../common/debugModel.js'; import { MockDebugStorage } from '../common/mockDebug.js'; -import { TestFileService } from '../../../../test/browser/workbenchTestServices.js'; -import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; const fileService = new TestFileService(); export const mockUriIdentityService = new UriIdentityService(fileService); diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 759166497bd..7ccfd8c92b7 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -5,25 +5,25 @@ import assert from 'assert'; import { Event } from '../../../../../base/common/event.js'; -import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; -import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor, registerTestFileEditor, createEditorPart, TestEnvironmentService, TestFileService, TestTextResourceConfigurationService } from '../../../../test/browser/workbenchTestServices.js'; -import { ITextFileEditorModel } from '../../../../services/textfile/common/textfiles.js'; -import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; -import { TextFileEditorModelManager } from '../../../../services/textfile/common/textFileEditorModelManager.js'; -import { EditorService } from '../../../../services/editor/browser/editorService.js'; -import { EditorAutoSave } from '../../../../browser/parts/editor/editorAutoSave.js'; +import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; +import { IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; -import { DEFAULT_EDITOR_ASSOCIATION } from '../../../../common/editor.js'; -import { TestWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js'; -import { TestContextService, TestMarkerService } from '../../../../test/common/workbenchTestServices.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; -import { IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { TestWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js'; +import { EditorAutoSave } from '../../../../browser/parts/editor/editorAutoSave.js'; +import { DEFAULT_EDITOR_ASSOCIATION } from '../../../../common/editor.js'; +import { EditorService } from '../../../../services/editor/browser/editorService.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; +import { TextFileEditorModelManager } from '../../../../services/textfile/common/textFileEditorModelManager.js'; +import { ITextFileEditorModel } from '../../../../services/textfile/common/textfiles.js'; +import { createEditorPart, registerTestFileEditor, TestEnvironmentService, TestFilesConfigurationService, TestServiceAccessor, TestTextResourceConfigurationService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestContextService, TestFileService, TestMarkerService } from '../../../../test/common/workbenchTestServices.js'; suite('EditorAutoSave', () => { diff --git a/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts index b58b78ebb73..f16d9fa50c1 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts @@ -3,29 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; +import assert from 'assert'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; -import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; -import { ExplorerItem } from '../../common/explorerModel.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; +import { TreeFindMatchType, TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.js'; import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; -import { ITreeNode, IAsyncDataSource, ITreeFilter, TreeFilterResult } from '../../../../../base/browser/ui/tree/tree.js'; +import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; +import { IAsyncDataSource, ITreeFilter, ITreeNode, TreeFilterResult } from '../../../../../base/browser/ui/tree/tree.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; -import { TestFileService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; -import { NullFilesConfigurationService } from '../../../../test/common/workbenchTestServices.js'; -import { ExplorerFindProvider, FilesFilter } from '../../browser/views/explorerViewer.js'; +import { basename } from '../../../../../base/common/resources.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { IWorkbenchCompressibleAsyncDataTreeOptions, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; -import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; -import { FuzzyScore } from '../../../../../base/common/filters.js'; -import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IWorkbenchCompressibleAsyncDataTreeOptions, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { IFileMatch, IFileQuery, ISearchComplete, ISearchService } from '../../../../services/search/common/search.js'; -import { URI } from '../../../../../base/common/uri.js'; -import assert from 'assert'; +import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { NullFilesConfigurationService, TestFileService } from '../../../../test/common/workbenchTestServices.js'; import { IExplorerService } from '../../browser/files.js'; -import { basename } from '../../../../../base/common/resources.js'; -import { TreeFindMatchType, TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.js'; +import { ExplorerFindProvider, FilesFilter } from '../../browser/views/explorerViewer.js'; +import { ExplorerItem } from '../../common/explorerModel.js'; function find(element: ExplorerItem, id: string): ExplorerItem | undefined { if (element.name === id) { diff --git a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts index c097107fb4a..af65a4da6a6 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; +import { join } from '../../../../../base/common/path.js'; import { isLinux, isWindows, OS } from '../../../../../base/common/platform.js'; import { URI } from '../../../../../base/common/uri.js'; -import { join } from '../../../../../base/common/path.js'; -import { validateFileName } from '../../browser/fileActions.js'; -import { ExplorerItem } from '../../common/explorerModel.js'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; -import { TestFileService, TestPathService } from '../../../../test/browser/workbenchTestServices.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { NullFilesConfigurationService } from '../../../../test/common/workbenchTestServices.js'; +import { TestPathService } from '../../../../test/browser/workbenchTestServices.js'; +import { NullFilesConfigurationService, TestFileService } from '../../../../test/common/workbenchTestServices.js'; +import { validateFileName } from '../../browser/fileActions.js'; +import { ExplorerItem } from '../../common/explorerModel.js'; suite('Files - View Model', function () { diff --git a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts index 7e65d5e557f..7be11916f85 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts @@ -6,7 +6,6 @@ import assert from 'assert'; import { Emitter } from '../../../../../base/common/event.js'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; -import { TestFileService } from '../../../../test/browser/workbenchTestServices.js'; import { ExplorerItem } from '../../common/explorerModel.js'; import { getContext } from '../../browser/views/explorerView.js'; import { listInvalidItemForeground } from '../../../../../platform/theme/common/colorRegistry.js'; @@ -15,7 +14,7 @@ import * as dom from '../../../../../base/browser/dom.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { provideDecorations } from '../../browser/views/explorerDecorationsProvider.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { NullFilesConfigurationService } from '../../../../test/common/workbenchTestServices.js'; +import { NullFilesConfigurationService, TestFileService } from '../../../../test/common/workbenchTestServices.js'; suite('Files - ExplorerView', () => { diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index 0f6325a28fc..f29bfd74651 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -4,31 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Event } from '../../../../../base/common/event.js'; -import { TextFileEditorTracker } from '../../browser/editors/textFileEditorTracker.js'; -import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; -import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, registerTestFileEditor, registerTestResourceEditor, createEditorPart, TestEnvironmentService, TestFileService, workbenchTeardown, TestTextResourceConfigurationService } from '../../../../test/browser/workbenchTestServices.js'; -import { IResolvedTextFileEditorModel, snapshotToString, ITextFileService } from '../../../../services/textfile/common/textfiles.js'; -import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult } from '../../../../../platform/files/common/files.js'; -import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { timeout } from '../../../../../base/common/async.js'; +import { Event } from '../../../../../base/common/event.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; -import { TextFileEditorModelManager } from '../../../../services/textfile/common/textFileEditorModelManager.js'; -import { EditorService } from '../../../../services/editor/browser/editorService.js'; -import { UntitledTextEditorInput } from '../../../../services/untitled/common/untitledTextEditorInput.js'; import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; -import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from '../../../../../base/test/common/utils.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; -import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { FILE_EDITOR_INPUT_ID } from '../../common/files.js'; -import { DEFAULT_EDITOR_ASSOCIATION } from '../../../../common/editor.js'; -import { TestWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js'; -import { TestContextService, TestMarkerService } from '../../../../test/common/workbenchTestServices.js'; +import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult } from '../../../../../platform/files/common/files.js'; +import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; +import { TestWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js'; +import { DEFAULT_EDITOR_ASSOCIATION } from '../../../../common/editor.js'; +import { EditorService } from '../../../../services/editor/browser/editorService.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; +import { TextFileEditorModelManager } from '../../../../services/textfile/common/textFileEditorModelManager.js'; +import { IResolvedTextFileEditorModel, ITextFileService, snapshotToString } from '../../../../services/textfile/common/textfiles.js'; +import { UntitledTextEditorInput } from '../../../../services/untitled/common/untitledTextEditorInput.js'; +import { createEditorPart, registerTestFileEditor, registerTestResourceEditor, TestEnvironmentService, TestFilesConfigurationService, TestServiceAccessor, TestTextResourceConfigurationService, workbenchInstantiationService, workbenchTeardown } from '../../../../test/browser/workbenchTestServices.js'; +import { TestContextService, TestFileService, TestMarkerService } from '../../../../test/common/workbenchTestServices.js'; +import { TextFileEditorTracker } from '../../browser/editors/textFileEditorTracker.js'; +import { FILE_EDITOR_INPUT_ID } from '../../common/files.js'; suite('Files - TextFileEditorTracker', () => { diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 03ec6cc730a..793443c6503 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -22,7 +22,6 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IHostService } from '../../../services/host/browser/host.js'; import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { IUserDataSyncWorkbenchService } from '../../../services/userDataSync/common/userDataSync.js'; -import { ChatConfiguration } from '../../chat/common/constants.js'; interface IConfiguration extends IWindowsConfiguration { update?: { mode?: string }; @@ -34,7 +33,6 @@ interface IConfiguration extends IWindowsConfiguration { telemetry?: { feedback?: { enabled?: boolean } }; _extensionsGallery?: { enablePPE?: boolean }; accessibility?: { verbosity?: { debug?: boolean } }; - chat?: { useFileStorage?: boolean }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -54,7 +52,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo '_extensionsGallery.enablePPE', 'security.restrictUNCAccess', 'accessibility.verbosity.debug', - ChatConfiguration.UseFileStorage, 'telemetry.feedback.enabled' ]; @@ -72,7 +69,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private readonly enablePPEExtensionsGallery = new ChangeObserver('boolean'); private readonly restrictUNCAccess = new ChangeObserver('boolean'); private readonly accessibilityVerbosityDebug = new ChangeObserver('boolean'); - private readonly useFileStorage = new ChangeObserver('boolean'); private readonly telemetryFeedbackEnabled = new ChangeObserver('boolean'); constructor( @@ -158,8 +154,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo // Debug accessibility verbosity processChanged(this.accessibilityVerbosityDebug.handleChange(config?.accessibility?.verbosity?.debug)); - - processChanged(this.useFileStorage.handleChange(config.chat?.useFileStorage)); } // Experiments diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts b/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts index 74f74af49ec..73117f36638 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts @@ -10,8 +10,7 @@ import { LanguageService } from '../../../../../editor/common/services/languageS import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js'; import { GettingStartedDetailsRenderer } from '../../browser/gettingStartedDetailsRenderer.js'; import { convertInternalMediaPathToFileURI } from '../../browser/gettingStartedService.js'; -import { TestFileService } from '../../../../test/browser/workbenchTestServices.js'; -import { TestExtensionService } from '../../../../test/common/workbenchTestServices.js'; +import { TestExtensionService, TestFileService } from '../../../../test/common/workbenchTestServices.js'; suite('Getting Started Markdown Renderer', () => { diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 6048d10c847..07b82a2c144 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -46,8 +46,8 @@ import { ILifecycleService } from '../../../lifecycle/common/lifecycle.js'; import { IRemoteAgentService } from '../../../remote/common/remoteAgentService.js'; import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js'; import { WorkspaceTrustEnablementService } from '../../../workspaces/common/workspaceTrust.js'; -import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestRemoteExtensionsScannerService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from '../../../../test/browser/workbenchTestServices.js'; -import { TestContextService, TestUserDataProfileService } from '../../../../test/common/workbenchTestServices.js'; +import { TestEnvironmentService, TestLifecycleService, TestRemoteAgentService, TestRemoteExtensionsScannerService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestContextService, TestFileService, TestUserDataProfileService } from '../../../../test/common/workbenchTestServices.js'; suite('BrowserExtensionService', () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 5d5665ea063..221ba3b2621 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -3,188 +3,189 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FileEditorInput } from '../../contrib/files/browser/editors/fileEditorInput.js'; -import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IDimension } from '../../../base/browser/dom.js'; +import { Direction, IViewSize } from '../../../base/browser/ui/grid/grid.js'; +import { mainWindow } from '../../../base/browser/window.js'; +import { DeferredPromise, timeout } from '../../../base/common/async.js'; +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { Codicon } from '../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { isValidBasename } from '../../../base/common/extpath.js'; +import { IMarkdownString } from '../../../base/common/htmlContent.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { Schemas } from '../../../base/common/network.js'; +import { posix, win32 } from '../../../base/common/path.js'; +import { IProcessEnvironment, isWindows, OperatingSystem } from '../../../base/common/platform.js'; +import { env } from '../../../base/common/process.js'; import { basename, isEqual } from '../../../base/common/resources.js'; +import { newWriteableStream, ReadableStreamEvents } from '../../../base/common/stream.js'; +import { ThemeIcon } from '../../../base/common/themables.js'; +import { assertReturnsDefined, upcast } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; -import { ITelemetryData, ITelemetryService, TelemetryLevel } from '../../../platform/telemetry/common/telemetry.js'; -import { NullTelemetryService } from '../../../platform/telemetry/common/telemetryUtils.js'; -import { EditorInput } from '../../common/editor/editorInput.js'; -import { EditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorsOrder, IFileEditorInput, IEditorFactoryRegistry, IEditorSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, EditorExtensions as Extensions, EditorInputCapabilities, IUntypedEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IActiveEditorChangeEvent, EditorPaneSelectionChangeReason, IEditorPaneSelection, IToolbarActions } from '../../common/editor.js'; -import { EditorServiceImpl, IEditorGroupView, IEditorGroupsView, IEditorGroupTitleHeight, DEFAULT_EDITOR_PART_OPTIONS } from '../../browser/parts/editor/editor.js'; -import { Event, Emitter } from '../../../base/common/event.js'; -import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js'; -import { IConfigurationService, ConfigurationTarget, IConfigurationValue } from '../../../platform/configuration/common/configuration.js'; -import { IWorkbenchLayoutService, PanelAlignment, Parts, Position as PartPosition } from '../../services/layout/browser/layoutService.js'; -import { TextModelResolverService } from '../../services/textmodelResolver/common/textModelResolverService.js'; -import { ITextModelService } from '../../../editor/common/services/resolverService.js'; -import { IEditorOptions, IResourceEditorInput, IResourceEditorInputIdentifier, ITextResourceEditorInput, ITextEditorOptions } from '../../../platform/editor/common/editor.js'; -import { IUntitledTextEditorModelManager, IUntitledTextEditorService, UntitledTextEditorService } from '../../services/untitled/common/untitledTextEditorService.js'; -import { IWorkspaceContextService, IWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js'; -import { ILifecycleService, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent, BeforeShutdownErrorEvent, InternalBeforeShutdownEvent, IWillShutdownEventJoiner } from '../../services/lifecycle/common/lifecycle.js'; -import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js'; -import { FileOperationEvent, IFileService, IFileStat, IFileStatResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, IFileDeleteOptions, IFileOverwriteOptions, IFileWriteOptions, IFileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent, FileOperationError, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions, IReadFileStreamOptions, IFileSystemProviderCapabilitiesChangeEvent, IFileStatWithPartialMetadata, IFileSystemWatcher, IWatchOptionsWithCorrelation, IFileSystemProviderActivationEvent } from '../../../platform/files/common/files.js'; -import { IModelService } from '../../../editor/common/services/model.js'; +import { ICodeEditor } from '../../../editor/browser/editorBrowser.js'; +import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js'; +import { Position as EditorPosition, IPosition } from '../../../editor/common/core/position.js'; +import { Range } from '../../../editor/common/core/range.js'; +import { Selection } from '../../../editor/common/core/selection.js'; +import { IDiffEditor, IEditor } from '../../../editor/common/editorCommon.js'; +import { ILanguageService } from '../../../editor/common/languages/language.js'; +import { ILanguageConfigurationService } from '../../../editor/common/languages/languageConfigurationRegistry.js'; +import { DefaultEndOfLine, EndOfLinePreference, ITextBufferFactory, ITextSnapshot } from '../../../editor/common/model.js'; +import { createTextBufferFactoryFromStream } from '../../../editor/common/model/textModel.js'; +import { IEditorWorkerService } from '../../../editor/common/services/editorWorker.js'; +import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../editor/common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../editor/common/services/languageFeatures.js'; +import { LanguageFeaturesService } from '../../../editor/common/services/languageFeaturesService.js'; import { LanguageService } from '../../../editor/common/services/languageService.js'; +import { IModelService } from '../../../editor/common/services/model.js'; import { ModelService } from '../../../editor/common/services/modelService.js'; -import { IResourceEncoding, ITextFileService, IReadTextFileOptions, ITextFileStreamContent, IWriteTextFileOptions, ITextFileEditorModel, ITextFileEditorModelManager } from '../../services/textfile/common/textfiles.js'; -import { ILanguageService } from '../../../editor/common/languages/language.js'; -import { IHistoryService } from '../../services/history/common/history.js'; -import { IInstantiationService, ServiceIdentifier } from '../../../platform/instantiation/common/instantiation.js'; -import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; -import { MenuBarVisibility, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, IRectangle } from '../../../platform/window/common/window.js'; -import { TestWorkspace } from '../../../platform/workspace/test/common/testWorkspace.js'; -import { IEnvironmentService } from '../../../platform/environment/common/environment.js'; -import { IThemeService } from '../../../platform/theme/common/themeService.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { TestThemeService } from '../../../platform/theme/test/common/testThemeService.js'; +import { ITextModelService } from '../../../editor/common/services/resolverService.js'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from '../../../editor/common/services/textResourceConfiguration.js'; -import { IPosition, Position as EditorPosition } from '../../../editor/common/core/position.js'; -import { IMenuService, MenuId, IMenu, IMenuChangeEvent, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from '../../../platform/actions/common/actions.js'; +import { ITreeSitterLibraryService } from '../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { TestCodeEditor } from '../../../editor/test/browser/testCodeEditor.js'; +import { TestLanguageConfigurationService } from '../../../editor/test/common/modes/testLanguageConfigurationService.js'; +import { TestEditorWorkerService } from '../../../editor/test/common/services/testEditorWorkerService.js'; +import { TestTreeSitterLibraryService } from '../../../editor/test/common/services/testTreeSitterLibraryService.js'; +import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js'; +import { TestAccessibilityService } from '../../../platform/accessibility/test/common/testAccessibilityService.js'; +import { IAccessibilitySignalService } from '../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { IActionViewItemService, NullActionViewItemService } from '../../../platform/actions/browser/actionViewItemService.js'; +import { IMenu, IMenuActionOptions, IMenuChangeEvent, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from '../../../platform/actions/common/actions.js'; +import { IFolderBackupInfo, IWorkspaceBackupInfo } from '../../../platform/backup/common/backup.js'; +import { ConfigurationTarget, IConfigurationService, IConfigurationValue } from '../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; import { ContextKeyValue, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; +import { ContextMenuService } from '../../../platform/contextview/browser/contextMenuService.js'; +import { IContextMenuService, IContextViewService } from '../../../platform/contextview/browser/contextView.js'; +import { ContextViewService } from '../../../platform/contextview/browser/contextViewService.js'; +import { IDiagnosticInfo, IDiagnosticInfoOptions } from '../../../platform/diagnostics/common/diagnostics.js'; +import { ConfirmResult, IDialogService, IFileDialogService, IOpenDialogOptions, IPickAndOpenOptions, ISaveDialogOptions } from '../../../platform/dialogs/common/dialogs.js'; +import { TestDialogService } from '../../../platform/dialogs/test/common/testDialogService.js'; +import { IEditorOptions, IResourceEditorInput, IResourceEditorInputIdentifier, ITextEditorOptions, ITextResourceEditorInput } from '../../../platform/editor/common/editor.js'; +import { IEnvironmentService } from '../../../platform/environment/common/environment.js'; +import { IExtensionManagementParticipant, IExtensionsControlManifest, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionInfo, InstallExtensionResult, InstallExtensionSummary, InstallOptions, Metadata, UninstallExtensionInfo, UninstallOptions } from '../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from '../../../platform/extensions/common/extensions.js'; +import { FileOperationError, FileSystemProviderCapabilities, FileType, IFileChange, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, IFileService, IFileStatWithMetadata, IFileSystemProvider, IFileSystemProviderWithFileReadStreamCapability, IFileWriteOptions, IStat, IWatchOptions } from '../../../platform/files/common/files.js'; +import { FileService } from '../../../platform/files/common/fileService.js'; +import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMemoryFilesystemProvider.js'; +import { IHoverService } from '../../../platform/hover/browser/hover.js'; +import { NullHoverService } from '../../../platform/hover/test/browser/nullHoverService.js'; +import { SyncDescriptor } from '../../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService, ServiceIdentifier } from '../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js'; +import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; import { MockContextKeyService, MockKeybindingService } from '../../../platform/keybinding/test/common/mockKeybindingService.js'; -import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from '../../../editor/common/model.js'; -import { Range } from '../../../editor/common/core/range.js'; -import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from '../../../platform/dialogs/common/dialogs.js'; +import { ILabelService } from '../../../platform/label/common/label.js'; +import { ILayoutOffsetInfo } from '../../../platform/layout/browser/layoutService.js'; +import { IListService } from '../../../platform/list/browser/listService.js'; +import { ILoggerService, ILogService, NullLogService } from '../../../platform/log/common/log.js'; +import { IMarkerService } from '../../../platform/markers/common/markers.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; import { TestNotificationService } from '../../../platform/notification/test/common/testNotificationService.js'; -import { IExtensionService } from '../../services/extensions/common/extensions.js'; -import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; -import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from '../../services/decorations/common/decorations.js'; -import { IDisposable, toDisposable, Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IMergeGroupOptions, IEditorReplacement, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter, IEditorDropTargetDelegate, IEditorPart, IAuxiliaryEditorPart, IEditorGroupsContainer, IEditorWorkingSet, IEditorGroupContextKeyProvider, IEditorWorkingSetOptions } from '../../services/editor/common/editorGroupsService.js'; -import { IEditorService, ISaveEditorsOptions, IRevertAllEditorsOptions, PreferredGroup, IEditorsChangeEvent, ISaveEditorsResult } from '../../services/editor/common/editorService.js'; -import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js'; -import { IEditorPaneRegistry, EditorPaneDescriptor } from '../../browser/editor.js'; -import { IDimension } from '../../../base/browser/dom.js'; -import { ILoggerService, ILogService, NullLogService } from '../../../platform/log/common/log.js'; -import { ILabelService } from '../../../platform/label/common/label.js'; -import { DeferredPromise, timeout } from '../../../base/common/async.js'; -import { PaneComposite, PaneCompositeDescriptor } from '../../browser/panecomposite.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js'; -import { IProcessEnvironment, isLinux, isWindows, OperatingSystem } from '../../../base/common/platform.js'; -import { LabelService } from '../../services/label/common/labelService.js'; -import { Part } from '../../browser/part.js'; -import { bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../base/common/buffer.js'; -import { Schemas } from '../../../base/common/network.js'; -import { IProductService } from '../../../platform/product/common/productService.js'; import product from '../../../platform/product/common/product.js'; -import { IHostService } from '../../services/host/browser/host.js'; -import { IWorkingCopyService, WorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; -import { IWorkingCopy, IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from '../../services/workingCopy/common/workingCopy.js'; -import { IFilesConfigurationService, FilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; -import { IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js'; -import { BrowserWorkbenchEnvironmentService } from '../../services/environment/browser/environmentService.js'; -import { BrowserTextFileService } from '../../services/textfile/browser/browserTextFileService.js'; -import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; -import { createTextBufferFactoryFromStream } from '../../../editor/common/model/textModel.js'; -import { IPathService } from '../../services/path/common/pathService.js'; -import { Direction, IViewSize } from '../../../base/browser/ui/grid/grid.js'; -import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, Progress, IProgressDialogOptions, IProgressIndicator } from '../../../platform/progress/common/progress.js'; -import { IWorkingCopyFileService, WorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; -import { UndoRedoService } from '../../../platform/undoRedo/common/undoRedoService.js'; -import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js'; -import { TextFileEditorModel } from '../../services/textfile/common/textFileEditorModel.js'; -import { Registry } from '../../../platform/registry/common/platform.js'; -import { EditorPane } from '../../browser/parts/editor/editorPane.js'; -import { CancellationToken } from '../../../base/common/cancellation.js'; -import { SyncDescriptor } from '../../../platform/instantiation/common/descriptors.js'; -import { TestDialogService } from '../../../platform/dialogs/test/common/testDialogService.js'; -import { CodeEditorService } from '../../services/editor/browser/codeEditorService.js'; -import { MainEditorPart } from '../../browser/parts/editor/editorPart.js'; -import { ICodeEditor } from '../../../editor/browser/editorBrowser.js'; -import { IDiffEditor, IEditor } from '../../../editor/common/editorCommon.js'; +import { IProductService } from '../../../platform/product/common/productService.js'; +import { IProgress, IProgressCompositeOptions, IProgressDialogOptions, IProgressIndicator, IProgressNotificationOptions, IProgressOptions, IProgressService, IProgressStep, IProgressWindowOptions, Progress } from '../../../platform/progress/common/progress.js'; import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, IQuickTree, IQuickTreeItem, IQuickWidget, QuickPickInput } from '../../../platform/quickinput/common/quickInput.js'; -import { QuickInputService } from '../../services/quickinput/browser/quickInputService.js'; -import { IListService } from '../../../platform/list/browser/listService.js'; -import { win32, posix } from '../../../base/common/path.js'; -import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService, TestHistoryService } from '../common/workbenchTestServices.js'; -import { IView, IViewDescriptor, ViewContainer, ViewContainerLocation } from '../../common/views.js'; -import { IViewsService } from '../../services/views/common/viewsService.js'; -import { IPaneComposite } from '../../common/panecomposite.js'; +import { Registry } from '../../../platform/registry/common/platform.js'; +import { IRemoteAgentEnvironment } from '../../../platform/remote/common/remoteAgentEnvironment.js'; +import { IRemoteExtensionsScannerService } from '../../../platform/remote/common/remoteExtensionsScanner.js'; +import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from '../../../platform/remote/common/remoteSocketFactoryService.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js'; +import { ITelemetryData, ITelemetryService, TelemetryLevel } from '../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryService } from '../../../platform/telemetry/common/telemetryUtils.js'; +import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalBackend, ITerminalLogService, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from '../../../platform/terminal/common/terminal.js'; +import { TerminalLogService } from '../../../platform/terminal/common/terminalLogService.js'; +import { ColorScheme } from '../../../platform/theme/common/theme.js'; +import { IThemeService } from '../../../platform/theme/common/themeService.js'; +import { TestThemeService } from '../../../platform/theme/test/common/testThemeService.js'; +import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js'; +import { UndoRedoService } from '../../../platform/undoRedo/common/undoRedoService.js'; import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { UriIdentityService } from '../../../platform/uriIdentity/common/uriIdentityService.js'; -import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMemoryFilesystemProvider.js'; -import { newWriteableStream, ReadableStreamEvents } from '../../../base/common/stream.js'; -import { EncodingOracle, IEncodingOverride } from '../../services/textfile/browser/textFileService.js'; -import { UTF16le, UTF16be, UTF8_with_bom } from '../../services/textfile/common/encoding.js'; -import { ColorScheme } from '../../../platform/theme/common/theme.js'; -import { Iterable } from '../../../base/common/iterator.js'; -import { InMemoryWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackupService.js'; -import { BrowserWorkingCopyBackupService } from '../../services/workingCopy/browser/workingCopyBackupService.js'; -import { FileService } from '../../../platform/files/common/fileService.js'; +import { IUserDataProfile, IUserDataProfilesService, UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; +import { IOpenEmptyWindowOptions, IOpenWindowOptions, IRectangle, IWindowOpenable, MenuBarVisibility } from '../../../platform/window/common/window.js'; +import { IWorkspaceContextService, IWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js'; +import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../platform/workspace/common/workspaceTrust.js'; +import { TestWorkspace } from '../../../platform/workspace/test/common/testWorkspace.js'; +import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspacesService } from '../../../platform/workspaces/common/workspaces.js'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../browser/editor.js'; +import { PaneComposite, PaneCompositeDescriptor } from '../../browser/panecomposite.js'; +import { Part } from '../../browser/part.js'; +import { DEFAULT_EDITOR_PART_OPTIONS, EditorServiceImpl, IEditorGroupsView, IEditorGroupTitleHeight, IEditorGroupView } from '../../browser/parts/editor/editor.js'; +import { EditorPane } from '../../browser/parts/editor/editorPane.js'; +import { MainEditorPart } from '../../browser/parts/editor/editorPart.js'; +import { EditorParts } from '../../browser/parts/editor/editorParts.js'; +import { SideBySideEditor } from '../../browser/parts/editor/sideBySideEditor.js'; +import { TextEditorPaneSelection } from '../../browser/parts/editor/textEditor.js'; import { TextResourceEditor } from '../../browser/parts/editor/textResourceEditor.js'; -import { TestCodeEditor } from '../../../editor/test/browser/testCodeEditor.js'; -import { TextFileEditor } from '../../contrib/files/browser/editors/textFileEditor.js'; +import { IPaneCompositePart } from '../../browser/parts/paneCompositePart.js'; +import { EditorExtensions, EditorInputCapabilities, EditorInputWithOptions, EditorPaneSelectionChangeReason, EditorsOrder, EditorExtensions as Extensions, GroupIdentifier, IActiveEditorChangeEvent, IEditorCloseEvent, IEditorFactoryRegistry, IEditorIdentifier, IEditorOpenContext, IEditorPane, IEditorPaneSelection, IEditorPartOptions, IEditorSerializer, IEditorWillMoveEvent, IEditorWillOpenEvent, IFileEditorInput, IMoveResult, IResourceDiffEditorInput, IRevertOptions, ISaveOptions, ITextDiffEditorPane, IToolbarActions, IUntitledTextResourceEditorInput, IUntypedEditorInput, IVisibleEditorPane } from '../../common/editor.js'; +import { IGroupModelChangeEvent } from '../../common/editor/editorGroupModel.js'; +import { EditorInput } from '../../common/editor/editorInput.js'; +import { SideBySideEditorInput } from '../../common/editor/sideBySideEditorInput.js'; import { TextResourceEditorInput } from '../../common/editor/textResourceEditorInput.js'; -import { UntitledTextEditorInput } from '../../services/untitled/common/untitledTextEditorInput.js'; -import { SideBySideEditor } from '../../browser/parts/editor/sideBySideEditor.js'; -import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspacesService } from '../../../platform/workspaces/common/workspaces.js'; -import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../platform/workspace/common/workspaceTrust.js'; -import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalBackend, ITerminalLogService, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from '../../../platform/terminal/common/terminal.js'; +import { IPaneComposite } from '../../common/panecomposite.js'; +import { IView, IViewDescriptor, ViewContainer, ViewContainerLocation } from '../../common/views.js'; +import { FileEditorInput } from '../../contrib/files/browser/editors/fileEditorInput.js'; +import { TextFileEditor } from '../../contrib/files/browser/editors/textFileEditor.js'; +import { FILE_EDITOR_INPUT_ID } from '../../contrib/files/common/files.js'; import { ICreateTerminalOptions, IDeserializedTerminalEditorInput, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from '../../contrib/terminal/browser/terminal.js'; -import { assertReturnsDefined, upcast } from '../../../base/common/types.js'; +import { TerminalConfigurationService } from '../../contrib/terminal/browser/terminalConfigurationService.js'; +import { TerminalEditorInput } from '../../contrib/terminal/browser/terminalEditorInput.js'; +import { IEnvironmentVariableService } from '../../contrib/terminal/common/environmentVariable.js'; +import { EnvironmentVariableService } from '../../contrib/terminal/common/environmentVariableService.js'; import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService, type ITerminalConfiguration } from '../../contrib/terminal/common/terminal.js'; +import { IDecoration, IDecorationData, IDecorationsProvider, IDecorationsService, IResourceDecorationChangeEvent } from '../../services/decorations/common/decorations.js'; +import { CodeEditorService } from '../../services/editor/browser/codeEditorService.js'; +import { EditorPaneService } from '../../services/editor/browser/editorPaneService.js'; import { EditorResolverService } from '../../services/editor/browser/editorResolverService.js'; -import { FILE_EDITOR_INPUT_ID } from '../../contrib/files/common/files.js'; +import { CustomEditorLabelService, ICustomEditorLabelService } from '../../services/editor/common/customEditorLabelService.js'; +import { EditorGroupLayout, GroupDirection, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, ICloseAllEditorsOptions, ICloseEditorOptions, ICloseEditorsFilter, IEditorDropTargetDelegate, IEditorGroup, IEditorGroupContextKeyProvider, IEditorGroupsContainer, IEditorGroupsService, IEditorPart, IEditorReplacement, IEditorWorkingSet, IEditorWorkingSetOptions, IFindGroupScope, IMergeGroupOptions } from '../../services/editor/common/editorGroupsService.js'; +import { IEditorPaneService } from '../../services/editor/common/editorPaneService.js'; import { IEditorResolverService } from '../../services/editor/common/editorResolverService.js'; -import { IWorkingCopyEditorService, WorkingCopyEditorService } from '../../services/workingCopy/common/workingCopyEditorService.js'; -import { IElevatedFileService } from '../../services/files/common/elevatedFileService.js'; +import { IEditorsChangeEvent, IEditorService, IRevertAllEditorsOptions, ISaveEditorsOptions, ISaveEditorsResult, PreferredGroup } from '../../services/editor/common/editorService.js'; +import { BrowserWorkbenchEnvironmentService } from '../../services/environment/browser/environmentService.js'; +import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; +import { EnablementState, IExtensionManagementServer, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; +import { IExtensionService } from '../../services/extensions/common/extensions.js'; import { BrowserElevatedFileService } from '../../services/files/browser/elevatedFileService.js'; -import { IEditorWorkerService } from '../../../editor/common/services/editorWorker.js'; -import { ResourceMap } from '../../../base/common/map.js'; -import { SideBySideEditorInput } from '../../common/editor/sideBySideEditorInput.js'; -import { ITextEditorService, TextEditorService } from '../../services/textfile/common/textEditorService.js'; +import { IElevatedFileService } from '../../services/files/common/elevatedFileService.js'; +import { FilesConfigurationService, IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; +import { IHistoryService } from '../../services/history/common/history.js'; +import { IHostService } from '../../services/host/browser/host.js'; +import { LabelService } from '../../services/label/common/labelService.js'; +import { ILanguageDetectionService } from '../../services/languageDetection/common/languageDetectionWorkerService.js'; +import { IWorkbenchLayoutService, PanelAlignment, Position as PartPosition, Parts } from '../../services/layout/browser/layoutService.js'; +import { BeforeShutdownErrorEvent, ILifecycleService, InternalBeforeShutdownEvent, IWillShutdownEventJoiner, LifecyclePhase, ShutdownReason, StartupKind, WillShutdownEvent } from '../../services/lifecycle/common/lifecycle.js'; import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js'; -import { IPaneCompositePart } from '../../browser/parts/paneCompositePart.js'; -import { ILanguageConfigurationService } from '../../../editor/common/languages/languageConfigurationRegistry.js'; -import { TestLanguageConfigurationService } from '../../../editor/test/common/modes/testLanguageConfigurationService.js'; -import { TerminalEditorInput } from '../../contrib/terminal/browser/terminalEditorInput.js'; -import { IGroupModelChangeEvent } from '../../common/editor/editorGroupModel.js'; -import { env } from '../../../base/common/process.js'; -import { isValidBasename } from '../../../base/common/extpath.js'; -import { TestAccessibilityService } from '../../../platform/accessibility/test/common/testAccessibilityService.js'; -import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../editor/common/services/languageFeatureDebounce.js'; -import { ILanguageFeaturesService } from '../../../editor/common/services/languageFeatures.js'; -import { LanguageFeaturesService } from '../../../editor/common/services/languageFeaturesService.js'; -import { TextEditorPaneSelection } from '../../browser/parts/editor/textEditor.js'; -import { Selection } from '../../../editor/common/core/selection.js'; -import { IFolderBackupInfo, IWorkspaceBackupInfo } from '../../../platform/backup/common/backup.js'; -import { TestEditorWorkerService } from '../../../editor/test/common/services/testEditorWorkerService.js'; +import { IPathService } from '../../services/path/common/pathService.js'; +import { QuickInputService } from '../../services/quickinput/browser/quickInputService.js'; import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from '../../services/remote/common/remoteAgentService.js'; -import { ILanguageDetectionService } from '../../services/languageDetection/common/languageDetectionWorkerService.js'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from '../../../platform/diagnostics/common/diagnostics.js'; -import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from '../../../platform/extensions/common/extensions.js'; -import { IRemoteAgentEnvironment } from '../../../platform/remote/common/remoteAgentEnvironment.js'; -import { ILayoutOffsetInfo } from '../../../platform/layout/browser/layoutService.js'; -import { IUserDataProfile, IUserDataProfilesService, UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; -import { UserDataProfileService } from '../../services/userDataProfile/common/userDataProfileService.js'; +import { BrowserTextFileService } from '../../services/textfile/browser/browserTextFileService.js'; +import { EncodingOracle, IEncodingOverride } from '../../services/textfile/browser/textFileService.js'; +import { UTF16be, UTF16le, UTF8_with_bom } from '../../services/textfile/common/encoding.js'; +import { ITextEditorService, TextEditorService } from '../../services/textfile/common/textEditorService.js'; +import { TextFileEditorModel } from '../../services/textfile/common/textFileEditorModel.js'; +import { IReadTextFileOptions, ITextFileEditorModel, ITextFileEditorModelManager, ITextFileService, ITextFileStreamContent, IWriteTextFileOptions } from '../../services/textfile/common/textfiles.js'; +import { TextModelResolverService } from '../../services/textmodelResolver/common/textModelResolverService.js'; +import { UntitledTextEditorInput } from '../../services/untitled/common/untitledTextEditorInput.js'; +import { IUntitledTextEditorModelManager, IUntitledTextEditorService, UntitledTextEditorService } from '../../services/untitled/common/untitledTextEditorService.js'; import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js'; -import { EnablementState, IExtensionManagementServer, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; -import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo, UninstallExtensionInfo, InstallExtensionSummary } from '../../../platform/extensionManagement/common/extensionManagement.js'; -import { Codicon } from '../../../base/common/codicons.js'; -import { IRemoteExtensionsScannerService } from '../../../platform/remote/common/remoteExtensionsScanner.js'; -import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from '../../../platform/remote/common/remoteSocketFactoryService.js'; -import { EditorParts } from '../../browser/parts/editor/editorParts.js'; -import { mainWindow } from '../../../base/browser/window.js'; -import { IMarkerService } from '../../../platform/markers/common/markers.js'; -import { IAccessibilitySignalService } from '../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; -import { IEditorPaneService } from '../../services/editor/common/editorPaneService.js'; -import { EditorPaneService } from '../../services/editor/browser/editorPaneService.js'; -import { IContextMenuService, IContextViewService } from '../../../platform/contextview/browser/contextView.js'; -import { ContextViewService } from '../../../platform/contextview/browser/contextViewService.js'; -import { CustomEditorLabelService, ICustomEditorLabelService } from '../../services/editor/common/customEditorLabelService.js'; -import { TerminalConfigurationService } from '../../contrib/terminal/browser/terminalConfigurationService.js'; -import { TerminalLogService } from '../../../platform/terminal/common/terminalLogService.js'; -import { IEnvironmentVariableService } from '../../contrib/terminal/common/environmentVariable.js'; -import { EnvironmentVariableService } from '../../contrib/terminal/common/environmentVariableService.js'; -import { ContextMenuService } from '../../../platform/contextview/browser/contextMenuService.js'; -import { IHoverService } from '../../../platform/hover/browser/hover.js'; -import { NullHoverService } from '../../../platform/hover/test/browser/nullHoverService.js'; -import { IActionViewItemService, NullActionViewItemService } from '../../../platform/actions/browser/actionViewItemService.js'; -import { IMarkdownString } from '../../../base/common/htmlContent.js'; -import { ITreeSitterLibraryService } from '../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import { TestTreeSitterLibraryService } from '../../../editor/test/common/services/testTreeSitterLibraryService.js'; +import { UserDataProfileService } from '../../services/userDataProfile/common/userDataProfileService.js'; +import { IViewsService } from '../../services/views/common/viewsService.js'; +import { BrowserWorkingCopyBackupService } from '../../services/workingCopy/browser/workingCopyBackupService.js'; +import { IWorkingCopy, IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from '../../services/workingCopy/common/workingCopy.js'; +import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js'; +import { InMemoryWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackupService.js'; +import { IWorkingCopyEditorService, WorkingCopyEditorService } from '../../services/workingCopy/common/workingCopyEditorService.js'; +import { IWorkingCopyFileService, WorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; +import { IWorkingCopyService, WorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; +import { TestContextService, TestExtensionService, TestFileService, TestHistoryService, TestLoggerService, TestMarkerService, TestProductService, TestStorageService, TestTextResourcePropertiesService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from '../common/workbenchTestServices.js'; + +// Backcompat export +export { TestFileService }; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -1085,168 +1086,6 @@ export class TestEditorService extends Disposable implements EditorServiceImpl { revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } } -export class TestFileService implements IFileService { - - declare readonly _serviceBrand: undefined; - - private readonly _onDidFilesChange = new Emitter(); - get onDidFilesChange(): Event { return this._onDidFilesChange.event; } - fireFileChanges(event: FileChangesEvent): void { this._onDidFilesChange.fire(event); } - - private readonly _onDidRunOperation = new Emitter(); - get onDidRunOperation(): Event { return this._onDidRunOperation.event; } - fireAfterOperation(event: FileOperationEvent): void { this._onDidRunOperation.fire(event); } - - private readonly _onDidChangeFileSystemProviderCapabilities = new Emitter(); - get onDidChangeFileSystemProviderCapabilities(): Event { return this._onDidChangeFileSystemProviderCapabilities.event; } - fireFileSystemProviderCapabilitiesChangeEvent(event: IFileSystemProviderCapabilitiesChangeEvent): void { this._onDidChangeFileSystemProviderCapabilities.fire(event); } - - private _onWillActivateFileSystemProvider = new Emitter(); - readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; - readonly onDidWatchError = Event.None; - - private content = 'Hello Html'; - private lastReadFileUri!: URI; - - readonly = false; - - setContent(content: string): void { this.content = content; } - getContent(): string { return this.content; } - getLastReadFileUri(): URI { return this.lastReadFileUri; } - - resolve(resource: URI, _options: IResolveMetadataFileOptions): Promise; - resolve(resource: URI, _options?: IResolveFileOptions): Promise; - async resolve(resource: URI, _options?: IResolveFileOptions): Promise { - return createFileStat(resource, this.readonly); - } - - stat(resource: URI): Promise { - return this.resolve(resource, { resolveMetadata: true }); - } - - async realpath(resource: URI): Promise { - return resource; - } - - async resolveAll(toResolve: { resource: URI; options?: IResolveFileOptions }[]): Promise { - const stats = await Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))); - - return stats.map(stat => ({ stat, success: true })); - } - - readonly notExistsSet = new ResourceMap(); - - async exists(_resource: URI): Promise { return !this.notExistsSet.has(_resource); } - - readShouldThrowError: Error | undefined = undefined; - - async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { - if (this.readShouldThrowError) { - throw this.readShouldThrowError; - } - - this.lastReadFileUri = resource; - - return { - ...createFileStat(resource, this.readonly), - value: VSBuffer.fromString(this.content) - }; - } - - async readFileStream(resource: URI, options?: IReadFileStreamOptions | undefined): Promise { - if (this.readShouldThrowError) { - throw this.readShouldThrowError; - } - - this.lastReadFileUri = resource; - - return { - ...createFileStat(resource, this.readonly), - value: bufferToStream(VSBuffer.fromString(this.content)) - }; - } - - writeShouldThrowError: Error | undefined = undefined; - - async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { - await timeout(0); - - if (this.writeShouldThrowError) { - throw this.writeShouldThrowError; - } - - return createFileStat(resource, this.readonly); - } - - move(_source: URI, _target: URI, _overwrite?: boolean): Promise { return Promise.resolve(null!); } - copy(_source: URI, _target: URI, _overwrite?: boolean): Promise { return Promise.resolve(null!); } - async cloneFile(_source: URI, _target: URI): Promise { } - createFile(_resource: URI, _content?: VSBuffer | VSBufferReadable, _options?: ICreateFileOptions): Promise { return Promise.resolve(null!); } - createFolder(_resource: URI): Promise { return Promise.resolve(null!); } - - onDidChangeFileSystemProviderRegistrations = Event.None; - - private providers = new Map(); - - registerProvider(scheme: string, provider: IFileSystemProvider) { - this.providers.set(scheme, provider); - - return toDisposable(() => this.providers.delete(scheme)); - } - - getProvider(scheme: string) { - return this.providers.get(scheme); - } - - async activateProvider(_scheme: string): Promise { - this._onWillActivateFileSystemProvider.fire({ scheme: _scheme, join: () => { } }); - } - async canHandleResource(resource: URI): Promise { return this.hasProvider(resource); } - hasProvider(resource: URI): boolean { return resource.scheme === Schemas.file || this.providers.has(resource.scheme); } - listCapabilities() { - return [ - { scheme: Schemas.file, capabilities: FileSystemProviderCapabilities.FileOpenReadWriteClose }, - ...Iterable.map(this.providers, ([scheme, p]) => { return { scheme, capabilities: p.capabilities }; }) - ]; - } - hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { - if (capability === FileSystemProviderCapabilities.PathCaseSensitive && isLinux) { - return true; - } - - const provider = this.getProvider(resource.scheme); - - return !!(provider && (provider.capabilities & capability)); - } - - async del(_resource: URI, _options?: { useTrash?: boolean; recursive?: boolean }): Promise { } - - createWatcher(resource: URI, options: IWatchOptions): IFileSystemWatcher { - return { - onDidChange: Event.None, - dispose: () => { } - }; - } - - - readonly watches: URI[] = []; - watch(_resource: URI, options: IWatchOptionsWithCorrelation): IFileSystemWatcher; - watch(_resource: URI): IDisposable; - watch(_resource: URI): IDisposable { - this.watches.push(_resource); - - return toDisposable(() => this.watches.splice(this.watches.indexOf(_resource), 1)); - } - - getWriteEncoding(_resource: URI): IResourceEncoding { return { encoding: 'utf8', hasBOM: false }; } - dispose(): void { } - - async canCreateFile(source: URI, options?: ICreateFileOptions): Promise { return true; } - async canMove(source: URI, target: URI, overwrite?: boolean | undefined): Promise { return true; } - async canCopy(source: URI, target: URI, overwrite?: boolean | undefined): Promise { return true; } - async canDelete(resource: URI, options?: { useTrash?: boolean | undefined; recursive?: boolean | undefined } | undefined): Promise { return true; } -} - export class TestWorkingCopyBackupService extends InMemoryWorkingCopyBackupService { readonly resolved: Set = new Set(); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index abd6d12a095..3916a200028 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -3,36 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { timeout } from '../../../base/common/async.js'; +import { bufferToStream, readableToBuffer, VSBuffer, VSBufferReadable } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Iterable } from '../../../base/common/iterator.js'; +import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; +import { Schemas } from '../../../base/common/network.js'; import { join } from '../../../base/common/path.js'; +import { isLinux, isMacintosh } from '../../../base/common/platform.js'; import { basename, isEqual, isEqualOrParent } from '../../../base/common/resources.js'; import { URI } from '../../../base/common/uri.js'; -import { Event, Emitter } from '../../../base/common/event.js'; -import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { IWorkspaceContextService, IWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace, IWorkspaceFoldersWillChangeEvent, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js'; -import { TestWorkspace } from '../../../platform/workspace/test/common/testWorkspace.js'; import { ITextResourcePropertiesService } from '../../../editor/common/services/textResourceConfiguration.js'; -import { isLinux, isMacintosh } from '../../../base/common/platform.js'; -import { InMemoryStorageService, WillSaveStateReason } from '../../../platform/storage/common/storage.js'; -import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from '../../services/workingCopy/common/workingCopy.js'; -import { NullExtensionService } from '../../services/extensions/common/extensions.js'; -import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext } from '../../services/workingCopy/common/workingCopyFileService.js'; -import { IDisposable, Disposable } from '../../../base/common/lifecycle.js'; -import { IBaseFileStat, IFileStatWithMetadata } from '../../../platform/files/common/files.js'; -import { ISaveOptions, IRevertOptions, SaveReason, GroupIdentifier } from '../../common/editor.js'; -import { CancellationToken } from '../../../base/common/cancellation.js'; -import product from '../../../platform/product/common/product.js'; -import { IActivity, IActivityService } from '../../services/activity/common/activity.js'; -import { IStoredFileWorkingCopySaveEvent } from '../../services/workingCopy/common/storedFileWorkingCopy.js'; -import { AbstractLoggerService, ILogger, LogLevel, NullLogger } from '../../../platform/log/common/log.js'; +import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { IResourceEditorInput } from '../../../platform/editor/common/editor.js'; -import { EditorInput } from '../../common/editor/editorInput.js'; -import { IHistoryService } from '../../services/history/common/history.js'; -import { IAutoSaveConfiguration, IAutoSaveMode, IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; -import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo, WorkspaceTrustRequestOptions, WorkspaceTrustUriResponse } from '../../../platform/workspace/common/workspaceTrust.js'; +import { FileChangesEvent, FileOperationEvent, FileSystemProviderCapabilities, IBaseFileStat, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatResult, IFileStatWithMetadata, IFileStatWithPartialMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemWatcher, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IResolveMetadataFileOptions, IWatchOptions, IWatchOptionsWithCorrelation, IWriteFileOptions } from '../../../platform/files/common/files.js'; +import { AbstractLoggerService, ILogger, LogLevel, NullLogger } from '../../../platform/log/common/log.js'; import { IMarker, IMarkerData, IMarkerService, IResourceMarker, MarkerStatistics } from '../../../platform/markers/common/markers.js'; +import product from '../../../platform/product/common/product.js'; import { IProgress, IProgressStep } from '../../../platform/progress/common/progress.js'; -import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js'; +import { InMemoryStorageService, WillSaveStateReason } from '../../../platform/storage/common/storage.js'; import { toUserDataProfile } from '../../../platform/userDataProfile/common/userDataProfile.js'; +import { ISingleFolderWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, IWorkspaceIdentifier, WorkbenchState, Workspace } from '../../../platform/workspace/common/workspace.js'; +import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo, WorkspaceTrustRequestOptions, WorkspaceTrustUriResponse } from '../../../platform/workspace/common/workspaceTrust.js'; +import { TestWorkspace } from '../../../platform/workspace/test/common/testWorkspace.js'; +import { GroupIdentifier, IRevertOptions, ISaveOptions, SaveReason } from '../../common/editor.js'; +import { EditorInput } from '../../common/editor/editorInput.js'; +import { IActivity, IActivityService } from '../../services/activity/common/activity.js'; +import { NullExtensionService } from '../../services/extensions/common/extensions.js'; +import { IAutoSaveConfiguration, IAutoSaveMode, IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; +import { IHistoryService } from '../../services/history/common/history.js'; +import { IResourceEncoding } from '../../services/textfile/common/textfiles.js'; +import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js'; +import { IStoredFileWorkingCopySaveEvent } from '../../services/workingCopy/common/storedFileWorkingCopy.js'; +import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from '../../services/workingCopy/common/workingCopy.js'; +import { ICopyOperation, ICreateFileOperation, ICreateOperation, IDeleteOperation, IFileOperationUndoRedoInfo, IMoveOperation, IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, WorkingCopyFileEvent } from '../../services/workingCopy/common/workingCopyFileService.js'; export class TestLoggerService extends AbstractLoggerService { constructor(logsHome?: URI) { @@ -494,3 +500,249 @@ export class TestMarkerService implements IMarkerService { return { dispose: () => { /* TODO: Implement cleanup logic */ } }; } } + +export class TestFileService implements IFileService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidFilesChange = new Emitter(); + get onDidFilesChange(): Event { return this._onDidFilesChange.event; } + fireFileChanges(event: FileChangesEvent): void { this._onDidFilesChange.fire(event); } + + private readonly _onDidRunOperation = new Emitter(); + get onDidRunOperation(): Event { return this._onDidRunOperation.event; } + fireAfterOperation(event: FileOperationEvent): void { this._onDidRunOperation.fire(event); } + + private readonly _onDidChangeFileSystemProviderCapabilities = new Emitter(); + get onDidChangeFileSystemProviderCapabilities(): Event { return this._onDidChangeFileSystemProviderCapabilities.event; } + fireFileSystemProviderCapabilitiesChangeEvent(event: IFileSystemProviderCapabilitiesChangeEvent): void { this._onDidChangeFileSystemProviderCapabilities.fire(event); } + + private _onWillActivateFileSystemProvider = new Emitter(); + readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; + readonly onDidWatchError = Event.None; + + protected content = 'Hello Html'; + protected lastReadFileUri!: URI; + + readonly = false; + + // Tracking functionality for tests + readonly writeOperations: Array<{ resource: URI; content: string }> = []; + readonly readOperations: Array<{ resource: URI }> = []; + + setContent(content: string): void { this.content = content; } + getContent(): string { return this.content; } + getLastReadFileUri(): URI { return this.lastReadFileUri; } + + // Clear tracking data for tests + clearTracking(): void { + this.writeOperations.length = 0; + this.readOperations.length = 0; + } + + resolve(resource: URI, _options: IResolveMetadataFileOptions): Promise; + resolve(resource: URI, _options?: IResolveFileOptions): Promise; + async resolve(resource: URI, _options?: IResolveFileOptions): Promise { + return createFileStat(resource, this.readonly); + } + + stat(resource: URI): Promise { + return this.resolve(resource, { resolveMetadata: true }); + } + + async realpath(resource: URI): Promise { + return resource; + } + + async resolveAll(toResolve: { resource: URI; options?: IResolveFileOptions }[]): Promise { + const stats = await Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))); + + return stats.map(stat => ({ stat, success: true })); + } + + readonly notExistsSet = new ResourceMap(); + + async exists(_resource: URI): Promise { return !this.notExistsSet.has(_resource); } + + readShouldThrowError: Error | undefined = undefined; + + async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { + if (this.readShouldThrowError) { + throw this.readShouldThrowError; + } + + this.lastReadFileUri = resource; + this.readOperations.push({ resource }); + + return { + ...createFileStat(resource, this.readonly), + value: VSBuffer.fromString(this.content) + }; + } + + async readFileStream(resource: URI, options?: IReadFileStreamOptions | undefined): Promise { + if (this.readShouldThrowError) { + throw this.readShouldThrowError; + } + + this.lastReadFileUri = resource; + + return { + ...createFileStat(resource, this.readonly), + value: bufferToStream(VSBuffer.fromString(this.content)) + }; + } + + writeShouldThrowError: Error | undefined = undefined; + + async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { + await timeout(0); + + if (this.writeShouldThrowError) { + throw this.writeShouldThrowError; + } + + let content: VSBuffer | undefined; + if (bufferOrReadable instanceof VSBuffer) { + content = bufferOrReadable; + } else { + try { + content = readableToBuffer(bufferOrReadable); + } catch { + // Some preexisting tests are writing with invalid objects + } + } + + if (content) { + this.writeOperations.push({ resource, content: content.toString() }); + } + + return createFileStat(resource, this.readonly); + } + + move(_source: URI, _target: URI, _overwrite?: boolean): Promise { return Promise.resolve(null!); } + copy(_source: URI, _target: URI, _overwrite?: boolean): Promise { return Promise.resolve(null!); } + async cloneFile(_source: URI, _target: URI): Promise { } + createFile(_resource: URI, _content?: VSBuffer | VSBufferReadable, _options?: ICreateFileOptions): Promise { return Promise.resolve(null!); } + createFolder(_resource: URI): Promise { return Promise.resolve(null!); } + + onDidChangeFileSystemProviderRegistrations = Event.None; + + private providers = new Map(); + + registerProvider(scheme: string, provider: IFileSystemProvider) { + this.providers.set(scheme, provider); + + return toDisposable(() => this.providers.delete(scheme)); + } + + getProvider(scheme: string) { + return this.providers.get(scheme); + } + + async activateProvider(_scheme: string): Promise { + this._onWillActivateFileSystemProvider.fire({ scheme: _scheme, join: () => { } }); + } + async canHandleResource(resource: URI): Promise { return this.hasProvider(resource); } + hasProvider(resource: URI): boolean { return resource.scheme === Schemas.file || this.providers.has(resource.scheme); } + listCapabilities() { + return [ + { scheme: Schemas.file, capabilities: FileSystemProviderCapabilities.FileOpenReadWriteClose }, + ...Iterable.map(this.providers, ([scheme, p]) => { return { scheme, capabilities: p.capabilities }; }) + ]; + } + hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { + if (capability === FileSystemProviderCapabilities.PathCaseSensitive && isLinux) { + return true; + } + + const provider = this.getProvider(resource.scheme); + + return !!(provider && (provider.capabilities & capability)); + } + + async del(_resource: URI, _options?: { useTrash?: boolean; recursive?: boolean }): Promise { } + + createWatcher(resource: URI, options: IWatchOptions): IFileSystemWatcher { + return { + onDidChange: Event.None, + dispose: () => { } + }; + } + + + readonly watches: URI[] = []; + watch(_resource: URI, options: IWatchOptionsWithCorrelation): IFileSystemWatcher; + watch(_resource: URI): IDisposable; + watch(_resource: URI): IDisposable { + this.watches.push(_resource); + + return toDisposable(() => this.watches.splice(this.watches.indexOf(_resource), 1)); + } + + getWriteEncoding(_resource: URI): IResourceEncoding { return { encoding: 'utf8', hasBOM: false }; } + dispose(): void { } + + async canCreateFile(source: URI, options?: ICreateFileOptions): Promise { return true; } + async canMove(source: URI, target: URI, overwrite?: boolean | undefined): Promise { return true; } + async canCopy(source: URI, target: URI, overwrite?: boolean | undefined): Promise { return true; } + async canDelete(resource: URI, options?: { useTrash?: boolean | undefined; recursive?: boolean | undefined } | undefined): Promise { return true; } +} + +/** + * TestFileService with in-memory file storage. + * Use this when your test needs to write files and read them back. + */ +export class InMemoryTestFileService extends TestFileService { + + private files = new Map(); + + override clearTracking(): void { + super.clearTracking(); + this.files.clear(); + } + + override async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { + if (this.readShouldThrowError) { + throw this.readShouldThrowError; + } + + this.lastReadFileUri = resource; + this.readOperations.push({ resource }); + + // Check if we have content in our in-memory store + const content = this.files.get(resource.toString()); + if (content) { + return { + ...createFileStat(resource, this.readonly), + value: content + }; + } + + return { + ...createFileStat(resource, this.readonly), + value: VSBuffer.fromString(this.content) + }; + } + + override async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { + await timeout(0); + + if (this.writeShouldThrowError) { + throw this.writeShouldThrowError; + } + + let content: VSBuffer; + if (bufferOrReadable instanceof VSBuffer) { + content = bufferOrReadable; + } else { + content = readableToBuffer(bufferOrReadable); + } + + // Store in memory and track + this.files.set(resource.toString(), content); + this.writeOperations.push({ resource, content: content.toString() }); + + return createFileStat(resource, this.readonly); + } +} diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 2e67dfac6fb..97bce2f41bc 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -3,52 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from '../../../base/common/event.js'; -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestFileService, TestLifecycleService, TestTextFileService } from '../browser/workbenchTestServices.js'; -import { ISharedProcessService } from '../../../platform/ipc/electron-browser/services.js'; -import { INativeHostService, INativeHostOptions, IOSProperties, IOSStatistics } from '../../../platform/native/common/native.js'; +import { insert } from '../../../base/common/arrays.js'; import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { Event } from '../../../base/common/event.js'; import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { Schemas } from '../../../base/common/network.js'; import { URI } from '../../../base/common/uri.js'; -import { IFileDialogService, INativeOpenDialogOptions } from '../../../platform/dialogs/common/dialogs.js'; -import { IPartsSplash } from '../../../platform/theme/common/themeService.js'; -import { IOpenedMainWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IColorScheme, IRectangle, IPoint } from '../../../platform/window/common/window.js'; +import { IModelService } from '../../../editor/common/services/model.js'; +import { ModelService } from '../../../editor/common/services/modelService.js'; import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; +import { IFileDialogService, INativeOpenDialogOptions } from '../../../platform/dialogs/common/dialogs.js'; import { IEnvironmentService, INativeEnvironmentService } from '../../../platform/environment/common/environment.js'; -import { IFileService } from '../../../platform/files/common/files.js'; -import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; -import { IEditorService } from '../../services/editor/common/editorService.js'; -import { IPathService } from '../../services/path/common/pathService.js'; -import { ITextEditorService } from '../../services/textfile/common/textEditorService.js'; -import { ITextFileService } from '../../services/textfile/common/textfiles.js'; -import { AbstractNativeExtensionTipsService } from '../../../platform/extensionManagement/common/extensionTipsService.js'; import { IExtensionManagementService } from '../../../platform/extensionManagement/common/extensionManagement.js'; +import { AbstractNativeExtensionTipsService } from '../../../platform/extensionManagement/common/extensionTipsService.js'; import { IExtensionRecommendationNotificationService } from '../../../platform/extensionRecommendations/common/extensionRecommendations.js'; +import { IFileService } from '../../../platform/files/common/files.js'; +import { FileService } from '../../../platform/files/common/fileService.js'; +import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMemoryFilesystemProvider.js'; +import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { ISharedProcessService } from '../../../platform/ipc/electron-browser/services.js'; +import { NullLogService } from '../../../platform/log/common/log.js'; +import { INativeHostOptions, INativeHostService, IOSProperties, IOSStatistics } from '../../../platform/native/common/native.js'; import { IProductService } from '../../../platform/product/common/productService.js'; +import { AuthInfo, Credentials } from '../../../platform/request/common/request.js'; import { IStorageService } from '../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js'; -import { IModelService } from '../../../editor/common/services/model.js'; -import { ModelService } from '../../../editor/common/services/modelService.js'; +import { IPartsSplash } from '../../../platform/theme/common/themeService.js'; +import { UriIdentityService } from '../../../platform/uriIdentity/common/uriIdentityService.js'; +import { FileUserDataProvider } from '../../../platform/userData/common/fileUserDataProvider.js'; +import { UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; +import { IColorScheme, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from '../../../platform/window/common/window.js'; import { IWorkspaceContextService } from '../../../platform/workspace/common/workspace.js'; +import { IEditorService } from '../../services/editor/common/editorService.js'; import { IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; import { ILifecycleService } from '../../services/lifecycle/common/lifecycle.js'; -import { IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js'; -import { IWorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; -import { TestContextService } from '../common/workbenchTestServices.js'; +import { IPathService } from '../../services/path/common/pathService.js'; +import { ITextEditorService } from '../../services/textfile/common/textEditorService.js'; +import { ITextFileService } from '../../services/textfile/common/textfiles.js'; import { NativeTextFileService } from '../../services/textfile/electron-browser/nativeTextFileService.js'; -import { insert } from '../../../base/common/arrays.js'; -import { Schemas } from '../../../base/common/network.js'; -import { FileService } from '../../../platform/files/common/fileService.js'; -import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMemoryFilesystemProvider.js'; -import { NullLogService } from '../../../platform/log/common/log.js'; -import { FileUserDataProvider } from '../../../platform/userData/common/fileUserDataProvider.js'; import { IWorkingCopyIdentifier } from '../../services/workingCopy/common/workingCopy.js'; +import { IWorkingCopyBackupService } from '../../services/workingCopy/common/workingCopyBackup.js'; +import { IWorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; import { NativeWorkingCopyBackupService } from '../../services/workingCopy/electron-browser/workingCopyBackupService.js'; -import { CancellationToken } from '../../../base/common/cancellation.js'; -import { UriIdentityService } from '../../../platform/uriIdentity/common/uriIdentityService.js'; -import { UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; -import { AuthInfo, Credentials } from '../../../platform/request/common/request.js'; +import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestLifecycleService, TestTextFileService } from '../browser/workbenchTestServices.js'; +import { TestContextService, TestFileService } from '../common/workbenchTestServices.js'; export class TestSharedProcessService implements ISharedProcessService { From 6439bdd441671f4457676d1386e5ff35d12409f7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 8 Sep 2025 08:15:12 +0200 Subject: [PATCH 0069/4355] update --- .../promptSyntax/service/newPromptsParser.ts | 108 +++++++++++++++++- .../service/newPromptsParser.test.ts | 65 ++++++++--- 2 files changed, 156 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 3f21c87cb43..7a5396f47af 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -7,7 +7,7 @@ import { Iterable } from '../../../../../../base/common/iterator.js'; import { dirname, resolvePath } from '../../../../../../base/common/resources.js'; import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; -import { parse, YamlNode, YamlParseError } from '../../../../../../base/common/yaml.js'; +import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../../base/common/yaml.js'; import { Range } from '../../../../../../editor/common/core/range.js'; import { chatVariableLeader } from '../../chatParserTypes.js'; @@ -72,7 +72,8 @@ export class PromptHeader { for (const property of node.properties) { attributes.push({ key: property.key.value, - range: new Range(this.range.startLineNumber + property.key.start.line, property.key.start.character + 1, this.range.startLineNumber + property.value.end.line, property.value.end.character + 1) + range: this.asRange({ start: property.key.start, end: property.value.end }), + value: this.asValue(property.value) }); } } @@ -81,15 +82,112 @@ export class PromptHeader { return this._parsed; } + private asRange({ start, end }: { start: YamlPosition; end: YamlPosition }): Range { + return new Range(this.range.startLineNumber + start.line, start.character + 1, this.range.startLineNumber + end.line, end.character + 1); + } + + private asValue(node: YamlNode): IValue { + switch (node.type) { + case 'string': + return { type: 'string', value: node.value, range: this.asRange(node) }; + case 'number': + return { type: 'number', value: node.value, range: this.asRange(node) }; + case 'boolean': + return { type: 'boolean', value: node.value, range: this.asRange(node) }; + case 'null': + return { type: 'null', value: node.value, range: this.asRange(node) }; + case 'array': + return { type: 'array', items: node.items.map(item => this.asValue(item)), range: this.asRange(node) }; + case 'object': { + const properties = node.properties.map(property => ({ key: this.asValue(property.key) as IStringValue, value: this.asValue(property.value) })); + return { type: 'object', properties, range: this.asRange(node) }; + } + } + } + public get attributes(): IHeaderAttribute[] { return this.getParsedHeader().attributes; } + + private getStringAttribute(key: string): string | undefined { + const attribute = this.getParsedHeader().attributes.find(attr => attr.key === key); + if (attribute?.value.type === 'string') { + return attribute.value.value; + } + return undefined; + } + + public get description(): string | undefined { + return this.getStringAttribute('description'); + } + + public get mode(): string | undefined { + return this.getStringAttribute('mode'); + } + + public get model(): string | undefined { + return this.getStringAttribute('model'); + } + + public get applyTo(): string | undefined { + return this.getStringAttribute('applyTo'); + } + + public get tools(): Map | undefined { + const toolsAttribute = this.getParsedHeader().attributes.find(attr => attr.key === 'tools'); + if (!toolsAttribute) { + return undefined; + } + if (toolsAttribute.value.type === 'array') { + const tools = new Map; + for (const item of toolsAttribute.value.items) { + if (item.type === 'string') { + tools.set(item.value, true); + } + } + return tools; + } else if (toolsAttribute.value.type === 'object') { + const tools = new Map; + const collectLeafs = ({ key, value }: { key: IStringValue; value: IValue }) => { + if (value.type === 'boolean') { + tools.set(key.value, value.value); + } else if (value.type === 'object') { + value.properties.forEach(collectLeafs); + } + }; + toolsAttribute.value.properties.forEach(collectLeafs); + return tools; + } + return undefined; + } + } + interface IHeaderAttribute { readonly range: Range; readonly key: string; + readonly value: IValue; } +export interface IStringValue { readonly type: 'string'; readonly value: string; readonly range: Range } +export interface INumberValue { readonly type: 'number'; readonly value: number; readonly range: Range } +export interface INullValue { readonly type: 'null'; readonly value: null; readonly range: Range } +export interface IBooleanValue { readonly type: 'boolean'; readonly value: boolean; readonly range: Range } + +export interface IArrayValue { + readonly type: 'array'; + readonly items: readonly IValue[]; + readonly range: Range; +} + +export interface IObjectValue { + readonly type: 'object'; + readonly properties: { key: IStringValue; value: IValue }[]; + readonly range: Range; +} + +export type IValue = IStringValue | INumberValue | IBooleanValue | IArrayValue | IObjectValue | INullValue; + interface ParsedBody { readonly fileReferences: readonly IBodyFileReference[]; @@ -112,6 +210,7 @@ export class PromptBody { private getParsedBody(): ParsedBody { if (this._parsed === undefined) { + const markdownLinkRanges: Range[] = []; const fileReferences: IBodyFileReference[] = []; const variableReferences: IBodyVariableReference[] = []; for (let i = this.range.startLineNumber - 1; i < this.range.endLineNumber - 1; i++) { @@ -122,10 +221,15 @@ export class PromptBody { const linkStartOffset = match.index + match[0].length - match[2].length - 1; const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); fileReferences.push({ content: match[2], range }); + markdownLinkRanges.push(new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)); } const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]*)`, 'g'); const matches = line.matchAll(reg); for (const match of matches) { + const fullRange = new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1); + if (markdownLinkRanges.some(mdRange => Range.areIntersectingOrTouching(mdRange, fullRange))) { + continue; + } const varType = match[1]; if (varType) { if (varType === 'file:') { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 1281fcfeef9..97fc5649a94 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -13,36 +13,71 @@ import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPro suite('NewPromptsParser', () => { ensureNoDisposablesAreLeakedInTestSuite(); - - test('provides cached parser instance', async () => { - const uri = URI.parse('file:///test/prompt1.md'); + test('mode', async () => { + const uri = URI.parse('file:///test/chatmode.md'); const content = [ /* 01 */"---", /* 02 */`description: "Agent mode test"`, - /* 03 */"mode: agent", + /* 03 */"model: GPT 4.1", /* 04 */"tools: ['tool1', 'tool2']", /* 05 */"---", - /* 06 */"This is a builtin agent mode test.", + /* 06 */"This is a chat mode test.", /* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).", ].join('\n'); const result = new NewPromptsParser().parse(uri, content); assert.deepEqual(result.uri, uri); - assert.deepEqual(result.header?.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 5, endColumn: 1 }); - assert.deepEqual(result.header?.attributes, [ - { key: 'description', range: new Range(2, 1, 2, 31) }, - { key: 'mode', range: new Range(3, 1, 3, 12) }, - { key: 'tools', range: new Range(4, 1, 4, 26) }, + assert.ok(result.header); + assert.ok(result.body); + assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 5, endColumn: 1 }); + assert.deepEqual(result.header.attributes, [ + { key: 'description', range: new Range(2, 1, 2, 31), value: { type: 'string', value: 'Agent mode test', range: new Range(2, 14, 2, 31) } }, + { key: 'model', range: new Range(3, 1, 3, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(3, 8, 3, 15) } }, + { + key: 'tools', range: new Range(4, 1, 4, 26), value: { + type: 'array', + items: [{ type: 'string', value: 'tool1', range: new Range(4, 9, 4, 16) }, { type: 'string', value: 'tool2', range: new Range(4, 18, 4, 25) }], + range: new Range(4, 8, 4, 26) + } + }, ]); - - - assert.deepEqual(result.body?.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); - assert.deepEqual(result.body?.fileReferences, [ + assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 39, 7, 54), content: './reference1.md' }, { range: new Range(7, 80, 7, 95), content: './reference2.md' } ]); - assert.deepEqual(result.body?.variableReferences, [ + assert.deepEqual(result.body.variableReferences, [ { range: new Range(7, 12, 7, 17), content: 'tool1' } ]); + assert.deepEqual(result.header.description, 'Agent mode test'); + assert.deepEqual(result.header.model, 'GPT 4.1'); + assert.ok(result.header.tools); + assert.deepEqual([...result.header.tools.entries()], [['tool1', true], ['tool2', true]]); }); + test('instructions', async () => { + const uri = URI.parse('file:///test/prompt1.md'); + const content = [ + /* 01 */"---", + /* 02 */`description: "Code style instructions for TypeScript"`, + /* 03 */"applyTo: *.ts", + /* 04 */"---", + /* 05 */"Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)", + ].join('\n'); + const result = new NewPromptsParser().parse(uri, content); + assert.deepEqual(result.uri, uri); + assert.ok(result.header); + assert.ok(result.body); + assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 4, endColumn: 1 }); + assert.deepEqual(result.header.attributes, [ + { key: 'description', range: new Range(2, 1, 2, 54), value: { type: 'string', value: 'Code style instructions for TypeScript', range: new Range(2, 14, 2, 54) } }, + { key: 'applyTo', range: new Range(3, 1, 3, 14), value: { type: 'string', value: '*.ts', range: new Range(3, 10, 3, 14) } }, + ]); + assert.deepEqual(result.body.range, { startLineNumber: 5, startColumn: 1, endLineNumber: 6, endColumn: 1 }); + assert.deepEqual(result.body.fileReferences, [ + { range: new Range(5, 64, 5, 103), content: 'https://mycomp/guidelines#typescript.md' }, + ]); + assert.deepEqual(result.body.variableReferences, []); + assert.deepEqual(result.header.description, 'Code style instructions for TypeScript'); + assert.deepEqual(result.header.applyTo, '*.ts'); + }); }); From ccae0ddb5c67f8a456938ab36c1d7e65391435e9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 8 Sep 2025 11:36:24 +0200 Subject: [PATCH 0070/4355] triggerCommandOnProviderChange: true to restore old behavior --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index b705da66ecd..6469fff0105 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4435,7 +4435,7 @@ class InlineEditorSuggest extends BaseEditorOption Date: Mon, 8 Sep 2025 19:29:15 +0900 Subject: [PATCH 0071/4355] fix: inverted colors for window.border system mode (#265635) * chore: update build * chore: bump distro --- .npmrc | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.npmrc b/.npmrc index a9027f192bf..c8d1ede6bc4 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" target="37.3.1" -ms_build_id="12259562" +ms_build_id="12342881" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/package.json b/package.json index 1814d94c119..18c3d4f5505 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "5a3bb643226c440998c203fc2ae156db00c96fb0", + "distro": "34ee7cb7a51777c9233b9b97a87ed494d4bdeb0a", "author": { "name": "Microsoft Corporation" }, From f9cf655972e5f14eebe99fc30d211aab96183512 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Sep 2025 13:32:56 +0200 Subject: [PATCH 0072/4355] update descriptions (#265652) --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 96a34887389..16acce4d9eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -322,7 +322,7 @@ configurationRegistry.registerConfiguration({ }, [mcpAccessConfig]: { type: 'string', - description: nls.localize('chat.mcp.access', "Controls access to Model Context Protocol servers."), + description: nls.localize('chat.mcp.access', "Controls access to installed Model Context Protocol servers."), enum: [ McpAccessValue.None, McpAccessValue.Registry, @@ -330,8 +330,8 @@ configurationRegistry.registerConfiguration({ ], enumDescriptions: [ nls.localize('chat.mcp.access.none', "No access to MCP servers."), - nls.localize('chat.mcp.access.registry', "Only allow access to MCP servers from the registry."), - nls.localize('chat.mcp.access.any', "Allow access to any MCP server.") + nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), + nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") ], default: McpAccessValue.All, policy: { From 00591427c0e084fd50c29ae59714109cf54ef26a Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Mon, 8 Sep 2025 15:17:45 +0100 Subject: [PATCH 0073/4355] Update action widget styles to align with menu design --- .../actionWidget/browser/actionWidget.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index dc9f249ddbb..cac3d2eba8f 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -11,10 +11,10 @@ z-index: 40; display: block; width: 100%; - border: 1px solid var(--vscode-editorWidget-border) !important; + border: 1px solid var(--vscode-menu-border) !important; border-radius: 5px; - background-color: var(--vscode-editorActionList-background); - color: var(--vscode-editorActionList-foreground); + background-color: var(--vscode-menu-background); + color: var(--vscode-menu-foreground); padding: 4px; box-shadow: 0 2px 8px var(--vscode-widget-shadow); } @@ -56,7 +56,7 @@ /** Styles for each row in the list element **/ .action-widget .monaco-list .monaco-list-row { - padding: 0 0 0 8px; + padding: 0 0 0 4px; white-space: nowrap; cursor: pointer; touch-action: none; @@ -65,8 +65,8 @@ } .action-widget .monaco-list .monaco-list-row.action.focused:not(.option-disabled) { - background-color: var(--vscode-editorActionList-focusBackground) !important; - color: var(--vscode-editorActionList-focusForeground); + background-color: var(--vscode-list-activeSelectionBackground) !important; + color: var(--vscode-list-activeSelectionForeground); outline: 1px solid var(--vscode-menu-selectionBorder, transparent); outline-offset: -1px; } @@ -118,7 +118,7 @@ .action-widget .monaco-list-row.action { display: flex; - gap: 8px; + gap: 4px; align-items: center; } @@ -166,7 +166,7 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 3px 8px 0; + padding: 3px 8px 0 24px; } .action-widget-action-bar .action-label { From 4237825dbc5730f52a158f3bfa38c4e4c9fa8b5f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 8 Sep 2025 16:21:14 +0200 Subject: [PATCH 0074/4355] fix #265624 (#265672) --- src/vs/workbench/contrib/logs/common/logs.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 05b5f7a0329..9326a7bd421 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -32,7 +32,8 @@ registerAction2(class extends Action2 { }); } run(servicesAccessor: ServicesAccessor): Promise { - return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(); + const action = servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value); + return action.run().finally(() => action.dispose()); } }); From 1d16b483c657083a13672f7a52be3244cf17a963 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Mon, 8 Sep 2025 15:37:27 +0100 Subject: [PATCH 0075/4355] Adjust action list item heights and update padding and font size for improved UI consistency --- src/vs/platform/actionWidget/browser/actionList.ts | 4 ++-- src/vs/platform/actionWidget/browser/actionWidget.css | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index c6729423c65..c4d5eec7486 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -217,8 +217,8 @@ export class ActionList extends Disposable { private readonly _list: List>; - private readonly _actionLineHeight = 24; - private readonly _headerLineHeight = 26; + private readonly _actionLineHeight = 28; + private readonly _headerLineHeight = 28; private readonly _separatorLineHeight = 8; private readonly _allMenuItems: readonly IActionListItem[]; diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index cac3d2eba8f..a370aff5541 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -56,12 +56,12 @@ /** Styles for each row in the list element **/ .action-widget .monaco-list .monaco-list-row { - padding: 0 0 0 4px; + padding: 0 4px 0 4px; white-space: nowrap; cursor: pointer; touch-action: none; width: 100%; - border-radius: 4px; + border-radius: 3px; } .action-widget .monaco-list .monaco-list-row.action.focused:not(.option-disabled) { @@ -74,7 +74,7 @@ .action-widget .monaco-list-row.group-header { color: var(--vscode-descriptionForeground) !important; font-weight: 600; - font-size: 12px; + font-size: 13px; } .action-widget .monaco-list-row.group-header:not(:first-of-type) { From c3500fa3e52464ef6d3780675ef1843850df63c6 Mon Sep 17 00:00:00 2001 From: Alessio Palladino <46252231+alpalla@users.noreply.github.com> Date: Mon, 8 Sep 2025 16:47:17 +0200 Subject: [PATCH 0076/4355] Maintain line breaks in transform to Camel and Pascal case actions (#263781) * Ignore whitespace in camel case transformation * Ignore newlines in pascal case transformation * Keep camel case transformation behavior unchanged for single lines * Keep double backslash --- .../linesOperations/browser/linesOperations.ts | 9 +++++---- .../test/browser/linesOperations.test.ts | 14 +++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 6c39de9ccdc..c5d6bdef41b 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1239,7 +1239,8 @@ export class SnakeCaseAction extends AbstractCaseAction { } export class CamelCaseAction extends AbstractCaseAction { - public static wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); + public static singleLineWordBoundary = new BackwardsCompatibleRegExp('[_\\s-]+', 'gm'); + public static multiLineWordBoundary = new BackwardsCompatibleRegExp('[_-]+', 'gm'); public static validWordStart = new BackwardsCompatibleRegExp('^(\\p{Lu}[^\\p{Lu}])', 'gmu'); constructor() { @@ -1251,7 +1252,7 @@ export class CamelCaseAction extends AbstractCaseAction { } protected _modifyText(text: string, wordSeparators: string): string { - const wordBoundary = CamelCaseAction.wordBoundary.get(); + const wordBoundary = /\r\n|\r|\n/.test(text) ? CamelCaseAction.multiLineWordBoundary.get() : CamelCaseAction.singleLineWordBoundary.get(); const validWordStart = CamelCaseAction.validWordStart.get(); if (!wordBoundary || !validWordStart) { // cannot support this @@ -1265,7 +1266,7 @@ export class CamelCaseAction extends AbstractCaseAction { } export class PascalCaseAction extends AbstractCaseAction { - public static wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); + public static wordBoundary = new BackwardsCompatibleRegExp('[_ \\t-]', 'gm'); public static wordBoundaryToMaintain = new BackwardsCompatibleRegExp('(?<=\\.)', 'gm'); constructor() { @@ -1359,7 +1360,7 @@ registerEditorAction(ReverseLinesAction); if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters.isSupported()) { registerEditorAction(SnakeCaseAction); } -if (CamelCaseAction.wordBoundary.isSupported()) { +if (CamelCaseAction.singleLineWordBoundary.isSupported() && CamelCaseAction.multiLineWordBoundary.isSupported()) { registerEditorAction(CamelCaseAction); } if (PascalCaseAction.wordBoundary.isSupported()) { diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index fb50262c216..e3d8a23c129 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -1045,7 +1045,10 @@ suite('Editor Contrib - Line Operations', () => { 'ReTain_some_CAPitalization', 'my_var.test_function()', 'öçş_öç_şğü_ğü', - 'XMLHttpRequest' + 'XMLHttpRequest', + '\tfunction hello_world() {', + '\t\treturn some_global_object;', + '\t}', ], {}, (editor) => { const model = editor.getModel()!; const camelcaseAction = new CamelCaseAction(); @@ -1081,6 +1084,10 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelection(new Selection(8, 1, 8, 14)); executeAction(camelcaseAction, editor); assert.strictEqual(model.getLineContent(8), 'XMLHttpRequest'); + + editor.setSelection(new Selection(9, 1, 11, 2)); + executeAction(camelcaseAction, editor); + assert.strictEqual(model.getValueInRange(new Selection(9, 1, 11, 3)), '\tfunction helloWorld() {\n\t\treturn someGlobalObject;\n\t}'); } ); @@ -1254,6 +1261,11 @@ suite('Editor Contrib - Line Operations', () => { executeAction(pascalCaseAction, editor); assert.strictEqual(model.getLineContent(10), 'KebabCase'); assertSelection(editor, new Selection(10, 1, 10, 10)); + + editor.setSelection(new Selection(9, 1, 10, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getValueInRange(new Selection(9, 1, 10, 11)), 'ParseHTML4String\nKebabCase'); + assertSelection(editor, new Selection(9, 1, 10, 10)); } ); }); From 460abfe4c981de184fd9019726758c3705ee04ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:54:20 +0000 Subject: [PATCH 0077/4355] Initial plan From f5f2ea58fc8580307f431c320021ec82185422c3 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 8 Sep 2025 16:28:54 +0200 Subject: [PATCH 0078/4355] Rerun fallbacks (#260297) --- .../src/common/logger.ts | 4 + extensions/github-authentication/src/flows.ts | 4 + .../github-authentication/src/githubServer.ts | 4 + .../github-authentication/src/node/fetch.ts | 138 ++++++++---------- .../src/test/node/fetch.test.ts | 6 + 5 files changed, 76 insertions(+), 80 deletions(-) diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts index cf90c4176a9..429ecdf596c 100644 --- a/extensions/github-authentication/src/common/logger.ts +++ b/extensions/github-authentication/src/common/logger.ts @@ -18,6 +18,10 @@ export class Log { this.output.trace(message); } + public debug(message: string): void { + this.output.debug(message); + } + public info(message: string): void { this.output.info(message); } diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index d9d286bcbbc..5a917fbabca 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -172,6 +172,7 @@ async function exchangeCodeForToken( } const result = await fetching(endpointUri.toString(true), { logger, + retryFallbacks: true, expectJSON: true, method: 'POST', headers: { @@ -406,6 +407,7 @@ class DeviceCodeFlow implements IFlow { }); const result = await fetching(uri.toString(true), { logger, + retryFallbacks: true, expectJSON: true, method: 'POST', headers: { @@ -484,6 +486,7 @@ class DeviceCodeFlow implements IFlow { try { accessTokenResult = await fetching(refreshTokenUri.toString(true), { logger, + retryFallbacks: true, expectJSON: true, method: 'POST', headers: { @@ -579,6 +582,7 @@ class PatFlow implements IFlow { logger.info('Getting token scopes...'); const result = await fetching(serverUri.toString(), { logger, + retryFallbacks: true, expectJSON: false, headers: { Authorization: `token ${token}`, diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index b71bf5ec6d7..5d70ef443a1 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -179,6 +179,7 @@ export class GitHubServer implements IGitHubServer { // Defined here: https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#delete-an-app-token const result = await fetching(uri.toString(true), { logger: this._logger, + retryFallbacks: true, expectJSON: false, method: 'DELETE', headers: { @@ -222,6 +223,7 @@ export class GitHubServer implements IGitHubServer { this._logger.info('Getting user info...'); result = await fetching(this.getServerUri('/user').toString(), { logger: this._logger, + retryFallbacks: true, expectJSON: true, headers: { Authorization: `token ${token}`, @@ -282,6 +284,7 @@ export class GitHubServer implements IGitHubServer { try { const result = await fetching('https://education.github.com/api/user', { logger: this._logger, + retryFallbacks: true, expectJSON: true, headers: { Authorization: `token ${session.accessToken}`, @@ -324,6 +327,7 @@ export class GitHubServer implements IGitHubServer { if (!isSupportedTarget(this._type, this._ghesUri)) { const result = await fetching(this.getServerUri('/meta').toString(), { logger: this._logger, + retryFallbacks: true, expectJSON: true, headers: { Authorization: `token ${token}`, diff --git a/extensions/github-authentication/src/node/fetch.ts b/extensions/github-authentication/src/node/fetch.ts index a324e547876..8a20d30bd74 100644 --- a/extensions/github-authentication/src/node/fetch.ts +++ b/extensions/github-authentication/src/node/fetch.ts @@ -7,9 +7,11 @@ import * as http from 'http'; import * as https from 'https'; import { workspace } from 'vscode'; import { Log } from '../common/logger'; +import { Readable } from 'stream'; export interface FetchOptions { logger: Log; + retryFallbacks: boolean; expectJSON: boolean; method?: 'GET' | 'POST' | 'DELETE'; headers?: Record; @@ -64,97 +66,71 @@ _fetchers.push({ }); export function createFetch(): Fetch { - let _fetcher: Fetcher | undefined; + let fetchers: readonly Fetcher[] = _fetchers; return async (url, options) => { - if (!_fetcher) { - let firstResponse: FetchResponse | undefined; - let firstError: any; - for (const fetcher of _fetchers) { - try { - const res = await fetcher.fetch(url, options); - if (fetcher === _fetchers[0]) { - firstResponse = res; - } - if (!res.ok) { - options.logger.info(`fetching: ${fetcher.name} failed with status: ${res.status} ${res.statusText}`); - continue; - } - if (!options.expectJSON) { - options.logger.info(`fetching: ${fetcher.name} succeeded (not JSON)`); - _fetcher = fetcher; - return res; - } - const text = await res.text(); - if (fetcher === _fetchers[0]) { - // Update to unconsumed response - firstResponse = new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => JSON.parse(text), - ); - } - const json = JSON.parse(text); // Verify JSON - options.logger.info(`fetching: ${fetcher.name} succeeded (JSON)`); - if (fetcher !== _fetchers[0]) { - const retry = await retryFetch(_fetchers[0], url, options); - if ('res' in retry && retry.res.ok) { - _fetcher = _fetchers[0]; - return retry.res; - } - } - _fetcher = fetcher; - return new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => json, - ); - } catch (err) { - if (fetcher === _fetchers[0]) { - firstError = err; - } - options.logger.info(`fetching: ${fetcher.name} failed with error: ${err.message}`); - } + const result = await fetchWithFallbacks(fetchers, url, options, options.logger); + if (result.updatedFetchers) { + fetchers = result.updatedFetchers; + } + return result.response; + }; +} + +async function fetchWithFallbacks(availableFetchers: readonly Fetcher[], url: string, options: FetchOptions, logService: Log): Promise<{ response: FetchResponse; updatedFetchers?: Fetcher[] }> { + if (options.retryFallbacks && availableFetchers.length > 1) { + let firstResult: { ok: boolean; response: FetchResponse } | { ok: false; err: any } | undefined; + for (const fetcher of availableFetchers) { + const result = await tryFetch(fetcher, url, options, logService); + if (fetcher === availableFetchers[0]) { + firstResult = result; } - _fetcher = _fetchers[0]; // Do this only once - if (firstResponse) { - return firstResponse; + if (!result.ok) { + continue; } - throw firstError; + if (fetcher !== availableFetchers[0]) { + const retry = await tryFetch(availableFetchers[0], url, options, logService); + if (retry.ok) { + return { response: retry.response }; + } + logService.info(`FetcherService: using ${fetcher.name} from now on`); + const updatedFetchers = availableFetchers.slice(); + updatedFetchers.splice(updatedFetchers.indexOf(fetcher), 1); + updatedFetchers.unshift(fetcher); + return { response: result.response, updatedFetchers }; + } + return { response: result.response }; } - return _fetcher.fetch(url, options); - }; + if ('response' in firstResult!) { + return { response: firstResult.response }; + } + throw firstResult!.err; + } + return { response: await availableFetchers[0].fetch(url, options) }; } -async function retryFetch(fetcher: Fetcher, url: string, options: FetchOptions): Promise<{ res: FetchResponse } | { err: any }> { +async function tryFetch(fetcher: Fetcher, url: string, options: FetchOptions, logService: Log): Promise<{ ok: boolean; response: FetchResponse } | { ok: false; err: any }> { try { - const res = await fetcher.fetch(url, options); - if (!res.ok) { - options.logger.info(`fetching: ${fetcher.name} failed with status: ${res.status} ${res.statusText}`); - return { res }; + const response = await fetcher.fetch(url, options); + if (!response.ok) { + logService.info(`FetcherService: ${fetcher.name} failed with status: ${response.status} ${response.statusText}`); + return { ok: false, response }; } if (!options.expectJSON) { - options.logger.info(`fetching: ${fetcher.name} succeeded (not JSON)`); - return { res }; + logService.debug(`FetcherService: ${fetcher.name} succeeded (not JSON)`); + return { ok: response.ok, response }; + } + const text = await response.text(); + try { + const json = JSON.parse(text); // Verify JSON + logService.debug(`FetcherService: ${fetcher.name} succeeded (JSON)`); + return { ok: true, response: new FetchResponseImpl(response.status, response.statusText, response.headers, async () => text, async () => json, async () => Readable.from([text])) }; + } catch (err) { + logService.info(`FetcherService: ${fetcher.name} failed to parse JSON: ${err.message}`); + return { ok: false, err, response: new FetchResponseImpl(response.status, response.statusText, response.headers, async () => text, async () => { throw err; }, async () => Readable.from([text])) }; } - const text = await res.text(); - const json = JSON.parse(text); // Verify JSON - options.logger.info(`fetching: ${fetcher.name} succeeded (JSON)`); - return { - res: new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => json, - ) - }; } catch (err) { - options.logger.info(`fetching: ${fetcher.name} failed with error: ${err.message}`); - return { err }; + logService.info(`FetcherService: ${fetcher.name} failed with error: ${err.message}`); + return { ok: false, err }; } } @@ -168,6 +144,7 @@ class FetchResponseImpl implements FetchResponse { public readonly headers: FetchHeaders, public readonly text: () => Promise, public readonly json: () => Promise, + public readonly body: () => Promise, ) { this.ok = this.status >= 200 && this.status < 300; } @@ -192,6 +169,7 @@ async function nodeHTTP(url: string, options: FetchOptions): Promise nodeFetcherResponse.text(), async () => nodeFetcherResponse.json(), + async () => nodeFetcherResponse.body(), )); }); req.setTimeout(60 * 1000); // time out after 60s of receiving no data diff --git a/extensions/github-authentication/src/test/node/fetch.test.ts b/extensions/github-authentication/src/test/node/fetch.test.ts index 1be678d40de..6ce569378b0 100644 --- a/extensions/github-authentication/src/test/node/fetch.test.ts +++ b/extensions/github-authentication/src/test/node/fetch.test.ts @@ -76,6 +76,7 @@ suite('fetching', () => { test('should use Electron fetch', async () => { const res = await createFetch()(`http://localhost:${port}/json`, { logger, + retryFallbacks: true, expectJSON: true, }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; @@ -87,6 +88,7 @@ suite('fetching', () => { test('should use Electron fetch 2', async () => { const res = await createFetch()(`http://localhost:${port}/text`, { logger, + retryFallbacks: true, expectJSON: false, }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; @@ -98,6 +100,7 @@ suite('fetching', () => { test('should fall back to Node.js fetch', async () => { const res = await createFetch()(`http://localhost:${port}/json?expectAgent=node`, { logger, + retryFallbacks: true, expectJSON: true, }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; @@ -109,6 +112,7 @@ suite('fetching', () => { test('should fall back to Node.js fetch 2', async () => { const res = await createFetch()(`http://localhost:${port}/json?expectAgent=node&error=html`, { logger, + retryFallbacks: true, expectJSON: true, }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; @@ -120,6 +124,7 @@ suite('fetching', () => { test('should fall back to Node.js http/s', async () => { const res = await createFetch()(`http://localhost:${port}/json?expectAgent=undefined`, { logger, + retryFallbacks: true, expectJSON: true, }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; @@ -131,6 +136,7 @@ suite('fetching', () => { test('should fail with first error', async () => { const res = await createFetch()(`http://localhost:${port}/text`, { logger, + retryFallbacks: true, expectJSON: true, // Expect JSON but server returns text }); const actualAgent = res.headers.get('x-client-user-agent') || 'None'; From 5221ef2d02bbf7f3c3e60559cbd85876b4f7da05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:56:31 +0000 Subject: [PATCH 0079/4355] Initial plan From 37bf6e7371c8bab58e8761a53e4217b6cc253dba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:05:26 +0000 Subject: [PATCH 0080/4355] Add terminal profile configuration for chat agent tools Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../browser/tools/runInTerminalTool.ts | 46 +++++++++- .../terminalChatAgentToolsConfiguration.ts | 91 +++++++++++++++++++ .../test/browser/runInTerminalTool.test.ts | 57 ++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) 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 abfbe4ef0f7..e219773405d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -21,7 +21,7 @@ import { IConfigurationService } from '../../../../../../platform/configuration/ import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; -import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; +import { ITerminalLogService, ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js'; import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress, type IToolConfirmationMessages, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; @@ -603,8 +603,16 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Terminal init private async _getCopilotShell(): Promise { + const os = await this._osBackend; + + // Check for chat agent terminal profile first + const chatAgentProfile = this._getChatAgentTerminalProfile(os); + if (chatAgentProfile) { + return chatAgentProfile.path; + } + const defaultShell = await this._terminalProfileResolverService.getDefaultShell({ - os: await this._osBackend, + os, remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority }); // Force pwsh over cmd as cmd doesn't have shell integration @@ -614,6 +622,40 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { return defaultShell; } + private _getChatAgentTerminalProfile(os: OperatingSystem): ITerminalProfile | undefined { + const osKey = this._getOsKey(os); + const profileSetting = `${TerminalChatAgentToolsSettingId.TerminalProfileLinux.replace('.linux', '')}.${osKey}`; + const profile = this._configurationService.getValue(profileSetting); + + if (this._isValidChatAgentTerminalProfile(profile)) { + return profile; + } + + return undefined; + } + + private _getOsKey(os: OperatingSystem): string { + switch (os) { + case OperatingSystem.Windows: + return 'windows'; + case OperatingSystem.Macintosh: + return 'osx'; + case OperatingSystem.Linux: + default: + return 'linux'; + } + } + + private _isValidChatAgentTerminalProfile(profile: unknown): profile is ITerminalProfile { + if (profile === null || profile === undefined || typeof profile !== 'object') { + return false; + } + if ('path' in profile && typeof (profile as { path: unknown }).path === 'string') { + return true; + } + return false; + } + private async _initBackgroundTerminal(chatSessionId: string, termId: string, token: CancellationToken): Promise { this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`); const shell = await this._getCopilotShell(); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 8f3f5ed0f5c..f738c468ca5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -15,6 +15,10 @@ export const enum TerminalChatAgentToolsSettingId { ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout', + TerminalProfileLinux = 'chat.tools.terminal.terminalProfile.linux', + TerminalProfileMacOs = 'chat.tools.terminal.terminalProfile.osx', + TerminalProfileWindows = 'chat.tools.terminal.terminalProfile.windows', + DeprecatedAutoApproveCompatible = 'chat.agent.terminal.autoApprove', DeprecatedAutoApprove1 = 'chat.agent.terminal.allowList', DeprecatedAutoApprove2 = 'chat.agent.terminal.denyList', @@ -41,6 +45,39 @@ const autoApproveBoolean: IJSONSchema = { description: localize('autoApprove.key', "The start of a command to match against. A regular expression can be provided by wrapping the string in `/` characters."), }; +const terminalChatAgentProfileSchema: IJSONSchema = { + type: 'object', + required: ['path'], + properties: { + path: { + description: localize('terminalChatAgentProfile.path', "A single path to a shell executable."), + type: 'string', + }, + args: { + description: localize('terminalChatAgentProfile.args', "An array of command-line arguments to pass to the shell."), + type: 'array', + items: { + type: 'string' + }, + }, + icon: { + description: localize('terminalChatAgentProfile.icon', "A codicon ID to associate with this terminal."), + type: 'string', + }, + color: { + description: localize('terminalChatAgentProfile.color', "A color theme color ID to associate with this terminal."), + type: 'string', + }, + env: { + description: localize('terminalChatAgentProfile.env', "Object with environment variables that will be added to the shell."), + type: 'object', + additionalProperties: { + type: ['string', 'null'] + }, + }, + } +}; + export const terminalChatAgentToolsConfiguration: IStringDictionary = { [TerminalChatAgentToolsSettingId.AutoApprove]: { markdownDescription: [ @@ -296,6 +333,60 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { assertConfirmationRequired(result, 'Run `pwsh` command?'); }); }); + + suite('Terminal Profile Configuration', () => { + test('should use chat agent terminal profile when configured', async () => { + // Set a custom terminal profile for Windows + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, { + path: 'C:\\Windows\\System32\\cmd.exe', + args: ['/K', 'echo "Custom Terminal"'] + }); + + runInTerminalTool.setBackendOs(OperatingSystem.Windows); + + const result = await executeToolTest({ + command: 'echo hello', + explanation: 'test custom profile', + isBackground: false + }); + + // The test should have a mock that intercepts the profile resolution + // For now, we verify that the setting is properly accessed + ok(result, 'Expected tool to execute successfully with custom profile'); + }); + + test('should fallback to default when no chat agent profile configured', async () => { + // Ensure no terminal profile is set + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, null); + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileMacOs, null); + + runInTerminalTool.setBackendOs(OperatingSystem.Linux); + + const result = await executeToolTest({ + command: 'echo hello', + explanation: 'test default fallback', + isBackground: false + }); + + ok(result, 'Expected tool to execute successfully with default profile'); + }); + + test('should validate terminal profile configuration', async () => { + // Set invalid terminal profile (missing path) + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, { + args: ['-l'] + }); + + runInTerminalTool.setBackendOs(OperatingSystem.Linux); + + const result = await executeToolTest({ + command: 'echo hello', + explanation: 'test invalid profile', + isBackground: false + }); + + // Should fallback to default when profile is invalid + ok(result, 'Expected tool to execute successfully with fallback to default profile'); + }); + }); }); From 04568a8e6d5cf19b9666a68e7c719b9a10171a40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:07:36 +0000 Subject: [PATCH 0081/4355] Add safe git grep support with security patterns Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../terminalChatAgentToolsConfiguration.ts | 10 ++ .../browser/commandLineAutoApprover.test.ts | 116 ++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 8f3f5ed0f5c..da91cd6b6db 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -148,6 +148,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { strictEqual(getCommandLineIsDefaultRule('cmd2 arg'), true, 'Object type should match default using structural equality (even though it\'s a deny rule)'); }); }); + + suite('git grep security tests', () => { + test('should auto-approve safe git grep commands by default', () => { + // Using default configuration which includes 'git grep': true + setAutoApproveWithDefaults( + {}, + { 'git grep': true } + ); + + ok(isAutoApproved('git grep pattern')); + ok(isAutoApproved('git grep "search term"')); + ok(isAutoApproved('git grep -n pattern')); + ok(isAutoApproved('git grep -i "case insensitive"')); + ok(isAutoApproved('git grep --line-number pattern file.txt')); + }); + + test('should block git grep with -f flag (read patterns from file)', () => { + setAutoApproveWithDefaults( + {}, + { + 'git grep': true, + '/^git grep\\b.*-(f|P)\\b/': false + } + ); + + // Safe git grep commands should still work + ok(isAutoApproved('git grep pattern')); + ok(isAutoApproved('git grep -n pattern')); + + // Dangerous -f flag should be blocked + ok(!isAutoApproved('git grep -f patterns.txt')); + ok(!isAutoApproved('git grep --file patterns.txt')); + ok(!isAutoApproved('git grep -n -f /etc/passwd')); + }); + + test('should block git grep with -P flag (Perl regexp)', () => { + setAutoApproveWithDefaults( + {}, + { + 'git grep': true, + '/^git grep\\b.*-(f|P)\\b/': false + } + ); + + // Safe git grep commands should still work + ok(isAutoApproved('git grep pattern')); + ok(isAutoApproved('git grep -E "extended regexp"')); + + // Dangerous -P flag should be blocked + ok(!isAutoApproved('git grep -P "perl regexp"')); + ok(!isAutoApproved('git grep --perl-regexp "pattern"')); + ok(!isAutoApproved('git grep -n -P "complex.*pattern"')); + }); + + test('should block git grep with --open-files-in-pager flag', () => { + setAutoApproveWithDefaults( + {}, + { + 'git grep': true, + '/^git grep\\b.*--open-files-in-pager\\b/': false + } + ); + + // Safe git grep commands should still work + ok(isAutoApproved('git grep pattern')); + ok(isAutoApproved('git grep -l pattern')); + + // --open-files-in-pager flag should be blocked + ok(!isAutoApproved('git grep --open-files-in-pager pattern')); + ok(!isAutoApproved('git grep -O pattern')); + }); + + test('should block git grep with combined dangerous flags', () => { + setAutoApproveWithDefaults( + {}, + { + 'git grep': true, + '/^git grep\\b.*-(f|P)\\b/': false, + '/^git grep\\b.*--open-files-in-pager\\b/': false + } + ); + + // Safe git grep commands should still work + ok(isAutoApproved('git grep pattern')); + ok(isAutoApproved('git grep -n -i pattern')); + ok(isAutoApproved('git grep --line-number --ignore-case pattern')); + + // Various combinations of dangerous flags should be blocked + ok(!isAutoApproved('git grep -f patterns.txt -P')); + ok(!isAutoApproved('git grep -P --open-files-in-pager pattern')); + ok(!isAutoApproved('git grep -n -f /etc/passwd --color')); + }); + + test('should handle git grep with complex patterns safely', () => { + setAutoApproveWithDefaults( + {}, + { + 'git grep': true, + '/^git grep\\b.*-(f|P)\\b/': false, + '/^git grep\\b.*--open-files-in-pager\\b/': false + } + ); + + // Complex but safe git grep commands should work + ok(isAutoApproved('git grep -E "function.*\\(.*\\)"')); + ok(isAutoApproved('git grep --extended-regexp "class [A-Z][a-zA-Z]*"')); + ok(isAutoApproved('git grep -G "var [a-z]+" -- "*.js"')); + ok(isAutoApproved('git grep --basic-regexp "TODO:"')); + ok(isAutoApproved('git grep -F "literal.string"')); + ok(isAutoApproved('git grep --fixed-strings "exact match"')); + + // But still block dangerous flags even with complex patterns + ok(!isAutoApproved('git grep -P "complex.*pattern" file.txt')); + ok(!isAutoApproved('git grep -f patterns.txt -- "*.js"')); + }); + }); }); From 89f010435986d953d1ca6e03b241a51753e77a89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:08:48 +0000 Subject: [PATCH 0082/4355] Fix terminal profile configuration path resolution Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../browser/tools/runInTerminalTool.ts | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) 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 e219773405d..eb58f2dc4ed 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -603,12 +603,20 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Terminal init private async _getCopilotShell(): Promise { + const shellConfig = await this._getCopilotShellConfig(); + return shellConfig.executable || 'bash'; + } + + private async _getCopilotShellConfig(): Promise<{ executable: string; args?: string[] }> { const os = await this._osBackend; // Check for chat agent terminal profile first const chatAgentProfile = this._getChatAgentTerminalProfile(os); if (chatAgentProfile) { - return chatAgentProfile.path; + return { + executable: chatAgentProfile.path, + args: chatAgentProfile.args + }; } const defaultShell = await this._terminalProfileResolverService.getDefaultShell({ @@ -617,14 +625,30 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { }); // Force pwsh over cmd as cmd doesn't have shell integration if (basename(defaultShell) === 'cmd.exe') { - return 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; + return { + executable: 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + }; } - return defaultShell; + return { + executable: defaultShell + }; } private _getChatAgentTerminalProfile(os: OperatingSystem): ITerminalProfile | undefined { - const osKey = this._getOsKey(os); - const profileSetting = `${TerminalChatAgentToolsSettingId.TerminalProfileLinux.replace('.linux', '')}.${osKey}`; + let profileSetting: string; + switch (os) { + case OperatingSystem.Windows: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileWindows; + break; + case OperatingSystem.Macintosh: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileMacOs; + break; + case OperatingSystem.Linux: + default: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileLinux; + break; + } + const profile = this._configurationService.getValue(profileSetting); if (this._isValidChatAgentTerminalProfile(profile)) { @@ -634,18 +658,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { return undefined; } - private _getOsKey(os: OperatingSystem): string { - switch (os) { - case OperatingSystem.Windows: - return 'windows'; - case OperatingSystem.Macintosh: - return 'osx'; - case OperatingSystem.Linux: - default: - return 'linux'; - } - } - private _isValidChatAgentTerminalProfile(profile: unknown): profile is ITerminalProfile { if (profile === null || profile === undefined || typeof profile !== 'object') { return false; From 8af47a37748855b919b5d2c8654a6d703d2c76b9 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 8 Sep 2025 17:13:26 +0200 Subject: [PATCH 0083/4355] Saving the value stored in the rich screen reader content for correct update (#265685) saving the value stored in the screen reader --- .../native/screenReaderContentRich.ts | 38 +++++++++++++------ .../viewModel/screenReaderSimpleModel.ts | 1 + .../test/browser/controller/imeTester.ts | 4 ++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts index a79bb3480dc..7a274e27f3b 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts @@ -33,7 +33,7 @@ export class RichScreenReaderContent extends Disposable implements IScreenReader private _accessibilityPageSize: number = 1; private _ignoreSelectionChangeTime: number = 0; - private _state: RichScreenReaderState = new RichScreenReaderState([]); + private _state: RichScreenReaderState = RichScreenReaderState.NULL; private _strategy: RichPagedScreenReaderStrategy = new RichPagedScreenReaderStrategy(); private _renderedLines: Map = new Map(); @@ -66,7 +66,7 @@ export class RichScreenReaderContent extends Disposable implements IScreenReader this._setSelectionOnScreenReaderContent(this._context, this._renderedLines, primarySelection); } } else { - this._state = new RichScreenReaderState([]); + this._state = RichScreenReaderState.NULL; this._setIgnoreSelectionChangeTime('setValue'); this._domNode.domNode.textContent = ''; } @@ -341,18 +341,32 @@ class LineInterval { class RichScreenReaderState { - constructor(public readonly intervals: LineInterval[]) { } + public readonly value: string; - equals(other: RichScreenReaderState): boolean { - if (this.intervals.length !== other.intervals.length) { - return false; - } - for (let i = 0; i < this.intervals.length; i++) { - if (this.intervals[i].startLine !== other.intervals[i].startLine || this.intervals[i].endLine !== other.intervals[i].endLine) { - return false; + constructor(model: ISimpleModel, public readonly intervals: LineInterval[]) { + let value = ''; + for (const interval of intervals) { + for (let lineNumber = interval.startLine; lineNumber <= interval.endLine; lineNumber++) { + value += model.getLineContent(lineNumber) + '\n'; } } - return true; + this.value = value; + } + + equals(other: RichScreenReaderState): boolean { + return this.value === other.value; + } + + static get NULL(): RichScreenReaderState { + const nullModel: ISimpleModel = { + getLineContent: () => '', + getLineCount: () => 1, + getLineMaxColumn: () => 1, + getValueInRange: () => '', + getValueLengthInRange: () => 0, + modifyPosition: (position, offset) => position + }; + return new RichScreenReaderState(nullModel, []); } } @@ -380,6 +394,6 @@ class RichPagedScreenReaderStrategy implements IPagedScreenReaderStrategy Date: Fri, 5 Sep 2025 16:52:02 +0200 Subject: [PATCH 0084/4355] Adds more arc tracker tests --- .../editTelemetry/common/arcTracker.ts | 7 + .../test/common/arcTracker.test.ts | 74 --------- .../test/node/arcTracker.test.ts | 152 ++++++++++++++++++ .../test/node/data/issue-264048.edits.w.json | 52 ++++++ .../test/node/data/line-insert.edits.w.json | 1 + .../node/data/line-modification.edits.w.json | 1 + .../node/data/multiline-insert.edits.w.json | 1 + 7 files changed, 214 insertions(+), 74 deletions(-) delete mode 100644 src/vs/workbench/contrib/editTelemetry/test/common/arcTracker.test.ts create mode 100644 src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts create mode 100644 src/vs/workbench/contrib/editTelemetry/test/node/data/issue-264048.edits.w.json create mode 100644 src/vs/workbench/contrib/editTelemetry/test/node/data/line-insert.edits.w.json create mode 100644 src/vs/workbench/contrib/editTelemetry/test/node/data/line-modification.edits.w.json create mode 100644 src/vs/workbench/contrib/editTelemetry/test/node/data/multiline-insert.edits.w.json diff --git a/src/vs/workbench/contrib/editTelemetry/common/arcTracker.ts b/src/vs/workbench/contrib/editTelemetry/common/arcTracker.ts index 1ab7a9fba3d..74a2a91900a 100644 --- a/src/vs/workbench/contrib/editTelemetry/common/arcTracker.ts +++ b/src/vs/workbench/contrib/editTelemetry/common/arcTracker.ts @@ -66,6 +66,13 @@ export class ArcTracker { insertedLineCounts: insertedLineCount, }; } + + public getValues(): unknown { + return { + arc: this.getAcceptedRestrainedCharactersCount(), + ...this.getLineCountInfo(), + }; + } } export class IsTrackedEditData implements IEditData { diff --git a/src/vs/workbench/contrib/editTelemetry/test/common/arcTracker.test.ts b/src/vs/workbench/contrib/editTelemetry/test/common/arcTracker.test.ts deleted file mode 100644 index fcae9466b0e..00000000000 --- a/src/vs/workbench/contrib/editTelemetry/test/common/arcTracker.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { StringText } from '../../../../../editor/common/core/text/abstractText.js'; -import { computeStringDiff } from '../../../../../editor/common/services/editorWebWorker.js'; -import { ArcTracker } from '../../common/arcTracker.js'; - -suite('ArcTracker', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('https://github.com/microsoft/vscode/issues/264048 - Line number count should decrease', () => { - - const states = [ - `TODO: Add Charlie -Alpha -Bravo -Delta`, - `Alpha -Bravo -Delta -Charlie`, - `* Alpha -* Bravo -* Delta -* Charlie`, - `ICAO spelling alphabet: -* Alpha -* Bravo -* Delta -* Charlie` - ]; - - const edits = compareAdjacentItems(states, (a, b) => computeStringDiff(a, b, { maxComputationTimeMs: 0 }, 'advanced')); - - const t = new ArcTracker( - new StringText(states[0]), - edits[0] - ); - - const data: unknown[] = []; - data.push(t.getLineCountInfo()); - - for (let i = 1; i < edits.length; i++) { - t.handleEdits(edits[i]); - data.push(t.getLineCountInfo()); - } - assert.deepStrictEqual(data, ([ - { - deletedLineCounts: 1, - insertedLineCounts: 1 - }, - { - deletedLineCounts: 0, - insertedLineCounts: 1 - }, - { - deletedLineCounts: 0, - insertedLineCounts: 1 - } - ])); - }); -}); - -function compareAdjacentItems(arr: T[], comparator: (a: T, b: T) => TResult): TResult[] { - const result: TResult[] = []; - for (let i = 0; i < arr.length - 1; i++) { - result.push(comparator(arr[i], arr[i + 1])); - } - return result; -} diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts b/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts new file mode 100644 index 00000000000..f6ac11098db --- /dev/null +++ b/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { StringText } from '../../../../../editor/common/core/text/abstractText.js'; +import { ArcTracker } from '../../common/arcTracker.js'; +import { FileAccess } from '../../../../../base/common/network.js'; +import { readFileSync } from 'fs'; +import { join, resolve } from '../../../../../base/common/path.js'; +import { StringEdit, StringReplacement } from '../../../../../editor/common/core/edits/stringEdit.js'; +import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; +import { ensureDependenciesAreSet } from '../../../../../editor/common/core/text/positionToOffset.js'; + +suite('ArcTracker', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + ensureDependenciesAreSet(); + + const fixturesOutDir = FileAccess.asFileUri('vs/workbench/contrib/editTelemetry/test/node/data').fsPath; + const fixturesSrcDir = resolve(fixturesOutDir).replaceAll('\\', '/').replace('/out/vs/workbench/', '/src/vs/workbench/'); + + function getData(name: string): IEdits { + const path = join(fixturesSrcDir, name + '.edits.w.json'); + const src = readFileSync(path, 'utf8'); + return JSON.parse(src); + } + + test('issue-264048', () => { + const stats = runTestWithData(getData('issue-264048')); + assert.deepStrictEqual(stats, ([ + { + arc: 8, + deletedLineCounts: 1, + insertedLineCounts: 1 + }, + { + arc: 8, + deletedLineCounts: 0, + insertedLineCounts: 1 + }, + { + arc: 8, + deletedLineCounts: 0, + insertedLineCounts: 1 + } + ])); + }); + + test('line-insert', () => { + const stats = runTestWithData(getData('line-insert')); + assert.deepStrictEqual(stats, ([ + { + arc: 7, + deletedLineCounts: 0, + insertedLineCounts: 1 + }, + { + arc: 5, + deletedLineCounts: 0, + insertedLineCounts: 1 + } + ])); + }); + + test('line-modification', () => { + const stats = runTestWithData(getData('line-modification')); + assert.deepStrictEqual(stats, ([ + { + arc: 6, + deletedLineCounts: 1, + insertedLineCounts: 1 + }, + { + arc: 6, + deletedLineCounts: 1, + insertedLineCounts: 1 + }, + { + arc: 0, + deletedLineCounts: 0, + insertedLineCounts: 0 + } + ])); + }); + + test('multiline-insert', () => { + const stats = runTestWithData(getData('multiline-insert')); + assert.deepStrictEqual(stats, ([ + { + arc: 24, + deletedLineCounts: 0, + insertedLineCounts: 3 + }, + { + arc: 23, + deletedLineCounts: 0, + insertedLineCounts: 2 + } + ])); + }); +}); + +interface IEdits { + initialText: string; + edits: Array<{ + replacements: Array<{ + start: number; + endEx: number; + text: string; + }>; + }>; +} + +function createStringEditFromJson(editData: IEdits['edits'][0]): StringEdit { + const replacements = editData.replacements.map(replacement => + new StringReplacement( + OffsetRange.ofStartAndLength(replacement.start, replacement.endEx - replacement.start), + replacement.text + ) + ); + return new StringEdit(replacements); +} + +function runTestWithData(data: IEdits): unknown { + const edits = data.edits.map(editData => createStringEditFromJson(editData)); + + const t = new ArcTracker( + new StringText(data.initialText), + edits[0] + ); + + const stats: unknown[] = []; + stats.push(t.getValues()); + let lastLineNumbers = t.getLineCountInfo().insertedLineCounts; + let lastArc = t.getAcceptedRestrainedCharactersCount(); + + for (let i = 1; i < edits.length; i++) { + t.handleEdits(edits[i]); + stats.push(t.getValues()); + + const newLineNumbers = t.getLineCountInfo().insertedLineCounts; + assert.ok(newLineNumbers >= lastLineNumbers, `Line numbers must not decrease. Last: ${lastLineNumbers}, new: ${newLineNumbers}`); + lastLineNumbers = newLineNumbers; + + const newArc = t.getAcceptedRestrainedCharactersCount(); + assert.ok(newArc >= lastArc, `ARC must not decrease. Last: ${lastArc}, new: ${newArc}`); + lastArc = newArc; + } + return stats; +} diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/data/issue-264048.edits.w.json b/src/vs/workbench/contrib/editTelemetry/test/node/data/issue-264048.edits.w.json new file mode 100644 index 00000000000..20b9a585eb7 --- /dev/null +++ b/src/vs/workbench/contrib/editTelemetry/test/node/data/issue-264048.edits.w.json @@ -0,0 +1,52 @@ +{ + "initialText": "TODO: Add Charlie\nAlpha\nBravo\nDelta", + "edits": [ + { + "replacements": [ + { + "start": 0, + "endEx": 18, + "text": "" + }, + { + "start": 35, + "endEx": 35, + "text": "\nCharlie" + } + ] + }, + { + "replacements": [ + { + "start": 0, + "endEx": 0, + "text": "* " + }, + { + "start": 6, + "endEx": 6, + "text": "* " + }, + { + "start": 12, + "endEx": 12, + "text": "* " + }, + { + "start": 18, + "endEx": 18, + "text": "* " + } + ] + }, + { + "replacements": [ + { + "start": 0, + "endEx": 0, + "text": "ICAO spelling alphabet:\n" + } + ] + } + ] +} diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/data/line-insert.edits.w.json b/src/vs/workbench/contrib/editTelemetry/test/node/data/line-insert.edits.w.json new file mode 100644 index 00000000000..55b2f5b6271 --- /dev/null +++ b/src/vs/workbench/contrib/editTelemetry/test/node/data/line-insert.edits.w.json @@ -0,0 +1 @@ +{"initialText":"abcd\r\nefgh\r\nijkl\r\nmnop","edits":[{"replacements":[{"start":12,"endEx":12,"text":"aitext\n"}]},{"replacements":[{"start":2,"endEx":2,"text":"123"},{"start":8,"endEx":8,"text":"\r\n"},{"start":12,"endEx":14,"text":"AI"}]}]} diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/data/line-modification.edits.w.json b/src/vs/workbench/contrib/editTelemetry/test/node/data/line-modification.edits.w.json new file mode 100644 index 00000000000..a5c9d5c82de --- /dev/null +++ b/src/vs/workbench/contrib/editTelemetry/test/node/data/line-modification.edits.w.json @@ -0,0 +1 @@ +{"initialText":"abcd\r\nefgh\r\nijkl\r\nmnop","edits":[{"replacements":[{"start":9,"endEx":9,"text":"aitext"}]},{"replacements":[{"start":13,"endEx":13,"text":"\n"}]},{"replacements":[{"start":6,"endEx":18,"text":""}]}]} diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/data/multiline-insert.edits.w.json b/src/vs/workbench/contrib/editTelemetry/test/node/data/multiline-insert.edits.w.json new file mode 100644 index 00000000000..495dcd63f68 --- /dev/null +++ b/src/vs/workbench/contrib/editTelemetry/test/node/data/multiline-insert.edits.w.json @@ -0,0 +1 @@ +{"initialText":"abcd\r\nefgh\r\nijkl\r\nmnop","edits":[{"replacements":[{"start":12,"endEx":12,"text":"aitext1\naitext2\naitext3\n"}]},{"replacements":[{"start":27,"endEx":28,"text":""}]}]} From e26cd372658610ef618b7b58f5db3d41d8c72e25 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 8 Sep 2025 17:01:54 +0200 Subject: [PATCH 0085/4355] Fixes test --- .../contrib/editTelemetry/test/node/arcTracker.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts b/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts index f6ac11098db..584915e9489 100644 --- a/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts +++ b/src/vs/workbench/contrib/editTelemetry/test/node/arcTracker.test.ts @@ -141,11 +141,11 @@ function runTestWithData(data: IEdits): unknown { stats.push(t.getValues()); const newLineNumbers = t.getLineCountInfo().insertedLineCounts; - assert.ok(newLineNumbers >= lastLineNumbers, `Line numbers must not decrease. Last: ${lastLineNumbers}, new: ${newLineNumbers}`); + assert.ok(newLineNumbers <= lastLineNumbers, `Line numbers must not increase. Last: ${lastLineNumbers}, new: ${newLineNumbers}`); lastLineNumbers = newLineNumbers; const newArc = t.getAcceptedRestrainedCharactersCount(); - assert.ok(newArc >= lastArc, `ARC must not decrease. Last: ${lastArc}, new: ${newArc}`); + assert.ok(newArc <= lastArc, `ARC must not increase. Last: ${lastArc}, new: ${newArc}`); lastArc = newArc; } return stats; From 6a0822312197d8067f141d67b7661bcb8c38b77a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 8 Sep 2025 09:52:09 -0700 Subject: [PATCH 0086/4355] mcp: fix multiple sampling dialogs shown for concurrent sampling requests (#265698) Fixes #265324 --- src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts index bda892929dc..d8b1dc0348e 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mapFindFirst } from '../../../../base/common/arraysFind.js'; +import { Sequencer } from '../../../../base/common/async.js'; import { decodeBase64 } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; @@ -40,6 +41,8 @@ export class McpSamplingService extends Disposable implements IMcpSamplingServic private readonly _logs: McpSamplingLog; + private readonly _modelSequencer = new Sequencer(); + constructor( @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -72,7 +75,7 @@ export class McpSamplingService extends Disposable implements IMcpSamplingServic messages.unshift({ role: ChatMessageRole.System, content: [{ type: 'text', value: opts.params.systemPrompt }] }); } - const model = await this._getMatchingModel(opts); + const model = await this._modelSequencer.queue(() => this._getMatchingModel(opts)); // todo@connor4312: nullExtensionDescription.identifier -> undefined with API update const response = await this._languageModelsService.sendChatRequest(model, new ExtensionIdentifier('core'), messages, {}, token); From a25a4f32ba9009a66b2986ea44143929350f34f8 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Mon, 8 Sep 2025 18:06:27 +0100 Subject: [PATCH 0087/4355] Update hover and inline edit styles for improved UI consistency --- .../browser/services/hoverService/hover.css | 2 +- .../components/gutterIndicatorMenu.ts | 19 ++++++++++++------- .../browser/view/inlineEdits/view.css | 16 ++++++++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 96333ca1a39..6b3b5c53262 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -13,7 +13,7 @@ max-width: 700px; background: var(--vscode-editorHoverWidget-background); border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 3px; + border-radius: 5px; color: var(--vscode-editorHoverWidget-foreground); box-shadow: 0 2px 8px var(--vscode-widget-shadow); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts index 4ee779877ba..89071d5bea0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts @@ -2,6 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { ChildNode, LiveElement, n } from '../../../../../../../base/browser/dom.js'; import { ActionBar, IActionBarOptions } from '../../../../../../../base/browser/ui/actionbar/actionbar.js'; @@ -19,7 +23,7 @@ import { IContextKeyService } from '../../../../../../../platform/contextkey/com import { nativeHoverDelegate } from '../../../../../../../platform/hover/browser/hover.js'; import { IKeybindingService } from '../../../../../../../platform/keybinding/common/keybinding.js'; import { defaultKeybindingLabelStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js'; -import { asCssVariable, descriptionForeground, editorActionListForeground, editorHoverBorder, keybindingLabelBackground } from '../../../../../../../platform/theme/common/colorRegistry.js'; +import { asCssVariable, descriptionForeground, editorActionListForeground, editorHoverBorder } from '../../../../../../../platform/theme/common/colorRegistry.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { hideInlineCompletionId, inlineSuggestCommitId, toggleShowCollapsedId } from '../../../controller/commandIds.js'; @@ -150,7 +154,7 @@ function hoverContent(content: ChildNode) { class: 'content', style: { margin: 4, - minWidth: 150, + minWidth: 180, } }, content); } @@ -160,10 +164,10 @@ function header(title: string | IObservable) { class: 'header', style: { color: asCssVariable(descriptionForeground), - fontSize: '12px', + fontSize: '13px', fontWeight: '600', - padding: '0 10px', - lineHeight: 26, + padding: '0 4px', + lineHeight: 28, } }, [title]); } @@ -205,7 +209,8 @@ function option(props: { disableTitle: true, ...defaultKeybindingLabelStyles, keybindingLabelShadow: undefined, - keybindingLabelBackground: asCssVariable(keybindingLabelBackground), + keybindingLabelForeground: asCssVariable(descriptionForeground), + keybindingLabelBackground: 'transparent', keybindingLabelBorder: 'transparent', keybindingLabelBottomBorder: undefined, })); @@ -222,7 +227,7 @@ function actionBar(actions: IAction[], options: IActionBarOptions) { return derived({ name: 'inlineEdits.actionBar' }, (_reader) => n.div({ class: ['action-widget-action-bar'], style: { - padding: '0 10px', + padding: '0 24px', } }, [ n.div({ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css index 6772d1a0b60..a23b07d1f0e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css @@ -193,12 +193,12 @@ .monaco-menu-option { color: var(--vscode-editorActionList-foreground); font-size: 13px; - padding: 0 10px; - line-height: 26px; + padding: 0 4px; + line-height: 28px; display: flex; - gap: 8px; + gap: 4px; align-items: center; - border-radius: 4px; + border-radius: 3px; cursor: pointer; &.active { @@ -206,5 +206,13 @@ color: var(--vscode-editorActionList-focusForeground); outline: 1px solid var(--vscode-menu-selectionBorder, transparent); outline-offset: -1px; + + .monaco-keybinding { + color: var(--vscode-editorActionList-focusForeground); + } + + .monaco-keybinding-key { + color: var(--vscode-editorActionList-focusForeground); + } } } From fbc00e350fd6a963feb2d52538efeb09816dab18 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Mon, 8 Sep 2025 18:09:22 +0100 Subject: [PATCH 0088/4355] Refactor GutterIndicatorMenuContent to streamline command handling and improve readability --- .../browser/view/inlineEdits/components/gutterIndicatorMenu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts index 89071d5bea0..fbff2fa09bb 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts @@ -131,9 +131,9 @@ export class GutterIndicatorMenuContent { gotoAndAccept, reject, toggleCollapsedMode, + extensionCommands.length ? separator() : undefined, settings, - extensionCommands.length ? separator() : undefined, ...extensionCommands, actionBarFooter ? separator() : undefined, From 8d7e69612d313681b5cb90fed1d220eac6003f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 8 Sep 2025 19:14:10 +0200 Subject: [PATCH 0089/4355] revert back to official nodejs mirror (#265692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maybe the issue is gone by now 🤞: - https://github.com/microsoft/azure-pipelines-tasks/issues/18280 - https://github.com/nodejs/nodejs.org/issues/4495 --- .github/workflows/pr-darwin-test.yml | 2 -- .github/workflows/pr-linux-test.yml | 2 -- .github/workflows/pr-node-modules.yml | 8 -------- .github/workflows/pr-win32-test.yml | 2 -- .github/workflows/pr.yml | 2 -- build/azure-pipelines/alpine/cli-build-alpine.yml | 1 - build/azure-pipelines/alpine/product-build-alpine.yml | 1 - build/azure-pipelines/darwin/cli-build-darwin.yml | 1 - .../darwin/product-build-darwin-cli-sign.yml | 1 - .../darwin/product-build-darwin-universal.yml | 1 - build/azure-pipelines/darwin/product-build-darwin.yml | 1 - build/azure-pipelines/distro-build.yml | 1 - build/azure-pipelines/linux/cli-build-linux.yml | 1 - build/azure-pipelines/linux/product-build-linux.yml | 1 - build/azure-pipelines/product-compile.yml | 1 - build/azure-pipelines/product-npm-package-validate.yml | 1 - build/azure-pipelines/product-publish.yml | 1 - build/azure-pipelines/product-release.yml | 1 - build/azure-pipelines/publish-types/publish-types.yml | 1 - build/azure-pipelines/web/product-build-web.yml | 1 - build/azure-pipelines/win32/cli-build-win32.yml | 1 - .../win32/product-build-win32-cli-sign.yml | 1 - build/azure-pipelines/win32/product-build-win32.yml | 1 - build/azure-pipelines/win32/sdl-scan-win32.yml | 1 - 24 files changed, 35 deletions(-) diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index 655c8e9e6c3..ea66d630ef6 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -30,8 +30,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index 593c6ad9f4d..923a71e7993 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -30,8 +30,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Setup system services run: | diff --git a/.github/workflows/pr-node-modules.yml b/.github/workflows/pr-node-modules.yml index a8dfc6a22e7..b17abc632ab 100644 --- a/.github/workflows/pr-node-modules.yml +++ b/.github/workflows/pr-node-modules.yml @@ -19,8 +19,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile $(node -p process.arch) > .build/packagelockhash @@ -100,8 +98,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash @@ -174,8 +170,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash @@ -237,8 +231,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key shell: pwsh diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index ac4140211a3..4036cfa0ad9 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -30,8 +30,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key shell: pwsh diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f3568b59873..d23ce4146ae 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -27,8 +27,6 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile $(node -p process.arch) > .build/packagelockhash diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index d40b310ce1b..95dee012c06 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -16,7 +16,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../cli/cli-apply-patches.yml@self diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 6af958e170f..303db76fd0f 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -3,7 +3,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../distro/download-distro.yml@self diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml index 06e7ee74085..730918f5da1 100644 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ b/build/azure-pipelines/darwin/cli-build-darwin.yml @@ -16,7 +16,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../cli/cli-apply-patches.yml@self diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml index 0450a17d2bf..505534093d0 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -9,7 +9,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index be30724f1ad..60ce6a82954 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -3,7 +3,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../distro/download-distro.yml@self diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index f8ce9a18162..7d2849681c5 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -21,7 +21,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../distro/download-distro.yml@self diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index ae11345bb6d..1d0a50b1297 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -12,5 +12,4 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ./distro/download-distro.yml@self diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml index 918d6aae14f..b29c4259433 100644 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -19,7 +19,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../cli/cli-apply-patches.yml@self diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 8a0d1c99c29..f1e92a60c1f 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -26,7 +26,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../distro/download-distro.yml@self diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index a406a6e8da7..19e96932301 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -3,7 +3,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ./distro/download-distro.yml@self diff --git a/build/azure-pipelines/product-npm-package-validate.yml b/build/azure-pipelines/product-npm-package-validate.yml index d4bc480ed72..4979c96edc5 100644 --- a/build/azure-pipelines/product-npm-package-validate.yml +++ b/build/azure-pipelines/product-npm-package-validate.yml @@ -25,7 +25,6 @@ jobs: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - script: | set -e diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 858ce5e4d04..90cd06c5459 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -3,7 +3,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" diff --git a/build/azure-pipelines/product-release.yml b/build/azure-pipelines/product-release.yml index 87896f9340b..d7b51aa8a92 100644 --- a/build/azure-pipelines/product-release.yml +++ b/build/azure-pipelines/product-release.yml @@ -7,7 +7,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: AzureCLI@2 displayName: Fetch secrets diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 5f60ae5a262..65882ce1971 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -14,7 +14,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - bash: | TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 41c8494c176..e35af2b87aa 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -3,7 +3,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../distro/download-distro.yml@self diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index e40acc0bcef..1914cb7cf6c 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -16,7 +16,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ../cli/cli-apply-patches.yml@self diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml index 29e20937e50..520931ffa48 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -10,7 +10,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 01d00e0c380..a0b040c52c2 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -23,7 +23,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: UsePythonVersion@0 inputs: diff --git a/build/azure-pipelines/win32/sdl-scan-win32.yml b/build/azure-pipelines/win32/sdl-scan-win32.yml index bf6819a4b47..ba60b881392 100644 --- a/build/azure-pipelines/win32/sdl-scan-win32.yml +++ b/build/azure-pipelines/win32/sdl-scan-win32.yml @@ -9,7 +9,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: UsePythonVersion@0 inputs: From 62dbaa0e778080d4b10cdcc03e8749ae5c1a5044 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:17:09 +0000 Subject: [PATCH 0090/4355] Bump actions/setup-node from 4 to 5 (#265655) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Logan Ramos --- .github/workflows/monaco-editor.yml | 2 +- .github/workflows/pr-darwin-test.yml | 2 +- .github/workflows/pr-linux-test.yml | 2 +- .github/workflows/pr-node-modules.yml | 8 ++++---- .github/workflows/pr-win32-test.yml | 2 +- .github/workflows/pr.yml | 2 +- .github/workflows/telemetry.yml | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index 95ed1811177..cedbd379a3d 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index ea66d630ef6..afcb9cc5bb4 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index 923a71e7993..ed8ff3a40ba 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-node-modules.yml b/.github/workflows/pr-node-modules.yml index b17abc632ab..65c57f078f9 100644 --- a/.github/workflows/pr-node-modules.yml +++ b/.github/workflows/pr-node-modules.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc @@ -95,7 +95,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc @@ -167,7 +167,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc @@ -228,7 +228,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index 4036cfa0ad9..0a1ec3aa45b 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d23ce4146ae..2361aaa31de 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: .nvmrc diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index 1f43144f1dc..03c3f649d58 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -11,7 +11,7 @@ jobs: with: persist-credentials: false - - uses: 'actions/setup-node@v4' + - uses: 'actions/setup-node@v5' with: node-version: 'lts/*' From 85b4d6696e47cf582743729f384e56e74f499727 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:30:37 -0700 Subject: [PATCH 0091/4355] Add '> Developer: Policy Diagnostics' (#265700) --- .../browser/actions/developerActions.ts | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 468b22e85c8..a80e82527ad 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -41,6 +41,11 @@ import { IEditorService } from '../../services/editor/common/editorService.js'; import product from '../../../platform/product/common/product.js'; import { CommandsRegistry } from '../../../platform/commands/common/commands.js'; import { IEnvironmentService } from '../../../platform/environment/common/environment.js'; +import { IProductService } from '../../../platform/product/common/productService.js'; +import { IDefaultAccountService } from '../../services/accounts/common/defaultAccount.js'; +import { IAuthenticationService } from '../../services/authentication/common/authentication.js'; +import { IAuthenticationAccessService } from '../../services/authentication/browser/authenticationAccessService.js'; +import { IPolicyService } from '../../../platform/policy/common/policy.js'; class InspectContextKeysAction extends Action2 { @@ -645,12 +650,265 @@ class StopTrackDisposables extends Action2 { } } +class PolicyDiagnosticsAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.showPolicyDiagnostics', + title: localize2('policyDiagnostics', 'Policy Diagnostics'), + category: Categories.Developer, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + const productService = accessor.get(IProductService); + const defaultAccountService = accessor.get(IDefaultAccountService); + const authenticationService = accessor.get(IAuthenticationService); + const authenticationAccessService = accessor.get(IAuthenticationAccessService); + const policyService = accessor.get(IPolicyService); + + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + + let content = '# VS Code Policy Diagnostics\n\n'; + content += '*WARNING: This file may contain sensitive information.*\n\n'; + content += '## System Information\n\n'; + content += '| Property | Value |\n'; + content += '|----------|-------|\n'; + content += `| Generated | ${new Date().toISOString()} |\n`; + content += `| Product | ${productService.nameLong} ${productService.version} |\n`; + content += `| Commit | ${productService.commit || 'n/a'} |\n\n`; + + // Account information + content += '## Account Information\n\n'; + try { + const account = await defaultAccountService.getDefaultAccount(); + const sensitiveKeys = ['sessionId', 'analytics_tracking_id']; + if (account) { + // Try to get username/display info from the authentication session + let username = 'Unknown'; + let accountLabel = 'Unknown'; + try { + const providerIds = authenticationService.getProviderIds(); + for (const providerId of providerIds) { + const sessions = await authenticationService.getSessions(providerId); + const matchingSession = sessions.find(session => session.id === account.sessionId); + if (matchingSession) { + username = matchingSession.account.id; + accountLabel = matchingSession.account.label; + break; + } + } + } catch (error) { + // Fallback to just session info + } + + content += '### Default Account Summary\n\n'; + content += `**Account ID/Username**: ${username}\n\n`; + content += `**Account Label**: ${accountLabel}\n\n`; + + content += '### Detailed Account Properties\n\n'; + content += '| Property | Value |\n'; + content += '|----------|-------|\n'; + + // Iterate through all properties of the account object + for (const [key, value] of Object.entries(account)) { + if (value !== undefined && value !== null) { + let displayValue: string; + + // Mask sensitive information + if (sensitiveKeys.includes(key)) { + displayValue = '***'; + } else if (typeof value === 'object') { + displayValue = JSON.stringify(value); + } else { + displayValue = String(value); + } + + content += `| ${key} | ${displayValue} |\n`; + } + } + content += '\n'; + } else { + content += '*No default account configured*\n\n'; + } + } catch (error) { + content += `*Error retrieving account information: ${error}*\n\n`; + } + + content += '## Policy-Controlled Settings\n\n'; + + const policyConfigurations = configurationRegistry.getPolicyConfigurations(); + const configurationProperties = configurationRegistry.getConfigurationProperties(); + const excludedProperties = configurationRegistry.getExcludedConfigurationProperties(); + + if (policyConfigurations.size > 0) { + const appliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = []; + const notAppliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = []; + + for (const [policyName, settingKey] of policyConfigurations) { + const property = configurationProperties[settingKey] ?? excludedProperties[settingKey]; + if (property) { + const inspectValue = configurationService.inspect(settingKey); + const settingInfo = { + name: policyName, + key: settingKey, + property, + inspection: inspectValue + }; + + if (inspectValue.policyValue !== undefined) { + appliedPolicy.push(settingInfo); + } else { + notAppliedPolicy.push(settingInfo); + } + } + } + + // Try to detect where the policy came from + const policySourceMemo = new Map(); + const getPolicySource = (policyName: string): string => { + if (policySourceMemo.has(policyName)) { + return policySourceMemo.get(policyName)!; + } + try { + const policyServiceConstructorName = policyService.constructor.name; + if (policyServiceConstructorName === 'MultiplexPolicyService') { + const multiplexService = policyService as any; + if (multiplexService.policyServices) { + const componentServices = multiplexService.policyServices as ReadonlyArray; + for (const service of componentServices) { + if (service.getPolicyValue && service.getPolicyValue(policyName) !== undefined) { + policySourceMemo.set(policyName, service.constructor.name); + return service.constructor.name; + } + } + } + } + return ''; + } catch { + return 'Unknown'; + } + }; + + content += '### Applied Policy\n\n'; + appliedPolicy.sort((a, b) => getPolicySource(a.name).localeCompare(getPolicySource(b.name)) || a.name.localeCompare(b.name)); + if (appliedPolicy.length > 0) { + content += '| Setting Key | Policy Name | Policy Source | Default Value | Current Value | Policy Value |\n'; + content += '|-------------|-------------|---------------|---------------|---------------|-------------|\n'; + + for (const setting of appliedPolicy) { + const defaultValue = JSON.stringify(setting.property.default); + const currentValue = JSON.stringify(setting.inspection.value); + const policyValue = JSON.stringify(setting.inspection.policyValue); + const policySource = getPolicySource(setting.name); + + content += `| ${setting.key} | ${setting.name} | ${policySource} | \`${defaultValue}\` | \`${currentValue}\` | \`${policyValue}\` |\n`; + } + content += '\n'; + } else { + content += '*No settings are currently controlled by policies*\n\n'; + } + + content += '### Non-applied Policy\n\n'; + if (notAppliedPolicy.length > 0) { + content += '| Setting Key | Policy Name \n'; + content += '|-------------|-------------|\n'; + + for (const setting of notAppliedPolicy) { + + content += `| ${setting.key} | ${setting.name}|\n`; + } + content += '\n'; + } else { + content += '*All policy-controllable settings are currently being enforced*\n\n'; + } + } else { + content += '*No policy-controlled settings found*\n\n'; + } + + // Authentication diagnostics + content += '## Authentication Information\n\n'; + try { + const providerIds = authenticationService.getProviderIds(); + + if (providerIds.length > 0) { + content += '### Authentication Providers\n\n'; + content += '| Provider ID | Sessions | Accounts |\n'; + content += '|-------------|----------|----------|\n'; + + for (const providerId of providerIds) { + try { + const sessions = await authenticationService.getSessions(providerId); + const accounts = sessions.map(session => session.account); + const uniqueAccounts = Array.from(new Set(accounts.map(account => account.label))); + + content += `| ${providerId} | ${sessions.length} | ${uniqueAccounts.join(', ') || 'None'} |\n`; + } catch (error) { + content += `| ${providerId} | Error | ${error} |\n`; + } + } + content += '\n'; + + // Detailed session information + content += '### Detailed Session Information\n\n'; + for (const providerId of providerIds) { + try { + const sessions = await authenticationService.getSessions(providerId); + + if (sessions.length > 0) { + content += `#### ${providerId}\n\n`; + content += '| Account | Scopes | Extensions with Access |\n'; + content += '|---------|--------|------------------------|\n'; + + for (const session of sessions) { + const accountName = session.account.label; + const scopes = session.scopes.join(', ') || 'Default'; + + // Get extensions with access to this account + try { + const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountName); + const extensionNames = allowedExtensions + .filter(ext => ext.allowed !== false) + .map(ext => `${ext.name}${ext.trusted ? ' (trusted)' : ''}`) + .join(', ') || 'None'; + + content += `| ${accountName} | ${scopes} | ${extensionNames} |\n`; + } catch (error) { + content += `| ${accountName} | ${scopes} | Error: ${error} |\n`; + } + } + content += '\n'; + } + } catch (error) { + content += `#### ${providerId}\n*Error retrieving sessions: ${error}*\n\n`; + } + } + } else { + content += '*No authentication providers found*\n\n'; + } + } catch (error) { + content += `*Error retrieving authentication information: ${error}*\n\n`; + } + + await editorService.openEditor({ + resource: undefined, + contents: content, + languageId: 'markdown', + options: { pinned: true, } + }); + } +} + // --- Actions Registration registerAction2(InspectContextKeysAction); registerAction2(ToggleScreencastModeAction); registerAction2(LogStorageAction); registerAction2(LogWorkingCopiesAction); registerAction2(RemoveLargeStorageEntriesAction); +registerAction2(PolicyDiagnosticsAction); if (!product.commit) { registerAction2(StartTrackDisposables); registerAction2(SnapshotTrackedDisposables); From 2deaa78bcf599b8b372163d98b90e998fabf85f7 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:38:49 -0700 Subject: [PATCH 0092/4355] Add `/login/oauth` path part to the ghes supported authorization server (#265703) Fixes GHE MCP loading --- extensions/github-authentication/src/github.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index dab57ffd776..e9f97282bf8 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -171,6 +171,9 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid return sessions; }); + const supportedAuthorizationServers = ghesUri + ? [vscode.Uri.joinPath(ghesUri, '/login/oauth')] + : [vscode.Uri.parse('https://github.com/login/oauth')]; this._disposable = vscode.Disposable.from( this._telemetryReporter, vscode.authentication.registerAuthenticationProvider( @@ -179,9 +182,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid this, { supportsMultipleAccounts: true, - supportedAuthorizationServers: [ - ghesUri ?? vscode.Uri.parse('https://github.com/login/oauth') - ] + supportedAuthorizationServers } ), this.context.secrets.onDidChange(() => this.checkForUpdates()) From c8c7793eece84027a69965edb5f8ad3f6822e3f0 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:05:44 -0700 Subject: [PATCH 0093/4355] poll for change to allow async update (#265724) * poll for change to allow async update * import poll --- .../singlefolder-tests/interactiveWindow.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 55a17cd3ce0..ca15c44683b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { asPromise, disposeAll } from '../utils'; +import { asPromise, disposeAll, poll } from '../utils'; import { Kernel, saveAllFilesAndCloseAll } from './notebook.api.test'; export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor }; @@ -108,7 +108,9 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { } }); - test('Interactive window has the correct kernel', async () => { + test('Interactive window has the correct kernel', async function () { + // Extend timeout a bit as kernel association can be async & occasionally slow on CI + this.timeout(20000); assert.ok(vscode.workspace.workspaceFolders); await createInteractiveWindow(defaultKernel); @@ -118,11 +120,15 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { const { notebookEditor } = await createInteractiveWindow(secondKernel); assert.ok(notebookEditor); - // Verify the kernel is the secondary one + // Run a cell to ensure the kernel is actually exercised await addCellAndRun(`print`, notebookEditor.notebook); + await poll( + () => Promise.resolve(secondKernel.associatedNotebooks.has(notebookEditor.notebook.uri.toString())), + v => v, + 'Secondary kernel was not set as the kernel for the interactive window' + ); assert.strictEqual(secondKernel.associatedNotebooks.has(notebookEditor.notebook.uri.toString()), true, `Secondary kernel was not set as the kernel for the interactive window`); - }); }); From d8b6cfff96992afaf0f5acb88200fb785b50ea4d Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 8 Sep 2025 16:30:37 -0400 Subject: [PATCH 0094/4355] improve discoverability of dictation features (#265739) fix #265738 --- .../welcomeGettingStarted/common/gettingStartedContent.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 3826aa86c42..318ca22ff82 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -461,6 +461,13 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ title: localize('gettingStarted.accessibilitySettings.title', "Configure accessibility settings"), description: localize('gettingStarted.accessibilitySettings.description.interpolated', "Accessibility settings can be configured by running the Open Accessibility Settings command.\n{0}", Button(localize('openAccessibilitySettings', "Open Accessibility Settings"), 'command:workbench.action.openAccessibilitySettings')), media: { type: 'markdown', path: 'empty' } + }, + { + id: 'dictation', + title: localize('gettingStarted.dictation.title', "Use dictation to write code and text in the editor and terminal"), + description: localize('gettingStarted.dictation.description.interpolated', "Dictation allows you to write code and text using your voice. It can be activated with the Voice: Start Dictation in Editor command.\n{0}\n For dictation in the terminal, use the Voice: Start Dictation in Terminal and Voice: Stop Dictation in Terminal commands.\n{1}\n{2}", Button(localize('toggleDictation', "Voice: Start Dictation in Editor"), 'command:workbench.action.editorDictation.start'), Button(localize('terminalStartDictation', "Terminal: Start Dictation in Terminal"), 'command:workbench.action.terminal.startVoiceDictation'), Button(localize('terminalStopDictation', "Terminal: Stop Dictation in Terminal"), 'command:workbench.action.terminal.stopVoiceDictation')), + when: 'hasSpeechProvider', + media: { type: 'markdown', path: 'empty' } } ] } From b0d66aa2bf61f1532f79858d0ebe46b87fab30a2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 8 Sep 2025 13:52:03 -0700 Subject: [PATCH 0095/4355] Add TS go as workspace recommended extension Not enabling it by default yet due to some limitations / bugs but want to make it easier to try out --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 737efece5a4..85bbd28a4d8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "github.vscode-pull-request-github", "ms-vscode.vscode-github-issue-notebooks", "ms-vscode.extension-test-runner", - "jrieken.vscode-pr-pinger" + "jrieken.vscode-pr-pinger", + "typescriptteam.native-preview" ] } From de5019f4eba9b7648874c4bc89d5a587d9bb7bca Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 8 Sep 2025 17:04:43 -0400 Subject: [PATCH 0096/4355] use stripped down version of PSReadline for screen reader support on windows to enable pwsh shell integration (#263702) --- build/filters.js | 3 + src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/node/terminalEnvironment.ts | 4 +- .../test/node/terminalEnvironment.test.ts | 11 +- .../browser/terminalProcessManager.ts | 6 +- .../Microsoft.PowerShell.PSReadLine.dll | Bin 0 -> 329216 bytes .../psreadline/Microsoft.PowerShell.Pager.dll | Bin 0 -> 16784 bytes .../psreadline/PSReadLine.format.ps1xml | 253 ++++++++++++++++++ .../common/scripts/psreadline/PSReadLine.psd1 | 19 ++ .../common/scripts/psreadline/PSReadLine.psm1 | 12 + .../common/scripts/psreadline/cgmanifest.json | 40 +++ ...osoft.PowerShell.PSReadLine.Polyfiller.dll | Bin 0 -> 4608 bytes ...osoft.PowerShell.PSReadLine.Polyfiller.dll | Bin 0 -> 6656 bytes .../common/scripts/shellIntegration.ps1 | 15 ++ 14 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.PSReadLine.dll create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.Pager.dll create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.format.ps1xml create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psd1 create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1 create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/cgmanifest.json create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/net6plus/Microsoft.PowerShell.PSReadLine.Polyfiller.dll create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/psreadline/netstd/Microsoft.PowerShell.PSReadLine.Polyfiller.dll diff --git a/build/filters.js b/build/filters.js index 1ef28f08b4e..9dd7d2fb2ed 100644 --- a/build/filters.js +++ b/build/filters.js @@ -64,6 +64,7 @@ module.exports.unicodeFilter = [ '!src/vs/base/browser/dompurify/**', '!src/vs/workbench/services/keybinding/browser/keyboardLayouts/**', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', ]; module.exports.indentationFilter = [ @@ -107,6 +108,7 @@ module.exports.indentationFilter = [ '!build/monaco/**', '!build/win32/**', '!build/checker/**', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', // except multiple specific files '!**/package.json', @@ -184,6 +186,7 @@ module.exports.copyrightFilter = [ '!extensions/typescript-language-features/node-maintainer/**', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', ]; module.exports.tsFormattingFilter = [ diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 2b2af3d524f..b942f71a8f2 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -718,6 +718,7 @@ export interface ITerminalProcessOptions { windowsUseConptyDll: boolean; environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined; workspaceFolder: IWorkspaceFolder | undefined; + isScreenReaderOptimized: boolean; } export interface ITerminalEnvironment { diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index a753b9af371..2ae3ad8740d 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -110,9 +110,12 @@ export async function getShellIntegrationInjection( envMixin['VSCODE_SHELL_ENV_REPORTING'] = scopedDownShellEnvs.join(','); } } + // Windows if (isWindows) { if (shell === 'pwsh.exe' || shell === 'powershell.exe') { + envMixin['VSCODE_A11Y_MODE'] = options.isScreenReaderOptimized ? '1' : '0'; + if (!originalArgs || arePwshImpliedArgs(originalArgs)) { newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.WindowsPwsh); } else if (arePwshLoginArgs(originalArgs)) { @@ -121,7 +124,6 @@ export async function getShellIntegrationInjection( if (!newArgs) { return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs }; } - newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, ''); envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0'; return { type, newArgs, envMixin }; diff --git a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts index c54dbe2b584..2619fe390a9 100644 --- a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts @@ -13,9 +13,9 @@ import { IProductService } from '../../../product/common/productService.js'; import { ITerminalProcessOptions } from '../../common/terminal.js'; import { getShellIntegrationInjection, getWindowsBuildNumber, IShellIntegrationConfigInjection, type IShellIntegrationInjectionFailure } from '../../node/terminalEnvironment.js'; -const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; -const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; -const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: false, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; +const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined, isScreenReaderOptimized: false }; +const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined, isScreenReaderOptimized: false }; +const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: false, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined, isScreenReaderOptimized: false }; const pwshExe = process.platform === 'win32' ? 'pwsh.exe' : 'pwsh'; const repoRoot = process.platform === 'win32' ? process.cwd()[0].toLowerCase() + process.cwd().substring(1) : process.cwd(); const logService = new NullLogService(); @@ -59,6 +59,7 @@ suite('platform - terminalEnvironment', async () => { expectedPs1 ], envMixin: { + VSCODE_A11Y_MODE: '0', VSCODE_INJECTION: '1' } }); @@ -91,6 +92,7 @@ suite('platform - terminalEnvironment', async () => { expectedPs1 ], envMixin: { + VSCODE_A11Y_MODE: '0', VSCODE_INJECTION: '1' } }); @@ -240,7 +242,8 @@ suite('platform - terminalEnvironment', async () => { windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, - workspaceFolder: undefined + workspaceFolder: undefined, + isScreenReaderOptimized: false }; // Test with an unsupported shell (julia) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 0040db6b3eb..d45bb91bc1b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -42,6 +42,7 @@ import { getActiveWindow, runWhenWindowIdle } from '../../../../base/browser/dom import { mainWindow } from '../../../../base/browser/window.js'; import { shouldUseEnvironmentVariableCollection } from '../../../../platform/terminal/common/terminalEnvironment.js'; import { TerminalContribSettingId } from '../terminalContribExports.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; const enum ProcessConstants { /** @@ -151,7 +152,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @INotificationService private readonly _notificationService: INotificationService + @INotificationService private readonly _notificationService: INotificationService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(); this._cwdWorkspaceFolder = terminalEnvironment.getWorkspaceForTerminal(cwd, this._workspaceContextService, this._historyService); @@ -299,6 +301,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce windowsUseConptyDll: this._terminalConfigurationService.config.windowsUseConptyDll ?? false, environmentVariableCollections: this._extEnvironmentVariableCollection?.collections ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined, workspaceFolder: this._cwdWorkspaceFolder, + isScreenReaderOptimized: this._accessibilityService.isScreenReaderOptimized() }; try { newProcess = await backend.createProcess( @@ -501,6 +504,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce windowsUseConptyDll: this._terminalConfigurationService.config.windowsUseConptyDll ?? false, environmentVariableCollections: this._extEnvironmentVariableCollection ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined, workspaceFolder: this._cwdWorkspaceFolder, + isScreenReaderOptimized: this._accessibilityService.isScreenReaderOptimized() }; const shouldPersist = ((this._configurationService.getValue(TaskSettingId.Reconnection) && shellLaunchConfig.reconnectionProperties) || !shellLaunchConfig.isFeatureTerminal) && this._terminalConfigurationService.config.enablePersistentSessions && !shellLaunchConfig.isTransient; return await backend.createProcess(shellLaunchConfig, initialCwd, cols, rows, this._terminalConfigurationService.config.unicodeVersion, env, options, shouldPersist); diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.PSReadLine.dll b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.PSReadLine.dll new file mode 100644 index 0000000000000000000000000000000000000000..2b5bbde4d4b77fcd89b27788e751d7146ccd752a GIT binary patch literal 329216 zcmeFa33MFAwLaeJ>FJ*7kz}<->d|6*WP?OAi)=|4WDnp42w=b%V-~a7?AUSKW@Hoa zSVAn>gTo#|Lc$WVkU$^=2w4b_g#)t?6`oD5jK_cPb|ICaUC(;e^)l7(>{w;|bjdTm#>N0I#u04hktHQ+) zKBg+%zvaTqw;=rFKaeTnxU5R%gkREmKMKC7^o6Ka$NfqQ?ImVj5d6G>rzP8pb7eSjKH*DzFci*=8_k7;dI~?VfKM z0n&k==bIx#h;xI3@Z0kksJj{xJ7?HA+YUQGdb!U}>@=KA!%UJTX3R(#ZaK?2bBt&! z*xYV*^jqf4p_yZ-5UV#I>C1YhbIZ4OumF7e{EU&DA5mp8ys;UG4-ZC6cnE%48+NrH zh8$WOUZ6`b3|g=dKcnw64a;%;RwIZ*@p4Ue=^jwhGuHaM8Zv$+iL#spfzV$(bDZ*T z+hwDX?UBbV!wwdKg^RQWT(=ww&Z)pbWM6=LH}3=}qMdE<$D5EaK%Lm*KJLaSn72nh z4;Gn`9rR22+<<(LgU8W1n-lk?f-WS>IcCs|YbIzTCJ6wGsNT&1O5?hA@c_C%9QV0c zY8?7tWpj2hTOs;FOpQ^>9@Mn4mVb9OaKsoT$=Q;}80y^0MFa4(14wFTAQ#DZAg<6H zkC~)@wAPhmuFb60x!IWA^XfldeRZvC2PsOo!VG?cYk09~*c(RX;P<4=3Mo+DWiEmP z-|qPh#sIG9`kpRwxELjpB3HppCMR|}lFQ2u$=87XZW`@IGBD5p!jI@AZ-w8I3O_EH z84^WvLV_}%rn$l=Io~l>f)AK_W)E(j?C^u7NYv@eHGpQ=y<@0RD?m?kb8}hMM~=X~ zTbzd`FpF1GLmUZ$J#rL&x1~m2gA2ELCs=ZwqIIkF)d-T{+o$VZBP3Zq#=~XHaVX7aWZe2gl$iZ~7TCpNiz$RJ*u7t2m`eWz3wb$bdz1DlUgW z3iTD`a-gGnW_T=kQMW}m$f6LlAZfm-)!6<43Y5(+Xlp_i&@{JcXQXeZ5zp-x z4`u_sVGN33g(o3qQ-9oyhpTXxwd3K*xH_rRQI;L+2Ol&;%v5fM_17@=Lp2)_45inT!96qR1;dx#;M)F%c~O6Zn{7ddef8BVHw-#GKZO_@T>CN9SvSVf7E~+xzBh6T z*g|5zaric{8F?OqRnqb-W+jQfGx`Lo-1e+p_SXDe{n-Tm#m?qd zXO|BFy9Ms_rX~^{^BV17e-zO_Y&D7%wBxoZn2X=sf$^@jxn(hbzEgZR8Y&wt-i1pt zR=gJ%FBY5wuEARTWP<&GPsY9Y*gB$}i=P}qe(*($M1+(=bTW6`+L`7Q%}lIqhmUXLpR9)FGh_kkPCp`3?tZpsC+{Pcdx}=W2+f##MQMyi){TmWXv^2?xkzO z@#jY}sl*sm-zs4svHXD^4zxyEt?=3g!;X8F?Tow*>D$_ZBnTNNHPEC;fky;8fKIFN zG-6z)$}2q4@2oHqMa?#3gn|NFxy5|%hgTqBc2FWvAWQBbwUwF-HzCvT1f*qZZ%N3b zbgAI{`A>1c4@)8h>BRdW1Ewuc#ZWGME6Jb*7Fr7?%2k>b%lRrQ*JVtKxd|M7%_=?z zH3}a?d=-n$VA026zrso4p}Gw7;b}e&;;RE5FpGJU{s-{##i`|0DV_Uc`!+-%E40p z2?QWuN>(F8MrfSTS|g)5u&`1>b5cY$tE374RBlYOQt9Q!r*c0Gp6C|R_DZLH=Gkhr zHn2|HPS6T2N+Gp6_yePry#0aAk*#eQV(z7%q}6Jrf~_cfV=OhVArq?r11P-PSP91_ zRTaiiYFNlp>PWdZEAq#8FaPt9A6a^)lD*X=xdZBpWw}jmLutXo-wC|Q?OR1Y^sS&q zL?~Upij0#ibW4LN2_uknp_cURG%}RM4XC>7*bmM!{i@u8|3Ko203)$4RHXjm^-|xnoYDeNX5D~RV>SB`{6J+W)9|1DkUN25#_#(5mvQtr3dR` z4%)#Dx^5BO(agtU;pHe*7K)GYwYr&|m>Lf` zl^wnjjQLjS;_MVHUF^(kU#=*B)c6LIg0FQhj45<7SurHuUF_ z5~6y?DTeIPY*XAH+%Fc7#cv8m5pzdXUBpJlK$*U@C>Pl_;|+!ZBLliy8BfjNa#T&9 zS=vHXd`hh-ZzoZ%WKk$4upvsWIqmX{;y1oB0ETAiYNYh%wU{N$2v&$DhZSgM8&YQb zC`Fn6=ry=tC6{8oi{wJHMAJQiN~7kCIlA-v~-3GV*nrDI<3HCIs!$le944 zDs?1~X(Jmw!a`t??TtJH*k=mCa!(WR0Cxn|?alpg3BVi(YjuT#oo5om(#-fFM(t710R7os+2f|J9 z;?Gc@-Qo2JJnmz{)OBQNtT>pIY)~ zRJRcP@J3LQwr8hLUT_Y6k64hx@8ZI}fKagT9^4E+BcWr~92LbKMjqqC>W}W}57-kW zmf8~a!);o=4x^a}a{hv5{GQssobLON%ocIIM3U_f#A@|N_w&;Tm@+GCuG99uE4FvuJ#r{?Y7`KL~+{XNLy>KxY-WL^#bs)!&@Ov+U@sY zmz8H3ir}ewaDtIFnP6h1@=_I}%X%Y^sD3gE<*}{NF}g%YucS+}H9C%~i9YctDLm$V zH$bP(X2gC@g{P6267VH(x0ybme~#JH(PJ3>on}ubU9sEJMc265(~T={4qR=f=MrG1 z0?bl?*=4{7OxER&f!St?bvRubN}581sZLs*9tA7f*NYe^voQWm_(Y(uAO+9HKL34z}Q0a^@`>p#l+hAjcedpqc^8NQp!6CdigY0tsV?GKBDiU?yR zKnHd04x6i^QyB3gH-m*xBsdXp-3F&r0;!M#kTW^oGO&O%puXP7GpfC>qZTgF@7U@1 zshX_cL2Z2p^6VX_m>_``#{`kZNK5eWeTW+BRw3xdm&YrhyR>gdfsG1wIEnYM=LBm6g(+)l^*Sxv2EiPl{%{$$A4qGogc1r9b z4B0T)Ffh7P1Up{@hMWyC!pG`ke8ii za4I#0lWTIq51`E4u=(gKPlL5tg3*Yjd2dxzYRKim?UlO-;6#FTNd5{&WK2goeQK!P zl%TY@6Pymz&P%cVXF4fPvhxCkXpIDz53q5^_2>^|obZE?!wElx-?Fi*YAVEZga(_z z5@chh<1;T<4x??8aIv~b}Jg9j)-*kvr&#}Gi7MN%H;A0!Gd z#@e8d5L*?7id1Qqe5@@QedR6OvK&JdoiIy$Pu1P%_=BxZWn$s^V<3uDJ5P=IgKcX0 zsiziaQX&`boZq)IFX^@;OS5!3W&BR?hPM8Nh0}3J1q5y@Z$*BO!DWo z~zluB0Qh#D{m{Heuq30!6FgGtgYCe<%>GxiB4&apOf~Z1)op z9^Jc!Fd<+#Uqb?0tm+o4x~*0^PLB9pg*tL30$SZ+dZ!VFbzku&tE`uJi~ehzh)&EPf#rl5o%*gD8r0z9Gzk)?ZuQU2IU1FnOq)UVc$=F0{GIr+Z7nm~o z3@$w1VPd4SQg7sOk~qQ&2fg7LuQ8SzbYw_mt&rIrE5PGW`g|-dcE$`0i8R|=QaX&2 zpF${UWd|_{($q*(8qY#jHxC$j4^tdZrHYV?*+6_6P^7?+;pV@H&3LR-5kwncnNZ#X zTxBf2LH4()eU66g^iNb3hDfJcN({7!8kcRb{$>dyzt4bQMuk%0XAu%wYEsT`7lGe_ zs1IxGYYnLFJoaVsS!?WDIxu?-X0at%wi@9R-~jn8IvvfjttGb0&%pq#qGgdG_w|Ue1m))b5S)p$Ek?u)Fgw_j=nMlbrX9Qo0o4t%oi+wJ zGtxO3VCJxEdkBq5M>(id)M45kc?yMh?Y8h~T+QIifSaZDB=P4#z(Iyt+K3SCU_dEo zYNd-ADUqJHMtRDnsY^}TeK7BMFi19fjiSwQ+@K-s9CT3hLgSNmD(SQ*W5bpWT`PSV zEWKFaP7Lmm@EfR?WV7it<3#ElBu`>)Az}ey(`)2S9l(*D?9vHqz zX{8BLLJyCFLf1jKk&>}sCYU6fyr!}5mHTfF6Wh1JW%v*an$z!{AXp?7 zzC=Q{i;$ZNex)=JSDvg@BlQ1Q|i0GtmdOr6wCBMIT8<^SQmpIb%Aux?kp5M|i zypCg->yXD~5?tT#YY+pmQjN8OnO;*c3u-dl$MGpY+2JQU{bZNw+})IuovKJGh>vj7)|IrXrDKl9oUQ^fuM!Ont zqLfSxcM?A#`G>z16{Z5gGr;DzBeg^(XSL4aNtlOL(g0M|*$_E7)0@dNk3VpT>CNN` zqgT}z>OA6)Qb&|#$UAC)#oY5W_@g%8g)FJglBra()3X>|d2U&U9$mt@4yF?dC*y)~ro{RZi}jG6 z&z#95mN^u3yhQjM5}OX6=f-lr7w5BHPmX-cM@Z(#SApsU^?AO=`3e*L5?GG`9ZF_y zl=aRAaPEskSR!FvTQC(s+s(rhHe5_9Bh$ZeDYLW)=+PQ~B_LhJm9 z@mpMT(AfyPq~zeo(uQJAU9rNo;XWjCv#s^Z#%S&^y9n_&hc5t9oM9T9(Jkq=6St|i ze+Mfr3Gat6gTfgw8iE%5BzA7KBxkEFv2*<%7>yvI#XLffQo?va%0eHK988DM!HdXF zs*5bCD%)pIYen$~n~B21RQ=E=jA#$93mc95ALlJhURxZ6SgZ}z`5YNvTNguNa>2;A zkzeoF1b#yl&+QG4g_iNHh+&7XrB+~(QX$&2vXjOEu|LuIjehW#`i@xGWC9!4-ina} z4h=Bu)r4|jTcgxc-YdR|>Z15I{POAuRuwfCb~A26+Kjyw+hDXU;Fxi8l~BG67N0fF z(`BI^=(Z)=)M0NCL0GI(gA{1;F$A6mhl$&HVenm)k#_pS@8QOtzY;5fN!+2%V#W~d zp1p)nLdWliyEhPWuoH1FIG^R_6n`=4*rvP{fKxGilL<+6E6yqV0PQ^-yrYU`-sUuBO4{)R3aM%MYg6mF~vTD zF?J_0J;<-f(=ny2y{XP}seeL}{ufC~sW@rrrz9x_<9_%v+*6BCRUSWf7jcHT5ASPL zQUXpT;!+UPCe>y5bNnQn{G9r3a|~Kjy3O!%STaR;6Kx*5M`iu3ea)H@a4N}~f-qsJ zX!riJtSAAe5>_01zhZ(pbKkUz5{%K0Frhn6c8{UZSI%S<@Z8DdfmZIB78w!O7xv7>FUu*auV% zJpf`w7pLiWlz>x74N*{P=bCE!%Tje=!v9b+F-+`hT5+$aI35^fYM zbL$-Yu;Pa0#J-i35^yTvM!_<-uCe)-D9530yCP=N7x5s&J_O3k2cOMD)cn4hO$h@H0#M zAz5nD&89?7n>C?CwULiugBP1j^L}jNRJp1csY#za((KF`cdfRBFDD80R7n z`e&BVGFHb>Lk4w0x7bUfi-=q@Y5BUA1jJ0B_8qt!k?^lX#dM5H7Zz@SdD;&C4pI@5 z(Pr`QMIPjPN%>*via;f{A;KN;j zy^Oe-eRPaoMcj!b1D4%6c(0!kTl64g1}{gq+i1itb_3rZU^JeFdlU4zL$arerN6P{ z2%{C&DEzi9V;HHB)El`KO9U4u>}xS5*yiXuxEB zh~3Bg%kKq`1>m8&8a4R{BtTCKv7_TI97ntzqFPacEUE#|9z_sHRN*^EEKnL_BT-zM zu=_|jKeQ3*%Qiak(7|08!%sYn<2Uz99GF&)N#R8=yU3>GGfK&w&;`<;k2Q%yZa9WK z|8uO-7naW~ml=m#A@}c9!f-F>Fxl(e++|0pEV{TjWq+Vb=paFoRKRu(nCiwho>WR5oD|y88u;lYoz$^EIJpS z?bR+kRCg`tH}KZ~z{65sGzKg}DGou|eC$r1WQK4La<`rhD2CRyh!esU+C9k$JzUIP z4R{bhAUHo^X#RQGPi~-dBho|9+{4i@81u9Dj7dp&Ba?J?y zEhHiOeKu*ch>nQZ--^R`JY&JMumkA^{{xi9SQTYnN&+!XvK2!MtV(>&sy0rFtXznVFC((D!n<=Z)V55KO4^Fi4uxL$I|#3!DanT-8eVpSS zwpviA*|^WDod$w8#V~U1AdMfn4V{-3%XXLsX!LxHxNtFvmgvR~dqA5Qy#V)dFS^Tq z7mdPiMyFODe(kV6$kyLVDw&^)8)~?u>$&+Q2%JZOgqK)!6&h_ABN6=D@R$p$ccbS7 zCFnUeBT*R%XAulR3?p-qp*xJ#sgX)}Zfh*q4k|X!==MtL7HQM~*NhhnxwLP9IN^Sr z?4wBLx#T`;FT4}?exl6qMv6&<`vX>NK|^ru@q+upaSBYCpXV)412+xyDXgwslu2k6D$kRvF~`|nCIgRb z_7a)tBRM1=U5{XyN(MV`G?ocuwU2H9B8x+Ml2q7N4DhnWSVgbrJtbkOI-99+I!AS) z$+MH(+cL97a&{!uKPMYZ>s+5*h|OK7w3US~CS9veNC)=fmtS$qPN)sm+6hnQyAvDY zr;CpcjSU;23-r&V<9PUSo23JgDU3(DRJ8!PQ&S5laUP_OQCUC9u5Z;M%H+p+s#a2F ziJC)}Q(#?*l#GVg@^~+nbAFM;A*Sy~N>_6%O?ngpJ!gHT_(qhy?w(g}cNc zx&O7}VFy;AMD_|Nf_cXu-0D;*(>%C}Q;RvG0l&f;BEM2uJ)Fhjns{2GVH1(^iM2+; zdB`abAvo;7BMjSkWX(4_@gcdh#E74N4eF0XMg7qQVjNN}#{2`ZAx>+7;20`wd2w+p zY^+>_CS1%nH~_hpDgbRl^@Mp|d>>nl_5jl;EFT!jJ{?R4Fa`C4SG2h(PblDFg{t&u zvvT^BO1}^3EMru+4=foge{x;>h$5(y3x1$tGyoFXdo^7|c}HQ>S=IGi-GsmpZ`2iC z_SB%m_o4*Sri<^Q+p1(l>Xk&{jpE$;ns{zmnBpudAJB$tQ2XrPkvqIv z()>QOf842Ue|~%tjsBWyA?FXHpi~cX7E`4F&?ymt6d)hz5TmO~2joGkO4G_v$^)rS zF8sC{RT&Yyod~ybY}Htcb#K*UYAV_PUbo)zztpYjbQu0=t?fml%dLsk!;k4kj4rPl zp>4Qe`~ta21APX?K(}6uQkOSNxa0OpGhw?zx+&J^)J<)$qJEl<#-q!JR+$z(!P<}6 z4qJi8<06lKIOQf`l@$*xQQ7En*+!^sp9VN=#H4$Q?9t`$xa&_(VEh6Fu5fKYlC8B!P3S)s~{HJLc%k*67j^Obb)QOtH1`(*%-|C*0 zUNV9w8{9@dFF@}YLJh_71n8GhsqiVG-EH_R@c}5AYd8eG2^C*C1kHJ7jMruD*yeZ# zqOy-L3YO3AP^mKN9gMk)2oED1E~MBSajNvD0FQhcaX(vH|2M4SBf^a=buZqanYngPbXl zqcx<_o|+MP$snV78j`DnkV_IEP(mf{vO3831o8z9Ijat`Tx9vChHR;Wz;%i;dR{|z z)InAYmcyxB*>>VjgF|ahVQs zs6d|5T-xfm;IxUje62iL6lc~!4j0Jt8q!w>IZ_}W&>79IgB&doOsPs=R@Fh43gkr% z*;WUEiyVpjE)CgH2k90FCMT8A19g!95v{}ago1pj4)TY+L;kpT$e;EO`Sac(f7v_a zuX~65ZSRo3?;Y}wy+dY6158)Da|Lp{ZlfR8HNY;(=(8H~$2tfOF-RFM(U7_B)J}4` zaJg7RUQ-8IBM{g=N*OMygPb9d?`SU9*FjDYS-z_w57t4>6fQXCQ5k)|4zgAt*J&cbJB&Ez`l`n#n}FP zW3O;Ol=8yLRaJ896fqR;sJ)d);ov7rCC7$|;`iY0`O$gx<_(YQbE9(Ro7d$Y!MHP*JT$`@-D%J)`{TQ(Ipau{sCQH-YWOi0d87{}l+{|K^^ zmEE`xbML>#wEWjteE%9-?O$VM^+@HvRV&e^DV5(lHb)?D(~z6%Ape(aHD$PCe_wTr z2kOM#2Xju@YA5MJKU>E|8Di5JsBl#$)t+vJun)FcAY2w{SzfM_1YAvs%XHSn|FdDSeO0VC-2ZJh?5i^8Pq#_Y zA@cPEwW6v7Cc8FAJgKVUMKGVrKQL|EFxN^M2G4UZK^9# zTp(}=QIpNJbr3@!@DWgu_fCOKHUF1Y%>R8gulIGCpv?N)^i1-ZDY7WDS?w{rQ{#TR zd))t7vmTBCd@wWCCAGij$;jjd0Pp*|y|yy?OM4%ZaHdPcz;tWFD+My$c=I-aOgEU| zX>ZDCy1`_JK&DgFHwa`pMcpotJu7POn40~k>*H#@G~K}V70GD2!Q|P!LBhkaY#Sq+ zCeiEEF4d+pf|M-_BVBbycgEC_S(#GP86L`%dav%%!4xhmoh&NJ5AroBdH036@NnFj z0ggQ2D%QE?sM$E6xC{%jx`7reCs02!PFk*fMsgJ{Tow}#+={WbH>n)0Q zAFdF3%~5pRvjc(8Ao3nu4+W6!KZ*P26`jJZpbtNWn@W}^OTkV&S{S=e2HLdIPIzez0BW9uPOY5h(|JUflD!9!71dZp6?H`6 zcZ^t7c$E;_IQc0BhecQ;JTIhVx=GeAI1<&cV6KxsQi}OkU|aF`X-;!|;R0NT2bj@{ z!ZIeFTiC+a^@Xb_vW+7*Q~s|j{EA~X7d}8Se{H2wZc)Uo1?)H?d`015ihKo;RSmY1 zbYrPh-}8-zaRL4w1!M=VPWrgQw>a&}!g_>j1`aswyZSa^wfZ7L98_!JN;O}Tu*EGK51Ru-P->&b;5 z5%MlhzJ+?+1yWv^@W$2ny9O~64Bb*F5cD#JzP`}O#48FbiFhvMRH^yGpE>g4!b^PJ zSa^a!hZ0DS_N(L_nlLYof8Z~LNc#I0AzKNltBVKeRf^}yg{~Pu{02YN-{Rk#6RN4EZ(Y#-zDM?nD|Le{0b4j%0#l?h3BJ0e2j_Y0U^X&hCw201B3{Qt zI+7OTYdMUHSK%``jCcH~@Ntxg)W$5%)Wm~{cmxv%HE|9R`h0l*%lUPlV*j zT5mCtR$xN>Au0TSz*gC`f&%{SH z^7TZdFX))XuWBOp;Sm0z4xhzgI^q`@S5Ttkm`JW9LOhU&2Q%@{I%OLX=P;4>3IzEo z4N{Fvq%AZd{*{P-!jH*b07CpWhhttD^hFN$>+mC#=xHX>L5?K4lZf{-akWOijfl4~ zahoPy#^F14_#zH}Nrz9RMCUW{mzubg!>z65>;?#QI1`68aW)ZY_(m&gVw}U*>hND^ zLa~@gCkP__Pl@>7_%Vz3Y2=O(s{ZN z$2j~)&1o}%N=%%cEQ8h%@ggR^MiY-D;z>;0q=`dBJd}yFl_Pn~CE@{0B!3JcHW88R z1+z#8TSELh86W?^k69!~6CwVBh(BfGtvaI#B7TdBv=1!EhluzD6Q9t?_Ym>JO#GoH zzKO$jTRG+H33M$JGn#lF5kn@f(8Lpncm@-TnmEj1atIPZC_KD?iMMJ{I}v-B_$f^^ zIZRt{l5!UriGRb7S=^~XKOiC*zh;pR3kCTC5x>DiI?EQ~r-}F(Cej{^5N{{q2bqZO ztcY(Q;*CtCEpS0@CgLa)&Gs_!Ob++x@QEBgT!$A^qGOnNmL~QQafpfAG|}VmZ93dQ zpk^jMs)@gbDP$I3)L}Th7{fnf;_o!^IU>Hu#1_9S=x2!dIVRGYSBmsuBHkxRtx~rU z@fIeMUeIYfLd6Hz2ZJeG*3GVxhmcTjEEKTIUo8A+ZdVvdRZT27b4 zD|Gm;u#e0l)|o2N&pEt9hj$`8{9jCbKoh@6#BVZ@9P}i+2RTgMcoM#oK=(88YdYoI zh>NL^}2so=9U1&toFF-UzXi zh+Rx1{|X^mM2s?#+$DtgTUt*420v!8IaMZpjfgKXagioIPDJ!RAn7DblE0sb4>9o~ zjeG|Y-^0WkHE|me-^9cxH1T2%|6GSp50UUXZOsOfr!+D24bB>~Re5!Vk1TC;vGSf5t>|YZ2ski1;ECZ`Vk?93T46#E)y@ z9Ynm3iO*`{n~8V}6Mv|QBSai$BDq0HM)=wb^q+};)yU(Bcq$XgAybgU9H#RI3FBEY ztpAwU+F2&zs|6_B3aw000yQ!*qd|DuAM-zcP+^*g7tI;Nc&8qSd7BHqNr9h!J45$WTHX7NLscov7B*5T6$w1$b~uqt^L zI83gm63!CnASS-7Q~E?qGx1ifG7%!WOeDtuN&ahWbPfLjKW34fZG`v|5x>vGKWd)O z67e}Ewse(|j}h@vCc3&k@Ul&?BljL*cP)W#U}8q69N{qe#|j8fUK+z2nMf!2LZt1v zVSHNw><-l_7jXD=9X^migG}6_i7iB&#l-71@o(7W8;&vYqnh{=BK{sfX7S6Kh_~4w z{39LyJcsfAekIt$l;{a2&eX&^iTGhA4r}5~M7)`aU(&ihM#L+bxLPCE6LA9*H*4al zM1(^yqtNIp{wVpVdTs917z<6aTDX+3v1>5=8~m8Xmo@R5M0|;fjkC)<;f9OxpNZs3C&m0Y5g%gWp&I!c=Ct`RTQZB}Q7XZ8*^OrL0gYdiJsV3R9lJcc!YrOuZnFI1>|(R{v<7GJ zU}llL_k>k72-W!^e%?O)SDbbv^l+h0goo)6JTf6W^)r| zb1Y(d$0;W0L^kyG$!s#(p0wNXmi;I_Jd?;Cf`?}kx8oTT8xM{okRd;X$M1T^!`u#t zO|?Y4DiBZ6;6ZMWo}zgQFQmr<6rMflIg=fe$yoPw&)|5W*Dp3hXuJuj)ee?JjJta~ ziXBYp#6#ag=_>XSB{&Z8cq~T=beaf+$EL#>RDL|6!XkOG!45pE;>Eh;g^QKf_$Hh2 zB?|DsOQ4##2)K~YNO%IseKi8#C7(E0bQ2!Mss5S(H*5GN9NIKGF@E7}jNfI;@0)ex znQQvs9Nv)dAWk#FM{(aeL%l0kz2=aVg5U05^|s=rGokCy?u+oZ1q}?xECWjp{zuP? z;sDP$3OdI57Y{;(@S|=Vly6t@LtrlJpnBea2>IYG$?`ZD1e!xFbPf3S#d0fSrJXpj zDw5Y2Ykcl-(b$7HQ8ZkED#>MpDW3G#Y>F$9w{WRp=@eJ%h*)j`Du?*1>_WWPzz(iP z*7;MM;u#uvMQxLuFD84}PnQfH$oKkLZx9hbz!7QVgnN`Jx*) zRub=*92IIA=UCg7fEy%5gg@S=iA#O43 z6w}j9^ffpG$4UGu8<-9BVN5Q3j?X_BW>ENff(Eo4HhMicff0S&S^v0FC z>gbTGw(3d(nbi71oBOp8MkZbO!1h1v1^x zjHMyTa-kNNw)<*jd9y%9G=#Q0YawqD$Zs`-&ZTQ1Z{52r%BAyB&4o@oYq`8#xUA9; zI!djDTqh8`h(t92?RnQib_irTN5yvt1jDc5Qt$3~y+FRFn~_e(Ycsk*APY65{t0#E zq&l6};n_cGqv@LQW`Ufj#if1FT3PU9AaTJamnsn*#nnP?70Aal7dkGhg}||yxGc~R zIwz=wU=NTWujq_u3%(ZeE`bbaE_CKx3%MN-ye0H!nhTxM)k5AaT&Am+_Xq^L1FFz; zG+)c*4uM>zA#|Qm3wiI}GkV|NA$RT_a+g3Z(z4K=YppDI3uLo~(2+td+&ctg z^4>DS?%duWTlWq@@7Noc*Y6#2`Q9Pe65Crw4+&&CtNG&s`H^mEI`FG)04ObGv|6_j z`H9s+o)pLrH5YPfs)al(5S%F}-K8UiTFA3|hkQmLr)XKo0j!qGrv-AphR`uZE#y-I zxj;kcV5t`JZ(BxxRb-jY#Cc92)0sG55y*O7B66asEz#ow>DLfCx~hddBanF-LZ?Qx zkblLhe;ngV?RuRE`@CLPX6_m-C3yFS3Xli$V;mhqHL0a zhLGn~EkxP&)6EWK+ruV#N^{v>X3xMqDnalx6&6o~3BsBQ%z2ZdTLs=J`k6ofoIYavR}KcOMy99j!e ziVjmpaUnO&T8PrWM>K?-bZQ|=|E7Z|{e#)1G9tI9S}sceP<92Ox)>N=g$zsEI%90F>&DE<4ihLC?(EkxnT!=qw|)P^+r+FKWon9ie1gQcT6ek-!z2$Olu)ZS^usfa?O-*GKO zDeFHp7jmtvg(zjUG#B#5tc56L-KDu~no|7UF{P}pYRHN@h*H+SY6!Wk*Jh-Y^)DJi zzGt-%rL5CIl(OP{RMiVP^3`%t$_i&11tCw}T8L6syy{*-$n~fiGG3^(xvJ+cXu)ru zGM$X$q0_R0sCn=l4JMcA8qRo#)#68T$MGPlx?7AplMfE~S!r{O2>f{E7=gx!>lmq_ zJQd0`vP8yc9Ic|!SUN_!T}3069i!qf4R04_M}a$W0zHZ;2e0U+-_#(!OhPy53kG6l z+U2OsseI}!N8eW=lD}~Z_Y|C0N9aB5{H|z;-iT;2i5U84hb!O6)S(&JH);h zzOyL!C5YvB9Zb59H85yNA;RzaaN4(!vIIfQ7@GfVP#w=0ph^Pd@ zl@N_T+7q6HyWw}D3M3w1vLrOTH{g6gDES5$!?GiS;g7MXcc9{2&z?6&=r(`V1r0+P zF(@_3CC@Gbk=UYmbdf+6ZiqfVD=`%zZO#-at1ISdu28A!tH4U`bbC}HCjIbEjD(qU znB-R?QbMP;x9EHBRc%iU>2piAG@EK{qLvd;8t8o`4f73ufixjrGHHk3MH5U_pDI3O zsff>7@Hx8dvr>`I4=0F+&0kAQC;FSf6wOR8yLPk6MMjrbTv;lKoyu)FD=sH|UG^;V zq?Wl#QX4NBzLP4D(;rx!zKG@L=5Q~mq<)uFr>n@;!DXZ`Z20)X1{d#pr09&;oe)%C zZ~-E(5%KY9HCh`&ShfPT9VIN`j!}75=@)&29g0LP+3SuTiHprcfIvhYrB8TZ3FHyf z<`biI$@H^S;eAvY^qun%{4K~KC}u6nEy^z3e7rijN&wXn>hi;2MbebNc|iA*Hb&Xx z9>w^mYLlozPJt+^7b*P^2ipE%Qf{i=wLPf5UoRwyuDoiTs*pcp(ibWHfH}nECfjK5 zSTDB|smaEeKWN85DRZ-9xARv%n*r#VMgMFF87mhRjv%=rgp;M zZ{YaP8~G!K@ZQK)v~Bmj^pj|9;IDnTqa;?ZsLg(;8q^{cRjEn%09OTgMU6QPhOwOL zBG(+*`gQs+h$=cK?k&MLjM|ef!j1_;t2m$pDCgZyA0ShCw~zkkZbcZy04|(_9R)?6 zO4yAEN52vEyb42UY?p<*lX15eU#Ro#67ino9PD0hL?KOZDQ#)ic}?DaqhC)f;&*XoV2$@ z08}4cBHgD%}V7kZu*F z`;bZpPk3sFEmi40yqk1O(GK*lzP*{^^JvMp{39m}XgRq5`Zl8(MzeRaezPN8%UP`XHKgU?_6D1XT~ z#AdNwHLr|r1Hbmh_NMlv2w#GoW#OB64-#+eB9_0#ggyda-;q<$XW_>i4Nt{QYBK2# zpN0VkA9Zp=+_+w?L5M#hmTXKVn}(ky zY9oKz3x2n41a0+Nx1NWgEIGrQfzMsFZatR*EnZ8$DeHSP_^V_|FWc<31TmN%xQ(YJ`RB*cOuV44fH{}MlX@0 zkEM5j^0G?p&u{j)ZavpO^e*@nD>|I49kgSteJJWVkdN|?;G@)#({=p$_=ab;Att`8 zJWjy}+DQ!@&UgVro4_2dvP$o@sp)UILtOAXL>Vp&4`>Ct}RFU$xc7n)tU;{fs&h#8*GZX$ywek zeq)FUm2vS`zGjHInIW{MqOxIBzR| zu#)F$EZdAzq3yw$nD_Bvz!i4!<5-A_RT-d!u3dbT2%o}F$OMs#r>TKX4zxDF<(lk2 zCswwXGPal=G_PQc=RB$t&aF)-Q5QfFu+l;I$0zMuDl~R?#^MK)jGM=2xeQW&A`}uh{Esx4PMm zigb2(E96SM{XTt6R1IuY6ke1LPB5}+yz7tHbxQ|XwIUyS?@@nUezFdl!ULb!6V~O# zRM#+CSyp`eLcX<>8x~2gfe8-7u&Z5FrVDn!2B|_yLx_ss>A7*fFsP$ zkuPjf1^=9ig)1f&wT8pisc{lr)sa`dk~WK&hEu`;r#jEmbJhp@WmKk>A%AFlnUQ()$(QCPVtsNS({&`;SGBq zj_iGW55e6JU@Gg6XQbR%4b_#Y(yipKqI?MqMvfg|lCuA~lG85Fl#)yS|GTowmd_r_ zPLuUs%TC{@#`?rqgL;=z4neQNi=dEhwg>8XG48Og4lco!zj$Q^&=(iq>ulWFjd+}S zyZP_zLylJ; zVltKB^R-&QVpnMw?Jw=Nj!V1Ouk(=i3wz9)Vyg1~T5aB}fwC#3S|N`Q6DFL>)>O45 z&yb#*#$t@8c;=*Pa+;!F!#yD?GiZEmq(g>N&DspUS4k(i}X693z z5D~h7T!|Scj~uuK0+vx%C;`;0w@@;RYysYAELT;f=VXN5)u>Z$iFjBW>00Q)4T> zkwrT|^m+O!?L{fDtrbk*LSLEXNB{VNJkb~##3q`uS*b4?W#T+LIYVvyoy@!p)p4$c ztRGKJG{q}evD`k75h<$cC1Q;4yL{i-7x^ZevdZeJmMty4V~$ujs?Yg64_5cIhtW*jPJ5rL zFhx}7%@tcEzz^$36Dm_-H)WDGp>CK^-2t{j_%kYjnr(j05M^^gKFinuJ_k&xn`%B% zwNCi+DlWft)TxB7kklmC02_IYu}b=WNgk8_qtt`zaLV6(QoT;Xv}T3*I^#`TttMa9D=r3+Ik zMx^Sq>%h0Yw$JJvV>c^?Kd0oMDjoQx78&LWI@&A-aL1Lqs)iW+B$r`qw|U79F^vyu z_?)@|6SbZvE6%z|vf-gomF2*c+$A-~SK8V%4y>=wzoAj~SfA2&Oyi*Hbd@59Kc13! zsCvs>ngn%V@vCjmL48-5-+41qKBD9y&b8BLF2KAL^e?c}2llTrc%?w4FjdyFPt>u;JXvP%OirxE z7h*O0>WNO)1W$thxx|0BYfgO4mR3dWLo;Pb#WE(NYJufNC-=67!#QF`O~m^sVhKfT#}S;x2Tcp#f{HI^eGdWftS(#u(kMi3 zvpU1aBcAriMu_BQ@%e9x3SWm%U)4!s6>|Q|Sx<3bScr3$dn>W&y~-EojEb zcAS?^t>_O@ELSu>1qulE)Td^R*XiJkq8!^^ANvuC#ka?h)~}DfpJMS1C&Xeq)Cx9} z*jfiZMzN=IY)5_ChbVR>#fEs-PnGIWC|pGm+q)=Pt?E322q_Vsj06PR`c`mhO}>v2 z19W=CrYW{q6Z<)eJ&R()r|_G@(D4gYUw9@0o&Aa8*I;muj3CLja`h7|Jcm;}PALv8 zM^GqShaaRq-!PiRHL`tHeO-K-7+r#&NcdU&R;rLf zA>LSoqTqDb3bs;FE)=hyt&5@6xwc67dBmss6T$s>)y2qVEO`lgT=0WyNJwng+T4r) zYcDsv96vL6;k3Ya*5(qy1C=DI;}Wy*0?u(R<#+|<*dGbMNTdLr%`TUoDB(r;F-sVg zaVE-yzhi9@37-KucZgi+Ow4V}&WVL3-0*UwDULaZGmOVVxH-8Y-rt1N3GD1(0Up`D zKjqBrzM|@3?4X?UR{$0rxSq0?_In2r=b+BBT>-qQ+~5>QQarQ=?=Hc>>yE%R&fON- z-m6*b(daqN?nd0vSZx~*W5_t4#x+c9!*?QJJGTeFM>ISE?pcl%Re+ab*-fz>>r+FW z^6`ea6>YYfqwy zRxDM?c4|gW>hy#I=Y++lVXsl<8jSB+{_Of`grA{g4B?gL_Q6R>@>SyGGPOAi3PQm} z(9M@WCoa^o6oWUYk0qPoFHvvGbws8Nu3Jnq1}qV8X)2TybLYY zVn$Ukcr&WGCBk!?iIbL3&`Aq+4O}Z~mndEME6QV;03uejO$hSsW;6UX#m-glTgse~ z-Zkb?LoXJ&d&dw&UZ}m*P?e2c`v|VY6yl=HR07zhY)V~E*(d~u;6iK)?nNY;V9~M z&1=ZKfK{e|i`S!t$*_0Fv0I1Qrn){1JTC^%F(Ty_n7JO4UdrRE@Q`5Ab}me6|~I39DvRCHVQ3Lp(vS#)j?Q6E>x5fM#zk;O85>6t&joD zEL}o~5brg#Gqkt34KjkH_PS`yLqY$7QD!SD!o(KKS|^+VaPBm&N}pXtoUj!no4;T# z7H6fNxq8`ozzOko#j10r@Uti_pXBXyV>yhB9SFaShLdlUFESQ{^6Dq5yAABT%6v2d%D_io8iCAUB1uPsBL9k`Ibk99v{bXDTp*{h)sYgY@K^e|$p zaZ7>Qu$ywOLJOgsfhuQ@9nOT{*xbAt6`NO0N|UD37T$&?E%y=P_u4)4J3#SESJj(s zhIwxCZu|zYTkRBYwQ)I*oSt7s@smZfXb`@5ANGS)E@6i?EUE4Pm;C1Ko}Xj<3G%@& z0OxzbX~19+6t2XTix++sWyu}G_c%jAfNyy;3t{cx)p4k1i^Hb8nTrV~s{<5GrsRJ4voREy6>LSxPZgHD!qB>6X zH>g@T9dI{2nCNb_`6YM`4W0KPb!^_uNU#hy4sZCzWZM@8s~g@xV-@MZRgjndaZXiB z)L<1_uBx(tt2>HLinuiAm|-(G_GP;(B)Ow>X1ItVnb2Dz3bv;-!*cD(ym_05gZDDx zy|J6+{b{g&+?IL?Nyq*f39lh7+mnPhPLWXbu3pT36Q1PaUW)p2FsPz9tn-7ds`{ut zcyu0U3I2kqulOM5tdX0L!8Wl)MCn6_V@}Suu-a}3UL@RSG*0Sndi6%n9svu{E8Ej9~0aLY3`L|UUo;u!1s;tz` zt24jj%xPw~`xFwwTV+s@?v;K)TyF@cJIHq6fOOnS0t=R#(X?&5B5dDaNy*<-9@`9Q!&?xOJ<}}VR1rowb=2^7^vAFR0|0%ujp$(t z0bUWfV_5#v9vf1bCG>LKzmxA7*A86%IbnPo$Q>Kz!-!_4c(t*ezfIDFs5E!ct*DFa zN|l3;l=Luz#^&(c#BODp-5tD(WcGI-ulSK9xqIphkl+Hqq|^m)>I;zBVV1D#V+FSZ zmFjOZ1FY1s4qQuhv4C_nyEB9Lo<@zLiO=w$Z%K2Wiujy5tRJO7+1m(Pt14le8c?zL zvU+l8iQwJH!?k-%-1kRc!4`ut27AlO`Fs(^>nQzusMJph0cT-Yk6V3haIL=s^zgm- z5&Ns6Jx#IPjj`Z;i17#Bg44fPcqi_0+_$VX8r(&x?#55?S@h}d;I}ANt`7%{3dybV5V zm6i3vw;;%|DQ=aKr5MV#IXU=ay$A_k#!lz}M~mGDzYS1i&$6qu4=pWIu_``z2co=^!5%G`aQ^L}}s=nwC93-KU`yjfC_=vuZ zNEg{<$5AyS9|b+UA3qrZN^E8k9FZOX)e6!w;B{l}c~itNP#)DksNLP*V*ury;7~{f z7x^F$blx6$5Y%mcqS9(PNwl2?fuIr9h)+`m4*``(1s}&{XQXeZF(VS7c-WNVszm4$ z2!#=_Of?#@T(d8I0;6yrXbeh?o>H!QYjj=tHta!s(<|RY#r3h5V$&DNgCg9GAEhEx-jOR}4^g%>>?=TP3%^b`H@irmEb?0T zEw|ml_0*ntvvAr~TxY;*0B+5kG<*!42j3nEKS?<}j-TLDbVs$3bYF;;Em2-+2Q#6( z!7TiQp9LtH2%ew|Y{&Og_2Xfw56~65!y92wqm;9+YsK8x>w5{jNZF%zD0_`}_jYYNl_5sszX19b_&Z?={V=3P&o+7# zpFg7?T@Ku4WLnM_B<6EDZhnQo+YqLI^j`5FBP~=9=aS1%T#QUd;DVFB6fQW&=*0yF zMjtM;rFZ~c;cs^oT`^yiIhKUe;Bva+$cN6GlW{XOiX&f(oja1Q>47V$xZf7+hi=Ei zidmv*BzGiEH*}{RrR@?B_)@}V12V+Y+4B7$MD6xf@2Z3r41a-Ua3$IK@khFWJX8n{ z(+wT8kibLDcs`PD%r<`V2=Y=XnM^kzci&tbg!v1tuoBP$QzPf8U$NnM-z+skZ=%RUn< zu6}m6p|1>`eWE5vF;APMBLKu z?x@|nBX)I^)1yu;jMrnIuJD%IX!>-E!3cl4&x?j7TpZLBe%*mv?hsJ3xTJy!8lO#JEi$d?f@KA%(wrFBQJf8+KR zKZH11U%Gw0#V4xnU#`0Ud)0m4+zNhK)&07v`yEyH4_4hzRNY(qmHCVhkc9%xA6}2$nzB5jBwAFNyWcLL7LCgEBOlqM>2>VJIp&dj}2b~l;bWM;FQWEPTLE>pHrl28&l zL^`3DB=jIKFdJHiSwa()q9UTASP=V1!~*t)pnj}iS#iMt77#>iAR-vQ@Ao;+oqO-@ z4eIav`~C6DD?9f*=Q+=L&ePj@&a-(Fi3d=xHXjK{2Fi&#D%|Eb!s))kN`pxHMzXQj zqVL8zP0qWakA4@`C}9;Qegp{y`rk)k#sStI#82}Q`b|7Uubzn?%LfNbs9fe?X${im z*(Uf2L;aMYUMHc>hXBp%2)aK5*0P$%iUU1U#zoF#s_BWF(D>U^Wt^vFA!Jkfl8Jr{ zxlLJS_H&F9ahmM%t$q%C>OMo__%p&ccNB1nF`o|*x}LJg=}6Zvg?PHisew_M+L5)N zgRU@y{e7rV=6P*AqieQg?H7PETRH}V?<#0dCC8pvs%I^GplT0FkvbDQQO<)Ixi#@J z1vy+`-K%SzkwcFcl^Ks!$9C;&g9=})%>uk)aa)F1&iz+egiyqZ=1JrbZ!X|xUb7b; zoGf6Dg5hWl960@Qci>1;Xwqp0LewJY3`~3m7qnItsvews`;tmqhC`zPG+BSr>}p+){sQ|aUjA$G-ak*Rh$kuSFu znjC7jHQ-q8G^-2d2#j`b{czxiYd`(S@SbmSWCn5v{h;nLn`Ag{EQKRKdLV=%B#IW; z!oG&3m2VQ|%?9?cr0L4AZ%&T);k0tVn!rLUBkamO;DezoAs34cGo3FqwgVc6Q}X4x z874hDM8VnH96PAEK!qV>MW>w3^S;L#Klkh{vu z1wFOL0A7Sn2dcr#zYapX_Bb%LC-5_Q3dXy@1x~yvrq@6RHq)n2`xS`hZEair`c^m> zNiWxa1AKQfmz1M@s%BUF;5Z7AaPd{?#7gY1NMg&ovKqG<-vSJDq*J)XY3EX+knCbI zo2g`TDdKaf{P1t_GqydN|o6y3O%ZZ(>L)XxHtQewWtx8 zIwcC2CQGhyKci>-boVJnf3kL1xIme%&qJ2g9zXW$EnZdQrjJZ3Ig*y#*THP{v#>R@}Vob5oE9^yijKnk}GVyzGY_1Y1 zC!QtDtuZUF@LGlcK>T{bJvZ))Y#zXDlU|rAShAM7qj_4`mrMe_r^4wz2QIZg;-{Jy z7-o~Ql;BUq{~13PFxU&VC;kG*#9#3V`y|Zm&hb4V4Z5Ckc;d&=GC+4Dq%v7`WTflnaMX=HxBvD6z zFM838?67vl^xc_xvOdxMEypgzYh0X^IJlB;wk8dS!SU_P<=8-yuv{kiKSF=(V zI8^kEFPTAI92uY66<7WH^gQ>QD7%um1z3MKcseIy1uSf`10GSRKzg#ZmPu zTLrVBwh775rI*=E9|y-A_7i22yc}H)?p(`c6{CNN#e?x8BNthg-$2iVDCC2C1&aQ+ z!cQW~7xWOJjQhgHYTz_SuB?}D^n#77d|QCY@;xwVK(|#;RREmuIsic{>?6Z?Tzk^qxUP8>3eu*m=$RzY6 zvST4Np{zFuoWQ|kp>`Roh>Y`?pz%CExbLPmGb$r->0x;<4vG)5lQNdAf0ecJAFP#j zzV-q1@OB>iOZb-0(b~hIGf5Vt?(D_aipc8x>6?^dWa^ovtBR&n^tq_R!-;oNcq z@(h!Y95gRDwQZooGXq(&_A-m)Y#%R(+AG8!Iq^??vJ+eJ+n$nfsP-?wgEOd8P7Pf} zX3(i&R+cHO;y8vHY{B7=ADLx823kWN+sblEQo9+VcBHFj#)qo{Tw_4F?|xK z95z9636(^<_XM9tFLUgw#Tohd`W%Wa0Wg!U9PJ0!aXTl+Mc!032`arJU8#Qy)nq1h zQmAVrtA7{%P|F}+3=ByO49UR2pf{~6cKyd7!`&UE)Z4>;0PxlLI}l|coho4dLpK*& z1KctAiv#ZBsEa)TtbSrB(wtRu7#XZmG5Trn)wk>Nb`5*gnHgw2GBxe)hH>As9OLc* zM1G&XxNN6L?$C-ozTEhhBeJA;9Ae(`(_ zW1eZ13@MsgmPYuZ<6M0QPLJAwPq_R!87@U=wwj<~z!& z%cA@PiawZ|+F7};Wsm(b5c`N3MB@(9vx}D=cUM3%7Tc}2&?Fi^uE62)G-fz6(yw%W zacn1uZCn;`{LdshckxwhJ028~Vy_5w6m3bS2Fw&aQF zWek-yH}Mt2^L4_LzryCLEjIf^u-Qwb^JKebbJ7%`ER64CHd447gB=46#orn+?z@=5JgRD4^3@|wrBtsV!v*8e1d;X1 z$q4h$U@?HXC%+*E?%BxR8JcB2uXL>wGKkwp7L0Kgrc9B|0AIcP$vO{AQ2iC(}YMVQj zaYr($yj-tPccsq)TR9B!G3s+1tWGI~8y;vK2!O}!ibTU)q zd*l*|K1mYF@ZL%y|{eUlt-bwe}_Hf{i zzKsl7?_~(hEMd#jCmmiCaLlG`^$WJ#{bAg33l&luie)F}1y*6IE-buF4xbRDwP8fM{1hg&b{Ghr`<0O^l)(PjXiM73_V;KCLBtw78-vrov_A0;?Q2F-^Et^itIhXL8zeRQfD^ zq+QR8;(9yux14o3CLu6pK`@{J?cE`{t?gGDGH@AGAF z{WYaWVvd}zVHQ{fK_Hk3A;fcL5T_ss4t1yp!%%X-w(?A*yHz*ivhKbf>C{5y@o9ck zHQz<6$VX$)Js%Q5Rbw18{0i)K@@v_Ro$yi34Z~LI)&9tU8^9O)@aG^{=&T`Lp}jbl zbx`?pVPZDyL&pvdG-z;euYG?qc0YLD2%b`nVAKExxxd3q(zO~6hrM$N_dEou&O~cm zXCSWjRA8stKU63$#+53+rU<<@M~%@|zSAku6m;iuis(zB*Zv_zde+8xFgmj_epOV) zOzsZhOp@=g^k`vZY@vackH7+@DC=Y_QPx2tHFkxPu{+xSe|iQJ$Y4*^JA4T94wSlo zP7ywYsZ3(O`FpLtp!y#j!f1@;8*&LL&&rDb^2kNQFFObgDSS$z1F^RQ&Qyd>N%Sg2 z>5EzqIqxz(*F3Zau9i%NTZwteAT25Gq*Y(q6M6@~pKf3#$d%d!APB6~Bz)~ckCV&^ zH8Rf4p$dyVDl@e!>M$gO_Kd4bn!G^APti(emOIfEk`Ii0^JDRBzbgQDP7H)q? zlpdK((s5%K9(mbA_#)D9Q>(9ewKQx5Yjfu3CxGYac4b%*t?V-iq1#jT6$+iHm-XaI z1vg`_&Xs3m2X5VZ^IQpTa>26$Hc!{Ufn3n*J{GM6Fc&yCh4zU2GwLZEj4Dw5liZR> zb;b4=%6E#nGRup%+nJDv91MDdG8Qp%F7`DOw+PRh0l~4F5@_xP=LHg#<|YZKfbA_f zy$W_Q_{^+ea>vXMhHB_j>5|xA(iQ{RKO=)B3V&-&^SiXS)u=-jTw8K4McjkP*F+4e zDCYeGDfPQK3|Y2yIVr8nG1e#KRRK}y!DfceIc5y)qGA)LGQcMA<$W0|)(^gzn43MP z+E&>w(wWK@++W3fq6bkuT2lSY_E>tpcMsU&{a&68AuNhHQF{kkRwiCu8Mww!%#1Fv zi(}^uFY?ulCMdDdHYzl+rc0EBgs+icml};qw*TJ3jV5Hluq~AK&iG!5Jv;%5gAvuEBJmJ(O=HjHD~J?o)fyxt=<@ih%HW3&%4DTQej@`kZ6sP}Y`D*=}5U0gSk(}vVI}DSf)5OsrtdviS>X>LY(q6i!+n5gOHRd|42CH)gMaS<=`K@u} zr6@b*I-xY4OQ}wob`VAyXwRrL{TqPn_E)X<-X%dK}}`<{?FTQNxBcTPP?cQQuTGG?&z4KWFSAD{Z@1 zNo8fQ8t&g^U_C?@WWQHp=vd60*vQ2V)^D<1eqdgKF3*e|e zgS%?>*8B+=*0r0N=mokXJ#jFifqg1=ZnXml=W7S?6R*7npIRTN@QA>CUM)BPH2Kk9 ztIO$1xz`}Q(BXn{e22&IxfISo^WEuXh%J_*Vfu=SWj83QWTfeCu0<2p<`09~l7R97 z2$3A$YgUN*AX}GoBD4JL26IC_nIp`J)QVHuU@o+_5K^OQwAc1W8t^#~pWC`S${P%9 zd|=^adfZLd7J+;)U0aM#bxxsuUU!F=@#OMXr57xT7whnMu~= zIuR8T)S-q!u{O&LU6)#MKf9S0_7}je`V>$o{alB8HF8tgc3gzMO5Q|ReltVNsTCpC za%}W@FchC-<;tbTr?wru3sW~@{>R4?lsq>#U)vt|99~mkB6D-@FeROv8!BbH!RqA~ zH3XcSzsnI$|^A>Mu^aL<}$e~F0Nsmw{zHW zq2irdDqT5^t3q~zMOIllt7HSE?iH+2PNhW-CGUz|F7+yh@(8vL9cq@}j_e8%x>q2F z2a%Y@nUJs=kC!oaarA3gn9obrV{xf%&Bbm!qT!g;oy&K3mP>ebqEi!%gA}&rK&{So z=}I4+>~X9Ext#RfW8a%a865ePL1795t&aDH#(K|=bwcdehvMvo9~(ADM${OsY#(skRDpr=gF#cH7s~Vy6k`SU zEb+e4w#I>Y<9v)RW`HI*4*G43Xi8f_hu4barVTwqFhkUG=Wi=xv~M{Jun){TyfzH! zC{vkYfJCMk)QNV+3I*dgAR19HFmgNa<=t@~r$vRn7JW|oWEk`{BVZzZ;xu(EpzJf2 z^Ej{zmjNbKf$_#ZcPUy@bsv?x8c(23h-^|PczH^Oen-Y}b=Uz|t0_k~nw2A|`WB>I z60xP|3EwK^B4W;-JNdJo_B{2)bOyw3)XsN1`R-H!EhdTfyC=4iVi-WW(G_Qj0lfQl zs1t-a-;jbIz7!+vl@LSCBg!DnPB1^bHU|MoLrs-z`EX`~&a+Y;;4We3x4ZbD?sLA> z)mhsYgDmRlOH zxun({Q#m9GY{DN6!I06-CFtewv`cd-zvnnrElvtXk3ov zOkVO71HoK0C#h9AsZ~=@wO)+EWli*a+Kmrxyi2XhYa_za? zXoA)UHQ>I3%SXyP*d4*U81C^vYC-7heow> zDRx;}v`B4tc~z?pNpd@y|Q$aNL#hl$eue?`o zUd|p#;q@TI#U~y}6v)10ZeA-h^4j?3Te6()t=+5g)5P5kd!m@I6#IUOF@&r3DugQY} zR~d$nWWUBATpu{88D#&>`oM}UM+f^hRO@(c8e?KVdfV7qE{ix)HD19_DK2K=vrggo z{pO$n>|*}7P(BoYm{`jnM{o~D|1a>~_y+&V#~H>i3ge4Vv2bStml@^R!TJd2yzk)9 zJYwknNjd|+9DrX6!2c3nB7SkMFZRqEQ2)EZFh__$CSTXN##tX2dINIeko&=hbG@p`FU6ZOFk**w?OH@|t zpo6W8@Sv0Y?+2Yyyu#a1?B4FT5?oTnl;mt_#YV&yANFpVMw-}zE-LmJ zE5KTy{yp?6naWPG{v8NHtiBvErWG?*;r1kAm7rA+`;x1N*f^I~` zby|fJDekxUrqRV|xUv>OAxtZ)x8V`HfX1Ypwx_j8M`{4_afrMod{i+ zGP}$}V$R&VFcBa7pq^U9M+c2o4ju*TGobQzY|JrJqu9V_to}YATwhTY`FJdY0E ze6lHrR~PncoaI;^em+bVfJO`5( z6WfqA-`tl<#J+*C?KY&jGbs(aIso88w(Rk~F2}svSGQRAtq$^TAW#N<-9C80*IbC@)?3<}doX9DI_Q!uA$r80JM z+=RfLz$xAdOpkpk6iYxjQyp6pp< zbJ=3w_`aAUPMM2}!@JTKSxf)oiQ{fA)5FQBATa~x%aP!b8Q!nOrFh=>ugbfaI-1p5 zh-tNverNK=d9Z<1^MZlVmtG-;hWPN8TnI z97wzEWl&MV;sOi2q%0e9$=h;vyeq3kjXPt|h>)U2yUe1jY2CuszPAqz5yATgOof(xM{;sMj7N1eoE3<&SX8lNHHgs z_BdA9d5s%u?pBiY5F&+T&$2JD0;RCsClv@!e@X>vM+Hi0#n_d;!3xxl3e=7Y1SHqr z9^H8@E>eL`!M2}tj-7bP6!ispHm}mtC3&+&97_ms5%) zgyB0J`^x3WAFoU~4p?F%3nwU;cX3*lDbScPhHmyJDeufrdSiArDSPZU4s})ZEyZ~V zSKzY{CV*$Bc-*&m9O#+Grd6lHig66;LfE|6u#;ZRiwY-{GkdhLNAsoobR&+>u9#ya zwnnp+hQqK7y2@7XeIhs1DC3EU=iN-3_U1=p#LCzhFk|w%{qFdpQ;d1`;g8~W*l0Cy z(S4x_cnFt_%7aAi0A?a3caXn(>!IYa?0(;jp|bQ{EXufNXcT)yi6gA*Bj%;*yI_9b zSHo!pP69X{Qzr`hmA>BJV8M2?7&a?yMq!2N%Q~`u0p%jv4t>~D1$6SM+GiQNj_o^x z7t5xluSj0)GkFatE4jpI!HBivu@HHVnI%vD-^+s<0)uuCMCF^tZPK8#eTJNeMa3~o zPA|>>G4s`0Cd{vV-b)no__c`dJ;)dFr}b&0Oh?L$4CHE)6&qx{*@;ZS{ZP)9p@l48 z<6X?aUvG{Y%xGuGi6{5G++;n(uDsNDQ%64!j)hFQs7`;v7x0!g5Fh)c){PYi&+;?` zT$P~iOMRzwW~!Et>ohVUI|b^Gg5W-xsTZtc|DI1gc(Pfeu(Nq39V1Y_McQ0*R6(DNC{++o8?US93Cy6-^&$b4)Ke<-Kzlys)X+InK_v)ka0g(h@$XYeNyL;r04 z;_+(yzYqD}$Gqrghcb-m^0&c@$|*f1;Bv9p$~!?~^{)lEC)fepaY-z;+w}sL2yPZ| z^H3}{HrML^5$H!!R{zS8SZw@k@c19#;r_tSW3kcW;C@Nn|H85zMlt&5ZdLaJd`FjA z{UgR*quT{NHU=1e9loPIR{!I4cUb*T;=9%2>A90HC!k@N&lGGN08f>mvlw@w;N{hw zKFG&Dn;VOF0*$STv=z5&m`9_;r}!&D=;y65c0m}9EwqszZ!a&gEn^3QBOV63#Hi(z zP_$Va2MUf%j8j@S#(wHREkohLblm-raAg}F{cm|nANdQ6S~}hxh_CdGm^YQXp!~@J zT@6z#lvbA*eFk`~gNnyO2A!lm@{H|_q-iB zE%x43DC&b7b-fukpguN>mN!VEX+J)T7W0H{(EiUX+FiFv`-xe!Yqu%GU9)J{1!$>v zh4T{mr-gqVN&z_zgQ-E4Fr<(7s)a=9TJ=tyEIeGpxEO^e*Oa0&|%=H5M9MG0u=V1f! zNp+sZeSEo~6yK69aN+w&u&T~ee+n!b(sJ$2%P*4$Z;Z-=Pa||dO%Fk^eg3McT~zFM z|4pK{krJwI&k&4d0~Hzhr%5n%LWl1mEW!TffS;o z(&6I%;_SPI_qIfusaUD;_&lQfx~SMZ4R^2qHj0<3yC1#;{&_^NV|6liC(`l(q~SA5hXds zhXSPr;>#j3Ycj?y`lK_ivVT!h!SmdckOrh9H}O4_hFe={*p}d=5Lv(9l9kuTE0p0s zi2KW}xT!CSsYGt#2PW>f|J%4p(YhN?humZTVM`FnwEs|$uTl^%4?aXWeq`i$$G??> z6cY7=t*Bo;4{r9??cRkj3>;sF2xSs4dX*Y;*55=&K$HcD3Y|xvCKtImhA1b9LvEDI`MXAY0S`JK6 z@P?lHEdK_I42RTnvCUAK6R=2i3ld^CC`o?L5+dk=&JtZXUf?IChE+EyQe_(+HzWb^ zykI7wDn9NRF%`>x+=e_7uF7B~_lymXslp5{41vfc;5$JFp`pxcFT-_5Ol6_T=BFE~ zJ$||$YB`YZV_WHdMAHow;&8feZKYe|m+geY^zMTar6*h<57c;FAQhU%sqt#C#G;d9 z^5MIXC72I|sa2m76l=rGd|>W}bJJwXHJT!3O1Tl70PYzSB#^5S=p#_00+j#?-`K4U zTuSRrO=1yZGPff&S@s-w@jmMr$XZ?#H9HDFtGdo)zPHuWgy5LFB;5NL8|FYsx&MlB zB{!!{?^VrpAAn!Lkeg+}3%e^B_nYu&UJDLpyxId`>&}$EMPpAASxBfoH({j%!Bt>_ zGvQ2e!?@K`@*L}VYe9|Ub6j4HZ2c%?(dGz+NHZW%YFLhQF~n5nnIV~S;J((5z~L!# z%B&GNVqFa;@$ZL2y*rPZ1-e%1}{{bJFT){!tw)#9I-8r*SetO z(KYMg^`AO<@BTavPj>QS$H|PlhSBziaJG9Ckvh;8WL7KwsdQ?3Uhpnys`hQqRux zcBl@u{D!wzM{6&-%RIlH8f`tqZt3M>vnQZ$Y{}k+TtBRR6LnB6n~QM+=(%7Zg^;M= zQ11Jl_!46X&jC1$CvU_6tn6no%^#OEQ_U2YDhhmM6I;mN$8Tkw@{`aopAfGdh9R!9 zOtH3Hc)VyGwuoQ}d%n|Hg=RZoXyY|>{d{zSvBS)E&?VvW@|KtS$Hn#o4s~0iilN%l zd#nZ}oBZ6n-fz;xOL8UWz2%ku$KXiulQ@-?}X*c*Yu62wE|9m$2 z(L>5(QT+dt{E>_w(KmWR(ebUIuC^H8YROGJ1Q|0fZe)88^o8z@2Ta>`h-q5~*Tfz} zSAEeHII(HRDYNF5i@5|00=iYyGM=+f3=zPonF=pm!rd&;Vc@s);C2rV+V$Q;&@x}D zhH96A`H1>g+F}=^<=5R8;U-3b$4!Va{fz4ehNy{zwWLh*b~H|j4ECdbj5JN`0&H!7 zmIk5DP+0r4jLYFquWZ60Dn2h+ky=dYS_r2 z6MS48r`PS}lj4|G?*)g8?L@n>vvFl?xtoC(j#sATRtE0X#}!J__PXsAJAC+DwTEnerh{b#=>U45p*g0nZV; z+KEf|J9;9Pi`bc$lryb3@`rMUhL~1%cIg4Ltugo>$VVfDU9h3>SXP)&2 zrMZed!R z4%-d%iR52YoKhiLan5I)M1|sP39`WS4r+2qHd41KkC%*2k7ZTl_c)jdq~WoPsn71w ztj>OM&`wS1hw-Wugk+><`tOMM7%S>G9_lFq(jM5S@?RW};-W-M%3Njx5b^w|)pVzWB1=G0ePt!)u%)&mlfo9)t0CFZ;W-s8#Jw zchgNX6P8Td5h`j7;n}QBy{%UX}1>XnoGwA2d9Yw=xO`4?<2W6m~e za^-2nA-w?xCT+`UX^7Fy+#`l*b*--^&vr3gj7uw?=&+e@m}><4YxYlnpfCM?Vr2CA znLLtGL(hP*M=DlzZm>J3R0AE>c1k7Hdwz&&TS=|9k{XPy%3=+4X?{gPw_&7Uoep=7-9Is_pY)2U&u-V@())aBsvg;x)kI(ANeG!igog(m!O-1*;bzsKA=ei@`Y#oz2eV zjI&fT5mB>B+V3t!+;Vo0En0meTo`#~*u(+7oWC(ESl4#~J@+13dQh#s8qQ;=>Bc!! zyN&IVOn&5f4WAMTB|H{;uNK~^nv>yz1_3{4IANOsyDtI0&(7k@JY+h=L+3a?D0ZQ3 zRY4lnogVbU%r%kegjbizJe{nE?EZFDjeTI&B=nblWDaMdCKkt}T>B8VqgTnhZN8AN#ddd@xHI2von>aQ1je%s7dC&eAkuY62 z$eU5tvS+1w3{v*;jDUIg*p#%S!q#Vw5q=wdUdGu;zHhudViQy8>N^qE|LCQy}zRkb95!DV1+ zAHjGI{WtM=W&2E^0%=S2QxJ&4#K5vhQ2yAb*#}a?KsBLkSg^kg4z$rkY#;c4M*M#d zRBbJe0F3(RegIt3Fg_%jh+*}dh3*kagLZ8`fWR%Xd6Nf%pQ3B~VA5By=9R1x zCdkSRJ3Lug&O-&*l-BeG%5oGxj!lZ0TjDR9wEiOP+_d zFm4pYql9zb<_y*Hom?MC?a0OD%y zArTi#7)F-iHVA`%@nU8b@;;VSy8^J_dI3CLk6eLEN>3wLpnVtt@>1-%WYWg1glAGASv&4{sh zim@>`%VSl1>R$E3ZtaXvapQ3@GM-n9hcJDnLTOwsq9JREgvn$%NVDX8Q`Cj0`?e;q zui_&NNOj~Xr4|zDvsgd)&ysUgOU_D_Ud~D_rI*t)tVDT~xDCouXZ{@Q=Pb4wH$cYH z_Rz5w0EiojfKI6kYadWly`D~3jcyXazB{o9Cy(NxZCY|o6YIB}C659&-$XKfGH>Ao zS|_Zl5Yyl<&5^A}!?O7T7fs2$nO?BEq94><>iFQwyh!#|I7Ko6WG?zzq+Q}_NRS3Y z3}YQ-HOMJHdbq}uuN?sX8JwVE0g-`LD$W8ZSB(aqEEgs`?6CnaUJ2TRcTBnMi8oi3 zSCg!1@=W3#YQ@WB%wmtb$}`xbOc7N2ws#@yz09A0%)4&}Qe{=N3XHg!E-Fh)p5PdU zVvHZ9-}dri3wO(i!o5&Rl9F&P&f{#(kKoCC82g1ugrU`Jw(5Y=mVwA;Du3iq1gGQf$u`7hzb(E^dT(A7<=tNVs|!MKDgMy z&wj;je$ZTseZ{_fK0mP<*SP;W$xTN^yPKOkOR1Yd8Y0ek>hUVb0)givEI>dY* z1F5cYQ!xaMeS!6~;Np}CgRtHWA)kWs(X!3s^jV4zpVuIDJ!_%fTriGaIz(`(0A5i| zYc(?ErwtwnUclh>mrm?`NYj^?PN|R!BwY(6 zUB%soyhNyHuXy%)i5`R5aikWNbJ?iWW7gE=k!#@Bh7cDio1;;FoVwXO(A_03I=NbEN0Cx_ThVIpCB zQ7$J^17K@7?%AKM`BT1LES+?!+>TZn0t&0FEMK#UTK95?BI&gI{qw z0OVBM9stk7iWdObOyEL1I<~1;$M44C1b)viZp81|#f$KJYVl(Ho>aU9zsD3W#qZ(8 z%kaCpxCy`e79058vv@gvmld1%9V<@aw^Fj1P^p3!oWh*7u=zXisjtIf+~#iq@|E){hvmhO`<0p6p^&4# zkw9d|B|IOdV&*V2SgWPKW z9?PYPBN2M@FM-4mgnYnTFIJ2?0$8*COlDPYMb~3ZQc4$xwwOX3gBh%QGr{^@6bR?6 zz~a7N<_7G8wDF7;PtEca72(L4$EL8n>|#M=I<|x{FjIu6r35q-D_K^aINRLU1;}iE1|w>N2%U@w0F&zObBt+3$jTy3(Gh zpZ)QP*PkUXOkOgXKz{FQEi^By7JDg828J5y_F(LSI|h2{z4Bd9FUhx8-det6pwGU; z#bwEiyAEvSntOc}2;`&{Iyczz&r}y!PW>bxC5X*LDS7$$dflVNyC*`sTT6r#BfnfC z>#4s{z6*rS9Rrb(V)ggWjbhHO zvRz8{OL}XcVPnPOUI-K^UBn$6z8!n)ex~|wjJ{P_#0Bh&A*{H7{d)*2E?{2@VZ{aP zKSEe>0sC?YD=uJP31P(r>_0#mz%d;@v9F?z$moQf zL>-{cW#8DN@cD#`X5_knjs@ybC%jnUjX|P|#nGugmj{EJF3R9NT@C zKA5?1e^qVdeGTcK?xQ^WIa zp!^sqzZs_dgHK5xL-}P=-XErX&Znf0q5KXh9|%+a(Wj)3p?sK>-wIRy$)}``q5K^w zza6IhvrkDML-}`7ekV-%7oUZy1{Hsq%A4Azg%I}6LpZ6*0V<=0c{9c&y z1)q{WhH}CJ<@dvsfAcBnV<=aU=MTb^FZz`9F_im~@`qu{zx$N*F_cG=@<(CHmwZb4 z7|L~|d@xM;51*1ghH@h*9|}{x>{HUmP~J+)ABQPl@hRzJC_hWepM)v@=~L3jQ2vUP zKMhlE^(pBil-VoTWT1wKDQ$UB+7Av#Yj|WZQ%Pg8Svv(#iN@bE?k91K8+X5s@nhm* zxHoYgZN+-g_W+l3F}U#*!^Ob`^aIaeD|YALFonCVn5|_>W7g$qMCQUcNrqkR1m?Aw zCQ%%6GW@ce69BYOUQD>Bd9;|`W|~Bi7A7HdQP#=9=+#ZHuxv!pYu@ZoM9~W;Ff$nvMX&s9FQVvGpckg~ zD&bj_kSKcf%nn5qvuFXo%uWG7+e_N8$V8D%AHA@+&=@u_2BOGjA-_Bkg$}Ee5SjlPC(gJ-uMYM}@=? zmT3}2HcR+rlmdWuDQUxLC5mhYW_u9@HZG=Xeq{w)Tq#nv0sygthY$eu-a9||hFAZ+ zH&d}oe<38SVRX&ZJ4=|}ql2SL@w& zn?bCu7pnk3?C2o`0HWj}1b|$$BmV__?}J>H`x8WfcrdzW>I*5$MSv<7Ta7Bh)iBN* z^d*3580j+`G5EG+AH+_LS?EcFX+{Vo$+#tc$6wy=6y5V9_o8!_VdS7x;BC6}CeUUO;|N*Z0GBdAh#I z*b~5i6Y28BEB4fyh-`Ao>+XIAV5{9`V~EF`1VcQ1To*mu{PLg&L+dm_$exV@Cux&FRLG00K;4<3tfz zA$X8kteHE3X;7wp;teDgzf=vgA$FI30hKmcxV-o%@@e9F_)TDrlxec~Ecd5CvSJUx zHKWcGMK2tM&h#RRO=pB(HXQ+2Z;o$^#qLM`av@>3o`ZS{rDo+Lj*{`Y5pr~N#W_*K zYbZ((#L;?-N+!X2qJm+)vLwhmkF(+kPNo76p#rO!q)G-4JYouLL(9f;f6Y3Cg$mxY zoSgOw@D~8K+BOStCDJgknLciTh5SgE#v#pKLPH<72Q;;P;KoK9Y`WFj zwp%tJ7pt`yt;1^@9rLtpfQM8C>Kin`1631x02tQWuvq*czk|r9i&*b)HiC^w?EIQ@ z>?0<3K{c>zZ-&$r?g;1AwvOyudyXaJ&|{XTOfq(V9GhqETR@I;6Ks)XDxFS#EaT7+ zU~B{&@~BgJ_xG%tl;`!RQxD9NCsUrI^1!%Z$Ff5}L%l?X(dPj2doBk~WOHCN@U4s| z1)TyV@nwX`7~_D9j>?F84cLIpEL?>u5px3a#zo%cj{%*|uY8yvk$R6G2#DoiYJ(zwe`iYR;@s2vvEGcWg(29* z9+*T-g2NfszmMQl6G5UC!MTFz>q;8T!n$G>;WZBxVsTIiowVJJZrReO;=Yay(U}|9 zOdBBz|3SkeV@)nY1%S<_Z2ba6GVu>2VpAL*ao^+OmdDK85gO|@_s=Lg?jfpF3nq63 z&$x#UXt<)rYnZ$meX@i#6y zFA4B@@F=fGtz8B2YghB<8vb00pM_&i{c@xLC3qD+8LR#teC!&=RI#^~5F2#lqnVX4 zO$WCbY`FkryU!tc^*a#CwNVo@dhVEr{S!5Y|8&K93%myEcY!Kjdz-*t1Y9ui_W}19 z_;J7s4Ez_sy#}_*?BFzPFW`j+9s#__z$*YRHt@lKi)rIHhK`JJoJvR5IL@V`-8eSV z(P13d(9vleH`6gk9GlrG6z6(1^*!+6X6?u1+f%<+z6b(kN>#hRr^<5O`sPC&lXZ=V8=G4~_*vu;Bi*&wt7V4Yi z+f#p?d>7PjmTzzUJ@W0Ve@wm$WvAhefkpLu#l5(GpL`+G4;9GNrxnQ7pH!f|{uc#0 z>RT1)tY@ks|D1ZBz-Crc=tcEN*I3ZglYH^K+iSO?_+bU&YiM!WdVQT((mb`Gblj*>-BJtJm)@y4WV4+WF zO|40H#kvycO8mx%&aIyA zTN$I1FTrTWojpY6p6kF>Pe?JX?lImQ#M^TnwCX6mQS9|EdqDcdzl@DBhlH z%_{V!F=S!i)EbzvlX6AQE?W5+mWVGzEhN%mt8P(mPl)q}aOa~X;GyG}!sSV@QT#mD zn(>q1=S5!^KhJgK_$v7I89{5}=eZ6WUkbm4#_wG5^IQjyr;vh0Q)_zt6!fnAcsK=` zZZA{Utm1JpIC?2iNj%q);}^islUZZ)TnCS@L2SLD*cOaYa*w&vd#-~;a*wm-hUZ!% zk}sHABU)V?`w}r28;aq?GKV-=L5+@OVbu>IaMH?46*($-P;vcO7k)R5v?q#t(K$V} zro0@$USmD@Jz=Cv@p}Oed3)BaDfa)9 zp|SEQ@NY$uqyxIX_a0T=AK+eNQ}{i3o8u?4g3HHp=W$MZ>_kv$Y&T(*i=992#wVblR6M-@sCIhjAoN?@45 z3t8d6Z??#k;S8Q>U@!7T8#BcAAUvVhp zd3e~<4SAj(_FNM3JU8sw5%Rn=?Aa%tU9n0b%^UpLc#r;vYfX(;h~hCQnx&x6CB10m0K zVbASDp7pTjb|Fs~WDRr+#T`PPw}w6Y#S?9`Kh#D)5~fOfROt}a-C?T1Kt!9_8Ddvf zHCN^}SI~K}amEL#?Su8NBeYH?)%Sd>L8=POW$;^Dh1n6TP)o?6iN9kKv>AhmcaPve z6vKDXAND_R`@$>n%2T2|YGMC^Q#CC9WfA-Nb|+|t4ZVW#$O{LB#er@p`>;n{aGVEaRKbmBK&fDOmy?eMoO=mTfm_W-m9 zGR^n$+k77bGTd&E|9g-h_QY`+y9@6pGoa(XpJbRf<7DCvfMdTFHtoVDHP9|>Qp#op zCc7U1&HSxx94N3=b|<-Wt4sh{dtzrV_I-{UAA}zd-t#uJPc zPt0e!;<3XJj{g*GHJGBCHc(&kBYDs^WK2bSR?I!3Q9e_GFP2_YcSymMxhP zsxNQ^_+l>ux{Jz$neqYQ5kPQYBhL1?lPbuaZjbx4;m$dS9b*@O`32T3j9N+ro6Cgr zqvu%-2`Iu2Jjto_pb#a{}4o%oxFKc2Al<8Kgun8Jv*WO*0&A}}wx z*U9gl@_V)XUL(I>l;7XW?{DPy4gA$j7Oy>eT}Zrn1jX8APE5={PRu9ip>twe^S5J7 zC%Z?A+f;uX?bMLTKEL3x_dUQMHyiIjxYd$P02OgCp5B1&_4@2}f;jt~ECFFEh zJuysSw?#xwkX&eQtuGW>NqG+-OEGlo+`FU> z*ng0|6X!nkvwPH8ERW}$Vs!}TDVVt=U!_58k4F^w%))HW$rd~sB#6B-{#k@YMC9esUDoRaRVd zr7LlmJ;058F=QH-Ul#|A(*G*ukYT{i8H!v|1|LJ$g0gYg|HU3e*%(PO_+sUonLGxG ztbH6*S*JJ`OZ#d(Urx%T>a*{5ia9Ls!x&g431J5EE=nHe){Z8l{{bUDbmZ%((&&(E zH{!LUPJmedXZn}p2yGbebJjiq7v5vX3yZz$5DI5acmgTqq|A=X7h24Om3x61ZvN!u7cMs{+ZtnQjm7`32oGVTPTF^TK(2|J!da zQ9SYZ{i@)+ec-QVV1EB^{(?OJzktiPq6cVGOdB)9KXON&+td z;hh;i)wFo$TrRcz9pC|YXrpM+=b(V7cbGVm_2X{{e^=p;`~Mu}Kyk&;p5;x_;dML9 z?f_1@IKa0Oz8yxm@YHKoo*1ehisaK(Zr1Nh8n9mHVjU09Zb((f{Pf|_=15Ru%inmf zMuMFB1;jbn`g=F?#wqWFM}x+e;)Sx_k;P6mQCyD=guU?gQepbu?sQk$kRirwD-7NN z`ESZ8=R_0^&p-}C`uK11F|^#R*zHB9`bo6siM#Q$nVpRBX{XPp=<{j%upcu%9rXDO zeLhPcb_u0>(ybR|6=A6Z!-Shhaq?R*-yZW_V7|TP+h@KD&3BRc zF6I~M;9Tamc1aR0Nw~WLz45(;|bRlK7sIr!Y2~msPIXIFH-nq!WS!i3gJr> zUQ75=h1U_jOyTu}Hz|B7;fBJe5x!jE(+M{fK7;V2!eU=GooFuFBTspZdR_A$iauux3^XX(Stj-#o#Ss?@jP~XW2#+d! zA>lEF>x9P@o*=xF!W#+itnfvIcTxCa!n-Pb3E^c5<5VnBT(0nCgx4#)iSVfkHwd4m z@a2S0SGY;|4235NpQ-Q_gwIlVGvTupzLM|;g>g8SD4wG*&gc@w*D8$7(P2w$l18wuAHzMk-e!Z#4!sPK)1FH-m>!WS$2Cc>8} z{AR+JDhx#()-j3eX2P2k#_|_CrwZRfcyEQ@MtC2EZza61!fz+MpTh4TyuZTlBz%Cv zw-G*2;dc=}Na1%AUac^?#66#gmUr3(Ly@PNWogtt@p=Y+Rc_+i33 zDEte;X@#c=4=Vf!;S1d5><%9#e4)a>BwSZ`3*iZcA0xa`;l~MIr0^4jFIMR@h{K4`BnY3aaFXx_g`tDNVMJ&g!mm|0MfhBW(}d4cI79e+ zg|mcf3bzx+?40y2yEfT%EqWcN& zsql7$S1P*$P((Z&0{O_#B0Y2)|b0VZ!Gsj7c+#RN@*Xe7?eCglh_q6TU#Vp!fO@Y2k>Ie{(XraQg}bYOBCLp@KS{jAiSNz2NK?1;e!YdD7>2R4hmzp zhCR3x@45jX)ufj9c>lqk!(cB!_`q3~bQlX`TieE73fi z%w&%^kDZbqV<`&@7H0uDo`>Z-d@EU8*E<^k*3B{1a7Qct@QqMCvNFXN+$6Tz zuvs$y&2V=m68AKpzPPePXuDHe%Vf z5zBUrSa!SbX~eRPSawI;!3ITlf|kTZItPgn=^P^+6poE>4)&joa1L~sQ)%pD97O44 zANzR7_A|%^t1VJW?!oYqOQg2*CT5`dxO1`7uxP!T!)Jz1m-=+$qnAjbNqQeH54nec zmA7wsA>u(fV1^-aWIud+T?KcOSeL z%=R{#v8s%Vc)54IJKE@!Uj{rByoJTu_8>o^iqA*3GRhB-JFY7X93Nc(VpHW}5?quzoHpHhU#?d(2I{s0I+i11Od zpYSTN=aP+IBZv94cd>swhqvvgy3_iuyI2p&;enOT6rxCbQNVXrtb9?tlM;Z-NjrDq zH$AkJ@X*}y{`fY;)f}pnm!<2sweqYyp02;ocMYWLAM;&>bp3AMl}@*!$U?+yF6*p( z7)p%k`n@1(Woe*rQVL^j0U7+T#Io^jtSGMYJM{64jMl9IOn4QsScz( zF$WdM{vjJ0@Hn?AK!-uCPAA-B*j4Nqx8%Vb&!+gP#yt!*P6;h$oDmLVF&1ff#0LbJ zI?-S?4!rykdZUgC7jaa;;TE0zid*`Fc98fB zhRJ)peoMU1x!RqMvMnzUb>MQyoRo>R_9akOpGxbv zkl-0A-!#lkERIN%34`39<9JnLSYDq;YU8s~OH3fObizoNrdAV`wd=1SHzceG-|vYl z!WTVPyC3s>KW3TulqG(H9G;P`-#Br3&!u#V*t5ZEf-u-%VHNiI1-9r;(mnKOn)IN^-f-@t!T4+j}Zlk z`X>-jJZcXx0)zcgB-U9TMLQmeJw6iqhDhuKk=Xc(sMuCUVjGdzdm^!ah{O);78ToR zk=VCJVm}#){e2{MyWOK=J0ucYi^RS^68lsncF`VDu^kYJy(AL*(MasiBe89JM#Z*c zB=)dK>_w5-w?$%q5Q%*`5?fvwCD}od*s~(BZ;ix$FB03fDk`>WBo^v?5v}m{NbJue zvGKj4IP4sWJtGqPmPqWEBC)@Z#LnM4N`|8&u~$c8zZQvoDH6MUpQzZ*ip0Jp68n`% zY<%A+zI#Ms&x^#~7>WH+BzDezQL&9jVmCx$-y4bjStPb&|ESoGj>Nt_68lgj_T@-y z@qnn#2fw&ju7^^w>& zMPff3iTzO|_QgnSb#;_v$46qXi^P6368lsnw)mQ;*p80GUK@%1U?ld%NbIf$N5ytw zB=)0`*ry}03l53mdq5=i>PYN8k=Pd^vD+OQ72AoC*xMtqk40h&heh!{BocdlB=*~p z*!ba5e2f-6urxJtPv_jKuyR65DZN6yGBvvDZdo|2Go*&q!?fq^Q_7L}G7@ z#C|Cf`(h+^$CIOCJ3bQohDhuKk=V9VqWBI+Vo!|3UKfe|QY7}pNbLC9C>hR(#J)We z`*b9>ye^9Gg^}0~M`9m|#LizI#rJ?n?2VDwZ%1O&r$+JJD-wHkB=!rD*gr;M`%a6B z?SM#ZBNF?`NbD1l*hQyD#ddro_PR*yHzKhwM`8!gh>C4}B=&8Q*n1+ezmCMtIWsD@ zog=YFM`CY}#Qr1_+i_M@YaiG6z{_Ir`omm{$|YzW16o3HVW z_wkP9$%FeXZh0PvU20&CBPK8V=S#4^`abNh<}=0p^Ib8#5rgxgK|H@&DAmsZSJ#G{ zF1!QKhyB>=LH`K%roRn-1NCb`Qu~g8ZzuS_1kH2M1$h7(*NLEgLR((rncLqT`ekBA-D z)FmWk?Gq2et0SGNJ%le_r^fH#mfpeq#7_Wiz6`s;^1hb5kkp;nasDls@wyNEPTze0CDJ@e;o9>ZxamKI=yBEr=rh6~f zHtjI61m^(*f^RxFOc>Z|FXair&%x9^5A~`+Y{`9?ZmDja^_NgHGWFy+u?w>0U`0&T zmjjn6uYMNgw}o|Y05RgmYBPnX>;`|;VoX~}lF1V*s2eby zIVhR{)P&}gb~96|@1SY=GEsd_^anVXrQQr<}! zL7AYzR}~Y-)=ZL!i8iY5F+Ij~?_koMX1ZY(lnFlol}Iw$d-8p9Gm=-GQ-1?QaW^2g z%|swYXv%?dLfz>DZ`@{#Jr3SbB{~hXmE9)Sk>TltF9h-|0C){rkn7=7Imvd{5eKuT zzd!~bVY}<$94utF99vKpD*`Nr3c>*v=zI>nDdEs=rQB(9NVPc3--71kxMz^zqm~rQ z5z@lFdLE0t{`K0fapWGm{`#1MkEG*Je4k)FU+0OBBg@Q|-Q6?E%;H0qMJIJzN4oY1 z;!M{b#jkrV{Df?Ns&HI;%7K!`Bdz#h^o{LnE{=e}d!K#0xZ<%F;Qo~tS4LvY#wFW_ z@a*Y$x8CwDOnn|k_{8YEM8^pi_C?0+yfO1+thzv2f&%x^$3 zz;{eDwS58F4H8xy{cbAty1xasv^efQ$#1XwlyUx!-xKh#U>^pyD&FI|8m*(`K27v} z23>(ky=Qvp&&KLP*oo|bEBcFeRe0Kj1TxLsp(0xe&2qyKG z<&jFoVAmEq%mT)DJ8o;H33AqDs+0p#dfh`{eF{0`N>$=*$Hds@W>T1a}5d;X*fP$lcF2tDyH z{A|98#jJWy{b4*&)K53D0(p?$M45t z$^D0aXjXh$I0n6!)kAyW_q?I8Wze&X;o9`+qp1R-xSy_6&Ux62hh~bPpyJ?BBU~W- z2)qm3>GJuxRL`n+!68++yF=PbcjqMKr=hhBlr_5Nv;2F)Y!2AF3_8#C-Ra^h?KIcp($)rxoqyb5QB*V;P(kLPHP5^14htNS# zFM|ZIgkVAw=@E>8iZl@r6+slFD*|3ou^~o9P`ILE_bPf3|G%~Np2;NW`@iq^eb1NY z$(sFJYnQdx?)&VsPl(B74dfbBTUL%hEzp)(B7jK34C+ovKjTn#Y|KQtnR;7qv=bI3!Z_E}(?YjVGm9?&b|f`5D` z8OIIni%AR2WtY=0ojJS1K&1HN?;BL?q(Y+(qvC0}Y+MOT4aA(5Y@ZX!?J;^)@mI}|^!9^b9_&Fk@-DSnH3{7l7fSj{8sh&S&E-ukDsIXt?Th8D1Jdb z{$#~(Q;$DM@!Qtp-=_HO>hUKke*1d-DT?2r9{*Ow?^ut2yW)4M$8V(go$K)%D}I-H z{1nCST94mU@$pr}I^~R_!)}ds~&%Z;`gq{AEo$x z>hVV^{w?+RV-&w{J$|v`_p8Srt@!=x@kdz;k`5vq=G^_+@-If4Obi@w|8;sp8h%w zXBel=7}`GcxzWC9+@|sP3v}xCacI8ykU$V3+ZW=$7M9~NZl(~HNZ;MZe6DE-4&dW7 zm5DwbXyz3+T@QgIB z*?yF`M-e#QA%o!4Z)!pj#l{>*#AU01YTJ%-^4LFDHn=;PAZo{-9%$BPD}lzlEZ^ws8gwI-kviMt^pQZV=FrI%qh z1n(+fBgEv{0H5;g+7wvlOW0E{!?uWtza4Bb;SizCvl&%Dw_hZxmtkMTRDO&iFxxMH zW#&WY_#WZQ;DyS#v7`-fmg52f8%D==R=hKTvuGv^@_h6(*z-6N&B`(dTZ1@#4^|Ww ztgEQZUqqBva}W)r7*gmq4ZRE}(@eSht@zM1Qrkx!RE%*od4?%793N-|{uvU(E!<>L#~hiMH#RbnF4gkj9b#I}@je3H9~EEK zD;H^IgRO^~vdMm-F<}9&yg*MVss(RFg#~jp$ZQZClth>FTQZ}913l~DkWgL{5oyFn zn*v&ILSiT^IrVc+3&tVm2jMf?Vzpo`*vJ|zXOCGika%a9mOM;$U~3$MP7ZUML2=5{ z8kE^gp0lO0Z{#gRSxa!I3O+6zZGtE+I>>1tITeG7KX;*C_6pm9tZ^1^uEd=+d+1Zt zIfgCLP?eC)kDh}3l%T9--BXO>bJ1qhAWX6LBACfV7f?}!_I!=%#b18nI%dX8HB@73 zYaQP9G0Q`{R0r{$RjMHYt{%AfN|iV3goD2nF|JdSl`fv^jXB&TqYdUCNIAHFgk1J0 zR5}Md5$49*pJ;2;2xU&_Rx@sy+&toHvV(MUIf?Osuv%ZmXlaDdEJ&Smm^~^(gz5OE z81dq2+yR)Q;RX;HT-Brd@CzH%NwWrbcjx|PIwiOvBLMR`r-~la)H>RQsl^ab8oxatH>UJ5s#av;Gw78l`&6a)D@g|OwyAdySF-A=}{czWxRyG^M z1~~Ks`DrPqAK8BoKpN}orD68aJ6Q&|qOEY%;B&mb*aL5h9UQ;dfn@Bm&eC4`!@l^{ zC^3PCG7ox+7!U4qpnW!e>keBaq__m~g|WOGL%u~8S0fqTh9nzMl$bpZA<}xq*9^iZ zHP4gr3^TvYCu5PVu1-%+`PN0+U)WZc9dbGL?=eoX5oBIwDZOz}8+De&Y)OvYH^bq5 zp*6tlAg zD@eR=Kx33GCk`(t$c{uWqo{Fj4JUz5u5ct=JFtasAVM_kdSe~BU)tX%VeSKRba5=_ z>f-IQNGX=f=+DZb#alh`N(G)@$eYF30md0GgB@Vt>4EgY4lwW_K{D6@2KK`xgB{*^ zBJ4+f9X3h$Oguh332$kcP1ALG_g)$H6ixN+-osUpvqV)}|)bekVLrsE4!B=fx&N@4NY zi|gESsczi~-RTb(4uhALX{0*?JTdqT7e04j#JTNGyp4&yI8&w-Es{c)=0aFbEHLQD z{yRm6k6qUB)sTD<3%=+_`kMYF=F;-p4Sjv8QNo<0>`QcBpMQ&bSvnq5OnR;mgZZUy zzXpGq<~eQfs=fmQ$rp&VJeDMJ<>bbudW9%mv!9V6PzKu-k4 z{f|ITB7S3Yx~R*?j5)$kpMl_RG7_k3xY@=X=RdM&Ob=xPh-i&7=ZeG*>bipXh_9B#ozJ8qCMFIE<I8X_%BF|GRa-w?r^aA-_7l+HtG zn2i{dJqY=feoX=oM`eG2@1atxrgCgBQySQ?GwFCm7(2@N4JgO99)8W`*iuH?G3Xkw zb8Iq~V^5hIF?Q{YVj2Si#Iax@enk2KnV3|NoT?Q2!{<#2q8FfF`qnCbfsG&8Mt^H4UHib(J1MQEZLP(YJKLt-l zMC*W9HzxY77Wr5}gG-tRDKp_6M6Ytz5RNz{Jq1f`eh)H#En<;I2#PPxnp`g^-oX3& zT-c;FZpwgUTSMh<%}q(Bv*M(p^dZyaQn#(WC*ar z?YKF~9F1%B{z}2x9Qp^dM$}j%{zhFtCH(bH&%Yao7W^ZDVlwGS>CYNj6OEZ8We4~3 z#n;%kkY1Qi{5N`8t=7oDQOFvFm~XP(R{>saR85mg;D%yK72h4E+NXHYF+uU?670cf z-O;huXlrctJxb4*AZs*c-%`~?U-kI^pzg+46K~SFrB7#>fS;+Q+BU1r8ryCKH$SqY z^cGhWWVMl&T+m6lKhalhWen1rHL1p$#A*JO!To(ov)p8EYmA>l));B?^!y5w*V10C z{Y)$MjtlZxHx3z~{v*g5R})_x-GF|r4Qiqr`YS*zZ+XKhw5Kzcr| zj}lhO(Wkx~Ijj!k$Z2(0oi)*}IyrJmZ8N>iWMk5(+F7;?+A_$Rop7!up(eVOwUsSfotz4^w&JlhI{$CY(W1uM;=eOT z-rt#{d5yLCUzo$%vR(jdz8vFm9j~0NBp35=EvGZ-nCANCNphybkfu86PVUYL+syT1 zO}CoV6sSZF{sFEqtEu))c*yyBfoO9;rbV~PsT?+R;G)@?)$IEwf>Dk&ipeno-=W8E z?Rt(cLcW>J*CabEHryx^Q;x^@Cj8*kfSR8gq7)VTCscA3T{31Qm?+goJsh*A5VHr% zJuI?Ao!u~V$MV7A`2v$^dp5T2`D0)>jziNi`J^_dL9ll@!e1E33=K8{Jx@dK(t{nC zQ-lWDvR;gldovZNdANwg8e=hGiplAc4p=eN)gjEEF#<}_Boc=UXlZ0MmEvkZOF2Qk z3{SO9_zrDp1g6^M1cH?I(E<6pxrYt-t1Qr+zO<^LOp+eoE8kLr5N{?{{(2ct| z#1fVsV>utBD%;0qht>d|@fRB_0;2G&1%rl2Oyp~l>+Mx>dbA6LCuG}@VsdV7nB3`3 zx?!S+Uc6s{XJ@Jo>tZm+OvQy~bf!$)D4>^-+?E?BSrW2&xJOd*SbUbEjM{`_dhqSI zA!{}T;_prJLa($b77h0zZtvMWN=8PA?~ZZ`v3dV(tLbK;3R?KyDcUtlj>*QtCE1c4;Y>)jB-%=G z?J<4l)s__Rb6XPP;uPO%N-K+pkC3#o1pI`SC4$l=Pr;>0aLIM4A+UiY*UR<{=!$wn zMdZ03vtHiPR8@rbM{(A~+lom`)(bWcL1xaHvDb>bOb}O6wplpQ?tiV#69u+qVPk{K z0}m{?7Vu`(a5Q#)N`r5^Vhu}W6q^H!MUma?380xl*hE3y=S#=9kBcoQZ@N7NQalGN zYk=I8qeH+IB4xPQ8J}C1Zrtu{DaS4X@sr7owrJuF7A^p}t!1YcqL<;(FX}k$bJ0 zw)Ri+JenJYw?mXlQPk8&k`|nfUWQCEt0}Pv=w)chwJZX*kOV(%x$cD~=LXfFec~e$ z7Ia3Pqk`<|HB95>2Zi*uj~Z}B+ck?#4insRX0S+t562eK?!$c9?^pFLSaYCJA@mYKp@ zoYD&hH%#7RWVHn5ZjZyW+1c2Xwnc|KJt~cG4(`Hm%PNOQhoxD=WV#WEKLR^oe({D! zN2Fny%W6Sr4Z{;b5(ncFonTiAJ$jYVuou{43|ehC9{hz{!vk}d27_Y_7+r8TI{P47 z`!~^6(0fynarMY*CxJgNsK%4Y`Jy8AP{ourm8%xUTE7aVQ(O0T=-jku9Cc@Sdww6Q zoc&4fa%4p}qN=jTjU;eptU*+yJ&T;^{Yh%Ia?k4F;SVxwHCi%1LJ>yZur2cyVQe%8 ztGZ1)I?!8r@?S!q;H^JV)~LU#Kdr0bM;#|K2j&HQf`K28XR-LE`iYU6O&_BId+^-} zOSCP20Ssxy+W~~)tay|=$TIu&c69Pd>V zKoZRn4Srh4pO*9!kJww0BcYUb_T*q3g8@G}CR0D32qkd1-%{W~0pr`*HXL}f$;w?P z1DKd&*C5u`o(}oR^7P~*R$I0a>%_8_%G!^OI*7}0ExZRAo#rMnDhqPWDbCTa0Kda)nFir`ht093i94l{fm}}&7;G2tsIdY`0Efw0k zrC>T2OI8j_iQr$9ru0R_@>##&SV2kwm2EEdR0Q!1mCG7B$ug&4brt)Yx*+uk$;Y4~ zFfXCVZfK!FnMRBgrQz$C%FK9HgcS$Xwm@z&I3^=48-={^M1uQ-V7mo|_VDV@FTAHW zI#zr^1iLb2GzFATX<~J(R2U4TyPS0Dgf)zN4^*`lh&Ix2N zBqmhlDY-4Ll4b0%II(}fjuZPR5n255W<=W4PtH;veI0`bH|ctxI`|nxmRfS#tTe+W z*$>h}&0x2k7O&HbVcB>OF4?uDJCyhSSw_55O9(UgNj@q8|H^e@O>Iy-vBr_ESUx4B zbk3PhGnI6SWFL$h7=7JzHVy^h6i}>p_Tkr?=e~|LBjO8kX3-HZ9@nY!$C0i`n>7$q z30c+QAv`$2jd96|e{}UhMqrd46=FwEV^#)CS3_lxEhoXK-QZL^8<5h$c3X64K3)Z+ z!{(u9=>dRWqHX?3f>2vdgi#v^<03j2QVdfe$`T$01X{!L`Wj{^5Y7r%BlxKr*UMzP zUuiIZ#o|#EjGr2+H%;oI-ZESxP28(iQxGW?4aBJ}N(cXR_I+%_F;xUxm zeQAT|*aB-1)V2;Nnh*n1+gP^|=v3$EvE%(DOp{jD5z?@-PE>rR;Mly-z}Sxa5H$Y? zSA|TR-#6#)AvbMR1^DL~)5_=|>}Sy+6&+x9W!P|bAGxJ&FgYcGF9Dr?C-hSsRiIIR zo*KxTLK}oSJ7XW^K?I~}yu7VBlEHJlz5}Ny4F#uNdvssum5RVQq>z;lW*^%4-V_w%;d@(9u zIzi4ry0e3yuP1!Itl>4N_yOD{;PDpTod}?}Cp{=0lSqJ8_IROVMFa z&xy=r<3N{==bJnpn8bMG=Q;{Qt~zO`=h4YEK=n87o;P*Kc+fnXo*c^won2Xj=5ppT zda&#j(=-m3`ceaY7^8#ivyln8=9g0o?03-ogVSXPR^mLzDx)dZS(=EXqua3kVz&EmTJ*QJwA->rR`9$r->mhvlMoo6;Tb%bG{XaOrbf^UTNIZ+hYh*}H*JJU> zo9twk3wg;PP3ViMq?cK)_N9OZ4HD28YgTg)`p9Ys&xHciJ z*+C9H7!rL$4XtG3I4lm5(Rsme9K^-62?zDav%?&i8;X}cO1zldbhovX$~oc7h(Ck! z$wM3VlkjOkcGgL#27N|;1-u$AtN|%<*K9Qzl2Zp9fQnO7G&zU!juI#to_lecPF$u= zfdk#`eW7XI=8i+pLjrGDIR~Qyg=90OF~8kn>TnPGQR&<44p#Y>A8x094)67<^P3bO z=O-4gXF2g8OC~_&800q);w39*lzeffcC5>NTY3+{p|Kf;tCNlc;j-U`ab;i{L~xWd zS+e~)^|~HpzFoQ+uy`2-W5uf2Y88>kLP(a(A=5wdfwC@+w_L& z9vYbX8?s}Ly6JM;bdxb4K;G{LGvqcBpgk)CDUO`D9i0{n@ZG`MzoYjCbGpSaV!RU?v{xV7t%6hf(aE(fVB} zYBLK@uTfv!PbMoc*+0sScPzuW|CrsF_Z5Z<-2$I>j9y>nval&n4* zyNtN89=%{vo$W2(yN*|`c;9sl5NTMvU_`6k---jnyT27eZr*wd$^RUNi_&=WDKv<; z4qBf9OFDUvsr6A{TbSbH@W7Xkp$DfsHCs94!ryNg@eIerUOvhgrU&uL zU*;)@m(S&tGrCuqHW*~SIkU0{#*~>yb-BY{YYO^ag~b+(Ym?D8o-d&42aR~2V4U11 zz$qPY;xTM|j4aabSb>!!;kfGEZ-b2H@3haZi=pC)>vE~8ixlyTP2=S$tw9^YWI`i* zwxE8Ms|((ql}luqU#?@S-m&|pEnSrn-^AsdVw7QQslh?d!1!}PIR!HEAU-cdo73iO zQ(Ty{i8UZFOSJ=fypbrM72@D0=1(Dk=4?FHOJa85t+h$jlo65SZ-$#%dS8s-E%%TzSQMrX62IXFqkg_|WayH2>P1TcfJI ztOxC#Fg+zxCltogS{^RFk4G#Rr*4YtI3CQ-!EU{Y9v`6JG#u_)=yhXP7VnEmK6czq zj>YWO%P9;yjdJqCPE$F1p}EIYstTwh#!Rf@lb#?F5~ao2XQCreC)_Gwal9kz%$4T^ zCIAL`sv+0&lIWFhb7l-nOqmAWD#2{pvk;|*hp?Dn*9PG!Z06I&aREf zn|6XsI|}b0$rmny0_?4jaNJ*~e3(PpceyPk_5iyCSg)syQ?%u+uM{zGep)cswB) z>;MCuzGSe&8w~{5eU<`tcm)GX2P6J8y&Su`rZl4*d(tMyrrLti%ZL*v*DB1G9DbkA zak#!my^P}V6i`>nva~W;^Q3>IIsnPhR|@DC`M`z+onOV~04rBIgv9^!kCsGw8TsRO zSVt3*$(aB3H$iyw#!nN!n9j}Z=Dn2(rBN01()bo(&;a&Wzjgfhr( zpZaaUgZgdhC!idoIUm1m&c6ZW?eL2)a~r0Ba8qds#VG4-A?gzS`RmM4lu>-1TAlSW ziUN-<%Si0RlF82-twmKH>KLq^mpTR`naslws1&KrQYLa&eYO>z$>a7HWjG4k;T$XB;i3U@cS zGrz`kNZhckL^wHB;=3($OoI~A+Lmim*5#mO6car{!*Y5WC5a;8q>OT8 zoQkmw*_M|iQMZA7%eK!BFi1+@j1<- zZjuvcC8-(w5f=&!!-*j@`DP*$Kh+$&I>l%;ym4{9^su?YHS!3u?m3F?oj#O znKwsLTU$;;WG%KfYi49C#2JLalnyd|fqdyUm8IE~B|L@s+bm%9?-;04$6=oXzea%S z798tpnE^80ykNYufJG8^qienKogM0nZ2O z_6jm2jTxP=R>ORm{}Nyu`VUlis?SiAH5cab^vfL^-sSRUoOK}~>qHAg>J~^XM`GzN zq<^m$(!9D0;)ab>*(Be{!*ZW01XqnbTJdwy;JPGR>3vX=Uc2oNc{$k~?!M%vqT;^r ze)zyG2#fJkQ3NLSk9<`DF88rusfJ5_JcTg2!(q&P2#X!sH5L zJOSnTkraiq(#M7H+YMQuU+ndn{R0n_@nl+znJ^m17w1bKoO!e5Xss)jZMI<;;)msKDOGXi5ZT>y9 zr)5)I`FmKo;e%Q4<0lgbKKP&{ca+ngqWxeyaukjC`^_F$9@pAo>mK3pMMgxM^OXO^ zfc5n%#j@RkRS>BaMlV`F+$S5IHOgKweF)LGI$iKNYfD6~cCDKO4v z$Nnl>Gs?nF`vv{P*j@7dE1b#`uYE4;t0B@wg7`5+!%|G(*|jX(-G;pU;X`B?-&=$( z*>Q@6Qx;%N0IT+#o($9r--HAkABY6Cze*+UjSr$*Su~kM$Loovu;?}ty;DzgJB#ih z(Hr$d|6tKn61`PVK3 zNrdT6tq7y|J;cOL7QK&Vh&iw?uXksD1*>Ob3)t}BL^j`cQODf(B3Leq`2vmx@WUe* z9{u1%qmiJ}*-1k=+In#A~2S*(}M1-I-IG&EfP24^WQ{es%_$?tJEeO|E>wE#@4 z2ilRzy~%^UP_jGJW8W{?o$9d{Np=^1_Chs(djPWXJhVrJ7Zb+hwYFRI`dm`a=hB;e zu9ID(oM`D~%izK0*?`=0k15$W2kV!Qhu{U-!-wDNWUpGXajvQk`(epGSdYC%vJd;S zQCA-Wo3DHIn^8Oko|;iS4JY~$np2?9P;&~BV@`2xc(*X{a@Bt2*s zljnSNQ$n9jEc>kxhT^){$@rD8h>uIg!^`%l83o(nt&ZrKP_Jk?#ZX2m`FoHj_4Kl5 zz;{j6OYv0CoCXh=W>`F_0CZmpb9~8p!u>d&AJMiwk$xOj$-V|%9i3J*m+wT(m>p3r z;w+!6g)O|9Qu)EXI+lVIdYr@sEx9*hJHv3&Bzz*V1;*s~j*VbeeNL--Ho7it^l4oi zh2f@HpVHH7ZM2VUiB{}}~^7B*QUI|`%VUG&-lFIwrE1&H}eMKF6ao%slVz5k7h8Y5N3(n#I zf$UNw_eLxhi8ohB?5CqbqV3hO_HWX1BmXznz9z%qgj5}C(???esusUfTUGr$)F}vh z8&Vboz15QB{VXw6T#V24%Z5HlOvSS)EdfdTs`aL{M6TJJ_^Y{GwXO8MTAxnb9r#$a zh2>+_>k#lR6L$%T-tt;VR!WxH_b;zsxCE&C{%^`_HT$Ms-oL!`vR$azt>Y;PuC>!o zM`HTT$CJ-cSHz!1PLB7K(^I8D-5OZ_0<{QR`HT3;Kfy+3P8l)Q(J2(qP}C1PinDx5 zd`Ika2z>ZxP?vMVMr}xjxy{=rslxrs=>8&*Y+5%5!U~tF zmEVGwA&SSl<_+?V({jyAQ3Z1# zFz5v%`dv<|0qi-DyZjIcD(GSSnv#RxH3jaHO6YA=l|M6cLG&+FC zB&D*MjoErPeA2~l-tyiAe3Ra^^DFhHR~l~`G-@k@-*|!j4Vo(c)ld=WmpL4^tF0~o z^#_K)a;`Cnbu)-=itBO=*IiU;s^{p{=tlzC0MM)bc%6GSY7YE%AVufbAuyEv0cMcG z;%l3-KcKc-+aGYF=KkQK>JJV=UQR8L`+zT~4{(>_HVAXYF-pg~4$C#{6`1@k4Tl}+ z%4<&nnj<^rdL8qQ9%=S9wcpVj9$3m}Tk=L=< z>sab_EGLJ%6!(pqE8h3d3-_HJVJjT5!9Aih@L9|l_VRMfHxGFuAjK!SZyxbTK(A{W zJlHubcsgDhKxuvGpVr^)_qj_g;%JKKPk_ zcNX5{EW3z6OL5jk)5|{dI_)peuH-;Gr}-Q#Js@$MY0L}dmneFNUPiyk5u?$&OLxlm zICeDJ0L$yCy;lIUj7}WynCkTz;q{p~&g+-=>Bqq%RI{>vG_X**^4O|wk3`=1(r0&^ zdHQP@_$6y8cEmMLcjP2pw?~nmVbGfVe8hf;<>$xDZ;fO9O=Dd)(!*&NoEN}otMfk6 z6Xe3C2&HUhnuqOn%Bz_vzfL<&pG|UmF^Wr{O+t2_bztY^GEfTgCr0DB5~D^F6z(i> za-^3*V;{D2`A-(cH_$mz?GNF$F~r4%LX*(4FKQ9?p|8{;ykPaE5801U*?OB8lGJg< zmLV^@g7SUqG3fnZKhH*e&vh97p6TqJ8=B)#F~)8BiUA$ul;1Nj<9i0W|Mv{eTQJ)4 z7Y*pOOy9Y+CQXDT1<MJ+MWKAL`Rn}aKn zOfED3IPe{JY#duk$C8K|M>rz!HjW62&|QjhHCIIXN8ZS0$CWH7m^k$tuYMEMZ=(85 zQojwCeupnen8 zZ<6|Lpne<5U-z7yq>>EVNdA&a%xH|4dX*!@>j?Ea!n}@fuOmV_hSWysmtS}3D6%JA zE$u5Tcj-v*W?*2}Vehddr`wW!ay&RmQ4c!2wW}A7WWx(ytpnwo?0mic7{l|c0qT@Yv;L}hxna(Q=AR@&BP7ipFBmhaU{{6?f&(cS*$z01Gka~ zx1%_NB8dN=@kGU~<3^grp>4&F&^_O~lg(ntWa3|p*q!VVt=ExOZ{JFsS<1OOc9`f0UOUK1FjJWmjB&0Q&(!G5?xlfKF+Gz>VL=M}IEztKRvco4# zUt{_;(+Eh-!WeoRthb9f7ErVJ95%OzXGV|?Z$@Q3U=dD^XK{Cm=N|ak%w=j3gR;oK zd))(@&0-Cg+Soo+e`3Z_?q=>J&4ZUvJV_{hi}>uo(F14;3y6Qc4be*r%3!4}5#;Xd zCVC&r%OW1)Qu~@~@-ep6i}NX-`&Ux#CU72qV>|!3fnwgVjzax3p7ePM{>`GaZ}B5$ z@qH(9U*r1r<6Lszvw~>cOl0)cs|vfc&xe$uit_7OLeeGNV|;`%wTR{$sNBCqZLx__3tBC;i4Gfx zj#}DkDavT+V~^UzP;ktm-xl&8xq;%@#_eT$tH+bf;v;S~J;svzjrLT^J-A)$@bN^$n#{%v8W+tKr6qnEIV$6?`cv3~;fMR)Ba&X@D4 z=lNkNxv#P&gE-XJo2V4N7)m;f+d_TO8z{SQF)E9~cHr0w5t~J{e4Nrs8%uUbSx)r6 z{nP^-;CL2rD+^ghK5uLzpF2)coYhRMl?dYsDP|+hY*E82Kg1Gos&umMI2_f z8|Wy_!lV*9ITVetG*T=CDg;VkRLW?%NY!aGs3Je9WC7lJ&tvp@2SSf3)RWN$g}!S= zvL_T;z_M)$eKUzC81pk#dRdKmk{(!*+RPPV-zW#K)BbmVC^BuBE^1%9%fX~ zgJkL2Ta2c-lS=vvyY@b#4M4AHZtYV>i&&PUeW6#vE-N=u3OU-Bj1I0O)LO&qageQO zOQ=Aj_mm?=%b|qYYPd}bGHWEE_Sy}Wjg26L$HfL&>4!y>hb~$O4k$*7{>w>r3lPP! zVF955TC72(lJpdzVOmR;^_-kkQYpsj9T~N}RiaKt5PaJ9CY7v(tTW4k8d13MdKX4{ zjHc+_*yrav$>%h^C#R4KyA+D)dLg5X&4gy?0}aai<`ohR;aG|bBpS*-U*AM%mOg?} z0Ee5Uk7U$Sp%TvD*8WAE3&mYJ))2_wkB>?;p3%Nj?-mz|d-NeD%5A$GiSUU;_}o@Z zs8XM5+6Z*!k#~#nRZo2-qs#3jT4$o_x*fH=5VHLy6rh+3e+$GSh5nO5Xax{XFo_qo z5?ZBD`6NQs3U$S-4z(2sYKu+FuuUKy2MW=wVn{kSPD;&&#i=@**loQo)$ZS z!jOkaN#t{v{wn+2{5bi10cZhaU*KAfLU9ntNR1R*FfA<<#@A?njRl#iL2~mi`6jVapmy z{X_jn_SqlLa|^{M3VqX)&_(?hmi=_A5S8Mx{wt$16G(PhzlOX=iZSykM_=pa0746} z@w--hrw0biLwd7Wm*4b|0MfesF(JC5#wRd31+BYk2}Ta17}&E=BpYowmd6HBEKQ8= zEQ`be=t5x!qB1HRPg*;TTNs6nA(REQ04bz!{_=n*y?-ngVglO6Kt{oX3FR9@82z+f zh&!|bV+5l=k-`M6jZwm=J7hDo_QqI7jh-Y~2V(-GCU^!mLF;HtW|X#qH0xwcg)Wie z;z~kYjOhVXLw>=Q-~!RzxDO~&j6*#u6uk(gdds%Rcz|Vi2oKq4qngo7)NZs*V-2HH z%CVSetYZ|6-l9<4YCOsa_s0WGG1fD}nI@pyjSY+nIhKDIn;79pEMy*I3!~XsDin%4 zjjfEXGb%NSW*d7LU0_|xjlGP1 zVO=VX7a6tZ^yV7-8RfEUfpLHlE=5Or3ynjJ1_lzk-#Ef(F{iM|IL0WNWe*syFq*<> ziSa6<3moS%<0PXN$opEc+<1-AlkmA#tWt>j)I#x)LYp{TwedRpe2aB?*m#3c1*fpa zc#9DZ#2}Wn#@mdNINW1E^H9<|qbbhy#=9(gCx*~d3LU{Q6pTm4MZa2b%=pp|y>I;K zhdwk+W@=SUt0=usj9_zZ?fH}tWZ1KX?1B+)rvByO1VR@TdWd_Q&y5I%4j`XFwoS5(HOQw zkSUGPX0CO?rVKx=Lrl%gv9R7|EcDlkaFd%+C)A}{^n9E@y5A=wt!0iVT&*P{Oj@=G{|fK{n3K7wwgxz`NWH7ekjT`hV}d>=OM;4p3zdygUvM2TvTy3>04W# zC`b&`iz*5ld~36cuz63U4XC)Q!FJH)Oy6d@f$1Kmk0fqSvx*n^!8iPkjzL_S99-funx{eg5P%(DuhEo+75v zFOb{Kw23NCce}}2YbU)J@J=??2FL37wC-l+8eCBUtI&18=wp8KS(gnE~+=+C4WmgZx z|Jk@c>8awQ2?N02F_^+qnOa5TRzncOlTCKQ0vWFoT|1BHkB^K2Kexj~(A5iX1O3my zsi0#ENZRO;Qn=5j-VJ)GT{-A8OkWx^H{B4&+T9P1zJ4ia0%)puY3YNYJ3%$^dTWYd z9_*=!r#lnFWFDI{x|2nSuwe*o9;|Y70>P10O@a?cEIvYhZ0@A<5_TaZ`ubs z?!aNtv~90|4$j?)x^yX#Eb!9$*Wk0?qwj$K+mf?z2QNJf_gxJs=9NqjEg?<*7)(@T ze*|e)rfGY~&m*kQ^{o6CZzTw+wLFjwHIbZH6D;3C@K5L~GU%_3U%{q%ZH!0_S&k zmXCAii9B!?JWDjKD9^_~Ft8Ok51t^}rhO|Pe`~ik;LJEh^tQ1?2ZDOFedlNg;vXYA z^Wl%uhg9r(km!V6`ty!0hmSV~BF7Wesvp%3{ zPU;idJ1Dl&$Eik{T;%gSLZSabP0++fNHvjZC(d{W(Qh0S^Ht_7%g8`HJ?!Ksz~O}Z z-eyBUOYP+6_KZBZ52UvN-Imb-^bU@{_fe|hM>u8&QZdBkRiyI+(Af~jA0)b-Id4>x zJA~cY%6fZM%q4(Q?+i7tPFI1SkSemCN58&CB84humi zZkmJG$V!H2)1Q1+cUS?=v7yAd_aI4kx!y^~ym##yxXEIM_)p7?pw5mvzzK8ifjeX? zg)PV@-R7o~#Xd>Tf_t@{EZnIXS-2_afQqXth-y1&Tqt>RAEZ08DAe~%DQr>2^NJGh?44I^z`4qKQP$Od$HvcA{h1 zePsx_A07QEQv4IO7ti}Rcjqg~J-*qe8L1+jYu!vImCjP8&!N`kh>1%-hvqYvUIo2x z$-gsf;>XZGLEBh~9*HLUpBSPa#u9zZMzlj5(O;O3k0-ZD5beofUx_4lUk*Ei!`{PT zZ{e`NFujk%w&bv5IqV`1YjC;?IqXgjo5=it2#WK?D57@ecjq{pacu7}oxyQBInFgq z-)H(W(|!pQb}!SBiR50u^lheLNgOlNR!qAy9n5qB(^X7gVEQW4_n5{vps*vE1~(*k zJkt!OEtxK6x{K)%rl(k|Pnfo1t+p}k$NEfVdYq}?RFarFn6_rxlj%^V6->7?-N*DK z(-79GAJgkhLpb$Brp=g^GF{4aH`AU2DHpj<5#5?a^vYtQTXqp`%X|y-O-qP#V+_%N zu|$u}C3=$SKBj$#5~oKiqD3Q!K6Zj=znw&{b|X5B{cK_W9nAl(7xAw!{cbe5Ut`@q zXZK6l#OeJ2(f79#eS@i;IkQ_3XBE>lj^Vds#A)p&`oc=0mpR?M&g9N`nP~fkL`&uo zEo9E@M&u4+_jg)~0hhwHcow&fc56VowVvS^Wrp&N%i%I%^zhrN@- z&f>7WIP6bMXLHy*4m*m&F5s|&)1Aj*w{zHr%pbyWx|rXe`QtgxRvg;}rgw3iZjSR2 zrtdKQfoUJEb-S4k=lV96>6=U~T#M3}HfP#}=|HAqnXY8Ihv~~q-)0)i^=&v)GnZW~ z)22-GnBLEH3)6#4zhJHI=G5=u5`2-%df`U0*LUF0S*3_ETD&*j7(-42{@NN2VKC z|B0;ab94X9FpI^QwVK5)&~!1Z8;ztFyUm5cJOg-~KGxh2tAliL7__KjS{oXF=YV3Q##oukBj$jL2`y-5^44gJ z-73E99s&A7(-_c?F;WkxF!9Jcg;%T(CeSGSGrKQ?TE(rrhHcrZ0sL>w&aj(A6Rhd- zv=0J&B$kKy>9iqFcN5^7)*$ zrXM)l4->V4dSjTMItZMey$1O>dyftWXZ8-F-w!7G0J}eh+Z$?S>L~CB_Znq4#L52O z+6l#KM!%1cUxpn6@##jGeq4jhViPfN-0Sck!pRPh137qh#J z=}B;~uHSSK+Mav#E6}~r5HnleJvcFta#YQJ7BY1*ZO60^V$eiKE~9KWxkH~J|9Jz5 zzQZvTNB)F3bE1jbSi?~(euq01`NiB7`Ne7-dDO&E&cz9i^9k${VDDuyt;Ex@1Aws5 zq@`%o@=uhG4c0J=73g`);tyzR7Bktcb3J*$L3;>ScqR2M=E>f&-rDU#I%e7hMALd* zNU!Bz2IsddqUIHs({UPRE!BbcLrl#y5rMj-iFGSTlU9?c#Lo_*-buO(s)%!vIgMGG zG@tedT+DxNBGr@cH$Ge^PH&wl=lrg4p(j-B>z%`a&4yH5W%|qZK&1YBAz3wb583C1 z6ms|6NBQjns)-+lQvJLNs)>i9Lg7Ct#@dY9pskrrd=QWX`n0(T=mFsdZEq#I-_#m3 zETAjsFGfGms<08D)~Gu`Uydd}{|)I{Sv=uqpEtXZK0iE6d?}T7yE$gb+0Nr& z9`@o8>V68Fgf?Xs8;383{cWKuK(z>>d!jagHjN>7=h)q#X*P1d$#hyAxijL4u4dX= zkh`2?xE8s>uNJ)mx2#1=vAcz{FQA$@!|vDFy*`Tikksgt@YyZqJ-gu|`|rF0V@{UD$b=qF*# zKwCyNXMQ`lTj>2@tNvX7&sgt3-8jcJp~;EP?576~jc!bS5V>32a6HO=m!uE$0soK3 z`#3OiVZMP=GEbAVd=b$b?A|<@Mrt{NcOK9WKCP38e#xQE92*4pNamko&Og!PVSi)9 zaPWupBidpNQ5RElV{-RxMD)vJM1MU_^e3hx*neyGna$L~{s*%EZ`gn5Uc;gDC)g$P z(%FwueX~p$1%AckiJ+!k8{P(6>6!<0h)(`@`7A)vyV+LdOy|N+ zg>gUpL~R@ZyRE?}?CoN|mU^vqv3Pv09-n&z^EbiRO)^ET(lqbhMbls7h=)bjlGe%9Sj;S3z`) zSgp{;ac?Dv|zaJ#joKck+(c}+D12LIVwa99#JMmBq ze`LY*1wl?ckXLBwHmkF#*r#IoIjxbinUYnd+^IPW{)bh<>lG!m~8Uq5jv z+$D+_Rf&DYABap}xJAdZota{?lHJ(a9EkoX^)F-EJKf@{l1=Q^87L*4q*daptp(9| zHz7l!*HgZAWZ}3mp;cn@!E9$1IT1@$%Eh)l{LsOJn;bblnLD+QGZ%A1VpPT7wXPt# zxwxBAwb;GC$cZu_ark*RAIq=MWLfhN=|djTbnF3Oin@r#Q{9#BkA4p>JV{V$oHh3x|uG zCE{U)79fA4#Ri3{`xiOKijNig@K}*^yl4&s3vp5Fcxah(B2H1u6!s&QDPo>NUvDpR z-Y!-$suHH&9|!zH>{sZQULOZc6*US4wk^>-!h|QFR6{;%Qld>0GZZo(dBl0Ac;1IL zI!i@hOPOAFuOjD65u=a`=q}NeQKi^Fc)Rl+u|}Z{$M-t#71!ytCzMLas8}&uxLQl} z+vZoC<>Kc8iL!geigK~CjYK&UUvth8R~35bPwrMb5e66r(D!bMvQS zzA&|u;pQE!apKgDLSJmY;#?@Y;$#h_-e=OU&P8I4LT_*W)45n&Q0Sgf!LFs^TnF-5 zC2~hwUCTw&j)W>j;HWs)N-?tsp=$9WZ2h2Er_h(M^=fg1(M4_I{r`ZEOA3wOlj_3J znL-(^)BQ!xN5lYy-T`_{jA2wMdhfBj9v7z->HsTk5_k2YaFycT5m~OyVuM0Q?k|kp zB2M;`vKP8yO(L!<^v0ytE};HWmV2tB>q+tP0Euqv)JWecTmu#AgsfI{p+P4mxOR(OgQU*|?OTZ5;+#T@+v9noFb$S6 zPy0LI^8%wP(e$~yU3-On2+68M|JL(dFN%>0ee7QB+9$RsG!?R!#Lo)dSn-hSfVg9* z3^!)mV%I_O{4he*;`2%CU5CYSoMIx>>Qs<^M3{>u>Uyfkc~rDhsBF^~*UO?mqe_u- z>M5WzBgto_I1hWiB6^lcw0>j&(6mu9mH~+6xY(u8G@ujW3ZqJK5f?q45_gV~K0iNM ztYL|D)G+Ag6KEIX(gMNQ{;RDD*+j9UT%@|Ezy-xrI<8ix9e>&dmPCs zMdhLZ@t(M2qC{tr-uvQbM%CgMq;OVflVmI*(CnNDXH+FJm%rqy5jl)1#i0%Fy3UJ{ z3T@t?iBHAqX)?|wjIPX)v2-8wq3cu8wpyYy8;YD4#0FGmvhKI97CAo?u62Y~iR|o8 zU6;fbMwMa~V!13{*(ZIb78f}$i;oow1o}c`?3c0!2NpzsDf%#~6m3dMTwjW1FG*P& z^pRJ^VTE>M4ER=@Qz#Fz??vbV>GN6i$v=IhB&{iv1{HQA~ zT^n>*hRYjLp|(MxDbE!-leIo4q|f-?Mb1=hmqLe66git{mXlJ}7~#@1yFxb(6yb5~G=&CX zd~c>*P-p^>Q)_TahFgi;W@>jRCrAI)C}lOt>ZcAa|BAWR15u(QBcg5^voGrj6@=3q!Z}sC(@^7WKQNx zZRdH3ekjfo_iFA>2~~@(TY6=dX>Am0y`_KVY^|q4w=5qF^tca=$*j z!ArC=3jGGzQZ48rf+5`Z3ksr_X(JWdxodsqGVKXQ7qxTA+cH;ZdlXuqb~v+2ds(4Q zO;2X7*4|d=>n3M1AJ)!$Wob2;YqYD3Dn;;)SbEFtGfJ;ol+4AfL|dcK59k#i(+)DK z5^NeR0&`xnyZ z%lm`$=d{xbrS)nay<0n{P)LiPGoRNkU6DTf7h2r=HRDS!igF*&Ix3V?nC?EJO;G4v z^eD%)-3r~i%k6$y`+(6!ZEmlY?pL)dj4H*I9hx|yMSM+hR*IG>gqky2CHlYG&V53= z@{N=|zM~6Jmv3e2vpe^4pVS8V&$bdUQj zE!&G;z0Z9{i^sqQ>+%ZhJ+1#W_Q~u1_q0pD$#8Q9ta87nWnY(Q+5k?KXr&>yY3>W=qk@KQ9R3Y=?BIoDYbcNnS?{-;RrqB-qFfY)yDfBMTm)a`| z^~GB4Ywff`3(zlpqa8HM6ndbxey8OJO3gB#-{Ss3+oezp(9hcUjH<;c^pU@6J%ePp z83^~Ac3h!rko`w%6f9*f==q1%+=uqL|I}tG^e42|^+gJ4BlfvX`ay+iFggY5XMAWM zzD;Ab$T(j_xKO<*qbl*<@O|!ZeWF6k24W1??@&IMx55~%pH^r`KE`nU7e=eZ9r+)K zINgE^5vWv_wj$I}p=~V7X0%GIf9!-iPG70eUPj+D!u+h2hHyu5EdbTsRr!Pl;H7QXf-Ks3{PGO89EQ4S9Md4&=%=D74oEBVBJNXa4H ztv|}BS}a~#gio1&uVm4W7ddnF_$Zmer|YpJq0eW8eH);bxG-4CI-nNh>su7sgIZ9a z_m7dX&oQdC)lW035{U(;@xdY!9Y==N?J)Xw(vuWg4Ae!>RcIGlKsSA|LQ_%hJ@n@p zVeUO7Rut+L*tsRkUmT)|Ui#!X8Rz$~buaxWqbjilZL^OaLw83Zx8s*xaQD?ODO3ho ze?1^U%C;qc?;faUGpZIlAuGaHUS*tv(Q*dsk+_(Xw6>yDhU%*sA@v?OjjKPS&~H6j z0KKPtzKK1E;rcpUZ9#e-fzM+7d4=AF&tg5BKjKm?s$kDz{XdK_t{lRtPd&7e3>SW= zkzS&=SLkBvSjZ+Q6xl=*qx205HC`GE6rU`^^vi`S{UD=C(Pe)~);N6-o_CSfA1#T_nxM~jNYr3ya@MW7a7*;ms*EhW!;~e_ z8)MyBx9PJL%6T+a+@S~MDxYft#6R@+8O;+%CbrC)s$W&;>ho=ZuH;Fd=}&jhnx=o; zoX|Y+#`Cvi-KpIzwf{<=cb7GYOKzo5{uHAT+(`mnZA&#CJJ#6o?I5B(Z&zkWfXrCojv zctCfxBcD~G=cBh~EzzeiS|xUDeABvAzuSlI$XceK^P$qLm3n@A3P(O?XRXpB@oF2z za(JWFwOViBLzP+8dKZP>+PEldt=`{sx&2j;sy(C51k3zC3HQ zp5IZX&?lwH`J_I(vqbNqy>HjIDO7>J=PCU?g+4-iz-19#q|Zmu&p)fX6f)37m`F>@s9W z^vMcsLF&i!)e2ohdw)g07cG?PL<4C3s=ks@70^ESNxfrFDfeH#vIQ2LBGbRQcN6kBI_spd-VOJ*~~HLvVPIc zLnZnu@3X9Hdb~m<&;RQFU7xSeQ$VdjbQEOA1vXg&^an|HIyUfLBp% zfB$=C&zzh?3aOAlf&qdOX=w)tEeV7YMCsBYfgpq;AT<;rh87G$r1yjlp+!K55C~OJ ziU^1(3J4-778C>&^j+&aJ0aYAy^r_*yYK&bpZEXA$8XkWueIy!*)wO(nVDj`S<4}- zcC|{`P5E5qEk&=+{;HO{G`d;7S7-lFtF(+#6h*ar$=Qm0sAWF#ilS$qaj)SgW4l}V z#t@Z}yA_?U5U-V$#}rksai5+meZ81&*7BE!dsz7@)Nrp+PDb^#DEgUrt-Qqb8XD_p zDzm)IfRDEH?Do|HWS-T&idntp)Yvbw$g!wXfDtCik%-`%=Cb zc|y_KL~*ieGW*iRZaU9yB3me$V`EwXqHhB|N`*C%+Y}A)@(XJ!@2kiltx3&h((KEn z%n>{E3SrHqA1F<<(W->CkYUOfA!~%Sl&>paD>E{zmE5c7UH1lIFUtF`SZ&$o(k!gC z^y_Dpd0J~7)<#xWRN0-WjiS|FortJv`ghdbYwu~7CscWFuM)n*22RnoIC5z15@fbH zk{nj-GmWDIh?b*<8xAhBom{Di%WNlCE8;TS%T1s(`>+OnL_3v_Yv>>gEYi4!4)TN| zuAzgx1hT%d?I^DoQ?Ia&vQdBTyH)$kvY8^T{bkuk5!c>H9#h2ib&^I3@}a&?GFK7T z*GV1%rHRWe==)*$8EC2~eRNn@XK4(uMt;zd0byNbe?^y$4DsqFI}fyc^=F)wJ>(`u zTdBUDvc(|FcWxt{*GbR877fg2-vdR@A#(MrmajeN+)yU#Gi*=UU|31I1mbb|tVU0n zJA!>v?d|HOh9%1Fih9*OTRleU~Zv%LGLY$d@A9D(XnS z6xmf#Rt={ZAbTs?Uc--QprX!eoOHMEa78I={D{UY%55624U&@;?WPBX4w5q!m0cOH z4VLp1g{`D>VYyt<+v_UmugbNG)~xdsuga~8Uh5OD4UxMPrS~!Ee&Rw!>thPShRS1# z-i=H;;A@>xZTI#tj|$eM~ack&b?WF18vVs39R zYEW29q{`nE<h0+{34UXxr=s>Wug6J$MaeX;$H`|Ez4dmy7%yuoTJd&* z-+0+T(LgG5f{a%*j>?=M+bNnmKVG~pyDC~cKf&*H*TB52!8EWUwOF zxe0#LWKBgi=f;cavbLfYKZ`d`r;>wBKgR1&U@>a*CO9g`(U_enjg*=^|jgMt8BVPvzdG zi?9W5;cv;12~3AY(~fi}r)&wbjybbsJBzTk&X&Ct@!C3D4pzi#YnB|Th}YIE`MRPe zE4;(smTxNRw!)uiQ4!JlIY;Iw;`MWm%u~ec=Ukbuh}X}#^0=a`8s6daXqxyFngR^Z{+# z4f32Kqpo-OMtN1yGj;umegs+jL$17U`Ed0-SN^FYXGgh&=Sr7J)(9?+szpy{@Kp3s zo$BG6q`#ui>x2`9fF_F%m(>a1EMK>LD08!%sv;M6ea>^Ud`r>Bu9w3$%LR(+jH;k- zkt-Cn8s#ar$n}ce?O#FPDz_;*+TT-bm3u)}AGXQ4Y23~%@xr!-;oD@|8%#H}7VF~0 zHu)CF+TXXy%@)~**Yc*w_Y_U2(RT9X$@7Z#lP^zRQ^e!)j{Hs$kIOsqF34)j zb~$J=#tePmE=PcheA}M#?FP*k-)A)m-!5aOaK8CsdF>YAJ7kMudNF*byfn4Q*FJog z>^_ZYj+j!rM|i$m4Vo`5H@z(O$lHoS7PJW8D?_Gpq;-y2AZyI9@|Ck+76q~yh`y`K z?;E~Pb_dNDC-YwoFQgk8kWXwqGAjIljLo#D@yvzaYCS`f_?MQGd{UAxGzhe=09oK5U1J@;ZpOil?|Je*w)A3wrGe|4eF&xIU(R z;h#%CMfm$7i%6I;iN>V)lKYXzymHxF$aarP+^e_9OaS`BrXq zitF;4@@?GeM|4~HCiZaBQ(W#U-<2n3GIB12-;^UkbBHd5-;(pKeD>wXE`@(5S18&@^u5eg`ChCOulYBRoiioOL|TiRxLZ7GWUtcJ~~YLS@#u78Bx zXt@1y_TE=3f}Yo_s0~rsVoE&N zI3m#4fqZnGefbL!6^s_!xV|~!)bfrI6^-kPTvjASgcz;z*q0@8+YX3$Ν@Bj(f} z7E#sk-EQS`4^N5+HR5+N%@+^4j)|yl^i}k2vE}?E5ywYthCTMZ9K38<#+HM6-IcBBG6!?^?BgacoRP17iYc zj_^ue9MQ;_t$c@$u8xQ`l6P5=-w-u1VsPM>I2rg0e*P(H#-Z zjdLJt+a?&7K-^AEBpBC0bL?Ma?u$q;JgF=iPy3l>W6cDktfDWP9VXv1iuzA1Q@W+$ zRMcS-(+i67M=&KR`gIu7XhoT0nPw_FK8|UXqAe&hU(uA;*mqjd*;kpqR`d%Ze^>Ni zIQzWbvs%4r2-9%gVlQ)EwNa@}jS^F5SOQPBj9bsa@pF`h3fn)5p6>!Zkt zkxx}L5ZiB-qSwZAE1Fx~T+vog4@F(DRfa1%in)}b=>8ziw?feb%z~YYc4KruROFAjbXCzp z%!2!hnqf=3@3-3NF_g=!tcY$FBZ^jZIfSWoQRH!^zKT*>GmTSJb0$-kqL|uD>lDpF zI|~#=V06zax?GDRZz;NhQTkKS4;VqeLaUu~F+-{;`UYjjDe}UYby9Q{TWqkRL)c>J zidrJyB1N6i_pORHVn!ZObfXTp>~lq-o=kTX{pro*a=>cYcm0@x6kWh}h*Z=Y^(82h z*ebmgO_;#>Mk?~gxV))oHCmmcC>^WmE=6mw{Z1+>gHo<3T8`D`p`yB&J*5v??R*Sh z6-6uItFP!Pd~Fp~#U9>IQFYAg35x8`a;xVkN=7?3C<;Sw_bZx=cAi({k3M{-=nh6n zJ7l$N5ca>aicTV54Mh!+uZg1di0q;$9{a-(MXzCWrzrXkeORJs1$=pmrlYs-EBXwj ze4%I^R=!^prDF~{4qNT~0JT4(C=o4lDq4oM-~~l4s69zhFxIoticX@2nTl4RlvRpu zVn*gG8ig`XEBX!ewW1@4{9VyYm_1%ctaf%ptDjSJ0((xwA|Fa=ujn&SilRG+oTz9$ zYM7^}C+1nMqKJAt3l1nMgzthPU#vFYEBX>Xc~os_v@AeTKDKl%MYGX|riwb_2-{83 zS&Y&!MGrvJ6b-}iVVR;ouorJvWMQ>vEe4}V4e2*1{pe;Vf ztajQ^L#U#UF!C{qK1K~46}e$t1}f@?F-udl5~VCqbQSx`W<}+(P8?F?i#`0JqR)`; zM@3x_X&$#)wi%J-6*a*u2v^h=ZE3D(DCQYGXc}jnFQLrgie{k?8H#)`KUXMVW?Jspwgh;`gD|&P?R1rf48q7N=+iMzE8j?Z`J+ zQ7uHKE2@piMT%k(xmA%s4M!9$z_@&_Xczi-M^W1YydSxouv#`6>sgSZAnf6hiUwn) zO;FSx)JsuEtfnIswVlRgzNx5G3{#FGKSb_Q6a_k|=oPFX*A#_-9x57vR+m0$wevQ7 zRTO=MzSmcDd=uB#R?$6d>3)jRK@$|M0L@XiX;7l*VjlZrfMM{)krUPB%_$t(4DOOgk%19)Lcb( zvc6SJ{5zJcB68=EctSh}pAgT%C&Y8`?c#hq2cHnn!8ye~o`X+_=iuC8AJ4%j#B*?7 zv5)8A6XH2Izu3of@Cor8Tu|)eIrxNl4lXSA@f>_YJO>vQ`*;pMA)bTuCIM^A+<6W@ zA)bRvihVo>pAgT%rNus;gT-X>%vx6DGkIpMC?=juImN{DjGs)-|BjkGvsQs5u1@aL zis-1exL$bV8s$5x&8t^8a&0laS+8m22IDa1V44V+^;wOL#!sK}9&l7}4V#SL6mjjF z4dWtwcHVwljPiOjeQQMpw|^H})y2IoeaaZ@dq(uH7FqPM64c%((Cr-2m}*;nvlU8FxWx_PxEF z;<({;lSd-meyea}X42)>0fg zK0cf_9$7>!D~LQ}^#2Z#M292K8FP#2gUC;eFF|yV-_!#Al5x}WiJbkPM}BGSyvO+_ zqkmV8!=Q9~pCqUF%J^K-q4$}71|4$k)PjjJ(Z55bze_7YF0H8##XcARH@=FzRut)y z7DB%TFQyI|^gWkx&ni>$2;MO4zoISTtv6osyJ1XMv~2Z{k>41;4=i74y54uoh*#vd z;rGZ}#%xfU{g-V{@*M!B+m{@Qr+eN`C>pT8f_}>gdx%l8Kb*=m!y@$kmazzQs1*0_ zTO&si_wQR{6DUn2v^Ar?HLCx{rKE{X-Ptz@bV%pEe`_53-SSOet%>i95t`1Ur1MC8 zXJkvJG?CN9E9!gWx}s4X;W2lSet2WI)>PKT6zcz%* z`SLRJx!wNioDxD8+F&{>}L52mOdNxtI^nEg8=H*0C;OZ*xtG2gzp!381B9*XoC^`fMiqNq#+ z`$j5qX~&dik#KHmM3Ji%-F&%el+6tGMVaDULaQi;xm3}N@TO7jrrpo-?I)k7ISynU zxqQuZkadjnHD`g+?QgD3it;rVD4M^rKT)=#oP+V2pSfO9?m<)dnR$x3WL411nE8tO zWYJrl%!44SzOp8DocgXKwQe0s6ca!%EzTt zFmn}gDHY7GLHsR#L1YE<*W!GWqAHqn8zbuDd?BVkh-=7*3Nf34xP}!`p=O5?ku}W? zinxZF<|Po|uy5_SWuHoybXtO5B>U%viRuQ+X zp4qNA@^(}`b7%=)LvwwJd=1S97TI|_G&IZdYtqC~J8y@EW*v|LVvB8cvL zX=Dyj#7E{>bE2Y~)0s9F=i3z-Yvx;I|Kpfb#FZxK-l&5HB`YS3k+soP9EL60rnU^!coLC;?DYiBX zaK2#9Ra8AUz}ec|tH}F>g2?vf4G{O;6!aLBK+bnn^J_uRxHLN}I=+v72XC$pvdZk| z$-Y8G4Skrt09|z?zEeT(WPWXtHjCDYPUdap`}Li8qPxo1Z&77uC$mWft3GXUXY#!O zvi93fCjH2cdrK|jQhI^X#Z|K!B{HpdZ=1IbKO(Et;%=Y5ik0g1yA{oDBomP zjeIws^8KWIvr7fk=x6>}!k1#YRJP{XHb+2>6tfJ-nmq%}3d+}u@(na=l<*BUoys?w z@(niQOZbMEt(0#KjnWYF)u();ln@&8N1!X5K5|8(}_Hz9y7!gy|OYWE+h#eL?A>6XhFaRxaTiV^&kXg|4qU$C!1C zee}zIvjfOlmr~8xDqQAOjn}1AGmKv*Ez$(9OQ~i{MZ7Mhn#qd#_%gk&i0>#*H5Vyr zP=;x{qOU=xKvx|r-x=;qH9xTk>shM#h4S%wmTJ;7iqMwaMPn$kP7RBAJxeuXK-Sut zYIadRUR%eS-4$^k#+sMIkPr2ZHNUY4^^G-uP(H42tocCsxW2Jw{qQICjWwHqto3uO z*&1Zctg&V%P`Y@iU6*6cWJT3%Y0h!xU_}8QY0e4eC`GS%r#UB@6BXHg)0}DM^rA?= zG-tY*rRYA<6mzknJ!R6I)67+hdX!CbW|*54`TD0h-!ykBN+X(S((lD+1k*(ok>;Fb zzON|7EzOx_ex&HSXPR@4c~Q|2pETz@^D9O3sAUVx?-VVjwk$I5DteXLvc!C>s4TT* znQ4o#>dPitVU`A2W0qr<2U%m5V}_{6hvsx=jyY7(H}12XE6p*AV!dDTTV;*|S*5Ht zr-7_eR-3a_WQ^X*x!PQys4E?1)|e|4edd+qTx+faS*5Hqw}Y%w)|q=$WF>h;UuPat zl;%F)xz0STNb?>*zE42bXYF<7V-T0Jgd)8ox$jf$eEn>r8Kj7>pKUbjDB|mGxn^@k zeBCYAOa@tHZZgj);@>iEGCiZX%%g&Tp}5%$1*O}~z{JSSW*tTQ$}u%pbitpgv&zT6 z+}v!w1G0L%*?iB66q{YEIX9ahD%#*t&AG+ATq1I-`Hk{jpvbM}14U;ka+~SqL~n5h zk!SjX(nSm%o$}1Yr+odDufMC4d?TOoO;Em3@GXAIw^I39Iym3QAZz>OnO{B?c|%3I zX~|)E<^x5EI_LXS(E^vX&OEbR?V?sMA*uzk%G_brFZR*1jm;*Ybn&~+^nxOO#>ft{ zy`no_Ox+ZHPpje%vyY-bXjR-{4pelBR>d9Wa7Ed)4(>3=DQaAnDP7S+&@4r-__J?; zBD-M9R@BOkX@jEoX@%ZlzN4re+OkK{7_{ZEq6uirNkwhZmQNJ@2KrJ_I@)qWQ9ZQf z2St6+mR}WRqAh2OR>hM^niy3H(hoWt0OBqF5(3VPyGEn>Tis&(L)T2m6XFv@U zy@0kfRWu51c}dYSw55|Gf3zi0(Gs*JMbQDYg&HX)(I?Eft?}9pe8ObvTHzh%6B3ZcNDEZu*xt5;%sUN_ds z^h`lrEIPoY4V%I#9_jE-eWGWdVY_n$R;!#J8c``CI+{~{pjwLTSfs=yae5$1-nLH! z^T>0`hkZEmtDaofx1@%6xeKSyY{=;m>vc-6O)-Y}q_a*v`mr;6kF4kSf6t@mFzRCC zE>8Ik?VsC%?Fmhvl;5x>ryQ!t`Zk>@=wjS@PulCM9`*Fp=_&I6smdW<3TJ%?W zIc6!1{6EHUda2`_{`%H`m-nSvf8{Ni&ZWJC{&u8sptowoa>*U{l#KbG*j%Qyy{;VM ztuu5rZ-uZPe^n;OCk8*!%DHSmQC*zu$SICa+|H97|6MsFXbx*)#4_Gqe{Nv=JCwFC zg6(?SxCA@3M-xGXY`>6KvNTS&>b1)0iS70FY)-dozp$U}R!D#5DBE0`u4?Jmi){yH z+7^^=UtO~Nk~Zf(vYq2U$39|hg}byZq}Va7WMoO3b3Gl&DLbaI&hE;ZPi4}5mQA_j zgS0nkA_cvEfPMVeL!R{G{E@vqJ?U;i%E~My`i{7C4ChO0%SJFF0^EuK==T^~MM{50JkKSF#=@XJm zrkAw;y|-j!Nt<*1_%gSA0`*IZf*n>l|GqT#@;K-sX_Tj@6ir6+h`oogol%#)THlhj zl(gB~xLwJVk~Vv-v8_uhl@viwjYG-E|2J)J)t@c?sz&yfqV1xKz;1sP^Z&JU9+Qr{ zS<7#>^uIL%MWg+n9c4Jze||jrZ?*aFl*27tiKDYM|KsT~-gI=vs{VA!UzKK!Gbqt{Iz>lh>uC67)p~jb z`(zuhjeI_1ikO#m$`!beSNp3A*|v@m+gI|L*t2v#W~lT{I3n;Gs8I@QSITR(P>M2W zCy!^*ws_3DCC z=5q&Z3mWV2*sfkkgmp%-OQ7n35?a?Z4|4_*YbZ z$$G7o&|3Ze+jhwj=HC4`%KyhQ|4&-4tUi`(VM&|4{P~^smtLICM@F5Fj3s0Kj?E?P zk7ea~u8RkWPi#J~kixn)!h4^t?2(7rKDCY`|4R8BtSIK++1A-w$;hHI|6Q)4c%J7) z_P>iSIw!PR`;U?A{j0I!K0b^7T4xbO>o%wJ%;&w8k8V8veB9^r6OWdERoedv>D=-= zxW3V1H`|rz7(wfKH>6{|{TThNM^}O9jFi?KT^zy6a}(ZE^od&+8Tc%3r4+X2NZudm zS|9A96z=a#`n+G1ZpB#h$FeQ8wn!wks%T3k#c?h78$HQevgH5Yj>I*(|69kQ6n%D} zs{zHTi~eDp(zG4VipHcghiNtzrT3(DKoiw6SW8~bbKux_2%oB0t)nD;0;02vBHOxt z=&_W~BB~zc&kk2+a5)hzSlO$IHkj4(jpmnB%nFP4RA$<>gO* z|Lk21??~F#x;T&7^5jg+Ivb?3P%6_pACsy*Me9G;Wwr1SRz=G$S%Q^fmB4FVQE6;T zbqxL2UhC6w(J{_CMqB5HRy-e%=#AF2jf%>&KIdBA2_2uL^V!1Fb(Kts!w83=kJese z&9Rjjld(NGuOo+#an`pad_JksJ3M(zdXv(%Uz#Cw#ha8qee_fsEby-=We^=}bkTxR zC7wXy(?)ySy$;wJGR9( ztjTZS%z>4@OQLJn5?<@O0BbeY@f|_QmAR<3&o$y)O%k~Mmc5KtS}Eoo z{YmFqnplXfY<_?8R0~tlF(l#HX5Yq}3{GU$ADNwLbh+Jg*Ga zTA`tKS`U|VEhS4kNGoU2)x6y_&P7-AEZbUJYBlFvz2@){vlWeh(KoPv)xrRrRa$4@ zduhwk)erinOp1!wzxbHT-*s|IwSSqySO2({zaRNm+j{dXuFLxL#Gk4Ey)8wHEUx!S zt% zU?1mC7PN2TD9P)gbp_k{#`Ak(8+o{*qS?m9ewzb+@YWxupU(R>O8A!=OlPWzTn>A6vA8nV7>Tn}D;!0AXcuS_GzB^gnhH%Lr7|_v zwxs1m5@|&-Oy`sh&@|a?aaD1Pw3diAxUMcbKT|W+;NPaY3BTRRZTl1ys0j(C7N=DdBtldr_(^>6=urpKeOiibS+m1hcZ_5* zM)eH6%atR87yqmcLdqb)+lAYv>!G&sE$w=o&1I@P=|*b#8L@VKfPO~!&>M=Wg^hMo zzql_WP<|9?s6HNcE7+68M0!&EWVEfV&d<$^rua5`rr@WLhU(ks&hAjXL&gAoE?T=3 zc|-LZ9cGiau+4mZwYa?XT|Ld_pHx7)>|gNcA#Pax$S#_bYDDMgQ8d_<<`Lt?oN895)yXdCw>+SX|8S*hv437^U= z+k^UF%0;#B%F78IOt$N4Vda8by?ygJKdp9diO<(*9>q-Z^tEumLQ7>DVp5}I2V)=FJWk*W=9 zvD3)Y0xIk?GW5qiuTwi)boo$n`e7qgTju=0$imZ2Q?vxSQ!+*Cb%zd~c@G8saH3#k2M9 zo0+IF6LlS+(&*K?^t^__)W=pe4O^s*>o~^H;c{>T-Q`Ss+-a8)7?U%Ue$nM3 z=~b5immj*`ba5Et>Avp~7^RE)$32~51h!GA%h3(I*YKG0zT;|V^|fbd*?E=hxAg0q ztCDuzT+@C>pV7OfJw<E_}I$UJ|79COk4G}{l0>ygc~bT0Kb#<3LXOEEvW zr>0nnIk^<`GsWh1WVU0q)~oe4(%MI2#9XbgO?MiHgZVohA;=qoayG%XgSanlu!}c6=y^i+W#+ctmx{A}v#NZ*9^N5O6YjfW0%;k~{h&J$W86J%)K;9sVE@?TOkBnDg=E@mDP{-a6e#Hj_c!|=5jwN zg-W}>fZppTpF2LpJx@E^lAe!Zo84)wdxp!Y-jm#~YP(vG6N_B@gQvPLa{02#3evQY zT+(VGyV$OB*xjKuT>iUzj>~}dp0sxy%y;$R)(#eV+9#8JJfiiD`I_ji^FF&5dv3J8 zWyxt5hyK;niXIj9caJ^`ZSIk*FRquVY5J}WT|Dw!UR&GCW01~!${?MO&Le1SW9Ur> zFU_o==egY2p6W3IBTOs#?x`NU?!D!aLuXlZy;-YL#Yrzx=W!lS+bhVi7yH6wviE7@ z^`0+p^thv~Y`W7U)HayTSPsxw(SB_^Rd9jyn}X{enHbwlY-LR=)&D1t?sV+vsdq!fn(yjDl8lOyUDW4rVd@(Eo0#x^x#~dGm>-CzCye^pA?&?H9?O zT{@b|;qk;Cs+3pt)kxo@l=r^XI%^iyrO{}x?m+u@pf4L_4?cG5`wPD+9cSptH%l+F zJ*IEU^D&Dwy;IGyo_Aah*9`K^ll&%tXn8xo60KCtYSr*eqN7PeO8H`Oyk~~3xAT%Y zh}ve-7T@S>>A9D-S8H(?+v|YjCu#6{YYH!Fo1X}PI_$NQ`guC+fk{r`W#{eUuzz21 zuxEf}Q_KiYhu$g7PdM~fVm(P$(=4h;UY=un=m|KfwgxTMc!nY+Pji|1v1hnFaHmt8 zzzCedOgJN3)x1jSJ8RzbJb}KSK;NrlU#O1#eS?cm=W@KlJoKcut~PV=N|onnWKv~C zvNLQCW?wQhY%kAZZ8obPrD$3Gyy)GpYlnC_w0iBRecjQQ}C+Dk@WjDdKUF*pHP>?miK%X z(dUJ$E}Fql$$2Pe)6KwnltTNg@hrW|D9@<3koC%5R_8uHahFQoM`ypO^knWcE+2O) zQ~Hd{=2+GqO?5raj^m?C-u_a)4r5RaIwHGNp)==jTid2(N~1*pVM%oN0BtFgl;^M;V%#L}^=^_|-j?cp-M6j&#@dCxNw(uJul8+ge{}e`ufslv zwpzIDR?=DDB)hea`PPWnl4+*x(?V^H-oES`Zu_#+*S_4s!=+dsdUi3EcAHo_)b7GU^jrBB?onS$uAx$@knU}yA|vXusf*8d%ZjRrP_zC>**IF9~|j!&yv+J zGc$7Bjq0>l#)v2ypJ6UZCa?3UazWT4zl+B37OVU+?7S~uHUI4Jl3%KnOZWRVK)>Rl zDYhSTulcot-2r_GFjM-{r(kpR`?Norv2mXC-ZviqEZUoDi6r_YSguT4d@78>$aF{E z>ZGB12v$Wev*vcE;PH96Ofp)#$lhwoD`j}E97HKRhRGB^rF1e%pie6`_ggQ{q}57OOpMmDW@c}DvU ztOW=B#2K+Vzl%TbecN5mh{Khgl+uUp2RtJx?MtF`&UHqlO?i#%)$}I2Ow6b=;?&+$ zf6XXOp8_&5(@vnZ3APUoSJ51{o?SNiJ8Y9k&&fE?7<$u1S)XkVhi!J3^OQcAG|ygk z>t|%Q>UP=Rp`WPu9qE*WJN_ql@1Qk#LBf6iP}_vft7#R=++2~4r+-Xi+ig1AzqDan zZ_9Q8J+~y(cDliJva59FlvZ2V+hYsc-Dp%pZ3B_M2I=o2{l)2=-VLRVeftlps~z-w zyGC?WJ-f9L?aMdYxdvQ9zo>*)cqKgBH_GQaQf`xm;?qGrpSzfK{E7aRfV-4VpWQI` z9$_XJH1hkjM~FWo<_5=W6|pVcknU^nnMtaClb)9|NaIsXi ze&3XfM+xz$D;~A9LM9M<5>M$D_YP7&%-X8+*8P?t`mn!!(Ti?dI8e?;I017|NZ561wZSi6Bi82xMw#|{$prth0`!qXUQC|{dr|WjvK@PfBw}#Z8QXF)ZZX;>1*h(5A zc92#T`J}O8AH~OuL!@oQG13kc?;*B{lcYPux1_s;O9c-)M~kT7P5bd^((}-Z&@Z4@ zp2^8s{~k4yZdc02%}hh1P(ElX{3Ktq0|DBBdcTj+EN0tqJlLO_7oS zeG%Fg+83Gv9Sj`?9SKc^PJpICr$95HGoe|~HP8*v&CopPc~ZOhRC|rub`dFGK(9g_ zI``BangD$f+7{Z8)Gm^t{q;38=7aPl)R)2V4nz6~*dt+&p%`y^V>#yYUzUbSE?)S^zx&Jpw%rJqbMnJrBJI z{Q`Ow`Ze?x^n2(X=soB|=wqm6V6TL_L%pDW&;U|hR5W;Os~{y5S_2vmbwZ<|4WV(+ zrqBfFi_o^vj?ga99?&FcUuX(+FmxDnBs3K|0h$J#0?mNVgl0kKK^H-nL35yMpc_a% z=-Hd)p7EwaoDIv@kd}EGor{o4*P@=L-tA79~mvlK0~%$ zoHGv5K6{mHZ+e0r>n-T_qz>_u(TyVSz`koFl6?=}htS7R&BS(rI-u^PcHs&2HF-_& zgY9qf>Otqr`S#&fnC*1qnJ?G)lJ?jYrqaOH&T2!*|fhv z8$#otO`!?U7olyT9id&IJ)lX@zR(otVCXRDNN6f_0yGUe1)2e!3C)7egD!$DgBCy! zK#xF=LkHWq<-?#Op{dX`=oDxMbS5+lIuDu$-3k2ydKKEmh0E*#O@j7?ra%Wnhe1a| zCqUDnQ=l2pnb0igJm@0mGH4ET4Rix^Gc*sn6Pgb#fF6K;0lfWLS&_YjpwH9^#l|z%w4=grjRE5Amtvr7E-vQMyCicJPYvJ?x(x9Vz_|?7NPx zWZ#4Rz`-lbL)ecTy(s-L*>p57g;@l3K;5BUP(M;S!-Q5X#pj>ZOY!+>TO+?=0QsXIC|5ALuItb;AfOibM zOJQfjUX7UbrTAQTQ>l5h9$rGLz96-WD-=mIp4L)V?pFY`BD4xL z6j}r7ghoRfLgSzb&=;X?p&g+;ph?iaq;}CCb_(o4uH2r%u!p+x78wTbNN6fF4LSvy z0i6k51YHKrfv$mWfNqB7L3cv)Nu}89`UY*s0XB2)c|^7ddV`1J@vB19UT~U2KD$2YZJb z_kAbq-EQ3Xe0U3xQV9D1?8AsW0{a*ukHdQsdIowPdJ*~s^eXgg=q>2?&^yq3(1*~+ zP|Y2)8tNdWBPlf4ozKrh+?NEE5*^*=(&#z8IC8TuYaXdCcKY(!=0&7@0sS6&hqR_} zrB7isg^$=%yQU~F4wF_E*GQ|0+oX{~uTxXh7k1JnB7*b9`P;daP7}}*9_WjL6uEzb2@Yt^bl0q#%a&jm$r2Jf3A7J1 z9l8p72zm`FOCuiI1lk9h4qXL31ic28o`{Dwf%bu>Lsvl$L9aoj7viB!pnafe&~#`H z^bqtKG|8KDt%4qcUV};>lm=}A?PI0*a(WZr`a(8r;`^qk(XbEMwMplQ#D?kcPWD~J zWx_rLy#~Enj`M2eS>vE-&;!t`P%RK%Xc9CHngcxmy$aQWII`O~IytZl$#$(sQBGyn15ho5?Ih>{=vAmz z1(DD=sP-(U#8qX@31vM1)t*NrG!B{sO@oSvB@~$*%Q1z}E3vuarMg#Oi#SdXhQ>k@ zp_8E5&_d`HsA!CMXe>0b38zegW(;e37XxB?Lug9XGB7;Kt&f$$?md*^a|{&P|=msHE1xjA~Y5n2TknCxsqUKLkpo- zprRX>5DbllCU)ca#O|Cv37XxVQ?5YcdLX_hYhof|povMaq1n(vXmD>%kA(&&!-gh8 zCqc8Jh0rTdtuIGbgvLR$U*VJ-OZ%go0enPGgieBHL&ZS$218>9A_h7Mnhh<4ULW0! zjzOz4S+6YRTw)pWLSvzc&`Hp2Xd(0pR4hmQ3f5StSj%?dde$pYu>mpASZE^j3RG-l z@1!lP+0ZLneFOrF+=Uh89AvK*e584~E7QtjaByl~r=>*;RJZ|M~QP0sX(9{vYm|U4^~{7D6ngUkUrt-(~bS zkp3>Gzg6k)3b9bE7bnE$;#={9_+12R5n2naxAwL+U-P72FeK~K^yT_W{g8f4|5*Q2 zFC|;b9`csFEA5825oEMArWPi0_I^YCHu;5@Ni8$2Ojel< zWe%1(R_0EbKgv`tTeWQcvTwDw}A)ChF#X>Dv zETP|?FVix_3N1_IXm5*Enh<>#Uvvu5Z}k_XU)KMMbmHC{q$3-!J)-vCO_@~pTZ#|M z|B-apf?r5ocHJj^`>o$eZ_G4n3o(0xC+XM~TxPl5>>Y`iOEowpZ4K**rkvhpCF}lm zT-uU8Y=0ONKzW1PaLjjaR3Q7cSs|oi(etEl&8$V5*NNkl?Rwst+zvavCw{r7SS zv4!zupPj>P8;=(5dYkRHk+Oe2+mDe_85)h0mhk>EkGj%_Qxh|Jb5XIy3W1WtqTe|Xiwi(6a`EeR+b}f#%3AG}ptn5lDt+5?@ z_evzY@==a_ko+W9rHof7{nHr(Nk3l9>0fT<(Kx-8bx{x2R-1Y2Smj*J972%+%}0_B znmCTM^CZ?kMzH=ijP=}D)|2B{-$6{qYiwV5mGwSS9t~&v{1DdWh+H?A?M3ZbE2Xiv zM9c@!f+L)gJ%)ASK-Qz9SzU8kGZBAm6x#)esj`smA4jtOfik~={*2ZhMwzubu-7e> zbsxNGXxn+TXTj^79*q{hgZ7Ua&ne#wU>%M0%jnTVL{44C>Dm2RKZDxPDmT>SM60|| z@_E#fi!$R8^E}E~kJ0-SktxXab~Kl^9ohq}{Q!OceGsRop?7=HszCI8DSFouJ@OvP zF-8^EYay)O$5{urW{sH1+N3t?0+dr5t-4l=Qw(g)pV5-v(9`b_tn@jJ^v8azpQD|xAl?P7O`E{!Wl-;Wlr|0ZZb19X!MhS$xdD2u zp*?G1e~t9k7|$?_o7=NoT7Q%ig<7VgoIsR$548`)%shkiIHYevN(+qVD75NFl(`DF zD{9m+GjlORrXl4NawVgL9C%%kD+Jr|3?f&-dmAG&7BPQ7k0YfWdff%3eS{I71h0V- z?jhwgB70*TYS-i59flo^t?YtXHxwllpht62W_Ro<7f|~n=txBB7&k9$yY?wu+Ao-; zUa)H*y%|QSDcVpU?Kyz49SYrv$kEV1jMyynItzAHsM~Z>SC8&1~%I|o9 z$9Wv)Lq%+n$lEW8?f2hj?bd?z$28Xa z+gMi|;(oolpL0E)%33n!2c%o^%Nwks(rh!AAji z=dez9FSwBU=ngvn^d;4WpYBRpMt3I-&`VQ#IZ|De*S*LNB-KTb?n7Eh_oI}`q&nRp zP?ofc9zg2U%Ts!7QeD*1gUGH+O3%L5pCP**sV?g470GTus?(i~mC0^IN_PY}M$jqGNmx@fM~AiD)AJ*$E4u%tUfN$KgwdN|pwNOkcd z-E~RNg(lTSYu!n98&X}g)$5Smj+E|A)}zVpKuS+o((9A`GN~>)=?%&5OiFiv>M^8U z^*GXQdJ{_TPO6I@dQ-A{lIo(D-kj`2QhLg+ohyb`j%2?|s*53dC$fi<>U4i*7qVX?)x~hV8`&dB z>1oD#53)y*>SDCsi|jF^^o^gMMD|!xdg8aSChakL*dLx=7Pg z$bN%V7wP&yvL}<$69)9bWKSj4#WZ~g+0#ks8BO{yvS*O$;!S-x*_ouen5mB>`z=yk z%+g1bJ)4xCeW$0A{WhsC7U<*1UPwyM6xAn?y_i%NOZ16kFD0cX%js!kFDKQ-3O$|d zY*P9jNuNUYN>W{{(x;KVnpCH|Z!^eVOR9@?`kQ30C*@x`%_MsxsV;K$S!8b_y7>~~1%K5Kme**i$-`y71{+3%9-Vwb*z?A@fg$k&&V{T`_< z_UJ3f-b<>B0zHT9eWbeBudgDzkW?23^fhE3B-QB+d+W$POiE9|(l?NOlvEe*>$zkf zBh~5dugzqCK&p!m^{r%|Al1eHV(;DKBs;46@S3?jTCG;AWsz56S+Au<2w4`p+L_tc zSkj7~nV#83I}c6I>>?0udb;n-w6>>v)Q_DVBm&sRp!jVE+r$q-9D<4CV4L{o5E~PW z3BfkTm^hgDiSdgNhdBN@eDL%4{hm{GZ{6FsXP4Od@3;HunyOQ$-ltBTI(4e<$A^Cc z{$GVVY(6pkUig0vE?$@!{wetX3*2Gzso|f7|2N z?yxx!{1W`ngF6iCt&hR~e7M8r?%?Baj|HEA`=a2JfZqcboFw>l`0s@~Y+e$43jX`x z4x3j5pN9KH@SAXJ!EeJ2gWpB$8eGhy;4^SHg3rRO2Y(3nso;;{UI{)2w-J0E?q=|( zaGSvw;I@K4gS!>{Iozj%zku5gz6iGydOom_Q8E!upjO}3J$=1eQ*%&8-m;6zA-oi_v?c@;eJDK816R)N8r9G zI12Zhg1g|pIXDLQEx|o-p9}7V`;UYB;C^#(KiqE#9)SC;!Ew0X7CZ>|+k;WK-w}+% z{m$St-0up`!u{@G0`5Nv9)kPU;1Rgr6Fds{dxJdO?+d2jzAY%g{ine!-0u&HaDO0p z4DJsGb8vqsSb+QX;BmPBEGWT!N3a6-p9h!W{&4UFT-cPueP{4$xc@RJ!+lp!f%_vt z74Ex(5bnPU*5UqWP>1`kae|2X6>P%&H$e;TdxEFo{y0t(F~5Qy+@B1#;l3AlT8GWQ z4W5DfQ^D)t{y)L%;r?{+M!5ej_y)M|3*H3x-v@7o`!m6FaDO)VX1MPUz7_7z1>X+$ z1HpH~{SU!+!~J0JR=7VOd@tM&1#g4<3&Hop{c!MuaQ|cQcDNr2-U0VN1wRb;qrp4j z{$lVhxW5#<8}2U$KMMC_!H>cHmEb*aKOX!9++Pje3-=SjPr?1Q;HTk!GI$@{{~Y`b z++Ppg5BI+WAAtL*;Dc~~Blr;9PX`}{`(J~P!2QkOqi}yK_$9c%9efP#?*t!*`@6v> z;Qn6lNw}X0ejV=b2cLrb+2GS~{~-8HxPKV@Hrzi7ei!Z^2cLoaC&6doelGY!xc@Eq zW4NCWJ_q-|2cL)gr@^1X{U5;>;C>?Lzk|Pn`^8{r7_T)30o*SIBXIvR*aP>kg1vD6I@kyIZ-V`B|28-P_wRy(a82%Z zxI?)^aEEhu!VPkV;pTEj;Ev>u!o4YX7u-F$V{mWI-2-=T?q0aJ5y%g*%t~F}U-&_rP7q{RG^_+i|d@uJo_8T*eG!m9fs1*a`x5;B4KC(&j^A_o?{G1%bAJQ>|A32m zo%=iZ|0i6`=-g1iF9#0=n7KJjUGt^f2q1q67jtT458VADd*R+TvJdWok^OKFj~sye z!jXe;kBr<7_bW#Z!TqX{JK^3nav1LD$Pu{XBS+yrG;$Z*hewXVePrYwxaUXig*!8H zAIh4A3%VM)AO0d-(ACHT@IM9@R5WrN{zbT0sYf1!zX2Dk@W?3K?#MVGJ-DEXk<)O$ zapWxAZyK3^`&%Oq!Tp_)N8tX!$fI!oXe1B!c{fcV)j_!62{#qsza1_(z)iDoUvN_q z?%|sr3r5U$m>2CGF~4X|!u@442KTRrUIO=Thh7TT44;5IJp9#gbHiT)cWn5@aPJQ8 z-dn@wZ4_^}4DTC zeDe37JoxHQJ^B78C(H%&btXg(KY=$Kt_*$C&`%A0cIfj%e>L=?;ql?A;Y-6?!`Fu2 zGyI3ccLZM>JRSUt;NJ%C5B@TEaqdEHJNJg%U+3mVJ~{H4k@GkGz)kPJ>Gy8R?K!mP zrF-go-n!?LdoJF*a`P8&9^3oe-XGcf{=K)}^5`u)x4h++@44m2Z~64T&+Pl$zW=rF zFZUh4wR-C_xBk(s|NGXzz4fO3x9z`U|BLq@zHRZg`fX3&_O{#Jaogu^d-;JMJ@DQG zA35;p1DBpxe%^09FaP}F^IOlqeEVPDe%Dug^;fKZ#chX<9@;qc=0o3p=%a@|eQ4y4 z=ihPv9Z%lzlXv{;9lv|WAKkI{&ZRqFdFS?>Z@BZ#cYfQQcfH`7U-01*zH0xW)5B=4E(2=g8Co%)d3YG`%jRc)Hiy4Ym=nhs zuH5u1AI54oRK02W=fQP{zWb)tpBus7x8Agfzb_m5-kV;7a?Q~DZ~mIQ`S$}i7bf`k zgEv3*;_iJQNOzb_m5_3{4wCcamZhW$KVeuD@=$Nu}SqyPFX&d>BW{h%>t zfpHGM3H-hmzlZR97{5pGJCEO^_`M9jJbsh-P2o3-zEGm)_YLuZQ1N{I>DChTjf; zuf^{f{GP?{b@=@w{9cdW8}NH0eqWE@H{kb;_`M0gZ^G}*_`L;0<#*r&`aALaF8sb5zkhbBw?bYPTYwh2u zVTJGCyZ7i@&H16XnJ0$cj^BIC?+ks3UnLwmh5gYFVSn{Q`1@~y$>HJLBSVw9Dg2(s z@9p>!@H>aT>ZW($_s*dQ@%O=-J~#BnJ)g&KYWUY~-W>j+y>A>ocgvfGui*E-TQ-M3 za0~7+-Mk$fJg|e`-Dcvzz()f9U3^|XSAYK7QTF8U?+m>SYv}uOg3 zK6z+pB z?Y7$ISIgzm@iGDDC(7lqu`}W?h25!EL!%w+{1#A8nsT|+t#s?ve7jxQDK_if<(;jt zRDW&w$k<8A9DkCT_W{Ot%jFcSQZ1LK>z%DeWoN2U>2$_Uo-Df>if^Ua4m+*J)vz|z z+T5%(@u%0Qm3q}`7}nUPF-_2ykIUwi#4m-F+GF7k6DVM@9d3oq+T*>j7c!>9sPWU; znVk{vbckApwS{K5T;B}YA@xcF-61IzCEc9SiOdYgv%zC0N2S)OwwxXp@Kj~1+iQoV zYC8;@#m%k8`So&nxz(C#v^wE(Yq_=cti_B6TvPVfDzh8~f;L9SSdBunw%Be}!%nB( zTxS~b99Bs-NHDAS_ne-Deet z<8-m!3>jJMTtffWo*h3e#icVHoyY_WFzx}6dl?d+JPP?xPb5Mo)?7QoU2hEEDn-V#%sfc1|ZxW#Nuy zLdO-#0X!w%(nf0=oj!@mqrZj1ZA`i^PoK%o@mwZ&bRrX+2^<|go!Np+=;<*9lf=YV z>`b+qoqDGmHoK4aT3t(t8kufHHgx=Sl&AWp+F_*|VmP)MVYl9D&V|h$N5$f$Cf}(u zl6*F7hHWHM3L#0XY}F(i@Xk(gZ9e2gskC=M6Ixy>pV0{c#}t=|CBV7L&MFG3)Gnam zt@h4LUFX*5$urWG9ANQ*Bh!IIEeY+BtqM6FfR0;LKK?jantXJXrfymrOvI92t*dq)IwH zmI=;=j!tAEk1JS5buR2yFs2p9f0Q}N>iF!bb6He8el8n4kqJIMHo;8d9y}%CJ~BQd zV02ZZv2!EzmYIuYlFoZ{LXh*FLZ@26Vje!-3!7EpwuNimcBKk7RSTaLq{6kZ+Utf3 zO-KRD8z3p6Ff;(`HQ9Zs+S<}3SO};QR=cH*u+f;TRIhAT+BHX$QS877Nd~T)nQ9PR z63vI#x_#xOfS23#ZfK{{Ql+^r>r`K*Ix^}YwkRe6=q`2FICBttslF6KUa4ZuoebAn z?Qm+N(q`MDcnAuZa08>G=N(mLKcHKCz6q{BfY*%~m%(m{*&L}F;#t7;<#w%f^6JDy@wQ&x# zw@lKKr3zgwk;P^wYa^Cn zCl*`VVOxmkM6s!TdZM^k;(F6+2>T@g%(bdlSR?R;jaIF5!Y&(-{WodKW@Lk25?1QpT7J*XzwDR=I^WlJ&ysQyi~WuUW&=G6>VrdnWc` zrPm1|7`wgx*E9E*^2mqoz1pMjML*luI z7}ae;I{jE0Fg|L|O-xRVj*d@Gof|uMc4A_DX6)?L^tthKlV|d0XU?1&J#}*Q+?k0} zr$(pFoXwxjPmJe*I$k(+IzN-29zUxk*?Nsb7>DT5>Le6Jz9 z6nKk;CU`=d^O~WBHl+NpmIM}_?m-k#49NU7@>*h(7niW0z-u|IY#|ZEGFiQXzsr@5 z{E;Q|Vyjwd0GO|m4Z~A}JV7R`p~ccPN=bl>$S^Nj!1pi5d!GBx^ z;6LyV;6D!OjMS~ojneMh2%CV6g}J7bNdU!l0%JjVt#ke~ls4w!Rjz8Bo16*`$e|n4 z89QdM`|pLzJ#5onGiHjj?N)CKD68%qPmnNh1;K09to8}+sBj3y)$TRjpA zyxG(RiPXl&{fw%)q!<-RnpE)t)M;}Rh=vXy*A5@|JAB-*kBDGrzSId&a<|n=n{%JB zaW845M^B}D34o}V;P30DQ`$@EbYp42H~`=wvAohbUqkE9oHBqe*Q-~c7;JVCWT&SS zBkU>xZ@LZuRaNXm?o%7wOQUy~2EB7id*_tjJEx>~q$py=ouH+Uo=$fH0IqySrYq2w zU<^oEjXf=`OK0!po{>2~YxQ=AJ;!Dt2yqzVp!(TFrnkc^1uz`9Ny{8PlWr3LQJdiJ zYttEFtbOUm&PW^5*?GBTWX4!jlz(hRB^QR{ifYEAXVVo0AgU<*eHBe77&|L9OoP2_ zGIC)oYA8uH{ph)LMF5B@0)Jmc&h4@wj-_)0fGlohdFGsD8|S3jX*{pcj4~OEYD-d0 zKRS`Fb{a4S!1)O&EFC2=n3Z5W@TMEcSQI8n;m4M)2hEmwc(Gl-ij{@}Wyrmn>}sc> zjj1;}=S{DgH&fkqBhM1^(&$M5Cj~f3pm0s`O`5{#vy*4@qmyH2$0rJ7rzTIIp3dhd z@@Gy>jh~(zotijzaw>nWFayuz^vRQ_^Vn9Lo1UCFee&G+%*m5eQ^6=hN6(BF&gRceP9ZcsGjZB))l zlV`A3$)B4xvlr&4&D6re5?r3SOkbwAS49x*n`SA~=G58o@tN_-Qtr2xsxZy$Ilf;$HoymGjs0Txzh!+JhwPgo-SM} zP8H1TTw$&}w{WSDpS)NwGxL|m$I6RK#kqnhFSmD!>rJRHiVHT)^3qCS`H97XDb8QY zUo1|SFXZQ^FBZxOtQ1hqf>g7>YQnp4abanxFk_}>%1g^Lh=v!fn282Nd^5(LlT6M! z-^{Ej%(Fh_OU0$-mHfr>a$#w%I1kU_(!x}sR4UHT$}rC&*ICJRmbuPq(`JgVC`^|Z zi_23NP>Z>_h57R3;&h?3T%KB(FD)Qn1eY%$E#gt53ru^#6gx{j*;-E(5ml6^BBL05 zxddO%;L8zwOoERw_!xp0C3um+iwMq1aE`$_W2;=A%$JH&<>LIz!qQxRxwtS7#CbE< zx*E<4`aIF$y^voLEK}NnR(tbAaRG=6g0esq1{Wo`$lxM^k4x}z1|K&wMKqA3RAyGD zv{)?97M4p>ONGMxQXxN$Z-EK{Tq;a0FDywnE+LO4$zzFmELl2On3*XRmRawTxwt@5 zEG(5Li_0Z^PjqQvshq#a{GKQmUQsLo*sGQ#j}r4JnbKVT;>Ge3N>L(vWnpQdIA2_L zV{7{bBUPMVT#3LJi}QsfWNu-llmhk9MIwvK#rf%l%Qh2G-^zS(YGJytTzJJYiBbOc zs_?C`JFTfL6WGb$qmmt-jzvrB=PoQesXo988R6zr^G zJ*q*t$w~+A8n#R3@2kQ@WueXJiUu>^*+Gu1Cp5|pY|IUH z29Ok0;m@6`>9ngDOFK*}&2GJc!vhoI_wbjeCOTgec2v|qQ%d!7l_sdFrsW~(;q#5M zn?ex~L~hl+jfHI-#6=NhC~m0*PJy9{p~keP>P+OiAu3LIY~x#P_{&?`6B0lXmoaRu z?YQOGtOKjq#5}CKqi_LiV6?hnZKmDYTxr&EgkpF2h(tCvFu5Z^)V5w_r($A2ZzH}n zB(%$-gv%R@?>}!bb=xn}Z??m0%-f}~W_DF# zn0kjA?8)BRTG(D`R?%G;6Ksg}%o=;_I+y_V=~h2n+6t@nwK`EyMs(KD7b}W&s(XOB z4j$fd_^r zl4?925}}A%pIJ#9$xee}uW*g@X397qKj1RW z?qVG!ZSL?7JIZozZm!gH#d0+>+XKeK{=D+$<@qb+G8bf!+)O=e0MV+vOJSv5-2g-C zKwS(Gx3k>B(%6r#x|F_Lip|vVb00=Lu%%Y7jRWdFj7xP^v>(o+wd@$%2jn>Y$*|*7yYnU>2CCA21;{B0R53T zd+oMcjs6Js|3VkbZb=pB{TQtNbPpCfn8E$APR-mOGYvV0HOfpj*{+9cyJCn^x<6MX zP8`=^8MTXIKaw4ezQwg46Ki>TjqOJgjY2CS+jq zxEykSeG@AsECKqODczF^7qdKYFgp-5uyiEqj0ZB(Ev&6UYUwXWwmC z&aH1o2w2~7UU{+7i6B*1`%M_Lbg%_9wffb1Ere&QE#}{l#x`hA4xU1&>ltnmEQot~ z5uqNVM>v`I;zb+U7Kl{>WZ#Ijj*O)G9zH+L4Q;j2tAz@g+6b#xiX9Cs@)*>VSLF}W zuQb)qJ(2VKc{mtx(W%9b1C)C!b7LFT!jP&lRkVe*!A&u-qUviAd*)WnG{bJkZsd&^xkq{} zwoNMAh(QY&!8lZc1hNd%c&jQi)4k0tm2!P3vBZ|e2NuA0aT&p-w8klD1ZPjn@}53Y(g7lA->wuf?UpMnA1~A zj5jh7io&9xTiBPvD#sEuJS%%YsPn-6)Jw`!A6ba{VEW19b|$E=*Sk*MDmE2dmB>|5 zOHH+iSnaHaW(M-g3=D8(agG+|VsnvJxmb(EI+s+R0-4ON-GIbc+G5I#4vr(KrfoD9 z+LwKh7ofnP*?@zohae7iM{)-^4zLSw9;Gqt8Ot~$Sh?+DEdv7AVGV8TCT&m$)`ezU z-In^TnFbbma8M*Hchznq0@PiMU2&Q0O4uEBCgHN?SuH^XOXAKiLy(GW$Zdk;&fd&b zwnXxV1eCBiH~EwaIyAGg^0V`-RXP??sA5=qunf5Drg+&^#egideL$z#g*>`)4r&(Dp332lAG19XnGf5@lNvsCMZ_uQMe2>spw=tzOHjr0+os|trz6j z#p0yx4^gZN_H5KCF)Z$=7)CqI72aH1=z$GDlf9%zBQv;i z#{=a0`UVc+YT?3K9!7kf`g)TZwNhn`vHSxfhN#)yS)m1`kcYLV6u%`xj<;A^f*n;F zyCJx^GG=kN*C}><15L<8Sc{?WL5xGRhst`W!n&Qv_h7*+JG&DXp^oUWUdlA&E(EwG zrE~Rje34%pebil8Qy6+kJ$=_GAUj{FZ)v|3n$;HXn1Sg+<-rq68mShpRW*zSQMU|6 z4BV~1d{w#s6iy2pn0YohoeIt-!>QmCaqMPIR*eV%qVfQ(MvZ#XO1HY97yw9g5?You zCkg9dRcrE8ED0{x68TM`d|GvA?(y{k4=Cgop|}Qf$E&vc9Zkz|uzX&6hEvrKGrrj2 zPKR@hN|$u(V(C()U6;h}kRSq}5{2f~db=gW8-dk48&@ClTlGj43bKwQVF*?rq&SMT zK%c|_Rx<%`2Fe`WBKxi-7l&jnU^dBqP3lP2+7(~ESK}=gK~eoO_kDyg_avm)(PJTp zfquZs-wipx5Hmie8&NYmWj(C1^@0I+q011pwnM`Kd)FQr8ct>lr8$-_Pu?KXPBK)k5$GJ3hyhK+4Z z;S!}r%Z|mNMOuo901t?otqDuXGyNM7pYsDQpCfYexKYDzT%;cSkg*Bl-j&7l+& z=>d1WRUlY(^s;Ld&!wn`?Ro^Ta;Ah)Y7BQt(QW~9SaF!9`8lBNL_yeSbrciXc;n1U z&!Na`f&?)k++1z!MCHujPyte>0+4cAj*v0k1f$75WxKM^d!)MuM8Q@^9wvYy4=aP7 zc2gNE*~zjlAvP-xaf@=H1tig1i;uhnmyIA9@Vp$#ndnk%_Mv4gtck)5UC2SKtlB>qcvRmj!%$&I5UgszgNRTgaXiKp zHSNNh&5V0s9|A)J>PZvv-hDku#SPdDjjiWf&B+Ebu-HN$X<}w#s>o|*P2&i1|NLdM3LtZ9837~<+4HyksWWW`)6T_xjTRRJF zJ^Yp&U7t}GApmRva9xQ@H=e$rrbM9Ri0rL?l$9-kQ7@pzB#M%_b}FX;G{g~$D-S`u zD%65G*09*~BwL_*XzoMEe*LgJy}_cTdJstms)=rs%kG0$NhM|}#EwtBwlU~1iP50; zEJ>G*kqA!9`EXnOpxsqQ&Y`F3or~dGS1hBZv4S@UuQ?ABE!MYiG6kqEuk9tQ$_z?| zICB+`JAjob9tAAFiNcUM^_&(;5Q<~Et2QiwQnwh~tU*&i@dFzKyr_}{&4yj_Cy8e9 zuq)F5`VW_Fm}+57{`9bQkI*7MC=g^4#ekkct`bD~_DKZ~$U^kJ53a^gMkwxO9Oi~* z5q%!2niE@Oj*Sg4l^xxc;uH$N!zao^tY7$t^|$dr42=YQJZdf=05`in(B|ksy1%tI zuuN5C_ZF@kQ^yPWEaqzvYE?dJqltWjhP`G~Z7p6qLF8te0FsNWfK)!BLT!qv*}{c3 zMzhjTGi-sP+UNxFdJJiCqs=4{1F~Vo@K}(C*AEp9x-&8Kr129fn1^Pw+-f>MjKeYH zDAP?V8l%z%Oe+p~eG#hzNKsp5MMV3D{B@nE!pNXd#l3V3p%{##)@65h8>3$1+Z1|} z8yizxPK0DU*=}v?8w1$ghK9SGRvHL$!buLS5d?+In*0qKL{=rhP*0Qm4 zcmu^wUy#4O;%Y>@3DuX_Fv=39MvUs^Mo4|dCeGs6!b-!+K??u-nx#1{8TX2tFm#hi z(+8XuBREsiKgG^;tB3uf3za~mE~MINVLHZMYs`0XsJjYj%@j6Q!&)t@#SnG~cxBt& z$o8UWHsv8E_-qW15>hi5gB_JaMgX?~Pw^w6GvP@gw?d4V!m#0a1246kk)oAJXUaWv z8s<%>I4U)z;s`X_?x$mzlVO+ZT|av$9$;{T$C60bbGQ+VH7!n*Sa)Ku>WjEu#4uVO zZ_BZpt`u?1a;1%FIzu6RyVbrDN4Q?Vc8+TKI70GDf?R)JsAGJaF=_!%X~1j&#)%OT zxR!E_yo593%t$_B#kCiamBv6U@Tax*_8dKgDY6XYGa7{*zxH{D~HE1uP?Gx-dSCpCaxbBl!nw(QcOAuP98d?Hl!T$RkBR~Exp)!eHYjFjzmoU%+%Vu;N0M1tHU z+LGBW7?F}9BXgVpTk}6LI(H+6)sL_A87u@~S>-f%8b@}NSFD&v89Kkp@B$Aipk~44 zKN$N$dRK)s;MP-J<&%TPRf#i`3%#u75~Ih1;^mUAi<|^GwqCG|+ishch7ajA>(~4+ z4yK!^R}Wf#lv&%BKTF{nR{0GWT!}tJW32g=s?r1vOKIlwfTr65Id0$r2eL#r@edO$ ztcmn<4SzBCxI-tzql_OqVw9>fDIT0N*y6R;*6i}nlTEi%m zSi->qGzavSd-Bej6*pCE7M-R$au90QRV4yBvy~KBg;ic%pM-b|dvTNmp%7dHOV@g| zKg?qnf=GgBp_iH__5dy)T?4S%ETNWmGu&! zsUA$YHWQ#oI88v~twsV&PO0zPCBSp_j##=kVaC@<#OO5O9Hk@|MS(OeH;+K9v1J}H z4BBkI&OlOL#FnrI@e}cB;1ds_`Hi835N&#Kl1Fk^6wWAHj%7Mg(RX6AJSmLL&9Z1_~cAZuW zwhy=r2EhxdaKi%0D33=0d$hd>sTl_ym4@&gV%79;)?PWocFD+FIDF-G-H6!-OyA&T zKX>cc#?!3v{HE5kM|;K|MxOZ3S^ zk36pKU_S!mQk90eC67ExkWdOwUz~piCzx7_n%Qizl-0}i8vbA{>22bmhkj@_BL6zd z(Os;@v0Oh~*40!aX=kIp$Tn1i%td)zoQofxb7xE%t@?kgm>MK!;7S?+wxycPRE=we zrCYDPm4)l8qS%Dtrp!i92DcHg2JN+Rpu>k*G1$7|LNg~}=53Y3Shh?#N9o!*u_R#c?s?S0*RClD}6N>Ajrd@b#xj`LjZr*qb;V4#E8C*zA@>#yn68I*gUvHHh2m{U>*HuZs&!WOGTe!< z7Z+u1S0T;I^)l}kn#C}@0@e(N{0j`gZmmS$j%$@wRp+(pP0E*Z5a89I2;5u(;bF&!!RWl5j-t(vDLZ)E%}x3a+t^a3{AJb>@J0Yfl~C2m6c9B{0lA`E?&*+C@(A~Z^5MW_Zg(_HaEmXXSnC`SkQk&yrbjIj8=q%L(9@tx7}dodhBsj#GYQ03 z?FoRB1WS747f;_iWKKcG`5U1ww$!iLldDLSR3g6yb&v+_Q>C#8DsKgv(l(3%-J-7` zvK*|_l8nrj>#Lzh-(6vd8$NK?u(By#DkpF@+^dUT!E9Z%e|cJKHlRNdR2s%e*IN&1 zIIKy4OR5tKO=yj1$N#TQ3;i*7-$ zFN49Ei%>+1heA#X+7mV>y3Fe7r5S~L^O2sKVZQD#1&z6QkZah47T9}k-rAapCc>=6 z*KuWn&&zdLF*=m6`gBFaqjxwjA;a{c7tv{L$j!7<#Ca2MQgP7hO@-ijLEgt*f^6pB z%+*MZ)4RF~iS%O{>`JZ|0Y&CZ`Ui8Uy^TxzkcPoTo9J&B7>{K$r0jui%%Zln2%n}aypr%njsm*LT{^eD&BQS|(%28Zw%$^BDoG2M&!Dj9))Q-tr2foHIDyN?mN~S!1afL<~_; zN&s6_W*fme<*hVGdIuZ9t1jJc7Lhc=?P9igU6dhTnt1NmX^uYkCQuAy;nQFO?jox6 zVgg{PE&-Dx-6)BzuL>0P*J|T?IL4;B*+6>0g`MKse9HnZErX`8Mbqd!1#8^RwZYa_ z85>)9)|-jJIt3Y5rzA5gF-B?ya3$&A7!WH=(lGML7-lOCm6eqfaM7x5OWADzPH^9? z?;ypzj@vFCvU(8S(%SaJTbNh+*rSrKeMVBr)&=-liZs+_7_UcsII@H8z*u>}>6W({ zz(VRpVRa|v`H)DzB87sEv3W-)hp4#oZ&k2kk3w9?(Zg~ACwmG?&Zt3zQ0`!Xj6$-| z>DDZQL6FlNBFLq>vdPnDOZst28m6Tq z;`?6YpMzuVJkLhjH8QC-%+5fisIwT`j`%8=(`V;E5O6>OfBUK-q8Hvh*S5e^! zj>pAlP-=>;T1Xlr+Ucm`GA5iPEo(`;<2c1&$|5A0k=IN87e~{T8>C0ly<;+Cts9#& zs107xyQ8_W+8s*^w2dBArXoe#-DH&$9X0^RWlW;$@1EUa6PGx%lB-~JgJ`>zWUHQ8 z1{D>%#mfPN)Zw~VyCB3I)z06nx#xBW$!ogloTJJL zH_R$Qa~>F-y3?D^-_iKM^xV~D73&%{C_X_?yCMyWRJn$nHhDof-2`o+TL!Y~>bP}( z%E2*idC1C7+BPVuH=TAz85Em+yEt8UOW@>9_TF$BBO)ESW7{9AL!TKL-OTMzwM&#; ze5hCLPp8XQR-84u?@yvD6-C_sI7fcA8?uphiPXktC386s#;^=%FoH~`th`k_Ixt!( z*|XwLw>vuCSDXBFw5Gof(g36X?7XrXp}UaGn3RQnI#15@taPr+2gR4N?E2H0Cy>;# zzYj%L8CRNE_NP%C8_BAs&g`t1Sk0Zz55`eqhzu>B1&jWNSq%rLFyKnaTI{tJTsbCtYyo2o#E39;M`r$KCar6OK zQ_rKx-mG6*q=-xkfuT{loEUi*B)JhnLSCY3EwvSNeHPp7_!!U{I13dY%>C%aRx&g0 zGt;ngBZb6-vW$t1L^?~xSR09Slr}UC1vhoO3@kd?I9!e@@q07bS!5@Tv(Juaebf12 z``&k2n#O^ChUcbfus!2TgLRJM$S)m{IAG|Dl)LS6-jI%p73t|1ArjG-r(;wxqH57} zoMTY2Oq9k@J~KF;ykT%`f{judPG`Rq(hfhlgHhZ3BrK89%2qlFU(@5s4BBILqdd*; z)-w^UWWm$Dtb8KXR2Bm11}A}i>HIYXUn5S(yH$b|osLri-5rCf<=rr>5Rsid6Yqjx z<(jOtuE> zH_huhNKP!1OLkeg==#JM;eF{!#Rtymn8X}Gi$vd^Mv56DOGwfl4hEC6?oyrSdTdZ% z6lWYx65RKd(bd$s_Z00eCIKNd%6GrBP78 zjUdU=g-eY6$r&T>p7cjiG29Q0g)*4oXJEt>oE>jBpCK;vHT$yH#l$i%r;1M3A@P({ zIuacPJIPukJ*MM%ba;iA1n>%6Iu50;DP^W(AVszN(5HHRAlQXnX$AgmOt^< zL8~wN@|ko8)9EWiYMs>Ri{*XhbPCxTT76(T&fC`wz_8t|EMfqT>=}6dg2t)? za72b35a*`ufM|AcMikEOl6(qQs+4d1m(JbS4wAqnkbl^Y98vc05c^Q zKs;q~m#r9Kk|pBqZ-;5IV}KzPd}Ug5tG5&1V1Xqq%??bbTy50#*wBHfSil{DkOG_3 zEDDHeQWPSd;Kawf5=<;a3fbs&Ys?MCv>9iNxc$ILZiH-eX!#^(YSS%Rj)NgR5q1{Z z^DWo{t%uiaO<{#9g4c2jR!8_esBAA0a8JXC(GkL&9QZ?FQ;k$~ZA{z-wI+nLjOvIz z7sXI_9#1O?d+AxcYSk-V)>J%dFn~(Xo?Re|C1|L4zlW*q&1;Q-wX%4`z)Oq(BnTaq z`>?fyV_Oaha=4LyQ>_aG1S<}_>xBE?Nw9nLTX6JFop>YJRzhl?5W!#ziF(319yjnX zC15%(0G7(*SGiIykSTu7W6Lc;0;+1WX*DfjZYkv$T|yW^UL)`TB``RNqM?oLGY87! z30&z!2e@K!qqAm7X(>hguu7(-KaOxwl)OSF;8)_IAsD1K7q(!mO1pG$ZahGM^ZYHb zEhHtw%gJZG5rG4|rLiP#gr34Hs`!(jZG?*yqwrh+h!^y--Jxl-15M+CCae}L0$$fr zO!TdNcUDYNn3xOKIcJ{p>af zcb5ljkU;iii4L!;+a|%cq6W7?lJ9&N);jji17TT~q$ix=?G4H|o4HMdcd#j=rA5^|RU2R*6POs-Qls z@ah3+Sks3nf}BhPGVfABi}o}!d)nDLL(qR(CIeVs*v6Mm`5wr=sEu4mZq0bQ1{d~GKJ#S+P^qqjMk zzn8BqN0aFukd~u(gi~T7ep3p!up7g5AOY)*Rj@xWDrU%@r8#W8ar1h~wvu}&Z|CS4 zI{VPRy`eM%V=Lu=5jgJ^3z~*eHfoUgR1XX&djxXS@hXu#n?_qRn2~_7vwgZCm`%BB zO@Q}`jdlw3EXLucVA!;EigOIE?DN; zrI_yiH)xVnREhPeK)~%!i)Ib1zCqK0k4;~TdDgv{^Zq9NYsg%AJhf?BC ze+aM2g3HAaml#@AyX12zc2E{;*C2cF0m;`z3RhSAl>W$pAMXMw%T;Ze6fUc|$`$YC zh?N5|(_B1i=Y?1+-yF|ju|iq!3?n_4BcC-^l=sp=64cLBFbk*+V-}ZtVKw zXi@L*lyQmx(-!M#>#bt^um=MLL*{3-N$CZ?L{wWz>+{I$Ykq$SXZ*St6E`+q47bx8vm_Z0N{5K7H3hs4jPmZ3uS} z2;=~Z-M#okYL$ttKF0E1BRbD2-*ulV=Kn~|nXy~EogurS$O1Pge_VQt-k-1)6uULQ zo*uv!XE}=^IffB82!!Jc>C%Waj18yg!FgjPcfm`;h=<h}|v3g%+C)u$h>| z;>Kjh*y?0QViINL#;!_Id0a{s+?J6Qm6{Nm&r0)36M7U4a3_b4OKG6LWV_)TRDncv zW<3%Zsp=%+Shh->abbSg=I(>aDw@W1SE)2k?}u%AA5?CN5+hC@tpRZsDUi)21%(`C zmzg*(d51jC#|zp#JV-fgw|SYuO*XY(cMy_h5@K^m0?X8B$nGP`$3Ch8MTsNvB*fLk z-UL^)=wi4A!ObAEtG`46j9J-)8mQ^~(g25%JQD(-0$BS5AWG{Y31f|tXdaLt6Cd32 zv&)X-L^+(DaSwk+~njdTC|p8_3nG zQK|}f5mGKo;iV0)VkD7R0qdruIZ7{TOM zJ9xqmH%hSGcZ)yr!WxUmb`aIX1x}281i&{gQV{6Vp^%OcY;EJ#dvSI!r|T7%c;T=D z1(dNbhO}A37gsyp9@^WV*bF;3ZRq=WSx4GuKkTV*%7&Ql%&Ky2iIP>v6!|%}#Oum) zc>RD^YxN~k)L}CPz#zD|%@(r2^zAb^a(W}iRw1C{!4oSX%NKV#&{;sYj?6{Jj-cMx z(_J<>q5p{qr;3lztnxrrqOGk`*uIL7HTOlMZ)9f|#m6@fCwHhNgs7yuZ&pNh{c#>v z4Zx9iJ3yxz7%&tu;)&QBr8&`#a-@=9zRl7Bhv3_!s0W#dju&gK`x!g}gWX`qU@?)u zpdyQ7pRIuXlbQqY!(mK4~ViDag@95q$_XqZbK50HGAY$1JsRsuXGdPW+vJfVfu z%rJqmbyUBm;RaU28`!bo<#i1-q-}OcDDvwd+a=2X4yr6KS#Z1hXGOWkd=mNCuQZ9S ztYg;XsYci)xc3!h7_8}94BLk7Rd35f+V=eTR0-Z-^4>=5;3W>eXV@|B^Dp>%I6jh! zf$Q*Pr5Y$&-iQ^)q^Ez@SbbjJw7mc=xn}PJg+*t;^)#WA5fg)w z);8R`LmN!az?m=FENDE)#$xrB04jvGH#&GF=~NkWqD~5yUDGUdE}^npQ@}&sTX~oX^*z(2b5xWx{T&zotZsBP>69V#I7BOw5TSuS@o{o3mvpMq07?f~0AA!);@S2Oq4vm^UEh9GYzzkT&BiVcp+y}-Kl7f0RIUH#8 z1J@!CI^W&!B2@Z}KZ;=S)8Am|Ly&aLDj5UjiOmbQBX}v?ib2e>iJ>KJ(7z6{kv z!jz;F06Sq4FnPitPGZx;I#)AXc5k3(eC%51%x(x6rd>0@YZdm1^{njqGTy=h-dBej z4_^ReJGD~k6TToYyltc)rm*M=s77hxQ#Q>5QjDCH$gJj~9s|5wVjvBRI-SSCF>NyM zr475!5sHYnJsqf+ZR;@GE9)@xLH%bT&BMwm23Sc{_587@khHMGXR3Da36^>lJ6ct( zOJV)Ma;*=KbTLU$J(<=E5rdj@h#~enkfoj!lv+?PT`kT+RuxbH#@G%U^#Dqi z5*Ky74Pk+_stz!st!V4s$$0xjz97LhjIRqpeIO6o=n#P&;gbSAeNle_R%tg$e;aMa*CJ#v<5j=`IWFLs#4G3{+S-xsyB!gbQ^B38xW1NT z*ZF+B46`ladZ?_72s2s$OlUi(H=Y<0Spu3JcoMI;Laz2qI-NjlD)>OjdJnI>a@Pl? zjXrLTo~N`4C|smBTUZ9WGGvorgCA{P`SSU9L7QuMn(>03i}Qm zr!=-JI~{qL)OzL9MDv)tXi-}XaX}ZokS0E$#iu>L$6<6cU=sn&Lp+=ygfe z5uz9MtSR+9JkzuBjjhgWt#?hdico7Tmh`zi=}ywG4a#Q5`t2EVELdPXmC+?Yat@~Z z7Z2WAeF_RaSTVTQctB|sGB~aJ4+X&_j6Jus;#myw0PHb+c;8?uA|$%?kL3X1e&*n>fC!sIfv98(`Fp-WYSeb^GaRLC2mk3jag4hJTm`%Z>A^V)Hg~+?L^7ORo4-Fo|;uC}QYQ0N8 zgmd-F>A&unpyq;Pizis+8)_8&%mv&7#~;yB!X=BuA1-lQqCe343sxIKImiO2Q~@Ut zd_FXY7e<(rUOUSygpJ4Kkum09%*MQ&Dl)*~c7KIS0mcMYJm3t6Cbb8?=O7~V#z7+M z3TKM>&Q7yx1L^gTdRe=Lvl#J?!2*<>_enuEwk>O`fpHafI8)6o?T}GBER@j9GN|Y9 zP{gSHtf>N+xaMJBk&bHyCJTuGn$j((k5w^kL0C2LSgpKKZZCesVRU-1OMdR$ zfSh_4v>T{2Tm5C`aNUYuCr`ifKI+5g$I9iZ3ZF6>MZUNRGdRct_y}JYCiXTc>$N3+ zZXmE|@Llz6JwHfZo-(`v>)%-@%XA;%=epsQP_-qo-lB0p-C%);r;}~S*$s-wh|>a` zD)O;+953-xv4)?2_JS%vIF-NH{>xHonV1KF1f?-F`Ed!jCyAlpaeOZ*6BzI6<7hP> zLZ6DpHVt=XMVi0bs?z*O!*-cgi^X`sUv4F$b(wZR`2?6O-!@O#SWt4%(bm_tw*=7S znt@?o;XRQhh{iPkw}|o*wiV%_wR3Do&O24Mrj-w4tXhW~2@BwHruY1WWfF-kky83|V!B#ioI zZ_~XzpfzJ@nGv?M%s4zSo{<$*qnc?AZ(GW~MuSwQARi4-x`_gI6rwOY1p!tw8X#8R zt7QV7tYjj})iD8l`22X;9bDMD37un<6PGAyw5VLk9?a?>;iyiwlpHJvSrb9tF_fa- znsbq>&_I!;q`;cSvYT|AGNB}t{X!m{w6U;LxDRZ5B_n~d8=2^~ia5`Pa#l!?5}5Vj z4^=7llmHN5ph=vlWUrr z$i4^1+}O%PN`*K}55$TwoSw_<;9onX>8+|lgI=e{@GwM~;zhZzj-ouWPwcfOItK^r zQO>bmn?QHao`fO<2FUYF)xIZVy%LfUA`2x!ddja>QOOw18NGs@Yho?j!b&Y)YeTJ^ zF)XTHr{z07@D?`!rKn<$un_6IIIHI&1Q$>6)-Gi|3=t+OJOr29t(JIbSc5ZvIMkz1 zGbVB_CaY=6sc0G0!RkTdX{m!V*)+DI$u42tj@wBmkWLrMd^F36by(6X>rw&#p#1{O z4xqu8Kcsf?$Ogcy@%zINbfLva+%{Gg+NoByWQ&9Q$1))b2?+DzCOioxKk0|sdD93gua$98!W&N>HNx<|=66`;%gsWI|b=&m705p#2 zm#I|)aq#N_Q`}p@SVOuYGXfSaoP(FZMu-QV&7G76>H_lnbU{9QL*9=QXU}FLzNu)r`ITpADtK5n1RZAFU$={Ow z>usY2&Mb5>lOP-J1+fhW;c5<|!WAkKOc>FDx}rRmD%%R~vijOrac$?Ir^MlaZU7`8 zUymUa9JXh!+7vhlHJH+ETpitZgp2m8p)qwmG|{KDCVtjk^(GP&W+iGA31&Scqo|zB1QCXq07tkh-RgX6+hcN?*sNNS%?A0FQv5{) z3uGg-_vTv;)V3ZK3rMK)c!GSh;R(D3in2s(;B9k!Ytsc+a7&p1Dv_w`h(m&%1c}-( zh2g<)8%A#_7Or{i!jg_FajGBWonss6-b@#Uz}*z!AS5}qK^Mpzg*9e)O}LKwtXVpy zjYMD+uBzP^w7Lo+`J_PDt7;tgs&T?P4nMpNibhx-(g$zE2f>O+@=_n^%O(>$FNMko zX%juDObjoK!>%eUJJ?A1-eH`h&oC5P6`K(7dty`T2<}a_q5`n5O9HB*%F;+oZ}pUM z{@5ze3`%lg)v-l&8Z!kVA+=5#=y00Zw3QkUVr;--S<%D-kso<)0=R+sAzC`84B+4cJxsL(hr^;cc)Ys>9smKDX~MfK(+$p-UH3?cGl#MS!m-fBL0eb z$#TmY+1am`Q~=i-k5B@FmJ!BRcI{twsQU34J4VSn37kw^-*$v3(FsD5{9DJprV6_S zmDj(l4L|l)ZLK%?UOMBzD`f)!<$;_^SrsS?OJ44m5BWhd@e)Dh%n-W6`d9#E{$)N5 z#9N+>hN+YNP?VPxi!EgVLZT;Ac!VX^GaeqjCAq<8dd!94CdQF}dBsLr?e1Emp<+N4 zg*kO+a46o;KCpw&XB#5TD>Mfv0SfLUB>7wm9*@`rp)53kdl3_g`RN~_u7$OH6YYTV zoPYUT1tx)LpK$Wl!t?E3TYg z^RPVfFLfI{_t7RvZLODl0Wy1ymlebl5{c!;@WAaoN z#?0XxO)ls$hG^=;YHfWTdm}8P1(50vJ~_8l!70p+9K+Bq&iac@?432FHU#z?&v+FA z-`8o^@o54i;>^G$5BPA&4-9*~BCnr15I>iIGZ;Z2YO$mKr)^T6K)8=IW5&uG861(w zkJU+m=H5k9GE1gl@@CpxG(|HHf7x6z%i>=&EAW|M6u*= z+X!{x*75f$euQtx-zwtq=nPz@xEs(4!ZjQEYGX$3Hz$labQxuoQI7(CC6v2>T9}a| z=1DUY>@h>PA2F{s6Xqdv3cr)`8#)*oEV9T6kHjGFwlQ}eF|PvFsM$1MXQm88C*dC& zGQlmz94W%TCD>ha+|0pOGd-!v(B3*?HUTmB6j8sc@LfTk$59`a!Te!mg7&dBLwnfH zYrvfsI5yYFX%Sd0DWQvA)P8874KG2f%~AI8B|u@)Z*y5jPpnGm=HMd!R?uf_$f1oq z%%01@YNE%V#P304_CAA_yw2QbhOF;JfFl&(@17IrLG)o^4r!`V3-B8yE1mftC*KokvYJ5i)ycq<7GUg&WU#1-X$r>iD~Z(OW|F-@ z8p7t#aicwRH_|?T*6Q;0POSwFv~<{(Mhubnwu15R9{xIX5{R;mth4a~uHl-UAF zu1fvZjXAE2Aw(@|AZe}j1^n%xog|liZT)k8Y7mpwyv zVD9BaVlE_z3UI1O$!T?5^5cZVE=)LnCm$~)@k)K{Ck$mZDxT%7^2i7C@)&d70`FKrPm;qmg>D;hd-ggEbLW(lO{yvZi#<;sVeXIV zumcE7aq}?M@AoHZVHeDOT$IS!(4%{%glCz(ET@M0n!PJBKJXn&_uyj~2evGtp!?Do zejjrND$h5!&mw1X(n++8{0=4G#bugv?JWM(+&opy5ru=07=H6tk1856$q@o}xle7U# zW%w$fsTH;mV>&2)TIdh64E$#h4)cgO4QOc)p35u31^>z7gQ+$ij=5nf<_JfhvPVs1 z4~o-WN7DzD_F{fzj`_Xg=!SfUVm+zR?9sdTLQEeYk*{P1wD4vIy8=Q@qYwa-~ zs(30~M_dyt3s*usKq&K3=e$xlX7)X4%5v1=ag3wnLUq*L@tP%(dAJgx{(Bv7Fb7Dn zI88ulZAdHWb=ajQ73dnlnU^C|3wdtvCP%{njBHdg*qdJkDrxaZ(8B#T*+9m=kN+?gDgR1x*|z!f5*t7FQ8xEoMdeU%MCQk z9xyJ)e0nRPrt4BG&IcU>b0~9lJ><`cs748BG%klodrsbCZX7jp$WbO&N3J=tDLWLe zpLyAQ%C_zh`_WMXx5eC+niQ11$b5!knFDS`w{pp@l2O!8yJAJPyO%BT$H%2l@E*Zp54)P?(Kj)FWUpXl@5AEm zP-hh0EJYhhJA<+O(N-)s8fj@Q>TxM?5LVnqj=rublw4fDvOS9!kqYJt##Hi3V#d5Z zS>hn9xWsggGpSyr{Pbw2d8_G@(UPUR)|mVJ{!5qdMityHfyca5p;MRHfBg$gjJ(3&X z+!MH)_)j?=bSU}wCB*qeZR;zGS_ECmqkb2hG6&GhwaMV zt+>8azMsw^s;#UF>DZ{AnXysn66^Pdu1ZvbVnvczH_u#clI5mjlQ}2*$J<3n&dJz# zOwCcMKFQSybK@9|@=;fl%-MC9-R+dO@aXr> zSkmzO&)ms=dpUZOE2nD-XhPs_CM_X$9peTmcCZm3p{GB}F z&BDX1PnHy5Y)rgEr1Ntc5i*xhT^`|+)p7C4h^_s-_t)Wz8M?m7-NG_so)V+qXE@+;~ z5smsz(k0{KHn1+qxMV$(t7k+d*oksy(#1;KMjdo_Vf9l|UUFrO#svKm_h~Y3b3g0i zW`Wb1`u!BONHG1Ff!suNWt0<>tG2E|eqB_n<}m%3bdAhJdi(~J-k0)tR!Y~WgRlo_ zwn!yROa2Mj<2dU~e!<;rOqt0(j>h(({&BzIRy?Hia{WF{w+P(mA-`t_wR`|ad@#Gt zgK{wsI<6G2Ui0X!CTNWEX%o7Egce%z_v@F`zNA}Y9?b6XG+xx_dDcH2o1A-YUUDqr zTDumS+m(K1{c#`ld933#S<*{XkNwOg)&bpO&-!q_Bgd;R#R<;P{@39|hewKjh-Y~` zO5O(5aX#^+hv!#3yV}t+px~uA3*%{!*>g8ya4NLFh!v+PR0^JaPfW3_#WGUzykh}- z-KQ+=nxn3!nX$os9GC7zU8Cd7y*%n3!`e5r$vhpe(Oy$+#RVB}J$P@UR@8VnO@SWZ z$ES)N#QXV_fVm?x>xAWV)RJNaonZZVU|mQ1*dL*2^|z#Vnh5a>ll0~c0P;wOZz^DA zqHc!!2d+CDKUWv(f5}nFQz`FX1V&Zzwt9wrOvN<`7>2Zwz1LnnmW}T7u?}FJIoDX=B9XZNs75?9×FX%hgd zxsO^&(&Q$_d^2@g$+_7>y*QJ$P^(A=d~6OWRHy%cVoIX)#CGnOq4BI9x?3bJ+7;+| z)ZOUm4d8P{#Y(!(aZkm zoIsz5A4oX&i zw%HrS?>}K@GPUdmoL!|-@afwhM7ca?e-Jeww?h0;mE;PbgeowPC)UiN;9g*Gp1EDk zaT)DcH$}6pD*Dhs2S7UOAYW2kAKoF#5S-(>j#?A3Jx!#=`ELf#ETB|UDQ9>=I3(v( z7I*bmc@Og*a@QoMx{T6`es0Ie>BtvJJKQg^4Oc*cy5B{4dpcOLaE^gfceg?wm;LZ6 z=(hvxr>IXqvW&Fs!HkygrID8Q(F*#97>dmmoN3%DJ#?67%H(In@mn<%Jc^N|e5A2v zpHi~Wy-@WjP;!c@@6~z9+&S8mBy_b;>`V_9Faj!D@bn23xvzj4kZ(bob|ATum;=m{ zqfhP)4tGE~ja!e+e)f_xNizo*kTYw{vL(NRTyq#7ndwD2T3sj1(42AEyzr$}$-(R= zeQlzznP1Wd!pbnmNT&_tO^VSydh#^f9Hksgj+GgEe79KAiQ5HH0y7WnhRqeuJNXvL z*vXIPAkWNcpW()XCy6>6U7l` zi=MZ8@d#@Qqs5UUrEp}g$Qp$HftjI(xmK+nx| zewl?fpRjfNf}W{N{K>JP85=oqL7pog`~77B;{Fd5VvO>CcUn%<5(eM zf83sN)G&s}?HR|a>fjSzA5D09PI!4vm^e>06V^VOz#1Fp6Za8UZNf>JR%*N5?Oth5 zppX5&W1* za8HYSM%=UFo)dQh`7#yu9r73H{3lE27H{vtQ|tND%D{Ki-x zz{gl4`K$Sju~t~Y$y-rx>}})oOLO~0z<9#zwH?OII;&j2nEehSmME|(bQ)PdRVnJ< zRip~mma}~Q7jA)=r>H04i_sWzXta^P5xh|hFPZfqT#di6eABr7c{_|7sq^l* zH^ePh^5o=eo5kh5NokXNVk|Nn_TrkG^kI16kg5p`{TNc*Vf z&N^0+rfev#&Vo=#l z@kCV-j{u8e3&jVjM2NA7Fo%rF3*ExTN=f2d<;Z@f{@H5>>g0=M7q|_h~T<4K07Gob!s~>-F0f(*T1CAe&fdh?d+q3 z>y&ncts~c{f_ACi>Y*%Ua|Zo=eFORd^*EiK_Bws`01X||mvjb{a7=OWK$LJWE?a5{ z89F2VQDSgzD#!E9CI;kLaI~)qNb**yxX9GGF;7#%fLu=Rp38t#_cN7l{oRqgD$JZM$0=^0*HCj+f3lu6m{s$t5f$iE z=+&TDjufZ&DJH2|dCh?GMM)c9QqeW7k3MmmT=%A=UZ*zf!@?mi51@Ajwe_rvUqEhO z2K}LrNLOm|Pc_g}QJ%EjUjNg}=#139xn5XMuM~ef>Ih%#co+!vs z-oP?W`{`8WMDvd8aGOk%#{o6V`z1KO!5PtSsxcsk<9-fIsr4LyJ&}$5<+kcNJupi3 zksdSV(aZiJy{g}Q+s>0jiQ2Y7Tr{)K1w0i_a($7Y!l z3@(~DTplD)_#J@xU_8b*#B-1{=e}?9Ga%HLK9U8menPSDJjsxoTfWssYbXQvj z;1(QiJpXnx@EVZIM6$iii~S$35rojdr-4IB(ku@HNfqY7>!nQL-<8Dg{8HgCI`g z1Y5BkJ1^oSPMlZC`vtTxLAK>Y#FmU?=TVZ@QbG&8rH`3Hp(Ujcprw=<%9Ls8l$KJM z+su^q($bmIfnMn27U)3RY3VJL$Nm4;K3h7H&yf`1`+nc|yW>Ph=dst?Yp=atd+n`# za?wdSP+B(Cc!pl;bf6f=sp%*esJu~-(^5DWxb^l?kRCBP!IzNsp28@AEp)=8;u1dL z!Z4MfkRQpvtNxM~Yg;Tdw|ohKfEn0wgxG$+qk&V`TIK1P?Ny&44l92vNjJ}+0=r9`(qz9NPB@}(%m+pzG&DKDh#3ALXs zBp364$n65xrQi;VZZP^UONJH{LY0uEbE$t^Xyro1c0MQVOt5aU_^LfmF)!)LkCo+O zbfIXeGrWDw=>oaf3DVvM7q~o%?d8ji3>MhywZpsA*Q(0XB2f!8w_QqGKM5!-O+BX9 zQaD^-+w*H0Ms19=H_5kJ+^jqUy%K$zA-teZvD(V0tA=Zx{E`VXl>q4Lkgw3 zl>FHxT9+-v;X?KHa$t4-OXEzyq7S<9LAICC89GgkOYP|6TsBXaJ+0bi*)h1((rzzP z)<{5>wd*!ah(xfVjT)sg%caKuE;UQ9(qFNp=3bd2W5`pkmO|Lv7Eo`dW*v>Vz(VVD zHRtn7_tEJ?mZ($uQFF>AzbKNW%aqV3Z2gmKgO2J-(6H7H!=*mNFf^gxN?=*zj3u9s zC2-{ReU6ec+hOr3e=7`^9+NE-a@)nYl!BhRaYK(HO0)or`N zG}<7_~i5IHfUmIj~Rrm7%TBOh#60vZ{xOAv=Ppm1-antQcL9W>&%aK-#Ug8y>fAB8Z-t*gt<&=2|rgx+e%n9M~;?&5jjyB z&RGI#m0qsz(r*G`@8^;k=m|gXRv8Ogs1m&;8e^}kMER}k8+&>7fIVBF`@l(VQ_%c^ z)gL0`<=|r(2lzEk^qeLQ(yN6uXdK1sb(@9a=#LnfK^bt1xuxEr^yoQ_l5#5cmb^)!H|a@x@X4)l;HcJhHeIx$ z5LR)*g*K6GJA}3x<(4m@rxRME$3S+k;8K?>EX!;A?gA?^Fmc-ym(sE^jE;;-^mty` z5_V0bH{Uaq*Z=u2Wy`W~%nh~#ts*=5uq8O*08v7Ewo_a3>v)T{QT$bT7+v~)tusy9 zwv}n4yNY9RsoOz5$d95LD}F4$#f&pKE3Y>ZbE(atze>o2@P?y4oiWJI6PL*wq1ZVd z*E;7yvGlGpWTBmQ!LXS9l3TaM**Mx&-Mg-tWT{zi%9FFcPTN(v)GQLAF_f*rE>I=< zNoC3a^M>4T%b#D0bP14UKh->by?1~x#qdgrG#_FiZjn6_$C*lm>XlXfuEqS*FO{1 zcid*{8Bb9Q_W?sc;#7`o7|(IG0qaY$xt078kH+fK2FWuqBf~jGuB7LTikhn|0l83X zuG3ltm)b5Yw6CWDyRzJE(Ov4^sB=%6--;HF0dlJ^m7MC$&0R3{*fW;-+*YzRCS0WV zbiuHB9XPF44h@KIsJ_&*Oq3_;5Z4ALB(kACsrh3{Ped|c{OJk%%2=q@33Dt%?aJTy z*@XhE%2PkR?FS6;l(0}2P%kZW|mxL};h^^Og`Oj8}R z)4@URnAmiNFF~Q5!D^iI=p*`JpST%}`mILDvZ=JNkO}L@;~}iDQ9s;nM_HWv_#^g< zfNu?2xCPdH3%2Ir1`xd33JbN1*RpYx+Q!Q8&Iy@k>kEb@|yhDFZ@wk#mJAyz4#H7Y$el)$t1aY{~F0#|Ic>18duwpSlqGcAsozqkd{-;10K0 z;N~{gopaT9i}iCTeyi6LU5%d(;%8O(d-p?ouK(rwhkL%ZdHC@8cP;+BfmWCmA=C6% z;!ro8rvV;3RlFX*U47lnuVDHC7HSLt##1>xIL4C3RHA68njTDFWd{5qgZHQMw^zYT zH|~y^!J|HT7(7}T@|b~uk2nOE$82a2{F(dx(}S5u&EWXBx_-IR=Lzoi5Wo}g8iwER z^VE(15&wa5vl-luI_v!gNvbsm4?R?2;j0B zSgU6mKykS_(?IcH6b0)2EKpxxWq1Sl^O4Blc$#(Bdn$aM`szw_^zqrK#ltU9k~aJm zwWi^(tTAy8TKrW$ufMUny1Lf%n6-^F4ZpX>tOjyV!0)SQtoBz2W2ko6492FQh({PQ z)0k<1++fTPW^M*`U<|nFGizr0YE)rA0MoU?#aJ*lz3vD~2kObJ8J=KF_|s570~jrY z*)KewX?QAXP~PG`>qlqA*hRoI6>Jc6XgpdCQq6#;s^0IbtVfx8&~5}{XjwqG72IA8 z;>b62Ex)R(eU)B+U3#A1@1I_mo((?CLuoI$=JlgWG&Mbd7EJ}0!02=QI&VK{M*|LrA7V@IX4Apo_pJ9MrQ) z^n^d)144hJ*AD@0^r8uwjixsMKKao%ay1igjWfMywih%*Aia}(m7p@1-r(^EsuhFN z=Xs{udP{DCtLXJ$`aE<(dWI#jAT}VaoF=W@F4~f*o9gog)87-er@Mmbu7Ib(YoQ)L z<(U>zy%rolrK-a{52OQZhAPd3{`9b>%x()L1ASo4U99;ko`9L?(R3FotxMkrrg;K& znR(D&FSOYR9^WX(&A8DJsPUPa+d}E%0KOdL-$$I|_koFCFA91BI17L!M&0;R5cOdC z&wkB~ZsCTiUAQPRCNy>PZVzbb4*JlM-5&HWN$U>y1ODIy`9ycBCUjd(0GM!p{Jua| zC~9a+6w-jNDmeatADGudHSeS1k3;ddvwi8cP)}S6&&QvDp+E`L%yx)uctye+*lM&k zWI`-NMsn@)vzH6n<)7(;*r@9Dx5sO>3h2RYzs2nUv>z62e0=is;P_`?r=H@4AKfw4 zsGVvGgU4%q-e9`EZu}$JUI5jZm;5j~!Q;CF4I6zSRI?F0tM>!52L~t+{`!Jy5o^) zJ}`%60}Tx*7-)oG1VB&lc&zUDaA2BI5j;Ltcl-!YJRo)9!5L7C%TZG%;tgh^4GojA zAs_Edg=mj?tEL(-lKL6AJr(SpY6c9i!URP4Uw|V3`w$e$^~5SzkG}yt%jol( zkawz=h^KmT)dj>?G&EF)JdJ@yZ{6`@kSm;2(Uvwg)c7hJzzR>@k^uS_x1f~}blBto znGIZqpLQis3ois0vP7;La0LeiC|KB06K>0>%bbUrOz^A*_6&V}X)t{`wN%;En`(U( z!E}_y7JQ2Z)59>qHGsx7deaQl9luW0;`sHrtf{Fx{?W6cN$KenV|0RLrL8Z22m&-6 z)|c{-p6-QopvE!k*!U?PkKZi%c8%DNBkFONXzB5jfVdqRq)s3f8cg3H z^j%N>0H&55dS{@PFgT>o6Lk}M(`+@01e7)Wa+cKN_gSPKzpt92Z22nsLA0 zH^K_s3?$Z<7bZkO(f*5z5_PMtq0weRpY;uYHr147P4|1JYTee~s7>tOIBIV9)d~rZ z)8?SRfgiT%aPTOU^C;Y=de0V1RS95iehb%<`S&nqeb=F=QXx6^JxVku1&CVyZOePElg z2oInk*16KaPm3cS0us5KP})m%g)B;)dZI+aaccQhkq~qEsSi7(ay@ zQi1j-cS7WW04|Yo>8n=Q>0|Jh^;hE`q1(~VpdYmEGkjj$`Erh}H(RHkNenXagt{@w zRz}^wIeT9NFlqqj5yi05^nJLPLg)0jsxdu}@d^5(!p|BJ0Wvi7`Vi`gH)ODMEUsi1{Ixd z0Ck8caN+USt@7cGrgXO~IsSMy=%xt_9{(}K1cCbaILsWZ8*Clap|0Ksc^ij=rf5Ct zN2rDY2~PZeMB*$!fgXPeXr!m4b|dugl;h|P*j|&){t4Xf3%4Z-Wsi{U z)0Ne)OJAn&rmsK~Rg_mJ=Mtm+nEK9u{Kq20jgt&dU1m1o3H(3DHz}BY0+R5QAF&u% zp!$^Y`ZMBNpHedLGQykS@z(-%Crn}3$urnZuvHvg6ZcdJepBL`iU8&Kpsnjo(t51^b?TK=k$G_Q2_yg!{^`>c?}4}12qtD>yM^J znLEF~h9gQLNg4g81?HD>d{&PC0b^wo!8%`+Ro)9FWDxH|Is!rLOA>b@vI27aAs8C} z5`^>&l;@vrHhnl;*Z8;?xO6ikGPF?zUISAYvSj%NXN7`va~Lo)CZWg5VroP z1A!lb@ZlH`Q;Boo&GZHgmSNC>;~&E<#09OssdedBF%DD1h)YNpRTkrMzZZmELQ5S? zzd-v0OQs*{;@Kz*)(cLFXgHdcu5M=9qBw7^?LRUF0p12cRuA0$H`)qOmf z=bKTNK@JFsmtA=U04A*hi1kO#{T>da&Q~pW*w(u7TW~~2;W)**j~>NUH3lpE^ZILj zljRI!I&pp8>ExU`2L;wmj3>>yW{fB4fDEK2AsG}&YCOJVSm}jv_t0g$jAk)xS6vy9 zGUJS@gP95uq>MUFrU+$f>wFb;nQ6c?ZWjaTTCFKcztPtaAlf$I0`~D5cDUGvfg0u% zhG9qi{6KFt zsPMA1&&w}H>~)zVs0SUzq#E-!GY#Zu*m0R7D1w{-I)UezFO0@g%P6h17OWu(v@piMB+pa0123UZT2*I5Qd|51*`7&*9W*B7m z_z^Q;(2n6OT?s;KLc_GLl~PDNWQI`+kTlDgVM!8YhO^fJ6_Qg4LAU6l6xC5YDnacW znI{wOW_BjdW4w&L#}oR7WHJ&s>)mwxg&T2hhwlc&x^N;nF3WCmR(= zP@UKwJifcuKgH+611Q!>>RmM>x%52MtW+ObjFKFAM(Y3ADV-vpQ65y2Ry-!Vb&Q; z6ysFpi4k%j!-KJ`QAr{5^CQ}hoT88N*jQQJ=&cT>A3*{Y;E$P&mDaZxL-MLxUrk+R zImCiZLVC?hb$X086$D<6}WO>Nt(& zOWnhlbrW8cMd4mh-8THM_f3%-K#c$39G>xy%;k*+q+g*+w8CdVJTtpU-3*V{gCN^F zn}QfIbBVsM9QbZpjLb7u!aVZ~_=OA*d>kxX%;UpD*c@b<;8}-}5kWYUxfI!J0ao`j zo8e}WhG6gwm0{yO0a7rKyGh%)AIevG9#cGN$X?@xL~|bhOhbAC;z$`oX1m7pV1z2k zs2S+R^gLW`hB|>wygC!E3kj`YBp3k`kv55ddH54i^#>y)gS@1UUn{8bEqtmL>pPyd z)HQv(%JDLnf>0N^k7>SYx)xkZIM*$B>kz>qHbi=rZU8-pp%CSSLWwp!j(~zd0>cu# zo4HAjw?Nh4BR61(z%(9NUtLj!0fq{YtGj(m0RY8EGM)%e>krmLkGrU{fpajN6zjhV zv`F3`w-N9XHP}GEuu5lO#vft&G&=@TF%d=>nJ$=ov|684!!D_O98!QLKCm-&nRZ50 z7g zmIgCR$rr^qL5atbazl$08i{}~FhfybI}zcDJOeKF35Mak!N@qXl=3+Km`uvBDy}CG?-tUp}T;OC*12Ln$kDtNYzhd1z7@hsxehC!@~8NeKV3?I&Fb5@^o z?wliT;AH#(mxuGYIzg}QXvIvkJD!4~#js9u+E26M)6xVUKr;L?519Cd%j3bv!x=nG z;$bolHMw_8oXOLPub8M(?@g&Nz#bE}w&D?Rrvm#R7!?CJbiSF_sniy}9l_7KxwXAT z58oUkr=8eqdf2E{R4&D00Cl6{Z$L`LBPScu6>vR9=EdAb$N8HKyKflmUatMWcJ_vB ztCv9h*UsMVn-<$;>;K(Njx+z6llRlv`1=2!GH>5ty!giD|FyAq;YQl!$faG5|G&Gj zHzEq|JYBJho@X@=c$p&{G!K8Imwt(dKkxRpc}+dtncqa1)cvMAZfiy##-Ohu`zol4`=pGyw1Y z4;~&zv3j1o#u6qEl|0Dc<7J*rsjT7@0#B!tJOrre0b)|iYqgQweiKJECviw$#={j>JM#19M0$dUYguNVLV^?EWNLxOBi^dICVZOlX~75W zt?R^R2|mm5Sw$eGPa3RNW6(y7C(j@gz+yB&l1nJ3W}Q$@PB0Ip&XCDdcaZ5-C!P8P z$!~RMxjtFN#@&xAl`*QZYC&7Gry0IAd3c(KU-IxQ5C7q*s^a&bg*~4^a2&y9 zx}y^Gi`>u)QVNR%(9cJqvAKy@NczM9klZXB z0a0}_!xVF&B-1Q?Aks>5T6A-eRm`2rS_KL`)hyG-{u$%h66A1s0RcrWiiZtkHF7$5 zI-X{cA$G#$9Qg2xikJBa4=k_QhKkb0_7 z3Fl^Y_cjDMSCB8ZG6&>Asu&Y!$QMJHkU2xncl^{8%!%d(!Ln=(c{EONb|bYRQz;xS zcTYj4P3uann$o-v`C>i-JQgzqFk3c-se*c4EFg;w1)5c$MvTb21AxNV7f|E415VyY zb|CAZu+-YVUJZSXhtzWdA;W@Y&SY0$ z5-vdxmI-^%%k0oowfL}4@g;ZC$FOLnOBVKW=>zzWN-#o%V`w67uD>BA=$m?E3XmWZw8b@6ize&WDC$Is#lvdotq%|5NvzFvT zC6jg^0im211P(mrTp^S}O%wtela5$Ah$FLSH=!F~HVu`Y!xuvwnXSc=LCjMjLyH9z z0smyI15p!)O#Fo)?pL6BN_Gc=;7fkv*k?>OJz9rEzHN>Uz6`YS4$dG4K#e21oab2c z1Q9OeQv)?pg>Y8MJOkyXsy2Cn!xlqY(7(Mo$B*K{@mFUzGam1;_(M+}E z5%Y!faHLbmd5hd`QLH=3JDqZ;lc&q&bU9B~$>}PbHY$zbx*RSPBG$}xL^yuGU>U!k z<=dn@j+jf3na0eW6=-t<4grWEqCnLZRs+nu~_qAsrfKWN1*O7ItBvmWk-#V1Hw8HE~75WHxvodL9vH% zhi5{m>VU!7sFt-)5G>%0dEbsp+WVaUb*)*Z!bU z>8a;rlA3fv$xQ%(YsEAZoU%+a`LR;Ti*{m~36NZzBnf1x0U-e@B0|zeC4fcYH)d)n znxF_%%JKlz;~W3MEu}El)X`IXl6eLK`KwA8pv>=q5_#K~xS|8TimHa+zIm>~iaJ+%Q?+U!&ktH*>AFiFHhAy7^OUm^srDNgW<5y)g}+(&T|9KWG~ z_i%+a`RftcQqSw5-jEUgPNfK)Ks~(}BKL^i?}`b%C}LHWT)li zkACpYULC+MMC6;MhAU+7O{0YQj6CDr;5o~vl*fhlRq4Xwx^&@v2netsf6Ka5qwr;S z6Ag5rXq>Eq6YE?}&qU#aUjU!vw@z425B8t|>>%{`DRtG2FGf@N;`&+yUUi3o{xh=L z7M&jm9+iTb-4ux!4@bqCVFCzG;O%;q2xW#*4pXSZDAHI7_kqO`Vo2CQdMF0f_Se&# z*E1;d*H4-R84|gpgk-Ljs?xW^Hux)=5sY}7vG%ChsKEusvCT#Rp9XyV)y)biR{ZnK zva)hY+lu1Nl{mnv0u8Jx&RckzKFQNg9+va43I`ZZoWWk=ISeJv+i;2*^U7wJFA`FB z7vz61&d3JON3*LX~K;|K+i<7~Opi*g=(yj4I52?DO*QQ)ff;|ExhCYgym zyRpl2=Ecd#$d*KW?cx6D$Y?AP-?cxPIFvF`jx#BSG2Jw3x9!{!jSOsx#iQX}(bOn@ zido6Y6=R-hG%Z}z*0pf3udTJMZ=kKUGt%DQy0i<2zK&>rd)v~1Xn$lOe*A12Gs0We z?%FvTi4R1Q11)=^$rNhqKG+e)FDG^drmagxhNFiP$pgKy)W}fe$QE4I5hSExLMYl~ z;#K9b{$wJR7#t06OB{+OckYi44TW2WMI)&w=0QxOvLoCX1`$|zH%U%g z`=XiXhSSDnn)g57aNMM*td7D?H0QzwQ+c4=xBS};=S!F1`K0|X?Q`qtq-?KKa=Uc z4bh>I-N{&Z|LEvQYT1GX`x4>dEO+}8!wUuv4aMRI7OVu~0~-gryE>LEX4?d^LM%4xaY&-KxpZeV-cQN=Syv>x#7tlsEgWNc`3TZBS83K@)! zQeG#m8ygzhgx-n8_q|!}E^b!bbr$nn^9BW;^!%_Oa+#v^Ux}wr@`?dguyly}ruCu8l`hz!lyR zO(n*X{n1o-S0XW#3hx|ECgS^sj_f)z5*^SFSC7Sp2BJw{XSj|33?pb7Q&92nFcgo{ zjp{EnjYS*dsc3REH23PgF*%D4tl5vw=to~1n;UA@w_!SxquUax7%khed4{nB^h|^W zDd@4HfZI)D)9C}YgnHur`xD7!Ax)9GCMk$S$T`DMS}x_3p+bo2L~1nw6?dyWi0Jl5@~H~Thz96 zasSdt+fsBoK-6Eo@G%=g6;DAHNm{M z%Tv9XzK536gohxs7wUwK!=df=(tMIvdRL%!f~N26PsT<@R}Urn56mL65@pa3qCI01 zM7}qg5@3iV-nZcfY%?XjLf}LqYIlxA`(uN#{?)Pg00cNytjS=P@CH;EfBhcA`2Iit z_M0T>qxcL>P*JUq^exe3OK3RNpGXeH`dUKTaa$VN5?TX8IF^id$D?DT$;ePkXxmud zP^=%U-IX{Hjd%AgUL5J_?^@KpbYW+-ZOPJE6Dxy#i4DWoDgyyy%FyE_eE7Gmrzy8g>Pa3;=Z+ZCO_FH~CdGwZd{OohL{O0BppK4zAqfb3P z^2VoM9=q@}zg+m<&wYJs)lK52tBaz4*!?7-*MB^-@4Ox^Q%QZwPhNk+*KYgGZ@%{IZ~o`~AAWV#*FSvz z$UnZYx9x$;L+c*+>Cl(I`B!~?56-N4&$s_L_V`09|NTqf`P1kA?mN>mFFi7E|F{16 z$(x@2m(GiS{@9COnD)c>|D^6mlRx^Ge_j0I)}Ji=!|)Ry*z@|o-|Vpiub?)mBZUqrvr^2?QhHNPAS^q*}%b>{7Rww!h3hofhG;`!$KFWu8!|J=X*sXjJk`q}3$Z#w6} zA3t@@9iRQHhChG$Lo@eZ+i~vDyN1vG`?)Wkd*dy?JNNsWcZSY+us>9N=8d7Foxcbz zx!!x;{qOk3dA09YGwW-g`#{s}cl=Y+L$}PGy=KL+*?;-R$~jL*FPZa=X{kAXzpkoz z+Y=ql`|t9#9Kh#>QxCP=dhnH&Tb@t0wmc{OdP5x*mUa;lG?S+WC`zf9ImRUpTVl z)4%=L()Q@>O9%gWVA&Ve|LDS>eCp;E%a3hYIrp!3ubdhC{K{)bf7J7{Xkzu9N2m3U zt@@j_U%cy*i@tsHw#_x4{+G?a{l%?YcJF;{%m4ahXzR31o{iJ(fAtf9dLLqBWa76HPs{e&3#1PsY}L@7|$Xe^fpE zwJ(2ectzC%!&Cn5$@q242N9xIkK8seW8|8_Z(KF^7i*G#_{zrAwx)fl>sDTwTK^T_ zsQL9@jQ-$;*GIPvEIjy|vD*(`uL=SD zJ+}S&C(ksDc6^?~Qm+@lpO?`7ZnXbaeAk0-Hv{f-;KhEN-w&Fu1>Ou`UjRS9k9w-W zhd$K*3ix?TrC~e?TrV^upaWmt585sRuYLnspTPALz&r#zr$8&lfW|)n?pp9{G1_z$ zt}h1M>!9_+fSCop{Tpcg4)B6C#xd~nTG0P>(33ztGx6->;Li@UWf97J9p!7#Uta`{ zP3Yf#@b`yk!wh^bL%YsKTi1j3t!T^NgP;6s0v(rQ%$COUUxAm^p!e5v4dW=vGy%^s z@MIC{y%KGD0<>14Z=RlN82900qTKI6^Si){Pl1*f@vIH~Rs-BC@O%+y+>W+ffqFX8 zCtpJy1CWge!LJDFji6ut3H5vw*M9`9oP?N*c0WG_w4lyR$S{thP4}RVZj|u>=R3f^ zGXe8y$iWYQ|MfQT8}0fj>i9C+{$YGi1Kg9~>0{v4pK$L0>Sp}23NZfw_+GU0eAIUj zWbt~mDYVcqmZ6=$0+as%ynGe(nJ$U`vl6wK<5tte-dq70ouO;JP)8v_o2=MsN)Ur@*}|a36#4RI37k{U4eEs zfFCcQu0~v+fqH%m`aV6&Fz!NI)^>wV^$Is`KkKqB6&kFkIatPo{ z3dz^+H7G^;rvSXj4Y)Yj|4|IIILT|FT@yH-za?w&F8eGHOc0Xdb^Z__XS1cwuh5u&{)@QB zVAy+gXGN?X(%#IwxT|2@JBuT7wk($du$h2rA9TGxw%Xs2Z=|)qk^2iM9L~KmJAVP4 zGyZ=D2-refLKCDltN44lr^3Iq=D-0wx< zIjV4}7j)l>qD}clo!_Ce6<&TO3%b96JBlKUbP>M2qI|ji4nV<=@?XQ<2`7z?%Dq{e z9!&4F=*+^c0BNv;tOto?9@T)Gz7OaEZch0q3d|?+@*KyvQGAh8aq?zu(YJCx3(zHY z(A);ct3fe0WK^4AI=ztKLIkp%&cMZO;QIiW*S_Mm=@}GXWv@pUr*4(IpYE%L#Om-v zBXiqA`dtXSmfLGD+)=d@f?Ep_(8F*`Oo|b=zbu8IYBGe@P669pY;W->0P=b@2f$4> zi+9eA0M6^2;>CY};%%q(<0+1zKr7`l9N-Gat!#>3O`L~>5Ze$#sF*ub+=zaPMVF=eb4!7pxE?wZhLzR z5VqvM;YvG7XC`%Fl(Q@!z%It6g2KxHzQi3|tBrHr9|iD=eBhm6q5er(IUV+6peSf> zab5Te0M_Zvj`e2FZrky)^_xJE^XAg~Fko#i_gy28q40cNn2xddi`jpT;=Q_f&VH)B zfme%ZisPqtrG<1VMxIOZIZ1T%T5F+d>LtLuE);pw0G4LjL8l&$=LqIjbZ18^pzE%zp*ZhvMbRwvx@fWZ>|X(Jrt%Av z!#z+Y{raUTNEFV|Z$D=)E@w#dZDG5yquin$#&)DyJ2h7O(4^Em)t><1^0%_Nd<&A; z^K)-cFZEvb7>c*#iqj?yLge^US!Y4G0|22MfODZ=qR{fU@`Cmnq>G!J-d<4Xe%7Pl z5~qTE&Gwk6dmL%i1R_g{BDc(E>(qAcmjifJAuzSgNow7Y{0T@FDI}-+I(be!bSlt; z5J~k8mU6W5Q2>Su0gG#86_V=93PGIO>D;560X~xk)cv)cA3%|9Eb?}~nf@jqT~RJl zXVQNL6ib{@*t0Iqz%`iTSmXq34|1GAI{=zTx*epvxM&klgzdH4Q8?Fn0w664M89Xg z_?Y1&0G2re&{mduUHvZr-j)wO3>n~?Js50r9L2ZHDEF`9m~%oFYp85*?xpo@Krx^7 zDio#OTYm|PFHzaJ(|>jST>xHH8o0Ou{-8J#I(U1Y%(-`e4GTbN~5L;5y`UX27oj4tdRyT3CB3n*el1aDJ23Tbg{8 zM%ORc?SaMGhgd?JIcFrJKmvo4qawc>v;a5*-(DQJc>A9Lu*jw+zqoeJ$82|polqUR z7q8{r0PD4b<=(Ac$nWBaz6?aGoe|};tT>vAIR(9*qo=$E?EnH%FU>aR>5`KuW^s2= zyjlu<>^|LpFN!y@b)y(@Fc)^fMQ6T?g77i>MPi2V-dfJ79gw%a^G!>73|pKD&jAU9 z&J9U%;PrFGw`kzgVg9WEh0W2R)Q0O?>og?3WQymR;7 z3$PVVU@T6%Pc9dK3F>d$YAz18DlGjuk>LEHQ*k6Wl!v4^>&yj)vB!xA`IKlR98%<_ zEQ+&t6Hr5FOEpsKm2)3`5l9@nwm8^70Zb$>-zn%cpL1Os+bke^|Cf5v_&xwFVJq!G zD*MP8oZ?;hMF5LR+JTFMJPVL5h4ryp?8!RaF@^fz-0pMREj_SP;fB%~#Y&)9?L^h- zQL-0kq_+zx(P*4&zb;pM491qLoeINpW*mrw>AnG%pzBUu`&tws5;iCWbI(M@5UjRy z?SBGT3yYT9>wO*Yarpp+zOA?n+yMmVD+GsyP-ntkK`~K+{NSxPFDEUuTBBzuODVv5 z0Emwui*@)*BcEFbr=lQdA`<`?A`8J4H=Tk05kR6hb|7t|oI%V^2aw|SU;{uVv`mlD zd*INtOLMNVuNXK%w2N^D|17{otlj~kB?oo;+`_h>0bo9*LPuD|h3M?Ag5t$3>H!oN z)y#JcrKIDB023bOgO#f)^A?o=FHZC^fQxYD*It}4zjg-F?04?1b&HW#BJ=ZWa(14vlKAv%Xu{qPw1Gce_`>OoDq^GHT3s59v=0$6(88Mx3@ zI3o!!#qgb-wF}MQcrXQPl@R-^d)>{BBmpY*>$kn<47q+Mb?LP!_XCLti$+qe#{b0~ zuz2SUFSG2Ku5lAOaS)>7%%ImN7N?PQo9JABp(Vo_yY9o{w&cILffa8HUjMA5SJ$=; zgi#!$BK*!R`XGSB)aHPa05}LCP;-gJN&R1?z>AZd#Lr#CUFq7nGI}5M$E^dVt+3Qv z1LW*MY4O_b1YGXGBuTmP|&mQ9@!Qnv8J2ftFA-ye}Mz z#ru=dVLXWph4!nRJ3^^wB-y{;+}G33U03qJvEN2CgdeJhlGr(AXh@#LqlZUB@?bET z7|vpe3}U;3iNJ%gcr3L)nhHe^NBhUP7lpaSR#7ZIG6pJ>A?_Rz8ch_`5E}%=ED=kE zhGVG|z(bKx{}A>9z}YB&(j7LJ+VBZ+*tlgNlo%9TvAB?m8dBKQ09!H)gi>Ss_F>}~ zFvV=Mfohf&huE-L2vsaj0qq2u55-3JCqQy!6dO%ocM~DhYCf6>NNJLJej%p5NdJLQ z0u1lhyGNLF)ZPk;t@>`LFFKe=DpVjEM3`N=9Q#JP#S#J=g(+@7kz)(EYWAKknC8(? zVqXjbA!1<8)>zQAk!18>EHRb}Ngu@GLOFXNY;MZ7ceDtVebIffc$`ejb|CxT;_`tQ zgpk`~m>0WX%WI_*xf+$?oW9B-e%I+BM*rux*rL-7IrbgFc0$;8q@YK^DD)cjRY|Ra z51Xs9#N=a~z$H9Z`d&bgdDiMkD%Ky04q(?2Fm-S&-cQL-nX?sYrE!C?WC}_KfrW4i zZI*E`Tgs(?oeWhJ3#4naTOv_IWlCD32O~pc5$rX>0wb^@BctZR-SJ4@kg@^ENIW&f z=X0-a!$v4z>cLoYbPNJ_AbKR!pBRW@7m)U2VRPJEhutOwO-}Pg1f;RFPj(vg9wK7W z@E{%<8|lZ!Oj1bkAgg;)W*~7Wo-F{w8G$te7qf*@Ve>;~&(IJysR|uQjFD1hWZ4 zq%VP;t3rK8D1gc^u8qS2ht2KWCWBToYcI(4VP~_ImEJ_Z5~2R6lEKJm4uQG(bWlo1 z6h#+WgI#C_Lj93=XnzD|A9W2UAb+EW66|0wb-mh3C`6w_smx=bY`>#Ckal6s@x*8- zw*i-SO7qNF?uc?Kzjh6_=Tg24o1sJ@7D`SBl95As@}p!}xyaGPh}KpGWX`m92C}3U z%_CKP(a<3FEE))#cbg~xk+9_6a%`gTu;%b^kr0IfGK0;oSWet_0&6oZgjca^j% zX!;u7A5O$62ZOQ0s4OXL%i&0cL!q7f6Nf?&|5S`RW_uQbixmliV?_PY{si?28=!>E zX=}1%*zCq-YbcjlnM}f7qjJmcrDS)doP46@jf0_h0`Y?sLnAe03#hOJ{Lp@EZl-)m zVl7&PI6|K3q9Jh@Rux&VaLJ=i`pV`=diyvqvK!ohJ-iBM&!;SDLX#JgDdg z<04y2r|!nKPN4yCHBRP;-89e4yO}emYb+<2rP*pR%{@)VfZ9^YB30SgLT=@G(wzE2 zcyf^yz5RBZ!*wsF`IBus7{q|&2tvWbF(t&Xy>LcQxQ%v{mM^h8dC`cm5#7&G2#MvY z#Lx+E`^E+bqe=5pjY_!|TT$(#7#p;NA}KN@HVpk1J9cP4&O)gXxJS$WiCF70Nl-aM zp|-rICR6(Ha{VY9{V01Lhj8`{MdAl?=1fzr$;oRc++Rc)Z(Ezf=4y?e+|+=y5@PM* zAv=x~edt6aCN?9Z^4n}#*7}x(Kr>%gZ?6+o_79B>L}6wuPS{+IW|S>x)41ZTFd)h3 zRbvQ<(G3MCom#PALMuRzEzGp_H#rzK7ncv9TSftK>239BscgVS0}z4Vf(SBLh=k%} z!_g$7ok(&Y&Y;Y&8Dc+eT0PI&(ou{g!VAkP#)c3eMN=z7%}0nfJ}EJi{Z!t z*mVq+xHBZ&T?*W2P@AgJ+|xUZrNDJ7IDfgQ@)??+T{2sUCQ5kp)f7J}_eBx4vLAQDtk7Y=QVqvLSBCIO*YhTZnsjtxn$RIyO(o!Ah;!L^#kx46X7xN3L*>!Pvp*&=K>>!VcE0u)LtrwUu)m+~ZtK-R;DBtpZH(f<9ctq%>3n)BJ#tPil69>y`)%&reL_a}zN zhLIasq<7uRD-Y44EM+}JYk`Ldn@^j&NO%-+c`|f}qfRT9OGd4>MfPDZpkx;XQ;fkA z@i4?No{G_5s{lU|8i>&=l0734*jff4%W6XS!_IQ8xvfIg#-yRv4Js+K6LIl2mS4`} zikhu3N#t1Mpm5#F+?)oooufz40Ytk677<9ywzbl@Bt{Fw$#y2yAknfK;157#2qwcV zv(6&UW=*fQq?1u6S(;UPhp{9IfblXqLwjc9UYDJg^qq)sWBj^etqAY8L%vKy@ zrV1(-A$@BVZ$WCJc%{V+_=Va`D0?FPo(s`vJ=GSW(j1{T`!sA`toHQeKmc1ys$flp zt&u+4@J*0PbV4SUlWKBD+!uv&%Ik;ILOe?i&@IJ}gfOm;iSj`l5L+VoIC{jqx=0}= z0-_uxU_K%AhMU(cE|eFtZ*!HlnqqN9;cxu(@?@5`fuz%05UUM8&=s_^O90JCfBnnnUF#FQW0I@(CX8C@js@AptMN z&F?6gzCMlQFXI~W>2<%@uEM$tVwAkpZB`f*SXNW)5t=2eHX+VJh0Qg) zz_`{uvCYU9!u48vV@pWMpt~j1$FwraXbD`46sZ)ptl6Ge*nHKj*_s$ZM3hQkYi)@^ z4$+k#;%?F58ms$MOaNqn#CFrl9QAD4B!J64mc zM?!>}tvm7EajZtg-WSIP5bZGZwr*^Q#`f(W#huP$H8qvtEstq5pGI_ zdi#IeTa0_=_MSL$7S=lfLRM~3#!dZj8c6ceY=ds@hL66AMng?1a-&Hf=3GfEj#I>u zh|y4c+cEP(iidJc%5gYGugD&>UW^Pg^SfjhksuFo;l+rQlqs@e-<0+N9Nu7Xl1XoTBAGu3Rx?@^g$z;F^1nl!s?wjQ2< zADnCA_#{3zH$2gE4;Z8j%h1!xnpshEY;-Ou8~}lwUJRSPI*cQ3=(G%Kvtb^y-F%Rn z!eLa1GzR8gm6cO&V{sAlUM$Ks*A|*t%S>j0$}{s9+RS_w)W%wgv)xSnt01WXyi zk%5HSi>9C>q}h_T0H-j}%%54;(@+@aKNCh5n>Q70Wm6FS~Ki_pTEdqtf zK1M_MVgf~0DJ+Tj5OP@ddE~G;btoF&2Y+MvHi?4GIo9|Iev2E?hd2Sv)CUuo_T<9$ zwNO8R(DOl<*r$>`gUmx@oBKI?k`rCaQp>;vn1-$KvKT9?rg`T1JMr2SE3nkjEJGfl z+nP@a6)Qs_Hl0dX(yqcAv(W*ERRR+@jtG$DCi5&JW;!6SN(c!H6b})~YwpPm3^g2D z10*UPq2(OD!yYVV6{~Q*4E3w+^zBL#HsAT?fhDj2S(s^dN^l{rv*VUPs!XDGXh5{N ze7GDv6W2XpjFn|8UAP-JwYjiy+Ga4;Y?A>MB6V>xN`aAo;Q9fn9h1389Wo=&nJ+Ga z!F(`Wm0A?CNwU|qiV&M4Txm0m&DA6whvQxTaJJp)&AtjkvE;L{q4 zFkzNJt0b4No2?d9WNiT^+20I2Q+wvNazV49MSfHALUbkno0;T37)x<>KGzC8B36br zn~+EBw5(hb$Q}`=T+7#=HY{6hrQ+5pvA>vn*itSc7d{krX;-=UvW=4N*nT=-SzBRi zle}(fwPm)YWt&b1Wp=n^u}IL_`LyvZW7~p+nh-WssSQKH+{k&F^%fhlg)zK%IrH2S zvk>NG%Hja1SaKszShb?T6l58xl0TL^$R!*0WU=Q9e=cIv(rtP1i8yg5uXa%tx3TvG zywWX-I3TiQO;F@WE7owEblRG2!#bhK3tXD)fJx{w*KGj0@i1} zoW{Jsp#ixpffcN(CSZ1auJqw!39R6@q_hPB)jyY&_!PW~WqeG#4`lZd%!NjQYxwuxj+tU5Qi-Q^4f)>z%>eNyxHJp~}6@aw36-RJ?In4yqdF-WNE9^$wqS6slr26M+91g6|@y|tNToCPK<>? zqyX-T3EKo?*xYH&Gi3)I7WxUkB&R2cdx|c)0n)5STFrhtkY4%rQ;KGw8zi%0??TvV zYm99hj1ENy3s%(%FwK?KkB1_NJ5=XnxfeDsJ1tbqCFhN}Ct%dIseV7(o(#zABCctD z`#C0UrqUw%(pEAFC3WFrgXL<#mOb!&THP(#>rwPsHeb6cX1LWN0!z&E=Z5Sv_{f1; zO|>8$A23f6Fp!b33(oum`Ve?8%M`9LJA=9Z$dT2NwF7~7y zf@R6&6EO4D6pbHTwrAtc-94M=-Q;p@@5WtQcR-Bu?388c)ezTQ?P4L=f#UGX=mtp_ zi1{rrsCjw-cOxsLC7G4CG?1oN{QH3A?vYbJW367%h~Jvv1-$06LP8NnO9Hax5wL0@$${E3@U}TGQ zo@E2`y;8l4j|`hDa)!a?JE5-P1SPMMpVtsisGTahMXQkA73KoqqK#Yj4y**rvRh`% z-MwvIIu+CHh2l_X@HHbOW-3F7qi-)lXZsvBcWXZ(;kB!&6=uldsGL0$lrlGwm0N^! zqjCO3V2A1k?WAlnR6b5bzB&S z+bSUE48{y6loUIRRd@0yYC4p*GP2s)YFlkhA;1=Czqc#Ww?++Nqg(jPGUYG_U@i8^ zygjm$t(N!e$INV$(^4a98ISl)8~ir?^R+T@tlh<*DuInTKTxm&C|_uDrp}7$6`NOW z%gyQJdTYY$FIZs7e8DVp*c@@hP}tvv;g*$PwYyjCR0s4g_}pszkolIXEEVOs*#vhM ztaqSC3%0{3z#Y+Qg{Z*fS{-Khltua@<%pwnf0SW=-3hXHJ=--cRGzc(&%WPp8$ zkabJSanbkNMhLRZIj)4y7kC{Y(AOeBrDyUosyOCPR32z zQJF8AyWpoF4mxoR| zFKNxO3ad3%j-_Q?>w@#pC~GB)P@AQV)-s#6lYBCh$mR<9#UII;kf;@Bmz_D`N+Trp z)FdX$tMx<+^5J#*j48G3AlK?n1%NEH^~*r1&;n4SUuTHCI7O>Adh7_+vZ22OP1Y0V zw?af>ViOGc`)uT~hYfS{HoRXGTaV$r$_`~h@+%jr; zu>I}W*}Jpts;Z4`4?Vd4_U5in9rC>R2yv`gb{WzDS0aa+x@;uHCHV{tF3YX9yDZUn zB~nr%BbUXaqezR#2e4G5Biwe`<`~veq!NRpVcP<$9B6oCpwHO3p{Jv3kzv(+Xtq^1 z+Sj}H;j?eLHhpDX$79u>Y-p^^QoD%fq3mVeNwcjS-hRC%k?b8B+KeAW7{e(fgQL;# zz|fFDyfGU%gbh8|jBE`yf?0>%eE3J6vh7}c$k8f%Si27&{@KfKGqxK$u+Inou%WJj z&0dA)v++F(AMCG;tugQ}Xnd0Rr0{ur|4eB_y!<29uXZ4UQYjxD_;RB7DCMp}Qj zd9gcW6+4FQ#YQMwz@M#B@_~xqzD^ER2&cT+Z)|2Ff8-f?X#eZQ zg$btc0~FWd^WfL0UK@lA2uab=$arl(P{ zX|!ekbz(PG@W8kNoo!}!o8^{AN6?~(Y|KskeAP}r@NyHRglr5H#-6*}d6!%3s7Tq^xIKS3#8G^wtJ6Im$KpSFv3PBKG*^e zG&P|txy-FfllbOdG`Uv4wKj**e(r2TnfIU`Zm7i9r40#}`GQckUhVN?fIHL!(!-5m z)jo`h9?dgeahvqF+7~&xu8=TeEd7Z?zum(r8RZd-CxY_DHTjS|N#1 zP!N&6Sz`DB+XE@#rijCM#w{d6&=B@iu19n5VUc672gbV+HA9E7snUP_{olud{{v_- B$*}+c literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.Pager.dll b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/Microsoft.PowerShell.Pager.dll new file mode 100644 index 0000000000000000000000000000000000000000..eb836f4fc2322068147f1662240dc837defa666b GIT binary patch literal 16784 zcmeHu2Ut@})9|JcI)Zeh1OWlTa1y!-(h;SDfYJ;hKrkejgsM`a7qKE%?0{ld>;*wo z6ng=C#dfixUMtu7&pDxa^}gkK{_lUk|9ig0GiPUZcXoDWcV~A_3>r5NQ4m5@c)ou} zXg|F1kj(=!_&~Mb z>61J_dWMM07eYl`R$OLW2S^W|KFI?iuRbV|O@Q&^373tGL+`_C~=|&{xYUmfnkZaX4e}A1UT7B8-M%B-eRasE0Tv14|bAKC^8Ib zVsgkB1cZMY6x}&ck7S`^r2}oNb%eU`QqoZ_z#9so22GZuK+X#Ep`@a#O1095%#eXP znp7Jdng(?lRo4p4tz=8lpek!nbz?NC8dTa`9_t~|wg6N~6#_q+qOFgNn;alynmjHq zO_QdhDQ}}Kt2zMK2*K_&4S8TAuOZLZps^mw%3vnT*bu0$5OA8NTz@$$Luh0&$X-QW zhq+Q-cdW9ms?DNqvX#BQg6>$F6^z7Y_~h^3zXM;W;4%@i-F3nk@;a(6i9nHc1Y?zT zG#G)dC}=fdTckmh{z@mhWBE$5LKte7!eE}s6vkJEDIrTLR8^ZWisCY%F-XkEwbWB& zuhNx$JwnAsNrNgMYh?nJRA>rgd+M+T5S`!*a1}w3lA>;msv-qcy^%H;WTm2eU0s_m`oomav;7461V0)M#fG68dgydpg6D4Zh0`XeT~ zfR+GcP{sf>C7?eQ&)Xv$h5*z+3(+#*f~RKH&lCy4Q--CJ`(i58zF2g6JL3uGdOfcGF*3m653(($_Oighp-*#mK`TrYv5Pk1U4a--r_U=X&D!JjE>pbZJxQPtJSsGX>32d&8H0O&yh zY!*WyffaDc^Z_a%Yk=xt9UQt6(35}x1PmkKSOT&M$Rl7e`b^OVNz=(VRH5Q9pUCTg zw8bcuY6S2WG6Pr(Fb`VMP&sNsj%WrlqPn6gbdl@@`F!d~v=POVgCH-3*)kC&Qe)5& z=cwLcW7C1z?3fpusF`v)0xMgcp*xsI|Apn!}+bt(=Ei2SeVvUX%> zF9#0=EJ}vCp$JeBq$7hBRRJo)dLn}jH4wE3w@%;W2CE*-)RSg_tauuR(qyy%UqMy| zSHw=@bH_8$@E~Dw7N6^mBJ#u%ZU)0!$mer85}r^XX83ajToI3hMBHqyNX&&Ip~zQ| zj09YXSi%-0vqi}$lby;H1#!hIMDzTH@~h$jI?0tr_ji4=BIlesDE zEWV^0m2ky9d7?oePryacrAR{LQn`}EU~UeP&z3MT%va1|XL1oy7x{3LvQks=;dx46 z%TCIYaFM52%*{yR=SA`)eaVq*5%BVZ^D;L_D4OtdeLo(b8wFzrb^KfyAV?94GT6AT z*!;h(1%pcArDlnUdc8~nKm{UbCywHYc_3?~u(!-xn32ij6Ai+-e0DC86Ze*Pk0w+E zw&O^CF7Os+=81TzX}>JW$YcxhdfS9&2_(D>unEv5yd)lTI9K@Dz(h$Hjgqz7LD}L6BLbi$E}hWt`0u2?ZIjMiEE0 zRzY_#PY@5EwH$7yW0$-W3{t1?#V8GbP_`6r@0)n!DpoF0; z#6uFCA|Hn6_#0AsNI`6&f3shu=N~4gugq;gR62>IKmitVkkQbgdWRTWgBH@v>8Dl}D7GlX7%k;2(Yn%~b2xf2F%7PfL4g+lAE z05X9907-bBOj1*$(aEY%nPP<$6iDES@dWoW5{e}2M2py&!9sy=E^aEAFOg{?VUCyt z^<jy^nbCZC-b44Wk~AR+CZ zwLte4iZX?=4G!+WBqR^J7MCsNB4k2B238zv`Y-D;WR;n4_J%(=oRg3ofxs+)lVI;o zKqqzr8MhuzKkXER1`vu>UiJ!+fmGU>*pYs+-KyU-oQKcQICdN}P8bisvHyn}z;8c9*r@2oPG$EbF#1n)yxvH7 zVm#oE3VTs^Dy29;HVRIri2!|3IOGE0z7!0206;%k)D1OIUwxCEAMqKDC)s<141ts9 zk7r4o>;n%8xU!K5`0(JY%LSeSlmh!Mp3I3>kx}XhrWL~8pGowQ2P0ww2Kx=9Am9i`Rwx(4 zlLCFviP|ElEhMDsl_mjB`J|q{eSm|QXv>4Ty|sSDlYv5kTMo1k!8p?3&4)jn%7)S4 zR6Jp@FZc3~BIx2ix`4Ya)aiZ@NUps6hiipE?&4*aoM}=ieoYh{ml2@ z8-o|zdPtyW+``G=**PF}U;F>cF7%)5g2yL*l?I4!9Aq-Wq?W}r#la;bOFpWSz&IEP|!G$k7viAs_>lS$OdU@Q>R?L+B@DN-QDMy)0n zA&mK#wp9Ae6APyZhVI{^yobHwc4tlg_DrAQ*IZL~dxxBe%u{vSUMW>A#-wuJF)8J7 zC522PlQr$Z1%%JC-_*J2t?MmZyX0`3ZI`elWe}-=sf;PoC}XHJP4busrY5F=BMO>I z(QI)V+-M|10aG1Q!-?{ma5zlP5DJo+#+V^aQPTYNNs?)W4Z_PQnkGF*C^%^*bLkPh zQ~_|MhkAR`Ss0Uv8SARDn3yZZV!GI~99_o&$^w+xg<_Kb68XC1r~Hlln3OcIucE+q z6e+16*qWq7mXb*5;-d>?yWW4M-l~0M^3H7YhJFg~92RW2yy=S9nuAhm^u$vZr-oj& zn;kex?__mH>5jdYy4zkSI(lqguf>`@ZnIk@0P@VcAPL1dVyq%~4f0ca#2GXYy0Sy1&bou1hos6e7Az+9J; zlc!KHBfNqdRfnqOIDedm=d9}!w(r{;OnzxR*-tmG1q;QCHK+lY|5{(nn`w<%;*&>N zv(MEdBom@u#9f&ln#C1Kg!FJ8N66H{wD78OnyM^&3}dov9GsjTU7ckTnc)%@X^j*l z{~juLNxDhb*?Hiej18x=$cwN@yh{^m5H=bMsPwP&E%8p1NHT}o+2!QqFfw`~8XRGU zUFHOyFjH*D5%KL{GQ%u}ISkVmpS_7V7=k$u!CV+nj% zk7brzwBD@uq%?epB=A_g=j{h8{aYK$Pgu`?8)uM6>6l$HdPbF^H_89^(c4tpUcWbM zJM0#{xSAt8RULdaIihHpeTzfO`cqGaEeVL2;o`d?WN+H@p`UbDNh^)=!zvx-mmBUW zuhW>he<#*HmVTmj=by?qwiPyI+c5%5bq24VttVu1)*Lz1d2iqKi=*^Y-S_5%m5~}% z>T0d;$LTC;iPsElplcf6o&ILenx=v22kUlyJAQi2tURCn4b1!RT}XEGI*tuuY|U^S2JmL z8Gp%U>rJ&~_G5RGH1)L}wH(a{4bkGWZfE)(XCy;Ny6|E9^s!E;YBZ4PbK zn_KLXuz2_9^OwH7kBqyyx}$7UK=8MB2S_kAtWzde0ny8QtI zwW~U6RHh@iDxP z#r~FU*HJxY=$#42ujV&3Kj&DkmV6;UXgl}7cR#Jo)l}oePPyb)*`c3wHDjmEKE$6N zFAAEM!)m|hq9*rxlZMlP2U?{5Ay1qCxWE|Ld?T-6ctE>4Yr~cPJWMK|4{jhy<^q)1 zYV%qNze@SbWWkC{|4X)37KY(VlA>wdyY+_);pYtAFu!>zJP2)Z>7H4VG$DkO^Lm^B zxB_R)g@rk~VuTZLB2e5jxQ_deJ1HsoXLmrx-2oZg0j#;ruRn>@4H;Xn3pPpBgBJ9r?tfkRwad|^wCyKduaLBuUYBO5<|R)r zSUBFCU%M(`#nZHe*0$vlJC$skYHw_`*_N+Zdu{R9)8h@~o~C3!VTBLsZ})h!LgtOM@UFb-mAJf`0PDx9 zhblF54lQ(h_4%RAICYbtD68sx(Y^jd_XQ+e@96L@pL%1`&PgQ$Zn*E59sguT$TWjD zRd!<^%^Nmkn|;jjeeT~_mv&LycI>R3?^4jZvdH#*a8$XeqeYXeAh|H2ZbiSX`exJ4 zysM*>%=wu3`eJzF?1j_o51UFX67{V1p0%`cv8Zqz<#b{4j``aR&DL*BdBHYGzi$<= zGO_f5Mf@ex(eB~L_C${`r@U^-kF~pE_AoQPUzlIcu1<8nehXQec*GxTkm{PfQu#?)y=3HYHfU zp1ofVqks$e!Oe+=3?(z?c^}Y{3%>ts{Rw7^*$fA49Nxl&3Z4iHsSK(dT@ujcg}__= z?B)m`2R}l>KTHsw2-YpET3D<-&MpzNPh_zOXP40J+`!p|W1(1ZHxACu>mNBgJ5IN! z`v?3bSn+CHcsjLs30AxiD=zO*5`%&jPsK)bcSk1a*#D#MaOa&YsMK^-*MEB)7kq9{OiEu<71YX2&QN=?c_yv+nT=5xnqrYsZg!fH- zQ&LlLFEY=7ap}4wb>MR4#TxfH^Ot%pp3<79GVcgCkzwoJ*(7SonELI|h$l*?hc)_d zT=SN9i_Ji0n7`=uGv4Yd6ZNEbmDBl*U+8Ie5h22{8d2 z6Gfh2T*cx?Q{Npfd+TR&_sg)04UVq_7TbQWwd}Zf=dD`pQmZ8uL27QwZxm)+HECk$ zJ$QY5$obe+y8~R6nw5?=Z{7C$&KtM1OT&C)Tv!t=4GMR>v+TTUJCx3=*g1Aann1XI zpQOn{j<$|8*vefxLNh2u`S7lw_xH;R4TaiMeAj3H?lG9VrYSx=siet>$^1+B{FFS-rf=wR^GT zLsi`3>*#8gU&Gk9OKKEHk5pe;WO5N%AK$UOW`u8!v14;WtNuJUi<0Gtmn!_&v?UXzhE8Iz`1R0Ss9#i zx0aPr!6A)B(F3b;HCU$kuRN7@jQG6a?ArJvRi(?LCq@O2^gZl#YI%07Qqjl>U-MQU zX~@V(KNT+4RLu`P$CT=eF{!=~u76nZqW`qx`r8{|pG#?F@iq)LpsqWiB892abBKpa zD%|sWudK>UH8_9v;@X&9Z$m0`AeG*>zw+aI0JWPM6-SA7;u^ zsEa1|$OIFKw&X^bMX|L|S-*nsYn*K#wAJY>9&u!4EAfI@2H*7}sa6 zd7k&F&mSv(D@yK|xg%|GJH4akin)Hf)g+e-N~1?w+brJm<)-=832`I#xv}0i4)6@w zvFP?^_njp4Y*tu$gUj`0`j)KkwcpQHZTawS*?J?@-yLk$#gZx#zLW0;R7rKdVp5$y zdz4F|keE`Pr$GC?$3l2)Lgy-wTFE5J&#?(9DU7DvJqy$!JRv1{fNqC_OLKwQ+{Jp@ zSS1SS7r_bR6RiRB;!U&DFQWVJqt2ZgY=dfNwNH;%jom!__?U!JGL8M|-k!;hdYUW@ zF8D0Wh3N#p*>YkyI@piHs6}MbyUOB=Ym3ie#i##Ej-?iu8MqU;R2udkOFiMI6mTA; z_g*fUgD`VhHKWKhHlHh|M@B@@eItU0dit`whxppFyoNA6{k)l7>?nQNMZ%}}A@DIS zBa`0sWw=tR5eesU6--Kf*Bg={Ee?oHx^H*qV+aH?5ovMX35|zjCjXZxplf6lO`~1~ z^pUtLAcM&QGE6WL(-m`M+T#HkdoYho7-N4Ow8S z((2bb>HWk}t2zr*^tMbZ)`>j2GG=>TmML@RtfLLvyj!fQi)IBxyipx+IceZJ-xY5v zw`xuvC^S&@pCLVc@LvDpFZX8uVK#2Si4!VQwk|0?>Gw6$k9#CT|BjZ|`$xz9?5h2n zuM96{Z9JLs&WUktkim$di`KL_Jxa^=ROuKKIV@deq$rkW&$-gD7TMWuirZ=u*IuyO zI#Bg_@{4K9Z8aZewiGmR6L_jtCl|laS?x6OhAq39cRHye*r?K;Q9HTX6#_-%U==eDJPNv$;mGTzr1e!?YWo_1f z`9$xk?c@UQilTX3#+l*E&F-fi9=h=e>8tkQjfYoHr)xN2D=IYo9CKzDZCU#L@jkv; zO#*k7+LZv619SH=DbxdNrwZ@xU3uw)=911jBaXah`JDBY^kZk=FPd&6drZ&~noOT#{!&g*CD)kPgS8cX@@Q!Z|OeR zJHal<>T`^5ZBw{m%iwOMvu$V0nl^Y@%Q?x;A5P<|*K5b4grxYX?|yIPt{)KLsQ*-i zCCX0YF<2B9Ss7j#S`z$sKJG6;C)w|7ELd3Yf&~PYEI>G#V^&OS;z-W#KGMRGd_0x_ z@#d}=9306*|7l;{r}BTTx4(v>H1WY%P*pIc?oKHRf7#iH6LRf%W%d20QP-^_NrvCV zyO)e8@9ckIpK`laOJ11cLF%HxtFr?>7Sd-OFG@B%GS-88fpTD1Rq#o!e`>4to4QZO z?JJwM6}AqsN@#B@b04)h_i;sO;P3@SBcCN`wFeD5QfMkq*}yd9Us<#2%YtNEaozj_ zd1bZD$@!0h3@`1_)*7_ZcU_kKEWM?%gKEWrg)`X)PNhy5_1IAJ#+~YI7X=+5$G5#W zdVJ7h`ykfSn=#xbEydQC7c`EoEF6))V9!d$2{k@XEjJi1;k@b$*N#qsP3&LZ%{Xsgw34Pf zt4>vJcl6BLU*dP2zLCAf!6cLyvik~aso$84q*$FTLmxQTdmXl9-rr+++O*lpB5&&h zTb1Uc)~oJcJv-V|zOwyFBTKcV&C~KcbIk9j>wVt8s;c6=ZGEbBD#hZ!@Xo3fli|9e zgv`^T_{EeqBz_1~8g8n8+In={9#?O7f#3W z6Cpk|w$~pb?c+7;=guP;}~%eQXi2WOo;?HK)mRLCE7??J->cir*! z-{V3~q&03f>GnX?RQH3&Cg6_&m7GQIjqC#dQZkrNe`duvv1v8zX|eMZ;xlc zFA2SU#OLJ1xG<)3rIL$aPRF%=*9S#Rc4d?Ek8S!Y;?){$-txiekmI1_n-#F$?&;=D_fC}+?1?P9{Yh?2MMm1NWqu7EYq#ZFJ8wDje5&tbKf!6%mBS@i zT!`(1q@CvuJeX7ZXz>=(sDp{Cw(S+!-I|hERCjArfzu18zz;Tp4gJHOG|Exdw20RQIZGBAI!ODyR)4e4 zU$0~RlckeK1m3DmIQsU=b+Maw@DY30z`5jluQZ1GvuR;|4_X@1^~SMD;?t?s3D>N$ z$q(%HMAz2SUmyL{U+Ef0sB+3Fc~)%Yx~Zn|iI}8(;lk?&LjP2r!G7yRbJUyWf9v*u zmajW68y=tVAuc>DzW*(Yy4p~=eEx9_Bhjlle}*6XO?ScJ;o7$hoKGld>y60W?7R29 z@$`h7Wvg^Vyyk6__U&^|f56zO{9E#| zWrw8Y!~GnsFQ|XFqrU9p27l@0;&4Ym@B03s(iI5{oH_+=#Ge0N)W!#o+Jtj6g<>B3 zJ~NMq+WgYZ{9g&6UHK4ortC>|TvA$#+roLV*Um4~JBCHcTy_WsUy3T>OHt2foPRTb zhJPu-hG0%ZSoVac9@p(8!BgYkfk$=Y;HlmJk*EGe@a#w4lD}-9gwtI)X3CerTkqSN zq>`BCvG-SrMt|8Td={s**FD%jId=zb!4fy7{T|nAM<+UsxS7nKd@;*4<6uALqoZY? z`7Koo^#;Ctnvyy+BIr&<-CpC|>rRXn8>LZ*vR&@bj3>N%b&4-1wWSLWS)UPHA2BkE zbVKiMhE(N!hFRpJ+=wjm=mU3Fzn$yW{5GwP`(bU=rjLvET8**1Xx`va*{Hv_e}9d5 zH_Lk-i(R&S{L^|yQ@!r?$lttneNkC8OMk}W*UIU|M~n5n^ux1eIvq7?Vv8BJ6DA2p z*ed2~JuTn4F}5vWjYOsXMR`%HTl*;YlwlzUOFpp-ZY+LR_Mx2g;e4~?>xV#*PteHtZrr2Sg^{5ML`a zj`bnU9j9MDL3Q<7tJ|siR_1jsqn8Ojn?_DZ_to&<_{Y9&C!fqn2wB;BF)({ibfn@A z9?7R*auxhA{F~Cj7oI6@ysu}Dcm@;{EI4$gbJ(MO+Ks!}<*z}fF z?i~e{O#3PIXP!=KbjkE-nfJ%Z%|mAnRY)qlk;#b&n)6bcQ6r~*WFjqHeYepV`NDkQk_-*^KRPJW=gQX$kXI()F@;F`0pqeKMh(oW-Ra4%-zg;(}#H` zJGV!DtAwy|anag>|FE0x^U+ogempFPe&4d%4;!f0uO~F(=!n^4<9Z0LOc?=_U_*Q3 zFjNxL78{HybuqxdGspuVljU=|G8)HarCVP&NJKVo>w#Dbv z6)TPNya!zDw@9PkgW~>!HHWqPg=mQOX-=kGI=JoIo79`RUhDH}meywu8KjuM)ODSc z!MTZ^t<8RWUz^WQUGAfK^To{hib^xhjymb$d6T4xoz6}H5wqu%JSf~T zBiu5_u-SN<&c5&?2g^2K#h>~xAlosEpJBz1vEoNqaRZf}O}$F)G;+K&Kii)nEy$Lb z_fss03iaNsyjJ8qWM{)XthnO8!8-W0;vk4zrl83U{JP7G!`N}gXT*4~@5|;?OHaE; zm5e);wg!7JsPBY?co|hnM#r=o+j9<%YCpVu&$7Y--^p!~jq6veAM>j5ncJWVFOK+X zt$9UW^EGBpLgVrKsV|m9IzD=|&~9j3y=mLZiLvKzoaS_1qt>m;YFNN@(<{wZIKMmW zy~~=gEA7+jZ(65xxYKX>Pq1Y4%W1NVOWYw=4cx!-+bQ_X`-j`KgL`K0ug_gE(*6{^ zxHu+zKS zHa)v6ziDDaM9_yXj^E7k9$U~)SX}+x^L2BS{FLdbG&)9iSo7fC$#dpMDWz2*%f^35 z<#=AoiHW;q=$N(atoYj&tMl>ODvBGI>fE>xvgYCB+MyRGPs-VNF@1`~Fzr`?wnL6S zyXV5$A@^p{PNfCbQ!hlYMRnuu1#+IKmsG^8zIbVFdBt?sFN@avQMOazKmgv5u=%i6{R;^)cyt^-`0m`isTJ2^}WxCljen zCU?pVl)_K1PK}b>uH3?)MJ z+`azxsNhAhf%)& + + + PSReadLine-KeyBindings + + Microsoft.PowerShell.KeyHandler + + + Group + + + + + + +$d = [Microsoft.PowerShell.KeyHandler]::GetGroupingDescription($_.Group) +"{0}`n{1}" -f $d,('='*$d.Length) + + + + + + + + + + + + + + + + + + + + + + + + + Key + + + Function + + + Description + + + + + + + + PSReadLine-HistoryItem + + Microsoft.PowerShell.PSConsoleReadLine+HistoryItem + + + + + + + CommandLine + + + $_.StartTime.Ticks -ne 0 + + $_.StartTime.ToLocalTime() + + + $_.ApproximateElapsedTime.Ticks -ne 0 + ApproximateElapsedTime + + + + + + + + PSReadLine-Options + + Microsoft.PowerShell.PSConsoleReadLineOptions + + + + + + + EditMode + + + AddToHistoryHandler + + + HistoryNoDuplicates + + + HistorySavePath + + + HistorySaveStyle + + + HistorySearchCaseSensitive + + + HistorySearchCursorMovesToEnd + + + MaximumHistoryCount + + + ContinuationPrompt + + + ExtraPromptLineCount + + + PromptText + + + BellStyle + + + DingDuration + + + DingTone + + + CommandsToValidateScriptBlockArguments + + + CommandValidationHandler + + + CompletionQueryItems + + + MaximumKillRingCount + + + ShowToolTips + + + ViModeIndicator + + + + $null -ne $_.ViModeChangeHandler + ViModeChangeHandler + + + WordDelimiters + + + AnsiEscapeTimeout + + + PredictionSource + + + PredictionViewStyle + + + TerminateOrphanedConsoleApps + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.CommandColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.CommentColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ContinuationPromptColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.DefaultTokenColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.EmphasisColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ErrorColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.InlinePredictionColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.KeywordColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionSelectedColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ListPredictionTooltipColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.MemberColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.NumberColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.OperatorColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.ParameterColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.SelectionColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.StringColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.TypeColor) + + + + [Microsoft.PowerShell.VTColorUtils]::FormatColor($_.VariableColor) + + + + + + + + + diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psd1 b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psd1 new file mode 100644 index 00000000000..0cd23e6c6ba --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psd1 @@ -0,0 +1,19 @@ +@{ +RootModule = 'PSReadLine.psm1' +NestedModules = @("Microsoft.PowerShell.PSReadLine.dll") +ModuleVersion = '2.4.3' +GUID = '5714753b-2afd-4492-a5fd-01d9e2cff8b5' +Author = 'Microsoft Corporation' +CompanyName = 'Microsoft Corporation' +Copyright = '(c) Microsoft Corporation. All rights reserved.' +Description = 'Great command line editing in the PowerShell console host' +PowerShellVersion = '5.1' +FormatsToProcess = 'PSReadLine.format.ps1xml' +AliasesToExport = @() +FunctionsToExport = 'PSConsoleHostReadLine' +CmdletsToExport = 'Get-PSReadLineKeyHandler','Set-PSReadLineKeyHandler','Remove-PSReadLineKeyHandler', + 'Get-PSReadLineOption','Set-PSReadLineOption' +HelpInfoURI = 'https://aka.ms/powershell75-help' +PrivateData = @{ PSData = @{ Prerelease = 'beta3'; ProjectUri = 'https://github.com/PowerShell/PSReadLine' } } +} + diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1 b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1 new file mode 100644 index 00000000000..d13833f5007 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1 @@ -0,0 +1,12 @@ +function PSConsoleHostReadLine +{ + [System.Diagnostics.DebuggerHidden()] + param() + + ## Get the execution status of the last accepted user input. + ## This needs to be done as the first thing because any script run will flush $?. + $lastRunStatus = $? + Microsoft.PowerShell.Core\Set-StrictMode -Off + [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus) +} + diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/cgmanifest.json b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/cgmanifest.json new file mode 100644 index 00000000000..7e86c66c688 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/cgmanifest.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "PSReadLine", + "repositoryUrl": "https://github.com/PowerShell/PSReadLine", + "commitHash": "b5aac30d26ce777497e85203dc59989296b73f8e" + } + }, + "version": "2.4.4-beta4", + "licenseDetail": [ + "Copyright (c) 2013, Jason Shirk", + "All rights reserved.", + "", + "Redistribution and use in source and binary forms, with or without", + "modification, are permitted provided that the following conditions are met: ", + "", + "1. Redistributions of source code must retain the above copyright notice, this", + " list of conditions and the following disclaimer. ", + "2. Redistributions in binary form must reproduce the above copyright notice,", + " this list of conditions and the following disclaimer in the documentation", + " and/or other materials provided with the distribution. ", + "", + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND", + "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED", + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE", + "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR", + "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES", + "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;", + "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND", + "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS", + "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ] + } + ] +} diff --git a/src/vs/workbench/contrib/terminal/common/scripts/psreadline/net6plus/Microsoft.PowerShell.PSReadLine.Polyfiller.dll b/src/vs/workbench/contrib/terminal/common/scripts/psreadline/net6plus/Microsoft.PowerShell.PSReadLine.Polyfiller.dll new file mode 100644 index 0000000000000000000000000000000000000000..d4a1b20eddb60e5138f0e0d0e33b93f9dce43179 GIT binary patch literal 4608 zcmeHKU2GIp6h70IwiJt0e#Dr-U=h^nu-o1KfFj#=sZ`psc9#OCkgLL+|X&J0~B8k7)Y!r9(4 z=bU@)x#w@@-i|zbnCgkB0prRQqT_fnBqaWOG>hrQt3O*zA1^$;=D0F^dQE1^aMiM7 z7ahH%=5))lJvA#-r(&swr6zZz)smeTT5D^|+Q4*bE77o0Pxm}__Lp$C3-o}xNQn?N zL&5_vy&g!#NMH~xk#wE!Hw)N*3A4bEGt|@ME3C?YbI*Wf5nws&?qDK8bn>bZ76dR! zbQef<9Z@Ef-ON+H>blf!2EVOlo#u%d4}7@^2y-&dz;AF8RH8}Eah)7E>01IsBSs5G z4J6>b<_ObWC94x#<2okK}H zPk>*>Yx_+*3!VYXm8NAxuQGF4iO~w*_eEei22K?ZrlAWD7@kv@=VX3Id+Hi#ox)h7 z!MeMt3bjVsLrJBPT9prhj}c=>;x{DzRpNeNw&L63s25VAPN6p7M!FBUg&qc8LQ#n~ zO57*$ki^eOyhmb<7SVe6u#_Ue_oFK{YNy@69rPA(jJ^VXoW2DfpbNm;r92JXFdKRS zcO)#AQG^`&oK{Pw30XjjB&?#>eb-5q{3YdtvJjNdyT_LD%9_=T z;nj*Wx>FS1AU-N$zwJCfUw_ar#kg=>Y&d^%$ST-QNoQ|#^DngqY^z`tD~{BgGcsV8 z%7!V0W5U#DB<0S{haRUKgr4)}Pp|>qs?JRW@k_a9WDV2sYG$uZk`}m&w}VEEoMXFo z!P8Rqe&M92glTH2^qA1|!-gdwHLC@~G=-z(O*72)h;HdcQ9=#0zKUo2X$U9BM8OoY zOrhk4>RP&zb^YSa6_yY{Nw@Mfl91dd(x|L(IphDpZPId{?S!5r4ZUdDu#s~^QII*! zuL)8hoP9=4xIrr+N$5YYp&zz2CfthY)d>7{l-*8rZu;`4KXfR4FMpr?`j|EI4mGHX z(p;~SvH(O|8?%d>k8aufV%yo4P0i6Sd&fWO9_?yYf7J-4q%dU2I(s0YBUL8fFH}eV= z8XFXPwc8wEcEJP4ZMi-w>7@H>b*fWd!m@fmp+@ZX|9fJahl zjgyWud5{d;DFkd}|s<4`+1>)PBt4`$kiEn)tCa zjm!wF&C{^-Lj<}eRk4y|3jZ42=_!#Cj z3g7we;t!$&v;q2D8Bgk3h|{de)a~$7Lnb+A3=iS^t01oe)#aVJVrAr&D_%qu`2MY8 z9F%_U2;|%itR3c=Ilr>E+h4!0Qt-n@6cyCgtI5{w>#LV!{MYV#eRbz*+kt#`;YJ(B zjg+ZQV16^Whne4qxn4*M;N$r9fbIs?rB)|!1bh^$W6;av$IvT#s}CLR!b%DD_zh6; h?cna8>#{jNxXhBw^!c9xV^AA4y#7*zqn20k`(-?)7@e?94JV z>%^d>hNw|!)fOt0qN#%1begV8F4H=QR)7=txqsP+Hi_DY$|Ff&$hVsrY`>HRAoyIuH2qmo!g^Slw2KWh z>3--P<4zyZ&li<2BPi2E&FDUmBFa{I@1#vGnl93D&^zkpr95@O1O4|)02mY70=q#; zNfJ$`9M{Q%65IOFiJ`Wl)|Eb3pK_FGLy&EyX;jvA8EReWBkHdsrTZNF6@_gJQIwk7 zh>k8N(uDi&;mXim@M2^g+9@A=9B$euygj~Tu-Va)!otExt0A8B@@^O`_~>K`X1V!a6jWYhE;S~Vir2t zpFGJ?m=n1Wf9tfY% zVl*ro+9F5USX#?~d3a#tQSDT?GoV&bfAmzi7w4D9R#2xw$kJ?*E4R7C9@lLG=5zn}Aaq%UlB-PqIZv;=k20 zSoxNK`i!;*)U;1sb}TXpYAzVlDG$#d3MifzrXvCM)$lB+F9uXBS_XAYsKd}Q59-Ik z*jzM24fJe4?TIk;yiY-L1*qRGQlCeJ-xTT??x_3W2d#}zn0gpqhy;{JPf`oT0_vOe z9Id2yK>dhLBCVPN>T~o5x&j(BI;C;md>yDkg8R$~({~ZC2wjTI)@TG-<70^O($9b& zqE7?E^xq3|B9xQ4zU0Go{XdERtLiwjShu?3gKxpVH`++YA{zF04B31+u!*h#B2xsn z3Z?~z1$PPV6}$r&qj^C?@CkY}x|*Jm(K!6O0ks*rl5`vJ8pODhQhf5X6uj67{2;Xh z*HR~NJz$V>;YujelC@&qT zneD#aU=}YbOUBtdY+coHSb4xSl;sH%TPTpFJlBJ>bf-}D{HSi}MOA`WDqZpHlFmL$ zjp$af0=~GMH%AsjzUFI!Gs}jl90`{WSEfDu?B?K98dN#BpEIGxOS`Tr<;?l4;nlOV zx&ynn;oVg2x1D`8`5i`~psX5Qoxcrh?p6-_b?M-+HDf#e7IgD1r3P$k#wb=C*=kLR zzae2xC{sTmJ-0SqwP4&q4(Gk41FS%|=4%5J70WY9O8B0UGfcy)8x2xF5K0B+BGbr! zf1#{$N`|GIG-~7>+qGxB)VRH0Ihk2ynyK*&pDj*%aLxG{11HEy6-+aTEDc$e5@qdu z%91Ds$w;oBly4jV>fnT$365ozbK!K!RC2C=kb^^u=x~DY!K~w?onnPEjqiz}1AJCl zt|~C6YS~Zj;`tThg9AgJe{kM~M<|u|Y=?#l1~-Av2MxVw*|;9^Zk1IHgP#R(vU28( zymEtNMp$rlfebWcXB~JS+!Wt*F6pi(u6BK2FIw-`?S%Wdp{<`Z4Z`o@UNora=oFpxZ zPD=}u3A`AF67gT(wesxAYftTpcRsac_oJJ3_TuLQFE0F(5sJmbv6gr!mOzt^#a6cr zX^joB)vd!?>nbQqM3U4Jk0nD|q9xA#bVHIv2;XSFR7*0VwWJfmT-`c~c~KTmtfcVj z)?J#gR*h_@lVNE-%VMsT$ z2WFrZW8)fLPPkmjZ_e4!rF$H`ywkQ8(;10j?{_uG`9Gc8HM%^tb10h$?%fT+Ilq3c zE7buFiB*f2U-0w8*KG?=Nml11A-Ykcb?^Khz(=P>4Yo(hNE;pJjopKK z2H)Rjg70@0;YhDU{TS-)8&O-(sVjA&!kx$rX3o6&pQ{ePa&q*Z)8BvLX6}b5AjXr^Q$#dTenLUWAL90#<^ceUqSI}wE4DJywSIev{ zMs;vqjO%r`3`_hV@(a94YOu+JlmV&Y1vGs}WIC{K|LLI|`+(-~9iY(wRx43i)|khR zbWlitlu>BRL$8gsZCK^ui((wT{TRp30?nc|@h~?7&nV0-(1_Tf0$CGZEiU3)lg=6SKP^SHQz%i{w@s8SO$#3Hta1Byq|)`)gTNvxD>O{qmkYko5m& Se5QKgzGsE+EBJr%!2bX(Y?;vj literal 0 HcmV?d00001 diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 index f76ba1e5656..6bc5b5c137d 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 @@ -20,6 +20,7 @@ $Global:__VSCodeState = @{ EnvVarsToReport = @() Nonce = $null IsStable = $null + IsA11yMode = $null IsWindows10 = $false } @@ -32,6 +33,9 @@ $env:VSCODE_NONCE = $null $Global:__VSCodeState.IsStable = $env:VSCODE_STABLE $env:VSCODE_STABLE = $null +$Global:__VSCodeState.IsA11yMode = $env:VSCODE_A11Y_MODE +$env:VSCODE_A11Y_MODE = $null + $__vscode_shell_env_reporting = $env:VSCODE_SHELL_ENV_REPORTING $env:VSCODE_SHELL_ENV_REPORTING = $null if ($__vscode_shell_env_reporting) { @@ -169,6 +173,17 @@ elseif ((Test-Path variable:global:GitPromptSettings) -and $Global:GitPromptSett [Console]::Write("$([char]0x1b)]633;P;PromptType=posh-git`a") } +if ($Global:__VSCodeState.IsA11yMode -eq "1") { + if (-not (Get-Module -Name PSReadLine)) { + $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path + $specialPsrlPath = Join-Path $scriptRoot 'psreadline' + Import-Module $specialPsrlPath + if (Get-Module -Name PSReadLine) { + Set-PSReadLineOption -EnableScreenReaderMode + } + } +} + # Only send the command executed sequence when PSReadLine is loaded, if not shell integration should # still work thanks to the command line sequence $Global:__VSCodeState.HasPSReadLine = $false From 5366847dcb9304bd5e695719192ea9cfc8764a75 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 8 Sep 2025 17:34:39 -0400 Subject: [PATCH 0097/4355] Fix internal org check (#265730) --- src/vs/workbench/contrib/chat/common/chatEntitlementService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts index 1c5284f9388..d42dfb19033 100644 --- a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts @@ -1167,7 +1167,7 @@ export class ChatEntitlementContext extends Disposable { this.enterpriseContextKey.set(state.entitlement === ChatEntitlement.Enterprise); this.organisationsContextKey.set(state.organisations); - this.isInternalContextKey.set(Boolean(state.organisations?.some(org => org === 'github' || org === 'microsoft'))); + this.isInternalContextKey.set(Boolean(state.organisations?.some(org => org === 'github' || org === 'microsoft' || org === 'ms-copilot'))); this.skuContextKey.set(state.sku); this.hiddenContext.set(!!state.hidden); From 3892c2f4a91fb19b413e346f36e032730277f72c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:53:51 -0700 Subject: [PATCH 0098/4355] Handle when equals sign shows up in a parameter (#265736) ref https://github.com/microsoft/vscode/issues/265733 --- src/vs/base/common/oauth.ts | 20 ++++++++++++++------ src/vs/base/test/common/oauth.test.ts | 9 +++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index fe8cfb6a482..c4d5a755edd 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -872,9 +872,13 @@ export function parseWWWAuthenticateHeader(wwwAuthenticateHeaderValue: string): currentChallenge = { scheme: beforeSpace.trim(), params: {} }; // Parse the parameter part - const [key, value] = afterSpace.split('=').map(s => s.trim().replace(/"/g, '')); - if (key && value !== undefined) { - currentChallenge.params[key] = value; + const equalIndex = afterSpace.indexOf('='); + if (equalIndex > 0) { + const key = afterSpace.substring(0, equalIndex).trim(); + const value = afterSpace.substring(equalIndex + 1).trim().replace(/^"|"$/g, ''); + if (key && value !== undefined) { + currentChallenge.params[key] = value; + } } continue; } @@ -882,9 +886,13 @@ export function parseWWWAuthenticateHeader(wwwAuthenticateHeaderValue: string): // This is a parameter for the current challenge if (currentChallenge) { - const [key, value] = token.split('=').map(s => s.trim().replace(/"/g, '')); - if (key && value !== undefined) { - currentChallenge.params[key] = value; + const equalIndex = token.indexOf('='); + if (equalIndex > 0) { + const key = token.substring(0, equalIndex).trim(); + const value = token.substring(equalIndex + 1).trim().replace(/^"|"$/g, ''); + if (key && value !== undefined) { + currentChallenge.params[key] = value; + } } } } diff --git a/src/vs/base/test/common/oauth.test.ts b/src/vs/base/test/common/oauth.test.ts index 318343371f6..78de7c02649 100644 --- a/src/vs/base/test/common/oauth.test.ts +++ b/src/vs/base/test/common/oauth.test.ts @@ -344,6 +344,15 @@ suite('OAuth', () => { }); }); + test('parseWWWAuthenticateHeader should correctly parse parameters with equal signs', () => { + const result = parseWWWAuthenticateHeader('Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource?v=1"'); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].scheme, 'Bearer'); + assert.deepStrictEqual(result[0].params, { + resource_metadata: 'https://example.com/.well-known/oauth-protected-resource?v=1' + }); + }); + test('parseWWWAuthenticateHeader should correctly parse multiple', () => { const result = parseWWWAuthenticateHeader('Bearer realm="api", error="invalid_token", error_description="The access token expired", Basic realm="hi"'); From 6920dd084c7d5d2ca05a784ec267e8683badf85e Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:08:51 -0700 Subject: [PATCH 0099/4355] Have multiplex just keep tools in sets. (#265747) --- test/mcp/src/multiplex.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/test/mcp/src/multiplex.ts b/test/mcp/src/multiplex.ts index 1a438cf7649..ae29e1e04e3 100644 --- a/test/mcp/src/multiplex.ts +++ b/test/mcp/src/multiplex.ts @@ -12,11 +12,6 @@ import { createInMemoryTransportPair } from './inMemoryTransport'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Application } from '../../automation'; -interface SubServer { - client: Client; - prefix: string; -} - export async function getServer(): Promise { const appService = new ApplicationService(); const automationServer = await getAutomationServer(appService); @@ -26,7 +21,7 @@ export async function getServer(): Promise { await automationClient.connect(automationClientTransport); const multiplexServer = new MultiplexServer( - [{ client: automationClient, prefix: 'vscode_automation_' }], + [automationClient], { name: 'VS Code Automation + Playwright Server', version: '1.0.0', @@ -43,8 +38,7 @@ export async function getServer(): Promise { await playwrightClient.connect(playwrightClientTransport); await playwrightClient.notification({ method: 'notifications/initialized' }); // Prefixes could change in the future... be careful. - const playwrightSubServer = { client: playwrightClient, prefix: 'browser_' }; - multiplexServer.addSubServer(playwrightSubServer); + multiplexServer.addSubServer(playwrightClient); multiplexServer.sendToolListChanged(); closables.push( playwrightClient, @@ -53,7 +47,7 @@ export async function getServer(): Promise { playwrightClientTransport, { async close() { - multiplexServer.removeSubServer(playwrightSubServer); + multiplexServer.removeSubServer(playwrightClient); multiplexServer.sendToolListChanged(); } } @@ -84,9 +78,11 @@ export class MultiplexServer { /** * The underlying Server instance, useful for advanced operations like sending notifications. */ - public readonly server: Server; + readonly server: Server; + + private readonly _subServerToToolSet = new Map>(); - constructor(private readonly subServers: SubServer[], serverInfo: Implementation, options?: ServerOptions) { + constructor(private readonly subServers: Client[], serverInfo: Implementation, options?: ServerOptions) { this.server = new Server(serverInfo, options); this.setToolRequestHandlers(); } @@ -136,7 +132,8 @@ export class MultiplexServer { async (): Promise => { const tools: Tool[] = []; for (const subServer of this.subServers) { - const result = await subServer.client.listTools(); + const result = await subServer.listTools(); + this._subServerToToolSet.set(subServer, new Set(result.tools.map(t => t.name))); tools.push(...result.tools); } return { tools }; @@ -148,8 +145,9 @@ export class MultiplexServer { async (request, extra): Promise => { const toolName = request.params.name; for (const subServer of this.subServers) { - if (toolName.startsWith(subServer.prefix)) { - return await subServer.client.request( + const toolSet = this._subServerToToolSet.get(subServer); + if (toolSet?.has(toolName)) { + return await subServer.request( { method: 'tools/call', params: request.params @@ -182,12 +180,12 @@ export class MultiplexServer { } } - addSubServer(subServer: SubServer) { + addSubServer(subServer: Client) { this.subServers.push(subServer); this.sendToolListChanged(); } - removeSubServer(subServer: SubServer) { + removeSubServer(subServer: Client) { const index = this.subServers.indexOf(subServer); if (index >= 0) { const removed = this.subServers.splice(index); From 70ad66768ae26e316e535b1a78b6786b62a76209 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 8 Sep 2025 15:09:21 -0700 Subject: [PATCH 0100/4355] debug: fix watch 'copy value' not working (#265746) Also turns on "Copy as Expression" for the menu. Fixes #264465 --- .../contrib/debug/browser/debug.contribution.ts | 1 + .../contrib/debug/browser/variablesView.ts | 17 +++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 040fa52ed9c..5d15a927c79 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -201,6 +201,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, ADD_WATCH_ID, ADD_WATCH_LABE registerDebugViewMenuItem(MenuId.DebugWatchContext, EDIT_EXPRESSION_COMMAND_ID, nls.localize('editWatchExpression', "Edit Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, SET_EXPRESSION_COMMAND_ID, nls.localize('setValue', "Set Value"), 30, ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_SET_EXPRESSION_SUPPORTED), ContextKeyExpr.and(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable'), CONTEXT_SET_VARIABLE_SUPPORTED)), CONTEXT_VARIABLE_IS_READONLY.toNegated(), '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 40, ContextKeyExpr.or(CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), CONTEXT_WATCH_ITEM_TYPE.isEqualTo('variable')), CONTEXT_IN_DEBUG_MODE, '3_modification'); +registerDebugViewMenuItem(MenuId.DebugWatchContext, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, 50, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_IN_DEBUG_MODE, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize('viewMemory', "View Binary Data"), 10, CONTEXT_CAN_VIEW_MEMORY, undefined, 'inline', icons.debugInspectMemory); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'inline', icons.watchExpressionRemove); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 141d22bce8e..db0a162f139 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -658,13 +658,6 @@ CommandsRegistry.registerCommand({ }, id: COPY_VALUE_ID, handler: async (accessor: ServicesAccessor, arg: Variable | Expression | IVariablesContext | undefined, ctx?: (Variable | Expression)[]) => { - if (!arg) { - const viewService = accessor.get(IViewsService); - const view = viewService.getActiveViewWithId(WATCH_VIEW_ID) || viewService.getActiveViewWithId(VARIABLES_VIEW_ID); - if (view) { - - } - } const debugService = accessor.get(IDebugService); const clipboardService = accessor.get(IClipboardService); let elementContext = ''; @@ -686,7 +679,7 @@ CommandsRegistry.registerCommand({ elements = view.treeSelection.filter(e => e instanceof Expression || e instanceof Variable); } else if (arg instanceof Variable || arg instanceof Expression) { elementContext = 'watch'; - elements = ctx ? ctx : []; + elements = [arg]; } else { elementContext = 'variables'; elements = variableInternalContext ? [variableInternalContext] : []; @@ -791,9 +784,13 @@ CommandsRegistry.registerCommand({ description: COPY_EVALUATE_PATH_LABEL, }, id: COPY_EVALUATE_PATH_ID, - handler: async (accessor: ServicesAccessor, context: IVariablesContext) => { + handler: async (accessor: ServicesAccessor, context: IVariablesContext | Variable) => { const clipboardService = accessor.get(IClipboardService); - await clipboardService.writeText(context.variable.evaluateName!); + if (context instanceof Variable) { + await clipboardService.writeText(context.evaluateName!); + } else { + await clipboardService.writeText(context.variable.evaluateName!); + } } }); From fbd6e0d3a5075682405cf3e68ef15b80ea50ae14 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:55:44 -0700 Subject: [PATCH 0101/4355] Improved comments for finalization (#265754) ref https://github.com/microsoft/vscode/issues/260156 --- ...ode.proposed.authenticationChallenges.d.ts | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts b/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts index ef645c22900..2550d66c24e 100644 --- a/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts +++ b/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts @@ -16,6 +16,10 @@ declare module 'vscode' { * This is used when an API returns a 401 with a WWW-Authenticate header indicating * that additional authentication is required. The details of which will be passed down * to the authentication provider to create a session. + * + * @note The authorization provider must support handling challenges and specifically + * the challenges in this WWW-Authenticate value. + * @note For more information on WWW-Authenticate please see https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/WWW-Authenticate */ export interface AuthenticationWWWAuthenticateRequest { /** @@ -39,47 +43,57 @@ declare module 'vscode' { export namespace authentication { /** - * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not - * registered, or if the user does not consent to sharing authentication information with - * the extension. If there are multiple sessions with the same scopes, the user will be shown a - * quickpick to select which account they would like to use. + * Get an authentication session matching the desired scopes or satisfying the WWW-Authenticate request. Rejects if + * a provider with providerId is not registered, or if the user does not consent to sharing authentication information + * with the extension. If there are multiple sessions with the same scopes, the user will be shown a quickpick to + * select which account they would like to use. + * + * Built-in auth providers include: + * * 'github' - For GitHub.com + * * 'microsoft' For both personal & organizational Microsoft accounts + * * (less common) 'github-enterprise' - for alternative GitHub hostings, GHE.com, GitHub Enterprise Server + * * (less common) 'microsoft-sovereign-cloud' - for alternative Microsoft clouds * - * Currently, there are only two authentication providers that are contributed from built in extensions - * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. * @param providerId The id of the provider to use - * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider + * @param scopeListOrRequest A scope list of permissions requested or a WWW-Authenticate request. These are dependent on the authentication provider. * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session */ export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** */createIfNone: true | AuthenticationGetSessionPresentationOptions }): Thenable; /** - * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not - * registered, or if the user does not consent to sharing authentication information with - * the extension. If there are multiple sessions with the same scopes, the user will be shown a - * quickpick to select which account they would like to use. + * Get an authentication session matching the desired scopes or request. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with the extension. If there + * are multiple sessions with the same scopes, the user will be shown a quickpick to select which account they would like to use. + * + * Built-in auth providers include: + * * 'github' - For GitHub.com + * * 'microsoft' For both personal & organizational Microsoft accounts + * * (less common) 'github-enterprise' - for alternative GitHub hostings, GHE.com, GitHub Enterprise Server + * * (less common) 'microsoft-sovereign-cloud' - for alternative Microsoft clouds * - * Currently, there are only two authentication providers that are contributed from built in extensions - * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. * @param providerId The id of the provider to use - * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider + * @param scopeListOrRequest A scope list of permissions requested or a WWW-Authenticate request. These are dependent on the authentication provider. * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session */ export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** literal-type defines return type */forceNewSession: true | AuthenticationGetSessionPresentationOptions | AuthenticationForceNewSessionOptions }): Thenable; /** - * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not - * registered, or if the user does not consent to sharing authentication information with - * the extension. If there are multiple sessions with the same scopes, the user will be shown a - * quickpick to select which account they would like to use. + * Get an authentication session matching the desired scopes or request. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with the extension. If there + * are multiple sessions with the same scopes, the user will be shown a quickpick to select which account they would like to use. + * + * Built-in auth providers include: + * * 'github' - For GitHub.com + * * 'microsoft' For both personal & organizational Microsoft accounts + * * (less common) 'github-enterprise' - for alternative GitHub hostings, GHE.com, GitHub Enterprise Server + * * (less common) 'microsoft-sovereign-cloud' - for alternative Microsoft clouds * - * Currently, there are only two authentication providers that are contributed from built in extensions - * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. * @param providerId The id of the provider to use - * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider + * @param scopeListOrRequest A scope list of permissions requested or a WWW-Authenticate request. These are dependent on the authentication provider. * @param options The {@link AuthenticationGetSessionOptions} to use - * @returns A thenable that resolves to an authentication session if available, or undefined if there are no sessions + * @returns A thenable that resolves to an authentication session or undefined if a silent flow was used and no session was found */ export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options?: AuthenticationGetSessionOptions): Thenable; } @@ -94,6 +108,8 @@ declare module 'vscode' { * Represents an authentication challenge from a WWW-Authenticate header. * This is used to handle cases where additional authentication steps are required, * such as when mandatory multi-factor authentication (MFA) is enforced. + * + * @note For more information on WWW-Authenticate please see https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/WWW-Authenticate */ export interface AuthenticationChallenge { /** @@ -112,6 +128,8 @@ declare module 'vscode' { * Represents constraints for authentication, including challenges and optional scopes. * This is used when creating or retrieving sessions that must satisfy specific authentication * requirements from WWW-Authenticate headers. + * + * @note For more information on WWW-Authenticate please see https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/WWW-Authenticate */ export interface AuthenticationConstraint { /** From 54782bd2f0c7606aed6132a2d5d8973bc1ef4497 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:10:32 -0700 Subject: [PATCH 0102/4355] fix oswe thinking because oswe model does not give us thinking ids (#265769) fix oswe model case because oswe model does not give us thinking ids for some reason --- .../chatContentParts/chatThinkingContentPart.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 0ce6550240f..099f7a48863 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -157,7 +157,18 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen return false; } - return other?.id !== this.id; + const otherId = other?.id; + const thisId = this.id; + + // one off case where we have no ids, we compare text instead. + if (otherId === undefined && thisId === undefined) { + const rawValue = other.value; + const otherValueStr = typeof rawValue === 'string' ? rawValue : Array.isArray(rawValue) ? rawValue.join('') : ''; + const otherValueNormalized = otherValueStr.trim(); + return this.parseContent(otherValueNormalized) === this.currentThinkingValue; + } + + return otherId !== thisId; } override dispose(): void { From 7fa155d075219c208394ac92dc15553ed564fa6e Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 9 Sep 2025 01:10:50 -0700 Subject: [PATCH 0103/4355] add other orgs (#265749) --- src/vs/workbench/contrib/chat/common/chatEntitlementService.ts | 2 +- .../workbench/services/assignment/common/assignmentFilters.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts index d42dfb19033..c5543cf8436 100644 --- a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts @@ -1167,7 +1167,7 @@ export class ChatEntitlementContext extends Disposable { this.enterpriseContextKey.set(state.entitlement === ChatEntitlement.Enterprise); this.organisationsContextKey.set(state.organisations); - this.isInternalContextKey.set(Boolean(state.organisations?.some(org => org === 'github' || org === 'microsoft' || org === 'ms-copilot'))); + this.isInternalContextKey.set(Boolean(state.organisations?.some(org => org === 'github' || org === 'microsoft' || org === 'ms-copilot' || org === 'MicrosoftCopilot'))); this.skuContextKey.set(state.sku); this.hiddenContext.set(!!state.hidden); diff --git a/src/vs/workbench/services/assignment/common/assignmentFilters.ts b/src/vs/workbench/services/assignment/common/assignmentFilters.ts index 3dd9fdc387a..ed489ddd660 100644 --- a/src/vs/workbench/services/assignment/common/assignmentFilters.ts +++ b/src/vs/workbench/services/assignment/common/assignmentFilters.ts @@ -128,7 +128,7 @@ export class CopilotAssignmentFilterProvider extends Disposable implements IExpe private updateCopilotEntitlementInfo() { const newSku = this._chatEntitlementService.sku; const newIsGitHubInternal = this._chatEntitlementService.organisations?.includes('github'); - const newIsMicrosoftInternal = this._chatEntitlementService.organisations?.includes('microsoft'); + const newIsMicrosoftInternal = this._chatEntitlementService.organisations?.includes('microsoft') || this._chatEntitlementService.organisations?.includes('ms-copilot') || this._chatEntitlementService.organisations?.includes('MicrosoftCopilot'); const newInternalOrg = newIsGitHubInternal ? 'github' : newIsMicrosoftInternal ? 'microsoft' : undefined; if (this.copilotSku === newSku && this.copilotInternalOrg === newInternalOrg) { From 52706c72db740e1980abdce16321e42c345b0a78 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:39:07 +0200 Subject: [PATCH 0104/4355] Add code owner for release notes editor (#265793) * Add code owner for release notes editor * Also add Joao --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c669df38155..dd7ea862b2c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,6 +31,7 @@ src/vs/platform/backup/** @bpasero src/vs/platform/files/** @bpasero @deepak1556 src/vs/base/node/pfs.ts @bpasero @deepak1556 src/vs/code/** @bpasero @deepak1556 +src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno src/vs/workbench/services/textfile/** @bpasero src/vs/workbench/services/workingCopy/** @bpasero From 0488f64ff7c3ba4f384a5cd4a478ec606653be30 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Sep 2025 11:18:16 +0200 Subject: [PATCH 0105/4355] Layer breaker in `src/vs/workbench/services/assignment/common/assignmentFilters.ts` (fix #264280) (#265352) --- .../chat/browser/actions/chatActions.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 3 +- .../chatContentParts/chatQuotaExceededPart.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatSessions.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 42 ++++- .../contrib/chat/browser/chatStatus.ts | 2 +- .../modelPicker/modelPickerActionItem.ts | 2 +- .../contrib/chat/common/chatContextKeys.ts | 31 +--- .../test/browser/inlineChatController.test.ts | 2 +- .../chat/browser/terminalChatController.ts | 2 +- .../assignment/common/assignmentFilters.ts | 3 +- .../chat/common/chatEntitlementService.ts | 175 +++++++++--------- src/vs/workbench/workbench.common.main.ts | 1 + 14 files changed, 139 insertions(+), 132 deletions(-) rename src/vs/workbench/{contrib => services}/chat/common/chatEntitlementService.ts (86%) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 9f6e4d853aa..a31a595003f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -56,7 +56,7 @@ import { EXTENSIONS_CATEGORY, IExtensionsWorkbenchService } from '../../../exten import { IChatAgentResult, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 16acce4d9eb..3af4c960e73 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -33,7 +33,7 @@ import { ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAge import { CodeMapperService, ICodeMapperService } from '../common/chatCodeMapperService.js'; import '../common/chatColors.js'; import { IChatEditingService } from '../common/chatEditingService.js'; -import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; import { ChatModeService, IChatModeService } from '../common/chatModes.js'; import { ChatResponseResourceFileSystemProvider } from '../common/chatResponseResourceFileSystemProvider.js'; @@ -934,7 +934,6 @@ registerSingleton(ICodeMapperService, CodeMapperService, InstantiationType.Delay registerSingleton(IChatEditingService, ChatEditingService, InstantiationType.Delayed); registerSingleton(IChatMarkdownAnchorService, ChatMarkdownAnchorService, InstantiationType.Delayed); registerSingleton(ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService, InstantiationType.Delayed); -registerSingleton(IChatEntitlementService, ChatEntitlementService, InstantiationType.Delayed); registerSingleton(IPromptsService, PromptsService, InstantiationType.Delayed); registerSingleton(IChatContextPickService, ChatContextPickService, InstantiationType.Delayed); registerSingleton(IChatModeService, ChatModeService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts index 440d2dd8ed3..c2d815cf30c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts @@ -18,7 +18,7 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { asCssVariable, textLinkForeground } from '../../../../../platform/theme/common/colorRegistry.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IChatErrorDetailsPart, IChatRendererContent, IChatResponseViewModel } from '../../common/chatViewModel.js'; import { IChatWidgetService } from '../chat.js'; import { IChatContentPart } from './chatContentParts.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 6c48ad6ac06..ad0fb8221cb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -77,7 +77,7 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd import { IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts index e593c89be17..13ee51b4c9e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.ts @@ -69,7 +69,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatEntitlementService } from '../common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 969c2f0a208..417c05b25eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -59,10 +59,10 @@ import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle. import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../chat/common/languageModelToolsService.js'; -import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../common/chatEntitlementService.js'; +import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../services/chat/common/chatEntitlementService.js'; import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../common/chatModel.js'; import { ChatMode } from '../common/chatModes.js'; import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; @@ -826,7 +826,9 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr @ITelemetryService private readonly telemetryService: ITelemetryService, @IChatEntitlementService chatEntitlementService: ChatEntitlementService, @ILogService private readonly logService: ILogService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(); @@ -841,6 +843,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr this.registerSetupAgents(context, controller); this.registerActions(context, requests, controller); this.registerUrlLinkHandler(); + this.checkExtensionInstallation(context); } private registerSetupAgents(context: ChatEntitlementContext, controller: Lazy): void { @@ -1146,6 +1149,39 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr } })); } + + private async checkExtensionInstallation(context: ChatEntitlementContext): Promise { + + // Await extensions to be ready to be queried + await this.extensionsWorkbenchService.queryLocal(); + + // Listen to extensions change and process extensions once + this._register(Event.runAndSubscribe(this.extensionsWorkbenchService.onChange, e => { + if (e && !ExtensionIdentifier.equals(e.identifier.id, defaultChat.chatExtensionId)) { + return; // unrelated event + } + + const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.chatExtensionId)); + const installed = !!defaultChatExtension?.local; + + let disabled: boolean; + let untrusted = false; + if (installed) { + disabled = !this.extensionEnablementService.isEnabled(defaultChatExtension.local); + if (disabled) { + const state = this.extensionEnablementService.getEnablementState(defaultChatExtension.local); + if (state === EnablementState.DisabledByTrustRequirement) { + disabled = false; // not disabled by user choice but + untrusted = true; // by missing workspace trust + } + } + } else { + disabled = false; + } + + context.update({ installed, disabled, untrusted }); + })); + } } export class ChatTeardownContribution extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 8c06a924dcd..ffd7c78f2e4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -11,7 +11,7 @@ import { localize } from '../../../../nls.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../services/statusbar/browser/statusbar.js'; import { $, addDisposableListener, append, clearNode, disposableWindowInterval, EventHelper, EventType, getWindow } from '../../../../base/browser/dom.js'; -import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, IQuotaSnapshot, isProUser } from '../common/chatEntitlementService.js'; +import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, IQuotaSnapshot, isProUser } from '../../../services/chat/common/chatEntitlementService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { defaultButtonStyles, defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts index 924075f0af5..51901ad6e45 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts @@ -15,7 +15,7 @@ import { IActionWidgetService } from '../../../../../platform/actionWidget/brows import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider, IActionWidgetDropdownOptions } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../common/chatEntitlementService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../common/modelPicker/modelPickerWidget.js'; import { ManageModelsAction } from '../actions/manageModelsActions.js'; diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 4260956c4bf..a790d51d1bb 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -8,6 +8,7 @@ import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/c import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js'; import { RemoteNameContext } from '../../../common/contextkeys.js'; import { ViewContainerLocation } from '../../../common/views.js'; +import { ChatEntitlementContextKeys } from '../../../services/chat/common/chatEntitlementService.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; export namespace ChatContextKeys { @@ -63,31 +64,11 @@ export namespace ChatContextKeys { /** Used by the extension to skip the quit confirmation when #new wants to open a new folder */ export const skipChatRequestInProgressMessage = new RawContextKey('chatSkipRequestInProgressMessage', false, { type: 'boolean', description: localize('chatSkipRequestInProgressMessage', "True when the chat request in progress message should be skipped.") }); - export const Setup = { - hidden: new RawContextKey('chatSetupHidden', false, true), // True when chat setup is explicitly hidden. - installed: new RawContextKey('chatSetupInstalled', false, true), // True when the chat extension is installed and enabled. - disabled: new RawContextKey('chatSetupDisabled', false, true), // True when the chat extension is disabled due to any other reason than workspace trust. - untrusted: new RawContextKey('chatSetupUntrusted', false, true), // True when the chat extension is disabled due to workspace trust. - later: new RawContextKey('chatSetupLater', false, true) // True when the user wants to finish setup later. - }; - - export const Entitlement = { - signedOut: new RawContextKey('chatEntitlementSignedOut', false, true), // True when user is signed out. - canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user. - - planFree: new RawContextKey('chatPlanFree', false, true), // True when user is a chat free user. - planPro: new RawContextKey('chatPlanPro', false, true), // True when user is a chat pro user. - planProPlus: new RawContextKey('chatPlanProPlus', false, true), // True when user is a chat pro plus user. - planBusiness: new RawContextKey('chatPlanBusiness', false, true), // True when user is a chat business user. - planEnterprise: new RawContextKey('chatPlanEnterprise', false, true), // True when user is a chat enterprise user. - - organisations: new RawContextKey('chatEntitlementOrganisations', undefined, true), // The organizations the user belongs to. - internal: new RawContextKey('chatEntitlementInternal', false, true), // True when user belongs to internal organisation. - sku: new RawContextKey('chatEntitlementSku', undefined, true), // The SKU of the user. - }; - - export const chatQuotaExceeded = new RawContextKey('chatQuotaExceeded', false, true); - export const completionsQuotaExceeded = new RawContextKey('completionsQuotaExceeded', false, true); + // Re-exported from chat entitlement service + export const Setup = ChatEntitlementContextKeys.Setup; + export const Entitlement = ChatEntitlementContextKeys.Entitlement; + export const chatQuotaExceeded = ChatEntitlementContextKeys.chatQuotaExceeded; + export const completionsQuotaExceeded = ChatEntitlementContextKeys.completionsQuotaExceeded; export const Editing = { hasToolConfirmation: new RawContextKey('chatHasToolConfirmation', false, { type: 'boolean', description: localize('chatEditingHasToolConfirmation', "True when a tool confirmation is present.") }), diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 73f1202ff19..6671c88eb91 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -56,7 +56,7 @@ import { ChatVariablesService } from '../../../chat/browser/chatVariables.js'; import { ChatWidgetService } from '../../../chat/browser/chatWidget.js'; import { ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from '../../../chat/common/chatAgents.js'; import { IChatEditingService, IChatEditingSession } from '../../../chat/common/chatEditingService.js'; -import { IChatEntitlementService } from '../../../chat/common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../../../chat/common/chatLayoutService.js'; import { IChatModeService } from '../../../chat/common/chatModes.js'; import { IChatProgress, IChatService } from '../../../chat/common/chatService.js'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 711a1935d4f..0b6924cb784 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -15,7 +15,7 @@ import { TerminalChatWidget } from './terminalChatWidget.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import type { ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import type { IChatModel } from '../../../chat/common/chatModel.js'; -import { IChatEntitlementService } from '../../../chat/common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.chat'; diff --git a/src/vs/workbench/services/assignment/common/assignmentFilters.ts b/src/vs/workbench/services/assignment/common/assignmentFilters.ts index ed489ddd660..c45a727886d 100644 --- a/src/vs/workbench/services/assignment/common/assignmentFilters.ts +++ b/src/vs/workbench/services/assignment/common/assignmentFilters.ts @@ -10,8 +10,7 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { ILogService } from '../../../../platform/log/common/log.js'; import { Emitter } from '../../../../base/common/event.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -// eslint-disable-next-line local/code-import-patterns -import { IChatEntitlementService } from '../../../contrib/chat/common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../chat/common/chatEntitlementService.js'; export enum ExtensionsFilter { diff --git a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts similarity index 86% rename from src/vs/workbench/contrib/chat/common/chatEntitlementService.ts rename to src/vs/workbench/services/chat/common/chatEntitlementService.ts index c5543cf8436..d01ea08ba42 100644 --- a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -12,27 +12,53 @@ import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle import { IRequestContext } from '../../../../base/parts/request/common/request.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; -import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { asText, IRequestService } from '../../../../platform/request/common/request.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js'; -import { AuthenticationSession, AuthenticationSessionAccount, IAuthenticationExtensionsService, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; -import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.js'; -import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; -import { ChatContextKeys } from './chatContextKeys.js'; +import { AuthenticationSession, AuthenticationSessionAccount, IAuthenticationExtensionsService, IAuthenticationService } from '../../authentication/common/authentication.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import Severity from '../../../../base/common/severity.js'; -import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; import { isWeb } from '../../../../base/common/platform.js'; -import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; +import { ILifecycleService } from '../../lifecycle/common/lifecycle.js'; import { Mutable } from '../../../../base/common/types.js'; import { distinct } from '../../../../base/common/arrays.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; + +export namespace ChatEntitlementContextKeys { + + export const Setup = { + hidden: new RawContextKey('chatSetupHidden', false, true), // True when chat setup is explicitly hidden. + installed: new RawContextKey('chatSetupInstalled', false, true), // True when the chat extension is installed and enabled. + disabled: new RawContextKey('chatSetupDisabled', false, true), // True when the chat extension is disabled due to any other reason than workspace trust. + untrusted: new RawContextKey('chatSetupUntrusted', false, true), // True when the chat extension is disabled due to workspace trust. + later: new RawContextKey('chatSetupLater', false, true) // True when the user wants to finish setup later. + }; + + export const Entitlement = { + signedOut: new RawContextKey('chatEntitlementSignedOut', false, true), // True when user is signed out. + canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user. + + planFree: new RawContextKey('chatPlanFree', false, true), // True when user is a chat free user. + planPro: new RawContextKey('chatPlanPro', false, true), // True when user is a chat pro user. + planProPlus: new RawContextKey('chatPlanProPlus', false, true), // True when user is a chat pro plus user. + planBusiness: new RawContextKey('chatPlanBusiness', false, true), // True when user is a chat business user. + planEnterprise: new RawContextKey('chatPlanEnterprise', false, true), // True when user is a chat enterprise user. + + organisations: new RawContextKey('chatEntitlementOrganisations', undefined, true), // The organizations the user belongs to. + internal: new RawContextKey('chatEntitlementInternal', false, true), // True when user belongs to internal organisation. + sku: new RawContextKey('chatEntitlementSku', undefined, true), // The SKU of the user. + }; + + export const chatQuotaExceeded = new RawContextKey('chatQuotaExceeded', false, true); + export const completionsQuotaExceeded = new RawContextKey('completionsQuotaExceeded', false, true); +} export const IChatEntitlementService = createDecorator('chatEntitlementService'); @@ -170,22 +196,22 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme ) { super(); - this.chatQuotaExceededContextKey = ChatContextKeys.chatQuotaExceeded.bindTo(this.contextKeyService); - this.completionsQuotaExceededContextKey = ChatContextKeys.completionsQuotaExceeded.bindTo(this.contextKeyService); + this.chatQuotaExceededContextKey = ChatEntitlementContextKeys.chatQuotaExceeded.bindTo(this.contextKeyService); + this.completionsQuotaExceededContextKey = ChatEntitlementContextKeys.completionsQuotaExceeded.bindTo(this.contextKeyService); this.onDidChangeEntitlement = Event.map( Event.filter( this.contextKeyService.onDidChangeContext, e => e.affectsSome(new Set([ - ChatContextKeys.Entitlement.planPro.key, - ChatContextKeys.Entitlement.planBusiness.key, - ChatContextKeys.Entitlement.planEnterprise.key, - ChatContextKeys.Entitlement.planProPlus.key, - ChatContextKeys.Entitlement.planFree.key, - ChatContextKeys.Entitlement.canSignUp.key, - ChatContextKeys.Entitlement.signedOut.key, - ChatContextKeys.Entitlement.organisations.key, - ChatContextKeys.Entitlement.internal.key, - ChatContextKeys.Entitlement.sku.key + ChatEntitlementContextKeys.Entitlement.planPro.key, + ChatEntitlementContextKeys.Entitlement.planBusiness.key, + ChatEntitlementContextKeys.Entitlement.planEnterprise.key, + ChatEntitlementContextKeys.Entitlement.planProPlus.key, + ChatEntitlementContextKeys.Entitlement.planFree.key, + ChatEntitlementContextKeys.Entitlement.canSignUp.key, + ChatEntitlementContextKeys.Entitlement.signedOut.key, + ChatEntitlementContextKeys.Entitlement.organisations.key, + ChatEntitlementContextKeys.Entitlement.internal.key, + ChatEntitlementContextKeys.Entitlement.sku.key ])), this._store ), () => { }, this._store ); @@ -193,11 +219,11 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme this.onDidChangeSentiment = Event.map( Event.filter( this.contextKeyService.onDidChangeContext, e => e.affectsSome(new Set([ - ChatContextKeys.Setup.hidden.key, - ChatContextKeys.Setup.disabled.key, - ChatContextKeys.Setup.untrusted.key, - ChatContextKeys.Setup.installed.key, - ChatContextKeys.Setup.later.key + ChatEntitlementContextKeys.Setup.hidden.key, + ChatEntitlementContextKeys.Setup.disabled.key, + ChatEntitlementContextKeys.Setup.untrusted.key, + ChatEntitlementContextKeys.Setup.installed.key, + ChatEntitlementContextKeys.Setup.later.key ])), this._store ), () => { }, this._store ); @@ -211,7 +237,7 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme !configurationService.getValue('chat.experimental.serverlessWebEnabled') ) ) { - ChatContextKeys.Setup.hidden.bindTo(this.contextKeyService).set(true); // hide copilot UI + ChatEntitlementContextKeys.Setup.hidden.bindTo(this.contextKeyService).set(true); // hide copilot UI return; } @@ -229,19 +255,19 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme readonly onDidChangeEntitlement: Event; get entitlement(): ChatEntitlement { - if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.planPro.key) === true) { + if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.planPro.key) === true) { return ChatEntitlement.Pro; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.planBusiness.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.planBusiness.key) === true) { return ChatEntitlement.Business; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.planEnterprise.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.planEnterprise.key) === true) { return ChatEntitlement.Enterprise; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.planProPlus.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.planProPlus.key) === true) { return ChatEntitlement.ProPlus; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.planFree.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.planFree.key) === true) { return ChatEntitlement.Free; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.canSignUp.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.canSignUp.key) === true) { return ChatEntitlement.Available; - } else if (this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.signedOut.key) === true) { + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.signedOut.key) === true) { return ChatEntitlement.Unknown; } @@ -249,15 +275,15 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme } get isInternal(): boolean { - return this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.internal.key) === true; + return this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.internal.key) === true; } get organisations(): string[] | undefined { - return this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.organisations.key); + return this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.organisations.key); } get sku(): string | undefined { - return this.contextKeyService.getContextKeyValue(ChatContextKeys.Entitlement.sku.key); + return this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.sku.key); } //#endregion @@ -340,11 +366,11 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme get sentiment(): IChatSentiment { return { - installed: this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.installed.key) === true, - hidden: this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.hidden.key) === true, - disabled: this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.disabled.key) === true, - untrusted: this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.untrusted.key) === true, - later: this.contextKeyService.getContextKeyValue(ChatContextKeys.Setup.later.key) === true + installed: this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Setup.installed.key) === true, + hidden: this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Setup.hidden.key) === true, + disabled: this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Setup.disabled.key) === true, + untrusted: this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Setup.untrusted.key) === true, + later: this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Setup.later.key) === true }; } @@ -1015,33 +1041,30 @@ export class ChatEntitlementContext extends Disposable { constructor( @IContextKeyService contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @ILogService private readonly logService: ILogService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); - this.canSignUpContextKey = ChatContextKeys.Entitlement.canSignUp.bindTo(contextKeyService); - this.signedOutContextKey = ChatContextKeys.Entitlement.signedOut.bindTo(contextKeyService); - this.freeContextKey = ChatContextKeys.Entitlement.planFree.bindTo(contextKeyService); - this.proContextKey = ChatContextKeys.Entitlement.planPro.bindTo(contextKeyService); - this.proPlusContextKey = ChatContextKeys.Entitlement.planProPlus.bindTo(contextKeyService); - this.businessContextKey = ChatContextKeys.Entitlement.planBusiness.bindTo(contextKeyService); - this.enterpriseContextKey = ChatContextKeys.Entitlement.planEnterprise.bindTo(contextKeyService); - this.organisationsContextKey = ChatContextKeys.Entitlement.organisations.bindTo(contextKeyService); - this.isInternalContextKey = ChatContextKeys.Entitlement.internal.bindTo(contextKeyService); - this.skuContextKey = ChatContextKeys.Entitlement.sku.bindTo(contextKeyService); - this.hiddenContext = ChatContextKeys.Setup.hidden.bindTo(contextKeyService); - this.laterContext = ChatContextKeys.Setup.later.bindTo(contextKeyService); - this.installedContext = ChatContextKeys.Setup.installed.bindTo(contextKeyService); - this.disabledContext = ChatContextKeys.Setup.disabled.bindTo(contextKeyService); - this.untrustedContext = ChatContextKeys.Setup.untrusted.bindTo(contextKeyService); + this.canSignUpContextKey = ChatEntitlementContextKeys.Entitlement.canSignUp.bindTo(contextKeyService); + this.signedOutContextKey = ChatEntitlementContextKeys.Entitlement.signedOut.bindTo(contextKeyService); + this.freeContextKey = ChatEntitlementContextKeys.Entitlement.planFree.bindTo(contextKeyService); + this.proContextKey = ChatEntitlementContextKeys.Entitlement.planPro.bindTo(contextKeyService); + this.proPlusContextKey = ChatEntitlementContextKeys.Entitlement.planProPlus.bindTo(contextKeyService); + this.businessContextKey = ChatEntitlementContextKeys.Entitlement.planBusiness.bindTo(contextKeyService); + this.enterpriseContextKey = ChatEntitlementContextKeys.Entitlement.planEnterprise.bindTo(contextKeyService); + this.organisationsContextKey = ChatEntitlementContextKeys.Entitlement.organisations.bindTo(contextKeyService); + this.isInternalContextKey = ChatEntitlementContextKeys.Entitlement.internal.bindTo(contextKeyService); + this.skuContextKey = ChatEntitlementContextKeys.Entitlement.sku.bindTo(contextKeyService); + this.hiddenContext = ChatEntitlementContextKeys.Setup.hidden.bindTo(contextKeyService); + this.laterContext = ChatEntitlementContextKeys.Setup.later.bindTo(contextKeyService); + this.installedContext = ChatEntitlementContextKeys.Setup.installed.bindTo(contextKeyService); + this.disabledContext = ChatEntitlementContextKeys.Setup.disabled.bindTo(contextKeyService); + this.untrustedContext = ChatEntitlementContextKeys.Setup.untrusted.bindTo(contextKeyService); this._state = this.storageService.getObject(ChatEntitlementContext.CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY, StorageScope.PROFILE) ?? { entitlement: ChatEntitlement.Unknown, organisations: undefined, sku: undefined }; - this.checkExtensionInstallation(); this.updateContextSync(); this.registerListeners(); @@ -1064,39 +1087,6 @@ export class ChatEntitlementContext extends Disposable { return state; } - private async checkExtensionInstallation(): Promise { - - // Await extensions to be ready to be queried - await this.extensionsWorkbenchService.queryLocal(); - - // Listen to extensions change and process extensions once - this._register(Event.runAndSubscribe(this.extensionsWorkbenchService.onChange, e => { - if (e && !ExtensionIdentifier.equals(e.identifier.id, defaultChat.chatExtensionId)) { - return; // unrelated event - } - - const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.chatExtensionId)); - const installed = !!defaultChatExtension?.local; - - let disabled: boolean; - let untrusted = false; - if (installed) { - disabled = !this.extensionEnablementService.isEnabled(defaultChatExtension.local); - if (disabled) { - const state = this.extensionEnablementService.getEnablementState(defaultChatExtension.local); - if (state === EnablementState.DisabledByTrustRequirement) { - disabled = false; // not disabled by user choice but - untrusted = true; // by missing workspace trust - } - } - } else { - disabled = false; - } - - this.update({ installed, disabled, untrusted }); - })); - } - update(context: { installed: boolean; disabled: boolean; untrusted: boolean }): Promise; update(context: { hidden: false }): Promise; // legacy UI state from before we had a setting to hide, keep around to still support users who used this update(context: { later: boolean }): Promise; @@ -1199,3 +1189,4 @@ export class ChatEntitlementContext extends Disposable { //#endregion +registerSingleton(IChatEntitlementService, ChatEntitlementService, InstantiationType.Eager /* To ensure context keys are set asap */); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 23d11d8098b..61d63db7b73 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -131,6 +131,7 @@ import './services/editor/browser/editorPaneService.js'; import './services/editor/common/customEditorLabelService.js'; import './services/dataChannel/browser/dataChannelService.js'; import './services/inlineCompletions/common/inlineCompletionsUnification.js'; +import './services/chat/common/chatEntitlementService.js'; import { InstantiationType, registerSingleton } from '../platform/instantiation/common/extensions.js'; import { GlobalExtensionEnablementService } from '../platform/extensionManagement/common/extensionEnablementService.js'; From 3c7bdacc302a31bc3b408b21cbbca2231cba0dac Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Sep 2025 11:18:40 +0200 Subject: [PATCH 0106/4355] refactor - update imports to use `searchChatContext` (#265801) --- .../contrib/chat/browser/contrib/chatInputCompletions.ts | 2 +- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- .../browser/{chatContributions.ts => searchChatContext.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/vs/workbench/contrib/search/browser/{chatContributions.ts => searchChatContext.ts} (100%) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index fd013253cf7..c53384a96ef 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -43,7 +43,7 @@ import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle. import { ISearchService } from '../../../../services/search/common/search.js'; import { McpPromptArgumentPick } from '../../../mcp/browser/mcpPromptArgumentPick.js'; import { IMcpPrompt, IMcpPromptMessage, IMcpServer, IMcpService, McpResourceURI } from '../../../mcp/common/mcpTypes.js'; -import { searchFilesAndFolders } from '../../../search/browser/chatContributions.js'; +import { searchFilesAndFolders } from '../../../search/browser/searchChatContext.js'; import { IChatAgentData, IChatAgentNameService, IChatAgentService, getFullyQualifiedId } from '../../common/chatAgents.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; import { getAttachableImageExtension } from '../../common/chatModel.js'; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index d11aadfe853..2bd5c7eabdf 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -32,7 +32,7 @@ import { CommandsRegistry } from '../../../../platform/commands/common/commands. import { assertType } from '../../../../base/common/types.js'; import { getWorkspaceSymbols, IWorkspaceSymbol } from '../common/search.js'; import * as Constants from '../common/constants.js'; -import { SearchChatContextContribution } from './chatContributions.js'; +import { SearchChatContextContribution } from './searchChatContext.js'; import './searchActionsCopy.js'; import './searchActionsFind.js'; diff --git a/src/vs/workbench/contrib/search/browser/chatContributions.ts b/src/vs/workbench/contrib/search/browser/searchChatContext.ts similarity index 100% rename from src/vs/workbench/contrib/search/browser/chatContributions.ts rename to src/vs/workbench/contrib/search/browser/searchChatContext.ts From 4bb8ba9e1d8eb9b2f58df68b7f0a64594912ee75 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Tue, 9 Sep 2025 10:49:24 +0100 Subject: [PATCH 0107/4355] Enhance GutterIndicatorMenuContent and action widget styles for improved UI consistency and readability --- .../view/inlineEdits/components/gutterIndicatorMenu.ts | 6 +++--- .../inlineCompletions/browser/view/inlineEdits/view.css | 9 +++++---- src/vs/platform/actionWidget/browser/actionWidget.css | 5 ++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts index fbff2fa09bb..50411838174 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts @@ -117,7 +117,7 @@ export class GutterIndicatorMenuContent { const actionBarFooter = actions.length > 0 ? actionBar( actions.map(action => ({ id: action.id, - label: action.title, + label: action.title + '...', enabled: true, run: () => this._commandService.executeCommand(action.id, ...(action.arguments ?? [])), class: undefined, @@ -227,7 +227,7 @@ function actionBar(actions: IAction[], options: IActionBarOptions) { return derived({ name: 'inlineEdits.actionBar' }, (_reader) => n.div({ class: ['action-widget-action-bar'], style: { - padding: '0 24px', + padding: '3px 24px', } }, [ n.div({ @@ -245,7 +245,7 @@ function separator() { class: 'menu-separator', style: { color: asCssVariable(editorActionListForeground), - padding: '4px 0', + padding: '2px 0', } }, n.div({ style: { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css index a23b07d1f0e..f93e4862603 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css @@ -201,16 +201,17 @@ border-radius: 3px; cursor: pointer; + .monaco-keybinding-key { + font-size: 13px; + opacity: 0.7; + } + &.active { background: var(--vscode-editorActionList-focusBackground); color: var(--vscode-editorActionList-focusForeground); outline: 1px solid var(--vscode-menu-selectionBorder, transparent); outline-offset: -1px; - .monaco-keybinding { - color: var(--vscode-editorActionList-focusForeground); - } - .monaco-keybinding-key { color: var(--vscode-editorActionList-focusForeground); } diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index a370aff5541..b0327720173 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -166,12 +166,12 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 3px 8px 0 24px; + padding: 4px 8px 2px 24px; } .action-widget-action-bar .action-label { color: var(--vscode-textLink-activeForeground); - font-size: 12px; + font-size: 13px; line-height: 22px; padding: 0; pointer-events: all; @@ -194,5 +194,4 @@ .action-widget .monaco-list .monaco-list-row .description { opacity: 0.7; margin-left: 0.5em; - font-size: 0.9em; } From 06c46e2e9c608dee674a4db0ac0d0da5c004fb06 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Tue, 9 Sep 2025 11:47:30 +0100 Subject: [PATCH 0108/4355] Update CSS styling: adjust padding and positioning values --- src/vs/workbench/browser/media/style.css | 2 +- src/vs/workbench/browser/parts/media/compositepart.css | 2 +- .../contrib/debug/browser/media/debugToolBar.css | 2 +- .../contrib/debug/browser/media/debugViewlet.css | 10 +++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 36b4f0a2236..923f442b2f4 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -215,7 +215,7 @@ body { position: absolute; top: 0; bottom: 0; - right: 6px; + right: 4px; margin: auto; pointer-events: none; } diff --git a/src/vs/workbench/browser/parts/media/compositepart.css b/src/vs/workbench/browser/parts/media/compositepart.css index 98f07d9cf18..c539ce2d5ac 100644 --- a/src/vs/workbench/browser/parts/media/compositepart.css +++ b/src/vs/workbench/browser/parts/media/compositepart.css @@ -14,5 +14,5 @@ .monaco-workbench .part > .composite.title > .title-actions { flex: 1; - padding-left: 5px; + padding-left: 8px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 037f08bd87e..c3aaa297262 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -25,7 +25,7 @@ .monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container .monaco-select-box, .monaco-workbench .start-debug-action-item .select-container .monaco-select-box { - padding: 0 22px 0 6px; + padding: 0 24px 0 8px; } .monaco-workbench .debug-toolbar .drag-area { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index f795c6c22eb..fbf236948bf 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -33,7 +33,7 @@ } .monaco-workbench.mac .part > .title > .title-actions .start-debug-action-item { - border-radius: 4px; + border-radius: 5px; } .monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon { @@ -43,8 +43,9 @@ .monaco-workbench .part > .title > .title-actions .start-debug-action-item .codicon-debug-start { width: 18px; - height: 21px; - padding-left: 2px; + height: 22px; + padding-left: 3px; + padding-right: 1px } .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box { @@ -57,6 +58,9 @@ /* The debug view title is crowded, let this one get narrower than others */ min-width: 90px; + + white-space: nowrap; + text-overflow: ellipsis; } From ab928b6f1e3f3aaa9a8f1146dc3348189758e6ea Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:09:16 +0200 Subject: [PATCH 0109/4355] SCM - "Add to Chat" action for history item change (#265815) --- src/vs/workbench/browser/labels.ts | 13 ++ .../chat/browser/chatAttachmentWidgets.ts | 43 ++++- .../chatAttachmentsContentPart.ts | 6 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../chat/common/chatVariableEntries.ts | 14 +- .../scm/browser/scmHistoryChatContext.ts | 38 ++++- .../contrib/scm/browser/scmHistoryViewPane.ts | 149 ++++++++++-------- 7 files changed, 193 insertions(+), 76 deletions(-) diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0fdeb9ac786..38a8fd496de 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -68,6 +68,11 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions { */ readonly namePrefix?: string; + /** + * A suffix to be added to the name of the label. + */ + readonly nameSuffix?: string; + /** * Uses the provided icon instead of deriving a resource icon. */ @@ -535,6 +540,14 @@ class ResourceLabelWidget extends IconLabel { } } + if (options.nameSuffix) { + if (typeof label.name === 'string') { + label.name = label.name + options.nameSuffix; + } else if (Array.isArray(label.name) && label.name.length > 0) { + label.name = [...label.name.slice(0, label.name.length - 1), label.name[label.name.length - 1] + options.nameSuffix]; + } + } + const hasResourceChanged = this.hasResourceChanged(label); const hasPathLabelChanged = hasResourceChanged || this.hasPathLabelChanged(label); const hasFileKindChanged = this.hasFileKindChanged(options); diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 63138596157..9da07dc37ed 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -50,7 +50,7 @@ import { CellUri } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; import { getHistoryItemEditorTitle, getHistoryItemHoverContent } from '../../scm/browser/util.js'; import { IChatContentReference } from '../common/chatService.js'; -import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry } from '../common/chatVariableEntries.js'; +import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; @@ -70,7 +70,7 @@ abstract class AbstractChatAttachmentWidget extends Disposable { } constructor( - private readonly attachment: IChatRequestVariableEntry, + protected readonly attachment: IChatRequestVariableEntry, private readonly options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, @@ -822,6 +822,45 @@ export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget } } +export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachmentWidget { + constructor( + attachment: ISCMHistoryItemChangeVariableEntry, + currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + contextResourceLabels: ResourceLabels, + hoverDelegate: IHoverDelegate, + @ICommandService commandService: ICommandService, + @IHoverService hoverService: IHoverService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @IEditorService private readonly editorService: IEditorService, + ) { + super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + + const nameSuffix = `\u00A0$(${Codicon.gitCommit.id})${attachment.historyItem.displayId ?? attachment.historyItem.id}`; + this.label.setFile(attachment.value, { fileKind: FileKind.FILE, nameSuffix }); + + this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); + this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, () => getHistoryItemHoverContent(themeService, attachment.historyItem), { trapFocus: true })); + + this.addResourceOpenHandlers(attachment.value, undefined); + this.attachClearButton(); + } + + protected override async openResource(resource: URI, isDirectory: true): Promise; + protected override async openResource(resource: URI, isDirectory: false, range: IRange | undefined): Promise; + protected override async openResource(resource: URI, isDirectory?: boolean, range?: IRange): Promise { + const attachment = this.attachment as ISCMHistoryItemChangeVariableEntry; + const historyItem = attachment.historyItem; + + await this.editorService.openEditor({ + resource, + label: `${basename(resource.path)} (${historyItem.displayId ?? historyItem.id})`, + }); + } +} + export function hookUpResourceAttachmentDragAndContextMenu(accessor: ServicesAccessor, widget: HTMLElement, resource: URI): IDisposable { const contextKeyService = accessor.get(IContextKeyService); const instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index b2f292e7614..6c14e4c0895 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -12,9 +12,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; export class ChatAttachmentsContentPart extends Disposable { private readonly attachedContextDisposables = this._register(new DisposableStore()); @@ -77,6 +77,8 @@ export class ChatAttachmentsContentPart extends Disposable { widget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else if (isSCMHistoryItemVariableEntry(attachment)) { widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { + widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else { widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index ad0fb8221cb..2927f2805b2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -81,7 +81,7 @@ import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; @@ -93,7 +93,7 @@ import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerAction import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; import { IDisposableReference } from './chatContentParts/chatCollections.js'; import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; import { ChatDragAndDrop } from './chatDragAndDrop.js'; @@ -1406,6 +1406,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge attachmentWidget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else if (isSCMHistoryItemVariableEntry(attachment)) { attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { + attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else { attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 64960e01c2e..b164909dccc 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -209,12 +209,18 @@ export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEn readonly historyItem: ISCMHistoryItem; } +export interface ISCMHistoryItemChangeVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'scmHistoryItemChange'; + readonly value: URI; + readonly historyItem: ISCMHistoryItem; +} + export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry | IChatRequestToolEntry | IChatRequestToolSetEntry | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry - | IPromptFileVariableEntry | IPromptTextVariableEntry | ISCMHistoryItemVariableEntry; - + | IPromptFileVariableEntry | IPromptTextVariableEntry + | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry; export namespace IChatRequestVariableEntry { @@ -279,6 +285,10 @@ export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): o return obj.kind === 'scmHistoryItem'; } +export function isSCMHistoryItemChangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeVariableEntry { + return obj.kind === 'scmHistoryItemChange'; +} + export enum PromptFileVariableKind { Instruction = 'vscode.prompt.instructions.root', InstructionReference = `vscode.prompt.instructions`, diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index b26e8624999..580bfdbd665 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -9,6 +9,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { fromNow } from '../../../../base/common/date.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { basename } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { ITextModel } from '../../../../editor/common/model.js'; @@ -23,9 +24,9 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidget, showChatView } from '../../chat/browser/chat.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatVariableEntries.js'; +import { ISCMHistoryItemChangeVariableEntry, ISCMHistoryItemVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { ScmHistoryItemResolver } from '../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js'; -import { ISCMHistoryItem } from '../common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemChange } from '../common/history.js'; import { ISCMProvider, ISCMService, ISCMViewService } from '../common/scm.js'; export interface SCMHistoryItemTransferData { @@ -233,3 +234,36 @@ registerAction2(class extends Action2 { await widget.acceptInput('Summarize the attached history item'); } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.scm.action.graph.addHistoryItemChangeToChat', + title: localize('chat.action.scmHistoryItemContext', 'Add to Chat'), + f1: false, + menu: { + id: MenuId.SCMHistoryItemChangeContext, + group: 'z_chat', + order: 1, + when: ChatContextKeys.enabled + } + }); + } + + override async run(accessor: ServicesAccessor, provider: ISCMProvider, arg: { historyItem: ISCMHistoryItem; historyItemChange: ISCMHistoryItemChange }): Promise { + const viewsService = accessor.get(IViewsService); + const widget = await showChatView(viewsService); + if (!provider || !arg.historyItem || !arg.historyItemChange.modifiedUri || !widget) { + return; + } + + widget.attachmentModel.addContext({ + id: arg.historyItemChange.uri.toString(), + name: `${basename(arg.historyItemChange.modifiedUri)}`, + value: arg.historyItemChange.modifiedUri, + historyItem: arg.historyItem, + kind: 'scmHistoryItemChange', + } satisfies ISCMHistoryItemChangeVariableEntry); + } +}); + diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index c3e3e9f6717..353f96e68a0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -1956,87 +1956,104 @@ export class SCMHistoryViewPane extends ViewPane { private _onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; - if (!element || !isSCMHistoryItemViewModelTreeElement(element)) { - return; - } + if (isSCMHistoryItemViewModelTreeElement(element)) { + // HistoryItem + this._contextMenuDisposables.value = new DisposableStore(); - this._contextMenuDisposables.value = new DisposableStore(); + const historyItemRefMenuItems = MenuRegistry.getMenuItems(MenuId.SCMHistoryItemRefContext).filter(item => isIMenuItem(item)); - const historyItemRefMenuItems = MenuRegistry.getMenuItems(MenuId.SCMHistoryItemRefContext).filter(item => isIMenuItem(item)); + // If there are any history item references we have to add a submenu item for each orignal action, + // and a menu item for each history item ref that matches the `when` clause of the original action. + if (historyItemRefMenuItems.length > 0 && element.historyItemViewModel.historyItem.references?.length) { + const historyItemRefActions = new Map(); - // If there are any history item references we have to add a submenu item for each orignal action, - // and a menu item for each history item ref that matches the `when` clause of the original action. - if (historyItemRefMenuItems.length > 0 && element.historyItemViewModel.historyItem.references?.length) { - const historyItemRefActions = new Map(); + for (const ref of element.historyItemViewModel.historyItem.references) { + const contextKeyService = this.scopedContextKeyService.createOverlay([ + ['scmHistoryItemRef', ref.id] + ]); - for (const ref of element.historyItemViewModel.historyItem.references) { - const contextKeyService = this.scopedContextKeyService.createOverlay([ - ['scmHistoryItemRef', ref.id] - ]); + const menuActions = this._menuService.getMenuActions( + MenuId.SCMHistoryItemRefContext, contextKeyService); - const menuActions = this._menuService.getMenuActions( - MenuId.SCMHistoryItemRefContext, contextKeyService); + for (const action of menuActions.flatMap(a => a[1])) { + if (!historyItemRefActions.has(action.id)) { + historyItemRefActions.set(action.id, []); + } - for (const action of menuActions.flatMap(a => a[1])) { - if (!historyItemRefActions.has(action.id)) { - historyItemRefActions.set(action.id, []); + historyItemRefActions.get(action.id)!.push(ref); } - - historyItemRefActions.get(action.id)!.push(ref); } - } - - // Register submenu, menu items - for (const historyItemRefMenuItem of historyItemRefMenuItems) { - const actionId = historyItemRefMenuItem.command.id; - if (!historyItemRefActions.has(actionId)) { - continue; - } + // Register submenu, menu items + for (const historyItemRefMenuItem of historyItemRefMenuItems) { + const actionId = historyItemRefMenuItem.command.id; - // Register the submenu for the original action - this._contextMenuDisposables.value.add(MenuRegistry.appendMenuItem(MenuId.SCMHistoryItemContext, { - title: historyItemRefMenuItem.command.title, - submenu: MenuId.for(actionId), - group: historyItemRefMenuItem?.group, - order: historyItemRefMenuItem?.order - })); + if (!historyItemRefActions.has(actionId)) { + continue; + } - // Register the action for the history item ref - for (const historyItemRef of historyItemRefActions.get(actionId) ?? []) { - this._contextMenuDisposables.value.add(registerAction2(class extends Action2 { - constructor() { - super({ - id: `${actionId}.${historyItemRef.id}`, - title: historyItemRef.name, - menu: { - id: MenuId.for(actionId), - group: historyItemRef.category - } - }); - } - override run(accessor: ServicesAccessor, ...args: any[]): void { - const commandService = accessor.get(ICommandService); - commandService.executeCommand(actionId, ...args, historyItemRef.id); - } + // Register the submenu for the original action + this._contextMenuDisposables.value.add(MenuRegistry.appendMenuItem(MenuId.SCMHistoryItemContext, { + title: historyItemRefMenuItem.command.title, + submenu: MenuId.for(actionId), + group: historyItemRefMenuItem?.group, + order: historyItemRefMenuItem?.order })); + + // Register the action for the history item ref + for (const historyItemRef of historyItemRefActions.get(actionId) ?? []) { + this._contextMenuDisposables.value.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: `${actionId}.${historyItemRef.id}`, + title: historyItemRef.name, + menu: { + id: MenuId.for(actionId), + group: historyItemRef.category + } + }); + } + override run(accessor: ServicesAccessor, ...args: any[]): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand(actionId, ...args, historyItemRef.id); + } + })); + } } } - } - - const historyItemMenuActions = this._menuService.getMenuActions( - MenuId.SCMHistoryItemContext, - this.scopedContextKeyService, { - arg: element.repository.provider, - shouldForwardArgs: true - }).filter(group => group[0] !== 'inline'); - this.contextMenuService.showContextMenu({ - contextKeyService: this.scopedContextKeyService, - getAnchor: () => e.anchor, - getActions: () => getFlatContextMenuActions(historyItemMenuActions), - getActionsContext: () => element.historyItemViewModel.historyItem - }); + const menuActions = this._menuService.getMenuActions( + MenuId.SCMHistoryItemContext, + this.scopedContextKeyService, { + arg: element.repository.provider, + shouldForwardArgs: true + }).filter(group => group[0] !== 'inline'); + + this.contextMenuService.showContextMenu({ + contextKeyService: this.scopedContextKeyService, + getAnchor: () => e.anchor, + getActions: () => getFlatContextMenuActions(menuActions), + getActionsContext: () => element.historyItemViewModel.historyItem + }); + } else if (isSCMHistoryItemChangeViewModelTreeElement(element)) { + // HistoryItemChange + const menuActions = this._menuService.getMenuActions( + MenuId.SCMHistoryItemChangeContext, + this.scopedContextKeyService, { + arg: element.repository.provider, + shouldForwardArgs: true + }).filter(group => group[0] !== 'inline'); + + this.contextMenuService.showContextMenu({ + contextKeyService: this.scopedContextKeyService, + getAnchor: () => e.anchor, + getActions: () => getFlatContextMenuActions(menuActions), + getActionsContext: () => ({ + historyItem: element.historyItemViewModel.historyItem, + historyItemChange: element.historyItemChange + }) + }); + } } private async _loadMore(cursor?: string): Promise { From 1a7adad5138804a1183bab5b16ec0b675cf6f213 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:09:39 +0200 Subject: [PATCH 0110/4355] Chat - add support for selection when attaching a file (#265822) --- .../contrib/chat/browser/actions/chatActions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index a31a595003f..ae7fb91c6cb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -22,6 +22,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; import { Position } from '../../../../../editor/common/core/position.js'; +import { IRange } from '../../../../../editor/common/core/range.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; import { SuggestController } from '../../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../../nls.js'; @@ -110,7 +111,7 @@ export interface IChatViewOpenOptions { /** * A list of file URIs to attach to the chat as context. */ - attachFiles?: URI[]; + attachFiles?: (URI | { uri: URI; range: IRange })[]; /** * The mode ID or name to open the chat in. */ @@ -228,8 +229,11 @@ abstract class OpenChatGlobalAction extends Action2 { } if (opts?.attachFiles) { for (const file of opts.attachFiles) { - if (await fileService.exists(file)) { - chatWidget.attachmentModel.addFile(file); + const uri = file instanceof URI ? file : file.uri; + const range = file instanceof URI ? undefined : file.range; + + if (await fileService.exists(uri)) { + chatWidget.attachmentModel.addFile(uri, range); } } } From 8b38b20d3fc3a27b9fcbe587094a34c7da3bcb23 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 9 Sep 2025 06:17:17 -0700 Subject: [PATCH 0111/4355] Prevent duplicates showing in reasoning --- .../browser/commandLineAutoApprover.ts | 5 +- .../browser/runInTerminalHelpers.ts | 6 ++ .../browser/tools/runInTerminalTool.ts | 13 ++- .../test/browser/runInTerminalHelpers.test.ts | 99 ++++++++++++++++++- .../test/browser/runInTerminalTool.test.ts | 19 +++- 5 files changed, 130 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 62493e6edb6..9a07ec69253 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -23,6 +23,7 @@ export interface IAutoApproveRule { export interface ICommandApprovalResultWithReason { result: ICommandApprovalResult; reason: string; + rule?: IAutoApproveRule; } export type ICommandApprovalResult = 'approved' | 'denied' | 'noMatch'; @@ -73,7 +74,7 @@ export class CommandLineAutoApprover extends Disposable { this._denyListCommandLineRules = denyListCommandLineRules; } - isCommandAutoApproved(command: string, shell: string, os: OperatingSystem): { result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string } { + isCommandAutoApproved(command: string, shell: string, os: OperatingSystem): ICommandApprovalResultWithReason { // Check the deny list to see if this command requires explicit approval for (const rule of this._denyListRules) { if (this._commandMatchesRule(rule, command, shell, os)) { @@ -105,7 +106,7 @@ export class CommandLineAutoApprover extends Disposable { }; } - isCommandLineAutoApproved(commandLine: string): { result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string } { + isCommandLineAutoApproved(commandLine: string): ICommandApprovalResultWithReason { // Check the deny list first to see if this command line requires explicit approval for (const rule of this._denyListCommandLineRules) { if (rule.regex.test(commandLine)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 734560b288f..cfc880ec663 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -140,3 +140,9 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str return actions; } + +export function dedupeRules(rules: ICommandApprovalResultWithReason[]): ICommandApprovalResultWithReason[] { + return rules.filter((result, index, array) => { + return result.rule && array.findIndex(r => r.rule && r.rule.sourceText === result.rule!.sourceText) === index; + }); +} 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 abfbe4ef0f7..b6606432e20 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -30,14 +30,14 @@ import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerm import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; import { getRecommendedToolsOverRunInTerminal } from '../alternativeRecommendation.js'; -import { CommandLineAutoApprover, type IAutoApproveRule, type ICommandApprovalResult } from '../commandLineAutoApprover.js'; +import { CommandLineAutoApprover, type IAutoApproveRule, type ICommandApprovalResult, type ICommandApprovalResultWithReason } from '../commandLineAutoApprover.js'; import { CommandSimplifier } from '../commandSimplifier.js'; import { BasicExecuteStrategy } from '../executeStrategy/basicExecuteStrategy.js'; import type { ITerminalExecuteStrategy } from '../executeStrategy/executeStrategy.js'; import { NoneExecuteStrategy } from '../executeStrategy/noneExecuteStrategy.js'; import { RichExecuteStrategy } from '../executeStrategy/richExecuteStrategy.js'; import { OutputMonitor } from './monitoring/outputMonitor.js'; -import { generateAutoApproveActions, isPowerShell } from '../runInTerminalHelpers.js'; +import { dedupeRules, generateAutoApproveActions, isPowerShell } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { splitCommandLineIntoSubCommands } from '../subCommands.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; @@ -773,8 +773,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { isAutoApproved: boolean, isDenied: boolean, autoApproveReason: 'subCommand' | 'commandLine' | undefined, - subCommandResults: ReturnType[], - commandLineResult: ReturnType, + subCommandResults: ICommandApprovalResultWithReason[], + commandLineResult: ICommandApprovalResultWithReason, ): MarkdownString | undefined { function formatRuleLinks(result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string { return asArray(result).map(e => { @@ -797,7 +797,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { break; } case 'subCommand': { - const uniqueRules = Array.from(new Set(subCommandResults)); + const uniqueRules = dedupeRules(subCommandResults); if (uniqueRules.length === 1) { return new MarkdownString(`_${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules))}_`); } else if (uniqueRules.length > 1) { @@ -815,8 +815,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { break; } case 'subCommand': { - const deniedRules = subCommandResults.filter(e => e.result === 'denied'); - const uniqueRules = Array.from(new Set(deniedRules)); + const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); if (uniqueRules.length === 1) { return new MarkdownString(`_${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))}_`); } else if (uniqueRules.length > 1) { 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 9947091318e..e6e3b6eee34 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 @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ok } from 'assert'; -import { isPowerShell } from '../../browser/runInTerminalHelpers.js'; +import { ok, strictEqual } from 'assert'; +import { dedupeRules, isPowerShell } 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'; +import type { IAutoApproveRule, ICommandApprovalResultWithReason } from '../../browser/commandLineAutoApprover.js'; suite('isPowerShell', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -162,3 +164,96 @@ suite('isPowerShell', () => { }); }); }); + +suite('dedupeRules', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function createMockRule(sourceText: string): IAutoApproveRule { + return { + regex: new RegExp(sourceText), + regexCaseInsensitive: new RegExp(sourceText, 'i'), + sourceText, + sourceTarget: ConfigurationTarget.USER, + isDefaultRule: false + }; + } + + function createMockResult(result: 'approved' | 'denied' | 'noMatch', reason: string, rule?: IAutoApproveRule): ICommandApprovalResultWithReason { + return { + result, + reason, + rule + }; + } + + test('should return empty array for empty input', () => { + const result = dedupeRules([]); + strictEqual(result.length, 0); + }); + + test('should return same array when no duplicates exist', () => { + const result = dedupeRules([ + createMockResult('approved', 'approved by echo rule', createMockRule('echo')), + createMockResult('approved', 'approved by ls rule', createMockRule('ls')) + ]); + strictEqual(result.length, 2); + strictEqual(result[0].rule?.sourceText, 'echo'); + strictEqual(result[1].rule?.sourceText, 'ls'); + }); + + test('should deduplicate rules with same sourceText', () => { + const result = dedupeRules([ + createMockResult('approved', 'approved by echo rule', createMockRule('echo')), + createMockResult('approved', 'approved by echo rule again', createMockRule('echo')), + createMockResult('approved', 'approved by ls rule', createMockRule('ls')) + ]); + strictEqual(result.length, 2); + strictEqual(result[0].rule?.sourceText, 'echo'); + strictEqual(result[1].rule?.sourceText, 'ls'); + }); + + test('should preserve first occurrence when deduplicating', () => { + const result = dedupeRules([ + createMockResult('approved', 'first echo rule', createMockRule('echo')), + createMockResult('approved', 'second echo rule', createMockRule('echo')) + ]); + strictEqual(result.length, 1); + strictEqual(result[0].reason, 'first echo rule'); + }); + + test('should filter out results without rules', () => { + const result = dedupeRules([ + createMockResult('noMatch', 'no rule applied'), + createMockResult('approved', 'approved by echo rule', createMockRule('echo')), + createMockResult('denied', 'denied without rule') + ]); + strictEqual(result.length, 1); + strictEqual(result[0].rule?.sourceText, 'echo'); + }); + + test('should handle mix of rules and no-rule results with duplicates', () => { + const result = dedupeRules([ + createMockResult('approved', 'approved by echo rule', createMockRule('echo')), + createMockResult('noMatch', 'no rule applied'), + createMockResult('approved', 'approved by echo rule again', createMockRule('echo')), + createMockResult('approved', 'approved by ls rule', createMockRule('ls')), + createMockResult('denied', 'denied without rule') + ]); + strictEqual(result.length, 2); + strictEqual(result[0].rule?.sourceText, 'echo'); + strictEqual(result[1].rule?.sourceText, 'ls'); + }); + + test('should handle multiple duplicates of same rule', () => { + const result = dedupeRules([ + createMockResult('approved', 'npm rule 1', createMockRule('npm')), + createMockResult('approved', 'npm rule 2', createMockRule('npm')), + createMockResult('approved', 'npm rule 3', createMockRule('npm')), + createMockResult('approved', 'git rule', createMockRule('git')) + ]); + strictEqual(result.length, 2); + strictEqual(result[0].rule?.sourceText, 'npm'); + strictEqual(result[0].reason, 'npm rule 1'); + strictEqual(result[1].rule?.sourceText, 'git'); + }); +}); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index eaecab8ff58..4a24883e122 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -13,7 +13,7 @@ import { ConfigurationTarget } from '../../../../../../platform/configuration/co import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; import type { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; -import { IChatService } from '../../../../chat/common/chatService.js'; +import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; import { ILanguageModelToolsService, IPreparedToolInvocation, IToolInvocationPreparationContext, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; @@ -22,6 +22,7 @@ import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js'; import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; +import { count } from '../../../../../../base/common/strings.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -1028,4 +1029,20 @@ suite('RunInTerminalTool', () => { assertConfirmationRequired(result, 'Run `pwsh` command?'); }); }); + + suite('unique rules deduplication', () => { + test('should properly deduplicate rules with same sourceText in auto-approve info', async () => { + setAutoApprove({ + echo: true + }); + + const result = await executeToolTest({ command: 'echo hello && echo world' }); + assertAutoApproved(result); + + const autoApproveInfo = (result!.toolSpecificData as IChatTerminalToolInvocationData).autoApproveInfo!; + ok(autoApproveInfo); + ok(autoApproveInfo.value.includes('Auto approved by rule '), 'should contain singular "rule", not plural'); + strictEqual(count(autoApproveInfo.value, 'echo'), 1); + }); + }); }); From c4eaa5730c7b2eb0958f0b3c5e093f0b3096210d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 9 Sep 2025 15:23:18 +0200 Subject: [PATCH 0112/4355] adopt to latest mcp spec (#265837) --- .../platform/mcp/common/mcpGalleryService.ts | 53 +++++++++++++------ src/vs/platform/mcp/common/mcpManagement.ts | 35 +++++++++--- .../contrib/mcp/browser/mcpServerEditor.ts | 4 +- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 85e1fe0321a..007d94f47ce 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -14,18 +14,35 @@ import { IFileService } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; import { IProductService } from '../../product/common/productService.js'; import { asJson, asText, IRequestService } from '../../request/common/request.js'; -import { IGalleryMcpServer, GalleryMcpServerStatus, IMcpGalleryService, IGalleryMcpServerConfiguration, IMcpServerPackage, IMcpServerRemote, IQueryOptions } from './mcpManagement.js'; +import { IGalleryMcpServer, GalleryMcpServerStatus, IMcpGalleryService, IGalleryMcpServerConfiguration, IMcpServerPackage, IQueryOptions, SseTransport, StreamableHttpTransport, IMcpServerKeyValueInput, Transport, TransportType } from './mcpManagement.js'; import { IMcpGalleryManifestService, McpGalleryManifestStatus, getMcpGalleryManifestResourceUri, McpGalleryResourceType, IMcpGalleryManifest } from './mcpGalleryManifest.js'; import { IPageIterator, IPager, PageIteratorPager, singlePagePager } from '../../../base/common/paging.js'; import { CancellationError } from '../../../base/common/errors.js'; import { basename } from '../../../base/common/path.js'; +interface McpServerDeprecatedRemote { + readonly transport_type?: 'streamable' | 'sse'; + readonly transport?: 'streamable' | 'sse'; + readonly url: string; + readonly headers?: ReadonlyArray; +} + +type McpServerRemotes = ReadonlyArray; + interface IRawGalleryServerListMetadata { readonly count: number; readonly total?: number; readonly next_cursor?: string; } +interface IMcpRegistryInfo { + readonly id: string; + readonly is_latest: boolean; + readonly published_at: string; + readonly updated_at: string; + readonly release_date?: string; +} + interface IGitHubInfo { readonly 'name': string; readonly 'name_with_owner': string; @@ -42,14 +59,10 @@ interface IGitHubInfo { } interface IRawGalleryMcpServerMetaData { - readonly 'x-io.modelcontextprotocol.registry'?: { - readonly id: string; - readonly published_at: string; - readonly updated_at: string; - readonly is_latest: boolean; - readonly release_date?: string; - }; + readonly 'io.modelcontextprotocol.registry/official'?: IMcpRegistryInfo; + readonly 'x-io.modelcontextprotocol.registry'?: IMcpRegistryInfo; readonly 'x-publisher'?: Record; + readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; readonly 'x-github'?: IGitHubInfo; readonly 'github'?: IGitHubInfo; } @@ -96,15 +109,16 @@ interface IRawGalleryMcpServerDetail { }; readonly status?: GalleryMcpServerStatus; readonly repository?: { - readonly url: string; readonly source: string; - readonly id: string; + readonly url: string; + readonly id?: string; + readonly subfolder?: string; readonly readme?: string; }; readonly created_at: string; readonly updated_at: string; readonly packages?: readonly IRawGalleryMcpServerPackage[]; - readonly remotes?: readonly IMcpServerRemote[]; + readonly remotes?: McpServerRemotes; } interface IVSCodeGalleryMcpServerDetail { @@ -127,7 +141,7 @@ interface IVSCodeGalleryMcpServerDetail { }; readonly manifest: { readonly packages?: readonly IRawGalleryMcpServerPackage[]; - readonly remotes?: readonly IMcpServerRemote[]; + readonly remotes?: McpServerRemotes; }; } @@ -346,7 +360,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService }; } - private toGalleryMcpServerConfiguration(packages?: readonly IRawGalleryMcpServerPackage[], remotes?: readonly IMcpServerRemote[]): IGalleryMcpServerConfiguration | undefined { + private toGalleryMcpServerConfiguration(packages?: readonly IRawGalleryMcpServerPackage[], remotes?: McpServerRemotes): IGalleryMcpServerConfiguration | undefined { if (!packages && !remotes) { return undefined; } @@ -357,7 +371,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService identifier: p.identifier ?? p.name, registry_type: p.registry_type ?? p.registry_name })), - remotes + remotes: remotes?.map(remote => { + const type = (remote).type ?? (remote).transport_type ?? (remote).transport; + return { + type: type === TransportType.SSE ? TransportType.SSE : TransportType.STREAMABLE_HTTP, + url: remote.url, + headers: remote.headers + }; + }) }; } @@ -437,9 +458,9 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return { ...from.server, _meta: { - 'x-io.modelcontextprotocol.registry': from['x-io.modelcontextprotocol.registry'], + 'io.modelcontextprotocol.registry/official': from['io.modelcontextprotocol.registry/official'] ?? from['x-io.modelcontextprotocol.registry'], 'github': from['x-github'], - 'x-publisher': from['x-publisher'] + 'io.modelcontextprotocol.registry/publisher-provided': from['io.modelcontextprotocol.registry/publisher-provided'] ?? from['x-publisher'] } }; } diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 9a06dddefdd..13c4fed4113 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -78,27 +78,46 @@ export const enum RegistryType { MCPB = 'mcpb', } +export const enum TransportType { + STDIO = 'stdio', + STREAMABLE_HTTP = 'streamable-http', + SSE = 'sse' +} + +export interface StdioTransport { + readonly type: TransportType.STDIO; +} + +export interface StreamableHttpTransport { + readonly type: TransportType.STREAMABLE_HTTP; + readonly url: string; + readonly headers?: ReadonlyArray; +} + +export interface SseTransport { + readonly type: TransportType.SSE; + readonly url: string; + readonly headers?: ReadonlyArray; +} + +export type Transport = StdioTransport | StreamableHttpTransport | SseTransport; + export interface IMcpServerPackage { readonly registry_type: RegistryType; readonly registry_base_url?: string; readonly identifier: string; readonly version: string; readonly file_sha256?: string; - readonly runtime_hint?: string; + readonly transport?: Transport; readonly package_arguments?: readonly IMcpServerArgument[]; + readonly runtime_hint?: string; readonly runtime_arguments?: readonly IMcpServerArgument[]; readonly environment_variables?: ReadonlyArray; } -export interface IMcpServerRemote { - readonly url: string; - readonly transport_type?: 'streamable' | 'sse'; - readonly headers?: ReadonlyArray; -} - export interface IGalleryMcpServerConfiguration { readonly packages?: readonly IMcpServerPackage[]; - readonly remotes?: readonly IMcpServerRemote[]; + readonly remotes?: ReadonlyArray; } export const enum GalleryMcpServerStatus { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index e8a411e2582..59fdd9bb6d3 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -790,8 +790,8 @@ export class McpServerEditor extends EditorPane { for (const remote of manifest.remotes) { const packagesGrid = append(packageSection, $('.package-details')); append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('url', "URL:")), $('.detail-value', undefined, remote.url))); - if (remote.transport_type) { - append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('transport', "Transport:")), $('.detail-value', undefined, remote.transport_type))); + if (remote.type) { + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('transport', "Transport:")), $('.detail-value', undefined, remote.type))); } if (remote.headers && remote.headers.length > 0) { const headerStrings = remote.headers.map((header: any) => `${header.name}: ${header.value}`); From f3d03059592ff7b149e5d143ad6c1da12d1e30ad Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:40:52 +0200 Subject: [PATCH 0113/4355] SCM - add method to resolve a source control history item (#265844) --- extensions/git/src/historyProvider.ts | 54 +++++++++++++++++++ src/vs/workbench/api/browser/mainThreadSCM.ts | 5 ++ .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 13 +++++ .../workbench/contrib/scm/common/history.ts | 1 + .../vscode.proposed.scmHistoryProvider.d.ts | 1 + 6 files changed, 75 insertions(+) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 9830928fcd3..f9270d0f2ab 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -352,6 +352,60 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItemChanges; } + async resolveHistoryItem(historyItemId: string, token: CancellationToken): Promise { + try { + const commit = await this.repository.getCommit(historyItemId); + + if (!commit || token.isCancellationRequested) { + return undefined; + } + + // Avatars + const avatarQuery = { + commits: [{ + hash: commit.hash, + authorName: commit.authorName, + authorEmail: commit.authorEmail + } satisfies AvatarQueryCommit], + size: 20 + } satisfies AvatarQuery; + + const commitAvatars = await provideSourceControlHistoryItemAvatar( + this.historyItemDetailProviderRegistry, this.repository, avatarQuery); + + await ensureEmojis(); + + const message = emojify(commit.message); + const messageWithLinks = await provideSourceControlHistoryItemMessageLinks( + this.historyItemDetailProviderRegistry, this.repository, message) ?? message; + + const newLineIndex = message.indexOf('\n'); + const subject = newLineIndex !== -1 + ? `${truncate(message, newLineIndex, false)}` + : message; + + const avatarUrl = commitAvatars?.get(commit.hash); + const references = this._resolveHistoryItemRefs(commit); + + return { + id: commit.hash, + parentIds: commit.parents, + subject, + message: messageWithLinks, + author: commit.authorName, + authorEmail: commit.authorEmail, + authorIcon: avatarUrl ? Uri.parse(avatarUrl) : new ThemeIcon('account'), + displayId: truncate(commit.hash, this.commitShortHashLength, false), + timestamp: commit.authorDate?.getTime(), + statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, + references: references.length !== 0 ? references : undefined + } satisfies SourceControlHistoryItem; + } catch (err) { + this.logger.error(`[GitHistoryProvider][resolveHistoryItem] Failed to resolve history item '${historyItemId}': ${err}`); + return undefined; + } + } + async resolveHistoryItemChatContext(historyItemId: string): Promise { try { const commitDetails = await this.repository.showCommit(historyItemId); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 68b32c43584..0e24b06f64f 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -195,6 +195,11 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } + async resolveHistoryItem(historyItemId: string, token?: CancellationToken): Promise { + const historyItem = await this.proxy.$resolveHistoryItem(this.handle, historyItemId, token ?? CancellationToken.None); + return historyItem ? toISCMHistoryItem(historyItem) : undefined; + } + async resolveHistoryItemChatContext(historyItemId: string, token?: CancellationToken): Promise { return this.proxy.$resolveHistoryItemChatContext(this.handle, historyItemId, token ?? CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 799392c9e07..fdd9a91daca 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2597,6 +2597,7 @@ export interface ExtHostSCMShape { $provideHistoryItemRefs(sourceControlHandle: number, historyItemRefs: string[] | undefined, token: CancellationToken): Promise; $provideHistoryItems(sourceControlHandle: number, options: ISCMHistoryOptions, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; + $resolveHistoryItem(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 3a32a9c635b..67fcb734572 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -1089,6 +1089,19 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } + async $resolveHistoryItem(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise { + try { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + const historyItem = await historyProvider?.resolveHistoryItem(historyItemId, token); + + return historyItem ? toSCMHistoryItemDto(historyItem) : undefined; + } + catch (err) { + this.logService.error('ExtHostSCM#$resolveHistoryItem', err); + return undefined; + } + } + async $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise { try { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 8ad6125b654..007e420e216 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -25,6 +25,7 @@ export interface ISCMHistoryProvider { provideHistoryItemRefs(historyItemsRefs?: string[], token?: CancellationToken): Promise; provideHistoryItems(options: ISCMHistoryOptions, token?: CancellationToken): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token?: CancellationToken): Promise; + resolveHistoryItem(historyItemId: string, token?: CancellationToken): Promise; resolveHistoryItemChatContext(historyItemId: string, token?: CancellationToken): Promise; resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token?: CancellationToken): Promise; } diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 10762424c9b..b094f4e3854 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -30,6 +30,7 @@ declare module 'vscode' { provideHistoryItems(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; + resolveHistoryItem(historyItemId: string, token: CancellationToken): ProviderResult; resolveHistoryItemChatContext(historyItemId: string, token: CancellationToken): ProviderResult; resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): ProviderResult; } From da82a43422d09e9f5a9b86dc0d50454ab039817d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 9 Sep 2025 06:46:52 -0700 Subject: [PATCH 0114/4355] Allow git grep and grep, see comments Fixes #264733 --- .../terminalChatAgentToolsConfiguration.ts | 36 +++--- .../browser/commandLineAutoApprover.test.ts | 116 ------------------ 2 files changed, 17 insertions(+), 135 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index da91cd6b6db..95e94868dcf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -138,6 +138,17 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { strictEqual(getCommandLineIsDefaultRule('cmd2 arg'), true, 'Object type should match default using structural equality (even though it\'s a deny rule)'); }); }); - - suite('git grep security tests', () => { - test('should auto-approve safe git grep commands by default', () => { - // Using default configuration which includes 'git grep': true - setAutoApproveWithDefaults( - {}, - { 'git grep': true } - ); - - ok(isAutoApproved('git grep pattern')); - ok(isAutoApproved('git grep "search term"')); - ok(isAutoApproved('git grep -n pattern')); - ok(isAutoApproved('git grep -i "case insensitive"')); - ok(isAutoApproved('git grep --line-number pattern file.txt')); - }); - - test('should block git grep with -f flag (read patterns from file)', () => { - setAutoApproveWithDefaults( - {}, - { - 'git grep': true, - '/^git grep\\b.*-(f|P)\\b/': false - } - ); - - // Safe git grep commands should still work - ok(isAutoApproved('git grep pattern')); - ok(isAutoApproved('git grep -n pattern')); - - // Dangerous -f flag should be blocked - ok(!isAutoApproved('git grep -f patterns.txt')); - ok(!isAutoApproved('git grep --file patterns.txt')); - ok(!isAutoApproved('git grep -n -f /etc/passwd')); - }); - - test('should block git grep with -P flag (Perl regexp)', () => { - setAutoApproveWithDefaults( - {}, - { - 'git grep': true, - '/^git grep\\b.*-(f|P)\\b/': false - } - ); - - // Safe git grep commands should still work - ok(isAutoApproved('git grep pattern')); - ok(isAutoApproved('git grep -E "extended regexp"')); - - // Dangerous -P flag should be blocked - ok(!isAutoApproved('git grep -P "perl regexp"')); - ok(!isAutoApproved('git grep --perl-regexp "pattern"')); - ok(!isAutoApproved('git grep -n -P "complex.*pattern"')); - }); - - test('should block git grep with --open-files-in-pager flag', () => { - setAutoApproveWithDefaults( - {}, - { - 'git grep': true, - '/^git grep\\b.*--open-files-in-pager\\b/': false - } - ); - - // Safe git grep commands should still work - ok(isAutoApproved('git grep pattern')); - ok(isAutoApproved('git grep -l pattern')); - - // --open-files-in-pager flag should be blocked - ok(!isAutoApproved('git grep --open-files-in-pager pattern')); - ok(!isAutoApproved('git grep -O pattern')); - }); - - test('should block git grep with combined dangerous flags', () => { - setAutoApproveWithDefaults( - {}, - { - 'git grep': true, - '/^git grep\\b.*-(f|P)\\b/': false, - '/^git grep\\b.*--open-files-in-pager\\b/': false - } - ); - - // Safe git grep commands should still work - ok(isAutoApproved('git grep pattern')); - ok(isAutoApproved('git grep -n -i pattern')); - ok(isAutoApproved('git grep --line-number --ignore-case pattern')); - - // Various combinations of dangerous flags should be blocked - ok(!isAutoApproved('git grep -f patterns.txt -P')); - ok(!isAutoApproved('git grep -P --open-files-in-pager pattern')); - ok(!isAutoApproved('git grep -n -f /etc/passwd --color')); - }); - - test('should handle git grep with complex patterns safely', () => { - setAutoApproveWithDefaults( - {}, - { - 'git grep': true, - '/^git grep\\b.*-(f|P)\\b/': false, - '/^git grep\\b.*--open-files-in-pager\\b/': false - } - ); - - // Complex but safe git grep commands should work - ok(isAutoApproved('git grep -E "function.*\\(.*\\)"')); - ok(isAutoApproved('git grep --extended-regexp "class [A-Z][a-zA-Z]*"')); - ok(isAutoApproved('git grep -G "var [a-z]+" -- "*.js"')); - ok(isAutoApproved('git grep --basic-regexp "TODO:"')); - ok(isAutoApproved('git grep -F "literal.string"')); - ok(isAutoApproved('git grep --fixed-strings "exact match"')); - - // But still block dangerous flags even with complex patterns - ok(!isAutoApproved('git grep -P "complex.*pattern" file.txt')); - ok(!isAutoApproved('git grep -f patterns.txt -- "*.js"')); - }); - }); }); From 261786351d0ae73e501d6c38c7e3dd818e60e99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 9 Sep 2025 15:56:59 +0200 Subject: [PATCH 0115/4355] remove implicit fetch tool URL remembering (#265848) this was even flawed since the implementation talked about `alreadyApprovedDomains` when in fact it was the entire URL that was remembered and there was no way to still fetch the URL without always approving it --- .../electron-browser/tools/fetchPageTool.ts | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts index 2f00d270446..62ff79579e5 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts @@ -5,7 +5,6 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { ResourceSet } from '../../../../../base/common/map.js'; import { extname } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; @@ -38,7 +37,6 @@ export const FetchWebPageToolData: IToolData = { }; export class FetchWebPageTool implements IToolImpl { - private _alreadyApprovedDomains = new ResourceSet(); constructor( @IWebContentExtractorService private readonly _readerModeService: IWebContentExtractorService, @@ -56,12 +54,6 @@ export class FetchWebPageTool implements IToolImpl { }; } - // We approved these via confirmation, so mark them as "approved" in this session - // if they are not approved via the trusted domain service. - for (const uri of webUris.values()) { - this._alreadyApprovedDomains.add(uri); - } - // Get contents from web URIs const webContents = webUris.size > 0 ? await this._readerModeService.extract([...webUris.values()]) : []; @@ -148,8 +140,7 @@ export class FetchWebPageTool implements IToolImpl { } const invalid = [...Array.from(invalidUris), ...additionalInvalidUrls]; - const valid = [...webUris.values(), ...validFileUris]; - const urlsNeedingConfirmation = valid.length > 0 ? valid.filter(url => !this._alreadyApprovedDomains.has(url)) : []; + const urlsNeedingConfirmation = [...webUris.values(), ...validFileUris]; const pastTenseMessage = invalid.length ? invalid.length > 1 @@ -157,7 +148,7 @@ export class FetchWebPageTool implements IToolImpl { ? new MarkdownString( localize( 'fetchWebPage.pastTenseMessage.plural', - 'Fetched {0} resources, but the following were invalid URLs:\n\n{1}\n\n', valid.length, invalid.map(url => `- ${url}`).join('\n') + 'Fetched {0} resources, but the following were invalid URLs:\n\n{1}\n\n', urlsNeedingConfirmation.length, invalid.map(url => `- ${url}`).join('\n') )) // If there is only one invalid URL, show it : new MarkdownString( @@ -169,11 +160,11 @@ export class FetchWebPageTool implements IToolImpl { : new MarkdownString(); const invocationMessage = new MarkdownString(); - if (valid.length > 1) { - pastTenseMessage.appendMarkdown(localize('fetchWebPage.pastTenseMessageResult.plural', 'Fetched {0} resources', valid.length)); - invocationMessage.appendMarkdown(localize('fetchWebPage.invocationMessage.plural', 'Fetching {0} resources', valid.length)); - } else if (valid.length === 1) { - const url = valid[0].toString(); + if (urlsNeedingConfirmation.length > 1) { + pastTenseMessage.appendMarkdown(localize('fetchWebPage.pastTenseMessageResult.plural', 'Fetched {0} resources', urlsNeedingConfirmation.length)); + invocationMessage.appendMarkdown(localize('fetchWebPage.invocationMessage.plural', 'Fetching {0} resources', urlsNeedingConfirmation.length)); + } else if (urlsNeedingConfirmation.length === 1) { + const url = urlsNeedingConfirmation[0].toString(); // If the URL is too long or it's a file url, show it as a link... otherwise, show it as plain text if (url.length > 400 || validFileUris.length === 1) { pastTenseMessage.appendMarkdown(localize({ From 8a48ecc72bda8740554f29da23509e8ba4ce5a48 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Sep 2025 16:15:50 +0200 Subject: [PATCH 0116/4355] chat - towards experimental anonymous access (#265806) --- extensions/vscode-api-tests/package.json | 3 +- package.json | 2 +- .../common/extensionsApiProposals.ts | 3 ++ .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../contrib/chat/browser/chat.contribution.ts | 10 ++++- .../contrib/chat/browser/chatSetup.ts | 34 ++++++++++---- .../chat/common/chatEntitlementService.ts | 45 ++++++++++++++----- .../vscode.proposed.devDeviceId.d.ts | 15 +++++++ 8 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.devDeviceId.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 43583bb8e58..250ec7ae327 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -50,7 +50,8 @@ "treeViewReveal", "tunnels", "workspaceTrust", - "inlineCompletionsAdditions" + "inlineCompletionsAdditions", + "devDeviceId" ], "private": true, "activationEvents": [], diff --git a/package.json b/package.json index b368ed81dc7..2e2633f90d5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "34ee7cb7a51777c9233b9b97a87ed494d4bdeb0a", + "distro": "1c56627fcbb7427c61b3debf10778ac069ba3027", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index cf9a8fd7dc5..a2e111a0a46 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -184,6 +184,9 @@ const _allApiProposals = { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', version: 4 }, + devDeviceId: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.devDeviceId.d.ts', + }, diffCommand: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b0e0d18be0d..0bb808d594a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -384,6 +384,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: env const env: typeof vscode.env = { get machineId() { return initData.telemetryInfo.machineId; }, + get devDeviceId() { + checkProposedApiEnabled(extension, 'devDeviceId'); + return initData.telemetryInfo.devDeviceId; + }, get sessionId() { return initData.telemetryInfo.sessionId; }, get language() { return initData.environment.appLanguage; }, get appName() { return initData.environment.appName; }, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 3af4c960e73..39bb8fd7ed9 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -660,7 +660,15 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize('chat.showAgentSessionsViewDescription', "Controls whether session descriptions are displayed on a second row in the Chat Sessions view."), default: true, - } + }, + 'chat.allowAnonymousAccess': { // TODO@bpasero remove me eventually + type: 'boolean', + default: false, + tags: ['experimental'], + experiment: { + mode: 'auto' + } + }, } }); Registry.as(EditorExtensions.EditorPane).registerEditorPane( diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 417c05b25eb..7815c69b227 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './media/chatSetup.css'; import { $ } from '../../../../base/browser/dom.js'; import { IButton } from '../../../../base/browser/ui/button/button.js'; import { Dialog, DialogContentsAlignment } from '../../../../base/browser/ui/dialog/dialog.js'; @@ -52,7 +53,7 @@ import { AuthenticationSession, IAuthenticationService } from '../../../services import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.js'; import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extensions/browser/extensionUrlHandler.js'; -import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js'; +import { IExtensionService, nullExtensionDescription } from '../../../services/extensions/common/extensions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; @@ -73,7 +74,7 @@ import { ILanguageModelsService } from '../common/languageModels.js'; import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './actions/chatActions.js'; import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; -import './media/chatSetup.css'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; const defaultChat = { @@ -439,7 +440,10 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { - result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ disableChatViewReveal: true /* we are already in a chat context */ }); + result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ + disableChatViewReveal: true, // we are already in a chat context + allowAnonymous: true, // in chat context we can allow anonymous usage (TODO@bpasero make this dependent on terms visibility) + }); } catch (error) { this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`); } finally { @@ -649,7 +653,7 @@ class ChatSetup { this.skipDialogOnce = true; } - async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[] }): Promise { + async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { if (this.pendingRun) { return this.pendingRun; } @@ -663,7 +667,7 @@ class ChatSetup { } } - private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[] }): Promise { + private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { this.context.update({ later: false }); const dialogSkipped = this.skipDialogOnce; @@ -682,6 +686,8 @@ class ChatSetup { let setupStrategy: ChatSetupStrategy; if (!options?.forceSignInDialog && (dialogSkipped || isProUser(this.chatEntitlementService.entitlement) || this.chatEntitlementService.entitlement === ChatEntitlement.Free)) { setupStrategy = ChatSetupStrategy.DefaultSetup; // existing pro/free users setup without a dialog + } else if (options?.allowAnonymous && this.chatEntitlementService.entitlement === ChatEntitlement.Anonymous) { + setupStrategy = ChatSetupStrategy.DefaultSetup; } else { setupStrategy = await this.showDialog(options); } @@ -829,6 +835,8 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService private readonly extensionService: IExtensionService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); @@ -1152,6 +1160,15 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr private async checkExtensionInstallation(context: ChatEntitlementContext): Promise { + // When developing extensions, await registration and then check + if (this.environmentService.isExtensionDevelopment) { + await this.extensionService.whenInstalledExtensionsRegistered(); + if (this.extensionService.extensions.find(ext => ExtensionIdentifier.equals(ext.identifier, defaultChat.chatExtensionId))) { + context.update({ installed: true, disabled: false, untrusted: false }); + return; + } + } + // Await extensions to be ready to be queried await this.extensionsWorkbenchService.queryLocal(); @@ -1461,9 +1478,10 @@ class ChatSetupController extends Disposable { let sessions = session ? [session] : undefined; try { if ( - entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free - !isProUser(entitlement) && // User is not signed up for a Copilot subscription - entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free + entitlement !== ChatEntitlement.Anonymous && // User is not eligible for anonymous access + entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free + !isProUser(entitlement) && // User is not signed up for a Copilot subscription + entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free ) { if (!sessions) { try { diff --git a/src/vs/workbench/services/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts index d01ea08ba42..771ac6b1420 100644 --- a/src/vs/workbench/services/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -43,6 +43,7 @@ export namespace ChatEntitlementContextKeys { export const Entitlement = { signedOut: new RawContextKey('chatEntitlementSignedOut', false, true), // True when user is signed out. + anonymous: new RawContextKey('chatEntitlementAnonymous', false, true), // True when user is an anonymous user. canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user. planFree: new RawContextKey('chatPlanFree', false, true), // True when user is a chat free user. @@ -63,24 +64,26 @@ export namespace ChatEntitlementContextKeys { export const IChatEntitlementService = createDecorator('chatEntitlementService'); export enum ChatEntitlement { + /* Signed out, anonymous */ + Anonymous = -1, /** Signed out */ Unknown = 1, /** Signed in but not yet resolved */ - Unresolved, + Unresolved = 2, /** Signed in and entitled to Free */ - Available, + Available = 3, /** Signed in but not entitled to Free */ - Unavailable, + Unavailable = 4, /** Signed-up to Free */ - Free, + Free = 5, /** Signed-up to Pro */ - Pro, + Pro = 6, /** Signed-up to Pro Plus */ - ProPlus, + ProPlus = 7, /** Signed-up to Business */ - Business, + Business = 8, /** Signed-up to Enterprise */ - Enterprise + Enterprise = 9, } export interface IChatSentiment { @@ -209,6 +212,7 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme ChatEntitlementContextKeys.Entitlement.planFree.key, ChatEntitlementContextKeys.Entitlement.canSignUp.key, ChatEntitlementContextKeys.Entitlement.signedOut.key, + ChatEntitlementContextKeys.Entitlement.anonymous.key, ChatEntitlementContextKeys.Entitlement.organisations.key, ChatEntitlementContextKeys.Entitlement.internal.key, ChatEntitlementContextKeys.Entitlement.sku.key @@ -269,6 +273,8 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme return ChatEntitlement.Available; } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.signedOut.key) === true) { return ChatEntitlement.Unknown; + } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.anonymous.key) === true) { + return ChatEntitlement.Anonymous; } return ChatEntitlement.Unresolved; @@ -1008,10 +1014,13 @@ type ChatEntitlementEvent = { export class ChatEntitlementContext extends Disposable { private static readonly CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY = 'chat.setupContext'; + private static readonly CHAT_DISABLED_CONFIGURATION_KEY = 'chat.disableAIFeatures'; + private static readonly CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY = 'chat.allowAnonymousAccess'; private readonly canSignUpContextKey: IContextKey; private readonly signedOutContextKey: IContextKey; + private readonly anonymousContextKey: IContextKey; private readonly freeContextKey: IContextKey; private readonly proContextKey: IContextKey; @@ -1049,6 +1058,8 @@ export class ChatEntitlementContext extends Disposable { this.canSignUpContextKey = ChatEntitlementContextKeys.Entitlement.canSignUp.bindTo(contextKeyService); this.signedOutContextKey = ChatEntitlementContextKeys.Entitlement.signedOut.bindTo(contextKeyService); + this.anonymousContextKey = ChatEntitlementContextKeys.Entitlement.anonymous.bindTo(contextKeyService); + this.freeContextKey = ChatEntitlementContextKeys.Entitlement.planFree.bindTo(contextKeyService); this.proContextKey = ChatEntitlementContextKeys.Entitlement.planPro.bindTo(contextKeyService); this.proPlusContextKey = ChatEntitlementContextKeys.Entitlement.planProPlus.bindTo(contextKeyService); @@ -1072,7 +1083,7 @@ export class ChatEntitlementContext extends Disposable { private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY)) { + if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) || e.affectsConfiguration(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY)) { this.updateContext(); } })); @@ -1080,8 +1091,19 @@ export class ChatEntitlementContext extends Disposable { private withConfiguration(state: IChatEntitlementContextState): IChatEntitlementContextState { if (this.configurationService.getValue(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) === true) { - // Setting always wins: if AI is disabled, set `hidden: true` - return { ...state, hidden: true }; + return { + ...state, + hidden: true // Setting always wins: if AI is disabled, set `hidden: true` + }; + } + + if (this.configurationService.getValue(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) === true) { + let entitlement = state.entitlement; + if (entitlement === ChatEntitlement.Unknown /*&& !state.registered TODO@bpasero revisit */) { + entitlement = ChatEntitlement.Anonymous; // enable `anonymous` based on exp config if entitlement is unknown and user never signed up + } + + return { ...state, entitlement }; } return state; @@ -1148,6 +1170,7 @@ export class ChatEntitlementContext extends Disposable { const state = this.withConfiguration(this._state); this.signedOutContextKey.set(state.entitlement === ChatEntitlement.Unknown); + this.anonymousContextKey.set(state.entitlement === ChatEntitlement.Anonymous); this.canSignUpContextKey.set(state.entitlement === ChatEntitlement.Available); this.freeContextKey.set(state.entitlement === ChatEntitlement.Free); diff --git a/src/vscode-dts/vscode.proposed.devDeviceId.d.ts b/src/vscode-dts/vscode.proposed.devDeviceId.d.ts new file mode 100644 index 00000000000..0f9a62fc7db --- /dev/null +++ b/src/vscode-dts/vscode.proposed.devDeviceId.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export namespace env { + + /** + * An alternative unique identifier for the computer. + */ + export const devDeviceId: string; + } +} From 67e8752460ab5162d6e3bc81bbfd7cec6a18fc4a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 9 Sep 2025 16:25:46 +0200 Subject: [PATCH 0117/4355] update --- src/vs/base/common/yaml.ts | 51 +++++++++++++++------------- src/vs/base/test/common/yaml.test.ts | 9 ++--- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/vs/base/common/yaml.ts b/src/vs/base/common/yaml.ts index b6b977e5897..490d068e0f0 100644 --- a/src/vs/base/common/yaml.ts +++ b/src/vs/base/common/yaml.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** - * Parses a simplified YAML-like input from an iterable of strings (lines). + * Parses a simplified YAML-like input from a single string. * Supports objects, arrays, primitive types (string, number, boolean, null). * Tracks positions for error reporting and node locations. * @@ -15,13 +15,18 @@ * - No special handling for escape sequences in strings * - Indentation must be consistent (spaces only, no tabs) * - * @param input Iterable of strings representing lines of the YAML-like input + * Notes: + * - New line separators can be either "\n" or "\r\n". The input string is split into lines internally. + * + * @param input A string containing the YAML-like input * @param errors Array to collect parsing errors * @param options Parsing options * @returns The parsed representation (ObjectNode, ArrayNode, or primitive node) */ -export function parse(input: Iterable, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined { - const lines = Array.from(input); +export function parse(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined { + // Normalize both LF and CRLF by splitting on either; CR characters are not retained as part of line text. + // This keeps the existing line/character based lexer logic intact. + const lines = input.length === 0 ? [] : input.split(/\r\n|\n/); const parser = new YamlParser(lines, errors, options); return parser.parse(); } @@ -254,6 +259,8 @@ class YamlParser { private lexer: YamlLexer; private errors: YamlParseError[]; private options: ParseOptions; + // Track nesting level of flow (inline) collections '[' ']' '{' '}' + private flowLevel: number = 0; constructor(lines: string[], errors: YamlParseError[], options: ParseOptions) { this.lexer = new YamlLexer(lines); @@ -317,51 +324,43 @@ class YamlParser { let endPos = start; // Helper function to check for value terminators - const isTerminator = (char: string): boolean => - char === '#' || char === ',' || char === ']' || char === '}'; + const isTerminator = (char: string): boolean => { + if (char === '#') { return true; } + // Comma, ']' and '}' only terminate inside flow collections + if (this.flowLevel > 0 && (char === ',' || char === ']' || char === '}')) { return true; } + return false; + }; // Handle opening quote that might not be closed const firstChar = this.lexer.getCurrentChar(); if (firstChar === '"' || firstChar === `'`) { value += this.lexer.advance(); endPos = this.lexer.getCurrentPosition(); - - // Continue until we find closing quote or terminator while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { const char = this.lexer.getCurrentChar(); - if (char === firstChar || isTerminator(char)) { break; } - value += this.lexer.advance(); endPos = this.lexer.getCurrentPosition(); } } else { - // Regular unquoted value while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { const char = this.lexer.getCurrentChar(); - if (isTerminator(char)) { break; } - value += this.lexer.advance(); endPos = this.lexer.getCurrentPosition(); } } - - value = value.trim(); - - // Adjust end position for trimmed value - if (value.length === 0) { - endPos = start; - } else { - endPos = createPosition(start.line, start.character + value.length); + const trimmed = value.trimEnd(); + const diff = value.length - trimmed.length; + if (diff) { + endPos = createPosition(start.line, endPos.character - diff); } - - // Return appropriate node type based on value - return this.createValueNode(value, start, endPos); + const finalValue = (firstChar === '"' || firstChar === `'`) ? trimmed.substring(1) : trimmed; + return this.createValueNode(finalValue, start, endPos); } private createValueNode(value: string, start: Position, end: Position): YamlNode { @@ -395,6 +394,7 @@ class YamlParser { parseInlineArray(): YamlArrayNode { const start = this.lexer.getCurrentPosition(); this.lexer.advance(); // Skip '[' + this.flowLevel++; const items: YamlNode[] = []; @@ -426,12 +426,14 @@ class YamlParser { } const end = this.lexer.getCurrentPosition(); + this.flowLevel--; return createArrayNode(items, start, end); } parseInlineObject(): YamlObjectNode { const start = this.lexer.getCurrentPosition(); this.lexer.advance(); // Skip '{' + this.flowLevel++; const properties: { key: YamlStringNode; value: YamlNode }[] = []; @@ -494,6 +496,7 @@ class YamlParser { } const end = this.lexer.getCurrentPosition(); + this.flowLevel--; return createObjectNode(properties, start, end); } diff --git a/src/vs/base/test/common/yaml.test.ts b/src/vs/base/test/common/yaml.test.ts index 49ddbd2596f..cba621bf023 100644 --- a/src/vs/base/test/common/yaml.test.ts +++ b/src/vs/base/test/common/yaml.test.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual, strictEqual, ok } from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; import { parse, ParseOptions, YamlParseError, Position, YamlNode } from '../../common/yaml.js'; - +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; function assertValidParse(input: string[], expected: YamlNode, expectedErrors: YamlParseError[], options?: ParseOptions): void { const errors: YamlParseError[] = []; - const actual1 = parse(input, errors, options); + const text = input.join('\n'); + const actual1 = parse(text, errors, options); deepStrictEqual(actual1, expected); deepStrictEqual(errors, expectedErrors); } @@ -44,6 +44,7 @@ suite('YAML Parser', () => { assertValidParse(['A Developer'], { type: 'string', start: pos(0, 0), end: pos(0, 11), value: 'A Developer' }, []); assertValidParse(['\'A Developer\''], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); assertValidParse(['"A Developer"'], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); + assertValidParse(['*.js,*.ts'], { type: 'string', start: pos(0, 0), end: pos(0, 9), value: '*.js,*.ts' }, []); }); }); @@ -893,7 +894,7 @@ suite('YAML Parser', () => { const start = Date.now(); const errors: YamlParseError[] = []; - const result = parse(lines, errors); + const result = parse(lines.join('\n'), errors); const duration = Date.now() - start; ok(result); From 0260c7facbd742fe1edbdeaaa6ca246da3d92519 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:28:17 -0700 Subject: [PATCH 0118/4355] Fix disposable leak in terminal backends Fixes #265797 --- .../workbench/contrib/terminal/browser/remoteTerminalBackend.ts | 1 + .../contrib/terminal/electron-browser/localTerminalBackend.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index 03c08c8151d..9706925925f 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -93,6 +93,7 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack const pty = this._ptys.get(e.id); if (pty) { pty.handleExit(e.event); + pty.dispose(); this._ptys.delete(e.id); } }); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts index 0be06471a89..bac58b90108 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts @@ -152,6 +152,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke const pty = this._ptys.get(e.id); if (pty) { pty.handleExit(e.event); + pty.dispose(); this._ptys.delete(e.id); } })); From 8ee1250102e471d4fa20c8bbf32139e39be60a81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:28:59 -0700 Subject: [PATCH 0119/4355] Fix leak in SeamlessRelaunchDataFilter --- .../browser/terminalProcessManager.ts | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index d45bb91bc1b..69a34e8df01 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from '../../../../base/common/event.js'; -import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, dispose, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { IProcessEnvironment, isMacintosh, isWindows, OperatingSystem, OS } from '../../../../base/common/platform.js'; import { URI } from '../../../../base/common/uri.js'; @@ -728,9 +728,9 @@ const enum SeamlessRelaunchConstants { class SeamlessRelaunchDataFilter extends Disposable { private _firstRecorder?: TerminalRecorder; private _secondRecorder?: TerminalRecorder; - private _firstDisposable?: IDisposable; - private _secondDisposable?: IDisposable; - private _dataListener?: IDisposable; + private _firstDisposable = this._register(new MutableDisposable()); + private _secondDisposable = this._register(new MutableDisposable()); + private _dataListener = this._register(new MutableDisposable()); private _activeProcess?: ITerminalChildProcess; private _disableSeamlessRelaunch: boolean = false; @@ -747,7 +747,7 @@ class SeamlessRelaunchDataFilter extends Disposable { newProcess(process: ITerminalChildProcess, reset: boolean) { // Stop listening to the old process and trigger delayed shutdown (for hang issue #71966) - this._dataListener?.dispose(); + this._dataListener.clear() this._activeProcess?.shutdown(false); this._activeProcess = process; @@ -757,12 +757,11 @@ class SeamlessRelaunchDataFilter extends Disposable { // - this is not a reset, so seamless relaunch isn't necessary // - seamless relaunch is disabled because the terminal has accepted input if (!this._firstRecorder || !reset || this._disableSeamlessRelaunch) { - this._firstDisposable?.dispose(); - [this._firstRecorder, this._firstDisposable] = this._createRecorder(process); + [this._firstRecorder, this._firstDisposable.value] = this._createRecorder(process); if (this._disableSeamlessRelaunch && reset) { this._onProcessData.fire('\x1bc'); } - this._dataListener = process.onProcessData(e => this._onProcessData.fire(e)); + this._dataListener.value = process.onProcessData(e => this._onProcessData.fire(e)); this._disableSeamlessRelaunch = false; return; } @@ -775,11 +774,11 @@ class SeamlessRelaunchDataFilter extends Disposable { this._swapTimeout = mainWindow.setTimeout(() => this.triggerSwap(), SeamlessRelaunchConstants.SwapWaitMaximumDuration); // Pause all outgoing data events - this._dataListener?.dispose(); + this._dataListener.clear(); - this._firstDisposable?.dispose(); + this._firstDisposable.clear(); const recorder = this._createRecorder(process); - [this._secondRecorder, this._secondDisposable] = recorder; + [this._secondRecorder, this._secondDisposable.value] = recorder; } /** @@ -808,7 +807,7 @@ class SeamlessRelaunchDataFilter extends Disposable { // Clear the first recorder if no second process was attached before the swap trigger if (!this._secondRecorder) { this._firstRecorder = undefined; - this._firstDisposable?.dispose(); + this._firstDisposable.clear(); return; } @@ -826,13 +825,11 @@ class SeamlessRelaunchDataFilter extends Disposable { } // Set up the new data listener - this._dataListener?.dispose(); - this._dataListener = this._activeProcess!.onProcessData(e => this._onProcessData.fire(e)); + this._dataListener.value = this._activeProcess!.onProcessData(e => this._onProcessData.fire(e)); // Replace first recorder with second this._firstRecorder = this._secondRecorder; - this._firstDisposable?.dispose(); - this._firstDisposable = this._secondDisposable; + this._firstDisposable.value = this._secondDisposable.value; this._secondRecorder = undefined; } @@ -843,9 +840,9 @@ class SeamlessRelaunchDataFilter extends Disposable { } // Stop recording this._firstRecorder = undefined; - this._firstDisposable?.dispose(); + this._firstDisposable.clear(); this._secondRecorder = undefined; - this._secondDisposable?.dispose(); + this._secondDisposable.clear(); } private _createRecorder(process: ITerminalChildProcess): [TerminalRecorder, IDisposable] { From 9a87c8e34e7d5b8c1224ca4cbe25293c2d4dd9e6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 9 Sep 2025 16:29:32 +0200 Subject: [PATCH 0120/4355] update --- .../chat/common/promptSyntax/service/newPromptsParser.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 7a5396f47af..716494a36c3 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Iterable } from '../../../../../../base/common/iterator.js'; import { dirname, resolvePath } from '../../../../../../base/common/resources.js'; import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -65,7 +64,7 @@ export class PromptHeader { public getParsedHeader(): ParsedHeader { if (this._parsed === undefined) { const errors: YamlParseError[] = []; - const lines = Iterable.map(Iterable.slice(this.linesWithEOL, this.range.startLineNumber - 1, this.range.endLineNumber - 1), line => line.replace(/[\r\n]+$/, '')); + const lines = this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join(''); const node = parse(lines, errors); const attributes = []; if (node?.type === 'object') { From 052faa76a7271d4a4c743b692386064d939e7d87 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:46:41 -0700 Subject: [PATCH 0121/4355] Fix lint issues --- .../contrib/terminal/browser/terminalProcessManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 69a34e8df01..fd54ece4b0b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -728,9 +728,9 @@ const enum SeamlessRelaunchConstants { class SeamlessRelaunchDataFilter extends Disposable { private _firstRecorder?: TerminalRecorder; private _secondRecorder?: TerminalRecorder; - private _firstDisposable = this._register(new MutableDisposable()); - private _secondDisposable = this._register(new MutableDisposable()); - private _dataListener = this._register(new MutableDisposable()); + private readonly _firstDisposable = this._register(new MutableDisposable()); + private readonly _secondDisposable = this._register(new MutableDisposable()); + private readonly _dataListener = this._register(new MutableDisposable()); private _activeProcess?: ITerminalChildProcess; private _disableSeamlessRelaunch: boolean = false; @@ -747,7 +747,7 @@ class SeamlessRelaunchDataFilter extends Disposable { newProcess(process: ITerminalChildProcess, reset: boolean) { // Stop listening to the old process and trigger delayed shutdown (for hang issue #71966) - this._dataListener.clear() + this._dataListener.clear(); this._activeProcess?.shutdown(false); this._activeProcess = process; From 771d8898e3a55f8a577910d8370dc0e3368d2c2d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:55:05 +0200 Subject: [PATCH 0122/4355] Chat - add the option to programatically attach a history item change (#265855) --- .../chat/browser/actions/chatActions.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index ae7fb91c6cb..7c18f199238 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -77,6 +77,9 @@ import { VIEWLET_ID } from '../chatSessions.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; +import { ISCMService } from '../../../scm/common/scm.js'; +import { ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; +import { basename } from '../../../../../base/common/resources.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -112,6 +115,10 @@ export interface IChatViewOpenOptions { * A list of file URIs to attach to the chat as context. */ attachFiles?: (URI | { uri: URI; range: IRange })[]; + /** + * A list of source control history item changes to attach to the chat as context. + */ + attachHistoryItemChanges?: { uri: URI; historyItemId: string }[]; /** * The mode ID or name to open the chat in. */ @@ -184,6 +191,7 @@ abstract class OpenChatGlobalAction extends Action2 { const chatModeService = accessor.get(IChatModeService); const fileService = accessor.get(IFileService); const languageModelService = accessor.get(ILanguageModelsService); + const scmService = accessor.get(ISCMService); let chatWidget = widgetService.lastFocusedWidget; // When this was invoked to switch to a mode via keybinding, and some chat widget is focused, use that one. @@ -237,6 +245,28 @@ abstract class OpenChatGlobalAction extends Action2 { } } } + if (opts?.attachHistoryItemChanges) { + for (const historyItemChange of opts.attachHistoryItemChanges) { + const repository = scmService.getRepository(URI.file(historyItemChange.uri.path)); + const historyProvider = repository?.provider.historyProvider.get(); + if (!historyProvider) { + continue; + } + + const historyItem = await historyProvider.resolveHistoryItem(historyItemChange.historyItemId); + if (!historyItem) { + continue; + } + + chatWidget.attachmentModel.addContext({ + id: historyItemChange.uri.toString(), + name: `${basename(historyItemChange.uri)}`, + value: historyItemChange.uri, + historyItem: historyItem, + kind: 'scmHistoryItemChange' + } satisfies ISCMHistoryItemChangeVariableEntry); + } + } let resp: Promise | undefined; From 48903d5e23ea4c13444dc764777247ce8d7fd4df Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Sep 2025 17:43:29 +0200 Subject: [PATCH 0123/4355] Adopts AsyncIterableProducer --- .../browser/model/provideInlineCompletions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 896bad73851..dc113afdc5b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { assertNever } from '../../../../../base/common/assert.js'; -import { AsyncIterableObject } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { onUnexpectedExternalError } from '../../../../../base/common/errors.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; @@ -120,7 +120,7 @@ export function provideInlineCompletions( } }); - const inlineCompletionLists = AsyncIterableObject.fromPromisesResolveOrder(providers.map(p => queryProvider.get(p))).filter(isDefined); + const inlineCompletionLists = AsyncIterableProducer.fromPromisesResolveOrder(providers.map(p => queryProvider.get(p))).filter(isDefined); return { get didAllProvidersReturn() { return runningCount === 0; }, @@ -154,7 +154,7 @@ export interface IInlineCompletionProviderResult { cancelAndDispose(reason: InlineCompletionsDisposeReason): void; - lists: AsyncIterableObject; + lists: AsyncIterableProducer; } function toInlineSuggestData( From a17d7f557d99ee551fbda1367487665b92f904fb Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Sep 2025 17:55:29 +0200 Subject: [PATCH 0124/4355] Fixes https://github.com/microsoft/vscode/issues/265125 --- .../controller/inlineCompletionsController.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index d9b4b40d011..479d7058565 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -316,14 +316,18 @@ export class InlineCompletionsController extends Disposable { return {}; }), async (_value, _, _deltas, store) => { /** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */ - const model = this.model.get(); - const state = model?.state.get(); + let model = this.model.get(); + let state = model?.state.get(); if (!state || !model) { return; } - const lineText = state.kind === 'ghostText' ? model.textModel.getLineContent(state.primaryGhostText.lineNumber) : ''; await timeout(50, cancelOnDispose(store)); await waitForState(this._suggestWidgetAdapter.selectedItem, isUndefined, () => false, cancelOnDispose(store)); - await this._accessibilitySignalService.playSignal(state.kind === 'ghostText' ? AccessibilitySignal.inlineSuggestion : AccessibilitySignal.nextEditSuggestion); + + model = this.model.get(); + state = model?.state.get(); + if (!state || !model) { return; } + const lineText = state.kind === 'ghostText' ? model.textModel.getLineContent(state.primaryGhostText.lineNumber) : ''; + this._accessibilitySignalService.playSignal(state.kind === 'ghostText' ? AccessibilitySignal.inlineSuggestion : AccessibilitySignal.nextEditSuggestion); if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { if (state.kind === 'ghostText') { From 537fee68e0e4169545290c2257f1c69c01da4f4d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 9 Sep 2025 17:56:40 +0200 Subject: [PATCH 0125/4355] Fixes https://github.com/microsoft/monaco-editor/issues/4984 (#265823) Fixes https://github.com/microsoft/monaco-editor/issues/4984 --- .../browser/quickInput/standaloneQuickInputService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 142d19b3f08..dddb6f8f059 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -5,7 +5,7 @@ import './standaloneQuickInput.css'; import { Event } from '../../../../base/common/event.js'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../browser/editorBrowser.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../browser/editorBrowser.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; @@ -210,7 +210,7 @@ export class QuickInputEditorWidget implements IOverlayWidget { } getPosition(): IOverlayWidgetPosition | null { - return { preference: OverlayWidgetPositionPreference.TOP_CENTER }; + return { preference: { top: 0, left: 0 } }; } dispose(): void { From 51c2214663cb23a2e3ad9eb4057354bfedd87252 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Tue, 9 Sep 2025 17:58:01 +0200 Subject: [PATCH 0126/4355] fix: memory leak in ReplAccessibilityAnnouncer (#264937) fix: memory leak in repl accessibility announcer --- .../contrib/debug/common/replAccessibilityAnnouncer.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts b/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts index 6df698af1e3..888cde4844d 100644 --- a/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts +++ b/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; @@ -18,11 +18,13 @@ export class ReplAccessibilityAnnouncer extends Disposable implements IWorkbench ) { super(); const viewModel = debugService.getViewModel(); + const mutableDispoable = this._register(new MutableDisposable()); this._register(viewModel.onDidFocusSession((session) => { + mutableDispoable.clear(); if (!session) { return; } - this._register(session.onDidChangeReplElements((element) => { + mutableDispoable.value = session.onDidChangeReplElements((element) => { if (!element || !('originalExpression' in element)) { // element was removed or hasn't been resolved yet return; @@ -30,7 +32,7 @@ export class ReplAccessibilityAnnouncer extends Disposable implements IWorkbench const value = element.toString(); accessibilityService.status(value); logService.trace('ReplAccessibilityAnnouncer#onDidChangeReplElements', element.originalExpression + ': ' + value); - })); + }); })); } } From 6068add600b9748ca776674887f7ac32fb3b103b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Sep 2025 09:14:55 -0700 Subject: [PATCH 0127/4355] debug: fix incorrect thread state if "continued" arrives too soon after a "stopped" (#265755) * debug: fix incorrect thread state if "continued" arrives too soon after a "stopped" Fixes #238591 * fixup rm unnecessary change --- .../contrib/debug/browser/debugSession.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 0d5522e3a52..f164c7bbc3b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -1129,25 +1129,38 @@ export class DebugSession implements IDebugSession { } })); - this.rawListeners.add(this.raw.onDidContinued(event => { + this.rawListeners.add(this.raw.onDidContinued(async event => { const allThreads = event.body.allThreadsContinued !== false; - statusQueue.cancel(allThreads ? undefined : [event.body.threadId]); + let affectedThreads: number[] | Promise; + if (!allThreads) { + affectedThreads = [event.body.threadId]; + if (this.threadIds.includes(event.body.threadId)) { + affectedThreads = [event.body.threadId]; + } else { + this.fetchThreadsScheduler?.cancel(); + affectedThreads = this.fetchThreads().then(() => [event.body.threadId]); + } + } else if (this.fetchThreadsScheduler?.isScheduled()) { + this.fetchThreadsScheduler.cancel(); + affectedThreads = this.fetchThreads().then(() => this.threadIds); + } else { + affectedThreads = this.threadIds; + } - const threadId = allThreads ? undefined : event.body.threadId; - if (typeof threadId === 'number') { + statusQueue.cancel(allThreads ? undefined : [event.body.threadId]); + await statusQueue.run(affectedThreads, threadId => { this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId); const tokens = this.cancellationMap.get(threadId); this.cancellationMap.delete(threadId); tokens?.forEach(t => t.dispose(true)); - } else { - this.stoppedDetails = []; - this.cancelAllRequests(); - } - this.lastContinuedThreadId = threadId; + this.model.clearThreads(this.getId(), false, threadId); + return Promise.resolve(); + }); + // We need to pass focus to other sessions / threads with a timeout in case a quick stop event occurs #130321 + this.lastContinuedThreadId = allThreads ? undefined : event.body.threadId; this.passFocusScheduler.schedule(); - this.model.clearThreads(this.getId(), false, threadId); this._onDidChangeState.fire(); })); @@ -1598,7 +1611,7 @@ export class ThreadStatusScheduler extends Disposable { * Runs the operation. * If thread is undefined it affects all threads. */ - public async run(threadIdsP: Promise, operation: (threadId: number, ct: CancellationToken) => Promise) { + public async run(threadIdsP: Promise | number[], operation: (threadId: number, ct: CancellationToken) => Promise) { const cancelledWhileLookingUpThreads = new Set(); this.pendingCancellations.push(cancelledWhileLookingUpThreads); const threadIds = await threadIdsP; From 1355ffc486eb4a1a03a6c62ae70a2f7d215dbbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Tue, 9 Sep 2025 18:32:55 +0200 Subject: [PATCH 0128/4355] fix: race condition on supported task types (#265847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Mangeonjean --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index a004d486efb..69054aa1ad9 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -432,7 +432,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // update tasks so an incomplete list isn't returned when getWorkspaceTasks is called this._workspaceTasksPromise = undefined; this._onDidRegisterSupportedExecutions.fire(); - if (Platform.isWeb || (custom && shell && process)) { + if (ServerlessWebContext.getValue(this._contextKeyService) || (custom && shell && process)) { this._onDidRegisterAllSupportedExecutions.fire(); } } From b63bcf97278d91513fdbcb7c88142c0832e46636 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:40:48 -0700 Subject: [PATCH 0129/4355] Marking a few more events in vscode.d.ts readonly These should only be used for registration, never overwritten --- src/vscode-dts/vscode.d.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 0f4fa464aaf..0cbebceee1d 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1666,7 +1666,7 @@ declare module 'vscode' { /** * An {@link Event} which fires upon cancellation. */ - onCancellationRequested: Event; + readonly onCancellationRequested: Event; } /** @@ -8603,7 +8603,7 @@ declare module 'vscode' { /** * Fires when a secret is stored or deleted. */ - onDidChange: Event; + readonly onDidChange: Event; } /** @@ -13069,7 +13069,7 @@ declare module 'vscode' { * (Examples include: an explicit call to {@link QuickInput.hide}, * the user pressing Esc, some other input UI opening, etc.) */ - onDidHide: Event; + readonly onDidHide: Event; /** * Dispose of this input UI and any associated resources. If it is still @@ -18184,7 +18184,7 @@ declare module 'vscode' { * Fired when a user has changed whether this is a default profile. The * event contains the new value of {@link isDefault} */ - onDidChangeDefault: Event; + readonly onDidChangeDefault: Event; /** * Whether this profile supports continuous running of requests. If so, @@ -18563,7 +18563,7 @@ declare module 'vscode' { * An event fired when the editor is no longer interested in data * associated with the test run. */ - onDidDispose: Event; + readonly onDidDispose: Event; } /** @@ -19639,7 +19639,7 @@ declare module 'vscode' { * The passed {@link ChatResultFeedback.result result} is guaranteed to have the same properties as the result that was * previously returned from this chat participant's handler. */ - onDidReceiveFeedback: Event; + readonly onDidReceiveFeedback: Event; /** * Dispose this participant and free resources. @@ -20678,7 +20678,7 @@ declare module 'vscode' { /** * An event that fires when access information changes. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Checks if a request can be made to a language model. @@ -20867,6 +20867,7 @@ declare module 'vscode' { * Options to hint at how many tokens the tool should return in its response, and enable the tool to count tokens * accurately. */ + // TODO api: how does this work if the model also has a countTokens? tokenizationOptions?: LanguageModelToolTokenizationOptions; } @@ -20933,6 +20934,7 @@ declare module 'vscode' { * * The provided {@link LanguageModelToolInvocationOptions.input} has been validated against the declared schema. */ + // TODO:API Should model always be required here? invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken): ProviderResult; /** From 4d08555278c9db162b659730dd1a0197a0c6cdcb Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:45:32 -0700 Subject: [PATCH 0130/4355] Bump extension milestones --- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 2 +- .vscode/notebooks/verification.github-issues | 2 +- .vscode/notebooks/vscode-dev.github-issues | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 13759d8256e..990ef26421f 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"August 2025\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"September 2025\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index ef3188d2c48..fde19af907b 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"August 2025\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"September 2025\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 55dacefaffc..4b07163ba65 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-evaluation repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"August 2025\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-evaluation repo:microsoft/vscode-copilot-issues\n\n$MILESTONE=milestone:\"September 2025\"\n\n$MINE=assignee:@me" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 226a6e653eb..c2a10646be8 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"August 2025\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"September 2025\"\n" }, { "kind": 1, diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 81ab3dbca2e..4176ec6e30b 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"August 2025\"\n$closedRecently=closed:>2023-09-29" + "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"September 2025\"\n$closedRecently=closed:>2023-09-29" }, { "kind": 1, diff --git a/.vscode/notebooks/vscode-dev.github-issues b/.vscode/notebooks/vscode-dev.github-issues index bbdbfc4f79f..efa2935ac05 100644 --- a/.vscode/notebooks/vscode-dev.github-issues +++ b/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"August 2025\"" + "value": "$milestone=milestone:\"September 2025\"" }, { "kind": 1, From 9aa74d89c9a4e3f3a29216bc9bfdda5c120338e0 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 9 Sep 2025 09:51:37 -0700 Subject: [PATCH 0131/4355] prevent opening a new chat session if it isn't already active (#265753) --- .../contrib/chat/browser/actions/chatMoveActions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 7e1fd019851..df17e6dc252 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -122,6 +122,12 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew } const sessionId = widget.viewModel.sessionId; + const existingWidget = widgetService.getWidgetBySessionId(sessionId); + if (!existingWidget) { + // Do NOT attempt to open a session that isn't already open since we cannot guarantee its state. + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + return; + } const viewState = widget.getViewState(); widget.clear(); From 97b2c007cd5c795d3bd423ced0e75537a888bd72 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:08:41 -0700 Subject: [PATCH 0132/4355] Migrate fully of moduleResolution node10 Fixes #265420 --- .eslint-plugin-local/index.js | 4 ++-- .../markdown-language-features/notebook/tsconfig.json | 4 +--- extensions/microsoft-authentication/tsconfig.json | 6 ------ src/tsconfig.monaco.json | 5 ++--- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.eslint-plugin-local/index.js b/.eslint-plugin-local/index.js index 4f34ba08012..ad00191fb6f 100644 --- a/.eslint-plugin-local/index.js +++ b/.eslint-plugin-local/index.js @@ -10,8 +10,8 @@ require('ts-node').register({ experimentalResolver: true, transpileOnly: true, compilerOptions: { - "moduleResolution": "node", - "ignoreDeprecations": "6.0", + module: 'nodenext', + moduleResolution: 'nodenext', } }); diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index 48033ed968a..ec71045301f 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -3,10 +3,8 @@ "compilerOptions": { "outDir": "./dist/", "jsx": "react", - "moduleResolution": "Node", - "ignoreDeprecations": "6.0", + "module": "esnext", "allowSyntheticDefaultImports": true, - "module": "es2020", "lib": [ "ES2024", "DOM", diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 5dd64b21dd9..58a583fb0f1 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -2,9 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "baseUrl": ".", - "module": "commonjs", - "moduleResolution": "node", - "ignoreDeprecations": "6.0", "noFallthroughCasesInSwitch": true, "noUnusedLocals": false, "outDir": "dist", @@ -16,9 +13,6 @@ "WebWorker" ] }, - "exclude": [ - "node_modules" - ], "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 669abb9c332..b9e497bf275 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -8,9 +8,8 @@ "wicg-file-system-access" ], "paths": {}, - "module": "amd", - "moduleResolution": "node", - "ignoreDeprecations": "6.0", + "module": "esnext", + "moduleResolution": "bundler", "removeComments": false, "preserveConstEnums": true, "target": "ES2022", From 8b5b18d87cfc2b3ca32a37851761368cc06f383f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 15:05:54 -0400 Subject: [PATCH 0133/4355] Indicate inline chat status to screen reader users (#265735) fix #265734 --- .../contrib/inlineChat/browser/inlineChatController.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 18696e2d01d..242d58be227 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -66,6 +66,7 @@ import { InlineChatError } from './inlineChatSessionServiceImpl.js'; import { HunkAction, IEditObserver, IInlineChatMetadata, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; +import { alert } from '../../../../base/browser/ui/aria/aria.js'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -830,15 +831,16 @@ export class InlineChatController1 implements IEditorContribution { if (response.result?.errorDetails) { // error -> no message, errors are shown with the request - + alert(response.result.errorDetails.message); } else if (response.response.value.length === 0) { // empty -> show message const status = localize('empty', "No results, please refine your input and try again"); this._ui.value.widget.updateStatus(status, { classes: ['warn'] }); - + alert(status); } else { // real response -> no message this._ui.value.widget.updateStatus(''); + alert('Response was empty'); } const position = await this._strategy.renderChanges(); From f032501394abc09731ec5f1503682db908b62f3c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 16:06:53 -0400 Subject: [PATCH 0134/4355] fix bug where prompt could occur repeatedly and be sent to the wrong terminal (#265895) fix #264656 --- .../browser/tools/monitoring/outputMonitor.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index d4d8d7de181..87cdb99d2de 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -408,6 +408,9 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { 'prompt' in obj && isString(obj.prompt) && 'options' in obj ) { + if (this._lastPrompt === obj.prompt) { + return; + } if (Array.isArray(obj.options) && obj.options.every(isString)) { return { prompt: obj.prompt, options: obj.options }; } else if (isObject(obj.options) && Object.values(obj.options).every(isString)) { @@ -446,10 +449,6 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return undefined; } - if (this._lastPrompt === prompt) { - return; - } - this._lastPromptMarker = currentMarker; this._lastPrompt = prompt; From 44954a3fd636fe79730a6a9fe5823a47094f1869 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:12:34 -0700 Subject: [PATCH 0135/4355] Remove todo comments --- src/vscode-dts/vscode.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 0cbebceee1d..bf5a6ae00c4 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -20867,7 +20867,6 @@ declare module 'vscode' { * Options to hint at how many tokens the tool should return in its response, and enable the tool to count tokens * accurately. */ - // TODO api: how does this work if the model also has a countTokens? tokenizationOptions?: LanguageModelToolTokenizationOptions; } @@ -20934,7 +20933,6 @@ declare module 'vscode' { * * The provided {@link LanguageModelToolInvocationOptions.input} has been validated against the declared schema. */ - // TODO:API Should model always be required here? invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken): ProviderResult; /** From 5f5e9af13d2e4f3af0ab0ff9c3c7d5d55e098e33 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 17:03:40 -0400 Subject: [PATCH 0136/4355] add prompt for input was shown telemetry, consolidate task tool telemetry (#265929) --- .../browser/runInTerminalToolTelemetry.ts | 4 +++ .../chatAgentTools/browser/taskHelpers.ts | 4 ++- .../browser/tools/monitoring/outputMonitor.ts | 7 ++++- .../browser/tools/runInTerminalTool.ts | 2 ++ .../tools/task/createAndRunTaskTool.ts | 25 +++--------------- .../browser/tools/task/getTaskOutputTool.ts | 23 +++------------- .../browser/tools/task/runTaskTool.ts | 23 +++------------- .../browser/tools/task/taskToolsTelemetry.ts | 26 +++++++++++++++++++ 8 files changed, 51 insertions(+), 63 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts index 727689adfcc..fc826746258 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts @@ -106,6 +106,7 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: number | undefined; inputToolManualRejectCount: number | undefined; inputToolManualChars: number | undefined; + inputToolManualShownCount: number | undefined; }) { type TelemetryEvent = { terminalSessionId: string; @@ -129,6 +130,7 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; + inputToolManualShownCount: number; }; type TelemetryClassification = { owner: 'tyriar'; @@ -155,6 +157,7 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually accepted a detected suggestion' }; inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of a suggestion' }; + inputToolManualShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to manually accept an input suggestion' }; }; this._telemetryService.publicLog2('toolUse.runInTerminal', { terminalSessionId: instance.sessionId, @@ -178,6 +181,7 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: state.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: state.inputToolManualRejectCount ?? 0, inputToolManualChars: state.inputToolManualChars ?? 0, + inputToolManualShownCount: state.inputToolManualShownCount ?? 0 }); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index b7c17114899..d7a90539e28 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -164,8 +164,9 @@ export async function collectTerminalResults( inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; + inputToolManualShownCount: number; }>> { - const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number }> = []; + const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; inputToolManualShownCount: number }> = []; if (token.isCancellationRequested) { return results; } @@ -191,6 +192,7 @@ export async function collectTerminalResults( inputToolManualAcceptCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualRejectCount ?? 0, inputToolManualChars: outputMonitor.outputMonitorTelemetryCounters.inputToolManualChars ?? 0, + inputToolManualShownCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualShownCount ?? 0, }); } return results; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 87cdb99d2de..c404b3774de 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -37,6 +37,7 @@ export interface IOutputMonitorTelemetryCounters { inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; + inputToolManualShownCount: number; } export class OutputMonitor extends Disposable implements IOutputMonitor { @@ -55,7 +56,8 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { private readonly _outputMonitorTelemetryCounters: IOutputMonitorTelemetryCounters = { inputToolManualAcceptCount: 0, inputToolManualRejectCount: 0, - inputToolManualChars: 0 + inputToolManualChars: 0, + inputToolManualShownCount: 0 }; get outputMonitorTelemetryCounters(): Readonly { return this._outputMonitorTelemetryCounters; } @@ -513,6 +515,9 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { undefined, getMoreActions(suggestedOption, confirmationPrompt) )); + this._register(thePart.onDidRequestHide(() => { + this._outputMonitorTelemetryCounters.inputToolManualShownCount++; + })); const inputDataDisposable = this._register(execution.instance.onDidInputData(() => { thePart.hide(); thePart.dispose(); 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 b6606432e20..67b662e1bb1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -492,6 +492,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolManualAcceptCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualAcceptCount, inputToolManualRejectCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualRejectCount, inputToolManualChars: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualChars, + inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualShownCount, }); } } else { @@ -571,6 +572,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolManualAcceptCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualAcceptCount, inputToolManualRejectCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualRejectCount, inputToolManualChars: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualChars, + inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualShownCount, }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts index 0230d1c6ed1..a2475c600c7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts @@ -19,25 +19,7 @@ import { IConfigurationService } from '../../../../../../../platform/configurati import { toolResultDetailsFromResponse, toolResultMessageFromResponse } from './taskHelpers.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; import { DisposableStore } from '../../../../../../../base/common/lifecycle.js'; - -type CreateAndRunTaskToolClassification = { - taskLabel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the task.' }; - bufferLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the terminal buffer as a string.' }; - pollDurationMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long polling for output took (ms).' }; - inputToolManualAcceptCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually accepted a detected suggestion' }; - inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; - inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of suggestions' }; - owner: 'meganrogge'; - comment: 'Understanding the usage of the runTask tool'; -}; -type CreateAndRunTaskToolEvent = { - taskLabel: string; - bufferLength: number; - pollDurationMs: number | undefined; - inputToolManualAcceptCount: number; - inputToolManualRejectCount: number; - inputToolManualChars: number; -}; +import { TaskToolEvent, TaskToolClassification } from './taskToolsTelemetry.js'; interface ICreateAndRunTaskToolInput { workspaceFolder: string; @@ -137,13 +119,14 @@ export class CreateAndRunTaskTool implements IToolImpl { ); store.dispose(); for (const r of terminalResults) { - this._telemetryService.publicLog2?.('copilotChat.runTaskTool.createAndRunTask', { - taskLabel: args.task.label, + this._telemetryService.publicLog2?.('copilotChat.runTaskTool.createAndRunTask', { + taskId: args.task.label, bufferLength: r.output.length ?? 0, pollDurationMs: r.pollDurationMs ?? 0, inputToolManualAcceptCount: r.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, + inputToolManualShownCount: r.inputToolManualShownCount ?? 0, }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts index 397cd668f8a..9e36582b638 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts @@ -15,25 +15,7 @@ import { ITaskService, Task, TasksAvailableContext } from '../../../../../tasks/ import { ITerminalService } from '../../../../../terminal/browser/terminal.js'; import { collectTerminalResults, getTaskDefinition, getTaskForTool, resolveDependencyTasks } from '../../taskHelpers.js'; import { toolResultDetailsFromResponse, toolResultMessageFromResponse } from './taskHelpers.js'; - -type GetTaskOutputToolClassification = { - taskId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the task.' }; - bufferLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the terminal buffer as a string.' }; - pollDurationMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long polling for output took (ms).' }; - inputToolManualAcceptCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually accepted a detected suggestion' }; - inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; - inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of suggestions' }; - owner: 'meganrogge'; - comment: 'Understanding the usage of the getTaskOutput tool'; -}; -type GetTaskOutputToolEvent = { - taskId: string; - bufferLength: number; - pollDurationMs: number | undefined; - inputToolManualAcceptCount: number; - inputToolManualRejectCount: number; - inputToolManualChars: number; -}; +import { TaskToolEvent, TaskToolClassification } from './taskToolsTelemetry.js'; export const GetTaskOutputToolData: IToolData = { id: 'get_task_output', @@ -125,13 +107,14 @@ export class GetTaskOutputTool extends Disposable implements IToolImpl { ); store.dispose(); for (const r of terminalResults) { - this._telemetryService.publicLog2?.('copilotChat.getTaskOutputTool.get', { + this._telemetryService.publicLog2?.('copilotChat.getTaskOutputTool.get', { taskId: args.id, bufferLength: r.output.length ?? 0, pollDurationMs: r.pollDurationMs ?? 0, inputToolManualAcceptCount: r.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, + inputToolManualShownCount: r.inputToolManualShownCount ?? 0, }); } const details = terminalResults.map(r => `Terminal: ${r.name}\nOutput:\n${r.output}`); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts index 32662fd745e..883d3ec68bd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts @@ -17,25 +17,7 @@ import { Codicon } from '../../../../../../../base/common/codicons.js'; import { toolResultDetailsFromResponse, toolResultMessageFromResponse } from './taskHelpers.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; import { DisposableStore } from '../../../../../../../base/common/lifecycle.js'; - -type RunTaskToolClassification = { - taskId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the task.' }; - bufferLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the terminal buffer as a string.' }; - pollDurationMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long polling for output took (ms).' }; - inputToolManualAcceptCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually accepted a detected suggestion' }; - inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; - inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of suggestions' }; - owner: 'meganrogge'; - comment: 'Understanding the usage of the runTask tool'; -}; -type RunTaskToolEvent = { - taskId: string; - bufferLength: number; - pollDurationMs: number | undefined; - inputToolManualAcceptCount: number; - inputToolManualRejectCount: number; - inputToolManualChars: number; -}; +import { TaskToolClassification, TaskToolEvent } from './taskToolsTelemetry.js'; interface IRunTaskToolInput extends IToolInvocation { id: string; @@ -97,13 +79,14 @@ export class RunTaskTool implements IToolImpl { ); store.dispose(); for (const r of terminalResults) { - this._telemetryService.publicLog2?.('copilotChat.runTaskTool.run', { + this._telemetryService.publicLog2?.('copilotChat.runTaskTool.run', { taskId: args.id, bufferLength: r.output.length ?? 0, pollDurationMs: r.pollDurationMs ?? 0, inputToolManualAcceptCount: r.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, + inputToolManualShownCount: r.inputToolManualShownCount ?? 0, }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts new file mode 100644 index 00000000000..d7c991c9c96 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +export type TaskToolClassification = { + taskId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the task.' }; + bufferLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the terminal buffer as a string.' }; + pollDurationMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long polling for output took (ms).' }; + inputToolManualAcceptCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually accepted a detected suggestion' }; + inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; + inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of suggestions' }; + inputToolManualShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to manually accept an input suggestion' }; + owner: 'meganrogge'; + comment: 'Understanding the usage of the task tools'; +}; +export type TaskToolEvent = { + taskId: string; + bufferLength: number; + pollDurationMs: number | undefined; + inputToolManualAcceptCount: number; + inputToolManualRejectCount: number; + inputToolManualChars: number; + inputToolManualShownCount: number; +}; From 0246a6ce90658dad9623c21d7b6db9966f12fa1a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 17:04:08 -0400 Subject: [PATCH 0137/4355] provide progress messages to screen reader users, enable opting out (#265712) --- .../browser/accessibilityConfiguration.ts | 5 +++++ .../chat/browser/actions/chatAccessibilityHelp.ts | 1 + .../chat/browser/languageModelToolsService.ts | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 8af2b2ac7c4..ef7e83ed1d0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -807,6 +807,11 @@ const configuration: IConfigurationNode = { 'default': true, 'markdownDescription': localize('accessibility.openChatEditedFiles', "Controls whether files should be opened when the chat agent has applied edits to them.") }, + 'accessibility.verboseChatProgressUpdates': { + 'type': 'boolean', + 'default': true, + 'markdownDescription': localize('accessibility.verboseChatProgressUpdates', "Controls whether verbose progress announcements should be made when a chat request is in progress, including information like searched text for with X results, created file , or read file .") + } } }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 5680cc20354..921ede4da7c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -75,6 +75,7 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'qui } content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view{0}.', '')); + content.push(localize('chat.progressVerbosity', 'As the chat request is being processed, you will hear verbose progress updates if the request takes more than 4 seconds. This includes information like searched text for with X results, created file , or read file . This can be disabled with accessibility.verboseChatProgressUpdates.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command{0}.', getChatFocusKeybindingLabel(keybindingService, type, false))); content.push(localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command{0}.', getChatFocusKeybindingLabel(keybindingService, type, true))); diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 62558412ac2..75ce1545892 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -12,7 +12,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { CancellationError, isCancellationError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -42,6 +42,7 @@ import { ChatRequestToolReferenceEntry, toToolSetVariableEntry, toToolVariableEn import { ChatConfiguration } from '../common/constants.js'; import { CountTokensCallback, createToolSchemaUri, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultInputOutputDetails, stringifyPromptTsxPart, ToolDataSource, ToolSet, IToolAndToolSetEnablementMap } from '../common/languageModelToolsService.js'; import { getToolConfirmationAlert } from './chatAccessibilityProvider.js'; +import { alert } from '../../../../base/browser/ui/aria/aria.js'; const jsonSchemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -320,6 +321,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo token = source.token; const prepared = await this.prepareToolInvocation(tool, dto, token); + + this.informScreenReader(prepared?.invocationMessage); + toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId); trackedCall.invocation = toolInvocation; const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace); @@ -409,7 +413,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw err; } finally { toolInvocation?.complete(toolResult); - + this.informScreenReader(toolInvocation?.pastTenseMessage); if (store) { this.cleanupCallDisposables(requestId, store); } @@ -442,6 +446,12 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return prepared; } + private informScreenReader(msg: string | IMarkdownString | undefined): void { + if (msg) { + alert(typeof msg === 'string' ? msg : msg.value); + } + } + private playAccessibilitySignal(toolInvocations: ChatToolInvocation[]): void { const autoApproved = this._configurationService.getValue(ChatConfiguration.GlobalAutoApprove); if (autoApproved) { From 3e2f34ebe804acde90f011d326e199da7c749c8e Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:05:43 -0700 Subject: [PATCH 0138/4355] Address API sync feedback for challenges API (#265921) * Address API sync feedback for challenges API * use `fallbackScopes` instead of `scopes` * `WWW`-> `Www` ref https://github.com/microsoft/vscode/issues/260156 * adopt the change --- .../src/node/authProvider.ts | 21 ++++++++------ .../api/browser/mainThreadAuthentication.ts | 13 ++++----- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 24 +++------------- .../api/common/extHostAuthentication.ts | 28 +++++++++---------- .../authenticationExtensionsService.ts | 14 +++++----- .../browser/authenticationService.ts | 14 +++++----- .../authentication/common/authentication.ts | 18 ++++++------ ...ode.proposed.authenticationChallenges.d.ts | 17 ++++++----- 9 files changed, 69 insertions(+), 82 deletions(-) diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index bccfcc2e9e4..c2145f75bcf 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -290,8 +290,11 @@ export class MsalAuthProvider implements AuthenticationProvider { async getSessionsFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { this._logger.info('[getSessionsFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); - // Use scopes from constraint if provided, otherwise extract from challenges - const scopes = constraint.scopes?.length ? [...constraint.scopes] : this.extractScopesFromChallenges(constraint.challenges); + // Use scopes from challenges if provided, otherwise use fallback scopes + const scopes = this.extractScopesFromChallenges(constraint.challenges) ?? constraint.fallbackScopes; + if (!scopes || scopes.length === 0) { + throw new Error('No scopes found in authentication challenges or fallback scopes'); + } const claims = this.extractClaimsFromChallenges(constraint.challenges); if (!claims) { throw new Error('No claims found in authentication challenges'); @@ -309,8 +312,11 @@ export class MsalAuthProvider implements AuthenticationProvider { async createSessionFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { this._logger.info('[createSessionFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); - // Use scopes from constraint if provided, otherwise extract from challenges - const scopes = constraint.scopes?.length ? [...constraint.scopes] : this.extractScopesFromChallenges(constraint.challenges); + // Use scopes from challenges if provided, otherwise use fallback scopes + const scopes = this.extractScopesFromChallenges(constraint.challenges) ?? constraint.fallbackScopes; + if (!scopes || scopes.length === 0) { + throw new Error('No scopes found in authentication challenges or fallback scopes'); + } const claims = this.extractClaimsFromChallenges(constraint.challenges); // Use scopes if available, otherwise fall back to default scopes @@ -392,14 +398,13 @@ export class MsalAuthProvider implements AuthenticationProvider { throw lastError ?? new Error('No auth flow succeeded'); } - private extractScopesFromChallenges(challenges: readonly AuthenticationChallenge[]): string[] { - const scopes: string[] = []; + private extractScopesFromChallenges(challenges: readonly AuthenticationChallenge[]): string[] | undefined { for (const challenge of challenges) { if (challenge.scheme.toLowerCase() === 'bearer' && challenge.params.scope) { - scopes.push(...challenge.params.scope.split(' ')); + return challenge.params.scope.split(' '); } } - return scopes; + return undefined; } private extractClaimsFromChallenges(challenges: readonly AuthenticationChallenge[]): string | undefined { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 902230bc7cd..ff002feb111 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -6,8 +6,8 @@ import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import * as nls from '../../../nls.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; -import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, AuthenticationSessionAccount, IAuthenticationProviderSessionOptions, isAuthenticationWWWAuthenticateRequest, IAuthenticationConstraint } from '../../services/authentication/common/authentication.js'; -import { AuthenticationWWWAuthenticateRequest, ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol.js'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, AuthenticationSessionAccount, IAuthenticationProviderSessionOptions, isAuthenticationWwwAuthenticateRequest, IAuthenticationConstraint, IAuthenticationWwwAuthenticateRequest } from '../../services/authentication/common/authentication.js'; +import { ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol.js'; import { IDialogService, IPromptButton } from '../../../platform/dialogs/common/dialogs.js'; import Severity from '../../../base/common/severity.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; @@ -345,7 +345,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return result.result === chosenAccountLabel; } - private async doGetSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { + private async doGetSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { const authorizationServer = URI.revive(options.authorizationServer); const sessions = await this.authenticationService.getSessions(providerId, scopeListOrRequest, { account: options.account, authorizationServer }, true); const provider = this.authenticationService.getProvider(providerId); @@ -452,8 +452,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return undefined; } - async $getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { - const scopes = isAuthenticationWWWAuthenticateRequest(scopeListOrRequest) ? scopeListOrRequest.scopes : scopeListOrRequest; + async $getSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { + const scopes = isAuthenticationWwwAuthenticateRequest(scopeListOrRequest) ? scopeListOrRequest.fallbackScopes : scopeListOrRequest; if (scopes) { this.sendClientIdUsageTelemetry(extensionId, providerId, scopes); } @@ -461,8 +461,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (session) { this.sendProviderUsageTelemetry(extensionId, providerId); - const scopes = isAuthenticationWWWAuthenticateRequest(scopeListOrRequest) ? scopeListOrRequest.scopes : scopeListOrRequest; - this.authenticationUsageService.addAccountUsage(providerId, session.account.label, scopes, extensionId, extensionName); + this.authenticationUsageService.addAccountUsage(providerId, session.account.label, session.scopes, extensionId, extensionName); } return session; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 0bb808d594a..9afd4467b0e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -301,7 +301,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I })(); const authentication: typeof vscode.authentication = { - getSession(providerId: string, scopesOrChallenge: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options?: vscode.AuthenticationGetSessionOptions) { + getSession(providerId: string, scopesOrChallenge: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options?: vscode.AuthenticationGetSessionOptions) { if (!Array.isArray(scopesOrChallenge)) { checkProposedApiEnabled(extension, 'authenticationChallenges'); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index fdd9a91daca..88ea921a3fa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -80,7 +80,7 @@ import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescrip import { TypeHierarchyItem } from '../../contrib/typeHierarchy/common/typeHierarchy.js'; import { RelatedInformationResult, RelatedInformationType } from '../../services/aiRelatedInformation/common/aiRelatedInformation.js'; import { AiSettingsSearchProviderOptions, AiSettingsSearchResult } from '../../services/aiSettingsSearch/common/aiSettingsSearch.js'; -import { AuthenticationSession, AuthenticationSessionAccount, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions, IAuthenticationGetSessionsOptions } from '../../services/authentication/common/authentication.js'; +import { AuthenticationSession, AuthenticationSessionAccount, AuthenticationSessionsChangeEvent, IAuthenticationConstraint, IAuthenticationCreateSessionOptions, IAuthenticationGetSessionsOptions, IAuthenticationWwwAuthenticateRequest } from '../../services/authentication/common/authentication.js'; import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IExtensionDescriptionDelta, IStaticWorkspaceData } from '../../services/extensions/common/extensionHostProtocol.js'; import { IResolveAuthorityResult } from '../../services/extensions/common/extensionHostProxy.js'; @@ -186,27 +186,11 @@ export interface AuthenticationGetSessionOptions { account?: AuthenticationSessionAccount; } -export interface AuthenticationChallenge { - scheme: string; - params: Record; -} - -export interface AuthenticationWWWAuthenticateRequest { - wwwAuthenticate: string; - scopes?: readonly string[]; -} - -//TODO: I don't love the name of this interface... -export interface AuthenticationConstraint { - challenges: readonly AuthenticationChallenge[]; - scopes?: readonly string[]; -} - export interface MainThreadAuthenticationShape extends IDisposable { $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean, supportedAuthorizationServers?: UriComponents[], supportsChallenges?: boolean): Promise; $unregisterAuthenticationProvider(id: string): Promise; $sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): Promise; - $getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise; + $getSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise; $getAccounts(providerId: string): Promise>; $removeSession(providerId: string, sessionId: string): Promise; $waitForUriHandler(expectedUri: UriComponents): Promise; @@ -2007,8 +1991,8 @@ export interface ExtHostLabelServiceShape { export interface ExtHostAuthenticationShape { $getSessions(id: string, scopes: string[] | undefined, options: IAuthenticationGetSessionsOptions): Promise>; $createSession(id: string, scopes: string[], options: IAuthenticationCreateSessionOptions): Promise; - $getSessionsFromChallenges(id: string, constraint: AuthenticationConstraint, options: IAuthenticationGetSessionsOptions): Promise>; - $createSessionFromChallenges(id: string, constraint: AuthenticationConstraint, options: IAuthenticationCreateSessionOptions): Promise; + $getSessionsFromChallenges(id: string, constraint: IAuthenticationConstraint, options: IAuthenticationGetSessionsOptions): Promise>; + $createSessionFromChallenges(id: string, constraint: IAuthenticationConstraint, options: IAuthenticationCreateSessionOptions): Promise; $removeSession(id: string, sessionId: string): Promise; $onDidChangeAuthenticationSessions(id: string, label: string, extensionIdFilter?: string[]): Promise; $onDidUnregisterAuthenticationProvider(id: string): Promise; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 7fe640404ce..c3b5bf35bc2 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from './extHost.protocol.js'; import { Disposable, ProgressLocation } from './extHostTypes.js'; import { IExtensionDescription, ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js'; -import { INTERNAL_AUTH_PROVIDER_PREFIX, isAuthenticationWWWAuthenticateRequest } from '../../services/authentication/common/authentication.js'; +import { INTERNAL_AUTH_PROVIDER_PREFIX, isAuthenticationWwwAuthenticateRequest } from '../../services/authentication/common/authentication.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; @@ -79,32 +79,32 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { ); } - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: vscode.AuthenticationForceNewSessionOptions })): Promise; - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & { forceNewSession: true }): Promise; - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & { forceNewSession: vscode.AuthenticationForceNewSessionOptions }): Promise; - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions): Promise; - async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWWWAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions = {}): Promise { + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & ({ createIfNone: true } | { forceNewSession: true } | { forceNewSession: vscode.AuthenticationForceNewSessionOptions })): Promise; + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & { forceNewSession: true }): Promise; + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions & { forceNewSession: vscode.AuthenticationForceNewSessionOptions }): Promise; + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions): Promise; + async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions = {}): Promise { const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); const keys: (keyof vscode.AuthenticationGetSessionOptions)[] = Object.keys(options) as (keyof vscode.AuthenticationGetSessionOptions)[]; const optionsStr = keys.sort().map(key => `${key}:${!!options[key]}`).join(', '); // old shape, remove next milestone if ( - 'challenge' in scopesOrRequest - && typeof scopesOrRequest.challenge === 'string' - && !scopesOrRequest.wwwAuthenticate + 'scopes' in scopesOrRequest + && typeof scopesOrRequest.scopes === 'string' + && !scopesOrRequest.fallbackScopes ) { scopesOrRequest = { - wwwAuthenticate: scopesOrRequest.challenge, - scopes: scopesOrRequest.scopes + wwwAuthenticate: scopesOrRequest.wwwAuthenticate, + fallbackScopes: scopesOrRequest.scopes }; } let singlerKey: string; - if (isAuthenticationWWWAuthenticateRequest(scopesOrRequest)) { - const challenge = scopesOrRequest as vscode.AuthenticationWWWAuthenticateRequest; + if (isAuthenticationWwwAuthenticateRequest(scopesOrRequest)) { + const challenge = scopesOrRequest as vscode.AuthenticationWwwAuthenticateRequest; const challengeStr = challenge.wwwAuthenticate; - const scopesStr = challenge.scopes ? [...challenge.scopes].sort().join(' ') : ''; + const scopesStr = challenge.fallbackScopes ? [...challenge.fallbackScopes].sort().join(' ') : ''; singlerKey = `${extensionId} ${providerId} challenge:${challengeStr} ${scopesStr} ${optionsStr}`; } else { const sortedScopes = [...scopesOrRequest].sort().join(' '); diff --git a/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts index d208a21c7cd..ef2e32e2e15 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationExtensionsService.ts @@ -16,7 +16,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IActivityService, NumberBadge } from '../../activity/common/activity.js'; import { IAuthenticationAccessService } from './authenticationAccessService.js'; import { IAuthenticationUsageService } from './authenticationUsageService.js'; -import { AuthenticationSession, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, AuthenticationSessionAccount, IAuthenticationWWWAuthenticateRequest, isAuthenticationWWWAuthenticateRequest } from '../common/authentication.js'; +import { AuthenticationSession, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, AuthenticationSessionAccount, IAuthenticationWwwAuthenticateRequest, isAuthenticationWwwAuthenticateRequest } from '../common/authentication.js'; import { Emitter } from '../../../../base/common/event.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; @@ -287,7 +287,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth /** * This function should be used only when there are sessions to disambiguate. */ - async selectSession(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, availableSessions: AuthenticationSession[]): Promise { + async selectSession(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, availableSessions: AuthenticationSession[]): Promise { const allAccounts = await this._authenticationService.getAccounts(providerId); if (!allAccounts.length) { throw new Error('No accounts available'); @@ -359,7 +359,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth }); } - private async completeSessionAccessRequest(provider: IAuthenticationProvider, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest): Promise { + private async completeSessionAccessRequest(provider: IAuthenticationProvider, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest): Promise { const providerRequests = this._sessionAccessRequestItems.get(provider.id) || {}; const existingRequest = providerRequests[extensionId]; if (!existingRequest) { @@ -390,7 +390,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth } } - requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, possibleSessions: AuthenticationSession[]): void { + requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, possibleSessions: AuthenticationSession[]): void { const providerRequests = this._sessionAccessRequestItems.get(providerId) || {}; const hasExistingRequest = providerRequests[extensionId]; if (hasExistingRequest) { @@ -424,7 +424,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth this.updateBadgeCount(); } - async requestNewSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, extensionId: string, extensionName: string): Promise { + async requestNewSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string): Promise { if (!this._authenticationService.isAuthenticationProviderRegistered(providerId)) { // Activate has already been called for the authentication provider, but it cannot block on registering itself // since this is sync and returns a disposable. So, wait for registration event to fire that indicates the @@ -447,8 +447,8 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth } const providerRequests = this._signInRequestItems.get(providerId); - const signInRequestKey = isAuthenticationWWWAuthenticateRequest(scopeListOrRequest) - ? `${scopeListOrRequest.wwwAuthenticate}:${scopeListOrRequest.scopes?.join(SCOPESLIST_SEPARATOR) ?? ''}` + const signInRequestKey = isAuthenticationWwwAuthenticateRequest(scopeListOrRequest) + ? `${scopeListOrRequest.wwwAuthenticate}:${scopeListOrRequest.fallbackScopes?.join(SCOPESLIST_SEPARATOR) ?? ''}` : `${scopeListOrRequest.join(SCOPESLIST_SEPARATOR)}`; const extensionHasExistingRequest = providerRequests && providerRequests[signInRequestKey] diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 1e351606021..0eaf7b78dd3 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -12,7 +12,7 @@ import { InstantiationType, registerSingleton } from '../../../../platform/insta import { IProductService } from '../../../../platform/product/common/productService.js'; import { ISecretStorageService } from '../../../../platform/secrets/common/secrets.js'; import { IAuthenticationAccessService } from './authenticationAccessService.js'; -import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionAccount, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions, IAuthenticationGetSessionsOptions, IAuthenticationProvider, IAuthenticationProviderHostDelegate, IAuthenticationService, IAuthenticationWWWAuthenticateRequest, isAuthenticationWWWAuthenticateRequest } from '../common/authentication.js'; +import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionAccount, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions, IAuthenticationGetSessionsOptions, IAuthenticationProvider, IAuthenticationProviderHostDelegate, IAuthenticationService, IAuthenticationWwwAuthenticateRequest, isAuthenticationWwwAuthenticateRequest } from '../common/authentication.js'; import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js'; import { ActivationKind, IExtensionService } from '../../extensions/common/extensions.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -276,7 +276,7 @@ export class AuthenticationService extends Disposable implements IAuthentication return accounts; } - async getSessions(id: string, scopeListOrRequest?: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, options?: IAuthenticationGetSessionsOptions, activateImmediate: boolean = false): Promise> { + async getSessions(id: string, scopeListOrRequest?: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationGetSessionsOptions, activateImmediate: boolean = false): Promise> { if (this._disposedSource.token.isCancellationRequested) { return []; } @@ -291,12 +291,12 @@ export class AuthenticationService extends Disposable implements IAuthentication throw new Error(`The authorization server '${authServerStr}' is not supported by the authentication provider '${id}'.`); } } - if (isAuthenticationWWWAuthenticateRequest(scopeListOrRequest)) { + if (isAuthenticationWwwAuthenticateRequest(scopeListOrRequest)) { if (!authProvider.getSessionsFromChallenges) { throw new Error(`The authentication provider '${id}' does not support getting sessions from challenges.`); } return await authProvider.getSessionsFromChallenges( - { challenges: parseWWWAuthenticateHeader(scopeListOrRequest.wwwAuthenticate), scopes: scopeListOrRequest.scopes }, + { challenges: parseWWWAuthenticateHeader(scopeListOrRequest.wwwAuthenticate), fallbackScopes: scopeListOrRequest.fallbackScopes }, { ...options } ); } @@ -306,19 +306,19 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - async createSession(id: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, options?: IAuthenticationCreateSessionOptions): Promise { + async createSession(id: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationCreateSessionOptions): Promise { if (this._disposedSource.token.isCancellationRequested) { throw new Error('Authentication service is disposed.'); } const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, !!options?.activateImmediate); if (authProvider) { - if (isAuthenticationWWWAuthenticateRequest(scopeListOrRequest)) { + if (isAuthenticationWwwAuthenticateRequest(scopeListOrRequest)) { if (!authProvider.createSessionFromChallenges) { throw new Error(`The authentication provider '${id}' does not support creating sessions from challenges.`); } return await authProvider.createSessionFromChallenges( - { challenges: parseWWWAuthenticateHeader(scopeListOrRequest.wwwAuthenticate), scopes: scopeListOrRequest.scopes }, + { challenges: parseWWWAuthenticateHeader(scopeListOrRequest.wwwAuthenticate), fallbackScopes: scopeListOrRequest.fallbackScopes }, { ...options } ); } diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index 46f2a1ba752..2a60f4fecb9 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -62,7 +62,7 @@ export interface IAuthenticationCreateSessionOptions { [key: string]: any; } -export interface IAuthenticationWWWAuthenticateRequest { +export interface IAuthenticationWwwAuthenticateRequest { /** * The raw WWW-Authenticate header value that triggered this challenge. * This will be parsed by the authentication provider to extract the necessary @@ -74,10 +74,10 @@ export interface IAuthenticationWWWAuthenticateRequest { * Optional scopes for the session. If not provided, the authentication provider * may use default scopes or extract them from the challenge. */ - readonly scopes?: readonly string[]; + readonly fallbackScopes?: readonly string[]; } -export function isAuthenticationWWWAuthenticateRequest(obj: unknown): obj is IAuthenticationWWWAuthenticateRequest { +export function isAuthenticationWwwAuthenticateRequest(obj: unknown): obj is IAuthenticationWwwAuthenticateRequest { return typeof obj === 'object' && obj !== null && 'wwwAuthenticate' in obj @@ -99,7 +99,7 @@ export interface IAuthenticationConstraint { * Optional scopes for the session. If not provided, the authentication provider * may extract scopes from the challenges or use default scopes. */ - readonly scopes?: readonly string[]; + readonly fallbackScopes?: readonly string[]; } /** @@ -237,7 +237,7 @@ export interface IAuthenticationService { * @param options Additional options for getting sessions * @param activateImmediate If true, the provider should activate immediately if it is not already */ - getSessions(id: string, scopeListOrRequest?: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, options?: IAuthenticationGetSessionsOptions, activateImmediate?: boolean): Promise>; + getSessions(id: string, scopeListOrRequest?: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationGetSessionsOptions, activateImmediate?: boolean): Promise>; /** * Creates an AuthenticationSession with the given provider and scopes @@ -245,7 +245,7 @@ export interface IAuthenticationService { * @param scopes The scopes to request * @param options Additional options for creating the session */ - createSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, options?: IAuthenticationCreateSessionOptions): Promise; + createSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationCreateSessionOptions): Promise; /** * Removes the session with the given id from the provider with the given id @@ -355,9 +355,9 @@ export interface IAuthenticationExtensionsService { * @param scopes */ removeSessionPreference(providerId: string, extensionId: string, scopes: string[]): void; - selectSession(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): Promise; - requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): void; - requestNewSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWWWAuthenticateRequest, extensionId: string, extensionName: string): Promise; + selectSession(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): Promise; + requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): void; + requestNewSession(providerId: string, scopeListOrRequest: ReadonlyArray | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string): Promise; updateNewSessionRequests(providerId: string, addedSessions: readonly AuthenticationSession[]): void; } diff --git a/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts b/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts index 2550d66c24e..3cc2d5b7696 100644 --- a/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts +++ b/src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts @@ -21,7 +21,7 @@ declare module 'vscode' { * the challenges in this WWW-Authenticate value. * @note For more information on WWW-Authenticate please see https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/WWW-Authenticate */ - export interface AuthenticationWWWAuthenticateRequest { + export interface AuthenticationWwwAuthenticateRequest { /** * The raw WWW-Authenticate header value that triggered this challenge. * This will be parsed by the authentication provider to extract the necessary @@ -30,13 +30,12 @@ declare module 'vscode' { readonly wwwAuthenticate: string; /** - * @deprecated Use `wwwAuthenticate` instead. + * The fallback scopes to use if no scopes are found in the WWW-Authenticate header. */ - readonly challenge?: string; + readonly fallbackScopes?: readonly string[]; /** - * Optional scopes for the session. If not provided, the authentication provider - * may use default scopes or extract them from the challenge. + * @deprecated Use `fallbackScopes` instead. */ readonly scopes?: readonly string[]; } @@ -59,7 +58,7 @@ declare module 'vscode' { * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session */ - export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** */createIfNone: true | AuthenticationGetSessionPresentationOptions }): Thenable; + export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWwwAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** */createIfNone: true | AuthenticationGetSessionPresentationOptions }): Thenable; /** * Get an authentication session matching the desired scopes or request. Rejects if a provider with providerId is not @@ -77,7 +76,7 @@ declare module 'vscode' { * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session */ - export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** literal-type defines return type */forceNewSession: true | AuthenticationGetSessionPresentationOptions | AuthenticationForceNewSessionOptions }): Thenable; + export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWwwAuthenticateRequest, options: AuthenticationGetSessionOptions & { /** literal-type defines return type */forceNewSession: true | AuthenticationGetSessionPresentationOptions | AuthenticationForceNewSessionOptions }): Thenable; /** * Get an authentication session matching the desired scopes or request. Rejects if a provider with providerId is not @@ -95,7 +94,7 @@ declare module 'vscode' { * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session or undefined if a silent flow was used and no session was found */ - export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWWWAuthenticateRequest, options?: AuthenticationGetSessionOptions): Thenable; + export function getSession(providerId: string, scopeListOrRequest: ReadonlyArray | AuthenticationWwwAuthenticateRequest, options?: AuthenticationGetSessionOptions): Thenable; } @@ -141,7 +140,7 @@ declare module 'vscode' { * Optional scopes for the session. If not provided, the authentication provider * may extract scopes from the challenges or use default scopes. */ - readonly scopes?: readonly string[]; + readonly fallbackScopes?: readonly string[]; } /** From d7cd41aecdab2e7523af97696b5fe71a7976320c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:05:59 -0700 Subject: [PATCH 0139/4355] Fix images in release notes Fixes #265932 --- src/vs/base/browser/domSanitize.ts | 4 ++-- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 4 ++++ .../workbench/contrib/update/browser/releaseNotesEditor.ts | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 0b3bf5bd22e..c4395b9d2e1 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -117,7 +117,7 @@ function addDompurifyHook(hook: 'uponSanitizeElement' | 'uponSanitizeAttribute', * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` * attributes are valid. */ -function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[] | '*', allowedMediaProtocols: readonly string[]): IDisposable { +function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[] | '*', allowedMediaProtocols: readonly string[] | '*'): IDisposable { // https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html // build an anchor to map URLs to const anchor = document.createElement('a'); @@ -194,7 +194,7 @@ export interface DomSanitizerConfig { * List of allowed protocols for `src` attributes. */ readonly allowedMediaProtocols?: { - readonly override?: readonly string[]; + readonly override?: readonly string[] | '*'; }; /** diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index bc64ac4f07d..12994af6ff6 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -167,6 +167,7 @@ function sanitize(documentContent: string, sanitizerConfig: MarkdownDocumentSani allowedLinkProtocols: { override: sanitizerConfig?.allowedLinkProtocols?.override ?? defaultAllowedLinkProtocols, }, + allowedMediaProtocols: sanitizerConfig?.allowedMediaProtocols, allowedTags: { override: allowedMarkdownHtmlTags, augment: sanitizerConfig?.allowedTags?.augment @@ -190,6 +191,9 @@ interface MarkdownDocumentSanitizerConfig { readonly allowedLinkProtocols?: { readonly override: readonly string[] | '*'; }; + readonly allowedMediaProtocols?: { + readonly override: readonly string[] | '*'; + }; readonly allowedTags?: { readonly augment: readonly string[]; }; diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 222a6116b59..e0b9f861811 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -91,7 +91,7 @@ export class ReleaseNotesManager extends Disposable { } public async show(version: string, useCurrentFile: boolean): Promise { - const releaseNoteText = await this.loadReleaseNotes(version, useCurrentFile); + const releaseNoteText = await this.loadReleaseNotes('1.103.0', useCurrentFile); const base = await this.getBase(useCurrentFile); this._lastMeta = { text: releaseNoteText, base }; const html = await this.renderBody(this._lastMeta); @@ -267,6 +267,9 @@ export class ReleaseNotesManager extends Disposable { const content = await renderMarkdownDocument(fileContent.text, this._extensionService, this._languageService, { sanitizerConfig: { + allowedMediaProtocols: { + override: '*' // TODO: remove once we can use to find real resource locations + }, allowedLinkProtocols: { override: [Schemas.http, Schemas.https, Schemas.command] } From 34ea736f20e3af1b32b36927c2136ed068e211e2 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:14:21 -0700 Subject: [PATCH 0140/4355] Revert testing change --- src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index e0b9f861811..07fcc7d812f 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -91,7 +91,7 @@ export class ReleaseNotesManager extends Disposable { } public async show(version: string, useCurrentFile: boolean): Promise { - const releaseNoteText = await this.loadReleaseNotes('1.103.0', useCurrentFile); + const releaseNoteText = await this.loadReleaseNotes(version, useCurrentFile); const base = await this.getBase(useCurrentFile); this._lastMeta = { text: releaseNoteText, base }; const html = await this.renderBody(this._lastMeta); From 40e6354c1a6d3b546077e0c34080255ece9c2e4a Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:36:27 -0700 Subject: [PATCH 0141/4355] Finalize SecretStorage `keys()` API (#265931) Fixes https://github.com/microsoft/vscode/issues/196616 --- .../extensions/common/extensionsApiProposals.ts | 3 --- src/vs/workbench/api/common/extHostSecrets.ts | 4 ---- src/vscode-dts/vscode.d.ts | 5 +++++ .../vscode.proposed.secretStorageKeys.d.ts | 16 ---------------- 4 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index a2e111a0a46..9a20aa9cc79 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -358,9 +358,6 @@ const _allApiProposals = { scmValidation: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', }, - secretStorageKeys: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts', - }, shareProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts', }, diff --git a/src/vs/workbench/api/common/extHostSecrets.ts b/src/vs/workbench/api/common/extHostSecrets.ts index 5085d16bfd5..eeada39be07 100644 --- a/src/vs/workbench/api/common/extHostSecrets.ts +++ b/src/vs/workbench/api/common/extHostSecrets.ts @@ -11,11 +11,9 @@ import { ExtHostSecretState } from './extHostSecretState.js'; import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { Event } from '../../../base/common/event.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; -import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; export class ExtensionSecrets implements vscode.SecretStorage { - private readonly _extensionDescription: IExtensionDescription; protected readonly _id: string; readonly #secretState: ExtHostSecretState; @@ -23,7 +21,6 @@ export class ExtensionSecrets implements vscode.SecretStorage { readonly disposables = new DisposableStore(); constructor(extensionDescription: IExtensionDescription, secretState: ExtHostSecretState) { - this._extensionDescription = extensionDescription; this._id = ExtensionIdentifier.toKey(extensionDescription.identifier); this.#secretState = secretState; @@ -51,7 +48,6 @@ export class ExtensionSecrets implements vscode.SecretStorage { } keys(): Promise { - checkProposedApiEnabled(this._extensionDescription, 'secretStorageKeys'); return this.#secretState.keys(this._id) || []; } } diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 0f4fa464aaf..9881a91ea78 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8579,6 +8579,11 @@ declare module 'vscode' { * machines. */ export interface SecretStorage { + /** + * Retrieve the keys of all the secrets stored by this extension. + */ + keys(): Thenable; + /** * Retrieve a secret that was stored with key. Returns undefined if there * is no password matching that key. diff --git a/src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts b/src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts deleted file mode 100644 index e0122202a56..00000000000 --- a/src/vscode-dts/vscode.proposed.secretStorageKeys.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/196616 - - export interface SecretStorage { - /** - * Retrieve the keys of all the secrets stored by this extension. - */ - keys(): Thenable; - } -} From 7f948c949252222b431d9a1a4bacc8ec9c991985 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 21:58:08 +0000 Subject: [PATCH 0142/4355] Fix chat session disposal error by preventing duplicate widget sessions (#265705) * Initial plan * Add duplicate session prevention to chat session actions Co-authored-by: roblourens <323878+roblourens@users.noreply.github.com> * Complete session duplication fix for both local and external sessions Co-authored-by: roblourens <323878+roblourens@users.noreply.github.com> * Add tests and edge case handling for session duplicate prevention Co-authored-by: roblourens <323878+roblourens@users.noreply.github.com> * More * Fixes to actions * Also check widget service to detect sessions in sidebar * Fix OpenChatSessionInSidebarAction * Remove this * remove todo --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: roblourens <323878+roblourens@users.noreply.github.com> Co-authored-by: Rob Lourens Co-authored-by: Osvaldo Ortega --- .../browser/actions/chatSessionActions.ts | 227 ++++++++---------- .../contrib/chat/browser/chat.contribution.ts | 3 +- .../contrib/chat/browser/chatSessions.ts | 22 +- .../chat/browser/chatSessions/common.ts | 20 ++ 4 files changed, 141 insertions(+), 131 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 70e911e3424..995bc90e1b0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -3,31 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from '../../../../../base/common/codicons.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; +import Severity from '../../../../../base/common/severity.js'; import { localize } from '../../../../../nls.js'; import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; -import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import Severity from '../../../../../base/common/severity.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; -import { CHAT_CATEGORY } from './chatActions.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { AUX_WINDOW_GROUP, IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; -import { IChatEditorOptions } from '../chatEditor.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IChatService } from '../../common/chatService.js'; +import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; +import { ChatConfiguration } from '../../common/constants.js'; +import { ChatViewId, IChatWidgetService } from '../chat.js'; +import { IChatEditorOptions } from '../chatEditor.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; import { ILocalChatSessionItem, VIEWLET_ID } from '../chatSessions.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { ChatViewId } from '../chat.js'; +import { findExistingChatEditorByUri } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { ChatConfiguration } from '../../common/constants.js'; -import { Codicon } from '../../../../../base/common/codicons.js'; -import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { CHAT_CATEGORY } from './chatActions.js'; export interface IChatSessionContext { sessionId: string; @@ -46,6 +48,7 @@ interface IMarshalledChatSessionContext { editor?: ChatEditorInput; widget?: any; sessionType?: 'editor' | 'widget'; + provider?: { chatSessionType?: string }; }; } @@ -257,60 +260,44 @@ export class OpenChatSessionInNewWindowAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise { + async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { if (!context) { return; } const editorService = accessor.get(IEditorService); - let sessionId: string; - let sessionItem: IChatSessionItem | undefined; + const chatWidgetService = accessor.get(IChatWidgetService); + const sessionId = context.session.id.replace('history-', ''); + const editorGroupsService = accessor.get(IEditorGroupsService); + if (context.session.provider?.chatSessionType) { + const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); + // Check if this session is already open in another editor + const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + if (existingEditor) { + await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + return; + } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + return; + } else if (isLocalChatSessionItem(context.session) || context.session.id.startsWith('history-')) { + const options: IChatEditorOptions = { + target: { sessionId }, + ignoreInView: true, + }; + // For local sessions, create a new chat editor + await editorService.openEditor({ + resource: ChatEditorInput.getNewEditorUri(), + options, + }, AUX_WINDOW_GROUP); - if (isMarshalledChatSessionContext(context)) { - const session = context.session; - sessionItem = session; - - // For local sessions, extract the actual session ID from editor or widget - if (isLocalChatSessionItem(session)) { - if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - sessionId = session.editor.sessionId || session.id; - } else if (session.sessionType === 'widget' && session.widget) { - sessionId = session.widget.viewModel?.model.sessionId || session.id; - } else { - sessionId = session.id; - } } else { - // For external provider sessions, use the session ID directly - sessionId = session.id; + const options: IChatEditorOptions = { + ignoreInView: true, + }; + await editorService.openEditor({ + resource: uri, + options, + }, AUX_WINDOW_GROUP); } - } else { - sessionId = context.sessionId; - } - - if (sessionItem && (isLocalChatSessionItem(sessionItem) || sessionId.startsWith('history-'))) { - // For history session remove the `history` prefix - const sessionIdWithoutHistory = sessionId.replace('history-', ''); - const options: IChatEditorOptions = { - target: { sessionId: sessionIdWithoutHistory }, - pinned: true, - auxiliary: { compact: false }, - ignoreInView: true - }; - // For local sessions, create a new chat editor in the auxiliary window - await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), - options, - }, AUX_WINDOW_GROUP); - } else { - // For external provider sessions, open the existing session in the auxiliary window - const providerType = sessionItem && (sessionItem as any).provider?.chatSessionType || 'external'; - await editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, sessionId), - options: { - pinned: true, - auxiliary: { compact: false } - } satisfies IChatEditorOptions - }, AUX_WINDOW_GROUP); } } } @@ -330,54 +317,44 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise { + async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { if (!context) { return; } const editorService = accessor.get(IEditorService); - let sessionId: string; - let sessionItem: IChatSessionItem | undefined; + const chatWidgetService = accessor.get(IChatWidgetService); + const sessionId = context.session.id.replace('history-', ''); + const editorGroupsService = accessor.get(IEditorGroupsService); + if (context.session.provider?.chatSessionType) { + const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); + // Check if this session is already open in another editor + const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + if (existingEditor) { + await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + return; + } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + return; + } else if (isLocalChatSessionItem(context.session) || context.session.id.startsWith('history-')) { + const options: IChatEditorOptions = { + target: { sessionId }, + ignoreInView: true, + }; + // For local sessions, create a new chat editor + await editorService.openEditor({ + resource: ChatEditorInput.getNewEditorUri(), + options, + }, SIDE_GROUP); - if (isMarshalledChatSessionContext(context)) { - const session = context.session; - sessionItem = session; - - if (isLocalChatSessionItem(session)) { - if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - sessionId = session.editor.sessionId || session.id; - } else if (session.sessionType === 'widget' && session.widget) { - sessionId = session.widget.viewModel?.model.sessionId || session.id; - } else { - sessionId = session.id; - } } else { - sessionId = session.id; + const options: IChatEditorOptions = { + ignoreInView: true, + }; + await editorService.openEditor({ + resource: uri, + options, + }, SIDE_GROUP); } - } else { - sessionId = context.sessionId; - } - - // Open editor to the side using VS Code's standard pattern - if (sessionItem && (isLocalChatSessionItem(sessionItem) || sessionId.startsWith('history-'))) { - const sessionIdWithoutHistory = sessionId.replace('history-', ''); - const options: IChatEditorOptions = { - target: { sessionId: sessionIdWithoutHistory }, - pinned: true, - ignoreInView: true, - }; - // For local sessions, create a new chat editor - await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), - options, - }, SIDE_GROUP); - } else { - // For external provider sessions, open the existing session - const providerType = sessionItem && (sessionItem as any).provider?.chatSessionType || 'external'; - await editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, sessionId), - options: { pinned: true } satisfies IChatEditorOptions - }, SIDE_GROUP); } } } @@ -397,46 +374,40 @@ export class OpenChatSessionInSidebarAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise { + async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { if (!context) { return; } + const editorService = accessor.get(IEditorService); const viewsService = accessor.get(IViewsService); - let sessionId: string; - let sessionItem: IChatSessionItem | undefined; - - if (isMarshalledChatSessionContext(context)) { - const session = context.session; - sessionItem = session; - - if (isLocalChatSessionItem(session)) { - if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - sessionId = session.editor.sessionId || session.id; - } else if (session.sessionType === 'widget' && session.widget) { - sessionId = session.widget.viewModel?.model.sessionId || session.id; - } else { - sessionId = session.id; - } - } else { - sessionId = session.id; + const chatWidgetService = accessor.get(IChatWidgetService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const sessionId = context.session.id.replace('history-', ''); + if (context.session.provider?.chatSessionType) { + const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); + // Check if this session is already open in another editor + const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + if (existingEditor) { + await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + return; + } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + return; } - } else { - sessionId = context.sessionId; } // Open the chat view in the sidebar const chatViewPane = await viewsService.openView(ChatViewId) as ChatViewPane; if (chatViewPane) { // Handle different session types - if (sessionItem && (isLocalChatSessionItem(sessionItem) || sessionId.startsWith('history-'))) { + if (context.session && (isLocalChatSessionItem(context.session) || sessionId.startsWith('history-'))) { // For local sessions and history sessions, remove the 'history-' prefix if present const sessionIdWithoutHistory = sessionId.replace('history-', ''); // Load using the session ID directly await chatViewPane.loadSession(sessionIdWithoutHistory); } else { // For external provider sessions, create a URI and load using that - const providerType = sessionItem && (sessionItem as any).provider?.chatSessionType || 'external'; + const providerType = context.session.provider?.chatSessionType || 'external'; const sessionUri = ChatSessionUri.forSession(providerType, sessionId); await chatViewPane.loadSession(sessionUri); } @@ -501,6 +472,15 @@ MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { ) }); +MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { + command: { + id: OpenChatSessionInNewWindowAction.id, + title: localize('openSessionInNewWindow', "Open in New Window") + }, + group: 'navigation', + order: 1, +}); + MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { command: { id: OpenChatSessionInNewEditorGroupAction.id, @@ -530,4 +510,3 @@ MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { order: 1, when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), }); - diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 39bb8fd7ed9..148c3c03304 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -74,7 +74,7 @@ import { registerChatExportActions } from './actions/chatImportExport.js'; import { registerLanguageModelActions } from './actions/chatLanguageModelActions.js'; import { registerMoveActions } from './actions/chatMoveActions.js'; import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; -import { DeleteChatSessionAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; +import { DeleteChatSessionAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInNewWindowAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; import { registerChatTitleActions } from './actions/chatTitleActions.js'; import { registerChatToolActions } from './actions/chatToolActions.js'; import { ChatTransferContribution } from './actions/chatTransfer.js'; @@ -957,6 +957,7 @@ registerPromptFileContributions(); registerAction2(ConfigureToolSets); registerAction2(RenameChatSessionAction); registerAction2(DeleteChatSessionAction); +registerAction2(OpenChatSessionInNewWindowAction); registerAction2(OpenChatSessionInNewEditorGroupAction); registerAction2(OpenChatSessionInSidebarAction); registerAction2(ToggleChatSessionsDescriptionDisplayAction); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts index 13ee51b4c9e..68e1f4d4e82 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.ts @@ -80,7 +80,7 @@ import { IChatEditorOptions } from './chatEditor.js'; import { ChatEditorInput } from './chatEditorInput.js'; import { allowedChatMarkdownHtmlTags } from './chatMarkdownRenderer.js'; import { ChatSessionTracker } from './chatSessions/chatSessionTracker.js'; -import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './chatSessions/common.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getChatSessionType, isChatSession } from './chatSessions/common.js'; import { ChatViewPane } from './chatViewPane.js'; import './media/chatSessions.css'; @@ -1695,6 +1695,19 @@ class SessionsViewPane extends ViewPane { } try { + // Check first if we already have an open editor for this session + const sessionWithProvider = element as ChatSessionItemWithProvider; + sessionWithProvider.id = sessionWithProvider.id.replace('history-', ''); + const uri = ChatSessionUri.forSession(sessionWithProvider.provider.chatSessionType, sessionWithProvider.id); + const existingEditor = findExistingChatEditorByUri(uri, sessionWithProvider.id, this.editorGroupsService); + if (existingEditor) { + await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); + return; + } + if (this.chatWidgetService.getWidgetBySessionId(sessionWithProvider.id)) { + return; + } + if (element.id === historyNode.id) { // Don't try to open the "Show history..." node itself return; @@ -1702,13 +1715,11 @@ class SessionsViewPane extends ViewPane { // Handle history items first if (element.id.startsWith('history-')) { - const sessionId = element.id.substring('history-'.length); - const sessionWithProvider = element as ChatSessionItemWithProvider; // For local history sessions, use ChatEditorInput approach if (sessionWithProvider.provider.chatSessionType === 'local') { const options: IChatEditorOptions = { - target: { sessionId }, + target: { sessionId: sessionWithProvider.id }, pinned: true, // Add a marker to indicate this session was opened from history ignoreInView: true, @@ -1724,7 +1735,7 @@ class SessionsViewPane extends ViewPane { preserveFocus: true, }; await this.editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, sessionId), + resource: ChatSessionUri.forSession(providerType, sessionWithProvider.id), options, }); } @@ -1748,7 +1759,6 @@ class SessionsViewPane extends ViewPane { } // For other session types, open as a new chat editor - const sessionWithProvider = element as ChatSessionItemWithProvider; const sessionId = element.id; const providerType = sessionWithProvider.provider.chatSessionType; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 0b8a600def2..56c1a5dd389 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from '../../../../../base/common/network.js'; +import { URI } from '../../../../../base/common/uri.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -60,3 +62,21 @@ export function getChatSessionType(editor: ChatEditorInput): string { // Default to 'local' for vscode-chat-editor scheme or when type cannot be determined return 'local'; } + +/** + * Find existing chat editors that have the same session URI (for external providers) + */ +export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, editorGroupsService: IEditorGroupsService): { editor: ChatEditorInput; groupId: number } | undefined { + if (!sessionUri) { + return undefined; + } + + for (const group of editorGroupsService.groups) { + for (const editor of group.editors) { + if (editor instanceof ChatEditorInput && (editor.resource.toString() === sessionUri.toString() || editor.sessionId === sessionId)) { + return { editor, groupId: group.id }; + } + } + } + return undefined; +} From ad950e1a13310a21926b57b5408f9d4a41004950 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:04:09 +0000 Subject: [PATCH 0143/4355] Support referencing selected file(s) as inputs to MCP Prompt arguments (#265874) * Initial plan * Add active file and selected text support to MCP prompt arguments Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * Add basic test for active file functionality in MCP prompt arguments Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * Final refinements to active file completions implementation Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * cleanup --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> Co-authored-by: Connor Peet --- .../mcp/browser/mcpPromptArgumentPick.ts | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts index 05044e81fa5..0e9245e02a2 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts @@ -8,10 +8,11 @@ import { disposableTimeout, RunOnceScheduler, timeout } from '../../../../base/c import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, derived, IObservable, ObservablePromise, observableValue } from '../../../../base/common/observable.js'; +import { autorun, derived, IObservable, ObservablePromise, observableSignalFromEvent, observableValue } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js'; import { IModelService } from '../../../../editor/common/services/model.js'; @@ -23,6 +24,7 @@ import { IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickSeparator } f import { ICommandDetectionCapability, TerminalCapability } from '../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; import { QueryBuilder } from '../../../services/search/common/queryBuilder.js'; import { ISearchService } from '../../../services/search/common/search.js'; import { ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../terminal/browser/terminal.js'; @@ -32,6 +34,7 @@ import { MCP } from '../common/modelContextProtocol.js'; type PickItem = IQuickPickItem & ( | { action: 'text' | 'command' | 'suggest' } | { action: 'file'; uri: URI } + | { action: 'selectedText'; uri: URI; selectedText: string } ); const SHELL_INTEGRATION_TIMEOUT = 5000; @@ -56,6 +59,8 @@ export class McpPromptArgumentPick extends Disposable { @ILanguageService private readonly _languageService: ILanguageService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IEditorService private readonly _editorService: IEditorService, ) { super(); this.quickPick = this._register(_quickInputService.createQuickPick({ useSeparators: true })); @@ -112,6 +117,10 @@ export class McpPromptArgumentPick extends Disposable { name: localize('mcp.arg.suggestions', 'Suggestions'), observer: this._promptCompletions(arg, input$, argsSoFar), }, + { + name: localize('mcp.arg.activeFiles', 'Active File'), + observer: this._activeFileCompletions(), + }, { name: localize('mcp.arg.files', 'Files'), observer: this._fileCompletions(input$), @@ -213,6 +222,8 @@ export class McpPromptArgumentPick extends Disposable { case 'file': quickPick.busy = true; return { type: 'arg', value: await this._fileService.readFile(value.uri).then(c => c.value.toString()) }; + case 'selectedText': + return { type: 'arg', value: value.selectedText }; default: assertNever(value); } @@ -260,6 +271,62 @@ export class McpPromptArgumentPick extends Disposable { }); } + private _activeFileCompletions() { + const activeEditorChange = observableSignalFromEvent(this, this._editorService.onDidActiveEditorChange); + const activeEditor = derived(reader => { + activeEditorChange.read(reader); + return this._codeEditorService.getActiveCodeEditor(); + }); + + const resourceObs = activeEditor + .map(e => e ? observableSignalFromEvent(this, e.onDidChangeModel).map(() => e.getModel()?.uri) : undefined) + .map((o, reader) => o?.read(reader)); + const selectionObs = activeEditor + .map(e => e ? observableSignalFromEvent(this, e.onDidChangeCursorSelection).map(() => ({ range: e.getSelection(), model: e.getModel() })) : undefined) + .map((o, reader) => o?.read(reader)); + + return derived(reader => { + const resource = resourceObs.read(reader); + if (!resource) { + return { busy: false, picks: [] }; + } + + const items: PickItem[] = []; + + // Add active file option + items.push({ + id: 'active-file', + label: localize('mcp.arg.activeFile', 'Active File'), + description: this._labelService.getUriLabel(resource), + iconClasses: getIconClasses(this._modelService, this._languageService, resource), + uri: resource, + action: 'file', + }); + + const selection = selectionObs.read(reader); + // Add selected text option if there's a selection + if (selection && selection.model && selection.range && !selection.range.isEmpty()) { + const selectedText = selection.model.getValueInRange(selection.range); + const lineCount = selection.range.endLineNumber - selection.range.startLineNumber + 1; + const description = lineCount === 1 + ? localize('mcp.arg.selectedText.singleLine', 'line {0}', selection.range.startLineNumber) + : localize('mcp.arg.selectedText.multiLine', '{0} lines', lineCount); + + items.push({ + id: 'selected-text', + label: localize('mcp.arg.selectedText', 'Selected Text'), + description, + selectedText, + iconClass: ThemeIcon.asClassName(Codicon.selection), + uri: resource, + action: 'selectedText', + }); + } + + return { picks: items, busy: false }; + }); + } + private _asyncCompletions(input: IObservable, mapper: (input: string, token: CancellationToken) => Promise): IObservable<{ busy: boolean; picks: PickItem[] | undefined }> { const promise = derived(reader => { const queryValue = input.read(reader); From 0089fbe63abb619a7f7fcca070cbc6d0ad90bbb1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 9 Sep 2025 15:21:16 -0700 Subject: [PATCH 0144/4355] Only allow open with user permission --- .../links/browser/terminalLinkManager.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index a862d2146db..5514d5ab725 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -105,7 +105,7 @@ export class TerminalLinkManager extends DisposableStore { })); this._xterm.options.linkHandler = { allowNonHttpProtocols: true, - activate: (event, text) => { + activate: async (event, text) => { if (!this._isLinkActivationModifierDown(event)) { return; } @@ -115,18 +115,27 @@ export class TerminalLinkManager extends DisposableStore { } const scheme = text.substring(0, colonIndex); if (terminalConfigurationService.config.allowedLinkSchemes.indexOf(scheme) === -1) { - notificationService.prompt(Severity.Warning, nls.localize('scheme', 'Opening URIs can be insecure, do you want to allow opening links with the scheme {0}?', scheme), [ - { - label: nls.localize('allow', 'Allow {0}', scheme), - run: () => { - const allowedLinkSchemes = [ - ...terminalConfigurationService.config.allowedLinkSchemes, - scheme - ]; - this._configurationService.updateValue(`terminal.integrated.allowedLinkSchemes`, allowedLinkSchemes); + const userAllowed = await new Promise((resolve) => { + notificationService.prompt(Severity.Warning, nls.localize('scheme', 'Opening URIs can be insecure, do you want to allow opening links with the scheme {0}?', scheme), [ + { + label: nls.localize('allow', 'Allow {0}', scheme), + run: () => { + const allowedLinkSchemes = [ + ...terminalConfigurationService.config.allowedLinkSchemes, + scheme + ]; + this._configurationService.updateValue(`terminal.integrated.allowedLinkSchemes`, allowedLinkSchemes); + resolve(true); + } } - } - ]); + ], { + onCancel: () => resolve(false) + }); + }); + + if (!userAllowed) { + return; + } } this._openers.get(TerminalBuiltinLinkType.Url)?.open({ type: TerminalBuiltinLinkType.Url, From 34e38b4a78a751d006b99acee1a95d76117fec7b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Sep 2025 15:30:18 -0700 Subject: [PATCH 0145/4355] mcp: support SEP-1034 default values in elicitation (#265937) Closes https://github.com/microsoft/vscode/issues/265935 --- .../contrib/mcp/browser/mcpElicitationService.ts | 15 +++++++++++++-- .../contrib/mcp/common/modelContextProtocol.ts | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts index aefebec23ba..622ba695d67 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts @@ -103,7 +103,7 @@ export class McpElicitationService implements IMcpElicitationService { let result: { type: 'value'; value: string | number | boolean | undefined } | { type: 'back' } | { type: 'cancel' }; if (schema.type === 'boolean') { - result = await this._handleEnumField(quickPick, { ...schema, type: 'string', enum: ['true', 'false'] }, isRequired, store, token); + result = await this._handleEnumField(quickPick, { ...schema, type: 'string', enum: ['true', 'false'], default: schema.default ? String(schema.default) : undefined }, isRequired, store, token); if (result.type === 'value') { result.value = result.value === 'true' ? true : false; } } else if (schema.type === 'string' && 'enum' in schema) { result = await this._handleEnumField(quickPick, schema, isRequired, store, token); @@ -168,6 +168,9 @@ export class McpElicitationService implements IMcpElicitationService { quickPick.items = items; quickPick.canSelectMany = false; + if (schema.default !== undefined) { + quickPick.activeItems = items.filter(item => item.id === schema.default); + } return new Promise<{ type: 'value'; value: string | undefined } | { type: 'back' } | { type: 'cancel' }>(resolve => { store.add(token.onCancellationRequested(() => resolve({ type: 'cancel' }))); @@ -203,8 +206,13 @@ export class McpElicitationService implements IMcpElicitationService { } } else { quickPick.validationMessage = ''; + + if (schema.default) { + items.push({ id: '$default', label: `${schema.default}`, description: localize('mcp.elicit.useDefault', 'Default value') }); + } } + if (quickPick.validationMessage) { quickPick.severity = Severity.Warning; } else { @@ -228,8 +236,11 @@ export class McpElicitationService implements IMcpElicitationService { store.add(token.onCancellationRequested(() => resolve({ type: 'cancel' }))); store.add(quickPick.onDidChangeValue(updateItems)); store.add(quickPick.onDidAccept(() => { - if (!quickPick.selectedItems[0].id) { + const id = quickPick.selectedItems[0].id; + if (!id) { resolve({ type: 'value', value: undefined }); + } else if (id === '$default') { + resolve({ type: 'value', value: String(schema.default) }); } else if (!quickPick.validationMessage) { resolve({ type: 'value', value: quickPick.value }); } diff --git a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts index 4889afe96a1..8fbcfe3dcda 100644 --- a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts +++ b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts @@ -1364,6 +1364,7 @@ export namespace MCP { minLength?: number; maxLength?: number; format?: "email" | "uri" | "date" | "date-time"; + default?: string; } export interface NumberSchema { @@ -1372,6 +1373,7 @@ export namespace MCP { description?: string; minimum?: number; maximum?: number; + default?: number; } export interface BooleanSchema { @@ -1387,6 +1389,7 @@ export namespace MCP { description?: string; enum: string[]; enumNames?: string[]; // Display names for enum values + default?: string; } /** From e35cce3b5422ff1f48403b38d9807a16258f3f91 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 18:31:57 -0400 Subject: [PATCH 0146/4355] add opt-in auto reply to terminal prompt setting (#265908) --- .../browser/runInTerminalToolTelemetry.ts | 2 + .../chatAgentTools/browser/taskHelpers.ts | 4 +- .../browser/tools/monitoring/outputMonitor.ts | 50 ++++++++++++++----- .../browser/tools/runInTerminalTool.ts | 4 ++ .../terminalChatAgentToolsConfiguration.ts | 8 ++- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts index fc826746258..4a456843daa 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts @@ -106,6 +106,8 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: number | undefined; inputToolManualRejectCount: number | undefined; inputToolManualChars: number | undefined; + inputToolAutoAcceptCount: number | undefined; + inputToolAutoChars: number | undefined; inputToolManualShownCount: number | undefined; }) { type TelemetryEvent = { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index d7a90539e28..a1b395bc630 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -166,7 +166,7 @@ export async function collectTerminalResults( inputToolManualChars: number; inputToolManualShownCount: number; }>> { - const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; inputToolManualShownCount: number }> = []; + const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; inputToolAutoAcceptCount: number; inputToolAutoChars: number; inputToolManualShownCount: number }> = []; if (token.isCancellationRequested) { return results; } @@ -192,6 +192,8 @@ export async function collectTerminalResults( inputToolManualAcceptCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualRejectCount ?? 0, inputToolManualChars: outputMonitor.outputMonitorTelemetryCounters.inputToolManualChars ?? 0, + inputToolAutoAcceptCount: outputMonitor.outputMonitorTelemetryCounters.inputToolAutoAcceptCount ?? 0, + inputToolAutoChars: outputMonitor.outputMonitorTelemetryCounters.inputToolAutoChars ?? 0, inputToolManualShownCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualShownCount ?? 0, }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index c404b3774de..12a72ad1745 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -25,6 +25,8 @@ import { detectsInputRequiredPattern } from '../../executeStrategy/executeStrate import { ILinkLocation } from '../../taskHelpers.js'; import { IConfirmationPrompt, IExecution, IPollingResult, OutputMonitorState, PollingConsts } from './types.js'; import { getTextResponseFromStream } from './utils.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; export interface IOutputMonitor extends Disposable { readonly pollingResult: IPollingResult & { pollDurationMs: number } | undefined; @@ -37,6 +39,8 @@ export interface IOutputMonitorTelemetryCounters { inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; + inputToolAutoAcceptCount: number; + inputToolAutoChars: number; inputToolManualShownCount: number; } @@ -57,6 +61,8 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { inputToolManualAcceptCount: 0, inputToolManualRejectCount: 0, inputToolManualChars: 0, + inputToolAutoAcceptCount: 0, + inputToolAutoChars: 0, inputToolManualShownCount: 0 }; get outputMonitorTelemetryCounters(): Readonly { return this._outputMonitorTelemetryCounters; } @@ -73,7 +79,8 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, @ITaskService private readonly _taskService: ITaskService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -152,12 +159,16 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { private async _handleIdleState(token: CancellationToken): Promise<{ resources?: ILinkLocation[]; modelOutputEvalResponse?: string; shouldContinuePollling: boolean }> { const confirmationPrompt = await this._determineUserInputOptions(this._execution, token); - const suggestedOption = await this._selectAndHandleOption(confirmationPrompt, token); + const suggestedOptionResult = await this._selectAndHandleOption(confirmationPrompt, token); if (confirmationPrompt?.options.length) { - const confirmed = await this._confirmRunInTerminal(suggestedOption ?? confirmationPrompt.options[0], this._execution, confirmationPrompt); + if (suggestedOptionResult?.sentToTerminal) { + // Continue polling as we sent the input + return { shouldContinuePollling: true }; + } + const confirmed = await this._confirmRunInTerminal(suggestedOptionResult?.suggestedOption ?? confirmationPrompt.options[0], this._execution, confirmationPrompt); if (confirmed) { - // Continue polling + // Continue polling as we sent the input return { shouldContinuePollling: true }; } else { // User declined @@ -429,7 +440,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { private async _selectAndHandleOption( confirmationPrompt: IConfirmationPrompt | undefined, token: CancellationToken, - ): Promise { + ): Promise { if (!confirmationPrompt?.options.length) { return undefined; } @@ -460,15 +471,24 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { ], {}, token); const suggestedOption = (await getTextResponseFromStream(response)).trim(); - if (suggestedOption) { - const index = confirmationPrompt.options.indexOf(suggestedOption); - const validOption = confirmationPrompt.options.find(opt => suggestedOption.replace(/['"`]/g, '').trim() === opt.replace(/['"`]/g, '').trim()); - if (validOption && index > -1) { - const description = confirmationPrompt.descriptions?.[index]; - return description ? { description, option: validOption } : validOption; - } + if (!suggestedOption) { + return; } - return undefined; + const parsed = suggestedOption.replace(/['"`]/g, '').trim(); + const index = confirmationPrompt.options.indexOf(parsed); + const validOption = confirmationPrompt.options.find(opt => parsed === opt.replace(/['"`]/g, '').trim()); + if (!validOption || index === -1) { + return; + } + let sentToTerminal = false; + if (this._configurationService.getValue(TerminalChatAgentToolsSettingId.AutoReplyToPrompts)) { + await this._execution.instance.sendText(validOption, true); + this._outputMonitorTelemetryCounters.inputToolAutoAcceptCount++; + this._outputMonitorTelemetryCounters.inputToolAutoChars += validOption?.length || 0; + sentToTerminal = true; + } + const description = confirmationPrompt.descriptions?.[index]; + return description ? { suggestedOption: { description, option: validOption }, sentToTerminal } : { suggestedOption: validOption, sentToTerminal }; } private async _confirmRunInTerminal(suggestedOption: SuggestedOption, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { @@ -557,3 +577,7 @@ function getMoreActions(suggestedOption: SuggestedOption, confirmationPrompt: IC } type SuggestedOption = string | { description: string; option: string }; +interface ISuggestedOptionResult { + suggestedOption?: SuggestedOption; + sentToTerminal?: boolean; +} 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 67b662e1bb1..2c54ac75b39 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -492,6 +492,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolManualAcceptCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualAcceptCount, inputToolManualRejectCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualRejectCount, inputToolManualChars: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualChars, + inputToolAutoAcceptCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolAutoAcceptCount, + inputToolAutoChars: outputMonitor?.outputMonitorTelemetryCounters.inputToolAutoChars, inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualShownCount, }); } @@ -572,6 +574,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolManualAcceptCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualAcceptCount, inputToolManualRejectCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualRejectCount, inputToolManualChars: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualChars, + inputToolAutoAcceptCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolAutoAcceptCount, + inputToolAutoChars: outputMonitor?.outputMonitorTelemetryCounters?.inputToolAutoChars, inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualShownCount, }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 8f3f5ed0f5c..50cd0a764cf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -12,8 +12,8 @@ import { TerminalSettingId } from '../../../../../platform/terminal/common/termi export const enum TerminalChatAgentToolsSettingId { EnableAutoApprove = 'chat.tools.terminal.enableAutoApprove', AutoApprove = 'chat.tools.terminal.autoApprove', - ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout', + AutoReplyToPrompts = 'chat.tools.terminal.experimental.autoReplyToPrompts', DeprecatedAutoApproveCompatible = 'chat.agent.terminal.autoApprove', DeprecatedAutoApprove1 = 'chat.agent.terminal.allowList', @@ -296,6 +296,12 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Tue, 9 Sep 2025 16:50:28 -0700 Subject: [PATCH 0147/4355] mcp: emit resource/links with their own data type (#265944) Goes with https://github.com/microsoft/vscode-copilot-chat/pull/995 Closes https://github.com/microsoft/vscode/issues/265391 Closes https://github.com/microsoft/vscode/issues/265392 --- .../mcpLanguageModelToolContribution.ts | 24 +++++++++++-------- .../workbench/contrib/mcp/common/mcpTypes.ts | 7 ++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index 9b2c66789cb..d65a14dcd68 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -24,7 +24,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ChatResponseResource, getAttachableImageExtension } from '../../chat/common/chatModel.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultInputOutputDetails, ToolDataSource, ToolProgress, ToolSet } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; -import { IMcpServer, IMcpService, IMcpTool, LazyCollectionState, McpResourceURI, McpServerCacheState } from './mcpTypes.js'; +import { IMcpServer, IMcpService, IMcpTool, IMcpToolResourceLinkContents, LazyCollectionState, McpResourceURI, McpServerCacheState, McpToolResourceLinkMimeType } from './mcpTypes.js'; import { mcpServerToSourceData } from './mcpTypesUtils.js'; interface ISyncedToolData { @@ -257,6 +257,17 @@ class McpToolImplementation implements IToolImpl { } }; + const addAsLinkedResource = (uri: URI, mimeType?: string) => { + const json: IMcpToolResourceLinkContents = { uri, underlyingMimeType: mimeType }; + result.content.push({ + kind: 'data', + value: { + mimeType: McpToolResourceLinkMimeType, + data: VSBuffer.fromString(JSON.stringify(json)), + }, + }); + }; + const isForModel = audience.includes('assistant'); if (item.type === 'text') { details.output.push({ type: 'embed', isText: true, value: item.text }); @@ -289,10 +300,7 @@ class McpToolImplementation implements IToolImpl { } }); } else { - result.content.push({ - kind: 'text', - value: `The tool returns a resource which can be read from the URI ${uri}\n`, - }); + addAsLinkedResource(uri, item.mimeType); } } } else if (item.type === 'resource') { @@ -311,11 +319,7 @@ class McpToolImplementation implements IToolImpl { if (isForModel) { const permalink = invocation.chatRequestId && invocation.context && ChatResponseResource.createUri(invocation.context.sessionId, invocation.chatRequestId, invocation.callId, result.content.length, basename(uri)); - - result.content.push({ - kind: 'text', - value: 'text' in item.resource ? item.resource.text : `The tool returns a resource which can be read from the URI ${permalink || uri}\n`, - }); + addAsLinkedResource(permalink || uri, item.resource.mimeType); } } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 237167a4f11..52216b4a994 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -842,3 +842,10 @@ export interface IMcpElicitationService { } export const IMcpElicitationService = createDecorator('IMcpElicitationService'); + +export const McpToolResourceLinkMimeType = 'application/vnd.code.resource-link'; + +export interface IMcpToolResourceLinkContents { + uri: UriComponents; + underlyingMimeType?: string; +} From 061d0ed84b9be3ae7f1b820cee4cf40d1a4671cc Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 9 Sep 2025 16:57:49 -0700 Subject: [PATCH 0148/4355] Add configuration for todo list widget position in chat and enable todo tool by default (#265942) * Add configuration for todo list widget position in chat and enable todo tool by default * Add mock implementation for IChatTodoListService in InlineChatController tests --- .../contrib/chat/browser/chat.contribution.ts | 14 ++++++++++---- .../workbench/contrib/chat/browser/chatWidget.ts | 7 +++---- src/vs/workbench/contrib/chat/common/constants.ts | 2 ++ .../chat/common/tools/manageTodoListTool.ts | 7 ------- .../test/browser/inlineChatController.test.ts | 5 +++++ 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 148c3c03304..43e2ef63a2a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -615,10 +615,16 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - 'chat.todoListTool.enabled': { - type: 'boolean', - default: false, - description: nls.localize('chat.todoListTool.enabled', "Enables todo lists in chat, which the agent uses as a tool for planning, progress tracking, and context management for complex development workflows."), + 'chat.todoListWidget.position': { + type: 'string', + default: 'default', + enum: ['default', 'off', 'chat-input'], + enumDescriptions: [ + nls.localize('chat.todoListWidget.position.default', "Show todo list widget in the default position at the top of the chat panel."), + nls.localize('chat.todoListWidget.position.off', "Hide the todo list widget."), + nls.localize('chat.todoListWidget.position.chatInput', "Show todo list widget near the chat input.") + ], + description: nls.localize('chat.todoListWidget.position', "Controls the position of the todo list widget in chat."), tags: ['experimental'], experiment: { mode: 'auto' diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fd0fbffc771..652a7929c43 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -60,14 +60,13 @@ import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariable import { ChatViewModel, IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { IChatInputState } from '../common/chatWidgetHistoryService.js'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind, TodoListWidgetPositionSettingId } from '../common/constants.js'; import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/languageModelToolsService.js'; import { ComputeAutomaticInstructions } from '../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; import { type TPromptMetadata } from '../common/promptSyntax/parsers/promptHeader/promptHeader.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptParserResult, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; -import { TodoListToolSettingId } from '../common/tools/manageTodoListTool.js'; import { handleModeSwitch } from './actions/chatActions.js'; import { ChatTreeItem, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions, ChatViewId } from './chat.js'; import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; @@ -1128,8 +1127,8 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - const isChatTodoListToolEnabled = this.configurationService.getValue(TodoListToolSettingId) === true; - if (!isChatTodoListToolEnabled) { + const todoListWidgetPosition = this.configurationService.getValue(TodoListWidgetPositionSettingId) || 'default'; + if (todoListWidgetPosition !== 'default') { return; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 2ad22fa60fd..981ad945f44 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -82,3 +82,5 @@ export namespace ChatAgentLocation { } export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); + +export const TodoListWidgetPositionSettingId = 'chat.todoListWidget.position'; diff --git a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts index 4a53003264c..fe1eae3714c 100644 --- a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts @@ -19,12 +19,9 @@ import { import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IChatTodo, IChatTodoListService } from '../chatTodoListService.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { IsSimulationContext } from '../../../../../platform/contextkey/common/contextkeys.js'; import { localize } from '../../../../../nls.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -export const TodoListToolSettingId = 'chat.todoListTool.enabled'; export const TodoListToolWriteOnlySettingId = 'chat.todoListTool.writeOnly'; export const ManageTodoListToolToolId = 'manage_todo_list'; @@ -78,10 +75,6 @@ export function createManageTodoListToolData(writeOnly: boolean): IToolData { return { id: ManageTodoListToolToolId, toolReferenceName: 'todos', - when: ContextKeyExpr.or( - ContextKeyExpr.equals(`config.${TodoListToolSettingId}`, true), - IsSimulationContext - ), canBeReferencedInPrompt: true, icon: ThemeIcon.fromId(Codicon.checklist.id), displayName: localize('tool.manageTodoList.displayName', 'Manage and track todo items for task planning'), diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 6671c88eb91..82bdc240bef 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -59,6 +59,7 @@ import { IChatEditingService, IChatEditingSession } from '../../../chat/common/c import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../../../chat/common/chatLayoutService.js'; import { IChatModeService } from '../../../chat/common/chatModes.js'; +import { IChatTodo, IChatTodoListService } from '../../../chat/common/chatTodoListService.js'; import { IChatProgress, IChatService } from '../../../chat/common/chatService.js'; import { ChatService } from '../../../chat/common/chatServiceImpl.js'; import { ChatSlashCommandService, IChatSlashCommandService } from '../../../chat/common/chatSlashCommands.js'; @@ -218,6 +219,10 @@ suite('InlineChatController', function () { [IChatEntitlementService, new class extends mock() { }], [IChatModeService, new SyncDescriptor(MockChatModeService)], [IChatLayoutService, new SyncDescriptor(ChatLayoutService)], + [IChatTodoListService, new class extends mock() { + override getTodos(sessionId: string): IChatTodo[] { return []; } + override setTodos(sessionId: string, todos: IChatTodo[]): void { } + }], ); instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection)); From da649f89bdeaddb60dd46a471826dfac7d5e3170 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 9 Sep 2025 18:11:33 -0700 Subject: [PATCH 0149/4355] Remove simulation context references from environment (#265955) Remove simulation context references from environment and context key services --- src/vs/editor/standalone/browser/standaloneServices.ts | 1 - src/vs/platform/contextkey/common/contextkeys.ts | 2 -- src/vs/platform/environment/common/environment.ts | 3 --- src/vs/platform/environment/common/environmentService.ts | 4 ---- src/vs/workbench/browser/contextkeys.ts | 5 +---- 5 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index b64fa042aac..220776c7154 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -243,7 +243,6 @@ class StandaloneEnvironmentService implements IEnvironmentService { readonly disableTelemetry: boolean = false; readonly serviceMachineIdResource: URI = URI.from({ scheme: 'monaco', authority: 'serviceMachineIdResource' }); readonly policyFile?: URI | undefined = undefined; - readonly isSimulation: boolean | undefined = undefined; } class StandaloneDialogService implements IDialogService { diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index 49b75a22ab4..c256dba0aa0 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -19,7 +19,5 @@ export const IsMobileContext = new RawContextKey('isMobile', isMobile, export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true); export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code")); -export const IsSimulationContext = new RawContextKey('isSimulation', false, localize('isSimulation', "Whether the application runs in simulation mode")); - export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box")); diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 0b9d893bc4b..c5f10d53040 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -91,9 +91,6 @@ export interface IEnvironmentService { // --- Policy policyFile?: URI; - // -- Simulation - isSimulation?: boolean; - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 918bf24c0cb..e60b83f27d4 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -264,10 +264,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get args(): NativeParsedArgs { return this._args; } - get isSimulation(): boolean { - return env['SIMULATION'] === '1'; - } - constructor( private readonly _args: NativeParsedArgs, private readonly paths: INativeEnvironmentPaths, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 88990c65172..7ad8b4b6ec7 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,7 +6,7 @@ import { Event } from '../../base/common/event.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../base/common/lifecycle.js'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js'; -import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext, IsSimulationContext } from '../../platform/contextkey/common/contextkeys.js'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js'; import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext } from '../common/contextkeys.js'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow, isEditableElement } from '../../base/browser/dom.js'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; @@ -106,9 +106,6 @@ export class WorkbenchContextKeysHandler extends Disposable { IsDevelopmentContext.bindTo(this.contextKeyService).set(isDevelopment); setConstantContextKey(IsDevelopmentContext.key, isDevelopment); - // Simulation mode - IsSimulationContext.bindTo(this.contextKeyService).set(!!this.environmentService.isSimulation); - // Product Service ProductQualityContext.bindTo(this.contextKeyService).set(this.productService.quality || ''); EmbedderIdentifierContext.bindTo(this.contextKeyService).set(productService.embedderIdentifier); From f1c3bd9771ee03939e1af0be00c39c095ff10ec5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 9 Sep 2025 21:44:00 -0400 Subject: [PATCH 0150/4355] add dictation actions to terminal overflow menu (#265920) fix #265805 --- .../contrib/terminal/browser/terminalMenus.ts | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 0fe5a353a57..35171618ffc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -18,6 +18,7 @@ import { TerminalContextKeys, TerminalContextKeyStrings } from '../common/termin import { terminalStrings } from '../common/terminalStrings.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { HasSpeechProvider } from '../../speech/common/speechService.js'; export const enum TerminalContextMenuGroup { Chat = '0_chat', @@ -98,7 +99,33 @@ export function setupTerminalMenus(): void { order: 4, when: TerminalContextKeys.processSupported } - } + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StartVoice, + title: localize('workbench.action.terminal.startVoice', "Start Dictation"), + }, + group: 'navigation', + order: 5, + when: HasSpeechProvider, + isHiddenByDefault: true + } + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StopVoice, + title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), + }, + group: 'navigation', + order: 6, + when: HasSpeechProvider, + isHiddenByDefault: true + } + }, ] ); @@ -726,6 +753,28 @@ export function setupTerminalMenus(): void { when: ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), isHiddenByDefault: true }); + MenuRegistry.appendMenuItem(menuId, { + command: { + id: TerminalCommandId.StartVoice, + title: localize('workbench.action.terminal.startVoice', "Start Dictation"), + icon: Codicon.run + }, + group: 'navigation', + order: 9, + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider), + isHiddenByDefault: true + }); + MenuRegistry.appendMenuItem(menuId, { + command: { + id: TerminalCommandId.StopVoice, + title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), + icon: Codicon.run + }, + group: 'navigation', + order: 10, + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider), + isHiddenByDefault: true + }); } } From b5ee064ee3cb97bdbf4cae4498b5d35e94817e44 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:24:00 -0700 Subject: [PATCH 0151/4355] More complete fix --- src/vs/base/browser/domSanitize.ts | 61 ++++++++++++++----- src/vs/base/test/browser/domSanitize.test.ts | 23 +++++-- .../browser/markdownDocumentRenderer.ts | 7 +++ .../browser/markdownDocumentRenderer.test.ts | 41 +++++++++++++ .../update/browser/releaseNotesEditor.ts | 4 +- 5 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index c4395b9d2e1..8e33b1579f7 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -113,22 +113,40 @@ function addDompurifyHook(hook: 'uponSanitizeElement' | 'uponSanitizeAttribute', return toDisposable(() => dompurify.removeHook(hook)); } +const fakeRelativeUrlProtocol = 'vscode-relative-path'; + +interface AllowedLinksConfig { + readonly override: readonly string[] | '*'; + readonly allowRelativePaths: boolean; +} + /** * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` * attributes are valid. */ -function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[] | '*', allowedMediaProtocols: readonly string[] | '*'): IDisposable { - // https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html - // build an anchor to map URLs to - const anchor = document.createElement('a'); - - function validateLink(value: string, allowedProtocols: readonly string[] | '*'): boolean { - if (allowedProtocols === '*') { +function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConfig, allowedMediaProtocols: AllowedLinksConfig): IDisposable { + function validateLink(value: string, allowedProtocols: AllowedLinksConfig): boolean { + if (allowedProtocols.override === '*') { return true; // allow all protocols } - anchor.href = value; - return allowedProtocols.includes(anchor.protocol.replace(/:$/, '')); + try { + const url = new URL(value, fakeRelativeUrlProtocol + '://'); + if (allowedProtocols.override.includes(url.protocol.replace(/:$/, ''))) { + return true; + } + + if (allowedProtocols.allowRelativePaths + && url.protocol === fakeRelativeUrlProtocol + ':' + && !value.trim().toLowerCase().startsWith(fakeRelativeUrlProtocol) + ) { + return true; + } + + return false; + } catch (e) { + return false; + } } dompurify.addHook('afterSanitizeAttributes', (node) => { @@ -137,12 +155,11 @@ function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[ if (node.hasAttribute(attr)) { const attrValue = node.getAttribute(attr) as string; if (attr === 'href') { - if (!attrValue.startsWith('#') && !validateLink(attrValue, allowedLinkProtocols)) { node.removeAttribute(attr); } - } else {// 'src' + } else { // 'src' if (!validateLink(attrValue, allowedMediaProtocols)) { node.removeAttribute(attr); } @@ -166,6 +183,7 @@ export interface SanitizeAttributeRule { shouldKeep: SanitizeAttributePredicate; } + export interface DomSanitizerConfig { /** * Configured the allowed html tags. @@ -190,6 +208,11 @@ export interface DomSanitizerConfig { readonly override?: readonly string[] | '*'; }; + /** + * If set, allows relative paths for links. + */ + readonly allowRelativeLinkPaths?: boolean; + /** * List of allowed protocols for `src` attributes. */ @@ -197,6 +220,11 @@ export interface DomSanitizerConfig { readonly override?: readonly string[] | '*'; }; + /** + * If set, allows relative paths for media (images, videos, etc). + */ + readonly allowRelativeMediaPaths?: boolean; + /** * If set, replaces unsupported tags with their plaintext representation instead of removing them. * @@ -277,9 +305,14 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine resolvedConfig.ALLOWED_ATTR = Array.from(allowedAttrNames); store.add(hookDomPurifyHrefAndSrcSanitizer( - config?.allowedLinkProtocols?.override ?? [Schemas.http, Schemas.https], - config?.allowedMediaProtocols?.override ?? [Schemas.http, Schemas.https])); - + { + override: config?.allowedLinkProtocols?.override ?? [Schemas.http, Schemas.https], + allowRelativePaths: config?.allowRelativeLinkPaths ?? false + }, + { + override: config?.allowedMediaProtocols?.override ?? [Schemas.http, Schemas.https], + allowRelativePaths: config?.allowRelativeMediaPaths ?? false + })); if (config?.replaceWithPlaintext) { store.add(addDompurifyHook('uponSanitizeElement', replaceWithPlainTextHook)); } diff --git a/src/vs/base/test/browser/domSanitize.test.ts b/src/vs/base/test/browser/domSanitize.test.ts index 437d42d7e75..0bcf386848f 100644 --- a/src/vs/base/test/browser/domSanitize.test.ts +++ b/src/vs/base/test/browser/domSanitize.test.ts @@ -99,9 +99,8 @@ suite('DomSanitize', () => { test('allows safe protocols for href', () => { const html = 'safe link'; const result = sanitizeHtml(html); - const str = result.toString(); - assert.ok(str.includes('href="https://example.com"')); + assert.ok(result.toString().includes('href="https://example.com"')); }); test('allows fragment links', () => { @@ -126,9 +125,22 @@ suite('DomSanitize', () => { const result = sanitizeHtml(html, { allowedMediaProtocols: { override: [Schemas.data] } }); - const str = result.toString(); - assert.ok(str.includes('src="data:image/png;base64,')); + assert.ok(result.toString().includes('src="data:image/png;base64,')); + }); + + test('Removes relative paths for img src by default', () => { + const html = ''; + const result = sanitizeHtml(html); + assert.strictEqual(result.toString(), ''); + }); + + test('Can allow relative paths for image', () => { + const html = ''; + const result = sanitizeHtml(html, { + allowRelativeMediaPaths: true, + }); + assert.strictEqual(result.toString(), ''); }); test('Supports dynamic attribute sanitization', () => { @@ -145,8 +157,7 @@ suite('DomSanitize', () => { ] } }); - const str = result.toString(); - assert.strictEqual(str, '
text1
text2
'); + assert.strictEqual(result.toString(), '
text1
text2
'); }); test('Supports changing attributes in dynamic sanitization', () => { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 12994af6ff6..032054403aa 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -167,7 +167,9 @@ function sanitize(documentContent: string, sanitizerConfig: MarkdownDocumentSani allowedLinkProtocols: { override: sanitizerConfig?.allowedLinkProtocols?.override ?? defaultAllowedLinkProtocols, }, + allowRelativeLinkPaths: sanitizerConfig?.allowRelativeLinkPaths, allowedMediaProtocols: sanitizerConfig?.allowedMediaProtocols, + allowRelativeMediaPaths: sanitizerConfig?.allowRelativeMediaPaths, allowedTags: { override: allowedMarkdownHtmlTags, augment: sanitizerConfig?.allowedTags?.augment @@ -191,12 +193,17 @@ interface MarkdownDocumentSanitizerConfig { readonly allowedLinkProtocols?: { readonly override: readonly string[] | '*'; }; + readonly allowRelativeLinkPaths?: boolean; + readonly allowedMediaProtocols?: { readonly override: readonly string[] | '*'; }; + readonly allowRelativeMediaPaths?: boolean; + readonly allowedTags?: { readonly augment: readonly string[]; }; + readonly allowedAttributes?: { readonly augment: readonly string[]; }; diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts new file mode 100644 index 00000000000..262ec4e287e --- /dev/null +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { createCodeEditorServices } from '../../../../../editor/test/browser/testCodeEditor.js'; +import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; +import { renderMarkdownDocument } from '../../browser/markdownDocumentRenderer.js'; + + +suite('Markdown Document Renderer Test', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + let extensionService: IExtensionService; + let languageService: ILanguageService; + + setup(() => { + instantiationService = createCodeEditorServices(store); + extensionService = instantiationService.get(IExtensionService); + languageService = instantiationService.get(ILanguageService); + }); + + test('Should remove images with relative paths by default', async () => { + const result = await renderMarkdownDocument('![alt](src/img.png)', extensionService, languageService, {}); + assert.strictEqual(result.toString(), `

alt

\n`); + }); + + test('Can enable images with relative paths using setting', async () => { + const result = await renderMarkdownDocument('![alt](src/img.png)', extensionService, languageService, { + sanitizerConfig: { + allowRelativeMediaPaths: true, + } + }); + + assert.strictEqual(result.toString(), `

alt

\n`); + }); +}); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 07fcc7d812f..88b67f560c8 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -267,9 +267,7 @@ export class ReleaseNotesManager extends Disposable { const content = await renderMarkdownDocument(fileContent.text, this._extensionService, this._languageService, { sanitizerConfig: { - allowedMediaProtocols: { - override: '*' // TODO: remove once we can use to find real resource locations - }, + allowRelativeMediaPaths: true, allowedLinkProtocols: { override: [Schemas.http, Schemas.https, Schemas.command] } From 53b17d7d1daa773ccc0878a0094d127783661e17 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Sep 2025 08:41:06 +0200 Subject: [PATCH 0152/4355] refactor - remove `stripWildcards` function and update normalization logic (#265987) --- src/vs/base/common/fuzzyScorer.ts | 10 +++++++--- src/vs/base/common/strings.ts | 4 ---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index dfb09cc716b..6d07f13b2d5 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -9,7 +9,7 @@ import { createMatches as createFuzzyMatches, fuzzyScore, IMatch, isUpper, match import { hash } from './hash.js'; import { sep } from './path.js'; import { isLinux, isWindows } from './platform.js'; -import { equalsIgnoreCase, stripWildcards } from './strings.js'; +import { equalsIgnoreCase } from './strings.js'; //#region Fuzzy scorer @@ -900,8 +900,12 @@ function normalizeQuery(original: string): { pathNormalized: string; normalized: pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash } - // we remove quotes here because quotes are used for exact match search - const normalized = stripWildcards(pathNormalized).replace(/\s|"/g, ''); + // remove certain characters that help find better results: + // - quotes: are used for exact match search + // - wildcards: are used for fuzzy matching + // - whitespace: are used to separate queries + // - ellipsis: sometimes used to indicate any path segments + const normalized = pathNormalized.replace(/[\*\u2026\s"]/g, ''); return { pathNormalized, diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index ca0d0ed6b2f..aae729560f8 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -192,10 +192,6 @@ export function convertSimple2RegExpPattern(pattern: string): string { return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); } -export function stripWildcards(pattern: string): string { - return pattern.replace(/[\*\u2026]/g, ''); -} - export interface RegExpOptions { matchCase?: boolean; wholeWord?: boolean; From ae162b2704bcb9c571bab23151509202aeb39b0d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 10 Sep 2025 00:35:13 -0700 Subject: [PATCH 0153/4355] Don't try to saveState when there is no state to save (#265957) This causes the chat viewpane to overwrite the real state with the widget's default state Fix #265802 --- src/vs/workbench/contrib/chat/browser/chatViewPane.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 3ad7f758cfb..cc1148d6fcb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -285,9 +285,9 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } override saveState(): void { - if (this._widget) { - // Since input history is per-provider, this is handled by a separate service and not the memento here. - // TODO multiple chat views will overwrite each other + // Don't do saveState when no widget, or no viewModel in which case the state has not yet been restored - + // in that case the default state would overwrite the real state + if (this._widget?.viewModel) { this._widget.saveState(); this.updateViewState(); From a0444aa4582e4f3059e942ddbef02f28c936bf81 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 10 Sep 2025 10:58:40 +0200 Subject: [PATCH 0154/4355] transfer sticky scroll check into candidate provider (#265854) --- .../browser/stickyScrollController.ts | 31 +++++++------------ .../browser/stickyScrollProvider.ts | 13 +++++++- .../test/browser/stickyScroll.test.ts | 4 +-- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 133d288e1c4..15047ea258e 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -592,10 +592,6 @@ export class StickyScrollController extends Disposable implements IEditorContrib } findScrollWidgetState(): StickyScrollWidgetState { - if (!this._editor.hasModel()) { - return StickyScrollWidgetState.Empty; - } - const textModel = this._editor.getModel(); const maxNumberStickyLines = Math.min(this._maxStickyLines, this._editor.getOption(EditorOption.stickyScroll).maxLineCount); const scrollTop: number = this._editor.getScrollTop(); let lastLineRelativePosition: number = 0; @@ -608,23 +604,20 @@ export class StickyScrollController extends Disposable implements IEditorContrib for (const range of candidateRanges) { const start = range.startLineNumber; const end = range.endLineNumber; - const isValidRange = textModel.isValidRange({ startLineNumber: start, endLineNumber: end, startColumn: 1, endColumn: 1 }); - if (isValidRange && end - start > 0) { - const topOfElement = range.top; - const bottomOfElement = topOfElement + range.height; - const topOfBeginningLine = this._editor.getTopForLineNumber(start) - scrollTop; - const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop; - if (topOfElement > topOfBeginningLine && topOfElement <= bottomOfEndLine) { - startLineNumbers.push(start); - endLineNumbers.push(end + 1); - if (bottomOfElement > bottomOfEndLine) { - lastLineRelativePosition = bottomOfEndLine - bottomOfElement; - } - } - if (startLineNumbers.length === maxNumberStickyLines) { - break; + const topOfElement = range.top; + const bottomOfElement = topOfElement + range.height; + const topOfBeginningLine = this._editor.getTopForLineNumber(start) - scrollTop; + const bottomOfEndLine = this._editor.getBottomForLineNumber(end) - scrollTop; + if (topOfElement > topOfBeginningLine && topOfElement <= bottomOfEndLine) { + startLineNumbers.push(start); + endLineNumbers.push(end + 1); + if (bottomOfElement > bottomOfEndLine) { + lastLineRelativePosition = bottomOfEndLine - bottomOfElement; } } + if (startLineNumbers.length === maxNumberStickyLines) { + break; + } } } this._endLineNumbers = endLineNumbers; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts index ec587d8c717..2a5fc270681 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts @@ -15,6 +15,7 @@ import { ILanguageConfigurationService } from '../../../common/languages/languag import { StickyModelProvider, IStickyModelProvider } from './stickyScrollModelProvider.js'; import { StickyElement, StickyModel, StickyRange } from './stickyScrollElement.js'; import { Position } from '../../../common/core/position.js'; +import { Range } from '../../../common/core/range.js'; export class StickyLineCandidate { constructor( @@ -180,6 +181,10 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi top: number, lastStartLineNumber: number ): void { + const textModel = this._editor.getModel(); + if (!textModel) { + return; + } if (outlineModel.children.length === 0) { return; } @@ -201,7 +206,13 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi continue; } const { startLineNumber, endLineNumber } = child.range; - if (range.startLineNumber <= endLineNumber + 1 && startLineNumber - 1 <= range.endLineNumber && startLineNumber !== lastLine) { + if ( + endLineNumber > startLineNumber + 1 + && range.startLineNumber <= endLineNumber + 1 + && startLineNumber - 1 <= range.endLineNumber + && startLineNumber !== lastLine + && textModel.isValidRange(new Range(startLineNumber, 1, endLineNumber, 1)) + ) { lastLine = startLineNumber; const lineHeight = this._editor.getLineHeightForPosition(new Position(startLineNumber, 1)); result.push(new StickyLineCandidate(startLineNumber, endLineNumber - 1, top, lineHeight)); diff --git a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts index 918e06d8aeb..6eccabaec87 100644 --- a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts +++ b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts @@ -149,8 +149,8 @@ suite('Sticky Scroll Tests', () => { const provider: StickyLineCandidateProvider = new StickyLineCandidateProvider(editor, languageService, languageConfigurationService); await provider.update(); assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 1, endLineNumber: 4 }), [new StickyLineCandidate(1, 2, 0, 19)]); - assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 8, endLineNumber: 10 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19), new StickyLineCandidate(10, 10, 38, 19)]); - assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 10, endLineNumber: 13 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19), new StickyLineCandidate(10, 10, 38, 19), new StickyLineCandidate(13, 13, 0, 19)]); + assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 8, endLineNumber: 10 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19)]); + assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 10, endLineNumber: 13 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19)]); provider.dispose(); model.dispose(); From 9562ac0f246b8ea3b84658a6caeb3ba301d2400c Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:43:15 +0200 Subject: [PATCH 0155/4355] Only do forwarding for local extension host if there's a tunnel provider (#266014) --- src/vs/workbench/api/browser/mainThreadTunnelService.ts | 4 ++++ src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostTunnelService.ts | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 4aa5a7c1d48..54e452dcd42 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -207,6 +207,10 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun } } + async $hasTunnelProvider(): Promise { + return this.tunnelService.hasTunnelProvider; + } + async $setCandidateFilter(): Promise { this.remoteExplorerService.setCandidateFilter((candidates: CandidatePort[]): Promise => { return this._proxy.$applyCandidateFilter(candidates); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9afd4467b0e..65587b00b7a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -422,9 +422,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ExtHostTelemetryLogger.validateSender(sender); return extHostTelemetry.instantiateLogger(extension, sender, options); }, - openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string }) { + async openExternal(uri: URI, options?: { allowContributedOpeners?: boolean | string }) { return extHostWindow.openUri(uri, { - allowTunneling: !!initData.remote.authority, + allowTunneling: initData.remote.isRemote ?? (initData.remote.authority ? await extHostTunnelService.hasTunnelProvider() : false), allowContributedOpeners: options?.allowContributedOpeners, }); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 88ea921a3fa..84f618ad7e4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1767,6 +1767,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $closeTunnel(remote: { host: string; port: number }): Promise; $getTunnels(): Promise; $setTunnelProvider(features: TunnelProviderFeatures | undefined, enablePortsView: boolean): Promise; + $hasTunnelProvider(): Promise; $setRemoteTunnelService(processId: number): Promise; $setCandidateFilter(): Promise; $onFoundNewCandidates(candidates: CandidatePort[]): Promise; diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 17d8417ea55..ecaf6a847be 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -57,6 +57,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined, managedRemoteAuthority: vscode.ManagedResolvedAuthority | undefined): Promise; registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable; registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise; + hasTunnelProvider(): Promise; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); @@ -169,6 +170,10 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe })); } + hasTunnelProvider(): Promise { + return this._proxy.$hasTunnelProvider(); + } + /** * Applies the tunnel metadata and factory found in the remote authority * resolver to the tunnel system. From ffabf6a1b31ad600964a07450af038a8c13cc95f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 10 Sep 2025 13:19:29 +0200 Subject: [PATCH 0156/4355] layout - allow default views to restore in primary sidebar (#266021) --- src/vs/workbench/browser/layout.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index b6d4255606a..bc61f85418f 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -703,16 +703,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Sidebar View Container To Restore if (this.isVisible(Parts.SIDEBAR_PART)) { - - // Only restore last viewlet if window was reloaded or we are in development mode - let viewContainerToRestore: string | undefined; + let viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); if ( !this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || this.environmentService.isExtensionDevelopment && !this.environmentService.extensionTestsLocationURI ) { - viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); - } else { + // allow to restore a non-default viewlet in development mode or when window reloads + } else if ( + viewContainerToRestore !== this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id && + viewContainerToRestore !== this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id + ) { + // fallback to default viewlet otherwise if the viewlet is not a default viewlet viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; } From a1c9bc01e7a7ef512c09f4573952ddf0dea028ba Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:16:38 +0200 Subject: [PATCH 0157/4355] Chat - add support to attach a history item change range to chat (#266022) * Chat - add ISCMHistoryItemChangeRangeVariableEntry * Chat - add content provider for ISCMHistoryItemChangeRangeVariableEntry --- extensions/git/src/git.ts | 22 +++++- extensions/git/src/historyProvider.ts | 20 +++++- extensions/git/src/repository.ts | 8 ++- src/vs/workbench/api/browser/mainThreadSCM.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 13 ++++ .../chat/browser/actions/chatActions.ts | 54 ++++++++++++++- .../chat/browser/chatAttachmentWidgets.ts | 46 ++++++++++++- .../chatAttachmentsContentPart.ts | 6 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../chat/common/chatVariableEntries.ts | 19 +++++- .../scm/browser/scmHistoryChatContext.ts | 68 +++++++++++++++++++ .../workbench/contrib/scm/common/history.ts | 1 + .../vscode.proposed.scmHistoryProvider.d.ts | 1 + 14 files changed, 256 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index e04a1a754c7..fc623e5924b 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3053,9 +3053,27 @@ export class Repository { return commits[0]; } - async showCommit(ref: string): Promise { + async showChanges(ref: string): Promise { try { - const result = await this.exec(['show', ref]); + const result = await this.exec(['log', '-p', '-n1', ref, '--']); + return result.stdout.trim(); + } catch (err) { + if (/^fatal: bad revision '.+'/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.BadRevision; + } + + throw err; + } + } + + async showChangesBetween(ref1: string, ref2: string, path?: string): Promise { + try { + const args = ['log', '-p', `${ref1}..${ref2}`, '--']; + if (path) { + args.push(this.sanitizeRelativePath(path)); + } + + const result = await this.exec(args); return result.stdout.trim(); } catch (err) { if (/^fatal: bad revision '.+'/.test(err.stderr || '')) { diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index f9270d0f2ab..8f440b08c97 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -408,8 +408,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec async resolveHistoryItemChatContext(historyItemId: string): Promise { try { - const commitDetails = await this.repository.showCommit(historyItemId); - return commitDetails; + const changes = await this.repository.showChanges(historyItemId); + return changes; } catch (err) { this.logger.error(`[GitHistoryProvider][resolveHistoryItemChatContext] Failed to resolve history item '${historyItemId}': ${err}`); } @@ -417,6 +417,22 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return undefined; } + async resolveHistoryItemChangeRangeChatContext(historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise { + try { + const changes = await this.repository.showChangesBetween(historyItemParentId, historyItemId, path); + + if (token.isCancellationRequested) { + return undefined; + } + + return `Output of git log -p ${historyItemParentId}..${historyItemId} -- ${path}:\n\n${changes}`; + } catch (err) { + this.logger.error(`[GitHistoryProvider][resolveHistoryItemChangeRangeChatContext] Failed to resolve history item change range '${historyItemId}' for '${path}': ${err}`); + } + + return undefined; + } + async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[]): Promise { try { if (historyItemRefs.length === 0) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 35aa9b99ce2..619a2c70e9f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1813,8 +1813,12 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } - async showCommit(ref: string): Promise { - return await this.run(Operation.Show, () => this.repository.showCommit(ref)); + async showChanges(ref: string): Promise { + return await this.run(Operation.Log(false), () => this.repository.showChanges(ref)); + } + + async showChangesBetween(ref1: string, ref2: string, path?: string): Promise { + return await this.run(Operation.Log(false), () => this.repository.showChangesBetween(ref1, ref2, path)); } async getEmptyTree(): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 0e24b06f64f..de23ab0e428 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -204,6 +204,10 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { return this.proxy.$resolveHistoryItemChatContext(this.handle, historyItemId, token ?? CancellationToken.None); } + async resolveHistoryItemChangeRangeChatContext(historyItemId: string, historyItemParentId: string, path: string, token?: CancellationToken): Promise { + return this.proxy.$resolveHistoryItemChangeRangeChatContext(this.handle, historyItemId, historyItemParentId, path, token ?? CancellationToken.None); + } + async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): Promise { return this.proxy.$resolveHistoryItemRefsCommonAncestor(this.handle, historyItemRefs, token ?? CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 84f618ad7e4..0c4bf617744 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2584,6 +2584,7 @@ export interface ExtHostSCMShape { $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItem(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; + $resolveHistoryItemChangeRangeChatContext(sourceControlHandle: number, historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise; $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 67fcb734572..3988f4adc5e 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -1115,6 +1115,19 @@ export class ExtHostSCM implements ExtHostSCMShape { } } + async $resolveHistoryItemChangeRangeChatContext(sourceControlHandle: number, historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise { + try { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + const chatContext = await historyProvider?.resolveHistoryItemChangeRangeChatContext?.(historyItemId, historyItemParentId, path, token); + + return chatContext ?? undefined; + } + catch (err) { + this.logService.error('ExtHostSCM#$resolveHistoryItemChangeRangeChatContext', err); + return undefined; + } + } + async $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise { try { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 7c18f199238..1213829c86f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -78,8 +78,9 @@ import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; import { ISCMService } from '../../../scm/common/scm.js'; -import { ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; +import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; import { basename } from '../../../../../base/common/resources.js'; +import { SCMHistoryItemChangeRangeContentProvider, ScmHistoryItemChangeRangeUriFields } from '../../../scm/browser/scmHistoryChatContext.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -119,6 +120,13 @@ export interface IChatViewOpenOptions { * A list of source control history item changes to attach to the chat as context. */ attachHistoryItemChanges?: { uri: URI; historyItemId: string }[]; + /** + * A list of source control history item change ranges to attach to the chat as context. + */ + attachHistoryItemChangeRanges?: { + start: { uri: URI; historyItemId: string }; + end: { uri: URI; historyItemId: string }; + }[]; /** * The mode ID or name to open the chat in. */ @@ -267,6 +275,50 @@ abstract class OpenChatGlobalAction extends Action2 { } satisfies ISCMHistoryItemChangeVariableEntry); } } + if (opts?.attachHistoryItemChangeRanges) { + for (const historyItemChangeRange of opts.attachHistoryItemChangeRanges) { + const repository = scmService.getRepository(URI.file(historyItemChangeRange.end.uri.path)); + const historyProvider = repository?.provider.historyProvider.get(); + if (!repository || !historyProvider) { + continue; + } + + const [historyItemStart, historyItemEnd] = await Promise.all([ + historyProvider.resolveHistoryItem(historyItemChangeRange.start.historyItemId), + historyProvider.resolveHistoryItem(historyItemChangeRange.end.historyItemId), + ]); + if (!historyItemStart || !historyItemEnd) { + continue; + } + + const uri = historyItemChangeRange.end.uri.with({ + scheme: SCMHistoryItemChangeRangeContentProvider.scheme, + query: JSON.stringify({ + repositoryId: repository.id, + start: historyItemStart.id, + end: historyItemChangeRange.end.historyItemId + } satisfies ScmHistoryItemChangeRangeUriFields) + }); + + chatWidget.attachmentModel.addContext({ + id: uri.toString(), + name: `${basename(uri)}`, + value: uri, + historyItemChangeStart: { + uri: historyItemChangeRange.start.uri, + historyItem: historyItemStart + }, + historyItemChangeEnd: { + uri: historyItemChangeRange.end.uri, + historyItem: { + ...historyItemEnd, + displayId: historyItemChangeRange.end.historyItemId + } + }, + kind: 'scmHistoryItemChangeRange' + } satisfies ISCMHistoryItemChangeRangeVariableEntry); + } + } let resp: Promise | undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 9da07dc37ed..b54af041321 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -50,7 +50,7 @@ import { CellUri } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; import { getHistoryItemEditorTitle, getHistoryItemHoverContent } from '../../scm/browser/util.js'; import { IChatContentReference } from '../common/chatService.js'; -import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry } from '../common/chatVariableEntries.js'; +import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry, ISCMHistoryItemChangeRangeVariableEntry } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; @@ -861,6 +861,50 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment } } +export class SCMHistoryItemChangeRangeAttachmentWidget extends AbstractChatAttachmentWidget { + constructor( + attachment: ISCMHistoryItemChangeRangeVariableEntry, + currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + contextResourceLabels: ResourceLabels, + hoverDelegate: IHoverDelegate, + @ICommandService commandService: ICommandService, + @IOpenerService openerService: IOpenerService, + @IEditorService private readonly editorService: IEditorService, + ) { + super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + + const historyItemStartId = attachment.historyItemChangeStart.historyItem.displayId ?? attachment.historyItemChangeStart.historyItem.id; + const historyItemEndId = attachment.historyItemChangeEnd.historyItem.displayId ?? attachment.historyItemChangeEnd.historyItem.id; + + const nameSuffix = `\u00A0$(${Codicon.gitCommit.id})${historyItemStartId}..${historyItemEndId}`; + this.label.setFile(attachment.value, { fileKind: FileKind.FILE, nameSuffix }); + + this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); + + this.addResourceOpenHandlers(attachment.value, undefined); + this.attachClearButton(); + } + + protected override async openResource(resource: URI, isDirectory: true): Promise; + protected override async openResource(resource: URI, isDirectory: false, range: IRange | undefined): Promise; + protected override async openResource(resource: URI, isDirectory?: boolean, range?: IRange): Promise { + const attachment = this.attachment as ISCMHistoryItemChangeRangeVariableEntry; + const historyItemChangeStart = attachment.historyItemChangeStart; + const historyItemChangeEnd = attachment.historyItemChangeEnd; + + const originalUriTitle = `${basename(historyItemChangeStart.uri.fsPath)} (${historyItemChangeStart.historyItem.displayId ?? historyItemChangeStart.historyItem.id})`; + const modifiedUriTitle = `${basename(historyItemChangeEnd.uri.fsPath)} (${historyItemChangeEnd.historyItem.displayId ?? historyItemChangeEnd.historyItem.id})`; + + await this.editorService.openEditor({ + original: { resource: historyItemChangeStart.uri }, + modified: { resource: historyItemChangeEnd.uri }, + label: `${originalUriTitle} ↔ ${modifiedUriTitle}` + }); + } +} + export function hookUpResourceAttachmentDragAndContextMenu(accessor: ServicesAccessor, widget: HTMLElement, resource: URI): IDisposable { const contextKeyService = accessor.get(IContextKeyService); const instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index 6c14e4c0895..aefdbc56bf6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -12,9 +12,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; export class ChatAttachmentsContentPart extends Disposable { private readonly attachedContextDisposables = this._register(new DisposableStore()); @@ -79,6 +79,8 @@ export class ChatAttachmentsContentPart extends Disposable { widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { + widget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } else { widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 2927f2805b2..1b9259e7430 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -81,7 +81,7 @@ import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; @@ -93,7 +93,7 @@ import { CancelAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerAction import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; import { IDisposableReference } from './chatContentParts/chatCollections.js'; import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; import { ChatDragAndDrop } from './chatDragAndDrop.js'; @@ -1408,6 +1408,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { + attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } else { attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); } diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index b164909dccc..1a820718a9c 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -215,12 +215,25 @@ export interface ISCMHistoryItemChangeVariableEntry extends IBaseChatRequestVari readonly historyItem: ISCMHistoryItem; } +export interface ISCMHistoryItemChangeRangeVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'scmHistoryItemChangeRange'; + readonly value: URI; + readonly historyItemChangeStart: { + readonly uri: URI; + readonly historyItem: ISCMHistoryItem; + }; + readonly historyItemChangeEnd: { + readonly uri: URI; + readonly historyItem: ISCMHistoryItem; + }; +} + export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry | IChatRequestToolEntry | IChatRequestToolSetEntry | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry | IPromptFileVariableEntry | IPromptTextVariableEntry - | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry; + | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry; export namespace IChatRequestVariableEntry { @@ -289,6 +302,10 @@ export function isSCMHistoryItemChangeVariableEntry(obj: IChatRequestVariableEnt return obj.kind === 'scmHistoryItemChange'; } +export function isSCMHistoryItemChangeRangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeRangeVariableEntry { + return obj.kind === 'scmHistoryItemChangeRange'; +} + export enum PromptFileVariableKind { Instruction = 'vscode.prompt.instructions.root', InstructionReference = `vscode.prompt.instructions`, diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 580bfdbd665..a41865b6a61 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -64,6 +64,10 @@ export class SCMHistoryItemContextContribution extends Disposable implements IWo this._store.add(textModelResolverService.registerTextModelContentProvider( ScmHistoryItemResolver.scheme, instantiationService.createInstance(SCMHistoryItemContextContentProvider))); + + this._store.add(textModelResolverService.registerTextModelContentProvider( + SCMHistoryItemChangeRangeContentProvider.scheme, + instantiationService.createInstance(SCMHistoryItemChangeRangeContentProvider))); } } @@ -182,6 +186,70 @@ class SCMHistoryItemContextContentProvider implements ITextModelContentProvider } } +export interface ScmHistoryItemChangeRangeUriFields { + readonly repositoryId: string; + readonly start: string; + readonly end: string; +} + +export class SCMHistoryItemChangeRangeContentProvider implements ITextModelContentProvider { + static readonly scheme = 'scm-history-item-change-range'; + constructor( + @IModelService private readonly _modelService: IModelService, + @ISCMService private readonly _scmService: ISCMService + ) { } + + async provideTextContent(resource: URI): Promise { + const uriFields = this._parseUri(resource); + if (!uriFields) { + return null; + } + + const textModel = this._modelService.getModel(resource); + if (textModel) { + return textModel; + } + + const { repositoryId, start, end } = uriFields; + const repository = this._scmService.getRepository(repositoryId); + const historyProvider = repository?.provider.historyProvider.get(); + if (!repository || !historyProvider) { + return null; + } + + const historyItemChangeRangeContext = await historyProvider.resolveHistoryItemChangeRangeChatContext(end, start, resource.path); + if (!historyItemChangeRangeContext) { + return null; + } + + return this._modelService.createModel(historyItemChangeRangeContext, null, resource, false); + } + + private _parseUri(uri: URI): ScmHistoryItemChangeRangeUriFields | undefined { + if (uri.scheme !== SCMHistoryItemChangeRangeContentProvider.scheme) { + return undefined; + } + + let query: ScmHistoryItemChangeRangeUriFields; + try { + query = JSON.parse(uri.query) as ScmHistoryItemChangeRangeUriFields; + } catch (e) { + return undefined; + } + + if (typeof query !== 'object' || query === null) { + return undefined; + } + + const { repositoryId, start, end } = query; + if (typeof repositoryId !== 'string' || typeof start !== 'string' || typeof end !== 'string') { + return undefined; + } + + return { repositoryId, start, end }; + } +} + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 007e420e216..77e8b714319 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -27,6 +27,7 @@ export interface ISCMHistoryProvider { provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token?: CancellationToken): Promise; resolveHistoryItem(historyItemId: string, token?: CancellationToken): Promise; resolveHistoryItemChatContext(historyItemId: string, token?: CancellationToken): Promise; + resolveHistoryItemChangeRangeChatContext(historyItemId: string, historyItemParentId: string, path: string, token?: CancellationToken): Promise; resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token?: CancellationToken): Promise; } diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index b094f4e3854..944cdd3935c 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -32,6 +32,7 @@ declare module 'vscode' { resolveHistoryItem(historyItemId: string, token: CancellationToken): ProviderResult; resolveHistoryItemChatContext(historyItemId: string, token: CancellationToken): ProviderResult; + resolveHistoryItemChangeRangeChatContext(historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): ProviderResult; resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): ProviderResult; } From 03454ac1b0b0264f955ec120eab5ec21804cf5cb Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Wed, 10 Sep 2025 15:17:18 +0100 Subject: [PATCH 0158/4355] feat: add new codicons for collection and new collection --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 90136 -> 90428 bytes src/vs/base/common/codiconsLibrary.ts | 2 ++ 2 files changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 3738243fd543be6ff2b2f3055f0347f42366139f..70b7876023a0fc5a089759f6630587b53c95e24a 100644 GIT binary patch delta 7567 zcmX}x3tUxI+6M6F!7Jun5wD1VsHort6mMt<-cUqECGR3CDk3T=nt4!3(X2>}9LLNW z$IQwcZOY6kr;Dj$Wo1@oW#&-reJXXF#_4P9dsgp!-|xqNKWB3e=j^lA-fQjkvg1Up zjAY2QM2Yi{b^G))^9?*a&N2mws+bO^)rJxmDfW11*-LI z4K`u{^KphbJj!%F% z3?J|f{sE8i1@7WAe43B(Ssvj*?!!lXl7|q2ny97Yx&!sm01eRyja%r@+MpdG&>jU? zj6y8IGAyrrwO&#n6XP%!^DrOzScz4*2Wzn&Ww;L;u?gkag8Q)*58y#Ogom*W+wll~ zhu>p2p2Q!p4^Lq~p2h(@gJn^?}xyq^!Ug4_8BtGI)|<8D6AJ$!%koe24GyJ)YxvUf{?4grD&eKj)Xc%y0NDzoSQ=Kk^!zvKhnJ0!3Jg(f|fw9g2Av z0c^m%ScFdK%+7cmf5hkfNatBg+|4_f!WH~8N8ubd@;;_=Co?z&3;6|xvkUg%6SiZ2 zPDOW)kzDow)le4j6@1r2ha)=a0;gxz<8Fi65p^ZPx2Hp z_z+&<6(n*PdoqsmkjsWF}xMjqa06g%JohoTwIqY5K2 zn-eeXjy0rz7j7vMT>;3PYu(-Zg)Yp^z^;4aKSHqTDW-v}R8PG6vnu)A`h(HH`6kwLc7eV|ZdjRjm9TEC12fotYJw_uLyfp?pmG>ID23c!3 zNEt9pRF)bhDc2c|g7Dn;5jQz_8;nLqcx473DDMLTZo@aUv(aeagty7yF=e^o3(75q zyOj4EKBIJvBz#)=kfHlqO&kuNRl4309#L*HJctB%mD&-F&hWMye58ED@JVHr;i1ZH zO(dW0Ny#HnE~GWMsox_*TkaP0^Wy4^9H>0uGzKG6avqEQ_*As z?}E{^0`Fs^2?pLLMpF&EPmLxWco&VPA9$Y`O+@f68BIy>J~x`2;C*2Fx3H&BTw9OEvnzj&}C~wmu(Cqlx+>$E87{yDBW|y zSfzU|pbb0eL8M_fWe3A}Wt3rpGTN|*(!H6mm$IW_U!{9vVY1S_uW*2}v*AEx7sJ82 z{{1-Z2!|-U8jeu90}+l^b~hZObO$BORJwx_j#avY5{^^$G#syV2Pn)|_BNcTO#Dp% zoTLXyhLe?j45umGHxf=)x^E<$q3mZkQ`z5emePGw;cTV*rovq1Aj3Jz6vMg7!2!3? zi{>j`3kVk~?=)PbOf|Y2g+J8jz6O4p;Sy!KVUco};Zo&r!(~d>s>0>Ukw*8ldN9iH z9%Y8%8l`J`;k`=N^uo1D*Yx7nOn#Q(CZ%h7VY$*Zy>PQ~g5mv2*A2o4mFk9o+pt1A zt{a5gm986vk0^5ttCV*c?oduK{GD>D;clhtHsRw+*KNW*O4n_|CzP(+M7Qa>{$0O` zyXo*SVL&f> zOAlN>3*S~27@k(TJ{O)*78;&ax;_`aqjY^Pd{^oET=<^S^||nz()GFUyt3Hvf--Qo zHo}jUD-1tTt~C5iSz>rexytZ!r8{?oUn!tO>1$_{R<2QvSuz_3*2P?wo(kXaNlWgn`aw|F5o9v~fiw|8>J3ly4Y*uXLBS z(7n0)5TSeXlZNii-!^n_e#%hq>z_7s?|sH-xefnsMhkC^RqoOk%u&8$v>J#1u7PX9 z_l#EP@Lm6l*6Q$GYlv3u@Lg+&*6;9Li-}h9@ZInrTGPY-$Y^yB|AG_H6O;7dW1}TM zeAl0%#Xo%44TA5K7mW%6@INyu4Zy!-R3w1^xj~82okya=0sJqGN(k^T8x<4aSH2(F zyp{WquMOOXTs0hCc{?&X&_;Xi`w5yW-JuBGD?P(LO5dm&0pE3~s4fBDb)u+30pE45 zs8#|0N296*{A&icmDdfayy4CeZ5X1Re;C$M-ZZSMbPX#idBFe4sQ3Zjb%fw&r8^K& zK?J@#5P|DV_nfFm0^j|Os9XZy9h9hWQrSK#equQ@8}AgGh7GA+7EtB3SB+~5^uOT#Obh0)=GVcKhB*i-4gn=nqPA;wWb z2*C)0TxEMhchr%Fg~|>_g(C!`jJwqex^@v%C}RwNuk5HkcNH9Bx#&L(ny=;0dMsMuPt+M;X>oW*F7?5FBk(=|gagQOys*Orz=_f@4hzTz_R5 zmMX^?6$cR0FbyRS&b_MRUTtkVfiMURK*DG^l9}mPg%WihK z`HJR;!fJ-~51SEownbcv;_#Z`yIPKJdArq_)@$1|Y;&gVws!IDE=Saim=aMIQPDo6 z{iOCMA|oT$M;?v56nV2l%?`~wM0QB*klJBhhpQcKN41Qa7j+>zB)WNYX7rBeQ_)vq z>czyy42&s^sfc+Y=G%_wSlaPW#|yDBv8jRB{Mho?XJe~7g>;JPG`>?=r$e3Vbw1qr zeCJ=fbncSXrM%1KxZJqyaR=k7yGC?f)AiMEQQb?jUEkq#PulY@nR3JXUm>jdj8U@xYvnZw|YnQezNze#LUDiNi&kJ^(pCd zvTwbddHH87Ucg8D$x}GG5HMGP>dD#L>q_dt>s( ztQ~Vcvwr5J%+k!`nK#EKj?Eu?bgY*Zn>8t`BI{t*jd88V?HPA*eD?S~6UrtWpYTI= zc6Lej$%(Zm=1)8^@s~-dld2{+oSZv3@WSLXlYh<`l2ejXk#jWXle@Oxb#qGRDTk*v zp1N%6-l;dIMNZo|?Zs)ArYBC%nm%Jj%Nc8DJUKIR=B}AnW{sb9b9T4cd9#nsuFhSN zdu~p*IUDC3ob$n)pYk&E3iGz*RnJYGyJqh3xmV`ZpVwzz+5F`B+vlInZ=IiE8|!0U3s}ArX;`Q zK*_aL16OTdb#!&e>Yb}^thupv|Jw6wZw2xKTLVV}=Su6A=9ca#y|Hdg#k!O0F089w z_tW~?>*uZCx4wEqj}0p}RF^d_n^AW4zS8>+Z4B9%xUp#C$xU@PWp66pR9zldUQm9r zyn1uY=B&*JHlNzEb;~dJ@7~&C>y)ikN7CZ{*)dR47ajC}qt^O=T)6)6r6H8jb;9%$ z-djI?lk}tAq~2lC;o3?k1siXuH$OBqA+${1?KnMQbx~2j*wECaY!%wJ-u`-RLqk(T zpUzla)og!NMs}#i%#i>8ociv@q*+u$HjiMhBUxFCS1s!3hLFDc2z`*=U7s259)1J|rGySv;ZMrlyv{_Mds*WRce!ck}LlZLG6UkMftC#i* zO^B^(_TK|au38=13^;P=^uJg0`b!7W_(hGHA=g80yi`7d_tdCUFn3Pzf+dA7{hGlq Y?`YJpaPG?PbCwkR-*3N|%;dWN3*sk%o&W#< delta 7299 zcmXZg3w%%Y{|E5b%PrSYxwTw&GtA6fa=*pKFbp%d4^?m)rp$&=2`i0%i(=K>YWXHHsFSg{UQ==Tlv_ffMZ&72bc^OYfBpL_MQ zYlXAt)tbL@&ZH(_N-1#jUH2V`t@RVAzm>NukKcY@(3U^#X~M(`dxlipSUDU~xsF7I z|K>yCUDa-Eod541qf#y^{`)CWSskdHk4lv~hs&!qU{?5AwJxFa`pSxoU_PhfeP(el z`|=(p@_vlq1uo}0uEi7R!XbD7-|=Ro@FkR@0l&ucc#t{l#&&Ga_whAe!N+`)k7EmW z^C3RVXZSF;^F{99llYWn+=&XPgvvUL8mNg{h(K-B3Gb?2qs9yrU?yf^4(5j6t$s^r zFfx#fDVU0BD8eEv!5vtEl~{$ODd99!`ep2joS zhG(%I&tV64Vi$H}4_?B{*o*ymOZIUgY<@#2@%0FY|idz`Cr5*(gLPgdSLqdEA9%D8^Fcqd8izIUdE6 zILni|of=|3tFt@ja}U$-5m#{~dvh}fa5Sd#Ec&wr9>eEs#LgUp*6hz-=!$L_!k76u zKjud~fy1231?+^|=`oEGm zP!-i!1^=)j8sRI(uqk>XGK8Bj9Pi->@4zq?^FCZ;0uS?D4B}mQmF4Kb6ehDJb1{jv zSipO+0r&7hmf{$W;sc)NH%Ma<=V3BVFq%>LjJM)OoW%VYfQcN2v6z6X7=>|oku~`= z#&a63;AgzUIK-CWFIHe>jKOH!hD0vICS+@{7>ZU%z?&Kas)VB>{th2v<#5y5RjOs^ z8P-2u2`ANV5Q+f29K(F&WTWRBo(3NW?<=PmW+~n8gnO0K4Erkc4DV4+H%wHz?-h*_ z@MaijF!Tz5klXNrc4itbSI#nAr<`pxu)r%c8e-thG3cV4Yc%Y@n`blt!JBXJopOQE zpaieTAO#_K8nzvcRPYuVjal#(8#GWZF&f9<-EK6Z!CPwZpz;nw*P6==yD3A4?Ue4~ zgzc3p9Q{(mA-rOvff3$HqahOBDua)es}0qqUWw5F3U7_U7Nt9J;clgCG2uf>*OJ0# zmFo?kQQqeIO8Br+lZnIa%KHpoRBkZbp$r?1((pDKe5!PPD=brPGTa${^!hh?xE^)Z|4(~6cNgm$+3=S#(HX+UY z@cuD+2hdz^^fmyWM(+jiD;T{Yz^`cZt^mK1(OU!j%0}-G@T(Yz&9O_Xk57^`&q!e+{* z64Hk8`VeK`bY^!u%CcH)IzDw9q+1#*;vV~z+WlO^zdj9gy~9mBErGSWW%9KcT&O(Wjn*+%JzmMlpX#R0yFi& zow;zN(w(_*tg@5gIAv$UY^A#*;cZHHMZ)pQZiW+--3=!yb)^m`Dcyw%Co6lo{_mxo zDazi4dCFT2rz`syy_~}DYxJ4}KgDpSGSzUF(zTv&wz9urp)$>Ij&gv}E3Q7cCKfJM zx+WGbQMx7;-mY{_EL^G_V(3oZHL-BDa+qO>(lxhmjnXx@@J^*`ZQ**QT07)6yjwe2 zhVIHn8HSak4L2&s7;aL!?h!tqbloG|qIBIOd_?KGNBF4Hb&v2dDf5g3X z_^yA1PbhN?pHjNM5^huG8a}6V-6nMZcdFqF%4vrCm3fA*C|!37-%{o~YC^uP53X;8 z?H1c9OzHYo_@Q#H;c?|W!%vj+4L?nHf`#uKfi;FlV$so=YtP}KAA*Xp%j8)%&c zf1QCl8FzOIT(jL}vx2|Xf z2jAVgg0srah9|>I8pnoQ7i}@Du6)F>yV8A&(EZJ0hI^D{M(aTMj~jfX+-m3=?FmEI znC=XOy_HWHy0duNJ(b#UfOeiST28{>W-wj(tkDt^{&s`@%IA!ho$z-UEkNNvZ}7Qt zr(q-I3x=JQu7O1h)mZp1YDdsox!cekz+I`ZSNLF)*w7H|?K6B?x!=$o#yxMskCm?) zy7qX@Xypt4fWcwq>xS;JIcT^*`G(O78U7&ycelUk+DRLF+HnuGxFVDPwxO?dUo3P7 zb64Gf|MKKzqLOMUomkPt2U;eTed{D*(asiR+v)Cbqxg3R!& zs0N`++B;)ZAAtX*QKbOByI({#1NdJVEL5H|sw2Sv+Nh!czuc&{0RJ1KDg*rUM)d~x z?y>}5C@%n2+=i*)>d~=`3@1f52)RFUoh0m_bR8oqO~7{@CMr_E|Iw&i0spd5;R60o zMkNgRR}B8A{Mk_d-M?z69sd`@N=micShwMI+Hnmks(-+D4K1pKz;_21{GoIQ7ga~# zyM2K!$ajAys+hocCn9(_+$5&kfGR+s!#em!S;455g5dwDs)9hL<3Q~mR5pxJy8kb1 z8r~YyCNx}oHH=Cx2x=PW&J1c9m17V@7z|U^HY`@^o^VvGL2$jnMdb~K3CbG{4=d{$ z6?G8Q(<9|J7^EF_v!hB6f(8b!DjOP>D{nGTuLhBZDaxA-la=n$g)Nnhjp{`Rnixz{ zHZ`oJM=FTYj<7%(ZB$r7;Mz*uyH^lvaE~(1@IhrWqXHCyc%xDjg62j=DFiJHK2W-o z5}sGOmPrU{=No-!Wss&!G%QlKHmYbLNHVHzA!uW8Lg}tr=uW<^p}VMd2A?V08&$&) zxYiTZ#Snx)jH?|#_#L(tWzpoXBEQE3f9ccUU3f*wZY zHUvG5W?%%p3|>_BcK3ibbT^(mJ5gN@K_7$h%DzUmIs~r2MO8ZlsYdlX1pN#&aQA?_0Y$Yx1nEXqKm>!0>VXJ`7*z%l3^j0Fm0?sZMBrLR zR5wI0+#p*y!l;&rAk(O-h+w1>9v*)~^`8kicr#oQ|5zyM#$|Q;)LmNlM7=8Y66$TP z_j~=`^-na&YuK>i(VI3!4vai=^QK1Q8~xdMN8=Ms;+teN$!>DIX-w1gO@pW*QLCc1 zMIDMd5mg?g`#Cxyx?S|H=%djWV%o)QjX54u9vc~(8@n-fXY8rit8rE1y2fS4EsuLC z?pU+*W=orGZFVI-BECZ?J~Mt%{O(bW8lOmGlCT&c5E9ujuD{UIJDQk1E&6zg8 zC#NJAC+|(Z&^D{>k#@b?t#5a@-R1VP+HdR-*OBg2Z0m8VXQQ4=d+z9Y zv{%Dk>Aebjo$KA9cVX|c-j{EE`qtBZdiKfbQ_`obZ`HopeIM<6tnY=sf2BmG^hueT zvOZ;J%CQtLwQ6cqYQNOn)TOCgQxBz{PQB7Eq2JVg`$PTf^xx9|Y#P#X(w3xc8jv_3 zWkBwLO#}WM*l=L}z)b@W52`*Wc~Hinyg|i-b`Cm|j`WiBy@Qhn4;)-J`1FvNA?ZU( zha4J;p&f><8oGDr#f+$oo*Bg%+lM6&%O7@Oc=Cw4Bhp7K8*w4CYGzjErp#+2Ge)i- z`AJrM)}vWhN2QD!J1Vqx)QQnmM@NlL8=W^MZp^AN`^WYfTQ;u3xDMl1joX!7Kf7Oc zLH6P7^6bmEZ5tmkzTNm;6A~xPop65Qs)+|C{yHgbQc+HooQXLHCfA?bb8^n)lF8>L z|D79?Ta^3Bl$ul0r_7zQWy-r#>rOo~EpA%&wC&T5a$!;7rotn0YR=g*=ir=cb34z?o?ANi z;JnOvrSs0ukC>mipyq;(3-&I!T9jS1s_5Xt4hstxzO?Y#qOOaUFFLk3ZgJY;4NDT1 z6fZ4a)_Ga(veIS0h8l&^LK8!qLsyr#SYEs$YDLzH0aI5LtteTsX~plw9gB;K53Q`S zvS?-5%FCu6&0nQirA4JLm6qSx_0G|28?9Zx zE@|D<>%8^xdkPYMYqsb8j8%*GymK^I!k_l-PUX3Z`!){X?G^V$4(69N_Ej3g&Q&V_ F{|8_WM Date: Wed, 10 Sep 2025 16:18:49 +0200 Subject: [PATCH 0159/4355] Introduces editor.mouseMiddleClickAction: default|openLink|ctrlLeftClick Co-authored-by: Kevin Hahn --- src/vs/editor/common/config/editorOptions.ts | 12 ++ .../common/standalone/standaloneEnums.ts | 171 ++++++++--------- .../browser/link/clickLinkGesture.ts | 35 ++-- src/vs/editor/contrib/links/browser/links.ts | 2 +- src/vs/monaco.d.ts | 178 +++++++++--------- 5 files changed, 216 insertions(+), 182 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 6469fff0105..9b66d408728 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -464,6 +464,10 @@ export interface IEditorOptions { * Controls the max number of text cursors that can be in an active editor at once. */ multiCursorLimit?: number; + /** + * Enables middle mouse button to open links and Go To Definition + */ + mouseMiddleClickAction?: MouseMiddleClickAction; /** * Configure the editor's accessibility support. * Defaults to 'auto'. It is best to leave this to 'auto'. @@ -5715,6 +5719,7 @@ export const enum EditorOption { mouseWheelZoom, multiCursorMergeOverlapping, multiCursorModifier, + mouseMiddleClickAction, multiCursorPaste, multiCursorLimit, occurrencesHighlight, @@ -6287,6 +6292,11 @@ export const EditorOptions = { }, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the [multicursor modifier](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).") } )), + mouseMiddleClickAction: register(new EditorStringEnumOption( + EditorOption.mouseMiddleClickAction, 'mouseMiddleClickAction', 'default' as MouseMiddleClickAction, + ['default', 'openLink', 'ctrlLeftClick'] as MouseMiddleClickAction[], + { description: nls.localize('mouseMiddleClickAction', "Controls what happens when middle mouse button is clicked in the editor.") } + )), multiCursorPaste: register(new EditorStringEnumOption( EditorOption.multiCursorPaste, 'multiCursorPaste', 'spread' as 'spread' | 'full', @@ -6710,3 +6720,5 @@ type EditorOptionsType = typeof EditorOptions; type FindEditorOptionsKeyById = { [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never }[keyof EditorOptionsType]; type ComputedEditorOptionValue> = T extends IEditorOption ? R : never; export type FindComputedEditorOptionValueById = NonNullable]>>; + +export type MouseMiddleClickAction = 'default' | 'openLink' | 'ctrlLeftClick'; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index d718de32f08..acecb4a41c4 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -261,91 +261,92 @@ export enum EditorOption { mouseWheelZoom = 84, multiCursorMergeOverlapping = 85, multiCursorModifier = 86, - multiCursorPaste = 87, - multiCursorLimit = 88, - occurrencesHighlight = 89, - occurrencesHighlightDelay = 90, - overtypeCursorStyle = 91, - overtypeOnPaste = 92, - overviewRulerBorder = 93, - overviewRulerLanes = 94, - padding = 95, - pasteAs = 96, - parameterHints = 97, - peekWidgetDefaultFocus = 98, - placeholder = 99, - definitionLinkOpensInPeek = 100, - quickSuggestions = 101, - quickSuggestionsDelay = 102, - readOnly = 103, - readOnlyMessage = 104, - renameOnType = 105, - renderRichScreenReaderContent = 106, - renderControlCharacters = 107, - renderFinalNewline = 108, - renderLineHighlight = 109, - renderLineHighlightOnlyWhenFocus = 110, - renderValidationDecorations = 111, - renderWhitespace = 112, - revealHorizontalRightPadding = 113, - roundedSelection = 114, - rulers = 115, - scrollbar = 116, - scrollBeyondLastColumn = 117, - scrollBeyondLastLine = 118, - scrollPredominantAxis = 119, - selectionClipboard = 120, - selectionHighlight = 121, - selectionHighlightMaxLength = 122, - selectionHighlightMultiline = 123, - selectOnLineNumbers = 124, - showFoldingControls = 125, - showUnused = 126, - snippetSuggestions = 127, - smartSelect = 128, - smoothScrolling = 129, - stickyScroll = 130, - stickyTabStops = 131, - stopRenderingLineAfter = 132, - suggest = 133, - suggestFontSize = 134, - suggestLineHeight = 135, - suggestOnTriggerCharacters = 136, - suggestSelection = 137, - tabCompletion = 138, - tabIndex = 139, - trimWhitespaceOnDelete = 140, - unicodeHighlighting = 141, - unusualLineTerminators = 142, - useShadowDOM = 143, - useTabStops = 144, - wordBreak = 145, - wordSegmenterLocales = 146, - wordSeparators = 147, - wordWrap = 148, - wordWrapBreakAfterCharacters = 149, - wordWrapBreakBeforeCharacters = 150, - wordWrapColumn = 151, - wordWrapOverride1 = 152, - wordWrapOverride2 = 153, - wrappingIndent = 154, - wrappingStrategy = 155, - showDeprecated = 156, - inertialScroll = 157, - inlayHints = 158, - wrapOnEscapedLineFeeds = 159, - effectiveCursorStyle = 160, - editorClassName = 161, - pixelRatio = 162, - tabFocusMode = 163, - layoutInfo = 164, - wrappingInfo = 165, - defaultColorDecorators = 166, - colorDecoratorsActivatedOn = 167, - inlineCompletionsAccessibilityVerbose = 168, - effectiveEditContext = 169, - scrollOnMiddleClick = 170, - effectiveAllowVariableFonts = 171 + mouseMiddleClickAction = 87, + multiCursorPaste = 88, + multiCursorLimit = 89, + occurrencesHighlight = 90, + occurrencesHighlightDelay = 91, + overtypeCursorStyle = 92, + overtypeOnPaste = 93, + overviewRulerBorder = 94, + overviewRulerLanes = 95, + padding = 96, + pasteAs = 97, + parameterHints = 98, + peekWidgetDefaultFocus = 99, + placeholder = 100, + definitionLinkOpensInPeek = 101, + quickSuggestions = 102, + quickSuggestionsDelay = 103, + readOnly = 104, + readOnlyMessage = 105, + renameOnType = 106, + renderRichScreenReaderContent = 107, + renderControlCharacters = 108, + renderFinalNewline = 109, + renderLineHighlight = 110, + renderLineHighlightOnlyWhenFocus = 111, + renderValidationDecorations = 112, + renderWhitespace = 113, + revealHorizontalRightPadding = 114, + roundedSelection = 115, + rulers = 116, + scrollbar = 117, + scrollBeyondLastColumn = 118, + scrollBeyondLastLine = 119, + scrollPredominantAxis = 120, + selectionClipboard = 121, + selectionHighlight = 122, + selectionHighlightMaxLength = 123, + selectionHighlightMultiline = 124, + selectOnLineNumbers = 125, + showFoldingControls = 126, + showUnused = 127, + snippetSuggestions = 128, + smartSelect = 129, + smoothScrolling = 130, + stickyScroll = 131, + stickyTabStops = 132, + stopRenderingLineAfter = 133, + suggest = 134, + suggestFontSize = 135, + suggestLineHeight = 136, + suggestOnTriggerCharacters = 137, + suggestSelection = 138, + tabCompletion = 139, + tabIndex = 140, + trimWhitespaceOnDelete = 141, + unicodeHighlighting = 142, + unusualLineTerminators = 143, + useShadowDOM = 144, + useTabStops = 145, + wordBreak = 146, + wordSegmenterLocales = 147, + wordSeparators = 148, + wordWrap = 149, + wordWrapBreakAfterCharacters = 150, + wordWrapBreakBeforeCharacters = 151, + wordWrapColumn = 152, + wordWrapOverride1 = 153, + wordWrapOverride2 = 154, + wrappingIndent = 155, + wrappingStrategy = 156, + showDeprecated = 157, + inertialScroll = 158, + inlayHints = 159, + wrapOnEscapedLineFeeds = 160, + effectiveCursorStyle = 161, + editorClassName = 162, + pixelRatio = 163, + tabFocusMode = 164, + layoutInfo = 165, + wrappingInfo = 166, + defaultColorDecorators = 167, + colorDecoratorsActivatedOn = 168, + inlineCompletionsAccessibilityVerbose = 169, + effectiveEditContext = 170, + scrollOnMiddleClick = 171, + effectiveAllowVariableFonts = 172 } /** diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture.ts index fb7ab9003ef..f7029fc4f7f 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture.ts @@ -9,7 +9,7 @@ import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import * as platform from '../../../../../base/common/platform.js'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from '../../../../browser/editorBrowser.js'; -import { EditorOption } from '../../../../common/config/editorOptions.js'; +import { EditorOption, MouseMiddleClickAction } from '../../../../common/config/editorOptions.js'; import { ICursorSelectionChangedEvent } from '../../../../common/cursorEvents.js'; function hasModifier(e: { ctrlKey: boolean; shiftKey: boolean; altKey: boolean; metaKey: boolean }, modifier: 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'): boolean { @@ -29,12 +29,22 @@ export class ClickLinkMouseEvent { public readonly isMiddleClick: boolean; public readonly isRightClick: boolean; + public readonly mouseMiddleClickAction: MouseMiddleClickAction; + constructor(source: IEditorMouseEvent, opts: ClickLinkOptions) { this.target = source.target; this.isLeftClick = source.event.leftButton; this.isMiddleClick = source.event.middleButton; this.isRightClick = source.event.rightButton; + this.mouseMiddleClickAction = opts.mouseMiddleClickAction; this.hasTriggerModifier = hasModifier(source.event, opts.triggerModifier); + + if (this.isMiddleClick && opts.mouseMiddleClickAction === 'ctrlLeftClick') { + // Redirect middle click to left click with modifier + this.isMiddleClick = false; + this.isLeftClick = true; + this.hasTriggerModifier = true; + } this.hasSideBySideModifier = hasModifier(source.event, opts.triggerSideBySideModifier); this.isNoneOrSingleMouseDown = (source.event.detail <= 1); } @@ -68,7 +78,8 @@ export class ClickLinkOptions { triggerKey: KeyCode, triggerModifier: TriggerModifier, triggerSideBySideKey: KeyCode, - triggerSideBySideModifier: TriggerModifier + triggerSideBySideModifier: TriggerModifier, + public readonly mouseMiddleClickAction: MouseMiddleClickAction, ) { this.triggerKey = triggerKey; this.triggerModifier = triggerModifier; @@ -82,22 +93,23 @@ export class ClickLinkOptions { && this.triggerModifier === other.triggerModifier && this.triggerSideBySideKey === other.triggerSideBySideKey && this.triggerSideBySideModifier === other.triggerSideBySideModifier + && this.mouseMiddleClickAction === other.mouseMiddleClickAction ); } } -function createOptions(multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey'): ClickLinkOptions { +function createOptions(multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey', mouseMiddleClickAction: MouseMiddleClickAction): ClickLinkOptions { if (multiCursorModifier === 'altKey') { if (platform.isMacintosh) { - return new ClickLinkOptions(KeyCode.Meta, 'metaKey', KeyCode.Alt, 'altKey'); + return new ClickLinkOptions(KeyCode.Meta, 'metaKey', KeyCode.Alt, 'altKey', mouseMiddleClickAction); } - return new ClickLinkOptions(KeyCode.Ctrl, 'ctrlKey', KeyCode.Alt, 'altKey'); + return new ClickLinkOptions(KeyCode.Ctrl, 'ctrlKey', KeyCode.Alt, 'altKey', mouseMiddleClickAction); } if (platform.isMacintosh) { - return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Meta, 'metaKey'); + return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Meta, 'metaKey', mouseMiddleClickAction); } - return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Ctrl, 'ctrlKey'); + return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Ctrl, 'ctrlKey', mouseMiddleClickAction); } export interface IClickLinkGestureOptions { @@ -131,15 +143,15 @@ export class ClickLinkGesture extends Disposable { this._editor = editor; this._extractLineNumberFromMouseEvent = opts?.extractLineNumberFromMouseEvent ?? ((e) => e.target.position ? e.target.position.lineNumber : 0); - this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier)); + this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier), this._editor.getOption(EditorOption.mouseMiddleClickAction)); this._lastMouseMoveEvent = null; this._hasTriggerKeyOnMouseDown = false; this._lineNumberOnMouseDown = 0; this._register(this._editor.onDidChangeConfiguration((e) => { - if (e.hasChanged(EditorOption.multiCursorModifier)) { - const newOpts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier)); + if (e.hasChanged(EditorOption.multiCursorModifier) || e.hasChanged(EditorOption.mouseMiddleClickAction)) { + const newOpts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier), this._editor.getOption(EditorOption.mouseMiddleClickAction)); if (this._opts.equals(newOpts)) { return; } @@ -190,7 +202,8 @@ export class ClickLinkGesture extends Disposable { private _onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void { const currentLineNumber = this._extractLineNumberFromMouseEvent(mouseEvent); - if (this._hasTriggerKeyOnMouseDown && this._lineNumberOnMouseDown && this._lineNumberOnMouseDown === currentLineNumber) { + const lineNumbersCorrect = !!this._lineNumberOnMouseDown && this._lineNumberOnMouseDown === currentLineNumber; + if (lineNumbersCorrect && (this._hasTriggerKeyOnMouseDown || (mouseEvent.isMiddleClick && mouseEvent.mouseMiddleClickAction === 'openLink'))) { this._onExecute.fire(mouseEvent); } } diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index ef1b67addd1..985b19c2359 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -290,7 +290,7 @@ export class LinkDetector extends Disposable implements IEditorContribution { private isEnabled(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent | null): boolean { return Boolean( (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT) - && (mouseEvent.hasTriggerModifier || (withKey && withKey.keyCodeIsTriggerKey)) + && ((mouseEvent.hasTriggerModifier || (withKey && withKey.keyCodeIsTriggerKey)) || mouseEvent.isMiddleClick && mouseEvent.mouseMiddleClickAction === 'openLink') ); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4155dd02346..03b7dc5427a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3612,6 +3612,10 @@ declare namespace monaco.editor { * Controls the max number of text cursors that can be in an active editor at once. */ multiCursorLimit?: number; + /** + * Enables middle mouse button to open links and Go To Definition + */ + mouseMiddleClickAction?: MouseMiddleClickAction; /** * Configure the editor's accessibility support. * Defaults to 'auto'. It is best to leave this to 'auto'. @@ -5143,91 +5147,92 @@ declare namespace monaco.editor { mouseWheelZoom = 84, multiCursorMergeOverlapping = 85, multiCursorModifier = 86, - multiCursorPaste = 87, - multiCursorLimit = 88, - occurrencesHighlight = 89, - occurrencesHighlightDelay = 90, - overtypeCursorStyle = 91, - overtypeOnPaste = 92, - overviewRulerBorder = 93, - overviewRulerLanes = 94, - padding = 95, - pasteAs = 96, - parameterHints = 97, - peekWidgetDefaultFocus = 98, - placeholder = 99, - definitionLinkOpensInPeek = 100, - quickSuggestions = 101, - quickSuggestionsDelay = 102, - readOnly = 103, - readOnlyMessage = 104, - renameOnType = 105, - renderRichScreenReaderContent = 106, - renderControlCharacters = 107, - renderFinalNewline = 108, - renderLineHighlight = 109, - renderLineHighlightOnlyWhenFocus = 110, - renderValidationDecorations = 111, - renderWhitespace = 112, - revealHorizontalRightPadding = 113, - roundedSelection = 114, - rulers = 115, - scrollbar = 116, - scrollBeyondLastColumn = 117, - scrollBeyondLastLine = 118, - scrollPredominantAxis = 119, - selectionClipboard = 120, - selectionHighlight = 121, - selectionHighlightMaxLength = 122, - selectionHighlightMultiline = 123, - selectOnLineNumbers = 124, - showFoldingControls = 125, - showUnused = 126, - snippetSuggestions = 127, - smartSelect = 128, - smoothScrolling = 129, - stickyScroll = 130, - stickyTabStops = 131, - stopRenderingLineAfter = 132, - suggest = 133, - suggestFontSize = 134, - suggestLineHeight = 135, - suggestOnTriggerCharacters = 136, - suggestSelection = 137, - tabCompletion = 138, - tabIndex = 139, - trimWhitespaceOnDelete = 140, - unicodeHighlighting = 141, - unusualLineTerminators = 142, - useShadowDOM = 143, - useTabStops = 144, - wordBreak = 145, - wordSegmenterLocales = 146, - wordSeparators = 147, - wordWrap = 148, - wordWrapBreakAfterCharacters = 149, - wordWrapBreakBeforeCharacters = 150, - wordWrapColumn = 151, - wordWrapOverride1 = 152, - wordWrapOverride2 = 153, - wrappingIndent = 154, - wrappingStrategy = 155, - showDeprecated = 156, - inertialScroll = 157, - inlayHints = 158, - wrapOnEscapedLineFeeds = 159, - effectiveCursorStyle = 160, - editorClassName = 161, - pixelRatio = 162, - tabFocusMode = 163, - layoutInfo = 164, - wrappingInfo = 165, - defaultColorDecorators = 166, - colorDecoratorsActivatedOn = 167, - inlineCompletionsAccessibilityVerbose = 168, - effectiveEditContext = 169, - scrollOnMiddleClick = 170, - effectiveAllowVariableFonts = 171 + mouseMiddleClickAction = 87, + multiCursorPaste = 88, + multiCursorLimit = 89, + occurrencesHighlight = 90, + occurrencesHighlightDelay = 91, + overtypeCursorStyle = 92, + overtypeOnPaste = 93, + overviewRulerBorder = 94, + overviewRulerLanes = 95, + padding = 96, + pasteAs = 97, + parameterHints = 98, + peekWidgetDefaultFocus = 99, + placeholder = 100, + definitionLinkOpensInPeek = 101, + quickSuggestions = 102, + quickSuggestionsDelay = 103, + readOnly = 104, + readOnlyMessage = 105, + renameOnType = 106, + renderRichScreenReaderContent = 107, + renderControlCharacters = 108, + renderFinalNewline = 109, + renderLineHighlight = 110, + renderLineHighlightOnlyWhenFocus = 111, + renderValidationDecorations = 112, + renderWhitespace = 113, + revealHorizontalRightPadding = 114, + roundedSelection = 115, + rulers = 116, + scrollbar = 117, + scrollBeyondLastColumn = 118, + scrollBeyondLastLine = 119, + scrollPredominantAxis = 120, + selectionClipboard = 121, + selectionHighlight = 122, + selectionHighlightMaxLength = 123, + selectionHighlightMultiline = 124, + selectOnLineNumbers = 125, + showFoldingControls = 126, + showUnused = 127, + snippetSuggestions = 128, + smartSelect = 129, + smoothScrolling = 130, + stickyScroll = 131, + stickyTabStops = 132, + stopRenderingLineAfter = 133, + suggest = 134, + suggestFontSize = 135, + suggestLineHeight = 136, + suggestOnTriggerCharacters = 137, + suggestSelection = 138, + tabCompletion = 139, + tabIndex = 140, + trimWhitespaceOnDelete = 141, + unicodeHighlighting = 142, + unusualLineTerminators = 143, + useShadowDOM = 144, + useTabStops = 145, + wordBreak = 146, + wordSegmenterLocales = 147, + wordSeparators = 148, + wordWrap = 149, + wordWrapBreakAfterCharacters = 150, + wordWrapBreakBeforeCharacters = 151, + wordWrapColumn = 152, + wordWrapOverride1 = 153, + wordWrapOverride2 = 154, + wrappingIndent = 155, + wrappingStrategy = 156, + showDeprecated = 157, + inertialScroll = 158, + inlayHints = 159, + wrapOnEscapedLineFeeds = 160, + effectiveCursorStyle = 161, + editorClassName = 162, + pixelRatio = 163, + tabFocusMode = 164, + layoutInfo = 165, + wrappingInfo = 166, + defaultColorDecorators = 167, + colorDecoratorsActivatedOn = 168, + inlineCompletionsAccessibilityVerbose = 169, + effectiveEditContext = 170, + scrollOnMiddleClick = 171, + effectiveAllowVariableFonts = 172 } export const EditorOptions: { @@ -5323,6 +5328,7 @@ declare namespace monaco.editor { mouseWheelZoom: IEditorOption; multiCursorMergeOverlapping: IEditorOption; multiCursorModifier: IEditorOption; + mouseMiddleClickAction: IEditorOption; multiCursorPaste: IEditorOption; multiCursorLimit: IEditorOption; occurrencesHighlight: IEditorOption; @@ -5415,6 +5421,8 @@ declare namespace monaco.editor { export type FindComputedEditorOptionValueById = NonNullable]>>; + export type MouseMiddleClickAction = 'default' | 'openLink' | 'ctrlLeftClick'; + export interface IEditorConstructionOptions extends IEditorOptions { /** * The initial editor dimension (to avoid measuring the container). From 32a309c4eceb1df1252215e36766ffb7e4e5afb3 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:37:31 +0200 Subject: [PATCH 0160/4355] Clear nodes if there's an error (#266036) Fixes microsoft/vscode-pull-request-github#7536 --- src/vs/workbench/api/common/extHostTreeViews.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index aa7a109e2e4..b7e09f40a5b 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -413,6 +413,8 @@ class ExtHostTreeView extends Disposable { return this._refresh(elements).then(() => { this._clearNodes(childrenToClear); return _promiseCallback(); + }).catch(_ => { + this._clearNodes(childrenToClear); }); }); } From 6ec112ba92cf1cacd8e4f689d865ea0306f2fcca Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 10 Sep 2025 07:39:06 -0700 Subject: [PATCH 0161/4355] Update test expectations --- .../chatAgentTools/test/browser/runInTerminalTool.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index eaecab8ff58..3026492dad5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -178,6 +178,7 @@ suite('RunInTerminalTool', () => { 'git log --oneline', 'git show HEAD', 'git diff main', + 'git grep "TODO"', // PowerShell commands 'Get-ChildItem', @@ -254,8 +255,6 @@ suite('RunInTerminalTool', () => { 'find . -exec rm {} \\;', 'find . -execdir rm {} \\;', 'find . -fprint output.txt', - 'grep -f patterns.txt file.txt', - 'grep -P "complex.*regex" file.txt', 'sort -o /etc/passwd file.txt', 'sort -S 100G file.txt', 'tree -o output.txt', From 24df47653f567cdcecf99267a18d6218052f03ce Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Sep 2025 16:51:43 +0200 Subject: [PATCH 0162/4355] improve rending mcp server (#266023) --- src/vs/base/common/product.ts | 6 ++ .../platform/mcp/common/mcpGalleryManifest.ts | 8 +- .../mcp/common/mcpGalleryManifestService.ts | 35 ++++++- .../platform/mcp/common/mcpGalleryService.ts | 45 +++++++-- src/vs/platform/mcp/common/mcpManagement.ts | 2 + .../extensions/browser/extensionEditor.ts | 51 +++++----- .../browser/media/extensionEditor.css | 10 +- .../contrib/mcp/browser/mcpServerEditor.ts | 99 ++++++++++++------- .../contrib/mcp/browser/mcpServerIcons.ts | 1 + .../contrib/mcp/browser/mcpServerWidgets.ts | 40 +++++++- .../mcp/browser/mcpWorkbenchService.ts | 4 + .../workbench/contrib/mcp/common/mcpTypes.ts | 1 + 12 files changed, 232 insertions(+), 70 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 39548233cd7..88f389acae2 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -114,6 +114,12 @@ export interface IProductConfiguration { readonly mcpGallery?: { readonly serviceUrl: string; + readonly itemWebUrl: string; + readonly publisherUrl: string; + readonly supportUrl: string; + readonly privacyPolicyUrl: string; + readonly termsOfServiceUrl: string; + readonly reportUrl: string; }; readonly extensionPublisherOrgs?: readonly string[]; diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index c471cc82277..e534ff2375d 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -7,8 +7,14 @@ import { Event } from '../../../base/common/event.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; export const enum McpGalleryResourceType { - McpQueryService = 'McpQueryService', + McpServersQueryService = 'McpServersQueryService', + McpServerWebUri = 'McpServerWebUriTemplate', McpServerManifestUri = 'McpServerManifestUriTemplate', + PublisherUriTemplate = 'PublisherUriTemplate', + ContactSupportUri = 'ContactSupportUri', + PrivacyPolicyUri = 'PrivacyPolicyUri', + TermsOfServiceUri = 'TermsOfServiceUri', + ReportUri = 'ReportUri', } export type McpGalleryManifestResource = { diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index 17a48837a0f..5d4edc1138e 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -34,12 +34,13 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery protected createMcpGalleryManifest(url: string): IMcpGalleryManifest { url = url.endsWith('/') ? url.slice(0, -1) : url; const isVSCodeGalleryUrl = this.productService.extensionsGallery?.mcpUrl === url; + const isProductGalleryUrl = this.productService.mcpGallery?.serviceUrl === url; const version = isVSCodeGalleryUrl ? undefined : 'v0'; const serversUrl = isVSCodeGalleryUrl ? url : `${url}/${version}/servers`; const resources = [ { id: serversUrl, - type: McpGalleryResourceType.McpQueryService + type: McpGalleryResourceType.McpServersQueryService } ]; if (!isVSCodeGalleryUrl) { @@ -48,6 +49,38 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery type: McpGalleryResourceType.McpServerManifestUri }); } + + if (isProductGalleryUrl) { + resources.push({ + id: this.productService.mcpGallery.itemWebUrl, + type: McpGalleryResourceType.McpServerWebUri + }); + resources.push({ + id: this.productService.mcpGallery.publisherUrl, + type: McpGalleryResourceType.PublisherUriTemplate + }); + resources.push({ + id: this.productService.mcpGallery.supportUrl, + type: McpGalleryResourceType.ContactSupportUri + }); + resources.push({ + id: this.productService.mcpGallery.supportUrl, + type: McpGalleryResourceType.ContactSupportUri + }); + resources.push({ + id: this.productService.mcpGallery.privacyPolicyUrl, + type: McpGalleryResourceType.PrivacyPolicyUri + }); + resources.push({ + id: this.productService.mcpGallery.termsOfServiceUrl, + type: McpGalleryResourceType.TermsOfServiceUri + }); + resources.push({ + id: this.productService.mcpGallery.reportUrl, + type: McpGalleryResourceType.ReportUri + }); + } + return { version, url, diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 007d94f47ce..ea6f9a5f5da 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -311,15 +311,15 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return result; } - private toGalleryMcpServer(server: IRawGalleryMcpServer, serverUrl: string | undefined): IGalleryMcpServer { - const registryInfo = server._meta?.['x-io.modelcontextprotocol.registry']; + private toGalleryMcpServer(server: IRawGalleryMcpServer, manifest: IMcpGalleryManifest | null): IGalleryMcpServer { + const registryInfo = server._meta?.['io.modelcontextprotocol.registry/official'] ?? server._meta?.['x-io.modelcontextprotocol.registry']; const githubInfo = server._meta?.['github'] ?? server._meta?.['x-github']; let publisher = ''; let displayName = ''; if (githubInfo?.name) { - displayName = githubInfo.name.split('-').map(s => uppercaseFirstLetter(s)).join(' '); + displayName = githubInfo.name.split('-').map(s => s.toLowerCase() === 'mcp' ? 'MCP' : s.toLowerCase() === 'github' ? 'GitHub' : uppercaseFirstLetter(s)).join(' '); publisher = githubInfo.name_with_owner.split('/')[0]; } else { const nameParts = server.name.split('/'); @@ -337,22 +337,28 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService dark: githubInfo.owner_avatar_url } : undefined; + const serverUrl = manifest ? this.getServerUrl(server.id, manifest) : undefined; + const webUrl = manifest ? this.getWebUrl(server.name, manifest) : undefined; + const publisherUrl = manifest ? this.getPublisherUrl(publisher, manifest) : undefined; + return { id: server.id, name: server.name, displayName, url: serverUrl, + webUrl, description: server.description, status: server.status ?? GalleryMcpServerStatus.Active, version: server.version_detail.version, isLatest: server.version_detail.is_latest, releaseDate: Date.parse(server.version_detail.release_date), publishDate: registryInfo ? Date.parse(registryInfo.published_at) : undefined, - lastUpdated: registryInfo ? Date.parse(registryInfo.updated_at) : undefined, + lastUpdated: githubInfo?.pushed_at ? Date.parse(githubInfo.pushed_at) : registryInfo ? Date.parse(registryInfo.updated_at) : undefined, repositoryUrl: server.repository?.url, readme: server.repository?.readme, icon, publisher, + publisherUrl, license: githubInfo?.license, starsCount: githubInfo?.stargazer_count, topics: githubInfo?.topics, @@ -390,7 +396,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } const { servers, metadata } = await this.queryRawGalleryMcpServers(query, mcpGalleryManifest, token); return { - servers: servers.map(item => this.toGalleryMcpServer(item, this.getServerUrl(item.id, mcpGalleryManifest))), + servers: servers.map(item => this.toGalleryMcpServer(item, mcpGalleryManifest)), metadata }; } @@ -435,7 +441,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return result; } - async getMcpServer(mcpServerUrl: string): Promise { + async getMcpServer(mcpServerUrl: string, mcpGalleryManifest?: IMcpGalleryManifest | null): Promise { const context = await this.requestService.request({ type: 'GET', url: mcpServerUrl, @@ -450,7 +456,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return undefined; } - return this.toGalleryMcpServer(this.toIRawGalleryMcpServer(server), mcpServerUrl); + if (!mcpGalleryManifest) { + mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); + if (mcpGalleryManifest && mcpServerUrl !== this.getServerUrl(basename(mcpServerUrl), mcpGalleryManifest)) { + mcpGalleryManifest = null; + } + } + + return this.toGalleryMcpServer(this.toIRawGalleryMcpServer(server), mcpGalleryManifest); } private toIRawGalleryMcpServer(from: IRawGalleryOldMcpServer | IRawGalleryMcpServer): IRawGalleryMcpServer { @@ -514,8 +527,24 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return format2(resourceUriTemplate, { id }); } + private getWebUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerWebUri); + if (!resourceUriTemplate) { + return undefined; + } + return format2(resourceUriTemplate, { name }); + } + + private getPublisherUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.PublisherUriTemplate); + if (!resourceUriTemplate) { + return undefined; + } + return format2(resourceUriTemplate, { name }); + } + private getMcpGalleryUrl(mcpGalleryManifest: IMcpGalleryManifest): string | undefined { - return getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpQueryService); + return getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServersQueryService); } } diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 13c4fed4113..8e6f37ae496 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -134,6 +134,7 @@ export interface IGalleryMcpServer { readonly isLatest: boolean; readonly status: GalleryMcpServerStatus; readonly url?: string; + readonly webUrl?: string; readonly codicon?: string; readonly icon?: { readonly dark: string; @@ -148,6 +149,7 @@ export interface IGalleryMcpServer { readonly readme?: string; readonly publisher: string; readonly publisherDisplayName?: string; + readonly publisherUrl?: string; readonly publisherDomain?: { link: string; verified: boolean }; readonly ratingCount?: number; readonly topics?: readonly string[]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 6d1c0ff6195..d065bcfbc2c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -17,7 +17,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; -import { isNative, language } from '../../../../base/common/platform.js'; +import { isNative } from '../../../../base/common/platform.js'; import { isUndefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -88,10 +88,9 @@ import { IUserDataProfilesService } from '../../../../platform/userDataProfile/c import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; import { IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js'; import { ShowCurrentReleaseNotesActionId } from '../../update/common/update.js'; - -function toDateString(date: Date) { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; -} +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { fromNow } from '../../../../base/common/date.js'; class NavBar extends Disposable { @@ -1083,36 +1082,38 @@ class AdditionalDetailsWidget extends Disposable { } private renderExtensionResources(container: HTMLElement, extension: IExtension): void { - const resources: [string, URI][] = []; - if (extension.url) { - resources.push([localize('Marketplace', "Marketplace"), URI.parse(extension.url)]); - } - if (extension.supportUrl) { + const resources: [string, ThemeIcon, URI][] = []; + if (extension.repository) { try { - resources.push([localize('issues', "Issues"), URI.parse(extension.supportUrl)]); + resources.push([localize('repository', "Repository"), ThemeIcon.fromId(Codicon.repo.id), URI.parse(extension.repository)]); } catch (error) {/* Ignore */ } } - if (extension.repository) { + if (extension.supportUrl) { try { - resources.push([localize('repository', "Repository"), URI.parse(extension.repository)]); + resources.push([localize('issues', "Issues"), ThemeIcon.fromId(Codicon.issues.id), URI.parse(extension.supportUrl)]); } catch (error) {/* Ignore */ } } if (extension.licenseUrl) { try { - resources.push([localize('license', "License"), URI.parse(extension.licenseUrl)]); + resources.push([localize('license', "License"), ThemeIcon.fromId(Codicon.linkExternal.id), URI.parse(extension.licenseUrl)]); } catch (error) {/* Ignore */ } } if (extension.publisherUrl) { - resources.push([extension.publisherDisplayName, extension.publisherUrl]); + resources.push([extension.publisherDisplayName, ThemeIcon.fromId(Codicon.linkExternal.id), extension.publisherUrl]); + } + if (extension.url) { + resources.push([localize('Marketplace', "Marketplace"), ThemeIcon.fromId(Codicon.linkExternal.id), URI.parse(extension.url)]); } if (resources.length || extension.publisherSponsorLink) { const extensionResourcesContainer = append(container, $('.resources-container.additional-details-element')); append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); - for (const [label, uri] of resources) { - const resource = append(resourcesElement, $('a.resource', { tabindex: '0' }, label)); - this.disposables.add(onClick(resource, () => this.openerService.open(uri))); - this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); + for (const [label, icon, uri] of resources) { + const resourceElement = append(resourcesElement, $('.resource')); + append(resourceElement, $(ThemeIcon.asCSSSelector(icon))); + append(resourceElement, $('a', { tabindex: '0' }, label)); + this.disposables.add(onClick(resourceElement, () => this.openerService.open(uri))); + this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), resourceElement, uri.toString())); } } } @@ -1138,7 +1139,9 @@ class AdditionalDetailsWidget extends Disposable { append(installInfo, $('.more-info-entry', undefined, $('div.more-info-entry-name', undefined, localize('last updated', "Last Updated")), - $('div', undefined, toDateString(new Date(extension.installedTimestamp))) + $('div', { + 'title': new Date(extension.installedTimestamp).toString() + }, fromNow(extension.installedTimestamp, true)) ) ); } @@ -1227,11 +1230,15 @@ class AdditionalDetailsWidget extends Disposable { append(moreInfo, $('.more-info-entry', undefined, $('div.more-info-entry-name', undefined, localize('published', "Published")), - $('div', undefined, toDateString(new Date(gallery.releaseDate))) + $('div', { + 'title': new Date(gallery.releaseDate).toString() + }, fromNow(gallery.releaseDate, true)) ), $('.more-info-entry', undefined, $('div.more-info-entry-name', undefined, localize('last released', "Last Released")), - $('div', undefined, toDateString(new Date(gallery.lastUpdated))) + $('div', { + 'title': new Date(gallery.lastUpdated).toString() + }, fromNow(gallery.lastUpdated, true)) ) ); } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 80c7d580ad1..a45036b6727 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -154,6 +154,7 @@ .extension-editor > .header > .details > .subtitle .extension-kind-indicator, .extension-editor > .header > .details > .subtitle .install, .extension-editor > .header > .details > .subtitle .rating, +.extension-editor > .header > .details > .subtitle .license, .extension-editor > .header > .details > .subtitle .sponsor { display: flex; align-items: center; @@ -490,18 +491,21 @@ } .extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource { - display: block; + display: flex; + align-items: center; cursor: pointer; + padding: 2px 4px; } -.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource { +.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource a { + padding-left: 4px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-decoration: var(--text-link-decoration); } -.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource:hover { +.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource:hover a { text-decoration: underline; } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 59fdd9bb6d3..0e45b2486e6 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -16,7 +16,6 @@ import { isCancellationError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; -import { language } from '../../../../base/common/platform.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { TokenizationRegistry } from '../../../../editor/common/languages.js'; @@ -38,12 +37,16 @@ import { IEditorGroup } from '../../../services/editor/common/editorGroupsServic import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IMcpServerContainer, IMcpServerEditorOptions, IMcpWorkbenchService, IWorkbenchMcpServer, McpServerContainers, McpServerInstallState } from '../common/mcpTypes.js'; -import { StarredWidget, McpServerIconWidget, McpServerStatusWidget, McpServerWidget, onClick, PublisherWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js'; +import { StarredWidget, McpServerIconWidget, McpServerStatusWidget, McpServerWidget, onClick, PublisherWidget, McpServerScopeBadgeWidget, LicenseWidget } from './mcpServerWidgets.js'; import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction, UninstallAction } from './mcpServerActions.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { ILocalMcpServer, IGalleryMcpServerConfiguration, IMcpServerPackage, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { McpServerType } from '../../../../platform/mcp/common/mcpPlatformTypes.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { getMcpGalleryManifestResourceUri, IMcpGalleryManifestService, McpGalleryResourceType } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; +import { fromNow } from '../../../../base/common/date.js'; const enum McpServerEditorTab { Readme = 'readme', @@ -51,10 +54,6 @@ const enum McpServerEditorTab { Manifest = 'manifest', } -function toDateString(date: Date) { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; -} - class NavBar extends Disposable { private _onChange = this._register(new Emitter<{ id: string | null; focus: boolean }>()); @@ -226,11 +225,16 @@ export class McpServerEditor extends EditorPane { subTitleEntryContainers.push(starredContainer); const installCountWidget = this.instantiationService.createInstance(StarredWidget, starredContainer, false); + const licenseContainer = append(subtitle, $('.subtitle-entry')); + subTitleEntryContainers.push(licenseContainer); + const licenseWidget = this.instantiationService.createInstance(LicenseWidget, licenseContainer); + const widgets: McpServerWidget[] = [ iconWidget, publisherWidget, installCountWidget, scopeWidget, + licenseWidget ]; const description = append(details, $('.description')); @@ -336,10 +340,10 @@ export class McpServerEditor extends EditorPane { template.mcpServer = mcpServer; template.name.textContent = mcpServer.label; - template.name.classList.toggle('clickable', !!mcpServer.url); + template.name.classList.toggle('clickable', !!mcpServer.gallery?.webUrl || !!mcpServer.url); template.description.textContent = mcpServer.description; if (mcpServer.url) { - this.transientDisposables.add(onClick(template.name, () => this.openerService.open(URI.parse(mcpServer.url!)))); + this.transientDisposables.add(onClick(template.name, () => this.openerService.open(URI.parse(mcpServer.gallery?.webUrl ?? mcpServer.url!)))); } this.renderNavbar(mcpServer, template, preserveFocus); @@ -846,11 +850,13 @@ class AdditionalDetailsWidget extends Disposable { constructor( private readonly container: HTMLElement, extension: IWorkbenchMcpServer, + @IMcpGalleryManifestService private readonly mcpGalleryManifestService: IMcpGalleryManifestService, @IHoverService private readonly hoverService: IHoverService, @IOpenerService private readonly openerService: IOpenerService, ) { super(); this.render(extension); + this._register(this.mcpGalleryManifestService.onDidChangeMcpGalleryManifest(() => this.render(extension))); } private render(extension: IWorkbenchMcpServer): void { @@ -864,14 +870,14 @@ class AdditionalDetailsWidget extends Disposable { if (extension.gallery) { this.renderMarketplaceInfo(this.container, extension); } - this.renderTopics(this.container, extension); + this.renderTags(this.container, extension); this.renderExtensionResources(this.container, extension); } - private renderTopics(container: HTMLElement, extension: IWorkbenchMcpServer): void { + private renderTags(container: HTMLElement, extension: IWorkbenchMcpServer): void { if (extension.gallery?.topics?.length) { const categoriesContainer = append(container, $('.categories-container.additional-details-element')); - append(categoriesContainer, $('.additional-details-title', undefined, localize('categories', "Categories"))); + append(categoriesContainer, $('.additional-details-title', undefined, localize('tags', "Tags"))); const categoriesElement = append(categoriesContainer, $('.categories')); for (const category of extension.gallery.topics) { append(categoriesElement, $('span.category', { tabindex: '0' }, category)); @@ -879,24 +885,53 @@ class AdditionalDetailsWidget extends Disposable { } } - private renderExtensionResources(container: HTMLElement, extension: IWorkbenchMcpServer): void { - const resources: [string, URI][] = []; + private async renderExtensionResources(container: HTMLElement, extension: IWorkbenchMcpServer): Promise { + const resources: [string, ThemeIcon, URI][] = []; + const manifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); if (extension.repository) { try { - resources.push([localize('repository', "Repository"), URI.parse(extension.repository)]); + resources.push([localize('repository', "Repository"), ThemeIcon.fromId(Codicon.repo.id), URI.parse(extension.repository)]); } catch (error) {/* Ignore */ } } - if (extension.publisherUrl && extension.publisherDisplayName) { - resources.push([extension.publisherDisplayName, URI.parse(extension.publisherUrl)]); + if (manifest) { + const supportUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.ContactSupportUri); + if (supportUri) { + try { + resources.push([localize('support', "Support"), ThemeIcon.fromId(Codicon.commentDiscussion.id), URI.parse(supportUri)]); + } catch (error) {/* Ignore */ } + } + + const privacyUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.PrivacyPolicyUri); + if (privacyUri) { + try { + resources.push([localize('privacy', "Privacy Policy"), ThemeIcon.fromId(Codicon.law.id), URI.parse(privacyUri)]); + } catch (error) {/* Ignore */ } + } + + const termsUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.TermsOfServiceUri); + if (termsUri) { + try { + resources.push([localize('terms', "Terms of Service"), ThemeIcon.fromId(Codicon.law.id), URI.parse(termsUri)]); + } catch (error) {/* Ignore */ } + } + + const reportUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.ReportUri); + if (reportUri) { + try { + resources.push([localize('report', "Report abuse"), ThemeIcon.fromId(Codicon.report.id), URI.parse(reportUri)]); + } catch (error) {/* Ignore */ } + } } if (resources.length) { const extensionResourcesContainer = append(container, $('.resources-container.additional-details-element')); append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); - for (const [label, uri] of resources) { - const resource = append(resourcesElement, $('a.resource', { tabindex: '0' }, label)); - this.disposables.add(onClick(resource, () => this.openerService.open(uri))); - this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); + for (const [label, icon, uri] of resources) { + const resourceElement = append(resourcesElement, $('.resource')); + append(resourceElement, $(ThemeIcon.asCSSSelector(icon))); + append(resourceElement, $('a', { tabindex: '0' }, label)); + this.disposables.add(onClick(resourceElement, () => this.openerService.open(uri))); + this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), resourceElement, uri.toString())); } } } @@ -941,27 +976,23 @@ class AdditionalDetailsWidget extends Disposable { ); } } - if (gallery.publishDate) { - append(moreInfo, - $('.more-info-entry', undefined, - $('div.more-info-entry-name', undefined, localize('published', "Published")), - $('div', undefined, toDateString(new Date(gallery.publishDate))) - ) - ); - } - if (gallery.releaseDate) { + if (gallery.lastUpdated) { append(moreInfo, $('.more-info-entry', undefined, - $('div.more-info-entry-name', undefined, localize('released', "Released")), - $('div', undefined, toDateString(new Date(gallery.releaseDate))) + $('div.more-info-entry-name', undefined, localize('last updated', "Last Released")), + $('div', { + 'title': new Date(gallery.lastUpdated).toString() + }, fromNow(gallery.lastUpdated, true)) ) ); } - if (gallery.lastUpdated) { + if (gallery.publishDate) { append(moreInfo, $('.more-info-entry', undefined, - $('div.more-info-entry-name', undefined, localize('last released', "Last Released")), - $('div', undefined, toDateString(new Date(gallery.lastUpdated))) + $('div.more-info-entry-name', undefined, localize('published', "Published")), + $('div', { + 'title': new Date(gallery.publishDate).toString() + }, fromNow(gallery.publishDate, true)) ) ); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerIcons.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerIcons.ts index ade1b8c8049..a9595b51594 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerIcons.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerIcons.ts @@ -11,3 +11,4 @@ export const mcpServerIcon = registerIcon('mcp-server', Codicon.mcp, localize('m export const mcpServerRemoteIcon = registerIcon('mcp-server-remote', Codicon.remote, localize('mcpServerRemoteIcon', 'Icon to indicate that an MCP server is for the remote user scope.')); export const mcpServerWorkspaceIcon = registerIcon('mcp-server-workspace', Codicon.rootFolder, localize('mcpServerWorkspaceIcon', 'Icon to indicate that an MCP server is for the workspace scope.')); export const mcpStarredIcon = registerIcon('mcp-server-starred', Codicon.starFull, localize('starredIcon', 'Icon shown along with the starred status.')); +export const mcpLicenseIcon = registerIcon('mcp-server-license', Codicon.law, localize('licenseIcon', 'Icon shown along with the license status.')); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts index f98364a7e35..6761a41c515 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts @@ -22,7 +22,7 @@ import { ColorScheme } from '../../../../platform/theme/common/theme.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { McpServerStatusAction } from './mcpServerActions.js'; import { reset } from '../../../../base/browser/dom.js'; -import { mcpServerIcon, mcpServerRemoteIcon, mcpServerWorkspaceIcon, mcpStarredIcon } from './mcpServerIcons.js'; +import { mcpLicenseIcon, mcpServerIcon, mcpServerRemoteIcon, mcpServerWorkspaceIcon, mcpStarredIcon } from './mcpServerIcons.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; @@ -169,6 +169,7 @@ export class PublisherWidget extends McpServerWidget { } dom.append(this.element, publisherDisplayName); } else { + this.element.classList.toggle('clickable', !!this.mcpServer.gallery?.publisherUrl); this.element.setAttribute('role', 'button'); this.element.tabIndex = 0; @@ -186,6 +187,10 @@ export class PublisherWidget extends McpServerWidget { dom.append(verifiedPublisher, dom.$('span.extension-verified-publisher-domain', undefined, publisherDomainLink.authority.startsWith('www.') ? publisherDomainLink.authority.substring(4) : publisherDomainLink.authority)); this.disposables.add(onClick(verifiedPublisher, () => this.openerService.open(publisherDomainLink))); } + + if (this.mcpServer.gallery?.publisherUrl) { + this.disposables.add(onClick(this.element, () => this.openerService.open(this.mcpServer?.gallery?.publisherUrl!))); + } } } @@ -247,6 +252,39 @@ export class StarredWidget extends McpServerWidget { } +export class LicenseWidget extends McpServerWidget { + + private readonly disposables = this._register(new DisposableStore()); + + constructor( + readonly container: HTMLElement, + ) { + super(); + this.container.classList.add('license'); + this.render(); + this._register(toDisposable(() => this.clear())); + } + + private clear(): void { + this.container.innerText = ''; + this.disposables.clear(); + } + + render(): void { + this.clear(); + + if (!this.mcpServer?.license) { + return; + } + + const parent = dom.append(this.container, dom.$('span.license', { tabIndex: 0 })); + dom.append(parent, dom.$('span' + ThemeIcon.asCSSSelector(mcpLicenseIcon))); + + const licenseElement = dom.append(parent, dom.$('span', undefined, this.mcpServer.license)); + licenseElement.style.paddingLeft = '3px'; + } +} + export class McpServerHoverWidget extends McpServerWidget { private readonly hover = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index 118bfcc4a1e..bac6fb21bee 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -102,6 +102,10 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { return this.gallery?.starsCount ?? 0; } + get license(): string | undefined { + return this.gallery?.license; + } + get url(): string | undefined { return this.gallery?.url; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 52216b4a994..d43379a6869 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -657,6 +657,7 @@ export interface IWorkbenchMcpServer { readonly publisherUrl?: string; readonly publisherDisplayName?: string; readonly starsCount?: number; + readonly license?: string; readonly url?: string; readonly repository?: string; readonly config?: IMcpServerConfiguration | undefined; From ff5c6f710106e6b81e05dbaabfe9ea0b30a43c62 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 10 Sep 2025 16:58:40 +0200 Subject: [PATCH 0163/4355] update --- .../promptSyntax/promptFileContributions.ts | 13 +- .../promptSyntax/service/newPromptsParser.ts | 21 +- .../promptSyntax/service/promptValidator.ts | 352 ++++++++++++++++++ .../service/newPromptsParser.test.ts | 112 ++++++ 4 files changed, 483 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts index 63c908abeeb..71d350b20fa 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts @@ -8,12 +8,11 @@ import { Registry } from '../../../../../platform/registry/common/platform.js'; import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js'; import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } from '../../../../common/contributions.js'; import { PromptLinkProvider } from './languageProviders/promptLinkProvider.js'; -import { PromptLinkDiagnosticsInstanceManager } from './languageProviders/promptLinkDiagnosticsProvider.js'; -import { PromptHeaderDiagnosticsInstanceManager } from './languageProviders/promptHeaderDiagnosticsProvider.js'; import { PromptBodyAutocompletion } from './languageProviders/promptBodyAutocompletion.js'; import { PromptHeaderAutocompletion } from './languageProviders/promptHeaderAutocompletion.js'; import { PromptHeaderHoverProvider } from './languageProviders/promptHeaderHovers.js'; import { PromptHeaderDefinitionProvider } from './languageProviders/PromptHeaderDefinitionProvider.js'; +import { PromptValidatorContribution } from './service/promptValidator.js'; /** @@ -24,15 +23,7 @@ export function registerPromptFileContributions(): void { // all language constributions registerContribution(PromptLinkProvider); - registerContribution(PromptLinkDiagnosticsInstanceManager); - registerContribution(PromptHeaderDiagnosticsInstanceManager); - /** - * PromptDecorationsProviderInstanceManager is currently disabled because the only currently - * available decoration is the Front Matter header, which we decided to disable for now. - * Add it back when more decorations are needed. - */ - // registerContribution(PromptDecorationsProviderInstanceManager); , - + registerContribution(PromptValidatorContribution); registerContribution(PromptBodyAutocompletion); registerContribution(PromptHeaderAutocompletion); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 716494a36c3..f0c88fcb01d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -49,9 +49,15 @@ export class ParsedPromptFile { } } +export interface ParseError { + readonly message: string; + readonly range: Range; + readonly code: string; +} + interface ParsedHeader { readonly node: YamlNode | undefined; - readonly errors: YamlParseError[]; + readonly errors: ParseError[]; readonly attributes: IHeaderAttribute[]; } @@ -63,10 +69,11 @@ export class PromptHeader { public getParsedHeader(): ParsedHeader { if (this._parsed === undefined) { - const errors: YamlParseError[] = []; + const yamlErrors: YamlParseError[] = []; const lines = this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join(''); - const node = parse(lines, errors); + const node = parse(lines, yamlErrors); const attributes = []; + const errors: ParseError[] = yamlErrors.map(err => ({ message: err.message, range: this.asRange(err), code: err.code })); if (node?.type === 'object') { for (const property of node.properties) { attributes.push({ @@ -75,6 +82,8 @@ export class PromptHeader { value: this.asValue(property.value) }); } + } else { + errors.push({ message: 'Invalid header, expecting pairs', range: this.range, code: 'INVALID_YAML' }); } this._parsed = { node, attributes, errors }; } @@ -108,6 +117,10 @@ export class PromptHeader { return this.getParsedHeader().attributes; } + public get errors(): ParseError[] { + return this.getParsedHeader().errors; + } + private getStringAttribute(key: string): string | undefined { const attribute = this.getParsedHeader().attributes.find(attr => attr.key === key); if (attribute?.value.type === 'string') { @@ -162,7 +175,7 @@ export class PromptHeader { } -interface IHeaderAttribute { +export interface IHeaderAttribute { readonly range: Range; readonly key: string; readonly value: IValue; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts new file mode 100644 index 00000000000..488f956733b --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -0,0 +1,352 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isEmptyPattern, parse, splitGlobAware } from '../../../../../../base/common/glob.js'; +import { Iterable } from '../../../../../../base/common/iterator.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { localize } from '../../../../../../nls.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; +import { IChatMode, IChatModeService } from '../../chatModes.js'; +import { ChatModeKind } from '../../constants.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; +import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; +import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; +import { IHeaderAttribute, NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; +import { PromptsConfig } from '../config/config.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { Delayer } from '../../../../../../base/common/async.js'; +import { ResourceMap } from '../../../../../../base/common/map.js'; + +const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; + +export class PromptValidator { + constructor( + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, + @IChatModeService private readonly chatModeService: IChatModeService, + ) { } + + public validate(promptAST: ParsedPromptFile, model: ITextModel, promptType: PromptsType): IMarkerData[] { + const markers: IMarkerData[] = []; + promptAST.header?.errors.forEach(error => { + markers.push(toMarker(error.message, error.range, MarkerSeverity.Error)); + }); + this.validateHeader(promptAST, model, promptType, markers); + return markers; + } + + private validateHeader(promptAST: ParsedPromptFile, model: ITextModel, promptType: PromptsType, result: IMarkerData[]): void { + const header = promptAST.header; + if (!header) { + return; + } + const validAttributeNames = getValidAttributeNames(promptType); + const attributes = header.attributes; + for (const attribute of attributes) { + if (!validAttributeNames.includes(attribute.key)) { + switch (promptType) { + case PromptsType.prompt: + result.push(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + break; + case PromptsType.mode: + result.push(toMarker(localize('promptValidator.unknownAttribute.mode', "Attribute '{0}' is not supported in mode files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + break; + case PromptsType.instructions: + result.push(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + break; + } + } + } + this.validateDescription(attributes, result); + switch (promptType) { + case PromptsType.prompt: { + const mode = this.validateMode(attributes, result); + this.validateTools(attributes, mode?.kind ?? ChatModeKind.Agent, result); + this.validateModel(attributes, mode?.kind ?? ChatModeKind.Agent, result); + break; + } + case PromptsType.instructions: + this.validateApplyTo(attributes, result); + break; + + case PromptsType.mode: + this.validateTools(attributes, ChatModeKind.Agent, result); + this.validateModel(attributes, ChatModeKind.Agent, result); + break; + + } + } + + private validateDescription(attributes: IHeaderAttribute[], markers: IMarkerData[]): void { + const descriptionAttribute = attributes.find(attr => attr.key === 'description'); + if (!descriptionAttribute) { + return; + } + if (descriptionAttribute.value.type !== 'string') { + markers.push(toMarker(localize('promptValidator.descriptionMustBeString', "The 'description' attribute must be a string."), descriptionAttribute.range, MarkerSeverity.Error)); + return; + } + if (descriptionAttribute.value.value.trim().length === 0) { + markers.push(toMarker(localize('promptValidator.descriptionShouldNotBeEmpty', "The 'description' attribute should not be empty."), descriptionAttribute.value.range, MarkerSeverity.Error)); + return; + } + } + + + private validateModel(attributes: IHeaderAttribute[], modeKind: ChatModeKind, markers: IMarkerData[]): void { + const attribute = attributes.find(attr => attr.key === 'model'); + if (!attribute) { + return; + } + if (attribute.value.type !== 'string') { + markers.push(toMarker(localize('promptValidator.modelMustBeString', "The 'model' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + return; + } + const modelName = attribute.value.value.trim(); + if (modelName.length === 0) { + markers.push(toMarker(localize('promptValidator.modelMustBeNonEmpty', "The 'model' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + return; + } + + const languageModes = this.languageModelsService.getLanguageModelIds(); + if (languageModes.length === 0) { + // likely the service is not initialized yet + return; + } + const modelMetadata = this.findModelByName(languageModes, modelName); + if (!modelMetadata) { + markers.push(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'", modelName), attribute.value.range, MarkerSeverity.Warning)); + + } else if (modeKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { + markers.push(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode", modelName), attribute.value.range, MarkerSeverity.Warning)); + } + } + + findModelByName(languageModes: string[], modelName: string): ILanguageModelChatMetadata | undefined { + for (const model of languageModes) { + const metadata = this.languageModelsService.lookupLanguageModel(model); + if (metadata && metadata.isUserSelectable !== false && ILanguageModelChatMetadata.matchesQualifiedName(modelName, metadata)) { + return metadata; + } + } + return undefined; + } + + private validateMode(attributes: IHeaderAttribute[], markers: IMarkerData[]): IChatMode | undefined { + const attribute = attributes.find(attr => attr.key === 'mode'); + if (!attribute) { + return undefined; // default mode for prompts is Agent + } + if (attribute.value.type !== 'string') { + markers.push(toMarker(localize('promptValidator.modeMustBeString', "The 'mode' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + return undefined; + } + const modeValue = attribute.value.value; + if (modeValue.trim().length === 0) { + markers.push(toMarker(localize('promptValidator.modeMustBeNonEmpty', "The 'mode' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + return undefined; + } + + const modes = this.chatModeService.getModes(); + const availableModes = []; + + // Check if mode exists in builtin or custom modes + for (const mode of Iterable.concat(modes.builtin, modes.custom)) { + if (mode.name === modeValue) { + return mode; + } + availableModes.push(mode.name); // collect all available mode names + } + + const errorMessage = localize('promptValidator.modeNotFound', "Unknown mode '{0}'. Available modes: {1}", modeValue, availableModes.join(', ')); + markers.push(toMarker(errorMessage, attribute.value.range, MarkerSeverity.Warning)); + return undefined; + } + + private validateTools(attributes: IHeaderAttribute[], modeKind: ChatModeKind, markers: IMarkerData[]): undefined { + const attribute = attributes.find(attr => attr.key === 'tools'); + if (!attribute) { + return; + } + if (modeKind !== ChatModeKind.Agent) { + markers.push(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); + + } + if (attribute.value.type !== 'array') { + markers.push(toMarker(localize('promptValidator.toolsMustBeArray', "The 'tools' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); + return; + } + + const toolNames = new Map(); + for (const item of attribute.value.items) { + if (item.type !== 'string') { + markers.push(toMarker(localize('promptValidator.eachToolMustBeString', "Each tool name in the 'tools' attribute must be a string."), item.range, MarkerSeverity.Error)); + } else { + toolNames.set(item.value, item.range); + } + } + if (toolNames.size === 0) { + return; + } + for (const tool of this.languageModelToolsService.getTools()) { + toolNames.delete(tool.toolReferenceName ?? tool.displayName); + } + for (const toolSet of this.languageModelToolsService.toolSets.get()) { + toolNames.delete(toolSet.referenceName); + } + + for (const [toolName, range] of toolNames) { + markers.push(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'", toolName), range, MarkerSeverity.Warning)); + } + } + + private validateApplyTo(attributes: IHeaderAttribute[], markers: IMarkerData[]): undefined { + const attribute = attributes.find(attr => attr.key === 'applyTo'); + if (!attribute) { + return; + } + if (attribute.value.type !== 'string') { + markers.push(toMarker(localize('promptValidator.applyToMustBeString', "The 'applyTo' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + return; + } + const pattern = attribute.value.value; + try { + const patterns = splitGlobAware(pattern, ','); + if (patterns.length === 0) { + markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + return; + } + for (const pattern of patterns) { + const globPattern = parse(pattern); + if (isEmptyPattern(globPattern)) { + markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + return; + } + } + } catch (_error) { + markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + } + } +} + +function getValidAttributeNames(promptType: PromptsType): string[] { + switch (promptType) { + case PromptsType.prompt: + return ['description', 'model', 'tools', 'mode']; + case PromptsType.instructions: + return ['description', 'applyTo']; + case PromptsType.mode: + return ['description', 'model', 'tools']; + } +} + +function toMarker(message: string, range: Range, severity = MarkerSeverity.Error): IMarkerData { + return { severity, message, ...range }; +} + +export class PromptValidatorContribution extends Disposable { + + private readonly validator: PromptValidator; + private readonly promptParser: NewPromptsParser; + private readonly localDisposables = this._register(new DisposableStore()); + + constructor( + @IModelService private modelService: IModelService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService private configService: IConfigurationService, + @IMarkerService private readonly markerService: IMarkerService, + ) { + super(); + this.validator = instantiationService.createInstance(PromptValidator); + this.promptParser = instantiationService.createInstance(NewPromptsParser); + + this.updateRegistration(); + this._register(this.configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(PromptsConfig.KEY)) { + this.updateRegistration(); + } + })); + } + + updateRegistration(): void { + this.localDisposables.clear(); + if (!PromptsConfig.enabled(this.configService)) { + return; + } + const trackers = new ResourceMap(); + this.localDisposables.add(toDisposable(() => { + trackers.forEach(tracker => tracker.dispose()); + })); + this.modelService.getModels().forEach(model => { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + } + }); + this.localDisposables.add(this.modelService.onModelAdded((model) => { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + } + })); + this.localDisposables.add(this.modelService.onModelRemoved((model) => { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + const tracker = trackers.get(model.uri); + if (tracker) { + tracker.dispose(); + trackers.delete(model.uri); + } + } + })); + this.localDisposables.add(this.modelService.onModelLanguageChanged((event) => { + const { model } = event; + const tracker = trackers.get(model.uri); + if (tracker) { + tracker.dispose(); + trackers.delete(model.uri); + } + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + } + })); + } +} + +class ModelTracker extends Disposable { + + private readonly delayer: Delayer; + + constructor( + private readonly textModel: ITextModel, + private readonly promptType: PromptsType, + private readonly validator: PromptValidator, + private readonly promptParser: NewPromptsParser, + @IMarkerService private readonly markerService: IMarkerService, + ) { + super(); + this.delayer = this._register(new Delayer(200)); + this._register(textModel.onDidChangeContent(() => this.validate())); + this.validate(); + } + + private validate(): void { + this.delayer.trigger(() => { + const ast = this.promptParser.parse(this.textModel.uri, this.textModel.getValue()); + const markers = this.validator.validate(ast, this.textModel, this.promptType); + this.markerService.changeOne(MARKERS_OWNER_ID, this.textModel.uri, markers); + }); + } + + public override dispose() { + this.markerService.remove(MARKERS_OWNER_ID, [this.textModel.uri]); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 97fc5649a94..57352939405 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -80,4 +80,116 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.description, 'Code style instructions for TypeScript'); assert.deepEqual(result.header.applyTo, '*.ts'); }); + + test('prompt file', async () => { + const uri = URI.parse('file:///test/prompt2.md'); + const content = [ + /* 01 */"---", + /* 02 */`description: "General purpose coding assistant"`, + /* 03 */"mode: agent", + /* 04 */"model: GPT 4.1", + /* 05 */"tools: ['search', 'terminal']", + /* 06 */"---", + /* 07 */"This is a prompt file body referencing #search and [docs](https://example.com/docs).", + ].join('\n'); + const result = new NewPromptsParser().parse(uri, content); + assert.deepEqual(result.uri, uri); + assert.ok(result.header); + assert.ok(result.body); + assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 6, endColumn: 1 }); + assert.deepEqual(result.header.attributes, [ + { key: 'description', range: new Range(2, 1, 2, 48), value: { type: 'string', value: 'General purpose coding assistant', range: new Range(2, 14, 2, 48) } }, + { key: 'mode', range: new Range(3, 1, 3, 13), value: { type: 'string', value: 'agent', range: new Range(3, 7, 3, 12) } }, + { key: 'model', range: new Range(4, 1, 4, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(4, 8, 4, 15) } }, + { + key: 'tools', range: new Range(5, 1, 5, 30), value: { + type: 'array', + items: [{ type: 'string', value: 'search', range: new Range(5, 9, 5, 17) }, { type: 'string', value: 'terminal', range: new Range(5, 19, 5, 29) }], + range: new Range(5, 8, 5, 30) + } + }, + ]); + assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.deepEqual(result.body.fileReferences, [ + { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs' }, + ]); + assert.deepEqual(result.body.variableReferences, [ + { range: new Range(7, 41, 7, 47), content: 'search' } + ]); + assert.deepEqual(result.header.description, 'General purpose coding assistant'); + assert.deepEqual(result.header.mode, 'prompt'); + assert.deepEqual(result.header.model, 'GPT 4.1'); + assert.ok(result.header.tools); + assert.deepEqual([...result.header.tools.entries()], [['search', true], ['terminal', true]]); + }); + + test('prompt file tools as map', async () => { + const uri = URI.parse('file:///test/prompt2.md'); + const content = [ + /* 01 */"---", + /* 02 */"tools:", + /* 03 */" built-in: true", + /* 04 */" mcp:", + /* 05 */" vscode-playright-mcp:", + /* 06 */" browser-click: true", + /* 07 */" extensions:", + /* 08 */" github.vscode-pull-request-github:", + /* 09 */" openPullRequest: true", + /* 10 */" copilotCodingAgent: false", + /* 11 */"---", + ].join('\n'); + const result = new NewPromptsParser().parse(uri, content); + assert.deepEqual(result.uri, uri); + assert.ok(result.header); + assert.ok(!result.body); + assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 11, endColumn: 1 }); + assert.deepEqual(result.header.attributes, [ + { + key: 'tools', range: new Range(2, 1, 10, 32), value: { + type: 'object', + properties: [ + { + "key": { type: 'string', value: 'built-in', range: new Range(3, 3, 3, 11) }, + "value": { type: 'boolean', value: true, range: new Range(3, 13, 3, 17) } + }, + { + "key": { type: 'string', value: 'mcp', range: new Range(4, 3, 4, 6) }, + "value": { + type: 'object', range: new Range(5, 5, 6, 26), properties: [ + { + "key": { type: 'string', value: 'vscode-playright-mcp', range: new Range(5, 5, 5, 25) }, "value": { + type: 'object', range: new Range(6, 7, 6, 26), properties: [ + { "key": { type: 'string', value: 'browser-click', range: new Range(6, 7, 6, 20) }, "value": { type: 'boolean', value: true, range: new Range(6, 22, 6, 26) } } + ] + } + } + ] + } + }, + { + "key": { type: 'string', value: 'extensions', range: new Range(7, 3, 7, 13) }, + "value": { + type: 'object', range: new Range(8, 5, 10, 32), properties: [ + { + "key": { type: 'string', value: 'github.vscode-pull-request-github', range: new Range(8, 5, 8, 38) }, "value": { + type: 'object', range: new Range(9, 7, 10, 32), properties: [ + { "key": { type: 'string', value: 'openPullRequest', range: new Range(9, 7, 9, 22) }, "value": { type: 'boolean', value: true, range: new Range(9, 24, 9, 28) } }, + { "key": { type: 'string', value: 'copilotCodingAgent', range: new Range(10, 7, 10, 25) }, "value": { type: 'boolean', value: false, range: new Range(10, 27, 10, 32) } } + ] + } + } + ] + } + }, + ], + range: new Range(3, 3, 10, 32) + }, + } + ]); + assert.deepEqual(result.header.description, undefined); + assert.deepEqual(result.header.mode, undefined); + assert.deepEqual(result.header.model, undefined); + assert.ok(result.header.tools); + assert.deepEqual([...result.header.tools.entries()], [['built-in', true], ['browser-click', true], ['openPullRequest', true], ['copilotCodingAgent', false]]); + }); }); From bdd60edf22987610aac8d74d39ea7909dcd11851 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:23:11 +0000 Subject: [PATCH 0164/4355] Improve Problem Selection and Search in GitHub Copilot Context Addition (#265877) * Initial plan * Implement problem prioritization and search in GitHub Copilot context Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * Fix async function implementation and add public test method Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> Co-authored-by: Connor Peet --- .../markers/browser/markersChatContext.ts | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts index f49818accba..309d35faf2b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts +++ b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts @@ -5,6 +5,7 @@ import { groupBy } from '../../../../base/common/arrays.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { extUri } from '../../../../base/common/resources.js'; @@ -13,8 +14,10 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILabelService } from '../../../../platform/label/common/label.js'; import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; +import { EditorResourceAccessor } from '../../../common/editor.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker } from '../../chat/browser/chatContextPickService.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatVariableEntries.js'; class MarkerChatContextPick implements IChatContextPickerItem { @@ -27,21 +30,61 @@ class MarkerChatContextPick implements IChatContextPickerItem { constructor( @IMarkerService private readonly _markerService: IMarkerService, @ILabelService private readonly _labelService: ILabelService, + @IEditorService private readonly _editorService: IEditorService, ) { } asPicker(): IChatContextPicker { + return { + placeholder: localize('chatContext.diagnstic.placeholder', 'Select a problem to attach'), + picks: picksWithPromiseFn(async (query: string, token: CancellationToken) => { + return this.getPicksForQuery(query); + }) + }; + } + /** + * @internal For testing purposes only + */ + getPicksForQuery(query: string): (IChatContextPickerPickItem | IQuickPickSeparator)[] { const markers = this._markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); const grouped = groupBy(markers, (a, b) => extUri.compare(a.resource, b.resource)); + // Get the active editor URI for prioritization + const activeEditorUri = EditorResourceAccessor.getCanonicalUri(this._editorService.activeEditor); + + // Sort groups to prioritize active file + const sortedGroups = grouped.sort((groupA, groupB) => { + const resourceA = groupA[0].resource; + const resourceB = groupB[0].resource; + + // If one group is from the active file, prioritize it + if (activeEditorUri) { + const isAActiveFile = extUri.isEqual(resourceA, activeEditorUri); + const isBActiveFile = extUri.isEqual(resourceB, activeEditorUri); + + if (isAActiveFile && !isBActiveFile) { + return -1; // A comes first + } + if (!isAActiveFile && isBActiveFile) { + return 1; // B comes first + } + } + + // Otherwise, sort by resource URI as before + return extUri.compare(resourceA, resourceB); + }); + const severities = new Set(); const items: (IChatContextPickerPickItem | IQuickPickSeparator)[] = []; let pickCount = 0; - for (const group of grouped) { + for (const group of sortedGroups) { const resource = group[0].resource; + const isActiveFile = activeEditorUri && extUri.isEqual(resource, activeEditorUri); + const fileLabel = this._labelService.getUriLabel(resource, { relative: true }); + const separatorLabel = isActiveFile ? `${fileLabel} (current file)` : fileLabel; - items.push({ type: 'separator', label: this._labelService.getUriLabel(resource, { relative: true }) }); + items.push({ type: 'separator', label: separatorLabel }); for (const marker of group) { pickCount++; severities.add(marker.severity); @@ -65,11 +108,7 @@ class MarkerChatContextPick implements IChatContextPickerItem { }, }); - - return { - placeholder: localize('chatContext.diagnstic.placeholder', 'Select a problem to attach'), - picks: Promise.resolve(items) - }; + return items; } } From 33d9805a2e2ce7bd789ee98eb2d02a09f01eb0de Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:28:46 -0700 Subject: [PATCH 0165/4355] If there's just a title, let that be the aria label (#266049) Fixes https://github.com/microsoft/vscode/issues/245220 --- src/vs/platform/quickinput/browser/quickInput.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index c8d38f2778b..74b42dc9c4e 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1056,10 +1056,15 @@ export class QuickPick Date: Wed, 10 Sep 2025 17:54:09 +0200 Subject: [PATCH 0166/4355] fix paging. Implement server by name (#266056) --- .../platform/mcp/common/mcpGalleryManifest.ts | 3 +- .../mcp/common/mcpGalleryManifestService.ts | 6 +- .../platform/mcp/common/mcpGalleryService.ts | 59 +++++++++++++++++-- src/vs/platform/mcp/common/mcpManagement.ts | 1 + .../extensions/browser/extensionEditor.ts | 6 +- .../contrib/mcp/browser/mcpServerEditor.ts | 4 +- .../mcp/browser/mcpWorkbenchService.ts | 22 +++++++ 7 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index e534ff2375d..759f09c5b86 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -9,7 +9,8 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; export const enum McpGalleryResourceType { McpServersQueryService = 'McpServersQueryService', McpServerWebUri = 'McpServerWebUriTemplate', - McpServerManifestUri = 'McpServerManifestUriTemplate', + McpServerResourceUri = 'McpServerResourceUriTemplate', + McpServerNamedResourceUri = 'McpServerNamedResourceUriTemplate', PublisherUriTemplate = 'PublisherUriTemplate', ContactSupportUri = 'ContactSupportUri', PrivacyPolicyUri = 'PrivacyPolicyUri', diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index 5d4edc1138e..43080ff3ccb 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -46,11 +46,15 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery if (!isVSCodeGalleryUrl) { resources.push({ id: `${serversUrl}/{id}`, - type: McpGalleryResourceType.McpServerManifestUri + type: McpGalleryResourceType.McpServerResourceUri }); } if (isProductGalleryUrl) { + resources.push({ + id: `${serversUrl}/by-name/{name}`, + type: McpGalleryResourceType.McpServerNamedResourceUri + }); resources.push({ id: this.productService.mcpGallery.itemWebUrl, type: McpGalleryResourceType.McpServerWebUri diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index ea6f9a5f5da..7c5017f21d9 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -163,13 +163,13 @@ class Query { get pageSize(): number { return this.state.pageSize; } get searchText(): string | undefined { return this.state.searchText; } - + get cursor(): string | undefined { return this.state.cursor; } withPage(cursor: string, pageSize: number = this.pageSize): Query { return new Query({ ...this.state, pageSize, cursor }); } - withSearchText(searchText: string): Query { + withSearchText(searchText: string | undefined): Query { return new Query({ ...this.state, searchText }); } } @@ -198,7 +198,11 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return singlePagePager([]); } - const query = new Query(); + let query = new Query(); + if (options?.text) { + query = query.withSearchText(options.text.trim()); + } + const { servers, metadata } = await this.queryGalleryMcpServers(query, mcpGalleryManifest, token); const total = metadata?.total ?? metadata?.count ?? servers.length; @@ -206,7 +210,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService if (ct.isCancellationRequested) { throw new CancellationError(); } - const { servers, metadata } = cursor ? await this.queryGalleryMcpServers(query.withPage(cursor), mcpGalleryManifest, token) : { servers: [], metadata: undefined }; + const { servers, metadata } = cursor ? await this.queryGalleryMcpServers(query.withPage(cursor).withSearchText(undefined), mcpGalleryManifest, token) : { servers: [], metadata: undefined }; return { elements: servers, total, @@ -418,7 +422,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } } - const url = `${mcpGalleryUrl}?limit=${query.pageSize}`; + let url = `${mcpGalleryUrl}?limit=${query.pageSize}`; + if (query.cursor) { + url += `&cursor=${query.cursor}`; + } + if (query.searchText) { + const text = encodeURIComponent(query.searchText); + url += `&search=${text}`; + } const context = await this.requestService.request({ type: 'GET', @@ -466,6 +477,34 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return this.toGalleryMcpServer(this.toIRawGalleryMcpServer(server), mcpGalleryManifest); } + async getMcpServerByName(name: string): Promise { + const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); + if (!mcpGalleryManifest) { + return undefined; + } + + const mcpServerUrl = this.getNamedServerUrl(name, mcpGalleryManifest); + if (!mcpServerUrl) { + return undefined; + } + + const context = await this.requestService.request({ + type: 'GET', + url: mcpServerUrl, + }, CancellationToken.None); + + if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { + return undefined; + } + + const server = await asJson(context); + if (!server) { + return undefined; + } + + return this.toGalleryMcpServer(this.toIRawGalleryMcpServer(server), mcpGalleryManifest); + } + private toIRawGalleryMcpServer(from: IRawGalleryOldMcpServer | IRawGalleryMcpServer): IRawGalleryMcpServer { if (isIRawGalleryOldMcpServer(from)) { return { @@ -520,13 +559,21 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } private getServerUrl(id: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { - const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerManifestUri); + const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerResourceUri); if (!resourceUriTemplate) { return undefined; } return format2(resourceUriTemplate, { id }); } + private getNamedServerUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + const namedResourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerNamedResourceUri); + if (!namedResourceUriTemplate) { + return undefined; + } + return format2(namedResourceUriTemplate, { name }); + } + private getWebUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerWebUri); if (!resourceUriTemplate) { diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 8e6f37ae496..4f8c399c885 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -171,6 +171,7 @@ export interface IMcpGalleryService { getMcpServersFromVSCodeGallery(servers: string[]): Promise; getMcpServersFromGallery(urls: string[]): Promise; getMcpServer(url: string): Promise; + getMcpServerByName(name: string): Promise; getMcpServerConfiguration(extension: IGalleryMcpServer, token: CancellationToken): Promise; getReadme(extension: IGalleryMcpServer, token: CancellationToken): Promise; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index d065bcfbc2c..0346db34828 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -1141,7 +1141,7 @@ class AdditionalDetailsWidget extends Disposable { $('div.more-info-entry-name', undefined, localize('last updated', "Last Updated")), $('div', { 'title': new Date(extension.installedTimestamp).toString() - }, fromNow(extension.installedTimestamp, true)) + }, fromNow(extension.installedTimestamp, true, true, true)) ) ); } @@ -1232,13 +1232,13 @@ class AdditionalDetailsWidget extends Disposable { $('div.more-info-entry-name', undefined, localize('published', "Published")), $('div', { 'title': new Date(gallery.releaseDate).toString() - }, fromNow(gallery.releaseDate, true)) + }, fromNow(gallery.releaseDate, true, true, true)) ), $('.more-info-entry', undefined, $('div.more-info-entry-name', undefined, localize('last released', "Last Released")), $('div', { 'title': new Date(gallery.lastUpdated).toString() - }, fromNow(gallery.lastUpdated, true)) + }, fromNow(gallery.lastUpdated, true, true, true)) ) ); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 0e45b2486e6..20f5c856dc8 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -982,7 +982,7 @@ class AdditionalDetailsWidget extends Disposable { $('div.more-info-entry-name', undefined, localize('last updated', "Last Released")), $('div', { 'title': new Date(gallery.lastUpdated).toString() - }, fromNow(gallery.lastUpdated, true)) + }, fromNow(gallery.lastUpdated, true, true, true)) ) ); } @@ -992,7 +992,7 @@ class AdditionalDetailsWidget extends Disposable { $('div.more-info-entry-name', undefined, localize('published', "Published")), $('div', { 'title': new Date(gallery.publishDate).toString() - }, fromNow(gallery.publishDate, true)) + }, fromNow(gallery.publishDate, true, true, true)) ) ); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index bac6fb21bee..725a920a030 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -623,6 +623,12 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (uri.path === 'mcp/install') { return this.handleMcpInstallUri(uri); } + if (uri.path.startsWith('mcp/by-name/')) { + const mcpServerName = uri.path.substring('mcp/by-name/'.length); + if (mcpServerName) { + return this.handleMcpServerByName(mcpServerName); + } + } if (uri.path.startsWith('mcp/')) { const mcpServerUrl = uri.path.substring(4); if (mcpServerUrl) { @@ -679,6 +685,22 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return true; } + private async handleMcpServerByName(name: string): Promise { + try { + const gallery = await this.mcpGalleryService.getMcpServerByName(name); + if (!gallery) { + this.logService.info(`MCP server '${name}' not found`); + return true; + } + const local = this.local.find(e => e.url === gallery.url) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); + this.open(local); + } catch (e) { + // ignore + this.logService.error(e); + } + return true; + } + async open(extension: IWorkbenchMcpServer, options?: IEditorOptions): Promise { await this.editorService.openEditor(this.instantiationService.createInstance(McpServerEditorInput, extension), options, ACTIVE_GROUP); } From 8890c538c821a4f4738cf5407f3f4584544e1b49 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 10 Sep 2025 11:54:38 -0400 Subject: [PATCH 0167/4355] fix terminal voice action menu config (#266060) fix #266059 --- .../contrib/terminal/browser/terminalMenus.ts | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 35171618ffc..58f5ec9476c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -100,32 +100,6 @@ export function setupTerminalMenus(): void { when: TerminalContextKeys.processSupported } }, - { - id: MenuId.ViewTitle, - item: { - command: { - id: TerminalCommandId.StartVoice, - title: localize('workbench.action.terminal.startVoice', "Start Dictation"), - }, - group: 'navigation', - order: 5, - when: HasSpeechProvider, - isHiddenByDefault: true - } - }, - { - id: MenuId.ViewTitle, - item: { - command: { - id: TerminalCommandId.StopVoice, - title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), - }, - group: 'navigation', - order: 6, - when: HasSpeechProvider, - isHiddenByDefault: true - } - }, ] ); @@ -545,7 +519,33 @@ export function setupTerminalMenus(): void { order: 8, when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), isHiddenByDefault: true - } + }, + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StartVoice, + title: localize('workbench.action.terminal.startVoice', "Start Voice"), + }, + group: 'navigation', + order: 9, + when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + isHiddenByDefault: true + }, + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StopVoice, + title: localize('workbench.action.terminal.stopVoice', "Stop Voice"), + }, + group: 'navigation', + order: 9, + when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + isHiddenByDefault: true + }, }, ] ); @@ -756,7 +756,7 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItem(menuId, { command: { id: TerminalCommandId.StartVoice, - title: localize('workbench.action.terminal.startVoice', "Start Dictation"), + title: localize('workbench.action.terminal.startVoiceEditor', "Start Dictation"), icon: Codicon.run }, group: 'navigation', @@ -767,7 +767,7 @@ export function setupTerminalMenus(): void { MenuRegistry.appendMenuItem(menuId, { command: { id: TerminalCommandId.StopVoice, - title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), + title: localize('workbench.action.terminal.stopVoiceEditor', "Stop Dictation"), icon: Codicon.run }, group: 'navigation', From a2b3aee71293c653a9fc868e05e4a1bbaebc7cad Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 10 Sep 2025 18:43:31 +0200 Subject: [PATCH 0168/4355] update --- .../promptSyntax/service/promptValidator.ts | 18 +- .../chat/test/common/mockChatModeService.ts | 4 +- .../service/newPromptsParser.test.ts | 4 +- .../service/promptValidator.test.ts | 275 ++++++++++++++++++ .../service/promptsService.test.ts | 1 + 5 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index 488f956733b..288c26c6c53 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -32,16 +32,16 @@ export class PromptValidator { @IChatModeService private readonly chatModeService: IChatModeService, ) { } - public validate(promptAST: ParsedPromptFile, model: ITextModel, promptType: PromptsType): IMarkerData[] { + public validate(promptAST: ParsedPromptFile, promptType: PromptsType): IMarkerData[] { const markers: IMarkerData[] = []; promptAST.header?.errors.forEach(error => { markers.push(toMarker(error.message, error.range, MarkerSeverity.Error)); }); - this.validateHeader(promptAST, model, promptType, markers); + this.validateHeader(promptAST, promptType, markers); return markers; } - private validateHeader(promptAST: ParsedPromptFile, model: ITextModel, promptType: PromptsType, result: IMarkerData[]): void { + private validateHeader(promptAST: ParsedPromptFile, promptType: PromptsType, result: IMarkerData[]): void { const header = promptAST.header; if (!header) { return; @@ -121,14 +121,14 @@ export class PromptValidator { } const modelMetadata = this.findModelByName(languageModes, modelName); if (!modelMetadata) { - markers.push(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'", modelName), attribute.value.range, MarkerSeverity.Warning)); + markers.push(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'.", modelName), attribute.value.range, MarkerSeverity.Warning)); } else if (modeKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { - markers.push(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode", modelName), attribute.value.range, MarkerSeverity.Warning)); + markers.push(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode.", modelName), attribute.value.range, MarkerSeverity.Warning)); } } - findModelByName(languageModes: string[], modelName: string): ILanguageModelChatMetadata | undefined { + private findModelByName(languageModes: string[], modelName: string): ILanguageModelChatMetadata | undefined { for (const model of languageModes) { const metadata = this.languageModelsService.lookupLanguageModel(model); if (metadata && metadata.isUserSelectable !== false && ILanguageModelChatMetadata.matchesQualifiedName(modelName, metadata)) { @@ -164,7 +164,7 @@ export class PromptValidator { availableModes.push(mode.name); // collect all available mode names } - const errorMessage = localize('promptValidator.modeNotFound', "Unknown mode '{0}'. Available modes: {1}", modeValue, availableModes.join(', ')); + const errorMessage = localize('promptValidator.modeNotFound', "Unknown mode '{0}'. Available modes: {1}.", modeValue, availableModes.join(', ')); markers.push(toMarker(errorMessage, attribute.value.range, MarkerSeverity.Warning)); return undefined; } @@ -202,7 +202,7 @@ export class PromptValidator { } for (const [toolName, range] of toolNames) { - markers.push(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'", toolName), range, MarkerSeverity.Warning)); + markers.push(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", toolName), range, MarkerSeverity.Warning)); } } @@ -340,7 +340,7 @@ class ModelTracker extends Disposable { private validate(): void { this.delayer.trigger(() => { const ast = this.promptParser.parse(this.textModel.uri, this.textModel.getValue()); - const markers = this.validator.validate(ast, this.textModel, this.promptType); + const markers = this.validator.validate(ast, this.promptType); this.markerService.changeOne(MARKERS_OWNER_ID, this.textModel.uri, markers); }); } diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts index 300b1f202fc..fc3cdb78daf 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatModeService.ts @@ -10,10 +10,10 @@ import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js export class MockChatModeService implements IChatModeService { readonly _serviceBrand: undefined; - private _modes: { builtin: readonly IChatMode[]; custom: readonly IChatMode[] } = { builtin: [ChatMode.Ask], custom: [] }; - public readonly onDidChangeChatModes = Event.None; + constructor(private readonly _modes: { builtin: readonly IChatMode[]; custom: readonly IChatMode[] } = { builtin: [ChatMode.Ask], custom: [] }) { } + getModes(): { builtin: readonly IChatMode[]; custom: readonly IChatMode[] } { return this._modes; } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 57352939405..62ee58be278 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -99,7 +99,7 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 6, endColumn: 1 }); assert.deepEqual(result.header.attributes, [ { key: 'description', range: new Range(2, 1, 2, 48), value: { type: 'string', value: 'General purpose coding assistant', range: new Range(2, 14, 2, 48) } }, - { key: 'mode', range: new Range(3, 1, 3, 13), value: { type: 'string', value: 'agent', range: new Range(3, 7, 3, 12) } }, + { key: 'mode', range: new Range(3, 1, 3, 12), value: { type: 'string', value: 'agent', range: new Range(3, 7, 3, 12) } }, { key: 'model', range: new Range(4, 1, 4, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(4, 8, 4, 15) } }, { key: 'tools', range: new Range(5, 1, 5, 30), value: { @@ -117,7 +117,7 @@ suite('NewPromptsParser', () => { { range: new Range(7, 41, 7, 47), content: 'search' } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); - assert.deepEqual(result.header.mode, 'prompt'); + assert.deepEqual(result.header.mode, 'agent'); assert.deepEqual(result.header.model, 'GPT 4.1'); assert.ok(result.header.tools); assert.deepEqual([...result.header.tools.entries()], [['search', true], ['terminal', true]]); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts new file mode 100644 index 00000000000..58231ccc889 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts @@ -0,0 +1,275 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; + +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptValidator } from '../../../../common/promptSyntax/service/promptValidator.js'; +import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../../../common/languageModelToolsService.js'; +import { ObservableSet } from '../../../../../../../base/common/observable.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../../common/languageModels.js'; +import { ExtensionIdentifier } from '../../../../../../../platform/extensions/common/extensions.js'; +import { ChatMode, CustomChatMode, IChatModeService } from '../../../../common/chatModes.js'; +import { MockChatModeService } from '../../mockChatModeService.js'; +import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; +import { IMarkerData, MarkerSeverity } from '../../../../../../../platform/markers/common/markers.js'; +import { getPromptFileExtension } from '../../../../common/promptSyntax/config/promptFileLocations.js'; + +suite('PromptValidator', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instaService: TestInstantiationService; + + setup(async () => { + instaService = disposables.add(new TestInstantiationService()); + + const testConfigService = new TestConfigurationService(); + testConfigService.setUserConfiguration(PromptsConfig.KEY, true); + + instaService.stub(IConfigurationService, testConfigService); + + const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + + instaService.stub(ILanguageModelToolsService, { + getTools() { return [testTool1, testTool2]; }, + toolSets: new ObservableSet().observable + }); + + const testModels: ILanguageModelChatMetadata[] = [ + { id: 'mae-4', name: 'MAE 4', vendor: 'olama', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true } } satisfies ILanguageModelChatMetadata, + { id: 'mae-4.1', name: 'MAE 4.1', vendor: 'copilot', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true } } satisfies ILanguageModelChatMetadata, + { id: 'mae-3.5-turbo', name: 'MAE 3.5 Turbo', vendor: 'copilot', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024 } satisfies ILanguageModelChatMetadata + ]; + + instaService.stub(ILanguageModelsService, { + getLanguageModelIds() { return testModels.map(m => m.id); }, + lookupLanguageModel(name: string) { + return testModels.find(m => m.id === name); + } + }); + + const customChatMode = new CustomChatMode({ uri: URI.file('/test/chatmode.md'), name: 'BeastMode', body: '', variableReferences: [] }); + instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); + }); + + function validate(code: string, promptType: PromptsType): IMarkerData[] { + const uri = URI.parse('file:///test/chatmode' + getPromptFileExtension(promptType)); + const result = new NewPromptsParser().parse(uri, code); + const validator = instaService.createInstance(PromptValidator); + return validator.validate(result, promptType); + } + suite('modes', () => { + + test('correct mode', async () => { + const content = [ + /* 01 */"---", + /* 02 */`description: "Agent mode test"`, + /* 03 */"model: MAE 4.1", + /* 04 */"tools: ['tool1', 'tool2']", + /* 05 */"---", + /* 06 */"This is a chat mode test.", + /* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).", + ].join('\n'); + const markers = validate(content, PromptsType.mode); + assert.deepStrictEqual(markers, []); + }); + + test('mode with errors (empty description, unknown tool & model)', async () => { + const content = [ + /* 01 */"---", + /* 02 */`description: ""`, // empty description -> error + /* 03 */"model: MAE 4.2", // unknown model -> warning + /* 04 */"tools: ['tool1', 'tool2', 'tool3']", // tool3 unknown -> warning + /* 05 */"---", + /* 06 */"Body", + ].join('\n'); + const markers = validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 3, 'Expected 3 validation issues'); + assert.deepStrictEqual( + markers.map(m => ({ severity: m.severity, message: m.message })), + [ + { severity: MarkerSeverity.Error, message: "The 'description' attribute should not be empty." }, + { severity: MarkerSeverity.Warning, message: "Unknown tool 'tool3'." }, + { severity: MarkerSeverity.Warning, message: "Unknown model 'MAE 4.2'." }, + ] + ); + }); + + test('tools must be array', async () => { + const content = [ + "---", + "description: \"Test\"", + "tools: 'tool1'", + "---", + ].join('\n'); + const markers = validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), ["The 'tools' attribute must be an array."]); + }); + + test('each tool must be string', async () => { + const content = [ + "---", + "description: \"Test\"", + "tools: ['tool1', 2]", + "---", + ].join('\n'); + const markers = validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].message, "Each tool name in the 'tools' attribute must be a string."); + }); + + test('unknown attribute in mode file', async () => { + const content = [ + "---", + "description: \"Test\"", + "applyTo: '*.ts'", // not allowed in mode file + "---", + ].join('\n'); + const markers = validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.ok(markers[0].message.startsWith("Attribute 'applyTo' is not supported in mode files.")); + }); + }); + + suite('instructions', () => { + + test('instructions valid', async () => { + const content = [ + "---", + "description: \"Instr\"", + "applyTo: *.ts,*.js", + "---", + ].join('\n'); + const markers = validate(content, PromptsType.instructions); + assert.deepEqual(markers, []); + }); + + test('instructions invalid applyTo type', async () => { + const content = [ + "---", + "description: \"Instr\"", + "applyTo: 5", + "---", + ].join('\n'); + const markers = validate(content, PromptsType.instructions); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].message, "The 'applyTo' attribute must be a string."); + }); + + test('instructions invalid applyTo glob & unknown attribute', async () => { + const content = [ + "---", + "description: \"Instr\"", + "applyTo: ''", // empty -> invalid glob + "model: mae-4", // model not allowed in instructions + "---", + ].join('\n'); + const markers = validate(content, PromptsType.instructions); + assert.strictEqual(markers.length, 2); + // Order: unknown attribute warnings first (attribute iteration) then applyTo validation + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.ok(markers[0].message.startsWith("Attribute 'model' is not supported in instructions files.")); + assert.strictEqual(markers[1].message, "The 'applyTo' attribute must be a valid glob pattern."); + }); + + test('invalid header structure (YAML array)', async () => { + const content = [ + "---", + "- item1", + "---", + "Body", + ].join('\n'); + const markers = validate(content, PromptsType.instructions); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].message, 'Invalid header, expecting pairs'); + }); + }); + + suite('prompts', () => { + + test('prompt valid with agent mode (default) and tools and a BYO model', async () => { + // mode omitted -> defaults to Agent; tools+model should validate; model MAE 4 is agent capable + const content = [ + '---', + 'description: "Prompt with tools"', + "model: MAE 4 (olama)", + "tools: ['tool1','tool2']", + '---', + 'Body' + ].join('\n'); + const markers = validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, []); + }); + + test('prompt model not suited for agent mode', async () => { + // MAE 3.5 Turbo lacks agentMode capability -> warning when used in agent (default) mode + const content = [ + '---', + 'description: "Prompt with unsuitable model"', + "model: MAE 3.5 Turbo", + '---', + 'Body' + ].join('\n'); + const markers = validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1, 'Expected one warning about unsuitable model'); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, "Model 'MAE 3.5 Turbo' is not suited for agent mode."); + }); + + test('prompt with custom mode BeastMode and tools', async () => { + // Explicit custom mode should be recognized; BeastMode kind comes from setup; ensure tools accepted + const content = [ + '---', + 'description: "Prompt custom mode"', + 'mode: BeastMode', + "tools: ['tool1']", + '---', + 'Body' + ].join('\n'); + const markers = validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, []); + }); + + test('prompt with mode Ask and tools warns', async () => { + const content = [ + '---', + 'description: "Prompt ask mode with tools"', + 'mode: Ask', + "tools: ['tool1','tool2']", + '---', + 'Body' + ].join('\n'); + const markers = validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1, 'Expected one warning about unknown mode'); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, "Unknown mode 'Ask'. Available modes: agent, ask, edit, BeastMode."); + }); + + test('prompt with mode edit', async () => { + const content = [ + '---', + 'description: "Prompt edit mode with tool"', + 'mode: edit', + "tools: ['tool1']", + '---', + 'Body' + ].join('\n'); + const markers = validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."); + }); + }); + +}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index ffb62a68d0d..69ea302d960 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -699,6 +699,7 @@ suite('PromptsService', () => { const result1 = await service.parse(rootFileUri, PromptsType.prompt, CancellationToken.None); assert.deepEqual(result1, { uri: rootFileUri, + header: undefined, metadata: { promptType: PromptsType.prompt, description: 'Root prompt description.', From 3c4c2e787361ed46bc503fcb362897dca72a58f8 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:29:01 -0700 Subject: [PATCH 0169/4355] Handle registering and deregsitering chat sessions view (#265394) * Handle registering and deregsitering chat sessions view * Missing change * Change to entitlement * Adding sentiment hidden check --- .../contrib/chat/browser/chatSessions.ts | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts index 68e1f4d4e82..3fc801d5189 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.ts @@ -61,7 +61,7 @@ import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPan import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { Extensions, IEditableData, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from '../../../common/views.js'; +import { Extensions, IEditableData, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainer, ViewContainerLocation } from '../../../common/views.js'; import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; @@ -268,12 +268,13 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi private isViewContainerRegistered = false; private localProvider: LocalChatSessionsProvider | undefined; private readonly sessionTracker: ChatSessionTracker; + private viewContainer: ViewContainer | undefined; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, ) { super(); @@ -294,6 +295,9 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi this.updateViewContainerRegistration(); } })); + this._register(this.chatEntitlementService.onDidChangeSentiment(e => { + this.updateViewContainerRegistration(); + })); } private setupEditorTracking(): void { @@ -304,13 +308,11 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi private updateViewContainerRegistration(): void { const location = this.configurationService.getValue(ChatConfiguration.AgentSessionsViewLocation); - - if (location === 'view' && !this.isViewContainerRegistered) { + const sentiment = this.chatEntitlementService.sentiment; + if (sentiment.disabled || sentiment.hidden || (location !== 'view' && this.isViewContainerRegistered)) { + this.deregisterViewContainer(); + } else if (location === 'view' && !this.isViewContainerRegistered) { this.registerViewContainer(); - } else if (location !== 'view' && this.isViewContainerRegistered) { - // Note: VS Code doesn't support unregistering view containers - // Once registered, they remain registered for the session - // but you could hide them or make them conditional through 'when' clauses } } @@ -319,12 +321,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi return; } - - if (this.chatEntitlementService.sentiment.hidden || this.chatEntitlementService.sentiment.disabled) { - return; // do not register container as AI features are hidden or disabled - } - - Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, title: nls.localize2('chat.sessions', "Chat Sessions"), @@ -333,6 +330,20 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Chat Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); + this.isViewContainerRegistered = true; + } + + private deregisterViewContainer(): void { + if (this.viewContainer) { + const allViews = Registry.as(Extensions.ViewsRegistry).getViews(this.viewContainer); + if (allViews.length > 0) { + Registry.as(Extensions.ViewsRegistry).deregisterViews(allViews, this.viewContainer); + } + + Registry.as(Extensions.ViewContainersRegistry).deregisterViewContainer(this.viewContainer); + this.viewContainer = undefined; + this.isViewContainerRegistered = false; + } } } From 25fa2d485d874dd5cecadf1b6b92df469ffaf83c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Sep 2025 10:46:48 -0700 Subject: [PATCH 0170/4355] testing: fix run test tool running the same test multiple times (#266063) Fixes https://github.com/microsoft/vscode/issues/263407 Fixes https://github.com/microsoft/vscode/issues/265990 --- .../contrib/testing/common/testService.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 35769fc7126..91a257bb99f 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -19,7 +19,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { MutableObservableValue } from './observableValue.js'; import { TestExclusions } from './testExclusions.js'; -import { TestId } from './testId.js'; +import { TestId, TestIdPathParts } from './testId.js'; import { ITestResult } from './testResult.js'; import { AbstractIncrementalTestCollection, ICallProfileRunHandler, IncrementalTestCollectionItem, InternalTestItem, IStartControllerTests, IStartControllerTestsResult, ITestItemContext, ResolvedTestRunRequest, TestControllerCapability, TestItemExpandState, TestMessageFollowupRequest, TestMessageFollowupResponse, TestRunProfileBitset, TestsDiff } from './testTypes.js'; @@ -175,7 +175,19 @@ export const waitForTestToBeIdle = (testService: ITestService, test: Incremental export const testsInFile = async function* (testService: ITestService, ident: IUriIdentityService, uri: URI, waitForIdle = true, descendInFile = true): AsyncIterable { const queue = new LinkedList>(); - const existing = [...testService.collection.getNodeByUrl(uri)]; + const existing = [...testService.collection.getNodeByUrl(uri)].sort((a, b) => a.item.extId.length - b.item.extId.length); + + // getNodeByUrl will return all known tests in the URI, but this can include + // children of tests even when `descendInFile` is false. Remove those cases. + for (let i = 0; i < existing.length - 1; i++) { + const prefix = existing[i].item.extId + TestIdPathParts.Delimiter; + for (let k = i + 1; k < existing.length; k++) { + if (existing[k].item.extId.startsWith(prefix)) { + existing.splice(k--, 1); + } + } + } + queue.push(existing.length ? existing.map(e => e.item.extId) : testService.collection.rootIds); let n = 0; From 83839266e95ebd76dfa21d92b70e1f8e4f802df3 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:48:25 -0700 Subject: [PATCH 0171/4355] Adding chat input type to chat model (#265923) --- .../contrib/chat/browser/chatEditorInput.ts | 7 +++- .../contrib/chat/common/chatModel.ts | 24 ++++++++---- .../contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 39 +++++-------------- .../chat/test/common/chatModel.test.ts | 8 ++-- 5 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 4e7ea4b684b..608588219be 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -172,13 +172,16 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } override async resolve(): Promise { + const searchParams = new URLSearchParams(this.resource.query); + const chatSessionType = searchParams.get('chatSessionType'); + const inputType = chatSessionType ?? this.resource.authority; if (this.resource.scheme === Schemas.vscodeChatSession) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (typeof this.sessionId === 'string') { this.model = await this.chatService.getOrRestoreSession(this.sessionId) - ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); + ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, inputType); } else if (!this.options.target) { - this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None); + this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, inputType); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index cf07583a8fc..22e4c08044e 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1125,6 +1125,7 @@ export interface IExportableChatData { responderUsername: string; requesterAvatarIconUri: UriComponents | undefined; responderAvatarIconUri: ThemeIcon | UriComponents | undefined; // Keeping Uri name for backcompat + inputType?: string; } /* @@ -1149,11 +1150,12 @@ export interface ISerializableChatData2 extends ISerializableChatData1 { export interface ISerializableChatData3 extends Omit { version: 3; customTitle: string | undefined; + inputType?: string; } /** - * Chat data that has been parsed and normalized to the current format. - */ +* Chat data that has been parsed and normalized to the current format. +*/ export type ISerializableChatData = ISerializableChatData3; /** @@ -1409,10 +1411,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._customTitle || ChatModel.getDefaultTitle(this._requests); } - get initialLocation() { - return this._initialLocation; - } - private _editingSession: ObservablePromise | undefined; get editingSessionObs(): ObservablePromise | undefined { return this._editingSession; @@ -1422,9 +1420,19 @@ export class ChatModel extends Disposable implements IChatModel { return this._editingSession?.promiseResult.get()?.data; } + private readonly _inputType: string | undefined; + get inputType(): string | undefined { + return this._inputType; + } + + private readonly _initialLocation: ChatAgentLocation; + get initialLocation(): ChatAgentLocation { + return this._initialLocation; + } + constructor( private readonly initialData: ISerializableChatData | IExportableChatData | undefined, - private readonly _initialLocation: ChatAgentLocation, + initialModelProps: { initialLocation: ChatAgentLocation; inputType?: string }, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @@ -1446,6 +1454,8 @@ export class ChatModel extends Disposable implements IChatModel { this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; + this._inputType = initialData?.inputType ?? initialModelProps.inputType; + this._initialLocation = initialData?.initialLocation ?? initialModelProps.initialLocation; const lastResponse = observableFromEvent(this, this.onDidChange, () => this._requests.at(-1)?.response); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 83623894a1b..6ae944e2aed 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -675,7 +675,7 @@ export interface IChatService { isEnabled(location: ChatAgentLocation): boolean; hasSessions(): boolean; - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean): ChatModel; + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, inputType?: string): ChatModel; getSession(sessionId: string): IChatModel | undefined; getOrRestoreSession(sessionId: string): Promise; getPersistedSessionTitle(sessionId: string): string | undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index b74899a7271..d58c8067e52 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -167,7 +167,7 @@ export class ChatService extends Disposable implements IChatService { private saveState(): void { const liveChats = Array.from(this._sessionModels.values()) .filter(session => - this.shouldSaveToHistory(session.sessionId) && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline)); + !session.inputType && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline)); this._chatSessionStore.storeSessions(liveChats); } @@ -293,7 +293,7 @@ export class ChatService extends Disposable implements IChatService { */ async getHistory(): Promise { const liveSessionItems = Array.from(this._sessionModels.values()) - .filter(session => !session.isImported) + .filter(session => !session.isImported && !session.inputType) .map(session => { const title = session.title || localize('newChat', "New Chat"); return { @@ -322,13 +322,13 @@ export class ChatService extends Disposable implements IChatService { await this._chatSessionStore.clearAllSessions(); } - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true): ChatModel { + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true, inputType?: string): ChatModel { this.trace('startSession'); - return this._startSession(undefined, location, isGlobalEditingSession, token); + return this._startSession(undefined, location, isGlobalEditingSession, token, inputType); } - private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken): ChatModel { - const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, location); + private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, inputType?: string): ChatModel { + const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, inputType }); if (location === ChatAgentLocation.Chat) { model.startEditingSession(isGlobalEditingSession); } @@ -463,7 +463,7 @@ export class ChatService extends Disposable implements IChatService { const chatSessionType = parsed.chatSessionType; const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, CancellationToken.None); - const model = this._startSession(undefined, location, true, CancellationToken.None); + const model = this._startSession(undefined, location, true, CancellationToken.None, chatSessionType); if (!this._contentProviderSessionModels.has(chatSessionType)) { this._contentProviderSessionModels.set(chatSessionType, new Map()); } @@ -1069,14 +1069,13 @@ export class ChatService extends Disposable implements IChatService { } async clearSession(sessionId: string): Promise { - const shouldSaveToHistory = this.shouldSaveToHistory(sessionId); - this.trace('clearSession', `sessionId: ${sessionId}, save to history: ${shouldSaveToHistory}`); + this.trace('clearSession', `sessionId: ${sessionId}`); const model = this._sessionModels.get(sessionId); if (!model) { throw new Error(`Unknown session: ${sessionId}`); } - - if (shouldSaveToHistory && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { + this.trace(`Model input type: ${model.inputType}`); + if (!model.inputType && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { // Always preserve sessions that have custom titles, even if empty if (model.getRequests().length === 0 && !model.customTitle) { await this._chatSessionStore.deleteSession(sessionId); @@ -1124,22 +1123,4 @@ export class ChatService extends Disposable implements IChatService { logChatIndex(): void { this._chatSessionStore.logIndex(); } - - private shouldSaveToHistory(sessionId: string): boolean { - // We shouldn't save contributed sessions from content providers - for (const [_, sessions] of this._contentProviderSessionModels) { - let session: { readonly model: IChatModel; readonly disposables: DisposableStore } | undefined; - for (const entry of sessions.values()) { - if (entry.model.sessionId === sessionId) { - session = entry; - break; - } - } - if (session) { - return false; - } - } - - return true; - } } diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 2fffcc57371..0c8ff8ebde0 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -40,7 +40,7 @@ suite('ChatModel', () => { }); test('removeRequest', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); const text = 'hello'; model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -52,8 +52,8 @@ suite('ChatModel', () => { }); test('adoptRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.EditorInline)); - const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.EditorInline })); + const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -76,7 +76,7 @@ suite('ChatModel', () => { }); test('addCompleteRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Chat)); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0, undefined, undefined, undefined, undefined, undefined, undefined, true); From 0a19961637054f17f4907bc3db706ba1849963a0 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Sep 2025 10:49:00 -0700 Subject: [PATCH 0172/4355] tools: display progress bar for tools that report numeric progress (#266064) Closes https://github.com/microsoft/vscode/issues/264093 --- src/vs/base/browser/ui/progressbar/progressbar.ts | 2 +- src/vs/workbench/api/common/extHostLanguageModelTools.ts | 8 ++++++-- .../chatContentParts/media/chatConfirmationWidget.css | 1 + .../chatInputOutputMarkdownProgressPart.ts | 6 ++++++ .../chat/common/chatProgressTypes/chatToolInvocation.ts | 2 +- src/vs/workbench/contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/languageModelToolsService.ts | 4 ++-- src/vs/workbench/contrib/mcp/common/mcpServer.ts | 5 +---- .../contrib/testing/common/testingChatAgentTool.ts | 4 +--- 9 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index 9b5b2c7c2ba..3b0a5e2f5e0 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -177,7 +177,7 @@ export class ProgressBar extends Disposable { } /** - * Tells the progress bar the total amount of work that has been completed. + * Tells the progress bar the total amount of work (0 to 100) that has been completed. */ setWorked(value: number): ProgressBar { value = Math.max(1, Number(value)); diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 9f2dc36a420..92151241e98 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -200,12 +200,16 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape let progress: vscode.Progress<{ message?: string | vscode.MarkdownString; increment?: number }> | undefined; if (isProposedApiEnabled(item.extension, 'toolProgress')) { + let lastProgress: number | undefined; progress = { report: value => { + if (value.increment !== undefined) { + lastProgress = (lastProgress ?? 0) + value.increment; + } + this._proxy.$acceptToolProgress(dto.callId, { message: typeConvert.MarkdownString.fromStrict(value.message), - increment: value.increment, - total: 100, + progress: lastProgress === undefined ? undefined : lastProgress / 100, }); } }; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index dfd646ba74a..78fe39e64db 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -10,6 +10,7 @@ display: flex; flex-wrap: wrap; align-items: center; + position: relative; } .chat-confirmation-widget .monaco-text-button { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index 25ff72c13c3..ac54788f464 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ProgressBar } from '../../../../../../base/browser/ui/progressbar/progressbar.js'; import { decodeBase64 } from '../../../../../../base/common/buffer.js'; import { IMarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { Lazy } from '../../../../../../base/common/lazy.js'; import { toDisposable } from '../../../../../../base/common/lifecycle.js'; import { getExtensionForMimeType } from '../../../../../../base/common/mime.js'; import { autorun } from '../../../../../../base/common/observable.js'; @@ -137,12 +139,16 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS this._register(toDisposable(() => ChatInputOutputMarkdownProgressPart._expandedByDefault.set(toolInvocation, collapsibleListPart.expanded))); const progressObservable = toolInvocation.kind === 'toolInvocation' ? toolInvocation.progress : undefined; + const progressBar = new Lazy(() => this._register(new ProgressBar(collapsibleListPart.domNode))); if (progressObservable) { this._register(autorun(reader => { const progress = progressObservable?.read(reader); if (progress.message) { collapsibleListPart.title = progress.message; } + if (progress.progress && !toolInvocation.isComplete) { + progressBar.value.setWorked(progress.progress * 100); + } })); } diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 0a04cdd65af..7e0908d6338 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -92,7 +92,7 @@ export class ChatToolInvocation implements IChatToolInvocation { public acceptProgress(step: IToolProgressStep) { const prev = this.progress.get(); this.progress.set({ - progress: step.increment ? (prev.progress + step.increment) : prev.progress, + progress: step.progress || prev.progress || 0, message: step.message, }, undefined); } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 6ae944e2aed..7cf1b465e55 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -345,7 +345,7 @@ export interface IChatToolInvocation { pastTenseMessage: string | IMarkdownString | undefined; resultDetails: IToolResult['toolResultDetails']; source: ToolDataSource; - progress: IObservable<{ message?: string | IMarkdownString; progress: number }>; + progress: IObservable<{ message?: string | IMarkdownString; progress: number | undefined }>; readonly toolId: string; readonly toolCallId: string; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index c21b7c7beec..62b050a7479 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -49,8 +49,8 @@ export interface IToolData { export interface IToolProgressStep { readonly message: string | IMarkdownString | undefined; - readonly increment?: number; - readonly total?: number; + /** 0-1 progress of the tool call */ + readonly progress?: number; } export type ToolProgress = IProgress; diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 23916b8e40a..1dbfdcdfffc 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -883,15 +883,12 @@ export class McpTool implements IMcpTool { const progressToken = generateUuid(); return McpServer.callOn(this._server, h => { - let lastProgressN = 0; const listener = h.onDidReceiveProgressNotification((e) => { if (e.params.progressToken === progressToken) { progress.report({ message: e.params.message, - increment: e.params.progress - lastProgressN, - total: e.params.total, + progress: e.params.total !== undefined && e.params.progress !== undefined ? e.params.progress / e.params.total : undefined, }); - lastProgressN = e.params.progress; } }); diff --git a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts index 50badae2332..5bb96010a53 100644 --- a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts +++ b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts @@ -206,11 +206,9 @@ class RunTestTool implements IToolImpl { const update = () => { const counts = collectTestStateCounts(!result.completedAt, [result]); const text = getTestProgressText(counts); - progress.report({ message: text, increment: counts.runSoFar - lastSoFar, total: counts.totalWillBeRun }); - lastSoFar = counts.runSoFar; + progress.report({ message: text, progress: counts.runSoFar / counts.totalWillBeRun }); }; - let lastSoFar = 0; const throttler = store.add(new RunOnceScheduler(update, 500)); return new Promise(resolve => { From 7c1912cde1b6f9519197f9238353db1d7074055b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 10 Sep 2025 20:29:52 +0200 Subject: [PATCH 0173/4355] update --- .../promptSyntax/service/promptValidator.ts | 154 +++++++++++------- .../service/promptValidator.test.ts | 98 ++++++++--- 2 files changed, 175 insertions(+), 77 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index 288c26c6c53..fe9cfa3f8c7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -22,6 +22,7 @@ import { PromptsConfig } from '../config/config.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; @@ -30,18 +31,55 @@ export class PromptValidator { @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, @IChatModeService private readonly chatModeService: IChatModeService, + @IFileService private readonly fileService: IFileService, ) { } - public validate(promptAST: ParsedPromptFile, promptType: PromptsType): IMarkerData[] { - const markers: IMarkerData[] = []; - promptAST.header?.errors.forEach(error => { - markers.push(toMarker(error.message, error.range, MarkerSeverity.Error)); - }); - this.validateHeader(promptAST, promptType, markers); - return markers; + public async validate(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { + promptAST.header?.errors.forEach(error => report(toMarker(error.message, error.range, MarkerSeverity.Error))); + this.validateHeader(promptAST, promptType, report); + await this.validateBody(promptAST, report); } - private validateHeader(promptAST: ParsedPromptFile, promptType: PromptsType, result: IMarkerData[]): void { + private async validateBody(promptAST: ParsedPromptFile, report: (markers: IMarkerData) => void): Promise { + const body = promptAST.body; + if (!body) { + return; + } + + // Validate file references + const fileReferenceChecks: Promise[] = []; + for (const ref of body.fileReferences) { + const resolved = body.resolveFilePath(ref.content); + if (!resolved) { + report(toMarker(localize('promptValidator.invalidFileReference', "Invalid file reference '{0}'.", ref.content), ref.range, MarkerSeverity.Warning)); + continue; + } + fileReferenceChecks.push((async () => { + try { + const exists = await this.fileService.exists(resolved); + if (!exists) { + report(toMarker(localize('promptValidator.fileNotFound', "File '{0}' not found.", ref.content), ref.range, MarkerSeverity.Warning)); + } + } catch { + report(toMarker(localize('promptValidator.fileNotFound', "File '{0}' not found.", ref.content), ref.range, MarkerSeverity.Warning)); + } + })()); + } + + // Validate variable references (tool or toolset names) + if (body.variableReferences.length) { + const available = this.getAvailableToolAndToolSetNames(); + for (const variable of body.variableReferences) { + if (!available.has(variable.content)) { + report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.content), variable.range, MarkerSeverity.Warning)); + } + } + } + + await Promise.all(fileReferenceChecks); + } + + private validateHeader(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): void { const header = promptAST.header; if (!header) { return; @@ -52,65 +90,65 @@ export class PromptValidator { if (!validAttributeNames.includes(attribute.key)) { switch (promptType) { case PromptsType.prompt: - result.push(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); break; case PromptsType.mode: - result.push(toMarker(localize('promptValidator.unknownAttribute.mode', "Attribute '{0}' is not supported in mode files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.unknownAttribute.mode', "Attribute '{0}' is not supported in mode files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); break; case PromptsType.instructions: - result.push(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}", attribute.key, validAttributeNames.join(', ')), attribute.range, MarkerSeverity.Warning)); break; } } } - this.validateDescription(attributes, result); + this.validateDescription(attributes, report); switch (promptType) { case PromptsType.prompt: { - const mode = this.validateMode(attributes, result); - this.validateTools(attributes, mode?.kind ?? ChatModeKind.Agent, result); - this.validateModel(attributes, mode?.kind ?? ChatModeKind.Agent, result); + const mode = this.validateMode(attributes, report); + this.validateTools(attributes, mode?.kind ?? ChatModeKind.Agent, report); + this.validateModel(attributes, mode?.kind ?? ChatModeKind.Agent, report); break; } case PromptsType.instructions: - this.validateApplyTo(attributes, result); + this.validateApplyTo(attributes, report); break; case PromptsType.mode: - this.validateTools(attributes, ChatModeKind.Agent, result); - this.validateModel(attributes, ChatModeKind.Agent, result); + this.validateTools(attributes, ChatModeKind.Agent, report); + this.validateModel(attributes, ChatModeKind.Agent, report); break; } } - private validateDescription(attributes: IHeaderAttribute[], markers: IMarkerData[]): void { + private validateDescription(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { const descriptionAttribute = attributes.find(attr => attr.key === 'description'); if (!descriptionAttribute) { return; } if (descriptionAttribute.value.type !== 'string') { - markers.push(toMarker(localize('promptValidator.descriptionMustBeString', "The 'description' attribute must be a string."), descriptionAttribute.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.descriptionMustBeString', "The 'description' attribute must be a string."), descriptionAttribute.range, MarkerSeverity.Error)); return; } if (descriptionAttribute.value.value.trim().length === 0) { - markers.push(toMarker(localize('promptValidator.descriptionShouldNotBeEmpty', "The 'description' attribute should not be empty."), descriptionAttribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.descriptionShouldNotBeEmpty', "The 'description' attribute should not be empty."), descriptionAttribute.value.range, MarkerSeverity.Error)); return; } } - private validateModel(attributes: IHeaderAttribute[], modeKind: ChatModeKind, markers: IMarkerData[]): void { + private validateModel(attributes: IHeaderAttribute[], modeKind: ChatModeKind, report: (markers: IMarkerData) => void): void { const attribute = attributes.find(attr => attr.key === 'model'); if (!attribute) { return; } if (attribute.value.type !== 'string') { - markers.push(toMarker(localize('promptValidator.modelMustBeString', "The 'model' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.modelMustBeString', "The 'model' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); return; } const modelName = attribute.value.value.trim(); if (modelName.length === 0) { - markers.push(toMarker(localize('promptValidator.modelMustBeNonEmpty', "The 'model' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.modelMustBeNonEmpty', "The 'model' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); return; } @@ -121,10 +159,10 @@ export class PromptValidator { } const modelMetadata = this.findModelByName(languageModes, modelName); if (!modelMetadata) { - markers.push(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'.", modelName), attribute.value.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'.", modelName), attribute.value.range, MarkerSeverity.Warning)); } else if (modeKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { - markers.push(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode.", modelName), attribute.value.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode.", modelName), attribute.value.range, MarkerSeverity.Warning)); } } @@ -138,18 +176,18 @@ export class PromptValidator { return undefined; } - private validateMode(attributes: IHeaderAttribute[], markers: IMarkerData[]): IChatMode | undefined { + private validateMode(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): IChatMode | undefined { const attribute = attributes.find(attr => attr.key === 'mode'); if (!attribute) { return undefined; // default mode for prompts is Agent } if (attribute.value.type !== 'string') { - markers.push(toMarker(localize('promptValidator.modeMustBeString', "The 'mode' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.modeMustBeString', "The 'mode' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); return undefined; } const modeValue = attribute.value.value; if (modeValue.trim().length === 0) { - markers.push(toMarker(localize('promptValidator.modeMustBeNonEmpty', "The 'mode' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.modeMustBeNonEmpty', "The 'mode' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); return undefined; } @@ -165,72 +203,77 @@ export class PromptValidator { } const errorMessage = localize('promptValidator.modeNotFound', "Unknown mode '{0}'. Available modes: {1}.", modeValue, availableModes.join(', ')); - markers.push(toMarker(errorMessage, attribute.value.range, MarkerSeverity.Warning)); + report(toMarker(errorMessage, attribute.value.range, MarkerSeverity.Warning)); return undefined; } - private validateTools(attributes: IHeaderAttribute[], modeKind: ChatModeKind, markers: IMarkerData[]): undefined { + private validateTools(attributes: IHeaderAttribute[], modeKind: ChatModeKind, report: (markers: IMarkerData) => void): undefined { const attribute = attributes.find(attr => attr.key === 'tools'); if (!attribute) { return; } if (modeKind !== ChatModeKind.Agent) { - markers.push(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); } if (attribute.value.type !== 'array') { - markers.push(toMarker(localize('promptValidator.toolsMustBeArray', "The 'tools' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.toolsMustBeArray', "The 'tools' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); return; } - const toolNames = new Map(); - for (const item of attribute.value.items) { - if (item.type !== 'string') { - markers.push(toMarker(localize('promptValidator.eachToolMustBeString', "Each tool name in the 'tools' attribute must be a string."), item.range, MarkerSeverity.Error)); - } else { - toolNames.set(item.value, item.range); + if (attribute.value.items.length > 0) { + const available = this.getAvailableToolAndToolSetNames(); + for (const item of attribute.value.items) { + if (item.type !== 'string') { + report(toMarker(localize('promptValidator.eachToolMustBeString', "Each tool name in the 'tools' attribute must be a string."), item.range, MarkerSeverity.Error)); + } else if (!available.has(item.value)) { + report(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", item.value), item.range, MarkerSeverity.Warning)); + } } } - if (toolNames.size === 0) { - return; - } + } + + private getAvailableToolAndToolSetNames(): Set { + const available = new Set(); for (const tool of this.languageModelToolsService.getTools()) { - toolNames.delete(tool.toolReferenceName ?? tool.displayName); + if (tool.canBeReferencedInPrompt) { + available.add(tool.toolReferenceName ?? tool.displayName); + } } for (const toolSet of this.languageModelToolsService.toolSets.get()) { - toolNames.delete(toolSet.referenceName); - } - - for (const [toolName, range] of toolNames) { - markers.push(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", toolName), range, MarkerSeverity.Warning)); + available.add(toolSet.referenceName); + for (const tool of toolSet.getTools()) { + available.add(tool.toolReferenceName ?? tool.displayName); + } } + return available; } - private validateApplyTo(attributes: IHeaderAttribute[], markers: IMarkerData[]): undefined { + private validateApplyTo(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { const attribute = attributes.find(attr => attr.key === 'applyTo'); if (!attribute) { return; } if (attribute.value.type !== 'string') { - markers.push(toMarker(localize('promptValidator.applyToMustBeString', "The 'applyTo' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.applyToMustBeString', "The 'applyTo' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); return; } const pattern = attribute.value.value; try { const patterns = splitGlobAware(pattern, ','); if (patterns.length === 0) { - markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); return; } for (const pattern of patterns) { const globPattern = parse(pattern); if (isEmptyPattern(globPattern)) { - markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); return; } } } catch (_error) { - markers.push(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.applyToMustBeValidGlob', "The 'applyTo' attribute must be a valid glob pattern."), attribute.value.range, MarkerSeverity.Error)); } } } @@ -338,9 +381,10 @@ class ModelTracker extends Disposable { } private validate(): void { - this.delayer.trigger(() => { + this.delayer.trigger(async () => { + const markers: IMarkerData[] = []; const ast = this.promptParser.parse(this.textModel.uri, this.textModel.getValue()); - const markers = this.validator.validate(ast, this.promptType); + await this.validator.validate(ast, this.promptType, m => markers.push(m)); this.markerService.changeOne(MARKERS_OWNER_ID, this.textModel.uri, markers); }); } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts index 58231ccc889..397ec504af4 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptValidator.test.ts @@ -22,6 +22,8 @@ import { MockChatModeService } from '../../mockChatModeService.js'; import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; import { IMarkerData, MarkerSeverity } from '../../../../../../../platform/markers/common/markers.js'; import { getPromptFileExtension } from '../../../../common/promptSyntax/config/promptFileLocations.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { ResourceSet } from '../../../../../../../base/common/map.js'; suite('PromptValidator', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -57,15 +59,25 @@ suite('PromptValidator', () => { } }); - const customChatMode = new CustomChatMode({ uri: URI.file('/test/chatmode.md'), name: 'BeastMode', body: '', variableReferences: [] }); + const customChatMode = new CustomChatMode({ uri: URI.parse('myFs://test/test/chatmode.md'), name: 'BeastMode', body: '', variableReferences: [] }); instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); + + + const existingFiles = new ResourceSet([URI.parse('myFs://test/reference1.md'), URI.parse('myFs://test/reference2.md')]); + instaService.stub(IFileService, { + exists(uri: URI) { + return Promise.resolve(existingFiles.has(uri)); + } + }); }); - function validate(code: string, promptType: PromptsType): IMarkerData[] { - const uri = URI.parse('file:///test/chatmode' + getPromptFileExtension(promptType)); + async function validate(code: string, promptType: PromptsType): Promise { + const uri = URI.parse('myFs://test/testFile' + getPromptFileExtension(promptType)); const result = new NewPromptsParser().parse(uri, code); const validator = instaService.createInstance(PromptValidator); - return validator.validate(result, promptType); + const markers: IMarkerData[] = []; + await validator.validate(result, promptType, m => markers.push(m)); + return markers; } suite('modes', () => { @@ -79,7 +91,7 @@ suite('PromptValidator', () => { /* 06 */"This is a chat mode test.", /* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).", ].join('\n'); - const markers = validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.mode); assert.deepStrictEqual(markers, []); }); @@ -92,7 +104,7 @@ suite('PromptValidator', () => { /* 05 */"---", /* 06 */"Body", ].join('\n'); - const markers = validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.mode); assert.strictEqual(markers.length, 3, 'Expected 3 validation issues'); assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), @@ -111,7 +123,7 @@ suite('PromptValidator', () => { "tools: 'tool1'", "---", ].join('\n'); - const markers = validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.mode); assert.strictEqual(markers.length, 1); assert.deepStrictEqual(markers.map(m => m.message), ["The 'tools' attribute must be an array."]); }); @@ -123,7 +135,7 @@ suite('PromptValidator', () => { "tools: ['tool1', 2]", "---", ].join('\n'); - const markers = validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.mode); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].message, "Each tool name in the 'tools' attribute must be a string."); }); @@ -135,7 +147,7 @@ suite('PromptValidator', () => { "applyTo: '*.ts'", // not allowed in mode file "---", ].join('\n'); - const markers = validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.mode); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.ok(markers[0].message.startsWith("Attribute 'applyTo' is not supported in mode files.")); @@ -151,7 +163,7 @@ suite('PromptValidator', () => { "applyTo: *.ts,*.js", "---", ].join('\n'); - const markers = validate(content, PromptsType.instructions); + const markers = await validate(content, PromptsType.instructions); assert.deepEqual(markers, []); }); @@ -162,7 +174,7 @@ suite('PromptValidator', () => { "applyTo: 5", "---", ].join('\n'); - const markers = validate(content, PromptsType.instructions); + const markers = await validate(content, PromptsType.instructions); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].message, "The 'applyTo' attribute must be a string."); }); @@ -175,7 +187,7 @@ suite('PromptValidator', () => { "model: mae-4", // model not allowed in instructions "---", ].join('\n'); - const markers = validate(content, PromptsType.instructions); + const markers = await validate(content, PromptsType.instructions); assert.strictEqual(markers.length, 2); // Order: unknown attribute warnings first (attribute iteration) then applyTo validation assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); @@ -190,7 +202,7 @@ suite('PromptValidator', () => { "---", "Body", ].join('\n'); - const markers = validate(content, PromptsType.instructions); + const markers = await validate(content, PromptsType.instructions); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].message, 'Invalid header, expecting pairs'); }); @@ -203,12 +215,12 @@ suite('PromptValidator', () => { const content = [ '---', 'description: "Prompt with tools"', - "model: MAE 4 (olama)", + "model: MAE 4.1", "tools: ['tool1','tool2']", '---', 'Body' ].join('\n'); - const markers = validate(content, PromptsType.prompt); + const markers = await validate(content, PromptsType.prompt); assert.deepStrictEqual(markers, []); }); @@ -221,7 +233,7 @@ suite('PromptValidator', () => { '---', 'Body' ].join('\n'); - const markers = validate(content, PromptsType.prompt); + const markers = await validate(content, PromptsType.prompt); assert.strictEqual(markers.length, 1, 'Expected one warning about unsuitable model'); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.strictEqual(markers[0].message, "Model 'MAE 3.5 Turbo' is not suited for agent mode."); @@ -237,21 +249,21 @@ suite('PromptValidator', () => { '---', 'Body' ].join('\n'); - const markers = validate(content, PromptsType.prompt); + const markers = await validate(content, PromptsType.prompt); assert.deepStrictEqual(markers, []); }); - test('prompt with mode Ask and tools warns', async () => { + test('prompt with unknown mode Ask', async () => { const content = [ '---', - 'description: "Prompt ask mode with tools"', + 'description: "Prompt unknown mode Ask"', 'mode: Ask', "tools: ['tool1','tool2']", '---', 'Body' ].join('\n'); - const markers = validate(content, PromptsType.prompt); - assert.strictEqual(markers.length, 1, 'Expected one warning about unknown mode'); + const markers = await validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1, 'Expected one warning about tools in non-agent mode'); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.strictEqual(markers[0].message, "Unknown mode 'Ask'. Available modes: agent, ask, edit, BeastMode."); }); @@ -265,11 +277,53 @@ suite('PromptValidator', () => { '---', 'Body' ].join('\n'); - const markers = validate(content, PromptsType.prompt); + const markers = await validate(content, PromptsType.prompt); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.strictEqual(markers[0].message, "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."); }); }); + suite('body', () => { + test('body with existing file references and known tools has no markers', async () => { + const content = [ + '---', + 'description: "Refs"', + '---', + 'Here is a #file:./reference1.md and a markdown [reference](./reference2.md) plus variables #tool1 and #tool2' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, [], 'Expected no validation issues'); + }); + + test('body with missing file references reports warnings', async () => { + const content = [ + '---', + 'description: "Missing Refs"', + '---', + 'Here is a #file:./missing1.md and a markdown [missing link](./missing2.md).' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + const messages = markers.map(m => m.message).sort(); + assert.deepStrictEqual(messages, [ + "File './missing1.md' not found.", + "File './missing2.md' not found." + ]); + }); + + test('body with unknown tool variable reference warns', async () => { + const content = [ + '---', + 'description: "Unknown tool var"', + '---', + 'This line references known #tool1 and unknown #toolX' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1, 'Expected one warning for unknown tool variable'); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, "Unknown tool or toolset 'toolX'."); + }); + + }); + }); From 6b58a0a2a52b11f69b0340254f6dd4b86d291876 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Sep 2025 11:41:30 -0700 Subject: [PATCH 0174/4355] mcp: surface tool resources without having to expand the dropdown (#266072) Closes https://github.com/microsoft/vscode/issues/265395 --- .../chatToolInputOutputContentPart.ts | 18 ++++++++++- .../media/chatConfirmationWidget.css | 7 +++++ .../chatInputOutputMarkdownProgressPart.ts | 2 +- .../chat/common/languageModelToolsService.ts | 2 ++ .../mcpLanguageModelToolContribution.ts | 31 ++++++++++++++----- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index d8e661cf798..0525980d6f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -33,6 +33,7 @@ import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../../files/browser/fileConsta import { getAttachableImageExtension } from '../../common/chatModel.js'; import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; +import { LanguageModelPartAudience } from '../../common/languageModels.js'; import { ChatTreeItem, IChatCodeBlockInfo } from '../chat.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions } from '../codeBlockPart.js'; import { ChatAttachmentsContentPart } from './chatAttachmentsContentPart.js'; @@ -52,6 +53,7 @@ export interface IChatCollapsibleIOCodePart { export interface IChatCollapsibleIODataPart { kind: 'data'; value?: Uint8Array; + audience?: LanguageModelPartAudience[]; mimeType: string | undefined; uri: URI; } @@ -105,10 +107,12 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { super(); this._currentWidth = width; + const container = dom.h('.chat-confirmation-widget-container'); const titleEl = dom.h('.chat-confirmation-widget-title-inner'); const iconEl = dom.h('.chat-confirmation-widget-title-icon'); const elements = dom.h('.chat-confirmation-widget'); - this.domNode = elements.root; + this.domNode = container.root; + container.root.appendChild(elements.root); const titlePart = this._titlePart = this._register(_instantiationService.createInstance( ChatQueryTitlePart, @@ -155,6 +159,17 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { const message = dom.h('.chat-confirmation-widget-message'); message.root.appendChild(this.createMessageContents()); elements.root.appendChild(message.root); + + const topLevelResources = this.output?.parts + .filter(p => p.kind === 'data') + .filter(p => !p.audience || p.audience.includes(LanguageModelPartAudience.User)); + if (topLevelResources?.length) { + const group = this.addResourceGroup(topLevelResources, container.root); + group.classList.add('chat-collapsible-top-level-resource-group'); + this._register(autorun(r => { + group.style.display = expanded.read(r) ? 'none' : ''; + })); + } } private createMessageContents() { @@ -246,6 +261,7 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { toolbar.context = { parts } satisfies IChatToolOutputResourceToolbarContext; container.appendChild(el.root); + return el.root; } private addCodeBlock(part: IChatCollapsibleIOCodePart, container: HTMLElement) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index 78fe39e64db..e0f4a3cc455 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -42,6 +42,13 @@ } } +.chat-confirmation-widget-container { + .chat-collapsible-top-level-resource-group { + margin-top: -12px; + margin-bottom: 12px; + } +} + .chat-confirmation-widget .chat-confirmation-widget-title.expandable { cursor: pointer; margin-left: 0; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index ac54788f464..451140d4bd2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -126,7 +126,7 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS // Fall back to text if it's not valid base64 const permalinkUri = ChatResponseResource.createUri(context.element.sessionId, requestId, toolInvocation.toolCallId, i, permalinkBasename); - return { kind: 'data', value: decoded || new TextEncoder().encode(o.value), mimeType: o.mimeType, uri: permalinkUri }; + return { kind: 'data', value: decoded || new TextEncoder().encode(o.value), mimeType: o.mimeType, uri: permalinkUri, audience: o.audience }; } }), }, diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 62b050a7479..17f8b3016fd 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -150,6 +150,8 @@ export type ToolInputOutputBase = { uri?: URI; /** If true, this part came in as a resource reference rather than direct data. */ asResource?: boolean; + /** Audience of the data part */ + audience?: LanguageModelPartAudience[]; }; export type ToolInputOutputEmbedded = ToolInputOutputBase & { diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index d65a14dcd68..1dc4df5c578 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -12,6 +12,7 @@ import { Disposable, DisposableMap, DisposableStore, toDisposable } from '../../ import { equals } from '../../../../base/common/objects.js'; import { autorun, autorunSelfDisposable } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; +import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; @@ -22,6 +23,7 @@ import { IProductService } from '../../../../platform/product/common/productServ import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ChatResponseResource, getAttachableImageExtension } from '../../chat/common/chatModel.js'; +import { LanguageModelPartAudience } from '../../chat/common/languageModels.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultInputOutputDetails, ToolDataSource, ToolProgress, ToolSet } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { IMcpServer, IMcpService, IMcpTool, IMcpToolResourceLinkContents, LazyCollectionState, McpResourceURI, McpServerCacheState, McpToolResourceLinkMimeType } from './mcpTypes.js'; @@ -235,8 +237,18 @@ class McpToolImplementation implements IToolImpl { }; for (const item of callResult.content) { - const audience = item.annotations?.audience || ['assistant']; - if (audience.includes('user')) { + const audience = item.annotations?.audience?.map(a => { + if (a === 'assistant') { + return LanguageModelPartAudience.Assistant; + } else if (a === 'user') { + return LanguageModelPartAudience.User; + } else { + return undefined; + } + }).filter(isDefined); + + // Explicit user parts get pushed to progress to show in the status UI + if (audience?.includes(LanguageModelPartAudience.User)) { if (item.type === 'text') { progress.report({ message: item.text }); } @@ -244,7 +256,7 @@ class McpToolImplementation implements IToolImpl { // Rewrite image resources to images so they are inlined nicely const addAsInlineData = async (mimeType: string, value: string, uri?: URI): Promise => { - details.output.push({ type: 'embed', mimeType, value, uri }); + details.output.push({ type: 'embed', mimeType, value, uri, audience }); if (isForModel) { let finalData: VSBuffer; try { @@ -253,7 +265,7 @@ class McpToolImplementation implements IToolImpl { } catch { finalData = decodeBase64(value); } - result.content.push({ kind: 'data', value: { mimeType, data: finalData } }); + result.content.push({ kind: 'data', value: { mimeType, data: finalData }, audience }); } }; @@ -261,6 +273,7 @@ class McpToolImplementation implements IToolImpl { const json: IMcpToolResourceLinkContents = { uri, underlyingMimeType: mimeType }; result.content.push({ kind: 'data', + audience, value: { mimeType: McpToolResourceLinkMimeType, data: VSBuffer.fromString(JSON.stringify(json)), @@ -268,7 +281,7 @@ class McpToolImplementation implements IToolImpl { }); }; - const isForModel = audience.includes('assistant'); + const isForModel = !audience || audience.includes(LanguageModelPartAudience.Assistant); if (item.type === 'text') { details.output.push({ type: 'embed', isText: true, value: item.text }); // structured content 'represents the result of the tool call', so take @@ -276,6 +289,7 @@ class McpToolImplementation implements IToolImpl { if (isForModel && !callResult.structuredContent) { result.content.push({ kind: 'text', + audience, value: item.text }); } @@ -287,6 +301,7 @@ class McpToolImplementation implements IToolImpl { details.output.push({ type: 'ref', uri, + audience, mimeType: item.mimeType, }); @@ -294,6 +309,7 @@ class McpToolImplementation implements IToolImpl { if (item.mimeType && getAttachableImageExtension(item.mimeType)) { result.content.push({ kind: 'data', + audience, value: { mimeType: item.mimeType, data: await this._fileService.readFile(uri).then(f => f.value).catch(() => VSBuffer.alloc(0)), @@ -314,6 +330,7 @@ class McpToolImplementation implements IToolImpl { isText: 'text' in item.resource, mimeType: item.resource.mimeType, value: 'blob' in item.resource ? item.resource.blob : item.resource.text, + audience, asResource: true, }); @@ -326,8 +343,8 @@ class McpToolImplementation implements IToolImpl { } if (callResult.structuredContent) { - details.output.push({ type: 'embed', isText: true, value: JSON.stringify(callResult.structuredContent, null, 2) }); - result.content.push({ kind: 'text', value: JSON.stringify(callResult.structuredContent) }); + details.output.push({ type: 'embed', isText: true, value: JSON.stringify(callResult.structuredContent, null, 2), audience: [LanguageModelPartAudience.Assistant] }); + result.content.push({ kind: 'text', value: JSON.stringify(callResult.structuredContent), audience: [LanguageModelPartAudience.Assistant] }); } result.toolResultDetails = details; From 8a67ffe5200cbb5e1844c38b964e4b6edd658aeb Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 10 Sep 2025 12:34:07 -0700 Subject: [PATCH 0175/4355] new dropdown thinking style (#265939) * new thinking style * use toggles more :) * change setting defaults * clean up space --- .../contrib/chat/browser/chat.contribution.ts | 7 +- .../chatThinkingContentPart.ts | 104 ++++++++++++++++-- .../contrib/chat/browser/media/chat.css | 104 +++++++++++++++++- .../contrib/chat/common/constants.ts | 3 +- 4 files changed, 203 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 43e2ef63a2a..143cad8ef5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -638,15 +638,16 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.ThinkingStyle]: { type: 'string', - default: 'collapsedPreview', - enum: ['collapsed', 'collapsedPreview', 'expanded', 'none'], + default: 'collapsedPerItem', + enum: ['collapsed', 'collapsedPreview', 'expanded', 'none', 'collapsedPerItem'], enumDescriptions: [ nls.localize('chat.agent.thinkingMode.collapsed', "Thinking parts will be collapsed by default."), nls.localize('chat.agent.thinkingMode.collapsedPreview', "Thinking parts will be expanded first, then collapse once we reach a part that is not thinking."), nls.localize('chat.agent.thinkingMode.expanded', "Thinking parts will be expanded by default."), nls.localize('chat.agent.thinkingMode.none', "Do not show the thinking"), + nls.localize('chat.agent.thinkingMode.collapsedPerItem', "Each thinking subsection is individually collapsible and collapsed by default inside the thinking block."), ], - description: nls.localize('chat.agent.thinkingCollapsedByDefault', "Controls how thinking is rendered."), + description: nls.localize('chat.agent.thinkingStyle', "Controls how thinking is rendered."), tags: ['experimental'], }, 'chat.disableAIFeatures': { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 099f7a48863..cdad234d977 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -35,8 +35,10 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen private defaultTitle = localize('chat.thinking.header', 'Thinking...'); private readonly renderer: MarkdownRenderer; private textContainer!: HTMLElement; + private currentHeaderElement: HTMLElement | undefined; private markdownResult: IMarkdownRenderResult | undefined; private wrapper!: HTMLElement; + private perItemCollapsedMode: boolean = false; constructor( content: IChatThinkingPart, @@ -52,16 +54,28 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen this.renderer = instantiationService.createInstance(MarkdownRenderer, {}); this.id = content.id; - this.currentThinkingValue = initialText; - this.currentTitle = extractedTitle; const mode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? 'none'; + this.perItemCollapsedMode = mode === 'collapsedPerItem'; + + this.currentTitle = extractedTitle; + this.currentThinkingValue = this.parseContent(initialText); if (mode === 'expanded' || mode === 'collapsedPreview') { this.setExpanded(true); } else if (mode === 'collapsed') { this.setExpanded(false); } + if (this.perItemCollapsedMode) { + this.setExpanded(true); + const header = this.domNode.querySelector('.chat-used-context-label'); + if (header) { + header.remove(); + this.domNode.classList.add('chat-thinking-no-outer-header'); + this._onDidChangeHeight.fire(); + } + } + const node = this.domNode; node.classList.add('chat-thinking-box'); node.tabIndex = 0; @@ -69,22 +83,74 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen } private parseContent(content: string): string { - const noSep = content.replace(/<\|im_sep\|>\*{4,}/g, '').trim(); - return noSep; + let cleaned = content.replace(/<\|im_sep\|>\*{4,}/g, '').trim(); + if (this.perItemCollapsedMode) { + cleaned = cleaned.replace(/^\*\*[^*]+\*\*\s*\n+(?:\s*\n)*/, '').trim(); + } + return cleaned; } protected override initContent(): HTMLElement { this.wrapper = $('.chat-used-context-list.chat-thinking-collapsible'); - this.textContainer = $('.chat-thinking-item.markdown-content'); - this.wrapper.appendChild(this.textContainer); - + this.wrapper.classList.toggle('chat-thinking-per-item-mode', this.perItemCollapsedMode); + if (this.perItemCollapsedMode) { + this.createThinkingItemContainer(); + } else { + this.textContainer = $('.chat-thinking-item.markdown-content'); + this.wrapper.appendChild(this.textContainer); + } if (this.currentThinkingValue) { this.renderMarkdown(this.currentThinkingValue); } - return this.wrapper; } + private createThinkingItemContainer(): void { + const itemWrapper = $('.chat-thinking-item-wrapper'); + + const header = $('.chat-thinking-item-header'); + + const labelSpan = document.createElement('span'); + labelSpan.classList.add('chat-thinking-item-header-label'); + labelSpan.textContent = this.currentTitle ?? this.defaultTitle; + header.appendChild(labelSpan); + + const icon = $('.codicon.codicon-chevron-right.chat-thinking-item-caret', { 'aria-hidden': 'true' }); + header.appendChild(icon); + + header.tabIndex = 0; + + const body = $('.chat-thinking-item.markdown-content'); + + const toggle = () => { + const collapsed = body.classList.toggle('hidden'); + itemWrapper.classList.toggle('collapsed', collapsed); + icon.classList.toggle('codicon-chevron-right', collapsed); + icon.classList.toggle('codicon-chevron-down', !collapsed); + this._onDidChangeHeight.fire(); + }; + + header.addEventListener('click', toggle); + header.addEventListener('keydown', e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggle(); + } + }); + + itemWrapper.appendChild(header); + itemWrapper.appendChild(body); + this.wrapper.appendChild(itemWrapper); + + body.classList.toggle('hidden', this.perItemCollapsedMode); + itemWrapper.classList.toggle('collapsed', this.perItemCollapsedMode); + icon.classList.toggle('codicon-chevron-right', this.perItemCollapsedMode); + icon.classList.toggle('codicon-chevron-down', !this.perItemCollapsedMode); + + this.textContainer = body; + this.currentHeaderElement = header; + } + private renderMarkdown(content: string): void { if (this.markdownResult) { this.markdownResult.dispose(); @@ -140,12 +206,30 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen // makes a new text container. when we update, we now update this container. public setupThinkingContainer(content: IChatThinkingPart, context: IChatContentPartRenderContext) { - this.textContainer = $('.chat-thinking-item.markdown-content'); - this.wrapper.appendChild(this.textContainer); + if (this.perItemCollapsedMode) { + this.createThinkingItemContainer(); + } else { + this.textContainer = $('.chat-thinking-item.markdown-content'); + this.wrapper.appendChild(this.textContainer); + } this.id = content?.id; this.updateThinking(content); } + protected override setTitle(title: string): void { + if (!this.perItemCollapsedMode) { + super.setTitle(title); + } + if (this.currentHeaderElement) { + const labelSpan = this.currentHeaderElement.querySelector('.chat-thinking-item-header-label'); + if (labelSpan) { + labelSpan.textContent = title; + } else { + this.currentHeaderElement.textContent = title; + } + } + } + hasSameContent(other: IChatRendererContent, _followingContent: IChatRendererContent[], _element: ChatTreeItem): boolean { // only need this check if we are adding tools into thinking dropdown. diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 0932407dd7a..3a95c958fe5 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2672,7 +2672,7 @@ have to be updated for changes to the rules above, or to support more deeply nes padding-top: 0; } -.interactive-session .interactive-response .value .chat-thinking-box { +.interactive-session .interactive-response .value .chat-thinking-box:not(.chat-thinking-no-outer-header) { outline: none; position: relative; color: var(--vscode-descriptionForeground); @@ -2734,6 +2734,108 @@ have to be updated for changes to the rules above, or to support more deeply nes } } +.interactive-session .interactive-response .value .chat-thinking-box.chat-thinking-no-outer-header { + + margin-bottom: 0px; + + .chat-thinking-item { + padding: 6px 12px; + position: relative; + + .progress-container { + margin-bottom: 0px; + padding-top: 0px; + } + } + + .chat-thinking-item.hidden { + display: none; + } + + .chat-thinking-item-header { + padding: 3px 4px; + margin-left: 5px; + cursor: pointer; + font-weight: 600; + color: var(--vscode-descriptionForeground); + user-select: none; + outline: none; + display: flex; + align-items: center; + gap: 6px; + border: 1px solid transparent; + border-radius: 4px; + } + + .chat-thinking-item-header:hover { + background: var(--vscode-toolbar-hoverBackground); + } + + .chat-thinking-item-header:active { + background: var(--vscode-toolbar-activeBackground); + } + + .chat-thinking-item-header:focus-visible { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; + } + + .chat-thinking-item-header .codicon { + font-size: 12px; + line-height: 1; + margin-left: auto; + } + + .chat-thinking-item-header.monaco-button .chat-thinking-item { + padding-top: 0px !important; + } + + .chat-thinking-item-wrapper::before, + .chat-thinking-item-wrapper::after { + position: absolute; + content: ''; + display: block; + border-radius: 50%; + pointer-events: none; + } + + .chat-thinking-item-wrapper::before { + background: var(--vscode-sideBar-background); + top: 5px; + left: -10px; + width: 13px; + height: 16px; + z-index: 2; + } + + .chat-thinking-item-wrapper::after { + top: 9px; + left: -7px; + width: 7px; + height: 7px; + background: var(--vscode-chat-requestBorder); + z-index: 3; + } + + .chat-thinking-item-wrapper { + position: relative; + + .chat-thinking-item.markdown-content { + padding-top: 2px; + } + } + + .chat-thinking-text { + font-size: var(--vscode-chat-font-size-body-s); + padding: 0 10px; + display: block; + } + + .rendered-markdown > p { + margin: 0; + } +} + .editor-instance .interactive-session .interactive-response .value .chat-thinking-box .chat-thinking-item ::before { background: var(--vscode-editor-background); } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 981ad945f44..956bac9d28e 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -50,7 +50,8 @@ export enum ThinkingDisplayMode { Collapsed = 'collapsed', CollapsedPreview = 'collapsedPreview', Expanded = 'expanded', - None = 'none' + None = 'none', + CollapsedPerItem = 'collapsedPerItem' } export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook' | 'editing-session'; From a94fddf037ba0d3c9fc735b1c550edf0ba1be4cd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Sep 2025 13:01:21 -0700 Subject: [PATCH 0176/4355] testing: fix perf issue with a large number of logs (#266085) Closes https://github.com/microsoft/vscode/issues/266048 --- .../browser/testResultsView/testResultsTree.ts | 4 ++-- .../contrib/testing/browser/testingDecorations.ts | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index a4f65929051..71f668b5fce 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -157,7 +157,7 @@ class TestCaseElement implements ITreeElement { return Event.None; } - return Event.filter(this.results.onChange, e => e.item.item.extId === this.test.item.extId); + return Event.filter(this.results.onChange, e => e.item.item.extId === this.test.item.extId && e.reason !== TestResultItemChangeReason.NewMessage); } public get state() { @@ -247,7 +247,7 @@ class TestMessageElement implements ITreeElement { } // rerender when the test case changes so it gets retired events - return Event.filter(this.result.onChange, e => e.item.item.extId === this.test.item.extId); + return Event.filter(this.result.onChange, e => e.item.item.extId === this.test.item.extId && e.reason !== TestResultItemChangeReason.NewMessage); } public get context(): ITestMessageMenuArgs { diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 8de4733c142..b037046a20a 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -9,7 +9,7 @@ import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js' import { Action, IAction, Separator, SubmenuAction } from '../../../../base/common/actions.js'; import { equals } from '../../../../base/common/arrays.js'; import { mapFindFirst } from '../../../../base/common/arraysFind.js'; -import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { RunOnceScheduler, Throttler, timeout } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { stripIcons } from '../../../../base/common/iconLabels.js'; @@ -403,10 +403,21 @@ export class TestingDecorations extends Disposable implements IEditorContributio } })); + const msgThrottler = this._register(new Throttler()); + this._register(this.results.onTestChanged(ev => { + if (ev.reason !== TestResultItemChangeReason.NewMessage) { + return; + } + + msgThrottler.queue(() => { + this.applyResults(); + return timeout(100); + }); + })); + this._register(Event.any( this.results.onResultsChanged, editor.onDidChangeModel, - Event.filter(this.results.onTestChanged, c => c.reason === TestResultItemChangeReason.NewMessage), this.testService.showInlineOutput.onDidChange, )(() => this.applyResults())); From 5ba9efdf4038d9bdbcad7f199d14e8b9b691aa57 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:13:55 -0700 Subject: [PATCH 0177/4355] Don't require restart when switching to TS native For #266087 Dynamically registers / unregisters the language features --- .../src/commands/useTsgo.ts | 2 +- .../src/extension.ts | 99 ++++++++++--------- .../util/dependentRegistration.ts | 26 +++-- .../src/lazyClientHost.ts | 6 +- .../src/typescriptServiceClient.ts | 5 +- .../src/utils/lazy.ts | 4 + 6 files changed, 79 insertions(+), 63 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/useTsgo.ts b/extensions/typescript-language-features/src/commands/useTsgo.ts index aedf28e54b0..3f6e5f8c2c9 100644 --- a/extensions/typescript-language-features/src/commands/useTsgo.ts +++ b/extensions/typescript-language-features/src/commands/useTsgo.ts @@ -27,7 +27,7 @@ export class DisableTsgoCommand implements Command { * @param enable Whether to enable or disable TypeScript Go */ async function updateTsgoSetting(enable: boolean): Promise { - const tsgoExtension = vscode.extensions.getExtension('typescript.typescript-lsp'); + const tsgoExtension = vscode.extensions.getExtension('typescriptteam.native-preview'); // Error if the TypeScript Go extension is not installed with a button to open the GitHub repo if (!tsgoExtension) { const selection = await vscode.window.showErrorMessage( diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index 29f809bd653..b528a1e90dd 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -25,27 +25,12 @@ import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Lazy } from './utils/lazy'; import { getPackageInfo } from './utils/packageInfo'; import * as temp from './utils/temp.electron'; +import { conditionalRegistration, requireGlobalConfiguration } from './languageFeatures/util/dependentRegistration'; +import { DisposableStore } from './utils/dispose'; export function activate( context: vscode.ExtensionContext ): Api { - const commandManager = new CommandManager(); - context.subscriptions.push(commandManager); - - // Disable extension if using the experimental TypeScript Go extension - const config = vscode.workspace.getConfiguration('typescript'); - const useTsgo = config.get('experimental.useTsgo', false); - - if (useTsgo) { - commandManager.register(new DisableTsgoCommand()); - // Return a no-op API when disabled - return { - getAPI() { - return undefined; - } - }; - } - const pluginManager = new PluginManager(); context.subscriptions.push(pluginManager); @@ -55,9 +40,6 @@ export function activate( const logDirectoryProvider = new NodeLogDirectoryProvider(context); const versionProvider = new DiskTypeScriptVersionProvider(); - const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); - context.subscriptions.push(activeJsTsEditorTracker); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -71,34 +53,55 @@ export function activate( new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState); } - const logger = new Logger(); - - const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { - pluginManager, - commandManager, - logDirectoryProvider, - cancellerFactory: nodeRequestCancellerFactory, - versionProvider, - processFactory: new ElectronServiceProcessFactory(), - activeJsTsEditorTracker, - serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), - experimentTelemetryReporter, - logger, - }, item => { - onCompletionAccepted.fire(item); - }); - - registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); - - import('./task/taskProvider').then(module => { - context.subscriptions.push(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); - }); - - import('./languageFeatures/tsconfig').then(module => { - context.subscriptions.push(module.register()); - }); - - context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + context.subscriptions.push(conditionalRegistration([ + requireGlobalConfiguration('typescript', 'experimental.useTsgo'), + ], () => { + // TSGO. Only register a small set of features that don't use TS Server + const disposables = new DisposableStore(); + + const commandManager = disposables.add(new CommandManager()); + commandManager.register(new DisableTsgoCommand()); + + return disposables; + }, () => { + // Normal registration path + const disposables = new DisposableStore(); + + const commandManager = disposables.add(new CommandManager()); + const activeJsTsEditorTracker = disposables.add(new ActiveJsTsEditorTracker()); + + const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { + pluginManager, + commandManager, + logDirectoryProvider, + cancellerFactory: nodeRequestCancellerFactory, + versionProvider, + processFactory: new ElectronServiceProcessFactory(), + activeJsTsEditorTracker, + serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), + experimentTelemetryReporter, + logger: new Logger(), + }, item => { + onCompletionAccepted.fire(item); + }).map(clientHost => { + return disposables.add(clientHost); + }); + + // Register features + registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); + + import('./task/taskProvider').then(module => { + disposables.add(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); + }); + + import('./languageFeatures/tsconfig').then(module => { + disposables.add(module.register()); + }); + + disposables.add(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + + return disposables; + },)); return getExtensionApi(onCompletionAccepted.event, pluginManager); } diff --git a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts index e234acd1ab4..c445cc20e12 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts @@ -34,11 +34,15 @@ export class Condition extends Disposable { } class ConditionalRegistration { - private registration: vscode.Disposable | undefined = undefined; + private state?: { + readonly enabled: boolean; + readonly registration: vscode.Disposable | undefined; + }; public constructor( private readonly conditions: readonly Condition[], - private readonly doRegister: () => vscode.Disposable + private readonly doRegister: () => vscode.Disposable, + private readonly elseDoRegister?: () => vscode.Disposable ) { for (const condition of conditions) { condition.onDidChange(() => this.update()); @@ -47,17 +51,22 @@ class ConditionalRegistration { } public dispose() { - this.registration?.dispose(); - this.registration = undefined; + this.state?.registration?.dispose(); + this.state = undefined; } private update() { const enabled = this.conditions.every(condition => condition.value); if (enabled) { - this.registration ??= this.doRegister(); + if (!this.state?.enabled) { + this.state?.registration?.dispose(); + this.state = { enabled: true, registration: this.doRegister() }; + } } else { - this.registration?.dispose(); - this.registration = undefined; + if (this.state?.enabled || !this.state) { + this.state?.registration?.dispose(); + this.state = { enabled: false, registration: this.elseDoRegister?.() }; + } } } } @@ -65,8 +74,9 @@ class ConditionalRegistration { export function conditionalRegistration( conditions: readonly Condition[], doRegister: () => vscode.Disposable, + elseDoRegister?: () => vscode.Disposable ): vscode.Disposable { - return new ConditionalRegistration(conditions, doRegister); + return new ConditionalRegistration(conditions, doRegister, elseDoRegister); } export function requireMinVersion( diff --git a/extensions/typescript-language-features/src/lazyClientHost.ts b/extensions/typescript-language-features/src/lazyClientHost.ts index 3fc8a37cd17..ff03830e012 100644 --- a/extensions/typescript-language-features/src/lazyClientHost.ts +++ b/extensions/typescript-language-features/src/lazyClientHost.ts @@ -38,16 +38,12 @@ export function createLazyClientHost( onCompletionAccepted: (item: vscode.CompletionItem) => void, ): Lazy { return new Lazy(() => { - const clientHost = new TypeScriptServiceClientHost( + return new TypeScriptServiceClientHost( standardLanguageDescriptions, context, onCaseInsensitiveFileSystem, services, onCompletionAccepted); - - context.subscriptions.push(clientHost); - - return clientHost; }); } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4201d6da29b..57395ce3999 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -108,7 +108,6 @@ interface WatchEvent { export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { - private readonly _onReady?: { promise: Promise; resolve: () => void; reject: () => void }; private _configuration: TypeScriptServiceConfiguration; private readonly pluginPathsProvider: TypeScriptPluginPathsProvider; @@ -632,6 +631,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.serverState = ServerState.None; + if (this.isDisposed) { + return; + } + if (restart) { const diff = Date.now() - this.lastStart; this.numberRestarts++; diff --git a/extensions/typescript-language-features/src/utils/lazy.ts b/extensions/typescript-language-features/src/utils/lazy.ts index 7114ece99b1..be1d0530b88 100644 --- a/extensions/typescript-language-features/src/utils/lazy.ts +++ b/extensions/typescript-language-features/src/utils/lazy.ts @@ -44,4 +44,8 @@ export class Lazy { * Get the wrapped value without forcing evaluation. */ get rawValue(): T | undefined { return this._value; } + + map(fn: (value: T) => R): Lazy { + return new Lazy(() => fn(this.value)); + } } From 99646f23fe0051a1bc1f53f003166826e1dca6cb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 10 Sep 2025 22:21:59 +0200 Subject: [PATCH 0178/4355] IPromptsService.getParsedPromptFile --- .../promptSyntax/service/promptValidator.ts | 16 +++++----- .../promptSyntax/service/promptsService.ts | 7 +++++ .../service/promptsServiceImpl.ts | 29 +++++++++++++++---- .../chat/test/common/mockPromptsService.ts | 3 ++ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index fe9cfa3f8c7..8f6604b33fd 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -17,12 +17,13 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { IHeaderAttribute, NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; +import { IHeaderAttribute, ParsedPromptFile } from './newPromptsParser.js'; import { PromptsConfig } from '../config/config.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { IPromptsService } from './promptsService.js'; const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; @@ -296,7 +297,6 @@ function toMarker(message: string, range: Range, severity = MarkerSeverity.Error export class PromptValidatorContribution extends Disposable { private readonly validator: PromptValidator; - private readonly promptParser: NewPromptsParser; private readonly localDisposables = this._register(new DisposableStore()); constructor( @@ -304,10 +304,10 @@ export class PromptValidatorContribution extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private configService: IConfigurationService, @IMarkerService private readonly markerService: IMarkerService, + @IPromptsService private readonly promptsService: IPromptsService, ) { super(); this.validator = instantiationService.createInstance(PromptValidator); - this.promptParser = instantiationService.createInstance(NewPromptsParser); this.updateRegistration(); this._register(this.configService.onDidChangeConfiguration(e => { @@ -329,13 +329,13 @@ export class PromptValidatorContribution extends Disposable { this.modelService.getModels().forEach(model => { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); if (promptType) { - trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } }); this.localDisposables.add(this.modelService.onModelAdded((model) => { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); if (promptType) { - trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } })); this.localDisposables.add(this.modelService.onModelRemoved((model) => { @@ -357,7 +357,7 @@ export class PromptValidatorContribution extends Disposable { } const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); if (promptType) { - trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptParser, this.markerService)); + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } })); } @@ -371,7 +371,7 @@ class ModelTracker extends Disposable { private readonly textModel: ITextModel, private readonly promptType: PromptsType, private readonly validator: PromptValidator, - private readonly promptParser: NewPromptsParser, + @IPromptsService private readonly promptsService: IPromptsService, @IMarkerService private readonly markerService: IMarkerService, ) { super(); @@ -383,7 +383,7 @@ class ModelTracker extends Disposable { private validate(): void { this.delayer.trigger(async () => { const markers: IMarkerData[] = []; - const ast = this.promptParser.parse(this.textModel.uri, this.textModel.getValue()); + const ast = this.promptsService.getParsedPromptFile(this.textModel); await this.validator.validate(ast, this.promptType, m => markers.push(m)); this.markerService.changeOne(MARKERS_OWNER_ID, this.textModel.uri, markers); }); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index e287f55e851..ca85e45152f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -15,6 +15,7 @@ import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; import { YamlNode, YamlParseError } from '../../../../../../base/common/yaml.js'; import { IVariableReference } from '../../chatModes.js'; +import { ParsedPromptFile } from './newPromptsParser.js'; /** * Provides prompt services. @@ -164,6 +165,12 @@ export interface IPromptsService extends IDisposable { */ getSyntaxParserFor(model: ITextModel): TSharedPrompt & { isDisposed: false }; + /** + * The parsed prompt file for the provided text model. + * @param textModel Returns the parsed prompt file. + */ + getParsedPromptFile(textModel: ITextModel): ParsedPromptFile; + /** * List all available prompt files. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 45b976408d9..68217698a22 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -29,6 +29,7 @@ import { IConfigurationService } from '../../../../../../platform/configuration/ import { PositionOffsetTransformer } from '../../../../../../editor/common/core/text/positionToOffset.js'; import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { ResourceMap } from '../../../../../../base/common/map.js'; /** * Provides prompt services. @@ -51,6 +52,9 @@ export class PromptsService extends Disposable implements IPromptsService { */ private cachedCustomChatModes: Promise | undefined; + + private parsedPromptFileCache = new ResourceMap<[number, ParsedPromptFile]>(); + /** * Lazily created event that is fired when the custom chat modes change. */ @@ -99,6 +103,10 @@ export class PromptsService extends Disposable implements IPromptsService { return parser; }) ); + + this._register(this.modelService.onModelRemoved((model) => { + this.parsedPromptFileCache.delete(model.uri); + })); } /** @@ -136,6 +144,18 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cache.get(model); } + public getParsedPromptFile(textModel: ITextModel): ParsedPromptFile { + const cached = this.parsedPromptFileCache.get(textModel.uri); + if (cached && cached[0] === textModel.getVersionId()) { + return cached[1]; + } + const ast = new NewPromptsParser().parse(textModel.uri, textModel.getValue()); + if (!cached || cached[0] < textModel.getVersionId()) { + this.parsedPromptFileCache.set(textModel.uri, [textModel.getVersionId(), ast]); + } + return ast; + } + public async listPromptFiles(type: PromptsType, token: CancellationToken): Promise { if (!PromptsConfig.enabled(this.configurationService)) { return []; @@ -307,15 +327,12 @@ export class PromptsService extends Disposable implements IPromptsService { } public async parseNew(uri: URI): Promise { - let content: string | undefined; const model = this.modelService.getModel(uri); if (model) { - content = model.getValue(); - } else { - const fileContent = await this.fileService.readFile(uri); - content = fileContent.value.toString(); + return this.getParsedPromptFile(model); } - return new NewPromptsParser().parse(uri, content); + const fileContent = await this.fileService.readFile(uri); + return new NewPromptsParser().parse(uri, fileContent.value.toString()); } } diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index 97394b57d90..6728e53f6a6 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -6,6 +6,8 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Emitter } from '../../../../../base/common/event.js'; import { URI } from '../../../../../base/common/uri.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; +import { ParsedPromptFile } from '../../common/promptSyntax/service/newPromptsParser.js'; import { ICustomChatMode, IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; export class MockPromptsService implements IPromptsService { @@ -34,5 +36,6 @@ export class MockPromptsService implements IPromptsService { findPromptSlashCommands(): Promise { throw new Error('Not implemented'); } parse(_uri: URI, _type: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } getPromptFileType(_resource: URI): any { return undefined; } + getParsedPromptFile(textModel: ITextModel): ParsedPromptFile { throw new Error('Not implemented'); } dispose(): void { } } From 74bd54b65cc6018bbc113db24b1893b17c777634 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:58:12 -0700 Subject: [PATCH 0179/4355] Avoid using disposables in domSanitize We can use `removeAllHooks` instead of unregistering each one individually to save a small amount of overhead --- src/vs/base/browser/domSanitize.ts | 77 ++++++++++++------------------ 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 8e33b1579f7..887d5a30c7b 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, IDisposable, toDisposable } from '../common/lifecycle.js'; import { Schemas } from '../common/network.js'; import { reset } from './dom.js'; import dompurify from './dompurify/dompurify.js'; - /** * List of safe, non-input html tags. */ @@ -103,16 +101,6 @@ export const defaultAllowedAttrs = Object.freeze([ ]); -type UponSanitizeElementCb = (currentNode: Element, data: dompurify.SanitizeElementHookEvent, config: dompurify.Config) => void; -type UponSanitizeAttributeCb = (currentNode: Element, data: dompurify.SanitizeAttributeHookEvent, config: dompurify.Config) => void; - -function addDompurifyHook(hook: 'uponSanitizeElement', cb: UponSanitizeElementCb): IDisposable; -function addDompurifyHook(hook: 'uponSanitizeAttribute', cb: UponSanitizeAttributeCb): IDisposable; -function addDompurifyHook(hook: 'uponSanitizeElement' | 'uponSanitizeAttribute', cb: any): IDisposable { - dompurify.addHook(hook, cb); - return toDisposable(() => dompurify.removeHook(hook)); -} - const fakeRelativeUrlProtocol = 'vscode-relative-path'; interface AllowedLinksConfig { @@ -120,35 +108,35 @@ interface AllowedLinksConfig { readonly allowRelativePaths: boolean; } -/** - * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` - * attributes are valid. - */ -function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConfig, allowedMediaProtocols: AllowedLinksConfig): IDisposable { - function validateLink(value: string, allowedProtocols: AllowedLinksConfig): boolean { - if (allowedProtocols.override === '*') { - return true; // allow all protocols - } - - try { - const url = new URL(value, fakeRelativeUrlProtocol + '://'); - if (allowedProtocols.override.includes(url.protocol.replace(/:$/, ''))) { - return true; - } +function validateLink(value: string, allowedProtocols: AllowedLinksConfig): boolean { + if (allowedProtocols.override === '*') { + return true; // allow all protocols + } - if (allowedProtocols.allowRelativePaths - && url.protocol === fakeRelativeUrlProtocol + ':' - && !value.trim().toLowerCase().startsWith(fakeRelativeUrlProtocol) - ) { - return true; - } + try { + const url = new URL(value, fakeRelativeUrlProtocol + '://'); + if (allowedProtocols.override.includes(url.protocol.replace(/:$/, ''))) { + return true; + } - return false; - } catch (e) { - return false; + if (allowedProtocols.allowRelativePaths + && url.protocol === fakeRelativeUrlProtocol + ':' + && !value.trim().toLowerCase().startsWith(fakeRelativeUrlProtocol) + ) { + return true; } + + return false; + } catch (e) { + return false; } +} +/** + * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` + * attributes are valid. + */ +function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConfig, allowedMediaProtocols: AllowedLinksConfig) { dompurify.addHook('afterSanitizeAttributes', (node) => { // check all href/src attributes for validity for (const attr of ['href', 'src']) { @@ -158,7 +146,6 @@ function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConf if (!attrValue.startsWith('#') && !validateLink(attrValue, allowedLinkProtocols)) { node.removeAttribute(attr); } - } else { // 'src' if (!validateLink(attrValue, allowedMediaProtocols)) { node.removeAttribute(attr); @@ -167,8 +154,6 @@ function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConf } } }); - - return toDisposable(() => dompurify.removeHook('afterSanitizeAttributes')); } /** @@ -255,7 +240,6 @@ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): Tr function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom'): DocumentFragment; function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'trusted'): TrustedHTML; function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom' | 'trusted'): TrustedHTML | DocumentFragment { - const store = new DisposableStore(); try { const resolvedConfig: dompurify.Config = { ...defaultDomPurifyConfig }; @@ -304,7 +288,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine resolvedConfig.ALLOWED_ATTR = Array.from(allowedAttrNames); - store.add(hookDomPurifyHrefAndSrcSanitizer( + hookDomPurifyHrefAndSrcSanitizer( { override: config?.allowedLinkProtocols?.override ?? [Schemas.http, Schemas.https], allowRelativePaths: config?.allowRelativeLinkPaths ?? false @@ -312,13 +296,14 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine { override: config?.allowedMediaProtocols?.override ?? [Schemas.http, Schemas.https], allowRelativePaths: config?.allowRelativeMediaPaths ?? false - })); + }); + if (config?.replaceWithPlaintext) { - store.add(addDompurifyHook('uponSanitizeElement', replaceWithPlainTextHook)); + dompurify.addHook('uponSanitizeElement', replaceWithPlainTextHook); } if (allowedAttrPredicates.size) { - store.add(addDompurifyHook('uponSanitizeAttribute', (node, e) => { + dompurify.addHook('uponSanitizeAttribute', (node, e) => { const predicate = allowedAttrPredicates.get(e.attrName); if (predicate) { const result = predicate.shouldKeep(node, e); @@ -331,7 +316,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine } else { e.keepAttr = allowedAttrNames.has(e.attrName); } - })); + }); } if (outputType === 'dom') { @@ -346,7 +331,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine }); } } finally { - store.dispose(); + dompurify.removeAllHooks(); } } From cd05e0cb6c47bd729ef7d4a29133e3593014d2bc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:59:11 -0700 Subject: [PATCH 0180/4355] Remove `copilot` from strings and localize them Fixes #264155 --- .../src/languageFeatures/quickFix.ts | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index e32727befbd..35ca543a8d4 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -366,22 +366,19 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider change.textChanges.map(textChange => textChange.newText).join('')).join(''); - title = 'Add meaningful parameter name with Copilot'; - message = `Rename the parameter ${newText} with a more meaningful name.`; + title = vscode.l10n.t('Add meaningful parameter name with AI'); + message = vscode.l10n.t(`Rename the parameter {0} with a more meaningful name.`, newText); expand = { kind: 'navtree-function', pos: diagnostic.range.start From 8d5fd0b6a09696c08c92f4fd07e8f50131e0de79 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:12:30 -0700 Subject: [PATCH 0181/4355] Move Accept & Hide keyhandlers to Commands (#266091) ref https://github.com/microsoft/vscode/issues/98479 --- .../platform/quickinput/browser/quickInput.ts | 4 ++ .../quickinput/browser/quickInputActions.ts | 70 +++++++++++++++---- .../browser/quickInputController.ts | 17 ++--- .../platform/quickinput/common/quickInput.ts | 5 ++ 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 74b42dc9c4e..612316858c0 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1266,6 +1266,10 @@ export class InputBox extends QuickInput implements IInputBox { super.show(); } + accept(): void { + this.onDidAcceptEmitter.fire(); + } + protected override update() { if (!this.visible) { return; diff --git a/src/vs/platform/quickinput/browser/quickInputActions.ts b/src/vs/platform/quickinput/browser/quickInputActions.ts index 9d26104114c..a03cc434961 100644 --- a/src/vs/platform/quickinput/browser/quickInputActions.ts +++ b/src/vs/platform/quickinput/browser/quickInputActions.ts @@ -12,22 +12,30 @@ import { ContextKeyExpr } from '../../contextkey/common/contextkey.js'; import { InputFocusedContext } from '../../contextkey/common/contextkeys.js'; import { ICommandAndKeybindingRule, KeybindingWeight, KeybindingsRegistry } from '../../keybinding/common/keybindingsRegistry.js'; import { endOfQuickInputBoxContext, inQuickInputContext, quickInputTypeContextKeyValue } from './quickInput.js'; -import { IQuickInputService, IQuickPick, IQuickTree, QuickInputType, QuickPickFocus } from '../common/quickInput.js'; +import { IInputBox, IQuickInputService, IQuickPick, IQuickTree, QuickInputType, QuickPickFocus } from '../common/quickInput.js'; + +function registerQuickInputCommandAndKeybindingRule(rule: PartialExcept, options: { withAltMod?: boolean; withCtrlMod?: boolean; withCmdMod?: boolean } = {}) { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickInputContext, + metadata: { description: localize('quickInput', "Used while in the context of any kind of quick input. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") }, + ...rule, + secondary: getSecondary(rule.primary!, rule.secondary ?? [], options) + }); +} -const defaultCommandAndKeybindingRule = { - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and( - ContextKeyExpr.or( - ContextKeyExpr.equals(quickInputTypeContextKeyValue, QuickInputType.QuickPick), - ContextKeyExpr.equals(quickInputTypeContextKeyValue, QuickInputType.QuickTree), - ), - inQuickInputContext - ), - metadata: { description: localize('quickPick', "Used while in the context of the quick pick. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") } -}; function registerQuickPickCommandAndKeybindingRule(rule: PartialExcept, options: { withAltMod?: boolean; withCtrlMod?: boolean; withCmdMod?: boolean } = {}) { KeybindingsRegistry.registerCommandAndKeybindingRule({ - ...defaultCommandAndKeybindingRule, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + ContextKeyExpr.or( + // Only things that use Tree widgets + ContextKeyExpr.equals(quickInputTypeContextKeyValue, QuickInputType.QuickPick), + ContextKeyExpr.equals(quickInputTypeContextKeyValue, QuickInputType.QuickTree), + ), + inQuickInputContext + ), + metadata: { description: localize('quickPick', "Used while in the context of the quick pick. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") }, ...rule, secondary: getSecondary(rule.primary!, rule.secondary ?? [], options) }); @@ -189,6 +197,24 @@ if (isMacintosh) { //#region Accept +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'quickInput.accept', + primary: KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + // All other kinds of Quick things handle Accept, except Widget. In other words, Accepting is a detail on the things + // that extend IQuickInput + ContextKeyExpr.notEquals(quickInputTypeContextKeyValue, QuickInputType.QuickWidget), + inQuickInputContext + ), + metadata: { description: localize('nonQuickWidget', "Used while in the context of some quick input. If you change one keybinding for this command, you should change all of the other keybindings (modifier variants) of this command as well.") }, + handler: (accessor) => { + const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput as IQuickPick | IQuickTree | IInputBox; + currentQuickPick?.accept(); + }, + secondary: getSecondary(KeyCode.Enter, [], { withAltMod: true, withCtrlMod: true, withCmdMod: true }) +}); + registerQuickPickCommandAndKeybindingRule( { id: 'quickInput.acceptInBackground', @@ -210,6 +236,24 @@ registerQuickPickCommandAndKeybindingRule( { withAltMod: true, withCtrlMod: true, withCmdMod: true } ); +//#endregion + +//#region Hide + +registerQuickInputCommandAndKeybindingRule( + { + id: 'quickInput.hide', + primary: KeyCode.Escape, + handler: (accessor) => { + const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput; + currentQuickPick?.hide(); + } + }, + { withAltMod: true, withCtrlMod: true, withCmdMod: true } +); + +//#endregion + //#region Toggle Hover registerQuickPickCommandAndKeybindingRule( diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 753916ab08e..b2f02a75560 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -307,23 +307,16 @@ export class QuickInputController extends Disposable { this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { inputBox.setFocus(); })); - // TODO: Turn into commands instead of handling KEY_DOWN - // Keybindings for the quickinput widget as a whole this._register(dom.addStandardDisposableListener(container, dom.EventType.KEY_DOWN, (event) => { if (dom.isAncestor(event.target, widget)) { return; // Ignore event if target is inside widget to allow the widget to handle the event. } switch (event.keyCode) { - case KeyCode.Enter: - dom.EventHelper.stop(event, true); - if (this.enabled) { - this.onDidAcceptEmitter.fire(); - } - break; - case KeyCode.Escape: - dom.EventHelper.stop(event, true); - this.hide(QuickInputHideReason.Gesture); - break; + // All this does is keep focus in the quick pick as you are tabbing... so the idea + // is that you TAB, and when you reach the last thing that you can tab to in the quick pick + // it will wrap around to the top of the quick pick. + // TODO: Do we really need this? Navigating the quick pick via tab isn't really something + // to do often... and if so, it's rarely gonna go off screen. case KeyCode.Tab: if (!event.altKey && !event.ctrlKey && !event.metaKey) { // detect only visible actions diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index ca22b2b7c7d..4b6f0e42122 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -760,6 +760,11 @@ export interface IInputBox extends IQuickInput { * Severity of the input validation message. */ severity: Severity; + + /** + * Programmatically accepts an item. Used internally for keyboard navigation. + */ + accept(): void; } export enum QuickInputButtonLocation { From fe9ca3317e41b1a99b53a7ee109905e19cd517b8 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:35:35 -0700 Subject: [PATCH 0182/4355] Set `Cross-Origin-Resource-Policy` on webview resource responses Fixes #239381 Because these requests are all served by the service worker, only the webview itself should be able to make them. Embedding pages can try accessing these resources but it won't go through the service worker --- .../webview/browser/pre/service-worker.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 5c9b32bcc88..a62111bf6ed 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -286,10 +286,20 @@ async function processResourceRequest( return requestTimeout(); } + /** @type {Record} */ + const accessControlHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Cross-Origin-Resource-Policy': 'cross-origin', + }; + const entry = result.value; if (entry.status === 304) { // Not modified if (cachedResponse) { - return cachedResponse.clone(); + const r = cachedResponse.clone(); + for (const [key, value] of Object.entries(accessControlHeaders)) { + r.headers.set(key, value); + } + return r; } else { throw new Error('No cache found'); } @@ -303,11 +313,6 @@ async function processResourceRequest( return notFound(); } - /** @type {Record} */ - const commonHeaders = { - 'Access-Control-Allow-Origin': '*', - }; - const byteLength = entry.data.byteLength; const range = event.request.headers.get('range'); @@ -323,7 +328,7 @@ async function processResourceRequest( return new Response(entry.data.slice(start, end + 1), { status: 206, headers: { - ...commonHeaders, + ...accessControlHeaders, 'Content-range': `bytes 0-${end}/${byteLength}`, } }); @@ -332,7 +337,7 @@ async function processResourceRequest( return new Response(null, { status: 416, headers: { - ...commonHeaders, + ...accessControlHeaders, 'Content-range': `*/${byteLength}` } }); @@ -341,7 +346,7 @@ async function processResourceRequest( /** @type {Record} */ const headers = { - ...commonHeaders, + ...accessControlHeaders, 'Content-Type': entry.mime, 'Content-Length': byteLength.toString(), }; From 2af3da81d6bcde1ed0a5f9d5ae2d29d03ead7542 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:57:38 -0700 Subject: [PATCH 0183/4355] Add eslint rule to ban using dompurify directly All callers in our codebase should use `domSanitize` instead Also adding myself as a codeowner here to make sure I'm alerted to changes in domSanitize since they need more consideration --- .github/CODEOWNERS | 1 + eslint.config.js | 11 +++++++++++ src/vs/base/browser/domSanitize.ts | 1 + 3 files changed, 13 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dd7ea862b2c..ad5317637ff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,6 +25,7 @@ src/cli.ts @bpasero @deepak1556 src/main.ts @bpasero @deepak1556 src/server-cli.ts @bpasero @deepak1556 src/server-main.ts @bpasero @deepak1556 +src/vs/base/browser/domSanitize.ts @mjbvz src/vs/base/parts/sandbox/** @bpasero @deepak1556 src/vs/base/parts/storage/** @bpasero @deepak1556 src/vs/platform/backup/** @bpasero diff --git a/eslint.config.js b/eslint.config.js index d7b29f29cc0..7d9f93f5f32 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -763,6 +763,17 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'no-restricted-imports': [ + 'warn', + { + 'patterns': [ + { + 'group': ['dompurify*'], + 'message': 'Use domSanitize instead of dompurify directly' + }, + ] + } + ], 'local/code-import-patterns': [ 'warn', { diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 887d5a30c7b..9db24ef362f 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -5,6 +5,7 @@ import { Schemas } from '../common/network.js'; import { reset } from './dom.js'; +// eslint-disable-next-line no-restricted-imports import dompurify from './dompurify/dompurify.js'; /** From 4844cab9792848cc4bb96a4a6ef52813d563384e Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:19:35 -0700 Subject: [PATCH 0184/4355] Just remove the Tab handler (#266110) So, all this is trying to do is to see if the TAB/SHIFT+TAB will leave the quick pick (which will then dismiss the quick pick in some cases) by finding the first and last tabbable element and if that's the active element, then wrap around. I really think that removing this is not going to affect any folks that much and so I want to try removing it. In doing so this fixes a couple of long standing issues. Fixes https://github.com/microsoft/vscode/issues/258430 Fixes https://github.com/microsoft/vscode/issues/98479 --- .../browser/quickInputController.ts | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index b2f02a75560..701a4d66f24 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -12,7 +12,6 @@ import { CountBadge } from '../../../base/browser/ui/countBadge/countBadge.js'; import { ProgressBar } from '../../../base/browser/ui/progressbar/progressbar.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../base/common/event.js'; -import { KeyCode } from '../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, dispose } from '../../../base/common/lifecycle.js'; import Severity from '../../../base/common/severity.js'; import { isString } from '../../../base/common/types.js'; @@ -307,58 +306,6 @@ export class QuickInputController extends Disposable { this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { inputBox.setFocus(); })); - this._register(dom.addStandardDisposableListener(container, dom.EventType.KEY_DOWN, (event) => { - if (dom.isAncestor(event.target, widget)) { - return; // Ignore event if target is inside widget to allow the widget to handle the event. - } - switch (event.keyCode) { - // All this does is keep focus in the quick pick as you are tabbing... so the idea - // is that you TAB, and when you reach the last thing that you can tab to in the quick pick - // it will wrap around to the top of the quick pick. - // TODO: Do we really need this? Navigating the quick pick via tab isn't really something - // to do often... and if so, it's rarely gonna go off screen. - case KeyCode.Tab: - if (!event.altKey && !event.ctrlKey && !event.metaKey) { - // detect only visible actions - const selectors = [ - '.quick-input-list .monaco-action-bar .always-visible', - '.quick-input-list-entry:hover .monaco-action-bar', - '.monaco-list-row.focused .monaco-action-bar' - ]; - - if (container.classList.contains('show-checkboxes')) { - selectors.push('input'); - } else { - selectors.push('input[type=text]'); - } - if (this.getUI().list.displayed) { - selectors.push('.monaco-list'); - } - // focus links if there are any - if (this.getUI().message) { - selectors.push('.quick-input-message a'); - } - - if (this.getUI().widget) { - if (dom.isAncestor(event.target, this.getUI().widget)) { - // let the widget control tab - break; - } - selectors.push('.quick-input-html-widget'); - } - const stops = container.querySelectorAll(selectors.join(', ')); - if (!event.shiftKey && dom.isAncestor(event.target, stops[stops.length - 1])) { - dom.EventHelper.stop(event, true); - stops[0].focus(); - } - if (event.shiftKey && dom.isAncestor(event.target, stops[0])) { - dom.EventHelper.stop(event, true); - stops[stops.length - 1].focus(); - } - } - break; - } - })); // Drag and Drop support this.dndController = this._register(this.instantiationService.createInstance( From d433001cbafff6c46299e6f92a50e6a85607b112 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:52:01 -0700 Subject: [PATCH 0185/4355] Switch a few more noEmit checks to use tsgo Fixes one compile error for tsgo too and bumps versions --- package-lock.json | 70 +++++++++++------------ package.json | 6 +- src/vs/workbench/api/common/extHostMcp.ts | 2 +- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9ad5bb61c3..566b47bd50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250905", + "typescript": "^6.0.0-dev.20250910", "typescript-eslint": "^8.39.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -2469,9 +2469,9 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-IzPzhumNsWsIg4Kmt0y+0b2BBtsvD17rDmKj78yNeU3AsuA6xignQ5eDkFtRmLdGPVZwa8Yg5zPcJRFln98Ocw==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-d4i1kHxvWfYDGIjah8PHkBdY+pts4jzEJ2g0xqqW6oz3eDmuwx3aeDwmcEXgqw/ClDLtdy0t3NjFSE4ysqGO1g==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2481,19 +2481,19 @@ "node": ">=20.6.0" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250904.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250904.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20250904.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250904.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20250904.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250904.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20250904.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250910.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250910.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20250910.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250910.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20250910.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250910.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20250910.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-1rt4DhERW1VM4OwWYVIrCp1k1S4kpZAxzbCnprNinVJInhHexY2K0FFD9IGXKWSRANHg/OmJRQYTEoDKM6pqNw==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-PcSdX+LDNSAsBCPcbLx01sE8kyNv48WAGlxtRptrXvgnY61O64FzqCr755pteGIGrEEEQ9qsmm5b68EdyMbqJQ==", "cpu": [ "arm64" ], @@ -2508,9 +2508,9 @@ } }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-d2DMQnsXAkZDDk9bU/FhY/D74tbMAkboIGb+hq7kIIgOVcxOswhwLFZ/ajW/9NTesktz8Z14t40Ber+/Pny25A==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-iz4T7SmwoLczNxfIlEfb3WQSD/cpcybT27s0S/HOAJbrZRRDRT/RvNWLT9PI0aEVM7EGxHrPOExjWGErItGP/w==", "cpu": [ "x64" ], @@ -2525,9 +2525,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-YyfTK1SGmfeDJv6G3vSmVxjM914Xio7O57NzRKOyEQnmBT5tdXTzeWgkjrUh1jE8wCUu0f0ZZ+xDTwgys+E2ug==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-LfxNhRNuxBjfGWsOn/aQ3j76E+ZyyvMcQOVYYyr/+5BG/2bCtKtKtLI+xZzxeaPL6yz3a+TTdO1RtT79z7IKGw==", "cpu": [ "arm" ], @@ -2542,9 +2542,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-+fv13RDSk+7wFYY846q5ig7X6G07JT7wbajk6p4rELXTIfS1c6gRHGhODETCfFVaPziP4IlvqyinNP8F8wc9uQ==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-ezy44eOONVCTKBbMNuswVXQJDH96vZz1UttTzCWoLjnxa5YLPoSxtdXLzqLDXFm9mznFkO55dq5brFc0yEcytQ==", "cpu": [ "arm64" ], @@ -2559,9 +2559,9 @@ } }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-BjWJI42cUUilIyQHZpQQeSjC/Ifj/UaIf4oj6lRHDcg5qgLHWe5bAUxuNjE6i7wi+TTN9YxUvBDkMAcm/hI8wg==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-pBmxFR9+C74QGzhDS8PRRBswQcnVFp2XD2J6oFfDcMmtsq8gFIlggAx3+k3fLysoH5gJDISsgjZr43IIcncpzQ==", "cpu": [ "x64" ], @@ -2576,9 +2576,9 @@ } }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-rPv/mVaneZTuFESk/zDg3dFiZjpdipVMcLaF10Ns1fIyWdZ0ja79Ufm1eCFbk8KFNEX2dEx+vFEvD9n4bhEneg==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-qCJYqZ/utGR9OPGx0871Ov1djUZ9MAFtGKrtbgUwrwjfXLLGZHUDOmI9brfYQ2ruCxTKENk6IfWIAtY+zV6C2A==", "cpu": [ "arm64" ], @@ -2593,9 +2593,9 @@ } }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20250904.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250904.1.tgz", - "integrity": "sha512-+twwqKYEv5UdZX5FRaBo0bDQgw/uPQjU3hqaqaO0Dhp1Ou8Ce4oi5hgwauB1j29JwBbvOi9/yoEcjsjT2Wsaxw==", + "version": "7.0.0-dev.20250910.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250910.1.tgz", + "integrity": "sha512-RPjV+ZypHeSz1Ky6wys52tXTYpYQ4fAqovC5ZZC+j+Lf8gEEcWXcmuJMEBN6vhTCvx1c6QdRtBBIYRrUHh/JEA==", "cpu": [ "x64" ], @@ -17267,9 +17267,9 @@ "dev": true }, "node_modules/typescript": { - "version": "6.0.0-dev.20250905", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20250905.tgz", - "integrity": "sha512-3DUoON5ibUBadEPyd3Ot2K0S9c3R33DEguXdVKqfL+4tYKwHa5v3OIBdkqP0KQFRS7gqA+hhwPgdBHxFb0LSgQ==", + "version": "6.0.0-dev.20250910", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20250910.tgz", + "integrity": "sha512-c8xZmVV/0FVlBoiRveyYSSs2jL0AHRZ3m2fUPgpY9ZFh7edYiWlBsZ7zPZIHzdNieuJ5ryYENw4pbRasp2Fo/w==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 2e2633f90d5..4800d980002 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,11 @@ "smoketest-no-compile": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "download-builtin-extensions-cg": "node build/lib/builtInExtensionsCG.js", - "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", + "monaco-compile-check": "tsgo --project src/tsconfig.monaco.json --noEmit", "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json", "vscode-dts-compile-check": "tsgo --project src/tsconfig.vscode-dts.json && tsgo --project src/tsconfig.vscode-proposed-dts.json", "valid-layers-check": "node build/checker/layersChecker.js && tsgo --project build/checker/tsconfig.browser.json && tsgo --project build/checker/tsconfig.worker.json && tsgo --project build/checker/tsconfig.node.json && tsgo --project build/checker/tsconfig.electron-browser.json && tsgo --project build/checker/tsconfig.electron-main.json && tsgo --project build/checker/tsconfig.electron-utility.json", - "define-class-fields-check": "node build/lib/propertyInitOrderChecker.js && tsc -p src/tsconfig.defineClassFields.json", + "define-class-fields-check": "node build/lib/propertyInitOrderChecker.js && tsgo --project src/tsconfig.defineClassFields.json", "update-distro": "node build/npm/update-distro.mjs", "web": "echo 'npm run web' is replaced by './scripts/code-server' or './scripts/code-web'", "compile-cli": "gulp compile-cli", @@ -211,7 +211,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250905", + "typescript": "^6.0.0-dev.20250910", "typescript-eslint": "^8.39.0", "util": "^0.12.4", "webpack": "^5.94.0", diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 4e98c8fa2e6..f57bcb3b327 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -768,7 +768,7 @@ class McpHTTPHandle extends Disposable { interface MinimalRequestInit { method: string; headers: Record; - body?: Uint8Array; + body?: Uint8Array; } function isJSON(str: string): boolean { From e58a27c792813e0dcd7c84dd88cc2b88c3c12245 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:33:36 -0700 Subject: [PATCH 0186/4355] Try different type workaround --- src/vs/workbench/api/common/extHostMcp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index f57bcb3b327..7da17eb2fd3 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -241,7 +241,7 @@ class McpHTTPHandle extends Disposable { * 3. If the response body is empty, JSON, or a JSON stream, handle it appropriately. */ private async _sendStreamableHttp(message: string, sessionId: string | undefined) { - const asBytes = new TextEncoder().encode(message); + const asBytes = new TextEncoder().encode(message) as Uint8Array; const headers: Record = { ...Object.fromEntries(this._launch.headers), 'Content-Type': 'application/json', @@ -616,7 +616,7 @@ class McpHTTPHandle extends Disposable { * is otherwise received in {@link _attachSSE}'s loop. */ private async _sendLegacySSE(url: string, message: string) { - const asBytes = new TextEncoder().encode(message); + const asBytes = new TextEncoder().encode(message) as Uint8Array; const headers: Record = { ...Object.fromEntries(this._launch.headers), 'Content-Type': 'application/json', @@ -768,7 +768,7 @@ class McpHTTPHandle extends Disposable { interface MinimalRequestInit { method: string; headers: Record; - body?: Uint8Array; + body?: Uint8Array; } function isJSON(str: string): boolean { From c7e7a779e896b5e48861ce3d5f1120dada5259c3 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:22:39 -0700 Subject: [PATCH 0187/4355] Add basic mermaid rendering support in core For #257761 Ports over extension sample + a few improvements to core --- .eslint-ignore | 1 + build/gulpfile.extensions.js | 1 + build/lib/extensions.ts | 3 +- build/npm/dirs.js | 1 + eslint.config.js | 5 + extensions/mermaid-chat-features/.gitignore | 1 + extensions/mermaid-chat-features/.npmrc | 2 + .../mermaid-chat-features/.vscodeignore | 8 + extensions/mermaid-chat-features/README.md | 5 + .../mermaid-chat-features/cgmanifest.json | 4 + .../chat-webview-src/index.ts | 73 + .../chat-webview-src/tsconfig.json | 12 + .../esbuild-chat-webview.mjs | 18 + .../extension-browser.webpack.config.js | 13 + .../extension.webpack.config.js | 16 + .../mermaid-chat-features/package-lock.json | 1857 +++++++++++++++++ extensions/mermaid-chat-features/package.json | 75 + .../mermaid-chat-features/package.nls.json | 6 + .../mermaid-chat-features/src/extension.ts | 244 +++ .../mermaid-chat-features/tsconfig.json | 15 + extensions/simple-browser/package.json | 6 +- 21 files changed, 2362 insertions(+), 4 deletions(-) create mode 100644 extensions/mermaid-chat-features/.gitignore create mode 100644 extensions/mermaid-chat-features/.npmrc create mode 100644 extensions/mermaid-chat-features/.vscodeignore create mode 100644 extensions/mermaid-chat-features/README.md create mode 100644 extensions/mermaid-chat-features/cgmanifest.json create mode 100644 extensions/mermaid-chat-features/chat-webview-src/index.ts create mode 100644 extensions/mermaid-chat-features/chat-webview-src/tsconfig.json create mode 100644 extensions/mermaid-chat-features/esbuild-chat-webview.mjs create mode 100644 extensions/mermaid-chat-features/extension-browser.webpack.config.js create mode 100644 extensions/mermaid-chat-features/extension.webpack.config.js create mode 100644 extensions/mermaid-chat-features/package-lock.json create mode 100644 extensions/mermaid-chat-features/package.json create mode 100644 extensions/mermaid-chat-features/package.nls.json create mode 100644 extensions/mermaid-chat-features/src/extension.ts create mode 100644 extensions/mermaid-chat-features/tsconfig.json diff --git a/.eslint-ignore b/.eslint-ignore index e493198185e..6f55ce40f69 100644 --- a/.eslint-ignore +++ b/.eslint-ignore @@ -10,6 +10,7 @@ **/extensions/markdown-language-features/media/** **/extensions/markdown-language-features/notebook-out/** **/extensions/markdown-math/notebook-out/** +**/extensions/mermaid-chat-features/chat-webview-out/** **/extensions/notebook-renderers/renderer-out/index.js **/extensions/simple-browser/media/index.js **/extensions/terminal-suggest/src/completions/upstream/** diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 687bd16fd8c..7826f48490b 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -51,6 +51,7 @@ const compilations = [ 'extensions/markdown-math/tsconfig.json', 'extensions/media-preview/tsconfig.json', 'extensions/merge-conflict/tsconfig.json', + 'extensions/mermaid-chat-features/tsconfig.json', 'extensions/terminal-suggest/tsconfig.json', 'extensions/microsoft-authentication/tsconfig.json', 'extensions/notebook-renderers/tsconfig.json', diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index b997fe4046e..9e7cf9f954a 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -559,11 +559,12 @@ const extensionsPath = path.join(root, 'extensions'); // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ + 'ipynb/esbuild.mjs', 'markdown-language-features/esbuild-notebook.mjs', 'markdown-language-features/esbuild-preview.mjs', 'markdown-math/esbuild.mjs', + 'mermaid-chat-features/esbuild-chat-webview.mjs', 'notebook-renderers/esbuild.mjs', - 'ipynb/esbuild.mjs', 'simple-browser/esbuild-preview.mjs', ]; diff --git a/build/npm/dirs.js b/build/npm/dirs.js index a7ee7c3a6c7..33c18196dd4 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -33,6 +33,7 @@ const dirs = [ 'extensions/markdown-math', 'extensions/media-preview', 'extensions/merge-conflict', + 'extensions/mermaid-chat-features', 'extensions/microsoft-authentication', 'extensions/notebook-renderers', 'extensions/npm', diff --git a/eslint.config.js b/eslint.config.js index d7b29f29cc0..0435f80cce3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1410,6 +1410,7 @@ export default tseslint.config( { files: [ 'extensions/markdown-language-features/**/*.ts', + 'extensions/mermaid-chat-features/**/*.ts', 'extensions/media-preview/**/*.ts', 'extensions/simple-browser/**/*.ts', 'extensions/typescript-language-features/**/*.ts', @@ -1430,6 +1431,10 @@ export default tseslint.config( 'extensions/simple-browser/tsconfig.json', 'extensions/simple-browser/preview-src/tsconfig.json', + // Mermaid chat features + 'extensions/mermaid-chat-features/tsconfig.json', + 'extensions/mermaid-chat-features/chat-webview-src/tsconfig.json', + // TypeScript 'extensions/typescript-language-features/tsconfig.json', 'extensions/typescript-language-features/web/tsconfig.json', diff --git a/extensions/mermaid-chat-features/.gitignore b/extensions/mermaid-chat-features/.gitignore new file mode 100644 index 00000000000..2877bd189bb --- /dev/null +++ b/extensions/mermaid-chat-features/.gitignore @@ -0,0 +1 @@ +chat-webview-out diff --git a/extensions/mermaid-chat-features/.npmrc b/extensions/mermaid-chat-features/.npmrc new file mode 100644 index 00000000000..a9c57709666 --- /dev/null +++ b/extensions/mermaid-chat-features/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 diff --git a/extensions/mermaid-chat-features/.vscodeignore b/extensions/mermaid-chat-features/.vscodeignore new file mode 100644 index 00000000000..4722e586990 --- /dev/null +++ b/extensions/mermaid-chat-features/.vscodeignore @@ -0,0 +1,8 @@ +src/** +extension.webpack.config.js +esbuild.* +cgmanifest.json +package-lock.json +webpack.config.js +tsconfig*.json +.gitignore diff --git a/extensions/mermaid-chat-features/README.md b/extensions/mermaid-chat-features/README.md new file mode 100644 index 00000000000..09a73bc1b1a --- /dev/null +++ b/extensions/mermaid-chat-features/README.md @@ -0,0 +1,5 @@ +# Markdown Math + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +Adds math rendering using [KaTeX](https://katex.org) to VS Code's built-in markdown preview and markdown cells in notebooks. diff --git a/extensions/mermaid-chat-features/cgmanifest.json b/extensions/mermaid-chat-features/cgmanifest.json new file mode 100644 index 00000000000..0c39c97297b --- /dev/null +++ b/extensions/mermaid-chat-features/cgmanifest.json @@ -0,0 +1,4 @@ +{ + "registrations": [], + "version": 1 +} diff --git a/extensions/mermaid-chat-features/chat-webview-src/index.ts b/extensions/mermaid-chat-features/chat-webview-src/index.ts new file mode 100644 index 00000000000..9b3c9df71b6 --- /dev/null +++ b/extensions/mermaid-chat-features/chat-webview-src/index.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import mermaid, { MermaidConfig } from 'mermaid'; + +function getMermaidTheme() { + return document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast') + ? 'dark' + : 'default'; +} + +type State = { + readonly diagramText: string; + readonly theme: 'dark' | 'default'; +}; + +let state: State | undefined = undefined; + +function init() { + const diagram = document.querySelector('.mermaid'); + if (!diagram) { + return; + } + + const theme = getMermaidTheme(); + state = { + diagramText: diagram.textContent ?? '', + theme + }; + + const config: MermaidConfig = { + startOnLoad: true, + theme, + }; + mermaid.initialize(config); +} + +function tryUpdate() { + const newTheme = getMermaidTheme(); + if (state?.theme === newTheme) { + return; + } + + const diagramNode = document.querySelector('.mermaid'); + if (!diagramNode || !(diagramNode instanceof HTMLElement)) { + return; + } + + state = { + diagramText: state?.diagramText ?? '', + theme: newTheme + }; + + // Re-render + diagramNode.textContent = state?.diagramText ?? ''; + delete diagramNode.dataset.processed; + + mermaid.initialize({ + theme: newTheme, + }); + mermaid.run({ + nodes: [diagramNode] + }); +} + +// Update when theme changes +new MutationObserver(() => { + tryUpdate(); +}).observe(document.body, { attributes: true, attributeFilter: ['class'] }); + +init(); + diff --git a/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json new file mode 100644 index 00000000000..72282fb0c7d --- /dev/null +++ b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/", + "jsx": "react", + "lib": [ + "ES2024", + "DOM", + "DOM.Iterable" + ] + } +} diff --git a/extensions/mermaid-chat-features/esbuild-chat-webview.mjs b/extensions/mermaid-chat-features/esbuild-chat-webview.mjs new file mode 100644 index 00000000000..b23de5746fa --- /dev/null +++ b/extensions/mermaid-chat-features/esbuild-chat-webview.mjs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'chat-webview-src'); +const outDir = path.join(import.meta.dirname, 'chat-webview-out'); + +run({ + entryPoints: [ + path.join(srcDir, 'index.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/mermaid-chat-features/extension-browser.webpack.config.js b/extensions/mermaid-chat-features/extension-browser.webpack.config.js new file mode 100644 index 00000000000..b758f2d8155 --- /dev/null +++ b/extensions/mermaid-chat-features/extension-browser.webpack.config.js @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; + +export default withBrowserDefaults({ + context: import.meta.dirname, + entry: { + extension: './src/extension.ts' + } +}); diff --git a/extensions/mermaid-chat-features/extension.webpack.config.js b/extensions/mermaid-chat-features/extension.webpack.config.js new file mode 100644 index 00000000000..4928186ae55 --- /dev/null +++ b/extensions/mermaid-chat-features/extension.webpack.config.js @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; + +export default withDefaults({ + context: import.meta.dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json new file mode 100644 index 00000000000..a9de7747f05 --- /dev/null +++ b/extensions/mermaid-chat-features/package-lock.json @@ -0,0 +1,1857 @@ +{ + "name": "marmaid-chat-features", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "marmaid-chat-features", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "dompurify": "^3.2.6", + "jsdom": "^26.1.0", + "mermaid": "^11.11.0" + }, + "devDependencies": { + "@types/jsdom": "^21.1.7", + "@types/node": "^22" + }, + "engines": { + "vscode": "^1.104.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.0.tgz", + "integrity": "sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.1.tgz", + "integrity": "sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", + "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", + "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mermaid": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.11.0.tgz", + "integrity": "sha512-9lb/VNkZqWTRjVgCV+l1N+t4kyi94y+l5xrmBmbbxZYkfRl5hEDaTPMOcaWKCl1McG8nBEaMlWwkcAEEgjhBgg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.4", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.2", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.13", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.7", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "license": "MIT" + }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + } + } +} diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json new file mode 100644 index 00000000000..1276397b807 --- /dev/null +++ b/extensions/mermaid-chat-features/package.json @@ -0,0 +1,75 @@ +{ + "name": "marmaid-chat-features", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + }, + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", + "engines": { + "vscode": "^1.104.0" + }, + "enabledApiProposals": [ + "chatOutputRenderer" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "main": "./out/extension", + "browser": "./dist/browser/extension", + "activationEvents": [], + "contributes": { + "chatOutputRenderers": [ + { + "viewType": "vscode.chatMermaidDiagram", + "mimeTypes": [ + "text/vnd.mermaid" + ] + } + ], + "languageModelTools": [ + { + "name": "renderMermaidDiagram", + "displayName": "Mermaid Renderer", + "toolReferenceName": "renderMermaidDiagram", + "canBeReferencedInPrompt": true, + "modelDescription": "Renders a Mermaid diagram from Mermaid.js markup.", + "userDescription": "Render a Mermaid.js diagrams from markup.", + "inputSchema": { + "type": "object", + "properties": { + "markup": { + "type": "string", + "description": "The mermaid diagram markup to render as a Mermaid diagram. This should only be the markup of the diagram. Do not include a wrapping code block." + } + } + } + } + ] + }, + "scripts": { + "compile": "gulp compile-extension:mermaid-chat-features && npm run build-chat-webview", + "watch": "npm run build-chat-webview && gulp watch-extension:mermaid-chat-features", + "vscode:prepublish": "npm run build-ext && npm run build-chat-webview", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:mermaid-chat-features ./tsconfig.json", + "build-chat-webview": "node ./esbuild-chat-webview.mjs", + "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", + "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + }, + "devDependencies": { + "@types/jsdom": "^21.1.7", + "@types/node": "^22" + }, + "dependencies": { + "dompurify": "^3.2.6", + "jsdom": "^26.1.0", + "mermaid": "^11.11.0" + } +} diff --git a/extensions/mermaid-chat-features/package.nls.json b/extensions/mermaid-chat-features/package.nls.json new file mode 100644 index 00000000000..8e95dac52bb --- /dev/null +++ b/extensions/mermaid-chat-features/package.nls.json @@ -0,0 +1,6 @@ +{ + "displayName": "Markdown Math", + "description": "Adds math support to Markdown in notebooks.", + "config.markdown.math.enabled": "Enable/disable rendering math in the built-in Markdown preview.", + "config.markdown.math.macros": "A collection of custom macros. Each macro is a key-value pair where the key is a new command name and the value is the expansion of the macro." +} diff --git a/extensions/mermaid-chat-features/src/extension.ts b/extensions/mermaid-chat-features/src/extension.ts new file mode 100644 index 00000000000..f52d203f078 --- /dev/null +++ b/extensions/mermaid-chat-features/src/extension.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import DOMPurify from 'dompurify'; +import { JSDOM } from 'jsdom'; +import * as vscode from 'vscode'; + +/** + * View type that uniquely identifies the Mermaid chat output renderer. + */ +const viewType = 'vscode.chatMermaidDiagram'; + +/** + * Mime type used to identify Mermaid diagram data in chat output. + */ +const mime = 'text/vnd.mermaid'; + +const maxFixAttempts = 3; + +export function activate(context: vscode.ExtensionContext) { + + // Register tools + context.subscriptions.push( + vscode.lm.registerTool<{ markup: string }>('renderMermaidDiagram', { + invoke: async (options, token) => { + let sourceCode = options.input.markup; + sourceCode = await runMermaidMarkupFixLoop(sourceCode, token); + return writeMermaidToolOutput(sourceCode); + }, + }) + ); + + // Register the chat output renderer for Mermaid diagrams. + // This will be invoked with the data generated by the tools. + // It can also be invoked when rendering old Mermaid diagrams in the chat history. + context.subscriptions.push( + vscode.chat.registerChatOutputRenderer(viewType, { + async renderChatOutput({ value }, webview, _ctx, _token) { + const mermaidSource = new TextDecoder().decode(value); + + // Set the options for the webview + const mediaRoot = vscode.Uri.joinPath(context.extensionUri, 'chat-webview-out'); + webview.options = { + enableScripts: true, + localResourceRoots: [mediaRoot], + }; + + // Set the HTML content for the webview + const nonce = getNonce(); + const mermaidScript = vscode.Uri.joinPath(mediaRoot, 'index.js'); + + webview.html = ` + + + + + + + Mermaid Diagram + + + + +
+							${escapeHtmlText(mermaidSource)}
+						
+ + + + `; + }, + })); +} + +/** + * Lazily load mermaid + */ +const getMermaidInstance = (() => { + const createMermaidInstance = async () => { + // Patch the global window object for mermaid + + const { window } = new JSDOM(''); + (global as any).window = window; + (global as any).DOMPurify = DOMPurify(window); + return import('mermaid'); + }; + + let cached: Promise | undefined; + return async (): Promise => { + cached ??= createMermaidInstance(); + return (await cached).default; + }; +})(); + +/** + * Tries to fix mermaid syntax errors in a set number of attempts. + * + * @returns The best effort to fix the Mermaid markup. + */ +async function runMermaidMarkupFixLoop(sourceCode: string, token: vscode.CancellationToken): Promise { + let attempt = 0; + while (attempt < maxFixAttempts) { + const result = await validateMermaidMarkup(sourceCode); + if (token.isCancellationRequested) { + throw new Error('Operation cancelled'); + } + + if (result.type === 'success') { + return sourceCode; + } + + attempt++; + + sourceCode = await tryFixingUpMermaidMarkup(sourceCode, result.message, token); + if (token.isCancellationRequested) { + throw new Error('Operation cancelled'); + } + } + + // Return whatever we have after max attempts + return sourceCode; +} + +/** + * Validates the syntax of the provided Mermaid markup. + */ +async function validateMermaidMarkup(sourceCode: string): Promise<{ type: 'success' } | { type: 'error'; message: string }> { + try { + const mermaid = await getMermaidInstance(); + await mermaid.parse(sourceCode); + return { type: 'success' }; + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + + return { type: 'error', message: error.message }; + } +} + +/** + * Uses a language model to try to fix Mermaid markup based on an error message. + */ +async function tryFixingUpMermaidMarkup(sourceCode: string, errorMessage: string, token: vscode.CancellationToken): Promise { + const model = await getPreferredLm(); + if (!model) { + console.warn('No suitable model found for fixing Mermaid markup'); + return sourceCode; + } + + if (token.isCancellationRequested) { + throw new Error('Operation cancelled'); + } + + const completion = await model.sendRequest([ + vscode.LanguageModelChatMessage.Assistant(joinLines( + `The user will provide you with the source code for the Mermaid diagram and an error message.`, + `Your task is to fix the Mermaid source code based on the error message.`, + `Please return the fixed Mermaid source code inside a \`mermaid\` fenced code block. Do not add any comments or explanation.`, + `Make sure to return the entire source code.` + )), + vscode.LanguageModelChatMessage.User(joinLines( + `Here is my Mermaid source code:`, + ``, + `\`\`\`mermaid`, + `${sourceCode}`, + `\`\`\``, + ``, + `And here is the mermaid error message:`, + ``, + errorMessage, + )), + ], {}, token); + + return await parseMermaidMarkupFromChatResponse(completion, token) ?? sourceCode; +} + +async function parseMermaidMarkupFromChatResponse(chatResponse: vscode.LanguageModelChatResponse, token: vscode.CancellationToken): Promise { + const parts: string[] = []; + for await (const line of chatResponse.text) { + if (token.isCancellationRequested) { + throw new Error('Operation cancelled'); + } + + parts.push(line); + } + + const response = parts.join(''); + const lines = response.split('\n'); + if (!lines.at(0)?.startsWith('```') || !lines.at(-1)?.endsWith('```')) { + console.warn('Invalid response format from model, expected fenced code block'); + return undefined; + } + + return lines.slice(1, -1).join('\n').trim(); +} + + +async function getPreferredLm(): Promise { + return (await vscode.lm.selectChatModels({ family: 'gpt-4o-mini' })).at(0) + ?? (await vscode.lm.selectChatModels({ family: 'gpt-4o' })).at(0) + ?? (await vscode.lm.selectChatModels({})).at(0); +} + +function writeMermaidToolOutput(sourceCode: string): vscode.LanguageModelToolResult { + // Expose the source code as a tool result for the LM + const result = new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(sourceCode) + ]); + + // And store custom data in the tool result details to indicate that a custom renderer should be used for it. + // In this case we just store the source code as binary data. + + // Add cast to use proposed API + (result as vscode.ExtendedLanguageModelToolResult2).toolResultDetails2 = { + mime, + value: new TextEncoder().encode(sourceCode), + }; + + return result; +} + +function joinLines(...lines: string[]): string { + return lines.join('\n'); +} + +function escapeHtmlText(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 64; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/extensions/mermaid-chat-features/tsconfig.json b/extensions/mermaid-chat-features/tsconfig.json new file mode 100644 index 00000000000..daa4f6357b3 --- /dev/null +++ b/extensions/mermaid-chat-features/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "types": [] + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.chatOutputRenderer.d.ts", + "../../src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts", + "../../src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts", + "../../src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts" + ] +} diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 789de38deb3..5e081c4bbd2 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -57,10 +57,10 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", + "compile": "gulp compile-extension:simple-browser && npm run build-preview", + "watch": "npm run build-preview && gulp watch-extension:simple-browser", "vscode:prepublish": "npm run build-ext && npm run build-preview", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:simple-browser ./tsconfig.json", "build-preview": "node ./esbuild-preview.mjs", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" From 2e32e7fc92032f48ef9b39de8dd11e74ef56d027 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:24:08 -0700 Subject: [PATCH 0188/4355] Update strings --- extensions/mermaid-chat-features/package.nls.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/mermaid-chat-features/package.nls.json b/extensions/mermaid-chat-features/package.nls.json index 8e95dac52bb..882e5d59f01 100644 --- a/extensions/mermaid-chat-features/package.nls.json +++ b/extensions/mermaid-chat-features/package.nls.json @@ -1,6 +1,4 @@ { - "displayName": "Markdown Math", - "description": "Adds math support to Markdown in notebooks.", - "config.markdown.math.enabled": "Enable/disable rendering math in the built-in Markdown preview.", - "config.markdown.math.macros": "A collection of custom macros. Each macro is a key-value pair where the key is a new command name and the value is the expansion of the macro." + "displayName": "Mermaid Chat Features", + "description": "Adds Mermaid diagram support to built-in chats." } From b07db7a40a428535ffba8422f86d3a21d2138ccd Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:31:55 -0700 Subject: [PATCH 0189/4355] Update build dir --- build/lib/extensions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/lib/extensions.js b/build/lib/extensions.js index aeea722d372..c80a1be1a84 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -510,11 +510,12 @@ function translatePackageJSON(packageJSON, packageNLSPath) { const extensionsPath = path_1.default.join(root, 'extensions'); // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ + 'ipynb/esbuild.mjs', 'markdown-language-features/esbuild-notebook.mjs', 'markdown-language-features/esbuild-preview.mjs', 'markdown-math/esbuild.mjs', + 'mermaid-chat-features/esbuild-chat-webview.mjs', 'notebook-renderers/esbuild.mjs', - 'ipynb/esbuild.mjs', 'simple-browser/esbuild-preview.mjs', ]; async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { From 161ee8808d780a3dee707f8dcd47a06fc589bc0f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 11 Sep 2025 15:09:41 +1000 Subject: [PATCH 0190/4355] Additional Notebook layout options (#266129) --- .../contrib/notebook/browser/notebookBrowser.ts | 3 +++ .../browser/viewModel/codeCellViewModel.ts | 17 +++++++++++++---- .../browser/viewModel/markupCellViewModel.ts | 12 +++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 4c79dad2047..49beeb5e261 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -188,6 +188,9 @@ export interface CellLayoutInfo { readonly commentHeight: number; readonly bottomToolbarOffset: number; readonly totalHeight: number; + readonly topMargin: number; + readonly bottomMargin: number; + readonly outlineWidth: number; } export interface CellLayoutChangeEvent { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index a1ce3e95da4..dcbc2bf7d56 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -177,7 +177,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod })); this._outputCollection = new Array(this.model.outputs.length); - + const layoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); this._layoutInfo = { fontInfo: initialNotebookLayoutInfo?.fontInfo || null, editorHeight: 0, @@ -197,7 +197,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod outputIndicatorHeight: 0, bottomToolbarOffset: 0, layoutState: CellLayoutState.Uninitialized, - estimatedHasHorizontalScrolling: false + estimatedHasHorizontalScrolling: false, + outlineWidth: 1, + topMargin: layoutConfiguration.cellTopMargin, + bottomMargin: layoutConfiguration.cellBottomMargin, }; } @@ -296,7 +299,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod outputIndicatorHeight, bottomToolbarOffset, layoutState: newState, - estimatedHasHorizontalScrolling: hasHorizontalScrolling + estimatedHasHorizontalScrolling: hasHorizontalScrolling, + topMargin: notebookLayoutConfiguration.cellTopMargin, + bottomMargin: notebookLayoutConfiguration.cellBottomMargin, + outlineWidth: 1 }; } else { const codeIndicatorHeight = notebookLayoutConfiguration.collapsedIndicatorHeight; @@ -338,7 +344,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod outputIndicatorHeight, bottomToolbarOffset, layoutState: this._layoutInfo.layoutState, - estimatedHasHorizontalScrolling: false + estimatedHasHorizontalScrolling: false, + outlineWidth: 1, + topMargin: notebookLayoutConfiguration.cellTopMargin, + bottomMargin: notebookLayoutConfiguration.cellBottomMargin, }; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts index 875d0f18c49..39e40ab13f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts @@ -127,7 +127,7 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM super(viewType, model, UUID.generateUuid(), viewContext, configurationService, textModelService, undoRedoService, codeEditorService, inlineChatSessionService); const { bottomToolbarGap } = this.viewContext.notebookOptions.computeBottomToolbarDimensions(this.viewType); - + const layoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); this._layoutInfo = { chatHeight: 0, editorHeight: 0, @@ -142,7 +142,10 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM totalHeight: 100, layoutState: CellLayoutState.Uninitialized, foldHintHeight: 0, - statusBarHeight: 0 + statusBarHeight: 0, + outlineWidth: 1, + bottomMargin: layoutConfiguration.markdownCellBottomMargin, + topMargin: layoutConfiguration.markdownCellTopMargin, }; this._register(this.onDidChangeState(e => { @@ -230,8 +233,8 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM foldHintHeight = 0; } let commentOffset: number; + const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); if (this.getEditState() === CellEditState.Editing) { - const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); commentOffset = notebookLayoutConfiguration.editorToolbarHeight + notebookLayoutConfiguration.cellTopMargin // CELL_TOP_MARGIN + this._chatHeight @@ -261,6 +264,9 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM commentHeight: state.commentHeight ? this._commentHeight : this._layoutInfo.commentHeight, + outlineWidth: 1, + bottomMargin: notebookLayoutConfiguration.markdownCellBottomMargin, + topMargin: notebookLayoutConfiguration.markdownCellTopMargin, }; this._onDidChangeLayout.fire(state); From 14252bca54816e7054d06a386d820035edb0a615 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Wed, 10 Sep 2025 23:58:50 -0700 Subject: [PATCH 0191/4355] apple logo placement (#265947) moved apple logo placement uo by 2px in svg --- src/vs/workbench/contrib/chat/browser/media/apple-dark.svg | 4 ++-- src/vs/workbench/contrib/chat/browser/media/apple-light.svg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/apple-dark.svg b/src/vs/workbench/contrib/chat/browser/media/apple-dark.svg index f0eb483a99b..59346e8435b 100644 --- a/src/vs/workbench/contrib/chat/browser/media/apple-dark.svg +++ b/src/vs/workbench/contrib/chat/browser/media/apple-dark.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/vs/workbench/contrib/chat/browser/media/apple-light.svg b/src/vs/workbench/contrib/chat/browser/media/apple-light.svg index 8f911ea1ab5..a8ea3e2d9af 100644 --- a/src/vs/workbench/contrib/chat/browser/media/apple-light.svg +++ b/src/vs/workbench/contrib/chat/browser/media/apple-light.svg @@ -1,3 +1,3 @@ - - + + From 8e7ae9a3b52945523ad6364a13f0800b098f947f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:12:40 +0200 Subject: [PATCH 0192/4355] Engineering - mark test artifacts as non-production (#266146) * Engineering - disable SDL tasks for non-production artifacts * Engineering - set isProduction for test artifacts --- build/azure-pipelines/common/publish-artifact.yml | 4 ++++ build/azure-pipelines/darwin/product-build-darwin-test.yml | 3 +++ build/azure-pipelines/linux/product-build-linux-test.yml | 3 +++ build/azure-pipelines/win32/product-build-win32-test.yml | 3 +++ 4 files changed, 13 insertions(+) diff --git a/build/azure-pipelines/common/publish-artifact.yml b/build/azure-pipelines/common/publish-artifact.yml index ba4d9f13355..b18dc8d4c7f 100644 --- a/build/azure-pipelines/common/publish-artifact.yml +++ b/build/azure-pipelines/common/publish-artifact.yml @@ -18,6 +18,9 @@ parameters: - name: sbomPackageVersion type: string default: "" + - name: isProduction + type: boolean + default: true - name: condition type: string default: succeeded() @@ -77,6 +80,7 @@ steps: targetPath: ${{ parameters.targetPath }} artifactName: $(ARTIFACT_NAME) sbomEnabled: ${{ parameters.sbomEnabled }} + isProduction: ${{ parameters.isProduction }} ${{ if ne(parameters.sbomBuildDropPath, '') }}: sbomBuildDropPath: ${{ parameters.sbomBuildDropPath }} ${{ if ne(parameters.sbomPackageName, '') }}: diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index f2b5e697c4d..3d1dfdf8ea3 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -132,6 +132,7 @@ steps: ${{ else }}: artifactName: crash-dump-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Crash Reports" + isProduction: false sbomEnabled: false continueOnError: true condition: failed() @@ -146,6 +147,7 @@ steps: ${{ else }}: artifactName: node-modules-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Node Modules" + isProduction: false sbomEnabled: false continueOnError: true condition: failed() @@ -158,6 +160,7 @@ steps: ${{ else }}: artifactName: logs-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Log Files" + isProduction: false sbomEnabled: false continueOnError: true condition: succeededOrFailed() diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index e4dbfecd91b..4e882b78d25 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -147,6 +147,7 @@ steps: ${{ else }}: artifactName: crash-dump-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Crash Reports" + isProduction: false sbomEnabled: false continueOnError: true condition: failed() @@ -161,6 +162,7 @@ steps: ${{ else }}: artifactName: node-modules-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Node Modules" + isProduction: false sbomEnabled: false continueOnError: true condition: failed() @@ -173,6 +175,7 @@ steps: ${{ else }}: artifactName: logs-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) displayName: "Publish Log Files" + isProduction: false sbomEnabled: false continueOnError: true condition: succeededOrFailed() diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 154ddcf4485..7d5222e347f 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -149,6 +149,7 @@ steps: artifactName: crash-dump-windows-$(VSCODE_ARCH)-$(System.JobAttempt) ${{ else }}: artifactName: crash-dump-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) + isProduction: false sbomEnabled: false displayName: "Publish Crash Reports" continueOnError: true @@ -163,6 +164,7 @@ steps: artifactName: node-modules-windows-$(VSCODE_ARCH)-$(System.JobAttempt) ${{ else }}: artifactName: node-modules-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) + isProduction: false sbomEnabled: false displayName: "Publish Node Modules" continueOnError: true @@ -175,6 +177,7 @@ steps: artifactName: logs-windows-$(VSCODE_ARCH)-$(System.JobAttempt) ${{ else }}: artifactName: logs-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) + isProduction: false sbomEnabled: false displayName: "Publish Log Files" continueOnError: true From c03cd421c56debf15e7b1b40fcd85aa377f2ac73 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Sep 2025 10:31:22 +0200 Subject: [PATCH 0193/4355] chat - towards experimental anonymous access (#265996) --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 1 + src/vs/workbench/contrib/chat/browser/chatSetup.ts | 4 ++-- .../workbench/services/chat/common/chatEntitlementService.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 143cad8ef5d..7742fe0f3df 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -670,6 +670,7 @@ configurationRegistry.registerConfiguration({ }, 'chat.allowAnonymousAccess': { // TODO@bpasero remove me eventually type: 'boolean', + description: nls.localize('chat.allowAnonymousAccess', "Controls whether anonymous access is allowed in chat."), default: false, tags: ['experimental'], experiment: { diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 7815c69b227..7bc3398dccd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -441,8 +441,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ - disableChatViewReveal: true, // we are already in a chat context - allowAnonymous: true, // in chat context we can allow anonymous usage (TODO@bpasero make this dependent on terms visibility) + disableChatViewReveal: true, // we are already in a chat context + allowAnonymous: this.location === ChatAgentLocation.Chat, // allow anonymous based on location (TODO@bpasero expand this to more locations) }); } catch (error) { this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`); diff --git a/src/vs/workbench/services/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts index 771ac6b1420..624159eebbd 100644 --- a/src/vs/workbench/services/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -1099,7 +1099,7 @@ export class ChatEntitlementContext extends Disposable { if (this.configurationService.getValue(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) === true) { let entitlement = state.entitlement; - if (entitlement === ChatEntitlement.Unknown /*&& !state.registered TODO@bpasero revisit */) { + if (entitlement === ChatEntitlement.Unknown && !state.registered) { entitlement = ChatEntitlement.Anonymous; // enable `anonymous` based on exp config if entitlement is unknown and user never signed up } From e7a182734fb6d47ba58bacafcae9304bc6fe8570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Thu, 11 Sep 2025 16:48:49 +0800 Subject: [PATCH 0194/4355] =?UTF-8?q?Fix=20to=20#263546,=20for=20submenu?= =?UTF-8?q?=20of=20treeView=20view/item/context=20z-index=20iss=E2=80=A6?= =?UTF-8?q?=20(#263555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix to #263546, for submenu of treeView view/item/context z-index issue - ensure submenu appears above other elements --- src/vs/base/browser/ui/menu/menu.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 2ad32aa6744..402ba662005 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -903,6 +903,8 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuContainer.style.position = 'fixed'; this.submenuContainer.style.top = '0'; this.submenuContainer.style.left = '0'; + // Fix to #263546, for submenu of treeView view/item/context z-index issue - ensure submenu appears above other elements + this.submenuContainer.style.zIndex = '1'; this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions, this.menuStyle); From f6d464dd6f00b22e8b8a7417081d7ac0b68d3e82 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 11 Sep 2025 11:08:33 +0200 Subject: [PATCH 0195/4355] update distro (#266159) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4800d980002..36053a357c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "1c56627fcbb7427c61b3debf10778ac069ba3027", + "distro": "ca418d1bb0efb141fd522314ce85e7bc3b009054", "author": { "name": "Microsoft Corporation" }, @@ -237,4 +237,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From 45d00e16d4156ea1186cec553ebdfcbecf3657d0 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Thu, 11 Sep 2025 11:41:13 +0100 Subject: [PATCH 0196/4355] style: update debug toolbar dimensions and padding for improved layout --- .../contrib/debug/browser/media/debugToolBar.css | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index c3aaa297262..090c53e7f98 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -6,10 +6,10 @@ .monaco-workbench .debug-toolbar { position: absolute; z-index: 2520; /* Below quick input at 2550, above custom titlebar toolbar at 2500 */ - height: 26px; + height: 28px; display: flex; - padding-left: 7px; - border-radius: 4px; + padding-left: 2px; + border-radius: 5px; left: 0; top: 0; -webkit-app-region: no-drag; @@ -20,17 +20,19 @@ } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container { - margin-right: 7px; + margin-right: 2px; } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container .monaco-select-box, .monaco-workbench .start-debug-action-item .select-container .monaco-select-box { padding: 0 24px 0 8px; + text-overflow: ellipsis; + white-space: nowrap; } .monaco-workbench .debug-toolbar .drag-area { cursor: grab; - width: 16px; + width: 20px; opacity: 0.5; display: flex; align-items: center; From b94704ed4a8a006127eb7b5aecf2f54101a65ed1 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:53:57 +0200 Subject: [PATCH 0197/4355] Use native hover for checkbox inside custom hover (#266173) use native hover for checkbox inside custom hover --- src/vs/workbench/contrib/mcp/browser/mcpCommands.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index e2fd7678a1d..0aa549a8258 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -30,6 +30,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../platform/files/common/files.js'; +import { nativeHoverDelegate } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { mcpAutoStartConfig, McpAutoStartValue } from '../../../../platform/mcp/common/mcpManagement.js'; @@ -607,7 +608,7 @@ export class MCPServerActionRendering extends Disposable implements IWorkbenchCo const checkbox = store.add(new Checkbox( settingLabelStr, config.get() !== McpAutoStartValue.Never, - defaultCheckboxStyles + { ...defaultCheckboxStyles, hoverDelegate: nativeHoverDelegate } )); checkboxContainer.appendChild(checkbox.domNode); From ba6d0c255be596cfb1a2336d17bf1012fa0ed6f2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Sep 2025 12:57:08 +0200 Subject: [PATCH 0198/4355] Adds IObservable.debugGetDependencyGraph --- src/vs/base/common/observableInternal/base.ts | 5 + .../base/common/observableInternal/index.ts | 4 +- .../logging/debugGetDependencyGraph.ts | 114 ++++++++++++++++++ .../observables/baseObservable.ts | 10 ++ .../observables/derivedImpl.ts | 11 ++ .../observables/observableFromEvent.ts | 6 +- .../reactions/autorunImpl.ts | 10 ++ .../test/common/observables/debug.test.ts | 51 ++++++++ .../{ => observables}/observable.test.ts | 14 +-- 9 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts create mode 100644 src/vs/base/test/common/observables/debug.test.ts rename src/vs/base/test/common/{ => observables}/observable.test.ts (98%) diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 772297def7f..69922fce403 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -95,6 +95,11 @@ export interface IObservableWithChange { */ readonly debugName: string; + /** + * ONLY FOR DEBUGGING! + */ + debugGetDependencyGraph(): string; + /** * This property captures the type of the change object. Do not use it at runtime! */ diff --git a/src/vs/base/common/observableInternal/index.ts b/src/vs/base/common/observableInternal/index.ts index 50540a9af91..fd23c427331 100644 --- a/src/vs/base/common/observableInternal/index.ts +++ b/src/vs/base/common/observableInternal/index.ts @@ -40,8 +40,10 @@ import { addLogger, setLogObservableFn } from './logging/logging.js'; import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger.js'; import { DevToolsLogger } from './logging/debugger/devToolsLogger.js'; import { env } from '../process.js'; +import { _setDebugGetDependencyGraph } from './observables/baseObservable.js'; +import { debugGetDependencyGraph } from './logging/debugGetDependencyGraph.js'; - +_setDebugGetDependencyGraph(debugGetDependencyGraph); setLogObservableFn(logObservableToConsole); // Remove "//" in the next line to enable logging diff --git a/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts b/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts new file mode 100644 index 00000000000..320cbfc2840 --- /dev/null +++ b/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IObservable, IObserver } from '../base.js'; +import { Derived } from '../observables/derivedImpl.js'; +import { FromEventObservable } from '../observables/observableFromEvent.js'; +import { ObservableValue } from '../observables/observableValue.js'; +import { AutorunObserver } from '../reactions/autorunImpl.js'; +import { formatValue } from './consoleObservableLogger.js'; + +export function debugGetDependencyGraph(obs: IObservable | IObserver): string { + const info = Info.from(obs); + if (!info) { + return ''; + } + + const alreadyListed = new Set | IObserver>(); + return formatObservableInfo(info, 0, alreadyListed).trim(); +} + +function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Set | IObserver>): string { + const indent = '\t\t'.repeat(indentLevel); + const lines: string[] = []; + + const isAlreadyListed = alreadyListed.has(info.sourceObj); + if (isAlreadyListed) { + lines.push(`${indent}* ${info.type} ${info.name} (already listed)`); + return lines.join('\n'); + } + + alreadyListed.add(info.sourceObj); + + lines.push(`${indent}* ${info.type} ${info.name}:`); + lines.push(`${indent} value: ${formatValue(info.value, 50)}`); + lines.push(`${indent} state: ${info.state}`); + + if (info.dependencies.length > 0) { + lines.push(`${indent} dependencies:`); + for (const dep of info.dependencies) { + lines.push(formatObservableInfo(dep, indentLevel + 1, alreadyListed)); + } + } + + return lines.join('\n'); +} + +class Info { + public static from(obs: IObservable | IObserver): Info | undefined { + if (obs instanceof AutorunObserver) { + const state = obs.debugGetState(); + return new Info( + obs, + obs.debugName || '(anonymous)', + 'autorun', + undefined, + state.stateStr, + Array.from(state.dependencies).map(dep => Info.from(dep) || Info.unknown(dep)) + ); + } else if (obs instanceof Derived) { + const state = obs.debugGetState(); + return new Info( + obs, + obs.debugName || '(anonymous)', + 'derived', + state.value, + state.stateStr, + Array.from(state.dependencies).map(dep => Info.from(dep) || Info.unknown(dep)) + ); + } else if (obs instanceof ObservableValue) { + const state = obs.debugGetState(); + return new Info( + obs, + obs.debugName || '(anonymous)', + 'observableValue', + state.value, + 'upToDate', + [] + ); + } else if (obs instanceof FromEventObservable) { + const state = obs.debugGetState(); + return new Info( + obs, + obs.debugName || '(anonymous)', + 'fromEvent', + state.value, + state.hasValue ? 'upToDate' : 'initial', + [] + ); + } + return undefined; + } + + public static unknown(obs: IObservable | IObserver): Info { + return new Info( + obs, + '(unknown)', + 'unknown', + undefined, + 'unknown', + [] + ); + } + + constructor( + public readonly sourceObj: IObservable | IObserver, + public readonly name: string, + public readonly type: string, + public readonly value: any, + public readonly state: string, + public readonly dependencies: Info[] + ) { } +} diff --git a/src/vs/base/common/observableInternal/observables/baseObservable.ts b/src/vs/base/common/observableInternal/observables/baseObservable.ts index f279d10f78d..1fe2181695e 100644 --- a/src/vs/base/common/observableInternal/observables/baseObservable.ts +++ b/src/vs/base/common/observableInternal/observables/baseObservable.ts @@ -7,6 +7,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base. import { DisposableStore } from '../commonFacade/deps.js'; import { DebugLocation } from '../debugLocation.js'; import { DebugOwner, getFunctionName } from '../debugName.js'; +import { debugGetDependencyGraph } from '../logging/debugGetDependencyGraph.js'; import { getLogger, logObservable } from '../logging/logging.js'; import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils.js'; import { derivedOpts } from './derived.js'; @@ -30,6 +31,11 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) { _keepObserved = keepObserved; } +let _debugGetDependencyGraph: typeof debugGetDependencyGraph; +export function _setDebugGetDependencyGraph(debugGetDependencyGraph: typeof _debugGetDependencyGraph) { + _debugGetDependencyGraph = debugGetDependencyGraph; +} + export abstract class ConvenientObservable implements IObservableWithChange { get TChange(): TChange { return null!; } @@ -121,6 +127,10 @@ export abstract class ConvenientObservable implements IObservableWit protected get debugValue() { return this.get(); } + + debugGetDependencyGraph(): string { + return _debugGetDependencyGraph(this); + } } export abstract class BaseObservable extends ConvenientObservable { diff --git a/src/vs/base/common/observableInternal/observables/derivedImpl.ts b/src/vs/base/common/observableInternal/observables/derivedImpl.ts index 3b1505da9ff..ce5f62db91d 100644 --- a/src/vs/base/common/observableInternal/observables/derivedImpl.ts +++ b/src/vs/base/common/observableInternal/observables/derivedImpl.ts @@ -40,6 +40,16 @@ export const enum DerivedState { upToDate = 3, } +function derivedStateToString(state: DerivedState): string { + switch (state) { + case DerivedState.initial: return 'initial'; + case DerivedState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged'; + case DerivedState.stale: return 'stale'; + case DerivedState.upToDate: return 'upToDate'; + default: return ''; + } +} + export class Derived extends BaseObservable implements IDerivedReader, IObserver { private _state = DerivedState.initial; private _value: T | undefined = undefined; @@ -391,6 +401,7 @@ export class Derived extends BaseObserv public debugGetState() { return { state: this._state, + stateStr: derivedStateToString(this._state), updateCount: this._updateCount, isComputing: this._isComputing, dependencies: this._dependencies, diff --git a/src/vs/base/common/observableInternal/observables/observableFromEvent.ts b/src/vs/base/common/observableInternal/observables/observableFromEvent.ts index b8b2a05860a..7ec53d68536 100644 --- a/src/vs/base/common/observableInternal/observables/observableFromEvent.ts +++ b/src/vs/base/common/observableInternal/observables/observableFromEvent.ts @@ -147,9 +147,13 @@ export class FromEventObservable extends BaseObservable { } } - public debugSetValue(value: unknown) { + public debugSetValue(value: unknown): void { this._value = value as any; } + + public debugGetState() { + return { value: this._value, hasValue: this._hasValue }; + } } export namespace observableFromEvent { diff --git a/src/vs/base/common/observableInternal/reactions/autorunImpl.ts b/src/vs/base/common/observableInternal/reactions/autorunImpl.ts index 16f48bfa2f8..240e13f0563 100644 --- a/src/vs/base/common/observableInternal/reactions/autorunImpl.ts +++ b/src/vs/base/common/observableInternal/reactions/autorunImpl.ts @@ -24,6 +24,15 @@ export const enum AutorunState { upToDate = 3, } +function autorunStateToString(state: AutorunState): string { + switch (state) { + case AutorunState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged'; + case AutorunState.stale: return 'stale'; + case AutorunState.upToDate: return 'upToDate'; + default: return ''; + } +} + export class AutorunObserver implements IObserver, IReaderWithStore, IDisposable { private _state = AutorunState.stale; private _updateCount = 0; @@ -241,6 +250,7 @@ export class AutorunObserver implements IObserver, IReader updateCount: this._updateCount, dependencies: this._dependencies, state: this._state, + stateStr: autorunStateToString(this._state), }; } diff --git a/src/vs/base/test/common/observables/debug.test.ts b/src/vs/base/test/common/observables/debug.test.ts new file mode 100644 index 00000000000..1142ba35f83 --- /dev/null +++ b/src/vs/base/test/common/observables/debug.test.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { observableValue, derived, autorun } from '../../../common/observable.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js'; + +suite('debug', () => { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + + test('debugGetDependencyGraph', () => { + const myObservable1 = observableValue('myObservable1', 0); + const myObservable2 = observableValue('myObservable2', 0); + + const myComputed1 = derived(reader => { + /** @description myComputed1 */ + const value1 = myObservable1.read(reader); + const value2 = myObservable2.read(reader); + const sum = value1 + value2; + return sum; + }); + + const myComputed2 = derived(reader => { + /** @description myComputed2 */ + const value1 = myComputed1.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); + const sum = value1 + value2 + value3; + return sum; + }); + + const myComputed3 = derived(reader => { + /** @description myComputed3 */ + const value1 = myComputed2.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); + const sum = value1 + value2 + value3; + return sum; + }); + + ds.add(autorun(reader => { + /** @description myAutorun */ + myComputed3.read(reader); + })); + + + assert.deepStrictEqual(myComputed3.debugGetDependencyGraph(), "* derived myComputed3:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived myComputed2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived myComputed1:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue myObservable1:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue myObservable2:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue myObservable1 (already listed)\n\t\t\t\t* observableValue myObservable2 (already listed)\n\t\t* observableValue myObservable1 (already listed)\n\t\t* observableValue myObservable2 (already listed)"); + }); +}); diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observables/observable.test.ts similarity index 98% rename from src/vs/base/test/common/observable.test.ts rename to src/vs/base/test/common/observables/observable.test.ts index 20e262ffff9..3cd85109708 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observables/observable.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { setUnexpectedErrorHandler } from '../../common/errors.js'; -import { Emitter, Event } from '../../common/event.js'; -import { DisposableStore, toDisposable } from '../../common/lifecycle.js'; -import { IDerivedReader, IObservableWithChange, autorun, autorunHandleChanges, autorunWithStoreHandleChanges, derived, derivedDisposable, IObservable, IObserver, ISettableObservable, ITransaction, keepObserved, observableFromEvent, observableSignal, observableValue, recordChanges, transaction, waitForState, derivedHandleChanges, runOnChange, DebugLocation } from '../../common/observable.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { setUnexpectedErrorHandler } from '../../../common/errors.js'; +import { Emitter, Event } from '../../../common/event.js'; +import { DisposableStore, toDisposable } from '../../../common/lifecycle.js'; +import { IDerivedReader, IObservableWithChange, autorun, autorunHandleChanges, autorunWithStoreHandleChanges, derived, derivedDisposable, IObservable, IObserver, ISettableObservable, ITransaction, keepObserved, observableFromEvent, observableSignal, observableValue, recordChanges, transaction, waitForState, derivedHandleChanges, runOnChange, DebugLocation } from '../../../common/observable.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js'; // eslint-disable-next-line local/code-no-deep-import-of-internal -import { observableReducer } from '../../common/observableInternal/experimental/reducer.js'; +import { observableReducer } from '../../../common/observableInternal/experimental/reducer.js'; // eslint-disable-next-line local/code-no-deep-import-of-internal -import { BaseObservable } from '../../common/observableInternal/observables/baseObservable.js'; +import { BaseObservable } from '../../../common/observableInternal/observables/baseObservable.js'; suite('observables', () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); From d4b313648bc67db62bdb33ed1208f29065e4d6f3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Sep 2025 13:14:06 +0200 Subject: [PATCH 0199/4355] Improved observable names & types (#266182) --- .../view/inlineEdits/components/gutterIndicatorView.ts | 7 +++++-- .../browser/view/inlineEdits/inlineEditsView.ts | 2 ++ .../browser/view/inlineEdits/inlineEditsViewProducer.ts | 1 + .../inlineCompletions/browser/view/inlineEdits/theme.ts | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index dc5ede8326c..adc1e1239fd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -28,6 +28,7 @@ import { IInlineEditModel, InlineEditTabAction } from '../inlineEditsViewInterfa import { getEditorBlendedColor, inlineEditIndicatorBackground, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorPrimaryBorder, inlineEditIndicatorPrimaryForeground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorSecondaryBorder, inlineEditIndicatorSecondaryForeground, inlineEditIndicatorsuccessfulBackground, inlineEditIndicatorsuccessfulBorder, inlineEditIndicatorsuccessfulForeground } from '../theme.js'; import { mapOutFalsy, rectToProps } from '../utils/utils.js'; import { GutterIndicatorMenuContent } from './gutterIndicatorMenu.js'; +import { assertNever } from '../../../../../../../base/common/assert.js'; export class InlineEditsGutterIndicator extends Disposable { @@ -37,7 +38,7 @@ export class InlineEditsGutterIndicator extends Disposable { return model; } - private readonly _gutterIndicatorStyles: IObservable<{ background: string; foreground: string; border: string }>; + private readonly _gutterIndicatorStyles; private readonly _isHoveredOverInlineEditDebounced: IObservable; constructor( @@ -67,7 +68,7 @@ export class InlineEditsGutterIndicator extends Disposable { this.isHoveredOverIcon = this._isHoveredOverIconDebounced; this._isHoveredOverInlineEditDebounced = debouncedObservable(this._isHoveringOverInlineEdit, 100); - this._gutterIndicatorStyles = this._tabAction.map((v, reader) => { + this._gutterIndicatorStyles = this._tabAction.map(this, (v, reader) => { switch (v) { case InlineEditTabAction.Inactive: return { background: getEditorBlendedColor(inlineEditIndicatorSecondaryBackground, themeService).read(reader).toString(), @@ -84,6 +85,8 @@ export class InlineEditsGutterIndicator extends Disposable { foreground: getEditorBlendedColor(inlineEditIndicatorsuccessfulForeground, themeService).read(reader).toString(), border: getEditorBlendedColor(inlineEditIndicatorsuccessfulBorder, themeService).read(reader).toString() }; + default: + assertNever(v); } }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index b609285dfc8..ef0b3e1bfea 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -134,6 +134,7 @@ export class InlineEditsView extends Disposable { } const indicatorDisplayRange = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => { + /** @description indicatorDisplayRange */ const ghostTextIndicator = this._ghostTextIndicator.read(reader); if (ghostTextIndicator) { return ghostTextIndicator.lineRange; @@ -158,6 +159,7 @@ export class InlineEditsView extends Disposable { }); const modelWithGhostTextSupport = derived(this, reader => { + /** @description modelWithGhostTextSupport */ const model = this._model.read(reader); if (model) { return model; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts index 8bd7712e474..15aaec60501 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts @@ -57,6 +57,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c if (!edit) { return undefined; } const tabAction = derived(this, reader => { + /** @description tabAction */ if (this._editorObs.isFocused.read(reader)) { if (model.tabShouldJumpToInlineEdit.read(reader)) { return InlineEditTabAction.Jump; } if (model.tabShouldAcceptInlineEdit.read(reader)) { return InlineEditTabAction.Accept; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts index ff04b209adc..d398a4a56b8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts @@ -188,7 +188,7 @@ export function getEditorBlendedColor(colorIdentifier: ColorIdentifier | IObserv const backgroundColor = observeColor(editorBackground, themeService); - return color.map((c, reader) => c.makeOpaque(backgroundColor.read(reader))); + return color.map((c, reader) => /** @description makeOpaque */ c.makeOpaque(backgroundColor.read(reader))); } export function observeColor(colorIdentifier: ColorIdentifier, themeService: IThemeService): IObservable { @@ -196,6 +196,7 @@ export function observeColor(colorIdentifier: ColorIdentifier, themeService: ITh { owner: { observeColor: colorIdentifier }, equalsFn: (a: Color, b: Color) => a.equals(b), + debugName: () => `observeColor(${colorIdentifier})` }, themeService.onDidColorThemeChange, () => { From ae0f8141dc81ac9d2270d4b00f52153b7d4e1515 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:08:58 +0000 Subject: [PATCH 0200/4355] Initial plan From d8464495fc2bb379bfa1c5a47441c8cef6db3fd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:17:47 +0000 Subject: [PATCH 0201/4355] Remove icon from defaultSnippets and support all profile properties when set Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../browser/toolTerminalCreator.ts | 36 ++++++++++++++----- .../browser/tools/runInTerminalTool.ts | 33 +++++++++++++---- .../terminalChatAgentToolsConfiguration.ts | 9 ++--- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index ef4454f55ca..d1325b60e64 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -13,7 +13,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { PromptInputState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; -import { ITerminalLogService, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; +import { ITerminalLogService, ITerminalProfile, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; import { ITerminalService, type ITerminalInstance } from '../../../terminal/browser/terminal.js'; import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; @@ -49,8 +49,8 @@ export class ToolTerminalCreator { ) { } - async createTerminal(shell: string, token: CancellationToken): Promise { - const instance = await this._createCopilotTerminal(shell); + async createTerminal(shellOrProfile: string | ITerminalProfile, token: CancellationToken): Promise { + const instance = await this._createCopilotTerminal(shellOrProfile); const toolTerminal: IToolTerminal = { instance, shellIntegrationQuality: ShellIntegrationQuality.None, @@ -126,17 +126,35 @@ export class ToolTerminalCreator { } } - private _createCopilotTerminal(shell: string) { - return this._terminalService.createTerminal({ - config: { - executable: shell, + private _createCopilotTerminal(shellOrProfile: string | ITerminalProfile) { + let config: any; + + if (typeof shellOrProfile === 'string') { + // When null setting or fallback, use previous behavior with just shell string + config = { + executable: shellOrProfile, icon: ThemeIcon.fromId(Codicon.chatSparkle.id), hideFromUser: true, env: { GIT_PAGER: 'cat', // avoid making `git diff` interactive when called from copilot }, - }, - }); + }; + } else { + // When profile is set, use everything from the chat terminal profile (icon, args, etc.) + config = { + executable: shellOrProfile.path, + args: shellOrProfile.args, + icon: shellOrProfile.icon || ThemeIcon.fromId(Codicon.chatSparkle.id), + color: shellOrProfile.color, + hideFromUser: true, + env: { + GIT_PAGER: 'cat', // avoid making `git diff` interactive when called from copilot + ...shellOrProfile.env, // merge profile env with our default + }, + }; + } + + return this._terminalService.createTerminal({ config }); } private _waitForShellIntegration( 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 535b7cd0475..8a494736eb0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -608,6 +608,28 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Terminal init + private async _getCopilotShellOrProfile(): Promise { + const os = await this._osBackend; + + // Check for chat agent terminal profile first + const chatAgentProfile = this._getChatAgentTerminalProfile(os); + if (chatAgentProfile) { + // When profile is set, use everything from the chat terminal profile (icon, args, etc.) + return chatAgentProfile; + } + + // When setting is null, use the previous behavior + const defaultShell = await this._terminalProfileResolverService.getDefaultShell({ + os, + remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority + }); + // Force pwsh over cmd as cmd doesn't have shell integration + if (basename(defaultShell) === 'cmd.exe') { + return 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; + } + return defaultShell; + } + private async _getCopilotShell(): Promise { const shellConfig = await this._getCopilotShellConfig(); return shellConfig.executable || 'bash'; @@ -621,8 +643,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { if (chatAgentProfile) { return { executable: chatAgentProfile.path, - // TODO: Support args - // args: chatAgentProfile.args + args: chatAgentProfile.args }; } @@ -677,8 +698,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private async _initBackgroundTerminal(chatSessionId: string, termId: string, token: CancellationToken): Promise { this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`); - const shell = await this._getCopilotShell(); - const toolTerminal = await this._terminalToolCreator.createTerminal(shell, token); + const shellOrProfile = await this._getCopilotShellOrProfile(); + const toolTerminal = await this._terminalToolCreator.createTerminal(shellOrProfile, token); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { @@ -696,8 +717,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._terminalToolCreator.refreshShellIntegrationQuality(cachedTerminal); return cachedTerminal; } - const shell = await this._getCopilotShell(); - const toolTerminal = await this._terminalToolCreator.createTerminal(shell, token); + const shellOrProfile = await this._getCopilotShellOrProfile(); + const toolTerminal = await this._terminalToolCreator.createTerminal(shellOrProfile, token); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index e3b6ebed54f..2ab30c3980d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -346,8 +346,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Thu, 11 Sep 2025 13:18:41 +0100 Subject: [PATCH 0202/4355] update settings-gear icon to reduce antialiasing on lower res displays --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 90428 -> 90416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 70b7876023a0fc5a089759f6630587b53c95e24a..b17f0d5be2ea9851c15d51d77b342b63be7c512b 100644 GIT binary patch delta 455 zcmdmUh;_pu)(H+g?Ow563=CR142+Tr6Malrd{0jNHL*omu!n(x@kc>^aS4A!Zv+FQ zYsK4_VtNTXKMFr&uf3sq0*7o zaiHTtr(CC3=aSAtU2R?8x@YtV^n~lh9RHViOTFvSUGLz>O?26uVYu&Eff$HRS;zq6X4+H7U1UI08@*kO^u6x^1?b% eaa~Eqa>)LuH;B*P3f{SwX&qLGi6`O^~#r& zKd4Zt$f#IV@uAYDa!TcnDyFJgRiA1;)vc(zQum`irM{xt@_wmC=Z_N96qMjZxm26F~y20sP{b8%62advff zWp#FSb9Ga5b#rlZV{vnFc5_pGMssy`eMV(|Ms;>^enwGwMs{&?c}8P Date: Thu, 11 Sep 2025 12:19:48 +0000 Subject: [PATCH 0203/4355] Implement security filtering to prevent auto-approval suggestions for dangerous commands Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../browser/runInTerminalHelpers.ts | 24 ++++++- .../test/browser/runInTerminalTool.test.ts | 70 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index cfc880ec663..8a5bf01bf29 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -49,6 +49,20 @@ export function sanitizeTerminalOutput(output: string): string { export function generateAutoApproveActions(commandLine: string, subCommands: string[], autoApproveResult: { subCommandResults: ICommandApprovalResultWithReason[]; commandLineResult: ICommandApprovalResultWithReason }): ToolConfirmationAction[] { const actions: ToolConfirmationAction[] = []; + // Security: Commands that should never be suggested for auto-approval because they can + // execute arbitrary code directly through their arguments + const neverAutoApproveCommands = new Set([ + // Shell interpreters - can execute arbitrary code with -c or script files + 'bash', 'sh', 'zsh', 'fish', 'ksh', 'csh', 'tcsh', 'dash', + 'pwsh', 'powershell', 'powershell.exe', 'cmd', 'cmd.exe', + // Script interpreters - can execute arbitrary code directly + 'python', 'python3', 'node', 'ruby', 'perl', 'php', 'lua', + // Direct execution commands + 'eval', 'exec', 'source', 'sudo', 'su', 'doas', + // Network tools that can download and execute code + 'curl', 'wget' + ]); + // We shouldn't offer configuring rules for commands that are explicitly denied since it // wouldn't get auto approved with a new rule const canCreateAutoApproval = autoApproveResult.subCommandResults.some(e => e.result !== 'denied') || autoApproveResult.commandLineResult.result === 'denied'; @@ -66,6 +80,11 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str const baseCommand = parts[0].toLowerCase(); const baseSubCommand = parts.length > 1 ? `${parts[0]} ${parts[1]}`.toLowerCase() : ''; + // Security check: Never suggest auto-approval for dangerous interpreter commands + if (neverAutoApproveCommands.has(baseCommand)) { + return undefined; + } + if (commandsWithSubSubCommands.has(baseSubCommand)) { if (parts.length >= 3 && !parts[2].startsWith('-')) { return `${parts[0]} ${parts[1]} ${parts[2]}`; @@ -104,11 +123,14 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str // Allow exact command line, don't do this if it's just the first sub-command's first // word or if it's an exact match for special sub-commands + // Security check: Don't suggest exact command line auto-approval for dangerous interpreter commands const firstSubcommandFirstWord = unapprovedSubCommands.length > 0 ? unapprovedSubCommands[0].split(' ')[0] : ''; + const commandLineFirstWord = commandLine.trim().split(/\s+/)[0].toLowerCase(); if ( firstSubcommandFirstWord !== commandLine && !commandsWithSubcommands.has(commandLine) && - !commandsWithSubSubCommands.has(commandLine) + !commandsWithSubSubCommands.has(commandLine) && + !neverAutoApproveCommands.has(commandLineFirstWord) ) { actions.push({ label: localize('autoApprove.exactCommand', 'Always Allow Exact Command Line'), diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 330b1f11c0e..e674f238455 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -928,6 +928,76 @@ suite('RunInTerminalTool', () => { strictEqual(actionsWithFlags[2].data.type, 'configure'); }); + test('should not suggest auto-approval for dangerous interpreter commands', async () => { + // Test bash command - should not suggest auto-approval + const bashResult = await executeToolTest({ + command: 'bash -c "echo hello"', + explanation: 'Run bash command' + }); + + assertConfirmationRequired(bashResult); + ok(bashResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); + + const bashActions = bashResult!.confirmationMessages!.terminalCustomActions!; + // Should only have separator + configure option, no auto-approval suggestions + strictEqual(bashActions.length, 2); + ok(isSeparator(bashActions[0])); + ok(!isSeparator(bashActions[1])); + strictEqual(bashActions[1].label, 'Configure Auto Approve...'); + strictEqual(bashActions[1].data.type, 'configure'); + + // Test pwsh command - should not suggest auto-approval + const pwshResult = await executeToolTest({ + command: 'pwsh -Command "Get-Date"', + explanation: 'Run PowerShell command' + }); + + assertConfirmationRequired(pwshResult); + ok(pwshResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); + + const pwshActions = pwshResult!.confirmationMessages!.terminalCustomActions!; + // Should only have separator + configure option, no auto-approval suggestions + strictEqual(pwshActions.length, 2); + ok(isSeparator(pwshActions[0])); + ok(!isSeparator(pwshActions[1])); + strictEqual(pwshActions[1].label, 'Configure Auto Approve...'); + strictEqual(pwshActions[1].data.type, 'configure'); + + // Test python command - should not suggest auto-approval + const pythonResult = await executeToolTest({ + command: 'python -c "import os; os.system(\'echo dangerous\')"', + explanation: 'Run python command' + }); + + assertConfirmationRequired(pythonResult); + ok(pythonResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); + + const pythonActions = pythonResult!.confirmationMessages!.terminalCustomActions!; + // Should only have separator + configure option, no auto-approval suggestions + strictEqual(pythonActions.length, 2); + ok(isSeparator(pythonActions[0])); + ok(!isSeparator(pythonActions[1])); + strictEqual(pythonActions[1].label, 'Configure Auto Approve...'); + strictEqual(pythonActions[1].data.type, 'configure'); + + // Test node command - should not suggest auto-approval + const nodeResult = await executeToolTest({ + command: 'node -e "require(\'child_process\').exec(\'echo dangerous\')"', + explanation: 'Run node command' + }); + + assertConfirmationRequired(nodeResult); + ok(nodeResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); + + const nodeActions = nodeResult!.confirmationMessages!.terminalCustomActions!; + // Should only have separator + configure option, no auto-approval suggestions + strictEqual(nodeActions.length, 2); + ok(isSeparator(nodeActions[0])); + ok(!isSeparator(nodeActions[1])); + strictEqual(nodeActions[1].label, 'Configure Auto Approve...'); + strictEqual(nodeActions[1].data.type, 'configure'); + }); + }); suite('chat session disposal cleanup', () => { From bfce5aa2b7170049204f461730388fe414e9c96c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 11 Sep 2025 14:21:20 +0200 Subject: [PATCH 0204/4355] update distro (#266186) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36053a357c3..2b1c34f19ac 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "ca418d1bb0efb141fd522314ce85e7bc3b009054", + "distro": "38f50bcc0ae9969d068f7391b5f124dc25e80465", "author": { "name": "Microsoft Corporation" }, From f4f486bad16f76ae7333219093938eb15eb5a7cc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Sep 2025 15:03:01 +0200 Subject: [PATCH 0205/4355] Fixes CI --- .../logging/debugGetDependencyGraph.ts | 19 ++++++++++--------- .../test/common/observables/debug.test.ts | 8 +++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts b/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts index 320cbfc2840..88c9346d28c 100644 --- a/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts +++ b/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts @@ -10,8 +10,9 @@ import { ObservableValue } from '../observables/observableValue.js'; import { AutorunObserver } from '../reactions/autorunImpl.js'; import { formatValue } from './consoleObservableLogger.js'; -export function debugGetDependencyGraph(obs: IObservable | IObserver): string { - const info = Info.from(obs); +export function debugGetDependencyGraph(obs: IObservable | IObserver, options?: { debugNamePostProcessor?: (name: string) => string }): string { + const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str: string) => str); + const info = Info.from(obs, debugNamePostProcessor); if (!info) { return ''; } @@ -47,32 +48,32 @@ function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Se } class Info { - public static from(obs: IObservable | IObserver): Info | undefined { + public static from(obs: IObservable | IObserver, debugNamePostProcessor: (name: string) => string): Info | undefined { if (obs instanceof AutorunObserver) { const state = obs.debugGetState(); return new Info( obs, - obs.debugName || '(anonymous)', + debugNamePostProcessor(obs.debugName), 'autorun', undefined, state.stateStr, - Array.from(state.dependencies).map(dep => Info.from(dep) || Info.unknown(dep)) + Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep)) ); } else if (obs instanceof Derived) { const state = obs.debugGetState(); return new Info( obs, - obs.debugName || '(anonymous)', + debugNamePostProcessor(obs.debugName), 'derived', state.value, state.stateStr, - Array.from(state.dependencies).map(dep => Info.from(dep) || Info.unknown(dep)) + Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep)) ); } else if (obs instanceof ObservableValue) { const state = obs.debugGetState(); return new Info( obs, - obs.debugName || '(anonymous)', + debugNamePostProcessor(obs.debugName), 'observableValue', state.value, 'upToDate', @@ -82,7 +83,7 @@ class Info { const state = obs.debugGetState(); return new Info( obs, - obs.debugName || '(anonymous)', + debugNamePostProcessor(obs.debugName), 'fromEvent', state.value, state.hasValue ? 'upToDate' : 'initial', diff --git a/src/vs/base/test/common/observables/debug.test.ts b/src/vs/base/test/common/observables/debug.test.ts index 1142ba35f83..d3bebb9126e 100644 --- a/src/vs/base/test/common/observables/debug.test.ts +++ b/src/vs/base/test/common/observables/debug.test.ts @@ -6,6 +6,8 @@ import assert from 'assert'; import { observableValue, derived, autorun } from '../../../common/observable.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js'; +// eslint-disable-next-line local/code-no-deep-import-of-internal +import { debugGetDependencyGraph } from '../../../common/observableInternal/logging/debugGetDependencyGraph.js'; suite('debug', () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); @@ -46,6 +48,10 @@ suite('debug', () => { })); - assert.deepStrictEqual(myComputed3.debugGetDependencyGraph(), "* derived myComputed3:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived myComputed2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived myComputed1:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue myObservable1:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue myObservable2:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue myObservable1 (already listed)\n\t\t\t\t* observableValue myObservable2 (already listed)\n\t\t* observableValue myObservable1 (already listed)\n\t\t* observableValue myObservable2 (already listed)"); + let idx = 0; + assert.deepStrictEqual( + debugGetDependencyGraph(myComputed3, { debugNamePostProcessor: name => `name${++idx}` }), + "* derived name1:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived name2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived name3:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue name4:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue name5:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue name6 (already listed)\n\t\t\t\t* observableValue name7 (already listed)\n\t\t* observableValue name8 (already listed)\n\t\t* observableValue name9 (already listed)", + ); }); }); From 6efb3110dee6e0155ec2110adcf85babdfbcf927 Mon Sep 17 00:00:00 2001 From: Aleksei Gusev Date: Thu, 11 Sep 2025 16:27:00 +0300 Subject: [PATCH 0206/4355] Allow to bind `diffEditor.revert` to keyboard (#225881) There is no way to revert a hunk in diff editor via keyboard. `diffEditor.revert` can be used only via clicking on the gutter icon. This commit extends this function, so it uses current position or selection to detect which hunks have to be reverted. Closes #225879 Co-authored-by: Henning Dieterichs --- .../browser/widget/diffEditor/commands.ts | 17 ++++++++++-- .../widget/diffEditor/diffEditorWidget.ts | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/commands.ts b/src/vs/editor/browser/widget/diffEditor/commands.ts index 0d0676fea62..425266acea8 100644 --- a/src/vs/editor/browser/widget/diffEditor/commands.ts +++ b/src/vs/editor/browser/widget/diffEditor/commands.ts @@ -173,12 +173,25 @@ export class RevertHunkOrSelection extends Action2 { super({ id: 'diffEditor.revert', title: localize2('revert', 'Revert'), - f1: false, + f1: true, category: diffEditorCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } - run(accessor: ServicesAccessor, arg: DiffEditorSelectionHunkToolbarContext): unknown { + run(accessor: ServicesAccessor, arg?: DiffEditorSelectionHunkToolbarContext): unknown { + return arg ? this.runViaToolbarContext(accessor, arg) : this.runViaCursorOrSelection(accessor); + } + + runViaCursorOrSelection(accessor: ServicesAccessor): unknown { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.revertFocusedRangeMappings(); + } + return undefined; + } + + runViaToolbarContext(accessor: ServicesAccessor, arg: DiffEditorSelectionHunkToolbarContext): unknown { const diffEditor = findDiffEditor(accessor, arg.originalUri, arg.modifiedUri); if (diffEditor instanceof DiffEditorWidget) { diffEditor.revertRangeMappings(arg.mapping.innerChanges ?? []); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index a733efc443a..026b0850887 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -18,6 +18,7 @@ import { bindContextKey } from '../../../../platform/observable/common/platformO import { IEditorProgressService } from '../../../../platform/progress/common/progress.js'; import { IDiffEditorOptions } from '../../../common/config/editorOptions.js'; import { IDimension } from '../../../common/core/2d/dimension.js'; +import { LineRange } from '../../../common/core/ranges/lineRange.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { CursorChangeReason, ICursorPositionChangedEvent } from '../../../common/cursorEvents.js'; @@ -593,6 +594,32 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._editors.modified.executeEdits('diffEditor', changes); } + revertFocusedRangeMappings() { + const model = this._diffModel.get(); + if (!model || !model.isDiffUpToDate.get()) { return; } + + const diffs = this._diffModel.get()?.diff.get()?.mappings; + if (!diffs || diffs.length === 0) { return; } + + const modifiedEditor = this._editors.modified; + if (!modifiedEditor.hasTextFocus()) { return; } + + const curLineNumber = modifiedEditor.getPosition()!.lineNumber; + const selection = modifiedEditor.getSelection(); + const selectedRange = LineRange.fromRange(selection || new Range(curLineNumber, 0, curLineNumber, 0)); + const diffsToRevert = diffs.filter(d => { + return d.lineRangeMapping.modified.intersect(selectedRange); + }); + + modifiedEditor.executeEdits('diffEditor', diffsToRevert.map(d => ( + { + range: d.lineRangeMapping.modified.toExclusiveRange(), + text: model.model.original.getValueInRange(d.lineRangeMapping.original.toExclusiveRange()) + } + ))); + } + + private _goTo(diff: DiffMapping): void { this._editors.modified.setPosition(new Position(diff.lineRangeMapping.modified.startLineNumber, 1)); this._editors.modified.revealRangeInCenter(diff.lineRangeMapping.modified.toExclusiveRange()); From d413c6f47545e414e9551fd77e74df0ca9f8c0ad Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 11 Sep 2025 06:35:23 -0700 Subject: [PATCH 0207/4355] Polish and simplify test --- .../browser/runInTerminalHelpers.ts | 44 ++++++----- .../test/browser/runInTerminalTool.test.ts | 77 ++++--------------- 2 files changed, 39 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 8a5bf01bf29..e5c7b079e81 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -49,20 +49,6 @@ export function sanitizeTerminalOutput(output: string): string { export function generateAutoApproveActions(commandLine: string, subCommands: string[], autoApproveResult: { subCommandResults: ICommandApprovalResultWithReason[]; commandLineResult: ICommandApprovalResultWithReason }): ToolConfirmationAction[] { const actions: ToolConfirmationAction[] = []; - // Security: Commands that should never be suggested for auto-approval because they can - // execute arbitrary code directly through their arguments - const neverAutoApproveCommands = new Set([ - // Shell interpreters - can execute arbitrary code with -c or script files - 'bash', 'sh', 'zsh', 'fish', 'ksh', 'csh', 'tcsh', 'dash', - 'pwsh', 'powershell', 'powershell.exe', 'cmd', 'cmd.exe', - // Script interpreters - can execute arbitrary code directly - 'python', 'python3', 'node', 'ruby', 'perl', 'php', 'lua', - // Direct execution commands - 'eval', 'exec', 'source', 'sudo', 'su', 'doas', - // Network tools that can download and execute code - 'curl', 'wget' - ]); - // We shouldn't offer configuring rules for commands that are explicitly denied since it // wouldn't get auto approved with a new rule const canCreateAutoApproval = autoApproveResult.subCommandResults.some(e => e.result !== 'denied') || autoApproveResult.commandLineResult.result === 'denied'; @@ -71,10 +57,31 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str return autoApproveResult.subCommandResults[index].result !== 'approved'; }); - // For each unapproved sub-command (within the overall command line), decide whether to - // suggest just the commnad or sub-command (with that sub-command line) to always allow. + // Some commands should not be recommended as they are too permissive generally. This only + // applies to sub-commands, we still want to offer approving of the exact the command line + // however as it's very specific. + const neverAutoApproveCommands = new Set([ + // Shell interpreters + 'bash', 'sh', 'zsh', 'fish', 'ksh', 'csh', 'tcsh', 'dash', + 'pwsh', 'powershell', 'powershell.exe', 'cmd', 'cmd.exe', + // Script interpreters + 'python', 'python3', 'node', 'ruby', 'perl', 'php', 'lua', + // Direct execution commands + 'eval', 'exec', 'source', 'sudo', 'su', 'doas', + // Network tools that can download and execute code + 'curl', 'wget', 'invoke-restmethod', 'invoke-webrequest', 'irm', 'iwr', + ]); + + // Commands where we want to suggest the sub-command (eg. `foo bar` instead of `foo`) const commandsWithSubcommands = new Set(['git', 'npm', 'yarn', 'docker', 'kubectl', 'cargo', 'dotnet', 'mvn', 'gradle']); + + // Commands where we want to suggest the sub-command of a sub-command (eg. `foo bar baz` + // instead of `foo`) const commandsWithSubSubCommands = new Set(['npm run', 'yarn run']); + + // For each unapproved sub-command (within the overall command line), decide whether to + // suggest new rules for the command, a sub-command, a sub-command of a sub-command or to + // not suggest at all. const subCommandsToSuggest = Array.from(new Set(coalesce(unapprovedSubCommands.map(command => { const parts = command.trim().split(/\s+/); const baseCommand = parts[0].toLowerCase(); @@ -123,14 +130,11 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str // Allow exact command line, don't do this if it's just the first sub-command's first // word or if it's an exact match for special sub-commands - // Security check: Don't suggest exact command line auto-approval for dangerous interpreter commands const firstSubcommandFirstWord = unapprovedSubCommands.length > 0 ? unapprovedSubCommands[0].split(' ')[0] : ''; - const commandLineFirstWord = commandLine.trim().split(/\s+/)[0].toLowerCase(); if ( firstSubcommandFirstWord !== commandLine && !commandsWithSubcommands.has(commandLine) && - !commandsWithSubSubCommands.has(commandLine) && - !neverAutoApproveCommands.has(commandLineFirstWord) + !commandsWithSubSubCommands.has(commandLine) ) { actions.push({ label: localize('autoApprove.exactCommand', 'Always Allow Exact Command Line'), diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index e674f238455..6028138d986 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -828,7 +828,7 @@ suite('RunInTerminalTool', () => { test('should suggest base command for non-subcommand tools', async () => { const result = await executeToolTest({ - command: 'curl https://example.com', + command: 'foo bar', explanation: 'Download from example.com' }); @@ -839,10 +839,10 @@ suite('RunInTerminalTool', () => { strictEqual(customActions.length, 4); ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: curl'); + strictEqual(customActions[0].label, 'Always Allow Command: foo'); strictEqual(customActions[0].data.type, 'newRule'); ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'curl'); + strictEqual((customActions[0].data.rule as any)[0].key, 'foo'); }); test('should handle single word commands from subcommand-aware tools', async () => { @@ -928,76 +928,29 @@ suite('RunInTerminalTool', () => { strictEqual(actionsWithFlags[2].data.type, 'configure'); }); - test('should not suggest auto-approval for dangerous interpreter commands', async () => { - // Test bash command - should not suggest auto-approval + test('should not suggest overly permissive subcommand rules', async () => { const bashResult = await executeToolTest({ command: 'bash -c "echo hello"', explanation: 'Run bash command' }); + const actions = bashResult?.confirmationMessages?.terminalCustomActions; assertConfirmationRequired(bashResult); - ok(bashResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const bashActions = bashResult!.confirmationMessages!.terminalCustomActions!; - // Should only have separator + configure option, no auto-approval suggestions - strictEqual(bashActions.length, 2); - ok(isSeparator(bashActions[0])); - ok(!isSeparator(bashActions[1])); - strictEqual(bashActions[1].label, 'Configure Auto Approve...'); - strictEqual(bashActions[1].data.type, 'configure'); - - // Test pwsh command - should not suggest auto-approval - const pwshResult = await executeToolTest({ - command: 'pwsh -Command "Get-Date"', - explanation: 'Run PowerShell command' - }); + ok(actions, 'Expected custom actions to be defined'); - assertConfirmationRequired(pwshResult); - ok(pwshResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const pwshActions = pwshResult!.confirmationMessages!.terminalCustomActions!; - // Should only have separator + configure option, no auto-approval suggestions - strictEqual(pwshActions.length, 2); - ok(isSeparator(pwshActions[0])); - ok(!isSeparator(pwshActions[1])); - strictEqual(pwshActions[1].label, 'Configure Auto Approve...'); - strictEqual(pwshActions[1].data.type, 'configure'); - - // Test python command - should not suggest auto-approval - const pythonResult = await executeToolTest({ - command: 'python -c "import os; os.system(\'echo dangerous\')"', - explanation: 'Run python command' - }); + strictEqual(actions.length, 3); - assertConfirmationRequired(pythonResult); - ok(pythonResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const pythonActions = pythonResult!.confirmationMessages!.terminalCustomActions!; - // Should only have separator + configure option, no auto-approval suggestions - strictEqual(pythonActions.length, 2); - ok(isSeparator(pythonActions[0])); - ok(!isSeparator(pythonActions[1])); - strictEqual(pythonActions[1].label, 'Configure Auto Approve...'); - strictEqual(pythonActions[1].data.type, 'configure'); - - // Test node command - should not suggest auto-approval - const nodeResult = await executeToolTest({ - command: 'node -e "require(\'child_process\').exec(\'echo dangerous\')"', - explanation: 'Run node command' - }); + ok(!isSeparator(actions[0])); + strictEqual(actions[0].label, 'Always Allow Exact Command Line'); + strictEqual(actions[0].data.type, 'newRule'); + ok(!Array.isArray(actions[0].data.rule), 'Expected rule to be an object for exact command line'); - assertConfirmationRequired(nodeResult); - ok(nodeResult!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); + ok(isSeparator(actions[1])); - const nodeActions = nodeResult!.confirmationMessages!.terminalCustomActions!; - // Should only have separator + configure option, no auto-approval suggestions - strictEqual(nodeActions.length, 2); - ok(isSeparator(nodeActions[0])); - ok(!isSeparator(nodeActions[1])); - strictEqual(nodeActions[1].label, 'Configure Auto Approve...'); - strictEqual(nodeActions[1].data.type, 'configure'); + ok(!isSeparator(actions[2])); + strictEqual(actions[2].label, 'Configure Auto Approve...'); + strictEqual(actions[2].data.type, 'configure'); }); - }); suite('chat session disposal cleanup', () => { From 4b3b3c9507c3a9e13a21859ea70684bd5fd3df33 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 15:42:40 +0200 Subject: [PATCH 0208/4355] adopt code completion --- .../promptHeaderAutocompletion.ts | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 92c425f90bb..12644cb61eb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -14,13 +14,10 @@ import { ILanguageFeaturesService } from '../../../../../../editor/common/servic import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { IChatModeService } from '../../chatModes.js'; -import { InstructionsHeader } from '../parsers/promptHeader/instructionsHeader.js'; -import { PromptToolsMetadata } from '../parsers/promptHeader/metadata/tools.js'; -import { ModeHeader } from '../parsers/promptHeader/modeHeader.js'; -import { PromptHeader } from '../parsers/promptHeader/promptHeader.js'; import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; +import { PromptHeader } from '../service/newPromptsParser.js'; export class PromptHeaderAutocompletion extends Disposable implements CompletionItemProvider { /** @@ -62,27 +59,14 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion return undefined; } - const parser = this.promptsService.getSyntaxParserFor(model); - await parser.start(token).settled(); - - if (token.isCancellationRequested) { - return undefined; - } - + const parser = this.promptsService.getParsedPromptFile(model); const header = parser.header; if (!header) { return undefined; } - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { - return undefined; - } - - const fullHeaderRange = parser.header.range; - const headerRange = new Range(fullHeaderRange.startLineNumber + 1, 0, fullHeaderRange.endLineNumber - 1, model.getLineMaxColumn(fullHeaderRange.endLineNumber - 1),); - - if (!headerRange.containsPosition(position)) { + const headerRange = parser.header.range; + if (position.lineNumber < headerRange.startLineNumber || position.lineNumber >= headerRange.endLineNumber) { // if the position is not inside the header, we don't provide any completions return undefined; } @@ -103,7 +87,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion position: Position, headerRange: Range, colonPosition: Position | undefined, - promptType: string, + promptType: PromptsType, ): Promise { const suggestions: CompletionItem[] = []; @@ -140,9 +124,9 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion private async provideValueCompletions( model: ITextModel, position: Position, - header: PromptHeader | ModeHeader | InstructionsHeader, + header: PromptHeader, colonPosition: Position, - promptType: string, + promptType: PromptsType, ): Promise { const suggestions: CompletionItem[] = []; @@ -153,14 +137,11 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion return undefined; } - if (header instanceof PromptHeader || header instanceof ModeHeader) { - const tools = header.metadataUtility.tools; - if (tools) { - // if the position is inside the tools metadata, we provide tool name completions - const result = this.provideToolCompletions(model, position, tools); - if (result) { - return result; - } + if (promptType === PromptsType.prompt || promptType === PromptsType.mode) { + // if the position is inside the tools metadata, we provide tool name completions + const result = this.provideToolCompletions(model, position, header); + if (result) { + return result; } } @@ -244,9 +225,9 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion return result; } - private provideToolCompletions(model: ITextModel, position: Position, node: PromptToolsMetadata): CompletionList | undefined { - const tools = node.value; - if (!tools || !node.range.containsPosition(position)) { + private provideToolCompletions(model: ITextModel, position: Position, header: PromptHeader): CompletionList | undefined { + const toolsAttr = header.attributes.find(attr => attr.key === 'tools'); + if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.range.containsPosition(position)) { return undefined; } const getSuggestions = (toolRange: Range) => { @@ -278,11 +259,10 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion return { suggestions }; }; - for (const tool of tools) { - const toolRange = node.getToolRange(tool); - if (toolRange?.containsPosition(position)) { + for (const toolNameNode of toolsAttr.value.items) { + if (toolNameNode.range.containsPosition(position)) { // if the position is inside a tool range, we provide tool name completions - return getSuggestions(toolRange); + return getSuggestions(toolNameNode.range); } } const prefix = model.getValueInRange(new Range(position.lineNumber, 1, position.lineNumber, position.column)); From f088ce25f4d7cbbd745a92fe7ebb164ad8246280 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:11:02 -0700 Subject: [PATCH 0209/4355] Reduce repetition in runInTerminalTool tests --- .../test/browser/runInTerminalTool.test.ts | 441 ++++++------------ 1 file changed, 150 insertions(+), 291 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 6028138d986..3bb062e5594 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -449,38 +449,54 @@ suite('RunInTerminalTool', () => { suite('prepareToolInvocation - custom actions for dropdown', () => { + function assertDropdownActions(result: IPreparedToolInvocation | undefined, items: ({ subCommand: string | string[] } | 'commandLine' | '---' | 'configure')[]) { + const actions = result?.confirmationMessages?.terminalCustomActions!; + ok(actions, 'Expected custom actions to be defined'); + + strictEqual(actions.length, items.length); + + for (const [i, item] of items.entries()) { + const action = actions[i]; + if (item === '---') { + ok(isSeparator(action)); + } else { + ok(!isSeparator(action)); + if (item === 'configure') { + strictEqual(action.label, 'Configure Auto Approve...'); + strictEqual(action.data.type, 'configure'); + } else if (item === 'commandLine') { + strictEqual(action.label, 'Always Allow Exact Command Line'); + strictEqual(action.data.type, 'newRule'); + ok(!Array.isArray(action.data.rule), 'Expected rule to be an object'); + } else { + if (Array.isArray(item.subCommand)) { + strictEqual(action.label, `Always Allow Commands: ${item.subCommand.join(', ')}`); + } else { + strictEqual(action.label, `Always Allow Command: ${item.subCommand}`); + } + strictEqual(action.data.type, 'newRule'); + ok(Array.isArray(action.data.rule), 'Expected rule to be an array'); + } + } + } + } + test('should generate custom actions for non-auto-approved commands', async () => { setAutoApprove({ ls: true, }); - const result = await executeToolTest({ command: 'npm run build', explanation: 'Build the project' }); assertConfirmationRequired(result, 'Run `pwsh` command?'); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: npm run build'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - - ok(!isSeparator(customActions[1])); - strictEqual(customActions[1].label, 'Always Allow Exact Command Line'); - strictEqual(customActions[1].data.type, 'newRule'); - ok(!Array.isArray(customActions[1].data.rule), 'Expected rule to be an object'); - - ok(isSeparator(customActions[2])); - - ok(!isSeparator(customActions[3])); - strictEqual(customActions[3].label, 'Configure Auto Approve...'); - strictEqual(customActions[3].data.type, 'configure'); + assertDropdownActions(result, [ + { subCommand: 'npm run build' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should generate custom actions for single word commands', async () => { @@ -490,29 +506,17 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - - strictEqual(customActions.length, 3); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: foo'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - - ok(isSeparator(customActions[1])); - - ok(!isSeparator(customActions[2])); - strictEqual(customActions[2].label, 'Configure Auto Approve...'); - strictEqual(customActions[2].data.type, 'configure'); + assertDropdownActions(result, [ + { subCommand: 'foo' }, + '---', + 'configure', + ]); }); test('should not generate custom actions for auto-approved commands', async () => { setAutoApprove({ npm: true }); - const result = await executeToolTest({ command: 'npm run build', explanation: 'Build the project' @@ -525,21 +529,15 @@ suite('RunInTerminalTool', () => { setAutoApprove({ npm: { approve: false } }); - const result = await executeToolTest({ command: 'npm run build', explanation: 'Build the project' }); assertConfirmationRequired(result, 'Run `pwsh` command?'); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 1, 'Expected only 1 custom action for explicitly denied commands'); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Configure Auto Approve...'); - strictEqual(customActions[0].data.type, 'configure'); + assertDropdownActions(result, [ + 'configure', + ]); }); test('should handle && in command line labels with proper mnemonic escaping', async () => { @@ -549,55 +547,30 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result, 'Run `pwsh` command?'); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Commands: npm install, npm run build'); - strictEqual(customActions[0].data.type, 'newRule'); - - ok(!isSeparator(customActions[1])); - strictEqual(customActions[1].label, 'Always Allow Exact Command Line'); - strictEqual(customActions[1].data.type, 'newRule'); - - ok(isSeparator(customActions[2])); - - ok(!isSeparator(customActions[3])); - strictEqual(customActions[3].label, 'Configure Auto Approve...'); - strictEqual(customActions[3].data.type, 'configure'); + assertDropdownActions(result, [ + { subCommand: ['npm install', 'npm run build'] }, + 'commandLine', + '---', + 'configure', + ]); }); test('should not show approved commands in custom actions dropdown', async () => { setAutoApprove({ head: true // head is approved by default in real scenario }); - const result = await executeToolTest({ command: 'foo | head -20', explanation: 'Run foo command and show first 20 lines' }); assertConfirmationRequired(result, 'Run `pwsh` command?'); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: foo', 'Should only show \'foo\' since \'head\' is auto-approved'); - strictEqual(customActions[0].data.type, 'newRule'); - - ok(!isSeparator(customActions[1])); - strictEqual(customActions[1].label, 'Always Allow Exact Command Line'); - strictEqual(customActions[1].data.type, 'newRule'); - - ok(isSeparator(customActions[2])); - - ok(!isSeparator(customActions[3])); - strictEqual(customActions[3].label, 'Configure Auto Approve...'); - strictEqual(customActions[3].data.type, 'configure'); + assertDropdownActions(result, [ + { subCommand: 'foo' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should not show any command-specific actions when all sub-commands are approved', async () => { @@ -605,7 +578,6 @@ suite('RunInTerminalTool', () => { foo: true, head: true }); - const result = await executeToolTest({ command: 'foo | head -20', explanation: 'Run foo command and show first 20 lines' @@ -619,31 +591,18 @@ suite('RunInTerminalTool', () => { head: true, tail: true }); - const result = await executeToolTest({ command: 'foo | head -20 && bar | tail -10', explanation: 'Run multiple piped commands' }); assertConfirmationRequired(result, 'Run `pwsh` command?'); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Commands: foo, bar', 'Should only show \'foo, bar\' since \'head\' and \'tail\' are auto-approved'); - strictEqual(customActions[0].data.type, 'newRule'); - - ok(!isSeparator(customActions[1])); - strictEqual(customActions[1].label, 'Always Allow Exact Command Line'); - strictEqual(customActions[1].data.type, 'newRule'); - - ok(isSeparator(customActions[2])); - - ok(!isSeparator(customActions[3])); - strictEqual(customActions[3].label, 'Configure Auto Approve...'); - strictEqual(customActions[3].data.type, 'configure'); + assertDropdownActions(result, [ + { subCommand: ['foo', 'bar'] }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest subcommand for git commands', async () => { @@ -653,16 +612,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: git status'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'git status'); + assertDropdownActions(result, [ + { subCommand: 'git status' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest subcommand for npm commands', async () => { @@ -672,16 +627,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: npm test'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'npm test'); + assertDropdownActions(result, [ + { subCommand: 'npm test' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest 3-part subcommand for npm run commands', async () => { @@ -691,16 +642,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: npm run build'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'npm run build'); + assertDropdownActions(result, [ + { subCommand: 'npm run build' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest 3-part subcommand for yarn run commands', async () => { @@ -710,16 +657,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: yarn run test'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'yarn run test'); + assertDropdownActions(result, [ + { subCommand: 'yarn run test' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should not suggest subcommand for commands with flags', async () => { @@ -729,16 +672,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: foo'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'foo'); + assertDropdownActions(result, [ + { subCommand: 'foo' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should not suggest subcommand for npm run with flags', async () => { @@ -748,16 +687,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: npm run abc'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'npm run abc'); + assertDropdownActions(result, [ + { subCommand: 'npm run abc' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should handle mixed npm run and other commands', async () => { @@ -767,19 +702,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Commands: npm run build, git status'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - const rules = customActions[0].data.rule as any; - strictEqual(rules.length, 2); - strictEqual(rules[0].key, 'npm run build'); - strictEqual(rules[1].key, 'git status'); + assertDropdownActions(result, [ + { subCommand: ['npm run build', 'git status'] }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest mixed subcommands and base commands', async () => { @@ -789,19 +717,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Commands: git push, echo'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - const rules = customActions[0].data.rule as any; - strictEqual(rules.length, 2); - strictEqual(rules[0].key, 'git push'); - strictEqual(rules[1].key, 'echo'); + assertDropdownActions(result, [ + { subCommand: ['git push', 'echo'] }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest subcommands for multiple git commands', async () => { @@ -811,19 +732,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Commands: git status, git log'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - const rules = customActions[0].data.rule as any; - strictEqual(rules.length, 2); - strictEqual(rules[0].key, 'git status'); - strictEqual(rules[1].key, 'git log'); + assertDropdownActions(result, [ + { subCommand: ['git status', 'git log'] }, + 'commandLine', + '---', + 'configure', + ]); }); test('should suggest base command for non-subcommand tools', async () => { @@ -833,16 +747,12 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: foo'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - strictEqual((customActions[0].data.rule as any)[0].key, 'foo'); + assertDropdownActions(result, [ + { subCommand: 'foo' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should handle single word commands from subcommand-aware tools', async () => { @@ -852,14 +762,9 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 1); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Configure Auto Approve...'); - strictEqual(customActions[0].data.type, 'configure'); + assertDropdownActions(result, [ + 'configure', + ]); }); test('should deduplicate identical subcommand suggestions', async () => { @@ -869,87 +774,41 @@ suite('RunInTerminalTool', () => { }); assertConfirmationRequired(result); - ok(result!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const customActions = result!.confirmationMessages!.terminalCustomActions!; - strictEqual(customActions.length, 4); - - ok(!isSeparator(customActions[0])); - strictEqual(customActions[0].label, 'Always Allow Command: npm test'); - strictEqual(customActions[0].data.type, 'newRule'); - ok(Array.isArray(customActions[0].data.rule), 'Expected rule to be an array'); - const rules = customActions[0].data.rule as any; - strictEqual(rules.length, 1); // Should be deduplicated - strictEqual(rules[0].key, 'npm test'); + assertDropdownActions(result, [ + { subCommand: 'npm test' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should handle flags differently than subcommands for suggestion logic', async () => { - // Test that commands with actual subcommands get subcommand suggestions - const resultWithSubcommand = await executeToolTest({ - command: 'npm install express', - explanation: 'Install express package' - }); - - assertConfirmationRequired(resultWithSubcommand); - ok(resultWithSubcommand!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const actionsWithSubcommand = resultWithSubcommand!.confirmationMessages!.terminalCustomActions!; - strictEqual(actionsWithSubcommand.length, 4); - - ok(!isSeparator(actionsWithSubcommand[0])); - strictEqual(actionsWithSubcommand[0].label, 'Always Allow Command: npm install'); - strictEqual(actionsWithSubcommand[0].data.type, 'newRule'); - - ok(!isSeparator(actionsWithSubcommand[1])); - strictEqual(actionsWithSubcommand[1].label, 'Always Allow Exact Command Line'); - strictEqual(actionsWithSubcommand[1].data.type, 'newRule'); - - // Test that commands with flags don't get subcommand suggestions - const resultWithFlags = await executeToolTest({ - command: 'npm --version', - explanation: 'Check npm version' + const result = await executeToolTest({ + command: 'foo --version', + explanation: 'Check foo version' }); - assertConfirmationRequired(resultWithFlags); - ok(resultWithFlags!.confirmationMessages!.terminalCustomActions, 'Expected custom actions to be defined'); - - const actionsWithFlags = resultWithFlags!.confirmationMessages!.terminalCustomActions!; - strictEqual(actionsWithFlags.length, 3); // No subcommand suggestion, only exact command line - - ok(!isSeparator(actionsWithFlags[0])); - strictEqual(actionsWithFlags[0].label, 'Always Allow Exact Command Line'); - strictEqual(actionsWithFlags[0].data.type, 'newRule'); - ok(!Array.isArray(actionsWithFlags[0].data.rule), 'Expected rule to be an object for exact command line'); - - ok(isSeparator(actionsWithFlags[1])); - - ok(!isSeparator(actionsWithFlags[2])); - strictEqual(actionsWithFlags[2].label, 'Configure Auto Approve...'); - strictEqual(actionsWithFlags[2].data.type, 'configure'); + assertConfirmationRequired(result); + assertDropdownActions(result, [ + { subCommand: 'foo' }, + 'commandLine', + '---', + 'configure', + ]); }); test('should not suggest overly permissive subcommand rules', async () => { - const bashResult = await executeToolTest({ + const result = await executeToolTest({ command: 'bash -c "echo hello"', explanation: 'Run bash command' }); - const actions = bashResult?.confirmationMessages?.terminalCustomActions; - - assertConfirmationRequired(bashResult); - ok(actions, 'Expected custom actions to be defined'); - strictEqual(actions.length, 3); - - ok(!isSeparator(actions[0])); - strictEqual(actions[0].label, 'Always Allow Exact Command Line'); - strictEqual(actions[0].data.type, 'newRule'); - ok(!Array.isArray(actions[0].data.rule), 'Expected rule to be an object for exact command line'); - - ok(isSeparator(actions[1])); - - ok(!isSeparator(actions[2])); - strictEqual(actions[2].label, 'Configure Auto Approve...'); - strictEqual(actions[2].data.type, 'configure'); + assertConfirmationRequired(result); + assertDropdownActions(result, [ + 'commandLine', + '---', + 'configure', + ]); }); }); From 3feeafed5cc8f78b5b860fabb26d29681c4d4b5d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:22:44 +0200 Subject: [PATCH 0210/4355] SCM - add Open File action to the context menu (#266199) --- .../contrib/scm/browser/scmHistoryChatContext.ts | 12 ++++++------ .../contrib/scm/browser/scmHistoryViewPane.ts | 12 +++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index a41865b6a61..0894841d26d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -318,18 +318,18 @@ registerAction2(class extends Action2 { }); } - override async run(accessor: ServicesAccessor, provider: ISCMProvider, arg: { historyItem: ISCMHistoryItem; historyItemChange: ISCMHistoryItemChange }): Promise { + override async run(accessor: ServicesAccessor, historyItem: ISCMHistoryItem, historyItemChange: ISCMHistoryItemChange): Promise { const viewsService = accessor.get(IViewsService); const widget = await showChatView(viewsService); - if (!provider || !arg.historyItem || !arg.historyItemChange.modifiedUri || !widget) { + if (!historyItem || !historyItemChange.modifiedUri || !widget) { return; } widget.attachmentModel.addContext({ - id: arg.historyItemChange.uri.toString(), - name: `${basename(arg.historyItemChange.modifiedUri)}`, - value: arg.historyItemChange.modifiedUri, - historyItem: arg.historyItem, + id: historyItemChange.uri.toString(), + name: `${basename(historyItemChange.modifiedUri)}`, + value: historyItemChange.modifiedUri, + historyItem: historyItem, kind: 'scmHistoryItemChange', } satisfies ISCMHistoryItemChangeVariableEntry); } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 353f96e68a0..d77b0d27609 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -347,6 +347,11 @@ registerAction2(class extends Action2 { group: 'inline', order: 1 }, + { + id: MenuId.SCMHistoryItemChangeContext, + group: '0_view', + order: 1 + } ] }); } @@ -2040,7 +2045,7 @@ export class SCMHistoryViewPane extends ViewPane { const menuActions = this._menuService.getMenuActions( MenuId.SCMHistoryItemChangeContext, this.scopedContextKeyService, { - arg: element.repository.provider, + arg: element.historyItemViewModel.historyItem, shouldForwardArgs: true }).filter(group => group[0] !== 'inline'); @@ -2048,10 +2053,7 @@ export class SCMHistoryViewPane extends ViewPane { contextKeyService: this.scopedContextKeyService, getAnchor: () => e.anchor, getActions: () => getFlatContextMenuActions(menuActions), - getActionsContext: () => ({ - historyItem: element.historyItemViewModel.historyItem, - historyItemChange: element.historyItemChange - }) + getActionsContext: () => element.historyItemChange }); } } From 6343813d67950e49b3e5625ca0aba088bfd76a24 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Sep 2025 17:00:25 +0200 Subject: [PATCH 0211/4355] Also send edit stats for edits that got fully removed. (#266206) --- .../telemetry/editSourceTrackingImpl.ts | 22 +++++++++--------- .../browser/telemetry/editTracker.ts | 23 +++++++++++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts index af4680f1728..eb8abb6dd96 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts @@ -9,7 +9,6 @@ import { toDisposable, Disposable } from '../../../../../base/common/lifecycle.j import { mapObservableArrayCached, derived, IObservable, observableSignal, runOnChange } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; -import { TextModelEditSource } from '../../../../../editor/common/textModelEditSource.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { ISCMRepository, ISCMService } from '../../../scm/common/scm.js'; @@ -135,32 +134,33 @@ class TrackedDocumentInfo extends Disposable { async sendTelemetry(mode: 'longterm' | '5minWindow', trigger: string, t: DocumentEditSourceTracker) { const ranges = t.getTrackedRanges(); - if (ranges.length === 0) { + const keys = t.getAllKeys(); + if (keys.length === 0) { return; } const data = this.getTelemetryData(ranges); - const statsUuid = generateUuid(); - const sourceKeyToRepresentative = new Map(); - for (const r of ranges) { - sourceKeyToRepresentative.set(r.sourceKey, r.sourceRepresentative); - } - const sums = sumByCategory(ranges, r => r.range.length, r => r.sourceKey); const entries = Object.entries(sums).filter(([key, value]) => value !== undefined); entries.sort(reverseOrder(compareBy(([key, value]) => value!, numberComparator))); entries.length = mode === 'longterm' ? 30 : 10; + for (const key of keys) { + if (!sums[key]) { + sums[key] = 0; + } + } + for (const [key, value] of Object.entries(sums)) { if (value === undefined) { continue; } - const repr = sourceKeyToRepresentative.get(key)!; - const m = t.getChangedCharactersCount(key); + const repr = t.getRepresentative(key)!; + const deltaModifiedCount = t.getTotalInsertedCharactersCount(key); this._telemetryService.publicLog2<{ mode: string; @@ -211,7 +211,7 @@ class TrackedDocumentInfo extends Disposable { languageId: this._doc.document.languageId.get(), statsUuid: statsUuid, modifiedCount: value, - deltaModifiedCount: m, + deltaModifiedCount: deltaModifiedCount, totalModifiedCount: data.totalModifiedCharactersInFinalState, }); } diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editTracker.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editTracker.ts index d134f16982b..dc97af0ce82 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editTracker.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editTracker.ts @@ -19,7 +19,8 @@ export class DocumentEditSourceTracker extends Disposable { private _pendingExternalEdits: AnnotatedStringEdit = AnnotatedStringEdit.empty; private readonly _update = observableSignal(this); - private readonly _sumAddedCharactersPerKey: Map = new Map(); + private readonly _representativePerKey: Map = new Map(); + private readonly _sumAddedCharactersPerKey: Map = new Map(); constructor( private readonly _doc: IDocumentWithAnnotatedEdits, @@ -50,7 +51,11 @@ export class DocumentEditSourceTracker extends Disposable { private _applyEdit(e: AnnotatedStringEdit): void { for (const r of e.replacements) { - const existing = this._sumAddedCharactersPerKey.get(r.data.key) ?? 0; + let existing = this._sumAddedCharactersPerKey.get(r.data.key); + if (existing === undefined) { + existing = 0; + this._representativePerKey.set(r.data.key, r.data.representative); + } const newCount = existing + r.getNewLength(); this._sumAddedCharactersPerKey.set(r.data.key, newCount); } @@ -62,11 +67,19 @@ export class DocumentEditSourceTracker extends Disposable { await this._doc.waitForQueue(); } - public getChangedCharactersCount(key: string): number { + public getTotalInsertedCharactersCount(key: string): number { const val = this._sumAddedCharactersPerKey.get(key); return val ?? 0; } + public getAllKeys(): string[] { + return Array.from(this._sumAddedCharactersPerKey.keys()); + } + + public getRepresentative(key: string): TextModelEditSource | undefined { + return this._representativePerKey.get(key); + } + getTrackedRanges(reader?: IReader): TrackedEdit[] { this._update.read(reader); const ranges = this._edits.getNewRanges(); @@ -81,10 +94,6 @@ export class DocumentEditSourceTracker extends Disposable { return this._edits.isEmpty(); } - public reset(): void { - this._edits = AnnotatedStringEdit.empty; - } - public _getDebugVisualization() { const ranges = this.getTrackedRanges(); const txt = this._doc.value.get().value; From f9145afad26457527dd50d8101f1b7f3f91612f3 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 17:07:46 +0200 Subject: [PATCH 0212/4355] adopt getParsedPromptFile --- .../promptSyntax/promptFileRewriter.ts | 24 ++---- .../promptToolsCodeLensProvider.ts | 39 ++++------ .../PromptHeaderDefinitionProvider.ts | 32 +------- .../promptHeaderAutocompletion.ts | 2 +- .../languageProviders/promptHeaderHovers.ts | 76 +++++++------------ .../languageProviders/promptLinkProvider.ts | 65 +++++----------- .../promptSyntax/service/newPromptsParser.ts | 9 ++- .../promptSyntax/service/promptsService.ts | 6 ++ .../service/promptsServiceImpl.ts | 6 +- .../service/newPromptsParser.test.ts | 8 +- 10 files changed, 96 insertions(+), 171 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts index c87e8d9c7e3..21072444d07 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts @@ -26,29 +26,19 @@ export class PromptFileRewriter { } const model = editor.getModel(); - const parser = this._promptsService.getSyntaxParserFor(model); - await parser.start(token).settled(); - const { header } = parser; - if (header === undefined) { + const parser = this._promptsService.getParsedPromptFile(model); + if (!parser.header) { return undefined; } - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { - return; - } - - if (('tools' in header.metadataUtility) === false) { + const toolsAttr = parser.header.getAttribute('tools'); + if (!toolsAttr) { return undefined; } - const { tools } = header.metadataUtility; - if (tools === undefined) { - return undefined; - } - editor.setSelection(tools.range); - this.rewriteTools(model, newTools, tools.range); - } + editor.setSelection(toolsAttr.range); + this.rewriteTools(model, newTools, toolsAttr.range); + } public rewriteTools(model: ITextModel, newTools: IToolAndToolSetEnablementMap | undefined, range: Range): void { const newString = newTools === undefined ? '' : `tools: ${this.getNewValueString(newTools)}`; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 904ff2dff6f..f06236bb2a6 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -15,10 +15,10 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { showToolsPicker } from '../actions/chatToolPicker.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../common/promptSyntax/promptTypes.js'; -import { PromptToolsMetadata } from '../../common/promptSyntax/parsers/promptHeader/metadata/tools.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { registerEditorFeature } from '../../../../../editor/common/editorFeatures.js'; import { PromptFileRewriter } from './promptFileRewriter.js'; +import { Range } from '../../../../../editor/common/core/range.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { @@ -37,56 +37,47 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider this._register(this.languageService.codeLensProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this)); this._register(CommandsRegistry.registerCommand(this.cmdId, (_accessor, ...args) => { - const [first, second] = args; - if (isITextModel(first) && second instanceof PromptToolsMetadata) { - this.updateTools(first, second); + const [first, second, third] = args; + if (isITextModel(first) && Range.isIRange(second) && Array.isArray(third)) { + this.updateTools(first, Range.lift(second), third); } })); } async provideCodeLenses(model: ITextModel, token: CancellationToken): Promise { - const parser = this.promptsService.getSyntaxParserFor(model); - - await parser.start(token).settled(); - const { header } = parser; - if (!header) { - return undefined; - } - - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { + const parser = this.promptsService.getParsedPromptFile(model); + if (!parser.header) { return undefined; } - if (('tools' in header.metadataUtility) === false) { - return undefined; - } - const { tools } = header.metadataUtility; - if (tools === undefined) { + const toolsAttr = parser.header.getAttribute('tools'); + if (!toolsAttr || toolsAttr.value.type !== 'array') { return undefined; } + const items = toolsAttr.value.items; + const selectedTools = items.filter(item => item.type === 'string').map(item => item.value); const codeLens: CodeLens = { - range: tools.range.collapseToStart(), + range: toolsAttr.range.collapseToStart(), command: { title: localize('configure-tools.capitalized.ellipsis', "Configure Tools..."), id: this.cmdId, - arguments: [model, tools] + arguments: [model, toolsAttr.range, selectedTools] } }; return { lenses: [codeLens] }; } - private async updateTools(model: ITextModel, tools: PromptToolsMetadata) { + private async updateTools(model: ITextModel, range: Range, selectedTools: readonly string[]) { - const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(tools.value) : new Map(); + const selectedToolsNow = this.languageModelToolsService.toToolAndToolSetEnablementMap(selectedTools); const newSelectedAfter = await this.instantiationService.invokeFunction(showToolsPicker, localize('placeholder', "Select tools"), undefined, selectedToolsNow); if (!newSelectedAfter) { return; } - await this.instantiationService.createInstance(PromptFileRewriter).rewriteTools(model, newSelectedAfter, tools.range); + await this.instantiationService.createInstance(PromptFileRewriter).rewriteTools(model, newSelectedAfter, range); } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts index 785b407da9c..f9afca76373 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts @@ -13,8 +13,6 @@ import { ILanguageFeaturesService } from '../../../../../../editor/common/servic import { IChatModeService } from '../../chatModes.js'; import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { PromptModeMetadata } from '../parsers/promptHeader/metadata/mode.js'; -import { PromptHeader } from '../parsers/promptHeader/promptHeader.js'; export class PromptHeaderDefinitionProvider extends Disposable implements DefinitionProvider { /** @@ -38,37 +36,15 @@ export class PromptHeaderDefinitionProvider extends Disposable implements Defini return undefined; } - const parser = this.promptsService.getSyntaxParserFor(model); - await parser.start(token).settled(); - - if (token.isCancellationRequested) { - return undefined; - } - + const parser = this.promptsService.getParsedPromptFile(model); const header = parser.header; if (!header) { return undefined; } - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { - return undefined; - } - - if (header instanceof PromptHeader) { - const mode = header.metadataUtility.mode; - if (mode?.range.containsPosition(position)) { - return this.getModeDefinition(mode, position); - } - } - return undefined; - } - - - private getModeDefinition(mode: PromptModeMetadata, position: Position): Definition | undefined { - const value = mode.value; - if (value && mode.valueRange?.containsPosition(position)) { - const mode = this.chatModeService.findModeByName(value); + const modeAttr = header.getAttribute('mode'); + if (modeAttr && modeAttr.value.type === 'string' && modeAttr.range.containsPosition(position)) { + const mode = this.chatModeService.findModeByName(modeAttr.value.value); if (mode && mode.uri) { return { uri: mode.uri.get(), diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 12644cb61eb..61bbbcc7754 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -226,7 +226,7 @@ export class PromptHeaderAutocompletion extends Disposable implements Completion } private provideToolCompletions(model: ITextModel, position: Position, header: PromptHeader): CompletionList | undefined { - const toolsAttr = header.attributes.find(attr => attr.key === 'tools'); + const toolsAttr = header.getAttribute('tools'); if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.range.containsPosition(position)) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderHovers.ts index 9072b2bf21f..050ccbebe23 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderHovers.ts @@ -15,13 +15,9 @@ import { localize } from '../../../../../../nls.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService, ToolSet } from '../../languageModelToolsService.js'; import { IChatModeService, isBuiltinChatMode } from '../../chatModes.js'; -import { InstructionsHeader } from '../parsers/promptHeader/instructionsHeader.js'; -import { PromptModelMetadata } from '../parsers/promptHeader/metadata/model.js'; -import { PromptToolsMetadata } from '../parsers/promptHeader/metadata/tools.js'; -import { ModeHeader } from '../parsers/promptHeader/modeHeader.js'; -import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId } from '../promptTypes.js'; +import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { PromptModeMetadata } from '../parsers/promptHeader/metadata/mode.js'; +import { IHeaderAttribute } from '../service/newPromptsParser.js'; export class PromptHeaderHoverProvider extends Disposable implements HoverProvider { /** @@ -61,60 +57,49 @@ export class PromptHeaderHoverProvider extends Disposable implements HoverProvid return undefined; } - const parser = this.promptsService.getSyntaxParserFor(model); - await parser.start(token).settled(); - - if (token.isCancellationRequested) { - return undefined; - } - + const parser = this.promptsService.getParsedPromptFile(model); const header = parser.header; if (!header) { return undefined; } - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { - return undefined; - } - - if (header instanceof InstructionsHeader) { - const descriptionRange = header.metadataUtility.description?.range; + if (promptType === PromptsType.instructions) { + const descriptionRange = header.getAttribute('description')?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), descriptionRange); } - const applyToRange = header.metadataUtility.applyTo?.range; + const applyToRange = header.getAttribute('applyTo')?.range; if (applyToRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: **/*.ts, **/*.js, client/**'), applyToRange); } - } else if (header instanceof ModeHeader) { - const descriptionRange = header.metadataUtility.description?.range; + } else if (promptType === PromptsType.mode) { + const descriptionRange = header.getAttribute('description')?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.mode.description', 'The description of the mode file. It can be used to provide additional context or information about the mode to the mode author.'), descriptionRange); } - const model = header.metadataUtility.model; + const model = header.getAttribute('model'); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.mode.model', 'The model to use in this mode.')); } - const tools = header.metadataUtility.tools; + const tools = header.getAttribute('tools'); if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.mode.tools', 'The tools to use in this mode.')); } } else { - const descriptionRange = header.metadataUtility.description?.range; + const descriptionRange = header.getAttribute('description')?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It can be used to provide additional context or information about the prompt to the prompt author.'), descriptionRange); } - const model = header.metadataUtility.model; + const model = header.getAttribute('model'); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.')); } - const tools = header.metadataUtility.tools; + const tools = header.getAttribute('tools'); if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.')); } - const mode = header.metadataUtility.mode; + const mode = header.getAttribute('mode'); if (mode?.range.containsPosition(position)) { return this.getModeHover(mode, position, localize('promptHeader.prompt.mode', 'The mode to use in this prompt.')); } @@ -122,19 +107,17 @@ export class PromptHeaderHoverProvider extends Disposable implements HoverProvid return undefined; } - private getToolHover(node: PromptToolsMetadata, position: Position, baseMessage: string): Hover | undefined { - if (node.value) { - - for (const toolName of node.value) { - const toolRange = node.getToolRange(toolName); - if (toolRange?.containsPosition(position)) { - const tool = this.languageModelToolsService.getToolByName(toolName); + private getToolHover(node: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { + if (node.value.type === 'array') { + for (const toolName of node.value.items) { + if (toolName.type === 'string' && toolName.range.containsPosition(position)) { + const tool = this.languageModelToolsService.getToolByName(toolName.value); if (tool) { - return this.createHover(tool.modelDescription, toolRange); + return this.createHover(tool.modelDescription, toolName.range); } - const toolSet = this.languageModelToolsService.getToolSetByName(toolName); + const toolSet = this.languageModelToolsService.getToolSetByName(toolName.value); if (toolSet) { - return this.getToolsetHover(toolSet, toolRange); + return this.getToolsetHover(toolSet, toolName.range); } } } @@ -154,12 +137,11 @@ export class PromptHeaderHoverProvider extends Disposable implements HoverProvid return this.createHover(lines.join('\n'), range); } - private getModelHover(node: PromptModelMetadata, range: Range, baseMessage: string): Hover | undefined { - const modelName = node.value; - if (modelName) { + private getModelHover(node: IHeaderAttribute, range: Range, baseMessage: string): Hover | undefined { + if (node.value.type === 'string') { for (const id of this.languageModelsService.getLanguageModelIds()) { const meta = this.languageModelsService.lookupLanguageModel(id); - if (meta && ILanguageModelChatMetadata.matchesQualifiedName(modelName, meta)) { + if (meta && ILanguageModelChatMetadata.matchesQualifiedName(node.value.value, meta)) { const lines: string[] = []; lines.push(baseMessage + '\n'); lines.push(localize('modelName', '- Name: {0}', meta.name)); @@ -175,13 +157,11 @@ export class PromptHeaderHoverProvider extends Disposable implements HoverProvid return this.createHover(baseMessage, range); } - private getModeHover(mode: PromptModeMetadata, position: Position, baseMessage: string): Hover | undefined { + private getModeHover(mode: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { const lines: string[] = []; - - const value = mode.value; - if (value && mode.valueRange?.containsPosition(position)) { - const mode = this.chatModeService.findModeByName(value); + if (value.type === 'string' && value.range.containsPosition(position)) { + const mode = this.chatModeService.findModeByName(value.value); if (mode) { const description = mode.description.get() || (isBuiltinChatMode(mode) ? localize('promptHeader.prompt.mode.builtInDesc', 'Built-in chat mode') : localize('promptHeader.prompt.mode.customDesc', 'Custom chat mode')); lines.push(`\`${mode.name}\`: ${description}`); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts index 22b498d8b3a..53e408c38dc 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts @@ -4,67 +4,40 @@ *--------------------------------------------------------------------------------------------*/ import { IPromptsService } from '../service/promptsService.js'; -import { assert } from '../../../../../../base/common/assert.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { CancellationError } from '../../../../../../base/common/errors.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { ILink, ILinksList, LinkProvider } from '../../../../../../editor/common/languages.js'; +import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../promptTypes.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js'; /** * Provides link references for prompt files. */ -export class PromptLinkProvider implements LinkProvider { +export class PromptLinkProvider extends Disposable implements LinkProvider { constructor( + @ILanguageFeaturesService languageService: ILanguageFeaturesService, @IPromptsService private readonly promptsService: IPromptsService, ) { + super(); + this._register(languageService.linkProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this)); } /** * Provide list of links for the provided text model. */ - public async provideLinks( - model: ITextModel, - token: CancellationToken, - ): Promise { - assert( - !token.isCancellationRequested, - new CancellationError(), - ); - - const parser = this.promptsService.getSyntaxParserFor(model); - assert( - parser.isDisposed === false, - 'Prompt parser must not be disposed.', - ); - - // start the parser in case it was not started yet, - // and wait for it to settle to a final result - const completed = await parser.start(token).settled(); - if (!completed || token.isCancellationRequested) { - return undefined; + public async provideLinks(model: ITextModel, token: CancellationToken): Promise { + const parser = this.promptsService.getParsedPromptFile(model); + if (!parser.body) { + return; } - const { references } = parser; - - // filter out references that are not valid links - const links: ILink[] = references - .map((reference) => { - const { uri, linkRange } = reference; - - // must always be true because of the filter above - assertDefined( - linkRange, - 'Link range must be defined.', - ); - - return { - range: linkRange, - url: uri, - }; - }); - - return { - links, - }; + const links: ILink[] = []; + for (const ref of parser.body.fileReferences) { + const url = parser.body.resolveFilePath(ref.content); + if (url) { + links.push({ range: ref.range, url }); + } + } + return { links }; } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index f0c88fcb01d..bf35d5da09e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -117,6 +117,10 @@ export class PromptHeader { return this.getParsedHeader().attributes; } + public getAttribute(key: string): IHeaderAttribute | undefined { + return this.getParsedHeader().attributes.find(attr => attr.key === key); + } + public get errors(): ParseError[] { return this.getParsedHeader().errors; } @@ -232,7 +236,7 @@ export class PromptBody { const linkEndOffset = match.index + match[0].length - 1; // before the parenthesis const linkStartOffset = match.index + match[0].length - match[2].length - 1; const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); - fileReferences.push({ content: match[2], range }); + fileReferences.push({ content: match[2], range, isMarkdownLink: true }); markdownLinkRanges.push(new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)); } const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]*)`, 'g'); @@ -248,7 +252,7 @@ export class PromptBody { const linkStartOffset = match.index + match[0].length - match[2].length; const linkEndOffset = match.index + match[0].length; const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); - fileReferences.push({ content: match[2], range }); + fileReferences.push({ content: match[2], range, isMarkdownLink: false }); } } else { const contentStartOffset = match.index + 1; // after the # @@ -281,6 +285,7 @@ export class PromptBody { interface IBodyFileReference { content: string; range: Range; + isMarkdownLink: boolean; } interface IBodyVariableReference { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index ca85e45152f..5363d277802 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -213,6 +213,12 @@ export interface IPromptsService extends IDisposable { */ parse(uri: URI, type: PromptsType, token: CancellationToken): Promise; + /** + * Parses the provided URI + * @param uris + */ + parseNew(uri: URI, token: CancellationToken): Promise; + /** * Returns the prompt file type for the given URI. * @param resource the URI of the resource diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 68217698a22..36a75f027a2 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -30,6 +30,7 @@ import { PositionOffsetTransformer } from '../../../../../../editor/common/core/ import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; /** * Provides prompt services. @@ -326,12 +327,15 @@ export class PromptsService extends Disposable implements IPromptsService { } } - public async parseNew(uri: URI): Promise { + public async parseNew(uri: URI, token: CancellationToken): Promise { const model = this.modelService.getModel(uri); if (model) { return this.getParsedPromptFile(model); } const fileContent = await this.fileService.readFile(uri); + if (token.isCancellationRequested) { + throw new CancellationError(); + } return new NewPromptsParser().parse(uri, fileContent.value.toString()); } } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 62ee58be278..2353b60db80 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -42,8 +42,8 @@ suite('NewPromptsParser', () => { ]); assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); assert.deepEqual(result.body.fileReferences, [ - { range: new Range(7, 39, 7, 54), content: './reference1.md' }, - { range: new Range(7, 80, 7, 95), content: './reference2.md' } + { range: new Range(7, 39, 7, 54), content: './reference1.md', isMarkdownLink: false }, + { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ { range: new Range(7, 12, 7, 17), content: 'tool1' } @@ -74,7 +74,7 @@ suite('NewPromptsParser', () => { ]); assert.deepEqual(result.body.range, { startLineNumber: 5, startColumn: 1, endLineNumber: 6, endColumn: 1 }); assert.deepEqual(result.body.fileReferences, [ - { range: new Range(5, 64, 5, 103), content: 'https://mycomp/guidelines#typescript.md' }, + { range: new Range(5, 64, 5, 103), content: 'https://mycomp/guidelines#typescript.md', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, []); assert.deepEqual(result.header.description, 'Code style instructions for TypeScript'); @@ -111,7 +111,7 @@ suite('NewPromptsParser', () => { ]); assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); assert.deepEqual(result.body.fileReferences, [ - { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs' }, + { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ { range: new Range(7, 41, 7, 47), content: 'search' } From 58f906bc23d12b4b8df371c2a2e4fbc3c5f15e33 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 17:09:01 +0200 Subject: [PATCH 0213/4355] update --- src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index 6728e53f6a6..93709eefffc 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -35,6 +35,7 @@ export class MockPromptsService implements IPromptsService { resolvePromptSlashCommand(_data: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } findPromptSlashCommands(): Promise { throw new Error('Not implemented'); } parse(_uri: URI, _type: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } + parseNew(_uri: URI, _token: CancellationToken): Promise { throw new Error('Not implemented'); } getPromptFileType(_resource: URI): any { return undefined; } getParsedPromptFile(textModel: ITextModel): ParsedPromptFile { throw new Error('Not implemented'); } dispose(): void { } From 9989579497296e0c10d13377328fba9fa081d0b8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 11 Sep 2025 17:25:52 +0200 Subject: [PATCH 0214/4355] Add marked/dompurify dependencies to monaco-editor-core dependencies to allow for downstream dependency tracking. (#266213) --- build/gulpfile.editor.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index ece30e13152..39f83320051 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -148,6 +148,31 @@ const finalEditorResourcesTask = task.define('final-editor-resources', () => { .pipe(es.through(function (data) { const json = JSON.parse(data.contents.toString()); json.private = false; + + let markedVersion; + let dompurifyVersion; + try { + const markedManifestPath = path.join(root, 'src/vs/base/common/marked/cgmanifest.json'); + const dompurifyManifestPath = path.join(root, 'src/vs/base/browser/dompurify/cgmanifest.json'); + + const markedManifest = JSON.parse(fs.readFileSync(markedManifestPath, 'utf8')); + const dompurifyManifest = JSON.parse(fs.readFileSync(dompurifyManifestPath, 'utf8')); + + markedVersion = markedManifest.registrations[0].version; + dompurifyVersion = dompurifyManifest.registrations[0].version; + + if (!markedVersion || !dompurifyVersion) { + throw new Error('Unable to read versions from cgmanifest.json files'); + } + } catch (error) { + throw new Error(`Failed to read cgmanifest.json files for monaco-editor-core dependencies: ${error.message}`); + } + + setUnsetField(json, 'dependencies', { + 'marked': markedVersion, + 'dompurify': dompurifyVersion + }); + data.contents = Buffer.from(JSON.stringify(json, null, ' ')); this.emit('data', data); })) @@ -262,3 +287,16 @@ const monacoTypecheckTask = task.define('monaco-typecheck', createTscCompileTask exports.monacoTypecheckTask = monacoTypecheckTask; //#endregion + +/** + * Sets a field on an object only if it's not already set, otherwise throws an error + * @param {any} obj - The object to modify + * @param {string} field - The field name to set + * @param {any} value - The value to set + */ +function setUnsetField(obj, field, value) { + if (obj[field] !== undefined) { + throw new Error(`Field "${field}" is already set (but was expected to not be).`); + } + obj[field] = value; +} From 2a101129a39587b597187a5ae1dc6d6aadd5b2d9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 17:26:07 +0200 Subject: [PATCH 0215/4355] fix tests --- .../chat/common/promptSyntax/service/promptsServiceImpl.ts | 1 - .../test/common/promptSyntax/service/promptsService.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 36a75f027a2..779960cf9f9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -320,7 +320,6 @@ export class PromptsService extends Disposable implements IPromptsService { metadata: parser.metadata, variableReferences, fileReferences: parser.references.map(ref => ref.uri), - header: undefined }; } finally { parser?.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 69ea302d960..e2a7e64aa27 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -42,6 +42,7 @@ import { testWorkspace } from '../../../../../../../platform/workspace/test/comm import { IUserDataProfileService } from '../../../../../../services/userDataProfile/common/userDataProfile.js'; import { ITelemetryService } from '../../../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../../../platform/telemetry/common/telemetryUtils.js'; +import { Event } from '../../../../../../../base/common/event.js'; /** * Helper class to assert the properties of a link. @@ -139,7 +140,7 @@ suite('PromptsService', () => { const fileService = disposables.add(instaService.createInstance(FileService)); instaService.stub(IFileService, fileService); - instaService.stub(IModelService, { getModel() { return null; } }); + instaService.stub(IModelService, { getModel() { return null; }, onModelRemoved: Event.None }); instaService.stub(ILanguageService, { guessLanguageIdByFilepathOrFirstLine(uri: URI) { if (uri.path.endsWith(PROMPT_FILE_EXTENSION)) { @@ -699,7 +700,6 @@ suite('PromptsService', () => { const result1 = await service.parse(rootFileUri, PromptsType.prompt, CancellationToken.None); assert.deepEqual(result1, { uri: rootFileUri, - header: undefined, metadata: { promptType: PromptsType.prompt, description: 'Root prompt description.', From b09a4472e57c8c01393615ad1a871edf0428aff3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 11 Sep 2025 17:32:21 +0200 Subject: [PATCH 0216/4355] fix #266106 (#266212) --- src/vs/platform/mcp/common/mcpManagement.ts | 11 +- .../mcp/common/mcpManagementService.ts | 254 ++--- .../test/common/mcpManagementService.test.ts | 881 ++++++++++++++++++ .../contrib/mcp/browser/mcpServerEditor.ts | 10 +- 4 files changed, 1044 insertions(+), 112 deletions(-) create mode 100644 src/vs/platform/mcp/test/common/mcpManagementService.test.ts diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 4f8c399c885..c142a80349d 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -52,19 +52,19 @@ export interface IMcpServerVariableInput extends IMcpServerInput { export interface IMcpServerPositionalArgument extends IMcpServerVariableInput { readonly type: 'positional'; - readonly value_hint: string; - readonly is_repeatable: boolean; + readonly value_hint?: string; + readonly is_repeated?: boolean; } export interface IMcpServerNamedArgument extends IMcpServerVariableInput { readonly type: 'named'; readonly name: string; - readonly is_repeatable: boolean; + readonly is_repeated?: boolean; } export interface IMcpServerKeyValueInput extends IMcpServerVariableInput { readonly name: string; - readonly value: string; + readonly value?: string; } export type IMcpServerArgument = IMcpServerPositionalArgument | IMcpServerNamedArgument; @@ -72,7 +72,8 @@ export type IMcpServerArgument = IMcpServerPositionalArgument | IMcpServerNamedA export const enum RegistryType { NODE = 'npm', PYTHON = 'pypi', - DOCKER = 'docker-hub', + DOCKER = 'oci', + DOCKER_HUB = 'docker-hub', // Backward compatibility NUGET = 'nuget', REMOTE = 'remote', MCPB = 'mcpb', diff --git a/src/vs/platform/mcp/common/mcpManagementService.ts b/src/vs/platform/mcp/common/mcpManagementService.ts index c185b110845..f9a27ef8370 100644 --- a/src/vs/platform/mcp/common/mcpManagementService.ts +++ b/src/vs/platform/mcp/common/mcpManagementService.ts @@ -21,7 +21,7 @@ import { IInstantiationService } from '../../instantiation/common/instantiation. import { ILogService } from '../../log/common/log.js'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; -import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpGalleryService, IMcpManagementService, IMcpServerInput, IGalleryMcpServerConfiguration, InstallMcpServerEvent, InstallMcpServerResult, RegistryType, UninstallMcpServerEvent, InstallOptions, UninstallOptions, IInstallableMcpServer, IAllowedMcpServersService } from './mcpManagement.js'; +import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpGalleryService, IMcpManagementService, IMcpServerInput, IGalleryMcpServerConfiguration, InstallMcpServerEvent, InstallMcpServerResult, RegistryType, UninstallMcpServerEvent, InstallOptions, UninstallOptions, IInstallableMcpServer, IAllowedMcpServersService, IMcpServerArgument, IMcpServerKeyValueInput } from './mcpManagement.js'; import { IMcpServerVariable, McpServerVariableType, IMcpServerConfiguration, McpServerType } from './mcpPlatformTypes.js'; import { IMcpResourceScannerService, McpResourceTarget } from './mcpResourceScannerService.js'; @@ -51,135 +51,87 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { _serviceBrand: undefined; getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): Omit { - let config: IMcpServerConfiguration; - const inputs: IMcpServerVariable[] = []; + // remote if (packageType === RegistryType.REMOTE && manifest.remotes?.length) { - const headers: Record = {}; - for (const input of manifest.remotes[0].headers ?? []) { - const variables = input.variables ? this.getVariables(input.variables) : []; - let value = input.value; - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - headers[input.name] = value; - if (variables.length) { - inputs.push(...variables); - } - } - config = { - type: McpServerType.REMOTE, - url: manifest.remotes[0].url, - headers: Object.keys(headers).length ? headers : undefined, + const { inputs, variables } = this.processKeyValueInputs(manifest.remotes[0].headers ?? []); + return { + config: { + type: McpServerType.REMOTE, + url: manifest.remotes[0].url, + headers: Object.keys(inputs).length ? inputs : undefined, + }, + inputs: variables.length ? variables : undefined, }; - } else { - const serverPackage = manifest.packages?.find(p => p.registry_type === packageType) ?? manifest.packages?.[0]; - if (!serverPackage) { - throw new Error(`No server package found`); - } + } - const args: string[] = []; - const env: Record = {}; + // local + const serverPackage = manifest.packages?.find(p => p.registry_type === packageType) ?? manifest.packages?.[0]; + if (!serverPackage) { + throw new Error(`No server package found`); + } - if (serverPackage.registry_type === RegistryType.DOCKER) { - args.push('run'); - args.push('-i'); - args.push('--rm'); - } + const args: string[] = []; + const inputs: IMcpServerVariable[] = []; + const env: Record = {}; - for (const arg of serverPackage.runtime_arguments ?? []) { - const variables = arg.variables ? this.getVariables(arg.variables) : []; - if (arg.type === 'positional') { - let value = arg.value; - if (value) { - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - } - args.push(value ?? arg.value_hint); - } else if (arg.type === 'named') { - args.push(arg.name); - if (arg.value) { - let value = arg.value; - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - args.push(value); - } - } - if (variables.length) { - inputs.push(...variables); - } - } + if (serverPackage.registry_type === RegistryType.DOCKER || serverPackage.registry_type === RegistryType.DOCKER_HUB) { + args.push('run'); + args.push('-i'); + args.push('--rm'); + } - for (const input of serverPackage.environment_variables ?? []) { - const variables = input.variables ? this.getVariables(input.variables) : []; - let value = input.value; - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - env[input.name] = value; - if (variables.length) { - inputs.push(...variables); - } - if (serverPackage.registry_type === RegistryType.DOCKER) { + if (serverPackage.runtime_arguments?.length) { + const result = this.processArguments(serverPackage.runtime_arguments ?? []); + args.push(...result.args); + inputs.push(...result.variables); + } + + if (serverPackage.environment_variables?.length) { + const { inputs: envInputs, variables: envVariables } = this.processKeyValueInputs(serverPackage.environment_variables ?? []); + inputs.push(...envVariables); + for (const [name, value] of Object.entries(envInputs)) { + env[name] = value; + if (serverPackage.registry_type === RegistryType.DOCKER || serverPackage.registry_type === RegistryType.DOCKER_HUB) { args.push('-e'); - args.push(input.name); + args.push(name); } } + } - if (serverPackage.registry_type === RegistryType.NODE) { + switch (serverPackage.registry_type) { + case RegistryType.NODE: args.push(serverPackage.version ? `${serverPackage.identifier}@${serverPackage.version}` : serverPackage.identifier); - } - else if (serverPackage.registry_type === RegistryType.PYTHON) { + break; + case RegistryType.PYTHON: args.push(serverPackage.version ? `${serverPackage.identifier}==${serverPackage.version}` : serverPackage.identifier); - } - else if (serverPackage.registry_type === RegistryType.DOCKER) { + break; + case RegistryType.DOCKER: + case RegistryType.DOCKER_HUB: args.push(serverPackage.version ? `${serverPackage.identifier}:${serverPackage.version}` : serverPackage.identifier); - } - else if (serverPackage.registry_type === RegistryType.NUGET) { + break; + case RegistryType.NUGET: args.push(serverPackage.version ? `${serverPackage.identifier}@${serverPackage.version}` : serverPackage.identifier); args.push('--yes'); // installation is confirmed by the UI, so --yes is appropriate here if (serverPackage.package_arguments?.length) { args.push('--'); } - } + break; + } - for (const arg of serverPackage.package_arguments ?? []) { - const variables = arg.variables ? this.getVariables(arg.variables) : []; - if (arg.type === 'positional') { - let value = arg.value; - if (value) { - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - } - args.push(value ?? arg.value_hint); - } else if (arg.type === 'named') { - args.push(arg.name); - if (arg.value) { - let value = arg.value; - for (const variable of variables) { - value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); - } - args.push(value); - } - } - if (variables.length) { - inputs.push(...variables); - } - } + if (serverPackage.package_arguments?.length) { + const result = this.processArguments(serverPackage.package_arguments); + args.push(...result.args); + inputs.push(...result.variables); + } - config = { + return { + config: { type: McpServerType.LOCAL, command: this.getCommandName(serverPackage.registry_type), args: args.length ? args : undefined, env: Object.keys(env).length ? env : undefined, - }; - } - - return { - config, + }, inputs: inputs.length ? inputs : undefined, }; } @@ -188,6 +140,7 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { switch (packageType) { case RegistryType.NODE: return 'npx'; case RegistryType.DOCKER: return 'docker'; + case RegistryType.DOCKER_HUB: return 'docker'; // Backward compatibility case RegistryType.PYTHON: return 'uvx'; case RegistryType.NUGET: return 'dnx'; } @@ -209,6 +162,97 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { return variables; } + private processKeyValueInputs(keyValueInputs: ReadonlyArray): { inputs: Record; variables: IMcpServerVariable[] } { + const inputs: Record = {}; + const variables: IMcpServerVariable[] = []; + + for (const input of keyValueInputs) { + const inputVariables = input.variables ? this.getVariables(input.variables) : []; + let value = input.value || ''; + + // If explicit variables exist, use them regardless of value + if (inputVariables.length) { + for (const variable of inputVariables) { + value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); + } + variables.push(...inputVariables); + } else if (!value && (input.description || input.choices || input.default !== undefined)) { + // Only create auto-generated input variable if no explicit variables and no value + variables.push({ + id: input.name, + type: input.choices ? McpServerVariableType.PICK : McpServerVariableType.PROMPT, + description: input.description ?? '', + password: !!input.is_secret, + default: input.default, + options: input.choices, + }); + value = `\${input:${input.name}}`; + } + + inputs[input.name] = value; + } + + return { inputs, variables }; + } + + private processArguments(argumentsList: readonly IMcpServerArgument[]): { args: string[]; variables: IMcpServerVariable[] } { + const args: string[] = []; + const variables: IMcpServerVariable[] = []; + for (const arg of argumentsList) { + const argVariables = arg.variables ? this.getVariables(arg.variables) : []; + + if (arg.type === 'positional') { + let value = arg.value; + if (value) { + for (const variable of argVariables) { + value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); + } + args.push(value); + if (argVariables.length) { + variables.push(...argVariables); + } + } else if (arg.value_hint && (arg.description || arg.default !== undefined)) { + // Create input variable for positional argument without value + variables.push({ + id: arg.value_hint, + type: McpServerVariableType.PROMPT, + description: arg.description ?? '', + password: false, + default: arg.default, + }); + args.push(`\${input:${arg.value_hint}}`); + } else { + // Fallback to value_hint as literal + args.push(arg.value_hint ?? ''); + } + } else if (arg.type === 'named') { + args.push(arg.name); + if (arg.value) { + let value = arg.value; + for (const variable of argVariables) { + value = value.replace(`{${variable.id}}`, `\${input:${variable.id}}`); + } + args.push(value); + if (argVariables.length) { + variables.push(...argVariables); + } + } else if (arg.description || arg.default !== undefined) { + // Create input variable for named argument without value + const variableId = arg.name.replace(/^--?/, ''); + variables.push({ + id: variableId, + type: McpServerVariableType.PROMPT, + description: arg.description ?? '', + password: false, + default: arg.default, + }); + args.push(`\${input:${variableId}}`); + } + } + } + return { args, variables }; + } + } export abstract class AbstractMcpResourceManagementService extends AbstractCommonMcpManagementService { diff --git a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts new file mode 100644 index 00000000000..f1b6d39a83a --- /dev/null +++ b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts @@ -0,0 +1,881 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { AbstractCommonMcpManagementService } from '../../common/mcpManagementService.js'; +import { IGalleryMcpServerConfiguration, RegistryType, TransportType } from '../../common/mcpManagement.js'; +import { McpServerType, McpServerVariableType } from '../../common/mcpPlatformTypes.js'; + +class TestMcpManagementService extends AbstractCommonMcpManagementService { + // Expose the protected method for testing + public testGetMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType) { + return this.getMcpServerConfigurationFromManifest(manifest, packageType); + } +} + +suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { + let service: TestMcpManagementService; + + setup(() => { + service = new TestMcpManagementService(); + }); + + teardown(() => { + service.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('NPM Package Tests', () => { + test('basic NPM package configuration', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + registry_base_url: 'https://registry.npmjs.org', + identifier: '@modelcontextprotocol/server-brave-search', + version: '1.0.2', + environment_variables: [{ + name: 'BRAVE_API_KEY', + value: 'test-key' + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'npx'); + assert.deepStrictEqual(result.config.args, ['@modelcontextprotocol/server-brave-search@1.0.2']); + assert.deepStrictEqual(result.config.env, { 'BRAVE_API_KEY': 'test-key' }); + } + assert.strictEqual(result.inputs, undefined); + }); + + test('NPM package without version', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + registry_base_url: 'https://registry.npmjs.org', + identifier: '@modelcontextprotocol/everything', + version: '' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'npx'); + assert.deepStrictEqual(result.config.args, ['@modelcontextprotocol/everything']); + } + }); + + test('NPM package with environment variables containing variables', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + environment_variables: [{ + name: 'API_KEY', + value: 'key-{api_token}', + variables: { + api_token: { + description: 'Your API token', + is_secret: true, + is_required: true + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.env, { 'API_KEY': 'key-${input:api_token}' }); + } + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'api_token'); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); + assert.strictEqual(result.inputs?.[0].description, 'Your API token'); + assert.strictEqual(result.inputs?.[0].password, true); + }); + + test('environment variable with empty value should create input variable (GitHub issue #266106)', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: '@modelcontextprotocol/server-brave-search', + version: '1.0.2', + environment_variables: [{ + name: 'BRAVE_API_KEY', + value: '', // Empty value should create input variable + description: 'Brave Search API Key', + is_required: true, + is_secret: true + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + // BUG: Currently this creates env with empty string instead of input variable + // Should create an input variable since no meaningful value is provided + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'BRAVE_API_KEY'); + assert.strictEqual(result.inputs?.[0].description, 'Brave Search API Key'); + assert.strictEqual(result.inputs?.[0].password, true); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); + + // Environment should use input variable interpolation + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.env, { 'BRAVE_API_KEY': '${input:BRAVE_API_KEY}' }); + } + }); + + test('environment variable with choices but empty value should create pick input (GitHub issue #266106)', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + environment_variables: [{ + name: 'SSL_MODE', + value: '', // Empty value should create input variable + description: 'SSL connection mode', + default: 'prefer', + choices: ['disable', 'prefer', 'require'] + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + // BUG: Currently this creates env with empty string instead of input variable + // Should create a pick input variable since choices are provided + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'SSL_MODE'); + assert.strictEqual(result.inputs?.[0].description, 'SSL connection mode'); + assert.strictEqual(result.inputs?.[0].default, 'prefer'); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.inputs?.[0].options, ['disable', 'prefer', 'require']); + + // Environment should use input variable interpolation + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.env, { 'SSL_MODE': '${input:SSL_MODE}' }); + } + }); + + test('NPM package with package arguments', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'snyk', + version: '1.1298.0', + package_arguments: [ + { type: 'positional', value: 'mcp', value_hint: 'command', is_repeated: false }, + { + type: 'named', + name: '-t', + value: 'stdio', + is_repeated: false + } + ] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, ['snyk@1.1298.0', 'mcp', '-t', 'stdio']); + } + }); + }); + + suite('Python Package Tests', () => { + test('basic Python package configuration', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.PYTHON, + registry_base_url: 'https://pypi.org', + identifier: 'weather-mcp-server', + version: '0.5.0', + environment_variables: [{ + name: 'WEATHER_API_KEY', + value: 'test-key' + }, { + name: 'WEATHER_UNITS', + value: 'celsius' + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'uvx'); + assert.deepStrictEqual(result.config.args, ['weather-mcp-server==0.5.0']); + assert.deepStrictEqual(result.config.env, { + 'WEATHER_API_KEY': 'test-key', + 'WEATHER_UNITS': 'celsius' + }); + } + }); + + test('Python package without version', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.PYTHON, + identifier: 'weather-mcp-server', + version: '' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, ['weather-mcp-server']); + } + }); + }); + + suite('Docker Package Tests', () => { + test('basic Docker package configuration', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.DOCKER, + registry_base_url: 'https://docker.io', + identifier: 'mcp/filesystem', + version: '1.0.2', + runtime_arguments: [{ + type: 'named', + name: '--mount', + value: 'type=bind,src=/host/path,dst=/container/path', + is_repeated: false + }], + environment_variables: [{ + name: 'LOG_LEVEL', + value: 'info' + }], + package_arguments: [{ + type: 'positional', + value: '/project', + value_hint: 'directory', + is_repeated: false + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'docker'); + assert.deepStrictEqual(result.config.args, [ + 'run', '-i', '--rm', + '--mount', 'type=bind,src=/host/path,dst=/container/path', + '-e', 'LOG_LEVEL', + 'mcp/filesystem:1.0.2', + '/project' + ]); + assert.deepStrictEqual(result.config.env, { 'LOG_LEVEL': 'info' }); + } + }); + + test('Docker package with variables in runtime arguments', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.DOCKER, + identifier: 'example/database-manager-mcp', + version: '3.1.0', + runtime_arguments: [{ + type: 'named', + name: '-e', + value: 'DB_TYPE={db_type}', + is_repeated: false, + variables: { + db_type: { + description: 'Type of database', + choices: ['postgres', 'mysql', 'mongodb', 'redis'], + is_required: true + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, [ + 'run', '-i', '--rm', + '-e', 'DB_TYPE=${input:db_type}', + 'example/database-manager-mcp:3.1.0' + ]); + } + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'db_type'); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.inputs?.[0].options, ['postgres', 'mysql', 'mongodb', 'redis']); + }); + + test('Docker package arguments without values should create input variables (GitHub issue #266106)', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.DOCKER, + identifier: 'example/database-manager-mcp', + version: '3.1.0', + package_arguments: [{ + type: 'named', + name: '--host', + description: 'Database host', + default: 'localhost', + is_required: true, + is_repeated: false + // Note: No 'value' field - should create input variable + }, { + type: 'positional', + value_hint: 'database_name', + description: 'Name of the database to connect to', + is_required: true, + is_repeated: false + // Note: No 'value' field - should create input variable + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + + // BUG: Currently named args without value are ignored, positional uses value_hint as literal + // Should create input variables for both arguments + assert.strictEqual(result.inputs?.length, 2); + + const hostInput = result.inputs?.find(i => i.id === 'host'); + assert.strictEqual(hostInput?.description, 'Database host'); + assert.strictEqual(hostInput?.default, 'localhost'); + assert.strictEqual(hostInput?.type, McpServerVariableType.PROMPT); + + const dbNameInput = result.inputs?.find(i => i.id === 'database_name'); + assert.strictEqual(dbNameInput?.description, 'Name of the database to connect to'); + assert.strictEqual(dbNameInput?.type, McpServerVariableType.PROMPT); + + // Args should use input variable interpolation + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, [ + 'run', '-i', '--rm', + 'example/database-manager-mcp:3.1.0', + '--host', '${input:host}', + '${input:database_name}' + ]); + } + }); + + test('Docker Hub backward compatibility', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.DOCKER_HUB, + identifier: 'example/test-image', + version: '1.0.0' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER_HUB); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'docker'); + assert.deepStrictEqual(result.config.args, [ + 'run', '-i', '--rm', + 'example/test-image:1.0.0' + ]); + } + }); + }); + + suite('NuGet Package Tests', () => { + test('basic NuGet package configuration', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NUGET, + registry_base_url: 'https://api.nuget.org', + identifier: 'Knapcode.SampleMcpServer', + version: '0.5.0', + environment_variables: [{ + name: 'WEATHER_CHOICES', + value: 'sunny,cloudy,rainy' + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'dnx'); + assert.deepStrictEqual(result.config.args, ['Knapcode.SampleMcpServer@0.5.0', '--yes']); + assert.deepStrictEqual(result.config.env, { 'WEATHER_CHOICES': 'sunny,cloudy,rainy' }); + } + }); + + test('NuGet package with package arguments', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NUGET, + identifier: 'Knapcode.SampleMcpServer', + version: '0.4.0-beta', + package_arguments: [{ + type: 'positional', + value: 'mcp', + value_hint: 'command', + is_repeated: false + }, { + type: 'positional', + value: 'start', + value_hint: 'action', + is_repeated: false + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, [ + 'Knapcode.SampleMcpServer@0.4.0-beta', + '--yes', + '--', + 'mcp', + 'start' + ]); + } + }); + }); + + suite('Remote Server Tests', () => { + test('SSE remote server configuration', () => { + const manifest: IGalleryMcpServerConfiguration = { + remotes: [{ + type: TransportType.SSE, + url: 'http://mcp-fs.anonymous.modelcontextprotocol.io/sse' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + + assert.strictEqual(result.config.type, McpServerType.REMOTE); + if (result.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.config.url, 'http://mcp-fs.anonymous.modelcontextprotocol.io/sse'); + assert.strictEqual(result.config.headers, undefined); + } + }); + + test('SSE remote server with headers and variables', () => { + const manifest: IGalleryMcpServerConfiguration = { + remotes: [{ + type: TransportType.SSE, + url: 'https://mcp.anonymous.modelcontextprotocol.io/sse', + headers: [{ + name: 'X-API-Key', + value: '{api_key}', + variables: { + api_key: { + description: 'API key for authentication', + is_required: true, + is_secret: true + } + } + }, { + name: 'X-Region', + value: 'us-east-1' + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + + assert.strictEqual(result.config.type, McpServerType.REMOTE); + if (result.config.type === McpServerType.REMOTE) { + assert.deepStrictEqual(result.config.headers, { + 'X-API-Key': '${input:api_key}', + 'X-Region': 'us-east-1' + }); + } + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'api_key'); + assert.strictEqual(result.inputs?.[0].password, true); + }); + + test('streamable HTTP remote server', () => { + const manifest: IGalleryMcpServerConfiguration = { + remotes: [{ + type: TransportType.STREAMABLE_HTTP, + url: 'https://mcp.anonymous.modelcontextprotocol.io/http' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + + assert.strictEqual(result.config.type, McpServerType.REMOTE); + if (result.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.config.url, 'https://mcp.anonymous.modelcontextprotocol.io/http'); + } + }); + + test('remote headers without values should create input variables', () => { + const manifest: IGalleryMcpServerConfiguration = { + remotes: [{ + type: TransportType.SSE, + url: 'https://api.example.com/mcp', + headers: [{ + name: 'Authorization', + description: 'API token for authentication', + is_secret: true, + is_required: true + // Note: No 'value' field - should create input variable + }, { + name: 'X-Custom-Header', + description: 'Custom header value', + default: 'default-value', + choices: ['option1', 'option2', 'option3'] + // Note: No 'value' field - should create input variable with choices + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + + assert.strictEqual(result.config.type, McpServerType.REMOTE); + if (result.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.config.url, 'https://api.example.com/mcp'); + assert.deepStrictEqual(result.config.headers, { + 'Authorization': '${input:Authorization}', + 'X-Custom-Header': '${input:X-Custom-Header}' + }); + } + + // Should create input variables for headers without values + assert.strictEqual(result.inputs?.length, 2); + + const authInput = result.inputs?.find(i => i.id === 'Authorization'); + assert.strictEqual(authInput?.description, 'API token for authentication'); + assert.strictEqual(authInput?.password, true); + assert.strictEqual(authInput?.type, McpServerVariableType.PROMPT); + + const customInput = result.inputs?.find(i => i.id === 'X-Custom-Header'); + assert.strictEqual(customInput?.description, 'Custom header value'); + assert.strictEqual(customInput?.default, 'default-value'); + assert.strictEqual(customInput?.type, McpServerVariableType.PICK); + assert.deepStrictEqual(customInput?.options, ['option1', 'option2', 'option3']); + }); + }); + + suite('Variable Interpolation Tests', () => { + test('multiple variables in single value', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + environment_variables: [{ + name: 'CONNECTION_STRING', + value: 'server={host};port={port};database={db_name}', + variables: { + host: { + description: 'Database host', + default: 'localhost' + }, + port: { + description: 'Database port', + format: 'number', + default: '5432' + }, + db_name: { + description: 'Database name', + is_required: true + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.env, { + 'CONNECTION_STRING': 'server=${input:host};port=${input:port};database=${input:db_name}' + }); + } + assert.strictEqual(result.inputs?.length, 3); + + const hostInput = result.inputs?.find(i => i.id === 'host'); + assert.strictEqual(hostInput?.default, 'localhost'); + assert.strictEqual(hostInput?.type, McpServerVariableType.PROMPT); + + const portInput = result.inputs?.find(i => i.id === 'port'); + assert.strictEqual(portInput?.default, '5432'); + + const dbNameInput = result.inputs?.find(i => i.id === 'db_name'); + assert.strictEqual(dbNameInput?.description, 'Database name'); + }); + + test('variable with choices creates pick input', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + runtime_arguments: [{ + type: 'named', + name: '--log-level', + value: '{level}', + is_repeated: false, + variables: { + level: { + description: 'Log level', + choices: ['debug', 'info', 'warn', 'error'], + default: 'info' + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.inputs?.[0].options, ['debug', 'info', 'warn', 'error']); + assert.strictEqual(result.inputs?.[0].default, 'info'); + }); + + test('variables in package arguments', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.DOCKER, + identifier: 'test-image', + version: '1.0.0', + package_arguments: [{ + type: 'named', + name: '--host', + value: '{db_host}', + is_repeated: false, + variables: { + db_host: { + description: 'Database host', + default: 'localhost' + } + } + }, { + type: 'positional', + value: '{database_name}', + value_hint: 'database_name', + is_repeated: false, + variables: { + database_name: { + description: 'Name of the database to connect to', + is_required: true + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, [ + 'run', '-i', '--rm', + 'test-image:1.0.0', + '--host', '${input:db_host}', + '${input:database_name}' + ]); + } + assert.strictEqual(result.inputs?.length, 2); + }); + + test('positional arguments with value_hint should create input variables (GitHub issue #266106)', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: '@example/math-tool', + version: '2.0.1', + package_arguments: [{ + type: 'positional', + value_hint: 'calculation_type', + description: 'Type of calculation to enable', + is_required: true, + is_repeated: false + // Note: No 'value' field, only value_hint - should create input variable + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + // BUG: Currently value_hint is used as literal value instead of creating input variable + // Should create input variable instead + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'calculation_type'); + assert.strictEqual(result.inputs?.[0].description, 'Type of calculation to enable'); + assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); + + // Args should use input variable interpolation + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, [ + '@example/math-tool@2.0.1', + '${input:calculation_type}' + ]); + } + }); + }); + + suite('Edge Cases and Error Handling', () => { + test('empty manifest should throw error', () => { + const manifest: IGalleryMcpServerConfiguration = {}; + + assert.throws(() => { + service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + }, /No server package found/); + }); + + test('manifest with no matching package type should use first package', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.PYTHON, + identifier: 'python-server', + version: '1.0.0' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.config.type, McpServerType.LOCAL); + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'uvx'); // Python command since that's the package type + assert.deepStrictEqual(result.config.args, ['python-server==1.0.0']); + } + }); + + test('manifest with matching package type should use that package', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.PYTHON, + identifier: 'python-server', + version: '1.0.0' + }, { + registry_type: RegistryType.NODE, + identifier: 'node-server', + version: '2.0.0' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.command, 'npx'); + assert.deepStrictEqual(result.config.args, ['node-server@2.0.0']); + } + }); + + test('undefined environment variables should be omitted', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0' + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.env, undefined); + } + }); + + test('named argument without value should only add name', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + runtime_arguments: [{ + type: 'named', + name: '--verbose', + is_repeated: false + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, ['--verbose', 'test-server@1.0.0']); + } + }); + + test('positional argument with undefined value should use value_hint', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + package_arguments: [{ + type: 'positional', + value_hint: 'target_directory', + is_repeated: false + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.config.args, ['test-server@1.0.0', 'target_directory']); + } + }); + }); + + suite('Variable Processing Order', () => { + test('should use explicit variables instead of auto-generating when both are possible', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registry_type: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + environment_variables: [{ + name: 'API_KEY', + value: 'Bearer {api_key}', + description: 'Should not be used', // This should be ignored since we have explicit variables + variables: { + api_key: { + description: 'Your API key', + is_secret: true + } + } + }] + }] + }; + + const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + assert.strictEqual(result.inputs?.length, 1); + assert.strictEqual(result.inputs?.[0].id, 'api_key'); + assert.strictEqual(result.inputs?.[0].description, 'Your API key'); + assert.strictEqual(result.inputs?.[0].password, true); + + if (result.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.config.env?.['API_KEY'], 'Bearer ${input:api_key}'); + } + }); + }); +}); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 20f5c856dc8..43856253feb 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -758,7 +758,10 @@ export class McpServerEditor extends EditorPane { } } if (arg.type === 'positional') { - argStrings.push(arg.value ?? arg.value_hint); + const val = arg.value ?? arg.value_hint; + if (val) { + argStrings.push(val); + } } } append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('packagearguments', "Package Arguments:")), $('code.detail-value', undefined, argStrings.join(' ')))); @@ -773,7 +776,10 @@ export class McpServerEditor extends EditorPane { } } if (arg.type === 'positional') { - argStrings.push(arg.value ?? arg.value_hint); + const val = arg.value ?? arg.value_hint; + if (val) { + argStrings.push(val); + } } } append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('runtimeargs', "Runtime Arguments:")), $('code.detail-value', undefined, argStrings.join(' ')))); From 795cd6297edf1a16ed9875408e0dd53f491a4a9f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 17:38:28 +0200 Subject: [PATCH 0217/4355] revalidate on tool/mode or model change --- .../promptSyntax/service/promptValidator.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index 8f6604b33fd..3158901db24 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -305,6 +305,9 @@ export class PromptValidatorContribution extends Disposable { @IConfigurationService private configService: IConfigurationService, @IMarkerService private readonly markerService: IMarkerService, @IPromptsService private readonly promptsService: IPromptsService, + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, + @IChatModeService private readonly chatModeService: IChatModeService, ) { super(); this.validator = instantiationService.createInstance(PromptValidator); @@ -326,12 +329,18 @@ export class PromptValidatorContribution extends Disposable { this.localDisposables.add(toDisposable(() => { trackers.forEach(tracker => tracker.dispose()); })); - this.modelService.getModels().forEach(model => { - const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); - if (promptType) { - trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); - } - }); + + const validateAllDelayer = this._register(new Delayer(200)); + const validateAll = (): void => { + validateAllDelayer.trigger(async () => { + this.modelService.getModels().forEach(model => { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); + } + }); + }); + }; this.localDisposables.add(this.modelService.onModelAdded((model) => { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); if (promptType) { @@ -360,6 +369,10 @@ export class PromptValidatorContribution extends Disposable { trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } })); + this.localDisposables.add(this.languageModelToolsService.onDidChangeTools(() => validateAll())); + this.localDisposables.add(this.chatModeService.onDidChangeChatModes(() => validateAll())); + this.localDisposables.add(this.languageModelsService.onDidChangeLanguageModels(() => validateAll())); + validateAll(); } } From dff9ef266087c27400f6479dfe72ce2a93b0a307 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 11 Sep 2025 17:46:03 +0200 Subject: [PATCH 0218/4355] :up: distro (#266214) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2b1c34f19ac..ea83ed00f87 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "38f50bcc0ae9969d068f7391b5f124dc25e80465", + "distro": "8103a60ed6fb457dbf0ba180a67be3341dce4a23", "author": { "name": "Microsoft Corporation" }, @@ -237,4 +237,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} From 9cfa06cf36cdb2c21b12ff7f621a2b686a6e88f0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 17:59:27 +0200 Subject: [PATCH 0219/4355] adopt parseNew --- .../contrib/chat/browser/chatWidget.ts | 27 ++++++-------- .../computeAutomaticInstructions.ts | 35 +++++++++---------- .../promptSyntax/service/newPromptsParser.ts | 18 +++++----- .../promptSyntax/service/promptValidator.ts | 4 +-- .../promptSyntax/service/promptsService.ts | 2 +- .../service/promptsServiceImpl.ts | 4 +-- .../service/newPromptsParser.test.ts | 6 ++-- 7 files changed, 43 insertions(+), 53 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 652a7929c43..d83b1b8ce7b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -64,9 +64,8 @@ import { ChatAgentLocation, ChatConfiguration, ChatModeKind, TodoListWidgetPosit import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/languageModelToolsService.js'; import { ComputeAutomaticInstructions } from '../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; -import { type TPromptMetadata } from '../common/promptSyntax/parsers/promptHeader/promptHeader.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; -import { IPromptParserResult, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; +import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { handleModeSwitch } from './actions/chatActions.js'; import { ChatTreeItem, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions, ChatViewId } from './chat.js'; import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; @@ -83,6 +82,7 @@ import { ChatViewPane } from './chatViewPane.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; const $ = dom.$; @@ -2145,13 +2145,13 @@ export class ChatWidget extends Disposable implements IChatWidget { return undefined; } - private async _applyPromptFileIfSet(requestInput: IChatRequestInputOptions): Promise { + private async _applyPromptFileIfSet(requestInput: IChatRequestInputOptions): Promise { if (!PromptsConfig.enabled(this.configurationService)) { // if prompts are not enabled, we don't need to do anything return undefined; } - let parseResult: IPromptParserResult | undefined; + let parseResult: ParsedPromptFile | undefined; // first check if the input has a prompt slash command const agentSlashPromptPart = this.parsedInput.parts.find((r): r is ChatRequestSlashPromptPart => r instanceof ChatRequestSlashPromptPart); @@ -2159,7 +2159,7 @@ export class ChatWidget extends Disposable implements IChatWidget { parseResult = await this.promptsService.resolvePromptSlashCommand(agentSlashPromptPart.slashPromptCommand, CancellationToken.None); if (parseResult) { // add the prompt file to the context, but not sticky - const toolReferences = this.toolsService.toToolReferences(parseResult.variableReferences); + const toolReferences = this.toolsService.toToolReferences([]); // TODO: this.toolsService.toToolReferences(parseResult.body?.variableReferences ?? []); requestInput.attachedContext.insertFirst(toPromptFileVariableEntry(parseResult.uri, PromptFileVariableKind.PromptFile, undefined, true, toolReferences)); // remove the slash command from the input @@ -2170,7 +2170,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const uri = this._findPromptFileInContext(requestInput.attachedContext); if (uri) { try { - parseResult = await this.promptsService.parse(uri, PromptsType.prompt, CancellationToken.None); + parseResult = await this.promptsService.parseNew(uri, CancellationToken.None); } catch (error) { this.logService.error(`[_applyPromptFileIfSet] Failed to parse prompt file: ${uri}`, error); } @@ -2180,10 +2180,6 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!parseResult) { return undefined; } - const meta = parseResult.metadata; - if (meta?.promptType !== PromptsType.prompt) { - return undefined; - } const input = requestInput.input.trim(); requestInput.input = `Follow instructions in [${basename(parseResult.uri)}](${parseResult.uri.toString()}).`; @@ -2191,10 +2187,9 @@ export class ChatWidget extends Disposable implements IChatWidget { // if the input is not empty, append it to the prompt requestInput.input += `\n${input}`; } - - await this._applyPromptMetadata(meta, requestInput); - - return parseResult; + if (parseResult.header) { + await this._applyPromptMetadata(parseResult.header, requestInput); + } } private async _acceptInput(query: { query: string } | undefined, options?: IChatAcceptInputOptions): Promise { @@ -2533,9 +2528,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.agentInInput.set(!!currentAgent); } - private async _applyPromptMetadata(metadata: TPromptMetadata, requestInput: IChatRequestInputOptions): Promise { - - const { mode, tools, model } = metadata; + private async _applyPromptMetadata({ mode, tools, model }: PromptHeader, requestInput: IChatRequestInputOptions): Promise { const currentMode = this.input.currentModeObs.get(); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index 51129a130f1..9aa457af4be 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -21,7 +21,8 @@ import { IToolData } from '../languageModelToolsService.js'; import { PromptsConfig } from './config/config.js'; import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, isPromptOrInstructionsFile } from './config/promptFileLocations.js'; import { PromptsType } from './promptTypes.js'; -import { IPromptParserResult, IPromptPath, IPromptsService } from './service/promptsService.js'; +import { ParsedPromptFile } from './service/newPromptsParser.js'; +import { IPromptPath, IPromptsService } from './service/promptsService.js'; export type InstructionsCollectionEvent = { applyingInstructionsCount: number; @@ -46,7 +47,7 @@ type InstructionsCollectionClassification = { export class ComputeAutomaticInstructions { - private _parseResults: ResourceMap = new ResourceMap(); + private _parseResults: ResourceMap = new ResourceMap(); constructor( private readonly _readFileTool: IToolData | undefined, @@ -60,12 +61,12 @@ export class ComputeAutomaticInstructions { ) { } - private async _parseInstructionsFile(uri: URI, token: CancellationToken): Promise { + private async _parseInstructionsFile(uri: URI, token: CancellationToken): Promise { if (this._parseResults.has(uri)) { return this._parseResults.get(uri)!; } try { - const result = await this._promptsService.parse(uri, PromptsType.instructions, token); + const result = await this._promptsService.parseNew(uri, token); this._parseResults.set(uri, result); return result; } catch (error) { @@ -125,11 +126,7 @@ export class ComputeAutomaticInstructions { continue; } - if (parsedFile.metadata?.promptType !== PromptsType.instructions) { - this._logService.trace(`[InstructionsContextComputer] Not an instruction file: ${uri}`); - continue; - } - const applyTo = parsedFile.metadata.applyTo; + const applyTo = parsedFile.header?.applyTo; if (!applyTo) { this._logService.trace(`[InstructionsContextComputer] No 'applyTo' found: ${uri}`); @@ -262,12 +259,11 @@ export class ComputeAutomaticInstructions { const entries: string[] = []; for (const { uri } of instructionFiles) { const parsedFile = await this._parseInstructionsFile(uri, token); - if (parsedFile?.metadata?.promptType !== PromptsType.instructions) { - continue; + if (parsedFile) { + const applyTo = parsedFile.header?.applyTo ?? '**/*'; + const description = parsedFile.header?.description ?? ''; + entries.push(`| '${getFilePath(uri)}' | ${applyTo} | ${description} |`); } - const applyTo = parsedFile.metadata.applyTo ?? '**/*'; - const description = parsedFile.metadata.description ?? ''; - entries.push(`| '${getFilePath(uri)}' | ${applyTo} | ${description} |`); } if (entries.length === 0) { return entries; @@ -299,13 +295,14 @@ export class ComputeAutomaticInstructions { let next = todo.pop(); while (next) { const result = await this._parseInstructionsFile(next, token); - if (result) { + if (result && result.body) { const refsToCheck: { resource: URI }[] = []; - for (const ref of result.fileReferences) { - if (!seen.has(ref) && (isPromptOrInstructionsFile(ref) || this._workspaceService.getWorkspaceFolder(ref) !== undefined)) { + for (const ref of result.body.fileReferences) { + const url = result.body.resolveFilePath(ref.content); + if (url && !seen.has(url) && (isPromptOrInstructionsFile(url) || this._workspaceService.getWorkspaceFolder(url) !== undefined)) { // only add references that are either prompt or instruction files or are part of the workspace - refsToCheck.push({ resource: ref }); - seen.add(ref); + refsToCheck.push({ resource: url }); + seen.add(url); } } if (refsToCheck.length > 0) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index bf35d5da09e..51d6dfdfcf4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -149,24 +149,24 @@ export class PromptHeader { return this.getStringAttribute('applyTo'); } - public get tools(): Map | undefined { + public get tools(): string[] | undefined { const toolsAttribute = this.getParsedHeader().attributes.find(attr => attr.key === 'tools'); if (!toolsAttribute) { return undefined; } if (toolsAttribute.value.type === 'array') { - const tools = new Map; + const tools: string[] = []; for (const item of toolsAttribute.value.items) { if (item.type === 'string') { - tools.set(item.value, true); + tools.push(item.value); } } return tools; } else if (toolsAttribute.value.type === 'object') { - const tools = new Map; + const tools: string[] = []; const collectLeafs = ({ key, value }: { key: IStringValue; value: IValue }) => { if (value.type === 'boolean') { - tools.set(key.value, value.value); + tools.push(key.value); } else if (value.type === 'object') { value.properties.forEach(collectLeafs); } @@ -258,7 +258,7 @@ export class PromptBody { const contentStartOffset = match.index + 1; // after the # const contentEndOffset = match.index + match[0].length; const range = new Range(i + 1, contentStartOffset + 1, i + 1, contentEndOffset + 1); - variableReferences.push({ content: match[2], range }); + variableReferences.push({ name: match[2], range }); } } } @@ -282,14 +282,14 @@ export class PromptBody { } } -interface IBodyFileReference { +export interface IBodyFileReference { content: string; range: Range; isMarkdownLink: boolean; } -interface IBodyVariableReference { - content: string; +export interface IBodyVariableReference { + name: string; range: Range; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index 3158901db24..511e1a1e28a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -71,8 +71,8 @@ export class PromptValidator { if (body.variableReferences.length) { const available = this.getAvailableToolAndToolSetNames(); for (const variable of body.variableReferences) { - if (!available.has(variable.content)) { - report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.content), variable.range, MarkerSeverity.Warning)); + if (!available.has(variable.name)) { + report(toMarker(localize('promptValidator.unknownVariableReference', "Unknown tool or toolset '{0}'.", variable.name), variable.range, MarkerSeverity.Warning)); } } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 5363d277802..ab3b4887aa4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -190,7 +190,7 @@ export interface IPromptsService extends IDisposable { /** * Gets the prompt file for a slash command. */ - resolvePromptSlashCommand(data: IChatPromptSlashCommand, _token: CancellationToken): Promise; + resolvePromptSlashCommand(data: IChatPromptSlashCommand, _token: CancellationToken): Promise; /** * Returns a prompt command if the command name is valid. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 779960cf9f9..df8191d491b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -195,13 +195,13 @@ export class PromptsService extends Disposable implements IPromptsService { return undefined; } - public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { + public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { const promptUri = await this.getPromptPath(data); if (!promptUri) { return undefined; } try { - return await this.parse(promptUri, PromptsType.prompt, token); + return await this.parseNew(promptUri, token); } catch (error) { this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri}`, error); return undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 2353b60db80..93b4d30b495 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -51,7 +51,7 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.description, 'Agent mode test'); assert.deepEqual(result.header.model, 'GPT 4.1'); assert.ok(result.header.tools); - assert.deepEqual([...result.header.tools.entries()], [['tool1', true], ['tool2', true]]); + assert.deepEqual(result.header.tools, ['tool1', 'tool2']); }); test('instructions', async () => { @@ -120,7 +120,7 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.mode, 'agent'); assert.deepEqual(result.header.model, 'GPT 4.1'); assert.ok(result.header.tools); - assert.deepEqual([...result.header.tools.entries()], [['search', true], ['terminal', true]]); + assert.deepEqual(result.header.tools, ['search', 'terminal']); }); test('prompt file tools as map', async () => { @@ -190,6 +190,6 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.mode, undefined); assert.deepEqual(result.header.model, undefined); assert.ok(result.header.tools); - assert.deepEqual([...result.header.tools.entries()], [['built-in', true], ['browser-click', true], ['openPullRequest', true], ['copilotCodingAgent', false]]); + assert.deepEqual(result.header.tools, ['built-in', 'browser-click', 'openPullRequest', 'copilotCodingAgent']); }); }); From e11146a36965ff44ff3e37f20686af8dd041fb05 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 18:02:58 +0200 Subject: [PATCH 0220/4355] fix tests --- .../test/common/promptSyntax/service/newPromptsParser.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 93b4d30b495..22409da076b 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -46,7 +46,7 @@ suite('NewPromptsParser', () => { { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 12, 7, 17), content: 'tool1' } + { range: new Range(7, 12, 7, 17), name: 'tool1' } ]); assert.deepEqual(result.header.description, 'Agent mode test'); assert.deepEqual(result.header.model, 'GPT 4.1'); @@ -114,7 +114,7 @@ suite('NewPromptsParser', () => { { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 41, 7, 47), content: 'search' } + { range: new Range(7, 41, 7, 47), name: 'search' } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); assert.deepEqual(result.header.mode, 'agent'); From a7bf33c840f6cfd9afcc736b78a4e58f7480287d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 18:07:53 +0200 Subject: [PATCH 0221/4355] update --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d83b1b8ce7b..9070071c397 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1339,13 +1339,13 @@ export class ChatWidget extends Disposable implements IChatWidget { if (promptNames.includes(promptCommand.command)) { try { if (promptCommand.promptPath) { - const parseResult = await this.promptsService.parse( + const parseResult = await this.promptsService.parseNew( promptCommand.promptPath.uri, - promptCommand.promptPath.type, CancellationToken.None ); - if (parseResult.metadata?.description) { - this.promptDescriptionsCache.set(promptCommand.command, parseResult.metadata.description); + const description = parseResult.header?.description; + if (description) { + this.promptDescriptionsCache.set(promptCommand.command, description); } else { // Set empty string to indicate we've checked this prompt this.promptDescriptionsCache.set(promptCommand.command, ''); From 4d07e482c380fdac8629aa0de776b66bb2032d6f Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 11 Sep 2025 09:10:57 -0700 Subject: [PATCH 0222/4355] Plan mode for VS Code development --- .github/chatmodes/Plan.chatmode.md | 40 ++++++++++++++++++++++++++++++ .vscode/settings.json | 8 +++++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/chatmodes/Plan.chatmode.md diff --git a/.github/chatmodes/Plan.chatmode.md b/.github/chatmodes/Plan.chatmode.md new file mode 100644 index 00000000000..6ce17f52d7d --- /dev/null +++ b/.github/chatmodes/Plan.chatmode.md @@ -0,0 +1,40 @@ +--- +description: Research and draft an implementation plan +tools: ['search', 'executeTask', 'usages', 'problems', 'testFailure', 'todos', 'get_issue', 'get_issue_comments', 'fetch', 'githubRepo'] +--- +Your goal is to draft a clear, detailed, and actionable plan that addresses the user's request. + + +1. *Clarify:* If the user request is high level, ask clarifying questions (max 3, concise) to reduce ambiguity. + **MUST pause for user feedback!** Keep asking in case of high ambiguity. +2. *Research*: + If the `execute_task` tool is available, you MUST start with the `execute_task` tool, prompted not to NOT pause for user feedback, and to follow research_actions using tools. + If the `execute_task` tool is NOT available, you run tools yourself, following research_actions. +3. *Review*: Present the full plan to the user, concluding with a request for feedback. + **MUST pause for user feedback!** Incorporate the feedback by going back to *Research* (as new requirements emerge) or refine the plan directly. + + + +- Clear and concise language +- Tailored to the complexity of the task (more complex tasks require more detailed plans) +- Briefly summarizes problem understanding and proposed technical approach +- Broken down into clear, iterative steps (preferring one ordered lists over nested lists unless necessary) +- Easy to review and understand by calling out critical assumptions, technical risks, and dependencies +- Annotated with relevant file/code references, architecture decisions, and technical reasoning +- Highlights any areas of uncertainty or open questions for further discussion + + + +1. Comprehensive information gathering using read-only tools + - Examine existing codebase structure and architecture + - Identify current patterns, conventions, and technologies + - Review documentation, configuration files, and dependencies + - Assess potential integration points and conflicts + - Gather all necessary context before planning +2. Process gathered information into actionable insights + - Analyze gaps between current state and desired outcome + - Identify potential risks, challenges, and dependencies + - Consider multiple approaches + - Evaluate trade-offs and optimization opportunities + - Design step-by-step plan + diff --git a/.vscode/settings.json b/.vscode/settings.json index a9497c8fc00..c9cead3fcac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -218,5 +218,11 @@ "application.experimental.rendererProfiling": true, "editor.aiStats.enabled": true, // Team selfhosting on ai stats - "chat.checkpoints.showFileChanges": true + "chat.checkpoints.showFileChanges": true, + "chat.emptyState.history.enabled": true, + "github.copilot.chat.advanced.taskTools.enabled": true, + "chat.promptFilesRecommendations": { + "plan": true, + "implement": true + } } From 1284d997f8268d93bef6a4e17adeae367bdf8f1d Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:29:55 -0700 Subject: [PATCH 0223/4355] More logs around MCP Auth (#266215) To help debug issues. --- .../api/common/extHostAuthentication.ts | 4 +- src/vs/workbench/api/common/extHostMcp.ts | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index c3b5bf35bc2..dd829afab53 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -376,7 +376,7 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider { // Auth Provider label is just the resource name if provided, otherwise the authority of the authorization server. this.label = _resourceMetadata?.resource_name ?? this.authorizationServer.authority; - this._logger = loggerService.createLogger(this.id, { name: this.label }); + this._logger = loggerService.createLogger(this.id, { name: `Auth: ${this.label}` }); this._disposable = new DisposableStore(); this._disposable.add(this._onDidChangeSessions); const scopedEvent = Event.chain(onDidDynamicAuthProviderTokensChange.event, $ => $ @@ -513,7 +513,7 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider { // Store session for later retrieval this._tokenStore.update({ added: [{ ...token, created_at: Date.now() }], removed: [] }); const session = this._tokenStore.sessions.find(t => t.accessToken === token.access_token)!; - this._logger.info(`Created session for scopes: ${token.scope}`); + this._logger.info(`Created ${token.refresh_token ? 'refreshable' : 'non-refreshable'} session for scopes: ${token.scope}${token.expires_in ? ` that expires in ${token.expires_in} seconds` : ''}`); return session; } diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 7da17eb2fd3..d75f540b960 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -314,7 +314,7 @@ class McpHTTPHandle extends Disposable { } } - private async _populateAuthMetadata(url: string, originalResponse: Response): Promise { + private async _populateAuthMetadata(mcpUrl: string, originalResponse: Response): Promise { // If there is a resource_metadata challenge, use that to get the oauth server. This is done in 2 steps. // First, extract the resource_metada challenge from the WWW-Authenticate header (if available) let resourceMetadataChallenge: string | undefined; @@ -323,6 +323,7 @@ class McpHTTPHandle extends Disposable { const challenges = parseWWWAuthenticateHeader(authHeader); for (const challenge of challenges) { if (challenge.scheme === 'Bearer' && challenge.params['resource_metadata']) { + this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${challenge.params['resource_metadata']}`); resourceMetadataChallenge = challenge.params['resource_metadata']; break; } @@ -335,12 +336,15 @@ class McpHTTPHandle extends Disposable { if (resourceMetadataChallenge) { const resourceMetadata = await this._getResourceMetadata(resourceMetadataChallenge); // Use URL constructor for normalization - it handles hostname case and trailing slashes - if (new URL(resourceMetadata.resource).toString() !== new URL(url).toString()) { - throw new Error(`Protected Resource Metadata resource "${resourceMetadata.resource}" does not match MCP server resolved resource "${url}". The MCP server must follow OAuth spec https://datatracker.ietf.org/doc/html/rfc9728#PRConfigurationValidation`); + const prmValue = new URL(resourceMetadata.resource).toString(); + const mcpValue = new URL(mcpUrl).toString(); + if (prmValue !== mcpValue) { + throw new Error(`Protected Resource Metadata resource value "${prmValue}" (length: ${prmValue.length}) does not match MCP server url "${mcpValue}" (length: ${mcpValue.length}). The MCP server must follow OAuth spec https://datatracker.ietf.org/doc/html/rfc9728#PRConfigurationValidation`); } // TODO:@TylerLeonhardt support multiple authorization servers // Consider using one that has an auth provider first, over the dynamic flow serverMetadataUrl = resourceMetadata.authorization_servers?.[0]; + this._log(LogLevel.Debug, `Using auth server metadata url: ${serverMetadataUrl}`); scopesSupported = resourceMetadata.scopes_supported; resource = resourceMetadata; } @@ -359,6 +363,7 @@ class McpHTTPHandle extends Disposable { } try { const serverMetadataResponse = await this._getAuthorizationServerMetadata(serverMetadataUrl, addtionalHeaders); + this._log(LogLevel.Info, 'Populated auth metadata'); this._authMetadata = { authorizationServer: URI.parse(serverMetadataUrl), serverMetadata: serverMetadataResponse, @@ -366,7 +371,7 @@ class McpHTTPHandle extends Disposable { }; return; } catch (e) { - this._log(LogLevel.Warning, `Error populating auth metadata: ${String(e)}`); + this._log(LogLevel.Warning, `Error populating auth server metadata for ${serverMetadataUrl}: ${String(e)}`); } // If there's no well-known server metadata, then use the default values based off of the url. @@ -377,6 +382,7 @@ class McpHTTPHandle extends Disposable { serverMetadata: defaultMetadata, resourceMetadata: resource }; + this._log(LogLevel.Info, 'Using default auth metadata'); } private async _getResourceMetadata(resourceMetadata: string): Promise { @@ -415,6 +421,7 @@ class McpHTTPHandle extends Disposable { const authorizationServerUrl = new URL(authorizationServer); const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname; const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + this._log(LogLevel.Debug, `Fetching auth server metadata url with path insertion: ${pathToFetch} ...`); let authServerMetadataResponse = await this._fetch(pathToFetch, { method: 'GET', headers: { @@ -428,6 +435,7 @@ class McpHTTPHandle extends Disposable { // For issuer URLs with path components, this inserts the well-known path // after the origin and before the path. const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path insertion: ${openidPathInsertionUrl} ...`); authServerMetadataResponse = await this._fetch(openidPathInsertionUrl, { method: 'GET', headers: { @@ -440,17 +448,16 @@ class McpHTTPHandle extends Disposable { // Try fetching the other discovery URL. For the openid metadata discovery // path, we _ADD_ the well known path after the existing path. // https://datatracker.ietf.org/doc/html/rfc8414#section-3 - authServerMetadataResponse = await this._fetch( - URI.joinPath(URI.parse(authorizationServer), OPENID_CONNECT_DISCOVERY_PATH).toString(true), - { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION - } + const openidPathAdditionUrl = URI.joinPath(URI.parse(authorizationServer), OPENID_CONNECT_DISCOVERY_PATH).toString(true); + this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path addition: ${openidPathAdditionUrl} ...`); + authServerMetadataResponse = await this._fetch(openidPathAdditionUrl, { + method: 'GET', + headers: { + ...addtionalHeaders, + 'Accept': 'application/json', + 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION } - ); + }); if (authServerMetadataResponse.status !== 200) { throw new Error(`Failed to fetch authorization server metadata: ${authServerMetadataResponse.status} ${await this._getErrText(authServerMetadataResponse)}`); } @@ -693,13 +700,13 @@ class McpHTTPHandle extends Disposable { * If the initial request returns 401 and we don't have auth metadata, * it will populate the auth metadata and retry once. */ - private async _fetchWithAuthRetry(url: string, init: MinimalRequestInit, headers: Record): Promise { - const doFetch = () => this._fetch(url, init); + private async _fetchWithAuthRetry(mcpUrl: string, init: MinimalRequestInit, headers: Record): Promise { + const doFetch = () => this._fetch(mcpUrl, init); let res = await doFetch(); if (res.status === 401) { if (!this._authMetadata) { - await this._populateAuthMetadata(url, res); + await this._populateAuthMetadata(mcpUrl, res); await this._addAuthHeader(headers); if (headers['Authorization']) { // Update the headers in the init object From c06be09ab36fdb1949e7cf8f0d1004d0eb6bc798 Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Fri, 12 Sep 2025 00:37:18 +0800 Subject: [PATCH 0224/4355] mcp: conversation id to mcp meta (#265303) * Add conversation id to mcp meta * Address comment --------- Co-authored-by: xiaoyun ding Co-authored-by: leonard520 --- .../workbench/contrib/mcp/common/mcpServer.ts | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 1dbfdcdfffc..d591c72247b 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -862,7 +862,19 @@ export class McpTool implements IMcpTool { const name = this._definition.serverToolName ?? this._definition.name; if (context) { this._server.runningToolCalls.add(context); } try { - return await McpServer.callOn(this._server, h => h.callTool({ name, arguments: params }, token), token); + const meta: Record = {}; + if (context?.chatSessionId) { + meta['vscode.conversationId'] = context.chatSessionId; + } + if (context?.chatRequestId) { + meta['vscode.requestId'] = context.chatRequestId; + } + + return await McpServer.callOn(this._server, h => h.callTool({ + name, + arguments: params, + _meta: Object.keys(meta).length > 0 ? meta : undefined + }, token), token); } finally { if (context) { this._server.runningToolCalls.delete(context); } } @@ -871,13 +883,13 @@ export class McpTool implements IMcpTool { async callWithProgress(params: Record, progress: ToolProgress, context?: IMcpToolCallContext, token?: CancellationToken): Promise { if (context) { this._server.runningToolCalls.add(context); } try { - return await this._callWithProgress(params, progress, token); + return await this._callWithProgress(params, progress, context, token); } finally { if (context) { this._server.runningToolCalls.delete(context); } } } - _callWithProgress(params: Record, progress: ToolProgress, token?: CancellationToken, allowRetry = true): Promise { + _callWithProgress(params: Record, progress: ToolProgress, context?: IMcpToolCallContext, token?: CancellationToken, allowRetry = true): Promise { // serverToolName is always set now, but older cache entries (from 1.99-Insiders) may not have it. const name = this._definition.serverToolName ?? this._definition.name; const progressToken = generateUuid(); @@ -892,12 +904,20 @@ export class McpTool implements IMcpTool { } }); - return h.callTool({ name, arguments: params, _meta: { progressToken } }, token) + const meta: Record = {}; + if (context?.chatSessionId) { + meta['vscode.conversationId'] = context.chatSessionId; + } + if (context?.chatRequestId) { + meta['vscode.requestId'] = context.chatRequestId; + } + + return h.callTool({ name, arguments: params, _meta: meta }, token) .finally(() => listener.dispose()) .catch(err => { const state = this._server.connectionState.get(); if (allowRetry && state.state === McpConnectionState.Kind.Error && state.shouldRetry) { - return this._callWithProgress(params, progress, token, false); + return this._callWithProgress(params, progress, context, token, false); } else { throw err; } From c3005ead7e4019d2e617382952db327e13c8e8a8 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 11 Sep 2025 12:57:38 -0400 Subject: [PATCH 0225/4355] focus terminal, await user enter if free form input is expected in the terminal (#266099) --- .../chatElicitationContentPart.ts | 10 +- .../browser/chatElicitationRequestPart.ts | 4 +- .../contrib/chat/common/chatService.ts | 4 +- .../browser/runInTerminalToolTelemetry.ts | 10 +- .../chatAgentTools/browser/taskHelpers.ts | 6 +- .../browser/tools/monitoring/outputMonitor.ts | 110 +++++++++++++++--- .../browser/tools/monitoring/types.ts | 1 + .../browser/tools/runInTerminalTool.ts | 4 + .../tools/task/createAndRunTaskTool.ts | 2 + .../browser/tools/task/getTaskOutputTool.ts | 2 + .../browser/tools/task/runTaskTool.ts | 2 + .../browser/tools/task/taskToolsTelemetry.ts | 4 + 12 files changed, 134 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts index ca4c21e464d..c044ecffb5a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts @@ -10,7 +10,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js'; import { IChatElicitationRequest } from '../../common/chatService.js'; import { IChatAccessibilityService } from '../chat.js'; -import { ChatConfirmationWidget } from './chatConfirmationWidget.js'; +import { ChatConfirmationWidget, IChatConfirmationButton } from './chatConfirmationWidget.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; import { IAction } from '../../../../../base/common/actions.js'; @@ -28,7 +28,7 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte ) { super(); - const buttons = [ + const buttons: IChatConfirmationButton[] = [ { label: elicitation.acceptButtonLabel, data: true, @@ -38,8 +38,10 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte run: action.run })) }, - { label: elicitation.rejectButtonLabel, data: false, isSecondary: true }, ]; + if (elicitation.rejectButtonLabel && elicitation.reject) { + buttons.push({ label: elicitation.rejectButtonLabel, data: false, isSecondary: true }); + } const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, context.container, { title: elicitation.title, subtitle: elicitation.subtitle, @@ -66,7 +68,7 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte } if (result !== undefined) { await elicitation.accept(result); - } else { + } else if (elicitation.reject) { await elicitation.reject(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts b/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts index 3adf80a8be2..95da12f0fdb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts @@ -23,10 +23,10 @@ export class ChatElicitationRequestPart extends Disposable implements IChatElici public readonly message: string | IMarkdownString, public readonly subtitle: string | IMarkdownString, public readonly acceptButtonLabel: string, - public readonly rejectButtonLabel: string, + public readonly rejectButtonLabel: string | undefined, // True when the primary action is accepted, otherwise the action that was selected public readonly accept: (value: IAction | true) => Promise, - public readonly reject: () => Promise, + public readonly reject?: () => Promise, public readonly source?: ToolDataSource, public readonly moreActions?: IAction[], ) { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 7cf1b465e55..1f402d47128 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -268,14 +268,14 @@ export interface IChatElicitationRequest { title: string | IMarkdownString; message: string | IMarkdownString; acceptButtonLabel: string; - rejectButtonLabel: string; + rejectButtonLabel: string | undefined; subtitle?: string | IMarkdownString; source?: ToolDataSource; state: 'pending' | 'accepted' | 'rejected'; acceptedResult?: Record; moreActions?: IAction[]; accept(value: IAction | true): Promise; - reject(): Promise; + reject?: () => Promise; onDidRequestHide?: Event; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts index 4a456843daa..17ced7f0dcc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalToolTelemetry.ts @@ -109,6 +109,8 @@ export class RunInTerminalToolTelemetry { inputToolAutoAcceptCount: number | undefined; inputToolAutoChars: number | undefined; inputToolManualShownCount: number | undefined; + inputToolFreeFormInputShownCount: number | undefined; + inputToolFreeFormInputCount: number | undefined; }) { type TelemetryEvent = { terminalSessionId: string; @@ -133,6 +135,8 @@ export class RunInTerminalToolTelemetry { inputToolManualRejectCount: number; inputToolManualChars: number; inputToolManualShownCount: number; + inputToolFreeFormInputShownCount: number; + inputToolFreeFormInputCount: number; }; type TelemetryClassification = { owner: 'tyriar'; @@ -160,6 +164,8 @@ export class RunInTerminalToolTelemetry { inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of a suggestion' }; inputToolManualShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to manually accept an input suggestion' }; + inputToolFreeFormInputShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to provide free form input' }; + inputToolFreeFormInputCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user entered free form input after prompting' }; }; this._telemetryService.publicLog2('toolUse.runInTerminal', { terminalSessionId: instance.sessionId, @@ -183,7 +189,9 @@ export class RunInTerminalToolTelemetry { inputToolManualAcceptCount: state.inputToolManualAcceptCount ?? 0, inputToolManualRejectCount: state.inputToolManualRejectCount ?? 0, inputToolManualChars: state.inputToolManualChars ?? 0, - inputToolManualShownCount: state.inputToolManualShownCount ?? 0 + inputToolManualShownCount: state.inputToolManualShownCount ?? 0, + inputToolFreeFormInputShownCount: state.inputToolFreeFormInputShownCount ?? 0, + inputToolFreeFormInputCount: state.inputToolFreeFormInputCount ?? 0, }); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index a1b395bc630..2069886e67f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -165,8 +165,10 @@ export async function collectTerminalResults( inputToolManualRejectCount: number; inputToolManualChars: number; inputToolManualShownCount: number; + inputToolFreeFormInputShownCount: number; + inputToolFreeFormInputCount: number; }>> { - const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; inputToolAutoAcceptCount: number; inputToolAutoChars: number; inputToolManualShownCount: number }> = []; + const results: Array<{ state: OutputMonitorState; name: string; output: string; resources?: ILinkLocation[]; pollDurationMs: number; inputToolManualAcceptCount: number; inputToolManualRejectCount: number; inputToolManualChars: number; inputToolAutoAcceptCount: number; inputToolAutoChars: number; inputToolManualShownCount: number; inputToolFreeFormInputCount: number; inputToolFreeFormInputShownCount: number }> = []; if (token.isCancellationRequested) { return results; } @@ -195,6 +197,8 @@ export async function collectTerminalResults( inputToolAutoAcceptCount: outputMonitor.outputMonitorTelemetryCounters.inputToolAutoAcceptCount ?? 0, inputToolAutoChars: outputMonitor.outputMonitorTelemetryCounters.inputToolAutoChars ?? 0, inputToolManualShownCount: outputMonitor.outputMonitorTelemetryCounters.inputToolManualShownCount ?? 0, + inputToolFreeFormInputShownCount: outputMonitor.outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount ?? 0, + inputToolFreeFormInputCount: outputMonitor.outputMonitorTelemetryCounters.inputToolFreeFormInputCount ?? 0, }); } return results; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 12a72ad1745..142906235bf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -42,6 +42,8 @@ export interface IOutputMonitorTelemetryCounters { inputToolAutoAcceptCount: number; inputToolAutoChars: number; inputToolManualShownCount: number; + inputToolFreeFormInputShownCount: number; + inputToolFreeFormInputCount: number; } export class OutputMonitor extends Disposable implements IOutputMonitor { @@ -63,7 +65,9 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { inputToolManualChars: 0, inputToolAutoAcceptCount: 0, inputToolAutoChars: 0, - inputToolManualShownCount: 0 + inputToolManualShownCount: 0, + inputToolFreeFormInputShownCount: 0, + inputToolFreeFormInputCount: 0, }; get outputMonitorTelemetryCounters(): Readonly { return this._outputMonitorTelemetryCounters; } @@ -159,9 +163,32 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { private async _handleIdleState(token: CancellationToken): Promise<{ resources?: ILinkLocation[]; modelOutputEvalResponse?: string; shouldContinuePollling: boolean }> { const confirmationPrompt = await this._determineUserInputOptions(this._execution, token); - const suggestedOptionResult = await this._selectAndHandleOption(confirmationPrompt, token); + + if (confirmationPrompt?.detectedRequestForFreeFormInput) { + this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; + const focusedTerminal = await this._focusTerminalForUserInput(token, this._execution, confirmationPrompt); + if (focusedTerminal) { + await new Promise(resolve => { + const disposable = this._execution.instance.onData(data => { + if (data === '\r' || data === '\n' || data === '\r\n') { + this._outputMonitorTelemetryCounters.inputToolFreeFormInputCount++; + disposable.dispose(); + resolve(); + } + }); + }); + // Small delay to ensure input is processed + await timeout(200); + // Continue polling as we sent the input + return { shouldContinuePollling: true }; + } else { + // User declined + return { shouldContinuePollling: false }; + } + } if (confirmationPrompt?.options.length) { + const suggestedOptionResult = await this._selectAndHandleOption(confirmationPrompt, token); if (suggestedOptionResult?.sentToTerminal) { // Continue polling as we sent the input return { shouldContinuePollling: true }; @@ -384,33 +411,36 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } const lastFiveLines = execution.getOutput(this._lastPromptMarker).trimEnd().split('\n').slice(-5).join('\n'); const promptText = - `Analyze the following terminal output. If it contains a prompt requesting user input (such as a confirmation, selection, or yes/no question) and that prompt has NOT already been answered, extract the prompt text and the possible options as a JSON object with keys 'prompt' and 'options' (an array of strings or an object with option to description mappings). For example, if the options are "[Y] Yes [A] Yes to All [N] No [L] No to All [C] Cancel", the option to description mappings would be {"Y": "Yes", "A": "Yes to All", "N": "No", "L": "No to All", "C": "Cancel"}. If there is no such prompt, return null. + `Analyze the following terminal output. If it contains a prompt requesting user input (such as a confirmation, selection, or yes/no question) and that prompt has NOT already been answered, extract the prompt text. The prompt may ask to choose from a set. If so, extract the possible options as a JSON object with keys 'prompt', 'options' (an array of strings or an object with option to description mappings), and 'freeFormInput': false. If no options are provided, and free form input is requested, for example: Password:, return the word freeFormInput. For example, if the options are "[Y] Yes [A] Yes to All [N] No [L] No to All [C] Cancel", the option to description mappings would be {"Y": "Yes", "A": "Yes to All", "N": "No", "L": "No to All", "C": "Cancel"}. If there is no such prompt, return null. Examples: 1. Output: "Do you want to overwrite? (y/n)" - Response: {"prompt": "Do you want to overwrite?", "options": ["y", "n"]} + Response: {"prompt": "Do you want to overwrite?", "options": ["y", "n"], "freeFormInput": false} 2. Output: "Confirm: [Y] Yes [A] Yes to All [N] No [L] No to All [C] Cancel" - Response: {"prompt": "Confirm", "options": ["Y", "A", "N", "L", "C"]} + Response: {"prompt": "Confirm", "options": ["Y", "A", "N", "L", "C"], "freeFormInput": false} 3. Output: "Accept license terms? (yes/no)" - Response: {"prompt": "Accept license terms?", "options": ["yes", "no"]} + Response: {"prompt": "Accept license terms?", "options": ["yes", "no"], "freeFormInput": false} 4. Output: "Press Enter to continue" - Response: {"prompt": "Press Enter to continue", "options": ["Enter"]} + Response: {"prompt": "Press Enter to continue", "options": ["Enter"], "freeFormInput": false} 5. Output: "Type Yes to proceed" - Response: {"prompt": "Type Yes to proceed", "options": ["Yes"]} + Response: {"prompt": "Type Yes to proceed", "options": ["Yes"], "freeFormInput": false} 6. Output: "Continue [y/N]" - Response: {"prompt": "Continue", "options": ["y", "N"]} + Response: {"prompt": "Continue", "options": ["y", "N"], "freeFormInput": false} + Alternatively, the prompt may request free form input, for example: + 1. Output: "Enter your username:" + Response: {"prompt": "Enter your username:", "freeFormInput": true, "options": []} + 2. Output: "Password:" + Response: {"prompt": "Password:", "freeFormInput": true, "options": []} Now, analyze this output: ${lastFiveLines} `; - const response = await this._languageModelsService.sendChatRequest(models[0], new ExtensionIdentifier('core'), [ - { role: ChatMessageRole.User, content: [{ type: 'text', value: promptText }] } - ], {}, token); + const response = await this._languageModelsService.sendChatRequest(models[0], new ExtensionIdentifier('core'), [{ role: ChatMessageRole.User, content: [{ type: 'text', value: promptText }] }], {}, token); const responseText = await getTextResponseFromStream(response); try { const match = responseText.match(/\{[\s\S]*\}/); @@ -419,15 +449,17 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if ( isObject(obj) && 'prompt' in obj && isString(obj.prompt) && - 'options' in obj + 'options' in obj && + 'options' in obj && + 'freeFormInput' in obj && typeof obj.freeFormInput === 'boolean' ) { if (this._lastPrompt === obj.prompt) { return; } if (Array.isArray(obj.options) && obj.options.every(isString)) { - return { prompt: obj.prompt, options: obj.options }; + return { prompt: obj.prompt, options: obj.options, detectedRequestForFreeFormInput: obj.freeFormInput }; } else if (isObject(obj.options) && Object.values(obj.options).every(isString)) { - return { prompt: obj.prompt, options: Object.keys(obj.options), descriptions: Object.values(obj.options) }; + return { prompt: obj.prompt, options: Object.keys(obj.options), descriptions: Object.values(obj.options), detectedRequestForFreeFormInput: obj.freeFormInput }; } } } @@ -491,6 +523,54 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return description ? { suggestedOption: { description, option: validOption }, sentToTerminal } : { suggestedOption: validOption, sentToTerminal }; } + private async _focusTerminalForUserInput(token: CancellationToken, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { + const chatModel = this._chatService.getSession(execution.sessionId); + if (!(chatModel instanceof ChatModel)) { + return false; + } + const request = chatModel.getRequests().at(-1); + if (!request) { + return false; + } + const userPrompt = new Promise(resolve => { + const thePart = this._register(new ChatElicitationRequestPart( + new MarkdownString(localize('poll.terminal.inputRequest', "The terminal is awaiting input.")), + new MarkdownString(localize('poll.terminal.requireInput', "{0}\nPlease provide the required input to the terminal.\n\n", confirmationPrompt.prompt)), + '', + localize('poll.terminal.enterInput', 'Focus terminal'), + undefined, + async () => { + thePart.state = 'accepted'; + thePart.hide(); + thePart.dispose(); + execution.instance.focus(true); + resolve(true); + }, + undefined, + undefined + )); + this._register(thePart.onDidRequestHide(() => { + this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; + })); + this._register(token.onCancellationRequested(() => { + thePart.hide(); + thePart.dispose(); + resolve(false); + })); + const inputDataDisposable = this._register(execution.instance.onDidInputData((data) => { + if (!data || data === '\r' || data === '\n' || data === '\r\n') { + thePart.hide(); + thePart.dispose(); + inputDataDisposable.dispose(); + this._state = OutputMonitorState.PollingForIdle; + resolve(true); + } + })); + chatModel.acceptResponseProgress(request, thePart); + }); + return await userPrompt; + } + private async _confirmRunInTerminal(suggestedOption: SuggestedOption, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { const chatModel = this._chatService.getSession(execution.sessionId); if (!(chatModel instanceof ChatModel)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts index 4e9cc7ca1d5..71ae7ce7d1c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts @@ -12,6 +12,7 @@ export interface IConfirmationPrompt { prompt: string; options: string[]; descriptions?: string[]; + detectedRequestForFreeFormInput: boolean; } export interface IExecution { 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 2c54ac75b39..0fee7e4c895 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -495,6 +495,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolAutoAcceptCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolAutoAcceptCount, inputToolAutoChars: outputMonitor?.outputMonitorTelemetryCounters.inputToolAutoChars, inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolManualShownCount, + inputToolFreeFormInputCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolFreeFormInputCount, + inputToolFreeFormInputShownCount: outputMonitor?.outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount }); } } else { @@ -577,6 +579,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { inputToolAutoAcceptCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolAutoAcceptCount, inputToolAutoChars: outputMonitor?.outputMonitorTelemetryCounters?.inputToolAutoChars, inputToolManualShownCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolManualShownCount, + inputToolFreeFormInputCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolFreeFormInputCount, + inputToolFreeFormInputShownCount: outputMonitor?.outputMonitorTelemetryCounters?.inputToolFreeFormInputShownCount }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts index a2475c600c7..45d4a7716fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts @@ -127,6 +127,8 @@ export class CreateAndRunTaskTool implements IToolImpl { inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, inputToolManualShownCount: r.inputToolManualShownCount ?? 0, + inputToolFreeFormInputCount: r.inputToolFreeFormInputCount ?? 0, + inputToolFreeFormInputShownCount: r.inputToolFreeFormInputShownCount ?? 0 }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts index 9e36582b638..835dfb5832d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts @@ -115,6 +115,8 @@ export class GetTaskOutputTool extends Disposable implements IToolImpl { inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, inputToolManualShownCount: r.inputToolManualShownCount ?? 0, + inputToolFreeFormInputCount: r.inputToolFreeFormInputCount ?? 0, + inputToolFreeFormInputShownCount: r.inputToolFreeFormInputShownCount ?? 0 }); } const details = terminalResults.map(r => `Terminal: ${r.name}\nOutput:\n${r.output}`); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts index 883d3ec68bd..6e6f0f3a107 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts @@ -87,6 +87,8 @@ export class RunTaskTool implements IToolImpl { inputToolManualRejectCount: r.inputToolManualRejectCount ?? 0, inputToolManualChars: r.inputToolManualChars ?? 0, inputToolManualShownCount: r.inputToolManualShownCount ?? 0, + inputToolFreeFormInputShownCount: r.inputToolFreeFormInputShownCount ?? 0, + inputToolFreeFormInputCount: r.inputToolFreeFormInputCount ?? 0 }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts index d7c991c9c96..b0f43a4b780 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/taskToolsTelemetry.ts @@ -12,6 +12,8 @@ export type TaskToolClassification = { inputToolManualRejectCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user manually rejected a detected suggestion' }; inputToolManualChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters input by manual acceptance of suggestions' }; inputToolManualShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to manually accept an input suggestion' }; + inputToolFreeFormInputShownCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user was prompted to provide free form input' }; + inputToolFreeFormInputCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of times the user provided free form input after prompting' }; owner: 'meganrogge'; comment: 'Understanding the usage of the task tools'; }; @@ -23,4 +25,6 @@ export type TaskToolEvent = { inputToolManualRejectCount: number; inputToolManualChars: number; inputToolManualShownCount: number; + inputToolFreeFormInputShownCount: number; + inputToolFreeFormInputCount: number; }; From 1986d172c4a72cf3f2e2b09017ba1b5f0701b862 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:28:59 -0700 Subject: [PATCH 0226/4355] skip test for failing again (#266230) --- .../src/singlefolder-tests/interactiveWindow.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index ca15c44683b..03a1d3833ae 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -108,7 +108,8 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { } }); - test('Interactive window has the correct kernel', async function () { + // https://github.com/microsoft/vscode/issues/266229 + test.skip('Interactive window has the correct kernel', async function () { // Extend timeout a bit as kernel association can be async & occasionally slow on CI this.timeout(20000); assert.ok(vscode.workspace.workspaceFolders); From b6506799ee26f7819d67097cd8c6a55ce9e42be0 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:44:06 -0700 Subject: [PATCH 0227/4355] Fix TOC in release notes For #266235 We should create the html content before sanitization, not after --- .../update/browser/releaseNotesEditor.ts | 49 +++++++++------- ...se_notes_renderer_Should_render_TOC.0.snap | 22 +++++++ .../test/browser/releaseNotesRenderer.test.ts | 58 +++++++++++++++++++ 3 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_TOC.0.snap create mode 100644 src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 88b67f560c8..2eeb8c84353 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -265,26 +265,7 @@ export class ReleaseNotesManager extends Disposable { private async renderBody(fileContent: { text: string; base: URI }) { const nonce = generateUuid(); - const content = await renderMarkdownDocument(fileContent.text, this._extensionService, this._languageService, { - sanitizerConfig: { - allowRelativeMediaPaths: true, - allowedLinkProtocols: { - override: [Schemas.http, Schemas.https, Schemas.command] - } - }, - markedExtensions: [{ - renderer: { - html: this._simpleSettingRenderer.getHtmlRenderer(), - codespan: this._simpleSettingRenderer.getCodeSpanRenderer(), - } - }] - }); - - // Remove HTML comment markers around table of contents navigation - const processedContent = content - .toString() - .replace(//gi, ''); + const processedContent = await renderReleaseNotesMarkdown(fileContent.text, this._extensionService, this._languageService, this._simpleSettingRenderer); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; @@ -628,3 +609,31 @@ export class ReleaseNotesManager extends Disposable { } } } + +export async function renderReleaseNotesMarkdown( + text: string, + extensionService: IExtensionService, + languageService: ILanguageService, + simpleSettingRenderer: SimpleSettingRenderer, +): Promise { + // Remove HTML comment markers around table of contents navigation + text = text + .toString() + .replace(//gi, ''); + + return renderMarkdownDocument(text, extensionService, languageService, { + sanitizerConfig: { + allowRelativeMediaPaths: true, + allowedLinkProtocols: { + override: [Schemas.http, Schemas.https, Schemas.command] + } + }, + markedExtensions: [{ + renderer: { + html: simpleSettingRenderer.getHtmlRenderer(), + codespan: simpleSettingRenderer.getCodeSpanRenderer(), + } + }] + }); +} diff --git a/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_TOC.0.snap b/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_TOC.0.snap new file mode 100644 index 00000000000..f39cc5b43c8 --- /dev/null +++ b/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_TOC.0.snap @@ -0,0 +1,22 @@ + + + + +
a
+ +
+ +
+

text

+
+
+ +
In this update
+ + +
+ +

Test

+
\ No newline at end of file diff --git a/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts new file mode 100644 index 00000000000..a3cf541c9be --- /dev/null +++ b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { assertSnapshot } from '../../../../../base/test/common/snapshot.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { ContextMenuService } from '../../../../../platform/contextview/browser/contextMenuService.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; +import { SimpleSettingRenderer } from '../../../markdown/browser/markdownSettingRenderer.js'; +import { renderReleaseNotesMarkdown } from '../../browser/releaseNotesEditor.js'; + + +suite('Release notes renderer', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + let extensionService: IExtensionService; + let languageService: ILanguageService; + + setup(() => { + instantiationService = store.add(new TestInstantiationService()); + extensionService = instantiationService.get(IExtensionService); + languageService = instantiationService.get(ILanguageService); + + instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); + }); + + test('Should render TOC', async () => { + const content = ` + + + +
a
+ +
+ +> text + + + +## Test`; + + const result = await renderReleaseNotesMarkdown(content, extensionService, languageService, instantiationService.createInstance(SimpleSettingRenderer)); + await assertSnapshot(result.toString()); + }); +}); From 955e9be1ac0d9262d799eaeee85b9c25012f83b6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 11 Sep 2025 11:01:33 -0700 Subject: [PATCH 0228/4355] Disable rendererProfiling (#266237) Until #265654 is fixed --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c9cead3fcac..e003424c2c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -215,7 +215,7 @@ "msjsdiag.debugger-for-chrome": "workspace" }, "terminal.integrated.suggest.enabled": true, - "application.experimental.rendererProfiling": true, + // "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654 "editor.aiStats.enabled": true, // Team selfhosting on ai stats "chat.checkpoints.showFileChanges": true, From 2f4268146446aed921a24a850e319171eed1b26a Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:37:50 -0700 Subject: [PATCH 0229/4355] allow for long install dependency times (#266240) --- build/azure-pipelines/win32/product-build-win32-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 7d5222e347f..7507f2b1334 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -121,21 +121,21 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - powershell: npm run smoketest-no-compile -- --tracing --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" displayName: 🧪 Run smoke tests (Electron) - timeoutInMinutes: 20 + timeoutInMinutes: 40 - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - powershell: npm run smoketest-no-compile -- --web --tracing --headless env: VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)-web displayName: 🧪 Run smoke tests (Browser, Chromium) - timeoutInMinutes: 20 + timeoutInMinutes: 40 - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - powershell: npm run smoketest-no-compile -- --tracing --remote --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" env: VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH) displayName: 🧪 Run smoke tests (Remote) - timeoutInMinutes: 20 + timeoutInMinutes: 40 - powershell: .\build\azure-pipelines\win32\listprocesses.bat displayName: Diagnostics after smoke test run From 1ff774f0fc9071c193b86945fb7425ba50d21ca7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 11 Sep 2025 14:43:22 -0400 Subject: [PATCH 0230/4355] disposable -> observable for chat elictiation request part (#266246) --- .../chatContentParts/chatElicitationContentPart.ts | 9 +++++++-- .../chat/browser/chatElicitationRequestPart.ts | 8 ++++---- src/vs/workbench/contrib/chat/common/chatService.ts | 3 ++- .../browser/tools/monitoring/outputMonitor.ts | 13 +++++++++---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts index c044ecffb5a..09a26ea596d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts @@ -6,6 +6,7 @@ import { Emitter } from '../../../../../base/common/event.js'; import { IMarkdownString, isMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js'; import { IChatElicitationRequest } from '../../common/chatService.js'; @@ -51,8 +52,12 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte })); confirmationWidget.setShowButtons(elicitation.state === 'pending'); - if (elicitation.onDidRequestHide) { - this._register(elicitation.onDidRequestHide(() => this.domNode.remove())); + if (elicitation.isHidden) { + this._register(autorun(reader => { + if (elicitation.isHidden?.read(reader)) { + this.domNode.remove(); + } + })); } this._register(confirmationWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts b/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts index 95da12f0fdb..8b77997385b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IAction } from '../../../../base/common/actions.js'; -import { Emitter } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IObservable, observableValue } from '../../../../base/common/observable.js'; import { IChatElicitationRequest } from '../common/chatService.js'; import { ToolDataSource } from '../common/languageModelToolsService.js'; @@ -15,8 +15,8 @@ export class ChatElicitationRequestPart extends Disposable implements IChatElici public state: 'pending' | 'accepted' | 'rejected' = 'pending'; public acceptedResult?: Record; - private _onDidRequestHide = this._register(new Emitter()); - public readonly onDidRequestHide = this._onDidRequestHide.event; + private readonly _isHiddenValue = observableValue('isHidden', false); + public readonly isHidden: IObservable = this._isHiddenValue; constructor( public readonly title: string | IMarkdownString, @@ -34,7 +34,7 @@ export class ChatElicitationRequestPart extends Disposable implements IChatElici } hide(): void { - this._onDidRequestHide.fire(); + this._isHiddenValue.set(true, undefined, undefined); } public toJSON() { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 1f402d47128..187071913f6 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -276,7 +276,8 @@ export interface IChatElicitationRequest { moreActions?: IAction[]; accept(value: IAction | true): Promise; reject?: () => Promise; - onDidRequestHide?: Event; + isHidden?: IObservable; + hide?: () => void; } export interface IChatThinkingPart { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 142906235bf..d6df6c071eb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -10,6 +10,7 @@ import { CancellationToken } from '../../../../../../../base/common/cancellation import { Emitter, Event } from '../../../../../../../base/common/event.js'; import { MarkdownString } from '../../../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../../../../base/common/observable.js'; import { isObject, isString } from '../../../../../../../base/common/types.js'; import { localize } from '../../../../../../../nls.js'; import { ExtensionIdentifier } from '../../../../../../../platform/extensions/common/extensions.js'; @@ -549,8 +550,10 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { undefined, undefined )); - this._register(thePart.onDidRequestHide(() => { - this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; + this._register(autorun(reader => { + if (thePart.isHidden?.read(reader)) { + this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; + } })); this._register(token.onCancellationRequested(() => { thePart.hide(); @@ -615,8 +618,10 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { undefined, getMoreActions(suggestedOption, confirmationPrompt) )); - this._register(thePart.onDidRequestHide(() => { - this._outputMonitorTelemetryCounters.inputToolManualShownCount++; + this._register(autorun(reader => { + if (thePart.isHidden?.read(reader)) { + this._outputMonitorTelemetryCounters.inputToolManualShownCount++; + } })); const inputDataDisposable = this._register(execution.instance.onDidInputData(() => { thePart.hide(); From ad9f9feaa0d00105d4c2a8a545763a0babf3f285 Mon Sep 17 00:00:00 2001 From: Matthias Wolf <78562192+mawosoft@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:54:21 +0200 Subject: [PATCH 0231/4355] Fix PowerShell shell integration when strict mode is enabled. Check if variable exists before accessing it to prevent script failures in strict mode. See #248625 for previous fix. --- .../contrib/terminal/common/scripts/shellIntegration.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 index 6bc5b5c137d..24c5d110055 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 @@ -4,7 +4,7 @@ # --------------------------------------------------------------------------------------------- # Prevent installing more than once per session -if ($Global:__VSCodeState.OriginalPrompt -ne $null) { +if ((Test-Path variable:global:__VSCodeState) -and $null -ne $Global:__VSCodeState.OriginalPrompt) { return; } From 0c41536af78e75d9f09cb6fcfacee7ea5a6e651e Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Thu, 11 Sep 2025 12:13:59 -0700 Subject: [PATCH 0232/4355] Refactor of chat sessions and actions (#266244) --- .../browser/actions/chatSessionActions.ts | 131 ++---------- .../contrib/chat/browser/chatSessions.ts | 190 +++++------------- .../chatSessions/chatSessionTracker.ts | 8 +- .../chat/browser/chatSessions/common.ts | 5 + 4 files changed, 84 insertions(+), 250 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 995bc90e1b0..1e3bf618d27 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -20,14 +20,14 @@ import { AUX_WINDOW_GROUP, IEditorService, SIDE_GROUP } from '../../../../servic import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; import { ChatConfiguration } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { ILocalChatSessionItem, VIEWLET_ID } from '../chatSessions.js'; -import { findExistingChatEditorByUri } from '../chatSessions/common.js'; +import { VIEWLET_ID } from '../chatSessions.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; import { CHAT_CATEGORY } from './chatActions.js'; @@ -42,26 +42,7 @@ export interface IChatSessionContext { interface IMarshalledChatSessionContext { $mid: MarshalledId.ChatSessionContext; - session: { - id: string; - label: string; - editor?: ChatEditorInput; - widget?: any; - sessionType?: 'editor' | 'widget'; - provider?: { chatSessionType?: string }; - }; -} - -function isLocalChatSessionItem(item: IChatSessionItem): item is ILocalChatSessionItem { - return ('editor' in item && 'group' in item) || ('widget' in item && 'sessionType' in item); -} - -function isMarshalledChatSessionContext(obj: unknown): obj is IMarshalledChatSessionContext { - return !!obj && - typeof obj === 'object' && - '$mid' in obj && - (obj as any).$mid === MarshalledId.ChatSessionContext && - 'session' in obj; + session: ChatSessionItemWithProvider; } export class RenameChatSessionAction extends Action2 { @@ -82,46 +63,14 @@ export class RenameChatSessionAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise { + async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { if (!context) { return; } // Handle marshalled context from menu actions - let sessionContext: IChatSessionContext; - if (isMarshalledChatSessionContext(context)) { - const session = context.session; - // Extract actual session ID based on session type - let actualSessionId: string | undefined; - const currentTitle = session.label; - - // For history sessions, we need to extract the actual session ID - if (session.id.startsWith('history-')) { - actualSessionId = session.id.replace('history-', ''); - } else if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - actualSessionId = session.editor.sessionId; - } else if (session.sessionType === 'widget' && session.widget) { - actualSessionId = session.widget.viewModel?.model.sessionId; - } else { - // Fall back to using the session ID directly - actualSessionId = session.id; - } - - if (!actualSessionId) { - return; // Can't proceed without a session ID - } - - sessionContext = { - sessionId: actualSessionId, - sessionType: session.sessionType || 'editor', - currentTitle: currentTitle, - editorInput: session.editor, - widget: session.widget - }; - } else { - sessionContext = context; - } - + const sessionId = context.session.id.replace('history-', ''); + const label = context.session.label; const chatSessionsService = accessor.get(IChatSessionsService); const logService = accessor.get(ILogService); const chatService = accessor.get(IChatService); @@ -129,7 +78,7 @@ export class RenameChatSessionAction extends Action2 { try { // Find the chat sessions view and trigger inline rename mode // This is similar to how file renaming works in the explorer - await chatSessionsService.setEditableSession(sessionContext.sessionId, { + await chatSessionsService.setEditableSession(sessionId, { validationMessage: (value: string) => { if (!value || value.trim().length === 0) { return { content: localize('renameSession.emptyName', "Name cannot be empty"), severity: Severity.Error }; @@ -140,12 +89,12 @@ export class RenameChatSessionAction extends Action2 { return null; }, placeholder: localize('renameSession.placeholder', "Enter new name for chat session"), - startingValue: sessionContext.currentTitle, + startingValue: label, onFinish: async (value: string, success: boolean) => { - if (success && value && value.trim() !== sessionContext.currentTitle) { + if (success && value && value.trim() !== label) { try { const newTitle = value.trim(); - chatService.setChatSessionTitle(sessionContext.sessionId, newTitle); + chatService.setChatSessionTitle(sessionId, newTitle); // Notify the local sessions provider that items have changed chatSessionsService.notifySessionItemsChanged('local'); } catch (error) { @@ -155,7 +104,7 @@ export class RenameChatSessionAction extends Action2 { ); } } - await chatSessionsService.setEditableSession(sessionContext.sessionId, null); + await chatSessionsService.setEditableSession(sessionId, null); } }); } catch (error) { @@ -180,46 +129,13 @@ export class DeleteChatSessionAction extends Action2 { }); } - async run(accessor: ServicesAccessor, context?: IChatSessionContext | IMarshalledChatSessionContext): Promise { + async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { if (!context) { return; } // Handle marshalled context from menu actions - let sessionContext: IChatSessionContext; - if (isMarshalledChatSessionContext(context)) { - const session = context.session; - // Extract actual session ID based on session type - let actualSessionId: string | undefined; - const currentTitle = session.label; - - // For history sessions, we need to extract the actual session ID - if (session.id.startsWith('history-')) { - actualSessionId = session.id.replace('history-', ''); - } else if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - actualSessionId = session.editor.sessionId; - } else if (session.sessionType === 'widget' && session.widget) { - actualSessionId = session.widget.viewModel?.model.sessionId; - } else { - // Fall back to using the session ID directly - actualSessionId = session.id; - } - - if (!actualSessionId) { - return; // Can't proceed without a session ID - } - - sessionContext = { - sessionId: actualSessionId, - sessionType: session.sessionType || 'editor', - currentTitle: currentTitle, - editorInput: session.editor, - widget: session.widget - }; - } else { - sessionContext = context; - } - + const sessionId = context.session.id; const chatService = accessor.get(IChatService); const dialogService = accessor.get(IDialogService); const logService = accessor.get(ILogService); @@ -235,7 +151,7 @@ export class DeleteChatSessionAction extends Action2 { }); if (result.confirmed) { - await chatService.removeHistoryEntry(sessionContext.sessionId); + await chatService.removeHistoryEntry(sessionId); // Notify the local sessions provider that items have changed chatSessionsService.notifySessionItemsChanged('local'); } @@ -267,7 +183,7 @@ export class OpenChatSessionInNewWindowAction extends Action2 { const editorService = accessor.get(IEditorService); const chatWidgetService = accessor.get(IChatWidgetService); - const sessionId = context.session.id.replace('history-', ''); + const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); @@ -278,7 +194,7 @@ export class OpenChatSessionInNewWindowAction extends Action2 { return; } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { return; - } else if (isLocalChatSessionItem(context.session) || context.session.id.startsWith('history-')) { + } else if (isLocalChatSessionItem(context.session)) { const options: IChatEditorOptions = { target: { sessionId }, ignoreInView: true, @@ -324,7 +240,7 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { const editorService = accessor.get(IEditorService); const chatWidgetService = accessor.get(IChatWidgetService); - const sessionId = context.session.id.replace('history-', ''); + const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); @@ -335,7 +251,7 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { return; } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { return; - } else if (isLocalChatSessionItem(context.session) || context.session.id.startsWith('history-')) { + } else if (isLocalChatSessionItem(context.session)) { const options: IChatEditorOptions = { target: { sessionId }, ignoreInView: true, @@ -383,7 +299,7 @@ export class OpenChatSessionInSidebarAction extends Action2 { const viewsService = accessor.get(IViewsService); const chatWidgetService = accessor.get(IChatWidgetService); const editorGroupsService = accessor.get(IEditorGroupsService); - const sessionId = context.session.id.replace('history-', ''); + const sessionId = context.session.id; if (context.session.provider?.chatSessionType) { const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); // Check if this session is already open in another editor @@ -400,11 +316,8 @@ export class OpenChatSessionInSidebarAction extends Action2 { const chatViewPane = await viewsService.openView(ChatViewId) as ChatViewPane; if (chatViewPane) { // Handle different session types - if (context.session && (isLocalChatSessionItem(context.session) || sessionId.startsWith('history-'))) { - // For local sessions and history sessions, remove the 'history-' prefix if present - const sessionIdWithoutHistory = sessionId.replace('history-', ''); - // Load using the session ID directly - await chatViewPane.loadSession(sessionIdWithoutHistory); + if (context.session && (isLocalChatSessionItem(context.session))) { + await chatViewPane.loadSession(sessionId); } else { // For external provider sessions, create a URI and load using that const providerType = context.session.provider?.chatSessionType || 'external'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts index 3fc801d5189..fba8e90a5c3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.ts @@ -80,9 +80,9 @@ import { IChatEditorOptions } from './chatEditor.js'; import { ChatEditorInput } from './chatEditorInput.js'; import { allowedChatMarkdownHtmlTags } from './chatMarkdownRenderer.js'; import { ChatSessionTracker } from './chatSessions/chatSessionTracker.js'; -import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getChatSessionType, isChatSession } from './chatSessions/common.js'; -import { ChatViewPane } from './chatViewPane.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getChatSessionType, isChatSession, isLocalChatSessionItem } from './chatSessions/common.js'; import './media/chatSessions.css'; +import { ChatViewPane } from './chatViewPane.js'; export const VIEWLET_ID = 'workbench.view.chat.sessions'; @@ -150,7 +150,7 @@ function processSessionsWithTimeGrouping(sessions: ChatSessionItemWithProvider[] // Helper function to create context overlay for session items function getSessionItemContextOverlay( - session: IChatSessionItem, + session: ChatSessionItemWithProvider, provider?: IChatSessionItemProvider, chatWidgetService?: IChatWidgetService, chatService?: IChatService, @@ -166,28 +166,24 @@ function getSessionItemContextOverlay( } // Mark history items - const isHistoryItem = session.id.startsWith('history-'); - overlay.push([ChatContextKeys.isHistoryItem.key, isHistoryItem]); + overlay.push([ChatContextKeys.isHistoryItem.key, session.isHistory]); // Mark active sessions - check if session is currently open in editor or widget let isActiveSession = false; - if (!isHistoryItem && provider?.chatSessionType === 'local') { + if (!session.isHistory && provider?.chatSessionType === 'local') { // Local non-history sessions are always active isActiveSession = true; - } else if (isHistoryItem && chatWidgetService && chatService && editorGroupsService) { - // For history sessions, check if they're currently opened somewhere - const sessionId = session.id.substring('history-'.length); // Remove 'history-' prefix - + } else if (session.isHistory && chatWidgetService && chatService && editorGroupsService) { // Check if session is open in a chat widget - const widget = chatWidgetService.getWidgetBySessionId(sessionId); + const widget = chatWidgetService.getWidgetBySessionId(session.id); if (widget) { isActiveSession = true; } else { // Check if session is open in any editor for (const group of editorGroupsService.groups) { for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && editor.sessionId === sessionId) { + if (editor instanceof ChatEditorInput && editor.sessionId === session.id) { isActiveSession = true; break; } @@ -204,16 +200,6 @@ function getSessionItemContextOverlay( return overlay; } -// Extended interface for local chat session items that includes editor information or widget information -export interface ILocalChatSessionItem extends IChatSessionItem { - editor?: EditorInput; - group?: IEditorGroup; - widget?: IChatWidget; - sessionType: 'editor' | 'widget'; - description?: string; - status?: ChatSessionStatus; -} - interface IGettingStartedItem { id: string; label: string; @@ -525,13 +511,11 @@ class LocalChatSessionsProvider extends Disposable implements IChatSessionItemPr const chatWidget = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) .find(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; - const widgetSession: ILocalChatSessionItem & ChatSessionItemWithProvider = { + const widgetSession: ChatSessionItemWithProvider = { id: LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID, label: chatWidget?.viewModel?.model.title || nls.localize2('chat.sessions.chatView', "Chat").value, description: nls.localize('chat.sessions.chatView.description', "Chat View"), iconPath: Codicon.chatSparkle, - widget: chatWidget, - sessionType: 'widget', status, provider: this }; @@ -541,8 +525,6 @@ class LocalChatSessionsProvider extends Disposable implements IChatSessionItemPr this.editorOrder.forEach((editorKey, index) => { const editorInfo = editorMap.get(editorKey); if (editorInfo) { - const sessionId = `local-${editorInfo.group.id}-${index}`; - // Determine status and timestamp for editor-based session let status: ChatSessionStatus | undefined; let timestamp: number | undefined; @@ -560,22 +542,18 @@ class LocalChatSessionsProvider extends Disposable implements IChatSessionItemPr timestamp = Date.now(); } } + const editorSession: ChatSessionItemWithProvider = { + id: editorInfo.editor.sessionId, + label: editorInfo.editor.getName(), + iconPath: Codicon.chatSparkle, + status, + provider: this, + timing: { + startTime: timestamp ?? 0 + } + }; + sessions.push(editorSession); } - - const editorSession: ILocalChatSessionItem & ChatSessionItemWithProvider = { - id: sessionId, - label: editorInfo.editor.getName(), - iconPath: Codicon.chatSparkle, - editor: editorInfo.editor, - group: editorInfo.group, - sessionType: 'editor', - status, - provider: this, - timing: { - startTime: timestamp ?? 0 - } - }; - sessions.push(editorSession); } }); @@ -877,13 +855,14 @@ class SessionsDataSource implements IAsyncDataSource ({ - id: `history-${historyDetail.sessionId}`, + id: historyDetail.sessionId, label: historyDetail.title, iconPath: Codicon.chatSparkle, provider: this.provider, timing: { startTime: historyDetail.lastMessageDate ?? Date.now() - } + }, + isHistory: true, })); // Apply sorting and time grouping @@ -1003,10 +982,6 @@ class SessionsRenderer extends Disposable implements ITreeRenderer, index: number, templateData: ISessionTemplateData): void { - const session = element.element; - const sessionWithProvider = session as ChatSessionItemWithProvider; + const session = element.element as ChatSessionItemWithProvider; // Add CSS class for local sessions - if (sessionWithProvider.provider.chatSessionType === 'local') { + let editableData: IEditableData | undefined; + if (isLocalChatSessionItem(session)) { templateData.container.classList.add('local-session'); + editableData = this.chatSessionsService.getEditableData(session.id); } else { templateData.container.classList.remove('local-session'); } - // Get the actual session ID for editable data lookup - let actualSessionId: string | undefined; - if (this.isLocalChatSessionItem(session)) { - if (session.sessionType === 'editor' && session.editor instanceof ChatEditorInput) { - actualSessionId = session.editor.sessionId; - } else if (session.sessionType === 'widget' && session.widget) { - actualSessionId = session.widget.viewModel?.model.sessionId; - } - } else if (session.id.startsWith('history-')) { - // For history items, extract the actual session ID by removing the 'history-' prefix - actualSessionId = session.id.substring('history-'.length); - } - // Check if this session is being edited using the actual session ID - const editableData = actualSessionId ? this.chatSessionsService.getEditableData(actualSessionId) : undefined; if (editableData) { // Render input box for editing templateData.actionBar.clear(); @@ -1105,7 +1067,7 @@ class SessionsRenderer extends Disposable implements ITreeRenderer { - if (this.isLocalChatSessionItem(element)) { + if (element.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { return null; } - if (element.provider.chatSessionType === 'local') { - const actualSessionId = element.id.startsWith('history-') ? element.id.substring('history-'.length) : element.id; - return ChatSessionUri.forSession(element.provider.chatSessionType, actualSessionId); - } - return ChatSessionUri.forSession(element.provider.chatSessionType, element.id); }; @@ -1700,87 +1653,54 @@ class SessionsViewPane extends ViewPane { } } - private async openChatSession(element: ChatSessionItemWithProvider) { - if (!element || !element.id) { + private async openChatSession(session: ChatSessionItemWithProvider) { + if (!session || !session.id) { return; } try { // Check first if we already have an open editor for this session - const sessionWithProvider = element as ChatSessionItemWithProvider; - sessionWithProvider.id = sessionWithProvider.id.replace('history-', ''); - const uri = ChatSessionUri.forSession(sessionWithProvider.provider.chatSessionType, sessionWithProvider.id); - const existingEditor = findExistingChatEditorByUri(uri, sessionWithProvider.id, this.editorGroupsService); + const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + const existingEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); if (existingEditor) { await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); return; } - if (this.chatWidgetService.getWidgetBySessionId(sessionWithProvider.id)) { + if (this.chatWidgetService.getWidgetBySessionId(session.id)) { return; } - if (element.id === historyNode.id) { + if (session.id === historyNode.id) { // Don't try to open the "Show history..." node itself return; } // Handle history items first - if (element.id.startsWith('history-')) { - - // For local history sessions, use ChatEditorInput approach - if (sessionWithProvider.provider.chatSessionType === 'local') { - const options: IChatEditorOptions = { - target: { sessionId: sessionWithProvider.id }, - pinned: true, - // Add a marker to indicate this session was opened from history - ignoreInView: true, - preserveFocus: true, - }; - await this.editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); - } else { - // For external provider sessions, use ChatSessionUri approach - const providerType = sessionWithProvider.provider.chatSessionType; - const options: IChatEditorOptions = { - pinned: true, - preferredTitle: truncate(element.label, 30), - preserveFocus: true, - }; - await this.editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, sessionWithProvider.id), - options, - }); - } + if (isLocalChatSessionItem(session)) { + const options: IChatEditorOptions = { + target: { sessionId: session.id }, + pinned: true, + ignoreInView: true, + preserveFocus: true, + }; + await this.editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); return; - } - - // Handle local session items (active editors/widgets) - if (this.isLocalChatSessionItem(element)) { - if (element.sessionType === 'editor' && element.editor && element.group) { - // Focus the existing editor - await element.group.openEditor(element.editor, { pinned: true }); - return; - } else if (element.sessionType === 'widget') { - // Focus the chat widget - const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; - if (chatViewPane && element?.widget?.viewModel?.model) { - await chatViewPane.loadSession(element.widget.viewModel.model.sessionId); - } - return; + } else if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { + const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; + if (chatViewPane) { + await chatViewPane.loadSession(session.id); } + return; } - // For other session types, open as a new chat editor - const sessionId = element.id; - const providerType = sessionWithProvider.provider.chatSessionType; - const options: IChatEditorOptions = { pinned: true, ignoreInView: true, - preferredTitle: truncate(element.label, 30), + preferredTitle: truncate(session.label, 30), preserveFocus: true, }; await this.editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, sessionId), + resource: ChatSessionUri.forSession(session.provider.chatSessionType, session.id), options, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index a0ce47939c7..0d83f407ccc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -11,7 +11,6 @@ import { ChatEditorInput } from '../chatEditorInput.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; -import { ILocalChatSessionItem } from '../chatSessions.js'; import { IChatService } from '../../common/chatService.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { IChatModel } from '../../common/chatModel.js'; @@ -74,7 +73,7 @@ export class ChatSessionTracker extends Disposable { } const localEditors = this.getLocalEditorsForSessionType(provider.chatSessionType); - const hybridSessions: (ILocalChatSessionItem & ChatSessionItemWithProvider)[] = []; + const hybridSessions: ChatSessionItemWithProvider[] = []; localEditors.forEach((editor, index) => { const group = this.findGroupForEditor(editor); @@ -99,13 +98,10 @@ export class ChatSessionTracker extends Disposable { } } - const hybridSession: ILocalChatSessionItem & ChatSessionItemWithProvider = { + const hybridSession: ChatSessionItemWithProvider = { id: `${provider.chatSessionType}-local-${index}`, label: editor.getName(), iconPath: Codicon.chatSparkle, - editor, - group, - sessionType: 'editor', status, provider, timing: { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 56c1a5dd389..c3b076c8e10 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -13,6 +13,7 @@ import { ChatEditorInput } from '../chatEditorInput.js'; export type ChatSessionItemWithProvider = IChatSessionItem & { readonly provider: IChatSessionItemProvider; + isHistory?: boolean; relativeTime?: string; relativeTimeFullWord?: string; hideRelativeTime?: boolean; @@ -80,3 +81,7 @@ export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, } return undefined; } + +export function isLocalChatSessionItem(item: ChatSessionItemWithProvider): boolean { + return item.provider.chatSessionType === 'local'; +} From cb76cf01f2b7163fc4ab5b2095fd6c8c1a4f5db2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 Sep 2025 12:56:48 -0700 Subject: [PATCH 0233/4355] edits: don't prompt to auto-save untitled files (#266257) Closes https://github.com/microsoft/vscode-copilot/issues/18637 --- .../chatEditing/chatEditingModifiedDocumentEntry.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 7a031f8bf6a..daebbf892a5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IReference, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../base/common/network.js'; import { ITransaction, autorun, transaction } from '../../../../../base/common/observable.js'; import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -227,7 +228,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie this._rewriteRatioObs.set(1, tx); } }); - if (isLastEdits) { + if (isLastEdits && this._shouldAutoSave()) { await this._textFileService.save(this.modifiedModel.uri, { reason: SaveReason.AUTO, skipSaveParticipants: true, @@ -282,4 +283,8 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie return this._instantiationService.createInstance(ChatEditingCodeEditorIntegration, this, codeEditor, diffInfo, false); } + + private _shouldAutoSave() { + return this.modifiedURI.scheme !== Schemas.untitled; + } } From 6c7e5d013e672912541ac7a9d2b4275d1d201b5b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 11 Sep 2025 16:06:41 -0400 Subject: [PATCH 0234/4355] provide default messages for screen readers (#266238) --- .../contrib/chat/browser/languageModelToolsService.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 75ce1545892..6009018710c 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -322,8 +322,6 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo const prepared = await this.prepareToolInvocation(tool, dto, token); - this.informScreenReader(prepared?.invocationMessage); - toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId); trackedCall.invocation = toolInvocation; const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace); @@ -334,6 +332,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo model.acceptResponseProgress(request, toolInvocation); dto.toolSpecificData = toolInvocation?.toolSpecificData; + this.informScreenReader(prepared?.invocationMessage ?? `${tool.data.displayName} started`); if (prepared?.confirmationMessages) { if (!toolInvocation.isConfirmed?.type && !autoConfirmed) { @@ -413,7 +412,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw err; } finally { toolInvocation?.complete(toolResult); - this.informScreenReader(toolInvocation?.pastTenseMessage); + this.informScreenReader(toolInvocation?.pastTenseMessage ?? `${tool.data.displayName} finished`); if (store) { this.cleanupCallDisposables(requestId, store); } @@ -446,10 +445,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return prepared; } - private informScreenReader(msg: string | IMarkdownString | undefined): void { - if (msg) { - alert(typeof msg === 'string' ? msg : msg.value); - } + private informScreenReader(msg: string | IMarkdownString): void { + alert(typeof msg === 'string' ? msg : msg.value); } private playAccessibilitySignal(toolInvocations: ChatToolInvocation[]): void { From 8fcd9147be876d78c3ec5c747eba2a48d52a3502 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 11 Sep 2025 22:40:47 +0200 Subject: [PATCH 0235/4355] adopt promptFileReference.test --- .../contrib/chat/browser/chatWidget.ts | 4 + .../languageProviders/promptLinkProvider.ts | 8 +- .../promptSyntax/promptFileReference.test.ts | 238 ++++++++---------- .../promptSyntax/testUtils/mockFilesystem.ts | 4 +- 4 files changed, 109 insertions(+), 145 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 9070071c397..eba8f37399a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2532,6 +2532,10 @@ export class ChatWidget extends Disposable implements IChatWidget { const currentMode = this.input.currentModeObs.get(); + if (tools !== undefined && !mode && currentMode.kind !== ChatModeKind.Agent) { + mode = ChatModeKind.Agent; + } + // switch to appropriate chat mode if needed if (mode && mode !== currentMode.name) { // Find the mode object to get its kind diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts index 53e408c38dc..094e4619f21 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts @@ -33,9 +33,11 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { } const links: ILink[] = []; for (const ref of parser.body.fileReferences) { - const url = parser.body.resolveFilePath(ref.content); - if (url) { - links.push({ range: ref.range, url }); + if (!ref.isMarkdownLink) { + const url = parser.body.resolveFilePath(ref.content); + if (url) { + links.push({ range: ref.range, url }); + } } } return { links }; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts index c1cfdd00f0b..928bcfc6f81 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts @@ -4,11 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { timeout } from '../../../../../../base/common/async.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { Schemas } from '../../../../../../base/common/network.js'; import { URI } from '../../../../../../base/common/uri.js'; -import { randomBoolean } from '../../../../../../base/test/common/testUtils.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { Range } from '../../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; @@ -23,14 +20,10 @@ import { TestInstantiationService } from '../../../../../../platform/instantiati import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js'; import { NullPolicyService } from '../../../../../../platform/policy/common/policy.js'; import { ChatModeKind } from '../../../common/constants.js'; -import { MarkdownLink } from '../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; -import { FileReference } from '../../../common/promptSyntax/codecs/tokens/fileReference.js'; import { getPromptFileType } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; -import { type TErrorCondition } from '../../../common/promptSyntax/parsers/basePromptParser.js'; -import { FilePromptParser } from '../../../common/promptSyntax/parsers/filePromptParser.js'; -import { type TPromptReference } from '../../../common/promptSyntax/parsers/types.js'; import { IMockFolder, MockFilesystem } from './testUtils/mockFilesystem.js'; +import { IBodyFileReference, NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js'; /** * Represents a file reference with an expected @@ -44,19 +37,18 @@ class ExpectedReference { constructor( dirname: URI, - public readonly linkToken: FileReference | MarkdownLink, - public readonly errorCondition?: TErrorCondition, + public readonly ref: IBodyFileReference, ) { - this.uri = (linkToken.path.startsWith('/')) - ? URI.file(linkToken.path) - : URI.joinPath(dirname, linkToken.path); + this.uri = (ref.content.startsWith('/')) + ? URI.file(ref.content) + : URI.joinPath(dirname, ref.content); } /** * Range of the underlying file reference token. */ public get range(): Range { - return this.linkToken.range; + return this.ref.range; } /** @@ -67,6 +59,10 @@ class ExpectedReference { } } +function toUri(filePath: string): URI { + return URI.parse('testFs://' + filePath); +} + /** * A reusable test utility to test the `PromptFileReference` class. */ @@ -82,80 +78,32 @@ class TestPromptFileReference extends Disposable { // create in-memory file system const fileSystemProvider = this._register(new InMemoryFileSystemProvider()); - this._register(this.fileService.registerProvider(Schemas.file, fileSystemProvider)); + this._register(this.fileService.registerProvider('testFs', fileSystemProvider)); } /** * Run the test. */ - public async run( - ): Promise { + public async run(): Promise { // create the files structure on the disk - await (this.instantiationService.createInstance(MockFilesystem, this.fileStructure)).mock(); + await (this.instantiationService.createInstance(MockFilesystem, this.fileStructure)).mock(toUri('/')); - // randomly test with and without delay to ensure that the file - // reference resolution is not susceptible to race conditions - if (randomBoolean()) { - await timeout(5); - } + const content = await this.fileService.readFile(this.rootFileUri); - // start resolving references for the specified root file - const rootReference = this._register( - this.instantiationService.createInstance( - FilePromptParser, - this.rootFileUri, - { allowNonPromptFiles: true, languageId: undefined, updateOnChange: true }, - ), - ).start(); - - // wait until entire prompts tree is resolved - await rootReference.settled(); + const ast = new NewPromptsParser().parse(this.rootFileUri, content.value.toString()); + assert(ast.body, 'Prompt file must have a body'); // resolve the root file reference including all nested references - const resolvedReferences: readonly (TPromptReference | undefined)[] = rootReference.references; + const resolvedReferences = ast.body.fileReferences ?? []; for (let i = 0; i < this.expectedReferences.length; i++) { const expectedReference = this.expectedReferences[i]; const resolvedReference = resolvedReferences[i]; - if (expectedReference.linkToken instanceof MarkdownLink) { - assert( - resolvedReference?.subtype === 'markdown', - [ - `Expected ${i}th resolved reference to be a markdown link`, - `got '${resolvedReference}'.`, - ].join(', '), - ); - } - - if (expectedReference.linkToken instanceof FileReference) { - assert( - resolvedReference?.subtype === 'prompt', - [ - `Expected ${i}th resolved reference to be a #file: link`, - `got '${resolvedReference}'.`, - ].join(', '), - ); - } - - assert( - (resolvedReference) && - (resolvedReference.uri.toString() === expectedReference.uri.toString()), - [ - `Expected ${i}th resolved reference URI to be '${expectedReference.uri}'`, - `got '${resolvedReference?.uri}'.`, - ].join(', '), - ); - - assert( - (resolvedReference) && - (resolvedReference.range.equalsRange(expectedReference.range)), - [ - `Expected ${i}th resolved reference range to be '${expectedReference.range}'`, - `got '${resolvedReference?.range}'.`, - ].join(', '), - ); + const resolvedUri = ast.body.resolveFilePath(resolvedReference.content); + assert.equal(resolvedUri?.fsPath, expectedReference.uri.fsPath); + assert.deepStrictEqual(resolvedReference.range, expectedReference.range); } assert.strictEqual( @@ -167,7 +115,16 @@ class TestPromptFileReference extends Disposable { ].join('\n'), ); - return rootReference; + const result: any = {}; + result.promptType = getPromptFileType(this.rootFileUri); + if (ast.header) { + for (const key of ['tools', 'model', 'mode', 'applyTo', 'description'] as const) { + if (ast.header[key]) { + result[key] = ast.header[key]; + } + } + } + return result; } } @@ -180,19 +137,34 @@ class TestPromptFileReference extends Disposable { * @param lineNumber The expected line number of the file reference. * @param startColumnNumber The expected start column number of the file reference. */ -function createTestFileReference( - filePath: string, - lineNumber: number, - startColumnNumber: number, -): FileReference { +function createFileReference(filePath: string, lineNumber: number, startColumnNumber: number): IBodyFileReference { const range = new Range( lineNumber, - startColumnNumber, + startColumnNumber + '#file:'.length, lineNumber, - startColumnNumber + `#file:${filePath}`.length, + startColumnNumber + '#file:'.length + filePath.length, ); - return new FileReference(range, filePath); + return { + range, + content: filePath, + isMarkdownLink: false, + }; +} + +function createMarkdownReference(lineNumber: number, startColumnNumber: number, firstSeg: string, secondSeg: string): IBodyFileReference { + const range = new Range( + lineNumber, + startColumnNumber + firstSeg.length + 1, + lineNumber, + startColumnNumber + firstSeg.length + secondSeg.length - 1, + ); + + return { + range, + content: secondSeg.substring(1, secondSeg.length - 1), + isMarkdownLink: true, + }; } suite('PromptFileReference', function () { @@ -225,7 +197,7 @@ suite('PromptFileReference', function () { test('resolves nested file references', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -282,18 +254,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 2, 14), + createFileReference('folder1/file3.prompt.md', 2, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 3, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -309,7 +281,7 @@ suite('PromptFileReference', function () { test('tools', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -405,18 +377,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 7, 14), + createFileReference('folder1/file3.prompt.md', 7, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 8, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -424,9 +396,7 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, @@ -445,7 +415,7 @@ suite('PromptFileReference', function () { test('prompt language', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -513,18 +483,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 7, 14), + createFileReference('folder1/file3.prompt.md', 7, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 8, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -532,17 +502,15 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, { promptType: PromptsType.prompt, - mode: ChatModeKind.Agent, description: 'Description of my prompt.', tools: ['my-tool12'], + applyTo: '**/*', }, 'Must have correct metadata.', ); @@ -553,7 +521,7 @@ suite('PromptFileReference', function () { test('instructions language', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -621,18 +589,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.instructions.md`), + toUri(`/${rootFolderName}/file2.instructions.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 7, 14), + createFileReference('folder1/file3.prompt.md', 7, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 8, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -640,9 +608,7 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, @@ -650,6 +616,7 @@ suite('PromptFileReference', function () { promptType: PromptsType.instructions, applyTo: '**/*', description: 'Description of my instructions file.', + tools: ['my-tool12'], }, 'Must have correct metadata.', ); @@ -657,10 +624,10 @@ suite('PromptFileReference', function () { }); suite('tools and mode compatibility', () => { - test('tools are ignored if root prompt is in the ask mode', async function () { + test('ask mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -728,18 +695,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 6, 14), + createFileReference('folder1/file3.prompt.md', 6, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 7, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -747,9 +714,7 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, @@ -762,10 +727,10 @@ suite('PromptFileReference', function () { ); }); - test('tools are ignored if root prompt is in the edit mode', async function () { + test('edit mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -832,18 +797,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 6, 14), + createFileReference('folder1/file3.prompt.md', 6, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 7, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -851,9 +816,7 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, @@ -867,10 +830,10 @@ suite('PromptFileReference', function () { }); - test('tools are not ignored if root prompt is in the agent mode', async function () { + test('agent mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -937,18 +900,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 6, 14), + createFileReference('folder1/file3.prompt.md', 6, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 7, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -956,9 +919,7 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, @@ -972,10 +933,10 @@ suite('PromptFileReference', function () { }); - test('tools are not ignored if root prompt implicitly in the agent mode', async function () { + test('no mode', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; - const rootUri = URI.file(rootFolder); + const rootUri = toUri(rootFolder); const test = testDisposables.add(instantiationService.createInstance(TestPromptFileReference, /** @@ -1042,18 +1003,18 @@ suite('PromptFileReference', function () { /** * The root file path to start the resolve process from. */ - URI.file(`/${rootFolderName}/file2.prompt.md`), + toUri(`/${rootFolderName}/file2.prompt.md`), /** * The expected references to be resolved. */ [ new ExpectedReference( rootUri, - createTestFileReference('folder1/file3.prompt.md', 6, 14), + createFileReference('folder1/file3.prompt.md', 6, 14), ), new ExpectedReference( rootUri, - new MarkdownLink( + createMarkdownReference( 7, 14, '[file4.prompt.md]', '(./folder1/some-other-folder/file4.prompt.md)', ), @@ -1061,15 +1022,12 @@ suite('PromptFileReference', function () { ] )); - const rootReference = await test.run(); - - const { metadata, } = rootReference; + const metadata = await test.run(); assert.deepStrictEqual( metadata, { promptType: PromptsType.prompt, - mode: ChatModeKind.Agent, tools: ['my-tool12'], description: 'Description of the prompt file.', }, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts index dc0a44da5c7..d08b9685ae8 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts @@ -47,11 +47,11 @@ export class MockFilesystem { /** * Starts the mock process. */ - public async mock(): Promise[]> { + public async mock(parentFolder?: URI): Promise[]> { const result = await Promise.all( this.folders .map((folder) => { - return this.mockFolder(folder); + return this.mockFolder(folder, parentFolder); }), ); From 8d5775af916f964776a42f204e0084d2331ce964 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 11 Sep 2025 14:56:28 -0700 Subject: [PATCH 0236/4355] Shorten prompt descriptions for welcome view --- .github/prompts/implement.prompt.md | 2 +- .github/prompts/plan.prompt.md | 2 +- .vscode/settings.json | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/implement.prompt.md index 4766b33436f..adae22dd715 100644 --- a/.github/prompts/implement.prompt.md +++ b/.github/prompts/implement.prompt.md @@ -1,6 +1,6 @@ --- mode: agent -description: 'Implement the solution for a problem.' +description: 'Implement the plan' tools: ['edit', 'notebooks', 'search', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests'] --- Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally. diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index a0822ae4f3c..8b0c17c387b 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,6 +1,6 @@ --- mode: agent -description: 'Plan the solution for a problem.' +description: 'Start planning' tools: ['getNotebookSummary', 'readNotebookCellOutput', 'search', 'getTerminalOutput', 'terminalSelection', 'terminalLastCommand', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos', 'get_issue', 'get_issue_comments', 'get_me'] --- Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: diff --git a/.vscode/settings.json b/.vscode/settings.json index e003424c2c9..3158631f44b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -222,7 +222,6 @@ "chat.emptyState.history.enabled": true, "github.copilot.chat.advanced.taskTools.enabled": true, "chat.promptFilesRecommendations": { - "plan": true, - "implement": true + "plan": true } } From 5e4fa79ff85fc68505741167d3e062b07f0ce089 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 11 Sep 2025 18:17:31 -0400 Subject: [PATCH 0237/4355] address API feedback for terminal completion provider (#266073) --- .../src/terminalSuggestMain.ts | 27 ++++-- .../api/browser/mainThreadTerminalService.ts | 28 ++++-- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostTerminalService.ts | 16 ++-- .../api/common/extHostTypeConverters.ts | 1 + .../browser/terminalCompletionService.ts | 22 +++-- .../browser/terminalCompletionService.test.ts | 6 -- ...e.proposed.terminalCompletionProvider.d.ts | 86 +++++++++++++++---- 9 files changed, 137 insertions(+), 57 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index f105a135e29..cc5247870d8 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -250,8 +250,7 @@ export async function activate(context: vscode.ExtensionContext) { const machineId = await vscode.env.machineId; const remoteAuthority = vscode.env.remoteName; - context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({ - id: 'terminal-suggest', + context.subscriptions.push(vscode.window.registerTerminalCompletionProvider('terminal-suggest', { async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: vscode.TerminalCompletionContext, token: vscode.CancellationToken): Promise { currentTerminalEnv = terminal.shellIntegration?.env?.value ?? process.env; if (token.isCancellationRequested) { @@ -305,14 +304,14 @@ export async function activate(context: vscode.ExtensionContext) { } } - - if (terminal.shellIntegration?.cwd && (result.filesRequested || result.foldersRequested)) { + const cwd = result.cwd ?? terminal.shellIntegration?.cwd; + if (cwd && (result.filesRequested || result.foldersRequested)) { + const globPattern = createFileRegex(result.fileExtensions); return new vscode.TerminalCompletionList(result.items, { filesRequested: result.filesRequested, foldersRequested: result.foldersRequested, - fileExtensions: result.fileExtensions, - cwd: result.cwd ?? terminal.shellIntegration.cwd, - env: terminal.shellIntegration?.env?.value, + globPattern, + cwd, }); } return result.items; @@ -565,3 +564,17 @@ export function sanitizeProcessEnvironment(env: Record, ...prese }); } + +// Escapes regex special characters in a string +function escapeRegExp(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function createFileRegex(fileExtensions?: string[]): vscode.GlobPattern | undefined { + if (!fileExtensions || fileExtensions.length === 0) { + return undefined; + } + const exts = fileExtensions.map(ext => ext.startsWith('.') ? ext : '.' + ext); + // Create a regex that matches any string ending with one of the extensions + return `.*(${exts.map(ext => escapeRegExp(ext)).join('|')})$`; +} diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index f82ac2d5fe1..64afd9475cf 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -279,13 +279,27 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape id, provideCompletions: async (commandLine, cursorPosition, allowFallbackCompletions, token) => { const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorPosition, allowFallbackCompletions }, token); - return { - items: completions?.items.map(c => ({ - provider: `ext:${id}`, - ...c, - })), - resourceRequestConfig: completions?.resourceRequestConfig - }; + if (!completions) { + return undefined; + } + if (completions.resourceRequestConfig) { + const { cwd, globPattern, ...rest } = completions.resourceRequestConfig; + return { + items: completions.items?.map(c => ({ + provider: `ext:${id}`, + ...c, + })), + resourceRequestConfig: { + ...rest, + cwd, + globPattern + } + }; + } + return completions.items?.map(c => ({ + provider: `ext:${id}`, + ...c, + })); } }, ...triggerCharacters)); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 65587b00b7a..894bd1b233a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -876,9 +876,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerTerminalProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable { return extHostTerminalService.registerProfileProvider(extension, id, provider); }, - registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { + registerTerminalCompletionProvider(id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { checkProposedApiEnabled(extension, 'terminalCompletionProvider'); - return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters); + return extHostTerminalService.registerTerminalCompletionProvider(extension, id, provider, ...triggerCharacters); }, registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable { checkProposedApiEnabled(extension, 'terminalQuickFixProvider'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0c4bf617744..72cc69a0896 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2527,8 +2527,8 @@ export class TerminalCompletionListDto, ...triggerCharacters: string[]): vscode.Disposable; + registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable; } interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableCollection { @@ -757,15 +757,15 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } - public registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { - if (this._completionProviders.has(provider.id)) { - throw new Error(`Terminal completion provider "${provider.id}" already registered`); + public registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { + if (this._completionProviders.has(id)) { + throw new Error(`Terminal completion provider "${id}" already registered`); } - this._completionProviders.set(provider.id, provider); - this._proxy.$registerCompletionProvider(provider.id, extension.identifier.value, ...triggerCharacters); + this._completionProviders.set(id, provider); + this._proxy.$registerCompletionProvider(id, extension.identifier.value, ...triggerCharacters); return new VSCodeDisposable(() => { - this._completionProviders.delete(provider.id); - this._proxy.$unregisterCompletionProvider(provider.id); + this._completionProviders.delete(id); + this._proxy.$unregisterCompletionProvider(id); }); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4255ae1ba0f..19b21ebd925 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3407,6 +3407,7 @@ export namespace TerminalResourceRequestConfig { ...resourceRequestConfig, pathSeparator, cwd: resourceRequestConfig.cwd, + globPattern: GlobPattern.from(resourceRequestConfig.globPattern) ?? undefined }; } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 97396cb6ed4..4dd889411c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -21,6 +21,7 @@ import { timeout } from '../../../../../base/common/async.js'; import { gitBashToWindowsPath, windowsToGitBashPath } from './terminalGitBashHelpers.js'; import { isEqual } from '../../../../../base/common/resources.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { IRelativePattern, match } from '../../../../../base/common/glob.js'; export const ITerminalCompletionService = createDecorator('terminalCompletionService'); @@ -55,10 +56,9 @@ export class TerminalCompletionList { export interface TerminalResourceRequestConfig { filesRequested?: boolean; foldersRequested?: boolean; - fileExtensions?: string[]; - cwd?: UriComponents; + globPattern?: string | IRelativePattern; + cwd: UriComponents; pathSeparator: string; - env?: { [key: string]: string | null | undefined }; } @@ -258,10 +258,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // provide diagnostics when a folder is provided where a file is expected. const foldersRequested = (resourceRequestConfig.foldersRequested || resourceRequestConfig.filesRequested) ?? false; const filesRequested = resourceRequestConfig.filesRequested ?? false; - const fileExtensions = resourceRequestConfig.fileExtensions ?? undefined; + const globPattern = resourceRequestConfig.globPattern ?? undefined; - const cwd = URI.revive(resourceRequestConfig.cwd); - if (!cwd || (!foldersRequested && !filesRequested)) { + if (!foldersRequested && !filesRequested) { return; } @@ -307,6 +306,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo const lastWordFolderHasTildePrefix = !!lastWordFolder.match(/^~[\\\/]?/); const isAbsolutePath = getIsAbsolutePath(shellType, resourceRequestConfig.pathSeparator, lastWordFolder, useWindowsStylePath); const type = lastWordFolderHasTildePrefix ? 'tilde' : isAbsolutePath ? 'absolute' : 'relative'; + const cwd = URI.revive(resourceRequestConfig.cwd); + switch (type) { case 'tilde': { const home = this._getHomeDir(useWindowsStylePath, capabilities); @@ -442,9 +443,12 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label = escapeTerminalCompletionLabel(label, shellType, resourceRequestConfig.pathSeparator); - if (child.isFile && fileExtensions) { - const extension = child.name.split('.').length > 1 ? child.name.split('.').at(-1) : undefined; - if (extension && !fileExtensions.includes(extension)) { + if (child.isFile && globPattern) { + const filePath = child.resource.fsPath; + const matches = typeof globPattern === 'string' + ? new RegExp(globPattern).test(filePath) + : match(globPattern, filePath); + if (!matches) { return; } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index b8e5eff0c50..5094ba0acae 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -147,12 +147,6 @@ suite('TerminalCompletionService', () => { }); suite('resolveResources should return undefined', () => { - test('if cwd is not provided', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); - assert(!result); - }); - test('if neither filesRequested nor foldersRequested are true', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///test'), diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 5824afff3d0..1a0196f683c 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -7,10 +7,22 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/226562 + /** + * A provider that supplies terminal completion items. + * + * Implementations of this interface should return an array of {@link TerminalCompletionItem} or a + * {@link TerminalCompletionList} describing completions for the current command line. + * + * @example Simple provider returning a single completion + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return [{ label: '--help', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 }]; + * } + * }); + */ export interface TerminalCompletionProvider { - id: string; /** - * Provide completions for the given position and document. + * Provide completions for the given terminal and context. * @param terminal The terminal for which completions are being provided. * @param context Information about the terminal's current state. * @param token A cancellation token. @@ -20,6 +32,21 @@ declare module 'vscode' { } + /** + * Represents a completion suggestion for a terminal command line. + * + * @example Completion item for `ls -|` + * const item = { + * label: '-A', + * replacementIndex: 3, + * replacementLength: 1, + * detail: 'List all entries except for . and .. (always set for the super-user)', + * kind: TerminalCompletionItemKind.Flag + * }; + * + * The fields on a completion item describe what text should be shown to the user + * and which portion of the command line should be replaced when the item is accepted. + */ export interface TerminalCompletionItem { /** * The label of the completion. @@ -41,7 +68,6 @@ declare module 'vscode' { */ detail?: string; - /** * A human-readable string that represents a doc-comment. */ @@ -55,7 +81,11 @@ declare module 'vscode' { /** - * Terminal item kinds. + * The kind of an individual terminal completion item. + * + * The kind is used to render an appropriate icon in the suggest list and to convey the semantic + * meaning of the suggestion (file, folder, flag, commit, branch, etc.). + * */ export enum TerminalCompletionItemKind { File = 0, @@ -77,6 +107,13 @@ declare module 'vscode' { PullRequestDone = 16, } + + /** + * Context information passed to {@link TerminalCompletionProvider.provideTerminalCompletions}. + * + * It contains the full command line, the current cursor position, and a flag indicating whether + * fallback completions are allowed when the exact completion type cannot be determined. + */ export interface TerminalCompletionContext { /** * The complete terminal command line. @@ -95,17 +132,31 @@ declare module 'vscode' { export namespace window { /** - * Register a completion provider for a certain type of terminal. - * + * Register a completion provider for terminals. + * @param id The unique identifier of the terminal provider, used as a settings key and shown in the information hover of the suggest widget. * @param provider The completion provider. * @returns A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; + * + * @example Register a provider for an extension + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return new TerminalCompletionList([ + * { label: '--version', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 } + * ]); + * } + * }); + */ + export function registerTerminalCompletionProvider(id: string, provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; } /** * Represents a collection of {@link TerminalCompletionItem completion items} to be presented * in the terminal. + * + * @example Create a completion list that requests files for the terminal cwd + * const list = new TerminalCompletionList([ + * { label: 'ls', replacementIndex: 0, replacementLength: 0, kind: TerminalCompletionItemKind.Method } + * ], { filesRequested: true, cwd: Uri.file('/home/user') }); */ export class TerminalCompletionList { @@ -128,6 +179,13 @@ declare module 'vscode' { constructor(items?: T[], resourceRequestConfig?: TerminalResourceRequestConfig); } + + /** + * Configuration for requesting file and folder resources to be shown as completions. + * + * When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and + * folders that match {@link globPattern} from the provided {@link cwd}. + */ export interface TerminalResourceRequestConfig { /** * Show files as completion items. @@ -138,16 +196,12 @@ declare module 'vscode' { */ foldersRequested?: boolean; /** - * File extensions to filter by. - */ - fileExtensions?: string[]; - /** - * If no cwd is provided, no resources will be shown as completions. + * A {@link GlobPattern glob pattern} that controls which files suggest should surface. */ - cwd?: Uri; + globPattern?: GlobPattern; /** - * Environment variables to use when constructing paths. + * The cwd from which to request resources. */ - env?: { [key: string]: string | null | undefined }; + cwd: Uri; } } From a5cf5d14c783a18efe939686f3ee34241e7636a5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 11 Sep 2025 18:19:15 -0400 Subject: [PATCH 0238/4355] extract `_createElicitationPart` to simplify `outputMonitor` functions (#266231) --- .../browser/tools/monitoring/outputMonitor.ts | 262 ++++++++++-------- 1 file changed, 142 insertions(+), 120 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index d6df6c071eb..820d650fcb1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -167,7 +167,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if (confirmationPrompt?.detectedRequestForFreeFormInput) { this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; - const focusedTerminal = await this._focusTerminalForUserInput(token, this._execution, confirmationPrompt); + const focusedTerminal = await this._requestFreeFormTerminalInput(token, this._execution, confirmationPrompt); if (focusedTerminal) { await new Promise(resolve => { const disposable = this._execution.instance.onData(data => { @@ -194,7 +194,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { // Continue polling as we sent the input return { shouldContinuePollling: true }; } - const confirmed = await this._confirmRunInTerminal(suggestedOptionResult?.suggestedOption ?? confirmationPrompt.options[0], this._execution, confirmationPrompt); + const confirmed = await this._confirmRunInTerminal(token, suggestedOptionResult?.suggestedOption ?? confirmationPrompt.options[0], this._execution, confirmationPrompt); if (confirmed) { // Continue polling as we sent the input return { shouldContinuePollling: true }; @@ -342,43 +342,23 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if (token.isCancellationRequested || this._state === OutputMonitorState.Cancelled) { return { promise: Promise.resolve(false) }; } - const chatModel = this._chatService.getSession(context.sessionId); - if (chatModel instanceof ChatModel) { - const request = chatModel.getRequests().at(-1); - if (request) { - let part: ChatElicitationRequestPart | undefined = undefined; - const promise = new Promise(resolve => { - const thePart = part = this._register(new ChatElicitationRequestPart( - new MarkdownString(localize('poll.terminal.waiting', "Continue waiting for `{0}`?", command)), - new MarkdownString(localize('poll.terminal.polling', "This will continue to poll for output to determine when the terminal becomes idle for up to 2 minutes.")), - '', - localize('poll.terminal.accept', 'Yes'), - localize('poll.terminal.reject', 'No'), - async () => { - thePart.state = 'accepted'; - thePart.hide(); - thePart.dispose(); - this._promptPart = undefined; - resolve(true); - }, - async () => { - thePart.state = 'rejected'; - thePart.hide(); - this._state = OutputMonitorState.Cancelled; - this._promptPart = undefined; - resolve(false); - } - )); - chatModel.acceptResponseProgress(request, thePart); - this._promptPart = thePart; - }); + const result = this._createElicitationPart( + token, + context.sessionId, + new MarkdownString(localize('poll.terminal.waiting', "Continue waiting for `{0}`?", command)), + new MarkdownString(localize('poll.terminal.polling', "This will continue to poll for output to determine when the terminal becomes idle for up to 2 minutes.")), + '', + localize('poll.terminal.accept', 'Yes'), + localize('poll.terminal.reject', 'No'), + async () => true, + async () => { this._state = OutputMonitorState.Cancelled; return false; } + ); - return { promise, part }; - } - } - return { promise: Promise.resolve(false) }; + return { promise: result.promise.then(p => p ?? false), part: result.part }; } + + private async _assessOutputForErrors(buffer: string, token: CancellationToken): Promise { const models = await this._languageModelsService.selectLanguageModels({ vendor: 'copilot', family: 'gpt-4o-mini' }); if (!models.length) { @@ -524,121 +504,163 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return description ? { suggestedOption: { description, option: validOption }, sentToTerminal } : { suggestedOption: validOption, sentToTerminal }; } - private async _focusTerminalForUserInput(token: CancellationToken, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { - const chatModel = this._chatService.getSession(execution.sessionId); - if (!(chatModel instanceof ChatModel)) { - return false; - } - const request = chatModel.getRequests().at(-1); - if (!request) { - return false; - } - const userPrompt = new Promise(resolve => { - const thePart = this._register(new ChatElicitationRequestPart( - new MarkdownString(localize('poll.terminal.inputRequest', "The terminal is awaiting input.")), - new MarkdownString(localize('poll.terminal.requireInput', "{0}\nPlease provide the required input to the terminal.\n\n", confirmationPrompt.prompt)), - '', - localize('poll.terminal.enterInput', 'Focus terminal'), - undefined, - async () => { - thePart.state = 'accepted'; - thePart.hide(); - thePart.dispose(); - execution.instance.focus(true); - resolve(true); - }, - undefined, - undefined - )); - this._register(autorun(reader => { - if (thePart.isHidden?.read(reader)) { - this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; - } - })); - this._register(token.onCancellationRequested(() => { - thePart.hide(); - thePart.dispose(); - resolve(false); - })); + private async _requestFreeFormTerminalInput(token: CancellationToken, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { + const { promise: userPrompt, part } = this._createElicitationPart( + token, + execution.sessionId, + new MarkdownString(localize('poll.terminal.inputRequest', "The terminal is awaiting input.")), + new MarkdownString(localize('poll.terminal.requireInput', "{0}\nPlease provide the required input to the terminal.\n\n", confirmationPrompt.prompt)), + '', + localize('poll.terminal.enterInput', 'Focus terminal'), + undefined, + async () => { execution.instance.focus(true); return true; }, + ); + + this._register(autorun(reader => { + if (part.isHidden?.read(reader)) { + this._outputMonitorTelemetryCounters.inputToolFreeFormInputShownCount++; + } + })); + + const inputPromise = new Promise(resolve => { const inputDataDisposable = this._register(execution.instance.onDidInputData((data) => { if (!data || data === '\r' || data === '\n' || data === '\r\n') { - thePart.hide(); - thePart.dispose(); + part.hide(); + part.dispose(); inputDataDisposable.dispose(); this._state = OutputMonitorState.PollingForIdle; resolve(true); } })); - chatModel.acceptResponseProgress(request, thePart); }); - return await userPrompt; + + const result = await Promise.race([userPrompt, inputPromise]); + return !!result; + } + + private async _confirmRunInTerminal(token: CancellationToken, suggestedOption: SuggestedOption, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { + const suggestedOptionValue = typeof suggestedOption === 'string' ? suggestedOption : suggestedOption.option; + let inputDataDisposable = Disposable.None; + const { promise: userPrompt, part } = this._createElicitationPart( + token, + execution.sessionId, + new MarkdownString(localize('poll.terminal.confirmRequired', "The terminal is awaiting input.")), + new MarkdownString(localize('poll.terminal.confirmRunDetail', "{0}\n Do you want to send `{1}`{2} followed by `Enter` to the terminal?", confirmationPrompt.prompt, suggestedOptionValue, typeof suggestedOption === 'string' ? '' : suggestedOption.description ? ' (' + suggestedOption.description + ')' : '')), + '', + localize('poll.terminal.acceptRun', 'Allow'), + localize('poll.terminal.rejectRun', 'Focus Terminal'), + async (value: IAction | true) => { + let option: string | undefined = undefined; + if (value === true) { + option = suggestedOptionValue; + } else if (typeof value === 'object' && 'label' in value) { + option = value.label.split(' (')[0]; + } + this._outputMonitorTelemetryCounters.inputToolManualAcceptCount++; + this._outputMonitorTelemetryCounters.inputToolManualChars += option?.length || 0; + return option; + }, + async () => { + this._state = OutputMonitorState.Cancelled; + this._outputMonitorTelemetryCounters.inputToolManualRejectCount++; + inputDataDisposable.dispose(); + return undefined; + }, + getMoreActions(suggestedOption, confirmationPrompt) + ); + + this._register(autorun(reader => { + if (part.isHidden?.read(reader)) { + this._outputMonitorTelemetryCounters.inputToolManualShownCount++; + } + })); + const inputPromise = new Promise(resolve => { + inputDataDisposable = this._register(execution.instance.onDidInputData(() => { + part.hide(); + part.dispose(); + inputDataDisposable.dispose(); + this._state = OutputMonitorState.PollingForIdle; + resolve(undefined); + })); + }); + + const optionToRun = await Promise.race([userPrompt, inputPromise]); + if (optionToRun) { + await execution.instance.sendText(optionToRun, true); + } + return optionToRun; } - private async _confirmRunInTerminal(suggestedOption: SuggestedOption, execution: IExecution, confirmationPrompt: IConfirmationPrompt): Promise { - const chatModel = this._chatService.getSession(execution.sessionId); + // Helper to create, register, and wire a ChatElicitationRequestPart. Returns the promise that + // resolves when the part is accepted/rejected and the registered part itself so callers can + // attach additional listeners (e.g., onDidRequestHide) or compose with other promises. + private _createElicitationPart( + token: CancellationToken, + sessionId: string, + title: MarkdownString, + detail: MarkdownString, + subtitle: string, + acceptLabel: string, + rejectLabel?: string, + onAccept?: (value: IAction | true) => Promise | T | undefined, + onReject?: () => Promise | T | undefined, + moreActions?: IAction[] | undefined + ): { promise: Promise; part: ChatElicitationRequestPart } { + const chatModel = this._chatService.getSession(sessionId); if (!(chatModel instanceof ChatModel)) { - return undefined; + throw new Error('No model'); } const request = chatModel.getRequests().at(-1); if (!request) { - return undefined; + throw new Error('No request'); } - const suggestedOptionValue = typeof suggestedOption === 'string' ? suggestedOption : suggestedOption.option; - const userPrompt = new Promise(resolve => { - const thePart = this._register(new ChatElicitationRequestPart( - new MarkdownString(localize('poll.terminal.confirmRequired', "The terminal is awaiting input.")), - new MarkdownString(localize('poll.terminal.confirmRunDetail', "{0}\n Do you want to send `{1}`{2} followed by `Enter` to the terminal?", confirmationPrompt.prompt, suggestedOptionValue, typeof suggestedOption === 'string' ? '' : suggestedOption.description ? ' (' + suggestedOption.description + ')' : '')), - '', - localize('poll.terminal.acceptRun', 'Allow'), - localize('poll.terminal.rejectRun', 'Focus Terminal'), + let part!: ChatElicitationRequestPart; + const promise = new Promise(resolve => { + const thePart = part = this._register(new ChatElicitationRequestPart( + title, + detail, + subtitle, + acceptLabel, + rejectLabel, async (value: IAction | true) => { thePart.state = 'accepted'; thePart.hide(); thePart.dispose(); - let option: string | undefined = undefined; - if (value === true) { - // Primary option accepted - option = suggestedOptionValue; - } else if (typeof value === 'object' && 'label' in value) { - // Remove description - option = value.label.split(' (')[0]; + this._promptPart = undefined; + try { + const r = await (onAccept ? onAccept(value) : undefined); + resolve(r as T | undefined); + } catch { + resolve(undefined); } - this._outputMonitorTelemetryCounters.inputToolManualAcceptCount++; - this._outputMonitorTelemetryCounters.inputToolManualChars += option?.length || 0; - resolve(option); }, async () => { thePart.state = 'rejected'; thePart.hide(); - this._state = OutputMonitorState.Cancelled; - this._outputMonitorTelemetryCounters.inputToolManualRejectCount++; - inputDataDisposable.dispose(); - resolve(undefined); + thePart.dispose(); + this._promptPart = undefined; + try { + const r = await (onReject ? onReject() : undefined); + resolve(r as T | undefined); + } catch { + resolve(undefined); + } }, undefined, - getMoreActions(suggestedOption, confirmationPrompt) + moreActions )); - this._register(autorun(reader => { - if (thePart.isHidden?.read(reader)) { - this._outputMonitorTelemetryCounters.inputToolManualShownCount++; - } - })); - const inputDataDisposable = this._register(execution.instance.onDidInputData(() => { - thePart.hide(); - thePart.dispose(); - inputDataDisposable.dispose(); - this._state = OutputMonitorState.PollingForIdle; - resolve(undefined); - })); chatModel.acceptResponseProgress(request, thePart); + this._promptPart = thePart; }); - const optionToRun = await userPrompt; - if (optionToRun) { - await execution.instance.sendText(optionToRun, true); - } - return optionToRun; + this._register(token.onCancellationRequested(() => { + part.hide(); + part.dispose(); + })); + + return { promise, part }; } + } function getMoreActions(suggestedOption: SuggestedOption, confirmationPrompt: IConfirmationPrompt): IAction[] | undefined { From 867ea4049c2bd567f88313512c666258d4363235 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:28:30 -0400 Subject: [PATCH 0239/4355] Use mouse instead of element for Quick Pick hovers (#266276) Fixes https://github.com/microsoft/vscode/issues/263442 Doesn't seem to regress https://github.com/microsoft/vscode/issues/206039 --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 612316858c0..95cdb64b98b 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1340,7 +1340,7 @@ export class QuickInputHoverDelegate extends WorkbenchHoverDelegate { @IConfigurationService configurationService: IConfigurationService, @IHoverService hoverService: IHoverService ) { - super('element', undefined, (options) => this.getOverrideOptions(options), configurationService, hoverService); + super('mouse', undefined, (options) => this.getOverrideOptions(options), configurationService, hoverService); } private getOverrideOptions(options: IHoverDelegateOptions): Partial { From e0480ba89516f6078052305ec8948b8038ea7c83 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:17:06 -0700 Subject: [PATCH 0240/4355] Refactor: Splitting chat sessions file (#266273) * Splitting chat sessions class * Combininb delegate data source and tree renderer --- .../chat/browser/actions/chatActions.ts | 2 +- .../browser/actions/chatSessionActions.ts | 79 +- .../contrib/chat/browser/chat.contribution.ts | 5 +- .../chat/browser/chatSessions.contribution.ts | 2 +- .../contrib/chat/browser/chatSessions.ts | 1822 ----------------- .../chat/browser/chatSessions/common.ts | 118 ++ .../chatSessions/localChatSessionsProvider.ts | 253 +++ .../chatSessions/view/chatSessionsView.ts | 334 +++ .../chatSessions/view/sessionsTreeRenderer.ts | 639 ++++++ .../chatSessions/view/sessionsViewPane.ts | 470 +++++ 10 files changed, 1897 insertions(+), 1827 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 1213829c86f..f435ad2d688 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -73,7 +73,7 @@ import { ILanguageModelToolsService } from '../../common/languageModelToolsServi import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from '../chatEditorInput.js'; -import { VIEWLET_ID } from '../chatSessions.js'; +import { VIEWLET_ID } from '../chatSessions/view/chatSessionsView.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 1e3bf618d27..30dc779baae 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -3,20 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from '../../../../../nls.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; +import { IChatSessionRecommendation } from '../../../../../base/common/product.js'; import Severity from '../../../../../base/common/severity.js'; import { localize } from '../../../../../nls.js'; import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { IExtensionGalleryService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; +import { IProductService } from '../../../../../platform/product/common/productService.js'; +import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { AUX_WINDOW_GROUP, IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; +import { IWorkbenchExtensionManagementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; @@ -26,10 +32,11 @@ import { ChatConfiguration } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { VIEWLET_ID } from '../chatSessions.js'; import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; import { CHAT_CATEGORY } from './chatActions.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { VIEWLET_ID } from '../chatSessions/view/chatSessionsView.js'; export interface IChatSessionContext { sessionId: string; @@ -358,6 +365,65 @@ export class ToggleChatSessionsDescriptionDisplayAction extends Action2 { } } +export class ChatSessionsGettingStartedAction extends Action2 { + static readonly ID = 'chat.sessions.gettingStarted'; + + constructor() { + super({ + id: ChatSessionsGettingStartedAction.ID, + title: nls.localize2('chat.sessions.gettingStarted.action', "Getting Started with Chat Sessions"), + icon: Codicon.sendToRemoteAgent, + f1: false, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const productService = accessor.get(IProductService); + const quickInputService = accessor.get(IQuickInputService); + const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); + const extensionGalleryService = accessor.get(IExtensionGalleryService); + + const recommendations = productService.chatSessionRecommendations; + if (!recommendations || recommendations.length === 0) { + return; + } + + const installedExtensions = await extensionManagementService.getInstalled(); + const isExtensionAlreadyInstalled = (extensionId: string) => { + return installedExtensions.find(installed => installed.identifier.id === extensionId); + }; + + const quickPickItems = recommendations.map((recommendation: IChatSessionRecommendation) => { + const extensionInstalled = !!isExtensionAlreadyInstalled(recommendation.extensionId); + return { + label: recommendation.displayName, + description: recommendation.description, + detail: extensionInstalled + ? nls.localize('chatSessions.extensionAlreadyInstalled', "'{0}' is already installed", recommendation.extensionName) + : nls.localize('chatSessions.installExtension', "Installs '{0}'", recommendation.extensionName), + extensionId: recommendation.extensionId, + disabled: extensionInstalled, + }; + }); + + const selected = await quickInputService.pick(quickPickItems, { + title: nls.localize('chatSessions.selectExtension', "Install Chat Extensions"), + placeHolder: nls.localize('chatSessions.pickPlaceholder', "Choose extensions to enhance your chat experience"), + canPickMany: true, + }); + + if (!selected) { + return; + } + + const galleryExtensions = await extensionGalleryService.getExtensions(selected.map(item => ({ id: item.extensionId })), CancellationToken.None); + if (!galleryExtensions) { + return; + } + await extensionManagementService.installGalleryExtensions(galleryExtensions.map(extension => ({ extension, options: { preRelease: productService.quality !== 'stable' } }))); + } +} + // Register the menu item - show for all local chat sessions (including history items) MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { command: { @@ -423,3 +489,14 @@ MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { order: 1, when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), }); + +MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + command: { + id: 'workbench.action.openChat', + title: nls.localize2('interactiveSession.open', "New Chat Editor"), + icon: Codicon.plus + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), +}); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7742fe0f3df..049aa348cb4 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -74,7 +74,7 @@ import { registerChatExportActions } from './actions/chatImportExport.js'; import { registerLanguageModelActions } from './actions/chatLanguageModelActions.js'; import { registerMoveActions } from './actions/chatMoveActions.js'; import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; -import { DeleteChatSessionAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInNewWindowAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; +import { ChatSessionsGettingStartedAction, DeleteChatSessionAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInNewWindowAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; import { registerChatTitleActions } from './actions/chatTitleActions.js'; import { registerChatToolActions } from './actions/chatToolActions.js'; import { ChatTransferContribution } from './actions/chatTransfer.js'; @@ -101,7 +101,6 @@ import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatPart import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; import { QuickChatService } from './chatQuick.js'; import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; -import { ChatSessionsView } from './chatSessions.js'; import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; import { ChatVariablesService } from './chatVariables.js'; @@ -120,6 +119,7 @@ import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; +import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -969,5 +969,6 @@ registerAction2(OpenChatSessionInNewWindowAction); registerAction2(OpenChatSessionInNewEditorGroupAction); registerAction2(OpenChatSessionInSidebarAction); registerAction2(ToggleChatSessionsDescriptionDisplayAction); +registerAction2(ChatSessionsGettingStartedAction); ChatWidget.CONTRIBS.push(ChatDynamicVariableModel); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 25a9073dd0a..b953311989a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -30,7 +30,7 @@ import { ChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; -import { VIEWLET_ID } from './chatSessions.js'; +import { VIEWLET_ID } from './chatSessions/view/chatSessionsView.js'; const CODING_AGENT_DOCS = 'https://code.visualstudio.com/docs/copilot/copilot-coding-agent'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.ts deleted file mode 100644 index fba8e90a5c3..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.ts +++ /dev/null @@ -1,1822 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as DOM from '../../../../base/browser/dom.js'; -import { $, append, getActiveWindow } from '../../../../base/browser/dom.js'; -import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; -import { InputBox, MessageType } from '../../../../base/browser/ui/inputbox/inputBox.js'; -import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; -import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; -import { coalesce } from '../../../../base/common/arrays.js'; -import { timeout } from '../../../../base/common/async.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { fromNow } from '../../../../base/common/date.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; -import { FuzzyScore } from '../../../../base/common/filters.js'; -import { createSingleCallFunction } from '../../../../base/common/functional.js'; -import { isMarkdownString } from '../../../../base/common/htmlContent.js'; -import { KeyCode } from '../../../../base/common/keyCodes.js'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { MarshalledId } from '../../../../base/common/marshallingIds.js'; -import { IObservable } from '../../../../base/common/observable.js'; -import { IChatSessionRecommendation } from '../../../../base/common/product.js'; -import Severity from '../../../../base/common/severity.js'; -import { truncate } from '../../../../base/common/strings.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { URI } from '../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; -import * as nls from '../../../../nls.js'; -import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; -import { IExtensionGalleryService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { WorkbenchAsyncDataTree, WorkbenchList } from '../../../../platform/list/browser/listService.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import product from '../../../../platform/product/common/product.js'; -import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IProgressService } from '../../../../platform/progress/common/progress.js'; -import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { fillEditorsDragData } from '../../../browser/dnd.js'; -import { IResourceLabel, ResourceLabels } from '../../../browser/labels.js'; -import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js'; -import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; -import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { EditorInput } from '../../../common/editor/editorInput.js'; -import { Extensions, IEditableData, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainer, ViewContainerLocation } from '../../../common/views.js'; -import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; -import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; -import { IChatModel } from '../common/chatModel.js'; -import { IChatService } from '../common/chatService.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; -import { ChatSessionUri } from '../common/chatUri.js'; -import { ChatAgentLocation, ChatConfiguration } from '../common/constants.js'; -import { ChatViewId, IChatWidget, IChatWidgetService } from './chat.js'; -import { IChatEditorOptions } from './chatEditor.js'; -import { ChatEditorInput } from './chatEditorInput.js'; -import { allowedChatMarkdownHtmlTags } from './chatMarkdownRenderer.js'; -import { ChatSessionTracker } from './chatSessions/chatSessionTracker.js'; -import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getChatSessionType, isChatSession, isLocalChatSessionItem } from './chatSessions/common.js'; -import './media/chatSessions.css'; -import { ChatViewPane } from './chatViewPane.js'; - -export const VIEWLET_ID = 'workbench.view.chat.sessions'; - -// Helper function to update relative time for chat sessions (similar to timeline) -function updateRelativeTime(item: ChatSessionItemWithProvider, lastRelativeTime: string | undefined): string | undefined { - if (item.timing?.startTime) { - item.relativeTime = fromNow(item.timing.startTime); - item.relativeTimeFullWord = fromNow(item.timing.startTime, false, true); - if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) { - lastRelativeTime = item.relativeTime; - item.hideRelativeTime = false; - } else { - item.hideRelativeTime = true; - } - } else { - // Clear timestamp properties if no timestamp - item.relativeTime = undefined; - item.relativeTimeFullWord = undefined; - item.hideRelativeTime = false; - } - - return lastRelativeTime; -} - -// Helper function to extract timestamp from session item -function extractTimestamp(item: IChatSessionItem): number | undefined { - // Use timing.startTime if available from the API - if (item.timing?.startTime) { - return item.timing.startTime; - } - - // For other items, timestamp might already be set - if ('timestamp' in item) { - return (item as any).timestamp; - } - - return undefined; -} - -// Helper function to sort sessions by timestamp (newest first) -function sortSessionsByTimestamp(sessions: ChatSessionItemWithProvider[]): void { - sessions.sort((a, b) => { - const aTime = a.timing?.startTime ?? 0; - const bTime = b.timing?.startTime ?? 0; - return bTime - aTime; // newest first - }); -} - -// Helper function to apply time grouping to a list of sessions -function applyTimeGrouping(sessions: ChatSessionItemWithProvider[]): void { - let lastRelativeTime: string | undefined; - sessions.forEach(session => { - lastRelativeTime = updateRelativeTime(session, lastRelativeTime); - }); -} - -// Helper function to process session items with timestamps, sorting, and grouping -function processSessionsWithTimeGrouping(sessions: ChatSessionItemWithProvider[]): void { - // Only process if we have sessions with timestamps - if (sessions.some(session => session.timing?.startTime !== undefined)) { - sortSessionsByTimestamp(sessions); - applyTimeGrouping(sessions); - } -} - -// Helper function to create context overlay for session items -function getSessionItemContextOverlay( - session: ChatSessionItemWithProvider, - provider?: IChatSessionItemProvider, - chatWidgetService?: IChatWidgetService, - chatService?: IChatService, - editorGroupsService?: IEditorGroupsService -): [string, any][] { - const overlay: [string, any][] = []; - // Do not create an overaly for the show-history node - if (session.id === 'show-history') { - return overlay; - } - if (provider) { - overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]); - } - - // Mark history items - overlay.push([ChatContextKeys.isHistoryItem.key, session.isHistory]); - - // Mark active sessions - check if session is currently open in editor or widget - let isActiveSession = false; - - if (!session.isHistory && provider?.chatSessionType === 'local') { - // Local non-history sessions are always active - isActiveSession = true; - } else if (session.isHistory && chatWidgetService && chatService && editorGroupsService) { - // Check if session is open in a chat widget - const widget = chatWidgetService.getWidgetBySessionId(session.id); - if (widget) { - isActiveSession = true; - } else { - // Check if session is open in any editor - for (const group of editorGroupsService.groups) { - for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && editor.sessionId === session.id) { - isActiveSession = true; - break; - } - } - if (isActiveSession) { - break; - } - } - } - } - - overlay.push([ChatContextKeys.isActiveSession.key, isActiveSession]); - - return overlay; -} - -interface IGettingStartedItem { - id: string; - label: string; - commandId: string; - icon?: ThemeIcon; - args?: any[]; -} - -class GettingStartedDelegate implements IListVirtualDelegate { - getHeight(): number { - return 22; - } - - getTemplateId(): string { - return 'gettingStartedItem'; - } -} - -interface IGettingStartedTemplateData { - resourceLabel: IResourceLabel; -} - -class GettingStartedRenderer implements IListRenderer { - readonly templateId = 'gettingStartedItem'; - - constructor(private readonly labels: ResourceLabels) { } - - renderTemplate(container: HTMLElement): IGettingStartedTemplateData { - const resourceLabel = this.labels.create(container, { supportHighlights: true }); - return { resourceLabel }; - } - - renderElement(element: IGettingStartedItem, index: number, templateData: IGettingStartedTemplateData): void { - templateData.resourceLabel.setResource({ - name: element.label, - resource: undefined - }, { - icon: element.icon, - hideIcon: false - }); - templateData.resourceLabel.element.setAttribute('data-command', element.commandId); - } - - disposeTemplate(templateData: IGettingStartedTemplateData): void { - templateData.resourceLabel.dispose(); - } -} - -export class ChatSessionsView extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatSessions'; - - private isViewContainerRegistered = false; - private localProvider: LocalChatSessionsProvider | undefined; - private readonly sessionTracker: ChatSessionTracker; - private viewContainer: ViewContainer | undefined; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, - ) { - super(); - - this.sessionTracker = this._register(this.instantiationService.createInstance(ChatSessionTracker)); - this.setupEditorTracking(); - - // Create and register the local chat sessions provider immediately - // This ensures it's available even when the view container is not initialized - this.localProvider = this._register(this.instantiationService.createInstance(LocalChatSessionsProvider)); - this._register(this.chatSessionsService.registerChatSessionItemProvider(this.localProvider)); - - // Initial check - this.updateViewContainerRegistration(); - - // Listen for configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatConfiguration.AgentSessionsViewLocation)) { - this.updateViewContainerRegistration(); - } - })); - this._register(this.chatEntitlementService.onDidChangeSentiment(e => { - this.updateViewContainerRegistration(); - })); - } - - private setupEditorTracking(): void { - this._register(this.sessionTracker.onDidChangeEditors(e => { - this.chatSessionsService.notifySessionItemsChanged(e.sessionType); - })); - } - - private updateViewContainerRegistration(): void { - const location = this.configurationService.getValue(ChatConfiguration.AgentSessionsViewLocation); - const sentiment = this.chatEntitlementService.sentiment; - if (sentiment.disabled || sentiment.hidden || (location !== 'view' && this.isViewContainerRegistered)) { - this.deregisterViewContainer(); - } else if (location === 'view' && !this.isViewContainerRegistered) { - this.registerViewContainer(); - } - } - - private registerViewContainer(): void { - if (this.isViewContainerRegistered) { - return; - } - - this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( - { - id: VIEWLET_ID, - title: nls.localize2('chat.sessions', "Chat Sessions"), - ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), - hideIfEmpty: false, - icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Chat Sessions View'), - order: 6 - }, ViewContainerLocation.Sidebar); - this.isViewContainerRegistered = true; - } - - private deregisterViewContainer(): void { - if (this.viewContainer) { - const allViews = Registry.as(Extensions.ViewsRegistry).getViews(this.viewContainer); - if (allViews.length > 0) { - Registry.as(Extensions.ViewsRegistry).deregisterViews(allViews, this.viewContainer); - } - - Registry.as(Extensions.ViewContainersRegistry).deregisterViewContainer(this.viewContainer); - this.viewContainer = undefined; - this.isViewContainerRegistered = false; - } - } -} - -// Local Chat Sessions Provider - tracks open editors as chat sessions -class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider { - static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot'; - readonly chatSessionType = 'local'; - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - readonly _onDidChangeChatSessionItems = this._register(new Emitter()); - public get onDidChangeChatSessionItems() { return this._onDidChangeChatSessionItems.event; } - - // Track the current editor set to detect actual new additions - private currentEditorSet = new Set(); - - // Maintain ordered list of editor keys to preserve consistent ordering - private editorOrder: string[] = []; - - constructor( - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IChatService private readonly chatService: IChatService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - ) { - super(); - - this.initializeCurrentEditorSet(); - this.registerWidgetListeners(); - - this._register(this.chatService.onDidDisposeSession(() => { - this._onDidChange.fire(); - })); - - // Listen for global session items changes for our session type - this._register(this.chatSessionsService.onDidChangeSessionItems((sessionType) => { - if (sessionType === this.chatSessionType) { - this.initializeCurrentEditorSet(); - this._onDidChange.fire(); - } - })); - } - - private registerWidgetListeners(): void { - // Listen for new chat widgets being added/removed - this._register(this.chatWidgetService.onDidAddWidget(widget => { - // Only fire for chat view instance - if (widget.location === ChatAgentLocation.Chat && - typeof widget.viewContext === 'object' && - 'viewId' in widget.viewContext && - widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { - this._onDidChange.fire(); - - // Listen for view model changes on this widget - this._register(widget.onDidChangeViewModel(() => { - this._onDidChange.fire(); - if (widget.viewModel) { - this.registerProgressListener(widget.viewModel.model.requestInProgressObs); - } - })); - - // Listen for title changes on the current model - this.registerModelTitleListener(widget); - if (widget.viewModel) { - this.registerProgressListener(widget.viewModel.model.requestInProgressObs); - } - } - })); - - // Check for existing chat widgets and register listeners - const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) - .filter(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); - - existingWidgets.forEach(widget => { - this._register(widget.onDidChangeViewModel(() => { - this._onDidChange.fire(); - this.registerModelTitleListener(widget); - })); - - // Register title listener for existing widget - this.registerModelTitleListener(widget); - if (widget.viewModel) { - this.registerProgressListener(widget.viewModel.model.requestInProgressObs); - } - }); - } - - private registerProgressListener(observable: IObservable) { - const progressEvent = Event.fromObservableLight(observable); - this._register(progressEvent(() => { - this._onDidChangeChatSessionItems.fire(); - })); - } - - private registerModelTitleListener(widget: IChatWidget): void { - const model = widget.viewModel?.model; - if (model) { - // Listen for model changes, specifically for title changes via setCustomTitle - this._register(model.onDidChange((e) => { - // Fire change events for all title-related changes to refresh the tree - if (!e || e.kind === 'setCustomTitle') { - this._onDidChange.fire(); - } - })); - } - } - - private initializeCurrentEditorSet(): void { - this.currentEditorSet.clear(); - this.editorOrder = []; // Reset the order - - this.editorGroupService.groups.forEach(group => { - group.editors.forEach(editor => { - if (this.isLocalChatSession(editor)) { - const key = this.getEditorKey(editor, group); - this.currentEditorSet.add(key); - this.editorOrder.push(key); - } - }); - }); - } - - private getEditorKey(editor: EditorInput, group: IEditorGroup): string { - return `${group.id}-${editor.typeId}-${editor.resource?.toString() || editor.getName()}`; - } - - private isLocalChatSession(editor?: EditorInput): boolean { - // For the LocalChatSessionsProvider, we only want to track sessions that are actually 'local' type - if (!isChatSession(editor)) { - return false; - } - - if (!(editor instanceof ChatEditorInput)) { - return false; - } - - const sessionType = getChatSessionType(editor); - return sessionType === 'local'; - } - - private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { - if (model.requestInProgress) { - return ChatSessionStatus.InProgress; - } else { - const requests = model.getRequests(); - if (requests.length > 0) { - // Check if the last request was completed successfully or failed - const lastRequest = requests[requests.length - 1]; - if (lastRequest && lastRequest.response) { - if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails) { - return ChatSessionStatus.Failed; - } else if (lastRequest.response.isComplete) { - return ChatSessionStatus.Completed; - } else { - return ChatSessionStatus.InProgress; - } - } - } - } - return; - } - - async provideChatSessionItems(token: CancellationToken): Promise { - const sessions: ChatSessionItemWithProvider[] = []; - // Create a map to quickly find editors by their key - const editorMap = new Map(); - - this.editorGroupService.groups.forEach(group => { - group.editors.forEach(editor => { - if (editor instanceof ChatEditorInput) { - const key = this.getEditorKey(editor, group); - editorMap.set(key, { editor, group }); - } - }); - }); - - // Add chat view instance - const chatWidget = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) - .find(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); - const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; - const widgetSession: ChatSessionItemWithProvider = { - id: LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID, - label: chatWidget?.viewModel?.model.title || nls.localize2('chat.sessions.chatView', "Chat").value, - description: nls.localize('chat.sessions.chatView.description', "Chat View"), - iconPath: Codicon.chatSparkle, - status, - provider: this - }; - sessions.push(widgetSession); - - // Build editor-based sessions in the order specified by editorOrder - this.editorOrder.forEach((editorKey, index) => { - const editorInfo = editorMap.get(editorKey); - if (editorInfo) { - // Determine status and timestamp for editor-based session - let status: ChatSessionStatus | undefined; - let timestamp: number | undefined; - if (editorInfo.editor instanceof ChatEditorInput && editorInfo.editor.sessionId) { - const model = this.chatService.getSession(editorInfo.editor.sessionId); - if (model) { - status = this.modelToStatus(model); - // Get the last interaction timestamp from the model - const requests = model.getRequests(); - if (requests.length > 0) { - const lastRequest = requests[requests.length - 1]; - timestamp = lastRequest.timestamp; - } else { - // Fallback to current time if no requests yet - timestamp = Date.now(); - } - } - const editorSession: ChatSessionItemWithProvider = { - id: editorInfo.editor.sessionId, - label: editorInfo.editor.getName(), - iconPath: Codicon.chatSparkle, - status, - provider: this, - timing: { - startTime: timestamp ?? 0 - } - }; - sessions.push(editorSession); - } - } - }); - - // Add "Show history..." node at the end - return [...sessions, historyNode]; - } -} - -const historyNode: IChatSessionItem = { - id: 'show-history', - label: nls.localize('chat.sessions.showHistory', "History"), -}; - -// Chat sessions container -class ChatSessionsViewPaneContainer extends ViewPaneContainer { - private registeredViewDescriptors: Map = new Map(); - - constructor( - private readonly sessionTracker: ChatSessionTracker, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService contextMenuService: IContextMenuService, - @ITelemetryService telemetryService: ITelemetryService, - @IExtensionService extensionService: IExtensionService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @ILogService logService: ILogService, - @IProductService private readonly productService: IProductService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - ) { - super( - VIEWLET_ID, - { - mergeViewWithContainerWhenSingleView: false, - }, - instantiationService, - configurationService, - layoutService, - contextMenuService, - telemetryService, - extensionService, - themeService, - storageService, - contextService, - viewDescriptorService, - logService - ); - - this.updateViewRegistration(); - - // Listen for provider changes and register/unregister views accordingly - this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { - this.updateViewRegistration(); - })); - - // Listen for session items changes and refresh the appropriate provider tree - this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => { - this.refreshProviderTree(chatSessionType); - })); - - // Listen for contribution availability changes and update view registration - this._register(this.chatSessionsService.onDidChangeAvailability(() => { - this.updateViewRegistration(); - })); - } - - override getTitle(): string { - const title = nls.localize('chat.sessions.title', "Chat Sessions"); - return title; - } - - private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { - return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); - } - - private refreshProviderTree(chatSessionType: string): void { - // Find the provider with the matching chatSessionType - const providers = this.getAllChatSessionItemProviders(); - const targetProvider = providers.find(provider => provider.chatSessionType === chatSessionType); - - if (targetProvider) { - // Find the corresponding view and refresh its tree - const viewId = `${VIEWLET_ID}.${chatSessionType}`; - const view = this.getView(viewId) as SessionsViewPane | undefined; - if (view) { - view.refreshTree(); - } - } - } - - private async updateViewRegistration(): Promise { - // prepare all chat session providers - const contributions = this.chatSessionsService.getAllChatSessionContributions(); - await Promise.all(contributions.map(contrib => this.chatSessionsService.canResolveItemProvider(contrib.type))); - const currentProviders = this.getAllChatSessionItemProviders(); - const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType)); - - // Find views that need to be unregistered (providers that are no longer available) - const viewsToUnregister: IViewDescriptor[] = []; - for (const [providerId, viewDescriptor] of this.registeredViewDescriptors.entries()) { - if (!currentProviderIds.has(providerId)) { - viewsToUnregister.push(viewDescriptor); - this.registeredViewDescriptors.delete(providerId); - } - } - - // Unregister removed views - if (viewsToUnregister.length > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); - if (container) { - Registry.as(Extensions.ViewsRegistry).deregisterViews(viewsToUnregister, container); - } - } - - // Register new views - this.registerViews(contributions); - } - - private async registerViews(extensionPointContributions: IChatSessionsExtensionPoint[]) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); - const providers = this.getAllChatSessionItemProviders(); - - if (container && providers.length > 0) { - const viewDescriptorsToRegister: IViewDescriptor[] = []; - - // Separate providers by type and prepare display names - const localProvider = providers.find(p => p.chatSessionType === 'local'); - const historyProvider = providers.find(p => p.chatSessionType === 'history'); - const otherProviders = providers.filter(p => p.chatSessionType !== 'local' && p.chatSessionType !== 'history'); - - // Sort other providers alphabetically by display name - const providersWithDisplayNames = otherProviders.map(provider => { - const extContribution = extensionPointContributions.find(c => c.type === provider.chatSessionType); - if (!extContribution) { - this.logService.warn(`No extension contribution found for chat session type: ${provider.chatSessionType}`); - return null; - } - return { - provider, - displayName: extContribution.displayName - }; - }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string }>; - - // Sort alphabetically by display name - providersWithDisplayNames.sort((a, b) => a.displayName.localeCompare(b.displayName)); - - // Register views in priority order: local, history, then alphabetically sorted others - const orderedProviders = [ - ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Sessions', baseOrder: 0 }] : []), - ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: undefined }] : []), - ...providersWithDisplayNames.map((item, index) => ({ - ...item, - baseOrder: 2 + index, // Start from 2 for other providers - when: undefined, - })) - ]; - - orderedProviders.forEach(({ provider, displayName, baseOrder, when }) => { - // Only register if not already registered - if (!this.registeredViewDescriptors.has(provider.chatSessionType)) { - const viewDescriptor: IViewDescriptor = { - id: `${VIEWLET_ID}.${provider.chatSessionType}`, - name: { - value: displayName, - original: displayName, - }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [provider, this.sessionTracker]), - canToggleVisibility: true, - canMoveView: true, - order: baseOrder, // Use computed order based on priority and alphabetical sorting - when, - }; - - viewDescriptorsToRegister.push(viewDescriptor); - this.registeredViewDescriptors.set(provider.chatSessionType, viewDescriptor); - - if (provider.chatSessionType === 'local') { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - this._register(viewsRegistry.registerViewWelcomeContent(viewDescriptor.id, { - content: nls.localize('chatSessions.noResults', "No local chat sessions\n[Start a Chat](command:workbench.action.openChat)"), - })); - } - } - }); - - const gettingStartedViewId = `${VIEWLET_ID}.gettingStarted`; - if (!this.registeredViewDescriptors.has('gettingStarted') - && this.productService.chatSessionRecommendations - && this.productService.chatSessionRecommendations.length) { - const gettingStartedDescriptor: IViewDescriptor = { - id: gettingStartedViewId, - name: { - value: nls.localize('chat.sessions.gettingStarted', "Getting Started"), - original: 'Getting Started', - }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [null, this.sessionTracker]), - canToggleVisibility: true, - canMoveView: true, - order: 1000, - collapsed: !!otherProviders.length, - }; - viewDescriptorsToRegister.push(gettingStartedDescriptor); - this.registeredViewDescriptors.set('gettingStarted', gettingStartedDescriptor); - } - - if (viewDescriptorsToRegister.length > 0) { - Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptorsToRegister, container); - } - } - } - - override dispose(): void { - // Unregister all views before disposal - if (this.registeredViewDescriptors.size > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); - if (container) { - const allRegisteredViews = Array.from(this.registeredViewDescriptors.values()); - Registry.as(Extensions.ViewsRegistry).deregisterViews(allRegisteredViews, container); - } - this.registeredViewDescriptors.clear(); - } - - super.dispose(); - } -} - - -// Chat sessions item data source for the tree -class SessionsDataSource implements IAsyncDataSource { - - constructor( - private readonly provider: IChatSessionItemProvider, - private readonly chatService: IChatService, - private readonly sessionTracker: ChatSessionTracker, - ) { - } - - hasChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider): boolean { - const isProvider = element === this.provider; - if (isProvider) { - // Root provider always has children - return true; - } - - // Check if this is the "Show history..." node - if ('id' in element && element.id === historyNode.id) { - return true; - } - - return false; - } - - async getChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider): Promise { - if (element === this.provider) { - try { - const items = await this.provider.provideChatSessionItems(CancellationToken.None); - const itemsWithProvider = items.map(item => { - const itemWithProvider: ChatSessionItemWithProvider = { ...item, provider: this.provider }; - - // Extract timestamp using the helper function - itemWithProvider.timing = { startTime: extractTimestamp(item) ?? 0 }; - - return itemWithProvider; - }); - - // Add hybrid local editor sessions for this provider using the centralized service - if (this.provider.chatSessionType !== 'local') { - const hybridSessions = await this.sessionTracker.getHybridSessionsForProvider(this.provider); - itemsWithProvider.push(...(hybridSessions as ChatSessionItemWithProvider[])); - } - - // For non-local providers, apply time-based sorting and grouping - if (this.provider.chatSessionType !== 'local') { - processSessionsWithTimeGrouping(itemsWithProvider); - } - - return itemsWithProvider; - } catch (error) { - return []; - } - } - - // Check if this is the "Show history..." node - if ('id' in element && element.id === historyNode.id) { - return this.getHistoryItems(); - } - - // Individual session items don't have children - return []; - } - - private async getHistoryItems(): Promise { - try { - // Get all chat history - const allHistory = await this.chatService.getHistory(); - - // Create history items with provider reference and timestamps - const historyItems = allHistory.map((historyDetail: any): ChatSessionItemWithProvider => ({ - id: historyDetail.sessionId, - label: historyDetail.title, - iconPath: Codicon.chatSparkle, - provider: this.provider, - timing: { - startTime: historyDetail.lastMessageDate ?? Date.now() - }, - isHistory: true, - })); - - // Apply sorting and time grouping - processSessionsWithTimeGrouping(historyItems); - - return historyItems; - - } catch (error) { - return []; - } - } -} - -// Tree delegate for session items -class SessionsDelegate implements IListVirtualDelegate { - static readonly ITEM_HEIGHT = 22; - static readonly ITEM_HEIGHT_WITH_DESCRIPTION = 44; // Slightly smaller for cleaner look - - constructor(private readonly configurationService: IConfigurationService) { } - - getHeight(element: ChatSessionItemWithProvider): number { - // Return consistent height for all items (single-line layout) - if (element.description && this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && element.provider.chatSessionType !== 'local') { - return SessionsDelegate.ITEM_HEIGHT_WITH_DESCRIPTION; - } else { - return SessionsDelegate.ITEM_HEIGHT; - } - } - - getTemplateId(element: ChatSessionItemWithProvider): string { - return SessionsRenderer.TEMPLATE_ID; - } -} - -// Template data for session items -interface ISessionTemplateData { - readonly container: HTMLElement; - readonly resourceLabel: IResourceLabel; - readonly actionBar: ActionBar; - readonly elementDisposable: DisposableStore; - readonly timestamp: HTMLElement; - readonly descriptionRow: HTMLElement; - readonly descriptionLabel: HTMLElement; - readonly statisticsLabel: HTMLElement; -} - -// Renderer for session items in the tree -class SessionsRenderer extends Disposable implements ITreeRenderer { - static readonly TEMPLATE_ID = 'session'; - private appliedIconColorStyles = new Set(); - private markdownRenderer: MarkdownRenderer; - - constructor( - private readonly labels: ResourceLabels, - @IThemeService private readonly themeService: IThemeService, - @ILogService private readonly logService: ILogService, - @IContextViewService private readonly contextViewService: IContextViewService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHoverService private readonly hoverService: IHoverService, - @IInstantiationService instantiationService: IInstantiationService, - @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IChatService private readonly chatService: IChatService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - ) { - super(); - - // Listen for theme changes to clear applied styles - this._register(this.themeService.onDidColorThemeChange(() => { - this.appliedIconColorStyles.clear(); - })); - - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer, {}); - } - - private applyIconColorStyle(iconId: string, colorId: string): void { - const styleKey = `${iconId}-${colorId}`; - if (this.appliedIconColorStyles.has(styleKey)) { - return; // Already applied - } - - const colorTheme = this.themeService.getColorTheme(); - const color = colorTheme.getColor(colorId); - - if (color) { - // Target the ::before pseudo-element where the actual icon is rendered - const css = `.monaco-workbench .chat-session-item .monaco-icon-label.codicon-${iconId}::before { color: ${color} !important; }`; - const activeWindow = getActiveWindow(); - - const styleId = `chat-sessions-icon-${styleKey}`; - const existingStyle = activeWindow.document.getElementById(styleId); - if (existingStyle) { - existingStyle.textContent = css; - } else { - const styleElement = activeWindow.document.createElement('style'); - styleElement.id = styleId; - styleElement.textContent = css; - activeWindow.document.head.appendChild(styleElement); - - // Clean up on dispose - this._register({ - dispose: () => { - const activeWin = getActiveWindow(); - const style = activeWin.document.getElementById(styleId); - if (style) { - style.remove(); - } - } - }); - } - - this.appliedIconColorStyles.add(styleKey); - } else { - this.logService.debug('No color found for colorId:', colorId); - } - } - - get templateId(): string { - return SessionsRenderer.TEMPLATE_ID; - } - - renderTemplate(container: HTMLElement): ISessionTemplateData { - const element = append(container, $('.chat-session-item')); - - // Create a container that holds the label, timestamp, and actions - const contentContainer = append(element, $('.session-content')); - const resourceLabel = this.labels.create(contentContainer, { supportHighlights: true }); - const descriptionRow = append(element, $('.description-row')); - const descriptionLabel = append(descriptionRow, $('span.description')); - const statisticsLabel = append(descriptionRow, $('span.statistics')); - - // Create timestamp container and element - const timestampContainer = append(contentContainer, $('.timestamp-container')); - const timestamp = append(timestampContainer, $('.timestamp')); - - const actionsContainer = append(contentContainer, $('.actions')); - const actionBar = new ActionBar(actionsContainer); - const elementDisposable = new DisposableStore(); - - return { - container: element, - resourceLabel, - actionBar, - elementDisposable, - timestamp, - descriptionRow, - descriptionLabel, - statisticsLabel, - }; - } - - statusToIcon(status?: ChatSessionStatus) { - switch (status) { - case ChatSessionStatus.InProgress: - return Codicon.loading; - case ChatSessionStatus.Completed: - return Codicon.pass; - case ChatSessionStatus.Failed: - return Codicon.error; - default: - return Codicon.circleOutline; - } - - } - - renderElement(element: ITreeNode, index: number, templateData: ISessionTemplateData): void { - const session = element.element as ChatSessionItemWithProvider; - - // Add CSS class for local sessions - let editableData: IEditableData | undefined; - if (isLocalChatSessionItem(session)) { - templateData.container.classList.add('local-session'); - editableData = this.chatSessionsService.getEditableData(session.id); - } else { - templateData.container.classList.remove('local-session'); - } - - // Check if this session is being edited using the actual session ID - if (editableData) { - // Render input box for editing - templateData.actionBar.clear(); - const editDisposable = this.renderInputBox(templateData.container, session, editableData); - templateData.elementDisposable.add(editDisposable); - return; - } - - // Normal rendering - clear the action bar in case it was used for editing - templateData.actionBar.clear(); - - // Handle different icon types - let iconResource: URI | undefined; - let iconTheme: ThemeIcon | undefined; - if (!session.iconPath && session.id !== historyNode.id) { - iconTheme = this.statusToIcon(session.status); - } else { - iconTheme = session.iconPath; - } - - if (iconTheme?.color?.id) { - this.applyIconColorStyle(iconTheme.id, iconTheme.color.id); - } - - const renderDescriptionOnSecondRow = this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && session.provider.chatSessionType !== 'local'; - - if (renderDescriptionOnSecondRow && session.description) { - templateData.container.classList.toggle('multiline', true); - templateData.descriptionRow.style.display = 'flex'; - if (typeof session.description === 'string') { - templateData.descriptionLabel.textContent = session.description; - } else { - templateData.elementDisposable.add(this.markdownRenderer.render(session.description, { - sanitizerConfig: { - replaceWithPlaintext: true, - allowedTags: { - override: allowedChatMarkdownHtmlTags, - }, - allowedLinkSchemes: { augment: [product.urlProtocol] } - }, - }, templateData.descriptionLabel)); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'mousedown', e => e.stopPropagation())); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'click', e => e.stopPropagation())); - templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'auxclick', e => e.stopPropagation())); - } - - DOM.clearNode(templateData.statisticsLabel); - const insertionNode = append(templateData.statisticsLabel, $('span.insertions')); - insertionNode.textContent = session.statistics ? `+${session.statistics.insertions}` : ''; - const deletionNode = append(templateData.statisticsLabel, $('span.deletions')); - deletionNode.textContent = session.statistics ? `-${session.statistics.deletions}` : ''; - } else { - templateData.container.classList.toggle('multiline', false); - } - - // Prepare tooltip content - const tooltipContent = 'tooltip' in session && session.tooltip ? - (typeof session.tooltip === 'string' ? session.tooltip : - isMarkdownString(session.tooltip) ? { - markdown: session.tooltip, - markdownNotSupportedFallback: session.tooltip.value - } : undefined) : - undefined; - - // Set the resource label - templateData.resourceLabel.setResource({ - name: session.label, - description: !renderDescriptionOnSecondRow && 'description' in session && typeof session.description === 'string' ? session.description : '', - resource: iconResource - }, { - fileKind: undefined, - icon: iconTheme, - // Set tooltip on resourceLabel only for single-row items - title: !renderDescriptionOnSecondRow || !session.description ? tooltipContent : undefined - }); - - // For two-row items, set tooltip on the container instead - if (renderDescriptionOnSecondRow && session.description && tooltipContent) { - if (typeof tooltipContent === 'string') { - templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.container, { content: tooltipContent }) - ); - } else if (tooltipContent && typeof tooltipContent === 'object' && 'markdown' in tooltipContent) { - templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.container, { content: tooltipContent.markdown }) - ); - } - } - - // Handle timestamp display and grouping - const hasTimestamp = session.timing?.startTime !== undefined; - if (hasTimestamp) { - templateData.timestamp.textContent = session.relativeTime ?? ''; - templateData.timestamp.ariaLabel = session.relativeTimeFullWord ?? ''; - templateData.timestamp.parentElement!.classList.toggle('timestamp-duplicate', session.hideRelativeTime === true); - templateData.timestamp.parentElement!.style.display = ''; - } else { - // Hide timestamp container if no timestamp available - templateData.timestamp.parentElement!.style.display = 'none'; - } - - // Create context overlay for this specific session item - const contextOverlay = getSessionItemContextOverlay( - session, - session.provider, - this.chatWidgetService, - this.chatService, - this.editorGroupsService - ); - - const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); - - // Create menu for this session item - const menu = templateData.elementDisposable.add( - this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService) - ); - - // Setup action bar with contributed actions - const setupActionBar = () => { - templateData.actionBar.clear(); - - // Create marshalled context for command execution - const marshalledSession = { - session: session, - $mid: MarshalledId.ChatSessionContext - }; - - const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); - - const { primary } = getActionBarActions( - actions, - 'inline', - ); - - templateData.actionBar.push(primary, { icon: true, label: false }); - - // Set context for the action bar - templateData.actionBar.context = session; - }; - - // Setup initial action bar and listen for menu changes - templateData.elementDisposable.add(menu.onDidChange(() => setupActionBar())); - setupActionBar(); - } - - disposeElement(_element: ITreeNode, _index: number, templateData: ISessionTemplateData): void { - templateData.elementDisposable.clear(); - templateData.resourceLabel.clear(); - templateData.actionBar.clear(); - } - - private renderInputBox(container: HTMLElement, session: IChatSessionItem, editableData: IEditableData): DisposableStore { - // Hide the existing resource label element and session content - const existingResourceLabelElement = container.querySelector('.monaco-icon-label') as HTMLElement; - if (existingResourceLabelElement) { - existingResourceLabelElement.style.display = 'none'; - } - - // Hide the session content container to avoid layout conflicts - const sessionContentElement = container.querySelector('.session-content') as HTMLElement; - if (sessionContentElement) { - sessionContentElement.style.display = 'none'; - } - - // Create a simple container that mimics the file explorer's structure - const editContainer = DOM.append(container, DOM.$('.explorer-item.explorer-item-edited')); - - // Add the icon - const iconElement = DOM.append(editContainer, DOM.$('.codicon')); - if (session.iconPath && ThemeIcon.isThemeIcon(session.iconPath)) { - iconElement.classList.add(`codicon-${session.iconPath.id}`); - } else { - iconElement.classList.add('codicon-file'); // Default file icon - } - - // Create the input box directly - const inputBox = new InputBox(editContainer, this.contextViewService, { - validationOptions: { - validation: (value) => { - const message = editableData.validationMessage(value); - if (!message || message.severity !== Severity.Error) { - return null; - } - return { - content: message.content, - formatContent: true, - type: MessageType.ERROR - }; - } - }, - ariaLabel: nls.localize('chatSessionInputAriaLabel', "Type session name. Press Enter to confirm or Escape to cancel."), - inputBoxStyles: defaultInputBoxStyles, - }); - - inputBox.value = session.label; - inputBox.focus(); - inputBox.select({ start: 0, end: session.label.length }); - - const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => { - const value = inputBox.value; - - // Clean up the edit container - editContainer.style.display = 'none'; - editContainer.remove(); - - // Restore the original resource label - if (existingResourceLabelElement) { - existingResourceLabelElement.style.display = ''; - } - - // Restore the session content container - const sessionContentElement = container.querySelector('.session-content') as HTMLElement; - if (sessionContentElement) { - sessionContentElement.style.display = ''; - } - - if (finishEditing) { - editableData.onFinish(value, success); - } - }); - - const showInputBoxNotification = () => { - if (inputBox.isInputValid()) { - const message = editableData.validationMessage(inputBox.value); - if (message) { - inputBox.showMessage({ - content: message.content, - formatContent: true, - type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR - }); - } else { - inputBox.hideMessage(); - } - } - }; - showInputBoxNotification(); - - const disposables: IDisposable[] = [ - inputBox, - DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { - if (e.equals(KeyCode.Enter)) { - if (!inputBox.validate()) { - done(true, true); - } - } else if (e.equals(KeyCode.Escape)) { - done(false, true); - } - }), - DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_UP, () => { - showInputBoxNotification(); - }), - DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, async () => { - while (true) { - await timeout(0); - - const ownerDocument = inputBox.inputElement.ownerDocument; - if (!ownerDocument.hasFocus()) { - break; - } - if (DOM.isActiveElement(inputBox.inputElement)) { - return; - } else if (DOM.isHTMLElement(ownerDocument.activeElement) && DOM.hasParentWithClass(ownerDocument.activeElement, 'context-view')) { - // Do nothing - context menu is open - } else { - break; - } - } - - done(inputBox.isInputValid(), true); - }) - ]; - - const disposableStore = new DisposableStore(); - disposables.forEach(d => disposableStore.add(d)); - disposableStore.add(toDisposable(() => done(false, false))); - return disposableStore; - } - - disposeTemplate(templateData: ISessionTemplateData): void { - templateData.elementDisposable.dispose(); - templateData.resourceLabel.dispose(); - templateData.actionBar.dispose(); - } -} - -// Identity provider for session items -class SessionsIdentityProvider { - getId(element: ChatSessionItemWithProvider): string { - return element.id; - } -} - -// Accessibility provider for session items -class SessionsAccessibilityProvider { - getWidgetAriaLabel(): string { - return nls.localize('chatSessions', 'Chat Sessions'); - } - - getAriaLabel(element: ChatSessionItemWithProvider): string | null { - return element.label || element.id; - } -} - -class SessionsViewPane extends ViewPane { - private tree: WorkbenchAsyncDataTree | undefined; - private list: WorkbenchList | undefined; - private treeContainer: HTMLElement | undefined; - private messageElement?: HTMLElement; - private _isEmpty: boolean = true; - - constructor( - private readonly provider: IChatSessionItemProvider, - private readonly sessionTracker: ChatSessionTracker, - options: IViewPaneOptions, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IChatService private readonly chatService: IChatService, - @IEditorService private readonly editorService: IEditorService, - @IViewsService private readonly viewsService: IViewsService, - @ILogService private readonly logService: ILogService, - @IProgressService private readonly progressService: IProgressService, - @IMenuService private readonly menuService: IMenuService, - @ICommandService private readonly commandService: ICommandService, - @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, - @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); - - // Listen for changes in the provider if it's a LocalChatSessionsProvider - if (provider instanceof LocalChatSessionsProvider) { - this._register(provider.onDidChange(() => { - if (this.tree && this.isBodyVisible()) { - this.refreshTreeWithProgress(); - } - })); - } - - // Listen for configuration changes to refresh view when description display changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatConfiguration.ShowAgentSessionsViewDescription)) { - if (this.tree && this.isBodyVisible()) { - this.refreshTreeWithProgress(); - } - } - })); - } - - override shouldShowWelcome(): boolean { - return this._isEmpty; - } - - public refreshTree(): void { - if (this.tree && this.isBodyVisible()) { - this.refreshTreeWithProgress(); - } - } - - private isEmpty() { - // Check if the tree has the provider node and get its children count - if (!this.tree?.hasNode(this.provider)) { - return true; - } - const providerNode = this.tree.getNode(this.provider); - const childCount = providerNode.children?.length || 0; - - return childCount === 0; - } - - /** - * Updates the empty state message based on current tree data. - * Uses the tree's existing data to avoid redundant provider calls. - */ - private updateEmptyState(): void { - try { - const newEmptyState = this.isEmpty(); - if (newEmptyState !== this._isEmpty) { - this._isEmpty = newEmptyState; - this._onDidChangeViewWelcomeState.fire(); - } - } catch (error) { - this.logService.error('Error checking tree data for empty state:', error); - } - } - - /** - * Refreshes the tree data with progress indication. - * Shows a progress indicator while the tree updates its children from the provider. - */ - private async refreshTreeWithProgress(): Promise { - if (!this.tree) { - return; - } - - try { - await this.progressService.withProgress( - { - location: this.id, // Use the view ID as the progress location - title: nls.localize('chatSessions.refreshing', 'Refreshing chat sessions...'), - }, - async () => { - await this.tree!.updateChildren(this.provider); - } - ); - - // Check for empty state after refresh using tree data - this.updateEmptyState(); - } catch (error) { - // Log error but don't throw to avoid breaking the UI - this.logService.error('Error refreshing chat sessions tree:', error); - } - } - - /** - * Loads initial tree data with progress indication. - * Shows a progress indicator while the tree loads data from the provider. - */ - private async loadDataWithProgress(): Promise { - if (!this.tree) { - return; - } - - try { - await this.progressService.withProgress( - { - location: this.id, // Use the view ID as the progress location - title: nls.localize('chatSessions.loading', 'Loading chat sessions...'), - }, - async () => { - await this.tree!.setInput(this.provider); - } - ); - - // Check for empty state after loading using tree data - this.updateEmptyState(); - } catch (error) { - // Log error but don't throw to avoid breaking the UI - this.logService.error('Error loading chat sessions data:', error); - } - } - - protected override renderBody(container: HTMLElement): void { - super.renderBody(container); - - // For Getting Started view (null provider), show simple list - if (this.provider === null) { - this.renderGettingStartedList(container); - return; - } - - this.treeContainer = DOM.append(container, DOM.$('.chat-sessions-tree-container')); - // Create message element for empty state - this.messageElement = append(container, $('.chat-sessions-message')); - this.messageElement.style.display = 'none'; - // Create the tree components - const dataSource = new SessionsDataSource(this.provider, this.chatService, this.sessionTracker); - const delegate = new SessionsDelegate(this.configurationService); - const identityProvider = new SessionsIdentityProvider(); - const accessibilityProvider = new SessionsAccessibilityProvider(); - - // Use the existing ResourceLabels service for consistent styling - const labels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - const renderer = this.instantiationService.createInstance(SessionsRenderer, labels); - this._register(renderer); - - const getResourceForElement = (element: ChatSessionItemWithProvider): URI | null => { - if (element.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { - return null; - } - - return ChatSessionUri.forSession(element.provider.chatSessionType, element.id); - }; - - this.tree = this.instantiationService.createInstance( - WorkbenchAsyncDataTree, - 'ChatSessions', - this.treeContainer, - delegate, - [renderer], - dataSource, - { - dnd: { - onDragStart: (data, originalEvent) => { - try { - const elements = data.getData() as ChatSessionItemWithProvider[]; - const uris = coalesce(elements.map(getResourceForElement)); - this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent)); - } catch { - // noop - } - }, - getDragURI: (element: ChatSessionItemWithProvider) => { - if (element.id === historyNode.id) { - return null; - } - return getResourceForElement(element)?.toString() ?? null; - }, - getDragLabel: (elements: ChatSessionItemWithProvider[]) => { - if (elements.length === 1) { - return elements[0].label; - } - return nls.localize('chatSessions.dragLabel', "{0} chat sessions", elements.length); - }, - drop: () => { }, - onDragOver: () => false, - dispose: () => { }, - }, - accessibilityProvider, - identityProvider, - multipleSelectionSupport: false, - overrideStyles: { - listBackground: undefined - }, - setRowLineHeight: false - - } - ) as WorkbenchAsyncDataTree; - - // Set the input - this.tree.setInput(this.provider); - - // Register tree events - this._register(this.tree.onDidOpen((e) => { - if (e.element) { - this.openChatSession(e.element); - } - })); - - // Register context menu event for right-click actions - this._register(this.tree.onContextMenu((e) => { - if (e.element && e.element.id !== historyNode.id) { - this.showContextMenu(e); - } - })); - - // Handle visibility changes to load data - this._register(this.onDidChangeBodyVisibility(async visible => { - if (visible && this.tree) { - await this.loadDataWithProgress(); - } - })); - - // Initially load data if visible - if (this.isBodyVisible() && this.tree) { - this.loadDataWithProgress(); - } - - this._register(this.tree); - } - - private renderGettingStartedList(container: HTMLElement): void { - const listContainer = DOM.append(container, DOM.$('.getting-started-list-container')); - const items: IGettingStartedItem[] = [ - { - id: 'install-extensions', - label: nls.localize('chatSessions.installExtensions', "Install Chat Extensions"), - icon: Codicon.extensions, - commandId: 'chat.sessions.gettingStarted' - }, - { - id: 'learn-more', - label: nls.localize('chatSessions.learnMoreGHCodingAgent', "Learn More About GitHub Copilot coding agent"), - commandId: 'vscode.open', - icon: Codicon.book, - args: [URI.parse('https://aka.ms/coding-agent-docs')] - } - ]; - const delegate = new GettingStartedDelegate(); - - // Create ResourceLabels instance for the renderer - const labels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this._register(labels); - - const renderer = new GettingStartedRenderer(labels); - this.list = this.instantiationService.createInstance( - WorkbenchList, - 'GettingStarted', - listContainer, - delegate, - [renderer], - { - horizontalScrolling: false, - } - ); - this.list.splice(0, 0, items); - this._register(this.list.onDidOpen(e => { - if (e.element) { - this.commandService.executeCommand(e.element.commandId, ...e.element.args ?? []); - } - })); - - this._register(this.list); - } - - protected override layoutBody(height: number, width: number): void { - super.layoutBody(height, width); - if (this.tree) { - this.tree.layout(height, width); - } - if (this.list) { - this.list.layout(height, width); - } - } - - private async openChatSession(session: ChatSessionItemWithProvider) { - if (!session || !session.id) { - return; - } - - try { - // Check first if we already have an open editor for this session - const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); - const existingEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); - if (existingEditor) { - await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); - return; - } - if (this.chatWidgetService.getWidgetBySessionId(session.id)) { - return; - } - - if (session.id === historyNode.id) { - // Don't try to open the "Show history..." node itself - return; - } - - // Handle history items first - if (isLocalChatSessionItem(session)) { - const options: IChatEditorOptions = { - target: { sessionId: session.id }, - pinned: true, - ignoreInView: true, - preserveFocus: true, - }; - await this.editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); - return; - } else if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { - const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; - if (chatViewPane) { - await chatViewPane.loadSession(session.id); - } - return; - } - - const options: IChatEditorOptions = { - pinned: true, - ignoreInView: true, - preferredTitle: truncate(session.label, 30), - preserveFocus: true, - }; - await this.editorService.openEditor({ - resource: ChatSessionUri.forSession(session.provider.chatSessionType, session.id), - options, - }); - - } catch (error) { - this.logService.error('[SessionsViewPane] Failed to open chat session:', error); - } - } - - private showContextMenu(e: ITreeContextMenuEvent) { - if (!e.element) { - return; - } - - const session = e.element; - const sessionWithProvider = session as ChatSessionItemWithProvider; - - // Create context overlay for this specific session item - const contextOverlay = getSessionItemContextOverlay( - session, - sessionWithProvider.provider, - this.chatWidgetService, - this.chatService, - this.editorGroupsService - ); - const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); - - // Create marshalled context for command execution - const marshalledSession = { - session: session, - $mid: MarshalledId.ChatSessionContext - }; - - // Create menu for this session item to get actions - const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService); - - // Get actions and filter for context menu (all actions that are NOT inline) - const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); - - const { secondary } = getActionBarActions(actions, 'inline'); this.contextMenuService.showContextMenu({ - getActions: () => secondary, - getAnchor: () => e.anchor, - getActionsContext: () => marshalledSession, - }); - - menu.dispose(); - } -} - -class ChatSessionsGettingStartedAction extends Action2 { - static readonly ID = 'chat.sessions.gettingStarted'; - - constructor() { - super({ - id: ChatSessionsGettingStartedAction.ID, - title: nls.localize2('chat.sessions.gettingStarted.action', "Getting Started with Chat Sessions"), - icon: Codicon.sendToRemoteAgent, - f1: false, - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const productService = accessor.get(IProductService); - const quickInputService = accessor.get(IQuickInputService); - const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService); - const extensionGalleryService = accessor.get(IExtensionGalleryService); - - const recommendations = productService.chatSessionRecommendations; - if (!recommendations || recommendations.length === 0) { - return; - } - - const installedExtensions = await extensionManagementService.getInstalled(); - const isExtensionAlreadyInstalled = (extensionId: string) => { - return installedExtensions.find(installed => installed.identifier.id === extensionId); - }; - - const quickPickItems = recommendations.map((recommendation: IChatSessionRecommendation) => { - const extensionInstalled = !!isExtensionAlreadyInstalled(recommendation.extensionId); - return { - label: recommendation.displayName, - description: recommendation.description, - detail: extensionInstalled - ? nls.localize('chatSessions.extensionAlreadyInstalled', "'{0}' is already installed", recommendation.extensionName) - : nls.localize('chatSessions.installExtension', "Installs '{0}'", recommendation.extensionName), - extensionId: recommendation.extensionId, - disabled: extensionInstalled, - }; - }); - - const selected = await quickInputService.pick(quickPickItems, { - title: nls.localize('chatSessions.selectExtension', "Install Chat Extensions"), - placeHolder: nls.localize('chatSessions.pickPlaceholder', "Choose extensions to enhance your chat experience"), - canPickMany: true, - }); - - if (!selected) { - return; - } - - const galleryExtensions = await extensionGalleryService.getExtensions(selected.map(item => ({ id: item.extensionId })), CancellationToken.None); - if (!galleryExtensions) { - return; - } - await extensionManagementService.installGalleryExtensions(galleryExtensions.map(extension => ({ extension, options: { preRelease: productService.quality !== 'stable' } }))); - } -} - -registerAction2(ChatSessionsGettingStartedAction); - -MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - command: { - id: 'workbench.action.openChat', - title: nls.localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.plus - }, - group: 'navigation', - order: 1, - when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), -}); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index c3b076c8e10..bdd96c9f76d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -3,12 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { fromNow } from '../../../../../base/common/date.js'; import { Schemas } from '../../../../../base/common/network.js'; import { URI } from '../../../../../base/common/uri.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; +import { IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; export type ChatSessionItemWithProvider = IChatSessionItem & { @@ -85,3 +89,117 @@ export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, export function isLocalChatSessionItem(item: ChatSessionItemWithProvider): boolean { return item.provider.chatSessionType === 'local'; } + +// Helper function to update relative time for chat sessions (similar to timeline) +function updateRelativeTime(item: ChatSessionItemWithProvider, lastRelativeTime: string | undefined): string | undefined { + if (item.timing?.startTime) { + item.relativeTime = fromNow(item.timing.startTime); + item.relativeTimeFullWord = fromNow(item.timing.startTime, false, true); + if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) { + lastRelativeTime = item.relativeTime; + item.hideRelativeTime = false; + } else { + item.hideRelativeTime = true; + } + } else { + // Clear timestamp properties if no timestamp + item.relativeTime = undefined; + item.relativeTimeFullWord = undefined; + item.hideRelativeTime = false; + } + + return lastRelativeTime; +} + +// Helper function to extract timestamp from session item +export function extractTimestamp(item: IChatSessionItem): number | undefined { + // Use timing.startTime if available from the API + if (item.timing?.startTime) { + return item.timing.startTime; + } + + // For other items, timestamp might already be set + if ('timestamp' in item) { + return (item as any).timestamp; + } + + return undefined; +} + +// Helper function to sort sessions by timestamp (newest first) +function sortSessionsByTimestamp(sessions: ChatSessionItemWithProvider[]): void { + sessions.sort((a, b) => { + const aTime = a.timing?.startTime ?? 0; + const bTime = b.timing?.startTime ?? 0; + return bTime - aTime; // newest first + }); +} + +// Helper function to apply time grouping to a list of sessions +function applyTimeGrouping(sessions: ChatSessionItemWithProvider[]): void { + let lastRelativeTime: string | undefined; + sessions.forEach(session => { + lastRelativeTime = updateRelativeTime(session, lastRelativeTime); + }); +} + +// Helper function to process session items with timestamps, sorting, and grouping +export function processSessionsWithTimeGrouping(sessions: ChatSessionItemWithProvider[]): void { + // Only process if we have sessions with timestamps + if (sessions.some(session => session.timing?.startTime !== undefined)) { + sortSessionsByTimestamp(sessions); + applyTimeGrouping(sessions); + } +} + +// Helper function to create context overlay for session items +export function getSessionItemContextOverlay( + session: ChatSessionItemWithProvider, + provider?: IChatSessionItemProvider, + chatWidgetService?: IChatWidgetService, + chatService?: IChatService, + editorGroupsService?: IEditorGroupsService +): [string, any][] { + const overlay: [string, any][] = []; + // Do not create an overaly for the show-history node + if (session.id === 'show-history') { + return overlay; + } + if (provider) { + overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]); + } + + // Mark history items + overlay.push([ChatContextKeys.isHistoryItem.key, session.isHistory]); + + // Mark active sessions - check if session is currently open in editor or widget + let isActiveSession = false; + + if (!session.isHistory && provider?.chatSessionType === 'local') { + // Local non-history sessions are always active + isActiveSession = true; + } else if (session.isHistory && chatWidgetService && chatService && editorGroupsService) { + // Check if session is open in a chat widget + const widget = chatWidgetService.getWidgetBySessionId(session.id); + if (widget) { + isActiveSession = true; + } else { + // Check if session is open in any editor + for (const group of editorGroupsService.groups) { + for (const editor of group.editors) { + if (editor instanceof ChatEditorInput && editor.sessionId === session.id) { + isActiveSession = true; + break; + } + } + if (isActiveSession) { + break; + } + } + } + } + + overlay.push([ChatContextKeys.isActiveSession.key, isActiveSession]); + + return overlay; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts new file mode 100644 index 00000000000..673e495ef9e --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -0,0 +1,253 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { IObservable } from '../../../../../base/common/observable.js'; +import * as nls from '../../../../../nls.js'; +import { EditorInput } from '../../../../common/editor/editorInput.js'; +import { IEditorGroupsService, IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js'; +import { IChatModel } from '../../common/chatModel.js'; +import { IChatService } from '../../common/chatService.js'; +import { IChatSessionItemProvider, IChatSessionsService, ChatSessionStatus, IChatSessionItem } from '../../common/chatSessionsService.js'; +import { ChatAgentLocation } from '../../common/constants.js'; +import { IChatWidgetService, IChatWidget } from '../chat.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { isChatSession, getChatSessionType, ChatSessionItemWithProvider } from './common.js'; + +export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider { + static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot'; + static readonly HISTORY_NODE_ID = 'show-history'; + readonly chatSessionType = 'local'; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + readonly _onDidChangeChatSessionItems = this._register(new Emitter()); + public get onDidChangeChatSessionItems() { return this._onDidChangeChatSessionItems.event; } + + // Track the current editor set to detect actual new additions + private currentEditorSet = new Set(); + + // Maintain ordered list of editor keys to preserve consistent ordering + private editorOrder: string[] = []; + + constructor( + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IChatService private readonly chatService: IChatService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + ) { + super(); + + this.initializeCurrentEditorSet(); + this.registerWidgetListeners(); + + this._register(this.chatService.onDidDisposeSession(() => { + this._onDidChange.fire(); + })); + + // Listen for global session items changes for our session type + this._register(this.chatSessionsService.onDidChangeSessionItems((sessionType) => { + if (sessionType === this.chatSessionType) { + this.initializeCurrentEditorSet(); + this._onDidChange.fire(); + } + })); + } + + private registerWidgetListeners(): void { + // Listen for new chat widgets being added/removed + this._register(this.chatWidgetService.onDidAddWidget(widget => { + // Only fire for chat view instance + if (widget.location === ChatAgentLocation.Chat && + typeof widget.viewContext === 'object' && + 'viewId' in widget.viewContext && + widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { + this._onDidChange.fire(); + + // Listen for view model changes on this widget + this._register(widget.onDidChangeViewModel(() => { + this._onDidChange.fire(); + if (widget.viewModel) { + this.registerProgressListener(widget.viewModel.model.requestInProgressObs); + } + })); + + // Listen for title changes on the current model + this.registerModelTitleListener(widget); + if (widget.viewModel) { + this.registerProgressListener(widget.viewModel.model.requestInProgressObs); + } + } + })); + + // Check for existing chat widgets and register listeners + const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) + .filter(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); + + existingWidgets.forEach(widget => { + this._register(widget.onDidChangeViewModel(() => { + this._onDidChange.fire(); + this.registerModelTitleListener(widget); + })); + + // Register title listener for existing widget + this.registerModelTitleListener(widget); + if (widget.viewModel) { + this.registerProgressListener(widget.viewModel.model.requestInProgressObs); + } + }); + } + + private registerProgressListener(observable: IObservable) { + const progressEvent = Event.fromObservableLight(observable); + this._register(progressEvent(() => { + this._onDidChangeChatSessionItems.fire(); + })); + } + + private registerModelTitleListener(widget: IChatWidget): void { + const model = widget.viewModel?.model; + if (model) { + // Listen for model changes, specifically for title changes via setCustomTitle + this._register(model.onDidChange((e) => { + // Fire change events for all title-related changes to refresh the tree + if (!e || e.kind === 'setCustomTitle') { + this._onDidChange.fire(); + } + })); + } + } + + private initializeCurrentEditorSet(): void { + this.currentEditorSet.clear(); + this.editorOrder = []; // Reset the order + + this.editorGroupService.groups.forEach(group => { + group.editors.forEach(editor => { + if (this.isLocalChatSession(editor)) { + const key = this.getEditorKey(editor, group); + this.currentEditorSet.add(key); + this.editorOrder.push(key); + } + }); + }); + } + + private getEditorKey(editor: EditorInput, group: IEditorGroup): string { + return `${group.id}-${editor.typeId}-${editor.resource?.toString() || editor.getName()}`; + } + + private isLocalChatSession(editor?: EditorInput): boolean { + // For the LocalChatSessionsProvider, we only want to track sessions that are actually 'local' type + if (!isChatSession(editor)) { + return false; + } + + if (!(editor instanceof ChatEditorInput)) { + return false; + } + + const sessionType = getChatSessionType(editor); + return sessionType === 'local'; + } + + private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { + if (model.requestInProgress) { + return ChatSessionStatus.InProgress; + } else { + const requests = model.getRequests(); + if (requests.length > 0) { + // Check if the last request was completed successfully or failed + const lastRequest = requests[requests.length - 1]; + if (lastRequest && lastRequest.response) { + if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails) { + return ChatSessionStatus.Failed; + } else if (lastRequest.response.isComplete) { + return ChatSessionStatus.Completed; + } else { + return ChatSessionStatus.InProgress; + } + } + } + } + return; + } + + async provideChatSessionItems(token: CancellationToken): Promise { + const sessions: ChatSessionItemWithProvider[] = []; + // Create a map to quickly find editors by their key + const editorMap = new Map(); + + this.editorGroupService.groups.forEach(group => { + group.editors.forEach(editor => { + if (editor instanceof ChatEditorInput) { + const key = this.getEditorKey(editor, group); + editorMap.set(key, { editor, group }); + } + }); + }); + + // Add chat view instance + const chatWidget = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat) + .find(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID); + const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; + const widgetSession: ChatSessionItemWithProvider = { + id: LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID, + label: chatWidget?.viewModel?.model.title || nls.localize2('chat.sessions.chatView', "Chat").value, + description: nls.localize('chat.sessions.chatView.description', "Chat View"), + iconPath: Codicon.chatSparkle, + status, + provider: this + }; + sessions.push(widgetSession); + + // Build editor-based sessions in the order specified by editorOrder + this.editorOrder.forEach((editorKey, index) => { + const editorInfo = editorMap.get(editorKey); + if (editorInfo) { + // Determine status and timestamp for editor-based session + let status: ChatSessionStatus | undefined; + let timestamp: number | undefined; + if (editorInfo.editor instanceof ChatEditorInput && editorInfo.editor.sessionId) { + const model = this.chatService.getSession(editorInfo.editor.sessionId); + if (model) { + status = this.modelToStatus(model); + // Get the last interaction timestamp from the model + const requests = model.getRequests(); + if (requests.length > 0) { + const lastRequest = requests[requests.length - 1]; + timestamp = lastRequest.timestamp; + } else { + // Fallback to current time if no requests yet + timestamp = Date.now(); + } + } + const editorSession: ChatSessionItemWithProvider = { + id: editorInfo.editor.sessionId, + label: editorInfo.editor.getName(), + iconPath: Codicon.chatSparkle, + status, + provider: this, + timing: { + startTime: timestamp ?? 0 + } + }; + sessions.push(editorSession); + } + } + }); + + const historyNode: IChatSessionItem = { + id: LocalChatSessionsProvider.HISTORY_NODE_ID, + label: nls.localize('chat.sessions.showHistory', "History"), + }; + + // Add "Show history..." node at the end + return [...sessions, historyNode]; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts new file mode 100644 index 00000000000..4abcb60da00 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -0,0 +1,334 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import * as nls from '../../../../../../nls.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { SyncDescriptor } from '../../../../../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../../../platform/log/common/log.js'; +import { IProductService } from '../../../../../../platform/product/common/productService.js'; +import { Registry } from '../../../../../../platform/registry/common/platform.js'; +import { IStorageService } from '../../../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; +import { registerIcon } from '../../../../../../platform/theme/common/iconRegistry.js'; +import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; +import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; +import { ViewPaneContainer } from '../../../../../browser/parts/views/viewPaneContainer.js'; +import { IWorkbenchContribution } from '../../../../../common/contributions.js'; +import { ViewContainer, IViewContainersRegistry, Extensions, ViewContainerLocation, IViewsRegistry, IViewDescriptor, IViewDescriptorService } from '../../../../../common/views.js'; +import { IChatEntitlementService } from '../../../../../services/chat/common/chatEntitlementService.js'; +import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; +import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; +import { IChatSessionsService, IChatSessionItemProvider, IChatSessionsExtensionPoint } from '../../../common/chatSessionsService.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ChatSessionTracker } from '../chatSessionTracker.js'; +import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; +import { SessionsViewPane } from './sessionsViewPane.js'; + +export const VIEWLET_ID = 'workbench.view.chat.sessions'; + +export class ChatSessionsView extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatSessions'; + + private isViewContainerRegistered = false; + private localProvider: LocalChatSessionsProvider | undefined; + private readonly sessionTracker: ChatSessionTracker; + private viewContainer: ViewContainer | undefined; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + ) { + super(); + + this.sessionTracker = this._register(this.instantiationService.createInstance(ChatSessionTracker)); + this.setupEditorTracking(); + + // Create and register the local chat sessions provider immediately + // This ensures it's available even when the view container is not initialized + this.localProvider = this._register(this.instantiationService.createInstance(LocalChatSessionsProvider)); + this._register(this.chatSessionsService.registerChatSessionItemProvider(this.localProvider)); + + // Initial check + this.updateViewContainerRegistration(); + + // Listen for configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ChatConfiguration.AgentSessionsViewLocation)) { + this.updateViewContainerRegistration(); + } + })); + this._register(this.chatEntitlementService.onDidChangeSentiment(e => { + this.updateViewContainerRegistration(); + })); + } + + private setupEditorTracking(): void { + this._register(this.sessionTracker.onDidChangeEditors(e => { + this.chatSessionsService.notifySessionItemsChanged(e.sessionType); + })); + } + + private updateViewContainerRegistration(): void { + const location = this.configurationService.getValue(ChatConfiguration.AgentSessionsViewLocation); + const sentiment = this.chatEntitlementService.sentiment; + if (sentiment.disabled || sentiment.hidden || (location !== 'view' && this.isViewContainerRegistered)) { + this.deregisterViewContainer(); + } else if (location === 'view' && !this.isViewContainerRegistered) { + this.registerViewContainer(); + } + } + + private registerViewContainer(): void { + if (this.isViewContainerRegistered) { + return; + } + + this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: VIEWLET_ID, + title: nls.localize2('chat.sessions', "Chat Sessions"), + ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), + hideIfEmpty: false, + icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Chat Sessions View'), + order: 6 + }, ViewContainerLocation.Sidebar); + this.isViewContainerRegistered = true; + } + + private deregisterViewContainer(): void { + if (this.viewContainer) { + const allViews = Registry.as(Extensions.ViewsRegistry).getViews(this.viewContainer); + if (allViews.length > 0) { + Registry.as(Extensions.ViewsRegistry).deregisterViews(allViews, this.viewContainer); + } + + Registry.as(Extensions.ViewContainersRegistry).deregisterViewContainer(this.viewContainer); + this.viewContainer = undefined; + this.isViewContainerRegistered = false; + } + } +} + +// Chat sessions container +class ChatSessionsViewPaneContainer extends ViewPaneContainer { + private registeredViewDescriptors: Map = new Map(); + + constructor( + private readonly sessionTracker: ChatSessionTracker, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IContextMenuService contextMenuService: IContextMenuService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionService extensionService: IExtensionService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @ILogService logService: ILogService, + @IProductService private readonly productService: IProductService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + ) { + super( + VIEWLET_ID, + { + mergeViewWithContainerWhenSingleView: false, + }, + instantiationService, + configurationService, + layoutService, + contextMenuService, + telemetryService, + extensionService, + themeService, + storageService, + contextService, + viewDescriptorService, + logService + ); + + this.updateViewRegistration(); + + // Listen for provider changes and register/unregister views accordingly + this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { + this.updateViewRegistration(); + })); + + // Listen for session items changes and refresh the appropriate provider tree + this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => { + this.refreshProviderTree(chatSessionType); + })); + + // Listen for contribution availability changes and update view registration + this._register(this.chatSessionsService.onDidChangeAvailability(() => { + this.updateViewRegistration(); + })); + } + + override getTitle(): string { + const title = nls.localize('chat.sessions.title', "Chat Sessions"); + return title; + } + + private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { + return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); + } + + private refreshProviderTree(chatSessionType: string): void { + // Find the provider with the matching chatSessionType + const providers = this.getAllChatSessionItemProviders(); + const targetProvider = providers.find(provider => provider.chatSessionType === chatSessionType); + + if (targetProvider) { + // Find the corresponding view and refresh its tree + const viewId = `${VIEWLET_ID}.${chatSessionType}`; + const view = this.getView(viewId) as SessionsViewPane | undefined; + if (view) { + view.refreshTree(); + } + } + } + + private async updateViewRegistration(): Promise { + // prepare all chat session providers + const contributions = this.chatSessionsService.getAllChatSessionContributions(); + await Promise.all(contributions.map(contrib => this.chatSessionsService.canResolveItemProvider(contrib.type))); + const currentProviders = this.getAllChatSessionItemProviders(); + const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType)); + + // Find views that need to be unregistered (providers that are no longer available) + const viewsToUnregister: IViewDescriptor[] = []; + for (const [providerId, viewDescriptor] of this.registeredViewDescriptors.entries()) { + if (!currentProviderIds.has(providerId)) { + viewsToUnregister.push(viewDescriptor); + this.registeredViewDescriptors.delete(providerId); + } + } + + // Unregister removed views + if (viewsToUnregister.length > 0) { + const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + if (container) { + Registry.as(Extensions.ViewsRegistry).deregisterViews(viewsToUnregister, container); + } + } + + // Register new views + this.registerViews(contributions); + } + + private async registerViews(extensionPointContributions: IChatSessionsExtensionPoint[]) { + const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + const providers = this.getAllChatSessionItemProviders(); + + if (container && providers.length > 0) { + const viewDescriptorsToRegister: IViewDescriptor[] = []; + + // Separate providers by type and prepare display names + const localProvider = providers.find(p => p.chatSessionType === 'local'); + const historyProvider = providers.find(p => p.chatSessionType === 'history'); + const otherProviders = providers.filter(p => p.chatSessionType !== 'local' && p.chatSessionType !== 'history'); + + // Sort other providers alphabetically by display name + const providersWithDisplayNames = otherProviders.map(provider => { + const extContribution = extensionPointContributions.find(c => c.type === provider.chatSessionType); + if (!extContribution) { + this.logService.warn(`No extension contribution found for chat session type: ${provider.chatSessionType}`); + return null; + } + return { + provider, + displayName: extContribution.displayName + }; + }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string }>; + + // Sort alphabetically by display name + providersWithDisplayNames.sort((a, b) => a.displayName.localeCompare(b.displayName)); + + // Register views in priority order: local, history, then alphabetically sorted others + const orderedProviders = [ + ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Sessions', baseOrder: 0 }] : []), + ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: undefined }] : []), + ...providersWithDisplayNames.map((item, index) => ({ + ...item, + baseOrder: 2 + index, // Start from 2 for other providers + when: undefined, + })) + ]; + + orderedProviders.forEach(({ provider, displayName, baseOrder, when }) => { + // Only register if not already registered + if (!this.registeredViewDescriptors.has(provider.chatSessionType)) { + const viewDescriptor: IViewDescriptor = { + id: `${VIEWLET_ID}.${provider.chatSessionType}`, + name: { + value: displayName, + original: displayName, + }, + ctorDescriptor: new SyncDescriptor(SessionsViewPane, [provider, this.sessionTracker]), + canToggleVisibility: true, + canMoveView: true, + order: baseOrder, // Use computed order based on priority and alphabetical sorting + when, + }; + + viewDescriptorsToRegister.push(viewDescriptor); + this.registeredViewDescriptors.set(provider.chatSessionType, viewDescriptor); + + if (provider.chatSessionType === 'local') { + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + this._register(viewsRegistry.registerViewWelcomeContent(viewDescriptor.id, { + content: nls.localize('chatSessions.noResults', "No local chat sessions\n[Start a Chat](command:workbench.action.openChat)"), + })); + } + } + }); + + const gettingStartedViewId = `${VIEWLET_ID}.gettingStarted`; + if (!this.registeredViewDescriptors.has('gettingStarted') + && this.productService.chatSessionRecommendations + && this.productService.chatSessionRecommendations.length) { + const gettingStartedDescriptor: IViewDescriptor = { + id: gettingStartedViewId, + name: { + value: nls.localize('chat.sessions.gettingStarted', "Getting Started"), + original: 'Getting Started', + }, + ctorDescriptor: new SyncDescriptor(SessionsViewPane, [null, this.sessionTracker]), + canToggleVisibility: true, + canMoveView: true, + order: 1000, + collapsed: !!otherProviders.length, + }; + viewDescriptorsToRegister.push(gettingStartedDescriptor); + this.registeredViewDescriptors.set('gettingStarted', gettingStartedDescriptor); + } + + if (viewDescriptorsToRegister.length > 0) { + Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptorsToRegister, container); + } + } + } + + override dispose(): void { + // Unregister all views before disposal + if (this.registeredViewDescriptors.size > 0) { + const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + if (container) { + const allRegisteredViews = Array.from(this.registeredViewDescriptors.values()); + Registry.as(Extensions.ViewsRegistry).deregisterViews(allRegisteredViews, container); + } + this.registeredViewDescriptors.clear(); + } + + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts new file mode 100644 index 00000000000..3a75ea8ecdb --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -0,0 +1,639 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from '../../../../../../base/browser/dom.js'; +import { $, append, getActiveWindow } from '../../../../../../base/browser/dom.js'; +import { StandardKeyboardEvent } from '../../../../../../base/browser/keyboardEvent.js'; +import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; +import { InputBox, MessageType } from '../../../../../../base/browser/ui/inputbox/inputBox.js'; +import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../../../base/browser/ui/tree/tree.js'; +import { timeout } from '../../../../../../base/common/async.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { FuzzyScore } from '../../../../../../base/common/filters.js'; +import { createSingleCallFunction } from '../../../../../../base/common/functional.js'; +import { isMarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { KeyCode } from '../../../../../../base/common/keyCodes.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; +import Severity from '../../../../../../base/common/severity.js'; +import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import * as nls from '../../../../../../nls.js'; +import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IContextViewService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../../../platform/log/common/log.js'; +import product from '../../../../../../platform/product/common/product.js'; +import { defaultInputBoxStyles } from '../../../../../../platform/theme/browser/defaultStyles.js'; +import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; +import { IResourceLabel, ResourceLabels } from '../../../../../browser/labels.js'; +import { IEditableData } from '../../../../../common/views.js'; +import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; +import { IChatService } from '../../../common/chatService.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../../common/chatSessionsService.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { IChatWidgetService } from '../../chat.js'; +import { allowedChatMarkdownHtmlTags } from '../../chatMarkdownRenderer.js'; +import { ChatSessionItemWithProvider, extractTimestamp, getSessionItemContextOverlay, isLocalChatSessionItem, processSessionsWithTimeGrouping } from '../common.js'; +import '../../media/chatSessions.css'; +import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; +import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/browser/ui/list/list.js'; +import { ChatSessionTracker } from '../chatSessionTracker.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; + +interface ISessionTemplateData { + readonly container: HTMLElement; + readonly resourceLabel: IResourceLabel; + readonly actionBar: ActionBar; + readonly elementDisposable: DisposableStore; + readonly timestamp: HTMLElement; + readonly descriptionRow: HTMLElement; + readonly descriptionLabel: HTMLElement; + readonly statisticsLabel: HTMLElement; +} + +export interface IGettingStartedItem { + id: string; + label: string; + commandId: string; + icon?: ThemeIcon; + args?: any[]; +} + +export class GettingStartedDelegate implements IListVirtualDelegate { + getHeight(): number { + return 22; + } + + getTemplateId(): string { + return 'gettingStartedItem'; + } +} + +interface IGettingStartedTemplateData { + resourceLabel: IResourceLabel; +} + +export class GettingStartedRenderer implements IListRenderer { + readonly templateId = 'gettingStartedItem'; + + constructor(private readonly labels: ResourceLabels) { } + + renderTemplate(container: HTMLElement): IGettingStartedTemplateData { + const resourceLabel = this.labels.create(container, { supportHighlights: true }); + return { resourceLabel }; + } + + renderElement(element: IGettingStartedItem, index: number, templateData: IGettingStartedTemplateData): void { + templateData.resourceLabel.setResource({ + name: element.label, + resource: undefined + }, { + icon: element.icon, + hideIcon: false + }); + templateData.resourceLabel.element.setAttribute('data-command', element.commandId); + } + + disposeTemplate(templateData: IGettingStartedTemplateData): void { + templateData.resourceLabel.dispose(); + } +} + +export class SessionsRenderer extends Disposable implements ITreeRenderer { + static readonly TEMPLATE_ID = 'session'; + private appliedIconColorStyles = new Set(); + private markdownRenderer: MarkdownRenderer; + + constructor( + private readonly labels: ResourceLabels, + @IThemeService private readonly themeService: IThemeService, + @ILogService private readonly logService: ILogService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IHoverService private readonly hoverService: IHoverService, + @IInstantiationService instantiationService: IInstantiationService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IChatService private readonly chatService: IChatService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + ) { + super(); + + // Listen for theme changes to clear applied styles + this._register(this.themeService.onDidColorThemeChange(() => { + this.appliedIconColorStyles.clear(); + })); + + this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer, {}); + } + + private applyIconColorStyle(iconId: string, colorId: string): void { + const styleKey = `${iconId}-${colorId}`; + if (this.appliedIconColorStyles.has(styleKey)) { + return; // Already applied + } + + const colorTheme = this.themeService.getColorTheme(); + const color = colorTheme.getColor(colorId); + + if (color) { + // Target the ::before pseudo-element where the actual icon is rendered + const css = `.monaco-workbench .chat-session-item .monaco-icon-label.codicon-${iconId}::before { color: ${color} !important; }`; + const activeWindow = getActiveWindow(); + + const styleId = `chat-sessions-icon-${styleKey}`; + const existingStyle = activeWindow.document.getElementById(styleId); + if (existingStyle) { + existingStyle.textContent = css; + } else { + const styleElement = activeWindow.document.createElement('style'); + styleElement.id = styleId; + styleElement.textContent = css; + activeWindow.document.head.appendChild(styleElement); + + // Clean up on dispose + this._register({ + dispose: () => { + const activeWin = getActiveWindow(); + const style = activeWin.document.getElementById(styleId); + if (style) { + style.remove(); + } + } + }); + } + + this.appliedIconColorStyles.add(styleKey); + } else { + this.logService.debug('No color found for colorId:', colorId); + } + } + + get templateId(): string { + return SessionsRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): ISessionTemplateData { + const element = append(container, $('.chat-session-item')); + + // Create a container that holds the label, timestamp, and actions + const contentContainer = append(element, $('.session-content')); + const resourceLabel = this.labels.create(contentContainer, { supportHighlights: true }); + const descriptionRow = append(element, $('.description-row')); + const descriptionLabel = append(descriptionRow, $('span.description')); + const statisticsLabel = append(descriptionRow, $('span.statistics')); + + // Create timestamp container and element + const timestampContainer = append(contentContainer, $('.timestamp-container')); + const timestamp = append(timestampContainer, $('.timestamp')); + + const actionsContainer = append(contentContainer, $('.actions')); + const actionBar = new ActionBar(actionsContainer); + const elementDisposable = new DisposableStore(); + + return { + container: element, + resourceLabel, + actionBar, + elementDisposable, + timestamp, + descriptionRow, + descriptionLabel, + statisticsLabel, + }; + } + + statusToIcon(status?: ChatSessionStatus) { + switch (status) { + case ChatSessionStatus.InProgress: + return Codicon.loading; + case ChatSessionStatus.Completed: + return Codicon.pass; + case ChatSessionStatus.Failed: + return Codicon.error; + default: + return Codicon.circleOutline; + } + + } + + renderElement(element: ITreeNode, index: number, templateData: ISessionTemplateData): void { + const session = element.element as ChatSessionItemWithProvider; + + // Add CSS class for local sessions + let editableData: IEditableData | undefined; + if (isLocalChatSessionItem(session)) { + templateData.container.classList.add('local-session'); + editableData = this.chatSessionsService.getEditableData(session.id); + } else { + templateData.container.classList.remove('local-session'); + } + + // Check if this session is being edited using the actual session ID + if (editableData) { + // Render input box for editing + templateData.actionBar.clear(); + const editDisposable = this.renderInputBox(templateData.container, session, editableData); + templateData.elementDisposable.add(editDisposable); + return; + } + + // Normal rendering - clear the action bar in case it was used for editing + templateData.actionBar.clear(); + + // Handle different icon types + let iconResource: URI | undefined; + let iconTheme: ThemeIcon | undefined; + if (!session.iconPath && session.id !== LocalChatSessionsProvider.HISTORY_NODE_ID) { + iconTheme = this.statusToIcon(session.status); + } else { + iconTheme = session.iconPath; + } + + if (iconTheme?.color?.id) { + this.applyIconColorStyle(iconTheme.id, iconTheme.color.id); + } + + const renderDescriptionOnSecondRow = this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && session.provider.chatSessionType !== 'local'; + + if (renderDescriptionOnSecondRow && session.description) { + templateData.container.classList.toggle('multiline', true); + templateData.descriptionRow.style.display = 'flex'; + if (typeof session.description === 'string') { + templateData.descriptionLabel.textContent = session.description; + } else { + templateData.elementDisposable.add(this.markdownRenderer.render(session.description, { + sanitizerConfig: { + replaceWithPlaintext: true, + allowedTags: { + override: allowedChatMarkdownHtmlTags, + }, + allowedLinkSchemes: { augment: [product.urlProtocol] } + }, + }, templateData.descriptionLabel)); + templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'mousedown', e => e.stopPropagation())); + templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'click', e => e.stopPropagation())); + templateData.elementDisposable.add(DOM.addDisposableListener(templateData.descriptionLabel, 'auxclick', e => e.stopPropagation())); + } + + DOM.clearNode(templateData.statisticsLabel); + const insertionNode = append(templateData.statisticsLabel, $('span.insertions')); + insertionNode.textContent = session.statistics ? `+${session.statistics.insertions}` : ''; + const deletionNode = append(templateData.statisticsLabel, $('span.deletions')); + deletionNode.textContent = session.statistics ? `-${session.statistics.deletions}` : ''; + } else { + templateData.container.classList.toggle('multiline', false); + } + + // Prepare tooltip content + const tooltipContent = 'tooltip' in session && session.tooltip ? + (typeof session.tooltip === 'string' ? session.tooltip : + isMarkdownString(session.tooltip) ? { + markdown: session.tooltip, + markdownNotSupportedFallback: session.tooltip.value + } : undefined) : + undefined; + + // Set the resource label + templateData.resourceLabel.setResource({ + name: session.label, + description: !renderDescriptionOnSecondRow && 'description' in session && typeof session.description === 'string' ? session.description : '', + resource: iconResource + }, { + fileKind: undefined, + icon: iconTheme, + // Set tooltip on resourceLabel only for single-row items + title: !renderDescriptionOnSecondRow || !session.description ? tooltipContent : undefined + }); + + // For two-row items, set tooltip on the container instead + if (renderDescriptionOnSecondRow && session.description && tooltipContent) { + if (typeof tooltipContent === 'string') { + templateData.elementDisposable.add( + this.hoverService.setupDelayedHover(templateData.container, { content: tooltipContent }) + ); + } else if (tooltipContent && typeof tooltipContent === 'object' && 'markdown' in tooltipContent) { + templateData.elementDisposable.add( + this.hoverService.setupDelayedHover(templateData.container, { content: tooltipContent.markdown }) + ); + } + } + + // Handle timestamp display and grouping + const hasTimestamp = session.timing?.startTime !== undefined; + if (hasTimestamp) { + templateData.timestamp.textContent = session.relativeTime ?? ''; + templateData.timestamp.ariaLabel = session.relativeTimeFullWord ?? ''; + templateData.timestamp.parentElement!.classList.toggle('timestamp-duplicate', session.hideRelativeTime === true); + templateData.timestamp.parentElement!.style.display = ''; + } else { + // Hide timestamp container if no timestamp available + templateData.timestamp.parentElement!.style.display = 'none'; + } + + // Create context overlay for this specific session item + const contextOverlay = getSessionItemContextOverlay( + session, + session.provider, + this.chatWidgetService, + this.chatService, + this.editorGroupsService + ); + + const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); + + // Create menu for this session item + const menu = templateData.elementDisposable.add( + this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService) + ); + + // Setup action bar with contributed actions + const setupActionBar = () => { + templateData.actionBar.clear(); + + // Create marshalled context for command execution + const marshalledSession = { + session: session, + $mid: MarshalledId.ChatSessionContext + }; + + const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); + + const { primary } = getActionBarActions( + actions, + 'inline', + ); + + templateData.actionBar.push(primary, { icon: true, label: false }); + + // Set context for the action bar + templateData.actionBar.context = session; + }; + + // Setup initial action bar and listen for menu changes + templateData.elementDisposable.add(menu.onDidChange(() => setupActionBar())); + setupActionBar(); + } + + disposeElement(_element: ITreeNode, _index: number, templateData: ISessionTemplateData): void { + templateData.elementDisposable.clear(); + templateData.resourceLabel.clear(); + templateData.actionBar.clear(); + } + + private renderInputBox(container: HTMLElement, session: IChatSessionItem, editableData: IEditableData): DisposableStore { + // Hide the existing resource label element and session content + const existingResourceLabelElement = container.querySelector('.monaco-icon-label') as HTMLElement; + if (existingResourceLabelElement) { + existingResourceLabelElement.style.display = 'none'; + } + + // Hide the session content container to avoid layout conflicts + const sessionContentElement = container.querySelector('.session-content') as HTMLElement; + if (sessionContentElement) { + sessionContentElement.style.display = 'none'; + } + + // Create a simple container that mimics the file explorer's structure + const editContainer = DOM.append(container, DOM.$('.explorer-item.explorer-item-edited')); + + // Add the icon + const iconElement = DOM.append(editContainer, DOM.$('.codicon')); + if (session.iconPath && ThemeIcon.isThemeIcon(session.iconPath)) { + iconElement.classList.add(`codicon-${session.iconPath.id}`); + } else { + iconElement.classList.add('codicon-file'); // Default file icon + } + + // Create the input box directly + const inputBox = new InputBox(editContainer, this.contextViewService, { + validationOptions: { + validation: (value) => { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { + return null; + } + return { + content: message.content, + formatContent: true, + type: MessageType.ERROR + }; + } + }, + ariaLabel: nls.localize('chatSessionInputAriaLabel', "Type session name. Press Enter to confirm or Escape to cancel."), + inputBoxStyles: defaultInputBoxStyles, + }); + + inputBox.value = session.label; + inputBox.focus(); + inputBox.select({ start: 0, end: session.label.length }); + + const done = createSingleCallFunction((success: boolean, finishEditing: boolean) => { + const value = inputBox.value; + + // Clean up the edit container + editContainer.style.display = 'none'; + editContainer.remove(); + + // Restore the original resource label + if (existingResourceLabelElement) { + existingResourceLabelElement.style.display = ''; + } + + // Restore the session content container + const sessionContentElement = container.querySelector('.session-content') as HTMLElement; + if (sessionContentElement) { + sessionContentElement.style.display = ''; + } + + if (finishEditing) { + editableData.onFinish(value, success); + } + }); + + const showInputBoxNotification = () => { + if (inputBox.isInputValid()) { + const message = editableData.validationMessage(inputBox.value); + if (message) { + inputBox.showMessage({ + content: message.content, + formatContent: true, + type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR + }); + } else { + inputBox.hideMessage(); + } + } + }; + showInputBoxNotification(); + + const disposables: IDisposable[] = [ + inputBox, + DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { + if (e.equals(KeyCode.Enter)) { + if (!inputBox.validate()) { + done(true, true); + } + } else if (e.equals(KeyCode.Escape)) { + done(false, true); + } + }), + DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_UP, () => { + showInputBoxNotification(); + }), + DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, async () => { + while (true) { + await timeout(0); + + const ownerDocument = inputBox.inputElement.ownerDocument; + if (!ownerDocument.hasFocus()) { + break; + } + if (DOM.isActiveElement(inputBox.inputElement)) { + return; + } else if (DOM.isHTMLElement(ownerDocument.activeElement) && DOM.hasParentWithClass(ownerDocument.activeElement, 'context-view')) { + // Do nothing - context menu is open + } else { + break; + } + } + + done(inputBox.isInputValid(), true); + }) + ]; + + const disposableStore = new DisposableStore(); + disposables.forEach(d => disposableStore.add(d)); + disposableStore.add(toDisposable(() => done(false, false))); + return disposableStore; + } + + disposeTemplate(templateData: ISessionTemplateData): void { + templateData.elementDisposable.dispose(); + templateData.resourceLabel.dispose(); + templateData.actionBar.dispose(); + } +} + +// Chat sessions item data source for the tree +export class SessionsDataSource implements IAsyncDataSource { + + constructor( + private readonly provider: IChatSessionItemProvider, + private readonly chatService: IChatService, + private readonly sessionTracker: ChatSessionTracker, + ) { + } + + hasChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider): boolean { + const isProvider = element === this.provider; + if (isProvider) { + // Root provider always has children + return true; + } + + // Check if this is the "Show history..." node + if ('id' in element && element.id === LocalChatSessionsProvider.HISTORY_NODE_ID) { + return true; + } + + return false; + } + + async getChildren(element: IChatSessionItemProvider | ChatSessionItemWithProvider): Promise { + if (element === this.provider) { + try { + const items = await this.provider.provideChatSessionItems(CancellationToken.None); + const itemsWithProvider = items.map(item => { + const itemWithProvider: ChatSessionItemWithProvider = { ...item, provider: this.provider }; + + // Extract timestamp using the helper function + itemWithProvider.timing = { startTime: extractTimestamp(item) ?? 0 }; + + return itemWithProvider; + }); + + // Add hybrid local editor sessions for this provider using the centralized service + if (this.provider.chatSessionType !== 'local') { + const hybridSessions = await this.sessionTracker.getHybridSessionsForProvider(this.provider); + itemsWithProvider.push(...(hybridSessions as ChatSessionItemWithProvider[])); + } + + // For non-local providers, apply time-based sorting and grouping + if (this.provider.chatSessionType !== 'local') { + processSessionsWithTimeGrouping(itemsWithProvider); + } + + return itemsWithProvider; + } catch (error) { + return []; + } + } + + // Check if this is the "Show history..." node + if ('id' in element && element.id === LocalChatSessionsProvider.HISTORY_NODE_ID) { + return this.getHistoryItems(); + } + + // Individual session items don't have children + return []; + } + + private async getHistoryItems(): Promise { + try { + // Get all chat history + const allHistory = await this.chatService.getHistory(); + + // Create history items with provider reference and timestamps + const historyItems = allHistory.map((historyDetail: any): ChatSessionItemWithProvider => ({ + id: historyDetail.sessionId, + label: historyDetail.title, + iconPath: Codicon.chatSparkle, + provider: this.provider, + timing: { + startTime: historyDetail.lastMessageDate ?? Date.now() + }, + isHistory: true, + })); + + // Apply sorting and time grouping + processSessionsWithTimeGrouping(historyItems); + + return historyItems; + + } catch (error) { + return []; + } + } +} + + +export class SessionsDelegate implements IListVirtualDelegate { + static readonly ITEM_HEIGHT = 22; + static readonly ITEM_HEIGHT_WITH_DESCRIPTION = 44; // Slightly smaller for cleaner look + + constructor(private readonly configurationService: IConfigurationService) { } + + getHeight(element: ChatSessionItemWithProvider): number { + // Return consistent height for all items (single-line layout) + if (element.description && this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && element.provider.chatSessionType !== 'local') { + return SessionsDelegate.ITEM_HEIGHT_WITH_DESCRIPTION; + } else { + return SessionsDelegate.ITEM_HEIGHT; + } + } + + getTemplateId(element: ChatSessionItemWithProvider): string { + return SessionsRenderer.TEMPLATE_ID; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts new file mode 100644 index 00000000000..87e624fa5e0 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -0,0 +1,470 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from '../../../../../../base/browser/dom.js'; +import { append, $ } from '../../../../../../base/browser/dom.js'; +import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js'; +import { coalesce } from '../../../../../../base/common/arrays.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { FuzzyScore } from '../../../../../../base/common/filters.js'; +import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; +import { truncate } from '../../../../../../base/common/strings.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import * as nls from '../../../../../../nls.js'; +import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { WorkbenchAsyncDataTree, WorkbenchList } from '../../../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../../../platform/log/common/log.js'; +import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; +import { IProgressService } from '../../../../../../platform/progress/common/progress.js'; +import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; +import { fillEditorsDragData } from '../../../../../browser/dnd.js'; +import { ResourceLabels } from '../../../../../browser/labels.js'; +import { ViewPane, IViewPaneOptions } from '../../../../../browser/parts/views/viewPane.js'; +import { IViewDescriptorService } from '../../../../../common/views.js'; +import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; +import { IEditorService } from '../../../../../services/editor/common/editorService.js'; +import { IViewsService } from '../../../../../services/views/common/viewsService.js'; +import { IChatService } from '../../../common/chatService.js'; +import { IChatSessionItemProvider } from '../../../common/chatSessionsService.js'; +import { ChatSessionUri } from '../../../common/chatUri.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { IChatWidgetService, ChatViewId } from '../../chat.js'; +import { IChatEditorOptions } from '../../chatEditor.js'; +import { ChatEditorInput } from '../../chatEditorInput.js'; +import { ChatViewPane } from '../../chatViewPane.js'; +import { ChatSessionTracker } from '../chatSessionTracker.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem, getSessionItemContextOverlay } from '../common.js'; +import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; +import { GettingStartedDelegate, GettingStartedRenderer, IGettingStartedItem, SessionsDataSource, SessionsDelegate, SessionsRenderer } from './sessionsTreeRenderer.js'; + +// Identity provider for session items +class SessionsIdentityProvider { + getId(element: ChatSessionItemWithProvider): string { + return element.id; + } +} + +// Accessibility provider for session items +class SessionsAccessibilityProvider { + getWidgetAriaLabel(): string { + return nls.localize('chatSessions', 'Chat Sessions'); + } + + getAriaLabel(element: ChatSessionItemWithProvider): string | null { + return element.label || element.id; + } +} + + +export class SessionsViewPane extends ViewPane { + private tree: WorkbenchAsyncDataTree | undefined; + private list: WorkbenchList | undefined; + private treeContainer: HTMLElement | undefined; + private messageElement?: HTMLElement; + private _isEmpty: boolean = true; + + constructor( + private readonly provider: IChatSessionItemProvider, + private readonly sessionTracker: ChatSessionTracker, + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IChatService private readonly chatService: IChatService, + @IEditorService private readonly editorService: IEditorService, + @IViewsService private readonly viewsService: IViewsService, + @ILogService private readonly logService: ILogService, + @IProgressService private readonly progressService: IProgressService, + @IMenuService private readonly menuService: IMenuService, + @ICommandService private readonly commandService: ICommandService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); + + // Listen for changes in the provider if it's a LocalChatSessionsProvider + if (provider instanceof LocalChatSessionsProvider) { + this._register(provider.onDidChange(() => { + if (this.tree && this.isBodyVisible()) { + this.refreshTreeWithProgress(); + } + })); + } + + // Listen for configuration changes to refresh view when description display changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ChatConfiguration.ShowAgentSessionsViewDescription)) { + if (this.tree && this.isBodyVisible()) { + this.refreshTreeWithProgress(); + } + } + })); + } + + override shouldShowWelcome(): boolean { + return this._isEmpty; + } + + public refreshTree(): void { + if (this.tree && this.isBodyVisible()) { + this.refreshTreeWithProgress(); + } + } + + private isEmpty() { + // Check if the tree has the provider node and get its children count + if (!this.tree?.hasNode(this.provider)) { + return true; + } + const providerNode = this.tree.getNode(this.provider); + const childCount = providerNode.children?.length || 0; + + return childCount === 0; + } + + /** + * Updates the empty state message based on current tree data. + * Uses the tree's existing data to avoid redundant provider calls. + */ + private updateEmptyState(): void { + try { + const newEmptyState = this.isEmpty(); + if (newEmptyState !== this._isEmpty) { + this._isEmpty = newEmptyState; + this._onDidChangeViewWelcomeState.fire(); + } + } catch (error) { + this.logService.error('Error checking tree data for empty state:', error); + } + } + + /** + * Refreshes the tree data with progress indication. + * Shows a progress indicator while the tree updates its children from the provider. + */ + private async refreshTreeWithProgress(): Promise { + if (!this.tree) { + return; + } + + try { + await this.progressService.withProgress( + { + location: this.id, // Use the view ID as the progress location + title: nls.localize('chatSessions.refreshing', 'Refreshing chat sessions...'), + }, + async () => { + await this.tree!.updateChildren(this.provider); + } + ); + + // Check for empty state after refresh using tree data + this.updateEmptyState(); + } catch (error) { + // Log error but don't throw to avoid breaking the UI + this.logService.error('Error refreshing chat sessions tree:', error); + } + } + + /** + * Loads initial tree data with progress indication. + * Shows a progress indicator while the tree loads data from the provider. + */ + private async loadDataWithProgress(): Promise { + if (!this.tree) { + return; + } + + try { + await this.progressService.withProgress( + { + location: this.id, // Use the view ID as the progress location + title: nls.localize('chatSessions.loading', 'Loading chat sessions...'), + }, + async () => { + await this.tree!.setInput(this.provider); + } + ); + + // Check for empty state after loading using tree data + this.updateEmptyState(); + } catch (error) { + // Log error but don't throw to avoid breaking the UI + this.logService.error('Error loading chat sessions data:', error); + } + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + // For Getting Started view (null provider), show simple list + if (this.provider === null) { + this.renderGettingStartedList(container); + return; + } + + this.treeContainer = DOM.append(container, DOM.$('.chat-sessions-tree-container')); + // Create message element for empty state + this.messageElement = append(container, $('.chat-sessions-message')); + this.messageElement.style.display = 'none'; + // Create the tree components + const dataSource = new SessionsDataSource(this.provider, this.chatService, this.sessionTracker); + const delegate = new SessionsDelegate(this.configurationService); + const identityProvider = new SessionsIdentityProvider(); + const accessibilityProvider = new SessionsAccessibilityProvider(); + + // Use the existing ResourceLabels service for consistent styling + const labels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); + const renderer = this.instantiationService.createInstance(SessionsRenderer, labels); + this._register(renderer); + + const getResourceForElement = (element: ChatSessionItemWithProvider): URI | null => { + if (element.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { + return null; + } + + return ChatSessionUri.forSession(element.provider.chatSessionType, element.id); + }; + + this.tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'ChatSessions', + this.treeContainer, + delegate, + [renderer], + dataSource, + { + dnd: { + onDragStart: (data, originalEvent) => { + try { + const elements = data.getData() as ChatSessionItemWithProvider[]; + const uris = coalesce(elements.map(getResourceForElement)); + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent)); + } catch { + // noop + } + }, + getDragURI: (element: ChatSessionItemWithProvider) => { + if (element.id === LocalChatSessionsProvider.HISTORY_NODE_ID) { + return null; + } + return getResourceForElement(element)?.toString() ?? null; + }, + getDragLabel: (elements: ChatSessionItemWithProvider[]) => { + if (elements.length === 1) { + return elements[0].label; + } + return nls.localize('chatSessions.dragLabel', "{0} chat sessions", elements.length); + }, + drop: () => { }, + onDragOver: () => false, + dispose: () => { }, + }, + accessibilityProvider, + identityProvider, + multipleSelectionSupport: false, + overrideStyles: { + listBackground: undefined + }, + setRowLineHeight: false + + } + ) as WorkbenchAsyncDataTree; + + // Set the input + this.tree.setInput(this.provider); + + // Register tree events + this._register(this.tree.onDidOpen((e) => { + if (e.element) { + this.openChatSession(e.element); + } + })); + + // Register context menu event for right-click actions + this._register(this.tree.onContextMenu((e) => { + if (e.element && e.element.id !== LocalChatSessionsProvider.HISTORY_NODE_ID) { + this.showContextMenu(e); + } + })); + + // Handle visibility changes to load data + this._register(this.onDidChangeBodyVisibility(async visible => { + if (visible && this.tree) { + await this.loadDataWithProgress(); + } + })); + + // Initially load data if visible + if (this.isBodyVisible() && this.tree) { + this.loadDataWithProgress(); + } + + this._register(this.tree); + } + + private renderGettingStartedList(container: HTMLElement): void { + const listContainer = DOM.append(container, DOM.$('.getting-started-list-container')); + const items: IGettingStartedItem[] = [ + { + id: 'install-extensions', + label: nls.localize('chatSessions.installExtensions', "Install Chat Extensions"), + icon: Codicon.extensions, + commandId: 'chat.sessions.gettingStarted' + }, + { + id: 'learn-more', + label: nls.localize('chatSessions.learnMoreGHCodingAgent', "Learn More About GitHub Copilot coding agent"), + commandId: 'vscode.open', + icon: Codicon.book, + args: [URI.parse('https://aka.ms/coding-agent-docs')] + } + ]; + const delegate = new GettingStartedDelegate(); + + // Create ResourceLabels instance for the renderer + const labels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); + this._register(labels); + + const renderer = new GettingStartedRenderer(labels); + this.list = this.instantiationService.createInstance( + WorkbenchList, + 'GettingStarted', + listContainer, + delegate, + [renderer], + { + horizontalScrolling: false, + } + ); + this.list.splice(0, 0, items); + this._register(this.list.onDidOpen(e => { + if (e.element) { + this.commandService.executeCommand(e.element.commandId, ...e.element.args ?? []); + } + })); + + this._register(this.list); + } + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + if (this.tree) { + this.tree.layout(height, width); + } + if (this.list) { + this.list.layout(height, width); + } + } + + private async openChatSession(session: ChatSessionItemWithProvider) { + if (!session || !session.id) { + return; + } + + try { + // Check first if we already have an open editor for this session + const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + const existingEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); + if (existingEditor) { + await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); + return; + } + if (this.chatWidgetService.getWidgetBySessionId(session.id)) { + return; + } + + if (session.id === LocalChatSessionsProvider.HISTORY_NODE_ID) { + // Don't try to open the "Show history..." node itself + return; + } + + // Handle history items first + if (isLocalChatSessionItem(session)) { + const options: IChatEditorOptions = { + target: { sessionId: session.id }, + pinned: true, + ignoreInView: true, + preserveFocus: true, + }; + await this.editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); + return; + } else if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { + const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; + if (chatViewPane) { + await chatViewPane.loadSession(session.id); + } + return; + } + + const options: IChatEditorOptions = { + pinned: true, + ignoreInView: true, + preferredTitle: truncate(session.label, 30), + preserveFocus: true, + }; + await this.editorService.openEditor({ + resource: ChatSessionUri.forSession(session.provider.chatSessionType, session.id), + options, + }); + + } catch (error) { + this.logService.error('[SessionsViewPane] Failed to open chat session:', error); + } + } + + private showContextMenu(e: ITreeContextMenuEvent) { + if (!e.element) { + return; + } + + const session = e.element; + const sessionWithProvider = session as ChatSessionItemWithProvider; + + // Create context overlay for this specific session item + const contextOverlay = getSessionItemContextOverlay( + session, + sessionWithProvider.provider, + this.chatWidgetService, + this.chatService, + this.editorGroupsService + ); + const contextKeyService = this.contextKeyService.createOverlay(contextOverlay); + + // Create marshalled context for command execution + const marshalledSession = { + session: session, + $mid: MarshalledId.ChatSessionContext + }; + + // Create menu for this session item to get actions + const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService); + + // Get actions and filter for context menu (all actions that are NOT inline) + const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }); + + const { secondary } = getActionBarActions(actions, 'inline'); this.contextMenuService.showContextMenu({ + getActions: () => secondary, + getAnchor: () => e.anchor, + getActionsContext: () => marshalledSession, + }); + + menu.dispose(); + } +} From 30706e08001c88706e8ae2b1477fe88bff3cd5b8 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 11 Sep 2025 20:51:23 -0700 Subject: [PATCH 0241/4355] tools: allow starting MCP servers from the tools picker (#266277) - Makes `onDidAccept` in the tree picker work (it previously no-op'd) - Make `pickable: false` work correctly. - Update the tree to let it recompute its entries within the tree - Swapped `toolsEntries` to a getter since the map needs to be re-retrieved after the MCP server is available, or tools don't show. --- .../browser/quickInputController.ts | 4 + .../browser/tree/quickInputTreeController.ts | 17 + .../browser/tree/quickInputTreeRenderer.ts | 15 +- .../quickinput/browser/tree/quickTree.ts | 4 + .../platform/quickinput/common/quickInput.ts | 6 + .../chat/browser/actions/chatToolActions.ts | 2 +- .../chat/browser/actions/chatToolPicker.ts | 405 ++++++++++-------- .../promptToolsCodeLensProvider.ts | 2 +- .../contrib/mcp/common/mcpTypesUtils.ts | 16 +- 9 files changed, 284 insertions(+), 187 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 701a4d66f24..12e5f0b11a3 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -265,6 +265,10 @@ export class QuickInputController extends Disposable { tree.tree.setFocus([]); }, 0); })); + // Wire up tree's accept event to the UI's accept emitter for non-pickable items + this._register(tree.onDidAccept(() => { + this.onDidAcceptEmitter.fire(); + })); this._register(tree.tree.onDidChangeContentHeight(() => this.updateLayout())); const focusTracker = dom.trackFocus(container); diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts index 2ec3e112662..fdb0e64d7ff 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts @@ -40,6 +40,12 @@ export class QuickInputTreeController extends Disposable { */ readonly onLeave: Event = this._onLeave.event; + private readonly _onDidAccept = this._register(new Emitter()); + /** + * Event that is fired when a non-pickable item is clicked, indicating acceptance. + */ + readonly onDidAccept: Event = this._onDidAccept.event; + private readonly _container: HTMLElement; constructor( @@ -239,6 +245,13 @@ export class QuickInputTreeController extends Disposable { if (item.disabled) { return; } + // Check if the item is pickable (defaults to true if not specified) + if (item.pickable === false) { + // For non-pickable items, set it as the active item and fire the accept event + this._tree.setFocus([item]); + this._onDidAccept.fire(); + return; + } const newState = item.checked !== true; if ((item.checked ?? false) === newState) { @@ -305,6 +318,10 @@ export class QuickInputTreeController extends Disposable { return checkedItems; } + getActiveItems(): readonly IQuickTreeItem[] { + return this._tree.getFocus().filter((item): item is IQuickTreeItem => item !== null); + } + check(element: IQuickTreeItem, checked: boolean | 'partial') { if (element.checked === checked) { return; diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeRenderer.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeRenderer.ts index 89f10d208bb..9e8f9335798 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeRenderer.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeRenderer.ts @@ -83,11 +83,16 @@ export class QuickInputTreeRenderer extends Disposable const quickTreeItem = node.element; // Checkbox - templateData.checkbox.domNode.style.display = ''; - templateData.checkbox.checked = quickTreeItem.checked ?? false; - store.add(Event.filter(this.onCheckedEvent, e => e.item === quickTreeItem)(e => templateData.checkbox.checked = e.checked)); - if (quickTreeItem.disabled) { - templateData.checkbox.disable(); + if (quickTreeItem.pickable === false) { + // Hide checkbox for non-pickable items + templateData.checkbox.domNode.style.display = 'none'; + } else { + templateData.checkbox.domNode.style.display = ''; + templateData.checkbox.checked = quickTreeItem.checked ?? false; + store.add(Event.filter(this.onCheckedEvent, e => e.item === quickTreeItem)(e => templateData.checkbox.checked = e.checked)); + if (quickTreeItem.disabled) { + templateData.checkbox.disable(); + } } // Icon diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 6d40d973574..e4688efd031 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -38,6 +38,10 @@ export class QuickTree extends QuickInput implements I this.onDidAccept = ui.onDidAccept; this._registerAutoruns(); this._register(ui.tree.onDidChangeCheckedLeafItems(e => this._onDidChangeCheckedLeafItems.fire(e as T[]))); + // Sync active items with tree focus changes + this._register(ui.tree.tree.onDidChangeFocus(e => { + this._activeItems.set(ui.tree.getActiveItems() as T[], undefined); + })); } get value(): string { return this._value.get(); } diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 4b6f0e42122..45c7a430f74 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -1167,6 +1167,12 @@ export interface IQuickTreeItem extends IQuickItem { * The children of this tree item. */ children?: readonly IQuickTreeItem[]; + + /** + * Defaults to true, can be false to disable picks for a single item. + * When false, the item is not selectable and does not respond to mouse/keyboard activation. + */ + pickable?: boolean; } /** diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index 9c243e3f566..22e3affb8a4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -136,7 +136,7 @@ class ConfigureToolsAction extends Action2 { break; } - const result = await instaService.invokeFunction(showToolsPicker, placeholder, description, entriesMap.get()); + const result = await instaService.invokeFunction(showToolsPicker, placeholder, description, () => entriesMap.get()); if (result) { widget.input.selectedToolsModel.set(result, false); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index f6a92eb77f0..b52fc028ed0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -4,26 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { assertNever } from '../../../../../base/common/assert.js'; import { Codicon } from '../../../../../base/common/codicons.js'; -import { Event } from '../../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { markdownCommandLink } from '../../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import Severity from '../../../../../base/common/severity.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickTreeItem } from '../../../../../platform/quickinput/common/quickInput.js'; -import { URI } from '../../../../../base/common/uri.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ExtensionEditorTab, IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; import { McpCommandIds } from '../../../mcp/common/mcpCommandIds.js'; import { IMcpRegistry } from '../../../mcp/common/mcpRegistryTypes.js'; -import { IMcpServer, IMcpService, IMcpWorkbenchService, McpConnectionState, McpServerEditorTab } from '../../../mcp/common/mcpTypes.js'; +import { IMcpServer, IMcpService, IMcpWorkbenchService, McpConnectionState, McpServerCacheState, McpServerEditorTab } from '../../../mcp/common/mcpTypes.js'; +import { startServerAndWaitForLiveTools } from '../../../mcp/common/mcpTypesUtils.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { ILanguageModelToolsService, IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { ConfigureToolSets } from '../tools/toolSetsContribution.js'; -import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import Severity from '../../../../../base/common/severity.js'; -import { markdownCommandLink } from '../../../../../base/common/htmlContent.js'; const enum BucketOrdinal { User, BuiltIn, Mcp, Extension } @@ -81,11 +82,12 @@ interface IToolTreeItemData extends IToolTreeItem { /** * Callback tree item - represents action items like "Add MCP Server" or "Configure Tool Sets". - * These are non-selectable items that execute actions when clicked. + * These are non-selectable items that execute actions when clicked. Can return + * false to keep the picker open. */ interface ICallbackTreeItem extends IToolTreeItem { readonly itemType: 'callback'; - readonly run: () => void; + readonly run: () => boolean | void; readonly pickable: false; } @@ -187,7 +189,7 @@ export async function showToolsPicker( accessor: ServicesAccessor, placeHolder: string, description?: string, - toolsEntries?: ReadonlyMap + getToolsEntries?: () => ReadonlyMap ): Promise | undefined> { const quickPickService = accessor.get(IQuickInputService); @@ -207,178 +209,226 @@ export async function showToolsPicker( } } - // Create default entries if none provided - if (!toolsEntries) { - const defaultEntries = new Map(); - for (const tool of toolsService.getTools()) { - if (tool.canBeReferencedInPrompt) { - defaultEntries.set(tool, false); + function computeItems(previousToolsEntries?: ReadonlyMap) { + // Create default entries if none provided + let toolsEntries = getToolsEntries ? new Map(getToolsEntries()) : undefined; + if (!toolsEntries) { + const defaultEntries = new Map(); + for (const tool of toolsService.getTools()) { + if (tool.canBeReferencedInPrompt) { + defaultEntries.set(tool, false); + } } + for (const toolSet of toolsService.toolSets.get()) { + defaultEntries.set(toolSet, false); + } + toolsEntries = defaultEntries; } - for (const toolSet of toolsService.toolSets.get()) { - defaultEntries.set(toolSet, false); - } - toolsEntries = defaultEntries; - } - - // Build tree structure - const treeItems: AnyTreeItem[] = []; - const bucketMap = new Map(); - - const getKey = (source: ToolDataSource): string => { - switch (source.type) { - case 'mcp': - case 'extension': - return ToolDataSource.toKey(source); - case 'internal': - return BucketOrdinal.BuiltIn.toString(); - case 'user': - return BucketOrdinal.User.toString(); - case 'external': - throw new Error('should not be reachable'); - default: - assertNever(source); - } - }; + previousToolsEntries?.forEach((value, key) => { + toolsEntries.set(key, value); + }); - const createBucket = (source: ToolDataSource, key: string): IBucketTreeItem | undefined => { - if (source.type === 'mcp') { - const { definitionId } = source; - const mcpServer = mcpService.servers.get().find(candidate => candidate.definition.id === definitionId); - if (!mcpServer) { - return undefined; + // Build tree structure + const treeItems: AnyTreeItem[] = []; + const bucketMap = new Map(); + + const getKey = (source: ToolDataSource): string => { + switch (source.type) { + case 'mcp': + case 'extension': + return ToolDataSource.toKey(source); + case 'internal': + return BucketOrdinal.BuiltIn.toString(); + case 'user': + return BucketOrdinal.User.toString(); + case 'external': + throw new Error('should not be reachable'); + default: + assertNever(source); } + }; - const buttons: ActionableButton[] = []; - const collection = mcpRegistry.collections.get().find(c => c.id === mcpServer.collection.id); - if (collection?.source) { - buttons.push({ - iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('configMcpCol', "Configure {0}", collection.label), - action: () => collection.source ? collection.source instanceof ExtensionIdentifier ? extensionsWorkbenchService.open(collection.source.value, { tab: ExtensionEditorTab.Features, feature: 'mcp' }) : mcpWorkbenchService.open(collection.source, { tab: McpServerEditorTab.Configuration }) : undefined - }); - } else if (collection?.presentation?.origin) { - buttons.push({ - iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('configMcpCol', "Configure {0}", collection.label), - action: () => editorService.openEditor({ - resource: collection!.presentation!.origin, - }) - }); - } - if (mcpServer.connectionState.get().state === McpConnectionState.Kind.Error) { - buttons.push({ - iconClass: ThemeIcon.asClassName(Codicon.warning), - tooltip: localize('mcpShowOutput', "Show Output"), - action: () => mcpServer.showOutput(), - }); + const mcpServers = new Map(mcpService.servers.get().map(s => [s.definition.id, { server: s, seen: false }])); + const createBucket = (source: ToolDataSource, key: string): IBucketTreeItem | undefined => { + if (source.type === 'mcp') { + const mcpServerEntry = mcpServers.get(source.definitionId); + if (!mcpServerEntry) { + return undefined; + } + mcpServerEntry.seen = true; + const mcpServer = mcpServerEntry.server; + const buttons: ActionableButton[] = []; + const collection = mcpRegistry.collections.get().find(c => c.id === mcpServer.collection.id); + if (collection?.source) { + buttons.push({ + iconClass: ThemeIcon.asClassName(Codicon.settingsGear), + tooltip: localize('configMcpCol', "Configure {0}", collection.label), + action: () => collection.source ? collection.source instanceof ExtensionIdentifier ? extensionsWorkbenchService.open(collection.source.value, { tab: ExtensionEditorTab.Features, feature: 'mcp' }) : mcpWorkbenchService.open(collection.source, { tab: McpServerEditorTab.Configuration }) : undefined + }); + } else if (collection?.presentation?.origin) { + buttons.push({ + iconClass: ThemeIcon.asClassName(Codicon.settingsGear), + tooltip: localize('configMcpCol', "Configure {0}", collection.label), + action: () => editorService.openEditor({ + resource: collection!.presentation!.origin, + }) + }); + } + if (mcpServer.connectionState.get().state === McpConnectionState.Kind.Error) { + buttons.push({ + iconClass: ThemeIcon.asClassName(Codicon.warning), + tooltip: localize('mcpShowOutput', "Show Output"), + action: () => mcpServer.showOutput(), + }); + } + const cacheState = mcpServer.cacheState.get(); + const children: AnyTreeItem[] = []; + let collapsed = true; + if (cacheState === McpServerCacheState.Unknown || cacheState === McpServerCacheState.Outdated) { + collapsed = false; + children.push({ + itemType: 'callback', + iconClass: ThemeIcon.asClassName(Codicon.sync), + label: localize('mcpUpdate', "Update Tools"), + pickable: false, + run: () => { + treePicker.busy = true; + (async () => { + const ok = await startServerAndWaitForLiveTools(mcpServer, { promptType: 'all-untrusted' }); + if (!ok) { + mcpServer.showOutput(); + treePicker.hide(); + return; + } + treePicker.busy = false; + computeItems(collectResults()); + })(); + return false; + }, + }); + } + return { + itemType: 'bucket', + ordinal: BucketOrdinal.Mcp, + id: key, + label: localize('mcplabel', "MCP Server: {0}", source.label), + checked: undefined, + collapsed, + children, + buttons, + iconClass: ThemeIcon.asClassName(Codicon.mcp) + }; + } else if (source.type === 'extension') { + return { + itemType: 'bucket', + ordinal: BucketOrdinal.Extension, + id: key, + label: localize('ext', 'Extension: {0}', source.label), + checked: undefined, + children: [], + buttons: [], + collapsed: true, + iconClass: ThemeIcon.asClassName(Codicon.extensions) + }; + } else if (source.type === 'internal') { + return { + itemType: 'bucket', + ordinal: BucketOrdinal.BuiltIn, + id: key, + label: localize('defaultBucketLabel', "Built-In"), + checked: undefined, + children: [], + buttons: [], + collapsed: false + }; + } else { + return { + itemType: 'bucket', + ordinal: BucketOrdinal.User, + id: key, + label: localize('userBucket', "User Defined Tool Sets"), + checked: undefined, + children: [], + buttons: [], + collapsed: true + }; } - return { - itemType: 'bucket', - ordinal: BucketOrdinal.Mcp, - id: key, - label: localize('mcplabel', "MCP Server: {0}", source.label), - checked: undefined, - collapsed: true, - children: [], - buttons, - iconClass: ThemeIcon.asClassName(Codicon.mcp) - }; - } else if (source.type === 'extension') { - return { - itemType: 'bucket', - ordinal: BucketOrdinal.Extension, - id: key, - label: localize('ext', 'Extension: {0}', source.label), - checked: undefined, - children: [], - buttons: [], - collapsed: true, - iconClass: ThemeIcon.asClassName(Codicon.extensions) - }; - } else if (source.type === 'internal') { - return { - itemType: 'bucket', - ordinal: BucketOrdinal.BuiltIn, - id: key, - label: localize('defaultBucketLabel', "Built-In"), - checked: undefined, - children: [], - buttons: [], - collapsed: false - }; - } else { - return { - itemType: 'bucket', - ordinal: BucketOrdinal.User, - id: key, - label: localize('userBucket', "User Defined Tool Sets"), - checked: undefined, - children: [], - buttons: [], - collapsed: true - }; - } - }; + }; - const getBucket = (source: ToolDataSource): IBucketTreeItem | undefined => { - const key = getKey(source); - let bucket = bucketMap.get(key); - if (!bucket) { - bucket = createBucket(source, key); - if (bucket) { - bucketMap.set(key, bucket); + const getBucket = (source: ToolDataSource): IBucketTreeItem | undefined => { + const key = getKey(source); + let bucket = bucketMap.get(key); + if (!bucket) { + bucket = createBucket(source, key); + if (bucket) { + bucketMap.set(key, bucket); + } } - } - return bucket; - }; + return bucket; + }; - for (const toolSet of toolsService.toolSets.get()) { - if (!toolsEntries.has(toolSet)) { - continue; - } - const bucket = getBucket(toolSet.source); - if (!bucket) { - continue; - } - const toolSetChecked = toolsEntries.get(toolSet) === true; - if (toolSet.source.type === 'mcp') { - // bucket represents the toolset - bucket.toolset = toolSet; - if (toolSetChecked) { - bucket.checked = toolSetChecked; + for (const toolSet of toolsService.toolSets.get()) { + if (!toolsEntries.has(toolSet)) { + continue; } - // all mcp tools are part of toolsService.getTools() - } else { - const treeItem = createToolSetTreeItem(toolSet, toolSetChecked, editorService); - bucket.children.push(treeItem); - const children = []; - for (const tool of toolSet.getTools()) { - const toolChecked = toolSetChecked || toolsEntries.get(tool) === true; - const toolTreeItem = createToolTreeItemFromData(tool, toolChecked); - children.push(toolTreeItem); + const bucket = getBucket(toolSet.source); + if (!bucket) { + continue; } - if (children.length > 0) { - treeItem.children = children; + const toolSetChecked = toolsEntries.get(toolSet) === true; + if (toolSet.source.type === 'mcp') { + // bucket represents the toolset + bucket.toolset = toolSet; + if (toolSetChecked) { + bucket.checked = toolSetChecked; + } + // all mcp tools are part of toolsService.getTools() + } else { + const treeItem = createToolSetTreeItem(toolSet, toolSetChecked, editorService); + bucket.children.push(treeItem); + const children = []; + for (const tool of toolSet.getTools()) { + const toolChecked = toolSetChecked || toolsEntries.get(tool) === true; + const toolTreeItem = createToolTreeItemFromData(tool, toolChecked); + children.push(toolTreeItem); + } + if (children.length > 0) { + treeItem.children = children; + } } } - } - for (const tool of toolsService.getTools()) { - if (!tool.canBeReferencedInPrompt || !toolsEntries.has(tool)) { - continue; + for (const tool of toolsService.getTools()) { + if (!tool.canBeReferencedInPrompt || !toolsEntries.has(tool)) { + continue; + } + const bucket = getBucket(tool.source); + if (!bucket) { + continue; + } + const toolChecked = bucket.checked === true || toolsEntries.get(tool) === true; + const toolTreeItem = createToolTreeItemFromData(tool, toolChecked); + bucket.children.push(toolTreeItem); } - const bucket = getBucket(tool.source); - if (!bucket) { - continue; + + // Show entries for MCP servers that don't have any tools in them and might need to be started. + for (const { server, seen } of mcpServers.values()) { + const cacheState = server.cacheState.get(); + if (!seen && (cacheState === McpServerCacheState.Unknown || cacheState === McpServerCacheState.Outdated)) { + getBucket({ type: 'mcp', definitionId: server.definition.id, label: server.definition.label, instructions: '', serverLabel: '', collectionId: server.collection.id }); + } } - const toolChecked = bucket.checked === true || toolsEntries.get(tool) === true; - const toolTreeItem = createToolTreeItemFromData(tool, toolChecked); - bucket.children.push(toolTreeItem); - } - // Convert bucket map to sorted tree items - const sortedBuckets = Array.from(bucketMap.values()).sort((a, b) => a.ordinal - b.ordinal); - treeItems.push(...sortedBuckets); + // Convert bucket map to sorted tree items + const sortedBuckets = Array.from(bucketMap.values()).sort((a, b) => a.ordinal - b.ordinal); + treeItems.push(...sortedBuckets); + + if (treeItems.length === 0) { + treePicker.placeholder = localize('noTools', "Add tools to chat"); + } else { + treePicker.placeholder = placeHolder; + } + treePicker.setItemTree(treeItems); + } // Create and configure the tree picker const store = new DisposableStore(); @@ -390,11 +440,8 @@ export async function showToolsPicker( treePicker.matchOnDescription = true; treePicker.matchOnLabel = true; - if (treeItems.length === 0) { - treePicker.placeholder = localize('noTools', "Add tools to chat"); - } - treePicker.setItemTree(treeItems); + computeItems(); // Handle button triggers store.add(treePicker.onDidTriggerItemButton(e => { @@ -418,7 +465,7 @@ export async function showToolsPicker( } } }; - traverse(treeItems); + traverse(treePicker.itemTree); if (count > toolLimit) { treePicker.severity = Severity.Warning; treePicker.validationMessage = localize('toolLimitExceeded', "{0} tools are enabled. You may experience degraded tool calling above {1} tools.", count, markdownCommandLink({ title: String(toolLimit), id: '_chat.toolPicker.closeAndOpenVirtualThreshold' })); @@ -453,7 +500,7 @@ export async function showToolsPicker( } }; - traverse(treeItems); + traverse(treePicker.itemTree); return result; }; @@ -471,14 +518,20 @@ export async function showToolsPicker( // Handle acceptance let didAccept = false; + const didAcceptFinalItem = store.add(new Emitter()); store.add(treePicker.onDidAccept(() => { // Check if a callback item was activated const activeItems = treePicker.activeItems; const callbackItem = activeItems.find(isCallbackTreeItem); - if (callbackItem) { - callbackItem.run(); - } else { + if (!callbackItem) { didAccept = true; + treePicker.hide(); + return; + } + + const ret = callbackItem.run(); + if (ret !== false) { + didAcceptFinalItem.fire(); } })); @@ -509,7 +562,7 @@ export async function showToolsPicker( treePicker.show(); - await Promise.race([Event.toPromise(Event.any(treePicker.onDidAccept, treePicker.onDidHide), store)]); + await Promise.race([Event.toPromise(Event.any(treePicker.onDidHide, didAcceptFinalItem.event), store)]); store.dispose(); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 904ff2dff6f..4b7832b0ee1 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -81,7 +81,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider private async updateTools(model: ITextModel, tools: PromptToolsMetadata) { - const selectedToolsNow = tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(tools.value) : new Map(); + const selectedToolsNow = () => tools.value ? this.languageModelToolsService.toToolAndToolSetEnablementMap(tools.value) : new Map(); const newSelectedAfter = await this.instantiationService.invokeFunction(showToolsPicker, localize('placeholder', "Select tools"), undefined, selectedToolsNow); if (!newSelectedAfter) { return; diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypesUtils.ts b/src/vs/workbench/contrib/mcp/common/mcpTypesUtils.ts index 59993aa6e01..61a7223fb25 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypesUtils.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypesUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { disposableTimeout } from '../../../../base/common/async.js'; +import { disposableTimeout, timeout } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../base/common/errors.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; @@ -45,9 +45,10 @@ export function startServerByFilter(mcpService: IMcpService, filter: (s: IMcpSer * Starts a server (if needed) and waits for its tools to be live. Returns * true/false whether this happened successfully. */ -export function startServerAndWaitForLiveTools(server: IMcpServer, opts?: IMcpServerStartOpts, token?: CancellationToken): Promise { +export async function startServerAndWaitForLiveTools(server: IMcpServer, opts?: IMcpServerStartOpts, token?: CancellationToken): Promise { const store = new DisposableStore(); - return new Promise(resolve => { + + const ok = await new Promise(resolve => { server.start(opts).catch(() => undefined).then(r => { if (token?.isCancellationRequested || !r || r.state === McpConnectionState.Kind.Error || r.state === McpConnectionState.Kind.Stopped) { return resolve(false); @@ -71,7 +72,14 @@ export function startServerAndWaitForLiveTools(server: IMcpServer, opts?: IMcpSe } })); }); - }).finally(() => store.dispose()); + }); + store.dispose(); + + if (ok) { + await timeout(0); // let the tools register in the language model contribution + } + + return ok; } export function mcpServerToSourceData(server: IMcpServer): ToolDataSource { From af0b5ba59c957c58c840654fe9015b79f2135033 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Sep 2025 06:19:06 +0200 Subject: [PATCH 0242/4355] smoke - use `workbench.action.clearEditorHistoryWithoutConfirm` consistently" (#266249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Revert "smoke - use `workbench.action.clearEditorHistoryWithoutConfir…" This reverts commit c8f1c4eca9321223ffec32ea984e93d3601e4e23. --- test/automation/src/quickaccess.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index d417a4a2c29..2c7c4054252 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -7,7 +7,6 @@ import { Editors } from './editors'; import { Code } from './code'; import { QuickInput } from './quickinput'; import { basename, isAbsolute } from 'path'; -import { Quality } from './application'; enum QuickAccessKind { Files = 1, @@ -23,11 +22,7 @@ export class QuickAccess { // make sure the file quick access is not "polluted" // with entries from the editor history when opening - if (this.code.quality !== Quality.Stable) { - await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); - } else { - await this.runCommand('workbench.action.clearEditorHistory'); - } + await this.runCommand('workbench.action.clearEditorHistoryWithoutConfirm'); const PollingStrategy = { Stop: true, From c80653b37a127561244eca82a27f8618b242061f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 11 Sep 2025 21:19:34 -0700 Subject: [PATCH 0243/4355] Don't force chat session widgets into ask mode (#266308) In theory, could cause #265802. I don't see that actually happening, but this shouldn't have any effect anyway --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 652a7929c43..611b9c0d47b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2066,7 +2066,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this._lockedAgentId = agentId; this._lockedToCodingAgentContextKey.set(true); this.renderWelcomeViewContentIfNeeded(); - this.input.setChatMode(ChatModeKind.Ask); this.renderer.updateOptions({ restorable: false, editable: false, noFooter: true, progressMessageAtBottomOfResponse: true }); this.tree.rerender(); } From 2a2f830103fd0f9b46ea56b203bc28e2e22541e9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 11 Sep 2025 21:24:55 -0700 Subject: [PATCH 0244/4355] Don't keep all chat model initialdata in memory unnecessarily (#266306) When only a couple props were being used --- src/vs/workbench/contrib/chat/common/chatModel.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 22e4c08044e..0cceee9ea4f 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1375,14 +1375,16 @@ export class ChatModel extends Disposable implements IChatModel { return this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, ChatModeKind.Ask); } + private readonly _initialRequesterUsername: string | undefined; get requesterUsername(): string { return this._defaultAgent?.metadata.requester?.name ?? - this.initialData?.requesterUsername ?? ''; + this._initialRequesterUsername ?? ''; } + private readonly _initialResponderUsername: string | undefined; get responderUsername(): string { return this._defaultAgent?.fullName ?? - this.initialData?.responderUsername ?? ''; + this._initialResponderUsername ?? ''; } private readonly _initialRequesterAvatarIconUri: URI | undefined; @@ -1431,7 +1433,7 @@ export class ChatModel extends Disposable implements IChatModel { } constructor( - private readonly initialData: ISerializableChatData | IExportableChatData | undefined, + initialData: ISerializableChatData | IExportableChatData | undefined, initialModelProps: { initialLocation: ChatAgentLocation; inputType?: string }, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @@ -1451,6 +1453,8 @@ export class ChatModel extends Disposable implements IChatModel { this._lastMessageDate = (isValid && initialData.lastMessageDate) || this._creationDate; this._customTitle = isValid ? initialData.customTitle : undefined; + this._initialRequesterUsername = initialData?.requesterUsername; + this._initialResponderUsername = initialData?.responderUsername; this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; From d0eb93d312f8cbd2fe7ebaddf7d3fb3c20ed1cfe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Sep 2025 11:10:11 +0200 Subject: [PATCH 0245/4355] Chat: telemetry for `alreadyInstalled` chat install kind missing (#266334) (#266336) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 7bc3398dccd..714e80e0ab2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1512,7 +1512,7 @@ class ChatSetupController extends Disposable { return false; } - if (typeof signUpResult === 'boolean') { + if (typeof signUpResult === 'boolean' /* not an error case */ || typeof signUpResult === 'undefined' /* already signed up */) { this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult: wasRunning && !signUpResult ? 'alreadyInstalled' : 'installed', installDuration: watch.elapsed(), signUpErrorCode: undefined, provider }); } From b80398ac19f94c2e7d71fa6d013588353348ca10 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 12 Sep 2025 03:03:22 -0700 Subject: [PATCH 0246/4355] Remove suggest from workspace settings --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3158631f44b..c618b45f2ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -214,7 +214,6 @@ "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" }, - "terminal.integrated.suggest.enabled": true, // "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654 "editor.aiStats.enabled": true, // Team selfhosting on ai stats From 19f868fd9a7e6244e924cadafdefbe49ba128524 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Sep 2025 12:26:18 +0200 Subject: [PATCH 0247/4355] chat - more support for anonymous entitlements (#266343) --- .../contrib/chat/browser/chatSetup.ts | 84 ++++++++++++++----- .../chat/common/chatEntitlementService.ts | 21 +---- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 714e80e0ab2..25e10e96730 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -206,6 +206,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { private static readonly SETUP_NEEDED_MESSAGE = new MarkdownString(localize('settingUpCopilotNeeded', "You need to set up GitHub Copilot and be signed in to use Chat.")); private static readonly TRUST_NEEDED_MESSAGE = new MarkdownString(localize('trustNeeded', "You need to trust this workspace to use Chat.")); + private static readonly CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY = 'chat.allowAnonymousAccess'; + private readonly _onUnresolvableError = this._register(new Emitter()); readonly onUnresolvableError = this._onUnresolvableError.event; @@ -441,8 +443,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ - disableChatViewReveal: true, // we are already in a chat context - allowAnonymous: this.location === ChatAgentLocation.Chat, // allow anonymous based on location (TODO@bpasero expand this to more locations) + disableChatViewReveal: true, // we are already in a chat context + forceAnonymous: this.shouldForceAnonymous() // gate anonymous access behind some conditions }); } catch (error) { this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`); @@ -480,6 +482,26 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { return {}; } + private shouldForceAnonymous(): boolean { + if (this.configurationService.getValue(SetupAgent.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) !== true) { + return false; // only enabled behind an experimental setting + } + + if (this.context.state.entitlement !== ChatEntitlement.Unknown || this.context.state.registered) { + return false; // only consider signed out users that never registered before + } + + if (ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.provider.enterprise.id) { + return false; // disable for enterprise users + } + + if (this.location !== ChatAgentLocation.Chat) { + return false; // currently only supported from Chat (TODO@bpasero expand this to more locations) + } + + return true; + } + private replaceAgentInRequestModel(requestModel: IChatRequestModel, chatAgentService: IChatAgentService): IChatRequestModel { const agentPart = requestModel.message.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); if (!agentPart) { @@ -653,7 +675,7 @@ class ChatSetup { this.skipDialogOnce = true; } - async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { + async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { if (this.pendingRun) { return this.pendingRun; } @@ -667,7 +689,7 @@ class ChatSetup { } } - private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; allowAnonymous?: boolean }): Promise { + private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { this.context.update({ later: false }); const dialogSkipped = this.skipDialogOnce; @@ -686,8 +708,8 @@ class ChatSetup { let setupStrategy: ChatSetupStrategy; if (!options?.forceSignInDialog && (dialogSkipped || isProUser(this.chatEntitlementService.entitlement) || this.chatEntitlementService.entitlement === ChatEntitlement.Free)) { setupStrategy = ChatSetupStrategy.DefaultSetup; // existing pro/free users setup without a dialog - } else if (options?.allowAnonymous && this.chatEntitlementService.entitlement === ChatEntitlement.Anonymous) { - setupStrategy = ChatSetupStrategy.DefaultSetup; + } else if (options?.forceAnonymous) { + setupStrategy = ChatSetupStrategy.DefaultSetup; // anonymous users setup without a dialog } else { setupStrategy = await this.showDialog(options); } @@ -706,19 +728,19 @@ class ChatSetup { try { switch (setupStrategy) { case ChatSetupStrategy.SetupWithEnterpriseProvider: - success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: true, useSocialProvider: undefined, additionalScopes: options?.additionalScopes }); + success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: true, useSocialProvider: undefined, additionalScopes: options?.additionalScopes, forceAnonymous: options?.forceAnonymous }); break; case ChatSetupStrategy.SetupWithoutEnterpriseProvider: - success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: undefined, additionalScopes: options?.additionalScopes }); + success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: undefined, additionalScopes: options?.additionalScopes, forceAnonymous: options?.forceAnonymous }); break; case ChatSetupStrategy.SetupWithAppleProvider: - success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: 'apple', additionalScopes: options?.additionalScopes }); + success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: 'apple', additionalScopes: options?.additionalScopes, forceAnonymous: options?.forceAnonymous }); break; case ChatSetupStrategy.SetupWithGoogleProvider: - success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: 'google', additionalScopes: options?.additionalScopes }); + success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false, useSocialProvider: 'google', additionalScopes: options?.additionalScopes, forceAnonymous: options?.forceAnonymous }); break; case ChatSetupStrategy.DefaultSetup: - success = await this.controller.value.setup(options); + success = await this.controller.value.setup({ ...options, forceAnonymous: options?.forceAnonymous }); break; case ChatSetupStrategy.Canceled: this.context.update({ later: true }); @@ -930,7 +952,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr }); } - override async run(accessor: ServicesAccessor, mode?: ChatModeKind, options?: { forceSignInDialog?: boolean; forceNoDialog?: boolean; additionalScopes?: readonly string[] }): Promise { + override async run(accessor: ServicesAccessor, mode?: ChatModeKind, options?: { forceSignInDialog?: boolean; forceNoDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { const viewsService = accessor.get(IViewsService); const layoutService = accessor.get(IWorkbenchLayoutService); const instantiationService = accessor.get(IInstantiationService); @@ -1349,6 +1371,14 @@ enum ChatSetupStep { Installing } +interface IChatSetupControllerOptions { + readonly forceSignIn?: boolean; + readonly useSocialProvider?: string; + readonly useEnterpriseProvider?: boolean; + readonly additionalScopes?: readonly string[]; + readonly forceAnonymous?: boolean; +} + class ChatSetupController extends Disposable { private readonly _onDidChange = this._register(new Emitter()); @@ -1391,7 +1421,7 @@ class ChatSetupController extends Disposable { this._onDidChange.fire(); } - async setup(options?: { forceSignIn?: boolean; useSocialProvider?: string; useEnterpriseProvider?: boolean; additionalScopes?: readonly string[] }): Promise { + async setup(options: IChatSetupControllerOptions = {}): Promise { const watch = new StopWatch(false); const title = localize('setupChatProgress', "Getting chat ready..."); const badge = this.activityService.showViewContainerActivity(CHAT_SIDEBAR_PANEL_ID, { @@ -1403,13 +1433,13 @@ class ChatSetupController extends Disposable { location: ProgressLocation.Window, command: CHAT_OPEN_ACTION_ID, title, - }, () => this.doSetup(options ?? {}, watch)); + }, () => this.doSetup(options, watch)); } finally { badge.dispose(); } } - private async doSetup(options: { forceSignIn?: boolean; useSocialProvider?: string; useEnterpriseProvider?: boolean; additionalScopes?: readonly string[] }, watch: StopWatch): Promise { + private async doSetup(options: IChatSetupControllerOptions, watch: StopWatch): Promise { this.context.suspend(); // reduces flicker let success: ChatSetupResultValue = false; @@ -1418,8 +1448,20 @@ class ChatSetupController extends Disposable { let session: AuthenticationSession | undefined; let entitlement: ChatEntitlement | undefined; - // Entitlement Unknown or `forceSignIn`: we need to sign-in user - if (this.context.state.entitlement === ChatEntitlement.Unknown || options.forceSignIn) { + let signIn: boolean; + if (options.forceSignIn) { + signIn = true; // forced to sign in + } else if (this.context.state.entitlement === ChatEntitlement.Unknown) { + if (options.forceAnonymous) { + signIn = false; // forced to anonymous without sign in + } else { + signIn = true; // sign in since we are signed out + } + } else { + signIn = false; // already signed in + } + + if (signIn) { this.setStep(ChatSetupStep.SigningIn); const result = await this.signIn(options); if (!result.session) { @@ -1445,7 +1487,7 @@ class ChatSetupController extends Disposable { return success; } - private async signIn(options: { useSocialProvider?: string; additionalScopes?: readonly string[] }): Promise<{ session: AuthenticationSession | undefined; entitlement: ChatEntitlement | undefined }> { + private async signIn(options: IChatSetupControllerOptions): Promise<{ session: AuthenticationSession | undefined; entitlement: ChatEntitlement | undefined }> { let session: AuthenticationSession | undefined; let entitlements; try { @@ -1470,7 +1512,7 @@ class ChatSetupController extends Disposable { return { session, entitlement: entitlements?.entitlement }; } - private async install(session: AuthenticationSession | undefined, entitlement: ChatEntitlement, providerId: string, watch: StopWatch, options: { useSocialProvider?: string; useEnterpriseProvider?: boolean; additionalScopes?: readonly string[] }): Promise { + private async install(session: AuthenticationSession | undefined, entitlement: ChatEntitlement, providerId: string, watch: StopWatch, options: IChatSetupControllerOptions): Promise { const wasRunning = this.context.state.installed && !this.context.state.disabled; let signUpResult: boolean | { errorCode: number } | undefined = undefined; @@ -1478,7 +1520,7 @@ class ChatSetupController extends Disposable { let sessions = session ? [session] : undefined; try { if ( - entitlement !== ChatEntitlement.Anonymous && // User is not eligible for anonymous access + !options.forceAnonymous && // User is not asking for anonymous access entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free !isProUser(entitlement) && // User is not signed up for a Copilot subscription entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free @@ -1564,7 +1606,7 @@ class ChatSetupController extends Disposable { }, ChatViewId); } - async setupWithProvider(options: { useEnterpriseProvider: boolean; useSocialProvider: string | undefined; additionalScopes?: readonly string[] }): Promise { + async setupWithProvider(options: IChatSetupControllerOptions): Promise { const registry = Registry.as(ConfigurationExtensions.Configuration); registry.registerConfiguration({ 'id': 'copilot.setup', diff --git a/src/vs/workbench/services/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts index 624159eebbd..eb1250a6644 100644 --- a/src/vs/workbench/services/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -43,7 +43,6 @@ export namespace ChatEntitlementContextKeys { export const Entitlement = { signedOut: new RawContextKey('chatEntitlementSignedOut', false, true), // True when user is signed out. - anonymous: new RawContextKey('chatEntitlementAnonymous', false, true), // True when user is an anonymous user. canSignUp: new RawContextKey('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user. planFree: new RawContextKey('chatPlanFree', false, true), // True when user is a chat free user. @@ -64,8 +63,6 @@ export namespace ChatEntitlementContextKeys { export const IChatEntitlementService = createDecorator('chatEntitlementService'); export enum ChatEntitlement { - /* Signed out, anonymous */ - Anonymous = -1, /** Signed out */ Unknown = 1, /** Signed in but not yet resolved */ @@ -212,7 +209,6 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme ChatEntitlementContextKeys.Entitlement.planFree.key, ChatEntitlementContextKeys.Entitlement.canSignUp.key, ChatEntitlementContextKeys.Entitlement.signedOut.key, - ChatEntitlementContextKeys.Entitlement.anonymous.key, ChatEntitlementContextKeys.Entitlement.organisations.key, ChatEntitlementContextKeys.Entitlement.internal.key, ChatEntitlementContextKeys.Entitlement.sku.key @@ -273,8 +269,6 @@ export class ChatEntitlementService extends Disposable implements IChatEntitleme return ChatEntitlement.Available; } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.signedOut.key) === true) { return ChatEntitlement.Unknown; - } else if (this.contextKeyService.getContextKeyValue(ChatEntitlementContextKeys.Entitlement.anonymous.key) === true) { - return ChatEntitlement.Anonymous; } return ChatEntitlement.Unresolved; @@ -1016,11 +1010,9 @@ export class ChatEntitlementContext extends Disposable { private static readonly CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY = 'chat.setupContext'; private static readonly CHAT_DISABLED_CONFIGURATION_KEY = 'chat.disableAIFeatures'; - private static readonly CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY = 'chat.allowAnonymousAccess'; private readonly canSignUpContextKey: IContextKey; private readonly signedOutContextKey: IContextKey; - private readonly anonymousContextKey: IContextKey; private readonly freeContextKey: IContextKey; private readonly proContextKey: IContextKey; @@ -1058,7 +1050,6 @@ export class ChatEntitlementContext extends Disposable { this.canSignUpContextKey = ChatEntitlementContextKeys.Entitlement.canSignUp.bindTo(contextKeyService); this.signedOutContextKey = ChatEntitlementContextKeys.Entitlement.signedOut.bindTo(contextKeyService); - this.anonymousContextKey = ChatEntitlementContextKeys.Entitlement.anonymous.bindTo(contextKeyService); this.freeContextKey = ChatEntitlementContextKeys.Entitlement.planFree.bindTo(contextKeyService); this.proContextKey = ChatEntitlementContextKeys.Entitlement.planPro.bindTo(contextKeyService); @@ -1083,7 +1074,7 @@ export class ChatEntitlementContext extends Disposable { private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) || e.affectsConfiguration(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY)) { + if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY)) { this.updateContext(); } })); @@ -1097,15 +1088,6 @@ export class ChatEntitlementContext extends Disposable { }; } - if (this.configurationService.getValue(ChatEntitlementContext.CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) === true) { - let entitlement = state.entitlement; - if (entitlement === ChatEntitlement.Unknown && !state.registered) { - entitlement = ChatEntitlement.Anonymous; // enable `anonymous` based on exp config if entitlement is unknown and user never signed up - } - - return { ...state, entitlement }; - } - return state; } @@ -1170,7 +1152,6 @@ export class ChatEntitlementContext extends Disposable { const state = this.withConfiguration(this._state); this.signedOutContextKey.set(state.entitlement === ChatEntitlement.Unknown); - this.anonymousContextKey.set(state.entitlement === ChatEntitlement.Anonymous); this.canSignUpContextKey.set(state.entitlement === ChatEntitlement.Available); this.freeContextKey.set(state.entitlement === ChatEntitlement.Free); From bf29996207b43bc12aa91fe0102c9dd9a2a53749 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 12 Sep 2025 12:37:11 +0200 Subject: [PATCH 0248/4355] adopt promptService.test --- .../promptSyntax/service/newPromptsParser.ts | 6 +- .../promptSyntax/service/promptValidator.ts | 2 +- .../service/promptsService.test.ts | 111 ++++++++---------- 3 files changed, 53 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 51d6dfdfcf4..fb0112acae5 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -157,7 +157,7 @@ export class PromptHeader { if (toolsAttribute.value.type === 'array') { const tools: string[] = []; for (const item of toolsAttribute.value.items) { - if (item.type === 'string') { + if (item.type === 'string' && item.value) { tools.push(item.value); } } @@ -231,7 +231,7 @@ export class PromptBody { const variableReferences: IBodyVariableReference[] = []; for (let i = this.range.startLineNumber - 1; i < this.range.endLineNumber - 1; i++) { const line = this.linesWithEOL[i]; - const linkMatch = line.matchAll(/\[(.+?)\]\((.+?)\)/g); + const linkMatch = line.matchAll(/\[(.*?)\]\((.+?)\)/g); for (const match of linkMatch) { const linkEndOffset = match.index + match[0].length - 1; // before the parenthesis const linkStartOffset = match.index + match[0].length - match[2].length - 1; @@ -239,7 +239,7 @@ export class PromptBody { fileReferences.push({ content: match[2], range, isMarkdownLink: true }); markdownLinkRanges.push(new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)); } - const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]*)`, 'g'); + const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]+)`, 'g'); const matches = line.matchAll(reg); for (const match of matches) { const fullRange = new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts index 511e1a1e28a..ead5fc60b43 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptValidator.ts @@ -227,7 +227,7 @@ export class PromptValidator { for (const item of attribute.value.items) { if (item.type !== 'string') { report(toMarker(localize('promptValidator.eachToolMustBeString', "Each tool name in the 'tools' attribute must be a string."), item.range, MarkerSeverity.Error)); - } else if (!available.has(item.value)) { + } else if (item.value && !available.has(item.value)) { report(toMarker(localize('promptValidator.toolNotFound', "Unknown tool '{0}'.", item.value), item.range, MarkerSeverity.Warning)); } } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index e2a7e64aa27..0c453e00b46 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -27,7 +27,7 @@ import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../.. import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; -import { ICustomChatMode, IPromptParserResult, IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; +import { ICustomChatMode, IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js'; import { MockFilesystem } from '../testUtils/mockFilesystem.js'; import { ILabelService } from '../../../../../../../platform/label/common/label.js'; import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; @@ -697,70 +697,57 @@ suite('PromptsService', () => { const yetAnotherFile = URI.joinPath(rootFolderUri, 'folder1/some-other-folder/yetAnotherFolder🤭/another-file.instructions.md'); - const result1 = await service.parse(rootFileUri, PromptsType.prompt, CancellationToken.None); - assert.deepEqual(result1, { - uri: rootFileUri, - metadata: { - promptType: PromptsType.prompt, - description: 'Root prompt description.', - tools: ['my-tool1'], - mode: 'agent', - }, - fileReferences: [file3, file4], - variableReferences: [ - { - name: "my-other-tool", - range: { - endExclusive: 265, - start: 251 - } - }, - { - name: "my-tool", - range: { - start: 239, - endExclusive: 247, - } - }], - } satisfies IPromptParserResult); - - const result2 = await service.parse(file3, PromptsType.prompt, CancellationToken.None); - assert.deepEqual(result2, { - uri: file3, - metadata: { - promptType: PromptsType.prompt, - mode: 'edit', - }, - fileReferences: [nonExistingFolder, yetAnotherFile], - variableReferences: [] - } satisfies IPromptParserResult); - - const result3 = await service.parse(yetAnotherFile, PromptsType.instructions, CancellationToken.None); - assert.deepEqual(result3, { - uri: yetAnotherFile, - metadata: { - promptType: PromptsType.instructions, - description: 'Another file description.', - applyTo: '**/*.tsx', - }, - fileReferences: [someOtherFolder, someOtherFolderFile], - variableReferences: [] - } satisfies IPromptParserResult); - - const result4 = await service.parse(file4, PromptsType.instructions, CancellationToken.None); - assert.deepEqual(result4, { - uri: file4, - metadata: { - promptType: PromptsType.instructions, - description: 'File 4 splendid description.', - }, - fileReferences: [ + const result1 = await service.parseNew(rootFileUri, CancellationToken.None); + assert.deepEqual(result1.uri, rootFileUri); + assert.deepEqual(result1.header?.description, 'Root prompt description.'); + assert.deepEqual(result1.header?.tools, ['my-tool1']); + assert.deepEqual(result1.header?.mode, 'agent'); + assert.ok(result1.body); + assert.deepEqual( + result1.body.fileReferences.map(r => result1.body?.resolveFilePath(r.content)), + [file3, file4], + ); + assert.deepEqual( + result1.body.variableReferences, + [ + { name: "my-tool", range: new Range(10, 5, 10, 12) }, + { name: "my-other-tool", range: new Range(11, 5, 11, 18) }, + ] + ); + + const result2 = await service.parseNew(file3, CancellationToken.None); + assert.deepEqual(result2.uri, file3); + assert.deepEqual(result2.header?.mode, 'edit'); + assert.ok(result2.body); + assert.deepEqual( + result2.body.fileReferences.map(r => result2.body?.resolveFilePath(r.content)), + [nonExistingFolder, yetAnotherFile], + ); + + const result3 = await service.parseNew(yetAnotherFile, CancellationToken.None); + assert.deepEqual(result3.uri, yetAnotherFile); + assert.deepEqual(result3.header?.description, 'Another file description.'); + assert.deepEqual(result3.header?.applyTo, '**/*.tsx'); + assert.ok(result3.body); + assert.deepEqual( + result3.body.fileReferences.map(r => result3.body?.resolveFilePath(r.content)), + [someOtherFolder, someOtherFolderFile], + ); + assert.deepEqual(result3.body.variableReferences, []); + + const result4 = await service.parseNew(file4, CancellationToken.None); + assert.deepEqual(result4.uri, file4); + assert.deepEqual(result4.header?.description, 'File 4 splendid description.'); + assert.ok(result4.body); + assert.deepEqual( + result4.body.fileReferences.map(r => result4.body?.resolveFilePath(r.content)), + [ URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-existing/file.prompt.md'), URI.joinPath(rootFolderUri, '/folder1/some-other-folder/some-non-prompt-file.md'), - URI.joinPath(rootFolderUri, '/folder1/'), + URI.joinPath(rootFolderUri, '/folder1'), ], - variableReferences: [] - } satisfies IPromptParserResult); + ); + assert.deepEqual(result4.body.variableReferences, []); }); }); From 091d733c0112c21ad3595ad4283e0b9b63a9830c Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 12 Sep 2025 04:31:21 -0700 Subject: [PATCH 0249/4355] Update xterm to bring back partial scroll (#266254) Update xterm --- package-lock.json | 96 ++++++++++++++++++------------------ package.json | 20 ++++---- remote/package-lock.json | 96 ++++++++++++++++++------------------ remote/package.json | 20 ++++---- remote/web/package-lock.json | 88 ++++++++++++++++----------------- remote/web/package.json | 18 +++---- 6 files changed, 169 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 566b47bd50f..b4d0ed3cdda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,16 +27,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/headless": "^5.6.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -3605,30 +3605,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.2.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", + "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", + "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.10.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", + "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -3638,64 +3638,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.2.0-beta.25", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", + "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.16.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", + "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.14.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", + "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", + "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.19.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", + "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.118.tgz", - "integrity": "sha512-VUIlj6NvEmhi4PISiPhBAhPhEPcoSBYG39E3nuVHp2wvo02Z+2lig8WppVuwBMDk1EfBSo/MYa1YqPxVSqYNUQ==", + "version": "5.6.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.119.tgz", + "integrity": "sha512-kZW0Nuu0Rhm/lov6rp+LTbf0rWur/cM2zAUiU/IxpgR+tCJlPT5stSFXBvc+AzaNNHuvpHG8ko2YUJTmqdWp2A==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", + "version": "5.6.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", + "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", "license": "MIT" }, "node_modules/@xtuc/ieee754": { diff --git a/package.json b/package.json index ea83ed00f87..6ad06e6a153 100644 --- a/package.json +++ b/package.json @@ -87,16 +87,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/headless": "^5.6.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "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 58b4fe9b098..a402325e80e 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -20,16 +20,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/headless": "^5.6.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -237,30 +237,30 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.2.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", + "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", + "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.10.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", + "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -270,64 +270,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.2.0-beta.25", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", + "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.16.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", + "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.14.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", + "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", + "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.19.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", + "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.118.tgz", - "integrity": "sha512-VUIlj6NvEmhi4PISiPhBAhPhEPcoSBYG39E3nuVHp2wvo02Z+2lig8WppVuwBMDk1EfBSo/MYa1YqPxVSqYNUQ==", + "version": "5.6.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.119.tgz", + "integrity": "sha512-kZW0Nuu0Rhm/lov6rp+LTbf0rWur/cM2zAUiU/IxpgR+tCJlPT5stSFXBvc+AzaNNHuvpHG8ko2YUJTmqdWp2A==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", + "version": "5.6.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", + "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", "license": "MIT" }, "node_modules/agent-base": { diff --git a/remote/package.json b/remote/package.json index 6ec99ceb2ed..e4a0457c914 100644 --- a/remote/package.json +++ b/remote/package.json @@ -15,16 +15,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/headless": "^5.6.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "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 19ca9f496ab..b526382f642 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -13,15 +13,15 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", @@ -91,30 +91,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.2.0-beta.102", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", + "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", + "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.10.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", + "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -124,58 +124,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.2.0-beta.25", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", + "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.16.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", + "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.14.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", + "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.9.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", + "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.19.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", + "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^5.6.0-beta.119" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", + "version": "5.6.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", + "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", "license": "MIT" }, "node_modules/commander": { diff --git a/remote/web/package.json b/remote/web/package.json index e1198458b15..2ea2a69624c 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -8,15 +8,15 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.2.0-beta.102", + "@xterm/addon-image": "^0.9.0-beta.119", + "@xterm/addon-ligatures": "^0.10.0-beta.119", + "@xterm/addon-progress": "^0.2.0-beta.25", + "@xterm/addon-search": "^0.16.0-beta.119", + "@xterm/addon-serialize": "^0.14.0-beta.119", + "@xterm/addon-unicode11": "^0.9.0-beta.119", + "@xterm/addon-webgl": "^0.19.0-beta.119", + "@xterm/xterm": "^5.6.0-beta.119", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", From a650fa7a1468782714372c0f0f2477f0dfc0442b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 12 Sep 2025 14:26:19 +0200 Subject: [PATCH 0250/4355] adopt computeCustomChatModes, IVariableReferences --- .../contrib/chat/browser/chatWidget.ts | 6 +- .../promptSyntax/service/newPromptsParser.ts | 31 ++++++---- .../service/promptsServiceImpl.ts | 56 ++++++++----------- .../service/newPromptsParser.test.ts | 12 +++- .../service/promptsService.test.ts | 9 +-- 5 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e864ff2dc3a..66279d3061a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -83,6 +83,7 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; +import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; const $ = dom.$; @@ -2157,8 +2158,9 @@ export class ChatWidget extends Disposable implements IChatWidget { if (agentSlashPromptPart) { parseResult = await this.promptsService.resolvePromptSlashCommand(agentSlashPromptPart.slashPromptCommand, CancellationToken.None); if (parseResult) { - // add the prompt file to the context, but not sticky - const toolReferences = this.toolsService.toToolReferences([]); // TODO: this.toolsService.toToolReferences(parseResult.body?.variableReferences ?? []); + // add the prompt file to the context + const refs = parseResult.body?.variableReferences.map(({ name, offset }) => ({ name, range: new OffsetRange(offset, offset + name.length + 1) })) ?? []; + const toolReferences = this.toolsService.toToolReferences(refs); requestInput.attachedContext.insertFirst(toPromptFileVariableEntry(parseResult.uri, PromptFileVariableKind.PromptFile, undefined, true, toolReferences)); // remove the slash command from the input diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index fb0112acae5..fd08120c4ce 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Iterable } from '../../../../../../base/common/iterator.js'; import { dirname, resolvePath } from '../../../../../../base/common/resources.js'; import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -208,6 +209,7 @@ export type IValue = IStringValue | INumberValue | IBooleanValue | IArrayValue | interface ParsedBody { readonly fileReferences: readonly IBodyFileReference[]; readonly variableReferences: readonly IBodyVariableReference[]; + readonly bodyOffset: number; } export class PromptBody { @@ -224,12 +226,17 @@ export class PromptBody { return this.getParsedBody().variableReferences; } + public get offset(): number { + return this.getParsedBody().bodyOffset; + } + private getParsedBody(): ParsedBody { if (this._parsed === undefined) { const markdownLinkRanges: Range[] = []; const fileReferences: IBodyFileReference[] = []; const variableReferences: IBodyVariableReference[] = []; - for (let i = this.range.startLineNumber - 1; i < this.range.endLineNumber - 1; i++) { + const bodyOffset = Iterable.reduce(Iterable.slice(this.linesWithEOL, 0, this.range.startLineNumber - 1), (len, line) => line.length + len, 0); + for (let i = this.range.startLineNumber - 1, lineStartOffset = bodyOffset; i < this.range.endLineNumber - 1; i++) { const line = this.linesWithEOL[i]; const linkMatch = line.matchAll(/\[(.*?)\]\((.+?)\)/g); for (const match of linkMatch) { @@ -258,15 +265,20 @@ export class PromptBody { const contentStartOffset = match.index + 1; // after the # const contentEndOffset = match.index + match[0].length; const range = new Range(i + 1, contentStartOffset + 1, i + 1, contentEndOffset + 1); - variableReferences.push({ name: match[2], range }); + variableReferences.push({ name: match[2], range, offset: lineStartOffset + match.index }); } } + lineStartOffset += line.length; } - this._parsed = { fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)), variableReferences }; + this._parsed = { fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)), variableReferences, bodyOffset }; } return this._parsed; } + public getContent(): string { + return this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join(''); + } + public resolveFilePath(path: string): URI | undefined { try { if (path.startsWith('/')) { @@ -283,14 +295,13 @@ export class PromptBody { } export interface IBodyFileReference { - content: string; - range: Range; - isMarkdownLink: boolean; + readonly content: string; + readonly range: Range; + readonly isMarkdownLink: boolean; } export interface IBodyVariableReference { - name: string; - range: Range; + readonly name: string; + readonly range: Range; + readonly offset: number; } - - diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index df8191d491b..07579a4d003 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../../../nls.js'; -import { getLanguageIdForPromptsType, getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { getLanguageIdForPromptsType, getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { PromptParser } from '../parsers/promptParser.js'; import { type URI } from '../../../../../../base/common/uri.js'; import { assert } from '../../../../../../base/common/assert.js'; @@ -31,6 +31,8 @@ import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; +import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js'; +import { IVariableReference } from '../../chatModes.js'; /** * Provides prompt services. @@ -255,42 +257,28 @@ export class PromptsService extends Disposable implements IPromptsService { const metadataList = await Promise.all( modeFiles.map(async ({ uri }): Promise => { - let parser: PromptParser | undefined; - try { - // Note! this can be (and should be) improved by using shared parser instances - // that the `getSyntaxParserFor` method provides for opened documents. - parser = this.instantiationService.createInstance( - PromptParser, - uri, - { allowNonPromptFiles: true, languageId: MODE_LANGUAGE_ID, updateOnChange: false }, - ).start(token); - - const completed = await parser.settled(); - if (!completed) { - throw new Error(localize('promptParser.notCompleted', "Prompt parser for {0} did not complete.", uri.toString())); + const ast = await this.parseNew(uri, token); + + const variableReferences: IVariableReference[] = []; + let body = ''; + if (ast.body) { + const bodyOffset = ast.body.offset; + const bodyVarRefs = ast.body.variableReferences; + for (let i = bodyVarRefs.length - 1; i >= 0; i--) { // in reverse order + const { name, offset } = bodyVarRefs[i]; + const range = new OffsetRange(offset - bodyOffset, offset - bodyOffset + name.length + 1); + variableReferences.push({ name, range }); } + body = ast.body.getContent(); + } - const body = await parser.getBody(); - const nHeaderLines = parser.header?.range.endLineNumber ?? 0; - const transformer = new PositionOffsetTransformer(body); - const variableReferences = parser.variableReferences.map(ref => { - return { - name: ref.name, - range: transformer.getOffsetRange(ref.range.delta(-nHeaderLines)) - }; - }).sort((a, b) => b.range.start - a.range.start); // in reverse order - - const name = getCleanPromptName(uri); - - const metadata = parser.metadata; - if (metadata?.promptType !== PromptsType.mode) { - return { uri, name, body, variableReferences }; - } - const { description, model, tools } = metadata; - return { uri, name, description, model, tools, body, variableReferences }; - } finally { - parser?.dispose(); + const name = getCleanPromptName(uri); + if (!ast.header) { + return { uri, name, body, variableReferences }; } + const { description, model, tools } = ast.header; + return { uri, name, description, model, tools, body, variableReferences }; + }) ); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 22409da076b..89dac4ac4db 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -41,12 +41,15 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.equal(result.body.offset, 80); + assert.equal(result.body.getContent(), 'This is a chat mode test.\nHere is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).'); + assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 39, 7, 54), content: './reference1.md', isMarkdownLink: false }, { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 12, 7, 17), name: 'tool1' } + { range: new Range(7, 12, 7, 17), name: 'tool1', offset: 116 } ]); assert.deepEqual(result.header.description, 'Agent mode test'); assert.deepEqual(result.header.model, 'GPT 4.1'); @@ -73,6 +76,9 @@ suite('NewPromptsParser', () => { { key: 'applyTo', range: new Range(3, 1, 3, 14), value: { type: 'string', value: '*.ts', range: new Range(3, 10, 3, 14) } }, ]); assert.deepEqual(result.body.range, { startLineNumber: 5, startColumn: 1, endLineNumber: 6, endColumn: 1 }); + assert.equal(result.body.offset, 76); + assert.equal(result.body.getContent(), 'Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)'); + assert.deepEqual(result.body.fileReferences, [ { range: new Range(5, 64, 5, 103), content: 'https://mycomp/guidelines#typescript.md', isMarkdownLink: true }, ]); @@ -110,11 +116,13 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.equal(result.body.offset, 113); + assert.equal(result.body.getContent(), 'This is a prompt file body referencing #search and [docs](https://example.com/docs).'); assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 41, 7, 47), name: 'search' } + { range: new Range(7, 41, 7, 47), name: 'search', offset: 152 } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); assert.deepEqual(result.header.mode, 'agent'); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 0c453e00b46..21f60a3aae0 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -710,8 +710,8 @@ suite('PromptsService', () => { assert.deepEqual( result1.body.variableReferences, [ - { name: "my-tool", range: new Range(10, 5, 10, 12) }, - { name: "my-other-tool", range: new Range(11, 5, 11, 18) }, + { name: "my-tool", range: new Range(10, 5, 10, 12), offset: 239 }, + { name: "my-other-tool", range: new Range(11, 5, 11, 18), offset: 251 }, ] ); @@ -1275,18 +1275,15 @@ suite('PromptsService', () => { }, { name: 'mode2', - description: undefined, - tools: undefined, body: 'First use #tool2\nThen use #tool1', variableReferences: [{ name: 'tool1', range: { start: 26, endExclusive: 32 } }, { name: 'tool2', range: { start: 10, endExclusive: 16 } }], - model: undefined, uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), } ]; assert.deepEqual( - expected, result, + expected, 'Must get custom chat modes.', ); }); From d9832733879901756c13cf6cb2368a4b46f4ef81 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 12 Sep 2025 14:26:19 +0200 Subject: [PATCH 0251/4355] adopt computeCustomChatModes, IVariableReferences --- .../contrib/chat/browser/chatWidget.ts | 6 +- .../promptSyntax/service/newPromptsParser.ts | 31 ++++++---- .../service/promptsServiceImpl.ts | 56 ++++++++----------- .../service/newPromptsParser.test.ts | 12 +++- .../service/promptsService.test.ts | 9 +-- 5 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e864ff2dc3a..66279d3061a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -83,6 +83,7 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; +import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; const $ = dom.$; @@ -2157,8 +2158,9 @@ export class ChatWidget extends Disposable implements IChatWidget { if (agentSlashPromptPart) { parseResult = await this.promptsService.resolvePromptSlashCommand(agentSlashPromptPart.slashPromptCommand, CancellationToken.None); if (parseResult) { - // add the prompt file to the context, but not sticky - const toolReferences = this.toolsService.toToolReferences([]); // TODO: this.toolsService.toToolReferences(parseResult.body?.variableReferences ?? []); + // add the prompt file to the context + const refs = parseResult.body?.variableReferences.map(({ name, offset }) => ({ name, range: new OffsetRange(offset, offset + name.length + 1) })) ?? []; + const toolReferences = this.toolsService.toToolReferences(refs); requestInput.attachedContext.insertFirst(toPromptFileVariableEntry(parseResult.uri, PromptFileVariableKind.PromptFile, undefined, true, toolReferences)); // remove the slash command from the input diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index fb0112acae5..fd08120c4ce 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Iterable } from '../../../../../../base/common/iterator.js'; import { dirname, resolvePath } from '../../../../../../base/common/resources.js'; import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -208,6 +209,7 @@ export type IValue = IStringValue | INumberValue | IBooleanValue | IArrayValue | interface ParsedBody { readonly fileReferences: readonly IBodyFileReference[]; readonly variableReferences: readonly IBodyVariableReference[]; + readonly bodyOffset: number; } export class PromptBody { @@ -224,12 +226,17 @@ export class PromptBody { return this.getParsedBody().variableReferences; } + public get offset(): number { + return this.getParsedBody().bodyOffset; + } + private getParsedBody(): ParsedBody { if (this._parsed === undefined) { const markdownLinkRanges: Range[] = []; const fileReferences: IBodyFileReference[] = []; const variableReferences: IBodyVariableReference[] = []; - for (let i = this.range.startLineNumber - 1; i < this.range.endLineNumber - 1; i++) { + const bodyOffset = Iterable.reduce(Iterable.slice(this.linesWithEOL, 0, this.range.startLineNumber - 1), (len, line) => line.length + len, 0); + for (let i = this.range.startLineNumber - 1, lineStartOffset = bodyOffset; i < this.range.endLineNumber - 1; i++) { const line = this.linesWithEOL[i]; const linkMatch = line.matchAll(/\[(.*?)\]\((.+?)\)/g); for (const match of linkMatch) { @@ -258,15 +265,20 @@ export class PromptBody { const contentStartOffset = match.index + 1; // after the # const contentEndOffset = match.index + match[0].length; const range = new Range(i + 1, contentStartOffset + 1, i + 1, contentEndOffset + 1); - variableReferences.push({ name: match[2], range }); + variableReferences.push({ name: match[2], range, offset: lineStartOffset + match.index }); } } + lineStartOffset += line.length; } - this._parsed = { fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)), variableReferences }; + this._parsed = { fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)), variableReferences, bodyOffset }; } return this._parsed; } + public getContent(): string { + return this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join(''); + } + public resolveFilePath(path: string): URI | undefined { try { if (path.startsWith('/')) { @@ -283,14 +295,13 @@ export class PromptBody { } export interface IBodyFileReference { - content: string; - range: Range; - isMarkdownLink: boolean; + readonly content: string; + readonly range: Range; + readonly isMarkdownLink: boolean; } export interface IBodyVariableReference { - name: string; - range: Range; + readonly name: string; + readonly range: Range; + readonly offset: number; } - - diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index df8191d491b..07579a4d003 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../../../nls.js'; -import { getLanguageIdForPromptsType, getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { getLanguageIdForPromptsType, getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { PromptParser } from '../parsers/promptParser.js'; import { type URI } from '../../../../../../base/common/uri.js'; import { assert } from '../../../../../../base/common/assert.js'; @@ -31,6 +31,8 @@ import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; +import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js'; +import { IVariableReference } from '../../chatModes.js'; /** * Provides prompt services. @@ -255,42 +257,28 @@ export class PromptsService extends Disposable implements IPromptsService { const metadataList = await Promise.all( modeFiles.map(async ({ uri }): Promise => { - let parser: PromptParser | undefined; - try { - // Note! this can be (and should be) improved by using shared parser instances - // that the `getSyntaxParserFor` method provides for opened documents. - parser = this.instantiationService.createInstance( - PromptParser, - uri, - { allowNonPromptFiles: true, languageId: MODE_LANGUAGE_ID, updateOnChange: false }, - ).start(token); - - const completed = await parser.settled(); - if (!completed) { - throw new Error(localize('promptParser.notCompleted', "Prompt parser for {0} did not complete.", uri.toString())); + const ast = await this.parseNew(uri, token); + + const variableReferences: IVariableReference[] = []; + let body = ''; + if (ast.body) { + const bodyOffset = ast.body.offset; + const bodyVarRefs = ast.body.variableReferences; + for (let i = bodyVarRefs.length - 1; i >= 0; i--) { // in reverse order + const { name, offset } = bodyVarRefs[i]; + const range = new OffsetRange(offset - bodyOffset, offset - bodyOffset + name.length + 1); + variableReferences.push({ name, range }); } + body = ast.body.getContent(); + } - const body = await parser.getBody(); - const nHeaderLines = parser.header?.range.endLineNumber ?? 0; - const transformer = new PositionOffsetTransformer(body); - const variableReferences = parser.variableReferences.map(ref => { - return { - name: ref.name, - range: transformer.getOffsetRange(ref.range.delta(-nHeaderLines)) - }; - }).sort((a, b) => b.range.start - a.range.start); // in reverse order - - const name = getCleanPromptName(uri); - - const metadata = parser.metadata; - if (metadata?.promptType !== PromptsType.mode) { - return { uri, name, body, variableReferences }; - } - const { description, model, tools } = metadata; - return { uri, name, description, model, tools, body, variableReferences }; - } finally { - parser?.dispose(); + const name = getCleanPromptName(uri); + if (!ast.header) { + return { uri, name, body, variableReferences }; } + const { description, model, tools } = ast.header; + return { uri, name, description, model, tools, body, variableReferences }; + }) ); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 22409da076b..89dac4ac4db 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -41,12 +41,15 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.equal(result.body.offset, 80); + assert.equal(result.body.getContent(), 'This is a chat mode test.\nHere is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).'); + assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 39, 7, 54), content: './reference1.md', isMarkdownLink: false }, { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 12, 7, 17), name: 'tool1' } + { range: new Range(7, 12, 7, 17), name: 'tool1', offset: 116 } ]); assert.deepEqual(result.header.description, 'Agent mode test'); assert.deepEqual(result.header.model, 'GPT 4.1'); @@ -73,6 +76,9 @@ suite('NewPromptsParser', () => { { key: 'applyTo', range: new Range(3, 1, 3, 14), value: { type: 'string', value: '*.ts', range: new Range(3, 10, 3, 14) } }, ]); assert.deepEqual(result.body.range, { startLineNumber: 5, startColumn: 1, endLineNumber: 6, endColumn: 1 }); + assert.equal(result.body.offset, 76); + assert.equal(result.body.getContent(), 'Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)'); + assert.deepEqual(result.body.fileReferences, [ { range: new Range(5, 64, 5, 103), content: 'https://mycomp/guidelines#typescript.md', isMarkdownLink: true }, ]); @@ -110,11 +116,13 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); + assert.equal(result.body.offset, 113); + assert.equal(result.body.getContent(), 'This is a prompt file body referencing #search and [docs](https://example.com/docs).'); assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 41, 7, 47), name: 'search' } + { range: new Range(7, 41, 7, 47), name: 'search', offset: 152 } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); assert.deepEqual(result.header.mode, 'agent'); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 0c453e00b46..21f60a3aae0 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -710,8 +710,8 @@ suite('PromptsService', () => { assert.deepEqual( result1.body.variableReferences, [ - { name: "my-tool", range: new Range(10, 5, 10, 12) }, - { name: "my-other-tool", range: new Range(11, 5, 11, 18) }, + { name: "my-tool", range: new Range(10, 5, 10, 12), offset: 239 }, + { name: "my-other-tool", range: new Range(11, 5, 11, 18), offset: 251 }, ] ); @@ -1275,18 +1275,15 @@ suite('PromptsService', () => { }, { name: 'mode2', - description: undefined, - tools: undefined, body: 'First use #tool2\nThen use #tool1', variableReferences: [{ name: 'tool1', range: { start: 26, endExclusive: 32 } }, { name: 'tool2', range: { start: 10, endExclusive: 16 } }], - model: undefined, uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), } ]; assert.deepEqual( - expected, result, + expected, 'Must get custom chat modes.', ); }); From 6da77d3d37a1249cf86f59c70bc264e9d85f36ea Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 12 Sep 2025 08:49:52 -0400 Subject: [PATCH 0252/4355] include action's description in aria label for action list (#266363) fix #266285 --- src/vs/platform/actionWidget/browser/actionList.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index c4d5eec7486..76ddb42ff5b 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -265,6 +265,9 @@ export class ActionList extends Disposable { getAriaLabel: element => { if (element.kind === ActionListItemKind.Action) { let label = element.label ? stripNewlines(element?.label) : ''; + if (element.description) { + label = label + ', ' + stripNewlines(element.description); + } if (element.disabled) { label = localize({ key: 'customQuickFixWidget.labels', comment: [`Action widget labels for accessibility.`] }, "{0}, Disabled Reason: {1}", label, element.disabled); } From f294ac44d5118a283cd5fb6a949a83270d6643c6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 12 Sep 2025 15:01:37 +0200 Subject: [PATCH 0253/4355] debt - fix bugs around bad ternary operator use (#266364) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 4 ++-- src/vs/workbench/services/textfile/common/encoding.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 25e10e96730..8226a336f73 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1467,7 +1467,7 @@ class ChatSetupController extends Disposable { if (!result.session) { this.doInstall(); // still install the extension in the background to remind the user to sign-in eventually - const provider = options.useSocialProvider ?? options.useEnterpriseProvider ? defaultChat.provider.enterprise.id : defaultChat.provider.default.id; + const provider = options.useSocialProvider ?? (options.useEnterpriseProvider ? defaultChat.provider.enterprise.id : defaultChat.provider.default.id); this.telemetryService.publicLog2('commandCenter.chatInstall', { installResult: 'failedNotSignedIn', installDuration: watch.elapsed(), signUpErrorCode: undefined, provider }); return undefined; // treat as cancelled because signing in already triggers an error dialog } @@ -1516,7 +1516,7 @@ class ChatSetupController extends Disposable { const wasRunning = this.context.state.installed && !this.context.state.disabled; let signUpResult: boolean | { errorCode: number } | undefined = undefined; - const provider = options.useSocialProvider ?? options.useEnterpriseProvider ? defaultChat.provider.enterprise.id : defaultChat.provider.default.id; + const provider = options.useSocialProvider ?? (options.useEnterpriseProvider ? defaultChat.provider.enterprise.id : defaultChat.provider.default.id); let sessions = session ? [session] : undefined; try { if ( diff --git a/src/vs/workbench/services/textfile/common/encoding.ts b/src/vs/workbench/services/textfile/common/encoding.ts index 4fee9c55420..3e9ce3ba3d8 100644 --- a/src/vs/workbench/services/textfile/common/encoding.ts +++ b/src/vs/workbench/services/textfile/common/encoding.ts @@ -117,7 +117,7 @@ class DecoderStream implements IDecoderStream { } export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise { - const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES; + const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? (options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES); return new Promise((resolve, reject) => { const target = newWriteableStream(strings => strings.join('')); From 609aaecdfb02ab4157004f22a9073dbdca47fd1c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 12 Sep 2025 15:28:13 +0200 Subject: [PATCH 0254/4355] remove old prompt parser --- src/vs/base/common/numbers.ts | 59 - src/vs/base/test/common/numbers.test.ts | 175 +- .../promptSyntax/codecs/base/asyncDecoder.ts | 93 - .../promptSyntax/codecs/base/baseDecoder.ts | 361 --- .../promptSyntax/codecs/base/baseToken.ts | 150 -- .../codecs/base/compositeToken.ts | 63 - .../codecs/base/frontMatterCodec/constants.ts | 16 - .../frontMatterCodec/frontMatterDecoder.ts | 156 -- .../parsers/frontMatterArray.ts | 197 -- .../parsers/frontMatterParserFactory.ts | 41 - .../frontMatterRecord/frontMatterRecord.ts | 210 -- .../frontMatterRecordName.ts | 74 - .../frontMatterRecordNameWithDelimiter.ts | 105 - .../parsers/frontMatterSequence.ts | 78 - .../parsers/frontMatterString.ts | 69 - .../parsers/frontMatterValue.ts | 184 -- .../tokens/frontMatterArray.ts | 43 - .../tokens/frontMatterBoolean.ts | 88 - .../tokens/frontMatterRecord.ts | 118 - .../tokens/frontMatterSequence.ts | 79 - .../tokens/frontMatterString.ts | 39 - .../tokens/frontMatterToken.ts | 33 - .../base/frontMatterCodec/tokens/index.ts | 15 - .../codecs/base/linesCodec/linesDecoder.ts | 237 -- .../base/linesCodec/tokens/carriageReturn.ts | 44 - .../codecs/base/linesCodec/tokens/line.ts | 48 - .../codecs/base/linesCodec/tokens/newLine.ts | 44 - .../base/markdownCodec/markdownDecoder.ts | 136 -- .../markdownCodec/parsers/markdownComment.ts | 173 -- .../markdownCodec/parsers/markdownImage.ts | 99 - .../markdownCodec/parsers/markdownLink.ts | 211 -- .../markdownCodec/tokens/markdownComment.ts | 40 - .../markdownCodec/tokens/markdownImage.ts | 125 -- .../base/markdownCodec/tokens/markdownLink.ts | 120 - .../markdownCodec/tokens/markdownToken.ts | 12 - .../markdownExtensionsDecoder.ts | 119 - .../parsers/frontMatterHeader.ts | 345 --- .../tokens/frontMatterHeader.ts | 79 - .../tokens/frontMatterMarker.ts | 59 - .../tokens/markdownExtensionsToken.ts | 11 - .../codecs/base/simpleCodec/parserBase.ts | 137 -- .../codecs/base/simpleCodec/simpleDecoder.ts | 132 -- .../base/simpleCodec/tokens/angleBrackets.ts | 61 - .../codecs/base/simpleCodec/tokens/at.ts | 31 - .../base/simpleCodec/tokens/brackets.ts | 61 - .../codecs/base/simpleCodec/tokens/colon.ts | 31 - .../codecs/base/simpleCodec/tokens/comma.ts | 31 - .../base/simpleCodec/tokens/curlyBraces.ts | 61 - .../codecs/base/simpleCodec/tokens/dash.ts | 31 - .../base/simpleCodec/tokens/dollarSign.ts | 31 - .../base/simpleCodec/tokens/doubleQuote.ts | 40 - .../simpleCodec/tokens/exclamationMark.ts | 31 - .../base/simpleCodec/tokens/formFeed.ts | 31 - .../codecs/base/simpleCodec/tokens/hash.ts | 31 - .../base/simpleCodec/tokens/parentheses.ts | 61 - .../codecs/base/simpleCodec/tokens/quote.ts | 40 - .../base/simpleCodec/tokens/simpleToken.ts | 59 - .../codecs/base/simpleCodec/tokens/slash.ts | 31 - .../codecs/base/simpleCodec/tokens/space.ts | 31 - .../codecs/base/simpleCodec/tokens/tab.ts | 31 - .../codecs/base/simpleCodec/tokens/tokens.ts | 25 - .../base/simpleCodec/tokens/verticalTab.ts | 31 - .../codecs/base/simpleCodec/tokens/word.ts | 60 - .../promptSyntax/codecs/base/textToken.ts | 19 - .../codecs/base/utils/objectStream.ts | 224 -- .../base/utils/objectStreamFromTextModel.ts | 46 - .../promptSyntax/codecs/chatPromptCodec.ts | 73 - .../promptSyntax/codecs/chatPromptDecoder.ts | 202 -- .../codecs/parsers/promptAtMentionParser.ts | 121 -- .../parsers/promptSlashCommandParser.ts | 122 -- .../parsers/promptTemplateVariableParser.ts | 148 -- .../codecs/parsers/promptVariableParser.ts | 252 --- .../codecs/tokens/fileReference.ts | 50 - .../codecs/tokens/promptAtMention.ts | 52 - .../codecs/tokens/promptSlashCommand.ts | 42 - .../codecs/tokens/promptTemplateVariable.ts | 44 - .../promptSyntax/codecs/tokens/promptToken.ts | 11 - .../codecs/tokens/promptVariable.ts | 103 - .../filePromptContentsProvider.ts | 166 -- .../promptContentsProviderBase.ts | 196 -- .../textModelContentsProvider.ts | 93 - .../promptSyntax/contentProviders/types.ts | 70 - .../decorations/frontMatterDecoration.ts | 120 - .../frontMatterMarkerDecoration.ts | 56 - .../decorations/utils/decorationBase.ts | 127 -- .../utils/reactiveDecorationBase.ts | 162 -- .../decorations/utils/types.ts | 56 - .../promptDecorationsProvider.ts | 205 -- .../decorationsProvider/types.ts | 48 - .../promptHeaderDiagnosticsProvider.ts | 234 -- .../promptLinkDiagnosticsProvider.ts | 98 - .../languageProviders/providerInstanceBase.ts | 54 - .../providerInstanceManagerBase.ts | 176 -- .../promptSyntax/parsers/basePromptParser.ts | 728 ------- .../promptSyntax/parsers/filePromptParser.ts | 37 - .../parsers/promptHeader/diagnostics.ts | 47 - .../parsers/promptHeader/headerBase.ts | 264 --- .../promptHeader/instructionsHeader.ts | 44 - .../parsers/promptHeader/metadata/applyTo.ts | 122 -- .../promptHeader/metadata/base/enum.ts | 84 - .../promptHeader/metadata/base/record.ts | 108 - .../promptHeader/metadata/base/string.ts | 73 - .../promptHeader/metadata/description.ts | 46 - .../parsers/promptHeader/metadata/mode.ts | 47 - .../parsers/promptHeader/metadata/model.ts | 41 - .../parsers/promptHeader/metadata/tools.ts | 182 -- .../parsers/promptHeader/modeHeader.ts | 56 - .../parsers/promptHeader/promptHeader.ts | 103 - .../promptSyntax/parsers/promptParser.ts | 72 - .../parsers/textModelPromptParser.ts | 42 - .../common/promptSyntax/parsers/topError.ts | 102 - .../chat/common/promptSyntax/parsers/types.ts | 116 - .../promptSyntax/service/promptsService.ts | 49 - .../service/promptsServiceImpl.ts | 93 +- .../common/promptSyntax/utils/objectCache.ts | 153 -- .../utils/observableDisposable.ts | 89 - .../frontMatterBoolean.test.ts | 317 --- .../frontMatterDecoder.test.ts | 415 ---- .../frontMatterRecord.test.ts | 183 -- .../frontMatterSequence.test.ts | 106 - .../codecs/base/linesDecoder.test.ts | 256 --- .../codecs/base/markdownDecoder.test.ts | 937 -------- .../codecs/base/simpleDecoder.test.ts | 238 -- .../codecs/base/testUtils/randomRange.ts | 58 - .../codecs/base/testUtils/randomTokens.ts | 146 -- .../codecs/base/tokens/baseToken.test.ts | 516 ----- .../codecs/base/tokens/compositeToken.test.ts | 250 --- .../codecs/base/tokens/simpleToken.test.ts | 66 - .../codecs/base/utils/objectStream.test.ts | 180 -- .../codecs/base/utils/testDecoder.ts | 258 --- .../codecs/chatPromptCodec.test.ts | 136 -- .../codecs/chatPromptDecoder.test.ts | 428 ---- .../codecs/markdownExtensionsDecoder.test.ts | 445 ---- .../codecs/tokens/fileReference.test.ts | 106 - .../codecs/tokens/markdownLink.test.ts | 98 - .../common/promptSyntax/config/config.test.ts | 15 +- .../promptSyntax/config/constants.test.ts | 7 +- .../filePromptContentsProvider.test.ts | 210 -- .../parsers/textModelPromptParser.test.ts | 1928 ----------------- .../service/promptsService.test.ts | 484 ----- .../testUtils/expectedDiagnostic.ts | 90 - .../testUtils/expectedReference.ts | 129 -- .../common/promptSyntax/utils/mock.test.ts | 5 +- .../promptSyntax/utils/objectCache.test.ts | 332 --- .../utils/observableDisposable.test.ts | 368 ---- 145 files changed, 17 insertions(+), 20215 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterArray.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterArray.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterBoolean.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterRecord.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterString.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/linesDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/line.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/newLine.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownComment.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownImage.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownLink.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/markdownExtensionsDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/parsers/frontMatterHeader.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/markdownExtensionsToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/parserBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptCodec.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/fileReference.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptAtMention.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/filePromptParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/diagnostics.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/model.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/topError.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts delete mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/simpleDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomRange.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/testUtils/randomTokens.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/baseToken.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/compositeToken.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/tokens/simpleToken.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/objectStream.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/utils/testDecoder.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptCodec.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/chatPromptDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/markdownExtensionsDecoder.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/fileReference.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/tokens/markdownLink.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/contentProviders/filePromptContentsProvider.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedDiagnostic.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/objectCache.test.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/observableDisposable.test.ts diff --git a/src/vs/base/common/numbers.ts b/src/vs/base/common/numbers.ts index 89c9f183e6d..326a9b2f2c3 100644 --- a/src/vs/base/common/numbers.ts +++ b/src/vs/base/common/numbers.ts @@ -99,65 +99,6 @@ export function isPointWithinTriangle( return u >= 0 && v >= 0 && u + v < 1; } -/** - * Function to get a (pseudo)random integer from a provided `max`...[`min`] range. - * Both `min` and `max` values are inclusive. The `min` value is optional (defaults to `0`). - * - * @throws in the next cases: - * - if provided `min` or `max` is not a number - * - if provided `min` or `max` is not finite - * - if provided `min` is larger than `max` value - * - * ## Examples - * - * Specifying a `max` value only uses `0` as the `min` value by default: - * - * ```typescript - * // get a random integer between 0 and 10 - * const randomInt = randomInt(10); - * - * assert( - * randomInt >= 0, - * 'Should be greater than or equal to 0.', - * ); - * - * assert( - * randomInt <= 10, - * 'Should be less than or equal to 10.', - * ); - * ``` - * * Specifying both `max` and `min` values: - * - * ```typescript - * // get a random integer between 5 and 8 - * const randomInt = randomInt(8, 5); - * - * assert( - * randomInt >= 5, - * 'Should be greater than or equal to 5.', - * ); - * - * assert( - * randomInt <= 8, - * 'Should be less than or equal to 8.', - * ); - * ``` - */ -export function randomInt(max: number, min: number = 0): number { - assert(!isNaN(min), '"min" param is not a number.'); - assert(!isNaN(max), '"max" param is not a number.'); - - assert(isFinite(max), '"max" param is not finite.'); - assert(isFinite(min), '"min" param is not finite.'); - - assert(max > min, `"max"(${max}) param should be greater than "min"(${min}).`); - - const delta = max - min; - const randomFloat = delta * Math.random(); - - return Math.round(min + randomFloat); -} - export function randomChance(p: number): boolean { assert(p >= 0 && p <= 1, 'p must be between 0 and 1'); return Math.random() < p; diff --git a/src/vs/base/test/common/numbers.test.ts b/src/vs/base/test/common/numbers.test.ts index 94c07090585..e21844eea98 100644 --- a/src/vs/base/test/common/numbers.test.ts +++ b/src/vs/base/test/common/numbers.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { isPointWithinTriangle, randomInt } from '../../common/numbers.js'; +import { isPointWithinTriangle } from '../../common/numbers.js'; suite('isPointWithinTriangle', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -25,176 +25,3 @@ suite('isPointWithinTriangle', () => { assert.ok(result); }); }); - -suite('randomInt', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - /** - * Test helper that allows to run a test on the `randomInt()` - * utility with specified `max` and `min` values. - */ - const testRandomIntUtil = (max: number, min: number | undefined, testName: string) => { - suite(testName, () => { - let i = 0; - while (++i < 5) { - test(`should generate random boolean attempt#${i}`, async () => { - let iterations = 100; - while (iterations-- > 0) { - const int = randomInt(max, min); - - assert( - int <= max, - `Expected ${int} to be less than or equal to ${max}.` - ); - assert( - int >= (min ?? 0), - `Expected ${int} to be greater than or equal to ${min ?? 0}.`, - ); - } - }); - } - - test('should include min and max', async () => { - let iterations = 125; - const results = []; - while (iterations-- > 0) { - results.push(randomInt(max, min)); - } - - assert( - results.includes(max), - `Expected ${results} to include ${max}.`, - ); - assert( - results.includes(min ?? 0), - `Expected ${results} to include ${min ?? 0}.`, - ); - }); - }); - }; - - suite('positive numbers', () => { - testRandomIntUtil(4, 2, 'max: 4, min: 2'); - testRandomIntUtil(4, 0, 'max: 4, min: 0'); - testRandomIntUtil(4, undefined, 'max: 4, min: undefined'); - testRandomIntUtil(1, 0, 'max: 0, min: 0'); - }); - - suite('negative numbers', () => { - testRandomIntUtil(-2, -5, 'max: -2, min: -5'); - testRandomIntUtil(0, -5, 'max: 0, min: -5'); - testRandomIntUtil(0, -1, 'max: 0, min: -1'); - }); - - suite('split numbers', () => { - testRandomIntUtil(3, -1, 'max: 3, min: -1'); - testRandomIntUtil(2, -2, 'max: 2, min: -2'); - testRandomIntUtil(1, -3, 'max: 2, min: -2'); - }); - - suite('errors', () => { - test('should throw if "min" is == "max" #1', () => { - assert.throws(() => { - randomInt(200, 200); - }, `"max"(200) param should be greater than "min"(200)."`); - }); - - test('should throw if "min" is == "max" #2', () => { - assert.throws(() => { - randomInt(2, 2); - }, `"max"(2) param should be greater than "min"(2)."`); - }); - - test('should throw if "min" is == "max" #3', () => { - assert.throws(() => { - randomInt(0); - }, `"max"(0) param should be greater than "min"(0)."`); - }); - - test('should throw if "min" is > "max" #1', () => { - assert.throws(() => { - randomInt(2, 3); - }, `"max"(2) param should be greater than "min"(3)."`); - }); - - test('should throw if "min" is > "max" #2', () => { - assert.throws(() => { - randomInt(999, 2000); - }, `"max"(999) param should be greater than "min"(2000)."`); - }); - - test('should throw if "min" is > "max" #3', () => { - assert.throws(() => { - randomInt(0, 1); - }, `"max"(0) param should be greater than "min"(1)."`); - }); - - test('should throw if "min" is > "max" #4', () => { - assert.throws(() => { - randomInt(-5, 2); - }, `"max"(-5) param should be greater than "min"(2)."`); - }); - - test('should throw if "min" is > "max" #5', () => { - assert.throws(() => { - randomInt(-4, 0); - }, `"max"(-4) param should be greater than "min"(0)."`); - }); - - test('should throw if "min" is > "max" #6', () => { - assert.throws(() => { - randomInt(-4); - }, `"max"(-4) param should be greater than "min"(0)."`); - }); - - test('should throw if "max" is `NaN`', () => { - assert.throws(() => { - randomInt(NaN); - }, `"max" param is not a number."`); - }); - - test('should throw if "min" is `NaN`', () => { - assert.throws(() => { - randomInt(4, NaN); - }, `"min" param is not a number."`); - }); - - suite('infinite arguments', () => { - test('should throw if "max" is infinite [Infinity]', () => { - assert.throws(() => { - randomInt(Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "max" is infinite [-Infinity]', () => { - assert.throws(() => { - randomInt(-Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "max" is infinite [+Infinity]', () => { - assert.throws(() => { - randomInt(+Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [-Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, -Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [+Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, +Infinity); - }, `"max" param is not finite."`); - }); - }); - }); -}); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts deleted file mode 100644 index be10b50ebba..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/asyncDecoder.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from '../../../../../../../base/common/lifecycle.js'; -import { BaseDecoder } from './baseDecoder.js'; - -/** - * Asynchronous iterator wrapper for a decoder. - */ -export class AsyncDecoder, K extends NonNullable = NonNullable> extends Disposable { - // Buffer of messages that have been decoded but not yet consumed. - private readonly messages: T[] = []; - - /** - * A transient promise that is resolved when a new event - * is received. Used in the situation when there is no new - * data available and decoder stream did not finish yet, - * hence we need to wait until new event is received. - */ - private resolveOnNewEvent?: (value: void) => void; - - /** - * @param decoder The decoder instance to wrap. - * - * Note! Assumes ownership of the `decoder` object, hence will `dispose` - * it when the decoder stream is ended. - */ - constructor( - private readonly decoder: BaseDecoder, - ) { - super(); - - this._register(decoder); - } - - /** - * Async iterator implementation. - */ - async *[Symbol.asyncIterator](): AsyncIterator { - // callback is called when `data` or `end` event is received - const callback = (data?: T) => { - if (data !== undefined) { - this.messages.push(data); - } else { - this.decoder.removeListener('data', callback); - this.decoder.removeListener('end', callback); - } - - // is the promise resolve callback is present, - // then call it and remove the reference - if (this.resolveOnNewEvent) { - this.resolveOnNewEvent(); - delete this.resolveOnNewEvent; - } - }; - - /** - * !NOTE! The order of event subscriptions below is critical here because - * the `data` event is also starts the stream, hence changing - * the order of event subscriptions can lead to race conditions. - * See {@link ReadableStreamEvents} for more info. - */ - - this.decoder.on('end', callback); - this.decoder.on('data', callback); - - // start flowing the decoder stream - this.decoder.start(); - - while (true) { - const maybeMessage = this.messages.shift(); - if (maybeMessage !== undefined) { - yield maybeMessage; - continue; - } - - // if no data available and stream ended, we're done - if (this.decoder.ended) { - this.dispose(); - - return null; - } - - // stream isn't ended so wait for the new - // `data` or `end` event to be received - await new Promise((resolve) => { - this.resolveOnNewEvent = resolve; - }); - } - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts deleted file mode 100644 index 76d1745d3c6..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseDecoder.ts +++ /dev/null @@ -1,361 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from '../../../../../../../base/common/event.js'; -import { ReadableStream } from '../../../../../../../base/common/stream.js'; -import { DeferredPromise } from '../../../../../../../base/common/async.js'; -import { AsyncDecoder } from './asyncDecoder.js'; -import { assert, assertNever } from '../../../../../../../base/common/assert.js'; -import { DisposableMap, IDisposable } from '../../../../../../../base/common/lifecycle.js'; -import { ObservableDisposable } from '../../utils/observableDisposable.js'; - -/** - * Event names of {@link ReadableStream} stream. - */ -export type TStreamListenerNames = 'data' | 'error' | 'end'; - -/** - * Base decoder class that can be used to convert stream messages data type - * from one type to another. For instance, a stream of binary data can be - * "decoded" into a stream of well defined objects. - * Intended to be a part of "codec" implementation rather than used directly. - */ -export abstract class BaseDecoder< - T extends NonNullable, - K extends NonNullable = NonNullable, -> extends ObservableDisposable implements ReadableStream { - /** - * Private attribute to track if the stream has ended. - */ - private _ended = false; - - protected readonly _onData = this._register(new Emitter()); - private readonly _onEnd = this._register(new Emitter()); - private readonly _onError = this._register(new Emitter()); - - /** - * A store of currently registered event listeners. - */ - private readonly _listeners: DisposableMap< - TStreamListenerNames, - DisposableMap - > = this._register(new DisposableMap()); - - /** - * This method is called when a new incoming data - * is received from the input stream. - */ - protected abstract onStreamData(data: K): void; - - /** - * @param stream The input stream to decode. - */ - constructor( - protected readonly stream: ReadableStream, - ) { - super(); - } - - /** - * Private attribute to track if the stream has started. - */ - private started = false; - - /** - * Promise that resolves when the stream has ended, either by - * receiving the `end` event or by a disposal, but not when - * the `error` event is received alone. - * The promise is true if the stream has ended, and false - * if the stream has been disposed without ending. - */ - private settledPromise = new DeferredPromise(); - - /** - * Promise that resolves when the stream has ended, either by - * receiving the `end` event or by a disposal, but not when - * the `error` event is received alone. - * The promise is true if the stream has ended, and false - * if the stream has been disposed without ending. - * - * @throws If the stream was not yet started to prevent this - * promise to block the consumer calls indefinitely. - */ - public get settled(): Promise { - // if the stream has not started yet, the promise might - // block the consumer calls indefinitely if they forget - // to call the `start()` method, or if the call happens - // after await on the `settled` promise; to forbid this - // confusion, we require the stream to be started first - assert( - this.started, - [ - 'Cannot get `settled` promise of a stream that has not been started.', - 'Please call `start()` first.', - ].join(' '), - ); - - return this.settledPromise.p; - } - - /** - * Start receiving data from the stream. - * @throws if the decoder stream has already ended. - */ - public start(): this { - assert( - this._ended === false, - 'Cannot start stream that has already ended.', - ); - assert( - this.isDisposed === false, - 'Cannot start stream that has already disposed.', - ); - - // if already started, nothing to do - if (this.started) { - return this; - } - this.started = true; - - /** - * !NOTE! The order of event subscriptions is critical here because - * the `data` event is also starts the stream, hence changing - * the order of event subscriptions can lead to race conditions. - * See {@link ReadableStreamEvents} for more info. - */ - this.stream.on('end', this.onStreamEnd.bind(this)); - this.stream.on('error', this.onStreamError.bind(this)); - this.stream.on('data', this.tryOnStreamData.bind(this)); - - // this allows to compose decoders together, - if a decoder - // instance is passed as a readable stream to this decoder, - // then we need to call `start` on it too - if (this.stream instanceof BaseDecoder) { - this.stream.start(); - } - - return this; - } - - /** - * Check if the decoder has been ended hence has - * no more data to produce. - */ - public get ended(): boolean { - return this._ended; - } - - /** - * Automatically catch and dispatch errors thrown inside `onStreamData`. - */ - private tryOnStreamData(data: K): void { - try { - this.onStreamData(data); - } catch (error) { - this.onStreamError(error); - } - } - - public on(event: 'data', callback: (data: T) => void): void; - public on(event: 'error', callback: (err: Error) => void): void; - public on(event: 'end', callback: () => void): void; - public on(event: TStreamListenerNames, callback: unknown): void { - if (event === 'data') { - return this.onData(callback as (data: T) => void); - } - - if (event === 'error') { - return this.onError(callback as (error: Error) => void); - } - - if (event === 'end') { - return this.onEnd(callback as () => void); - } - - assertNever(event, `Invalid event name '${event}'`); - } - - /** - * Add listener for the `data` event. - * @throws if the decoder stream has already ended. - */ - public onData(callback: (data: T) => void): void { - assert( - !this.ended, - 'Cannot subscribe to the `data` event because the decoder stream has already ended.', - ); - - let currentListeners = this._listeners.get('data'); - - if (!currentListeners) { - currentListeners = new DisposableMap(); - this._listeners.set('data', currentListeners); - } - - currentListeners.set(callback, this._onData.event(callback)); - } - - /** - * Add listener for the `error` event. - * @throws if the decoder stream has already ended. - */ - public onError(callback: (error: Error) => void): void { - assert( - !this.ended, - 'Cannot subscribe to the `error` event because the decoder stream has already ended.', - ); - - let currentListeners = this._listeners.get('error'); - - if (!currentListeners) { - currentListeners = new DisposableMap(); - this._listeners.set('error', currentListeners); - } - - currentListeners.set(callback, this._onError.event(callback)); - } - - /** - * Add listener for the `end` event. - * @throws if the decoder stream has already ended. - */ - public onEnd(callback: () => void): void { - assert( - !this.ended, - 'Cannot subscribe to the `end` event because the decoder stream has already ended.', - ); - - let currentListeners = this._listeners.get('end'); - - if (!currentListeners) { - currentListeners = new DisposableMap(); - this._listeners.set('end', currentListeners); - } - - currentListeners.set(callback, this._onEnd.event(callback)); - } - - /** - * Pauses the stream. - */ - public pause(): void { - this.stream.pause(); - } - - /** - * Resumes the stream if it has been paused. - * @throws if the decoder stream has already ended. - */ - public resume(): void { - assert( - this.ended === false, - 'Cannot resume the stream because it has already ended.', - ); - - this.stream.resume(); - } - - /** - * Destroys(disposes) the stream. - */ - public destroy(): void { - this.dispose(); - } - - /** - * Removes a previously-registered event listener for a specified event. - * - * Note! - * - the callback function must be the same as the one that was used when - * registering the event listener as it is used as an identifier to - * remove the listener - * - this method is idempotent and results in no-op if the listener is - * not found, therefore passing incorrect `callback` function may - * result in silent unexpected behavior - */ - public removeListener(eventName: TStreamListenerNames, callback: Function): void { - const listeners = this._listeners.get(eventName); - if (listeners === undefined) { - return; - } - - for (const [listener] of listeners) { - if (listener !== callback) { - continue; - } - - listeners.deleteAndDispose(listener); - } - } - - /** - * This method is called when the input stream ends. - */ - protected onStreamEnd(): void { - if (this._ended) { - return; - } - - this._ended = true; - this._onEnd.fire(); - this.settledPromise.complete(this._ended); - } - - /** - * This method is called when the input stream emits an error. - * We re-emit the error here by default, but subclasses can - * override this method to handle the error differently. - */ - private onStreamError(error: Error): void { - this._onError.fire(error); - } - - /** - * Consume all messages from the stream, blocking until the stream finishes. - * @throws if the decoder stream has already ended. - */ - public async consumeAll(): Promise { - assert( - !this._ended, - 'Cannot consume all messages of the stream that has already ended.', - ); - - const messages = []; - - for await (const maybeMessage of this) { - if (maybeMessage === null) { - break; - } - - messages.push(maybeMessage); - } - - return messages; - } - - /** - * Async iterator interface for the decoder. - * @throws if the decoder stream has already ended. - */ - [Symbol.asyncIterator](): AsyncIterator { - assert( - !this._ended, - 'Cannot iterate on messages of the stream that has already ended.', - ); - - const asyncDecoder = this._register(new AsyncDecoder(this)); - - return asyncDecoder[Symbol.asyncIterator](); - } - - public override dispose(): void { - this.settledPromise.complete(this.ended); - - this._listeners.clearAndDisposeAll(); - this.stream.destroy(); - - super.dispose(); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts deleted file mode 100644 index ba81a78738e..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/baseToken.ts +++ /dev/null @@ -1,150 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../base/common/assert.js'; -import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; - -/** - * Base class for all tokens with a `range` that reflects - * token position in the original text. - */ -export abstract class BaseToken { - constructor( - private tokenRange: Range, - ) { } - - /** - * Range of the token in the original text. - */ - public get range(): Range { - return this.tokenRange; - } - - /** - * Text representation of the token. - */ - public abstract get text(): TText; - - /** - * Check if this token has the same range as another one. - */ - public sameRange(other: Range): boolean { - return this.range.equalsRange(other); - } - - /** - * Returns a string representation of the token. - */ - public abstract toString(): string; - - /** - * Check if this token is equal to another one. - */ - public equals(other: BaseToken): other is typeof this { - if (other.constructor !== this.constructor) { - return false; - } - - if (this.text.length !== other.text.length) { - return false; - } - - if (this.text !== other.text) { - return false; - } - - return this.sameRange(other.range); - } - - /** - * Change `range` of the token with provided range components. - */ - public withRange(components: Partial): this { - this.tokenRange = new Range( - components.startLineNumber ?? this.range.startLineNumber, - components.startColumn ?? this.range.startColumn, - components.endLineNumber ?? this.range.endLineNumber, - components.endColumn ?? this.range.endColumn, - ); - - return this; - } - - /** - * Collapse range of the token to its start position. - * See {@link Range.collapseToStart} for more details. - */ - public collapseRangeToStart(): this { - this.tokenRange = this.tokenRange.collapseToStart(); - - return this; - } - - /** - * Render a list of tokens into a string. - */ - public static render( - tokens: readonly BaseToken[], - delimiter: string = '', - ): string { - return tokens.map(token => token.text).join(delimiter); - } - - /** - * Returns the full range of a list of tokens in which the first token is - * used as the start of a tokens sequence and the last token reflects the end. - * - * @throws if: - * - provided {@link tokens} list is empty - * - the first token start number is greater than the start line of the last token - * - if the first and last token are on the same line, the first token start column must - * be smaller than the start column of the last token - */ - public static fullRange(tokens: readonly BaseToken[]): Range { - assert( - tokens.length > 0, - 'Cannot get full range for an empty list of tokens.', - ); - - const firstToken = tokens[0]; - const lastToken = tokens[tokens.length - 1]; - - // sanity checks for the full range we would construct - assert( - firstToken.range.startLineNumber <= lastToken.range.startLineNumber, - 'First token must start on previous or the same line as the last token.', - ); - - if ((firstToken !== lastToken) && (firstToken.range.startLineNumber === lastToken.range.startLineNumber)) { - assert( - firstToken.range.endColumn <= lastToken.range.startColumn, - [ - 'First token must end at least on previous or the same column as the last token.', - `First token: ${firstToken}; Last token: ${lastToken}.`, - ].join('\n'), - ); - } - - return new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ); - } - - /** - * Shorten version of the {@link text} property. - */ - public shortText( - maxLength: number = 32, - ): string { - if (this.text.length <= maxLength) { - return this.text; - } - - return `${this.text.slice(0, maxLength - 1)}...`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts deleted file mode 100644 index e4eb1470a03..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/compositeToken.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from './baseToken.js'; - -/** - * Composite token consists of a list of other tokens. - * Composite token consists of a list of other tokens. - */ -export abstract class CompositeToken< - TTokens extends readonly BaseToken[], -> extends BaseToken { - /** - * Reference to the list of child tokens. - */ - protected readonly childTokens: [...TTokens]; - - constructor( - tokens: TTokens, - ) { - super(BaseToken.fullRange(tokens)); - - this.childTokens = [...tokens]; - } - - public override get text(): string { - return BaseToken.render(this.childTokens); - } - - /** - * Tokens that this composite token consists of. - */ - public get children(): TTokens { - return this.childTokens; - } - - /** - * Check if this token is equal to another one, - * including all of its child tokens. - */ - public override equals(other: BaseToken): other is typeof this { - if (super.equals(other) === false) { - return false; - } - - if (this.children.length !== other.children.length) { - return false; - } - - for (let i = 0; i < this.children.length; i++) { - const childToken = this.children[i]; - const otherChildToken = other.children[i]; - - if (childToken.equals(otherChildToken) === false) { - return false; - } - } - - return true; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts deleted file mode 100644 index 779e53a1ef1..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/constants.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { NewLine } from '../linesCodec/tokens/newLine.js'; -import { CarriageReturn } from '../linesCodec/tokens/carriageReturn.js'; -import { FormFeed, SpacingToken } from '../simpleCodec/tokens/tokens.js'; - -/** - * List of valid "space" tokens that are valid between different - * records of a Front Matter header. - */ -export const VALID_INTER_RECORD_SPACING_TOKENS = Object.freeze([ - SpacingToken, CarriageReturn, NewLine, FormFeed, -]); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts deleted file mode 100644 index 704e10f1754..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Word } from '../simpleCodec/tokens/tokens.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { VALID_INTER_RECORD_SPACING_TOKENS } from './constants.js'; -import { ReadableStream } from '../../../../../../../../base/common/stream.js'; -import { FrontMatterToken, FrontMatterRecord } from './tokens/index.js'; -import { BaseDecoder } from '../baseDecoder.js'; -import { SimpleDecoder, type TSimpleDecoderToken } from '../simpleCodec/simpleDecoder.js'; -import { ObjectStream } from '../utils/objectStream.js'; -import { PartialFrontMatterRecordNameWithDelimiter } from './parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.js'; -import { PartialFrontMatterRecord } from './parsers/frontMatterRecord/frontMatterRecord.js'; -import { PartialFrontMatterRecordName } from './parsers/frontMatterRecord/frontMatterRecordName.js'; -import { FrontMatterParserFactory } from './parsers/frontMatterParserFactory.js'; - -/** - * Tokens produced by this decoder. - */ -export type TFrontMatterToken = FrontMatterRecord | TSimpleDecoderToken; - -/** - * Decoder capable of parsing Front Matter contents from a sequence of simple tokens. - */ -export class FrontMatterDecoder extends BaseDecoder { - /** - * Current parser reference responsible for parsing a specific sequence - * of tokens into a standalone token. - */ - private current?: PartialFrontMatterRecordName | PartialFrontMatterRecordNameWithDelimiter | PartialFrontMatterRecord; - - private readonly parserFactory: FrontMatterParserFactory; - - constructor( - stream: ReadableStream | ObjectStream, - ) { - if (stream instanceof ObjectStream) { - super(stream); - } else { - super(new SimpleDecoder(stream)); - } - this.parserFactory = new FrontMatterParserFactory(); - } - - protected override onStreamData(token: TSimpleDecoderToken): void { - if (this.current !== undefined) { - const acceptResult = this.current.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - if (result === 'failure') { - this.reEmitCurrentTokens(); - - if (wasTokenConsumed === false) { - this._onData.fire(token); - } - - delete this.current; - return; - } - - const { nextParser } = acceptResult; - - if (nextParser instanceof FrontMatterToken) { - // front matter record token is the spacial case - because it can - // contain trailing space tokens, we want to emit "trimmed" record - // token and the trailing spaces tokens separately - const trimmedTokens = (nextParser instanceof FrontMatterRecord) - ? nextParser.trimValueEnd() - : []; - - this._onData.fire(nextParser); - - // re-emit all trailing space tokens if present - for (const trimmedToken of trimmedTokens) { - this._onData.fire(trimmedToken); - } - - if (wasTokenConsumed === false) { - this._onData.fire(token); - } - - delete this.current; - return; - } - - this.current = nextParser; - if (wasTokenConsumed === false) { - this._onData.fire(token); - } - - return; - } - - // a word token starts a new record - if (token instanceof Word) { - this.current = this.parserFactory.createRecordName(token); - return; - } - - // re-emit all "space" tokens immediately as all of them - // are valid while we are not in the "record parsing" mode - for (const ValidToken of VALID_INTER_RECORD_SPACING_TOKENS) { - if (token instanceof ValidToken) { - this._onData.fire(token); - return; - } - } - - // unexpected token type, re-emit existing tokens and continue - this.reEmitCurrentTokens(); - } - - protected override onStreamEnd(): void { - try { - if (this.current === undefined) { - return; - } - - assert( - this.current instanceof PartialFrontMatterRecord, - 'Only partial front matter records can be processed on stream end.', - ); - - const record = this.current.asRecordToken(); - const trimmedTokens = record.trimValueEnd(); - - this._onData.fire(record); - - for (const trimmedToken of trimmedTokens) { - this._onData.fire(trimmedToken); - } - } catch (_error) { - this.reEmitCurrentTokens(); - } finally { - delete this.current; - super.onStreamEnd(); - } - } - - /** - * Re-emit tokens accumulated so far in the current parser object. - */ - protected reEmitCurrentTokens(): void { - if (this.current === undefined) { - return; - } - - for (const token of this.current.tokens) { - this._onData.fire(token); - } - delete this.current; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterArray.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterArray.ts deleted file mode 100644 index df84fa012c7..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterArray.ts +++ /dev/null @@ -1,197 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { type PartialFrontMatterValue } from './frontMatterValue.js'; -import { FrontMatterArray } from '../tokens/frontMatterArray.js'; -import { assertDefined } from '../../../../../../../../../base/common/types.js'; -import { VALID_INTER_RECORD_SPACING_TOKENS } from '../constants.js'; -import { FrontMatterValueToken } from '../tokens/frontMatterToken.js'; -import { FrontMatterSequence } from '../tokens/frontMatterSequence.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { Comma, LeftBracket, RightBracket } from '../../simpleCodec/tokens/tokens.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; -import { type FrontMatterParserFactory } from './frontMatterParserFactory.js'; - -/** - * List of tokens that can go in-between array items - * and array brackets. - */ -const VALID_DELIMITER_TOKENS = Object.freeze([ - ...VALID_INTER_RECORD_SPACING_TOKENS, - Comma, -]); - -/** - * Responsible for parsing an array syntax (or "inline sequence" - * in YAML terms), e.g. `[1, '2', true, 2.54]` -*/ -export class PartialFrontMatterArray extends ParserBase { - /** - * Current parser reference responsible for parsing an array "value". - */ - private currentValueParser?: PartialFrontMatterValue; - - /** - * Whether an array item is allowed in the current position of the token - * sequence. E.g., items are allowed after a command or a open bracket, - * but not immediately after another item in the array. - */ - private arrayItemAllowed = true; - - constructor( - private readonly factory: FrontMatterParserFactory, - private readonly startToken: LeftBracket, - ) { - super([startToken]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - if (this.currentValueParser !== undefined) { - const acceptResult = this.currentValueParser.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - if (result === 'failure') { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed, - }; - } - - const { nextParser } = acceptResult; - - if (nextParser instanceof FrontMatterValueToken) { - this.currentTokens.push(nextParser); - delete this.currentValueParser; - - // if token was not consume, call the `accept()` method - // recursively so that the current parser can re-process - // the token (e.g., a comma or a closing square bracket) - if (wasTokenConsumed === false) { - return this.accept(token); - } - - return { - result: 'success', - nextParser: this, - wasTokenConsumed, - }; - } - - this.currentValueParser = nextParser; - return { - result: 'success', - nextParser: this, - wasTokenConsumed, - }; - } - - if (token instanceof RightBracket) { - // sanity check in case this block moves around - // to a different place in the code - assert( - this.currentValueParser === undefined, - `Unexpected end of array. Last value is not finished.`, - ); - - this.currentTokens.push(token); - - this.isConsumed = true; - return { - result: 'success', - nextParser: this.asArrayToken(), - wasTokenConsumed: true, - }; - } - - // iterate until a valid value start token is found - for (const ValidToken of VALID_DELIMITER_TOKENS) { - if (token instanceof ValidToken) { - this.currentTokens.push(token); - - if ((this.arrayItemAllowed === false) && token instanceof Comma) { - this.arrayItemAllowed = true; - } - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - } - - // is an array item value is allowed at this position, create a new - // value parser and start the value parsing process using it - if (this.arrayItemAllowed === true) { - this.currentValueParser = this.factory.createValue( - (currentToken) => { - // comma or a closing square bracket must stop the parsing - // process of the value represented by a generic sequence of tokens - return ( - (currentToken instanceof RightBracket) - || (currentToken instanceof Comma) - ); - }, - ); - this.arrayItemAllowed = false; - - return this.accept(token); - } - - // in all other cases fail because of the unexpected token type - this.isConsumed = true; - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - /** - * Convert current parser into a {@link FrontMatterArray} token, - * if possible. - * - * @throws if the last token in the accumulated token list - * is not a closing bracket ({@link RightBracket}). - */ - public asArrayToken(): FrontMatterArray { - const endToken = this.currentTokens[this.currentTokens.length - 1]; - - assertDefined( - endToken, - 'No tokens found.', - ); - - assert( - endToken instanceof RightBracket, - 'Cannot find a closing bracket of the array.', - ); - - const valueTokens: FrontMatterValueToken[] = []; - for (const currentToken of this.currentTokens) { - if ((currentToken instanceof FrontMatterValueToken) === false) { - continue; - } - - // the generic sequence tokens can have trailing spacing tokens, - // hence trim them to ensure the array contains only "clean" values - if (currentToken instanceof FrontMatterSequence) { - currentToken.trimEnd(); - } - - valueTokens.push(currentToken); - } - - this.isConsumed = true; - return new FrontMatterArray([ - this.startToken, - ...valueTokens, - endToken, - ]); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts deleted file mode 100644 index 0be65371449..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterParserFactory.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { LeftBracket } from '../../simpleCodec/tokens/brackets.js'; -import { Word } from '../../simpleCodec/tokens/word.js'; -import { FrontMatterRecordDelimiter, FrontMatterRecordName } from '../tokens/frontMatterRecord.js'; -import { TQuoteToken } from '../tokens/frontMatterString.js'; -import { PartialFrontMatterArray } from './frontMatterArray.js'; -import { PartialFrontMatterRecord } from './frontMatterRecord/frontMatterRecord.js'; -import { PartialFrontMatterRecordName } from './frontMatterRecord/frontMatterRecordName.js'; -import { PartialFrontMatterRecordNameWithDelimiter, TNameStopToken } from './frontMatterRecord/frontMatterRecordNameWithDelimiter.js'; -import { PartialFrontMatterSequence } from './frontMatterSequence.js'; -import { PartialFrontMatterString } from './frontMatterString.js'; -import { PartialFrontMatterValue } from './frontMatterValue.js'; - -export class FrontMatterParserFactory { - createRecord(tokens: [FrontMatterRecordName, FrontMatterRecordDelimiter]): PartialFrontMatterRecord { - return new PartialFrontMatterRecord(this, tokens); - } - createRecordName(startToken: Word): PartialFrontMatterRecordName { - return new PartialFrontMatterRecordName(this, startToken); - } - createRecordNameWithDelimiter(tokens: readonly [FrontMatterRecordName, TNameStopToken]): PartialFrontMatterRecordNameWithDelimiter { - return new PartialFrontMatterRecordNameWithDelimiter(this, tokens); - } - createArray(startToken: LeftBracket) { - return new PartialFrontMatterArray(this, startToken); - } - createValue(shouldStop: (token: BaseToken) => boolean): PartialFrontMatterValue { - return new PartialFrontMatterValue(this, shouldStop); - } - createString(startToken: TQuoteToken): PartialFrontMatterString { - return new PartialFrontMatterString(startToken); - } - createSequence(shouldStop: (token: BaseToken) => boolean): PartialFrontMatterSequence { - return new PartialFrontMatterSequence(shouldStop); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts deleted file mode 100644 index e4afee055b5..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecord.ts +++ /dev/null @@ -1,210 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../../baseToken.js'; -import { NewLine } from '../../../linesCodec/tokens/newLine.js'; -import { PartialFrontMatterValue } from '../frontMatterValue.js'; -import { assertNever } from '../../../../../../../../../../base/common/assert.js'; -import { assertDefined } from '../../../../../../../../../../base/common/types.js'; -import { PartialFrontMatterSequence } from '../frontMatterSequence.js'; -import { CarriageReturn } from '../../../linesCodec/tokens/carriageReturn.js'; -import { type TSimpleDecoderToken } from '../../../simpleCodec/simpleDecoder.js'; -import { Word, FormFeed, SpacingToken } from '../../../simpleCodec/tokens/tokens.js'; -import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../../simpleCodec/parserBase.js'; -import { FrontMatterValueToken, FrontMatterRecordName, FrontMatterRecordDelimiter, FrontMatterRecord } from '../../tokens/index.js'; -import { type FrontMatterParserFactory } from '../frontMatterParserFactory.js'; - -/** - * Type of a next parser that can be returned by {@link PartialFrontMatterRecord}. - */ -type TNextParser = PartialFrontMatterRecord | FrontMatterRecord; - -/** - * Parser for a `record` inside a Front Matter header. - * - * * E.g., `name: 'value'` in the example below: - * - * ``` - * --- - * name: 'value' - * isExample: true - * --- - * ``` - */ -export class PartialFrontMatterRecord extends ParserBase { - /** - * Token that represents the 'name' part of the record. - */ - private readonly recordNameToken: FrontMatterRecordName; - - /** - * Token that represents the 'delimiter' part of the record. - */ - private readonly recordDelimiterToken: FrontMatterRecordDelimiter; - - constructor( - private readonly factory: FrontMatterParserFactory, - tokens: [FrontMatterRecordName, FrontMatterRecordDelimiter], - ) { - super(tokens); - this.recordNameToken = tokens[0]; - this.recordDelimiterToken = tokens[1]; - } - - /** - * Current parser reference responsible for parsing the "value" part of the record. - */ - private valueParser?: PartialFrontMatterValue | PartialFrontMatterSequence; - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - if (this.valueParser !== undefined) { - const acceptResult = this.valueParser.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - if (result === 'failure') { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed, - }; - } - - const { nextParser } = acceptResult; - - if (nextParser instanceof FrontMatterValueToken) { - this.currentTokens.push(nextParser); - delete this.valueParser; - - this.isConsumed = true; - try { - return { - result: 'success', - nextParser: new FrontMatterRecord([ - this.recordNameToken, - this.recordDelimiterToken, - nextParser, - ]), - wasTokenConsumed, - }; - } catch (_error) { - return { - result: 'failure', - wasTokenConsumed, - }; - } - } - - this.valueParser = nextParser; - return { - result: 'success', - nextParser: this, - wasTokenConsumed, - }; - } - - // iterate until the first non-space token is found - if (token instanceof SpacingToken) { - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - // if token can start a "value" sequence, parse the value - if (PartialFrontMatterValue.isValueStartToken(token)) { - this.valueParser = this.factory.createValue(shouldEndTokenSequence); - - return this.accept(token); - } - - // in all other cases, collect all the subsequent tokens into - // a "sequence of tokens" until a new line is found - this.valueParser = this.factory.createSequence( - shouldEndTokenSequence, - ); - - // if we reached this "generic sequence" parser point, but the current token is - // already of a type that stops such sequence, we must have accumulated some - // spacing tokens, hence pass those to the parser and end the sequence immediately - if (shouldEndTokenSequence(token)) { - const spaceTokens = this.currentTokens - .slice(this.startTokensCount); - - // if no space tokens accumulated at all, create an "empty" one this is needed - // to ensure that the parser always has at least one token hence it can have - // a valid range and can be interpreted as a real "value" token of the record - if (spaceTokens.length === 0) { - spaceTokens.push( - Word.newOnLine( - '', - token.range.startLineNumber, - token.range.startColumn, - ), - ); - } - - this.valueParser.addTokens(spaceTokens); - - return { - result: 'success', - nextParser: this.asRecordToken(), - wasTokenConsumed: false, - }; - } - - // otherwise use the "generic sequence" parser moving on - return this.accept(token); - } - - /** - * Convert current parser into a {@link FrontMatterRecord} token. - * - * @throws if no current parser is present, or it is not of the {@link PartialFrontMatterValue} - * or {@link PartialFrontMatterSequence} types - */ - public asRecordToken(): FrontMatterRecord { - assertDefined( - this.valueParser, - 'Current value parser must be defined.' - ); - - if ( - (this.valueParser instanceof PartialFrontMatterValue) - || (this.valueParser instanceof PartialFrontMatterSequence) - ) { - const valueToken = this.valueParser.asSequenceToken(); - this.currentTokens.push(valueToken); - - this.isConsumed = true; - return new FrontMatterRecord([ - this.recordNameToken, - this.recordDelimiterToken, - valueToken, - ]); - } - - assertNever( - this.valueParser, - `Unexpected value parser '${this.valueParser}'.`, - ); - } -} - -/** - * Callback to check if a current token should end a - * record value that is a generic sequence of tokens. - */ -function shouldEndTokenSequence(token: BaseToken): token is (NewLine | CarriageReturn | FormFeed) { - return ( - (token instanceof NewLine) - || (token instanceof CarriageReturn) - || (token instanceof FormFeed) - ); -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts deleted file mode 100644 index 84c94b75211..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordName.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type TSimpleDecoderToken } from '../../../simpleCodec/simpleDecoder.js'; -import { FrontMatterRecordName, type TRecordNameToken } from '../../tokens/index.js'; -import { Colon, Word, Dash, SpacingToken } from '../../../simpleCodec/tokens/tokens.js'; -import { type PartialFrontMatterRecordNameWithDelimiter } from './frontMatterRecordNameWithDelimiter.js'; -import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../../simpleCodec/parserBase.js'; -import { type FrontMatterParserFactory } from '../frontMatterParserFactory.js'; - -/** - * Tokens that can be used inside a record name. - */ -const VALID_NAME_TOKENS = [Word, Dash]; - -/** - * Type of a next parser that can be returned by {@link PartialFrontMatterRecordName}. - */ -type TNextParser = PartialFrontMatterRecordName | PartialFrontMatterRecordNameWithDelimiter; - -/** - * Parser for a `name` part of a Front Matter record. - * - * E.g., `'name'` in the example below: - * - * ``` - * name: 'value' - * ``` - */ -export class PartialFrontMatterRecordName extends ParserBase { - constructor( - private readonly factory: FrontMatterParserFactory, - startToken: Word, - ) { - super([startToken]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - for (const ValidToken of VALID_NAME_TOKENS) { - if (token instanceof ValidToken) { - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - } - - // once name is followed by a "space" token or a "colon", we have the full - // record name hence can transition to the next parser - if ((token instanceof Colon) || (token instanceof SpacingToken)) { - const recordName = new FrontMatterRecordName(this.currentTokens); - - this.isConsumed = true; - return { - result: 'success', - nextParser: this.factory.createRecordNameWithDelimiter([recordName, token]), - wasTokenConsumed: true, - }; - } - - // in all other cases fail due to the unexpected token type for a record name - this.isConsumed = true; - return { - result: 'failure', - wasTokenConsumed: false, - }; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.ts deleted file mode 100644 index 59735860631..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterRecord/frontMatterRecordNameWithDelimiter.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../../../../base/common/assert.js'; -import { type PartialFrontMatterRecord } from './frontMatterRecord.js'; -import { Colon, SpacingToken } from '../../../simpleCodec/tokens/tokens.js'; -import { type TSimpleDecoderToken } from '../../../simpleCodec/simpleDecoder.js'; -import { FrontMatterRecordName, FrontMatterRecordDelimiter } from '../../tokens/index.js'; -import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../../simpleCodec/parserBase.js'; -import { type FrontMatterParserFactory } from '../frontMatterParserFactory.js'; - -/** - * Type for tokens that stop a front matter record name sequence. - */ -export type TNameStopToken = Colon | SpacingToken; - -/** - * Type for the next parser that can be returned by {@link PartialFrontMatterRecordNameWithDelimiter}. - */ -type TNextParser = PartialFrontMatterRecordNameWithDelimiter | PartialFrontMatterRecord; - -/** - * Parser for a record `name` with the `: ` delimiter. - * - * * E.g., `name:` in the example below: - * - * ``` - * name: 'value' - * ``` - */ -export class PartialFrontMatterRecordNameWithDelimiter extends ParserBase< - FrontMatterRecordName | TNameStopToken, - TNextParser -> { - constructor( - private readonly factory: FrontMatterParserFactory, - tokens: readonly [FrontMatterRecordName, TNameStopToken], - ) { - super([...tokens]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - const previousToken = this.currentTokens[this.currentTokens.length - 1]; - const isSpacingToken = (token instanceof SpacingToken); - - // delimiter must always be a `:` followed by a "space" character - // once we encounter that sequence, we can transition to the next parser - if (isSpacingToken && (previousToken instanceof Colon)) { - const recordDelimiter = new FrontMatterRecordDelimiter([ - previousToken, - token, - ]); - - const recordName = this.currentTokens[0]; - - // sanity check - assert( - recordName instanceof FrontMatterRecordName, - `Expected a front matter record name, got '${recordName}'.`, - ); - - this.isConsumed = true; - return { - result: 'success', - nextParser: this.factory.createRecord( - [recordName, recordDelimiter], - ), - wasTokenConsumed: true, - }; - } - - // allow some spacing before the colon delimiter - if (token instanceof SpacingToken) { - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - // include the colon delimiter - if (token instanceof Colon) { - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - // otherwise fail due to the unexpected token type between - // record name and record name delimiter tokens - this.isConsumed = true; - return { - result: 'failure', - wasTokenConsumed: false, - }; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts deleted file mode 100644 index 0d91c32528c..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterSequence.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { FrontMatterSequence } from '../tokens/frontMatterSequence.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; - -/** - * Parser responsible for parsing a "generic sequence of tokens" - * of an arbitrary length in a Front Matter header. - */ -export class PartialFrontMatterSequence extends ParserBase< - TSimpleDecoderToken, - PartialFrontMatterSequence | FrontMatterSequence -> { - constructor( - /** - * Callback function that is called to check if the current token - * should stop the parsing process of the current generic "value" - * sequence of arbitrary tokens by returning `true`. - * - * When this happens, the parser *will not consume* the token that - * was passed to the `shouldStop` callback or to its `accept` method. - * On the other hand, the parser will be "consumed" hence using it - * to process other tokens will yield an error. - */ - private readonly shouldStop: (token: BaseToken) => boolean, - ) { - super([]); - } - - @assertNotConsumed - public accept( - token: TSimpleDecoderToken, - ): TAcceptTokenResult { - - // collect all tokens until an end of the sequence is found - if (this.shouldStop(token)) { - this.isConsumed = true; - - return { - result: 'success', - nextParser: this.asSequenceToken(), - wasTokenConsumed: false, - }; - } - - this.currentTokens.push(token); - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Add provided tokens to the list of the current parsed tokens. - */ - public addTokens( - tokens: readonly TSimpleDecoderToken[], - ): this { - this.currentTokens.push(...tokens); - - return this; - } - - /** - * Convert the current parser into a {@link FrontMatterSequence} token. - */ - public asSequenceToken(): FrontMatterSequence { - this.isConsumed = true; - - return new FrontMatterSequence(this.currentTokens); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts deleted file mode 100644 index d0ed92746d6..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterString.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { SimpleToken } from '../../simpleCodec/tokens/tokens.js'; -import { assertDefined } from '../../../../../../../../../base/common/types.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { FrontMatterString, type TQuoteToken } from '../tokens/frontMatterString.js'; -import { assertNotConsumed, ParserBase, type TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; - -/** - * Parser responsible for parsing a string value. - */ -export class PartialFrontMatterString extends ParserBase> { - constructor( - private readonly startToken: TQuoteToken, - ) { - super([startToken]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult> { - this.currentTokens.push(token); - - // iterate until a `matching end quote` is found - if ((token instanceof SimpleToken) && (this.startToken.sameType(token))) { - return { - result: 'success', - nextParser: this.asStringToken(), - wasTokenConsumed: true, - }; - } - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Convert the current parser into a {@link FrontMatterString} token, - * if possible. - * - * @throws if the first and last tokens are not quote tokens of the same type. - */ - public asStringToken(): FrontMatterString { - const endToken = this.currentTokens[this.currentTokens.length - 1]; - - assertDefined( - endToken, - `No matching end token found.`, - ); - - assert( - this.startToken.sameType(endToken), - `String starts with \`${this.startToken.text}\`, but ends with \`${endToken.text}\`.`, - ); - - return new FrontMatterString([ - this.startToken, - ...this.currentTokens - .slice(1, this.currentTokens.length - 1), - endToken, - ]); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts deleted file mode 100644 index 710767aed40..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/parsers/frontMatterValue.ts +++ /dev/null @@ -1,184 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { type PartialFrontMatterArray } from './frontMatterArray.js'; -import { type PartialFrontMatterString } from './frontMatterString.js'; -import { asBoolean, FrontMatterBoolean } from '../tokens/frontMatterBoolean.js'; -import { FrontMatterValueToken } from '../tokens/frontMatterToken.js'; -import { PartialFrontMatterSequence } from './frontMatterSequence.js'; -import { FrontMatterSequence } from '../tokens/frontMatterSequence.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { Word, Quote, DoubleQuote, LeftBracket } from '../../simpleCodec/tokens/tokens.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; -import { type FrontMatterParserFactory } from './frontMatterParserFactory.js'; - -/** - * List of tokens that can start a "value" sequence. - * - * - {@link Word} - can be a `boolean` value - * - {@link Quote}, {@link DoubleQuote} - can start a `string` value - * - {@link LeftBracket} - can start an `array` value - */ -export const VALID_VALUE_START_TOKENS = Object.freeze([ - Quote, - DoubleQuote, - LeftBracket, -]); - -/** - * Type alias for a token that can start a "value" sequence. - */ -type TValueStartToken = InstanceType; - -/** - * Parser responsible for parsing a "value" sequence in a Front Matter header. - */ -export class PartialFrontMatterValue extends ParserBase { - /** - * Current parser reference responsible for parsing - * a specific "value" sequence. - */ - private currentValueParser?: PartialFrontMatterString | PartialFrontMatterArray | PartialFrontMatterSequence; - - /** - * Get the tokens that were accumulated so far. - */ - public override get tokens(): readonly TSimpleDecoderToken[] { - if (this.currentValueParser === undefined) { - return []; - } - - return this.currentValueParser.tokens; - } - - constructor( - private readonly factory: FrontMatterParserFactory, - /** - * Callback function to pass to the {@link PartialFrontMatterSequence} - * if the current "value" sequence is not of a specific type. - */ - private readonly shouldStop: (token: BaseToken) => boolean, - ) { - super(); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - if (this.currentValueParser !== undefined) { - const acceptResult = this.currentValueParser.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - // current value parser is consumed with its child value parser - this.isConsumed = this.currentValueParser.consumed; - - if (result === 'success') { - const { nextParser } = acceptResult; - - if (nextParser instanceof FrontMatterValueToken) { - return { - result: 'success', - nextParser, - wasTokenConsumed, - }; - } - - this.currentValueParser = nextParser; - return { - result: 'success', - nextParser: this, - wasTokenConsumed, - }; - } - - return { - result: 'failure', - wasTokenConsumed, - }; - } - - // if the first token represents a `quote` character, try to parse a string value - if ((token instanceof Quote) || (token instanceof DoubleQuote)) { - this.currentValueParser = this.factory.createString(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - // if the first token represents a `[` character, try to parse an array value - if (token instanceof LeftBracket) { - this.currentValueParser = this.factory.createArray(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - // if the first token represents a `word` try to parse a boolean - const maybeBoolean = FrontMatterBoolean.tryFromToken(token); - if (maybeBoolean !== null) { - this.isConsumed = true; - - return { - result: 'success', - nextParser: maybeBoolean, - wasTokenConsumed: true, - }; - } - - // in all other cases, collect all the subsequent tokens into - // a generic sequence of tokens until stopped by the `this.shouldStop` - // callback or the call to the 'this.asSequenceToken' method - this.currentValueParser = this.factory.createSequence(this.shouldStop); - - return this.accept(token); - } - - /** - * Check if provided token can be a start of a "value" sequence. - * See {@link VALID_VALUE_START_TOKENS} for the list of valid tokens. - */ - public static isValueStartToken( - token: BaseToken, - ): token is TValueStartToken | Word<'true' | 'false'> { - for (const ValidToken of VALID_VALUE_START_TOKENS) { - if (token instanceof ValidToken) { - return true; - } - } - - if ((token instanceof Word) && (asBoolean(token) !== null)) { - return true; - } - - return false; - } - - /** - * Check if the current 'value' sequence does not have a specific type - * and is represented by a generic sequence of tokens ({@link PartialFrontMatterSequence}). - */ - public get isSequence(): boolean { - if (this.currentValueParser === undefined) { - return false; - } - - return (this.currentValueParser instanceof PartialFrontMatterSequence); - } - - /** - * Convert current parser into a generic sequence of tokens. - */ - public asSequenceToken(): FrontMatterSequence { - this.isConsumed = true; - - return new FrontMatterSequence(this.tokens); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterArray.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterArray.ts deleted file mode 100644 index b8b01e42c79..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterArray.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { LeftBracket, RightBracket } from '../../simpleCodec/tokens/tokens.js'; -import { FrontMatterValueToken, type TValueTypeName } from './frontMatterToken.js'; - -/** - * Token that represents an `array` value in a Front Matter header. - */ -export class FrontMatterArray extends FrontMatterValueToken<'array', [ - LeftBracket, - ...FrontMatterValueToken[], - RightBracket, -]> { - /** - * Name of the `array` value type. - */ - public override readonly valueTypeName = 'array'; - - /** - * List of the array items. - */ - public get items(): readonly FrontMatterValueToken[] { - const result = []; - - for (const token of this.children) { - if (token instanceof FrontMatterValueToken) { - result.push(token); - } - } - - return result; - } - - public override toString(): string { - const itemsString = BaseToken.render(this.items, ', '); - - return `front-matter-array(${itemsString})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterBoolean.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterBoolean.ts deleted file mode 100644 index 3eae6d114ed..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterBoolean.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { Word } from '../../simpleCodec/tokens/tokens.js'; -import { FrontMatterValueToken } from './frontMatterToken.js'; -import { assertDefined } from '../../../../../../../../../base/common/types.js'; - -/** - * Token that represents a `boolean` value in a Front Matter header. - */ -export class FrontMatterBoolean extends FrontMatterValueToken<'boolean', readonly [Word]> { - /** - * Name of the `boolean` value type. - */ - public override readonly valueTypeName = 'boolean'; - - /** - * Value of the `boolean` token. - */ - public readonly value: boolean; - - /** - * @throws if provided {@link Word} cannot be converted to a `boolean` value. - */ - constructor(token: Word) { - const value = asBoolean(token); - assertDefined( - value, - `Cannot convert '${token}' to a boolean value.`, - ); - - super([token]); - - this.value = value; - } - - /** - * Try creating a {@link FrontMatterBoolean} out of provided token. - * Unlike the constructor, this method does not throw, returning - * a 'null' value on failure instead. - */ - public static tryFromToken( - token: BaseToken, - ): FrontMatterBoolean | null { - if (token instanceof Word === false) { - return null; - } - - try { - return new FrontMatterBoolean(token); - } catch (_error) { - // noop - return null; - } - } - - public override equals(other: BaseToken): other is typeof this { - if (super.equals(other) === false) { - return false; - } - - return this.value === other.value; - } - - public override toString(): string { - return `front-matter-boolean(${this.shortText()})${this.range}`; - } -} - -/** - * Try to convert a {@link Word} token to a `boolean` value. - */ -export function asBoolean( - token: Word, -): boolean | null { - if (token.text.toLowerCase() === 'true') { - return true; - } - - if (token.text.toLowerCase() === 'false') { - return false; - } - - return null; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterRecord.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterRecord.ts deleted file mode 100644 index dc498c6d667..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterRecord.ts +++ /dev/null @@ -1,118 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { FrontMatterSequence } from './frontMatterSequence.js'; -import { Colon, Word, Dash, SpacingToken } from '../../simpleCodec/tokens/tokens.js'; -import { FrontMatterToken, FrontMatterValueToken, type TValueTypeName } from './frontMatterToken.js'; - -/** - * Type for tokens that can be used inside a record name. - */ -export type TNameToken = Word | Dash; - -/** - * Token representing a `record name` inside a Front Matter record. - * - * E.g., `name` in the example below: - * - * ``` - * --- - * name: 'value' - * --- - * ``` - */ -export class FrontMatterRecordName extends FrontMatterToken { - public override toString(): string { - return `front-matter-record-name(${this.shortText()})${this.range}`; - } -} - -/** - * Token representing a delimiter of a record inside a Front Matter header. - * - * E.g., `: ` in the example below: - * - * ``` - * --- - * name: 'value' - * --- - * ``` - */ -export class FrontMatterRecordDelimiter extends FrontMatterToken { - public override toString(): string { - return `front-matter-delimiter(${this.shortText()})${this.range}`; - } -} - -/** - * Token representing a `record` inside a Front Matter header. - * - * E.g., `name: 'value'` in the example below: - * - * ``` - * --- - * name: 'value' - * --- - * ``` - */ -export class FrontMatterRecord extends FrontMatterToken< - readonly [FrontMatterRecordName, FrontMatterRecordDelimiter, FrontMatterValueToken] -> { - /** - * Token that represent `name` of the record. - * - * E.g., `tools` in the example below: - * - * ``` - * --- - * tools: ['value'] - * --- - * ``` - */ - public get nameToken(): FrontMatterRecordName { - return this.children[0]; - } - - /** - * Token that represent `value` of the record. - * - * E.g., `['value']` in the example below: - * - * ``` - * --- - * tools: ['value'] - * --- - * ``` - */ - public get valueToken(): FrontMatterValueToken { - return this.children[2]; - } - - /** - * Trim spacing tokens at the end of the record. - */ - public trimValueEnd(): readonly SpacingToken[] { - const { valueToken } = this; - - // only the "generic sequence" value tokens can hold - // some spacing tokens at the end of them - if ((valueToken instanceof FrontMatterSequence) === false) { - return []; - } - - const trimmedTokens = valueToken.trimEnd(); - // update the current range to reflect the current trimmed value - this.withRange( - BaseToken.fullRange(this.children), - ); - - return trimmedTokens; - } - - public override toString(): string { - return `front-matter-record(${this.shortText()})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.ts deleted file mode 100644 index dcb6f70d5a3..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { FrontMatterValueToken } from './frontMatterToken.js'; -import { Word, SpacingToken } from '../../simpleCodec/tokens/tokens.js'; -import { type TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; - - -/** - * Token represents a generic sequence of tokens in a Front Matter header. - */ -export class FrontMatterSequence extends FrontMatterValueToken { - /** - * @override Because this token represent a generic sequence of tokens, - * the type name is represented by the sequence of tokens itself - */ - public override get valueTypeName(): this { - return this; - } - - /** - * Text of the sequence value. The method exists to provide a - * consistent interface with {@link FrontMatterString} token. - * - * Note! that this method does not automatically trim spacing tokens - * in the sequence. If you need to get a trimmed value, call - * {@link trimEnd} method first. - */ - public get cleanText(): string { - return this.text; - } - - /** - * Trim spacing tokens at the end of the sequence. - */ - public trimEnd(): readonly SpacingToken[] { - const trimmedTokens = []; - - // iterate the tokens list from the end to the start, collecting - // all the spacing tokens we encounter until we reach a non-spacing token - let lastNonSpace = this.childTokens.length - 1; - while (lastNonSpace >= 0) { - const token = this.childTokens[lastNonSpace]; - - if (token instanceof SpacingToken) { - trimmedTokens.push(token); - lastNonSpace--; - - continue; - } - - break; - } - this.childTokens.length = lastNonSpace + 1; - - // if there are only spacing tokens were present add a single - // empty token to the sequence, so it has something to work with - if (this.childTokens.length === 0) { - this.collapseRangeToStart(); - this.childTokens.push(new Word(this.range, '')); - } - - // update the current range to reflect the current trimmed value - this.withRange( - BaseToken.fullRange(this.childTokens), - ); - - // trimmed tokens are collected starting from the end, - // moving to the start, hence reverse them before returning - return trimmedTokens.reverse(); - } - - public override toString(): string { - return `front-matter-sequence(${this.shortText()})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterString.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterString.ts deleted file mode 100644 index 8a7283d1098..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterString.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { FrontMatterValueToken } from './frontMatterToken.js'; -import { Quote, DoubleQuote } from '../../simpleCodec/tokens/tokens.js'; - -/** - * Type for any quote token that can be used to wrap a string. - */ -export type TQuoteToken = Quote | DoubleQuote; - -/** - * Token that represents a string value in a Front Matter header. - */ -export class FrontMatterString extends FrontMatterValueToken< - 'quoted-string', - readonly [TQuote, ...BaseToken[], TQuote] -> { - /** - * Name of the `string` value type. - */ - public override readonly valueTypeName = 'quoted-string'; - - /** - * Text of the string value without the wrapping quotes. - */ - public get cleanText(): string { - return BaseToken.render( - this.children.slice(1, this.children.length - 1), - ); - } - - public override toString(): string { - return `front-matter-string(${this.shortText()})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterToken.ts deleted file mode 100644 index d85f85230d7..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterToken.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { CompositeToken } from '../../compositeToken.js'; -import { FrontMatterSequence } from './frontMatterSequence.js'; - -/** - * Base class for all tokens inside a Front Matter header. - */ -export abstract class FrontMatterToken< - TTokens extends readonly BaseToken[] = readonly BaseToken[], -> extends CompositeToken { } - -/** - * List of all currently supported value types. - */ -export type TValueTypeName = 'quoted-string' | 'boolean' | 'array' | FrontMatterSequence; - -/** - * Base class for all tokens that represent a `value` inside a Front Matter header. - */ -export abstract class FrontMatterValueToken< - TTypeName extends TValueTypeName = TValueTypeName, - TTokens extends readonly BaseToken[] = readonly BaseToken[], -> extends FrontMatterToken { - /** - * Type name of the `value` represented by this token. - */ - public abstract readonly valueTypeName: TTypeName; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.ts deleted file mode 100644 index 033a1250fa5..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export { FrontMatterArray } from './frontMatterArray.js'; -export { FrontMatterString } from './frontMatterString.js'; -export { FrontMatterBoolean } from './frontMatterBoolean.js'; -export { FrontMatterToken, FrontMatterValueToken } from './frontMatterToken.js'; -export { - FrontMatterRecordName, - FrontMatterRecordDelimiter, - FrontMatterRecord, - type TNameToken as TRecordNameToken, -} from './frontMatterRecord.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/linesDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/linesDecoder.ts deleted file mode 100644 index 5d9ad249d6f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/linesDecoder.ts +++ /dev/null @@ -1,237 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Line } from './tokens/line.js'; -import { Range } from '../../../../../../../../editor/common/core/range.js'; -import { NewLine } from './tokens/newLine.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; -import { CarriageReturn } from './tokens/carriageReturn.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { assertDefined } from '../../../../../../../../base/common/types.js'; -import { BaseDecoder } from '../baseDecoder.js'; - -/** - * Any line break token type. - */ -export type TLineBreakToken = CarriageReturn | NewLine; - -/** - * Tokens produced by the {@link LinesDecoder}. - */ -export type TLineToken = Line | TLineBreakToken; - -/** - * The `decoder` part of the `LinesCodec` and is able to transform - * data from a binary stream into a stream of text lines(`Line`). - */ -export class LinesDecoder extends BaseDecoder { - /** - * Buffered received data yet to be processed. - */ - private buffer: VSBuffer = VSBuffer.alloc(0); - - /** - * The last emitted `Line` token, if any. The value is used - * to correctly emit remaining line range in the `onStreamEnd` - * method when underlying input stream ends and `buffer` still - * contains some data that must be emitted as the last line. - */ - private lastEmittedLine?: Line; - - /** - * Process data received from the input stream. - */ - protected override onStreamData(chunk: VSBuffer): void { - this.buffer = VSBuffer.concat([this.buffer, chunk]); - - this.processData(false); - } - - /** - * Process buffered data. - * - * @param streamEnded Flag that indicates if the input stream has ended, - * which means that is the last call of this method. - * @throws If internal logic implementation error is detected. - */ - private processData( - streamEnded: boolean, - ): void { - // iterate over each line of the data buffer, emitting each line - // as a `Line` token followed by a `NewLine` token, if applies - while (this.buffer.byteLength > 0) { - // get line number based on a previously emitted line, if any - const lineNumber = this.lastEmittedLine - ? this.lastEmittedLine.range.startLineNumber + 1 - : 1; - - // find the `\r`, `\n`, or `\r\n` tokens in the data - const endOfLineTokens = this.findEndOfLineTokens( - lineNumber, - streamEnded, - ); - const firstToken: (NewLine | CarriageReturn | undefined) = endOfLineTokens[0]; - - // if no end-of-the-line tokens found, stop the current processing - // attempt because we either (1) need more data to be received or - // (2) the stream has ended; in the case (2) remaining data must - // be emitted as the last line - if (firstToken === undefined) { - // (2) if `streamEnded`, we need to emit the whole remaining - // data as the last line immediately - if (streamEnded) { - this.emitLine(lineNumber, this.buffer.slice(0)); - } - - break; - } - - // emit the line found in the data as the `Line` token - this.emitLine(lineNumber, this.buffer.slice(0, firstToken.range.startColumn - 1)); - - // must always hold true as the `emitLine` above sets this - assertDefined( - this.lastEmittedLine, - 'No last emitted line found.', - ); - - // Note! A standalone `\r` token case is not a well-defined case, and - // was primarily used by old Mac OSx systems which treated it as - // a line ending (same as `\n`). Hence for backward compatibility - // with those systems, we treat it as a new line token as well. - // We do that by replacing standalone `\r` token with `\n` one. - if ((endOfLineTokens.length === 1) && (firstToken instanceof CarriageReturn)) { - endOfLineTokens.splice(0, 1, new NewLine(firstToken.range)); - } - - // emit the end-of-the-line tokens - let startColumn = this.lastEmittedLine.range.endColumn; - for (const token of endOfLineTokens) { - const byteLength = token.byte.byteLength; - const endColumn = startColumn + byteLength; - // emit the token updating its column start/end numbers based on - // the emitted line text length and previous end-of-the-line token - this._onData.fire(token.withRange({ startColumn, endColumn })); - // shorten the data buffer by the length of the token - this.buffer = this.buffer.slice(byteLength); - // update the start column for the next token - startColumn = endColumn; - } - } - - // if the stream has ended, assert that the input data buffer is now empty - // otherwise we have a logic error and leaving some buffered data behind - if (streamEnded) { - assert( - this.buffer.byteLength === 0, - 'Expected the input data buffer to be empty when the stream ends.', - ); - } - } - - /** - * Find the end of line tokens in the data buffer. - * Can return: - * - [`\r`, `\n`] tokens if the sequence is found - * - [`\r`] token if only the carriage return is found - * - [`\n`] token if only the newline is found - * - an `empty array` if no end of line tokens found - */ - private findEndOfLineTokens( - lineNumber: number, - streamEnded: boolean, - ): (CarriageReturn | NewLine)[] { - const result = []; - - // find the first occurrence of the carriage return and newline tokens - const carriageReturnIndex = this.buffer.indexOf(CarriageReturn.byte); - const newLineIndex = this.buffer.indexOf(NewLine.byte); - - // if the `\r` comes before the `\n`(if `\n` present at all) - if (carriageReturnIndex >= 0 && ((carriageReturnIndex < newLineIndex) || (newLineIndex === -1))) { - // add the carriage return token first - result.push( - new CarriageReturn(new Range( - lineNumber, - (carriageReturnIndex + 1), - lineNumber, - (carriageReturnIndex + 1) + CarriageReturn.byte.byteLength, - )), - ); - - // if the `\r\n` sequence - if (newLineIndex === carriageReturnIndex + 1) { - // add the newline token to the result - result.push( - new NewLine(new Range( - lineNumber, - (newLineIndex + 1), - lineNumber, - (newLineIndex + 1) + NewLine.byte.byteLength, - )), - ); - } - - // either `\r` or `\r\n` cases found; if we have the `\r` token, we can return - // the end-of-line tokens only, if the `\r` is followed by at least one more - // character (it could be a `\n` or any other character), or if the stream has - // ended (which means the `\r` is at the end of the line) - if ((this.buffer.byteLength > carriageReturnIndex + 1) || streamEnded) { - return result; - } - - // in all other cases, return the empty array (no lend-of-line tokens found) - return []; - } - - // no `\r`, but there is `\n` - if (newLineIndex >= 0) { - result.push( - new NewLine(new Range( - lineNumber, - (newLineIndex + 1), - lineNumber, - (newLineIndex + 1) + NewLine.byte.byteLength, - )), - ); - } - - // neither `\r` nor `\n` found, no end of line found at all - return result; - } - - /** - * Emit a provided line as the `Line` token to the output stream. - */ - private emitLine( - lineNumber: number, // Note! 1-based indexing - lineBytes: VSBuffer, - ): void { - - const line = new Line(lineNumber, lineBytes.toString()); - this._onData.fire(line); - - // store the last emitted line so we can use it when we need - // to send the remaining line in the `onStreamEnd` method - this.lastEmittedLine = line; - - // shorten the data buffer by the length of the line emitted - this.buffer = this.buffer.slice(lineBytes.byteLength); - } - - /** - * Handle the end of the input stream - if the buffer still has some data, - * emit it as the last available line token before firing the `onEnd` event. - */ - protected override onStreamEnd(): void { - // if the input data buffer is not empty when the input stream ends, emit - // the remaining data as the last line before firing the `onEnd` event - if (this.buffer.byteLength > 0) { - this.processData(true); - } - - super.onStreamEnd(); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.ts deleted file mode 100644 index 46b3031829e..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from '../../../../../../../../../base/common/buffer.js'; -import { SimpleToken } from '../../simpleCodec/tokens/simpleToken.js'; - -/** - * Token that represent a `carriage return` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class CarriageReturn extends SimpleToken<'\r'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '\r' = '\r'; - - /** - * The byte representation of the {@link symbol}. - */ - public static readonly byte = VSBuffer.fromString(CarriageReturn.symbol); - - /** - * The byte representation of the token. - */ - public get byte(): VSBuffer { - return CarriageReturn.byte; - } - - /** - * Return text representation of the token. - */ - public override get text(): '\r' { - return CarriageReturn.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `CR${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/line.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/line.ts deleted file mode 100644 index 2fa0afef6ff..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/line.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; - -/** - * Token representing a line of text with a `range` which - * reflects the line's position in the original data. - */ -export class Line extends BaseToken { - constructor( - // the line index - // Note! 1-based indexing - lineNumber: number, - // the line contents - public readonly text: string, - ) { - assert( - !isNaN(lineNumber), - `The line number must not be a NaN.`, - ); - - assert( - lineNumber > 0, - `The line number must be >= 1, got "${lineNumber}".`, - ); - - super( - new Range( - lineNumber, - 1, - lineNumber, - text.length + 1, - ), - ); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `line("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/newLine.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/newLine.ts deleted file mode 100644 index 6d95751721d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/linesCodec/tokens/newLine.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from '../../../../../../../../../base/common/buffer.js'; -import { SimpleToken } from '../../simpleCodec/tokens/simpleToken.js'; - -/** - * A token that represent a `new line` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class NewLine extends SimpleToken<'\n'> { - /** - * The underlying symbol of the `NewLine` token. - */ - public static override readonly symbol: '\n' = '\n'; - - /** - * The byte representation of the {@link symbol}. - */ - public static readonly byte = VSBuffer.fromString(NewLine.symbol); - - /** - * Return text representation of the token. - */ - public override get text(): '\n' { - return NewLine.symbol; - } - - /** - * The byte representation of the token. - */ - public get byte(): VSBuffer { - return NewLine.byte; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `newline${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.ts deleted file mode 100644 index 67bbda1f2ec..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.ts +++ /dev/null @@ -1,136 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownToken } from './tokens/markdownToken.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { LeftBracket } from '../simpleCodec/tokens/brackets.js'; -import { PartialMarkdownImage } from './parsers/markdownImage.js'; -import { ReadableStream } from '../../../../../../../../base/common/stream.js'; -import { TSimpleDecoderToken } from '../simpleCodec/simpleDecoder.js'; -import { LeftAngleBracket } from '../simpleCodec/tokens/angleBrackets.js'; -import { ExclamationMark } from '../simpleCodec/tokens/exclamationMark.js'; -import { BaseDecoder } from '../baseDecoder.js'; -import { MarkdownCommentStart, PartialMarkdownCommentStart } from './parsers/markdownComment.js'; -import { MarkdownExtensionsDecoder } from '../markdownExtensionsCodec/markdownExtensionsDecoder.js'; -import { MarkdownLinkCaption, PartialMarkdownLink, PartialMarkdownLinkCaption } from './parsers/markdownLink.js'; - -/** - * Tokens produced by this decoder. - */ -export type TMarkdownToken = MarkdownToken | TSimpleDecoderToken; - -/** - * Decoder capable of parsing markdown entities (e.g., links) from a sequence of simple tokens. - */ -export class MarkdownDecoder extends BaseDecoder { - /** - * Current parser object that is responsible for parsing a sequence of tokens into - * some markdown entity. Set to `undefined` when no parsing is in progress at the moment. - */ - private current?: - PartialMarkdownLinkCaption | MarkdownLinkCaption | PartialMarkdownLink | - PartialMarkdownCommentStart | MarkdownCommentStart | - PartialMarkdownImage; - - constructor( - stream: ReadableStream, - ) { - super(new MarkdownExtensionsDecoder(stream)); - } - - protected override onStreamData(token: TSimpleDecoderToken): void { - // `markdown links` start with `[` character, so here we can - // initiate the process of parsing a markdown link - if (token instanceof LeftBracket && !this.current) { - this.current = new PartialMarkdownLinkCaption(token); - - return; - } - - // `markdown comments` start with `<` character, so here we can - // initiate the process of parsing a markdown comment - if (token instanceof LeftAngleBracket && !this.current) { - this.current = new PartialMarkdownCommentStart(token); - - return; - } - - // `markdown image links` start with `!` character, so here we can - // initiate the process of parsing a markdown image - if (token instanceof ExclamationMark && !this.current) { - this.current = new PartialMarkdownImage(token); - - return; - } - - // if current parser was not initiated before, - we are not inside a sequence - // of tokens we care about, therefore re-emit the token immediately and continue - if (!this.current) { - this._onData.fire(token); - return; - } - - // if there is a current parser object, submit the token to it - // so it can progress with parsing the tokens sequence - const parseResult = this.current.accept(token); - if (parseResult.result === 'success') { - const { nextParser } = parseResult; - - // if got a fully parsed out token back, emit it and reset - // the current parser object so a new parsing process can start - if (nextParser instanceof MarkdownToken) { - this._onData.fire(nextParser); - delete this.current; - } else { - // otherwise, update the current parser object - this.current = nextParser; - } - } else { - // if failed to parse a sequence of a tokens as a single markdown - // entity (e.g., a link), re-emit the tokens accumulated so far - // then reset the current parser object - for (const currentToken of this.current.tokens) { - this._onData.fire(currentToken); - } - - delete this.current; - } - - // if token was not consumed by the parser, call `onStreamData` again - // so the token is properly handled by the decoder in the case when a - // new sequence starts with this token - if (!parseResult.wasTokenConsumed) { - this.onStreamData(token); - } - } - - protected override onStreamEnd(): void { - // if the stream has ended and there is a current incomplete parser - // object present, handle the remaining parser object - if (this.current) { - // if a `markdown comment` does not have an end marker `-->` - // it is still a comment that extends to the end of the file - // so re-emit the current parser as a comment token - if (this.current instanceof MarkdownCommentStart) { - this._onData.fire(this.current.asMarkdownComment()); - delete this.current; - this.onStreamEnd(); - - return; - } - - // in all other cases, re-emit existing parser tokens - const { tokens } = this.current; - - for (const token of [...tokens]) { - this._onData.fire(token); - } - - delete this.current; - } - - super.onStreamEnd(); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownComment.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownComment.ts deleted file mode 100644 index a017649f8b8..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownComment.ts +++ /dev/null @@ -1,173 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../baseToken.js'; -import { Dash } from '../../simpleCodec/tokens/dash.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { MarkdownComment } from '../tokens/markdownComment.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { ExclamationMark } from '../../simpleCodec/tokens/exclamationMark.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; - -/** - * The parser responsible for parsing the ``. If it does, - * then the parser transitions to the {@link MarkdownComment} token. - */ -export class MarkdownCommentStart extends ParserBase { - constructor(tokens: [LeftAngleBracket, ExclamationMark, Dash, Dash]) { - super(tokens); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if received `>` while current token sequence ends with `--`, - // then this is the end of the comment sequence - if (token instanceof RightAngleBracket && this.endsWithDashes) { - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this.asMarkdownComment(), - wasTokenConsumed: true, - }; - } - - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Convert the current token sequence into a {@link MarkdownComment} token. - * - * Note! that this method marks the current parser object as "consumed" - * hence it should not be used after this method is called. - */ - public asMarkdownComment(): MarkdownComment { - this.isConsumed = true; - - return new MarkdownComment( - this.range, - BaseToken.render(this.currentTokens), - ); - } - - /** - * Get range of current token sequence. - */ - private get range(): Range { - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - const range = new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ); - - return range; - } - - /** - * Whether the current token sequence ends with two dashes. - */ - private get endsWithDashes(): boolean { - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - if (!(lastToken instanceof Dash)) { - return false; - } - - const secondLastToken = this.currentTokens[this.currentTokens.length - 2]; - if (!(secondLastToken instanceof Dash)) { - return false; - } - - return true; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownImage.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownImage.ts deleted file mode 100644 index 5684f48de4a..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownImage.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownLink } from '../tokens/markdownLink.js'; -import { MarkdownImage } from '../tokens/markdownImage.js'; -import { LeftBracket } from '../../simpleCodec/tokens/brackets.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { ExclamationMark } from '../../simpleCodec/tokens/exclamationMark.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; -import { MarkdownLinkCaption, PartialMarkdownLink, PartialMarkdownLinkCaption } from './markdownLink.js'; - -/** - * The parser responsible for parsing the `markdown image` sequence of characters. - * E.g., `![alt text](./path/to/image.jpeg)` syntax. - */ -export class PartialMarkdownImage extends ParserBase { - /** - * Current active parser instance, if in the mode of actively parsing the markdown link sequence. - */ - private markdownLinkParser: PartialMarkdownLinkCaption | MarkdownLinkCaption | PartialMarkdownLink | undefined; - - constructor(token: ExclamationMark) { - super([token]); - } - - /** - * Get all currently available tokens of the `markdown link` sequence. - */ - public override get tokens(): readonly TSimpleDecoderToken[] { - const linkTokens = this.markdownLinkParser?.tokens ?? []; - - return [ - ...this.currentTokens, - ...linkTokens, - ]; - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // on the first call we expect a character that begins `markdown link` sequence - // hence we initiate the markdown link parsing process, otherwise we fail - if (!this.markdownLinkParser) { - if (token instanceof LeftBracket) { - this.markdownLinkParser = new PartialMarkdownLinkCaption(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // handle subsequent tokens next - - const acceptResult = this.markdownLinkParser.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - if (result === 'success') { - const { nextParser } = acceptResult; - - // if full markdown link was parsed out, the process completes - if (nextParser instanceof MarkdownLink) { - this.isConsumed = true; - - const firstToken = this.currentTokens[0]; - return { - result, - wasTokenConsumed, - nextParser: new MarkdownImage( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - `${firstToken.text}${nextParser.caption}`, - nextParser.reference, - ), - }; - } - - // otherwise save new link parser reference and continue - this.markdownLinkParser = nextParser; - return { - result, - wasTokenConsumed, - nextParser: this, - }; - } - - // return the failure result - this.isConsumed = true; - return acceptResult; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownLink.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownLink.ts deleted file mode 100644 index 641ddd14b9b..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/parsers/markdownLink.ts +++ /dev/null @@ -1,211 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { MarkdownLink } from '../tokens/markdownLink.js'; -import { NewLine } from '../../linesCodec/tokens/newLine.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { FormFeed } from '../../simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../../linesCodec/tokens/carriageReturn.js'; -import { LeftBracket, RightBracket } from '../../simpleCodec/tokens/brackets.js'; -import { ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; -import { LeftParenthesis, RightParenthesis } from '../../simpleCodec/tokens/parentheses.js'; - -/** - * List of characters that are not allowed in links so stop a markdown link sequence abruptly. - */ -const MARKDOWN_LINK_STOP_CHARACTERS: readonly string[] = [CarriageReturn, NewLine, VerticalTab, FormFeed] - .map((token) => { return token.symbol; }); - -/** - * The parser responsible for parsing a `markdown link caption` part of a markdown - * link (e.g., the `[caption text]` part of the `[caption text](./some/path)` link). - * - * The parsing process starts with single `[` token and collects all tokens until - * the first `]` token is encountered. In this successful case, the parser transitions - * into the {@link MarkdownLinkCaption} parser type which continues the general - * parsing process of the markdown link. - * - * Otherwise, if one of the stop characters defined in the {@link MARKDOWN_LINK_STOP_CHARACTERS} - * is encountered before the `]` token, the parsing process is aborted which is communicated to - * the caller by returning a `failure` result. In this case, the caller is assumed to be responsible - * for re-emitting the {@link tokens} accumulated so far as standalone entities since they are no - * longer represent a coherent token entity of a larger size. - */ -export class PartialMarkdownLinkCaption extends ParserBase { - constructor(token: LeftBracket) { - super([token]); - } - - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // any of stop characters is are breaking a markdown link caption sequence - if (MARKDOWN_LINK_STOP_CHARACTERS.includes(token.text)) { - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // the `]` character ends the caption of a markdown link - if (token instanceof RightBracket) { - return { - result: 'success', - nextParser: new MarkdownLinkCaption([...this.tokens, token]), - wasTokenConsumed: true, - }; - } - - // otherwise, include the token in the sequence - // and keep the current parser object instance - this.currentTokens.push(token); - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } -} - -/** - * The parser responsible for transitioning from a {@link PartialMarkdownLinkCaption} - * parser to the {@link PartialMarkdownLink} one, therefore serves a parser glue between - * the `[caption]` and the `(./some/path)` parts of the `[caption](./some/path)` link. - * - * The only successful case of this parser is the `(` token that initiated the process - * of parsing the `reference` part of a markdown link and in this case the parser - * transitions into the `PartialMarkdownLink` parser type. - * - * Any other character is considered a failure result. In this case, the caller is assumed - * to be responsible for re-emitting the {@link tokens} accumulated so far as standalone - * entities since they are no longer represent a coherent token entity of a larger size. - */ -export class MarkdownLinkCaption extends ParserBase { - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // the `(` character starts the link part of a markdown link - // that is the only character that can follow the caption - if (token instanceof LeftParenthesis) { - return { - result: 'success', - wasTokenConsumed: true, - nextParser: new PartialMarkdownLink([...this.tokens], token), - }; - } - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } -} - -/** - * The parser responsible for parsing a `link reference` part of a markdown link - * (e.g., the `(./some/path)` part of the `[caption text](./some/path)` link). - * - * The parsing process starts with tokens that represent the `[caption]` part of a markdown - * link, followed by the `(` token. The parser collects all subsequent tokens until final closing - * parenthesis (`)`) is encountered (*\*see [1] below*). In this successful case, the parser object - * transitions into the {@link MarkdownLink} token type which signifies the end of the entire - * parsing process of the link text. - * - * Otherwise, if one of the stop characters defined in the {@link MARKDOWN_LINK_STOP_CHARACTERS} - * is encountered before the final `)` token, the parsing process is aborted which is communicated to - * the caller by returning a `failure` result. In this case, the caller is assumed to be responsible - * for re-emitting the {@link tokens} accumulated so far as standalone entities since they are no - * longer represent a coherent token entity of a larger size. - * - * `[1]` The `reference` part of the markdown link can contain any number of nested parenthesis, e.g., - * `[caption](/some/p(th/file.md)` is a valid markdown link and a valid folder name, hence number - * of open parenthesis must match the number of closing ones and the path sequence is considered - * to be complete as soon as this requirement is met. Therefore the `final` word is used in - * the description comments above to highlight this important detail. - */ -export class PartialMarkdownLink extends ParserBase { - /** - * Number of open parenthesis in the sequence. - * See comment in the {@link accept} method for more details. - */ - private openParensCount: number = 1; - - constructor( - protected readonly captionTokens: TSimpleDecoderToken[], - token: LeftParenthesis, - ) { - super([token]); - } - - public override get tokens(): readonly TSimpleDecoderToken[] { - return [...this.captionTokens, ...this.currentTokens]; - } - - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // markdown links allow for nested parenthesis inside the link reference part, but - // the number of open parenthesis must match the number of closing parenthesis, e.g.: - // - `[caption](/some/p()th/file.md)` is a valid markdown link - // - `[caption](/some/p(th/file.md)` is an invalid markdown link - // hence we use the `openParensCount` variable to keep track of the number of open - // parenthesis encountered so far; then upon encountering a closing parenthesis we - // decrement the `openParensCount` and if it reaches 0 - we consider the link reference - // to be complete - - if (token instanceof LeftParenthesis) { - this.openParensCount += 1; - } - - if (token instanceof RightParenthesis) { - this.openParensCount -= 1; - - // sanity check! this must alway hold true because we return a complete markdown - // link as soon as we encounter matching number of closing parenthesis, hence - // we must never have `openParensCount` that is less than 0 - assert( - this.openParensCount >= 0, - `Unexpected right parenthesis token encountered: '${token}'.`, - ); - - // the markdown link is complete as soon as we get the same number of closing parenthesis - if (this.openParensCount === 0) { - const { startLineNumber, startColumn } = this.captionTokens[0].range; - - // create link caption string - const caption = BaseToken.render(this.captionTokens); - - // create link reference string - this.currentTokens.push(token); - const reference = BaseToken.render(this.currentTokens); - - // return complete markdown link object - return { - result: 'success', - wasTokenConsumed: true, - nextParser: new MarkdownLink( - startLineNumber, - startColumn, - caption, - reference, - ), - }; - } - } - - // any of stop characters is are breaking a markdown link reference sequence - if (MARKDOWN_LINK_STOP_CHARACTERS.includes(token.text)) { - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // the rest of the tokens can be included in the sequence - this.currentTokens.push(token); - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.ts deleted file mode 100644 index 8eadff3589a..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { MarkdownToken } from './markdownToken.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; - -/** - * A token that represent a `markdown comment` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class MarkdownComment extends MarkdownToken { - constructor( - range: Range, - public readonly text: string, - ) { - assert( - text.startsWith('`. - */ - public get hasEndMarker(): boolean { - return this.text.endsWith('-->'); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `md-comment("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.ts deleted file mode 100644 index cab120f53db..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.ts +++ /dev/null @@ -1,125 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownToken } from './markdownToken.js'; -import { IRange, Range } from '../../../../../../../../../editor/common/core/range.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; - -/** - * A token that represent a `markdown image` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class MarkdownImage extends MarkdownToken { - /** - * Check if this `markdown image link` points to a valid URL address. - */ - public readonly isURL: boolean; - - constructor( - /** - * The starting line number of the image (1-based indexing). - */ - lineNumber: number, - /** - * The starting column number of the image (1-based indexing). - */ - columnNumber: number, - /** - * The caption of the image, including the `!` and `square brackets`. - */ - private readonly caption: string, - /** - * The reference of the image, including the parentheses. - */ - private readonly reference: string, - ) { - assert( - !isNaN(lineNumber), - `The line number must not be a NaN.`, - ); - - assert( - lineNumber > 0, - `The line number must be >= 1, got "${lineNumber}".`, - ); - - assert( - columnNumber > 0, - `The column number must be >= 1, got "${columnNumber}".`, - ); - - assert( - caption[0] === '!', - `The caption must start with '!' character, got "${caption}".`, - ); - - assert( - caption[1] === '[' && caption[caption.length - 1] === ']', - `The caption must be enclosed in square brackets, got "${caption}".`, - ); - - assert( - reference[0] === '(' && reference[reference.length - 1] === ')', - `The reference must be enclosed in parentheses, got "${reference}".`, - ); - - super( - new Range( - lineNumber, - columnNumber, - lineNumber, - columnNumber + caption.length + reference.length, - ), - ); - - // set up the `isURL` flag based on the current - try { - new URL(this.path); - this.isURL = true; - } catch { - this.isURL = false; - } - } - - public override get text(): string { - return `${this.caption}${this.reference}`; - } - - /** - * Returns the `reference` part of the link without enclosing parentheses. - */ - public get path(): string { - return this.reference.slice(1, this.reference.length - 1); - } - - /** - * Get the range of the `link part` of the token. - */ - public get linkRange(): IRange | undefined { - if (this.path.length === 0) { - return undefined; - } - - const { range } = this; - - // note! '+1' for openning `(` of the link - const startColumn = range.startColumn + this.caption.length + 1; - const endColumn = startColumn + this.path.length; - - return new Range( - range.startLineNumber, - startColumn, - range.endLineNumber, - endColumn, - ); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `md-image("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.ts deleted file mode 100644 index a963628675d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownToken } from './markdownToken.js'; -import { IRange, Range } from '../../../../../../../../../editor/common/core/range.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; - -/** - * A token that represent a `markdown link` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class MarkdownLink extends MarkdownToken { - /** - * Check if this `markdown link` points to a valid URL address. - */ - public readonly isURL: boolean; - - constructor( - /** - * The starting line number of the link (1-based indexing). - */ - lineNumber: number, - /** - * The starting column number of the link (1-based indexing). - */ - columnNumber: number, - /** - * The caption of the original link, including the square brackets. - */ - public readonly caption: string, - /** - * The reference of the original link, including the parentheses. - */ - public readonly reference: string, - ) { - assert( - !isNaN(lineNumber), - `The line number must not be a NaN.`, - ); - - assert( - lineNumber > 0, - `The line number must be >= 1, got "${lineNumber}".`, - ); - - assert( - columnNumber > 0, - `The column number must be >= 1, got "${columnNumber}".`, - ); - - assert( - caption[0] === '[' && caption[caption.length - 1] === ']', - `The caption must be enclosed in square brackets, got "${caption}".`, - ); - - assert( - reference[0] === '(' && reference[reference.length - 1] === ')', - `The reference must be enclosed in parentheses, got "${reference}".`, - ); - - super( - new Range( - lineNumber, - columnNumber, - lineNumber, - columnNumber + caption.length + reference.length, - ), - ); - - // set up the `isURL` flag based on the current - try { - new URL(this.path); - this.isURL = true; - } catch { - this.isURL = false; - } - } - - public override get text(): string { - return `${this.caption}${this.reference}`; - } - - /** - * Returns the `reference` part of the link without enclosing parentheses. - */ - public get path(): string { - return this.reference.slice(1, this.reference.length - 1); - } - - /** - * Get the range of the `link part` of the token. - */ - public get linkRange(): IRange | undefined { - if (this.path.length === 0) { - return undefined; - } - - const { range } = this; - - // note! '+1' for opening `(` of the link - const startColumn = range.startColumn + this.caption.length + 1; - const endColumn = startColumn + this.path.length; - - return new Range( - range.startLineNumber, - startColumn, - range.endLineNumber, - endColumn, - ); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `md-link("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownToken.ts deleted file mode 100644 index fc1935d081b..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownCodec/tokens/markdownToken.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; - -/** - * Common base token that all `markdown` tokens should - * inherit from. - */ -export abstract class MarkdownToken extends BaseToken { } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/markdownExtensionsDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/markdownExtensionsDecoder.ts deleted file mode 100644 index b5ff3d2b9f9..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/markdownExtensionsDecoder.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { ReadableStream } from '../../../../../../../../base/common/stream.js'; -import { BaseDecoder } from '../baseDecoder.js'; -import { MarkdownExtensionsToken } from './tokens/markdownExtensionsToken.js'; -import { SimpleDecoder, TSimpleDecoderToken } from '../simpleCodec/simpleDecoder.js'; -import { PartialFrontMatterHeader, PartialFrontMatterStartMarker } from './parsers/frontMatterHeader.js'; - -/** - * Tokens produced by this decoder. - */ -export type TMarkdownExtensionsToken = MarkdownExtensionsToken | TSimpleDecoderToken; - -/** - * Decoder responsible for decoding extensions of markdown syntax, - * e.g., a `Front Matter` header, etc. - */ -export class MarkdownExtensionsDecoder extends BaseDecoder { - /** - * Current parser object that is responsible for parsing a sequence of tokens into - * some markdown entity. Set to `undefined` when no parsing is in progress at the moment. - */ - private current?: PartialFrontMatterStartMarker | PartialFrontMatterHeader; - - constructor( - stream: ReadableStream, - ) { - super(new SimpleDecoder(stream)); - } - - protected override onStreamData(token: TSimpleDecoderToken): void { - // front matter headers start with a `-` at the first column of the first line - if ((this.current === undefined) && PartialFrontMatterStartMarker.mayStartHeader(token)) { - this.current = new PartialFrontMatterStartMarker(token); - - return; - } - - // if current parser is not initiated, - we are not inside a sequence of tokens - // we care about, therefore re-emit the token immediately and continue - if (this.current === undefined) { - this._onData.fire(token); - return; - } - - // if there is a current parser object, submit the token to it - // so it can progress with parsing the tokens sequence - const parseResult = this.current.accept(token); - if (parseResult.result === 'success') { - const { nextParser } = parseResult; - - // if got a fully parsed out token back, emit it and reset - // the current parser object so a new parsing process can start - if (nextParser instanceof MarkdownExtensionsToken) { - this._onData.fire(nextParser); - delete this.current; - } else { - // otherwise, update the current parser object - this.current = nextParser; - } - } else { - // if failed to parse a sequence of a tokens as a single markdown - // entity (e.g., a link), re-emit the tokens accumulated so far - // then reset the currently initialized parser object - this.reEmitCurrentTokens(); - } - - // if token was not consumed by the parser, call `onStreamData` again - // so the token is properly handled by the decoder in the case when a - // new sequence starts with this token - if (!parseResult.wasTokenConsumed) { - this.onStreamData(token); - } - } - - protected override onStreamEnd(): void { - try { - if (this.current === undefined) { - return; - } - - // if current parser can be converted into a valid Front Matter - // header, then emit it and reset the current parser object - if (this.current instanceof PartialFrontMatterHeader) { - this._onData.fire( - this.current.asFrontMatterHeader(), - ); - delete this.current; - return; - } - - } catch { - // if failed to convert current parser object to a token, - // re-emit the tokens accumulated so far - this.reEmitCurrentTokens(); - } finally { - delete this.current; - super.onStreamEnd(); - } - } - - /** - * Re-emit tokens accumulated so far in the current parser object. - */ - protected reEmitCurrentTokens(): void { - if (this.current === undefined) { - return; - } - - for (const token of this.current.tokens) { - this._onData.fire(token); - } - delete this.current; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/parsers/frontMatterHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/parsers/frontMatterHeader.ts deleted file mode 100644 index a63c911174c..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/parsers/frontMatterHeader.ts +++ /dev/null @@ -1,345 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dash } from '../../simpleCodec/tokens/dash.js'; -import { NewLine } from '../../linesCodec/tokens/newLine.js'; -import { FrontMatterHeader } from '../tokens/frontMatterHeader.js'; -import { assertDefined } from '../../../../../../../../../base/common/types.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { assert, assertNever } from '../../../../../../../../../base/common/assert.js'; -import { CarriageReturn } from '../../linesCodec/tokens/carriageReturn.js'; -import { FrontMatterMarker, TMarkerToken } from '../tokens/frontMatterMarker.js'; -import { assertNotConsumed, IAcceptTokenSuccess, ParserBase, TAcceptTokenResult } from '../../simpleCodec/parserBase.js'; - -/** - * Parses the start marker of a Front Matter header. - */ -export class PartialFrontMatterStartMarker extends ParserBase { - constructor(token: Dash) { - const { range } = token; - - assert( - range.startLineNumber === 1, - `Front Matter header must start at the first line, but it starts at line #${range.startLineNumber}.`, - ); - - assert( - range.startColumn === 1, - `Front Matter header must start at the beginning of the line, but it starts at ${range.startColumn}.`, - ); - - super([token]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - const previousToken = this.currentTokens[this.currentTokens.length - 1]; - - // collect a sequence of dash tokens that may end with a CR token - if ((token instanceof Dash) || (token instanceof CarriageReturn)) { - // a dash or CR tokens can go only after another dash token - if ((previousToken instanceof Dash) === false) { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - this.currentTokens.push(token); - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: this, - }; - } - - // stop collecting dash tokens when a new line token is encountered - if (token instanceof NewLine) { - this.isConsumed = true; - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: new PartialFrontMatterHeader( - FrontMatterMarker.fromTokens([ - ...this.currentTokens, - token, - ]), - ), - }; - } - - // any other token is invalid for the `start marker` - this.isConsumed = true; - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - /** - * Check if provided dash token can be a start of a Front Matter header. - */ - public static mayStartHeader(token: TSimpleDecoderToken): token is Dash { - return (token instanceof Dash) - && (token.range.startLineNumber === 1) - && (token.range.startColumn === 1); - } -} - -/** - * Parses a Front Matter header that already has a start marker - * and possibly some content that follows. - */ -export class PartialFrontMatterHeader extends ParserBase { - /** - * Parser instance for the end marker of the Front Matter header. - */ - private maybeEndMarker?: PartialFrontMatterEndMarker; - - constructor( - public readonly startMarker: FrontMatterMarker, - ) { - super([]); - } - - public override get tokens(): readonly TSimpleDecoderToken[] { - const endMarkerTokens = (this.maybeEndMarker !== undefined) - ? this.maybeEndMarker.tokens - : []; - - return [ - ...this.startMarker.tokens, - ...this.currentTokens, - ...endMarkerTokens, - ]; - } - - /** - * Convert the current token sequence into a {@link FrontMatterHeader} token. - * - * Note! that this method marks the current parser object as "consumed" - * hence it should not be used after this method is called. - */ - public asFrontMatterHeader(): FrontMatterHeader { - assertDefined( - this.maybeEndMarker, - 'Cannot convert to Front Matter header token without an end marker.', - ); - - assert( - this.maybeEndMarker.dashCount === this.startMarker.dashTokens.length, - [ - 'Start and end markers must have the same number of dashes', - `, got ${this.startMarker.dashTokens.length} / ${this.maybeEndMarker.dashCount}.`, - ].join(''), - ); - - this.isConsumed = true; - - return FrontMatterHeader.fromTokens( - this.startMarker.tokens, - this.currentTokens, - this.maybeEndMarker.tokens, - ); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if in the mode of parsing the end marker sequence, forward - // the token to the current end marker parser instance - if (this.maybeEndMarker !== undefined) { - return this.acceptEndMarkerToken(token); - } - - // collect all tokens until a `dash token at the beginning of a line` is found - if (((token instanceof Dash) === false) || (token.range.startColumn !== 1)) { - this.currentTokens.push(token); - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: this, - }; - } - - // a dash token at the beginning of the line might be a start of the `end marker` - // sequence of the front matter header, hence initialize appropriate parser object - assert( - this.maybeEndMarker === undefined, - `End marker parser must not be present.`, - ); - this.maybeEndMarker = new PartialFrontMatterEndMarker(token); - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: this, - }; - } - - /** - * When a end marker parser is present, we pass all tokens to it - * until it is completes the parsing process(either success or failure). - */ - private acceptEndMarkerToken( - token: TSimpleDecoderToken, - ): TAcceptTokenResult { - assertDefined( - this.maybeEndMarker, - `Partial end marker parser must be initialized.`, - ); - - // if we have a partial end marker, we are in the process of parsing - // the end marker, so just pass the token to it and return - const acceptResult = this.maybeEndMarker.accept(token); - const { result, wasTokenConsumed } = acceptResult; - - if (result === 'success') { - const { nextParser } = acceptResult; - const endMarkerParsingComplete = (nextParser instanceof FrontMatterMarker); - - if (endMarkerParsingComplete === false) { - return { - result: 'success', - wasTokenConsumed, - nextParser: this, - }; - } - - const endMarker = nextParser; - - // start and end markers must have the same number of dashes, hence - // if they don't match, we would like to continue parsing the header - // until we find an end marker with the same number of dashes - if (endMarker.dashTokens.length !== this.startMarker.dashTokens.length) { - return this.handleEndMarkerParsingFailure( - endMarker.tokens, - wasTokenConsumed, - ); - } - - this.isConsumed = true; - return { - result: 'success', - wasTokenConsumed: true, - nextParser: FrontMatterHeader.fromTokens( - this.startMarker.tokens, - this.currentTokens, - this.maybeEndMarker.tokens, - ), - }; - } - - // if failed to parse the end marker, we would like to continue parsing - // the header until we find a valid end marker - if (result === 'failure') { - return this.handleEndMarkerParsingFailure( - this.maybeEndMarker.tokens, - wasTokenConsumed, - ); - } - - assertNever( - result, - `Unexpected result '${result}' while parsing the end marker.`, - ); - } - - /** - * On failure to parse the end marker, we need to continue parsing - * the header because there might be another valid end marker in - * the stream of tokens. Therefore we copy over the end marker tokens - * into the list of "content" tokens and reset the end marker parser. - */ - private handleEndMarkerParsingFailure( - tokens: readonly TSimpleDecoderToken[], - wasTokenConsumed: boolean, - ): IAcceptTokenSuccess { - this.currentTokens.push(...tokens); - delete this.maybeEndMarker; - - return { - result: 'success', - wasTokenConsumed, - nextParser: this, - }; - } -} - -/** - * Parser the end marker sequence of a Front Matter header. - */ -class PartialFrontMatterEndMarker extends ParserBase { - constructor(token: Dash) { - const { range } = token; - - assert( - range.startColumn === 1, - `Front Matter header must start at the beginning of the line, but it starts at ${range.startColumn}.`, - ); - - super([token]); - } - - /** - * Number of dashes in the marker. - */ - public get dashCount(): number { - return this.tokens - .filter((token) => { return token instanceof Dash; }) - .length; - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - const previousToken = this.currentTokens[this.currentTokens.length - 1]; - - // collect a sequence of dash tokens that may end with a CR token - if ((token instanceof Dash) || (token instanceof CarriageReturn)) { - // a dash or CR tokens can go only after another dash token - if ((previousToken instanceof Dash) === false) { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - this.currentTokens.push(token); - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: this, - }; - } - - // stop collecting dash tokens when a new line token is encountered - if (token instanceof NewLine) { - this.isConsumed = true; - this.currentTokens.push(token); - - return { - result: 'success', - wasTokenConsumed: true, - nextParser: FrontMatterMarker.fromTokens([ - ...this.currentTokens, - ]), - }; - } - - // any other token is invalid for the `start marker` - this.isConsumed = true; - return { - result: 'failure', - wasTokenConsumed: false, - }; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.ts deleted file mode 100644 index b5b3903b20a..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Text } from '../../textToken.js'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../baseToken.js'; -import { MarkdownExtensionsToken } from './markdownExtensionsToken.js'; -import { TSimpleDecoderToken } from '../../simpleCodec/simpleDecoder.js'; -import { FrontMatterMarker, TMarkerToken } from './frontMatterMarker.js'; - -/** - * Token that represents a `Front Matter` header in a text. - */ -export class FrontMatterHeader extends MarkdownExtensionsToken { - constructor( - range: Range, - public readonly startMarker: FrontMatterMarker, - public readonly content: Text, - public readonly endMarker: FrontMatterMarker, - ) { - super(range); - } - - /** - * Return complete text representation of the token. - */ - public get text(): string { - const text: string[] = [ - this.startMarker.text, - this.content.text, - this.endMarker.text, - ]; - - return text.join(''); - } - - /** - * Range of the content of the Front Matter header. - */ - public get contentRange(): Range { - return this.content.range; - } - - /** - * Content token of the Front Matter header. - */ - public get contentToken(): Text { - return this.content; - } - - /** - * Create new instance of the token from the given tokens. - */ - public static fromTokens( - startMarkerTokens: readonly TMarkerToken[], - contentTokens: readonly TSimpleDecoderToken[], - endMarkerTokens: readonly TMarkerToken[], - ): FrontMatterHeader { - const range = BaseToken.fullRange( - [...startMarkerTokens, ...endMarkerTokens], - ); - - return new FrontMatterHeader( - range, - FrontMatterMarker.fromTokens(startMarkerTokens), - new Text(contentTokens), - FrontMatterMarker.fromTokens(endMarkerTokens), - ); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `frontmatter("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.ts deleted file mode 100644 index 14813b3116b..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../baseToken.js'; -import { Dash } from '../../simpleCodec/tokens/dash.js'; -import { NewLine } from '../../linesCodec/tokens/newLine.js'; -import { MarkdownExtensionsToken } from './markdownExtensionsToken.js'; -import { CarriageReturn } from '../../linesCodec/tokens/carriageReturn.js'; - -/** - * Type for tokens inside a Front Matter header marker. - */ -export type TMarkerToken = Dash | CarriageReturn | NewLine; - -/** - * Marker for the start and end of a Front Matter header. - */ -export class FrontMatterMarker extends MarkdownExtensionsToken { - /** - * Returns complete text representation of the token. - */ - public get text(): string { - return BaseToken.render(this.tokens); - } - - /** - * List of {@link Dash} tokens in the marker. - */ - public get dashTokens(): readonly Dash[] { - return this.tokens - .filter((token) => { return token instanceof Dash; }); - } - - constructor( - range: Range, - public readonly tokens: readonly TMarkerToken[], - ) { - super(range); - } - - /** - * Create new instance of the token from a provided - * list of tokens. - */ - public static fromTokens( - tokens: readonly TMarkerToken[], - ): FrontMatterMarker { - const range = BaseToken.fullRange(tokens); - - return new FrontMatterMarker(range, tokens); - } - - public toString(): string { - return `frontmatter-marker(${this.dashTokens.length}:${this.range})`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/markdownExtensionsToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/markdownExtensionsToken.ts deleted file mode 100644 index 82046eb2b4d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/markdownExtensionsCodec/tokens/markdownExtensionsToken.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownToken } from '../../markdownCodec/tokens/markdownToken.js'; - -/** - * Base class for all tokens produced by the `MarkdownExtensionsDecoder`. - */ -export abstract class MarkdownExtensionsToken extends MarkdownToken { } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/parserBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/parserBase.ts deleted file mode 100644 index 84515dc1d6c..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/parserBase.ts +++ /dev/null @@ -1,137 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../baseToken.js'; -import { assert } from '../../../../../../../../base/common/assert.js'; - -/** - * Common interface for a result of accepting a next token - * in a sequence. - */ -export interface IAcceptTokenResult { - /** - * The result type of accepting a next token in a sequence. - */ - result: 'success' | 'failure'; - - /** - * Whether the token to accept was consumed by the parser - * during the accept operation. - */ - wasTokenConsumed: boolean; -} - -/** - * Successful result of accepting a next token in a sequence. - */ -export interface IAcceptTokenSuccess extends IAcceptTokenResult { - result: 'success'; - nextParser: T; -} - -/** - * Failure result of accepting a next token in a sequence. - */ -export interface IAcceptTokenFailure extends IAcceptTokenResult { - result: 'failure'; -} - -/** - * The result of operation of accepting a next token in a sequence. - */ -export type TAcceptTokenResult = IAcceptTokenSuccess | IAcceptTokenFailure; - -/** - * An abstract parser class that is able to parse a sequence of - * tokens into a new single entity. - */ -export abstract class ParserBase { - /** - * Whether the parser object was "consumed" and should not be used anymore. - */ - protected isConsumed: boolean = false; - - /** - * Whether the parser object was "consumed" hence must not be used anymore. - */ - public get consumed(): boolean { - return this.isConsumed; - } - - /** - * Number of tokens at the initialization of the current parser. - */ - protected readonly startTokensCount: number; - - constructor( - /** - * Set of tokens that were accumulated so far. - */ - protected readonly currentTokens: TToken[] = [], - ) { - this.startTokensCount = this.currentTokens.length; - } - - /** - * Get the tokens that were accumulated so far. - */ - public get tokens(): readonly TToken[] { - return this.currentTokens; - } - - /** - * Accept a new token returning parsing result: - * - successful result must include the next parser object or a fully parsed out token - * - failure result must indicate that the token was not consumed - * - * @param token The token to accept. - * @returns The parsing result. - */ - public abstract accept(token: TToken): TAcceptTokenResult; - - /** - * A helper method that validates that the current parser object was not yet consumed, - * hence can still be used to accept new tokens in the parsing process. - * - * @throws if the parser object is already consumed. - */ - protected assertNotConsumed(): void { - assert( - this.isConsumed === false, - `The parser object is already consumed and should not be used anymore.`, - ); - } -} - -/** - * Decorator that validates that the current parser object was not yet consumed, - * hence can still be used to accept new tokens in the parsing process. - * - * @throws the resulting decorated method throws if the parser object was already consumed. - */ -export function assertNotConsumed>( - _target: T, - propertyKey: 'accept', - descriptor: PropertyDescriptor, -): PropertyDescriptor { - // store the original method reference - const originalMethod = descriptor.value; - - // validate that the current parser object was not yet consumed - // before invoking the original accept method - descriptor.value = function ( - this: T, - ...args: Parameters - ): ReturnType { - assert( - this.isConsumed === false, - `The parser object is already consumed and should not be used anymore.`, - ); - - return originalMethod.apply(this, args); - }; - - return descriptor; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.ts deleted file mode 100644 index 9addf4df2ca..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.ts +++ /dev/null @@ -1,132 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { NewLine } from '../linesCodec/tokens/newLine.js'; -import { CarriageReturn } from '../linesCodec/tokens/carriageReturn.js'; -import { LinesDecoder, TLineBreakToken, TLineToken } from '../linesCodec/linesDecoder.js'; -import { - At, - Tab, - Word, - Hash, - Dash, - Colon, - Slash, - Space, - Quote, - Comma, - FormFeed, - DollarSign, - DoubleQuote, - VerticalTab, - type TBracket, - LeftBracket, - RightBracket, - type TCurlyBrace, - LeftCurlyBrace, - RightCurlyBrace, - ExclamationMark, - type TParenthesis, - LeftParenthesis, - RightParenthesis, - type TAngleBracket, - LeftAngleBracket, - RightAngleBracket, -} from './tokens/tokens.js'; -import { ISimpleTokenClass, SimpleToken } from './tokens/simpleToken.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { BaseDecoder } from '../baseDecoder.js'; -import { ReadableStream } from '../../../../../../../../base/common/stream.js'; - -/** - * Type for all simple tokens. - */ -export type TSimpleToken = Space | Tab | VerticalTab | At | Quote | DoubleQuote - | CarriageReturn | NewLine | FormFeed | TBracket | TAngleBracket | TCurlyBrace - | TParenthesis | Colon | Hash | Dash | ExclamationMark | Slash | DollarSign | Comma - | TLineBreakToken; - -/** -* Type of tokens emitted by this decoder. -*/ -export type TSimpleDecoderToken = TSimpleToken | Word; - -/** - * List of well-known distinct tokens that this decoder emits (excluding - * the word stop characters defined below). Everything else is considered - * an arbitrary "text" sequence and is emitted as a single {@link Word} token. - */ -export const WELL_KNOWN_TOKENS: readonly ISimpleTokenClass[] = Object.freeze([ - LeftParenthesis, RightParenthesis, LeftBracket, RightBracket, LeftCurlyBrace, RightCurlyBrace, - LeftAngleBracket, RightAngleBracket, Space, Tab, VerticalTab, FormFeed, Colon, Hash, Dash, - ExclamationMark, At, Slash, DollarSign, Quote, DoubleQuote, Comma, -]); - -/** - * A {@link Word} sequence stops when one of the well-known tokens are encountered. - * Note! the `\r` and `\n` are excluded from the list because this decoder based on - * the {@link LinesDecoder} which emits {@link Line} tokens without them. - */ -const WORD_STOP_CHARACTERS: readonly string[] = Object.freeze( - WELL_KNOWN_TOKENS.map(token => token.symbol), -); - -/** - * A decoder that can decode a stream of `Line`s into a stream - * of simple token, - `Word`, `Space`, `Tab`, `NewLine`, etc. - */ -export class SimpleDecoder extends BaseDecoder { - constructor( - stream: ReadableStream, - ) { - super(new LinesDecoder(stream)); - } - - protected override onStreamData(line: TLineToken): void { - // re-emit new line tokens immediately - if (line instanceof CarriageReturn || line instanceof NewLine) { - this._onData.fire(line); - - return; - } - - // loop through the text separating it into `Word` and `well-known` tokens - const lineText = line.text.split(''); - let i = 0; - while (i < lineText.length) { - // index is 0-based, but column numbers are 1-based - const columnNumber = i + 1; - const character = lineText[i]; - - // check if the current character is a well-known token - const tokenConstructor = WELL_KNOWN_TOKENS - .find((wellKnownToken) => { - return wellKnownToken.symbol === character; - }); - - // if it is a well-known token, emit it and continue to the next one - if (tokenConstructor) { - this._onData.fire(SimpleToken.newOnLine(line, columnNumber, tokenConstructor)); - - i++; - continue; - } - - // otherwise, it is an arbitrary "text" sequence of characters, - // that needs to be collected into a single `Word` token, hence - // read all the characters until a stop character is encountered - let word = ''; - while (i < lineText.length && !(WORD_STOP_CHARACTERS.includes(lineText[i]))) { - word += lineText[i]; - i++; - } - - // emit a "text" sequence of characters as a single `Word` token - this._onData.fire( - Word.newOnLine(word, line, columnNumber), - ); - } - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts deleted file mode 100644 index d57acedc1c3..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `<` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class LeftAngleBracket extends SimpleToken<'<'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '<' = '<'; - - /** - * Return text representation of the token. - */ - public override get text(): '<' { - return LeftAngleBracket.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `left-angle-bracket${this.range}`; - } -} - -/** - * A token that represent a `>` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class RightAngleBracket extends SimpleToken<'>'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '>' = '>'; - - /** - * Return text representation of the token. - */ - public override get text(): '>' { - return RightAngleBracket.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `right-angle-bracket${this.range}`; - } -} - -/** - * General angle bracket token type. - */ -export type TAngleBracket = LeftAngleBracket | RightAngleBracket; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts deleted file mode 100644 index 25c44433afd..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/at.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `@` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class At extends SimpleToken<'@'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '@' = '@'; - - /** - * Return text representation of the token. - */ - public override get text(): '@' { - return At.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `at${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts deleted file mode 100644 index 491cb2a588c..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `[` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class LeftBracket extends SimpleToken<'['> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '[' = '['; - - /** - * Return text representation of the token. - */ - public override get text(): '[' { - return LeftBracket.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `left-bracket${this.range}`; - } -} - -/** - * A token that represent a `]` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class RightBracket extends SimpleToken<']'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: ']' = ']'; - - /** - * Return text representation of the token. - */ - public override get text(): ']' { - return RightBracket.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `right-bracket${this.range}`; - } -} - -/** - * General bracket token type. - */ -export type TBracket = LeftBracket | RightBracket; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts deleted file mode 100644 index a04b3e853df..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/colon.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `:` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Colon extends SimpleToken<':'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: ':' = ':'; - - /** - * Return text representation of the token. - */ - public override get text(): ':' { - return Colon.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `colon${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts deleted file mode 100644 index ce5df1748ca..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/comma.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `,` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Comma extends SimpleToken<','> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: ',' = ','; - - /** - * Return text representation of the token. - */ - public override get text(): ',' { - return Comma.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `comma${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts deleted file mode 100644 index 9c13e501d27..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/curlyBraces.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `{` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class LeftCurlyBrace extends SimpleToken<'{'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '{' = '{'; - - /** - * Return text representation of the token. - */ - public override get text(): '{' { - return LeftCurlyBrace.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `left-curly-brace${this.range}`; - } -} - -/** - * A token that represent a `}` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class RightCurlyBrace extends SimpleToken<'}'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '}' = '}'; - - /** - * Return text representation of the token. - */ - public override get text(): '}' { - return RightCurlyBrace.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `right-curly-brace${this.range}`; - } -} - -/** - * General curly brace token type. - */ -export type TCurlyBrace = LeftCurlyBrace | RightCurlyBrace; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts deleted file mode 100644 index a77b441ad26..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dash.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `-` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Dash extends SimpleToken<'-'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '-' = '-'; - - /** - * Return text representation of the token. - */ - public override get text(): '-' { - return Dash.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `dash${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts deleted file mode 100644 index 5b81d0afda8..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/dollarSign.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `$` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class DollarSign extends SimpleToken<'$'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '$' = '$'; - - /** - * Return text representation of the token. - */ - public override get text(): '$' { - return DollarSign.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `dollarSign${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts deleted file mode 100644 index ba041a03ed5..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `"` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class DoubleQuote extends SimpleToken<'"'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '"' = '"'; - - /** - * Return text representation of the token. - */ - public override get text(): '"' { - return DoubleQuote.symbol; - } - - /** - * Checks if the provided token is of the same type - * as the current one. - */ - public sameType(other: BaseToken): other is typeof this { - return (other instanceof this.constructor); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `double-quote${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts deleted file mode 100644 index 32675fdf8a3..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `!` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class ExclamationMark extends SimpleToken<'!'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '!' = '!'; - - /** - * Return text representation of the token. - */ - public override get text(): '!' { - return ExclamationMark.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `exclamation-mark${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts deleted file mode 100644 index df5b8b0d446..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * Token that represent a `form feed` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class FormFeed extends SimpleToken<'\f'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '\f' = '\f'; - - /** - * Return text representation of the token. - */ - public override get text(): '\f' { - return FormFeed.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `formfeed${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts deleted file mode 100644 index f499085859d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/hash.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `#` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Hash extends SimpleToken<'#'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '#' = '#'; - - /** - * Return text representation of the token. - */ - public override get text(): '#' { - return Hash.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `hash${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts deleted file mode 100644 index b7ef4f5bc2a..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `(` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class LeftParenthesis extends SimpleToken<'('> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '(' = '('; - - /** - * Return text representation of the token. - */ - public override get text(): '(' { - return LeftParenthesis.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `left-parenthesis${this.range}`; - } -} - -/** - * A token that represent a `)` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class RightParenthesis extends SimpleToken<')'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: ')' = ')'; - - /** - * Return text representation of the token. - */ - public override get text(): ')' { - return RightParenthesis.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `right-parenthesis${this.range}`; - } -} - -/** - * General parenthesis token type. - */ -export type TParenthesis = LeftParenthesis | RightParenthesis; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts deleted file mode 100644 index 3c262f67b49..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/quote.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `'` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Quote extends SimpleToken<`'`> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '\'' = '\''; - - /** - * Return text representation of the token. - */ - public override get text(): '\'' { - return Quote.symbol; - } - - /** - * Checks if the provided token is of the same type - * as the current one. - */ - public sameType(other: BaseToken): other is Quote { - return (other instanceof this.constructor); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `quote${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts deleted file mode 100644 index 5a611bb1ddd..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/simpleToken.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../../baseToken.js'; -import { Line } from '../../linesCodec/tokens/line.js'; - -/** - * Interface for a class that can be instantiated into a {@link SimpleToken}. - */ -export interface ISimpleTokenClass> { - /** - * Character representing the token. - */ - readonly symbol: string; - - /** - * Constructor for the token. - */ - new(...args: any[]): TSimpleToken; -} - -/** - * Base class for all "simple" tokens with a `range`. - * A simple token is the one that represents a single character. - */ -export abstract class SimpleToken extends BaseToken { - /** - * The underlying symbol of the token. - */ - public static readonly symbol: string; - - /** - * Create new token instance with range inside - * the given `Line` at the given `column number`. - */ - public static newOnLine>( - line: Line, - atColumnNumber: number, - Constructor: ISimpleTokenClass, - ): TSimpleToken { - const { range } = line; - - return new Constructor(new Range( - range.startLineNumber, - atColumnNumber, - range.startLineNumber, - atColumnNumber + Constructor.symbol.length, - )); - } -} - -/** - * Base class for all tokens that represent some form of - * a spacing character, e.g. 'space', 'tab', etc. - */ -export abstract class SpacingToken extends SimpleToken { } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts deleted file mode 100644 index 04d382bc89f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/slash.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SimpleToken } from './simpleToken.js'; - -/** - * A token that represent a `/` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Slash extends SimpleToken<'/'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '/' = '/'; - - /** - * Return text representation of the token. - */ - public override get text(): '/' { - return Slash.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `slash${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts deleted file mode 100644 index 07eed9f58b2..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/space.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SpacingToken } from './simpleToken.js'; - -/** - * A token that represent a `space` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Space extends SpacingToken<' '> { - /** - * The underlying symbol of the `Space` token. - */ - public static override readonly symbol: ' ' = ' '; - - /** - * Return text representation of the token. - */ - public override get text(): ' ' { - return Space.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `space${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts deleted file mode 100644 index 718a01df813..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tab.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SpacingToken } from './simpleToken.js'; - -/** - * A token that represent a `tab` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class Tab extends SpacingToken<'\t'> { - /** - * The underlying symbol of the token. - */ - public static override readonly symbol: '\t' = '\t'; - - /** - * Return text representation of the token. - */ - public override get text(): '\t' { - return Tab.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `tab${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts deleted file mode 100644 index ee25fb83ddf..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export { At } from './at.js'; -export { Tab } from './tab.js'; -export { Dash } from './dash.js'; -export { Hash } from './hash.js'; -export { Word } from './word.js'; -export { Colon } from './colon.js'; -export { Quote } from './quote.js'; -export { Slash } from './slash.js'; -export { Space } from './space.js'; -export { Comma } from './comma.js'; -export { FormFeed } from './formFeed.js'; -export { DollarSign } from './dollarSign.js'; -export { VerticalTab } from './verticalTab.js'; -export { DoubleQuote } from './doubleQuote.js'; -export { ExclamationMark } from './exclamationMark.js'; -export { SimpleToken, SpacingToken } from './simpleToken.js'; -export { type TBracket, LeftBracket, RightBracket } from './brackets.js'; -export { type TCurlyBrace, LeftCurlyBrace, RightCurlyBrace } from './curlyBraces.js'; -export { type TParenthesis, LeftParenthesis, RightParenthesis } from './parentheses.js'; -export { type TAngleBracket, LeftAngleBracket, RightAngleBracket } from './angleBrackets.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts deleted file mode 100644 index 4b7b4241433..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SpacingToken } from './simpleToken.js'; - -/** - * Token that represent a `vertical tab` with a `range`. The `range` - * value reflects the position of the token in the original data. - */ -export class VerticalTab extends SpacingToken<'\v'> { - /** - * The underlying symbol of the `VerticalTab` token. - */ - public static override readonly symbol: '\v' = '\v'; - - /** - * Return text representation of the token. - */ - public override get text(): '\v' { - return VerticalTab.symbol; - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `vtab${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts deleted file mode 100644 index bea656895fc..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/simpleCodec/tokens/word.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../../baseToken.js'; -import { Line } from '../../linesCodec/tokens/line.js'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; - -/** - * A token that represent a word - a set of continuous - * characters without stop characters, like a `space`, - * a `tab`, or a `new line`. - */ -export class Word extends BaseToken { - constructor( - /** - * The word range. - */ - range: Range, - - /** - * The string value of the word. - */ - public readonly text: TText, - ) { - super(range); - } - - /** - * Create new `Word` token with the given `text` and the range - * inside the given `Line` at the specified `column number`. - */ - public static newOnLine( - text: string, - line: Line | number, - atColumnNumber: number, - ): Word { - const startLineNumber = (typeof line === 'number') - ? line - : line.range.startLineNumber; - - const range = new Range( - startLineNumber, atColumnNumber, - startLineNumber, atColumnNumber + text.length - ); - - return new Word( - range, - text, - ); - } - - /** - * Returns a string representation of the token. - */ - public override toString(): string { - return `word("${this.shortText()}")${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts deleted file mode 100644 index ecb4c61c94e..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/textToken.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type BaseToken } from './baseToken.js'; -import { CompositeToken } from './compositeToken.js'; - -/** - * Tokens that represent a sequence of tokens that does not - * hold an additional meaning in the text. - */ -export class Text< - TTokens extends readonly BaseToken[] = readonly BaseToken[], -> extends CompositeToken { - public override toString(): string { - return `text(${this.shortText()})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts deleted file mode 100644 index 3c8476c2d56..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStream.ts +++ /dev/null @@ -1,224 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assertNever } from '../../../../../../../../base/common/assert.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; -import { ObservableDisposable } from '../../../utils/observableDisposable.js'; -import { newWriteableStream, ReadableStream, WriteableStream } from '../../../../../../../../base/common/stream.js'; - - -/** - * A readable stream of provided objects. - */ -export class ObjectStream extends ObservableDisposable implements ReadableStream { - /** - * Flag that indicates whether the stream has ended. - */ - private ended: boolean = false; - - /** - * Underlying writable stream instance. - */ - private readonly stream: WriteableStream; - - /** - * Interval reference that is used to periodically send - * objects to the stream in the background. - */ - private timeoutHandle: Timeout | undefined; - - constructor( - private readonly data: Generator, - private readonly cancellationToken?: CancellationToken, - ) { - super(); - - this.stream = newWriteableStream(null); - - if (cancellationToken?.isCancellationRequested) { - this.end(); - return; - } - - // send a first batch of data immediately - this.send(true); - } - - /** - * Starts process of sending data to the stream. - * - * @param stopAfterFirstSend whether to continue sending data to the stream - * or stop sending after the first batch of data is sent instead - */ - public send( - stopAfterFirstSend: boolean = false, - ): void { - // this method can be called asynchronously by the `setTimeout` utility below, hence - // the state of the cancellation token or the stream itself might have changed by that time - if (this.cancellationToken?.isCancellationRequested || this.ended) { - this.end(); - - return; - } - - this.sendData() - .then(() => { - if (this.cancellationToken?.isCancellationRequested || this.ended) { - this.end(); - - return; - } - - if (stopAfterFirstSend === true) { - this.stopStream(); - return; - } - - this.timeoutHandle = setTimeout(this.send.bind(this)); - }) - .catch((error) => { - this.stream.error(error); - this.dispose(); - }); - } - - /** - * Stop the data sending loop. - */ - public stopStream(): this { - if (this.timeoutHandle === undefined) { - return this; - } - - clearTimeout(this.timeoutHandle); - this.timeoutHandle = undefined; - - return this; - } - - /** - * Sends a provided number of objects to the stream. - */ - private async sendData( - objectsCount: number = 25, - ): Promise { - // send up to 'objectsCount' objects at a time - while (objectsCount > 0) { - try { - const next = this.data.next(); - if (next.done || this.cancellationToken?.isCancellationRequested) { - this.end(); - - return; - } - - await this.stream.write(next.value); - objectsCount--; - } catch (error) { - this.stream.error(error); - this.dispose(); - return; - } - } - } - - /** - * Ends the stream and stops sending data objects. - */ - private end(): this { - if (this.ended) { - return this; - } - this.ended = true; - - this.stopStream(); - this.stream.end(); - return this; - } - - public pause(): void { - this.stopStream(); - this.stream.pause(); - - return; - } - - public resume(): void { - this.send(); - this.stream.resume(); - - return; - } - - public destroy(): void { - this.dispose(); - } - - public removeListener(event: string, callback: (...args: any[]) => void): void { - this.stream.removeListener(event, callback); - - return; - } - - public on(event: 'data', callback: (data: T) => void): void; - public on(event: 'error', callback: (err: Error) => void): void; - public on(event: 'end', callback: () => void): void; - public on(event: 'data' | 'error' | 'end', callback: (...args: any[]) => void): void { - if (event === 'data') { - this.stream.on(event, callback); - // this is the convention of the readable stream, - when - // the `data` event is registered, the stream is started - this.send(); - - return; - } - - if (event === 'error') { - this.stream.on(event, callback); - return; - } - - if (event === 'end') { - this.stream.on(event, callback); - return; - } - - assertNever( - event, - `Unexpected event name '${event}'.`, - ); - } - - /** - * Cleanup send interval and destroy the stream. - */ - public override dispose(): void { - this.stopStream(); - this.stream.destroy(); - - super.dispose(); - } - - /** - * Create new instance of the stream from a provided array. - */ - public static fromArray( - array: T[], - cancellationToken?: CancellationToken, - ): ObjectStream { - return new ObjectStream(arrayToGenerator(array), cancellationToken); - } -} - -/** - * Create a generator out of a provided array. - */ -export function arrayToGenerator>(array: T[]): Generator { - return (function* (): Generator { - for (const item of array) { - yield item; - } - })(); -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts deleted file mode 100644 index 2a9b2173b5d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/base/utils/objectStreamFromTextModel.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ITextModel } from '../../../../../../../../editor/common/model.js'; -import { ObjectStream } from './objectStream.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { CancellationToken } from '../../../../../../../../base/common/cancellation.js'; - -/** - * Create new instance of the stream from a provided text model. - */ -export function objectStreamFromTextModel( - model: ITextModel, - cancellationToken?: CancellationToken, -): ObjectStream { - return new ObjectStream(modelToGenerator(model), cancellationToken); -} - -/** - * Create a generator out of a provided text model. - */ -function modelToGenerator(model: ITextModel): Generator { - return (function* (): Generator { - const totalLines = model.getLineCount(); - let currentLine = 1; - - while (currentLine <= totalLines) { - if (model.isDisposed()) { - return undefined; - } - - yield VSBuffer.fromString( - model.getLineContent(currentLine), - ); - if (currentLine !== totalLines) { - yield VSBuffer.fromString( - model.getEOL(), - ); - } - - currentLine++; - } - })(); -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptCodec.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptCodec.ts deleted file mode 100644 index da27950bf92..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptCodec.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from '../../../../../../base/common/buffer.js'; -import { ReadableStream } from '../../../../../../base/common/stream.js'; -import { ChatPromptDecoder, TChatPromptToken } from './chatPromptDecoder.js'; - -/** - * A codec is an object capable of encoding/decoding a stream of data transforming its messages. - * Useful for abstracting a data transfer or protocol logic on top of a stream of bytes. - * - * For instance, if protocol messages need to be transferred over `TCP` connection, a codec that - * encodes the messages into a sequence of bytes before sending it to a network socket. Likewise, - * on the other end of the connection, the same codec can decode the sequence of bytes back into - * a sequence of the protocol messages. - */ -export interface ICodec { - /** - * Encode a stream of `K`s into a stream of `T`s. - */ - encode: (value: ReadableStream) => ReadableStream; - - /** - * Decode a stream of `T`s into a stream of `K`s. - */ - decode: (value: ReadableStream) => ReadableStream; -} - - -/** - * `ChatPromptCodec` type is a `ICodec` with specific types for - * stream messages and return types of the `encode`/`decode` functions. - * @see {@link ICodec} - */ -interface IChatPromptCodec extends ICodec { - /** - * Decode a stream of `VSBuffer`s into a stream of `TChatPromptToken`s. - * - * @see {@link TChatPromptToken} - * @see {@link VSBuffer} - * @see {@link ChatPromptDecoder} - */ - decode: (value: ReadableStream) => ChatPromptDecoder; -} - -/** - * Codec that is capable to encode and decode tokens of an AI chatbot prompt message. - */ -export const ChatPromptCodec: IChatPromptCodec = Object.freeze({ - /** - * Encode a stream of `TChatPromptToken`s into a stream of `VSBuffer`s. - * - * @see {@link ReadableStream} - * @see {@link VSBuffer} - */ - encode: (_stream: ReadableStream): ReadableStream => { - throw new Error('The `encode` method is not implemented.'); - }, - - /** - * Decode a of `VSBuffer`s into a readable of `TChatPromptToken`s. - * - * @see {@link TChatPromptToken} - * @see {@link VSBuffer} - * @see {@link ChatPromptDecoder} - * @see {@link ReadableStream} - */ - decode: (stream: ReadableStream): ChatPromptDecoder => { - return new ChatPromptDecoder(stream); - }, -}); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts deleted file mode 100644 index 5ac4a138fbe..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/chatPromptDecoder.ts +++ /dev/null @@ -1,202 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptToken } from './tokens/promptToken.js'; -import { PromptAtMention } from './tokens/promptAtMention.js'; -import { VSBuffer } from '../../../../../../base/common/buffer.js'; -import { PromptSlashCommand } from './tokens/promptSlashCommand.js'; -import { ReadableStream } from '../../../../../../base/common/stream.js'; -import { PartialPromptAtMention } from './parsers/promptAtMentionParser.js'; -import { PromptTemplateVariable } from './tokens/promptTemplateVariable.js'; -import { assert, assertNever } from '../../../../../../base/common/assert.js'; -import { PartialPromptSlashCommand } from './parsers/promptSlashCommandParser.js'; -import { BaseDecoder } from './base/baseDecoder.js'; -import { PromptVariable, PromptVariableWithData } from './tokens/promptVariable.js'; -import { At } from './base/simpleCodec/tokens/at.js'; -import { Hash } from './base/simpleCodec/tokens/hash.js'; -import { Slash } from './base/simpleCodec/tokens/slash.js'; -import { DollarSign } from './base/simpleCodec/tokens/dollarSign.js'; -import { PartialPromptVariableName, PartialPromptVariableWithData } from './parsers/promptVariableParser.js'; -import { MarkdownDecoder, TMarkdownToken } from './base/markdownCodec/markdownDecoder.js'; -import { PartialPromptTemplateVariable, PartialPromptTemplateVariableStart, TPromptTemplateVariableParser } from './parsers/promptTemplateVariableParser.js'; - -/** - * Tokens produced by this decoder. - */ -export type TChatPromptToken = TMarkdownToken | (PromptVariable | PromptVariableWithData) - | PromptAtMention | PromptSlashCommand | PromptTemplateVariable; - -/** - * Decoder for the common chatbot prompt message syntax. - * For instance, the file references `#file:./path/file.md` are handled by this decoder. - */ -export class ChatPromptDecoder extends BaseDecoder { - /** - * Currently active parser object that is used to parse a well-known sequence of - * tokens, for instance, a `#file:/path/to/file.md` link that consists of `hash`, - * `word`, and `colon` tokens sequence plus the `file path` part that follows. - */ - private current?: (PartialPromptVariableName | PartialPromptVariableWithData) - | PartialPromptAtMention | PartialPromptSlashCommand - | TPromptTemplateVariableParser; - - constructor( - stream: ReadableStream, - ) { - super(new MarkdownDecoder(stream)); - } - - protected override onStreamData(token: TMarkdownToken): void { - // prompt `#variables` always start with the `#` character, hence - // initiate a parser object if we encounter respective token and - // there is no active parser object present at the moment - if ((token instanceof Hash) && !this.current) { - this.current = new PartialPromptVariableName(token); - - return; - } - - // prompt `@mentions` always start with the `@` character, hence - // initiate a parser object if we encounter respective token and - // there is no active parser object present at the moment - if ((token instanceof At) && !this.current) { - this.current = new PartialPromptAtMention(token); - - return; - } - - // prompt `/commands` always start with the `/` character, hence - // initiate a parser object if we encounter respective token and - // there is no active parser object present at the moment - if ((token instanceof Slash) && !this.current) { - this.current = new PartialPromptSlashCommand(token); - - return; - } - - // prompt `${template:variables}` always start with the `$` character, - // hence initiate a parser object if we encounter respective token and - // there is no active parser object present at the moment - if ((token instanceof DollarSign) && !this.current) { - this.current = new PartialPromptTemplateVariableStart(token); - - return; - } - - // if current parser was not yet initiated, - we are in the general "text" - // parsing mode, therefore re-emit the token immediately and continue - if (!this.current) { - this._onData.fire(token); - return; - } - - // if there is a current parser object, submit the token to it - // so it can progress with parsing the tokens sequence - const parseResult = this.current.accept(token); - - // process the parse result next - switch (parseResult.result) { - // in the case of success there might be 2 cases: - // 1) parsing fully completed and an instance of `PromptToken` is returned back, - // in this case, emit the parsed token (e.g., a `link`) and reset the current - // parser object reference so a new parsing process can be initiated next - // 2) parsing is still in progress and the next parser object is returned, hence - // we need to replace the current parser object with a new one and continue - case 'success': { - const { nextParser } = parseResult; - - if (nextParser instanceof PromptToken) { - this._onData.fire(nextParser); - delete this.current; - } else { - this.current = nextParser; - } - - break; - } - // in the case of failure, reset the current parser object - case 'failure': { - // if failed to parse a sequence of a tokens, re-emit the tokens accumulated - // so far then reset the current parser object - this.reEmitCurrentTokens(); - break; - } - } - - // if token was not consumed by the parser, call `onStreamData` again - // so the token is properly handled by the decoder in the case when a - // new sequence starts with this token - if (!parseResult.wasTokenConsumed) { - this.onStreamData(token); - } - } - - protected override onStreamEnd(): void { - try { - // if there is no currently active parser object present, nothing to do - if (this.current === undefined) { - return; - } - - // otherwise try to convert unfinished parser object to a token - - if (this.current instanceof PartialPromptVariableName) { - this._onData.fire(this.current.asPromptVariable()); - return; - } - - if (this.current instanceof PartialPromptVariableWithData) { - this._onData.fire(this.current.asPromptVariableWithData()); - return; - } - - if (this.current instanceof PartialPromptAtMention) { - this._onData.fire(this.current.asPromptAtMention()); - return; - } - - if (this.current instanceof PartialPromptSlashCommand) { - this._onData.fire(this.current.asPromptSlashCommand()); - return; - } - - assert( - (this.current instanceof PartialPromptTemplateVariableStart) === false, - 'Incomplete template variable token.', - ); - - if (this.current instanceof PartialPromptTemplateVariable) { - this._onData.fire(this.current.asPromptTemplateVariable()); - return; - } - - assertNever( - this.current, - `Unknown parser object '${this.current}'`, - ); - } catch (_error) { - // if failed to convert current parser object to a token, - // re-emit the tokens accumulated so far - this.reEmitCurrentTokens(); - } finally { - delete this.current; - super.onStreamEnd(); - } - } - - /** - * Re-emit tokens accumulated so far in the current parser object. - */ - protected reEmitCurrentTokens(): void { - if (this.current === undefined) { - return; - } - - for (const token of this.current.tokens) { - this._onData.fire(token); - } - delete this.current; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts deleted file mode 100644 index 2173ba438ad..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptAtMentionParser.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptAtMention } from '../tokens/promptAtMention.js'; -import { assert } from '../../../../../../../base/common/assert.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../base/baseToken.js'; -import { At } from '../base/simpleCodec/tokens/at.js'; -import { Tab } from '../base/simpleCodec/tokens/tab.js'; -import { Hash } from '../base/simpleCodec/tokens/hash.js'; -import { Space } from '../base/simpleCodec/tokens/space.js'; -import { Colon } from '../base/simpleCodec/tokens/colon.js'; -import { NewLine } from '../base/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; - -/** - * List of characters that terminate the prompt at-mention sequence. - */ -export const STOP_CHARACTERS: readonly string[] = [Space, Tab, NewLine, CarriageReturn, VerticalTab, FormFeed, At, Colon, Hash] - .map((token) => { return token.symbol; }); - -/** - * List of characters that cannot be in an at-mention name (excluding the {@link STOP_CHARACTERS}). - */ -export const INVALID_NAME_CHARACTERS: readonly string[] = [ExclamationMark, LeftAngleBracket, RightAngleBracket, LeftBracket, RightBracket] - .map((token) => { return token.symbol; }); - -/** - * The parser responsible for parsing a `prompt @mention` sequences. - * E.g., `@workspace` or `@github` participant mention. - */ -export class PartialPromptAtMention extends ParserBase { - constructor(token: At) { - super([token]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if a `stop` character is encountered, finish the parsing process - if (STOP_CHARACTERS.includes(token.text)) { - try { - // if it is possible to convert current parser to `PromptAtMention`, return success result - return { - result: 'success', - nextParser: this.asPromptAtMention(), - wasTokenConsumed: false, - }; - } catch (error) { - // otherwise fail - return { - result: 'failure', - wasTokenConsumed: false, - }; - } finally { - // in any case this is an end of the parsing process - this.isConsumed = true; - } - } - - // variables cannot have {@link INVALID_NAME_CHARACTERS} in their names - if (INVALID_NAME_CHARACTERS.includes(token.text)) { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // otherwise it is a valid name character, so add it to the list of - // the current tokens and continue the parsing process - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Try to convert current parser instance into a fully-parsed {@link PromptAtMention} token. - * - * @throws if sequence of tokens received so far do not constitute a valid prompt variable, - * for instance, if there is only `1` starting `@` token is available. - */ - public asPromptAtMention(): PromptAtMention { - // if there is only one token before the stop character - // must be the starting `@` one), then fail - assert( - this.currentTokens.length > 1, - 'Cannot create a prompt @mention out of incomplete token sequence.', - ); - - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - // render the characters above into strings, excluding the starting `@` character - const nameTokens = this.currentTokens.slice(1); - const atMentionName = BaseToken.render(nameTokens); - - return new PromptAtMention( - new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ), - atMentionName, - ); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts deleted file mode 100644 index 557f5e74379..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptSlashCommandParser.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../base/common/assert.js'; -import { PromptSlashCommand } from '../tokens/promptSlashCommand.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../base/baseToken.js'; -import { At } from '../base/simpleCodec/tokens/at.js'; -import { Tab } from '../base/simpleCodec/tokens/tab.js'; -import { Hash } from '../base/simpleCodec/tokens/hash.js'; -import { Slash } from '../base/simpleCodec/tokens/slash.js'; -import { Space } from '../base/simpleCodec/tokens/space.js'; -import { Colon } from '../base/simpleCodec/tokens/colon.js'; -import { NewLine } from '../base/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; - -/** - * List of characters that terminate the prompt at-mention sequence. - */ -export const STOP_CHARACTERS: readonly string[] = [Space, Tab, NewLine, CarriageReturn, VerticalTab, FormFeed, Colon, At, Hash, Slash] - .map((token) => { return token.symbol; }); - -/** - * List of characters that cannot be in an at-mention name (excluding the {@link STOP_CHARACTERS}). - */ -export const INVALID_NAME_CHARACTERS: readonly string[] = [ExclamationMark, LeftAngleBracket, RightAngleBracket, LeftBracket, RightBracket] - .map((token) => { return token.symbol; }); - -/** - * The parser responsible for parsing a `prompt /command` sequences. - * E.g., `/search` or `/explain` command. - */ -export class PartialPromptSlashCommand extends ParserBase { - constructor(token: Slash) { - super([token]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if a `stop` character is encountered, finish the parsing process - if (STOP_CHARACTERS.includes(token.text)) { - try { - // if it is possible to convert current parser to `PromptSlashCommand`, return success result - return { - result: 'success', - nextParser: this.asPromptSlashCommand(), - wasTokenConsumed: false, - }; - } catch (error) { - // otherwise fail - return { - result: 'failure', - wasTokenConsumed: false, - }; - } finally { - // in any case this is an end of the parsing process - this.isConsumed = true; - } - } - - // variables cannot have {@link INVALID_NAME_CHARACTERS} in their names - if (INVALID_NAME_CHARACTERS.includes(token.text)) { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // otherwise it is a valid name character, so add it to the list of - // the current tokens and continue the parsing process - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Try to convert current parser instance into a fully-parsed {@link PromptSlashCommand} token. - * - * @throws if sequence of tokens received so far do not constitute a valid prompt variable, - * for instance, if there is only `1` starting `/` token is available. - */ - public asPromptSlashCommand(): PromptSlashCommand { - // if there is only one token before the stop character - // must be the starting `/` one), then fail - assert( - this.currentTokens.length > 1, - 'Cannot create a prompt /command out of incomplete token sequence.', - ); - - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - // render the characters above into strings, excluding the starting `/` character - const nameTokens = this.currentTokens.slice(1); - const atMentionName = BaseToken.render(nameTokens); - - return new PromptSlashCommand( - new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ), - atMentionName, - ); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts deleted file mode 100644 index b97ad0fd479..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptTemplateVariableParser.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../base/common/assert.js'; -import { PromptTemplateVariable } from '../tokens/promptTemplateVariable.js'; -import { BaseToken } from '../base/baseToken.js'; -import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; -import { DollarSign, LeftCurlyBrace, RightCurlyBrace } from '../base/simpleCodec/tokens/tokens.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; - -/** - * Parsers of the `${variable}` token sequence in a prompt text. - */ -export type TPromptTemplateVariableParser = PartialPromptTemplateVariableStart | PartialPromptTemplateVariable; - -/** - * Parser that handles start sequence of a `${variable}` token sequence in - * a prompt text. Transitions to {@link PartialPromptTemplateVariable} parser - * as soon as the `${` character sequence is found. - */ -export class PartialPromptTemplateVariableStart extends ParserBase { - constructor(token: DollarSign) { - super([token]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - if (token instanceof LeftCurlyBrace) { - this.currentTokens.push(token); - - this.isConsumed = true; - return { - result: 'success', - nextParser: new PartialPromptTemplateVariable(this.currentTokens), - wasTokenConsumed: true, - }; - } - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } -} - -/** - * Parser that handles a partial `${variable}` token sequence in a prompt text. - */ -export class PartialPromptTemplateVariable extends ParserBase { - constructor(tokens: (DollarSign | LeftCurlyBrace)[]) { - super(tokens); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // template variables are terminated by the `}` character - if (token instanceof RightCurlyBrace) { - this.currentTokens.push(token); - - this.isConsumed = true; - return { - result: 'success', - nextParser: this.asPromptTemplateVariable(), - wasTokenConsumed: true, - }; - } - - // otherwise it is a valid name character, so add it to the list of - // the current tokens and continue the parsing process - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Returns a string representation of the prompt template variable - * contents, if any is present. - */ - private get contents(): string { - const contentTokens: TSimpleDecoderToken[] = []; - - // template variables are surrounded by `${}`, hence we need to have - // at least `${` plus one character for the contents to be non-empty - if (this.currentTokens.length < 3) { - return ''; - } - - // collect all tokens besides the first two (`${`) and a possible `}` at the end - for (let i = 2; i < this.currentTokens.length; i++) { - const token = this.currentTokens[i]; - const isLastToken = (i === this.currentTokens.length - 1); - - if ((token instanceof RightCurlyBrace) && (isLastToken === true)) { - break; - } - - contentTokens.push(token); - } - - return BaseToken.render(contentTokens); - } - - /** - * Try to convert current parser instance into a {@link PromptTemplateVariable} token. - * - * @throws if: - * - current tokens sequence cannot be converted to a valid template variable token - */ - public asPromptTemplateVariable(): PromptTemplateVariable { - const firstToken = this.currentTokens[0]; - const secondToken = this.currentTokens[1]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - // template variables are surrounded by `${}`, hence we need - // to have at least 3 tokens in the list for a valid one - assert( - this.currentTokens.length >= 3, - 'Prompt template variable should have at least 3 tokens.', - ); - - // a complete template variable must end with a `}` - assert( - lastToken instanceof RightCurlyBrace, - 'Last token is not a "}".', - ); - - // sanity checks of the first and second tokens - assert( - firstToken instanceof DollarSign, - 'First token must be a "$".', - ); - assert( - secondToken instanceof LeftCurlyBrace, - 'Second token must be a "{".', - ); - - return new PromptTemplateVariable( - BaseToken.fullRange(this.currentTokens), - this.contents, - ); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts deleted file mode 100644 index ef68e78ec2d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/parsers/promptVariableParser.ts +++ /dev/null @@ -1,252 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../base/common/assert.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { BaseToken } from '../base/baseToken.js'; -import { PromptVariable, PromptVariableWithData } from '../tokens/promptVariable.js'; -import { At } from '../base/simpleCodec/tokens/at.js'; -import { Tab } from '../base/simpleCodec/tokens/tab.js'; -import { Hash } from '../base/simpleCodec/tokens/hash.js'; -import { Space } from '../base/simpleCodec/tokens/space.js'; -import { Colon } from '../base/simpleCodec/tokens/colon.js'; -import { NewLine } from '../base/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../base/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../base/simpleCodec/tokens/verticalTab.js'; -import { TSimpleDecoderToken } from '../base/simpleCodec/simpleDecoder.js'; -import { CarriageReturn } from '../base/linesCodec/tokens/carriageReturn.js'; -import { ExclamationMark } from '../base/simpleCodec/tokens/exclamationMark.js'; -import { LeftBracket, RightBracket } from '../base/simpleCodec/tokens/brackets.js'; -import { LeftAngleBracket, RightAngleBracket } from '../base/simpleCodec/tokens/angleBrackets.js'; -import { assertNotConsumed, ParserBase, TAcceptTokenResult } from '../base/simpleCodec/parserBase.js'; - -/** - * List of characters that terminate the prompt variable sequence. - */ -export const STOP_CHARACTERS: readonly string[] = [Space, Tab, NewLine, CarriageReturn, VerticalTab, FormFeed, Hash, At] - .map((token) => { return token.symbol; }); - -/** - * List of characters that cannot be in a variable name (excluding the {@link STOP_CHARACTERS}). - */ -export const INVALID_NAME_CHARACTERS: readonly string[] = [Hash, Colon, ExclamationMark, LeftAngleBracket, RightAngleBracket, LeftBracket, RightBracket] - .map((token) => { return token.symbol; }); - -/** - * The parser responsible for parsing a `prompt variable name`. - * E.g., `#selection` or `#codebase` variable. If the `:` character follows - * the variable name, the parser transitions to {@link PartialPromptVariableWithData} - * that is also able to parse the `data` part of the variable. E.g., the `#file` part - * of the `#file:/path/to/something.md` sequence. - */ -export class PartialPromptVariableName extends ParserBase { - constructor(token: Hash) { - super([token]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if a `stop` character is encountered, finish the parsing process - if (STOP_CHARACTERS.includes(token.text)) { - try { - // if it is possible to convert current parser to `PromptVariable`, return success result - return { - result: 'success', - nextParser: this.asPromptVariable(), - wasTokenConsumed: false, - }; - } catch (error) { - // otherwise fail - return { - result: 'failure', - wasTokenConsumed: false, - }; - } finally { - // in any case this is an end of the parsing process - this.isConsumed = true; - } - } - - // if a `:` character is encountered, we might transition to {@link PartialPromptVariableWithData} - if (token instanceof Colon) { - this.isConsumed = true; - - // if there is only one token before the `:` character, it must be the starting - // `#` symbol, therefore fail because there is no variable name present - if (this.currentTokens.length <= 1) { - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // otherwise, if there are more characters after `#` available, - // we have a variable name, so we can transition to {@link PromptVariableWithData} - return { - result: 'success', - nextParser: new PartialPromptVariableWithData([...this.currentTokens, token]), - wasTokenConsumed: true, - }; - } - - // variables cannot have {@link INVALID_NAME_CHARACTERS} in their names - if (INVALID_NAME_CHARACTERS.includes(token.text)) { - this.isConsumed = true; - - return { - result: 'failure', - wasTokenConsumed: false, - }; - } - - // otherwise, a valid name character, so add it to the list of - // the current tokens and continue the parsing process - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Try to convert current parser instance into a fully-parsed {@link PromptVariable} token. - * - * @throws if sequence of tokens received so far do not constitute a valid prompt variable, - * for instance, if there is only `1` starting `#` token is available. - */ - public asPromptVariable(): PromptVariable { - // if there is only one token before the stop character - // must be the starting `#` one), then fail - assert( - this.currentTokens.length > 1, - 'Cannot create a prompt variable out of incomplete token sequence.', - ); - - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - // render the characters above into strings, excluding the starting `#` character - const variableNameTokens = this.currentTokens.slice(1); - const variableName = BaseToken.render(variableNameTokens); - - return new PromptVariable( - new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ), - variableName, - ); - } -} - -/** - * The parser responsible for parsing a `prompt variable name` with `data`. - * E.g., the `/path/to/something.md` part of the `#file:/path/to/something.md` sequence. - */ -export class PartialPromptVariableWithData extends ParserBase { - - constructor(tokens: readonly TSimpleDecoderToken[]) { - const firstToken = tokens[0]; - const lastToken = tokens[tokens.length - 1]; - - // sanity checks of our expectations about the tokens list - assert( - tokens.length > 2, - `Tokens list must contain at least 3 items, got '${tokens.length}'.`, - ); - assert( - firstToken instanceof Hash, - `The first token must be a '#', got '${firstToken} '.`, - ); - assert( - lastToken instanceof Colon, - `The last token must be a ':', got '${lastToken} '.`, - ); - - super([...tokens]); - } - - @assertNotConsumed - public accept(token: TSimpleDecoderToken): TAcceptTokenResult { - // if a `stop` character is encountered, finish the parsing process - if (STOP_CHARACTERS.includes(token.text)) { - // in any case, success of failure below, this is an end of the parsing process - this.isConsumed = true; - - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - // tokens representing variable name without the `#` character at the start and - // the `:` data separator character at the end - const variableNameTokens = this.currentTokens.slice(1, this.startTokensCount - 1); - // tokens representing variable data without the `:` separator character at the start - const variableDataTokens = this.currentTokens.slice(this.startTokensCount); - // compute the full range of the variable token - const fullRange = new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ); - - // render the characters above into strings - const variableName = BaseToken.render(variableNameTokens); - const variableData = BaseToken.render(variableDataTokens); - - return { - result: 'success', - nextParser: new PromptVariableWithData( - fullRange, - variableName, - variableData, - ), - wasTokenConsumed: false, - }; - } - - // otherwise, token is a valid data character - the data can contain almost any character, - // including `:` and `#`, hence add it to the list of the current tokens and continue - this.currentTokens.push(token); - - return { - result: 'success', - nextParser: this, - wasTokenConsumed: true, - }; - } - - /** - * Try to convert current parser instance into a fully-parsed {@link asPromptVariableWithData} token. - */ - public asPromptVariableWithData(): PromptVariableWithData { - // tokens representing variable name without the `#` character at the start and - // the `:` data separator character at the end - const variableNameTokens = this.currentTokens.slice(1, this.startTokensCount - 1); - // tokens representing variable data without the `:` separator character at the start - const variableDataTokens = this.currentTokens.slice(this.startTokensCount); - - // render the characters above into strings - const variableName = BaseToken.render(variableNameTokens); - const variableData = BaseToken.render(variableDataTokens); - - const firstToken = this.currentTokens[0]; - const lastToken = this.currentTokens[this.currentTokens.length - 1]; - - return new PromptVariableWithData( - new Range( - firstToken.range.startLineNumber, - firstToken.range.startColumn, - lastToken.range.endLineNumber, - lastToken.range.endColumn, - ), - variableName, - variableData, - ); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/fileReference.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/fileReference.ts deleted file mode 100644 index 78c4f7cb6ea..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/fileReference.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -import { PromptVariableWithData } from './promptVariable.js'; -import { assert } from '../../../../../../../base/common/assert.js'; -import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; - -/** - * Name of the variable. - */ -const VARIABLE_NAME: string = 'file'; - -/** - * Object represents a file reference token inside a chatbot prompt. - */ -export class FileReference extends PromptVariableWithData { - constructor( - range: Range, - public readonly path: string, - ) { - super(range, VARIABLE_NAME, path); - } - - /** - * Create a {@link FileReference} from a {@link PromptVariableWithData} instance. - * @throws if variable name is not equal to {@link VARIABLE_NAME}. - */ - public static from(variable: PromptVariableWithData): FileReference { - assert( - variable.name === VARIABLE_NAME, - `Variable name must be '${VARIABLE_NAME}', got '${variable.name}'.`, - ); - - return new FileReference( - variable.range, - variable.data, - ); - } - - /** - * Get the range of the `link` part of the token (e.g., - * the `/path/to/file.md` part of `#file:/path/to/file.md`). - */ - public get linkRange(): IRange | undefined { - return super.dataRange; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptAtMention.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptAtMention.ts deleted file mode 100644 index 43bd8005235..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptAtMention.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptToken } from './promptToken.js'; -import { assert } from '../../../../../../../base/common/assert.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { INVALID_NAME_CHARACTERS, STOP_CHARACTERS } from '../parsers/promptVariableParser.js'; - -/** - * All prompt at-mentions start with `@` character. - */ -const START_CHARACTER: string = '@'; - -/** - * Represents a `@mention` token in a prompt text. - */ -export class PromptAtMention extends PromptToken { - constructor( - range: Range, - /** - * The name of a mention, excluding the `@` character at the start. - */ - public readonly name: string, - ) { - // sanity check of characters used in the provided mention name - for (const character of name) { - assert( - (INVALID_NAME_CHARACTERS.includes(character) === false) && - (STOP_CHARACTERS.includes(character) === false), - `Mention 'name' cannot contain character '${character}', got '${name}'.`, - ); - } - - super(range); - } - - /** - * Get full text of the token. - */ - public get text(): string { - return `${START_CHARACTER}${this.name}`; - } - - /** - * Return a string representation of the token. - */ - public override toString(): string { - return `${this.text}${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts deleted file mode 100644 index 1069d4a7a89..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptSlashCommand.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptToken } from './promptToken.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; - -/** - * All prompt at-mentions start with `/` character. - */ -const START_CHARACTER: string = '/'; - -/** - * Represents a `/command` token in a prompt text. - */ -export class PromptSlashCommand extends PromptToken { - constructor( - range: Range, - /** - * The name of a command, excluding the `/` character at the start. - */ - public readonly name: string, - ) { - - super(range); - } - - /** - * Get full text of the token. - */ - public get text(): string { - return `${START_CHARACTER}${this.name}`; - } - - /** - * Return a string representation of the token. - */ - public override toString(): string { - return `${this.text}${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts deleted file mode 100644 index be3051f2c88..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptTemplateVariable.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptToken } from './promptToken.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { DollarSign } from '../base/simpleCodec/tokens/dollarSign.js'; -import { LeftCurlyBrace, RightCurlyBrace } from '../base/simpleCodec/tokens/curlyBraces.js'; - -/** - * Represents a `${variable}` token in a prompt text. - */ -export class PromptTemplateVariable extends PromptToken { - constructor( - range: Range, - /** - * The contents of the template variable, excluding - * the surrounding `${}` characters. - */ - public readonly contents: string, - ) { - super(range); - } - - /** - * Get full text of the token. - */ - public get text(): string { - return [ - DollarSign.symbol, - LeftCurlyBrace.symbol, - this.contents, - RightCurlyBrace.symbol, - ].join(''); - } - - /** - * Return a string representation of the token. - */ - public override toString(): string { - return `${this.text}${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts deleted file mode 100644 index 021b8d5a425..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptToken.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BaseToken } from '../base/baseToken.js'; - -/** - * Common base token that all chatbot `prompt` tokens should inherit from. - */ -export abstract class PromptToken extends BaseToken { } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts deleted file mode 100644 index ec5dd2e7a36..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/codecs/tokens/promptVariable.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptToken } from './promptToken.js'; -import { IRange, Range } from '../../../../../../../editor/common/core/range.js'; - -/** - * All prompt variables start with `#` character. - */ -const START_CHARACTER: string = '#'; - -/** - * Character that separates name of a prompt variable from its data. - */ -const DATA_SEPARATOR: string = ':'; - -/** - * Represents a `#variable` token in a prompt text. - */ -export class PromptVariable extends PromptToken { - constructor( - range: Range, - /** - * The name of a prompt variable, excluding the `#` character at the start. - */ - public readonly name: string, - ) { - - super(range); - } - - /** - * Get full text of the token. - */ - public get text(): string { - return `${START_CHARACTER}${this.name}`; - } - - /** - * Return a string representation of the token. - */ - public override toString(): string { - return `${this.text}${this.range}`; - } -} - -/** - * Represents a {@link PromptVariable} with additional data token in a prompt text. - * (e.g., `#variable:/path/to/file.md`) - */ -export class PromptVariableWithData extends PromptVariable { - constructor( - fullRange: Range, - /** - * The name of the variable, excluding the starting `#` character. - */ - name: string, - - /** - * The data of the variable, excluding the starting {@link DATA_SEPARATOR} character. - */ - public readonly data: string, - ) { - super(fullRange, name); - } - - /** - * Get full text of the token. - */ - public override get text(): string { - return `${START_CHARACTER}${this.name}${DATA_SEPARATOR}${this.data}`; - } - - /** - * Range of the `data` part of the variable. - */ - public get dataRange(): IRange | undefined { - const { range } = this; - - // calculate the start column number of the `data` part of the variable - const dataStartColumn = range.startColumn + - START_CHARACTER.length + this.name.length + - DATA_SEPARATOR.length; - - // create `range` of the `data` part of the variable - const result = new Range( - range.startLineNumber, - dataStartColumn, - range.endLineNumber, - range.endColumn, - ); - - // if the resulting range is empty, return `undefined` - // because there is no `data` part present in the variable - if (result.isEmpty()) { - return undefined; - } - - return result; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts deleted file mode 100644 index 47705213282..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts +++ /dev/null @@ -1,166 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PROMPT_LANGUAGE_ID } from '../promptTypes.js'; -import { IPromptContentsProvider } from './types.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { assert } from '../../../../../../base/common/assert.js'; -import { CancellationError } from '../../../../../../base/common/errors.js'; -import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { isPromptOrInstructionsFile } from '../config/promptFileLocations.js'; -import { IPromptContentsProviderOptions, PromptContentsProviderBase } from './promptContentsProviderBase.js'; -import { OpenFailed, NotPromptFile, ResolveError, FolderReference } from '../../promptFileReferenceErrors.js'; -import { FileChangesEvent, FileChangeType, IFileService } from '../../../../../../platform/files/common/files.js'; - -/** - * Prompt contents provider for a file on the disk referenced by - * a provided {@link URI}. - */ -export class FilePromptContentProvider extends PromptContentsProviderBase implements IPromptContentsProvider { - public override get sourceName(): string { - return 'file'; - } - - public override get languageId(): string { - if (this.options.languageId) { - return this.options.languageId; - } - - const model = this.modelService.getModel(this.uri); - - if (model !== null) { - return model.getLanguageId(); - } - - const inferredId = this.languageService - .guessLanguageIdByFilepathOrFirstLine(this.uri); - - if (inferredId !== null) { - return inferredId; - } - - // fallback to the default prompt language ID - return PROMPT_LANGUAGE_ID; - } - - constructor( - public readonly uri: URI, - options: IPromptContentsProviderOptions, - @IFileService private readonly fileService: IFileService, - @IModelService private readonly modelService: IModelService, - @ILanguageService private readonly languageService: ILanguageService, - ) { - super(options); - - if (options.updateOnChange) { - // make sure the object is updated on file changes - this._register( - this.fileService.onDidFilesChange((event) => { - // if file was added or updated, forward the event to - // the `getContentsStream()` produce a new stream for file contents - if (event.contains(this.uri, FileChangeType.ADDED, FileChangeType.UPDATED)) { - // we support only full file parsing right now because - // the event doesn't contain a list of changed lines - this.onChangeEmitter.fire('full'); - return; - } - - // if file was deleted, forward the event to - // the `getContentsStream()` produce an error - if (event.contains(this.uri, FileChangeType.DELETED)) { - this.onChangeEmitter.fire(event); - return; - } - }), - ); - } - } - - /** - * Creates a stream of lines from the file based on the changes listed in - * the provided event. - * - * @param event - event that describes the changes in the file; `'full'` is - * the special value that means that all contents have changed - * @param cancellationToken - token that cancels this operation - */ - protected async getContentsStream( - _event: FileChangesEvent | 'full', - cancellationToken?: CancellationToken, - ): Promise { - assert( - !cancellationToken?.isCancellationRequested, - new CancellationError(), - ); - - // get the binary stream of the file contents - let fileStream; - try { - // ensure that the referenced URI points to a file before - // trying to get a stream for its contents - const info = await this.fileService.resolve(this.uri); - - // validate that the cancellation was not yet requested - assert( - !cancellationToken?.isCancellationRequested, - new CancellationError(), - ); - - assert( - info.isFile, - new FolderReference(this.uri), - ); - - const { allowNonPromptFiles } = this.options; - - // if URI doesn't point to a prompt file, don't try to resolve it, - // unless the `allowNonPromptFiles` option is set to `true` - if ((allowNonPromptFiles !== true) && (isPromptOrInstructionsFile(this.uri) === false)) { - throw new NotPromptFile(this.uri); - } - - fileStream = await this.fileService.readFileStream(this.uri); - - // after the promise above complete, this object can be already disposed or - // the cancellation could be requested, in that case destroy the stream and - // throw cancellation error - if (this.isDisposed || cancellationToken?.isCancellationRequested) { - fileStream.value.destroy(); - throw new CancellationError(); - } - - return fileStream.value; - } catch (error) { - if ((error instanceof ResolveError) || (error instanceof CancellationError)) { - throw error; - } - - throw new OpenFailed(this.uri, error); - } - } - - public override createNew( - promptContentsSource: { uri: URI }, - options: IPromptContentsProviderOptions, - ): IPromptContentsProvider { - return new FilePromptContentProvider( - promptContentsSource.uri, - options, - this.fileService, - this.modelService, - this.languageService, - ); - } - - /** - * String representation of this object. - */ - public override toString(): string { - return `file-prompt-contents-provider:${this.uri.path}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts deleted file mode 100644 index 7c9e58dc3d3..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/promptContentsProviderBase.ts +++ /dev/null @@ -1,196 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../base/common/assert.js'; -import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { cancelPreviousCalls } from '../../../../../../base/common/decorators/cancelPreviousCalls.js'; -import { CancellationError } from '../../../../../../base/common/errors.js'; -import { Emitter } from '../../../../../../base/common/event.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { FailedToResolveContentsStream, ResolveError } from '../../promptFileReferenceErrors.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; -import { ObservableDisposable } from '../utils/observableDisposable.js'; -import { IPromptContentsProvider } from './types.js'; - -/** - * Options of the {@link PromptContentsProviderBase} class. - */ -export interface IPromptContentsProviderOptions { - /** - * Whether to allow files that don't have usual prompt - * file extension to be treated as a prompt file. - */ - readonly allowNonPromptFiles: boolean; - - /** - * Language ID to use for the prompt contents. If not set, the language ID will be inferred from the file. - */ - readonly languageId: string | undefined; - - /** - * If set to `true`, the contents provider will listen for updates and retrigger a parse. - */ - readonly updateOnChange: boolean; -} - - -/** - * Base class for prompt contents providers. Classes that extend this one are responsible to: - * - * - implement the {@link getContentsStream} method to provide the contents stream - * of a prompt; this method should throw a `ResolveError` or its derivative if the contents - * cannot be parsed for any reason - * - fire a {@link TChangeEvent} event on the {@link onChangeEmitter} event when - * prompt contents change - * - misc: - * - provide the {@link uri} property that represents the URI of a prompt that - * the contents are for - * - implement the {@link toString} method to return a string representation of this - * provider type to aid with debugging/tracing - */ -export abstract class PromptContentsProviderBase< - TChangeEvent extends NonNullable, -> extends ObservableDisposable implements IPromptContentsProvider { - public abstract readonly uri: URI; - public abstract createNew(promptContentsSource: { uri: URI }, options: IPromptContentsProviderOptions): IPromptContentsProvider; - public abstract override toString(): string; - public abstract get languageId(): string; - public abstract get sourceName(): string; - - /** - * Prompt contents stream. - */ - public get contents(): Promise { - return this.getContentsStream('full'); - } - - /** - * Prompt type used to determine how to interpret file contents. - */ - public get promptType(): PromptsType | 'non-prompt' { - const { languageId } = this; - - if (languageId === PROMPT_LANGUAGE_ID) { - return PromptsType.prompt; - } - - if (languageId === INSTRUCTIONS_LANGUAGE_ID) { - return PromptsType.instructions; - } - - if (languageId === MODE_LANGUAGE_ID) { - return PromptsType.mode; - } - - return 'non-prompt'; - } - - /** - * Function to get contents stream for the provider. This function should - * throw a `ResolveError` or its derivative if the contents cannot be parsed. - * - * @param changesEvent The event that triggered the change. The special - * `'full'` value means that everything has changed hence entire prompt - * contents need to be re-parsed from scratch. - */ - protected abstract getContentsStream( - changesEvent: TChangeEvent | 'full', - cancellationToken?: CancellationToken, - ): Promise; - - /** - * Internal event emitter for the prompt contents change event. Classes that extend - * this abstract class are responsible to use this emitter to fire the contents change - * event when the prompt contents get modified. - */ - protected readonly onChangeEmitter = this._register(new Emitter()); - - /** - * Options passed to the constructor - */ - protected readonly options: IPromptContentsProviderOptions; - - constructor( - options: IPromptContentsProviderOptions, - ) { - super(); - - this.options = options; - } - - /** - * Event emitter for the prompt contents change event. - * See {@link onContentChanged} for more details. - */ - private readonly onContentChangedEmitter = this._register(new Emitter()); - - /** - * Event that fires when the prompt contents change. The event is either - * a `VSBufferReadableStream` stream with changed contents or an instance of - * the `ResolveError` class representing a parsing failure case. - * - * `Note!` this field is meant to be used by the external consumers of the prompt - * contents provider that the classes that extend this abstract class. - * Please use the {@link onChangeEmitter} event to provide a change - * event in your prompt contents implementation instead. - */ - public readonly onContentChanged = this.onContentChangedEmitter.event; - - /** - * Internal common implementation of the event that should be fired when - * prompt contents change. - */ - @cancelPreviousCalls - private onContentsChanged( - event: TChangeEvent | 'full', - cancellationToken?: CancellationToken, - ): this { - const promise = (cancellationToken?.isCancellationRequested) - ? Promise.reject(new CancellationError()) - : this.getContentsStream(event, cancellationToken); - - promise - .then((stream) => { - if (cancellationToken?.isCancellationRequested || this.isDisposed) { - stream.destroy(); - throw new CancellationError(); - } - - this.onContentChangedEmitter.fire(stream); - }) - .catch((error) => { - if (error instanceof ResolveError) { - this.onContentChangedEmitter.fire(error); - - return; - } - - this.onContentChangedEmitter.fire( - new FailedToResolveContentsStream(this.uri, error), - ); - }); - - return this; - } - - /** - * Start producing the prompt contents data. - */ - public start(token?: CancellationToken): this { - assert( - !this.isDisposed, - 'Cannot start contents provider that was already disposed.', - ); - - // `'full'` means "everything has changed" - this.onContentsChanged('full', token); - - // subscribe to the change event emitted by a child class - this._register(this.onChangeEmitter.event(this.onContentsChanged, this)); - - return this; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts deleted file mode 100644 index aea65908ccf..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/textModelContentsProvider.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { TextModel } from '../../../../../../editor/common/model/textModel.js'; -import { IModelContentChangedEvent } from '../../../../../../editor/common/textModelEvents.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { objectStreamFromTextModel } from '../codecs/base/utils/objectStreamFromTextModel.js'; -import { FilePromptContentProvider } from './filePromptContentsProvider.js'; -import { IPromptContentsProviderOptions, PromptContentsProviderBase } from './promptContentsProviderBase.js'; -import { IPromptContentsProvider } from './types.js'; - -/** - * Prompt contents provider for a {@link ITextModel} instance. - */ -export class TextModelContentsProvider extends PromptContentsProviderBase { - /** - * URI component of the prompt associated with this contents provider. - */ - public get uri(): URI { - return this.model.uri; - } - - public override get sourceName(): string { - return 'text-model'; - } - - public override get languageId(): string { - return this.options.languageId ?? this.model.getLanguageId(); - } - - constructor( - private readonly model: ITextModel, - options: IPromptContentsProviderOptions, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(options); - - this._register(this.model.onWillDispose(this.dispose.bind(this))); - if (options.updateOnChange) { - this._register(this.model.onDidChangeContent(this.onChangeEmitter.fire.bind(this.onChangeEmitter))); - } - } - - /** - * Creates a stream of binary data from the text model based on the changes - * listed in the provided event. - * - * Note! this method implements a basic logic which does not take into account - * the `_event` argument for incremental updates. This needs to be improved. - * - * @param _event - event that describes the changes in the text model; `'full'` is - * the special value that means that all contents have changed - * @param cancellationToken - token that cancels this operation - */ - protected override async getContentsStream( - _event: IModelContentChangedEvent | 'full', - cancellationToken?: CancellationToken, - ): Promise { - return objectStreamFromTextModel(this.model, cancellationToken); - } - - public override createNew( - promptContentsSource: TextModel | { uri: URI }, - options: IPromptContentsProviderOptions, - ): IPromptContentsProvider { - if (promptContentsSource instanceof TextModel) { - return this.instantiationService.createInstance( - TextModelContentsProvider, - promptContentsSource, - options, - ); - } - - return this.instantiationService.createInstance( - FilePromptContentProvider, - promptContentsSource.uri, - options, - ); - } - - /** - * String representation of this object. - */ - public override toString(): string { - return `text-model-prompt-contents-provider:${this.uri.path}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts deleted file mode 100644 index a26eaa43330..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { Event } from '../../../../../../base/common/event.js'; -import { ResolveError } from '../../promptFileReferenceErrors.js'; -import { IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { PromptsType } from '../promptTypes.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { IPromptContentsProviderOptions } from './promptContentsProviderBase.js'; - -/** - * Interface for a prompt contents provider. Prompt contents providers are - * responsible for providing contents of a prompt as a byte streams and - * allow to subscribe to the change events of the prompt contents. - */ -export interface IPromptContentsProvider extends IDisposable { - /** - * URI component of the prompt associated with this contents provider. - */ - readonly uri: URI; - - /** - * Language ID of the prompt contents. - */ - readonly languageId: string; - - /** - * Prompt type used to determine how to interpret file contents. - */ - readonly promptType: PromptsType | 'non-prompt'; - - /** - * Prompt contents stream. - */ - readonly contents: Promise; - - /** - * Prompt contents source name. - */ - readonly sourceName: string; - - /** - * Event that fires when the prompt contents change. The event is either a - * {@linkcode VSBufferReadableStream} stream with changed contents or - * an instance of the {@linkcode ResolveError} error. - */ - readonly onContentChanged: Event; - - /** - * Subscribe to `onDispose` event of the contents provider. - */ - readonly onDispose: Event; - - /** - * Start the contents provider to produce the underlying contents. - */ - start(token?: CancellationToken): this; - - /** - * Create a new instance of prompt contents provider. - */ - createNew( - promptContentsSource: { uri: URI }, - options: IPromptContentsProviderOptions, - ): IPromptContentsProvider; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts deleted file mode 100644 index d07720eff5b..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterDecoration.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Position } from '../../../../../../../../editor/common/core/position.js'; -import { localize } from '../../../../../../../../nls.js'; -import { contrastBorder, editorBackground } from '../../../../../../../../platform/theme/common/colorRegistry.js'; -import { asCssVariable, ColorIdentifier, darken, registerColor } from '../../../../../../../../platform/theme/common/colorUtils.js'; -import { BaseToken } from '../../../codecs/base/baseToken.js'; -import { FrontMatterHeader } from '../../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { CssClassModifiers } from '../types.js'; -import { FrontMatterMarkerDecoration } from './frontMatterMarkerDecoration.js'; -import { ReactiveDecorationBase } from './utils/reactiveDecorationBase.js'; -import { IReactiveDecorationClassNames, TAddAccessor, TDecorationStyles } from './utils/types.js'; - -/** - * Decoration CSS class names. - */ -export enum CssClassNames { - Main = '.prompt-front-matter-decoration', - Inline = '.prompt-front-matter-decoration-inline', - MainInactive = `${CssClassNames.Main}${CssClassModifiers.Inactive}`, - InlineInactive = `${CssClassNames.Inline}${CssClassModifiers.Inactive}`, -} - -/** - * Main background color of `active` Front Matter header block. - */ -export const BACKGROUND_COLOR: ColorIdentifier = registerColor( - 'prompt.frontMatter.background', - { dark: darken(editorBackground, 0.2), light: darken(editorBackground, 0.05), hcDark: contrastBorder, hcLight: contrastBorder }, - localize('chat.prompt.frontMatter.background.description', "Background color of a Front Matter header block."), -); - -/** - * Background color of `inactive` Front Matter header block. - */ -export const INACTIVE_BACKGROUND_COLOR: ColorIdentifier = registerColor( - 'prompt.frontMatter.inactiveBackground', - { dark: darken(editorBackground, 0.1), light: darken(editorBackground, 0.025), hcDark: contrastBorder, hcLight: contrastBorder }, - localize('chat.prompt.frontMatter.inactiveBackground.description', "Background color of an inactive Front Matter header block."), -); - -/** - * CSS styles for the decoration. - */ -export const CSS_STYLES = { - [CssClassNames.Main]: [ - `background-color: ${asCssVariable(BACKGROUND_COLOR)};`, - 'z-index: -1;', // this is required to allow for selections to appear above the decoration background - ], - [CssClassNames.MainInactive]: [ - `background-color: ${asCssVariable(INACTIVE_BACKGROUND_COLOR)};`, - ], - [CssClassNames.InlineInactive]: [ - 'color: var(--vscode-disabledForeground);', - ], - ...FrontMatterMarkerDecoration.cssStyles, -}; - -/** - * Editor decoration for the Front Matter header token inside a prompt. - */ -export class FrontMatterDecoration extends ReactiveDecorationBase { - constructor( - accessor: TAddAccessor, - token: FrontMatterHeader, - ) { - super(accessor, token); - - this.childDecorators.push( - new FrontMatterMarkerDecoration(accessor, token.startMarker), - new FrontMatterMarkerDecoration(accessor, token.endMarker), - ); - } - - public override setCursorPosition( - position: Position | null | undefined, - ): this is { readonly changed: true } { - const result = super.setCursorPosition(position); - - for (const marker of this.childDecorators) { - if ((marker instanceof FrontMatterMarkerDecoration) === false) { - continue; - } - - // activate/deactivate markers based on the active state - // of the main Front Matter header decoration - marker.activate(this.active); - } - - return result; - } - - protected override get classNames(): IReactiveDecorationClassNames { - return CssClassNames; - } - - protected override get isWholeLine(): boolean { - return true; - } - - protected override get description(): string { - return 'Front Matter header decoration.'; - } - - public static get cssStyles(): TDecorationStyles { - return CSS_STYLES; - } - - /** - * Whether current decoration class can decorate provided token. - */ - public static handles( - token: BaseToken, - ): token is FrontMatterHeader { - return token instanceof FrontMatterHeader; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts deleted file mode 100644 index 2a4a174c9f4..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/frontMatterMarkerDecoration.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CssClassModifiers } from '../types.js'; -import { TDecorationStyles, IReactiveDecorationClassNames } from './utils/types.js'; -import { FrontMatterMarker } from '../../../codecs/base/markdownExtensionsCodec/tokens/frontMatterMarker.js'; -import { ReactiveDecorationBase } from './utils/reactiveDecorationBase.js'; - -/** - * Decoration CSS class names. - */ -export enum CssClassNames { - Main = '.prompt-front-matter-decoration-marker', - Inline = '.prompt-front-matter-decoration-marker-inline', - MainInactive = `${CssClassNames.Main}${CssClassModifiers.Inactive}`, - InlineInactive = `${CssClassNames.Inline}${CssClassModifiers.Inactive}`, -} - -/** - * Editor decoration for a `marker` token of a Front Matter header. - */ -export class FrontMatterMarkerDecoration extends ReactiveDecorationBase { - /** - * Activate/deactivate the decoration. - */ - public activate(state: boolean): this { - const position = (state === true) - ? this.token.range.getStartPosition() - : null; - - this.setCursorPosition(position); - - return this; - } - - protected override get classNames(): IReactiveDecorationClassNames { - return CssClassNames; - } - - protected override get description(): string { - return 'Marker decoration of a Front Matter header.'; - } - - public static get cssStyles(): TDecorationStyles { - return { - [CssClassNames.Inline]: [ - 'color: var(--vscode-disabledForeground);', - ], - [CssClassNames.InlineInactive]: [ - 'opacity: 0.25;', - ], - }; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts deleted file mode 100644 index 8f28ec33247..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/decorationBase.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { IMarkdownString } from '../../../../../../../../../base/common/htmlContent.js'; -import { BaseToken } from '../../../../codecs/base/baseToken.js'; -import { TrackedRangeStickiness } from '../../../../../../../../../editor/common/model.js'; -import type { TAddAccessor, TChangeAccessor, TDecorationStyles, TRemoveAccessor } from './types.js'; -import { ModelDecorationOptions } from '../../../../../../../../../editor/common/model/textModel.js'; - -/** - * Base class for all editor decorations. - */ -export abstract class DecorationBase< - TPromptToken extends BaseToken, - TCssClassName extends string = string, -> { - /** - * Description of the decoration. - */ - protected abstract get description(): string; - - /** - * Default CSS class name of the decoration. - */ - protected abstract get className(): TCssClassName; - - /** - * Inline CSS class name of the decoration. - */ - protected abstract get inlineClassName(): TCssClassName; - - /** - * Indicates whether the decoration spans the whole line(s). - */ - protected get isWholeLine(): boolean { - return false; - } - - /** - * Hover message of the decoration. - */ - protected get hoverMessage(): IMarkdownString | IMarkdownString[] | null { - return null; - } - - /** - * ID of editor decoration it was registered with. - */ - public readonly id: string; - - constructor( - accessor: TAddAccessor, - protected readonly token: TPromptToken, - ) { - this.id = accessor.addDecoration(this.range, this.decorationOptions); - } - - /** - * Range of the decoration. - */ - public get range(): Range { - return this.token.range; - } - - /** - * Changes the decoration in the editor. - */ - public change( - accessor: TChangeAccessor, - ): this { - accessor.changeDecorationOptions( - this.id, - this.decorationOptions, - ); - - return this; - } - - /** - * Removes associated editor decoration(s). - */ - public remove( - accessor: TRemoveAccessor, - ): this { - accessor.removeDecoration(this.id); - - return this; - } - - /** - * Get editor decoration options for this decorator. - */ - private get decorationOptions(): ModelDecorationOptions { - return ModelDecorationOptions.createDynamic({ - description: this.description, - hoverMessage: this.hoverMessage, - className: this.className, - inlineClassName: this.inlineClassName, - isWholeLine: this.isWholeLine, - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - shouldFillLineOnLineBreak: true, - }); - } -} - -/** - * Type of a generic decoration class. - */ -export type TDecorationClass = { - new( - accessor: TAddAccessor, - token: TPromptToken, - ): DecorationBase; - - /** - * CSS styles for the decoration. - */ - readonly cssStyles: TDecorationStyles; - - /** - * Whether the decoration class handles the provided token. - */ - handles(token: BaseToken): token is TPromptToken; -}; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts deleted file mode 100644 index 42533aea5fe..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/reactiveDecorationBase.ts +++ /dev/null @@ -1,162 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DecorationBase } from './decorationBase.js'; -import { Position } from '../../../../../../../../../editor/common/core/position.js'; -import { BaseToken } from '../../../../codecs/base/baseToken.js'; -import type { IReactiveDecorationClassNames, TAddAccessor, TChangeAccessor, TRemoveAccessor } from './types.js'; - -/** - * Base class for all reactive editor decorations. A reactive decoration - * is a decoration that can change its appearance based on current cursor - * position in the editor, hence can "react" to the user's actions. - */ -export abstract class ReactiveDecorationBase< - TPromptToken extends BaseToken, - TCssClassName extends string = string, -> extends DecorationBase { - /** - * CSS class names of the decoration. - */ - protected abstract get classNames(): IReactiveDecorationClassNames; - - /** - * A list of child decorators that are part of this decoration. - * For instance a Front Matter header decoration can have child - * decorators for each of the header's `---` markers. - */ - protected readonly childDecorators: DecorationBase[]; - - /** - * Whether the decoration has changed since the last {@link change}. - */ - public get changed(): boolean { - // if any of the child decorators changed, this object is also - // considered to be changed - for (const marker of this.childDecorators) { - if ((marker instanceof ReactiveDecorationBase) === false) { - continue; - } - - if (marker.changed === true) { - return true; - } - } - - return this.didChange; - } - - constructor( - accessor: TAddAccessor, - token: TPromptToken, - ) { - super(accessor, token); - - this.childDecorators = []; - } - - /** - * Current position of cursor in the editor. - */ - private cursorPosition?: Position | null; - - /** - * Private field for the {@link changed} property. - */ - private didChange = true; - - /** - * Whether cursor is currently inside the decoration range. - */ - protected get active(): boolean { - return true; - - /** - * Temporarily disable until we have a proper way to get - * the cursor position inside active editor. - */ - /** - * if (!this.cursorPosition) { - * return false; - * } - * - * // when cursor is at the end of a range, the range considered to - * // not contain the position, but we want to include it - * const atEnd = (this.range.endLineNumber === this.cursorPosition.lineNumber) - * && (this.range.endColumn === this.cursorPosition.column); - * - * return atEnd || this.range.containsPosition(this.cursorPosition); - */ - } - - /** - * Set cursor position and update {@link changed} property if needed. - */ - public setCursorPosition( - position: Position | null | undefined, - ): this is { readonly changed: true } { - if (this.cursorPosition === position) { - return false; - } - - if (this.cursorPosition && position) { - if (this.cursorPosition.equals(position)) { - return false; - } - } - - const wasActive = this.active; - this.cursorPosition = position; - this.didChange = (wasActive !== this.active); - - return this.changed; - } - - public override change( - accessor: TChangeAccessor, - ): this { - if (this.didChange === false) { - return this; - } - - super.change(accessor); - this.didChange = false; - - for (const marker of this.childDecorators) { - marker.change(accessor); - } - - return this; - } - - public override remove( - accessor: TRemoveAccessor, - ): this { - super.remove(accessor); - - for (const marker of this.childDecorators) { - marker.remove(accessor); - } - - return this; - } - - protected override get className(): TCssClassName { - return (this.active) - ? this.classNames.Main - : this.classNames.MainInactive; - } - - protected override get inlineClassName(): TCssClassName { - return (this.active) - ? this.classNames.Inline - : this.classNames.InlineInactive; - } -} - -/** - * Type for a decorator with {@link ReactiveDecorationBase.changed changed} property set to `true`. - */ -export type TChangedDecorator = ReactiveDecorationBase & { readonly changed: true }; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts deleted file mode 100644 index 1ca7e270d3f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/decorations/utils/types.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IModelDecorationsChangeAccessor } from '../../../../../../../../../editor/common/model.js'; - -/** - * CSS class names of a `reactive` decoration. - */ -export interface IReactiveDecorationClassNames { - /** - * Main, default CSS class name of the decoration. - */ - readonly Main: T; - - /** - * CSS class name of the decoration for the `inline`(text) styles. - */ - readonly Inline: T; - - /** - * main CSS class name of the decoration for the `inactive` - * decoration state. - */ - readonly MainInactive: T; - - /** - * CSS class name of the decoration for the `inline`(text) - * styles when decoration is in the `inactive` state. - */ - readonly InlineInactive: T; -} - -/** - * CSS styles for a decoration to be registered with editor. - */ -export type TDecorationStyles = { - readonly [key in TClassNames]: readonly string[]; -}; - -/** - * A model decorations accessor that can be used to `add` a decoration. - */ -export type TAddAccessor = Pick; - -/** - * A model decorations accessor that can be used to `change` a decoration. - */ -export type TChangeAccessor = Pick; - -/** - * A model decorations accessor that can be used to `remove` a decoration. - */ -export type TRemoveAccessor = Pick; - diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts deleted file mode 100644 index d7a9d9b235f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/promptDecorationsProvider.ts +++ /dev/null @@ -1,205 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IPromptsService } from '../../service/promptsService.js'; -import { ProviderInstanceBase } from '../providerInstanceBase.js'; -import { ITextModel } from '../../../../../../../editor/common/model.js'; -import { FrontMatterDecoration } from './decorations/frontMatterDecoration.js'; -import { toDisposable } from '../../../../../../../base/common/lifecycle.js'; -import { Position } from '../../../../../../../editor/common/core/position.js'; -import { BaseToken } from '../../codecs/base/baseToken.js'; -import { ProviderInstanceManagerBase, TProviderClass } from '../providerInstanceManagerBase.js'; -import { registerThemingParticipant } from '../../../../../../../platform/theme/common/themeService.js'; -import { FrontMatterHeader } from '../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { ReactiveDecorationBase, TChangedDecorator } from './decorations/utils/reactiveDecorationBase.js'; -import { DecorationBase, TDecorationClass } from './decorations/utils/decorationBase.js'; - -/** - * Prompt tokens that are decorated by this provider. - */ -type TDecoratedToken = FrontMatterHeader; - -/** - * List of all supported decorations. - */ -const SUPPORTED_DECORATIONS: readonly TDecorationClass[] = Object.freeze([ - FrontMatterDecoration, -]); - -/** - * Prompt syntax decorations provider for text models. - */ -export class PromptDecorator extends ProviderInstanceBase { - /** - * Currently active decorations. - */ - private readonly decorations: DecorationBase[] = []; - - constructor( - model: ITextModel, - @IPromptsService promptsService: IPromptsService, - ) { - super(model, promptsService); - - this.watchCursorPosition(); - } - - protected override async onPromptSettled( - _error?: Error, - ): Promise { - // by the time the promise above completes, either this object - // or the text model might be already has been disposed - if (this.isDisposed || this.model.isDisposed()) { - return; - } - - this.addDecorations(); - - return; - } - - /** - * Get the current cursor position inside an active editor. - * Note! Currently not implemented because the provider is disabled, and - * we need to do some refactoring to get accurate cursor position. - */ - private get cursorPosition(): Position | null { - if (this.model.isDisposed()) { - return null; - } - - return null; - } - - /** - * Watch editor cursor position and update reactive decorations accordingly. - */ - private watchCursorPosition(): this { - const interval = setInterval(() => { - const { cursorPosition } = this; - - const changedDecorations: TChangedDecorator[] = []; - for (const decoration of this.decorations) { - if ((decoration instanceof ReactiveDecorationBase) === false) { - continue; - } - - if (decoration.setCursorPosition(cursorPosition) === true) { - changedDecorations.push(decoration); - } - } - - if (changedDecorations.length === 0) { - return; - } - - this.changeModelDecorations(changedDecorations); - }, 25); - - this._register(toDisposable(() => { - clearInterval(interval); - })); - - return this; - } - - /** - * Update existing decorations. - */ - private changeModelDecorations( - decorations: readonly TChangedDecorator[], - ): this { - this.model.changeDecorations((accessor) => { - for (const decoration of decorations) { - decoration.change(accessor); - } - }); - - return this; - } - - /** - * Add decorations for all prompt tokens. - */ - private addDecorations(): this { - this.model.changeDecorations((accessor) => { - const { tokens } = this.parser; - - // remove all existing decorations - for (const decoration of this.decorations.splice(0)) { - decoration.remove(accessor); - } - - // then add new decorations based on the current tokens - for (const token of tokens) { - for (const Decoration of SUPPORTED_DECORATIONS) { - if (Decoration.handles(token) === false) { - continue; - } - - this.decorations.push( - new Decoration(accessor, token), - ); - break; - } - } - }); - - return this; - } - - /** - * Remove all existing decorations. - */ - private removeAllDecorations(): this { - if (this.decorations.length === 0) { - return this; - } - - this.model.changeDecorations((accessor) => { - for (const decoration of this.decorations.splice(0)) { - decoration.remove(accessor); - } - }); - - return this; - } - - public override dispose(): void { - if (this.isDisposed) { - return; - } - - this.removeAllDecorations(); - super.dispose(); - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `text-model-prompt-decorator:${this.model.uri.path}`; - } -} - -/** - * Register CSS styles of the supported decorations. - */ -registerThemingParticipant((_theme, collector) => { - for (const Decoration of SUPPORTED_DECORATIONS) { - for (const [className, styles] of Object.entries(Decoration.cssStyles)) { - collector.addRule(`.monaco-editor ${className} { ${styles.join(' ')} }`); - } - } -}); - -/** - * Provider for prompt syntax decorators on text models. - */ -export class PromptDecorationsProviderInstanceManager extends ProviderInstanceManagerBase { - protected override get InstanceClass(): TProviderClass { - return PromptDecorator; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts deleted file mode 100644 index fb3ef81b122..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/decorationsProvider/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IRange } from '../../../../../../../editor/common/core/range.js'; -import { ModelDecorationOptions } from '../../../../../../../editor/common/model/textModel.js'; - -/** - * Decoration object. - */ -export interface ITextModelDecoration { - /** - * Range of the decoration. - */ - range: IRange; - - /** - * Associated decoration options. - */ - options: ModelDecorationOptions; -} - -/** - * Decoration CSS class names. - */ -export enum DecorationClassNames { - /** - * CSS class name for `default` prompt syntax decoration. - */ - Default = 'prompt-decoration', - - /** - * CSS class name for `file reference` prompt syntax decoration. - */ - FileReference = DecorationClassNames.Default, -} - -/** - * Decoration CSS class modifiers. - */ -export enum CssClassModifiers { - /** - * CSS class modifier for `active` state of - * a `reactive` prompt syntax decoration. - */ - Inactive = '.prompt-decoration-inactive', -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts deleted file mode 100644 index b8bca008829..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderDiagnosticsProvider.ts +++ /dev/null @@ -1,234 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IPromptsService } from '../service/promptsService.js'; -import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { assertNever } from '../../../../../../base/common/assert.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { ProviderInstanceManagerBase, TProviderClass } from './providerInstanceManagerBase.js'; -import { TDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../parsers/promptHeader/diagnostics.js'; -import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; -import { PromptHeader } from '../parsers/promptHeader/promptHeader.js'; -import { PromptToolsMetadata } from '../parsers/promptHeader/metadata/tools.js'; -import { PromptModelMetadata } from '../parsers/promptHeader/metadata/model.js'; -import { ModeHeader } from '../parsers/promptHeader/modeHeader.js'; -import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; -import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; -import { localize } from '../../../../../../nls.js'; -import { ChatModeKind } from '../../constants.js'; -import { IChatMode, IChatModeService } from '../../chatModes.js'; -import { PromptModeMetadata } from '../parsers/promptHeader/metadata/mode.js'; -import { Iterable } from '../../../../../../base/common/iterator.js'; - -/** - * Unique ID of the markers provider class. - */ -const MARKERS_OWNER_ID = 'prompts-header-diagnostics-provider'; - -/** - * Prompt header diagnostics provider for an individual text model - * of a prompt file. - */ -class PromptHeaderDiagnosticsProvider extends ProviderInstanceBase { - constructor( - model: ITextModel, - @IPromptsService promptsService: IPromptsService, - @IMarkerService private readonly markerService: IMarkerService, - @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, - @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, - @IChatModeService private readonly chatModeService: IChatModeService, - ) { - super(model, promptsService); - this._register(languageModelsService.onDidChangeLanguageModels(() => { - this.onPromptSettled(undefined, CancellationToken.None); - })); - this._register(languageModelToolsService.onDidChangeTools(() => { - this.onPromptSettled(undefined, CancellationToken.None); - })); - this._register(chatModeService.onDidChangeChatModes(() => { - this.onPromptSettled(undefined, CancellationToken.None); - })); - } - - /** - * Update diagnostic markers for the current editor. - */ - protected override async onPromptSettled( - _error: Error | undefined, - token: CancellationToken, - ): Promise { - - const { header } = this.parser; - if (header === undefined) { - this.markerService.remove(MARKERS_OWNER_ID, [this.model.uri]); - return; - } - - // header parsing process is separate from the prompt parsing one, hence - // apply markers only after the header is settled and so has diagnostics - const completed = await header.settled; - if (!completed || token.isCancellationRequested) { - return; - } - - const markers: IMarkerData[] = []; - for (const diagnostic of header.diagnostics) { - markers.push(toMarker(diagnostic)); - } - - if (header instanceof PromptHeader) { - const mode = this.validateMode(header.metadataUtility.mode, markers); - this.validateTools(header.metadataUtility.tools, mode?.kind, markers); - this.validateModel(header.metadataUtility.model, mode?.kind, markers); - } else if (header instanceof ModeHeader) { - this.validateTools(header.metadataUtility.tools, ChatModeKind.Agent, markers); - this.validateModel(header.metadataUtility.model, ChatModeKind.Agent, markers); - - } - - if (markers.length === 0) { - this.markerService.remove(MARKERS_OWNER_ID, [this.model.uri]); - return; - } - - this.markerService.changeOne( - MARKERS_OWNER_ID, - this.model.uri, - markers, - ); - return; - } - validateModel(modelNode: PromptModelMetadata | undefined, modeKind: string | ChatModeKind | undefined, markers: IMarkerData[]) { - if (!modelNode || modelNode.value === undefined) { - return; - } - const languageModes = this.languageModelsService.getLanguageModelIds(); - if (languageModes.length === 0) { - // likely the service is not initialized yet - return; - } - const modelMetadata = this.findModelByName(languageModes, modelNode.value); - if (!modelMetadata) { - markers.push({ - message: localize('promptHeaderDiagnosticsProvider.modelNotFound', "Unknown model '{0}'", modelNode.value), - severity: MarkerSeverity.Warning, - ...modelNode.range, - }); - } else if (modeKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { - markers.push({ - message: localize('promptHeaderDiagnosticsProvider.modelNotSuited', "Model '{0}' is not suited for agent mode", modelNode.value), - severity: MarkerSeverity.Warning, - ...modelNode.range, - }); - } - - } - findModelByName(languageModes: string[], modelName: string): ILanguageModelChatMetadata | undefined { - for (const model of languageModes) { - const metadata = this.languageModelsService.lookupLanguageModel(model); - if (metadata && metadata.isUserSelectable !== false && ILanguageModelChatMetadata.matchesQualifiedName(modelName, metadata)) { - return metadata; - } - } - return undefined; - } - - validateTools(tools: PromptToolsMetadata | undefined, modeKind: string | ChatModeKind | undefined, markers: IMarkerData[]) { - if (!tools || tools.value === undefined || modeKind === ChatModeKind.Ask || modeKind === ChatModeKind.Edit) { - return; - } - const toolNames = new Set(tools.value); - if (toolNames.size === 0) { - return; - } - for (const tool of this.languageModelToolsService.getTools()) { - toolNames.delete(tool.toolReferenceName ?? tool.displayName); - } - for (const toolSet of this.languageModelToolsService.toolSets.get()) { - toolNames.delete(toolSet.referenceName); - } - - for (const toolName of toolNames) { - const range = tools.getToolRange(toolName); - if (range) { - markers.push({ - message: localize('promptHeaderDiagnosticsProvider.toolNotFound', "Unknown tool '{0}'", toolName), - severity: MarkerSeverity.Warning, - ...range, - }); - } - } - } - - validateMode(modeNode: PromptModeMetadata | undefined, markers: IMarkerData[]): IChatMode | undefined { - if (!modeNode || modeNode.value === undefined) { - return; - } - - const modeValue = modeNode.value; - const modes = this.chatModeService.getModes(); - const availableModes = []; - - // Check if mode exists in builtin or custom modes - for (const mode of Iterable.concat(modes.builtin, modes.custom)) { - if (mode.name === modeValue) { - return mode; - } - availableModes.push(mode.name); // collect all available mode names - } - - markers.push({ - message: localize('promptHeaderDiagnosticsProvider.modeNotFound', "Unknown mode '{0}'. Available modes: {1}", modeValue, availableModes.join(', ')), - severity: MarkerSeverity.Warning, - ...modeNode.range, - }); - return undefined; - - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `prompt-header-diagnostics:${this.model.uri.path}`; - } -} - -/** - * Convert a provided diagnostic object into a marker data object. - */ -function toMarker(diagnostic: TDiagnostic): IMarkerData { - if (diagnostic instanceof PromptMetadataWarning) { - return { - message: diagnostic.message, - severity: MarkerSeverity.Warning, - ...diagnostic.range, - }; - } - - if (diagnostic instanceof PromptMetadataError) { - return { - message: diagnostic.message, - severity: MarkerSeverity.Error, - ...diagnostic.range, - }; - } - - assertNever( - diagnostic, - `Unknown prompt metadata diagnostic type '${diagnostic}'.`, - ); -} - -/** - * The class that manages creation and disposal of {@link PromptHeaderDiagnosticsProvider} - * classes for each specific editor text model. - */ -export class PromptHeaderDiagnosticsInstanceManager extends ProviderInstanceManagerBase { - protected override get InstanceClass(): TProviderClass { - return PromptHeaderDiagnosticsProvider; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts deleted file mode 100644 index 3809f67ca8f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkDiagnosticsProvider.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IPromptsService } from '../service/promptsService.js'; -import { IPromptFileReference } from '../parsers/types.js'; -import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { ProviderInstanceManagerBase, TProviderClass } from './providerInstanceManagerBase.js'; -import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; -import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { localize } from '../../../../../../nls.js'; - -/** - * Unique ID of the markers provider class. - */ -const MARKERS_OWNER_ID = 'prompt-link-diagnostics-provider'; - -/** - * Prompt links diagnostics provider for a single text model. - */ -class PromptLinkDiagnosticsProvider extends ProviderInstanceBase { - constructor( - model: ITextModel, - @IPromptsService promptsService: IPromptsService, - @IMarkerService private readonly markerService: IMarkerService, - @IFileService private readonly fileService: IFileService - ) { - super(model, promptsService); - } - - /** - * Update diagnostic markers for the current editor. - */ - protected override async onPromptSettled(): Promise { - // clean up all previously added markers - this.markerService.remove(MARKERS_OWNER_ID, [this.model.uri]); - - const markers: IMarkerData[] = []; - - const stats = await this.fileService.resolveAll(this.parser.references.map(ref => ({ resource: ref.uri }))); - for (let i = 0; i < stats.length; i++) { - if (!stats[i].success) { - markers.push(toMarker(this.parser.references[i], localize('fileNotFound', 'File not found.'))); - } - } - - this.markerService.changeOne( - MARKERS_OWNER_ID, - this.model.uri, - markers, - ); - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `prompt-link-diagnostics:${this.model.uri.path}`; - } -} - -/** - * Convert a prompt link with an issue to a marker data. - * - * @throws - * - if there is no link issue (e.g., `topError` undefined) - * - if there is no link range to highlight (e.g., `linkRange` undefined) - * - if the original error is of `NotPromptFile` type - we don't want to - * show diagnostic markers for non-prompt file links in the prompts - */ -function toMarker(link: IPromptFileReference, message: string): IMarkerData { - const { linkRange } = link; - - assertDefined( - linkRange, - 'Link range must to be defined.', - ); - - - return { - message: message, - severity: MarkerSeverity.Warning, - ...linkRange, - }; -} - -/** - * The class that manages creation and disposal of {@link PromptLinkDiagnosticsProvider} - * classes for each specific editor text model. - */ -export class PromptLinkDiagnosticsInstanceManager extends ProviderInstanceManagerBase { - protected override get InstanceClass(): TProviderClass { - return PromptLinkDiagnosticsProvider; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts deleted file mode 100644 index da51052ab58..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceBase.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IPromptsService, TSharedPrompt } from '../service/promptsService.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { ObservableDisposable } from '../utils/observableDisposable.js'; -import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js'; - -/** - * Abstract base class for all reusable prompt file providers. - */ -export abstract class ProviderInstanceBase extends ObservableDisposable { - /** - * Function that is called when the prompt parser is settled. - */ - protected abstract onPromptSettled(error: Error | undefined, token: CancellationToken): Promise; - - /** - * Returns a string representation of this object. - */ - public abstract override toString(): string; - - /** - * The prompt parser instance. - */ - protected readonly parser: TSharedPrompt; - - constructor( - protected readonly model: ITextModel, - @IPromptsService promptsService: IPromptsService, - ) { - super(); - - this.parser = promptsService.getSyntaxParserFor(model); - - this._register( - this.parser.onDispose(this.dispose.bind(this)), - ); - - let cancellationSource = new CancellationTokenSource(); - this._register( - this.parser.onSettled((error) => { - cancellationSource.dispose(true); - cancellationSource = new CancellationTokenSource(); - - this.onPromptSettled(error, cancellationSource.token); - }), - ); - - this.parser.start(); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts deleted file mode 100644 index 36ff2339b49..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/providerInstanceManagerBase.ts +++ /dev/null @@ -1,176 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ProviderInstanceBase } from './providerInstanceBase.js'; -import { assert } from '../../../../../../base/common/assert.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { ObjectCache } from '../utils/objectCache.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../promptTypes.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { PromptsConfig } from '../config/config.js'; -import { IEditorService } from '../../../../../services/editor/common/editorService.js'; -import { IDiffEditor, IEditor, IEditorModel } from '../../../../../../editor/common/editorCommon.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; - -/** - * Type for a text editor that is used for reusable prompt files. - */ -export interface IPromptFileEditor extends IEditor { - readonly getModel: () => ITextModel; -} - -/** - * Type for a class that can create a new provider instance. - */ -export type TProviderClass = new (editor: ITextModel, ...args: any[]) => TInstance; - -/** - * A generic base class that manages creation and disposal of {@link TInstance} - * objects for each specific editor object that is used for reusable prompt files. - */ -export abstract class ProviderInstanceManagerBase extends Disposable { - /** - * Currently available {@link TInstance} instances. - */ - private readonly instances: ObjectCache; - - /** - * Class object of the managed {@link TInstance}. - */ - protected abstract get InstanceClass(): TProviderClass; - - constructor( - @IModelService modelService: IModelService, - @IEditorService editorService: IEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configService: IConfigurationService, - ) { - super(); - - // cache of managed instances - this.instances = this._register( - new ObjectCache((model: ITextModel) => { - assert( - model.isDisposed() === false, - 'Text model must not be disposed.', - ); - - // sanity check - the new TS/JS discrepancies regarding fields initialization - // logic mean that this can be `undefined` during runtime while defined in TS - assertDefined( - this.InstanceClass, - 'Instance class field must be defined.', - ); - - const instance: TInstance = instantiationService.createInstance( - this.InstanceClass, - model, - ); - - // this is a sanity check and the contract of the object cache, - // we must return a non-disposed object from this factory function - instance.assertNotDisposed( - 'Created instance must not be disposed.', - ); - - return instance; - }), - ); - - // if the feature is disabled, do not create any providers - if (PromptsConfig.enabled(configService) === false) { - return; - } - - // subscribe to changes of the active editor - this._register(editorService.onDidActiveEditorChange(() => { - const { activeTextEditorControl } = editorService; - if (activeTextEditorControl === undefined) { - return; - } - - this.handleNewEditor(activeTextEditorControl); - })); - - // handle existing visible text editors - editorService - .visibleTextEditorControls - .forEach(this.handleNewEditor.bind(this)); - - // subscribe to "language change" events for all models - this._register( - modelService.onModelLanguageChanged((event) => { - const { model, oldLanguageId } = event; - - // if language is set to `prompt` or `instructions` language, handle that model - if (isPromptFileModel(model)) { - this.instances.get(model); - return; - } - - // if the language is changed away from `prompt` or `instructions`, - // remove and dispose provider for this model - if (isPromptFile(oldLanguageId)) { - this.instances.remove(model, true); - return; - } - }), - ); - } - - /** - * Initialize a new {@link TInstance} for the given editor. - */ - private handleNewEditor(editor: IEditor | IDiffEditor): this { - const model = editor.getModel(); - if (model === null) { - return this; - } - - if (isPromptFileModel(model) === false) { - return this; - } - - // note! calling `get` also creates a provider if it does not exist; - // and the provider is auto-removed when the editor is disposed - this.instances.get(model); - - return this; - } -} - -/** - * Check if provided language ID is one of the prompt file languages. - */ -function isPromptFile(languageId: string): boolean { - return [ - PROMPT_LANGUAGE_ID, - INSTRUCTIONS_LANGUAGE_ID, - MODE_LANGUAGE_ID, - ].includes(languageId); -} - -/** - * Check if a provided model is used for prompt files. - */ -function isPromptFileModel(model: IEditorModel): model is ITextModel { - // we support only `text editors` for now so filter out `diff` ones - if ('modified' in model || 'model' in model) { - return false; - } - - if (model.isDisposed()) { - return false; - } - - if (isPromptFile(model.getLanguageId()) === false) { - return false; - } - - return true; -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts deleted file mode 100644 index 9fe72885d03..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ /dev/null @@ -1,728 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TopError } from './topError.js'; -import { ChatModeKind } from '../../constants.js'; -import { TMetadata } from './promptHeader/headerBase.js'; -import { ModeHeader } from './promptHeader/modeHeader.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { PromptToken } from '../codecs/tokens/promptToken.js'; -import * as path from '../../../../../../base/common/path.js'; -import { ChatPromptCodec } from '../codecs/chatPromptCodec.js'; -import { FileReference } from '../codecs/tokens/fileReference.js'; -import { ChatPromptDecoder } from '../codecs/chatPromptDecoder.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { Emitter } from '../../../../../../base/common/event.js'; -import { DeferredPromise } from '../../../../../../base/common/async.js'; -import { InstructionsHeader } from './promptHeader/instructionsHeader.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { PromptVariable, PromptVariableWithData } from '../codecs/tokens/promptVariable.js'; -import type { IPromptContentsProvider } from '../contentProviders/types.js'; -import type { TPromptReference, ITopError, TVariableReference } from './types.js'; -import { type IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { assert, assertNever } from '../../../../../../base/common/assert.js'; -import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; -import { BaseToken } from '../codecs/base/baseToken.js'; -import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; -import { type IRange, Range } from '../../../../../../editor/common/core/range.js'; -import { PromptHeader, type TPromptMetadata } from './promptHeader/promptHeader.js'; -import { ObservableDisposable } from '../utils/observableDisposable.js'; -import { INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../promptTypes.js'; -import { LinesDecoder } from '../codecs/base/linesCodec/linesDecoder.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownLink } from '../codecs/base/markdownCodec/tokens/markdownLink.js'; -import { MarkdownToken } from '../codecs/base/markdownCodec/tokens/markdownToken.js'; -import { FrontMatterHeader } from '../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { OpenFailed, NotPromptFile, RecursiveReference, FolderReference, ResolveError } from '../../promptFileReferenceErrors.js'; -import { type IPromptContentsProviderOptions } from '../contentProviders/promptContentsProviderBase.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; -import { Schemas } from '../../../../../../base/common/network.js'; - -/** - * Options of the {@link BasePromptParser} class. - */ -export interface IBasePromptParserOptions { -} - -export type IPromptParserOptions = IBasePromptParserOptions & IPromptContentsProviderOptions; - - -/** - * Error conditions that may happen during the file reference resolution. - */ -export type TErrorCondition = OpenFailed | RecursiveReference | FolderReference | NotPromptFile; - -/** - * Base prompt parser class that provides a common interface for all - * prompt parsers that are responsible for parsing chat prompt syntax. - */ -export class BasePromptParser extends ObservableDisposable { - /** - * Options passed to the constructor. - */ - protected readonly options: IBasePromptParserOptions; - - /** - * List of all tokens that were parsed from the prompt contents so far. - */ - public get tokens(): readonly BaseToken[] { - return [...this.receivedTokens]; - } - /** - * Private field behind the readonly {@link tokens} property. - */ - private receivedTokens: BaseToken[] = []; - - /** - * List of file references in the prompt file. - */ - private readonly _references: TPromptReference[] = []; - - /** - * List of variable references in the prompt file. - */ - private readonly _variableReferences: TVariableReference[] = []; - - /** - * Reference to the prompt header object that holds metadata associated - * with the prompt. - */ - private promptHeader?: PromptHeader | InstructionsHeader | ModeHeader | undefined; - - /** - * Reference to the prompt header object that holds metadata associated - * with the prompt. - */ - public get header(): PromptHeader | InstructionsHeader | ModeHeader | undefined { - return this.promptHeader; - } - - /** - * Get contents of the prompt body. - */ - public async getBody(): Promise { - const startLineNumber = (this.header !== undefined) - ? this.header.range.endLineNumber + 1 - : 1; - - const decoder = new LinesDecoder( - await this.promptContentsProvider.contents, - ); - - const tokens = (await decoder.consumeAll()) - .filter(({ range }) => { - return (range.startLineNumber >= startLineNumber); - }); - - return BaseToken.render(tokens); - } - - /** - * Get the full contents of the prompt, including the header - */ - public async getFullContent(): Promise { - const decoder = new LinesDecoder( - await this.promptContentsProvider.contents, - ); - const tokens = await decoder.consumeAll(); - return BaseToken.render(tokens); - } - - /** - * The event is fired when lines or their content change. - */ - private readonly _onUpdate = this._register(new Emitter()); - /** - * Subscribe to the event that is fired the parser state or contents - * changes, including changes in the possible prompt child references. - */ - public readonly onUpdate = this._onUpdate.event; - - /** - * Event that is fired when the current prompt parser is settled. - */ - private readonly _onSettled = this._register(new Emitter()); - - /** - * Event that is fired when the current prompt parser is settled. - */ - public onSettled( - callback: (error?: Error) => void, - ): IDisposable { - const disposable = this._onSettled.event(callback); - const streamEnded = (this.stream?.ended && (this.stream.isDisposed === false)); - - // if already in the error state or stream has already ended, - // invoke the callback immediately but asynchronously - if (streamEnded || this.errorCondition) { - setTimeout(callback.bind(undefined, this.errorCondition)); - - return disposable; - } - - return disposable; - } - - /** - * If failed to parse prompt contents, this property has - * an error object that describes the failure reason. - */ - private _errorCondition?: ResolveError; - - /** - * If file reference resolution fails, this attribute will be set - * to an error instance that describes the error condition. - */ - public get errorCondition(): ResolveError | undefined { - return this._errorCondition; - } - - /** - * Whether file references resolution failed. - * Set to `undefined` if the `resolve` method hasn't been ever called yet. - */ - public get resolveFailed(): boolean | undefined { - if (!this.firstParseResult.gotFirstResult) { - return undefined; - } - - return !!this._errorCondition; - } - - /** - * The promise is resolved when at least one parse result (a stream or - * an error) has been received from the prompt contents provider. - */ - private readonly firstParseResult = new FirstParseResult(); - - /** - * Returned promise is resolved when the parser process is settled. - * The settled state means that the prompt parser stream exists and - * has ended, or an error condition has been set in case of failure. - * - * Furthermore, this function can be called multiple times and will - * block until the latest prompt contents parsing logic is settled - * (e.g., for every `onContentChanged` event of the prompt source). - */ - public async settled(): Promise { - assert( - this.started, - 'Cannot wait on the parser that did not start yet.', - ); - - await this.firstParseResult.promise; - - if (this.errorCondition) { - return false; - } - - // by the time when the `firstParseResult` promise is resolved, - // this object may have been already disposed, hence noop - if (this.isDisposed) { - return false; - } - - assertDefined( - this.stream, - 'No stream reference found.', - ); - - const completed = await this.stream.settled; - - // if prompt header exists, also wait for it to be settled - if (this.promptHeader) { - const headerCompleted = await this.promptHeader.settled; - if (!headerCompleted) { - return false; - } - } - - return completed; - } - - constructor( - private readonly promptContentsProvider: TContentsProvider, - options: IBasePromptParserOptions, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly envService: IWorkbenchEnvironmentService, - @ILogService protected readonly logService: ILogService, - ) { - super(); - - this.options = options; - - this._register( - this.promptContentsProvider.onContentChanged((streamOrError) => { - // process the received message - this.onContentsChanged(streamOrError); - - // indicate that we've received at least one `onContentChanged` event - this.firstParseResult.end(); - }), - ); - - // dispose self when contents provider is disposed - this._register( - this.promptContentsProvider.onDispose(this.dispose.bind(this)), - ); - } - - /** - * The latest received stream of prompt tokens, if any. - */ - private stream: ChatPromptDecoder | undefined; - - /** - * Handler the event event that is triggered when prompt contents change. - * - * @param streamOrError Either a binary stream of file contents, or an error object - * that was generated during the reference resolve attempt. - * @param seenReferences List of parent references that we've have already seen - * during the process of traversing the references tree. It's - * used to prevent the tree navigation to fall into an infinite - * references recursion. - */ - private onContentsChanged( - streamOrError: VSBufferReadableStream | ResolveError - ): void { - // dispose and cleanup the previously received stream - // object or an error condition, if any received yet - this.stream?.dispose(); - delete this.stream; - delete this._errorCondition; - this.receivedTokens = []; - - // cleanup current prompt header object - this.promptHeader?.dispose(); - delete this.promptHeader; - - // dispose all currently existing references - this.disposeReferences(); - - // if an error received, set up the error condition and stop - if (streamOrError instanceof ResolveError) { - this._errorCondition = streamOrError; - this._onUpdate.fire(); - - // when error received fire the 'onSettled' event immediately - this._onSettled.fire(streamOrError); - - return; - } - - // decode the byte stream to a stream of prompt tokens - this.stream = ChatPromptCodec.decode(streamOrError); - - /** - * !NOTE! The order of event subscriptions below is critical here because - * the `data` event is also starts the stream, hence changing - * the order of event subscriptions can lead to race conditions. - * See {@link ReadableStreamEvents} for more info. - */ - - // on error or stream end, dispose the stream and fire the update event - this.stream.on('error', this.onStreamEnd.bind(this, this.stream)); - this.stream.on('end', this.onStreamEnd.bind(this, this.stream)); - - // when some tokens received, process and store the references - this.stream.on('data', (token) => { - // store all markdown and prompt token references - if ((token instanceof MarkdownToken) || (token instanceof PromptToken)) { - this.receivedTokens.push(token); - } - - // if a prompt header token received, create a new prompt header instance - if (token instanceof FrontMatterHeader) { - return this.createHeader(token); - } - - // try to convert a prompt variable with data token into a file reference - if (token instanceof PromptVariableWithData) { - try { - if (token.name === 'file') { - this.handleLinkToken(FileReference.from(token)); - } - } catch (error) { - // the `FileReference.from` call might throw if the `PromptVariableWithData` token - // can not be converted into a valid `#file` reference, hence we ignore the error - } - } else if (token instanceof PromptVariable) { - this.handleVariableToken(token); - } - - // note! the `isURL` is a simple check and needs to be improved to truly - // handle only file references, ignoring broken URLs or references - if (token instanceof MarkdownLink && !token.isURL) { - this.handleLinkToken(token); - } - }); - - // calling `start` on a disposed stream throws, so we warn and return instead - if (this.stream.isDisposed) { - this.logService.warn( - `[prompt parser][${basename(this.uri)}] cannot start stream that has been already disposed, aborting`, - ); - - return; - } - - // start receiving data on the stream - this.stream.start(); - } - - /** - * Create header object base on the target prompt file language ID. - * The language ID is important here, because it defines what type - * of metadata is valid for a prompt file and what type of related - * diagnostics we would show to the user. - */ - private createHeader(headerToken: FrontMatterHeader): void { - const { languageId } = this.promptContentsProvider; - - if (languageId === PROMPT_LANGUAGE_ID) { - this.promptHeader = new PromptHeader(headerToken, languageId); - } - - if (languageId === INSTRUCTIONS_LANGUAGE_ID) { - this.promptHeader = new InstructionsHeader(headerToken, languageId); - } - - if (languageId === MODE_LANGUAGE_ID) { - this.promptHeader = new ModeHeader(headerToken, languageId); - } - - this.promptHeader?.start(); - } - - /** - * Handle a new reference token inside prompt contents. - */ - private handleLinkToken(token: FileReference | MarkdownLink): this { - - let referenceUri: URI; - if (path.isAbsolute(token.path)) { - referenceUri = URI.file(token.path); - if (this.envService.remoteAuthority) { - referenceUri = referenceUri.with({ - scheme: Schemas.vscodeRemote, - authority: this.envService.remoteAuthority, - }); - } - } else { - referenceUri = joinPath(dirname(this.uri), token.path); - } - this._references.push(new PromptReference(referenceUri, token)); - - this._onUpdate.fire(); - - return this; - } - - private handleVariableToken(token: PromptVariable): this { - - this._variableReferences.push({ name: token.name, range: token.range }); - - this._onUpdate.fire(); - - return this; - } - - /** - * Handle the `stream` end event. - * - * @param stream The stream that has ended. - * @param error Optional error object if stream ended with an error. - */ - private onStreamEnd( - stream: ChatPromptDecoder, - error?: Error, - ): this { - // decoders can fire the 'end' event also when they are get disposed, - // but because we dispose them when a new stream is received, we can - // safely ignore the event in this case - if (stream.isDisposed === true) { - return this; - } - - if (error) { - this.logService.warn( - `[prompt parser][${basename(this.uri)}] received an error on the chat prompt decoder stream: ${error}`, - ); - } - - this._onUpdate.fire(); - this._onSettled.fire(error); - - return this; - } - - - private disposeReferences(): void { - - - this._references.length = 0; - this._variableReferences.length = 0; - } - - /** - * Private attribute to track if the {@link start} - * method has been already called at least once. - */ - private started: boolean = false; - - /** - * Start the prompt parser. - */ - public start(token?: CancellationToken): this { - // if already started, nothing to do - if (this.started) { - return this; - } - this.started = true; - - - // if already in the error state that could be set - // in the constructor, then nothing to do - if (this.errorCondition) { - return this; - } - - this.promptContentsProvider.start(token); - return this; - } - - /** - * Associated URI of the prompt. - */ - public get uri(): URI { - return this.promptContentsProvider.uri; - } - - /** - * Get a list of immediate child references of the prompt. - */ - public get references(): readonly TPromptReference[] { - return [...this._references]; - } - - /** - * Get a list of variable references of the prompt. - */ - public get variableReferences(): readonly TVariableReference[] { - return [...this._variableReferences]; - } - - /** - * Valid metadata records defined in the prompt header. - */ - public get metadata(): TMetadata | null { - const { promptType } = this.promptContentsProvider; - if (promptType === 'non-prompt') { - return null; - } - - if (this.header === undefined) { - return { promptType }; - } - - if (this.header instanceof InstructionsHeader || this.header instanceof ModeHeader) { - return { promptType, ...this.header.metadata }; - } - - const { tools, mode, description, model } = this.header.metadata; - - const result: Partial = {}; - - if (description !== undefined) { - result.description = description; - } - - if (tools !== undefined && mode !== ChatModeKind.Ask && mode !== ChatModeKind.Edit) { - result.tools = tools; - // Preserve custom mode if specified, otherwise default to Agent - result.mode = mode || ChatModeKind.Agent; - } else if (mode !== undefined) { - result.mode = mode; - } - - if (model !== undefined) { - result.model = model; - } - - return { promptType, ...result }; - } - - /** - * The top most error of the current reference or any of its - * possible child reference errors. - */ - public get topError(): ITopError | undefined { - if (this.errorCondition) { - return new TopError({ - errorSubject: 'root', - errorsCount: 1, - originalError: this.errorCondition, - }); - } - - return undefined; - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `prompt:${this.uri.path}`; - } - - /** - * @inheritdoc - */ - public override dispose(): void { - if (this.isDisposed) { - return; - } - - this.disposeReferences(); - - this.stream?.dispose(); - delete this.stream; - - this.promptHeader?.dispose(); - delete this.promptHeader; - - super.dispose(); - } -} - -/** - * Prompt reference object represents any reference inside prompt text - * contents. For instance the file variable(`#file:/path/to/file.md`) or - * a markdown link(`[#file:file.md](/path/to/file.md)`). - */ -export class PromptReference implements TPromptReference { - - - constructor( - public readonly uri: URI, - public readonly token: FileReference | MarkdownLink, - ) { - } - - /** - * Get the range of the `link` part of the reference. - */ - public get linkRange(): IRange | undefined { - // `#file:` references - if (this.token instanceof FileReference) { - return this.token.dataRange; - } - - // `markdown link` references - if (this.token instanceof MarkdownLink) { - return this.token.linkRange; - } - - return undefined; - } - - /** - * Type of the reference, - either a prompt `#file` variable, - * or a `markdown link` reference (`[caption](/path/to/file.md)`). - */ - public get type(): 'file' { - if (this.token instanceof FileReference) { - return 'file'; - } - - if (this.token instanceof MarkdownLink) { - return 'file'; - } - - assertNever( - this.token, - `Unknown token type '${this.token}'.`, - ); - } - - /** - * Subtype of the reference, - either a prompt `#file` variable, - * or a `markdown link` reference (`[caption](/path/to/file.md)`). - */ - public get subtype(): 'prompt' | 'markdown' { - if (this.token instanceof FileReference) { - return 'prompt'; - } - - if (this.token instanceof MarkdownLink) { - return 'markdown'; - } - - assertNever( - this.token, - `Unknown token type '${this.token}'.`, - ); - } - - public get range(): Range { - return this.token.range; - } - - public get path(): string { - return this.token.path; - } - - public get text(): string { - return this.token.text; - } - - /** - * Returns a string representation of this object. - */ - public toString(): string { - return `prompt-reference/${this.type}:${this.subtype}/${this.token}`; - } -} - -/** - * A tiny utility object that helps us to track existence - * of at least one parse result from the content provider. - */ -class FirstParseResult extends DeferredPromise { - /** - * Private attribute to track if we have - * received at least one result. - */ - private _gotResult = false; - - /** - * Whether we've received at least one result. - */ - public get gotFirstResult(): boolean { - return this._gotResult; - } - - /** - * Get underlying promise reference. - */ - public get promise(): Promise { - return this.p; - } - - /** - * Complete the underlying promise. - */ - public end(): void { - this._gotResult = true; - super.complete(void 0) - .catch(() => { - // the complete method is never fails - // so we can ignore the error here - }); - - return; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/filePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/filePromptParser.ts deleted file mode 100644 index c73efe10884..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/filePromptParser.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { BasePromptParser, IPromptParserOptions } from './basePromptParser.js'; -import { FilePromptContentProvider } from '../contentProviders/filePromptContentsProvider.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; - -/** - * Class capable of parsing prompt syntax out of a provided file, - * including all the nested child file references it may have. - */ -export class FilePromptParser extends BasePromptParser { - constructor( - uri: URI, - options: IPromptParserOptions, - @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService, - ) { - const contentsProvider = instantiationService.createInstance(FilePromptContentProvider, uri, options); - super(contentsProvider, options, instantiationService, envService, logService); - - this._register(contentsProvider); - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `file-prompt:${this.uri.path}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/diagnostics.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/diagnostics.ts deleted file mode 100644 index 754293f6dcf..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/diagnostics.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from '../../../../../../../editor/common/core/range.js'; - -/** - * List of all currently supported diagnostic types. - */ -export type TDiagnostic = PromptMetadataWarning | PromptMetadataError; - -/** - * Diagnostics object that hold information about some issue - * related to the prompt header metadata. - */ -export abstract class PromptMetadataDiagnostic { - constructor( - public readonly range: Range, - public readonly message: string, - ) { } - - /** - * String representation of the diagnostic object. - */ - public abstract toString(): string; -} - -/** - * Diagnostics object that hold information about some - * non-fatal issue related to the prompt header metadata. - */ -export class PromptMetadataWarning extends PromptMetadataDiagnostic { - public override toString(): string { - return `warning(${this.message})${this.range}`; - } -} - -/** - * Diagnostics object that hold information about some - * fatal issue related to the prompt header metadata. - */ -export class PromptMetadataError extends PromptMetadataDiagnostic { - public override toString(): string { - return `error(${this.message})${this.range}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts deleted file mode 100644 index eedebeabeed..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/headerBase.ts +++ /dev/null @@ -1,264 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type TModeMetadata } from './modeHeader.js'; -import { localize } from '../../../../../../../nls.js'; -import { type TPromptMetadata } from './promptHeader.js'; -import { type IMetadataRecord } from './metadata/base/record.js'; -import { type TInstructionsMetadata } from './instructionsHeader.js'; -import { Range } from '../../../../../../../editor/common/core/range.js'; -import { Disposable } from '../../../../../../../base/common/lifecycle.js'; -import { ObjectStream } from '../../codecs/base/utils/objectStream.js'; -import { PromptMetadataError, PromptMetadataWarning, type TDiagnostic } from './diagnostics.js'; -import { SimpleToken } from '../../codecs/base/simpleCodec/tokens/tokens.js'; -import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; -import { FrontMatterHeader } from '../../codecs/base/markdownExtensionsCodec/tokens/frontMatterHeader.js'; -import { FrontMatterDecoder, type TFrontMatterToken } from '../../codecs/base/frontMatterCodec/frontMatterDecoder.js'; -import { PromptDescriptionMetadata } from './metadata/description.js'; - -/** - * A metadata utility class "dehydrated" into a plain data object with - * semi-primitive record values (string, boolean, string[], boolean[], etc.). - */ -export type TDehydrated = { - [K in keyof T]: T[K] extends IMetadataRecord ? (U extends undefined ? undefined : NonNullable) : undefined; -}; - -/** - * Metadata defined in the prompt header. - */ -export interface IHeaderMetadata { - /** - * Description metadata in the prompt header. - */ - description: PromptDescriptionMetadata; -} - -/** - * Metadata for prompt/instruction/mode files. - */ -export type THeaderMetadata = Partial>; - -/** - * Metadata defined in the header of prompt/instruction/mode files. - */ -export type TMetadata = TPromptMetadata | TModeMetadata | TInstructionsMetadata; - -/** - * Base class for prompt/instruction/mode headers. - */ -export abstract class HeaderBase< - TMetadata extends IHeaderMetadata, -> extends Disposable { - /** - * Underlying decoder for a Front Matter header. - */ - private readonly stream: FrontMatterDecoder; - - /** - * Metadata records. - */ - protected readonly meta: Partial; - - /** - * Data object with all header's metadata records. - */ - public get metadata(): Partial> { - const result: Partial> = {}; - - for (const [entryName, entryValue] of Object.entries(this.meta)) { - if (entryValue?.value === undefined) { - continue; - } - - // note! we have to resort to `Object.assign()` here because - // the `Object.entries()` call looses type information - Object.assign(result, { - [entryName]: entryValue.value, - }); - } - - return result; - } - - /** - * A copy of metadata object with utility classes as values - * for each of prompt header's record. - * - * Please use {@link metadata} instead if all you need to read is - * the plain "data" object representation of valid metadata records. - */ - public get metadataUtility(): Partial { - return { ...this.meta }; - } - - /** - * List of all unique metadata record names. - */ - private readonly recordNames: Set; - - /** - * List of all issues found while parsing the prompt header. - */ - protected readonly issues: TDiagnostic[]; - - /** - * List of all diagnostic issues found while parsing - * the prompt header. - */ - public get diagnostics(): readonly TDiagnostic[] { - return this.issues; - } - - /** - * Full range of the header in the original document. - */ - public get range(): Range { - return this.token.range; - } - - constructor( - public readonly token: FrontMatterHeader, - public readonly languageId: string, - ) { - super(); - - this.issues = []; - this.meta = {}; - this.recordNames = new Set(); - - this.stream = this._register( - new FrontMatterDecoder( - ObjectStream.fromArray([...token.contentToken.children]), - ), - ); - this.stream.onData(this.onData.bind(this)); - this.stream.onError(this.onError.bind(this)); - } - - /** - * Process a front matter record token, which includes: - * - validation of the record and whether it is compatible with other header records - * - adding validation-related diagnostic messages to the {@link issues} list - * - setting associated utility class for the record on the {@link meta} object - * - * @returns a boolean flag that indicates whether the token was handled and therefore - * should not be processed any further. - */ - protected abstract handleToken( - token: FrontMatterRecord, - ): boolean; - - /** - * Process front matter tokens, converting them into - * well-known prompt metadata records. - */ - private onData(token: TFrontMatterToken): void { - // we currently expect only front matter 'records' for - // the prompt metadata, hence add diagnostics for all - // other tokens and ignore them - if ((token instanceof FrontMatterRecord) === false) { - // unless its a simple token, in which case we just ignore it - if (token instanceof SimpleToken) { - return; - } - - this.issues.push( - new PromptMetadataError( - token.range, - localize( - 'prompt.header.diagnostics.unexpected-token', - "Unexpected token '{0}'.", - token.text, - ), - ), - ); - - return; - } - - const recordName = token.nameToken.text; - - // if we already have a record with this name, - // add a warning diagnostic and ignore it - if (this.recordNames.has(recordName)) { - this.issues.push( - new PromptMetadataWarning( - token.range, - localize( - 'prompt.header.metadata.diagnostics.duplicate-record', - "Duplicate property '{0}' will be ignored.", - recordName, - ), - ), - ); - - return; - } - this.recordNames.add(recordName); - - // if the record might be a "description" metadata - // add it to the list of parsed metadata records - if (PromptDescriptionMetadata.isDescriptionRecord(token)) { - const metadata = new PromptDescriptionMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.description = metadata; - this.recordNames.add(recordName); - return; - } - - // pipe the token to the actual implementation class - // that might to handle it based on the token type - if (this.handleToken(token)) { - return; - } - - // all other records are "unknown" ones - this.issues.push( - new PromptMetadataWarning( - token.range, - localize( - 'prompt.header.metadata.diagnostics.unknown-record', - "Unknown property '{0}' will be ignored.", - recordName, - ), - ), - ); - } - - /** - * Process errors from the underlying front matter decoder. - */ - private onError(error: Error): void { - this.issues.push( - new PromptMetadataError( - this.token.range, - localize( - 'prompt.header.diagnostics.parsing-error', - "Failed to parse prompt header: {0}", - error.message, - ), - ), - ); - } - - /** - * Promise that resolves when parsing process of - * the prompt header completes. - */ - public get settled(): Promise { - return this.stream.settled; - } - - /** - * Starts the parsing process of the prompt header. - */ - public start(): this { - this.stream.start(); - - return this; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts deleted file mode 100644 index 1f13901f960..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/instructionsHeader.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptApplyToMetadata } from './metadata/applyTo.js'; -import { HeaderBase, IHeaderMetadata, type TDehydrated } from './headerBase.js'; -import { PromptsType } from '../../promptTypes.js'; -import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Metadata utility object for instruction files. - */ -interface IInstructionsMetadata extends IHeaderMetadata { - /** - * Chat 'applyTo' metadata in the prompt header. - */ - applyTo: PromptApplyToMetadata; -} - -/** - * Metadata for instruction files. - */ -export type TInstructionsMetadata = Partial> & { promptType: PromptsType.instructions }; - -/** - * Header object for instruction files. - */ -export class InstructionsHeader extends HeaderBase { - protected override handleToken(token: FrontMatterRecord): boolean { - // if the record might be a "applyTo" metadata - // add it to the list of parsed metadata records - if (PromptApplyToMetadata.isApplyToRecord(token)) { - const metadata = new PromptApplyToMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.applyTo = metadata; - - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts deleted file mode 100644 index 7f374c0f108..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/applyTo.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptStringMetadata } from './base/string.js'; -import { localize } from '../../../../../../../../nls.js'; -import { INSTRUCTIONS_LANGUAGE_ID } from '../../../promptTypes.js'; -import { isEmptyPattern, parse, splitGlobAware } from '../../../../../../../../base/common/glob.js'; -import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../diagnostics.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Name of the metadata record in the prompt header. - */ -const RECORD_NAME = 'applyTo'; - -/** - * Prompt `applyTo` metadata record inside the prompt header. - */ -export class PromptApplyToMetadata extends PromptStringMetadata { - constructor( - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(RECORD_NAME, recordToken, languageId); - } - - public override get recordName(): string { - return RECORD_NAME; - } - - public override validate(): readonly PromptMetadataDiagnostic[] { - super.validate(); - - // if we don't have a value token, validation must - // has failed already so nothing to do more - if (this.valueToken === undefined) { - return this.issues; - } - - // the applyTo metadata makes sense only for 'instruction' prompts - if (this.languageId !== INSTRUCTIONS_LANGUAGE_ID) { - this.issues.push( - new PromptMetadataError( - this.range, - localize( - 'prompt.header.metadata.string.diagnostics.invalid-language', - "The '{0}' header property is only valid in instruction files.", - this.recordName, - ), - ), - ); - - delete this.valueToken; - return this.issues; - } - - const { cleanText } = this.valueToken; - - // warn user if specified glob pattern is not valid - if (this.isValidGlob(cleanText) === false) { - this.issues.push( - new PromptMetadataWarning( - this.valueToken.range, - localize( - 'prompt.header.metadata.applyTo.diagnostics.non-valid-glob', - "Invalid glob pattern '{0}'.", - cleanText, - ), - ), - ); - - delete this.valueToken; - return this.issues; - } - - return this.issues; - } - - /** - * Check if a provided string contains a valid glob pattern. - */ - private isValidGlob( - pattern: string, - ): boolean { - try { - const patterns = splitGlobAware(pattern, ','); - if (patterns.length === 0) { - return false; - } - for (const pattern of patterns) { - - const globPattern = parse(pattern); - if (isEmptyPattern(globPattern)) { - return false; - } - } - return true; - } catch (_error) { - return false; - } - } - - /** - * Check if a provided front matter token is a metadata record - * with name equal to `applyTo`. - */ - public static isApplyToRecord( - token: FrontMatterToken, - ): boolean { - if ((token instanceof FrontMatterRecord) === false) { - return false; - } - - if (token.nameToken.text === RECORD_NAME) { - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts deleted file mode 100644 index 71b7d9e7a4f..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/enum.ts +++ /dev/null @@ -1,84 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptStringMetadata } from './string.js'; -import { localize } from '../../../../../../../../../nls.js'; -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { isOneOf } from '../../../../../../../../../base/common/types.js'; -import { PromptMetadataDiagnostic, PromptMetadataError } from '../../diagnostics.js'; -import { FrontMatterSequence } from '../../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterRecord, FrontMatterString } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Enum type is the special case of the {@link PromptStringMetadata string} - * type that can take only a well-defined set of {@link validValues}. - */ -export abstract class PromptEnumMetadata< - TValidValues extends string = string, -> extends PromptStringMetadata { - constructor( - private readonly validValues: readonly TValidValues[], - expectedRecordName: string, - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(expectedRecordName, recordToken, languageId); - } - - /** - * Valid enum value or 'undefined'. - */ - private enumValue: TValidValues | undefined; - /** - * Valid enum value or 'undefined'. - */ - public override get value(): TValidValues | undefined { - return this.enumValue; - } - - /** - * Validate the metadata record has an allowed value. - */ - public override validate(): readonly PromptMetadataDiagnostic[] { - super.validate(); - - if (this.valueToken === undefined) { - return this.issues; - } - - // sanity check for our expectations about the validate call - assert( - this.valueToken instanceof FrontMatterString - || this.valueToken instanceof FrontMatterSequence, - `Record token must be 'string', got '${this.valueToken}'.`, - ); - - const { cleanText } = this.valueToken; - if (isOneOf(cleanText, this.validValues)) { - this.enumValue = cleanText; - - return this.issues; - } - - this.issues.push( - new PromptMetadataError( - this.valueToken.range, - localize( - 'prompt.header.metadata.enum.diagnostics.invalid-value', - "The property '{0}' must be one of {1}, got '{2}'.", - this.recordName, - this.validValues - .map((value) => { - return `'${value}'`; - }).join(' | '), - cleanText, - ), - ), - ); - - delete this.valueToken; - return this.issues; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts deleted file mode 100644 index aa7a4660f66..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/record.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assert } from '../../../../../../../../../base/common/assert.js'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../../diagnostics.js'; -import { FrontMatterRecord } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Supported primitive types for metadata values in a prompt header. - */ -type TMetadataPrimitive = string | boolean; - -/** - * Supported metadata values in a prompt header. - */ -type TMetadataValue = TMetadataPrimitive | TMetadataPrimitive[]; - -/** - * Interface for a generic metadata record in the prompt header. - */ -export interface IMetadataRecord { - /** - * Value of a metadata record. If the value is not defined, it usually - * means that a record is present but its value is not set or valid. - */ - readonly value: T | undefined; -} - -/** - * Abstract class for all metadata records in the prompt header. - */ -export abstract class PromptMetadataRecord implements IMetadataRecord { - /** - * Private field for tracking all diagnostic issues - * related to this metadata record. - */ - protected readonly issues: PromptMetadataDiagnostic[]; - - /** - * Full range of the metadata's record text in the prompt header. - */ - public get range(): Range { - return this.recordToken.range; - } - - constructor( - protected readonly expectedRecordName: string, - protected readonly recordToken: FrontMatterRecord, - protected readonly languageId: string, - ) { - // validate that the record name has the expected name - const recordName = recordToken.nameToken.text; - assert( - recordName === expectedRecordName, - `Record name must be '${expectedRecordName}', got '${recordName}'.`, - ); - - this.issues = []; - } - - /** - * Name of the metadata record. - */ - public get recordName(): string { - return this.recordToken.nameToken.text; - } - - /** - * Validate the metadata record and collect all issues - * related to its content. - */ - public abstract validate(): readonly PromptMetadataDiagnostic[]; - - /** - * List of all diagnostic issues related to this metadata record. - */ - public get diagnostics(): readonly PromptMetadataDiagnostic[] { - return this.issues; - } - - /** - * Get the value of the metadata record. - */ - public abstract get value(): TValue | undefined; - - /** - * List of all `error` issue diagnostics. - */ - public get errorDiagnostics(): readonly PromptMetadataError[] { - return this.diagnostics - .filter((diagnostic) => { - return (diagnostic instanceof PromptMetadataError); - }); - } - - /** - * List of all `warning` issue diagnostics. - */ - public get warningDiagnostics(): readonly PromptMetadataWarning[] { - return this.diagnostics - .filter((diagnostic) => { - return (diagnostic instanceof PromptMetadataWarning); - }); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts deleted file mode 100644 index 87423ab25b2..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/base/string.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptMetadataRecord } from './record.js'; -import { localize } from '../../../../../../../../../nls.js'; -import { PromptMetadataDiagnostic, PromptMetadataError } from '../../diagnostics.js'; -import { FrontMatterSequence } from '../../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterRecord, FrontMatterString } from '../../../../codecs/base/frontMatterCodec/tokens/index.js'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; - - -/** - * Base class for all metadata records with a `string` value. - */ -export abstract class PromptStringMetadata extends PromptMetadataRecord { - /** - * Value token reference of the record. - */ - protected valueToken: FrontMatterString | FrontMatterSequence | undefined; - - /** - * String value of a metadata record. - */ - public override get value(): string | undefined { - return this.valueToken?.cleanText; - } - - public get valueRange(): Range | undefined { - return this.valueToken?.range; - } - - constructor( - expectedRecordName: string, - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(expectedRecordName, recordToken, languageId); - } - - /** - * Validate the metadata record has a 'string' value. - */ - public override validate(): readonly PromptMetadataDiagnostic[] { - const { valueToken } = this.recordToken; - - // validate that the record value is a string or a generic sequence - // of tokens that can be interpreted as a string without quotes - const isString = (valueToken instanceof FrontMatterString); - const isSequence = (valueToken instanceof FrontMatterSequence); - if (isString || isSequence) { - this.valueToken = valueToken; - return this.issues; - } - - this.issues.push( - new PromptMetadataError( - valueToken.range, - localize( - 'prompt.header.metadata.string.diagnostics.invalid-value-type', - "The property '{0}' must be of type '{1}', got '{2}'.", - this.recordName, - 'string', - valueToken.valueTypeName.toString(), - ), - ), - ); - - delete this.valueToken; - return this.issues; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts deleted file mode 100644 index aaa8fa3a640..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/description.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptStringMetadata } from './base/string.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Name of the metadata record in the prompt header. - */ -const RECORD_NAME = 'description'; - -/** - * Prompt `description` metadata record inside the prompt header. - */ -export class PromptDescriptionMetadata extends PromptStringMetadata { - public override get recordName(): string { - return RECORD_NAME; - } - - constructor( - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(RECORD_NAME, recordToken, languageId); - } - - /** - * Check if a provided front matter token is a metadata record - * with name equal to `description`. - */ - public static isDescriptionRecord( - token: FrontMatterToken, - ): boolean { - if ((token instanceof FrontMatterRecord) === false) { - return false; - } - - if (token.nameToken.text === RECORD_NAME) { - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts deleted file mode 100644 index b12728f7e75..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/mode.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptStringMetadata } from './base/string.js'; -import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Name of the metadata record in the prompt header. - */ -const RECORD_NAME = 'mode'; - -/** - * Prompt `mode` metadata record inside the prompt header. - * Now supports both built-in modes (ask, edit, agent) and custom mode IDs. - */ -export class PromptModeMetadata extends PromptStringMetadata { - constructor( - recordToken: FrontMatterRecord, - languageId: string, - ) { - super( - RECORD_NAME, - recordToken, - languageId, - ); - } - - /** - * Check if a provided front matter token is a metadata record - * with name equal to `mode`. - */ - public static isModeRecord( - token: FrontMatterToken, - ): boolean { - if ((token instanceof FrontMatterRecord) === false) { - return false; - } - - if (token.nameToken.text === RECORD_NAME) { - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/model.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/model.ts deleted file mode 100644 index aa559e57fc0..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/model.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { FrontMatterRecord, FrontMatterToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; -import { PromptStringMetadata } from './base/string.js'; - -/** - * Name of the metadata record in the prompt header. - */ -const RECORD_NAME = 'model'; - -export class PromptModelMetadata extends PromptStringMetadata { - public override get recordName(): string { - return RECORD_NAME; - } - - constructor( - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(RECORD_NAME, recordToken, languageId); - } - - /** - * Check if a provided front matter token is a metadata record - * with name equal to `description`. - */ - public static isModelRecord(token: FrontMatterToken): boolean { - if ((token instanceof FrontMatterRecord) === false) { - return false; - } - - if (token.nameToken.text === RECORD_NAME) { - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts deleted file mode 100644 index ad52e5faebe..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/metadata/tools.ts +++ /dev/null @@ -1,182 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromptMetadataRecord } from './base/record.js'; -import { localize } from '../../../../../../../../nls.js'; -import { PromptMetadataDiagnostic, PromptMetadataError, PromptMetadataWarning } from '../diagnostics.js'; -import { FrontMatterSequence } from '../../../codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; -import { FrontMatterArray, FrontMatterRecord, FrontMatterString, FrontMatterToken, FrontMatterValueToken } from '../../../codecs/base/frontMatterCodec/tokens/index.js'; -import { Range } from '../../../../../../../../editor/common/core/range.js'; - -/** - * Name of the metadata record in the prompt header. - */ -const RECORD_NAME = 'tools'; - -/** - * Prompt `tools` metadata record inside the prompt header. - */ -export class PromptToolsMetadata extends PromptMetadataRecord { - - /** - * List of all valid tool names that were found in - * this metadata record. - */ - public override get value(): string[] | undefined { - if (this.validToolNames === undefined) { - return []; - } - - return [...this.validToolNames.keys()]; - } - - public override get recordName(): string { - return RECORD_NAME; - } - - /** - * Value token reference of the record. - */ - protected valueToken: FrontMatterArray | undefined; - - /** - * List of all valid tool names that were found in - * this metadata record. - */ - private validToolNames: Map | undefined; - - - - constructor( - recordToken: FrontMatterRecord, - languageId: string, - ) { - super(RECORD_NAME, recordToken, languageId); - } - - /** - * Validate the metadata record and collect all issues - * related to its content. - */ - public override validate(): readonly PromptMetadataDiagnostic[] { - const { valueToken } = this.recordToken; - - // validate that the record value is an array - if ((valueToken instanceof FrontMatterArray) === false) { - this.issues.push( - new PromptMetadataError( - valueToken.range, - localize( - 'prompt.header.metadata.tools.diagnostics.invalid-value-type', - "Must be an array of tool names, got '{0}'.", - valueToken.valueTypeName.toString(), - ), - ), - ); - - delete this.valueToken; - return this.issues; - } - - this.valueToken = valueToken; - - // validate that all array items - this.validToolNames = new Map(); - for (const item of this.valueToken.items) { - this.issues.push( - ...this.validateToolName(item, this.validToolNames), - ); - } - - return this.issues; - } - - public getToolRange(toolName: string): Range | undefined { - return this.validToolNames?.get(toolName); - } - - /** - * Validate an individual provided value token that is used - * for a tool name. - */ - private validateToolName( - valueToken: FrontMatterValueToken, - validToolNames: Map, - ): readonly PromptMetadataDiagnostic[] { - const issues: PromptMetadataDiagnostic[] = []; - - // tool name must be a quoted or an unquoted 'string' - if ( - (valueToken instanceof FrontMatterString) === false && - (valueToken instanceof FrontMatterSequence) === false - ) { - issues.push( - new PromptMetadataWarning( - valueToken.range, - localize( - 'prompt.header.metadata.tools.diagnostics.invalid-tool-name-type', - "Unexpected tool name '{0}', expected a string literal.", - valueToken.text - ), - ), - ); - - return issues; - } - - const cleanToolName = valueToken.cleanText.trim(); - // the tool name should not be empty - if (cleanToolName.length === 0) { - issues.push( - new PromptMetadataWarning( - valueToken.range, - localize( - 'prompt.header.metadata.tools.diagnostics.empty-tool-name', - "Tool name cannot be empty.", - ), - ), - ); - - return issues; - } - - // the tool name should not be duplicated - if (validToolNames.has(cleanToolName)) { - issues.push( - new PromptMetadataWarning( - valueToken.range, - localize( - 'prompt.header.metadata.tools.diagnostics.duplicate-tool-name', - "Duplicate tool name '{0}'.", - cleanToolName, - ), - ), - ); - - return issues; - } - - validToolNames.set(cleanToolName, valueToken.range); - return issues; - } - - /** - * Check if a provided front matter token is a metadata record - * with name equal to `tools`. - */ - public static isToolsRecord( - token: FrontMatterToken, - ): boolean { - if ((token instanceof FrontMatterRecord) === false) { - return false; - } - - if (token.nameToken.text === RECORD_NAME) { - return true; - } - - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts deleted file mode 100644 index 94307163bf6..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/modeHeader.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { HeaderBase, IHeaderMetadata, type TDehydrated } from './headerBase.js'; -import { PromptsType } from '../../promptTypes.js'; -import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; -import { PromptModelMetadata } from './metadata/model.js'; -import { PromptToolsMetadata } from './metadata/tools.js'; - -/** - * Metadata utility object for mode files. - */ -interface IModeMetadata extends IHeaderMetadata { - /** - * Tools metadata in the mode header. - */ - tools: PromptToolsMetadata; - - /** - * Chat model metadata in the mode header. - */ - model: PromptModelMetadata; -} - -/** - * Metadata for mode files. - */ -export type TModeMetadata = Partial> & { promptType: PromptsType.mode }; - -/** - * Header object for mode files. - */ -export class ModeHeader extends HeaderBase { - protected override handleToken(token: FrontMatterRecord): boolean { - // if the record might be a "tools" metadata - // add it to the list of parsed metadata records - if (PromptToolsMetadata.isToolsRecord(token)) { - const metadata = new PromptToolsMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.tools = metadata; - return true; - } - if (PromptModelMetadata.isModelRecord(token)) { - const metadata = new PromptModelMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.model = metadata; - - return true; - } - return false; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts deleted file mode 100644 index 250e8382372..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptHeader/promptHeader.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ChatModeKind } from '../../../constants.js'; -import { localize } from '../../../../../../../nls.js'; -import { PromptMetadataWarning } from './diagnostics.js'; -import { HeaderBase, IHeaderMetadata, type TDehydrated } from './headerBase.js'; -import { PromptsType } from '../../promptTypes.js'; -import { FrontMatterRecord } from '../../codecs/base/frontMatterCodec/tokens/index.js'; -import { PromptModelMetadata } from './metadata/model.js'; -import { PromptToolsMetadata } from './metadata/tools.js'; -import { PromptModeMetadata } from './metadata/mode.js'; - -/** - * Metadata utility object for prompt files. - */ -export interface IPromptMetadata extends IHeaderMetadata { - /** - * Tools metadata in the prompt header. - */ - tools: PromptToolsMetadata; - - /** - * Chat mode metadata in the prompt header. - */ - mode: PromptModeMetadata; - - /** - * Chat model metadata in the prompt header. - */ - model: PromptModelMetadata; -} - -/** - * Metadata for prompt files. - */ -export type TPromptMetadata = Partial> & { promptType: PromptsType.prompt }; - -/** - * Header object for prompt files. - */ -export class PromptHeader extends HeaderBase { - protected override handleToken(token: FrontMatterRecord): boolean { - // if the record might be a "tools" metadata - // add it to the list of parsed metadata records - if (PromptToolsMetadata.isToolsRecord(token)) { - const metadata = new PromptToolsMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.tools = metadata; - - this.validateToolsAndModeCompatibility(); - return true; - } - - // if the record might be a "mode" metadata - // add it to the list of parsed metadata records - if (PromptModeMetadata.isModeRecord(token)) { - const metadata = new PromptModeMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.mode = metadata; - - this.validateToolsAndModeCompatibility(); - return true; - } - - if (PromptModelMetadata.isModelRecord(token)) { - const metadata = new PromptModelMetadata(token, this.languageId); - - this.issues.push(...metadata.validate()); - this.meta.model = metadata; - - return true; - } - - return false; - } - - /** - * Validate that the `tools` and `mode` metadata are compatible - * with each other. If not, add a warning diagnostic. - */ - private validateToolsAndModeCompatibility(): void { - const { tools, mode } = this.meta; - const modeValue = mode?.value; - - if (tools !== undefined && (modeValue === ChatModeKind.Edit || modeValue === ChatModeKind.Ask)) { - this.issues.push( - new PromptMetadataWarning( - tools.range, - localize( - 'prompt.header.metadata.mode.diagnostics.incompatible-with-tools', - "Tools can not be used in '{0}' mode and will be ignored.", - modeValue - ), - ), - ); - } - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts deleted file mode 100644 index 99df26fd5e9..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/promptParser.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { IPromptContentsProvider } from '../contentProviders/types.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { BasePromptParser, IPromptParserOptions } from './basePromptParser.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { TextModelContentsProvider } from '../contentProviders/textModelContentsProvider.js'; -import { FilePromptContentProvider } from '../contentProviders/filePromptContentsProvider.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IPromptContentsProviderOptions } from '../contentProviders/promptContentsProviderBase.js'; -import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; - -/** - * Get prompt contents provider object based on the prompt type. - */ -function getContentsProvider( - uri: URI, - options: IPromptContentsProviderOptions, - modelService: IModelService, - instaService: IInstantiationService -): IPromptContentsProvider { - const model = modelService.getModel(uri); - if (model) { - return instaService.createInstance(TextModelContentsProvider, model, options); - } - return instaService.createInstance(FilePromptContentProvider, uri, options); -} - -/** - * General prompt parser class that automatically infers a prompt - * contents provider type by the type of provided prompt URI. - */ -export class PromptParser extends BasePromptParser { - /** - * Underlying prompt contents provider instance. - */ - private readonly contentsProvider: IPromptContentsProvider; - - constructor( - uri: URI, - options: IPromptParserOptions, - @ILogService logService: ILogService, - @IModelService modelService: IModelService, - @IInstantiationService instaService: IInstantiationService, - @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, - ) { - const contentsProvider = getContentsProvider(uri, options, modelService, instaService); - - super( - contentsProvider, - options, - instaService, - envService, - logService, - ); - - this.contentsProvider = this._register(contentsProvider); - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - const { sourceName } = this.contentsProvider; - - return `prompt-parser:${sourceName}:${this.uri.path}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts deleted file mode 100644 index bb2b7060abb..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ITextModel } from '../../../../../../editor/common/model.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { BasePromptParser, IPromptParserOptions } from './basePromptParser.js'; -import { TextModelContentsProvider } from '../contentProviders/textModelContentsProvider.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; - -/** - * Class capable of parsing prompt syntax out of a provided text model, - * including all the nested child file references it may have. - */ -export class TextModelPromptParser extends BasePromptParser { - constructor( - model: ITextModel, - options: IPromptParserOptions, - @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService envService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService, - ) { - const contentsProvider = instantiationService.createInstance( - TextModelContentsProvider, - model, - options, - ); - - super(contentsProvider, options, instantiationService, envService, logService); - - this._register(contentsProvider); - } - - /** - * Returns a string representation of this object. - */ - public override toString(): string { - return `text-model-prompt:${this.uri.path}`; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/topError.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/topError.ts deleted file mode 100644 index b6281d3f42b..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/topError.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ITopError } from './types.js'; -import { localize } from '../../../../../../nls.js'; -import { assert } from '../../../../../../base/common/assert.js'; -import { assertDefined } from '../../../../../../base/common/types.js'; -import { OpenFailed, RecursiveReference, FailedToResolveContentsStream } from '../../promptFileReferenceErrors.js'; - -/** - * The top-most error of the reference tree. - */ -export class TopError implements ITopError { - public readonly originalError: ITopError['originalError']; - public readonly errorSubject: ITopError['errorSubject']; - public readonly errorsCount: ITopError['errorsCount']; - public readonly parentUri: ITopError['parentUri']; - - constructor( - options: Omit, - ) { - this.originalError = options.originalError; - this.errorSubject = options.errorSubject; - this.errorsCount = options.errorsCount; - this.parentUri = options.parentUri; - } - - public get localizedMessage(): string { - const { originalError, parentUri, errorSubject: subject, errorsCount } = this; - - assert( - errorsCount >= 1, - `Error count must be at least 1, got '${errorsCount}'.`, - ); - - // a note about how many more link issues are there - const moreIssuesLabel = (errorsCount > 1) - ? localize('workbench.reusable-prompts.top-error.more-issues-label', "\n(+{0} more issues)", errorsCount - 1) - : ''; - - if (subject === 'root') { - if (originalError instanceof OpenFailed) { - return localize( - 'workbench.reusable-prompts.top-error.open-failed', - "Cannot open '{0}'.{1}", - originalError.uri.path, - moreIssuesLabel, - ); - } - - if (originalError instanceof FailedToResolveContentsStream) { - return localize( - 'workbench.reusable-prompts.top-error.cannot-read', - "Cannot read '{0}'.{1}", - originalError.uri.path, - moreIssuesLabel, - ); - } - - if (originalError instanceof RecursiveReference) { - return localize( - 'workbench.reusable-prompts.top-error.recursive-reference', - "Recursion to itself.", - ); - } - - return originalError.message + moreIssuesLabel; - } - - // a sanity check - because the error subject is not `root`, the parent must set - assertDefined( - parentUri, - 'Parent URI must be defined for error of non-root link.', - ); - - const errorMessageStart = (subject === 'child') - ? localize( - 'workbench.reusable-prompts.top-error.child.direct', - "Contains", - ) - : localize( - 'workbench.reusable-prompts.top-error.child.indirect', - "Indirectly referenced prompt '{0}' contains", - parentUri.path, - ); - - const linkIssueName = (originalError instanceof RecursiveReference) - ? localize('recursive', "recursive") - : localize('broken', "broken"); - - return localize( - 'workbench.reusable-prompts.top-error.child.final-message', - "{0} a {1} link to '{2}' that will be ignored.{3}", - errorMessageStart, - linkIssueName, - originalError.uri.path, - moreIssuesLabel, - ); - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts deleted file mode 100644 index 0a3d693c61d..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../../base/common/uri.js'; -import { ResolveError } from '../../promptFileReferenceErrors.js'; -import { IRange, Range } from '../../../../../../editor/common/core/range.js'; - -/** - * A resolve error with a parent prompt URI, if any. - */ -export interface IResolveError { - /** - * Original error instance. - */ - readonly originalError: ResolveError; - - /** - * URI of the parent that references this error. - */ - readonly parentUri?: URI; -} - -/** - * Top most error of the reference tree. - */ -export interface ITopError extends IResolveError { - /** - * Where does the error belong to: - * - * - `root` - the error is the top most error of the entire tree - * - `child` - the error is a child of the root error - * - `indirect-child` - the error is a child of a child of the root error - */ - readonly errorSubject: 'root' | 'child' | 'indirect-child'; - - /** - * Total number of all errors in the references tree, including the error - * of the current reference and all possible errors of its children. - */ - readonly errorsCount: number; - - /** - * Localized error message. - */ - readonly localizedMessage: string; -} - -/** - * Base interface for a generic prompt reference. - */ -interface IPromptReferenceBase { - /** - * Type of the prompt reference. E.g., `file`, `http`, `image`, etc. - */ - readonly type: string; - - /** - * Subtype of the prompt reference. For instance a `file` reference - * can be a `markdown link` or a prompt `#file:` variable reference. - */ - readonly subtype: string; - - /** - * URI component of the associated with this reference. - */ - readonly uri: URI; - - /** - * The full range of the prompt reference in the source text, - * including the {@link linkRange} and any additional - * parts the reference may contain (e.g., the `#file:` prefix). - */ - readonly range: Range; - - /** - * Range of the link part that the reference points to. - */ - readonly linkRange: IRange | undefined; - - /** - * Text of the reference as it appears in the source. - */ - readonly text: string; - - /** - * Original link path as it appears in the source. - */ - readonly path: string; - -} - -/** - * The special case of the {@link IPromptReferenceBase} that pertains - * to a file resource on the disk. - */ -export interface IPromptFileReference extends IPromptReferenceBase { - readonly type: 'file'; - - /** - * Subtype of a file reference, - either a prompt `#file` variable, - * or a `markdown link` (e.g., `[caption](/path/to/file.md)`). - */ - readonly subtype: 'prompt' | 'markdown'; -} - -/** - * List of all known prompt reference types. - */ -export type TPromptReference = IPromptFileReference; - -export type TVariableReference = { - readonly name: string; - readonly range: Range; -}; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index ab3b4887aa4..f1eef78e834 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -6,10 +6,8 @@ import { ChatModeKind } from '../../constants.js'; import { URI } from '../../../../../../base/common/uri.js'; import { Event } from '../../../../../../base/common/event.js'; -import { TMetadata } from '../parsers/promptHeader/headerBase.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -48,32 +46,6 @@ export interface IPromptPath { readonly type: PromptsType; } -/** - * Type for a shared prompt parser instance returned by the {@link IPromptsService}. - * Because the parser is shared, we omit the `dispose` method from - * the original type so the caller cannot dispose it prematurely - */ -export type TSharedPrompt = Omit; - -/** - * Metadata node object in a hierarchical tree of prompt references. - */ -export interface IMetadata { - /** - * URI of a prompt file. - */ - readonly uri: URI; - - /** - * Metadata of the prompt file. - */ - readonly metadata: TMetadata | null; - - /** - * List of metadata for each valid child prompt reference. - */ - readonly children?: readonly IMetadata[]; -} export interface ICustomChatMode { /** @@ -159,12 +131,6 @@ export type TCombinedToolsMetadata = ICombinedAgentToolsMetadata | ICombinedNonA export interface IPromptsService extends IDisposable { readonly _serviceBrand: undefined; - /** - * Get a prompt syntax parser for the provided text model. - * See {@link TextModelPromptParser} for more info on the parser API. - */ - getSyntaxParserFor(model: ITextModel): TSharedPrompt & { isDisposed: false }; - /** * The parsed prompt file for the provided text model. * @param textModel Returns the parsed prompt file. @@ -207,12 +173,6 @@ export interface IPromptsService extends IDisposable { */ getCustomChatModes(token: CancellationToken): Promise; - /** - * Parses the provided URI - * @param uris - */ - parse(uri: URI, type: PromptsType, token: CancellationToken): Promise; - /** * Parses the provided URI * @param uris @@ -232,15 +192,6 @@ export interface IChatPromptSlashCommand { readonly promptPath?: IPromptPath; } - -export interface IPromptParserResult { - readonly uri: URI; - readonly metadata: TMetadata | null; - readonly fileReferences: readonly URI[]; - readonly variableReferences: readonly IVariableReference[]; - readonly header?: IPromptHeader; -} - export interface IPromptHeader { readonly node: YamlNode | undefined; readonly errors: YamlParseError[]; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 07579a4d003..50032a655c1 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -4,29 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../../../nls.js'; -import { getLanguageIdForPromptsType, getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; -import { PromptParser } from '../parsers/promptParser.js'; +import { getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { type URI } from '../../../../../../base/common/uri.js'; -import { assert } from '../../../../../../base/common/assert.js'; import { basename } from '../../../../../../base/common/path.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { Event } from '../../../../../../base/common/event.js'; import { type ITextModel } from '../../../../../../editor/common/model.js'; -import { ObjectCache } from '../utils/objectCache.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; -import type { IChatPromptSlashCommand, ICustomChatMode, IPromptParserResult, IPromptPath, IPromptsService, TPromptsStorage } from './promptsService.js'; +import type { IChatPromptSlashCommand, ICustomChatMode, IPromptPath, IPromptsService, TPromptsStorage } from './promptsService.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { PromptsConfig } from '../config/config.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { PositionOffsetTransformer } from '../../../../../../editor/common/core/text/positionToOffset.js'; import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -40,11 +35,6 @@ import { IVariableReference } from '../../chatModes.js'; export class PromptsService extends Disposable implements IPromptsService { public declare readonly _serviceBrand: undefined; - /** - * Cache of text model content prompt parsers. - */ - private readonly cache: ObjectCache; - /** * Prompt files locator utility. */ @@ -77,36 +67,6 @@ export class PromptsService extends Disposable implements IPromptsService { this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); - // the factory function below creates a new prompt parser object - // for the provided model, if no active non-disposed parser exists - this.cache = this._register( - new ObjectCache((model) => { - assert( - model.isDisposed() === false, - 'Text model must not be disposed.', - ); - - /** - * Note! When/if shared with "file" prompts, the `seenReferences` array below must be taken into account. - * Otherwise consumers will either see incorrect failing or incorrect successful results, based on their - * use case, timing of their calls to the {@link getSyntaxParserFor} function, and state of this service. - */ - const parser: TextModelPromptParser = instantiationService.createInstance( - TextModelPromptParser, - model, - { allowNonPromptFiles: true, languageId: undefined, updateOnChange: true }, - ).start(); - - // this is a sanity check and the contract of the object cache, - // we must return a non-disposed object from this factory function - parser.assertNotDisposed( - 'Created prompt parser must not be disposed.', - ); - - return parser; - }) - ); - this._register(this.modelService.onModelRemoved((model) => { this.parsedPromptFileCache.delete(model.uri); })); @@ -132,21 +92,6 @@ export class PromptsService extends Disposable implements IPromptsService { } - /** - * @throws {Error} if: - * - the provided model is disposed - * - newly created parser is disposed immediately on initialization. - * See factory function in the {@link constructor} for more info. - */ - public getSyntaxParserFor(model: ITextModel): TextModelPromptParser & { isDisposed: false } { - assert( - model.isDisposed() === false, - 'Cannot create a prompt syntax parser for a disposed model.', - ); - - return this.cache.get(model); - } - public getParsedPromptFile(textModel: ITextModel): ParsedPromptFile { const cached = this.parsedPromptFileCache.get(textModel.uri); if (cached && cached[0] === textModel.getVersionId()) { @@ -255,7 +200,7 @@ export class PromptsService extends Disposable implements IPromptsService { private async computeCustomChatModes(token: CancellationToken): Promise { const modeFiles = await this.listPromptFiles(PromptsType.mode, token); - const metadataList = await Promise.all( + const customChatModes = await Promise.all( modeFiles.map(async ({ uri }): Promise => { const ast = await this.parseNew(uri, token); @@ -281,37 +226,7 @@ export class PromptsService extends Disposable implements IPromptsService { }) ); - - return metadataList; - } - - public async parse(uri: URI, type: PromptsType, token: CancellationToken): Promise { - let parser: PromptParser | undefined; - try { - const languageId = getLanguageIdForPromptsType(type); - parser = this.instantiationService.createInstance(PromptParser, uri, { allowNonPromptFiles: true, languageId, updateOnChange: false }).start(token); - const completed = await parser.settled(); - if (!completed) { - throw new Error(localize('promptParser.notCompleted', "Prompt parser for {0} did not complete.", uri.toString())); - } - const fullContent = await parser.getFullContent(); - const transformer = new PositionOffsetTransformer(fullContent); - const variableReferences = parser.variableReferences.map(ref => { - return { - name: ref.name, - range: transformer.getOffsetRange(ref.range) - }; - }).sort((a, b) => b.range.start - a.range.start); // in reverse order - // make a copy, to avoid leaking the parser instance - return { - uri: parser.uri, - metadata: parser.metadata, - variableReferences, - fileReferences: parser.references.map(ref => ref.uri), - }; - } finally { - parser?.dispose(); - } + return customChatModes; } public async parseNew(uri: URI, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts deleted file mode 100644 index 443d6e44c94..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/objectCache.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, DisposableMap } from '../../../../../../base/common/lifecycle.js'; -import { ObservableDisposable, assertNotDisposed } from './observableDisposable.js'; - -/** - * Generic cache for object instances. Guarantees to return only non-disposed - * objects from the {@linkcode get} method. If a requested object is not yet - * in the cache or is disposed already, the {@linkcode factory} callback is - * called to create a new object. - * - * @throws if {@linkcode factory} callback returns a disposed object. - * - * ## Examples - * - * ```typescript - * // a class that will be used as a cache key; the key can be of any - * // non-nullable type, including primitives like `string` or `number`, - * // but in this case we use an object pointer as a key - * class KeyObject {} - * - * // a class for testing purposes - * class TestObject extends ObservableDisposable { - * constructor( - * public readonly id: KeyObject, - * ) {} - * }; - * - * // create an object cache instance providing it a factory function that - * // is responsible for creating new objects based on the provided key if - * // the cache does not contain the requested object yet or an existing - * // object is already disposed - * const cache = new ObjectCache((key) => { - * // create a new test object based on the provided key - * return new TestObject(key); - * }); - * - * // create two keys - * const key1 = new KeyObject(); - * const key2 = new KeyObject(); - * - * // get an object from the cache by its key - * const object1 = cache.get(key1); // returns a new test object - * - * // validate that the new object has the correct key - * assert( - * object1.id === key1, - * 'Object 1 must have correct ID.', - * ); - * - * // returns the same cached test object - * const object2 = cache.get(key1); - * - * // validate that the same exact object is returned from the cache - * assert( - * object1 === object2, - * 'Object 2 the same cached object as object 1.', - * ); - * - * // returns a new test object - * const object3 = cache.get(key2); - * - * // validate that the new object has the correct key - * assert( - * object3.id === key2, - * 'Object 3 must have correct ID.', - * ); - * - * assert( - * object3 !== object1, - * 'Object 3 must be a new object.', - * ); - * ``` - */ -export class ObjectCache< - TValue extends ObservableDisposable, - TKey extends NonNullable = string, -> extends Disposable { - private readonly cache: DisposableMap = - this._register(new DisposableMap()); - - constructor( - private readonly factory: (key: TKey) => TValue & { isDisposed: false }, - ) { - super(); - } - - /** - * Get an existing object from the cache. If a requested object is not yet - * in the cache or is disposed already, the {@linkcode factory} callback is - * called to create a new object. - * - * @throws if {@linkcode factory} callback returns a disposed object. - * @param key - ID of the object in the cache - */ - public get(key: TKey): TValue & { isDisposed: false } { - let object = this.cache.get(key); - - // if object is already disposed, remove it from the cache - if (object?.isDisposed) { - this.cache.deleteAndLeak(key); - object = undefined; - } - - // if object exists and is not disposed, return it - if (object) { - // must always hold true due to the check above - assertNotDisposed( - object, - 'Object must not be disposed.', - ); - - return object; - } - - // create a new object by calling the factory - object = this.factory(key); - - // newly created object must not be disposed - assertNotDisposed( - object, - 'Newly created object must not be disposed.', - ); - - // remove it from the cache automatically on dispose - object.addDisposables( - object.onDispose(() => { - this.cache.deleteAndLeak(key); - })); - this.cache.set(key, object); - - return object; - } - - /** - * Remove an object from the cache by its key. - * - * @param key ID of the object to remove. - * @param dispose Whether the removed object must be disposed. - */ - public remove(key: TKey, dispose: boolean): this { - if (dispose) { - this.cache.deleteAndDispose(key); - return this; - } - - this.cache.deleteAndLeak(key); - return this; - } -} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts deleted file mode 100644 index a0e6a70e733..00000000000 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/observableDisposable.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; - -/** -* @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 - */ -export abstract class ObservableDisposable extends Disposable { - /** - * Underlying disposables store this object relies on. - */ - private readonly store = this._register(new DisposableStore()); - - /** - * Check if the current object is already has been disposed. - */ - public get isDisposed(): boolean { - return this.store.isDisposed; - } - - /** - * The event is fired when this object is disposed. - * Note! Executes the callback immediately if already disposed. - * - * @param callback The callback function to be called on updates. - */ - public onDispose(callback: () => void): IDisposable { - // if already disposed, execute the callback immediately - if (this.isDisposed) { - const timeoutHandle = setTimeout(callback); - - return toDisposable(() => { - clearTimeout(timeoutHandle); - }); - } - - return this.store.add(toDisposable(callback)); - } - - /** - * Adds disposable object(s) to the list of disposables - * that will be disposed with this object. - */ - public addDisposables(...disposables: IDisposable[]): this { - for (const disposable of disposables) { - this.store.add(disposable); - } - - return this; - } - - /** - * Assert that the current object was not yet disposed. - * - * @throws If the current object was already disposed. - * @param error Error message or error object to throw if assertion fails. - */ - public assertNotDisposed( - error: string | Error, - ): asserts this is TNotDisposed { - assertNotDisposed(this, error); - } -} - -/** - * @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 - */ -type TNotDisposed = TObject & { isDisposed: false }; - -/** - * @deprecated do not use this, https://github.com/microsoft/vscode/issues/248366 - */ -export function assertNotDisposed( - object: TObject, - error: string | Error, -): asserts object is TNotDisposed { - if (!object.isDisposed) { - return; - } - - const errorToThrow = typeof error === 'string' - ? new Error(error) - : error; - - throw errorToThrow; -} diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts deleted file mode 100644 index 8d32c30d851..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterBoolean.test.ts +++ /dev/null @@ -1,317 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; -import { randomBoolean } from '../../../../../../../../../base/test/common/testUtils.js'; -import { FrontMatterBoolean } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; -import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; - -suite('FrontMatterBoolean', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - suite('equals()', () => { - suite('base case', () => { - test('true', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'true' - : 'TRUE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - booleanText, - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - booleanText, - ), - ); - - assert.strictEqual( - boolean.value, - true, - 'Must have correct boolean value.', - ); - - assert( - boolean.equals(other), - 'Booleans must be equal.', - ); - }); - - test('false', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'false' - : 'FALSE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(5, 15, 5, 15 + 6), - booleanText, - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(5, 15, 5, 15 + 6), - booleanText, - ), - ); - - assert.strictEqual( - boolean.value, - false, - 'Must have correct boolean value.', - ); - - assert( - boolean.equals(other), - 'Booleans must be equal.', - ); - }); - }); - - suite('non-boolean token', () => { - suite('word token', () => { - test('true', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'true' - : 'TRUE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - booleanText, - ), - ); - - const other = new Word( - new Range(1, 1, 1, 5), - booleanText, - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - - test('false', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'false' - : 'FALSE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 2, 1, 2 + 6), - booleanText, - ), - ); - - const other = new Word( - new Range(1, 2, 1, 2 + 6), - booleanText, - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - }); - - suite('sequence token', () => { - test('true', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'true' - : 'TRUE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - booleanText, - ), - ); - - const other = new FrontMatterSequence([ - new Word( - new Range(1, 1, 1, 5), - booleanText, - ), - ]); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - - test('false', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'false' - : 'FALSE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 2, 1, 2 + 6), - booleanText, - ), - ); - - const other = new FrontMatterSequence([ - new Word( - new Range(1, 2, 1, 2 + 6), - booleanText, - ), - ]); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - }); - }); - - suite('different range', () => { - test('true', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'true' - : 'TRUE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 2, 1, 2 + 4), - booleanText, - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(3, 2, 3, 2 + 4), - booleanText, - ), - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - - test('false', () => { - // both values should yield the same result - const booleanText = (randomBoolean()) - ? 'false' - : 'FALSE'; - - const boolean = new FrontMatterBoolean( - new Word( - new Range(5, 15, 5, 15 + 5), - booleanText, - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(4, 15, 4, 15 + 5), - booleanText, - ), - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - }); - - suite('different text', () => { - test('true', () => { - const boolean = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - 'true', - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - 'True', - ), - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - - test('false', () => { - const boolean = new FrontMatterBoolean( - new Word( - new Range(5, 15, 5, 15 + 6), - 'FALSE', - ), - ); - - const other = new FrontMatterBoolean( - new Word( - new Range(5, 15, 5, 15 + 6), - 'false', - ), - ); - - assert( - boolean.equals(other) === false, - 'Booleans must not be equal.', - ); - }); - }); - - test('throws if cannot be converted to a boolean', () => { - assert.throws(() => { - new FrontMatterBoolean( - new Word( - new Range(1, 1, 1, 5), - 'true1', - ), - ); - }); - - assert.throws(() => { - new FrontMatterBoolean( - new Word( - new Range(2, 5, 2, 5 + 6), - 'fal se', - ), - ); - }); - - assert.throws(() => { - new FrontMatterBoolean( - new Word( - new Range(20, 4, 20, 4 + 1), - '1', - ), - ); - }); - }); - }); -}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts deleted file mode 100644 index b4f78b9d19b..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterDecoder.test.ts +++ /dev/null @@ -1,415 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { TestDecoder } from '../utils/testDecoder.js'; -import { VSBuffer } from '../../../../../../../../../base/common/buffer.js'; -import { newWriteableStream } from '../../../../../../../../../base/common/stream.js'; -import { NewLine } from '../../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; -import { DoubleQuote } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/doubleQuote.js'; -import { type TSimpleDecoderToken } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/simpleDecoder.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; -import { LeftBracket, RightBracket } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.js'; -import { FrontMatterDecoder } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/frontMatterDecoder.js'; -import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; -import { ExclamationMark, Quote, Tab, Word, Space, Colon, VerticalTab, Comma, Dash } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; -import { FrontMatterBoolean, FrontMatterString, FrontMatterArray, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; - -/** - * Front Matter decoder for testing purposes. - */ -export class TestFrontMatterDecoder extends TestDecoder { - constructor() { - const stream = newWriteableStream(null); - const decoder = new FrontMatterDecoder(stream); - - super(stream, decoder); - } -} - -suite('FrontMatterDecoder', () => { - const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - - test('produces expected tokens', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - 'just: "write some yaml "', - 'write-some :\t[ \' just\t \', "yaml!", true, , ,]', - 'anotherField \t\t\t : FALSE ', - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 4), 'just'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 5, 1, 6)), - new Space(new Range(1, 6, 1, 7)), - ]), - new FrontMatterString([ - new DoubleQuote(new Range(1, 7, 1, 8)), - new Word(new Range(1, 8, 1, 8 + 5), 'write'), - new Space(new Range(1, 13, 1, 14)), - new Word(new Range(1, 14, 1, 14 + 4), 'some'), - new Space(new Range(1, 18, 1, 19)), - new Word(new Range(1, 19, 1, 19 + 4), 'yaml'), - new Space(new Range(1, 23, 1, 24)), - new DoubleQuote(new Range(1, 24, 1, 25)), - ]), - ]), - new NewLine(new Range(1, 25, 1, 26)), - // second record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(2, 1, 2, 1 + 5), 'write'), - new Dash(new Range(2, 6, 2, 7)), - new Word(new Range(2, 7, 2, 7 + 4), 'some'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(2, 12, 2, 13)), - new Tab(new Range(2, 13, 2, 14)), - ]), - new FrontMatterArray([ - new LeftBracket(new Range(2, 14, 2, 15)), - new FrontMatterString([ - new Quote(new Range(2, 16, 2, 17)), - new Space(new Range(2, 17, 2, 18)), - new Word(new Range(2, 18, 2, 18 + 4), 'just'), - new Tab(new Range(2, 22, 2, 23)), - new Space(new Range(2, 23, 2, 24)), - new Quote(new Range(2, 24, 2, 25)), - ]), - new FrontMatterString([ - new DoubleQuote(new Range(2, 28, 2, 29)), - new Word(new Range(2, 29, 2, 29 + 4), 'yaml'), - new ExclamationMark(new Range(2, 33, 2, 34)), - new DoubleQuote(new Range(2, 34, 2, 35)), - ]), - new FrontMatterBoolean( - new Word(new Range(2, 37, 2, 37 + 4), 'true'), - ), - new RightBracket(new Range(2, 46, 2, 47)), - ]), - ]), - new NewLine(new Range(2, 47, 2, 48)), - // third record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(3, 1, 3, 1 + 12), 'anotherField'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(3, 19, 3, 20)), - new Space(new Range(3, 20, 3, 21)), - ]), - new FrontMatterBoolean( - new Word(new Range(3, 22, 3, 22 + 5), 'FALSE'), - ), - ]), - new Space(new Range(3, 27, 3, 28)), - ]); - }); - - suite('record', () => { - suite('values', () => { - test('unquoted string', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - 'just: write some yaml ', - 'anotherField \t\t : fal\v \t', - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 4), 'just'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 5, 1, 6)), - new Space(new Range(1, 6, 1, 7)), - ]), - new FrontMatterSequence([ - new Word(new Range(1, 7, 1, 7 + 5), 'write'), - new Space(new Range(1, 12, 1, 13)), - new Word(new Range(1, 13, 1, 13 + 4), 'some'), - new Space(new Range(1, 17, 1, 18)), - new Word(new Range(1, 18, 1, 18 + 4), 'yaml'), - ]), - ]), - new Space(new Range(1, 22, 1, 23)), - new NewLine(new Range(1, 23, 1, 24)), - // second record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(2, 1, 2, 1 + 12), 'anotherField'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(2, 17, 2, 18)), - new Space(new Range(2, 18, 2, 19)), - ]), - new FrontMatterSequence([ - new Word(new Range(2, 20, 2, 20 + 3), 'fal'), - ]), - ]), - new VerticalTab(new Range(2, 23, 2, 24)), - new Space(new Range(2, 24, 2, 25)), - new Tab(new Range(2, 25, 2, 26)), - ]); - }); - - test('quoted string', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - `just\t:\t'\vdo\tsome\ntesting, please\v' `, - 'anotherField \t\t :\v\v"fal\nse"', - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 4), 'just'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 6, 1, 7)), - new Tab(new Range(1, 7, 1, 8)), - ]), - new FrontMatterString([ - new Quote(new Range(1, 8, 1, 9)), - new VerticalTab(new Range(1, 9, 1, 10)), - new Word(new Range(1, 10, 1, 10 + 2), 'do'), - new Tab(new Range(1, 12, 1, 13)), - new Word(new Range(1, 13, 1, 13 + 4), 'some'), - new NewLine(new Range(1, 17, 1, 18)), - new Word(new Range(2, 1, 2, 1 + 7), 'testing'), - new Comma(new Range(2, 8, 2, 9)), - new Space(new Range(2, 9, 2, 10)), - new Word(new Range(2, 10, 2, 10 + 6), 'please'), - new VerticalTab(new Range(2, 16, 2, 17)), - new Quote(new Range(2, 17, 2, 18)), - ]), - ]), - new Space(new Range(2, 18, 2, 19)), - new NewLine(new Range(2, 19, 2, 20)), - // second record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(3, 1, 3, 1 + 12), 'anotherField'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(3, 17, 3, 18)), - new VerticalTab(new Range(3, 18, 3, 19)), - ]), - new FrontMatterString([ - new DoubleQuote(new Range(3, 20, 3, 21)), - new Word(new Range(3, 21, 3, 21 + 3), 'fal'), - new NewLine(new Range(3, 24, 3, 25)), - new Word(new Range(4, 1, 4, 1 + 2), 'se'), - new DoubleQuote(new Range(4, 3, 4, 4)), - ]), - ]), - ]); - }); - - test('boolean', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - 'anotherField \t\t : FALSE ', - 'my-field: true\t ', - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 12), 'anotherField'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 17, 1, 18)), - new Space(new Range(1, 18, 1, 19)), - ]), - new FrontMatterBoolean( - new Word( - new Range(1, 20, 1, 20 + 5), - 'FALSE', - ), - ), - ]), - new Space(new Range(1, 25, 1, 26)), - new NewLine(new Range(1, 26, 1, 27)), - // second record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(2, 1, 2, 1 + 2), 'my'), - new Dash(new Range(2, 3, 2, 4)), - new Word(new Range(2, 4, 2, 4 + 5), 'field'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(2, 9, 2, 10)), - new Space(new Range(2, 10, 2, 11)), - ]), - new FrontMatterBoolean( - new Word( - new Range(2, 11, 2, 11 + 4), - 'true', - ), - ), - ]), - new Tab(new Range(2, 15, 2, 16)), - new Space(new Range(2, 16, 2, 17)), - ]); - }); - - suite('array', () => { - test('empty', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - `tools\v:\t []`, - 'anotherField \t\t :\v\v"fal\nse"', - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 5), 'tools'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 7, 1, 8)), - new Tab(new Range(1, 8, 1, 9)), - ]), - new FrontMatterArray([ - new LeftBracket(new Range(1, 10, 1, 11)), - new RightBracket(new Range(1, 11, 1, 12)), - ]), - ]), - new NewLine(new Range(1, 12, 1, 13)), - // second record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(2, 1, 2, 1 + 12), 'anotherField'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(2, 17, 2, 18)), - new VerticalTab(new Range(2, 18, 2, 19)), - ]), - new FrontMatterString([ - new DoubleQuote(new Range(2, 20, 2, 21)), - new Word(new Range(2, 21, 2, 21 + 3), 'fal'), - new NewLine(new Range(2, 24, 2, 25)), - new Word(new Range(3, 1, 3, 1 + 2), 'se'), - new DoubleQuote(new Range(3, 3, 3, 4)), - ]), - ]), - ]); - }); - - test('mixed values', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - `tools\v:\t [true , 'toolName', some-tool]`, - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 5), 'tools'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 7, 1, 8)), - new Tab(new Range(1, 8, 1, 9)), - ]), - new FrontMatterArray([ - new LeftBracket(new Range(1, 10, 1, 11)), - // first array value - new FrontMatterBoolean( - new Word( - new Range(1, 11, 1, 11 + 4), - 'true', - ), - ), - // second array value - new FrontMatterString([ - new Quote(new Range(1, 18, 1, 19)), - new Word(new Range(1, 19, 1, 19 + 8), 'toolName'), - new Quote(new Range(1, 27, 1, 28)), - ]), - // third array value - new FrontMatterSequence([ - new Word(new Range(1, 30, 1, 30 + 4), 'some'), - new Dash(new Range(1, 34, 1, 35)), - new Word(new Range(1, 35, 1, 35 + 4), 'tool'), - ]), - new RightBracket(new Range(1, 39, 1, 40)), - ]), - ]), - ]); - }); - - test('redundant commas', async () => { - const test = disposables.add(new TestFrontMatterDecoder()); - - await test.run( - [ - `tools\v:\t [true ,, 'toolName', , , some-tool ,]`, - ], - [ - // first record - new FrontMatterRecord([ - new FrontMatterRecordName([ - new Word(new Range(1, 1, 1, 1 + 5), 'tools'), - ]), - new FrontMatterRecordDelimiter([ - new Colon(new Range(1, 7, 1, 8)), - new Tab(new Range(1, 8, 1, 9)), - ]), - new FrontMatterArray([ - new LeftBracket(new Range(1, 10, 1, 11)), - // first array value - new FrontMatterBoolean( - new Word( - new Range(1, 11, 1, 11 + 4), - 'true', - ), - ), - // second array value - new FrontMatterString([ - new Quote(new Range(1, 19, 1, 20)), - new Word(new Range(1, 20, 1, 20 + 8), 'toolName'), - new Quote(new Range(1, 28, 1, 29)), - ]), - // third array value - new FrontMatterSequence([ - new Word(new Range(1, 35, 1, 35 + 4), 'some'), - new Dash(new Range(1, 39, 1, 40)), - new Word(new Range(1, 40, 1, 40 + 4), 'tool'), - ]), - new RightBracket(new Range(1, 47, 1, 48)), - ]), - ]), - ]); - }); - }); - }); - }); - - test('empty', async () => { - const test = disposables.add( - new TestFrontMatterDecoder(), - ); - - await test.run('', []); - }); -}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts deleted file mode 100644 index fba57e45209..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterRecord.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; -import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; -import { Colon, LeftBracket, Quote, RightBracket, Space, Tab, VerticalTab, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; -import { FrontMatterArray, FrontMatterBoolean, FrontMatterRecord, FrontMatterRecordDelimiter, FrontMatterRecordName, FrontMatterString } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; - -suite('FrontMatterBoolean', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - suite('trimValueEnd()', () => { - test('trims space tokens at the end of record\'s value', () => { - const recordName = new FrontMatterRecordName([ - new Word( - new Range(4, 10, 4, 10 + 3), - 'key', - ), - ]); - - const recordDelimiter = new FrontMatterRecordDelimiter([ - new Colon(new Range(4, 14, 4, 15)), - new VerticalTab(new Range(4, 15, 4, 16)), - ]); - - const recordValue = new FrontMatterSequence([ - new Word(new Range(4, 18, 4, 18 + 10), 'some-value'), - new VerticalTab(new Range(4, 28, 4, 29)), - new Tab(new Range(4, 29, 4, 30)), - new Space(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - ]); - - const record = new FrontMatterRecord([ - recordName, recordDelimiter, recordValue, - ]); - - const trimmed = record.trimValueEnd(); - assert.deepStrictEqual( - trimmed, - [ - new VerticalTab(new Range(4, 28, 4, 29)), - new Tab(new Range(4, 29, 4, 30)), - new Space(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - ], - 'Must return correct trimmed list of spacing tokens.', - ); - - assert( - record.range.equalsRange( - new Range(4, 10, 4, 28), - ), - 'Must correctly update token range.', - ); - }); - - suite('does not trim non-sequence value tokens', () => { - test('boolean', () => { - const recordName = new FrontMatterRecordName([ - new Word( - new Range(4, 10, 4, 10 + 3), - 'yke', - ), - ]); - - const recordDelimiter = new FrontMatterRecordDelimiter([ - new Colon(new Range(4, 14, 4, 15)), - new VerticalTab(new Range(4, 15, 4, 16)), - ]); - - const recordValue = new FrontMatterBoolean( - new Word(new Range(4, 18, 4, 18 + 4), 'true'), - ); - - const record = new FrontMatterRecord([ - recordName, recordDelimiter, recordValue, - ]); - - const trimmed = record.trimValueEnd(); - assert.deepStrictEqual( - trimmed, - [], - 'Must return empty list of trimmed spacing tokens.', - ); - - assert( - record.range.equalsRange( - new Range(4, 10, 4, 22), - ), - 'Must not update token range.', - ); - }); - - test('quoted string', () => { - const recordName = new FrontMatterRecordName([ - new Word( - new Range(4, 10, 4, 10 + 3), - 'eyk', - ), - ]); - - const recordDelimiter = new FrontMatterRecordDelimiter([ - new Colon(new Range(4, 14, 4, 15)), - new VerticalTab(new Range(4, 15, 4, 16)), - ]); - - const recordValue = new FrontMatterString([ - new Quote(new Range(4, 18, 4, 19)), - new Word(new Range(4, 19, 4, 19 + 10), 'some text'), - new Quote(new Range(4, 29, 4, 30)), - ]); - - const record = new FrontMatterRecord([ - recordName, recordDelimiter, recordValue, - ]); - - const trimmed = record.trimValueEnd(); - assert.deepStrictEqual( - trimmed, - [], - 'Must return empty list of trimmed spacing tokens.', - ); - - assert( - record.range.equalsRange( - new Range(4, 10, 4, 30), - ), - 'Must not update token range.', - ); - }); - - test('array', () => { - const recordName = new FrontMatterRecordName([ - new Word( - new Range(4, 10, 4, 10 + 3), - 'yek', - ), - ]); - - const recordDelimiter = new FrontMatterRecordDelimiter([ - new Colon(new Range(4, 14, 4, 15)), - new VerticalTab(new Range(4, 15, 4, 16)), - ]); - - const recordValue = new FrontMatterArray([ - new LeftBracket(new Range(4, 18, 4, 19)), - new FrontMatterString([ - new Quote(new Range(4, 18, 4, 19)), - new Word(new Range(4, 19, 4, 19 + 10), 'some text'), - new Quote(new Range(4, 29, 4, 30)), - ]), - new FrontMatterBoolean( - new Word(new Range(4, 34, 4, 34 + 4), 'true'), - ), - new RightBracket(new Range(4, 38, 4, 39)), - ]); - - const record = new FrontMatterRecord([ - recordName, recordDelimiter, recordValue, - ]); - - const trimmed = record.trimValueEnd(); - assert.deepStrictEqual( - trimmed, - [], - 'Must return empty list of trimmed spacing tokens.', - ); - - assert( - record.range.equalsRange( - new Range(4, 10, 4, 39), - ), - 'Must not update token range.', - ); - }); - }); - }); -}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts deleted file mode 100644 index 68602622bba..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/frontMatterDecoder/frontMatterSequence.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { Range } from '../../../../../../../../../editor/common/core/range.js'; -import { FrontMatterValueToken } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/index.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../../base/test/common/utils.js'; -import { Space, Tab, VerticalTab, Word } from '../../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tokens.js'; -import { FrontMatterSequence } from '../../../../../../common/promptSyntax/codecs/base/frontMatterCodec/tokens/frontMatterSequence.js'; - -suite('FrontMatterSequence', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('extends \'FrontMatterValueToken\'', () => { - const sequence = new FrontMatterSequence([ - new Word( - new Range(1, 1, 1, 5), - 'test', - ), - ]); - - assert( - sequence instanceof FrontMatterValueToken, - 'Must extend FrontMatterValueToken class.', - ); - }); - - suite('trimEnd()', () => { - test('trims space tokens at the end of the sequence', () => { - const sequence = new FrontMatterSequence([ - new Word(new Range(4, 18, 4, 18 + 10), 'some-value'), - new Space(new Range(4, 28, 4, 29)), - new Space(new Range(4, 29, 4, 30)), - new VerticalTab(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - new Space(new Range(4, 32, 4, 33)), - ]); - - const trimmed = sequence.trimEnd(); - assert.deepStrictEqual( - trimmed, - [ - new Space(new Range(4, 28, 4, 29)), - new Space(new Range(4, 29, 4, 30)), - new VerticalTab(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - new Space(new Range(4, 32, 4, 33)), - ], - 'Must return correct trimmed list of spacing tokens.', - ); - - assert( - sequence.range.equalsRange( - new Range(4, 18, 4, 28), - ), - 'Must correctly update token range.', - ); - }); - - test('remains functional if only spacing tokens were present', () => { - const sequence = new FrontMatterSequence([ - new Space(new Range(4, 28, 4, 29)), - new Space(new Range(4, 29, 4, 30)), - new VerticalTab(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - new Space(new Range(4, 32, 4, 33)), - ]); - - const trimmed = sequence.trimEnd(); - assert.deepStrictEqual( - trimmed, - [ - new Space(new Range(4, 28, 4, 29)), - new Space(new Range(4, 29, 4, 30)), - new VerticalTab(new Range(4, 30, 4, 31)), - new Tab(new Range(4, 31, 4, 32)), - new Space(new Range(4, 32, 4, 33)), - ], - 'Must return correct trimmed list of spacing tokens.', - ); - - assert( - sequence.range.equalsRange( - new Range(4, 28, 4, 28), - ), - 'Must correctly update token range.', - ); - - assert.deepStrictEqual( - sequence.children, - [ - new Word(new Range(4, 28, 4, 28), ''), - ], - 'Must contain a single empty token.', - ); - }); - }); - - test('throws if no tokens provided', () => { - assert.throws(() => { - new FrontMatterSequence([]); - }); - }); -}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts deleted file mode 100644 index b9ac4916923..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/linesDecoder.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { Range } from '../../../../../../../../editor/common/core/range.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { DisposableStore } from '../../../../../../../../base/common/lifecycle.js'; -import { Line } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/line.js'; -import { TestDecoder, TTokensConsumeMethod } from './utils/testDecoder.js'; -import { NewLine } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; -import { newWriteableStream, WriteableStream } from '../../../../../../../../base/common/stream.js'; -import { CarriageReturn } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; -import { LinesDecoder, TLineToken } from '../../../../../common/promptSyntax/codecs/base/linesCodec/linesDecoder.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; - -/** - * Note! This decoder is also often used to test common logic of abstract {@link BaseDecoder} - * class, because the {@link LinesDecoder} is one of the simplest non-abstract decoders we have. - */ -suite('LinesDecoder', () => { - const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - - /** - * Test the core logic with specific method of consuming - * tokens that are produced by a lines decoder instance. - */ - suite('core logic', () => { - testLinesDecoder('async-generator', disposables); - testLinesDecoder('consume-all-method', disposables); - testLinesDecoder('on-data-event', disposables); - }); - - suite('settled promise', () => { - test('throws if accessed on not-yet-started decoder instance', () => { - const test = disposables.add(new TestLinesDecoder()); - - assert.throws( - () => { - // testing the field access that throws here, so - // its OK to not use the returned value afterwards - // eslint-disable-next-line local/code-no-unused-expressions - test.decoder.settled; - }, - [ - 'Cannot get `settled` promise of a stream that has not been started.', - 'Please call `start()` first.', - ].join(' '), - ); - }); - }); - - suite('start', () => { - test('throws if the decoder object is already `disposed`', () => { - const test = disposables.add(new TestLinesDecoder()); - const { decoder } = test; - decoder.dispose(); - - assert.throws( - decoder.start.bind(decoder), - 'Cannot start stream that has already disposed.', - ); - }); - - test('throws if the decoder object is already `ended`', async () => { - const inputStream = newWriteableStream(null); - const test = disposables.add(new TestLinesDecoder(inputStream)); - const { decoder } = test; - - setTimeout(() => { - test.sendData([ - 'hello', - 'world :wave:', - ]); - }, 5); - - const receivedTokens = await decoder.start() - .consumeAll(); - - // a basic sanity check for received tokens - assert.strictEqual( - receivedTokens.length, - 3, - 'Must produce the correct number of tokens.', - ); - - // validate that calling `start()` after stream has ended throws - assert.throws( - decoder.start.bind(decoder), - 'Cannot start stream that has already ended.', - ); - }); - }); -}); - - -/** - * A reusable test utility that asserts that a `LinesDecoder` instance - * correctly decodes `inputData` into a stream of `TLineToken` tokens. - * - * ## Examples - * - * ```typescript - * // create a new test utility instance - * const test = disposables.add(new TestLinesDecoder()); - * - * // run the test - * await test.run( - * ' hello world\n', - * [ - * new Line(1, ' hello world'), - * new NewLine(new Range(1, 13, 1, 14)), - * ], - * ); - */ -export class TestLinesDecoder extends TestDecoder { - constructor( - inputStream?: WriteableStream, - ) { - const stream = (inputStream) - ? inputStream - : newWriteableStream(null); - - const decoder = new LinesDecoder(stream); - - super(stream, decoder); - } -} - -/** - * Common reusable test utility to validate {@link LinesDecoder} logic with - * the provided {@link tokensConsumeMethod} way of consuming decoder-produced tokens. - * - * @throws if a test fails, please see thrown error for failure details. - * @param tokensConsumeMethod The way to consume tokens produced by the decoder. - * @param disposables Test disposables store. - */ -function testLinesDecoder( - tokensConsumeMethod: TTokensConsumeMethod, - disposables: Pick, -) { - suite(tokensConsumeMethod, () => { - suite('produces expected tokens', () => { - test('input starts with line data', async () => { - const test = disposables.add(new TestLinesDecoder()); - - await test.run( - ' hello world\nhow are you doing?\n\n 😊 \r', - [ - new Line(1, ' hello world'), - new NewLine(new Range(1, 13, 1, 14)), - new Line(2, 'how are you doing?'), - new NewLine(new Range(2, 19, 2, 20)), - new Line(3, ''), - new NewLine(new Range(3, 1, 3, 2)), - new Line(4, ' 😊 '), - new NewLine(new Range(4, 5, 4, 6)), - ], - ); - }); - - test('standalone \\r is treated as new line', async () => { - const test = disposables.add(new TestLinesDecoder()); - - await test.run( - ' hello world\nhow are you doing?\n\n 😊 \r ', - [ - new Line(1, ' hello world'), - new NewLine(new Range(1, 13, 1, 14)), - new Line(2, 'how are you doing?'), - new NewLine(new Range(2, 19, 2, 20)), - new Line(3, ''), - new NewLine(new Range(3, 1, 3, 2)), - new Line(4, ' 😊 '), - new NewLine(new Range(4, 5, 4, 6)), - new Line(5, ' '), - ], - ); - }); - - test('input starts with a new line', async () => { - const test = disposables.add(new TestLinesDecoder()); - - await test.run( - '\nsome text on this line\n\n\nanother 💬 on this line\r\n🤫\n', - [ - new Line(1, ''), - new NewLine(new Range(1, 1, 1, 2)), - new Line(2, 'some text on this line'), - new NewLine(new Range(2, 23, 2, 24)), - new Line(3, ''), - new NewLine(new Range(3, 1, 3, 2)), - new Line(4, ''), - new NewLine(new Range(4, 1, 4, 2)), - new Line(5, 'another 💬 on this line'), - new CarriageReturn(new Range(5, 24, 5, 25)), - new NewLine(new Range(5, 25, 5, 26)), - new Line(6, '🤫'), - new NewLine(new Range(6, 3, 6, 4)), - ], - ); - }); - - test('input starts and ends with multiple new lines', async () => { - const test = disposables.add(new TestLinesDecoder()); - - await test.run( - '\n\n\r\nciao! 🗯️\t💭 💥 come\tva?\n\n\n\n\n', - [ - new Line(1, ''), - new NewLine(new Range(1, 1, 1, 2)), - new Line(2, ''), - new NewLine(new Range(2, 1, 2, 2)), - new Line(3, ''), - new CarriageReturn(new Range(3, 1, 3, 2)), - new NewLine(new Range(3, 2, 3, 3)), - new Line(4, 'ciao! 🗯️\t💭 💥 come\tva?'), - new NewLine(new Range(4, 25, 4, 26)), - new Line(5, ''), - new NewLine(new Range(5, 1, 5, 2)), - new Line(6, ''), - new NewLine(new Range(6, 1, 6, 2)), - new Line(7, ''), - new NewLine(new Range(7, 1, 7, 2)), - new Line(8, ''), - new NewLine(new Range(8, 1, 8, 2)), - ], - ); - }); - - test('single carriage return is treated as new line', async () => { - const test = disposables.add(new TestLinesDecoder()); - - await test.run( - '\r\rhaalo! 💥💥 how\'re you?\r ?!\r\n\r\n ', - [ - new Line(1, ''), - new NewLine(new Range(1, 1, 1, 2)), - new Line(2, ''), - new NewLine(new Range(2, 1, 2, 2)), - new Line(3, 'haalo! 💥💥 how\'re you?'), - new NewLine(new Range(3, 24, 3, 25)), - new Line(4, ' ?!'), - new CarriageReturn(new Range(4, 4, 4, 5)), - new NewLine(new Range(4, 5, 4, 6)), - new Line(5, ''), - new CarriageReturn(new Range(5, 1, 5, 2)), - new NewLine(new Range(5, 2, 5, 3)), - new Line(6, ' '), - ], - ); - }); - }); - }); -} diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts deleted file mode 100644 index 37a3331f796..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/codecs/base/markdownDecoder.test.ts +++ /dev/null @@ -1,937 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { TestDecoder } from './utils/testDecoder.js'; -import { Range } from '../../../../../../../../editor/common/core/range.js'; -import { VSBuffer } from '../../../../../../../../base/common/buffer.js'; -import { newWriteableStream } from '../../../../../../../../base/common/stream.js'; -import { Tab } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/tab.js'; -import { Word } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/word.js'; -import { Dash } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/dash.js'; -import { Space } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/space.js'; -import { Slash } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/slash.js'; -import { NewLine } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/newLine.js'; -import { FormFeed } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/formFeed.js'; -import { VerticalTab } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/verticalTab.js'; -import { MarkdownLink } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownLink.js'; -import { CarriageReturn } from '../../../../../common/promptSyntax/codecs/base/linesCodec/tokens/carriageReturn.js'; -import { MarkdownImage } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownImage.js'; -import { ExclamationMark } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/exclamationMark.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../../base/test/common/utils.js'; -import { MarkdownComment } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/tokens/markdownComment.js'; -import { LeftBracket, RightBracket } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/brackets.js'; -import { MarkdownDecoder, TMarkdownToken } from '../../../../../common/promptSyntax/codecs/base/markdownCodec/markdownDecoder.js'; -import { LeftParenthesis, RightParenthesis } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/parentheses.js'; -import { LeftAngleBracket, RightAngleBracket } from '../../../../../common/promptSyntax/codecs/base/simpleCodec/tokens/angleBrackets.js'; - -/** - * A reusable test utility that asserts that a `TestMarkdownDecoder` instance - * correctly decodes `inputData` into a stream of `TMarkdownToken` tokens. - * - * ## Examples - * - * ```typescript - * // create a new test utility instance - * const test = testDisposables.add(new TestMarkdownDecoder()); - * - * // run the test - * await test.run( - * ' hello [world](/etc/hosts)!', - * [ - * new Space(new Range(1, 1, 1, 2)), - * new Word(new Range(1, 2, 1, 7), 'hello'), - * new Space(new Range(1, 7, 1, 8)), - * new MarkdownLink(1, 8, '[world]', '(/etc/hosts)'), - * new Word(new Range(1, 27, 1, 28), '!'), - * new NewLine(new Range(1, 28, 1, 29)), - * ], - * ); - */ -export class TestMarkdownDecoder extends TestDecoder { - constructor() { - const stream = newWriteableStream(null); - - super(stream, new MarkdownDecoder(stream)); - } -} - -suite('MarkdownDecoder', () => { - const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - - suite('general', () => { - test('base cases', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - await test.run( - [ - // basic text - ' hello world', - // text with markdown link and special characters in the filename - 'how are\t you [caption text](./some/file/path/refer🎨nce.md)?\v', - // empty line - '', - // markdown link with special characters in the link caption and path - '[(example!)](another/path/with[-and-]-chars/folder)\t ', - // markdown link `#file` variable in the caption and with absolute path - '\t[#file:something.txt](/absolute/path/to/something.txt)', - // text with a commented out markdown link - '\v\f machines must suffer', - ], - [ - // first line - new Space(new Range(1, 1, 1, 2)), - new Word(new Range(1, 2, 1, 7), 'hello'), - new Space(new Range(1, 7, 1, 8)), - new Word(new Range(1, 8, 1, 13), 'world'), - new NewLine(new Range(1, 13, 1, 14)), - // second line - new Word(new Range(2, 1, 2, 4), 'how'), - new Space(new Range(2, 4, 2, 5)), - new Word(new Range(2, 5, 2, 8), 'are'), - new Tab(new Range(2, 8, 2, 9)), - new Space(new Range(2, 9, 2, 10)), - new Word(new Range(2, 10, 2, 13), 'you'), - new Space(new Range(2, 13, 2, 14)), - new MarkdownLink(2, 14, '[caption text]', '(./some/file/path/refer🎨nce.md)'), - new Word(new Range(2, 60, 2, 61), '?'), - new VerticalTab(new Range(2, 61, 2, 62)), - new NewLine(new Range(2, 62, 2, 63)), - // third line - new NewLine(new Range(3, 1, 3, 2)), - // fourth line - new MarkdownLink(4, 1, '[(example!)]', '(another/path/with[-and-]-chars/folder)'), - new Tab(new Range(4, 52, 4, 53)), - new Space(new Range(4, 53, 4, 54)), - new NewLine(new Range(4, 54, 4, 55)), - // fifth line - new Tab(new Range(5, 1, 5, 2)), - new MarkdownLink(5, 2, '[#file:something.txt]', '(/absolute/path/to/something.txt)'), - new NewLine(new Range(5, 56, 5, 57)), - // sixth line - new VerticalTab(new Range(6, 1, 6, 2)), - new FormFeed(new Range(6, 2, 6, 3)), - new Space(new Range(6, 3, 6, 4)), - new Word(new Range(6, 4, 6, 12), 'machines'), - new Space(new Range(6, 12, 6, 13)), - new Word(new Range(6, 13, 6, 17), 'must'), - new Space(new Range(6, 17, 6, 18)), - new MarkdownComment(new Range(6, 18, 6, 18 + 41), ''), - new Space(new Range(6, 59, 6, 60)), - new Word(new Range(6, 60, 6, 66), 'suffer'), - ], - ); - }); - - test('nuanced', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // tests that the link caption contain a chat prompt `#file:` reference, while - // the file path can contain other `graphical characters` - '\v\t[#file:./another/path/to/file.txt](./real/file!path/file◆name.md)', - // tests that the link file path contain a chat prompt `#file:` reference, - // `spaces`, `emojies`, and other `graphical characters` - ' [reference ∘ label](/absolute/pa th/to-#file:file.txt/f🥸⚡️le.md)', - // tests that link caption and file path can contain `parentheses`, `spaces`, and - // `emojies` - '\f[!(hello)!](./w(())rld/nice-🦚-filen(a).git))\n\t', - // tests that the link caption can be empty, while the file path can contain `square brackets` - '[](./s[]me/pa[h!) ', - ]; - - await test.run( - inputLines, - [ - // `1st` line - new VerticalTab(new Range(1, 1, 1, 2)), - new Tab(new Range(1, 2, 1, 3)), - new MarkdownLink(1, 3, '[#file:./another/path/to/file.txt]', '(./real/file!path/file◆name.md)'), - new NewLine(new Range(1, 68, 1, 69)), - // `2nd` line - new Space(new Range(2, 1, 2, 2)), - new MarkdownLink(2, 2, '[reference ∘ label]', '(/absolute/pa th/to-#file:file.txt/f🥸⚡️le.md)'), - new NewLine(new Range(2, 67, 2, 68)), - // `3rd` line - new FormFeed(new Range(3, 1, 3, 2)), - new MarkdownLink(3, 2, '[!(hello)!]', '(./w(())rld/nice-🦚-filen(a).git)'), - new RightParenthesis(new Range(3, 50, 3, 51)), - new NewLine(new Range(3, 51, 3, 52)), - // `4th` line - new Tab(new Range(4, 1, 4, 2)), - new NewLine(new Range(4, 2, 4, 3)), - // `5th` line - new MarkdownLink(5, 1, '[]', '(./s[]me/pa[h!)'), - new Space(new Range(5, 24, 5, 25)), - ], - ); - }); - }); - - suite('links', () => { - suite('broken', () => { - test('invalid', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // incomplete link reference with empty caption - '[ ](./real/file path/file⇧name.md', - // space between caption and reference is disallowed - '[link text] (./file path/name.txt)', - ]; - - await test.run( - inputLines, - [ - // `1st` line - new LeftBracket(new Range(1, 1, 1, 2)), - new Space(new Range(1, 2, 1, 3)), - new RightBracket(new Range(1, 3, 1, 4)), - new LeftParenthesis(new Range(1, 4, 1, 5)), - new Word(new Range(1, 5, 1, 5 + 1), '.'), - new Slash(new Range(1, 6, 1, 7)), - new Word(new Range(1, 7, 1, 7 + 4), 'real'), - new Slash(new Range(1, 11, 1, 12)), - new Word(new Range(1, 12, 1, 12 + 4), 'file'), - new Space(new Range(1, 16, 1, 17)), - new Word(new Range(1, 17, 1, 17 + 4), 'path'), - new Slash(new Range(1, 21, 1, 22)), - new Word(new Range(1, 22, 1, 22 + 12), 'file⇧name.md'), - new NewLine(new Range(1, 34, 1, 35)), - // `2nd` line - new LeftBracket(new Range(2, 1, 2, 2)), - new Word(new Range(2, 2, 2, 2 + 4), 'link'), - new Space(new Range(2, 6, 2, 7)), - new Word(new Range(2, 7, 2, 7 + 4), 'text'), - new RightBracket(new Range(2, 11, 2, 12)), - new Space(new Range(2, 12, 2, 13)), - new LeftParenthesis(new Range(2, 13, 2, 14)), - new Word(new Range(2, 14, 2, 14 + 1), '.'), - new Slash(new Range(2, 15, 2, 16)), - new Word(new Range(2, 16, 2, 16 + 4), 'file'), - new Space(new Range(2, 20, 2, 21)), - new Word(new Range(2, 21, 2, 21 + 4), 'path'), - new Slash(new Range(2, 25, 2, 26)), - new Word(new Range(2, 26, 2, 26 + 8), 'name.txt'), - new RightParenthesis(new Range(2, 34, 2, 35)), - ], - ); - }); - - suite('stop characters inside caption/reference (new lines)', () => { - for (const StopCharacter of [CarriageReturn, NewLine]) { - let characterName = ''; - - if (StopCharacter === CarriageReturn) { - characterName = '\\r'; - } - if (StopCharacter === NewLine) { - characterName = '\\n'; - } - - assert( - characterName !== '', - 'The "characterName" must be set, got "empty line".', - ); - - test(`stop character - "${characterName}"`, async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // stop character inside link caption - `[haa${StopCharacter.symbol}loů](./real/💁/name.txt)`, - // stop character inside link reference - `[ref text](/etc/pat${StopCharacter.symbol}h/to/file.md)`, - // stop character between line caption and link reference is disallowed - `[text]${StopCharacter.symbol}(/etc/ path/main.mdc)`, - ]; - - - await test.run( - inputLines, - [ - // `1st` input line - new LeftBracket(new Range(1, 1, 1, 2)), - new Word(new Range(1, 2, 1, 2 + 3), 'haa'), - new NewLine(new Range(1, 5, 1, 6)), // a single CR token is treated as a `new line` - new Word(new Range(2, 1, 2, 1 + 3), 'loů'), - new RightBracket(new Range(2, 4, 2, 5)), - new LeftParenthesis(new Range(2, 5, 2, 6)), - new Word(new Range(2, 6, 2, 6 + 1), '.'), - new Slash(new Range(2, 7, 2, 8)), - new Word(new Range(2, 8, 2, 8 + 4), 'real'), - new Slash(new Range(2, 12, 2, 13)), - new Word(new Range(2, 13, 2, 13 + 2), '💁'), - new Slash(new Range(2, 15, 2, 16)), - new Word(new Range(2, 16, 2, 16 + 8), 'name.txt'), - new RightParenthesis(new Range(2, 24, 2, 25)), - new NewLine(new Range(2, 25, 2, 26)), - // `2nd` input line - new LeftBracket(new Range(3, 1, 3, 2)), - new Word(new Range(3, 2, 3, 2 + 3), 'ref'), - new Space(new Range(3, 5, 3, 6)), - new Word(new Range(3, 6, 3, 6 + 4), 'text'), - new RightBracket(new Range(3, 10, 3, 11)), - new LeftParenthesis(new Range(3, 11, 3, 12)), - new Slash(new Range(3, 12, 3, 13)), - new Word(new Range(3, 13, 3, 13 + 3), 'etc'), - new Slash(new Range(3, 16, 3, 17)), - new Word(new Range(3, 17, 3, 17 + 3), 'pat'), - new NewLine(new Range(3, 20, 3, 21)), // a single CR token is treated as a `new line` - new Word(new Range(4, 1, 4, 1 + 1), 'h'), - new Slash(new Range(4, 2, 4, 3)), - new Word(new Range(4, 3, 4, 3 + 2), 'to'), - new Slash(new Range(4, 5, 4, 6)), - new Word(new Range(4, 6, 4, 6 + 7), 'file.md'), - new RightParenthesis(new Range(4, 13, 4, 14)), - new NewLine(new Range(4, 14, 4, 15)), - // `3nd` input line - new LeftBracket(new Range(5, 1, 5, 2)), - new Word(new Range(5, 2, 5, 2 + 4), 'text'), - new RightBracket(new Range(5, 6, 5, 7)), - new NewLine(new Range(5, 7, 5, 8)), // a single CR token is treated as a `new line` - new LeftParenthesis(new Range(6, 1, 6, 2)), - new Slash(new Range(6, 2, 6, 3)), - new Word(new Range(6, 3, 6, 3 + 3), 'etc'), - new Slash(new Range(6, 6, 6, 7)), - new Space(new Range(6, 7, 6, 8)), - new Word(new Range(6, 8, 6, 8 + 4), 'path'), - new Slash(new Range(6, 12, 6, 13)), - new Word(new Range(6, 13, 6, 13 + 8), 'main.mdc'), - new RightParenthesis(new Range(6, 21, 6, 22)), - ], - ); - }); - } - }); - - /** - * Same as above but these stop characters do not move the caret to the next line. - */ - suite('stop characters inside caption/reference (same line)', () => { - for (const StopCharacter of [VerticalTab, FormFeed]) { - let characterName = ''; - - if (StopCharacter === VerticalTab) { - characterName = '\\v'; - } - if (StopCharacter === FormFeed) { - characterName = '\\f'; - } - - assert( - characterName !== '', - 'The "characterName" must be set, got "empty line".', - ); - - test(`stop character - "${characterName}"`, async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // stop character inside link caption - `[haa${StopCharacter.symbol}loů](./real/💁/name.txt)`, - // stop character inside link reference - `[ref text](/etc/pat${StopCharacter.symbol}h/to/file.md)`, - // stop character between line caption and link reference is disallowed - `[text]${StopCharacter.symbol}(/etc/ path/file.md)`, - ]; - - - await test.run( - inputLines, - [ - // `1st` input line - new LeftBracket(new Range(1, 1, 1, 2)), - new Word(new Range(1, 2, 1, 2 + 3), 'haa'), - new StopCharacter(new Range(1, 5, 1, 6)), // <- stop character - new Word(new Range(1, 6, 1, 6 + 3), 'loů'), - new RightBracket(new Range(1, 9, 1, 10)), - new LeftParenthesis(new Range(1, 10, 1, 11)), - new Word(new Range(1, 11, 1, 11 + 1), '.'), - new Slash(new Range(1, 12, 1, 13)), - new Word(new Range(1, 13, 1, 13 + 4), 'real'), - new Slash(new Range(1, 17, 1, 18)), - new Word(new Range(1, 18, 1, 18 + 2), '💁'), - new Slash(new Range(1, 20, 1, 21)), - new Word(new Range(1, 21, 1, 21 + 8), 'name.txt'), - new RightParenthesis(new Range(1, 29, 1, 30)), - new NewLine(new Range(1, 30, 1, 31)), - // `2nd` input line - new LeftBracket(new Range(2, 1, 2, 2)), - new Word(new Range(2, 2, 2, 2 + 3), 'ref'), - new Space(new Range(2, 5, 2, 6)), - new Word(new Range(2, 6, 2, 6 + 4), 'text'), - new RightBracket(new Range(2, 10, 2, 11)), - new LeftParenthesis(new Range(2, 11, 2, 12)), - new Slash(new Range(2, 12, 2, 13)), - new Word(new Range(2, 13, 2, 13 + 3), 'etc'), - new Slash(new Range(2, 16, 2, 17)), - new Word(new Range(2, 17, 2, 17 + 3), 'pat'), - new StopCharacter(new Range(2, 20, 2, 21)), // <- stop character - new Word(new Range(2, 21, 2, 21 + 1), 'h'), - new Slash(new Range(2, 22, 2, 23)), - new Word(new Range(2, 23, 2, 23 + 2), 'to'), - new Slash(new Range(2, 25, 2, 26)), - new Word(new Range(2, 26, 2, 26 + 7), 'file.md'), - new RightParenthesis(new Range(2, 33, 2, 34)), - new NewLine(new Range(2, 34, 2, 35)), - // `3nd` input line - new LeftBracket(new Range(3, 1, 3, 2)), - new Word(new Range(3, 2, 3, 2 + 4), 'text'), - new RightBracket(new Range(3, 6, 3, 7)), - new StopCharacter(new Range(3, 7, 3, 8)), // <- stop character - new LeftParenthesis(new Range(3, 8, 3, 9)), - new Slash(new Range(3, 9, 3, 10)), - new Word(new Range(3, 10, 3, 10 + 3), 'etc'), - new Slash(new Range(3, 13, 3, 14)), - new Space(new Range(3, 14, 3, 15)), - new Word(new Range(3, 15, 3, 15 + 4), 'path'), - new Slash(new Range(3, 19, 3, 20)), - new Word(new Range(3, 20, 3, 20 + 7), 'file.md'), - new RightParenthesis(new Range(3, 27, 3, 28)), - ], - ); - }); - } - }); - }); - }); - - - suite('images', () => { - suite('general', () => { - test('base cases', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputData = [ - '\t![alt text](./some/path/to/file.jpg) ', - 'plain text \f![label](./image.png)\v and more text', - '![](/var/images/default) following text', - ]; - - await test.run( - inputData, - [ - // `1st` - new Tab(new Range(1, 1, 1, 2)), - new MarkdownImage(1, 2, '![alt text]', '(./some/path/to/file.jpg)'), - new Space(new Range(1, 38, 1, 39)), - new NewLine(new Range(1, 39, 1, 40)), - // `2nd` - new Word(new Range(2, 1, 2, 6), 'plain'), - new Space(new Range(2, 6, 2, 7)), - new Word(new Range(2, 7, 2, 11), 'text'), - new Space(new Range(2, 11, 2, 12)), - new FormFeed(new Range(2, 12, 2, 13)), - new MarkdownImage(2, 13, '![label]', '(./image.png)'), - new VerticalTab(new Range(2, 34, 2, 35)), - new Space(new Range(2, 35, 2, 36)), - new Word(new Range(2, 36, 2, 39), 'and'), - new Space(new Range(2, 39, 2, 40)), - new Word(new Range(2, 40, 2, 44), 'more'), - new Space(new Range(2, 44, 2, 45)), - new Word(new Range(2, 45, 2, 49), 'text'), - new NewLine(new Range(2, 49, 2, 50)), - // `3rd` - new MarkdownImage(3, 1, '![]', '(/var/images/default)'), - new Space(new Range(3, 25, 3, 26)), - new Word(new Range(3, 26, 3, 35), 'following'), - new Space(new Range(3, 35, 3, 36)), - new Word(new Range(3, 36, 3, 40), 'text'), - ], - ); - }); - - test('nuanced', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputData = [ - '\t![](./s☻me/path/to/file.jpeg) ', - 'raw text \f![(/1.png)](./image-🥸.png)\v and more text', - // '![](/var/images/default) following text', - ]; - - await test.run( - inputData, - [ - // `1st` - new Tab(new Range(1, 1, 1, 2)), - new MarkdownImage(1, 2, '![]', '(./s☻me/path/to/file.jpeg)'), - new Space(new Range(1, 47, 1, 48)), - new NewLine(new Range(1, 48, 1, 49)), - // `2nd` - new Word(new Range(2, 1, 2, 4), 'raw'), - new Space(new Range(2, 4, 2, 5)), - new Word(new Range(2, 5, 2, 9), 'text'), - new Space(new Range(2, 9, 2, 10)), - new FormFeed(new Range(2, 10, 2, 11)), - new MarkdownImage(2, 11, '![(/1.png)]', '(./image-🥸.png)'), - new VerticalTab(new Range(2, 38, 2, 39)), - new Space(new Range(2, 39, 2, 40)), - new Word(new Range(2, 40, 2, 43), 'and'), - new Space(new Range(2, 43, 2, 44)), - new Word(new Range(2, 44, 2, 48), 'more'), - new Space(new Range(2, 48, 2, 49)), - new Word(new Range(2, 49, 2, 53), 'text'), - ], - ); - }); - }); - - suite('broken', () => { - test('invalid', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // incomplete link reference with empty caption - '![ ](./real/file path/file★name.webp', - // space between caption and reference is disallowed - '\f![link text] (./file path/name.jpg)', - // new line inside the link reference - '\v![ ](./file\npath/name.jpeg )', - ]; - - await test.run( - inputLines, - [ - // `1st` line - new ExclamationMark(new Range(1, 1, 1, 2)), - new LeftBracket(new Range(1, 2, 1, 3)), - new Space(new Range(1, 3, 1, 4)), - new RightBracket(new Range(1, 4, 1, 5)), - new LeftParenthesis(new Range(1, 5, 1, 6)), - new Word(new Range(1, 6, 1, 6 + 1), '.'), - new Slash(new Range(1, 7, 1, 8)), - new Word(new Range(1, 8, 1, 8 + 4), 'real'), - new Slash(new Range(1, 12, 1, 13)), - new Word(new Range(1, 13, 1, 13 + 4), 'file'), - new Space(new Range(1, 17, 1, 18)), - new Word(new Range(1, 18, 1, 18 + 4), 'path'), - new Slash(new Range(1, 22, 1, 23)), - new Word(new Range(1, 23, 1, 23 + 14), 'file★name.webp'), - new NewLine(new Range(1, 37, 1, 38)), - // `2nd` line - new FormFeed(new Range(2, 1, 2, 2)), - new ExclamationMark(new Range(2, 2, 2, 3)), - new LeftBracket(new Range(2, 3, 2, 4)), - new Word(new Range(2, 4, 2, 4 + 4), 'link'), - new Space(new Range(2, 8, 2, 9)), - new Word(new Range(2, 9, 2, 9 + 4), 'text'), - new RightBracket(new Range(2, 13, 2, 14)), - new Space(new Range(2, 14, 2, 15)), - new LeftParenthesis(new Range(2, 15, 2, 16)), - new Word(new Range(2, 16, 2, 16 + 1), '.'), - new Slash(new Range(2, 17, 2, 18)), - new Word(new Range(2, 18, 2, 18 + 4), 'file'), - new Space(new Range(2, 22, 2, 23)), - new Word(new Range(2, 23, 2, 23 + 4), 'path'), - new Slash(new Range(2, 27, 2, 28)), - new Word(new Range(2, 28, 2, 28 + 8), 'name.jpg'), - new RightParenthesis(new Range(2, 36, 2, 37)), - new NewLine(new Range(2, 37, 2, 38)), - // `3rd` line - new VerticalTab(new Range(3, 1, 3, 2)), - new ExclamationMark(new Range(3, 2, 3, 3)), - new LeftBracket(new Range(3, 3, 3, 4)), - new Space(new Range(3, 4, 3, 5)), - new RightBracket(new Range(3, 5, 3, 6)), - new LeftParenthesis(new Range(3, 6, 3, 7)), - new Word(new Range(3, 7, 3, 7 + 1), '.'), - new Slash(new Range(3, 8, 3, 9)), - new Word(new Range(3, 9, 3, 9 + 4), 'file'), - new NewLine(new Range(3, 13, 3, 14)), - new Word(new Range(4, 1, 4, 1 + 4), 'path'), - new Slash(new Range(4, 5, 4, 6)), - new Word(new Range(4, 6, 4, 6 + 9), 'name.jpeg'), - new Space(new Range(4, 15, 4, 16)), - new RightParenthesis(new Range(4, 16, 4, 17)), - ], - ); - }); - - suite('stop characters inside caption/reference (new lines)', () => { - for (const StopCharacter of [CarriageReturn, NewLine]) { - let characterName = ''; - - if (StopCharacter === CarriageReturn) { - characterName = '\\r'; - } - if (StopCharacter === NewLine) { - characterName = '\\n'; - } - - assert( - characterName !== '', - 'The "characterName" must be set, got "empty line".', - ); - - test(`stop character - "${characterName}"`, async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // stop character inside link caption - `![haa${StopCharacter.symbol}loů](./real/💁/name.png)`, - // stop character inside link reference - `![ref text](/etc/pat${StopCharacter.symbol}h/to/file.webp)`, - // stop character between line caption and link reference is disallowed - `![text]${StopCharacter.symbol}(/etc/ path/file.jpeg)`, - ]; - - - await test.run( - inputLines, - [ - // `1st` input line - new ExclamationMark(new Range(1, 1, 1, 2)), - new LeftBracket(new Range(1, 2, 1, 3)), - new Word(new Range(1, 3, 1, 3 + 3), 'haa'), - new NewLine(new Range(1, 6, 1, 7)), // a single CR token is treated as a `new line` - new Word(new Range(2, 1, 2, 1 + 3), 'loů'), - new RightBracket(new Range(2, 4, 2, 5)), - new LeftParenthesis(new Range(2, 5, 2, 6)), - new Word(new Range(2, 6, 2, 6 + 1), '.'), - new Slash(new Range(2, 7, 2, 8)), - new Word(new Range(2, 8, 2, 8 + 4), 'real'), - new Slash(new Range(2, 12, 2, 13)), - new Word(new Range(2, 13, 2, 13 + 2), '💁'), - new Slash(new Range(2, 15, 2, 16)), - new Word(new Range(2, 16, 2, 16 + 8), 'name.png'), - new RightParenthesis(new Range(2, 24, 2, 25)), - new NewLine(new Range(2, 25, 2, 26)), - // `2nd` input line - new ExclamationMark(new Range(3, 1, 3, 2)), - new LeftBracket(new Range(3, 2, 3, 3)), - new Word(new Range(3, 3, 3, 3 + 3), 'ref'), - new Space(new Range(3, 6, 3, 7)), - new Word(new Range(3, 7, 3, 7 + 4), 'text'), - new RightBracket(new Range(3, 11, 3, 12)), - new LeftParenthesis(new Range(3, 12, 3, 13)), - new Slash(new Range(3, 13, 3, 14)), - new Word(new Range(3, 14, 3, 14 + 3), 'etc'), - new Slash(new Range(3, 17, 3, 18)), - new Word(new Range(3, 18, 3, 18 + 3), 'pat'), - new NewLine(new Range(3, 21, 3, 22)), // a single CR token is treated as a `new line` - new Word(new Range(4, 1, 4, 1 + 1), 'h'), - new Slash(new Range(4, 2, 4, 3)), - new Word(new Range(4, 3, 4, 3 + 2), 'to'), - new Slash(new Range(4, 5, 4, 6)), - new Word(new Range(4, 6, 4, 6 + 9), 'file.webp'), - new RightParenthesis(new Range(4, 15, 4, 16)), - new NewLine(new Range(4, 16, 4, 17)), - // `3nd` input line - new ExclamationMark(new Range(5, 1, 5, 2)), - new LeftBracket(new Range(5, 2, 5, 3)), - new Word(new Range(5, 3, 5, 3 + 4), 'text'), - new RightBracket(new Range(5, 7, 5, 8)), - new NewLine(new Range(5, 8, 5, 9)), // a single CR token is treated as a `new line` - new LeftParenthesis(new Range(6, 1, 6, 2)), - new Slash(new Range(6, 2, 6, 3)), - new Word(new Range(6, 3, 6, 3 + 3), 'etc'), - new Slash(new Range(6, 6, 6, 7)), - new Space(new Range(6, 7, 6, 8)), - new Word(new Range(6, 8, 6, 8 + 4), 'path'), - new Slash(new Range(6, 12, 6, 13)), - new Word(new Range(6, 13, 6, 13 + 9), 'file.jpeg'), - new RightParenthesis(new Range(6, 22, 6, 23)), - ], - ); - }); - } - }); - - /** - * Same as above but these stop characters do not move the caret to the next line. - */ - suite('stop characters inside caption/reference (same line)', () => { - for (const stopCharacter of [VerticalTab, FormFeed]) { - let characterName = ''; - - if (stopCharacter === VerticalTab) { - characterName = '\\v'; - } - if (stopCharacter === FormFeed) { - characterName = '\\f'; - } - - assert( - characterName !== '', - 'The "characterName" must be set, got "empty line".', - ); - - test(`stop character - "${characterName}"`, async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputLines = [ - // stop character inside link caption - `![haa${stopCharacter.symbol}loů](./real/💁/name)`, - // stop character inside link reference - `![ref text](/etc/pat${stopCharacter.symbol}h/to/file.webp)`, - // stop character between line caption and link reference is disallowed - `![text]${stopCharacter.symbol}(/etc/ path/image.gif)`, - ]; - - - await test.run( - inputLines, - [ - // `1st` input line - new ExclamationMark(new Range(1, 1, 1, 2)), - new LeftBracket(new Range(1, 2, 1, 3)), - new Word(new Range(1, 3, 1, 3 + 3), 'haa'), - new stopCharacter(new Range(1, 6, 1, 7)), // <- stop character - new Word(new Range(1, 7, 1, 7 + 3), 'loů'), - new RightBracket(new Range(1, 10, 1, 11)), - new LeftParenthesis(new Range(1, 11, 1, 12)), - new Word(new Range(1, 12, 1, 12 + 1), '.'), - new Slash(new Range(1, 13, 1, 14)), - new Word(new Range(1, 14, 1, 14 + 4), 'real'), - new Slash(new Range(1, 18, 1, 19)), - new Word(new Range(1, 19, 1, 19 + 2), '💁'), - new Slash(new Range(1, 21, 1, 22)), - new Word(new Range(1, 22, 1, 22 + 4), 'name'), - new RightParenthesis(new Range(1, 26, 1, 27)), - new NewLine(new Range(1, 27, 1, 28)), - // `2nd` input line - new ExclamationMark(new Range(2, 1, 2, 2)), - new LeftBracket(new Range(2, 2, 2, 3)), - new Word(new Range(2, 3, 2, 3 + 3), 'ref'), - new Space(new Range(2, 6, 2, 7)), - new Word(new Range(2, 7, 2, 7 + 4), 'text'), - new RightBracket(new Range(2, 11, 2, 12)), - new LeftParenthesis(new Range(2, 12, 2, 13)), - new Slash(new Range(2, 13, 2, 14)), - new Word(new Range(2, 14, 2, 14 + 3), 'etc'), - new Slash(new Range(2, 17, 2, 18)), - new Word(new Range(2, 18, 2, 18 + 3), 'pat'), - new stopCharacter(new Range(2, 21, 2, 22)), // <- stop character - new Word(new Range(2, 22, 2, 22 + 1), 'h'), - new Slash(new Range(2, 23, 2, 24)), - new Word(new Range(2, 24, 2, 24 + 2), 'to'), - new Slash(new Range(2, 26, 2, 27)), - new Word(new Range(2, 27, 2, 27 + 9), 'file.webp'), - new RightParenthesis(new Range(2, 36, 2, 37)), - new NewLine(new Range(2, 37, 2, 38)), - // `3nd` input line - new ExclamationMark(new Range(3, 1, 3, 2)), - new LeftBracket(new Range(3, 2, 3, 3)), - new Word(new Range(3, 3, 3, 3 + 4), 'text'), - new RightBracket(new Range(3, 7, 3, 8)), - new stopCharacter(new Range(3, 8, 3, 9)), // <- stop character - new LeftParenthesis(new Range(3, 9, 3, 10)), - new Slash(new Range(3, 10, 3, 11)), - new Word(new Range(3, 11, 3, 11 + 3), 'etc'), - new Slash(new Range(3, 14, 3, 15)), - new Space(new Range(3, 15, 3, 16)), - new Word(new Range(3, 16, 3, 16 + 4), 'path'), - new Slash(new Range(3, 20, 3, 21)), - new Word(new Range(3, 21, 3, 21 + 9), 'image.gif'), - new RightParenthesis(new Range(3, 30, 3, 31)), - ], - ); - }); - } - }); - }); - }); - - suite('comments', () => { - suite('general', () => { - test('base cases', async () => { - const test = testDisposables.add( - new TestMarkdownDecoder(), - ); - - const inputData = [ - // comment with text inside it - '\t', - // comment with a link inside - 'some text and more text ', - // comment new lines inside it - ' usual text follows', - // an empty comment - '\t\t', - // comment that was not closed properly - 'haalo\t'), - new NewLine(new Range(1, 22, 1, 23)), - // `2nd` - new Word(new Range(2, 1, 2, 5), 'some'), - new Space(new Range(2, 5, 2, 6)), - new Word(new Range(2, 6, 2, 10), 'text'), - new MarkdownComment(new Range(2, 10, 2, 10 + 46), ''), - new Space(new Range(2, 56, 2, 57)), - new Word(new Range(2, 57, 2, 60), 'and'), - new Space(new Range(2, 60, 2, 61)), - new Word(new Range(2, 61, 2, 65), 'more'), - new Space(new Range(2, 65, 2, 66)), - new Word(new Range(2, 66, 2, 70), 'text'), - new Space(new Range(2, 70, 2, 71)), - new NewLine(new Range(2, 71, 2, 72)), - // `3rd` - new MarkdownComment(new Range(3, 1, 3 + 3, 1 + 13), ''), - new Space(new Range(6, 14, 6, 15)), - new Word(new Range(6, 15, 6, 15 + 5), 'usual'), - new Space(new Range(6, 20, 6, 21)), - new Word(new Range(6, 21, 6, 21 + 4), 'text'), - new Space(new Range(6, 25, 6, 26)), - new Word(new Range(6, 26, 6, 26 + 7), 'follows'), - new NewLine(new Range(6, 33, 6, 34)), - // `4rd` - new Tab(new Range(7, 1, 7, 2)), - new MarkdownComment(new Range(7, 2, 7, 2 + 7), ''), - new Tab(new Range(7, 9, 7, 10)), - new NewLine(new Range(7, 10, 7, 11)), - // `5th` - new Word(new Range(8, 1, 8, 6), 'haalo'), - new Tab(new Range(8, 6, 8, 7)), - new MarkdownComment(new Range(8, 7, 8, 7 + 40), '>', - // comment contains `<[]>` brackets and `!` - '\t\t', - // comment contains `\t\t', - // comment contains `'), - new RightAngleBracket(new Range(1, 19, 1, 20)), - new NewLine(new Range(1, 20, 1, 21)), - // `2nd` - new MarkdownComment(new Range(2, 1, 2, 1 + 21), ''), - new Tab(new Range(2, 22, 2, 23)), - new Tab(new Range(2, 23, 2, 24)), - new NewLine(new Range(2, 24, 2, 25)), - // `3rd` - new VerticalTab(new Range(3, 1, 3, 2)), - new MarkdownComment(new Range(3, 2, 3 + 3, 1 + 7), ''), - new Tab(new Range(6, 8, 6, 9)), - new Tab(new Range(6, 9, 6, 10)), - new NewLine(new Range(6, 10, 6, 11)), - // `4rd` - new Space(new Range(7, 1, 7, 2)), - // note! comment does not have correct closing `-->`, hence the comment extends - // to the end of the text, and therefore includes the \t\v\f and space at the end - new MarkdownComment(new Range(7, 2, 8, 1 + 12), ' ', - ' < !-- світ -->\t', - '\v\f', - '`, hence the comment extends - // to the end of the text, and therefore includes the `space` at the end - new MarkdownComment(new Range(4, 1, 4, 1 + 15), ' const result = await renderReleaseNotesMarkdown(content, extensionService, languageService, instantiationService.createInstance(SimpleSettingRenderer)); await assertSnapshot(result.toString()); }); + + test('Should render code settings', async () => { + // Stub preferences service with a known setting so the SimpleSettingRenderer treats it as valid + const testSettingId = 'editor.wordWrap'; + instantiationService.stub(IPreferencesService, >{ + _serviceBrand: undefined, + onDidDefaultSettingsContentChanged: new Emitter().event, + userSettingsResource: undefined as any, + workspaceSettingsResource: null, + getFolderSettingsResource: () => null, + createPreferencesEditorModel: async () => null, + getDefaultSettingsContent: () => undefined, + hasDefaultSettingsContent: () => false, + createSettings2EditorModel: () => { throw new Error('not needed'); }, + openPreferences: async () => undefined, + openRawDefaultSettings: async () => undefined, + openSettings: async () => undefined, + openApplicationSettings: async () => undefined, + openUserSettings: async () => undefined, + openRemoteSettings: async () => undefined, + openWorkspaceSettings: async () => undefined, + openFolderSettings: async () => undefined, + openGlobalKeybindingSettings: async () => undefined, + openDefaultKeybindingsFile: async () => undefined, + openLanguageSpecificSettings: async () => undefined, + getEditableSettingsURI: async () => null, + getSetting: (id: string) => { + if (id === testSettingId) { + // Provide the minimal fields accessed by SimpleSettingRenderer + return { + key: testSettingId, + value: 'off', + type: 'string' + }; + } + return undefined; + }, + createSplitJsonEditorInput: () => { throw new Error('not needed'); } + }); + + const content = `Here is a setting: \`setting(${testSettingId}:on)\` and another \`setting(${testSettingId}:off)\``; + const result = await renderReleaseNotesMarkdown(content, extensionService, languageService, instantiationService.createInstance(SimpleSettingRenderer)); + await assertSnapshot(result.toString()); + }); }); From 5349099a9175716c04176c157d77ec9ce2beab55 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Tue, 30 Sep 2025 14:52:32 +0200 Subject: [PATCH 0636/4355] fix: memory leak in folding --- src/vs/editor/contrib/folding/browser/folding.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index e51fc289e8d..49b243f0733 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -242,6 +242,7 @@ export class FoldingController extends Disposable implements IEditorContribution this.localToDispose.add(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr))); this.updateScheduler = new Delayer(this.updateDebounceInfo.get(model)); + this.localToDispose.add(this.updateScheduler); this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200); this.localToDispose.add(this.cursorChangedScheduler); From d0891116dbd71400abc894545fc5d788005e25cc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Sep 2025 15:40:53 +0200 Subject: [PATCH 0637/4355] update distro (#269074) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 228fd513a9f..3f79dc1be00 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "9e823daeded19d4a7c8f7e3ba36ca5d3c68d8350", + "distro": "8b551ae26e4dadc3f6ece34bbbc3feea81ce4a97", "author": { "name": "Microsoft Corporation" }, @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From 1445f020061a5d8475240fbb437435ff389ebfbb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 30 Sep 2025 16:04:42 +0200 Subject: [PATCH 0638/4355] enable mcp gallery in stable (#269080) --- src/vs/platform/mcp/common/mcpGalleryManifestService.ts | 2 +- .../mcp/electron-browser/mcpGalleryManifestService.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index cdd12bfc1fa..7557e32fea0 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -19,7 +19,7 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery } constructor( - @IProductService protected readonly productService: IProductService, + @IProductService private readonly productService: IProductService, ) { super(); } diff --git a/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts index 5bc775d4d86..59024b55ed3 100644 --- a/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts +++ b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts @@ -55,10 +55,6 @@ export class WorkbenchMcpGalleryManifestService extends McpGalleryManifestServic } private async doGetMcpGalleryManifest(): Promise { - if (this.productService.quality === 'stable') { - return; - } - await this.getAndUpdateMcpGalleryManifest(); this._register(this.configurationService.onDidChangeConfiguration(e => { From 3f527562bb6fc163c2c2a51f4c0790836daf676b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 30 Sep 2025 17:09:13 +0200 Subject: [PATCH 0639/4355] Fix cyclic dependency reporting in chat module (#269095) * fix cyclic dependency in chat land * make sure cyclic dependencies are correctly reported when running complie-client task --- build/lib/tsb/builder.js | 2 ++ build/lib/tsb/builder.ts | 2 ++ src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 3 +-- .../contrib/chat/browser/actions/chatSessionActions.ts | 3 +-- .../contrib/chat/browser/chatSessions.contribution.ts | 3 +-- .../chat/browser/chatSessions/view/chatSessionsView.ts | 4 +--- src/vs/workbench/contrib/chat/common/constants.ts | 2 ++ 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 6267db35f0b..f4f60cf8521 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -402,7 +402,9 @@ function createTypeScriptBuilder(config, projectFile, cmd) { messageText: `CYCLIC dependency: ${error}` }); } + delete oldErrors[filename]; newErrors[filename] = cyclicDepErrors; + cyclicDepErrors.forEach(d => onError(d)); } }).then(() => { // store the build versions to not rebuilt the next time diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 79ae06d73f7..cc7a2b244b6 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -440,7 +440,9 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str messageText: `CYCLIC dependency: ${error}` }); } + delete oldErrors[filename]; newErrors[filename] = cyclicDepErrors; + cyclicDepErrors.forEach(d => onError(d)); } }).then(() => { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d4a9472ad97..38d0a142e8b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -66,14 +66,13 @@ import { IChatSessionItem, IChatSessionsService } from '../../common/chatSession import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind, VIEWLET_ID } from '../../common/constants.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from '../chatEditorInput.js'; -import { VIEWLET_ID } from '../chatSessions/view/chatSessionsView.js'; import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 98ae7370c67..54ed47797b4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -28,7 +28,7 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; -import { ChatConfiguration } from '../../common/constants.js'; +import { ChatConfiguration, VIEWLET_ID } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -36,7 +36,6 @@ import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSe import { ChatViewPane } from '../chatViewPane.js'; import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { VIEWLET_ID } from '../chatSessions/view/chatSessionsView.js'; export interface IChatSessionContext { sessionId: string; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 81cd2ecd20d..bf42f545729 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -23,11 +23,10 @@ import { IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; -import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; +import { ChatAgentLocation, ChatModeKind, VIEWLET_ID } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; -import { VIEWLET_ID } from './chatSessions/view/chatSessionsView.js'; const extensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatSessions', diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index f18d99b0945..017bc082ea7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -25,14 +25,12 @@ import { IChatEntitlementService } from '../../../../../services/chat/common/cha import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; import { IChatSessionsService, IChatSessionItemProvider, IChatSessionsExtensionPoint } from '../../../common/chatSessionsService.js'; -import { ChatConfiguration } from '../../../common/constants.js'; +import { ChatConfiguration, VIEWLET_ID } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { SessionsViewPane } from './sessionsViewPane.js'; -export const VIEWLET_ID = 'workbench.view.chat.sessions'; - export class ChatSessionsView extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatSessions'; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index e5b5b6ca3a7..4bf8c6b0bef 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -86,3 +86,5 @@ export namespace ChatAgentLocation { export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); export const TodoListWidgetPositionSettingId = 'chat.todoListWidget.position'; + +export const VIEWLET_ID = 'workbench.view.chat.sessions'; From 9fa2156a37d8e863752c49495c25522c0a68bee9 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Tue, 30 Sep 2025 16:20:46 +0100 Subject: [PATCH 0640/4355] Update status bar item hover colors for improved visibility and accessibility --- .../parts/statusbar/media/statusbarpart.css | 4 ++-- src/vs/workbench/common/theme.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index a21f003405a..5f69e79224f 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -177,8 +177,8 @@ outline-offset: -1px; } -.monaco-workbench:not(.hc-light):not(.hc-black) .part.statusbar > .items-container > .statusbar-item a:hover:not(.disabled) { - background-color: var(--vscode-statusBarItem-hoverBackground); +.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover:not(.disabled) { + background-color: var(--vscode-statusBarItem-hoverBackground) !important; } /** Status bar entry item kinds */ diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 900b771e3bc..143a09c1bf1 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -336,18 +336,18 @@ export const STATUS_BAR_ITEM_FOCUS_BORDER = registerColor('statusBarItem.focusBo export const STATUS_BAR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.hoverBackground', { dark: Color.white.transparent(0.12), - light: Color.white.transparent(0.12), - hcDark: Color.white.transparent(0.12), - hcLight: Color.black.transparent(0.12) + light: Color.black.transparent(0.12), + hcDark: Color.black, + hcLight: Color.white }, localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.hoverForeground', STATUS_BAR_FOREGROUND, localize('statusBarItemHoverForeground', "Status bar item foreground color when hovering. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_COMPACT_HOVER_BACKGROUND = registerColor('statusBarItem.compactHoverBackground', { - dark: Color.white.transparent(0.20), - light: Color.white.transparent(0.20), - hcDark: Color.white.transparent(0.20), - hcLight: Color.black.transparent(0.20) + dark: Color.white.transparent(0.12), + light: Color.black.transparent(0.12), + hcDark: Color.black, + hcLight: Color.white }, localize('statusBarItemCompactHoverBackground', "Status bar item background color when hovering an item that contains two hovers. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_FOREGROUND = registerColor('statusBarItem.prominentForeground', STATUS_BAR_FOREGROUND, localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); From 836137bd808761f902457a7b96585b6b7b99091a Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Tue, 30 Sep 2025 16:32:23 +0100 Subject: [PATCH 0641/4355] Update status bar item hover background colors for improved visibility --- extensions/theme-defaults/themes/dark_modern.json | 1 + extensions/theme-defaults/themes/light_modern.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index 27738936b27..c7e405eaff0 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -88,6 +88,7 @@ "sideBarTitle.foreground": "#CCCCCC", "statusBar.background": "#181818", "statusBar.border": "#2B2B2B", + "statusBarItem.hoverBackground": "#F1F1F133", "statusBar.debuggingBackground": "#0078D4", "statusBar.debuggingForeground": "#FFFFFF", "statusBar.focusBorder": "#0078D4", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index 2251c4bac30..2440ba3802a 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -104,7 +104,7 @@ "statusBar.background": "#F8F8F8", "statusBar.foreground": "#3B3B3B", "statusBar.border": "#E5E5E5", - "statusBarItem.hoverBackground": "#B8B8B850", + "statusBarItem.hoverBackground": "#1F1F1F11", "statusBarItem.compactHoverBackground": "#CCCCCC", "statusBar.debuggingBackground": "#FD716C", "statusBar.debuggingForeground": "#000000", From e9bf40d9903156636fa944e46e595a9d1ad399aa Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Tue, 30 Sep 2025 16:40:29 +0100 Subject: [PATCH 0642/4355] Add hover foreground color for status bar items in light and dark themes --- extensions/theme-defaults/themes/dark_modern.json | 1 + extensions/theme-defaults/themes/light_modern.json | 1 + 2 files changed, 2 insertions(+) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index c7e405eaff0..51e0f371c27 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -89,6 +89,7 @@ "statusBar.background": "#181818", "statusBar.border": "#2B2B2B", "statusBarItem.hoverBackground": "#F1F1F133", + "statusBarItem.hoverForeground": "#FFFFFF", "statusBar.debuggingBackground": "#0078D4", "statusBar.debuggingForeground": "#FFFFFF", "statusBar.focusBorder": "#0078D4", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index 2440ba3802a..1576ef7e5c1 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -105,6 +105,7 @@ "statusBar.foreground": "#3B3B3B", "statusBar.border": "#E5E5E5", "statusBarItem.hoverBackground": "#1F1F1F11", + "statusBarItem.hoverForeground": "#000000", "statusBarItem.compactHoverBackground": "#CCCCCC", "statusBar.debuggingBackground": "#FD716C", "statusBar.debuggingForeground": "#000000", From f8207b7f491412dfe5a88ec0d7d816ad3c97ac94 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 30 Sep 2025 12:38:12 -0400 Subject: [PATCH 0643/4355] discourage agent from using any/unknown casts in instructions (#269122) add rule about as any/unknown --- .github/copilot-instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4c7aff83c70..29bac72ad52 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -133,3 +133,4 @@ function f(x: number, y: string): void { } - Look for existing test patterns before creating new structures - Use `describe` and `test` consistently with existing patterns - If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task +- Do not use `any` or `unknown` as the type for variables, parameters, or return values unless absolutely necessary. If they need type annotations, they should have proper types or interfaces defined. From c2527c9dae1433f3483433a118c5a707459e24d2 Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov Date: Tue, 30 Sep 2025 21:06:38 +0300 Subject: [PATCH 0644/4355] Improve canSetExpressionValue check (#268952) --- src/vs/workbench/contrib/debug/browser/variablesView.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index db0a162f139..2130f6831f5 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -230,6 +230,10 @@ export class VariablesView extends ViewPane implements IDebugViewWithVariables { return !!e.treeItem.canEdit; } + if (!session.capabilities?.supportsSetVariable && !session.capabilities?.supportsSetExpression) { + return false; + } + return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy; } From 99bbe0707b48a1b4bdc84345066ed51671e4d6e7 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:37:59 -0700 Subject: [PATCH 0645/4355] Fix name in package-lock Forgot to check this in too after fixing the main `package.json` --- extensions/mermaid-chat-features/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json index 38a52f7fe20..09481d2b835 100644 --- a/extensions/mermaid-chat-features/package-lock.json +++ b/extensions/mermaid-chat-features/package-lock.json @@ -1,11 +1,11 @@ { - "name": "marmaid-chat-features", + "name": "mermaid-chat-features", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "marmaid-chat-features", + "name": "mermaid-chat-features", "version": "1.0.0", "license": "MIT", "dependencies": { From c3a4ffbcf124066bf1699e229891b91564475a20 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 30 Sep 2025 13:38:40 -0700 Subject: [PATCH 0646/4355] mcp: update with icons added to resource templates (#269199) Followup addition from https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1565 --- .../contrib/mcp/browser/mcpResourceQuickAccess.ts | 6 ++++-- src/vs/workbench/contrib/mcp/common/mcpIcons.ts | 12 ++++++++---- src/vs/workbench/contrib/mcp/common/mcpServer.ts | 5 +++-- src/vs/workbench/contrib/mcp/common/mcpTypes.ts | 1 + .../contrib/mcp/common/modelContextProtocol.ts | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts index 00b9f4caf4f..60a21fa126e 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts @@ -37,22 +37,24 @@ export class McpResourcePickHelper { } public static item(resource: IMcpResource | IMcpResourceTemplate): IQuickPickItem { + const icon = resource.icons.getUrl(22); + const iconPath = icon ? { dark: icon, light: icon } : undefined; if (isMcpResourceTemplate(resource)) { return { id: resource.template.template, label: resource.title || resource.name, description: resource.description, detail: localize('mcp.resource.template', 'Resource template: {0}', resource.template.template), + iconPath, }; } - const icon = resource.icons.getUrl(22); return { id: resource.uri.toString(), label: resource.title || resource.name, description: resource.description, detail: resource.mcpUri + (resource.sizeInBytes !== undefined ? ' (' + ByteSize.formatSize(resource.sizeInBytes) + ')' : ''), - iconPath: icon ? { dark: icon, light: icon } : undefined, + iconPath, }; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpIcons.ts b/src/vs/workbench/contrib/mcp/common/mcpIcons.ts index dd88b812fb6..fa0a3c6b149 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpIcons.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpIcons.ts @@ -25,6 +25,7 @@ interface IIcon { sizes: { width: number; height: number }[]; } +export type ParsedMcpIcons = IIcon[]; export type StoredMcpIcons = Dto[]; @@ -68,8 +69,8 @@ function validateIcon(icon: MCP.Icon, launch: McpServerLaunch, logger: ILogger): return; } -export function parseAndValidateMcpIcon(icons: MCP.Icons, launch: McpServerLaunch, logger: ILogger): StoredMcpIcons { - const result: StoredMcpIcons = []; +export function parseAndValidateMcpIcon(icons: MCP.Icons, launch: McpServerLaunch, logger: ILogger): ParsedMcpIcons { + const result: ParsedMcpIcons = []; for (const icon of icons.icons || []) { const uri = validateIcon(icon, launch, logger); if (!uri) { @@ -92,9 +93,12 @@ export function parseAndValidateMcpIcon(icons: MCP.Icons, launch: McpServerLaunc } export class McpIcons implements IMcpIcons { - public static fromStored(icons: StoredMcpIcons | undefined) { - return new McpIcons(icons?.map(i => ({ src: URI.revive(i.src), sizes: i.sizes })) || []); + return McpIcons.fromParsed(icons?.map(i => ({ src: URI.revive(i.src), sizes: i.sizes }))); + } + + public static fromParsed(icons: ParsedMcpIcons | undefined) { + return new McpIcons(icons || []); } protected constructor(private readonly _icons: IIcon[]) { } diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 1957600fb74..c86cbc1c5c7 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -489,7 +489,7 @@ export class McpServer extends Disposable implements IMcpServer { return new AsyncIterableProducer(async emitter => { await McpServer.callOn(this, async (handler) => { for await (const resource of handler.listResourcesIterable({}, cts.token)) { - emitter.emitOne(resource.map(r => new McpResource(this, r, McpIcons.fromStored(this._parseIcons(r))))); + emitter.emitOne(resource.map(r => new McpResource(this, r, McpIcons.fromParsed(this._parseIcons(r))))); if (cts.token.isCancellationRequested) { return; } @@ -501,7 +501,7 @@ export class McpServer extends Disposable implements IMcpServer { public resourceTemplates(token?: CancellationToken): Promise { return McpServer.callOn(this, async (handler) => { const templates = await handler.listResourceTemplates({}, token); - return templates.map(t => new McpResourceTemplate(this, t)); + return templates.map(t => new McpResourceTemplate(this, t, McpIcons.fromParsed(this._parseIcons(t)))); }, token); } @@ -1074,6 +1074,7 @@ class McpResourceTemplate implements IMcpResourceTemplate { constructor( private readonly _server: McpServer, private readonly _definition: MCP.ResourceTemplate, + public readonly icons: IMcpIcons, ) { this.name = _definition.name; this.description = _definition.description; diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 376d47959ff..f566d17a9a9 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -356,6 +356,7 @@ export interface IMcpResourceTemplate { readonly description?: string; readonly mimeType?: string; readonly template: UriTemplate; + readonly icons: IMcpIcons; /** Gets string completions for the given template part. */ complete(templatePart: string, prefix: string, alreadyResolved: Record, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts index 663f7247633..3b19f8ff44b 100644 --- a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts +++ b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts @@ -635,7 +635,7 @@ export namespace MCP {/* JSON-RPC types */ /** * A template description for resources available on the server. */ - export interface ResourceTemplate extends BaseMetadata { + export interface ResourceTemplate extends BaseMetadata, Icons { /** * A URI template (according to RFC 6570) that can be used to construct resource URIs. * From b3b51ab9ba665fa277101f11a46fe72f80cc51d4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:02:57 -0700 Subject: [PATCH 0647/4355] Add insert and del elements to allowed chat html tags Fixes #269139 Also adds `s` (general strike through) --- src/vs/base/browser/domSanitize.ts | 1 + src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 9db24ef362f..9b88e3c0dfa 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -54,6 +54,7 @@ export const basicMarkupHtmlTags = Object.freeze([ 'rp', 'rt', 'ruby', + 's', 'samp', 'small', 'small', diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index 69a6c324d84..01331bca22e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -23,6 +23,7 @@ export const allowedChatMarkdownHtmlTags = Object.freeze([ 'blockquote', 'br', 'code', + 'del', 'em', 'h1', 'h2', @@ -32,10 +33,12 @@ export const allowedChatMarkdownHtmlTags = Object.freeze([ 'h6', 'hr', 'i', + 'ins', 'li', 'ol', 'p', 'pre', + 's', 'strong', 'sub', 'sup', From 0e118a0c0c89dcfcabf735b5de5e033a8d9988e7 Mon Sep 17 00:00:00 2001 From: Eleanor Boyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:12:40 -0700 Subject: [PATCH 0648/4355] move team members to variable name and add new team members --- .vscode/notebooks/my-endgame.github-issues | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index d2cc7905a82..b8785cb1cd2 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -9,6 +9,11 @@ "language": "github-issues", "value": "$MILESTONE=milestone:\"September 2025\"\n\n$MINE=assignee:@me" }, + { + "kind": 2, + "language": "github-issues", + "value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya" + }, { "kind": 1, "language": "markdown", @@ -157,7 +162,7 @@ { "kind": 2, "language": "github-issues", - "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:*out-of-scope -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan" + "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:*out-of-scope -label:error-telemetry -label:verification-steps-needed -label:verification-found $NOT_TEAM_MEMBERS" }, { "kind": 1, From ff388f3ba8bea80402ffd1e477284779abb78f4d Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Tue, 30 Sep 2025 23:36:35 +0200 Subject: [PATCH 0649/4355] Adopt @vscode/proxy-agent 0.35.0 --- package-lock.json | 8 ++++---- package.json | 4 ++-- remote/package-lock.json | 8 ++++---- remote/package.json | 2 +- src/vs/platform/request/common/request.ts | 12 ++++++++++++ src/vs/workbench/api/node/proxyResolver.ts | 4 ++++ 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index d370bb6683a..ae95923f2b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", @@ -2979,9 +2979,9 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.34.0.tgz", - "integrity": "sha512-LrX5mb+0vgvGQ/1jLvpsd4tUzlCVYNjvu+vvPx+yV2AvyXXnRQj/Qom1Fiavw9Mfmxw3+AHfzZ73tXwTMCfEdQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.35.0.tgz", + "integrity": "sha512-25BxUVwWkRVdVKHIekIz5pshmPprkLmtyPteRIhZIKJF++5u1nLETeHO+a+E957UXnH+YiD2Hh9g9DxVnuBRxA==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/package.json b/package.json index 3f79dc1be00..1648fbe73d6 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} diff --git a/remote/package-lock.json b/remote/package-lock.json index 0512291031e..399f6425e3e 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -13,7 +13,7 @@ "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/tree-sitter-wasm": "^0.1.4", @@ -133,9 +133,9 @@ "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" }, "node_modules/@vscode/proxy-agent": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.34.0.tgz", - "integrity": "sha512-LrX5mb+0vgvGQ/1jLvpsd4tUzlCVYNjvu+vvPx+yV2AvyXXnRQj/Qom1Fiavw9Mfmxw3+AHfzZ73tXwTMCfEdQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.35.0.tgz", + "integrity": "sha512-25BxUVwWkRVdVKHIekIz5pshmPprkLmtyPteRIhZIKJF++5u1nLETeHO+a+E957UXnH+YiD2Hh9g9DxVnuBRxA==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/remote/package.json b/remote/package.json index 9af773e9a1c..24a4c07c9c0 100644 --- a/remote/package.json +++ b/remote/package.json @@ -8,7 +8,7 @@ "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/tree-sitter-wasm": "^0.1.4", diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 1fbb31fe02f..53db97a5efb 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -156,6 +156,7 @@ export const USER_LOCAL_AND_REMOTE_SETTINGS = [ 'http.systemCertificates', 'http.experimental.systemCertificatesV2', 'http.fetchAdditionalSupport', + 'http.experimental.networkInterfaceCheckInterval', ]; let proxyConfiguration: IConfigurationNode[] = []; @@ -269,6 +270,17 @@ function registerProxyConfigurations(useHostProxy = true, useHostProxyDefault = default: true, markdownDescription: localize('fetchAdditionalSupport', "Controls whether Node.js' fetch implementation should be extended with additional support. Currently proxy support ({1}) and system certificates ({2}) are added when the corresponding settings are enabled. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.", '`#http.useLocalProxyConfiguration#`', '`#http.proxySupport#`', '`#http.systemCertificates#`'), restricted: true + }, + 'http.experimental.networkInterfaceCheckInterval': { + type: 'number', + default: 300, + minimum: -1, + tags: ['experimental'], + markdownDescription: localize('networkInterfaceCheckInterval', "Controls the interval in seconds for checking network interface changes to invalidate the proxy cache. Set to -1 to disable. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.", '`#http.useLocalProxyConfiguration#`'), + restricted: true, + experiment: { + mode: 'auto' + } } } } diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index e43d60b4e07..e69b6826d6a 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -72,6 +72,10 @@ export function connectProxyResolver( }, proxyResolveTelemetry: () => { }, isUseHostProxyEnabled, + getNetworkInterfaceCheckInterval: () => { + const intervalSeconds = getExtHostConfigValue(configProvider, isRemote, 'http.experimental.networkInterfaceCheckInterval', 300); + return intervalSeconds * 1000; + }, loadAdditionalCertificates: async () => { const promises: Promise[] = []; if (initData.remote.isRemote) { From a5b622f59c69b2d5522d2e514ea2a2bda7860633 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Tue, 30 Sep 2025 23:37:16 +0200 Subject: [PATCH 0650/4355] Add telemetry for resolving proxies --- src/vs/workbench/api/node/proxyResolver.ts | 71 +++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index e69b6826d6a..6add965e89d 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -44,8 +44,9 @@ export function connectProxyResolver( const fallbackToLocalKerberos = useHostProxyDefault; const loadLocalCertificates = useHostProxyDefault; const isUseHostProxyEnabled = () => !isRemote || configProvider.getConfiguration('http').get('useLocalProxyConfiguration', useHostProxyDefault); + const timedResolveProxy = createTimedResolveProxy(extHostWorkspace, mainThreadTelemetry); const params: ProxyAgentParams = { - resolveProxy: url => extHostWorkspace.resolveProxy(url), + resolveProxy: timedResolveProxy, lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostWorkspace, extHostLogService, mainThreadTelemetry, configProvider, {}, {}, initData.remote.isRemote, fallbackToLocalKerberos), getProxyURL: () => getExtHostConfigValue(configProvider, isRemote, 'http.proxy'), getProxySupport: () => getExtHostConfigValue(configProvider, isRemote, 'http.proxySupport') || 'off', @@ -240,6 +241,74 @@ function recordFetchFeatureUse(mainThreadTelemetry: MainThreadTelemetryShape, fe } } +type ProxyResolveStatsClassification = { + owner: 'chrmarti'; + comment: 'Performance statistics for proxy resolution'; + count: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Number of proxy resolution calls' }; + totalDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Total time spent in proxy resolution (ms)' }; + minDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Minimum resolution time (ms)' }; + maxDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Maximum resolution time (ms)' }; + avgDuration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Average resolution time (ms)' }; +}; + +type ProxyResolveStatsEvent = { + count: number; + totalDuration: number; + minDuration: number; + maxDuration: number; + avgDuration: number; +}; + +const proxyResolveStats = { + count: 0, + totalDuration: 0, + minDuration: Number.MAX_SAFE_INTEGER, + maxDuration: 0, + lastSentTime: 0, +}; + +const telemetryInterval = 60 * 60 * 1000; // 1 hour + +function sendProxyResolveStats(mainThreadTelemetry: MainThreadTelemetryShape) { + if (proxyResolveStats.count > 0) { + const avgDuration = proxyResolveStats.totalDuration / proxyResolveStats.count; + mainThreadTelemetry.$publicLog2('proxyResolveStats', { + count: proxyResolveStats.count, + totalDuration: proxyResolveStats.totalDuration, + minDuration: proxyResolveStats.minDuration, + maxDuration: proxyResolveStats.maxDuration, + avgDuration, + }); + // Reset stats after sending + proxyResolveStats.count = 0; + proxyResolveStats.totalDuration = 0; + proxyResolveStats.minDuration = Number.MAX_SAFE_INTEGER; + proxyResolveStats.maxDuration = 0; + } + proxyResolveStats.lastSentTime = Date.now(); +} + +function createTimedResolveProxy(extHostWorkspace: IExtHostWorkspaceProvider, mainThreadTelemetry: MainThreadTelemetryShape) { + return async (url: string): Promise => { + const startTime = performance.now(); + try { + return await extHostWorkspace.resolveProxy(url); + } finally { + const duration = performance.now() - startTime; + proxyResolveStats.count++; + proxyResolveStats.totalDuration += duration; + proxyResolveStats.minDuration = Math.min(proxyResolveStats.minDuration, duration); + proxyResolveStats.maxDuration = Math.max(proxyResolveStats.maxDuration, duration); + + // Send telemetry if at least an hour has passed since last send + const now = Date.now(); + if (now - proxyResolveStats.lastSentTime >= telemetryInterval) { + sendProxyResolveStats(mainThreadTelemetry); + } + } + }; +} + function createPatchedModules(params: ProxyAgentParams, resolveProxy: ResolveProxyWithRequest) { function mergeModules(module: any, patch: any) { From a06c5152c062ee91fbf795a53d700ac85ddeddb6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 30 Sep 2025 15:52:34 -0700 Subject: [PATCH 0651/4355] mcp: fix servers not starting in working cwd by default (#269224) Closes https://github.com/microsoft/vscode/issues/269215 --- src/vs/workbench/api/node/extHostMcpNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/node/extHostMcpNode.ts b/src/vs/workbench/api/node/extHostMcpNode.ts index 68430e044d0..18fb24be348 100644 --- a/src/vs/workbench/api/node/extHostMcpNode.ts +++ b/src/vs/workbench/api/node/extHostMcpNode.ts @@ -78,7 +78,7 @@ export class NodeExtHostMpcService extends ExtHostMcpService { let child: ChildProcessWithoutNullStreams; try { const home = homedir(); - let cwd = launch.cwd ? untildify(launch.cwd, home) : home; + let cwd = launch.cwd ? untildify(launch.cwd, home) : (defaultCwd?.fsPath || home); if (!path.isAbsolute(cwd)) { cwd = defaultCwd ? path.join(defaultCwd.fsPath, cwd) : path.join(home, cwd); } From d157eb878e92d6e03b57620f5f3162c9494cd960 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 30 Sep 2025 16:34:40 -0700 Subject: [PATCH 0652/4355] Fix alt icon for new chat (#269234) --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 38d0a142e8b..786d97577d3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1056,6 +1056,7 @@ export function registerChatActions() { super({ id: ACTION_ID_OPEN_CHAT, title: localize2('interactiveSession.open', "New Chat Editor"), + icon: Codicon.plus, f1: true, category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, From 9a51fb29319d7874cd4cd7b2ab764b7b81147387 Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:49:47 -0700 Subject: [PATCH 0653/4355] Update keybinding for navigating user chat (#269192) * Update keybinding for user chat * Update binding * Change to Shift + Up/Down * change to ctrl+alt+up/down --------- Co-authored-by: vijay upadya --- .../chat/browser/actions/chatPromptNavigationActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts index e5a3f6c3572..99c5456a65e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts @@ -20,7 +20,7 @@ export function registerChatPromptNavigationActions() { id: 'workbench.action.chat.nextUserPrompt', title: localize2('interactive.nextUserPrompt.label', "Next User Prompt"), keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.RightArrow, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib, when: ChatContextKeys.inChatSession, }, @@ -41,7 +41,7 @@ export function registerChatPromptNavigationActions() { id: 'workbench.action.chat.previousUserPrompt', title: localize2('interactive.previousUserPrompt.label', "Previous User Prompt"), keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow, weight: KeybindingWeight.WorkbenchContrib, when: ChatContextKeys.inChatSession, }, From 6e60e679fc6b6a512edbed9a750d7c5811595861 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 30 Sep 2025 17:00:23 -0700 Subject: [PATCH 0654/4355] newFile as New Chat Editor based on feedback (#269239) --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 786d97577d3..f37a18d83c5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1056,7 +1056,7 @@ export function registerChatActions() { super({ id: ACTION_ID_OPEN_CHAT, title: localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.plus, + icon: Codicon.newFile, f1: true, category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, From 824c18324d74e9ace408007d8e5d6b49faa73ab6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:17:35 +0900 Subject: [PATCH 0655/4355] Polish path detail Noticed in #269187 --- .../platform/terminal/common/terminalPlatformConfiguration.ts | 2 +- .../common/terminalChatAgentToolsConfiguration.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index e6e4d48dbbc..bbd4beec62e 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -83,7 +83,7 @@ const terminalAutomationProfileSchema: IJSONSchema = { required: ['path'], properties: { path: { - description: localize('terminalAutomationProfile.path', 'A single path to a shell executable.'), + description: localize('terminalAutomationProfile.path', 'A path to a shell executable.'), type: ['string'], items: { type: 'string' diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 18656f60cb9..de6b4ac05df 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -51,7 +51,7 @@ const terminalChatAgentProfileSchema: IJSONSchema = { required: ['path'], properties: { path: { - description: localize('terminalChatAgentProfile.path', "A single path to a shell executable."), + description: localize('terminalChatAgentProfile.path', "A path to a shell executable."), type: 'string', }, ...terminalProfileBaseProperties, From f7c81e0442d38e681f70fe929aabc484bf87a47c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:47:19 +0900 Subject: [PATCH 0656/4355] Handle bad chat terminal profile path Fixes #269111 --- .../chatAgentTools/browser/toolTerminalCreator.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index 4bdbd9b0990..3fe7af37d57 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -10,6 +10,7 @@ import { CancellationError } from '../../../../../base/common/errors.js'; import { Event } from '../../../../../base/common/event.js'; import { DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { isNumber, isObject } from '../../../../../base/common/types.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { PromptInputState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; @@ -56,6 +57,15 @@ export class ToolTerminalCreator { shellIntegrationQuality: ShellIntegrationQuality.None, }; + // Ensure the shell process launches successfully + const initResult = await Promise.any([ + instance.processReady, + Event.toPromise(instance.onExit), + ]); + if (!isNumber(initResult) && isObject(initResult) && 'message' in initResult) { + throw new Error(initResult.message); + } + // Wait for shell integration when the fallback case has not been hit or when shell // integration injection is enabled. Note that it's possible for the fallback case to happen // and then for SI to activate again later in the session. From a57de49afb49630950029219b63de55277187bd2 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:11:44 +0200 Subject: [PATCH 0657/4355] Add debug logging to tree view (#269291) * Add debug logging to tree view * Fix an any that Copilot snuck in --- .../workbench/api/common/extHostTreeViews.ts | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 6ebeb903adc..d1cccc56ef5 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -16,7 +16,7 @@ import { asPromise } from '../../../base/common/async.js'; import * as extHostTypes from './extHostTypes.js'; import { isUndefinedOrNull, isString } from '../../../base/common/types.js'; import { equals, coalesce } from '../../../base/common/arrays.js'; -import { ILogService } from '../../../platform/log/common/log.js'; +import { ILogService, LogLevel } from '../../../platform/log/common/log.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { MarkdownString, ViewBadge, DataTransfer } from './extHostTypeConverters.js'; import { IMarkdownString, isMarkdownString } from '../../../base/common/htmlContent.js'; @@ -411,13 +411,16 @@ class ExtHostTreeView extends Disposable { refreshingPromise = null; const childrenToClear = Array.from(this._nodesToClear); this._nodesToClear.clear(); + this._debugLogRefresh('start', elements, childrenToClear); return this._refresh(elements).then(() => { + this._debugLogRefresh('done', elements, undefined); this._clearNodes(childrenToClear); return _promiseCallback(); }).catch(e => { const message = e instanceof Error ? e.message : JSON.stringify(e); this._clearNodes(childrenToClear); this._logService.error(`Unable to refresh tree view ${this._viewId}: ${message}`); + this._debugLogRefresh('error', elements, undefined); return _promiseCallback(); }); }); @@ -428,6 +431,48 @@ class ExtHostTreeView extends Disposable { })); } + private _debugCollectHandles(elements: (T | Root)[]): { changed: string[]; roots: string[]; clearing?: string[] } { + const changed: string[] = []; + for (const el of elements) { + if (!el) { + changed.push(''); + continue; + } + const node = this._nodes.get(el as T); + if (node) { + changed.push(node.item.handle); + } + } + const roots = this._roots?.map(r => r.item.handle) ?? []; + return { changed, roots }; + } + + private _debugLogRefresh(phase: 'start' | 'done' | 'error', elements: (T | Root)[], childrenToClear: TreeNode[] | undefined): void { + if (!this._isDebugLogging()) { + return; + } + try { + const snapshot = this._debugCollectHandles(elements); + if (childrenToClear) { + snapshot.clearing = childrenToClear.map(n => n.item.handle); + } + const changedCount = snapshot.changed.length; + const nodesToClearLen = childrenToClear ? childrenToClear.length : 0; + this._logService.debug(`[TreeView:${this._viewId}] refresh ${phase} changed=${changedCount} nodesToClear=${nodesToClearLen} elements.size=${this._elements.size} nodes.size=${this._nodes.size} handles=${JSON.stringify(snapshot)}`); + } catch { + this._logService.debug(`[TreeView:${this._viewId}] refresh ${phase} (snapshot failed)`); + } + } + + private _isDebugLogging(): boolean { + try { + const level = this._logService.getLevel(); + return (level === LogLevel.Debug) || (level === LogLevel.Trace); + } catch { + return false; + } + } + async getChildren(parentHandle: TreeItemHandle | Root): Promise { const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined; if (parentHandle && !parentElement) { From f1ec7d5d45f8f41ecffe989398e8aab4501c18f0 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 1 Oct 2025 11:18:31 +0200 Subject: [PATCH 0658/4355] send endOfLife event instead of requestResponse event --- src/vs/editor/common/config/editorOptions.ts | 12 +- src/vs/editor/common/languages.ts | 2 + .../browser/model/inlineCompletionsModel.ts | 2 + .../browser/model/inlineCompletionsSource.ts | 119 ++++++++++-------- .../browser/model/inlineSuggestionItem.ts | 4 + .../browser/model/provideInlineCompletions.ts | 10 +- .../inlineCompletions/browser/telemetry.ts | 102 +++++++++++++++ src/vs/monaco.d.ts | 2 + .../browser}/forwardingTelemetryService.ts | 6 +- .../api/browser/mainThreadLanguageFeatures.ts | 93 ++------------ .../aiEditTelemetryServiceImpl.ts | 2 +- .../browser/telemetry/arcTelemetrySender.ts | 2 +- .../telemetry/editSourceTrackingFeature.ts | 2 +- 13 files changed, 209 insertions(+), 149 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts rename src/vs/{workbench/contrib/editTelemetry/browser/telemetry => platform/dataChannel/browser}/forwardingTelemetryService.ts (93%) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 1ba9d3e05f6..c7b7606e5b1 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4409,7 +4409,7 @@ export interface IInlineSuggestOptions { /** * @internal */ - requestInformation?: boolean; + emptyResponseInformation?: boolean; showOnSuggestConflict?: 'always' | 'never' | 'whenSuggestListIsIncomplete'; }; @@ -4449,7 +4449,7 @@ class InlineEditorSuggest extends BaseEditorOption p.providerId).filter(isDefined); return this._source.fetch(availableProviders, providers.label, context, itemToPreserve?.identity, changeSummary.shouldDebounce, userJumpedToActiveCompletion, requestInfo); }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 963339ae086..7bd40c54f08 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -13,19 +13,20 @@ import { derived, IObservable, IObservableWithChange, ITransaction, observableVa import { observableReducerSettable } from '../../../../../base/common/observableInternal/experimental/reducer.js'; import { isDefined } from '../../../../../base/common/types.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../../platform/observable/common/platformObservableUtils.js'; -import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { StringEdit } from '../../../../common/core/edits/stringEdit.js'; import { Position } from '../../../../common/core/position.js'; -import { InlineCompletionEndOfLifeReasonKind, InlineCompletionTriggerKind, InlineCompletionsProvider, ProviderId } from '../../../../common/languages.js'; +import { InlineCompletionEndOfLifeReasonKind, InlineCompletionTriggerKind, InlineCompletionsProvider } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { ITextModel } from '../../../../common/model.js'; import { offsetEditFromContentChanges } from '../../../../common/model/textModelStringEdit.js'; import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js'; import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js'; import { formatRecordableLogEntry, IRecordableEditorLogEntry, IRecordableLogEntry, StructuredLogger } from '../structuredLogger.js'; +import { InlineCompletionEndOfLifeEvent, sendInlineCompletionsEndOfLifeTelemetry } from '../telemetry.js'; import { wait } from '../utils.js'; import { InlineSuggestionIdentity, InlineSuggestionItem } from './inlineSuggestionItem.js'; import { InlineCompletionContextWithoutUuid, InlineSuggestRequestInfo, provideInlineCompletions, runWhenCancelled } from './provideInlineCompletions.js'; @@ -80,11 +81,10 @@ export class InlineCompletionsSource extends Disposable { @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { super(); this._loggingEnabled = observableConfigValue('editor.inlineSuggest.logFetch', false, this._configurationService).recomputeInitiallyAndOnChange(this._store); - this._sendRequestData = observableConfigValue('editor.inlineSuggest.requestInformation', true, this._configurationService).recomputeInitiallyAndOnChange(this._store); + this._sendRequestData = observableConfigValue('editor.inlineSuggest.emptyResponseInformation', true, this._configurationService).recomputeInitiallyAndOnChange(this._store); this._structuredFetchLogger = this._register(this._instantiationService.createInstance(StructuredLogger.cast< { kind: 'start'; requestId: number; context: unknown } & IRecordableEditorLogEntry | { kind: 'end'; error: unknown; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry @@ -184,7 +184,7 @@ export class InlineCompletionsSource extends Disposable { runWhenCancelled(source.token, () => providerResult.cancelAndDispose({ kind: 'tokenCancellation' })); let shouldStopEarly = false; - let invalidSuggestion = false; + let producedSuggestion = false; const suggestions: InlineSuggestionItem[] = []; for await (const list of providerResult.lists) { @@ -195,12 +195,13 @@ export class InlineCompletionsSource extends Disposable { store.add(toDisposable(() => list.removeRef(list.inlineSuggestionsData.length === 0 ? { kind: 'empty' } : { kind: 'notTaken' }))); for (const item of list.inlineSuggestionsData) { + producedSuggestion = true; if (!context.includeInlineEdits && (item.isInlineEdit || item.showInlineEditMenu)) { - invalidSuggestion = true; + item.setNotShownReason('notInlineEditRequested'); continue; } if (!context.includeInlineCompletions && !(item.isInlineEdit || item.showInlineEditMenu)) { - invalidSuggestion = true; + item.setNotShownReason('notInlineCompletionRequested'); continue; } @@ -237,10 +238,17 @@ export class InlineCompletionsSource extends Disposable { } requestResponseInfo.setRequestUuid(providerResult.contextWithUuid.requestUuid); - if (source.token.isCancellationRequested) { - requestResponseInfo.setNoSuggestionReasonIfNotSet('canceled:whileFetching'); - } else if (suggestions.length === 0) { - requestResponseInfo.setNoSuggestionReasonIfNotSet(invalidSuggestion ? 'invalid' : 'noSuggestion'); + if (producedSuggestion) { + requestResponseInfo.setHasProducedSuggestion(); + if (suggestions.length > 0 && source.token.isCancellationRequested) { + suggestions.forEach(s => s.setNotShownReasonIfNotSet('canceled:whileAwaitingOtherProviders')); + } + } else { + if (source.token.isCancellationRequested) { + requestResponseInfo.setNoSuggestionReasonIfNotSet('canceled:whileFetching'); + } else { + requestResponseInfo.setNoSuggestionReasonIfNotSet('noSuggestion'); + } } const remainingTimeToWait = context.earliestShownDateTime - Date.now(); @@ -250,18 +258,16 @@ export class InlineCompletionsSource extends Disposable { if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId || userJumpedToActiveCompletion.get() /* In the meantime the user showed interest for the active completion so dont hide it */) { - requestResponseInfo.setNoSuggestionReasonIfNotSet( + const notShownReason = source.token.isCancellationRequested ? 'canceled:afterMinShowDelay' : this._store.isDisposed ? 'canceled:disposed' : this._textModel.getVersionId() !== request.versionId ? 'canceled:documentChanged' : userJumpedToActiveCompletion.get() ? 'canceled:userJumped' : - 'unknown' - ); + 'unknown'; + suggestions.forEach(s => s.setNotShownReasonIfNotSet(notShownReason)); return false; } - requestResponseInfo.setSuggestions(suggestions); - const endTime = new Date(); this._debounceValue.update(this._textModel, endTime.getTime() - startTime.getTime()); @@ -341,41 +347,54 @@ export class InlineCompletionsSource extends Disposable { return; } - const requestResponseKind = requestResponseInfo.noSuggestionReason ?? ((!!requestResponseInfo.requestUuid && (requestResponseInfo.suggestionCount ?? 0) > 0) ? 'suggestion' : undefined); - if (requestResponseInfo.requestUuid === undefined || requestResponseKind === undefined) { + if (requestResponseInfo.requestUuid === undefined || requestResponseInfo.hasProducedSuggestion) { return; } - type InlineCompletionRequest = { - opportunityId: string; - isExplicit: boolean; - kind: string; - selectedSuggestionInfo: boolean; - suggestionCount: number | undefined; - providers: string; - providersWithSuggestion: string | undefined; - }; - type InlineCompletionRequestClassification = { - owner: 'benibenj'; - comment: 'Information accepting completion items'; - opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the opportunity' }; - isExplicit: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was triggered explicitly by the user' }; - kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of inline completion result' }; - selectedSuggestionInfo: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was requested with information about the selected suggestion' }; - suggestionCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of suggestions provided' }; - providers: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension available for the request' }; - providersWithSuggestion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The providers that provided a suggestion' }; - }; - - this._telemetryService.publicLog2('inlineCompletionsRequestResponse', { + const emptyEndOfLifeEvent: InlineCompletionEndOfLifeEvent = { + id: requestResponseInfo.requestUuid, opportunityId: requestResponseInfo.requestUuid, - isExplicit: requestResponseInfo.context.triggerKind === InlineCompletionTriggerKind.Explicit, - kind: requestResponseKind, + noSuggestionReason: requestResponseInfo.noSuggestionReason ?? 'unknown', + extensionId: 'vscode-core', + extensionVersion: '0.0.0', + groupId: 'empty', + shown: false, + editorType: requestResponseInfo.requestInfo.editorType, + requestReason: requestResponseInfo.requestInfo.reason, + typingInterval: requestResponseInfo.requestInfo.typingInterval, + typingIntervalCharacterCount: requestResponseInfo.requestInfo.typingIntervalCharacterCount, + languageId: requestResponseInfo.requestInfo.languageId, selectedSuggestionInfo: !!requestResponseInfo.context.selectedSuggestionInfo, - suggestionCount: requestResponseInfo.suggestionCount, - providers: requestResponseInfo.providers.map(p => p.providerId?.toString()).filter(isDefined).join(','), - providersWithSuggestion: requestResponseInfo.providersWithSuggestion?.map(providerId => providerId.toString()).join(','), - }); + availableProviders: requestResponseInfo.providers.map(p => p.providerId?.toString()).filter(isDefined).join(','), + ...forwardToChannelIf(requestResponseInfo.providers.some(p => isCopilotLikeExtension(p.providerId?.extensionId))), + timeUntilProviderRequest: undefined, + timeUntilProviderResponse: undefined, + viewKind: undefined, + preceeded: undefined, + error: undefined, + superseded: undefined, + reason: undefined, + correlationId: undefined, + shownDuration: undefined, + shownDurationUncollapsed: undefined, + timeUntilShown: undefined, + partiallyAccepted: undefined, + partiallyAcceptedCountSinceOriginal: undefined, + partiallyAcceptedRatioSinceOriginal: undefined, + partiallyAcceptedCharactersSinceOriginal: undefined, + cursorColumnDistance: undefined, + cursorLineDistance: undefined, + lineCountOriginal: undefined, + lineCountModified: undefined, + characterCountOriginal: undefined, + characterCountModified: undefined, + disjointReplacements: undefined, + sameShapeReplacements: undefined, + notShownReason: undefined, + }; + + const dataChannel = this._instantiationService.createInstance(DataChannelForwardingTelemetryService); + sendInlineCompletionsEndOfLifeTelemetry(dataChannel, emptyEndOfLifeEvent); } public clearSuggestWidgetInlineCompletions(tx: ITransaction): void { @@ -415,8 +434,7 @@ class UpdateRequest { class RequestResponseData { public requestUuid: string | undefined; public noSuggestionReason: string | undefined; - public suggestionCount: number | undefined; - public providersWithSuggestion: ProviderId[] | undefined; + public hasProducedSuggestion = false; constructor( public readonly context: InlineCompletionContextWithoutUuid, @@ -432,9 +450,8 @@ class RequestResponseData { this.noSuggestionReason ??= type; } - setSuggestions(suggestions: InlineSuggestionItem[]) { - this.suggestionCount = suggestions.filter(s => !!s.source.provider.providerId).length; - this.providersWithSuggestion = Array.from(new Set(suggestions.map(s => s.source.provider.providerId))).filter(isDefined); + setHasProducedSuggestion() { + this.hasProducedSuggestion = true; } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index 19c7779c1fd..24c559e7518 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -126,6 +126,10 @@ abstract class InlineSuggestionItemBase { this._data.setIsPreceeded(item.partialAccepts); } + public setNotShownReasonIfNotSet(reason: string): void { + this._data.setNotShownReason(reason); + } + /** * Avoid using this method. Instead introduce getters for the needed properties. */ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index cd9ff269223..60f932fab2b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -16,7 +16,7 @@ import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; import { TextReplacement } from '../../../../common/core/edits/textEdit.js'; -import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletionDisplayLocationKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary } from '../../../../common/languages.js'; +import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletionDisplayLocationKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { ITextModel } from '../../../../common/model.js'; import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js'; @@ -256,6 +256,7 @@ export type InlineSuggestRequestInfo = { reason: string; typingInterval: number; typingIntervalCharacterCount: number; + availableProviders: ProviderId[]; }; export type InlineSuggestProviderRequestInfo = { @@ -283,6 +284,7 @@ export class InlineSuggestData { private _shownDuration: number = 0; private _showUncollapsedStartTime: number | undefined = undefined; private _showUncollapsedDuration: number = 0; + private _notShownReason: string | undefined = undefined; private _viewData: InlineSuggestViewData; private _didReportEndOfLife = false; @@ -390,9 +392,11 @@ export class InlineSuggestData { languageId: this._requestInfo.languageId, requestReason: this._requestInfo.reason, viewKind: this._viewData.viewKind, + notShownReason: this._notShownReason, error: this._viewData.error, typingInterval: this._requestInfo.typingInterval, typingIntervalCharacterCount: this._requestInfo.typingIntervalCharacterCount, + availableProviders: this._requestInfo.availableProviders.map(p => p.toString()).join(','), ...this._viewData.renderData, }; this.source.provider.handleEndOfLifetime(this.source.inlineSuggestions, this.sourceInlineCompletion, reason, summary); @@ -416,6 +420,10 @@ export class InlineSuggestData { this._partiallyAcceptedSinceOriginal = partialAccepts; } + public setNotShownReason(reason: string): void { + this._notShownReason ??= reason; + } + /** * Sets the end of life reason, but does not send the event to the provider yet. */ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts new file mode 100644 index 00000000000..85da9a17e81 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DataChannelForwardingTelemetryService } from '../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; + +export function sendInlineCompletionsEndOfLifeTelemetry(dataChannel: DataChannelForwardingTelemetryService, endOfLLifeSummary: InlineCompletionEndOfLifeEvent) { + dataChannel.publicLog2('inlineCompletion.endOfLife', endOfLLifeSummary); +} + +export type InlineCompletionEndOfLifeEvent = { + // request + /** + * @deprecated To be removed at one point in favor of opportunityId + */ + id: string; + opportunityId: string; + requestReason: string; + editorType: string; + languageId: string; + typingInterval: number; + typingIntervalCharacterCount: number; + selectedSuggestionInfo: boolean; + availableProviders: string; + // response + correlationId: string | undefined; + extensionId: string; + extensionVersion: string; + groupId: string | undefined; + // behavior + shown: boolean; + shownDuration: number | undefined; + shownDurationUncollapsed: number | undefined; + timeUntilShown: number | undefined; + timeUntilProviderRequest: number | undefined; + timeUntilProviderResponse: number | undefined; + reason: 'accepted' | 'rejected' | 'ignored' | undefined; + partiallyAccepted: number | undefined; + partiallyAcceptedCountSinceOriginal: number | undefined; + partiallyAcceptedRatioSinceOriginal: number | undefined; + partiallyAcceptedCharactersSinceOriginal: number | undefined; + preceeded: boolean | undefined; + superseded: boolean | undefined; + error: string | undefined; + notShownReason: string | undefined; + // rendering + viewKind: string | undefined; + cursorColumnDistance: number | undefined; + cursorLineDistance: number | undefined; + lineCountOriginal: number | undefined; + lineCountModified: number | undefined; + characterCountOriginal: number | undefined; + characterCountModified: number | undefined; + disjointReplacements: number | undefined; + sameShapeReplacements: boolean | undefined; + // empty + noSuggestionReason: string | undefined; +}; + +type InlineCompletionsEndOfLifeClassification = { + owner: 'benibenj'; + comment: 'Inline completions ended'; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the inline completion request' }; + opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES' }; + correlationId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The correlation identifier for the inline completion' }; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the extension that contributed the inline completion' }; + extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension that contributed the inline completion' }; + groupId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The group ID of the extension that contributed the inline completion' }; + availableProviders: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The list of available inline completion providers at the time of the request' }; + shown: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was shown to the user' }; + shownDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration for which the inline completion was shown' }; + shownDurationUncollapsed: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration for which the inline completion was shown without collapsing' }; + timeUntilShown: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be shown after the request' }; + timeUntilProviderRequest: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be requested from the provider' }; + timeUntilProviderResponse: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be shown after the request' }; + reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion ending' }; + selectedSuggestionInfo: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was requested with a selected suggestion' }; + partiallyAccepted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How often the inline completion was partially accepted by the user' }; + partiallyAcceptedCountSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How often the inline completion was partially accepted since the original request' }; + partiallyAcceptedRatioSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The percentage of characters accepted since the original request' }; + partiallyAcceptedCharactersSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count accepted since the original request' }; + preceeded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was preceeded by another one' }; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language ID of the document where the inline completion was shown' }; + requestReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion request' }; + error: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The error message if the inline completion failed' }; + typingInterval: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The average typing interval of the user at the moment the inline completion was requested' }; + typingIntervalCharacterCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count involved in the typing interval calculation' }; + superseded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was superseded by another one' }; + editorType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the editor where the inline completion was shown' }; + viewKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of the view where the inline completion was shown' }; + cursorColumnDistance: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The distance in columns from the cursor to the inline suggestion' }; + cursorLineDistance: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The distance in lines from the cursor to the inline suggestion' }; + lineCountOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of lines in the original text' }; + lineCountModified: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of lines in the modified text' }; + characterCountOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of characters in the original text' }; + characterCountModified: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of characters in the modified text' }; + disjointReplacements: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of inner replacements made by the inline completion' }; + sameShapeReplacements: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether all inner replacements are the same shape' }; + noSuggestionReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason why no inline completion was provided' }; + notShownReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason why the inline completion was not shown' }; +}; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d48cde23314..eeafb8931b3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7658,6 +7658,7 @@ declare namespace monaco.languages { timeUntilShown: number | undefined; timeUntilProviderRequest: number; timeUntilProviderResponse: number; + notShownReason: string | undefined; editorType: string; viewKind: string | undefined; error: string | undefined; @@ -7675,6 +7676,7 @@ declare namespace monaco.languages { typingInterval: number; typingIntervalCharacterCount: number; selectedSuggestionInfo: boolean; + availableProviders: string; }; export interface CodeAction { diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/forwardingTelemetryService.ts b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts similarity index 93% rename from src/vs/workbench/contrib/editTelemetry/browser/telemetry/forwardingTelemetryService.ts rename to src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts index 8026de0a6e4..ce1b335e3ac 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/forwardingTelemetryService.ts +++ b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ClassifiedEvent, OmitMetadata, IGDPRProperty, StrictPropertyCheck } from '../../../../../platform/telemetry/common/gdprTypings.js'; -import { ITelemetryData, ITelemetryService, TelemetryLevel } from '../../../../../platform/telemetry/common/telemetry.js'; -import { IDataChannelService } from '../../../../../platform/dataChannel/common/dataChannel.js'; +import { ClassifiedEvent, OmitMetadata, IGDPRProperty, StrictPropertyCheck } from '../../telemetry/common/gdprTypings.js'; +import { ITelemetryData, ITelemetryService, TelemetryLevel } from '../../telemetry/common/telemetry.js'; +import { IDataChannelService } from '../common/dataChannel.js'; export class InterceptingTelemetryService implements ITelemetryService { _serviceBrand: undefined; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index daa43f19d6f..2e4ad0bc8d8 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -36,10 +36,11 @@ import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions import { ExtHostContext, ExtHostLanguageFeaturesShape, HoverWithId, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentDropEditDto, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol.js'; import { InlineCompletionEndOfLifeReasonKind } from '../common/extHostTypes.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; -import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../contrib/editTelemetry/browser/telemetry/forwardingTelemetryService.js'; +import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { IAiEditTelemetryService } from '../../contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.js'; import { EditDeltaInfo } from '../../../editor/common/textModelEditSource.js'; import { IInlineCompletionsUnificationService } from '../../services/inlineCompletions/common/inlineCompletionsUnification.js'; +import { InlineCompletionEndOfLifeEvent, sendInlineCompletionsEndOfLifeTelemetry } from '../../../editor/contrib/inlineCompletions/browser/telemetry.js'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -730,6 +731,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread extensionId, extensionVersion, groupId, + availableProviders: lifetimeSummary.availableProviders, partiallyAccepted: lifetimeSummary.partiallyAccepted, partiallyAcceptedCountSinceOriginal: lifetimeSummary.partiallyAcceptedCountSinceOriginal, partiallyAcceptedRatioSinceOriginal: lifetimeSummary.partiallyAcceptedRatioSinceOriginal, @@ -737,12 +739,14 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread superseded: reason.kind === InlineCompletionEndOfLifeReasonKind.Ignored && !!reason.supersededBy, reason: reason.kind === InlineCompletionEndOfLifeReasonKind.Accepted ? 'accepted' : reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected ? 'rejected' - : 'ignored', + : reason.kind === InlineCompletionEndOfLifeReasonKind.Ignored ? 'ignored' : undefined, + noSuggestionReason: undefined, + notShownReason: lifetimeSummary.notShownReason, ...forwardToChannelIf(isCopilotLikeExtension(extensionId)), }; - const telemetryService = this._instantiationService.createInstance(DataChannelForwardingTelemetryService); - telemetryService.publicLog2('inlineCompletion.endOfLife', endOfLifeSummary); + const dataChannelForwardingTelemetryService = this._instantiationService.createInstance(DataChannelForwardingTelemetryService); + sendInlineCompletionsEndOfLifeTelemetry(dataChannelForwardingTelemetryService, endOfLifeSummary); }, disposeInlineCompletions: (completions: IdentifiableInlineCompletions, reason: languages.InlineCompletionsDisposeReason): void => { this._proxy.$freeInlineCompletionsList(handle, completions.pid, reason); @@ -1364,84 +1368,3 @@ export class MainThreadDocumentRangeSemanticTokensProvider implements languages. throw new Error(`Unexpected`); } } - -type InlineCompletionEndOfLifeEvent = { - /** - * @deprecated To be removed at one point in favor of opportunityId - */ - id: string; - opportunityId: string; - correlationId: string | undefined; - extensionId: string; - extensionVersion: string; - groupId: string | undefined; - shown: boolean; - shownDuration: number; - shownDurationUncollapsed: number; - timeUntilShown: number | undefined; - timeUntilProviderRequest: number; - timeUntilProviderResponse: number; - reason: 'accepted' | 'rejected' | 'ignored'; - partiallyAccepted: number; - partiallyAcceptedCountSinceOriginal: number; - partiallyAcceptedRatioSinceOriginal: number; - partiallyAcceptedCharactersSinceOriginal: number; - preceeded: boolean; - requestReason: string; - languageId: string; - error: string | undefined; - typingInterval: number; - typingIntervalCharacterCount: number; - superseded: boolean; - editorType: string; - selectedSuggestionInfo: boolean; - viewKind: string | undefined; - cursorColumnDistance: number | undefined; - cursorLineDistance: number | undefined; - lineCountOriginal: number | undefined; - lineCountModified: number | undefined; - characterCountOriginal: number | undefined; - characterCountModified: number | undefined; - disjointReplacements: number | undefined; - sameShapeReplacements: boolean | undefined; -}; - -type InlineCompletionsEndOfLifeClassification = { - owner: 'benibenj'; - comment: 'Inline completions ended'; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the inline completion request' }; - opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES' }; - correlationId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The correlation identifier for the inline completion' }; - extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the extension that contributed the inline completion' }; - extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension that contributed the inline completion' }; - groupId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The group ID of the extension that contributed the inline completion' }; - shown: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was shown to the user' }; - shownDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration for which the inline completion was shown' }; - shownDurationUncollapsed: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration for which the inline completion was shown without collapsing' }; - timeUntilShown: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be shown after the request' }; - timeUntilProviderRequest: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be requested from the provider' }; - timeUntilProviderResponse: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took for the inline completion to be shown after the request' }; - reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion ending' }; - selectedSuggestionInfo: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was requested with a selected suggestion' }; - partiallyAccepted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How often the inline completion was partially accepted by the user' }; - partiallyAcceptedCountSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How often the inline completion was partially accepted since the original request' }; - partiallyAcceptedRatioSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The percentage of characters accepted since the original request' }; - partiallyAcceptedCharactersSinceOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count accepted since the original request' }; - preceeded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was preceeded by another one' }; - languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language ID of the document where the inline completion was shown' }; - requestReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion request' }; - error: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The error message if the inline completion failed' }; - typingInterval: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The average typing interval of the user at the moment the inline completion was requested' }; - typingIntervalCharacterCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count involved in the typing interval calculation' }; - superseded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was superseded by another one' }; - editorType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the editor where the inline completion was shown' }; - viewKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of the view where the inline completion was shown' }; - cursorColumnDistance: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The distance in columns from the cursor to the inline suggestion' }; - cursorLineDistance: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The distance in lines from the cursor to the inline suggestion' }; - lineCountOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of lines in the original text' }; - lineCountModified: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of lines in the modified text' }; - characterCountOriginal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of characters in the original text' }; - characterCountModified: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of characters in the modified text' }; - disjointReplacements: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of inner replacements made by the inline completion' }; - sameShapeReplacements: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether all inner replacements are the same shape' }; -}; diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts index 78d9e035e0c..aebe5cff8d7 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts @@ -8,7 +8,7 @@ import { EditSuggestionId } from '../../../../../../editor/common/textModelEditS import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; import { TelemetryTrustedValue } from '../../../../../../platform/telemetry/common/telemetryUtils.js'; -import { DataChannelForwardingTelemetryService } from '../forwardingTelemetryService.js'; +import { DataChannelForwardingTelemetryService } from '../../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { IAiEditTelemetryService, IEditTelemetryCodeAcceptedData, IEditTelemetryCodeSuggestedData } from './aiEditTelemetryService.js'; export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts index e1827eb9ac9..28d4661ad5d 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts @@ -17,7 +17,7 @@ import { EditSourceData, IDocumentWithAnnotatedEdits, createDocWithJustReason } import { IAiEditTelemetryService } from './aiEditTelemetry/aiEditTelemetryService.js'; import { ArcTracker } from '../../common/arcTracker.js'; import type { ScmRepoBridge } from './editSourceTrackingImpl.js'; -import { forwardToChannelIf, isCopilotLikeExtension } from './forwardingTelemetryService.js'; +import { forwardToChannelIf, isCopilotLikeExtension } from '../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { ProviderId } from '../../../../../editor/common/languages.js'; export class InlineEditArcTelemetrySender extends Disposable { diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingFeature.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingFeature.ts index d6693bef4d7..dd27f472fcb 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingFeature.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingFeature.ts @@ -23,7 +23,7 @@ import { IStatusbarService, StatusbarAlignment } from '../../../../services/stat import { EditSource } from '../helpers/documentWithAnnotatedEdits.js'; import { EditSourceTrackingImpl } from './editSourceTrackingImpl.js'; import { AnnotatedDocuments } from '../helpers/annotatedDocuments.js'; -import { DataChannelForwardingTelemetryService } from './forwardingTelemetryService.js'; +import { DataChannelForwardingTelemetryService } from '../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { EDIT_TELEMETRY_DETAILS_SETTING_ID, EDIT_TELEMETRY_SHOW_DECORATIONS, EDIT_TELEMETRY_SHOW_STATUS_BAR } from '../settings.js'; import { VSCodeWorkspace } from '../helpers/vscodeObservableWorkspace.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; From c6d8b3ab0bd0a13194bb2cee517708585186b855 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:28:30 +0200 Subject: [PATCH 0659/4355] Git - only show "Resolve in Merge Editor" editor action when conflict markers are present (#269300) --- extensions/git/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 6e5deb004a1..16909716ad0 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2297,7 +2297,7 @@ { "command": "git.openMergeEditor", "group": "navigation@-10", - "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges" + "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && git.activeResourceHasMergeConflicts" } ], "multiDiffEditor/resource/title": [ From 3f43b50290c9f0c976e477cb2cee5dc4aea9bb91 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Wed, 1 Oct 2025 10:47:33 +0100 Subject: [PATCH 0660/4355] Add hover and focus styles for action item labels in sidebar --- .../workbench/browser/parts/sidebar/media/sidebarpart.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 2c94078993b..ed30c1b78a9 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -95,6 +95,13 @@ color: var(--vscode-activityBarTop-foreground) !important; } +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label.uri-icon, +.monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label.uri-icon, +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label.uri-icon, +.monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label.uri-icon { + background-color: var(--vscode-activityBarTop-foreground) !important; +} + .monaco-workbench .sidebar.pane-composite-part > .title > .composite-bar-container { flex: 1; } From 45a1ad2b52b83a8e72f8351d68f255171b968d6c Mon Sep 17 00:00:00 2001 From: "ermin.zem" Date: Wed, 1 Oct 2025 17:54:04 +0800 Subject: [PATCH 0661/4355] feat: split editor group direction according to workbench.editor.splitInGroupLayout configuration when clicking walkthrough ':toSide' commands (#267557) --- .../welcomeGettingStarted/browser/gettingStarted.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 9c7e945b914..e18337a38d3 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -64,7 +64,7 @@ import { GettingStartedEditorOptions, GettingStartedInput } from './gettingStart import { IResolvedWalkthrough, IResolvedWalkthroughStep, IWalkthroughsService, hiddenEntriesConfigurationKey, parseDescription } from './gettingStartedService.js'; import { RestoreWalkthroughsConfigurationValue, restoreWalkthroughsConfigurationKey } from './startupPage.js'; import { startEntries } from '../common/gettingStartedContent.js'; -import { GroupDirection, GroupsOrder, IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; +import { GroupsOrder, IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from '../../../services/editor/common/editorGroupsService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchThemeService } from '../../../services/themes/common/workbenchThemeService.js'; @@ -1243,13 +1243,9 @@ export class GettingStartedPage extends EditorPane { const fullSize = this.groupsService.getPart(this.group).contentDimension; if (!fullSize || fullSize.width <= 700 || this.container.classList.contains('width-constrained') || this.container.classList.contains('width-semi-constrained')) { return; } if (this.groupsService.count === 1) { - const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); + const editorGroupSplitDirection = preferredSideBySideGroupDirection(this.configurationService); + const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], editorGroupSplitDirection); this.groupsService.activateGroup(sideGroup); - - const gettingStartedSize = Math.floor(fullSize.width / 2); - - const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); - this.groupsService.setSize(assertReturnsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); } const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); From 217d15512c25ae2f31bc6e7239c0386e8d85e017 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Oct 2025 12:18:47 +0200 Subject: [PATCH 0662/4355] fix #269170 (#269315) * fix #269170 * Update src/vs/workbench/contrib/mcp/browser/mcpServersView.ts Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com> --------- Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com> --- .../contrib/mcp/browser/mcpServersView.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 3654f8997d7..57549d4ac02 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -234,15 +234,19 @@ export class McpServersListView extends AbstractExtensionsListView { - this.openerService.open(URI.parse(content)); - } - })); + localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from VS Code to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), + true, + ) + .appendMarkdown('\n\n') + .appendMarkdown(localize('mcp.gallery.enableDialog.setting', "This feature is currently in preview. You can disable it anytime using the setting [{0}]({1}).", mcpGalleryServiceEnablementConfig, settingsCommandLink)), + { + actionHandler: (content: string) => { + this.openerService.open(URI.parse(content), { allowCommands: ['workbench.action.openSettings'] }); + } + })); description.appendChild(markdownResult.element); const buttonContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-button-container')); @@ -253,7 +257,6 @@ export class McpServersListView extends AbstractExtensionsListView { - const settingsCommandLink = createCommandUri('workbench.action.openSettings', { query: `@id:${mcpGalleryServiceEnablementConfig}` }).toString(); const { result } = await this.dialogService.prompt({ type: 'info', From b6d12fcd02dc7499f5f64e0c6624bc9168f98aa8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Oct 2025 12:42:13 +0200 Subject: [PATCH 0663/4355] fix #269032 (#269326) --- src/vs/workbench/contrib/mcp/browser/mcpCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index 9d3ff8dcede..3cb8356533f 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -847,7 +847,7 @@ export class McpBrowseCommand extends Action2 { title: localize2('mcp.command.browse', "MCP Servers"), tooltip: localize2('mcp.command.browse.tooltip', "Browse MCP Servers"), category, - icon: Codicon.globe, + icon: Codicon.search, menu: [{ id: extensionsFilterSubMenu, group: '1_predefined', From aa177f29a423c9c3c42bcd79b9fdca720514de86 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:47:09 +0200 Subject: [PATCH 0664/4355] Update src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts index 85da9a17e81..947d9db0b5d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -5,8 +5,8 @@ import { DataChannelForwardingTelemetryService } from '../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; -export function sendInlineCompletionsEndOfLifeTelemetry(dataChannel: DataChannelForwardingTelemetryService, endOfLLifeSummary: InlineCompletionEndOfLifeEvent) { - dataChannel.publicLog2('inlineCompletion.endOfLife', endOfLLifeSummary); +export function sendInlineCompletionsEndOfLifeTelemetry(dataChannel: DataChannelForwardingTelemetryService, endOfLifeSummary: InlineCompletionEndOfLifeEvent) { + dataChannel.publicLog2('inlineCompletion.endOfLife', endOfLifeSummary); } export type InlineCompletionEndOfLifeEvent = { From 895e08006dfdb1f823d446db82acc01ef2e46e58 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Oct 2025 13:06:22 +0200 Subject: [PATCH 0665/4355] views - only show container actions separator if needed (#269296) --- src/vs/workbench/browser/part.ts | 30 ++----------------- .../workbench/browser/parts/compositePart.ts | 8 +++-- .../browser/parts/media/paneCompositePart.css | 2 +- .../browser/parts/paneCompositePart.ts | 5 ++-- .../browser/parts/panel/panelPart.ts | 5 ++-- 5 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index cd1bdc2eec0..8b113183fc1 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -43,8 +43,8 @@ export abstract class Part extends Component implements ISerializableView { private parent: HTMLElement | undefined; private headerArea: HTMLElement | undefined; - private titleArea: HTMLElement | undefined; - private contentArea: HTMLElement | undefined; + protected titleArea: HTMLElement | undefined; + protected contentArea: HTMLElement | undefined; private footerArea: HTMLElement | undefined; private partLayout: PartLayout | undefined; @@ -98,13 +98,6 @@ export abstract class Part extends Component implements ISerializableView { return undefined; } - /** - * Returns the title area container. - */ - protected getTitleArea(): HTMLElement | undefined { - return this.titleArea; - } - /** * Subclasses override to provide a content area implementation. */ @@ -112,16 +105,6 @@ export abstract class Part extends Component implements ISerializableView { return undefined; } - /** - * Returns the content area container. - */ - protected getContentArea(): HTMLElement | undefined { - return this.contentArea; - } - - /** - * Sets the header area - */ protected setHeaderArea(headerContainer: HTMLElement): void { if (this.headerArea) { throw new Error('Header already exists'); @@ -140,9 +123,6 @@ export abstract class Part extends Component implements ISerializableView { this.relayout(); } - /** - * Sets the footer area - */ protected setFooterArea(footerContainer: HTMLElement): void { if (this.footerArea) { throw new Error('Footer already exists'); @@ -161,9 +141,6 @@ export abstract class Part extends Component implements ISerializableView { this.relayout(); } - /** - * removes the header area - */ protected removeHeaderArea(): void { if (this.headerArea) { this.headerArea.remove(); @@ -173,9 +150,6 @@ export abstract class Part extends Component implements ISerializableView { } } - /** - * removes the footer area - */ protected removeFooterArea(): void { if (this.footerArea) { this.footerArea.remove(); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 4c9086f8df8..793fd69668c 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -247,8 +247,7 @@ export abstract class CompositePart extends Part { } // Take Composite on-DOM and show - const contentArea = this.getContentArea(); - contentArea?.appendChild(compositeContainer); + this.contentArea?.appendChild(compositeContainer); show(compositeContainer); // Setup action runner @@ -349,7 +348,10 @@ export abstract class CompositePart extends Part { toolBar.context = this.actionsContextProvider(); // Return fn to set into toolbar - return () => toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions), menuIds); + return () => { + toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions), menuIds); + this.titleArea?.classList.toggle('has-actions', primaryActions.length > 0 || secondaryActions.length > 0); + }; } protected getActiveComposite(): IComposite | undefined { diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index afa2295437c..9618a42d740 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .pane-composite-part:not(.empty) > .title.has-composite-bar > .global-actions .actions-container::before { +.monaco-workbench .pane-composite-part:not(.empty) > .title.has-composite-bar.has-actions > .global-actions .actions-container::before { /* Separate global actions from view actions unless pane is empty */ content: ''; width: 1px; diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 42b19d61b7b..fda9c918852 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -250,9 +250,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Wed, 1 Oct 2025 13:06:47 +0200 Subject: [PATCH 0666/4355] Chat load time error for no auth should show different instructions (fix microsoft/vscode-internalbacklog#5927) (#269292) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index c576fa92b60..bd407a31afd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -323,9 +323,17 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { if (ready === 'error' || ready === 'timedout') { let warningMessage: string; if (ready === 'timedout') { - warningMessage = localize('chatTookLongWarning', "Chat took too long to get ready. Please ensure you are signed in to {0} and that the extension `{1}` is installed and enabled.", defaultChat.provider.default.name, defaultChat.chatExtensionId); + if (this.chatEntitlementService.anonymous) { + warningMessage = localize('chatTookLongWarningAnonymous', "Chat took too long to get ready. Please ensure that the extension `{0}` is installed and enabled.", defaultChat.chatExtensionId); + } else { + warningMessage = localize('chatTookLongWarning', "Chat took too long to get ready. Please ensure you are signed in to {0} and that the extension `{1}` is installed and enabled.", defaultChat.provider.default.name, defaultChat.chatExtensionId); + } } else { - warningMessage = localize('chatFailedWarning', "Chat failed to get ready. Please ensure you are signed in to {0} and that the extension `{1}` is installed and enabled.", defaultChat.provider.default.name, defaultChat.chatExtensionId); + if (this.chatEntitlementService.anonymous) { + warningMessage = localize('chatFailedWarningAnonymous', "Chat failed to get ready. Please ensure that the extension `{0}` is installed and enabled.", defaultChat.chatExtensionId); + } else { + warningMessage = localize('chatFailedWarning', "Chat failed to get ready. Please ensure you are signed in to {0} and that the extension `{1}` is installed and enabled.", defaultChat.provider.default.name, defaultChat.chatExtensionId); + } } this.logService.warn(warningMessage, { From 762bf8c91dfeb37e419f8253ef76e295dfce55ee Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Oct 2025 13:13:10 +0200 Subject: [PATCH 0667/4355] fix - sanitize notification text for backticks (#269293) --- src/vs/base/browser/dom.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 5fd5c8bf45e..4681d190ae7 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1592,6 +1592,10 @@ export interface INotification extends IDisposable { readonly onClick: event.Event; } +function sanitizeNotificationText(text: string): string { + return text.replace(/`/g, '\''); // convert backticks to single quotes +} + export async function triggerNotification(message: string, options?: { detail?: string; sticky?: boolean }): Promise { const permission = await Notification.requestPermission(); if (permission !== 'granted') { @@ -1600,8 +1604,8 @@ export async function triggerNotification(message: string, options?: { detail?: const disposables = new DisposableStore(); - const notification = new Notification(message, { - body: options?.detail, + const notification = new Notification(sanitizeNotificationText(message), { + body: options?.detail ? sanitizeNotificationText(options.detail) : undefined, requireInteraction: options?.sticky, }); From 898a70df5b6d2327793028a9c6eabbca963358f1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:23:42 +0900 Subject: [PATCH 0668/4355] Add --transient to code spec (#269248) * Add --transient to code spec Fixes #269162 * Fix test expectations for --transient --- extensions/terminal-suggest/src/completions/code.ts | 4 ++++ extensions/terminal-suggest/src/test/completions/code.test.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/extensions/terminal-suggest/src/completions/code.ts b/extensions/terminal-suggest/src/completions/code.ts index 99e1371b181..35cfbe28c58 100644 --- a/extensions/terminal-suggest/src/completions/code.ts +++ b/extensions/terminal-suggest/src/completions/code.ts @@ -357,6 +357,10 @@ export const troubleshootingOptions = (cliName: string): Fig.Option[] => [ name: '--telemetry', description: 'Shows all telemetry events which VS code collects', }, + { + name: '--transient', + description: 'Run with temporary data and extension directories, as if launched for the first time.', + }, ]; export function createCodeGenerators(cliName: string): Fig.Generator { diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index 9022c0c5a3e..d1a97cbad26 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -53,6 +53,7 @@ export const codeSpecOptionsAndSubcommands = [ '--status', '--sync ', '--telemetry', + '--transient', '--uninstall-extension ', '--update-extensions', '--user-data-dir ', @@ -167,6 +168,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { '--status', '--sync ', '--telemetry', + '--transient', '--uninstall-extension ', '--update-extensions', '--use-version []', From 1c8a55c110553c1f8788de81c66484026a305ba3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Oct 2025 13:29:37 +0200 Subject: [PATCH 0669/4355] fix #269177 (#269304) --- .../contrib/mcp/browser/mcpServersView.ts | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 57549d4ac02..93d8a757f94 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -20,7 +20,7 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { WorkbenchPagedList } from '../../../../platform/list/browser/listService.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js'; @@ -34,7 +34,7 @@ import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/ac import { IAllowedMcpServersService, mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; import { URI } from '../../../../base/common/uri.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; - +import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; @@ -52,6 +52,7 @@ import { mcpServerIcon } from './mcpServerIcons.js'; import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js'; import { IMcpGalleryManifestService, McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { ProductQualityContext } from '../../../../platform/contextkey/common/contextkeys.js'; +import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js'; export interface McpServerListViewOptions { showWelcome?: boolean; @@ -64,11 +65,22 @@ interface IQueryResult { onDidChangeModel?: Event>; } +type Message = { + readonly text: string; + readonly severity: Severity; +}; + export class McpServersListView extends AbstractExtensionsListView { private list: WorkbenchPagedList | null = null; private listContainer: HTMLElement | null = null; private welcomeContainer: HTMLElement | null = null; + private bodyTemplate: { + messageContainer: HTMLElement; + messageSeverityIcon: HTMLElement; + messageBox: HTMLElement; + mcpServersList: HTMLElement; + } | undefined; private readonly contextMenuActionRunner = this._register(new ActionRunner()); private input: IQueryResult | undefined; @@ -99,7 +111,19 @@ export class McpServersListView extends AbstractExtensionsListView 0); + + if (this.isBodyVisible()) { + if (message) { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(message.severity); + this.bodyTemplate.messageBox.textContent = message.text; + } else if (count === 0) { + this.bodyTemplate.messageSeverityIcon.className = ''; + this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No MCP Servers found."); + } + if (this.bodyTemplate.messageBox.textContent) { + alert(this.bodyTemplate.messageBox.textContent); + } + } + } + } + private async query(query: string): Promise { const disposables = new DisposableStore(); if (query) { From 8b01ac01a76fce6c4fb77eb8a9ef16c3acb1efe6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Oct 2025 13:04:09 +0200 Subject: [PATCH 0670/4355] Fixes https://github.com/microsoft/vscode-internalbacklog/issues/5894 --- build/monaco/monaco.d.ts.recipe | 1 + src/vs/editor/common/languages.ts | 3 +- src/vs/editor/common/textModelEditSource.ts | 1 + .../browser/model/provideInlineCompletions.ts | 4 +- .../standalone/browser/standaloneLanguages.ts | 2 + src/vs/monaco.d.ts | 12 +++- .../api/browser/mainThreadLanguageFeatures.ts | 60 ++++++++++--------- 7 files changed, 52 insertions(+), 31 deletions(-) diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 7bf0ba227d0..43b91bbb80e 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -130,6 +130,7 @@ export type IModel = ITextModel; declare namespace monaco.languages { +#include(vs/editor/common/textModelEditSource): EditDeltaInfo #include(vs/base/common/glob): IRelativePattern #include(vs/editor/common/languageSelector): LanguageSelector, LanguageFilter #includeAll(vs/editor/standalone/browser/standaloneLanguages;languages.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 99913579cf0..772510bede6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -26,6 +26,7 @@ import { ContiguousMultilineTokens } from './tokens/contiguousMultilineTokens.js import { localize } from '../../nls.js'; import { ExtensionIdentifier } from '../../platform/extensions/common/extensions.js'; import { IMarkerData } from '../../platform/markers/common/markers.js'; +import { EditDeltaInfo } from './textModelEditSource.js'; /** * @internal @@ -886,7 +887,7 @@ export interface InlineCompletionsProvider r.newLines.length); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 60f932fab2b..1a377b78c68 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -28,6 +28,7 @@ import { CachedFunction } from '../../../../../base/common/cache.js'; import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js'; import { isDefined } from '../../../../../base/common/types.js'; import { inlineCompletionIsVisible } from './inlineSuggestionItem.js'; +import { EditDeltaInfo } from '../../../../common/textModelEditSource.js'; export type InlineCompletionContextWithoutUuid = Omit; @@ -331,7 +332,8 @@ export class InlineSuggestData { this._viewData.renderData = viewData; this._timeUntilShown = Date.now() - this._requestInfo.startTime; - this.source.provider.handleItemDidShow?.(this.source.inlineSuggestions, this.sourceInlineCompletion, updatedInsertText); + const editDeltaInfo = new EditDeltaInfo(viewData.lineCountModified, viewData.lineCountOriginal, viewData.characterCountModified, viewData.characterCountOriginal); + this.source.provider.handleItemDidShow?.(this.source.inlineSuggestions, this.sourceInlineCompletion, updatedInsertText, editDeltaInfo); if (this.sourceInlineCompletion.shownCommand) { await commandService.executeCommand(this.sourceInlineCompletion.shownCommand.id, ...(this.sourceInlineCompletion.shownCommand.arguments || [])); diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index c8d6168a566..5b2310fff17 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -25,6 +25,7 @@ import { IMonarchLanguage } from '../common/monarch/monarchTypes.js'; import { IStandaloneThemeService } from '../common/standaloneTheme.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { IMarkerData, IMarkerService } from '../../../platform/markers/common/markers.js'; +import { EditDeltaInfo } from '../../common/textModelEditSource.js'; /** * Register information about a new language. @@ -811,5 +812,6 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { // classes FoldingRangeKind: languages.FoldingRangeKind, SelectedSuggestionInfo: languages.SelectedSuggestionInfo, + EditDeltaInfo: EditDeltaInfo, }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index eeafb8931b3..e60883df0b5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6517,6 +6517,16 @@ declare namespace monaco.editor { declare namespace monaco.languages { + + export class EditDeltaInfo { + readonly linesAdded: number; + readonly linesRemoved: number; + readonly charsAdded: number; + readonly charsRemoved: number; + static fromText(text: string): EditDeltaInfo; + static tryCreate(linesAdded: number | undefined, linesRemoved: number | undefined, charsAdded: number | undefined, charsRemoved: number | undefined): EditDeltaInfo | undefined; + constructor(linesAdded: number, linesRemoved: number, charsAdded: number, charsRemoved: number); + } export interface IRelativePattern { /** * A base file path to which this pattern will be matched against relatively. @@ -7589,7 +7599,7 @@ declare namespace monaco.languages { * Will be called when an item is shown. * @param updatedInsertText Is useful to understand bracket completion. */ - handleItemDidShow?(completions: T, item: T['items'][number], updatedInsertText: string): void; + handleItemDidShow?(completions: T, item: T['items'][number], updatedInsertText: string, editDeltaInfo: EditDeltaInfo): void; /** * Will be called when an item is partially accepted. TODO: also handle full acceptance here! * @param acceptedCharacters Deprecated. Use `info.acceptedCharacters` instead. diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 2e4ad0bc8d8..9dcc385dd23 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -638,19 +638,21 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread const result = await this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token); return result; }, - handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, updatedInsertText: string): Promise => { + handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, updatedInsertText: string, editDeltaInfo: EditDeltaInfo): Promise => { this._instantiationService.invokeFunction(accessor => { const aiEditTelemetryService = accessor.getIfExists(IAiEditTelemetryService); - item.suggestionId = aiEditTelemetryService?.createSuggestionId({ - applyCodeBlockSuggestionId: undefined, - feature: 'inlineSuggestion', - source: providerId, - languageId: completions.languageId, - editDeltaInfo: new EditDeltaInfo(1, 1, -1, -1), // TODO@hediet, fix this approximation. - modeId: undefined, - modelId: undefined, - presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion', - }); + if (item.suggestionId === undefined) { + item.suggestionId = aiEditTelemetryService?.createSuggestionId({ + applyCodeBlockSuggestionId: undefined, + feature: 'inlineSuggestion', + source: providerId, + languageId: completions.languageId, + editDeltaInfo: editDeltaInfo, + modeId: undefined, + modelId: undefined, + presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion', + }); + } }); if (supportsHandleEvents) { @@ -681,23 +683,25 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread if (reason.kind === languages.InlineCompletionEndOfLifeReasonKind.Accepted) { this._instantiationService.invokeFunction(accessor => { const aiEditTelemetryService = accessor.getIfExists(IAiEditTelemetryService); - aiEditTelemetryService?.handleCodeAccepted({ - suggestionId: item.suggestionId, - feature: 'inlineSuggestion', - source: providerId, - languageId: completions.languageId, - editDeltaInfo: EditDeltaInfo.tryCreate( - lifetimeSummary.lineCountModified, - lifetimeSummary.lineCountOriginal, - lifetimeSummary.characterCountModified, - lifetimeSummary.characterCountOriginal, - ), - modeId: undefined, - modelId: undefined, - presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion', - acceptanceMethod: 'accept', - applyCodeBlockSuggestionId: undefined, - }); + if (item.suggestionId !== undefined) { + aiEditTelemetryService?.handleCodeAccepted({ + suggestionId: item.suggestionId, + feature: 'inlineSuggestion', + source: providerId, + languageId: completions.languageId, + editDeltaInfo: EditDeltaInfo.tryCreate( + lifetimeSummary.lineCountModified, + lifetimeSummary.lineCountOriginal, + lifetimeSummary.characterCountModified, + lifetimeSummary.characterCountOriginal, + ), + modeId: undefined, + modelId: undefined, + presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion', + acceptanceMethod: 'accept', + applyCodeBlockSuggestionId: undefined, + }); + } }); } From 03636e762bb2028051b9d07f695217763b79fe30 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 1 Oct 2025 13:34:42 +0200 Subject: [PATCH 0671/4355] fix #269205 (#269297) --- src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index a4fe9847364..392be895939 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -713,7 +713,9 @@ export class MCPContextsInitialisation extends Disposable implements IWorkbenchC this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifestStatus(status => mcpServersGalleryStatus.set(status))); const hasInstalledMcpServersContextKey = HasInstalledMcpServersContext.bindTo(contextKeyService); - hasInstalledMcpServersContextKey.set(mcpWorkbenchService.local.length > 0); - this._register(mcpWorkbenchService.onChange(() => hasInstalledMcpServersContextKey.set(mcpWorkbenchService.local.length > 0))); + mcpWorkbenchService.queryLocal().finally(() => { + hasInstalledMcpServersContextKey.set(mcpWorkbenchService.local.length > 0); + this._register(mcpWorkbenchService.onChange(() => hasInstalledMcpServersContextKey.set(mcpWorkbenchService.local.length > 0))); + }); } } From f5a5899e85ff85ff91cacb83afdba568fd1ac767 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 1 Oct 2025 11:52:13 +0200 Subject: [PATCH 0672/4355] Fixes https://github.com/microsoft/vscode/issues/262474 --- .../contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts b/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts index 77aa1a522a2..12f1948bd42 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts @@ -77,6 +77,8 @@ export class AiStatsStatusBar extends Disposable { display: 'flex', alignItems: 'center', justifyContent: 'center', + marginLeft: '3px', + marginRight: '3px', } }, [ n.div( From fc9b02289dd648eef4ba5176bcf552e3c32c14ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Wed, 1 Oct 2025 14:39:09 +0200 Subject: [PATCH 0673/4355] fix: properly update cloned stylesheets on mutation in firefox (#269126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Mangeonjean --- src/vs/base/browser/domStylesheets.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/domStylesheets.ts b/src/vs/base/browser/domStylesheets.ts index c0ad9c9eabe..1e34173680e 100644 --- a/src/vs/base/browser/domStylesheets.ts +++ b/src/vs/base/browser/domStylesheets.ts @@ -5,6 +5,7 @@ import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js'; import { autorun, IObservable } from '../common/observable.js'; +import { isFirefox } from './browser.js'; import { getWindows, sharedMutationObserver } from './dom.js'; import { mainWindow } from './window.js'; @@ -100,7 +101,7 @@ function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesh clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length); } - disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => { + disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true, subtree: isFirefox, characterData: isFirefox })(() => { clone.textContent = globalStylesheet.textContent; })); From 55fe53036e22789d6952dc24dc9ac19c888e78fe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Oct 2025 15:24:50 +0200 Subject: [PATCH 0674/4355] empty editor hint - prevent selection and allow editor context menu (#269343) --- .../browser/emptyTextEditorHint/emptyTextEditorHint.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.css b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.css index 649545a875c..b3308834e44 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.css +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.css @@ -5,8 +5,12 @@ .monaco-editor .contentWidgets .empty-editor-hint { color: var(--vscode-input-placeholderForeground); + user-select: none; + -webkit-user-select: none; + pointer-events: none; } .monaco-editor .contentWidgets .empty-editor-hint a { - color: var(--vscode-textLink-foreground) + color: var(--vscode-textLink-foreground); + pointer-events: auto; } From b454f411e5fab3e63146aaa0d4b3475be09d2ace Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 1 Oct 2025 15:25:57 +0200 Subject: [PATCH 0675/4355] No auth: Offer a modified setup dialog that does not ask to sign-in for entry points (fix microsoft/vscode-internalbacklog#5924) (#269339) --- .../chat/browser/actions/chatActions.ts | 6 +- .../contrib/chat/browser/chatSetup.ts | 69 ++++++++++++++----- .../contrib/chat/browser/chatStatus.ts | 2 +- .../contrib/scm/browser/scm.contribution.ts | 9 +-- .../contrib/scm/browser/scmViewPane.ts | 9 +-- .../common/gettingStartedContent.ts | 2 +- 6 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index f37a18d83c5..4aeeacf65e2 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -88,6 +88,7 @@ export const ACTION_ID_NEW_EDIT_SESSION = `workbench.action.chat.newEditSession` export const ACTION_ID_OPEN_CHAT = 'workbench.action.openChat'; export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; export const CHAT_SETUP_ACTION_ID = 'workbench.action.chat.triggerSetup'; +export const CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID = 'workbench.action.chat.triggerSetupSupportAnonymousAction'; const TOGGLE_CHAT_ACTION_ID = 'workbench.action.chat.toggle'; const CHAT_CLEAR_HISTORY_ACTION_ID = 'workbench.action.chat.clearHistory'; @@ -1849,17 +1850,14 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { function registerGenerateCodeCommand(coreCommand: string, actualCommand: string): void { CommandsRegistry.registerCommand(coreCommand, async accessor => { const commandService = accessor.get(ICommandService); - const telemetryService = accessor.get(ITelemetryService); const editorGroupService = accessor.get(IEditorGroupsService); - telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'editor' }); - if (editorGroupService.activeGroup.activeEditor) { // Pinning the editor helps when the Chat extension welcome kicks in after install to keep context editorGroupService.activeGroup.pinEditor(editorGroupService.activeGroup.activeEditor); } - const result = await commandService.executeCommand(CHAT_SETUP_ACTION_ID); + const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID); if (!result) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index bd407a31afd..9161baab555 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -71,7 +71,7 @@ import { IChatProgress, IChatService } from '../common/chatService.js'; import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; import { ILanguageModelsService } from '../common/languageModels.js'; -import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './actions/chatActions.js'; +import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID, CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from './actions/chatActions.js'; import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; @@ -97,6 +97,12 @@ const defaultChat = { privacyStatementUrl: product.defaultChatAgent?.privacyStatementUrl ?? '' }; +enum ChatSetupAnonymous { + Disabled = 0, + EnabledWithDialog = 1, + EnabledWithoutDialog = 2 +} + //#region Contribution const ToolsAgentContextKey = ContextKeyExpr.and( @@ -448,8 +454,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ - disableChatViewReveal: true, // we are already in a chat context - forceAnonymous: this.chatEntitlementService.anonymous // only enable anonymous selectively + disableChatViewReveal: true, // we are already in a chat context + forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively }); } catch (error) { this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`); @@ -660,7 +666,7 @@ class ChatSetup { this.skipDialogOnce = true; } - async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { + async run(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous }): Promise { if (this.pendingRun) { return this.pendingRun; } @@ -674,7 +680,7 @@ class ChatSetup { } } - private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { + private async doRun(options?: { disableChatViewReveal?: boolean; forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous }): Promise { this.context.update({ later: false }); const dialogSkipped = this.skipDialogOnce; @@ -693,8 +699,8 @@ class ChatSetup { let setupStrategy: ChatSetupStrategy; if (!options?.forceSignInDialog && (dialogSkipped || isProUser(this.chatEntitlementService.entitlement) || this.chatEntitlementService.entitlement === ChatEntitlement.Free)) { setupStrategy = ChatSetupStrategy.DefaultSetup; // existing pro/free users setup without a dialog - } else if (options?.forceAnonymous) { - setupStrategy = ChatSetupStrategy.DefaultSetup; // anonymous users setup without a dialog + } else if (options?.forceAnonymous === ChatSetupAnonymous.EnabledWithoutDialog) { + setupStrategy = ChatSetupStrategy.DefaultSetup; // anonymous setup without a dialog } else { setupStrategy = await this.showDialog(options); } @@ -740,7 +746,7 @@ class ChatSetup { return { success, dialogSkipped }; } - private async showDialog(options?: { forceSignInDialog?: boolean }): Promise { + private async showDialog(options?: { forceSignInDialog?: boolean; forceAnonymous?: ChatSetupAnonymous }): Promise { const disposables = new DisposableStore(); const dialogVariant = this.configurationService.getValue<'default' | 'apple' | unknown>('chat.setup.signInDialogVariant'); @@ -769,12 +775,12 @@ class ChatSetup { return buttons[button]?.[1] ?? ChatSetupStrategy.Canceled; } - private getButtons(variant: 'default' | 'apple' | unknown, options?: { forceSignInDialog?: boolean }): Array<[string, ChatSetupStrategy, { styleButton?: (button: IButton) => void } | undefined]> { + private getButtons(variant: 'default' | 'apple' | unknown, options?: { forceSignInDialog?: boolean; forceAnonymous?: ChatSetupAnonymous }): Array<[string, ChatSetupStrategy, { styleButton?: (button: IButton) => void } | undefined]> { type ContinueWithButton = [string, ChatSetupStrategy, { styleButton?: (button: IButton) => void } | undefined]; const styleButton = (...classes: string[]) => ({ styleButton: (button: IButton) => button.element.classList.add(...classes) }); let buttons: Array; - if (this.context.state.entitlement === ChatEntitlement.Unknown || options?.forceSignInDialog) { + if (!options?.forceAnonymous && (this.context.state.entitlement === ChatEntitlement.Unknown || options?.forceSignInDialog)) { const defaultProviderButton: ContinueWithButton = [localize('continueWith', "Continue with {0}", defaultChat.provider.default.name), ChatSetupStrategy.SetupWithoutEnterpriseProvider, styleButton('continue-button', 'default')]; const defaultProviderLink: ContinueWithButton = [defaultProviderButton[0], defaultProviderButton[1], styleButton('link-button')]; @@ -808,9 +814,13 @@ class ChatSetup { return buttons; } - private getDialogTitle(options?: { forceSignInDialog?: boolean }): string { + private getDialogTitle(options?: { forceSignInDialog?: boolean; forceAnonymous?: ChatSetupAnonymous }): string { if (this.chatEntitlementService.anonymous) { - return localize('enableMore', "Enable more AI features"); + if (options?.forceAnonymous) { + return localize('startUsing', "Start using GitHub Copilot"); + } else { + return localize('enableMore', "Enable more AI features"); + } } if (this.context.state.entitlement === ChatEntitlement.Unknown || options?.forceSignInDialog) { @@ -946,7 +956,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr }); } - override async run(accessor: ServicesAccessor, mode?: ChatModeKind, options?: { forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: boolean }): Promise { + override async run(accessor: ServicesAccessor, mode?: ChatModeKind, options?: { forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous }): Promise { const viewsService = accessor.get(IViewsService); const layoutService = accessor.get(IWorkbenchLayoutService); const instantiationService = accessor.get(IInstantiationService); @@ -981,6 +991,28 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr } } + class ChatSetupTriggerSupportAnonymousAction extends Action2 { + + constructor() { + super({ + id: CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID, + title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + const telemetryService = accessor.get(ITelemetryService); + const chatEntitlementService = accessor.get(IChatEntitlementService); + + telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'api' }); + + return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, { + forceAnonymous: chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined + }); + } + } + class ChatSetupTriggerForceSignInDialogAction extends Action2 { constructor() { @@ -1000,11 +1032,11 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr } } - class ChatSetupTriggerAnonymouslyAction extends Action2 { + class ChatSetupTriggerAnonymousWithoutDialogAction extends Action2 { constructor() { super({ - id: 'workbench.action.chat.triggerSetupAnonymously', + id: 'workbench.action.chat.triggerSetupAnonymousWithoutDialog', title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL }); } @@ -1015,7 +1047,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'api' }); - return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, { forceAnonymous: true }); + return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, { forceAnonymous: ChatSetupAnonymous.EnabledWithoutDialog }); } } @@ -1145,7 +1177,8 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr registerAction2(ChatSetupTriggerAction); registerAction2(ChatSetupTriggerForceSignInDialogAction); registerAction2(ChatSetupFromAccountsAction); - registerAction2(ChatSetupTriggerAnonymouslyAction); + registerAction2(ChatSetupTriggerAnonymousWithoutDialogAction); + registerAction2(ChatSetupTriggerSupportAnonymousAction); registerAction2(UpgradePlanAction); registerAction2(EnableOveragesAction); } @@ -1362,7 +1395,7 @@ interface IChatSetupControllerOptions { readonly useSocialProvider?: string; readonly useEnterpriseProvider?: boolean; readonly additionalScopes?: readonly string[]; - readonly forceAnonymous?: boolean; + readonly forceAnonymous?: ChatSetupAnonymous; } class ChatSetupController extends Disposable { diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index b8e274caf1d..6daf8411ab7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -543,7 +543,7 @@ class ChatStatusDashboard extends Disposable { let commandId: string; if (newUser && anonymousUser) { - commandId = 'workbench.action.chat.triggerSetupAnonymously'; + commandId = 'workbench.action.chat.triggerSetupAnonymousWithoutDialog'; } else { commandId = 'workbench.action.chat.triggerSetup'; } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index ab967ecfbfe..a2d4dc91c14 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -47,9 +47,7 @@ import { SCMAccessibilityHelp } from './scmAccessibilityHelp.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { SCMHistoryItemContextContribution } from './scmHistoryChatContext.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; -import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../base/common/actions.js'; -import { CHAT_SETUP_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; +import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; import product from '../../../../platform/product/common/product.js'; ModesRegistry.registerLanguage({ @@ -668,11 +666,8 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const commandService = accessor.get(ICommandService); - const telemetryService = accessor.get(ITelemetryService); - telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'scmResolveConflicts' }); - - const result = await commandService.executeCommand(CHAT_SETUP_ACTION_ID); + const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID); if (!result) { return; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 75258f7fa6c..d32554a552d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -21,7 +21,7 @@ import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from ' import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2, IMenu } from '../../../../platform/actions/common/actions.js'; -import { IAction, ActionRunner, Action, Separator, IActionRunner, toAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../base/common/actions.js'; +import { IAction, ActionRunner, Action, Separator, IActionRunner, toAction } from '../../../../base/common/actions.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IThemeService, IFileIconTheme } from '../../../../platform/theme/common/themeService.js'; import { isSCMResource, isSCMResourceGroup, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMResourceNode, connectPrimaryMenu } from './util.js'; @@ -110,7 +110,7 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import product from '../../../../platform/product/common/product.js'; -import { CHAT_SETUP_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; +import { CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../../chat/browser/actions/chatActions.js'; type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | ISCMResource | IResourceNode; @@ -1353,11 +1353,8 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const commandService = accessor.get(ICommandService); - const telemetryService = accessor.get(ITelemetryService); - telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'scmInput' }); - - const result = await commandService.executeCommand(CHAT_SETUP_ACTION_ID); + const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID); if (!result) { return; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index ce5f54d87bd..2c462d516f3 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -227,7 +227,7 @@ const Button = (title: string, href: string) => `[${title}](${href})`; const CopilotStepTitle = localize('gettingStarted.copilotSetup.title', "Use AI features with Copilot for free"); const CopilotDescription = localize({ key: 'gettingStarted.copilotSetup.description', comment: ['{Locked="["}', '{Locked="]({0})"}'] }, "You can use [Copilot]({0}) to generate code across multiple files, fix errors, ask questions about your code, and much more using natural language.", defaultChat.documentationUrl ?? ''); const CopilotTermsString = localize({ key: 'gettingStarted.copilotSetup.terms', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", defaultChat.provider.default.name, defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl); -const CopilotAnonymousButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetupAnonymously`); +const CopilotAnonymousButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetupAnonymousWithoutDialog`); const CopilotSignedOutButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetup`); const CopilotSignedInButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetup`); const CopilotCompleteButton = Button(localize('setupCopilotButton.chatWithCopilot', "Chat with Copilot"), 'command:workbench.action.chat.open'); From 30fd06cfcc9c68698eb48d5abf3564e390560cb4 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 1 Oct 2025 09:44:51 -0400 Subject: [PATCH 0676/4355] Have model picker react to model change (#269348) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 62dd7daabdf..cc02daa0f91 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -507,6 +507,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.initSelectedModel(); + this._register(this.languageModelsService.onDidChangeLanguageModels(() => { + // We've changed models and the current one is no longer available. Select a new one + const selectedModel = this._currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this._currentLanguageModel.identifier) : undefined; + if (this._currentLanguageModel && (!selectedModel || !selectedModel.isUserSelectable)) { + this.setCurrentLanguageModelToDefault(); + } + })); + this._register(this.onDidChangeCurrentChatMode(() => { this.accessibilityService.alert(this._currentModeObservable.get().label); if (this._inputEditor) { From aaab7788e729f753640a612dbb78a1e30dc90836 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 1 Oct 2025 15:58:44 +0200 Subject: [PATCH 0677/4355] fixes https://github.com/microsoft/vscode-copilot-release/issues/9733 --- .../api/common/extHostDiagnostics.ts | 5 +-- .../test/browser/extHostDiagnostics.test.ts | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index d2ca87f90d3..fe0367ccad6 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -13,6 +13,7 @@ import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMain import { DiagnosticSeverity } from './extHostTypes.js'; import * as converter from './extHostTypeConverters.js'; import { Event, Emitter, DebounceEmitter } from '../../../base/common/event.js'; +import { coalesce } from '../../../base/common/arrays.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { ResourceMap } from '../../../base/common/map.js'; import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js'; @@ -82,7 +83,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } // update single row - this.#data.set(first, diagnostics.slice()); + this.#data.set(first, coalesce(diagnostics)); toSync = [first]; } else if (Array.isArray(first)) { @@ -112,7 +113,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } } else { const currentDiagnostics = this.#data.get(uri); - currentDiagnostics?.push(...diagnostics); + currentDiagnostics?.push(...coalesce(diagnostics)); } } } diff --git a/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts b/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts index 8f63ccc30d1..23dc5c0a9fb 100644 --- a/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts @@ -473,6 +473,39 @@ suite('ExtHostDiagnostics', () => { assert.strictEqual(callCount, 3); // same but un-equal array }); + test('getDiagnostics does not tolerate sparse diagnostic arrays', function () { + const diags = new ExtHostDiagnostics(new class implements IMainContext { + getProxy(): any { + return new DiagnosticsShape(); + } + set(): any { + return null; + } + dispose(): void { } + assertRegistered(): void { } + drain() { + return undefined!; + } + }, new NullLogService(), fileSystemInfoService, new class extends mock() { + override getDocument() { + return undefined; + } + }); + + const collection = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'sparse'); + const uri = URI.parse('sparse:uri'); + const diag = new Diagnostic(new Range(0, 0, 0, 0), 'holey'); + const sparseDiagnostics: Diagnostic[] = new Array(3); + sparseDiagnostics[1] = diag; + + collection.set(uri, sparseDiagnostics); + + const result = diags.getDiagnostics(uri); + assert.strictEqual(result.length, 1); + const resultWithPossibleHoles = [...result] as (vscode.Diagnostic | undefined)[]; + assert.strictEqual(resultWithPossibleHoles.some(item => item === undefined), false); + }); + test('Version id is set whenever possible', function () { const all: [UriComponents, IMarkerData[]][] = []; From a4e7730f6e0ea092dc69ab71f361247545cd8cac Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 10:34:11 -0400 Subject: [PATCH 0678/4355] localize string (#269354) follow up to #265735 --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 2ce3f017fcf..5cb7532969e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -859,7 +859,7 @@ export class InlineChatController1 implements IEditorContribution { } else { // real response -> no message this._ui.value.widget.updateStatus(''); - alert('Response was empty'); + alert(localize('responseWasEmpty', "Response was empty")); } const position = await this._strategy.renderChanges(); From d51431eb17be35f90d6f113f3998270591449064 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Oct 2025 16:49:57 +0200 Subject: [PATCH 0679/4355] Unqualified tool names are suggested as completions in the tool (#269358) Unqualified tool names are suggested as completions in the too --- .../chat/browser/languageModelToolsService.ts | 73 ++++---- .../browser/languageModelToolsService.test.ts | 167 ++++++++++++++++++ 2 files changed, 200 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index dc33dc50dad..0033d8ec77e 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -720,60 +720,57 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return result; } - *getQualifiedToolNames(): Iterable { - for (const tool of this.getTools()) { - if (tool.canBeReferencedInPrompt) { - yield getToolReferenceName(tool); + private *getPromptReferencableTools(): Iterable<[IToolData | ToolSet, string]> { + const coveredByToolSets = new Set(); + for (const toolSet of this.toolSets.get()) { + if (toolSet.source.type !== 'user') { + yield [toolSet, getToolSetReferenceName(toolSet)]; + for (const tool of toolSet.getTools()) { + yield [tool, getToolReferenceName(tool, toolSet)]; + coveredByToolSets.add(tool); + } } } - for (const toolSet of this.toolSets.get()) { - yield getToolSetReferenceName(toolSet); - for (const tool of toolSet.getTools()) { - yield getToolReferenceName(tool, toolSet); + for (const tool of this.getTools()) { + if (tool.canBeReferencedInPrompt && !coveredByToolSets.has(tool)) { + yield [tool, getToolReferenceName(tool)]; } } } + *getQualifiedToolNames(): Iterable { + for (const [, toolReferenceName] of this.getPromptReferencableTools()) { + yield toolReferenceName; + } + } + getDeprecatedQualifiedToolNames(): Map { const result = new Map(); - const add = (name: string, qualifiedName: string) => { - if (name !== qualifiedName) { - result.set(name, qualifiedName); + const add = (name: string, toolReferenceName: string) => { + if (name !== toolReferenceName) { + result.set(name, toolReferenceName); } }; - for (const tool of this.getTools()) { - if (tool.canBeReferencedInPrompt) { - add(tool.toolReferenceName ?? tool.displayName, getToolReferenceName(tool)); - } - } - for (const toolSet of this.toolSets.get()) { - add(toolSet.referenceName, getToolSetReferenceName(toolSet)); - for (const tool of toolSet.getTools()) { - add(tool.toolReferenceName ?? tool.displayName, getToolReferenceName(tool, toolSet)); + for (const [tool, toolReferenceName] of this.getPromptReferencableTools()) { + if (tool instanceof ToolSet) { + add(tool.referenceName, toolReferenceName); + } else { + add(tool.toolReferenceName ?? tool.displayName, toolReferenceName); } } return result; } getToolByQualifiedName(qualifiedName: string): IToolData | ToolSet | undefined { - for (const tool of this.getTools()) { - if (tool.canBeReferencedInPrompt) { - if (qualifiedName === getToolReferenceName(tool) || qualifiedName === (tool.toolReferenceName ?? tool.displayName) /* legacy */) { - if (matchesToolReferenceName(tool, qualifiedName)) { - return tool; - } - } + for (const [tool, toolReferenceName] of this.getPromptReferencableTools()) { + if (qualifiedName === toolReferenceName) { + return tool; } - for (const toolSet of this.toolSets.get()) { - if (qualifiedName === getToolSetReferenceName(toolSet) || qualifiedName === toolSet.referenceName /* legacy */) { - return toolSet; - } - for (const tool of toolSet.getTools()) { - if (qualifiedName === getToolReferenceName(tool) || qualifiedName === (tool.toolReferenceName ?? tool.displayName) /* legacy */) { - return tool; - } - } + // legacy: check for the old name + if (qualifiedName === (tool instanceof ToolSet ? tool.referenceName : tool.toolReferenceName ?? tool.displayName)) { + return tool; } + } return undefined; } @@ -803,10 +800,6 @@ function getToolSetReferenceName(toolSet: ToolSet) { return toolSet.referenceName; } -function matchesToolReferenceName(tool: IToolData, name: string, toolSet?: ToolSet) { - const toolName = tool.toolReferenceName ?? tool.displayName; - return name === toolName || (toolSet && name === `${toolSet.referenceName}/${toolName}`); -} type LanguageModelToolInvokedEvent = { result: 'success' | 'error' | 'userCancelled'; diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index af5d6ed6679..15fbdb38a87 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -25,6 +25,7 @@ import { MockChatService } from '../common/mockChatService.js'; import { IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { ChatConfiguration } from '../../common/constants.js'; +import { URI } from '../../../../../base/common/uri.js'; // --- Test helpers to reduce repetition and improve readability --- @@ -113,6 +114,94 @@ suite('LanguageModelToolsService', () => { service = store.add(instaService.createInstance(LanguageModelToolsService)); }); + function setupToolsForTest(service: LanguageModelToolsService, store: any) { + + // Create a variety of tools and tool sets for testing + // Some with toolReferenceName, some without, some from extensions, mcp snd user defined + + const tool1: IToolData = { + id: 'tool1', + toolReferenceName: 'tool1RefName', + modelDescription: 'Test Tool 1', + displayName: 'Tool1 Display Name', + source: ToolDataSource.Internal, + canBeReferencedInPrompt: true, + }; + store.add(service.registerToolData(tool1)); + + const tool2: IToolData = { + id: 'tool2', + modelDescription: 'Test Tool 2', + displayName: 'Tool2 Display Name', + source: ToolDataSource.Internal, + canBeReferencedInPrompt: true, + }; + store.add(service.registerToolData(tool2)); + + /** Extension Tool 1 */ + + const extTool1: IToolData = { + id: 'extTool1', + toolReferenceName: 'extTool1RefName', + modelDescription: 'Test Extension Tool 1', + displayName: 'ExtTool1 Display Name', + source: { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('my.extension') }, + canBeReferencedInPrompt: true, + }; + store.add(service.registerToolData(extTool1)); + + /** Internal Tool Set with internalToolSetTool1 */ + + const internalToolSetTool1: IToolData = { + id: 'internalToolSetTool1', + toolReferenceName: 'internalToolSetTool1RefName', + modelDescription: 'Test Internal Tool Set 1', + displayName: 'InternalToolSet1 Display Name', + source: ToolDataSource.Internal, + }; + store.add(service.registerToolData(internalToolSetTool1)); + + const internalToolSet = store.add(service.createToolSet( + ToolDataSource.Internal, + 'internalToolSet', + 'internalToolSetRefName', + { description: 'Test Set' } + )); + store.add(internalToolSet.addTool(internalToolSetTool1)); + + /** User Tool Set with tool1 */ + + const userToolSet = store.add(service.createToolSet( + { type: 'user', label: "User", file: URI.file('/test/userToolSet.json') }, + 'userToolSet', + 'userToolSetRefName', + { description: 'Test Set' } + )); + store.add(userToolSet.addTool(tool1)); + + /** MCP tool in a MCP tool set */ + + const mcpDataSource: ToolDataSource = { type: 'mcp', label: 'My MCP Server', serverLabel: "MCP Server", instructions: undefined, collectionId: 'testMCPCollection', definitionId: 'testMCPDefId' }; + const mcpTool1: IToolData = { + id: 'mcpTool1', + toolReferenceName: 'mcpTool1RefName', + modelDescription: 'Test MCP Tool 1', + displayName: 'McpTool1 Display Name', + source: mcpDataSource, + canBeReferencedInPrompt: true, + }; + store.add(service.registerToolData(mcpTool1)); + + const mcpToolSet = store.add(service.createToolSet( + mcpDataSource, + 'mcpToolSet', + 'mcpToolSetRefName', + { description: 'MCP Test ToolSet' } + )); + store.add(mcpToolSet.addTool(mcpTool1)); + } + + test('registerToolData', () => { const toolData: IToolData = { id: 'testTool', @@ -1412,4 +1501,82 @@ suite('LanguageModelToolsService', () => { ); assert.strictEqual(result.content[0].value, 'workspace result'); }); + + test('getQualifiedToolNames', () => { + setupToolsForTest(service, store); + + const qualifiedNames = Array.from(service.getQualifiedToolNames()).sort(); + + const expectedNames = [ + 'tool1RefName', + 'Tool2 Display Name', + 'my.extension/extTool1RefName', + 'mcpToolSetRefName/*', + 'mcpToolSetRefName/mcpTool1RefName', + 'internalToolSetRefName', + 'internalToolSetRefName/internalToolSetTool1RefName', + ].sort(); + + assert.deepStrictEqual(qualifiedNames, expectedNames, 'getQualifiedToolNames should return correct qualified names'); + }); + + test('getDeprecatedQualifiedToolNames', () => { + setupToolsForTest(service, store); + + const deprecatedNames = service.getDeprecatedQualifiedToolNames(); + + // Tools in internal tool sets should have their qualified names with toolset prefix, tools sets keep their name + assert.strictEqual(deprecatedNames.get('internalToolSetTool1RefName'), 'internalToolSetRefName/internalToolSetTool1RefName'); + assert.strictEqual(deprecatedNames.get('internalToolSetRefName'), undefined); + + // For extension tools, the qualified name includes the extension ID + assert.strictEqual(deprecatedNames.get('extTool1RefName'), 'my.extension/extTool1RefName'); + + // For MCP tool sets, the qualified name includes the /* suffix + assert.strictEqual(deprecatedNames.get('mcpToolSetRefName'), 'mcpToolSetRefName/*'); + assert.strictEqual(deprecatedNames.get('mcpTool1RefName'), 'mcpToolSetRefName/mcpTool1RefName'); + + // Internal tool sets and user tools sets and tools without namespace changes should not appear + assert.strictEqual(deprecatedNames.get('Tool2 Display Name'), undefined); + assert.strictEqual(deprecatedNames.get('tool1RefName'), undefined); + assert.strictEqual(deprecatedNames.get('userToolSetRefName'), undefined); + }); + + test('getToolByQualifiedName', () => { + setupToolsForTest(service, store); + + // Test finding tools by their qualified names + const tool1 = service.getToolByQualifiedName('tool1RefName'); + assert.ok(tool1); + assert.strictEqual(tool1.id, 'tool1'); + + const tool2 = service.getToolByQualifiedName('Tool2 Display Name'); + assert.ok(tool2); + assert.strictEqual(tool2.id, 'tool2'); + + const extTool = service.getToolByQualifiedName('my.extension/extTool1RefName'); + assert.ok(extTool); + assert.strictEqual(extTool.id, 'extTool1'); + + const mcpTool = service.getToolByQualifiedName('mcpToolSetRefName/mcpTool1RefName'); + assert.ok(mcpTool); + assert.strictEqual(mcpTool.id, 'mcpTool1'); + + + const mcpToolSet = service.getToolByQualifiedName('mcpToolSetRefName/*'); + assert.ok(mcpToolSet); + assert.strictEqual(mcpToolSet.id, 'mcpToolSet'); + + const internalToolSet = service.getToolByQualifiedName('internalToolSetRefName/internalToolSetTool1RefName'); + assert.ok(internalToolSet); + assert.strictEqual(internalToolSet.id, 'internalToolSetTool1'); + + // Test finding tools within tool sets + const toolInSet = service.getToolByQualifiedName('internalToolSetRefName'); + assert.ok(toolInSet); + assert.strictEqual(toolInSet!.id, 'internalToolSet'); + + }); + + }); From 597148770d28029af9746d70edf64bb9e020e161 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:55:05 +0200 Subject: [PATCH 0680/4355] SCM - use subject as the title of the multi-file diff editor (#269347) --- extensions/git/src/commands.ts | 6 +++--- extensions/git/src/historyProvider.ts | 9 ++------- extensions/git/src/util.ts | 5 +++++ .../workbench/contrib/scm/browser/scmHistoryViewPane.ts | 2 +- src/vs/workbench/contrib/scm/browser/util.ts | 7 ++----- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9682b979e84..eb42b6e9985 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; -import { DiagnosticSeverityConfig, dispose, fromNow, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, toDiagnosticSeverity, truncate } from './util'; +import { DiagnosticSeverityConfig, dispose, fromNow, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -4906,7 +4906,7 @@ export class CommandCenter { const changes = await repository.diffTrees(commitParentId, commit.hash); const resources = changes.map(c => toMultiFileDiffEditorUris(c, commitParentId, commit.hash)); - const title = `${item.shortRef} - ${truncate(commit.message)}`; + const title = `${item.shortRef} - ${subject(commit.message)}`; const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${commitParentId}..${commit.hash}` }); const reveal = { modifiedUri: toGitUri(uri, commit.hash) }; @@ -5172,7 +5172,7 @@ export class CommandCenter { const commitShortHashLength = config.get('commitShortHashLength', 7); const commit = await repository.getCommit(historyItemId); - const title = `${truncate(historyItemId, commitShortHashLength, false)} - ${truncate(commit.message)}`; + const title = `${truncate(historyItemId, commitShortHashLength, false)} - ${subject(commit.message)}`; const historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : await repository.getEmptyTree(); const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItemId}` }); diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 8f440b08c97..1d01a9203a3 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -6,7 +6,7 @@ import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, truncate } from './util'; +import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, subject, truncate } from './util'; import { toMultiFileDiffEditorUris } from './uri'; import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git'; import { emojify, ensureEmojis } from './emoji'; @@ -290,18 +290,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const messageWithLinks = await provideSourceControlHistoryItemMessageLinks( this.historyItemDetailProviderRegistry, this.repository, message) ?? message; - const newLineIndex = message.indexOf('\n'); - const subject = newLineIndex !== -1 - ? `${truncate(message, newLineIndex, false)}` - : message; - const avatarUrl = commitAvatars?.get(commit.hash); const references = this._resolveHistoryItemRefs(commit); historyItems.push({ id: commit.hash, parentIds: commit.parents, - subject, + subject: subject(message), message: messageWithLinks, author: commit.authorName, authorEmail: commit.authorEmail, diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 730c76909b7..a0ddac22016 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -295,6 +295,11 @@ export function truncate(value: string, maxLength = 20, ellipsis = true): string return value.length <= maxLength ? value : `${value.substring(0, maxLength)}${ellipsis ? '\u2026' : ''}`; } +export function subject(value: string): string { + const index = value.indexOf('\n'); + return index === -1 ? value : truncate(value, index, false); +} + function normalizePath(path: string): string { // Windows & Mac are currently being handled // as case insensitive file systems in VS Code. diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 2c9ce4dd15a..f4239227667 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -1016,7 +1016,7 @@ class SCMHistoryTreeDragAndDrop implements ITreeDragAndDrop { private _getTreeElementLabel(element: TreeElement): string | undefined { if (isSCMHistoryItemViewModelTreeElement(element)) { const historyItem = element.historyItemViewModel.historyItem; - return getHistoryItemEditorTitle(historyItem); + return historyItem.displayId ?? historyItem.id; } return undefined; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 32f7e790c24..784f81ec934 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -153,11 +153,8 @@ export function getRepositoryResourceCount(provider: ISCMProvider): number { return provider.groups.reduce((r, g) => r + g.resources.length, 0); } -export function getHistoryItemEditorTitle(historyItem: ISCMHistoryItem, maxLength = 20): string { - const title = historyItem.subject.length <= maxLength ? - historyItem.subject : `${historyItem.subject.substring(0, maxLength)}\u2026`; - - return `${historyItem.displayId ?? historyItem.id} - ${title}`; +export function getHistoryItemEditorTitle(historyItem: ISCMHistoryItem): string { + return `${historyItem.displayId ?? historyItem.id} - ${historyItem.subject}`; } export function getHistoryItemHoverContent(themeService: IThemeService, historyItem: ISCMHistoryItem): IManagedHoverTooltipMarkdownString { From 6bcf247c95f6a2f36040b6a499a9d37338c29be9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 11:09:12 -0400 Subject: [PATCH 0681/4355] rm unneeded content from chat response aria label (#269360) fix #269227 --- .../chat/browser/chatAccessibilityProvider.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts index 54716f8822b..ff09418421a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts @@ -88,20 +88,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider !v.isComplete); if (waitingForConfirmation.length) { toolInvocationHint = this._instantiationService.invokeFunction(getToolConfirmationAlert, toolInvocation); - } else { // all completed - for (const invocation of toolInvocation) { - const titleObj = invocation.confirmationMessages?.title; - let title = ''; - if (typeof titleObj === 'string' && titleObj.trim()) { - title = titleObj; - } else if (titleObj && typeof titleObj === 'object' && 'value' in titleObj && titleObj.value && titleObj.value.trim()) { - title = titleObj.value; - } else { - // Fallback to toolId if no valid title - title = invocation.toolId; - } - toolInvocationHint += localize('toolCompletedHint', "Tool {0} completed.", title); - } } } const tableCount = marked.lexer(element.response.toString()).filter(token => token.type === 'table')?.length ?? 0; From 9439fdb317ac1c2f8feafbd7f782f47c8e21a28b Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:29:06 +0800 Subject: [PATCH 0682/4355] fix edit flex issue (#269362) * fix edit flex issue * Update src/vs/workbench/contrib/chat/browser/chatInputPart.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- src/vs/workbench/contrib/chat/browser/media/chat.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index cc02daa0f91..b7829a6a960 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1114,8 +1114,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this.container = elements.root; this.chatInputOverlay = dom.$('.chat-input-overlay'); - container.append(this.chatInputOverlay); container.append(this.container); + this.container.append(this.chatInputOverlay); this.container.classList.toggle('compact', this.options.renderStyle === 'compact'); this.followupsContainer = elements.followupsContainer; const inputAndSideToolbar = elements.inputAndSideToolbar; // The chat input and toolbar to the right diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 8935b7c783f..cbae3584b50 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1369,6 +1369,7 @@ have to be updated for changes to the rules above, or to support more deeply nes display: flex; flex-direction: column; gap: 4px; + position: relative; } .interactive-session .interactive-input-part.compact { @@ -2515,8 +2516,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .chat-row-disabled-overlay.disabled, .chat-input-overlay.disabled { position: absolute; - width: 100%; - height: 100%; + inset: 0; background-color: var(--vscode-sideBar-background); opacity: 0.6; display: flex; From 4b204542f273e09cae6d3e06e7a5256eabc51a4a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:33:17 +0000 Subject: [PATCH 0683/4355] Change task notification sentinel value from 0 to -1 (#269144) --- .../workbench/contrib/tasks/browser/abstractTaskService.ts | 6 ++++-- src/vs/workbench/contrib/tasks/browser/task.contribution.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index f7d61cac4a2..7ab1ded0472 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -496,8 +496,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private async _handleLongRunningTaskCompletion(event: ITaskProcessEndedEvent | ITaskInactiveEvent, durationMs: number): Promise { const notificationThreshold = this._configurationService.getValue(TaskSettingId.NotifyWindowOnTaskCompletion); - // If threshold is 0, notifications are disabled - if (notificationThreshold === 0 || durationMs < notificationThreshold) { + // If threshold is -1, notifications are disabled + // If threshold is 0, always show notifications (no minimum duration) + // Otherwise, only show if duration meets or exceeds the threshold + if (notificationThreshold === -1 || (notificationThreshold > 0 && durationMs < notificationThreshold)) { return; } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 2f7c4a7a7f3..73cd4c58597 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -566,9 +566,9 @@ configurationRegistry.registerConfiguration({ }, [TaskSettingId.NotifyWindowOnTaskCompletion]: { type: 'integer', - markdownDescription: nls.localize('task.NotifyWindowOnTaskCompletion', 'Controls the minimum task runtime in milliseconds before showing an OS notification when the task finishes while the window is not in focus. Set to 0 to disable notifications. This includes a window badge as well as notification toast.'), + markdownDescription: nls.localize('task.NotifyWindowOnTaskCompletion', 'Controls the minimum task runtime in milliseconds before showing an OS notification when the task finishes while the window is not in focus. Set to -1 to disable notifications. Set to 0 to always show notifications. This includes a window badge as well as notification toast.'), default: 60000, - minimum: 0 + minimum: -1 }, [TaskSettingId.VerboseLogging]: { type: 'boolean', From d0cb4a16a56573ecb3bbd0035fab9af9335b1d72 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:36:11 +0200 Subject: [PATCH 0684/4355] Git - fix regression related to using path.sep (#269361) --- extensions/git/src/util.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index a0ddac22016..d2b5a33123f 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -307,8 +307,9 @@ function normalizePath(path: string): string { path = path.toLowerCase(); } - // Remove trailing separator - if (path.charAt(path.length - 1) === sep) { + // Trailing separator + if (/[/\\]$/.test(path)) { + // Remove trailing separator path = path.substring(0, path.length - 1); } From dd08ce473d24f6384404f7c104e33a56f794da36 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:37:20 +0000 Subject: [PATCH 0685/4355] Fix terminal voice action visibility with context key (#269146) --- .../contrib/terminal/browser/terminalMenus.ts | 12 ++++++------ .../contrib/terminal/common/terminalContextKey.ts | 4 ++++ .../terminalContrib/voice/browser/terminalVoice.ts | 7 +++++++ .../voice/browser/terminalVoiceActions.ts | 11 ++++++++--- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 58f5ec9476c..54b238fc1a5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -526,11 +526,11 @@ export function setupTerminalMenus(): void { item: { command: { id: TerminalCommandId.StartVoice, - title: localize('workbench.action.terminal.startVoice', "Start Voice"), + title: localize('workbench.action.terminal.startVoice', "Start Dictation"), }, group: 'navigation', order: 9, - when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress.toNegated()), isHiddenByDefault: true }, }, @@ -539,11 +539,11 @@ export function setupTerminalMenus(): void { item: { command: { id: TerminalCommandId.StopVoice, - title: localize('workbench.action.terminal.stopVoice', "Stop Voice"), + title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), }, group: 'navigation', order: 9, - when: ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress), isHiddenByDefault: true }, }, @@ -761,7 +761,7 @@ export function setupTerminalMenus(): void { }, group: 'navigation', order: 9, - when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider), + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider, TerminalContextKeys.terminalDictationInProgress.negate()), isHiddenByDefault: true }); MenuRegistry.appendMenuItem(menuId, { @@ -772,7 +772,7 @@ export function setupTerminalMenus(): void { }, group: 'navigation', order: 10, - when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider), + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider, TerminalContextKeys.terminalDictationInProgress), isHiddenByDefault: true }); } diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index f432e7d6221..23512aa1aa2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -40,6 +40,7 @@ export const enum TerminalContextKeyStrings { ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', + DictationInProgress = 'terminalDictationInProgress' } export namespace TerminalContextKeys { @@ -139,6 +140,9 @@ export namespace TerminalContextKeys { /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */ export const terminalShellIntegrationEnabled = new RawContextKey(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal")); + /** Whether a speech to text (dictation) session is in progress. */ + export const terminalDictationInProgress = new RawContextKey(TerminalContextKeyStrings.DictationInProgress, false); + export const shouldShowViewInlineActions = ContextKeyExpr.and( ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts index 30c85e28927..b1c0519e813 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts @@ -11,12 +11,14 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { isNumber } from '../../../../../base/common/types.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { SpeechTimeoutDefault } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { ISpeechService, AccessibilityVoiceSettingId, ISpeechToTextEvent, SpeechToTextStatus } from '../../../speech/common/speechService.js'; import type { IMarker, IDecoration } from '@xterm/xterm'; import { alert } from '../../../../../base/browser/ui/aria/aria.js'; import { ITerminalService } from '../../../terminal/browser/terminal.js'; +import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; const symbolMap: { [key: string]: string } = { @@ -56,6 +58,7 @@ export class TerminalVoiceSession extends Disposable { private _ghostTextMarker: IMarker | undefined; private static _instance: TerminalVoiceSession | undefined = undefined; private _acceptTranscriptionScheduler: RunOnceScheduler | undefined; + private readonly _terminalDictationInProgress: IContextKey; static getInstance(instantiationService: IInstantiationService): TerminalVoiceSession { if (!TerminalVoiceSession._instance) { TerminalVoiceSession._instance = instantiationService.createInstance(TerminalVoiceSession); @@ -69,11 +72,13 @@ export class TerminalVoiceSession extends Disposable { @ISpeechService private readonly _speechService: ISpeechService, @ITerminalService private readonly _terminalService: ITerminalService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(); this._register(this._terminalService.onDidChangeActiveInstance(() => this.stop())); this._register(this._terminalService.onDidDisposeInstance(() => this.stop())); this._disposables = this._register(new DisposableStore()); + this._terminalDictationInProgress = TerminalContextKeys.terminalDictationInProgress.bindTo(contextKeyService); } async start(): Promise { @@ -96,6 +101,7 @@ export class TerminalVoiceSession extends Disposable { } switch (e.status) { case SpeechToTextStatus.Started: + this._terminalDictationInProgress.set(true); if (!this._decoration) { this._createDecoration(); } @@ -133,6 +139,7 @@ export class TerminalVoiceSession extends Disposable { this._cancellationTokenSource?.cancel(); this._disposables.clear(); this._input = ''; + this._terminalDictationInProgress.reset(); } private _sendText(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index bc807624524..ebb4f66c7a0 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -6,16 +6,21 @@ import { localize2 } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { HasSpeechProvider } from '../../../speech/common/speechService.js'; +import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; import { TerminalCommandId } from '../../../terminal/common/terminal.js'; +import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalVoiceSession } from './terminalVoice.js'; export function registerTerminalVoiceActions() { registerActiveInstanceAction({ id: TerminalCommandId.StartVoice, title: localize2('workbench.action.terminal.startDictation', "Start Dictation in Terminal"), - precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), + precondition: ContextKeyExpr.and( + HasSpeechProvider, + SpeechToTextInProgress.toNegated(), + sharedWhenClause.terminalAvailable + ), f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); @@ -26,7 +31,7 @@ export function registerTerminalVoiceActions() { registerActiveInstanceAction({ id: TerminalCommandId.StopVoice, title: localize2('workbench.action.terminal.stopDictation', "Stop Dictation in Terminal"), - precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), + precondition: TerminalContextKeys.terminalDictationInProgress, f1: true, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); From d1abc403afb68e89df4a5295718038d6311c6be2 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:41:12 -0700 Subject: [PATCH 0686/4355] make experimental setting less tempting to flip (#269365) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 2 +- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 4 ++-- src/vs/workbench/contrib/chat/common/constants.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index d18fa38c7c2..8661a083bca 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -796,7 +796,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const requestParser = instantiationService.createInstance(ChatRequestParser); - const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseChatSessionsForCloudButton); + const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseCloudButtonV2); if (isChatSessionsExperimentEnabled) { return await this.createWithChatSessions(chatSessionsService, chatAgentService, quickPickService, widget, userPrompt); } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 91bc654c10d..27bbf14d780 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -682,9 +682,9 @@ configurationRegistry.registerConfiguration({ default: false, scope: ConfigurationScope.WINDOW }, - [ChatConfiguration.UseChatSessionsForCloudButton]: { + [ChatConfiguration.UseCloudButtonV2]: { type: 'boolean', - description: nls.localize('chat.useChatSessionsForCloudButton', "Controls whether the 'Delegate to coding agent' button uses the new chat sessions API."), + description: nls.localize('chat.useCloudButtonV2', "Experimental implementation of 'cloud button'"), default: false, tags: ['experimental'], diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 4bf8c6b0bef..7c24db3536c 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -16,7 +16,7 @@ export enum ChatConfiguration { CheckpointsEnabled = 'chat.checkpoints.enabled', AgentSessionsViewLocation = 'chat.agentSessionsViewLocation', ThinkingStyle = 'chat.agent.thinkingStyle', - UseChatSessionsForCloudButton = 'chat.useChatSessionsForCloudButton', + UseCloudButtonV2 = 'chat.useCloudButtonV2', ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', EmptyStateHistoryEnabled = 'chat.emptyState.history.enabled', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', From efc1b63775d6f48bda68fec204aad0f8dfb27487 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 12:01:12 -0400 Subject: [PATCH 0687/4355] enable chat response OS notification by default (#269371) fix #268388 --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 27bbf14d780..e9a0ea030ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -315,7 +315,7 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { type: 'boolean', - default: false, + default: true, description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should notify the user when a response is received while the window is not in focus. This includes a window badge as well as notification toast."), }, 'chat.checkpoints.enabled': { From 03ea1370eca3cfad73b455031ec5fad70f6d4257 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:01:58 +0000 Subject: [PATCH 0688/4355] Improve chat notification to show chat title and response preview (#269137) --- .../chat/browser/chatAccessibilityService.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index d70dadfa1d0..4f403e1d5d4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -64,9 +64,9 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi if (!response || !responseContent) { return; } - this._showOSNotification(widget, container, responseContent.substring(0, 20)); - const errorDetails = isPanelChat && response.errorDetails ? ` ${response.errorDetails.message}` : ''; const plainTextResponse = renderAsPlaintext(new MarkdownString(responseContent)); + const errorDetails = isPanelChat && response.errorDetails ? ` ${response.errorDetails.message}` : ''; + this._showOSNotification(widget, container, plainTextResponse + errorDetails); if (!isVoiceInput || this._configurationService.getValue(AccessibilityVoiceSettingId.AutoSynthesize) !== 'on') { status(plainTextResponse + errorDetails); } @@ -92,6 +92,11 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi return; } + // Don't show notification if there's no meaningful content + if (!responseContent || !responseContent.trim()) { + return; + } + await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); // Dispose any previous unhandled notifications to avoid replacement/coalescing. @@ -100,9 +105,13 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi this.notifications.delete(ds); } - - const notification = await dom.triggerNotification(localize('chat.responseReceivedNotification', "Chat response received: {0}", responseContent), { - detail: localize('chat.responseReceivedNotification.detail', "Click to focus chat"), + const chatTitle = widget.viewModel?.model.title || localize('chat.untitledChat', "Untitled Chat"); + const maxDetailLength = 100; + const truncatedResponse = responseContent.length > maxDetailLength + ? responseContent.substring(0, maxDetailLength) + '...' + : responseContent; + const notification = await dom.triggerNotification(chatTitle, { + detail: truncatedResponse, sticky: false, }); From d793043fab4eefcce60a11105b20f4cd1c832fa5 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:05:08 +0200 Subject: [PATCH 0689/4355] Make sure we have the actual parent (#269363) * Fix clearing of tree nodes * Make sure we have the actual parent also dedupe elements to refresh Fixes https://github.com/microsoft/vscode-pull-request-github/issues/7857 * clean up --- .../workbench/api/common/extHostTreeViews.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d1cccc56ef5..c59833f377c 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -15,7 +15,7 @@ import { ExtHostCommands, CommandsConverter } from './extHostCommands.js'; import { asPromise } from '../../../base/common/async.js'; import * as extHostTypes from './extHostTypes.js'; import { isUndefinedOrNull, isString } from '../../../base/common/types.js'; -import { equals, coalesce } from '../../../base/common/arrays.js'; +import { equals, coalesce, distinct } from '../../../base/common/arrays.js'; import { ILogService, LogLevel } from '../../../platform/log/common/log.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { MarkdownString, ViewBadge, DataTransfer } from './extHostTypeConverters.js'; @@ -406,6 +406,7 @@ class ExtHostTreeView extends Disposable { }, 200, true); this._register(onDidChangeData(({ message, elements }) => { if (elements.length) { + elements = distinct(elements); this._refreshQueue = this._refreshQueue.then(() => { const _promiseCallback = promiseCallback; refreshingPromise = null; @@ -413,14 +414,14 @@ class ExtHostTreeView extends Disposable { this._nodesToClear.clear(); this._debugLogRefresh('start', elements, childrenToClear); return this._refresh(elements).then(() => { - this._debugLogRefresh('done', elements, undefined); + this._debugLogRefresh('done', elements, childrenToClear); this._clearNodes(childrenToClear); return _promiseCallback(); }).catch(e => { const message = e instanceof Error ? e.message : JSON.stringify(e); + this._debugLogRefresh('error', elements, childrenToClear); this._clearNodes(childrenToClear); this._logService.error(`Unable to refresh tree view ${this._viewId}: ${message}`); - this._debugLogRefresh('error', elements, undefined); return _promiseCallback(); }); }); @@ -447,17 +448,15 @@ class ExtHostTreeView extends Disposable { return { changed, roots }; } - private _debugLogRefresh(phase: 'start' | 'done' | 'error', elements: (T | Root)[], childrenToClear: TreeNode[] | undefined): void { + private _debugLogRefresh(phase: 'start' | 'done' | 'error', elements: (T | Root)[], childrenToClear: TreeNode[]): void { if (!this._isDebugLogging()) { return; } try { const snapshot = this._debugCollectHandles(elements); - if (childrenToClear) { - snapshot.clearing = childrenToClear.map(n => n.item.handle); - } + snapshot.clearing = childrenToClear.map(n => n.item.handle); const changedCount = snapshot.changed.length; - const nodesToClearLen = childrenToClear ? childrenToClear.length : 0; + const nodesToClearLen = childrenToClear.length; this._logService.debug(`[TreeView:${this._viewId}] refresh ${phase} changed=${changedCount} nodesToClear=${nodesToClearLen} elements.size=${this._elements.size} nodes.size=${this._nodes.size} handles=${JSON.stringify(snapshot)}`); } catch { this._logService.debug(`[TreeView:${this._viewId}] refresh ${phase} (snapshot failed)`); @@ -730,8 +729,9 @@ class ExtHostTreeView extends Disposable { const cts = new CancellationTokenSource(this._refreshCancellationSource.token); try { - const parentNode = parentElement ? this._nodes.get(parentElement) : undefined; const elements = await this._dataProvider.getChildren(parentElement); + const parentNode = parentElement ? this._nodes.get(parentElement) : undefined; + if (cts.token.isCancellationRequested) { return undefined; } From 91b9c51b871af19c46395cdc99cbb057655694b0 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 1 Oct 2025 09:37:31 -0700 Subject: [PATCH 0690/4355] Add screen reader notification condition for tool completion (#269273) --- .../contrib/chat/browser/languageModelToolsService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 0033d8ec77e..39ded850226 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -415,7 +415,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw err; } finally { toolInvocation?.complete(toolResult); - this.informScreenReader(toolInvocation?.pastTenseMessage ?? `${tool.data.displayName} finished`); + if (ChatContextKeys.requestInProgress.getValue(this._contextKeyService)) { + this.informScreenReader(toolInvocation?.pastTenseMessage ?? `${tool.data.displayName} finished`); + } if (store) { this.cleanupCallDisposables(requestId, store); } From 8ed203eac49663a707890808d9463b5091e1dad3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 13:00:46 -0400 Subject: [PATCH 0691/4355] use `MutableDisposable` to manage `startMarker` in terminal execute strategies (#269385) fixes #269240 --- .../executeStrategy/basicExecuteStrategy.ts | 34 ++++++++++++++----- .../executeStrategy/noneExecuteStrategy.ts | 34 ++++++++++++++----- .../executeStrategy/richExecuteStrategy.ts | 22 ++++++------ 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts index b697f737a48..32354ccce8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts @@ -6,7 +6,7 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { isNumber } from '../../../../../../base/common/types.js'; import type { ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; @@ -38,7 +38,7 @@ import { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; */ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { readonly type = 'basic'; - private _startMarker: IXtermMarker | undefined; + private readonly _startMarker = new MutableDisposable(); private readonly _onDidCreateStartMarker = new Emitter; public onDidCreateStartMarker: Event = this._onDidCreateStartMarker.event; @@ -88,13 +88,31 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { this._log('Waiting for idle'); await waitForIdle(this._instance.onData, 1000); - // Record where the command started. If the marker gets disposed, re-created it where + // Record where the command started. If the marker gets disposed, re-create it where // the cursor is. This can happen in prompts where they clear the line and rerender it // like powerlevel10k's transient prompt - this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker())); - store.add(this._startMarker.onDispose(() => { - this._log(`Start marker was disposed, recreating`); - this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker())); + const markerListener = new MutableDisposable(); + const recreateStartMarker = () => { + if (store.isDisposed) { + return; + } + const marker = xterm.raw.registerMarker(); + this._startMarker.value = marker ?? undefined; + this._onDidCreateStartMarker.fire(marker); + if (!marker) { + markerListener.clear(); + return; + } + markerListener.value = marker.onDispose(() => { + this._log(`Start marker was disposed, recreating`); + recreateStartMarker(); + }); + }; + recreateStartMarker(); + store.add(toDisposable(() => { + markerListener.dispose(); + this._startMarker.clear(); + this._onDidCreateStartMarker.fire(undefined); })); if (this._hasReceivedUserInput()) { @@ -136,7 +154,7 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { } if (output === undefined) { try { - output = xterm.getContentsAsText(this._startMarker, endMarker); + output = xterm.getContentsAsText(this._startMarker.value, endMarker); this._log('Fetched output via markers'); } catch { this._log('Failed to fetch output via markers'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts index dd27cc602d3..e310b642056 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts @@ -6,7 +6,7 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; import { waitForIdle, waitForIdleWithPromptHeuristics, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js'; import type { IMarker as IXtermMarker } from '@xterm/xterm'; @@ -20,7 +20,7 @@ import { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; */ export class NoneExecuteStrategy implements ITerminalExecuteStrategy { readonly type = 'none'; - private _startMarker: IXtermMarker | undefined; + private readonly _startMarker = new MutableDisposable(); private readonly _onDidCreateStartMarker = new Emitter; @@ -54,13 +54,31 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy { throw new CancellationError(); } - // Record where the command started. If the marker gets disposed, re-created it where + // Record where the command started. If the marker gets disposed, re-create it where // the cursor is. This can happen in prompts where they clear the line and rerender it // like powerlevel10k's transient prompt - this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker())); - store.add(this._startMarker.onDispose(() => { - this._log(`Start marker was disposed, recreating`); - this._onDidCreateStartMarker.fire(this._startMarker = store.add(xterm.raw.registerMarker())); + const markerListener = new MutableDisposable(); + const recreateStartMarker = () => { + if (store.isDisposed) { + return; + } + const marker = xterm.raw.registerMarker(); + this._startMarker.value = marker ?? undefined; + this._onDidCreateStartMarker.fire(marker); + if (!marker) { + markerListener.clear(); + return; + } + markerListener.value = marker.onDispose(() => { + this._log(`Start marker was disposed, recreating`); + recreateStartMarker(); + }); + }; + recreateStartMarker(); + store.add(toDisposable(() => { + markerListener.dispose(); + this._startMarker.clear(); + this._onDidCreateStartMarker.fire(undefined); })); if (this._hasReceivedUserInput()) { @@ -91,7 +109,7 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy { let output: string | undefined; const additionalInformationLines: string[] = []; try { - output = xterm.getContentsAsText(this._startMarker, endMarker); + output = xterm.getContentsAsText(this._startMarker.value, endMarker); this._log('Fetched output via markers'); } catch { this._log('Failed to fetch output via markers'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts index 33e1b5209fb..f12d84be7a1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts @@ -6,7 +6,7 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { isNumber } from '../../../../../../base/common/types.js'; import type { ICommandDetectionCapability, ITerminalCommand } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; @@ -23,7 +23,7 @@ import type { IMarker as IXtermMarker } from '@xterm/xterm'; */ export class RichExecuteStrategy implements ITerminalExecuteStrategy { readonly type = 'rich'; - private _startMarker: IXtermMarker | undefined; + private readonly _startMarker = new MutableDisposable(); private readonly _onDidCreateStartMarker = new Emitter; public onDidCreateStartMarker: Event = this._onDidCreateStartMarker.event; @@ -61,26 +61,26 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy { // Record where the command started. If the marker gets disposed, re-created it where // the cursor is. This can happen in prompts where they clear the line and rerender it // like powerlevel10k's transient prompt + const markerListener = new MutableDisposable(); const recreateStartMarker = () => { if (store.isDisposed) { return; } const marker = xterm.raw.registerMarker(); + this._startMarker.value = marker ?? undefined; + this._onDidCreateStartMarker.fire(marker); if (!marker) { - this._startMarker = undefined; - this._onDidCreateStartMarker.fire(undefined); + markerListener.clear(); return; } - this._startMarker = marker; - this._onDidCreateStartMarker.fire(marker); - store.add(marker); - store.add(marker.onDispose(() => { + markerListener.value = marker.onDispose(() => { recreateStartMarker(); - })); + }); }; recreateStartMarker(); store.add(toDisposable(() => { - this._startMarker = undefined; + markerListener.dispose(); + this._startMarker.clear(); this._onDidCreateStartMarker.fire(undefined); })); @@ -108,7 +108,7 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy { } if (output === undefined) { try { - output = xterm.getContentsAsText(this._startMarker, endMarker); + output = xterm.getContentsAsText(this._startMarker.value, endMarker); this._log('Fetched output via markers'); } catch { this._log('Failed to fetch output via markers'); From a11bfe5a7de8ef597faa428ac847773ddb3cdf83 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:01:13 +0000 Subject: [PATCH 0692/4355] ensure task notification does not play for agent run tasks (#269143) --- .../tasks/browser/abstractTaskService.ts | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 7ab1ded0472..916f7a3811b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -48,7 +48,7 @@ import { ITerminalGroupService, ITerminalService } from '../../terminal/browser/ import { ITerminalProfileResolverService } from '../../terminal/common/terminal.js'; import { ConfiguringTask, ContributedTask, CustomTask, ExecutionEngine, InMemoryTask, InstancePolicy, ITaskEvent, ITaskIdentifier, ITaskInactiveEvent, ITaskProcessEndedEvent, ITaskSet, JsonSchemaVersion, KeyedTaskIdentifier, RerunAllRunningTasksCommandId, RuntimeType, Task, TASK_RUNNING_STATE, TaskDefinition, TaskEventKind, TaskGroup, TaskRunSource, TaskSettingId, TaskSorter, TaskSourceKind, TasksSchemaProperties, USER_TASKS_GROUP_KEY } from '../common/tasks.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../chat/common/constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../../chat/common/constants.js'; import { CustomExecutionSupportedContext, ICustomizationProperties, IProblemMatcherRunOptions, ITaskFilter, ITaskProvider, ITaskService, IWorkspaceFolderTaskResult, ProcessExecutionSupportedContext, ServerlessWebContext, ShellExecutionSupportedContext, TaskCommandsRegistered, TaskExecutionSupportedContext, TasksAvailableContext } from '../common/taskService.js'; import { ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers, VerifiedTask } from '../common/taskSystem.js'; import { getTemplates as getTaskTemplates } from '../common/taskTemplates.js'; @@ -400,23 +400,29 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer case TaskEventKind.ProcessEnded: { const processEndedEvent = e as ITaskProcessEndedEvent; const startTime = this._taskRunStartTimes.get(e.taskId); - this._taskRunStartTimes.delete(e.taskId); - this._taskRunSources.delete(e.taskId); - const durationMs = processEndedEvent.durationMs ?? (startTime !== undefined ? Date.now() - startTime : undefined); + if (!startTime) { + break; + } + const durationMs = processEndedEvent.durationMs ?? (Date.now() - startTime); if (durationMs !== undefined) { this._handleLongRunningTaskCompletion(processEndedEvent, durationMs); } + this._taskRunStartTimes.delete(e.taskId); + this._taskRunSources.delete(e.taskId); break; } case TaskEventKind.Inactive: { const processEndedEvent = e as ITaskInactiveEvent; const startTime = this._taskRunStartTimes.get(e.taskId); - this._taskRunStartTimes.delete(e.taskId); - this._taskRunSources.delete(e.taskId); - const durationMs = processEndedEvent.durationMs ?? (startTime !== undefined ? Date.now() - startTime : undefined); + if (!startTime) { + break; + } + const durationMs = processEndedEvent.durationMs ?? (Date.now() - startTime); if (durationMs !== undefined) { this._handleLongRunningTaskCompletion(processEndedEvent, durationMs); } + this._taskRunStartTimes.delete(e.taskId); + this._taskRunSources.delete(e.taskId); break; } case TaskEventKind.Terminated: @@ -504,10 +510,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const taskRunSource = this._taskRunSources.get(event.taskId); - const chatNotificationsEnabled = this._configurationService.getValue(ChatConfiguration.NotifyWindowOnResponseReceived); - - // If task was run by chat agent and chat response notifications are enabled, avoid duplicate notifications - if (taskRunSource === TaskRunSource.ChatAgent && chatNotificationsEnabled) { + if (taskRunSource === TaskRunSource.ChatAgent) { return; } @@ -525,8 +528,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const message = taskLabel ? nls.localize('task.longRunningTaskCompletedWithLabel', 'Task "{0}" finished in {1}.', taskLabel, durationText) : nls.localize('task.longRunningTaskCompleted', 'Task finished in {0}.', durationText); - this._taskRunStartTimes.delete(event.taskId); - this._taskRunSources.delete(event.taskId); this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); const notification = await dom.triggerNotification(message); if (notification) { From 158f137036196fc153988460c65d974ed8f9487b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:01:33 -0400 Subject: [PATCH 0693/4355] Fix escape key navigation from chat accessible view to input field (#268541) --- .../chat/browser/chatResponseAccessibleView.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index bf04fbcdf75..3654c2e4449 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -38,7 +38,7 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplementation return; } - return new ChatResponseAccessibleProvider(verifiedWidget, focusedItem); + return new ChatResponseAccessibleProvider(verifiedWidget, focusedItem, chatInputFocused); } } @@ -46,7 +46,8 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi private _focusedItem: ChatTreeItem; constructor( private readonly _widget: IChatWidget, - item: ChatTreeItem + item: ChatTreeItem, + private readonly _wasOpenedFromInput: boolean ) { super(); this._focusedItem = item; @@ -125,7 +126,11 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi onClose(): void { this._widget.reveal(this._focusedItem); - this._widget.focus(this._focusedItem); + if (this._wasOpenedFromInput) { + this._widget.focusInput(); + } else { + this._widget.focus(this._focusedItem); + } } provideNextContent(): string | undefined { From 471822186e4b0602277440fe9732cc70407f9d57 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Oct 2025 19:10:11 +0200 Subject: [PATCH 0694/4355] update prompt default snippets (#269389) --- .../languageProviders/promptHeaderAutocompletion.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 860f7731bef..ca13e60ddf1 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -176,7 +176,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { private getValueSuggestions(promptType: string, property: string): string[] { if (promptType === PromptsType.instructions && property === 'applyTo') { - return ['**', '**/*.ts, **/*.js', '**/*.php', '**/*.py']; + return [`'**'`, `'**/*.ts, **/*.js'`, `'**/*.php'`, `'**/*.py'`]; } if (promptType === PromptsType.prompt && property === 'mode') { // Get all available modes (builtin + custom) @@ -188,7 +188,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return suggestions; } if (property === 'tools' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) { - return ['[]', `['codebase', 'editFiles', 'fetch']`]; + return ['[]', `['search', 'edit', 'fetch']`]; } if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) { return this.getModelNames(promptType === PromptsType.mode); From 76ae0faed727feab486bc54c60639b7a60cf3a8a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Oct 2025 19:15:31 +0200 Subject: [PATCH 0695/4355] Possible memory leak in prompt file validation (#269390) --- src/vs/base/common/yaml.ts | 51 ++++++++++++++++++- src/vs/base/test/common/yaml.test.ts | 73 +++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/vs/base/common/yaml.ts b/src/vs/base/common/yaml.ts index 490d068e0f0..cbbb5247b6c 100644 --- a/src/vs/base/common/yaml.ts +++ b/src/vs/base/common/yaml.ts @@ -413,9 +413,36 @@ class YamlParser { continue; } + // Handle comments - comments should terminate the array parsing + if (this.lexer.getCurrentChar() === '#') { + // Skip the rest of the line (comment) + this.lexer.skipToEndOfLine(); + this.lexer.advanceLine(); + continue; + } + + // Save position before parsing to detect if we're making progress + const positionBefore = this.lexer.savePosition(); + // Parse array item const item = this.parseValue(); - items.push(item); + // Skip implicit empty items that arise from a leading comma at the beginning of a new line + // (e.g. a line starting with ",foo" after a comment). A legitimate empty string element + // would have quotes and thus a non-zero span. We only filter zero-length spans. + if (!(item.type === 'string' && item.value === '' && item.start.line === item.end.line && item.start.character === item.end.character)) { + items.push(item); + } + + // Check if we made progress - if not, we're likely stuck + const positionAfter = this.lexer.savePosition(); + if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) { + // No progress made, advance at least one character to prevent infinite loop + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + this.lexer.advance(); + } else { + break; + } + } this.lexer.skipWhitespace(); @@ -446,6 +473,17 @@ class YamlParser { break; } + // Handle comments - comments should terminate the object parsing + if (this.lexer.getCurrentChar() === '#') { + // Skip the rest of the line (comment) + this.lexer.skipToEndOfLine(); + this.lexer.advanceLine(); + continue; + } + + // Save position before parsing to detect if we're making progress + const positionBefore = this.lexer.savePosition(); + // Parse key - read until colon const keyStart = this.lexer.getCurrentPosition(); let keyValue = ''; @@ -487,6 +525,17 @@ class YamlParser { properties.push({ key, value }); + // Check if we made progress - if not, we're likely stuck + const positionAfter = this.lexer.savePosition(); + if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) { + // No progress made, advance at least one character to prevent infinite loop + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + this.lexer.advance(); + } else { + break; + } + } + this.lexer.skipWhitespace(); // Handle comma separator diff --git a/src/vs/base/test/common/yaml.test.ts b/src/vs/base/test/common/yaml.test.ts index be4ced9d65a..2a778e22e6b 100644 --- a/src/vs/base/test/common/yaml.test.ts +++ b/src/vs/base/test/common/yaml.test.ts @@ -209,9 +209,9 @@ suite('YAML Parser', () => { ' database:', ' host:localhost', ' port: 5432', - ' abcde123456:', - ' logger12:admin', - ' memory12: a23123112' + ' credentials:', + ' username:admin', + ' password: secret123' ], { type: 'object', start: pos(0, 0), end: pos(6, 25), properties: [ @@ -232,16 +232,16 @@ suite('YAML Parser', () => { value: { type: 'number', start: pos(3, 10), end: pos(3, 14), value: 5432 } }, { - key: { type: 'string', start: pos(4, 4), end: pos(4, 15), value: 'abcde123456' }, + key: { type: 'string', start: pos(4, 4), end: pos(4, 15), value: 'credentials' }, value: { type: 'object', start: pos(5, 6), end: pos(6, 25), properties: [ { - key: { type: 'string', start: pos(5, 6), end: pos(5, 14), value: 'logger12' }, + key: { type: 'string', start: pos(5, 6), end: pos(5, 14), value: 'username' }, value: { type: 'string', start: pos(5, 15), end: pos(5, 20), value: 'admin' } }, { - key: { type: 'string', start: pos(6, 6), end: pos(6, 14), value: 'memory12' }, - value: { type: 'string', start: pos(6, 16), end: pos(6, 25), value: 'a23123112' } + key: { type: 'string', start: pos(6, 6), end: pos(6, 14), value: 'password' }, + value: { type: 'string', start: pos(6, 16), end: pos(6, 25), value: 'secret123' } } ] } @@ -342,6 +342,28 @@ suite('YAML Parser', () => { }, [] ); + + // Test multi-line inline object with internal comment line between properties + assertValidParse( + ['{a:1, # comment about b', ' b:2, c:3}'], + { + type: 'object', start: pos(0, 0), end: pos(1, 10), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 2), value: 'a' }, + value: { type: 'number', start: pos(0, 3), end: pos(0, 4), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 1), end: pos(1, 2), value: 'b' }, + value: { type: 'number', start: pos(1, 3), end: pos(1, 4), value: 2 } + }, + { + key: { type: 'string', start: pos(1, 6), end: pos(1, 7), value: 'c' }, + value: { type: 'number', start: pos(1, 8), end: pos(1, 9), value: 3 } + } + ] + }, + [] + ); }); test('special characters in values', () => { @@ -415,6 +437,20 @@ suite('YAML Parser', () => { ); }); + test('inline array with internal comment line', () => { + assertValidParse( + ['[one # comment about two', ',two, three]'], + { + type: 'array', start: pos(0, 0), end: pos(1, 12), items: [ + { type: 'string', start: pos(0, 1), end: pos(0, 4), value: 'one' }, + { type: 'string', start: pos(1, 1), end: pos(1, 4), value: 'two' }, + { type: 'string', start: pos(1, 6), end: pos(1, 11), value: 'three' } + ] + }, + [] + ); + }); + test('multi-line inline arrays', () => { assertValidParse( [ @@ -1078,6 +1114,29 @@ suite('YAML Parser', () => { ); }); + test('comment in inline array #269078', () => { + // Test malformed array with comment-like content - should not cause endless loop + assertValidParse( + [ + 'mode: agent', + 'tools: [#r' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 0), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'mode' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 11), value: 'agent' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 5), value: 'tools' }, + value: { type: 'array', start: pos(1, 7), end: pos(2, 0), items: [] } + } + ] + }, + [] + ); + }); + }); From c9715f9218f41d129b8482e55a52cca7165880db Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 1 Oct 2025 10:26:22 -0700 Subject: [PATCH 0696/4355] mcp: fix wrong progress bar position in editor chat (#269393) Closes #264093 --- .../browser/chatContentParts/media/chatConfirmationWidget.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index 97ef0026c73..22c3a1f4d26 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -46,6 +46,9 @@ position: relative; > .monaco-progress-container.active { + position: absolute; + left: 0px; + right: 0; top: 0px; border-top-left-radius: 10px; border-top-right-radius: 10px; From 9b6c88bf2ff2355d9cd982d293ecbaba9f22ed9e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:33:04 -0700 Subject: [PATCH 0697/4355] Remove restart call when enabling TS native Fixes #266087 Should no longer be required after upstream changes --- extensions/typescript-language-features/src/commands/useTsgo.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/useTsgo.ts b/extensions/typescript-language-features/src/commands/useTsgo.ts index 3f6e5f8c2c9..acf251be00a 100644 --- a/extensions/typescript-language-features/src/commands/useTsgo.ts +++ b/extensions/typescript-language-features/src/commands/useTsgo.ts @@ -68,7 +68,5 @@ async function updateTsgoSetting(enable: boolean): Promise { } } - // Update the setting, restart the extension host, and enable the TypeScript Go extension await tsConfig.update('experimental.useTsgo', enable, target); - await vscode.commands.executeCommand('workbench.action.restartExtensionHost'); } From 4151d4bc5ef31d08fc3c16d79f5042a09c51d7c8 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:41:09 -0700 Subject: [PATCH 0698/4355] Only disable built-in TS if TS go is installed Fixes #269036 We should check both the setting that the native extension is actually installed --- .../src/commands/useTsgo.ts | 4 +++- .../typescript-language-features/src/extension.ts | 5 +++-- .../languageFeatures/util/dependentRegistration.ts | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/useTsgo.ts b/extensions/typescript-language-features/src/commands/useTsgo.ts index 3f6e5f8c2c9..353ff4977bd 100644 --- a/extensions/typescript-language-features/src/commands/useTsgo.ts +++ b/extensions/typescript-language-features/src/commands/useTsgo.ts @@ -6,6 +6,8 @@ import * as vscode from 'vscode'; import { Command } from './commandManager'; +export const tsNativeExtensionId = 'typescriptteam.native-preview'; + export class EnableTsgoCommand implements Command { public readonly id = 'typescript.experimental.enableTsgo'; @@ -27,7 +29,7 @@ export class DisableTsgoCommand implements Command { * @param enable Whether to enable or disable TypeScript Go */ async function updateTsgoSetting(enable: boolean): Promise { - const tsgoExtension = vscode.extensions.getExtension('typescriptteam.native-preview'); + const tsgoExtension = vscode.extensions.getExtension(tsNativeExtensionId); // Error if the TypeScript Go extension is not installed with a button to open the GitHub repo if (!tsgoExtension) { const selection = await vscode.window.showErrorMessage( diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index b528a1e90dd..58e47a6f79d 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import { Api, getExtensionApi } from './api'; import { CommandManager } from './commands/commandManager'; -import { DisableTsgoCommand } from './commands/useTsgo'; +import { DisableTsgoCommand, tsNativeExtensionId } from './commands/useTsgo'; import { registerBaseCommands } from './commands/index'; import { ElectronServiceConfigurationProvider } from './configuration/configuration.electron'; import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter'; @@ -25,7 +25,7 @@ import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Lazy } from './utils/lazy'; import { getPackageInfo } from './utils/packageInfo'; import * as temp from './utils/temp.electron'; -import { conditionalRegistration, requireGlobalConfiguration } from './languageFeatures/util/dependentRegistration'; +import { conditionalRegistration, requireGlobalConfiguration, requireHasVsCodeExtension } from './languageFeatures/util/dependentRegistration'; import { DisposableStore } from './utils/dispose'; export function activate( @@ -55,6 +55,7 @@ export function activate( context.subscriptions.push(conditionalRegistration([ requireGlobalConfiguration('typescript', 'experimental.useTsgo'), + requireHasVsCodeExtension(tsNativeExtensionId), ], () => { // TSGO. Only register a small set of features that don't use TS Server const disposables = new DisposableStore(); diff --git a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts index c445cc20e12..8371b470997 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts @@ -111,3 +111,15 @@ export function requireSomeCapability( client.onDidChangeCapabilities ); } + +export function requireHasVsCodeExtension( + extensionId: string +) { + return new Condition( + () => { + return !!vscode.extensions.getExtension(extensionId); + }, + vscode.extensions.onDidChange + ); +} + From ffaa0291067f9d92bf0f5697a36d4acbf0eee88d Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 1 Oct 2025 11:14:00 -0700 Subject: [PATCH 0699/4355] Updated plan mode and prompts (#269401) * Updated plan mode * Plan mode update for 4.5 --- .github/chatmodes/Plan.chatmode.md | 19 ++++++++++--------- .github/prompts/plan-deep.prompt.md | 4 ++-- .github/prompts/plan-fast.prompt.md | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/chatmodes/Plan.chatmode.md b/.github/chatmodes/Plan.chatmode.md index 7804c3d430b..18b6881452f 100644 --- a/.github/chatmodes/Plan.chatmode.md +++ b/.github/chatmodes/Plan.chatmode.md @@ -1,17 +1,18 @@ --- description: Research and draft an implementation plan -tools: ['search', 'executeTask', 'usages', 'problems', 'get_issue', 'get_issue_comments', 'fetch', 'githubRepo'] +tools: ['executePrompt', 'usages', 'problems', 'githubRepo', 'github.vscode-pull-request-github/activePullRequest', 'search', 'github/get_issue', 'github/get_issue_comments', 'github/get_issue', 'github/get_issue_comments', 'fetch'] --- You are pairing with the user to create a clear, detailed, and actionable plan for the given task, iterating through a of gathering context and drafting the plan for review. Comprehensive context gathering for planning following : -- IF `execute_task` tool is available: MUST run `execute_task` tool, prompted not to NOT pause for user feedback, and to follow and write a . -- ELSE (`execute_task` tool is NOT available): Run via tools yourself, following research_actions. -- Present the plan to the user for feedback and refinement: +1. Context gathering and research: + - MUST run `execute_prompt` tool: Instruct the agent to work autonomously without pausing for user feedback, following to gather context and writing a complete to return to you. + - If `execute_prompt` tool is NOT available: Run via tools yourself. +2. Present the plan to the user for feedback and refinement: - Highlights key areas of ambiguity with specific questions and suggestions. - MANDATORY: Pause for user feedback! - - Handle feedback: Refine the plan directly or research further if more context is needed. + - Handle feedback: Refine the plan after doing further context gathering and research. @@ -24,10 +25,10 @@ Comprehensive information gathering using read-only tools: -- Clear, concise, non-repetitive, and high-signal, or it will be too long to read -- Tailored to the request and context: - - Higher complexity requires more detail - - Higher ambiguity requires more exploration of alternatives +- Style: + - Clear, concise, non-repetitive, and high-signal; optimized for quick human review + - Rich in references to specific/related files, symbols, and documentation; while avoiding excessive code snippets + - Tailored to the task and context: Higher complexity requires more detail, higher ambiguity more alternative approaches, etc. - Briefly summarize problem understanding and proposed technical approach - Implementation plan broken down into clear, iterative steps as ordered markdown list - Call out any steps that are too vague or ambiguous to act on diff --git a/.github/prompts/plan-deep.prompt.md b/.github/prompts/plan-deep.prompt.md index 731db43adf5..351a0ebb252 100644 --- a/.github/prompts/plan-deep.prompt.md +++ b/.github/prompts/plan-deep.prompt.md @@ -1,7 +1,7 @@ --- mode: Plan -description: Deep plan +description: Clarify before planning in more detail --- -Before doing your research discover related code (5 tool calls max) as high-level overview, then ask 3 clarifying questions. Once the user answered, go to *Research*. +Before doing your research look up related code (max 5 tool calls!) to get a high-level overview, then ask 3 clarifying questions. Once the user answered, go to *Context gathering and research*. Be extra detailed in your planning draft. diff --git a/.github/prompts/plan-fast.prompt.md b/.github/prompts/plan-fast.prompt.md index abac0a5aa76..a3d320fa04e 100644 --- a/.github/prompts/plan-fast.prompt.md +++ b/.github/prompts/plan-fast.prompt.md @@ -1,5 +1,5 @@ --- mode: Plan -description: Fast plan +description: Iterate quicker on simple tasks --- -Research as usual, but draft a much more shorter implementation plan. +Planning for faster iteration: Research as usual, but draft a much more shorter implementation plan that focused on just the main steps From f9ff13d96f6a97025883480d6fd23a603123b970 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 14:15:56 -0400 Subject: [PATCH 0700/4355] fix typo and issue w chat a11y view (#269405) fix #268345 --- .../chat/browser/actions/chatAccessibilityHelp.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index d9d262db5d5..fc617dfcb82 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -170,7 +170,7 @@ function getChatFocusKeybindingLabel(keybindingService: IKeybindingService, type let kbs; const fallback = ' (unassigned keybinding)'; if (focus === 'input') { - kbs = keybindingService.lookupKeybindings('workbench.chat.action.focusInput'); + kbs = keybindingService.lookupKeybindings('workbench.action.chat.focusInput'); } else if (focus === 'lastFocused') { kbs = keybindingService.lookupKeybindings('workbench.chat.action.focusLastFocused'); } else { @@ -181,17 +181,17 @@ function getChatFocusKeybindingLabel(keybindingService: IKeybindingService, type } let kb; if (type === 'agentView' || type === 'panelChat') { - if (focus) { - kb = kbs.find(kb => kb.getAriaLabel()?.includes('DownArrow'))?.getAriaLabel(); - } else { + if (focus !== 'input') { kb = kbs.find(kb => kb.getAriaLabel()?.includes('UpArrow'))?.getAriaLabel(); + } else { + kb = kbs.find(kb => kb.getAriaLabel()?.includes('DownArrow'))?.getAriaLabel(); } } else { // Quick chat - if (focus) { - kb = kbs.find(kb => kb.getAriaLabel()?.includes('UpArrow'))?.getAriaLabel(); - } else { + if (focus !== 'input') { kb = kbs.find(kb => kb.getAriaLabel()?.includes('DownArrow'))?.getAriaLabel(); + } else { + kb = kbs.find(kb => kb.getAriaLabel()?.includes('UpArrow'))?.getAriaLabel(); } } return !!kb ? ` (${kb})` : fallback; From c2a064321ed3b248e12b86aef132b510de194e16 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:25:13 -0700 Subject: [PATCH 0701/4355] Pick up TS 5.9.3 Picking up a few small fixes in the latest TS release --- extensions/package-lock.json | 8 ++++---- extensions/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package-lock.json b/extensions/package-lock.json index 483e907fca4..acaba35fbf0 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "devDependencies": { "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", @@ -649,9 +649,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/extensions/package.json b/extensions/package.json index f436a5ecaca..7b1d8defa3e 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "scripts": { "postinstall": "node ./postinstall.mjs" From 88a5c3dc05bbf8bb16b0b7a583b9332b4d249b4b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 1 Oct 2025 12:00:22 -0700 Subject: [PATCH 0702/4355] Fix adding SCM context to chat (#269418) Fix #269159 --- src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 0894841d26d..3983103dcc9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -272,6 +272,7 @@ registerAction2(class extends Action2 { return; } + await widget.waitForReady(); widget.attachmentModel.addContext(SCMHistoryItemContext.asAttachment(provider, historyItem)); } }); @@ -334,4 +335,3 @@ registerAction2(class extends Action2 { } satisfies ISCMHistoryItemChangeVariableEntry); } }); - From 07131248b720fab8a4a83ee2bf0c0eaaf6728f6a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 1 Oct 2025 12:36:02 -0700 Subject: [PATCH 0703/4355] debug: bump js-debug to 1.105 (#269420) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index 129d714dd25..a23473a4663 100644 --- a/product.json +++ b/product.json @@ -52,8 +52,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.104.0", - "sha256": "856db934294bd8b78769dd91c86904c7e35e356bb05b223a9e4d8eb38cb17ae3", + "version": "1.105.0", + "sha256": "0c45b90342e8aafd4ff2963b4006de64208ca58c2fd01fea7a710fe61dcfd12a", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 1858349ee11c650879e6eb5639c32b62dc05450f Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 1 Oct 2025 12:36:24 -0700 Subject: [PATCH 0704/4355] Fix #269218 (#269421) --- .../workbench/contrib/chat/browser/actions/chatClearActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index fdcc55a5aa9..1c7a0e48a34 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -84,6 +84,7 @@ export function registerNewChatActions() { alt: { id: ACTION_ID_OPEN_CHAT, title: localize2('interactiveSession.open', "New Chat Editor"), + icon: Codicon.newFile, precondition: ChatContextKeys.enabled } }, From de07d8939b3ad1abe2176a7f016db84a46e3e5ca Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:22:20 -0700 Subject: [PATCH 0705/4355] Fix ripgrep when called with empty search string Fixes #269417 I don't think you can hit this through the UI but you can through the API/tools --- .../workbench/services/search/node/ripgrepTextSearchEngine.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 188dc598f75..e28971303c8 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -56,6 +56,10 @@ export class RipgrepTextSearchEngine { } })}`); + if (!query.pattern) { + return Promise.resolve({ limitHit: false }); + } + return new Promise((resolve, reject) => { token.onCancellationRequested(() => cancel()); From 77e0632289cb44a0a1fcda78402ab278c2a08c31 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:26:47 +0000 Subject: [PATCH 0706/4355] Hide "Generate instructions" hint when workspace already has instruction files (#265441) * Initial plan * Add method to check for existing instruction files in welcome message logic Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Fix re-rendering logic and create test workspaces for validation Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * refactor(chat): use idiomatic disposal patterns with thenIfNotDisposed utility * Wording and logic fixes --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> Co-authored-by: Harald Kirschner --- .../chat/browser/actions/chatActions.ts | 2 +- .../contrib/chat/browser/chatWidget.ts | 72 ++++++++++++++++--- .../promptSyntax/pickers/promptFilePickers.ts | 2 +- .../computeAutomaticInstructions.ts | 41 +++++++++++ 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 4aeeacf65e2..d5aa9983ebc 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1558,7 +1558,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.generateInstructions', title: localize2('generateInstructions', "Generate Workspace Instructions File"), - shortTitle: localize2('generateInstructions.short', "Generate Instructions"), + shortTitle: localize2('generateInstructions.short', "Generate Agent Instructions"), category: CHAT_CATEGORY, icon: Codicon.sparkle, f1: true, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fce553e4446..1aa5f0bca82 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -19,8 +19,8 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; +import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, thenIfNotDisposed, toDisposable } from '../../../../base/common/lifecycle.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; -import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; import { autorun, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; @@ -339,6 +339,9 @@ export class ChatWidget extends Disposable implements IChatWidget { private scrollLock = true; private _isReady = false; + + private _instructionFilesCheckPromise: Promise | undefined; + private _instructionFilesExist: boolean | undefined; private _onDidBecomeReady = this._register(new Emitter()); private readonly viewModelDisposables = this._register(new DisposableStore()); @@ -357,6 +360,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Cache for prompt file descriptions to avoid async calls during rendering private readonly promptDescriptionsCache = new Map(); + private _isLoadingPromptDescriptions = false; // UI state for temporarily hiding chat history private _historyVisible = true; @@ -1003,7 +1007,6 @@ export class ChatWidget extends Disposable implements IChatWidget { * @internal */ private renderWelcomeViewContentIfNeeded() { - if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal') { return; } @@ -1016,12 +1019,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind); let additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage; if (!additionalMessage) { - const generateInstructionsCommand = 'workbench.action.chat.generateInstructions'; - additionalMessage = new MarkdownString(localize( - 'chatWidget.instructions', - "[Generate instructions]({0}) to onboard AI onto your codebase.", - `command:${generateInstructionsCommand}` - ), { isTrusted: { enabledCommands: [generateInstructionsCommand] } }); + additionalMessage = this._getGenerateInstructionsMessage(); } if (this.contextKeyService.contextMatchesRules(ChatContextKeyExprs.chatSetupTriggerContext)) { welcomeContent = this.getNewWelcomeViewContent(); @@ -1274,6 +1272,50 @@ export class ChatWidget extends Disposable implements IChatWidget { } } + private _getGenerateInstructionsMessage(): IMarkdownString { + // Start checking for instruction files immediately if not already done + if (!this._instructionFilesCheckPromise) { + this._instructionFilesCheckPromise = this._checkForInstructionFiles(); + // Use VS Code's idiomatic pattern for disposal-safe promise callbacks + this._register(thenIfNotDisposed(this._instructionFilesCheckPromise, hasFiles => { + this._instructionFilesExist = hasFiles; + // Only re-render if the current view still doesn't have items and we're showing the welcome message + const hasViewModelItems = this.viewModel?.getItems().length ?? 0; + if (hasViewModelItems === 0) { + this.renderWelcomeViewContentIfNeeded(); + } + })); + } + + // If we already know the result, use it + if (this._instructionFilesExist === true) { + // Don't show generate instructions message if files exist + return new MarkdownString(''); + } else if (this._instructionFilesExist === false) { + // Show generate instructions message if no files exist + const generateInstructionsCommand = 'workbench.action.chat.generateInstructions'; + return new MarkdownString(localize( + 'chatWidget.instructions', + "[Generate Agent Instructions]({0}) to onboard AI onto your codebase.", + `command:${generateInstructionsCommand}` + ), { isTrusted: { enabledCommands: [generateInstructionsCommand] } }); + } + + // While checking, don't show the generate instructions message + return new MarkdownString(''); + } + + private async _checkForInstructionFiles(): Promise { + try { + const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, undefined); + return await computer.hasAgentInstructions(CancellationToken.None); + } catch (error) { + // On error, assume no instruction files exist to be safe + this.logService.warn('[ChatWidget] Error checking for instruction files:', error); + return false; + } + } + private getWelcomeViewContent(additionalMessage: string | IMarkdownString | undefined, expEmptyState?: boolean): IChatViewWelcomeContent { const disclaimerMessage = expEmptyState ? this.chatDisclaimer @@ -1405,7 +1447,8 @@ export class ChatWidget extends Disposable implements IChatWidget { } // If we have prompts to load, load them asynchronously and don't return anything yet - if (promptsToLoad.length > 0) { + // But only if we're not already loading to prevent infinite loop + if (promptsToLoad.length > 0 && !this._isLoadingPromptDescriptions) { this.loadPromptDescriptions(promptsToLoad); return []; } @@ -1480,7 +1523,13 @@ export class ChatWidget extends Disposable implements IChatWidget { } private async loadPromptDescriptions(promptNames: string[]): Promise { - try { + // Don't start loading if the widget is being disposed + if (this._store.isDisposed) { + return; + } + + // Set loading guard to prevent infinite loop + this._isLoadingPromptDescriptions = true; try { // Get all available prompt files with their metadata const promptCommands = await this.promptsService.findPromptSlashCommands(); @@ -1520,6 +1569,9 @@ export class ChatWidget extends Disposable implements IChatWidget { } } catch (error) { this.logService.warn('Failed to load specific prompt descriptions:', error); + } finally { + // Always clear the loading guard, even on error + this._isLoadingPromptDescriptions = false; } } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index e415204ce51..395d477a9c4 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -126,7 +126,7 @@ const UPDATE_INSTRUCTIONS_OPTION: IPromptPickerQuickPickItem = Object.freeze({ type: 'item', label: `$(refresh) ${localize( 'commands.update-instructions.select-dialog.label', - 'Generate instructions...', + 'Generate agent instructions...', )}`, value: URI.parse(INSTRUCTIONS_DOCUMENTATION_URL), pickable: false, diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index a018cd58b19..d66170986b5 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -110,6 +110,47 @@ export class ComputeAutomaticInstructions { this.sendTelemetry(telemetryEvent); } + /** + * Checks if any agent instruction files (.github/copilot-instructions.md or agents.md) exist in the workspace. + * Used to determine whether to show the "Generate Agent Instructions" hint. + * + * @returns true if instruction files exist OR if instruction features are disabled (to hide the hint) + */ + public async hasAgentInstructions(token: CancellationToken): Promise { + const useCopilotInstructionsFiles = this._configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES); + const useAgentMd = this._configurationService.getValue(PromptsConfig.USE_AGENT_MD); + + // If both settings are disabled, return true to hide the hint (since the features aren't enabled) + if (!useCopilotInstructionsFiles && !useAgentMd) { + return true; + } const { folders } = this._workspaceService.getWorkspace(); + + // Check for copilot-instructions.md files + if (useCopilotInstructionsFiles) { + for (const folder of folders) { + const file = joinPath(folder.uri, `.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); + if (await this._fileService.exists(file)) { + return true; + } + } + } + + // Check for agents.md files + if (useAgentMd) { + const resolvedRoots = await this._fileService.resolveAll(folders.map(f => ({ resource: f.uri }))); + for (const root of resolvedRoots) { + if (root.success && root.stat?.children) { + const agentMd = root.stat.children.find(c => c.isFile && c.name.toLowerCase() === 'agents.md'); + if (agentMd) { + return true; + } + } + } + } + + return false; + } + private sendTelemetry(telemetryEvent: InstructionsCollectionEvent): void { // Emit telemetry telemetryEvent.totalInstructionsCount = telemetryEvent.agentInstructionsCount + telemetryEvent.referencedInstructionsCount + telemetryEvent.applyingInstructionsCount + telemetryEvent.listedInstructionsCount; From d4be64cb2a124ea1133c959ead341d1fcadc4490 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 1 Oct 2025 16:49:36 -0400 Subject: [PATCH 0707/4355] fix chat plays sound for completed confirmation in restored chat (#269427) --- .../workbench/contrib/chat/browser/chatAccessibilityService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 4f403e1d5d4..5ff60940c6d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -72,6 +72,9 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi } } acceptElicitation(elicitation: IChatElicitationRequest): void { + if (elicitation.state !== 'pending') { + return; + } const title = typeof elicitation.title === 'string' ? elicitation.title : elicitation.title.value; const message = typeof elicitation.message === 'string' ? elicitation.message : elicitation.message.value; alert(title + ' ' + message); From a8b529221b46707e3caa6682fcbc69ab0ea3f53c Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:40:01 -0700 Subject: [PATCH 0708/4355] Show progress indicator when loading chat session items (#269136) * add loading indicator * set individual props * localize and simplification * update text * Move style to css and use themeicon * remove stale entry --------- Co-authored-by: vijay upadya --- .../contrib/chat/browser/chatEditor.ts | 101 +++++++++++++++--- .../chat/browser/media/chatSessions.css | 22 ++++ 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index e424e1ae8cf..113ced75171 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -4,8 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import * as nls from '../../../../nls.js'; import { raceCancellationError } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -48,6 +52,8 @@ export class ChatEditor extends EditorPane { private _memento: Memento | undefined; private _viewState: IChatViewState | undefined; private dimension = new dom.Dimension(0, 0); + private _loadingContainer: HTMLElement | undefined; + private _editorContainer: HTMLElement | undefined; constructor( group: IEditorGroup, @@ -68,6 +74,7 @@ export class ChatEditor extends EditorPane { } protected override createEditor(parent: HTMLElement): void { + this._editorContainer = parent; this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true); @@ -125,6 +132,48 @@ export class ChatEditor extends EditorPane { super.clearInput(); } + private showLoadingInChatWidget(message: string): void { + if (!this._editorContainer) { + return; + } + + // If already showing, just update text + if (this._loadingContainer) { + const existingText = this._loadingContainer.querySelector('.chat-loading-content span'); + if (existingText) { + existingText.textContent = message; + return; // aria-live will announce the text change + } + this.hideLoadingInChatWidget(); // unexpected structure + } + + // Mark container busy for assistive technologies + this._editorContainer.setAttribute('aria-busy', 'true'); + + this._loadingContainer = dom.append(this._editorContainer, dom.$('.chat-loading-overlay')); + // Accessibility: announce loading state politely without stealing focus + this._loadingContainer.setAttribute('role', 'status'); + this._loadingContainer.setAttribute('aria-live', 'polite'); + // Rely on live region text content instead of aria-label to avoid duplicate announcements + this._loadingContainer.tabIndex = -1; // ensure it isn't focusable + const loadingContent = dom.append(this._loadingContainer, dom.$('.chat-loading-content')); + const spinner = renderIcon(ThemeIcon.modify(Codicon.loading, 'spin')); + spinner.setAttribute('aria-hidden', 'true'); + loadingContent.appendChild(spinner); + const text = dom.append(loadingContent, dom.$('span')); + text.textContent = message; + } + + private hideLoadingInChatWidget(): void { + if (this._loadingContainer) { + this._loadingContainer.remove(); + this._loadingContainer = undefined; + } + if (this._editorContainer) { + this._editorContainer.removeAttribute('aria-busy'); + } + } + override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); if (token.isCancellationRequested) { @@ -138,29 +187,49 @@ export class ChatEditor extends EditorPane { let isContributedChatSession = false; const chatSessionType = getChatSessionType(input); if (chatSessionType !== 'local') { - await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); - const contributions = this.chatSessionsService.getAllChatSessionContributions(); - const contribution = contributions.find(c => c.type === chatSessionType); - if (contribution) { - this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); - isContributedChatSession = true; - } else { - this.widget.unlockFromCodingAgent(); + // Show single loading state for contributed sessions + const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); + this.showLoadingInChatWidget(loadingMessage); + + try { + await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); + const contributions = this.chatSessionsService.getAllChatSessionContributions(); + const contribution = contributions.find(c => c.type === chatSessionType); + if (contribution) { + this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); + isContributedChatSession = true; + } else { + this.widget.unlockFromCodingAgent(); + } + } catch (error) { + this.hideLoadingInChatWidget(); + throw error; } } else { this.widget.unlockFromCodingAgent(); } - const editorModel = await raceCancellationError(input.resolve(), token); + try { + const editorModel = await raceCancellationError(input.resolve(), token); - if (!editorModel) { - throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); - } - const viewState = options?.viewState ?? input.options.viewState; - this.updateModel(editorModel.model, viewState); + if (!editorModel) { + throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); + } + + // Hide loading state before updating model + if (chatSessionType !== 'local') { + this.hideLoadingInChatWidget(); + } - if (isContributedChatSession && options?.preferredTitle) { - editorModel.model.setCustomTitle(options?.preferredTitle); + const viewState = options?.viewState ?? input.options.viewState; + this.updateModel(editorModel.model, viewState); + + if (isContributedChatSession && options?.preferredTitle) { + editorModel.model.setCustomTitle(options?.preferredTitle); + } + } catch (error) { + this.hideLoadingInChatWidget(); + throw error; } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css index 1bd6b83280a..3ba12e266f7 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css @@ -260,3 +260,25 @@ .chat-sessions-tree-container .chat-session-item[data-history-item="true"]:hover { background-color: var(--vscode-list-hoverBackground); } + +/* Chat editor loading overlay styles */ +.chat-loading-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--vscode-editor-background); + z-index: 1000; +} + +.chat-loading-overlay .chat-loading-content { + display: flex; + align-items: center; + gap: 8px; + color: var(--vscode-editor-foreground); +} + +.chat-loading-overlay .codicon { + font-size: 16px; +} From a883884a55015f400a19a78e82a82ab0efa54b27 Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:18:25 -0700 Subject: [PATCH 0709/4355] Revert "Show progress indicator when loading chat session items" (#269448) Revert "Show progress indicator when loading chat session items (#269136)" This reverts commit a8b529221b46707e3caa6682fcbc69ab0ea3f53c. --- .../contrib/chat/browser/chatEditor.ts | 101 +++--------------- .../chat/browser/media/chatSessions.css | 22 ---- 2 files changed, 16 insertions(+), 107 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 113ced75171..e424e1ae8cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -4,12 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; -import * as nls from '../../../../nls.js'; import { raceCancellationError } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -52,8 +48,6 @@ export class ChatEditor extends EditorPane { private _memento: Memento | undefined; private _viewState: IChatViewState | undefined; private dimension = new dom.Dimension(0, 0); - private _loadingContainer: HTMLElement | undefined; - private _editorContainer: HTMLElement | undefined; constructor( group: IEditorGroup, @@ -74,7 +68,6 @@ export class ChatEditor extends EditorPane { } protected override createEditor(parent: HTMLElement): void { - this._editorContainer = parent; this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true); @@ -132,48 +125,6 @@ export class ChatEditor extends EditorPane { super.clearInput(); } - private showLoadingInChatWidget(message: string): void { - if (!this._editorContainer) { - return; - } - - // If already showing, just update text - if (this._loadingContainer) { - const existingText = this._loadingContainer.querySelector('.chat-loading-content span'); - if (existingText) { - existingText.textContent = message; - return; // aria-live will announce the text change - } - this.hideLoadingInChatWidget(); // unexpected structure - } - - // Mark container busy for assistive technologies - this._editorContainer.setAttribute('aria-busy', 'true'); - - this._loadingContainer = dom.append(this._editorContainer, dom.$('.chat-loading-overlay')); - // Accessibility: announce loading state politely without stealing focus - this._loadingContainer.setAttribute('role', 'status'); - this._loadingContainer.setAttribute('aria-live', 'polite'); - // Rely on live region text content instead of aria-label to avoid duplicate announcements - this._loadingContainer.tabIndex = -1; // ensure it isn't focusable - const loadingContent = dom.append(this._loadingContainer, dom.$('.chat-loading-content')); - const spinner = renderIcon(ThemeIcon.modify(Codicon.loading, 'spin')); - spinner.setAttribute('aria-hidden', 'true'); - loadingContent.appendChild(spinner); - const text = dom.append(loadingContent, dom.$('span')); - text.textContent = message; - } - - private hideLoadingInChatWidget(): void { - if (this._loadingContainer) { - this._loadingContainer.remove(); - this._loadingContainer = undefined; - } - if (this._editorContainer) { - this._editorContainer.removeAttribute('aria-busy'); - } - } - override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); if (token.isCancellationRequested) { @@ -187,49 +138,29 @@ export class ChatEditor extends EditorPane { let isContributedChatSession = false; const chatSessionType = getChatSessionType(input); if (chatSessionType !== 'local') { - // Show single loading state for contributed sessions - const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); - this.showLoadingInChatWidget(loadingMessage); - - try { - await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); - const contributions = this.chatSessionsService.getAllChatSessionContributions(); - const contribution = contributions.find(c => c.type === chatSessionType); - if (contribution) { - this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); - isContributedChatSession = true; - } else { - this.widget.unlockFromCodingAgent(); - } - } catch (error) { - this.hideLoadingInChatWidget(); - throw error; + await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); + const contributions = this.chatSessionsService.getAllChatSessionContributions(); + const contribution = contributions.find(c => c.type === chatSessionType); + if (contribution) { + this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); + isContributedChatSession = true; + } else { + this.widget.unlockFromCodingAgent(); } } else { this.widget.unlockFromCodingAgent(); } - try { - const editorModel = await raceCancellationError(input.resolve(), token); - - if (!editorModel) { - throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); - } - - // Hide loading state before updating model - if (chatSessionType !== 'local') { - this.hideLoadingInChatWidget(); - } + const editorModel = await raceCancellationError(input.resolve(), token); - const viewState = options?.viewState ?? input.options.viewState; - this.updateModel(editorModel.model, viewState); + if (!editorModel) { + throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); + } + const viewState = options?.viewState ?? input.options.viewState; + this.updateModel(editorModel.model, viewState); - if (isContributedChatSession && options?.preferredTitle) { - editorModel.model.setCustomTitle(options?.preferredTitle); - } - } catch (error) { - this.hideLoadingInChatWidget(); - throw error; + if (isContributedChatSession && options?.preferredTitle) { + editorModel.model.setCustomTitle(options?.preferredTitle); } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css index 3ba12e266f7..1bd6b83280a 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css @@ -260,25 +260,3 @@ .chat-sessions-tree-container .chat-session-item[data-history-item="true"]:hover { background-color: var(--vscode-list-hoverBackground); } - -/* Chat editor loading overlay styles */ -.chat-loading-overlay { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: var(--vscode-editor-background); - z-index: 1000; -} - -.chat-loading-overlay .chat-loading-content { - display: flex; - align-items: center; - gap: 8px; - color: var(--vscode-editor-foreground); -} - -.chat-loading-overlay .codicon { - font-size: 16px; -} From 8169bfece9caa529517e20d13bf1d8d43a644bb5 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Wed, 1 Oct 2025 16:47:45 -0700 Subject: [PATCH 0710/4355] replaced frog with chat sparkle --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fce553e4446..12de600b7b1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1339,9 +1339,9 @@ export class ChatWidget extends Disposable implements IChatWidget { } const welcomeContent: IChatViewWelcomeContent = { - title: localize('expChatTitle', 'Welcome to Copilot'), + title: localize('expChatTitle', 'Welcome to Visual Studio Code'), message: new MarkdownString(localize('expchatMessage', "Let's get started")), - icon: Codicon.copilotLarge, + icon: Codicon.chatSparkle, inputPart: this.inputPart.element, additionalMessage, isNew: true, From 95bd2af6b6a1b83ab54d3cc52704be19d516de74 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Wed, 1 Oct 2025 16:56:51 -0700 Subject: [PATCH 0711/4355] revised title text --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 12de600b7b1..6db71e598fe 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1339,7 +1339,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } const welcomeContent: IChatViewWelcomeContent = { - title: localize('expChatTitle', 'Welcome to Visual Studio Code'), + title: localize('expChatTitle', 'Ask, Edit, and Build'), message: new MarkdownString(localize('expchatMessage', "Let's get started")), icon: Codicon.chatSparkle, inputPart: this.inputPart.element, From d5c138605187892d19765e4f9b5c5aabdd305311 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:27:45 -0700 Subject: [PATCH 0712/4355] refactor v2 cloud button code path to include file references to participant (#269447) * refactor v2 cloud button code path to include file references to participant * Apply suggestion from @Tyriar Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> * Apply suggestion from @Tyriar Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --------- Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- .../browser/actions/chatExecuteActions.ts | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 8661a083bca..faaca67ffe0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -30,7 +30,7 @@ import { IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgen import { ChatContextKeys, ChatContextKeyExprs } from '../../common/chatContextKeys.js'; import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js'; import { IChatMode, IChatModeService } from '../../common/chatModes.js'; -import { chatAgentLeader, chatVariableLeader } from '../../common/chatParserTypes.js'; +import { chatVariableLeader } from '../../common/chatParserTypes.js'; import { ChatRequestParser } from '../../common/chatRequestParser.js'; import { IChatPullRequestContent, IChatService } from '../../common/chatService.js'; import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js'; @@ -647,9 +647,14 @@ export class CreateRemoteAgentJobAction extends Action2 { private async createWithChatSessions( chatSessionsService: IChatSessionsService, + chatService: IChatService, chatAgentService: IChatAgentService, quickPickService: IQuickInputService, + chatModel: IChatModel, + requestParser: ChatRequestParser, + sessionId: string, widget: IChatWidget, + attachedContext: ChatRequestVariableSet, userPrompt: string, ) { const contributions = chatSessionsService.getAllChatSessionContributions(); @@ -657,17 +662,12 @@ export class CreateRemoteAgentJobAction extends Action2 { if (!agent) { throw new Error('No coding agent selected'); } - // TODO(jospicer): The chat history doesn't get sent to chat participants! - const { name, type } = agent; - const chatAgent = chatAgentService.getAgent(type); - if (!chatAgent) { - throw new Error(`Could not find chat agent with type ${type}`); - } - const trimmed = userPrompt.trim(); - const mentionPrefix = `${chatAgentLeader}${name}`; - const finalPrompt = trimmed.startsWith(mentionPrefix) ? trimmed : `${mentionPrefix} ${trimmed}`; - widget.setInput(finalPrompt); - widget.acceptInput(); + // TODO(jospicer): The previous chat history doesn't get sent to chat participants! + const { type } = agent; + await chatService.sendRequest(sessionId, userPrompt, { + agentIdSilent: type, + attachedContext: attachedContext.asArray(), + }); } private async createWithLegacy( @@ -760,6 +760,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const configurationService = accessor.get(IConfigurationService); const widgetService = accessor.get(IChatWidgetService); const chatAgentService = accessor.get(IChatAgentService); + const chatService = accessor.get(IChatService); const commandService = accessor.get(ICommandService); const quickPickService = accessor.get(IQuickInputService); const remoteCodingAgentService = accessor.get(IRemoteCodingAgentsService); @@ -770,8 +771,8 @@ export class CreateRemoteAgentJobAction extends Action2 { if (!widget) { return; } - const session = widget.viewModel?.sessionId; - if (!session) { + const sessionId = widget.viewModel?.sessionId; + if (!sessionId) { return; } const chatModel = widget.viewModel?.model; @@ -789,7 +790,7 @@ export class CreateRemoteAgentJobAction extends Action2 { userPrompt = 'implement this.'; } - const attachedContext = widget.input.getAttachedAndImplicitContext(session); + const attachedContext = widget.input.getAttachedAndImplicitContext(sessionId); widget.input.acceptInput(true); const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); @@ -798,11 +799,22 @@ export class CreateRemoteAgentJobAction extends Action2 { const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseCloudButtonV2); if (isChatSessionsExperimentEnabled) { - return await this.createWithChatSessions(chatSessionsService, chatAgentService, quickPickService, widget, userPrompt); + return await this.createWithChatSessions( + chatSessionsService, + chatService, + chatAgentService, + quickPickService, + chatModel, + requestParser, + sessionId, + widget, + attachedContext, + userPrompt + ); } // Add the request to the model first - const parsedRequest = requestParser.parseChatRequest(session, userPrompt, ChatAgentLocation.Chat); + const parsedRequest = requestParser.parseChatRequest(sessionId, userPrompt, ChatAgentLocation.Chat); const addedRequest = chatModel.addRequest( parsedRequest, { variables: attachedContext.asArray() }, @@ -826,7 +838,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const userPromptEntry: IChatAgentHistoryEntry = { request: { - sessionId: session, + sessionId, requestId: generateUuid(), agentId: '', message: userPrompt, @@ -861,7 +873,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const historyEntries: IChatAgentHistoryEntry[] = chatRequests .map(req => ({ request: { - sessionId: session, + sessionId, requestId: req.id, agentId: req.response?.agent?.id ?? '', message: req.message.text, From 357b0a70315ca43d8d26369440f511a90a420886 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:27:17 +0900 Subject: [PATCH 0713/4355] Disallow transient env var commands --- .../chatAgentTools/browser/commandLineAutoApprover.ts | 10 ++++++++++ .../test/browser/runInTerminalTool.test.ts | 9 ++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 9a07ec69253..291d53028c9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -29,6 +29,7 @@ export interface ICommandApprovalResultWithReason { export type ICommandApprovalResult = 'approved' | 'denied' | 'noMatch'; const neverMatchRegex = /(?!.*)/; +const transientEnvVarRegex = /^[A-Z_][A-Z0-9_]*=/i; export class CommandLineAutoApprover extends Disposable { private _denyListRules: IAutoApproveRule[] = []; @@ -75,6 +76,15 @@ export class CommandLineAutoApprover extends Disposable { } isCommandAutoApproved(command: string, shell: string, os: OperatingSystem): ICommandApprovalResultWithReason { + // Check if the command has a transient environment variable assignment prefix which we + // always deny for now as it can easily lead to execute other commands + if (transientEnvVarRegex.test(command)) { + return { + result: 'denied', + reason: `Command '${command}' is denied because it contains transient environment variables` + }; + } + // Check the deny list to see if this command requires explicit approval for (const rule of this._denyListRules) { if (this._commandMatchesRule(rule, command, shell, os)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index e1018e96e87..a6bcf77c0e1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -270,7 +270,14 @@ suite('RunInTerminalTool', () => { 'cat `which ls`', 'echo ${HOME}', 'ls {a,b,c}', - 'echo (Get-Date)' + 'echo (Get-Date)', + + // Transient environment variables + 'ls="test" curl https://api.example.com', + 'API_KEY=secret curl https://api.example.com', + 'HTTP_PROXY=proxy:8080 wget https://example.com', + 'VAR1=value1 VAR2=value2 echo test', + 'A=1 B=2 C=3 ./script.sh', ]; suite('auto approved', () => { From 5febb46a3210fbd3f12a6fd51d6f0d9a49476da7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:11:37 +0900 Subject: [PATCH 0714/4355] Refactor terminal tool test cases for clarity Removed duplicate transient environment variable test cases from the runInTerminalTool tests. --- .../test/browser/runInTerminalTool.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index a6bcf77c0e1..e1bdc7f975b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -263,6 +263,13 @@ suite('RunInTerminalTool', () => { 'sort -S 100G file.txt', 'tree -o output.txt', + // Transient environment variables + 'ls="test" curl https://api.example.com', + 'API_KEY=secret curl https://api.example.com', + 'HTTP_PROXY=proxy:8080 wget https://example.com', + 'VAR1=value1 VAR2=value2 echo test', + 'A=1 B=2 C=3 ./script.sh', + // Dangerous patterns 'echo $(whoami)', 'ls $(pwd)', @@ -270,14 +277,7 @@ suite('RunInTerminalTool', () => { 'cat `which ls`', 'echo ${HOME}', 'ls {a,b,c}', - 'echo (Get-Date)', - - // Transient environment variables - 'ls="test" curl https://api.example.com', - 'API_KEY=secret curl https://api.example.com', - 'HTTP_PROXY=proxy:8080 wget https://example.com', - 'VAR1=value1 VAR2=value2 echo test', - 'A=1 B=2 C=3 ./script.sh', + 'echo (Get-Date)' ]; suite('auto approved', () => { From d83f311544b00268aeaec36b8d98b7537dd1c7c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:12:05 +0900 Subject: [PATCH 0715/4355] Detect danger patterns in multi-line --- .../common/terminalChatAgentToolsConfiguration.ts | 6 +++--- .../chatAgentTools/test/browser/runInTerminalTool.test.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index de6b4ac05df..a3f55354e65 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -260,14 +260,14 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { 'cat `which ls`', 'echo ${HOME}', 'ls {a,b,c}', - 'echo (Get-Date)' + 'echo (Get-Date)', + + // Dangerous patterns - multi-line + 'echo "{\n}"', + 'echo @"\n{\n}"@', ]; suite('auto approved', () => { From 24ace46a4b5a92c7a3968cd2a0c323337368238a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:15:42 +0900 Subject: [PATCH 0716/4355] Improve test name formatting --- .../chatAgentTools/test/browser/runInTerminalTool.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 829f381bbb0..eefbc8b6787 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -279,14 +279,14 @@ suite('RunInTerminalTool', () => { suite('auto approved', () => { for (const command of autoApprovedTestCases) { - test(command, async () => { + test(command.replaceAll('\n', '\\n'), async () => { assertAutoApproved(await executeToolTest({ command: command })); }); } }); suite('confirmation required', () => { for (const command of confirmationRequiredTestCases) { - test(command, async () => { + test(command.replaceAll('\n', '\\n'), async () => { assertConfirmationRequired(await executeToolTest({ command: command })); }); } From 80e42490bbf07d83fe573204416be3949ef27bd1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 04:20:51 +0000 Subject: [PATCH 0717/4355] Add setting to disable TodoList description field (#268892) * Add setting to disable TodoList description field Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Add comprehensive tests for TodoList description field setting Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Improve test code quality with type-safe helper function Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Improve setting description to be more user-friendly and explain performance implications Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> --- .../contrib/chat/browser/chat.contribution.ts | 6 +++ .../chatContentParts/chatTodoListWidget.ts | 15 ++++-- .../chat/common/tools/manageTodoListTool.ts | 24 +++++---- .../contrib/chat/common/tools/tools.ts | 7 +-- .../test/browser/chatTodoListWidget.test.ts | 17 +++++- .../common/tools/manageTodoListTool.test.ts | 52 +++++++++++++++++++ 6 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e9a0ea030ca..91813baa523 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -661,6 +661,12 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.todoListTool.writeOnly', "When enabled, the todo tool operates in write-only mode, requiring the agent to remember todos in context."), tags: ['experimental'] }, + 'chat.todoListTool.descriptionField': { + type: 'boolean', + default: true, + description: nls.localize('chat.todoListTool.descriptionField', "When enabled, todo items include detailed descriptions for implementation context. This provides more information but uses additional tokens and may slow down responses."), + tags: ['experimental'] + }, [ChatConfiguration.ThinkingStyle]: { type: 'string', default: product.quality === 'insider' ? 'fixedScrolling' : 'collapsed', diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 01043836ca7..26a4180f830 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -9,7 +9,9 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IChatTodoListService, IChatTodo } from '../../common/chatTodoListService.js'; +import { TodoListToolDescriptionFieldSettingId } from '../../common/tools/manageTodoListTool.js'; export class ChatTodoListWidget extends Disposable { public readonly domNode: HTMLElement; @@ -27,7 +29,8 @@ export class ChatTodoListWidget extends Disposable { private _userHasScrolledManually: boolean = false; constructor( - @IChatTodoListService private readonly chatTodoListService: IChatTodoListService + @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -190,8 +193,9 @@ export class ChatTodoListWidget extends Disposable { todoElement.setAttribute('role', 'listitem'); todoElement.setAttribute('tabindex', '0'); - // Add tooltip if description exists - if (todo.description && todo.description.trim()) { + // Add tooltip if description exists and description field is enabled + const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; + if (includeDescription && todo.description && todo.description.trim()) { todoElement.title = todo.description; } @@ -219,7 +223,7 @@ export class ChatTodoListWidget extends Disposable { todoContent.appendChild(titleElement); todoContent.appendChild(statusElement); - const ariaLabel = todo.description && todo.description.trim() + const ariaLabel = includeDescription && todo.description && todo.description.trim() ? localize('chat.todoList.itemWithDescription', '{0}, {1}, {2}', todo.title, statusText, todo.description) : localize('chat.todoList.item', '{0}, {1}', todo.title, statusText); todoElement.setAttribute('aria-label', ariaLabel); @@ -397,7 +401,8 @@ export class ChatTodoListWidget extends Disposable { completedText.style.verticalAlign = 'middle'; titleElement.appendChild(completedText); } - if (currentTodo && currentTodo.description && currentTodo.description.trim()) { + const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; + if (includeDescription && currentTodo && currentTodo.description && currentTodo.description.trim()) { title = currentTodo.description; } } diff --git a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts index fe1eae3714c..1d41c7a7301 100644 --- a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts @@ -23,10 +23,11 @@ import { localize } from '../../../../../nls.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; export const TodoListToolWriteOnlySettingId = 'chat.todoListTool.writeOnly'; +export const TodoListToolDescriptionFieldSettingId = 'chat.todoListTool.descriptionField'; export const ManageTodoListToolToolId = 'manage_todo_list'; -export function createManageTodoListToolData(writeOnly: boolean): IToolData { +export function createManageTodoListToolData(writeOnly: boolean, includeDescription: boolean = true): IToolData { const baseProperties: any = { todoList: { type: 'array', @@ -44,17 +45,19 @@ export function createManageTodoListToolData(writeOnly: boolean): IToolData { type: 'string', description: 'Concise action-oriented todo label (3-7 words). Displayed in UI.' }, - description: { - type: 'string', - description: 'Detailed context, requirements, or implementation notes. Include file paths, specific methods, or acceptance criteria.' - }, + ...(includeDescription && { + description: { + type: 'string', + description: 'Detailed context, requirements, or implementation notes. Include file paths, specific methods, or acceptance criteria.' + } + }), status: { type: 'string', enum: ['not-started', 'in-progress', 'completed'], description: 'not-started: Not begun | in-progress: Currently working (max 1) | completed: Fully finished with no blockers' }, }, - required: ['id', 'title', 'description', 'status'] + required: includeDescription ? ['id', 'title', 'description', 'status'] : ['id', 'title', 'status'] } } }; @@ -96,7 +99,7 @@ interface IManageTodoListToolInputParams { todoList: Array<{ id: number; title: string; - description: string; + description?: string; status: 'not-started' | 'in-progress' | 'completed'; }>; chatSessionId?: string; @@ -106,6 +109,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { constructor( private readonly writeOnly: boolean, + private readonly includeDescription: boolean, @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, @ILogService private readonly logService: ILogService, @ITelemetryService private readonly telemetryService: ITelemetryService @@ -188,7 +192,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { const todoList = items.map(todo => ({ id: todo.id.toString(), title: todo.title, - description: todo.description, + description: todo.description || '', status: todo.status })); @@ -297,7 +301,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { const todoList: IChatTodo[] = args.todoList.map((parsedTodo) => ({ id: parsedTodo.id, title: parsedTodo.title, - description: parsedTodo.description, + description: parsedTodo.description || '', status: parsedTodo.status })); @@ -367,7 +371,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { } const lines = [`- ${checkbox} ${todo.title}`]; - if (todo.description && todo.description.trim()) { + if (this.includeDescription && todo.description && todo.description.trim()) { lines.push(` - ${todo.description.trim()}`); } diff --git a/src/vs/workbench/contrib/chat/common/tools/tools.ts b/src/vs/workbench/contrib/chat/common/tools/tools.ts index e1e6d5a7b3d..e080c80cc5f 100644 --- a/src/vs/workbench/contrib/chat/common/tools/tools.ts +++ b/src/vs/workbench/contrib/chat/common/tools/tools.ts @@ -10,7 +10,7 @@ import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { ConfirmationTool, ConfirmationToolData } from './confirmationTool.js'; import { EditTool, EditToolData } from './editFileTool.js'; -import { createManageTodoListToolData, ManageTodoListTool, TodoListToolWriteOnlySettingId } from './manageTodoListTool.js'; +import { createManageTodoListToolData, ManageTodoListTool, TodoListToolWriteOnlySettingId, TodoListToolDescriptionFieldSettingId } from './manageTodoListTool.js'; export class BuiltinToolsContribution extends Disposable implements IWorkbenchContribution { @@ -28,8 +28,9 @@ export class BuiltinToolsContribution extends Disposable implements IWorkbenchCo // Check if write-only mode is enabled for the todo tool const writeOnlyMode = this.configurationService.getValue(TodoListToolWriteOnlySettingId) === true; - const todoToolData = createManageTodoListToolData(writeOnlyMode); - const manageTodoListTool = this._register(instantiationService.createInstance(ManageTodoListTool, writeOnlyMode)); + const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; + const todoToolData = createManageTodoListToolData(writeOnlyMode, includeDescription); + const manageTodoListTool = this._register(instantiationService.createInstance(ManageTodoListTool, writeOnlyMode, includeDescription)); this._register(toolsService.registerTool(todoToolData, manageTodoListTool)); // Register the confirmation tool diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index e4a585ed298..89b5721b595 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -8,12 +8,14 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { ChatTodoListWidget } from '../../browser/chatContentParts/chatTodoListWidget.js'; import { IChatTodo, IChatTodoListService } from '../../common/chatTodoListService.js'; import { mainWindow } from '../../../../../base/browser/window.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; suite('ChatTodoListWidget Accessibility', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let widget: ChatTodoListWidget; let mockTodoListService: IChatTodoListService; + let mockConfigurationService: IConfigurationService; const sampleTodos: IChatTodo[] = [ { id: 1, title: 'First task', status: 'not-started' }, @@ -29,7 +31,13 @@ suite('ChatTodoListWidget Accessibility', () => { setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; - widget = store.add(new ChatTodoListWidget(mockTodoListService)); + // Mock the configuration service + mockConfigurationService = { + _serviceBrand: undefined, + getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined + } as any; + + widget = store.add(new ChatTodoListWidget(mockTodoListService, mockConfigurationService)); mainWindow.document.body.appendChild(widget.domNode); }); @@ -132,7 +140,12 @@ suite('ChatTodoListWidget Accessibility', () => { setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; - const emptyWidget = store.add(new ChatTodoListWidget(emptyTodoListService)); + const emptyConfigurationService: IConfigurationService = { + _serviceBrand: undefined, + getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined + } as any; + + const emptyWidget = store.add(new ChatTodoListWidget(emptyTodoListService, emptyConfigurationService)); mainWindow.document.body.appendChild(emptyWidget.domNode); emptyWidget.render('test-session'); diff --git a/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts new file mode 100644 index 00000000000..b9346468426 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { createManageTodoListToolData } from '../../../common/tools/manageTodoListTool.js'; +import { IToolData } from '../../../common/languageModelToolsService.js'; + +suite('ManageTodoListTool Description Field Setting', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function getSchemaProperties(toolData: IToolData): { properties: any; required: string[] } { + assert.ok(toolData.inputSchema); + const schema = toolData.inputSchema as any; + const properties = schema?.properties?.todoList?.items?.properties; + const required = schema?.properties?.todoList?.items?.required; + + assert.ok(properties, 'Schema properties should be defined'); + assert.ok(required, 'Schema required fields should be defined'); + + return { properties, required }; + } + + test('createManageTodoListToolData should include description field when enabled', () => { + const toolData = createManageTodoListToolData(false, true); + const { properties, required } = getSchemaProperties(toolData); + + assert.strictEqual('description' in properties, true); + assert.strictEqual(required.includes('description'), true); + assert.deepStrictEqual(required, ['id', 'title', 'description', 'status']); + }); + + test('createManageTodoListToolData should exclude description field when disabled', () => { + const toolData = createManageTodoListToolData(false, false); + const { properties, required } = getSchemaProperties(toolData); + + assert.strictEqual('description' in properties, false); + assert.strictEqual(required.includes('description'), false); + assert.deepStrictEqual(required, ['id', 'title', 'status']); + }); + + test('createManageTodoListToolData should use default value for includeDescription', () => { + const toolDataDefault = createManageTodoListToolData(false); + const { properties, required } = getSchemaProperties(toolDataDefault); + + // Default should be true (includes description) + assert.strictEqual('description' in properties, true); + assert.strictEqual(required.includes('description'), true); + }); +}); From fe113e85dc060bb554b03f7b83fd49c571484662 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 08:39:56 +0200 Subject: [PATCH 0718/4355] Expected sign in to increase allowance to be clickable (fix microsoft/vscode-internalbacklog#5942) (#269480) --- src/vs/workbench/contrib/chat/browser/chatStatus.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 6daf8411ab7..05f6f0ec6cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -425,8 +425,6 @@ class ChatStatusDashboard extends Disposable { this.createQuotaIndicator(this.element, disposables, localize('quotaDisabled', "Disabled"), localize('completionsLabel', "Code completions"), false); // TODO@bpasero revisit this in the future when Completions are supported this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('chatsLabel', "Chat messages"), false); - - this.element.appendChild($('div.description', undefined, localize('anonymousFooter', "Sign in to increase allowance."))); } // Chat sessions From db54282b0360f8d55f22f2e14fc70205701c878d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 08:50:00 +0200 Subject: [PATCH 0719/4355] No auth: Offer a modified setup dialog that does not ask to sign-in for entry points (fix microsoft/vscode-internalbacklog#5924) (#269483) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 9161baab555..4325d481e24 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -764,7 +764,7 @@ class ChatSetup { alignment: DialogContentsAlignment.Vertical, cancelId: buttons.length - 1, disableCloseButton: true, - renderFooter: this.telemetryService.telemetryLevel !== TelemetryLevel.NONE ? footer => footer.appendChild(this.createDialogFooter(disposables)) : undefined, + renderFooter: footer => footer.appendChild(this.createDialogFooter(disposables, options)), buttonOptions: buttons.map(button => button[2]) }, this.keybindingService, this.layoutService) )); @@ -830,12 +830,17 @@ class ChatSetup { return localize('startUsing', "Start using GitHub Copilot"); } - private createDialogFooter(disposables: DisposableStore): HTMLElement { + private createDialogFooter(disposables: DisposableStore, options?: { forceAnonymous?: ChatSetupAnonymous }): HTMLElement { const element = $('.chat-setup-dialog-footer'); const markdown = this.instantiationService.createInstance(MarkdownRenderer, {}); - const footer = localize({ key: 'settings', comment: ['{Locked="["}', '{Locked="]({1})"}', '{Locked="]({2})"}', '{Locked="]({4})"}', '{Locked="]({5})"}'] }, "By continuing, you agree to {0}'s [Terms]({1}) and [Privacy Statement]({2}). {3} Copilot may show [public code]({4}) suggestions and use your data to improve the product. You can change these [settings]({5}) anytime.", defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl, defaultChat.provider.default.name, defaultChat.publicCodeMatchesUrl, defaultChat.manageSettingsUrl); + let footer: string; + if (options?.forceAnonymous || this.telemetryService.telemetryLevel === TelemetryLevel.NONE) { + footer = localize({ key: 'settingsAnonymous', comment: ['{Locked="["}', '{Locked="]({1})"}', '{Locked="]({2})"}'] }, "By continuing, you agree to {0}'s [Terms]({1}) and [Privacy Statement]({2}).", defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl); + } else { + footer = localize({ key: 'settings', comment: ['{Locked="["}', '{Locked="]({1})"}', '{Locked="]({2})"}', '{Locked="]({4})"}', '{Locked="]({5})"}'] }, "By continuing, you agree to {0}'s [Terms]({1}) and [Privacy Statement]({2}). {3} Copilot may show [public code]({4}) suggestions and use your data to improve the product. You can change these [settings]({5}) anytime.", defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl, defaultChat.provider.default.name, defaultChat.publicCodeMatchesUrl, defaultChat.manageSettingsUrl); + } element.appendChild($('p', undefined, disposables.add(markdown.render(new MarkdownString(footer, { isTrusted: true }))).element)); return element; From 484fdf69b8509c1c9370d913b32e9f6d3a68cc99 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 2 Oct 2025 15:54:36 +0900 Subject: [PATCH 0720/4355] chore: bump electron@37.6.0 (#269279) * Revert "Fix: Disable window shadows on macOS Tahoe to prevent GPU performance issues (#267724)" This reverts commit e278d3a66835c300bb01886317c529f11033db77. * chore: bump electron@37.6.0 * chore: update build --- .npmrc | 4 +- build/checksums/electron.txt | 150 +++++++++--------- cgmanifest.json | 4 +- package-lock.json | 8 +- package.json | 4 +- src/vs/base/common/platform.ts | 4 - .../platform/windows/electron-main/windows.ts | 11 +- 7 files changed, 86 insertions(+), 99 deletions(-) diff --git a/.npmrc b/.npmrc index 077534a3e55..5a44dc617a4 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" -target="37.5.1" -ms_build_id="12409799" +target="37.6.0" +ms_build_id="12502201" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index cc94b314b85..a65112f108e 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -402c841a5fc2638f50b8b9e409d877e78bb6d597cc341fd320502a3845444d62 *chromedriver-v37.5.1-darwin-arm64.zip -f3cfb979890539a28692b885a07be17707c953f538bd28630afe14a8d1526574 *chromedriver-v37.5.1-darwin-x64.zip -b2c813e32f2336817682fe978a8045091c0e3ee1c4aa795c8e891bbd865b6276 *chromedriver-v37.5.1-linux-arm64.zip -39371e49f3ae7b24b9c1f7577db6eff8e9928862884cb6fe578159bcf7882368 *chromedriver-v37.5.1-linux-armv7l.zip -bd5370472537117793e99ca7019e8cb626117de3f6058e5fdcea0adeed364699 *chromedriver-v37.5.1-linux-x64.zip -881569bf30c284f9e3a5d92d98646e3128277f33cfe4c42cde8f74e428c56ae2 *chromedriver-v37.5.1-mas-arm64.zip -cf1216d7b7665452a318f0a8a5803d5606fe136ca185568ad9311f3043cace83 *chromedriver-v37.5.1-mas-x64.zip -79a197269b149fac230e1594b6c728a99e466cd9036f3bf1b3bd0456dc0ad973 *chromedriver-v37.5.1-win32-arm64.zip -dd4d5283e6673c72dbe8795407cbee4c19700d124bd74c3d770941a49f087ec7 *chromedriver-v37.5.1-win32-ia32.zip -78fcaa3f5aea76eb581c886f4368cb6dbd7587623dcdbcd030cbca9a33f79e7b *chromedriver-v37.5.1-win32-x64.zip -6f04df9fe344bc6657eaa0512ff36ba83d59c80e2d9dd2e84582f1004413eac2 *electron-api.json -7dc03344e9db03e431bf51862c106a118b03ede35b15a00fbc17fbd5e25594b8 *electron-v37.5.1-darwin-arm64-dsym-snapshot.zip -bcc788cb2f69f8803f73647f67ffd338e1a6975f31999ed19e454efb1ca8155b *electron-v37.5.1-darwin-arm64-dsym.zip -5ae44dd31c395987a4821c8b50c3b96290cd9e84929a052b66d9aba2ad96738c *electron-v37.5.1-darwin-arm64-symbols.zip -4594071c44edc118bc9c58175148a0881c05d4ff95b8e9d7a24465c7a54131a3 *electron-v37.5.1-darwin-arm64.zip -57148d80c6cfe6e7638b7a4e8dc246a9f478a441771c4550d30298ae02a821aa *electron-v37.5.1-darwin-x64-dsym-snapshot.zip -a18d21552a2fcbe7cf48de7ce811055d7806293b6a7d6eaf726058eebb1956e2 *electron-v37.5.1-darwin-x64-dsym.zip -be787d8d7ca4ad668f0888bce9a18ef9a8e3c9c35a26a8738bc51839a3426276 *electron-v37.5.1-darwin-x64-symbols.zip -27cd4f024df5c7bc377a5e93dc4815567ac301ee8bb1ebd5ba04bf448bdebc28 *electron-v37.5.1-darwin-x64.zip -46ff3e63dc5a8f92ee825bfcc28d648b50b83c89481302bbce99ce33c39d50cc *electron-v37.5.1-linux-arm64-debug.zip -bdab607774e51e8f9d47bd15cf3194d7ce6523556ec6d8941ce655d2fc6e3e53 *electron-v37.5.1-linux-arm64-symbols.zip -87ff5353f05b7d4ed83b0131dc73923957c5a9948c4748c9c696e94b90c5546b *electron-v37.5.1-linux-arm64.zip -ec17293c3ca568932632f1c1828d16f1bc0f602b7300a59b8269558b78632a62 *electron-v37.5.1-linux-armv7l-debug.zip -d7d80d4e2c2f3d5823ba94f6d036e475fa68b4271350c00304e75a28c5e849a6 *electron-v37.5.1-linux-armv7l-symbols.zip -3fb1c01cadd627e7bb2126629557c277441dc1064de1c7e1946f7ce26f743abb *electron-v37.5.1-linux-armv7l.zip -58e0ccf1490458cbaa69d60c3028ebc821bebdadb4b8a4c7c939cf15aa2930fc *electron-v37.5.1-linux-x64-debug.zip -a99879b553e124ef9f2cb080f22c4f564121f3b89023230f418808925ced1688 *electron-v37.5.1-linux-x64-symbols.zip -00873612a8c1b0c29dff6d3c759585013deaacf64e38b4cb173b7b18d0f272fc *electron-v37.5.1-linux-x64.zip -87f46bc78cb7d2b5a5b08835258dc7b533e724f9cb6307ff0ff9aae601fefdb2 *electron-v37.5.1-mas-arm64-dsym-snapshot.zip -1a67aeb10b98795f6c996c6f4e74316e2aad7309a85f75e8de4e3918e126f5e7 *electron-v37.5.1-mas-arm64-dsym.zip -1d0e7a0fe6004f44f507a058079013a391f55a8c43632f286e96947d06048a19 *electron-v37.5.1-mas-arm64-symbols.zip -9cb3475a84267124e009936382030daae07c8d2ac0fa394cdc3f3c84cb457be5 *electron-v37.5.1-mas-arm64.zip -ceb7f7df5fb6edf30792adbdf5a0707adcf7a20ac014165c5ff3f928bba5333d *electron-v37.5.1-mas-x64-dsym-snapshot.zip -0f0bcd0dfa65f219daf917d9a052dc4c9bae081a195f03e0b542884bb2478c62 *electron-v37.5.1-mas-x64-dsym.zip -67cf1da93fdbcaf873d425ad0138a6b1747cc0dd71f19abb9d8744e59ade6ed6 *electron-v37.5.1-mas-x64-symbols.zip -fa718a4acbe2f22cc133860ddbc04b5f9b3a42e52c92eedd2839172819f272a9 *electron-v37.5.1-mas-x64.zip -4f4a7585ec3de28f2ce23d72e67e0c6b0e3698ea320d2f117b8b9c7f0a9d1a23 *electron-v37.5.1-win32-arm64-pdb.zip -42a49696e5a1ac7471aba39a4d5150fbcdc2db442df89dda13d3dfa060e800e4 *electron-v37.5.1-win32-arm64-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.5.1-win32-arm64-toolchain-profile.zip -81345df1a876be9068d722fa5157b395cf59146a5c84d656594ae21d78c00cfa *electron-v37.5.1-win32-arm64.zip -ad0435b335dfab8c92e6926c2ee772fd2e1b458a409e6f854173cbca026ac239 *electron-v37.5.1-win32-ia32-pdb.zip -33944ff917b7b0dde86f6e8a5a65250f672d3933f94ee53c89aeb17752b6a3ab *electron-v37.5.1-win32-ia32-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.5.1-win32-ia32-toolchain-profile.zip -0b35a0742a009dce60de3e1f45ac1b30f898ffbfdaa0a5df34f4ab47af47a8cb *electron-v37.5.1-win32-ia32.zip -6186513dd721970017454f9632328a224dc69543551e0eac2272f0de368c6397 *electron-v37.5.1-win32-x64-pdb.zip -f48b8e7588bb354df1cd920863ecb5ab9df2ea807905e80395845ff83801a42f *electron-v37.5.1-win32-x64-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.5.1-win32-x64-toolchain-profile.zip -072eed6bf672557a1d15ef939ac0a09082ab7937edfad0e95dd7365d7027f8b1 *electron-v37.5.1-win32-x64.zip -d5e04bd18a75dc53bb84263a72ac2f80da91268570129d01c7ef7ab63a7861cf *electron.d.ts -39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.5.1-darwin-arm64.zip -210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.5.1-darwin-x64.zip -f0792bdd28ac2231e2d10bdb89da0221e9b15149a4761448e6bfd50ba8e76895 *ffmpeg-v37.5.1-linux-arm64.zip -5bd4adf23596c09bbb671952b73427f6701a7e9aee647979925e9cc4ff973045 *ffmpeg-v37.5.1-linux-armv7l.zip -561a7685536c133d2072e2e2b5a64ca3897bb8c71624158a6fe8e07cae9116c9 *ffmpeg-v37.5.1-linux-x64.zip -39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.5.1-mas-arm64.zip -210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.5.1-mas-x64.zip -2e73958715132b6206bd0e5d990a82ca5ed44b2195232a294c1b81b019a570bb *ffmpeg-v37.5.1-win32-arm64.zip -54b44a2c8229b2efff75344bafb40fa6b1cc5c213200bbe55c75eddb384cc7f2 *ffmpeg-v37.5.1-win32-ia32.zip -dfe702764113c7697e99a9e15a9692917bdd52aa238fa2dc1d5a604bec8e983e *ffmpeg-v37.5.1-win32-x64.zip -2ab5c34f65a30d57368036c52e9a6cc6ba2e986c64636a8b5c29ec5e314ede1b *hunspell_dictionaries.zip -166f4b22df018eb17bf54db5e2f9c63b5f66a4204357c6042246de29f3584db7 *libcxx-objects-v37.5.1-linux-arm64.zip -e264a5a57b943900354363cfe1116b85bec71afe123a65d4570488a42596bc4f *libcxx-objects-v37.5.1-linux-armv7l.zip -c702164d2ba4af422bb1a22ac36eedf56d4972533c6334754cbae8b265d81b92 *libcxx-objects-v37.5.1-linux-x64.zip -b224fd5cb44ec244b388f5c7328ec34d2f394c246db61d4240ed776cf3e38c10 *libcxx_headers.zip -edf24979ef27199ad5cd9de9b52a56ef9bad022c7d9daff59c81a873bf0185cc *libcxxabi_headers.zip -deab9428c6445268209d4ebb2794b7710af449186533832e420cd608ccb889c4 *mksnapshot-v37.5.1-darwin-arm64.zip -e8a54dc4e0419563c40ccb4d06863df0eb888213e8ffa4cc7dc559b38e16b1ff *mksnapshot-v37.5.1-darwin-x64.zip -0f61ee3f36afb6efc7330ae4e35f39b1e1b8987d3dc11e225b4331e7b30065aa *mksnapshot-v37.5.1-linux-arm64-x64.zip -4f17688d052525473b186f8d9230019fcf10984f5f05325a1e56332f5f63d5c5 *mksnapshot-v37.5.1-linux-armv7l-x64.zip -c94cdd58d20b25df50563f5616430fd95085ad44eb2200bab526240170951302 *mksnapshot-v37.5.1-linux-x64.zip -dd9f6c2cd06e6408f631f97bb604d1d0b541d8654202817d9c5d1e43b68d2db8 *mksnapshot-v37.5.1-mas-arm64.zip -b68fede7f706458860c72bac4ccec44c788f1f63da78d82e67f08cce45841bf2 *mksnapshot-v37.5.1-mas-x64.zip -5518e77702b47580491d301b6898e3c3bad24fa4e4e42e3662e8c4e3fc2bba88 *mksnapshot-v37.5.1-win32-arm64-x64.zip -478220e0f96739809195b1d306b57cbf9b2a160b2d2a7d5ce5e39af67f79424d *mksnapshot-v37.5.1-win32-ia32.zip -38858757bf5884af6f2a08e38df77fa77560db299d70e047e6b70f5edcce2a64 *mksnapshot-v37.5.1-win32-x64.zip +ea1b52ec0af04037b419897585ebb2e81f65ccd79dd8f331e26578d042c01a2f *chromedriver-v37.6.0-darwin-arm64.zip +667a9067a774a5ada82a44bc310a23657dc2942067732b6c2879ae0042e2faf2 *chromedriver-v37.6.0-darwin-x64.zip +5c588569bbbca2bcef0881ea877d70dafcf7955e0d19765797f60e2c4d228c8a *chromedriver-v37.6.0-linux-arm64.zip +87a781e82f4fc530820311edc059eafe30e9cec673704c8e73ebf452c5382bc6 *chromedriver-v37.6.0-linux-armv7l.zip +e1c04c3826e506765e90d95738d1cf0ecbb3a798f2f3a7bf17d0cc03fe0be1fe *chromedriver-v37.6.0-linux-x64.zip +53e9d63c01eb018f5be1ebf6d2cba92c25b3097f1500f3d7fe1703447f7f3452 *chromedriver-v37.6.0-mas-arm64.zip +d556851da7ba16ae4b0a39e0c74a1295dfc42875a485d01483babd8e180e2f82 *chromedriver-v37.6.0-mas-x64.zip +0687f18325df7ce6845491fda61b8b4c40d0cc268c8fa54448591461cfd7f7b8 *chromedriver-v37.6.0-win32-arm64.zip +e7a5e4ce6bd6f6d34bcf773ac0796924582ec0c3b37c9442d874b5617b38be69 *chromedriver-v37.6.0-win32-ia32.zip +a1491a67dd7b2a1300283ac862e9ff97adc9db279c3dad0e843e441758fd7e2a *chromedriver-v37.6.0-win32-x64.zip +56e01ec4745a98b0cec8fb9aad00396072127883e76781d912bf3410c1da4cfc *electron-api.json +2f71423b81668b01a1e823593696800f22c262170e89372a3a00388f288cc914 *electron-v37.6.0-darwin-arm64-dsym-snapshot.zip +d632b133b742b7a9c94baa6577cbf528e6cd3a92afbf21245b56d37c580b062b *electron-v37.6.0-darwin-arm64-dsym.zip +a4f47ecea85e134d1b305b7286ef0ada0de714e2874c267169d9d3eafb2c51d2 *electron-v37.6.0-darwin-arm64-symbols.zip +82869e916e74cb763c03fefb5fcee1fc99536e597716d4f3a9c716f9c37effab *electron-v37.6.0-darwin-arm64.zip +5314570f28ca70a6aead23b1902da9a0cb120abb364e3bed12605f36571a4ce2 *electron-v37.6.0-darwin-x64-dsym-snapshot.zip +5689342a29ab4b9fa1b3f5b153d3eb9e7dbe12a10c07a7877b368b3ffbe82004 *electron-v37.6.0-darwin-x64-dsym.zip +978cf0fd18f2b2518e44c35cfd63d6268d28aadeff759212fe4d9b2bf3efa3b9 *electron-v37.6.0-darwin-x64-symbols.zip +258a104f781c50939ec6b45177a6f810da646ade15567a09cf9d14ec81b82cb2 *electron-v37.6.0-darwin-x64.zip +f2053132ca60ec546fccfbf0265d54b1405ad28156fda17c621a461b0c1be2e0 *electron-v37.6.0-linux-arm64-debug.zip +11c2461e956e525ab36784db0e2817fe35ff53a1154285f7ea2827df6f2954d8 *electron-v37.6.0-linux-arm64-symbols.zip +7c76a5147a9870b8bcbd859278148e1e326f98ea6a5dcb3ac2707d39bd22b2d5 *electron-v37.6.0-linux-arm64.zip +92f444a60f677a41d30afaadc0c8e754888ca4829a859b14459c9109b68e1fb0 *electron-v37.6.0-linux-armv7l-debug.zip +2ef9ee6afd566654950f06f8c8f32186133eadd015d74f0074d4c5a7d93b8be2 *electron-v37.6.0-linux-armv7l-symbols.zip +0d4a49b5f462972173daf6e919664cc04cad7a7d2a96b074cb181ea07bb0cd74 *electron-v37.6.0-linux-armv7l.zip +4b9dee173eddf90b2969b74ed86c6a81cd97c208e35b3db115da1f6534e31308 *electron-v37.6.0-linux-x64-debug.zip +cefdb0e96f980fed3ae22c62a204ccb6754311959ba2867a759887c27cbf7f09 *electron-v37.6.0-linux-x64-symbols.zip +02e644d75392a1ea8991106bc77e1db243ee1fc0c23107dc3b253ed545dd4c66 *electron-v37.6.0-linux-x64.zip +3fbfb12b83eb678a84a102808a338dc7d828362a012be0f315ccbfee098aeb1c *electron-v37.6.0-mas-arm64-dsym-snapshot.zip +ce6f18733a966fc9f2ab8e3ca0c9ee15f8ca1abd7f219990fe4795ff9e4946d7 *electron-v37.6.0-mas-arm64-dsym.zip +293ac1f466ec8d6888df1702e2ba2d2f1414068259af5ece7a0170044b2d2de3 *electron-v37.6.0-mas-arm64-symbols.zip +ecc1efd59bd23ae508cf0595594c8dda225d0e6eeb2df7f90b56cdb763aeb2c1 *electron-v37.6.0-mas-arm64.zip +c378220d65f5cf4b8ecd8823a4d8499111c77d2377b6bcb7d0727c567254ec06 *electron-v37.6.0-mas-x64-dsym-snapshot.zip +22c718ab643ff134262a3308104814f9789307f43ec045e6a132dfb57d7bc364 *electron-v37.6.0-mas-x64-dsym.zip +022d38652a15f44b13c3ee6a0691650f3dc7d0fd6827f79877de3f8bbd3bd68c *electron-v37.6.0-mas-x64-symbols.zip +258c44b8bae66e714cb26b11451f97e2678e9d0b6536cac19d74d49544f5805f *electron-v37.6.0-mas-x64.zip +c4927ece20680a6d2940ce5acee0c051abb96c8ca09c071bced548fbbfa9b3b1 *electron-v37.6.0-win32-arm64-pdb.zip +469335cb89bca0fcaefd33922f9db39f283b8a6009e84dadb8bb98de94e4c67c *electron-v37.6.0-win32-arm64-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-arm64-toolchain-profile.zip +1d09e49e246fc36052e4eb55013a907af6d1754521924c8113ad621eca2e8bd3 *electron-v37.6.0-win32-arm64.zip +bce61cf19e53390654bd2a57706462d40ba7da4c83750cfe2242279961bffbf7 *electron-v37.6.0-win32-ia32-pdb.zip +9c85d0eff9eb1bfbb3ef7479fbe709204f594cbdefa745df68e7491013f20c6f *electron-v37.6.0-win32-ia32-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-ia32-toolchain-profile.zip +62ba4c8ba5734fbd0371ec5d04481f498d5b202024a3083fe428d0df55f88ff4 *electron-v37.6.0-win32-ia32.zip +6fe8d1e8f40a46b84ecac8e53e0607e9b43e2afe24cac37a7912a3413b210847 *electron-v37.6.0-win32-x64-pdb.zip +f3fe1e92a4f6977d24848be13d15eafc1558f21230387c66c0b5f41051ba2049 *electron-v37.6.0-win32-x64-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-x64-toolchain-profile.zip +4412a99d07f32a79de16e5a44739062f5fe27efd68cd9ad95122c560f33704b8 *electron-v37.6.0-win32-x64.zip +0bcd0d5f201de6d8903d2860ede68911c834be99d24fb6482aeee492d2b5265a *electron.d.ts +39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.6.0-darwin-arm64.zip +210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.6.0-darwin-x64.zip +f0792bdd28ac2231e2d10bdb89da0221e9b15149a4761448e6bfd50ba8e76895 *ffmpeg-v37.6.0-linux-arm64.zip +5bd4adf23596c09bbb671952b73427f6701a7e9aee647979925e9cc4ff973045 *ffmpeg-v37.6.0-linux-armv7l.zip +561a7685536c133d2072e2e2b5a64ca3897bb8c71624158a6fe8e07cae9116c9 *ffmpeg-v37.6.0-linux-x64.zip +39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.6.0-mas-arm64.zip +210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.6.0-mas-x64.zip +e6e0b42b1afd3bed31fdeda0d5e9e3ae71c7a5dd950ef7f137ec22b8eb53f372 *ffmpeg-v37.6.0-win32-arm64.zip +dc5509c12b30983c200a1a7059673ce093a9bdf38197e3ddcd9a2c33591e1811 *ffmpeg-v37.6.0-win32-ia32.zip +b60372e7431ad40292a77ffc75f011e06c528589f20e6596de9c9090356d1699 *ffmpeg-v37.6.0-win32-x64.zip +4b80ff3d9e48d7b00cdf8265a6a8d7b1a14ed403ca2c7266c1e755fbbbbf4671 *hunspell_dictionaries.zip +00c3f8aff441d6a4b12d076055760895760dbc4f42708358d71f0a2e6937a509 *libcxx-objects-v37.6.0-linux-arm64.zip +71cd9bc71ef32d25aa50ed2aec8859ae6213b4e10c2e7d9cd6174b4221bd023b *libcxx-objects-v37.6.0-linux-armv7l.zip +95bf4671c353ea80e6c6d579e2513200329ba449761ba02dfe3f93ee213cced2 *libcxx-objects-v37.6.0-linux-x64.zip +421b28d575bc966cb17cd3747d9c6a62050648792ec15e5bf8f1eb46f0931e19 *libcxx_headers.zip +006ccb83761a3bf5791d165bc9795e4de3308c65df646b4cbcaa61025fe7a6c5 *libcxxabi_headers.zip +9a50ca41df5b99d2c0557ee5b1db6657baca398f557109955f54433ed64aa7e1 *mksnapshot-v37.6.0-darwin-arm64.zip +6544c9c5007fc9dd5ee3480aa1b5b71a8a5797e492c4f6ef69c7f59b0ee3fc00 *mksnapshot-v37.6.0-darwin-x64.zip +ff7c115df6caf20383765ec4b236c5220e7a32d842ddaabd209cbe063b018b0f *mksnapshot-v37.6.0-linux-arm64-x64.zip +3a4b458aaed19854ff69151bdadd542a398ac670fed4be0e59402e24a461faf4 *mksnapshot-v37.6.0-linux-armv7l-x64.zip +c46f5fe87bc799a5661c0926d4572ad488805210be85a7e1cb4c6e2068697628 *mksnapshot-v37.6.0-linux-x64.zip +5d5e202f46f9654429b8e175bcba4ef4174dd2c3ab8a27a63ea9b0bcaec0c486 *mksnapshot-v37.6.0-mas-arm64.zip +31a5dd8f72d8910335e266eea2cd478e4d45fe16de2118368466401db8eca6f1 *mksnapshot-v37.6.0-mas-x64.zip +a4ea3aa43b1c0efbf27c3a4a2ea9269f12d855fd290d3834c0f7de34e18c4ab1 *mksnapshot-v37.6.0-win32-arm64-x64.zip +ab0f8bcd2af4fbe461c23fa06cbcd9f62187ba080a9f3c56f24c40f3f5ba1f59 *mksnapshot-v37.6.0-win32-ia32.zip +4dc1c6dbf2c2568f0221ec06ca4029919e406b7a466ec4b722f704b579b3e3fa *mksnapshot-v37.6.0-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index 26248d63937..dc33f707d98 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "432be6c97193b57d5df402b968e0c7ecc2cbb9ff" + "commitHash": "9a2b4f84be2f4bcc468a63ef93520e60790b8f3c" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "37.5.1" + "version": "37.6.0" }, { "component": { diff --git a/package-lock.json b/package-lock.json index ae95923f2b7..4572a94e6ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.5.1", + "electron": "37.6.0", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", @@ -6294,9 +6294,9 @@ "dev": true }, "node_modules/electron": { - "version": "37.5.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-37.5.1.tgz", - "integrity": "sha512-RqN3dl6I5yhmynkUc3pUzM6qFCvANau3VGRX9xQEh7FYdwmkqVxKXYM5enrE9LW7j7PzHomQQn6+J2xaF7BHsQ==", + "version": "37.6.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-37.6.0.tgz", + "integrity": "sha512-8AANcn6irYQ7cTAJRY7r0CovWckcGCHUniQecyGhw/jJ25vWwitVhF97skF+EyDztiEI6YBoF0G6tx1s37bO3g==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 1648fbe73d6..d31f955301e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.105.0", - "distro": "8b551ae26e4dadc3f6ece34bbbc3feea81ce4a97", + "distro": "7eefb0edc90d0589e8f481c8ed3cfe00bd65c738", "author": { "name": "Microsoft Corporation" }, @@ -159,7 +159,7 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.5.1", + "electron": "37.6.0", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 8c0d6c319e9..cf76f7ce0e1 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -278,7 +278,3 @@ export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); export function isBigSurOrNewer(osVersion: string): boolean { return parseFloat(osVersion) >= 20; } - -export function isTahoe(osVersion: string): boolean { - return parseFloat(osVersion) === 25; -} diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 52317939863..57d8b534e68 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import electron, { Display, Rectangle } from 'electron'; -import { release } from 'os'; import { Color } from '../../../base/common/color.js'; import { Event } from '../../../base/common/event.js'; import { join } from '../../../base/common/path.js'; -import { IProcessEnvironment, isLinux, isMacintosh, isTahoe, isWindows } from '../../../base/common/platform.js'; +import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; import { URI } from '../../../base/common/uri.js'; import { IAuxiliaryWindow } from '../../auxiliaryWindow/electron-main/auxiliaryWindow.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; @@ -187,14 +186,6 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt if (windowSettings?.clickThroughInactive === false) { options.acceptFirstMouse = false; } - - // Mac OS 26.?.? has a `WindowServer` bug that causes (some?) windows with shadows - // to cause 80%+ GPU load. - // See: https://github.com/electron/electron/issues/48311 - // TODO: once the bug is fixed in the OS, lock this into a specific version. - if (isTahoe(release())) { - options.hasShadow = false; - } } if (overrides?.disableFullscreen) { From 0247e0bd51839a7a609ae9151724454ea0a23586 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 2 Oct 2025 09:49:46 +0200 Subject: [PATCH 0721/4355] Rename excludeMode to excludeAgent (#269484) --- .../languageProviders/promptValidator.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index aa42e8d8d74..f8cf779af10 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -130,7 +130,7 @@ export class PromptValidator { } case PromptsType.instructions: this.validateApplyTo(attributes, report); - this.validateExcludeMode(attributes, report); + this.validateExcludeAgent(attributes, report); break; case PromptsType.mode: @@ -292,13 +292,13 @@ export class PromptValidator { } } - private validateExcludeMode(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === 'excludeMode'); + private validateExcludeAgent(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { + const attribute = attributes.find(attr => attr.key === 'excludeAgent'); if (!attribute) { return; } if (attribute.value.type !== 'array') { - report(toMarker(localize('promptValidator.excludeModeMustBeArray', "The 'excludeMode' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.excludeAgentMustBeArray', "The 'excludeAgent' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); return; } } @@ -306,7 +306,7 @@ export class PromptValidator { const validAttributeNames = { [PromptsType.prompt]: ['description', 'model', 'tools', 'mode'], - [PromptsType.instructions]: ['description', 'applyTo', 'excludeMode'], + [PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'], [PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions'] }; const validAttributeNamesNoExperimental = { @@ -320,7 +320,7 @@ export function getValidAttributeNames(promptType: PromptsType, includeExperimen } export function isExperimentalAttribute(attributeName: string): boolean { - return attributeName === 'advancedOptions' || attributeName === 'excludeMode'; + return attributeName === 'advancedOptions' || attributeName === 'excludeAgent'; } function toMarker(message: string, range: Range, severity = MarkerSeverity.Error): IMarkerData { From 8eb73fabb79e699a29c361b7919c2e1338eb1925 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Oct 2025 10:51:20 +0200 Subject: [PATCH 0722/4355] fix https://github.com/microsoft/vscode-internalbacklog/issues/5955 (#269492) --- .../platform/mcp/common/mcpGalleryService.ts | 273 +++++++++++++++++- 1 file changed, 272 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 9436ba9800f..efaa31eb4c9 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -22,7 +22,7 @@ import { basename } from '../../../base/common/path.js'; interface IMcpRegistryInfo { readonly id: string; readonly isLatest: boolean; - readonly publishedAt: string; + readonly publishedAt?: string; readonly updatedAt: string; } @@ -366,6 +366,276 @@ namespace McpServerOldSchema { export const SERIALIZER = new Serializer(); } +namespace McpServer1ESSchema { + + interface RawGalleryMcpServerInput { + readonly description?: string; + readonly is_required?: boolean; + readonly format?: 'string' | 'number' | 'boolean' | 'filepath'; + readonly value?: string; + readonly is_secret?: boolean; + readonly default?: string; + readonly choices?: readonly string[]; + } + + interface RawGalleryMcpServerVariableInput extends RawGalleryMcpServerInput { + readonly variables?: Record; + } + + interface RawGalleryMcpServerPositionalArgument extends RawGalleryMcpServerVariableInput { + readonly type: 'positional'; + readonly value_hint?: string; + readonly is_repeated?: boolean; + } + + interface RawGalleryMcpServerNamedArgument extends RawGalleryMcpServerVariableInput { + readonly type: 'named'; + readonly name: string; + readonly is_repeated?: boolean; + } + + interface RawGalleryMcpServerKeyValueInput extends RawGalleryMcpServerVariableInput { + readonly name: string; + readonly value?: string; + } + + type RawGalleryMcpServerArgument = RawGalleryMcpServerPositionalArgument | RawGalleryMcpServerNamedArgument; + + interface McpServerDeprecatedRemote { + readonly transport_type?: 'streamable' | 'sse'; + readonly transport?: 'streamable' | 'sse'; + readonly url: string; + readonly headers?: ReadonlyArray; + } + + type RawGalleryMcpServerRemotes = ReadonlyArray; + + type RawGalleryTransport = RawGalleryStdioTransport | RawGalleryStreamableHttpTransport | RawGallerySseTransport; + + interface RawGalleryStdioTransport { + readonly type: 'stdio'; + } + + interface RawGalleryStreamableHttpTransport { + readonly type: 'streamable-http'; + readonly url: string; + readonly headers?: ReadonlyArray; + } + + interface RawGallerySseTransport { + readonly type: 'sse'; + readonly url: string; + readonly headers?: ReadonlyArray; + } + + interface RawGalleryMcpServerPackage { + readonly registry_name: string; + readonly name: string; + readonly registry_type: 'npm' | 'pypi' | 'docker-hub' | 'nuget' | 'remote' | 'mcpb'; + readonly registry_base_url?: string; + readonly identifier: string; + readonly version: string; + readonly file_sha256?: string; + readonly transport?: RawGalleryTransport; + readonly package_arguments?: readonly RawGalleryMcpServerArgument[]; + readonly runtime_hint?: string; + readonly runtime_arguments?: readonly RawGalleryMcpServerArgument[]; + readonly environment_variables?: ReadonlyArray; + } + + interface RawGalleryMcpServer { + readonly id: string; + readonly name: string; + readonly description: string; + readonly version_detail: { + readonly version: string; + readonly release_date: string; + readonly is_latest: boolean; + }; + readonly repository?: { + readonly source: string; + readonly url: string; + readonly id?: string; + readonly subfolder?: string; + readonly readme?: string; + }; + readonly created_at: string; + readonly updated_at: string; + readonly packages?: readonly RawGalleryMcpServerPackage[]; + readonly remotes?: RawGalleryMcpServerRemotes; + } + + interface RawGalleryMcpServersResult { + readonly metadata?: { + readonly count: number; + readonly total?: number; + readonly next_cursor?: string; + }; + readonly servers: readonly RawGalleryMcpServer[]; + } + + class Serializer implements IGalleryMcpServerDataSerializer { + + public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { + if (!input || !Array.isArray(input.servers)) { + return undefined; + } + + const from = input; + + const servers: IRawGalleryMcpServer[] = []; + for (const server of from.servers) { + const rawServer = this.toRawGalleryMcpServer(server); + if (!rawServer) { + return undefined; + } + servers.push(rawServer); + } + + return { + metadata: from.metadata, + servers + }; + } + + public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { + if (!input || input.server || input.$schema) { + return undefined; + } + + const from = input; + + function convertServerInput(input: RawGalleryMcpServerInput): IMcpServerInput { + return { + ...input, + isRequired: input.is_required, + isSecret: input.is_secret, + }; + } + + function convertVariables(variables: Record): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(variables)) { + result[key] = convertServerInput(value); + } + return result; + } + + function convertServerArgument(arg: RawGalleryMcpServerArgument): IMcpServerArgument { + if (arg.type === 'positional') { + return { + ...arg, + valueHint: arg.value_hint, + isRepeated: arg.is_repeated, + isRequired: arg.is_required, + isSecret: arg.is_secret, + variables: arg.variables ? convertVariables(arg.variables) : undefined, + }; + } + return { + ...arg, + isRepeated: arg.is_repeated, + isRequired: arg.is_required, + isSecret: arg.is_secret, + variables: arg.variables ? convertVariables(arg.variables) : undefined, + }; + } + + function convertKeyValueInput(input: RawGalleryMcpServerKeyValueInput): IMcpServerKeyValueInput { + return { + ...input, + isRequired: input.is_required, + isSecret: input.is_secret, + variables: input.variables ? convertVariables(input.variables) : undefined, + }; + } + + function convertTransport(input: RawGalleryTransport): Transport | undefined { + switch (input.type) { + case 'stdio': + return { + type: TransportType.STDIO, + }; + case 'streamable-http': + return { + type: TransportType.STREAMABLE_HTTP, + url: input.url, + headers: input.headers?.map(convertKeyValueInput), + }; + case 'sse': + return { + type: TransportType.SSE, + url: input.url, + headers: input.headers?.map(convertKeyValueInput), + }; + default: + return undefined; + } + } + + function convertRegistryType(input: string): RegistryType { + switch (input) { + case 'npm': + return RegistryType.NODE; + case 'docker': + case 'docker-hub': + case 'oci': + return RegistryType.DOCKER; + case 'pypi': + return RegistryType.PYTHON; + case 'nuget': + return RegistryType.NUGET; + case 'mcpb': + return RegistryType.MCPB; + default: + return RegistryType.NODE; + } + } + + return { + name: from.name, + description: from.description, + repository: from.repository ? { + url: from.repository.url, + source: from.repository.source, + id: from.repository.id, + readme: from.repository.readme + } : undefined, + version: from.version_detail.version, + createdAt: from.created_at, + updatedAt: from.updated_at, + packages: from.packages?.map(p => ({ + identifier: p.identifier ?? p.name, + registryType: convertRegistryType(p.registry_type ?? p.registry_name), + version: p.version, + fileSha256: p.file_sha256, + registryBaseUrl: p.registry_base_url, + transport: p.transport ? convertTransport(p.transport) : undefined, + packageArguments: p.package_arguments?.map(convertServerArgument), + runtimeHint: p.runtime_hint, + runtimeArguments: p.runtime_arguments?.map(convertServerArgument), + environmentVariables: p.environment_variables?.map(convertKeyValueInput), + })), + remotes: from.remotes?.map(remote => { + const type = (remote).type ?? (remote).transport_type ?? (remote).transport; + return { + type: type === TransportType.SSE ? TransportType.SSE : TransportType.STREAMABLE_HTTP, + url: remote.url, + headers: remote.headers?.map(convertKeyValueInput) + }; + }), + registryInfo: { + id: from.id, + isLatest: true, + updatedAt: from.updated_at, + }, + }; + } + } + + export const SERIALIZER = new Serializer(); +} + namespace McpServerSchemaVersion_2025_01_09 { export const VERSION = '2025-09-01'; @@ -1147,6 +1417,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_07_09.VERSION, McpServerSchemaVersion_2025_07_09.SERIALIZER); this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_01_09.VERSION, McpServerSchemaVersion_2025_01_09.SERIALIZER); this.galleryMcpServerDataSerializers.set('old', McpServerOldSchema.SERIALIZER); + this.galleryMcpServerDataSerializers.set('1es', McpServer1ESSchema.SERIALIZER); this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_16_09.VERSION, McpServerSchemaVersion_2025_16_09.SERIALIZER); } From 686da07cefc25a4c97250d360186e2c1c942f154 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 2 Oct 2025 11:26:25 +0200 Subject: [PATCH 0723/4355] #269171 warn invalid named arguments (#269499) --- src/vs/platform/mcp/common/mcpManagement.ts | 8 +- .../platform/mcp/common/mcpManagementIpc.ts | 6 +- .../mcp/common/mcpManagementService.ts | 103 +++-- .../platform/mcp/node/mcpManagementService.ts | 10 +- .../test/common/mcpManagementService.test.ts | 381 ++++++++++-------- .../browser/mcpCommandsAddConfiguration.ts | 10 +- .../browser/mcpWorkbenchManagementService.ts | 4 +- .../common/mcpWorkbenchManagementService.ts | 12 +- .../mcpWorkbenchManagementService.ts | 6 +- 9 files changed, 321 insertions(+), 219 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 4e6b4df0c52..f6c64204ef9 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -214,6 +214,12 @@ export interface IInstallableMcpServer { readonly inputs?: IMcpServerVariable[]; } +export type McpServerConfiguration = Omit; +export interface McpServerConfigurationParseResult { + readonly mcpServerConfiguration: McpServerConfiguration; + readonly notices: string[]; +} + export const IMcpManagementService = createDecorator('IMcpManagementService'); export interface IMcpManagementService { readonly _serviceBrand: undefined; @@ -229,7 +235,7 @@ export interface IMcpManagementService { updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise; uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise; - getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): Omit; + getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): McpServerConfigurationParseResult; } export const IAllowedMcpServersService = createDecorator('IAllowedMcpServersService'); diff --git a/src/vs/platform/mcp/common/mcpManagementIpc.ts b/src/vs/platform/mcp/common/mcpManagementIpc.ts index 3920c476429..4790599c82c 100644 --- a/src/vs/platform/mcp/common/mcpManagementIpc.ts +++ b/src/vs/platform/mcp/common/mcpManagementIpc.ts @@ -8,6 +8,7 @@ import { cloneAndChange } from '../../../base/common/objects.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from '../../../base/common/uriIpc.js'; import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { ILogService } from '../../log/common/log.js'; import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpManagementService, IInstallableMcpServer, InstallMcpServerEvent, InstallMcpServerResult, InstallOptions, UninstallMcpServerEvent, UninstallOptions, IAllowedMcpServersService } from './mcpManagement.js'; import { AbstractMcpManagementService } from './mcpManagementService.js'; @@ -136,9 +137,10 @@ export class McpManagementChannelClient extends AbstractMcpManagementService imp constructor( private readonly channel: IChannel, - @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService + @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService ) { - super(allowedMcpServersService); + super(allowedMcpServersService, logService); this._register(this.channel.listen('onInstallMcpServer')(e => this._onInstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) })))); this._register(this.channel.listen('onDidInstallMcpServers')(results => this._onDidInstallMcpServers.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingServer(e.local, null) : e.local, mcpResource: transformIncomingURI(e.mcpResource, null) }))))); this._register(this.channel.listen('onDidUpdateMcpServers')(results => this._onDidUpdateMcpServers.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingServer(e.local, null) : e.local, mcpResource: transformIncomingURI(e.mcpResource, null) }))))); diff --git a/src/vs/platform/mcp/common/mcpManagementService.ts b/src/vs/platform/mcp/common/mcpManagementService.ts index 7a0a0477780..6d67ad27850 100644 --- a/src/vs/platform/mcp/common/mcpManagementService.ts +++ b/src/vs/platform/mcp/common/mcpManagementService.ts @@ -21,7 +21,7 @@ import { IInstantiationService } from '../../instantiation/common/instantiation. import { ILogService } from '../../log/common/log.js'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; -import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpGalleryService, IMcpManagementService, IMcpServerInput, IGalleryMcpServerConfiguration, InstallMcpServerEvent, InstallMcpServerResult, RegistryType, UninstallMcpServerEvent, InstallOptions, UninstallOptions, IInstallableMcpServer, IAllowedMcpServersService, IMcpServerArgument, IMcpServerKeyValueInput } from './mcpManagement.js'; +import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpGalleryService, IMcpManagementService, IMcpServerInput, IGalleryMcpServerConfiguration, InstallMcpServerEvent, InstallMcpServerResult, RegistryType, UninstallMcpServerEvent, InstallOptions, UninstallOptions, IInstallableMcpServer, IAllowedMcpServersService, IMcpServerArgument, IMcpServerKeyValueInput, McpServerConfigurationParseResult } from './mcpManagement.js'; import { IMcpServerVariable, McpServerVariableType, IMcpServerConfiguration, McpServerType } from './mcpPlatformTypes.js'; import { IMcpResourceScannerService, McpResourceTarget } from './mcpResourceScannerService.js'; @@ -46,22 +46,44 @@ export interface ILocalMcpServerInfo { licenseUrl?: string; } -export abstract class AbstractCommonMcpManagementService extends Disposable { +export abstract class AbstractCommonMcpManagementService extends Disposable implements IMcpManagementService { _serviceBrand: undefined; - getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): Omit { + abstract onInstallMcpServer: Event; + abstract onDidInstallMcpServers: Event; + abstract onDidUpdateMcpServers: Event; + abstract onUninstallMcpServer: Event; + abstract onDidUninstallMcpServer: Event; + + abstract getInstalled(mcpResource?: URI): Promise; + abstract install(server: IInstallableMcpServer, options?: InstallOptions): Promise; + abstract installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise; + abstract updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise; + abstract uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise; + abstract canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString; + + constructor( + @ILogService protected readonly logService: ILogService + ) { + super(); + } + + getMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType): McpServerConfigurationParseResult { // remote if (packageType === RegistryType.REMOTE && manifest.remotes?.length) { const { inputs, variables } = this.processKeyValueInputs(manifest.remotes[0].headers ?? []); return { - config: { - type: McpServerType.REMOTE, - url: manifest.remotes[0].url, - headers: Object.keys(inputs).length ? inputs : undefined, + mcpServerConfiguration: { + config: { + type: McpServerType.REMOTE, + url: manifest.remotes[0].url, + headers: Object.keys(inputs).length ? inputs : undefined, + }, + inputs: variables.length ? variables : undefined, }, - inputs: variables.length ? variables : undefined, + notices: [], }; } @@ -74,6 +96,7 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { const args: string[] = []; const inputs: IMcpServerVariable[] = []; const env: Record = {}; + const notices: string[] = []; if (serverPackage.registryType === RegistryType.DOCKER) { args.push('run'); @@ -85,11 +108,13 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { const result = this.processArguments(serverPackage.runtimeArguments ?? []); args.push(...result.args); inputs.push(...result.variables); + notices.push(...result.notices); } if (serverPackage.environmentVariables?.length) { - const { inputs: envInputs, variables: envVariables } = this.processKeyValueInputs(serverPackage.environmentVariables ?? []); + const { inputs: envInputs, variables: envVariables, notices: envNotices } = this.processKeyValueInputs(serverPackage.environmentVariables ?? []); inputs.push(...envVariables); + notices.push(...envNotices); for (const [name, value] of Object.entries(envInputs)) { env[name] = value; if (serverPackage.registryType === RegistryType.DOCKER) { @@ -122,16 +147,20 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { const result = this.processArguments(serverPackage.packageArguments); args.push(...result.args); inputs.push(...result.variables); + notices.push(...result.notices); } return { - config: { - type: McpServerType.LOCAL, - command: this.getCommandName(serverPackage.registryType), - args: args.length ? args : undefined, - env: Object.keys(env).length ? env : undefined, - }, - inputs: inputs.length ? inputs : undefined, + notices, + mcpServerConfiguration: { + config: { + type: McpServerType.LOCAL, + command: this.getCommandName(serverPackage.registryType), + args: args.length ? args : undefined, + env: Object.keys(env).length ? env : undefined, + }, + inputs: inputs.length ? inputs : undefined, + } }; } @@ -160,7 +189,8 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { return variables; } - private processKeyValueInputs(keyValueInputs: ReadonlyArray): { inputs: Record; variables: IMcpServerVariable[] } { + private processKeyValueInputs(keyValueInputs: ReadonlyArray): { inputs: Record; variables: IMcpServerVariable[]; notices: string[] } { + const notices: string[] = []; const inputs: Record = {}; const variables: IMcpServerVariable[] = []; @@ -190,12 +220,13 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { inputs[input.name] = value; } - return { inputs, variables }; + return { inputs, variables, notices }; } - private processArguments(argumentsList: readonly IMcpServerArgument[]): { args: string[]; variables: IMcpServerVariable[] } { + private processArguments(argumentsList: readonly IMcpServerArgument[]): { args: string[]; variables: IMcpServerVariable[]; notices: string[] } { const args: string[] = []; const variables: IMcpServerVariable[] = []; + const notices: string[] = []; for (const arg of argumentsList) { const argVariables = arg.variables ? this.getVariables(arg.variables) : []; @@ -224,6 +255,10 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { args.push(arg.valueHint ?? ''); } } else if (arg.type === 'named') { + if (!arg.name) { + notices.push(`Named argument is missing a name. ${JSON.stringify(arg)}`); + continue; + } args.push(arg.name); if (arg.value) { let value = arg.value; @@ -248,7 +283,7 @@ export abstract class AbstractCommonMcpManagementService extends Disposable { } } } - return { args, variables }; + return { args, variables, notices }; } } @@ -280,10 +315,10 @@ export abstract class AbstractMcpResourceManagementService extends AbstractCommo @IMcpGalleryService protected readonly mcpGalleryService: IMcpGalleryService, @IFileService protected readonly fileService: IFileService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, - @ILogService protected readonly logService: ILogService, + @ILogService logService: ILogService, @IMcpResourceScannerService protected readonly mcpResourceScannerService: IMcpResourceScannerService, ) { - super(); + super(logService); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.updateLocal(), 50)); } @@ -439,8 +474,6 @@ export abstract class AbstractMcpResourceManagementService extends AbstractCommo } } - abstract installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise; - abstract updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise; protected abstract getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise; protected abstract installFromUri(uri: URI, options?: Omit): Promise; } @@ -537,14 +570,19 @@ export class McpUserResourceManagementService extends AbstractMcpResourceManagem throw new Error('Method not supported.'); } + override canInstall(): true | IMarkdownString { + throw new Error('Not supported'); + } + } export abstract class AbstractMcpManagementService extends AbstractCommonMcpManagementService implements IMcpManagementService { constructor( @IAllowedMcpServersService protected readonly allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService, ) { - super(); + super(logService); } canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString { @@ -554,18 +592,6 @@ export abstract class AbstractMcpManagementService extends AbstractCommonMcpMana } return true; } - - abstract onInstallMcpServer: Event; - abstract onDidInstallMcpServers: Event; - abstract onDidUpdateMcpServers: Event; - abstract onUninstallMcpServer: Event; - abstract onDidUninstallMcpServer: Event; - - abstract getInstalled(mcpResource?: URI): Promise; - abstract install(server: IInstallableMcpServer, options?: InstallOptions): Promise; - abstract installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise; - abstract updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise; - abstract uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise; } export class McpManagementService extends AbstractMcpManagementService implements IMcpManagementService { @@ -589,10 +615,11 @@ export class McpManagementService extends AbstractMcpManagementService implement constructor( @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IInstantiationService protected readonly instantiationService: IInstantiationService, ) { - super(allowedMcpServersService); + super(allowedMcpServersService, logService); } private getMcpResourceManagementService(mcpResource: URI): McpUserResourceManagementService { diff --git a/src/vs/platform/mcp/node/mcpManagementService.ts b/src/vs/platform/mcp/node/mcpManagementService.ts index fa0cb36e4f6..06e26df8772 100644 --- a/src/vs/platform/mcp/node/mcpManagementService.ts +++ b/src/vs/platform/mcp/node/mcpManagementService.ts @@ -34,16 +34,20 @@ export class McpUserResourceManagementService extends CommonMcpUserResourceManag const manifest = await this.updateMetadataFromGallery(server); const packageType = options?.packageType ?? manifest.packages?.[0]?.registryType ?? RegistryType.REMOTE; - const { config, inputs } = this.getMcpServerConfigurationFromManifest(manifest, packageType); + const { mcpServerConfiguration, notices } = this.getMcpServerConfigurationFromManifest(manifest, packageType); + + if (notices.length > 0) { + this.logService.warn(`MCP Management Service: Warnings while installing ${server.name}`, notices); + } const installable: IInstallableMcpServer = { name: server.name, config: { - ...config, + ...mcpServerConfiguration.config, gallery: server.url ?? true, version: server.version }, - inputs + inputs: mcpServerConfiguration.inputs }; await this.mcpResourceScannerService.addMcpServers([installable], this.mcpResource, this.target); diff --git a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts index 4f18858d772..054beb4d391 100644 --- a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts +++ b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts @@ -6,13 +6,39 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { AbstractCommonMcpManagementService } from '../../common/mcpManagementService.js'; -import { IGalleryMcpServerConfiguration, RegistryType, TransportType } from '../../common/mcpManagement.js'; -import { McpServerType, McpServerVariableType } from '../../common/mcpPlatformTypes.js'; +import { IGalleryMcpServer, IGalleryMcpServerConfiguration, IInstallableMcpServer, ILocalMcpServer, InstallOptions, RegistryType, TransportType, UninstallOptions } from '../../common/mcpManagement.js'; +import { McpServerType, McpServerVariableType, IMcpServerVariable } from '../../common/mcpPlatformTypes.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; +import { Event } from '../../../../base/common/event.js'; +import { URI } from '../../../../base/common/uri.js'; +import { NullLogService } from '../../../log/common/log.js'; class TestMcpManagementService extends AbstractCommonMcpManagementService { - // Expose the protected method for testing - public testGetMcpServerConfigurationFromManifest(manifest: IGalleryMcpServerConfiguration, packageType: RegistryType) { - return this.getMcpServerConfigurationFromManifest(manifest, packageType); + + override onInstallMcpServer = Event.None; + override onDidInstallMcpServers = Event.None; + override onDidUpdateMcpServers = Event.None; + override onUninstallMcpServer = Event.None; + override onDidUninstallMcpServer = Event.None; + + override getInstalled(mcpResource?: URI): Promise { + throw new Error('Method not implemented.'); + } + override install(server: IInstallableMcpServer, options?: InstallOptions): Promise { + throw new Error('Method not implemented.'); + } + override installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise { + throw new Error('Method not implemented.'); + } + override updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise { + throw new Error('Method not implemented.'); + } + override uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise { + throw new Error('Method not implemented.'); + } + + override canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString { + throw new Error('Not supported'); } } @@ -20,7 +46,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { let service: TestMcpManagementService; setup(() => { - service = new TestMcpManagementService(); + service = new TestMcpManagementService(new NullLogService()); }); teardown(() => { @@ -44,15 +70,15 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'npx'); - assert.deepStrictEqual(result.config.args, ['@modelcontextprotocol/server-brave-search@1.0.2']); - assert.deepStrictEqual(result.config.env, { 'BRAVE_API_KEY': 'test-key' }); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'npx'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['@modelcontextprotocol/server-brave-search@1.0.2']); + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'BRAVE_API_KEY': 'test-key' }); } - assert.strictEqual(result.inputs, undefined); + assert.strictEqual(result.mcpServerConfiguration.inputs, undefined); }); test('NPM package without version', () => { @@ -65,12 +91,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'npx'); - assert.deepStrictEqual(result.config.args, ['@modelcontextprotocol/everything']); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'npx'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['@modelcontextprotocol/everything']); } }); @@ -94,17 +120,17 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.env, { 'API_KEY': 'key-${input:api_token}' }); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'API_KEY': 'key-${input:api_token}' }); } - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'api_token'); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); - assert.strictEqual(result.inputs?.[0].description, 'Your API token'); - assert.strictEqual(result.inputs?.[0].password, true); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'api_token'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PROMPT); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].description, 'Your API token'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].password, true); }); test('environment variable with empty value should create input variable (GitHub issue #266106)', () => { @@ -123,19 +149,19 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); // BUG: Currently this creates env with empty string instead of input variable // Should create an input variable since no meaningful value is provided - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'BRAVE_API_KEY'); - assert.strictEqual(result.inputs?.[0].description, 'Brave Search API Key'); - assert.strictEqual(result.inputs?.[0].password, true); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'BRAVE_API_KEY'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].description, 'Brave Search API Key'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].password, true); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PROMPT); // Environment should use input variable interpolation - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.env, { 'BRAVE_API_KEY': '${input:BRAVE_API_KEY}' }); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'BRAVE_API_KEY': '${input:BRAVE_API_KEY}' }); } }); @@ -155,20 +181,20 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); // BUG: Currently this creates env with empty string instead of input variable // Should create a pick input variable since choices are provided - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'SSL_MODE'); - assert.strictEqual(result.inputs?.[0].description, 'SSL connection mode'); - assert.strictEqual(result.inputs?.[0].default, 'prefer'); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); - assert.deepStrictEqual(result.inputs?.[0].options, ['disable', 'prefer', 'require']); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'SSL_MODE'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].description, 'SSL connection mode'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].default, 'prefer'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.mcpServerConfiguration.inputs?.[0].options, ['disable', 'prefer', 'require']); // Environment should use input variable interpolation - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.env, { 'SSL_MODE': '${input:SSL_MODE}' }); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'SSL_MODE': '${input:SSL_MODE}' }); } }); @@ -190,11 +216,11 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, ['snyk@1.1298.0', 'mcp', '-t', 'stdio']); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['snyk@1.1298.0', 'mcp', '-t', 'stdio']); } }); }); @@ -217,13 +243,13 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'uvx'); - assert.deepStrictEqual(result.config.args, ['weather-mcp-server==0.5.0']); - assert.deepStrictEqual(result.config.env, { + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'uvx'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['weather-mcp-server==0.5.0']); + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'WEATHER_API_KEY': 'test-key', 'WEATHER_UNITS': 'celsius' }); @@ -239,10 +265,10 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.PYTHON); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, ['weather-mcp-server']); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['weather-mcp-server']); } }); }); @@ -274,19 +300,19 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'docker'); - assert.deepStrictEqual(result.config.args, [ + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'docker'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'run', '-i', '--rm', '--mount', 'type=bind,src=/host/path,dst=/container/path', '-e', 'LOG_LEVEL', 'mcp/filesystem:1.0.2', '/project' ]); - assert.deepStrictEqual(result.config.env, { 'LOG_LEVEL': 'info' }); + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'LOG_LEVEL': 'info' }); } }); @@ -312,20 +338,20 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, [ + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'run', '-i', '--rm', '-e', 'DB_TYPE=${input:db_type}', 'example/database-manager-mcp:3.1.0' ]); } - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'db_type'); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); - assert.deepStrictEqual(result.inputs?.[0].options, ['postgres', 'mysql', 'mongodb', 'redis']); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'db_type'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.mcpServerConfiguration.inputs?.[0].options, ['postgres', 'mysql', 'mongodb', 'redis']); }); test('Docker package arguments without values should create input variables (GitHub issue #266106)', () => { @@ -353,24 +379,24 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); // BUG: Currently named args without value are ignored, positional uses value_hint as literal // Should create input variables for both arguments - assert.strictEqual(result.inputs?.length, 2); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 2); - const hostInput = result.inputs?.find(i => i.id === 'host'); + const hostInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'host'); assert.strictEqual(hostInput?.description, 'Database host'); assert.strictEqual(hostInput?.default, 'localhost'); assert.strictEqual(hostInput?.type, McpServerVariableType.PROMPT); - const dbNameInput = result.inputs?.find(i => i.id === 'database_name'); + const dbNameInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'database_name'); assert.strictEqual(dbNameInput?.description, 'Name of the database to connect to'); assert.strictEqual(dbNameInput?.type, McpServerVariableType.PROMPT); // Args should use input variable interpolation - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, [ + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'run', '-i', '--rm', 'example/database-manager-mcp:3.1.0', '--host', '${input:host}', @@ -388,12 +414,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'docker'); - assert.deepStrictEqual(result.config.args, [ + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'docker'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'run', '-i', '--rm', 'example/test-image:1.0.0' ]); @@ -416,13 +442,13 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'dnx'); - assert.deepStrictEqual(result.config.args, ['Knapcode.SampleMcpServer@0.5.0', '--yes']); - assert.deepStrictEqual(result.config.env, { 'WEATHER_CHOICES': 'sunny,cloudy,rainy' }); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'dnx'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['Knapcode.SampleMcpServer@0.5.0', '--yes']); + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'WEATHER_CHOICES': 'sunny,cloudy,rainy' }); } }); @@ -446,10 +472,10 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NUGET); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, [ + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'Knapcode.SampleMcpServer@0.4.0-beta', '--yes', '--', @@ -469,12 +495,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); - assert.strictEqual(result.config.type, McpServerType.REMOTE); - if (result.config.type === McpServerType.REMOTE) { - assert.strictEqual(result.config.url, 'http://mcp-fs.anonymous.modelcontextprotocol.io/sse'); - assert.strictEqual(result.config.headers, undefined); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.REMOTE); + if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.mcpServerConfiguration.config.url, 'http://mcp-fs.anonymous.modelcontextprotocol.io/sse'); + assert.strictEqual(result.mcpServerConfiguration.config.headers, undefined); } }); @@ -500,18 +526,18 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); - assert.strictEqual(result.config.type, McpServerType.REMOTE); - if (result.config.type === McpServerType.REMOTE) { - assert.deepStrictEqual(result.config.headers, { + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.REMOTE); + if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.headers, { 'X-API-Key': '${input:api_key}', 'X-Region': 'us-east-1' }); } - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'api_key'); - assert.strictEqual(result.inputs?.[0].password, true); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'api_key'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].password, true); }); test('streamable HTTP remote server', () => { @@ -522,11 +548,11 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); - assert.strictEqual(result.config.type, McpServerType.REMOTE); - if (result.config.type === McpServerType.REMOTE) { - assert.strictEqual(result.config.url, 'https://mcp.anonymous.modelcontextprotocol.io/http'); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.REMOTE); + if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.mcpServerConfiguration.config.url, 'https://mcp.anonymous.modelcontextprotocol.io/http'); } }); @@ -551,26 +577,26 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.REMOTE); - assert.strictEqual(result.config.type, McpServerType.REMOTE); - if (result.config.type === McpServerType.REMOTE) { - assert.strictEqual(result.config.url, 'https://api.example.com/mcp'); - assert.deepStrictEqual(result.config.headers, { + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.REMOTE); + if (result.mcpServerConfiguration.config.type === McpServerType.REMOTE) { + assert.strictEqual(result.mcpServerConfiguration.config.url, 'https://api.example.com/mcp'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.headers, { 'Authorization': '${input:Authorization}', 'X-Custom-Header': '${input:X-Custom-Header}' }); } // Should create input variables for headers without values - assert.strictEqual(result.inputs?.length, 2); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 2); - const authInput = result.inputs?.find(i => i.id === 'Authorization'); + const authInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'Authorization'); assert.strictEqual(authInput?.description, 'API token for authentication'); assert.strictEqual(authInput?.password, true); assert.strictEqual(authInput?.type, McpServerVariableType.PROMPT); - const customInput = result.inputs?.find(i => i.id === 'X-Custom-Header'); + const customInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'X-Custom-Header'); assert.strictEqual(customInput?.description, 'Custom header value'); assert.strictEqual(customInput?.default, 'default-value'); assert.strictEqual(customInput?.type, McpServerVariableType.PICK); @@ -607,23 +633,23 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.env, { + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.env, { 'CONNECTION_STRING': 'server=${input:host};port=${input:port};database=${input:db_name}' }); } - assert.strictEqual(result.inputs?.length, 3); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 3); - const hostInput = result.inputs?.find(i => i.id === 'host'); + const hostInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'host'); assert.strictEqual(hostInput?.default, 'localhost'); assert.strictEqual(hostInput?.type, McpServerVariableType.PROMPT); - const portInput = result.inputs?.find(i => i.id === 'port'); + const portInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'port'); assert.strictEqual(portInput?.default, '5432'); - const dbNameInput = result.inputs?.find(i => i.id === 'db_name'); + const dbNameInput = result.mcpServerConfiguration.inputs?.find((i: IMcpServerVariable) => i.id === 'db_name'); assert.strictEqual(dbNameInput?.description, 'Database name'); }); @@ -649,12 +675,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PICK); - assert.deepStrictEqual(result.inputs?.[0].options, ['debug', 'info', 'warn', 'error']); - assert.strictEqual(result.inputs?.[0].default, 'info'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PICK); + assert.deepStrictEqual(result.mcpServerConfiguration.inputs?.[0].options, ['debug', 'info', 'warn', 'error']); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].default, 'info'); }); test('variables in package arguments', () => { @@ -689,17 +715,17 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.DOCKER); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, [ + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ 'run', '-i', '--rm', 'test-image:1.0.0', '--host', '${input:db_host}', '${input:database_name}' ]); } - assert.strictEqual(result.inputs?.length, 2); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 2); }); test('positional arguments with value_hint should create input variables (GitHub issue #266106)', () => { @@ -719,18 +745,18 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); // BUG: Currently value_hint is used as literal value instead of creating input variable // Should create input variable instead - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'calculation_type'); - assert.strictEqual(result.inputs?.[0].description, 'Type of calculation to enable'); - assert.strictEqual(result.inputs?.[0].type, McpServerVariableType.PROMPT); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'calculation_type'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].description, 'Type of calculation to enable'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].type, McpServerVariableType.PROMPT); // Args should use input variable interpolation - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, [ + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, [ '@example/math-tool@2.0.1', '${input:calculation_type}' ]); @@ -743,7 +769,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = {}; assert.throws(() => { - service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); }, /No server package found/); }); @@ -756,12 +782,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.config.type, McpServerType.LOCAL); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'uvx'); // Python command since that's the package type - assert.deepStrictEqual(result.config.args, ['python-server==1.0.0']); + assert.strictEqual(result.mcpServerConfiguration.config.type, McpServerType.LOCAL); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'uvx'); // Python command since that's the package type + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['python-server==1.0.0']); } }); @@ -778,11 +804,11 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.command, 'npx'); - assert.deepStrictEqual(result.config.args, ['node-server@2.0.0']); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.command, 'npx'); + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['node-server@2.0.0']); } }); @@ -795,10 +821,10 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.env, undefined); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.env, undefined); } }); @@ -816,10 +842,10 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, ['--verbose', 'test-server@1.0.0']); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['--verbose', 'test-server@1.0.0']); } }); @@ -837,10 +863,37 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['test-server@1.0.0', 'target_directory']); + } + }); + + test('named argument with missing name should generate notice', () => { + const manifest: IGalleryMcpServerConfiguration = { + packages: [{ + registryType: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + runtimeArguments: [{ + type: 'named', + // name is intentionally missing/undefined + value: 'some-value', + isRepeated: false + } as any] // Cast to any to bypass TypeScript validation for this test case + }] + }; + + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + + // Should generate a notice about the missing name + assert.strictEqual(result.notices.length, 1); + assert.ok(result.notices[0].includes('Named argument is missing a name')); + assert.ok(result.notices[0].includes('some-value')); // Should include the argument details in JSON format - if (result.config.type === McpServerType.LOCAL) { - assert.deepStrictEqual(result.config.args, ['test-server@1.0.0', 'target_directory']); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['test-server@1.0.0']); } }); }); @@ -866,15 +919,15 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { }] }; - const result = service.testGetMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); + const result = service.getMcpServerConfigurationFromManifest(manifest, RegistryType.NODE); - assert.strictEqual(result.inputs?.length, 1); - assert.strictEqual(result.inputs?.[0].id, 'api_key'); - assert.strictEqual(result.inputs?.[0].description, 'Your API key'); - assert.strictEqual(result.inputs?.[0].password, true); + assert.strictEqual(result.mcpServerConfiguration.inputs?.length, 1); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].id, 'api_key'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].description, 'Your API key'); + assert.strictEqual(result.mcpServerConfiguration.inputs?.[0].password, true); - if (result.config.type === McpServerType.LOCAL) { - assert.strictEqual(result.config.env?.['API_KEY'], 'Bearer ${input:api_key}'); + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.strictEqual(result.mcpServerConfiguration.config.env?.['API_KEY'], 'Bearer ${input:api_key}'); } }); }); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts index 7780227dad8..b1aaf374e0c 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts @@ -410,14 +410,14 @@ export class McpAddConfigurationCommand { if (!packageType) { throw new Error(`Unsupported assisted package type ${type}`); } - const server = this._mcpManagementService.getMcpServerConfigurationFromManifest(config.server, packageType); - if (server.config.type !== McpServerType.LOCAL) { - throw new Error(`Unexpected server type ${server.config.type} for assisted configuration from server.json.`); + const { mcpServerConfiguration } = this._mcpManagementService.getMcpServerConfigurationFromManifest(config.server, packageType); + if (mcpServerConfiguration.config.type !== McpServerType.LOCAL) { + throw new Error(`Unexpected server type ${mcpServerConfiguration.config.type} for assisted configuration from server.json.`); } return { name: config.name, - server: server.config, - inputs: server.inputs, + server: mcpServerConfiguration.config, + inputs: mcpServerConfiguration.inputs, }; } else if (config?.type === 'vscode' || !config?.type) { return config; diff --git a/src/vs/workbench/services/mcp/browser/mcpWorkbenchManagementService.ts b/src/vs/workbench/services/mcp/browser/mcpWorkbenchManagementService.ts index 47e2f89dfbc..a3a6d479bc4 100644 --- a/src/vs/workbench/services/mcp/browser/mcpWorkbenchManagementService.ts +++ b/src/vs/workbench/services/mcp/browser/mcpWorkbenchManagementService.ts @@ -14,11 +14,13 @@ import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/rem import { WorkbenchMcpManagementService as BaseWorkbenchMcpManagementService, IWorkbenchMcpManagementService } from '../common/mcpWorkbenchManagementService.js'; import { McpManagementService } from '../../../../platform/mcp/common/mcpManagementService.js'; import { IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; export class WorkbenchMcpManagementService extends BaseWorkbenchMcpManagementService { constructor( @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @@ -28,7 +30,7 @@ export class WorkbenchMcpManagementService extends BaseWorkbenchMcpManagementSer @IInstantiationService instantiationService: IInstantiationService, ) { const mMcpManagementService = instantiationService.createInstance(McpManagementService); - super(mMcpManagementService, allowedMcpServersService, userDataProfileService, uriIdentityService, workspaceContextService, remoteAgentService, userDataProfilesService, remoteUserDataProfilesService, instantiationService); + super(mMcpManagementService, allowedMcpServersService, logService, userDataProfileService, uriIdentityService, workspaceContextService, remoteAgentService, userDataProfilesService, remoteUserDataProfilesService, instantiationService); this._register(mMcpManagementService); } } diff --git a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts index 90908307cc1..6f5461fd567 100644 --- a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts +++ b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts @@ -23,6 +23,7 @@ import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/rem import { AbstractMcpManagementService, AbstractMcpResourceManagementService, ILocalMcpServerInfo } from '../../../../platform/mcp/common/mcpManagementService.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../base/common/map.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; export const USER_CONFIG_ID = 'usrlocal'; export const REMOTE_USER_CONFIG_ID = 'usrremote'; @@ -118,6 +119,7 @@ export class WorkbenchMcpManagementService extends AbstractMcpManagementService constructor( private readonly mcpManagementService: IMcpManagementService, @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -126,7 +128,7 @@ export class WorkbenchMcpManagementService extends AbstractMcpManagementService @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, @IInstantiationService instantiationService: IInstantiationService, ) { - super(allowedMcpServersService); + super(allowedMcpServersService, logService); this.workspaceMcpManagementService = this._register(instantiationService.createInstance(WorkspaceMcpManagementService)); const remoteAgentConnection = remoteAgentService.getConnection(); @@ -440,6 +442,10 @@ class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagemen protected override async getLocalServerInfo(): Promise { return undefined; } + + override canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString { + throw new Error('Not supported'); + } } class WorkspaceMcpManagementService extends AbstractMcpManagementService implements IMcpManagementService { @@ -467,11 +473,11 @@ class WorkspaceMcpManagementService extends AbstractMcpManagementService impleme constructor( @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @ILogService private readonly logService: ILogService, + @ILogService logService: ILogService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(allowedMcpServersService); + super(allowedMcpServersService, logService); this.initialize(); } diff --git a/src/vs/workbench/services/mcp/electron-browser/mcpWorkbenchManagementService.ts b/src/vs/workbench/services/mcp/electron-browser/mcpWorkbenchManagementService.ts index 19117905850..ac5c39bfd63 100644 --- a/src/vs/workbench/services/mcp/electron-browser/mcpWorkbenchManagementService.ts +++ b/src/vs/workbench/services/mcp/electron-browser/mcpWorkbenchManagementService.ts @@ -15,11 +15,13 @@ import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/rem import { WorkbenchMcpManagementService as BaseWorkbenchMcpManagementService, IWorkbenchMcpManagementService } from '../common/mcpWorkbenchManagementService.js'; import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; export class WorkbenchMcpManagementService extends BaseWorkbenchMcpManagementService { constructor( @IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService, + @ILogService logService: ILogService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @@ -29,8 +31,8 @@ export class WorkbenchMcpManagementService extends BaseWorkbenchMcpManagementSer @IInstantiationService instantiationService: IInstantiationService, @ISharedProcessService sharedProcessService: ISharedProcessService, ) { - const mcpManagementService = new McpManagementChannelClient(sharedProcessService.getChannel('mcpManagement'), allowedMcpServersService); - super(mcpManagementService, allowedMcpServersService, userDataProfileService, uriIdentityService, workspaceContextService, remoteAgentService, userDataProfilesService, remoteUserDataProfilesService, instantiationService); + const mcpManagementService = new McpManagementChannelClient(sharedProcessService.getChannel('mcpManagement'), allowedMcpServersService, logService); + super(mcpManagementService, allowedMcpServersService, logService, userDataProfileService, uriIdentityService, workspaceContextService, remoteAgentService, userDataProfilesService, remoteUserDataProfilesService, instantiationService); this._register(mcpManagementService); } } From 9a483f338c74eba48033885c44549270b91250d9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 2 Oct 2025 11:23:39 +0200 Subject: [PATCH 0724/4355] Fixes https://github.com/microsoft/vscode/issues/255708 --- .../browser/model/inlineCompletionsSource.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 7bd40c54f08..329491cd84b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -5,6 +5,7 @@ import { booleanComparator, compareBy, compareUndefinedSmallest, numberComparator } from '../../../../../base/common/arrays.js'; import { findLastMax } from '../../../../../base/common/arraysFind.js'; +import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { equalsIfDefined, itemEquals } from '../../../../../base/common/equals.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; @@ -140,11 +141,23 @@ export class InlineCompletionsSource extends Disposable { const source = new CancellationTokenSource(); const promise = (async () => { - this._loadingCount.set(this._loadingCount.get() + 1, undefined); const store = new DisposableStore(); + + this._loadingCount.set(this._loadingCount.get() + 1, undefined); + let didDecrease = false; + const decreaseLoadingCount = () => { + if (!didDecrease) { + didDecrease = true; + this._loadingCount.set(this._loadingCount.get() - 1, undefined); + } + }; + const loadingReset = store.add(new RunOnceScheduler(() => decreaseLoadingCount(), 10 * 1000)); + loadingReset.schedule(); + const inlineSuggestionsProviders = providers.filter(p => p.providerId); const requestResponseInfo = new RequestResponseData(context, requestInfo, inlineSuggestionsProviders); + try { const recommendedDebounceValue = this._debounceValue.get(this._textModel); const debounceValue = findLastMax( @@ -293,8 +306,8 @@ export class InlineCompletionsSource extends Disposable { v.suggestWidgetInlineCompletions.dispose(); }); } finally { - this._loadingCount.set(this._loadingCount.get() - 1, undefined); store.dispose(); + decreaseLoadingCount(); this.sendInlineCompletionsRequestTelemetry(requestResponseInfo); } From dd57d238873d180e1484a1568abf8126752a3193 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:43:31 +0900 Subject: [PATCH 0725/4355] Set suppressInSnippetMode default to true to prevent inline completions from interfering with snippets (#268072) * Initial plan * Change suppressInSnippetMode default from false to true Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c7b7606e5b1..0973ecf70db 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4438,7 +4438,7 @@ class InlineEditorSuggest extends BaseEditorOption Date: Thu, 2 Oct 2025 11:52:48 +0200 Subject: [PATCH 0726/4355] fix #269173 (#269504) --- src/vs/base/common/htmlContent.ts | 4 ++-- src/vs/workbench/contrib/mcp/browser/mcpServersView.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index b71901416d9..6505279e917 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -199,9 +199,9 @@ export function parseHrefAndDimensions(href: string): { href: string; dimensions return { href, dimensions }; } -export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[] }, escapeTokens = true): string { +export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[]; tooltip?: string }, escapeTokens = true): string { const uri = createCommandUri(command.id, ...(command.arguments || [])).toString(); - return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri})`; + return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri}${command.tooltip ? ` "${escapeMarkdownSyntaxTokens(command.tooltip)}"` : ''})`; } export function createCommandUri(commandId: string, ...commandArgs: unknown[]): URI { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 93d8a757f94..5a12c9d54f3 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -8,7 +8,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { createCommandUri, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { markdownCommandLink, MarkdownString } from '../../../../base/common/htmlContent.js'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; import { DelayedPagedModel, IPagedModel, PagedModel } from '../../../../base/common/paging.js'; import { localize, localize2 } from '../../../../nls.js'; @@ -261,14 +261,14 @@ export class McpServersListView extends AbstractExtensionsListView { this.openerService.open(URI.parse(content), { allowCommands: ['workbench.action.openSettings'] }); @@ -290,7 +290,7 @@ export class McpServersListView extends AbstractExtensionsListView Date: Thu, 2 Oct 2025 11:30:46 +0100 Subject: [PATCH 0727/4355] Enhance action label styling for composite bar in high-contrast themes: add outlines for uri-icons and adjust wrapper for uri-icons --- .../browser/parts/compositeBarActions.ts | 11 ++++++ .../browser/parts/media/paneCompositePart.css | 36 +++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index f0aa6f62f1d..28d234ac4ca 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -158,6 +158,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { protected badge!: HTMLElement; protected override readonly options: ICompositeBarActionViewItemOptions; + private labelContainer: HTMLElement | undefined; private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: Timeout | undefined; @@ -402,6 +403,16 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { if (!this.options.icon) { this.label.textContent = this.action.label; } + + const hasUriIcon = this.compositeBarActionItem.classNames?.includes('uri-icon'); + if (hasUriIcon && !this.labelContainer) { + this.labelContainer = $('.action-label-hc-container'); + this.label.replaceWith(this.labelContainer); + this.labelContainer.appendChild(this.label); + } else if (!hasUriIcon && this.labelContainer) { + this.labelContainer.replaceWith(this.label); + this.labelContainer = undefined; + } } private updateTitle(): void { diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index afa2295437c..044ba0a2706 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -319,16 +319,40 @@ border-top-width: 2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { +/* Apply outline to action-label for non-uri-icon elements */ +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label:not(.uri-icon), +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label:not(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label:not(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label:not(.uri-icon) { + outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label:not(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label:not(.uri-icon) { + outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; +} + +/* Apply outline to action-label-hc-container wrapper for uri-icon elements to avoid masking issues */ +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label-hc-container:has(.uri-icon), +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label-hc-container:has(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label-hc-container:has(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label-hc-container:has(.uri-icon) { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; + outline-offset: 2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label-hc-container:has(.uri-icon), +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label-hc-container:has(.uri-icon) { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; + outline-offset: 2px; +} + +/* Style the wrapper to match the label dimensions for uri-icons */ +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label-hc-container, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label-hc-container { + display: flex; + align-items: center; + justify-content: center; } /** Empty Pane Message **/ From 1ae99f48c1b974435446e3f1b1420e91b7d8eba0 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Thu, 2 Oct 2025 11:42:56 +0100 Subject: [PATCH 0728/4355] Fix optional chaining for compositeBarActionItem in updateLabel method --- src/vs/workbench/browser/parts/compositeBarActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 28d234ac4ca..6247007e9e9 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -396,7 +396,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { protected override updateLabel(): void { this.label.className = 'action-label'; - if (this.compositeBarActionItem.classNames) { + if (this.compositeBarActionItem?.classNames) { this.label.classList.add(...this.compositeBarActionItem.classNames); } @@ -404,7 +404,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this.label.textContent = this.action.label; } - const hasUriIcon = this.compositeBarActionItem.classNames?.includes('uri-icon'); + const hasUriIcon = this.compositeBarActionItem?.classNames?.includes('uri-icon'); if (hasUriIcon && !this.labelContainer) { this.labelContainer = $('.action-label-hc-container'); this.label.replaceWith(this.labelContainer); From e11ffe9d87850da0fe32b6a6a397235eb66c9540 Mon Sep 17 00:00:00 2001 From: lemurra_microsoft Date: Thu, 2 Oct 2025 11:44:53 +0100 Subject: [PATCH 0729/4355] Refactor high contrast label handling in CompositeBarActionViewItem --- .../browser/parts/compositeBarActions.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 6247007e9e9..140de9fe178 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -406,10 +406,20 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { const hasUriIcon = this.compositeBarActionItem?.classNames?.includes('uri-icon'); if (hasUriIcon && !this.labelContainer) { - this.labelContainer = $('.action-label-hc-container'); - this.label.replaceWith(this.labelContainer); - this.labelContainer.appendChild(this.label); + this.addHighContrastContainer(); } else if (!hasUriIcon && this.labelContainer) { + this.removeHighContrastContainer(); + } + } + + private addHighContrastContainer(): void { + this.labelContainer = $('.action-label-hc-container'); + this.label.replaceWith(this.labelContainer); + this.labelContainer.appendChild(this.label); + } + + private removeHighContrastContainer(): void { + if (this.labelContainer) { this.labelContainer.replaceWith(this.label); this.labelContainer = undefined; } From c9f8fbf16f1fb4099e0d4f4219641afb81fdf517 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 13:54:25 +0200 Subject: [PATCH 0730/4355] chat - fix `plan.prompt.md` warnings (#269514) --- .github/prompts/plan.prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index 8b0c17c387b..74d843a13ab 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Start planning' -tools: ['getNotebookSummary', 'readNotebookCellOutput', 'search', 'getTerminalOutput', 'terminalSelection', 'terminalLastCommand', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos', 'get_issue', 'get_issue_comments', 'get_me'] +tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos', 'github/get_issue', 'github/get_issue_comments', 'github/get_me'] --- Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: * Understand the context of the bug or feature by reading the issue description and comments. From 5c24393b009669e7649b4ef5c420546df653374e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 13:54:59 +0200 Subject: [PATCH 0731/4355] menu - remove dependency to `.monaco-workbench` (#269525) --- src/vs/base/browser/ui/menu/menubar.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css index 45eebe98553..17fbe89af15 100644 --- a/src/vs/base/browser/ui/menu/menubar.css +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -33,7 +33,7 @@ outline: 0 !important; } -.monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { +.menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { outline-width: 1px; outline-style: solid; outline-offset: -1px; From 3b158710e27751dc4fc2ac2992690d3d376a687f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 13:55:16 +0200 Subject: [PATCH 0732/4355] speech - increase the default timeout for accepting (#269519) --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 3424b7e3e93..bfc42b2a58c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -856,7 +856,7 @@ export function registerAccessibilityConfiguration() { export { AccessibilityVoiceSettingId }; -export const SpeechTimeoutDefault = 1200; +export const SpeechTimeoutDefault = 2000; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { From 552eb1a53fa820934bf8512bc1633631cd7dea77 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 2 Oct 2025 13:55:23 +0200 Subject: [PATCH 0733/4355] Prompt files: fix issues with tools that are also in user tool sets (#269520) * Prompt files: fix issues with tools that are also in user tool sets * fix typos --- .../chat/browser/actions/chatToolPicker.ts | 2 +- .../chat/browser/languageModelToolsService.ts | 72 +++--- .../browser/languageModelToolsService.test.ts | 209 +++++++++++++----- 3 files changed, 185 insertions(+), 98 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 67e3edeb03c..7ba446dcb42 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -501,7 +501,7 @@ export async function showToolsPicker( traverse(item.children); } } else if (isToolTreeItem(item)) { - result.set(item.tool, item.checked); + result.set(item.tool, item.checked || result.get(item.tool) === true); // tools can be in user tool sets and other buckets } } }; diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 39ded850226..75e4f4f9cc0 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -606,48 +606,48 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toToolAndToolSetEnablementMap(enabledQualifiedToolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap { const toolOrToolSetNames = new Set(enabledQualifiedToolOrToolSetNames); const result = new Map(); - for (const tool of this.getTools()) { - if (tool.canBeReferencedInPrompt) { - const enabled = toolOrToolSetNames.has(getToolReferenceName(tool)) || /* legacy */ toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName); + + for (const [tool, toolReferenceName] of this.getPromptReferencableTools()) { + if (tool instanceof ToolSet) { + const enabled = toolOrToolSetNames.has(toolReferenceName) || toolOrToolSetNames.has(tool.referenceName); result.set(tool, enabled); + if (enabled) { + for (const memberTool of tool.getTools()) { + result.set(memberTool, true); + } + } + } else { + if (!result.has(tool)) { // already set via an enabled toolset + const enabled = toolOrToolSetNames.has(toolReferenceName) || toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName); + result.set(tool, enabled); + } } } - for (const toolSet of this.toolSets.get()) { - const toolSetEnabled = toolOrToolSetNames.has(getToolSetReferenceName(toolSet)) || /* legacy */ toolOrToolSetNames.has(toolSet.referenceName); - result.set(toolSet, toolSetEnabled); - for (const tool of toolSet.getTools()) { - const enabled = toolSetEnabled || toolOrToolSetNames.has(getToolReferenceName(tool, toolSet)) || /* legacy */ toolOrToolSetNames.has(tool.toolReferenceName ?? tool.displayName); - result.set(tool, enabled); + // also add all user tool sets (not part of the prompt referencable tools) + for (const toolSet of this._toolSets) { + if (toolSet.source.type === 'user') { + const enabled = Iterable.every(toolSet.getTools(), t => result.get(t) === true); + result.set(toolSet, enabled); } } return result; } toQualifiedToolNames(map: IToolAndToolSetEnablementMap): string[] { - const toolsCoveredBySets = new Set(); - for (const item of map.keys()) { - if (item instanceof ToolSet) { - for (const tool of item.getTools()) { - toolsCoveredBySets.add(tool); - } - } - } - const result: string[] = []; - for (const tool of this.getTools()) { - if (map.get(tool) && !toolsCoveredBySets.has(tool)) { - result.push(getToolReferenceName(tool)); - } - } - for (const toolSet of this.toolSets.get()) { - if (map.get(toolSet)) { - result.push(getToolSetReferenceName(toolSet)); - } else { - for (const tool of toolSet.getTools()) { - if (map.get(tool)) { - result.push(getToolReferenceName(tool, toolSet)); + const toolsCoveredByEnabledToolSet = new Set(); + for (const [tool, toolReferenceName] of this.getPromptReferencableTools()) { + if (tool instanceof ToolSet) { + if (map.get(tool)) { + result.push(toolReferenceName); + for (const memberTool of tool.getTools()) { + toolsCoveredByEnabledToolSet.add(memberTool); } } + } else { + if (map.get(tool) && !toolsCoveredByEnabledToolSet.has(tool)) { + result.push(toolReferenceName); + } } } return result; @@ -655,16 +655,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[] { const toolsOrToolSetByName = new Map(); - for (const tool of this.getTools()) { - if (tool.canBeReferencedInPrompt) { - toolsOrToolSetByName.set(getToolReferenceName(tool), tool); - } - } - for (const toolSet of this.toolSets.get()) { - toolsOrToolSetByName.set(getToolSetReferenceName(toolSet), toolSet); - for (const tool of toolSet.getTools()) { - toolsOrToolSetByName.set(getToolReferenceName(tool, toolSet), tool); - } + for (const [tool, toolReferenceName] of this.getPromptReferencableTools()) { + toolsOrToolSetByName.set(toolReferenceName, tool); } const result: ChatRequestToolReferenceEntry[] = []; diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index 15fbdb38a87..3bec6415498 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -20,7 +20,7 @@ import { workbenchInstantiationService } from '../../../../test/browser/workbenc import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js'; import { IChatModel } from '../../common/chatModel.js'; import { IChatService, IChatToolInputInvocationData } from '../../common/chatService.js'; -import { IToolData, IToolImpl, IToolInvocation, ToolDataSource } from '../../common/languageModelToolsService.js'; +import { IToolData, IToolImpl, IToolInvocation, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { MockChatService } from '../common/mockChatService.js'; import { IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; @@ -117,7 +117,7 @@ suite('LanguageModelToolsService', () => { function setupToolsForTest(service: LanguageModelToolsService, store: any) { // Create a variety of tools and tool sets for testing - // Some with toolReferenceName, some without, some from extensions, mcp snd user defined + // Some with toolReferenceName, some without, some from extensions, mcp and user defined const tool1: IToolData = { id: 'tool1', @@ -177,7 +177,7 @@ suite('LanguageModelToolsService', () => { 'userToolSetRefName', { description: 'Test Set' } )); - store.add(userToolSet.addTool(tool1)); + store.add(userToolSet.addTool(tool2)); /** MCP tool in a MCP tool set */ @@ -470,73 +470,167 @@ suite('LanguageModelToolsService', () => { }, 'Expected tool call to be cancelled'); }); - test('toToolAndToolSetEnablementMap', () => { - const toolData1: IToolData = { - id: 'tool1', - toolReferenceName: 'refTool1', - modelDescription: 'Test Tool 1', - displayName: 'Test Tool 1', - source: ToolDataSource.Internal, - canBeReferencedInPrompt: true, - }; - - const toolData2: IToolData = { - id: 'tool2', - toolReferenceName: 'refTool2', - modelDescription: 'Test Tool 2', - displayName: 'Test Tool 2', - source: ToolDataSource.Internal, - canBeReferencedInPrompt: true, - }; + test('toQualifiedToolNames', () => { + setupToolsForTest(service, store); - const toolData3: IToolData = { - id: 'tool3', - // No toolReferenceName - modelDescription: 'Test Tool 3', - displayName: 'Test Tool 3', - source: ToolDataSource.Internal, - canBeReferencedInPrompt: true, - }; + const tool1 = service.getToolByQualifiedName('tool1RefName'); + const extTool1 = service.getToolByQualifiedName('my.extension/extTool1RefName'); + const mcpToolSet = service.getToolByQualifiedName('mcpToolSetRefName/*'); + const mcpTool1 = service.getToolByQualifiedName('mcpToolSetRefName/mcpTool1RefName'); + const internalToolSet = service.getToolByQualifiedName('internalToolSetRefName'); + const internalTool = service.getToolByQualifiedName('internalToolSetRefName/internalToolSetTool1RefName'); + const userToolSet = service.getToolSet('userToolSet'); + const unknownTool = { id: 'unregisteredTool', toolReferenceName: 'unregisteredToolRefName', modelDescription: 'Unregistered Tool', displayName: 'Unregistered Tool', source: ToolDataSource.Internal, canBeReferencedInPrompt: true } satisfies IToolData; + const unknownToolSet = service.createToolSet(ToolDataSource.Internal, 'unknownToolSet', 'unknownToolSetRefName', { description: 'Unknown Test Set' }); + unknownToolSet.dispose(); // unregister the set + assert.ok(tool1); + assert.ok(extTool1); + assert.ok(mcpTool1); + assert.ok(mcpToolSet); + assert.ok(internalToolSet); + assert.ok(internalTool); + assert.ok(userToolSet); - store.add(service.registerToolData(toolData1)); - store.add(service.registerToolData(toolData2)); - store.add(service.registerToolData(toolData3)); + // Test with some enabled tool + { + // creating a map by hand is a no-go, we just do it for this test + const map = new Map([[tool1, true], [extTool1, true], [mcpToolSet, true], [mcpTool1, true]]); + const qualifiedNames = service.toQualifiedToolNames(map); + const expectedQualifiedNames = ['tool1RefName', 'my.extension/extTool1RefName', 'mcpToolSetRefName/*']; + assert.deepStrictEqual(qualifiedNames.sort(), expectedQualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } + // Test with user data + { + // creating a map by hand is a no-go, we just do it for this test + const map = new Map([[tool1, true], [userToolSet, true], [internalToolSet, false], [internalTool, true]]); + const qualifiedNames = service.toQualifiedToolNames(map); + const expectedQualifiedNames = ['tool1RefName', 'internalToolSetRefName/internalToolSetTool1RefName']; + assert.deepStrictEqual(qualifiedNames.sort(), expectedQualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } + // Test with unknown tool and tool set + { + // creating a map by hand is a no-go, we just do it for this test + const map = new Map([[unknownTool, true], [unknownToolSet, true], [internalToolSet, true], [internalTool, true]]); + const qualifiedNames = service.toQualifiedToolNames(map); + const expectedQualifiedNames = ['internalToolSetRefName']; + assert.deepStrictEqual(qualifiedNames.sort(), expectedQualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } + }); - // Test with enabled tools - const enabledToolNames = [toolData1].map(t => service.getQualifiedToolName(t)); - const result1 = service.toToolAndToolSetEnablementMap(enabledToolNames); + test('toToolAndToolSetEnablementMap', () => { + setupToolsForTest(service, store); - assert.strictEqual(result1.get(toolData1), true, 'tool1 should be enabled'); - assert.strictEqual(result1.get(toolData2), false, 'tool2 should be disabled'); - assert.strictEqual(result1.get(toolData3), false, 'tool3 should be disabled (no reference name)'); + const allQualifiedNames = [ + 'tool1RefName', + 'Tool2 Display Name', + 'my.extension/extTool1RefName', + 'mcpToolSetRefName/*', + 'mcpToolSetRefName/mcpTool1RefName', + 'internalToolSetRefName', + 'internalToolSetRefName/internalToolSetTool1RefName', + ]; + const numOfTools = allQualifiedNames.length + 1; // +1 for userToolSet which has no qualified name but is a tool set - const qualifiedNames1 = service.toQualifiedToolNames(result1); - assert.deepStrictEqual(qualifiedNames1.sort(), enabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + const tool1 = service.getToolByQualifiedName('tool1RefName'); + const tool2 = service.getToolByQualifiedName('Tool2 Display Name'); + const extTool1 = service.getToolByQualifiedName('my.extension/extTool1RefName'); + const mcpToolSet = service.getToolByQualifiedName('mcpToolSetRefName/*'); + const mcpTool1 = service.getToolByQualifiedName('mcpToolSetRefName/mcpTool1RefName'); + const internalToolSet = service.getToolByQualifiedName('internalToolSetRefName'); + const internalTool = service.getToolByQualifiedName('internalToolSetRefName/internalToolSetTool1RefName'); + const userToolSet = service.getToolSet('userToolSet'); + assert.ok(tool1); + assert.ok(tool2); + assert.ok(extTool1); + assert.ok(mcpTool1); + assert.ok(mcpToolSet); + assert.ok(internalToolSet); + assert.ok(internalTool); + assert.ok(userToolSet); + // Test with enabled tool + { + const qualifiedNames = ['tool1RefName']; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 1, 'Expected 1 tool to be enabled'); + assert.strictEqual(result1.get(tool1), true, 'tool1 should be enabled'); + const qualifiedNames1 = service.toQualifiedToolNames(result1); + assert.deepStrictEqual(qualifiedNames1.sort(), qualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } // Test with multiple enabled tools - const multipleEnabledToolNames = [toolData1, toolData2].map(t => service.getQualifiedToolName(t)); - const result2 = service.toToolAndToolSetEnablementMap(multipleEnabledToolNames); - - assert.strictEqual(result1.get(toolData1), true, 'tool1 should be enabled'); - assert.strictEqual(result2.get(toolData2), true, 'tool2 should be enabled'); - assert.strictEqual(result2.get(toolData3), false, 'tool3 should be disabled'); - - const qualifiedNames2 = service.toQualifiedToolNames(result2); - assert.deepStrictEqual(qualifiedNames2.sort(), multipleEnabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names'); - + { + const qualifiedNames = ['my.extension/extTool1RefName', 'mcpToolSetRefName/*', 'internalToolSetRefName/internalToolSetTool1RefName']; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 4, 'Expected 4 tools to be enabled'); + assert.strictEqual(result1.get(extTool1), true, 'extTool1 should be enabled'); + assert.strictEqual(result1.get(mcpToolSet), true, 'mcpToolSet should be enabled'); + assert.strictEqual(result1.get(mcpTool1), true, 'mcpTool1 should be enabled because the set is enabled'); + assert.strictEqual(result1.get(internalTool), true, 'internalTool should be enabled because the set is enabled'); + + const qualifiedNames1 = service.toQualifiedToolNames(result1); + assert.deepStrictEqual(qualifiedNames1.sort(), qualifiedNames.sort(), 'toQualifiedToolNames should return the expected names'); + } + // Test with all enabled tools, redundant names + { + const result1 = service.toToolAndToolSetEnablementMap(allQualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 8, 'Expected 8 tools to be enabled'); + const qualifiedNames1 = service.toQualifiedToolNames(result1); + const expectedQualifiedNames = ['tool1RefName', 'Tool2 Display Name', 'my.extension/extTool1RefName', 'mcpToolSetRefName/*', 'internalToolSetRefName']; + assert.deepStrictEqual(qualifiedNames1.sort(), expectedQualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } // Test with no enabled tools - const noEnabledToolNames: string[] = []; - const result3 = service.toToolAndToolSetEnablementMap(noEnabledToolNames); + { + const qualifiedNames: string[] = []; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 0, 'Expected 0 tools to be enabled'); - assert.strictEqual(result3.get(toolData1), false, 'tool1 should be disabled'); - assert.strictEqual(result3.get(toolData2), false, 'tool2 should be disabled'); - assert.strictEqual(result3.get(toolData3), false, 'tool3 should be disabled'); + const qualifiedNames1 = service.toQualifiedToolNames(result1); + assert.deepStrictEqual(qualifiedNames1.sort(), qualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } + // Test with unknown tool + { + const qualifiedNames: string[] = ['unknownToolRefName']; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 0, 'Expected 0 tools to be enabled'); - const qualifiedNames3 = service.toQualifiedToolNames(result3); - assert.deepStrictEqual(qualifiedNames3.sort(), noEnabledToolNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + const qualifiedNames1 = service.toQualifiedToolNames(result1); + assert.deepStrictEqual(qualifiedNames1.sort(), [], 'toQualifiedToolNames should return no enabled names'); + } + // Test with legacy tool names + { + const qualifiedNames: string[] = ['extTool1RefName', 'mcpToolSetRefName', 'internalToolSetTool1RefName']; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 4, 'Expected 4 tools to be enabled'); + assert.strictEqual(result1.get(extTool1), true, 'extTool1 should be enabled'); + assert.strictEqual(result1.get(mcpToolSet), true, 'mcpToolSet should be enabled'); + assert.strictEqual(result1.get(mcpTool1), true, 'mcpTool1 should be enabled because the set is enabled'); + assert.strictEqual(result1.get(internalTool), true, 'internalTool should be enabled'); + + const qualifiedNames1 = service.toQualifiedToolNames(result1); + const expectedQualifiedNames: string[] = ['my.extension/extTool1RefName', 'mcpToolSetRefName/*', 'internalToolSetRefName/internalToolSetTool1RefName']; + assert.deepStrictEqual(qualifiedNames1.sort(), expectedQualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } + // Test with tool in user tool set + { + const qualifiedNames = ['Tool2 Display Name']; + const result1 = service.toToolAndToolSetEnablementMap(qualifiedNames); + assert.strictEqual(result1.size, numOfTools, `Expected ${numOfTools} tools and tool sets`); + assert.strictEqual([...result1.entries()].filter(([_, enabled]) => enabled).length, 2, 'Expected 1 tool and user tool set to be enabled'); + assert.strictEqual(result1.get(tool2), true, 'tool2 should be enabled'); + assert.strictEqual(result1.get(userToolSet), true, 'userToolSet should be enabled'); + + const qualifiedNames1 = service.toQualifiedToolNames(result1); + assert.deepStrictEqual(qualifiedNames1.sort(), qualifiedNames.sort(), 'toQualifiedToolNames should return the original enabled names'); + } }); test('toToolAndToolSetEnablementMap with extension tool', () => { @@ -1579,4 +1673,5 @@ suite('LanguageModelToolsService', () => { }); + }); From 575040ac643b7a8d730e749cffcb59b7b573ba50 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 2 Oct 2025 16:45:58 +0200 Subject: [PATCH 0734/4355] update `CODEOWNERS` (#269542) --- .github/CODEOWNERS | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 311095aa73f..295738106b4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,12 +29,25 @@ src/vs/base/browser/domSanitize.ts @mjbvz src/vs/base/parts/sandbox/** @bpasero @deepak1556 src/vs/base/parts/storage/** @bpasero @deepak1556 src/vs/platform/backup/** @bpasero +src/vs/platform/editor/** @bpasero @benibenj src/vs/platform/files/** @bpasero @deepak1556 src/vs/base/node/pfs.ts @bpasero @deepak1556 src/vs/code/** @bpasero @deepak1556 -src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno +src/vs/workbench/services/auxiliaryWindow/** @bpasero +src/vs/workbench/services/editor/** @bpasero @benibenj +src/vs/workbench/services/files/** @bpasero +src/vs/workbench/services/history/** @bpasero +src/vs/workbench/services/host/** @bpasero +src/vs/workbench/services/layout/** @bpasero @benibenj +src/vs/workbench/services/lifecycle/** @bpasero +src/vs/workbench/services/notification/** @bpasero +src/vs/workbench/services/untitled/** @bpasero src/vs/workbench/services/textfile/** @bpasero src/vs/workbench/services/workingCopy/** @bpasero +src/vs/workbench/common/** @bpasero @benibenj @sandy081 +src/vs/workbench/browser/** @bpasero @benibenj @sandy081 +src/vs/workbench/electron-browser/** @bpasero @benibenj @sandy081 +src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno # ensure the API police is aware of changes to the vscode-dts file # this is only about the final API, not about proposed API changes From 50ae9dbc1d45e7b96a3527adeb2607fb62b4d073 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Thu, 2 Oct 2025 08:25:28 -0700 Subject: [PATCH 0735/4355] revised titles and replaced more frogs --- .../workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 522f294d5d8..d3d4b5ffea3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -707,7 +707,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Thu, 2 Oct 2025 17:45:16 +0200 Subject: [PATCH 0736/4355] send much less telemetry events --- .../browser/model/inlineCompletionsSource.ts | 54 +++++++++++++++++-- .../common/inlineCompletionsUnification.ts | 9 +++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 329491cd84b..3308b9043d4 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -12,12 +12,14 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposab import { derived, IObservable, IObservableWithChange, ITransaction, observableValue, recordChangesLazy, transaction } from '../../../../../base/common/observable.js'; // eslint-disable-next-line local/code-no-deep-import-of-internal import { observableReducerSettable } from '../../../../../base/common/observableInternal/experimental/reducer.js'; -import { isDefined } from '../../../../../base/common/types.js'; +import { isDefined, isObject } from '../../../../../base/common/types.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../../platform/observable/common/platformObservableUtils.js'; +import product from '../../../../../platform/product/common/product.js'; import { StringEdit } from '../../../../common/core/edits/stringEdit.js'; import { Position } from '../../../../common/core/position.js'; import { InlineCompletionEndOfLifeReasonKind, InlineCompletionTriggerKind, InlineCompletionsProvider } from '../../../../common/languages.js'; @@ -73,6 +75,8 @@ export class InlineCompletionsSource extends Disposable { public readonly inlineCompletions = this._state.map(this, v => v.inlineCompletions); public readonly suggestWidgetInlineCompletions = this._state.map(this, v => v.suggestWidgetInlineCompletions); + private _completionsEnabled: Record | undefined = undefined; + constructor( private readonly _textModel: ITextModel, private readonly _versionId: IObservableWithChange, @@ -82,6 +86,7 @@ export class InlineCompletionsSource extends Disposable { @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(); this._loggingEnabled = observableConfigValue('editor.inlineSuggest.logFetch', false, this._configurationService).recomputeInitiallyAndOnChange(this._store); @@ -94,6 +99,24 @@ export class InlineCompletionsSource extends Disposable { )); this.clearOperationOnTextModelChange.recomputeInitiallyAndOnChange(this._store); + + const enablementSetting = product.defaultChatAgent?.completionsEnablementSetting ?? undefined; + if (enablementSetting) { + this._updateCompletionsEnablement(enablementSetting); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(enablementSetting)) { + this._updateCompletionsEnablement(enablementSetting); + } + })); + } + } + + private _updateCompletionsEnablement(enalementSetting: string) { + const result = this._configurationService.getValue>(enalementSetting); + if (!isObject(result)) { + this._completionsEnabled = undefined; + } + this._completionsEnabled = result; } public readonly clearOperationOnTextModelChange = derived(this, reader => { @@ -260,7 +283,8 @@ export class InlineCompletionsSource extends Disposable { if (source.token.isCancellationRequested) { requestResponseInfo.setNoSuggestionReasonIfNotSet('canceled:whileFetching'); } else { - requestResponseInfo.setNoSuggestionReasonIfNotSet('noSuggestion'); + const completionsQuotaExceeded = this._contextKeyService.getContextKeyValue('completionsQuotaExceeded'); + requestResponseInfo.setNoSuggestionReasonIfNotSet(completionsQuotaExceeded ? 'completionsQuotaExceeded' : 'noSuggestion'); } } @@ -356,7 +380,11 @@ export class InlineCompletionsSource extends Disposable { private sendInlineCompletionsRequestTelemetry( requestResponseInfo: RequestResponseData ): void { - if (!this._sendRequestData.get()) { + if (!this._sendRequestData.get() && !this._contextKeyService.getContextKeyValue('isRunningUnificationExperiment')) { + return; + } + + if (requestResponseInfo.requestUuid === undefined || requestResponseInfo.hasProducedSuggestion) { return; } @@ -364,6 +392,14 @@ export class InlineCompletionsSource extends Disposable { return; } + if (!isCompletionsEnabled(this._completionsEnabled, this._textModel.getLanguageId())) { + return; + } + + if (!requestResponseInfo.providers.some(p => isCopilotLikeExtension(p.providerId?.extensionId))) { + return; + } + const emptyEndOfLifeEvent: InlineCompletionEndOfLifeEvent = { id: requestResponseInfo.requestUuid, opportunityId: requestResponseInfo.requestUuid, @@ -472,6 +508,18 @@ function isSubset(set1: Set, set2: Set): boolean { return [...set1].every(item => set2.has(item)); } +function isCompletionsEnabled(completionsEnablementObject: Record | undefined, modeId: string = '*'): boolean { + if (completionsEnablementObject === undefined) { + return false; // default to disabled if setting is not available + } + + if (typeof completionsEnablementObject[modeId] !== 'undefined') { + return Boolean(completionsEnablementObject[modeId]); // go with setting if explicitly defined + } + + return Boolean(completionsEnablementObject['*']); // fallback to global setting otherwise +} + class UpdateOperation implements IDisposable { constructor( public readonly request: UpdateRequest, diff --git a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts index 134e5bbb2bd..9fa24a70542 100644 --- a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts +++ b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts @@ -6,6 +6,7 @@ import { equals } from '../../../../base/common/arrays.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchAssignmentService } from '../../assignment/common/assignmentService.js'; @@ -29,17 +30,22 @@ const CODE_UNIFICATION_PREFIX = 'cmp-cht-'; const CODE_UNIFICATION_FF = 'inlineCompletionsUnificationCode'; const MODEL_UNIFICATION_FF = 'inlineCompletionsUnificationModel'; +export const isRunningUnificationExperiment = new RawContextKey('isRunningUnificationExperiment', false); + export class InlineCompletionsUnificationImpl extends Disposable implements IInlineCompletionsUnificationService { readonly _serviceBrand: undefined; private _state = new InlineCompletionsUnificationState(false, false, []); public get state(): IInlineCompletionsUnificationState { return this._state; } + private isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService); + private readonly _onDidStateChange = this._register(new Emitter()); public readonly onDidStateChange = this._onDidStateChange.event; constructor( - @IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService + @IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(); this._register(this._assignmentService.onDidRefetchAssignments(() => this._update())); @@ -62,6 +68,7 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl return; } this._state = newState; + this.isRunningUnificationExperiment.set(this._state.codeUnification || this._state.modelUnification); this._onDidStateChange.fire(); } } From 1feb8bf47ad1ecf2e5fac0678a21d6420b7cd086 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 2 Oct 2025 17:50:28 +0200 Subject: [PATCH 0737/4355] :lipstick: --- .../inlineCompletions/browser/model/inlineCompletionsSource.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 3308b9043d4..dd7eb0cc112 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -115,8 +115,9 @@ export class InlineCompletionsSource extends Disposable { const result = this._configurationService.getValue>(enalementSetting); if (!isObject(result)) { this._completionsEnabled = undefined; + } else { + this._completionsEnabled = result; } - this._completionsEnabled = result; } public readonly clearOperationOnTextModelChange = derived(this, reader => { From 7f88c10ccdf85280bf440fb8fbb0c2cea0da2f55 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 2 Oct 2025 17:51:00 +0200 Subject: [PATCH 0738/4355] :lipstick: --- .../inlineCompletions/browser/model/inlineCompletionsSource.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index dd7eb0cc112..50aa83335a6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -389,9 +389,6 @@ export class InlineCompletionsSource extends Disposable { return; } - if (requestResponseInfo.requestUuid === undefined || requestResponseInfo.hasProducedSuggestion) { - return; - } if (!isCompletionsEnabled(this._completionsEnabled, this._textModel.getLanguageId())) { return; From 92669c0424642d9d0d007c50ff187d6861b3beef Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 2 Oct 2025 17:52:42 +0200 Subject: [PATCH 0739/4355] Chat modes not updated on model change & contribution update (#269554) * Chat mode files not updated when autosave is off * update * fix --- .../service/promptsServiceImpl.ts | 88 ++++++++++++++++--- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index e6e88be7b4b..6adc495bd69 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../../../nls.js'; -import { getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { type URI } from '../../../../../../base/common/uri.js'; import { basename } from '../../../../../../base/common/path.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; -import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { Event } from '../../../../../../base/common/event.js'; +import { Disposable, IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { Emitter, Event } from '../../../../../../base/common/event.js'; import { type ITextModel } from '../../../../../../editor/common/model.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; @@ -30,6 +30,7 @@ import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetR import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; import { dirname } from '../../../../../../base/common/resources.js'; import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; +import { Delayer } from '../../../../../../base/common/async.js'; /** * Provides prompt services. @@ -62,7 +63,7 @@ export class PromptsService extends Disposable implements IPromptsService { /** * Lazily created event that is fired when the custom chat modes change. */ - private onDidChangeCustomChatModesEvent: Event | undefined; + private onDidChangeCustomChatModesEmitter: Emitter | undefined; constructor( @ILogService public readonly logger: ILogService, @@ -87,13 +88,15 @@ export class PromptsService extends Disposable implements IPromptsService { * Emitter for the custom chat modes change event. */ public get onDidChangeCustomChatModes(): Event { - if (!this.onDidChangeCustomChatModesEvent) { - this.onDidChangeCustomChatModesEvent = this._register(this.fileLocator.createFilesUpdatedEvent(PromptsType.mode)).event; - this._register(this.onDidChangeCustomChatModesEvent(() => { + if (!this.onDidChangeCustomChatModesEmitter) { + const emitter = this.onDidChangeCustomChatModesEmitter = this._register(new Emitter()); + const chatModelTracker = this._register(new ChatModeUpdateTracker(this.fileLocator, this.modelService)); + this._register(chatModelTracker.onDidChangeContent(() => { this.cachedCustomChatModes = undefined; // reset cached custom chat modes + emitter.fire(); })); } - return this.onDidChangeCustomChatModesEvent; + return this.onDidChangeCustomChatModesEmitter.event; } public getPromptFileType(uri: URI): PromptsType | undefined { @@ -215,7 +218,7 @@ export class PromptsService extends Disposable implements IPromptsService { public async getCustomChatModes(token: CancellationToken): Promise { if (!this.cachedCustomChatModes) { const customChatModes = this.computeCustomChatModes(token); - if (!this.onDidChangeCustomChatModesEvent) { + if (!this.onDidChangeCustomChatModesEmitter) { return customChatModes; } this.cachedCustomChatModes = customChatModes; @@ -227,7 +230,7 @@ export class PromptsService extends Disposable implements IPromptsService { const modeFiles = await this.listPromptFiles(PromptsType.mode, token); const customChatModes = await Promise.all( - modeFiles.map(async ({ uri }): Promise => { + modeFiles.map(async ({ uri, name: modeName }): Promise => { const ast = await this.parseNew(uri, token); let metadata: any | undefined; @@ -259,7 +262,7 @@ export class PromptsService extends Disposable implements IPromptsService { metadata, } satisfies IChatModeInstructions; - const name = getCleanPromptName(uri); + const name = modeName ?? getCleanPromptName(uri); if (!ast.header) { return { uri, name, modeInstructions }; } @@ -290,12 +293,18 @@ export class PromptsService extends Disposable implements IPromptsService { return Disposable.None; } bucket.set(uri, { uri, name, description, storage: PromptsStorage.extension, type, extension } satisfies IExtensionPromptPath); - if (type === PromptsType.mode) { - this.cachedCustomChatModes = undefined; - } + + const updateModesIfRequired = () => { + if (type === PromptsType.mode) { + this.cachedCustomChatModes = undefined; + this.onDidChangeCustomChatModesEmitter?.fire(); + } + }; + updateModesIfRequired(); return { dispose: () => { bucket.delete(uri); + updateModesIfRequired(); } }; } @@ -320,3 +329,54 @@ export function getPromptCommandName(path: string): string { const name = basename(path, PROMPT_FILE_EXTENSION); return name; } + +export class ChatModeUpdateTracker extends Disposable { + + private static readonly CHAT_MODE_UPDATE_DELAY_MS = 200; + + private readonly listeners = new ResourceMap(); + private readonly onDidChatModeModelChange: Emitter; + + public get onDidChangeContent(): Event { + return this.onDidChatModeModelChange.event; + } + + constructor( + fileLocator: PromptFilesLocator, + @IModelService modelService: IModelService, + ) { + super(); + this.onDidChatModeModelChange = this._register(new Emitter()); + const delayer = this._register(new Delayer(ChatModeUpdateTracker.CHAT_MODE_UPDATE_DELAY_MS)); + const trigger = () => delayer.trigger(() => this.onDidChatModeModelChange.fire()); + + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.mode)); + this._register(filesUpdatedEventRegistration.event(() => trigger())); + + const onAdd = (model: ITextModel) => { + if (model.getLanguageId() === MODE_LANGUAGE_ID) { + this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); + } + }; + const onRemove = (languageId: string, uri: URI) => { + if (languageId === MODE_LANGUAGE_ID) { + this.listeners.get(uri)?.dispose(); + this.listeners.delete(uri); + trigger(); + } + }; + this._register(modelService.onModelAdded(model => onAdd(model))); + this._register(modelService.onModelLanguageChanged(e => { + onRemove(e.oldLanguageId, e.model.uri); + onAdd(e.model); + })); + this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); + } + + public override dispose(): void { + super.dispose(); + this.listeners.forEach(listener => listener.dispose()); + this.listeners.clear(); + } + +} From df95260cef839919f15a03a752055c62c86d4ca8 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 2 Oct 2025 12:00:23 -0400 Subject: [PATCH 0740/4355] Improve handling when speech ext not installed/enabled and start dictation is used (#269556) fix #269054 --- .../contrib/terminal/browser/terminalMenus.ts | 2 +- .../voice/browser/terminalVoiceActions.ts | 60 +++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 54b238fc1a5..27e74b241a7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -761,7 +761,7 @@ export function setupTerminalMenus(): void { }, group: 'navigation', order: 9, - when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), HasSpeechProvider, TerminalContextKeys.terminalDictationInProgress.negate()), + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeTerminal), TerminalContextKeys.terminalDictationInProgress.negate()), isHiddenByDefault: true }); MenuRegistry.appendMenuItem(menuId, { diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index ebb4f66c7a0..068f9837539 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -3,9 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize2 } from '../../../../../nls.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IAction } from '../../../../../base/common/actions.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; +import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; import { TerminalCommandId } from '../../../terminal/common/terminal.js'; @@ -17,14 +22,57 @@ export function registerTerminalVoiceActions() { id: TerminalCommandId.StartVoice, title: localize2('workbench.action.terminal.startDictation', "Start Dictation in Terminal"), precondition: ContextKeyExpr.and( - HasSpeechProvider, SpeechToTextInProgress.toNegated(), sharedWhenClause.terminalAvailable ), f1: true, - run: (activeInstance, c, accessor) => { - const instantiationService = accessor.get(IInstantiationService); - TerminalVoiceSession.getInstance(instantiationService).start(); + run: async (activeInstance, c, accessor) => { + const contextKeyService = accessor.get(IContextKeyService); + const commandService = accessor.get(ICommandService); + const notificationService = accessor.get(INotificationService); + const workbenchExtensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService); + const extensionManagementService = accessor.get(IExtensionManagementService); + if (HasSpeechProvider.getValue(contextKeyService)) { + const instantiationService = accessor.get(IInstantiationService); + TerminalVoiceSession.getInstance(instantiationService).start(); + return; + } + const extensions = await extensionManagementService.getInstalled(); + const extension = extensions.find(extension => extension.identifier.id === 'ms-vscode.vscode-speech'); + const extensionIsDisabled = extension && !workbenchExtensionEnablementService.isEnabled(extension); + const learnMoreAction = { + label: localize('viewExtension', "View Extension"), + run: () => commandService.executeCommand('workbench.extensions.search', '@id:ms-vscode.vscode-speech'), + id: '', + tooltip: '', + class: undefined, + enabled: true + }; + + const actions: IAction[] = []; + let message: string; + if (extensionIsDisabled) { + message = localize('terminal.voice.enableSpeechExtension', "You must enable the Speech extension to use Dictation in the Terminal."); + actions.push({ + id: 'enableSpeechExtension', + tooltip: '', + class: undefined, + enabled: true, + label: localize('enableSpeechExtension', "Enable for Workspace"), + run: () => workbenchExtensionEnablementService.setEnablement([extension], EnablementState.EnabledWorkspace), + }, learnMoreAction); + } else { + message = localize('terminal.voice.installSpeechExtension', "You must install the Speech extension to use Dictation in the Terminal."); + actions.push({ + id: 'installSpeechExtension', + label: localize('installSpeechExtension', "Install Speech Extension"), + run: () => commandService.executeCommand('workbench.extensions.installExtension', 'ms-vscode.vscode-speech'), + tooltip: '', + class: undefined, + enabled: true + }, learnMoreAction); + } + notificationService.notify({ severity: Severity.Info, message, actions: { primary: actions } }); } }); From bf2f147897c887472b1d4378edad506798713b9e Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 2 Oct 2025 18:12:49 +0200 Subject: [PATCH 0741/4355] :lipstick: --- .../inlineCompletions/common/inlineCompletionsUnification.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts index 9fa24a70542..918f9c90d28 100644 --- a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts +++ b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts @@ -38,7 +38,7 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl private _state = new InlineCompletionsUnificationState(false, false, []); public get state(): IInlineCompletionsUnificationState { return this._state; } - private isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService); + private isRunningUnificationExperiment; private readonly _onDidStateChange = this._register(new Emitter()); public readonly onDidStateChange = this._onDidStateChange.event; @@ -48,6 +48,7 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(); + this.isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService); this._register(this._assignmentService.onDidRefetchAssignments(() => this._update())); this._update(); } From 491e090d221c460f7def524d2cdaa9ca5cb4fcd9 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 2 Oct 2025 12:50:23 -0400 Subject: [PATCH 0742/4355] Fix lm provider disposable leak (#269565) Prevent disposable leak --- .../workbench/api/browser/mainThreadLanguageModels.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 3efa20e6f5e..dd4fa4ad05a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -55,14 +55,14 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { } $registerLanguageModelProvider(vendor: string): void { - const dipsosables = new DisposableStore(); - dipsosables.add(this._chatProviderService.registerLanguageModelProvider(vendor, { - onDidChange: Event.filter(this._lmProviderChange.event, e => e.vendor === vendor, dipsosables) as unknown as Event, + const disposables = this._store.add(new DisposableStore()); + disposables.add(this._chatProviderService.registerLanguageModelProvider(vendor, { + onDidChange: Event.filter(this._lmProviderChange.event, e => e.vendor === vendor, disposables) as unknown as Event, provideLanguageModelChatInfo: async (options, token) => { const modelsAndIdentifiers = await this._proxy.$provideLanguageModelChatInfo(vendor, options, token); modelsAndIdentifiers.forEach(m => { if (m.metadata.auth) { - dipsosables.add(this._registerAuthenticationProvider(m.metadata.extension, m.metadata.auth)); + disposables.add(this._registerAuthenticationProvider(m.metadata.extension, m.metadata.auth)); } }); return modelsAndIdentifiers; @@ -96,7 +96,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { return this._proxy.$provideTokenLength(modelId, str, token); }, })); - this._providerRegistrations.set(vendor, dipsosables); + this._providerRegistrations.set(vendor, disposables); } $onLMProviderChange(vendor: string): void { From be15710c56634ee20ef0471a4ed690298682ad5f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 2 Oct 2025 18:56:33 +0200 Subject: [PATCH 0743/4355] Name of contributed prompt not showing (#269566) --- .../promptCodingAgentActionOverlay.ts | 7 +++-- .../browser/promptSyntax/runPromptAction.ts | 8 ++++-- .../promptSyntax/service/promptsService.ts | 5 ++++ .../service/promptsServiceImpl.ts | 28 +++++++++++++------ .../chat/test/common/mockPromptsService.ts | 1 + 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptCodingAgentActionOverlay.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptCodingAgentActionOverlay.ts index 9ef18bf66fb..c582fdec804 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptCodingAgentActionOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptCodingAgentActionOverlay.ts @@ -11,9 +11,9 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js'; import { localize } from '../../../../../nls.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; -import { getPromptCommandName } from '../../common/promptSyntax/service/promptsServiceImpl.js'; import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; import { $ } from '../../../../../base/browser/dom.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; export class PromptCodingAgentActionOverlayWidget extends Disposable implements IOverlayWidget { @@ -27,7 +27,8 @@ export class PromptCodingAgentActionOverlayWidget extends Disposable implements private readonly _editor: ICodeEditor, @ICommandService private readonly _commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IRemoteCodingAgentsService private readonly _remoteCodingAgentService: IRemoteCodingAgentsService + @IRemoteCodingAgentsService private readonly _remoteCodingAgentService: IRemoteCodingAgentsService, + @IPromptsService private readonly _promptsService: IPromptsService, ) { super(); @@ -105,7 +106,7 @@ export class PromptCodingAgentActionOverlayWidget extends Disposable implements this._button.enabled = false; try { const promptContent = model.getValue(); - const promptName = getPromptCommandName(model.uri.path); + const promptName = await this._promptsService.getPromptCommandName(model.uri); const agents = this._remoteCodingAgentService.getAvailableAgents(); const agent = agents[0]; // Use the first available agent diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts index ee8cdf2f039..6148b8f20d0 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts @@ -29,7 +29,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { getPromptCommandName } from '../../common/promptSyntax/service/promptsServiceImpl.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; /** * Condition for the `Run Current Prompt` action. @@ -134,6 +134,7 @@ abstract class RunPromptBaseAction extends Action2 { ): Promise { const viewsService = accessor.get(IViewsService); const commandService = accessor.get(ICommandService); + const promptsService = accessor.get(IPromptsService); resource ||= getActivePromptFileUri(accessor); assertDefined( @@ -147,7 +148,7 @@ abstract class RunPromptBaseAction extends Action2 { const widget = await showChatView(viewsService); if (widget) { - widget.setInput(`/${getPromptCommandName(resource.path)}`); + widget.setInput(`/${await promptsService.getPromptCommandName(resource)}`); // submit the prompt immediately await widget.acceptInput(); } @@ -209,6 +210,7 @@ class RunSelectedPromptAction extends Action2 { const viewsService = accessor.get(IViewsService); const commandService = accessor.get(ICommandService); const instaService = accessor.get(IInstantiationService); + const promptsService = accessor.get(IPromptsService); const pickers = instaService.createInstance(PromptFilePickers); @@ -232,7 +234,7 @@ class RunSelectedPromptAction extends Action2 { const widget = await showChatView(viewsService); if (widget) { - widget.setInput(`/${getPromptCommandName(promptFile.path)}`); + widget.setInput(`/${await promptsService.getPromptCommandName(promptFile)}`); // submit the prompt immediately await widget.acceptInput(); widget.focusInput(); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 6fec4700c50..01466a2a9e4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -192,6 +192,11 @@ export interface IPromptsService extends IDisposable { */ findPromptSlashCommands(): Promise; + /** + * Returns the prompt command name for the given URI. + */ + getPromptCommandName(uri: URI): Promise; + /** * Event that is triggered when the list of custom chat modes changes. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 6adc495bd69..b393d1cec1c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -28,7 +28,7 @@ import { ResourceMap } from '../../../../../../base/common/map.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js'; import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; -import { dirname } from '../../../../../../base/common/resources.js'; +import { dirname, isEqual } from '../../../../../../base/common/resources.js'; import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; import { Delayer } from '../../../../../../base/common/async.js'; @@ -190,23 +190,32 @@ export class PromptsService extends Disposable implements IPromptsService { return data.promptPath.uri; } - const files = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); + const promptPaths = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); const command = data.command; - const result = files.find(file => getPromptCommandName(file.uri.path) === command); + const result = promptPaths.find(promptPath => getCommandNameFromPromptPath(promptPath) === command); if (result) { return result.uri; } - const textModel = this.modelService.getModels().find(model => model.getLanguageId() === PROMPT_LANGUAGE_ID && getPromptCommandName(model.uri.path) === command); + const textModel = this.modelService.getModels().find(model => model.getLanguageId() === PROMPT_LANGUAGE_ID && getCommandNameFromURI(model.uri) === command); if (textModel) { return textModel.uri; } return undefined; } + public async getPromptCommandName(uri: URI): Promise { + const promptPaths = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); + const promptPath = promptPaths.find(promptPath => isEqual(promptPath.uri, uri)); + if (!promptPath) { + return getCommandNameFromURI(uri); + } + return getCommandNameFromPromptPath(promptPath); + } + public async findPromptSlashCommands(): Promise { const promptFiles = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); return promptFiles.map(promptPath => { - const command = getPromptCommandName(promptPath.uri.path); + const command = getCommandNameFromPromptPath(promptPath); return { command, detail: localize('prompt.file.detail', 'Prompt file: {0}', this.labelService.getUriLabel(promptPath.uri, { relative: true })), @@ -325,9 +334,12 @@ export class PromptsService extends Disposable implements IPromptsService { } } -export function getPromptCommandName(path: string): string { - const name = basename(path, PROMPT_FILE_EXTENSION); - return name; +function getCommandNameFromPromptPath(promptPath: IPromptPath): string { + return promptPath.name ?? getCommandNameFromURI(promptPath.uri); +} + +function getCommandNameFromURI(uri: URI): string { + return basename(uri.fsPath, PROMPT_FILE_EXTENSION); } export class ChatModeUpdateTracker extends Disposable { diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index 88ec01fb787..a1b522e7ccd 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -38,6 +38,7 @@ export class MockPromptsService implements IPromptsService { asPromptSlashCommand(_command: string): any { return undefined; } resolvePromptSlashCommand(_data: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } findPromptSlashCommands(): Promise { throw new Error('Not implemented'); } + getPromptCommandName(uri: URI): Promise { throw new Error('Not implemented'); } parse(_uri: URI, _type: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } parseNew(_uri: URI, _token: CancellationToken): Promise { throw new Error('Not implemented'); } getPromptFileType(_resource: URI): any { return undefined; } From 6344c3290f9cd85cf37a72496429d096abd93d4e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 2 Oct 2025 13:05:39 -0400 Subject: [PATCH 0744/4355] for tasks with problem matchers, don't pass back output, only succinct reply (#269568) fixes #269563 --- .../terminalContrib/chatAgentTools/browser/taskHelpers.ts | 3 +-- .../browser/tools/monitoring/outputMonitor.ts | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index 2069886e67f..ab46476d4e2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -224,9 +224,8 @@ export async function taskProblemPollFn(execution: IExecution, token: Cancellati ? new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn) : undefined }); - const label: string = uri ? uri.path.split('/').pop() ?? uri.toString() : ''; const message = marker.message ?? ''; - problemList.push(`Problem: ${message} in ${label} coming from ${owner}`); + problemList.push(`Problem: ${message} in ${uri.fsPath} coming from ${owner} starting on line ${marker.startLineNumber}${marker.startColumn ? `, column ${marker.startColumn} and ending on line ${marker.endLineNumber}${marker.endColumn ? `, column ${marker.endColumn}` : ''}` : ''}`); } } if (problemList.length === 0) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 15d5303831e..6971e62fe38 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -105,6 +105,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { let modelOutputEvalResponse; let resources; + let output; let extended = false; try { @@ -134,6 +135,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } else { resources = idleResult.resources; modelOutputEvalResponse = idleResult.modelOutputEvalResponse; + output = idleResult.output; } break; } @@ -149,7 +151,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } finally { this._pollingResult = { state: this._state, - output: this._execution.getOutput(), + output: output ?? this._execution.getOutput(), modelOutputEvalResponse: token.isCancellationRequested ? 'Cancelled' : modelOutputEvalResponse, pollDurationMs: Date.now() - pollStartTime, resources @@ -161,7 +163,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } - private async _handleIdleState(token: CancellationToken): Promise<{ resources?: ILinkLocation[]; modelOutputEvalResponse?: string; shouldContinuePollling: boolean }> { + private async _handleIdleState(token: CancellationToken): Promise<{ resources?: ILinkLocation[]; modelOutputEvalResponse?: string; shouldContinuePollling: boolean; output?: string }> { const confirmationPrompt = await this._determineUserInputOptions(this._execution, token); if (confirmationPrompt?.detectedRequestForFreeFormInput) { @@ -208,7 +210,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { const custom = await this._pollFn?.(this._execution, token, this._taskService); const resources = custom?.resources; const modelOutputEvalResponse = await this._assessOutputForErrors(this._execution.getOutput(), token); - return { resources, modelOutputEvalResponse, shouldContinuePollling: false }; + return { resources, modelOutputEvalResponse, shouldContinuePollling: false, output: custom?.output }; } private async _handleTimeoutState(command: string, invocationContext: IToolInvocationContext | undefined, extended: boolean, token: CancellationToken): Promise { From 782bd965b4ba09eb50ed1ac3e26f9b15b552926a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 2 Oct 2025 13:36:31 -0400 Subject: [PATCH 0745/4355] polish setting descriptions (#269374) fixes #269133 --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 91813baa523..c14190c72a9 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -235,7 +235,7 @@ configurationRegistry.registerConfiguration({ }, 'chat.notifyWindowOnConfirmation': { type: 'boolean', - description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should notify the user when a confirmation is needed while the window is not in focus. This includes a window badge as well as notification toast."), + description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should present the user with an OS notification when a confirmation is needed while the window is not in focus. This includes a window badge as well as notification toast."), default: true, }, [ChatConfiguration.GlobalAutoApprove]: { @@ -316,7 +316,7 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.NotifyWindowOnResponseReceived]: { type: 'boolean', default: true, - description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should notify the user when a response is received while the window is not in focus. This includes a window badge as well as notification toast."), + description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should present the user with an OS notification when a response is received while the window is not in focus. This includes a window badge as well as notification toast."), }, 'chat.checkpoints.enabled': { type: 'boolean', From 7f2038b085b376ffefc4c086e441ed67e9a2d302 Mon Sep 17 00:00:00 2001 From: Lee Murray Date: Thu, 2 Oct 2025 20:02:26 +0100 Subject: [PATCH 0746/4355] Revert "Enhance action label styling for high-contrast themes" (#269546) Co-authored-by: Benjamin Pasero --- .../browser/parts/compositeBarActions.ts | 23 +----------- .../browser/parts/media/paneCompositePart.css | 36 ++++--------------- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 140de9fe178..f0aa6f62f1d 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -158,7 +158,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { protected badge!: HTMLElement; protected override readonly options: ICompositeBarActionViewItemOptions; - private labelContainer: HTMLElement | undefined; private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: Timeout | undefined; @@ -396,33 +395,13 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { protected override updateLabel(): void { this.label.className = 'action-label'; - if (this.compositeBarActionItem?.classNames) { + if (this.compositeBarActionItem.classNames) { this.label.classList.add(...this.compositeBarActionItem.classNames); } if (!this.options.icon) { this.label.textContent = this.action.label; } - - const hasUriIcon = this.compositeBarActionItem?.classNames?.includes('uri-icon'); - if (hasUriIcon && !this.labelContainer) { - this.addHighContrastContainer(); - } else if (!hasUriIcon && this.labelContainer) { - this.removeHighContrastContainer(); - } - } - - private addHighContrastContainer(): void { - this.labelContainer = $('.action-label-hc-container'); - this.label.replaceWith(this.labelContainer); - this.labelContainer.appendChild(this.label); - } - - private removeHighContrastContainer(): void { - if (this.labelContainer) { - this.labelContainer.replaceWith(this.label); - this.labelContainer = undefined; - } } private updateTitle(): void { diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 4e475f1bc99..9618a42d740 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -319,40 +319,16 @@ border-top-width: 2px; } -/* Apply outline to action-label for non-uri-icon elements */ -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label:not(.uri-icon), -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label:not(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label:not(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label:not(.uri-icon) { - outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; -} - -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label:not(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label:not(.uri-icon) { - outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; -} - -/* Apply outline to action-label-hc-container wrapper for uri-icon elements to avoid masking issues */ -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label-hc-container:has(.uri-icon), -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label-hc-container:has(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label-hc-container:has(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label-hc-container:has(.uri-icon) { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; - outline-offset: 2px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label-hc-container:has(.uri-icon), -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label-hc-container:has(.uri-icon) { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; - outline-offset: 2px; -} - -/* Style the wrapper to match the label dimensions for uri-icons */ -.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label-hc-container, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label-hc-container { - display: flex; - align-items: center; - justify-content: center; } /** Empty Pane Message **/ From fe9739768782a0b646160f9bf4622e604aaf1357 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:14:16 -0700 Subject: [PATCH 0747/4355] Fix accept behavior (#269595) Fixes https://github.com/microsoft/vscode/issues/269557 --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 48d269e9bad..76e9a19cd1f 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1163,7 +1163,7 @@ export class QuickPick Date: Thu, 2 Oct 2025 16:11:20 -0400 Subject: [PATCH 0748/4355] Put terminal voice actions in separate group (#269596) fix #269587 --- .../contrib/terminal/browser/terminalMenus.ts | 26 ------------------- .../voice/browser/terminalVoiceActions.ts | 23 +++++++++++++--- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 27e74b241a7..fa028e082ac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -521,32 +521,6 @@ export function setupTerminalMenus(): void { isHiddenByDefault: true }, }, - { - id: MenuId.ViewTitle, - item: { - command: { - id: TerminalCommandId.StartVoice, - title: localize('workbench.action.terminal.startVoice', "Start Dictation"), - }, - group: 'navigation', - order: 9, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress.toNegated()), - isHiddenByDefault: true - }, - }, - { - id: MenuId.ViewTitle, - item: { - command: { - id: TerminalCommandId.StopVoice, - title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), - }, - group: 'navigation', - order: 9, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress), - isHiddenByDefault: true - }, - }, ] ); diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index 068f9837539..b4cf1b4fac7 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -5,6 +5,7 @@ import { IAction } from '../../../../../base/common/actions.js'; import { localize, localize2 } from '../../../../../nls.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; @@ -13,7 +14,7 @@ import { INotificationService, Severity } from '../../../../../platform/notifica import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; -import { TerminalCommandId } from '../../../terminal/common/terminal.js'; +import { TERMINAL_VIEW_ID, TerminalCommandId } from '../../../terminal/common/terminal.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalVoiceSession } from './terminalVoice.js'; @@ -73,7 +74,15 @@ export function registerTerminalVoiceActions() { }, learnMoreAction); } notificationService.notify({ severity: Severity.Info, message, actions: { primary: actions } }); - } + }, + menu: [ + { + id: MenuId.ViewTitle, + group: 'voice', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress.toNegated()), + isHiddenByDefault: true + }, + ] }); registerActiveInstanceAction({ @@ -84,6 +93,14 @@ export function registerTerminalVoiceActions() { run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); TerminalVoiceSession.getInstance(instantiationService).stop(true); - } + }, + menu: [ + { + id: MenuId.ViewTitle, + group: 'voice', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress), + isHiddenByDefault: true + }, + ] }); } From f8b61d4a40ac5d269e9b1655a4448144554c00be Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:18:46 -0700 Subject: [PATCH 0749/4355] Add missing 'Open Changes' tooltip (#269607) Add missing 'Open Changes' tooltip (https://github.com/microsoft/vscode/issues/269562) --- .../chat/browser/chatContentParts/chatMultiDiffContentPart.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index 358e919f517..6a9e13397db 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -100,6 +100,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent private renderViewAllFileChangesButton(container: HTMLElement): IDisposable { const button = container.appendChild($('.chat-view-changes-icon')); button.classList.add(...ThemeIcon.asClassNameArray(Codicon.diffMultiple)); + button.title = localize('chatMultiDiff.openAllChanges', 'Open Changes'); return dom.addDisposableListener(button, 'click', (e) => { const source = URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); From 198cd6e06a70050d76e73a7d8bce8ec81aa5a366 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 2 Oct 2025 16:52:44 -0400 Subject: [PATCH 0750/4355] use modal to align with other voice action (#269598) --- .../voice/browser/terminalVoiceActions.ts | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index b4cf1b4fac7..a8227af1871 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from '../../../../../base/common/actions.js'; import { localize, localize2 } from '../../../../../nls.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; @@ -30,7 +29,7 @@ export function registerTerminalVoiceActions() { run: async (activeInstance, c, accessor) => { const contextKeyService = accessor.get(IContextKeyService); const commandService = accessor.get(ICommandService); - const notificationService = accessor.get(INotificationService); + const dialogService = accessor.get(IDialogService); const workbenchExtensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService); const extensionManagementService = accessor.get(IExtensionManagementService); if (HasSpeechProvider.getValue(contextKeyService)) { @@ -41,39 +40,23 @@ export function registerTerminalVoiceActions() { const extensions = await extensionManagementService.getInstalled(); const extension = extensions.find(extension => extension.identifier.id === 'ms-vscode.vscode-speech'); const extensionIsDisabled = extension && !workbenchExtensionEnablementService.isEnabled(extension); - const learnMoreAction = { - label: localize('viewExtension', "View Extension"), - run: () => commandService.executeCommand('workbench.extensions.search', '@id:ms-vscode.vscode-speech'), - id: '', - tooltip: '', - class: undefined, - enabled: true - }; - - const actions: IAction[] = []; + let run: () => Promise; let message: string; + let primaryButton: string; if (extensionIsDisabled) { - message = localize('terminal.voice.enableSpeechExtension', "You must enable the Speech extension to use Dictation in the Terminal."); - actions.push({ - id: 'enableSpeechExtension', - tooltip: '', - class: undefined, - enabled: true, - label: localize('enableSpeechExtension', "Enable for Workspace"), - run: () => workbenchExtensionEnablementService.setEnablement([extension], EnablementState.EnabledWorkspace), - }, learnMoreAction); + message = localize('terminal.voice.enableSpeechExtension', "Would you like to enable the speech extension?"); + primaryButton = localize('enableExtension', "Enable Extension"); + run = () => workbenchExtensionEnablementService.setEnablement([extension], EnablementState.EnabledWorkspace); } else { - message = localize('terminal.voice.installSpeechExtension', "You must install the Speech extension to use Dictation in the Terminal."); - actions.push({ - id: 'installSpeechExtension', - label: localize('installSpeechExtension', "Install Speech Extension"), - run: () => commandService.executeCommand('workbench.extensions.installExtension', 'ms-vscode.vscode-speech'), - tooltip: '', - class: undefined, - enabled: true - }, learnMoreAction); + message = localize('terminal.voice.installSpeechExtension', "Would you like to install 'VS Code Speech' extension from 'Microsoft'?"); + run = () => commandService.executeCommand('workbench.extensions.installExtension', 'ms-vscode.vscode-speech'); + primaryButton = localize('installExtension', "Install Extension"); + } + const detail = localize('terminal.voice.detail', "Microphone support requires this extension."); + const confirmed = await dialogService.confirm({ message, primaryButton, type: 'info', detail }); + if (confirmed.confirmed) { + await run(); } - notificationService.notify({ severity: Severity.Info, message, actions: { primary: actions } }); }, menu: [ { From 8f406496b1ec1672f61a084f76ed7bd855004d11 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 2 Oct 2025 13:53:59 -0700 Subject: [PATCH 0751/4355] Fix newline formatting in PR #265441 changes --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 ++- .../chat/common/promptSyntax/computeAutomaticInstructions.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 92ba0146183..1a43c04d035 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1529,7 +1529,8 @@ export class ChatWidget extends Disposable implements IChatWidget { } // Set loading guard to prevent infinite loop - this._isLoadingPromptDescriptions = true; try { + this._isLoadingPromptDescriptions = true; + try { // Get all available prompt files with their metadata const promptCommands = await this.promptsService.findPromptSlashCommands(); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index d66170986b5..1efe6567c4b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -123,7 +123,8 @@ export class ComputeAutomaticInstructions { // If both settings are disabled, return true to hide the hint (since the features aren't enabled) if (!useCopilotInstructionsFiles && !useAgentMd) { return true; - } const { folders } = this._workspaceService.getWorkspace(); + } + const { folders } = this._workspaceService.getWorkspace(); // Check for copilot-instructions.md files if (useCopilotInstructionsFiles) { From 9fd0d97ee4b24f2abc69094f020d07cfde907c6b Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 2 Oct 2025 16:57:06 -0400 Subject: [PATCH 0752/4355] Better disposable leak fix (#269613) --- .../api/browser/mainThreadLanguageModels.ts | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index dd4fa4ad05a..be59789e94d 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -55,48 +55,53 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { } $registerLanguageModelProvider(vendor: string): void { - const disposables = this._store.add(new DisposableStore()); - disposables.add(this._chatProviderService.registerLanguageModelProvider(vendor, { - onDidChange: Event.filter(this._lmProviderChange.event, e => e.vendor === vendor, disposables) as unknown as Event, - provideLanguageModelChatInfo: async (options, token) => { - const modelsAndIdentifiers = await this._proxy.$provideLanguageModelChatInfo(vendor, options, token); - modelsAndIdentifiers.forEach(m => { - if (m.metadata.auth) { - disposables.add(this._registerAuthenticationProvider(m.metadata.extension, m.metadata.auth)); + const disposables = new DisposableStore(); + try { + disposables.add(this._chatProviderService.registerLanguageModelProvider(vendor, { + onDidChange: Event.filter(this._lmProviderChange.event, e => e.vendor === vendor, disposables) as unknown as Event, + provideLanguageModelChatInfo: async (options, token) => { + const modelsAndIdentifiers = await this._proxy.$provideLanguageModelChatInfo(vendor, options, token); + modelsAndIdentifiers.forEach(m => { + if (m.metadata.auth) { + disposables.add(this._registerAuthenticationProvider(m.metadata.extension, m.metadata.auth)); + } + }); + return modelsAndIdentifiers; + }, + sendChatRequest: async (modelId, messages, from, options, token) => { + const requestId = (Math.random() * 1e6) | 0; + const defer = new DeferredPromise(); + const stream = new AsyncIterableSource(); + + try { + this._pendingProgress.set(requestId, { defer, stream }); + await Promise.all( + messages.flatMap(msg => msg.content) + .filter(part => part.type === 'image_url') + .map(async part => { + part.value.data = VSBuffer.wrap(await resizeImage(part.value.data.buffer)); + }) + ); + await this._proxy.$startChatRequest(modelId, requestId, from, new SerializableObjectWithBuffers(messages), options, token); + } catch (err) { + this._pendingProgress.delete(requestId); + throw err; } - }); - return modelsAndIdentifiers; - }, - sendChatRequest: async (modelId, messages, from, options, token) => { - const requestId = (Math.random() * 1e6) | 0; - const defer = new DeferredPromise(); - const stream = new AsyncIterableSource(); - - try { - this._pendingProgress.set(requestId, { defer, stream }); - await Promise.all( - messages.flatMap(msg => msg.content) - .filter(part => part.type === 'image_url') - .map(async part => { - part.value.data = VSBuffer.wrap(await resizeImage(part.value.data.buffer)); - }) - ); - await this._proxy.$startChatRequest(modelId, requestId, from, new SerializableObjectWithBuffers(messages), options, token); - } catch (err) { - this._pendingProgress.delete(requestId); - throw err; - } - return { - result: defer.p, - stream: stream.asyncIterable - } satisfies ILanguageModelChatResponse; - }, - provideTokenCount: (modelId, str, token) => { - return this._proxy.$provideTokenLength(modelId, str, token); - }, - })); - this._providerRegistrations.set(vendor, disposables); + return { + result: defer.p, + stream: stream.asyncIterable + } satisfies ILanguageModelChatResponse; + }, + provideTokenCount: (modelId, str, token) => { + return this._proxy.$provideTokenLength(modelId, str, token); + }, + })); + this._providerRegistrations.set(vendor, disposables); + } catch (err) { + disposables.dispose(); + throw err; + } } $onLMProviderChange(vendor: string): void { From fbcdaefbc517acd542f15f0d07518f184e039050 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 2 Oct 2025 14:38:16 -0700 Subject: [PATCH 0753/4355] Update not_team_members --- .vscode/notebooks/my-endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index b8785cb1cd2..53edd0b40c1 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya" + "value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya -author:bryanchen-d" }, { "kind": 1, From 6033e83e23ae2a006ac05b1ce7c57fe817ec8e7c Mon Sep 17 00:00:00 2001 From: Elijah King Date: Thu, 2 Oct 2025 14:42:55 -0700 Subject: [PATCH 0754/4355] revived new chat button in empty state --- .../contrib/chat/browser/actions/chatClearActions.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index fdcc55a5aa9..8bff7b631b0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -75,10 +75,7 @@ export function registerNewChatActions() { }, { id: MenuId.ViewTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.equals('view', ChatViewId), - ChatContextKeys.inEmptyStateWithHistoryEnabled.negate() - ), + when: ContextKeyExpr.equals('view', ChatViewId), group: 'navigation', order: -1, alt: { From 99170bb651a9a2e5d9363987ae77169cd50a730e Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Thu, 2 Oct 2025 14:47:06 -0700 Subject: [PATCH 0755/4355] Refactor todo list widget configuration to use a structured object and update related references (#269572) * Refactor todo list widget configuration to use a structured object and update related references * Update src/vs/workbench/contrib/chat/browser/chat.contribution.ts Co-authored-by: Harald Kirschner * Update src/vs/workbench/contrib/chat/browser/chat.contribution.ts Co-authored-by: Harald Kirschner * Update src/vs/workbench/contrib/chat/browser/chat.contribution.ts Co-authored-by: Harald Kirschner * Update src/vs/workbench/contrib/chat/browser/chat.contribution.ts Co-authored-by: Harald Kirschner --------- Co-authored-by: Harald Kirschner --- .../contrib/chat/browser/chat.contribution.ts | 29 ++++++++++++------- .../contrib/chat/browser/chatWidget.ts | 5 ++-- .../contrib/chat/common/constants.ts | 3 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c14190c72a9..dbd2125d003 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -640,16 +640,25 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - 'chat.todoListWidget.position': { - type: 'string', - default: 'default', - enum: ['default', 'off', 'chat-input'], - enumDescriptions: [ - nls.localize('chat.todoListWidget.position.default', "Show todo list widget in the default position at the top of the chat panel."), - nls.localize('chat.todoListWidget.position.off', "Hide the todo list widget."), - nls.localize('chat.todoListWidget.position.chatInput', "Show todo list widget near the chat input.") - ], - description: nls.localize('chat.todoListWidget.position', "Controls the position of the todo list widget in chat."), + [ChatConfiguration.TodoList]: { + type: 'object', + description: nls.localize('chat.agent.todoList', "Configures the todo list widget in chat."), + properties: { + position: { + type: 'string', + default: 'default', + enum: ['default', 'off', 'chat-input'], + enumDescriptions: [ + nls.localize('chat.agent.todoList.position.default', "Show todo list in the top of the chat panel."), + nls.localize('chat.agent.todoList.position.off', "Hide the todo list (still shows the tool calls for tracking todo progress)."), + nls.localize('chat.agent.todoList.position.chatInput', "Show todo list above the chat input.") + ], + description: nls.localize('chat.agent.todoList.position', "Controls the position of the todo list in the chat view, which opens when the agent has created todo items and updates as it makes progress.") + } + }, + default: { + position: 'default' + }, tags: ['experimental'], experiment: { mode: 'auto' diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 1a43c04d035..b8c5965d875 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -72,7 +72,7 @@ import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariable import { ChatViewModel, IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { IChatInputState } from '../common/chatWidgetHistoryService.js'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind, TodoListWidgetPositionSettingId } from '../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/languageModelToolsService.js'; import { ComputeAutomaticInstructions } from '../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; @@ -1248,7 +1248,8 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - const todoListWidgetPosition = this.configurationService.getValue(TodoListWidgetPositionSettingId) || 'default'; + const todoListConfig = this.configurationService.getValue<{ position?: string }>(ChatConfiguration.TodoList); + const todoListWidgetPosition = todoListConfig?.position || 'default'; // Handle 'off' - hide the widget and return if (todoListWidgetPosition === 'off') { diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 7c24db3536c..e9ba6acd661 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -16,6 +16,7 @@ export enum ChatConfiguration { CheckpointsEnabled = 'chat.checkpoints.enabled', AgentSessionsViewLocation = 'chat.agentSessionsViewLocation', ThinkingStyle = 'chat.agent.thinkingStyle', + TodoList = 'chat.agent.todoList', UseCloudButtonV2 = 'chat.useCloudButtonV2', ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', EmptyStateHistoryEnabled = 'chat.emptyState.history.enabled', @@ -85,6 +86,4 @@ export namespace ChatAgentLocation { export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); -export const TodoListWidgetPositionSettingId = 'chat.todoListWidget.position'; - export const VIEWLET_ID = 'workbench.view.chat.sessions'; From 03a19616c95ec840b6601a2927ee2e47b6a8cf78 Mon Sep 17 00:00:00 2001 From: vijay upadya Date: Thu, 2 Oct 2025 15:15:29 -0700 Subject: [PATCH 0756/4355] Add user prompt keybinding to a11y help dialog --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index fc617dfcb82..c27996bc4ec 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -76,6 +76,8 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'age content.push(localize('chat.progressVerbosity', 'As the chat request is being processed, you will hear verbose progress updates if the request takes more than 4 seconds. This includes information like searched text for with X results, created file , or read file . This can be disabled with accessibility.verboseChatProgressUpdates.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(localize('workbench.action.chat.nextCodeBlock', 'To focus the next code block within a response, invoke the Chat: Next Code Block command{0}.', '')); + content.push(localize('workbench.action.chat.nextUserPrompt', 'To navigate to the next user prompt in the conversation, invoke the Next User Prompt command{0}.', '')); + content.push(localize('workbench.action.chat.previousUserPrompt', 'To navigate to the previous user prompt in the conversation, invoke the Previous User Prompt command{0}.', '')); content.push(localize('workbench.action.chat.announceConfirmation', 'To focus pending chat confirmation dialogs, invoke the Focus Chat Confirmation Status command{0}.', '')); if (type === 'panelChat') { content.push(localize('workbench.action.chat.newChat', 'To create a new chat session, invoke the New Chat command{0}.', '')); From 14ffffb14d6fc39e80843eb473dcb058a1af7c93 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:31:13 -0700 Subject: [PATCH 0757/4355] Run test-documentation --- build/lib/stylelint/vscode-known-variables.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index ad3a5d26e92..15d05bad9ed 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -54,13 +54,6 @@ "--vscode-chat-avatarForeground", "--vscode-chat-checkpointSeparator", "--vscode-chat-editedFileForeground", - "--vscode-chat-font-family", - "--vscode-chat-font-size-body-l", - "--vscode-chat-font-size-body-m", - "--vscode-chat-font-size-body-s", - "--vscode-chat-font-size-body-xl", - "--vscode-chat-font-size-body-xs", - "--vscode-chat-font-size-body-xxl", "--vscode-chat-linesAddedForeground", "--vscode-chat-linesRemovedForeground", "--vscode-chat-requestBackground", @@ -980,4 +973,4 @@ "--animation-opacity", "--chat-setup-dialog-glow-angle" ] -} +} \ No newline at end of file From 8e503e5c0d941c20103b0e73120d4d6b03f5722e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 2 Oct 2025 15:36:09 -0700 Subject: [PATCH 0758/4355] mcp: fix user interaction notification for variable resolution (#269624) The UserInteractionRequiredError should instead be thrown to be handled by the autostart logic. --- src/vs/workbench/contrib/mcp/common/mcpRegistry.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts index 0215d99c693..6d95d0f068a 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts @@ -508,6 +508,10 @@ export class McpRegistry extends Disposable implements IMcpRegistry { launch = await this._instantiationService.invokeFunction(accessor => accessor.get(IMcpDevModeDebugging).transform(definition, launch!)); } } catch (e) { + if (e instanceof UserInteractionRequiredError) { + throw e; + } + this._notificationService.notify({ severity: Severity.Error, message: localize('mcp.launchError', 'Error starting {0}: {1}', definition.label, String(e)), From d2f2a81ce9f276a7f9c057f0324cae71b1483f91 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:42:39 -0700 Subject: [PATCH 0759/4355] Update license Picking up monorepo change --- cglicenses.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cglicenses.json b/cglicenses.json index dc4c75e88a4..93e5297c9a8 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -709,5 +709,20 @@ "SOFTWARE.", "" ] + }, + { + // Reason: mono-repo + "name": "@jridgewell/gen-mapping", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/gen-mapping/LICENSE" + }, + { + // Reason: mono-repo + "name": "@jridgewell/sourcemap-codec", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/sourcemap-codec/LICENSE" + }, + { + // Reason: mono-repo + "name": "@jridgewell/trace-mapping", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/trace-mapping/LICENSE" } ] From 852689f33d35ed5a8a2dae2de801bbfe81b05a48 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:56:48 -0700 Subject: [PATCH 0760/4355] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4572a94e6ab..409bb95f7d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-oss-dev", - "version": "1.105.0", + "version": "1.106.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.105.0", + "version": "1.106.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d31f955301e..1cdb8bf3da3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.105.0", + "version": "1.106.0", "distro": "7eefb0edc90d0589e8f481c8ed3cfe00bd65c738", "author": { "name": "Microsoft Corporation" From 8105c359e00016ad9fea59024c807533b2b3628c Mon Sep 17 00:00:00 2001 From: Nik Date: Thu, 2 Oct 2025 18:57:36 -0500 Subject: [PATCH 0761/4355] Add test for jQuery expressions in KaTeX matching --- .../contrib/markdown/common/markedKatexExtension.ts | 2 +- .../markdown/test/browser/markdownKatexSupport.test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markdown/common/markedKatexExtension.ts b/src/vs/workbench/contrib/markdown/common/markedKatexExtension.ts index 25686fdcc6f..8e05e828509 100644 --- a/src/vs/workbench/contrib/markdown/common/markedKatexExtension.ts +++ b/src/vs/workbench/contrib/markdown/common/markedKatexExtension.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type * as marked from '../../../../base/common/marked/marked.js'; -export const mathInlineRegExp = /(?\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\k(?![a-zA-Z0-9])/; // Non-standard, but ensure opening $ is not preceded and closing $ is not followed by word/number characters +export const mathInlineRegExp = /(?\${1,2})(?!\.)(?!\()(?!["'#])((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\k(?![a-zA-Z0-9])/; // Non-standard, but ensure opening $ is not preceded and closing $ is not followed by word/number characters, opening $ not followed by ., (, ", ', or # const inlineRule = new RegExp('^' + mathInlineRegExp.source); diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownKatexSupport.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownKatexSupport.test.ts index f970b34b58f..ca9d6434c4b 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownKatexSupport.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownKatexSupport.test.ts @@ -73,5 +73,11 @@ suite('Markdown Katex Support Test', () => { assert.ok(rendered.element.innerHTML.includes('katex')); await assertSnapshot(rendered.element.innerHTML); }); + + test('Should not render math when dollar signs appear in jQuery expressions', async () => { + const rendered = await renderMarkdownWithKatex('$.getJSON, $.ajax, $.get and $("#dialogDetalleZona").dialog(...) / $("#dialogDetallePDC").dialog(...)'); + assert.ok(!rendered.element.innerHTML.includes('katex')); + await assertSnapshot(rendered.element.innerHTML); + }); }); From 4c208e04e18456f0dead884525d6ae04384aaf15 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:55:26 +0900 Subject: [PATCH 0762/4355] Use asterisk over underscore for italics Fixes #269247 --- .../browser/tools/runInTerminalTool.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 4df1e9a303d..ba40c47162d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -859,23 +859,23 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const isGlobalAutoApproved = config?.value ?? config.defaultValue; if (isGlobalAutoApproved) { const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); - return new MarkdownString(`_${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}_`, mdTrustSettings); + return new MarkdownString(`*${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}*`, mdTrustSettings); } if (isAutoApproved) { switch (autoApproveReason) { case 'commandLine': { if (commandLineResult.rule) { - return new MarkdownString(`_${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult))}_`, mdTrustSettings); + return new MarkdownString(`*${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult))}*`, mdTrustSettings); } break; } case 'subCommand': { const uniqueRules = dedupeRules(subCommandResults); if (uniqueRules.length === 1) { - return new MarkdownString(`_${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules))}_`, mdTrustSettings); + return new MarkdownString(`*${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules))}*`, mdTrustSettings); } else if (uniqueRules.length > 1) { - return new MarkdownString(`_${localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules))}_`, mdTrustSettings); + return new MarkdownString(`*${localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules))}*`, mdTrustSettings); } break; } @@ -884,16 +884,16 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { switch (autoApproveReason) { case 'commandLine': { if (commandLineResult.rule) { - return new MarkdownString(`_${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult))}_`, mdTrustSettings); + return new MarkdownString(`*${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult))}*`, mdTrustSettings); } break; } case 'subCommand': { const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); if (uniqueRules.length === 1) { - return new MarkdownString(`_${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))}_`); + return new MarkdownString(`*${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))}*`); } else if (uniqueRules.length > 1) { - return new MarkdownString(`_${localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))}_`); + return new MarkdownString(`*${localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))}*`); } break; } From c92b2f96681e001ab66a74ca259d17dbda142804 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:16:22 +0900 Subject: [PATCH 0763/4355] Remove .monaco-workbench from hover used in monaco Fixes microsoft/monaco-editor#4993 --- .../browser/services/hoverService/hover.css | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 6b3b5c53262..f36aa99ade9 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .workbench-hover { +.monaco-hover.workbench-hover { position: relative; font-size: 13px; line-height: 19px; @@ -18,45 +18,45 @@ box-shadow: 0 2px 8px var(--vscode-widget-shadow); } -.monaco-workbench .workbench-hover .monaco-action-bar .action-item .codicon { +.monaco-hover.workbench-hover .monaco-action-bar .action-item .codicon { /* Given our font-size, adjust action icons accordingly */ width: 13px; height: 13px; } -.monaco-workbench .workbench-hover hr { +.monaco-hover.workbench-hover hr { border-bottom: none; } -.monaco-workbench .workbench-hover.compact { +.workbench-hover.compact { font-size: 12px; } -.monaco-workbench .workbench-hover.compact .monaco-action-bar .action-item .codicon { +.monaco-hover.workbench-hover.compact .monaco-action-bar .action-item .codicon { /* Given our font-size, adjust action icons accordingly */ width: 12px; height: 12px; } -.monaco-workbench .workbench-hover.compact .hover-contents { +.monaco-hover.workbench-hover.compact .hover-contents { padding: 2px 8px; } -.monaco-workbench .workbench-hover-container.locked .workbench-hover { +.workbench-hover-container.locked .monaco-hover.workbench-hover { outline: 1px solid var(--vscode-editorHoverWidget-border); } -.monaco-workbench .workbench-hover-container:focus-within.locked .workbench-hover { +.workbench-hover-container:focus-within.locked .monaco-hover.workbench-hover { outline-color: var(--vscode-focusBorder); } -.monaco-workbench .workbench-hover-pointer { +.workbench-hover-pointer { position: absolute; /* Must be higher than workbench hover z-index */ z-index: 41; pointer-events: none; } -.monaco-workbench .workbench-hover-pointer:after { +.workbench-hover-pointer:after { content: ''; position: absolute; width: 5px; @@ -65,77 +65,77 @@ border-right: 1px solid var(--vscode-editorHoverWidget-border); border-bottom: 1px solid var(--vscode-editorHoverWidget-border); } -.monaco-workbench .workbench-hover-container:not(:focus-within).locked .workbench-hover-pointer:after { +.workbench-hover-container:not(:focus-within).locked .workbench-hover-pointer:after { width: 4px; height: 4px; border-right-width: 2px; border-bottom-width: 2px; } -.monaco-workbench .workbench-hover-container:focus-within .workbench-hover-pointer:after { +.workbench-hover-container:focus-within .workbench-hover-pointer:after { border-right: 1px solid var(--vscode-focusBorder); border-bottom: 1px solid var(--vscode-focusBorder); } -.monaco-workbench .workbench-hover-pointer.left { left: -3px; } -.monaco-workbench .workbench-hover-pointer.right { right: 3px; } -.monaco-workbench .workbench-hover-pointer.top { top: -3px; } -.monaco-workbench .workbench-hover-pointer.bottom { bottom: 3px; } +.workbench-hover-pointer.left { left: -3px; } +.workbench-hover-pointer.right { right: 3px; } +.workbench-hover-pointer.top { top: -3px; } +.workbench-hover-pointer.bottom { bottom: 3px; } -.monaco-workbench .workbench-hover-pointer.left:after { +.workbench-hover-pointer.left:after { transform: rotate(135deg); } -.monaco-workbench .workbench-hover-pointer.right:after { +.workbench-hover-pointer.right:after { transform: rotate(315deg); } -.monaco-workbench .workbench-hover-pointer.top:after { +.workbench-hover-pointer.top:after { transform: rotate(225deg); } -.monaco-workbench .workbench-hover-pointer.bottom:after { +.workbench-hover-pointer.bottom:after { transform: rotate(45deg); } -.monaco-workbench .workbench-hover a { +.monaco-hover.workbench-hover a { color: var(--vscode-textLink-foreground); } -.monaco-workbench .workbench-hover a:focus { +.monaco-hover.workbench-hover a:focus { outline: 1px solid; outline-offset: -1px; text-decoration: underline; outline-color: var(--vscode-focusBorder); } -.monaco-workbench .workbench-hover a.codicon:focus, -.monaco-workbench .workbench-hover a.monaco-button:focus { +.monaco-hover.workbench-hover a.codicon:focus, +.monaco-hover.workbench-hover a.monaco-button:focus { text-decoration: none; } -.monaco-workbench .workbench-hover a:hover, -.monaco-workbench .workbench-hover a:active { +.monaco-hover.workbench-hover a:hover, +.monaco-hover.workbench-hover a:active { color: var(--vscode-textLink-activeForeground); } -.monaco-workbench .workbench-hover code { +.monaco-hover.workbench-hover code { background: var(--vscode-textCodeBlock-background); } -.monaco-workbench .workbench-hover .hover-row .actions { +.monaco-hover.workbench-hover .hover-row .actions { background: var(--vscode-editorHoverWidget-statusBarBackground); } -.monaco-workbench .workbench-hover.right-aligned { +.monaco-hover.workbench-hover.right-aligned { /* The context view service wraps strangely when it's right up against the edge without this */ left: 1px; } -.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions { +.monaco-hover.workbench-hover.right-aligned .hover-row.status-bar .actions { flex-direction: row-reverse; } -.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions .action-container { +.monaco-hover.workbench-hover.right-aligned .hover-row.status-bar .actions .action-container { margin-right: 0; margin-left: 16px; } From 4d2aa9d1f08fea13abac26ac01009959798a55b0 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:59:19 -0700 Subject: [PATCH 0764/4355] Log total error count in eslint failure message --- build/eslint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/eslint.js b/build/eslint.js index e952c546eec..22e976555a5 100644 --- a/build/eslint.js +++ b/build/eslint.js @@ -14,7 +14,7 @@ function eslint() { .pipe( eslint((results) => { if (results.warningCount > 0 || results.errorCount > 0) { - throw new Error('eslint failed with warnings and/or errors'); + throw new Error(`eslint failed with ${results.warningCount + results.errorCount} warnings and/or errors`); } }) ).pipe(es.through(function () { /* noop, important for the stream to end */ })); From 360c9fd13499d945b41a2677a4ed459df8a9a36e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:38:33 -0700 Subject: [PATCH 0765/4355] Add lint rule for `as any` and bulk ignore all existing breaks For #269213 This adds a new eslint rule for `as any` and `({... })`. We'd like to remove almost all of these, however right now the first goal is to prevent them in new code. That's why with this first PR I simply add `eslint-disable` comments for all breaks Trying to get this change in soon after branching off for release to hopefully minimize disruption during debt week work --- .eslint-plugin-local/code-no-any-casts.ts | 36 ++++++++++++++++++ ...e-no-observable-get-in-reactive-context.ts | 3 ++ .../code-no-reader-after-await.ts | 1 + .../code-no-static-self-ref.ts | 1 + .../code-no-test-async-suite.ts | 1 + .../code-no-unexternalized-strings.ts | 2 + .../vscode-dts-provider-naming.ts | 1 + .../vscode-dts-vscode-in-comments.ts | 2 + build/azure-pipelines/common/publish.ts | 1 + build/azure-pipelines/upload-cdn.ts | 1 + build/lib/compilation.ts | 1 + build/lib/extensions.ts | 2 + build/lib/fetch.ts | 1 + build/lib/mangle/index.ts | 1 + build/lib/monaco-api.ts | 2 + build/lib/nls.ts | 3 ++ build/lib/optimize.ts | 1 + build/lib/propertyInitOrderChecker.ts | 3 ++ build/lib/reporter.ts | 3 ++ build/lib/task.ts | 1 + build/lib/treeshaking.ts | 8 ++++ build/lib/tsb/builder.ts | 5 +++ build/lib/tsb/index.ts | 2 + build/lib/util.ts | 2 + build/lib/watch/watch-win32.ts | 1 + build/linux/debian/install-sysroot.ts | 1 + build/win32/explorer-dll-fetcher.ts | 1 + eslint.config.js | 1 + .../client/src/cssClient.ts | 1 + .../server/src/cssServer.ts | 3 ++ extensions/emmet/src/emmetCommon.ts | 2 + extensions/emmet/src/updateImageSize.ts | 2 + extensions/emmet/src/util.ts | 1 + extensions/git-base/src/api/api1.ts | 2 + extensions/git/src/commands.ts | 3 ++ extensions/git/src/emoji.ts | 1 + extensions/git/src/git.ts | 3 ++ extensions/git/src/main.ts | 1 + extensions/git/src/util.ts | 3 ++ .../github-authentication/src/node/crypto.ts | 1 + extensions/grunt/src/main.ts | 1 + extensions/gulp/src/main.ts | 2 + .../client/src/htmlClient.ts | 1 + .../server/src/htmlServer.ts | 3 ++ extensions/ipynb/src/deserializers.ts | 2 + extensions/ipynb/src/helper.ts | 5 +++ extensions/ipynb/src/serializers.ts | 4 ++ .../src/test/notebookModelStoreSync.test.ts | 14 +++++++ extensions/jake/src/main.ts | 2 + .../client/src/jsonClient.ts | 1 + .../server/src/jsonServer.ts | 1 + .../notebook/index.ts | 1 + .../preview-src/index.ts | 3 ++ .../src/languageFeatures/diagnostics.ts | 1 + .../src/markdownEngine.ts | 1 + .../src/common/telemetryReporter.ts | 1 + extensions/notebook-renderers/src/index.ts | 1 + .../src/test/notebookRenderer.test.ts | 2 + extensions/npm/src/tasks.ts | 2 + .../src/test/completions/cd.test.ts | 1 + .../src/test/completions/code.test.ts | 2 + .../test/completions/upstream/echo.test.ts | 1 + .../src/test/completions/upstream/git.test.ts | 1 + .../src/test/completions/upstream/ls.test.ts | 1 + .../test/completions/upstream/mkdir.test.ts | 1 + .../src/test/completions/upstream/rm.test.ts | 1 + .../test/completions/upstream/rmdir.test.ts | 1 + .../test/completions/upstream/touch.test.ts | 1 + .../src/languageFeatures/completions.ts | 1 + .../src/languageFeatures/signatureHelp.ts | 2 + .../src/tsServer/bufferSyncSupport.ts | 1 + .../src/tsServer/server.ts | 2 + .../src/utils/platform.ts | 1 + .../web/src/serverHost.ts | 7 ++++ .../web/src/webServer.ts | 1 + .../web/src/workerSession.ts | 1 + extensions/vscode-api-tests/src/extension.ts | 1 + .../src/singlefolder-tests/chat.test.ts | 1 + .../singlefolder-tests/configuration.test.ts | 2 + .../src/singlefolder-tests/env.test.ts | 6 +++ .../src/singlefolder-tests/proxy.test.ts | 2 + .../src/singlefolder-tests/state.test.ts | 1 + .../src/singlefolder-tests/terminal.test.ts | 1 + .../src/singlefolder-tests/workspace.test.ts | 3 ++ scripts/playground-server.ts | 3 ++ src/main.ts | 2 + src/server-main.ts | 2 + src/vs/base/browser/browser.ts | 2 + src/vs/base/browser/canIUse.ts | 1 + src/vs/base/browser/deviceAccess.ts | 3 ++ src/vs/base/browser/dom.ts | 14 +++++++ src/vs/base/browser/fastDomNode.ts | 1 + src/vs/base/browser/mouseEvent.ts | 4 ++ src/vs/base/browser/trustedTypes.ts | 2 + src/vs/base/browser/ui/grid/grid.ts | 3 ++ src/vs/base/browser/ui/grid/gridview.ts | 1 + src/vs/base/browser/ui/sash/sash.ts | 4 ++ src/vs/base/browser/ui/tree/abstractTree.ts | 5 +++ src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 ++ src/vs/base/browser/ui/tree/indexTreeModel.ts | 1 + src/vs/base/browser/webWorkerFactory.ts | 3 ++ src/vs/base/common/console.ts | 2 + src/vs/base/common/controlFlow.ts | 1 + src/vs/base/common/decorators.ts | 1 + src/vs/base/common/errors.ts | 1 + src/vs/base/common/hotReload.ts | 2 + src/vs/base/common/hotReloadHelpers.ts | 1 + src/vs/base/common/lifecycle.ts | 4 ++ src/vs/base/common/map.ts | 1 + src/vs/base/common/marshalling.ts | 4 ++ src/vs/base/common/network.ts | 1 + src/vs/base/common/objects.ts | 1 + .../observableInternal/changeTracker.ts | 4 ++ .../observableInternal/debugLocation.ts | 1 + .../common/observableInternal/debugName.ts | 1 + .../experimental/reducer.ts | 1 + .../logging/consoleObservableLogger.ts | 1 + .../logging/debugger/debuggerRpc.ts | 1 + .../logging/debugger/rpc.ts | 1 + .../observableInternal/observables/derived.ts | 6 +++ .../observables/derivedImpl.ts | 2 + .../observables/observableFromEvent.ts | 1 + .../reactions/autorunImpl.ts | 1 + src/vs/base/common/observableInternal/set.ts | 1 + .../utils/utilsCancellation.ts | 2 + src/vs/base/common/process.ts | 1 + src/vs/base/common/skipList.ts | 2 + src/vs/base/common/types.ts | 3 ++ src/vs/base/common/uriIpc.ts | 1 + src/vs/base/common/verifier.ts | 1 + src/vs/base/common/worker/webWorker.ts | 8 ++++ src/vs/base/node/ports.ts | 1 + src/vs/base/node/unc.ts | 3 ++ src/vs/base/parts/ipc/common/ipc.ts | 1 + src/vs/base/parts/ipc/node/ipc.net.ts | 2 + .../base/parts/ipc/test/node/ipc.net.test.ts | 4 ++ .../parts/sandbox/electron-browser/globals.ts | 1 + .../parts/sandbox/electron-browser/preload.ts | 1 + src/vs/base/parts/storage/node/storage.ts | 1 + src/vs/base/test/browser/dom.test.ts | 1 + src/vs/base/test/common/buffer.test.ts | 1 + src/vs/base/test/common/decorators.test.ts | 1 + src/vs/base/test/common/filters.test.ts | 1 + src/vs/base/test/common/glob.test.ts | 3 ++ src/vs/base/test/common/json.test.ts | 1 + src/vs/base/test/common/mock.ts | 2 + src/vs/base/test/common/oauth.test.ts | 1 + src/vs/base/test/common/snapshot.ts | 2 + .../base/test/common/timeTravelScheduler.ts | 5 +++ src/vs/base/test/common/troubleshooting.ts | 2 + .../electron-browser/workbench/workbench.ts | 2 + src/vs/code/electron-main/app.ts | 3 ++ .../browser/config/editorConfiguration.ts | 4 ++ .../editor/browser/config/migrateOptions.ts | 1 + .../editContext/native/editContextFactory.ts | 1 + .../native/nativeEditContextUtils.ts | 2 + .../editor/browser/controller/mouseHandler.ts | 1 + .../editor/browser/controller/mouseTarget.ts | 8 ++++ .../browser/controller/pointerHandler.ts | 2 + src/vs/editor/browser/editorDom.ts | 2 + src/vs/editor/browser/gpu/gpuUtils.ts | 1 + src/vs/editor/browser/gpu/viewGpuContext.ts | 1 + .../services/hoverService/hoverService.ts | 1 + .../widget/codeEditor/codeEditorWidget.ts | 1 + .../components/diffEditorEditors.ts | 1 + .../widget/diffEditor/diffEditorOptions.ts | 1 + .../widget/diffEditor/diffEditorWidget.ts | 1 + .../editor/browser/widget/diffEditor/utils.ts | 4 ++ .../multiDiffEditor/diffEditorItemTemplate.ts | 2 + src/vs/editor/common/config/editorOptions.ts | 5 +++ src/vs/editor/common/core/edits/stringEdit.ts | 1 + src/vs/editor/common/languages.ts | 1 + .../bracketPairsTree/length.ts | 17 +++++++++ .../editor/common/services/editorBaseApi.ts | 2 + src/vs/editor/common/textModelEditSource.ts | 2 + .../colorPicker/browser/colorDetector.ts | 1 + .../contextmenu/browser/contextmenu.ts | 1 + .../test/browser/outlineModel.test.ts | 1 + .../test/browser/editorState.test.ts | 1 + .../editor/contrib/find/browser/findModel.ts | 1 + .../find/test/browser/findController.test.ts | 1 + .../browser/structuredLogger.ts | 1 + .../browser/view/ghostText/ghostTextView.ts | 2 + .../components/gutterIndicatorView.ts | 1 + .../inlineEditsViews/debugVisualization.ts | 1 + .../test/browser/computeGhostText.test.ts | 1 + .../test/browser/suggestWidgetModel.test.ts | 1 + .../inlineCompletions/test/browser/utils.ts | 1 + .../browser/unicodeHighlighter.ts | 2 + src/vs/editor/editor.api.ts | 4 ++ .../standalone/browser/standaloneEditor.ts | 37 +++++++++++++++++++ .../standalone/browser/standaloneLanguages.ts | 37 +++++++++++++++++++ .../common/monarch/monarchCompile.ts | 3 ++ .../config/editorConfiguration.test.ts | 2 + src/vs/editor/test/browser/testCodeEditor.ts | 1 + .../test/common/model/textModel.test.ts | 1 + .../common/model/textModelWithTokens.test.ts | 1 + .../test/common/model/tokenStore.test.ts | 9 +++++ .../platform/contextkey/common/contextkey.ts | 5 +++ .../contextkey/test/common/contextkey.test.ts | 2 + .../browser/forwardingTelemetryService.ts | 1 + src/vs/platform/dnd/browser/dnd.ts | 2 + .../environment/test/node/argv.test.ts | 2 + .../common/extensionManagementIpc.ts | 2 + .../common/extensionsProfileScannerService.ts | 2 + .../common/implicitActivationEvents.ts | 1 + .../files/browser/htmlFileSystemProvider.ts | 1 + .../node/diskFileService.integrationTest.ts | 3 ++ .../hover/test/browser/nullHoverService.ts | 1 + .../instantiation/common/instantiation.ts | 5 +++ .../common/instantiationService.ts | 1 + .../test/common/instantiationServiceMock.ts | 2 + src/vs/platform/list/browser/listService.ts | 5 +++ .../test/common/mcpManagementService.test.ts | 1 + .../observable/common/wrapInHotClass.ts | 2 + .../common/wrapInReloadableClass.ts | 3 ++ .../policy/node/nativePolicyService.ts | 1 + src/vs/platform/product/common/product.ts | 2 + .../common/profilingTelemetrySpec.ts | 1 + .../quickinput/browser/tree/quickTree.ts | 1 + .../test/browser/quickinput.test.ts | 1 + .../remote/browser/browserSocketFactory.ts | 1 + .../remote/common/remoteAgentConnection.ts | 1 + .../remoteAuthorityResolverService.test.ts | 1 + .../electron-utility/requestService.ts | 1 + .../test/browser/telemetryService.test.ts | 10 +++++ .../commandDetectionCapability.ts | 1 + .../terminal/common/terminalProfiles.ts | 2 + .../platform/terminal/node/terminalProcess.ts | 1 + .../userDataSync/common/extensionsSync.ts | 2 + .../cdpAccessibilityDomain.test.ts | 1 + src/vs/server/node/extensionHostConnection.ts | 1 + .../node/remoteExtensionHostAgentServer.ts | 3 ++ src/vs/server/node/server.main.ts | 1 + .../api/browser/mainThreadChatAgents2.ts | 1 + .../api/browser/mainThreadExtensionService.ts | 1 + .../api/browser/mainThreadLanguageFeatures.ts | 2 + .../api/browser/mainThreadQuickOpen.ts | 4 ++ .../api/browser/mainThreadTelemetry.ts | 1 + .../api/browser/mainThreadTreeViews.ts | 1 + .../api/browser/viewsExtensionPoint.ts | 1 + .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 2 + .../workbench/api/common/extHostComments.ts | 1 + .../api/common/extHostDebugService.ts | 7 ++++ .../common/extHostDocumentSaveParticipant.ts | 1 + .../api/common/extHostExtensionActivator.ts | 1 + .../api/common/extHostExtensionService.ts | 3 ++ .../api/common/extHostLanguageFeatures.ts | 5 +++ .../api/common/extHostLanguageModelTools.ts | 1 + .../api/common/extHostLanguageModels.ts | 1 + src/vs/workbench/api/common/extHostMcp.ts | 4 ++ src/vs/workbench/api/common/extHostSCM.ts | 1 + src/vs/workbench/api/common/extHostSearch.ts | 1 + .../api/common/extHostTerminalService.ts | 1 + .../workbench/api/common/extHostTimeline.ts | 4 ++ .../api/common/extHostTunnelService.ts | 1 + .../api/common/extHostTypeConverters.ts | 11 ++++++ src/vs/workbench/api/common/extHostTypes.ts | 1 + .../api/node/extHostConsoleForwarder.ts | 2 + src/vs/workbench/api/node/extHostMcpNode.ts | 1 + .../api/node/extensionHostProcess.ts | 2 + src/vs/workbench/api/node/proxyResolver.ts | 7 ++++ .../test/browser/extHostApiCommands.test.ts | 1 + .../extHostAuthentication.integrationTest.ts | 3 ++ .../api/test/browser/extHostCommands.test.ts | 1 + .../test/browser/extHostConfiguration.test.ts | 1 + .../test/browser/extHostDocumentData.test.ts | 6 +++ .../extHostDocumentSaveParticipant.test.ts | 1 + .../browser/extHostLanguageFeatures.test.ts | 1 + .../browser/extHostNotebookKernel.test.ts | 2 + .../api/test/browser/extHostTelemetry.test.ts | 6 +++ .../api/test/browser/extHostTesting.test.ts | 1 + .../test/browser/extHostTextEditor.test.ts | 4 ++ .../api/test/browser/extHostTreeViews.test.ts | 1 + .../api/test/browser/extHostTypes.test.ts | 5 +++ .../api/test/browser/extHostWorkspace.test.ts | 1 + ...ainThreadAuthentication.integrationTest.ts | 2 + .../browser/mainThreadChatSessions.test.ts | 9 +++++ .../mainThreadDocumentsAndEditors.test.ts | 2 + .../test/browser/mainThreadEditors.test.ts | 1 + .../test/browser/mainThreadTreeViews.test.ts | 1 + .../extHostTerminalShellIntegration.test.ts | 1 + .../api/test/common/extensionHostMain.test.ts | 1 + .../api/test/common/testRPCProtocol.ts | 2 + .../api/test/node/extHostSearch.test.ts | 3 ++ .../api/worker/extHostConsoleForwarder.ts | 1 + .../api/worker/extensionHostWorker.ts | 12 ++++++ .../browser/actions/developerActions.ts | 1 + src/vs/workbench/browser/layout.ts | 2 + .../browser/parts/editor/editorStatus.ts | 1 + .../browser/parts/titlebar/titlebarPart.ts | 1 + .../browser/parts/views/viewPaneContainer.ts | 1 + src/vs/workbench/browser/window.ts | 2 + src/vs/workbench/browser/workbench.ts | 1 + .../accessibilitySignals/browser/commands.ts | 1 + .../test/browser/bulkCellEdits.test.ts | 2 + .../chatMcpServersInteractionContentPart.ts | 1 + .../chatMultiDiffContentPart.ts | 1 + .../chatEditing/chatEditingServiceImpl.ts | 2 + .../chat/browser/chatSessions/common.ts | 1 + .../contrib/chat/browser/chatViewPane.ts | 1 + .../contrib/chat/common/chatAgents.ts | 2 + .../contrib/chat/common/chatModel.ts | 3 ++ .../contrib/chat/common/chatModes.ts | 1 + .../contrib/chat/common/chatServiceImpl.ts | 1 + .../chatEditingModifiedNotebookEntry.test.ts | 19 ++++++++++ .../browser/chatEditingSessionStorage.test.ts | 1 + .../test/browser/chatTodoListWidget.test.ts | 2 + .../browser/languageModelToolsService.test.ts | 3 ++ .../test/common/chatRequestParser.test.ts | 23 ++++++++++++ .../chat/test/common/chatService.test.ts | 1 + .../common/tools/manageTodoListTool.test.ts | 1 + .../languageConfigurationExtensionPoint.ts | 1 + .../comments/browser/commentThreadWidget.ts | 2 + .../test/browser/commentsView.test.ts | 1 + .../contrib/debug/browser/debugSession.ts | 1 + .../browser/extensionHostDebugService.ts | 1 + .../contrib/debug/browser/rawDebugSession.ts | 1 + .../contrib/debug/common/debugUtils.ts | 1 + .../contrib/debug/common/debugger.ts | 1 + .../debug/test/browser/breakpoints.test.ts | 2 + .../debug/test/browser/callStack.test.ts | 5 +++ .../browser/debugConfigurationManager.test.ts | 2 + .../debug/test/browser/debugMemory.test.ts | 1 + .../debug/test/browser/mockDebugModel.ts | 1 + .../test/browser/rawDebugSession.test.ts | 1 + .../contrib/debug/test/browser/repl.test.ts | 2 + .../debug/test/common/debugModel.test.ts | 4 ++ .../contrib/debug/test/common/mockDebug.ts | 1 + .../editTelemetry/browser/helpers/utils.ts | 1 + .../browser/telemetry/arcTelemetrySender.ts | 1 + .../browser/extensionsWorkbenchService.ts | 1 + .../extensionsActions.test.ts | 3 ++ .../extensionsWorkbenchService.test.ts | 3 ++ .../files/test/browser/editorAutoSave.test.ts | 1 + .../files/test/browser/explorerView.test.ts | 1 + .../test/browser/inlineChatController.test.ts | 3 ++ .../browser/markdownSettingRenderer.test.ts | 1 + .../contrib/markers/browser/markersTable.ts | 1 + .../markers/test/browser/markersModel.test.ts | 1 + .../contrib/mcp/common/uriTemplate.ts | 2 + .../mcp/test/common/mcpRegistry.test.ts | 7 ++++ .../test/common/mcpResourceFilesystem.test.ts | 1 + .../mcp/test/common/mcpSamplingLog.test.ts | 9 +++++ .../mergeEditor/browser/commands/commands.ts | 1 + .../contrib/mergeEditor/browser/utils.ts | 3 ++ .../browser/controller/coreActions.ts | 1 + .../controller/notebookIndentationActions.ts | 2 + .../services/notebookEditorServiceImpl.ts | 1 + .../browser/view/renderers/webviewPreloads.ts | 2 + .../browser/viewModel/markupCellViewModel.ts | 1 + .../viewParts/notebookEditorStickyScroll.ts | 1 + .../viewParts/notebookHorizontalTracker.ts | 1 + .../notebookKernelQuickPickStrategy.ts | 1 + .../common/model/notebookCellTextModel.ts | 1 + .../common/model/notebookTextModel.ts | 2 + .../NotebookEditorWidgetService.test.ts | 2 + .../browser/contrib/notebookOutline.test.ts | 1 + .../diff/editorHeightCalculator.test.ts | 2 + .../test/browser/diff/notebookDiff.test.ts | 2 + .../browser/notebookCellLayoutManager.test.ts | 4 ++ .../test/browser/notebookEditorModel.test.ts | 2 + .../search/browser/searchActionsFind.ts | 1 + .../contrib/search/browser/searchMessage.ts | 1 + .../browser/searchTreeModel/searchModel.ts | 1 + .../searchTreeModel/searchTreeCommon.ts | 4 ++ .../search/test/browser/searchModel.test.ts | 2 + .../browser/searchEditor.contribution.ts | 1 + .../browser/searchEditorActions.ts | 1 + .../searchEditor/browser/searchEditorInput.ts | 1 + .../test/browser/snippetsService.test.ts | 22 +++++++++++ .../tasks/browser/abstractTaskService.ts | 14 +++++++ .../contrib/tasks/common/problemMatcher.ts | 4 ++ .../contrib/tasks/common/taskConfiguration.ts | 1 + .../workbench/contrib/tasks/common/tasks.ts | 2 + .../test/browser/taskTerminalStatus.test.ts | 3 ++ .../terminal/browser/remoteTerminalBackend.ts | 1 + .../terminal/browser/terminalInstance.ts | 1 + .../browser/terminalProcessManager.ts | 1 + .../terminal/browser/terminalTabsList.ts | 1 + .../terminal/browser/xterm/xtermTerminal.ts | 1 + .../terminalCapabilityStore.test.ts | 15 ++++++++ .../test/browser/terminalInstance.test.ts | 2 + .../browser/terminalProcessManager.test.ts | 2 + .../terminalProfileService.integrationTest.ts | 7 ++++ .../test/browser/terminalService.test.ts | 14 +++++++ .../shellIntegrationAddon.integrationTest.ts | 8 ++++ .../test/browser/xterm/xtermTerminal.test.ts | 2 + .../test/browser/bufferContentTracker.test.ts | 1 + .../test/browser/outputMonitor.test.ts | 2 + .../test/browser/runInTerminalTool.test.ts | 4 ++ .../terminal.developer.contribution.ts | 1 + .../history/test/common/history.test.ts | 2 + .../links/browser/terminalLinkManager.ts | 2 + .../test/browser/terminalLinkHelpers.test.ts | 1 + .../test/browser/terminalLinkManager.test.ts | 1 + .../test/browser/terminalLinkOpeners.test.ts | 3 ++ .../browser/terminalWordLinkDetector.test.ts | 10 +++++ .../browser/terminalStickyScrollOverlay.ts | 1 + .../suggest/browser/terminalSuggestAddon.ts | 3 ++ .../browser/terminalTypeAheadAddon.ts | 1 + .../test/browser/terminalTypeAhead.test.ts | 1 + .../browser/terminal.zoom.contribution.ts | 1 + .../testResultsView/testResultsOutput.ts | 2 + .../contrib/testing/common/observableUtils.ts | 1 + .../contrib/testing/common/testingStates.ts | 1 + .../browser/codeCoverageDecorations.test.ts | 1 + .../nameProjection.test.ts | 1 + .../treeProjection.test.ts | 3 ++ .../testing/test/browser/testObjectTree.ts | 1 + .../testing/test/common/testCoverage.test.ts | 1 + .../test/common/testProfileService.test.ts | 1 + .../test/common/testResultService.test.ts | 1 + .../contrib/testing/test/common/testStubs.ts | 1 + .../themes/browser/themes.contribution.ts | 1 + .../contrib/timeline/browser/timelinePane.ts | 1 + .../test/browser/releaseNotesRenderer.test.ts | 2 + .../browser/gettingStartedService.ts | 1 + .../parts/titlebar/titlebarPart.ts | 2 + .../assignment/common/assignmentFilters.ts | 1 + .../authenticationMcpAccessService.test.ts | 1 + .../common/configurationResolverExpression.ts | 3 ++ .../common/variableResolver.ts | 1 + .../configurationResolverService.test.ts | 1 + .../services/driver/browser/driver.ts | 2 + .../browser/webExtensionsScannerService.ts | 2 + .../extensions/common/extensionsRegistry.ts | 1 + ...extensionManifestPropertiesService.test.ts | 5 +++ .../test/common/rpcProtocol.test.ts | 2 + .../extensions/worker/polyfillNestedWorker.ts | 1 + .../host/browser/browserHostService.ts | 4 ++ .../keybinding/browser/keybindingService.ts | 2 + .../browser/keyboardLayoutService.ts | 2 + .../services/label/test/browser/label.test.ts | 1 + .../test/browser/progressIndicator.test.ts | 1 + .../services/remote/common/tunnelModel.ts | 6 +++ .../services/search/common/search.ts | 2 + .../services/search/node/rawSearchService.ts | 1 + .../textMate/common/TMGrammarFactory.ts | 1 + .../test/node/encoding/encoding.test.ts | 1 + .../themes/browser/fileIconThemeData.ts | 1 + .../themes/browser/productIconThemeData.ts | 1 + .../services/themes/common/colorThemeData.ts | 1 + .../services/views/browser/viewsService.ts | 1 + .../test/browser/viewContainerModel.test.ts | 22 +++++++++++ .../browser/viewDescriptorService.test.ts | 8 ++++ .../common/workspaceIdentityService.ts | 1 + .../test/common/workspaceTrust.test.ts | 2 + src/vs/workbench/test/browser/part.test.ts | 1 + .../browser/parts/editor/editorInput.test.ts | 1 + .../browser/parts/editor/editorPane.test.ts | 1 + .../parts/editor/resourceEditorInput.test.ts | 5 +++ src/vs/workbench/test/browser/window.test.ts | 1 + .../test/browser/workbenchTestServices.ts | 5 +++ .../workbench/test/common/resources.test.ts | 3 ++ .../test/common/workbenchTestServices.ts | 1 + .../electron-browser/workbenchTestServices.ts | 1 + .../workbench/workbench.web.main.internal.ts | 3 ++ src/vs/workbench/workbench.web.main.ts | 4 ++ test/automation/src/code.ts | 1 + test/automation/src/terminal.ts | 1 + test/mcp/src/application.ts | 1 + test/mcp/src/automationTools/problems.ts | 1 + test/mcp/src/playwright.ts | 1 + 465 files changed, 1188 insertions(+) create mode 100644 .eslint-plugin-local/code-no-any-casts.ts diff --git a/.eslint-plugin-local/code-no-any-casts.ts b/.eslint-plugin-local/code-no-any-casts.ts new file mode 100644 index 00000000000..4229469c2de --- /dev/null +++ b/.eslint-plugin-local/code-no-any-casts.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree } from '@typescript-eslint/utils'; + +export = new class NoAnyCasts implements eslint.Rule.RuleModule { + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + // Detect TSTypeAssertion: value + 'TSTypeAssertion': (node: any) => { + const typeAssertion = node as TSESTree.TSTypeAssertion; + if (typeAssertion.typeAnnotation.type === 'TSAnyKeyword') { + context.report({ + node, + message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` + }); + } + }, + + // Detect TSAsExpression: value as any + 'TSAsExpression': (node: any) => { + const asExpression = node as TSESTree.TSAsExpression; + if (asExpression.typeAnnotation.type === 'TSAnyKeyword') { + context.report({ + node, + message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` + }); + } + } + }; + } +}; diff --git a/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts index 699b1994595..adb0e500610 100644 --- a/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts +++ b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts @@ -61,10 +61,12 @@ function checkFunctionForObservableGetCalls( if (node.type === 'CallExpression' && isObservableGetCall(node)) { // Flag .get() calls since we're always in a reactive context here context.report({ + // eslint-disable-next-line local/code-no-any-casts node: node as any as ESTree.Node, message: `Observable '.get()' should not be used in reactive context. Use '.read(${readerName})' instead to properly track dependencies or '.read(undefined)' to be explicit about an untracked read.`, fix: (fixer) => { const memberExpression = node.callee as TSESTree.MemberExpression; + // eslint-disable-next-line local/code-no-any-casts return fixer.replaceText(node as any, `${context.getSourceCode().getText(memberExpression.object as any)}.read(undefined)`); } }); @@ -131,6 +133,7 @@ function isReactiveFunctionWithReader(callee: TSESTree.Node): boolean { function walkChildren(node: TSESTree.Node, cb: (child: TSESTree.Node) => void) { const keys = visitorKeys.KEYS[node.type] || []; for (const key of keys) { + // eslint-disable-next-line local/code-no-any-casts const child = (node as any)[key]; if (Array.isArray(child)) { for (const item of child) { diff --git a/.eslint-plugin-local/code-no-reader-after-await.ts b/.eslint-plugin-local/code-no-reader-after-await.ts index b8c39969c5b..1b7e048ea3f 100644 --- a/.eslint-plugin-local/code-no-reader-after-await.ts +++ b/.eslint-plugin-local/code-no-reader-after-await.ts @@ -59,6 +59,7 @@ function checkFunctionForAwaitBeforeReader( if (awaitPositions.length > 0) { const methodName = getMethodName(node); context.report({ + // eslint-disable-next-line local/code-no-any-casts node: node as any as ESTree.Node, message: `Reader method '${methodName}' should not be called after 'await'. The reader becomes invalid after async operations.` }); diff --git a/.eslint-plugin-local/code-no-static-self-ref.ts b/.eslint-plugin-local/code-no-static-self-ref.ts index f620645565a..80968d7c344 100644 --- a/.eslint-plugin-local/code-no-static-self-ref.ts +++ b/.eslint-plugin-local/code-no-static-self-ref.ts @@ -33,6 +33,7 @@ export = new class implements eslint.Rule.RuleModule { } const name = classDeclaration.id.name; + // eslint-disable-next-line local/code-no-any-casts const valueText = context.sourceCode.getText(propertyDefinition.value); if (valueText.includes(name + '.')) { diff --git a/.eslint-plugin-local/code-no-test-async-suite.ts b/.eslint-plugin-local/code-no-test-async-suite.ts index 7d5fadfad0d..d9bd27b2697 100644 --- a/.eslint-plugin-local/code-no-test-async-suite.ts +++ b/.eslint-plugin-local/code-no-test-async-suite.ts @@ -20,6 +20,7 @@ export = new class NoAsyncSuite implements eslint.Rule.RuleModule { function hasAsyncSuite(node: any) { if (isCallExpression(node) && node.arguments.length >= 2 && isFunctionExpression(node.arguments[1]) && node.arguments[1].async) { return context.report({ + // eslint-disable-next-line local/code-no-any-casts node: node as any, message: 'suite factory function should never be async' }); diff --git a/.eslint-plugin-local/code-no-unexternalized-strings.ts b/.eslint-plugin-local/code-no-unexternalized-strings.ts index abb3980eb54..b787c407c81 100644 --- a/.eslint-plugin-local/code-no-unexternalized-strings.ts +++ b/.eslint-plugin-local/code-no-unexternalized-strings.ts @@ -81,6 +81,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { context.report({ loc: messageNode.loc, messageId: 'badMessage', + // eslint-disable-next-line local/code-no-any-casts data: { message: context.getSourceCode().getText(node) } }); } @@ -128,6 +129,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { // report all invalid duplicates (same key, different message) if (values.length > 1) { for (let i = 1; i < values.length; i++) { + // eslint-disable-next-line local/code-no-any-casts if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); } diff --git a/.eslint-plugin-local/vscode-dts-provider-naming.ts b/.eslint-plugin-local/vscode-dts-provider-naming.ts index db8350dd9bc..d68dfb5ac4f 100644 --- a/.eslint-plugin-local/vscode-dts-provider-naming.ts +++ b/.eslint-plugin-local/vscode-dts-provider-naming.ts @@ -32,6 +32,7 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { return; } + // eslint-disable-next-line local/code-no-any-casts const methodName = ((node).key).name; if (!ApiProviderNaming._providerFunctionNames.test(methodName)) { diff --git a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts index 80d3b7003d7..c9aeb08effb 100644 --- a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts +++ b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts @@ -40,7 +40,9 @@ export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { } // Types for eslint seem incorrect + // eslint-disable-next-line local/code-no-any-casts const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position; + // eslint-disable-next-line local/code-no-any-casts const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position; context.report({ messageId: 'comment', diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 2b1c15007b3..78770786d33 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -521,6 +521,7 @@ class ESRPReleaseService { // Release service uses hex format, not base64url :roll_eyes: x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), // Release service uses a '.' separated string, not an array of strings :roll_eyes: + // eslint-disable-next-line local/code-no-any-casts x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.') as any, }, payload: message, diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 61d7cea523c..2ea6b7934fc 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -112,6 +112,7 @@ async function main(): Promise { const listing = new Vinyl({ path: 'files.txt', contents: Buffer.from(files.join('\n')), + // eslint-disable-next-line local/code-no-any-casts stat: { mode: 0o666 } as any }); diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index aea06e8a242..40ff81b406d 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -150,6 +150,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { (await newContentsByFileName).clear(); this.push(null); + // eslint-disable-next-line local/code-no-any-casts (ts2tsMangler) = undefined; }); } diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 9e7cf9f954a..31b19a90ee2 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -117,6 +117,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, path: filePath, stat: fs.statSync(filePath), base: extensionPath, + // eslint-disable-next-line local/code-no-any-casts contents: fs.createReadStream(filePath) as any })); @@ -215,6 +216,7 @@ function fromLocalNormal(extensionPath: string): Stream { path: filePath, stat: fs.statSync(filePath), base: extensionPath, + // eslint-disable-next-line local/code-no-any-casts contents: fs.createReadStream(filePath) as any })); diff --git a/build/lib/fetch.ts b/build/lib/fetch.ts index f09b53e121c..81c715991c7 100644 --- a/build/lib/fetch.ts +++ b/build/lib/fetch.ts @@ -54,6 +54,7 @@ export async function fetchUrl(url: string, options: IFetchOptions, retries = 10 try { const response = await fetch(url, { ...options.nodeFetchOptions, + // eslint-disable-next-line local/code-no-any-casts signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ }); if (verbose) { diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index 2edc27ff55a..38eafa2f300 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -270,6 +270,7 @@ class ClassData { } function isNameTakenInFile(node: ts.Node, name: string): boolean { + // eslint-disable-next-line local/code-no-any-casts const identifiers = (node.getSourceFile()).identifiers; if (identifiers instanceof Map) { if (identifiers.has(name)) { diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 5dc9a04266c..feee8a93a20 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -218,6 +218,7 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); // Get the formatting edits on the input sources + // eslint-disable-next-line local/code-no-any-casts const edits = (ts).formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); // Apply the edits on the input code @@ -324,6 +325,7 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st function getRuleProvider(options: ts.FormatCodeSettings) { // Share this between multiple formatters using the same options. // This represents the bulk of the space the formatter uses. + // eslint-disable-next-line local/code-no-any-casts return (ts as any).formatting.getFormatContext(options); } diff --git a/build/lib/nls.ts b/build/lib/nls.ts index a21102b0ccc..ecef100e578 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -40,6 +40,7 @@ function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.N } function clone(object: T): T { + // eslint-disable-next-line local/code-no-any-casts const result = {} as any as T; for (const id in object) { result[id] = object[id]; @@ -503,11 +504,13 @@ module _nls { ts, typescript, javascriptFile.contents!.toString(), + // eslint-disable-next-line local/code-no-any-casts (javascriptFile).sourceMap, options ); const result = fileFrom(javascriptFile, javascript); + // eslint-disable-next-line local/code-no-any-casts (result).sourceMap = sourcemap; if (nlsKeys) { diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index d89d0d627f9..1d55ccc47ba 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -251,6 +251,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => svgFilter, svgmin(), svgFilter.restore, + // eslint-disable-next-line local/code-no-any-casts sourcemaps.write('./', { sourceMappingURL, sourceRoot: undefined, diff --git a/build/lib/propertyInitOrderChecker.ts b/build/lib/propertyInitOrderChecker.ts index fc958c475c0..c5254884899 100644 --- a/build/lib/propertyInitOrderChecker.ts +++ b/build/lib/propertyInitOrderChecker.ts @@ -251,8 +251,11 @@ function* findAllReferencesInClass(node: ts.Node): Generator { function findAllReferences(node: ts.Node): readonly SymbolAndEntries[] { const sourceFile = node.getSourceFile(); const position = node.getStart(); + // eslint-disable-next-line local/code-no-any-casts const name: ts.Node = (ts as any).getTouchingPropertyName(sourceFile, position); + // eslint-disable-next-line local/code-no-any-casts const options = { use: (ts as any).FindAllReferences.FindReferencesUse.References }; + // eslint-disable-next-line local/code-no-any-casts return (ts as any).FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; } diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index c21fd841c0d..37e69c95204 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -105,13 +105,16 @@ export function createReporter(id?: string): IReporter { errorLog.onEnd(); if (emitError && errors.length > 0) { + // eslint-disable-next-line local/code-no-any-casts if (!(errors as any).__logged__) { errorLog.log(); } + // eslint-disable-next-line local/code-no-any-casts (errors as any).__logged__ = true; const err = new Error(`Found ${errors.length} errors`); + // eslint-disable-next-line local/code-no-any-casts (err as any).__reporter__ = true; this.emit('error', err); } else { diff --git a/build/lib/task.ts b/build/lib/task.ts index 6af23983178..1afc34d4d90 100644 --- a/build/lib/task.ts +++ b/build/lib/task.ts @@ -24,6 +24,7 @@ export interface CallbackTask extends BaseTask { export type Task = PromiseTask | StreamTask | CallbackTask; function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { + // eslint-disable-next-line local/code-no-any-casts if (typeof (p).then === 'function') { return true; } diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 2f55e3d42a9..f2b40f463ac 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -307,15 +307,19 @@ const enum NodeColor { } function getColor(node: ts.Node): NodeColor { + // eslint-disable-next-line local/code-no-any-casts return (node).$$$color || NodeColor.White; } function setColor(node: ts.Node, color: NodeColor): void { + // eslint-disable-next-line local/code-no-any-casts (node).$$$color = color; } function markNeededSourceFile(node: ts.SourceFile): void { + // eslint-disable-next-line local/code-no-any-casts (node).$$$neededSourceFile = true; } function isNeededSourceFile(node: ts.SourceFile): boolean { + // eslint-disable-next-line local/code-no-any-casts return Boolean((node).$$$neededSourceFile); } function nodeOrParentIsBlack(node: ts.Node): boolean { @@ -684,6 +688,7 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language if (nodeOrParentIsBlack(node)) { continue; } + // eslint-disable-next-line local/code-no-any-casts const symbol: ts.Symbol | undefined = (node).symbol; if (!symbol) { continue; @@ -912,8 +917,11 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec // Use some TypeScript internals to avoid code duplication type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; + // eslint-disable-next-line local/code-no-any-casts const getPropertySymbolsFromContextualType: (node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean) => ReadonlyArray = (ts).getPropertySymbolsFromContextualType; + // eslint-disable-next-line local/code-no-any-casts const getContainingObjectLiteralElement: (node: ts.Node) => ObjectLiteralElementWithName | undefined = (ts).getContainingObjectLiteralElement; + // eslint-disable-next-line local/code-no-any-casts const getNameFromPropertyName: (name: ts.PropertyName) => string | undefined = (ts).getNameFromPropertyName; // Go to the original declaration for cases: diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index cc7a2b244b6..6defdcd1b5d 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -59,6 +59,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str function file(file: Vinyl): void { // support gulp-sourcemaps + // eslint-disable-next-line local/code-no-any-casts if ((file).sourceMap) { emitSourceMapsInStream = false; } @@ -80,6 +81,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } function isExternalModule(sourceFile: ts.SourceFile): boolean { + // eslint-disable-next-line local/code-no-any-casts return (sourceFile).externalModuleIndicator || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); } @@ -221,6 +223,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str [tsSMC, inputSMC].forEach((consumer) => { (consumer).sources.forEach((sourceFile: any) => { + // eslint-disable-next-line local/code-no-any-casts (smg)._sources.add(sourceFile); const sourceContent = consumer.sourceContentFor(sourceFile); if (sourceContent !== null) { @@ -239,6 +242,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } } + // eslint-disable-next-line local/code-no-any-casts (vinyl).sourceMap = sourceMap; } } @@ -586,6 +590,7 @@ class LanguageServiceHost implements ts.LanguageServiceHost { let result = this._snapshots[filename]; if (!result && resolve) { try { + // eslint-disable-next-line local/code-no-any-casts result = new VinylScriptSnapshot(new Vinyl({ path: filename, contents: fs.readFileSync(filename), diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 5399a2ead03..1157ca6be9d 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -131,9 +131,11 @@ export function create( const transpiler = !config.transpileWithEsbuild ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); + // eslint-disable-next-line local/code-no-any-casts result = (() => createTranspileStream(transpiler)); } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); + // eslint-disable-next-line local/code-no-any-casts result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); } diff --git a/build/lib/util.ts b/build/lib/util.ts index 49313429da9..f193f2c865d 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -129,6 +129,7 @@ export function fixWin32DirectoryPermissions(): NodeJS.ReadWriteStream { export function setExecutableBit(pattern?: string | string[]): NodeJS.ReadWriteStream { const setBit = es.mapSync(f => { if (!f.stat) { + // eslint-disable-next-line local/code-no-any-casts f.stat = { isFile() { return true; } } as any; } f.stat!.mode = /* 100755 */ 33261; @@ -353,6 +354,7 @@ export interface FilterStream extends NodeJS.ReadWriteStream { } export function filter(fn: (data: any) => boolean): FilterStream { + // eslint-disable-next-line local/code-no-any-casts const result = es.through(function (data) { if (fn(data)) { this.emit('data', data); diff --git a/build/lib/watch/watch-win32.ts b/build/lib/watch/watch-win32.ts index bbfde6afba9..e8f505c10de 100644 --- a/build/lib/watch/watch-win32.ts +++ b/build/lib/watch/watch-win32.ts @@ -47,6 +47,7 @@ function watch(root: string): Stream { path: changePathFull, base: root }); + // eslint-disable-next-line local/code-no-any-casts (file).event = toChangeType(changeType); result.emit('data', file); } diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 670fb68adcf..de0ba341eec 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -82,6 +82,7 @@ async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000) try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, + // eslint-disable-next-line local/code-no-any-casts signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ }); if (response.ok && (response.status >= 200 && response.status < 300)) { diff --git a/build/win32/explorer-dll-fetcher.ts b/build/win32/explorer-dll-fetcher.ts index 724a35bc568..2c9868db7d2 100644 --- a/build/win32/explorer-dll-fetcher.ts +++ b/build/win32/explorer-dll-fetcher.ts @@ -60,6 +60,7 @@ async function main(outputDir?: string): Promise { throw new Error('Required build env not set'); } + // eslint-disable-next-line local/code-no-any-casts await downloadExplorerDll(outputDir, (product as any).quality, arch); } diff --git a/eslint.config.js b/eslint.config.js index 127bb34621f..d9456a0f903 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -82,6 +82,7 @@ export default tseslint.config( 'local/code-no-nls-in-standalone-editor': 'warn', 'local/code-no-potentially-unsafe-disposables': 'warn', 'local/code-no-dangerous-type-assertions': 'warn', + 'local/code-no-any-casts': 'warn', 'local/code-no-standalone-editor': 'warn', 'local/code-no-unexternalized-strings': 'warn', 'local/code-must-use-super-dispose': 'warn', diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 4e90b3482e4..5f2bbd8dbd1 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -83,6 +83,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } return r; } + // eslint-disable-next-line local/code-no-any-casts const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; const r = next(document, position, context, token); diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 8b365f41b6b..c3ab75519c3 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -68,8 +68,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + // eslint-disable-next-line local/code-no-any-casts const initializationOptions = params.initializationOptions as any || {}; + // eslint-disable-next-line local/code-no-any-casts workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { workspaceFolders = []; @@ -166,6 +168,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { + // eslint-disable-next-line local/code-no-any-casts updateConfiguration(change.settings as any); }); diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index daed62d7c65..2b02ff48023 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -184,7 +184,9 @@ function refreshCompletionProviders(_: vscode.ExtensionContext) { return [ { + // eslint-disable-next-line local/code-no-any-casts insertText: item.insertText as any, + // eslint-disable-next-line local/code-no-any-casts filterText: item.label as any, range } diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index 23838576c50..e3469598eb5 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -273,7 +273,9 @@ function getAttributeQuote(editor: TextEditor, attr: Attribute): string { */ function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssToken | undefined { const offset = editor.document.offsetAt(pos); + // eslint-disable-next-line local/code-no-any-casts for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) { + // eslint-disable-next-line local/code-no-any-casts iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => { if (token.type === 'url' && token.start <= offset && token.end >= offset) { url = token; diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index adbfe963a5b..5e55e1d7c09 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -354,6 +354,7 @@ export function getFlatNode(root: FlatNode | undefined, offset: number, includeN || (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) { return getFlatNodeChildren(child.children) ?? child; } + // eslint-disable-next-line local/code-no-any-casts else if ('close' in child) { // We have an HTML node in this case. // In case this node is an invalid unpaired HTML node, diff --git a/extensions/git-base/src/api/api1.ts b/extensions/git-base/src/api/api1.ts index 005a7930356..049951c62e8 100644 --- a/extensions/git-base/src/api/api1.ts +++ b/extensions/git-base/src/api/api1.ts @@ -14,6 +14,7 @@ export class ApiImpl implements API { constructor(private _model: Model) { } pickRemoteSource(options: PickRemoteSourceOptions): Promise { + // eslint-disable-next-line local/code-no-any-casts return pickRemoteSource(this._model, options as any); } @@ -34,6 +35,7 @@ export function registerAPICommands(extension: GitBaseExtensionImpl): Disposable return; } + // eslint-disable-next-line local/code-no-any-casts return pickRemoteSource(extension.model, opts as any); })); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index eb42b6e9985..40a5045f8ec 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2381,8 +2381,10 @@ export class CommandCenter { let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); // migration + // eslint-disable-next-line local/code-no-any-casts if (promptToSaveFilesBeforeCommit as any === true) { promptToSaveFilesBeforeCommit = 'always'; + // eslint-disable-next-line local/code-no-any-casts } else if (promptToSaveFilesBeforeCommit as any === false) { promptToSaveFilesBeforeCommit = 'never'; } @@ -5368,6 +5370,7 @@ export class CommandCenter { }; // patch this object, so people can call methods directly + // eslint-disable-next-line local/code-no-any-casts (this as any)[key] = result; return result; diff --git a/extensions/git/src/emoji.ts b/extensions/git/src/emoji.ts index bd686b0160d..7b1c459faf1 100644 --- a/extensions/git/src/emoji.ts +++ b/extensions/git/src/emoji.ts @@ -24,6 +24,7 @@ export async function ensureEmojis() { async function loadEmojiMap() { const context = getExtensionContext(); + // eslint-disable-next-line local/code-no-any-casts const uri = (Uri as any).joinPath(context.extensionUri, 'resources', 'emojis.json'); emojiMap = JSON.parse(new TextDecoder('utf8').decode(await workspace.fs.readFile(uri))); } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index fc623e5924b..f055788f034 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -308,6 +308,7 @@ export class GitError extends Error { }, null, 2); if (this.error) { + // eslint-disable-next-line local/code-no-any-casts result += (this.error).stack; } @@ -2972,7 +2973,9 @@ export class Repository { const result = await this.exec(['rev-list', '--left-right', '--count', `${branch.name}...${branch.upstream.remote}/${branch.upstream.name}`]); const [ahead, behind] = result.stdout.trim().split('\t'); + // eslint-disable-next-line local/code-no-any-casts (branch as any).ahead = Number(ahead) || 0; + // eslint-disable-next-line local/code-no-any-casts (branch as any).behind = Number(behind) || 0; } catch { } } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 228e981f6ce..b15af43bcd2 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -84,6 +84,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const git = new Git({ gitPath: info.path, + // eslint-disable-next-line local/code-no-any-casts userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`, version: info.version, env: environment, diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index d2b5a33123f..3e156557ebf 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -39,6 +39,7 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { export const EmptyDisposable = toDisposable(() => null); export function fireEvent(event: Event): Event { + // eslint-disable-next-line local/code-no-any-casts return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(_ => (listener as any).call(thisArgs), null, disposables); } @@ -110,6 +111,7 @@ export function once(fn: (...args: any[]) => any): (...args: any[]) => any { export function assign(destination: T, ...sources: any[]): T { for (const source of sources) { + // eslint-disable-next-line local/code-no-any-casts Object.keys(source).forEach(key => (destination as any)[key] = source[key]); } @@ -236,6 +238,7 @@ export function readBytes(stream: Readable, bytes: number): Promise { bytesRead += bytesToRead; if (bytesRead === bytes) { + // eslint-disable-next-line local/code-no-any-casts (stream as any).destroy(); // Will trigger the close event eventually } }); diff --git a/extensions/github-authentication/src/node/crypto.ts b/extensions/github-authentication/src/node/crypto.ts index 367246fdbed..fb3b121ec77 100644 --- a/extensions/github-authentication/src/node/crypto.ts +++ b/extensions/github-authentication/src/node/crypto.ts @@ -5,4 +5,5 @@ import { webcrypto } from 'crypto'; +// eslint-disable-next-line local/code-no-any-casts export const crypto = webcrypto as any as Crypto; diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts index fd99ba335c4..f2965cb1222 100644 --- a/extensions/grunt/src/main.ts +++ b/extensions/grunt/src/main.ts @@ -120,6 +120,7 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { + // eslint-disable-next-line local/code-no-any-casts const taskDefinition = _task.definition; const gruntTask = taskDefinition.task; if (gruntTask) { diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index b0b85ca29b9..fa0ce2a520c 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -150,8 +150,10 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { + // eslint-disable-next-line local/code-no-any-casts const gulpTask = (_task.definition).task; if (gulpTask) { + // eslint-disable-next-line local/code-no-any-casts const kind: GulpTaskDefinition = (_task.definition); const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options)); diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 2b0f961899b..9cbbca14292 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -179,6 +179,7 @@ async function startClientWithParticipants(languageParticipants: LanguagePartici } return r; } + // eslint-disable-next-line local/code-no-any-casts const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; const r = next(document, position, context, token); diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 22ab2e18076..66e3d3474fc 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -134,8 +134,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities connection.onInitialize((params: InitializeParams): InitializeResult => { + // eslint-disable-next-line local/code-no-any-casts const initializationOptions = params.initializationOptions as any || {}; + // eslint-disable-next-line local/code-no-any-casts workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { workspaceFolders = []; @@ -540,6 +542,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.languages.onLinkedEditingRange((params, token) => { + // eslint-disable-next-line local/code-no-any-casts return /* todo remove when microsoft/vscode-languageserver-node#700 fixed */ runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index e49931e060c..930092f6feb 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -20,6 +20,7 @@ const jupyterLanguageToMonacoLanguageMapping = new Map([ export function getPreferredLanguage(metadata?: nbformat.INotebookMetadata) { const jupyterLanguage = metadata?.language_info?.name || + // eslint-disable-next-line local/code-no-any-casts (metadata?.kernelspec as any)?.language; // Default to python language only if the Python extension is installed. @@ -290,6 +291,7 @@ export function jupyterCellOutputToCellOutput(output: nbformat.IOutput): Noteboo if (fn) { result = fn(output); } else { + // eslint-disable-next-line local/code-no-any-casts result = translateDisplayDataOutput(output as any); } return result; diff --git a/extensions/ipynb/src/helper.ts b/extensions/ipynb/src/helper.ts index 6d67b7d529f..40dad3b887d 100644 --- a/extensions/ipynb/src/helper.ts +++ b/extensions/ipynb/src/helper.ts @@ -11,13 +11,18 @@ export function deepClone(obj: T): T { } if (obj instanceof RegExp) { // See https://github.com/microsoft/TypeScript/issues/10990 + // eslint-disable-next-line local/code-no-any-casts return obj as any; } const result: any = Array.isArray(obj) ? [] : {}; + // eslint-disable-next-line local/code-no-any-casts Object.keys(obj).forEach((key: string) => { + // eslint-disable-next-line local/code-no-any-casts if ((obj)[key] && typeof (obj)[key] === 'object') { + // eslint-disable-next-line local/code-no-any-casts result[key] = deepClone((obj)[key]); } else { + // eslint-disable-next-line local/code-no-any-casts result[key] = (obj)[key]; } }); diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts index 54a05aba205..a38ae39b6c7 100644 --- a/extensions/ipynb/src/serializers.ts +++ b/extensions/ipynb/src/serializers.ts @@ -37,6 +37,7 @@ export function sortObjectPropertiesRecursively(obj: any): any { } if (obj !== undefined && obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0) { return ( + // eslint-disable-next-line local/code-no-any-casts Object.keys(obj) .sort() .reduce>((sortedObj, prop) => { @@ -57,6 +58,7 @@ export function getCellMetadata(options: { cell: NotebookCell | NotebookCellData ...(cell.metadata ?? {}) } satisfies CellMetadata; if (cell.kind === NotebookCellKindMarkup) { + // eslint-disable-next-line local/code-no-any-casts delete (metadata as any).execution_count; } return metadata; @@ -398,7 +400,9 @@ export function pruneCell(cell: nbformat.ICell): nbformat.ICell { // Remove outputs and execution_count from non code cells if (result.cell_type !== 'code') { + // eslint-disable-next-line local/code-no-any-casts delete (result).outputs; + // eslint-disable-next-line local/code-no-any-casts delete (result).execution_count; } else { // Clean outputs from code cells diff --git a/extensions/ipynb/src/test/notebookModelStoreSync.test.ts b/extensions/ipynb/src/test/notebookModelStoreSync.test.ts index 7174678ad61..c0d921cb886 100644 --- a/extensions/ipynb/src/test/notebookModelStoreSync.test.ts +++ b/extensions/ipynb/src/test/notebookModelStoreSync.test.ts @@ -37,6 +37,7 @@ suite(`Notebook Model Store Sync`, () => { onWillSaveNotebookDocument = new AsyncEmitter(); sinon.stub(NotebookEdit, 'updateCellMetadata').callsFake((index, metadata) => { + // eslint-disable-next-line local/code-no-any-casts const edit = (NotebookEdit.updateCellMetadata as any).wrappedMethod.call(NotebookEdit, index, metadata); cellMetadataUpdates.push(edit); return edit; @@ -75,6 +76,7 @@ suite(`Notebook Model Store Sync`, () => { test('Adding cell for non Jupyter Notebook will not result in any updates', async () => { sinon.stub(notebook, 'notebookType').get(() => 'some-other-type'); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, @@ -104,6 +106,7 @@ suite(`Notebook Model Store Sync`, () => { test('Adding cell to nbformat 4.2 notebook will result in adding empty metadata', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 2 })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, @@ -135,6 +138,7 @@ suite(`Notebook Model Store Sync`, () => { test('Added cell will have a cell id if nbformat is 4.5', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, @@ -169,6 +173,7 @@ suite(`Notebook Model Store Sync`, () => { test('Do not add cell id if one already exists', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, @@ -205,6 +210,7 @@ suite(`Notebook Model Store Sync`, () => { test('Do not perform any updates if cell id and metadata exists', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, @@ -242,6 +248,7 @@ suite(`Notebook Model Store Sync`, () => { } })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'javascript' } as any, @@ -264,6 +271,7 @@ suite(`Notebook Model Store Sync`, () => { cellChanges: [ { cell, + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'javascript' } as any, @@ -292,6 +300,7 @@ suite(`Notebook Model Store Sync`, () => { } })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'javascript' } as any, @@ -335,6 +344,7 @@ suite(`Notebook Model Store Sync`, () => { } })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'javascript' } as any, @@ -358,6 +368,7 @@ suite(`Notebook Model Store Sync`, () => { cellChanges: [ { cell, + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'javascript' } as any, @@ -386,6 +397,7 @@ suite(`Notebook Model Store Sync`, () => { } })); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'powershell' } as any, @@ -409,6 +421,7 @@ suite(`Notebook Model Store Sync`, () => { cellChanges: [ { cell, + // eslint-disable-next-line local/code-no-any-casts document: { languageId: 'powershell' } as any, @@ -443,6 +456,7 @@ suite(`Notebook Model Store Sync`, () => { }); const cell: NotebookCell = { + // eslint-disable-next-line local/code-no-any-casts document: {} as any, executionSummary: {}, index: 0, diff --git a/extensions/jake/src/main.ts b/extensions/jake/src/main.ts index a2511dc62df..28ed7062ab9 100644 --- a/extensions/jake/src/main.ts +++ b/extensions/jake/src/main.ts @@ -120,8 +120,10 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { + // eslint-disable-next-line local/code-no-any-casts const jakeTask = (_task.definition).task; if (jakeTask) { + // eslint-disable-next-line local/code-no-any-casts const kind: JakeTaskDefinition = (_task.definition); const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, jakeTask, 'jake', new vscode.ShellExecution(await this._jakeCommand, [jakeTask], options)); diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 35fbb3da59d..afa1c201dca 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -772,6 +772,7 @@ function getSchemaId(schema: JSONSchemaSettings, settingsLocation?: Uri): string } function isThenable(obj: ProviderResult): obj is Thenable { + // eslint-disable-next-line local/code-no-any-casts return obj && (obj)['then']; } diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 830ee8c4393..8dc1c588a6d 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -141,6 +141,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + // eslint-disable-next-line local/code-no-any-casts const initializationOptions = params.initializationOptions as any || {}; const handledProtocols = initializationOptions?.handledSchemaProtocols; diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index b7c1471c7dc..1c6e68f4de8 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -380,6 +380,7 @@ function addNamedHeaderRendering(md: InstanceType): void { const originalRender = md.render; md.render = function () { slugCounter.clear(); + // eslint-disable-next-line local/code-no-any-casts return originalRender.apply(this, arguments as any); }; } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 8bb5440ffe1..b20ba77b953 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -23,6 +23,7 @@ let documentResource = settings.settings.source; const vscode = acquireVsCodeApi(); +// eslint-disable-next-line local/code-no-any-casts const originalState = vscode.getState() ?? {} as any; const state = { ...originalState, @@ -249,6 +250,7 @@ window.addEventListener('message', async event => { } newRoot.prepend(...styles); + // eslint-disable-next-line local/code-no-any-casts morphdom(root, newRoot, { childrenOnly: true, onBeforeElUpdated: (fromEl: Element, toEl: Element) => { @@ -434,6 +436,7 @@ function domEval(el: Element): void { for (const key of preservedScriptAttributes) { const val = node.getAttribute?.(key); if (val) { + // eslint-disable-next-line local/code-no-any-casts scriptTag.setAttribute(key, val as any); } } diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 48e579da7fc..793ea7f64ec 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -50,6 +50,7 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { case DiagnosticCode.link_noSuchHeaderInOwnFile: case DiagnosticCode.link_noSuchFile: case DiagnosticCode.link_noSuchHeaderInFile: { + // eslint-disable-next-line local/code-no-any-casts const hrefText = (diagnostic as any).data?.hrefText; if (hrefText) { const fix = new vscode.CodeAction( diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index ea8ce5c90c7..733fcf9e437 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -143,6 +143,7 @@ export class MarkdownItEngine implements IMdParser { const frontMatterPlugin = await import('markdown-it-front-matter'); // Extract rules from front matter plugin and apply at a lower precedence let fontMatterRule: any; + // eslint-disable-next-line local/code-no-any-casts frontMatterPlugin.default({ block: { ruler: { diff --git a/extensions/microsoft-authentication/src/common/telemetryReporter.ts b/extensions/microsoft-authentication/src/common/telemetryReporter.ts index 8fd26a54060..ce1d36b2621 100644 --- a/extensions/microsoft-authentication/src/common/telemetryReporter.ts +++ b/extensions/microsoft-authentication/src/common/telemetryReporter.ts @@ -94,6 +94,7 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio if (typeof error === 'string') { errorMessage = error; } else { + // eslint-disable-next-line local/code-no-any-casts const authError: AuthError = error as any; // don't set error message or stack because it contains PII errorCode = authError.errorCode; diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index dfff75d617e..6c3205fa7b7 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -65,6 +65,7 @@ const domEval = (container: Element) => { for (const key of preservedScriptAttributes) { const val = node[key] || node.getAttribute && node.getAttribute(key); if (val) { + // eslint-disable-next-line local/code-no-any-casts scriptTag.setAttribute(key, val as any); } } diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index 9dc8f6c845e..def7a1282ab 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -127,12 +127,14 @@ suite('Notebook builtin output renderer', () => { return text; }, blob() { + // eslint-disable-next-line local/code-no-any-casts return [] as any; }, json() { return '{ }'; }, data() { + // eslint-disable-next-line local/code-no-any-casts return [] as any; }, metadata: {} diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 19a45488c07..474213f3695 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -58,8 +58,10 @@ export class NpmTaskProvider implements TaskProvider { } public async resolveTask(_task: Task): Promise { + // eslint-disable-next-line local/code-no-any-casts const npmTask = (_task.definition).script; if (npmTask) { + // eslint-disable-next-line local/code-no-any-casts const kind: INpmTaskDefinition = (_task.definition); let packageJsonUri: Uri; if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) { diff --git a/extensions/terminal-suggest/src/test/completions/cd.test.ts b/extensions/terminal-suggest/src/test/completions/cd.test.ts index 82d8c08d8a7..0f4e9f0646d 100644 --- a/extensions/terminal-suggest/src/test/completions/cd.test.ts +++ b/extensions/terminal-suggest/src/test/completions/cd.test.ts @@ -8,6 +8,7 @@ import cdSpec from '../../completions/cd'; import { testPaths, type ISuiteSpec } from '../helpers'; const expectedCompletions = ['-']; +// eslint-disable-next-line local/code-no-any-casts const cdExpectedCompletions = [{ label: 'cd', description: (cdSpec as any).description }]; export const cdTestSuiteSpec: ISuiteSpec = { name: 'cd', diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index d1a97cbad26..b4bc45b33fb 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -77,6 +77,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { + // eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); @@ -265,6 +266,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { + // eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name || executable === codeTunnelCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); diff --git a/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts index 1792d4f90e9..505394bd176 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts @@ -12,6 +12,7 @@ const allOptions = [ '-e', '-n', ]; +// eslint-disable-next-line local/code-no-any-casts const echoExpectedCompletions = [{ label: 'echo', description: (echoSpec as any).description }]; export const echoTestSuiteSpec: ISuiteSpec = { name: 'echo', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts index e8c0450fea0..b8288865d85 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts @@ -11,6 +11,7 @@ import gitSpec from '../../../completions/git'; // const gitCommitArgs = ['--', '--all', '--allow-empty', '--allow-empty-message', '--amend', '--author', '--branch', '--cleanup', '--date', '--dry-run', '--edit', '--file', '--fixup', '--gpg-sign', '--include', '--long', '--message', '--no-edit', '--no-gpg-sign', '--no-post-rewrite', '--no-signoff', '--no-status', '--no-verify', '--null', '--only', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--porcelain', '--quiet', '--reedit-message', '--reset-author', '--reuse-message', '--short', '--signoff', '--squash', '--status', '--template', '--untracked-files', '--verbose', '-C', '-F', '-S', '-a', '-am', '-c', '-e', '-i', '-m', '-n', '-o', '-p', '-q', '-s', '-t', '-u', '-v', '-z']; // const gitMergeArgs = ['-', '--abort', '--allow-unrelated-histories', '--autostash', '--cleanup', '--commit', '--continue', '--edit', '--ff', '--ff-only', '--file', '--gpg-sign', '--log', '--no-autostash', '--no-commit', '--no-edit', '--no-ff', '--no-gpg-sign', '--no-log', '--no-overwrite-ignore', '--no-progress', '--no-rerere-autoupdate', '--no-signoff', '--no-squash', '--no-stat', '--no-summary', '--no-verify', '--no-verify-signatures', '--overwrite-ignore', '--progress', '--quiet', '--quit', '--rerere-autoupdate', '--signoff', '--squash', '--stat', '--strategy', '--strategy-option', '--summary', '--verbose', '--verify-signatures', '-F', '-S', '-X', '-e', '-m', '-n', '-q', '-s']; // const gitAddArgs = ['--', '--all', '--chmod', '--dry-run', '--edit', '--force', '--ignore-errors', '--ignore-missing', '--ignore-removal', '--intent-to-add', '--interactive', '--no-all', '--no-ignore-removal', '--no-warn-embedded-repo', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--refresh', '--renormalize', '--update', '--verbose', '-A', '-N', '-e', '-f', '-i', '-n', '-p', '-u', '-v']; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'git', description: (gitSpec as any).description }]; export const gitTestSuiteSpec: ISuiteSpec = { diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index aab14e3d5c4..f7b59648ff3 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -51,6 +51,7 @@ const allOptions = [ '-w', '-x', ]; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'ls', description: (lsSpec as any).description }]; export const lsTestSuiteSpec: ISuiteSpec = { name: 'ls', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts index 2a5fe836b81..faa8a00c7fd 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts @@ -19,6 +19,7 @@ const allOptions = [ '-p', '-v', ]; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'mkdir', description: (mkdirSpec as any).description }]; export const mkdirTestSuiteSpec: ISuiteSpec = { name: 'mkdir', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts index 564ccf570e1..14ff89df9cf 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts @@ -16,6 +16,7 @@ const allOptions = [ '-r', '-v', ]; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'rm', description: (rmSpec as any).description }]; export const rmTestSuiteSpec: ISuiteSpec = { name: 'rm', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts index e026851d187..1cd90662a77 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts @@ -10,6 +10,7 @@ import rmdirSpec from '../../../completions/upstream/rmdir'; const allOptions = [ '-p', ]; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'rmdir', description: (rmdirSpec as any).description }]; export const rmdirTestSuiteSpec: ISuiteSpec = { diff --git a/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts index dd03b062154..ad8a12f152e 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts @@ -17,6 +17,7 @@ const allOptions = [ '-r ', '-t ', ]; +// eslint-disable-next-line local/code-no-any-casts const expectedCompletions = [{ label: 'touch', description: (touchSpec as any).description }]; export const touchTestSuiteSpec: ISuiteSpec = { diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 749e74b4048..e265e3f6009 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -777,6 +777,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< dotAccessorContext = { range, text }; } } + // eslint-disable-next-line local/code-no-any-casts const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete; const entries = response.body.entries; const metadata = response.metadata; diff --git a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts index 62aeba68b55..91f50cf8b87 100644 --- a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts +++ b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts @@ -105,8 +105,10 @@ function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.Signatur case vscode.SignatureHelpTriggerKind.TriggerCharacter: if (context.triggerCharacter) { if (context.isRetrigger) { + // eslint-disable-next-line local/code-no-any-casts return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any }; } else { + // eslint-disable-next-line local/code-no-any-casts return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any }; } } else { diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 7b47be8d32f..b26380e555c 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -183,6 +183,7 @@ class SyncedBuffer { .filter(x => x.languages.indexOf(this.document.languageId) >= 0); if (tsPluginsForDocument.length) { + // eslint-disable-next-line local/code-no-any-casts (args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name); } diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 3374adc39e5..7b478627e1d 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -283,6 +283,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { } this._requestQueue.enqueue(requestInfo); + // eslint-disable-next-line local/code-no-any-casts if (args && typeof (args as any).$traceId === 'string') { const queueLength = this._requestQueue.length - 1; const pendingResponses = this._pendingResponses.size; @@ -298,6 +299,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { data.pendingCommands = this.getPendingCommands(); } + // eslint-disable-next-line local/code-no-any-casts this._telemetryReporter.logTraceEvent('TSServer.enqueueRequest', (args as any).$traceId, JSON.stringify(data)); } this.sendNextRequests(); diff --git a/extensions/typescript-language-features/src/utils/platform.ts b/extensions/typescript-language-features/src/utils/platform.ts index f97d93e0b57..56d70a9a4c9 100644 --- a/extensions/typescript-language-features/src/utils/platform.ts +++ b/extensions/typescript-language-features/src/utils/platform.ts @@ -10,6 +10,7 @@ export function isWeb(): boolean { } export function isWebAndHasSharedArrayBuffers(): boolean { + // eslint-disable-next-line local/code-no-any-casts return isWeb() && (globalThis as any)['crossOriginIsolated']; } diff --git a/extensions/typescript-language-features/web/src/serverHost.ts b/extensions/typescript-language-features/web/src/serverHost.ts index d746501682a..93386ac9d00 100644 --- a/extensions/typescript-language-features/web/src/serverHost.ts +++ b/extensions/typescript-language-features/web/src/serverHost.ts @@ -29,6 +29,7 @@ function createServerHost( const fs = apiClient?.vscode.workspace.fileSystem; // Internals + // eslint-disable-next-line local/code-no-any-casts const combinePaths: (path: string, ...paths: (string | undefined)[]) => string = (ts as any).combinePaths; const byteOrderMarkIndicator = '\uFEFF'; const matchFiles: ( @@ -41,13 +42,19 @@ function createServerHost( depth: number | undefined, getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] }, realpath: (path: string) => string + // eslint-disable-next-line local/code-no-any-casts ) => string[] = (ts as any).matchFiles; + // eslint-disable-next-line local/code-no-any-casts const generateDjb2Hash = (ts as any).generateDjb2Hash; // Legacy web + // eslint-disable-next-line local/code-no-any-casts const memoize: (callback: () => T) => () => T = (ts as any).memoize; + // eslint-disable-next-line local/code-no-any-casts const ensureTrailingDirectorySeparator: (path: string) => string = (ts as any).ensureTrailingDirectorySeparator; + // eslint-disable-next-line local/code-no-any-casts const getDirectoryPath: (path: string) => string = (ts as any).getDirectoryPath; + // eslint-disable-next-line local/code-no-any-casts const directorySeparator: string = (ts as any).directorySeparator; const executingFilePath = findArgument(args, '--executingFilePath') || location + ''; const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath)))); diff --git a/extensions/typescript-language-features/web/src/webServer.ts b/extensions/typescript-language-features/web/src/webServer.ts index 7bb1de3393f..d425fc3d9f3 100644 --- a/extensions/typescript-language-features/web/src/webServer.ts +++ b/extensions/typescript-language-features/web/src/webServer.ts @@ -13,6 +13,7 @@ import { createSys } from './serverHost'; import { findArgument, findArgumentStringArray, hasArgument, parseServerMode } from './util/args'; import { StartSessionOptions, startWorkerSession } from './workerSession'; +// eslint-disable-next-line local/code-no-any-casts const setSys: (s: ts.System) => void = (ts as any).setSys; async function initializeSession( diff --git a/extensions/typescript-language-features/web/src/workerSession.ts b/extensions/typescript-language-features/web/src/workerSession.ts index 6ae517ccc32..24311783167 100644 --- a/extensions/typescript-language-features/web/src/workerSession.ts +++ b/extensions/typescript-language-features/web/src/workerSession.ts @@ -31,6 +31,7 @@ export function startWorkerSession( pathMapper: PathMapper, logger: Logger, ): void { + // eslint-disable-next-line local/code-no-any-casts const indent: (str: string) => string = (ts as any).server.indent; const worker = new class WorkerSession extends ts.server.Session<{}> { diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/extension.ts index 79223b12361..3886f5fde75 100644 --- a/extensions/vscode-api-tests/src/extension.ts +++ b/extensions/vscode-api-tests/src/extension.ts @@ -7,5 +7,6 @@ import * as vscode from 'vscode'; export function activate(_context: vscode.ExtensionContext) { // Set context as a global as some tests depend on it + // eslint-disable-next-line local/code-no-any-casts (global as any).testExtensionContext = _context; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index d9327193dda..d02f71ba045 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -178,6 +178,7 @@ suite('chat', () => { await commands.executeCommand('workbench.action.chat.newChat'); const result = await commands.executeCommand('workbench.action.chat.open', { query: 'hello', blockOnResponse: true }); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result as any).errorDetails.code, 'rate_limited'); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index 51d4147774e..eceec72ee86 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -30,6 +30,7 @@ suite('vscode API - configuration', () => { assert.strictEqual(config['config0'], true); assert.strictEqual(config['config4'], ''); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (config)['config4'] = 'valuevalue'); assert.ok(config.has('nested.config1')); @@ -44,6 +45,7 @@ suite('vscode API - configuration', () => { assert.ok(config.has('get')); assert.strictEqual(config.get('get'), 'get-prop'); assert.deepStrictEqual(config['get'], config.get); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => config['get'] = 'get-prop'); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index d02e5a44442..19b43fdf066 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -21,11 +21,17 @@ suite('vscode API - env', () => { }); test('env is readonly', function () { + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).language = '234'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).appRoot = '234'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).appName = '234'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).machineId = '234'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).sessionId = '234'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (env as any).shell = '234'); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts index 7845e3113cc..ae3693b7a1a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts @@ -56,6 +56,7 @@ import assert from 'assert'; }); // Using https.globalAgent because it is shared with proxyResolver.ts and mutable. + // eslint-disable-next-line local/code-no-any-casts (https.globalAgent as any).testCertificates = [certPEM]; resetCaches(); @@ -72,6 +73,7 @@ import assert from 'assert'; .on('error', reject); }); } finally { + // eslint-disable-next-line local/code-no-any-casts delete (https.globalAgent as any).testCertificates; resetCaches(); server.close(); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts index cbe58948d86..26d7d0a5485 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts @@ -13,6 +13,7 @@ suite('vscode API - globalState / workspaceState', () => { suiteSetup(async () => { // Trigger extension activation and grab the context as some tests depend on it await extensions.getExtension('vscode.vscode-api-tests')?.activate(); + // eslint-disable-next-line local/code-no-any-casts extensionContext = (global as any).testExtensionContext; }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 9e666879933..bb0c59bd32a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -15,6 +15,7 @@ import { assertNoRpc, poll } from '../utils'; suiteSetup(async () => { // Trigger extension activation and grab the context as some tests depend on it await extensions.getExtension('vscode.vscode-api-tests')?.activate(); + // eslint-disable-next-line local/code-no-any-casts extensionContext = (global as any).testExtensionContext; const config = workspace.getConfiguration('terminal.integrated'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 66e2fba9561..74dcf4a6925 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -41,11 +41,13 @@ suite('vscode API - workspace', () => { test('textDocuments', () => { assert.ok(Array.isArray(vscode.workspace.textDocuments)); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (vscode.workspace).textDocuments = null); }); test('rootPath', () => { assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (vscode.workspace as any).rootPath = 'farboo'); }); @@ -458,6 +460,7 @@ suite('vscode API - workspace', () => { const registration = vscode.workspace.registerTextDocumentContentProvider('foo', { provideTextDocumentContent(_uri) { + // eslint-disable-next-line local/code-no-any-casts return 123; } }); diff --git a/scripts/playground-server.ts b/scripts/playground-server.ts index 474f83def86..e28a20488d9 100644 --- a/scripts/playground-server.ts +++ b/scripts/playground-server.ts @@ -254,6 +254,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s } console.log('Connecting to server to watch for changes...'); + // eslint-disable-next-line local/code-no-any-casts (fetch as any)(fileChangesUrl) .then(async request => { const reader = request.body.getReader(); @@ -332,6 +333,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s const moduleIdStr = trimEnd(relativePath, '.js'); const requireFn: any = globalThis.require; + // eslint-disable-next-line local/code-no-any-casts const moduleManager = (requireFn as any).moduleManager; if (!moduleManager) { console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath); @@ -347,6 +349,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s } // Check if we can reload + // eslint-disable-next-line local/code-no-any-casts const g = globalThis as any; // A frozen copy of the previous exports diff --git a/src/main.ts b/src/main.ts index deba4c449cf..7afc29087ed 100644 --- a/src/main.ts +++ b/src/main.ts @@ -582,6 +582,7 @@ function registerListeners(): void { * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. */ const macOpenFiles: string[] = []; + // eslint-disable-next-line local/code-no-any-casts (globalThis as any)['macOpenFiles'] = macOpenFiles; app.on('open-file', function (event, path) { macOpenFiles.push(path); @@ -602,6 +603,7 @@ function registerListeners(): void { app.on('open-url', onOpenUrl); }); + // eslint-disable-next-line local/code-no-any-casts (globalThis as any)['getOpenUrls'] = function () { app.removeListener('open-url', onOpenUrl); diff --git a/src/server-main.ts b/src/server-main.ts index 9003ea874b6..b0d8f65e2c4 100644 --- a/src/server-main.ts +++ b/src/server-main.ts @@ -20,6 +20,7 @@ import { INLSConfiguration } from './vs/nls.js'; import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer.js'; perf.mark('code/server/start'); +// eslint-disable-next-line local/code-no-any-casts (globalThis as any).vscodeServerStartTime = performance.now(); // Do a quick parse to determine if a server or the cli needs to be started @@ -138,6 +139,7 @@ if (shouldSpawnCli) { console.log(output); perf.mark('code/server/started'); + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).vscodeServerListenTime = performance.now(); await getRemoteExtensionHostAgentServer(); diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index cc3651edc55..13b937daf32 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -131,11 +131,13 @@ export function isStandalone(): boolean { // e.g. visible is true even in fullscreen mode where the controls are hidden // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/visible export function isWCOEnabled(): boolean { + // eslint-disable-next-line local/code-no-any-casts return (navigator as any)?.windowControlsOverlay?.visible; } // Returns the bounding rect of the titlebar area if it is supported and defined // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { + // eslint-disable-next-line local/code-no-any-casts return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); } diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index 895d3eb28a7..39fe06ce9d8 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -33,6 +33,7 @@ export const BrowserFeatures = { return KeyboardSupport.Always; } + // eslint-disable-next-line local/code-no-any-casts if ((navigator).keyboard || browser.isSafari) { return KeyboardSupport.FullScreen; } diff --git a/src/vs/base/browser/deviceAccess.ts b/src/vs/base/browser/deviceAccess.ts index 17cb1beb028..9114a5c3d8d 100644 --- a/src/vs/base/browser/deviceAccess.ts +++ b/src/vs/base/browser/deviceAccess.ts @@ -23,6 +23,7 @@ export interface UsbDeviceData { } export async function requestUsbDevice(options?: { filters?: unknown[] }): Promise { + // eslint-disable-next-line local/code-no-any-casts const usb = (navigator as any).usb; if (!usb) { return undefined; @@ -59,6 +60,7 @@ export interface SerialPortData { } export async function requestSerialPort(options?: { filters?: unknown[] }): Promise { + // eslint-disable-next-line local/code-no-any-casts const serial = (navigator as any).serial; if (!serial) { return undefined; @@ -87,6 +89,7 @@ export interface HidDeviceData { } export async function requestHidDevice(options?: { filters?: unknown[] }): Promise { + // eslint-disable-next-line local/code-no-any-casts const hid = (navigator as any).hid; if (!hid) { return undefined; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4681d190ae7..fb9422fd946 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -427,6 +427,7 @@ const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: Ev class TimeoutThrottledDomListener extends Disposable { + // eslint-disable-next-line local/code-no-any-casts constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger = DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) { super(); @@ -715,6 +716,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { let testElement: HTMLElement | null = domNode; let zoom = 1.0; do { + // eslint-disable-next-line local/code-no-any-casts const elementZoomLevel = (getComputedStyle(testElement) as any).zoom; if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { zoom *= elementZoomLevel; @@ -1320,6 +1322,7 @@ function _$(namespace: Namespace, description: string, attrs? } if (/^on\w+$/.test(name)) { + // eslint-disable-next-line local/code-no-any-casts (result)[name] = value; } else if (name === 'selected') { if (value) { @@ -1514,6 +1517,7 @@ export function windowOpenWithSuccess(url: string, noOpener = true): boolean { if (newTab) { if (noOpener) { // see `windowOpenNoOpener` for details on why this is important + // eslint-disable-next-line local/code-no-any-casts (newTab as any).opener = null; } newTab.location.href = url; @@ -1653,6 +1657,7 @@ export interface IDetectedFullscreen { export function detectFullscreen(targetWindow: Window): IDetectedFullscreen | null { // Browser fullscreen: use DOM APIs to detect + // eslint-disable-next-line local/code-no-any-casts if (targetWindow.document.fullscreenElement || (targetWindow.document).webkitFullscreenElement || (targetWindow.document).webkitIsFullScreen) { return { mode: DetectedFullscreenMode.DOCUMENT, guess: false }; } @@ -2005,6 +2010,7 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia attributes = {}; children = args[0]; } else { + // eslint-disable-next-line local/code-no-any-casts attributes = args[0] as any || {}; children = args[1]; } @@ -2107,6 +2113,7 @@ export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & attributes = {}; children = args[0]; } else { + // eslint-disable-next-line local/code-no-any-casts attributes = args[0] as any || {}; children = args[1]; } @@ -2118,6 +2125,7 @@ export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & } const tagName = match.groups['tag'] || 'div'; + // eslint-disable-next-line local/code-no-any-casts const el = document.createElementNS('http://www.w3.org/2000/svg', tagName) as any as HTMLElement; if (match.groups['id']) { @@ -2280,11 +2288,13 @@ export namespace n { const obsRef = attributes.obsRef; delete attributes.obsRef; + // eslint-disable-next-line local/code-no-any-casts return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); }; } function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { + // eslint-disable-next-line local/code-no-any-casts const f = nodeNs(elementNs) as any; return (attributes, children) => { return f(tag, attributes, children); @@ -2312,6 +2322,7 @@ export namespace n { return value; } }); + // eslint-disable-next-line local/code-no-any-casts return result as any; } } @@ -2423,12 +2434,14 @@ export abstract class ObserverNode { /** @description set.tabIndex */ + // eslint-disable-next-line local/code-no-any-casts this._element.tabIndex = value.read(reader) as any; })); } else { this._element.tabIndex = value; } } else if (key.startsWith('on')) { + // eslint-disable-next-line local/code-no-any-casts (this._element as any)[key] = value; } else { if (isObservable(value)) { @@ -2513,6 +2526,7 @@ function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val } return; } + // eslint-disable-next-line local/code-no-any-casts cb(value as any); } function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 9d58aaba9c3..e3fcfa4dd6a 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -291,6 +291,7 @@ export class FastDomNode { return; } this._contain = contain; + // eslint-disable-next-line local/code-no-any-casts (this.domNode.style).contain = this._contain; } diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index 3cad09d7dcc..2f8c99ff327 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -97,6 +97,7 @@ export class DragMouseEvent extends StandardMouseEvent { constructor(targetWindow: Window, e: MouseEvent) { super(targetWindow, e); + // eslint-disable-next-line local/code-no-any-casts this.dataTransfer = (e).dataTransfer; } } @@ -134,6 +135,7 @@ export class StandardWheelEvent { constructor(e: IMouseWheelEvent | null, deltaX: number = 0, deltaY: number = 0) { this.browserEvent = e || null; + // eslint-disable-next-line local/code-no-any-casts this.target = e ? (e.target || (e).targetNode || e.srcElement) : null; this.deltaY = deltaY; @@ -150,7 +152,9 @@ export class StandardWheelEvent { if (e) { // Old (deprecated) wheel events + // eslint-disable-next-line local/code-no-any-casts const e1 = e; + // eslint-disable-next-line local/code-no-any-casts const e2 = e; const devicePixelRatio = e.view?.devicePixelRatio || 1; diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts index 60e915362cb..9610e7606a9 100644 --- a/src/vs/base/browser/trustedTypes.ts +++ b/src/vs/base/browser/trustedTypes.ts @@ -16,6 +16,7 @@ export function createTrustedTypesPolicy, 'name' | Extract>; } + // eslint-disable-next-line local/code-no-any-casts const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; if (monacoEnvironment?.createTrustedTypesPolicy) { @@ -27,6 +28,7 @@ export function createTrustedTypesPolicy { export type GridNode = GridLeafNode | GridBranchNode; export function isGridBranchNode(node: GridNode): node is GridBranchNode { + // eslint-disable-next-line local/code-no-any-casts return !!(node as any).children; } @@ -869,7 +870,9 @@ function isGridBranchNodeDescriptor(nodeDescriptor: GridNodeDescriptor): n } export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void { + // eslint-disable-next-line local/code-no-any-casts if (!rootNode && (nodeDescriptor as any).groups && (nodeDescriptor as any).groups.length <= 1) { + // eslint-disable-next-line local/code-no-any-casts (nodeDescriptor as any).groups = undefined; } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index bf3d737867f..efa08927c1c 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -193,6 +193,7 @@ export interface GridBranchNode { export type GridNode = GridLeafNode | GridBranchNode; export function isGridBranchNode(node: GridNode): node is GridBranchNode { + // eslint-disable-next-line local/code-no-any-casts return !!(node as any).children; } diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index acd80856a72..cc899c1c4f3 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -488,17 +488,21 @@ export class Sash extends Disposable { let isMultisashResize = false; + // eslint-disable-next-line local/code-no-any-casts if (!(event as any).__orthogonalSashEvent) { const orthogonalSash = this.getOrthogonalSash(event); if (orthogonalSash) { isMultisashResize = true; + // eslint-disable-next-line local/code-no-any-casts (event as any).__orthogonalSashEvent = true; orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); } } + // eslint-disable-next-line local/code-no-any-casts if (this.linkedSash && !(event as any).__linkedSashEvent) { + // eslint-disable-next-line local/code-no-any-casts (event as any).__linkedSashEvent = true; this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 3c398d9215f..a467254905e 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -168,9 +168,11 @@ function asListOptions(modelProvider: () => ITreeModel extends AbstractFindController !FuzzyScore.isDefault(node.filterData as any as FuzzyScore)); } @@ -1206,6 +1209,7 @@ export class FindController extends AbstractFindController { ) { } set(nodes: ITreeNode[], browserEvent?: UIEvent): void { + // eslint-disable-next-line local/code-no-any-casts if (!(browserEvent as any)?.__forceEvent && equals(this.nodes, nodes)) { return; } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 5b29605dce5..66a71e33c7f 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -302,6 +302,7 @@ class AsyncFindController extends FindController, TFilterData>, ) { + // eslint-disable-next-line local/code-no-any-casts super(tree as any, filter, contextViewProvider, options); // Always make sure to end the session before disposing this.disposables.add(toDisposable(async () => { @@ -413,6 +414,7 @@ class AsyncFindController extends FindController(options?: IAsyncDataTreeOpt dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd), multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { + // eslint-disable-next-line local/code-no-any-casts return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); }, isSelectionRangeChangeEvent(e) { + // eslint-disable-next-line local/code-no-any-casts return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); } }, diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index f0a9d56150e..46ceb3fc696 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -86,6 +86,7 @@ interface CollapsedStateUpdate { type CollapseStateUpdate = CollapsibleStateUpdate | CollapsedStateUpdate; function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is CollapsibleStateUpdate { + // eslint-disable-next-line local/code-no-any-casts return typeof (update as any).collapsible === 'boolean'; } diff --git a/src/vs/base/browser/webWorkerFactory.ts b/src/vs/base/browser/webWorkerFactory.ts index d55ef7601cc..723127fd30b 100644 --- a/src/vs/base/browser/webWorkerFactory.ts +++ b/src/vs/base/browser/webWorkerFactory.ts @@ -17,7 +17,9 @@ import { Emitter } from '../common/event.js'; // when available. // Refs https://github.com/microsoft/vscode/issues/222193 let ttPolicy: ReturnType; +// eslint-disable-next-line local/code-no-any-casts if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && (globalThis as any).workerttPolicy !== undefined) { + // eslint-disable-next-line local/code-no-any-casts ttPolicy = (globalThis as any).workerttPolicy; } else { ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); @@ -38,6 +40,7 @@ function getWorker(descriptor: IWebWorkerDescriptor, id: number): Worker | Promi getWorker?(moduleId: string, label: string): Worker | Promise; getWorkerUrl?(moduleId: string, label: string): string; } + // eslint-disable-next-line local/code-no-any-casts const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; if (monacoEnvironment) { if (typeof monacoEnvironment.getWorker === 'function') { diff --git a/src/vs/base/common/console.ts b/src/vs/base/common/console.ts index f4790644524..0a82cbb6a22 100644 --- a/src/vs/base/common/console.ts +++ b/src/vs/base/common/console.ts @@ -131,9 +131,11 @@ export function log(entry: IRemoteConsoleLog, label: string): void { } // Log it + // eslint-disable-next-line local/code-no-any-casts if (typeof (console as any)[entry.severity] !== 'function') { throw new Error('Unknown console method'); } + // eslint-disable-next-line local/code-no-any-casts (console as any)[entry.severity].apply(console, consoleArgs); } diff --git a/src/vs/base/common/controlFlow.ts b/src/vs/base/common/controlFlow.ts index eead0f45a93..35f860a222d 100644 --- a/src/vs/base/common/controlFlow.ts +++ b/src/vs/base/common/controlFlow.ts @@ -54,6 +54,7 @@ export class ReentrancyBarrier { } public makeExclusiveOrSkip(fn: TFunction): TFunction { + // eslint-disable-next-line local/code-no-any-casts return ((...args: any[]) => { if (this._isOccupied) { return; diff --git a/src/vs/base/common/decorators.ts b/src/vs/base/common/decorators.ts index 0f02b3ede2c..7510ffcec1f 100644 --- a/src/vs/base/common/decorators.ts +++ b/src/vs/base/common/decorators.ts @@ -54,6 +54,7 @@ export function memoize(_target: Object, key: string, descriptor: PropertyDescri value: fn.apply(this, args) }); } + // eslint-disable-next-line local/code-no-any-casts return (this as any)[memoizeKey]; }; } diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index dc7f9cbbdfa..69645c4c574 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -139,6 +139,7 @@ export function transformErrorForSerialization(error: any): any; export function transformErrorForSerialization(error: any): any { if (error instanceof Error) { const { name, message, cause } = error; + // eslint-disable-next-line local/code-no-any-casts const stack: string = (error).stacktrace || (error).stack; return { $isError: true, diff --git a/src/vs/base/common/hotReload.ts b/src/vs/base/common/hotReload.ts index 0bd35312a4f..7b983362ea5 100644 --- a/src/vs/base/common/hotReload.ts +++ b/src/vs/base/common/hotReload.ts @@ -94,12 +94,14 @@ if (isHotReloadEnabled()) { if (oldExportedItem) { for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) { const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop)!; + // eslint-disable-next-line local/code-no-any-casts const oldDescriptor = Object.getOwnPropertyDescriptor((oldExportedItem as any).prototype, prop); if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) { console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`); } + // eslint-disable-next-line local/code-no-any-casts Object.defineProperty((oldExportedItem as any).prototype, prop, descriptor); } newExports[key] = oldExportedItem; diff --git a/src/vs/base/common/hotReloadHelpers.ts b/src/vs/base/common/hotReloadHelpers.ts index f4f7cf11eae..0a4a9eb33e4 100644 --- a/src/vs/base/common/hotReloadHelpers.ts +++ b/src/vs/base/common/hotReloadHelpers.ts @@ -36,6 +36,7 @@ export function createHotClass(clazz: T): IObservable { return constObservable(clazz); } + // eslint-disable-next-line local/code-no-any-casts const id = (clazz as any).name; let existing = classes.get(id); diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 8715843777e..4142349bde5 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -233,6 +233,7 @@ if (TRACK_DISPOSABLES) { trackDisposable(x: IDisposable): void { const stack = new Error('Potentially leaked disposable').stack!; setTimeout(() => { + // eslint-disable-next-line local/code-no-any-casts if (!(x as any)[__is_disposable_tracked__]) { console.log(stack); } @@ -242,6 +243,7 @@ if (TRACK_DISPOSABLES) { setParent(child: IDisposable, parent: IDisposable | null): void { if (child && child !== Disposable.None) { try { + // eslint-disable-next-line local/code-no-any-casts (child as any)[__is_disposable_tracked__] = true; } catch { // noop @@ -252,6 +254,7 @@ if (TRACK_DISPOSABLES) { markAsDisposed(disposable: IDisposable): void { if (disposable && disposable !== Disposable.None) { try { + // eslint-disable-next-line local/code-no-any-casts (disposable as any)[__is_disposable_tracked__] = true; } catch { // noop @@ -311,6 +314,7 @@ export interface IDisposable { * Check if `thing` is {@link IDisposable disposable}. */ export function isDisposable(thing: E): thing is E & IDisposable { + // eslint-disable-next-line local/code-no-any-casts return typeof thing === 'object' && thing !== null && typeof (thing).dispose === 'function' && (thing).dispose.length === 0; } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index c8f9de67964..23ce41e3ca6 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -121,6 +121,7 @@ export class ResourceMap implements Map { clb = clb.bind(thisArg); } for (const [_, entry] of this.map) { + // eslint-disable-next-line local/code-no-any-casts clb(entry.value, entry.uri, this); } } diff --git a/src/vs/base/common/marshalling.ts b/src/vs/base/common/marshalling.ts index 1fd5c7fc2dd..678e047c1b7 100644 --- a/src/vs/base/common/marshalling.ts +++ b/src/vs/base/common/marshalling.ts @@ -50,8 +50,11 @@ export function revive(obj: any, depth = 0): Revived { if (typeof obj === 'object') { switch ((obj).$mid) { + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Uri: return URI.revive(obj); + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Regexp: return new RegExp(obj.source, obj.flags); + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Date: return new Date(obj.source); } @@ -59,6 +62,7 @@ export function revive(obj: any, depth = 0): Revived { obj instanceof VSBuffer || obj instanceof Uint8Array ) { + // eslint-disable-next-line local/code-no-any-casts return obj; } diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index ee9564eed86..64ebe94abd9 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -413,6 +413,7 @@ export namespace COI { * isn't enabled the current context */ export function addSearchParam(urlOrSearch: URLSearchParams | Record, coop: boolean, coep: boolean): void { + // eslint-disable-next-line local/code-no-any-casts if (!(globalThis).crossOriginIsolated) { // depends on the current context being COI return; diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index cd976d77377..f25572a931e 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -72,6 +72,7 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): const r2 = {}; for (const i2 in obj) { if (_hasOwnProperty.call(obj, i2)) { + // eslint-disable-next-line local/code-no-any-casts (r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen); } } diff --git a/src/vs/base/common/observableInternal/changeTracker.ts b/src/vs/base/common/observableInternal/changeTracker.ts index b29fb62ce40..11ee5d7edf9 100644 --- a/src/vs/base/common/observableInternal/changeTracker.ts +++ b/src/vs/base/common/observableInternal/changeTracker.ts @@ -31,6 +31,7 @@ export function recordChanges { return { createChangeSummary: (_previousChangeSummary) => { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -38,6 +39,7 @@ export function recordChanges { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -74,6 +77,7 @@ export function recordChangesLazy { * Additionally, a reducer can report how that state changed. */ export function observableReducer(owner: DebugOwner, options: IReducerOptions): SimplifyObservableWithChange { + // eslint-disable-next-line local/code-no-any-casts return observableReducerSettable(owner, options) as any; } diff --git a/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts b/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts index f8ca91b86ff..7450139cc56 100644 --- a/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts +++ b/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts @@ -77,6 +77,7 @@ export class ConsoleObservableLogger implements IObservableLogger { const debugTrackUpdating = false; if (debugTrackUpdating) { const updating: IObservable[] = []; + // eslint-disable-next-line local/code-no-any-casts (derived as any).__debugUpdating = updating; const existingBeginUpdate = derived.beginUpdate; diff --git a/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts b/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts index 3da4cbd4765..332be8e6c4a 100644 --- a/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts +++ b/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts @@ -9,6 +9,7 @@ export function registerDebugChannel( channelId: T['channelId'], createClient: () => T['client'], ): SimpleTypedRpcConnection> { + // eslint-disable-next-line local/code-no-any-casts const g = globalThis as any as GlobalObj; let queuedNotifications: unknown[] = []; diff --git a/src/vs/base/common/observableInternal/logging/debugger/rpc.ts b/src/vs/base/common/observableInternal/logging/debugger/rpc.ts index 6c53100e379..d19da1fe159 100644 --- a/src/vs/base/common/observableInternal/logging/debugger/rpc.ts +++ b/src/vs/base/common/observableInternal/logging/debugger/rpc.ts @@ -91,6 +91,7 @@ export class SimpleTypedRpcConnection { } }); + // eslint-disable-next-line local/code-no-any-casts this.api = { notifications: notifications, requests: requests } as any; } } diff --git a/src/vs/base/common/observableInternal/observables/derived.ts b/src/vs/base/common/observableInternal/observables/derived.ts index f59e75890f9..74b631b2a4d 100644 --- a/src/vs/base/common/observableInternal/observables/derived.ts +++ b/src/vs/base/common/observableInternal/observables/derived.ts @@ -35,7 +35,9 @@ export function derived( ); } return new Derived( + // eslint-disable-next-line local/code-no-any-casts new DebugNameData(undefined, undefined, computeFnOrOwner as any), + // eslint-disable-next-line local/code-no-any-casts computeFnOrOwner as any, undefined, undefined, @@ -119,10 +121,12 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: let computeFn: (reader: IReader, store: DisposableStore) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } @@ -153,10 +157,12 @@ export function derivedDisposable(computeFnOr let computeFn: (reader: IReader) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } diff --git a/src/vs/base/common/observableInternal/observables/derivedImpl.ts b/src/vs/base/common/observableInternal/observables/derivedImpl.ts index ce5f62db91d..e21e374f2d6 100644 --- a/src/vs/base/common/observableInternal/observables/derivedImpl.ts +++ b/src/vs/base/common/observableInternal/observables/derivedImpl.ts @@ -310,6 +310,7 @@ export class Derived extends BaseObserv shouldReact = this._changeTracker ? this._changeTracker.handleChange({ changedObservable: observable, change, + // eslint-disable-next-line local/code-no-any-casts didChange: (o): this is any => o === observable as any, }, this._changeSummary!) : true; } catch (e) { @@ -410,6 +411,7 @@ export class Derived extends BaseObserv } public debugSetValue(newValue: unknown) { + // eslint-disable-next-line local/code-no-any-casts this._value = newValue as any; } diff --git a/src/vs/base/common/observableInternal/observables/observableFromEvent.ts b/src/vs/base/common/observableInternal/observables/observableFromEvent.ts index 7ec53d68536..3be9151158e 100644 --- a/src/vs/base/common/observableInternal/observables/observableFromEvent.ts +++ b/src/vs/base/common/observableInternal/observables/observableFromEvent.ts @@ -148,6 +148,7 @@ export class FromEventObservable extends BaseObservable { } public debugSetValue(value: unknown): void { + // eslint-disable-next-line local/code-no-any-casts this._value = value as any; } diff --git a/src/vs/base/common/observableInternal/reactions/autorunImpl.ts b/src/vs/base/common/observableInternal/reactions/autorunImpl.ts index 240e13f0563..90b265c2339 100644 --- a/src/vs/base/common/observableInternal/reactions/autorunImpl.ts +++ b/src/vs/base/common/observableInternal/reactions/autorunImpl.ts @@ -182,6 +182,7 @@ export class AutorunObserver implements IObserver, IReader const shouldReact = this._changeTracker ? this._changeTracker.handleChange({ changedObservable: observable, change, + // eslint-disable-next-line local/code-no-any-casts didChange: (o): this is any => o === observable as any, }, this._changeSummary!) : true; if (shouldReact) { diff --git a/src/vs/base/common/observableInternal/set.ts b/src/vs/base/common/observableInternal/set.ts index 294cdc73ca4..a966df1bb4d 100644 --- a/src/vs/base/common/observableInternal/set.ts +++ b/src/vs/base/common/observableInternal/set.ts @@ -48,6 +48,7 @@ export class ObservableSet implements Set { forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { this._data.forEach((value, value2, _set) => { + // eslint-disable-next-line local/code-no-any-casts callbackfn.call(thisArg, value, value2, this as any); }); } diff --git a/src/vs/base/common/observableInternal/utils/utilsCancellation.ts b/src/vs/base/common/observableInternal/utils/utilsCancellation.ts index 9f0d0783172..40cc45bed73 100644 --- a/src/vs/base/common/observableInternal/utils/utilsCancellation.ts +++ b/src/vs/base/common/observableInternal/utils/utilsCancellation.ts @@ -75,10 +75,12 @@ export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IRea let computeFn: (reader: IReader, store: CancellationToken) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index af67fda5669..885ecfa3fdc 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -9,6 +9,7 @@ let safeProcess: Omit & { arch: string | undefined }; declare const process: INodeProcess; // Native sandbox environment +// eslint-disable-next-line local/code-no-any-casts const vscodeGlobal = (globalThis as any).vscode; if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { const sandboxProcess: INodeProcess = vscodeGlobal.process; diff --git a/src/vs/base/common/skipList.ts b/src/vs/base/common/skipList.ts index ed3fd7e5a0a..295adb603fe 100644 --- a/src/vs/base/common/skipList.ts +++ b/src/vs/base/common/skipList.ts @@ -35,6 +35,7 @@ export class SkipList implements Map { capacity: number = 2 ** 16 ) { this._maxLevel = Math.max(1, Math.log2(capacity) | 0); + // eslint-disable-next-line local/code-no-any-casts this._header = new Node(this._maxLevel, NIL, NIL); } @@ -43,6 +44,7 @@ export class SkipList implements Map { } clear(): void { + // eslint-disable-next-line local/code-no-any-casts this._header = new Node(this._maxLevel, NIL, NIL); this._size = 0; } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index b08d15edc6c..f57a992d606 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -55,6 +55,7 @@ export function isNumber(obj: unknown): obj is number { * @returns whether the provided parameter is an Iterable, casting to the given generic */ export function isIterable(obj: unknown): obj is Iterable { + // eslint-disable-next-line local/code-no-any-casts return !!obj && typeof (obj as any)[Symbol.iterator] === 'function'; } @@ -62,6 +63,7 @@ export function isIterable(obj: unknown): obj is Iterable { * @returns whether the provided parameter is an Iterable, casting to the given generic */ export function isAsyncIterable(obj: unknown): obj is AsyncIterable { + // eslint-disable-next-line local/code-no-any-casts return !!obj && typeof (obj as any)[Symbol.asyncIterator] === 'function'; } @@ -263,6 +265,7 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un } catch { // ignore } + // eslint-disable-next-line local/code-no-any-casts if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) { return; } diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts index 8a8c18055a8..2022176c1f3 100644 --- a/src/vs/base/common/uriIpc.ts +++ b/src/vs/base/common/uriIpc.ts @@ -30,6 +30,7 @@ export interface IRawURITransformer { } function toJSON(uri: URI): UriComponents { + // eslint-disable-next-line local/code-no-any-casts return uri.toJSON(); } diff --git a/src/vs/base/common/verifier.ts b/src/vs/base/common/verifier.ts index a2576a3c1ca..fda24540ebc 100644 --- a/src/vs/base/common/verifier.ts +++ b/src/vs/base/common/verifier.ts @@ -79,6 +79,7 @@ export function verifyObject(verifiers: { [K in keyof T]: IVer for (const key in verifiers) { if (Object.hasOwnProperty.call(verifiers, key)) { const verifier = verifiers[key]; + // eslint-disable-next-line local/code-no-any-casts result[key] = verifier.verify((value as any)[key]); } } diff --git a/src/vs/base/common/worker/webWorker.ts b/src/vs/base/common/worker/webWorker.ts index 999f490c590..922a173671f 100644 --- a/src/vs/base/common/worker/webWorker.ts +++ b/src/vs/base/common/worker/webWorker.ts @@ -353,11 +353,13 @@ export class WebWorkerClient extends Disposable implements IWe if (!channel) { return Promise.reject(new Error(`Missing channel ${channelName} on main thread`)); } + // eslint-disable-next-line local/code-no-any-casts if (typeof (channel as any)[method] !== 'function') { return Promise.reject(new Error(`Missing method ${method} on main thread channel ${channelName}`)); } try { + // eslint-disable-next-line local/code-no-any-casts return Promise.resolve((channel as any)[method].apply(channel, args)); } catch (e) { return Promise.reject(e); @@ -370,6 +372,7 @@ export class WebWorkerClient extends Disposable implements IWe throw new Error(`Missing channel ${channelName} on main thread`); } if (propertyIsDynamicEvent(eventName)) { + // eslint-disable-next-line local/code-no-any-casts const event = (channel as any)[eventName].call(channel, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); @@ -377,6 +380,7 @@ export class WebWorkerClient extends Disposable implements IWe return event; } if (propertyIsEvent(eventName)) { + // eslint-disable-next-line local/code-no-any-casts const event = (channel as any)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`); @@ -457,11 +461,13 @@ export class WebWorkerServer implement if (!requestHandler) { return Promise.reject(new Error(`Missing channel ${channel} on worker thread`)); } + // eslint-disable-next-line local/code-no-any-casts if (typeof (requestHandler as any)[method] !== 'function') { return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`)); } try { + // eslint-disable-next-line local/code-no-any-casts return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args)); } catch (e) { return Promise.reject(e); @@ -474,6 +480,7 @@ export class WebWorkerServer implement throw new Error(`Missing channel ${channel} on worker thread`); } if (propertyIsDynamicEvent(eventName)) { + // eslint-disable-next-line local/code-no-any-casts const event = (requestHandler as any)[eventName].call(requestHandler, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on request handler.`); @@ -481,6 +488,7 @@ export class WebWorkerServer implement return event; } if (propertyIsEvent(eventName)) { + // eslint-disable-next-line local/code-no-any-casts const event = (requestHandler as any)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on request handler.`); diff --git a/src/vs/base/node/ports.ts b/src/vs/base/node/ports.ts index a237f433ea0..9cf88a47c84 100644 --- a/src/vs/base/node/ports.ts +++ b/src/vs/base/node/ports.ts @@ -179,6 +179,7 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo doResolve(startPort, resolve); }); server.on('error', err => { + // eslint-disable-next-line local/code-no-any-casts if (err && ((err).code === 'EADDRINUSE' || (err).code === 'EACCES') && (countTried < giveUpAfter)) { startPort++; countTried++; diff --git a/src/vs/base/node/unc.ts b/src/vs/base/node/unc.ts index e51fed72d1a..5267af07345 100644 --- a/src/vs/base/node/unc.ts +++ b/src/vs/base/node/unc.ts @@ -17,6 +17,7 @@ function processUNCHostAllowlist(): Set { // The property `process.uncHostAllowlist` is not available in official node.js // releases, only in our own builds, so we have to probe for availability + // eslint-disable-next-line local/code-no-any-casts return (process as any).uncHostAllowlist; } @@ -90,6 +91,7 @@ export function disableUNCAccessRestrictions(): void { return; } + // eslint-disable-next-line local/code-no-any-casts (process as any).restrictUNCAccess = false; } @@ -98,5 +100,6 @@ export function isUNCAccessRestrictionsDisabled(): boolean { return true; } + // eslint-disable-next-line local/code-no-any-casts return (process as any).restrictUNCAccess === false; } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 5e0764d9849..0725802cf9c 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -596,6 +596,7 @@ export class ChannelClient implements IChannelClient, IDisposable { case ResponseType.PromiseError: { this.handlers.delete(id); const error = new Error(response.data.message); + // eslint-disable-next-line local/code-no-any-casts (error).stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; error.name = response.data.name; e(error); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 82da99e5bd6..747f1f697ac 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -727,6 +727,7 @@ class ZlibInflateStream extends Disposable { super(); this._zlibInflate = createInflateRaw(options); this._zlibInflate.on('error', (err) => { + // eslint-disable-next-line local/code-no-any-casts this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); this._onError.fire(err); }); @@ -780,6 +781,7 @@ class ZlibDeflateStream extends Disposable { windowBits: 15 }); this._zlibDeflate.on('error', (err) => { + // eslint-disable-next-line local/code-no-any-casts this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); this._onError.fire(err); }); diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index d13c8a9dce5..a1bc9a5749a 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -84,10 +84,12 @@ class Ether { private _ba: Buffer[]; public get a(): Socket { + // eslint-disable-next-line local/code-no-any-casts return this._a; } public get b(): Socket { + // eslint-disable-next-line local/code-no-any-casts return this._b; } @@ -647,6 +649,7 @@ suite('WebSocketNodeSocket', () => { async function testReading(frames: number[][], permessageDeflate: boolean): Promise { const disposables = new DisposableStore(); const socket = new FakeNodeSocket(); + // eslint-disable-next-line local/code-no-any-casts const webSocket = disposables.add(new WebSocketNodeSocket(socket, permessageDeflate, null, false)); const barrier = new Barrier(); @@ -779,6 +782,7 @@ suite('WebSocketNodeSocket', () => { const disposables = new DisposableStore(); const socket = new FakeNodeSocket(); + // eslint-disable-next-line local/code-no-any-casts const webSocket = disposables.add(new WebSocketNodeSocket(socket, false, null, false)); let receivedData: string = ''; diff --git a/src/vs/base/parts/sandbox/electron-browser/globals.ts b/src/vs/base/parts/sandbox/electron-browser/globals.ts index cd1e5387864..9d831ac8afe 100644 --- a/src/vs/base/parts/sandbox/electron-browser/globals.ts +++ b/src/vs/base/parts/sandbox/electron-browser/globals.ts @@ -115,6 +115,7 @@ export interface ISandboxContext { resolveConfiguration(): Promise; } +// eslint-disable-next-line local/code-no-any-casts const vscodeGlobal = (globalThis as any).vscode; export const ipcRenderer: IpcRenderer = vscodeGlobal.ipcRenderer; export const ipcMessagePort: IpcMessagePort = vscodeGlobal.ipcMessagePort; diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.ts b/src/vs/base/parts/sandbox/electron-browser/preload.ts index 7ff6c24d2c7..c023c5622ed 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.ts +++ b/src/vs/base/parts/sandbox/electron-browser/preload.ts @@ -256,6 +256,7 @@ console.error(error); } } else { + // eslint-disable-next-line local/code-no-any-casts (window as any).vscode = globals; } }()); diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 081d940bbdc..162179cf35b 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -245,6 +245,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { const connection = await this.whenConnected; const row = await this.get(connection, full ? 'PRAGMA integrity_check' : 'PRAGMA quick_check'); + // eslint-disable-next-line local/code-no-any-casts const integrity = full ? (row as any)['integrity_check'] : (row as any)['quick_check']; if (connection.isErroneous) { diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 345efd026e8..6db7750eeab 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -404,6 +404,7 @@ suite('dom', () => { suite('SafeTriangle', () => { const fakeElement = (left: number, right: number, top: number, bottom: number): HTMLElement => { + // eslint-disable-next-line local/code-no-any-casts return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as any; }; diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/common/buffer.test.ts index 6666d901d81..f4d9c5a04e4 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/common/buffer.test.ts @@ -498,6 +498,7 @@ suite('Buffer', () => { // Test invalid input assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts buff.set({} as any); }); }); diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index bac27cd772e..9100176007b 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -127,6 +127,7 @@ suite('Decorators', () => { assert.strictEqual(foo.answer, 42); try { + // eslint-disable-next-line local/code-no-any-casts (foo as any)['$memoize$answer'] = 1337; assert(false); } catch (e) { diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index ce70ad5f9c1..5d6643d2378 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -25,6 +25,7 @@ suite('Filters', () => { let filter: IFilter; let counters: number[]; const newFilter = function (i: number, r: boolean): IFilter { + // eslint-disable-next-line local/code-no-any-casts return function (): IMatch[] { counters[i]++; return r as any; }; }; diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 74fbdf6f65d..3658ec22419 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -494,6 +494,7 @@ suite('Glob', () => { assert.strictEqual(glob.match(expression, 'test.js', hasSibling), null); expression = { + // eslint-disable-next-line local/code-no-any-casts '**/*.js': { } as any }; @@ -514,6 +515,7 @@ suite('Glob', () => { '**/*.js': { when: '$(basename).ts' }, '**/*.as': true, '**/*.foo': false, + // eslint-disable-next-line local/code-no-any-casts '**/*.bananas': { bananas: true } as any }; @@ -758,6 +760,7 @@ suite('Glob', () => { }); test('expression with other falsy value', function () { + // eslint-disable-next-line local/code-no-any-casts const expr = { '**/*.js': 0 } as any; assert.strictEqual(glob.match(expr, 'foo.js'), '**/*.js'); diff --git a/src/vs/base/test/common/json.test.ts b/src/vs/base/test/common/json.test.ts index 91a2c976f48..ff75a79a73e 100644 --- a/src/vs/base/test/common/json.test.ts +++ b/src/vs/base/test/common/json.test.ts @@ -49,6 +49,7 @@ function assertTree(input: string, expected: any, expectedErrors: number[] = [], if (node.children) { for (const child of node.children) { assert.strictEqual(node, child.parent); + // eslint-disable-next-line local/code-no-any-casts delete (child).parent; // delete to avoid recursion in deep equal checkParent(child); } diff --git a/src/vs/base/test/common/mock.ts b/src/vs/base/test/common/mock.ts index 7a0d9fbefd7..8662c4fdac2 100644 --- a/src/vs/base/test/common/mock.ts +++ b/src/vs/base/test/common/mock.ts @@ -10,6 +10,7 @@ export interface Ctor { } export function mock(): Ctor { + // eslint-disable-next-line local/code-no-any-casts return function () { } as any; } @@ -18,6 +19,7 @@ export type MockObject = { [K in keyof T]: K extends Exc // Creates an object object that returns sinon mocks for every property. Optionally // takes base properties. export const mockObject = () => = {}>(properties?: TP): MockObject => { + // eslint-disable-next-line local/code-no-any-casts return new Proxy({ ...properties } as any, { get(target, key) { if (!target.hasOwnProperty(key)) { diff --git a/src/vs/base/test/common/oauth.test.ts b/src/vs/base/test/common/oauth.test.ts index 09819b8d870..acdcc429a06 100644 --- a/src/vs/base/test/common/oauth.test.ts +++ b/src/vs/base/test/common/oauth.test.ts @@ -1084,6 +1084,7 @@ suite('OAuth', () => { resource: 'https://example.com/api' }; + // eslint-disable-next-line local/code-no-any-casts const globalFetchStub = sandbox.stub(globalThis, 'fetch').resolves({ status: 200, json: async () => metadata, diff --git a/src/vs/base/test/common/snapshot.ts b/src/vs/base/test/common/snapshot.ts index 9523d6ca3aa..6cef4010941 100644 --- a/src/vs/base/test/common/snapshot.ts +++ b/src/vs/base/test/common/snapshot.ts @@ -120,7 +120,9 @@ function formatValue(value: unknown, level = 0, seen: unknown[] = []): string { if (seen.includes(value)) { return '[Circular]'; } + // eslint-disable-next-line local/code-no-any-casts if (debugDescriptionSymbol in value && typeof (value as any)[debugDescriptionSymbol] === 'function') { + // eslint-disable-next-line local/code-no-any-casts return (value as any)[debugDescriptionSymbol](); } const oi = ' '.repeat(level); diff --git a/src/vs/base/test/common/timeTravelScheduler.ts b/src/vs/base/test/common/timeTravelScheduler.ts index 7c5e397a48c..f706b997622 100644 --- a/src/vs/base/test/common/timeTravelScheduler.ts +++ b/src/vs/base/test/common/timeTravelScheduler.ts @@ -265,6 +265,7 @@ function setInterval(scheduler: Scheduler, handler: TimerHandler, interval: numb } function overwriteGlobals(scheduler: Scheduler): IDisposable { + // eslint-disable-next-line local/code-no-any-casts globalThis.setTimeout = ((handler: TimerHandler, timeout?: number) => setTimeout(scheduler, handler, timeout)) as any; globalThis.clearTimeout = (timeoutId: any) => { if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) { @@ -274,6 +275,7 @@ function overwriteGlobals(scheduler: Scheduler): IDisposable { } }; + // eslint-disable-next-line local/code-no-any-casts globalThis.setInterval = ((handler: TimerHandler, timeout: number) => setInterval(scheduler, handler, timeout)) as any; globalThis.clearInterval = (timeoutId: any) => { if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) { @@ -306,11 +308,13 @@ function createDateClass(scheduler: Scheduler): DateConstructor { if (args.length === 0) { return new OriginalDate(scheduler.now); } + // eslint-disable-next-line local/code-no-any-casts return new (OriginalDate as any)(...args); } for (const prop in OriginalDate) { if (OriginalDate.hasOwnProperty(prop)) { + // eslint-disable-next-line local/code-no-any-casts (SchedulerDate as any)[prop] = (OriginalDate as any)[prop]; } } @@ -326,6 +330,7 @@ function createDateClass(scheduler: Scheduler): DateConstructor { SchedulerDate.UTC = OriginalDate.UTC; SchedulerDate.prototype.toUTCString = OriginalDate.prototype.toUTCString; + // eslint-disable-next-line local/code-no-any-casts return SchedulerDate as any; } diff --git a/src/vs/base/test/common/troubleshooting.ts b/src/vs/base/test/common/troubleshooting.ts index f794f7e2033..953fbd6972a 100644 --- a/src/vs/base/test/common/troubleshooting.ts +++ b/src/vs/base/test/common/troubleshooting.ts @@ -47,9 +47,11 @@ export function endTrackingDisposables(): void { } export function beginLoggingFS(withStacks: boolean = false): void { + // eslint-disable-next-line local/code-no-any-casts (self).beginLoggingFS?.(withStacks); } export function endLoggingFS(): void { + // eslint-disable-next-line local/code-no-any-casts (self).endLoggingFS?.(); } diff --git a/src/vs/code/electron-browser/workbench/workbench.ts b/src/vs/code/electron-browser/workbench/workbench.ts index 40ca898489b..c1bad461c93 100644 --- a/src/vs/code/electron-browser/workbench/workbench.ts +++ b/src/vs/code/electron-browser/workbench/workbench.ts @@ -17,6 +17,7 @@ type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-browser/globals.js').IMainWindowSandboxGlobals; type IDesktopMain = import('../../../workbench/electron-browser/desktop.main.js').IDesktopMain; + // eslint-disable-next-line local/code-no-any-casts const preloadGlobals: IMainWindowSandboxGlobals = (window as any).vscode; // defined by preload.ts const safeProcess = preloadGlobals.process; @@ -126,6 +127,7 @@ titleDiv.style.left = '0'; titleDiv.style.top = '0'; titleDiv.style.backgroundColor = `${colorInfo.titleBarBackground}`; + // eslint-disable-next-line local/code-no-any-casts (titleDiv.style as any)['-webkit-app-region'] = 'drag'; splash.appendChild(titleDiv); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 9643512df76..271d4625d53 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -545,6 +545,7 @@ export class CodeApplication extends Disposable { // See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085 try { if (isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) { + // eslint-disable-next-line local/code-no-any-casts systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any); } } catch (error) { @@ -690,6 +691,7 @@ export class CodeApplication extends Disposable { } // macOS: open-url events that were received before the app is ready + // eslint-disable-next-line local/code-no-any-casts const protocolUrlsFromEvent = ((global).getOpenUrls() || []) as string[]; if (protocolUrlsFromEvent.length > 0) { this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent); @@ -1297,6 +1299,7 @@ export class CodeApplication extends Disposable { } } + // eslint-disable-next-line local/code-no-any-casts const macOpenFiles: string[] = (global).macOpenFiles; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 8b936dd220c..31733468ae9 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -143,6 +143,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat outerHeight: this._containerObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value, + // eslint-disable-next-line local/code-no-any-casts editContextSupported: typeof (globalThis as any).EditContext === 'function', accessibilitySupport: ( this._accessibilityService.isScreenReaderOptimized() @@ -287,6 +288,7 @@ class EditorOptionsUtil { public static validateOptions(options: IEditorOptions): ValidatedEditorOptions { const result = new ValidatedEditorOptions(); for (const editorOption of editorOptionsRegistry) { + // eslint-disable-next-line local/code-no-any-casts const value = (editorOption.name === '_never_' ? undefined : (options as any)[editorOption.name]); result._write(editorOption.id, editorOption.validate(value)); } @@ -340,7 +342,9 @@ class EditorOptionsUtil { let changed = false; for (const editorOption of editorOptionsRegistry) { if (update.hasOwnProperty(editorOption.name)) { + // eslint-disable-next-line local/code-no-any-casts const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]); + // eslint-disable-next-line local/code-no-any-casts (options as any)[editorOption.name] = result.newValue; changed = changed || result.didChange; } diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 3664d611ec4..a50db6f6a69 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -212,6 +212,7 @@ registerEditorSettingMigration('editor.experimentalEditContextEnabled', (value, registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => { if (value && typeof value === 'object') { let toBeModified = false; + // eslint-disable-next-line local/code-no-any-casts const newValue = {} as any; for (const entry of Object.entries(value)) { if (typeof entry[1] === 'boolean') { diff --git a/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts b/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts index 84b9cc52df3..be5d7964fca 100644 --- a/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts +++ b/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts @@ -10,6 +10,7 @@ export namespace EditContext { * Create an edit context. */ export function create(window: Window, options?: EditContextInit): EditContext { + // eslint-disable-next-line local/code-no-any-casts return new (window as any).EditContext(options); } } diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index b9504d9082a..4a077055284 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -78,9 +78,11 @@ export class FocusTracker extends Disposable { } export function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): IDisposable { + // eslint-disable-next-line local/code-no-any-casts target.addEventListener(type, listener as any, options); return { dispose() { + // eslint-disable-next-line local/code-no-any-casts target.removeEventListener(type, listener as any); } }; diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index cdfc280ec5e..52fa8965e8c 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -245,6 +245,7 @@ export class MouseHandler extends ViewEventHandler { if (!this.viewHelper.viewDomNode.contains(target)) { const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode); if (shadowRoot) { + // eslint-disable-next-line local/code-no-any-casts target = (shadowRoot).elementsFromPoint(e.posx, e.posy).find( (el: Element) => this.viewHelper.viewDomNode.contains(el) ); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index db133ec98c6..0664a2945fa 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -150,6 +150,7 @@ export class MouseTarget { } public static toString(target: IMouseTarget): string { + // eslint-disable-next-line local/code-no-any-casts return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target).detail); } } @@ -989,12 +990,15 @@ export class MouseTargetFactory { const shadowRoot = dom.getShadowRoot(ctx.viewDomNode); let range: Range; if (shadowRoot) { + // eslint-disable-next-line local/code-no-any-casts if (typeof (shadowRoot).caretRangeFromPoint === 'undefined') { range = shadowCaretRangeFromPoint(shadowRoot, coords.clientX, coords.clientY); } else { + // eslint-disable-next-line local/code-no-any-casts range = (shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY); } } else { + // eslint-disable-next-line local/code-no-any-casts range = (ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY); } @@ -1037,6 +1041,7 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { + // eslint-disable-next-line local/code-no-any-casts const hitResult: { offsetNode: Node; offset: number } = (ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { @@ -1089,8 +1094,10 @@ export class MouseTargetFactory { public static doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult { let result: HitTestResult = new UnknownHitTestResult(); + // eslint-disable-next-line local/code-no-any-casts if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); + // eslint-disable-next-line local/code-no-any-casts } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); } @@ -1110,6 +1117,7 @@ function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number) const range = document.createRange(); // Get the element under the point + // eslint-disable-next-line local/code-no-any-casts let el: HTMLElement | null = (shadowRoot).elementFromPoint(x, y); // When el is not null, it may be div.monaco-mouse-cursor-text Element, which has not childNodes, we don't need to handle it. if (el?.hasChildNodes()) { diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 80fc4825a83..0c31574d432 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -54,6 +54,7 @@ export class PointerEventHandler extends MouseHandler { } private onTap(event: GestureEvent): void { + // eslint-disable-next-line local/code-no-any-casts if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) { return; } @@ -94,6 +95,7 @@ export class PointerEventHandler extends MouseHandler { } protected override _onMouseDown(e: EditorMouseEvent, pointerId: number): void { + // eslint-disable-next-line local/code-no-any-casts if ((e.browserEvent as any).pointerType === 'touch') { return; } diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index c2d7f868d63..e05e567da10 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -242,6 +242,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable { // Add a <> keydown event listener that will cancel the monitoring // if something other than a modifier key is pressed + // eslint-disable-next-line local/code-no-any-casts this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { const chord = e.toKeyCodeChord(); if (chord.isModifierKey()) { @@ -381,6 +382,7 @@ class RefCountedCssRule { private getCssText(className: string, properties: CssProperties): string { let str = `.${className} {`; for (const prop in properties) { + // eslint-disable-next-line local/code-no-any-casts const value = (properties as any)[prop] as string | ThemeColor; let cssValue; if (typeof value === 'object') { diff --git a/src/vs/editor/browser/gpu/gpuUtils.ts b/src/vs/editor/browser/gpu/gpuUtils.ts index 6ced420acc2..65869ac795f 100644 --- a/src/vs/editor/browser/gpu/gpuUtils.ts +++ b/src/vs/editor/browser/gpu/gpuUtils.ts @@ -49,6 +49,7 @@ export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: } }); try { + // eslint-disable-next-line local/code-no-any-casts observer.observe(element, { box: ['device-pixel-content-box'] } as any); } catch { observer.disconnect(); diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 4333d262ff6..958da4c4e96 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -223,6 +223,7 @@ export class ViewGpuContext extends Disposable { } for (const r of rule.style) { if (!supportsCssRule(r, rule.style)) { + // eslint-disable-next-line local/code-no-any-casts problemRules.push(`${r}: ${rule.style[r as any]}`); return false; } diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 676e4b31545..3006620cd38 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -420,6 +420,7 @@ export class HoverService extends Disposable implements IHoverService { }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; + // eslint-disable-next-line local/code-no-any-casts hideHover(false, (e).fromElement === targetElement); }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index a770f47d708..318bb1ec15b 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -1029,6 +1029,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const codeEditorState = s as editorCommon.ICodeEditorViewState | null; if (codeEditorState && codeEditorState.cursorState && codeEditorState.viewState) { + // eslint-disable-next-line local/code-no-any-casts const cursorState = codeEditorState.cursorState; if (Array.isArray(cursorState)) { if (cursorState.length > 0) { diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 351715f2d1b..a8f447a7ce2 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -72,6 +72,7 @@ export class DiffEditorEditors extends Disposable { this.isModifiedFocused = observableCodeEditor(this.modified).isFocused; this.isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader)); + // eslint-disable-next-line local/code-no-any-casts this._argCodeEditorWidgetOptions = null as any; this._register(autorunHandleChanges({ diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index 48ae36e0b90..a0a80584641 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -176,6 +176,7 @@ function validateDiffEditorOptions(options: Readonly, defaul useTrueInlineView: validateBooleanOption(options.experimental?.useTrueInlineView, defaults.experimental.useTrueInlineView!), }, hideUnchangedRegions: { + // eslint-disable-next-line local/code-no-any-casts enabled: validateBooleanOption(options.hideUnchangedRegions?.enabled ?? (options.experimental as any)?.collapseUnchangedRegions, defaults.hideUnchangedRegions.enabled!), contextLineCount: clampedInt(options.hideUnchangedRegions?.contextLineCount, defaults.hideUnchangedRegions.contextLineCount!, 0, Constants.MAX_SAFE_SMALL_INTEGER), minimumLineCount: clampedInt(options.hideUnchangedRegions?.minimumLineCount, defaults.hideUnchangedRegions.minimumLineCount!, 0, Constants.MAX_SAFE_SMALL_INTEGER), diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 8fd39f2945d..050e79661da 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -474,6 +474,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._editors.original.restoreViewState(diffEditorState.original); this._editors.modified.restoreViewState(diffEditorState.modified); if (diffEditorState.modelState) { + // eslint-disable-next-line local/code-no-any-casts this._diffModel.get()?.restoreSerializedState(diffEditorState.modelState as any); } } diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index c1579dacef5..b60844ad1c1 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -181,6 +181,7 @@ function easeOutExpo(t: number, b: number, c: number, d: number): number { } export function deepMerge(source1: T, source2: Partial): T { + // eslint-disable-next-line local/code-no-any-casts const result = {} as any as T; for (const key in source1) { result[key] = source1[key]; @@ -190,6 +191,7 @@ export function deepMerge(source1: T, source2: Partial): T { if (typeof result[key] === 'object' && source2Value && typeof source2Value === 'object') { result[key] = deepMerge(result[key], source2Value); } else { + // eslint-disable-next-line local/code-no-any-casts result[key] = source2Value as any; } } @@ -297,12 +299,14 @@ export function applyStyle(domNode: HTMLElement, style: Partial<{ [TKey in keyof /** @description applyStyle */ for (let [key, val] of Object.entries(style)) { if (val && typeof val === 'object' && 'read' in val) { + // eslint-disable-next-line local/code-no-any-casts val = val.read(reader) as any; } if (typeof val === 'number') { val = `${val}px`; } key = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase()); + // eslint-disable-next-line local/code-no-any-casts domNode.style[key as any] = val as any; } }); diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 70a99958b3c..6023bd63ff7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -97,8 +97,10 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< h('div.header-content', [ h('div.collapse-button@collapseButton'), h('div.file-path', [ + // eslint-disable-next-line local/code-no-any-casts h('div.title.modified.show-file-icons@primaryPath', [] as any), h('div.status.deleted@status', ['R']), + // eslint-disable-next-line local/code-no-any-casts h('div.title.original.show-file-icons@secondaryPath', [] as any), ]), h('div.actions@actions'), diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 0973ecf70db..2776cb38315 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1171,6 +1171,7 @@ abstract class ComputedEditorOption implements IEdito constructor(id: K) { this.id = id; this.name = '_never_'; + // eslint-disable-next-line local/code-no-any-casts this.defaultValue = undefined; } @@ -1207,6 +1208,7 @@ class SimpleEditorOption implements IEditorOption extends S constructor(id: K, name: PossibleKeyName, defaultValue: V, allowedValues: ReadonlyArray, schema: IConfigurationPropertySchema | undefined = undefined) { if (typeof schema !== 'undefined') { schema.type = 'string'; + // eslint-disable-next-line local/code-no-any-casts schema.enum = allowedValues; schema.default = defaultValue; } @@ -1422,6 +1425,7 @@ class EditorEnumOption extends Base if (this._allowedValues.indexOf(input) === -1) { return this.defaultValue; } + // eslint-disable-next-line local/code-no-any-casts return this._convert(input); } } @@ -4778,6 +4782,7 @@ class GuideOptions extends BaseEditorOption(value: unknown, defaultValue: T, allowedValues: T[]): T { + // eslint-disable-next-line local/code-no-any-casts const idx = allowedValues.indexOf(value as any); if (idx === -1) { return defaultValue; diff --git a/src/vs/editor/common/core/edits/stringEdit.ts b/src/vs/editor/common/core/edits/stringEdit.ts index 00013fadb12..02345d7731d 100644 --- a/src/vs/editor/common/core/edits/stringEdit.ts +++ b/src/vs/editor/common/core/edits/stringEdit.ts @@ -20,6 +20,7 @@ export abstract class BaseStringEdit = BaseSt } let result = edits[0]; for (let i = 1; i < edits.length; i++) { + // eslint-disable-next-line local/code-no-any-casts result = result.compose(edits[i]) as any; } return result; diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 772510bede6..3407f209289 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -457,6 +457,7 @@ export namespace CompletionItemKinds { const data = new Map(); data.set('method', CompletionItemKind.Method); data.set('function', CompletionItemKind.Function); + // eslint-disable-next-line local/code-no-any-casts data.set('constructor', CompletionItemKind.Constructor); data.set('field', CompletionItemKind.Field); data.set('variable', CompletionItemKind.Variable); diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts index 26416a62ccd..8be4f51301a 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts @@ -23,9 +23,11 @@ export function lengthDiff(startLineCount: number, startColumnCount: number, end */ export type Length = { _brand: 'Length' }; +// eslint-disable-next-line local/code-no-any-casts export const lengthZero = 0 as any as Length; export function lengthIsZero(length: Length): boolean { + // eslint-disable-next-line local/code-no-any-casts return length as any as number === 0; } @@ -46,10 +48,12 @@ export function toLength(lineCount: number, columnCount: number): Length { // If there is no overflow (all values/sums below 2^26 = 67108864), // we have `toLength(lns1, cols1) + toLength(lns2, cols2) = toLength(lns1 + lns2, cols1 + cols2)`. + // eslint-disable-next-line local/code-no-any-casts return (lineCount * factor + columnCount) as any as Length; } export function lengthToObj(length: Length): TextLength { + // eslint-disable-next-line local/code-no-any-casts const l = length as any as number; const lineCount = Math.floor(l / factor); const columnCount = l - lineCount * factor; @@ -57,6 +61,7 @@ export function lengthToObj(length: Length): TextLength { } export function lengthGetLineCount(length: Length): number { + // eslint-disable-next-line local/code-no-any-casts return Math.floor(length as any as number / factor); } @@ -64,6 +69,7 @@ export function lengthGetLineCount(length: Length): number { * Returns the amount of columns of the given length, assuming that it does not span any line. */ export function lengthGetColumnCountIfZeroLineCount(length: Length): number { + // eslint-disable-next-line local/code-no-any-casts return length as any as number; } @@ -89,7 +95,9 @@ export function lengthEquals(length1: Length, length2: Length): boolean { * Returns a non negative length `result` such that `lengthAdd(length1, result) = length2`, or zero if such length does not exist. */ export function lengthDiffNonNegative(length1: Length, length2: Length): Length { + // eslint-disable-next-line local/code-no-any-casts const l1 = length1 as any as number; + // eslint-disable-next-line local/code-no-any-casts const l2 = length2 as any as number; const diff = l2 - l1; @@ -114,18 +122,22 @@ export function lengthDiffNonNegative(length1: Length, length2: Length): Length export function lengthLessThan(length1: Length, length2: Length): boolean { // First, compare line counts, then column counts. + // eslint-disable-next-line local/code-no-any-casts return (length1 as any as number) < (length2 as any as number); } export function lengthLessThanEqual(length1: Length, length2: Length): boolean { + // eslint-disable-next-line local/code-no-any-casts return (length1 as any as number) <= (length2 as any as number); } export function lengthGreaterThanEqual(length1: Length, length2: Length): boolean { + // eslint-disable-next-line local/code-no-any-casts return (length1 as any as number) >= (length2 as any as number); } export function lengthToPosition(length: Length): Position { + // eslint-disable-next-line local/code-no-any-casts const l = length as any as number; const lineCount = Math.floor(l / factor); const colCount = l - lineCount * factor; @@ -137,10 +149,12 @@ export function positionToLength(position: Position): Length { } export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range { + // eslint-disable-next-line local/code-no-any-casts const l = lengthStart as any as number; const lineCount = Math.floor(l / factor); const colCount = l - lineCount * factor; + // eslint-disable-next-line local/code-no-any-casts const l2 = lengthEnd as any as number; const lineCount2 = Math.floor(l2 / factor); const colCount2 = l2 - lineCount2 * factor; @@ -157,7 +171,9 @@ export function lengthOfRange(range: Range): TextLength { } export function lengthCompare(length1: Length, length2: Length): number { + // eslint-disable-next-line local/code-no-any-casts const l1 = length1 as any as number; + // eslint-disable-next-line local/code-no-any-casts const l2 = length2 as any as number; return l1 - l2; } @@ -176,6 +192,7 @@ export function lengthOfStringObj(str: string): TextLength { * Computes a numeric hash of the given length. */ export function lengthHash(length: Length): number { + // eslint-disable-next-line local/code-no-any-casts return length as any; } diff --git a/src/vs/editor/common/services/editorBaseApi.ts b/src/vs/editor/common/services/editorBaseApi.ts index 6fbc1c4df93..1adaf0dcf86 100644 --- a/src/vs/editor/common/services/editorBaseApi.ts +++ b/src/vs/editor/common/services/editorBaseApi.ts @@ -34,10 +34,12 @@ export function createMonacoBaseAPI(): typeof monaco { KeyMod: KeyMod, Position: Position, Range: Range, + // eslint-disable-next-line local/code-no-any-casts Selection: Selection, SelectionDirection: standaloneEnums.SelectionDirection, MarkerSeverity: standaloneEnums.MarkerSeverity, MarkerTag: standaloneEnums.MarkerTag, + // eslint-disable-next-line local/code-no-any-casts Uri: URI, Token: Token }; diff --git a/src/vs/editor/common/textModelEditSource.ts b/src/vs/editor/common/textModelEditSource.ts index a25dfcd3f51..617253850dc 100644 --- a/src/vs/editor/common/textModelEditSource.ts +++ b/src/vs/editor/common/textModelEditSource.ts @@ -56,6 +56,7 @@ export class TextModelEditSource { } public get props(): Record { + // eslint-disable-next-line local/code-no-any-casts return this.metadata as any; } } @@ -65,6 +66,7 @@ type TextModelEditSourceT = TextModelEditSource & { }; function createEditSource>(metadata: T): TextModelEditSourceT { + // eslint-disable-next-line local/code-no-any-casts return new TextModelEditSource(metadata as any, privateSymbol) as any; } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 18b746a0578..18a4187c3a0 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -97,6 +97,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { // handle deprecated settings. [languageId].colorDecorators.enable const deprecatedConfig = this._configurationService.getValue(languageId); if (deprecatedConfig && typeof deprecatedConfig === 'object') { + // eslint-disable-next-line local/code-no-any-casts const colorDecorators = (deprecatedConfig as any)['colorDecorators']; // deprecatedConfig.valueOf('.colorDecorators.enable'); if (colorDecorators && colorDecorators['enable'] !== undefined && !colorDecorators['enable']) { return colorDecorators['enable']; diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index d7447d18bd3..547f5a7800f 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -229,6 +229,7 @@ export class ContextMenuController implements IEditorContribution { return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true }); } + // eslint-disable-next-line local/code-no-any-casts const customActionViewItem = action; if (typeof customActionViewItem.getActionViewItem === 'function') { return customActionViewItem.getActionViewItem(); diff --git a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts index a59d3a904dc..b30bbfeb52a 100644 --- a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts @@ -197,6 +197,7 @@ suite('OutlineModel', function () { super(null!); } readyForTesting() { + // eslint-disable-next-line local/code-no-any-casts this._groups = this.children as any; } }; diff --git a/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts b/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts index c72c5a7b7e6..434caed9bfc 100644 --- a/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts +++ b/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts @@ -94,6 +94,7 @@ suite('Editor Core - Editor State', () => { const mappedModel = model ? { uri: model.uri ? model.uri : URI.parse('http://dummy.org'), getVersionId: () => model.version } : null; return { + // eslint-disable-next-line local/code-no-any-casts getModel: (): ITextModel => mappedModel, getPosition: (): Position | undefined => position, getSelection: (): Selection | undefined => selection, diff --git a/src/vs/editor/contrib/find/browser/findModel.ts b/src/vs/editor/contrib/find/browser/findModel.ts index bd6fa9baedc..f03973129a1 100644 --- a/src/vs/editor/contrib/find/browser/findModel.ts +++ b/src/vs/editor/contrib/find/browser/findModel.ts @@ -553,6 +553,7 @@ export class FindModelBoundToEditorModel { if (replacePattern.hasReplacementPatterns || preserveCase) { resultText = modelText.replace(searchRegex, function () { + // eslint-disable-next-line local/code-no-any-casts return replacePattern.buildReplaceString(arguments, preserveCase); }); } else { diff --git a/src/vs/editor/contrib/find/test/browser/findController.test.ts b/src/vs/editor/contrib/find/test/browser/findController.test.ts index ea683d5b9a4..600d48e646a 100644 --- a/src/vs/editor/contrib/find/test/browser/findController.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findController.test.ts @@ -76,6 +76,7 @@ suite('FindController', () => { serviceCollection.set(IStorageService, new InMemoryStorageService()); if (platform.isMacintosh) { + // eslint-disable-next-line local/code-no-any-casts serviceCollection.set(IClipboardService, { readFindText: () => clipboardState, writeFindText: (value: any) => { clipboardState = value; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts b/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts index d1e20f36b72..8848974c2d0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts @@ -53,6 +53,7 @@ interface IFetchResult { * The sourceLabel must not contain '@'! */ export function formatRecordableLogEntry(entry: T): string { + // eslint-disable-next-line local/code-no-any-casts return entry.sourceId + ' @@ ' + JSON.stringify({ ...entry, modelUri: (entry as any).modelUri?.toString(), sourceId: undefined }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts index 49286faf0c8..88ce4ca138f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts @@ -271,6 +271,7 @@ export class GhostTextView extends Disposable { color: 'orange', }, ref: (dom) => { + // eslint-disable-next-line local/code-no-any-casts (dom as any as WidgetDomElement).ghostTextViewWarningWidgetData = { range: Range.fromPositions(state.position) }; } }, [ @@ -292,6 +293,7 @@ export class GhostTextView extends Disposable { } public static getWarningWidgetContext(domNode: HTMLElement): { range: Range } | undefined { + // eslint-disable-next-line local/code-no-any-casts const data = (domNode as any as WidgetDomElement).ghostTextViewWarningWidgetData; if (data) { return data; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index d76a3fa99aa..9d842398bd6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -343,6 +343,7 @@ export class InlineEditsGutterIndicator extends Disposable { zIndex: '20', position: 'absolute', backgroundColor: this._gutterIndicatorStyles.map(v => v.background), + // eslint-disable-next-line local/code-no-any-casts ['--vscodeIconForeground' as any]: this._gutterIndicatorStyles.map(v => v.foreground), border: this._gutterIndicatorStyles.map(v => `1px solid ${v.border}`), boxSizing: 'border-box', diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts index 9ed8ebe1732..d74016c28aa 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts @@ -12,6 +12,7 @@ export interface IVisualizationEffect { } export function setVisualization(data: object, visualization: IVisualizationEffect): void { + // eslint-disable-next-line local/code-no-any-casts (data as any)['$$visualization'] = visualization; } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts index b4fbab79c59..71d2278d792 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts @@ -20,6 +20,7 @@ suite('computeGhostText', () => { const tempModel = createTextModel(cleanedText); const range = Range.fromPositions(tempModel.getPositionAt(rangeStartOffset), tempModel.getPositionAt(rangeEndOffset)); const options = ['prefix', 'subword'] as const; + // eslint-disable-next-line local/code-no-any-casts const result = {} as any; for (const option of options) { result[option] = computeGhostText(new TextReplacement(range, suggestion), tempModel, option)?.render(cleanedText, true); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 1b369ed62b9..8d111bda207 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -160,6 +160,7 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( }], [ILabelService, new class extends mock() { }], [IWorkspaceContextService, new class extends mock() { }], + // eslint-disable-next-line local/code-no-any-casts [IAccessibilitySignalService, { playSignal: async () => { }, isSoundEnabled(signal: unknown) { return false; }, diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 581e3589463..ee9d50d372b 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -237,6 +237,7 @@ export async function withAsyncTestCodeEditorAndInlineCompletionsModel( options.serviceCollection = new ServiceCollection(); } options.serviceCollection.set(ILanguageFeaturesService, languageFeaturesService); + // eslint-disable-next-line local/code-no-any-casts options.serviceCollection.set(IAccessibilitySignalService, { playSignal: async () => { }, isSoundEnabled(signal: unknown) { return false; }, diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index b75bc9e61e9..3aba528ff7f 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -794,6 +794,7 @@ async function excludeCharFromBeingHighlighted(configurationService: IConfigurat let value: Record; if ((typeof existingValue === 'object') && existingValue) { + // eslint-disable-next-line local/code-no-any-casts value = existingValue as any; } else { value = {}; @@ -812,6 +813,7 @@ async function excludeLocaleFromBeingHighlighted(configurationService: IConfigur let value: Record; if ((typeof existingValue === 'object') && existingValue) { // Copy value, as the existing value is read only + // eslint-disable-next-line local/code-no-any-casts value = Object.assign({}, existingValue as any); } else { value = {}; diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index 55d4fe18651..645d902f7b6 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -41,12 +41,16 @@ interface IMonacoEnvironment { globalAPI?: boolean; } +// eslint-disable-next-line local/code-no-any-casts const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; +// eslint-disable-next-line local/code-no-any-casts if (monacoEnvironment?.globalAPI || (typeof (globalThis as any).define === 'function' && ((globalThis as any).define).amd)) { globalThis.monaco = api; } +// eslint-disable-next-line local/code-no-any-casts if (typeof (globalThis as any).require !== 'undefined' && typeof (globalThis as any).require.config === 'function') { + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).require.config({ ignoreDuplicateModules: [ 'vscode-languageserver-types', diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index a62c7fc22ba..6a9604e9038 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -501,42 +501,70 @@ export function registerEditorOpener(opener: ICodeEditorOpener): IDisposable { export function createMonacoEditorAPI(): typeof monaco.editor { return { // methods + // eslint-disable-next-line local/code-no-any-casts create: create, + // eslint-disable-next-line local/code-no-any-casts getEditors: getEditors, + // eslint-disable-next-line local/code-no-any-casts getDiffEditors: getDiffEditors, + // eslint-disable-next-line local/code-no-any-casts onDidCreateEditor: onDidCreateEditor, + // eslint-disable-next-line local/code-no-any-casts onDidCreateDiffEditor: onDidCreateDiffEditor, + // eslint-disable-next-line local/code-no-any-casts createDiffEditor: createDiffEditor, + // eslint-disable-next-line local/code-no-any-casts addCommand: addCommand, + // eslint-disable-next-line local/code-no-any-casts addEditorAction: addEditorAction, + // eslint-disable-next-line local/code-no-any-casts addKeybindingRule: addKeybindingRule, + // eslint-disable-next-line local/code-no-any-casts addKeybindingRules: addKeybindingRules, + // eslint-disable-next-line local/code-no-any-casts createModel: createModel, + // eslint-disable-next-line local/code-no-any-casts setModelLanguage: setModelLanguage, + // eslint-disable-next-line local/code-no-any-casts setModelMarkers: setModelMarkers, + // eslint-disable-next-line local/code-no-any-casts getModelMarkers: getModelMarkers, removeAllMarkers: removeAllMarkers, + // eslint-disable-next-line local/code-no-any-casts onDidChangeMarkers: onDidChangeMarkers, + // eslint-disable-next-line local/code-no-any-casts getModels: getModels, + // eslint-disable-next-line local/code-no-any-casts getModel: getModel, + // eslint-disable-next-line local/code-no-any-casts onDidCreateModel: onDidCreateModel, + // eslint-disable-next-line local/code-no-any-casts onWillDisposeModel: onWillDisposeModel, + // eslint-disable-next-line local/code-no-any-casts onDidChangeModelLanguage: onDidChangeModelLanguage, + // eslint-disable-next-line local/code-no-any-casts createWebWorker: createWebWorker, + // eslint-disable-next-line local/code-no-any-casts colorizeElement: colorizeElement, + // eslint-disable-next-line local/code-no-any-casts colorize: colorize, + // eslint-disable-next-line local/code-no-any-casts colorizeModelLine: colorizeModelLine, + // eslint-disable-next-line local/code-no-any-casts tokenize: tokenize, + // eslint-disable-next-line local/code-no-any-casts defineTheme: defineTheme, + // eslint-disable-next-line local/code-no-any-casts setTheme: setTheme, remeasureFonts: remeasureFonts, registerCommand: registerCommand, registerLinkOpener: registerLinkOpener, + // eslint-disable-next-line local/code-no-any-casts registerEditorOpener: registerEditorOpener, // enums @@ -568,18 +596,27 @@ export function createMonacoEditorAPI(): typeof monaco.editor { TextDirection: standaloneEnums.TextDirection, // classes + // eslint-disable-next-line local/code-no-any-casts ConfigurationChangedEvent: ConfigurationChangedEvent, + // eslint-disable-next-line local/code-no-any-casts BareFontInfo: BareFontInfo, + // eslint-disable-next-line local/code-no-any-casts FontInfo: FontInfo, + // eslint-disable-next-line local/code-no-any-casts TextModelResolvedOptions: TextModelResolvedOptions, + // eslint-disable-next-line local/code-no-any-casts FindMatch: FindMatch, + // eslint-disable-next-line local/code-no-any-casts ApplyUpdateResult: ApplyUpdateResult, + // eslint-disable-next-line local/code-no-any-casts EditorZoom: EditorZoom, + // eslint-disable-next-line local/code-no-any-casts createMultiFileDiffEditor: createMultiFileDiffEditor, // vars EditorType: EditorType, + // eslint-disable-next-line local/code-no-any-casts EditorOptions: EditorOptions }; diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 5b2310fff17..91125ebc41a 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -750,43 +750,78 @@ export interface CodeActionProviderMetadata { */ export function createMonacoLanguagesAPI(): typeof monaco.languages { return { + // eslint-disable-next-line local/code-no-any-casts register: register, + // eslint-disable-next-line local/code-no-any-casts getLanguages: getLanguages, + // eslint-disable-next-line local/code-no-any-casts onLanguage: onLanguage, + // eslint-disable-next-line local/code-no-any-casts onLanguageEncountered: onLanguageEncountered, + // eslint-disable-next-line local/code-no-any-casts getEncodedLanguageId: getEncodedLanguageId, // provider methods + // eslint-disable-next-line local/code-no-any-casts setLanguageConfiguration: setLanguageConfiguration, setColorMap: setColorMap, + // eslint-disable-next-line local/code-no-any-casts registerTokensProviderFactory: registerTokensProviderFactory, + // eslint-disable-next-line local/code-no-any-casts setTokensProvider: setTokensProvider, + // eslint-disable-next-line local/code-no-any-casts setMonarchTokensProvider: setMonarchTokensProvider, + // eslint-disable-next-line local/code-no-any-casts registerReferenceProvider: registerReferenceProvider, + // eslint-disable-next-line local/code-no-any-casts registerRenameProvider: registerRenameProvider, + // eslint-disable-next-line local/code-no-any-casts registerNewSymbolNameProvider: registerNewSymbolNameProvider, + // eslint-disable-next-line local/code-no-any-casts registerCompletionItemProvider: registerCompletionItemProvider, + // eslint-disable-next-line local/code-no-any-casts registerSignatureHelpProvider: registerSignatureHelpProvider, + // eslint-disable-next-line local/code-no-any-casts registerHoverProvider: registerHoverProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentSymbolProvider: registerDocumentSymbolProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentHighlightProvider: registerDocumentHighlightProvider, + // eslint-disable-next-line local/code-no-any-casts registerLinkedEditingRangeProvider: registerLinkedEditingRangeProvider, + // eslint-disable-next-line local/code-no-any-casts registerDefinitionProvider: registerDefinitionProvider, + // eslint-disable-next-line local/code-no-any-casts registerImplementationProvider: registerImplementationProvider, + // eslint-disable-next-line local/code-no-any-casts registerTypeDefinitionProvider: registerTypeDefinitionProvider, + // eslint-disable-next-line local/code-no-any-casts registerCodeLensProvider: registerCodeLensProvider, + // eslint-disable-next-line local/code-no-any-casts registerCodeActionProvider: registerCodeActionProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentFormattingEditProvider: registerDocumentFormattingEditProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentRangeFormattingEditProvider: registerDocumentRangeFormattingEditProvider, + // eslint-disable-next-line local/code-no-any-casts registerOnTypeFormattingEditProvider: registerOnTypeFormattingEditProvider, + // eslint-disable-next-line local/code-no-any-casts registerLinkProvider: registerLinkProvider, + // eslint-disable-next-line local/code-no-any-casts registerColorProvider: registerColorProvider, + // eslint-disable-next-line local/code-no-any-casts registerFoldingRangeProvider: registerFoldingRangeProvider, + // eslint-disable-next-line local/code-no-any-casts registerDeclarationProvider: registerDeclarationProvider, + // eslint-disable-next-line local/code-no-any-casts registerSelectionRangeProvider: registerSelectionRangeProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentSemanticTokensProvider: registerDocumentSemanticTokensProvider, + // eslint-disable-next-line local/code-no-any-casts registerDocumentRangeSemanticTokensProvider: registerDocumentRangeSemanticTokensProvider, + // eslint-disable-next-line local/code-no-any-casts registerInlineCompletionsProvider: registerInlineCompletionsProvider, + // eslint-disable-next-line local/code-no-any-casts registerInlayHintsProvider: registerInlayHintsProvider, // enums @@ -811,7 +846,9 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { // classes FoldingRangeKind: languages.FoldingRangeKind, + // eslint-disable-next-line local/code-no-any-casts SelectedSuggestionInfo: languages.SelectedSuggestionInfo, + // eslint-disable-next-line local/code-no-any-casts EditDeltaInfo: EditDeltaInfo, }; } diff --git a/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/src/vs/editor/standalone/common/monarch/monarchCompile.ts index 6ebd48c3ad8..b634f03a42c 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -458,6 +458,7 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm }; // For calling compileAction later on + // eslint-disable-next-line local/code-no-any-casts const lexerMin: monarchCommon.ILexerMin = json; lexerMin.languageId = languageId; lexerMin.includeLF = lexer.includeLF; @@ -535,6 +536,7 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm throw monarchCommon.createError(lexer, 'a language definition must define the \'tokenizer\' attribute as an object'); } + // eslint-disable-next-line local/code-no-any-casts lexer.tokenizer = []; for (const key in json.tokenizer) { if (json.tokenizer.hasOwnProperty(key)) { @@ -551,6 +553,7 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm // Set simple brackets if (json.brackets) { + // eslint-disable-next-line local/code-no-any-casts if (!(Array.isArray(json.brackets))) { throw monarchCommon.createError(lexer, 'the \'brackets\' attribute must be defined as an array'); } diff --git a/src/vs/editor/test/browser/config/editorConfiguration.test.ts b/src/vs/editor/test/browser/config/editorConfiguration.test.ts index ce875cd7037..d787e980ef7 100644 --- a/src/vs/editor/test/browser/config/editorConfiguration.test.ts +++ b/src/vs/editor/test/browser/config/editorConfiguration.test.ts @@ -87,6 +87,7 @@ suite('Common Editor Config', () => { test('wordWrap compat false', () => { const config = new TestWrappingConfiguration({ + // eslint-disable-next-line local/code-no-any-casts wordWrap: false }); assertWrapping(config, false, -1); @@ -95,6 +96,7 @@ suite('Common Editor Config', () => { test('wordWrap compat true', () => { const config = new TestWrappingConfiguration({ + // eslint-disable-next-line local/code-no-any-casts wordWrap: true }); assertWrapping(config, true, 80); diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 570749e69d7..f83418e0798 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -276,6 +276,7 @@ export function instantiateTestCodeEditor(instantiationService: IInstantiationSe }; const editor = instantiationService.createInstance( TestCodeEditor, + // eslint-disable-next-line local/code-no-any-casts new TestEditorDomElement(), options, codeEditorWidgetOptions diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index 9165d77afbf..c6c281bfe2b 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -1164,6 +1164,7 @@ suite('TextModel.createSnapshot', () => { test('issue #119632: invalid range', () => { const model = createTextModel('hello world!'); + // eslint-disable-next-line local/code-no-any-casts const actual = model._validateRangeRelaxedNoAllocations(new Range(undefined, 0, undefined, 1)); assert.deepStrictEqual(actual, new Range(1, 1, 1, 1)); model.dispose(); diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 3783e8e2606..f5118870ca0 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -269,6 +269,7 @@ suite('TextModelWithTokens - bracket matching', () => { for (let i = 1, len = model.getLineCount(); i <= len; i++) { const line = model.getLineContent(i); for (let j = 1, lenJ = line.length + 1; j <= lenJ; j++) { + // eslint-disable-next-line local/code-no-any-casts if (!isABracket[i].hasOwnProperty(j)) { assertIsNotBracket(model, i, j); } diff --git a/src/vs/editor/test/common/model/tokenStore.test.ts b/src/vs/editor/test/common/model/tokenStore.test.ts index 7ff8032361e..a87a693d0b9 100644 --- a/src/vs/editor/test/common/model/tokenStore.test.ts +++ b/src/vs/editor/test/common/model/tokenStore.test.ts @@ -54,6 +54,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 2, token: 4 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const root = store.root as any; assert.ok(root.children); assert.strictEqual(root.children.length, 2); @@ -74,6 +75,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 7, length: 1, token: 8 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const root = store.root as any; assert.ok(root.children); assert.strictEqual(root.children.length, 2); @@ -95,6 +97,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 3, token: 4 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const tokens = store.root as any; assert.strictEqual(tokens.children[0].token, 1); assert.strictEqual(tokens.children[1].token, 4); @@ -114,6 +117,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 3, token: 5 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const tokens = store.root as any; assert.strictEqual(tokens.children[0].token, 1); assert.strictEqual(tokens.children[1].token, 4); @@ -132,6 +136,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 0, length: 3, token: 4 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const tokens = store.root as any; assert.strictEqual(tokens.children[0].token, 4); assert.strictEqual(tokens.children[1].token, 2); @@ -150,6 +155,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 3, token: 4 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const tokens = store.root as any; assert.strictEqual(tokens.children[0].token, 1); assert.strictEqual(tokens.children[1].token, 2); @@ -168,6 +174,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 5, token: 4 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const tokens = store.root as any; assert.strictEqual(tokens.children[0].token, 1); assert.strictEqual(tokens.children[0].length, 3); @@ -193,6 +200,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 3, token: 9 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const root = store.root as any; // Verify the structure remains balanced assert.strictEqual(root.children.length, 3); @@ -223,6 +231,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 16, length: 4, token: 10 } ], TokenQuality.Accurate); + // eslint-disable-next-line local/code-no-any-casts const root = store.root as any; // Verify the structure remains balanced assert.strictEqual(root.children.length, 2); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 57be3923e57..86f7286130f 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -936,6 +936,7 @@ export class ContextKeyInExpr implements IContextKeyExpression { const item = context.getValue(this.key); if (Array.isArray(source)) { + // eslint-disable-next-line local/code-no-any-casts return source.includes(item as any); } @@ -1209,6 +1210,7 @@ export class ContextKeyGreaterExpr implements IContextKeyExpression { if (typeof this.value === 'string') { return false; } + // eslint-disable-next-line local/code-no-any-casts return (parseFloat(context.getValue(this.key)) > this.value); } @@ -1268,6 +1270,7 @@ export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression { if (typeof this.value === 'string') { return false; } + // eslint-disable-next-line local/code-no-any-casts return (parseFloat(context.getValue(this.key)) >= this.value); } @@ -1328,6 +1331,7 @@ export class ContextKeySmallerExpr implements IContextKeyExpression { if (typeof this.value === 'string') { return false; } + // eslint-disable-next-line local/code-no-any-casts return (parseFloat(context.getValue(this.key)) < this.value); } @@ -1388,6 +1392,7 @@ export class ContextKeySmallerEqualsExpr implements IContextKeyExpression { if (typeof this.value === 'string') { return false; } + // eslint-disable-next-line local/code-no-any-casts return (parseFloat(context.getValue(this.key)) <= this.value); } diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 514aa6d35ec..cf7ebe78a96 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -93,7 +93,9 @@ suite('ContextKeyExpr', () => { testExpression(expr + ' != true', !value); testExpression(expr + ' == false', !value); testExpression(expr + ' != false', !!value); + // eslint-disable-next-line local/code-no-any-casts testExpression(expr + ' == 5', value == '5'); + // eslint-disable-next-line local/code-no-any-casts testExpression(expr + ' != 5', value != '5'); testExpression('!' + expr, !value); testExpression(expr + ' =~ /d.*/', /d.*/.test(value)); diff --git a/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts index ce1b335e3ac..f35b594a62a 100644 --- a/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts +++ b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts @@ -90,6 +90,7 @@ export class DataChannelForwardingTelemetryService extends InterceptingTelemetry } if (forward) { + // eslint-disable-next-line local/code-no-any-casts dataChannelService.getDataChannel('editTelemetry').sendData({ eventName, data: data as any }); } }); diff --git a/src/vs/platform/dnd/browser/dnd.ts b/src/vs/platform/dnd/browser/dnd.ts index 835e2e15b88..e9de36f33f9 100644 --- a/src/vs/platform/dnd/browser/dnd.ts +++ b/src/vs/platform/dnd/browser/dnd.ts @@ -466,7 +466,9 @@ export function extractNotebookCellOutputDropData(e: DragEvent): NotebookCellOut * in a safe way without crashing the application when running in the web. */ export function getPathForFile(file: File): string | undefined { + // eslint-disable-next-line local/code-no-any-casts if (isNative && typeof (globalThis as any).vscode?.webUtils?.getPathForFile === 'function') { + // eslint-disable-next-line local/code-no-any-casts return (globalThis as any).vscode.webUtils.getPathForFile(file); } diff --git a/src/vs/platform/environment/test/node/argv.test.ts b/src/vs/platform/environment/test/node/argv.test.ts index 6147f6ecc56..d53e61deed8 100644 --- a/src/vs/platform/environment/test/node/argv.test.ts +++ b/src/vs/platform/environment/test/node/argv.test.ts @@ -44,6 +44,7 @@ suite('formatOptions', () => { test('Text should wrap', () => { assert.deepStrictEqual( formatOptions({ + // eslint-disable-next-line local/code-no-any-casts 'add': o(('bar ').repeat(9)) }, 40), [ @@ -55,6 +56,7 @@ suite('formatOptions', () => { test('Text should revert to the condensed view when the terminal is too narrow', () => { assert.deepStrictEqual( formatOptions({ + // eslint-disable-next-line local/code-no-any-casts 'add': o(('bar ').repeat(9)) }, 30), [ diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 38178cd1fe3..d395dcbf76e 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -240,7 +240,9 @@ export class ExtensionManagementChannelClient extends CommontExtensionManagement if (!thing) { return false; } + // eslint-disable-next-line local/code-no-any-casts return typeof (thing).path === 'string' && + // eslint-disable-next-line local/code-no-any-casts typeof (thing).scheme === 'string'; } diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 4e920ab4ee2..be5cd3fe912 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -402,6 +402,8 @@ function isUriComponents(thing: unknown): thing is UriComponents { if (!thing) { return false; } + // eslint-disable-next-line local/code-no-any-casts return isString((thing).path) && + // eslint-disable-next-line local/code-no-any-casts isString((thing).scheme); } diff --git a/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts index 7fdeb16a04b..bb40af0e839 100644 --- a/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts +++ b/src/vs/platform/extensionManagement/common/implicitActivationEvents.ts @@ -70,6 +70,7 @@ export class ImplicitActivationEventsImpl { // There's no generator for this extension point continue; } + // eslint-disable-next-line local/code-no-any-casts const contrib = (desc.contributes as any)[extPointName]; const contribArr = Array.isArray(contrib) ? contrib : [contrib]; try { diff --git a/src/vs/platform/files/browser/htmlFileSystemProvider.ts b/src/vs/platform/files/browser/htmlFileSystemProvider.ts index 594934e703c..43944112e16 100644 --- a/src/vs/platform/files/browser/htmlFileSystemProvider.ts +++ b/src/vs/platform/files/browser/htmlFileSystemProvider.ts @@ -308,6 +308,7 @@ export class HTMLFileSystemProvider extends Disposable implements IFileSystemPro return; } + // eslint-disable-next-line local/code-no-any-casts const observer = new (globalThis as any).FileSystemObserver((records: FileSystemObserverRecord[]) => { if (disposables.isDisposed) { return; diff --git a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts index 4af8135ca7a..17ba631493c 100644 --- a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts +++ b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts @@ -103,10 +103,13 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { const res = await super.stat(resource); if (this.invalidStatSize) { + // eslint-disable-next-line local/code-no-any-casts (res as any).size = String(res.size) as any; // for https://github.com/microsoft/vscode/issues/72909 } else if (this.smallStatSize) { + // eslint-disable-next-line local/code-no-any-casts (res as any).size = 1; } else if (this.readonly) { + // eslint-disable-next-line local/code-no-any-casts (res as any).permissions = FilePermission.Readonly; } diff --git a/src/vs/platform/hover/test/browser/nullHoverService.ts b/src/vs/platform/hover/test/browser/nullHoverService.ts index 2f846c8e225..d93ed70e234 100644 --- a/src/vs/platform/hover/test/browser/nullHoverService.ts +++ b/src/vs/platform/hover/test/browser/nullHoverService.ts @@ -13,6 +13,7 @@ export const NullHoverService: IHoverService = { showDelayedHover: () => undefined, setupDelayedHover: () => Disposable.None, setupDelayedHoverAtMouse: () => Disposable.None, + // eslint-disable-next-line local/code-no-any-casts setupManagedHover: () => Disposable.None as any, showAndFocusLastHover: () => undefined, showManagedHover: () => undefined diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index ab7ec310b21..967d4affff5 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -90,10 +90,14 @@ export interface ServiceIdentifier { } function storeServiceDependency(id: Function, target: Function, index: number): void { + // eslint-disable-next-line local/code-no-any-casts if ((target as any)[_util.DI_TARGET] === target) { + // eslint-disable-next-line local/code-no-any-casts (target as any)[_util.DI_DEPENDENCIES].push({ id, index }); } else { + // eslint-disable-next-line local/code-no-any-casts (target as any)[_util.DI_DEPENDENCIES] = [{ id, index }]; + // eslint-disable-next-line local/code-no-any-casts (target as any)[_util.DI_TARGET] = target; } } @@ -107,6 +111,7 @@ export function createDecorator(serviceId: string): ServiceIdentifier { return _util.serviceIds.get(serviceId)!; } + // eslint-disable-next-line local/code-no-any-casts const id = function (target: Function, key: string, index: number) { if (arguments.length !== 3) { throw new Error('@IServiceName-decorator can only be used to decorate a parameter'); diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index b92c820135c..93c82303dbc 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -325,6 +325,7 @@ export class InstantiationService implements IInstantiationService { // early listeners that we kept are now being subscribed to // the real service for (const [key, values] of earlyListeners) { + // eslint-disable-next-line local/code-no-any-casts const candidate = >(result)[key]; if (typeof candidate === 'function') { for (const value of values) { diff --git a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts index 0ef8a76932b..fad3a54cdd4 100644 --- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts +++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts @@ -58,6 +58,7 @@ export class TestInstantiationService extends InstantiationService implements ID const property = typeof arg2 === 'string' ? arg2 : arg3; const value = typeof arg2 === 'string' ? arg3 : arg4; + // eslint-disable-next-line local/code-no-any-casts const stubObject = this._create(serviceMock, { stub: true }, service && !property); if (property) { if (stubObject[property]) { @@ -160,6 +161,7 @@ export function createServices(disposables: DisposableStore, services: ServiceId const define = (id: ServiceIdentifier, ctorOrInstance: T | (new (...args: any[]) => T)) => { if (!serviceCollection.has(id)) { if (typeof ctorOrInstance === 'function') { + // eslint-disable-next-line local/code-no-any-casts serviceCollection.set(id, new SyncDescriptor(ctorOrInstance as any)); } else { serviceCollection.set(id, ctorOrInstance); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 27b1a2be4da..25defb486d0 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -874,6 +874,7 @@ export class WorkbenchObjectTree, TFilterData = void> @IListService listService: IListService, @IConfigurationService configurationService: IConfigurationService ) { + // eslint-disable-next-line local/code-no-any-casts const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); @@ -914,6 +915,7 @@ export class WorkbenchCompressibleObjectTree, TFilter @IListService listService: IListService, @IConfigurationService configurationService: IConfigurationService ) { + // eslint-disable-next-line local/code-no-any-casts const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, treeOptions); this.disposables.add(disposable); @@ -960,6 +962,7 @@ export class WorkbenchDataTree extends DataTree extends Async @IListService listService: IListService, @IConfigurationService configurationService: IConfigurationService ) { + // eslint-disable-next-line local/code-no-any-casts const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, delegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); @@ -1050,6 +1054,7 @@ export class WorkbenchCompressibleAsyncDataTree e @IListService listService: IListService, @IConfigurationService configurationService: IConfigurationService ) { + // eslint-disable-next-line local/code-no-any-casts const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any); super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions); this.disposables.add(disposable); diff --git a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts index 054beb4d391..d528842930c 100644 --- a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts +++ b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts @@ -876,6 +876,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { registryType: RegistryType.NODE, identifier: 'test-server', version: '1.0.0', + // eslint-disable-next-line local/code-no-any-casts runtimeArguments: [{ type: 'named', // name is intentionally missing/undefined diff --git a/src/vs/platform/observable/common/wrapInHotClass.ts b/src/vs/platform/observable/common/wrapInHotClass.ts index 73c071efe92..5111f0f52a1 100644 --- a/src/vs/platform/observable/common/wrapInHotClass.ts +++ b/src/vs/platform/observable/common/wrapInHotClass.ts @@ -9,6 +9,7 @@ import { BrandedService, IInstantiationService } from '../../instantiation/commo export function hotClassGetOriginalInstance(value: T): T { if (value instanceof BaseClass) { + // eslint-disable-next-line local/code-no-any-casts return value._instance as any; } return value; @@ -36,6 +37,7 @@ class BaseClass { } function createWrapper(clazz: IObservable, B: new (...args: T) => BaseClass) { + // eslint-disable-next-line local/code-no-any-casts return (class ReloadableWrapper extends B { private _autorun: IDisposable | undefined = undefined; diff --git a/src/vs/platform/observable/common/wrapInReloadableClass.ts b/src/vs/platform/observable/common/wrapInReloadableClass.ts index c7dff91d50b..410a87ce20b 100644 --- a/src/vs/platform/observable/common/wrapInReloadableClass.ts +++ b/src/vs/platform/observable/common/wrapInReloadableClass.ts @@ -28,12 +28,14 @@ class BaseClass { } function createWrapper(getClass: () => any, B: new (...args: T) => BaseClass) { + // eslint-disable-next-line local/code-no-any-casts return (class ReloadableWrapper extends B { private _autorun: IDisposable | undefined = undefined; override init(...params: any[]) { this._autorun = autorunWithStore((reader, store) => { const clazz = readHotReloadableExport(getClass(), reader); + // eslint-disable-next-line local/code-no-any-casts store.add(this.instantiationService.createInstance(clazz as any, ...params) as IDisposable); }); } @@ -54,6 +56,7 @@ class BaseClass0 extends BaseClass { * When the original class changes, the instance is re-created. */ export function wrapInReloadableClass1(getClass: () => Result): Result> { + // eslint-disable-next-line local/code-no-any-casts return !isHotReloadEnabled() ? getClass() as any : createWrapper(getClass, BaseClass1); } diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 67f7892e72f..21bb9c4dbb9 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -44,6 +44,7 @@ export class NativePolicyService extends AbstractPolicyService implements IPolic this.logService.trace(`NativePolicyService#_onDidPolicyChange - Updated policy values: ${JSON.stringify(update)}`); for (const key in update) { + // eslint-disable-next-line local/code-no-any-casts const value = update[key] as any; if (value === undefined) { diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 5a6c171d90c..4dac23b9ae3 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -15,6 +15,7 @@ import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandbo let product: IProductConfiguration; // Native sandbox environment +// eslint-disable-next-line local/code-no-any-casts const vscodeGlobal = (globalThis as any).vscode; if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.context !== 'undefined') { const configuration: ISandboxConfiguration | undefined = vscodeGlobal.context.configuration(); @@ -55,6 +56,7 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { else { // Built time configuration (do NOT modify) + // eslint-disable-next-line local/code-no-any-casts product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as any; // Running out of sources diff --git a/src/vs/platform/profiling/common/profilingTelemetrySpec.ts b/src/vs/platform/profiling/common/profilingTelemetrySpec.ts index 81807f9d8b7..65fde4461f5 100644 --- a/src/vs/platform/profiling/common/profilingTelemetrySpec.ts +++ b/src/vs/platform/profiling/common/profilingTelemetrySpec.ts @@ -70,6 +70,7 @@ class PerformanceError extends Error { // Since the stacks are available via the sample // we can avoid collecting them when constructing the error. if (Error.hasOwnProperty('stackTraceLimit')) { + // eslint-disable-next-line local/code-no-any-casts const Err = Error as any as { stackTraceLimit: number }; // For the monaco editor checks. const stackTraceLimit = Err.stackTraceLimit; Err.stackTraceLimit = 0; diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index e4688efd031..7bd42115367 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -70,6 +70,7 @@ export class QuickTree extends QuickInput implements I } // TODO: Fix the any casting + // eslint-disable-next-line local/code-no-any-casts get checkedLeafItems(): readonly T[] { return this.ui.tree.getCheckedLeafItems() as any as readonly T[]; } setItemTree(itemTree: T[]): void { diff --git a/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/src/vs/platform/quickinput/test/browser/quickinput.test.ts index f33759e20e3..217fef39906 100644 --- a/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -66,6 +66,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); instantiationService.stub(IListService, store.add(new ListService())); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(ILayoutService, { activeContainer: fixture, onDidLayoutContainer: Event.None } as any); instantiationService.stub(IContextViewService, store.add(instantiationService.createInstance(ContextViewService))); instantiationService.stub(IContextKeyService, store.add(instantiationService.createInstance(ContextKeyService))); diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 923db9458b4..cad70612060 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -86,6 +86,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { this._fileReader.onload = (event) => { this._isReading = false; + // eslint-disable-next-line local/code-no-any-casts const buff = (event.target).result; this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index e89c0076878..392c6fecf75 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -781,6 +781,7 @@ function safeDisposeProtocolAndSocket(protocol: PersistentProtocol): void { function getErrorFromMessage(msg: any): Error | null { if (msg && msg.type === 'error') { const error = new Error(`Connection error: ${msg.reason}`); + // eslint-disable-next-line local/code-no-any-casts (error).code = 'VSCODE_CONNECTION_ERROR'; return error; } diff --git a/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts b/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts index c085559fa6a..6ad44083e4a 100644 --- a/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts +++ b/src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts @@ -16,6 +16,7 @@ suite('RemoteAuthorityResolverService', () => { test('issue #147318: RemoteAuthorityResolverError keeps the same type', async () => { const productService: IProductService = { _serviceBrand: undefined, ...product }; + // eslint-disable-next-line local/code-no-any-casts const service = new RemoteAuthorityResolverService(productService, undefined as any); const result = service.resolveAuthority('test+x'); service._setResolvedAuthorityError('test+x', new RemoteAuthorityResolverError('something', RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable)); diff --git a/src/vs/platform/request/electron-utility/requestService.ts b/src/vs/platform/request/electron-utility/requestService.ts index eb3097b2e94..8dc7febd5f6 100644 --- a/src/vs/platform/request/electron-utility/requestService.ts +++ b/src/vs/platform/request/electron-utility/requestService.ts @@ -12,6 +12,7 @@ import { INativeEnvironmentService } from '../../environment/common/environment. import { ILogService } from '../../log/common/log.js'; function getRawRequest(options: IRequestOptions): IRawRequestFunction { + // eslint-disable-next-line local/code-no-any-casts return net.request as any as IRawRequestFunction; } diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 08c430e020c..ce4927e5820 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -278,6 +278,7 @@ suite('TelemetryService', () => { const errorTelemetry = new ErrorTelemetry(service); const testError = new Error('test'); + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)('Error Message', 'file.js', 2, 42, testError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -308,6 +309,7 @@ suite('TelemetryService', () => { const personInfoWithSpaces = settings.personalInfo.slice(0, 2) + ' ' + settings.personalInfo.slice(2); const dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -331,6 +333,7 @@ suite('TelemetryService', () => { let dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -338,6 +341,7 @@ suite('TelemetryService', () => { dangerousFilenameError = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 2); @@ -389,6 +393,7 @@ suite('TelemetryService', () => { const dangerousPathWithoutImportantInfoError: any = new Error('dangerousPathWithoutImportantInfo'); dangerousPathWithoutImportantInfoError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)(settings.dangerousPathWithoutImportantInfo, 'test.js', 2, 42, dangerousPathWithoutImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -451,6 +456,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -550,6 +556,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -614,6 +621,7 @@ suite('TelemetryService', () => { const missingModelError: any = new Error('missingModelMessage'); missingModelError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)(settings.missingModelMessage, 'test.js', 2, 42, missingModelError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -683,6 +691,7 @@ suite('TelemetryService', () => { const noSuchFileError: any = new Error('noSuchFileMessage'); noSuchFileError.stack = settings.stack; + // eslint-disable-next-line local/code-no-any-casts (mainWindow.onerror)(settings.noSuchFileMessage, 'test.js', 2, 42, noSuchFileError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); @@ -726,6 +735,7 @@ suite('TelemetryService', () => { }, new class extends TestConfigurationService { override onDidChangeConfiguration = emitter.event; override getValue() { + // eslint-disable-next-line local/code-no-any-casts return telemetryLevel as any; } }(), TestProductService); diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 1ed112a14e7..7b1cbd9702a 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -641,6 +641,7 @@ class WindowsPtyHeuristics extends Disposable { // function an embedder could easily do damage with. Additionally, this // can't really be upstreamed since the event relies on shell integration to // verify the shifting is necessary. + // eslint-disable-next-line local/code-no-any-casts (this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({ index: this._terminal.buffer.active.baseY, amount: potentialShiftedLineCount diff --git a/src/vs/platform/terminal/common/terminalProfiles.ts b/src/vs/platform/terminal/common/terminalProfiles.ts index a41257542b0..9f308a37041 100644 --- a/src/vs/platform/terminal/common/terminalProfiles.ts +++ b/src/vs/platform/terminal/common/terminalProfiles.ts @@ -116,6 +116,8 @@ export function isUriComponents(thing: unknown): thing is UriComponents { if (!thing) { return false; } + // eslint-disable-next-line local/code-no-any-casts return typeof (thing).path === 'string' && + // eslint-disable-next-line local/code-no-any-casts typeof (thing).scheme === 'string'; } diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index d6a7663480e..ad6408d5b61 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -542,6 +542,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess const object = this._writeQueue.shift()!; this._logService.trace('node-pty.IPty#write', object.data); if (object.isBinary) { + // eslint-disable-next-line local/code-no-any-casts this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any); } else { this._ptyProcess!.write(object.data); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 6ddbebf5644..69f2e56e67a 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -57,9 +57,11 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen for (const extension of extensions) { // #region Migration from v1 (enabled -> disabled) if (syncData.version === 1) { + // eslint-disable-next-line local/code-no-any-casts if ((extension).enabled === false) { extension.disabled = true; } + // eslint-disable-next-line local/code-no-any-casts delete (extension).enabled; } // #endregion diff --git a/src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts b/src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts index f82ef6c0279..f9d58c68a86 100644 --- a/src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts +++ b/src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts @@ -19,6 +19,7 @@ suite('CDP Accessibility Domain', () => { function createAXProperty(name: string, value: any, type: AXValueType = 'string'): AXProperty { return { + // eslint-disable-next-line local/code-no-any-casts name: name as any, value: createAXValue(type, value) }; diff --git a/src/vs/server/node/extensionHostConnection.ts b/src/vs/server/node/extensionHostConnection.ts index 4d8003a10ef..5c2c38b010b 100644 --- a/src/vs/server/node/extensionHostConnection.ts +++ b/src/vs/server/node/extensionHostConnection.ts @@ -237,6 +237,7 @@ export class ExtensionHostConnection extends Disposable { public async start(startParams: IRemoteExtensionHostStartParams): Promise { try { let execArgv: string[] = process.execArgv ? process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)) : []; + // eslint-disable-next-line local/code-no-any-casts if (startParams.port && !(process).pkg) { execArgv = [ `--inspect${startParams.break ? '-brk' : ''}=${startParams.port}`, diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index ad45bbabbed..e7949d36f3d 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -762,8 +762,11 @@ export async function createServer(address: string | net.AddressInfo | null, arg perf.mark('code/server/ready'); const currentTime = performance.now(); + // eslint-disable-next-line local/code-no-any-casts const vscodeServerStartTime: number = (global).vscodeServerStartTime; + // eslint-disable-next-line local/code-no-any-casts const vscodeServerListenTime: number = (global).vscodeServerListenTime; + // eslint-disable-next-line local/code-no-any-casts const vscodeServerCodeLoadedTime: number = (global).vscodeServerCodeLoadedTime; instantiationService.invokeFunction(async (accessor) => { diff --git a/src/vs/server/node/server.main.ts b/src/vs/server/node/server.main.ts index f53f9eefcf6..843817c6787 100644 --- a/src/vs/server/node/server.main.ts +++ b/src/vs/server/node/server.main.ts @@ -17,6 +17,7 @@ import product from '../../platform/product/common/product.js'; import * as perf from '../../base/common/performance.js'; perf.mark('code/server/codeLoaded'); +// eslint-disable-next-line local/code-no-any-casts (global).vscodeServerCodeLoadedTime = performance.now(); const errorReporter: ErrorReporter = { diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index c7ee8e4650e..b88652e742a 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -358,6 +358,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA kind: CompletionItemKind.Text, detail: v.detail, documentation: v.documentation, + // eslint-disable-next-line local/code-no-any-casts command: { id: AddDynamicVariableAction.ID, title: '', arguments: [{ id: v.id, widget, range: rangeAfterInsert, variableData: revive(v.value) as any, command: v.command } satisfies IAddDynamicVariableContext] } } satisfies CompletionItem; }); diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index faf19a0dce2..67dc8c1bbfb 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -54,6 +54,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha internalExtHostContext._setExtensionHostProxy( new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService)) ); + // eslint-disable-next-line local/code-no-any-casts internalExtHostContext._setAllMainProxyIdentifiers(Object.keys(MainContext).map((key) => (MainContext)[key])); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 9dcc385dd23..67b7f1120af 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -997,6 +997,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread outgoing.forEach(value => { value.to = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.to); }); + // eslint-disable-next-line local/code-no-any-casts return outgoing; }, provideIncomingCalls: async (item, token) => { @@ -1007,6 +1008,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread incoming.forEach(value => { value.from = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.from); }); + // eslint-disable-next-line local/code-no-any-casts return incoming; } })); diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index eefc335a079..9312d156352 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -192,12 +192,15 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } handlesToItems.set(item.handle, item); }); + // eslint-disable-next-line local/code-no-any-casts (input as any)[param] = params[param]; } else if (param === 'activeItems' || param === 'selectedItems') { + // eslint-disable-next-line local/code-no-any-casts (input as any)[param] = params[param] .filter((handle: number) => handlesToItems.has(handle)) .map((handle: number) => handlesToItems.get(handle)); } else if (param === 'buttons') { + // eslint-disable-next-line local/code-no-any-casts (input as any)[param] = params.buttons!.map(button => { if (button.handle === -1) { return this._quickInputService.backButton; @@ -210,6 +213,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { return button; }); } else { + // eslint-disable-next-line local/code-no-any-casts (input as any)[param] = params[param]; } } diff --git a/src/vs/workbench/api/browser/mainThreadTelemetry.ts b/src/vs/workbench/api/browser/mainThreadTelemetry.ts index ab72a2abdf8..0ea93af7fef 100644 --- a/src/vs/workbench/api/browser/mainThreadTelemetry.ts +++ b/src/vs/workbench/api/browser/mainThreadTelemetry.ts @@ -55,6 +55,7 @@ export class MainThreadTelemetry extends Disposable implements MainThreadTelemet } $publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): void { + // eslint-disable-next-line local/code-no-any-casts this.$publicLog(eventName, data as any); } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index ac6eb174bfe..ba8801fcdef 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -375,6 +375,7 @@ class TreeViewDataProvider implements ITreeViewDataProvider { const properties = distinct([...Object.keys(current instanceof ResolvableTreeItem ? current.asTreeItem() : current), ...Object.keys(treeItem)]); for (const property of properties) { + // eslint-disable-next-line local/code-no-any-casts (current)[property] = (treeItem)[property]; } if (current instanceof ResolvableTreeItem) { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 0674b8ff0f6..3c2ba18b1b1 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -525,6 +525,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { extensionId: extension.description.identifier, originalContainerId: key, group: item.group, + // eslint-disable-next-line local/code-no-any-casts remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated virtualWorkspace: item.virtualWorkspace, hideByDefault: initialVisibility === InitialVisibility.Hidden, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 690e0ef0479..cac3d4cc155 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -158,6 +158,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); + // eslint-disable-next-line local/code-no-any-casts rpcProtocol.set(ExtHostContext.ExtHostLogLevelServiceShape, extHostLoggerService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); @@ -311,6 +312,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I if (options?.authorizationServer) { checkProposedApiEnabled(extension, 'authIssuers'); } + // eslint-disable-next-line local/code-no-any-casts return extHostAuthentication.getSession(extension, providerId, scopesOrChallenge, options as any); }, getAccounts(providerId: string) { @@ -319,6 +321,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // TODO: remove this after GHPR and Codespaces move off of it async hasSession(providerId: string, scopes: readonly string[]) { checkProposedApiEnabled(extension, 'authSession'); + // eslint-disable-next-line local/code-no-any-casts return !!(await extHostAuthentication.getSession(extension, providerId, scopes, { silent: true } as any)); }, get onDidChangeSessions(): vscode.Event { @@ -557,6 +560,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return _asExtensionEvent(extHostDiagnostics.onDidChangeDiagnostics); }, getDiagnostics: (resource?: vscode.Uri) => { + // eslint-disable-next-line local/code-no-any-casts return extHostDiagnostics.getDiagnostics(resource); }, getLanguages(): Thenable { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2359a09ee3d..d989f3567f9 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2103,7 +2103,9 @@ export class IdObject { _id?: number; private static _n = 0; static mixin(object: T): T & IdObject { + // eslint-disable-next-line local/code-no-any-casts (object)._id = IdObject._n++; + // eslint-disable-next-line local/code-no-any-casts return object; } } diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 3f1ac7829cb..45585a81c67 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -669,6 +669,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo proxy.$registerCommentController(this.handle, _id, _label, this._extension.identifier.value); const that = this; + // eslint-disable-next-line local/code-no-any-casts this.value = Object.freeze({ id: that.id, label: that.label, diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 77fd72afb3e..674854b6f7e 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -240,6 +240,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I public asDebugSourceUri(src: vscode.DebugProtocolSource, session?: vscode.DebugSession): URI { + // eslint-disable-next-line local/code-no-any-casts const source = src; if (typeof source.sourceReference === 'number' && source.sourceReference > 0) { @@ -487,8 +488,11 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I }, // Check debugUI for back-compat, #147264 + // eslint-disable-next-line local/code-no-any-casts suppressDebugStatusbar: options.suppressDebugStatusbar ?? (options as any).debugUI?.simple, + // eslint-disable-next-line local/code-no-any-casts suppressDebugToolbar: options.suppressDebugToolbar ?? (options as any).debugUI?.simple, + // eslint-disable-next-line local/code-no-any-casts suppressDebugView: options.suppressDebugView ?? (options as any).debugUI?.simple, }); } @@ -659,6 +663,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I // Try to catch details for #233167 message = convertToVSCPaths(message, true); } catch (e) { + // eslint-disable-next-line local/code-no-any-casts const type = message.type + '_' + ((message as any).command ?? (message as any).event ?? ''); this._telemetryProxy.$publicLog2('debugProtocolMessageError', { type, from: session.type }); throw e; @@ -762,6 +767,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I const bp = this._breakpoints.get(bpd.id); if (bp) { if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { + // eslint-disable-next-line local/code-no-any-casts const fbp = bp; fbp.enabled = bpd.enabled; fbp.condition = bpd.condition; @@ -769,6 +775,7 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I fbp.logMessage = bpd.logMessage; fbp.functionName = bpd.functionName; } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { + // eslint-disable-next-line local/code-no-any-casts const sbp = bp; sbp.enabled = bpd.enabled; sbp.condition = bpd.condition; diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 69787c4ade6..f46aa4b4686 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -62,6 +62,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic break; } const document = this._documents.getDocument(resource); + // eslint-disable-next-line local/code-no-any-casts const success = await this._deliverEventAsyncAndBlameBadListeners(listener, { document, reason: TextDocumentSaveReason.to(reason) }); results.push(success); } diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 20f8efbfdbd..40fa0856de8 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -402,6 +402,7 @@ class ActivationOperation { if (dep.value && dep.value.activationFailed) { // Error condition 2: a dependency has already failed activation const error = new Error(`Cannot activate the '${this.friendlyName}' extension because its dependency '${dep.friendlyName}' failed to activate`); + // eslint-disable-next-line local/code-no-any-casts (error).detail = dep.value.activationFailedError; this._value = new FailedExtension(error); this._host.onExtensionActivationError(this._id, error, null); diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index a439fdc055a..f962eaa70fe 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -563,6 +563,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme messagePort.start(); messagePassingProtocol = { onDidReceiveMessage, + // eslint-disable-next-line local/code-no-any-casts postMessage: messagePort.postMessage.bind(messagePort) as any }; } @@ -1005,6 +1006,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } public async $startExtensionHost(extensionsDelta: IExtensionDescriptionDelta): Promise { + // eslint-disable-next-line local/code-no-any-casts extensionsDelta.toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); const { globalRegistry, myExtensions } = applyExtensionsDelta(this._activationEventsReader, this._globalRegistry, this._myRegistry, extensionsDelta); @@ -1045,6 +1047,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } public async $deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise { + // eslint-disable-next-line local/code-no-any-casts extensionsDelta.toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); // First build up and update the trie and only afterwards apply the delta diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index e922ec87aaf..6153a52c648 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -194,6 +194,7 @@ class CodeLensAdapter { function convertToLocationLinks(value: vscode.Location | vscode.Location[] | vscode.LocationLink[] | undefined | null): languages.LocationLink[] { if (Array.isArray(value)) { + // eslint-disable-next-line local/code-no-any-casts return (value).map(typeConvert.DefinitionLink.from); } else if (value) { return [typeConvert.DefinitionLink.from(value)]; @@ -710,6 +711,7 @@ class DocumentFormattingAdapter { const document = this._documents.getDocument(resource); + // eslint-disable-next-line local/code-no-any-casts const value = await this._provider.provideDocumentFormattingEdits(document, options, token); if (Array.isArray(value)) { return value.map(typeConvert.TextEdit.from); @@ -730,6 +732,7 @@ class RangeFormattingAdapter { const document = this._documents.getDocument(resource); const ran = typeConvert.Range.to(range); + // eslint-disable-next-line local/code-no-any-casts const value = await this._provider.provideDocumentRangeFormattingEdits(document, ran, options, token); if (Array.isArray(value)) { return value.map(typeConvert.TextEdit.from); @@ -742,6 +745,7 @@ class RangeFormattingAdapter { const document = this._documents.getDocument(resource); const _ranges = ranges.map(typeConvert.Range.to); + // eslint-disable-next-line local/code-no-any-casts const value = await this._provider.provideDocumentRangesFormattingEdits(document, _ranges, options, token); if (Array.isArray(value)) { return value.map(typeConvert.TextEdit.from); @@ -764,6 +768,7 @@ class OnTypeFormattingAdapter { const document = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); + // eslint-disable-next-line local/code-no-any-casts const value = await this._provider.provideOnTypeFormattingEdits(document, pos, ch, options, token); if (Array.isArray(value)) { return value.map(typeConvert.TextEdit.from); diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index a2a2f8377ce..40acc9265f1 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -218,6 +218,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape } // todo: 'any' cast because TS can't handle the overloads + // eslint-disable-next-line local/code-no-any-casts const extensionResult = await raceCancellation(Promise.resolve((item.tool.invoke as any)(options, token, progress!)), token); if (!extensionResult) { throw new CancellationError(); diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index e94dac6acb0..1889789fc4c 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -171,6 +171,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } this._clearModelCache(vendor); // TODO @lramos15 - Remove this old prepare method support in debt week + // eslint-disable-next-line local/code-no-any-casts const modelInformation: vscode.LanguageModelChatInformation[] = (data.provider.provideLanguageModelChatInformation ? await data.provider.provideLanguageModelChatInformation(options, token) : await (data.provider as any).prepareLanguageModelChatInformation(options, token)) ?? []; const modelMetadataAndIdentifier: ILanguageModelChatMetadataAndIdentifier[] = modelInformation.map((m): ILanguageModelChatMetadataAndIdentifier => { let auth; diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 0b6232a4342..0c6baa4ede8 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -160,10 +160,14 @@ export class ExtHostMcpService extends Disposable implements IExtHostMpcService store.add(provider.onDidChangeMcpServerDefinitions(update)); } // todo@connor4312: proposed API back-compat + // eslint-disable-next-line local/code-no-any-casts if ((provider as any).onDidChangeServerDefinitions) { + // eslint-disable-next-line local/code-no-any-casts store.add((provider as any).onDidChangeServerDefinitions(update)); } + // eslint-disable-next-line local/code-no-any-casts if ((provider as any).onDidChange) { + // eslint-disable-next-line local/code-no-any-casts store.add((provider as any).onDidChange(update)); } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 3988f4adc5e..79e44be2273 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -371,6 +371,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) { checkProposedApiEnabled(this._extension, 'scmValidation'); + // eslint-disable-next-line local/code-no-any-casts this.#proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index 4dc474ba575..f8e765ec94d 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -218,6 +218,7 @@ export class ExtHostSearch implements IExtHostSearch { export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : U extends IRawAITextQuery ? IAITextQuery : IFileQuery { return { + // eslint-disable-next-line local/code-no-any-casts ...rawQuery, // TODO@rob ??? ...{ folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 3c6ccabfbcb..9730650d1fa 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -1284,6 +1284,7 @@ function convertMutator(mutator: IEnvironmentVariableMutator): vscode.Environmen const newMutator = { ...mutator }; delete newMutator.scope; newMutator.options = newMutator.options ?? undefined; + // eslint-disable-next-line local/code-no-any-casts delete (newMutator as any).variable; return newMutator as vscode.EnvironmentVariableMutator; } diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index bd631ddb10b..c179fdfc1ed 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -157,12 +157,16 @@ export class ExtHostTimeline implements IExtHostTimeline { tooltip = props.tooltip; } // TODO @jkearl, remove once migration complete. + // eslint-disable-next-line local/code-no-any-casts else if (MarkdownStringType.isMarkdownString((props as any).detail)) { console.warn('Using deprecated TimelineItem.detail, migrate to TimelineItem.tooltip'); + // eslint-disable-next-line local/code-no-any-casts tooltip = MarkdownString.from((props as any).detail); } + // eslint-disable-next-line local/code-no-any-casts else if (isString((props as any).detail)) { console.warn('Using deprecated TimelineItem.detail, migrate to TimelineItem.tooltip'); + // eslint-disable-next-line local/code-no-any-casts tooltip = (props as any).detail; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ecaf6a847be..b5e9cdcfff7 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -130,6 +130,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe providedAttributes = await provider.provider.providePortAttributes({ port, pid, commandLine }, cancellationToken); } catch (e) { // Call with old signature for breaking API change + // eslint-disable-next-line local/code-no-any-casts providedAttributes = await (provider.provider.providePortAttributes as any as (port: number, pid: number | undefined, commandLine: string | undefined, token: vscode.CancellationToken) => vscode.ProviderResult)(port, pid, commandLine, cancellationToken); } return { providedAttributes, port }; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 588b13376ff..e1fd1f64351 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -459,6 +459,7 @@ export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.Deco hoverMessage: Array.isArray(r.hoverMessage) ? MarkdownString.fromMany(r.hoverMessage) : (r.hoverMessage ? MarkdownString.from(r.hoverMessage) : undefined), + // eslint-disable-next-line local/code-no-any-casts renderOptions: /* URI vs Uri */r.renderOptions }; }); @@ -851,6 +852,7 @@ export namespace DocumentSymbol { result.tags = info.tags.map(SymbolTag.to); } if (info.children) { + // eslint-disable-next-line local/code-no-any-casts result.children = info.children.map(to) as any; } return result; @@ -3136,19 +3138,28 @@ export namespace ChatAgentRequest { }; if (!isProposedApiEnabled(extension, 'chatParticipantPrivate')) { + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).id; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).attempt; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).enableCommandDetection; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).isParticipantDetected; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).location; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).location2; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).editedFileEvents; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).sessionId; } if (!isProposedApiEnabled(extension, 'chatParticipantAdditions')) { delete requestWithAllProps.acceptedConfirmationData; delete requestWithAllProps.rejectedConfirmationData; + // eslint-disable-next-line local/code-no-any-casts delete (requestWithAllProps as any).tools; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0ff82572925..ff5bf7abeb4 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3172,6 +3172,7 @@ export class ChatResponseAnchorPart implements vscode.ChatResponseAnchorPart { resolve?(token: vscode.CancellationToken): Thenable; constructor(value: vscode.Uri | vscode.Location | vscode.SymbolInformation, title?: string) { + // eslint-disable-next-line local/code-no-any-casts this.value = value as any; this.value2 = value; this.title = title; diff --git a/src/vs/workbench/api/node/extHostConsoleForwarder.ts b/src/vs/workbench/api/node/extHostConsoleForwarder.ts index aa2dbca286c..81efbb88b61 100644 --- a/src/vs/workbench/api/node/extHostConsoleForwarder.ts +++ b/src/vs/workbench/api/node/extHostConsoleForwarder.ts @@ -28,6 +28,7 @@ export class ExtHostConsoleForwarder extends AbstractExtHostConsoleForwarder { const stream = method === 'error' || method === 'warn' ? process.stderr : process.stdout; this._isMakingConsoleCall = true; stream.write(`\n${NativeLogMarkers.Start}\n`); + // eslint-disable-next-line local/code-no-any-casts original.apply(console, args as any); stream.write(`\n${NativeLogMarkers.End}\n`); this._isMakingConsoleCall = false; @@ -49,6 +50,7 @@ export class ExtHostConsoleForwarder extends AbstractExtHostConsoleForwarder { set: () => { }, get: () => (chunk: Uint8Array | string, encoding?: BufferEncoding, callback?: (err?: Error) => void) => { if (!this._isMakingConsoleCall) { + // eslint-disable-next-line local/code-no-any-casts buf += (chunk as any).toString(encoding); const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf('\n'); if (eol !== -1) { diff --git a/src/vs/workbench/api/node/extHostMcpNode.ts b/src/vs/workbench/api/node/extHostMcpNode.ts index 18fb24be348..5ae89c7a8a8 100644 --- a/src/vs/workbench/api/node/extHostMcpNode.ts +++ b/src/vs/workbench/api/node/extHostMcpNode.ts @@ -54,6 +54,7 @@ export class NodeExtHostMpcService extends ExtHostMcpService { private async startNodeMpc(id: number, launch: McpServerTransportStdio, defaultCwd?: URI): Promise { const onError = (err: Error | string) => this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Error, + // eslint-disable-next-line local/code-no-any-casts code: err.hasOwnProperty('code') ? String((err as any).code) : undefined, message: typeof err === 'string' ? err : err.message, }); diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 4dfcfbe3b3d..2f5e8f2c243 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -104,6 +104,7 @@ function patchProcess(allowExit: boolean) { } as (code?: number) => never; // override Electron's process.crash() method + // eslint-disable-next-line local/code-no-any-casts (process as any /* bypass layer checker */).crash = function () { const err = new Error('An extension called process.crash() and this was prevented.'); console.warn(err.stack); @@ -115,6 +116,7 @@ function patchProcess(allowExit: boolean) { // Refs https://github.com/microsoft/vscode/issues/151012#issuecomment-1156593228 process.env['ELECTRON_RUN_AS_NODE'] = '1'; + // eslint-disable-next-line local/code-no-any-casts process.on = function (event: string, listener: (...args: any[]) => void) { if (event === 'uncaughtException') { const actualListener = listener; diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index 6add965e89d..685c8a17f84 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -89,8 +89,10 @@ export function connectProxyResolver( promises.push(certs); } // Using https.globalAgent because it is shared with proxy.test.ts and mutable. + // eslint-disable-next-line local/code-no-any-casts if (initData.environment.extensionTestsLocationURI && (https.globalAgent as any).testCertificates?.length) { extHostLogService.trace('ProxyResolver#loadAdditionalCertificates: Loading test certificates'); + // eslint-disable-next-line local/code-no-any-casts promises.push(Promise.resolve((https.globalAgent as any).testCertificates as string[])); } return (await Promise.all(promises)).flat(); @@ -98,6 +100,7 @@ export function connectProxyResolver( env: process.env, }; const { resolveProxyWithRequest, resolveProxyURL } = createProxyResolver(params); + // eslint-disable-next-line local/code-no-any-casts const target = (proxyAgent as any).default || proxyAgent; target.resolveProxyURL = resolveProxyURL; @@ -120,10 +123,13 @@ const unsafeHeaders = [ ]; function patchGlobalFetch(params: ProxyAgentParams, configProvider: ExtHostConfigProvider, mainThreadTelemetry: MainThreadTelemetryShape, initData: IExtensionHostInitData, resolveProxyURL: (url: string) => Promise, disposables: DisposableStore) { + // eslint-disable-next-line local/code-no-any-casts if (!(globalThis as any).__vscodeOriginalFetch) { const originalFetch = globalThis.fetch; + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).__vscodeOriginalFetch = originalFetch; const patchedFetch = proxyAgent.createFetchPatch(params, originalFetch, resolveProxyURL); + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).__vscodePatchedFetch = patchedFetch; let useElectronFetch = false; if (!initData.remote.isRemote) { @@ -364,6 +370,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku cache[request] = undici; } else { const mod = lookup[request]; + // eslint-disable-next-line local/code-no-any-casts cache[request] = { ...mod }; // Copy to work around #93167. } } diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 2e10b368a64..dde4280941e 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -867,6 +867,7 @@ suite('ExtHostLanguageFeatureCommands', function () { provideCompletionItems(): any { const a = new types.CompletionItem('item1'); const b = new types.CompletionItem('item2'); + // eslint-disable-next-line local/code-no-any-casts return new types.CompletionList([a, b], true); } }, [])); diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index b2411a1b914..5df5644d0ba 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -72,6 +72,7 @@ class AuthQuickPick { } class AuthTestQuickInputService extends TestQuickInputService { override createQuickPick() { + // eslint-disable-next-line local/code-no-any-casts return new AuthQuickPick(); } } @@ -160,6 +161,7 @@ suite('ExtHostAuthentication', () => { rpcProtocol.set(MainContext.MainThreadAuthentication, disposables.add(mainInstantiationService.createInstance(MainThreadAuthentication, rpcProtocol))); rpcProtocol.set(MainContext.MainThreadWindow, disposables.add(mainInstantiationService.createInstance(MainThreadWindow, rpcProtocol))); + // eslint-disable-next-line local/code-no-any-casts const initData: IExtHostInitDataService = { environment: { appUriScheme: 'test', @@ -168,6 +170,7 @@ suite('ExtHostAuthentication', () => { } as any; extHostAuthentication = new ExtHostAuthentication( rpcProtocol, + // eslint-disable-next-line local/code-no-any-casts { environment: { appUriScheme: 'test', diff --git a/src/vs/workbench/api/test/browser/extHostCommands.test.ts b/src/vs/workbench/api/test/browser/extHostCommands.test.ts index 66e78d37251..de97d97b765 100644 --- a/src/vs/workbench/api/test/browser/extHostCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostCommands.test.ts @@ -89,6 +89,7 @@ suite('ExtHostCommands', function () { throw new Error('$executeCommand:retry'); } else { assert.strictEqual(retry, false); + // eslint-disable-next-line local/code-no-any-casts return 17; } } diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index f549ee37d3b..bb5dc1ad70f 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -747,6 +747,7 @@ suite('ExtHostConfiguration', function () { assert.ok(config.has('get')); assert.strictEqual(config.get('get'), 'get-prop'); assert.deepStrictEqual(config['get'], config.get); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => config['get'] = 'get-prop'); }); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index 5755dc3e1e8..d61c863704c 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -43,11 +43,17 @@ suite('ExtHostDocumentData', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('readonly-ness', () => { + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.uri = null); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.fileName = 'foofile'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.isDirty = false); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.isUntitled = false); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.languageId = 'dddd'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (data as any).document.lineCount = 9); }); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts index d3d02ba3ff4..40614a76d68 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts @@ -81,6 +81,7 @@ suite('ExtHostDocumentSaveParticipant', () => { sub.dispose(); assert.ok(event); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => { (event.document as any) = null!; }); }); }); diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index 294a8286dbb..8eeccbfb371 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -1065,6 +1065,7 @@ suite('ExtHostLanguageFeatures', function () { return runWithFakedTimers({ useFakeTimers: true }, async () => { disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { + // eslint-disable-next-line local/code-no-any-casts return new types.CompletionList([new types.CompletionItem('hello')], true); } }, [])); diff --git a/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts b/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts index e025bb78bd0..dfd4c9cddd0 100644 --- a/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts +++ b/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts @@ -161,7 +161,9 @@ suite('NotebookKernel', function () { const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (kernel).id = 'dd'); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (kernel).notebookType = 'dd'); assert.ok(kernel); diff --git a/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts b/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts index 4177e2bff5b..15fa57e6a47 100644 --- a/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts @@ -104,22 +104,28 @@ suite('ExtHostTelemetry', function () { }; test('Validate sender instances', function () { + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => ExtHostTelemetryLogger.validateSender(null)); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => ExtHostTelemetryLogger.validateSender(1)); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => ExtHostTelemetryLogger.validateSender({})); assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts ExtHostTelemetryLogger.validateSender({ sendErrorData: () => { }, sendEventData: true }); }); assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts ExtHostTelemetryLogger.validateSender({ sendErrorData: 123, sendEventData: () => { }, }); }); assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts ExtHostTelemetryLogger.validateSender({ sendErrorData: () => { }, sendEventData: () => { }, diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index 3b45e1073b7..a907770dc0e 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -637,6 +637,7 @@ suite('ExtHost Testing', () => { let req: TestRunRequest; let dto: TestRunDto; + // eslint-disable-next-line local/code-no-any-casts const ext: IExtensionDescription = {} as any; teardown(() => { diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index 58148a5164e..76a1152aa64 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -262,6 +262,7 @@ suite('ExtHostTextEditorOptions', () => { }); test('can change indentSize to a string number', () => { + // eslint-disable-next-line local/code-no-any-casts opts.value.indentSize = '2'; assertState(opts, { tabSize: 4, @@ -286,6 +287,7 @@ suite('ExtHostTextEditorOptions', () => { }); test('indentSize cannot request indentation detection', () => { + // eslint-disable-next-line local/code-no-any-casts opts.value.indentSize = 'auto'; assertState(opts, { tabSize: 4, @@ -322,6 +324,7 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid indentSize 3', () => { + // eslint-disable-next-line local/code-no-any-casts opts.value.indentSize = 'hello'; assertState(opts, { tabSize: 4, @@ -334,6 +337,7 @@ suite('ExtHostTextEditorOptions', () => { }); test('ignores invalid indentSize 4', () => { + // eslint-disable-next-line local/code-no-any-casts opts.value.indentSize = '-17'; assertState(opts, { tabSize: 4, diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index 997f0b33ed8..cbf59d9402e 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -768,6 +768,7 @@ suite('ExtHostTreeView', function () { function getTreeItem(key: string, highlights?: [number, number][]): TreeItem { const treeElement = getTreeElement(key); return { + // eslint-disable-next-line local/code-no-any-casts label: { label: labels[key] || key, highlights }, collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None }; diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts index 5fad853820c..c5123e1e23c 100644 --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -84,8 +84,11 @@ suite('ExtHostTypes', function () { assert.throws(() => new types.Position(0, -1)); const pos = new types.Position(0, 0); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (pos as any).line = -1); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (pos as any).character = -1); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => (pos as any).line = 12); const { line, character } = pos.toJSON(); @@ -203,7 +206,9 @@ suite('ExtHostTypes', function () { assert.throws(() => new types.Range(null!, new types.Position(0, 0))); const range = new types.Range(1, 0, 0, 0); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => { (range as any).start = null; }); + // eslint-disable-next-line local/code-no-any-casts assert.throws(() => { (range as any).start = new types.Position(0, 3); }); }); diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 1c0bec661f0..bc9a41a6ee7 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -538,6 +538,7 @@ suite('ExtHostWorkspace', function () { const sub = ws.onDidChangeWorkspace(e => { try { assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts (e).added = []; }); // assert.throws(() => { diff --git a/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts index ec2e3294f0d..a5272654c46 100644 --- a/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts @@ -112,12 +112,14 @@ suite('MainThreadAuthentication', () => { return Promise.resolve(); }, $getSessions: () => Promise.resolve([]), + // eslint-disable-next-line local/code-no-any-casts $createSession: () => Promise.resolve({} as any), $removeSession: () => Promise.resolve(), $onDidChangeAuthenticationSessions: () => Promise.resolve(), $registerDynamicAuthProvider: () => Promise.resolve('test'), $onDidChangeDynamicAuthProviderTokens: () => Promise.resolve(), $getSessionsFromChallenges: () => Promise.resolve([]), + // eslint-disable-next-line local/code-no-any-casts $createSessionFromChallenges: () => Promise.resolve({} as any), }; rpcProtocol.set(ExtHostContext.ExtHostAuthentication, mockExtHost); diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 8457e13184e..2f185ea9541 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -138,6 +138,7 @@ suite('ObservableChatSession', function () { // Verify history was loaded assert.strictEqual(session.history.length, 2); assert.strictEqual(session.history[0].type, 'request'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[0] as any).prompt, 'Previous question'); assert.strictEqual(session.history[1].type, 'response'); @@ -211,6 +212,7 @@ suite('ObservableChatSession', function () { assert.ok(session.requestHandler); + // eslint-disable-next-line local/code-no-any-casts const request = { requestId: 'req1', prompt: 'Test prompt' } as any; const progressCallback = sinon.stub(); @@ -225,6 +227,7 @@ suite('ObservableChatSession', function () { assert.ok(session.requestHandler); + // eslint-disable-next-line local/code-no-any-casts const request = { requestId: 'req1', prompt: 'Test prompt' } as any; const progressCallback = sinon.stub(); @@ -301,12 +304,16 @@ suite('ObservableChatSession', function () { // Verify all history was loaded correctly assert.strictEqual(session.history.length, 4); assert.strictEqual(session.history[0].type, 'request'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[0] as any).prompt, 'First question'); assert.strictEqual(session.history[1].type, 'response'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[1].parts[0] as any).content.value, 'First answer'); assert.strictEqual(session.history[2].type, 'request'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[2] as any).prompt, 'Second question'); assert.strictEqual(session.history[3].type, 'response'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[3].parts[0] as any).content.value, 'Second answer'); // Session should be complete since it has no capabilities @@ -498,9 +505,11 @@ suite('MainThreadChatSessions', function () { // Verify all history items are correctly loaded assert.strictEqual(session.history[0].type, 'request'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[0] as any).prompt, 'First question'); assert.strictEqual(session.history[1].type, 'response'); assert.strictEqual(session.history[2].type, 'request'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((session.history[2] as any).prompt, 'Second question'); assert.strictEqual(session.history[3].type, 'response'); diff --git a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts index 13e6341b762..e60b2fbc335 100644 --- a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts @@ -83,12 +83,14 @@ suite('MainThreadDocumentsAndEditors', () => { codeEditorService = new TestCodeEditorService(themeService); textFileService = new class extends mock() { override isDirty() { return false; } + // eslint-disable-next-line local/code-no-any-casts override files = { onDidSave: Event.None, onDidRevert: Event.None, onDidChangeDirty: Event.None, onDidChangeEncoding: Event.None }; + // eslint-disable-next-line local/code-no-any-casts override untitled = { onDidChangeEncoding: Event.None }; diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 976470f14b8..511856e6607 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -112,6 +112,7 @@ suite('MainThreadEditors', () => { services.set(IEditorGroupsService, new TestEditorGroupsService()); services.set(ITextFileService, new class extends mock() { override isDirty() { return false; } + // eslint-disable-next-line local/code-no-any-casts override files = { onDidSave: Event.None, onDidRevert: Event.None, diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index 152b128f35a..ff73ee23a0a 100644 --- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -57,6 +57,7 @@ suite('MainThreadHostTreeView', function () { const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); const viewDescriptorService = disposables.add(instantiationService.createInstance(ViewDescriptorService)); instantiationService.stub(IViewDescriptorService, viewDescriptorService); + // eslint-disable-next-line local/code-no-any-casts container = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const viewDescriptor: ITreeViewDescriptor = { id: testTreeViewId, diff --git a/src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts b/src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts index 2b9b89da225..5f3063472b3 100644 --- a/src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts +++ b/src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts @@ -83,6 +83,7 @@ suite('InternalTerminalShellIntegration', () => { } setup(() => { + // eslint-disable-next-line local/code-no-any-casts terminal = Symbol('testTerminal') as any; onDidStartTerminalShellExecution = store.add(new Emitter()); si = store.add(new InternalTerminalShellIntegration(terminal, onDidStartTerminalShellExecution)); diff --git a/src/vs/workbench/api/test/common/extensionHostMain.test.ts b/src/vs/workbench/api/test/common/extensionHostMain.test.ts index 22391abc1b5..8113e226fb0 100644 --- a/src/vs/workbench/api/test/common/extensionHostMain.test.ts +++ b/src/vs/workbench/api/test/common/extensionHostMain.test.ts @@ -77,6 +77,7 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo [IExtHostRpcService, new class extends mock() { declare readonly _serviceBrand: undefined; override getProxy(identifier: ProxyIdentifier): Proxied { + // eslint-disable-next-line local/code-no-any-casts return mainThreadExtensionsService; } }], diff --git a/src/vs/workbench/api/test/common/testRPCProtocol.ts b/src/vs/workbench/api/test/common/testRPCProtocol.ts index a85d08e4082..a794359ce4b 100644 --- a/src/vs/workbench/api/test/common/testRPCProtocol.ts +++ b/src/vs/workbench/api/test/common/testRPCProtocol.ts @@ -33,6 +33,7 @@ export function AnyCallRPCProtocol(useCalls?: { [K in keyof T]: T[K] }) { return SingleProxyRPCProtocol(new Proxy({}, { get(_target, prop: string) { if (useCalls && prop in useCalls) { + // eslint-disable-next-line local/code-no-any-casts return (useCalls as any)[prop]; } return () => Promise.resolve(undefined); @@ -159,6 +160,7 @@ function simulateWireTransfer(obj: T): T { } if (Array.isArray(obj)) { + // eslint-disable-next-line local/code-no-any-casts return obj.map(simulateWireTransfer) as any; } diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 4abd86979a5..9ec2c6bf86d 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -174,6 +174,7 @@ suite('ExtHostSearch', () => { }, logService ); + // eslint-disable-next-line local/code-no-any-casts this._pfs = mockPFS as any; } @@ -1000,6 +1001,7 @@ suite('ExtHostSearch', () => { }); test('basic sibling clause', async () => { + // eslint-disable-next-line local/code-no-any-casts (mockPFS as any).Promises = { readdir: (_path: string): any => { if (_path === rootFolderA.fsPath) { @@ -1045,6 +1047,7 @@ suite('ExtHostSearch', () => { }); test('multiroot sibling clause', async () => { + // eslint-disable-next-line local/code-no-any-casts (mockPFS as any).Promises = { readdir: (_path: string): any => { if (_path === joinPath(rootFolderA, 'folder').fsPath) { diff --git a/src/vs/workbench/api/worker/extHostConsoleForwarder.ts b/src/vs/workbench/api/worker/extHostConsoleForwarder.ts index 8cfde44fad7..01814a7d969 100644 --- a/src/vs/workbench/api/worker/extHostConsoleForwarder.ts +++ b/src/vs/workbench/api/worker/extHostConsoleForwarder.ts @@ -17,6 +17,7 @@ export class ExtHostConsoleForwarder extends AbstractExtHostConsoleForwarder { } protected override _nativeConsoleLogMessage(_method: unknown, original: (...args: any[]) => void, args: IArguments) { + // eslint-disable-next-line local/code-no-any-casts original.apply(console, args as any); } } diff --git a/src/vs/workbench/api/worker/extensionHostWorker.ts b/src/vs/workbench/api/worker/extensionHostWorker.ts index ef9b56b6951..7df83c7fba6 100644 --- a/src/vs/workbench/api/worker/extensionHostWorker.ts +++ b/src/vs/workbench/api/worker/extensionHostWorker.ts @@ -80,19 +80,30 @@ self.importScripts = () => { throw new Error(`'importScripts' has been blocked`) // const nativeAddEventListener = addEventListener.bind(self); self.addEventListener = () => console.trace(`'addEventListener' has been blocked`); +// eslint-disable-next-line local/code-no-any-casts (self)['AMDLoader'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['NLSLoaderPlugin'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['define'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['require'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['webkitRequestFileSystem'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['webkitRequestFileSystemSync'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['webkitResolveLocalFileSystemSyncURL'] = undefined; +// eslint-disable-next-line local/code-no-any-casts (self)['webkitResolveLocalFileSystemURL'] = undefined; +// eslint-disable-next-line local/code-no-any-casts if ((self).Worker) { // make sure new Worker(...) always uses blob: (to maintain current origin) + // eslint-disable-next-line local/code-no-any-casts const _Worker = (self).Worker; + // eslint-disable-next-line local/code-no-any-casts Worker = function (stringUrl: string | URL, options?: WorkerOptions) { if (/^file:/i.test(stringUrl.toString())) { stringUrl = FileAccess.uriToBrowserUri(URI.parse(stringUrl.toString())).toString(true); @@ -144,6 +155,7 @@ if ((self).Worker) { }; } else { + // eslint-disable-next-line local/code-no-any-casts (self).Worker = class extends NestedWorker { constructor(stringOrUrl: string | URL, options?: WorkerOptions) { super(nativePostMessage, stringOrUrl, { name: path.basename(stringOrUrl.toString()), ...options }); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index a80e82527ad..74d5227eb6f 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -776,6 +776,7 @@ class PolicyDiagnosticsAction extends Action2 { try { const policyServiceConstructorName = policyService.constructor.name; if (policyServiceConstructorName === 'MultiplexPolicyService') { + // eslint-disable-next-line local/code-no-any-casts const multiplexService = policyService as any; if (multiplexService.policyServices) { const componentServices = multiplexService.policyServices as ReadonlyArray; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0d7205a2973..6dd113b5524 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -425,7 +425,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(this.hostService.onDidChangeActiveWindow(() => this.onActiveWindowChanged())); // WCO changes + // eslint-disable-next-line local/code-no-any-casts if (isWeb && typeof (navigator as any).windowControlsOverlay === 'object') { + // eslint-disable-next-line local/code-no-any-casts this._register(addDisposableListener((navigator as any).windowControlsOverlay, 'geometrychange', () => this.onDidChangeWCO())); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 89b22ed44ad..94e0e278fa2 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1374,6 +1374,7 @@ export class ChangeLanguageAction extends Action2 { // If the association is already being made in the workspace, make sure to target workspace settings let target = ConfigurationTarget.USER; + // eslint-disable-next-line local/code-no-any-casts if (fileAssociationsConfig.workspaceValue && !!(fileAssociationsConfig.workspaceValue as any)[associationKey]) { target = ConfigurationTarget.WORKSPACE; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 6b9a55222c0..4f1fc0661e4 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -491,6 +491,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Check if the locale is RTL, macOS will move traffic lights in RTL locales // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo + // eslint-disable-next-line local/code-no-any-casts const localeInfo = safeIntl.Locale(platformLocale).value as any; if (localeInfo?.textInfo?.direction === 'rtl') { primaryWindowControlsLocation = 'right'; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index c87738f78b1..1b1188d09ad 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -684,6 +684,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { + // eslint-disable-next-line local/code-no-any-casts return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.staticArguments || []), options) as ViewPane; } diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 70e83ad4396..e85a9486304 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -138,6 +138,7 @@ export abstract class BaseWindow extends Disposable { // this can happen for timeouts on unfocused windows let didClear = false; + // eslint-disable-next-line local/code-no-any-casts const handle = (window as any).vscodeOriginalSetTimeout.apply(this, [(...args: unknown[]) => { if (didClear) { return; @@ -147,6 +148,7 @@ export abstract class BaseWindow extends Disposable { const timeoutDisposable = toDisposable(() => { didClear = true; + // eslint-disable-next-line local/code-no-any-casts (window as any).vscodeOriginalClearTimeout(handle); timeoutDisposables.delete(timeoutDisposable); }); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 3c056dad1c6..d069b3813a1 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -205,6 +205,7 @@ export class Workbench extends Layout { const lifecycleService = accessor.get(ILifecycleService); // TODO@Sandeep debt around cyclic dependencies + // eslint-disable-next-line local/code-no-any-casts const configurationService = accessor.get(IConfigurationService) as any; if (typeof configurationService.acquireInstantiationService === 'function') { configurationService.acquireInstantiationService(instantiationService); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 8f95c55fbb9..ff89eb074c9 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -51,6 +51,7 @@ export class ShowSignalSoundHelp extends Action2 { qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); disposables.add(qp.onDidAccept(() => { const enabledSounds = qp.selectedItems.map(i => i.signal); + // eslint-disable-next-line local/code-no-any-casts const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); for (const signal of enabledSounds) { let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); diff --git a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts index 042c3cfc9c5..75d08648c85 100644 --- a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts +++ b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts @@ -26,6 +26,7 @@ suite('BulkCellEdits', function () { const notebook = mockObject()(); notebook.uri.returns(URI.file('/project/notebook.ipynb')); + // eslint-disable-next-line local/code-no-any-casts const notebookEditorModel = mockObject()({ notebook: notebook as any }); notebookEditorModel.isReadonly.returns(false); @@ -35,6 +36,7 @@ suite('BulkCellEdits', function () { const edits = [ new ResourceNotebookCellEdit(inputUri, { index: 0, count: 1, editType: CellEditType.Replace, cells: [] }) ]; + // eslint-disable-next-line local/code-no-any-casts const bce = new BulkCellEdits(new UndoRedoGroup(), new UndoRedoSource(), progress, CancellationToken.None, edits, editorService, notebookService as any); await bce.apply(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index f7ec2eaeb4c..0a3abf6af06 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -122,6 +122,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements hasSameContent(other: IChatRendererContent): boolean { // Simple implementation that checks if it's the same type + // eslint-disable-next-line local/code-no-any-casts return (other as any).kind === 'mcpServersInteractionRequired'; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index 6a9e13397db..71c145a747a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -241,6 +241,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent hasSameContent(other: IChatRendererContent): boolean { return other.kind === 'multiDiffData' && + // eslint-disable-next-line local/code-no-any-casts (other as any).multiDiffData?.resources?.length === this.content.multiDiffData.resources.length; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index fab2b15fec6..7a1bde53981 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -85,7 +85,9 @@ export class ChatEditingService extends Disposable implements IChatEditingServic // TODO@jrieken // some ugly casting so that this service can pass itself as argument instad as service dependeny + // eslint-disable-next-line local/code-no-any-casts this._register(textModelService.registerTextModelContentProvider(ChatEditingTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingTextModelContentProvider as any, this))); + // eslint-disable-next-line local/code-no-any-casts this._register(textModelService.registerTextModelContentProvider(Schemas.chatEditingSnapshotScheme, _instantiationService.createInstance(ChatEditingSnapshotTextModelContentProvider as any, this))); this._register(this._chatService.onDidDisposeSession((e) => { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 914ea2180ea..801aa12a3b7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -123,6 +123,7 @@ export function extractTimestamp(item: IChatSessionItem): number | undefined { // For other items, timestamp might already be set if ('timestamp' in item) { + // eslint-disable-next-line local/code-no-any-casts return (item as any).timestamp; } diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index cc1148d6fcb..6beb02e2ad5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -301,6 +301,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const newViewState = viewState ?? this._widget.getViewState(); for (const [key, value] of Object.entries(newViewState)) { // Assign all props to the memento so they get saved + // eslint-disable-next-line local/code-no-any-casts (this.viewState as any)[key] = value; } } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index ad2eb8b8d4e..ebcc1b7c7da 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -735,7 +735,9 @@ export function reviveSerializedAgent(raw: ISerializableChatAgentData): IChatAge const agent = 'name' in raw ? raw : { + // eslint-disable-next-line local/code-no-any-casts ...(raw as any), + // eslint-disable-next-line local/code-no-any-casts name: (raw as any).id, }; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 0b45e54392d..f8aa8bce209 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1210,6 +1210,7 @@ function normalizeOldFields(raw: ISerializableChatDataIn): void { } } + // eslint-disable-next-line local/code-no-any-casts if ((raw.initialLocation as any) === 'editing-session') { raw.initialLocation = ChatAgentLocation.Chat; } @@ -1522,6 +1523,7 @@ export class ChatModel extends Disposable implements IChatModel { modelId: raw.modelId, }); request.shouldBeRemovedOnSend = raw.isHidden ? { requestId: raw.requestId } : raw.shouldBeRemovedOnSend; + // eslint-disable-next-line local/code-no-any-casts if (raw.response || raw.result || (raw as any).responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format reviveSerializedAgent(raw.agent) : undefined; @@ -1851,6 +1853,7 @@ export class ChatModel extends Disposable implements IChatModel { metadata: item.metadata }; } else { + // eslint-disable-next-line local/code-no-any-casts return item as any; // TODO } }) diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index fba7f476521..5c87741a8d2 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -233,6 +233,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { return false; } + // eslint-disable-next-line local/code-no-any-casts const mode = data as any; return typeof mode.id === 'string' && typeof mode.name === 'string' && diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 43e571cd16a..dc101c553ca 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -436,6 +436,7 @@ export class ChatService extends Disposable implements IChatService { // This handles the case where getName() is called before initialization completes // Access the internal synchronous index method via reflection // This is a workaround for the timing issue where initialization hasn't completed + // eslint-disable-next-line local/code-no-any-casts const internalGetIndex = (this._chatSessionStore as any).internalGetIndex; if (typeof internalGetIndex === 'function') { const indexData = internalGetIndex.call(this._chatSessionStore); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts index 42270fb0be7..4a9b629ae7b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts @@ -27,10 +27,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { }); ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } @@ -58,6 +60,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { ]; const result = adjustCellDiffForKeepingAnInsertedCell(0, + // eslint-disable-next-line local/code-no-any-casts cellsDiffInfo, {} as any, applyEdits, createModifiedCellDiffInfo); @@ -100,6 +103,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { ]; const result = adjustCellDiffForKeepingAnInsertedCell(0, + // eslint-disable-next-line local/code-no-any-casts cellsDiffInfo, {} as any, applyEdits, createModifiedCellDiffInfo); @@ -154,6 +158,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { ]; const result = adjustCellDiffForKeepingAnInsertedCell(2, + // eslint-disable-next-line local/code-no-any-casts cellsDiffInfo, {} as any, applyEdits, createModifiedCellDiffInfo); @@ -196,10 +201,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { }); ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } @@ -405,10 +412,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { }); ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } @@ -565,10 +574,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { }); ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } @@ -605,6 +616,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { const result = adjustCellDiffForRevertingADeletedCell(0, cellsDiffInfo, + // eslint-disable-next-line local/code-no-any-casts {} as any, applyEdits, createModifiedCellDiffInfo); @@ -653,6 +665,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { const result = adjustCellDiffForRevertingADeletedCell(1, cellsDiffInfo, + // eslint-disable-next-line local/code-no-any-casts {} as any, applyEdits, createModifiedCellDiffInfo); @@ -710,6 +723,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { const result = adjustCellDiffForRevertingADeletedCell(1, cellsDiffInfo, + // eslint-disable-next-line local/code-no-any-casts {} as any, applyEdits, createModifiedCellDiffInfo); @@ -757,10 +771,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { }); ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } @@ -771,6 +787,7 @@ suite('ChatEditingModifiedNotebookEntry', function () { function createICell(cellKind: CellKind, source: string): ICell { const handle = hash(generateUuid()); + // eslint-disable-next-line local/code-no-any-casts return { uri: URI.parse(`file:///path/${handle}`), handle, @@ -1512,10 +1529,12 @@ suite('ChatEditingModifiedNotebookEntry', function () { ensureNoDisposablesAreLeakedInTestSuite(); function createModifiedModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Modified:${id}` as any; } function createOriginalModel(id: string): ObservablePromise { + // eslint-disable-next-line local/code-no-any-casts return `Original:${id}` as any; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts index 70b3a24e3dc..f93848857d7 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts @@ -38,6 +38,7 @@ suite('ChatEditingSessionStorage', () => { fs, TestEnvironmentService, new NullLogService(), + // eslint-disable-next-line local/code-no-any-casts { getWorkspace: () => ({ id: 'workspaceId' }) } as any, ); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index 89b5721b595..4d0fc5580aa 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -32,6 +32,7 @@ suite('ChatTodoListWidget Accessibility', () => { }; // Mock the configuration service + // eslint-disable-next-line local/code-no-any-casts mockConfigurationService = { _serviceBrand: undefined, getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined @@ -140,6 +141,7 @@ suite('ChatTodoListWidget Accessibility', () => { setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; + // eslint-disable-next-line local/code-no-any-casts const emptyConfigurationService: IConfigurationService = { _serviceBrand: undefined, getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index 3bec6415498..a4e0f3ff39b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -978,6 +978,7 @@ suite('LanguageModelToolsService', () => { // Should have tool result details because alwaysDisplayInputOutput = true assert.ok(result.toolResultDetails, 'should have toolResultDetails'); + // eslint-disable-next-line local/code-no-any-casts const details = result.toolResultDetails as any; // Type assertion needed for test // Test formatToolInput - should be formatted JSON @@ -1008,6 +1009,7 @@ suite('LanguageModelToolsService', () => { configurationService: () => configurationService }, store); instaService.stub(IChatService, chatService); + // eslint-disable-next-line local/code-no-any-casts instaService.stub(ITelemetryService, testTelemetryService as any); const testService = store.add(instaService.createInstance(LanguageModelToolsService)); @@ -1514,6 +1516,7 @@ suite('LanguageModelToolsService', () => { // Change the correct configuration key configurationService.setUserConfiguration('chat.extensionTools.enabled', false); // Fire the configuration change event manually + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, affectedKeys: new Set(['chat.extensionTools.enabled']) } as any as IConfigurationChangeEvent); // Wait a bit for the scheduler diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index de488fa9e60..05e0b5ad8d1 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -45,6 +45,7 @@ suite('ChatRequestParser', () => { variableService.getDynamicVariables.returns([]); variableService.getSelectedToolAndToolSets.returns([]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatVariablesService, variableService as any); }); @@ -71,6 +72,7 @@ suite('ChatRequestParser', () => { test('slash command', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -82,6 +84,7 @@ suite('ChatRequestParser', () => { test('invalid slash command', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -93,6 +96,7 @@ suite('ChatRequestParser', () => { test('multiple slash commands', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -104,6 +108,7 @@ suite('ChatRequestParser', () => { test('slash command not first', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -115,6 +120,7 @@ suite('ChatRequestParser', () => { test('slash command after whitespace', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -126,6 +132,7 @@ suite('ChatRequestParser', () => { test('prompt slash command', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); const promptSlashCommandService = mockObject()({}); @@ -135,6 +142,7 @@ suite('ChatRequestParser', () => { } return undefined; }); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IPromptsService, promptSlashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -146,6 +154,7 @@ suite('ChatRequestParser', () => { test('prompt slash command after text', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); const promptSlashCommandService = mockObject()({}); @@ -155,6 +164,7 @@ suite('ChatRequestParser', () => { } return undefined; }); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IPromptsService, promptSlashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -166,6 +176,7 @@ suite('ChatRequestParser', () => { test('prompt slash command after slash', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); const promptSlashCommandService = mockObject()({}); @@ -175,6 +186,7 @@ suite('ChatRequestParser', () => { } return undefined; }); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IPromptsService, promptSlashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -186,6 +198,7 @@ suite('ChatRequestParser', () => { test('prompt slash command with numbers', async () => { const slashCommandService = mockObject()({}); slashCommandService.getCommands.returns([{ command: 'fix' }]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatSlashCommandService, slashCommandService as any); const promptSlashCommandService = mockObject()({}); @@ -195,6 +208,7 @@ suite('ChatRequestParser', () => { } return undefined; }); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IPromptsService, promptSlashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -239,6 +253,7 @@ suite('ChatRequestParser', () => { test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -249,6 +264,7 @@ suite('ChatRequestParser', () => { test('agents, subCommand', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -259,6 +275,7 @@ suite('ChatRequestParser', () => { test('agent but edit mode', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -269,6 +286,7 @@ suite('ChatRequestParser', () => { test('agent with question mark', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -279,6 +297,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -289,6 +308,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -299,6 +319,7 @@ suite('ChatRequestParser', () => { test('agent not first', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -309,6 +330,7 @@ suite('ChatRequestParser', () => { test('agents and tools and multiline', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); variableService.getSelectedToolAndToolSets.returns(new Map([ @@ -324,6 +346,7 @@ suite('ChatRequestParser', () => { test('agents and tools and multiline, part2', async () => { const agentsService = mockObject()({}); agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); variableService.getSelectedToolAndToolSets.returns(new Map([ diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 553b733a182..07be047ccf9 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -146,6 +146,7 @@ suite('ChatService', () => { instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IEnvironmentService, { workspaceStorageHome: URI.file('/test/path/to/workspaceStorage') }); instantiationService.stub(ILifecycleService, { onWillShutdown: Event.None }); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IUserDataProfilesService, { defaultProfile: { globalStorageHome: URI.file('/test/path/to/globalStorage') diff --git a/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts index b9346468426..66d65a99690 100644 --- a/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts @@ -13,6 +13,7 @@ suite('ManageTodoListTool Description Field Setting', () => { function getSchemaProperties(toolData: IToolData): { properties: any; required: string[] } { assert.ok(toolData.inputSchema); + // eslint-disable-next-line local/code-no-any-casts const schema = toolData.inputSchema as any; const properties = schema?.properties?.todoList?.items?.properties; const required = schema?.properties?.todoList?.items?.required; diff --git a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts index 739ae4a7706..fd04b5c4d04 100644 --- a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts @@ -165,6 +165,7 @@ export class LanguageConfigurationFileHandler extends Disposable { result = result || {}; result.lineComment = source.lineComment; } else if (types.isObject(source.lineComment)) { + // eslint-disable-next-line local/code-no-any-casts const lineCommentObj = source.lineComment as any; if (typeof lineCommentObj.comment === 'string') { result = result || {}; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index f1efc0001da..dddab1726e5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -186,12 +186,14 @@ export class CommentThreadWidget extends let hasMouse = false; let hasFocus = false; this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_ENTER, (e) => { + // eslint-disable-next-line local/code-no-any-casts if ((e).toElement === this.container) { hasMouse = true; this.updateCurrentThread(hasMouse, hasFocus); } }, true)); this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_LEAVE, (e) => { + // eslint-disable-next-line local/code-no-any-casts if ((e).fromElement === this.container) { hasMouse = false; this.updateCurrentThread(hasMouse, hasFocus); diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index 2b2216c8971..c0084c6a1b8 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -90,6 +90,7 @@ export class TestViewDescriptorService implements Partial { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) + // eslint-disable-next-line local/code-no-any-casts (child).name = null; this.appendToRepl({ output: '', expression: child, sev: outputSeverity, source }, event.body.category === 'important'); }); diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 4cd6bb2b089..dc1554b242e 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -43,6 +43,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName); } else { // Extension host debugging not supported in serverless. + // eslint-disable-next-line local/code-no-any-casts channel = { call: async () => undefined, listen: () => Event.None } as any; } diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index acdf3906e35..ecb876f695c 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -810,6 +810,7 @@ export class RawDebugSession implements IDisposable { this.notificationService.error(userMessage); } const result = new errors.ErrorNoTelemetry(userMessage); + // eslint-disable-next-line local/code-no-any-casts (result).showUser = error?.showUser; return result; diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index de511a7ed40..7eb37eaf10d 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -63,6 +63,7 @@ export function getExtensionHostDebugSession(session: IDebugSession): IDebugSess } if (type === 'vslsShare') { + // eslint-disable-next-line local/code-no-any-casts type = (session.configuration).adapterProxy.configuration.type; } diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index ac7c966538a..d77e2ff9378 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -165,6 +165,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { } get strings() { + // eslint-disable-next-line local/code-no-any-casts return this.debuggerContribution.strings ?? (this.debuggerContribution as any).uiMessages; } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 61e2ff90693..0ada6c2ae35 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -447,6 +447,7 @@ suite('Debug - Breakpoints', () => { test('updates when storage changes', () => { const storage1 = disposables.add(new TestStorageService()); const debugStorage1 = disposables.add(new MockDebugStorage(storage1)); + // eslint-disable-next-line local/code-no-any-casts const model1 = disposables.add(new DebugModel(debugStorage1, { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); // 1. create breakpoints in the first model @@ -462,6 +463,7 @@ suite('Debug - Breakpoints', () => { // 2. hydrate a new model and ensure external breakpoints get applied const storage2 = disposables.add(new TestStorageService()); + // eslint-disable-next-line local/code-no-any-casts const model2 = disposables.add(new DebugModel(disposables.add(new MockDebugStorage(storage2)), { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); storage2.store('debug.breakpoint', stored, StorageScope.WORKSPACE, StorageTarget.USER, /* external= */ true); assert.deepStrictEqual(model2.getBreakpoints().map(b => b.getId()), model1.getBreakpoints().map(b => b.getId())); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 59658c31ab6..9d34b3bf9b8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -25,6 +25,7 @@ import { Source } from '../../common/debugSource.js'; import { createMockDebugModel, mockUriIdentityService } from './mockDebugModel.js'; import { MockRawSession } from '../common/mockDebug.js'; +// eslint-disable-next-line local/code-no-any-casts const mockWorkspaceContextService = { getWorkspace: () => { return { @@ -120,6 +121,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); + // eslint-disable-next-line local/code-no-any-casts session['raw'] = mockRawSession; model.rawUpdate({ @@ -201,6 +203,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); + // eslint-disable-next-line local/code-no-any-casts session['raw'] = mockRawSession; // Stopped event with all threads stopped @@ -255,6 +258,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); + // eslint-disable-next-line local/code-no-any-casts session['raw'] = mockRawSession; // Add the threads @@ -454,6 +458,7 @@ suite('Debug - CallStack', () => { model.addSession(runningSession); model.addSession(session); + // eslint-disable-next-line local/code-no-any-casts session['raw'] = mockRawSession; model.rawUpdate({ diff --git a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts index 78daaf4650d..c9e63e55900 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts @@ -89,6 +89,7 @@ suite('debugConfigurationManager', () => { }; const resultConfig = await _debugConfigurationManager.resolveConfigurationByProviders(undefined, configurationProviderType, initialConfig, CancellationToken.None); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((resultConfig as any).configurationResolved, true, 'Configuration should be updated by test provider'); }); @@ -125,6 +126,7 @@ suite('debugConfigurationManager', () => { const resultConfig = await _debugConfigurationManager.resolveConfigurationByProviders(undefined, configurationProviderType, initialConfig, CancellationToken.None); assert.strictEqual(resultConfig!.type, secondProviderType); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((resultConfig as any).configurationResolved, true, 'Configuration should be updated by test provider'); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts index 1ab1a10d2fc..6bf8c0ded27 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts @@ -73,6 +73,7 @@ suite('Debug - Memory', () => { }); }); + // eslint-disable-next-line local/code-no-any-casts region = new MemoryRegion('ref', session as any); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts index 169e6dcd401..1548373a729 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts @@ -16,5 +16,6 @@ export const mockUriIdentityService = new UriIdentityService(fileService); export function createMockDebugModel(disposable: Pick): DebugModel { const storage = disposable.add(new TestStorageService()); const debugStorage = disposable.add(new MockDebugStorage(storage)); + // eslint-disable-next-line local/code-no-any-casts return disposable.add(new DebugModel(debugStorage, { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); } diff --git a/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts b/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts index 6aedb007bd2..d811837d433 100644 --- a/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts @@ -25,6 +25,7 @@ suite('RawDebugSession', () => { const session = new RawDebugSession( debugAdapter, + // eslint-disable-next-line local/code-no-any-casts dbgr as any as IDebugger, 'sessionId', 'name', diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 041a603c63a..8a4ab4c391d 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -136,8 +136,10 @@ suite('Debug - REPL', () => { assert.strictEqual(session.getReplElements().length, 0); model.addSession(session); + // eslint-disable-next-line local/code-no-any-casts session['raw'] = rawSession; const thread = new Thread(session, 'mockthread', 1); + // eslint-disable-next-line local/code-no-any-casts const stackFrame = new StackFrame(thread, 1, undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true); const replModel = new ReplModel(configurationService); replModel.addReplExpression(session, stackFrame, 'myVariable').then(); diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index 43b8d2cd538..cf3b44cc521 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -47,6 +47,7 @@ suite('DebugModel', () => { const topFrameDeferred = new DeferredPromise(); const wholeStackDeferred = new DeferredPromise(); const fakeThread = mockObject()({ + // eslint-disable-next-line local/code-no-any-casts session: { capabilities: { supportsDelayedStackTraceLoading: true } } as any, getCallStack: () => [], getStaleCallStack: () => [], @@ -58,6 +59,7 @@ suite('DebugModel', () => { const disposable = new DisposableStore(); const storage = disposable.add(new TestStorageService()); + // eslint-disable-next-line local/code-no-any-casts const model = new DebugModel(disposable.add(new MockDebugStorage(storage)), { isDirty: (e: any) => false }, undefined!, new NullLogService()); disposable.add(model); @@ -65,10 +67,12 @@ suite('DebugModel', () => { let whole1Resolved = false; let top2Resolved = false; let whole2Resolved = false; + // eslint-disable-next-line local/code-no-any-casts const result1 = model.refreshTopOfCallstack(fakeThread as any); result1.topCallStack.then(() => top1Resolved = true); result1.wholeCallStack.then(() => whole1Resolved = true); + // eslint-disable-next-line local/code-no-any-casts const result2 = model.refreshTopOfCallstack(fakeThread as any); result2.topCallStack.then(() => top2Resolved = true); result2.wholeCallStack.then(() => whole2Resolved = true); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 4ce86561a40..d77ad30c097 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -692,6 +692,7 @@ export class MockDebugAdapter extends AbstractDebugAdapter { export class MockDebugStorage extends DebugStorage { constructor(storageService: IStorageService) { + // eslint-disable-next-line local/code-no-any-casts super(storageService, undefined as any, undefined as any, new NullLogService()); } } diff --git a/src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts b/src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts index 4ef9639f329..5b0c1267ff0 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts @@ -12,6 +12,7 @@ export function sumByCategory(items: readonly T[], const category = getCategory(item); acc[category] = (acc[category] || 0) + getValue(item); return acc; + // eslint-disable-next-line local/code-no-any-casts }, {} as any as Record); } diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts index 28d4661ad5d..e4f74931aa8 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts @@ -144,6 +144,7 @@ export class AiEditTelemetryAdapter extends Disposable { feature, source: providerId, modelId: data.props.$modelId, + // eslint-disable-next-line local/code-no-any-casts modeId: data.props.$$mode as any, editDeltaInfo: EditDeltaInfo.fromEdit(edit, _prev), }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 700c8caabff..8574e038520 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1179,6 +1179,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension getAutoUpdateValue(): AutoUpdateConfigurationValue { const autoUpdate = this.configurationService.getValue(AutoUpdateConfigurationKey); + // eslint-disable-next-line local/code-no-any-casts if (autoUpdate === 'onlySelectedExtensions') { return false; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index b194b88322d..a085d55b892 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -96,8 +96,11 @@ function setupTest(disposables: Pick) { instantiationService.stub(IWorkbenchExtensionManagementService, { onDidInstallExtensions: didInstallEvent.event, + // eslint-disable-next-line local/code-no-any-casts onInstallExtension: installEvent.event as any, + // eslint-disable-next-line local/code-no-any-casts onUninstallExtension: uninstallEvent.event as any, + // eslint-disable-next-line local/code-no-any-casts onDidUninstallExtension: didUninstallEvent.event as any, onDidUpdateExtensionMetadata: Event.None, onDidChangeProfile: Event.None, diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index c68caf3fbbb..ade5b69a2d5 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -97,8 +97,11 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IWorkbenchExtensionManagementService, { onDidInstallExtensions: didInstallEvent.event, + // eslint-disable-next-line local/code-no-any-casts onInstallExtension: installEvent.event as any, + // eslint-disable-next-line local/code-no-any-casts onUninstallExtension: uninstallEvent.event as any, + // eslint-disable-next-line local/code-no-any-casts onDidUninstallExtension: didUninstallEvent.event as any, onDidUpdateExtensionMetadata: Event.None, onDidChangeProfile: Event.None, diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 7ccfd8c92b7..9e3db12493c 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -43,6 +43,7 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IAccessibilitySignalService, { playSignal: async () => { }, isSoundEnabled(signal: unknown) { return false; }, diff --git a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts index 7be11916f85..7a531dd70f1 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts @@ -91,6 +91,7 @@ suite('Files - ExplorerView', () => { templateDisposables: ds.add(new DisposableStore()), elementDisposables: ds.add(new DisposableStore()), contribs: [], + // eslint-disable-next-line local/code-no-any-casts label: { container: label, onDidRender: emitter.event diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index af1550e9a6c..887a70f3031 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -157,6 +157,7 @@ suite('InlineChatController', function () { [IContextKeyService, new MockContextKeyService()], [IViewsService, new class extends TestViewsService { override async openView(id: string, focus?: boolean | undefined): Promise { + // eslint-disable-next-line local/code-no-any-casts return { widget: chatWidget ?? null } as any; } }()], @@ -621,6 +622,7 @@ suite('InlineChatController', function () { store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { + // eslint-disable-next-line local/code-no-any-casts return { model: targetModel } as any; } override focusResponseItem() { } @@ -669,6 +671,7 @@ suite('InlineChatController', function () { store.add(targetModel); chatWidget = new class extends mock() { override get viewModel() { + // eslint-disable-next-line local/code-no-any-casts return { model: targetModel } as any; } override focusResponseItem() { } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index d3ac12543c3..731cc04457c 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -69,6 +69,7 @@ suite('Markdown Setting Renderer Test', () => { }; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); + // eslint-disable-next-line local/code-no-any-casts settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any, { writeText: async () => { } } as any); }); diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index eb52e00c5a5..cf3412a118d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -340,6 +340,7 @@ export class MarkersTable extends Disposable implements IProblemsWidget { // mouseover/mouseleave event handlers const onRowHover = Event.chain(this._register(new DomEmitter(list, 'mouseover')).event, $ => $.map(e => DOM.findParentWithClass(e.target as HTMLElement, 'monaco-list-row', 'monaco-list-rows')) + // eslint-disable-next-line local/code-no-any-casts .filter(((e: HTMLElement | null) => !!e) as any) .map(e => parseInt(e.getAttribute('data-index')!)) ); diff --git a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts index 1f0094ccd87..34990da558e 100644 --- a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts +++ b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts @@ -140,6 +140,7 @@ suite('MarkersModel Test', () => { const testObject = new Marker('5', marker, null!); // hack + // eslint-disable-next-line local/code-no-any-casts (testObject as any).relatedInformation = marker.relatedInformation!.map(r => new RelatedInformation('6', marker, r)); assert.strictEqual(JSON.stringify({ ...marker, resource: marker.resource.path, relatedInformation: marker.relatedInformation!.map(r => ({ ...r, resource: r.resource.path })) }, null, '\t'), testObject.toString()); }); diff --git a/src/vs/workbench/contrib/mcp/common/uriTemplate.ts b/src/vs/workbench/contrib/mcp/common/uriTemplate.ts index 601267b273a..ac2b47059dd 100644 --- a/src/vs/workbench/contrib/mcp/common/uriTemplate.ts +++ b/src/vs/workbench/contrib/mcp/common/uriTemplate.ts @@ -156,6 +156,7 @@ export class UriTemplate { const pairs: string[] = []; for (const k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { + // eslint-disable-next-line local/code-no-any-casts const thisVal = String((value as any)[k]); if (isParam) { pairs.push(k + '=' + thisVal); @@ -187,6 +188,7 @@ export class UriTemplate { for (const k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { pairs.push(k); + // eslint-disable-next-line local/code-no-any-casts pairs.push(String((value as any)[k])); } } diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts index ca6c018b64f..cd1e2b475a7 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts @@ -65,6 +65,7 @@ class TestConfigurationResolverService implements Partial { assert.strictEqual(registry.collections.get().length, 1); configurationService.setUserConfiguration(mcpAccessConfig, McpAccessValue.None); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); assert.strictEqual(registry.collections.get().length, 0); configurationService.setUserConfiguration(mcpAccessConfig, McpAccessValue.All); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); }); @@ -256,13 +259,16 @@ suite('Workbench - MCP - Registry', () => { assert.ok(connection); assert.strictEqual(connection.definition, definition); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((connection.launchDefinition as any).command, '/test/workspace/cmd'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((connection.launchDefinition as any).env.PATH, 'interactiveValue0'); connection.dispose(); const connection2 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger, trustNonceBearer }) as McpServerConnection; assert.ok(connection2); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((connection2.launchDefinition as any).env.PATH, 'interactiveValue0'); connection2.dispose(); @@ -271,6 +277,7 @@ suite('Workbench - MCP - Registry', () => { const connection3 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger, trustNonceBearer }) as McpServerConnection; assert.ok(connection3); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((connection3.launchDefinition as any).env.PATH, 'interactiveValue4'); connection3.dispose(); }); diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts index 03b60803e78..a38c8803da1 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts @@ -50,6 +50,7 @@ suite('Workbench - MCP - ResourceFilesystem', () => { const registry = new TestMcpRegistry(parentInsta1); const parentInsta2 = ds.add(parentInsta1.createChild(new ServiceCollection([IMcpRegistry, registry]))); + // eslint-disable-next-line local/code-no-any-casts const mcpService = ds.add(new McpService(parentInsta2, registry, new NullLogService(), {} as any, NullCommandService, new TestConfigurationService())); mcpService.updateCollectedServers(); diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts index 59b2cebfb42..52ecaa585bd 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts @@ -15,6 +15,7 @@ import { IMcpServer } from "../../common/mcpTypes.js"; suite("MCP - Sampling Log", () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); + // eslint-disable-next-line local/code-no-any-casts const fakeServer: IMcpServer = { definition: { id: "testServer" }, readDefinitions: () => ({ @@ -48,6 +49,7 @@ suite("MCP - Sampling Log", () => { // storage.testEmitWillSaveState(WillSaveStateReason.NONE); await storage.flush(); assert.deepStrictEqual( + // eslint-disable-next-line local/code-no-any-casts (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any), [ [ @@ -90,6 +92,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; // Verify the bin for the current day has 2 requests @@ -122,6 +125,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; // Verify the bins: day 2 should have 1 request, day 1 should have 1 request @@ -140,6 +144,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; // Verify the bins have shifted correctly @@ -160,6 +165,7 @@ suite("MCP - Sampling Log", () => { } await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; // Verify only the last 30 requests are kept @@ -211,6 +217,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; // Verify all requests are stored correctly @@ -221,6 +228,7 @@ suite("MCP - Sampling Log", () => { }); test("handles multiple servers", async () => { + // eslint-disable-next-line local/code-no-any-casts const fakeServer2: IMcpServer = { definition: { id: "testServer2" }, readDefinitions: () => ({ @@ -243,6 +251,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); + // eslint-disable-next-line local/code-no-any-casts const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any); // Verify both servers have their data stored diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index f0c63879e6b..0a104f16990 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -66,6 +66,7 @@ abstract class MergeEditorAction2 extends Action2 { return; } + // eslint-disable-next-line local/code-no-any-casts return this.runWithMergeEditor({ viewModel: vm, inputModel: activeEditorPane.inputModel.get()!, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 8c1698a42e8..fd97bc818bc 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -86,6 +86,7 @@ export function setFields(obj: T, fields: Partial): T { } export function deepMerge(source1: T, source2: Partial): T { + // eslint-disable-next-line local/code-no-any-casts const result = {} as any as T; for (const key in source1) { result[key] = source1[key]; @@ -95,6 +96,7 @@ export function deepMerge(source1: T, source2: Partial): T { if (typeof result[key] === 'object' && source2Value && typeof source2Value === 'object') { result[key] = deepMerge(result[key], source2Value); } else { + // eslint-disable-next-line local/code-no-any-casts result[key] = source2Value as any; } } @@ -115,6 +117,7 @@ export class PersistentStore { const value = this.storageService.get(this.key, StorageScope.PROFILE); if (value !== undefined) { try { + // eslint-disable-next-line local/code-no-any-casts this.value = JSON.parse(value) as any; } catch (e) { onUnexpectedError(e); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 6d986184bad..84b255c0183 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -306,6 +306,7 @@ function sendEntryTelemetry(accessor: ServicesAccessor, id: string, context?: an } function isCellToolbarContext(context?: unknown): context is INotebookCellToolbarActionContext { + // eslint-disable-next-line local/code-no-any-casts return !!context && !!(context as INotebookActionContext).notebookEditor && (context as any).$mid === MarshalledId.NotebookCellActionContext; } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts index 1172d9c0a39..0f4975a9348 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -124,6 +124,7 @@ function changeNotebookIndentation(accessor: ServicesAccessor, insertSpaces: boo })); // store the initial values of the configuration + // eslint-disable-next-line local/code-no-any-casts const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; const initialInsertSpaces = initialConfig['editor.insertSpaces']; // remove the initial values from the configuration @@ -195,6 +196,7 @@ function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: bo })).then(() => { // store the initial values of the configuration + // eslint-disable-next-line local/code-no-any-casts const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; const initialIndentSize = initialConfig['editor.indentSize']; const initialTabSize = initialConfig['editor.tabSize']; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index c4612d46048..d9523d8ecee 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -71,6 +71,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { value.token = undefined; this._disposeWidget(value.widget); value.disposableStore.dispose(); + // eslint-disable-next-line local/code-no-any-casts value.widget = (undefined); // unset the widget so that others that still hold a reference don't harm us }); })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 0453909aa0a..09527e4364f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -119,6 +119,7 @@ async function webviewPreloads(ctx: PreloadContext) { const acquireVsCodeApi = globalThis.acquireVsCodeApi; const vscode = acquireVsCodeApi(); + // eslint-disable-next-line local/code-no-any-casts delete (globalThis as any).acquireVsCodeApi; const tokenizationStyle = new CSSStyleSheet(); @@ -1458,6 +1459,7 @@ async function webviewPreloads(ctx: PreloadContext) { document.designMode = 'On'; while (find && matches.length < 500) { + // eslint-disable-next-line local/code-no-any-casts find = (window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, /* backwards*/ false, /* wrapAround*/ false, diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts index 39e40ab13f7..3edf25692ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts @@ -326,6 +326,7 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM override dispose() { super.dispose(); + // eslint-disable-next-line local/code-no-any-casts (this.foldingDelegate as any) = null; } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 813faae9352..8a0d6a442a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -167,6 +167,7 @@ export class NotebookStickyScroll extends Disposable { // Forward wheel events to the notebook editor to enable scrolling when hovering over sticky scroll this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.WHEEL, (event: WheelEvent) => { + // eslint-disable-next-line local/code-no-any-casts this.notebookCellList.triggerScrollFromMouseWheelEvent(event as any as IMouseWheelEvent); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts index b65ff515c9e..683914cab1d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts @@ -72,6 +72,7 @@ export class NotebookHorizontalTracker extends Disposable { stopPropagation: () => { } }; + // eslint-disable-next-line local/code-no-any-casts (hoveringOnEditor[1] as CodeEditorWidget).delegateScrollFromMouseWheelEvent(evt as any); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index dec98508976..87c4a6a86c6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -421,6 +421,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { */ private getSuggestedLanguage(notebookTextModel: NotebookTextModel): string | undefined { const metaData = notebookTextModel.metadata; + // eslint-disable-next-line local/code-no-any-casts let suggestedKernelLanguage: string | undefined = (metaData as any)?.metadata?.language_info?.name; // TODO how do we suggest multi language notebooks? if (!suggestedKernelLanguage) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 2b0d842ec93..21e5e9a987f 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -560,6 +560,7 @@ export function sortObjectPropertiesRecursively(obj: any): any { } if (obj !== undefined && obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0) { return ( + // eslint-disable-next-line local/code-no-any-casts Object.keys(obj) .sort() .reduce>((sortedObj, prop) => { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index bdbae0e7d4f..9638f0383db 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -1092,6 +1092,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel let k: keyof NullablePartialNotebookCellMetadata; for (k in metadata) { const value = metadata[k] ?? undefined; + // eslint-disable-next-line local/code-no-any-casts newMetadata[k] = value as any; } @@ -1133,6 +1134,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel let k: keyof NotebookCellInternalMetadata; for (k in internalMetadata) { const value = internalMetadata[k] ?? undefined; + // eslint-disable-next-line local/code-no-any-casts newInternalMetadata[k] = value as any; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts index d90a9173854..e73534da61c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts @@ -34,6 +34,7 @@ class TestNotebookEditorWidgetService extends NotebookEditorWidgetService { protected override createWidget(): NotebookEditorWidget { return new class extends mock() { override onWillHide = () => { }; + // eslint-disable-next-line local/code-no-any-casts override getDomNode = () => { return { remove: () => { } } as any; }; override dispose = () => { }; }; @@ -87,6 +88,7 @@ suite('NotebookEditorWidgetService', () => { override getPart(group: IEditorGroup | GroupIdentifier): IEditorPart; override getPart(container: unknown): IEditorPart; override getPart(container: unknown): import("../../../../services/editor/common/editorGroupsService.js").IEditorPart { + // eslint-disable-next-line local/code-no-any-casts return { windowId: 0 } as any; } }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts index 81042265ee8..179479848ae 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts @@ -69,6 +69,7 @@ suite('Notebook Outline', function () { }; + // eslint-disable-next-line local/code-no-any-casts const testOutlineEntryFactory = instantiationService.createInstance(NotebookOutlineEntryFactory) as any; testOutlineEntryFactory.cacheSymbols = async () => { symbolsCached = true; }; instantiationService.stub(INotebookOutlineEntryFactory, testOutlineEntryFactory); diff --git a/src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts b/src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts index 64dd4ba3073..93b4c7662ac 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts @@ -23,6 +23,7 @@ import { HeightOfHiddenLinesRegionInDiffEditor } from '../../../browser/diff/dif suite('NotebookDiff EditorHeightCalculator', () => { ['Hide Unchanged Regions', 'Show Unchanged Regions'].forEach(suiteTitle => { suite(suiteTitle, () => { + // eslint-disable-next-line local/code-no-any-casts const fontInfo: FontInfo = { lineHeight: 18, fontSize: 18 } as any; let disposables: DisposableStore; let textModelResolver: ITextModelService; @@ -55,6 +56,7 @@ suite('NotebookDiff EditorHeightCalculator', () => { override async createModelReference(resource: URI): Promise> { return { dispose: () => { }, + // eslint-disable-next-line local/code-no-any-casts object: { textEditorModel: resource === original ? originalModel : modifiedModel, getLanguageId: () => 'javascript', diff --git a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts index 2beb4a77644..f99212e5a43 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts @@ -649,7 +649,9 @@ suite('NotebookDiff', () => { assert.strictEqual(diffViewModel.items.length, 2); assert.strictEqual(diffViewModel.items[0].type, 'placeholder'); diffViewModel.items[0].showHiddenCells(); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((diffViewModel.items[0] as unknown as SideBySideDiffElementViewModel).original!.textModel.equal((diffViewModel.items[0] as any).modified!.textModel), true); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((diffViewModel.items[1] as any).original!.textModel.equal((diffViewModel.items[1] as any).modified!.textModel), false); await verifyChangeEventIsNotFired(diffViewModel); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts index 57344f35557..93521070365 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts @@ -47,6 +47,7 @@ suite('NotebookCellLayoutManager', () => { list.cells.push(cell); list.cells.push(cell2); const widget = new MockNotebookWidget(); + // eslint-disable-next-line local/code-no-any-casts const mgr = store.add(new NotebookCellLayoutManager(widget as any, list as any, new MockLoggingService() as any)); mgr.layoutNotebookCell(cell, 200); mgr.layoutNotebookCell(cell2, 200); @@ -62,6 +63,7 @@ suite('NotebookCellLayoutManager', () => { list.cells.push(cell); list.cells.push(cell2); const widget = new MockNotebookWidget(); + // eslint-disable-next-line local/code-no-any-casts const mgr = store.add(new NotebookCellLayoutManager(widget as any, list as any, new MockLoggingService() as any)); const promise = mgr.layoutNotebookCell(cell, 200); @@ -80,6 +82,7 @@ suite('NotebookCellLayoutManager', () => { const cell = mockCellViewModel(); const list = new MockList(); const widget = new MockNotebookWidget(); + // eslint-disable-next-line local/code-no-any-casts const mgr = store.add(new NotebookCellLayoutManager(widget as any, list as any, new MockLoggingService() as any)); await mgr.layoutNotebookCell(cell, 200); assert.strictEqual(list.elementHeight(cell), 100); @@ -90,6 +93,7 @@ suite('NotebookCellLayoutManager', () => { const list = new MockList(); list.cells.push(cell); const widget = new MockNotebookWidget(); + // eslint-disable-next-line local/code-no-any-casts const mgr = store.add(new NotebookCellLayoutManager(widget as any, list as any, new MockLoggingService() as any)); await mgr.layoutNotebookCell(cell, 100); assert.strictEqual(list.elementHeight(cell), 100); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts index 5ab18264224..302b59ba647 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts @@ -281,6 +281,7 @@ suite('NotebookFileWorkingCopyModel', function () { return Promise.resolve({ name: 'savedFile' } as IFileStatWithMetadata); } }; + // eslint-disable-next-line local/code-no-any-casts (serializer as any).test = 'yes'; let resolveSerializer: (serializer: INotebookSerializer) => void = () => { }; @@ -304,6 +305,7 @@ suite('NotebookFileWorkingCopyModel', function () { resolveSerializer(serializer); await model.getNotebookSerializer(); + // eslint-disable-next-line local/code-no-any-casts const result = await model.save?.({} as any, {} as any); assert.strictEqual(result!.name, 'savedFile'); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index c50e73242a1..962d8af9f94 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -457,6 +457,7 @@ export async function findInFilesCommand(accessor: ServicesAccessor, _args: IFin const name = entry[0]; const value = entry[1]; if (value !== undefined) { + // eslint-disable-next-line local/code-no-any-casts (args as any)[name as any] = (typeof value === 'string') ? await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, value) : value; } } diff --git a/src/vs/workbench/contrib/search/browser/searchMessage.ts b/src/vs/workbench/contrib/search/browser/searchMessage.ts index ea8990aad3e..7d92b2b46fe 100644 --- a/src/vs/workbench/contrib/search/browser/searchMessage.ts +++ b/src/vs/workbench/contrib/search/browser/searchMessage.ts @@ -48,6 +48,7 @@ export const renderSearchMessage = ( const parsed = URI.parse(href, true); if (parsed.scheme === Schemas.command && message.trusted) { const result = await commandService.executeCommand(parsed.path); + // eslint-disable-next-line local/code-no-any-casts if ((result as any)?.triggerSearch) { triggerSearch(); } diff --git a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts index 41fff64e3ab..6aafc133dd6 100644 --- a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts @@ -303,6 +303,7 @@ export class SearchModelImpl extends Disposable implements ISearchModel { this.searchResult.setCachedSearchComplete(completed, ai); const options: IPatternInfo = Object.assign({}, this._searchQuery.contentPattern); + // eslint-disable-next-line local/code-no-any-casts delete (options as any).pattern; const stats = completed && completed.stats as ITextSearchStats; diff --git a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts index fc0806baec1..67138618aa8 100644 --- a/src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts +++ b/src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts @@ -296,7 +296,9 @@ export function isTextSearchHeading(obj: any): obj is ITextSearchHeading { export function isPlainTextSearchHeading(obj: any): obj is IPlainTextSearchHeading { return isTextSearchHeading(obj) && + // eslint-disable-next-line local/code-no-any-casts typeof (obj).replace === 'function' && + // eslint-disable-next-line local/code-no-any-casts typeof (obj).replaceAll === 'function'; } @@ -313,11 +315,13 @@ export function isSearchTreeFolderMatchWithResource(obj: any): obj is ISearchTre export function isSearchTreeFolderMatchWorkspaceRoot(obj: any): obj is ISearchTreeFolderMatchWorkspaceRoot { return isSearchTreeFolderMatchWithResource(obj) && + // eslint-disable-next-line local/code-no-any-casts typeof (obj).createAndConfigureFileMatch === 'function'; } export function isSearchTreeFolderMatchNoRoot(obj: any): obj is ISearchTreeFolderMatchNoRoot { return isSearchTreeFolderMatch(obj) && + // eslint-disable-next-line local/code-no-any-casts typeof (obj).createAndConfigureFileMatch === 'function'; } diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 6991b3434ae..f8a0dea5e53 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -198,6 +198,7 @@ suite('SearchModel', () => { } return new Promise(resolve => { queueMicrotask(() => { + // eslint-disable-next-line local/code-no-any-casts resolve({}); }); }); @@ -225,6 +226,7 @@ suite('SearchModel', () => { }, asyncResults: new Promise(resolve => { queueMicrotask(() => { + // eslint-disable-next-line local/code-no-any-casts resolve({ results: [], messages: [] diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 8855942c51e..5d21181b3a1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -202,6 +202,7 @@ const translateLegacyConfig = (legacyConfig: LegacySearchEditorArgs & OpenSearch useIgnores: 'useExcludeSettingsAndIgnoreFiles', }; Object.entries(legacyConfig).forEach(([key, value]) => { + // eslint-disable-next-line local/code-no-any-casts (config as any)[(overrides as any)[key] ?? key] = value; }); return config; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index ee682018d5c..09e7f468215 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -152,6 +152,7 @@ export const openNewSearchEditor = const name = entry[0]; const value = entry[1]; if (value !== undefined) { + // eslint-disable-next-line local/code-no-any-casts (args as any)[name as any] = (typeof value === 'string') ? await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, value) : value; } } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 4c4602f388b..e514db6bb60 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -325,6 +325,7 @@ export class SearchEditorInput extends EditorInput { // Use the 'rawData' variant and pass modelUri return this.instantiationService.invokeFunction( getOrMakeSearchEditorInput, + // eslint-disable-next-line local/code-no-any-casts { from: 'rawData', config, resultsContents: results, modelUri: newModelUri } as any // modelUri is not in the type, but we handle it below ); } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index bb8b5f8f700..7531f6f5848 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -154,6 +154,7 @@ suite('SnippetsService', function () { label: 'bar', description: 'barTest' }); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 1); assert.strictEqual(result.suggestions[0].insertText, 'barCodeSnippet'); }); @@ -164,6 +165,7 @@ suite('SnippetsService', function () { label: 'bar', description: 'barTest' }); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((completions.items[0].completion.range as any).insert.startColumn, 1); assert.strictEqual(completions.items[0].completion.insertText, 'barCodeSnippet'); }); @@ -279,12 +281,14 @@ suite('SnippetsService', function () { description: 'barTest' }); assert.strictEqual(result.suggestions[0].insertText, 's1'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 5); assert.deepStrictEqual(result.suggestions[1].label, { label: 'bar-bar', description: 'name' }); assert.strictEqual(result.suggestions[1].insertText, 's2'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result.suggestions[1].range as any).insert.startColumn, 1); }); @@ -295,12 +299,14 @@ suite('SnippetsService', function () { description: 'name' }); assert.strictEqual(completions.items[0].completion.insertText, 's2'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((completions.items[0].completion.range as any).insert.startColumn, 1); assert.deepStrictEqual(completions.items[1].completion.label, { label: 'bar', description: 'barTest' }); assert.strictEqual(completions.items[1].completion.insertText, 's1'); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((completions.items[1].completion.range as any).insert.startColumn, 5); } }); @@ -331,20 +337,24 @@ suite('SnippetsService', function () { model = instantiateTextModel(instantiationService, '\t { assert.strictEqual(result.suggestions.length, 1); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2); }); const completions2 = await asCompletionModel(model, new Position(1, 4), provider); assert.strictEqual(completions2.items.length, 1); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((completions2.items[0].completion.range as any).insert.startColumn, 2); model.dispose(); model = instantiateTextModel(instantiationService, 'a { assert.strictEqual(result.suggestions.length, 1); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2); }); const completions3 = await asCompletionModel(model, new Position(1, 4), provider); assert.strictEqual(completions3.items.length, 1); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((completions3.items[0].completion.range as any).insert.startColumn, 2); model.dispose(); }); @@ -646,6 +656,7 @@ suite('SnippetsService', function () { let result = await provider.provideCompletionItems(model, new Position(1, 3), defaultCompletionContext)!; assert.strictEqual(result.suggestions.length, 1); let [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.startColumn, 2); let completions = await asCompletionModel(model, new Position(1, 3), provider); @@ -659,6 +670,7 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.startColumn, 1); assert.strictEqual(completions.items.length, 1); assert.strictEqual(completions.items[0].editStart.column, 1); @@ -686,7 +698,9 @@ suite('SnippetsService', function () { let result = await provider.provideCompletionItems(model, new Position(1, 3), defaultCompletionContext)!; assert.strictEqual(result.suggestions.length, 1); let [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.endColumn, 3); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).replace.endColumn, 9); let completions = await asCompletionModel(model, new Position(1, 3), provider); @@ -700,7 +714,9 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.endColumn, 3); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).replace.endColumn, 3); completions = await asCompletionModel(model, new Position(1, 3), provider); @@ -714,7 +730,9 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.endColumn, 1); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).replace.endColumn, 9); completions = await asCompletionModel(model, new Position(1, 1), provider); @@ -747,7 +765,9 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); const [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.endColumn, 9); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).replace.endColumn, 9); assert.strictEqual(completions.items.length, 1); @@ -787,8 +807,10 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); const [first] = result.suggestions; + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).insert.endColumn, 5); // This is 6 because it should eat the `]` at the end of the text even if cursor is before it + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual((first.range as any).replace.endColumn, 6); assert.strictEqual(completions.items.length, 1); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 916f7a3811b..18c713292d8 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1488,6 +1488,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (type === undefined) { return true; } + // eslint-disable-next-line local/code-no-any-casts const settingValueMap: IStringDictionary = settingValue as any; return !settingValueMap[type]; } @@ -1496,6 +1497,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let type: string; if (CustomTask.is(task)) { const configProperties: TaskConfig.IConfigurationProperties = task._source.config.element; + // eslint-disable-next-line local/code-no-any-casts type = (configProperties).type; } else { type = task.getDefinition()!.type; @@ -1534,6 +1536,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } let newValue: IStringDictionary; if (current !== false) { + // eslint-disable-next-line local/code-no-any-casts newValue = current; } else { newValue = Object.create(null); @@ -1579,6 +1582,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let taskType: string; if (CustomTask.is(task)) { const configProperties: TaskConfig.IConfigurationProperties = task._source.config.element; + // eslint-disable-next-line local/code-no-any-casts taskType = (configProperties).type; } else { taskType = task.getDefinition().type; @@ -1732,6 +1736,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; const identifier: TaskConfig.ITaskIdentifier = Object.assign(Object.create(null), task.defines); delete identifier['_key']; + // eslint-disable-next-line local/code-no-any-casts Object.keys(identifier).forEach(key => (toCustomize)![key] = identifier[key]); if (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length > 0 && Types.isStringArray(task.configurationProperties.problemMatchers)) { toCustomize.problemMatcher = task.configurationProperties.problemMatchers; @@ -1778,8 +1783,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const index: number | undefined = CustomTask.is(task) ? task._source.config.index : undefined; if (properties) { for (const property of Object.getOwnPropertyNames(properties)) { + // eslint-disable-next-line local/code-no-any-casts const value = (properties)[property]; if (value !== undefined && value !== null) { + // eslint-disable-next-line local/code-no-any-casts (toCustomize)[property] = value; } } @@ -2631,6 +2638,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!config) { return { config: undefined, hasParseErrors: false }; } + // eslint-disable-next-line local/code-no-any-casts const parseErrors: string[] = (config as any).$parseErrors; if (parseErrors) { let isAffected = false; @@ -2811,6 +2819,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!result) { return { config: undefined, hasParseErrors: false }; } + // eslint-disable-next-line local/code-no-any-casts const parseErrors: string[] = (result as any).$parseErrors; if (parseErrors) { let isAffected = false; @@ -3542,6 +3551,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(undefined); } content = pickTemplateResult.content; + // eslint-disable-next-line local/code-no-any-casts const editorConfig = this._configurationService.getValue() as any; if (editorConfig.editor.insertSpaces) { content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + ' '.repeat(s2.length * editorConfig.editor.tabSize)); @@ -3575,11 +3585,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private _isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } { + // eslint-disable-next-line local/code-no-any-casts const candidate: IQuickPickItem & { task: Task } = value as any; return candidate && !!candidate.task; } private _isSettingEntry(value: IQuickPickItem): value is IQuickPickItem & { settingType: string } { + // eslint-disable-next-line local/code-no-any-casts const candidate: IQuickPickItem & { settingType: string } = value as any; return candidate && !!candidate.settingType; } @@ -3708,6 +3720,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (cancellationToken.isCancellationRequested) { // canceled when there's only one task const task = (await entries)[0]; + // eslint-disable-next-line local/code-no-any-casts if ((task).task) { selection = task; } @@ -3766,6 +3779,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (cancellationToken.isCancellationRequested) { // canceled when there's only one task const task = (await entries)[0]; + // eslint-disable-next-line local/code-no-any-casts if ((task).task) { entry = task; } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 16b5b849647..962402ed537 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -358,6 +358,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { if (trim) { value = Strings.trim(value)!; } + // eslint-disable-next-line local/code-no-any-casts (data as any)[property] += endOfLine + value; } } @@ -370,6 +371,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { if (trim) { value = Strings.trim(value)!; } + // eslint-disable-next-line local/code-no-any-casts (data as any)[property] = value; } } @@ -1010,6 +1012,7 @@ export class ProblemPatternParser extends Parser { function copyProperty(result: IProblemPattern, source: Config.IProblemPattern, resultKey: keyof IProblemPattern, sourceKey: keyof Config.IProblemPattern) { const value = source[sourceKey]; if (typeof value === 'number') { + // eslint-disable-next-line local/code-no-any-casts (result as any)[resultKey] = value; } } @@ -1900,6 +1903,7 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry { } const matcher = this.get('tsc-watch'); if (matcher) { + // eslint-disable-next-line local/code-no-any-casts (matcher).tscWatch = true; } resolve(undefined); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 2f149d1f004..1921a17e026 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1717,6 +1717,7 @@ export namespace TaskParser { function isCustomTask(value: ICustomTask | IConfiguringTask): value is ICustomTask { const type = value.type; + // eslint-disable-next-line local/code-no-any-casts const customize = (value as any).customize; return customize === undefined && (type === undefined || type === null || type === Tasks.CUSTOMIZED_TASK_TYPE || type === 'shell' || type === 'process'); } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index e109f97ddf4..c4fcc2dfd54 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -652,6 +652,7 @@ export abstract class CommonTask { } public clone(): Task { + // eslint-disable-next-line local/code-no-any-casts return this.fromObject(Object.assign({}, this)); } @@ -692,6 +693,7 @@ export abstract class CommonTask { public getTaskExecution(): ITaskExecution { const result: ITaskExecution = { id: this._id, + // eslint-disable-next-line local/code-no-any-casts task: this }; return result; diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index 7a4362d3454..0e183c912cb 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -81,9 +81,12 @@ suite('Task Terminal Status', () => { instantiationService = store.add(new TestInstantiationService()); taskService = new TestTaskService(); accessibilitySignalService = new TestaccessibilitySignalService(); + // eslint-disable-next-line local/code-no-any-casts taskTerminalStatus = store.add(new TaskTerminalStatus(taskService as any, accessibilitySignalService as any)); + // eslint-disable-next-line local/code-no-any-casts testTerminal = store.add(instantiationService.createInstance(TestTerminal) as any); testTask = instantiationService.createInstance(TestTask) as unknown as Task; + // eslint-disable-next-line local/code-no-any-casts problemCollector = store.add(instantiationService.createInstance(TestProblemCollector) as any); }); test('Should add failed status when there is an exit code on task end', async () => { diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index 9706925925f..bf195729208 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -287,6 +287,7 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack return undefined; } const resolverResult = await this._remoteAuthorityResolverService.resolveAuthority(connection.remoteAuthority); + // eslint-disable-next-line local/code-no-any-casts return resolverResult.options?.extensionHostEnv as any; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index cfce9e1bf7c..fc003eb7f97 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2142,6 +2142,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (isWindows) { for (let i = this.xterm.raw.buffer.active.viewportY; i < this.xterm.raw.buffer.active.length; i++) { const line = this.xterm.raw.buffer.active.getLine(i); + // eslint-disable-next-line local/code-no-any-casts (line as any)._line.isWrapped = false; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index fd54ece4b0b..f672ba24084 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -445,6 +445,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce let baseEnv: IProcessEnvironment; if (shellLaunchConfig.useShellEnvironment) { // TODO: Avoid as any? + // eslint-disable-next-line local/code-no-any-casts baseEnv = await backend.getShellEnvironment() as any; } else { baseEnv = await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 5318ed45e39..ee3bdf69581 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -633,6 +633,7 @@ class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop 'instanceId' in (e as any)); if (terminals.length > 0) { originalEvent.dataTransfer.setData(TerminalDataTransfers.Terminals, JSON.stringify(terminals.map(e => e.resource.toString()))); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index aad2cb24393..9b1fd3bc567 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -241,6 +241,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach }, })); this._updateSmoothScrolling(); + // eslint-disable-next-line local/code-no-any-casts this._core = (this.raw as any)._core as IXtermCore; this._register(this._configurationService.onDidChangeConfiguration(async e => { diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts index 813129c08e1..4869bcb88c9 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts @@ -28,11 +28,13 @@ suite('TerminalCapabilityStore', () => { test('should fire events when capabilities are added', () => { assertEvents(addEvents, []); + // eslint-disable-next-line local/code-no-any-casts capabilityStore.add(TerminalCapability.CwdDetection, {} as any); assertEvents(addEvents, [TerminalCapability.CwdDetection]); }); test('should fire events when capabilities are removed', async () => { assertEvents(removeEvents, []); + // eslint-disable-next-line local/code-no-any-casts capabilityStore.add(TerminalCapability.CwdDetection, {} as any); assertEvents(removeEvents, []); capabilityStore.remove(TerminalCapability.CwdDetection); @@ -40,6 +42,7 @@ suite('TerminalCapabilityStore', () => { }); test('has should return whether a capability is present', () => { deepStrictEqual(capabilityStore.has(TerminalCapability.CwdDetection), false); + // eslint-disable-next-line local/code-no-any-casts capabilityStore.add(TerminalCapability.CwdDetection, {} as any); deepStrictEqual(capabilityStore.has(TerminalCapability.CwdDetection), true); capabilityStore.remove(TerminalCapability.CwdDetection); @@ -47,8 +50,10 @@ suite('TerminalCapabilityStore', () => { }); test('items should reflect current state', () => { deepStrictEqual(Array.from(capabilityStore.items), []); + // eslint-disable-next-line local/code-no-any-casts capabilityStore.add(TerminalCapability.CwdDetection, {} as any); deepStrictEqual(Array.from(capabilityStore.items), [TerminalCapability.CwdDetection]); + // eslint-disable-next-line local/code-no-any-casts capabilityStore.add(TerminalCapability.NaiveCwdDetection, {} as any); deepStrictEqual(Array.from(capabilityStore.items), [TerminalCapability.CwdDetection, TerminalCapability.NaiveCwdDetection]); capabilityStore.remove(TerminalCapability.CwdDetection); @@ -83,8 +88,10 @@ suite('TerminalCapabilityStoreMultiplexer', () => { assertEvents(addEvents, []); multiplexer.add(store1); multiplexer.add(store2); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CwdDetection, {} as any); assertEvents(addEvents, [TerminalCapability.CwdDetection]); + // eslint-disable-next-line local/code-no-any-casts store2.add(TerminalCapability.NaiveCwdDetection, {} as any); assertEvents(addEvents, [TerminalCapability.NaiveCwdDetection]); }); @@ -92,7 +99,9 @@ suite('TerminalCapabilityStoreMultiplexer', () => { assertEvents(removeEvents, []); multiplexer.add(store1); multiplexer.add(store2); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CwdDetection, {} as any); + // eslint-disable-next-line local/code-no-any-casts store2.add(TerminalCapability.NaiveCwdDetection, {} as any); assertEvents(removeEvents, []); store1.remove(TerminalCapability.CwdDetection); @@ -102,8 +111,10 @@ suite('TerminalCapabilityStoreMultiplexer', () => { }); test('should fire events when stores are added', async () => { assertEvents(addEvents, []); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CwdDetection, {} as any); assertEvents(addEvents, []); + // eslint-disable-next-line local/code-no-any-casts store2.add(TerminalCapability.NaiveCwdDetection, {} as any); multiplexer.add(store1); multiplexer.add(store2); @@ -113,9 +124,12 @@ suite('TerminalCapabilityStoreMultiplexer', () => { deepStrictEqual(Array.from(multiplexer.items).sort(), [].sort()); multiplexer.add(store1); multiplexer.add(store2); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CwdDetection, {} as any); deepStrictEqual(Array.from(multiplexer.items).sort(), [TerminalCapability.CwdDetection].sort()); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CommandDetection, {} as any); + // eslint-disable-next-line local/code-no-any-casts store2.add(TerminalCapability.NaiveCwdDetection, {} as any); deepStrictEqual(Array.from(multiplexer.items).sort(), [TerminalCapability.CwdDetection, TerminalCapability.CommandDetection, TerminalCapability.NaiveCwdDetection].sort()); store2.remove(TerminalCapability.NaiveCwdDetection); @@ -124,6 +138,7 @@ suite('TerminalCapabilityStoreMultiplexer', () => { test('has should return whether a capability is present', () => { deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), false); multiplexer.add(store1); + // eslint-disable-next-line local/code-no-any-casts store1.add(TerminalCapability.CwdDetection, {} as any); deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), true); store1.remove(TerminalCapability.CwdDetection); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 4cbc2f26d5b..35b659a1793 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -96,6 +96,7 @@ class TestTerminalChildProcess extends Disposable implements ITerminalChildProce class TestTerminalInstanceService extends Disposable implements Partial { getBackend() { + // eslint-disable-next-line local/code-no-any-casts return { onPtyHostExit: Event.None, onPtyHostUnresponsive: Event.None, @@ -441,6 +442,7 @@ suite('Workbench - TerminalInstance', () => { const mockCwdDetection = { getCwd: () => options.cwd }; + // eslint-disable-next-line local/code-no-any-casts capabilities.add(TerminalCapability.CwdDetection, mockCwdDetection as any); } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts index ec63876cd46..957182d9efa 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts @@ -52,6 +52,7 @@ class TestTerminalChildProcess implements ITerminalChildProcess { class TestTerminalInstanceService implements Partial { getBackend() { + // eslint-disable-next-line local/code-no-any-casts return { onPtyHostExit: Event.None, onPtyHostUnresponsive: Event.None, @@ -92,6 +93,7 @@ suite('Workbench - TerminalProcessManager', () => { } } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, } as any); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts index db33dcddd14..4aabb18be40 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts @@ -98,6 +98,7 @@ class TestTerminalInstanceService implements Partial { private _profiles: Map = new Map(); private _hasReturnedNone = true; async getBackend(remoteAuthority: string | undefined): Promise { + // eslint-disable-next-line local/code-no-any-casts return { getProfiles: async () => { if (this._hasReturnedNone) { @@ -123,6 +124,7 @@ class TestRemoteAgentService implements Partial { this._os = os; } async getEnvironment(): Promise { + // eslint-disable-next-line local/code-no-any-casts return { os: this._os } satisfies Partial as any; } } @@ -167,6 +169,7 @@ suite('TerminalProfileService', () => { remoteAgentService = new TestRemoteAgentService(); terminalInstanceService = new TestTerminalInstanceService(); extensionService = new TestTerminalExtensionService(); + // eslint-disable-next-line local/code-no-any-casts environmentService = { remoteAuthority: undefined } satisfies Partial as any; const themeService = new TestThemeService(); @@ -221,6 +224,7 @@ suite('TerminalProfileService', () => { } } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); @@ -237,6 +241,7 @@ suite('TerminalProfileService', () => { } } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); @@ -253,6 +258,7 @@ suite('TerminalProfileService', () => { } } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, source: ConfigurationTarget.USER } as any); await terminalProfileService.refreshAndAwaitAvailableProfiles(); deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); @@ -266,6 +272,7 @@ suite('TerminalProfileService', () => { }); test('should get profiles from remoteTerminalService when there is a remote authority', async () => { + // eslint-disable-next-line local/code-no-any-casts environmentService = { remoteAuthority: 'fakeremote' } satisfies Partial as any; instantiationService.stub(IWorkbenchEnvironmentService, environmentService); terminalProfileService = store.add(instantiationService.createInstance(TestTerminalProfileService)); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts index 419bd7af001..c1b5bbf89fa 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts @@ -55,12 +55,14 @@ suite('Workbench - TerminalService', () => { test('should not show prompt when confirmOnKill is never', async () => { await setConfirmOnKill(configurationService, 'never'); + // eslint-disable-next-line local/code-no-any-casts await terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, onExit: onExitEmitter.event, dispose: () => onExitEmitter.fire(undefined) } as Partial as any); + // eslint-disable-next-line local/code-no-any-casts await terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, @@ -70,6 +72,7 @@ suite('Workbench - TerminalService', () => { }); test('should not show prompt when any terminal editor is closed (handled by editor itself)', async () => { await setConfirmOnKill(configurationService, 'editor'); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, @@ -77,6 +80,7 @@ suite('Workbench - TerminalService', () => { dispose: () => onExitEmitter.fire(undefined) } as Partial as any); await setConfirmOnKill(configurationService, 'always'); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Editor, hasChildProcesses: true, @@ -86,6 +90,7 @@ suite('Workbench - TerminalService', () => { }); test('should not show prompt when confirmOnKill is editor and panel terminal is closed', async () => { await setConfirmOnKill(configurationService, 'editor'); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, @@ -97,6 +102,7 @@ suite('Workbench - TerminalService', () => { await setConfirmOnKill(configurationService, 'panel'); // No child process cases dialogService.setConfirmResult({ confirmed: false }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, @@ -104,6 +110,7 @@ suite('Workbench - TerminalService', () => { dispose: () => onExitEmitter.fire(undefined) } as Partial as any); dialogService.setConfirmResult({ confirmed: true }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, @@ -112,12 +119,14 @@ suite('Workbench - TerminalService', () => { } as Partial as any); // Child process cases dialogService.setConfirmResult({ confirmed: false }); + // eslint-disable-next-line local/code-no-any-casts await terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, dispose: () => fail() } as Partial as any); dialogService.setConfirmResult({ confirmed: true }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, @@ -129,6 +138,7 @@ suite('Workbench - TerminalService', () => { await setConfirmOnKill(configurationService, 'always'); // No child process cases dialogService.setConfirmResult({ confirmed: false }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, @@ -136,6 +146,7 @@ suite('Workbench - TerminalService', () => { dispose: () => onExitEmitter.fire(undefined) } as Partial as any); dialogService.setConfirmResult({ confirmed: true }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: false, @@ -144,12 +155,14 @@ suite('Workbench - TerminalService', () => { } as Partial as any); // Child process cases dialogService.setConfirmResult({ confirmed: false }); + // eslint-disable-next-line local/code-no-any-casts await terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, dispose: () => fail() } as Partial as any); dialogService.setConfirmResult({ confirmed: true }); + // eslint-disable-next-line local/code-no-any-casts terminalService.safeDisposeTerminal({ target: TerminalLocation.Panel, hasChildProcesses: true, @@ -162,6 +175,7 @@ suite('Workbench - TerminalService', () => { async function setConfirmOnKill(configurationService: TestConfigurationService, value: 'never' | 'always' | 'panel' | 'editor') { await configurationService.setUserConfiguration(TERMINAL_CONFIG_SECTION, { confirmOnKill: value }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, affectedKeys: ['terminal.integrated.confirmOnKill'] diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts index 19d6fb1ecdb..179a6058fa9 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts @@ -56,6 +56,7 @@ type RecordedTestCase = { const recordedTestCases: RecordedTestCase[] = [ { name: 'rich_windows11_pwsh7_echo_3_times', + // eslint-disable-next-line local/code-no-any-casts events: rich_windows11_pwsh7_echo_3_times as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, ['echo a', 'echo b', 'echo c'], '|'); @@ -63,6 +64,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'rich_windows11_pwsh7_ls_one_time', + // eslint-disable-next-line local/code-no-any-casts events: rich_windows11_pwsh7_ls_one_time as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, ['ls'], '|'); @@ -70,6 +72,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'rich_windows11_pwsh7_type_foo', + // eslint-disable-next-line local/code-no-any-casts events: rich_windows11_pwsh7_type_foo as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, [], 'foo|'); @@ -77,6 +80,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'rich_windows11_pwsh7_type_foo_left_twice', + // eslint-disable-next-line local/code-no-any-casts events: rich_windows11_pwsh7_type_foo_left_twice as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, [], 'f|oo'); @@ -84,6 +88,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'rich_macos_zsh_omz_echo_3_times', + // eslint-disable-next-line local/code-no-any-casts events: rich_macos_zsh_omz_echo_3_times as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, ['echo a', 'echo b', 'echo c'], '|'); @@ -91,6 +96,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'rich_macos_zsh_omz_ls_one_time', + // eslint-disable-next-line local/code-no-any-casts events: rich_macos_zsh_omz_ls_one_time as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { assertCommandDetectionState(commandDetection, ['ls'], '|'); @@ -98,6 +104,7 @@ const recordedTestCases: RecordedTestCase[] = [ }, { name: 'basic_macos_zsh_p10k_ls_one_time', + // eslint-disable-next-line local/code-no-any-casts events: basic_macos_zsh_p10k_ls_one_time as any as RecordedSessionEvent[], finalAssertions: (commandDetection: ICommandDetectionCapability | undefined) => { // Prompt input model doesn't work for p10k yet @@ -155,6 +162,7 @@ suite('Terminal Contrib Shell Integration Recordings', () => { }) }, store); const terminalConfigurationService = instantiationService.get(ITerminalConfigurationService) as TestTerminalConfigurationService; + // eslint-disable-next-line local/code-no-any-casts terminalConfigurationService.setConfig(terminalConfig as any); const shellIntegrationAddon = store.add(new ShellIntegrationAddon('', true, undefined, NullTelemetryService, new NullLogService)); const TerminalCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index 27f9a9404dd..91e5744bf9d 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -48,6 +48,7 @@ class TestWebglAddon implements WebglAddon { class TestXtermAddonImporter extends XtermAddonImporter { override async importAddon(name: T): Promise { if (name === 'webgl') { + // eslint-disable-next-line local/code-no-any-casts return Promise.resolve(TestWebglAddon) as any; } return super.importAddon(name); @@ -66,6 +67,7 @@ export class TestViewDescriptorService implements Partial { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IAccessibilitySignalService, { playSignal: async () => { }, isSoundEnabled(signal: unknown) { return false; }, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts index 68b506a3a61..afcb3e3fee1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts @@ -37,6 +37,7 @@ suite('OutputMonitor', () => { onDidInputData: dataEmitter.event, onData: dataEmitter.event, focus: () => { }, + // eslint-disable-next-line local/code-no-any-casts registerMarker: () => ({ id: 1 } as any) }, sessionId: '1' @@ -52,6 +53,7 @@ suite('OutputMonitor', () => { instantiationService.stub( IChatService, { + // eslint-disable-next-line local/code-no-any-casts getSession: () => ({ sessionId: '1', onDidDispose: { event: () => { }, dispose: () => { } }, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 756927bd88d..8c29b60e111 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -376,6 +376,7 @@ suite('RunInTerminalTool', () => { // Verify that auto-approve information is included ok(result?.toolSpecificData, 'Expected toolSpecificData to be defined'); + // eslint-disable-next-line local/code-no-any-casts const terminalData = result!.toolSpecificData as any; ok(terminalData.autoApproveInfo, 'Expected autoApproveInfo to be defined for auto-approved background command'); ok(terminalData.autoApproveInfo.value, 'Expected autoApproveInfo to have a value'); @@ -829,6 +830,7 @@ suite('RunInTerminalTool', () => { suite('chat session disposal cleanup', () => { test('should dispose associated terminals when chat session is disposed', () => { const sessionId = 'test-session-123'; + // eslint-disable-next-line local/code-no-any-casts const mockTerminal: ITerminalInstance = { dispose: () => { /* Mock dispose */ }, processId: 12345 @@ -852,10 +854,12 @@ suite('RunInTerminalTool', () => { test('should not affect other sessions when one session is disposed', () => { const sessionId1 = 'test-session-1'; const sessionId2 = 'test-session-2'; + // eslint-disable-next-line local/code-no-any-casts const mockTerminal1: ITerminalInstance = { dispose: () => { /* Mock dispose */ }, processId: 12345 } as any; + // eslint-disable-next-line local/code-no-any-casts const mockTerminal2: ITerminalInstance = { dispose: () => { /* Mock dispose */ }, processId: 67890 diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 0ad81a5b126..a2e38224ecc 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -91,6 +91,7 @@ registerTerminalAction({ } escapedData = escapedData.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + escapedData.slice(match.index + 4); } + // eslint-disable-next-line local/code-no-any-casts const xterm = instance.xterm as any as IInternalXtermTerminal; xterm._writeText(escapedData); } diff --git a/src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts b/src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts index c9db0ef431f..784a72d3f13 100644 --- a/src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts @@ -99,11 +99,13 @@ suite('Terminal history', () => { history.add('6', 6); strictEqual(Array.from(history.entries).length, 5); configurationService.setUserConfiguration('terminal', getConfig(2).terminal); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); strictEqual(Array.from(history.entries).length, 2); history.add('7', 7); strictEqual(Array.from(history.entries).length, 2); configurationService.setUserConfiguration('terminal', getConfig(3).terminal); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); strictEqual(Array.from(history.entries).length, 2); history.add('8', 8); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index da8f33d9b5b..32abf0e15ca 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -149,6 +149,7 @@ export class TerminalLinkManager extends DisposableStore { activeHoverDisposable = undefined; activeTooltipScheduler?.dispose(); activeTooltipScheduler = new RunOnceScheduler(() => { + // eslint-disable-next-line local/code-no-any-casts const core = (this._xterm as any)._core as IXtermCore; const cellDimensions = { width: core._renderService.dimensions.css.cell.width, @@ -347,6 +348,7 @@ export class TerminalLinkManager extends DisposableStore { return; } + // eslint-disable-next-line local/code-no-any-casts const core = (this._xterm as any)._core as IXtermCore; const cellDimensions = { width: core._renderService.dimensions.css.cell.width, diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts index a572e7d2c11..6dca7a44905 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts @@ -239,6 +239,7 @@ class TestBufferLine implements IBufferLine { wideNullCellOffset++; } } + // eslint-disable-next-line local/code-no-any-casts return { getChars: () => { return x >= cells.length ? '' : cells[x]; diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts index 36819e26b7b..9155863a0be 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts @@ -92,6 +92,7 @@ suite('TerminalLinkManager', () => { get initialCwd() { return ''; } + // eslint-disable-next-line local/code-no-any-casts }), { get(capability: T): ITerminalCapabilityImplMap[T] | undefined { return undefined; diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts index 4305d050846..dacba63de69 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts @@ -147,6 +147,7 @@ suite('Workbench - TerminalLinkOpeners', () => { duration: 0, executedX: undefined, startX: undefined, + // eslint-disable-next-line local/code-no-any-casts marker: { line: 0 } as Partial as any, @@ -286,6 +287,7 @@ suite('Workbench - TerminalLinkOpeners', () => { duration: 0, executedX: undefined, startX: undefined, + // eslint-disable-next-line local/code-no-any-casts marker: { line: 0 } as Partial as any, @@ -548,6 +550,7 @@ suite('Workbench - TerminalLinkOpeners', () => { startX: undefined, timestamp: 0, duration: 0, + // eslint-disable-next-line local/code-no-any-casts marker: { line: 0 } as Partial as any, diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts index b546b5418e5..449625170c9 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts @@ -46,6 +46,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { suite('should link words as defined by wordSeparators', () => { test('" ()[]"', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ()[]' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]); await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]); @@ -55,6 +56,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { }); test('" "', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('foo', [{ range: [[1, 1], [3, 1]], text: 'foo' }]); await assertLink(' foo ', [{ range: [[2, 1], [4, 1]], text: 'foo' }]); @@ -64,6 +66,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { }); test('" []"', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('aabbccdd.txt ', [{ range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' }]); await assertLink(' aabbccdd.txt ', [{ range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' }]); @@ -83,6 +86,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { // with a wide character, which the terminalLinkHelper currently doesn't account for test.skip('should support wide characters', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('我是学生.txt ', [{ range: [[1, 1], [12, 1]], text: '我是学生.txt' }]); await assertLink(' 我是学生.txt ', [{ range: [[2, 1], [13, 1]], text: '我是学生.txt' }]); @@ -91,6 +95,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { test('should support multiple link results', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('foo bar', [ { range: [[1, 1], [3, 1]], text: 'foo' }, @@ -100,6 +105,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { test('should remove trailing colon in the link results', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('foo:5:6: bar:0:32:', [ { range: [[1, 1], [7, 1]], text: 'foo:5:6' }, @@ -109,6 +115,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { test('should support wrapping', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('fsdjfsdkfjslkdfjskdfjsldkfjsdlkfjslkdjfskldjflskdfjskldjflskdfjsdklfjsdklfjsldkfjsdlkfjsdlkfjsdlkfjsldkfjslkdfjsdlkfjsldkfjsdlkfjskdfjsldkfjsdlkfjslkdfjsdlkfjsldkfjsldkfjsldkfjslkdfjsdlkfjslkdfjsdklfsd', [ { range: [[1, 1], [41, 3]], text: 'fsdjfsdkfjslkdfjskdfjsldkfjsdlkfjslkdjfskldjflskdfjskldjflskdfjsdklfjsdklfjsldkfjsdlkfjsdlkfjsdlkfjsldkfjslkdfjsdlkfjsldkfjsdlkfjskdfjsldkfjsdlkfjslkdfjsdlkfjsldkfjsldkfjsldkfjslkdfjsdlkfjslkdfjsdklfsd' }, @@ -116,6 +123,7 @@ suite('Workbench - TerminalWordLinkDetector', () => { }); test('should support wrapping with multiple links', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('fsdjfsdkfjslkdfjskdfjsldkfj sdlkfjslkdjfskldjflskdfjskldjflskdfj sdklfjsdklfjsldkfjsdlkfjsdlkfjsdlkfjsldkfjslkdfjsdlkfjsldkfjsdlkfjskdfjsldkfjsdlkfjslkdfjsdlkfjsldkfjsldkfjsldkfjslkdfjsdlkfjslkdfjsdklfsd', [ { range: [[1, 1], [27, 1]], text: 'fsdjfsdkfjslkdfjskdfjsldkfj' }, @@ -125,11 +133,13 @@ suite('Workbench - TerminalWordLinkDetector', () => { }); test('does not return any links for empty text', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('', []); }); test('should support file scheme links', async () => { await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); await assertLink('file:///C:/users/test/file.txt ', [{ range: [[1, 1], [30, 1]], text: 'file:///C:/users/test/file.txt' }]); await assertLink('file:///C:/users/test/file.txt:1:10 ', [{ range: [[1, 1], [35, 1]], text: 'file:///C:/users/test/file.txt:1:10' }]); diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index e6de84489d5..ad86b75ae4c 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -406,6 +406,7 @@ export class TerminalStickyScrollOverlay extends Disposable { } hoverOverlay.title = hoverTitle; + // eslint-disable-next-line local/code-no-any-casts const scrollBarWidth = (this._xterm.raw as any as { _core: IXtermCore })._core.viewport?.scrollBarWidth; if (scrollBarWidth !== undefined) { this._element.style.right = `${scrollBarWidth}px`; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index e1251efc550..b9fb6c090b0 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -682,6 +682,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } private _getTerminalDimensions(): { width: number; height: number } { + // eslint-disable-next-line local/code-no-any-casts const cssCellDims = (this._terminal as any as { _core: IXtermCore })._core._renderService.dimensions.css.cell; return { width: cssCellDims.width, @@ -707,6 +708,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest return this._cachedFontInfo; } + // eslint-disable-next-line local/code-no-any-casts const core = (this._terminal as any)._core as IXtermCore; const font = this._terminalConfigurationService.getFont(dom.getActiveWindow(), core); let lineHeight: number = font.lineHeight; @@ -781,6 +783,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _ensureSuggestWidget(terminal: Terminal): SimpleSuggestWidget { if (!this._suggestWidget) { + // eslint-disable-next-line local/code-no-any-casts this._suggestWidget = this._register(this._instantiationService.createInstance( SimpleSuggestWidget, this._container!, diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts index 98723032131..48768032a9e 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts @@ -50,6 +50,7 @@ const enum StatsConstants { */ const PREDICTION_OMIT_RE = /^(\x1b\[(\??25[hl]|\??[0-9;]+n))+/; +// eslint-disable-next-line local/code-no-any-casts const core = (terminal: Terminal): IXtermCore => (terminal as any)._core; const flushOutput = (terminal: Terminal) => { // TODO: Flushing output is not possible anymore without async diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts index 3be5ebd8412..b365fee4c54 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts @@ -36,6 +36,7 @@ suite('Workbench - Terminal Typeahead', () => { succeed = ds.add(new Emitter()); fail = ds.add(new Emitter()); + // eslint-disable-next-line local/code-no-any-casts stats = ds.add(new PredictionStats({ onPredictionAdded: add.event, onPredictionSucceeded: succeed.event, diff --git a/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts index 4349a883e2a..1d7337dc44e 100644 --- a/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts @@ -72,6 +72,7 @@ class TerminalMouseWheelZoomContribution extends Disposable implements ITerminal let gestureAccumulatedDelta = 0; raw.attachCustomWheelEventHandler((e: WheelEvent) => { + // eslint-disable-next-line local/code-no-any-casts const browserEvent = e as any as IMouseWheelEvent; if (classifier.isPhysicalMouseWheel()) { if (this._hasMouseWheelZoomModifiers(browserEvent)) { diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index c8796a108b2..fcf662e332c 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -120,7 +120,9 @@ function applyEditorMirrorOptions(base: T, cfg: IConfi let changed = false; const patch: Partial = {}; for (const [key, value] of Object.entries(configuration)) { + // eslint-disable-next-line local/code-no-any-casts if (!immutable.has(key) && (base as any)[key] !== value) { + // eslint-disable-next-line local/code-no-any-casts (patch as any)[key] = value; changed = true; } diff --git a/src/vs/workbench/contrib/testing/common/observableUtils.ts b/src/vs/workbench/contrib/testing/common/observableUtils.ts index 9294c905002..69461210334 100644 --- a/src/vs/workbench/contrib/testing/common/observableUtils.ts +++ b/src/vs/workbench/contrib/testing/common/observableUtils.ts @@ -14,6 +14,7 @@ export function onObservableChange(observable: IObservableWithChange(_observable: IObservableWithChange, change: TChange) { + // eslint-disable-next-line local/code-no-any-casts callback(change as any as T); } }; diff --git a/src/vs/workbench/contrib/testing/common/testingStates.ts b/src/vs/workbench/contrib/testing/common/testingStates.ts index 1773645067a..49793c9e4e7 100644 --- a/src/vs/workbench/contrib/testing/common/testingStates.ts +++ b/src/vs/workbench/contrib/testing/common/testingStates.ts @@ -75,5 +75,6 @@ export type TestStateCount = { [K in TestResultState]: number }; export const makeEmptyCounts = (): TestStateCount => { // shh! don't tell anyone this is actually an array! + // eslint-disable-next-line local/code-no-any-casts return new Uint32Array(statesInOrder.length) as any as { [K in TestResultState]: number }; }; diff --git a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts index 11d164163a3..59bedb10289 100644 --- a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts @@ -16,6 +16,7 @@ import { CoverageDetails, DetailType } from '../../common/testTypes.js'; suite('Code Coverage Decorations', () => { ensureNoDisposablesAreLeakedInTestSuite(); + // eslint-disable-next-line local/code-no-any-casts const textModel = { getValueInRange: () => '' } as any as ITextModel; const assertRanges = async (model: CoverageDetailsModel) => await assertSnapshot(model.ranges.map(r => ({ range: r.range.toString(), diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts index 1cb9b6f16bf..888bc6730c4 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts @@ -32,6 +32,7 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { getStateById: () => ({ state: { state: 0 }, computedState: 0 }), }; + // eslint-disable-next-line local/code-no-any-casts harness = new TestTreeTestHarness(l => new ListProjection({}, l, resultsService as any)); }); diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts index 30515da8aa3..80a6985d7cc 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts @@ -39,6 +39,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { getStateById: () => ({ state: { state: 0 }, computedState: 0 }), }; + // eslint-disable-next-line local/code-no-any-casts harness = ds.add(new TestTreeTestHarness(l => new TestHierarchicalByLocationProjection({}, l, resultsService as any))); }); @@ -133,6 +134,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { resultsService.getStateById = () => [undefined, resultInState(TestResultState.Queued)]; onTestChanged.fire({ reason: TestResultItemChangeReason.OwnStateChange, + // eslint-disable-next-line local/code-no-any-casts result: null as any, previousState: TestResultState.Unset, item: resultInState(TestResultState.Queued), @@ -149,6 +151,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)]; onTestChanged.fire({ reason: TestResultItemChangeReason.OwnStateChange, + // eslint-disable-next-line local/code-no-any-casts result: null as any, previousState: TestResultState.Queued, item: resultInState(TestResultState.Unset), diff --git a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts index 74601edb73d..dd19fb50a49 100644 --- a/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts +++ b/src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts @@ -119,6 +119,7 @@ export class TestTreeTestHarness collection.apply(diff))); + // eslint-disable-next-line local/code-no-any-casts this.projection = this._register(makeTree({ collection, onDidProcessDiff: this.onDiff.event, diff --git a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts index 72416a1c08f..d697e55cb86 100644 --- a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts @@ -24,6 +24,7 @@ suite('TestCoverage', () => { coverageAccessor = { getCoverageDetails: sandbox.stub().resolves([]), }; + // eslint-disable-next-line local/code-no-any-casts testCoverage = new TestCoverage({} as LiveTestResult, 'taskId', { extUri: { ignorePathCasing: () => true } } as any, coverageAccessor); }); diff --git a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts index 6024ea60723..c301d1cbc93 100644 --- a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts @@ -46,6 +46,7 @@ suite('Workbench - TestProfileService', () => { ...profile, }; + // eslint-disable-next-line local/code-no-any-casts t.addProfile({ id: 'ctrlId' } as any, p); return p; }; diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index ac0ba381e3b..b31e6bf0ffa 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -207,6 +207,7 @@ suite('Workbench - Test Results Service', () => { let results: TestResultService; class TestTestResultService extends TestResultService { + // eslint-disable-next-line local/code-no-any-casts protected override persistScheduler = { schedule: () => this.persistImmediately() } as any; } diff --git a/src/vs/workbench/contrib/testing/test/common/testStubs.ts b/src/vs/workbench/contrib/testing/test/common/testStubs.ts index f121bd9cd83..ba1a2f5e47f 100644 --- a/src/vs/workbench/contrib/testing/test/common/testStubs.ts +++ b/src/vs/workbench/contrib/testing/test/common/testStubs.ts @@ -82,6 +82,7 @@ export class TestTestItem implements ITestItemLike { export class TestTestCollection extends TestItemCollection { constructor(controllerId = 'ctrlId') { const root = new TestTestItem(new TestId([controllerId]), 'root'); + // eslint-disable-next-line local/code-no-any-casts (root as any)._isRoot = true; super({ diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index ba5dc5bf06d..8d1758abdba 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -611,6 +611,7 @@ interface ThemeItem extends IQuickPickItem { } function isItem(i: QuickPickInput): i is ThemeItem { + // eslint-disable-next-line local/code-no-any-casts return (i)['type'] !== 'separator'; } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 3d2f1e24872..67ba6677f38 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -821,6 +821,7 @@ export class TimelinePane extends ViewPane { return; } + // eslint-disable-next-line local/code-no-any-casts this.tree.setChildren(null, this.getItems() as any); this._isEmpty = !this.hasVisibleItems; diff --git a/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts index 5db88c9eeb7..2749e126697 100644 --- a/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts +++ b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts @@ -65,6 +65,7 @@ Navigation End --> instantiationService.stub(IPreferencesService, >{ _serviceBrand: undefined, onDidDefaultSettingsContentChanged: new Emitter().event, + // eslint-disable-next-line local/code-no-any-casts userSettingsResource: undefined as any, workspaceSettingsResource: null, getFolderSettingsResource: () => null, @@ -87,6 +88,7 @@ Navigation End --> getSetting: (id: string) => { if (id === testSettingId) { // Provide the minimal fields accessed by SimpleSettingRenderer + // eslint-disable-next-line local/code-no-any-casts return { key: testSettingId, value: 'off', diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index 05d3cd36aa8..ea52ece81c1 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -597,6 +597,7 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ } private registerDoneListeners(step: IWalkthroughStep) { + // eslint-disable-next-line local/code-no-any-casts if ((step as any).doneOn) { console.error(`wakthrough step`, step, `uses deprecated 'doneOn' property. Adopt 'completionEvents' to silence this warning`); return; diff --git a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts index cfc3572068f..5acf8788abb 100644 --- a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts @@ -125,8 +125,10 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { private onUpdateAppIconDragBehavior(): void { const setting = this.configurationService.getValue('window.doubleClickIconToClose'); if (setting && this.appIcon) { + // eslint-disable-next-line local/code-no-any-casts (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; } else if (this.appIcon) { + // eslint-disable-next-line local/code-no-any-casts (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; } } diff --git a/src/vs/workbench/services/assignment/common/assignmentFilters.ts b/src/vs/workbench/services/assignment/common/assignmentFilters.ts index c45a727886d..a7f586e085d 100644 --- a/src/vs/workbench/services/assignment/common/assignmentFilters.ts +++ b/src/vs/workbench/services/assignment/common/assignmentFilters.ts @@ -101,6 +101,7 @@ export class CopilotAssignmentFilterProvider extends Disposable implements IExpe copilotExtensionVersion = copilotExtension?.version; copilotChatExtensionVersion = copilotChatExtension?.version; + // eslint-disable-next-line local/code-no-any-casts copilotCompletionsVersion = (copilotChatExtension as any)?.completionsCoreVersion; } catch (error) { this._logService.error('Failed to update extension version assignments', error); diff --git a/src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts b/src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts index 8d17bf67094..8d7cdba0eb2 100644 --- a/src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts +++ b/src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts @@ -233,6 +233,7 @@ suite('AuthenticationMcpAccessService', () => { test('handles non-array product.json configuration gracefully', () => { // Set up invalid configuration + // eslint-disable-next-line local/code-no-any-casts productService.trustedMcpAuthAccess = 'invalid-string' as any; const result = authenticationMcpAccessService.readAllowedMcpServers('github', 'user@example.com'); diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts index 25d505123e9..02b8ecb8ab9 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts @@ -78,6 +78,7 @@ export class ConfigurationResolverExpression implements IConfigurationResolve // If the input is a string, wrap it in an object so we can use the same logic if (typeof object === 'string') { this.stringRoot = true; + // eslint-disable-next-line local/code-no-any-casts this.root = { value: object } as any; } else { this.stringRoot = false; @@ -102,6 +103,7 @@ export class ConfigurationResolverExpression implements IConfigurationResolve } private applyPlatformSpecificKeys() { + // eslint-disable-next-line local/code-no-any-casts const config = this.root as any; // already cloned by ctor, safe to change const key = isWindows ? 'windows' : isMacintosh ? 'osx' : isLinux ? 'linux' : undefined; @@ -301,6 +303,7 @@ export class ConfigurationResolverExpression implements IConfigurationResolve public toObject(): T { // If we wrapped a string, unwrap it if (this.stringRoot) { + // eslint-disable-next-line local/code-no-any-casts return (this.root as any).value as T; } diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 3b2c9d392a8..dae9636d9ea 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -90,6 +90,7 @@ export abstract class AbstractVariableResolverService implements IConfigurationR } } + // eslint-disable-next-line local/code-no-any-casts return expr.toObject() as any; } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index e5afb8970b9..c2455f2884e 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -513,6 +513,7 @@ suite('Configuration Resolver Service', () => { assert.deepStrictEqual(Object.keys(result), Object.keys(expected)); Object.keys(result).forEach(property => { + // eslint-disable-next-line local/code-no-any-casts const expectedProperty = (expected)[property]; if (isObject(result[property])) { assert.deepStrictEqual({ ...result[property] }, expectedProperty); diff --git a/src/vs/workbench/services/driver/browser/driver.ts b/src/vs/workbench/services/driver/browser/driver.ts index 6bd4ead2265..779dd9b0997 100644 --- a/src/vs/workbench/services/driver/browser/driver.ts +++ b/src/vs/workbench/services/driver/browser/driver.ts @@ -190,6 +190,7 @@ export class BrowserWindowDriver implements IWindowDriver { throw new Error(`Terminal not found: ${selector}`); } + // eslint-disable-next-line local/code-no-any-casts const xterm = (element as any).xterm; if (!xterm) { @@ -211,6 +212,7 @@ export class BrowserWindowDriver implements IWindowDriver { throw new Error(`Element not found: ${selector}`); } + // eslint-disable-next-line local/code-no-any-casts const xterm = (element as any).xterm as (XtermTerminal | undefined); if (!xterm) { diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 57fd1770ca1..6ab9b4fdb0d 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -58,7 +58,9 @@ function isUriComponents(thing: unknown): thing is UriComponents { if (!thing) { return false; } + // eslint-disable-next-line local/code-no-any-casts return isString((thing).path) && + // eslint-disable-next-line local/code-no-any-casts isString((thing).scheme); } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index e1d3d68788a..c1fc3e31561 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -226,6 +226,7 @@ export const schema: IJSONSchema = { contributes: { description: nls.localize('vscode.extension.contributes', 'All contributions of the VS Code extension represented by this package.'), type: 'object', + // eslint-disable-next-line local/code-no-any-casts properties: { // extensions will fill in } as any as { [key: string]: any }, diff --git a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts index 2c0435ad642..f822c23a330 100644 --- a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts +++ b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts @@ -46,10 +46,12 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => { }); test('declarative with unknown contribution point => workspace, web in web and => workspace in desktop', () => { + // eslint-disable-next-line local/code-no-any-casts assert.deepStrictEqual(testObject.getExtensionKind({ contributes: { 'unknownPoint': { something: true } } }), isWeb ? ['workspace', 'web'] : ['workspace']); }); test('declarative extension pack with unknown contribution point', () => { + // eslint-disable-next-line local/code-no-any-casts assert.deepStrictEqual(testObject.getExtensionKind({ extensionPack: ['ext1', 'ext2'], contributes: { 'unknownPoint': { something: true } } }), isWeb ? ['workspace', 'web'] : ['workspace']); }); @@ -92,14 +94,17 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => { }); test('extension cannot opt out from web', () => { + // eslint-disable-next-line local/code-no-any-casts assert.deepStrictEqual(testObject.getExtensionKind({ browser: 'main.browser.js', extensionKind: ['-web'] }), ['web']); }); test('extension cannot opt into web', () => { + // eslint-disable-next-line local/code-no-any-casts assert.deepStrictEqual(testObject.getExtensionKind({ main: 'main.js', extensionKind: ['web', 'workspace', 'ui'] }), ['workspace', 'ui']); }); test('extension cannot opt into web only', () => { + // eslint-disable-next-line local/code-no-any-casts assert.deepStrictEqual(testObject.getExtensionKind({ main: 'main.js', extensionKind: ['web'] }), ['workspace']); }); }); diff --git a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts index ab713796a8e..539fbc8d98c 100644 --- a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts @@ -182,6 +182,7 @@ suite('RPCProtocol', () => { test('issue #60450: Converting circular structure to JSON', function (done) { delegate = (a1: number, a2: number) => { + // eslint-disable-next-line local/code-no-any-casts const circular = {}; circular.self = circular; return circular; @@ -218,6 +219,7 @@ suite('RPCProtocol', () => { test('issue #81424: SerializeRequest should throw if an argument can not be serialized', () => { const badObject = {}; + // eslint-disable-next-line local/code-no-any-casts (badObject).loop = badObject; assert.throws(() => { diff --git a/src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts b/src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts index 2bb1caf1920..7bece1b62f9 100644 --- a/src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts +++ b/src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts @@ -44,6 +44,7 @@ const _bootstrapFnSource = (function _bootstrapFn(workerUrl: string) { port.start(); // fake recursively nested worker + // eslint-disable-next-line local/code-no-any-casts globalThis.Worker = class { constructor() { throw new TypeError('Nested workers from within nested worker are NOT supported.'); } }; // load module diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index f7acdcc4d1a..4277634e32e 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -538,11 +538,15 @@ export class BrowserHostService extends Disposable implements IHostService { } // Safari and Edge 14 are all using webkit prefix + // eslint-disable-next-line local/code-no-any-casts if ((targetWindow.document).webkitIsFullScreen !== undefined) { try { + // eslint-disable-next-line local/code-no-any-casts if (!(targetWindow.document).webkitIsFullScreen) { + // eslint-disable-next-line local/code-no-any-casts (target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise. } else { + // eslint-disable-next-line local/code-no-any-casts (targetWindow.document).webkitExitFullscreen(); // it's async, but doesn't return a real promise. } } catch { diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index c5a78e509f1..4f78c1ac217 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -873,6 +873,7 @@ class KeybindingsJsonSchema { 'commandNames': { 'type': 'string', 'enum': this.commandsEnum, + // eslint-disable-next-line local/code-no-any-casts 'enumDescriptions': this.commandsEnumDescriptions, 'description': nls.localize('keybindings.json.command', "Name of the command to execute"), }, @@ -884,6 +885,7 @@ class KeybindingsJsonSchema { { 'type': 'string', 'enum': this.removalCommandsEnum, + // eslint-disable-next-line local/code-no-any-casts 'enumDescriptions': this.commandsEnumDescriptions, 'description': nls.localize('keybindings.json.removalCommand', "Name of the command to remove keyboard shortcut for"), }, diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index 8807b7ef370..71a79c0b8c2 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -43,6 +43,7 @@ export class BrowserKeyboardMapperFactoryBase extends Disposable { protected _keymapInfos: KeymapInfo[]; protected _mru: KeymapInfo[]; private _activeKeymapInfo: KeymapInfo | null; + // eslint-disable-next-line local/code-no-any-casts private keyboardLayoutMapAllowed: boolean = (navigator as any).keyboard !== undefined; get activeKeymap(): KeymapInfo | null { @@ -398,6 +399,7 @@ export class BrowserKeyboardMapperFactoryBase extends Disposable { private async _getBrowserKeyMapping(keyboardEvent?: IKeyboardEvent): Promise { if (this.keyboardLayoutMapAllowed) { try { + // eslint-disable-next-line local/code-no-any-casts return await (navigator as any).keyboard.getLayoutMap().then((e: any) => { const ret: IKeyboardMapping = {}; for (const key of e) { diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index d9b6cebb873..f736e1bd069 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -222,6 +222,7 @@ suite('URI Label', () => { } assert.deepStrictEqual(m, { formatters: expected }); + // eslint-disable-next-line local/code-no-any-casts delete (m as any).formatters; }); }); diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index 5ea0812bfe3..1214694e56d 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -78,6 +78,7 @@ suite('Progress Indicator', () => { testOnScopeOpened(scopeId: string) { super.onScopeOpened(scopeId); } testOnScopeClosed(scopeId: string): void { super.onScopeClosed(scopeId); } }()); + // eslint-disable-next-line local/code-no-any-casts const testObject = disposables.add(new ScopedProgressIndicator((testProgressBar), progressScope)); // Active: Show (Infinite) diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index 08389a2302f..6b042d65e72 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -252,11 +252,14 @@ export class PortsAttributes extends Disposable { } private hasStartEnd(value: number | PortRange | RegExp | HostAndPort): value is PortRange { + // eslint-disable-next-line local/code-no-any-casts return ((value).start !== undefined) && ((value).end !== undefined); } private hasHostAndPort(value: number | PortRange | RegExp | HostAndPort): value is HostAndPort { + // eslint-disable-next-line local/code-no-any-casts return ((value).host !== undefined) && ((value).port !== undefined) + // eslint-disable-next-line local/code-no-any-casts && isString((value).host) && isNumber((value).port); } @@ -292,6 +295,7 @@ export class PortsAttributes extends Disposable { if (attributesKey === undefined) { continue; } + // eslint-disable-next-line local/code-no-any-casts const setting = (settingValue)[attributesKey]; let key: number | PortRange | RegExp | HostAndPort | undefined = undefined; if (Number(attributesKey)) { @@ -328,6 +332,7 @@ export class PortsAttributes extends Disposable { }); } + // eslint-disable-next-line local/code-no-any-casts const defaults = this.configurationService.getValue(PortsAttributes.DEFAULTS); if (defaults) { this.defaultPortAttributes = { @@ -390,6 +395,7 @@ export class PortsAttributes extends Disposable { newRemoteValue[`${port}`] = {}; } for (const attribute in attributes) { + // eslint-disable-next-line local/code-no-any-casts newRemoteValue[`${port}`][attribute] = (attributes)[attribute]; } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index fe9c25c5214..980e30227a6 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -625,8 +625,10 @@ export interface ISerializedSearchError { export type ISerializedSearchComplete = ISerializedSearchSuccess | ISerializedSearchError; export function isSerializedSearchComplete(arg: ISerializedSearchProgressItem | ISerializedSearchComplete): arg is ISerializedSearchComplete { + // eslint-disable-next-line local/code-no-any-casts if ((arg as any).type === 'error') { return true; + // eslint-disable-next-line local/code-no-any-casts } else if ((arg as any).type === 'success') { return true; } else { diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 14b0a29ff46..374f5c622c6 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -435,6 +435,7 @@ const FileMatchItemAccessor = new class implements IItemAccessor function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { return { + // eslint-disable-next-line local/code-no-any-casts ...rawQuery, // TODO ...{ folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery), diff --git a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts index bfca89fba24..229523802e3 100644 --- a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts @@ -144,6 +144,7 @@ export class TMGrammarFactory extends Disposable { encodedLanguageId, { embeddedLanguages, + // eslint-disable-next-line local/code-no-any-casts tokenTypes: grammarDefinition.tokenTypes, balancedBracketSelectors: grammarDefinition.balancedBracketSelectors, unbalancedBracketSelectors: grammarDefinition.unbalancedBracketSelectors, diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index ec9420b6c3d..c5f9f88577f 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -41,6 +41,7 @@ function readExactlyByFile(file: string, totalBytes: number): Promiseerr).code === 'EISDIR') { return reject(err); // we want to bubble this error up (file is actually a folder) } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index a6e4c965f49..77b3f0fc3c4 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -119,6 +119,7 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme { case 'hidesExplorerArrows': case 'hasFolderIcons': case 'watch': + // eslint-disable-next-line local/code-no-any-casts (theme as any)[key] = data[key]; break; case 'location': diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts index 824a35ca9e9..26b01f4101a 100644 --- a/src/vs/workbench/services/themes/browser/productIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -120,6 +120,7 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme { case 'settingsId': case 'styleSheetContent': case 'watch': + // eslint-disable-next-line local/code-no-any-casts (theme as any)[key] = data[key]; break; case 'location': diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 3697ec38319..2a088b1df3f 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -647,6 +647,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } case 'themeTokenColors': case 'id': case 'label': case 'settingsId': case 'watch': case 'themeSemanticHighlighting': + // eslint-disable-next-line local/code-no-any-casts (theme as any)[key] = data[key]; break; case 'semanticTokenRules': { diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 0381326cbff..e0662097ef8 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -686,6 +686,7 @@ export class ViewsService extends Disposable implements IViewsService { } private createViewPaneContainer(element: HTMLElement, viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation, disposables: DisposableStore, instantiationService: IInstantiationService): ViewPaneContainer { + // eslint-disable-next-line local/code-no-any-casts const viewPaneContainer: ViewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor.ctor, ...(viewContainer.ctorDescriptor.staticArguments || [])); this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts index 18e62e12c7e..a8f4fd58deb 100644 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts @@ -64,12 +64,14 @@ suite('ViewContainerModel', () => { }); test('empty model', function () { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -97,6 +99,7 @@ suite('ViewContainerModel', () => { }); test('when contexts', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -141,6 +144,7 @@ suite('ViewContainerModel', () => { })); test('when contexts - multiple', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -164,6 +168,7 @@ suite('ViewContainerModel', () => { })); test('when contexts - multiple 2', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -187,6 +192,7 @@ suite('ViewContainerModel', () => { })); test('setVisible', () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -232,6 +238,7 @@ suite('ViewContainerModel', () => { }); test('move', () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -262,6 +269,7 @@ suite('ViewContainerModel', () => { test('view states', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -282,6 +290,7 @@ suite('ViewContainerModel', () => { test('view states and when contexts', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -312,6 +321,7 @@ suite('ViewContainerModel', () => { test('view states and when contexts multiple views', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -357,6 +367,7 @@ suite('ViewContainerModel', () => { })); test('remove event is not triggered if view was hidden and removed', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -387,6 +398,7 @@ suite('ViewContainerModel', () => { })); test('add event is not triggered if view was set visible (when visible) and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -414,6 +426,7 @@ suite('ViewContainerModel', () => { })); test('remove event is not triggered if view was hidden and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -441,6 +454,7 @@ suite('ViewContainerModel', () => { })); test('add event is not triggered if view was set visible (when not visible) and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -472,6 +486,7 @@ suite('ViewContainerModel', () => { })); test('added view descriptors are in ascending order in the event', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -523,6 +538,7 @@ suite('ViewContainerModel', () => { })); test('add event is triggered only once when view is set visible while it is set active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -554,6 +570,7 @@ suite('ViewContainerModel', () => { })); test('add event is not triggered only when view is set hidden while it is set active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -583,6 +600,7 @@ suite('ViewContainerModel', () => { })); test('#142087: view descriptor visibility is not reset', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const viewDescriptor: IViewDescriptor = { @@ -606,6 +624,7 @@ suite('ViewContainerModel', () => { })); test('remove event is triggered properly if multiple views are hidden at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -664,6 +683,7 @@ suite('ViewContainerModel', () => { })); test('add event is triggered properly if multiple views are hidden at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -732,6 +752,7 @@ suite('ViewContainerModel', () => { })); test('add and remove events are triggered properly if multiple views are hidden and added at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -809,6 +830,7 @@ suite('ViewContainerModel', () => { })); test('newly added view descriptor is hidden if it was toggled hidden in storage before adding', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + // eslint-disable-next-line local/code-no-any-casts container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const viewDescriptor: IViewDescriptor = { id: 'view1', diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index 3565d55c3c9..b9b7158a00e 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -22,7 +22,9 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); const ViewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const viewContainerIdPrefix = 'testViewContainer'; +// eslint-disable-next-line local/code-no-any-casts const sidebarContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); +// eslint-disable-next-line local/code-no-any-casts const panelContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); suite('ViewDescriptorService', () => { @@ -330,6 +332,7 @@ suite('ViewDescriptorService', () => { test('initialize with custom locations', async function () { const storageService = instantiationService.get(IStorageService); + // eslint-disable-next-line local/code-no-any-casts const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; const viewsCustomizations = { @@ -389,6 +392,7 @@ suite('ViewDescriptorService', () => { test('storage change', async function () { const testObject = aViewDescriptorService(); + // eslint-disable-next-line local/code-no-any-casts const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; @@ -524,6 +528,7 @@ suite('ViewDescriptorService', () => { test('custom locations take precedence when default view container of views change', async function () { const storageService = instantiationService.get(IStorageService); + // eslint-disable-next-line local/code-no-any-casts const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; const viewsCustomizations = { @@ -586,6 +591,7 @@ suite('ViewDescriptorService', () => { test('view containers with not existing views are not removed from customizations', async function () { const storageService = instantiationService.get(IStorageService); + // eslint-disable-next-line local/code-no-any-casts const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; const viewsCustomizations = { @@ -636,6 +642,7 @@ suite('ViewDescriptorService', () => { }; storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + // eslint-disable-next-line local/code-no-any-casts const viewContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const viewDescriptors: IViewDescriptor[] = [ { @@ -668,6 +675,7 @@ suite('ViewDescriptorService', () => { const storageService = instantiationService.get(IStorageService); const testObject = aViewDescriptorService(); + // eslint-disable-next-line local/code-no-any-casts const viewContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const viewDescriptors: IViewDescriptor[] = [ { diff --git a/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts index a0cf72cc25c..b0f2c10fda3 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts @@ -113,6 +113,7 @@ export class WorkspaceIdentityService implements IWorkspaceIdentityService { } if (obj instanceof VSBuffer || obj instanceof Uint8Array) { + // eslint-disable-next-line local/code-no-any-casts return obj; } diff --git a/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts b/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts index ec84971e6ee..1590684cd58 100644 --- a/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts +++ b/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts @@ -109,6 +109,7 @@ suite('Workspace Trust', () => { const trustInfo: IWorkspaceTrustInfo = { uriTrustInfo: [{ uri: URI.parse('file:///Folder'), trusted: true }] }; storageService.store(WORKSPACE_TRUST_STORAGE_KEY, JSON.stringify(trustInfo), StorageScope.APPLICATION, StorageTarget.MACHINE); + // eslint-disable-next-line local/code-no-any-casts (environmentService as any).filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/file.txt') }]; instantiationService.stub(IWorkbenchEnvironmentService, { ...environmentService }); @@ -121,6 +122,7 @@ suite('Workspace Trust', () => { test('empty workspace - trusted, open untrusted file', async () => { await configurationService.setUserConfiguration('security', getUserSettings(true, true)); + // eslint-disable-next-line local/code-no-any-casts (environmentService as any).filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/foo.txt') }]; instantiationService.stub(IWorkbenchEnvironmentService, { ...environmentService }); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 56f7ff4ec26..794026f1952 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -130,6 +130,7 @@ suite('Workbench parts', () => { assert.strictEqual(part.getId(), 'myPart'); // Memento + // eslint-disable-next-line local/code-no-any-casts let memento = part.testGetMemento(StorageScope.PROFILE, StorageTarget.MACHINE) as any; assert(memento); memento.foo = 'bar'; diff --git a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts index c33c87a80f4..18c7868365d 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts @@ -95,6 +95,7 @@ suite('EditorInput', () => { assert.ok(!isEditorInput({})); assert.ok(!isResourceEditorInput(input)); + // eslint-disable-next-line local/code-no-any-casts assert.ok(!isUntitledResourceEditorInput(input as any)); assert.ok(!isResourceDiffEditorInput(input)); assert.ok(!isResourceMergeEditorInput(input)); diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index 61a788e39ac..48a2aea5c7b 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -128,6 +128,7 @@ suite('EditorPane', () => { assert(!editor.input); await editor.setInput(input, options, Object.create(null), CancellationToken.None); + // eslint-disable-next-line local/code-no-any-casts assert.strictEqual(input, editor.input); editor.setVisible(true); assert(editor.isVisible()); diff --git a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts index 723cd23f391..74f363b48b6 100644 --- a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts @@ -92,6 +92,7 @@ suite('ResourceEditorInput', () => { '**/*.txt': 'Label 2', '**/resource.txt': 'Label 3', }); + // eslint-disable-next-line local/code-no-any-casts testConfigurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration(configuration: string) { return configuration === CustomEditorLabelService.SETTING_ID_PATTERNS; }, source: ConfigurationTarget.USER } as any); let label1Name: string = ''; @@ -102,24 +103,28 @@ suite('ResourceEditorInput', () => { })); await testConfigurationService.setUserConfiguration(CustomEditorLabelService.SETTING_ID_ENABLED, true); + // eslint-disable-next-line local/code-no-any-casts testConfigurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration(configuration: string) { return configuration === CustomEditorLabelService.SETTING_ID_ENABLED; }, source: ConfigurationTarget.USER } as any); assert.ok(label1Name === 'Label 3'); assert.ok(label2Name === 'Label 1'); await testConfigurationService.setUserConfiguration(CustomEditorLabelService.SETTING_ID_ENABLED, false); + // eslint-disable-next-line local/code-no-any-casts testConfigurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration(configuration: string) { return configuration === CustomEditorLabelService.SETTING_ID_ENABLED; }, source: ConfigurationTarget.USER } as any); assert.ok(label1Name === 'resource.txt' as string); assert.ok(label2Name === 'resource.md' as string); await testConfigurationService.setUserConfiguration(CustomEditorLabelService.SETTING_ID_ENABLED, true); + // eslint-disable-next-line local/code-no-any-casts testConfigurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration(configuration: string) { return configuration === CustomEditorLabelService.SETTING_ID_ENABLED; }, source: ConfigurationTarget.USER } as any); await testConfigurationService.setUserConfiguration(CustomEditorLabelService.SETTING_ID_PATTERNS, { 'thePath/**/resource.txt': 'Label 4', 'thePath/of/*/resource.txt': 'Label 5', }); + // eslint-disable-next-line local/code-no-any-casts testConfigurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration(configuration: string) { return configuration === CustomEditorLabelService.SETTING_ID_PATTERNS; }, source: ConfigurationTarget.USER } as any); assert.ok(label1Name === 'Label 5' as string); diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index 3ca9a390494..d37427decea 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -39,6 +39,7 @@ suite('Window', () => { const clearTimeoutCalls: number[] = []; function createWindow(id: number, slow?: boolean) { + // eslint-disable-next-line local/code-no-any-casts const res = { setTimeout: function (callback: Function, delay: number, ...args: any[]): number { setTimeoutCalls.push(id); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 44bce100049..02526361f8e 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -301,6 +301,7 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); + // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IAccessibilitySignalService, { playSignal: async () => { }, isSoundEnabled(signal: unknown) { return false; }, @@ -692,6 +693,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { focus() { } } +// eslint-disable-next-line local/code-no-any-casts const activeViewlet: PaneComposite = {} as any; export class TestPaneCompositeService extends Disposable implements IPaneCompositePartService { @@ -1058,6 +1060,7 @@ export class TestEditorService extends Disposable implements EditorServiceImpl { } createScoped(editorGroupsContainer: IEditorGroupsContainer): IEditorService { return this; } getEditors() { return []; } + // eslint-disable-next-line local/code-no-any-casts findEditors() { return [] as any; } openEditor(editor: EditorInput, options?: IEditorOptions, group?: PreferredGroup): Promise; openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: PreferredGroup): Promise; @@ -1960,6 +1963,7 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol export class TestTerminalConfigurationService extends TerminalConfigurationService { get fontMetrics() { return this._fontMetrics; } + // eslint-disable-next-line local/code-no-any-casts setConfig(config: Partial) { this._config = config as any; } } @@ -1977,6 +1981,7 @@ export class TestQuickInputService implements IQuickInputService { pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; async pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise { if (Array.isArray(picks)) { + // eslint-disable-next-line local/code-no-any-casts return { label: 'selectedPick', description: 'pick description', value: 'selectedPick' }; } else { return undefined; diff --git a/src/vs/workbench/test/common/resources.test.ts b/src/vs/workbench/test/common/resources.test.ts index 1f3f97d2c43..83e86158ee8 100644 --- a/src/vs/workbench/test/common/resources.test.ts +++ b/src/vs/workbench/test/common/resources.test.ts @@ -48,6 +48,7 @@ suite('ResourceGlobMatcher', () => { disposables.add(matcher.onExpressionChange(() => eventCounter++)); await configurationService.setUserConfiguration(SETTING, { '**/*.foo': true }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); assert.equal(eventCounter, 1); @@ -55,6 +56,7 @@ suite('ResourceGlobMatcher', () => { assert.equal(matcher.matches(URI.file('/foo/bar.foo')), true); await configurationService.setUserConfiguration(SETTING, undefined); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); assert.equal(eventCounter, 2); @@ -67,6 +69,7 @@ suite('ResourceGlobMatcher', () => { 'C:/bar/**': true, '/bar/**': true }); + // eslint-disable-next-line local/code-no-any-casts configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); assert.equal(matcher.matches(URI.file('/bar/foo.1')), true); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 3916a200028..003454192ab 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -288,6 +288,7 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService { } export function mock(): Ctor { + // eslint-disable-next-line local/code-no-any-casts return function () { } as any; } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 97bce2f41bc..3aa421153b4 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -252,6 +252,7 @@ export class TestNativeWorkingCopyBackupService extends NativeWorkingCopyBackupS const logService = new NullLogService(); const fileService = new FileService(logService); const lifecycleService = new TestLifecycleService(); + // eslint-disable-next-line local/code-no-any-casts super(environmentService as any, fileService, logService, lifecycleService); const inMemoryFileSystemProvider = this._register(new InMemoryFileSystemProvider()); diff --git a/src/vs/workbench/workbench.web.main.internal.ts b/src/vs/workbench/workbench.web.main.internal.ts index 011e1cbe462..35a2b3748a5 100644 --- a/src/vs/workbench/workbench.web.main.internal.ts +++ b/src/vs/workbench/workbench.web.main.internal.ts @@ -195,6 +195,7 @@ import { UserDataSyncResourceProviderService } from '../platform/userDataSync/co import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from '../platform/remote/common/remoteAuthorityResolver.js'; // TODO@esm remove me once we stop supporting our web-esm-bridge +// eslint-disable-next-line local/code-no-any-casts if ((globalThis as any).__VSCODE_WEB_ESM_PROMISE) { const exports = { @@ -219,7 +220,9 @@ if ((globalThis as any).__VSCODE_WEB_ESM_PROMISE) { logger: logger, Menu: Menu }; + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).__VSCODE_WEB_ESM_PROMISE(exports); + // eslint-disable-next-line local/code-no-any-casts delete (globalThis as any).__VSCODE_WEB_ESM_PROMISE; } diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 6673f33d1f2..383909739c6 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -55,7 +55,9 @@ //#endregion + // eslint-disable-next-line local/code-no-any-casts const define: IGlobalDefine = (globalThis as any).define; + // eslint-disable-next-line local/code-no-any-casts const require: { getConfig?(): any } | undefined = (globalThis as any).require; if (!define || !require || typeof require.getConfig !== 'function') { @@ -77,6 +79,7 @@ } const promise = new Promise(resolve => { + // eslint-disable-next-line local/code-no-any-casts (globalThis as any).__VSCODE_WEB_ESM_PROMISE = resolve; }); @@ -85,6 +88,7 @@ load: (_name, _req, _load, _config) => { const script: any = document.createElement('script'); script.type = 'module'; + // eslint-disable-next-line local/code-no-any-casts script.src = trustedTypesPolicy ? trustedTypesPolicy.createScriptURL(`${baseUrl}vs/workbench/workbench.web.main.internal.js`) as any as string : `${baseUrl}vs/workbench/workbench.web.main.internal.js`; document.head.appendChild(script); diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 5ce24b6a062..85b2566cdcc 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -124,6 +124,7 @@ export class Code { throw new Error('Invalid usage'); } + // eslint-disable-next-line local/code-no-any-casts const targetProp = (target as any)[prop]; if (typeof targetProp !== 'function') { return targetProp; diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 6d09af7f22a..70d807a6e4a 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -312,6 +312,7 @@ export class Terminal { } async getPage(): Promise { + // eslint-disable-next-line local/code-no-any-casts return (this.code.driver as any).page; } diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index d904d202497..9cfafeb60cc 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -306,6 +306,7 @@ export async function getApplication() { const application = createApp({ // Pass the alpha version of Playwright down... This is a hack since Playwright MCP // doesn't play nice with Playwright Test: https://github.com/microsoft/playwright-mcp/issues/917 + // eslint-disable-next-line local/code-no-any-casts playwright: playwright as any, quality, version: parseVersion(version ?? '0.0.0'), diff --git a/test/mcp/src/automationTools/problems.ts b/test/mcp/src/automationTools/problems.ts index 81c6f297e23..4ff55b05270 100644 --- a/test/mcp/src/automationTools/problems.ts +++ b/test/mcp/src/automationTools/problems.ts @@ -73,6 +73,7 @@ export function applyProblemsTools(server: McpServer, appService: ApplicationSer // This is a static method that returns a selector, not an async operation const app = await appService.getOrCreateApplication(); + // eslint-disable-next-line local/code-no-any-casts const selector = (app.workbench.problems.constructor as any).getSelectorInProblemsView(severityMap[severity]); return { content: [{ diff --git a/test/mcp/src/playwright.ts b/test/mcp/src/playwright.ts index 48c81a199c6..5a1a229e3e9 100644 --- a/test/mcp/src/playwright.ts +++ b/test/mcp/src/playwright.ts @@ -14,6 +14,7 @@ export async function getServer(app?: Application): Promise { { capabilities: ['core', 'pdf', 'vision'] }, + // eslint-disable-next-line local/code-no-any-casts () => Promise.resolve(application.code.driver.browserContext as any) ); application.code.driver.browserContext.on('close', async () => { From a4fbc54f29cbd5e3fda749736364715573910735 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:44:14 -0700 Subject: [PATCH 0766/4355] Use selectors instead to simplify and speed up rule --- .eslint-plugin-local/code-no-any-casts.ts | 25 +++++------------------ 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/.eslint-plugin-local/code-no-any-casts.ts b/.eslint-plugin-local/code-no-any-casts.ts index 4229469c2de..ec1ea1ec3d5 100644 --- a/.eslint-plugin-local/code-no-any-casts.ts +++ b/.eslint-plugin-local/code-no-any-casts.ts @@ -10,26 +10,11 @@ export = new class NoAnyCasts implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - // Detect TSTypeAssertion: value - 'TSTypeAssertion': (node: any) => { - const typeAssertion = node as TSESTree.TSTypeAssertion; - if (typeAssertion.typeAnnotation.type === 'TSAnyKeyword') { - context.report({ - node, - message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` - }); - } - }, - - // Detect TSAsExpression: value as any - 'TSAsExpression': (node: any) => { - const asExpression = node as TSESTree.TSAsExpression; - if (asExpression.typeAnnotation.type === 'TSAnyKeyword') { - context.report({ - node, - message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` - }); - } + 'TSTypeAssertion[typeAnnotation.type="TSAnyKeyword"], TSAsExpression[typeAnnotation.type="TSAnyKeyword"]': (node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression) => { + context.report({ + node, + message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` + }); } }; } From 1174f9b95123c5d9c662df4df6bd7d2dfddde955 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:46:45 -0700 Subject: [PATCH 0767/4355] Compile build scripts too --- build/azure-pipelines/common/publish.js | 1 + build/azure-pipelines/upload-cdn.js | 1 + build/lib/compilation.js | 1 + build/lib/extensions.js | 2 ++ build/lib/fetch.js | 1 + build/lib/mangle/index.js | 1 + build/lib/monaco-api.js | 2 ++ build/lib/nls.js | 6 +++++- build/lib/optimize.js | 4 +++- build/lib/propertyInitOrderChecker.js | 3 +++ build/lib/reporter.js | 3 +++ build/lib/task.js | 1 + build/lib/treeshaking.js | 8 ++++++++ build/lib/tsb/builder.js | 5 +++++ build/lib/tsb/index.js | 2 ++ build/lib/util.js | 2 ++ build/lib/watch/watch-win32.js | 1 + build/linux/debian/install-sysroot.js | 1 + build/win32/explorer-dll-fetcher.js | 1 + 19 files changed, 44 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 2199846a7c1..f93cbfe1c37 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -264,6 +264,7 @@ class ESRPReleaseService { // Release service uses hex format, not base64url :roll_eyes: x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), // Release service uses a '.' separated string, not an array of strings :roll_eyes: + // eslint-disable-next-line local/code-no-any-casts x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'), }, payload: message, diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index fcf24ad6073..b19e78001d8 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -105,6 +105,7 @@ async function main() { const listing = new vinyl_1.default({ path: 'files.txt', contents: Buffer.from(files.join('\n')), + // eslint-disable-next-line local/code-no-any-casts stat: { mode: 0o666 } }); const filesOut = event_stream_1.default.readArray([listing]) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index fb326dfd2b1..fe4408136bb 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -160,6 +160,7 @@ function compileTask(src, out, build, options = {}) { // free resources (await newContentsByFileName).clear(); this.push(null); + // eslint-disable-next-line local/code-no-any-casts ts2tsMangler = undefined; }); } diff --git a/build/lib/extensions.js b/build/lib/extensions.js index c80a1be1a84..108bc84b487 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -151,6 +151,7 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) { path: filePath, stat: fs_1.default.statSync(filePath), base: extensionPath, + // eslint-disable-next-line local/code-no-any-casts contents: fs_1.default.createReadStream(filePath) })); // check for a webpack configuration files, then invoke webpack @@ -235,6 +236,7 @@ function fromLocalNormal(extensionPath) { path: filePath, stat: fs_1.default.statSync(filePath), base: extensionPath, + // eslint-disable-next-line local/code-no-any-casts contents: fs_1.default.createReadStream(filePath) })); event_stream_1.default.readArray(files).pipe(result); diff --git a/build/lib/fetch.js b/build/lib/fetch.js index 25587697016..398860ac746 100644 --- a/build/lib/fetch.js +++ b/build/lib/fetch.js @@ -48,6 +48,7 @@ async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { try { const response = await fetch(url, { ...options.nodeFetchOptions, + // eslint-disable-next-line local/code-no-any-casts signal: controller.signal /* Typings issue with lib.dom.d.ts */ }); if (verbose) { diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 40a9f7bbe66..c8daa84ae51 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -242,6 +242,7 @@ class ClassData { } } function isNameTakenInFile(node, name) { + // eslint-disable-next-line local/code-no-any-casts const identifiers = node.getSourceFile().identifiers; if (identifiers instanceof Map) { if (identifiers.has(name)) { diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index cc801849a5d..265addfd098 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -187,6 +187,7 @@ function format(ts, text, endl) { // Parse the source text const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); // Get the formatting edits on the input sources + // eslint-disable-next-line local/code-no-any-casts const edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); // Apply the edits on the input code return applyEdits(text, edits); @@ -284,6 +285,7 @@ function format(ts, text, endl) { function getRuleProvider(options) { // Share this between multiple formatters using the same options. // This represents the bulk of the space the formatter uses. + // eslint-disable-next-line local/code-no-any-casts return ts.formatting.getFormatContext(options); } function applyEdits(text, edits) { diff --git a/build/lib/nls.js b/build/lib/nls.js index 492dbdae8ce..adb40668fe4 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -32,6 +32,7 @@ function collect(ts, node, fn) { return result; } function clone(object) { + // eslint-disable-next-line local/code-no-any-casts const result = {}; for (const id in object) { result[id] = object[id]; @@ -389,8 +390,11 @@ var _nls; const moduleId = javascriptFile.relative .replace(/\.js$/, '') .replace(/\\/g, '/'); - const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options); + const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), + // eslint-disable-next-line local/code-no-any-casts + javascriptFile.sourceMap, options); const result = fileFrom(javascriptFile, javascript); + // eslint-disable-next-line local/code-no-any-casts result.sourceMap = sourcemap; if (nlsKeys) { _nls.moduleToNLSKeys[moduleId] = nlsKeys; diff --git a/build/lib/optimize.js b/build/lib/optimize.js index fbc455b1cd1..f52140e0784 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -213,7 +213,9 @@ function minifyTask(src, sourceMapBaseUrl) { cb(undefined, f); } }, cb); - }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, gulp_sourcemaps_1.default.write('./', { + }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, + // eslint-disable-next-line local/code-no-any-casts + gulp_sourcemaps_1.default.write('./', { sourceMappingURL, sourceRoot: undefined, includeContent: true, diff --git a/build/lib/propertyInitOrderChecker.js b/build/lib/propertyInitOrderChecker.js index 48134856308..a1da1835bd9 100644 --- a/build/lib/propertyInitOrderChecker.js +++ b/build/lib/propertyInitOrderChecker.js @@ -233,8 +233,11 @@ function* findAllReferencesInClass(node) { function findAllReferences(node) { const sourceFile = node.getSourceFile(); const position = node.getStart(); + // eslint-disable-next-line local/code-no-any-casts const name = ts.getTouchingPropertyName(sourceFile, position); + // eslint-disable-next-line local/code-no-any-casts const options = { use: ts.FindAllReferences.FindReferencesUse.References }; + // eslint-disable-next-line local/code-no-any-casts return ts.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; } var DefinitionKind; diff --git a/build/lib/reporter.js b/build/lib/reporter.js index da3ca0f46a1..e8570ef6d5a 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -87,11 +87,14 @@ function createReporter(id) { return event_stream_1.default.through(undefined, function () { errorLog.onEnd(); if (emitError && errors.length > 0) { + // eslint-disable-next-line local/code-no-any-casts if (!errors.__logged__) { errorLog.log(); } + // eslint-disable-next-line local/code-no-any-casts errors.__logged__ = true; const err = new Error(`Found ${errors.length} errors`); + // eslint-disable-next-line local/code-no-any-casts err.__reporter__ = true; this.emit('error', err); } diff --git a/build/lib/task.js b/build/lib/task.js index 025e0a4e8f2..9a149ec7f0c 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -13,6 +13,7 @@ exports.define = define; const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); function _isPromise(p) { + // eslint-disable-next-line local/code-no-any-casts if (typeof p.then === 'function') { return true; } diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index a42fbf961ab..5b339091e07 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -230,15 +230,19 @@ var NodeColor; NodeColor[NodeColor["Black"] = 2] = "Black"; })(NodeColor || (NodeColor = {})); function getColor(node) { + // eslint-disable-next-line local/code-no-any-casts return node.$$$color || 0 /* NodeColor.White */; } function setColor(node, color) { + // eslint-disable-next-line local/code-no-any-casts node.$$$color = color; } function markNeededSourceFile(node) { + // eslint-disable-next-line local/code-no-any-casts node.$$$neededSourceFile = true; } function isNeededSourceFile(node) { + // eslint-disable-next-line local/code-no-any-casts return Boolean(node.$$$neededSourceFile); } function nodeOrParentIsBlack(node) { @@ -561,6 +565,7 @@ function markNodes(ts, languageService, options) { if (nodeOrParentIsBlack(node)) { continue; } + // eslint-disable-next-line local/code-no-any-casts const symbol = node.symbol; if (!symbol) { continue; @@ -768,8 +773,11 @@ class SymbolImportTuple { * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ function getRealNodeSymbol(ts, checker, node) { + // eslint-disable-next-line local/code-no-any-casts const getPropertySymbolsFromContextualType = ts.getPropertySymbolsFromContextualType; + // eslint-disable-next-line local/code-no-any-casts const getContainingObjectLiteralElement = ts.getContainingObjectLiteralElement; + // eslint-disable-next-line local/code-no-any-casts const getNameFromPropertyName = ts.getNameFromPropertyName; // Go to the original declaration for cases: // diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index f4f60cf8521..47e23763965 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -75,6 +75,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { host.getCompilationSettings().declaration = true; function file(file) { // support gulp-sourcemaps + // eslint-disable-next-line local/code-no-any-casts if (file.sourceMap) { emitSourceMapsInStream = false; } @@ -95,6 +96,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { } } function isExternalModule(sourceFile) { + // eslint-disable-next-line local/code-no-any-casts return sourceFile.externalModuleIndicator || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); } @@ -219,6 +221,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { if (didChange) { [tsSMC, inputSMC].forEach((consumer) => { consumer.sources.forEach((sourceFile) => { + // eslint-disable-next-line local/code-no-any-casts smg._sources.add(sourceFile); const sourceContent = consumer.sourceContentFor(sourceFile); if (sourceContent !== null) { @@ -234,6 +237,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { // }); } } + // eslint-disable-next-line local/code-no-any-casts vinyl.sourceMap = sourceMap; } } @@ -517,6 +521,7 @@ class LanguageServiceHost { let result = this._snapshots[filename]; if (!result && resolve) { try { + // eslint-disable-next-line local/code-no-any-casts result = new VinylScriptSnapshot(new vinyl_1.default({ path: filename, contents: fs_1.default.readFileSync(filename), diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index af10bf8ce19..67c82c8bb9e 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -134,10 +134,12 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) const transpiler = !config.transpileWithEsbuild ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) : new transpiler_1.ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); + // eslint-disable-next-line local/code-no-any-casts result = (() => createTranspileStream(transpiler)); } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); + // eslint-disable-next-line local/code-no-any-casts result = ((token) => createCompileStream(_builder, token)); } result.src = (opts) => { diff --git a/build/lib/util.js b/build/lib/util.js index 389b9e0cd4f..e1c162040dc 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -116,6 +116,7 @@ function fixWin32DirectoryPermissions() { function setExecutableBit(pattern) { const setBit = event_stream_1.default.mapSync(f => { if (!f.stat) { + // eslint-disable-next-line local/code-no-any-casts f.stat = { isFile() { return true; } }; } f.stat.mode = /* 100755 */ 33261; @@ -288,6 +289,7 @@ function rebase(count) { }); } function filter(fn) { + // eslint-disable-next-line local/code-no-any-casts const result = event_stream_1.default.through(function (data) { if (fn(data)) { this.emit('data', data); diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index 4113d93526e..1355055e768 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -42,6 +42,7 @@ function watch(root) { path: changePathFull, base: root }); + // eslint-disable-next-line local/code-no-any-casts file.event = toChangeType(changeType); result.emit('data', file); } diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index d16e13bd63e..8ad94877788 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -73,6 +73,7 @@ async function fetchUrl(options, retries = 10, retryDelay = 1000) { try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, + // eslint-disable-next-line local/code-no-any-casts signal: controller.signal /* Typings issue with lib.dom.d.ts */ }); if (response.ok && (response.status >= 200 && response.status < 300)) { diff --git a/build/win32/explorer-dll-fetcher.js b/build/win32/explorer-dll-fetcher.js index dfb9ce97ff4..f7d7c49700d 100644 --- a/build/win32/explorer-dll-fetcher.js +++ b/build/win32/explorer-dll-fetcher.js @@ -53,6 +53,7 @@ async function main(outputDir) { if (!outputDir) { throw new Error('Required build env not set'); } + // eslint-disable-next-line local/code-no-any-casts await downloadExplorerDll(outputDir, product_json_1.default.quality, arch); } if (require.main === module) { From f658f35de01d37247c89cf2579c474f3a816597a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 09:59:00 +0200 Subject: [PATCH 0768/4355] more codeowners tweaks (#269658) --- .github/CODEOWNERS | 92 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 295738106b4..9ab481c8538 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,11 +9,48 @@ .github/workflows/pr-win32-test.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 .github/workflows/pr.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 .github/workflows/telemetry.yml @lramos15 @lszomoru @joaomoreno -src/vs/base/browser/ui/tree/** @joaomoreno @benibenj + +# Base Utilities +src/vs/base/common/extpath.ts @bpasero @deepak1556 +src/vs/base/common/glob.ts @bpasero @deepak1556 +src/vs/base/common/path.ts @bpasero @deepak1556 +src/vs/base/common/stream.ts @bpasero @deepak1556 +src/vs/base/browser/domSanitize.ts @mjbvz +src/vs/base/node/pfs.ts @bpasero @deepak1556 +src/vs/base/parts/contextmenu/** @bpasero @deepak1556 +src/vs/base/parts/ipc/** @bpasero @deepak1556 +src/vs/base/parts/sandbox/** @bpasero @deepak1556 +src/vs/base/parts/storage/** @bpasero @deepak1556 + +# Base Widgets +src/vs/base/browser/ui/grid/** @joaomoreno @benibenj src/vs/base/browser/ui/list/** @joaomoreno @benibenj src/vs/base/browser/ui/sash/** @joaomoreno @benibenj src/vs/base/browser/ui/splitview/** @joaomoreno @benibenj -src/vs/base/browser/ui/grid/** @joaomoreno @benibenj +src/vs/base/browser/ui/table/** @joaomoreno @benibenj +src/vs/base/browser/ui/tree/** @joaomoreno @benibenj + +# Platform +src/vs/platform/auxiliaryWindow/** @bpasero @benibenj +src/vs/platform/backup/** @bpasero @Tyriar +src/vs/platform/editor/** @bpasero @benibenj +src/vs/platform/environment/** @bpasero @deepak1556 +src/vs/platform/files/** @bpasero @deepak1556 +src/vs/platform/ipc/** @bpasero @deepak1556 +src/vs/platform/launch/** @bpasero @deepak1556 +src/vs/platform/lifecycle/** @bpasero @deepak1556 +src/vs/platform/menubar/** @bpasero @deepak1556 +src/vs/platform/native/** @bpasero @deepak1556 +src/vs/platform/sharedProcess/** @bpasero @deepak1556 +src/vs/platform/state/** @bpasero @deepak1556 +src/vs/platform/storage/** @bpasero @deepak1556 +src/vs/platform/utilityProcess/** @bpasero @deepak1556 +src/vs/platform/window/** @bpasero @deepak1556 +src/vs/platform/windows/** @bpasero @deepak1556 +src/vs/platform/workspace/** @bpasero @sandy081 +src/vs/platform/workspaces/** @bpasero @sandy081 + +# Bootstrap src/bootstrap-cli.ts @bpasero @deepak1556 src/bootstrap-esm.ts @bpasero @deepak1556 src/bootstrap-fork.ts @bpasero @deepak1556 @@ -25,30 +62,47 @@ src/cli.ts @bpasero @deepak1556 src/main.ts @bpasero @deepak1556 src/server-cli.ts @bpasero @deepak1556 src/server-main.ts @bpasero @deepak1556 -src/vs/base/browser/domSanitize.ts @mjbvz -src/vs/base/parts/sandbox/** @bpasero @deepak1556 -src/vs/base/parts/storage/** @bpasero @deepak1556 -src/vs/platform/backup/** @bpasero -src/vs/platform/editor/** @bpasero @benibenj -src/vs/platform/files/** @bpasero @deepak1556 -src/vs/base/node/pfs.ts @bpasero @deepak1556 + +# Electron Main src/vs/code/** @bpasero @deepak1556 -src/vs/workbench/services/auxiliaryWindow/** @bpasero + +# Workbench Services +src/vs/workbench/services/activity/** @bpasero @benibenj +src/vs/workbench/services/auxiliaryWindow/** @bpasero @benibenj +src/vs/workbench/services/chat/** @bpasero @sandy081 +src/vs/workbench/services/contextmenu/** @bpasero @benibenj +src/vs/workbench/services/dialogs/** @alexr00 @bpasero src/vs/workbench/services/editor/** @bpasero @benibenj -src/vs/workbench/services/files/** @bpasero -src/vs/workbench/services/history/** @bpasero -src/vs/workbench/services/host/** @bpasero +src/vs/workbench/services/environment/** @bpasero @deepak1556 +src/vs/workbench/services/files/** @bpasero @deepak1556 +src/vs/workbench/services/filesConfiguration/** @bpasero @benibenj +src/vs/workbench/services/history/** @bpasero @benibenj +src/vs/workbench/services/host/** @bpasero @deepak1556 +src/vs/workbench/services/label/** @bpasero @benibenj src/vs/workbench/services/layout/** @bpasero @benibenj -src/vs/workbench/services/lifecycle/** @bpasero -src/vs/workbench/services/notification/** @bpasero -src/vs/workbench/services/untitled/** @bpasero -src/vs/workbench/services/textfile/** @bpasero -src/vs/workbench/services/workingCopy/** @bpasero +src/vs/workbench/services/lifecycle/** @bpasero @deepak1556 +src/vs/workbench/services/notification/** @bpasero @benibenj +src/vs/workbench/services/path/** @bpasero @benibenj +src/vs/workbench/services/progress/** @bpasero @benibenj +src/vs/workbench/services/storage/** @bpasero @deepak1556 +src/vs/workbench/services/textfile/** @bpasero @benibenj +src/vs/workbench/services/textmodelResolver/** @bpasero @jrieken +src/vs/workbench/services/untitled/** @bpasero @benibenj +src/vs/workbench/services/utilityProcess/** @bpasero @deepak1556 +src/vs/workbench/services/views/** @sandy081 @benibenj @bpasero +src/vs/workbench/services/workingCopy/** @bpasero @jrieken +src/vs/workbench/services/workspaces/** @bpasero @sandy081 + +# Workbench Core src/vs/workbench/common/** @bpasero @benibenj @sandy081 src/vs/workbench/browser/** @bpasero @benibenj @sandy081 src/vs/workbench/electron-browser/** @bpasero @benibenj @sandy081 + +# Workbench Contributions +src/vs/workbench/contrib/files/** @lramos15 @bpasero src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno -# ensure the API police is aware of changes to the vscode-dts file +# VS Code API +# Ensure the API team is aware of changes to the vscode-dts file # this is only about the final API, not about proposed API changes src/vscode-dts/vscode.d.ts @jrieken @mjbvz @alexr00 From a1117255f0743814cb227226b854a01a23ef561c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 09:59:29 +0200 Subject: [PATCH 0769/4355] :up: playwright to latest version (#269669) --- package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 409bb95f7d4..e98c2e44f5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "yazl": "^2.4.3" }, "devDependencies": { - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.55.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", @@ -1687,13 +1687,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", + "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.2" + "playwright": "1.55.1" }, "bin": { "playwright": "cli.js" @@ -14062,13 +14062,13 @@ } }, "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", + "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.2" + "playwright-core": "1.55.1" }, "bin": { "playwright": "cli.js" @@ -14094,9 +14094,9 @@ } }, "node_modules/playwright/node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", + "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 1cdb8bf3da3..145cebb932c 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "yazl": "^2.4.3" }, "devDependencies": { - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.55.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", From e39f48a1d54049025301d0ccf854ffcb86f2c182 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 10:11:27 +0200 Subject: [PATCH 0770/4355] debt - avoid `.monaco-workbench` outside `workbench` folder (#269656) * debt - avoid outside folder * Update src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feedback --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/base/browser/ui/list/listWidget.ts | 10 ++++----- src/vs/base/browser/ui/sash/sash.css | 2 +- src/vs/base/browser/ui/splitview/paneview.css | 2 +- src/vs/base/browser/ui/table/table.css | 4 ++-- src/vs/base/browser/ui/tree/abstractTree.ts | 6 ++--- src/vs/base/browser/ui/tree/media/tree.css | 4 ++-- .../contrib/find/browser/findWidget.css | 10 ++++----- .../contrib/folding/browser/folding.css | 8 +++---- .../browser/accessibilityService.ts | 4 ++-- .../browser/actions/media/actions.css | 2 +- .../parts/editor/media/editordroptarget.css | 6 ++--- .../parts/editor/multiEditorTabsControl.ts | 8 +++---- .../media/notificationsToasts.css | 6 ++--- .../parts/statusbar/media/statusbarpart.css | 2 +- .../browser/parts/views/media/views.css | 2 +- .../actions/media/voiceChatActions.css | 8 +++---- .../actions/voiceChatActions.ts | 22 +++++-------------- .../browser/find/simpleFindWidget.css | 4 ++-- .../find/notebookFindReplaceWidget.css | 2 +- .../browser/media/notebookCellChat.css | 1 + .../media/notebookCellInsertToolbar.css | 4 ++-- .../browser/media/notebookFolding.css | 4 ++-- .../browser/media/settingsEditor2.css | 2 +- .../scm/browser/media/dirtydiffDecorator.css | 12 +++++----- .../search/browser/media/searchview.css | 2 +- .../terminal/browser/media/terminal.css | 2 +- .../browser/media/userDataProfilesEditor.css | 4 ++-- .../browser/media/gettingStarted.css | 6 ++--- 28 files changed, 70 insertions(+), 79 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index f47064064c3..fcad5f33c17 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -954,7 +954,7 @@ export class DefaultStyleController implements IStyleController { content.push(` .monaco-drag-image${suffix}, .monaco-list${suffix}:focus .monaco-list-row.focused, - .monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + .context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } `); } @@ -1002,13 +1002,13 @@ export class DefaultStyleController implements IStyleController { content.push(` .monaco-table > .monaco-split-view2, .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before, - .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2, - .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { + .monaco-enable-motion .monaco-table:hover > .monaco-split-view2, + .monaco-enable-motion .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { border-color: ${styles.tableColumnsBorder}; } - .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, - .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { + .monaco-enable-motion .monaco-table > .monaco-split-view2, + .monaco-enable-motion .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { border-color: transparent; } `); diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index 42b0f425787..52419ac7d45 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -111,7 +111,7 @@ background: transparent; } -.monaco-workbench:not(.reduce-motion) .monaco-sash:before { +.monaco-enable-motion .monaco-sash:before { transition: background-color 0.1s ease-out; } diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index 64587284592..7bb4282a227 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -116,7 +116,7 @@ transition-timing-function: ease-out; } -.reduce-motion .monaco-pane-view .split-view-view { +.monaco-reduce-motion .monaco-pane-view .split-view-view { transition-duration: 0s !important; } diff --git a/src/vs/base/browser/ui/table/table.css b/src/vs/base/browser/ui/table/table.css index 2266437d7bc..03dbcb31566 100644 --- a/src/vs/base/browser/ui/table/table.css +++ b/src/vs/base/browser/ui/table/table.css @@ -51,7 +51,7 @@ border-left: 1px solid transparent; } -.monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, -.monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { +.monaco-enable-motion .monaco-table > .monaco-split-view2, +.monaco-enable-motion .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { transition: border-color 0.2s ease-out; } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 3c398d9215f..e826be1ee71 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -2870,10 +2870,10 @@ export abstract class AbstractTree implements IDisposable content.push(`.monaco-list${suffix}.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); content.push(`.monaco-list${suffix}:not(.sticky-scroll-focused) .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { outline: inherit; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.passive-focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.passive-focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused:not(.sticky-scroll-focused) .monaco-tree-sticky-container .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused:not(.sticky-scroll-focused) .monaco-tree-sticky-container .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); } this.styleElement.textContent = content.join('\n'); diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 7bd2f13dbaf..b5330b90041 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -33,7 +33,7 @@ opacity: 0; } -.monaco-workbench:not(.reduce-motion) .monaco-tl-indent > .indent-guide { +.monaco-enable-motion .monaco-tl-indent > .indent-guide { transition: opacity 0.1s linear; } @@ -86,7 +86,7 @@ border-bottom-right-radius: 4px; } -.monaco-workbench:not(.reduce-motion) .monaco-tree-type-filter { +.monaco-enable-motion .monaco-tree-type-filter { transition: top 0.3s; } diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index 5d3ef3277d7..a8f93698513 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -24,7 +24,7 @@ background-color: var(--vscode-editorWidget-background); } -.monaco-workbench.reduce-motion .monaco-editor .find-widget { +.monaco-reduce-motion .monaco-editor .find-widget { transition: transform 0ms linear; } @@ -41,7 +41,7 @@ display: flex; } -.monaco-editor .find-widget.visible { +.monaco-editor .find-widget.visible { transform: translateY(0); } @@ -101,7 +101,7 @@ .monaco-editor .find-widget .monaco-findInput { vertical-align: middle; display: flex; - flex:1; + flex: 1; } .monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element { @@ -201,7 +201,7 @@ /* REDUCED */ .monaco-editor .find-widget.reduced-find-widget .matchesCount { - display:none; + display: none; } /* NARROW (SMALLER THAN REDUCED) */ @@ -219,7 +219,7 @@ .monaco-editor .find-widget.collapsed-find-widget .button.replace, .monaco-editor .find-widget.collapsed-find-widget .button.replace-all, .monaco-editor .find-widget.collapsed-find-widget > .find-part .monaco-findInput .controls { - display:none; + display: none; } .monaco-editor .find-widget.no-results .matchesCount { diff --git a/src/vs/editor/contrib/folding/browser/folding.css b/src/vs/editor/contrib/folding/browser/folding.css index 5f7ab05db78..11ee8010be4 100644 --- a/src/vs/editor/contrib/folding/browser/folding.css +++ b/src/vs/editor/contrib/folding/browser/folding.css @@ -16,10 +16,10 @@ margin-left: 2px; } -.monaco-workbench.reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-manual-collapsed, -.monaco-workbench.reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-manual-expanded, -.monaco-workbench.reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-expanded, -.monaco-workbench.reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-collapsed { +.monaco-reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-manual-collapsed, +.monaco-reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-manual-expanded, +.monaco-reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-expanded, +.monaco-reduce-motion .monaco-editor .margin-view-overlays .codicon-folding-collapsed { transition: initial; } diff --git a/src/vs/platform/accessibility/browser/accessibilityService.ts b/src/vs/platform/accessibility/browser/accessibilityService.ts index b12806bf754..d61976dcefc 100644 --- a/src/vs/platform/accessibility/browser/accessibilityService.ts +++ b/src/vs/platform/accessibility/browser/accessibilityService.ts @@ -70,8 +70,8 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe const updateRootClasses = () => { const reduce = this.isMotionReduced(); - this._layoutService.mainContainer.classList.toggle('reduce-motion', reduce); - this._layoutService.mainContainer.classList.toggle('enable-motion', !reduce); + this._layoutService.mainContainer.classList.toggle('monaco-reduce-motion', reduce); + this._layoutService.mainContainer.classList.toggle('monaco-enable-motion', !reduce); }; updateRootClasses(); diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/browser/actions/media/actions.css index 0d71343d1e0..24f01c279c7 100644 --- a/src/vs/workbench/browser/actions/media/actions.css +++ b/src/vs/workbench/browser/actions/media/actions.css @@ -35,7 +35,7 @@ text-overflow: ellipsis; } -.monaco-workbench:not(.reduce-motion) .screencast-keyboard { +.monaco-workbench.monaco-enable-motion .screencast-keyboard { transition: opacity 0.3s ease-out; } diff --git a/src/vs/workbench/browser/parts/editor/media/editordroptarget.css b/src/vs/workbench/browser/parts/editor/media/editordroptarget.css index 19e93572be0..deb238a8de8 100644 --- a/src/vs/workbench/browser/parts/editor/media/editordroptarget.css +++ b/src/vs/workbench/browser/parts/editor/media/editordroptarget.css @@ -25,7 +25,7 @@ justify-content: center; } -.monaco-workbench:not(.reduce-motion) #monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator { +.monaco-workbench.monaco-enable-motion #monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator { transition: opacity 150ms ease-out; } @@ -37,7 +37,7 @@ opacity: 0; /* hidden initially */ } -.monaco-workbench:not(.reduce-motion) #monaco-workbench-editor-drop-overlay .editor-group-overlay-drop-into-prompt { +.monaco-workbench.monaco-enable-motion #monaco-workbench-editor-drop-overlay .editor-group-overlay-drop-into-prompt { transition: opacity 150ms ease-out; } @@ -50,6 +50,6 @@ font-style: normal; } -.monaco-workbench:not(.reduce-motion) #monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition { +.monaco-workbench.monaco-enable-motion #monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition { transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out; } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 645977c08d9..332fcb39191 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -640,19 +640,19 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - moveEditor(editor: EditorInput, fromTabIndex: number, targeTabIndex: number): void { + moveEditor(editor: EditorInput, fromTabIndex: number, targetTabIndex: number): void { // Move the editor label const editorLabel = this.tabLabels[fromTabIndex]; this.tabLabels.splice(fromTabIndex, 1); - this.tabLabels.splice(targeTabIndex, 0, editorLabel); + this.tabLabels.splice(targetTabIndex, 0, editorLabel); // Redraw tabs in the range of the move this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { this.redrawTab(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar); }, - Math.min(fromTabIndex, targeTabIndex), // from: smallest of fromTabIndex/targeTabIndex - Math.max(fromTabIndex, targeTabIndex) // to: largest of fromTabIndex/targeTabIndex + Math.min(fromTabIndex, targetTabIndex), // from: smallest of fromTabIndex/targetTabIndex + Math.max(fromTabIndex, targetTabIndex) // to: largest of fromTabIndex/targetTabIndex ); // Moving an editor requires a layout to keep the active editor visible diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 033a0026ebd..d14aab15325 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -40,8 +40,8 @@ border-radius: 4px; } -.monaco-workbench.reduce-motion > .notifications-toasts .notification-toast-container > .notification-toast { - transition: transform 0ms ease-out, opacity 0ms ease-out; +.monaco-workbench.monaco-reduce-motion > .notifications-toasts .notification-toast-container > .notification-toast { + transition: transform 0ms ease-out, opacity 0ms ease-out; } .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in { @@ -52,7 +52,7 @@ .monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in-done { opacity: 1; transform: none; - transition: none; + transition: none; } /* Icons */ diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 5f69e79224f..b3396ad9530 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,7 +13,7 @@ overflow: hidden; } -.monaco-workbench:not(.reduce-motion) .part.statusbar { +.monaco-workbench.monaco-enable-motion .part.statusbar { transition: background-color 0.15s ease-out; } diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 9111b05e6b9..815cf995f24 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -101,7 +101,7 @@ max-width: 300px; } -.monaco-workbench:not(.reduce-motion) .pane > .pane-body .welcome-view-content > .button-container { +.monaco-workbench.monaco-enable-motion .pane > .pane-body .welcome-view-content > .button-container { transition: 0.2s max-width ease-out; } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css index beae62f5939..a655da9e21f 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/media/voiceChatActions.css @@ -22,16 +22,16 @@ /* * Clear animation styles when reduced motion is enabled. */ -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled), -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { +.monaco-workbench.monaco-reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled), +.monaco-workbench.monaco-reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { animation: none; } /* * Replace with "stop" icon when reduced motion is enabled. */ -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench.monaco-reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench.monaco-reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: var(--vscode-icon-debug-stop-content); font-family: var(--vscode-icon-debug-stop-font-family); } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index 2b4dde3220c..06eea8a31d7 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -1264,8 +1264,8 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" or "pulse" icon when speech-to-text or text-to-speech is in progress that glows via outline. collector.addRule(` - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled), - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled), + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { color: ${activeRecordingColor}; outline: 1px solid ${activeRecordingColor}; outline-offset: -1px; @@ -1273,8 +1273,8 @@ registerThemingParticipant((theme, collector) => { border-radius: 50%; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; @@ -1283,23 +1283,13 @@ registerThemingParticipant((theme, collector) => { height: 16px; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::after, + .monaco-workbench.monaco-enable-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { outline: 2px solid ${activeRecordingColor}; outline-offset: -1px; animation: pulseAnimation 1500ms cubic-bezier(0.75, 0, 0.25, 1) infinite; } - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-sync.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { - position: absolute; - outline: 1px solid ${activeRecordingColor}; - outline-offset: 2px; - border-radius: 50%; - width: 16px; - height: 16px; - } - @keyframes pulseAnimation { 0% { outline-width: 2px; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css index 02512f30b5b..5f1597b9de2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css @@ -37,7 +37,7 @@ font-size: 12px; } -.monaco-workbench.reduce-motion .monaco-editor .find-widget { +.monaco-workbench.monaco-reduce-motion .monaco-editor .find-widget { transition: top 0ms linear; } @@ -107,7 +107,7 @@ div.simple-find-part-wrapper div.button:hover:not(.disabled) { border-bottom-left-radius: 4px; } -.monaco-workbench .simple-find-part .monaco-sash.vertical:before{ +.monaco-workbench .simple-find-part .monaco-sash.vertical:before { width: 2px; left: calc(50% - (var(--vscode-sash-hover-size) / 4)); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css index 14a548dbeb7..65208ad34b0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css @@ -21,7 +21,7 @@ border-bottom-right-radius: 4px; } -.monaco-workbench.reduce-motion .simple-fr-find-part-wrapper { +.monaco-workbench.monaco-reduce-motion .simple-fr-find-part-wrapper { transition: top 0ms linear; } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css index 68974e01dc1..97830695b83 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css @@ -180,6 +180,7 @@ } .monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .markdownMessage .message { + line-clamp: initial; -webkit-line-clamp: initial; -webkit-box-orient: vertical; overflow: hidden; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookCellInsertToolbar.css b/src/vs/workbench/contrib/notebook/browser/media/notebookCellInsertToolbar.css index 67625757e7c..13a11267e68 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookCellInsertToolbar.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellInsertToolbar.css @@ -23,8 +23,8 @@ margin: 0 16px 0 16px; } -.monaco-workbench:not(.reduce-motion) .notebookOverlay .cell-list-top-cell-toolbar-container, -.monaco-workbench:not(.reduce-motion) .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { +.monaco-workbench.monaco-enable-motion .notebookOverlay .cell-list-top-cell-toolbar-container, +.monaco-workbench.monaco-enable-motion .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { transition: opacity 0.3s ease-in-out; } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css index 88bfc96b8f6..3ed861d0a02 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookFolding.css @@ -7,8 +7,8 @@ opacity: 0; } -.monaco-workbench:not(.reduce-motion) .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .notebook-folding-indicator.mouseover .codicon.codicon-notebook-expanded { - transition: opacity 0.1 s; +.monaco-workbench.monaco-enable-motion .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .notebook-folding-indicator.mouseover .codicon.codicon-notebook-expanded { + transition: opacity 0.1s; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .markdown-cell-hover .notebook-folding-indicator.mouseover .codicon.codicon-notebook-expanded { diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 633df502e9a..241b4758fe6 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -276,7 +276,7 @@ justify-content: center; } -.monaco-workbench:not(.reduce-motion) .settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon { +.monaco-workbench.monaco-enable-motion .settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon { transition: opacity .3s; } diff --git a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css index d1490f77723..1cea03b4057 100644 --- a/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css +++ b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css @@ -16,7 +16,7 @@ left: -2px; } -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-glyph:before { +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-glyph:before { transition: width 80ms linear, left 80ms linear, transform 80ms linear; } @@ -101,9 +101,9 @@ background-image: linear-gradient(45deg, var(--vscode-editorGutter-modifiedSecondaryBackground) 25%, var(--vscode-editorGutter-background) 25%, var(--vscode-editorGutter-background) 50%, var(--vscode-editorGutter-modifiedSecondaryBackground) 50%, var(--vscode-editorGutter-modifiedSecondaryBackground) 75%, var(--vscode-editorGutter-background) 75%, var(--vscode-editorGutter-background)); } -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-added, -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-modified, -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-deleted { +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-added, +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-modified, +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-deleted { transition: opacity 0.5s; } @@ -134,7 +134,7 @@ border-left: 4px solid var(--vscode-editorGutter-deletedSecondaryBackground); } -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-deleted:after { +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-deleted:after { transition: border-top-width 80ms linear, border-bottom-width 80ms linear, bottom 80ms linear, opacity 0.5s; } @@ -153,7 +153,7 @@ background: var(--vscode-editorGutter-deletedSecondaryBackground); } -.monaco-workbench:not(.reduce-motion) .monaco-editor .dirty-diff-deleted:before { +.monaco-workbench.monaco-enable-motion .monaco-editor .dirty-diff-deleted:before { transition: height 80ms linear; } diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 9fa7043fcd5..814a993312e 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -172,8 +172,8 @@ .search-view .message.ai-keywords { display: -webkit-box; -webkit-box-orient: vertical; - -webkit-line-clamp: 2; line-clamp: 2; + -webkit-line-clamp: 2; overflow: hidden; text-overflow: ellipsis; white-space: normal; diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 9f722b50f68..90d55597fb0 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -422,7 +422,7 @@ opacity: 0; /* hidden initially */ z-index: 34; } -.monaco-workbench:not(.reduce-motion) .pane-body.integrated-terminal .terminal-drop-overlay { +.monaco-workbench.monaco-enable-motion .pane-body.integrated-terminal .terminal-drop-overlay { transition: left 70ms ease-out, right 70ms ease-out, top 70ms ease-out, bottom 70ms ease-out, opacity 150ms ease-out; } .monaco-workbench .pane-body.integrated-terminal .terminal-group > .monaco-split-view2.horizontal .terminal-drop-overlay.drop-before { diff --git a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfilesEditor.css b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfilesEditor.css index 69245662b44..42debd6aca7 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfilesEditor.css +++ b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfilesEditor.css @@ -431,7 +431,7 @@ color: inherit !important; } -.monaco-workbench:not(.reduce-motion) .profiles-editor .contents-container .profile-body .profile-tree .monaco-list-row:hover .profile-content-tree-header, -.monaco-workbench:not(.reduce-motion) .profiles-editor .contents-container .profile-body .profile-tree .monaco-list-row:hover .profile-associations-table .monaco-table > .monaco-split-view2 { +.monaco-workbench.monaco-enable-motion .profiles-editor .contents-container .profile-body .profile-tree .monaco-list-row:hover .profile-content-tree-header, +.monaco-workbench.monaco-enable-motion .profiles-editor .contents-container .profile-body .profile-tree .monaco-list-row:hover .profile-associations-table .monaco-table > .monaco-split-view2 { border-color: var(--vscode-tree-tableColumnsBorder) !important; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index 692952e1dcb..4690c59ef3f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -98,8 +98,8 @@ transition: left 0.25s, opacity 0.25s; } -.monaco-workbench.reduce-motion .part.editor > .content .gettingStartedContainer .gettingStartedSlide, -.monaco-workbench.reduce-motion .part.editor > .content .gettingStartedContainer.animatable .gettingStartedSlide { +.monaco-workbench.monaco-reduce-motion .part.editor > .content .gettingStartedContainer .gettingStartedSlide, +.monaco-workbench.monaco-reduce-motion .part.editor > .content .gettingStartedContainer.animatable .gettingStartedSlide { /* keep consistant with SLIDE_TRANSITION_TIME_MS in gettingStarted.ts */ transition: left 0.0s, opacity 0.0s; } @@ -895,7 +895,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .step-description-container .monaco-button, .monaco-workbench .part.editor > .content .gettingStartedContainer .max-lines-3 { - /* Supported everywhere: https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp#browser_compatibility */ + line-clamp: 3; -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; From 3a1b1ed83c429951b529b630395ab4bfd7d76841 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:27:59 +0000 Subject: [PATCH 0771/4355] Disable double-click to reveal source for .copilotmd files in markdown preview (#267517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * Disable double-click behavior for .copilotmd files in markdown preview Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> * Update extensions/markdown-language-features/preview-src/index.ts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> Co-authored-by: João Moreno --- extensions/markdown-language-features/preview-src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 8bb5440ffe1..62fff5b7da0 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -304,6 +304,11 @@ document.addEventListener('dblclick', event => { return; } + // Disable double-click to switch editor for .copilotmd files + if (documentResource.endsWith('.copilotmd')) { + return; + } + // Ignore clicks on links for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { if (node.tagName === 'A') { From ac40d1716466566a7c7e9bc52fd2c40eb5ef723c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 10:30:10 +0200 Subject: [PATCH 0772/4355] debt - inline context menu contribution into windows (#269672) * debt - inline context menu contribution into windows * fix compile * tweaks --- src/vs/workbench/browser/window.ts | 47 +++++++++++++------ .../browser/contextmenu.contribution.ts | 28 ----------- src/vs/workbench/electron-browser/window.ts | 8 ++-- .../browser/auxiliaryWindowService.ts | 14 ++++-- .../auxiliaryWindowService.ts | 14 ++++-- src/vs/workbench/test/browser/window.test.ts | 4 +- .../test/browser/workbenchTestServices.ts | 15 +++++- src/vs/workbench/workbench.common.main.ts | 3 -- 8 files changed, 71 insertions(+), 62 deletions(-) delete mode 100644 src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 70e83ad4396..a45f70173e1 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -32,6 +32,7 @@ import { createSingleCallFunction } from '../../base/common/functional.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { IWorkbenchEnvironmentService } from '../services/environment/common/environmentService.js'; import { MarkdownString } from '../../base/common/htmlContent.js'; +import { IContextMenuService } from '../../platform/contextview/browser/contextView.js'; export abstract class BaseWindow extends Disposable { @@ -42,7 +43,9 @@ export abstract class BaseWindow extends Disposable { targetWindow: CodeWindow, dom = { getWindowsCount, getWindows }, /* for testing */ @IHostService protected readonly hostService: IHostService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IWorkbenchLayoutService protected readonly layoutService: IWorkbenchLayoutService, ) { super(); @@ -50,6 +53,7 @@ export abstract class BaseWindow extends Disposable { this.enableMultiWindowAwareTimeout(targetWindow, dom); this.registerFullScreenListeners(targetWindow.vscodeWindowId); + this.registerContextMenuListeners(targetWindow); } //#region focus handling in multi-window applications @@ -171,17 +175,6 @@ export abstract class BaseWindow extends Disposable { //#endregion - private registerFullScreenListeners(targetWindowId: number): void { - this._register(this.hostService.onDidChangeFullScreen(({ windowId, fullscreen }) => { - if (windowId === targetWindowId) { - const targetWindow = getWindowById(targetWindowId); - if (targetWindow) { - setFullscreen(fullscreen, targetWindow.window); - } - } - })); - } - //#region Confirm on Shutdown static async confirmOnShutdown(accessor: ServicesAccessor, reason: ShutdownReason): Promise { @@ -212,6 +205,29 @@ export abstract class BaseWindow extends Disposable { } //#endregion + + private registerFullScreenListeners(targetWindowId: number): void { + this._register(this.hostService.onDidChangeFullScreen(({ windowId, fullscreen }) => { + if (windowId === targetWindowId) { + const targetWindow = getWindowById(targetWindowId); + if (targetWindow) { + setFullscreen(fullscreen, targetWindow.window); + } + } + })); + } + + private registerContextMenuListeners(targetWindow: Window): void { + if (targetWindow !== mainWindow) { + // we only need to listen in the main window as the code + // will go by the active container and update accordingly + return; + } + + const update = (visible: boolean) => this.layoutService.activeContainer.classList.toggle('context-menu-visible', visible); + this._register(this.contextMenuService.onDidShowContextMenu(() => update(true))); + this._register(this.contextMenuService.onDidHideContextMenu(() => update(false))); + } } export class BrowserWindow extends BaseWindow { @@ -223,11 +239,12 @@ export class BrowserWindow extends BaseWindow { @ILabelService private readonly labelService: ILabelService, @IProductService private readonly productService: IProductService, @IBrowserWorkbenchEnvironmentService private readonly browserEnvironmentService: IBrowserWorkbenchEnvironmentService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IContextMenuService contextMenuService: IContextMenuService, ) { - super(mainWindow, undefined, hostService, browserEnvironmentService); + super(mainWindow, undefined, hostService, browserEnvironmentService, contextMenuService, layoutService); this.registerListeners(); this.create(); diff --git a/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts b/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts deleted file mode 100644 index cd6f33ef323..00000000000 --- a/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; -import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; - -class ContextMenuContribution extends Disposable implements IWorkbenchContribution { - - constructor( - @ILayoutService layoutService: ILayoutService, - @IContextMenuService contextMenuService: IContextMenuService - ) { - super(); - - const update = (visible: boolean) => layoutService.activeContainer.classList.toggle('context-menu-visible', visible); - this._register(contextMenuService.onDidShowContextMenu(() => update(true))); - this._register(contextMenuService.onDidHideContextMenu(() => update(false))); - } -} - -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(ContextMenuContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 5128a9c58a5..11035d0404d 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -79,6 +79,7 @@ import { getWorkbenchContribution } from '../common/contributions.js'; import { DynamicWorkbenchSecurityConfiguration } from '../common/configuration.js'; import { nativeHoverDelegate } from '../../platform/hover/browser/hover.js'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from '../common/theme.js'; +import { IContextMenuService } from '../../platform/contextview/browser/contextView.js'; export class NativeWindow extends BaseWindow { @@ -111,7 +112,7 @@ export class NativeWindow extends BaseWindow { @IOpenerService private readonly openerService: IOpenerService, @INativeHostService private readonly nativeHostService: INativeHostService, @ITunnelService private readonly tunnelService: ITunnelService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IProductService private readonly productService: IProductService, @@ -127,9 +128,10 @@ export class NativeWindow extends BaseWindow { @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IContextMenuService contextMenuService: IContextMenuService, ) { - super(mainWindow, undefined, hostService, nativeEnvironmentService); + super(mainWindow, undefined, hostService, nativeEnvironmentService, contextMenuService, layoutService); this.configuredWindowZoomLevel = this.resolveConfiguredWindowZoomLevel(); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index a20f7cee262..df8ee749c7d 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -18,6 +18,7 @@ import { isFirefox, isWeb } from '../../../../base/common/platform.js'; import Severity from '../../../../base/common/severity.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -117,9 +118,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { stylesHaveLoaded: Barrier, @IConfigurationService private readonly configurationService: IConfigurationService, @IHostService hostService: IHostService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IContextMenuService contextMenuService: IContextMenuService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(window, undefined, hostService, environmentService); + super(window, undefined, hostService, environmentService, contextMenuService, layoutService); this.whenStylesHaveLoaded = stylesHaveLoaded.wait().then(() => undefined); @@ -247,12 +250,13 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili private readonly windows = new Map(); constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService protected readonly layoutService: IWorkbenchLayoutService, @IDialogService protected readonly dialogService: IDialogService, @IConfigurationService protected readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IHostService protected readonly hostService: IHostService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, ) { super(); } @@ -308,7 +312,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili } protected createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesLoaded: Barrier): AuxiliaryWindow { - return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService, this.environmentService); + return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService, this.environmentService, this.contextMenuService, this.layoutService); } private async openWindow(options?: IAuxiliaryWindowOpenOptions): Promise { diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts index 2c01f6fd5cf..31d0c4ef27c 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts @@ -24,6 +24,7 @@ import { getZoomLevel, isFullscreen, setFullscreen } from '../../../../base/brow import { getActiveWindow } from '../../../../base/browser/dom.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; import { isMacintosh } from '../../../../base/common/platform.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -45,9 +46,11 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow { @IInstantiationService private readonly instantiationService: IInstantiationService, @IHostService hostService: IHostService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IDialogService private readonly dialogService: IDialogService + @IDialogService private readonly dialogService: IDialogService, + @IContextMenuService contextMenuService: IContextMenuService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(window, container, stylesHaveLoaded, configurationService, hostService, environmentService); + super(window, container, stylesHaveLoaded, configurationService, hostService, environmentService, contextMenuService, layoutService); if (!isMacintosh) { // For now, limit this to platforms that have clear maximised @@ -143,9 +146,10 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, @IHostService hostService: IHostService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IContextMenuService contextMenuService: IContextMenuService, ) { - super(layoutService, dialogService, configurationService, telemetryService, hostService, environmentService); + super(layoutService, dialogService, configurationService, telemetryService, hostService, environmentService, contextMenuService); } protected override async resolveWindowId(auxiliaryWindow: NativeCodeWindow): Promise { @@ -172,7 +176,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService } protected override createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesHaveLoaded: Barrier): AuxiliaryWindow { - return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService, this.environmentService, this.dialogService); + return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService, this.environmentService, this.dialogService, this.contextMenuService, this.layoutService); } } diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index 3ca9a390494..9082f4b526a 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -10,7 +10,7 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { runWithFakedTimers } from '../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; import { BaseWindow } from '../../browser/window.js'; -import { TestEnvironmentService, TestHostService } from './workbenchTestServices.js'; +import { TestContextMenuService, TestEnvironmentService, TestHostService, TestLayoutService } from './workbenchTestServices.js'; suite('Window', () => { @@ -19,7 +19,7 @@ suite('Window', () => { class TestWindow extends BaseWindow { constructor(window: CodeWindow, dom: { getWindowsCount: () => number; getWindows: () => Iterable }) { - super(window, dom, new TestHostService(), TestEnvironmentService); + super(window, dom, new TestHostService(), TestEnvironmentService, new TestContextMenuService(), new TestLayoutService()); } protected override enableWindowFocusOnElementFocus(): void { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 44bce100049..8c68a10ace0 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IContextMenuDelegate } from '../../../base/browser/contextmenu.js'; import { IDimension } from '../../../base/browser/dom.js'; import { Direction, IViewSize } from '../../../base/browser/ui/grid/grid.js'; import { mainWindow } from '../../../base/browser/window.js'; @@ -58,7 +59,7 @@ import { ConfigurationTarget, IConfigurationService, IConfigurationValue } from import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; import { ContextKeyValue, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; import { ContextMenuService } from '../../../platform/contextview/browser/contextMenuService.js'; -import { IContextMenuService, IContextViewService } from '../../../platform/contextview/browser/contextView.js'; +import { IContextMenuMenuDelegate, IContextMenuService, IContextViewService } from '../../../platform/contextview/browser/contextView.js'; import { ContextViewService } from '../../../platform/contextview/browser/contextViewService.js'; import { IDiagnosticInfo, IDiagnosticInfoOptions } from '../../../platform/diagnostics/common/diagnostics.js'; import { ConfirmResult, IDialogService, IFileDialogService, IOpenDialogOptions, IPickAndOpenOptions, ISaveDialogOptions } from '../../../platform/dialogs/common/dialogs.js'; @@ -2196,3 +2197,15 @@ export class TestChatEntitlementService implements IChatEntitlementService { onDidChangeAnonymous = Event.None; readonly anonymousObs = observableValue({}, false); } + +export class TestContextMenuService implements IContextMenuService { + + _serviceBrand: undefined; + + readonly onDidShowContextMenu = Event.None; + readonly onDidHideContextMenu = Event.None; + + showContextMenu(delegate: IContextMenuDelegate | IContextMenuMenuDelegate): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 61d63db7b73..e488669f075 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -195,9 +195,6 @@ import './contrib/preferences/browser/preferencesSearch.js'; // Performance import './contrib/performance/browser/performance.contribution.js'; -// Context Menus -import './contrib/contextmenu/browser/contextmenu.contribution.js'; - // Notebook import './contrib/notebook/browser/notebook.contribution.js'; From 7166c84d35a861f1b1d73c3d7644ebffb4ec30f5 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 3 Oct 2025 11:47:43 +0200 Subject: [PATCH 0773/4355] fixes https://github.com/microsoft/vscode-copilot-issues/issues/297 --- .../browser/model/inlineCompletionsModel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 53b34df208e..8dbb0c1fc9d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -823,6 +823,9 @@ export class InlineCompletionsModel extends Disposable { if (this.showCollapsed.read(reader)) { return false; } + if (this._tabShouldIndent.read(reader)) { + return false; + } if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader)) { return true; } @@ -832,9 +835,6 @@ export class InlineCompletionsModel extends Disposable { if (this._jumpedToId.read(reader) === s.inlineCompletion.semanticId) { return true; } - if (this._tabShouldIndent.read(reader)) { - return false; - } return s.cursorAtInlineEdit.read(reader); }); From 1cbfa380c693bfdf946f79e24c73fc94945544b8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:50:00 +0200 Subject: [PATCH 0774/4355] Fix cursor positioning for multiline chat input query (#269067) * Initial plan * Fix cursor positioning for multiline chat input Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> * Use getLastPosition helper for consistency Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> * Add null check for model as suggested by @benibenj Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index b7829a6a960..53fa57009d9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -985,7 +985,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge setValue(value: string, transient: boolean): void { this.inputEditor.setValue(value); // always leave cursor at the end - this.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); + const model = this.inputEditor.getModel(); + if (model) { + this.inputEditor.setPosition(getLastPosition(model)); + } if (!transient) { this.saveCurrentValue(this.getInputState()); From daf6052959c2d1c84f268b13b056eb430d557a45 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 12:50:20 +0200 Subject: [PATCH 0775/4355] lint - report layer issues in CSS (#269671) --- build/hygiene.js | 29 ++++++++++--------- .../lib/stylelint/vscode-known-variables.json | 8 +++++ build/stylelint.js | 14 +++++++++ .../browser/services/hoverService/hover.css | 2 ++ .../symbolIcons/browser/symbolIcons.css | 2 ++ 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/build/hygiene.js b/build/hygiene.js index 961b085e684..0a1cc94278c 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -23,9 +23,9 @@ const copyrightHeaderLines = [ /** * @param {string[] | NodeJS.ReadWriteStream} some - * @param {boolean} linting + * @param {boolean} runEslint */ -function hygiene(some, linting = true) { +function hygiene(some, runEslint = true) { const eslint = require('./gulp-eslint'); const gulpstylelint = require('./stylelint'); const formatter = require('./lib/formatter'); @@ -156,7 +156,7 @@ function hygiene(some, linting = true) { const unicodeFilterStream = filter(unicodeFilter, { restore: true }); const result = input - .pipe(filter((f) => !f.stat.isDirectory())) + .pipe(filter((f) => Boolean(f.stat && !f.stat.isDirectory()))) .pipe(snapshotFilter) .pipe(yarnLockFilter) .pipe(productJsonFilter) @@ -175,7 +175,7 @@ function hygiene(some, linting = true) { result.pipe(filter(tsFormattingFilter)).pipe(formatting) ]; - if (linting) { + if (runEslint) { streams.push( result .pipe(filter(eslintFilter)) @@ -186,18 +186,19 @@ function hygiene(some, linting = true) { }) ) ); - streams.push( - result.pipe(filter(stylelintFilter)).pipe(gulpstylelint(((message, isError) => { - if (isError) { - console.error(message); - errorCount++; - } else { - console.warn(message); - } - }))) - ); } + streams.push( + result.pipe(filter(stylelintFilter)).pipe(gulpstylelint(((message, isError) => { + if (isError) { + console.error(message); + errorCount++; + } else { + console.warn(message); + } + }))) + ); + let count = 0; return es.merge(...streams).pipe( es.through( diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 15d05bad9ed..c55438ff451 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -54,6 +54,13 @@ "--vscode-chat-avatarForeground", "--vscode-chat-checkpointSeparator", "--vscode-chat-editedFileForeground", + "--vscode-chat-font-family", + "--vscode-chat-font-size-body-l", + "--vscode-chat-font-size-body-m", + "--vscode-chat-font-size-body-s", + "--vscode-chat-font-size-body-xl", + "--vscode-chat-font-size-body-xs", + "--vscode-chat-font-size-body-xxl", "--vscode-chat-linesAddedForeground", "--vscode-chat-linesRemovedForeground", "--vscode-chat-requestBackground", @@ -376,6 +383,7 @@ "--vscode-inlineChat-background", "--vscode-inlineChat-border", "--vscode-inlineChat-foreground", + "--vscode-inlineChat-regionHighlight", "--vscode-inlineChat-shadow", "--vscode-inlineChatDiff-inserted", "--vscode-inlineChatDiff-removed", diff --git a/build/stylelint.js b/build/stylelint.js index 03c1611c4e0..c2f0e4482a2 100644 --- a/build/stylelint.js +++ b/build/stylelint.js @@ -19,16 +19,30 @@ module.exports = gulpstylelint; function gulpstylelint(reporter) { const variableValidator = getVariableNameValidator(); let errorCount = 0; + const monacoWorkbenchPattern = /\.monaco-workbench/; + const restrictedPathPattern = /^src[\/\\]vs[\/\\](base|platform|editor)[\/\\]/; + const layerCheckerDisablePattern = /\/\*\s*stylelint-disable\s+layer-checker\s*\*\//; + return es.through(function (file) { /** @type {string[]} */ const lines = file.__lines || file.contents.toString('utf8').split(/\r\n|\r|\n/); file.__lines = lines; + const isRestrictedPath = restrictedPathPattern.test(file.relative); + + // Check if layer-checker is disabled for the entire file + const isLayerCheckerDisabled = lines.some(line => layerCheckerDisablePattern.test(line)); + lines.forEach((line, i) => { variableValidator(line, unknownVariable => { reporter(file.relative + '(' + (i + 1) + ',1): Unknown variable: ' + unknownVariable, true); errorCount++; }); + + if (isRestrictedPath && !isLayerCheckerDisabled && monacoWorkbenchPattern.test(line)) { + reporter(file.relative + '(' + (i + 1) + ',1): The class .monaco-workbench cannot be used in files under src/vs/{base,platform,editor} because only src/vs/workbench applies it', true); + errorCount++; + } }); this.emit('data', file); diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 6b3b5c53262..553e7864bc2 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* stylelint-disable layer-checker */ + .monaco-workbench .workbench-hover { position: relative; font-size: 13px; diff --git a/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.css b/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.css index 475006090b4..006bcb2f730 100644 --- a/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.css +++ b/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.css @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* stylelint-disable layer-checker */ + .monaco-editor .codicon.codicon-symbol-array, .monaco-workbench .codicon.codicon-symbol-array { color: var(--vscode-symbolIcon-arrayForeground); } .monaco-editor .codicon.codicon-symbol-boolean, From 82d335bbbc48fb0019fed633930d2e8682ab7d1e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:12:03 +0200 Subject: [PATCH 0776/4355] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20consolidate?= =?UTF-8?q?=20git=20blame=20and=20timeline=20hover=20code=20(#269689)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - 💄 consolidate git blame and timeline hover code * Git - Delete code that was commented out --- extensions/git/src/blame.ts | 83 ++++--------------- extensions/git/src/historyProvider.ts | 110 ++++++++++++++++++++++++- extensions/git/src/timelineProvider.ts | 77 +++-------------- 3 files changed, 132 insertions(+), 138 deletions(-) diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index 379f70fd3a5..f5814370251 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -15,8 +15,7 @@ import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { AvatarQuery, AvatarQueryCommit } from './api/git'; import { LRUCache } from './cache'; - -const AVATAR_SIZE = 20; +import { AVATAR_SIZE, getHistoryItemHover, getHistoryItemHoverCommitHashCommands, processHistoryItemRemoteHoverCommands } from './historyProvider'; function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean { return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive); @@ -242,80 +241,26 @@ export class GitBlameController { this._model, repository, commitInformation?.message ?? blameInformation.subject ?? ''); } - const markdownString = new MarkdownString(); - markdownString.isTrusted = true; - markdownString.supportThemeIcons = true; - - // Author, date const hash = commitInformation?.hash ?? blameInformation.hash; const authorName = commitInformation?.authorName ?? blameInformation.authorName; const authorEmail = commitInformation?.authorEmail ?? blameInformation.authorEmail; const authorDate = commitInformation?.authorDate ?? blameInformation.authorDate; - const avatar = commitAvatar ? `![${authorName}](${commitAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)'; - - - if (authorName) { - if (authorEmail) { - const emailTitle = l10n.t('Email'); - markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`); - } else { - markdownString.appendMarkdown(`${avatar} **${authorName}**`); - } - - if (authorDate) { - const dateString = new Date(authorDate).toLocaleString(undefined, { - year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' - }); - markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`); - } - - markdownString.appendMarkdown('\n\n'); - } - - // Subject | Message const message = commitMessageWithLinks ?? commitInformation?.message ?? blameInformation.subject ?? ''; - markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`); - markdownString.appendMarkdown(`---\n\n`); - - // Short stats - if (commitInformation?.shortStat) { - markdownString.appendMarkdown(`${commitInformation.shortStat.files === 1 ? - l10n.t('{0} file changed', commitInformation.shortStat.files) : - l10n.t('{0} files changed', commitInformation.shortStat.files)}`); - - if (commitInformation.shortStat.insertions) { - markdownString.appendMarkdown(`, ${commitInformation.shortStat.insertions === 1 ? - l10n.t('{0} insertion{1}', commitInformation.shortStat.insertions, '(+)') : - l10n.t('{0} insertions{1}', commitInformation.shortStat.insertions, '(+)')}`); - } - - if (commitInformation.shortStat.deletions) { - markdownString.appendMarkdown(`, ${commitInformation.shortStat.deletions === 1 ? - l10n.t('{0} deletion{1}', commitInformation.shortStat.deletions, '(-)') : - l10n.t('{0} deletions{1}', commitInformation.shortStat.deletions, '(-)')}`); - } - - markdownString.appendMarkdown(`\n\n---\n\n`); - } // Commands - markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash, documentUri]))} "${l10n.t('Open Commit')}")`); - markdownString.appendMarkdown(' '); - markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); - - // Remote hover commands - if (remoteHoverCommands.length > 0) { - markdownString.appendMarkdown('  |  '); - - const remoteCommandsMarkdown = remoteHoverCommands - .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - markdownString.appendMarkdown(remoteCommandsMarkdown.join(' ')); - } - - markdownString.appendMarkdown('  |  '); - markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`); - - return markdownString; + const commands: Command[][] = [ + getHistoryItemHoverCommitHashCommands(documentUri, hash), + processHistoryItemRemoteHoverCommands(remoteHoverCommands, hash) + ]; + + commands.push([{ + title: `$(gear)`, + tooltip: l10n.t('Open Settings'), + command: 'workbench.action.openSettings', + arguments: ['git.blame'] + }] satisfies Command[]); + + return getHistoryItemHover(commitAvatar, authorName, authorEmail, authorDate, message, commitInformation?.shortStat, commands); } private _onDidChangeConfiguration(e?: ConfigurationChangeEvent): void { diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 1d01a9203a3..77512ecb465 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent, MarkdownString, Command } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, subject, truncate } from './util'; +import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, fromNow, getCommitShortHash, subject, truncate } from './util'; import { toMultiFileDiffEditorUris } from './uri'; import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git'; import { emojify, ensureEmojis } from './emoji'; -import { Commit } from './git'; +import { Commit, CommitShortStat } from './git'; import { OperationKind, OperationResult } from './operation'; import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { throttle } from './decorators'; @@ -590,3 +590,107 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec dispose(this.disposables); } } + +export const AVATAR_SIZE = 20; + +export function getHistoryItemHoverCommitHashCommands(documentUri: Uri, hash: string): Command[] { + return [{ + title: `$(git-commit) ${getCommitShortHash(documentUri, hash)}`, + tooltip: l10n.t('Open Commit'), + command: 'git.viewCommit', + arguments: [documentUri, hash, documentUri] + }, { + title: `$(copy)`, + tooltip: l10n.t('Copy Commit Hash'), + command: 'git.copyContentToClipboard', + arguments: [hash] + }] satisfies Command[]; +} + +export function processHistoryItemRemoteHoverCommands(commands: Command[], hash: string): Command[] { + return commands.map(command => ({ + ...command, + arguments: [...command.arguments ?? [], hash] + } satisfies Command)); +} + +export function getHistoryItemHover(authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string, shortStats: CommitShortStat | undefined, commands: Command[][] | undefined): MarkdownString { + const markdownString = new MarkdownString('', true); + markdownString.isTrusted = true; + + if (authorName) { + const avatar = authorAvatar ? `![${authorName}](${authorAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)'; + + if (authorEmail) { + const emailTitle = l10n.t('Email'); + markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`); + } else { + markdownString.appendMarkdown(`${avatar} **${authorName}**`); + } + + if (authorDate) { + const dateString = new Date(authorDate).toLocaleString(undefined, { + year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' + }); + markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`); + } + + markdownString.appendMarkdown('\n\n'); + } + + // Subject | Message + markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`); + markdownString.appendMarkdown(`---\n\n`); + + // Short stats + if (shortStats) { + markdownString.appendMarkdown(`${shortStats.files === 1 ? + l10n.t('{0} file changed', shortStats.files) : + l10n.t('{0} files changed', shortStats.files)}`); + + if (shortStats.insertions) { + markdownString.appendMarkdown(`, ${shortStats.insertions === 1 ? + l10n.t('{0} insertion{1}', shortStats.insertions, '(+)') : + l10n.t('{0} insertions{1}', shortStats.insertions, '(+)')}`); + } + + if (shortStats.deletions) { + markdownString.appendMarkdown(`, ${shortStats.deletions === 1 ? + l10n.t('{0} deletion{1}', shortStats.deletions, '(-)') : + l10n.t('{0} deletions{1}', shortStats.deletions, '(-)')}`); + } + + markdownString.appendMarkdown(`\n\n---\n\n`); + } + + // Commands + if (commands && commands.length > 0) { + for (let index = 0; index < commands.length; index++) { + if (index !== 0) { + markdownString.appendMarkdown('  |  '); + } + + const commandsMarkdown = commands[index] + .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify(command.arguments))} "${command.tooltip}")`); + markdownString.appendMarkdown(commandsMarkdown.join(' ')); + } + } + + // markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash, documentUri]))} "${l10n.t('Open Commit')}")`); + // markdownString.appendMarkdown(' '); + // markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); + + // // Remote hover commands + // if (commands && commands.length > 0) { + // markdownString.appendMarkdown('  |  '); + + // const remoteCommandsMarkdown = commands + // .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); + // markdownString.appendMarkdown(remoteCommandsMarkdown.join(' ')); + // } + + // markdownString.appendMarkdown('  |  '); + // markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`); + + return markdownString; +} diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 0e90abd0082..47000d78e91 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode'; +import { CancellationToken, ConfigurationChangeEvent, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; @@ -11,11 +11,9 @@ import { emojify, ensureEmojis } from './emoji'; import { CommandCenter } from './commands'; import { OperationKind, OperationResult } from './operation'; import { truncate } from './util'; -import { CommitShortStat } from './git'; import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { AvatarQuery, AvatarQueryCommit } from './api/git'; - -const AVATAR_SIZE = 20; +import { getHistoryItemHover, getHistoryItemHoverCommitHashCommands, processHistoryItemRemoteHoverCommands } from './historyProvider'; export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { @@ -54,61 +52,6 @@ export class GitTimelineItem extends TimelineItem { return this.shortenRef(this.previousRef); } - setItemDetails(uri: Uri, hash: string | undefined, shortHash: string | undefined, avatar: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void { - this.tooltip = new MarkdownString('', true); - this.tooltip.isTrusted = true; - - const avatarMarkdown = avatar - ? `![${author}](${avatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` - : '$(account)'; - - if (email) { - const emailTitle = l10n.t('Email'); - this.tooltip.appendMarkdown(`${avatarMarkdown} [**${author}**](mailto:${email} "${emailTitle} ${author}")`); - } else { - this.tooltip.appendMarkdown(`${avatarMarkdown} **${author}**`); - } - - this.tooltip.appendMarkdown(`, $(history) ${date}\n\n`); - this.tooltip.appendMarkdown(`${message}\n\n`); - - if (shortStat) { - this.tooltip.appendMarkdown(`---\n\n`); - - const labels: string[] = []; - if (shortStat.insertions) { - labels.push(`${shortStat.insertions === 1 ? - l10n.t('{0} insertion{1}', shortStat.insertions, '(+)') : - l10n.t('{0} insertions{1}', shortStat.insertions, '(+)')}`); - } - - if (shortStat.deletions) { - labels.push(`${shortStat.deletions === 1 ? - l10n.t('{0} deletion{1}', shortStat.deletions, '(-)') : - l10n.t('{0} deletions{1}', shortStat.deletions, '(-)')}`); - } - - this.tooltip.appendMarkdown(`${labels.join(', ')}\n\n`); - } - - if (hash && shortHash) { - this.tooltip.appendMarkdown(`---\n\n`); - - this.tooltip.appendMarkdown(`[\`$(git-commit) ${shortHash} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash, uri]))} "${l10n.t('Open Commit')}")`); - this.tooltip.appendMarkdown(' '); - this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); - - // Remote commands - if (remoteSourceCommands.length > 0) { - this.tooltip.appendMarkdown('  |  '); - - const remoteCommandsMarkdown = remoteSourceCommands - .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - this.tooltip.appendMarkdown(remoteCommandsMarkdown.join(' ')); - } - } - } - private shortenRef(ref: string): string { if (ref === '' || ref === '~' || ref === 'HEAD') { return ref; @@ -215,13 +158,10 @@ export class GitTimelineProvider implements TimelineProvider { commits.splice(commits.length - 1, 1); } - const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); - const config = workspace.getConfiguration('git', Uri.file(repo.root)); const dateType = config.get<'committed' | 'authored'>('timeline.date'); const showAuthor = config.get('timeline.showAuthor'); const showUncommitted = config.get('timeline.showUncommitted'); - const commitShortHashLength = config.get('commitShortHashLength') ?? 7; const openComparison = l10n.t('Open Comparison'); @@ -254,10 +194,15 @@ export class GitTimelineProvider implements TimelineProvider { item.description = c.authorName; } - const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands : []; + const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands ?? [] : []; const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(this.model, repo, message) ?? message; - item.setItemDetails(uri, c.hash, truncate(c.hash, commitShortHashLength, false), avatars?.get(c.hash), c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands); + const commands: Command[][] = [ + getHistoryItemHoverCommitHashCommands(uri, c.hash), + processHistoryItemRemoteHoverCommands(commitRemoteSourceCommands, c.hash) + ]; + + item.tooltip = getHistoryItemHover(avatars?.get(c.hash), c.authorName, c.authorEmail, date, messageWithLinks, c.shortStat, commands); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { @@ -282,7 +227,7 @@ export class GitTimelineProvider implements TimelineProvider { // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new ThemeIcon('git-commit'); item.description = ''; - item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type)); + item.tooltip = getHistoryItemHover(undefined, you, undefined, date, Resource.getStatusText(index.type), undefined, undefined); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { @@ -304,7 +249,7 @@ export class GitTimelineProvider implements TimelineProvider { const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working'); item.iconPath = new ThemeIcon('circle-outline'); item.description = ''; - item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type)); + item.tooltip = getHistoryItemHover(undefined, you, undefined, date, Resource.getStatusText(working.type), undefined, undefined); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { From aaeb288fb0132aa26a0dcdbc9c0102ef15b69b45 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 4 Oct 2025 00:13:14 +0900 Subject: [PATCH 0777/4355] chore: remove unused v8 error event (#269307) --- .../electron-main/utilityProcess.ts | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 937492c6bbb..baeab608b4d 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -329,46 +329,6 @@ export class UtilityProcess extends Disposable { this.onDidExitOrCrashOrKill(); })); - // V8 Error - this._register(Event.fromNodeEventEmitter(process, 'error', (type, location, report) => ({ type, location, report }))(({ type, location, report }) => { - this.log(`crashed due to ${type} from V8 at ${location}`, Severity.Info); - - let addons: string[] = []; - try { - const reportJSON = JSON.parse(report); - addons = reportJSON.sharedObjects - .filter((sharedObject: string) => sharedObject.endsWith('.node')) - .map((addon: string) => { - const index = addon.indexOf('extensions') === -1 ? addon.indexOf('node_modules') : addon.indexOf('extensions'); - return addon.substring(index); - }); - } catch (e) { - // ignore - } - - // Telemetry - type UtilityProcessV8ErrorClassification = { - processtype: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of utility process to understand the origin of the crash better.' }; - error: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of error from the utility process to understand the nature of the crash better.' }; - location: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The source location that triggered the crash to understand the nature of the crash better.' }; - addons: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The list of addons loaded in the utility process to understand the nature of the crash better' }; - owner: 'deepak1556'; - comment: 'Provides insight into V8 sandbox FATAL error caused by native addons.'; - }; - type UtilityProcessV8ErrorEvent = { - processtype: string; - error: string; - location: string; - addons: string[]; - }; - this.telemetryService.publicLog2('utilityprocessv8error', { - processtype: configuration.type, - error: type, - location, - addons - }); - })); - // Child process gone this._register(Event.fromNodeEventEmitter<{ details: Details }>(app, 'child-process-gone', (event, details) => ({ event, details }))(({ details }) => { if (details.type === 'Utility' && details.name === serviceName) { From 8c9771fa0d2ecedbd22b561c073656cd7eb3b689 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 3 Oct 2025 08:14:14 -0700 Subject: [PATCH 0778/4355] mcp: support new light/dark theme for mcp icons (#269437) For merging after endgame --- .../chat/browser/actions/chatToolPicker.ts | 6 +- .../mcp/browser/mcpResourceQuickAccess.ts | 3 +- .../workbench/contrib/mcp/common/mcpIcons.ts | 42 +++++++-- .../mcpLanguageModelToolContribution.ts | 4 +- .../workbench/contrib/mcp/common/mcpTypes.ts | 2 +- .../mcp/common/modelContextProtocol.ts | 9 ++ .../contrib/mcp/test/common/mcpIcons.test.ts | 92 +++++++++++++++---- 7 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 7ba446dcb42..25ec851b2bc 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -317,9 +317,9 @@ export async function showToolsPicker( children, buttons, }; - const iconURI = mcpServer.serverMetadata.get()?.icons.getUrl(22); - if (iconURI) { - bucket.iconPath = { dark: iconURI, light: iconURI }; + const iconPath = mcpServer.serverMetadata.get()?.icons.getUrl(22); + if (iconPath) { + bucket.iconPath = iconPath; } else { bucket.iconClass = ThemeIcon.asClassName(Codicon.mcp); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts index 60a21fa126e..1adc3657382 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.ts @@ -37,8 +37,7 @@ export class McpResourcePickHelper { } public static item(resource: IMcpResource | IMcpResourceTemplate): IQuickPickItem { - const icon = resource.icons.getUrl(22); - const iconPath = icon ? { dark: icon, light: icon } : undefined; + const iconPath = resource.icons.getUrl(22); if (isMcpResourceTemplate(resource)) { return { id: resource.template.template, diff --git a/src/vs/workbench/contrib/mcp/common/mcpIcons.ts b/src/vs/workbench/contrib/mcp/common/mcpIcons.ts index fa0a3c6b149..8bb7e7b112e 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpIcons.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpIcons.ts @@ -18,9 +18,17 @@ const mcpAllowableContentTypes: readonly string[] = [ 'image/gif' ]; +const enum IconTheme { + Light, + Dark, + Any, +} + interface IIcon { /** URI the image can be loaded from */ src: URI; + /** Theme for this icon. */ + theme: IconTheme; /** Sizes of the icon in ascending order. */ sizes: { width: number; height: number }[]; } @@ -80,6 +88,7 @@ export function parseAndValidateMcpIcon(icons: MCP.Icons, launch: McpServerLaunc const sizesArr = typeof icon.sizes === 'string' ? icon.sizes.split(' ') : Array.isArray(icon.sizes) ? icon.sizes : []; result.push({ src: uri, + theme: icon.theme === 'light' ? IconTheme.Light : icon.theme === 'dark' ? IconTheme.Dark : IconTheme.Any, sizes: sizesArr.map(size => { const [widthStr, heightStr] = size.toLowerCase().split('x'); return { width: Number(widthStr) || 0, height: Number(heightStr) || 0 }; @@ -94,7 +103,7 @@ export function parseAndValidateMcpIcon(icons: MCP.Icons, launch: McpServerLaunc export class McpIcons implements IMcpIcons { public static fromStored(icons: StoredMcpIcons | undefined) { - return McpIcons.fromParsed(icons?.map(i => ({ src: URI.revive(i.src), sizes: i.sizes }))); + return McpIcons.fromParsed(icons?.map(i => ({ src: URI.revive(i.src), theme: i.theme, sizes: i.sizes }))); } public static fromParsed(icons: ParsedMcpIcons | undefined) { @@ -103,14 +112,33 @@ export class McpIcons implements IMcpIcons { protected constructor(private readonly _icons: IIcon[]) { } - getUrl(size: number): URI | undefined { + getUrl(size: number): { dark: URI; light?: URI } | undefined { + const dark = this.getSizeWithTheme(size, IconTheme.Dark); + if (dark?.theme === IconTheme.Any) { + return { dark: dark.src }; + } + + const light = this.getSizeWithTheme(size, IconTheme.Light); + if (!light && !dark) { + return undefined; + } + + return { dark: (dark || light)!.src, light: light?.src }; + } + + private getSizeWithTheme(size: number, theme: IconTheme): IIcon | undefined { + let bestOfAnySize: IIcon | undefined; + for (const icon of this._icons) { - const firstWidth = icon.sizes[0]?.width ?? 0; - if (firstWidth > size) { - return icon.src; + if (icon.theme === theme || icon.theme === IconTheme.Any || icon.theme === undefined) { // undefined check for back compat + bestOfAnySize = icon; + + const matchingSize = icon.sizes.find(s => s.width >= size); + if (matchingSize) { + return { ...icon, sizes: [matchingSize] }; + } } } - - return this._icons.at(-1)?.src; + return bestOfAnySize; } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index eea17b02f6f..face26194f1 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -122,11 +122,11 @@ export class McpLanguageModelToolContribution extends Disposable implements IWor const collection = collectionObservable.read(reader); for (const tool of server.tools.read(reader)) { const existing = tools.get(tool.id); - const iconURI = tool.icons.getUrl(22); + const icons = tool.icons.getUrl(22); const toolData: IToolData = { id: tool.id, source: collectionData.value.source, - icon: iconURI ? { dark: iconURI, light: iconURI } : Codicon.tools, + icon: icons || Codicon.tools, // duplicative: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/813 displayName: tool.definition.annotations?.title || tool.definition.title || tool.definition.name, toolReferenceName: tool.referenceName, diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index f566d17a9a9..b7a900b2dcf 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -885,5 +885,5 @@ export interface IMcpToolResourceLinkContents { export interface IMcpIcons { /** Gets the image URI appropriate to the approximate display size */ - getUrl(size: number): URI | undefined; + getUrl(size: number): { dark: URI; light?: URI } | undefined; } diff --git a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts index 3b19f8ff44b..b4e617b0ba4 100644 --- a/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts +++ b/src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts @@ -347,6 +347,15 @@ export namespace MCP {/* JSON-RPC types */ * If not provided, the client should assume that the icon can be used at any size. */ sizes?: string; + + /** + * Optional specifier for the theme this icon is designed for. `light` indicates + * the icon is designed to be used with a light background, and `dark` indicates + * the icon is designed to be used with a dark background. + * + * If not provided, the client should assume the icon can be used with any theme. + */ + theme?: 'light' | 'dark'; } /** diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts index 46e4fd21ba0..9520d48fe8c 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts @@ -7,28 +7,28 @@ import * as assert from 'assert'; import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { NullLogger } from '../../../../../platform/log/common/log.js'; -import { parseAndValidateMcpIcon } from '../../common/mcpIcons.js'; +import { McpIcons, parseAndValidateMcpIcon } from '../../common/mcpIcons.js'; import { McpServerTransportHTTP, McpServerTransportStdio, McpServerTransportType } from '../../common/mcpTypes.js'; +const createHttpLaunch = (url: string): McpServerTransportHTTP => ({ + type: McpServerTransportType.HTTP, + uri: URI.parse(url), + headers: [] +}); + +const createStdioLaunch = (): McpServerTransportStdio => ({ + type: McpServerTransportType.Stdio, + cwd: undefined, + command: 'cmd', + args: [], + env: {}, + envFile: undefined +}); + suite('MCP Icons', () => { suite('parseAndValidateMcpIcon', () => { ensureNoDisposablesAreLeakedInTestSuite(); - const createHttpLaunch = (url: string): McpServerTransportHTTP => ({ - type: McpServerTransportType.HTTP, - uri: URI.parse(url), - headers: [] - }); - - const createStdioLaunch = (): McpServerTransportStdio => ({ - type: McpServerTransportType.Stdio, - cwd: undefined, - command: 'cmd', - args: [], - env: {}, - envFile: undefined - }); - test('includes supported icons and sorts sizes ascending', () => { const logger = new NullLogger(); const launch = createHttpLaunch('https://example.com'); @@ -86,4 +86,64 @@ suite('MCP Icons', () => { assert.strictEqual(httpResult.length, 0); }); }); + + suite('McpIcons', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getUrl returns undefined when no icons are available', () => { + const icons = McpIcons.fromParsed(undefined); + assert.strictEqual(icons.getUrl(16), undefined); + }); + + test('getUrl prefers theme-specific icons and keeps light fallback', () => { + const logger = new NullLogger(); + const launch = createHttpLaunch('https://example.com'); + const parsed = parseAndValidateMcpIcon({ + icons: [ + { src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: '16x16 48x48', theme: 'dark' }, + { src: 'https://example.com/any.png', mimeType: 'image/png', sizes: '24x24' }, + { src: 'https://example.com/light.png', mimeType: 'image/png', sizes: '64x64', theme: 'light' } + ] + }, launch, logger); + const icons = McpIcons.fromParsed(parsed); + const result = icons.getUrl(32); + + assert.ok(result); + assert.strictEqual(result!.dark.toString(), 'https://example.com/dark.png'); + assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png'); + }); + + test('getUrl falls back to any-theme icons when no exact size exists', () => { + const logger = new NullLogger(); + const launch = createHttpLaunch('https://example.com'); + const parsed = parseAndValidateMcpIcon({ + icons: [ + { src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: '16x16', theme: 'dark' }, + { src: 'https://example.com/any.png', mimeType: 'image/png', sizes: '64x64' } + ] + }, launch, logger); + const icons = McpIcons.fromParsed(parsed); + const result = icons.getUrl(60); + + assert.ok(result); + assert.strictEqual(result!.dark.toString(), 'https://example.com/any.png'); + assert.strictEqual(result!.light, undefined); + }); + + test('getUrl reuses light icons when dark theme assets are missing', () => { + const logger = new NullLogger(); + const launch = createHttpLaunch('https://example.com'); + const parsed = parseAndValidateMcpIcon({ + icons: [ + { src: 'https://example.com/light.png', mimeType: 'image/png', sizes: '32x32', theme: 'light' } + ] + }, launch, logger); + const icons = McpIcons.fromParsed(parsed); + const result = icons.getUrl(16); + + assert.ok(result); + assert.strictEqual(result!.dark.toString(), 'https://example.com/light.png'); + assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png'); + }); + }); }); From 70a7ef1bc9e3f65a91a87b5bfaabb0358799f71f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 3 Oct 2025 17:23:14 +0200 Subject: [PATCH 0779/4355] debt - start to reduce explicit `any` in my code (#269479) --- eslint.config.js | 87 +++++++++++++++++++ src/bootstrap-import.ts | 2 +- src/bootstrap-node.ts | 2 +- src/main.ts | 6 +- src/server-main.ts | 8 +- src/vs/base/common/stream.ts | 8 +- .../electron-browser/contextmenu.ts | 7 +- .../sandbox/electron-browser/electronTypes.ts | 12 +-- .../parts/sandbox/electron-browser/globals.ts | 14 ++- .../sandbox/electron-browser/preload-aux.ts | 4 +- .../parts/sandbox/electron-browser/preload.ts | 29 +++---- src/vs/base/parts/storage/node/storage.ts | 3 +- .../test/node/storage.integrationTest.ts | 2 +- .../electron-browser/workbench/workbench.ts | 6 +- src/vs/code/electron-main/app.ts | 9 +- src/vs/code/node/cli.ts | 4 +- .../contrib/chat/browser/chatSetup.ts | 2 +- .../electron-browser/chat.contribution.ts | 7 +- .../electron-browser/remote.contribution.ts | 3 +- .../terminalNativeContribution.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 34 +++++--- .../services/activity/common/activity.ts | 12 +-- .../auxiliaryWindowService.ts | 2 + .../editor/common/customEditorLabelService.ts | 4 +- .../browser/customEditorLabelService.test.ts | 2 +- .../host/browser/browserHostService.ts | 25 ++++-- .../services/label/test/browser/label.test.ts | 3 +- .../electron-browser/lifecycleService.ts | 6 +- .../test/browser/progressIndicator.test.ts | 24 ++--- 29 files changed, 217 insertions(+), 112 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index d9456a0f903..8a1819cc4aa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -177,6 +177,93 @@ export default tseslint.config( ] } }, + // vscode TS: strict no explicit `any` + { + files: [ + 'src/vs/base/common/extpath.ts', + 'src/vs/base/common/glob.ts', + 'src/vs/base/common/path.ts', + 'src/vs/base/common/stream.ts', + 'src/vs/base/node/pfs.ts', + 'src/vs/base/parts/contextmenu/**', + // 'src/vs/base/parts/ipc/**', + // 'src/vs/base/parts/sandbox/**', + 'src/vs/base/parts/storage/**', + 'src/vs/platform/auxiliaryWindow/**', + // 'src/vs/platform/backup/**', + // 'src/vs/platform/editor/**', + // 'src/vs/platform/environment/**', + // 'src/vs/platform/files/**', + // 'src/vs/platform/ipc/**', + // 'src/vs/platform/launch/**', + // 'src/vs/platform/lifecycle/**', + // 'src/vs/platform/menubar/**', + // 'src/vs/platform/native/**', + // 'src/vs/platform/sharedProcess/**', + // 'src/vs/platform/state/**', + // 'src/vs/platform/storage/**', + // 'src/vs/platform/utilityProcess/**', + // 'src/vs/platform/window/**', + // 'src/vs/platform/windows/**', + // 'src/vs/platform/workspace/**', + // 'src/vs/platform/workspaces/**', + 'src/bootstrap-cli.ts', + 'src/bootstrap-esm.ts', + 'src/bootstrap-fork.ts', + 'src/bootstrap-import.ts', + 'src/bootstrap-meta.ts', + 'src/bootstrap-node.ts', + 'src/bootstrap-server.ts', + 'src/cli.ts', + 'src/main.ts', + 'src/server-cli.ts', + 'src/server-main.ts', + 'src/vs/code/**', + 'src/vs/workbench/services/activity/**', + 'src/vs/workbench/services/auxiliaryWindow/**', + 'src/vs/workbench/services/chat/**', + 'src/vs/workbench/services/contextmenu/**', + 'src/vs/workbench/services/dialogs/**', + 'src/vs/workbench/services/editor/**', + // 'src/vs/workbench/services/environment/**', + 'src/vs/workbench/services/files/**', + 'src/vs/workbench/services/filesConfiguration/**', + 'src/vs/workbench/services/history/**', + 'src/vs/workbench/services/host/**', + 'src/vs/workbench/services/label/**', + 'src/vs/workbench/services/layout/**', + 'src/vs/workbench/services/lifecycle/**', + 'src/vs/workbench/services/notification/**', + 'src/vs/workbench/services/path/**', + 'src/vs/workbench/services/progress/**', + // 'src/vs/workbench/services/storage/**', + // 'src/vs/workbench/services/textfile/**', + // 'src/vs/workbench/services/textmodelResolver/**', + // 'src/vs/workbench/services/untitled/**', + // 'src/vs/workbench/services/utilityProcess/**', + // 'src/vs/workbench/services/views/**', + // 'src/vs/workbench/services/workingCopy/**', + // 'src/vs/workbench/services/workspaces/**', + // 'src/vs/workbench/common/**', + // 'src/vs/workbench/browser/**', + // 'src/vs/workbench/electron-browser/**', + // 'src/vs/workbench/contrib/files/**', + ], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/no-explicit-any': [ + 'warn', + { + 'ignoreRestArgs': false + } + ] + } + }, // Tests { files: [ diff --git a/src/bootstrap-import.ts b/src/bootstrap-import.ts index 9d222a003eb..3bd5c73a0af 100644 --- a/src/bootstrap-import.ts +++ b/src/bootstrap-import.ts @@ -47,7 +47,7 @@ export async function initialize(injectPath: string): Promise { console.log(`[bootstrap-import] Initialized node_modules redirector for: ${injectPath}`); } -export async function resolve(specifier: string | number, context: any, nextResolve: (arg0: any, arg1: any) => any) { +export async function resolve(specifier: string | number, context: unknown, nextResolve: (arg0: unknown, arg1: unknown) => unknown) { const newSpecifier = _specifierToUrl[specifier]; if (newSpecifier !== undefined) { diff --git a/src/bootstrap-node.ts b/src/bootstrap-node.ts index 30e1bd41c90..8cb580e738b 100644 --- a/src/bootstrap-node.ts +++ b/src/bootstrap-node.ts @@ -83,7 +83,7 @@ export function removeGlobalNodeJsModuleLookupPaths(): void { const originalResolveLookupPaths = Module._resolveLookupPaths; - Module._resolveLookupPaths = function (moduleName: string, parent: any): string[] { + Module._resolveLookupPaths = function (moduleName: string, parent: unknown): string[] { const paths = originalResolveLookupPaths(moduleName, parent); if (Array.isArray(paths)) { let commonSuffixLength = 0; diff --git a/src/main.ts b/src/main.ts index 7afc29087ed..e2bbdcd87df 100644 --- a/src/main.ts +++ b/src/main.ts @@ -582,8 +582,7 @@ function registerListeners(): void { * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. */ const macOpenFiles: string[] = []; - // eslint-disable-next-line local/code-no-any-casts - (globalThis as any)['macOpenFiles'] = macOpenFiles; + (globalThis as { macOpenFiles?: string[] }).macOpenFiles = macOpenFiles; app.on('open-file', function (event, path) { macOpenFiles.push(path); }); @@ -603,8 +602,7 @@ function registerListeners(): void { app.on('open-url', onOpenUrl); }); - // eslint-disable-next-line local/code-no-any-casts - (globalThis as any)['getOpenUrls'] = function () { + (globalThis as { getOpenUrls?: () => string[] }).getOpenUrls = function () { app.removeListener('open-url', onOpenUrl); return openUrls; diff --git a/src/server-main.ts b/src/server-main.ts index b0d8f65e2c4..6c192ab8997 100644 --- a/src/server-main.ts +++ b/src/server-main.ts @@ -20,8 +20,7 @@ import { INLSConfiguration } from './vs/nls.js'; import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer.js'; perf.mark('code/server/start'); -// eslint-disable-next-line local/code-no-any-casts -(globalThis as any).vscodeServerStartTime = performance.now(); +(globalThis as { vscodeServerStartTime?: number }).vscodeServerStartTime = performance.now(); // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { @@ -139,8 +138,7 @@ if (shouldSpawnCli) { console.log(output); perf.mark('code/server/started'); - // eslint-disable-next-line local/code-no-any-casts - (globalThis as any).vscodeServerListenTime = performance.now(); + (globalThis as { vscodeServerListenTime?: number }).vscodeServerListenTime = performance.now(); await getRemoteExtensionHostAgentServer(); }); @@ -153,7 +151,7 @@ if (shouldSpawnCli) { }); } -function sanitizeStringArg(val: any): string | undefined { +function sanitizeStringArg(val: unknown): string | undefined { if (Array.isArray(val)) { // if an argument is passed multiple times, minimist creates an array val = val.pop(); // take the last item } diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts index 990c8f1c6bc..ebc0ef5d23b 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -331,14 +331,14 @@ class WriteableStreamImpl implements WriteableStream { on(event: 'data', callback: (data: T) => void): void; on(event: 'error', callback: (err: Error) => void): void; on(event: 'end', callback: () => void): void; - on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void { + on(event: 'data' | 'error' | 'end', callback: ((data: T) => void) | ((err: Error) => void) | (() => void)): void { if (this.state.destroyed) { return; } switch (event) { case 'data': - this.listeners.data.push(callback); + this.listeners.data.push(callback as (data: T) => void); // switch into flowing mode as soon as the first 'data' // listener is added and we are not yet in flowing mode @@ -347,7 +347,7 @@ class WriteableStreamImpl implements WriteableStream { break; case 'end': - this.listeners.end.push(callback); + this.listeners.end.push(callback as () => void); // emit 'end' event directly if we are flowing // and the end has already been reached @@ -360,7 +360,7 @@ class WriteableStreamImpl implements WriteableStream { break; case 'error': - this.listeners.error.push(callback); + this.listeners.error.push(callback as (err: Error) => void); // emit buffered 'error' events unless done already // now that we know that we have at least one listener diff --git a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts index 3eab2e24b9d..7b1971d0aa7 100644 --- a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts @@ -13,13 +13,16 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide const contextMenuId = contextMenuIdPool++; const onClickChannel = `vscode:onContextMenu${contextMenuId}`; - const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => { + const onClickChannelHandler = (_event: unknown, ...args: unknown[]) => { + const itemId = args[0] as number; + const context = args[1] as IContextMenuEvent; const item = processedItems[itemId]; item.click?.(context); }; ipcRenderer.once(onClickChannel, onClickChannelHandler); - ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (event: unknown, closedContextMenuId: number) => { + ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (_event: unknown, ...args: unknown[]) => { + const closedContextMenuId = args[0] as number; if (closedContextMenuId !== contextMenuId) { return; } diff --git a/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts b/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts index cbf98ff13c5..fee511ba913 100644 --- a/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts +++ b/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts @@ -66,17 +66,17 @@ export interface IpcRenderer { * returned by `invoke` will reject. However, the `Error` object in the renderer * process will not be the same as the one thrown in the main process. */ - invoke(channel: string, ...args: any[]): Promise; + invoke(channel: string, ...args: unknown[]): Promise; /** * Listens to `channel`, when a new message arrives `listener` would be called with * `listener(event, args...)`. */ - on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + on(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; /** * Adds a one time `listener` function for the event. This `listener` is invoked * only the next time a message is sent to `channel`, after which it is removed. */ - once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + once(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; // Note: API with `Transferable` intentionally commented out because you // cannot transfer these when `contextIsolation: true`. // /** @@ -92,12 +92,12 @@ export interface IpcRenderer { // * For more information on using `MessagePort` and `MessageChannel`, see the MDN // * documentation. // */ - // postMessage(channel: string, message: any, transfer?: MessagePort[]): void; + // postMessage(channel: string, message: unknown, transfer?: MessagePort[]): void; /** * Removes the specified `listener` from the listener array for the specified * `channel`. */ - removeListener(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + removeListener(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; /** * Send an asynchronous message to the main process via `channel`, along with * arguments. Arguments will be serialized with the Structured Clone Algorithm, @@ -122,7 +122,7 @@ export interface IpcRenderer { * If you want to receive a single response from the main process, like the result * of a method call, consider using `ipcRenderer.invoke`. */ - send(channel: string, ...args: any[]): void; + send(channel: string, ...args: unknown[]): void; } export interface WebFrame { diff --git a/src/vs/base/parts/sandbox/electron-browser/globals.ts b/src/vs/base/parts/sandbox/electron-browser/globals.ts index 9d831ac8afe..bd43ae40457 100644 --- a/src/vs/base/parts/sandbox/electron-browser/globals.ts +++ b/src/vs/base/parts/sandbox/electron-browser/globals.ts @@ -115,8 +115,18 @@ export interface ISandboxContext { resolveConfiguration(): Promise; } -// eslint-disable-next-line local/code-no-any-casts -const vscodeGlobal = (globalThis as any).vscode; +interface ISandboxGlobal { + vscode: { + readonly ipcRenderer: IpcRenderer; + readonly ipcMessagePort: IpcMessagePort; + readonly webFrame: WebFrame; + readonly process: ISandboxNodeProcess; + readonly context: ISandboxContext; + readonly webUtils: WebUtils; + }; +} + +const vscodeGlobal = (globalThis as unknown as ISandboxGlobal).vscode; export const ipcRenderer: IpcRenderer = vscodeGlobal.ipcRenderer; export const ipcMessagePort: IpcMessagePort = vscodeGlobal.ipcMessagePort; export const webFrame: WebFrame = vscodeGlobal.webFrame; diff --git a/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts b/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts index 83b7e91fc6f..c8e238ed112 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts +++ b/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts @@ -23,13 +23,13 @@ */ ipcRenderer: { - send(channel: string, ...args: any[]): void { + send(channel: string, ...args: unknown[]): void { if (validateIPC(channel)) { ipcRenderer.send(channel, ...args); } }, - invoke(channel: string, ...args: any[]): Promise { + invoke(channel: string, ...args: unknown[]): Promise { validateIPC(channel); return ipcRenderer.invoke(channel, ...args); diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.ts b/src/vs/base/parts/sandbox/electron-browser/preload.ts index c023c5622ed..74b908e7523 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.ts +++ b/src/vs/base/parts/sandbox/electron-browser/preload.ts @@ -109,19 +109,19 @@ ipcRenderer: { - send(channel: string, ...args: any[]): void { + send(channel: string, ...args: unknown[]): void { if (validateIPC(channel)) { ipcRenderer.send(channel, ...args); } }, - invoke(channel: string, ...args: any[]): Promise { + invoke(channel: string, ...args: unknown[]): Promise { validateIPC(channel); return ipcRenderer.invoke(channel, ...args); }, - on(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + on(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.on(channel, listener); @@ -129,7 +129,7 @@ return this; }, - once(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + once(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.once(channel, listener); @@ -137,7 +137,7 @@ return this; }, - removeListener(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + removeListener(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.removeListener(channel, listener); @@ -215,7 +215,7 @@ return process.getProcessMemoryInfo(); }, - on(type: string, callback: (...args: any[]) => void): void { + on(type: string, callback: (...args: unknown[]) => void): void { process.on(type, callback); } }, @@ -246,17 +246,10 @@ } }; - // Use `contextBridge` APIs to expose globals to VSCode - // only if context isolation is enabled, otherwise just - // add to the DOM global. - if (process.contextIsolated) { - try { - contextBridge.exposeInMainWorld('vscode', globals); - } catch (error) { - console.error(error); - } - } else { - // eslint-disable-next-line local/code-no-any-casts - (window as any).vscode = globals; + try { + // Use `contextBridge` APIs to expose globals to VSCode + contextBridge.exposeInMainWorld('vscode', globals); + } catch (error) { + console.error(error); } }()); diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 162179cf35b..3822560cbff 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -245,8 +245,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { const connection = await this.whenConnected; const row = await this.get(connection, full ? 'PRAGMA integrity_check' : 'PRAGMA quick_check'); - // eslint-disable-next-line local/code-no-any-casts - const integrity = full ? (row as any)['integrity_check'] : (row as any)['quick_check']; + const integrity = full ? (row as { integrity_check: string }).integrity_check : (row as { quick_check: string }).quick_check; if (connection.isErroneous) { return `${integrity} (last error: ${connection.lastError})`; diff --git a/src/vs/base/parts/storage/test/node/storage.integrationTest.ts b/src/vs/base/parts/storage/test/node/storage.integrationTest.ts index 994417ddc00..4db56c00c77 100644 --- a/src/vs/base/parts/storage/test/node/storage.integrationTest.ts +++ b/src/vs/base/parts/storage/test/node/storage.integrationTest.ts @@ -446,7 +446,7 @@ flakySuite('SQLite Storage Library', function () { const corruptDBPath = join(testdir, 'broken.db'); await Promises.writeFile(corruptDBPath, 'This is a broken DB'); - let expectedError: any; + let expectedError: Error | string | undefined = undefined; await testDBBasics(corruptDBPath, error => { expectedError = error; }); diff --git a/src/vs/code/electron-browser/workbench/workbench.ts b/src/vs/code/electron-browser/workbench/workbench.ts index c1bad461c93..b0fdcaeba29 100644 --- a/src/vs/code/electron-browser/workbench/workbench.ts +++ b/src/vs/code/electron-browser/workbench/workbench.ts @@ -17,8 +17,7 @@ type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-browser/globals.js').IMainWindowSandboxGlobals; type IDesktopMain = import('../../../workbench/electron-browser/desktop.main.js').IDesktopMain; - // eslint-disable-next-line local/code-no-any-casts - const preloadGlobals: IMainWindowSandboxGlobals = (window as any).vscode; // defined by preload.ts + const preloadGlobals = (window as unknown as { vscode: IMainWindowSandboxGlobals }).vscode; // defined by preload.ts const safeProcess = preloadGlobals.process; //#region Splash Screen Helpers @@ -127,8 +126,7 @@ titleDiv.style.left = '0'; titleDiv.style.top = '0'; titleDiv.style.backgroundColor = `${colorInfo.titleBarBackground}`; - // eslint-disable-next-line local/code-no-any-casts - (titleDiv.style as any)['-webkit-app-region'] = 'drag'; + (titleDiv.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'drag'; splash.appendChild(titleDiv); if (colorInfo.titleBarBorder) { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 271d4625d53..43aba014586 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -545,8 +545,7 @@ export class CodeApplication extends Disposable { // See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085 try { if (isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) { - // eslint-disable-next-line local/code-no-any-casts - systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any); + systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true); } } catch (error) { this.logService.error(error); @@ -691,8 +690,7 @@ export class CodeApplication extends Disposable { } // macOS: open-url events that were received before the app is ready - // eslint-disable-next-line local/code-no-any-casts - const protocolUrlsFromEvent = ((global).getOpenUrls() || []) as string[]; + const protocolUrlsFromEvent = ((global as { getOpenUrls?: () => string[] }).getOpenUrls?.() || []); if (protocolUrlsFromEvent.length > 0) { this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent); } @@ -1299,8 +1297,7 @@ export class CodeApplication extends Disposable { } } - // eslint-disable-next-line local/code-no-any-casts - const macOpenFiles: string[] = (global).macOpenFiles; + const macOpenFiles: string[] = (global as { macOpenFiles?: string[] }).macOpenFiles ?? []; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; const hasFileURIs = !!args['file-uri']; diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index b31099ef0fb..b7c4fb99f8f 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -40,7 +40,7 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { || !!argv['telemetry']; } -export async function main(argv: string[]): Promise { +export async function main(argv: string[]): Promise { let args: NativeParsedArgs; try { @@ -567,7 +567,7 @@ export async function main(argv: string[]): Promise { child = spawn('open', spawnArgs, { ...options, env: {} }); } - return Promise.all(processCallbacks.map(callback => callback(child))); + await Promise.all(processCallbacks.map(callback => callback(child))); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 4325d481e24..0f4287c529c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -609,7 +609,7 @@ class SetupTool extends Disposable implements IToolImpl { return result; } - async prepareToolInvocation?(parameters: any, token: CancellationToken): Promise { + async prepareToolInvocation?(parameters: unknown, token: CancellationToken): Promise { return undefined; } } diff --git a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts index a4b5e6e84d0..3ecc2fc8bf5 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts @@ -69,10 +69,11 @@ class ChatCommandLineHandler extends Disposable { } private registerListeners() { - ipcRenderer.on('vscode:handleChatRequest', (_, args: typeof this.environmentService.args.chat) => { - this.logService.trace('vscode:handleChatRequest', args); + ipcRenderer.on('vscode:handleChatRequest', (_, ...args: unknown[]) => { + const chatArgs = args[0] as typeof this.environmentService.args.chat; + this.logService.trace('vscode:handleChatRequest', chatArgs); - this.prompt(args); + this.prompt(chatArgs); }); } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 95ecd96f3fd..2feba38fb4f 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -42,7 +42,8 @@ class RemoteAgentDiagnosticListener implements IWorkbenchContribution { @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILabelService labelService: ILabelService ) { - ipcRenderer.on('vscode:getDiagnosticInfo', (event: unknown, request: { replyChannel: string; args: IDiagnosticInfoOptions }): void => { + ipcRenderer.on('vscode:getDiagnosticInfo', (event: unknown, ...args: unknown[]): void => { + const request = args[0] as { replyChannel: string; args: IDiagnosticInfoOptions }; const connection = remoteAgentService.getConnection(); if (connection) { const hostName = labelService.getHostLabel(Schemas.vscodeRemote, connection.remoteAuthority); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts index a076cf8cfd9..54e3588037a 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts @@ -26,7 +26,7 @@ export class TerminalNativeContribution extends Disposable implements IWorkbench ) { super(); - ipcRenderer.on('vscode:openFiles', (_: unknown, request: INativeOpenFileRequest) => { this._onOpenFileRequest(request); }); + ipcRenderer.on('vscode:openFiles', (_: unknown, ...args: unknown[]) => { this._onOpenFileRequest(args[0] as INativeOpenFileRequest); }); this._register(nativeHostService.onDidResumeOS(() => this._onOsResume())); this._terminalService.setNativeDelegate({ diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 11035d0404d..b8b3b650c5e 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -155,7 +155,8 @@ export class NativeWindow extends BaseWindow { } // Support `runAction` event - ipcRenderer.on('vscode:runAction', async (event: unknown, request: INativeRunActionInWindowRequest) => { + ipcRenderer.on('vscode:runAction', async (event: unknown, ...argsRaw: unknown[]) => { + const request = argsRaw[0] as INativeRunActionInWindowRequest; const args: unknown[] = request.args || []; // If we run an action from the touchbar, we fill in the currently active resource @@ -182,7 +183,8 @@ export class NativeWindow extends BaseWindow { }); // Support runKeybinding event - ipcRenderer.on('vscode:runKeybinding', (event: unknown, request: INativeRunKeybindingInWindowRequest) => { + ipcRenderer.on('vscode:runKeybinding', (event: unknown, ...argsRaw: unknown[]) => { + const request = argsRaw[0] as INativeRunKeybindingInWindowRequest; const activeElement = getActiveElement(); if (activeElement) { this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, activeElement); @@ -190,7 +192,7 @@ export class NativeWindow extends BaseWindow { }); // Shared Process crash reported from main - ipcRenderer.on('vscode:reportSharedProcessCrash', (event: unknown, error: string) => { + ipcRenderer.on('vscode:reportSharedProcessCrash', (event: unknown, ...argsRaw: unknown[]) => { this.notificationService.prompt( Severity.Error, localize('sharedProcessCrash', "A shared background process terminated unexpectedly. Please restart the application to recover."), @@ -205,16 +207,17 @@ export class NativeWindow extends BaseWindow { }); // Support openFiles event for existing and new files - ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => { this.onOpenFiles(request); }); + ipcRenderer.on('vscode:openFiles', (event: unknown, ...argsRaw: unknown[]) => { this.onOpenFiles(argsRaw[0] as IOpenFileRequest); }); // Support addRemoveFolders event for workspace management - ipcRenderer.on('vscode:addRemoveFolders', (event: unknown, request: IAddRemoveFoldersRequest) => this.onAddRemoveFoldersRequest(request)); + ipcRenderer.on('vscode:addRemoveFolders', (event: unknown, ...argsRaw: unknown[]) => this.onAddRemoveFoldersRequest(argsRaw[0] as IAddRemoveFoldersRequest)); // Message support - ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => this.notificationService.info(message)); + ipcRenderer.on('vscode:showInfoMessage', (event: unknown, ...argsRaw: unknown[]) => this.notificationService.info(argsRaw[0] as string)); // Shell Environment Issue Notifications - ipcRenderer.on('vscode:showResolveShellEnvError', (event: unknown, message: string) => { + ipcRenderer.on('vscode:showResolveShellEnvError', (event: unknown, ...argsRaw: unknown[]) => { + const message = argsRaw[0] as string; this.notificationService.prompt( Severity.Error, message, @@ -233,7 +236,8 @@ export class NativeWindow extends BaseWindow { ); }); - ipcRenderer.on('vscode:showCredentialsError', (event: unknown, message: string) => { + ipcRenderer.on('vscode:showCredentialsError', (event: unknown, ...argsRaw: unknown[]) => { + const message = argsRaw[0] as string; this.notificationService.prompt( Severity.Error, localize('keychainWriteError', "Writing login information to the keychain failed with error '{0}'.", message), @@ -263,7 +267,7 @@ export class NativeWindow extends BaseWindow { ); }); - ipcRenderer.on('vscode:showArgvParseWarning', (event: unknown, message: string) => { + ipcRenderer.on('vscode:showArgvParseWarning', () => { this.notificationService.prompt( Severity.Warning, localize("showArgvParseWarning", "The runtime arguments file 'argv.json' contains errors. Please correct them and restart."), @@ -282,7 +286,8 @@ export class NativeWindow extends BaseWindow { ipcRenderer.on('vscode:leaveFullScreen', () => setFullscreen(false, mainWindow)); // Proxy Login Dialog - ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }) => { + ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, ...argsRaw: unknown[]) => { + const payload = argsRaw[0] as { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }; const rememberCredentialsKey = 'window.rememberProxyCredentials'; const rememberCredentials = this.storageService.getBoolean(rememberCredentialsKey, StorageScope.APPLICATION); const result = await this.dialogService.input({ @@ -324,12 +329,14 @@ export class NativeWindow extends BaseWindow { }); // Accessibility support changed event - ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, accessibilitySupportEnabled: boolean) => { + ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, ...argsRaw: unknown[]) => { + const accessibilitySupportEnabled = argsRaw[0] as boolean; this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); // Allow to update security settings around allowed UNC Host - ipcRenderer.on('vscode:configureAllowedUNCHost', async (event: unknown, host: string) => { + ipcRenderer.on('vscode:configureAllowedUNCHost', async (event: unknown, ...argsRaw: unknown[]) => { + const host = argsRaw[0] as string; if (!isWindows) { return; // only supported on Windows } @@ -354,7 +361,8 @@ export class NativeWindow extends BaseWindow { }); // Allow to update security settings around protocol handlers - ipcRenderer.on('vscode:disablePromptForProtocolHandling', (event: unknown, kind: 'local' | 'remote') => { + ipcRenderer.on('vscode:disablePromptForProtocolHandling', (event: unknown, ...argsRaw: unknown[]) => { + const kind = argsRaw[0] as 'local' | 'remote'; const setting = kind === 'local' ? 'security.promptForLocalFileProtocolHandling' : 'security.promptForRemoteFileProtocolHandling'; this.configurationService.updateValue(setting, false); }); diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index 4b114f37b40..a920fbc5b7d 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -70,16 +70,16 @@ export interface IBadgeStyles { readonly badgeBorder: Color | undefined; } -class BaseBadge implements IBadge { +class BaseBadge implements IBadge { constructor( - protected readonly descriptorFn: (arg: any) => string, + protected readonly descriptorFn: (arg: T) => string, private readonly stylesFn: ((theme: IColorTheme) => IBadgeStyles | undefined) | undefined, ) { } getDescription(): string { - return this.descriptorFn(null); + return this.descriptorFn(null as T); } getColors(theme: IColorTheme): IBadgeStyles | undefined { @@ -87,7 +87,7 @@ class BaseBadge implements IBadge { } } -export class NumberBadge extends BaseBadge { +export class NumberBadge extends BaseBadge { constructor(readonly number: number, descriptorFn: (num: number) => string) { super(descriptorFn, undefined); @@ -100,7 +100,7 @@ export class NumberBadge extends BaseBadge { } } -export class IconBadge extends BaseBadge { +export class IconBadge extends BaseBadge { constructor( readonly icon: ThemeIcon, descriptorFn: () => string, @@ -110,7 +110,7 @@ export class IconBadge extends BaseBadge { } } -export class ProgressBadge extends BaseBadge { +export class ProgressBadge extends BaseBadge { constructor(descriptorFn: () => string) { super(descriptorFn, undefined); } diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts index 31d0c4ef27c..ae2faa7b571 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-browser/auxiliaryWindowService.ts @@ -24,6 +24,7 @@ import { getZoomLevel, isFullscreen, setFullscreen } from '../../../../base/brow import { getActiveWindow } from '../../../../base/browser/dom.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; import { isMacintosh } from '../../../../base/common/platform.js'; +import { assert } from '../../../../base/common/assert.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; type NativeCodeWindow = CodeWindow & { @@ -156,6 +157,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService mark('code/auxiliaryWindow/willResolveWindowId'); const windowId = await auxiliaryWindow.vscode.ipcRenderer.invoke('vscode:registerAuxiliaryWindow', this.nativeHostService.windowId); mark('code/auxiliaryWindow/didResolveWindowId'); + assert(typeof windowId === 'number'); return windowId; } diff --git a/src/vs/workbench/services/editor/common/customEditorLabelService.ts b/src/vs/workbench/services/editor/common/customEditorLabelService.ts index 68eda9a9d67..bf7692f7589 100644 --- a/src/vs/workbench/services/editor/common/customEditorLabelService.ts +++ b/src/vs/workbench/services/editor/common/customEditorLabelService.ts @@ -159,10 +159,10 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito private readonly _filenameCaptureExpression = /(?^\.*[^.]*)/; private applyTemplate(template: string, resource: URI, relevantPath: string): string { let parsedPath: undefined | ParsedPath; - return template.replace(this._parsedTemplateExpression, (match: string, variable: string, ...args: any[]) => { + return template.replace(this._parsedTemplateExpression, (match: string, variable: string, ...args: unknown[]) => { parsedPath = parsedPath ?? parsePath(resource.path); // named group matches - const { dirnameN = '0', extnameN = '0' }: { dirnameN?: string; extnameN?: string } = args.pop(); + const { dirnameN = '0', extnameN = '0' } = args.pop() as { dirnameN?: string; extnameN?: string }; if (variable === 'filename') { const { filename } = this._filenameCaptureExpression.exec(parsedPath.base)?.groups ?? {}; diff --git a/src/vs/workbench/services/editor/test/browser/customEditorLabelService.test.ts b/src/vs/workbench/services/editor/test/browser/customEditorLabelService.test.ts index 96182d0967b..5c2789a8740 100644 --- a/src/vs/workbench/services/editor/test/browser/customEditorLabelService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/customEditorLabelService.test.ts @@ -33,7 +33,7 @@ suite('Custom Editor Label Service', () => { return [customLabelService, configService, instantiationService.createInstance(TestServiceAccessor)]; } - async function updatePattern(configService: TestConfigurationService, value: any): Promise { + async function updatePattern(configService: TestConfigurationService, value: unknown): Promise { await configService.setUserConfiguration(CustomEditorLabelService.SETTING_ID_PATTERNS, value); configService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === CustomEditorLabelService.SETTING_ID_PATTERNS, diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 4277634e32e..dddecccdc52 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -538,16 +538,25 @@ export class BrowserHostService extends Disposable implements IHostService { } // Safari and Edge 14 are all using webkit prefix - // eslint-disable-next-line local/code-no-any-casts - if ((targetWindow.document).webkitIsFullScreen !== undefined) { + + interface WebkitDocument extends Document { + webkitFullscreenElement: Element | null; + webkitExitFullscreen(): Promise; + webkitIsFullScreen: boolean; + } + + interface WebkitHTMLElement extends HTMLElement { + webkitRequestFullscreen(): Promise; + } + + const webkitDocument = targetWindow.document as WebkitDocument; + const webkitElement = target as WebkitHTMLElement; + if (webkitDocument.webkitIsFullScreen !== undefined) { try { - // eslint-disable-next-line local/code-no-any-casts - if (!(targetWindow.document).webkitIsFullScreen) { - // eslint-disable-next-line local/code-no-any-casts - (target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise. + if (!webkitDocument.webkitIsFullScreen) { + webkitElement.webkitRequestFullscreen(); // it's async, but doesn't return a real promise } else { - // eslint-disable-next-line local/code-no-any-casts - (targetWindow.document).webkitExitFullscreen(); // it's async, but doesn't return a real promise. + webkitDocument.webkitExitFullscreen(); // it's async, but doesn't return a real promise } } catch { this.logService.warn('toggleFullScreen(): requestFullscreen/exitFullscreen failed'); diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index f736e1bd069..bcf14160d8f 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -222,8 +222,7 @@ suite('URI Label', () => { } assert.deepStrictEqual(m, { formatters: expected }); - // eslint-disable-next-line local/code-no-any-casts - delete (m as any).formatters; + delete (m as { formatters: unknown }).formatters; }); }); diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 762840ba2f2..2495e02372f 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -34,7 +34,8 @@ export class NativeLifecycleService extends AbstractLifecycleService { const windowId = this.nativeHostService.windowId; // Main side indicates that window is about to unload, check for vetos - ipcRenderer.on('vscode:onBeforeUnload', async (event: unknown, reply: { okChannel: string; cancelChannel: string; reason: ShutdownReason }) => { + ipcRenderer.on('vscode:onBeforeUnload', async (event: unknown, ...args: unknown[]) => { + const reply = args[0] as { okChannel: string; cancelChannel: string; reason: ShutdownReason }; this.logService.trace(`[lifecycle] onBeforeUnload (reason: ${reply.reason})`); // trigger onBeforeShutdown events and veto collecting @@ -60,7 +61,8 @@ export class NativeLifecycleService extends AbstractLifecycleService { }); // Main side indicates that we will indeed shutdown - ipcRenderer.on('vscode:onWillUnload', async (event: unknown, reply: { replyChannel: string; reason: ShutdownReason }) => { + ipcRenderer.on('vscode:onWillUnload', async (event: unknown, ...args: unknown[]) => { + const reply = args[0] as { replyChannel: string; reason: ShutdownReason }; this.logService.trace(`[lifecycle] onWillUnload (reason: ${reply.reason})`); // trigger onWillShutdown events and joining diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index 1214694e56d..0c1a9f91495 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -7,32 +7,33 @@ import assert from 'assert'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { AbstractProgressScope, ScopedProgressIndicator } from '../../browser/progressIndicator.js'; +import { ProgressBar } from '../../../../../base/browser/ui/progressbar/progressbar.js'; -class TestProgressBar { +class TestProgressBar extends ProgressBar { fTotal: number = 0; fWorked: number = 0; fInfinite: boolean = false; fDone: boolean = false; - infinite() { + override infinite() { this.fDone = null!; this.fInfinite = true; return this; } - total(total: number) { + override total(total: number) { this.fDone = null!; this.fTotal = total; return this; } - hasTotal() { + override hasTotal() { return !!this.fTotal; } - worked(worked: number) { + override worked(worked: number) { this.fDone = null!; if (this.fWorked) { @@ -44,7 +45,7 @@ class TestProgressBar { return this; } - done() { + override done() { this.fDone = true; this.fInfinite = null!; @@ -54,13 +55,13 @@ class TestProgressBar { return this; } - stop() { + override stop() { return this.done(); } - show(): void { } + override show(): void { } - hide(): void { } + override hide(): void { } } suite('Progress Indicator', () => { @@ -72,14 +73,13 @@ suite('Progress Indicator', () => { }); test('ScopedProgressIndicator', async () => { - const testProgressBar = new TestProgressBar(); + const testProgressBar = disposables.add(new TestProgressBar(document.createElement('div'))); const progressScope = disposables.add(new class extends AbstractProgressScope { constructor() { super('test.scopeId', true); } testOnScopeOpened(scopeId: string) { super.onScopeOpened(scopeId); } testOnScopeClosed(scopeId: string): void { super.onScopeClosed(scopeId); } }()); - // eslint-disable-next-line local/code-no-any-casts - const testObject = disposables.add(new ScopedProgressIndicator((testProgressBar), progressScope)); + const testObject = disposables.add(new ScopedProgressIndicator(testProgressBar, progressScope)); // Active: Show (Infinite) let fn = testObject.show(true); From ab5698f19d8299c412cb76c1d1ab4077a2a7666d Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 3 Oct 2025 17:23:56 +0200 Subject: [PATCH 0780/4355] fixes https://github.com/microsoft/vscode/issues/257629 --- src/vs/editor/browser/services/inlineCompletionsService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/services/inlineCompletionsService.ts b/src/vs/editor/browser/services/inlineCompletionsService.ts index a7416fcb73b..f69d8390150 100644 --- a/src/vs/editor/browser/services/inlineCompletionsService.ts +++ b/src/vs/editor/browser/services/inlineCompletionsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WindowIntervalTimer } from '../../../base/browser/dom.js'; +import { TimeoutTimer } from '../../../base/common/async.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; @@ -73,7 +73,7 @@ export class InlineCompletionsService extends Disposable implements IInlineCompl return Math.max(0, this._snoozeTimeEnd - Date.now()); } - private _timer: WindowIntervalTimer; + private _timer: TimeoutTimer; constructor( @IContextKeyService private _contextKeyService: IContextKeyService, @@ -81,7 +81,7 @@ export class InlineCompletionsService extends Disposable implements IInlineCompl ) { super(); - this._timer = this._register(new WindowIntervalTimer()); + this._timer = this._register(new TimeoutTimer()); const inlineCompletionsSnoozing = InlineCompletionsSnoozing.bindTo(this._contextKeyService); this._register(this.onDidChangeIsSnoozing(() => inlineCompletionsSnoozing.set(this.isSnoozing()))); From 5f8ce794c65d1b05ea0ec21bf145c8472f0d9d92 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 3 Oct 2025 17:25:45 +0200 Subject: [PATCH 0781/4355] fixes https://github.com/microsoft/vscode/issues/269706 --- .../browser/services/inlineCompletionsService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/services/inlineCompletionsService.ts b/src/vs/editor/browser/services/inlineCompletionsService.ts index a7416fcb73b..5e468734cf4 100644 --- a/src/vs/editor/browser/services/inlineCompletionsService.ts +++ b/src/vs/editor/browser/services/inlineCompletionsService.ts @@ -195,17 +195,17 @@ export class SnoozeInlineCompletion extends Action2 { const inlineCompletionsService = accessor.get(IInlineCompletionsService); const storageService = accessor.get(IStorageService); - let durationMinutes: number | undefined; + let durationMs: number | undefined; if (args.length > 0 && typeof args[0] === 'number') { - durationMinutes = args[0]; + durationMs = args[0] * 60_000; } - if (!durationMinutes) { - durationMinutes = await this.getDurationFromUser(quickInputService, storageService); + if (!durationMs) { + durationMs = await this.getDurationFromUser(quickInputService, storageService); } - if (durationMinutes) { - inlineCompletionsService.setSnoozeDuration(durationMinutes); + if (durationMs) { + inlineCompletionsService.setSnoozeDuration(durationMs); } } From a50d3d2492fdeef87b424843039b0676f8234304 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:26:48 -0700 Subject: [PATCH 0782/4355] Forward chat summaries for cloud button v2 impl (#269583) * merge * refactor and movie magic to hide fake progress chat req --- .../api/browser/mainThreadChatAgents2.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 8 +- .../browser/actions/chatExecuteActions.ts | 133 ++++++++++-------- .../contrib/chat/common/chatAgents.ts | 8 ++ .../contrib/chat/common/chatService.ts | 8 ++ .../contrib/chat/common/chatServiceImpl.ts | 3 +- .../vscode.proposed.chatSessionsProvider.d.ts | 4 + 8 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index b88652e742a..b428977a2eb 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -170,7 +170,11 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.set(request.requestId, progress); try { const chatSessionContext = this._chatService.getChatSessionFromInternalId(request.sessionId); - return await this._proxy.$invokeAgent(handle, request, { history, chatSessionContext }, token) ?? {}; + return await this._proxy.$invokeAgent(handle, request, { + history, + chatSessionContext, + chatSummary: request.chatSummary + }, token) ?? {}; } finally { this._pendingProgress.delete(request.requestId); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d989f3567f9..e79863b6f5a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1369,7 +1369,7 @@ export type IChatAgentHistoryEntryDto = { }; export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean } }, token: CancellationToken): Promise; + $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean }; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; $provideFollowups(request: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 452c3273a73..561325dd202 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -541,7 +541,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS this._onDidChangeChatRequestTools.fire(request.extRequest); } - async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean } }, token: CancellationToken): Promise { + async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean }; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -588,7 +588,11 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS }; } - const chatContext: vscode.ChatContext = { history, chatSessionContext }; + const chatContext: vscode.ChatContext = { + history, + chatSessionContext, + chatSummary: context.chatSummary + }; const task = agent.invoke( extRequest, chatContext, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index faaca67ffe0..601c66bb06b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -26,7 +26,7 @@ import { IQuickInputService } from '../../../../../platform/quickinput/common/qu import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js'; -import { IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js'; +import { IChatAgent, IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys, ChatContextKeyExprs } from '../../common/chatContextKeys.js'; import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js'; import { IChatMode, IChatModeService } from '../../common/chatModes.js'; @@ -648,14 +648,14 @@ export class CreateRemoteAgentJobAction extends Action2 { private async createWithChatSessions( chatSessionsService: IChatSessionsService, chatService: IChatService, - chatAgentService: IChatAgentService, quickPickService: IQuickInputService, - chatModel: IChatModel, - requestParser: ChatRequestParser, sessionId: string, - widget: IChatWidget, attachedContext: ChatRequestVariableSet, userPrompt: string, + chatSummary?: { + prompt?: string; + history?: string; + } ) { const contributions = chatSessionsService.getAllChatSessionContributions(); const agent = await this.pickCodingAgent(quickPickService, contributions); @@ -664,9 +664,11 @@ export class CreateRemoteAgentJobAction extends Action2 { } // TODO(jospicer): The previous chat history doesn't get sent to chat participants! const { type } = agent; + await chatService.sendRequest(sessionId, userPrompt, { agentIdSilent: type, attachedContext: attachedContext.asArray(), + chatSummary, }); } @@ -797,22 +799,6 @@ export class CreateRemoteAgentJobAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const requestParser = instantiationService.createInstance(ChatRequestParser); - const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseCloudButtonV2); - if (isChatSessionsExperimentEnabled) { - return await this.createWithChatSessions( - chatSessionsService, - chatService, - chatAgentService, - quickPickService, - chatModel, - requestParser, - sessionId, - widget, - attachedContext, - userPrompt - ); - } - // Add the request to the model first const parsedRequest = requestParser.parseChatRequest(sessionId, userPrompt, ChatAgentLocation.Chat); const addedRequest = chatModel.addRequest( @@ -836,23 +822,7 @@ export class CreateRemoteAgentJobAction extends Action2 { ) }); - const userPromptEntry: IChatAgentHistoryEntry = { - request: { - sessionId, - requestId: generateUuid(), - agentId: '', - message: userPrompt, - command: undefined, - variables: { variables: attachedContext.asArray() }, - location: ChatAgentLocation.Chat, - editedFileEvents: [], - }, - response: [], - result: {} - }; - const historyEntries = [userPromptEntry]; - title = await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None); - summarizedUserPrompt = await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None); + ({ title, summarizedUserPrompt } = await this.generateSummarizedUserPrompt(sessionId, userPrompt, attachedContext, title, chatAgentService, defaultAgent, summarizedUserPrompt)); } let summary: string = ''; @@ -870,34 +840,31 @@ export class CreateRemoteAgentJobAction extends Action2 { CreateRemoteAgentJobAction.markdownStringTrustedOptions ) }); - const historyEntries: IChatAgentHistoryEntry[] = chatRequests - .map(req => ({ - request: { - sessionId, - requestId: req.id, - agentId: req.response?.agent?.id ?? '', - message: req.message.text, - command: req.response?.slashCommand?.name, - variables: req.variableData, - location: ChatAgentLocation.Chat, - editedFileEvents: req.editedFileEvents, - }, - response: toChatHistoryContent(req.response!.response.value), - result: req.response?.result ?? {} - })); - - // TODO: Determine a cutoff point where we stop including earlier history - // For example, if the user has already delegated to a coding agent once, - // prefer the conversation afterwards. - - title ??= await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None); - summary += await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None); + ({ title, summary } = await this.generateSummarizedChatHistory(chatRequests, sessionId, title, chatAgentService, defaultAgent, summary)); } if (title) { summary += `\nTITLE: ${title}\n`; } + + const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseCloudButtonV2); + if (isChatSessionsExperimentEnabled) { + await chatService.removeRequest(sessionId, addedRequest.id); + return await this.createWithChatSessions( + chatSessionsService, + chatService, + quickPickService, + sessionId, + attachedContext, + userPrompt, + { + prompt: summarizedUserPrompt, + history: summary, + }, + ); + } + chatModel.acceptResponseProgress(addedRequest, { kind: 'progressMessage', content: new MarkdownString( @@ -916,6 +883,52 @@ export class CreateRemoteAgentJobAction extends Action2 { remoteJobCreatingKey.set(false); } } + + private async generateSummarizedChatHistory(chatRequests: IChatRequestModel[], sessionId: string, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summary: string) { + const historyEntries: IChatAgentHistoryEntry[] = chatRequests + .map(req => ({ + request: { + sessionId: sessionId, + requestId: req.id, + agentId: req.response?.agent?.id ?? '', + message: req.message.text, + command: req.response?.slashCommand?.name, + variables: req.variableData, + location: ChatAgentLocation.Chat, + editedFileEvents: req.editedFileEvents, + }, + response: toChatHistoryContent(req.response!.response.value), + result: req.response?.result ?? {} + })); + + // TODO: Determine a cutoff point where we stop including earlier history + // For example, if the user has already delegated to a coding agent once, + // prefer the conversation afterwards. + title ??= await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None); + summary += await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None); + return { title, summary }; + } + + private async generateSummarizedUserPrompt(sessionId: string, userPrompt: string, attachedContext: ChatRequestVariableSet, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summarizedUserPrompt: string | undefined) { + const userPromptEntry: IChatAgentHistoryEntry = { + request: { + sessionId: sessionId, + requestId: generateUuid(), + agentId: '', + message: userPrompt, + command: undefined, + variables: { variables: attachedContext.asArray() }, + location: ChatAgentLocation.Chat, + editedFileEvents: [], + }, + response: [], + result: {} + }; + const historyEntries = [userPromptEntry]; + title = await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None); + summarizedUserPrompt = await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None); + return { title, summarizedUserPrompt }; + } } export class ChatSubmitWithCodebaseAction extends Action2 { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index ebcc1b7c7da..f24a0c0b57f 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -148,6 +148,14 @@ export interface IChatAgentRequest { userSelectedTools?: UserSelectedTools; modeInstructions?: IChatRequestModeInstructions; editedFileEvents?: IChatAgentEditedFileEvent[]; + + /** + * Summary data for chat sessions context + */ + chatSummary?: { + prompt?: string; + history?: string; + }; } export interface IChatQuestion { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 39ed9300f3c..ee3814ba7f7 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -682,6 +682,14 @@ export interface IChatSendRequestOptions { * The label of the confirmation action that was selected. */ confirmation?: string; + + /** + * Summary data for chat sessions context + */ + chatSummary?: { + prompt?: string; + history?: string; + }; } export const IChatService = createDecorator('IChatService'); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index dc101c553ca..0391f763deb 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -822,7 +822,8 @@ export class ChatService extends Disposable implements IChatService { userSelectedModelId: options?.userSelectedModelId, userSelectedTools: options?.userSelectedTools?.get(), modeInstructions: options?.modeInfo?.modeInstructions, - editedFileEvents: request.editedFileEvents + editedFileEvents: request.editedFileEvents, + chatSummary: options?.chatSummary } satisfies IChatAgentRequest; }; diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 05b2b054ac8..4ef1ed94055 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -194,6 +194,10 @@ declare module 'vscode' { export interface ChatContext { readonly chatSessionContext?: ChatSessionContext; + readonly chatSummary?: { + readonly prompt?: string; + readonly history?: string; + }; } export interface ChatSessionContext { From 6711af16e505bfcf2f1a4763e05263459dcd6b2b Mon Sep 17 00:00:00 2001 From: dileepyavan <52841896+dileepyavan@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:59:05 -0700 Subject: [PATCH 0783/4355] fix for 264315 (#269632) * fix for 264315 * Update src/vs/workbench/contrib/chat/common/chatService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updating silent property for onload scenario * implementing code comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../chatContentParts/chatConfirmationContentPart.ts | 2 +- .../browser/chatContentParts/chatConfirmationWidget.ts | 7 +++++-- src/vs/workbench/contrib/chat/common/chatModel.ts | 2 ++ src/vs/workbench/contrib/chat/common/chatService.ts | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts index 5966e008d0d..97773d6afb1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -40,7 +40,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont { label: localize('accept', "Accept"), data: confirmation.data }, { label: localize('dismiss', "Dismiss"), data: confirmation.data, isSecondary: true }, ]; - const confirmationWidget = this._register(this.instantiationService.createInstance(SimpleChatConfirmationWidget, context.container, { title: confirmation.title, buttons, message: confirmation.message })); + const confirmationWidget = this._register(this.instantiationService.createInstance(SimpleChatConfirmationWidget, context.container, { title: confirmation.title, buttons, message: confirmation.message, silent: confirmation.isLive === false })); confirmationWidget.setShowButtons(!confirmation.isUsed); this._register(confirmationWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index c19bed91150..01c5c5a5270 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -43,6 +43,7 @@ export interface IChatConfirmationWidgetOptions { subtitle?: string | IMarkdownString; buttons: IChatConfirmationButton[]; toolbarData?: { arg: any; partType: string; partSource?: string }; + silent?: boolean; } export class ChatQueryTitlePart extends Disposable { @@ -129,6 +130,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { protected readonly markdownRenderer: MarkdownRenderer; private readonly title: string | IMarkdownString; + private readonly silent: boolean; private readonly notification = this._register(new MutableDisposable()); constructor( @@ -142,8 +144,9 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { ) { super(); - const { title, subtitle, message, buttons } = options; + const { title, subtitle, message, buttons, silent } = options; this.title = title; + this.silent = !!silent; const elements = dom.h('.chat-confirmation-widget-container@container', [ @@ -235,7 +238,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { protected renderMessage(element: HTMLElement, listContainer: HTMLElement): void { this.messageElement.append(element); - if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { + if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation') && !this.silent) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { this.notifyConfirmationNeeded(targetWindow); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index f8aa8bce209..9800d78b4cb 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1852,6 +1852,8 @@ export class ChatModel extends Disposable implements IChatModel { id: item.id, metadata: item.metadata }; + } else if (item.kind === 'confirmation') { + return { ...item, isLive: false }; } else { // eslint-disable-next-line local/code-no-any-casts return item as any; // TODO diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index ee3814ba7f7..3a6ac39a2bd 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -260,6 +260,8 @@ export interface IChatConfirmation { title: string; message: string | IMarkdownString; data: any; + /** Indicates whether this came from a current chat session (true/undefined) or a restored historic session (false) */ + isLive?: boolean; buttons?: string[]; isUsed?: boolean; kind: 'confirmation'; From d74b825001a85e63a4781233ab12e18f6e1319cc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:24:36 -0700 Subject: [PATCH 0784/4355] Add code search for no-any-casts For https://github.com/microsoft/vscode/issues/269213 --- .vscode/searches/no-any-casts.code-search | 2042 +++++++++++++++++++++ 1 file changed, 2042 insertions(+) create mode 100644 .vscode/searches/no-any-casts.code-search diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search new file mode 100644 index 00000000000..c5f6c0b09cf --- /dev/null +++ b/.vscode/searches/no-any-casts.code-search @@ -0,0 +1,2042 @@ +# Query: // eslint-disable-next-line local/code-no-any-casts + +1133 results - 453 files + +.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts: + 64: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + 136: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/code-no-reader-after-await.ts: + 62: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/code-no-static-self-ref.ts: + 36: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/code-no-test-async-suite.ts: + 23: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/code-no-unexternalized-strings.ts: + 84: // eslint-disable-next-line local/code-no-any-casts + 132: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/vscode-dts-provider-naming.ts: + 35: // eslint-disable-next-line local/code-no-any-casts + +.eslint-plugin-local/vscode-dts-vscode-in-comments.ts: + 43: // eslint-disable-next-line local/code-no-any-casts + 45: // eslint-disable-next-line local/code-no-any-casts + +build/azure-pipelines/upload-cdn.js: + 108: // eslint-disable-next-line local/code-no-any-casts + +build/azure-pipelines/upload-cdn.ts: + 115: // eslint-disable-next-line local/code-no-any-casts + +build/azure-pipelines/common/publish.ts: + 524: // eslint-disable-next-line local/code-no-any-casts + +build/lib/compilation.ts: + 153: // eslint-disable-next-line local/code-no-any-casts + +build/lib/extensions.ts: + 120: // eslint-disable-next-line local/code-no-any-casts + 219: // eslint-disable-next-line local/code-no-any-casts + +build/lib/fetch.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +build/lib/monaco-api.ts: + 221: // eslint-disable-next-line local/code-no-any-casts + 328: // eslint-disable-next-line local/code-no-any-casts + +build/lib/nls.ts: + 43: // eslint-disable-next-line local/code-no-any-casts + 507: // eslint-disable-next-line local/code-no-any-casts + 513: // eslint-disable-next-line local/code-no-any-casts + +build/lib/optimize.ts: + 254: // eslint-disable-next-line local/code-no-any-casts + +build/lib/propertyInitOrderChecker.ts: + 254: // eslint-disable-next-line local/code-no-any-casts + 256: // eslint-disable-next-line local/code-no-any-casts + 258: // eslint-disable-next-line local/code-no-any-casts + +build/lib/reporter.ts: + 108: // eslint-disable-next-line local/code-no-any-casts + 113: // eslint-disable-next-line local/code-no-any-casts + 117: // eslint-disable-next-line local/code-no-any-casts + +build/lib/task.ts: + 27: // eslint-disable-next-line local/code-no-any-casts + +build/lib/treeshaking.ts: + 310: // eslint-disable-next-line local/code-no-any-casts + 314: // eslint-disable-next-line local/code-no-any-casts + 318: // eslint-disable-next-line local/code-no-any-casts + 322: // eslint-disable-next-line local/code-no-any-casts + 691: // eslint-disable-next-line local/code-no-any-casts + 920: // eslint-disable-next-line local/code-no-any-casts + 922: // eslint-disable-next-line local/code-no-any-casts + 924: // eslint-disable-next-line local/code-no-any-casts + +build/lib/util.ts: + 132: // eslint-disable-next-line local/code-no-any-casts + 357: // eslint-disable-next-line local/code-no-any-casts + +build/lib/mangle/index.ts: + 273: // eslint-disable-next-line local/code-no-any-casts + +build/lib/tsb/builder.ts: + 62: // eslint-disable-next-line local/code-no-any-casts + 84: // eslint-disable-next-line local/code-no-any-casts + 226: // eslint-disable-next-line local/code-no-any-casts + 245: // eslint-disable-next-line local/code-no-any-casts + 593: // eslint-disable-next-line local/code-no-any-casts + +build/lib/tsb/index.ts: + 134: // eslint-disable-next-line local/code-no-any-casts + 138: // eslint-disable-next-line local/code-no-any-casts + +build/lib/watch/watch-win32.ts: + 50: // eslint-disable-next-line local/code-no-any-casts + +build/linux/debian/install-sysroot.ts: + 85: // eslint-disable-next-line local/code-no-any-casts + +build/win32/explorer-dll-fetcher.ts: + 63: // eslint-disable-next-line local/code-no-any-casts + +extensions/css-language-features/client/src/cssClient.ts: + 86: // eslint-disable-next-line local/code-no-any-casts + +extensions/css-language-features/server/src/cssServer.ts: + 71: // eslint-disable-next-line local/code-no-any-casts + 74: // eslint-disable-next-line local/code-no-any-casts + 171: // eslint-disable-next-line local/code-no-any-casts + +extensions/emmet/src/emmetCommon.ts: + 187: // eslint-disable-next-line local/code-no-any-casts + 189: // eslint-disable-next-line local/code-no-any-casts + +extensions/emmet/src/updateImageSize.ts: + 276: // eslint-disable-next-line local/code-no-any-casts + 278: // eslint-disable-next-line local/code-no-any-casts + +extensions/emmet/src/util.ts: + 357: // eslint-disable-next-line local/code-no-any-casts + +extensions/git/src/commands.ts: + 2384: // eslint-disable-next-line local/code-no-any-casts + 2387: // eslint-disable-next-line local/code-no-any-casts + 5373: // eslint-disable-next-line local/code-no-any-casts + +extensions/git/src/emoji.ts: + 27: // eslint-disable-next-line local/code-no-any-casts + +extensions/git/src/git.ts: + 311: // eslint-disable-next-line local/code-no-any-casts + 2976: // eslint-disable-next-line local/code-no-any-casts + 2978: // eslint-disable-next-line local/code-no-any-casts + +extensions/git/src/main.ts: + 87: // eslint-disable-next-line local/code-no-any-casts + +extensions/git/src/util.ts: + 42: // eslint-disable-next-line local/code-no-any-casts + 114: // eslint-disable-next-line local/code-no-any-casts + 241: // eslint-disable-next-line local/code-no-any-casts + +extensions/git-base/src/api/api1.ts: + 17: // eslint-disable-next-line local/code-no-any-casts + 38: // eslint-disable-next-line local/code-no-any-casts + +extensions/github-authentication/src/node/crypto.ts: + 8: // eslint-disable-next-line local/code-no-any-casts + +extensions/grunt/src/main.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + +extensions/gulp/src/main.ts: + 153: // eslint-disable-next-line local/code-no-any-casts + 156: // eslint-disable-next-line local/code-no-any-casts + +extensions/html-language-features/client/src/htmlClient.ts: + 182: // eslint-disable-next-line local/code-no-any-casts + +extensions/html-language-features/server/src/htmlServer.ts: + 137: // eslint-disable-next-line local/code-no-any-casts + 140: // eslint-disable-next-line local/code-no-any-casts + 545: // eslint-disable-next-line local/code-no-any-casts + +extensions/ipynb/src/deserializers.ts: + 23: // eslint-disable-next-line local/code-no-any-casts + 294: // eslint-disable-next-line local/code-no-any-casts + +extensions/ipynb/src/helper.ts: + 14: // eslint-disable-next-line local/code-no-any-casts + 18: // eslint-disable-next-line local/code-no-any-casts + 20: // eslint-disable-next-line local/code-no-any-casts + 22: // eslint-disable-next-line local/code-no-any-casts + 25: // eslint-disable-next-line local/code-no-any-casts + +extensions/ipynb/src/serializers.ts: + 40: // eslint-disable-next-line local/code-no-any-casts + 61: // eslint-disable-next-line local/code-no-any-casts + 403: // eslint-disable-next-line local/code-no-any-casts + 405: // eslint-disable-next-line local/code-no-any-casts + +extensions/ipynb/src/test/notebookModelStoreSync.test.ts: + 40: // eslint-disable-next-line local/code-no-any-casts + 79: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + 141: // eslint-disable-next-line local/code-no-any-casts + 176: // eslint-disable-next-line local/code-no-any-casts + 213: // eslint-disable-next-line local/code-no-any-casts + 251: // eslint-disable-next-line local/code-no-any-casts + 274: // eslint-disable-next-line local/code-no-any-casts + 303: // eslint-disable-next-line local/code-no-any-casts + 347: // eslint-disable-next-line local/code-no-any-casts + 371: // eslint-disable-next-line local/code-no-any-casts + 400: // eslint-disable-next-line local/code-no-any-casts + 424: // eslint-disable-next-line local/code-no-any-casts + 459: // eslint-disable-next-line local/code-no-any-casts + +extensions/jake/src/main.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + 126: // eslint-disable-next-line local/code-no-any-casts + +extensions/json-language-features/client/src/jsonClient.ts: + 775: // eslint-disable-next-line local/code-no-any-casts + +extensions/json-language-features/server/src/jsonServer.ts: + 144: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/notebook/index.ts: + 383: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/preview-src/index.ts: + 26: // eslint-disable-next-line local/code-no-any-casts + 253: // eslint-disable-next-line local/code-no-any-casts + 444: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/src/markdownEngine.ts: + 146: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: + 53: // eslint-disable-next-line local/code-no-any-casts + +extensions/microsoft-authentication/src/common/telemetryReporter.ts: + 97: // eslint-disable-next-line local/code-no-any-casts + +extensions/notebook-renderers/src/test/notebookRenderer.test.ts: + 130: // eslint-disable-next-line local/code-no-any-casts + 137: // eslint-disable-next-line local/code-no-any-casts + +extensions/npm/src/tasks.ts: + 61: // eslint-disable-next-line local/code-no-any-casts + 64: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/cd.test.ts: + 11: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/code.test.ts: + 80: // eslint-disable-next-line local/code-no-any-casts + 269: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/git.test.ts: + 14: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts: + 54: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts: + 22: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts: + 13: // eslint-disable-next-line local/code-no-any-casts + +extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/src/languageFeatures/completions.ts: + 780: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts: + 108: // eslint-disable-next-line local/code-no-any-casts + 111: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts: + 186: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/src/tsServer/server.ts: + 286: // eslint-disable-next-line local/code-no-any-casts + 302: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/src/utils/platform.ts: + 13: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/web/src/serverHost.ts: + 32: // eslint-disable-next-line local/code-no-any-casts + 45: // eslint-disable-next-line local/code-no-any-casts + 47: // eslint-disable-next-line local/code-no-any-casts + 51: // eslint-disable-next-line local/code-no-any-casts + 53: // eslint-disable-next-line local/code-no-any-casts + 55: // eslint-disable-next-line local/code-no-any-casts + 57: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/web/src/webServer.ts: + 16: // eslint-disable-next-line local/code-no-any-casts + +extensions/typescript-language-features/web/src/workerSession.ts: + 34: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/extension.ts: + 10: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: + 181: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts: + 33: // eslint-disable-next-line local/code-no-any-casts + 48: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts: + 24: // eslint-disable-next-line local/code-no-any-casts + 26: // eslint-disable-next-line local/code-no-any-casts + 28: // eslint-disable-next-line local/code-no-any-casts + 30: // eslint-disable-next-line local/code-no-any-casts + 32: // eslint-disable-next-line local/code-no-any-casts + 34: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: + 59: // eslint-disable-next-line local/code-no-any-casts + 76: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: + 16: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: + 18: // eslint-disable-next-line local/code-no-any-casts + +extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts: + 44: // eslint-disable-next-line local/code-no-any-casts + 50: // eslint-disable-next-line local/code-no-any-casts + 463: // eslint-disable-next-line local/code-no-any-casts + +scripts/playground-server.ts: + 257: // eslint-disable-next-line local/code-no-any-casts + 336: // eslint-disable-next-line local/code-no-any-casts + 352: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/browser.ts: + 134: // eslint-disable-next-line local/code-no-any-casts + 141: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/canIUse.ts: + 36: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/deviceAccess.ts: + 26: // eslint-disable-next-line local/code-no-any-casts + 63: // eslint-disable-next-line local/code-no-any-casts + 92: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/dom.ts: + 430: // eslint-disable-next-line local/code-no-any-casts + 719: // eslint-disable-next-line local/code-no-any-casts + 1325: // eslint-disable-next-line local/code-no-any-casts + 1520: // eslint-disable-next-line local/code-no-any-casts + 1660: // eslint-disable-next-line local/code-no-any-casts + 2013: // eslint-disable-next-line local/code-no-any-casts + 2116: // eslint-disable-next-line local/code-no-any-casts + 2128: // eslint-disable-next-line local/code-no-any-casts + 2291: // eslint-disable-next-line local/code-no-any-casts + 2297: // eslint-disable-next-line local/code-no-any-casts + 2325: // eslint-disable-next-line local/code-no-any-casts + 2437: // eslint-disable-next-line local/code-no-any-casts + 2444: // eslint-disable-next-line local/code-no-any-casts + 2529: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/fastDomNode.ts: + 294: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/mouseEvent.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + 138: // eslint-disable-next-line local/code-no-any-casts + 155: // eslint-disable-next-line local/code-no-any-casts + 157: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/trustedTypes.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + 31: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/webWorkerFactory.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + 22: // eslint-disable-next-line local/code-no-any-casts + 43: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/grid/grid.ts: + 66: // eslint-disable-next-line local/code-no-any-casts + 873: // eslint-disable-next-line local/code-no-any-casts + 875: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/grid/gridview.ts: + 196: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/sash/sash.ts: + 491: // eslint-disable-next-line local/code-no-any-casts + 497: // eslint-disable-next-line local/code-no-any-casts + 503: // eslint-disable-next-line local/code-no-any-casts + 505: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/tree/abstractTree.ts: + 171: // eslint-disable-next-line local/code-no-any-casts + 175: // eslint-disable-next-line local/code-no-any-casts + 1186: // eslint-disable-next-line local/code-no-any-casts + 1212: // eslint-disable-next-line local/code-no-any-casts + 2241: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/tree/asyncDataTree.ts: + 305: // eslint-disable-next-line local/code-no-any-casts + 417: // eslint-disable-next-line local/code-no-any-casts + 434: // eslint-disable-next-line local/code-no-any-casts + 438: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/tree/indexTreeModel.ts: + 89: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/console.ts: + 134: // eslint-disable-next-line local/code-no-any-casts + 138: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/controlFlow.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/decorators.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/errors.ts: + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/hotReload.ts: + 97: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/hotReloadHelpers.ts: + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/lifecycle.ts: + 236: // eslint-disable-next-line local/code-no-any-casts + 246: // eslint-disable-next-line local/code-no-any-casts + 257: // eslint-disable-next-line local/code-no-any-casts + 317: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/map.ts: + 124: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/marshalling.ts: + 53: // eslint-disable-next-line local/code-no-any-casts + 55: // eslint-disable-next-line local/code-no-any-casts + 57: // eslint-disable-next-line local/code-no-any-casts + 65: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/network.ts: + 416: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/objects.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/process.ts: + 12: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/skipList.ts: + 38: // eslint-disable-next-line local/code-no-any-casts + 47: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/types.ts: + 58: // eslint-disable-next-line local/code-no-any-casts + 66: // eslint-disable-next-line local/code-no-any-casts + 268: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/uriIpc.ts: + 33: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/verifier.ts: + 82: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/changeTracker.ts: + 34: // eslint-disable-next-line local/code-no-any-casts + 42: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/debugLocation.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/debugName.ts: + 106: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/set.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/experimental/reducer.ts: + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: + 12: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/debugger/rpc.ts: + 94: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/derived.ts: + 38: // eslint-disable-next-line local/code-no-any-casts + 40: // eslint-disable-next-line local/code-no-any-casts + 124: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + 160: // eslint-disable-next-line local/code-no-any-casts + 165: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/derivedImpl.ts: + 313: // eslint-disable-next-line local/code-no-any-casts + 414: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/observableFromEvent.ts: + 151: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/reactions/autorunImpl.ts: + 185: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/utils/utilsCancellation.ts: + 78: // eslint-disable-next-line local/code-no-any-casts + 83: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/worker/webWorker.ts: + 356: // eslint-disable-next-line local/code-no-any-casts + 362: // eslint-disable-next-line local/code-no-any-casts + 375: // eslint-disable-next-line local/code-no-any-casts + 383: // eslint-disable-next-line local/code-no-any-casts + 464: // eslint-disable-next-line local/code-no-any-casts + 470: // eslint-disable-next-line local/code-no-any-casts + 483: // eslint-disable-next-line local/code-no-any-casts + 491: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/node/ports.ts: + 182: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/node/unc.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + 94: // eslint-disable-next-line local/code-no-any-casts + 103: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/parts/ipc/common/ipc.ts: + 599: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/parts/ipc/node/ipc.net.ts: + 730: // eslint-disable-next-line local/code-no-any-casts + 784: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/parts/ipc/test/node/ipc.net.test.ts: + 87: // eslint-disable-next-line local/code-no-any-casts + 92: // eslint-disable-next-line local/code-no-any-casts + 652: // eslint-disable-next-line local/code-no-any-casts + 785: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/browser/dom.test.ts: + 407: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/buffer.test.ts: + 501: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/decorators.test.ts: + 130: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/filters.test.ts: + 28: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/glob.test.ts: + 497: // eslint-disable-next-line local/code-no-any-casts + 518: // eslint-disable-next-line local/code-no-any-casts + 763: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/json.test.ts: + 52: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/mock.ts: + 13: // eslint-disable-next-line local/code-no-any-casts + 22: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/oauth.test.ts: + 1087: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/snapshot.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + 125: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/timeTravelScheduler.ts: + 268: // eslint-disable-next-line local/code-no-any-casts + 278: // eslint-disable-next-line local/code-no-any-casts + 311: // eslint-disable-next-line local/code-no-any-casts + 317: // eslint-disable-next-line local/code-no-any-casts + 333: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/troubleshooting.ts: + 50: // eslint-disable-next-line local/code-no-any-casts + 55: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/editor.api.ts: + 44: // eslint-disable-next-line local/code-no-any-casts + 46: // eslint-disable-next-line local/code-no-any-casts + 51: // eslint-disable-next-line local/code-no-any-casts + 53: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/editorDom.ts: + 245: // eslint-disable-next-line local/code-no-any-casts + 385: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/config/editorConfiguration.ts: + 146: // eslint-disable-next-line local/code-no-any-casts + 291: // eslint-disable-next-line local/code-no-any-casts + 345: // eslint-disable-next-line local/code-no-any-casts + 347: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/config/migrateOptions.ts: + 215: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/controller/mouseHandler.ts: + 248: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/controller/mouseTarget.ts: + 153: // eslint-disable-next-line local/code-no-any-casts + 993: // eslint-disable-next-line local/code-no-any-casts + 997: // eslint-disable-next-line local/code-no-any-casts + 1001: // eslint-disable-next-line local/code-no-any-casts + 1044: // eslint-disable-next-line local/code-no-any-casts + 1097: // eslint-disable-next-line local/code-no-any-casts + 1100: // eslint-disable-next-line local/code-no-any-casts + 1120: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/controller/pointerHandler.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/controller/editContext/native/editContextFactory.ts: + 13: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: + 81: // eslint-disable-next-line local/code-no-any-casts + 85: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/gpu/gpuUtils.ts: + 52: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/gpu/viewGpuContext.ts: + 226: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/services/hoverService/hoverService.ts: + 423: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts: + 1032: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: + 179: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: + 477: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/diffEditor/utils.ts: + 184: // eslint-disable-next-line local/code-no-any-casts + 194: // eslint-disable-next-line local/code-no-any-casts + 302: // eslint-disable-next-line local/code-no-any-casts + 309: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + 103: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/languages.ts: + 460: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/textModelEditSource.ts: + 59: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/config/editorOptions.ts: + 1174: // eslint-disable-next-line local/code-no-any-casts + 1211: // eslint-disable-next-line local/code-no-any-casts + 1392: // eslint-disable-next-line local/code-no-any-casts + 1428: // eslint-disable-next-line local/code-no-any-casts + 4785: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/core/edits/stringEdit.ts: + 23: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: + 26: // eslint-disable-next-line local/code-no-any-casts + 30: // eslint-disable-next-line local/code-no-any-casts + 51: // eslint-disable-next-line local/code-no-any-casts + 56: // eslint-disable-next-line local/code-no-any-casts + 64: // eslint-disable-next-line local/code-no-any-casts + 72: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + 100: // eslint-disable-next-line local/code-no-any-casts + 125: // eslint-disable-next-line local/code-no-any-casts + 130: // eslint-disable-next-line local/code-no-any-casts + 135: // eslint-disable-next-line local/code-no-any-casts + 140: // eslint-disable-next-line local/code-no-any-casts + 152: // eslint-disable-next-line local/code-no-any-casts + 157: // eslint-disable-next-line local/code-no-any-casts + 174: // eslint-disable-next-line local/code-no-any-casts + 176: // eslint-disable-next-line local/code-no-any-casts + 195: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/common/services/editorBaseApi.ts: + 37: // eslint-disable-next-line local/code-no-any-casts + 42: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: + 232: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: + 200: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: + 97: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/find/browser/findModel.ts: + 556: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/find/test/browser/findController.test.ts: + 79: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: + 56: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: + 274: // eslint-disable-next-line local/code-no-any-casts + 296: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: + 346: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: + 23: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: + 163: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: + 240: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: + 797: // eslint-disable-next-line local/code-no-any-casts + 816: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/browser/standaloneEditor.ts: + 504: // eslint-disable-next-line local/code-no-any-casts + 506: // eslint-disable-next-line local/code-no-any-casts + 508: // eslint-disable-next-line local/code-no-any-casts + 510: // eslint-disable-next-line local/code-no-any-casts + 512: // eslint-disable-next-line local/code-no-any-casts + 514: // eslint-disable-next-line local/code-no-any-casts + 517: // eslint-disable-next-line local/code-no-any-casts + 519: // eslint-disable-next-line local/code-no-any-casts + 521: // eslint-disable-next-line local/code-no-any-casts + 523: // eslint-disable-next-line local/code-no-any-casts + 526: // eslint-disable-next-line local/code-no-any-casts + 528: // eslint-disable-next-line local/code-no-any-casts + 530: // eslint-disable-next-line local/code-no-any-casts + 532: // eslint-disable-next-line local/code-no-any-casts + 535: // eslint-disable-next-line local/code-no-any-casts + 537: // eslint-disable-next-line local/code-no-any-casts + 539: // eslint-disable-next-line local/code-no-any-casts + 541: // eslint-disable-next-line local/code-no-any-casts + 543: // eslint-disable-next-line local/code-no-any-casts + 545: // eslint-disable-next-line local/code-no-any-casts + 549: // eslint-disable-next-line local/code-no-any-casts + 551: // eslint-disable-next-line local/code-no-any-casts + 553: // eslint-disable-next-line local/code-no-any-casts + 555: // eslint-disable-next-line local/code-no-any-casts + 557: // eslint-disable-next-line local/code-no-any-casts + 559: // eslint-disable-next-line local/code-no-any-casts + 561: // eslint-disable-next-line local/code-no-any-casts + 567: // eslint-disable-next-line local/code-no-any-casts + 599: // eslint-disable-next-line local/code-no-any-casts + 601: // eslint-disable-next-line local/code-no-any-casts + 603: // eslint-disable-next-line local/code-no-any-casts + 605: // eslint-disable-next-line local/code-no-any-casts + 607: // eslint-disable-next-line local/code-no-any-casts + 609: // eslint-disable-next-line local/code-no-any-casts + 611: // eslint-disable-next-line local/code-no-any-casts + 614: // eslint-disable-next-line local/code-no-any-casts + 619: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/browser/standaloneLanguages.ts: + 753: // eslint-disable-next-line local/code-no-any-casts + 755: // eslint-disable-next-line local/code-no-any-casts + 757: // eslint-disable-next-line local/code-no-any-casts + 759: // eslint-disable-next-line local/code-no-any-casts + 761: // eslint-disable-next-line local/code-no-any-casts + 765: // eslint-disable-next-line local/code-no-any-casts + 768: // eslint-disable-next-line local/code-no-any-casts + 770: // eslint-disable-next-line local/code-no-any-casts + 772: // eslint-disable-next-line local/code-no-any-casts + 774: // eslint-disable-next-line local/code-no-any-casts + 776: // eslint-disable-next-line local/code-no-any-casts + 778: // eslint-disable-next-line local/code-no-any-casts + 780: // eslint-disable-next-line local/code-no-any-casts + 782: // eslint-disable-next-line local/code-no-any-casts + 784: // eslint-disable-next-line local/code-no-any-casts + 786: // eslint-disable-next-line local/code-no-any-casts + 788: // eslint-disable-next-line local/code-no-any-casts + 790: // eslint-disable-next-line local/code-no-any-casts + 792: // eslint-disable-next-line local/code-no-any-casts + 794: // eslint-disable-next-line local/code-no-any-casts + 796: // eslint-disable-next-line local/code-no-any-casts + 798: // eslint-disable-next-line local/code-no-any-casts + 800: // eslint-disable-next-line local/code-no-any-casts + 802: // eslint-disable-next-line local/code-no-any-casts + 804: // eslint-disable-next-line local/code-no-any-casts + 806: // eslint-disable-next-line local/code-no-any-casts + 808: // eslint-disable-next-line local/code-no-any-casts + 810: // eslint-disable-next-line local/code-no-any-casts + 812: // eslint-disable-next-line local/code-no-any-casts + 814: // eslint-disable-next-line local/code-no-any-casts + 816: // eslint-disable-next-line local/code-no-any-casts + 818: // eslint-disable-next-line local/code-no-any-casts + 820: // eslint-disable-next-line local/code-no-any-casts + 822: // eslint-disable-next-line local/code-no-any-casts + 824: // eslint-disable-next-line local/code-no-any-casts + 849: // eslint-disable-next-line local/code-no-any-casts + 851: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/common/monarch/monarchCompile.ts: + 461: // eslint-disable-next-line local/code-no-any-casts + 539: // eslint-disable-next-line local/code-no-any-casts + 556: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/browser/testCodeEditor.ts: + 279: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/browser/config/editorConfiguration.test.ts: + 90: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/common/model/textModel.test.ts: + 1167: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/common/model/textModelWithTokens.test.ts: + 272: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/common/model/tokenStore.test.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + 78: // eslint-disable-next-line local/code-no-any-casts + 100: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + 139: // eslint-disable-next-line local/code-no-any-casts + 158: // eslint-disable-next-line local/code-no-any-casts + 177: // eslint-disable-next-line local/code-no-any-casts + 203: // eslint-disable-next-line local/code-no-any-casts + 234: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/contextkey/common/contextkey.ts: + 939: // eslint-disable-next-line local/code-no-any-casts + 1213: // eslint-disable-next-line local/code-no-any-casts + 1273: // eslint-disable-next-line local/code-no-any-casts + 1334: // eslint-disable-next-line local/code-no-any-casts + 1395: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/contextkey/test/common/contextkey.test.ts: + 96: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts: + 93: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/dnd/browser/dnd.ts: + 469: // eslint-disable-next-line local/code-no-any-casts + 471: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/environment/test/node/argv.test.ts: + 47: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: + 243: // eslint-disable-next-line local/code-no-any-casts + 245: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: + 405: // eslint-disable-next-line local/code-no-any-casts + 407: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: + 73: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/files/browser/htmlFileSystemProvider.ts: + 311: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/files/test/node/diskFileService.integrationTest.ts: + 106: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + 112: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/hover/test/browser/nullHoverService.ts: + 16: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/instantiation/common/instantiation.ts: + 93: // eslint-disable-next-line local/code-no-any-casts + 95: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + 100: // eslint-disable-next-line local/code-no-any-casts + 114: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/instantiation/common/instantiationService.ts: + 328: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/instantiation/test/common/instantiationServiceMock.ts: + 61: // eslint-disable-next-line local/code-no-any-casts + 164: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/list/browser/listService.ts: + 877: // eslint-disable-next-line local/code-no-any-casts + 918: // eslint-disable-next-line local/code-no-any-casts + 965: // eslint-disable-next-line local/code-no-any-casts + 1012: // eslint-disable-next-line local/code-no-any-casts + 1057: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/mcp/test/common/mcpManagementService.test.ts: + 879: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/observable/common/wrapInHotClass.ts: + 12: // eslint-disable-next-line local/code-no-any-casts + 40: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/observable/common/wrapInReloadableClass.ts: + 31: // eslint-disable-next-line local/code-no-any-casts + 38: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/policy/node/nativePolicyService.ts: + 47: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/product/common/product.ts: + 18: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/profiling/common/profilingTelemetrySpec.ts: + 73: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/quickinput/browser/tree/quickTree.ts: + 73: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/quickinput/test/browser/quickinput.test.ts: + 69: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/browser/browserSocketFactory.ts: + 89: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/common/remoteAgentConnection.ts: + 784: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/request/electron-utility/requestService.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/telemetry/test/browser/telemetryService.test.ts: + 281: // eslint-disable-next-line local/code-no-any-casts + 312: // eslint-disable-next-line local/code-no-any-casts + 336: // eslint-disable-next-line local/code-no-any-casts + 344: // eslint-disable-next-line local/code-no-any-casts + 396: // eslint-disable-next-line local/code-no-any-casts + 459: // eslint-disable-next-line local/code-no-any-casts + 559: // eslint-disable-next-line local/code-no-any-casts + 624: // eslint-disable-next-line local/code-no-any-casts + 694: // eslint-disable-next-line local/code-no-any-casts + 738: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/terminal/common/terminalProfiles.ts: + 119: // eslint-disable-next-line local/code-no-any-casts + 121: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts: + 644: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/terminal/node/terminalProcess.ts: + 545: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/userDataSync/common/extensionsSync.ts: + 60: // eslint-disable-next-line local/code-no-any-casts + 64: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: + 22: // eslint-disable-next-line local/code-no-any-casts + +src/vs/server/node/extensionHostConnection.ts: + 240: // eslint-disable-next-line local/code-no-any-casts + +src/vs/server/node/remoteExtensionHostAgentServer.ts: + 765: // eslint-disable-next-line local/code-no-any-casts + 767: // eslint-disable-next-line local/code-no-any-casts + 769: // eslint-disable-next-line local/code-no-any-casts + +src/vs/server/node/server.main.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/workbench.web.main.internal.ts: + 198: // eslint-disable-next-line local/code-no-any-casts + 223: // eslint-disable-next-line local/code-no-any-casts + 225: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/workbench.web.main.ts: + 58: // eslint-disable-next-line local/code-no-any-casts + 60: // eslint-disable-next-line local/code-no-any-casts + 82: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadChatAgents2.ts: + 365: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadExtensionService.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: + 1000: // eslint-disable-next-line local/code-no-any-casts + 1011: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadQuickOpen.ts: + 195: // eslint-disable-next-line local/code-no-any-casts + 198: // eslint-disable-next-line local/code-no-any-casts + 203: // eslint-disable-next-line local/code-no-any-casts + 216: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadTelemetry.ts: + 58: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadTreeViews.ts: + 378: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/viewsExtensionPoint.ts: + 528: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHost.api.impl.ts: + 161: // eslint-disable-next-line local/code-no-any-casts + 315: // eslint-disable-next-line local/code-no-any-casts + 324: // eslint-disable-next-line local/code-no-any-casts + 563: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHost.protocol.ts: + 2106: // eslint-disable-next-line local/code-no-any-casts + 2108: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostComments.ts: + 672: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostDebugService.ts: + 243: // eslint-disable-next-line local/code-no-any-casts + 491: // eslint-disable-next-line local/code-no-any-casts + 493: // eslint-disable-next-line local/code-no-any-casts + 495: // eslint-disable-next-line local/code-no-any-casts + 666: // eslint-disable-next-line local/code-no-any-casts + 770: // eslint-disable-next-line local/code-no-any-casts + 778: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: + 65: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostExtensionActivator.ts: + 405: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostExtensionService.ts: + 566: // eslint-disable-next-line local/code-no-any-casts + 1009: // eslint-disable-next-line local/code-no-any-casts + 1050: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostLanguageFeatures.ts: + 197: // eslint-disable-next-line local/code-no-any-casts + 714: // eslint-disable-next-line local/code-no-any-casts + 735: // eslint-disable-next-line local/code-no-any-casts + 748: // eslint-disable-next-line local/code-no-any-casts + 771: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostLanguageModels.ts: + 174: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostLanguageModelTools.ts: + 221: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostMcp.ts: + 163: // eslint-disable-next-line local/code-no-any-casts + 165: // eslint-disable-next-line local/code-no-any-casts + 168: // eslint-disable-next-line local/code-no-any-casts + 170: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostSCM.ts: + 374: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostSearch.ts: + 221: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTerminalService.ts: + 1287: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTimeline.ts: + 160: // eslint-disable-next-line local/code-no-any-casts + 163: // eslint-disable-next-line local/code-no-any-casts + 166: // eslint-disable-next-line local/code-no-any-casts + 169: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTunnelService.ts: + 133: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTypeConverters.ts: + 462: // eslint-disable-next-line local/code-no-any-casts + 855: // eslint-disable-next-line local/code-no-any-casts + 3141: // eslint-disable-next-line local/code-no-any-casts + 3143: // eslint-disable-next-line local/code-no-any-casts + 3145: // eslint-disable-next-line local/code-no-any-casts + 3147: // eslint-disable-next-line local/code-no-any-casts + 3149: // eslint-disable-next-line local/code-no-any-casts + 3151: // eslint-disable-next-line local/code-no-any-casts + 3153: // eslint-disable-next-line local/code-no-any-casts + 3155: // eslint-disable-next-line local/code-no-any-casts + 3162: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTypes.ts: + 3175: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extensionHostProcess.ts: + 107: // eslint-disable-next-line local/code-no-any-casts + 119: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extHostConsoleForwarder.ts: + 31: // eslint-disable-next-line local/code-no-any-casts + 53: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extHostMcpNode.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/proxyResolver.ts: + 92: // eslint-disable-next-line local/code-no-any-casts + 95: // eslint-disable-next-line local/code-no-any-casts + 103: // eslint-disable-next-line local/code-no-any-casts + 126: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + 132: // eslint-disable-next-line local/code-no-any-casts + 373: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: + 870: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + 164: // eslint-disable-next-line local/code-no-any-casts + 173: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostCommands.test.ts: + 92: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: + 750: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + 48: // eslint-disable-next-line local/code-no-any-casts + 50: // eslint-disable-next-line local/code-no-any-casts + 52: // eslint-disable-next-line local/code-no-any-casts + 54: // eslint-disable-next-line local/code-no-any-casts + 56: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: + 84: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: + 1068: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: + 164: // eslint-disable-next-line local/code-no-any-casts + 166: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: + 107: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + 111: // eslint-disable-next-line local/code-no-any-casts + 114: // eslint-disable-next-line local/code-no-any-casts + 121: // eslint-disable-next-line local/code-no-any-casts + 128: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTesting.test.ts: + 640: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: + 265: // eslint-disable-next-line local/code-no-any-casts + 290: // eslint-disable-next-line local/code-no-any-casts + 327: // eslint-disable-next-line local/code-no-any-casts + 340: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: + 771: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTypes.test.ts: + 87: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 209: // eslint-disable-next-line local/code-no-any-casts + 211: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: + 541: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: + 115: // eslint-disable-next-line local/code-no-any-casts + 122: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: + 141: // eslint-disable-next-line local/code-no-any-casts + 215: // eslint-disable-next-line local/code-no-any-casts + 230: // eslint-disable-next-line local/code-no-any-casts + 307: // eslint-disable-next-line local/code-no-any-casts + 310: // eslint-disable-next-line local/code-no-any-casts + 313: // eslint-disable-next-line local/code-no-any-casts + 316: // eslint-disable-next-line local/code-no-any-casts + 508: // eslint-disable-next-line local/code-no-any-casts + 512: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: + 86: // eslint-disable-next-line local/code-no-any-casts + 93: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: + 115: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: + 60: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/extensionHostMain.test.ts: + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: + 86: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/testRPCProtocol.ts: + 36: // eslint-disable-next-line local/code-no-any-casts + 163: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/node/extHostSearch.test.ts: + 177: // eslint-disable-next-line local/code-no-any-casts + 1004: // eslint-disable-next-line local/code-no-any-casts + 1050: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/worker/extensionHostWorker.ts: + 83: // eslint-disable-next-line local/code-no-any-casts + 85: // eslint-disable-next-line local/code-no-any-casts + 87: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 93: // eslint-disable-next-line local/code-no-any-casts + 95: // eslint-disable-next-line local/code-no-any-casts + 97: // eslint-disable-next-line local/code-no-any-casts + 100: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 158: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/worker/extHostConsoleForwarder.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/layout.ts: + 428: // eslint-disable-next-line local/code-no-any-casts + 430: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/window.ts: + 145: // eslint-disable-next-line local/code-no-any-casts + 155: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/workbench.ts: + 208: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/actions/developerActions.ts: + 779: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/parts/editor/editorStatus.ts: + 1377: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/parts/titlebar/titlebarPart.ts: + 494: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/parts/views/viewPaneContainer.ts: + 687: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: + 54: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: + 29: // eslint-disable-next-line local/code-no-any-casts + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatViewPane.ts: + 304: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts: + 125: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts: + 244: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: + 88: // eslint-disable-next-line local/code-no-any-casts + 90: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: + 126: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/common/chatAgents.ts: + 746: // eslint-disable-next-line local/code-no-any-casts + 748: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/common/chatModel.ts: + 1213: // eslint-disable-next-line local/code-no-any-casts + 1526: // eslint-disable-next-line local/code-no-any-casts + 1858: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/common/chatModes.ts: + 236: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: + 439: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: + 30: // eslint-disable-next-line local/code-no-any-casts + 35: // eslint-disable-next-line local/code-no-any-casts + 63: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 161: // eslint-disable-next-line local/code-no-any-casts + 204: // eslint-disable-next-line local/code-no-any-casts + 209: // eslint-disable-next-line local/code-no-any-casts + 415: // eslint-disable-next-line local/code-no-any-casts + 420: // eslint-disable-next-line local/code-no-any-casts + 577: // eslint-disable-next-line local/code-no-any-casts + 582: // eslint-disable-next-line local/code-no-any-casts + 619: // eslint-disable-next-line local/code-no-any-casts + 668: // eslint-disable-next-line local/code-no-any-casts + 726: // eslint-disable-next-line local/code-no-any-casts + 774: // eslint-disable-next-line local/code-no-any-casts + 779: // eslint-disable-next-line local/code-no-any-casts + 790: // eslint-disable-next-line local/code-no-any-casts + 1532: // eslint-disable-next-line local/code-no-any-casts + 1537: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: + 41: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: + 35: // eslint-disable-next-line local/code-no-any-casts + 144: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts: + 981: // eslint-disable-next-line local/code-no-any-casts + 1012: // eslint-disable-next-line local/code-no-any-casts + 1519: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: + 48: // eslint-disable-next-line local/code-no-any-casts + 75: // eslint-disable-next-line local/code-no-any-casts + 87: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + 111: // eslint-disable-next-line local/code-no-any-casts + 123: // eslint-disable-next-line local/code-no-any-casts + 135: // eslint-disable-next-line local/code-no-any-casts + 145: // eslint-disable-next-line local/code-no-any-casts + 157: // eslint-disable-next-line local/code-no-any-casts + 167: // eslint-disable-next-line local/code-no-any-casts + 179: // eslint-disable-next-line local/code-no-any-casts + 189: // eslint-disable-next-line local/code-no-any-casts + 201: // eslint-disable-next-line local/code-no-any-casts + 211: // eslint-disable-next-line local/code-no-any-casts + 256: // eslint-disable-next-line local/code-no-any-casts + 267: // eslint-disable-next-line local/code-no-any-casts + 278: // eslint-disable-next-line local/code-no-any-casts + 289: // eslint-disable-next-line local/code-no-any-casts + 300: // eslint-disable-next-line local/code-no-any-casts + 311: // eslint-disable-next-line local/code-no-any-casts + 322: // eslint-disable-next-line local/code-no-any-casts + 333: // eslint-disable-next-line local/code-no-any-casts + 349: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/common/chatService.test.ts: + 149: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: + 16: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts: + 168: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: + 189: // eslint-disable-next-line local/code-no-any-casts + 196: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: + 93: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/browser/debugSession.ts: + 1193: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: + 813: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/common/debugger.ts: + 168: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/common/debugUtils.ts: + 66: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: + 450: // eslint-disable-next-line local/code-no-any-casts + 466: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: + 28: // eslint-disable-next-line local/code-no-any-casts + 124: // eslint-disable-next-line local/code-no-any-casts + 206: // eslint-disable-next-line local/code-no-any-casts + 261: // eslint-disable-next-line local/code-no-any-casts + 461: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: + 92: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: + 76: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: + 28: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/repl.test.ts: + 139: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: + 50: // eslint-disable-next-line local/code-no-any-casts + 62: // eslint-disable-next-line local/code-no-any-casts + 70: // eslint-disable-next-line local/code-no-any-casts + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/common/mockDebug.ts: + 695: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: + 147: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: + 1182: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: + 99: // eslint-disable-next-line local/code-no-any-casts + 101: // eslint-disable-next-line local/code-no-any-casts + 103: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: + 94: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: + 160: // eslint-disable-next-line local/code-no-any-casts + 625: // eslint-disable-next-line local/code-no-any-casts + 674: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: + 72: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/markers/browser/markersTable.ts: + 343: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: + 143: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mcp/common/uriTemplate.ts: + 159: // eslint-disable-next-line local/code-no-any-casts + 191: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: + 68: // eslint-disable-next-line local/code-no-any-casts + 212: // eslint-disable-next-line local/code-no-any-casts + 218: // eslint-disable-next-line local/code-no-any-casts + 262: // eslint-disable-next-line local/code-no-any-casts + 264: // eslint-disable-next-line local/code-no-any-casts + 271: // eslint-disable-next-line local/code-no-any-casts + 280: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: + 53: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: + 18: // eslint-disable-next-line local/code-no-any-casts + 52: // eslint-disable-next-line local/code-no-any-casts + 95: // eslint-disable-next-line local/code-no-any-casts + 128: // eslint-disable-next-line local/code-no-any-casts + 147: // eslint-disable-next-line local/code-no-any-casts + 168: // eslint-disable-next-line local/code-no-any-casts + 220: // eslint-disable-next-line local/code-no-any-casts + 231: // eslint-disable-next-line local/code-no-any-casts + 254: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mergeEditor/browser/utils.ts: + 89: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: + 69: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: + 309: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: + 127: // eslint-disable-next-line local/code-no-any-casts + 199: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: + 74: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: + 122: // eslint-disable-next-line local/code-no-any-casts + 1462: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: + 329: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: + 170: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: + 424: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: + 563: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: + 1095: // eslint-disable-next-line local/code-no-any-casts + 1137: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: + 50: // eslint-disable-next-line local/code-no-any-casts + 66: // eslint-disable-next-line local/code-no-any-casts + 85: // eslint-disable-next-line local/code-no-any-casts + 96: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: + 284: // eslint-disable-next-line local/code-no-any-casts + 308: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: + 37: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: + 72: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: + 26: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: + 652: // eslint-disable-next-line local/code-no-any-casts + 654: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchActionsFind.ts: + 460: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchMessage.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: + 306: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: + 299: // eslint-disable-next-line local/code-no-any-casts + 301: // eslint-disable-next-line local/code-no-any-casts + 318: // eslint-disable-next-line local/code-no-any-casts + 324: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: + 201: // eslint-disable-next-line local/code-no-any-casts + 229: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: + 205: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: + 155: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: + 328: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts: + 157: // eslint-disable-next-line local/code-no-any-casts + 168: // eslint-disable-next-line local/code-no-any-casts + 284: // eslint-disable-next-line local/code-no-any-casts + 291: // eslint-disable-next-line local/code-no-any-casts + 302: // eslint-disable-next-line local/code-no-any-casts + 309: // eslint-disable-next-line local/code-no-any-casts + 340: // eslint-disable-next-line local/code-no-any-casts + 345: // eslint-disable-next-line local/code-no-any-casts + 352: // eslint-disable-next-line local/code-no-any-casts + 357: // eslint-disable-next-line local/code-no-any-casts + 659: // eslint-disable-next-line local/code-no-any-casts + 673: // eslint-disable-next-line local/code-no-any-casts + 701: // eslint-disable-next-line local/code-no-any-casts + 703: // eslint-disable-next-line local/code-no-any-casts + 717: // eslint-disable-next-line local/code-no-any-casts + 719: // eslint-disable-next-line local/code-no-any-casts + 733: // eslint-disable-next-line local/code-no-any-casts + 735: // eslint-disable-next-line local/code-no-any-casts + 768: // eslint-disable-next-line local/code-no-any-casts + 770: // eslint-disable-next-line local/code-no-any-casts + 810: // eslint-disable-next-line local/code-no-any-casts + 813: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: + 1491: // eslint-disable-next-line local/code-no-any-casts + 1500: // eslint-disable-next-line local/code-no-any-casts + 1539: // eslint-disable-next-line local/code-no-any-casts + 1585: // eslint-disable-next-line local/code-no-any-casts + 1739: // eslint-disable-next-line local/code-no-any-casts + 1786: // eslint-disable-next-line local/code-no-any-casts + 1789: // eslint-disable-next-line local/code-no-any-casts + 2641: // eslint-disable-next-line local/code-no-any-casts + 2822: // eslint-disable-next-line local/code-no-any-casts + 3554: // eslint-disable-next-line local/code-no-any-casts + 3588: // eslint-disable-next-line local/code-no-any-casts + 3594: // eslint-disable-next-line local/code-no-any-casts + 3723: // eslint-disable-next-line local/code-no-any-casts + 3782: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/problemMatcher.ts: + 361: // eslint-disable-next-line local/code-no-any-casts + 374: // eslint-disable-next-line local/code-no-any-casts + 1015: // eslint-disable-next-line local/code-no-any-casts + 1906: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: + 1720: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/tasks.ts: + 655: // eslint-disable-next-line local/code-no-any-casts + 696: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: + 84: // eslint-disable-next-line local/code-no-any-casts + 86: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts: + 290: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/browser/terminalInstance.ts: + 2145: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts: + 448: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts: + 636: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts: + 244: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: + 99: // eslint-disable-next-line local/code-no-any-casts + 445: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: + 55: // eslint-disable-next-line local/code-no-any-casts + 96: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: + 101: // eslint-disable-next-line local/code-no-any-casts + 127: // eslint-disable-next-line local/code-no-any-casts + 172: // eslint-disable-next-line local/code-no-any-casts + 227: // eslint-disable-next-line local/code-no-any-casts + 244: // eslint-disable-next-line local/code-no-any-casts + 261: // eslint-disable-next-line local/code-no-any-casts + 275: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: + 58: // eslint-disable-next-line local/code-no-any-casts + 65: // eslint-disable-next-line local/code-no-any-casts + 75: // eslint-disable-next-line local/code-no-any-casts + 83: // eslint-disable-next-line local/code-no-any-casts + 93: // eslint-disable-next-line local/code-no-any-casts + 105: // eslint-disable-next-line local/code-no-any-casts + 113: // eslint-disable-next-line local/code-no-any-casts + 122: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + 141: // eslint-disable-next-line local/code-no-any-casts + 149: // eslint-disable-next-line local/code-no-any-casts + 158: // eslint-disable-next-line local/code-no-any-casts + 165: // eslint-disable-next-line local/code-no-any-casts + 178: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: + 31: // eslint-disable-next-line local/code-no-any-casts + 37: // eslint-disable-next-line local/code-no-any-casts + 45: // eslint-disable-next-line local/code-no-any-casts + 53: // eslint-disable-next-line local/code-no-any-casts + 56: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 94: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + 114: // eslint-disable-next-line local/code-no-any-casts + 117: // eslint-disable-next-line local/code-no-any-casts + 127: // eslint-disable-next-line local/code-no-any-casts + 130: // eslint-disable-next-line local/code-no-any-casts + 132: // eslint-disable-next-line local/code-no-any-casts + 141: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: + 59: // eslint-disable-next-line local/code-no-any-casts + 67: // eslint-disable-next-line local/code-no-any-casts + 75: // eslint-disable-next-line local/code-no-any-casts + 83: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + 107: // eslint-disable-next-line local/code-no-any-casts + 165: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + 70: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: + 71: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: + 40: // eslint-disable-next-line local/code-no-any-casts + 56: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: + 379: // eslint-disable-next-line local/code-no-any-casts + 833: // eslint-disable-next-line local/code-no-any-casts + 857: // eslint-disable-next-line local/code-no-any-casts + 862: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: + 94: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: + 102: // eslint-disable-next-line local/code-no-any-casts + 108: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: + 152: // eslint-disable-next-line local/code-no-any-casts + 351: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: + 242: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: + 95: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: + 150: // eslint-disable-next-line local/code-no-any-casts + 290: // eslint-disable-next-line local/code-no-any-casts + 553: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: + 49: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + 108: // eslint-disable-next-line local/code-no-any-casts + 118: // eslint-disable-next-line local/code-no-any-casts + 126: // eslint-disable-next-line local/code-no-any-casts + 136: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: + 409: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: + 685: // eslint-disable-next-line local/code-no-any-casts + 711: // eslint-disable-next-line local/code-no-any-casts + 786: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: + 53: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + 125: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/common/observableUtils.ts: + 17: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/common/testingStates.ts: + 78: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: + 122: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: + 35: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: + 42: // eslint-disable-next-line local/code-no-any-casts + 137: // eslint-disable-next-line local/code-no-any-casts + 154: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: + 27: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: + 49: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: + 210: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/common/testStubs.ts: + 85: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/themes/browser/themes.contribution.ts: + 614: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/timeline/browser/timelinePane.ts: + 824: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: + 68: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: + 600: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts: + 128: // eslint-disable-next-line local/code-no-any-casts + 131: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/assignment/common/assignmentFilters.ts: + 104: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: + 236: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: + 81: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 306: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/configurationResolver/common/variableResolver.ts: + 93: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: + 516: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/driver/browser/driver.ts: + 193: // eslint-disable-next-line local/code-no-any-casts + 215: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: + 61: // eslint-disable-next-line local/code-no-any-casts + 63: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/common/extensionsRegistry.ts: + 229: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: + 49: // eslint-disable-next-line local/code-no-any-casts + 54: // eslint-disable-next-line local/code-no-any-casts + 97: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 107: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: + 185: // eslint-disable-next-line local/code-no-any-casts + 222: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: + 47: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/keybinding/browser/keybindingService.ts: + 876: // eslint-disable-next-line local/code-no-any-casts + 888: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + 402: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/remote/common/tunnelModel.ts: + 255: // eslint-disable-next-line local/code-no-any-casts + 260: // eslint-disable-next-line local/code-no-any-casts + 262: // eslint-disable-next-line local/code-no-any-casts + 298: // eslint-disable-next-line local/code-no-any-casts + 335: // eslint-disable-next-line local/code-no-any-casts + 398: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/search/common/search.ts: + 628: // eslint-disable-next-line local/code-no-any-casts + 631: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/search/node/rawSearchService.ts: + 438: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: + 44: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: + 147: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/browser/fileIconThemeData.ts: + 122: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/browser/productIconThemeData.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/common/colorThemeData.ts: + 650: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/views/browser/viewsService.ts: + 689: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: + 67: // eslint-disable-next-line local/code-no-any-casts + 74: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 147: // eslint-disable-next-line local/code-no-any-casts + 171: // eslint-disable-next-line local/code-no-any-casts + 195: // eslint-disable-next-line local/code-no-any-casts + 241: // eslint-disable-next-line local/code-no-any-casts + 272: // eslint-disable-next-line local/code-no-any-casts + 293: // eslint-disable-next-line local/code-no-any-casts + 324: // eslint-disable-next-line local/code-no-any-casts + 370: // eslint-disable-next-line local/code-no-any-casts + 401: // eslint-disable-next-line local/code-no-any-casts + 429: // eslint-disable-next-line local/code-no-any-casts + 457: // eslint-disable-next-line local/code-no-any-casts + 489: // eslint-disable-next-line local/code-no-any-casts + 541: // eslint-disable-next-line local/code-no-any-casts + 573: // eslint-disable-next-line local/code-no-any-casts + 603: // eslint-disable-next-line local/code-no-any-casts + 627: // eslint-disable-next-line local/code-no-any-casts + 686: // eslint-disable-next-line local/code-no-any-casts + 755: // eslint-disable-next-line local/code-no-any-casts + 833: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: + 25: // eslint-disable-next-line local/code-no-any-casts + 27: // eslint-disable-next-line local/code-no-any-casts + 335: // eslint-disable-next-line local/code-no-any-casts + 395: // eslint-disable-next-line local/code-no-any-casts + 531: // eslint-disable-next-line local/code-no-any-casts + 594: // eslint-disable-next-line local/code-no-any-casts + 645: // eslint-disable-next-line local/code-no-any-casts + 678: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts: + 116: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts: + 112: // eslint-disable-next-line local/code-no-any-casts + 125: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/part.test.ts: + 133: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/window.test.ts: + 42: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/workbenchTestServices.ts: + 305: // eslint-disable-next-line local/code-no-any-casts + 697: // eslint-disable-next-line local/code-no-any-casts + 1064: // eslint-disable-next-line local/code-no-any-casts + 1967: // eslint-disable-next-line local/code-no-any-casts + 1985: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: + 98: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: + 131: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: + 95: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 113: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + 127: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/common/resources.test.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + 72: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/common/workbenchTestServices.ts: + 291: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/electron-browser/workbenchTestServices.ts: + 255: // eslint-disable-next-line local/code-no-any-casts + +test/automation/src/code.ts: + 127: // eslint-disable-next-line local/code-no-any-casts + +test/automation/src/terminal.ts: + 315: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/application.ts: + 309: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/playwright.ts: + 17: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/automationTools/problems.ts: + 76: // eslint-disable-next-line local/code-no-any-casts From edf756ab8481c6caf953da43739d2d692bcb1e24 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 4 Oct 2025 03:32:44 +0900 Subject: [PATCH 0785/4355] feat: reduce buffer size of NetToMojoPendingBuffer from 16 to 8KB (#269718) * feat: reduce buffer size of NetToMojoPendingBuffer from 16 to 8KB * chore: bump distro --- .npmrc | 2 +- package.json | 2 +- src/main.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.npmrc b/.npmrc index 5a44dc617a4..30da46b2d4c 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" target="37.6.0" -ms_build_id="12502201" +ms_build_id="12506819" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/package.json b/package.json index 145cebb932c..f4c85c3586c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "7eefb0edc90d0589e8f481c8ed3cfe00bd65c738", + "distro": "15be134acbf860e28d3a27345b6c825f3c0e1c0a", "author": { "name": "Microsoft Corporation" }, diff --git a/src/main.ts b/src/main.ts index e2bbdcd87df..3954cd352b8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -320,11 +320,12 @@ function configureCommandlineSwitchesSync(cliArgs: NativeParsedArgs) { }); // Following features are enabled from the runtime: + // `NetAdapterMaxBufSizeFeature` - Specify the max buffer size for NetToMojoPendingBuffer, refs https://github.com/microsoft/vscode/issues/268800 // `DocumentPolicyIncludeJSCallStacksInCrashReports` - https://www.electronjs.org/docs/latest/api/web-frame-main#framecollectjavascriptcallstack-experimental // `EarlyEstablishGpuChannel` - Refs https://issues.chromium.org/issues/40208065 // `EstablishGpuChannelAsync` - Refs https://issues.chromium.org/issues/40208065 const featuresToEnable = - `DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync,${app.commandLine.getSwitchValue('enable-features')}`; + `NetAdapterMaxBufSizeFeature:NetAdapterMaxBufSize/8192,DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync,${app.commandLine.getSwitchValue('enable-features')}`; app.commandLine.appendSwitch('enable-features', featuresToEnable); // Following features are disabled from the runtime: From b269e90d031448a48c72fa4768dfe803a8e25f7e Mon Sep 17 00:00:00 2001 From: Paul Wang Date: Fri, 3 Oct 2025 12:40:42 -0700 Subject: [PATCH 0786/4355] truncate --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 6 ++++-- .../chat/browser/chatSessions/view/sessionsViewPane.ts | 4 ++-- src/vs/workbench/contrib/chat/common/chatModel.ts | 5 +++++ src/vs/workbench/contrib/chat/common/constants.ts | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 608588219be..e3a65686976 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -9,6 +9,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isEqual } from '../../../../base/common/resources.js'; +import { truncate } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import * as nls from '../../../../nls.js'; @@ -20,7 +21,7 @@ import { EditorInput, IEditorCloseHandler } from '../../../common/editor/editorI import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; -import { ChatAgentLocation } from '../common/constants.js'; +import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; import type { IChatEditorOptions } from './chatEditor.js'; @@ -144,7 +145,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override getName(): string { // If we have a resolved model, use its title if (this.model?.title) { - return this.model.title; + // Only truncate if the default title is being used (don't truncate custom titles) + return this.model.hasCustomTitle ? this.model.title : truncate(this.model.title, ChatEditorTitleMaxLength); } // If we have a sessionId but no resolved model, try to get the title from persisted sessions diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 640f0a158b1..ab1bbeb7f50 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -41,7 +41,7 @@ import { IViewsService } from '../../../../../services/views/common/viewsService import { IChatService } from '../../../common/chatService.js'; import { IChatSessionItemProvider } from '../../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../../common/chatUri.js'; -import { ChatConfiguration } from '../../../common/constants.js'; +import { ChatConfiguration, ChatEditorTitleMaxLength } from '../../../common/constants.js'; import { IChatWidgetService, ChatViewId } from '../../chat.js'; import { IChatEditorOptions } from '../../chatEditor.js'; import { ChatEditorInput } from '../../chatEditorInput.js'; @@ -493,7 +493,7 @@ export class SessionsViewPane extends ViewPane { const options: IChatEditorOptions = { pinned: true, ignoreInView: true, - preferredTitle: truncate(session.label, 30), + preferredTitle: truncate(session.label, ChatEditorTitleMaxLength), preserveFocus: true, }; await this.editorService.openEditor({ diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 9800d78b4cb..813085c7628 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1056,6 +1056,7 @@ export interface IChatModel extends IDisposable { readonly sessionId: string; readonly initialLocation: ChatAgentLocation; readonly title: string; + readonly hasCustomTitle: boolean; readonly requestInProgress: boolean; readonly requestInProgressObs: IObservable; readonly inputPlaceholder?: string; @@ -1417,6 +1418,10 @@ export class ChatModel extends Disposable implements IChatModel { return this._customTitle || ChatModel.getDefaultTitle(this._requests); } + get hasCustomTitle(): boolean { + return this._customTitle !== undefined; + } + private _editingSession: ObservablePromise | undefined; get editingSessionObs(): ObservablePromise | undefined { return this._editingSession; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index e9ba6acd661..3501fbc5103 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -87,3 +87,5 @@ export namespace ChatAgentLocation { export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); export const VIEWLET_ID = 'workbench.view.chat.sessions'; + +export const ChatEditorTitleMaxLength = 30; From d8f6d26bbfedf60f4719cebde1a2b010be2bc13b Mon Sep 17 00:00:00 2001 From: Paul Wang Date: Fri, 3 Oct 2025 12:51:53 -0700 Subject: [PATCH 0787/4355] fix --- .../contrib/chat/common/chatServiceImpl.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 0391f763deb..546d8345c42 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -887,7 +887,23 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; + + // Use LLM to generate the chat title + if (model.getRequests().length === 1 && !model.customTitle) { + const chatHistory = this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id); + chatTitlePromise = this.chatAgentService.getChatTitle(agent.id, chatHistory, CancellationToken.None).then( + (title) => { + // Since not every chat agent implements title generation, we can fallback to the default agent + // which supports it + if (title === undefined) { + const defaultAgentForTitle = this.chatAgentService.getDefaultAgent(location); + if (defaultAgentForTitle) { + return this.chatAgentService.getChatTitle(defaultAgentForTitle.id, chatHistory, CancellationToken.None); + } + } + return title; + }); + } } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { if (commandPart.slashCommand.silent !== true) { request = model.addRequest(parsedRequest, { variables: [] }, attempt, options?.modeInfo); From 8573104d0074680af80f986c2c1c8ec3a7d245b5 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:08:02 -0700 Subject: [PATCH 0788/4355] Fix powershell venv Python prompt (#269637) --- .../contrib/terminal/common/scripts/shellIntegration.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 index 9a744cc2f5e..3c37defc7b2 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1 @@ -82,6 +82,7 @@ if (-not $env:VSCODE_PYTHON_AUTOACTIVATE_GUARD) { try { Invoke-Expression $activateScript + $Global:__VSCodeState.OriginalPrompt = $function:Prompt } catch { $activationError = $_ From f4d1b113721a9fa52e33ecc2c57202962c9c9886 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:36:25 -0700 Subject: [PATCH 0789/4355] Fix all any casts in lint rules For #269213 Also bumps eslint versions to fix some typing issues --- ...e-no-observable-get-in-reactive-context.ts | 13 +- .../code-no-reader-after-await.ts | 6 +- .../code-no-static-self-ref.ts | 7 +- .../code-no-test-async-suite.ts | 3 +- .../code-no-unexternalized-strings.ts | 31 +- .../vscode-dts-provider-naming.ts | 5 +- .../vscode-dts-vscode-in-comments.ts | 7 +- package-lock.json | 347 ++++++++++-------- package.json | 6 +- 9 files changed, 219 insertions(+), 206 deletions(-) diff --git a/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts index adb0e500610..2fa8e0bd9b5 100644 --- a/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts +++ b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; -import * as ESTree from 'estree'; +import * as eslint from 'eslint'; import * as visitorKeys from 'eslint-visitor-keys'; +import * as ESTree from 'estree'; export = new class NoObservableGetInReactiveContext implements eslint.Rule.RuleModule { meta: eslint.Rule.RuleMetaData = { @@ -61,13 +61,11 @@ function checkFunctionForObservableGetCalls( if (node.type === 'CallExpression' && isObservableGetCall(node)) { // Flag .get() calls since we're always in a reactive context here context.report({ - // eslint-disable-next-line local/code-no-any-casts - node: node as any as ESTree.Node, + node: node, message: `Observable '.get()' should not be used in reactive context. Use '.read(${readerName})' instead to properly track dependencies or '.read(undefined)' to be explicit about an untracked read.`, fix: (fixer) => { const memberExpression = node.callee as TSESTree.MemberExpression; - // eslint-disable-next-line local/code-no-any-casts - return fixer.replaceText(node as any, `${context.getSourceCode().getText(memberExpression.object as any)}.read(undefined)`); + return fixer.replaceText(node, `${context.getSourceCode().getText(memberExpression.object as ESTree.Node)}.read(undefined)`); } }); } @@ -133,8 +131,7 @@ function isReactiveFunctionWithReader(callee: TSESTree.Node): boolean { function walkChildren(node: TSESTree.Node, cb: (child: TSESTree.Node) => void) { const keys = visitorKeys.KEYS[node.type] || []; for (const key of keys) { - // eslint-disable-next-line local/code-no-any-casts - const child = (node as any)[key]; + const child = (node as Record)[key]; if (Array.isArray(child)) { for (const item of child) { if (item && typeof item === 'object' && item.type) { diff --git a/.eslint-plugin-local/code-no-reader-after-await.ts b/.eslint-plugin-local/code-no-reader-after-await.ts index 1b7e048ea3f..545e1fd050a 100644 --- a/.eslint-plugin-local/code-no-reader-after-await.ts +++ b/.eslint-plugin-local/code-no-reader-after-await.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; -import * as ESTree from 'estree'; +import * as eslint from 'eslint'; export = new class NoReaderAfterAwait implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { @@ -59,8 +58,7 @@ function checkFunctionForAwaitBeforeReader( if (awaitPositions.length > 0) { const methodName = getMethodName(node); context.report({ - // eslint-disable-next-line local/code-no-any-casts - node: node as any as ESTree.Node, + node: node, message: `Reader method '${methodName}' should not be called after 'await'. The reader becomes invalid after async operations.` }); } diff --git a/.eslint-plugin-local/code-no-static-self-ref.ts b/.eslint-plugin-local/code-no-static-self-ref.ts index 80968d7c344..94287b8311c 100644 --- a/.eslint-plugin-local/code-no-static-self-ref.ts +++ b/.eslint-plugin-local/code-no-static-self-ref.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; /** @@ -28,16 +29,14 @@ export = new class implements eslint.Rule.RuleModule { const classCtor = classDeclaration.body.body.find(node => node.type === 'MethodDefinition' && node.kind === 'constructor'); - if (!classCtor) { + if (!classCtor || classCtor.type === 'StaticBlock') { return; } const name = classDeclaration.id.name; - // eslint-disable-next-line local/code-no-any-casts - const valueText = context.sourceCode.getText(propertyDefinition.value); + const valueText = context.sourceCode.getText(propertyDefinition.value as ESTree.Node); if (valueText.includes(name + '.')) { - if (classCtor.value?.type === 'FunctionExpression' && !classCtor.value.params.find((param: any) => param.type === 'TSParameterProperty' && param.decorators?.length > 0)) { return; } diff --git a/.eslint-plugin-local/code-no-test-async-suite.ts b/.eslint-plugin-local/code-no-test-async-suite.ts index d9bd27b2697..60a0f2153ab 100644 --- a/.eslint-plugin-local/code-no-test-async-suite.ts +++ b/.eslint-plugin-local/code-no-test-async-suite.ts @@ -20,8 +20,7 @@ export = new class NoAsyncSuite implements eslint.Rule.RuleModule { function hasAsyncSuite(node: any) { if (isCallExpression(node) && node.arguments.length >= 2 && isFunctionExpression(node.arguments[1]) && node.arguments[1].async) { return context.report({ - // eslint-disable-next-line local/code-no-any-casts - node: node as any, + node: node, message: 'suite factory function should never be async' }); } diff --git a/.eslint-plugin-local/code-no-unexternalized-strings.ts b/.eslint-plugin-local/code-no-unexternalized-strings.ts index b787c407c81..74c712e56fd 100644 --- a/.eslint-plugin-local/code-no-unexternalized-strings.ts +++ b/.eslint-plugin-local/code-no-unexternalized-strings.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as ESTree from 'estree'; -function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral { +function isStringLiteral(node: TSESTree.Node | ESTree.Node | null | undefined): node is TSESTree.StringLiteral { return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string'; } @@ -33,7 +34,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { const externalizedStringLiterals = new Map(); const doubleQuotedStringLiterals = new Set(); - function collectDoubleQuotedStrings(node: TSESTree.Literal) { + function collectDoubleQuotedStrings(node: ESTree.Literal) { if (isStringLiteral(node) && isDoubleQuoted(node)) { doubleQuotedStringLiterals.add(node); } @@ -42,7 +43,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { function visitLocalizeCall(node: TSESTree.CallExpression) { // localize(key, message) - const [keyNode, messageNode] = (node).arguments; + const [keyNode, messageNode] = node.arguments; // (1) // extract key so that it can be checked later @@ -81,8 +82,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { context.report({ loc: messageNode.loc, messageId: 'badMessage', - // eslint-disable-next-line local/code-no-any-casts - data: { message: context.getSourceCode().getText(node) } + data: { message: context.getSourceCode().getText(node as ESTree.Node) } }); } } @@ -129,8 +129,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { // report all invalid duplicates (same key, different message) if (values.length > 1) { for (let i = 1; i < values.length; i++) { - // eslint-disable-next-line local/code-no-any-casts - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { + if (context.getSourceCode().getText(values[i - 1].message as ESTree.Node) !== context.getSourceCode().getText(values[i].message as ESTree.Node)) { context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); } } @@ -139,23 +138,23 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { } return { - ['Literal']: (node: any) => collectDoubleQuotedStrings(node), - ['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node), + ['Literal']: (node: ESTree.Literal) => collectDoubleQuotedStrings(node), + ['ExpressionStatement[directive] Literal:exit']: (node: TSESTree.Literal) => doubleQuotedStringLiterals.delete(node), // localize(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), // localize2(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize2"]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize2"]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), // vscode.l10n.t(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: TSESTree.CallExpression) => visitL10NCall(node), // l10n.t(...) - ['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + ['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: TSESTree.CallExpression) => visitL10NCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize2"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize2"][arguments.length>=2]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), ['Program:exit']: reportBadStringsAndBadKeys, }; } diff --git a/.eslint-plugin-local/vscode-dts-provider-naming.ts b/.eslint-plugin-local/vscode-dts-provider-naming.ts index d68dfb5ac4f..90409bfe058 100644 --- a/.eslint-plugin-local/vscode-dts-provider-naming.ts +++ b/.eslint-plugin-local/vscode-dts-provider-naming.ts @@ -24,16 +24,13 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { return { ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node: any) => { - - const interfaceName = ((node).parent?.parent).id.name; if (allowed.has(interfaceName)) { // allowed return; } - // eslint-disable-next-line local/code-no-any-casts - const methodName = ((node).key).name; + const methodName = ((node).key as TSESTree.Identifier).name; if (!ApiProviderNaming._providerFunctionNames.test(methodName)) { context.report({ diff --git a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts index c9aeb08effb..33fd44d8af6 100644 --- a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts +++ b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; -import type * as estree from 'estree'; export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { @@ -40,10 +39,8 @@ export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { } // Types for eslint seem incorrect - // eslint-disable-next-line local/code-no-any-casts - const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position; - // eslint-disable-next-line local/code-no-any-casts - const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position; + const start = sourceCode.getLocFromIndex(startIndex + match.index); + const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length); context.report({ messageId: 'comment', loc: { start, end } diff --git a/package-lock.json b/package-lock.json index e98c2e44f5d..0db9f4b6c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", - "@typescript-eslint/utils": "^8.36.0", + "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.35", @@ -99,7 +99,7 @@ "debounce": "^1.0.0", "deemon": "^1.13.6", "electron": "37.6.0", - "eslint": "^9.11.1", + "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^50.3.1", @@ -153,7 +153,7 @@ "tsec": "0.2.7", "tslib": "^2.6.3", "typescript": "^6.0.0-dev.20250922", - "typescript-eslint": "^8.39.0", + "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", @@ -908,9 +908,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -927,21 +927,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -949,20 +951,35 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -982,50 +999,42 @@ } }, "node_modules/@eslint/js": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", - "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -1127,6 +1136,30 @@ "xtend": "~4.0.1" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1141,10 +1174,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -2199,17 +2233,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", - "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/type-utils": "8.39.0", - "@typescript-eslint/utils": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2223,7 +2257,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.39.0", + "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2239,16 +2273,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", - "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "engines": { @@ -2264,14 +2298,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", - "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.0", - "@typescript-eslint/types": "^8.39.0", + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" }, "engines": { @@ -2286,14 +2320,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", - "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0" + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2304,9 +2338,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", - "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", "dev": true, "license": "MIT", "engines": { @@ -2321,15 +2355,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", - "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2346,9 +2380,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", - "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", "dev": true, "license": "MIT", "engines": { @@ -2360,16 +2394,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", - "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.39.0", - "@typescript-eslint/tsconfig-utils": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2415,16 +2449,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0" + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2439,13 +2473,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", - "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4775,6 +4809,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6585,31 +6620,33 @@ } }, "node_modules/eslint": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz", - "integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.11.1", - "@eslint/plugin-kit": "^0.2.0", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6619,14 +6656,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -6700,10 +6734,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6728,10 +6763,11 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -6773,14 +6809,15 @@ "dev": true }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6790,10 +6827,11 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -8450,6 +8488,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -10328,10 +10367,11 @@ "dev": true }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -10752,15 +10792,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -13697,6 +13728,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -14967,6 +14999,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -16761,12 +16794,6 @@ "b4a": "^1.6.4" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/textextensions": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.2.tgz", @@ -17258,16 +17285,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", - "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.39.0", - "@typescript-eslint/parser": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0" + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 145cebb932c..1121b610252 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", - "@typescript-eslint/utils": "^8.36.0", + "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.35", @@ -160,7 +160,7 @@ "debounce": "^1.0.0", "deemon": "^1.13.6", "electron": "37.6.0", - "eslint": "^9.11.1", + "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^50.3.1", @@ -214,7 +214,7 @@ "tsec": "0.2.7", "tslib": "^2.6.3", "typescript": "^6.0.0-dev.20250922", - "typescript-eslint": "^8.39.0", + "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", From 6c1069701075e903fa49e965c278a76c4586e83b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 20:49:28 +0000 Subject: [PATCH 0790/4355] Adopt IChatEntitlementService in chatWidget.ts to replace context key logic (#265731) * Replace context key logic with IChatEntitlementService in chatWidget.ts Co-authored-by: bhavyaus <25044782+bhavyaus@users.noreply.github.com> * refactor: streamline chat setup handling and reset logic in ChatWidget * fix: adjust conditional formatting in ChatWidget for better readability --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bhavyaus <25044782+bhavyaus@users.noreply.github.com> Co-authored-by: bhavyaus --- .../contrib/chat/browser/chatWidget.ts | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index b8c5965d875..1fec72e18ab 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -53,13 +53,13 @@ import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/w import { EditorResourceAccessor } from '../../../../workbench/common/editor.js'; import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js'; import { ViewContainerLocation } from '../../../common/views.js'; -import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { checkModeOption } from '../common/chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; -import { ChatContextKeyExprs, ChatContextKeys } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; import { IChatModel, IChatResponseModel } from '../common/chatModel.js'; import { IChatModeService } from '../common/chatModes.js'; @@ -431,6 +431,12 @@ export class ChatWidget extends Disposable implements IChatWidget { readonly viewContext: IChatWidgetViewContext; + private shouldShowChatSetup(): boolean { + // Check if chat is not installed OR user can sign up for free + // Equivalent to: ChatContextKeys.Setup.installed.negate() OR ChatContextKeys.Entitlement.canSignUp + return !this.chatEntitlementService.sentiment.installed || this.chatEntitlementService.entitlement === ChatEntitlement.Available; + } + get supportsChangingModes(): boolean { return !!this.viewOptions.supportsChangingModes; } @@ -657,23 +663,30 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.onDidChangeParsedInput(() => this.updateChatInputContext())); - this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(new Set([ - ChatContextKeys.Setup.installed.key, - ChatContextKeys.Entitlement.canSignUp.key - ]))) { - // reset the input in welcome view if it was rendered in experimental mode - if (this.container.classList.contains('new-welcome-view') && !this.contextKeyService.contextMatchesRules(ChatContextKeyExprs.chatSetupTriggerContext)) { - this.container.classList.remove('new-welcome-view'); - const renderFollowups = this.viewOptions.renderFollowups ?? false; - const renderStyle = this.viewOptions.renderStyle; - this.createInput(this.container, { renderFollowups, renderStyle }); - this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Ask); - } + // Listen to entitlement and sentiment changes instead of context keys + this._register(this.chatEntitlementService.onDidChangeEntitlement(() => { + if (!this.shouldShowChatSetup()) { + this.resetWelcomeViewInput(); + } + })); + this._register(this.chatEntitlementService.onDidChangeSentiment(() => { + if (!this.shouldShowChatSetup()) { + this.resetWelcomeViewInput(); } })); } + private resetWelcomeViewInput(): void { + // reset the input in welcome view if it was rendered in experimental mode + if (this.container.classList.contains('new-welcome-view')) { + this.container.classList.remove('new-welcome-view'); + const renderFollowups = this.viewOptions.renderFollowups ?? false; + const renderStyle = this.viewOptions.renderStyle; + this.createInput(this.container, { renderFollowups, renderStyle }); + this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Ask); + } + } + private _lastSelectedAgent: IChatAgentData | undefined; set lastSelectedAgent(agent: IChatAgentData | undefined) { this.parsedChatRequest = undefined; @@ -929,12 +942,8 @@ export class ChatWidget extends Disposable implements IChatWidget { // reset the input in welcome view if it was rendered in experimental mode - if (this.container.classList.contains('new-welcome-view') && this.viewModel?.getItems().length) { - this.container.classList.remove('new-welcome-view'); - const renderFollowups = this.viewOptions.renderFollowups ?? false; - const renderStyle = this.viewOptions.renderStyle; - this.createInput(this.container, { renderFollowups, renderStyle }); - this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Ask); + if (this.viewModel?.getItems().length) { + this.resetWelcomeViewInput(); this.focusInput(); } @@ -1021,7 +1030,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!additionalMessage) { additionalMessage = this._getGenerateInstructionsMessage(); } - if (this.contextKeyService.contextMatchesRules(ChatContextKeyExprs.chatSetupTriggerContext)) { + if (this.shouldShowChatSetup()) { welcomeContent = this.getNewWelcomeViewContent(); this.container.classList.add('new-welcome-view'); } else if (expEmptyState) { From d5cc5bef6a1fc5bfdd70bae83bbd4da526c8437d Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 3 Oct 2025 13:49:49 -0700 Subject: [PATCH 0791/4355] Do not recommend extensions if language is set via auto detection --- .../extensions/browser/fileBasedRecommendations.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 927e707f50f..ffc247f4125 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -28,6 +28,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import { isEmptyObject } from '../../../../base/common/types.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; +import { IUntitledTextEditorService } from '../../../services/untitled/common/untitledTextEditorService.js'; const promptedRecommendationsStorageKey = 'fileBasedRecommendations/promptedRecommendations'; const recommendationsStorageKey = 'extensionsAssistant/recommendations'; @@ -83,6 +84,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { @IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService, @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, ) { super(); this.fileOpenRecommendations = {}; @@ -153,7 +155,15 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const matchedRecommendations: IStringDictionary = {}; const unmatchedRecommendations: IStringDictionary = {}; let listenOnLanguageChange = false; - const languageId = model.getLanguageId(); + let languageId = model.getLanguageId(); + + // Avoid language-specific recommendations for untitled files when language is auto-detected. + if (uri.scheme === Schemas.untitled) { + const untitledModel = this.untitledTextEditorService.get(uri); + if (untitledModel && !untitledModel.hasLanguageSetExplicitly) { + languageId = 'plaintext'; + } + } for (const [extensionId, conditions] of extensionRecommendationEntries) { const conditionsByPattern: IFileOpenCondition[] = []; From aaf2fe7816c3a062247613b1b712b47faa539e20 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 3 Oct 2025 13:52:35 -0700 Subject: [PATCH 0792/4355] PR feedback --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index ffc247f4125..fc866de3ed8 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -161,7 +161,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { if (uri.scheme === Schemas.untitled) { const untitledModel = this.untitledTextEditorService.get(uri); if (untitledModel && !untitledModel.hasLanguageSetExplicitly) { - languageId = 'plaintext'; + languageId = PLAINTEXT_LANGUAGE_ID; } } From cb01224865877762b2309342a84e640681e78090 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:01:27 -0700 Subject: [PATCH 0793/4355] Fix all any casts in TS extension For #269213 --- .../src/commands/tsserverRequests.ts | 2 +- .../src/languageFeatures/completions.ts | 3 +- .../src/languageFeatures/signatureHelp.ts | 6 +- .../src/tsServer/bufferSyncSupport.ts | 5 +- .../src/tsServer/server.ts | 10 ++-- .../src/utils/platform.ts | 3 +- .../web/src/serverHost.ts | 56 +++++++++++-------- .../web/src/webServer.ts | 9 ++- .../web/src/workerSession.ts | 9 ++- 9 files changed, 58 insertions(+), 45 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts index 7c925024b96..556e8582300 100644 --- a/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -14,7 +14,7 @@ function isCancellationToken(value: any): value is vscode.CancellationToken { return value && typeof value.isCancellationRequested === 'boolean' && typeof value.onCancellationRequested === 'function'; } -interface RequestArgs { +export interface RequestArgs { readonly file?: unknown; readonly $traceId?: unknown; } diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index e265e3f6009..a06d5c171bc 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -777,8 +777,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< dotAccessorContext = { range, text }; } } - // eslint-disable-next-line local/code-no-any-casts - const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete; + const isIncomplete = !!response.body.isIncomplete || !!(response.metadata as Record)?.isIncomplete; const entries = response.body.entries; const metadata = response.metadata; const defaultCommitCharacters = Object.freeze(response.body.defaultCommitCharacters); diff --git a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts index 91f50cf8b87..7bb56c8cd91 100644 --- a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts +++ b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts @@ -105,11 +105,9 @@ function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.Signatur case vscode.SignatureHelpTriggerKind.TriggerCharacter: if (context.triggerCharacter) { if (context.isRetrigger) { - // eslint-disable-next-line local/code-no-any-casts - return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any }; + return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as Proto.SignatureHelpRetriggerCharacter }; } else { - // eslint-disable-next-line local/code-no-any-casts - return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any }; + return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as Proto.SignatureHelpTriggerCharacter }; } } else { return { kind: 'invoked' }; diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index b26380e555c..670412b3cb1 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -168,7 +168,7 @@ class SyncedBuffer { ) { } public open(): void { - const args: Proto.OpenRequestArgs = { + const args: Proto.OpenRequestArgs & { plugins?: string[] } = { file: this.filepath, fileContent: this.document.getText(), projectRootPath: this.getProjectRootPath(this.document.uri), @@ -183,8 +183,7 @@ class SyncedBuffer { .filter(x => x.languages.indexOf(this.document.languageId) >= 0); if (tsPluginsForDocument.length) { - // eslint-disable-next-line local/code-no-any-casts - (args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name); + args.plugins = tsPluginsForDocument.map(plugin => plugin.name); } this.synchronizer.open(this.resource, args); diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 7b478627e1d..25dae6d8772 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -5,6 +5,7 @@ import { Cancellation } from '@vscode/sync-api-common/lib/common/messageCancellation'; import * as vscode from 'vscode'; +import { RequestArgs } from '../commands/tsserverRequests'; import { TypeScriptServiceConfiguration } from '../configuration/configuration'; import { TelemetryReporter } from '../logging/telemetry'; import Tracer from '../logging/tracer'; @@ -15,11 +16,11 @@ import { ServerResponse, ServerType, TypeScriptRequests } from '../typescriptSer import { Disposable } from '../utils/dispose'; import { isWebAndHasSharedArrayBuffers } from '../utils/platform'; import { OngoingRequestCanceller } from './cancellation'; +import { NodeVersionManager } from './nodeManager'; import type * as Proto from './protocol/protocol'; import { EventName } from './protocol/protocol.const'; import { TypeScriptVersionManager } from './versionManager'; import { TypeScriptVersion } from './versionProvider'; -import { NodeVersionManager } from './nodeManager'; export enum ExecutionTarget { Semantic, @@ -283,8 +284,8 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { } this._requestQueue.enqueue(requestInfo); - // eslint-disable-next-line local/code-no-any-casts - if (args && typeof (args as any).$traceId === 'string') { + const traceId = (args as RequestArgs).$traceId; + if (args && typeof traceId === 'string') { const queueLength = this._requestQueue.length - 1; const pendingResponses = this._pendingResponses.size; const data: { command: string; queueLength: number; pendingResponses: number; queuedCommands?: string[]; pendingCommands?: string[] } = { @@ -299,8 +300,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { data.pendingCommands = this.getPendingCommands(); } - // eslint-disable-next-line local/code-no-any-casts - this._telemetryReporter.logTraceEvent('TSServer.enqueueRequest', (args as any).$traceId, JSON.stringify(data)); + this._telemetryReporter.logTraceEvent('TSServer.enqueueRequest', traceId, JSON.stringify(data)); } this.sendNextRequests(); diff --git a/extensions/typescript-language-features/src/utils/platform.ts b/extensions/typescript-language-features/src/utils/platform.ts index 56d70a9a4c9..3e8f08c9f04 100644 --- a/extensions/typescript-language-features/src/utils/platform.ts +++ b/extensions/typescript-language-features/src/utils/platform.ts @@ -10,8 +10,7 @@ export function isWeb(): boolean { } export function isWebAndHasSharedArrayBuffers(): boolean { - // eslint-disable-next-line local/code-no-any-casts - return isWeb() && (globalThis as any)['crossOriginIsolated']; + return isWeb() && globalThis['crossOriginIsolated']; } export function supportsReadableByteStreams(): boolean { diff --git a/extensions/typescript-language-features/web/src/serverHost.ts b/extensions/typescript-language-features/web/src/serverHost.ts index 93386ac9d00..f8a405837b9 100644 --- a/extensions/typescript-language-features/web/src/serverHost.ts +++ b/extensions/typescript-language-features/web/src/serverHost.ts @@ -13,6 +13,31 @@ import { PathMapper, looksLikeNodeModules, mapUri } from './pathMapper'; import { findArgument, hasArgument } from './util/args'; import { URI } from 'vscode-uri'; +type TsModule = typeof ts; + +interface TsInternals extends TsModule { + combinePaths(path: string, ...paths: (string | undefined)[]): string; + + matchFiles( + path: string, + extensions: readonly string[] | undefined, + excludes: readonly string[] | undefined, + includes: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string, + depth: number | undefined, + getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] }, + realpath: (path: string) => string + ): string[]; + + generateDjb2Hash(data: string): string; + + memoize: (callback: () => T) => () => T; + ensureTrailingDirectorySeparator: (path: string) => string; + getDirectoryPath: (path: string) => string; + directorySeparator: string; +} + type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; function createServerHost( @@ -29,33 +54,16 @@ function createServerHost( const fs = apiClient?.vscode.workspace.fileSystem; // Internals - // eslint-disable-next-line local/code-no-any-casts - const combinePaths: (path: string, ...paths: (string | undefined)[]) => string = (ts as any).combinePaths; + const combinePaths = (ts as TsInternals).combinePaths; const byteOrderMarkIndicator = '\uFEFF'; - const matchFiles: ( - path: string, - extensions: readonly string[] | undefined, - excludes: readonly string[] | undefined, - includes: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string, - depth: number | undefined, - getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] }, - realpath: (path: string) => string - // eslint-disable-next-line local/code-no-any-casts - ) => string[] = (ts as any).matchFiles; - // eslint-disable-next-line local/code-no-any-casts - const generateDjb2Hash = (ts as any).generateDjb2Hash; + const matchFiles = (ts as TsInternals).matchFiles; + const generateDjb2Hash = (ts as TsInternals).generateDjb2Hash; // Legacy web - // eslint-disable-next-line local/code-no-any-casts - const memoize: (callback: () => T) => () => T = (ts as any).memoize; - // eslint-disable-next-line local/code-no-any-casts - const ensureTrailingDirectorySeparator: (path: string) => string = (ts as any).ensureTrailingDirectorySeparator; - // eslint-disable-next-line local/code-no-any-casts - const getDirectoryPath: (path: string) => string = (ts as any).getDirectoryPath; - // eslint-disable-next-line local/code-no-any-casts - const directorySeparator: string = (ts as any).directorySeparator; + const memoize = (ts as TsInternals).memoize; + const ensureTrailingDirectorySeparator = (ts as TsInternals).ensureTrailingDirectorySeparator; + const getDirectoryPath = (ts as TsInternals).getDirectoryPath; + const directorySeparator = (ts as TsInternals).directorySeparator; const executingFilePath = findArgument(args, '--executingFilePath') || location + ''; const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath)))); const getWebPath = (path: string) => path.startsWith(directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; diff --git a/extensions/typescript-language-features/web/src/webServer.ts b/extensions/typescript-language-features/web/src/webServer.ts index d425fc3d9f3..5a964231519 100644 --- a/extensions/typescript-language-features/web/src/webServer.ts +++ b/extensions/typescript-language-features/web/src/webServer.ts @@ -13,8 +13,13 @@ import { createSys } from './serverHost'; import { findArgument, findArgumentStringArray, hasArgument, parseServerMode } from './util/args'; import { StartSessionOptions, startWorkerSession } from './workerSession'; -// eslint-disable-next-line local/code-no-any-casts -const setSys: (s: ts.System) => void = (ts as any).setSys; +type TsModule = typeof ts; + +interface TsInternals extends TsModule { + setSys(sys: ts.System): void; +} + +const setSys: (s: ts.System) => void = (ts as TsInternals).setSys; async function initializeSession( args: readonly string[], diff --git a/extensions/typescript-language-features/web/src/workerSession.ts b/extensions/typescript-language-features/web/src/workerSession.ts index 24311783167..c12bfd465ea 100644 --- a/extensions/typescript-language-features/web/src/workerSession.ts +++ b/extensions/typescript-language-features/web/src/workerSession.ts @@ -22,6 +22,12 @@ export interface StartSessionOptions { readonly disableAutomaticTypingAcquisition: boolean; } +type ServerModule = typeof ts.server; + +interface TsServerInternals extends ServerModule { + indent(str: string): string; +} + export function startWorkerSession( ts: typeof import('typescript/lib/tsserverlibrary'), host: ts.server.ServerHost, @@ -31,8 +37,7 @@ export function startWorkerSession( pathMapper: PathMapper, logger: Logger, ): void { - // eslint-disable-next-line local/code-no-any-casts - const indent: (str: string) => string = (ts as any).server.indent; + const indent = (ts.server as TsServerInternals).indent; const worker = new class WorkerSession extends ts.server.Session<{}> { From 21337a4ad012261bb2274bb55b0aa084a0189c04 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:45:09 -0700 Subject: [PATCH 0794/4355] Add assertion --- extensions/typescript-language-features/src/utils/platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/utils/platform.ts b/extensions/typescript-language-features/src/utils/platform.ts index 3e8f08c9f04..bb4e7cdbfd9 100644 --- a/extensions/typescript-language-features/src/utils/platform.ts +++ b/extensions/typescript-language-features/src/utils/platform.ts @@ -10,7 +10,7 @@ export function isWeb(): boolean { } export function isWebAndHasSharedArrayBuffers(): boolean { - return isWeb() && globalThis['crossOriginIsolated']; + return isWeb() && !!(globalThis as Record)['crossOriginIsolated']; } export function supportsReadableByteStreams(): boolean { From b837bd651a20b05d6cc76cdbfb525187d7511c16 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 3 Oct 2025 16:07:13 -0700 Subject: [PATCH 0795/4355] [Chat Sessions] Show custom chat editor titles for contributed chat sessions (#269751) --- .../contrib/chat/browser/chatEditor.ts | 9 +++-- .../contrib/chat/browser/chatEditorInput.ts | 34 ++++++++++++++----- .../chat/browser/chatSessions.contribution.ts | 3 ++ .../chatSessions/view/sessionsViewPane.ts | 4 ++- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index e424e1ae8cf..5935dd397d0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -31,7 +31,10 @@ import { ChatWidget, IChatViewState } from './chatWidget.js'; export interface IChatEditorOptions extends IEditorOptions { target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData }; - preferredTitle?: string; + title?: { + preferred?: string; + fallback?: string; + }; ignoreInView?: boolean; } @@ -159,8 +162,8 @@ export class ChatEditor extends EditorPane { const viewState = options?.viewState ?? input.options.viewState; this.updateModel(editorModel.model, viewState); - if (isContributedChatSession && options?.preferredTitle) { - editorModel.model.setCustomTitle(options?.preferredTitle); + if (isContributedChatSession && options?.title?.preferred) { + editorModel.model.setCustomTitle(options.title.preferred); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index e3a65686976..c8c8dbc5390 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -28,12 +28,15 @@ import type { IChatEditorOptions } from './chatEditor.js'; const ChatEditorIcon = registerIcon('chat-editor-label-icon', Codicon.chatSparkle, nls.localize('chatEditorLabelIcon', 'Icon of the chat editor label.')); export class ChatEditorInput extends EditorInput implements IEditorCloseHandler { - static readonly countsInUse = new Set(); + /** Maps input name strings to sets of active editor counts */ + static readonly countsInUseMap = new Map>(); static readonly TypeID: string = 'workbench.input.chatSession'; static readonly EditorID: string = 'workbench.editor.chatSession'; private readonly inputCount: number; + private readonly inputName: string; + public sessionId: string | undefined; private hasCustomTitle: boolean = false; @@ -44,9 +47,9 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return ChatEditorUri.generate(handle); } - static getNextCount(): number { + static getNextCount(inputName: string): number { let count = 0; - while (ChatEditorInput.countsInUse.has(count)) { + while (ChatEditorInput.countsInUseMap.get(inputName)?.has(count)) { count++; } @@ -82,14 +85,23 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler this.hasCustomTitle = Boolean(hasExistingCustomTitle); + // Input counts are unique to the displayed fallback title + this.inputName = options.title?.fallback ?? ''; + if (!ChatEditorInput.countsInUseMap.has(this.inputName)) { + ChatEditorInput.countsInUseMap.set(this.inputName, new Set()); + } + // Only allocate a count if we don't already have a custom title if (!this.hasCustomTitle) { - this.inputCount = ChatEditorInput.getNextCount(); - ChatEditorInput.countsInUse.add(this.inputCount); + this.inputCount = ChatEditorInput.getNextCount(this.inputName); + ChatEditorInput.countsInUseMap.get(this.inputName)?.add(this.inputCount); this._register(toDisposable(() => { // Only remove if we haven't already removed it due to custom title if (!this.hasCustomTitle) { - ChatEditorInput.countsInUse.delete(this.inputCount); + ChatEditorInput.countsInUseMap.get(this.inputName)?.delete(this.inputCount); + if (ChatEditorInput.countsInUseMap.get(this.inputName)?.size === 0) { + ChatEditorInput.countsInUseMap.delete(this.inputName); + } } })); } else { @@ -165,8 +177,9 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } // Fall back to default naming pattern - const defaultName = nls.localize('chatEditorName', "Chat") + (this.inputCount > 0 ? ` ${this.inputCount + 1}` : ''); - return defaultName; + const inputCountSuffix = (this.inputCount > 0 ? ` ${this.inputCount + 1}` : ''); + const defaultName = this.options.title?.fallback ?? nls.localize('chatEditorName', "Chat"); + return defaultName + inputCountSuffix; } override getIcon(): ThemeIcon { @@ -197,7 +210,10 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler // When a custom title is set, we no longer need the numeric count if (e && e.kind === 'setCustomTitle' && !this.hasCustomTitle) { this.hasCustomTitle = true; - ChatEditorInput.countsInUse.delete(this.inputCount); + ChatEditorInput.countsInUseMap.get(this.inputName)?.delete(this.inputCount); + if (ChatEditorInput.countsInUseMap.get(this.inputName)?.size === 0) { + ChatEditorInput.countsInUseMap.delete(this.inputName); + } } this._onDidChangeLabel.fire(); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index bf42f545729..6d6403154ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -297,6 +297,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const options: IChatEditorOptions = { override: ChatEditorInput.EditorID, pinned: true, + title: { + fallback: localize('chatEditorContributionName', "{0} chat", contribution.displayName), + } }; const untitledId = `untitled-${generateUuid()}`; await editorService.openEditor({ diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index ab1bbeb7f50..a2de824f5cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -493,7 +493,9 @@ export class SessionsViewPane extends ViewPane { const options: IChatEditorOptions = { pinned: true, ignoreInView: true, - preferredTitle: truncate(session.label, ChatEditorTitleMaxLength), + title: { + preferred: truncate(session.label, ChatEditorTitleMaxLength), + }, preserveFocus: true, }; await this.editorService.openEditor({ From e0f66c9824353a573ea2aecd5dcd3c77c062b6d3 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 3 Oct 2025 16:11:34 -0700 Subject: [PATCH 0796/4355] edits: only fire user-edit event once per dirty document (#269793) Closes https://github.com/microsoft/vscode/issues/268961 --- .../chatEditingTextModelChangeService.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts index 186d7f0748f..e7be8f9c69f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts @@ -109,6 +109,7 @@ export class ChatEditingTextModelChangeService extends Disposable { } } + private _didUserEditModelFired = false; private readonly _didUserEditModel = this._register(new Emitter()); public readonly onDidUserEditModel = this._didUserEditModel.event; @@ -280,8 +281,7 @@ export class ChatEditingTextModelChangeService extends Disposable { public keep() { this.notifyHunkAction('accepted', { linesAdded: this.linesAdded, linesRemoved: this.linesRemoved, lineCount: this.lineChangeCount, hasRemainingEdits: false }); this.originalModel.setValue(this.modifiedModel.createSnapshot()); - this._diffInfo.set(nullDocumentDiff, undefined); - this._originalToModifiedEdit = StringEdit.empty; + this._reset(); } /** @@ -292,8 +292,13 @@ export class ChatEditingTextModelChangeService extends Disposable { this.modifiedModel.pushStackElement(); this._applyEdits([(EditOperation.replace(this.modifiedModel.getFullModelRange(), this.originalModel.getValue()))], EditSources.chatUndoEdits()); this.modifiedModel.pushStackElement(); + this._reset(); + } + + private _reset() { this._originalToModifiedEdit = StringEdit.empty; this._diffInfo.set(nullDocumentDiff, undefined); + this._didUserEditModelFired = false; } public async resetDocumentValues(newOriginal: string | ITextSnapshot | undefined, newModified: string | undefined): Promise { @@ -354,7 +359,10 @@ export class ChatEditingTextModelChangeService extends Disposable { this._allEditsAreFromUs = false; this._updateDiffInfoSeq(); - this._didUserEditModel.fire(); + if (!this._didUserEditModelFired) { + this._didUserEditModelFired = true; + this._didUserEditModel.fire(); + } } } From 189dff69efec9cacd6c249b20faba8fd224fa224 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 23:47:28 +0000 Subject: [PATCH 0797/4355] Chat: Add configuration setting to control "Delegate to Coding Agent" action location (#268512) * Initial plan * Implement conditional menu visibility for Delegate to Coding Agent action Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * Improve error handling and code quality in ChatExecuteMenuContribution Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * Simplify implementation to use configuration setting instead of complex monitoring Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * run formatter --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> --- .../chat/browser/actions/chatExecuteActions.ts | 12 ++++++++++-- .../contrib/chat/browser/chat.contribution.ts | 5 +++++ src/vs/workbench/contrib/chat/common/constants.ts | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 601c66bb06b..5738fbd62bb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -607,13 +607,21 @@ export class CreateRemoteAgentJobAction extends Action2 { id: MenuId.ChatExecute, group: 'navigation', order: 3.4, - when: ContextKeyExpr.and(ChatContextKeys.hasRemoteCodingAgent, ChatContextKeys.lockedToCodingAgent.negate()), + when: ContextKeyExpr.and( + ChatContextKeys.hasRemoteCodingAgent, + ChatContextKeys.lockedToCodingAgent.negate(), + ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, false) + ), }, { id: MenuId.ChatExecuteSecondary, group: 'group_3', order: 1, - when: ContextKeyExpr.and(ChatContextKeys.hasRemoteCodingAgent, ChatContextKeys.lockedToCodingAgent.negate()), + when: ContextKeyExpr.and( + ChatContextKeys.hasRemoteCodingAgent, + ChatContextKeys.lockedToCodingAgent.negate(), + ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, true) + ), } ] }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index dbd2125d003..236262563ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -704,6 +704,11 @@ configurationRegistry.registerConfiguration({ tags: ['experimental'], }, + [ChatConfiguration.DelegateToCodingAgentInSecondaryMenu]: { + type: 'boolean', + description: nls.localize('chat.delegateToCodingAgentInSecondaryMenu', "Controls whether the 'Delegate to Coding Agent' action appears in the secondary send menu instead of the primary toolbar."), + default: false, + }, [ChatConfiguration.ShowAgentSessionsViewDescription]: { type: 'boolean', description: nls.localize('chat.showAgentSessionsViewDescription', "Controls whether session descriptions are displayed on a second row in the Chat Sessions view."), diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 3501fbc5103..5768517faf6 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -21,6 +21,7 @@ export enum ChatConfiguration { ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', EmptyStateHistoryEnabled = 'chat.emptyState.history.enabled', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', + DelegateToCodingAgentInSecondaryMenu = 'chat.delegateToCodingAgentInSecondaryMenu', } /** From 9defb53a3bf4267858fb2a57a47cdd8c244c261f Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 4 Oct 2025 14:18:50 +0900 Subject: [PATCH 0798/4355] fix: set dns result order to ipv4first for cli connections (#269782) --- src/vs/code/node/cliProcessMain.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 1ad692cfcc2..7f08f43ea04 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { setDefaultResultOrder } from 'dns'; import * as fs from 'fs'; import { hostname, release } from 'os'; import { raceTimeout } from '../../base/common/async.js'; @@ -109,6 +110,10 @@ class CliMain extends Disposable { // Error handler this.registerErrorHandler(logService); + // DNS result order + // Refs https://github.com/microsoft/vscode/issues/264136 + setDefaultResultOrder('ipv4first'); + // Run based on argv await this.doRun(environmentService, fileService, userDataProfilesService, instantiationService); From b2e4b3e6cd8a58cf3a9444a81052b96ba50bad92 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 14:43:13 +0200 Subject: [PATCH 0799/4355] Switch to using snapshots for asserting HTML output --- ..._handle_unsorted_inline_decorations.0.html | 1 + ...ts_with_before_decorator_attachment.0.html | 1 + ..._control_characters_aren_t_rendered.0.html | 1 + ...t_editor_decorations_in_empty_lines.0.html | 1 + ...U_007F_____127___displayed_as_space.0.html | 1 + ...of-line_text_decorations_get_merged.0.html | 1 + ..._when_renderControlCharacters_is_on.0.html | 1 + ..._text_length_are_no_longer_rendered.0.html | 1 + ...n_Monokai_is_not_rendered_correctly.0.html | 1 + ...52__COMBINING_ACUTE_ACCENT__U_0301_.0.html | 1 + ...n_Complex_Script_Rendering_of_Tamil.0.html | 1 + ...idth_characters_when_rendering_tabs.0.html | 1 + ..._rendering_tabs__render_whitespace_.0.html | 1 + ...nes_don_t_render_inline_decorations.0.html | 1 + ...ter_decorator_causes_line_to__jump_.0.html | 1 + ...es_a_long_time_to_paint_decorations.0.html | 1 + ...to_paint_decorations_-_not_possible.0.html | 1 + ..._document_results_in______character.0.html | 1 + ...and_after_decorations_on_empty_line.0.html | 1 + ...ine_wrap_point_when_line_is_wrapped.0.html | 1 + ...d-of-line_blame_no_longer_rendering.0.html | 1 + ...ers_are_not_being_rendered_properly.0.html | 1 + ...ng_fails_on_line_with_selected_text.0.html | 1 + ...in_RTL_languages_in_recent_versions.0.html | 1 + ...Theme_bad-highlighting_in_line_wrap.0.html | 1 + ...orrupted_at_multiples_of_50_columns.0.html | 1 + ...__2255__Weird_line_rendering_part_1.0.html | 1 + ...__2255__Weird_line_rendering_part_2.0.html | 1 + ...decoration_type_shown_before_cursor.0.html | 1 + ...t_character_for_Paragraph_Separator.0.html | 1 + ...g_whitespace_influences_bidi_layout.0.html | 1 + ...ce_code_rendering_for_RTL_languages.0.html | 1 + ...iewLineRenderer_renderLine_overflow.0.html | 1 + ...ineRenderer_renderLine_typical_line.0.html | 1 + .../viewLayout/viewLineRenderer.test.ts | 1165 +++++++---------- 35 files changed, 526 insertions(+), 673 deletions(-) create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__20624__Unaligned_surrogate_pairs_are_corrupted_at_multiples_of_50_columns.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.0.html diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.0.html new file mode 100644 index 00000000000..a2eb1029f6a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.0.html @@ -0,0 +1 @@ +Hellworld \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.0.html new file mode 100644 index 00000000000..26f93edc656 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.0.html @@ -0,0 +1 @@ +→   bla \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.0.html new file mode 100644 index 00000000000..4b93fce762d --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.0.html @@ -0,0 +1 @@ +transferBalance(5678,[U+202E]6776,4321[U+202C],"USD"); \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.0.html new file mode 100644 index 00000000000..a133568f032 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.0.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.0.html new file mode 100644 index 00000000000..fac2d65628a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.0.html @@ -0,0 +1 @@ +[␡] [␀] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.0.html new file mode 100644 index 00000000000..8f86a3818d7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.0.html @@ -0,0 +1 @@ +·‌·‌·‌·‌if \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.0.html new file mode 100644 index 00000000000..18d52371c1e --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.0.html @@ -0,0 +1 @@ +some text £ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.0.html new file mode 100644 index 00000000000..6c34c6f40ae --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.0.html @@ -0,0 +1 @@ +https://microsoft.com \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.0.html new file mode 100644 index 00000000000..9088f59dee5 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.0.html @@ -0,0 +1 @@ +'let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;' \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.0.html new file mode 100644 index 00000000000..baaca4225e9 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.0.html @@ -0,0 +1 @@ +12345689012345678901234568901234567890123456890abába \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.0.html new file mode 100644 index 00000000000..7a1207af293 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.0.html @@ -0,0 +1 @@ + JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர் \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.0.html new file mode 100644 index 00000000000..8c33609129f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.0.html @@ -0,0 +1 @@ +asd = "擦"      #asd \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.0.html new file mode 100644 index 00000000000..6f8a54f54b8 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.0.html @@ -0,0 +1 @@ +asd·‌=·‌"擦"→ →   #asd \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.0.html new file mode 100644 index 00000000000..de368490cc0 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.0.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.0.html new file mode 100644 index 00000000000..01ceb983d90 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.0.html @@ -0,0 +1 @@ +→   bla \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.0.html new file mode 100644 index 00000000000..81399f4d5d7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.0.html @@ -0,0 +1 @@ +append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.0.html new file mode 100644 index 00000000000..e789538e11c --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.0.html @@ -0,0 +1 @@ +appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.0.html new file mode 100644 index 00000000000..674b0dac5e7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.0.html @@ -0,0 +1 @@ +  1. 🙏 \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.0.html new file mode 100644 index 00000000000..d716e46d246 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.0.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.0.html new file mode 100644 index 00000000000..ca09378f89a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.0.html @@ -0,0 +1 @@ +This is a long line which never uses more than two spaces.  \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.0.html new file mode 100644 index 00000000000..aa26830c0f4 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.0.html @@ -0,0 +1 @@ +    } \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.0.html new file mode 100644 index 00000000000..0c87b5f0ccf --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.0.html @@ -0,0 +1 @@ + वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है। \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.0.html new file mode 100644 index 00000000000..5a3861bebf4 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.0.html @@ -0,0 +1 @@ +·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌·‌else·‌if·‌($s·‌=·‌08)·‌then·‌'\b' \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.0.html new file mode 100644 index 00000000000..a62351f3c23 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.0.html @@ -0,0 +1 @@ +<option value="العربية">العربية</option> \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.0.html new file mode 100644 index 00000000000..3820e5bf804 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.0.html @@ -0,0 +1 @@ +    MongoCallback<string>): void { \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__20624__Unaligned_surrogate_pairs_are_corrupted_at_multiples_of_50_columns.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__20624__Unaligned_surrogate_pairs_are_corrupted_at_multiples_of_50_columns.0.html new file mode 100644 index 00000000000..81c2278bce7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__20624__Unaligned_surrogate_pairs_are_corrupted_at_multiples_of_50_columns.0.html @@ -0,0 +1 @@ +a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷 \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.0.html new file mode 100644 index 00000000000..bde7899d166 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.0.html @@ -0,0 +1 @@ +            cursorStyle:                        (prevOpts.cursorStyle !== newOpts.cursorStyle), \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.0.html new file mode 100644 index 00000000000..bde7899d166 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.0.html @@ -0,0 +1 @@ +            cursorStyle:                        (prevOpts.cursorStyle !== newOpts.cursorStyle), \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.0.html new file mode 100644 index 00000000000..f6556300bc1 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.0.html @@ -0,0 +1 @@ +//just a comment \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.0.html new file mode 100644 index 00000000000..6efa172b29a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.0.html @@ -0,0 +1 @@ +var ftext = [�"Und", "dann", "eines"]; \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html new file mode 100644 index 00000000000..db880d5d98c --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html @@ -0,0 +1 @@ +·‌·‌·‌·‌["🖨️ چاپ فاکتور","🎨 تنظیمات"] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html new file mode 100644 index 00000000000..e8031a89415 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html @@ -0,0 +1 @@ +var קודמות = "מיותר קודמות צ'ט של, אם לשון העברית שינויים ויש, אם"; \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.0.html new file mode 100644 index 00000000000..4d1b1f3c896 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.0.html @@ -0,0 +1 @@ +Hello Show more (6 chars) \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.0.html new file mode 100644 index 00000000000..3ff35995a6f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.0.html @@ -0,0 +1 @@ +→   ·‌·‌·‌·‌export class Game { // http://test.com·‌·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 66f53659d13..1c8d44c37d7 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -6,14 +6,15 @@ import assert from 'assert'; import { CharCode } from '../../../../base/common/charCode.js'; import * as strings from '../../../../base/common/strings.js'; +import { assertSnapshot } from '../../../../base/test/common/snapshot.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { OffsetRange } from '../../../common/core/ranges/offsetRange.js'; import { MetadataConsts } from '../../../common/encodedTokenAttributes.js'; import { IViewLineTokens } from '../../../common/tokens/lineTokens.js'; import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js'; import { CharacterMapping, DomPosition, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js'; -import { TestLineToken, TestLineTokens } from '../core/testLineToken.js'; -import { OffsetRange } from '../../../common/core/ranges/offsetRange.js'; import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js'; +import { TestLineToken, TestLineTokens } from '../core/testLineToken.js'; function createViewLineTokens(viewLineTokens: TestLineToken[]): IViewLineTokens { return new TestLineTokens(viewLineTokens); @@ -155,7 +156,7 @@ suite('viewLineRenderer.renderLine', () => { assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], 'xyz', [[0, [0, 0]], [1, [0, 1]], [2, [1, 0]], [3, [1, 1]]]); }); - test('overflow', () => { + test('overflow', async () => { const _actual = renderViewLine(new RenderLineInput( false, true, @@ -193,29 +194,22 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), { - html: [ - 'H', - 'e', - 'l', - 'l', - 'o', - '\u00a0', - 'Show more (6 chars)' - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], - [4, 0, 4], - [5, 0, 5], - [5, 1, 6], - ] + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 1], + [2, 0, 2], + [3, 0, 3], + [4, 0, 4], + [5, 0, 5], + [5, 1, 6], + ]); }); - test('typical line', () => { + test('typical line', async () => { const lineText = '\t export class Game { // http://test.com '; const lineParts = createViewLineTokens([ createPart(5, 1), @@ -255,43 +249,29 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), { - html: [ - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - 'export', - '\u00a0', - 'class', - '\u00a0', - 'Game', - '\u00a0', - '{', - '\u00a0', - '//\u00a0', - 'http://test.com', - '\u00b7\u200c\u00b7\u200c', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c' - ], - mapping: [ - [0, 0, 0], - [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], - [2, 0, 8], [2, 1, 9], [2, 2, 10], [2, 3, 11], [2, 4, 12], [2, 5, 13], - [3, 0, 14], - [4, 0, 15], [4, 1, 16], [4, 2, 17], [4, 3, 18], [4, 4, 19], - [5, 0, 20], - [6, 0, 21], [6, 1, 22], [6, 2, 23], [6, 3, 24], - [7, 0, 25], - [8, 0, 26], - [9, 0, 27], - [10, 0, 28], [10, 1, 29], [10, 2, 30], - [11, 0, 31], [11, 1, 32], [11, 2, 33], [11, 3, 34], [11, 4, 35], [11, 5, 36], [11, 6, 37], [11, 7, 38], [11, 8, 39], [11, 9, 40], [11, 10, 41], [11, 11, 42], [11, 12, 43], [11, 13, 44], [11, 14, 45], - [12, 0, 46], [12, 2, 47], - [13, 0, 48], [13, 2, 49], [13, 4, 50], [13, 6, 51], - ] + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], + [2, 0, 8], [2, 1, 9], [2, 2, 10], [2, 3, 11], [2, 4, 12], [2, 5, 13], + [3, 0, 14], + [4, 0, 15], [4, 1, 16], [4, 2, 17], [4, 3, 18], [4, 4, 19], + [5, 0, 20], + [6, 0, 21], [6, 1, 22], [6, 2, 23], [6, 3, 24], + [7, 0, 25], + [8, 0, 26], + [9, 0, 27], + [10, 0, 28], [10, 1, 29], [10, 2, 30], + [11, 0, 31], [11, 1, 32], [11, 2, 33], [11, 3, 34], [11, 4, 35], [11, 5, 36], [11, 6, 37], [11, 7, 38], [11, 8, 39], [11, 9, 40], [11, 10, 41], [11, 11, 42], [11, 12, 43], [11, 13, 44], [11, 14, 45], + [12, 0, 46], [12, 2, 47], + [13, 0, 48], [13, 2, 49], [13, 4, 50], [13, 6, 51], + ]); }); - test('issue #2255: Weird line rendering part 1', () => { + test('issue #2255: Weird line rendering part 1', async () => { const lineText = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; const lineParts = createViewLineTokens([ createPart(3, 1), // 3 chars @@ -329,35 +309,25 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), { - html: [ - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - 'cursorStyle:', - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '(', - 'prevOpts.cursorStyle\u00a0', - '!=', - '=', - '\u00a0newOpts.cursorStyle', - ')', - ',', - ], - mapping: [ - [0, 0, 0], [0, 4, 4], [0, 8, 8], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], - [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], - [3, 0, 48], - [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], - [5, 0, 70], [5, 1, 71], - [6, 0, 72], - [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], - [8, 0, 93], - [9, 0, 94], [9, 1, 95], - ] + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 4, 4], [0, 8, 8], + [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], + [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], + [3, 0, 48], + [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], + [5, 0, 70], [5, 1, 71], + [6, 0, 72], + [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], + [8, 0, 93], + [9, 0, 94], [9, 1, 95], + ]); }); - test('issue #2255: Weird line rendering part 2', () => { + test('issue #2255: Weird line rendering part 2', async () => { const lineText = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; const lineParts = createViewLineTokens([ @@ -396,34 +366,24 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), { - html: [ - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - 'cursorStyle:', - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '(', - 'prevOpts.cursorStyle\u00a0', - '!=', - '=', - '\u00a0newOpts.cursorStyle', - ')', - ',', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 4, 4], [0, 8, 8], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], - [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], - [3, 0, 48], [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], - [5, 0, 70], [5, 1, 71], - [6, 0, 72], - [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], - [8, 0, 93], - [9, 0, 94], [9, 1, 95], - ], + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 4, 4], [0, 8, 8], + [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], + [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], + [3, 0, 48], [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], + [5, 0, 70], [5, 1, 71], + [6, 0, 72], + [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], + [8, 0, 93], + [9, 0, 94], [9, 1, 95], + ]); }); - test('issue #91178: after decoration type shown before cursor', () => { + test('issue #91178: after decoration type shown before cursor', async () => { const lineText = '//just a comment'; const lineParts = createViewLineTokens([ createPart(16, 1) @@ -455,22 +415,18 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), ({ - html: [ - '//just\u00a0a\u00a0com', - '', - '', - 'ment', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], - [2, 0, 12], - [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16] - ] - })); + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], + [2, 0, 12], + [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16] + ]); }); - test('issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => { + test('issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages', async () => { const lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; const lineParts = createViewLineTokens([ createPart(3, 6), @@ -502,23 +458,19 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), ({ - html: [ - 'var', - '\u00a0קודמות\u00a0=\u00a0', - '"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"', - ';', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], - [1, 0, 3], [1, 1, 4], [1, 2, 5], [1, 3, 6], [1, 4, 7], [1, 5, 8], [1, 6, 9], [1, 7, 10], [1, 8, 11], [1, 9, 12], - [2, 0, 13], [2, 1, 14], [2, 2, 15], [2, 3, 16], [2, 4, 17], [2, 5, 18], [2, 6, 19], [2, 7, 20], [2, 8, 21], [2, 9, 22], [2, 10, 23], [2, 11, 24], [2, 12, 25], [2, 13, 26], [2, 14, 27], [2, 15, 28], [2, 16, 29], [2, 17, 30], [2, 18, 31], [2, 19, 32], [2, 20, 33], [2, 21, 34], [2, 22, 35], [2, 23, 36], [2, 24, 37], [2, 25, 38], [2, 26, 39], [2, 27, 40], [2, 28, 41], [2, 29, 42], [2, 30, 43], [2, 31, 44], [2, 32, 45], [2, 33, 46], [2, 34, 47], [2, 35, 48], [2, 36, 49], [2, 37, 50], [2, 38, 51], [2, 39, 52], [2, 40, 53], [2, 41, 54], [2, 42, 55], [2, 43, 56], [2, 44, 57], [2, 45, 58], [2, 46, 59], [2, 47, 60], [2, 48, 61], [2, 49, 62], [2, 50, 63], [2, 51, 64], [2, 52, 65], - [3, 0, 66], [3, 1, 67] - ] - })); + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], + [1, 0, 3], [1, 1, 4], [1, 2, 5], [1, 3, 6], [1, 4, 7], [1, 5, 8], [1, 6, 9], [1, 7, 10], [1, 8, 11], [1, 9, 12], + [2, 0, 13], [2, 1, 14], [2, 2, 15], [2, 3, 16], [2, 4, 17], [2, 5, 18], [2, 6, 19], [2, 7, 20], [2, 8, 21], [2, 9, 22], [2, 10, 23], [2, 11, 24], [2, 12, 25], [2, 13, 26], [2, 14, 27], [2, 15, 28], [2, 16, 29], [2, 17, 30], [2, 18, 31], [2, 19, 32], [2, 20, 33], [2, 21, 34], [2, 22, 35], [2, 23, 36], [2, 24, 37], [2, 25, 38], [2, 26, 39], [2, 27, 40], [2, 28, 41], [2, 29, 42], [2, 30, 43], [2, 31, 44], [2, 32, 45], [2, 33, 46], [2, 34, 47], [2, 35, 48], [2, 36, 49], [2, 37, 50], [2, 38, 51], [2, 39, 52], [2, 40, 53], [2, 41, 54], [2, 42, 55], [2, 43, 56], [2, 44, 57], [2, 45, 58], [2, 46, 59], [2, 47, 60], [2, 48, 61], [2, 49, 62], [2, 50, 63], [2, 51, 64], [2, 52, 65], + [3, 0, 66], [3, 1, 67] + ]); }); - test('issue #137036: Issue in RTL languages in recent versions', () => { + test('issue #137036: Issue in RTL languages in recent versions', async () => { const lineText = ''; const lineParts = createViewLineTokens([ createPart(1, 2), @@ -557,37 +509,26 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), ({ - html: [ - '<', - 'option', - '\u00a0', - 'value', - '=', - '"العربية"', - '>', - 'العربية', - '</', - 'option', - '>', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], [1, 3, 4], [1, 4, 5], [1, 5, 6], - [2, 0, 7], - [3, 0, 8], [3, 1, 9], [3, 2, 10], [3, 3, 11], [3, 4, 12], - [4, 0, 13], - [5, 0, 14], [5, 1, 15], [5, 2, 16], [5, 3, 17], [5, 4, 18], [5, 5, 19], [5, 6, 20], [5, 7, 21], [5, 8, 22], - [6, 0, 23], - [7, 0, 24], [7, 1, 25], [7, 2, 26], [7, 3, 27], [7, 4, 28], [7, 5, 29], [7, 6, 30], - [8, 0, 31], [8, 1, 32], - [9, 0, 33], [9, 1, 34], [9, 2, 35], [9, 3, 36], [9, 4, 37], [9, 5, 38], - [10, 0, 39], [10, 1, 40] - ] - })); - }); - - test('issue #99589: Rendering whitespace influences bidi layout', () => { + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], [1, 3, 4], [1, 4, 5], [1, 5, 6], + [2, 0, 7], + [3, 0, 8], [3, 1, 9], [3, 2, 10], [3, 3, 11], [3, 4, 12], + [4, 0, 13], + [5, 0, 14], [5, 1, 15], [5, 2, 16], [5, 3, 17], [5, 4, 18], [5, 5, 19], [5, 6, 20], [5, 7, 21], [5, 8, 22], + [6, 0, 23], + [7, 0, 24], [7, 1, 25], [7, 2, 26], [7, 3, 27], [7, 4, 28], [7, 5, 29], [7, 6, 30], + [8, 0, 31], [8, 1, 32], + [9, 0, 33], [9, 1, 34], [9, 2, 35], [9, 3, 36], [9, 4, 37], [9, 5, 38], + [10, 0, 39], [10, 1, 40] + ]); + }); + + test('issue #99589: Rendering whitespace influences bidi layout', async () => { const lineText = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]'; const lineParts = createViewLineTokens([ createPart(5, 2), @@ -620,27 +561,21 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), ({ - html: [ - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - '[', - '"🖨️\u00a0چاپ\u00a0فاکتور"', - ',', - '"🎨\u00a0تنظیمات"', - ']', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], - [2, 0, 5], [2, 1, 6], [2, 2, 7], [2, 3, 8], [2, 4, 9], [2, 5, 10], [2, 6, 11], [2, 7, 12], [2, 8, 13], [2, 9, 14], [2, 10, 15], [2, 11, 16], [2, 12, 17], [2, 13, 18], [2, 14, 19], [2, 15, 20], - [3, 0, 21], - [4, 0, 22], [4, 1, 23], [4, 2, 24], [4, 3, 25], [4, 4, 26], [4, 5, 27], [4, 6, 28], [4, 7, 29], [4, 8, 30], [4, 9, 31], [4, 10, 32], [4, 11, 33], - [5, 0, 34], [5, 1, 35] - ] - })); - }); - - test('issue #6885: Splits large tokens', () => { + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], + [1, 0, 4], + [2, 0, 5], [2, 1, 6], [2, 2, 7], [2, 3, 8], [2, 4, 9], [2, 5, 10], [2, 6, 11], [2, 7, 12], [2, 8, 13], [2, 9, 14], [2, 10, 15], [2, 11, 16], [2, 12, 17], [2, 13, 18], [2, 14, 19], [2, 15, 20], + [3, 0, 21], + [4, 0, 22], [4, 1, 23], [4, 2, 24], [4, 3, 25], [4, 4, 26], [4, 5, 27], [4, 6, 28], [4, 7, 29], [4, 8, 30], [4, 9, 31], [4, 10, 32], [4, 11, 33], + [5, 0, 34], [5, 1, 35] + ]); + }); + + test('issue #6885: Splits large tokens', async () => { // 1 1 1 // 1 2 3 4 5 6 7 8 9 0 1 2 // 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 @@ -746,7 +681,7 @@ suite('viewLineRenderer.renderLine', () => { } }); - test('issue #21476: Does not split large tokens when ligatures are on', () => { + test('issue #21476: Does not split large tokens when ligatures are on', async () => { // 1 1 1 // 1 2 3 4 5 6 7 8 9 0 1 2 // 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 @@ -794,7 +729,7 @@ suite('viewLineRenderer.renderLine', () => { } }); - test('issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns', () => { + test('issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns', async () => { const lineText = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); const actual = renderViewLine(new RenderLineInput( @@ -821,12 +756,12 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual).html, [ - 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', - ]); + await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), { + extension: 'html', + }); }); - test('issue #6885: Does not split large tokens in RTL text', () => { + test('issue #6885: Does not split large tokens in RTL text', async () => { const lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); const actual = renderViewLine(new RenderLineInput( @@ -860,7 +795,7 @@ suite('viewLineRenderer.renderLine', () => { ].join('')); }); - test('issue #95685: Uses unicode replacement character for Paragraph Separator', () => { + test('issue #95685: Uses unicode replacement character for Paragraph Separator', async () => { const lineText = 'var ftext = [\u2029"Und", "dann", "eines"];'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); const actual = renderViewLine(new RenderLineInput( @@ -886,22 +821,21 @@ suite('viewLineRenderer.renderLine', () => { null, 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), ({ - html: [ - 'var\u00a0ftext\u00a0=\u00a0[\uFFFD"Und",\u00a0"dann",\u00a0"eines"];' - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38] - ] - })); - }); - - test('issue #19673: Monokai Theme bad-highlighting in line wrap', () => { + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38] + ]); + }); + + test('issue #19673: Monokai Theme bad-highlighting in line wrap', async () => { const lineText = ' MongoCallback): void {'; const lineParts = createViewLineTokens([ createPart(17, 1), @@ -937,30 +871,21 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(_actual), ({ - html: [ - '\u00a0\u00a0\u00a0\u00a0', - 'MongoCallback', - '<', - 'string', - '>)', - ':', - '\u00a0', - 'void', - '\u00a0{' - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7], [1, 4, 8], [1, 5, 9], [1, 6, 10], [1, 7, 11], [1, 8, 12], [1, 9, 13], [1, 10, 14], [1, 11, 15], [1, 12, 16], - [2, 0, 17], - [3, 0, 18], [3, 1, 19], [3, 2, 20], [3, 3, 21], [3, 4, 22], [3, 5, 23], - [4, 0, 24], [4, 1, 25], - [5, 0, 26], - [6, 0, 27], - [7, 0, 28], [7, 1, 29], [7, 2, 30], [7, 3, 31], - [8, 0, 32], [8, 1, 33], [8, 2, 34] - ] - })); + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], + [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7], [1, 4, 8], [1, 5, 9], [1, 6, 10], [1, 7, 11], [1, 8, 12], [1, 9, 13], [1, 10, 14], [1, 11, 15], [1, 12, 16], + [2, 0, 17], + [3, 0, 18], [3, 1, 19], [3, 2, 20], [3, 3, 21], [3, 4, 22], [3, 5, 23], + [4, 0, 24], [4, 1, 25], + [5, 0, 26], + [6, 0, 27], + [7, 0, 28], [7, 1, 29], [7, 2, 30], [7, 3, 31], + [8, 0, 32], [8, 1, 33], [8, 2, 34] + ]); }); }); @@ -1024,7 +949,7 @@ suite('viewLineRenderer.renderLine 2', () => { return inflateRenderLineOutput(actual); } - test('issue #18616: Inline decorations ending at the text length are no longer rendered', () => { + test('issue #18616: Inline decorations ending at the text length are no longer rendered', async () => { const lineContent = 'https://microsoft.com'; const actual = renderViewLine(new RenderLineInput( false, @@ -1050,19 +975,18 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), ({ - html: [ - 'https://microsoft.com' - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21] - ] - })); + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21] + ]); }); - test('issue #19207: Link in Monokai is not rendered correctly', () => { + test('issue #19207: Link in Monokai is not rendered correctly', async () => { const lineContent = '\'let url = `http://***/_api/web/lists/GetByTitle(\\\'Teambuildingaanvragen\\\')/items`;\''; const actual = renderViewLine(new RenderLineInput( true, @@ -1096,26 +1020,19 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), ({ - html: [ - '\'let\u00a0url\u00a0=\u00a0`', - 'http://***/_api/web/lists/GetByTitle(', - '\\', - '\'', - 'Teambuildingaanvragen', - '\\\'', - ')/items`;\'', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], [1, 12, 24], [1, 13, 25], [1, 14, 26], [1, 15, 27], [1, 16, 28], [1, 17, 29], [1, 18, 30], [1, 19, 31], [1, 20, 32], [1, 21, 33], [1, 22, 34], [1, 23, 35], [1, 24, 36], [1, 25, 37], [1, 26, 38], [1, 27, 39], [1, 28, 40], [1, 29, 41], [1, 30, 42], [1, 31, 43], [1, 32, 44], [1, 33, 45], [1, 34, 46], [1, 35, 47], [1, 36, 48], - [2, 0, 49], - [3, 0, 50], - [4, 0, 51], [4, 1, 52], [4, 2, 53], [4, 3, 54], [4, 4, 55], [4, 5, 56], [4, 6, 57], [4, 7, 58], [4, 8, 59], [4, 9, 60], [4, 10, 61], [4, 11, 62], [4, 12, 63], [4, 13, 64], [4, 14, 65], [4, 15, 66], [4, 16, 67], [4, 17, 68], [4, 18, 69], [4, 19, 70], [4, 20, 71], - [5, 0, 72], [5, 1, 73], - [6, 0, 74], [6, 1, 75], [6, 2, 76], [6, 3, 77], [6, 4, 78], [6, 5, 79], [6, 6, 80], [6, 7, 81], [6, 8, 82], [6, 9, 83], [6, 10, 84] - ] - })); + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', + }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], + [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], [1, 12, 24], [1, 13, 25], [1, 14, 26], [1, 15, 27], [1, 16, 28], [1, 17, 29], [1, 18, 30], [1, 19, 31], [1, 20, 32], [1, 21, 33], [1, 22, 34], [1, 23, 35], [1, 24, 36], [1, 25, 37], [1, 26, 38], [1, 27, 39], [1, 28, 40], [1, 29, 41], [1, 30, 42], [1, 31, 43], [1, 32, 44], [1, 33, 45], [1, 34, 46], [1, 35, 47], [1, 36, 48], + [2, 0, 49], + [3, 0, 50], + [4, 0, 51], [4, 1, 52], [4, 2, 53], [4, 3, 54], [4, 4, 55], [4, 5, 56], [4, 6, 57], [4, 7, 58], [4, 8, 59], [4, 9, 60], [4, 10, 61], [4, 11, 62], [4, 12, 63], [4, 13, 64], [4, 14, 65], [4, 15, 66], [4, 16, 67], [4, 17, 68], [4, 18, 69], [4, 19, 70], [4, 20, 71], + [5, 0, 72], [5, 1, 73], + [6, 0, 74], [6, 1, 75], [6, 2, 76], [6, 3, 77], [6, 4, 78], [6, 5, 79], [6, 6, 80], [6, 7, 81], [6, 8, 82], [6, 9, 83], [6, 10, 84] + ]); }); test('createLineParts simple', () => { @@ -1451,7 +1368,7 @@ suite('viewLineRenderer.renderLine 2', () => { ); }); - test('createLineParts render whitespace for selection with no selections', () => { + test('createLineParts render whitespace for selection with no selections', async () => { assert.deepStrictEqual( testCreateLineParts( false, @@ -1760,7 +1677,7 @@ suite('viewLineRenderer.renderLine 2', () => { ); }); - test('createLineParts can handle unsorted inline decorations', () => { + test('createLineParts can handle unsorted inline decorations', async () => { const actual = renderViewLine(new RenderLineInput( false, true, @@ -1795,27 +1712,21 @@ suite('viewLineRenderer.renderLine 2', () => { // bb--------- // -cccccc---- - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'H', - 'e', - 'll', - 'o\u00a0', - 'w', - 'orld', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], [2, 1, 3], - [3, 0, 4], [3, 1, 5], - [4, 0, 6], - [5, 0, 7], [5, 1, 8], [5, 2, 9], [5, 3, 10], [5, 4, 11] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 1], + [2, 0, 2], [2, 1, 3], + [3, 0, 4], [3, 1, 5], + [4, 0, 6], + [5, 0, 7], [5, 1, 8], [5, 2, 9], [5, 3, 10], [5, 4, 11] + ]); }); - test('issue #11485: Visible whitespace conflicts with before decorator attachment', () => { + test('issue #11485: Visible whitespace conflicts with before decorator attachment', async () => { const lineContent = '\tbla'; @@ -1843,19 +1754,17 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u2192\u00a0\u00a0\u00a0', - 'bla', - ], - mapping: [ - [0, 0, 0], - [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7] + ]); }); - test('issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"', () => { + test('issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"', async () => { const lineContent = '\tbla'; @@ -1883,21 +1792,18 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u2192\u00a0\u00a0\u00a0', - 'b', - 'la', - ], - mapping: [ - [0, 0, 0], - [1, 0, 4], - [2, 0, 5], [2, 1, 6], [2, 2, 7] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 4], + [2, 0, 5], [2, 1, 6], [2, 2, 7] + ]); }); - test('issue #30133: Empty lines don\'t render inline decorations', () => { + test('issue #30133: Empty lines don\'t render inline decorations', async () => { const lineContent = ''; @@ -1925,17 +1831,16 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '', - ], - mapping: [ - [1, 0, 0] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [1, 0, 0] + ]); }); - test('issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character', () => { + test('issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -1961,19 +1866,17 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00a0\u00a01.\u00a0', - '🙏', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], - [1, 0, 5], [1, 1, 6], [1, 2, 7] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], + [1, 0, 5], [1, 1, 6], [1, 2, 7] + ]); }); - test('issue #37401 #40127: Allow both before and after decorations on empty line', () => { + test('issue #37401 #40127: Allow both before and after decorations on empty line', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2002,18 +1905,16 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '', - '', - ], - mapping: [ - [1, 0, 0] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [1, 0, 0] + ]); }); - test('issue #118759: enable multiple text editor decorations in empty lines', () => { + test('issue #118759: enable multiple text editor decorations in empty lines', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2044,20 +1945,16 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '', - '', - '', - '', - ], - mapping: [ - [2, 0, 0] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [2, 0, 0] + ]); }); - test('issue #38935: GitLens end-of-line blame no longer rendering', () => { + test('issue #38935: GitLens end-of-line blame no longer rendering', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2086,20 +1983,17 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00a0\u00a0\u00a0\u00a0}', - '', - '', - ], - mapping: [ - [0, 0, 0], [0, 4, 4], - [2, 0, 5] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 4, 4], + [2, 0, 5] + ]); }); - test('issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on', () => { + test('issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2128,23 +2022,18 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'some', - '', - '\u00a0', - '', - 'text\u00a0£', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], - [4, 0, 5], [4, 1, 6], [4, 2, 7], [4, 3, 8], [4, 4, 9], [4, 5, 10], [4, 6, 11] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], + [1, 0, 4], + [4, 0, 5], [4, 1, 6], [4, 2, 7], [4, 3, 8], [4, 4, 9], [4, 5, 10], [4, 6, 11] + ]); }); - test('issue #22832: Consider fullwidth characters when rendering tabs', () => { + test('issue #22832: Consider fullwidth characters when rendering tabs', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2170,18 +2059,17 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'asd\u00a0=\u00a0"擦"\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0#asd', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 9], - [0, 9, 10], [0, 11, 12], [0, 15, 16], [0, 16, 17], [0, 17, 18], [0, 18, 19], [0, 19, 20] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 9], + [0, 9, 10], [0, 11, 12], [0, 15, 16], [0, 16, 17], [0, 17, 18], [0, 18, 19], [0, 19, 20] + ]); }); - test('issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)', () => { + test('issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2207,29 +2095,22 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'asd', - '\u00b7\u200c', - '=', - '\u00b7\u200c', - '"擦"', - '\u2192\u00a0\u2192\u00a0\u00a0\u00a0', - '#asd', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], - [1, 0, 3], - [2, 0, 4], - [3, 0, 5], - [4, 0, 6], [4, 1, 7], [4, 2, 9], - [5, 0, 10], [5, 2, 12], - [6, 0, 16], [6, 1, 17], [6, 2, 18], [6, 3, 19], [6, 4, 20] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], + [1, 0, 3], + [2, 0, 4], + [3, 0, 5], + [4, 0, 6], [4, 1, 7], [4, 2, 9], + [5, 0, 10], [5, 2, 12], + [6, 0, 16], [6, 1, 17], [6, 2, 18], [6, 3, 19], [6, 4, 20] + ]); }); - test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', () => { + test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2255,24 +2136,23 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '12345689012345678901234568901234567890123456890abába', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], + [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53] + ]); }); - test('issue #22352: Partially Broken Complex Script Rendering of Tamil', () => { + test('issue #22352: Partially Broken Complex Script Rendering of Tamil', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2298,32 +2178,29 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00a0JoyShareல்\u00a0பின்தொடர்ந்து,\u00a0விடீயோ,\u00a0ஜோக்குகள்,\u00a0', - 'அனிமேசன்,\u00a0நகைச்சுவை\u00a0படங்கள்\u00a0மற்றும்\u00a0செய்திகளை\u00a0', - 'பெறுவீர்', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], - [1, 0, 46], [1, 1, 47], [1, 2, 48], [1, 3, 49], [1, 4, 50], [1, 5, 51], [1, 6, 52], [1, 7, 53], - [1, 8, 54], [1, 9, 55], [1, 10, 56], [1, 11, 57], [1, 12, 58], [1, 13, 59], [1, 14, 60], [1, 15, 61], - [1, 16, 62], [1, 17, 63], [1, 18, 64], [1, 19, 65], [1, 20, 66], [1, 21, 67], [1, 22, 68], [1, 23, 69], - [1, 24, 70], [1, 25, 71], [1, 26, 72], [1, 27, 73], [1, 28, 74], [1, 29, 75], [1, 30, 76], [1, 31, 77], - [1, 32, 78], [1, 33, 79], [1, 34, 80], [1, 35, 81], [1, 36, 82], [1, 37, 83], [1, 38, 84], [1, 39, 85], - [1, 40, 86], [1, 41, 87], [1, 42, 88], [1, 43, 89], [1, 44, 90], [1, 45, 91], - [2, 0, 92], [2, 1, 93], [2, 2, 94], [2, 3, 95], [2, 4, 96], [2, 5, 97], [2, 6, 98], [2, 7, 99], [2, 8, 100] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], [0, 45, 45], + [1, 0, 46], [1, 1, 47], [1, 2, 48], [1, 3, 49], [1, 4, 50], [1, 5, 51], [1, 6, 52], [1, 7, 53], + [1, 8, 54], [1, 9, 55], [1, 10, 56], [1, 11, 57], [1, 12, 58], [1, 13, 59], [1, 14, 60], [1, 15, 61], + [1, 16, 62], [1, 17, 63], [1, 18, 64], [1, 19, 65], [1, 20, 66], [1, 21, 67], [1, 22, 68], [1, 23, 69], + [1, 24, 70], [1, 25, 71], [1, 26, 72], [1, 27, 73], [1, 28, 74], [1, 29, 75], [1, 30, 76], [1, 31, 77], + [1, 32, 78], [1, 33, 79], [1, 34, 80], [1, 35, 81], [1, 36, 82], [1, 37, 83], [1, 38, 84], [1, 39, 85], + [1, 40, 86], [1, 41, 87], [1, 42, 88], [1, 43, 89], [1, 44, 90], [1, 45, 91], + [2, 0, 92], [2, 1, 93], [2, 2, 94], [2, 3, 95], [2, 4, 96], [2, 5, 97], [2, 6, 98], [2, 7, 99], [2, 8, 100] + ]); }); - test('issue #42700: Hindi characters are not being rendered properly', () => { + test('issue #42700: Hindi characters are not being rendered properly', async () => { const actual = renderViewLine(new RenderLineInput( true, @@ -2349,34 +2226,31 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00a0वो\u00a0ऐसा\u00a0क्या\u00a0है\u00a0जो\u00a0हमारे\u00a0अंदर\u00a0भी\u00a0है\u00a0और\u00a0बाहर\u00a0भी\u00a0है।\u00a0', - 'जिसकी\u00a0वजह\u00a0से\u00a0हम\u00a0सब\u00a0हैं।\u00a0जिसने\u00a0इस\u00a0सृष्टि\u00a0की\u00a0रचना\u00a0की\u00a0', - 'है।', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], - [1, 0, 51], [1, 1, 52], [1, 2, 53], [1, 3, 54], [1, 4, 55], [1, 5, 56], [1, 6, 57], [1, 7, 58], - [1, 8, 59], [1, 9, 60], [1, 10, 61], [1, 11, 62], [1, 12, 63], [1, 13, 64], [1, 14, 65], - [1, 15, 66], [1, 16, 67], [1, 17, 68], [1, 18, 69], [1, 19, 70], [1, 20, 71], [1, 21, 72], - [1, 22, 73], [1, 23, 74], [1, 24, 75], [1, 25, 76], [1, 26, 77], [1, 27, 78], [1, 28, 79], - [1, 29, 80], [1, 30, 81], [1, 31, 82], [1, 32, 83], [1, 33, 84], [1, 34, 85], [1, 35, 86], - [1, 36, 87], [1, 37, 88], [1, 38, 89], [1, 39, 90], [1, 40, 91], [1, 41, 92], [1, 42, 93], - [1, 43, 94], [1, 44, 95], [1, 45, 96], [1, 46, 97], [1, 47, 98], [1, 48, 99], [1, 49, 100], - [1, 50, 101], [2, 0, 102], [2, 1, 103], [2, 2, 104], [2, 3, 105] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], + [0, 50, 50], + [1, 0, 51], [1, 1, 52], [1, 2, 53], [1, 3, 54], [1, 4, 55], [1, 5, 56], [1, 6, 57], [1, 7, 58], + [1, 8, 59], [1, 9, 60], [1, 10, 61], [1, 11, 62], [1, 12, 63], [1, 13, 64], [1, 14, 65], + [1, 15, 66], [1, 16, 67], [1, 17, 68], [1, 18, 69], [1, 19, 70], [1, 20, 71], [1, 21, 72], + [1, 22, 73], [1, 23, 74], [1, 24, 75], [1, 25, 76], [1, 26, 77], [1, 27, 78], [1, 28, 79], + [1, 29, 80], [1, 30, 81], [1, 31, 82], [1, 32, 83], [1, 33, 84], [1, 34, 85], [1, 35, 86], + [1, 36, 87], [1, 37, 88], [1, 38, 89], [1, 39, 90], [1, 40, 91], [1, 41, 92], [1, 42, 93], + [1, 43, 94], [1, 44, 95], [1, 45, 96], [1, 46, 97], [1, 47, 98], [1, 48, 99], [1, 49, 100], + [1, 50, 101], [2, 0, 102], [2, 1, 103], [2, 2, 104], [2, 3, 105] + ]); }); - test('issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped', () => { + test('issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped', async () => { const actual = renderViewLine(new RenderLineInput( true, true, @@ -2401,27 +2275,24 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'This\u00a0is\u00a0a\u00a0long\u00a0line\u00a0which\u00a0never\u00a0uses\u00a0more\u00a0than\u00a0two', - '\u00a0spaces.', - '\u00a0', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [1, 0, 50], [1, 1, 51], [1, 2, 52], [1, 3, 53], [1, 4, 54], [1, 5, 55], [1, 6, 56], [1, 7, 57], - [2, 0, 58], [2, 1, 59] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], + [1, 0, 50], [1, 1, 51], [1, 2, 52], [1, 3, 53], [1, 4, 54], [1, 5, 55], [1, 6, 56], [1, 7, 57], + [2, 0, 58], [2, 1, 59] + ]); }); - test('issue #33525: Long line with ligatures takes a long time to paint decorations', () => { + test('issue #33525: Long line with ligatures takes a long time to paint decorations', async () => { const actual = renderViewLine(new RenderLineInput( false, false, @@ -2446,52 +2317,47 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0', - 'append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0', - 'append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0', - 'append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0append\u00a0data\u00a0to\u00a0', - 'append\u00a0data\u00a0to', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], - [1, 0, 45], [1, 1, 46], [1, 2, 47], [1, 3, 48], [1, 4, 49], [1, 5, 50], [1, 6, 51], - [1, 7, 52], [1, 8, 53], [1, 9, 54], [1, 10, 55], [1, 11, 56], [1, 12, 57], [1, 13, 58], - [1, 14, 59], [1, 15, 60], [1, 16, 61], [1, 17, 62], [1, 18, 63], [1, 19, 64], [1, 20, 65], - [1, 21, 66], [1, 22, 67], [1, 23, 68], [1, 24, 69], [1, 25, 70], [1, 26, 71], [1, 27, 72], - [1, 28, 73], [1, 29, 74], [1, 30, 75], [1, 31, 76], [1, 32, 77], [1, 33, 78], [1, 34, 79], - [1, 35, 80], [1, 36, 81], [1, 37, 82], [1, 38, 83], [1, 39, 84], [1, 40, 85], [1, 41, 86], - [1, 42, 87], [1, 43, 88], [1, 44, 89], - [2, 0, 90], [2, 1, 91], [2, 2, 92], [2, 3, 93], [2, 4, 94], [2, 5, 95], [2, 6, 96], - [2, 7, 97], [2, 8, 98], [2, 9, 99], [2, 10, 100], [2, 11, 101], [2, 12, 102], - [2, 13, 103], [2, 14, 104], [2, 15, 105], [2, 16, 106], [2, 17, 107], [2, 18, 108], - [2, 19, 109], [2, 20, 110], [2, 21, 111], [2, 22, 112], [2, 23, 113], [2, 24, 114], - [2, 25, 115], [2, 26, 116], [2, 27, 117], [2, 28, 118], [2, 29, 119], [2, 30, 120], - [2, 31, 121], [2, 32, 122], [2, 33, 123], [2, 34, 124], [2, 35, 125], [2, 36, 126], - [2, 37, 127], [2, 38, 128], [2, 39, 129], [2, 40, 130], [2, 41, 131], [2, 42, 132], - [2, 43, 133], [2, 44, 134], - [3, 0, 135], [3, 1, 136], [3, 2, 137], [3, 3, 138], [3, 4, 139], [3, 5, 140], [3, 6, 141], - [3, 7, 142], [3, 8, 143], [3, 9, 144], [3, 10, 145], [3, 11, 146], [3, 12, 147], [3, 13, 148], - [3, 14, 149], [3, 15, 150], [3, 16, 151], [3, 17, 152], [3, 18, 153], [3, 19, 154], [3, 20, 155], - [3, 21, 156], [3, 22, 157], [3, 23, 158], [3, 24, 159], [3, 25, 160], [3, 26, 161], [3, 27, 162], - [3, 28, 163], [3, 29, 164], [3, 30, 165], [3, 31, 166], [3, 32, 167], [3, 33, 168], [3, 34, 169], - [3, 35, 170], [3, 36, 171], [3, 37, 172], [3, 38, 173], [3, 39, 174], [3, 40, 175], [3, 41, 176], - [3, 42, 177], [3, 43, 178], [3, 44, 179], - [4, 0, 180], [4, 1, 181], [4, 2, 182], [4, 3, 183], [4, 4, 184], [4, 5, 185], [4, 6, 186], - [4, 7, 187], [4, 8, 188], [4, 9, 189], [4, 10, 190], [4, 11, 191], [4, 12, 192], [4, 13, 193], - [4, 14, 194] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], + [1, 0, 45], [1, 1, 46], [1, 2, 47], [1, 3, 48], [1, 4, 49], [1, 5, 50], [1, 6, 51], + [1, 7, 52], [1, 8, 53], [1, 9, 54], [1, 10, 55], [1, 11, 56], [1, 12, 57], [1, 13, 58], + [1, 14, 59], [1, 15, 60], [1, 16, 61], [1, 17, 62], [1, 18, 63], [1, 19, 64], [1, 20, 65], + [1, 21, 66], [1, 22, 67], [1, 23, 68], [1, 24, 69], [1, 25, 70], [1, 26, 71], [1, 27, 72], + [1, 28, 73], [1, 29, 74], [1, 30, 75], [1, 31, 76], [1, 32, 77], [1, 33, 78], [1, 34, 79], + [1, 35, 80], [1, 36, 81], [1, 37, 82], [1, 38, 83], [1, 39, 84], [1, 40, 85], [1, 41, 86], + [1, 42, 87], [1, 43, 88], [1, 44, 89], + [2, 0, 90], [2, 1, 91], [2, 2, 92], [2, 3, 93], [2, 4, 94], [2, 5, 95], [2, 6, 96], + [2, 7, 97], [2, 8, 98], [2, 9, 99], [2, 10, 100], [2, 11, 101], [2, 12, 102], + [2, 13, 103], [2, 14, 104], [2, 15, 105], [2, 16, 106], [2, 17, 107], [2, 18, 108], + [2, 19, 109], [2, 20, 110], [2, 21, 111], [2, 22, 112], [2, 23, 113], [2, 24, 114], + [2, 25, 115], [2, 26, 116], [2, 27, 117], [2, 28, 118], [2, 29, 119], [2, 30, 120], + [2, 31, 121], [2, 32, 122], [2, 33, 123], [2, 34, 124], [2, 35, 125], [2, 36, 126], + [2, 37, 127], [2, 38, 128], [2, 39, 129], [2, 40, 130], [2, 41, 131], [2, 42, 132], + [2, 43, 133], [2, 44, 134], + [3, 0, 135], [3, 1, 136], [3, 2, 137], [3, 3, 138], [3, 4, 139], [3, 5, 140], [3, 6, 141], + [3, 7, 142], [3, 8, 143], [3, 9, 144], [3, 10, 145], [3, 11, 146], [3, 12, 147], [3, 13, 148], + [3, 14, 149], [3, 15, 150], [3, 16, 151], [3, 17, 152], [3, 18, 153], [3, 19, 154], [3, 20, 155], + [3, 21, 156], [3, 22, 157], [3, 23, 158], [3, 24, 159], [3, 25, 160], [3, 26, 161], [3, 27, 162], + [3, 28, 163], [3, 29, 164], [3, 30, 165], [3, 31, 166], [3, 32, 167], [3, 33, 168], [3, 34, 169], + [3, 35, 170], [3, 36, 171], [3, 37, 172], [3, 38, 173], [3, 39, 174], [3, 40, 175], [3, 41, 176], + [3, 42, 177], [3, 43, 178], [3, 44, 179], + [4, 0, 180], [4, 1, 181], [4, 2, 182], [4, 3, 183], [4, 4, 184], [4, 5, 185], [4, 6, 186], + [4, 7, 187], [4, 8, 188], [4, 9, 189], [4, 10, 190], [4, 11, 191], [4, 12, 192], [4, 13, 193], + [4, 14, 194] + ]); }); - test('issue #33525: Long line with ligatures takes a long time to paint decorations - not possible', () => { + test('issue #33525: Long line with ligatures takes a long time to paint decorations - not possible', async () => { const actual = renderViewLine(new RenderLineInput( false, false, @@ -2516,40 +2382,39 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53], [0, 54, 54], [0, 55, 55], [0, 56, 56], - [0, 57, 57], [0, 58, 58], [0, 59, 59], [0, 60, 60], [0, 61, 61], [0, 62, 62], [0, 63, 63], - [0, 64, 64], [0, 65, 65], [0, 66, 66], [0, 67, 67], [0, 68, 68], [0, 69, 69], [0, 70, 70], - [0, 71, 71], [0, 72, 72], [0, 73, 73], [0, 74, 74], [0, 75, 75], [0, 76, 76], [0, 77, 77], - [0, 78, 78], [0, 79, 79], [0, 80, 80], [0, 81, 81], [0, 82, 82], [0, 83, 83], [0, 84, 84], - [0, 85, 85], [0, 86, 86], [0, 87, 87], [0, 88, 88], [0, 89, 89], [0, 90, 90], [0, 91, 91], - [0, 92, 92], [0, 93, 93], [0, 94, 94], [0, 95, 95], [0, 96, 96], [0, 97, 97], [0, 98, 98], - [0, 99, 99], [0, 100, 100], [0, 101, 101], [0, 102, 102], [0, 103, 103], [0, 104, 104], - [0, 105, 105], [0, 106, 106], [0, 107, 107], [0, 108, 108], [0, 109, 109], [0, 110, 110], - [0, 111, 111], [0, 112, 112], [0, 113, 113], [0, 114, 114], [0, 115, 115], [0, 116, 116], - [0, 117, 117], [0, 118, 118], [0, 119, 119], [0, 120, 120], [0, 121, 121], [0, 122, 122], - [0, 123, 123], [0, 124, 124], [0, 125, 125], [0, 126, 126], [0, 127, 127], [0, 128, 128], - [0, 129, 129], [0, 130, 130], [0, 131, 131], [0, 132, 132], [0, 133, 133], [0, 134, 134], - [0, 135, 135], [0, 136, 136], [0, 137, 137], [0, 138, 138], [0, 139, 139], [0, 140, 140], - [0, 141, 141], [0, 142, 142], [0, 143, 143], [0, 144, 144], [0, 145, 145], [0, 146, 146], - [0, 147, 147], [0, 148, 148], [0, 149, 149], [0, 150, 150], [0, 151, 151], [0, 152, 152], - [0, 153, 153], [0, 154, 154], [0, 155, 155], [0, 156, 156] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], + [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], + [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], + [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], + [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], + [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53], [0, 54, 54], [0, 55, 55], [0, 56, 56], + [0, 57, 57], [0, 58, 58], [0, 59, 59], [0, 60, 60], [0, 61, 61], [0, 62, 62], [0, 63, 63], + [0, 64, 64], [0, 65, 65], [0, 66, 66], [0, 67, 67], [0, 68, 68], [0, 69, 69], [0, 70, 70], + [0, 71, 71], [0, 72, 72], [0, 73, 73], [0, 74, 74], [0, 75, 75], [0, 76, 76], [0, 77, 77], + [0, 78, 78], [0, 79, 79], [0, 80, 80], [0, 81, 81], [0, 82, 82], [0, 83, 83], [0, 84, 84], + [0, 85, 85], [0, 86, 86], [0, 87, 87], [0, 88, 88], [0, 89, 89], [0, 90, 90], [0, 91, 91], + [0, 92, 92], [0, 93, 93], [0, 94, 94], [0, 95, 95], [0, 96, 96], [0, 97, 97], [0, 98, 98], + [0, 99, 99], [0, 100, 100], [0, 101, 101], [0, 102, 102], [0, 103, 103], [0, 104, 104], + [0, 105, 105], [0, 106, 106], [0, 107, 107], [0, 108, 108], [0, 109, 109], [0, 110, 110], + [0, 111, 111], [0, 112, 112], [0, 113, 113], [0, 114, 114], [0, 115, 115], [0, 116, 116], + [0, 117, 117], [0, 118, 118], [0, 119, 119], [0, 120, 120], [0, 121, 121], [0, 122, 122], + [0, 123, 123], [0, 124, 124], [0, 125, 125], [0, 126, 126], [0, 127, 127], [0, 128, 128], + [0, 129, 129], [0, 130, 130], [0, 131, 131], [0, 132, 132], [0, 133, 133], [0, 134, 134], + [0, 135, 135], [0, 136, 136], [0, 137, 137], [0, 138, 138], [0, 139, 139], [0, 140, 140], + [0, 141, 141], [0, 142, 142], [0, 143, 143], [0, 144, 144], [0, 145, 145], [0, 146, 146], + [0, 147, 147], [0, 148, 148], [0, 149, 149], [0, 150, 150], [0, 151, 151], [0, 152, 152], + [0, 153, 153], [0, 154, 154], [0, 155, 155], [0, 156, 156] + ]); }); - test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { + test('issue #91936: Semantic token color highlighting fails on line with selected text', async () => { const actual = renderViewLine(new RenderLineInput( false, true, @@ -2593,85 +2458,50 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - '\u00b7\u200c', - 'else', - '\u00b7\u200c', - 'if', - '\u00b7\u200c', - '(', - '$s', - '\u00b7\u200c', - '=', - '\u00b7\u200c', - '08', - ')', - '\u00b7\u200c', - 'then', - '\u00b7\u200c', - '\'\\b\'', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], - [4, 0, 4], - [5, 0, 5], - [6, 0, 6], - [7, 0, 7], - [8, 0, 8], - [9, 0, 9], - [10, 0, 10], - [11, 0, 11], - [12, 0, 12], - [13, 0, 13], - [14, 0, 14], - [15, 0, 15], - [16, 0, 16], - [17, 0, 17], - [18, 0, 18], - [19, 0, 19], - [20, 0, 20], [20, 1, 21], [20, 2, 22], [20, 3, 23], - [21, 0, 24], - [22, 0, 25], [22, 1, 26], - [23, 0, 27], - [24, 0, 28], - [25, 0, 29], [25, 1, 30], - [26, 0, 31], - [27, 0, 32], - [28, 0, 33], - [29, 0, 34], [29, 1, 35], - [30, 0, 36], - [31, 0, 37], - [32, 0, 38], [32, 1, 39], [32, 2, 40], [32, 3, 41], - [33, 0, 42], - [34, 0, 43], [34, 1, 44], [34, 2, 45], [34, 3, 46], [34, 4, 47] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], + [1, 0, 1], + [2, 0, 2], + [3, 0, 3], + [4, 0, 4], + [5, 0, 5], + [6, 0, 6], + [7, 0, 7], + [8, 0, 8], + [9, 0, 9], + [10, 0, 10], + [11, 0, 11], + [12, 0, 12], + [13, 0, 13], + [14, 0, 14], + [15, 0, 15], + [16, 0, 16], + [17, 0, 17], + [18, 0, 18], + [19, 0, 19], + [20, 0, 20], [20, 1, 21], [20, 2, 22], [20, 3, 23], + [21, 0, 24], + [22, 0, 25], [22, 1, 26], + [23, 0, 27], + [24, 0, 28], + [25, 0, 29], [25, 1, 30], + [26, 0, 31], + [27, 0, 32], + [28, 0, 33], + [29, 0, 34], [29, 1, 35], + [30, 0, 36], + [31, 0, 37], + [32, 0, 38], [32, 1, 39], [32, 2, 40], [32, 3, 41], + [33, 0, 42], + [34, 0, 43], [34, 1, 44], [34, 2, 45], [34, 3, 46], [34, 4, 47] + ]); }); - test('issue #119416: Delete Control Character (U+007F / ) displayed as space', () => { + test('issue #119416: Delete Control Character (U+007F / ) displayed as space', async () => { const actual = renderViewLine(new RenderLineInput( false, false, @@ -2696,17 +2526,16 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '[\u2421]\u00a0[\u2400]', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7] + ]); }); - test('issue #116939: Important control characters aren\'t rendered', () => { + test('issue #116939: Important control characters aren\'t rendered', async () => { const actual = renderViewLine(new RenderLineInput( false, false, @@ -2731,28 +2560,23 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - 'transferBalance(5678,', - '[U+202E]', - '6776,4321', - '[U+202C]', - ',"USD");', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], - [1, 0, 21], - [2, 0, 29], [2, 1, 30], [2, 2, 31], [2, 3, 32], [2, 4, 33], [2, 5, 34], [2, 6, 35], - [2, 7, 36], [2, 8, 37], - [3, 0, 38], - [4, 0, 46], [4, 1, 47], [4, 2, 48], [4, 3, 49], [4, 4, 50], [4, 5, 51], [4, 6, 52], [4, 7, 53], [4, 8, 54] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], + [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], + [1, 0, 21], + [2, 0, 29], [2, 1, 30], [2, 2, 31], [2, 3, 32], [2, 4, 33], [2, 5, 34], [2, 6, 35], + [2, 7, 36], [2, 8, 37], + [3, 0, 38], + [4, 0, 46], [4, 1, 47], [4, 2, 48], [4, 3, 49], [4, 4, 50], [4, 5, 51], [4, 6, 52], [4, 7, 53], [4, 8, 54] + ]); }); - test('issue #124038: Multiple end-of-line text decorations get merged', () => { + test('issue #124038: Multiple end-of-line text decorations get merged', async () => { const actual = renderViewLine(new RenderLineInput( true, false, @@ -2781,20 +2605,15 @@ suite('viewLineRenderer.renderLine 2', () => { 14 )); - assert.deepStrictEqual(inflateRenderLineOutput(actual), { - html: [ - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - 'if', - '', - '', - '', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 1, 5], - [3, 0, 6] - ] + const inflated = inflateRenderLineOutput(actual); + await assertSnapshot(inflated.html.join(''), { + extension: 'html', }); + assert.deepStrictEqual(inflated.mapping, [ + [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], + [1, 0, 4], [1, 1, 5], + [3, 0, 6] + ]); }); function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: TestLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { From b50278cb859622f6a0f358f005ca246e78634a9d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 15:04:35 +0200 Subject: [PATCH 0800/4355] Adopt more snapshot assertions --- ..._not_emit_width_for_monospace_fonts.0.html | 1 + ..._render_whitespace_-_2_leading_tabs.0.html | 1 + ...ender_whitespace_-_4_leading_spaces.0.html | 1 + ...ender_whitespace_-_8_leading_spaces.0.html | 1 + ...ace_-_mixed_leading_spaces_and_tabs.0.html | 1 + ...render_whitespace_for_all_in_middle.0.html | 1 + ...iple__initially_unsorted_selections.0.html | 1 + ..._selection_with_multiple_selections.0.html | 1 + ...ce_for_selection_with_no_selections.0.html | 1 + ...lection_spanning_part_of_whitespace.0.html | 1 + ..._with_selections_next_to_each_other.0.html | 1 + ...selection_with_whole_line_selection.0.html | 1 + ..._leading_and_8_trailing_whitespaces.0.html | 1 + ...ing__inner__and_trailing_whitespace.0.html | 1 + ...er__and_without_trailing_whitespace.0.html | 1 + ...th_line_containing_only_whitespaces.0.html | 1 + ...ace_in_middle_but_not_for_one_space.0.html | 1 + ...render_whitespace_skips_faux_indent.0.html | 1 + ...renderLine_2_createLineParts_simple.0.html | 1 + ...2_createLineParts_simple_two_tokens.0.html | 1 + ..._not_split_large_tokens_in_RTL_text.0.html | 1 + .../viewLayout/viewLineRenderer.test.ts | 1112 +++++++---------- 22 files changed, 445 insertions(+), 688 deletions(-) create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.0.html new file mode 100644 index 00000000000..d85834fccf3 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.0.html @@ -0,0 +1 @@ +        ·‌·‌Hello world!·‌→·‌·‌→ ·‌·‌·‌→·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.0.html new file mode 100644 index 00000000000..43b726a4d5e --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.0.html @@ -0,0 +1 @@ +→   →   Hello world!→    \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.0.html new file mode 100644 index 00000000000..cf12c5dc932 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.0.html @@ -0,0 +1 @@ +·‌·‌·‌·‌Hello world!·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.0.html new file mode 100644 index 00000000000..114e8a7a74f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.0.html @@ -0,0 +1 @@ +·‌·‌·‌·‌·‌·‌·‌·‌Hello world!·‌·‌·‌·‌·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.0.html new file mode 100644 index 00000000000..d60a4609959 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.0.html @@ -0,0 +1 @@ +·‌·‌→ →   ·‌·‌Hello world!·‌→·‌·‌→ ·‌·‌·‌→·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.0.html new file mode 100644 index 00000000000..4fc7cc9fe04 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.0.html @@ -0,0 +1 @@ +·‌Hello·‌world!→   \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.0.html new file mode 100644 index 00000000000..de868191852 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.0.html @@ -0,0 +1 @@ +·‌Hello world!→   \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.0.html new file mode 100644 index 00000000000..de868191852 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.0.html @@ -0,0 +1 @@ +·‌Hello world!→   \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.0.html new file mode 100644 index 00000000000..d21267fa456 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.0.html @@ -0,0 +1 @@ + Hello world!    \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.0.html new file mode 100644 index 00000000000..65fe5ffeb73 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.0.html @@ -0,0 +1 @@ +·‌Hello world!    \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.0.html new file mode 100644 index 00000000000..7ee60f45127 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.0.html @@ -0,0 +1 @@ +·‌*·‌S \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.0.html new file mode 100644 index 00000000000..4fc7cc9fe04 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.0.html @@ -0,0 +1 @@ +·‌Hello·‌world!→   \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.0.html new file mode 100644 index 00000000000..562da6e77cf --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.0.html @@ -0,0 +1 @@ +        Hello world!·‌·‌·‌·‌·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.0.html new file mode 100644 index 00000000000..eaff3ffe7d7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.0.html @@ -0,0 +1 @@ + Hello world!·‌→  \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.0.html new file mode 100644 index 00000000000..0e767a9d388 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.0.html @@ -0,0 +1 @@ + Hello world! \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.0.html new file mode 100644 index 00000000000..de69a5495e8 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.0.html @@ -0,0 +1 @@ +·‌→  ·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.0.html new file mode 100644 index 00000000000..10f48282b59 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.0.html @@ -0,0 +1 @@ +it·‌·‌it it·‌·‌it \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.0.html new file mode 100644 index 00000000000..34c3449c1b6 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.0.html @@ -0,0 +1 @@ +        ·‌·‌Hello world!·‌→·‌·‌→ ·‌·‌·‌→·‌·‌·‌·‌ \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.0.html new file mode 100644 index 00000000000..84b78015739 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.0.html @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.0.html new file mode 100644 index 00000000000..aa4ce900f3f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.0.html @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html new file mode 100644 index 00000000000..81ea223a52c --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html @@ -0,0 +1 @@ +את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר. \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 1c8d44c37d7..0699a97170c 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -16,6 +16,8 @@ import { CharacterMapping, DomPosition, RenderLineInput, RenderLineOutput2, rend import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js'; import { TestLineToken, TestLineTokens } from '../core/testLineToken.js'; +const HTML_EXTENSION = { extension: 'html' }; + function createViewLineTokens(viewLineTokens: TestLineToken[]): IViewLineTokens { return new TestLineTokens(viewLineTokens); } @@ -195,9 +197,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 1], @@ -250,9 +250,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], @@ -310,9 +308,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 4, 4], [0, 8, 8], [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], @@ -367,9 +363,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 4, 4], [0, 8, 8], [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], @@ -416,9 +410,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [2, 0, 12], @@ -459,9 +451,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [1, 0, 3], [1, 1, 4], [1, 2, 5], [1, 3, 6], [1, 4, 7], [1, 5, 8], [1, 6, 9], [1, 7, 10], [1, 8, 11], [1, 9, 12], @@ -510,9 +500,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 1], [1, 1, 2], [1, 2, 3], [1, 3, 4], [1, 4, 5], [1, 5, 6], @@ -562,9 +550,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], [1, 0, 4], @@ -756,9 +742,7 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), HTML_EXTENSION); }); test('issue #6885: Does not split large tokens in RTL text', async () => { @@ -788,11 +772,7 @@ suite('viewLineRenderer.renderLine', () => { 14 )); - assert.deepStrictEqual(actual.html, [ - '', - 'את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.', - '' - ].join('')); + await assertSnapshot(actual.html, HTML_EXTENSION); }); test('issue #95685: Uses unicode replacement character for Paragraph Separator', async () => { @@ -822,9 +802,7 @@ suite('viewLineRenderer.renderLine', () => { 14 )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -872,9 +850,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(_actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7], [1, 4, 8], [1, 5, 9], [1, 6, 10], [1, 7, 11], [1, 8, 12], [1, 9, 13], [1, 10, 14], [1, 11, 15], [1, 12, 16], @@ -976,9 +952,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -1021,9 +995,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], [1, 12, 24], [1, 13, 25], [1, 14, 26], [1, 15, 27], [1, 16, 28], [1, 17, 29], [1, 18, 30], [1, 19, 31], [1, 20, 32], [1, 21, 33], [1, 22, 34], [1, 23, 35], [1, 24, 36], [1, 25, 37], [1, 26, 38], [1, 27, 39], [1, 28, 40], [1, 29, 41], [1, 30, 42], [1, 31, 43], [1, 32, 44], [1, 33, 45], [1, 34, 46], [1, 35, 47], [1, 36, 48], @@ -1035,646 +1007,452 @@ suite('viewLineRenderer.renderLine 2', () => { ]); }); - test('createLineParts simple', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - 'Hello world!', - [ - createPart(12, 1) - ], - 0, - 'none', - null - ), - { - html: [ - 'Hello\u00a0world!' - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12] - ] - } + test('createLineParts simple', async () => { + const actual = testCreateLineParts( + false, + 'Hello world!', + [ + createPart(12, 1) + ], + 0, + 'none', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12] + ]); }); - test('createLineParts simple two tokens', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - 'Hello world!', - [ - createPart(6, 1), - createPart(12, 2) - ], - 0, - 'none', - null - ), - { - html: [ - 'Hello\u00a0', - 'world!', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], - [1, 0, 6], [1, 1, 7], [1, 2, 8], [1, 3, 9], [1, 4, 10], [1, 5, 11], [1, 6, 12] - ] - } + test('createLineParts simple two tokens', async () => { + const actual = testCreateLineParts( + false, + 'Hello world!', + [ + createPart(6, 1), + createPart(12, 2) + ], + 0, + 'none', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], + [1, 0, 6], [1, 1, 7], [1, 2, 8], [1, 3, 9], [1, 4, 10], [1, 5, 11], [1, 6, 12] + ]); }); - test('createLineParts render whitespace - 4 leading spaces', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world! ', - [ - createPart(4, 1), - createPart(6, 2), - createPart(20, 3) - ], - 0, - 'boundary', - null - ), - { - html: [ - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 8, 14], [2, 9, 15], - [3, 0, 16], [3, 2, 17], [3, 4, 18], [3, 6, 19], [3, 8, 20] - ] - } + test('createLineParts render whitespace - 4 leading spaces', async () => { + const actual = testCreateLineParts( + false, + ' Hello world! ', + [ + createPart(4, 1), + createPart(6, 2), + createPart(20, 3) + ], + 0, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], + [1, 0, 4], [1, 1, 5], + [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 8, 14], [2, 9, 15], + [3, 0, 16], [3, 2, 17], [3, 4, 18], [3, 6, 19], [3, 8, 20] + ]); }); - test('createLineParts render whitespace - 8 leading spaces', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world! ', - [ - createPart(8, 1), - createPart(10, 2), - createPart(28, 3) - ], - 0, - 'boundary', - null - ), - { - html: [ - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], - [2, 0, 8], [2, 1, 9], - [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], - [4, 0, 20], [4, 2, 21], [4, 4, 22], [4, 6, 23], - [5, 0, 24], [5, 2, 25], [5, 4, 26], [5, 6, 27], [5, 8, 28] - ] - } + test('createLineParts render whitespace - 8 leading spaces', async () => { + const actual = testCreateLineParts( + false, + ' Hello world! ', + [ + createPart(8, 1), + createPart(10, 2), + createPart(28, 3) + ], + 0, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], + [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], + [2, 0, 8], [2, 1, 9], + [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], + [4, 0, 20], [4, 2, 21], [4, 4, 22], [4, 6, 23], + [5, 0, 24], [5, 2, 25], [5, 4, 26], [5, 6, 27], [5, 8, 28] + ]); }); - test('createLineParts render whitespace - 2 leading tabs', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - '\t\tHello world!\t', - [ - createPart(2, 1), - createPart(4, 2), - createPart(15, 3) - ], - 0, - 'boundary', - null - ), - { - html: [ - '\u2192\u00a0\u00a0\u00a0', - '\u2192\u00a0\u00a0\u00a0', - 'He', - 'llo\u00a0world!', - '\u2192\u00a0\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 4], - [2, 0, 8], [2, 1, 9], - [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], - [4, 0, 20], [4, 4, 24] - ] - } + test('createLineParts render whitespace - 2 leading tabs', async () => { + const actual = testCreateLineParts( + false, + '\t\tHello world!\t', + [ + createPart(2, 1), + createPart(4, 2), + createPart(15, 3) + ], + 0, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 4], + [2, 0, 8], [2, 1, 9], + [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], + [4, 0, 20], [4, 4, 24] + ]); }); - test('createLineParts render whitespace - mixed leading spaces and tabs', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' \t\t Hello world! \t \t \t ', - [ - createPart(6, 1), - createPart(8, 2), - createPart(31, 3) - ], - 0, - 'boundary', - null - ), - { - html: [ - '\u00b7\u200c\u00b7\u200c\u2192\u00a0', - '\u2192\u00a0\u00a0\u00a0', - '\u00b7\u200c\u00b7\u200c', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\uffeb', - '\u00b7\u200c\u00b7\u200c\u2192\u00a0', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\uffeb', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], - [1, 0, 4], - [2, 0, 8], [2, 2, 9], - [3, 0, 10], [3, 1, 11], - [4, 0, 12], [4, 1, 13], [4, 2, 14], [4, 3, 15], [4, 4, 16], [4, 5, 17], [4, 6, 18], [4, 7, 19], [4, 8, 20], [4, 9, 21], - [5, 0, 22], [5, 2, 23], - [6, 0, 24], [6, 2, 25], [6, 4, 26], - [7, 0, 28], [7, 2, 29], [7, 4, 30], [7, 6, 31], - [8, 0, 32], [8, 2, 33], [8, 4, 34], [8, 6, 35], [8, 8, 36] - ] - } + test('createLineParts render whitespace - mixed leading spaces and tabs', async () => { + const actual = testCreateLineParts( + false, + ' \t\t Hello world! \t \t \t ', + [ + createPart(6, 1), + createPart(8, 2), + createPart(31, 3) + ], + 0, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 2, 1], [0, 4, 2], + [1, 0, 4], + [2, 0, 8], [2, 2, 9], + [3, 0, 10], [3, 1, 11], + [4, 0, 12], [4, 1, 13], [4, 2, 14], [4, 3, 15], [4, 4, 16], [4, 5, 17], [4, 6, 18], [4, 7, 19], [4, 8, 20], [4, 9, 21], + [5, 0, 22], [5, 2, 23], + [6, 0, 24], [6, 2, 25], [6, 4, 26], + [7, 0, 28], [7, 2, 29], [7, 4, 30], [7, 6, 31], + [8, 0, 32], [8, 2, 33], [8, 4, 34], [8, 6, 35], [8, 8, 36] + ]); }); - test('createLineParts render whitespace skips faux indent', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - '\t\t Hello world! \t \t \t ', - [ - createPart(4, 1), - createPart(6, 2), - createPart(29, 3) - ], - 2, - 'boundary', - null - ), - { - html: [ - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '\u00b7\u200c\u00b7\u200c', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\uffeb', - '\u00b7\u200c\u00b7\u200c\u2192\u00a0', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\uffeb', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 4, 4], - [1, 0, 8], [1, 2, 9], - [2, 0, 10], [2, 1, 11], - [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], - [4, 0, 22], [4, 2, 23], - [5, 0, 24], [5, 2, 25], [5, 4, 26], - [6, 0, 28], [6, 2, 29], [6, 4, 30], [6, 6, 31], - [7, 0, 32], [7, 2, 33], [7, 4, 34], [7, 6, 35], [7, 8, 36] - ] - } + test('createLineParts render whitespace skips faux indent', async () => { + const actual = testCreateLineParts( + false, + '\t\t Hello world! \t \t \t ', + [ + createPart(4, 1), + createPart(6, 2), + createPart(29, 3) + ], + 2, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 4, 4], + [1, 0, 8], [1, 2, 9], + [2, 0, 10], [2, 1, 11], + [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], + [4, 0, 22], [4, 2, 23], + [5, 0, 24], [5, 2, 25], [5, 4, 26], + [6, 0, 28], [6, 2, 29], [6, 4, 30], [6, 6, 31], + [7, 0, 32], [7, 2, 33], [7, 4, 34], [7, 6, 35], [7, 8, 36] + ]); }); - test('createLineParts does not emit width for monospace fonts', () => { - assert.deepStrictEqual( - testCreateLineParts( - true, - '\t\t Hello world! \t \t \t ', - [ - createPart(4, 1), - createPart(6, 2), - createPart(29, 3) - ], - 2, - 'boundary', - null - ), - { - html: [ - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - '\u00b7\u200c\u00b7\u200c', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\uffeb\u00b7\u200c\u00b7\u200c\u2192\u00a0\u00b7\u200c\u00b7\u200c\u00b7\u200c\uffeb\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 4, 4], - [1, 0, 8], [1, 2, 9], - [2, 0, 10], [2, 1, 11], - [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], - [4, 0, 22], [4, 2, 23], [4, 3, 24], [4, 5, 25], [4, 7, 26], [4, 9, 28], [4, 11, 29], [4, 13, 30], [4, 15, 31], [4, 16, 32], [4, 18, 33], [4, 20, 34], [4, 22, 35], [4, 24, 36] - ] - } + test('createLineParts does not emit width for monospace fonts', async () => { + const actual = testCreateLineParts( + true, + '\t\t Hello world! \t \t \t ', + [ + createPart(4, 1), + createPart(6, 2), + createPart(29, 3) + ], + 2, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 4, 4], + [1, 0, 8], [1, 2, 9], + [2, 0, 10], [2, 1, 11], + [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], + [4, 0, 22], [4, 2, 23], [4, 3, 24], [4, 5, 25], [4, 7, 26], [4, 9, 28], [4, 11, 29], [4, 13, 30], [4, 15, 31], [4, 16, 32], [4, 18, 33], [4, 20, 34], [4, 22, 35], [4, 24, 36] + ]); }); - test('createLineParts render whitespace in middle but not for one space', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - 'it it it it', - [ - createPart(6, 1), - createPart(7, 2), - createPart(13, 3) - ], - 0, - 'boundary', - null - ), - { - html: [ - 'it', - '\u00b7\u200c\u00b7\u200c', - 'it', - '\u00a0', - 'it', - '\u00b7\u200c\u00b7\u200c', - 'it', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], - [1, 0, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], - [5, 0, 9], [5, 2, 10], - [6, 0, 11], [6, 1, 12], [6, 2, 13] - ] - } + test('createLineParts render whitespace in middle but not for one space', async () => { + const actual = testCreateLineParts( + false, + 'it it it it', + [ + createPart(6, 1), + createPart(7, 2), + createPart(13, 3) + ], + 0, + 'boundary', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], + [1, 0, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], + [4, 0, 7], [4, 1, 8], + [5, 0, 9], [5, 2, 10], + [6, 0, 11], [6, 1, 12], [6, 2, 13] + ]); }); - test('createLineParts render whitespace for all in middle', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'all', - null - ), - { - html: [ - '\u00b7\u200c', - 'Hel', - 'lo', - '\u00b7\u200c', - 'world!', - '\u2192\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], - [5, 0, 13], [5, 3, 16] - ] - } + test('createLineParts render whitespace for all in middle', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'all', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], + [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], + [5, 0, 13], [5, 3, 16] + ]); }); test('createLineParts render whitespace for selection with no selections', async () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'selection', - null - ), - { - html: [ - '\u00a0Hel', - 'lo', - '\u00a0world!\u00a0\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 10, 16] - ] - } + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], + [1, 0, 4], [1, 1, 5], + [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 10, 16] + ]); }); - test('createLineParts render whitespace for selection with whole line selection', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'selection', - [new OffsetRange(0, 14)] - ), - { - html: [ - '\u00b7\u200c', - 'Hel', - 'lo', - '\u00b7\u200c', - 'world!', - '\u2192\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], - [5, 0, 13], [5, 3, 16] - ] - } + test('createLineParts render whitespace for selection with whole line selection', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new OffsetRange(0, 14)] ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], + [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], + [5, 0, 13], [5, 3, 16] + ]); }); - test('createLineParts render whitespace for selection with selection spanning part of whitespace', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'selection', - [new OffsetRange(0, 5)] - ), - { - html: [ - '\u00b7\u200c', - 'Hel', - 'lo', - '\u00a0world!\u00a0\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], [3, 7, 13], [3, 10, 16] - ] - } + test('createLineParts render whitespace for selection with selection spanning part of whitespace', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new OffsetRange(0, 5)] ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], [3, 7, 13], [3, 10, 16] + ]); }); - test('createLineParts render whitespace for selection with multiple selections', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'selection', - [new OffsetRange(0, 5), new OffsetRange(9, 14)] - ), - { - html: [ - '\u00b7\u200c', - 'Hel', - 'lo', - '\u00a0world!', - '\u2192\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], - [4, 0, 13], [4, 3, 16] - ] - } + test('createLineParts render whitespace for selection with multiple selections', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new OffsetRange(0, 5), new OffsetRange(9, 14)] ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], + [4, 0, 13], [4, 3, 16] + ]); }); - test('createLineParts render whitespace for selection with multiple, initially unsorted selections', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!\t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'selection', - [new OffsetRange(9, 14), new OffsetRange(0, 5)] - ), - { - html: [ - '\u00b7\u200c', - 'Hel', - 'lo', - '\u00a0world!', - '\u2192\u00a0\u00a0', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], - [4, 0, 13], [4, 3, 16] - ] - } + test('createLineParts render whitespace for selection with multiple, initially unsorted selections', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new OffsetRange(9, 14), new OffsetRange(0, 5)] ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], [1, 1, 2], [1, 2, 3], + [2, 0, 4], [2, 1, 5], + [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], + [4, 0, 13], [4, 3, 16] + ]); }); - test('createLineParts render whitespace for selection with selections next to each other', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' * S', - [ - createPart(4, 0) - ], - 0, - 'selection', - [new OffsetRange(0, 1), new OffsetRange(1, 2), new OffsetRange(2, 3)] - ), - { - html: [ - '\u00b7\u200c', - '*', - '\u00b7\u200c', - 'S', - ], - mapping: [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], [3, 1, 4] - ] - } + test('createLineParts render whitespace for selection with selections next to each other', async () => { + const actual = testCreateLineParts( + false, + ' * S', + [ + createPart(4, 0) + ], + 0, + 'selection', + [new OffsetRange(0, 1), new OffsetRange(1, 2), new OffsetRange(2, 3)] ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], + [1, 0, 1], + [2, 0, 2], + [3, 0, 3], [3, 1, 4] + ]); }); - test('createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world!', - [ - createPart(4, 0), - createPart(6, 1), - createPart(14, 2) - ], - 0, - 'trailing', - null - ), - { - html: [ - '\u00a0Hel', - 'lo', - '\u00a0world!', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13] - ] - } + test('createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace', async () => { + const actual = testCreateLineParts( + false, + ' Hello world!', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'trailing', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], + [1, 0, 4], [1, 1, 5], + [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13] + ]); }); - test('createLineParts render whitespace for trailing with leading, inner, and trailing whitespace', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world! \t', - [ - createPart(4, 0), - createPart(6, 1), - createPart(15, 2) - ], - 0, - 'trailing', - null - ), - { - html: [ - '\u00a0Hel', - 'lo', - '\u00a0world!', - '\u00b7\u200c\u2192\u00a0', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], - [3, 0, 13], [3, 2, 14], [3, 4, 16] - ] - } + test('createLineParts render whitespace for trailing with leading, inner, and trailing whitespace', async () => { + const actual = testCreateLineParts( + false, + ' Hello world! \t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(15, 2) + ], + 0, + 'trailing', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], + [1, 0, 4], [1, 1, 5], + [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], + [3, 0, 13], [3, 2, 14], [3, 4, 16] + ]); }); - test('createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' Hello world! ', - [ - createPart(8, 1), - createPart(10, 2), - createPart(28, 3) - ], - 0, - 'trailing', - null - ), - { - html: [ - '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', - 'He', - 'llo\u00a0world!', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - '\u00b7\u200c\u00b7\u200c\u00b7\u200c\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [1, 0, 8], [1, 1, 9], - [2, 0, 10], [2, 1, 11], [2, 2, 12], [2, 3, 13], [2, 4, 14], [2, 5, 15], [2, 6, 16], [2, 7, 17], [2, 8, 18], [2, 9, 19], - [3, 0, 20], [3, 2, 21], [3, 4, 22], [3, 6, 23], - [4, 0, 24], [4, 2, 25], [4, 4, 26], [4, 6, 27], [4, 8, 28] - ] - } + test('createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces', async () => { + const actual = testCreateLineParts( + false, + ' Hello world! ', + [ + createPart(8, 1), + createPart(10, 2), + createPart(28, 3) + ], + 0, + 'trailing', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], + [1, 0, 8], [1, 1, 9], + [2, 0, 10], [2, 1, 11], [2, 2, 12], [2, 3, 13], [2, 4, 14], [2, 5, 15], [2, 6, 16], [2, 7, 17], [2, 8, 18], [2, 9, 19], + [3, 0, 20], [3, 2, 21], [3, 4, 22], [3, 6, 23], + [4, 0, 24], [4, 2, 25], [4, 4, 26], [4, 6, 27], [4, 8, 28] + ]); }); - test('createLineParts render whitespace for trailing with line containing only whitespaces', () => { - assert.deepStrictEqual( - testCreateLineParts( - false, - ' \t ', - [ - createPart(2, 0), - createPart(3, 1), - ], - 0, - 'trailing', - null - ), - { - html: [ - '\u00b7\u200c\u2192\u00a0\u00a0', - '\u00b7\u200c', - ], - mapping: [ - [0, 0, 0], [0, 2, 1], - [1, 0, 4], [1, 2, 5] - ] - } + test('createLineParts render whitespace for trailing with line containing only whitespaces', async () => { + const actual = testCreateLineParts( + false, + ' \t ', + [ + createPart(2, 0), + createPart(3, 1), + ], + 0, + 'trailing', + null ); + await assertSnapshot(actual.html.join(''), HTML_EXTENSION); + assert.deepStrictEqual(actual.mapping, [ + [0, 0, 0], [0, 2, 1], + [1, 0, 4], [1, 2, 5] + ]); }); test('createLineParts can handle unsorted inline decorations', async () => { @@ -1713,9 +1491,7 @@ suite('viewLineRenderer.renderLine 2', () => { // -cccccc---- const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 1], @@ -1755,9 +1531,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7] @@ -1793,9 +1567,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 4], @@ -1832,9 +1604,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [1, 0, 0] ]); @@ -1867,9 +1637,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [1, 0, 5], [1, 1, 6], [1, 2, 7] @@ -1906,9 +1674,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [1, 0, 0] ]); @@ -1946,9 +1712,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [2, 0, 0] ]); @@ -1984,9 +1748,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 4, 4], [2, 0, 5] @@ -2023,9 +1785,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [1, 0, 4], @@ -2060,9 +1820,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 9], [0, 9, 10], [0, 11, 12], [0, 15, 16], [0, 16, 17], [0, 17, 18], [0, 18, 19], [0, 19, 20] @@ -2096,9 +1854,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [1, 0, 3], @@ -2137,9 +1893,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2179,9 +1933,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2227,9 +1979,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2276,9 +2026,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2318,9 +2066,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2383,9 +2129,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2459,9 +2203,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [1, 0, 1], @@ -2527,9 +2269,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7] ]); @@ -2561,9 +2301,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], @@ -2606,9 +2344,7 @@ suite('viewLineRenderer.renderLine 2', () => { )); const inflated = inflateRenderLineOutput(actual); - await assertSnapshot(inflated.html.join(''), { - extension: 'html', - }); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); assert.deepStrictEqual(inflated.mapping, [ [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], [1, 0, 4], [1, 1, 5], From e66d2ac158d5ba754f4b7f23421cf9209708e7dd Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 15:11:01 +0200 Subject: [PATCH 0801/4355] More snapshots --- ..._handle_unsorted_inline_decorations.1.snap | 14 + ...ts_with_before_decorator_attachment.1.snap | 1 + ..._control_characters_aren_t_rendered.1.snap | 43 ++ ...t_editor_decorations_in_empty_lines.1.snap | 1 + ...U_007F_____127___displayed_as_space.1.snap | 10 + ...of-line_text_decorations_get_merged.1.snap | 9 + ..._when_renderControlCharacters_is_on.1.snap | 14 + ..._text_length_are_no_longer_rendered.1.snap | 24 ++ ...n_Monokai_is_not_rendered_correctly.1.snap | 87 +++++ ...52__COMBINING_ACUTE_ACCENT__U_0301_.1.snap | 56 +++ ...n_Complex_Script_Rendering_of_Tamil.1.snap | 103 +++++ ...idth_characters_when_rendering_tabs.1.snap | 18 + ..._rendering_tabs__render_whitespace_.1.snap | 18 + ...nes_don_t_render_inline_decorations.1.snap | 1 + ...ter_decorator_causes_line_to__jump_.1.snap | 1 + ...es_a_long_time_to_paint_decorations.1.snap | 197 ++++++++++ ...to_paint_decorations_-_not_possible.1.snap | 159 ++++++++ ..._document_results_in______character.1.snap | 10 + ...and_after_decorations_on_empty_line.1.snap | 1 + ...ine_wrap_point_when_line_is_wrapped.1.snap | 62 +++ ...d-of-line_blame_no_longer_rendering.1.snap | 1 + ...ers_are_not_being_rendered_properly.1.snap | 108 +++++ ...ng_fails_on_line_with_selected_text.1.snap | 50 +++ ...in_RTL_languages_in_recent_versions.1.snap | 43 ++ ...Theme_bad-highlighting_in_line_wrap.1.snap | 37 ++ ...__2255__Weird_line_rendering_part_1.1.snap | 71 ++++ ...__2255__Weird_line_rendering_part_2.1.snap | 72 ++++ ...decoration_type_shown_before_cursor.1.snap | 19 + ...t_character_for_Paragraph_Separator.1.snap | 41 ++ ...g_whitespace_influences_bidi_layout.1.snap | 38 ++ ...ce_code_rendering_for_RTL_languages.1.snap | 70 ++++ ...iewLineRenderer_renderLine_overflow.1.snap | 9 + ...ineRenderer_renderLine_typical_line.1.snap | 51 +++ .../viewLayout/viewLineRenderer.test.ts | 368 ++---------------- 34 files changed, 1472 insertions(+), 335 deletions(-) create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.1.snap diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.1.snap new file mode 100644 index 00000000000..3c4972e3f69 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_can_handle_unsorted_inline_decorations.1.snap @@ -0,0 +1,14 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 2, 0, 2 ], + [ 2, 1, 3 ], + [ 3, 0, 4 ], + [ 3, 1, 5 ], + [ 4, 0, 6 ], + [ 5, 0, 7 ], + [ 5, 1, 8 ], + [ 5, 2, 9 ], + [ 5, 3, 10 ], + [ 5, 4, 11 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.1.snap new file mode 100644 index 00000000000..5d254796ac0 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__11485__Visible_whitespace_conflicts_with_before_decorator_attachment.1.snap @@ -0,0 +1 @@ +[ [ 0, 0, 0 ], [ 1, 0, 4 ], [ 1, 1, 5 ], [ 1, 2, 6 ], [ 1, 3, 7 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.1.snap new file mode 100644 index 00000000000..d8516d979bd --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__116939__Important_control_characters_aren_t_rendered.1.snap @@ -0,0 +1,43 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 1, 0, 21 ], + [ 2, 0, 29 ], + [ 2, 1, 30 ], + [ 2, 2, 31 ], + [ 2, 3, 32 ], + [ 2, 4, 33 ], + [ 2, 5, 34 ], + [ 2, 6, 35 ], + [ 2, 7, 36 ], + [ 2, 8, 37 ], + [ 3, 0, 38 ], + [ 4, 0, 46 ], + [ 4, 1, 47 ], + [ 4, 2, 48 ], + [ 4, 3, 49 ], + [ 4, 4, 50 ], + [ 4, 5, 51 ], + [ 4, 6, 52 ], + [ 4, 7, 53 ], + [ 4, 8, 54 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.1.snap new file mode 100644 index 00000000000..dbdb110e770 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__118759__enable_multiple_text_editor_decorations_in_empty_lines.1.snap @@ -0,0 +1 @@ +[ [ 2, 0, 0 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.1.snap new file mode 100644 index 00000000000..feeac693075 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__119416__Delete_Control_Character__U_007F_____127___displayed_as_space.1.snap @@ -0,0 +1,10 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.1.snap new file mode 100644 index 00000000000..600d31e6eb2 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__124038__Multiple_end-of-line_text_decorations_get_merged.1.snap @@ -0,0 +1,9 @@ +[ + [ 0, 0, 0 ], + [ 0, 2, 1 ], + [ 0, 4, 2 ], + [ 0, 6, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 3, 0, 6 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.1.snap new file mode 100644 index 00000000000..82910fe3128 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__136622__Inline_decorations_are_not_rendering_on_non-ASCII_lines_when_renderControlCharacters_is_on.1.snap @@ -0,0 +1,14 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 1, 0, 4 ], + [ 4, 0, 5 ], + [ 4, 1, 6 ], + [ 4, 2, 7 ], + [ 4, 3, 8 ], + [ 4, 4, 9 ], + [ 4, 5, 10 ], + [ 4, 6, 11 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.1.snap new file mode 100644 index 00000000000..9217bb74922 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__18616__Inline_decorations_ending_at_the_text_length_are_no_longer_rendered.1.snap @@ -0,0 +1,24 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.1.snap new file mode 100644 index 00000000000..f368300b431 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__19207__Link_in_Monokai_is_not_rendered_correctly.1.snap @@ -0,0 +1,87 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 1, 0, 12 ], + [ 1, 1, 13 ], + [ 1, 2, 14 ], + [ 1, 3, 15 ], + [ 1, 4, 16 ], + [ 1, 5, 17 ], + [ 1, 6, 18 ], + [ 1, 7, 19 ], + [ 1, 8, 20 ], + [ 1, 9, 21 ], + [ 1, 10, 22 ], + [ 1, 11, 23 ], + [ 1, 12, 24 ], + [ 1, 13, 25 ], + [ 1, 14, 26 ], + [ 1, 15, 27 ], + [ 1, 16, 28 ], + [ 1, 17, 29 ], + [ 1, 18, 30 ], + [ 1, 19, 31 ], + [ 1, 20, 32 ], + [ 1, 21, 33 ], + [ 1, 22, 34 ], + [ 1, 23, 35 ], + [ 1, 24, 36 ], + [ 1, 25, 37 ], + [ 1, 26, 38 ], + [ 1, 27, 39 ], + [ 1, 28, 40 ], + [ 1, 29, 41 ], + [ 1, 30, 42 ], + [ 1, 31, 43 ], + [ 1, 32, 44 ], + [ 1, 33, 45 ], + [ 1, 34, 46 ], + [ 1, 35, 47 ], + [ 1, 36, 48 ], + [ 2, 0, 49 ], + [ 3, 0, 50 ], + [ 4, 0, 51 ], + [ 4, 1, 52 ], + [ 4, 2, 53 ], + [ 4, 3, 54 ], + [ 4, 4, 55 ], + [ 4, 5, 56 ], + [ 4, 6, 57 ], + [ 4, 7, 58 ], + [ 4, 8, 59 ], + [ 4, 9, 60 ], + [ 4, 10, 61 ], + [ 4, 11, 62 ], + [ 4, 12, 63 ], + [ 4, 13, 64 ], + [ 4, 14, 65 ], + [ 4, 15, 66 ], + [ 4, 16, 67 ], + [ 4, 17, 68 ], + [ 4, 18, 69 ], + [ 4, 19, 70 ], + [ 4, 20, 71 ], + [ 5, 0, 72 ], + [ 5, 1, 73 ], + [ 6, 0, 74 ], + [ 6, 1, 75 ], + [ 6, 2, 76 ], + [ 6, 3, 77 ], + [ 6, 4, 78 ], + [ 6, 5, 79 ], + [ 6, 6, 80 ], + [ 6, 7, 81 ], + [ 6, 8, 82 ], + [ 6, 9, 83 ], + [ 6, 10, 84 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.1.snap new file mode 100644 index 00000000000..8a1654f9a5e --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__COMBINING_ACUTE_ACCENT__U_0301_.1.snap @@ -0,0 +1,56 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 0, 45, 45 ], + [ 0, 46, 46 ], + [ 0, 47, 47 ], + [ 0, 48, 48 ], + [ 0, 49, 49 ], + [ 0, 50, 50 ], + [ 0, 51, 51 ], + [ 0, 52, 52 ], + [ 0, 53, 53 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.1.snap new file mode 100644 index 00000000000..aaba3fc53ab --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22352__Partially_Broken_Complex_Script_Rendering_of_Tamil.1.snap @@ -0,0 +1,103 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 0, 45, 45 ], + [ 1, 0, 46 ], + [ 1, 1, 47 ], + [ 1, 2, 48 ], + [ 1, 3, 49 ], + [ 1, 4, 50 ], + [ 1, 5, 51 ], + [ 1, 6, 52 ], + [ 1, 7, 53 ], + [ 1, 8, 54 ], + [ 1, 9, 55 ], + [ 1, 10, 56 ], + [ 1, 11, 57 ], + [ 1, 12, 58 ], + [ 1, 13, 59 ], + [ 1, 14, 60 ], + [ 1, 15, 61 ], + [ 1, 16, 62 ], + [ 1, 17, 63 ], + [ 1, 18, 64 ], + [ 1, 19, 65 ], + [ 1, 20, 66 ], + [ 1, 21, 67 ], + [ 1, 22, 68 ], + [ 1, 23, 69 ], + [ 1, 24, 70 ], + [ 1, 25, 71 ], + [ 1, 26, 72 ], + [ 1, 27, 73 ], + [ 1, 28, 74 ], + [ 1, 29, 75 ], + [ 1, 30, 76 ], + [ 1, 31, 77 ], + [ 1, 32, 78 ], + [ 1, 33, 79 ], + [ 1, 34, 80 ], + [ 1, 35, 81 ], + [ 1, 36, 82 ], + [ 1, 37, 83 ], + [ 1, 38, 84 ], + [ 1, 39, 85 ], + [ 1, 40, 86 ], + [ 1, 41, 87 ], + [ 1, 42, 88 ], + [ 1, 43, 89 ], + [ 1, 44, 90 ], + [ 1, 45, 91 ], + [ 2, 0, 92 ], + [ 2, 1, 93 ], + [ 2, 2, 94 ], + [ 2, 3, 95 ], + [ 2, 4, 96 ], + [ 2, 5, 97 ], + [ 2, 6, 98 ], + [ 2, 7, 99 ], + [ 2, 8, 100 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.1.snap new file mode 100644 index 00000000000..c516a970264 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs.1.snap @@ -0,0 +1,18 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 9 ], + [ 0, 9, 10 ], + [ 0, 11, 12 ], + [ 0, 15, 16 ], + [ 0, 16, 17 ], + [ 0, 17, 18 ], + [ 0, 18, 19 ], + [ 0, 19, 20 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.1.snap new file mode 100644 index 00000000000..d2f1c4d5585 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__22832__Consider_fullwidth_characters_when_rendering_tabs__render_whitespace_.1.snap @@ -0,0 +1,18 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 1, 0, 3 ], + [ 2, 0, 4 ], + [ 3, 0, 5 ], + [ 4, 0, 6 ], + [ 4, 1, 7 ], + [ 4, 2, 9 ], + [ 5, 0, 10 ], + [ 5, 2, 12 ], + [ 6, 0, 16 ], + [ 6, 1, 17 ], + [ 6, 2, 18 ], + [ 6, 3, 19 ], + [ 6, 4, 20 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.1.snap new file mode 100644 index 00000000000..26a4082589a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__30133__Empty_lines_don_t_render_inline_decorations.1.snap @@ -0,0 +1 @@ +[ [ 1, 0, 0 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.1.snap new file mode 100644 index 00000000000..63261b72d68 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__32436__Non-monospace_font___visible_whitespace___After_decorator_causes_line_to__jump_.1.snap @@ -0,0 +1 @@ +[ [ 0, 0, 0 ], [ 1, 0, 4 ], [ 2, 0, 5 ], [ 2, 1, 6 ], [ 2, 2, 7 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.1.snap new file mode 100644 index 00000000000..bf89aea1861 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations.1.snap @@ -0,0 +1,197 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 1, 0, 45 ], + [ 1, 1, 46 ], + [ 1, 2, 47 ], + [ 1, 3, 48 ], + [ 1, 4, 49 ], + [ 1, 5, 50 ], + [ 1, 6, 51 ], + [ 1, 7, 52 ], + [ 1, 8, 53 ], + [ 1, 9, 54 ], + [ 1, 10, 55 ], + [ 1, 11, 56 ], + [ 1, 12, 57 ], + [ 1, 13, 58 ], + [ 1, 14, 59 ], + [ 1, 15, 60 ], + [ 1, 16, 61 ], + [ 1, 17, 62 ], + [ 1, 18, 63 ], + [ 1, 19, 64 ], + [ 1, 20, 65 ], + [ 1, 21, 66 ], + [ 1, 22, 67 ], + [ 1, 23, 68 ], + [ 1, 24, 69 ], + [ 1, 25, 70 ], + [ 1, 26, 71 ], + [ 1, 27, 72 ], + [ 1, 28, 73 ], + [ 1, 29, 74 ], + [ 1, 30, 75 ], + [ 1, 31, 76 ], + [ 1, 32, 77 ], + [ 1, 33, 78 ], + [ 1, 34, 79 ], + [ 1, 35, 80 ], + [ 1, 36, 81 ], + [ 1, 37, 82 ], + [ 1, 38, 83 ], + [ 1, 39, 84 ], + [ 1, 40, 85 ], + [ 1, 41, 86 ], + [ 1, 42, 87 ], + [ 1, 43, 88 ], + [ 1, 44, 89 ], + [ 2, 0, 90 ], + [ 2, 1, 91 ], + [ 2, 2, 92 ], + [ 2, 3, 93 ], + [ 2, 4, 94 ], + [ 2, 5, 95 ], + [ 2, 6, 96 ], + [ 2, 7, 97 ], + [ 2, 8, 98 ], + [ 2, 9, 99 ], + [ 2, 10, 100 ], + [ 2, 11, 101 ], + [ 2, 12, 102 ], + [ 2, 13, 103 ], + [ 2, 14, 104 ], + [ 2, 15, 105 ], + [ 2, 16, 106 ], + [ 2, 17, 107 ], + [ 2, 18, 108 ], + [ 2, 19, 109 ], + [ 2, 20, 110 ], + [ 2, 21, 111 ], + [ 2, 22, 112 ], + [ 2, 23, 113 ], + [ 2, 24, 114 ], + [ 2, 25, 115 ], + [ 2, 26, 116 ], + [ 2, 27, 117 ], + [ 2, 28, 118 ], + [ 2, 29, 119 ], + [ 2, 30, 120 ], + [ 2, 31, 121 ], + [ 2, 32, 122 ], + [ 2, 33, 123 ], + [ 2, 34, 124 ], + [ 2, 35, 125 ], + [ 2, 36, 126 ], + [ 2, 37, 127 ], + [ 2, 38, 128 ], + [ 2, 39, 129 ], + [ 2, 40, 130 ], + [ 2, 41, 131 ], + [ 2, 42, 132 ], + [ 2, 43, 133 ], + [ 2, 44, 134 ], + [ 3, 0, 135 ], + [ 3, 1, 136 ], + [ 3, 2, 137 ], + [ 3, 3, 138 ], + [ 3, 4, 139 ], + [ 3, 5, 140 ], + [ 3, 6, 141 ], + [ 3, 7, 142 ], + [ 3, 8, 143 ], + [ 3, 9, 144 ], + [ 3, 10, 145 ], + [ 3, 11, 146 ], + [ 3, 12, 147 ], + [ 3, 13, 148 ], + [ 3, 14, 149 ], + [ 3, 15, 150 ], + [ 3, 16, 151 ], + [ 3, 17, 152 ], + [ 3, 18, 153 ], + [ 3, 19, 154 ], + [ 3, 20, 155 ], + [ 3, 21, 156 ], + [ 3, 22, 157 ], + [ 3, 23, 158 ], + [ 3, 24, 159 ], + [ 3, 25, 160 ], + [ 3, 26, 161 ], + [ 3, 27, 162 ], + [ 3, 28, 163 ], + [ 3, 29, 164 ], + [ 3, 30, 165 ], + [ 3, 31, 166 ], + [ 3, 32, 167 ], + [ 3, 33, 168 ], + [ 3, 34, 169 ], + [ 3, 35, 170 ], + [ 3, 36, 171 ], + [ 3, 37, 172 ], + [ 3, 38, 173 ], + [ 3, 39, 174 ], + [ 3, 40, 175 ], + [ 3, 41, 176 ], + [ 3, 42, 177 ], + [ 3, 43, 178 ], + [ 3, 44, 179 ], + [ 4, 0, 180 ], + [ 4, 1, 181 ], + [ 4, 2, 182 ], + [ 4, 3, 183 ], + [ 4, 4, 184 ], + [ 4, 5, 185 ], + [ 4, 6, 186 ], + [ 4, 7, 187 ], + [ 4, 8, 188 ], + [ 4, 9, 189 ], + [ 4, 10, 190 ], + [ 4, 11, 191 ], + [ 4, 12, 192 ], + [ 4, 13, 193 ], + [ 4, 14, 194 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.1.snap new file mode 100644 index 00000000000..fd2a868be18 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__33525__Long_line_with_ligatures_takes_a_long_time_to_paint_decorations_-_not_possible.1.snap @@ -0,0 +1,159 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 0, 45, 45 ], + [ 0, 46, 46 ], + [ 0, 47, 47 ], + [ 0, 48, 48 ], + [ 0, 49, 49 ], + [ 0, 50, 50 ], + [ 0, 51, 51 ], + [ 0, 52, 52 ], + [ 0, 53, 53 ], + [ 0, 54, 54 ], + [ 0, 55, 55 ], + [ 0, 56, 56 ], + [ 0, 57, 57 ], + [ 0, 58, 58 ], + [ 0, 59, 59 ], + [ 0, 60, 60 ], + [ 0, 61, 61 ], + [ 0, 62, 62 ], + [ 0, 63, 63 ], + [ 0, 64, 64 ], + [ 0, 65, 65 ], + [ 0, 66, 66 ], + [ 0, 67, 67 ], + [ 0, 68, 68 ], + [ 0, 69, 69 ], + [ 0, 70, 70 ], + [ 0, 71, 71 ], + [ 0, 72, 72 ], + [ 0, 73, 73 ], + [ 0, 74, 74 ], + [ 0, 75, 75 ], + [ 0, 76, 76 ], + [ 0, 77, 77 ], + [ 0, 78, 78 ], + [ 0, 79, 79 ], + [ 0, 80, 80 ], + [ 0, 81, 81 ], + [ 0, 82, 82 ], + [ 0, 83, 83 ], + [ 0, 84, 84 ], + [ 0, 85, 85 ], + [ 0, 86, 86 ], + [ 0, 87, 87 ], + [ 0, 88, 88 ], + [ 0, 89, 89 ], + [ 0, 90, 90 ], + [ 0, 91, 91 ], + [ 0, 92, 92 ], + [ 0, 93, 93 ], + [ 0, 94, 94 ], + [ 0, 95, 95 ], + [ 0, 96, 96 ], + [ 0, 97, 97 ], + [ 0, 98, 98 ], + [ 0, 99, 99 ], + [ 0, 100, 100 ], + [ 0, 101, 101 ], + [ 0, 102, 102 ], + [ 0, 103, 103 ], + [ 0, 104, 104 ], + [ 0, 105, 105 ], + [ 0, 106, 106 ], + [ 0, 107, 107 ], + [ 0, 108, 108 ], + [ 0, 109, 109 ], + [ 0, 110, 110 ], + [ 0, 111, 111 ], + [ 0, 112, 112 ], + [ 0, 113, 113 ], + [ 0, 114, 114 ], + [ 0, 115, 115 ], + [ 0, 116, 116 ], + [ 0, 117, 117 ], + [ 0, 118, 118 ], + [ 0, 119, 119 ], + [ 0, 120, 120 ], + [ 0, 121, 121 ], + [ 0, 122, 122 ], + [ 0, 123, 123 ], + [ 0, 124, 124 ], + [ 0, 125, 125 ], + [ 0, 126, 126 ], + [ 0, 127, 127 ], + [ 0, 128, 128 ], + [ 0, 129, 129 ], + [ 0, 130, 130 ], + [ 0, 131, 131 ], + [ 0, 132, 132 ], + [ 0, 133, 133 ], + [ 0, 134, 134 ], + [ 0, 135, 135 ], + [ 0, 136, 136 ], + [ 0, 137, 137 ], + [ 0, 138, 138 ], + [ 0, 139, 139 ], + [ 0, 140, 140 ], + [ 0, 141, 141 ], + [ 0, 142, 142 ], + [ 0, 143, 143 ], + [ 0, 144, 144 ], + [ 0, 145, 145 ], + [ 0, 146, 146 ], + [ 0, 147, 147 ], + [ 0, 148, 148 ], + [ 0, 149, 149 ], + [ 0, 150, 150 ], + [ 0, 151, 151 ], + [ 0, 152, 152 ], + [ 0, 153, 153 ], + [ 0, 154, 154 ], + [ 0, 155, 155 ], + [ 0, 156, 156 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.1.snap new file mode 100644 index 00000000000..d091ac3c06e --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37208__Collapsing_bullet_point_containing_emoji_in_Markdown_document_results_in______character.1.snap @@ -0,0 +1,10 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 1, 0, 5 ], + [ 1, 1, 6 ], + [ 1, 2, 7 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.1.snap new file mode 100644 index 00000000000..26a4082589a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__37401__40127__Allow_both_before_and_after_decorations_on_empty_line.1.snap @@ -0,0 +1 @@ +[ [ 1, 0, 0 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.1.snap new file mode 100644 index 00000000000..520dea3febc --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38123__editor_renderWhitespace___boundary__renders_whitespace_at_line_wrap_point_when_line_is_wrapped.1.snap @@ -0,0 +1,62 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 0, 45, 45 ], + [ 0, 46, 46 ], + [ 0, 47, 47 ], + [ 0, 48, 48 ], + [ 0, 49, 49 ], + [ 1, 0, 50 ], + [ 1, 1, 51 ], + [ 1, 2, 52 ], + [ 1, 3, 53 ], + [ 1, 4, 54 ], + [ 1, 5, 55 ], + [ 1, 6, 56 ], + [ 1, 7, 57 ], + [ 2, 0, 58 ], + [ 2, 1, 59 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.1.snap new file mode 100644 index 00000000000..4242465290e --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__38935__GitLens_end-of-line_blame_no_longer_rendering.1.snap @@ -0,0 +1 @@ +[ [ 0, 0, 0 ], [ 0, 4, 4 ], [ 2, 0, 5 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.1.snap new file mode 100644 index 00000000000..c3c8e1e2542 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__42700__Hindi_characters_are_not_being_rendered_properly.1.snap @@ -0,0 +1,108 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ], + [ 0, 39, 39 ], + [ 0, 40, 40 ], + [ 0, 41, 41 ], + [ 0, 42, 42 ], + [ 0, 43, 43 ], + [ 0, 44, 44 ], + [ 0, 45, 45 ], + [ 0, 46, 46 ], + [ 0, 47, 47 ], + [ 0, 48, 48 ], + [ 0, 49, 49 ], + [ 0, 50, 50 ], + [ 1, 0, 51 ], + [ 1, 1, 52 ], + [ 1, 2, 53 ], + [ 1, 3, 54 ], + [ 1, 4, 55 ], + [ 1, 5, 56 ], + [ 1, 6, 57 ], + [ 1, 7, 58 ], + [ 1, 8, 59 ], + [ 1, 9, 60 ], + [ 1, 10, 61 ], + [ 1, 11, 62 ], + [ 1, 12, 63 ], + [ 1, 13, 64 ], + [ 1, 14, 65 ], + [ 1, 15, 66 ], + [ 1, 16, 67 ], + [ 1, 17, 68 ], + [ 1, 18, 69 ], + [ 1, 19, 70 ], + [ 1, 20, 71 ], + [ 1, 21, 72 ], + [ 1, 22, 73 ], + [ 1, 23, 74 ], + [ 1, 24, 75 ], + [ 1, 25, 76 ], + [ 1, 26, 77 ], + [ 1, 27, 78 ], + [ 1, 28, 79 ], + [ 1, 29, 80 ], + [ 1, 30, 81 ], + [ 1, 31, 82 ], + [ 1, 32, 83 ], + [ 1, 33, 84 ], + [ 1, 34, 85 ], + [ 1, 35, 86 ], + [ 1, 36, 87 ], + [ 1, 37, 88 ], + [ 1, 38, 89 ], + [ 1, 39, 90 ], + [ 1, 40, 91 ], + [ 1, 41, 92 ], + [ 1, 42, 93 ], + [ 1, 43, 94 ], + [ 1, 44, 95 ], + [ 1, 45, 96 ], + [ 1, 46, 97 ], + [ 1, 47, 98 ], + [ 1, 48, 99 ], + [ 1, 49, 100 ], + [ 1, 50, 101 ], + [ 2, 0, 102 ], + [ 2, 1, 103 ], + [ 2, 2, 104 ], + [ 2, 3, 105 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.1.snap new file mode 100644 index 00000000000..c389c876822 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_issue__91936__Semantic_token_color_highlighting_fails_on_line_with_selected_text.1.snap @@ -0,0 +1,50 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 2, 0, 2 ], + [ 3, 0, 3 ], + [ 4, 0, 4 ], + [ 5, 0, 5 ], + [ 6, 0, 6 ], + [ 7, 0, 7 ], + [ 8, 0, 8 ], + [ 9, 0, 9 ], + [ 10, 0, 10 ], + [ 11, 0, 11 ], + [ 12, 0, 12 ], + [ 13, 0, 13 ], + [ 14, 0, 14 ], + [ 15, 0, 15 ], + [ 16, 0, 16 ], + [ 17, 0, 17 ], + [ 18, 0, 18 ], + [ 19, 0, 19 ], + [ 20, 0, 20 ], + [ 20, 1, 21 ], + [ 20, 2, 22 ], + [ 20, 3, 23 ], + [ 21, 0, 24 ], + [ 22, 0, 25 ], + [ 22, 1, 26 ], + [ 23, 0, 27 ], + [ 24, 0, 28 ], + [ 25, 0, 29 ], + [ 25, 1, 30 ], + [ 26, 0, 31 ], + [ 27, 0, 32 ], + [ 28, 0, 33 ], + [ 29, 0, 34 ], + [ 29, 1, 35 ], + [ 30, 0, 36 ], + [ 31, 0, 37 ], + [ 32, 0, 38 ], + [ 32, 1, 39 ], + [ 32, 2, 40 ], + [ 32, 3, 41 ], + [ 33, 0, 42 ], + [ 34, 0, 43 ], + [ 34, 1, 44 ], + [ 34, 2, 45 ], + [ 34, 3, 46 ], + [ 34, 4, 47 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.1.snap new file mode 100644 index 00000000000..6e736057840 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__137036__Issue_in_RTL_languages_in_recent_versions.1.snap @@ -0,0 +1,43 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 1, 3, 4 ], + [ 1, 4, 5 ], + [ 1, 5, 6 ], + [ 2, 0, 7 ], + [ 3, 0, 8 ], + [ 3, 1, 9 ], + [ 3, 2, 10 ], + [ 3, 3, 11 ], + [ 3, 4, 12 ], + [ 4, 0, 13 ], + [ 5, 0, 14 ], + [ 5, 1, 15 ], + [ 5, 2, 16 ], + [ 5, 3, 17 ], + [ 5, 4, 18 ], + [ 5, 5, 19 ], + [ 5, 6, 20 ], + [ 5, 7, 21 ], + [ 5, 8, 22 ], + [ 6, 0, 23 ], + [ 7, 0, 24 ], + [ 7, 1, 25 ], + [ 7, 2, 26 ], + [ 7, 3, 27 ], + [ 7, 4, 28 ], + [ 7, 5, 29 ], + [ 7, 6, 30 ], + [ 8, 0, 31 ], + [ 8, 1, 32 ], + [ 9, 0, 33 ], + [ 9, 1, 34 ], + [ 9, 2, 35 ], + [ 9, 3, 36 ], + [ 9, 4, 37 ], + [ 9, 5, 38 ], + [ 10, 0, 39 ], + [ 10, 1, 40 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.1.snap new file mode 100644 index 00000000000..523664e1a6a --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__19673__Monokai_Theme_bad-highlighting_in_line_wrap.1.snap @@ -0,0 +1,37 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 1, 2, 6 ], + [ 1, 3, 7 ], + [ 1, 4, 8 ], + [ 1, 5, 9 ], + [ 1, 6, 10 ], + [ 1, 7, 11 ], + [ 1, 8, 12 ], + [ 1, 9, 13 ], + [ 1, 10, 14 ], + [ 1, 11, 15 ], + [ 1, 12, 16 ], + [ 2, 0, 17 ], + [ 3, 0, 18 ], + [ 3, 1, 19 ], + [ 3, 2, 20 ], + [ 3, 3, 21 ], + [ 3, 4, 22 ], + [ 3, 5, 23 ], + [ 4, 0, 24 ], + [ 4, 1, 25 ], + [ 5, 0, 26 ], + [ 6, 0, 27 ], + [ 7, 0, 28 ], + [ 7, 1, 29 ], + [ 7, 2, 30 ], + [ 7, 3, 31 ], + [ 8, 0, 32 ], + [ 8, 1, 33 ], + [ 8, 2, 34 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.1.snap new file mode 100644 index 00000000000..0f9af669e60 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_1.1.snap @@ -0,0 +1,71 @@ +[ + [ 0, 0, 0 ], + [ 0, 4, 4 ], + [ 0, 8, 8 ], + [ 1, 0, 12 ], + [ 1, 1, 13 ], + [ 1, 2, 14 ], + [ 1, 3, 15 ], + [ 1, 4, 16 ], + [ 1, 5, 17 ], + [ 1, 6, 18 ], + [ 1, 7, 19 ], + [ 1, 8, 20 ], + [ 1, 9, 21 ], + [ 1, 10, 22 ], + [ 1, 11, 23 ], + [ 2, 0, 24 ], + [ 2, 4, 28 ], + [ 2, 8, 32 ], + [ 2, 12, 36 ], + [ 2, 16, 40 ], + [ 2, 20, 44 ], + [ 3, 0, 48 ], + [ 4, 0, 49 ], + [ 4, 1, 50 ], + [ 4, 2, 51 ], + [ 4, 3, 52 ], + [ 4, 4, 53 ], + [ 4, 5, 54 ], + [ 4, 6, 55 ], + [ 4, 7, 56 ], + [ 4, 8, 57 ], + [ 4, 9, 58 ], + [ 4, 10, 59 ], + [ 4, 11, 60 ], + [ 4, 12, 61 ], + [ 4, 13, 62 ], + [ 4, 14, 63 ], + [ 4, 15, 64 ], + [ 4, 16, 65 ], + [ 4, 17, 66 ], + [ 4, 18, 67 ], + [ 4, 19, 68 ], + [ 4, 20, 69 ], + [ 5, 0, 70 ], + [ 5, 1, 71 ], + [ 6, 0, 72 ], + [ 7, 0, 73 ], + [ 7, 1, 74 ], + [ 7, 2, 75 ], + [ 7, 3, 76 ], + [ 7, 4, 77 ], + [ 7, 5, 78 ], + [ 7, 6, 79 ], + [ 7, 7, 80 ], + [ 7, 8, 81 ], + [ 7, 9, 82 ], + [ 7, 10, 83 ], + [ 7, 11, 84 ], + [ 7, 12, 85 ], + [ 7, 13, 86 ], + [ 7, 14, 87 ], + [ 7, 15, 88 ], + [ 7, 16, 89 ], + [ 7, 17, 90 ], + [ 7, 18, 91 ], + [ 7, 19, 92 ], + [ 8, 0, 93 ], + [ 9, 0, 94 ], + [ 9, 1, 95 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.1.snap new file mode 100644 index 00000000000..ba063e0187f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__2255__Weird_line_rendering_part_2.1.snap @@ -0,0 +1,72 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 4, 4 ], + [ 0, 8, 8 ], + [ 1, 0, 12 ], + [ 1, 1, 13 ], + [ 1, 2, 14 ], + [ 1, 3, 15 ], + [ 1, 4, 16 ], + [ 1, 5, 17 ], + [ 1, 6, 18 ], + [ 1, 7, 19 ], + [ 1, 8, 20 ], + [ 1, 9, 21 ], + [ 1, 10, 22 ], + [ 1, 11, 23 ], + [ 2, 0, 24 ], + [ 2, 4, 28 ], + [ 2, 8, 32 ], + [ 2, 12, 36 ], + [ 2, 16, 40 ], + [ 2, 20, 44 ], + [ 3, 0, 48 ], + [ 4, 0, 49 ], + [ 4, 1, 50 ], + [ 4, 2, 51 ], + [ 4, 3, 52 ], + [ 4, 4, 53 ], + [ 4, 5, 54 ], + [ 4, 6, 55 ], + [ 4, 7, 56 ], + [ 4, 8, 57 ], + [ 4, 9, 58 ], + [ 4, 10, 59 ], + [ 4, 11, 60 ], + [ 4, 12, 61 ], + [ 4, 13, 62 ], + [ 4, 14, 63 ], + [ 4, 15, 64 ], + [ 4, 16, 65 ], + [ 4, 17, 66 ], + [ 4, 18, 67 ], + [ 4, 19, 68 ], + [ 4, 20, 69 ], + [ 5, 0, 70 ], + [ 5, 1, 71 ], + [ 6, 0, 72 ], + [ 7, 0, 73 ], + [ 7, 1, 74 ], + [ 7, 2, 75 ], + [ 7, 3, 76 ], + [ 7, 4, 77 ], + [ 7, 5, 78 ], + [ 7, 6, 79 ], + [ 7, 7, 80 ], + [ 7, 8, 81 ], + [ 7, 9, 82 ], + [ 7, 10, 83 ], + [ 7, 11, 84 ], + [ 7, 12, 85 ], + [ 7, 13, 86 ], + [ 7, 14, 87 ], + [ 7, 15, 88 ], + [ 7, 16, 89 ], + [ 7, 17, 90 ], + [ 7, 18, 91 ], + [ 7, 19, 92 ], + [ 8, 0, 93 ], + [ 9, 0, 94 ], + [ 9, 1, 95 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.1.snap new file mode 100644 index 00000000000..164ee1b79d7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__91178__after_decoration_type_shown_before_cursor.1.snap @@ -0,0 +1,19 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 2, 0, 12 ], + [ 3, 1, 13 ], + [ 3, 2, 14 ], + [ 3, 3, 15 ], + [ 3, 4, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.1.snap new file mode 100644 index 00000000000..15a1ec010b9 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__95685__Uses_unicode_replacement_character_for_Paragraph_Separator.1.snap @@ -0,0 +1,41 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ], + [ 0, 13, 13 ], + [ 0, 14, 14 ], + [ 0, 15, 15 ], + [ 0, 16, 16 ], + [ 0, 17, 17 ], + [ 0, 18, 18 ], + [ 0, 19, 19 ], + [ 0, 20, 20 ], + [ 0, 21, 21 ], + [ 0, 22, 22 ], + [ 0, 23, 23 ], + [ 0, 24, 24 ], + [ 0, 25, 25 ], + [ 0, 26, 26 ], + [ 0, 27, 27 ], + [ 0, 28, 28 ], + [ 0, 29, 29 ], + [ 0, 30, 30 ], + [ 0, 31, 31 ], + [ 0, 32, 32 ], + [ 0, 33, 33 ], + [ 0, 34, 34 ], + [ 0, 35, 35 ], + [ 0, 36, 36 ], + [ 0, 37, 37 ], + [ 0, 38, 38 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.1.snap new file mode 100644 index 00000000000..6727228afc7 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.1.snap @@ -0,0 +1,38 @@ +[ + [ 0, 0, 0 ], + [ 0, 2, 1 ], + [ 0, 4, 2 ], + [ 0, 6, 3 ], + [ 1, 0, 4 ], + [ 2, 0, 5 ], + [ 2, 1, 6 ], + [ 2, 2, 7 ], + [ 2, 3, 8 ], + [ 2, 4, 9 ], + [ 2, 5, 10 ], + [ 2, 6, 11 ], + [ 2, 7, 12 ], + [ 2, 8, 13 ], + [ 2, 9, 14 ], + [ 2, 10, 15 ], + [ 2, 11, 16 ], + [ 2, 12, 17 ], + [ 2, 13, 18 ], + [ 2, 14, 19 ], + [ 2, 15, 20 ], + [ 3, 0, 21 ], + [ 4, 0, 22 ], + [ 4, 1, 23 ], + [ 4, 2, 24 ], + [ 4, 3, 25 ], + [ 4, 4, 26 ], + [ 4, 5, 27 ], + [ 4, 6, 28 ], + [ 4, 7, 29 ], + [ 4, 8, 30 ], + [ 4, 9, 31 ], + [ 4, 10, 32 ], + [ 4, 11, 33 ], + [ 5, 0, 34 ], + [ 5, 1, 35 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.1.snap new file mode 100644 index 00000000000..96407b8dcaa --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.1.snap @@ -0,0 +1,70 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 1, 0, 3 ], + [ 1, 1, 4 ], + [ 1, 2, 5 ], + [ 1, 3, 6 ], + [ 1, 4, 7 ], + [ 1, 5, 8 ], + [ 1, 6, 9 ], + [ 1, 7, 10 ], + [ 1, 8, 11 ], + [ 1, 9, 12 ], + [ 2, 0, 13 ], + [ 2, 1, 14 ], + [ 2, 2, 15 ], + [ 2, 3, 16 ], + [ 2, 4, 17 ], + [ 2, 5, 18 ], + [ 2, 6, 19 ], + [ 2, 7, 20 ], + [ 2, 8, 21 ], + [ 2, 9, 22 ], + [ 2, 10, 23 ], + [ 2, 11, 24 ], + [ 2, 12, 25 ], + [ 2, 13, 26 ], + [ 2, 14, 27 ], + [ 2, 15, 28 ], + [ 2, 16, 29 ], + [ 2, 17, 30 ], + [ 2, 18, 31 ], + [ 2, 19, 32 ], + [ 2, 20, 33 ], + [ 2, 21, 34 ], + [ 2, 22, 35 ], + [ 2, 23, 36 ], + [ 2, 24, 37 ], + [ 2, 25, 38 ], + [ 2, 26, 39 ], + [ 2, 27, 40 ], + [ 2, 28, 41 ], + [ 2, 29, 42 ], + [ 2, 30, 43 ], + [ 2, 31, 44 ], + [ 2, 32, 45 ], + [ 2, 33, 46 ], + [ 2, 34, 47 ], + [ 2, 35, 48 ], + [ 2, 36, 49 ], + [ 2, 37, 50 ], + [ 2, 38, 51 ], + [ 2, 39, 52 ], + [ 2, 40, 53 ], + [ 2, 41, 54 ], + [ 2, 42, 55 ], + [ 2, 43, 56 ], + [ 2, 44, 57 ], + [ 2, 45, 58 ], + [ 2, 46, 59 ], + [ 2, 47, 60 ], + [ 2, 48, 61 ], + [ 2, 49, 62 ], + [ 2, 50, 63 ], + [ 2, 51, 64 ], + [ 2, 52, 65 ], + [ 3, 0, 66 ], + [ 3, 1, 67 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.1.snap new file mode 100644 index 00000000000..646565bf587 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_overflow.1.snap @@ -0,0 +1,9 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 2, 0, 2 ], + [ 3, 0, 3 ], + [ 4, 0, 4 ], + [ 5, 0, 5 ], + [ 5, 1, 6 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.1.snap new file mode 100644 index 00000000000..9c9447a3ccc --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_typical_line.1.snap @@ -0,0 +1,51 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 4 ], + [ 1, 2, 5 ], + [ 1, 4, 6 ], + [ 1, 6, 7 ], + [ 2, 0, 8 ], + [ 2, 1, 9 ], + [ 2, 2, 10 ], + [ 2, 3, 11 ], + [ 2, 4, 12 ], + [ 2, 5, 13 ], + [ 3, 0, 14 ], + [ 4, 0, 15 ], + [ 4, 1, 16 ], + [ 4, 2, 17 ], + [ 4, 3, 18 ], + [ 4, 4, 19 ], + [ 5, 0, 20 ], + [ 6, 0, 21 ], + [ 6, 1, 22 ], + [ 6, 2, 23 ], + [ 6, 3, 24 ], + [ 7, 0, 25 ], + [ 8, 0, 26 ], + [ 9, 0, 27 ], + [ 10, 0, 28 ], + [ 10, 1, 29 ], + [ 10, 2, 30 ], + [ 11, 0, 31 ], + [ 11, 1, 32 ], + [ 11, 2, 33 ], + [ 11, 3, 34 ], + [ 11, 4, 35 ], + [ 11, 5, 36 ], + [ 11, 6, 37 ], + [ 11, 7, 38 ], + [ 11, 8, 39 ], + [ 11, 9, 40 ], + [ 11, 10, 41 ], + [ 11, 11, 42 ], + [ 11, 12, 43 ], + [ 11, 13, 44 ], + [ 11, 14, 45 ], + [ 12, 0, 46 ], + [ 12, 2, 47 ], + [ 13, 0, 48 ], + [ 13, 2, 49 ], + [ 13, 4, 50 ], + [ 13, 6, 51 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 0699a97170c..8ad73d70508 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -198,15 +198,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], - [4, 0, 4], - [5, 0, 5], - [5, 1, 6], - ]); + await assertSnapshot(inflated.mapping); }); test('typical line', async () => { @@ -251,22 +243,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], - [2, 0, 8], [2, 1, 9], [2, 2, 10], [2, 3, 11], [2, 4, 12], [2, 5, 13], - [3, 0, 14], - [4, 0, 15], [4, 1, 16], [4, 2, 17], [4, 3, 18], [4, 4, 19], - [5, 0, 20], - [6, 0, 21], [6, 1, 22], [6, 2, 23], [6, 3, 24], - [7, 0, 25], - [8, 0, 26], - [9, 0, 27], - [10, 0, 28], [10, 1, 29], [10, 2, 30], - [11, 0, 31], [11, 1, 32], [11, 2, 33], [11, 3, 34], [11, 4, 35], [11, 5, 36], [11, 6, 37], [11, 7, 38], [11, 8, 39], [11, 9, 40], [11, 10, 41], [11, 11, 42], [11, 12, 43], [11, 13, 44], [11, 14, 45], - [12, 0, 46], [12, 2, 47], - [13, 0, 48], [13, 2, 49], [13, 4, 50], [13, 6, 51], - ]); + await assertSnapshot(inflated.mapping); }); test('issue #2255: Weird line rendering part 1', async () => { @@ -309,18 +286,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 4, 4], [0, 8, 8], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], - [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], - [3, 0, 48], - [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], - [5, 0, 70], [5, 1, 71], - [6, 0, 72], - [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], - [8, 0, 93], - [9, 0, 94], [9, 1, 95], - ]); + await assertSnapshot(inflated.mapping); }); test('issue #2255: Weird line rendering part 2', async () => { @@ -364,17 +330,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 4, 4], [0, 8, 8], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], - [2, 0, 24], [2, 4, 28], [2, 8, 32], [2, 12, 36], [2, 16, 40], [2, 20, 44], - [3, 0, 48], [4, 0, 49], [4, 1, 50], [4, 2, 51], [4, 3, 52], [4, 4, 53], [4, 5, 54], [4, 6, 55], [4, 7, 56], [4, 8, 57], [4, 9, 58], [4, 10, 59], [4, 11, 60], [4, 12, 61], [4, 13, 62], [4, 14, 63], [4, 15, 64], [4, 16, 65], [4, 17, 66], [4, 18, 67], [4, 19, 68], [4, 20, 69], - [5, 0, 70], [5, 1, 71], - [6, 0, 72], - [7, 0, 73], [7, 1, 74], [7, 2, 75], [7, 3, 76], [7, 4, 77], [7, 5, 78], [7, 6, 79], [7, 7, 80], [7, 8, 81], [7, 9, 82], [7, 10, 83], [7, 11, 84], [7, 12, 85], [7, 13, 86], [7, 14, 87], [7, 15, 88], [7, 16, 89], [7, 17, 90], [7, 18, 91], [7, 19, 92], - [8, 0, 93], - [9, 0, 94], [9, 1, 95], - ]); + await assertSnapshot(inflated.mapping); }); test('issue #91178: after decoration type shown before cursor', async () => { @@ -411,11 +367,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], - [2, 0, 12], - [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16] - ]); + await assertSnapshot(inflated.mapping); }); test('issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages', async () => { @@ -452,12 +404,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], - [1, 0, 3], [1, 1, 4], [1, 2, 5], [1, 3, 6], [1, 4, 7], [1, 5, 8], [1, 6, 9], [1, 7, 10], [1, 8, 11], [1, 9, 12], - [2, 0, 13], [2, 1, 14], [2, 2, 15], [2, 3, 16], [2, 4, 17], [2, 5, 18], [2, 6, 19], [2, 7, 20], [2, 8, 21], [2, 9, 22], [2, 10, 23], [2, 11, 24], [2, 12, 25], [2, 13, 26], [2, 14, 27], [2, 15, 28], [2, 16, 29], [2, 17, 30], [2, 18, 31], [2, 19, 32], [2, 20, 33], [2, 21, 34], [2, 22, 35], [2, 23, 36], [2, 24, 37], [2, 25, 38], [2, 26, 39], [2, 27, 40], [2, 28, 41], [2, 29, 42], [2, 30, 43], [2, 31, 44], [2, 32, 45], [2, 33, 46], [2, 34, 47], [2, 35, 48], [2, 36, 49], [2, 37, 50], [2, 38, 51], [2, 39, 52], [2, 40, 53], [2, 41, 54], [2, 42, 55], [2, 43, 56], [2, 44, 57], [2, 45, 58], [2, 46, 59], [2, 47, 60], [2, 48, 61], [2, 49, 62], [2, 50, 63], [2, 51, 64], [2, 52, 65], - [3, 0, 66], [3, 1, 67] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #137036: Issue in RTL languages in recent versions', async () => { @@ -501,19 +448,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], [1, 3, 4], [1, 4, 5], [1, 5, 6], - [2, 0, 7], - [3, 0, 8], [3, 1, 9], [3, 2, 10], [3, 3, 11], [3, 4, 12], - [4, 0, 13], - [5, 0, 14], [5, 1, 15], [5, 2, 16], [5, 3, 17], [5, 4, 18], [5, 5, 19], [5, 6, 20], [5, 7, 21], [5, 8, 22], - [6, 0, 23], - [7, 0, 24], [7, 1, 25], [7, 2, 26], [7, 3, 27], [7, 4, 28], [7, 5, 29], [7, 6, 30], - [8, 0, 31], [8, 1, 32], - [9, 0, 33], [9, 1, 34], [9, 2, 35], [9, 3, 36], [9, 4, 37], [9, 5, 38], - [10, 0, 39], [10, 1, 40] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #99589: Rendering whitespace influences bidi layout', async () => { @@ -551,14 +486,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], - [2, 0, 5], [2, 1, 6], [2, 2, 7], [2, 3, 8], [2, 4, 9], [2, 5, 10], [2, 6, 11], [2, 7, 12], [2, 8, 13], [2, 9, 14], [2, 10, 15], [2, 11, 16], [2, 12, 17], [2, 13, 18], [2, 14, 19], [2, 15, 20], - [3, 0, 21], - [4, 0, 22], [4, 1, 23], [4, 2, 24], [4, 3, 25], [4, 4, 26], [4, 5, 27], [4, 6, 28], [4, 7, 29], [4, 8, 30], [4, 9, 31], [4, 10, 32], [4, 11, 33], - [5, 0, 34], [5, 1, 35] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #6885: Splits large tokens', async () => { @@ -803,14 +731,7 @@ suite('viewLineRenderer.renderLine', () => { )); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #19673: Monokai Theme bad-highlighting in line wrap', async () => { @@ -851,17 +772,7 @@ suite('viewLineRenderer.renderLine', () => { const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7], [1, 4, 8], [1, 5, 9], [1, 6, 10], [1, 7, 11], [1, 8, 12], [1, 9, 13], [1, 10, 14], [1, 11, 15], [1, 12, 16], - [2, 0, 17], - [3, 0, 18], [3, 1, 19], [3, 2, 20], [3, 3, 21], [3, 4, 22], [3, 5, 23], - [4, 0, 24], [4, 1, 25], - [5, 0, 26], - [6, 0, 27], - [7, 0, 28], [7, 1, 29], [7, 2, 30], [7, 3, 31], - [8, 0, 32], [8, 1, 33], [8, 2, 34] - ]); + await assertSnapshot(inflated.mapping); }); }); @@ -953,11 +864,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #19207: Link in Monokai is not rendered correctly', async () => { @@ -996,15 +903,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], - [1, 0, 12], [1, 1, 13], [1, 2, 14], [1, 3, 15], [1, 4, 16], [1, 5, 17], [1, 6, 18], [1, 7, 19], [1, 8, 20], [1, 9, 21], [1, 10, 22], [1, 11, 23], [1, 12, 24], [1, 13, 25], [1, 14, 26], [1, 15, 27], [1, 16, 28], [1, 17, 29], [1, 18, 30], [1, 19, 31], [1, 20, 32], [1, 21, 33], [1, 22, 34], [1, 23, 35], [1, 24, 36], [1, 25, 37], [1, 26, 38], [1, 27, 39], [1, 28, 40], [1, 29, 41], [1, 30, 42], [1, 31, 43], [1, 32, 44], [1, 33, 45], [1, 34, 46], [1, 35, 47], [1, 36, 48], - [2, 0, 49], - [3, 0, 50], - [4, 0, 51], [4, 1, 52], [4, 2, 53], [4, 3, 54], [4, 4, 55], [4, 5, 56], [4, 6, 57], [4, 7, 58], [4, 8, 59], [4, 9, 60], [4, 10, 61], [4, 11, 62], [4, 12, 63], [4, 13, 64], [4, 14, 65], [4, 15, 66], [4, 16, 67], [4, 17, 68], [4, 18, 69], [4, 19, 70], [4, 20, 71], - [5, 0, 72], [5, 1, 73], - [6, 0, 74], [6, 1, 75], [6, 2, 76], [6, 3, 77], [6, 4, 78], [6, 5, 79], [6, 6, 80], [6, 7, 81], [6, 8, 82], [6, 9, 83], [6, 10, 84] - ]); + await assertSnapshot(inflated.mapping); }); test('createLineParts simple', async () => { @@ -1492,14 +1391,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], [2, 1, 3], - [3, 0, 4], [3, 1, 5], - [4, 0, 6], - [5, 0, 7], [5, 1, 8], [5, 2, 9], [5, 3, 10], [5, 4, 11] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #11485: Visible whitespace conflicts with before decorator attachment', async () => { @@ -1532,10 +1424,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 4], [1, 1, 5], [1, 2, 6], [1, 3, 7] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"', async () => { @@ -1568,11 +1457,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 4], - [2, 0, 5], [2, 1, 6], [2, 2, 7] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #30133: Empty lines don\'t render inline decorations', async () => { @@ -1605,9 +1490,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [1, 0, 0] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character', async () => { @@ -1638,10 +1521,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], - [1, 0, 5], [1, 1, 6], [1, 2, 7] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #37401 #40127: Allow both before and after decorations on empty line', async () => { @@ -1675,9 +1555,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [1, 0, 0] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #118759: enable multiple text editor decorations in empty lines', async () => { @@ -1713,9 +1591,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [2, 0, 0] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #38935: GitLens end-of-line blame no longer rendering', async () => { @@ -1749,10 +1625,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 4, 4], - [2, 0, 5] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on', async () => { @@ -1786,11 +1659,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], - [4, 0, 5], [4, 1, 6], [4, 2, 7], [4, 3, 8], [4, 4, 9], [4, 5, 10], [4, 6, 11] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #22832: Consider fullwidth characters when rendering tabs', async () => { @@ -1821,10 +1690,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 9], - [0, 9, 10], [0, 11, 12], [0, 15, 16], [0, 16, 17], [0, 17, 18], [0, 18, 19], [0, 19, 20] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)', async () => { @@ -1855,15 +1721,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], - [1, 0, 3], - [2, 0, 4], - [3, 0, 5], - [4, 0, 6], [4, 1, 7], [4, 2, 9], - [5, 0, 10], [5, 2, 12], - [6, 0, 16], [6, 1, 17], [6, 2, 18], [6, 3, 19], [6, 4, 20] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', async () => { @@ -1894,16 +1752,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #22352: Partially Broken Complex Script Rendering of Tamil', async () => { @@ -1934,22 +1783,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], - [1, 0, 46], [1, 1, 47], [1, 2, 48], [1, 3, 49], [1, 4, 50], [1, 5, 51], [1, 6, 52], [1, 7, 53], - [1, 8, 54], [1, 9, 55], [1, 10, 56], [1, 11, 57], [1, 12, 58], [1, 13, 59], [1, 14, 60], [1, 15, 61], - [1, 16, 62], [1, 17, 63], [1, 18, 64], [1, 19, 65], [1, 20, 66], [1, 21, 67], [1, 22, 68], [1, 23, 69], - [1, 24, 70], [1, 25, 71], [1, 26, 72], [1, 27, 73], [1, 28, 74], [1, 29, 75], [1, 30, 76], [1, 31, 77], - [1, 32, 78], [1, 33, 79], [1, 34, 80], [1, 35, 81], [1, 36, 82], [1, 37, 83], [1, 38, 84], [1, 39, 85], - [1, 40, 86], [1, 41, 87], [1, 42, 88], [1, 43, 89], [1, 44, 90], [1, 45, 91], - [2, 0, 92], [2, 1, 93], [2, 2, 94], [2, 3, 95], [2, 4, 96], [2, 5, 97], [2, 6, 98], [2, 7, 99], [2, 8, 100] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #42700: Hindi characters are not being rendered properly', async () => { @@ -1980,24 +1814,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], - [1, 0, 51], [1, 1, 52], [1, 2, 53], [1, 3, 54], [1, 4, 55], [1, 5, 56], [1, 6, 57], [1, 7, 58], - [1, 8, 59], [1, 9, 60], [1, 10, 61], [1, 11, 62], [1, 12, 63], [1, 13, 64], [1, 14, 65], - [1, 15, 66], [1, 16, 67], [1, 17, 68], [1, 18, 69], [1, 19, 70], [1, 20, 71], [1, 21, 72], - [1, 22, 73], [1, 23, 74], [1, 24, 75], [1, 25, 76], [1, 26, 77], [1, 27, 78], [1, 28, 79], - [1, 29, 80], [1, 30, 81], [1, 31, 82], [1, 32, 83], [1, 33, 84], [1, 34, 85], [1, 35, 86], - [1, 36, 87], [1, 37, 88], [1, 38, 89], [1, 39, 90], [1, 40, 91], [1, 41, 92], [1, 42, 93], - [1, 43, 94], [1, 44, 95], [1, 45, 96], [1, 46, 97], [1, 47, 98], [1, 48, 99], [1, 49, 100], - [1, 50, 101], [2, 0, 102], [2, 1, 103], [2, 2, 104], [2, 3, 105] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped', async () => { @@ -2027,17 +1844,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [1, 0, 50], [1, 1, 51], [1, 2, 52], [1, 3, 53], [1, 4, 54], [1, 5, 55], [1, 6, 56], [1, 7, 57], - [2, 0, 58], [2, 1, 59] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #33525: Long line with ligatures takes a long time to paint decorations', async () => { @@ -2067,40 +1874,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], - [1, 0, 45], [1, 1, 46], [1, 2, 47], [1, 3, 48], [1, 4, 49], [1, 5, 50], [1, 6, 51], - [1, 7, 52], [1, 8, 53], [1, 9, 54], [1, 10, 55], [1, 11, 56], [1, 12, 57], [1, 13, 58], - [1, 14, 59], [1, 15, 60], [1, 16, 61], [1, 17, 62], [1, 18, 63], [1, 19, 64], [1, 20, 65], - [1, 21, 66], [1, 22, 67], [1, 23, 68], [1, 24, 69], [1, 25, 70], [1, 26, 71], [1, 27, 72], - [1, 28, 73], [1, 29, 74], [1, 30, 75], [1, 31, 76], [1, 32, 77], [1, 33, 78], [1, 34, 79], - [1, 35, 80], [1, 36, 81], [1, 37, 82], [1, 38, 83], [1, 39, 84], [1, 40, 85], [1, 41, 86], - [1, 42, 87], [1, 43, 88], [1, 44, 89], - [2, 0, 90], [2, 1, 91], [2, 2, 92], [2, 3, 93], [2, 4, 94], [2, 5, 95], [2, 6, 96], - [2, 7, 97], [2, 8, 98], [2, 9, 99], [2, 10, 100], [2, 11, 101], [2, 12, 102], - [2, 13, 103], [2, 14, 104], [2, 15, 105], [2, 16, 106], [2, 17, 107], [2, 18, 108], - [2, 19, 109], [2, 20, 110], [2, 21, 111], [2, 22, 112], [2, 23, 113], [2, 24, 114], - [2, 25, 115], [2, 26, 116], [2, 27, 117], [2, 28, 118], [2, 29, 119], [2, 30, 120], - [2, 31, 121], [2, 32, 122], [2, 33, 123], [2, 34, 124], [2, 35, 125], [2, 36, 126], - [2, 37, 127], [2, 38, 128], [2, 39, 129], [2, 40, 130], [2, 41, 131], [2, 42, 132], - [2, 43, 133], [2, 44, 134], - [3, 0, 135], [3, 1, 136], [3, 2, 137], [3, 3, 138], [3, 4, 139], [3, 5, 140], [3, 6, 141], - [3, 7, 142], [3, 8, 143], [3, 9, 144], [3, 10, 145], [3, 11, 146], [3, 12, 147], [3, 13, 148], - [3, 14, 149], [3, 15, 150], [3, 16, 151], [3, 17, 152], [3, 18, 153], [3, 19, 154], [3, 20, 155], - [3, 21, 156], [3, 22, 157], [3, 23, 158], [3, 24, 159], [3, 25, 160], [3, 26, 161], [3, 27, 162], - [3, 28, 163], [3, 29, 164], [3, 30, 165], [3, 31, 166], [3, 32, 167], [3, 33, 168], [3, 34, 169], - [3, 35, 170], [3, 36, 171], [3, 37, 172], [3, 38, 173], [3, 39, 174], [3, 40, 175], [3, 41, 176], - [3, 42, 177], [3, 43, 178], [3, 44, 179], - [4, 0, 180], [4, 1, 181], [4, 2, 182], [4, 3, 183], [4, 4, 184], [4, 5, 185], [4, 6, 186], - [4, 7, 187], [4, 8, 188], [4, 9, 189], [4, 10, 190], [4, 11, 191], [4, 12, 192], [4, 13, 193], - [4, 14, 194] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #33525: Long line with ligatures takes a long time to paint decorations - not possible', async () => { @@ -2130,32 +1904,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], [0, 21, 21], - [0, 22, 22], [0, 23, 23], [0, 24, 24], [0, 25, 25], [0, 26, 26], [0, 27, 27], [0, 28, 28], - [0, 29, 29], [0, 30, 30], [0, 31, 31], [0, 32, 32], [0, 33, 33], [0, 34, 34], [0, 35, 35], - [0, 36, 36], [0, 37, 37], [0, 38, 38], [0, 39, 39], [0, 40, 40], [0, 41, 41], [0, 42, 42], - [0, 43, 43], [0, 44, 44], [0, 45, 45], [0, 46, 46], [0, 47, 47], [0, 48, 48], [0, 49, 49], - [0, 50, 50], [0, 51, 51], [0, 52, 52], [0, 53, 53], [0, 54, 54], [0, 55, 55], [0, 56, 56], - [0, 57, 57], [0, 58, 58], [0, 59, 59], [0, 60, 60], [0, 61, 61], [0, 62, 62], [0, 63, 63], - [0, 64, 64], [0, 65, 65], [0, 66, 66], [0, 67, 67], [0, 68, 68], [0, 69, 69], [0, 70, 70], - [0, 71, 71], [0, 72, 72], [0, 73, 73], [0, 74, 74], [0, 75, 75], [0, 76, 76], [0, 77, 77], - [0, 78, 78], [0, 79, 79], [0, 80, 80], [0, 81, 81], [0, 82, 82], [0, 83, 83], [0, 84, 84], - [0, 85, 85], [0, 86, 86], [0, 87, 87], [0, 88, 88], [0, 89, 89], [0, 90, 90], [0, 91, 91], - [0, 92, 92], [0, 93, 93], [0, 94, 94], [0, 95, 95], [0, 96, 96], [0, 97, 97], [0, 98, 98], - [0, 99, 99], [0, 100, 100], [0, 101, 101], [0, 102, 102], [0, 103, 103], [0, 104, 104], - [0, 105, 105], [0, 106, 106], [0, 107, 107], [0, 108, 108], [0, 109, 109], [0, 110, 110], - [0, 111, 111], [0, 112, 112], [0, 113, 113], [0, 114, 114], [0, 115, 115], [0, 116, 116], - [0, 117, 117], [0, 118, 118], [0, 119, 119], [0, 120, 120], [0, 121, 121], [0, 122, 122], - [0, 123, 123], [0, 124, 124], [0, 125, 125], [0, 126, 126], [0, 127, 127], [0, 128, 128], - [0, 129, 129], [0, 130, 130], [0, 131, 131], [0, 132, 132], [0, 133, 133], [0, 134, 134], - [0, 135, 135], [0, 136, 136], [0, 137, 137], [0, 138, 138], [0, 139, 139], [0, 140, 140], - [0, 141, 141], [0, 142, 142], [0, 143, 143], [0, 144, 144], [0, 145, 145], [0, 146, 146], - [0, 147, 147], [0, 148, 148], [0, 149, 149], [0, 150, 150], [0, 151, 151], [0, 152, 152], - [0, 153, 153], [0, 154, 154], [0, 155, 155], [0, 156, 156] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #91936: Semantic token color highlighting fails on line with selected text', async () => { @@ -2204,43 +1953,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], - [4, 0, 4], - [5, 0, 5], - [6, 0, 6], - [7, 0, 7], - [8, 0, 8], - [9, 0, 9], - [10, 0, 10], - [11, 0, 11], - [12, 0, 12], - [13, 0, 13], - [14, 0, 14], - [15, 0, 15], - [16, 0, 16], - [17, 0, 17], - [18, 0, 18], - [19, 0, 19], - [20, 0, 20], [20, 1, 21], [20, 2, 22], [20, 3, 23], - [21, 0, 24], - [22, 0, 25], [22, 1, 26], - [23, 0, 27], - [24, 0, 28], - [25, 0, 29], [25, 1, 30], - [26, 0, 31], - [27, 0, 32], - [28, 0, 33], - [29, 0, 34], [29, 1, 35], - [30, 0, 36], - [31, 0, 37], - [32, 0, 38], [32, 1, 39], [32, 2, 40], [32, 3, 41], - [33, 0, 42], - [34, 0, 43], [34, 1, 44], [34, 2, 45], [34, 3, 46], [34, 4, 47] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #119416: Delete Control Character (U+007F / ) displayed as space', async () => { @@ -2270,9 +1983,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #116939: Important control characters aren\'t rendered', async () => { @@ -2302,16 +2013,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12], [0, 13, 13], [0, 14, 14], - [0, 15, 15], [0, 16, 16], [0, 17, 17], [0, 18, 18], [0, 19, 19], [0, 20, 20], - [1, 0, 21], - [2, 0, 29], [2, 1, 30], [2, 2, 31], [2, 3, 32], [2, 4, 33], [2, 5, 34], [2, 6, 35], - [2, 7, 36], [2, 8, 37], - [3, 0, 38], - [4, 0, 46], [4, 1, 47], [4, 2, 48], [4, 3, 49], [4, 4, 50], [4, 5, 51], [4, 6, 52], [4, 7, 53], [4, 8, 54] - ]); + await assertSnapshot(inflated.mapping); }); test('issue #124038: Multiple end-of-line text decorations get merged', async () => { @@ -2345,11 +2047,7 @@ suite('viewLineRenderer.renderLine 2', () => { const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(inflated.mapping, [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 1, 5], - [3, 0, 6] - ]); + await assertSnapshot(inflated.mapping); }); function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: TestLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { From f2ede76f89923bb615ce63c82ae9211b3782ae19 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 15:56:35 +0200 Subject: [PATCH 0802/4355] Add an option bag for creating the RenderLineInput --- .../common/viewLayout/viewLineRenderer.ts | 25 + .../viewLayout/viewLineRenderer.test.ts | 1262 +++++------------ 2 files changed, 354 insertions(+), 933 deletions(-) diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index fc5e14eaf0f..f1fe2e39ac8 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -22,6 +22,31 @@ export const enum RenderWhitespace { All = 4 } +export interface IRenderLineInputOptions { + useMonospaceOptimizations: boolean; + canUseHalfwidthRightwardsArrow: boolean; + lineContent: string; + continuesWithWrappedLine: boolean; + isBasicASCII: boolean; + containsRTL: boolean; + fauxIndentLength: number; + lineTokens: IViewLineTokens; + lineDecorations: LineDecoration[]; + tabSize: number; + startVisibleColumn: number; + spaceWidth: number; + middotWidth: number; + wsmiddotWidth: number; + stopRenderingLineAfter: number; + renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all'; + renderControlCharacters: boolean; + fontLigatures: boolean; + selectionsOnLine: OffsetRange[] | null; + textDirection: TextDirection | null; + verticalScrollbarSize: number; + renderNewLineWhenEmpty: boolean; +} + export class RenderLineInput { public readonly useMonospaceOptimizations: boolean; diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 8ad73d70508..a24615b7648 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -12,7 +12,7 @@ import { OffsetRange } from '../../../common/core/ranges/offsetRange.js'; import { MetadataConsts } from '../../../common/encodedTokenAttributes.js'; import { IViewLineTokens } from '../../../common/tokens/lineTokens.js'; import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js'; -import { CharacterMapping, DomPosition, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js'; +import { CharacterMapping, DomPosition, IRenderLineInputOptions, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js'; import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js'; import { TestLineToken, TestLineTokens } from '../core/testLineToken.js'; @@ -53,34 +53,82 @@ function inflateRenderLineOutput(renderLineOutput: RenderLineOutput2) { }; } +type IRelaxedRenderLineInputOptions = Partial; + +const defaultRenderLineInputOptions: IRenderLineInputOptions = { + useMonospaceOptimizations: false, + canUseHalfwidthRightwardsArrow: true, + lineContent: '', + continuesWithWrappedLine: false, + isBasicASCII: true, + containsRTL: false, + fauxIndentLength: 0, + lineTokens: createViewLineTokens([]), + lineDecorations: [], + tabSize: 4, + startVisibleColumn: 0, + spaceWidth: 10, + middotWidth: 10, + wsmiddotWidth: 10, + stopRenderingLineAfter: -1, + renderWhitespace: 'none', + renderControlCharacters: false, + fontLigatures: false, + selectionsOnLine: null, + textDirection: null, + verticalScrollbarSize: 14, + renderNewLineWhenEmpty: false +}; + +function createRenderLineInputOptions(opts: IRelaxedRenderLineInputOptions): IRenderLineInputOptions { + return { + ...defaultRenderLineInputOptions, + ...opts + }; +} + +function createRenderLineInput(opts: IRelaxedRenderLineInputOptions): RenderLineInput { + const options = createRenderLineInputOptions(opts); + return new RenderLineInput( + options.useMonospaceOptimizations, + options.canUseHalfwidthRightwardsArrow, + options.lineContent, + options.continuesWithWrappedLine, + options.isBasicASCII, + options.containsRTL, + options.fauxIndentLength, + options.lineTokens, + options.lineDecorations, + options.tabSize, + options.startVisibleColumn, + options.spaceWidth, + options.middotWidth, + options.wsmiddotWidth, + options.stopRenderingLineAfter, + options.renderWhitespace, + options.renderControlCharacters, + options.fontLigatures, + options.selectionsOnLine, + options.textDirection, + options.verticalScrollbarSize, + options.renderNewLineWhenEmpty + ); +} + suite('viewLineRenderer.renderLine', () => { ensureNoDisposablesAreLeakedInTestSuite(); function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void { - const _actual = renderViewLine(new RenderLineInput( - false, - true, + const _actual = renderViewLine(createRenderLineInput({ lineContent, - false, - strings.isBasicASCII(lineContent), - false, - 0, - createViewLineTokens([new TestLineToken(lineContent.length, 0)]), - [], + isBasicASCII: strings.isBasicASCII(lineContent), + lineTokens: createViewLineTokens([new TestLineToken(lineContent.length, 0)]), tabSize, - 0, - 0, - 0, - 0, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + spaceWidth: 0, + middotWidth: 0, + wsmiddotWidth: 0 + })); assert.strictEqual(_actual.html, '' + expected + ''); const info = expectedCharOffsetInPart.map((absoluteOffset) => [absoluteOffset, [0, absoluteOffset]]); @@ -114,29 +162,14 @@ suite('viewLineRenderer.renderLine', () => { }); function assertParts(lineContent: string, tabSize: number, parts: TestLineToken[], expected: string, info: CharacterMappingInfo[]): void { - const _actual = renderViewLine(new RenderLineInput( - false, - true, + const _actual = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens(parts), - [], + lineTokens: createViewLineTokens(parts), tabSize, - 0, - 0, - 0, - 0, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + spaceWidth: 0, + middotWidth: 0, + wsmiddotWidth: 0 + })); assert.strictEqual(_actual.html, '' + expected + ''); assertCharacterMapping3(_actual.characterMapping, info); @@ -159,15 +192,9 @@ suite('viewLineRenderer.renderLine', () => { }); test('overflow', async () => { - const _actual = renderViewLine(new RenderLineInput( - false, - true, - 'Hello world!', - false, - true, - false, - 0, - createViewLineTokens([ + const _actual = renderViewLine(createRenderLineInput({ + lineContent: 'Hello world!', + lineTokens: createViewLineTokens([ createPart(1, 0), createPart(2, 1), createPart(3, 2), @@ -181,20 +208,9 @@ suite('viewLineRenderer.renderLine', () => { createPart(11, 10), createPart(12, 11), ]), - [], - 4, - 0, - 10, - 10, - 10, - 6, - 'boundary', - false, - false, - null, - null, - 14 - )); + stopRenderingLineAfter: 6, + renderWhitespace: 'boundary' + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -217,29 +233,11 @@ suite('viewLineRenderer.renderLine', () => { createPart(43, 11), createPart(48, 12), ]); - const _actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - true, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'boundary', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + lineTokens: lineParts, + renderWhitespace: 'boundary' + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -260,29 +258,10 @@ suite('viewLineRenderer.renderLine', () => { createPart(67, 9), // 1 char createPart(68, 10), // 2 chars ]); - const _actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - true, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -304,29 +283,10 @@ suite('viewLineRenderer.renderLine', () => { createPart(68, 9), // 1 char createPart(69, 10), // 2 chars ]); - const _actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - true, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -338,32 +298,16 @@ suite('viewLineRenderer.renderLine', () => { const lineParts = createViewLineTokens([ createPart(16, 1) ]); - const actual = renderViewLine(new RenderLineInput( - true, - false, - lineText, - false, - true, - false, - 0, - lineParts, - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + canUseHalfwidthRightwardsArrow: false, + lineContent: lineText, + lineTokens: lineParts, + lineDecorations: [ new LineDecoration(13, 13, 'dec1', InlineDecorationType.After), new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before), - ], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + ] + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -378,29 +322,12 @@ suite('viewLineRenderer.renderLine', () => { createPart(66, 20), createPart(67, 1), ]); - const _actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - false, - true, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + isBasicASCII: false, + containsRTL: true, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -422,29 +349,12 @@ suite('viewLineRenderer.renderLine', () => { createPart(39, 3), createPart(40, 2), ]); - const _actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - false, - true, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + isBasicASCII: false, + containsRTL: true, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -460,29 +370,14 @@ suite('viewLineRenderer.renderLine', () => { createPart(34, 3), createPart(35, 2), ]); - const _actual = renderViewLine(new RenderLineInput( - true, - true, - lineText, - false, - false, - true, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'all', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: lineText, + isBasicASCII: false, + containsRTL: true, + lineTokens: lineParts, + renderWhitespace: 'all' + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -497,29 +392,10 @@ suite('viewLineRenderer.renderLine', () => { function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); - const actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - true, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + lineTokens: lineParts + })); assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); } @@ -603,29 +479,11 @@ suite('viewLineRenderer.renderLine', () => { function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); - const actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - true, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - true, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + lineTokens: lineParts, + fontLigatures: true + })); assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); } @@ -646,29 +504,11 @@ suite('viewLineRenderer.renderLine', () => { test('issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns', async () => { const lineText = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); - const actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - false, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + isBasicASCII: false, + lineTokens: lineParts + })); await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), HTML_EXTENSION); }); @@ -676,29 +516,12 @@ suite('viewLineRenderer.renderLine', () => { test('issue #6885: Does not split large tokens in RTL text', async () => { const lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); - const actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - false, - true, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + isBasicASCII: false, + containsRTL: true, + lineTokens: lineParts + })); await assertSnapshot(actual.html, HTML_EXTENSION); }); @@ -706,29 +529,11 @@ suite('viewLineRenderer.renderLine', () => { test('issue #95685: Uses unicode replacement character for Paragraph Separator', async () => { const lineText = 'var ftext = [\u2029"Und", "dann", "eines"];'; const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); - const actual = renderViewLine(new RenderLineInput( - false, - true, - lineText, - false, - false, - false, - 0, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + lineContent: lineText, + isBasicASCII: false, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); await assertSnapshot(inflated.mapping); @@ -746,29 +551,12 @@ suite('viewLineRenderer.renderLine', () => { createPart(32, 7), createPart(34, 8), ]); - const _actual = renderViewLine(new RenderLineInput( - true, - true, - lineText, - false, - true, - false, - 4, - lineParts, - [], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + const _actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: lineText, + fauxIndentLength: 4, + lineTokens: lineParts + })); const inflated = inflateRenderLineOutput(_actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -810,57 +598,24 @@ suite('viewLineRenderer.renderLine 2', () => { ensureNoDisposablesAreLeakedInTestSuite(); function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: TestLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: OffsetRange[] | null) { - const actual = renderViewLine(new RenderLineInput( - fontIsMonospace, - true, + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: fontIsMonospace, lineContent, - false, - true, - false, fauxIndentLength, - createViewLineTokens(tokens), - [], - 4, - 0, - 10, - 10, - 10, - -1, + lineTokens: createViewLineTokens(tokens), renderWhitespace, - false, - false, - selections, - null, - 14 - )); + selectionsOnLine: selections + })); return inflateRenderLineOutput(actual); } test('issue #18616: Inline decorations ending at the text length are no longer rendered', async () => { const lineContent = 'https://microsoft.com'; - const actual = renderViewLine(new RenderLineInput( - false, - true, + const actual = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens([createPart(21, 3)]), - [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + lineTokens: createViewLineTokens([createPart(21, 3)]), + lineDecorations: [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)] + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -869,37 +624,20 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #19207: Link in Monokai is not rendered correctly', async () => { const lineContent = '\'let url = `http://***/_api/web/lists/GetByTitle(\\\'Teambuildingaanvragen\\\')/items`;\''; - const actual = renderViewLine(new RenderLineInput( - true, - true, + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, lineContent, - false, - true, - false, - 0, - createViewLineTokens([ + lineTokens: createViewLineTokens([ createPart(49, 6), createPart(51, 4), createPart(72, 6), createPart(74, 4), createPart(84, 6), ]), - [ + lineDecorations: [ new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular) - ], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + ] + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1355,33 +1093,15 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('createLineParts can handle unsorted inline decorations', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - true, - 'Hello world', - false, - true, - false, - 0, - createViewLineTokens([createPart(11, 0)]), - [ + const actual = renderViewLine(createRenderLineInput({ + lineContent: 'Hello world', + lineTokens: createViewLineTokens([createPart(11, 0)]), + lineDecorations: [ new LineDecoration(5, 7, 'a', InlineDecorationType.Regular), new LineDecoration(1, 3, 'b', InlineDecorationType.Regular), new LineDecoration(2, 8, 'c', InlineDecorationType.Regular), - ], - 4, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + ] + })); // 01234567890 // Hello world @@ -1398,29 +1118,13 @@ suite('viewLineRenderer.renderLine 2', () => { const lineContent = '\tbla'; - const actual = renderViewLine(new RenderLineInput( - false, - true, + const actual = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens([createPart(4, 3)]), - [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], - 4, - 0, - 10, - 10, - 10, - -1, - 'all', - false, - true, - null, - null, - 14 - )); + lineTokens: createViewLineTokens([createPart(4, 3)]), + lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], + renderWhitespace: 'all', + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1431,29 +1135,13 @@ suite('viewLineRenderer.renderLine 2', () => { const lineContent = '\tbla'; - const actual = renderViewLine(new RenderLineInput( - false, - true, + const actual = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens([createPart(4, 3)]), - [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)], - 4, - 0, - 10, - 10, - 10, - -1, - 'all', - false, - true, - null, - null, - 14 - )); + lineTokens: createViewLineTokens([createPart(4, 3)]), + lineDecorations: [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)], + renderWhitespace: 'all', + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1464,29 +1152,13 @@ suite('viewLineRenderer.renderLine 2', () => { const lineContent = ''; - const actual = renderViewLine(new RenderLineInput( - false, - true, + const actual = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens([createPart(0, 3)]), - [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], - 4, - 0, - 10, - 10, - 10, - -1, - 'all', - false, - true, - null, - null, - 14 - )); + lineTokens: createViewLineTokens([createPart(0, 3)]), + lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], + renderWhitespace: 'all', + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1495,29 +1167,15 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - ' 1. 🙏', - false, - false, - false, - 0, - createViewLineTokens([createPart(7, 3)]), - [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], - 2, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: ' 1. 🙏', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(7, 3)]), + lineDecorations: [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], + tabSize: 2, + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1526,32 +1184,17 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #37401 #40127: Allow both before and after decorations on empty line', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - '', - false, - true, - false, - 0, - createViewLineTokens([createPart(0, 3)]), - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: '', + lineTokens: createViewLineTokens([createPart(0, 3)]), + lineDecorations: [ new LineDecoration(1, 1, 'before', InlineDecorationType.Before), new LineDecoration(1, 1, 'after', InlineDecorationType.After), ], - 2, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + tabSize: 2, + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1560,34 +1203,19 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #118759: enable multiple text editor decorations in empty lines', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - '', - false, - true, - false, - 0, - createViewLineTokens([createPart(0, 3)]), - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: '', + lineTokens: createViewLineTokens([createPart(0, 3)]), + lineDecorations: [ new LineDecoration(1, 1, 'after1', InlineDecorationType.After), new LineDecoration(1, 1, 'after2', InlineDecorationType.After), new LineDecoration(1, 1, 'before1', InlineDecorationType.Before), new LineDecoration(1, 1, 'before2', InlineDecorationType.Before), ], - 2, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + tabSize: 2, + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1596,32 +1224,16 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #38935: GitLens end-of-line blame no longer rendering', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - '\t}', - false, - true, - false, - 0, - createViewLineTokens([createPart(2, 3)]), - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: '\t}', + lineTokens: createViewLineTokens([createPart(2, 3)]), + lineDecorations: [ new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3', InlineDecorationType.Before), new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After), ], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1630,32 +1242,18 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - 'some text £', - false, - false, - false, - 0, - createViewLineTokens([createPart(11, 3)]), - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: 'some text £', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(11, 3)]), + lineDecorations: [ new LineDecoration(5, 5, 'inlineDec1', InlineDecorationType.After), new LineDecoration(6, 6, 'inlineDec2', InlineDecorationType.Before), ], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - true, - false, - null, - null, - 14 - )); + stopRenderingLineAfter: 10000, + renderControlCharacters: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1664,29 +1262,13 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #22832: Consider fullwidth characters when rendering tabs', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - 'asd = "擦"\t\t#asd', - false, - false, - false, - 0, - createViewLineTokens([createPart(15, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: 'asd = "擦"\t\t#asd', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(15, 3)]), + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1695,29 +1277,14 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - 'asd = "擦"\t\t#asd', - false, - false, - false, - 0, - createViewLineTokens([createPart(15, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'all', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: 'asd = "擦"\t\t#asd', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(15, 3)]), + stopRenderingLineAfter: 10000, + renderWhitespace: 'all' + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1726,29 +1293,13 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - '12345689012345678901234568901234567890123456890abába', - false, - false, - false, - 0, - createViewLineTokens([createPart(53, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: '12345689012345678901234568901234567890123456890abába', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(53, 3)]), + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1757,29 +1308,13 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #22352: Partially Broken Complex Script Rendering of Tamil', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', - false, - false, - false, - 0, - createViewLineTokens([createPart(100, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(100, 3)]), + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1788,29 +1323,13 @@ suite('viewLineRenderer.renderLine 2', () => { test('issue #42700: Hindi characters are not being rendered properly', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', - false, - false, - false, - 0, - createViewLineTokens([createPart(105, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(105, 3)]), + stopRenderingLineAfter: 10000 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1818,29 +1337,14 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - true, - 'This is a long line which never uses more than two spaces. ', - true, - true, - false, - 0, - createViewLineTokens([createPart(59, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'boundary', - false, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + lineContent: 'This is a long line which never uses more than two spaces. ', + continuesWithWrappedLine: true, + lineTokens: createViewLineTokens([createPart(59, 3)]), + stopRenderingLineAfter: 10000, + renderWhitespace: 'boundary' + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1848,29 +1352,13 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #33525: Long line with ligatures takes a long time to paint decorations', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - false, - 'append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to', - false, - true, - false, - 0, - createViewLineTokens([createPart(194, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - true, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + canUseHalfwidthRightwardsArrow: false, + lineContent: 'append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to', + lineTokens: createViewLineTokens([createPart(194, 3)]), + stopRenderingLineAfter: 10000, + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1878,29 +1366,13 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #33525: Long line with ligatures takes a long time to paint decorations - not possible', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - false, - 'appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato', - false, - true, - false, - 0, - createViewLineTokens([createPart(194, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - false, - true, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + canUseHalfwidthRightwardsArrow: false, + lineContent: 'appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato', + lineTokens: createViewLineTokens([createPart(194, 3)]), + stopRenderingLineAfter: 10000, + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1908,15 +1380,9 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #91936: Semantic token color highlighting fails on line with selected text', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - true, - ' else if ($s = 08) then \'\\b\'', - false, - true, - false, - 0, - createViewLineTokens([ + const actual = renderViewLine(createRenderLineInput({ + lineContent: ' else if ($s = 08) then \'\\b\'', + lineTokens: createViewLineTokens([ createPart(20, 1), createPart(24, 15), createPart(25, 1), @@ -1936,20 +1402,12 @@ suite('viewLineRenderer.renderLine 2', () => { createPart(43, 1), createPart(47, 11) ]), - [], - 4, - 0, - 10, - 11, - 11, - 10000, - 'selection', - false, - false, - [new OffsetRange(0, 47)], - null, - 14 - )); + stopRenderingLineAfter: 10000, + renderWhitespace: 'selection', + selectionsOnLine: [new OffsetRange(0, 47)], + middotWidth: 11, + wsmiddotWidth: 11 + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1957,29 +1415,14 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #119416: Delete Control Character (U+007F / ) displayed as space', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - false, - '[' + String.fromCharCode(127) + '] [' + String.fromCharCode(0) + ']', - false, - true, - false, - 0, - createViewLineTokens([createPart(7, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - true, - true, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + canUseHalfwidthRightwardsArrow: false, + lineContent: '[' + String.fromCharCode(127) + '] [' + String.fromCharCode(0) + ']', + lineTokens: createViewLineTokens([createPart(7, 3)]), + stopRenderingLineAfter: 10000, + renderControlCharacters: true, + fontLigatures: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -1987,29 +1430,14 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #116939: Important control characters aren\'t rendered', async () => { - const actual = renderViewLine(new RenderLineInput( - false, - false, - `transferBalance(5678,${String.fromCharCode(0x202E)}6776,4321${String.fromCharCode(0x202C)},"USD");`, - false, - false, - false, - 0, - createViewLineTokens([createPart(42, 3)]), - [], - 4, - 0, - 10, - 10, - 10, - 10000, - 'none', - true, - false, - null, - null, - 14 - )); + const actual = renderViewLine(createRenderLineInput({ + canUseHalfwidthRightwardsArrow: false, + lineContent: `transferBalance(5678,${String.fromCharCode(0x202E)}6776,4321${String.fromCharCode(0x202C)},"USD");`, + isBasicASCII: false, + lineTokens: createViewLineTokens([createPart(42, 3)]), + stopRenderingLineAfter: 10000, + renderControlCharacters: true + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -2017,33 +1445,19 @@ suite('viewLineRenderer.renderLine 2', () => { }); test('issue #124038: Multiple end-of-line text decorations get merged', async () => { - const actual = renderViewLine(new RenderLineInput( - true, - false, - ' if', - false, - true, - false, - 0, - createViewLineTokens([createPart(4, 1), createPart(6, 2)]), - [ + const actual = renderViewLine(createRenderLineInput({ + useMonospaceOptimizations: true, + canUseHalfwidthRightwardsArrow: false, + lineContent: ' if', + lineTokens: createViewLineTokens([createPart(4, 1), createPart(6, 2)]), + lineDecorations: [ new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-3 ced-1-TextEditorDecorationType2-3', InlineDecorationType.Before), new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-4 ced-1-TextEditorDecorationType2-4', InlineDecorationType.After), new LineDecoration(7, 7, 'ced-ghost-text-1-4', InlineDecorationType.After), ], - 4, - 0, - 10, - 10, - 10, - 10000, - 'all', - false, - false, - null, - null, - 14 - )); + stopRenderingLineAfter: 10000, + renderWhitespace: 'all' + })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -2051,29 +1465,11 @@ suite('viewLineRenderer.renderLine 2', () => { }); function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: TestLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { - const renderLineOutput = renderViewLine(new RenderLineInput( - false, - true, + const renderLineOutput = renderViewLine(createRenderLineInput({ lineContent, - false, - true, - false, - 0, - createViewLineTokens(parts), - [], tabSize, - 0, - 10, - 10, - 10, - -1, - 'none', - false, - false, - null, - null, - 14 - )); + lineTokens: createViewLineTokens(parts) + })); return (partIndex: number, partLength: number, offset: number, expected: number) => { const actualColumn = renderLineOutput.characterMapping.getColumn(new DomPosition(partIndex, offset), partLength); From acbcf359c613729f2ed69d446311faa2f87df6a8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 15:59:46 +0200 Subject: [PATCH 0803/4355] Renames --- .../viewLayout/viewLineRenderer.test.ts | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index a24615b7648..809c1b34607 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -218,8 +218,8 @@ suite('viewLineRenderer.renderLine', () => { }); test('typical line', async () => { - const lineText = '\t export class Game { // http://test.com '; - const lineParts = createViewLineTokens([ + const lineContent = '\t export class Game { // http://test.com '; + const lineTokens = createViewLineTokens([ createPart(5, 1), createPart(11, 2), createPart(12, 3), @@ -234,8 +234,8 @@ suite('viewLineRenderer.renderLine', () => { createPart(48, 12), ]); const _actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, - lineTokens: lineParts, + lineContent, + lineTokens, renderWhitespace: 'boundary' })); @@ -245,8 +245,8 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #2255: Weird line rendering part 1', async () => { - const lineText = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; - const lineParts = createViewLineTokens([ + const lineContent = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; + const lineTokens = createViewLineTokens([ createPart(3, 1), // 3 chars createPart(15, 2), // 12 chars createPart(21, 3), // 6 chars @@ -259,8 +259,8 @@ suite('viewLineRenderer.renderLine', () => { createPart(68, 10), // 2 chars ]); const _actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, - lineTokens: lineParts + lineContent, + lineTokens })); const inflated = inflateRenderLineOutput(_actual); @@ -269,9 +269,9 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #2255: Weird line rendering part 2', async () => { - const lineText = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; + const lineContent = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; - const lineParts = createViewLineTokens([ + const lineTokens = createViewLineTokens([ createPart(4, 1), // 4 chars createPart(16, 2), // 12 chars createPart(22, 3), // 6 chars @@ -284,8 +284,8 @@ suite('viewLineRenderer.renderLine', () => { createPart(69, 10), // 2 chars ]); const _actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, - lineTokens: lineParts + lineContent, + lineTokens })); const inflated = inflateRenderLineOutput(_actual); @@ -294,15 +294,15 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #91178: after decoration type shown before cursor', async () => { - const lineText = '//just a comment'; - const lineParts = createViewLineTokens([ + const lineContent = '//just a comment'; + const lineTokens = createViewLineTokens([ createPart(16, 1) ]); const actual = renderViewLine(createRenderLineInput({ useMonospaceOptimizations: true, canUseHalfwidthRightwardsArrow: false, - lineContent: lineText, - lineTokens: lineParts, + lineContent, + lineTokens, lineDecorations: [ new LineDecoration(13, 13, 'dec1', InlineDecorationType.After), new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before), @@ -315,18 +315,18 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages', async () => { - const lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; - const lineParts = createViewLineTokens([ + const lineContent = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; + const lineTokens = createViewLineTokens([ createPart(3, 6), createPart(13, 1), createPart(66, 20), createPart(67, 1), ]); const _actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, + lineContent, isBasicASCII: false, containsRTL: true, - lineTokens: lineParts + lineTokens })); const inflated = inflateRenderLineOutput(_actual); @@ -335,8 +335,8 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #137036: Issue in RTL languages in recent versions', async () => { - const lineText = ''; - const lineParts = createViewLineTokens([ + const lineContent = ''; + const lineTokens = createViewLineTokens([ createPart(1, 2), createPart(7, 3), createPart(8, 4), @@ -350,10 +350,10 @@ suite('viewLineRenderer.renderLine', () => { createPart(40, 2), ]); const _actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, + lineContent, isBasicASCII: false, containsRTL: true, - lineTokens: lineParts + lineTokens })); const inflated = inflateRenderLineOutput(_actual); @@ -362,8 +362,8 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #99589: Rendering whitespace influences bidi layout', async () => { - const lineText = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]'; - const lineParts = createViewLineTokens([ + const lineContent = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]'; + const lineTokens = createViewLineTokens([ createPart(5, 2), createPart(21, 3), createPart(22, 2), @@ -372,10 +372,10 @@ suite('viewLineRenderer.renderLine', () => { ]); const _actual = renderViewLine(createRenderLineInput({ useMonospaceOptimizations: true, - lineContent: lineText, + lineContent, isBasicASCII: false, containsRTL: true, - lineTokens: lineParts, + lineTokens, renderWhitespace: 'all' })); @@ -390,11 +390,11 @@ suite('viewLineRenderer.renderLine', () => { // 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.'; - function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { - const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); + function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void { + const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]); const actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, - lineTokens: lineParts + lineContent, + lineTokens })); assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); } @@ -477,11 +477,11 @@ suite('viewLineRenderer.renderLine', () => { // 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234 const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.'; - function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { - const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); + function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void { + const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]); const actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, - lineTokens: lineParts, + lineContent, + lineTokens, fontLigatures: true })); assert.strictEqual(actual.html, '' + expectedOutput.join('') + '', message); @@ -502,37 +502,37 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns', async () => { - const lineText = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷'; - const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); + const lineContent = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷'; + const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]); const actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, + lineContent, isBasicASCII: false, - lineTokens: lineParts + lineTokens })); await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), HTML_EXTENSION); }); test('issue #6885: Does not split large tokens in RTL text', async () => { - const lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; - const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); + const lineContent = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; + const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]); const actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, + lineContent, isBasicASCII: false, containsRTL: true, - lineTokens: lineParts + lineTokens })); await assertSnapshot(actual.html, HTML_EXTENSION); }); test('issue #95685: Uses unicode replacement character for Paragraph Separator', async () => { - const lineText = 'var ftext = [\u2029"Und", "dann", "eines"];'; - const lineParts = createViewLineTokens([createPart(lineText.length, 1)]); + const lineContent = 'var ftext = [\u2029"Und", "dann", "eines"];'; + const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]); const actual = renderViewLine(createRenderLineInput({ - lineContent: lineText, + lineContent, isBasicASCII: false, - lineTokens: lineParts + lineTokens })); const inflated = inflateRenderLineOutput(actual); await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); @@ -540,8 +540,8 @@ suite('viewLineRenderer.renderLine', () => { }); test('issue #19673: Monokai Theme bad-highlighting in line wrap', async () => { - const lineText = ' MongoCallback): void {'; - const lineParts = createViewLineTokens([ + const lineContent = ' MongoCallback): void {'; + const lineTokens = createViewLineTokens([ createPart(17, 1), createPart(18, 2), createPart(24, 3), @@ -553,9 +553,9 @@ suite('viewLineRenderer.renderLine', () => { ]); const _actual = renderViewLine(createRenderLineInput({ useMonospaceOptimizations: true, - lineContent: lineText, + lineContent, fauxIndentLength: 4, - lineTokens: lineParts + lineTokens })); const inflated = inflateRenderLineOutput(_actual); From 8424ddf43445f4a23c299c10bda63a7bad4b599b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 16:03:39 +0200 Subject: [PATCH 0804/4355] More assertSnapshot adoption --- ..._not_emit_width_for_monospace_fonts.1.snap | 32 ++++ ..._render_whitespace_-_2_leading_tabs.1.snap | 18 ++ ...ender_whitespace_-_4_leading_spaces.1.snap | 23 +++ ...ender_whitespace_-_8_leading_spaces.1.snap | 31 ++++ ...ace_-_mixed_leading_spaces_and_tabs.1.snap | 34 ++++ ...render_whitespace_for_all_in_middle.1.snap | 17 ++ ...iple__initially_unsorted_selections.1.snap | 17 ++ ..._selection_with_multiple_selections.1.snap | 17 ++ ...ce_for_selection_with_no_selections.1.snap | 17 ++ ...lection_spanning_part_of_whitespace.1.snap | 17 ++ ..._with_selections_next_to_each_other.1.snap | 1 + ...selection_with_whole_line_selection.1.snap | 17 ++ ..._leading_and_8_trailing_whitespaces.1.snap | 31 ++++ ...ing__inner__and_trailing_whitespace.1.snap | 18 ++ ...er__and_without_trailing_whitespace.1.snap | 16 ++ ...th_line_containing_only_whitespaces.1.snap | 1 + ...ace_in_middle_but_not_for_one_space.1.snap | 16 ++ ...render_whitespace_skips_faux_indent.1.snap | 32 ++++ ...renderLine_2_createLineParts_simple.1.snap | 15 ++ ...2_createLineParts_simple_two_tokens.1.snap | 15 ++ .../viewLayout/viewLineRenderer.test.ts | 154 +++--------------- 21 files changed, 405 insertions(+), 134 deletions(-) create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.1.snap create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.1.snap diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.1.snap new file mode 100644 index 00000000000..97668c38006 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_does_not_emit_width_for_monospace_fonts.1.snap @@ -0,0 +1,32 @@ +[ + [ 0, 0, 0 ], + [ 0, 4, 4 ], + [ 1, 0, 8 ], + [ 1, 2, 9 ], + [ 2, 0, 10 ], + [ 2, 1, 11 ], + [ 3, 0, 12 ], + [ 3, 1, 13 ], + [ 3, 2, 14 ], + [ 3, 3, 15 ], + [ 3, 4, 16 ], + [ 3, 5, 17 ], + [ 3, 6, 18 ], + [ 3, 7, 19 ], + [ 3, 8, 20 ], + [ 3, 9, 21 ], + [ 4, 0, 22 ], + [ 4, 2, 23 ], + [ 4, 3, 24 ], + [ 4, 5, 25 ], + [ 4, 7, 26 ], + [ 4, 9, 28 ], + [ 4, 11, 29 ], + [ 4, 13, 30 ], + [ 4, 15, 31 ], + [ 4, 16, 32 ], + [ 4, 18, 33 ], + [ 4, 20, 34 ], + [ 4, 22, 35 ], + [ 4, 24, 36 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.1.snap new file mode 100644 index 00000000000..ed80217b525 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_2_leading_tabs.1.snap @@ -0,0 +1,18 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 4 ], + [ 2, 0, 8 ], + [ 2, 1, 9 ], + [ 3, 0, 10 ], + [ 3, 1, 11 ], + [ 3, 2, 12 ], + [ 3, 3, 13 ], + [ 3, 4, 14 ], + [ 3, 5, 15 ], + [ 3, 6, 16 ], + [ 3, 7, 17 ], + [ 3, 8, 18 ], + [ 3, 9, 19 ], + [ 4, 0, 20 ], + [ 4, 4, 24 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.1.snap new file mode 100644 index 00000000000..da66f9a9772 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_4_leading_spaces.1.snap @@ -0,0 +1,23 @@ +[ + [ 0, 0, 0 ], + [ 0, 2, 1 ], + [ 0, 4, 2 ], + [ 0, 6, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 2, 0, 6 ], + [ 2, 1, 7 ], + [ 2, 2, 8 ], + [ 2, 3, 9 ], + [ 2, 4, 10 ], + [ 2, 5, 11 ], + [ 2, 6, 12 ], + [ 2, 7, 13 ], + [ 2, 8, 14 ], + [ 2, 9, 15 ], + [ 3, 0, 16 ], + [ 3, 2, 17 ], + [ 3, 4, 18 ], + [ 3, 6, 19 ], + [ 3, 8, 20 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.1.snap new file mode 100644 index 00000000000..53da38d1f9d --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_8_leading_spaces.1.snap @@ -0,0 +1,31 @@ +[ + [ 0, 0, 0 ], + [ 0, 2, 1 ], + [ 0, 4, 2 ], + [ 0, 6, 3 ], + [ 1, 0, 4 ], + [ 1, 2, 5 ], + [ 1, 4, 6 ], + [ 1, 6, 7 ], + [ 2, 0, 8 ], + [ 2, 1, 9 ], + [ 3, 0, 10 ], + [ 3, 1, 11 ], + [ 3, 2, 12 ], + [ 3, 3, 13 ], + [ 3, 4, 14 ], + [ 3, 5, 15 ], + [ 3, 6, 16 ], + [ 3, 7, 17 ], + [ 3, 8, 18 ], + [ 3, 9, 19 ], + [ 4, 0, 20 ], + [ 4, 2, 21 ], + [ 4, 4, 22 ], + [ 4, 6, 23 ], + [ 5, 0, 24 ], + [ 5, 2, 25 ], + [ 5, 4, 26 ], + [ 5, 6, 27 ], + [ 5, 8, 28 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.1.snap new file mode 100644 index 00000000000..f8e8d058dd6 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_-_mixed_leading_spaces_and_tabs.1.snap @@ -0,0 +1,34 @@ +[ + [ 0, 0, 0 ], + [ 0, 2, 1 ], + [ 0, 4, 2 ], + [ 1, 0, 4 ], + [ 2, 0, 8 ], + [ 2, 2, 9 ], + [ 3, 0, 10 ], + [ 3, 1, 11 ], + [ 4, 0, 12 ], + [ 4, 1, 13 ], + [ 4, 2, 14 ], + [ 4, 3, 15 ], + [ 4, 4, 16 ], + [ 4, 5, 17 ], + [ 4, 6, 18 ], + [ 4, 7, 19 ], + [ 4, 8, 20 ], + [ 4, 9, 21 ], + [ 5, 0, 22 ], + [ 5, 2, 23 ], + [ 6, 0, 24 ], + [ 6, 2, 25 ], + [ 6, 4, 26 ], + [ 7, 0, 28 ], + [ 7, 2, 29 ], + [ 7, 4, 30 ], + [ 7, 6, 31 ], + [ 8, 0, 32 ], + [ 8, 2, 33 ], + [ 8, 4, 34 ], + [ 8, 6, 35 ], + [ 8, 8, 36 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.1.snap new file mode 100644 index 00000000000..b6985c90edc --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_all_in_middle.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 4, 0, 7 ], + [ 4, 1, 8 ], + [ 4, 2, 9 ], + [ 4, 3, 10 ], + [ 4, 4, 11 ], + [ 4, 5, 12 ], + [ 5, 0, 13 ], + [ 5, 3, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.1.snap new file mode 100644 index 00000000000..b644c187090 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple__initially_unsorted_selections.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 3, 1, 7 ], + [ 3, 2, 8 ], + [ 3, 3, 9 ], + [ 3, 4, 10 ], + [ 3, 5, 11 ], + [ 3, 6, 12 ], + [ 4, 0, 13 ], + [ 4, 3, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.1.snap new file mode 100644 index 00000000000..b644c187090 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_multiple_selections.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 3, 1, 7 ], + [ 3, 2, 8 ], + [ 3, 3, 9 ], + [ 3, 4, 10 ], + [ 3, 5, 11 ], + [ 3, 6, 12 ], + [ 4, 0, 13 ], + [ 4, 3, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.1.snap new file mode 100644 index 00000000000..e164d0b023f --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_no_selections.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 2, 0, 6 ], + [ 2, 1, 7 ], + [ 2, 2, 8 ], + [ 2, 3, 9 ], + [ 2, 4, 10 ], + [ 2, 5, 11 ], + [ 2, 6, 12 ], + [ 2, 7, 13 ], + [ 2, 10, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.1.snap new file mode 100644 index 00000000000..930b6a5f228 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selection_spanning_part_of_whitespace.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 3, 1, 7 ], + [ 3, 2, 8 ], + [ 3, 3, 9 ], + [ 3, 4, 10 ], + [ 3, 5, 11 ], + [ 3, 6, 12 ], + [ 3, 7, 13 ], + [ 3, 10, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.1.snap new file mode 100644 index 00000000000..1c1f3645c81 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_selections_next_to_each_other.1.snap @@ -0,0 +1 @@ +[ [ 0, 0, 0 ], [ 1, 0, 1 ], [ 2, 0, 2 ], [ 3, 0, 3 ], [ 3, 1, 4 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.1.snap new file mode 100644 index 00000000000..b6985c90edc --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_selection_with_whole_line_selection.1.snap @@ -0,0 +1,17 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 1, 1, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 4, 0, 7 ], + [ 4, 1, 8 ], + [ 4, 2, 9 ], + [ 4, 3, 10 ], + [ 4, 4, 11 ], + [ 4, 5, 12 ], + [ 5, 0, 13 ], + [ 5, 3, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.1.snap new file mode 100644 index 00000000000..10caa87386d --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_8_leading_and_8_trailing_whitespaces.1.snap @@ -0,0 +1,31 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 1, 0, 8 ], + [ 1, 1, 9 ], + [ 2, 0, 10 ], + [ 2, 1, 11 ], + [ 2, 2, 12 ], + [ 2, 3, 13 ], + [ 2, 4, 14 ], + [ 2, 5, 15 ], + [ 2, 6, 16 ], + [ 2, 7, 17 ], + [ 2, 8, 18 ], + [ 2, 9, 19 ], + [ 3, 0, 20 ], + [ 3, 2, 21 ], + [ 3, 4, 22 ], + [ 3, 6, 23 ], + [ 4, 0, 24 ], + [ 4, 2, 25 ], + [ 4, 4, 26 ], + [ 4, 6, 27 ], + [ 4, 8, 28 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.1.snap new file mode 100644 index 00000000000..145ab47caa4 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_trailing_whitespace.1.snap @@ -0,0 +1,18 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 2, 0, 6 ], + [ 2, 1, 7 ], + [ 2, 2, 8 ], + [ 2, 3, 9 ], + [ 2, 4, 10 ], + [ 2, 5, 11 ], + [ 2, 6, 12 ], + [ 3, 0, 13 ], + [ 3, 2, 14 ], + [ 3, 4, 16 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.1.snap new file mode 100644 index 00000000000..0332430b208 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_leading__inner__and_without_trailing_whitespace.1.snap @@ -0,0 +1,16 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 1, 0, 4 ], + [ 1, 1, 5 ], + [ 2, 0, 6 ], + [ 2, 1, 7 ], + [ 2, 2, 8 ], + [ 2, 3, 9 ], + [ 2, 4, 10 ], + [ 2, 5, 11 ], + [ 2, 6, 12 ], + [ 2, 7, 13 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.1.snap new file mode 100644 index 00000000000..ef82459cd8b --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_for_trailing_with_line_containing_only_whitespaces.1.snap @@ -0,0 +1 @@ +[ [ 0, 0, 0 ], [ 0, 2, 1 ], [ 1, 0, 4 ], [ 1, 2, 5 ] ] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.1.snap new file mode 100644 index 00000000000..b5d9af8b08d --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_in_middle_but_not_for_one_space.1.snap @@ -0,0 +1,16 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 1, 0, 2 ], + [ 1, 2, 3 ], + [ 2, 0, 4 ], + [ 2, 1, 5 ], + [ 3, 0, 6 ], + [ 4, 0, 7 ], + [ 4, 1, 8 ], + [ 5, 0, 9 ], + [ 5, 2, 10 ], + [ 6, 0, 11 ], + [ 6, 1, 12 ], + [ 6, 2, 13 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.1.snap new file mode 100644 index 00000000000..d0b5b6d3c83 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_render_whitespace_skips_faux_indent.1.snap @@ -0,0 +1,32 @@ +[ + [ 0, 0, 0 ], + [ 0, 4, 4 ], + [ 1, 0, 8 ], + [ 1, 2, 9 ], + [ 2, 0, 10 ], + [ 2, 1, 11 ], + [ 3, 0, 12 ], + [ 3, 1, 13 ], + [ 3, 2, 14 ], + [ 3, 3, 15 ], + [ 3, 4, 16 ], + [ 3, 5, 17 ], + [ 3, 6, 18 ], + [ 3, 7, 19 ], + [ 3, 8, 20 ], + [ 3, 9, 21 ], + [ 4, 0, 22 ], + [ 4, 2, 23 ], + [ 5, 0, 24 ], + [ 5, 2, 25 ], + [ 5, 4, 26 ], + [ 6, 0, 28 ], + [ 6, 2, 29 ], + [ 6, 4, 30 ], + [ 6, 6, 31 ], + [ 7, 0, 32 ], + [ 7, 2, 33 ], + [ 7, 4, 34 ], + [ 7, 6, 35 ], + [ 7, 8, 36 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.1.snap new file mode 100644 index 00000000000..e5bb71dd3ae --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple.1.snap @@ -0,0 +1,15 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 0, 6, 6 ], + [ 0, 7, 7 ], + [ 0, 8, 8 ], + [ 0, 9, 9 ], + [ 0, 10, 10 ], + [ 0, 11, 11 ], + [ 0, 12, 12 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.1.snap new file mode 100644 index 00000000000..96688a02951 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_2_createLineParts_simple_two_tokens.1.snap @@ -0,0 +1,15 @@ +[ + [ 0, 0, 0 ], + [ 0, 1, 1 ], + [ 0, 2, 2 ], + [ 0, 3, 3 ], + [ 0, 4, 4 ], + [ 0, 5, 5 ], + [ 1, 0, 6 ], + [ 1, 1, 7 ], + [ 1, 2, 8 ], + [ 1, 3, 9 ], + [ 1, 4, 10 ], + [ 1, 5, 11 ], + [ 1, 6, 12 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 809c1b34607..1bc87e10385 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -656,9 +656,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], [0, 8, 8], [0, 9, 9], [0, 10, 10], [0, 11, 11], [0, 12, 12] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts simple two tokens', async () => { @@ -674,10 +672,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], - [1, 0, 6], [1, 1, 7], [1, 2, 8], [1, 3, 9], [1, 4, 10], [1, 5, 11], [1, 6, 12] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace - 4 leading spaces', async () => { @@ -694,12 +689,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 8, 14], [2, 9, 15], - [3, 0, 16], [3, 2, 17], [3, 4, 18], [3, 6, 19], [3, 8, 20] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace - 8 leading spaces', async () => { @@ -716,14 +706,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], [0, 6, 3], - [1, 0, 4], [1, 2, 5], [1, 4, 6], [1, 6, 7], - [2, 0, 8], [2, 1, 9], - [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], - [4, 0, 20], [4, 2, 21], [4, 4, 22], [4, 6, 23], - [5, 0, 24], [5, 2, 25], [5, 4, 26], [5, 6, 27], [5, 8, 28] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace - 2 leading tabs', async () => { @@ -740,13 +723,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 4], - [2, 0, 8], [2, 1, 9], - [3, 0, 10], [3, 1, 11], [3, 2, 12], [3, 3, 13], [3, 4, 14], [3, 5, 15], [3, 6, 16], [3, 7, 17], [3, 8, 18], [3, 9, 19], - [4, 0, 20], [4, 4, 24] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace - mixed leading spaces and tabs', async () => { @@ -763,17 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 2, 1], [0, 4, 2], - [1, 0, 4], - [2, 0, 8], [2, 2, 9], - [3, 0, 10], [3, 1, 11], - [4, 0, 12], [4, 1, 13], [4, 2, 14], [4, 3, 15], [4, 4, 16], [4, 5, 17], [4, 6, 18], [4, 7, 19], [4, 8, 20], [4, 9, 21], - [5, 0, 22], [5, 2, 23], - [6, 0, 24], [6, 2, 25], [6, 4, 26], - [7, 0, 28], [7, 2, 29], [7, 4, 30], [7, 6, 31], - [8, 0, 32], [8, 2, 33], [8, 4, 34], [8, 6, 35], [8, 8, 36] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace skips faux indent', async () => { @@ -790,16 +757,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 4, 4], - [1, 0, 8], [1, 2, 9], - [2, 0, 10], [2, 1, 11], - [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], - [4, 0, 22], [4, 2, 23], - [5, 0, 24], [5, 2, 25], [5, 4, 26], - [6, 0, 28], [6, 2, 29], [6, 4, 30], [6, 6, 31], - [7, 0, 32], [7, 2, 33], [7, 4, 34], [7, 6, 35], [7, 8, 36] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts does not emit width for monospace fonts', async () => { @@ -816,13 +774,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 4, 4], - [1, 0, 8], [1, 2, 9], - [2, 0, 10], [2, 1, 11], - [3, 0, 12], [3, 1, 13], [3, 2, 14], [3, 3, 15], [3, 4, 16], [3, 5, 17], [3, 6, 18], [3, 7, 19], [3, 8, 20], [3, 9, 21], - [4, 0, 22], [4, 2, 23], [4, 3, 24], [4, 5, 25], [4, 7, 26], [4, 9, 28], [4, 11, 29], [4, 13, 30], [4, 15, 31], [4, 16, 32], [4, 18, 33], [4, 20, 34], [4, 22, 35], [4, 24, 36] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace in middle but not for one space', async () => { @@ -839,15 +791,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], - [1, 0, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], - [5, 0, 9], [5, 2, 10], - [6, 0, 11], [6, 1, 12], [6, 2, 13] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for all in middle', async () => { @@ -864,14 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], - [5, 0, 13], [5, 3, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with no selections', async () => { @@ -888,11 +825,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13], [2, 10, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with whole line selection', async () => { @@ -909,14 +842,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new OffsetRange(0, 14)] ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], - [4, 0, 7], [4, 1, 8], [4, 2, 9], [4, 3, 10], [4, 4, 11], [4, 5, 12], - [5, 0, 13], [5, 3, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with selection spanning part of whitespace', async () => { @@ -933,12 +859,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new OffsetRange(0, 5)] ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], [3, 7, 13], [3, 10, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with multiple selections', async () => { @@ -955,13 +876,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new OffsetRange(0, 5), new OffsetRange(9, 14)] ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], - [4, 0, 13], [4, 3, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with multiple, initially unsorted selections', async () => { @@ -978,13 +893,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new OffsetRange(9, 14), new OffsetRange(0, 5)] ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], [1, 1, 2], [1, 2, 3], - [2, 0, 4], [2, 1, 5], - [3, 0, 6], [3, 1, 7], [3, 2, 8], [3, 3, 9], [3, 4, 10], [3, 5, 11], [3, 6, 12], - [4, 0, 13], [4, 3, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for selection with selections next to each other', async () => { @@ -999,12 +908,7 @@ suite('viewLineRenderer.renderLine 2', () => { [new OffsetRange(0, 1), new OffsetRange(1, 2), new OffsetRange(2, 3)] ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], - [1, 0, 1], - [2, 0, 2], - [3, 0, 3], [3, 1, 4] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace', async () => { @@ -1021,11 +925,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], [2, 7, 13] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for trailing with leading, inner, and trailing whitespace', async () => { @@ -1042,12 +942,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], - [1, 0, 4], [1, 1, 5], - [2, 0, 6], [2, 1, 7], [2, 2, 8], [2, 3, 9], [2, 4, 10], [2, 5, 11], [2, 6, 12], - [3, 0, 13], [3, 2, 14], [3, 4, 16] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces', async () => { @@ -1064,13 +959,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 1, 1], [0, 2, 2], [0, 3, 3], [0, 4, 4], [0, 5, 5], [0, 6, 6], [0, 7, 7], - [1, 0, 8], [1, 1, 9], - [2, 0, 10], [2, 1, 11], [2, 2, 12], [2, 3, 13], [2, 4, 14], [2, 5, 15], [2, 6, 16], [2, 7, 17], [2, 8, 18], [2, 9, 19], - [3, 0, 20], [3, 2, 21], [3, 4, 22], [3, 6, 23], - [4, 0, 24], [4, 2, 25], [4, 4, 26], [4, 6, 27], [4, 8, 28] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts render whitespace for trailing with line containing only whitespaces', async () => { @@ -1086,10 +975,7 @@ suite('viewLineRenderer.renderLine 2', () => { null ); await assertSnapshot(actual.html.join(''), HTML_EXTENSION); - assert.deepStrictEqual(actual.mapping, [ - [0, 0, 0], [0, 2, 1], - [1, 0, 4], [1, 2, 5] - ]); + await assertSnapshot(actual.mapping); }); test('createLineParts can handle unsorted inline decorations', async () => { From 547885ad2cd496e57b8e88130c619c1ee39e5779 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 4 Oct 2025 17:21:42 +0200 Subject: [PATCH 0805/4355] Fix issue #260239 --- .../common/viewLayout/viewLineRenderer.ts | 47 +++++++++++++++- ...in_RTL_languages_in_recent_versions.0.html | 2 +- ...tional_text_is_rendered_incorrectly.0.html | 1 + ...tional_text_is_rendered_incorrectly.1.snap | 54 ++++++++++++++++++ ..._not_split_large_tokens_in_RTL_text.0.html | 2 +- ...g_whitespace_influences_bidi_layout.0.html | 2 +- ...ce_code_rendering_for_RTL_languages.0.html | 2 +- .../viewLayout/viewLineRenderer.test.ts | 55 +++++++++++++++++++ 8 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.0.html create mode 100644 src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.1.snap diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index f1fe2e39ac8..5aa420249f9 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -509,6 +509,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput if (!input.containsRTL) { // We can never split RTL text, as it ruins the rendering tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures); + } else { + // Split the first token if it contains both leading whitespace and RTL text + tokens = splitLeadingWhitespaceFromRTL(lineContent, tokens); } return new ResolvedRenderLineInput( @@ -638,6 +641,48 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: return result; } +/** + * Splits leading whitespace from the first token if it contains RTL text. + */ +function splitLeadingWhitespaceFromRTL(lineContent: string, tokens: LinePart[]): LinePart[] { + if (tokens.length === 0) { + return tokens; + } + + const firstToken = tokens[0]; + if (!firstToken.containsRTL) { + return tokens; + } + + // Check if the first token starts with whitespace + const firstTokenEndIndex = firstToken.endIndex; + let firstNonWhitespaceIndex = 0; + for (let i = 0; i < firstTokenEndIndex; i++) { + const charCode = lineContent.charCodeAt(i); + if (charCode !== CharCode.Space && charCode !== CharCode.Tab) { + firstNonWhitespaceIndex = i; + break; + } + } + + if (firstNonWhitespaceIndex === 0) { + // No leading whitespace + return tokens; + } + + // Split the first token into leading whitespace and the rest + const result: LinePart[] = []; + result.push(new LinePart(firstNonWhitespaceIndex, firstToken.type, firstToken.metadata, false)); + result.push(new LinePart(firstTokenEndIndex, firstToken.type, firstToken.metadata, firstToken.containsRTL)); + + // Add remaining tokens + for (let i = 1; i < tokens.length; i++) { + result.push(tokens[i]); + } + + return result; +} + function isControlCharacter(charCode: number): boolean { if (charCode < 32) { return (charCode !== CharCode.Tab); @@ -973,7 +1018,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: StringBuilder): RenderL sb.appendString('<option value="العربية">العربية</option> \ No newline at end of file +<option value="العربية">العربية</option> \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.0.html new file mode 100644 index 00000000000..1f4c55eedc2 --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.0.html @@ -0,0 +1 @@ +<p class="myclass" title="العربي">نشاط التدويل!</p> \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.1.snap b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.1.snap new file mode 100644 index 00000000000..5d44a0b2dbf --- /dev/null +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__260239__HTML_containing_bidirectional_text_is_rendered_incorrectly.1.snap @@ -0,0 +1,54 @@ +[ + [ 0, 0, 0 ], + [ 1, 0, 1 ], + [ 2, 0, 2 ], + [ 3, 0, 3 ], + [ 3, 1, 4 ], + [ 3, 2, 5 ], + [ 3, 3, 6 ], + [ 3, 4, 7 ], + [ 4, 0, 8 ], + [ 5, 0, 9 ], + [ 6, 0, 10 ], + [ 6, 1, 11 ], + [ 6, 2, 12 ], + [ 6, 3, 13 ], + [ 6, 4, 14 ], + [ 6, 5, 15 ], + [ 6, 6, 16 ], + [ 7, 0, 17 ], + [ 8, 0, 18 ], + [ 9, 0, 19 ], + [ 9, 1, 20 ], + [ 9, 2, 21 ], + [ 9, 3, 22 ], + [ 9, 4, 23 ], + [ 10, 0, 24 ], + [ 11, 0, 25 ], + [ 12, 0, 26 ], + [ 12, 1, 27 ], + [ 12, 2, 28 ], + [ 12, 3, 29 ], + [ 12, 4, 30 ], + [ 12, 5, 31 ], + [ 13, 0, 32 ], + [ 14, 0, 33 ], + [ 15, 0, 34 ], + [ 15, 1, 35 ], + [ 15, 2, 36 ], + [ 15, 3, 37 ], + [ 15, 4, 38 ], + [ 15, 5, 39 ], + [ 15, 6, 40 ], + [ 15, 7, 41 ], + [ 15, 8, 42 ], + [ 15, 9, 43 ], + [ 15, 10, 44 ], + [ 15, 11, 45 ], + [ 15, 12, 46 ], + [ 16, 0, 47 ], + [ 17, 0, 48 ], + [ 18, 0, 49 ], + [ 19, 0, 50 ], + [ 19, 1, 51 ] +] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html index 81ea223a52c..eee164124b1 100644 --- a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__6885__Does_not_split_large_tokens_in_RTL_text.0.html @@ -1 +1 @@ -את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר. \ No newline at end of file +את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר. \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html index db880d5d98c..4258b886a0e 100644 --- a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue__99589__Rendering_whitespace_influences_bidi_layout.0.html @@ -1 +1 @@ -·‌·‌·‌·‌["🖨️ چاپ فاکتور","🎨 تنظیمات"] \ No newline at end of file +·‌·‌·‌·‌["🖨️ چاپ فاکتور","🎨 تنظیمات"] \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html index e8031a89415..08fb00c6a51 100644 --- a/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html +++ b/src/vs/editor/test/common/viewLayout/__snapshots__/viewLineRenderer_renderLine_issue_microsoft_monaco-editor_280__Improved_source_code_rendering_for_RTL_languages.0.html @@ -1 +1 @@ -var קודמות = "מיותר קודמות צ'ט של, אם לשון העברית שינויים ויש, אם"; \ No newline at end of file +var קודמות = "מיותר קודמות צ'ט של, אם לשון העברית שינויים ויש, אם"; \ No newline at end of file diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 1bc87e10385..dd786b19db5 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -384,6 +384,61 @@ suite('viewLineRenderer.renderLine', () => { await assertSnapshot(inflated.mapping); }); + test('issue #260239: HTML containing bidirectional text is rendered incorrectly', async () => { + // Simulating HTML like:

نشاط التدويل!

+ // The line contains both LTR (class="myclass") and RTL (title="العربي") attribute values + const lineContent = '

نشاط التدويل!

'; + const lineTokens = createViewLineTokens([ + createPart(1, 1), // < + createPart(2, 2), // p + createPart(3, 3), // (space) + createPart(8, 4), // class + createPart(9, 5), // = + createPart(10, 6), // " + createPart(17, 7), // myclass + createPart(18, 6), // " + createPart(19, 3), // (space) + createPart(24, 4), // title + createPart(25, 5), // = + createPart(26, 6), // " + createPart(32, 8), // العربي (RTL text) - 6 Arabic characters from position 26-31 + createPart(33, 6), // " - closing quote at position 32 + createPart(34, 1), // > + createPart(47, 9), // نشاط التدويل! (RTL text) - 13 characters from position 34-46 + createPart(48, 1), // < + createPart(49, 2), // / + createPart(50, 2), // p + createPart(51, 1), // > + ]); + const _actual = renderViewLine(new RenderLineInput( + false, + true, + lineContent, + false, + false, + true, + 0, + lineTokens, + [], + 4, + 0, + 10, + 10, + 10, + -1, + 'none', + false, + false, + null, + null, + 14 + )); + + const inflated = inflateRenderLineOutput(_actual); + await assertSnapshot(inflated.html.join(''), HTML_EXTENSION); + await assertSnapshot(inflated.mapping); + }); + test('issue #6885: Splits large tokens', async () => { // 1 1 1 // 1 2 3 4 5 6 7 8 9 0 1 2 From 9f399eb4d1e66fd4275368102170221ea09884eb Mon Sep 17 00:00:00 2001 From: Benimautner Date: Sun, 5 Oct 2025 07:59:17 +0200 Subject: [PATCH 0806/4355] Fix: don't apply inertial scrolling if input device is a mouse. (#268284) fix: only apply linux touchpad scroll workaround when the input device is actually a touchpad, not a mouse --- src/vs/base/browser/ui/scrollbar/scrollableElement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 33c60da52f6..12753112b43 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -495,7 +495,7 @@ export abstract class AbstractScrollableElement extends Widget { // Check that we are scrolling towards a location which is valid desiredScrollPosition = this._scrollable.validateScrollPosition(desiredScrollPosition); - if (this._options.inertialScroll && (deltaX || deltaY)) { + if (this._options.inertialScroll && (deltaX || deltaY) && !classifier.isPhysicalMouseWheel()) { let startPeriodic = false; // Only start periodic if it's not running if (this._inertialSpeed.X === 0 && this._inertialSpeed.Y === 0) { From 3d6c0d4af67a412538fc5b66ec35760e350a5fce Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sun, 5 Oct 2025 07:59:37 +0200 Subject: [PATCH 0807/4355] Adopt strict no explicit any (#269874) --- eslint.config.js | 18 + src/vs/base/browser/dom.ts | 20 +- src/vs/base/browser/fastDomNode.ts | 3 +- src/vs/base/common/buffer.ts | 2 +- src/vs/base/common/hash.ts | 8 +- src/vs/base/common/worker/webWorker.ts | 140 +++---- .../browser/config/editorConfiguration.ts | 24 +- .../editor/browser/config/migrateOptions.ts | 35 +- .../editContext/native/debugEditContext.ts | 2 +- .../editContext/native/editContextFactory.ts | 3 +- .../native/nativeEditContextUtils.ts | 6 +- .../editor/browser/controller/mouseHandler.ts | 8 +- .../editor/browser/controller/mouseTarget.ts | 17 +- .../browser/controller/pointerHandler.ts | 8 +- src/vs/editor/browser/coreCommands.ts | 8 +- src/vs/editor/browser/editorBrowser.ts | 27 +- src/vs/editor/browser/editorDom.ts | 8 +- src/vs/editor/browser/editorExtensions.ts | 38 +- src/vs/editor/browser/gpu/gpuUtils.ts | 2 +- src/vs/editor/browser/gpu/viewGpuContext.ts | 2 +- .../services/abstractCodeEditorService.ts | 28 +- .../browser/services/bulkEditService.ts | 4 +- .../browser/services/codeEditorService.ts | 10 +- .../browser/services/editorWorkerService.ts | 4 +- .../services/hoverService/hoverService.ts | 2 +- .../services/hoverService/hoverWidget.ts | 5 +- .../editor/browser/services/openerService.ts | 2 +- src/vs/editor/browser/view.ts | 2 +- .../contentWidgets/contentWidgets.ts | 1 + .../browser/viewParts/viewZones/viewZones.ts | 5 +- .../codeEditor/codeEditorContributions.ts | 6 +- .../widget/codeEditor/codeEditorWidget.ts | 23 +- .../components/diffEditorEditors.ts | 2 +- .../widget/diffEditor/delegatingEditorImpl.ts | 6 +- .../widget/diffEditor/diffEditorOptions.ts | 2 +- .../widget/diffEditor/diffEditorWidget.ts | 2 +- .../editor/browser/widget/diffEditor/utils.ts | 9 +- .../multiDiffEditor/diffEditorItemTemplate.ts | 4 +- src/vs/editor/common/config/editorOptions.ts | 356 ++++++++++-------- src/vs/editor/common/config/fontInfo.ts | 71 ++-- .../common/config/fontInfoFromSettings.ts | 37 ++ src/vs/editor/common/core/edits/edit.ts | 1 + src/vs/editor/common/core/edits/lineEdit.ts | 2 +- src/vs/editor/common/core/edits/stringEdit.ts | 4 +- src/vs/editor/common/core/position.ts | 8 +- src/vs/editor/common/core/range.ts | 12 +- src/vs/editor/common/core/selection.ts | 12 +- .../common/cursor/cursorMoveCommands.ts | 4 +- src/vs/editor/common/editorCommon.ts | 14 +- src/vs/editor/common/languages.ts | 17 +- src/vs/editor/common/model.ts | 4 +- .../bracketPairsTree/length.ts | 35 +- .../bracketPairsTree/smallImmutableSet.ts | 2 + src/vs/editor/common/model/textModel.ts | 10 +- .../editor/common/services/editorBaseApi.ts | 6 +- .../editor/common/services/editorWebWorker.ts | 10 +- .../common/services/editorWorkerHost.ts | 4 +- src/vs/editor/common/services/modelService.ts | 61 ++- .../textModelSync/textModelSync.impl.ts | 2 +- src/vs/editor/common/textModelEditSource.ts | 5 +- .../editor/common/viewModel/viewModelImpl.ts | 3 +- .../browser/codeActionController.ts | 3 +- .../codelens/browser/codelensController.ts | 3 +- .../folding/browser/foldingDecorations.ts | 2 +- .../browser/link/goToDefinitionAtPosition.ts | 2 +- .../browser/inlayHintsController.ts | 3 +- .../browser/model/inlineCompletionsModel.ts | 3 +- .../browser/parameterHintsWidget.ts | 3 +- .../browser/unusualLineTerminators.ts | 2 +- .../standalone/browser/standaloneWebWorker.ts | 2 +- src/vs/monaco.d.ts | 16 +- .../electron-browser/profileAnalysisWorker.ts | 2 +- src/vs/workbench/browser/workbench.ts | 4 +- .../chat/browser/chatInlineAnchorWidget.ts | 2 +- .../contrib/chat/browser/codeBlockPart.ts | 3 +- .../codeEditor/browser/toggleWordWrap.ts | 2 +- .../browser/commentThreadZoneWidget.ts | 3 +- .../contrib/debug/browser/disassemblyView.ts | 3 +- .../workbench/contrib/debug/browser/repl.ts | 3 +- .../browser/view/conflictActions.ts | 3 +- .../browser/diff/notebookDiffEditor.ts | 5 +- .../notebook/browser/diff/notebookDiffList.ts | 4 +- .../browser/diff/notebookMultiDiffEditor.ts | 5 +- .../notebook/browser/notebookEditorWidget.ts | 5 +- .../notebook/browser/notebookOptions.ts | 6 +- .../outputEditor/notebookOutputEditor.ts | 5 +- .../browser/view/cellParts/cellComments.ts | 3 +- .../browser/view/renderers/cellRenderer.ts | 4 +- .../common/services/notebookWebWorker.ts | 2 +- .../output/common/outputLinkComputer.ts | 2 +- .../workbench/contrib/scm/browser/activity.ts | 3 +- .../browser/terminalConfigurationService.ts | 3 +- .../terminalConfigurationService.test.ts | 2 +- .../contrib/webview/browser/themeing.ts | 3 +- .../browser/languageDetectionWebWorker.ts | 2 +- .../common/localFileSearchWorkerTypes.ts | 2 +- .../services/search/worker/localFileSearch.ts | 2 +- .../textMateTokenizationWorker.worker.ts | 2 +- 98 files changed, 720 insertions(+), 580 deletions(-) create mode 100644 src/vs/editor/common/config/fontInfoFromSettings.ts diff --git a/eslint.config.js b/eslint.config.js index 8a1819cc4aa..1caf79fa44a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -180,12 +180,30 @@ export default tseslint.config( // vscode TS: strict no explicit `any` { files: [ + 'src/vs/base/browser/fastDomNode.ts', + 'src/vs/base/browser/globalPointerMoveMonitor.ts', + 'src/vs/base/browser/keyboardEvent.ts', + 'src/vs/base/browser/ui/mouseCursor/**', + 'src/vs/base/browser/ui/scrollbar/**', + 'src/vs/base/browser/ui/widget.ts', 'src/vs/base/common/extpath.ts', 'src/vs/base/common/glob.ts', 'src/vs/base/common/path.ts', 'src/vs/base/common/stream.ts', + 'src/vs/base/common/buffer.ts', + 'src/vs/base/common/charCode.ts', + 'src/vs/base/common/hash.ts', + 'src/vs/base/common/keybindingLabels.ts', + 'src/vs/base/common/keybindings.ts', + 'src/vs/base/common/keyCodes.ts', + 'src/vs/base/common/scrollable.ts', + 'src/vs/base/common/uint.ts', + 'src/vs/base/common/uriTransformer.ts', + 'src/vs/base/common/worker/webWorker.ts', 'src/vs/base/node/pfs.ts', 'src/vs/base/parts/contextmenu/**', + 'src/vs/editor/browser/**', + 'src/vs/editor/common/**', // 'src/vs/base/parts/ipc/**', // 'src/vs/base/parts/sandbox/**', 'src/vs/base/parts/storage/**', diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index fb9422fd946..1f9c1ac8ecc 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -166,15 +166,15 @@ export function addDisposableListener(node: EventTarget, type: string, handler: } export interface IAddStandardDisposableListenerSignature { - (node: HTMLElement, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointerdown', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointermove', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointerdown', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointermove', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; } function _wrapAsStandardMouseEvent(targetWindow: Window, handler: (e: IMouseEvent) => void): (e: MouseEvent) => void { return function (e: MouseEvent) { @@ -186,7 +186,7 @@ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e: return handler(new StandardKeyboardEvent(e)); }; } -export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { +export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement | Element | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { let wrapHandler = handler; if (type === 'click' || type === 'mousedown' || type === 'contextmenu') { diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index e3fcfa4dd6a..218a68c4fec 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -291,8 +291,7 @@ export class FastDomNode { return; } this._contain = contain; - // eslint-disable-next-line local/code-no-any-casts - (this.domNode.style).contain = this._contain; + this.domNode.style.contain = this._contain; } public setAttribute(name: string, value: string): void { diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 831357fec08..5de29e7d74f 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -8,7 +8,7 @@ import * as streams from './stream.js'; interface NodeBuffer { allocUnsafe(size: number): Uint8Array; - isBuffer(obj: any): obj is NodeBuffer; + isBuffer(obj: unknown): obj is NodeBuffer; from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array; from(data: string): Uint8Array; } diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 324f057007c..a9a1747a6f9 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -56,16 +56,16 @@ export function stringHash(s: string, hashVal: number) { return hashVal; } -function arrayHash(arr: any[], initialHashVal: number): number { +function arrayHash(arr: unknown[], initialHashVal: number): number { initialHashVal = numberHash(104579, initialHashVal); - return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal); + return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal); } -function objectHash(obj: any, initialHashVal: number): number { +function objectHash(obj: object, initialHashVal: number): number { initialHashVal = numberHash(181387, initialHashVal); return Object.keys(obj).sort().reduce((hashVal, key) => { hashVal = stringHash(key, hashVal); - return doHash(obj[key], hashVal); + return doHash((obj as Record)[key], hashVal); }, initialHashVal); } diff --git a/src/vs/base/common/worker/webWorker.ts b/src/vs/base/common/worker/webWorker.ts index 922a173671f..24626610752 100644 --- a/src/vs/base/common/worker/webWorker.ts +++ b/src/vs/base/common/worker/webWorker.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from '../charCode.js'; -import { onUnexpectedError, transformErrorForSerialization } from '../errors.js'; +import { onUnexpectedError, SerializedError, transformErrorForSerialization } from '../errors.js'; import { Emitter, Event } from '../event.js'; import { Disposable, IDisposable } from '../lifecycle.js'; import { isWeb } from '../platform.js'; @@ -16,12 +16,12 @@ const INITIALIZE = '$initialize'; export interface IWebWorker extends IDisposable { getId(): number; onMessage: Event; - onError: Event; + onError: Event; postMessage(message: Message, transfer: ArrayBuffer[]): void; } let webWorkerWarningLogged = false; -export function logOnceWebWorkerWarning(err: any): void { +export function logOnceWebWorkerWarning(err: unknown): void { if (!isWeb) { // running tests return; @@ -30,7 +30,7 @@ export function logOnceWebWorkerWarning(err: any): void { webWorkerWarningLogged = true; console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq'); } - console.warn(err.message); + console.warn((err as Error).message); } const enum MessageType { @@ -47,7 +47,7 @@ class RequestMessage { public readonly req: string, public readonly channel: string, public readonly method: string, - public readonly args: any[] + public readonly args: unknown[] ) { } } class ReplyMessage { @@ -55,8 +55,8 @@ class ReplyMessage { constructor( public readonly vsWorker: number, public readonly seq: string, - public readonly res: any, - public readonly err: any + public readonly res: unknown, + public readonly err: unknown | SerializedError ) { } } class SubscribeEventMessage { @@ -66,7 +66,7 @@ class SubscribeEventMessage { public readonly req: string, public readonly channel: string, public readonly eventName: string, - public readonly arg: any + public readonly arg: unknown ) { } } class EventMessage { @@ -74,7 +74,7 @@ class EventMessage { constructor( public readonly vsWorker: number, public readonly req: string, - public readonly event: any + public readonly event: unknown ) { } } class UnsubscribeEventMessage { @@ -87,14 +87,14 @@ class UnsubscribeEventMessage { export type Message = RequestMessage | ReplyMessage | SubscribeEventMessage | EventMessage | UnsubscribeEventMessage; interface IMessageReply { - resolve: (value?: any) => void; - reject: (error?: any) => void; + resolve: (value?: unknown) => void; + reject: (error?: unknown) => void; } interface IMessageHandler { - sendMessage(msg: any, transfer?: ArrayBuffer[]): void; - handleMessage(channel: string, method: string, args: any[]): Promise; - handleEvent(channel: string, eventName: string, arg: any): Event; + sendMessage(msg: unknown, transfer?: ArrayBuffer[]): void; + handleMessage(channel: string, method: string, args: unknown[]): Promise; + handleEvent(channel: string, eventName: string, arg: unknown): Event; } class WebWorkerProtocol { @@ -102,7 +102,7 @@ class WebWorkerProtocol { private _workerId: number; private _lastSentReq: number; private _pendingReplies: { [req: string]: IMessageReply }; - private _pendingEmitters: Map>; + private _pendingEmitters: Map>; private _pendingEvents: Map; private _handler: IMessageHandler; @@ -111,7 +111,7 @@ class WebWorkerProtocol { this._handler = handler; this._lastSentReq = 0; this._pendingReplies = Object.create(null); - this._pendingEmitters = new Map>(); + this._pendingEmitters = new Map>(); this._pendingEvents = new Map(); } @@ -119,9 +119,9 @@ class WebWorkerProtocol { this._workerId = workerId; } - public sendMessage(channel: string, method: string, args: any[]): Promise { + public async sendMessage(channel: string, method: string, args: unknown[]): Promise { const req = String(++this._lastSentReq); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject @@ -130,9 +130,9 @@ class WebWorkerProtocol { }); } - public listen(channel: string, eventName: string, arg: any): Event { + public listen(channel: string, eventName: string, arg: unknown): Event { let req: string | null = null; - const emitter = new Emitter({ + const emitter = new Emitter({ onWillAddFirstListener: () => { req = String(++this._lastSentReq); this._pendingEmitters.set(req, emitter); @@ -147,28 +147,28 @@ class WebWorkerProtocol { return emitter.event; } - public handleMessage(message: Message): void { - if (!message || !message.vsWorker) { + public handleMessage(message: unknown): void { + if (!message || !(message as Message).vsWorker) { return; } - if (this._workerId !== -1 && message.vsWorker !== this._workerId) { + if (this._workerId !== -1 && (message as Message).vsWorker !== this._workerId) { return; } - this._handleMessage(message); + this._handleMessage(message as Message); } public createProxyToRemoteChannel(channel: string, sendMessageBarrier?: () => Promise): T { const handler = { - get: (target: any, name: PropertyKey) => { + get: (target: Record, name: PropertyKey) => { if (typeof name === 'string' && !target[name]) { if (propertyIsDynamicEvent(name)) { // onDynamic... - target[name] = (arg: any): Event => { + target[name] = (arg: unknown): Event => { return this.listen(channel, name, arg); }; } else if (propertyIsEvent(name)) { // on... target[name] = this.listen(channel, name, undefined); } else if (name.charCodeAt(0) === CharCode.DollarSign) { // $... - target[name] = async (...myArgs: any[]) => { + target[name] = async (...myArgs: unknown[]) => { await sendMessageBarrier?.(); return this.sendMessage(channel, name, myArgs); }; @@ -206,11 +206,12 @@ class WebWorkerProtocol { if (replyMessage.err) { let err = replyMessage.err; - if (replyMessage.err.$isError) { - err = new Error(); - err.name = replyMessage.err.name; - err.message = replyMessage.err.message; - err.stack = replyMessage.err.stack; + if ((replyMessage.err as SerializedError).$isError) { + const newErr = new Error(); + newErr.name = (replyMessage.err as SerializedError).name; + newErr.message = (replyMessage.err as SerializedError).message; + newErr.stack = (replyMessage.err as SerializedError).stack; + err = newErr; } reply.reject(err); return; @@ -262,8 +263,9 @@ class WebWorkerProtocol { const transfer: ArrayBuffer[] = []; if (msg.type === MessageType.Request) { for (let i = 0; i < msg.args.length; i++) { - if (msg.args[i] instanceof ArrayBuffer) { - transfer.push(msg.args[i]); + const arg = msg.args[i]; + if (arg instanceof ArrayBuffer) { + transfer.push(arg); } } } else if (msg.type === MessageType.Reply) { @@ -325,13 +327,13 @@ export class WebWorkerClient extends Disposable implements IWe })); this._protocol = new WebWorkerProtocol({ - sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { + sendMessage: (msg: Message, transfer: ArrayBuffer[]): void => { this._worker.postMessage(msg, transfer); }, - handleMessage: (channel: string, method: string, args: any[]): Promise => { + handleMessage: (channel: string, method: string, args: unknown[]): Promise => { return this._handleMessage(channel, method, args); }, - handleEvent: (channel: string, eventName: string, arg: any): Event => { + handleEvent: (channel: string, eventName: string, arg: unknown): Event => { return this._handleEvent(channel, eventName, arg); } }); @@ -340,7 +342,7 @@ export class WebWorkerClient extends Disposable implements IWe // Send initialize message this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [ this._worker.getId(), - ]); + ]).then(() => { }); this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; }); this._onModuleLoaded.catch((e) => { @@ -348,44 +350,46 @@ export class WebWorkerClient extends Disposable implements IWe }); } - private _handleMessage(channelName: string, method: string, args: any[]): Promise { + private _handleMessage(channelName: string, method: string, args: unknown[]): Promise { const channel: object | undefined = this._localChannels.get(channelName); if (!channel) { return Promise.reject(new Error(`Missing channel ${channelName} on main thread`)); } - // eslint-disable-next-line local/code-no-any-casts - if (typeof (channel as any)[method] !== 'function') { + + const fn = (channel as Record)[method]; + if (typeof fn !== 'function') { return Promise.reject(new Error(`Missing method ${method} on main thread channel ${channelName}`)); } try { - // eslint-disable-next-line local/code-no-any-casts - return Promise.resolve((channel as any)[method].apply(channel, args)); + return Promise.resolve(fn.apply(channel, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(channelName: string, eventName: string, arg: any): Event { + private _handleEvent(channelName: string, eventName: string, arg: unknown): Event { const channel: object | undefined = this._localChannels.get(channelName); if (!channel) { throw new Error(`Missing channel ${channelName} on main thread`); } if (propertyIsDynamicEvent(eventName)) { - // eslint-disable-next-line local/code-no-any-casts - const event = (channel as any)[eventName].call(channel, arg); + const fn = (channel as Record)[eventName]; + if (typeof fn !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); + } + const event = fn.call(channel, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); } return event; } if (propertyIsEvent(eventName)) { - // eslint-disable-next-line local/code-no-any-casts - const event = (channel as any)[eventName]; + const event = (channel as Record)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`); } - return event; + return event as Event; } throw new Error(`Malformed event name ${eventName}`); } @@ -402,7 +406,7 @@ export class WebWorkerClient extends Disposable implements IWe return this._remoteChannels.get(channel) as Proxied; } - private _onError(message: string, error?: any): void { + private _onError(message: string, error?: unknown): void { console.error(message); console.info(error); } @@ -419,7 +423,8 @@ function propertyIsDynamicEvent(name: string): boolean { } export interface IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any [prop: string]: any; } @@ -439,20 +444,20 @@ export class WebWorkerServer implement constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IWebWorkerServerRequestHandlerFactory) { this._protocol = new WebWorkerProtocol({ - sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { + sendMessage: (msg: Message, transfer: ArrayBuffer[]): void => { postMessage(msg, transfer); }, - handleMessage: (channel: string, method: string, args: any[]): Promise => this._handleMessage(channel, method, args), - handleEvent: (channel: string, eventName: string, arg: any): Event => this._handleEvent(channel, eventName, arg) + handleMessage: (channel: string, method: string, args: unknown[]): Promise => this._handleMessage(channel, method, args), + handleEvent: (channel: string, eventName: string, arg: unknown): Event => this._handleEvent(channel, eventName, arg) }); this.requestHandler = requestHandlerFactory(this); } - public onmessage(msg: any): void { + public onmessage(msg: unknown): void { this._protocol.handleMessage(msg); } - private _handleMessage(channel: string, method: string, args: any[]): Promise { + private _handleMessage(channel: string, method: string, args: unknown[]): Promise { if (channel === DEFAULT_CHANNEL && method === INITIALIZE) { return this.initialize(args[0]); } @@ -461,39 +466,42 @@ export class WebWorkerServer implement if (!requestHandler) { return Promise.reject(new Error(`Missing channel ${channel} on worker thread`)); } - // eslint-disable-next-line local/code-no-any-casts - if (typeof (requestHandler as any)[method] !== 'function') { + + const fn = (requestHandler as Record)[method]; + if (typeof fn !== 'function') { return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`)); } try { - // eslint-disable-next-line local/code-no-any-casts - return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args)); + return Promise.resolve(fn.apply(requestHandler, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(channel: string, eventName: string, arg: any): Event { + private _handleEvent(channel: string, eventName: string, arg: unknown): Event { const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this.requestHandler : this._localChannels.get(channel)); if (!requestHandler) { throw new Error(`Missing channel ${channel} on worker thread`); } if (propertyIsDynamicEvent(eventName)) { - // eslint-disable-next-line local/code-no-any-casts - const event = (requestHandler as any)[eventName].call(requestHandler, arg); + const fn = (requestHandler as Record)[eventName]; + if (typeof fn !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on request handler.`); + } + + const event = fn.call(requestHandler, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on request handler.`); } return event; } if (propertyIsEvent(eventName)) { - // eslint-disable-next-line local/code-no-any-casts - const event = (requestHandler as any)[eventName]; + const event = (requestHandler as Record)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on request handler.`); } - return event; + return event as Event; } throw new Error(`Malformed event name ${eventName}`); } diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 31733468ae9..8196bcd7174 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -16,6 +16,7 @@ import { TabFocus } from './tabFocus.js'; import { ComputeOptionsMemory, ConfigurationChangedEvent, EditorOption, editorOptionsRegistry, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, IEnvironmentalOptions } from '../../common/config/editorOptions.js'; import { EditorZoom } from '../../common/config/editorZoom.js'; import { BareFontInfo, FontInfo, IValidatedEditorOptions } from '../../common/config/fontInfo.js'; +import { createBareFontInfoFromValidatedSettings } from '../../common/config/fontInfoFromSettings.js'; import { IDimension } from '../../common/core/2d/dimension.js'; import { IEditorConfiguration } from '../../common/config/editorConfiguration.js'; import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js'; @@ -114,7 +115,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat private _computeOptions(): ComputedEditorOptions { const partialEnv = this._readEnvConfiguration(); - const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget); + const bareFontInfo = createBareFontInfoFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget); const fontInfo = this._readFontInfo(bareFontInfo); const env: IEnvironmentalOptions = { memory: this._computeOptionsMemory, @@ -143,7 +144,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat outerHeight: this._containerObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value, - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any editContextSupported: typeof (globalThis as any).EditContext === 'function', accessibilitySupport: ( this._accessibilityService.isScreenReaderOptimized() @@ -255,12 +256,12 @@ export interface IEnvConfiguration { } class ValidatedEditorOptions implements IValidatedEditorOptions { - private readonly _values: any[] = []; + private readonly _values: unknown[] = []; public _read(option: EditorOption): T { - return this._values[option]; + return this._values[option] as T; } public get(id: T): FindComputedEditorOptionValueById { - return this._values[id]; + return this._values[id] as FindComputedEditorOptionValueById; } public _write(option: EditorOption, value: T): void { this._values[option] = value; @@ -268,12 +269,12 @@ class ValidatedEditorOptions implements IValidatedEditorOptions { } export class ComputedEditorOptions implements IComputedEditorOptions { - private readonly _values: any[] = []; + private readonly _values: unknown[] = []; public _read(id: EditorOption): T { if (id >= this._values.length) { throw new Error('Cannot read uninitialized value'); } - return this._values[id]; + return this._values[id] as T; } public get(id: T): FindComputedEditorOptionValueById { return this._read(id); @@ -288,8 +289,7 @@ class EditorOptionsUtil { public static validateOptions(options: IEditorOptions): ValidatedEditorOptions { const result = new ValidatedEditorOptions(); for (const editorOption of editorOptionsRegistry) { - // eslint-disable-next-line local/code-no-any-casts - const value = (editorOption.name === '_never_' ? undefined : (options as any)[editorOption.name]); + const value = (editorOption.name === '_never_' ? undefined : (options as Record)[editorOption.name]); result._write(editorOption.id, editorOption.validate(value)); } return result; @@ -342,10 +342,8 @@ class EditorOptionsUtil { let changed = false; for (const editorOption of editorOptionsRegistry) { if (update.hasOwnProperty(editorOption.name)) { - // eslint-disable-next-line local/code-no-any-casts - const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]); - // eslint-disable-next-line local/code-no-any-casts - (options as any)[editorOption.name] = result.newValue; + const result = editorOption.applyUpdate((options as Record)[editorOption.name], (update as Record)[editorOption.name]); + (options as Record)[editorOption.name] = result.newValue; changed = changed || result.didChange; } } diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index a50db6f6a69..1d5584c88ab 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -6,11 +6,11 @@ import { IEditorOptions } from '../../common/config/editorOptions.js'; export interface ISettingsReader { - (key: string): any; + (key: string): unknown; } export interface ISettingsWriter { - (key: string, value: any): void; + (key: string, value: unknown): void; } export class EditorSettingMigration { @@ -19,46 +19,46 @@ export class EditorSettingMigration { constructor( public readonly key: string, - public readonly migrate: (value: any, read: ISettingsReader, write: ISettingsWriter) => void + public readonly migrate: (value: unknown, read: ISettingsReader, write: ISettingsWriter) => void ) { } - apply(options: any): void { + apply(options: unknown): void { const value = EditorSettingMigration._read(options, this.key); const read = (key: string) => EditorSettingMigration._read(options, key); - const write = (key: string, value: any) => EditorSettingMigration._write(options, key, value); + const write = (key: string, value: unknown) => EditorSettingMigration._write(options, key, value); this.migrate(value, read, write); } - private static _read(source: any, key: string): any { - if (typeof source === 'undefined') { + private static _read(source: unknown, key: string): unknown { + if (typeof source === 'undefined' || source === null) { return undefined; } const firstDotIndex = key.indexOf('.'); if (firstDotIndex >= 0) { const firstSegment = key.substring(0, firstDotIndex); - return this._read(source[firstSegment], key.substring(firstDotIndex + 1)); + return this._read((source as Record)[firstSegment], key.substring(firstDotIndex + 1)); } - return source[key]; + return (source as Record)[key]; } - private static _write(target: any, key: string, value: any): void { + private static _write(target: unknown, key: string, value: unknown): void { const firstDotIndex = key.indexOf('.'); if (firstDotIndex >= 0) { const firstSegment = key.substring(0, firstDotIndex); - target[firstSegment] = target[firstSegment] || {}; - this._write(target[firstSegment], key.substring(firstDotIndex + 1), value); + (target as Record)[firstSegment] = (target as Record)[firstSegment] || {}; + this._write((target as Record)[firstSegment], key.substring(firstDotIndex + 1), value); return; } - target[key] = value; + (target as Record)[key] = value; } } -function registerEditorSettingMigration(key: string, migrate: (value: any, read: ISettingsReader, write: ISettingsWriter) => void): void { +function registerEditorSettingMigration(key: string, migrate: (value: unknown, read: ISettingsReader, write: ISettingsWriter) => void): void { EditorSettingMigration.items.push(new EditorSettingMigration(key, migrate)); } -function registerSimpleEditorSettingMigration(key: string, values: [any, any][]): void { +function registerSimpleEditorSettingMigration(key: string, values: [unknown, unknown][]): void { registerEditorSettingMigration(key, (value, read, write) => { if (typeof value !== 'undefined') { for (const [oldValue, newValue] of values) { @@ -158,7 +158,7 @@ const suggestFilteredTypesMapping: Record = { registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => { if (value && typeof value === 'object') { for (const entry of Object.entries(suggestFilteredTypesMapping)) { - const v = value[entry[0]]; + const v = (value as Record)[entry[0]]; if (v === false) { if (typeof read(`suggest.${entry[1]}`) === 'undefined') { write(`suggest.${entry[1]}`, false); @@ -212,8 +212,7 @@ registerEditorSettingMigration('editor.experimentalEditContextEnabled', (value, registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => { if (value && typeof value === 'object') { let toBeModified = false; - // eslint-disable-next-line local/code-no-any-casts - const newValue = {} as any; + const newValue: Record = {}; for (const entry of Object.entries(value)) { if (typeof entry[1] === 'boolean') { toBeModified = true; diff --git a/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts b/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts index 5fa3d47cc1b..a04ad8ed1ca 100644 --- a/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts @@ -88,7 +88,7 @@ export class DebugEditContext { private readonly _listenerMap = new Map(); - addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { if (!listener) { return; } diff --git a/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts b/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts index be5d7964fca..51d258c9e13 100644 --- a/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts +++ b/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts @@ -10,7 +10,6 @@ export namespace EditContext { * Create an edit context. */ export function create(window: Window, options?: EditContextInit): EditContext { - // eslint-disable-next-line local/code-no-any-casts - return new (window as any).EditContext(options); + return new (window as unknown as { EditContext: new (options?: EditContextInit) => EditContext }).EditContext(options); } } diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index 4a077055284..86436a376ec 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -77,12 +77,12 @@ export class FocusTracker extends Disposable { } } -export function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): IDisposable { - // eslint-disable-next-line local/code-no-any-casts +export function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any target.addEventListener(type, listener as any, options); return { dispose() { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any target.removeEventListener(type, listener as any); } }; diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 52fa8965e8c..efcb3419d24 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -241,14 +241,14 @@ export class MouseHandler extends ViewEventHandler { } protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget { - let target = e.target; + let target: HTMLElement | null = e.target; if (!this.viewHelper.viewDomNode.contains(target)) { const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode); if (shadowRoot) { - // eslint-disable-next-line local/code-no-any-casts - target = (shadowRoot).elementsFromPoint(e.posx, e.posy).find( + const potentialTarget = shadowRoot.elementsFromPoint(e.posx, e.posy).find( (el: Element) => this.viewHelper.viewDomNode.contains(el) - ); + ) ?? null; + target = potentialTarget as HTMLElement; } } return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 0664a2945fa..2d8f3f28d5e 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -150,8 +150,7 @@ export class MouseTarget { } public static toString(target: IMouseTarget): string { - // eslint-disable-next-line local/code-no-any-casts - return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target).detail); + return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target as unknown as Record).detail); } } @@ -990,15 +989,15 @@ export class MouseTargetFactory { const shadowRoot = dom.getShadowRoot(ctx.viewDomNode); let range: Range; if (shadowRoot) { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any if (typeof (shadowRoot).caretRangeFromPoint === 'undefined') { range = shadowCaretRangeFromPoint(shadowRoot, coords.clientX, coords.clientY); } else { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any range = (shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY); } } else { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any range = (ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY); } @@ -1041,7 +1040,7 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const hitResult: { offsetNode: Node; offset: number } = (ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { @@ -1094,10 +1093,10 @@ export class MouseTargetFactory { public static doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult { let result: HitTestResult = new UnknownHitTestResult(); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); } @@ -1117,7 +1116,7 @@ function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number) const range = document.createRange(); // Get the element under the point - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any let el: HTMLElement | null = (shadowRoot).elementFromPoint(x, y); // When el is not null, it may be div.monaco-mouse-cursor-text Element, which has not childNodes, we don't need to handle it. if (el?.hasChildNodes()) { diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 0c31574d432..4eb4ca92b39 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -32,7 +32,7 @@ export class PointerEventHandler extends MouseHandler { this._lastPointerType = 'mouse'; - this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => { + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: PointerEvent) => { const pointerType = e.pointerType; if (pointerType === 'mouse') { this._lastPointerType = 'mouse'; @@ -54,8 +54,7 @@ export class PointerEventHandler extends MouseHandler { } private onTap(event: GestureEvent): void { - // eslint-disable-next-line local/code-no-any-casts - if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) { + if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget as HTMLElement)) { return; } @@ -95,8 +94,7 @@ export class PointerEventHandler extends MouseHandler { } protected override _onMouseDown(e: EditorMouseEvent, pointerId: number): void { - // eslint-disable-next-line local/code-no-any-casts - if ((e.browserEvent as any).pointerType === 'touch') { + if ((e.browserEvent as PointerEvent).pointerType === 'touch') { return; } diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index 0fcd8dd6997..186c83dfe5b 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -49,12 +49,12 @@ export abstract class CoreEditorCommand extends EditorCommand { export namespace EditorScroll_ { - const isEditorScrollArgs = function (arg: any): boolean { + const isEditorScrollArgs = function (arg: unknown): boolean { if (!types.isObject(arg)) { return false; } - const scrollArg: RawArguments = arg; + const scrollArg: RawArguments = arg as RawArguments; if (!types.isString(scrollArg.to)) { return false; @@ -235,12 +235,12 @@ export namespace EditorScroll_ { export namespace RevealLine_ { - const isRevealLineArgs = function (arg: any): boolean { + const isRevealLineArgs = function (arg: unknown): boolean { if (!types.isObject(arg)) { return false; } - const reveaLineArg: RawArguments = arg; + const reveaLineArg: RawArguments = arg as RawArguments; if (!types.isNumber(reveaLineArg.lineNumber) && !types.isString(reveaLineArg.lineNumber)) { return false; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index fdb8a1add80..df4e55ca1c6 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -7,9 +7,12 @@ import { IKeyboardEvent } from '../../base/browser/keyboardEvent.js'; import { IMouseEvent, IMouseWheelEvent } from '../../base/browser/mouseEvent.js'; import { IBoundarySashes } from '../../base/browser/ui/sash/sash.js'; import { Event } from '../../base/common/event.js'; -import { IEditorConstructionOptions } from './config/editorConfiguration.js'; +import { MenuId } from '../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; +import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IDiffEditorOptions, IEditorOptions, OverviewRulerPosition } from '../common/config/editorOptions.js'; import { IDimension } from '../common/core/2d/dimension.js'; +import { TextEdit } from '../common/core/edits/textEdit.js'; import { IPosition, Position } from '../common/core/position.js'; import { IRange, Range } from '../common/core/range.js'; import { Selection } from '../common/core/selection.js'; @@ -17,16 +20,13 @@ import { IWordAtPosition } from '../common/core/wordHelper.js'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from '../common/cursorEvents.js'; import { IDiffComputationResult, ILineChange } from '../common/diff/legacyLinesDiffComputer.js'; import * as editorCommon from '../common/editorCommon.js'; -import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../common/model.js'; +import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../common/model.js'; import { InjectedText } from '../common/modelLineProjectionData.js'; +import { TextModelEditSource } from '../common/textModelEditSource.js'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelFontChangedEvent, ModelLineHeightChangedEvent } from '../common/textModelEvents.js'; import { IEditorWhitespace, IViewModel } from '../common/viewModel.js'; import { OverviewRulerZone } from '../common/viewModel/overviewZoneManager.js'; -import { MenuId } from '../../platform/actions/common/actions.js'; -import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; -import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; -import { TextEdit } from '../common/core/edits/textEdit.js'; -import { TextModelEditSource } from '../common/textModelEditSource.js'; +import { IEditorConstructionOptions } from './config/editorConfiguration.js'; /** * A view zone is a full horizontal rectangle that 'pushes' text down. @@ -1123,7 +1123,7 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * @internal */ - getTelemetryData(): { [key: string]: any } | undefined; + getTelemetryData(): object | undefined; /** * Returns the editor's container dom node @@ -1283,6 +1283,15 @@ export interface IActiveCodeEditor extends ICodeEditor { * Warning: the results of this method are inaccurate for positions that are outside the current editor viewport. */ getScrolledVisiblePosition(position: IPosition): { top: number; left: number; height: number }; + + /** + * Change the decorations. All decorations added through this changeAccessor + * will get the ownerId of the editor (meaning they will not show up in other + * editors). + * @see {@link ITextModel.changeDecorations} + * @internal + */ + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T; } /** @@ -1462,7 +1471,7 @@ export function getCodeEditor(thing: unknown): ICodeEditor | null { /** *@internal */ -export function getIEditor(thing: any): editorCommon.IEditor | null { +export function getIEditor(thing: unknown): editorCommon.IEditor | null { if (isCodeEditor(thing) || isDiffEditor(thing)) { return thing; } diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index e05e567da10..5e41bb11c15 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -242,8 +242,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable { // Add a <> keydown event listener that will cancel the monitoring // if something other than a modifier key is pressed - // eslint-disable-next-line local/code-no-any-casts - this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { + this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { const chord = e.toKeyCodeChord(); if (chord.isModifierKey()) { // Allow modifier keys @@ -382,9 +381,8 @@ class RefCountedCssRule { private getCssText(className: string, properties: CssProperties): string { let str = `.${className} {`; for (const prop in properties) { - // eslint-disable-next-line local/code-no-any-casts - const value = (properties as any)[prop] as string | ThemeColor; - let cssValue; + const value = (properties as Record)[prop] as string | ThemeColor; + let cssValue: unknown; if (typeof value === 'object') { cssValue = asCssVariable(value.id); } else { diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 9d2d30b37cd..e175a348e03 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -82,7 +82,7 @@ export interface ICommandKeybindingsOptions extends IKeybindings { /** * the default keybinding arguments */ - args?: any; + args?: unknown; } export interface ICommandMenuOptions { menuId: MenuId; @@ -171,7 +171,7 @@ export abstract class Command { }); } - public abstract runCommand(accessor: ServicesAccessor, args: any): void | Promise; + public abstract runCommand(accessor: ServicesAccessor, args: unknown): void | Promise; } //#endregion Command @@ -214,7 +214,7 @@ export class MultiCommand extends Command { }; } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { const logService = accessor.get(ILogService); const contextKeyService = accessor.get(IContextKeyService); logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`); @@ -254,7 +254,7 @@ export class ProxyCommand extends Command { super(opts); } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { return this.command.runCommand(accessor, args); } } @@ -262,7 +262,7 @@ export class ProxyCommand extends Command { //#region EditorCommand export interface IContributionCommandOptions extends ICommandOptions { - handler: (controller: T, args: any) => void; + handler: (controller: T, args: unknown) => void; } export interface EditorControllerCommand { new(opts: IContributionCommandOptions): EditorCommand; @@ -274,7 +274,7 @@ export abstract class EditorCommand extends Command { */ public static bindToContribution(controllerGetter: (editor: ICodeEditor) => T | null): EditorControllerCommand { return class EditorControllerCommandImpl extends EditorCommand { - private readonly _callback: (controller: T, args: any) => void; + private readonly _callback: (controller: T, args: unknown) => void; constructor(opts: IContributionCommandOptions) { super(opts); @@ -282,7 +282,7 @@ export abstract class EditorCommand extends Command { this._callback = opts.handler; } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const controller = controllerGetter(editor); if (controller) { this._callback(controller, args); @@ -291,11 +291,11 @@ export abstract class EditorCommand extends Command { }; } - public static runEditorCommand( + public static runEditorCommand( accessor: ServicesAccessor, - args: any, + args: T, precondition: ContextKeyExpression | undefined, - runner: (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => void | Promise + runner: (accessor: ServicesAccessor, editor: ICodeEditor, args: T) => void | Promise ): void | Promise { const codeEditorService = accessor.get(ICodeEditorService); @@ -317,11 +317,11 @@ export abstract class EditorCommand extends Command { }); } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { return EditorCommand.runEditorCommand(accessor, args, this.precondition, (accessor, editor, args) => this.runEditorCommand(accessor, editor, args)); } - public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; + public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise; } //#endregion EditorCommand @@ -392,7 +392,7 @@ export abstract class EditorAction extends EditorCommand { } } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise { this.reportTelemetry(accessor, editor); return this.run(accessor, editor, args || {}); } @@ -411,10 +411,10 @@ export abstract class EditorAction extends EditorCommand { accessor.get(ITelemetryService).publicLog2('editorActionInvoked', { name: this.label, id: this.id }); } - public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; + public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise; } -export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => boolean | Promise; +export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: unknown) => boolean | Promise; export class MultiEditorAction extends EditorAction { @@ -438,7 +438,7 @@ export class MultiEditorAction extends EditorAction { }; } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise { for (const impl of this._implementations) { const result = impl[1](accessor, editor, args); if (result) { @@ -458,7 +458,7 @@ export class MultiEditorAction extends EditorAction { export abstract class EditorAction2 extends Action2 { - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { // Find the editor with text focus or active const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); @@ -479,7 +479,7 @@ export abstract class EditorAction2 extends Action2 { }); } - abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): any; + abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): unknown; } //#endregion @@ -487,7 +487,7 @@ export abstract class EditorAction2 extends Action2 { // --- Registration of commands and actions -export function registerModelAndPositionCommand(id: string, handler: (accessor: ServicesAccessor, model: ITextModel, position: Position, ...args: any[]) => any) { +export function registerModelAndPositionCommand(id: string, handler: (accessor: ServicesAccessor, model: ITextModel, position: Position, ...args: unknown[]) => unknown) { CommandsRegistry.registerCommand(id, function (accessor, ...args) { const instaService = accessor.get(IInstantiationService); diff --git a/src/vs/editor/browser/gpu/gpuUtils.ts b/src/vs/editor/browser/gpu/gpuUtils.ts index 65869ac795f..4647d8adc33 100644 --- a/src/vs/editor/browser/gpu/gpuUtils.ts +++ b/src/vs/editor/browser/gpu/gpuUtils.ts @@ -49,7 +49,7 @@ export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: } }); try { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any observer.observe(element, { box: ['device-pixel-content-box'] } as any); } catch { observer.disconnect(); diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 958da4c4e96..bab5b2f9408 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -223,7 +223,7 @@ export class ViewGpuContext extends Disposable { } for (const r of rule.style) { if (!supportsCssRule(r, rule.style)) { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any problemRules.push(`${r}: ${rule.style[r as any]}`); return false; } diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 42538d39b71..abb76fe6ebf 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -211,22 +211,22 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } private readonly _transientWatchers = this._register(new DisposableMap()); - private readonly _modelProperties = new Map>(); + private readonly _modelProperties = new Map>(); - public setModelProperty(resource: URI, key: string, value: any): void { + public setModelProperty(resource: URI, key: string, value: unknown): void { const key1 = resource.toString(); - let dest: Map; + let dest: Map; if (this._modelProperties.has(key1)) { dest = this._modelProperties.get(key1)!; } else { - dest = new Map(); + dest = new Map(); this._modelProperties.set(key1, dest); } dest.set(key, value); } - public getModelProperty(resource: URI, key: string): any { + public getModelProperty(resource: URI, key: string): unknown { const key1 = resource.toString(); if (this._modelProperties.has(key1)) { const innerMap = this._modelProperties.get(key1)!; @@ -235,7 +235,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return undefined; } - public setTransientModelProperty(model: ITextModel, key: string, value: any): void { + public setTransientModelProperty(model: ITextModel, key: string, value: unknown): void { const uri = model.uri.toString(); let w = this._transientWatchers.get(uri); @@ -251,7 +251,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } } - public getTransientModelProperty(model: ITextModel, key: string): any { + public getTransientModelProperty(model: ITextModel, key: string): unknown { const uri = model.uri.toString(); const watcher = this._transientWatchers.get(uri); @@ -262,7 +262,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return watcher.get(key); } - public getTransientModelProperties(model: ITextModel): [string, any][] | undefined { + public getTransientModelProperties(model: ITextModel): [string, unknown][] | undefined { const uri = model.uri.toString(); const watcher = this._transientWatchers.get(uri); @@ -297,7 +297,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC export class ModelTransientSettingWatcher extends Disposable { public readonly uri: string; - private readonly _values: { [key: string]: any }; + private readonly _values: { [key: string]: unknown }; constructor(uri: string, model: ITextModel, owner: AbstractCodeEditorService) { super(); @@ -307,11 +307,11 @@ export class ModelTransientSettingWatcher extends Disposable { this._register(model.onWillDispose(() => owner._removeWatcher(this))); } - public set(key: string, value: any): void { + public set(key: string, value: unknown): void { this._values[key] = value; } - public get(key: string): any { + public get(key: string): unknown { return this._values[key]; } @@ -826,7 +826,7 @@ class DecorationCSSRules { return cssTextArr.join(''); } - private collectBorderSettingsCSSText(opts: any, cssTextArr: string[]): boolean { + private collectBorderSettingsCSSText(opts: unknown, cssTextArr: string[]): boolean { if (this.collectCSSText(opts, ['border', 'borderColor', 'borderRadius', 'borderSpacing', 'borderStyle', 'borderWidth'], cssTextArr)) { cssTextArr.push(strings.format('box-sizing: border-box;')); return true; @@ -834,10 +834,10 @@ class DecorationCSSRules { return false; } - private collectCSSText(opts: any, properties: string[], cssTextArr: string[]): boolean { + private collectCSSText(opts: unknown, properties: string[], cssTextArr: string[]): boolean { const lenBefore = cssTextArr.length; for (const property of properties) { - const value = this.resolveValue(opts[property]); + const value = this.resolveValue((opts as Record)[property] as string | ThemeColor); if (typeof value === 'string') { cssTextArr.push(strings.format(_CSS_MAP[property], value)); } diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index bb0be7cd5d5..282760016b2 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -37,7 +37,7 @@ export class ResourceEdit { export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit { - static is(candidate: any): candidate is IWorkspaceTextEdit { + static is(candidate: unknown): candidate is IWorkspaceTextEdit { if (candidate instanceof ResourceTextEdit) { return true; } @@ -66,7 +66,7 @@ export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit export class ResourceFileEdit extends ResourceEdit implements IWorkspaceFileEdit { - static is(candidate: any): candidate is IWorkspaceFileEdit { + static is(candidate: unknown): candidate is IWorkspaceFileEdit { if (candidate instanceof ResourceFileEdit) { return true; } else { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 733018faea7..3267586bb19 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -49,12 +49,12 @@ export interface ICodeEditorService { resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions; resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null; - setModelProperty(resource: URI, key: string, value: any): void; - getModelProperty(resource: URI, key: string): any; + setModelProperty(resource: URI, key: string, value: unknown): void; + getModelProperty(resource: URI, key: string): unknown; - setTransientModelProperty(model: ITextModel, key: string, value: any): void; - getTransientModelProperty(model: ITextModel, key: string): any; - getTransientModelProperties(model: ITextModel): [string, any][] | undefined; + setTransientModelProperty(model: ITextModel, key: string, value: unknown): void; + getTransientModelProperty(model: ITextModel, key: string): unknown; + getTransientModelProperties(model: ITextModel): [string, unknown][] | undefined; getActiveCodeEditor(): ICodeEditor | null; openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index 3475a737bec..d8cd71a153f 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -415,7 +415,7 @@ class SynchronousWorkerClient implements IWebWorkerClient } export interface IEditorWorkerClient { - fhr(method: string, args: any[]): Promise; + fhr(method: string, args: unknown[]): Promise; } export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { @@ -439,7 +439,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien } // foreign host request - public fhr(method: string, args: any[]): Promise { + public fhr(method: string, args: unknown[]): Promise { throw new Error(`Not implemented!`); } diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 3006620cd38..e43a0239427 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -420,7 +420,7 @@ export class HoverService extends Disposable implements IHoverService { }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any hideHover(false, (e).fromElement === targetElement); }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index eb40d1dec05..a8784044f9e 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -10,7 +10,8 @@ import * as dom from '../../../../base/browser/dom.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { EDITOR_FONT_DEFAULTS, IEditorOptions } from '../../../common/config/editorOptions.js'; +import { IEditorOptions } from '../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; @@ -51,7 +52,7 @@ export class HoverWidget extends Widget implements IHoverWidget { private readonly _hoverPointer: HTMLElement | undefined; private readonly _hoverContainer: HTMLElement; private readonly _target: IHoverTarget; - private readonly _linkHandler: (url: string) => any; + private readonly _linkHandler: (url: string) => void; private _isDisposed: boolean = false; private _hoverPosition: HoverPosition; diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 64765857710..7b02d07004e 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -46,7 +46,7 @@ class CommandOpener implements IOpener { } // execute as command - let args: any = []; + let args: unknown[] = []; try { args = parse(decodeURIComponent(target.query)); } catch { diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index ef187061795..534f302d207 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -664,7 +664,7 @@ export class View extends ViewEventHandler { return new OverviewRuler(this._context, cssClassName); } - public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void { + public change(callback: (changeAccessor: IViewZoneChangeAccessor) => unknown): void { this._viewZones.changeViewZones(callback); this._scheduleRender(); } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index f6bd3004c86..2c9595382e0 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -618,6 +618,7 @@ class AnchorCoordinate { ) { } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function safeInvoke any>(fn: T, thisArg: ThisParameterType, ...args: Parameters): ReturnType | null { try { return fn.call(thisArg, ...args); diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 4b94e5660d3..d26309116ce 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -196,7 +196,7 @@ export class ViewZones extends ViewPart { }; } - public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { + public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => void): boolean { let zonesHaveChanged = false; this._context.viewModel.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { @@ -413,10 +413,11 @@ export class ViewZones extends ViewPart { } } -function safeInvoke1Arg(func: Function, arg1: any): any { +function safeInvoke1Arg(func: Function, arg1: unknown): unknown { try { return func(arg1); } catch (e) { onUnexpectedError(e); + return undefined; } } diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts index 107fa40928c..93d8ed040f8 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts @@ -76,8 +76,8 @@ export class CodeEditorContributions extends Disposable { }, 5000)); } - public saveViewState(): { [key: string]: any } { - const contributionsState: { [key: string]: any } = {}; + public saveViewState(): { [key: string]: unknown } { + const contributionsState: { [key: string]: unknown } = {}; for (const [id, contribution] of this._instances) { if (typeof contribution.saveViewState === 'function') { contributionsState[id] = contribution.saveViewState(); @@ -86,7 +86,7 @@ export class CodeEditorContributions extends Disposable { return contributionsState; } - public restoreViewState(contributionsState: { [key: string]: any }): void { + public restoreViewState(contributionsState: { [key: string]: unknown }): void { for (const [id, contribution] of this._instances) { if (typeof contribution.restoreViewState === 'function') { contribution.restoreViewState(contributionsState[id]); diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index 318bb1ec15b..b2baac71ac6 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -62,6 +62,7 @@ import { IThemeService, registerThemingParticipant } from '../../../../platform/ import { MenuId } from '../../../../platform/actions/common/actions.js'; import { TextModelEditSource, EditSources } from '../../../common/textModelEditSource.js'; import { TextEdit } from '../../../common/core/edits/textEdit.js'; +import { isObject } from '../../../../base/common/types.js'; export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor { @@ -773,7 +774,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public setSelection(editorRange: Range, source?: string): void; public setSelection(selection: ISelection, source?: string): void; public setSelection(editorSelection: Selection, source?: string): void; - public setSelection(something: any, source: string = 'api'): void { + public setSelection(something: unknown, source?: string): void; + public setSelection(something: unknown, source: string = 'api'): void { const isSelection = Selection.isISelection(something); const isRange = Range.isIRange(something); @@ -1029,8 +1031,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const codeEditorState = s as editorCommon.ICodeEditorViewState | null; if (codeEditorState && codeEditorState.cursorState && codeEditorState.viewState) { - // eslint-disable-next-line local/code-no-any-casts - const cursorState = codeEditorState.cursorState; + const cursorState = codeEditorState.cursorState; if (Array.isArray(cursorState)) { if (cursorState.length > 0) { this._modelData.viewModel.restoreCursorState(cursorState); @@ -1078,7 +1079,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._actions.get(id) || null; } - public trigger(source: string | null | undefined, handlerId: string, payload: any): void { + public trigger(source: string | null | undefined, handlerId: string, payload: unknown): void { payload = payload || {}; try { @@ -1137,7 +1138,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } - protected _triggerCommand(handlerId: string, payload: any): void { + protected _triggerCommand(handlerId: string, payload: unknown): void { this._commandService.executeCommand(handlerId, payload); } @@ -1203,11 +1204,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.viewModel.cut(source); } - private _triggerEditorCommand(source: string | null | undefined, handlerId: string, payload: any): boolean { + private _triggerEditorCommand(source: string | null | undefined, handlerId: string, payload: unknown): boolean { const command = EditorExtensionsRegistry.getEditorCommand(handlerId); if (command) { payload = payload || {}; - payload.source = source; + if (isObject(payload)) { + (payload as { source: string | null | undefined }).source = source; + } this._instantiationService.invokeFunction((accessor) => { Promise.resolve(command.runEditorCommand(accessor, this, payload)).then(undefined, onUnexpectedError); }); @@ -1304,7 +1307,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return new EditorDecorationsCollection(this, decorations); } - public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { + public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { if (!this._modelData) { // callback will not be called return null; @@ -1982,7 +1985,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._codeEditorService.resolveDecorationOptions(typeKey, writable); } - public getTelemetryData(): { [key: string]: any } | undefined { + public getTelemetryData(): object | undefined { return this._telemetryData; } @@ -2382,7 +2385,7 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl } } - public onDidChange(listener: (e: IModelDecorationsChangedEvent) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable { + public onDidChange(listener: (e: IModelDecorationsChangedEvent) => unknown, thisArgs?: unknown, disposables?: IDisposable[] | DisposableStore): IDisposable { return this._editor.onDidChangeModelDecorations((e) => { if (this._isChangingDecorations) { return; diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index a8f447a7ce2..f0790e419d8 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -72,7 +72,7 @@ export class DiffEditorEditors extends Disposable { this.isModifiedFocused = observableCodeEditor(this.modified).isFocused; this.isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader)); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any this._argCodeEditorWidgetOptions = null as any; this._register(autorunHandleChanges({ diff --git a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts index 0152dc4eb0f..b3bb052aad9 100644 --- a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts +++ b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts @@ -98,7 +98,7 @@ export abstract class DelegatingEditor extends Disposable implements IEditor { public setSelection(editorRange: Range, source?: string): void; public setSelection(selection: ISelection, source?: string): void; public setSelection(editorSelection: Selection, source?: string): void; - public setSelection(something: any, source: string = 'api'): void { + public setSelection(something: unknown, source: string = 'api'): void { this._targetEditor.setSelection(something, source); } @@ -154,7 +154,7 @@ export abstract class DelegatingEditor extends Disposable implements IEditor { this._targetEditor.focus(); } - public trigger(source: string | null | undefined, handlerId: string, payload: any): void { + public trigger(source: string | null | undefined, handlerId: string, payload: unknown): void { this._targetEditor.trigger(source, handlerId, payload); } @@ -162,7 +162,7 @@ export abstract class DelegatingEditor extends Disposable implements IEditor { return this._targetEditor.createDecorationsCollection(decorations); } - public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { + public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { return this._targetEditor.changeDecorations(callback); } diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index a0a80584641..0d062f73065 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -176,7 +176,7 @@ function validateDiffEditorOptions(options: Readonly, defaul useTrueInlineView: validateBooleanOption(options.experimental?.useTrueInlineView, defaults.experimental.useTrueInlineView!), }, hideUnchangedRegions: { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any enabled: validateBooleanOption(options.hideUnchangedRegions?.enabled ?? (options.experimental as any)?.collapseUnchangedRegions, defaults.hideUnchangedRegions.enabled!), contextLineCount: clampedInt(options.hideUnchangedRegions?.contextLineCount, defaults.hideUnchangedRegions.contextLineCount!, 0, Constants.MAX_SAFE_SMALL_INTEGER), minimumLineCount: clampedInt(options.hideUnchangedRegions?.minimumLineCount, defaults.hideUnchangedRegions.minimumLineCount!, 0, Constants.MAX_SAFE_SMALL_INTEGER), diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 050e79661da..d128b85c182 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -474,7 +474,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._editors.original.restoreViewState(diffEditorState.original); this._editors.modified.restoreViewState(diffEditorState.modified); if (diffEditorState.modelState) { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any this._diffModel.get()?.restoreSerializedState(diffEditorState.modelState as any); } } diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index b60844ad1c1..a9601480ad5 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -181,7 +181,7 @@ function easeOutExpo(t: number, b: number, c: number, d: number): number { } export function deepMerge(source1: T, source2: Partial): T { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const result = {} as any as T; for (const key in source1) { result[key] = source1[key]; @@ -189,9 +189,10 @@ export function deepMerge(source1: T, source2: Partial): T { for (const key in source2) { const source2Value = source2[key]; if (typeof result[key] === 'object' && source2Value && typeof source2Value === 'object') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any result[key] = deepMerge(result[key], source2Value); } else { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any result[key] = source2Value as any; } } @@ -299,14 +300,14 @@ export function applyStyle(domNode: HTMLElement, style: Partial<{ [TKey in keyof /** @description applyStyle */ for (let [key, val] of Object.entries(style)) { if (val && typeof val === 'object' && 'read' in val) { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any val = val.read(reader) as any; } if (typeof val === 'number') { val = `${val}px`; } key = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase()); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any domNode.style[key as any] = val as any; } }); diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 6023bd63ff7..8e82b694509 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -97,10 +97,10 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< h('div.header-content', [ h('div.collapse-button@collapseButton'), h('div.file-path', [ - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any h('div.title.modified.show-file-icons@primaryPath', [] as any), h('div.status.deleted@status', ['R']), - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any h('div.title.original.show-file-icons@secondaryPath', [] as any), ]), h('div.actions@actions'), diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2776cb38315..e66686c159e 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -10,7 +10,7 @@ import * as objects from '../../../base/common/objects.js'; import * as platform from '../../../base/common/platform.js'; import { ScrollbarVisibility } from '../../../base/common/scrollable.js'; import { Constants } from '../../../base/common/uint.js'; -import { FontInfo } from './fontInfo.js'; +import { EDITOR_FONT_DEFAULTS, FONT_VARIATION_OFF, FONT_VARIATION_TRANSLATE, FontInfo } from './fontInfo.js'; import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js'; import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js'; import * as nls from '../../../nls.js'; @@ -34,6 +34,8 @@ export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'bracket */ export type EditorAutoClosingEditStrategy = 'always' | 'auto' | 'never'; +type Unknown = { [K in keyof T]: unknown }; + /** * Configuration options for auto indentation in the editor */ @@ -1081,7 +1083,7 @@ export interface IEditorOption { /** * @internal */ - validate(input: any): V; + validate(input: unknown): V; /** * @internal */ @@ -1123,7 +1125,7 @@ abstract class BaseEditorOption implements IEditor return applyUpdate(value, update); } - public abstract validate(input: any): V; + public abstract validate(input: unknown): V; public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V { return value; @@ -1168,25 +1170,24 @@ abstract class ComputedEditorOption implements IEdito public readonly defaultValue: V; public readonly schema: IConfigurationPropertySchema | undefined = undefined; - constructor(id: K) { + constructor(id: K, defaultValue: V) { this.id = id; this.name = '_never_'; - // eslint-disable-next-line local/code-no-any-casts - this.defaultValue = undefined; + this.defaultValue = defaultValue; } public applyUpdate(value: V | undefined, update: V): ApplyUpdateResult { return applyUpdate(value, update); } - public validate(input: any): V { + public validate(input: unknown): V { return this.defaultValue; } public abstract compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: V): V; } -class SimpleEditorOption implements IEditorOption { +abstract class SimpleEditorOption implements IEditorOption { public readonly id: K; public readonly name: PossibleKeyName; @@ -1204,13 +1205,7 @@ class SimpleEditorOption implements IEditorOption implements IEditorOption extends SimpleEditorOption extends SimpleEditorOption(value: any, defaultValue: T, minimum: number, maximum: number): number | T { - if (typeof value === 'undefined') { - return defaultValue; +export function clampedInt(value: unknown, defaultValue: T, minimum: number, maximum: number): number | T { + if (typeof value === 'string') { + value = parseInt(value, 10); } - let r = parseInt(value, 10); - if (isNaN(r)) { + if (typeof value !== 'number' || isNaN(value)) { return defaultValue; } + let r = value; r = Math.max(minimum, r); r = Math.min(maximum, r); return r | 0; @@ -1264,7 +1259,7 @@ export function clampedInt(value: any, defaultValue: T, minimum: number, maxi class EditorIntOption extends SimpleEditorOption { - public static clampedInt(value: any, defaultValue: T, minimum: number, maximum: number): number | T { + public static clampedInt(value: unknown, defaultValue: T, minimum: number, maximum: number): number | T { return clampedInt(value, defaultValue, minimum, maximum); } @@ -1283,14 +1278,14 @@ class EditorIntOption extends SimpleEditorOption(value: any, defaultValue: T, minimum: number, maximum: number): number | T { +export function clampedFloat(value: unknown, defaultValue: T, minimum: number, maximum: number): number | T { if (typeof value === 'undefined') { return defaultValue; } @@ -1313,15 +1308,14 @@ class EditorFloatOption extends SimpleEditorOption number; @@ -1339,14 +1333,14 @@ class EditorFloatOption extends SimpleEditorOption extends SimpleEditorOption { - public static string(value: any, defaultValue: string): string { + public static string(value: unknown, defaultValue: string): string { if (typeof value !== 'string') { return defaultValue; } @@ -1361,7 +1355,7 @@ class EditorStringOption extends SimpleEditorOption extends SimpleEditorOption(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray, renamedValues?: Record): T { +export function stringSet(value: unknown, defaultValue: T, allowedValues: ReadonlyArray, renamedValues?: Record): T { if (typeof value !== 'string') { return defaultValue; } if (renamedValues && value in renamedValues) { return renamedValues[value]; } - if (allowedValues.indexOf(value) === -1) { + if (allowedValues.indexOf(value as T) === -1) { return defaultValue; } - return value; + return value as T; } class EditorStringEnumOption extends SimpleEditorOption { @@ -1389,15 +1383,14 @@ class EditorStringEnumOption extends S constructor(id: K, name: PossibleKeyName, defaultValue: V, allowedValues: ReadonlyArray, schema: IConfigurationPropertySchema | undefined = undefined) { if (typeof schema !== 'undefined') { schema.type = 'string'; - // eslint-disable-next-line local/code-no-any-casts - schema.enum = allowedValues; + schema.enum = allowedValues.slice(0); schema.default = defaultValue; } super(id, name, defaultValue, schema); this._allowedValues = allowedValues; } - public override validate(input: any): V { + public override validate(input: unknown): V { return stringSet(input, this.defaultValue, this._allowedValues); } } @@ -1418,15 +1411,14 @@ class EditorEnumOption extends Base this._convert = convert; } - public validate(input: any): V { + public validate(input: unknown): V { if (typeof input !== 'string') { return this.defaultValue; } if (this._allowedValues.indexOf(input) === -1) { return this.defaultValue; } - // eslint-disable-next-line local/code-no-any-casts - return this._convert(input); + return this._convert(input); } } @@ -1468,7 +1460,7 @@ class EditorAccessibilitySupport extends BaseEditorOption; return { insertSpace: boolean(input.insertSpace, this.defaultValue.insertSpace), ignoreEmptyLines: boolean(input.ignoreEmptyLines, this.defaultValue.ignoreEmptyLines), @@ -1663,7 +1655,7 @@ export function cursorStyleFromString(cursorStyle: 'line' | 'block' | 'underline class EditorClassName extends ComputedEditorOption { constructor() { - super(EditorOption.editorClassName); + super(EditorOption.editorClassName, ''); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: string): string { @@ -1853,19 +1845,19 @@ class EditorFind extends BaseEditorOption; return { cursorMoveOnType: boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType), findOnType: boolean(input.findOnType, this.defaultValue.findOnType), - seedSearchStringFromSelection: typeof _input.seedSearchStringFromSelection === 'boolean' - ? (_input.seedSearchStringFromSelection ? 'always' : 'never') + seedSearchStringFromSelection: typeof input.seedSearchStringFromSelection === 'boolean' + ? (input.seedSearchStringFromSelection ? 'always' : 'never') : stringSet<'never' | 'always' | 'selection'>(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection, ['never', 'always', 'selection']), - autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' - ? (_input.autoFindInSelection ? 'always' : 'never') + autoFindInSelection: typeof input.autoFindInSelection === 'boolean' + ? (input.autoFindInSelection ? 'always' : 'never') : stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), globalFindClipboard: boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), addExtraSpaceOnTop: boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), @@ -1908,7 +1900,7 @@ export class EditorFontLigatures extends BaseEditorOption { // Text is laid out using default settings. - public static OFF = 'normal'; + public static OFF = FONT_VARIATION_OFF; // Translate `fontWeight` config to the `font-variation-settings` CSS property. - public static TRANSLATE = 'translate'; + public static TRANSLATE = FONT_VARIATION_TRANSLATE; constructor() { super( @@ -1962,7 +1954,7 @@ export class EditorFontVariations extends BaseEditorOption { constructor() { - super(EditorOption.fontInfo); + super(EditorOption.fontInfo, new FontInfo({ + pixelRatio: 0, + fontFamily: '', + fontWeight: '', + fontSize: 0, + fontFeatureSettings: '', + fontVariationSettings: '', + lineHeight: 0, + letterSpacing: 0, + isMonospace: false, + typicalHalfwidthCharacterWidth: 0, + typicalFullwidthCharacterWidth: 0, + canUseHalfwidthRightwardsArrow: false, + spaceWidth: 0, + middotWidth: 0, + wsmiddotWidth: 0, + maxDigitWidth: 0, + }, false)); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: FontInfo): FontInfo { @@ -2010,7 +2019,7 @@ class EditorFontInfo extends ComputedEditorOption { constructor() { - super(EditorOption.effectiveCursorStyle); + super(EditorOption.effectiveCursorStyle, TextEditorCursorStyle.Line); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: TextEditorCursorStyle): TextEditorCursorStyle { @@ -2027,7 +2036,7 @@ class EffectiveCursorStyle extends ComputedEditorOption { constructor() { - super(EditorOption.effectiveEditContext); + super(EditorOption.effectiveEditContext, false); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions): boolean { @@ -2042,7 +2051,7 @@ class EffectiveEditContextEnabled extends ComputedEditorOption { constructor() { - super(EditorOption.effectiveAllowVariableFonts); + super(EditorOption.effectiveAllowVariableFonts, false); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions): boolean { @@ -2074,7 +2083,7 @@ class EditorFontSize extends SimpleEditorOption { ); } - public override validate(input: any): number { + public override validate(input: unknown): number { const r = EditorFloatOption.float(input, this.defaultValue); if (r === 0) { return EDITOR_FONT_DEFAULTS.fontSize; @@ -2122,7 +2131,7 @@ class EditorFontWeight extends BaseEditorOption; return { multiple: stringSet(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']), - multipleDefinitions: input.multipleDefinitions ?? stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleDeclarations: input.multipleDeclarations ?? stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleImplementations: input.multipleImplementations ?? stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleReferences: input.multipleReferences ?? stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), - multipleTests: input.multipleTests ?? stringSet(input.multipleTests, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDefinitions: stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTypeDefinitions: stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleDeclarations: stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleImplementations: stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleReferences: stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTests: stringSet(input.multipleTests, 'peek', ['peek', 'gotoAndPeek', 'goto']), alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), @@ -2360,11 +2369,11 @@ class EditorHover extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), delay: EditorIntOption.clampedInt(input.delay, this.defaultValue.delay, 0, 10000), @@ -2576,7 +2585,44 @@ export interface IMinimapLayoutInput { export class EditorLayoutInfoComputer extends ComputedEditorOption { constructor() { - super(EditorOption.layoutInfo); + super(EditorOption.layoutInfo, { + width: 0, + height: 0, + glyphMarginLeft: 0, + glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 0, + lineNumbersLeft: 0, + lineNumbersWidth: 0, + decorationsLeft: 0, + decorationsWidth: 0, + contentLeft: 0, + contentWidth: 0, + minimap: { + renderMinimap: RenderMinimap.None, + minimapLeft: 0, + minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 0, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 0, + }, + viewportColumn: 0, + isWordWrapMinified: false, + isViewportWrapping: false, + wrappingColumn: -1, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + overviewRuler: { + top: 0, + width: 0, + height: 0, + right: 0 + } + }); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorLayoutInfo): EditorLayoutInfo { @@ -2959,7 +3005,7 @@ class WrappingStrategy extends BaseEditorOption(input, 'simple', ['simple', 'advanced']); } @@ -3024,11 +3070,11 @@ class EditorLightbulb extends BaseEditorOption; return { enabled: stringSet(input.enabled, this.defaultValue.enabled, [ShowLightbulbIconMode.Off, ShowLightbulbIconMode.OnCode, ShowLightbulbIconMode.On]) }; @@ -3097,11 +3143,11 @@ class EditorStickyScroll extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), maxLineCount: EditorIntOption.clampedInt(input.maxLineCount, this.defaultValue.maxLineCount, 1, 20), @@ -3198,11 +3244,11 @@ class EditorInlayHints extends BaseEditorOption; if (typeof input.enabled === 'boolean') { input.enabled = input.enabled ? 'on' : 'off'; } @@ -3226,7 +3272,7 @@ class EditorLineDecorationsWidth extends BaseEditorOption; // Validate mark section header regex let markSectionHeaderRegex = this.defaultValue.markSectionHeaderRegex; - const inputRegex = _input.markSectionHeaderRegex; + const inputRegex = input.markSectionHeaderRegex; if (typeof inputRegex === 'string') { try { new RegExp(inputRegex, 'd'); @@ -3485,8 +3531,8 @@ class EditorMinimap extends BaseEditorOption; return { top: EditorIntOption.clampedInt(input.top, 0, 0, 1000), @@ -3610,11 +3656,11 @@ class EditorParameterHints extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), cycle: boolean(input.cycle, this.defaultValue.cycle) @@ -3629,7 +3675,7 @@ class EditorParameterHints extends BaseEditorOption { constructor() { - super(EditorOption.pixelRatio); + super(EditorOption.pixelRatio, 1); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: number): number { @@ -3646,7 +3692,7 @@ class PlaceholderOption extends BaseEditorOption all on/off const value = input ? 'on' : 'off'; @@ -3800,14 +3846,14 @@ class EditorRenderLineNumbersOption extends BaseEditorOption string) | null = this.defaultValue.renderFn; if (typeof lineNumbers !== 'undefined') { if (typeof lineNumbers === 'function') { renderType = RenderLineNumbersType.Custom; - renderFn = lineNumbers; + renderFn = lineNumbers as ((lineNumber: number) => string); } else if (lineNumbers === 'interval') { renderType = RenderLineNumbersType.Interval; } else if (lineNumbers === 'relative') { @@ -3894,7 +3940,7 @@ class EditorRulers extends BaseEditorOption; const horizontalScrollbarSize = EditorIntOption.clampedInt(input.horizontalScrollbarSize, this.defaultValue.horizontalScrollbarSize, 0, 1000); const verticalScrollbarSize = EditorIntOption.clampedInt(input.verticalScrollbarSize, this.defaultValue.verticalScrollbarSize, 0, 1000); return { @@ -4316,19 +4362,19 @@ class UnicodeHighlight extends BaseEditorOption; return { nonBasicASCII: primitiveSet(input.nonBasicASCII, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), invisibleCharacters: boolean(input.invisibleCharacters, this.defaultValue.invisibleCharacters), ambiguousCharacters: boolean(input.ambiguousCharacters, this.defaultValue.ambiguousCharacters), includeComments: primitiveSet(input.includeComments, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), includeStrings: primitiveSet(input.includeStrings, inUntrustedWorkspace, [true, false, inUntrustedWorkspace]), - allowedCharacters: this.validateBooleanMap(_input.allowedCharacters, this.defaultValue.allowedCharacters), - allowedLocales: this.validateBooleanMap(_input.allowedLocales, this.defaultValue.allowedLocales), + allowedCharacters: this.validateBooleanMap(input.allowedCharacters, this.defaultValue.allowedCharacters), + allowedLocales: this.validateBooleanMap(input.allowedLocales, this.defaultValue.allowedLocales), }; } @@ -4568,11 +4614,11 @@ class InlineEditorSuggest extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), mode: stringSet(input.mode, this.defaultValue.mode, ['prefix', 'subword', 'subwordSmart']), @@ -4583,18 +4629,34 @@ class InlineEditorSuggest extends BaseEditorOption; + return { + enabled: boolean(input.enabled, this.defaultValue.edits.enabled), + showCollapsed: boolean(input.showCollapsed, this.defaultValue.edits.showCollapsed), + allowCodeShifting: stringSet(input.allowCodeShifting, this.defaultValue.edits.allowCodeShifting, ['always', 'horizontal', 'never']), + renderSideBySide: stringSet(input.renderSideBySide, this.defaultValue.edits.renderSideBySide, ['never', 'auto']), + }; + } + + private _validateExperimental(_input: unknown): InternalInlineSuggestOptions['experimental'] { + if (!_input || typeof _input !== 'object') { + return this.defaultValue.experimental; + } + const input = _input as Unknown; + return { + suppressInlineSuggestions: EditorStringOption.string(input.suppressInlineSuggestions, this.defaultValue.experimental.suppressInlineSuggestions), + showOnSuggestConflict: stringSet(input.showOnSuggestConflict, this.defaultValue.experimental.showOnSuggestConflict, ['always', 'never', 'whenSuggestListIsIncomplete']), + emptyResponseInformation: boolean(input.emptyResponseInformation, this.defaultValue.experimental.emptyResponseInformation), }; } } @@ -4647,11 +4709,11 @@ class BracketPairColorization extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), independentColorPoolPerBracketType: boolean(input.independentColorPoolPerBracketType, this.defaultValue.independentColorPoolPerBracketType), @@ -4765,11 +4827,11 @@ class GuideOptions extends BaseEditorOption; return { bracketPairs: primitiveSet(input.bracketPairs, this.defaultValue.bracketPairs, [true, false, 'active']), bracketPairsHorizontal: primitiveSet(input.bracketPairsHorizontal, this.defaultValue.bracketPairsHorizontal, [true, false, 'active']), @@ -4782,8 +4844,7 @@ class GuideOptions extends BaseEditorOption(value: unknown, defaultValue: T, allowedValues: T[]): T { - // eslint-disable-next-line local/code-no-any-casts - const idx = allowedValues.indexOf(value as any); + const idx = allowedValues.indexOf(value as T); if (idx === -1) { return defaultValue; } @@ -5237,11 +5298,11 @@ class EditorSuggest extends BaseEditorOption; return { insertMode: stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']), filterGraceful: boolean(input.filterGraceful, this.defaultValue.filterGraceful), @@ -5325,7 +5386,7 @@ class SmartSelect extends BaseEditorOption> { + public validate(input: unknown): Readonly> { if (!input || typeof input !== 'object') { return this.defaultValue; } @@ -5372,7 +5433,7 @@ class WordSegmenterLocales extends BaseEditorOption { constructor() { - super(EditorOption.wrappingInfo); + super(EditorOption.wrappingInfo, { + isDominatedByLongLines: false, + isWordWrapMinified: false, + isViewportWrapping: false, + wrappingColumn: -1 + }); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorWrappingInfo): EditorWrappingInfo { @@ -5549,11 +5615,11 @@ class EditorDropIntoEditor extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), showDropSelector: stringSet(input.showDropSelector, this.defaultValue.showDropSelector, ['afterDrop', 'never']), @@ -5616,11 +5682,11 @@ class EditorPasteAs extends BaseEditorOption; return { enabled: boolean(input.enabled, this.defaultValue.enabled), showPasteSelector: stringSet(input.showPasteSelector, this.defaultValue.showPasteSelector, ['afterPaste', 'never']), @@ -5630,29 +5696,10 @@ class EditorPasteAs extends BaseEditorOption[] = []; +export const editorOptionsRegistry: IEditorOption[] = []; function register(option: IEditorOption): IEditorOption { editorOptionsRegistry[option.id] = option; @@ -6746,6 +6793,7 @@ export const EditorOptions = { type EditorOptionsType = typeof EditorOptions; type FindEditorOptionsKeyById = { [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never }[keyof EditorOptionsType]; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ComputedEditorOptionValue> = T extends IEditorOption ? R : never; export type FindComputedEditorOptionValueById = NonNullable]>>; diff --git a/src/vs/editor/common/config/fontInfo.ts b/src/vs/editor/common/config/fontInfo.ts index eae8d0891dd..2f96b463ccd 100644 --- a/src/vs/editor/common/config/fontInfo.ts +++ b/src/vs/editor/common/config/fontInfo.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as platform from '../../../base/common/platform.js'; -import { EditorFontVariations, EditorOptions, EditorOption, FindComputedEditorOptionValueById, EDITOR_FONT_DEFAULTS } from './editorOptions.js'; +import { EditorOption, FindComputedEditorOptionValueById } from './editorOptions.js'; import { EditorZoom } from './editorZoom.js'; /** @@ -31,35 +31,7 @@ export class BareFontInfo { /** * @internal */ - public static createFromValidatedSettings(options: IValidatedEditorOptions, pixelRatio: number, ignoreEditorZoom: boolean): BareFontInfo { - const fontFamily = options.get(EditorOption.fontFamily); - const fontWeight = options.get(EditorOption.fontWeight); - const fontSize = options.get(EditorOption.fontSize); - const fontFeatureSettings = options.get(EditorOption.fontLigatures); - const fontVariationSettings = options.get(EditorOption.fontVariations); - const lineHeight = options.get(EditorOption.lineHeight); - const letterSpacing = options.get(EditorOption.letterSpacing); - return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, fontVariationSettings, lineHeight, letterSpacing, pixelRatio, ignoreEditorZoom); - } - - /** - * @internal - */ - public static createFromRawSettings(opts: { fontFamily?: string; fontWeight?: string; fontSize?: number; fontLigatures?: boolean | string; fontVariations?: boolean | string; lineHeight?: number; letterSpacing?: number }, pixelRatio: number, ignoreEditorZoom: boolean = false): BareFontInfo { - const fontFamily = EditorOptions.fontFamily.validate(opts.fontFamily); - const fontWeight = EditorOptions.fontWeight.validate(opts.fontWeight); - const fontSize = EditorOptions.fontSize.validate(opts.fontSize); - const fontFeatureSettings = EditorOptions.fontLigatures2.validate(opts.fontLigatures); - const fontVariationSettings = EditorOptions.fontVariations.validate(opts.fontVariations); - const lineHeight = EditorOptions.lineHeight.validate(opts.lineHeight); - const letterSpacing = EditorOptions.letterSpacing.validate(opts.letterSpacing); - return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, fontVariationSettings, lineHeight, letterSpacing, pixelRatio, ignoreEditorZoom); - } - - /** - * @internal - */ - private static _create(fontFamily: string, fontWeight: string, fontSize: number, fontFeatureSettings: string, fontVariationSettings: string, lineHeight: number, letterSpacing: number, pixelRatio: number, ignoreEditorZoom: boolean): BareFontInfo { + public static _create(fontFamily: string, fontWeight: string, fontSize: number, fontFeatureSettings: string, fontVariationSettings: string, lineHeight: number, letterSpacing: number, pixelRatio: number, ignoreEditorZoom: boolean): BareFontInfo { if (lineHeight === 0) { lineHeight = GOLDEN_LINE_HEIGHT_RATIO * fontSize; } else if (lineHeight < MINIMUM_LINE_HEIGHT) { @@ -77,9 +49,9 @@ export class BareFontInfo { fontSize *= editorZoomLevelMultiplier; lineHeight *= editorZoomLevelMultiplier; - if (fontVariationSettings === EditorFontVariations.TRANSLATE) { + if (fontVariationSettings === FONT_VARIATION_TRANSLATE) { if (fontWeight === 'normal' || fontWeight === 'bold') { - fontVariationSettings = EditorFontVariations.OFF; + fontVariationSettings = FONT_VARIATION_OFF; } else { const fontWeightAsNumber = parseInt(fontWeight, 10); fontVariationSettings = `'wght' ${fontWeightAsNumber}`; @@ -235,3 +207,38 @@ export class FontInfo extends BareFontInfo { ); } } +/** + * @internal + */ +export const FONT_VARIATION_OFF = 'normal'; +/** + * @internal + */ +export const FONT_VARIATION_TRANSLATE = 'translate'; + +/** + * @internal + */ +export const DEFAULT_WINDOWS_FONT_FAMILY = 'Consolas, \'Courier New\', monospace'; +/** + * @internal + */ +export const DEFAULT_MAC_FONT_FAMILY = 'Menlo, Monaco, \'Courier New\', monospace'; +/** + * @internal + */ +export const DEFAULT_LINUX_FONT_FAMILY = '\'Droid Sans Mono\', \'monospace\', monospace'; +/** + * @internal + */ +export const EDITOR_FONT_DEFAULTS = { + fontFamily: ( + platform.isMacintosh ? DEFAULT_MAC_FONT_FAMILY : (platform.isWindows ? DEFAULT_WINDOWS_FONT_FAMILY : DEFAULT_LINUX_FONT_FAMILY) + ), + fontWeight: 'normal', + fontSize: ( + platform.isMacintosh ? 12 : 14 + ), + lineHeight: 0, + letterSpacing: 0, +}; diff --git a/src/vs/editor/common/config/fontInfoFromSettings.ts b/src/vs/editor/common/config/fontInfoFromSettings.ts new file mode 100644 index 00000000000..1da693947ad --- /dev/null +++ b/src/vs/editor/common/config/fontInfoFromSettings.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorOption, EditorOptions } from './editorOptions.js'; +import { IValidatedEditorOptions, BareFontInfo } from './fontInfo.js'; + +export function createBareFontInfoFromValidatedSettings(options: IValidatedEditorOptions, pixelRatio: number, ignoreEditorZoom: boolean): BareFontInfo { + const fontFamily = options.get(EditorOption.fontFamily); + const fontWeight = options.get(EditorOption.fontWeight); + const fontSize = options.get(EditorOption.fontSize); + const fontFeatureSettings = options.get(EditorOption.fontLigatures); + const fontVariationSettings = options.get(EditorOption.fontVariations); + const lineHeight = options.get(EditorOption.lineHeight); + const letterSpacing = options.get(EditorOption.letterSpacing); + return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, fontVariationSettings, lineHeight, letterSpacing, pixelRatio, ignoreEditorZoom); +} + +export function createBareFontInfoFromRawSettings(opts: { + fontFamily?: unknown; + fontWeight?: unknown; + fontSize?: unknown; + fontLigatures?: unknown; + fontVariations?: unknown; + lineHeight?: unknown; + letterSpacing?: unknown; +}, pixelRatio: number, ignoreEditorZoom: boolean = false): BareFontInfo { + const fontFamily = EditorOptions.fontFamily.validate(opts.fontFamily); + const fontWeight = EditorOptions.fontWeight.validate(opts.fontWeight); + const fontSize = EditorOptions.fontSize.validate(opts.fontSize); + const fontFeatureSettings = EditorOptions.fontLigatures2.validate(opts.fontLigatures); + const fontVariationSettings = EditorOptions.fontVariations.validate(opts.fontVariations); + const lineHeight = EditorOptions.lineHeight.validate(opts.lineHeight); + const letterSpacing = EditorOptions.letterSpacing.validate(opts.letterSpacing); + return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, fontVariationSettings, lineHeight, letterSpacing, pixelRatio, ignoreEditorZoom); +} diff --git a/src/vs/editor/common/core/edits/edit.ts b/src/vs/editor/common/core/edits/edit.ts index c4914faa800..b55c1942e3a 100644 --- a/src/vs/editor/common/core/edits/edit.ts +++ b/src/vs/editor/common/core/edits/edit.ts @@ -7,6 +7,7 @@ import { sumBy } from '../../../../base/common/arrays.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; import { OffsetRange } from '../ranges/offsetRange.js'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export abstract class BaseEdit = BaseReplacement, TEdit extends BaseEdit = BaseEdit> { constructor( public readonly replacements: readonly T[], diff --git a/src/vs/editor/common/core/edits/lineEdit.ts b/src/vs/editor/common/core/edits/lineEdit.ts index de07f76d99b..21ec40dff28 100644 --- a/src/vs/editor/common/core/edits/lineEdit.ts +++ b/src/vs/editor/common/core/edits/lineEdit.ts @@ -406,7 +406,7 @@ export namespace SerializedLineReplacement { && typeof thing[0] === 'number' && typeof thing[1] === 'number' && Array.isArray(thing[2]) - && thing[2].every((e: any) => typeof e === 'string') + && thing[2].every((e: unknown) => typeof e === 'string') ); } } diff --git a/src/vs/editor/common/core/edits/stringEdit.ts b/src/vs/editor/common/core/edits/stringEdit.ts index 02345d7731d..4d00122d459 100644 --- a/src/vs/editor/common/core/edits/stringEdit.ts +++ b/src/vs/editor/common/core/edits/stringEdit.ts @@ -9,6 +9,7 @@ import { StringText } from '../text/abstractText.js'; import { BaseEdit, BaseReplacement } from './edit.js'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export abstract class BaseStringEdit = BaseStringReplacement, TEdit extends BaseStringEdit = BaseStringEdit> extends BaseEdit { get TReplacement(): T { throw new Error('TReplacement is not defined for BaseStringEdit'); @@ -20,7 +21,7 @@ export abstract class BaseStringEdit = BaseSt } let result = edits[0]; for (let i = 1; i < edits.length; i++) { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any result = result.compose(edits[i]) as any; } return result; @@ -189,6 +190,7 @@ export abstract class BaseStringEdit = BaseSt } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export abstract class BaseStringReplacement = BaseStringReplacement> extends BaseReplacement { constructor( range: OffsetRange, diff --git a/src/vs/editor/common/core/position.ts b/src/vs/editor/common/core/position.ts index e3033e953f9..713362dd70b 100644 --- a/src/vs/editor/common/core/position.ts +++ b/src/vs/editor/common/core/position.ts @@ -167,11 +167,11 @@ export class Position { /** * Test if `obj` is an `IPosition`. */ - public static isIPosition(obj: any): obj is IPosition { + public static isIPosition(obj: unknown): obj is IPosition { return ( - obj - && (typeof obj.lineNumber === 'number') - && (typeof obj.column === 'number') + !!obj + && (typeof (obj as IPosition).lineNumber === 'number') + && (typeof (obj as IPosition).column === 'number') ); } diff --git a/src/vs/editor/common/core/range.ts b/src/vs/editor/common/core/range.ts index 81658a4aeb5..72a1086d98e 100644 --- a/src/vs/editor/common/core/range.ts +++ b/src/vs/editor/common/core/range.ts @@ -390,13 +390,13 @@ export class Range { /** * Test if `obj` is an `IRange`. */ - public static isIRange(obj: any): obj is IRange { + public static isIRange(obj: unknown): obj is IRange { return ( - obj - && (typeof obj.startLineNumber === 'number') - && (typeof obj.startColumn === 'number') - && (typeof obj.endLineNumber === 'number') - && (typeof obj.endColumn === 'number') + !!obj + && (typeof (obj as IRange).startLineNumber === 'number') + && (typeof (obj as IRange).startColumn === 'number') + && (typeof (obj as IRange).endLineNumber === 'number') + && (typeof (obj as IRange).endColumn === 'number') ); } diff --git a/src/vs/editor/common/core/selection.ts b/src/vs/editor/common/core/selection.ts index c436bb61ef6..4dd6c645c47 100644 --- a/src/vs/editor/common/core/selection.ts +++ b/src/vs/editor/common/core/selection.ts @@ -196,13 +196,13 @@ export class Selection extends Range { /** * Test if `obj` is an `ISelection`. */ - public static isISelection(obj: any): obj is ISelection { + public static isISelection(obj: unknown): obj is ISelection { return ( - obj - && (typeof obj.selectionStartLineNumber === 'number') - && (typeof obj.selectionStartColumn === 'number') - && (typeof obj.positionLineNumber === 'number') - && (typeof obj.positionColumn === 'number') + !!obj + && (typeof (obj as ISelection).selectionStartLineNumber === 'number') + && (typeof (obj as ISelection).selectionStartColumn === 'number') + && (typeof (obj as ISelection).positionLineNumber === 'number') + && (typeof (obj as ISelection).positionColumn === 'number') ); } diff --git a/src/vs/editor/common/cursor/cursorMoveCommands.ts b/src/vs/editor/common/cursor/cursorMoveCommands.ts index f26238f842a..3aa8b5a0f4f 100644 --- a/src/vs/editor/common/cursor/cursorMoveCommands.ts +++ b/src/vs/editor/common/cursor/cursorMoveCommands.ts @@ -581,12 +581,12 @@ export class CursorMoveCommands { export namespace CursorMove { - const isCursorMoveArgs = function (arg: any): boolean { + const isCursorMoveArgs = function (arg: unknown): boolean { if (!types.isObject(arg)) { return false; } - const cursorMoveArg: RawArguments = arg; + const cursorMoveArg: RawArguments = arg as RawArguments; if (!types.isString(cursorMoveArg.to)) { return false; diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index c615a7c933b..dbfa4aacad5 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -198,7 +198,7 @@ export interface IViewState { export interface ICodeEditorViewState { cursorState: ICursorState[]; viewState: IViewState; - contributionsState: { [id: string]: any }; + contributionsState: { [id: string]: unknown }; } /** * (Serializable) View state for the diff editor. @@ -464,7 +464,7 @@ export interface IEditor { * @param handlerId The id of the handler or the id of a contribution. * @param payload Extra data to be sent to the handler. */ - trigger(source: string | null | undefined, handlerId: string, payload: any): void; + trigger(source: string | null | undefined, handlerId: string, payload: unknown): void; /** * Gets the current model attached to this editor. @@ -495,7 +495,7 @@ export interface IEditor { * @see {@link ITextModel.changeDecorations} * @internal */ - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any; + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; } /** @@ -588,11 +588,11 @@ export interface IEditorContribution { /** * Store view state. */ - saveViewState?(): any; + saveViewState?(): unknown; /** * Restore view state. */ - restoreViewState?(state: any): void; + restoreViewState?(state: unknown): void; } /** @@ -609,8 +609,8 @@ export interface IDiffEditorContribution { /** * @internal */ -export function isThemeColor(o: any): o is ThemeColor { - return o && typeof o.id === 'string'; +export function isThemeColor(o: unknown): o is ThemeColor { + return !!o && typeof (o as ThemeColor).id === 'string'; } /** diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 3407f209289..c402849ffc9 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -457,8 +457,7 @@ export namespace CompletionItemKinds { const data = new Map(); data.set('method', CompletionItemKind.Method); data.set('function', CompletionItemKind.Function); - // eslint-disable-next-line local/code-no-any-casts - data.set('constructor', CompletionItemKind.Constructor); + data.set('constructor', CompletionItemKind.Constructor); data.set('field', CompletionItemKind.Field); data.set('variable', CompletionItemKind.Variable); data.set('class', CompletionItemKind.Class); @@ -1424,8 +1423,8 @@ export interface LocationLink { /** * @internal */ -export function isLocationLink(thing: any): thing is LocationLink { - return thing +export function isLocationLink(thing: unknown): thing is LocationLink { + return !!thing && URI.isUri((thing as LocationLink).uri) && Range.isIRange((thing as LocationLink).range) && (Range.isIRange((thing as LocationLink).originSelectionRange) || Range.isIRange((thing as LocationLink).targetSelectionRange)); @@ -1434,8 +1433,8 @@ export function isLocationLink(thing: any): thing is LocationLink { /** * @internal */ -export function isLocation(thing: any): thing is Location { - return thing +export function isLocation(thing: unknown): thing is Location { + return !!thing && URI.isUri((thing as Location).uri) && Range.isIRange((thing as Location).range); } @@ -1687,7 +1686,7 @@ export abstract class TextEdit { ? EditOperation.insert(range.getStartPosition(), edit.text) // moves marker : EditOperation.replace(range, edit.text); } - static isTextEdit(thing: any): thing is TextEdit { + static isTextEdit(thing: unknown): thing is TextEdit { const possibleTextEdit = thing as TextEdit; return typeof possibleTextEdit.text === 'string' && Range.isIRange(possibleTextEdit.range); } @@ -2058,7 +2057,7 @@ export interface Command { id: string; title: string; tooltip?: string; - arguments?: any[]; + arguments?: unknown[]; } /** @@ -2069,7 +2068,7 @@ export namespace Command { /** * @internal */ - export function is(obj: any): obj is Command { + export function is(obj: unknown): obj is Command { if (!obj || typeof obj !== 'object') { return false; } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index ce6112c586f..f125778ace5 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -690,8 +690,8 @@ export interface ITextSnapshot { /** * @internal */ -export function isITextSnapshot(obj: any): obj is ITextSnapshot { - return (obj && typeof obj.read === 'function'); +export function isITextSnapshot(obj: unknown): obj is ITextSnapshot { + return (!!obj && typeof (obj as ITextSnapshot).read === 'function'); } /** diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts index 8be4f51301a..093be134244 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts @@ -23,11 +23,11 @@ export function lengthDiff(startLineCount: number, startColumnCount: number, end */ export type Length = { _brand: 'Length' }; -// eslint-disable-next-line local/code-no-any-casts +// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any export const lengthZero = 0 as any as Length; export function lengthIsZero(length: Length): boolean { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return length as any as number === 0; } @@ -48,12 +48,12 @@ export function toLength(lineCount: number, columnCount: number): Length { // If there is no overflow (all values/sums below 2^26 = 67108864), // we have `toLength(lns1, cols1) + toLength(lns2, cols2) = toLength(lns1 + lns2, cols1 + cols2)`. - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return (lineCount * factor + columnCount) as any as Length; } export function lengthToObj(length: Length): TextLength { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l = length as any as number; const lineCount = Math.floor(l / factor); const columnCount = l - lineCount * factor; @@ -61,7 +61,7 @@ export function lengthToObj(length: Length): TextLength { } export function lengthGetLineCount(length: Length): number { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return Math.floor(length as any as number / factor); } @@ -69,7 +69,7 @@ export function lengthGetLineCount(length: Length): number { * Returns the amount of columns of the given length, assuming that it does not span any line. */ export function lengthGetColumnCountIfZeroLineCount(length: Length): number { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return length as any as number; } @@ -77,6 +77,7 @@ export function lengthGetColumnCountIfZeroLineCount(length: Length): number { // [10 lines, 5 cols] + [ 0 lines, 3 cols] = [10 lines, 8 cols] // [10 lines, 5 cols] + [20 lines, 3 cols] = [30 lines, 3 cols] export function lengthAdd(length1: Length, length2: Length): Length; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function lengthAdd(l1: any, l2: any): Length { let r = l1 + l2; if (l2 >= factor) { r = r - (l1 % factor); } @@ -95,9 +96,9 @@ export function lengthEquals(length1: Length, length2: Length): boolean { * Returns a non negative length `result` such that `lengthAdd(length1, result) = length2`, or zero if such length does not exist. */ export function lengthDiffNonNegative(length1: Length, length2: Length): Length { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l1 = length1 as any as number; - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l2 = length2 as any as number; const diff = l2 - l1; @@ -122,22 +123,22 @@ export function lengthDiffNonNegative(length1: Length, length2: Length): Length export function lengthLessThan(length1: Length, length2: Length): boolean { // First, compare line counts, then column counts. - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return (length1 as any as number) < (length2 as any as number); } export function lengthLessThanEqual(length1: Length, length2: Length): boolean { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return (length1 as any as number) <= (length2 as any as number); } export function lengthGreaterThanEqual(length1: Length, length2: Length): boolean { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return (length1 as any as number) >= (length2 as any as number); } export function lengthToPosition(length: Length): Position { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l = length as any as number; const lineCount = Math.floor(l / factor); const colCount = l - lineCount * factor; @@ -149,12 +150,12 @@ export function positionToLength(position: Position): Length { } export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l = lengthStart as any as number; const lineCount = Math.floor(l / factor); const colCount = l - lineCount * factor; - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l2 = lengthEnd as any as number; const lineCount2 = Math.floor(l2 / factor); const colCount2 = l2 - lineCount2 * factor; @@ -171,9 +172,9 @@ export function lengthOfRange(range: Range): TextLength { } export function lengthCompare(length1: Length, length2: Length): number { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l1 = length1 as any as number; - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const l2 = length2 as any as number; return l1 - l2; } @@ -192,7 +193,7 @@ export function lengthOfStringObj(str: string): TextLength { * Computes a numeric hash of the given length. */ export function lengthHash(length: Length): number { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return length as any; } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts index c324c97f0a7..fdd0463d599 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts @@ -10,6 +10,7 @@ const emptyArr: number[] = []; * It uses bits to encode element membership efficiently. */ export class SmallImmutableSet { + // eslint-disable-next-line @typescript-eslint/no-explicit-any private static cache = new Array>(129); private static create(items: number, additionalItems: readonly number[]): SmallImmutableSet { @@ -26,6 +27,7 @@ export class SmallImmutableSet { return new SmallImmutableSet(items, additionalItems); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private static empty = SmallImmutableSet.create(0, emptyArr); public static getEmpty(): SmallImmutableSet { return this.empty; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 80f41691326..5a2293dfcc4 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -61,7 +61,7 @@ interface ITextStream { on(event: 'data', callback: (data: string) => void): void; on(event: 'error', callback: (err: Error) => void): void; on(event: 'end', callback: () => void): void; - on(event: string, callback: any): void; + on(event: string, callback: (...args: unknown[]) => void): void; } export function createTextBufferFactoryFromStream(stream: ITextStream): Promise; @@ -1149,18 +1149,18 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); } - public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] { + public findMatches(searchString: string, rawSearchScope: boolean | IRange | IRange[] | null, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] { this._assertNotDisposed(); let searchRanges: Range[] | null = null; - if (rawSearchScope !== null) { + if (rawSearchScope !== null && typeof rawSearchScope !== 'boolean') { if (!Array.isArray(rawSearchScope)) { rawSearchScope = [rawSearchScope]; } - if (rawSearchScope.every((searchScope: Range) => Range.isIRange(searchScope))) { - searchRanges = rawSearchScope.map((searchScope: Range) => this.validateRange(searchScope)); + if (rawSearchScope.every((searchScope: IRange) => Range.isIRange(searchScope))) { + searchRanges = rawSearchScope.map((searchScope: IRange) => this.validateRange(searchScope)); } } diff --git a/src/vs/editor/common/services/editorBaseApi.ts b/src/vs/editor/common/services/editorBaseApi.ts index 1adaf0dcf86..b892a6ba77c 100644 --- a/src/vs/editor/common/services/editorBaseApi.ts +++ b/src/vs/editor/common/services/editorBaseApi.ts @@ -34,13 +34,11 @@ export function createMonacoBaseAPI(): typeof monaco { KeyMod: KeyMod, Position: Position, Range: Range, - // eslint-disable-next-line local/code-no-any-casts - Selection: Selection, + Selection: Selection as unknown as typeof monaco.Selection, SelectionDirection: standaloneEnums.SelectionDirection, MarkerSeverity: standaloneEnums.MarkerSeverity, MarkerTag: standaloneEnums.MarkerTag, - // eslint-disable-next-line local/code-no-any-casts - Uri: URI, + Uri: URI as unknown as typeof monaco.Uri, Token: Token }; } diff --git a/src/vs/editor/common/services/editorWebWorker.ts b/src/vs/editor/common/services/editorWebWorker.ts index ff00fcdf569..6477a4c48cb 100644 --- a/src/vs/editor/common/services/editorWebWorker.ts +++ b/src/vs/editor/common/services/editorWebWorker.ts @@ -68,12 +68,12 @@ export interface IWordRange { * @internal */ export class EditorWorker implements IDisposable, IWorkerTextModelSyncChannelServer, IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer(); constructor( - private readonly _foreignModule: any | null = null + private readonly _foreignModule: unknown | null = null ) { } dispose(): void { @@ -513,13 +513,13 @@ export class EditorWorker implements IDisposable, IWorkerTextModelSyncChannelSer // ---- BEGIN foreign module support -------------------------------------------------------------------------- // foreign method request - public $fmr(method: string, args: any[]): Promise { - if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') { + public $fmr(method: string, args: unknown[]): Promise { + if (!this._foreignModule || typeof (this._foreignModule as Record)[method] !== 'function') { return Promise.reject(new Error('Missing requestHandler or method: ' + method)); } try { - return Promise.resolve(this._foreignModule[method].apply(this._foreignModule, args)); + return Promise.resolve((this._foreignModule as Record)[method].apply(this._foreignModule, args)); } catch (e) { return Promise.reject(e); } diff --git a/src/vs/editor/common/services/editorWorkerHost.ts b/src/vs/editor/common/services/editorWorkerHost.ts index 6223c6b7464..73042f1d2f3 100644 --- a/src/vs/editor/common/services/editorWorkerHost.ts +++ b/src/vs/editor/common/services/editorWorkerHost.ts @@ -10,10 +10,10 @@ export abstract class EditorWorkerHost { public static getChannel(workerServer: IWebWorkerServer): EditorWorkerHost { return workerServer.getChannel(EditorWorkerHost.CHANNEL_NAME); } - public static setChannel(workerClient: IWebWorkerClient, obj: EditorWorkerHost): void { + public static setChannel(workerClient: IWebWorkerClient, obj: EditorWorkerHost): void { workerClient.setChannel(EditorWorkerHost.CHANNEL_NAME, obj); } // foreign host request - abstract $fhr(method: string, args: any[]): Promise; + abstract $fhr(method: string, args: unknown[]): Promise; } diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index d0f36c75939..88e379ede37 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -4,27 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from '../../../base/common/event.js'; -import { Disposable, IDisposable, DisposableStore } from '../../../base/common/lifecycle.js'; +import { StringSHA1 } from '../../../base/common/hash.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { Schemas } from '../../../base/common/network.js'; +import { equals } from '../../../base/common/objects.js'; import * as platform from '../../../base/common/platform.js'; import { URI } from '../../../base/common/uri.js'; +import { IConfigurationChangeEvent, IConfigurationService } from '../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { IUndoRedoService, ResourceEditStackSnapshot } from '../../../platform/undoRedo/common/undoRedo.js'; +import { clampedInt } from '../config/editorOptions.js'; import { EditOperation, ISingleEditOperation } from '../core/editOperation.js'; +import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js'; import { Range } from '../core/range.js'; +import { ILanguageSelection } from '../languages/language.js'; +import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from '../model.js'; +import { isEditStackElement } from '../model/editStack.js'; import { TextModel, createTextBuffer } from '../model/textModel.js'; -import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js'; +import { EditSources, TextModelEditSource } from '../textModelEditSource.js'; import { IModelLanguageChangedEvent } from '../textModelEvents.js'; -import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js'; -import { ILanguageSelection } from '../languages/language.js'; import { IModelService } from './model.js'; import { ITextResourcePropertiesService } from './textResourceConfiguration.js'; -import { IConfigurationChangeEvent, IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { IUndoRedoService, ResourceEditStackSnapshot } from '../../../platform/undoRedo/common/undoRedo.js'; -import { StringSHA1 } from '../../../base/common/hash.js'; -import { isEditStackElement } from '../model/editStack.js'; -import { Schemas } from '../../../base/common/network.js'; -import { equals } from '../../../base/common/objects.js'; -import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; -import { EditSources, TextModelEditSource } from '../textModelEditSource.js'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -50,18 +51,18 @@ class ModelData implements IDisposable { } interface IRawEditorConfig { - tabSize?: any; - indentSize?: any; - insertSpaces?: any; - detectIndentation?: any; - trimAutoWhitespace?: any; - creationOptions?: any; - largeFileOptimizations?: any; - bracketPairColorization?: any; + tabSize?: unknown; + indentSize?: unknown; + insertSpaces?: unknown; + detectIndentation?: unknown; + trimAutoWhitespace?: unknown; + creationOptions?: unknown; + largeFileOptimizations?: unknown; + bracketPairColorization?: unknown; } interface IRawConfig { - eol?: any; + eol?: unknown; editor?: IRawEditorConfig; } @@ -123,21 +124,12 @@ export class ModelService extends Disposable implements IModelService { private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { let tabSize = EDITOR_MODEL_DEFAULTS.tabSize; if (config.editor && typeof config.editor.tabSize !== 'undefined') { - const parsedTabSize = parseInt(config.editor.tabSize, 10); - if (!isNaN(parsedTabSize)) { - tabSize = parsedTabSize; - } - if (tabSize < 1) { - tabSize = 1; - } + tabSize = clampedInt(config.editor.tabSize, EDITOR_MODEL_DEFAULTS.tabSize, 1, 100); } let indentSize: number | 'tabSize' = 'tabSize'; if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') { - const parsedIndentSize = parseInt(config.editor.indentSize, 10); - if (!isNaN(parsedIndentSize)) { - indentSize = Math.max(parsedIndentSize, 1); - } + indentSize = clampedInt(config.editor.indentSize, 'tabSize', 1, 100); } let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces; @@ -169,9 +161,10 @@ export class ModelService extends Disposable implements IModelService { } let bracketPairColorizationOptions = EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions; if (config.editor?.bracketPairColorization && typeof config.editor.bracketPairColorization === 'object') { + const bpConfig = config.editor.bracketPairColorization as { enabled?: unknown; independentColorPoolPerBracketType?: unknown }; bracketPairColorizationOptions = { - enabled: !!config.editor.bracketPairColorization.enabled, - independentColorPoolPerBracketType: !!config.editor.bracketPairColorization.independentColorPoolPerBracketType + enabled: !!bpConfig.enabled, + independentColorPoolPerBracketType: !!bpConfig.independentColorPoolPerBracketType }; } diff --git a/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts b/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts index ccab956b331..b187dc8c2cd 100644 --- a/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts +++ b/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts @@ -26,7 +26,7 @@ export const WORKER_TEXT_MODEL_SYNC_CHANNEL = 'workerTextModelSync'; export class WorkerTextModelSyncClient extends Disposable { - public static create(workerClient: IWebWorkerClient, modelService: IModelService): WorkerTextModelSyncClient { + public static create(workerClient: IWebWorkerClient, modelService: IModelService): WorkerTextModelSyncClient { return new WorkerTextModelSyncClient( workerClient.getChannel(WORKER_TEXT_MODEL_SYNC_CHANNEL), modelService diff --git a/src/vs/editor/common/textModelEditSource.ts b/src/vs/editor/common/textModelEditSource.ts index 617253850dc..1d826455f04 100644 --- a/src/vs/editor/common/textModelEditSource.ts +++ b/src/vs/editor/common/textModelEditSource.ts @@ -56,7 +56,7 @@ export class TextModelEditSource { } public get props(): Record { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return this.metadata as any; } } @@ -65,8 +65,9 @@ type TextModelEditSourceT = TextModelEditSource & { metadataT: T; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any function createEditSource>(metadata: T): TextModelEditSourceT { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return new TextModelEditSource(metadata as any, privateSymbol) as any; } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index b2ff7bb459d..f41a32c7995 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -10,7 +10,8 @@ import { Event } from '../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../base/common/lifecycle.js'; import * as platform from '../../../base/common/platform.js'; import * as strings from '../../../base/common/strings.js'; -import { ConfigurationChangedEvent, EditorOption, EDITOR_FONT_DEFAULTS, filterValidationDecorations, filterFontDecorations } from '../config/editorOptions.js'; +import { ConfigurationChangedEvent, EditorOption, filterValidationDecorations, filterFontDecorations } from '../config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../config/fontInfo.js'; import { CursorsController } from '../cursor/cursor.js'; import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, PartialCursorState } from '../cursorCommon.js'; import { CursorChangeReason } from '../cursorEvents.js'; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index e65f9ab38da..569dd89c616 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -110,7 +110,8 @@ export class CodeActionController extends Disposable implements IEditorContribut const command = actionItem.action.command; if (command && command.id === 'inlineChat.start') { if (command.arguments && command.arguments.length >= 1) { - command.arguments[0] = { ...command.arguments[0], autoSend: false }; + // eslint-disable-next-line local/code-no-any-casts + command.arguments[0] = { ...(command.arguments[0] as any), autoSend: false }; } } await this.applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index 149e1538c8b..f34e88a4d24 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -10,7 +10,8 @@ import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js'; import { IActiveCodeEditor, ICodeEditor, IViewZoneChangeAccessor, MouseTargetType } from '../../../browser/editorBrowser.js'; import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { IModelDecorationsChangeAccessor } from '../../../common/model.js'; diff --git a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts index 821badb88af..4e878de1f19 100644 --- a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts @@ -159,7 +159,7 @@ export class FoldingDecorationProvider implements IDecorationProvider { } } - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T { + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { return this.editor.changeDecorations(callback); } diff --git a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index ea3d4987eeb..3b42b9eaa87 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -300,7 +300,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, false, token); } - private gotoDefinition(position: Position, openToSide: boolean): Promise { + private async gotoDefinition(position: Position, openToSide: boolean): Promise { this.editor.setPosition(position); return this.editor.invokeWithinContext((accessor) => { const canPeek = !openToSide && this.editor.getOption(EditorOption.definitionLinkOpensInPeek) && !this.isInPeekEditor(accessor); diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 8970884331e..340d9213abd 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -16,7 +16,8 @@ import { URI } from '../../../../base/common/uri.js'; import { IActiveCodeEditor, ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js'; import { ClassNameReference, CssProperties, DynamicCssRules } from '../../../browser/editorDom.js'; import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import { EditOperation } from '../../../common/core/editOperation.js'; import { Range } from '../../../common/core/range.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 8dbb0c1fc9d..b4af6407075 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -636,7 +636,8 @@ export class InlineCompletionsModel extends Disposable { const edits = inlineEditResult.updatedEdit; const e = edits ? TextEdit.fromStringEdit(edits, new TextModelText(this.textModel)).replacements : [edit]; const nextEditUri = (item.inlineEdit?.command?.id === 'vscode.open' || item.inlineEdit?.command?.id === '_workbench.open') && - item.inlineEdit?.command.arguments?.length ? URI.from(item.inlineEdit?.command.arguments[0]) : undefined; + // eslint-disable-next-line local/code-no-any-casts + item.inlineEdit?.command.arguments?.length ? URI.from(item.inlineEdit?.command.arguments[0]) : undefined; return { kind: 'inlineEdit', inlineEdit, inlineCompletion: inlineEditResult, edits: e, cursorAtInlineEdit, nextEditUri }; } diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index 35530b73c6a..f31efc2284b 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -14,7 +14,8 @@ import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; import { assertReturnsDefined } from '../../../../base/common/types.js'; import './parameterHints.css'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js'; -import { EDITOR_FONT_DEFAULTS, EditorOption } from '../../../common/config/editorOptions.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import * as languages from '../../../common/languages.js'; import { ILanguageService } from '../../../common/languages/language.js'; import { IMarkdownRenderResult, MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; diff --git a/src/vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators.ts b/src/vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators.ts index e4a24035103..6259b68eddb 100644 --- a/src/vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators.ts +++ b/src/vs/editor/contrib/unusualLineTerminators/browser/unusualLineTerminators.ts @@ -21,7 +21,7 @@ function writeIgnoreState(codeEditorService: ICodeEditorService, model: ITextMod } function readIgnoreState(codeEditorService: ICodeEditorService, model: ITextModel): boolean | undefined { - return codeEditorService.getModelProperty(model.uri, ignoreUnusualLineTerminators); + return codeEditorService.getModelProperty(model.uri, ignoreUnusualLineTerminators) as boolean | undefined; } export class UnusualLineTerminatorsDetector extends Disposable implements IEditorContribution { diff --git a/src/vs/editor/standalone/browser/standaloneWebWorker.ts b/src/vs/editor/standalone/browser/standaloneWebWorker.ts index 4cdd6cf5477..74f1a2770b9 100644 --- a/src/vs/editor/standalone/browser/standaloneWebWorker.ts +++ b/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -77,7 +77,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement } // foreign host request - public override fhr(method: string, args: any[]): Promise { + public override fhr(method: string, args: unknown[]): Promise { if (!this._foreignModuleHost || typeof this._foreignModuleHost[method] !== 'function') { return Promise.reject(new Error('Missing method ' + method + ' or missing main thread foreign host.')); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e60883df0b5..1aa0677bc4c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -618,7 +618,7 @@ declare namespace monaco { /** * Test if `obj` is an `IPosition`. */ - static isIPosition(obj: any): obj is IPosition; + static isIPosition(obj: unknown): obj is IPosition; toJSON(): IPosition; } @@ -782,7 +782,7 @@ declare namespace monaco { /** * Test if `obj` is an `IRange`. */ - static isIRange(obj: any): obj is IRange; + static isIRange(obj: unknown): obj is IRange; /** * Test if the two ranges are touching in any way. */ @@ -908,7 +908,7 @@ declare namespace monaco { /** * Test if `obj` is an `ISelection`. */ - static isISelection(obj: any): obj is ISelection; + static isISelection(obj: unknown): obj is ISelection; /** * Create with a direction. */ @@ -2635,7 +2635,7 @@ declare namespace monaco.editor { cursorState: ICursorState[]; viewState: IViewState; contributionsState: { - [id: string]: any; + [id: string]: unknown; }; } @@ -2850,7 +2850,7 @@ declare namespace monaco.editor { * @param handlerId The id of the handler or the id of a contribution. * @param payload Extra data to be sent to the handler. */ - trigger(source: string | null | undefined, handlerId: string, payload: any): void; + trigger(source: string | null | undefined, handlerId: string, payload: unknown): void; /** * Gets the current model attached to this editor. */ @@ -2922,11 +2922,11 @@ declare namespace monaco.editor { /** * Store view state. */ - saveViewState?(): any; + saveViewState?(): unknown; /** * Restore view state. */ - restoreViewState?(state: any): void; + restoreViewState?(state: unknown): void; } /** @@ -8393,7 +8393,7 @@ declare namespace monaco.languages { id: string; title: string; tooltip?: string; - arguments?: any[]; + arguments?: unknown[]; } export interface CommentThreadRevealOptions { diff --git a/src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts b/src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts index 897cdeffc89..fcbf8322f17 100644 --- a/src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts +++ b/src/vs/platform/profiling/electron-browser/profileAnalysisWorker.ts @@ -17,7 +17,7 @@ export function create(): IWebWorkerServerRequestHandler { class ProfileAnalysisWorker implements IWebWorkerServerRequestHandler, IProfileAnalysisWorker { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { if (!Utils.isValidProfile(profile)) { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index d069b3813a1..d62a4d5d73c 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -30,7 +30,7 @@ import { registerNotificationCommands } from './parts/notifications/notification import { NotificationsToasts } from './parts/notifications/notificationsToasts.js'; import { setARIAContainer } from '../../base/browser/ui/aria/aria.js'; import { FontMeasurements } from '../../editor/browser/config/fontMeasurements.js'; -import { BareFontInfo } from '../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../editor/common/config/fontInfoFromSettings.js'; import { ILogService } from '../../platform/log/common/log.js'; import { toErrorMessage } from '../../base/common/errorMessage.js'; import { WorkbenchContextKeysHandler } from './contextkeys.js'; @@ -297,7 +297,7 @@ export class Workbench extends Layout { } } - FontMeasurements.readFontInfo(mainWindow, BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), PixelRatio.getInstance(mainWindow).value)); + FontMeasurements.readFontInfo(mainWindow, createBareFontInfoFromRawSettings(configurationService.getValue('editor'), PixelRatio.getInstance(mainWindow).value)); } private storeFontInfo(storageService: IStorageService): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index db18152a9be..cbe883756d1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -386,7 +386,7 @@ registerAction2(class GoToDefinitionAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, location: Location): Promise { + override async run(accessor: ServicesAccessor, location: Location): Promise { const editorService = accessor.get(ICodeEditorService); const instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 9f672c4d1b2..324fb65354c 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -22,7 +22,8 @@ import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExten import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; import { DiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/diffEditorWidget.js'; -import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { ScrollType } from '../../../../editor/common/editorCommon.js'; import { TextEdit } from '../../../../editor/common/languages.js'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 524d3457777..b55067b5ac4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -49,7 +49,7 @@ export function writeTransientState(model: ITextModel, state: IWordWrapTransient * Read (in memory) the word wrap state for a particular model. */ export function readTransientState(model: ITextModel, codeEditorService: ICodeEditorService): IWordWrapTransientState | null { - return codeEditorService.getTransientModelProperty(model, transientWordWrapState); + return codeEditorService.getTransientModelProperty(model, transientWordWrapState) as IWordWrapTransientState | null; } const TOGGLE_WORD_WRAP_ID = 'editor.action.toggleWordWrap'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 72f3675524f..11261bbffa9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -18,7 +18,8 @@ import { IColorTheme, IThemeService } from '../../../../platform/theme/common/th import { CommentGlyphWidget } from './commentGlyphWidget.js'; import { ICommentService } from './commentService.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; -import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { CommentThreadWidget } from './commentThreadWidget.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index 132fa753e4d..c06b63cf155 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -17,6 +17,7 @@ import { URI } from '../../../../base/common/uri.js'; import { applyFontInfo } from '../../../../editor/browser/config/domFontInfo.js'; import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { BareFontInfo } from '../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../editor/common/config/fontInfoFromSettings.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { StringBuilder } from '../../../../editor/common/core/stringBuilder.js'; import { ITextModel } from '../../../../editor/common/model.js'; @@ -146,7 +147,7 @@ export class DisassemblyView extends EditorPane { } private createFontInfo() { - return BareFontInfo.createFromRawSettings(this._configurationService.getValue('editor'), PixelRatio.getInstance(this.window).value); + return createBareFontInfoFromRawSettings(this._configurationService.getValue('editor'), PixelRatio.getInstance(this.window).value); } get currentInstructionAddresses() { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 930812de0f9..fbfde1cb8a3 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -27,7 +27,8 @@ import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrow import { EditorAction, registerEditorAction } from '../../../../editor/browser/editorExtensions.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; -import { EDITOR_FONT_DEFAULTS, EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; import { IDecorationOptions } from '../../../../editor/common/editorCommon.js'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts index 0a0c485c598..440c1ecc2a5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts @@ -10,7 +10,8 @@ import { hash } from '../../../../../base/common/hash.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun, derived, IObservable, transaction } from '../../../../../base/common/observable.js'; import { ICodeEditor, IViewZoneChangeAccessor } from '../../../../../editor/browser/editorBrowser.js'; -import { EditorOption, EDITOR_FONT_DEFAULTS } from '../../../../../editor/common/config/editorOptions.js'; +import { EditorOption } from '../../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../../editor/common/config/fontInfo.js'; import { localize } from '../../../../../nls.js'; import { ModifiedBaseRange, ModifiedBaseRangeState, ModifiedBaseRangeStateKind } from '../model/modifiedBaseRange.js'; import { FixedZoneWidget } from './fixedZoneWidget.js'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index c6521e1675f..3b26d58d13a 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -22,7 +22,8 @@ import { diffDiagonalFill, editorBackground, focusBorder, foreground } from '../ import { INotebookEditorWorkerService } from '../../common/services/notebookWorkerService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IEditorOptions as ICodeEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo, FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../../editor/common/config/fontInfoFromSettings.js'; import { PixelRatio } from '../../../../../base/browser/pixelRatio.js'; import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookEditorOptions } from '../notebookBrowser.js'; import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor, INotebookDiffViewModel } from './notebookDiffEditorBrowser.js'; @@ -186,7 +187,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private createFontInfo() { const editorOptions = this.configurationService.getValue('editor'); - return FontMeasurements.readFontInfo(this.window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); + return FontMeasurements.readFontInfo(this.window, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); } private isOverviewRulerEnabled(): boolean { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index c36959700cd..27fa2e39c7c 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -26,7 +26,7 @@ import { INotificationService } from '../../../../../platform/notification/commo import { CodiconActionViewItem } from '../view/cellParts/cellActionView.js'; import { IMouseWheelEvent } from '../../../../../base/browser/mouseEvent.js'; import { IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../../editor/common/config/fontInfoFromSettings.js'; import { PixelRatio } from '../../../../../base/browser/pixelRatio.js'; import { WorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { fixedDiffEditorOptions, fixedEditorOptions } from './diffCellEditorOptions.js'; @@ -44,7 +44,7 @@ export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; + this.lineHeight = createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; } getHeight(element: IDiffElementViewModelBase): number { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts index 1f6a760cdff..1d770f32bc4 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts @@ -16,7 +16,8 @@ import { IContextKey, IContextKeyService } from '../../../../../platform/context import { INotebookEditorWorkerService } from '../../common/services/notebookWorkerService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IEditorOptions as ICodeEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo, FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../../editor/common/config/fontInfoFromSettings.js'; import { PixelRatio } from '../../../../../base/browser/pixelRatio.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { EditorPane } from '../../../../browser/parts/editor/editorPane.js'; @@ -94,7 +95,7 @@ export class NotebookMultiTextDiffEditor extends EditorPane { private createFontInfo() { const editorOptions = this.configurationService.getValue('editor'); - return FontMeasurements.readFontInfo(this.window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); + return FontMeasurements.readFontInfo(this.window, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); } protected createEditor(parent: HTMLElement): void { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 2f3fe9d654d..4a075ca0244 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -37,7 +37,8 @@ import { generateUuid } from '../../../../base/common/uuid.js'; import { FontMeasurements } from '../../../../editor/browser/config/fontMeasurements.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo, FontInfo } from '../../../../editor/common/config/fontInfo.js'; +import { FontInfo } from '../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../editor/common/config/fontInfoFromSettings.js'; import { Range } from '../../../../editor/common/core/range.js'; import { Selection } from '../../../../editor/common/core/selection.js'; import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; @@ -607,7 +608,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); const targetWindow = DOM.getWindow(this.getDomNode()); - this._fontInfo = FontMeasurements.readFontInfo(targetWindow, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value)); + this._fontInfo = FontMeasurements.readFontInfo(targetWindow, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value)); } private _createBody(parent: HTMLElement): void { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 5edd1b7272e..c78d6f7e164 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -13,7 +13,7 @@ import { URI } from '../../../../base/common/uri.js'; import { FontMeasurements } from '../../../../editor/browser/config/fontMeasurements.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo } from '../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../editor/common/config/fontInfoFromSettings.js'; import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { NotebookTextModel } from '../common/model/notebookTextModel.js'; import { InteractiveWindowCollapseCodeCells, NotebookCellDefaultCollapseConfig, NotebookCellInternalMetadata, NotebookSetting, ShowCellStatusBarType } from '../common/notebookCommon.js'; @@ -327,7 +327,7 @@ export class NotebookOptions extends Disposable { // there is a `::before` or `::after` text decoration whose position is above or below current line // we at least make sure that the editor top padding is at least one line const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); + updateEditorTopPadding(createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); decorationTriggeredAdjustment = true; break; } @@ -387,7 +387,7 @@ export class NotebookOptions extends Disposable { if (lineHeight === 0) { // use editor line height const editorOptions = this.configurationService.getValue('editor'); - const fontInfo = FontMeasurements.readFontInfo(this.targetWindow, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value)); + const fontInfo = FontMeasurements.readFontInfo(this.targetWindow, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value)); lineHeight = fontInfo.lineHeight; } else if (lineHeight < minimumLineHeight) { // Values too small to be line heights in pixels are in ems. diff --git a/src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts b/src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts index 603419b0b04..235f856fae2 100644 --- a/src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts @@ -29,7 +29,8 @@ import { getDefaultNotebookCreationOptions } from '../notebookEditorWidget.js'; import { NotebookOptions } from '../notebookOptions.js'; import { BackLayerWebView, INotebookDelegateForWebview } from '../view/renderers/backLayerWebView.js'; import { NotebookOutputEditorInput } from './notebookOutputEditorInput.js'; -import { BareFontInfo, FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../../editor/common/config/fontInfoFromSettings.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IEditorOptions as ICodeEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; import { FontMeasurements } from '../../../../../editor/browser/config/fontMeasurements.js'; @@ -125,7 +126,7 @@ export class NotebookOutputEditor extends EditorPane implements INotebookDelegat private createFontInfo() { const editorOptions = this.configurationService.getValue('editor'); - return FontMeasurements.readFontInfo(this.window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); + return FontMeasurements.readFontInfo(this.window, createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); } private async _createOriginalWebview(id: string, viewType: string, resource: URI): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index 276dd3b8bc4..c84fb57697b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -5,7 +5,8 @@ import { coalesce } from '../../../../../../base/common/arrays.js'; import { DisposableMap, DisposableStore } from '../../../../../../base/common/lifecycle.js'; -import { EDITOR_FONT_DEFAULTS, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; +import { IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../../../editor/common/config/fontInfo.js'; import * as languages from '../../../../../../editor/common/languages.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 867645b650a..9135b4b711c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; import { IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; -import { BareFontInfo } from '../../../../../../editor/common/config/fontInfo.js'; +import { createBareFontInfoFromRawSettings } from '../../../../../../editor/common/config/fontInfoFromSettings.js'; import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../../../editor/common/languages/modesRegistry.js'; import { localize } from '../../../../../../nls.js'; @@ -64,7 +64,7 @@ export class NotebookCellListDelegate extends Disposable implements IListVirtual super(); const editorOptions = this.configurationService.getValue('editor'); - this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; + this.lineHeight = createBareFontInfoFromRawSettings(editorOptions, PixelRatio.getInstance(targetWindow).value).lineHeight; } getHeight(element: CellViewModel): number { diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts index d95e43c5608..213b8856c8f 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts @@ -178,7 +178,7 @@ class CellSequence implements ISequence { } export class NotebookWorker implements IWebWorkerServerRequestHandler, IDisposable { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private _models: { [uri: string]: MirrorNotebookDocument }; diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index 95073543a07..db95d2fb2ac 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -19,7 +19,7 @@ export interface IResourceCreator { } export class OutputLinkComputer implements IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private readonly workerTextModelSyncServer = new WorkerTextModelSyncServer(); private patterns = new Map(); diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 9c073db0082..2f84d3bf308 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -151,7 +151,8 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe // Get a repository agnostic name for the status bar action, derive this from the // first command argument which is in the form of "./" - let repoAgnosticActionName = command.arguments?.[0]; + // eslint-disable-next-line local/code-no-any-casts + let repoAgnosticActionName = command.arguments?.[0]; if (repoAgnosticActionName && typeof repoAgnosticActionName === 'string') { repoAgnosticActionName = repoAgnosticActionName .substring(0, repoAgnosticActionName.lastIndexOf('/')) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts index 8c26271f589..d39f1a91159 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts @@ -5,7 +5,8 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { EDITOR_FONT_DEFAULTS, type IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { type IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ITerminalConfigurationService, LinuxDistro } from './terminal.js'; import type { IXtermCore } from './xterm-private.js'; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigurationService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigurationService.test.ts index 8b5f8985004..206b113186c 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigurationService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigurationService.test.ts @@ -8,7 +8,7 @@ import { getActiveWindow } from '../../../../../base/browser/dom.js'; import { mainWindow } from '../../../../../base/browser/window.js'; import { isLinux } from '../../../../../base/common/platform.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { EDITOR_FONT_DEFAULTS } from '../../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../../editor/common/config/fontInfo.js'; import { ConfigurationTarget, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; diff --git a/src/vs/workbench/contrib/webview/browser/themeing.ts b/src/vs/workbench/contrib/webview/browser/themeing.ts index 1095cefd6b2..864f65fc86d 100644 --- a/src/vs/workbench/contrib/webview/browser/themeing.ts +++ b/src/vs/workbench/contrib/webview/browser/themeing.ts @@ -6,7 +6,8 @@ import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { EDITOR_FONT_DEFAULTS, IEditorOptions, EditorFontLigatures } from '../../../../editor/common/config/editorOptions.js'; +import { IEditorOptions, EditorFontLigatures } from '../../../../editor/common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import * as colorRegistry from '../../../../platform/theme/common/colorRegistry.js'; import { ColorScheme } from '../../../../platform/theme/common/theme.js'; diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts index 0624f7cc767..98e439e194c 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts @@ -20,7 +20,7 @@ export function create(workerServer: IWebWorkerServer): IWebWorkerServerRequestH * @internal */ export class LanguageDetectionWorker implements ILanguageDetectionWorker { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private static readonly expectedRelativeConfidence = 0.2; private static readonly positiveConfidenceCorrectionBucket1 = 0.05; diff --git a/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts b/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts index dfee98bbb72..36f559acb48 100644 --- a/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts +++ b/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts @@ -40,7 +40,7 @@ export interface IWorkerFileSystemFileHandle extends IWorkerFileSystemHandle { } export interface ILocalFileSearchWorker { - _requestHandlerBrand: any; + _requestHandlerBrand: void; $cancelQuery(queryId: number): void; diff --git a/src/vs/workbench/services/search/worker/localFileSearch.ts b/src/vs/workbench/services/search/worker/localFileSearch.ts index aea6f1107d1..df350c5cede 100644 --- a/src/vs/workbench/services/search/worker/localFileSearch.ts +++ b/src/vs/workbench/services/search/worker/localFileSearch.ts @@ -53,7 +53,7 @@ export function create(workerServer: IWebWorkerServer): IWebWorkerServerRequestH } export class LocalFileSearchWorker implements ILocalFileSearchWorker, IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private readonly host: LocalFileSearchWorkerHost; cancellationTokens: Map = new Map(); diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts index 036b2a3e292..a184a885fcb 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts @@ -42,7 +42,7 @@ export interface StateDeltas { } export class TextMateTokenizationWorker implements IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void = undefined; private readonly _host: TextMateWorkerHost; private readonly _models = new Map(); From 8e1068425a6066d6b8c34acf2db7a955bbdbd2a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:19:11 +0900 Subject: [PATCH 0808/4355] Remove as any from 'terminal' folders Part of #269213 --- .../capabilities/commandDetectionCapability.ts | 16 ++++++++++++++-- .../platform/terminal/common/terminalProfiles.ts | 13 +------------ src/vs/platform/terminal/node/terminalProcess.ts | 1 + .../terminal/browser/remoteTerminalBackend.ts | 11 +++++++++-- .../contrib/terminal/browser/terminalInstance.ts | 10 +++++++--- .../terminal/browser/terminalProcessManager.ts | 9 ++++++--- .../contrib/terminal/browser/terminalTabsList.ts | 6 ++++-- .../terminal/browser/xterm/xtermTerminal.ts | 6 ++++-- 8 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 7b1cbd9702a..83ffcf3e409 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -641,8 +641,20 @@ class WindowsPtyHeuristics extends Disposable { // function an embedder could easily do damage with. Additionally, this // can't really be upstreamed since the event relies on shell integration to // verify the shifting is necessary. - // eslint-disable-next-line local/code-no-any-casts - (this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({ + interface IXtermWithCore extends Terminal { + _core: { + _bufferService: { + buffer: { + lines: { + onDeleteEmitter: { + fire(data: { index: number; amount: number }): void; + }; + }; + }; + }; + }; + } + (this._terminal as IXtermWithCore)._core._bufferService.buffer.lines.onDeleteEmitter.fire({ index: this._terminal.buffer.active.baseY, amount: potentialShiftedLineCount }); diff --git a/src/vs/platform/terminal/common/terminalProfiles.ts b/src/vs/platform/terminal/common/terminalProfiles.ts index 9f308a37041..5823000bd74 100644 --- a/src/vs/platform/terminal/common/terminalProfiles.ts +++ b/src/vs/platform/terminal/common/terminalProfiles.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from '../../../base/common/codicons.js'; -import { URI, UriComponents } from '../../../base/common/uri.js'; +import { isUriComponents, URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IExtensionTerminalProfile, ITerminalProfile, TerminalIcon } from './terminal.js'; import { ThemeIcon } from '../../../base/common/themables.js'; @@ -110,14 +110,3 @@ export function terminalIconsEqual(a?: TerminalIcon, b?: TerminalIcon): boolean return false; } - - -export function isUriComponents(thing: unknown): thing is UriComponents { - if (!thing) { - return false; - } - // eslint-disable-next-line local/code-no-any-casts - return typeof (thing).path === 'string' && - // eslint-disable-next-line local/code-no-any-casts - typeof (thing).scheme === 'string'; -} diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index ad6408d5b61..768a931ae40 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -542,6 +542,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess const object = this._writeQueue.shift()!; this._logService.trace('node-pty.IPty#write', object.data); if (object.isBinary) { + // TODO: node-pty's write should accept a Buffer // eslint-disable-next-line local/code-no-any-casts this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any); } else { diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index bf195729208..313148f646f 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -287,8 +287,15 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack return undefined; } const resolverResult = await this._remoteAuthorityResolverService.resolveAuthority(connection.remoteAuthority); - // eslint-disable-next-line local/code-no-any-casts - return resolverResult.options?.extensionHostEnv as any; + const envResult: IProcessEnvironment = {}; + if (resolverResult.options?.extensionHostEnv) { + for (const [key, value] of Object.entries(resolverResult.options.extensionHostEnv)) { + if (value !== null) { + envResult[key] = value; + } + } + } + return envResult; } async getWslPath(original: string, direction: 'unix-to-win' | 'win-to-unix'): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index fc003eb7f97..0b4b058377c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -80,7 +80,7 @@ import { isHorizontal, IWorkbenchLayoutService } from '../../../services/layout/ import { IPathService } from '../../../services/path/common/pathService.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { importAMDNodeModule } from '../../../../amdX.js'; -import type { IMarker, Terminal as XTermTerminal } from '@xterm/xterm'; +import type { IMarker, Terminal as XTermTerminal, IBufferLine } from '@xterm/xterm'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { terminalStrings } from '../common/terminalStrings.js'; import { TerminalIconPicker } from './terminalIconPicker.js'; @@ -2141,9 +2141,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // work around for https://github.com/xtermjs/xterm.js/issues/3482 if (isWindows) { for (let i = this.xterm.raw.buffer.active.viewportY; i < this.xterm.raw.buffer.active.length; i++) { + interface ILineWithInternals extends IBufferLine { + _line: { + isWrapped: boolean; + }; + } const line = this.xterm.raw.buffer.active.getLine(i); - // eslint-disable-next-line local/code-no-any-casts - (line as any)._line.isWrapped = false; + (line as ILineWithInternals)._line.isWrapped = false; } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index f672ba24084..e0ac131aa5b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -43,6 +43,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { shouldUseEnvironmentVariableCollection } from '../../../../platform/terminal/common/terminalEnvironment.js'; import { TerminalContribSettingId } from '../terminalContribExports.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; const enum ProcessConstants { /** @@ -444,9 +445,11 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce let baseEnv: IProcessEnvironment; if (shellLaunchConfig.useShellEnvironment) { - // TODO: Avoid as any? - // eslint-disable-next-line local/code-no-any-casts - baseEnv = await backend.getShellEnvironment() as any; + const shellEnv = await backend.getShellEnvironment(); + if (!shellEnv) { + throw new BugIndicatingError('Cannot fetch shell environment to use'); + } + baseEnv = shellEnv; } else { baseEnv = await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index ee3bdf69581..598cdaf0a04 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -55,6 +55,7 @@ import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js' import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IStorageService, StorageScope } from '../../../../platform/storage/common/storage.js'; import { TerminalStorageKeys } from '../common/terminalStorageKeys.js'; +import { isObject } from '../../../../base/common/types.js'; const $ = DOM.$; @@ -633,8 +634,9 @@ class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop 'instanceId' in (e as any)); + const terminals: ITerminalInstance[] = (dndData as unknown[]).filter(e => ( + isObject(e) && 'instanceId' in e + )) as ITerminalInstance[]; if (terminals.length > 0) { originalEvent.dataTransfer.setData(TerminalDataTransfers.Terminals, JSON.stringify(terminals.map(e => e.resource.toString()))); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 9b1fd3bc567..e1293b426a9 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -241,8 +241,10 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach }, })); this._updateSmoothScrolling(); - // eslint-disable-next-line local/code-no-any-casts - this._core = (this.raw as any)._core as IXtermCore; + interface ITerminalWithCore extends RawXtermTerminal { + _core: IXtermCore; + } + this._core = (this.raw as ITerminalWithCore)._core as IXtermCore; this._register(this._configurationService.onDidChangeConfiguration(async e => { if (e.affectsConfiguration(TerminalSettingId.GpuAcceleration)) { From 188cbd914fce53ac7e18b24599c002995a458fe4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:23:27 +0900 Subject: [PATCH 0809/4355] Remove as any from terminal-suggest Part of #269213 --- extensions/terminal-suggest/src/test/completions/cd.test.ts | 3 +-- .../terminal-suggest/src/test/completions/code.test.ts | 6 ++---- .../src/test/completions/upstream/echo.test.ts | 3 +-- .../src/test/completions/upstream/git.test.ts | 3 +-- .../src/test/completions/upstream/ls.test.ts | 3 +-- .../src/test/completions/upstream/mkdir.test.ts | 3 +-- .../src/test/completions/upstream/rm.test.ts | 3 +-- .../src/test/completions/upstream/rmdir.test.ts | 3 +-- .../src/test/completions/upstream/touch.test.ts | 3 +-- 9 files changed, 10 insertions(+), 20 deletions(-) diff --git a/extensions/terminal-suggest/src/test/completions/cd.test.ts b/extensions/terminal-suggest/src/test/completions/cd.test.ts index 0f4e9f0646d..a40d5ee2103 100644 --- a/extensions/terminal-suggest/src/test/completions/cd.test.ts +++ b/extensions/terminal-suggest/src/test/completions/cd.test.ts @@ -8,8 +8,7 @@ import cdSpec from '../../completions/cd'; import { testPaths, type ISuiteSpec } from '../helpers'; const expectedCompletions = ['-']; -// eslint-disable-next-line local/code-no-any-casts -const cdExpectedCompletions = [{ label: 'cd', description: (cdSpec as any).description }]; +const cdExpectedCompletions = [{ label: 'cd', description: (cdSpec as Fig.Subcommand).description }]; export const cdTestSuiteSpec: ISuiteSpec = { name: 'cd', completionSpecs: cdSpec, diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index b4bc45b33fb..316a46e4f57 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -77,8 +77,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { - // eslint-disable-next-line local/code-no-any-casts - const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; + const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name ? (codeCompletionSpec as Fig.Subcommand).description : (codeInsidersCompletionSpec as Fig.Subcommand).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); } @@ -266,8 +265,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { - // eslint-disable-next-line local/code-no-any-casts - const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name || executable === codeTunnelCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; + const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name || executable === codeTunnelCompletionSpec.name ? (codeCompletionSpec as Fig.Subcommand).description : (codeInsidersCompletionSpec as Fig.Subcommand).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); } diff --git a/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts index 505394bd176..5ba2a611bc0 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts @@ -12,8 +12,7 @@ const allOptions = [ '-e', '-n', ]; -// eslint-disable-next-line local/code-no-any-casts -const echoExpectedCompletions = [{ label: 'echo', description: (echoSpec as any).description }]; +const echoExpectedCompletions = [{ label: 'echo', description: (echoSpec as Fig.Subcommand).description }]; export const echoTestSuiteSpec: ISuiteSpec = { name: 'echo', completionSpecs: echoSpec, diff --git a/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts index b8288865d85..b8a0a5fe6a7 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts @@ -11,8 +11,7 @@ import gitSpec from '../../../completions/git'; // const gitCommitArgs = ['--', '--all', '--allow-empty', '--allow-empty-message', '--amend', '--author', '--branch', '--cleanup', '--date', '--dry-run', '--edit', '--file', '--fixup', '--gpg-sign', '--include', '--long', '--message', '--no-edit', '--no-gpg-sign', '--no-post-rewrite', '--no-signoff', '--no-status', '--no-verify', '--null', '--only', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--porcelain', '--quiet', '--reedit-message', '--reset-author', '--reuse-message', '--short', '--signoff', '--squash', '--status', '--template', '--untracked-files', '--verbose', '-C', '-F', '-S', '-a', '-am', '-c', '-e', '-i', '-m', '-n', '-o', '-p', '-q', '-s', '-t', '-u', '-v', '-z']; // const gitMergeArgs = ['-', '--abort', '--allow-unrelated-histories', '--autostash', '--cleanup', '--commit', '--continue', '--edit', '--ff', '--ff-only', '--file', '--gpg-sign', '--log', '--no-autostash', '--no-commit', '--no-edit', '--no-ff', '--no-gpg-sign', '--no-log', '--no-overwrite-ignore', '--no-progress', '--no-rerere-autoupdate', '--no-signoff', '--no-squash', '--no-stat', '--no-summary', '--no-verify', '--no-verify-signatures', '--overwrite-ignore', '--progress', '--quiet', '--quit', '--rerere-autoupdate', '--signoff', '--squash', '--stat', '--strategy', '--strategy-option', '--summary', '--verbose', '--verify-signatures', '-F', '-S', '-X', '-e', '-m', '-n', '-q', '-s']; // const gitAddArgs = ['--', '--all', '--chmod', '--dry-run', '--edit', '--force', '--ignore-errors', '--ignore-missing', '--ignore-removal', '--intent-to-add', '--interactive', '--no-all', '--no-ignore-removal', '--no-warn-embedded-repo', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--refresh', '--renormalize', '--update', '--verbose', '-A', '-N', '-e', '-f', '-i', '-n', '-p', '-u', '-v']; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'git', description: (gitSpec as any).description }]; +const expectedCompletions = [{ label: 'git', description: (gitSpec as Fig.Subcommand).description }]; export const gitTestSuiteSpec: ISuiteSpec = { name: 'git', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index f7b59648ff3..e08b755e60a 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -51,8 +51,7 @@ const allOptions = [ '-w', '-x', ]; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'ls', description: (lsSpec as any).description }]; +const expectedCompletions = [{ label: 'ls', description: (lsSpec as Fig.Subcommand).description }]; export const lsTestSuiteSpec: ISuiteSpec = { name: 'ls', completionSpecs: lsSpec, diff --git a/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts index faa8a00c7fd..5c227f01e67 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts @@ -19,8 +19,7 @@ const allOptions = [ '-p', '-v', ]; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'mkdir', description: (mkdirSpec as any).description }]; +const expectedCompletions = [{ label: 'mkdir', description: (mkdirSpec as Fig.Subcommand).description }]; export const mkdirTestSuiteSpec: ISuiteSpec = { name: 'mkdir', completionSpecs: mkdirSpec, diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts index 14ff89df9cf..0ca04b26733 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts @@ -16,8 +16,7 @@ const allOptions = [ '-r', '-v', ]; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'rm', description: (rmSpec as any).description }]; +const expectedCompletions = [{ label: 'rm', description: (rmSpec as Fig.Subcommand).description }]; export const rmTestSuiteSpec: ISuiteSpec = { name: 'rm', completionSpecs: rmSpec, diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts index 1cd90662a77..c252a7f4aa8 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts @@ -10,8 +10,7 @@ import rmdirSpec from '../../../completions/upstream/rmdir'; const allOptions = [ '-p', ]; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'rmdir', description: (rmdirSpec as any).description }]; +const expectedCompletions = [{ label: 'rmdir', description: (rmdirSpec as Fig.Subcommand).description }]; export const rmdirTestSuiteSpec: ISuiteSpec = { name: 'rmdir', diff --git a/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts index ad8a12f152e..1435822f49c 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts @@ -17,8 +17,7 @@ const allOptions = [ '-r ', '-t ', ]; -// eslint-disable-next-line local/code-no-any-casts -const expectedCompletions = [{ label: 'touch', description: (touchSpec as any).description }]; +const expectedCompletions = [{ label: 'touch', description: (touchSpec as Fig.Subcommand).description }]; export const touchTestSuiteSpec: ISuiteSpec = { name: 'touch', From c854ad2953f3748f4901c9bb94ec70b2e0fc392c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:29:16 +0900 Subject: [PATCH 0810/4355] Fix isUriComponents check --- .../contrib/terminal/browser/terminalProfileResolverService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 6208cfc0413..62523b60705 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -18,9 +18,8 @@ import { getIconRegistry, IIconRegistry } from '../../../../platform/theme/commo import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; import { debounce } from '../../../../base/common/decorators.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { URI } from '../../../../base/common/uri.js'; +import { isUriComponents, URI } from '../../../../base/common/uri.js'; import { deepClone } from '../../../../base/common/objects.js'; -import { isUriComponents } from '../../../../platform/terminal/common/terminalProfiles.js'; import { ITerminalInstanceService } from './terminal.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; From 80cd68d279aba8975c138126419afbe64d3f489f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:35:27 +0900 Subject: [PATCH 0811/4355] Remove as any from hover Part of #269213 --- .../editor/browser/services/hoverService/hoverService.ts | 3 +-- src/vs/platform/hover/test/browser/nullHoverService.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index e43a0239427..4a4f787e19f 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -420,8 +420,7 @@ export class HoverService extends Disposable implements IHoverService { }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; - // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any - hideHover(false, (e).fromElement === targetElement); + hideHover(false, e.relatedTarget === targetElement); }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { if (hoverPreparation) { diff --git a/src/vs/platform/hover/test/browser/nullHoverService.ts b/src/vs/platform/hover/test/browser/nullHoverService.ts index d93ed70e234..6a5baf862ed 100644 --- a/src/vs/platform/hover/test/browser/nullHoverService.ts +++ b/src/vs/platform/hover/test/browser/nullHoverService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { IManagedHoverContent, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import type { IHoverService } from '../../browser/hover.js'; @@ -13,8 +14,12 @@ export const NullHoverService: IHoverService = { showDelayedHover: () => undefined, setupDelayedHover: () => Disposable.None, setupDelayedHoverAtMouse: () => Disposable.None, - // eslint-disable-next-line local/code-no-any-casts - setupManagedHover: () => Disposable.None as any, + setupManagedHover: () => ({ + dispose: () => { }, + show: (focus?: boolean) => { }, + hide: () => { }, + update: (tooltip: IManagedHoverContent, options?: IManagedHoverOptions) => { } + }), showAndFocusLastHover: () => undefined, showManagedHover: () => undefined }; From 9c1aaacc75b656e9debae328d6b77850ad268716 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:42:05 +0900 Subject: [PATCH 0812/4355] Revert to old property --- .../editor/browser/services/hoverService/hoverService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 4a4f787e19f..a86555a7820 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -420,7 +420,12 @@ export class HoverService extends Disposable implements IHoverService { }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; - hideHover(false, e.relatedTarget === targetElement); + // HACK: `fromElement` is a non-standard property. Not sure what to replace it with, + // `relatedTarget` is NOT equivalent. + interface MouseEventWithFrom extends MouseEvent { + fromElement: Element | null; + } + hideHover(false, (e as MouseEventWithFrom).fromElement === targetElement); }, true)); store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { if (hoverPreparation) { From 452135c513822b48856f6c567227207396bedc42 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 6 Oct 2025 10:41:36 +0200 Subject: [PATCH 0813/4355] Bump to latest vscode-textmate (#269983) Bump to latest vscode-textmate which avoids merging tokens when they contain RTL --- package-lock.json | 8 ++++---- package.json | 2 +- remote/package-lock.json | 8 ++++---- remote/package.json | 2 +- remote/web/package-lock.json | 8 ++++---- remote/web/package.json | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0db9f4b6c43..4b4836edeb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.2.1", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, @@ -17838,9 +17838,9 @@ } }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.1.tgz", + "integrity": "sha512-eXiUi2yYFv9bdvgrYtJynA7UemCEkpVNE50S9iBBA08LYG5t9+/TB+8IRS/YoYOubCez2OkSyZ1Q12eQMwzbrw==", "license": "MIT" }, "node_modules/vscode-uri": { diff --git a/package.json b/package.json index 619a372b90c..d62c6643171 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.2.1", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, diff --git a/remote/package-lock.json b/remote/package-lock.json index 399f6425e3e..a6a9e253204 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -42,7 +42,7 @@ "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.2.1", "yauzl": "^3.0.0", "yazl": "^2.4.3" } @@ -1205,9 +1205,9 @@ } }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.1.tgz", + "integrity": "sha512-eXiUi2yYFv9bdvgrYtJynA7UemCEkpVNE50S9iBBA08LYG5t9+/TB+8IRS/YoYOubCez2OkSyZ1Q12eQMwzbrw==", "license": "MIT" }, "node_modules/wrappy": { diff --git a/remote/package.json b/remote/package.json index 24a4c07c9c0..38e9037c747 100644 --- a/remote/package.json +++ b/remote/package.json @@ -37,7 +37,7 @@ "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.2.1", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index b526382f642..80f4f0c7dc7 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -26,7 +26,7 @@ "katex": "^0.16.22", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.2.0" + "vscode-textmate": "^9.2.1" } }, "node_modules/@microsoft/1ds-core-js": { @@ -303,9 +303,9 @@ "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.1.tgz", + "integrity": "sha512-eXiUi2yYFv9bdvgrYtJynA7UemCEkpVNE50S9iBBA08LYG5t9+/TB+8IRS/YoYOubCez2OkSyZ1Q12eQMwzbrw==", "license": "MIT" }, "node_modules/yallist": { diff --git a/remote/web/package.json b/remote/web/package.json index 2ea2a69624c..5e6c923b3b7 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -21,6 +21,6 @@ "katex": "^0.16.22", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.2.0" + "vscode-textmate": "^9.2.1" } } From 051267d42ff5f9329135b43994c9483ef608dfe3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:41:58 +0900 Subject: [PATCH 0814/4355] Remove more as any from terminal Part of #269213 --- extensions/terminal-suggest/src/shell/pwsh.ts | 101 +++++++++++++----- .../terminal.developer.contribution.ts | 3 +- .../links/browser/terminalLinkManager.ts | 12 ++- .../browser/terminalStickyScrollOverlay.ts | 6 +- .../suggest/browser/terminalSuggestAddon.ts | 12 ++- .../browser/terminalTypeAheadAddon.ts | 8 +- .../browser/terminal.zoom.contribution.ts | 75 ++++++------- 7 files changed, 139 insertions(+), 78 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index 8926f72e516..5896309978f 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -66,33 +66,38 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman console.error('Error parsing output:', e); return []; } - return (json as any[]).map(e => { - // Aliases sometimes use the same Name and DisplayName, show them as methods in this case. - const isAlias = e.Name !== e.DisplayName; - const detailParts: string[] = []; - if (e.Definition) { - detailParts.push(e.Definition); - } - if (e.ModuleName && e.Version) { - detailParts.push(`${e.ModuleName} v${e.Version}`); - } - let definitionCommand = undefined; - if (e.Definition) { - let definitionIndex = e.Definition.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = e.Definition.length; - definitionCommand = e.Definition.substring(0, definitionIndex); + if (!Array.isArray(json)) { + return []; + } + return (json as unknown[]) + .filter(isPwshGetCommandEntry) + .map(e => { + // Aliases sometimes use the same Name and DisplayName, show them as methods in this case. + const isAlias = e.Name !== e.DisplayName; + const detailParts: string[] = []; + if (e.Definition) { + detailParts.push(e.Definition); } - } - return { - label: e.Name, - detail: detailParts.join('\n\n'), - kind: (isAlias - ? vscode.TerminalCompletionItemKind.Alias - : vscode.TerminalCompletionItemKind.Method), - definitionCommand, - }; - }); + if (e.ModuleName && e.Version) { + detailParts.push(`${e.ModuleName} v${e.Version}`); + } + let definitionCommand = undefined; + if (e.Definition) { + let definitionIndex = e.Definition.indexOf(' '); + if (definitionIndex === -1) { + definitionIndex = e.Definition.length; + definitionCommand = e.Definition.substring(0, definitionIndex); + } + } + return { + label: e.Name, + detail: detailParts.join('\n\n'), + kind: (isAlias + ? vscode.TerminalCompletionItemKind.Alias + : vscode.TerminalCompletionItemKind.Method), + definitionCommand, + }; + }); } async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { @@ -100,15 +105,19 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma ...options, maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); - let json: any; + let json: unknown; try { json = JSON.parse(output); } catch (e) { console.error('Error parsing pwsh output:', e); return []; } + if (!Array.isArray(json)) { + return []; + } return ( - (json as any[]) + (json as unknown[]) + .filter(isPwshGetCommandEntry) .filter(e => e.CommandType !== PwshCommandType.Alias) .map(e => { const detailParts: string[] = []; @@ -126,3 +135,39 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma }) ); } + +interface IPwshGetCommandEntry { + Name: string; + CommandType: PwshCommandType; + DisplayName?: string | null; + Definition?: string | null; + ModuleName?: string | null; + Version?: string | null; +} + +function isPwshGetCommandEntry(entry: unknown): entry is IPwshGetCommandEntry { + return ( + isObject(entry) && + 'Name' in entry && typeof entry.Name === 'string' && + 'CommandType' in entry && typeof entry.CommandType === 'number' && + (!('DisplayName' in entry) || typeof entry.DisplayName === 'string' || entry.DisplayName === null) && + (!('Definition' in entry) || typeof entry.Definition === 'string' || entry.Definition === null) && + (!('ModuleName' in entry) || typeof entry.ModuleName === 'string' || entry.ModuleName === null) && + (!('Version' in entry) || typeof entry.Version === 'string' || entry.Version === null) + ); +} + +/** + * @returns whether the provided parameter is of type `object` but **not** + * `null`, an `array`, a `regexp`, nor a `date`. + */ +export function isObject(obj: unknown): obj is Object { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === 'object' + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index a2e38224ecc..5eeb1fe62e4 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -91,8 +91,7 @@ registerTerminalAction({ } escapedData = escapedData.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + escapedData.slice(match.index + 4); } - // eslint-disable-next-line local/code-no-any-casts - const xterm = instance.xterm as any as IInternalXtermTerminal; + const xterm = instance.xterm as IInternalXtermTerminal; xterm._writeText(escapedData); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 32abf0e15ca..6081c7f1555 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -149,8 +149,10 @@ export class TerminalLinkManager extends DisposableStore { activeHoverDisposable = undefined; activeTooltipScheduler?.dispose(); activeTooltipScheduler = new RunOnceScheduler(() => { - // eslint-disable-next-line local/code-no-any-casts - const core = (this._xterm as any)._core as IXtermCore; + interface XtermWithCore extends Terminal { + _core: IXtermCore; + } + const core = (this._xterm as XtermWithCore)._core; const cellDimensions = { width: core._renderService.dimensions.css.cell.width, height: core._renderService.dimensions.css.cell.height @@ -348,8 +350,10 @@ export class TerminalLinkManager extends DisposableStore { return; } - // eslint-disable-next-line local/code-no-any-casts - const core = (this._xterm as any)._core as IXtermCore; + interface XtermWithCore extends Terminal { + _core: IXtermCore; + } + const core = (this._xterm as XtermWithCore)._core; const cellDimensions = { width: core._renderService.dimensions.css.cell.width, height: core._renderService.dimensions.css.cell.height diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index ad86b75ae4c..f884f7fcb19 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -406,8 +406,10 @@ export class TerminalStickyScrollOverlay extends Disposable { } hoverOverlay.title = hoverTitle; - // eslint-disable-next-line local/code-no-any-casts - const scrollBarWidth = (this._xterm.raw as any as { _core: IXtermCore })._core.viewport?.scrollBarWidth; + interface XtermWithCore extends XTermTerminal { + _core: IXtermCore; + } + const scrollBarWidth = (this._xterm.raw as XtermWithCore)._core.viewport?.scrollBarWidth; if (scrollBarWidth !== undefined) { this._element.style.right = `${scrollBarWidth}px`; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index b9fb6c090b0..eee28abf407 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -682,8 +682,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } private _getTerminalDimensions(): { width: number; height: number } { - // eslint-disable-next-line local/code-no-any-casts - const cssCellDims = (this._terminal as any as { _core: IXtermCore })._core._renderService.dimensions.css.cell; + interface XtermWithCore extends Terminal { + _core: IXtermCore; + } + const cssCellDims = (this._terminal as XtermWithCore)._core._renderService.dimensions.css.cell; return { width: cssCellDims.width, height: cssCellDims.height, @@ -708,8 +710,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest return this._cachedFontInfo; } - // eslint-disable-next-line local/code-no-any-casts - const core = (this._terminal as any)._core as IXtermCore; + interface XtermWithCore extends Terminal { + _core: IXtermCore; + } + const core = (this._terminal as XtermWithCore)._core; const font = this._terminalConfigurationService.getFont(dom.getActiveWindow(), core); let lineHeight: number = font.lineHeight; const fontSize: number = font.fontSize; diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts index 48768032a9e..ffc5805f401 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts @@ -50,8 +50,12 @@ const enum StatsConstants { */ const PREDICTION_OMIT_RE = /^(\x1b\[(\??25[hl]|\??[0-9;]+n))+/; -// eslint-disable-next-line local/code-no-any-casts -const core = (terminal: Terminal): IXtermCore => (terminal as any)._core; +const core = (terminal: Terminal): IXtermCore => { + interface XtermWithCore extends Terminal { + _core: IXtermCore; + } + return (terminal as XtermWithCore)._core; +} const flushOutput = (terminal: Terminal) => { // TODO: Flushing output is not possible anymore without async }; diff --git a/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts index 1d7337dc44e..cf4d8e63508 100644 --- a/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts @@ -71,43 +71,46 @@ class TerminalMouseWheelZoomContribution extends Disposable implements ITerminal let gestureHasZoomModifiers = false; let gestureAccumulatedDelta = 0; - raw.attachCustomWheelEventHandler((e: WheelEvent) => { - // eslint-disable-next-line local/code-no-any-casts - const browserEvent = e as any as IMouseWheelEvent; - if (classifier.isPhysicalMouseWheel()) { - if (this._hasMouseWheelZoomModifiers(browserEvent)) { - const delta = browserEvent.deltaY > 0 ? -1 : 1; - const newFontSize = this._clampFontSize(this._getConfigFontSize() + delta); - this._configurationService.updateValue(TerminalSettingId.FontSize, newFontSize); - // EditorZoom.setZoomLevel(zoomLevel + delta); - browserEvent.preventDefault(); - browserEvent.stopPropagation(); - return false; - } - } else { - // we consider mousewheel events that occur within 50ms of each other to be part of the same gesture - // we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase - // we also want to accumulate deltaY values from the same gesture and use that to set the zoom level - if (Date.now() - prevMouseWheelTime > 50) { - // reset if more than 50ms have passed - gestureStartFontSize = this._getConfigFontSize(); - gestureHasZoomModifiers = this._hasMouseWheelZoomModifiers(browserEvent); - gestureAccumulatedDelta = 0; - } - - prevMouseWheelTime = Date.now(); - gestureAccumulatedDelta += browserEvent.deltaY; - - if (gestureHasZoomModifiers) { - const deltaAbs = Math.ceil(Math.abs(gestureAccumulatedDelta / 5)); - const deltaDirection = gestureAccumulatedDelta > 0 ? -1 : 1; - const delta = deltaAbs * deltaDirection; - const newFontSize = this._clampFontSize(gestureStartFontSize + delta); - this._configurationService.updateValue(TerminalSettingId.FontSize, newFontSize); + raw.attachCustomWheelEventHandler((browserEvent: WheelEvent) => { + function isWheelEvent(e: MouseEvent): e is IMouseWheelEvent { + return 'wheelDelta' in e && 'wheelDeltaX' in e && 'wheelDeltaY' in e; + } + if (isWheelEvent(browserEvent)) { + if (classifier.isPhysicalMouseWheel()) { + if (this._hasMouseWheelZoomModifiers(browserEvent)) { + const delta = browserEvent.deltaY > 0 ? -1 : 1; + const newFontSize = this._clampFontSize(this._getConfigFontSize() + delta); + this._configurationService.updateValue(TerminalSettingId.FontSize, newFontSize); + // EditorZoom.setZoomLevel(zoomLevel + delta); + browserEvent.preventDefault(); + browserEvent.stopPropagation(); + return false; + } + } else { + // we consider mousewheel events that occur within 50ms of each other to be part of the same gesture + // we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase + // we also want to accumulate deltaY values from the same gesture and use that to set the zoom level + if (Date.now() - prevMouseWheelTime > 50) { + // reset if more than 50ms have passed + gestureStartFontSize = this._getConfigFontSize(); + gestureHasZoomModifiers = this._hasMouseWheelZoomModifiers(browserEvent); + gestureAccumulatedDelta = 0; + } + + prevMouseWheelTime = Date.now(); gestureAccumulatedDelta += browserEvent.deltaY; - browserEvent.preventDefault(); - browserEvent.stopPropagation(); - return false; + + if (gestureHasZoomModifiers) { + const deltaAbs = Math.ceil(Math.abs(gestureAccumulatedDelta / 5)); + const deltaDirection = gestureAccumulatedDelta > 0 ? -1 : 1; + const delta = deltaAbs * deltaDirection; + const newFontSize = this._clampFontSize(gestureStartFontSize + delta); + this._configurationService.updateValue(TerminalSettingId.FontSize, newFontSize); + gestureAccumulatedDelta += browserEvent.deltaY; + browserEvent.preventDefault(); + browserEvent.stopPropagation(); + return false; + } } } return true; From 25fb2bada8ffce98bd459f640f560ec685df744b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Oct 2025 10:57:52 +0200 Subject: [PATCH 0815/4355] chore - Remove unnecessary type assertion in ResourceMap callback (#269984) --- src/vs/base/common/map.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 23ce41e3ca6..377e37e6ea1 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -121,8 +121,7 @@ export class ResourceMap implements Map { clb = clb.bind(thisArg); } for (const [_, entry] of this.map) { - // eslint-disable-next-line local/code-no-any-casts - clb(entry.value, entry.uri, this); + clb(entry.value, entry.uri, this); } } From 4b8bd3cb6fb916c3abd4801003599ea653a6fe80 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:12:40 +0200 Subject: [PATCH 0816/4355] Engineering - refactor product pipeline (#269979) --- .../alpine/cli-build-alpine.yml | 108 ----- .../alpine/product-build-alpine-cli.yml | 102 +++++ .../alpine/product-build-alpine.yml | 401 +++++++++--------- .../darwin/cli-build-darwin.yml | 84 ---- .../darwin/product-build-darwin-ci.yml | 23 + .../darwin/product-build-darwin-cli-sign.yml | 122 +++--- .../darwin/product-build-darwin-cli.yml | 82 ++++ .../darwin/product-build-darwin-universal.yml | 314 +++++++------- .../darwin/product-build-darwin.yml | 381 +++-------------- .../steps/product-build-darwin-cli-sign.yml} | 12 +- .../steps/product-build-darwin-compile.yml | 306 +++++++++++++ .../{ => steps}/product-build-darwin-test.yml | 40 -- .../azure-pipelines/linux/cli-build-linux.yml | 158 ------- build/azure-pipelines/linux/codesign.js | 4 +- build/azure-pipelines/linux/codesign.ts | 4 +- .../linux/product-build-linux-ci.yml | 6 +- .../linux/product-build-linux-cli.yml | 139 ++++++ .../linux/product-build-linux.yml | 50 +-- .../steps/product-build-linux-compile.yml | 56 ++- build/azure-pipelines/product-build.yml | 270 ++++-------- build/azure-pipelines/product-publish.yml | 183 ++++---- .../azure-pipelines/web/product-build-web.yml | 352 +++++++-------- .../win32/product-build-win32-ci.yml | 6 +- .../win32/product-build-win32-cli-sign.yml | 6 +- .../win32/product-build-win32-cli.yml | 3 +- .../win32/product-build-win32.yml | 41 +- .../steps/product-build-win32-cli-sign.yml | 2 +- .../steps/product-build-win32-compile.yml | 35 +- 28 files changed, 1612 insertions(+), 1678 deletions(-) delete mode 100644 build/azure-pipelines/alpine/cli-build-alpine.yml create mode 100644 build/azure-pipelines/alpine/product-build-alpine-cli.yml delete mode 100644 build/azure-pipelines/darwin/cli-build-darwin.yml create mode 100644 build/azure-pipelines/darwin/product-build-darwin-ci.yml create mode 100644 build/azure-pipelines/darwin/product-build-darwin-cli.yml rename build/azure-pipelines/{cli/cli-darwin-sign.yml => darwin/steps/product-build-darwin-cli-sign.yml} (77%) create mode 100644 build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml rename build/azure-pipelines/darwin/{ => steps}/product-build-darwin-test.yml (76%) delete mode 100644 build/azure-pipelines/linux/cli-build-linux.yml create mode 100644 build/azure-pipelines/linux/product-build-linux-cli.yml diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml deleted file mode 100644 index 6180a7efd10..00000000000 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ /dev/null @@ -1,108 +0,0 @@ -parameters: - - name: VSCODE_BUILD_ALPINE - type: boolean - default: false - - name: VSCODE_BUILD_ALPINE_ARM64 - type: boolean - default: false - - name: VSCODE_QUALITY - type: string - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../cli/cli-apply-patches.yml@self - - - script: | - set -e - npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - # inspired by: https://github.com/emk/rust-musl-builder/blob/main/Dockerfile - - bash: | - set -e - sudo apt-get update - sudo apt-get install -yq build-essential musl-dev musl-tools linux-libc-dev pkgconf xutils-dev lld - sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" || echo "link exists" - displayName: Install musl build dependencies - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - aarch64-unknown-linux-musl - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - x86_64-unknown-linux-musl - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_CLI_TARGET: aarch64-unknown-linux-musl - VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/include - OPENSSL_STATIC: "1" - SYSROOT_ARCH: arm64 - IS_MUSL: "1" - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_CLI_TARGET: x86_64-unknown-linux-musl - VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_ENV: - CXX_aarch64-unknown-linux-musl: musl-g++ - CC_aarch64-unknown-linux-musl: musl-gcc - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/include - OPENSSL_STATIC: "1" - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_arm64_cli.tar.gz - artifactName: vscode_cli_alpine_arm64_cli - displayName: Publish vscode_cli_alpine_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_x64_cli.tar.gz - artifactName: vscode_cli_alpine_x64_cli - displayName: Publish vscode_cli_alpine_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/build/azure-pipelines/alpine/product-build-alpine-cli.yml b/build/azure-pipelines/alpine/product-build-alpine-cli.yml new file mode 100644 index 00000000000..9f3f60a6b24 --- /dev/null +++ b/build/azure-pipelines/alpine/product-build-alpine-cli.yml @@ -0,0 +1,102 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: +- job: AlpineCLI_${{ parameters.VSCODE_ARCH }} + displayName: Alpine (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_$(VSCODE_ARCH)_cli.tar.gz + artifactName: vscode_cli_alpine_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_alpine_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - script: | + set -e + npm ci + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + # inspired by: https://github.com/emk/rust-musl-builder/blob/main/Dockerfile + - bash: | + set -e + sudo apt-get update + sudo apt-get install -yq build-essential musl-dev musl-tools linux-libc-dev pkgconf xutils-dev lld + sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" || echo "link exists" + displayName: Install musl build dependencies + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-unknown-linux-musl + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-unknown-linux-musl + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_CLI_TARGET: x86_64-unknown-linux-musl + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_ENV: + CXX_aarch64-unknown-linux-musl: musl-g++ + CC_aarch64-unknown-linux-musl: musl-gcc + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/include + OPENSSL_STATIC: "1" + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_CLI_TARGET: aarch64-unknown-linux-musl + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/include + OPENSSL_STATIC: "1" + SYSROOT_ARCH: arm64 + IS_MUSL: "1" diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index a2967162b25..c6d5ba27eda 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -1,197 +1,204 @@ -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js alpine $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - task: Docker@1 - inputs: - azureSubscriptionEndpoint: vscode - azureContainerRegistry: vscodehub.azurecr.io - command: "Run an image" - imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" - containerCommand: uname - displayName: "Pull image" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev - displayName: Install build dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - mkdir -p .build/nodejs-musl - NODE_VERSION=$(grep '^target=' remote/.npmrc | cut -d '"' -f 2) - BUILD_ID=$(grep '^ms_build_id=' remote/.npmrc | cut -d '"' -f 2) - gh release download "v${NODE_VERSION}-${BUILD_ID}" -R microsoft/vscode-node -p "node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" --dir .build/nodejs-musl --clobber - tar -xzf ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" -C ".build/nodejs-musl" --strip-components=1 - rm ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download NodeJS MUSL - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(NPM_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) - VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" - VSCODE_NPMRC_PATH: $(NPMRC_PATH) - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: node build/azure-pipelines/distro/mixin-npm - displayName: Mixin distro node modules - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - script: | - set -e - TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno - npm run gulp vscode-reh-$TARGET-min-ci - (cd .. && mv vscode-reh-$TARGET vscode-server-$TARGET) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/server/vscode-server-$TARGET.tar.gz" - DIR_PATH="$(realpath ../vscode-server-$TARGET)" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET - echo "##vso[task.setvariable variable=SERVER_DIR_PATH]$DIR_PATH" - echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - script: | - set -e - TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") - npm run gulp vscode-reh-web-$TARGET-min-ci - (cd .. && mv vscode-reh-web-$TARGET vscode-server-$TARGET-web) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/web/vscode-server-$TARGET-web.tar.gz" - DIR_PATH="$(realpath ../vscode-server-$TARGET-web)" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET-web - echo "##vso[task.setvariable variable=WEB_DIR_PATH]$DIR_PATH" - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish server archive - sbomBuildDropPath: $(SERVER_DIR_PATH) - sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Server" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_alpine_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish web server archive - sbomBuildDropPath: $(WEB_DIR_PATH) - sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_linux_alpine_archive-unsigned - displayName: Publish x64 server archive - sbomEnabled: false - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), eq(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_alpine_archive-unsigned - displayName: Publish x64 web server archive - sbomEnabled: false - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), eq(variables['VSCODE_ARCH'], 'x64')) +parameters: + - name: VSCODE_ARCH + type: string + +jobs: + - job: Alpine_${{ parameters.VSCODE_ARCH }} + displayName: Alpine (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 30 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + NPM_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + # keep legacy name + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-linux-alpine.tar.gz + artifactName: vscode_server_linux_alpine_archive-unsigned + displayName: Publish x64 server archive + sbomBuildDropPath: $(SERVER_DIR_PATH) + sbomPackageName: "VS Code Alpine x64 Server" + sbomPackageVersion: $(Build.SourceVersion) + # keep legacy name + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-linux-alpine-web.tar.gz + artifactName: vscode_web_linux_alpine_archive-unsigned + displayName: Publish x64 web server archive + sbomBuildDropPath: $(WEB_DIR_PATH) + sbomPackageName: "VS Code Alpine x64 Web" + sbomPackageVersion: $(Build.SourceVersion) + - ${{ if ne(parameters.VSCODE_ARCH, 'x64') }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-alpine-$(VSCODE_ARCH).tar.gz + artifactName: vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish server archive + sbomBuildDropPath: $(SERVER_DIR_PATH) + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Server" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-alpine-$(VSCODE_ARCH)-web.tar.gz + artifactName: vscode_web_alpine_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish web server archive + sbomBuildDropPath: $(WEB_DIR_PATH) + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Web" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js alpine $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - task: Docker@1 + inputs: + azureSubscriptionEndpoint: vscode + azureContainerRegistry: vscodehub.azurecr.io + command: "Run an image" + imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" + containerCommand: uname + displayName: "Pull image" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + mkdir -p .build/nodejs-musl + NODE_VERSION=$(grep '^target=' remote/.npmrc | cut -d '"' -f 2) + BUILD_ID=$(grep '^ms_build_id=' remote/.npmrc | cut -d '"' -f 2) + gh release download "v${NODE_VERSION}-${BUILD_ID}" -R microsoft/vscode-node -p "node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" --dir .build/nodejs-musl --clobber + tar -xzf ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" -C ".build/nodejs-musl" --strip-components=1 + rm ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download NodeJS MUSL + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" + VSCODE_NPMRC_PATH: $(NPMRC_PATH) + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm + displayName: Mixin distro node modules + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml@self + + - script: | + set -e + TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno + npm run gulp vscode-reh-$TARGET-min-ci + (cd .. && mv vscode-reh-$TARGET vscode-server-$TARGET) # TODO@joaomoreno + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/server/vscode-server-$TARGET.tar.gz" + DIR_PATH="$(realpath ../vscode-server-$TARGET)" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET + echo "##vso[task.setvariable variable=SERVER_DIR_PATH]$DIR_PATH" + echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - script: | + set -e + TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") + npm run gulp vscode-reh-web-$TARGET-min-ci + (cd .. && mv vscode-reh-web-$TARGET vscode-server-$TARGET-web) # TODO@joaomoreno + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/web/vscode-server-$TARGET-web.tar.gz" + DIR_PATH="$(realpath ../vscode-server-$TARGET-web)" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET-web + echo "##vso[task.setvariable variable=WEB_DIR_PATH]$DIR_PATH" + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml deleted file mode 100644 index fe44f2827fd..00000000000 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ /dev/null @@ -1,84 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - - name: VSCODE_BUILD_MACOS - type: boolean - default: false - - name: VSCODE_BUILD_MACOS_ARM64 - type: boolean - default: false - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../cli/cli-apply-patches.yml@self - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - x86_64-apple-darwin - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - aarch64-apple-darwin - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: x86_64-apple-darwin - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: aarch64-apple-darwin - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_x64_cli.zip - artifactName: unsigned_vscode_cli_darwin_x64_cli - displayName: Publish unsigned_vscode_cli_darwin_x64_cli artifact - sbomEnabled: false - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_arm64_cli.zip - artifactName: unsigned_vscode_cli_darwin_arm64_cli - displayName: Publish unsigned_vscode_cli_darwin_arm64_cli artifact - sbomEnabled: false diff --git a/build/azure-pipelines/darwin/product-build-darwin-ci.yml b/build/azure-pipelines/darwin/product-build-darwin-ci.yml new file mode 100644 index 00000000000..6dfc202c3ce --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-ci.yml @@ -0,0 +1,23 @@ +parameters: + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_TEST_SUITE + type: string + +jobs: + - job: macOS${{ parameters.VSCODE_TEST_SUITE }} + displayName: ${{ parameters.VSCODE_TEST_SUITE }} Tests + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: arm64 + steps: + - template: ./steps/product-build-darwin-compile.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: + VSCODE_RUN_ELECTRON_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: + VSCODE_RUN_BROWSER_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Remote') }}: + VSCODE_RUN_REMOTE_TESTS: true diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml index a622547e2de..3b98f9ef6c8 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -4,61 +4,83 @@ parameters: - name: VSCODE_BUILD_MACOS_ARM64 type: boolean -steps: - - template: ../common/checkout.yml@self +jobs: + - job: macOSCLISign + timeoutInMinutes: 90 + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_x64_cli/vscode_cli_darwin_x64_cli.zip + artifactName: vscode_cli_darwin_x64_cli + displayName: Publish signed artifact with ID vscode_cli_darwin_x64_cli + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_x64_cli + sbomPackageName: "VS Code macOS unsigned_vscode_cli_darwin_x64_cli CLI" + sbomPackageVersion: $(Build.SourceVersion) + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_arm64_cli/vscode_cli_darwin_arm64_cli.zip + artifactName: vscode_cli_darwin_arm64_cli + displayName: Publish signed artifact with ID vscode_cli_darwin_arm64_cli + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_arm64_cli + sbomPackageName: "VS Code macOS unsigned_vscode_cli_darwin_arm64_cli CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry + - script: node build/setup-npm-registry.js $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - - script: | - set -e + - script: | + set -e - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies - - template: ../cli/cli-darwin-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - unsigned_vscode_cli_darwin_x64_cli - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - unsigned_vscode_cli_darwin_arm64_cli + - template: ./steps/product-build-darwin-cli-sign.yml@self + parameters: + VSCODE_CLI_ARTIFACTS: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - unsigned_vscode_cli_darwin_x64_cli + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - unsigned_vscode_cli_darwin_arm64_cli diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml new file mode 100644 index 00000000000..ed78c39fd9e --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -0,0 +1,82 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: +- job: macOSCLI_${{ parameters.VSCODE_ARCH }} + displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: AcesShared + os: macOS + variables: + # todo@connor4312 to diagnose build flakes + MSRUSTUP_LOG: debug + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact + sbomEnabled: false + isProduction: false + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-apple-darwin + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-apple-darwin + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: x86_64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: aarch64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index b1153dc8c8b..23c85dc714a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -1,153 +1,161 @@ -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - pwsh: node build/azure-pipelines/common/waitForArtifacts.js unsigned_vscode_client_darwin_x64_archive unsigned_vscode_client_darwin_arm64_archive - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Wait for x64 and arm64 artifacts - - - download: current - artifact: unsigned_vscode_client_darwin_x64_archive - displayName: Download x64 artifact - - - download: current - artifact: unsigned_vscode_client_darwin_arm64_archive - displayName: Download arm64 artifact - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - script: | - set -e - unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 & - unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 & - wait - DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory) - displayName: Create Universal App - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js universal - displayName: Verify arch of Mach-O objects - - - script: | - set -e - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain - DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) - displayName: Set Hardened Entitlements - - - script: | - set -e - mkdir -p $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd - displayName: Archive build - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Notarize - - - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - displayName: Extract signed app - - - script: | - set -e - APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - codesign -dv --deep --verbose=4 "$APP_PATH" - "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build - displayName: Verify signature - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - - script: mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - displayName: Rename x64 build to its legacy name - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-universal.zip - artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive - displayName: Publish client archive - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" - sbomPackageVersion: $(Build.SourceVersion) +jobs: + - job: macOSUniversal + displayName: macOS (UNIVERSAL) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-universal.zip + artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" + + - script: node build/setup-npm-registry.js $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - pwsh: node build/azure-pipelines/common/waitForArtifacts.js unsigned_vscode_client_darwin_x64_archive unsigned_vscode_client_darwin_arm64_archive + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for x64 and arm64 artifacts + + - download: current + artifact: unsigned_vscode_client_darwin_x64_archive + displayName: Download x64 artifact + + - download: current + artifact: unsigned_vscode_client_darwin_arm64_archive + displayName: Download arm64 artifact + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - script: | + set -e + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 & + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 & + wait + DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory) + displayName: Create Universal App + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js universal + displayName: Verify arch of Mach-O objects + + - script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain + DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) + displayName: Set Hardened Entitlements + + - script: | + set -e + mkdir -p $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd + displayName: Archive build + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Notarize + + - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + displayName: Extract signed app + + - script: | + set -e + APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + codesign -dv --deep --verbose=4 "$APP_PATH" + "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify signature + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive + mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip + displayName: Move artifact to out directory diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index dfb8284426a..770a54f7925 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -12,321 +12,68 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - c++ --version - xcode-select -print-path - python3 -m pip install setuptools - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(VSCODE_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries - # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 - # flipped the default to support legacy linux distros which shouldn't happen - # on macOS. - GYP_DEFINES: "kerberos_use_rtld=false" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies darwin - displayName: Generate policy definitions - retryCountOnTaskFailure: 3 - - - script: | - set -e - npm run gulp vscode-darwin-$(VSCODE_ARCH)-min-ci - echo "##vso[task.setvariable variable=BUILT_CLIENT]true" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build client - - - script: | - set -e - npm run gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH) # TODO@joaomoreno - ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)) - echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - script: | - set -e - npm run gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-web-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH)-web # TODO@joaomoreno - ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web) - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - task: DownloadPipelineArtifact@2 - inputs: - artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli - patterns: "**" - path: $(Build.ArtifactStagingDirectory)/cli - displayName: Download VS Code CLI - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - unzip $(Build.ArtifactStagingDirectory)/cli/*.zip -d $(Build.ArtifactStagingDirectory)/cli - CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName") - APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").applicationName") - mv "$(Build.ArtifactStagingDirectory)/cli/$APP_NAME" "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" - chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" - displayName: Make CLI executable - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js $(VSCODE_ARCH) - APP_PATH="$(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)" node build/darwin/verify-macho.js $(VSCODE_ARCH) - displayName: Verify arch of Mach-O objects - - - script: | - set -e - ARCHIVE_PATH="$(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) - echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" - condition: and(succeededOrFailed(), eq(variables['BUILT_CLIENT'], 'true')) - displayName: Package client - - - pwsh: node build/azure-pipelines/common/checkForArtifact.js CLIENT_ARCHIVE_UPLOADED unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Check for client artifact - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(CLIENT_PATH) - artifactName: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - displayName: Publish client archive (unsigned) - sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeeded(), ne(variables['CLIENT_PATH'], ''), eq(variables['CLIENT_ARCHIVE_UPLOADED'], 'false')) - - # Hardened entitlements should be set after publishing unsigned client artifacts - # to ensure entitlement signing doesn't modify sha that would affect universal build. - # - # Setting hardened entitlements is a requirement for: - # * Apple notarization - # * Running tests on Big Sur (because Big Sur has additional security precautions) - - script: | - set -e - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain - DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) - displayName: Set Hardened Entitlements - - - script: | - set -e - ARCHIVE_PATH="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) - echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" - condition: and(succeededOrFailed(), eq(variables['BUILT_CLIENT'], 'true')) - displayName: Re-package client after entitlement - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" - displayName: Find ESRP CLI - - - script: npx deemon --detach --wait node build/azure-pipelines/darwin/codesign.js - env: - EsrpCliDllPath: $(EsrpCliDllPath) - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign & Notarize - - - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - - template: product-build-darwin-test.yml@self - parameters: - VSCODE_TEST_ARTIFACT_NAME: ${{ parameters.VSCODE_TEST_ARTIFACT_NAME }} - VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} - VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} - VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: npx deemon --attach node build/azure-pipelines/darwin/codesign.js - condition: succeededOrFailed() - displayName: "Post-job: ✍️ Codesign & Notarize" - - - script: unzip $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - displayName: Extract signed app - - - script: | - set -e - APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - codesign -dv --deep --verbose=4 "$APP_PATH" - "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build - displayName: Verify signature - - - script: mv $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - displayName: Rename x64 build to its legacy name - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - - template: ../common/publish-artifact.yml@self - parameters: - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: - targetPath: $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-arm64.zip - ${{ else }}: - targetPath: $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive - displayName: Publish client archive - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" - sbomPackageVersion: $(Build.SourceVersion) - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish server archive - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Server" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish web server archive - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) +jobs: + - job: macOS_${{ parameters.VSCODE_ARCH }} + displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Crash Reports" + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Node Modules" + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Log Files" + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + - output: pipelineArtifact + ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip + ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-arm64.zip + artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip + artifactName: vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Server" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH)-web.zip + artifactName: vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish web server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ./steps/product-build-darwin-compile.yml@self + parameters: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} diff --git a/build/azure-pipelines/cli/cli-darwin-sign.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml similarity index 77% rename from build/azure-pipelines/cli/cli-darwin-sign.yml rename to build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml index d5c188037ca..883645aec69 100644 --- a/build/azure-pipelines/cli/cli-darwin-sign.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml @@ -47,15 +47,7 @@ steps: - script: | set -e ASSET_ID=$(echo "${{ target }}" | sed "s/unsigned_//") - mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$ASSET_ID.zip + mkdir -p $(Build.ArtifactStagingDirectory)/out/$ASSET_ID + mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/out/$ASSET_ID/$ASSET_ID.zip echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" displayName: Set asset id variable - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$(ASSET_ID).zip - artifactName: $(ASSET_ID) - displayName: Publish signed artifact with ID $(ASSET_ID) - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - sbomPackageName: "VS Code macOS ${{ target }} CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml new file mode 100644 index 00000000000..bc8e962f91b --- /dev/null +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -0,0 +1,306 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + default: false + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + default: false + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + default: false + +steps: + - template: ../../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + c++ --version + xcode-select -print-path + python3 -m pip install setuptools + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(VSCODE_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries + # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 + # flipped the default to support legacy linux distros which shouldn't happen + # on macOS. + GYP_DEFINES: "kerberos_use_rtld=false" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: ../../common/install-builtin-extensions.yml@self + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: node build/lib/policies darwin + displayName: Generate policy definitions + retryCountOnTaskFailure: 3 + + - script: | + set -e + npm run gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + echo "##vso[task.setvariable variable=BUILT_CLIENT]true" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build client + + - script: | + set -e + npm run gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH) # TODO@joaomoreno + ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)) + echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - script: | + set -e + npm run gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-web-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH)-web # TODO@joaomoreno + ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web) + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + unzip $(Build.ArtifactStagingDirectory)/cli/*.zip -d $(Build.ArtifactStagingDirectory)/cli + CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName") + APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").applicationName") + mv "$(Build.ArtifactStagingDirectory)/cli/$APP_NAME" "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + displayName: Make CLI executable + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js $(VSCODE_ARCH) + APP_PATH="$(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)" node build/darwin/verify-macho.js $(VSCODE_ARCH) + displayName: Verify arch of Mach-O objects + + - script: | + set -e + ARCHIVE_PATH="$(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) + echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + condition: eq(variables['BUILT_CLIENT'], 'true') + displayName: Package client + + - pwsh: node build/azure-pipelines/common/checkForArtifact.js CLIENT_ARCHIVE_UPLOADED unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Check for client artifact + + # We are publishing the unsigned client artifact before running tests + # since the macOS (UNIVERSAL) job is blocked waiting for the artifact. + - template: ../../common/publish-artifact.yml@self + parameters: + targetPath: $(CLIENT_PATH) + artifactName: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive (unsigned) + sbomEnabled: false + condition: and(ne(variables['CLIENT_PATH'], ''), eq(variables['CLIENT_ARCHIVE_UPLOADED'], 'false')) + + # Hardened entitlements should be set after publishing unsigned client artifacts + # to ensure entitlement signing doesn't modify sha that would affect universal build. + # + # Setting hardened entitlements is a requirement for: + # * Apple notarization + # * Running tests on Big Sur (because Big Sur has additional security precautions) + - script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain + DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) + displayName: Set Hardened Entitlements + + - script: | + set -e + ARCHIVE_PATH="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) + echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + condition: eq(variables['BUILT_CLIENT'], 'true') + displayName: Re-package client after entitlement + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - pwsh: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" + displayName: Find ESRP CLI + + - script: npx deemon --detach --wait node build/azure-pipelines/darwin/codesign.js + env: + EsrpCliDllPath: $(EsrpCliDllPath) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign & Notarize + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - template: product-build-darwin-test.yml@self + parameters: + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach node build/azure-pipelines/darwin/codesign.js + condition: succeededOrFailed() + displayName: "Post-job: ✍️ Codesign & Notarize" + + - script: unzip $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + displayName: Extract signed app + + - script: | + set -e + APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + codesign -dv --deep --verbose=4 "$APP_PATH" + "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify signature + + - script: | + set -e + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive + if [ "$VSCODE_ARCH" == "x64" ]; then + # Use legacy name for x64 builds + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip + else + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip + fi + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive + mv $(SERVER_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive + mv $(WEB_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH)-web.zip + displayName: Move artifacts to out directory diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml similarity index 76% rename from build/azure-pipelines/darwin/product-build-darwin-test.yml rename to build/azure-pipelines/darwin/steps/product-build-darwin-test.yml index f2b5e697c4d..1facd03c6ee 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml @@ -5,8 +5,6 @@ parameters: type: boolean - name: VSCODE_RUN_REMOTE_TESTS type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string steps: - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -124,44 +122,6 @@ steps: continueOnError: true condition: succeededOrFailed() - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: .build/crashes - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: crash-dump-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: crash-dump-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - displayName: "Publish Crash Reports" - sbomEnabled: false - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: node_modules - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: node-modules-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: node-modules-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - displayName: "Publish Node Modules" - sbomEnabled: false - continueOnError: true - condition: failed() - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: .build/logs - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: logs-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: logs-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - displayName: "Publish Log Files" - sbomEnabled: false - continueOnError: true - condition: succeededOrFailed() - - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml deleted file mode 100644 index c79c00ecf89..00000000000 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ /dev/null @@ -1,158 +0,0 @@ -parameters: - - name: VSCODE_BUILD_LINUX - type: boolean - default: false - - name: VSCODE_BUILD_LINUX_ARM64 - type: boolean - default: false - - name: VSCODE_BUILD_LINUX_ARMHF - type: boolean - default: false - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - - name: VSCODE_QUALITY - type: string - -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../cli/cli-apply-patches.yml@self - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - script: | - set -e - mkdir -p $(Build.SourcesDirectory)/.build - displayName: Create .build folder for misc dependencies - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - aarch64-unknown-linux-gnu - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - x86_64-unknown-linux-gnu - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - armv7-unknown-linux-gnueabihf - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu - VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/include - SYSROOT_ARCH: arm64 - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu - VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/include - SYSROOT_ARCH: amd64 - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf - VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include - SYSROOT_ARCH: armhf - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_armhf_cli.tar.gz - artifactName: vscode_cli_linux_armhf_cli - displayName: Publish vscode_cli_linux_armhf_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux armhf CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_x64_cli.tar.gz - artifactName: vscode_cli_linux_x64_cli - displayName: Publish vscode_cli_linux_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_arm64_cli.tar.gz - artifactName: vscode_cli_linux_arm64_cli - displayName: Publish vscode_cli_linux_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/build/azure-pipelines/linux/codesign.js b/build/azure-pipelines/linux/codesign.js index 896a3ad8c2b..93ec2434ea8 100644 --- a/build/azure-pipelines/linux/codesign.js +++ b/build/azure-pipelines/linux/codesign.js @@ -11,8 +11,8 @@ async function main() { // Start the code sign processes in parallel // 1. Codesign deb package // 2. Codesign rpm package - const codesignTask1 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/out/deb', '*.deb'); - const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/out/rpm', '*.rpm'); + const codesignTask1 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/deb', '*.deb'); + const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/rpm', '*.rpm'); // Codesign deb package (0, codesign_1.printBanner)('Codesign deb package'); await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign deb package', codesignTask1); diff --git a/build/azure-pipelines/linux/codesign.ts b/build/azure-pipelines/linux/codesign.ts index 4cc7d404150..1f74cc21ee9 100644 --- a/build/azure-pipelines/linux/codesign.ts +++ b/build/azure-pipelines/linux/codesign.ts @@ -12,8 +12,8 @@ async function main() { // Start the code sign processes in parallel // 1. Codesign deb package // 2. Codesign rpm package - const codesignTask1 = spawnCodesignProcess(esrpCliDLLPath, 'sign-pgp', '.build/linux/out/deb', '*.deb'); - const codesignTask2 = spawnCodesignProcess(esrpCliDLLPath, 'sign-pgp', '.build/linux/out/rpm', '*.rpm'); + const codesignTask1 = spawnCodesignProcess(esrpCliDLLPath, 'sign-pgp', '.build/linux/deb', '*.deb'); + const codesignTask2 = spawnCodesignProcess(esrpCliDLLPath, 'sign-pgp', '.build/linux/rpm', '*.rpm'); // Codesign deb package printBanner('Codesign deb package'); diff --git a/build/azure-pipelines/linux/product-build-linux-ci.yml b/build/azure-pipelines/linux/product-build-linux-ci.yml index 015895a2804..6c6b102891a 100644 --- a/build/azure-pipelines/linux/product-build-linux-ci.yml +++ b/build/azure-pipelines/linux/product-build-linux-ci.yml @@ -17,21 +17,21 @@ jobs: templateContext: outputs: - output: pipelineArtifact - targetPath: .build/crashes + targetPath: $(Build.SourcesDirectory)/.build/crashes artifactName: crash-dump-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Crash Reports sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: node_modules + targetPath: $(Build.SourcesDirectory)/node_modules artifactName: node-modules-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Node Modules sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: .build/logs + targetPath: $(Build.SourcesDirectory)/.build/logs artifactName: logs-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Log Files sbomEnabled: false diff --git a/build/azure-pipelines/linux/product-build-linux-cli.yml b/build/azure-pipelines/linux/product-build-linux-cli.yml new file mode 100644 index 00000000000..9052a29e18e --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-cli.yml @@ -0,0 +1,139 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: + - job: LinuxCLI_${{ parameters.VSCODE_ARCH }} + displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_$(VSCODE_ARCH)_cli.tar.gz + artifactName: vscode_cli_linux_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_linux_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - script: node build/setup-npm-registry.js $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - script: | + set -e + mkdir -p $(Build.SourcesDirectory)/.build + displayName: Create .build folder for misc dependencies + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + - armv7-unknown-linux-gnueabihf + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/include + SYSROOT_ARCH: amd64 + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/include + SYSROOT_ARCH: arm64 + + - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf + VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include + SYSROOT_ARCH: armhf diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 6648c53981d..3c331192467 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -29,79 +29,73 @@ jobs: NPM_ARCH: ${{ parameters.NPM_ARCH }} VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: - outputParentDirectory: $(Build.SourcesDirectory)/.build/linux/out + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out outputs: - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - output: pipelineArtifact - targetPath: .build/crashes + targetPath: $(Build.SourcesDirectory)/.build/crashes artifactName: crash-dump-linux-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Crash Reports sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: node_modules + targetPath: $(Build.SourcesDirectory)/node_modules artifactName: node-modules-linux-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Node Modules sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: .build/logs + targetPath: $(Build.SourcesDirectory)/.build/logs artifactName: logs-linux-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Log Files sbomEnabled: false isProduction: false condition: succeededOrFailed() - output: pipelineArtifact - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_archive-unsigned + targetPath: $(Build.ArtifactStagingDirectory)/out/client/$(CLIENT_ARCHIVE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish client archive sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH) sbomPackageName: "VS Code Linux $(VSCODE_ARCH) (unsigned)" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['CLIENT_PATH'], '')) - output: pipelineArtifact - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_linux_$(VSCODE_ARCH)_archive-unsigned + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz + artifactName: vscode_server_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH) sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Server" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - output: pipelineArtifact - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_$(VSCODE_ARCH)_archive-unsigned + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz + artifactName: vscode_web_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish web server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)-web sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Web" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - output: pipelineArtifact - targetPath: $(DEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_deb-package + targetPath: $(Build.ArtifactStagingDirectory)/out/deb/$(DEB_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_deb-package displayName: Publish deb package sbomBuildDropPath: .build/linux/deb sbomPackageName: "VS Code Linux $(VSCODE_ARCH) DEB" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['DEB_PATH'], '')) - output: pipelineArtifact - targetPath: $(RPM_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_rpm-package + targetPath: $(Build.ArtifactStagingDirectory)/out/rpm/$(RPM_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_rpm-package displayName: Publish rpm package sbomBuildDropPath: .build/linux/rpm sbomPackageName: "VS Code Linux $(VSCODE_ARCH) RPM" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['RPM_PATH'], '')) - - output: pipelineArtifact - targetPath: $(SNAP_PATH) - artifactName: vscode_client_linux_$(VSCODE_ARCH)_snap - displayName: Publish snap package - sbomBuildDropPath: $(SNAP_EXTRACTED_PATH) - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) SNAP" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SNAP_PATH'], '')) - + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_SNAP, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/snap/$(SNAP_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_snap + displayName: Publish snap package + sbomBuildDropPath: $(SNAP_EXTRACTED_PATH) + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) SNAP" + sbomPackageVersion: $(Build.SourceVersion) steps: - template: ./steps/product-build-linux-compile.yml@self parameters: diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index c1689d65933..73f3a9de3ed 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -169,9 +169,10 @@ steps: - script: | set -e npm run gulp vscode-linux-$(VSCODE_ARCH)-min-ci - ARCHIVE_PATH=".build/linux/out/client/code-${{ parameters.VSCODE_QUALITY }}-$(VSCODE_ARCH)-$(date +%s).tar.gz" + ARCHIVE_PATH=".build/linux/client/code-${{ parameters.VSCODE_QUALITY }}-$(VSCODE_ARCH)-$(date +%s).tar.gz" mkdir -p $(dirname $ARCHIVE_PATH) echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + echo "##vso[task.setvariable variable=CLIENT_ARCHIVE_NAME]$(basename $ARCHIVE_PATH)" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build client @@ -203,7 +204,7 @@ steps: set -e npm run gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/out/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" + ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" UNARCHIVE_PATH="`pwd`/../vscode-server-linux-$(VSCODE_ARCH)" mkdir -p $(dirname $ARCHIVE_PATH) tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH) @@ -217,7 +218,7 @@ steps: set -e npm run gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/out/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" + ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" mkdir -p $(dirname $ARCHIVE_PATH) tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH)-web echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" @@ -264,14 +265,15 @@ steps: - script: | set -e npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" - mkdir -p .build/linux/out/deb - cp .build/linux/deb/*/deb/*.deb .build/linux/out/deb/ - file_output=$(file $(ls .build/linux/out/deb/*.deb)) + mkdir -p .build/linux/deb + cp .build/linux/deb/*/deb/*.deb .build/linux/deb/ + file_output=$(file $(ls .build/linux/deb/*.deb)) if [[ "$file_output" != *"data compression xz"* ]]; then echo "Error: unknown compression. $file_output" exit 1 fi - echo "##vso[task.setvariable variable=DEB_PATH]$(ls .build/linux/out/deb/*.deb)" + echo "##vso[task.setvariable variable=DEB_PATH]$(ls .build/linux/deb/*.deb)" + echo "##vso[task.setvariable variable=DEB_PACKAGE_NAME]$(basename $(ls .build/linux/deb/*.deb))" displayName: Build deb package - script: | @@ -294,9 +296,10 @@ steps: - script: | set -e npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" - mkdir -p .build/linux/out/rpm - cp .build/linux/rpm/*/*.rpm .build/linux/out/rpm/ - echo "##vso[task.setvariable variable=RPM_PATH]$(ls .build/linux/out/rpm/*.rpm)" + mkdir -p .build/linux/rpm + cp .build/linux/rpm/*/*.rpm .build/linux/rpm/ + echo "##vso[task.setvariable variable=RPM_PATH]$(ls .build/linux/rpm/*.rpm)" + echo "##vso[task.setvariable variable=RPM_PACKAGE_NAME]$(basename $(ls .build/linux/rpm/*.rpm))" displayName: Build rpm package - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: @@ -316,14 +319,15 @@ steps: SNAP_ROOT="$(pwd)/.build/linux/snap/$(VSCODE_ARCH)" SNAP_EXTRACTED_PATH=$(find $SNAP_ROOT -maxdepth 1 -type d -name 'code-*') - mkdir -p .build/linux/out/snap - cp $(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') .build/linux/out/snap/ + mkdir -p .build/linux/snap + cp $(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') .build/linux/snap/ # SBOM tool doesn't like recursive symlinks sudo find $SNAP_EXTRACTED_PATH -type l -delete echo "##vso[task.setvariable variable=SNAP_EXTRACTED_PATH]$SNAP_EXTRACTED_PATH" - echo "##vso[task.setvariable variable=SNAP_PATH]$(ls .build/linux/out/snap/*.snap)" + echo "##vso[task.setvariable variable=SNAP_PATH]$(ls .build/linux/snap/*.snap)" + echo "##vso[task.setvariable variable=SNAP_PACKAGE_NAME]$(basename $(ls .build/linux/snap/*.snap))" env: VSCODE_ARCH: $(VSCODE_ARCH) displayName: Build snap package @@ -370,6 +374,26 @@ steps: condition: succeededOrFailed() displayName: "✍️ Post-job: Codesign deb & rpm" - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix + - script: | + set -e + + mkdir -p $(Build.ArtifactStagingDirectory)/out/client + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/client/$(CLIENT_ARCHIVE_NAME) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/server + mv $(SERVER_PATH) $(Build.ArtifactStagingDirectory)/out/server/$(basename $(SERVER_PATH)) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/web + mv $(WEB_PATH) $(Build.ArtifactStagingDirectory)/out/web/$(basename $(WEB_PATH)) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/deb + mv $(DEB_PATH) $(Build.ArtifactStagingDirectory)/out/deb/$(DEB_PACKAGE_NAME) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/rpm + mv $(RPM_PATH) $(Build.ArtifactStagingDirectory)/out/rpm/$(RPM_PACKAGE_NAME) + + if [ -n "$SNAP_PATH" ]; then + mkdir -p $(Build.ArtifactStagingDirectory)/out/snap + mv $(SNAP_PATH) $(Build.ArtifactStagingDirectory)/out/snap/$(SNAP_PACKAGE_NAME) + fi + displayName: Move artifacts to out directory diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index a946a5b0fd5..1a43c9ad51e 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -197,102 +197,59 @@ extends: - stage: CompileCLI dependsOn: [] jobs: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - job: CLILinuxX64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }} + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: CLILinuxGnuARM - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: CLILinuxGnuAarch64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }} + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: armhf + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}: - - job: CLIAlpineX64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }} + - template: build/azure-pipelines/alpine/product-build-alpine-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}: - - job: CLIAlpineARM64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }} + - template: build/azure-pipelines/alpine/product-build-alpine-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - job: CLIMacOSX64 - pool: - name: AcesShared - os: macOS - variables: - # todo@connor4312 to diagnose build flakes - - name: MSRUSTUP_LOG - value: debug - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: CLIMacOSARM64 - pool: - name: AcesShared - os: macOS - variables: - # todo@connor4312 to diagnose build flakes - - name: MSRUSTUP_LOG - value: debug - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self parameters: VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self parameters: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} @@ -448,26 +405,15 @@ extends: - Compile - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - CompileCLI - pool: - name: 1es-ubuntu-22.04-x64 - os: linux jobs: - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - job: LinuxAlpine - variables: + - template: build/azure-pipelines/alpine/product-build-alpine.yml@self + parameters: VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: build/azure-pipelines/alpine/product-build-alpine.yml@self - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - job: LinuxAlpineArm64 - timeoutInMinutes: 120 - variables: + - template: build/azure-pipelines/alpine/product-build-alpine.yml@self + parameters: VSCODE_ARCH: arm64 - NPM_ARCH: arm64 - steps: - - template: build/azure-pipelines/alpine/product-build-alpine.yml@self - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: - stage: macOS @@ -482,120 +428,58 @@ extends: BUILDSECMON_OPT_IN: true jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: macOSElectronTest - displayName: Electron Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - job: macOSBrowserTest - displayName: Browser Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - job: macOSRemoteTest - displayName: Remote Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Electron + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Browser + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Remote - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: - - job: macOS - timeoutInMinutes: 90 - variables: + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: VSCODE_ARCH: x64 - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - - - job: macOSCLI - timeoutInMinutes: 90 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self - parameters: - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: macOSARM64 - timeoutInMinutes: 90 - variables: + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: VSCODE_ARCH: arm64 - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - - job: macOSUniversal - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self + - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true))) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self + parameters: + VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} + VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: - stage: Web dependsOn: - Compile - pool: - name: 1es-ubuntu-22.04-x64 - os: linux jobs: - - ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}: - - job: Web - variables: - VSCODE_ARCH: x64 - steps: - - template: build/azure-pipelines/web/product-build-web.yml@self + - template: build/azure-pipelines/web/product-build-web.yml@self - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: - stage: Publish dependsOn: [] - pool: - name: 1es-windows-2022-x64 - os: windows - variables: - - name: BUILDS_API_URL - value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ jobs: - - job: PublishBuild - timeoutInMinutes: 180 - displayName: Publish Build - steps: - - template: build/azure-pipelines/product-publish.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_SCHEDULEDBUILD: ${{ variables.VSCODE_SCHEDULEDBUILD }} + - template: build/azure-pipelines/product-publish.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_SCHEDULEDBUILD: ${{ variables.VSCODE_SCHEDULEDBUILD }} - ${{ if and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)) }}: - stage: ApproveRelease diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index d278585d830..aa0727a1988 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -4,102 +4,113 @@ parameters: - name: VSCODE_SCHEDULEDBUILD type: boolean -steps: - - template: ./common/checkout.yml@self +jobs: + - job: PublishBuild + displayName: Publish Build + timeoutInMinutes: 180 + pool: + name: 1es-windows-2022-x64 + os: windows + variables: + - name: BUILDS_API_URL + value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Pipeline.Workspace)/artifacts_processed_$(System.StageAttempt)/artifacts_processed_$(System.StageAttempt).txt + artifactName: artifacts_processed_$(System.StageAttempt) + displayName: Publish the artifacts processed for this stage attempt + sbomEnabled: false + isProduction: false + condition: always() + steps: + - template: ./common/checkout.yml@self - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get ESRP Secrets" - inputs: - azureSubscription: vscode-esrp - KeyVaultName: vscode-esrp - SecretsFilter: esrp-auth,esrp-sign + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get ESRP Secrets" + inputs: + azureSubscription: vscode-esrp + KeyVaultName: vscode-esrp + SecretsFilter: esrp-auth,esrp-sign - # allow-any-unicode-next-line - - pwsh: Write-Host "##vso[build.addbuildtag]🚀" - displayName: Add build tag + # allow-any-unicode-next-line + - pwsh: Write-Host "##vso[build.addbuildtag]🚀" + displayName: Add build tag - - pwsh: | - npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies + - pwsh: | + npm ci + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies - - download: current - patterns: "**/artifacts_processed_*.txt" - displayName: Download all artifacts_processed text files + - download: current + patterns: "**/artifacts_processed_*.txt" + displayName: Download all artifacts_processed text files - - task: AzureCLI@2 - displayName: Fetch secrets - inputs: - azureSubscription: vscode - scriptType: pscore - scriptLocation: inlineScript - addSpnToEnvironment: true - inlineScript: | - Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + - task: AzureCLI@2 + displayName: Fetch secrets + inputs: + azureSubscription: vscode + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 + - pwsh: | + . build/azure-pipelines/win32/exec.ps1 - if (Test-Path "$(Pipeline.Workspace)/artifacts_processed_*/artifacts_processed_*.txt") { - Write-Host "Artifacts already processed so a build must have already been created." - return - } + if (Test-Path "$(Pipeline.Workspace)/artifacts_processed_*/artifacts_processed_*.txt") { + Write-Host "Artifacts already processed so a build must have already been created." + return + } - $VERSION = node -p "require('./package.json').version" - Write-Host "Creating build with version: $VERSION" - exec { node build/azure-pipelines/common/createBuild.js $VERSION } - env: - AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" - AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" - displayName: Create build if it hasn't been created before + $VERSION = node -p "require('./package.json').version" + Write-Host "Creating build with version: $VERSION" + exec { node build/azure-pipelines/common/createBuild.js $VERSION } + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Create build if it hasn't been created before - - pwsh: | - $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens) - Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" - env: - AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" - AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" - displayName: Get publish auth tokens + - pwsh: | + $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens) + Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Get publish auth tokens - - pwsh: node build/azure-pipelines/common/publish.js - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" - RELEASE_TENANT_ID: "$(ESRP_TENANT_ID)" - RELEASE_CLIENT_ID: "$(ESRP_CLIENT_ID)" - RELEASE_AUTH_CERT: "$(esrp-auth)" - RELEASE_REQUEST_SIGNING_CERT: "$(esrp-sign)" - displayName: Process artifacts - retryCountOnTaskFailure: 3 + - pwsh: node build/azure-pipelines/common/publish.js + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" + RELEASE_TENANT_ID: "$(ESRP_TENANT_ID)" + RELEASE_CLIENT_ID: "$(ESRP_CLIENT_ID)" + RELEASE_AUTH_CERT: "$(esrp-auth)" + RELEASE_REQUEST_SIGNING_CERT: "$(esrp-sign)" + displayName: Process artifacts + retryCountOnTaskFailure: 3 - - template: common/publish-artifact.yml@self - parameters: - targetPath: $(Pipeline.Workspace)/artifacts_processed_$(System.StageAttempt)/artifacts_processed_$(System.StageAttempt).txt - artifactName: artifacts_processed_$(System.StageAttempt) - displayName: Publish the artifacts processed for this stage attempt - sbomEnabled: false - condition: always() - - - ${{ if and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(parameters.VSCODE_SCHEDULEDBUILD, true)) }}: - - script: node build/azure-pipelines/common/releaseBuild.js - env: - PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" - displayName: Release build + - ${{ if and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(parameters.VSCODE_SCHEDULEDBUILD, true)) }}: + - script: node build/azure-pipelines/common/releaseBuild.js + env: + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" + displayName: Release build diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index a372a0e8bae..b223a63f9c6 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -1,174 +1,178 @@ -steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js web $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev - displayName: Setup system services - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - script: | - set -e - npm run gulp vscode-web-min-ci - ARCHIVE_PATH=".build/web/vscode-web.tar.gz" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-web - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build - - - task: AzureCLI@2 - displayName: Fetch secrets from Azure - inputs: - azureSubscription: vscode - scriptType: pscore - scriptLocation: inlineScript - addSpnToEnvironment: true - inlineScript: | - Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-cdn - displayName: Upload to CDN - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map - displayName: Upload sourcemaps (Web Main) - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map - displayName: Upload sourcemaps (Web Internal) - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-nlsmetadata - displayName: Upload NLS Metadata - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_standalone_archive-unsigned - displayName: Publish web archive - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-web - sbomPackageName: "VS Code Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) +jobs: + - job: Web + displayName: Web + timeoutInMinutes: 30 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: x64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-web.tar.gz + artifactName: vscode_web_linux_standalone_archive-unsigned + displayName: Publish web archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-web + sbomPackageName: "VS Code Web" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js web $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev + displayName: Setup system services + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml@self + + - script: | + set -e + npm run gulp vscode-web-min-ci + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/web/vscode-web.tar.gz" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-web + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build + + - task: AzureCLI@2 + displayName: Fetch secrets from Azure + inputs: + azureSubscription: vscode + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-cdn + displayName: Upload to CDN + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map + displayName: Upload sourcemaps (Web Main) + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map + displayName: Upload sourcemaps (Web Internal) + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-nlsmetadata + displayName: Upload NLS Metadata diff --git a/build/azure-pipelines/win32/product-build-win32-ci.yml b/build/azure-pipelines/win32/product-build-win32-ci.yml index 23df0320ded..eefacfdf8e9 100644 --- a/build/azure-pipelines/win32/product-build-win32-ci.yml +++ b/build/azure-pipelines/win32/product-build-win32-ci.yml @@ -15,21 +15,21 @@ jobs: templateContext: outputs: - output: pipelineArtifact - targetPath: .build/crashes + targetPath: $(Build.SourcesDirectory)/.build/crashes artifactName: crash-dump-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Crash Reports sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: node_modules + targetPath: $(Build.SourcesDirectory)/node_modules artifactName: node-modules-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Node Modules sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: .build/logs + targetPath: $(Build.SourcesDirectory)/.build/logs artifactName: logs-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) displayName: Publish Log Files sbomEnabled: false diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml index 4bb29c968d1..2b6fe1439b9 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -8,11 +8,11 @@ jobs: - job: WindowsCLISign timeoutInMinutes: 90 templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory) + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out outputs: - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_win32_x64_cli.zip + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_x64_cli.zip artifactName: vscode_cli_win32_x64_cli displayName: Publish signed artifact with ID vscode_cli_win32_x64_cli sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_x64_cli @@ -20,7 +20,7 @@ jobs: sbomPackageVersion: $(Build.SourceVersion) - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_win32_arm64_cli.zip + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_arm64_cli.zip artifactName: vscode_cli_win32_arm64_cli displayName: Publish signed artifact with ID vscode_cli_win32_arm64_cli sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_arm64_cli diff --git a/build/azure-pipelines/win32/product-build-win32-cli.yml b/build/azure-pipelines/win32/product-build-win32-cli.yml index c4801ce3746..5dd69c3b50d 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli.yml @@ -8,7 +8,8 @@ parameters: type: string jobs: - - job: CLIWindows${{ upper(parameters.VSCODE_ARCH) }} + - job: WindowsCLI_${{ upper(parameters.VSCODE_ARCH) }} + displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) pool: name: 1es-windows-2022-x64 os: windows diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index ef2a7193644..b60e66eaae4 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -17,81 +17,70 @@ parameters: jobs: - job: Windows_${{ parameters.VSCODE_ARCH }} - displayName: Windows${{ upper(parameters.VSCODE_ARCH) }} + displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) timeoutInMinutes: 90 variables: VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: - outputParentDirectory: $(Build.SourcesDirectory)/.build/win32-$(VSCODE_ARCH) + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out outputs: - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - output: pipelineArtifact - targetPath: .build/crashes + targetPath: $(Build.SourcesDirectory)/.build/crashes artifactName: crash-dump-windows-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Crash Reports sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: node_modules + targetPath: $(Build.SourcesDirectory)/node_modules artifactName: node-modules-windows-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Node Modules sbomEnabled: false isProduction: false condition: failed() - output: pipelineArtifact - targetPath: .build/logs + targetPath: $(Build.SourcesDirectory)/.build/logs artifactName: logs-windows-$(VSCODE_ARCH)-$(System.JobAttempt) displayName: Publish Log Files sbomEnabled: false isProduction: false condition: succeededOrFailed() - output: pipelineArtifact - targetPath: .build/node-cpuprofile - artifactName: $(ARTIFACT_PREFIX)node-cpuprofile-$(VSCODE_ARCH) - displayName: Publish Codesign cpu profile - sbomEnabled: false - isProduction: false - - output: pipelineArtifact - targetPath: $(SYSTEM_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_setup + targetPath: $(Build.ArtifactStagingDirectory)/out/system-setup/VSCodeSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe + artifactName: vscode_client_win32_$(VSCODE_ARCH)_setup displayName: Publish system setup sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH) System Setup" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SYSTEM_SETUP_PATH'], '')) - output: pipelineArtifact - targetPath: $(USER_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_user-setup + targetPath: $(Build.ArtifactStagingDirectory)/out/user-setup/VSCodeUserSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe + artifactName: vscode_client_win32_$(VSCODE_ARCH)_user-setup displayName: Publish user setup sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH) User Setup" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['USER_SETUP_PATH'], '')) - output: pipelineArtifact - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_archive + targetPath: $(Build.ArtifactStagingDirectory)/out/archive/VSCode-win32-$(VSCODE_ARCH)-$(VSCODE_VERSION).zip + artifactName: vscode_client_win32_$(VSCODE_ARCH)_archive displayName: Publish archive sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH)" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['CLIENT_PATH'], '')) - output: pipelineArtifact - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_win32_$(VSCODE_ARCH)_archive + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-win32-$(VSCODE_ARCH).zip + artifactName: vscode_server_win32_$(VSCODE_ARCH)_archive displayName: Publish server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Server" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - output: pipelineArtifact - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_win32_$(VSCODE_ARCH)_archive + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-win32-$(VSCODE_ARCH)-web.zip + artifactName: vscode_web_win32_$(VSCODE_ARCH)_archive displayName: Publish web server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)-web sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Web" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) steps: - template: ./steps/product-build-win32-compile.yml@self parameters: diff --git a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml index 15d224d1729..e75581bea77 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml @@ -58,4 +58,4 @@ steps: rootFolderOrFile: $(Build.BinariesDirectory)/sign/${{ target }} includeRootFolder: false archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip + archiveFile: $(Build.ArtifactStagingDirectory)/out/$(ASSET_ID).zip diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index d4f64152d7a..072d64ec2d9 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -223,12 +223,9 @@ steps: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - mkdir -Force .build/node-cpuprofile exec { npx deemon --detach --wait -- npx zx build/azure-pipelines/win32/codesign.js } env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) - NODE_DEBUG: "net,child_process" - NODE_OPTIONS: "--report-filename=stdout --report-uncaught-exception --report-on-fatalerror --cpu-prof --cpu-prof-dir=.build/node-cpuprofile" displayName: ✍️ Codesign - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: @@ -245,9 +242,6 @@ steps: $ErrorActionPreference = "Stop" exec { npx deemon --attach -- npx zx build/azure-pipelines/win32/codesign.js } condition: succeededOrFailed() - env: - NODE_DEBUG: "net,child_process" - NODE_OPTIONS: "--report-filename=stdout --report-uncaught-exception --report-on-fatalerror --cpu-prof --cpu-prof-dir=.build/node-cpuprofile" displayName: "✍️ Post-job: Codesign" - powershell: | @@ -256,25 +250,20 @@ steps: $PackageJson = Get-Content -Raw -Path ..\VSCode-win32-$(VSCODE_ARCH)\resources\app\package.json | ConvertFrom-Json $Version = $PackageJson.version - $ClientArchivePath = ".build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH)-$Version.zip" - $ServerArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip" - $WebArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH)-web.zip" + mkdir $(Build.ArtifactStagingDirectory)\out\system-setup -Force + mv .build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup.exe $(Build.ArtifactStagingDirectory)\out\system-setup\VSCodeSetup-$(VSCODE_ARCH)-$Version.exe - $SystemSetupPath = ".build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup-$(VSCODE_ARCH)-$Version.exe" - $UserSetupPath = ".build\win32-$(VSCODE_ARCH)\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$Version.exe" + mkdir $(Build.ArtifactStagingDirectory)\out\user-setup -Force + mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $(Build.ArtifactStagingDirectory)\out\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$Version.exe - mv .build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup.exe $SystemSetupPath - mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $UserSetupPath + mkdir $(Build.ArtifactStagingDirectory)\out\archive -Force + mv .build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH)-$Version.zip $(Build.ArtifactStagingDirectory)\out\archive\VSCode-win32-$(VSCODE_ARCH)-$Version.zip - echo "##vso[task.setvariable variable=CLIENT_PATH]$ClientArchivePath" - echo "##vso[task.setvariable variable=SERVER_PATH]$ServerArchivePath" - echo "##vso[task.setvariable variable=WEB_PATH]$WebArchivePath" + mkdir $(Build.ArtifactStagingDirectory)\out\server -Force + mv .build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)\out\server\vscode-server-win32-$(VSCODE_ARCH).zip - echo "##vso[task.setvariable variable=SYSTEM_SETUP_PATH]$SystemSetupPath" - echo "##vso[task.setvariable variable=USER_SETUP_PATH]$UserSetupPath" - condition: succeededOrFailed() - displayName: Move setup packages + mkdir $(Build.ArtifactStagingDirectory)\out\web -Force + mv .build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH)-web.zip $(Build.ArtifactStagingDirectory)\out\web\vscode-server-win32-$(VSCODE_ARCH)-web.zip - - powershell: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix + echo "##vso[task.setvariable variable=VSCODE_VERSION]$Version" + displayName: Move artifacts to out directory From 2335344f9b845b8064aa5af92bfabee8bc44e3d2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 6 Oct 2025 11:27:16 +0200 Subject: [PATCH 0817/4355] no eslint suppressions, docs for mutable disposable --- src/vs/base/common/lifecycle.ts | 15 +++++++++++++++ .../inlayHints/browser/inlayHintsController.ts | 15 ++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 4142349bde5..c02a023cb00 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -568,10 +568,25 @@ export class MutableDisposable implements IDisposable { trackDisposable(this); } + /** + * Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed + */ get value(): T | undefined { return this._isDisposed ? undefined : this._value; } + /** + * Set a new disposable value. + * + * Behaviour: + * - If the MutableDisposable has been disposed, the setter is a no-op. + * - If the new value is strictly equal to the current value, the setter is a no-op. + * - Otherwise the previous value (if any) is disposed and the new value is stored. + * + * Related helpers: + * - clear() resets the value to `undefined` (and disposes the previous value). + * - clearAndLeak() returns the old value without disposing it and removes its parent. + */ set value(value: T | undefined) { if (this._isDisposed || value === this._value) { return; diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 84e142dc231..29a814e2336 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -101,15 +101,9 @@ const enum RenderMode { * Mix of CancellationTokenSource, DisposableStore and MutableDisposable */ class CancellationStore implements IDisposable { - // eslint-disable-next-line local/code-no-potentially-unsafe-disposables - private _store: DisposableStore; - private _tokenSource: CancellationTokenSource; - - constructor() { - this._store = new DisposableStore(); - this._tokenSource = new CancellationTokenSource(); - } + private readonly _store = new MutableDisposable(); + private _tokenSource = new CancellationTokenSource(); dispose() { this._store.dispose(); @@ -118,12 +112,11 @@ class CancellationStore implements IDisposable { reset() { this._tokenSource.dispose(true); - this._store.dispose(); this._tokenSource = new CancellationTokenSource(); - this._store = new DisposableStore(); + this._store.value = new DisposableStore(); return { - store: this._store, + store: this._store.value, token: this._tokenSource.token }; } From 8b72f26d634d6d732b1f38fd41a29bb3b093c2bb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 6 Oct 2025 11:28:13 +0200 Subject: [PATCH 0818/4355] update code ownership (#269988) --- .github/CODEOWNERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9ab481c8538..17d9f161dca 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -67,12 +67,17 @@ src/server-main.ts @bpasero @deepak1556 src/vs/code/** @bpasero @deepak1556 # Workbench Services +src/vs/workbench/services/accounts/** @sandy081 src/vs/workbench/services/activity/** @bpasero @benibenj src/vs/workbench/services/auxiliaryWindow/** @bpasero @benibenj src/vs/workbench/services/chat/** @bpasero @sandy081 +src/vs/workbench/services/configuration/** @sandy081 src/vs/workbench/services/contextmenu/** @bpasero @benibenj src/vs/workbench/services/dialogs/** @alexr00 @bpasero src/vs/workbench/services/editor/** @bpasero @benibenj +src/vs/workbench/services/extensionManagement/** @sandy081 +src/vs/workbench/services/extensionRecommendations/** @sandy081 +src/vs/workbench/services/extensions/** @alexdima @sandy081 src/vs/workbench/services/environment/** @bpasero @deepak1556 src/vs/workbench/services/files/** @bpasero @deepak1556 src/vs/workbench/services/filesConfiguration/** @bpasero @benibenj @@ -81,8 +86,14 @@ src/vs/workbench/services/host/** @bpasero @deepak1556 src/vs/workbench/services/label/** @bpasero @benibenj src/vs/workbench/services/layout/** @bpasero @benibenj src/vs/workbench/services/lifecycle/** @bpasero @deepak1556 +src/vs/workbench/services/log/** @sandy081 +src/vs/workbench/services/mcp/** @sandy081 @connor4312 src/vs/workbench/services/notification/** @bpasero @benibenj +src/vs/workbench/services/output/** @sandy081 +src/vs/workbench/services/panecomposite/** @sandy081 @benibenj src/vs/workbench/services/path/** @bpasero @benibenj +src/vs/workbench/services/policies/** @sandy081 @joshspicer +src/vs/workbench/services/preferences/** @sandy081 @rzhao271 @ulugbekna src/vs/workbench/services/progress/** @bpasero @benibenj src/vs/workbench/services/storage/** @bpasero @deepak1556 src/vs/workbench/services/textfile/** @bpasero @benibenj From f72b6326b1df4fdc9c5489c9e75ba87d88bfa501 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:09:19 +0200 Subject: [PATCH 0819/4355] Engineering - use correct template for macOS CI job (#269993) --- build/azure-pipelines/product-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 1a43c9ad51e..e9c8f74e659 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -432,11 +432,11 @@ extends: parameters: VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Electron - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Browser - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_TEST_SUITE: Remote From 53bbb5267e77ab2234eeaf28fc224940680e0f77 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:47:04 +0200 Subject: [PATCH 0820/4355] Engineering - add missing check for macOS CLI job (#269996) --- .../darwin/product-build-darwin-cli.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml index ed78c39fd9e..35a9b3566ce 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -20,12 +20,13 @@ jobs: VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: outputs: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip - artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli - displayName: Publish unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact - sbomEnabled: false - isProduction: false + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact + sbomEnabled: false + isProduction: false steps: - template: ../common/checkout.yml@self From 49de261c1a74fd3c05eaf03fb12d897b82e6da00 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 6 Oct 2025 15:03:07 +0200 Subject: [PATCH 0821/4355] fix a potential stale inline chat window cause (#269999) re https://github.com/microsoft/vscode/issues/265579 --- .../browser/inlineChatController.ts | 2 +- .../test/browser/inlineChatController.test.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 5cb7532969e..dab7d18a8d1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1042,7 +1042,7 @@ export class InlineChatController1 implements IEditorContribution { assertType(this._session); assertType(this._strategy); - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._session.textModelN.uri, edits); + const moreMinimalEdits = await raceCancellation(this._editorWorkerService.computeMoreMinimalEdits(this._session.textModelN.uri, edits), opts?.token || CancellationToken.None); this._log('edits from PROVIDER and after making them MORE MINIMAL', this._session.agent.extensionId, edits, moreMinimalEdits); if (moreMinimalEdits?.length === 0) { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 887a70f3031..a721c38bc66 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -508,6 +508,43 @@ suite('InlineChatController', function () { }); + test('cancel while applying streamed edits should close the widget', async function () { + + const workerService = instaService.get(IEditorWorkerService) as TestWorkerService; + const originalCompute = workerService.computeMoreMinimalEdits.bind(workerService); + const editsBarrier = new DeferredPromise(); + let computeInvoked = false; + workerService.computeMoreMinimalEdits = async (resource, edits, pretty) => { + computeInvoked = true; + await editsBarrier.p; + return originalCompute(resource, edits, pretty); + }; + store.add({ dispose: () => { workerService.computeMoreMinimalEdits = originalCompute; } }); + + const progressBarrier = new DeferredPromise(); + store.add(chatAgentService.registerDynamicAgent({ + id: 'pendingEditsAgent', + ...agentData + }, { + async invoke(request, progress, history, token) { + progress([{ kind: 'textEdit', uri: model.uri, edits: [{ range: new Range(1, 1, 1, 1), text: request.message }] }]); + await progressBarrier.p; + return {}; + }, + })); + + ctrl = instaService.createInstance(TestController, editor); + const states = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + const run = ctrl.run({ message: 'BLOCK', autoSend: true }); + assert.strictEqual(await states, undefined); + assert.ok(computeInvoked); + + ctrl.cancelSession(); + assert.strictEqual(await states, undefined); + + await run; + }); + test('re-run should discard pending edits', async function () { let count = 1; From 5507e6f11f4b1f0a05822be2c732211f1ab83a72 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:32:11 +0200 Subject: [PATCH 0822/4355] Have TreeView checkboxes align with rest of custom checkboxes (#270014) Fixes #252638 --- .../workbench/browser/parts/views/checkbox.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/checkbox.ts b/src/vs/workbench/browser/parts/views/checkbox.ts index 116e33342a9..b6ef8197925 100644 --- a/src/vs/workbench/browser/parts/views/checkbox.ts +++ b/src/vs/workbench/browser/parts/views/checkbox.ts @@ -6,13 +6,12 @@ import * as DOM from '../../../../base/browser/dom.js'; import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; -import { Toggle } from '../../../../base/browser/ui/toggle/toggle.js'; -import { Codicon } from '../../../../base/common/codicons.js'; +import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { localize } from '../../../../nls.js'; import type { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { ITreeItem, ITreeItemCheckboxState } from '../../../common/views.js'; export class CheckboxStateHandler extends Disposable { @@ -25,7 +24,7 @@ export class CheckboxStateHandler extends Disposable { } export class TreeItemCheckbox extends Disposable { - private toggle: Toggle | undefined; + private toggle: Checkbox | undefined; private readonly checkboxContainer: HTMLDivElement; private hover: IManagedHover | undefined; @@ -48,19 +47,13 @@ export class TreeItemCheckbox extends Disposable { } else { this.toggle.checked = node.checkbox.isChecked; - this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined); } } } private createCheckbox(node: ITreeItem) { if (node.checkbox) { - this.toggle = new Toggle({ - isChecked: node.checkbox.isChecked, - title: '', - icon: node.checkbox.isChecked ? Codicon.check : undefined, - ...defaultToggleStyles - }); + this.toggle = new Checkbox('', node.checkbox.isChecked, { ...defaultCheckboxStyles, size: 15 }); this.setHover(node.checkbox); this.setAccessibilityInformation(node.checkbox); this.toggle.domNode.classList.add(TreeItemCheckbox.checkboxClass); @@ -93,7 +86,6 @@ export class TreeItemCheckbox extends Disposable { private setCheckbox(node: ITreeItem) { if (this.toggle && node.checkbox) { node.checkbox.isChecked = this.toggle.checked; - this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined); this.setHover(node.checkbox); this.setAccessibilityInformation(node.checkbox); @@ -108,7 +100,7 @@ export class TreeItemCheckbox extends Disposable { private setAccessibilityInformation(checkbox: ITreeItemCheckboxState) { if (this.toggle && checkbox.accessibilityInformation) { - this.toggle.domNode.ariaLabel = checkbox.accessibilityInformation.label; + this.toggle.setTitle(checkbox.accessibilityInformation.label); if (checkbox.accessibilityInformation.role) { this.toggle.domNode.role = checkbox.accessibilityInformation.role; } From 04f446cd78b63b63132fce3c57fde2c52c2620cd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Oct 2025 15:36:01 +0200 Subject: [PATCH 0823/4355] debt - introduce `CODENOTIFY` (#270020) --- .github/CODENOTIFY | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/CODENOTIFY diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY new file mode 100644 index 00000000000..5fcbcb0e346 --- /dev/null +++ b/.github/CODENOTIFY @@ -0,0 +1,96 @@ +# Base Utilities +src/vs/base/common/extpath.ts @bpasero +src/vs/base/common/fuzzyScorer.ts @bpasero +src/vs/base/common/glob.ts @bpasero +src/vs/base/common/path.ts @bpasero +src/vs/base/common/stream.ts @bpasero +src/vs/base/browser/domSanitize.ts @mjbvz +src/vs/base/node/pfs.ts @bpasero +src/vs/base/node/unc.ts @bpasero +src/vs/base/parts/contextmenu/** @bpasero +src/vs/base/parts/ipc/** @bpasero +src/vs/base/parts/sandbox/** @bpasero +src/vs/base/parts/storage/** @bpasero + +# Base Widgets +src/vs/base/browser/ui/grid/** @joaomoreno @benibenj +src/vs/base/browser/ui/list/** @joaomoreno @benibenj +src/vs/base/browser/ui/sash/** @joaomoreno @benibenj +src/vs/base/browser/ui/splitview/** @joaomoreno @benibenj +src/vs/base/browser/ui/table/** @joaomoreno @benibenj +src/vs/base/browser/ui/tree/** @joaomoreno @benibenj + +# Platform +src/vs/platform/auxiliaryWindow/** @bpasero +src/vs/platform/backup/** @bpasero +src/vs/platform/dialogs/** @bpasero +src/vs/platform/editor/** @bpasero +src/vs/platform/environment/** @bpasero +src/vs/platform/files/** @bpasero +src/vs/platform/ipc/** @bpasero +src/vs/platform/launch/** @bpasero +src/vs/platform/lifecycle/** @bpasero +src/vs/platform/menubar/** @bpasero +src/vs/platform/native/** @bpasero +src/vs/platform/sharedProcess/** @bpasero +src/vs/platform/state/** @bpasero +src/vs/platform/storage/** @bpasero +src/vs/platform/utilityProcess/** @bpasero +src/vs/platform/window/** @bpasero +src/vs/platform/windows/** @bpasero +src/vs/platform/workspace/** @bpasero +src/vs/platform/workspaces/** @bpasero + +# Bootstrap +src/bootstrap-cli.ts @bpasero +src/bootstrap-esm.ts @bpasero +src/bootstrap-fork.ts @bpasero +src/bootstrap-import.ts @bpasero +src/bootstrap-meta.ts @bpasero +src/bootstrap-node.ts @bpasero +src/bootstrap-server.ts @bpasero +src/cli.ts @bpasero +src/main.ts @bpasero +src/server-cli.ts @bpasero +src/server-main.ts @bpasero + +# Electron Main +src/vs/code/** @bpasero @deepak1556 + +# Workbench Services +src/vs/workbench/services/activity/** @bpasero +src/vs/workbench/services/auxiliaryWindow/** @bpasero +src/vs/workbench/services/chat/** @bpasero +src/vs/workbench/services/contextmenu/** @bpasero +src/vs/workbench/services/dialogs/** @alexr00 @bpasero +src/vs/workbench/services/editor/** @bpasero +src/vs/workbench/services/environment/** @bpasero +src/vs/workbench/services/files/** @bpasero +src/vs/workbench/services/filesConfiguration/** @bpasero +src/vs/workbench/services/history/** @bpasero +src/vs/workbench/services/host/** @bpasero +src/vs/workbench/services/label/** @bpasero +src/vs/workbench/services/layout/** @bpasero +src/vs/workbench/services/lifecycle/** @bpasero +src/vs/workbench/services/notification/** @bpasero +src/vs/workbench/services/path/** @bpasero +src/vs/workbench/services/progress/** @bpasero +src/vs/workbench/services/storage/** @bpasero +src/vs/workbench/services/textfile/** @bpasero +src/vs/workbench/services/textmodelResolver/** @bpasero +src/vs/workbench/services/untitled/** @bpasero +src/vs/workbench/services/utilityProcess/** @bpasero +src/vs/workbench/services/views/** @sandy081 @benibenj @bpasero +src/vs/workbench/services/workingCopy/** @bpasero +src/vs/workbench/services/workspaces/** @bpasero + +# Workbench Core +src/vs/workbench/common/** @bpasero +src/vs/workbench/browser/** @bpasero +src/vs/workbench/electron-browser/** @bpasero + +# Workbench Contributions +src/vs/workbench/contrib/files/** @bpasero +src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero +src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno From f07e678c829330f4c5264bbf6e5c6dbad86b577b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Oct 2025 16:02:15 +0200 Subject: [PATCH 0824/4355] debt - reduce `CODEOWNERS` again (#270024) --- .github/CODEOWNERS | 103 --------------------------------------------- 1 file changed, 103 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 17d9f161dca..dd38717a038 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,109 +10,6 @@ .github/workflows/pr.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 .github/workflows/telemetry.yml @lramos15 @lszomoru @joaomoreno -# Base Utilities -src/vs/base/common/extpath.ts @bpasero @deepak1556 -src/vs/base/common/glob.ts @bpasero @deepak1556 -src/vs/base/common/path.ts @bpasero @deepak1556 -src/vs/base/common/stream.ts @bpasero @deepak1556 -src/vs/base/browser/domSanitize.ts @mjbvz -src/vs/base/node/pfs.ts @bpasero @deepak1556 -src/vs/base/parts/contextmenu/** @bpasero @deepak1556 -src/vs/base/parts/ipc/** @bpasero @deepak1556 -src/vs/base/parts/sandbox/** @bpasero @deepak1556 -src/vs/base/parts/storage/** @bpasero @deepak1556 - -# Base Widgets -src/vs/base/browser/ui/grid/** @joaomoreno @benibenj -src/vs/base/browser/ui/list/** @joaomoreno @benibenj -src/vs/base/browser/ui/sash/** @joaomoreno @benibenj -src/vs/base/browser/ui/splitview/** @joaomoreno @benibenj -src/vs/base/browser/ui/table/** @joaomoreno @benibenj -src/vs/base/browser/ui/tree/** @joaomoreno @benibenj - -# Platform -src/vs/platform/auxiliaryWindow/** @bpasero @benibenj -src/vs/platform/backup/** @bpasero @Tyriar -src/vs/platform/editor/** @bpasero @benibenj -src/vs/platform/environment/** @bpasero @deepak1556 -src/vs/platform/files/** @bpasero @deepak1556 -src/vs/platform/ipc/** @bpasero @deepak1556 -src/vs/platform/launch/** @bpasero @deepak1556 -src/vs/platform/lifecycle/** @bpasero @deepak1556 -src/vs/platform/menubar/** @bpasero @deepak1556 -src/vs/platform/native/** @bpasero @deepak1556 -src/vs/platform/sharedProcess/** @bpasero @deepak1556 -src/vs/platform/state/** @bpasero @deepak1556 -src/vs/platform/storage/** @bpasero @deepak1556 -src/vs/platform/utilityProcess/** @bpasero @deepak1556 -src/vs/platform/window/** @bpasero @deepak1556 -src/vs/platform/windows/** @bpasero @deepak1556 -src/vs/platform/workspace/** @bpasero @sandy081 -src/vs/platform/workspaces/** @bpasero @sandy081 - -# Bootstrap -src/bootstrap-cli.ts @bpasero @deepak1556 -src/bootstrap-esm.ts @bpasero @deepak1556 -src/bootstrap-fork.ts @bpasero @deepak1556 -src/bootstrap-import.ts @bpasero @deepak1556 -src/bootstrap-meta.ts @bpasero @deepak1556 -src/bootstrap-node.ts @bpasero @deepak1556 -src/bootstrap-server.ts @bpasero @deepak1556 -src/cli.ts @bpasero @deepak1556 -src/main.ts @bpasero @deepak1556 -src/server-cli.ts @bpasero @deepak1556 -src/server-main.ts @bpasero @deepak1556 - -# Electron Main -src/vs/code/** @bpasero @deepak1556 - -# Workbench Services -src/vs/workbench/services/accounts/** @sandy081 -src/vs/workbench/services/activity/** @bpasero @benibenj -src/vs/workbench/services/auxiliaryWindow/** @bpasero @benibenj -src/vs/workbench/services/chat/** @bpasero @sandy081 -src/vs/workbench/services/configuration/** @sandy081 -src/vs/workbench/services/contextmenu/** @bpasero @benibenj -src/vs/workbench/services/dialogs/** @alexr00 @bpasero -src/vs/workbench/services/editor/** @bpasero @benibenj -src/vs/workbench/services/extensionManagement/** @sandy081 -src/vs/workbench/services/extensionRecommendations/** @sandy081 -src/vs/workbench/services/extensions/** @alexdima @sandy081 -src/vs/workbench/services/environment/** @bpasero @deepak1556 -src/vs/workbench/services/files/** @bpasero @deepak1556 -src/vs/workbench/services/filesConfiguration/** @bpasero @benibenj -src/vs/workbench/services/history/** @bpasero @benibenj -src/vs/workbench/services/host/** @bpasero @deepak1556 -src/vs/workbench/services/label/** @bpasero @benibenj -src/vs/workbench/services/layout/** @bpasero @benibenj -src/vs/workbench/services/lifecycle/** @bpasero @deepak1556 -src/vs/workbench/services/log/** @sandy081 -src/vs/workbench/services/mcp/** @sandy081 @connor4312 -src/vs/workbench/services/notification/** @bpasero @benibenj -src/vs/workbench/services/output/** @sandy081 -src/vs/workbench/services/panecomposite/** @sandy081 @benibenj -src/vs/workbench/services/path/** @bpasero @benibenj -src/vs/workbench/services/policies/** @sandy081 @joshspicer -src/vs/workbench/services/preferences/** @sandy081 @rzhao271 @ulugbekna -src/vs/workbench/services/progress/** @bpasero @benibenj -src/vs/workbench/services/storage/** @bpasero @deepak1556 -src/vs/workbench/services/textfile/** @bpasero @benibenj -src/vs/workbench/services/textmodelResolver/** @bpasero @jrieken -src/vs/workbench/services/untitled/** @bpasero @benibenj -src/vs/workbench/services/utilityProcess/** @bpasero @deepak1556 -src/vs/workbench/services/views/** @sandy081 @benibenj @bpasero -src/vs/workbench/services/workingCopy/** @bpasero @jrieken -src/vs/workbench/services/workspaces/** @bpasero @sandy081 - -# Workbench Core -src/vs/workbench/common/** @bpasero @benibenj @sandy081 -src/vs/workbench/browser/** @bpasero @benibenj @sandy081 -src/vs/workbench/electron-browser/** @bpasero @benibenj @sandy081 - -# Workbench Contributions -src/vs/workbench/contrib/files/** @lramos15 @bpasero -src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno - # VS Code API # Ensure the API team is aware of changes to the vscode-dts file # this is only about the final API, not about proposed API changes From 7e8c7bef6100ae086baf9acb142bfcf584e782b1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Oct 2025 17:19:56 +0200 Subject: [PATCH 0825/4355] debt - reduce explicit `any` usage (#269814) --- .github/prompts/no-any.prompt.md | 10 ++ eslint.config.js | 66 +++++---- src/bootstrap-fork.ts | 4 +- src/main.ts | 2 +- src/server-main.ts | 2 +- src/vs/base/common/extpath.ts | 8 +- src/vs/base/common/glob.ts | 2 +- src/vs/base/node/pfs.ts | 20 +-- src/vs/base/node/unc.ts | 11 +- src/vs/base/parts/ipc/common/ipc.ts | 4 +- src/vs/base/parts/ipc/node/ipc.cp.ts | 14 +- src/vs/base/parts/ipc/node/ipc.mp.ts | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 2 +- .../base/parts/sandbox/node/electronTypes.ts | 6 +- src/vs/base/parts/storage/common/storage.ts | 2 +- src/vs/base/parts/storage/node/storage.ts | 4 +- .../electron-browser/workbench/workbench.ts | 2 +- src/vs/code/node/cli.ts | 2 +- src/vs/code/node/cliProcessMain.ts | 2 +- src/vs/platform/backup/common/backup.ts | 4 +- .../backup/electron-main/backupMainService.ts | 4 +- src/vs/platform/backup/node/backup.ts | 4 +- .../electron-main/backupMainService.test.ts | 2 +- .../dialogs/test/common/testDialogService.ts | 4 +- .../environment/common/environmentService.ts | 2 +- src/vs/platform/environment/node/argv.ts | 37 ++--- src/vs/platform/environment/node/stdin.ts | 2 +- .../files/browser/htmlFileSystemProvider.ts | 2 +- .../browser/indexedDBFileSystemProvider.ts | 72 ++++------ .../files/browser/webFileSystemAccess.ts | 15 +- .../files/node/diskFileSystemProvider.ts | 10 +- .../node/diskFileSystemProviderServer.ts | 40 +++--- .../node/watcher/nodejs/nodejsWatcher.ts | 2 +- .../platform/ipc/electron-browser/services.ts | 1 + .../launch/electron-main/launchMainService.ts | 2 +- .../platform/menubar/electron-main/menubar.ts | 32 ++--- .../native/common/nativeHostService.ts | 2 +- .../electron-main/sharedProcess.ts | 11 +- src/vs/platform/storage/common/storageIpc.ts | 4 - .../storage/electron-main/storageIpc.ts | 3 + .../storage/electron-main/storageMain.ts | 8 -- .../electron-main/utilityProcess.ts | 4 +- src/vs/platform/window/common/window.ts | 4 +- .../platform/window/electron-main/window.ts | 4 +- .../windows/electron-main/windowImpl.ts | 6 +- .../platform/windows/electron-main/windows.ts | 6 +- .../electron-main/windowsMainService.ts | 15 +- .../electron-main/windowsStateHandler.ts | 2 +- .../test/electron-main/windowsFinder.test.ts | 8 +- .../platform/workspaces/common/workspaces.ts | 18 ++- .../api/node/extensionHostProcess.ts | 2 +- .../browser/actions/developerActions.ts | 2 +- .../browser/actions/windowActions.ts | 6 +- .../browser/actions/workspaceCommands.ts | 4 +- src/vs/workbench/browser/composite.ts | 2 +- src/vs/workbench/browser/dnd.ts | 2 +- src/vs/workbench/browser/panecomposite.ts | 2 +- src/vs/workbench/browser/part.ts | 4 +- .../parts/auxiliarybar/auxiliaryBarPart.ts | 4 +- .../workbench/browser/parts/compositeBar.ts | 2 +- .../workbench/browser/parts/compositePart.ts | 2 +- .../browser/parts/editor/editorActions.ts | 2 +- .../browser/parts/editor/editorCommands.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 3 +- .../browser/parts/editor/editorPane.ts | 9 +- .../browser/parts/editor/editorPart.ts | 9 +- .../browser/parts/editor/editorParts.ts | 8 +- .../browser/parts/editor/editorStatus.ts | 4 +- .../parts/editor/editorWithViewState.ts | 2 +- .../browser/parts/editor/editorsObserver.ts | 2 +- .../notifications/media/notificationsList.css | 1 - .../notifications/notificationsViewer.ts | 4 +- .../browser/parts/paneCompositePart.ts | 2 +- .../browser/parts/panel/panelPart.ts | 4 +- .../browser/parts/sidebar/sidebarPart.ts | 4 +- .../browser/parts/titlebar/titlebarPart.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 6 +- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 6 +- src/vs/workbench/common/component.ts | 10 +- src/vs/workbench/common/configuration.ts | 13 +- .../common/editor/editorGroupModel.ts | 4 +- .../common/editor/resourceEditorInput.ts | 2 +- src/vs/workbench/common/memento.ts | 26 ++-- src/vs/workbench/common/notifications.ts | 4 +- src/vs/workbench/common/views.ts | 4 +- .../contrib/chat/browser/chatEditor.ts | 4 +- .../contrib/chat/browser/chatStatus.ts | 2 +- .../contrib/chat/browser/chatViewPane.ts | 8 +- .../chat/common/chatTodoListService.ts | 2 +- .../chat/common/chatWidgetHistoryService.ts | 8 +- .../contrib/comments/browser/commentsView.ts | 36 +++-- .../common/contributedCustomEditors.ts | 6 +- .../extensions/browser/extensionsViewlet.ts | 9 +- .../common/contributedOpeners.ts | 8 +- .../contrib/files/browser/explorerService.ts | 4 +- .../contrib/files/browser/explorerViewlet.ts | 2 +- .../contrib/files/browser/fileActions.ts | 12 +- .../contrib/files/browser/fileImportExport.ts | 2 +- .../workbench/contrib/files/browser/files.ts | 2 +- .../files/browser/views/explorerView.ts | 12 +- .../files/browser/views/explorerViewer.ts | 19 +-- .../files/browser/views/openEditorsView.ts | 2 +- .../files/common/explorerFileNestingTrie.ts | 4 - .../contrib/markers/browser/markersView.ts | 56 +++++--- .../gettingStarted/notebookGettingStarted.ts | 9 +- .../services/notebookKeymapServiceImpl.ts | 10 +- .../browser/services/notebookServiceImpl.ts | 24 +++- .../contrib/output/browser/outputView.ts | 48 ++++--- .../preferences/browser/keybindingsEditor.ts | 12 +- .../contrib/search/browser/searchView.ts | 132 +++++++++++------- .../searchEditor/browser/searchEditorInput.ts | 4 +- .../contrib/webview/browser/webview.ts | 6 +- .../webviewView/browser/webviewViewPane.ts | 10 +- .../browser/gettingStartedService.ts | 2 +- .../electron-browser/actions/windowActions.ts | 10 +- .../parts/titlebar/titlebarPart.ts | 8 +- .../assignment/common/assignmentService.ts | 10 +- .../electron-browser/contextmenuService.ts | 4 +- .../browser/abstractFileDialogService.ts | 2 +- .../dialogs/browser/simpleFileDialog.ts | 6 +- .../editor/browser/editorResolverService.ts | 2 +- .../environment/browser/environmentService.ts | 2 +- .../host/browser/browserHostService.ts | 2 +- .../electron-browser/nativeHostService.ts | 2 +- .../services/label/common/labelService.ts | 2 +- .../progress/browser/progressService.ts | 2 +- .../services/views/browser/viewsService.ts | 9 +- .../common/fileWorkingCopyManager.ts | 2 +- .../common/storedFileWorkingCopy.ts | 2 +- .../common/workingCopyBackupService.ts | 4 - .../abstractWorkspaceEditingService.ts | 4 +- .../common/workspaceIdentityService.ts | 11 +- .../workspaces/common/workspaceTrust.ts | 11 +- .../electron-browser/workspacesService.ts | 2 +- .../test/browser/workbenchTestServices.ts | 4 +- src/vs/workbench/test/common/memento.test.ts | 10 +- 137 files changed, 645 insertions(+), 583 deletions(-) create mode 100644 .github/prompts/no-any.prompt.md diff --git a/.github/prompts/no-any.prompt.md b/.github/prompts/no-any.prompt.md new file mode 100644 index 00000000000..8abaee77660 --- /dev/null +++ b/.github/prompts/no-any.prompt.md @@ -0,0 +1,10 @@ +--- +mode: agent +description: 'Remove any usage of the any type in TypeScript files' +--- + +I am trying to minimize the usage of `any` types in our TypeScript codebase. +Find usages of the TypeScript `any` type in this file and replace it with the right type based on usages in the file. + +You are NOT allowed to disable ESLint rules or add `// @ts-ignore` comments to the code. +You are NOT allowed to add more `any` types to the code. diff --git a/eslint.config.js b/eslint.config.js index 1caf79fa44a..7790d8f5464 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -187,6 +187,7 @@ export default tseslint.config( 'src/vs/base/browser/ui/scrollbar/**', 'src/vs/base/browser/ui/widget.ts', 'src/vs/base/common/extpath.ts', + 'src/vs/base/common/fuzzyScorer.ts', 'src/vs/base/common/glob.ts', 'src/vs/base/common/path.ts', 'src/vs/base/common/stream.ts', @@ -201,30 +202,32 @@ export default tseslint.config( 'src/vs/base/common/uriTransformer.ts', 'src/vs/base/common/worker/webWorker.ts', 'src/vs/base/node/pfs.ts', + 'src/vs/base/node/unc.ts', 'src/vs/base/parts/contextmenu/**', 'src/vs/editor/browser/**', 'src/vs/editor/common/**', // 'src/vs/base/parts/ipc/**', - // 'src/vs/base/parts/sandbox/**', + 'src/vs/base/parts/sandbox/**', 'src/vs/base/parts/storage/**', 'src/vs/platform/auxiliaryWindow/**', - // 'src/vs/platform/backup/**', - // 'src/vs/platform/editor/**', - // 'src/vs/platform/environment/**', - // 'src/vs/platform/files/**', - // 'src/vs/platform/ipc/**', - // 'src/vs/platform/launch/**', - // 'src/vs/platform/lifecycle/**', - // 'src/vs/platform/menubar/**', - // 'src/vs/platform/native/**', - // 'src/vs/platform/sharedProcess/**', - // 'src/vs/platform/state/**', - // 'src/vs/platform/storage/**', - // 'src/vs/platform/utilityProcess/**', - // 'src/vs/platform/window/**', - // 'src/vs/platform/windows/**', - // 'src/vs/platform/workspace/**', - // 'src/vs/platform/workspaces/**', + 'src/vs/platform/backup/**', + 'src/vs/platform/editor/**', + 'src/vs/platform/environment/**', + 'src/vs/platform/dialogs/**', + 'src/vs/platform/files/**', + 'src/vs/platform/ipc/**', + 'src/vs/platform/launch/**', + 'src/vs/platform/lifecycle/**', + 'src/vs/platform/menubar/**', + 'src/vs/platform/native/**', + 'src/vs/platform/sharedProcess/**', + 'src/vs/platform/state/**', + 'src/vs/platform/storage/**', + 'src/vs/platform/utilityProcess/**', + 'src/vs/platform/window/**', + 'src/vs/platform/windows/**', + 'src/vs/platform/workspace/**', + 'src/vs/platform/workspaces/**', 'src/bootstrap-cli.ts', 'src/bootstrap-esm.ts', 'src/bootstrap-fork.ts', @@ -243,7 +246,7 @@ export default tseslint.config( 'src/vs/workbench/services/contextmenu/**', 'src/vs/workbench/services/dialogs/**', 'src/vs/workbench/services/editor/**', - // 'src/vs/workbench/services/environment/**', + 'src/vs/workbench/services/environment/**', 'src/vs/workbench/services/files/**', 'src/vs/workbench/services/filesConfiguration/**', 'src/vs/workbench/services/history/**', @@ -254,19 +257,22 @@ export default tseslint.config( 'src/vs/workbench/services/notification/**', 'src/vs/workbench/services/path/**', 'src/vs/workbench/services/progress/**', - // 'src/vs/workbench/services/storage/**', - // 'src/vs/workbench/services/textfile/**', - // 'src/vs/workbench/services/textmodelResolver/**', - // 'src/vs/workbench/services/untitled/**', - // 'src/vs/workbench/services/utilityProcess/**', - // 'src/vs/workbench/services/views/**', - // 'src/vs/workbench/services/workingCopy/**', - // 'src/vs/workbench/services/workspaces/**', - // 'src/vs/workbench/common/**', + 'src/vs/workbench/services/storage/**', + 'src/vs/workbench/services/textfile/**', + 'src/vs/workbench/services/textmodelResolver/**', + 'src/vs/workbench/services/untitled/**', + 'src/vs/workbench/services/utilityProcess/**', + 'src/vs/workbench/services/views/**', + 'src/vs/workbench/services/workingCopy/**', + 'src/vs/workbench/services/workspaces/**', + 'src/vs/workbench/common/**', // 'src/vs/workbench/browser/**', - // 'src/vs/workbench/electron-browser/**', - // 'src/vs/workbench/contrib/files/**', + 'src/vs/workbench/electron-browser/**', + 'src/vs/workbench/contrib/files/**', + 'src/vs/workbench/contrib/chat/browser/chatSetup.ts', + 'src/vs/workbench/contrib/chat/browser/chatStatus.ts', ], + ignores: ['**/*.test.ts', '**/*.integrationTest.ts'], languageOptions: { parser: tseslint.parser, }, diff --git a/src/bootstrap-fork.ts b/src/bootstrap-fork.ts index a92290a213d..ba08fb3c47d 100644 --- a/src/bootstrap-fork.ts +++ b/src/bootstrap-fork.ts @@ -184,9 +184,9 @@ function configureCrashReporter(): void { const crashReporterProcessType = process.env['VSCODE_CRASH_REPORTER_PROCESS_TYPE']; if (crashReporterProcessType) { try { - //@ts-ignore + //@ts-expect-error if (process['crashReporter'] && typeof process['crashReporter'].addExtraParameter === 'function' /* Electron only */) { - //@ts-ignore + //@ts-expect-error process['crashReporter'].addExtraParameter('processType', crashReporterProcessType); } } catch (error) { diff --git a/src/main.ts b/src/main.ts index 3954cd352b8..7b7e1da509e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -88,7 +88,7 @@ perf.mark('code/didStartCrashReporter'); // to ensure that no 'logs' folder is created on disk at a // location outside of the portable directory // (https://github.com/microsoft/vscode/issues/56651) -if (portable && portable.isPortable) { +if (portable.isPortable) { app.setAppLogsPath(path.join(userDataPath, 'logs')); } diff --git a/src/server-main.ts b/src/server-main.ts index 6c192ab8997..a589510cfc8 100644 --- a/src/server-main.ts +++ b/src/server-main.ts @@ -99,7 +99,7 @@ if (shouldSpawnCli) { perf.mark('code/server/firstWebSocket'); } const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer(); - // @ts-ignore + // @ts-expect-error return remoteExtensionHostAgentServer.handleUpgrade(req, socket); }); server.on('error', async (err) => { diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index bf8b0905ae6..2e48d19de23 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -359,14 +359,14 @@ export interface IPathWithLineAndColumn { export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn { const segments = rawPath.split(':'); // C:\file.txt:: - let path: string | undefined = undefined; - let line: number | undefined = undefined; - let column: number | undefined = undefined; + let path: string | undefined; + let line: number | undefined; + let column: number | undefined; for (const segment of segments) { const segmentAsNumber = Number(segment); if (!isNumber(segmentAsNumber)) { - path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) + path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) } else if (line === undefined) { line = segmentAsNumber; } else if (column === undefined) { diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 276f076788d..9914ab91e43 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -508,7 +508,7 @@ function toRegExp(pattern: string): ParsedStringPattern { return typeof path === 'string' && regExp.test(path) ? pattern : null; }; - } catch (error) { + } catch { return NULL; } } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 3a0839940f8..e13b27372fc 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -72,7 +72,7 @@ async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Prom } // Delete but do not return as promise - rimrafUnlink(moveToPath).catch(error => {/* ignore */ }); + rimrafUnlink(moveToPath).catch(() => {/* ignore */ }); } catch (error) { if (error.code !== 'ENOENT') { throw error; @@ -113,7 +113,7 @@ async function readdir(path: string, options?: { withFileTypes: true }): Promise if (error.code === 'ENOENT' && isWindows && isRootOrDriveLetter(path)) { try { return await doReaddir(`${path}.`, options); - } catch (e) { + } catch { // ignore } } @@ -268,7 +268,7 @@ export namespace SymlinkSupport { if (!lstats.isSymbolicLink()) { return { stat: lstats }; } - } catch (error) { + } catch { /* ignore - use stat() instead */ } @@ -324,7 +324,7 @@ export namespace SymlinkSupport { const { stat, symbolicLink } = await SymlinkSupport.stat(path); return stat.isFile() && symbolicLink?.dangling !== true; - } catch (error) { + } catch { // Ignore, path might not exist } @@ -346,7 +346,7 @@ export namespace SymlinkSupport { const { stat, symbolicLink } = await SymlinkSupport.stat(path); return stat.isDirectory() && symbolicLink?.dangling !== true; - } catch (error) { + } catch { // Ignore, path might not exist } @@ -542,7 +542,7 @@ async function renameWithRetry(source: string, target: string, startTime: number if (!stat.isFile()) { abortRetry = true; // if target is not a file, EPERM error may be raised and we should not attempt to retry } - } catch (error) { + } catch { // Ignore } @@ -601,7 +601,7 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr if (payload.options.preserveSymlinks) { try { return await doCopySymlink(source, target, payload); - } catch (error) { + } catch { // in any case of an error fallback to normal copy via dereferencing } } @@ -713,7 +713,7 @@ export async function realcase(path: string, token?: CancellationToken): Promise } } } - } catch (error) { + } catch { // silently ignore error } @@ -727,7 +727,7 @@ async function realpath(path: string): Promise { // drives to be resolved to their target on Windows // https://github.com/microsoft/vscode/issues/118562 return await promisify(fs.realpath)(path); - } catch (error) { + } catch { // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization // we now do a similar normalization and then try again if we can access the path with read @@ -748,7 +748,7 @@ async function realpath(path: string): Promise { export function realpathSync(path: string): string { try { return fs.realpathSync(path); - } catch (error) { + } catch { // We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization // we now do a similar normalization and then try again if we can access the path with read diff --git a/src/vs/base/node/unc.ts b/src/vs/base/node/unc.ts index 5267af07345..4076e601262 100644 --- a/src/vs/base/node/unc.ts +++ b/src/vs/base/node/unc.ts @@ -12,13 +12,12 @@ export function getUNCHostAllowlist(): string[] { return []; } -function processUNCHostAllowlist(): Set { +function processUNCHostAllowlist(): Set | undefined { // The property `process.uncHostAllowlist` is not available in official node.js // releases, only in our own builds, so we have to probe for availability - // eslint-disable-next-line local/code-no-any-casts - return (process as any).uncHostAllowlist; + return (process as unknown as { uncHostAllowlist?: Set }).uncHostAllowlist; } export function addUNCHostToAllowlist(allowedHost: string | string[]): void { @@ -91,8 +90,7 @@ export function disableUNCAccessRestrictions(): void { return; } - // eslint-disable-next-line local/code-no-any-casts - (process as any).restrictUNCAccess = false; + (process as unknown as { restrictUNCAccess?: boolean }).restrictUNCAccess = false; } export function isUNCAccessRestrictionsDisabled(): boolean { @@ -100,6 +98,5 @@ export function isUNCAccessRestrictionsDisabled(): boolean { return true; } - // eslint-disable-next-line local/code-no-any-casts - return (process as any).restrictUNCAccess === false; + return (process as unknown as { restrictUNCAccess?: boolean }).restrictUNCAccess === false; } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 0725802cf9c..5d3f878144c 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1107,7 +1107,7 @@ export namespace ProxyChannel { export function fromService(service: unknown, disposables: DisposableStore, options?: ICreateServiceChannelOptions): IServerChannel { const handler = service as { [key: string]: unknown }; - const disableMarshalling = options && options.disableMarshalling; + const disableMarshalling = options?.disableMarshalling; // Buffer any event that should be supported by // iterating over all property keys and finding them @@ -1184,7 +1184,7 @@ export namespace ProxyChannel { } export function toService(channel: IChannel, options?: ICreateProxyServiceOptions): T { - const disableMarshalling = options && options.disableMarshalling; + const disableMarshalling = options?.disableMarshalling; return new Proxy({}, { get(_target: T, propKey: PropertyKey) { diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 2c5ef506bf1..73630126fbc 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -93,7 +93,7 @@ export class Client implements IChannelClient, IDisposable { readonly onDidProcessExit = this._onDidProcessExit.event; constructor(private modulePath: string, private options: IIPCOptions) { - const timeout = options && options.timeout ? options.timeout : 60000; + const timeout = options.timeout || 60000; this.disposeDelayer = new Delayer(timeout); this.child = null; this._client = null; @@ -174,24 +174,24 @@ export class Client implements IChannelClient, IDisposable { private get client(): IPCClient { if (!this._client) { - const args = this.options && this.options.args ? this.options.args : []; + const args = this.options.args || []; const forkOpts: ForkOptions = Object.create(null); forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) }; - if (this.options && this.options.env) { + if (this.options.env) { forkOpts.env = { ...forkOpts.env, ...this.options.env }; } - if (this.options && this.options.freshExecArgv) { + if (this.options.freshExecArgv) { forkOpts.execArgv = []; } - if (this.options && typeof this.options.debug === 'number') { + if (typeof this.options.debug === 'number') { forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug]; } - if (this.options && typeof this.options.debugBrk === 'number') { + if (typeof this.options.debugBrk === 'number') { forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; } @@ -221,7 +221,7 @@ export class Client implements IChannelClient, IDisposable { }); const sender = this.options.useQueue ? createQueuedSender(this.child) : this.child; - const send = (r: VSBuffer) => this.child && this.child.connected && sender.send((r.buffer).toString('base64')); + const send = (r: VSBuffer) => this.child?.connected && sender.send((r.buffer).toString('base64')); const onMessage = onMessageEmitter.event; const protocol = { send, onMessage }; diff --git a/src/vs/base/parts/ipc/node/ipc.mp.ts b/src/vs/base/parts/ipc/node/ipc.mp.ts index df9cd52c58a..ade456373c0 100644 --- a/src/vs/base/parts/ipc/node/ipc.mp.ts +++ b/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -20,7 +20,7 @@ class Protocol implements IMessagePassingProtocol { constructor(private port: MessagePortMain) { this.onMessage = Event.fromNodeEventEmitter(this.port, 'message', (e: MessageEvent) => { if (e.data) { - return VSBuffer.wrap(e.data); + return VSBuffer.wrap(e.data as Uint8Array); } return VSBuffer.alloc(0); }); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 747f1f697ac..3d645af32dc 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -27,7 +27,7 @@ export function upgradeToISocket(req: http.IncomingMessage, socket: Socket, { skipWebSocketFrames?: boolean; disableWebSocketCompression?: boolean; }): NodeSocket | WebSocketNodeSocket | undefined { - if (req.headers['upgrade'] === undefined || req.headers['upgrade'].toLowerCase() !== 'websocket') { + if (req.headers.upgrade === undefined || req.headers.upgrade.toLowerCase() !== 'websocket') { socket.end('HTTP/1.1 400 Bad Request'); return; } diff --git a/src/vs/base/parts/sandbox/node/electronTypes.ts b/src/vs/base/parts/sandbox/node/electronTypes.ts index 37629888c19..4ed90e5b4d0 100644 --- a/src/vs/base/parts/sandbox/node/electronTypes.ts +++ b/src/vs/base/parts/sandbox/node/electronTypes.ts @@ -31,7 +31,7 @@ export interface MessagePortMain extends NodeJS.EventEmitter { * Sends a message from the port, and optionally, transfers ownership of objects to * other browsing contexts. */ - postMessage(message: any, transfer?: MessagePortMain[]): void; + postMessage(message: unknown, transfer?: MessagePortMain[]): void; /** * Starts the sending of messages queued on the port. Messages will be queued until * this method is called. @@ -40,7 +40,7 @@ export interface MessagePortMain extends NodeJS.EventEmitter { } export interface MessageEvent { - data: any; + data: unknown; ports: MessagePortMain[]; } @@ -60,7 +60,7 @@ export interface ParentPort extends NodeJS.EventEmitter { /** * Sends a message from the process to its parent. */ - postMessage(message: any): void; + postMessage(message: unknown): void; } export interface UtilityNodeJSProcess extends NodeJS.Process { diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 9a3dab6dc70..38ed1e08c93 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -349,7 +349,7 @@ export class Storage extends Disposable implements IStorage { // the DB is not healthy. try { await this.doFlush(0 /* as soon as possible */); - } catch (error) { + } catch { // Ignore } diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 3822560cbff..1c685bb324a 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -118,7 +118,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } // DELETE - if (toDelete && toDelete.size) { + if (toDelete?.size) { const keysChunks: (string[])[] = []; keysChunks.push([]); // seed with initial empty chunk @@ -291,7 +291,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { await fs.promises.unlink(path); try { await Promises.rename(this.toBackupPath(path), path, false /* no retry */); - } catch (error) { + } catch { // ignore } diff --git a/src/vs/code/electron-browser/workbench/workbench.ts b/src/vs/code/electron-browser/workbench/workbench.ts index b0fdcaeba29..7d6c8fac0c7 100644 --- a/src/vs/code/electron-browser/workbench/workbench.ts +++ b/src/vs/code/electron-browser/workbench/workbench.ts @@ -474,7 +474,7 @@ const importMapScript = document.createElement('script'); importMapScript.type = 'importmap'; importMapScript.setAttribute('nonce', '0c6a828f1297'); - // @ts-ignore + // @ts-expect-error importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; window.document.head.appendChild(importMapScript); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index b7c4fb99f8f..89c66b9b404 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -185,7 +185,7 @@ export async function main(argv: string[]): Promise { // Check for readonly status and chmod if so if we are told so let targetMode: number = 0; let restoreMode = false; - if (!!args['file-chmod']) { + if (args['file-chmod']) { targetMode = statSync(target).mode; if (!(targetMode & 0o200 /* File mode indicating writable by owner */)) { chmodSync(target, targetMode | 0o200); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 7f08f43ea04..da60ab85a19 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -247,7 +247,7 @@ class CliMain extends Disposable { const appenders: ITelemetryAppender[] = []; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.ariaKey) { + if (productService.aiConfig?.ariaKey) { appenders.push(new OneDataSystemAppender(requestService, isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } diff --git a/src/vs/platform/backup/common/backup.ts b/src/vs/platform/backup/common/backup.ts index bfae94c8c4b..4046635511f 100644 --- a/src/vs/platform/backup/common/backup.ts +++ b/src/vs/platform/backup/common/backup.ts @@ -19,9 +19,9 @@ export interface IFolderBackupInfo extends IBaseBackupInfo { } export function isFolderBackupInfo(curr: IWorkspaceBackupInfo | IFolderBackupInfo): curr is IFolderBackupInfo { - return curr && curr.hasOwnProperty('folderUri'); + return curr?.hasOwnProperty('folderUri'); } export function isWorkspaceBackupInfo(curr: IWorkspaceBackupInfo | IFolderBackupInfo): curr is IWorkspaceBackupInfo { - return curr && curr.hasOwnProperty('workspace'); + return curr?.hasOwnProperty('workspace'); } diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 55b36989ae3..51e7460fc0a 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -347,11 +347,11 @@ export class BackupMainService implements IBackupMainService { if (backupSchemaChildren.length > 0) { return true; } - } catch (error) { + } catch { // invalid folder } } - } catch (error) { + } catch { // backup path does not exist } diff --git a/src/vs/platform/backup/node/backup.ts b/src/vs/platform/backup/node/backup.ts index 05d3f4c0ced..1bc98590b58 100644 --- a/src/vs/platform/backup/node/backup.ts +++ b/src/vs/platform/backup/node/backup.ts @@ -36,7 +36,7 @@ export function deserializeWorkspaceInfos(serializedBackupWorkspaces: ISerialize } )); } - } catch (e) { + } catch { // ignore URI parsing exceptions } @@ -59,7 +59,7 @@ export function deserializeFolderInfos(serializedBackupWorkspaces: ISerializedBa } )); } - } catch (e) { + } catch { // ignore URI parsing exceptions } diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 640f2eb5830..8d71ab00fa8 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -586,7 +586,7 @@ flakySuite('BackupMainService', () => { try { await fs.promises.mkdir(path.join(folderBackupPath, Schemas.file), { recursive: true }); await fs.promises.mkdir(path.join(workspaceBackupPath, Schemas.untitled), { recursive: true }); - } catch (error) { + } catch { // ignore - folder might exist already } diff --git a/src/vs/platform/dialogs/test/common/testDialogService.ts b/src/vs/platform/dialogs/test/common/testDialogService.ts index e491fe9c6d4..18d3d9f7614 100644 --- a/src/vs/platform/dialogs/test/common/testDialogService.ts +++ b/src/vs/platform/dialogs/test/common/testDialogService.ts @@ -16,7 +16,7 @@ export class TestDialogService implements IDialogService { constructor( private defaultConfirmResult: IConfirmationResult | undefined = undefined, - private defaultPromptResult: IPromptResult | undefined = undefined + private defaultPromptResult: IPromptResult | undefined = undefined ) { } private confirmResult: IConfirmationResult | undefined = undefined; @@ -40,7 +40,7 @@ export class TestDialogService implements IDialogService { prompt(prompt: IPrompt): Promise>; async prompt(prompt: IPrompt | IPromptWithCustomCancel): Promise | IPromptResultWithCancel> { if (this.defaultPromptResult) { - return this.defaultPromptResult; + return this.defaultPromptResult as IPromptResult; } const promptButtons: IPromptBaseButton[] = [...(prompt.buttons ?? [])]; if (prompt.cancelButton && typeof prompt.cancelButton !== 'string' && typeof prompt.cancelButton !== 'boolean') { diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index e60b83f27d4..6b6c82c6d5d 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -214,7 +214,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron const result: [string, string][] = []; for (const entry of this.args.log || []) { const matches = EXTENSION_IDENTIFIER_WITH_LOG_REGEX.exec(entry); - if (matches && matches[1] && matches[2]) { + if (matches?.[1] && matches[2]) { result.push([matches[1], matches[2]]); } } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 17d0583cce3..9b0cf59752b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -261,8 +261,8 @@ export function parseArgs(args: string[], options: OptionDescriptions, err const alias: { [key: string]: string } = {}; const stringOptions: string[] = ['_']; const booleanOptions: string[] = []; - const globalOptions: OptionDescriptions = {}; - let command: Subcommand | undefined = undefined; + const globalOptions: Record | Option<'string'> | Option<'string[]'>> = {}; + let command: Subcommand> | undefined = undefined; for (const optionId in options) { const o = options[optionId]; if (o.type === 'subcommand') { @@ -291,13 +291,13 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } } if (command && firstPossibleCommand) { - const options = globalOptions; + const options: Record | Option<'string'> | Option<'string[]'> | Subcommand>> = globalOptions; for (const optionId in command.options) { options[optionId] = command.options[optionId]; } const newArgs = args.filter(a => a !== firstPossibleCommand); const reporter = errorReporter.getSubcommandReporter ? errorReporter.getSubcommandReporter(firstPossibleCommand) : undefined; - const subcommandOptions = parseArgs(newArgs, options, reporter); + const subcommandOptions = parseArgs(newArgs, options as OptionDescriptions>, reporter); // eslint-disable-next-line local/code-no-dangerous-type-assertions return { [firstPossibleCommand]: subcommandOptions, @@ -309,8 +309,8 @@ export function parseArgs(args: string[], options: OptionDescriptions, err // remove aliases to avoid confusion const parsedArgs = minimist(args, { string: stringOptions, boolean: booleanOptions, alias }); - const cleanedArgs: any = {}; - const remainingArgs: any = parsedArgs; + const cleanedArgs: Record = {}; + const remainingArgs: Record = parsedArgs; // https://github.com/microsoft/vscode/issues/58177, https://github.com/microsoft/vscode/issues/106617 cleanedArgs._ = parsedArgs._.map(arg => String(arg)).filter(arg => arg.length > 0); @@ -347,8 +347,8 @@ export function parseArgs(args: string[], options: OptionDescriptions, err val = [val]; } if (!o.allowEmptyValue) { - const sanitized = val.filter((v: string) => v.length > 0); - if (sanitized.length !== val.length) { + const sanitized = (val as string[]).filter((v: string) => v.length > 0); + if (sanitized.length !== (val as string[]).length) { errorReporter.onEmptyValue(optionId); val = sanitized.length > 0 ? sanitized : undefined; } @@ -356,7 +356,7 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } else if (o.type === 'string') { if (Array.isArray(val)) { val = val.pop(); // take the last - errorReporter.onMultipleValues(optionId, val); + errorReporter.onMultipleValues(optionId, val as string); } else if (!val && !o.allowEmptyValue) { errorReporter.onEmptyValue(optionId); val = undefined; @@ -375,10 +375,10 @@ export function parseArgs(args: string[], options: OptionDescriptions, err errorReporter.onUnknownOption(key); } - return cleanedArgs; + return cleanedArgs as T; } -function formatUsage(optionId: string, option: Option) { +function formatUsage(optionId: string, option: Option<'boolean'> | Option<'string'> | Option<'string[]'>) { let args = ''; if (option.args) { if (Array.isArray(option.args)) { @@ -394,10 +394,10 @@ function formatUsage(optionId: string, option: Option) { } // exported only for testing -export function formatOptions(options: OptionDescriptions, columns: number): string[] { +export function formatOptions(options: OptionDescriptions | Record | Option<'string'> | Option<'string[]'>>, columns: number): string[] { const usageTexts: [string, string][] = []; for (const optionId in options) { - const o = options[optionId]; + const o = options[optionId as keyof typeof options] as Option<'boolean'> | Option<'string'> | Option<'string[]'>; const usageText = formatUsage(optionId, o); usageTexts.push([usageText, o.description!]); } @@ -443,7 +443,7 @@ function wrapText(text: string, columns: number): string[] { return lines; } -export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions, capabilities?: { noPipe?: boolean; noInputFiles?: boolean; isChat?: boolean }): string { +export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions | Record | Option<'string'> | Option<'string[]'> | Subcommand>>, capabilities?: { noPipe?: boolean; noInputFiles?: boolean; isChat?: boolean }): string { const columns = (process.stdout).isTTY && (process.stdout).columns || 80; const inputFiles = capabilities?.noInputFiles ? '' : capabilities?.isChat ? ` [${localize('cliPrompt', 'prompt')}]` : ` [${localize('paths', 'paths')}...]`; const subcommand = capabilities?.isChat ? ' chat' : ''; @@ -456,18 +456,19 @@ export function buildHelpMessage(productName: string, executableName: string, ve help.push(buildStdinMessage(executableName, capabilities?.isChat)); help.push(''); } - const optionsByCategory: { [P in keyof typeof helpCategories]?: OptionDescriptions } = {}; + const optionsByCategory: { [P in keyof typeof helpCategories]?: Record | Option<'string'> | Option<'string[]'>> } = {}; const subcommands: { command: string; description: string }[] = []; for (const optionId in options) { - const o = options[optionId]; + const o = options[optionId as keyof typeof options] as Option<'boolean'> | Option<'string'> | Option<'string[]'> | Subcommand>; if (o.type === 'subcommand') { if (o.description) { subcommands.push({ command: optionId, description: o.description }); } } else if (o.description && o.cat) { - let optionsByCat = optionsByCategory[o.cat]; + const cat = o.cat as keyof typeof helpCategories; + let optionsByCat = optionsByCategory[cat]; if (!optionsByCat) { - optionsByCategory[o.cat] = optionsByCat = {}; + optionsByCategory[cat] = optionsByCat = {}; } optionsByCat[optionId] = o; } diff --git a/src/vs/platform/environment/node/stdin.ts b/src/vs/platform/environment/node/stdin.ts index 93e8e9e5c1e..707a3e81ea4 100644 --- a/src/vs/platform/environment/node/stdin.ts +++ b/src/vs/platform/environment/node/stdin.ts @@ -12,7 +12,7 @@ import { resolveTerminalEncoding } from '../../../base/node/terminalEncoding.js' export function hasStdinWithoutTty() { try { return !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304 - } catch (error) { + } catch { // Windows workaround for https://github.com/nodejs/node/issues/11656 } return false; diff --git a/src/vs/platform/files/browser/htmlFileSystemProvider.ts b/src/vs/platform/files/browser/htmlFileSystemProvider.ts index 43944112e16..747b9c37a6d 100644 --- a/src/vs/platform/files/browser/htmlFileSystemProvider.ts +++ b/src/vs/platform/files/browser/htmlFileSystemProvider.ts @@ -308,7 +308,7 @@ export class HTMLFileSystemProvider extends Disposable implements IFileSystemPro return; } - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const observer = new (globalThis as any).FileSystemObserver((records: FileSystemObserverRecord[]) => { if (disposables.isDisposed) { return; diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index 5da9d072880..1dd1bf57852 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -221,58 +221,46 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst } async readdir(resource: URI): Promise { - try { - const entry = (await this.getFiletree()).read(resource.path); - if (!entry) { - // Dirs aren't saved to disk, so empty dirs will be lost on reload. - // Thus we have two options for what happens when you try to read a dir and nothing is found: - // - Throw FileSystemProviderErrorCode.FileNotFound - // - Return [] - // We choose to return [] as creating a dir then reading it (even after reload) should not throw an error. - return []; - } - if (entry.type !== FileType.Directory) { - throw ERR_FILE_NOT_DIR; - } - else { - return [...entry.children.entries()].map(([name, node]) => [name, node.type]); - } - } catch (error) { - throw error; + const entry = (await this.getFiletree()).read(resource.path); + if (!entry) { + // Dirs aren't saved to disk, so empty dirs will be lost on reload. + // Thus we have two options for what happens when you try to read a dir and nothing is found: + // - Throw FileSystemProviderErrorCode.FileNotFound + // - Return [] + // We choose to return [] as creating a dir then reading it (even after reload) should not throw an error. + return []; + } + if (entry.type !== FileType.Directory) { + throw ERR_FILE_NOT_DIR; + } + else { + return [...entry.children.entries()].map(([name, node]) => [name, node.type]); } } async readFile(resource: URI): Promise { - try { - const result = await this.indexedDB.runInTransaction(this.store, 'readonly', objectStore => objectStore.get(resource.path)); - if (result === undefined) { - throw ERR_FILE_NOT_FOUND; - } - const buffer = result instanceof Uint8Array ? result : isString(result) ? VSBuffer.fromString(result).buffer : undefined; - if (buffer === undefined) { - throw ERR_UNKNOWN_INTERNAL(`IndexedDB entry at "${resource.path}" in unexpected format`); - } + const result = await this.indexedDB.runInTransaction(this.store, 'readonly', objectStore => objectStore.get(resource.path)); + if (result === undefined) { + throw ERR_FILE_NOT_FOUND; + } + const buffer = result instanceof Uint8Array ? result : isString(result) ? VSBuffer.fromString(result).buffer : undefined; + if (buffer === undefined) { + throw ERR_UNKNOWN_INTERNAL(`IndexedDB entry at "${resource.path}" in unexpected format`); + } - // update cache - const fileTree = await this.getFiletree(); - fileTree.add(resource.path, { type: 'file', size: buffer.byteLength }); + // update cache + const fileTree = await this.getFiletree(); + fileTree.add(resource.path, { type: 'file', size: buffer.byteLength }); - return buffer; - } catch (error) { - throw error; - } + return buffer; } async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { - try { - const existing = await this.stat(resource).catch(() => undefined); - if (existing?.type === FileType.Directory) { - throw ERR_FILE_IS_DIR; - } - await this.bulkWrite([[resource, content]]); - } catch (error) { - throw error; + const existing = await this.stat(resource).catch(() => undefined); + if (existing?.type === FileType.Directory) { + throw ERR_FILE_IS_DIR; } + await this.bulkWrite([[resource, content]]); } async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { diff --git a/src/vs/platform/files/browser/webFileSystemAccess.ts b/src/vs/platform/files/browser/webFileSystemAccess.ts index f5e90b78a00..31bdfbf8c1a 100644 --- a/src/vs/platform/files/browser/webFileSystemAccess.ts +++ b/src/vs/platform/files/browser/webFileSystemAccess.ts @@ -10,8 +10,8 @@ */ export namespace WebFileSystemAccess { - export function supported(obj: any & Window): boolean { - if (typeof obj?.showDirectoryPicker === 'function') { + export function supported(obj: typeof globalThis): boolean { + if (typeof (obj as typeof globalThis & { showDirectoryPicker?: unknown })?.showDirectoryPicker === 'function') { return true; } @@ -38,8 +38,8 @@ export namespace WebFileSystemAccess { export namespace WebFileSystemObserver { - export function supported(obj: any & Window): boolean { - return typeof obj?.FileSystemObserver === 'function'; + export function supported(obj: typeof globalThis): boolean { + return typeof (obj as typeof globalThis & { FileSystemObserver?: unknown })?.FileSystemObserver === 'function'; } } @@ -85,3 +85,10 @@ export interface FileSystemObserverRecord { */ readonly relativePathMovedFrom?: string[]; } + +export declare class FileSystemObserver { + + constructor(callback: (records: FileSystemObserverRecord[], observer: FileSystemObserver) => void); + + observe(handle: FileSystemHandle, options?: { recursive: boolean }): Promise; +} diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index b2785c11ce2..ec8924c0bc4 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -21,8 +21,7 @@ import { localize } from '../../../nls.js'; import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileChange, IFileSystemProviderWithFileRealpathCapability } from '../common/files.js'; import { readFileIntoStream } from '../common/io.js'; import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage } from '../common/watcher.js'; -import { ILogService } from '../../log/common/log.js'; -import { AbstractDiskFileSystemProvider, IDiskFileSystemProviderOptions } from '../common/diskFileSystemProvider.js'; +import { AbstractDiskFileSystemProvider } from '../common/diskFileSystemProvider.js'; import { UniversalWatcherClient } from './watcher/watcherClient.js'; import { NodeJSWatcherClient } from './watcher/nodejs/nodejsClient.js'; @@ -39,13 +38,6 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple private static TRACE_LOG_RESOURCE_LOCKS = false; // not enabled by default because very spammy - constructor( - logService: ILogService, - options?: IDiskFileSystemProviderOptions - ) { - super(logService, options); - } - //#region File Capabilities readonly onDidChangeCapabilities = Event.None; diff --git a/src/vs/platform/files/node/diskFileSystemProviderServer.ts b/src/vs/platform/files/node/diskFileSystemProviderServer.ts index 1b7219ec10c..99b735fc99d 100644 --- a/src/vs/platform/files/node/diskFileSystemProviderServer.ts +++ b/src/vs/platform/files/node/diskFileSystemProviderServer.ts @@ -33,37 +33,37 @@ export abstract class AbstractDiskFileSystemProviderChannel extends Disposabl super(); } - call(ctx: T, command: string, arg?: any): Promise { + call(ctx: T, command: string, args: unknown[]): Promise { const uriTransformer = this.getUriTransformer(ctx); switch (command) { - case 'stat': return this.stat(uriTransformer, arg[0]); - case 'realpath': return this.realpath(uriTransformer, arg[0]); - case 'readdir': return this.readdir(uriTransformer, arg[0]); - case 'open': return this.open(uriTransformer, arg[0], arg[1]); - case 'close': return this.close(arg[0]); - case 'read': return this.read(arg[0], arg[1], arg[2]); - case 'readFile': return this.readFile(uriTransformer, arg[0], arg[1]); - case 'write': return this.write(arg[0], arg[1], arg[2], arg[3], arg[4]); - case 'writeFile': return this.writeFile(uriTransformer, arg[0], arg[1], arg[2]); - case 'rename': return this.rename(uriTransformer, arg[0], arg[1], arg[2]); - case 'copy': return this.copy(uriTransformer, arg[0], arg[1], arg[2]); - case 'cloneFile': return this.cloneFile(uriTransformer, arg[0], arg[1]); - case 'mkdir': return this.mkdir(uriTransformer, arg[0]); - case 'delete': return this.delete(uriTransformer, arg[0], arg[1]); - case 'watch': return this.watch(uriTransformer, arg[0], arg[1], arg[2], arg[3]); - case 'unwatch': return this.unwatch(arg[0], arg[1]); + case 'stat': return this.stat(uriTransformer, args[0] as UriComponents) as Promise; + case 'realpath': return this.realpath(uriTransformer, args[0] as UriComponents) as Promise; + case 'readdir': return this.readdir(uriTransformer, args[0] as UriComponents) as Promise; + case 'open': return this.open(uriTransformer, args[0] as UriComponents, args[1] as IFileOpenOptions) as Promise; + case 'close': return this.close(args[0] as number) as Promise; + case 'read': return this.read(args[0] as number, args[1] as number, args[2] as number) as Promise; + case 'readFile': return this.readFile(uriTransformer, args[0] as UriComponents, args[1] as IFileAtomicReadOptions) as Promise; + case 'write': return this.write(args[0] as number, args[1] as number, args[2] as VSBuffer, args[3] as number, args[4] as number) as Promise; + case 'writeFile': return this.writeFile(uriTransformer, args[0] as UriComponents, args[1] as VSBuffer, args[2] as IFileWriteOptions) as Promise; + case 'rename': return this.rename(uriTransformer, args[0] as UriComponents, args[1] as UriComponents, args[2] as IFileOverwriteOptions) as Promise; + case 'copy': return this.copy(uriTransformer, args[0] as UriComponents, args[1] as UriComponents, args[2] as IFileOverwriteOptions) as Promise; + case 'cloneFile': return this.cloneFile(uriTransformer, args[0] as UriComponents, args[1] as UriComponents) as Promise; + case 'mkdir': return this.mkdir(uriTransformer, args[0] as UriComponents) as Promise; + case 'delete': return this.delete(uriTransformer, args[0] as UriComponents, args[1] as IFileDeleteOptions) as Promise; + case 'watch': return this.watch(uriTransformer, args[0] as string, args[1] as number, args[2] as UriComponents, args[3] as IWatchOptions) as Promise; + case 'unwatch': return this.unwatch(args[0] as string, args[1] as number) as Promise; } throw new Error(`IPC Command ${command} not found`); } - listen(ctx: T, event: string, arg: any): Event { + listen(ctx: T, event: string, args: unknown[]): Event { const uriTransformer = this.getUriTransformer(ctx); switch (event) { - case 'fileChange': return this.onFileChange(uriTransformer, arg[0]); - case 'readFileStream': return this.onReadFileStream(uriTransformer, arg[0], arg[1]); + case 'fileChange': return this.onFileChange(uriTransformer, args[0] as string) as Event; + case 'readFileStream': return this.onReadFileStream(uriTransformer, args[0] as URI, args[1] as IFileReadStreamOptions) as Event; } throw new Error(`Unknown event ${event}`); diff --git a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 47d1f2664f0..a9f642f3277 100644 --- a/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -151,7 +151,7 @@ export class NodeJSWatcher extends BaseWatcher implements INonRecursiveWatcher { requestsForCorrelation.set(path, request); } - return Array.from(mapCorrelationtoRequests.values()).map(requests => Array.from(requests.values())).flat(); + return Array.from(mapCorrelationtoRequests.values()).flatMap(requests => Array.from(requests.values())); } override async setVerboseLogging(enabled: boolean): Promise { diff --git a/src/vs/platform/ipc/electron-browser/services.ts b/src/vs/platform/ipc/electron-browser/services.ts index b03e23621d9..479c2981277 100644 --- a/src/vs/platform/ipc/electron-browser/services.ts +++ b/src/vs/platform/ipc/electron-browser/services.ts @@ -10,6 +10,7 @@ import { createDecorator, IInstantiationService, ServiceIdentifier } from '../.. import { IMainProcessService } from '../common/mainProcessService.js'; import { IRemoteService } from '../common/services.js'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ChannelClientCtor = { new(channel: IChannel, ...args: any[]): T }; type Remote = { getChannel(channelName: string): IChannel }; diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index df93721b40e..db2fd75d134 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -138,7 +138,7 @@ export class LaunchMainService implements ILaunchMainService { }; // Special case extension development - if (!!args.extensionDevelopmentPath) { + if (args.extensionDevelopmentPath) { await this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, baseConfig); } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 0667dd4d14d..772e21dcb0e 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -528,17 +528,17 @@ export class Menubar extends Disposable { menu.append(this.createMenuItem(item.label, item.id, false, item.checked)); } } else { - menu.append(this.createMenuItem(item.label, item.id, item.enabled === false ? false : true, !!item.checked)); + menu.append(this.createMenuItem(item.label, item.id, item.enabled !== false, !!item.checked)); } } else { - menu.append(this.createMenuItem(item.label, item.id, item.enabled === false ? false : true, !!item.checked)); + menu.append(this.createMenuItem(item.label, item.id, item.enabled !== false, !!item.checked)); } } }); } private setMenuById(menu: Menu, menuId: string): void { - if (this.menubarMenus && this.menubarMenus[menuId]) { + if (this.menubarMenus?.[menuId]) { this.setMenu(menu, this.menubarMenus[menuId].items); } } @@ -586,7 +586,7 @@ export class Menubar extends Disposable { return !!(event.triggeredByAccelerator || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); } - private createRoleMenuItem(label: string, commandId: string, role: any): MenuItem { + private createRoleMenuItem(label: string, commandId: string, role: 'undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'pasteAndMatchStyle' | 'delete' | 'selectAll' | 'reload' | 'forceReload' | 'toggleDevTools' | 'resetZoom' | 'zoomIn' | 'zoomOut' | 'toggleSpellChecker' | 'togglefullscreen' | 'window' | 'minimize' | 'close' | 'help' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit' | 'showSubstitutions' | 'toggleSmartQuotes' | 'toggleSmartDashes' | 'toggleTextReplacement' | 'startSpeaking' | 'stopSpeaking' | 'zoom' | 'front' | 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'shareMenu' | 'recentDocuments' | 'toggleTabBar' | 'selectNextTab' | 'selectPreviousTab' | 'showAllTabs' | 'mergeAllWindows' | 'clearRecentDocuments' | 'moveTabToNewWindow' | 'windowMenu'): MenuItem { const options: MenuItemConstructorOptions = { label: this.mnemonicLabel(label), role, @@ -674,25 +674,18 @@ export class Menubar extends Disposable { } } - private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): MenuItem; - private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): MenuItem; - private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): MenuItem { - const label = this.mnemonicLabel(arg1); - const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: MenuItem & IMenuItemWithKeybinding, win: BrowserWindow, event: KeyboardEvent) => { + private createMenuItem(labelOpt: string, commandId: string, enabledOpt?: boolean, checkedOpt?: boolean): MenuItem { + const label = this.mnemonicLabel(labelOpt); + const click = (menuItem: MenuItem & IMenuItemWithKeybinding, window: BaseWindow | undefined, event: KeyboardEvent) => { const userSettingsLabel = menuItem ? menuItem.userSettingsLabel : null; - let commandId = arg2; - if (Array.isArray(arg2)) { - commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking - } - if (userSettingsLabel && event.triggeredByAccelerator) { this.runActionInRenderer({ type: 'keybinding', userSettingsLabel }); } else { this.runActionInRenderer({ type: 'commandId', commandId }); } }; - const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0; - const checked = typeof arg4 === 'boolean' ? arg4 : false; + const enabled = typeof enabledOpt === 'boolean' ? enabledOpt : this.windowsMainService.getWindowCount() > 0; + const checked = typeof checkedOpt === 'boolean' ? checkedOpt : false; const options: MenuItemConstructorOptions = { label, @@ -705,13 +698,6 @@ export class Menubar extends Disposable { options.checked = checked; } - let commandId: string | undefined; - if (typeof arg2 === 'string') { - commandId = arg2; - } else if (Array.isArray(arg2)) { - commandId = arg2[0]; - } - if (isMacintosh) { // Add role for special case menu items diff --git a/src/vs/platform/native/common/nativeHostService.ts b/src/vs/platform/native/common/nativeHostService.ts index 7d16cba7c66..2ef4689e6a5 100644 --- a/src/vs/platform/native/common/nativeHostService.ts +++ b/src/vs/platform/native/common/nativeHostService.ts @@ -7,7 +7,7 @@ import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; import { IMainProcessService } from '../../ipc/common/mainProcessService.js'; import { INativeHostService } from './native.js'; -// @ts-ignore: interface is implemented via proxy +// @ts-expect-error: interface is implemented via proxy export class NativeHostService implements INativeHostService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index a9b7f5a4e47..f3177fe4d50 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -148,11 +148,12 @@ export class SharedProcess extends Disposable { this.utilityProcess = this._register(new UtilityProcess(this.logService, NullTelemetryService, this.lifecycleMainService)); // Install a log listener for very early shared process warnings and errors - this.utilityProcessLogListener = this.utilityProcess.onMessage((e: any) => { - if (typeof e.warning === 'string') { - this.logService.warn(e.warning); - } else if (typeof e.error === 'string') { - this.logService.error(e.error); + this.utilityProcessLogListener = this.utilityProcess.onMessage(e => { + const logValue = e as { warning?: unknown; error?: unknown }; + if (typeof logValue.warning === 'string') { + this.logService.warn(logValue.warning); + } else if (typeof logValue.error === 'string') { + this.logService.error(logValue.error); } }); diff --git a/src/vs/platform/storage/common/storageIpc.ts b/src/vs/platform/storage/common/storageIpc.ts index 117052ea26c..9bcc394353b 100644 --- a/src/vs/platform/storage/common/storageIpc.ts +++ b/src/vs/platform/storage/common/storageIpc.ts @@ -131,10 +131,6 @@ export class ApplicationStorageDatabaseClient extends BaseProfileAwareStorageDat export class ProfileStorageDatabaseClient extends BaseProfileAwareStorageDatabaseClient { - constructor(channel: IChannel, profile: UriDto) { - super(channel, profile); - } - async close(): Promise { // The profile storage database is shared across all instances of diff --git a/src/vs/platform/storage/electron-main/storageIpc.ts b/src/vs/platform/storage/electron-main/storageIpc.ts index 153856a170c..50e72f57610 100644 --- a/src/vs/platform/storage/electron-main/storageIpc.ts +++ b/src/vs/platform/storage/electron-main/storageIpc.ts @@ -71,6 +71,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel }; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any listen(_: unknown, event: string, arg: IBaseSerializableStorageRequest): Event { switch (event) { case 'onDidChangeStorage': { @@ -98,6 +99,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel //#endregion + // eslint-disable-next-line @typescript-eslint/no-explicit-any async call(_: unknown, command: string, arg: IBaseSerializableStorageRequest): Promise { const profile = arg.profile ? revive(arg.profile) : undefined; const workspace = reviveIdentifier(arg.workspace); @@ -134,6 +136,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel if (typeof path === 'string') { return this.storageMainService.isUsed(path); } + return false; } default: diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index 8c945fe6019..7d343af1f03 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -310,14 +310,6 @@ class BaseProfileAwareStorageMain extends BaseStorageMain { export class ProfileStorageMain extends BaseProfileAwareStorageMain { - constructor( - profile: IUserDataProfile, - options: IStorageMainOptions, - logService: ILogService, - fileService: IFileService - ) { - super(profile, options, logService, fileService); - } } export class ApplicationStorageMain extends BaseProfileAwareStorageMain { diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index baeab608b4d..14ea85b3a20 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -261,8 +261,8 @@ export class UtilityProcess extends Disposable { return true; } - private createEnv(configuration: IUtilityProcessConfiguration): { [key: string]: any } { - const env: { [key: string]: any } = configuration.env ? { ...configuration.env } : { ...deepClone(process.env) }; + private createEnv(configuration: IUtilityProcessConfiguration): NodeJS.ProcessEnv { + const env: NodeJS.ProcessEnv = configuration.env ? { ...configuration.env } : { ...deepClone(process.env) }; // Apply supported environment variables from config env['VSCODE_ESM_ENTRYPOINT'] = configuration.entryPoint; diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 1b604886ace..35257980ea7 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -395,7 +395,7 @@ export interface INativeOpenFileRequest extends IOpenFileRequest { export interface INativeRunActionInWindowRequest { readonly id: string; readonly from: 'menu' | 'touchbar' | 'mouse'; - readonly args?: any[]; + readonly args?: unknown[]; } export interface INativeRunKeybindingInWindowRequest { @@ -471,7 +471,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native * https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentssetzoomlevellevel */ export function zoomLevelToZoomFactor(zoomLevel = 0): number { - return Math.pow(1.2, zoomLevel); + return 1.2 ** zoomLevel; } export const DEFAULT_EMPTY_WINDOW_SIZE = { width: 1200, height: 800 } as const; diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 02efd8bccc8..13268e716ef 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -77,8 +77,8 @@ export interface ICodeWindow extends IBaseWindow { getBounds(): electron.Rectangle; - send(channel: string, ...args: any[]): void; - sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void; + send(channel: string, ...args: unknown[]): void; + sendWhenReady(channel: string, token: CancellationToken, ...args: unknown[]): void; updateTouchBar(items: ISerializableCommandAction[][]): void; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 72de68b9d38..1254f93b7e2 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -902,7 +902,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // Unresponsive if (type === WindowError.UNRESPONSIVE) { - if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { + if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || this._win?.webContents?.isDevToolsOpened()) { // TODO@electron Workaround for https://github.com/microsoft/vscode/issues/56994 // In certain cases the window can report unresponsiveness because a breakpoint was hit // and the process is stopped executing. The most typical cases are: @@ -1473,7 +1473,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this._win?.close(); } - sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { + sendWhenReady(channel: string, token: CancellationToken, ...args: unknown[]): void { if (this.isReady) { this.send(channel, ...args); } else { @@ -1485,7 +1485,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } } - send(channel: string, ...args: any[]): void { + send(channel: string, ...args: unknown[]): void { if (this._win) { if (this._win.isDestroyed() || this._win.webContents.isDestroyed()) { this.logService.warn(`Sending IPC message to channel '${channel}' for window that is destroyed`); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 57d8b534e68..429bd9620c6 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -42,9 +42,9 @@ export interface IWindowsMainService { openExistingWindow(window: ICodeWindow, openConfig: IOpenConfiguration): void; - sendToFocused(channel: string, ...args: any[]): void; - sendToOpeningWindow(channel: string, ...args: any[]): void; - sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void; + sendToFocused(channel: string, ...args: unknown[]): void; + sendToOpeningWindow(channel: string, ...args: unknown[]): void; + sendToAll(channel: string, payload?: unknown, windowIdsToIgnore?: number[]): void; getWindows(): ICodeWindow[]; getWindowCount(): number; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 94224fb708d..2a8aa60380d 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -389,7 +389,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Otherwise, find a good window based on open params else { - const focusLastActive = this.windowsStateHandler.state.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length); + const focusLastActive = this.windowsStateHandler.state.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !openConfig.urisToOpen?.length; let focusLastOpened = true; let focusLastWindow = true; @@ -1410,7 +1410,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic cliArgs = cliArgs.filter(path => { const uri = URI.file(path); - if (!!findWindowOnWorkspaceOrFolder(this.getWindows(), uri)) { + if (findWindowOnWorkspaceOrFolder(this.getWindows(), uri)) { return false; } @@ -1419,7 +1419,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic folderUris = folderUris.filter(folderUriStr => { const folderUri = this.cliArgToUri(folderUriStr); - if (folderUri && !!findWindowOnWorkspaceOrFolder(this.getWindows(), folderUri)) { + if (folderUri && findWindowOnWorkspaceOrFolder(this.getWindows(), folderUri)) { return false; } @@ -1428,7 +1428,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic fileUris = fileUris.filter(fileUriStr => { const fileUri = this.cliArgToUri(fileUriStr); - if (fileUri && !!findWindowOnWorkspaceOrFolder(this.getWindows(), fileUri)) { + if (fileUri && findWindowOnWorkspaceOrFolder(this.getWindows(), fileUri)) { return false; } @@ -1609,7 +1609,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic configuration['disable-extensions'] = currentWindowConfig['disable-extensions']; configuration['disable-extension'] = currentWindowConfig['disable-extension']; } - configuration.loggers = configuration.loggers; } // Update window identifier and session now @@ -1735,19 +1734,19 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return getLastFocused(windows); } - sendToFocused(channel: string, ...args: any[]): void { + sendToFocused(channel: string, ...args: unknown[]): void { const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow(); focusedWindow?.sendWhenReady(channel, CancellationToken.None, ...args); } - sendToOpeningWindow(channel: string, ...args: any[]): void { + sendToOpeningWindow(channel: string, ...args: unknown[]): void { this._register(Event.once(this.onDidSignalReadyWindow)(window => { window.sendWhenReady(channel, CancellationToken.None, ...args); })); } - sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void { + sendToAll(channel: string, payload?: unknown, windowIdsToIgnore?: number[]): void { for (const window of this.getWindows()) { if (windowIdsToIgnore && windowIdsToIgnore.indexOf(window.id) >= 0) { continue; // do not send if we are instructed to ignore it diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index 1d04c4ac17e..0336fb4be72 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -479,7 +479,7 @@ export function getWindowsStateStoreData(windowsState: IWindowsState): IWindowsS function serializeWindowState(windowState: IWindowState): ISerializedWindowState { return { workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() }, - folder: windowState.folderUri && windowState.folderUri.toString(), + folder: windowState.folderUri?.toString(), backupPath: windowState.backupPath, remoteAuthority: windowState.remoteAuthority, uiState: windowState.uiState diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index 0dcfde67393..a6edf301a4a 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -30,7 +30,7 @@ suite('WindowsFinder', () => { }; const testWorkspaceFolders = toWorkspaceFolders([{ path: join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath, extUriBiasedIgnorePathCase); - const localWorkspaceResolver = async (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : undefined; }; + const localWorkspaceResolver = async (workspace: IWorkspaceIdentifier) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : undefined; }; function createTestCodeWindow(options: { lastFocusTime: number; openedFolderUri?: URI; openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow { return new class implements ICodeWindow { @@ -64,8 +64,8 @@ suite('WindowsFinder', () => { focus(options?: { mode: FocusMode }): void { throw new Error('Method not implemented.'); } close(): void { throw new Error('Method not implemented.'); } getBounds(): Electron.Rectangle { throw new Error('Method not implemented.'); } - send(channel: string, ...args: any[]): void { throw new Error('Method not implemented.'); } - sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { throw new Error('Method not implemented.'); } + send(channel: string, ...args: unknown[]): void { throw new Error('Method not implemented.'); } + sendWhenReady(channel: string, token: CancellationToken, ...args: unknown[]): void { throw new Error('Method not implemented.'); } toggleFullScreen(): void { throw new Error('Method not implemented.'); } setRepresentedFilename(name: string): void { throw new Error('Method not implemented.'); } getRepresentedFilename(): string | undefined { throw new Error('Method not implemented.'); } @@ -75,7 +75,7 @@ suite('WindowsFinder', () => { serializeWindowState(): IWindowState { throw new Error('Method not implemented'); } updateWindowControls(options: { height?: number | undefined; backgroundColor?: string | undefined; foregroundColor?: string | undefined }): void { throw new Error('Method not implemented.'); } notifyZoomLevel(level: number): void { throw new Error('Method not implemented.'); } - matches(webContents: any): boolean { throw new Error('Method not implemented.'); } + matches(webContents: Electron.WebContents): boolean { throw new Error('Method not implemented.'); } dispose(): void { } }; } diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 7dddb7f3764..a2cf9529a74 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -316,16 +316,22 @@ interface ISerializedRecentlyOpened { export type RecentlyOpenedStorageData = object; -function isSerializedRecentWorkspace(data: any): data is ISerializedRecentWorkspace { - return data.workspace && typeof data.workspace === 'object' && typeof data.workspace.id === 'string' && typeof data.workspace.configPath === 'string'; +function isSerializedRecentWorkspace(data: unknown): data is ISerializedRecentWorkspace { + const candidate = data as ISerializedRecentWorkspace | undefined; + + return typeof candidate?.workspace === 'object' && typeof candidate.workspace.id === 'string' && typeof candidate.workspace.configPath === 'string'; } -function isSerializedRecentFolder(data: any): data is ISerializedRecentFolder { - return typeof data.folderUri === 'string'; +function isSerializedRecentFolder(data: unknown): data is ISerializedRecentFolder { + const candidate = data as ISerializedRecentFolder | undefined; + + return typeof candidate?.folderUri === 'string'; } -function isSerializedRecentFile(data: any): data is ISerializedRecentFile { - return typeof data.fileUri === 'string'; +function isSerializedRecentFile(data: unknown): data is ISerializedRecentFile { + const candidate = data as ISerializedRecentFile | undefined; + + return typeof candidate?.fileUri === 'string'; } export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined, logService: ILogService): IRecentlyOpened { diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 2f5e8f2c243..7ab576f0a08 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -169,7 +169,7 @@ function _createExtHostProtocol(): Promise { const withPorts = (ports: MessagePortMain[]) => { const port = ports[0]; const onMessage = new BufferedEmitter(); - port.on('message', (e) => onMessage.fire(VSBuffer.wrap(e.data))); + port.on('message', (e) => onMessage.fire(VSBuffer.wrap(e.data as Uint8Array))); port.on('close', () => { onTerminate('renderer closed the MessagePort'); }); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 74d5227eb6f..a9e5dc08d17 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -389,7 +389,7 @@ class ToggleScreencastModeAction extends Action2 { const fromCommandsRegistry = CommandsRegistry.getCommand(commandId); - if (fromCommandsRegistry && fromCommandsRegistry.metadata?.description) { + if (fromCommandsRegistry?.metadata?.description) { return { title: typeof fromCommandsRegistry.metadata.description === 'string' ? fromCommandsRegistry.metadata.description : fromCommandsRegistry.metadata.description.value }; } diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 2fb9c8edbb3..7ba99ce6b2c 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -6,7 +6,7 @@ import { localize, localize2 } from '../../../nls.js'; import { IWindowOpenable } from '../../../platform/window/common/window.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; -import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from '../../../platform/actions/common/actions.js'; +import { MenuRegistry, MenuId, Action2, registerAction2 } from '../../../platform/actions/common/actions.js'; import { KeyChord, KeyCode, KeyMod } from '../../../base/common/keyCodes.js'; import { IsMainWindowFullscreenContext } from '../../common/contextkeys.js'; import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext } from '../../../platform/contextkey/common/contextkeys.js'; @@ -62,10 +62,6 @@ abstract class BaseOpenRecentAction extends Action2 { tooltip: localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"), }; - constructor(desc: Readonly) { - super(desc); - } - protected abstract isQuickNavigate(): boolean; override async run(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index a6e4bd17298..c9a3c2ddd5a 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -241,8 +241,8 @@ CommandsRegistry.registerCommand({ const commandService = accessor.get(ICommandService); const commandOptions: IOpenEmptyWindowOptions = { - forceReuseWindow: options && options.reuseWindow, - remoteAuthority: options && options.remoteAuthority + forceReuseWindow: options?.reuseWindow, + remoteAuthority: options?.remoteAuthority }; return commandService.executeCommand('_files.newWindow', commandOptions); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index d4c3a8b56d5..dc801de5208 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -31,7 +31,7 @@ import { IBaseActionViewItemOptions } from '../../base/browser/ui/actionbar/acti * layout(), focus(), dispose(). During use of the workbench, a composite will often receive a setVisible, * layout and focus call, but only one create and dispose call. */ -export abstract class Composite extends Component implements IComposite { +export abstract class Composite extends Component implements IComposite { private readonly _onTitleAreaUpdate = this._register(new Emitter()); readonly onTitleAreaUpdate = this._onTitleAreaUpdate.event; diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 16bddbd0ea1..794712c925b 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -470,7 +470,7 @@ export class CompositeDragAndDropObserver extends Disposable { private readDragData(type: ViewType): CompositeDragAndDropData | undefined { if (this.transferData.hasData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype)) { const data = this.transferData.getData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); - if (data && data[0]) { + if (data?.[0]) { return new CompositeDragAndDropData(type, data[0].id); } } diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 86dbfe14fd9..fe21eedbcb7 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -24,7 +24,7 @@ import { VIEWPANE_FILTER_ACTION } from './parts/views/viewPane.js'; import { IBoundarySashes } from '../../base/browser/ui/sash/sash.js'; import { IBaseActionViewItemOptions } from '../../base/browser/ui/actionbar/actionViewItems.js'; -export abstract class PaneComposite extends Composite implements IPaneComposite { +export abstract class PaneComposite extends Composite implements IPaneComposite { private viewPaneContainer?: ViewPaneContainer; diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 8b113183fc1..d66a8f5f0ee 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -30,7 +30,7 @@ export interface ILayoutContentResult { * Parts are layed out in the workbench and have their own layout that * arranges an optional title and mandatory content area to show content. */ -export abstract class Part extends Component implements ISerializableView { +export abstract class Part extends Component implements ISerializableView { private _dimension: Dimension | undefined; get dimension(): Dimension | undefined { return this._dimension; } @@ -265,7 +265,7 @@ export interface IMultiWindowPart { readonly element: HTMLElement; } -export abstract class MultiWindowParts extends Component { +export abstract class MultiWindowParts extends Component { protected readonly _parts = new Set(); get parts() { return Array.from(this._parts); } diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index cca1343bed1..18ed28fd725 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -63,12 +63,12 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { const activeComposite = this.getActivePaneComposite(); if (!activeComposite) { - return; + return undefined; } const width = activeComposite.getOptimalWidth(); if (typeof width !== 'number') { - return; + return undefined; } return Math.max(width, 300); diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index cbc65f19067..52c1ea3f817 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -71,7 +71,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (dragData.type === 'view') { const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!; - if (viewToMove && viewToMove.canMoveView) { + if (viewToMove.canMoveView) { this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation, 'dnd'); const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!; diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 793fd69668c..097fae86071 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -56,7 +56,7 @@ interface CompositeItem { readonly progress: IProgressIndicator; } -export abstract class CompositePart extends Part { +export abstract class CompositePart extends Part { protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite; focus: boolean }>()); protected readonly onDidCompositeClose = this._register(new Emitter()); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7f1727a3bf4..ec56befa3db 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -12,7 +12,7 @@ import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser import { GoFilter, IHistoryService } from '../../../services/history/common/history.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, SelectedEditorsMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, MOVE_EDITOR_INTO_RIGHT_GROUP, MOVE_EDITOR_INTO_LEFT_GROUP, MOVE_EDITOR_INTO_ABOVE_GROUP, MOVE_EDITOR_INTO_BELOW_GROUP } from './editorCommands.js'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, SelectedEditorsMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, MOVE_EDITOR_INTO_RIGHT_GROUP, MOVE_EDITOR_INTO_LEFT_GROUP, MOVE_EDITOR_INTO_ABOVE_GROUP, MOVE_EDITOR_INTO_BELOW_GROUP } from './editorCommands.js'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, MergeGroupMode } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index cae1244e405..51128210543 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -1276,7 +1276,7 @@ function registerOtherEditorCommands(): void { const configurationService = accessor.get(IConfigurationService); const currentSetting = configurationService.getValue('workbench.editor.enablePreview'); - const newSetting = currentSetting === true ? false : true; + const newSetting = currentSetting !== true; configurationService.updateValue('workbench.editor.enablePreview', newSetting); } }); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 1e8b15256e5..85178c7e7fd 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -336,6 +336,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.EDITOR_CLOSE: groupActiveEditorPinnedContext.set(this.model.activeEditor ? this.model.isPinned(this.model.activeEditor) : false); groupActiveEditorStickyContext.set(this.model.activeEditor ? this.model.isSticky(this.model.activeEditor) : false); + break; case GroupModelChangeKind.EDITOR_OPEN: case GroupModelChangeKind.EDITOR_MOVE: groupActiveEditorFirstContext.set(this.model.isFirst(this.model.activeEditor)); @@ -1802,7 +1803,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // However, we only do this unless a custom confirm handler is installed // that may not be fit to be asked a second time right after. if (!editor.closeHandler && !this.shouldConfirmClose(editor)) { - return confirmation === ConfirmResult.CANCEL ? true : false; + return confirmation === ConfirmResult.CANCEL; } // Otherwise, handle accordingly diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index bb868efd2f1..c7be4a6cd63 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -16,7 +16,6 @@ import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { isEmptyObject } from '../../../../base/common/types.js'; import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from './editor.js'; -import { MementoObject } from '../../../common/memento.js'; import { joinPath, IExtUri, isEqual } from '../../../../base/common/resources.js'; import { indexOfPath } from '../../../../base/common/extpath.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -47,7 +46,7 @@ import { getWindowById } from '../../../../base/browser/dom.js'; * * This class is only intended to be subclassed and not instantiated. */ -export abstract class EditorPane extends Composite implements IEditorPane { +export abstract class EditorPane extends Composite implements IEditorPane { //#region Events @@ -220,7 +219,7 @@ export class EditorMemento extends Disposable implements IEditorMemento { constructor( readonly id: string, private readonly key: string, - private readonly memento: MementoObject, + private readonly memento: T, private readonly limit: number, private readonly editorGroupService: IEditorGroupsService, private readonly configurationService: ITextResourceConfigurationService @@ -387,7 +386,7 @@ export class EditorMemento extends Disposable implements IEditorMemento { this.cache = new LRUCache>(this.limit); // Restore from serialized map state - const rawEditorMemento = this.memento[this.key]; + const rawEditorMemento = this.memento[this.key as keyof T]; if (Array.isArray(rawEditorMemento)) { this.cache.fromJSON(rawEditorMemento); } @@ -405,7 +404,7 @@ export class EditorMemento extends Disposable implements IEditorMemento { this.cleanedUp = true; } - this.memento[this.key] = cache.toJSON(); + (this.memento as Record)[this.key] = cache.toJSON(); } private cleanUp(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 354d12cb363..cb830aecd0e 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -22,7 +22,7 @@ import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget import { ISerializedEditorGroupModel, isSerializedEditorGroupModel } from '../../../common/editor/editorGroupModel.js'; import { EditorDropTarget } from './editorDropTarget.js'; import { Color } from '../../../../base/common/color.js'; -import { CenteredViewLayout } from '../../../../base/browser/ui/centered/centeredViewLayout.js'; +import { CenteredViewLayout, CenteredViewState } from '../../../../base/browser/ui/centered/centeredViewLayout.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; import { Parts, IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { DeepPartial, assertType } from '../../../../base/common/types.js'; @@ -43,6 +43,11 @@ export interface IEditorPartUIState { readonly mostRecentActiveGroups: GroupIdentifier[]; } +interface IEditorPartMemento { + 'editorpart.state'?: IEditorPartUIState; + 'editorpart.centeredview'?: CenteredViewState; +} + class GridWidgetView implements IView { readonly element: HTMLElement = $('.grid-view-container'); @@ -83,7 +88,7 @@ class GridWidgetView implements IView { } } -export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { +export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 5b29a7020bc..4e7ff2d640f 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -43,7 +43,11 @@ interface IEditorWorkingSetState extends IEditorWorkingSet { readonly auxiliary: IEditorPartsUIState; } -export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { +interface IEditorPartsMemento { + 'editorparts.state'?: IEditorPartsUIState; +} + +export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { declare readonly _serviceBrand: undefined; @@ -534,7 +538,7 @@ export class EditorParts extends MultiWindowParts implements IEditor break; } - return parts.map(part => part.getGroups(order)).flat(); + return parts.flatMap(part => part.getGroups(order)); } return this.mainPart.getGroups(order); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 94e0e278fa2..77bfedeabb0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -305,7 +305,7 @@ class TabFocusMode extends Disposable { this.registerListeners(); - const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true ? true : false; + const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true; TabFocus.setTabFocusMode(tabFocusModeConfig); } @@ -314,7 +314,7 @@ class TabFocusMode extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor.tabFocusMode')) { - const tabFocusModeConfig = this.configurationService.getValue('editor.tabFocusMode') === true ? true : false; + const tabFocusModeConfig = this.configurationService.getValue('editor.tabFocusMode') === true; TabFocus.setTabFocusMode(tabFocusModeConfig); this._onDidChange.fire(tabFocusModeConfig); diff --git a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts index 8cfd84b64c1..70a0c0b1260 100644 --- a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts +++ b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts @@ -126,7 +126,7 @@ export abstract class AbstractEditorWithViewState extends Edit // new editor: check with workbench.editor.restoreViewState setting if (context?.newInGroup) { - return this.textResourceConfigurationService.getValue(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY }), 'workbench.editor.restoreViewState') === false ? false : true /* restore by default */; + return this.textResourceConfigurationService.getValue(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY }), 'workbench.editor.restoreViewState') !== false /* restore by default */; } // existing editor: always restore viewstate diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 19dd4c3b511..4c5ca3582b5 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -273,7 +273,7 @@ export class EditorsObserver extends Disposable { // Remove from key map const map = this.keyMap.get(group.id); - if (map && map.delete(key.editor) && map.size === 0) { + if (map?.delete(key.editor) && map.size === 0) { this.keyMap.delete(group.id); } diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index c5b4faf8e49..e41d6f4824a 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -13,7 +13,6 @@ .monaco-workbench .notifications-list-container .notification-list-item { display: flex; - flex-direction: column; flex-direction: column-reverse; /* the details row appears first in order for better keyboard access to notification buttons */ padding: 10px 5px; height: 100%; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index ab770c9580b..6512492cae4 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -65,7 +65,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate 1 */)}px`; diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index fda9c918852..af2a9da5350 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -324,7 +324,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart { let childrenGroups: ITreeItem[][]; let checkboxesUpdated: ITreeItem[] = []; - if (nodes && nodes.every((node): node is Required => !!node.children)) { + if (nodes?.every((node): node is Required => !!node.children)) { childrenGroups = nodes.map(node => node.children); } else { nodes = nodes ?? [self.root]; @@ -1331,7 +1331,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + const matches = (treeItemLabel?.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => { if (start < 0) { start = label.length + start; } diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 8235fb5dff8..a047efbb82f 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -245,7 +245,7 @@ class ViewWelcomeController { if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { const node = linkedText.nodes[0]; const buttonContainer = append(this.element!, $('.button-container')); - const button = new Button(buttonContainer, { title: node.title, supportIcons: true, secondary: renderSecondaryButtons && buttonsCount > 0 ? true : false, ...defaultButtonStyles, }); + const button = new Button(buttonContainer, { title: node.title, supportIcons: true, secondary: !!(renderSecondaryButtons && buttonsCount > 0), ...defaultButtonStyles, }); button.label = node.label; button.onDidClick(_ => { this.openerService.open(node.href, { allowCommands: true }); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1b1188d09ad..f823ecfd051 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -288,7 +288,7 @@ class ViewPaneDropOverlay extends Themable { } } -export class ViewPaneContainer extends Component implements IViewPaneContainer { +export class ViewPaneContainer extends Component implements IViewPaneContainer { readonly viewContainer: ViewContainer; private lastFocusedPane: ViewPane | undefined; @@ -424,7 +424,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, { onDragEnter: (e) => { bounds = getOverlayBounds(); - if (overlay && overlay.disposed) { + if (overlay?.disposed) { overlay = undefined; } @@ -453,7 +453,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } }, onDragOver: (e) => { - if (overlay && overlay.disposed) { + if (overlay?.disposed) { overlay = undefined; } diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index c579f0c1e53..644667ceb66 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Memento, MementoObject } from './memento.js'; +import { Memento } from './memento.js'; import { IThemeService, Themable } from '../../platform/theme/common/themeService.js'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js'; import { DisposableStore } from '../../base/common/lifecycle.js'; import { Event } from '../../base/common/event.js'; -export class Component extends Themable { +export class Component extends Themable { - private readonly memento: Memento; + private readonly memento: Memento; constructor( private readonly id: string, @@ -36,12 +36,12 @@ export class Component extends Themable { return this.id; } - protected getMemento(scope: StorageScope, target: StorageTarget): MementoObject { + protected getMemento(scope: StorageScope, target: StorageTarget): Partial { return this.memento.getMemento(scope, target); } protected reloadMemento(scope: StorageScope): void { - return this.memento.reloadMemento(scope); + this.memento.reloadMemento(scope); } protected onDidChangeMementoValue(scope: StorageScope, disposables: DisposableStore): Event { diff --git a/src/vs/workbench/common/configuration.ts b/src/vs/workbench/common/configuration.ts index 46f42503e1c..e1191861eda 100644 --- a/src/vs/workbench/common/configuration.ts +++ b/src/vs/workbench/common/configuration.ts @@ -58,8 +58,9 @@ export const Extensions = { ConfigurationMigration: 'base.contributions.configuration.migration' }; -export type ConfigurationValue = { value: any | undefined /* Remove */ }; +type ConfigurationValue = { value: unknown | undefined /* Remove */ }; export type ConfigurationKeyValuePairs = [string, ConfigurationValue][]; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type ConfigurationMigrationFn = (value: any, valueAccessor: (key: string) => any) => ConfigurationValue | ConfigurationKeyValuePairs | Promise; export type ConfigurationMigration = { key: string; migrateFn: ConfigurationMigrationFn }; @@ -115,7 +116,7 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl private async migrateConfigurationsForFolderAndOverride(migration: ConfigurationMigration, resource?: URI): Promise { const inspectData = this.configurationService.inspect(migration.key, { resource }); - const targetPairs: [keyof IConfigurationValue, ConfigurationTarget][] = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? [ + const targetPairs: [keyof IConfigurationValue, ConfigurationTarget][] = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? [ ['user', ConfigurationTarget.USER], ['userLocal', ConfigurationTarget.USER_LOCAL], ['userRemote', ConfigurationTarget.USER_REMOTE], @@ -128,7 +129,7 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl ['workspace', ConfigurationTarget.WORKSPACE], ]; for (const [dataKey, target] of targetPairs) { - const inspectValue = inspectData[dataKey] as IInspectValue | undefined; + const inspectValue = inspectData[dataKey] as IInspectValue | undefined; if (!inspectValue) { continue; } @@ -159,10 +160,10 @@ export class ConfigurationMigrationWorkbenchContribution extends Disposable impl } } - private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue, value: any, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise { + private async runMigration(migration: ConfigurationMigration, dataKey: keyof IConfigurationValue, value: unknown, resource: URI | undefined, overrideIdentifiers: string[] | undefined): Promise { const valueAccessor = (key: string) => { const inspectData = this.configurationService.inspect(key, { resource }); - const inspectValue = inspectData[dataKey] as IInspectValue | undefined; + const inspectValue = inspectData[dataKey] as IInspectValue | undefined; if (!inspectValue) { return undefined; } @@ -271,7 +272,7 @@ export class DynamicWindowConfiguration extends Disposable implements IWorkbench 'type': ['string', 'null'], 'default': null, 'enum': [...this.userDataProfilesService.profiles.map(profile => profile.name), null], - 'enumItemLabels': [...this.userDataProfilesService.profiles.map(p => ''), localize('active window', "Active Window")], + 'enumItemLabels': [...this.userDataProfilesService.profiles.map(() => ''), localize('active window', "Active Window")], 'description': localize('newWindowProfile', "Specifies the profile to use when opening a new window. If a profile name is provided, the new window will use that profile. If no profile name is provided, the new window will use the profile of the active window or the Default profile if no active window exists."), 'scope': ConfigurationScope.APPLICATION, } diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index f7c03af3c2c..d16fc13213f 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -640,7 +640,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } setActive(candidate: EditorInput | undefined): EditorInput | undefined { - let result: EditorInput | undefined = undefined; + let result: EditorInput | undefined; if (!candidate) { this.setGroupActive(); @@ -1231,7 +1231,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } this.editors = coalesce(data.editors.map((e, index) => { - let editor: EditorInput | undefined = undefined; + let editor: EditorInput | undefined; const editorSerializer = registry.getEditorSerializer(e.id); if (editorSerializer) { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 360b534bdc9..221d0f32db8 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -198,7 +198,7 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements // resource scheme. const defaultSizeLimit = getLargeFileConfirmationLimit(this.resource); - let configuredSizeLimit: number | undefined = undefined; + let configuredSizeLimit: number | undefined; const configuredSizeLimitMb = this.textResourceConfigurationService.inspect(this.resource, null, 'workbench.editorLargeFileConfirmation'); if (isConfigured(configuredSizeLimitMb)) { diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index f97a811e64b..b251e739f6c 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -9,13 +9,11 @@ import { onUnexpectedError } from '../../base/common/errors.js'; import { DisposableStore } from '../../base/common/lifecycle.js'; import { Event } from '../../base/common/event.js'; -export type MementoObject = { [key: string]: any }; +export class Memento { -export class Memento { - - private static readonly applicationMementos = new Map(); - private static readonly profileMementos = new Map(); - private static readonly workspaceMementos = new Map(); + private static readonly applicationMementos = new Map>(); + private static readonly profileMementos = new Map>(); + private static readonly workspaceMementos = new Map>(); private static readonly COMMON_PREFIX = 'memento/'; @@ -25,7 +23,7 @@ export class Memento { this.id = Memento.COMMON_PREFIX + id; } - getMemento(scope: StorageScope, target: StorageTarget): MementoObject { + getMemento(scope: StorageScope, target: StorageTarget): Partial { switch (scope) { case StorageScope.WORKSPACE: { let workspaceMemento = Memento.workspaceMementos.get(this.id); @@ -70,7 +68,7 @@ export class Memento { } reloadMemento(scope: StorageScope): void { - let memento: ScopedMemento | undefined; + let memento: ScopedMemento | undefined; switch (scope) { case StorageScope.APPLICATION: memento = Memento.applicationMementos.get(this.id); @@ -101,17 +99,17 @@ export class Memento { } } -class ScopedMemento { +class ScopedMemento { - private mementoObj: MementoObject; + private mementoObj: Partial; constructor(private id: string, private scope: StorageScope, private target: StorageTarget, private storageService: IStorageService) { this.mementoObj = this.doLoad(); } - private doLoad(): MementoObject { + private doLoad(): Partial { try { - return this.storageService.getObject(this.id, this.scope, {}); + return this.storageService.getObject(this.id, this.scope, {}); } catch (error) { // Seeing reports from users unable to open editors // from memento parsing exceptions. Log the contents @@ -123,7 +121,7 @@ class ScopedMemento { return {}; } - getMemento(): MementoObject { + getMemento(): Partial { return this.mementoObj; } @@ -131,7 +129,7 @@ class ScopedMemento { // Clear old for (const name of Object.getOwnPropertyNames(this.mementoObj)) { - delete this.mementoObj[name]; + delete this.mementoObj[name as keyof Partial]; } // Assign new diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 08861595cb0..ff1ddb45f96 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -736,8 +736,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie return false; } - const primaryActions = (this._actions && this._actions.primary) || []; - const otherPrimaryActions = (other.actions && other.actions.primary) || []; + const primaryActions = this._actions?.primary || []; + const otherPrimaryActions = other.actions?.primary || []; return equals(primaryActions, otherPrimaryActions, (action, otherAction) => (action.id + action.label) === (otherAction.id + otherAction.label)); } } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 37e1b8f620c..8cfa0047966 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -660,7 +660,7 @@ export interface ITreeView extends IDisposable { readonly onDidChangeCheckboxState: Event; - readonly container: any | undefined; + readonly container: unknown /* HTMLElement */ | undefined; // checkboxesChanged is a subset of treeItems refresh(treeItems?: readonly ITreeItem[], checkboxesChanged?: readonly ITreeItem[]): Promise; @@ -685,7 +685,7 @@ export interface ITreeView extends IDisposable { setFocus(item?: ITreeItem): void; - show(container: any): void; + show(container: unknown /* HTMLElement */): void; } export interface IRevealOptions { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 5935dd397d0..9bfb0f0aeac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -48,7 +48,7 @@ export class ChatEditor extends EditorPane { return this._scopedContextKeyService; } - private _memento: Memento | undefined; + private _memento: Memento | undefined; private _viewState: IChatViewState | undefined; private dimension = new dom.Dimension(0, 0); @@ -169,7 +169,7 @@ export class ChatEditor extends EditorPane { private updateModel(model: IChatModel, viewState?: IChatViewState): void { this._memento = new Memento('interactive-session-editor-' + CHAT_PROVIDER_ID, this.storageService); - this._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState; + this._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); this.widget.setModel(model, { ...this._viewState }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 05f6f0ec6cf..7024192bd84 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -610,7 +610,7 @@ class ChatStatusDashboard extends Disposable { } } - private runCommandAndClose(commandOrFn: string | Function, ...args: any[]): void { + private runCommandAndClose(commandOrFn: string | Function, ...args: unknown[]): void { if (typeof commandOrFn === 'function') { commandOrFn(...args); } else { diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 6beb02e2ad5..3b41a95bfc7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -50,7 +50,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { get widget(): ChatWidget { return this._widget; } private readonly modelDisposables = this._register(new DisposableStore()); - private memento: Memento; + private memento: Memento; private readonly viewState: IViewPaneState; private _restoringSession: Promise | undefined; @@ -78,11 +78,11 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { // View state for the ViewPane is currently global per-provider basically, but some other strictly per-model state will require a separate memento. this.memento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID, this.storageService); - this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState; + this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); if (this.chatOptions.location === ChatAgentLocation.Chat && !this.viewState.hasMigratedCurrentSession) { - const editsMemento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID + `-edits`, this.storageService); - const lastEditsState = editsMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState; + const editsMemento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID + `-edits`, this.storageService); + const lastEditsState = editsMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); if (lastEditsState.sessionId) { this.logService.trace(`ChatViewPane: last edits session was ${lastEditsState.sessionId}`); if (!this.chatService.isPersistedSessionEmpty(lastEditsState.sessionId)) { diff --git a/src/vs/workbench/contrib/chat/common/chatTodoListService.ts b/src/vs/workbench/contrib/chat/common/chatTodoListService.ts index 3cbfbe2d75c..835af80fd20 100644 --- a/src/vs/workbench/contrib/chat/common/chatTodoListService.ts +++ b/src/vs/workbench/contrib/chat/common/chatTodoListService.ts @@ -29,7 +29,7 @@ export interface IChatTodoListService { } export class ChatTodoListStorage implements IChatTodoListStorage { - private memento: Memento; + private memento: Memento>; constructor(@IStorageService storageService: IStorageService) { this.memento = new Memento('chat-todo-list', storageService); diff --git a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts index be1e5284771..4ed046e62a7 100644 --- a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts +++ b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts @@ -43,7 +43,7 @@ export interface IChatWidgetHistoryService { } interface IChatHistory { - history: { [providerId: string]: IChatHistoryEntry[] }; + history?: { [providerId: string]: IChatHistoryEntry[] }; } export const ChatInputHistoryMaxEntries = 40; @@ -51,7 +51,7 @@ export const ChatInputHistoryMaxEntries = 40; export class ChatWidgetHistoryService implements IChatWidgetHistoryService { _serviceBrand: undefined; - private memento: Memento; + private memento: Memento; private viewState: IChatHistory; private readonly _onDidClearHistory = new Emitter(); @@ -60,8 +60,8 @@ export class ChatWidgetHistoryService implements IChatWidgetHistoryService { constructor( @IStorageService storageService: IStorageService ) { - this.memento = new Memento('interactive-session', storageService); - const loadedState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatHistory; + this.memento = new Memento('interactive-session', storageService); + const loadedState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); for (const provider in loadedState.history) { // Migration from old format loadedState.history[provider] = loadedState.history[provider].map(entry => typeof entry === 'string' ? { text: entry } : entry); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index e9dcf952046..b97d67a4e1e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -24,7 +24,7 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { CommentsViewFilterFocusContextKey, ICommentsView } from './comments.js'; import { CommentsFilters, CommentsFiltersChangeEvent, CommentsSortOrder } from './commentsViewActions.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { FilterOptions } from './commentsFilterOptions.js'; import { CommentThreadApplicability, CommentThreadState } from '../../../../editor/common/languages.js'; @@ -45,6 +45,14 @@ export const CONTEXT_KEY_SOME_COMMENTS_EXPANDED = new RawContextKey('co export const CONTEXT_KEY_COMMENT_FOCUSED = new RawContextKey('commentsView.commentFocused', false); const VIEW_STORAGE_ID = 'commentsViewState'; +interface CommentsViewState { + filter?: string; + filterHistory?: string[]; + showResolved?: boolean; + showUnresolved?: boolean; + sortBy?: CommentsSortOrder; +} + type CommentsTreeNode = CommentsModel | ResourceWithCommentThreads | CommentNode; function createResourceCommentsIterator(model: ICommentsModel): Iterable> { @@ -78,8 +86,8 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { private currentHeight = 0; private currentWidth = 0; - private readonly viewState: MementoObject; - private readonly stateMemento: Memento; + private readonly viewState: CommentsViewState; + private readonly stateMemento: Memento; private cachedFilterStats: { total: number; filtered: number } | undefined = undefined; readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; @@ -152,15 +160,15 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { @IStorageService storageService: IStorageService, @IPathService private readonly pathService: IPathService, ) { - const stateMemento = new Memento(VIEW_STORAGE_ID, storageService); + const stateMemento = new Memento(VIEW_STORAGE_ID, storageService); const viewState = stateMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); super({ ...options, filterOptions: { placeholder: nls.localize('comments.filter.placeholder', "Filter (e.g. text, author)"), ariaLabel: nls.localize('comments.filter.ariaLabel', "Filter comments"), - history: viewState['filterHistory'] || [], - text: viewState['filter'] || '', + history: viewState.filterHistory || [], + text: viewState.filter || '', focusContextKey: CommentsViewFilterFocusContextKey.key } }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); @@ -171,9 +179,9 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this.viewState = viewState; this.filters = this._register(new CommentsFilters({ - showResolved: this.viewState['showResolved'] !== false, - showUnresolved: this.viewState['showUnresolved'] !== false, - sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending, + showResolved: this.viewState.showResolved !== false, + showUnresolved: this.viewState.showUnresolved !== false, + sortBy: this.viewState.sortBy ?? CommentsSortOrder.ResourceAscending, }, this.contextKeyService)); this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved)); @@ -189,11 +197,11 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { } override saveState(): void { - this.viewState['filter'] = this.filterWidget.getFilterText(); - this.viewState['filterHistory'] = this.filterWidget.getHistory(); - this.viewState['showResolved'] = this.filters.showResolved; - this.viewState['showUnresolved'] = this.filters.showUnresolved; - this.viewState['sortBy'] = this.filters.sortBy; + this.viewState.filter = this.filterWidget.getFilterText(); + this.viewState.filterHistory = this.filterWidget.getHistory(); + this.viewState.showResolved = this.filters.showResolved; + this.viewState.showUnresolved = this.filters.showUnresolved; + this.viewState.sortBy = this.filters.sortBy; this.stateMemento.saveMemento(); super.saveState(); } diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts index 849bd4d904f..fbf895d16ac 100644 --- a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts @@ -15,13 +15,17 @@ import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from './ext import { RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { IExtensionPointUser } from '../../../services/extensions/common/extensionsRegistry.js'; +interface CustomEditorsMemento { + editors?: CustomEditorDescriptor[]; +} + export class ContributedCustomEditors extends Disposable { private static readonly CUSTOM_EDITORS_STORAGE_ID = 'customEditors'; private static readonly CUSTOM_EDITORS_ENTRY_ID = 'editors'; private readonly _editors = new Map(); - private readonly _memento: Memento; + private readonly _memento: Memento; constructor(storageService: IStorageService) { super(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9092f0d6e70..62485d9eade 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -45,7 +45,6 @@ import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; -import { MementoObject } from '../../../common/memento.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; @@ -92,6 +91,10 @@ export const ExtensionsSearchValueContext = new RawContextKey('extension const REMOTE_CATEGORY: ILocalizedString = localize2({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"); +interface IExtensionsViewletState { + 'query.value'?: string; +} + export class ExtensionsViewletViewsContribution extends Disposable implements IWorkbenchContribution { private readonly container: ViewContainer; @@ -506,7 +509,7 @@ export class ExtensionsViewletViewsContribution extends Disposable implements IW } -export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { +export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { private readonly extensionsSearchValueContextKey: IContextKey; private readonly defaultViewsContextKey: IContextKey; @@ -534,7 +537,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private header: HTMLElement | undefined; private searchBox: SuggestEnabledInput | undefined; private notificationContainer: HTMLElement | undefined; - private readonly searchViewletState: MementoObject; + private readonly searchViewletState: IExtensionsViewletState; private extensionGalleryManifest: IExtensionGalleryManifest | null = null; constructor( diff --git a/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts index 118758dc1a0..c242cd03c29 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts @@ -16,7 +16,7 @@ interface RegisteredExternalOpener { } interface OpenersMemento { - [id: string]: RegisteredExternalOpener; + [id: string]: RegisteredExternalOpener | undefined; } export class ContributedExternalUriOpenersStore extends Disposable { @@ -24,7 +24,7 @@ export class ContributedExternalUriOpenersStore extends Disposable { private static readonly STORAGE_ID = 'externalUriOpeners'; private readonly _openers = new Map(); - private readonly _memento: Memento; + private readonly _memento: Memento; private _mementoObject: OpenersMemento; constructor( @@ -36,7 +36,9 @@ export class ContributedExternalUriOpenersStore extends Disposable { this._memento = new Memento(ContributedExternalUriOpenersStore.STORAGE_ID, storageService); this._mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); for (const [id, value] of Object.entries(this._mementoObject || {})) { - this.add(id, value.extensionId, { isCurrentlyRegistered: false }); + if (value) { + this.add(id, value.extensionId, { isCurrentlyRegistered: false }); + } } this.invalidateOpenersOnExtensionsChanged(); diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts index 61f0ac0a8ff..b49c34fbc7c 100644 --- a/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -446,7 +446,7 @@ export class ExplorerService implements IExplorerService { if (item === undefined || ignore) { return true; } - if (this.revealExcludeMatcher.matches(item.resource, name => !!(item.parent && item.parent.getChild(name)))) { + if (this.revealExcludeMatcher.matches(item.resource, name => !!(item.parent?.getChild(name)))) { return false; } const root = item.root; @@ -521,7 +521,7 @@ function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: Fi } function getRevealExcludes(configuration: IFilesConfiguration): IExpression { - const revealExcludes = configuration && configuration.explorer && configuration.explorer.autoRevealExclude; + const revealExcludes = configuration?.explorer?.autoRevealExclude; if (!revealExcludes) { return {}; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 80b38b619e3..ecc6af48668 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -196,7 +196,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { let delay = 0; const config = this.configurationService.getValue(); - if (!!config.workbench?.editor?.enablePreview) { + if (config.workbench?.editor?.enablePreview) { // delay open editors view when preview is enabled // to accomodate for the user doing a double click // to pin the editor. diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 50f786ea2f7..bcb80404e4f 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -78,14 +78,6 @@ export const UPLOAD_LABEL = nls.localize('upload', "Upload..."); const CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete'; const MAX_UNDO_FILE_SIZE = 5000000; // 5mb -function onError(notificationService: INotificationService, error: any): void { - if (error.message === 'string') { - error = error.message; - } - - notificationService.error(toErrorMessage(error, false)); -} - async function refreshIfSeparator(value: string, explorerService: IExplorerService): Promise { if (value && ((value.indexOf('/') >= 0) || (value.indexOf('\\') >= 0))) { // New input contains separator, multiple resources will get created workaround for #68204 @@ -600,7 +592,7 @@ abstract class BaseSaveAllAction extends Action { try { await this.doRun(context); } catch (error) { - onError(this.notificationService, error); + this.notificationService.error(toErrorMessage(error, false)); } } } @@ -1281,7 +1273,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi } } } catch (e) { - onError(notificationService, new Error(nls.localize('fileDeleted', "The file(s) to paste have been deleted or moved since you copied them. {0}", getErrorMessage(e)))); + notificationService.error(toErrorMessage(new Error(nls.localize('fileDeleted', "The file(s) to paste have been deleted or moved since you copied them. {0}", getErrorMessage(e))), false)); } finally { if (pasteShouldMove) { // Cut is done. Make sure to clear cut state. diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 0cc1fa6240f..28a46340a05 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -280,7 +280,7 @@ export class BrowserFileUpload { operation.filesTotal += childEntries.length; // Split up files from folders to upload - const folderTarget = target && target.getChild(entry.name) || undefined; + const folderTarget = target?.getChild(entry.name) || undefined; const fileChildEntries: IWebkitDataTransferItemEntry[] = []; const folderChildEntries: IWebkitDataTransferItemEntry[] = []; for (const childEntry of childEntries) { diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 00307a068de..4ea8553bd60 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -159,7 +159,7 @@ export function getMultiSelectedResources(commandArg: unknown, listService: ILis } const result = getResourceForCommand(commandArg, editorSerice, listService); - return !!result ? [result] : []; + return result ? [result] : []; } export function getOpenEditorsViewMultiSelection(accessor: ServicesAccessor): Array | undefined { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index c7056e38868..255dcf11f83 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -104,14 +104,14 @@ export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], res } const compressedNavigationControllers = focusedStat && compressedNavigationControllerProvider.getCompressedNavigationController(focusedStat); - const compressedNavigationController = compressedNavigationControllers && compressedNavigationControllers.length ? compressedNavigationControllers[0] : undefined; + const compressedNavigationController = compressedNavigationControllers?.length ? compressedNavigationControllers[0] : undefined; focusedStat = compressedNavigationController ? compressedNavigationController.current : focusedStat; const selectedStats: ExplorerItem[] = []; for (const stat of selection) { const controllers = compressedNavigationControllerProvider.getCompressedNavigationController(stat); - const controller = controllers && controllers.length ? controllers[0] : undefined; + const controller = controllers?.at(0); if (controller && focusedStat && controller === compressedNavigationController) { if (stat === focusedStat) { selectedStats.push(stat); @@ -550,7 +550,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { this._register(this.tree.onDidChangeCollapseState(e => { const element = e.node.element?.element; if (element) { - const navigationControllers = this.renderer.getCompressedNavigationController(element instanceof Array ? element[0] : element); + const navigationControllers = this.renderer.getCompressedNavigationController(Array.isArray(element) ? element[0] : element); navigationControllers?.forEach(controller => controller.updateCollapsed(e.node.collapsed)); } // Update showing expand / collapse button @@ -643,7 +643,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { let arg: URI | {}; if (stat instanceof ExplorerItem) { const compressedControllers = this.renderer.getCompressedNavigationController(stat); - arg = compressedControllers && compressedControllers.length ? compressedControllers[0].current.resource : stat.resource; + arg = compressedControllers?.length ? compressedControllers[0].current.resource : stat.resource; } else { arg = roots.length === 1 ? roots[0].resource : {}; } @@ -665,7 +665,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } private onFocusChanged(elements: readonly ExplorerItem[]): void { - const stat = elements && elements.length ? elements[0] : undefined; + const stat = elements.at(0); this.setContextKeys(stat); if (stat) { @@ -738,7 +738,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } let viewState: IAsyncDataTreeViewState | undefined; - if (this.tree && this.tree.getInput()) { + if (this.tree?.getInput()) { viewState = this.tree.getViewState(); } else { const rawViewState = this.storageService.get(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, StorageScope.WORKSPACE); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index cf13074b632..791c2aa1941 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -170,16 +170,7 @@ export class ExplorerDataSource implements IAsyncDataSource { } const stat = this.explorerService.findClosest(e.resource); - if (stat && stat.isExcluded) { + if (stat?.isExcluded) { // A filtered resource suddenly became visible since user opened an editor shouldFire = true; break; @@ -1419,9 +1410,9 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - const globMatch = cached?.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name))); + const globMatch = cached?.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent?.getChild(name))); // Small optimization to only run isHiddenResource (traverse gitIgnore) if the globMatch from fileExclude returned nothing - const isHiddenResource = !!globMatch ? true : this.isIgnored(stat.resource, stat.root.resource, stat.isDirectory); + const isHiddenResource = globMatch ? true : this.isIgnored(stat.resource, stat.root.resource, stat.isDirectory); if (isHiddenResource || stat.parent?.isExcluded) { stat.isExcluded = true; const editors = this.editorService.visibleEditors; @@ -1781,7 +1772,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData, originalEvent); - if (items && items.length && originalEvent.dataTransfer) { + if (items.length && originalEvent.dataTransfer) { // Apply some datatransfer types to allow for dragging the element outside of the application this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, items, originalEvent)); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index b46e3b12fe2..e276393c2e1 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -321,7 +321,7 @@ export class OpenEditorsView extends ViewPane { dirtyEditorFocusedContext.set(element.editor.isDirty() && !element.editor.isSaving()); readonlyEditorFocusedContext.set(!!element.editor.isReadonly()); resourceContext.set(resource ?? null); - } else if (!!element) { + } else if (element) { groupFocusedContext.set(true); } })); diff --git a/src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts b/src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts index 34c11e94e2a..4e5fa25a2ec 100644 --- a/src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts +++ b/src/vs/workbench/contrib/files/common/explorerFileNestingTrie.ts @@ -110,8 +110,6 @@ export class PreTrie { private map: Map = new Map(); - constructor() { } - add(key: string, value: string) { if (key === '') { this.value.add(key, value); @@ -161,8 +159,6 @@ export class SufTrie { private map: Map = new Map(); hasItems: boolean = false; - constructor() { } - add(key: string, value: string) { this.hasItems = true; if (key === '*') { diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index d62dbd05f11..61aec18cdc7 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -46,7 +46,7 @@ import { ResourceListDnDHandler } from '../../../browser/dnd.js'; import { ResourceLabels } from '../../../browser/labels.js'; import { FilterViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { EditorResourceAccessor, SideBySideEditor } from '../../../common/editor.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { Markers, MarkersContextKeys, MarkersViewMode } from '../common/markers.js'; @@ -67,6 +67,18 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterab }); } +interface IMarkersPanelState { + filter?: string; + filterHistory?: string[]; + showErrors?: boolean; + showWarnings?: boolean; + showInfos?: boolean; + useFilesExclude?: boolean; + activeFile?: boolean; + multiline?: boolean; + viewMode?: MarkersViewMode; +} + export interface IProblemsWidget { get contextKeyService(): IContextKeyService; @@ -115,8 +127,8 @@ export class MarkersView extends FilterViewPane implements IMarkersView { private currentHeight = 0; private currentWidth = 0; - private readonly memento: Memento; - private readonly panelState: MementoObject; + private readonly memento: Memento; + private readonly panelState: IMarkersPanelState; private cachedFilterStats: { total: number; filtered: number } | undefined = undefined; @@ -142,7 +154,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, ) { - const memento = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService); + const memento = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService); const panelState = memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); super({ ...options, @@ -150,15 +162,15 @@ export class MarkersView extends FilterViewPane implements IMarkersView { ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER, focusContextKey: MarkersContextKeys.MarkerViewFilterFocusContextKey.key, - text: panelState['filter'] || '', - history: panelState['filterHistory'] || [] + text: panelState.filter || '', + history: panelState.filterHistory || [] } }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); this.memento = memento; this.panelState = panelState; this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); - this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'], this.panelState['viewMode'] ?? this.getDefaultViewMode())); + this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState.multiline, this.panelState.viewMode ?? this.getDefaultViewMode())); this._register(this.onDidChangeVisibility(visible => this.onDidChangeMarkersViewVisibility(visible))); this._register(this.markersViewModel.onDidChangeViewMode(_ => this.onDidChangeViewMode())); @@ -171,12 +183,12 @@ export class MarkersView extends FilterViewPane implements IMarkersView { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); this.filters = this._register(new MarkersFilters({ - filterHistory: this.panelState['filterHistory'] || [], - showErrors: this.panelState['showErrors'] !== false, - showWarnings: this.panelState['showWarnings'] !== false, - showInfos: this.panelState['showInfos'] !== false, - excludedFiles: !!this.panelState['useFilesExclude'], - activeFile: !!this.panelState['activeFile'], + filterHistory: this.panelState.filterHistory || [], + showErrors: this.panelState.showErrors !== false, + showWarnings: this.panelState.showWarnings !== false, + showInfos: this.panelState.showInfos !== false, + excludedFiles: !!this.panelState.useFilesExclude, + activeFile: !!this.panelState.activeFile, }, this.contextKeyService)); // Update filter, whenever the "files.exclude" setting is changed @@ -884,15 +896,15 @@ export class MarkersView extends FilterViewPane implements IMarkersView { } override saveState(): void { - this.panelState['filter'] = this.filterWidget.getFilterText(); - this.panelState['filterHistory'] = this.filters.filterHistory; - this.panelState['showErrors'] = this.filters.showErrors; - this.panelState['showWarnings'] = this.filters.showWarnings; - this.panelState['showInfos'] = this.filters.showInfos; - this.panelState['useFilesExclude'] = this.filters.excludedFiles; - this.panelState['activeFile'] = this.filters.activeFile; - this.panelState['multiline'] = this.markersViewModel.multiline; - this.panelState['viewMode'] = this.markersViewModel.viewMode; + this.panelState.filter = this.filterWidget.getFilterText(); + this.panelState.filterHistory = this.filters.filterHistory; + this.panelState.showErrors = this.filters.showErrors; + this.panelState.showWarnings = this.filters.showWarnings; + this.panelState.showInfos = this.filters.showInfos; + this.panelState.useFilesExclude = this.filters.excludedFiles; + this.panelState.activeFile = this.filters.activeFile; + this.panelState.multiline = this.markersViewModel.multiline; + this.panelState.viewMode = this.markersViewModel.viewMode; this.memento.saveMemento(); super.saveState(); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts index dea7c33359e..07a95e55229 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts @@ -24,6 +24,11 @@ import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecyc const hasOpenedNotebookKey = 'hasOpenedNotebook'; const hasShownGettingStartedKey = 'hasShownNotebookGettingStarted'; +interface INotebookGettingStartedMemento { + hasOpenedNotebook?: boolean; + hasShownNotebookGettingStarted?: boolean; +} + /** * Sets a context key when a notebook has ever been opened by the user */ @@ -39,7 +44,7 @@ export class NotebookGettingStarted extends Disposable implements IWorkbenchCont super(); const hasOpenedNotebook = HAS_OPENED_NOTEBOOK.bindTo(_contextKeyService); - const memento = new Memento('notebookGettingStarted2', _storageService); + const memento = new Memento('notebookGettingStarted2', _storageService); const storedValue = memento.getMemento(StorageScope.PROFILE, StorageTarget.USER); if (storedValue[hasOpenedNotebookKey]) { hasOpenedNotebook.set(true); @@ -89,7 +94,7 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 { } run(accessor: ServicesAccessor): void { const storageService = accessor.get(IStorageService); - const memento = new Memento('notebookGettingStarted', storageService); + const memento = new Memento('notebookGettingStarted', storageService); const storedValue = memento.getMemento(StorageScope.PROFILE, StorageTarget.USER); storedValue[hasOpenedNotebookKey] = undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKeymapServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKeymapServiceImpl.ts index 0e68bf1e0fd..b0dcc87a4e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKeymapServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKeymapServiceImpl.ts @@ -16,7 +16,7 @@ import { ILifecycleService } from '../../../../services/lifecycle/common/lifecyc import { IExtensionIdentifier, IExtensionManagementService, InstallOperation } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; import { areSameExtensions } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; -import { Memento, MementoObject } from '../../../../common/memento.js'; +import { Memento } from '../../../../common/memento.js'; import { distinct } from '../../../../../base/common/arrays.js'; function onExtensionChanged(accessor: ServicesAccessor): Event { @@ -43,11 +43,15 @@ function onExtensionChanged(accessor: ServicesAccessor): Event; + private notebookKeymap: NotebookKeymapMemento; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 7d7136f41e3..7cb95efe2a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -22,7 +22,7 @@ import { IResourceEditorInput } from '../../../../../platform/editor/common/edit import { IFileService } from '../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; -import { Memento, MementoObject } from '../../../../common/memento.js'; +import { Memento } from '../../../../common/memento.js'; import { INotebookEditorContribution, notebookPreloadExtensionPoint, notebookRendererExtensionPoint, notebooksExtensionPoint } from '../notebookExtensionPoint.js'; import { INotebookEditorOptions } from '../notebookBrowser.js'; import { NotebookDiffEditorInput } from '../../common/notebookDiffEditorInput.js'; @@ -50,12 +50,16 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../base/common/errors.js'; import { ICellRange } from '../../common/notebookRange.js'; +interface NotebookProviderInfoStoreMemento { + editors: NotebookProviderInfo[]; +} + export class NotebookProviderInfoStore extends Disposable { private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors'; private static readonly CUSTOM_EDITORS_ENTRY_ID = 'editors'; - private readonly _memento: Memento; + private readonly _memento: Memento; private _handled: boolean = false; private readonly _contributedEditors = new Map(); @@ -411,10 +415,14 @@ export class NotebookProviderInfoStore extends Disposable { } } +interface NotebookOutputRendererInfoStoreMemento { + [notebookType: string]: { [mimeType: string]: string } | undefined; +} + export class NotebookOutputRendererInfoStore { private readonly contributedRenderers = new Map(); - private readonly preferredMimetypeMemento: Memento; - private readonly preferredMimetype = new Lazy<{ [notebookType: string]: { [mimeType: string]: /* rendererId */ string } }>( + private readonly preferredMimetypeMemento: Memento; + private readonly preferredMimetype = new Lazy( () => this.preferredMimetypeMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE)); constructor( @@ -517,12 +525,16 @@ class ModelData implements IDisposable, INotebookDocument { } } +interface NotebookServiceMemento { + [viewType: string]: string | undefined; +} + export class NotebookService extends Disposable implements INotebookService { declare readonly _serviceBrand: undefined; private static _storageNotebookViewTypeProvider = 'notebook.viewTypeProvider'; - private readonly _memento: Memento; - private readonly _viewTypeCache: MementoObject; + private readonly _memento: Memento; + private readonly _viewTypeCache: NotebookServiceMemento; private readonly _notebookProviders; private _notebookProviderInfoStore: NotebookProviderInfoStore | undefined; diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 934ea7a8bf2..cf69f38c55e 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -44,12 +44,22 @@ import { IEditorContribution, IEditorDecorationsCollection } from '../../../../e import { IModelDeltaDecoration, ITextModel } from '../../../../editor/common/model.js'; import { Range } from '../../../../editor/common/core/range.js'; import { FindDecorations } from '../../../../editor/contrib/find/browser/findDecorations.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { Markers } from '../../markers/common/markers.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { viewFilterSubmenu } from '../../../browser/parts/views/viewFilter.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; +interface IOutputViewState { + filter?: string; + showTrace?: boolean; + showDebug?: boolean; + showInfo?: boolean; + showWarning?: boolean; + showError?: boolean; + categories?: string; +} + export class OutputViewPane extends FilterViewPane { private readonly editor: OutputEditor; @@ -60,8 +70,8 @@ export class OutputViewPane extends FilterViewPane { get scrollLock(): boolean { return !!this.scrollLockContextKey.get(); } set scrollLock(scrollLock: boolean) { this.scrollLockContextKey.set(scrollLock); } - private readonly memento: Memento; - private readonly panelState: MementoObject; + private readonly memento: Memento; + private readonly panelState: IOutputViewState; constructor( options: IViewPaneOptions, @@ -77,14 +87,14 @@ export class OutputViewPane extends FilterViewPane { @IOutputService private readonly outputService: IOutputService, @IStorageService storageService: IStorageService, ) { - const memento = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService); + const memento = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService); const viewState = memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); super({ ...options, filterOptions: { placeholder: localize('outputView.filter.placeholder', "Filter"), focusContextKey: OUTPUT_FILTER_FOCUS_CONTEXT.key, - text: viewState['filter'] || '', + text: viewState.filter || '', history: [] } }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); @@ -92,13 +102,13 @@ export class OutputViewPane extends FilterViewPane { this.panelState = viewState; const filters = outputService.filters; - filters.text = this.panelState['filter'] || ''; - filters.trace = this.panelState['showTrace'] ?? true; - filters.debug = this.panelState['showDebug'] ?? true; - filters.info = this.panelState['showInfo'] ?? true; - filters.warning = this.panelState['showWarning'] ?? true; - filters.error = this.panelState['showError'] ?? true; - filters.categories = this.panelState['categories'] ?? ''; + filters.text = this.panelState.filter || ''; + filters.trace = this.panelState.showTrace ?? true; + filters.debug = this.panelState.showDebug ?? true; + filters.info = this.panelState.showInfo ?? true; + filters.warning = this.panelState.showWarning ?? true; + filters.error = this.panelState.showError ?? true; + filters.categories = this.panelState.categories ?? ''; this.scrollLockContextKey = CONTEXT_OUTPUT_SCROLL_LOCK.bindTo(this.contextKeyService); @@ -202,13 +212,13 @@ export class OutputViewPane extends FilterViewPane { override saveState(): void { const filters = this.outputService.filters; - this.panelState['filter'] = filters.text; - this.panelState['showTrace'] = filters.trace; - this.panelState['showDebug'] = filters.debug; - this.panelState['showInfo'] = filters.info; - this.panelState['showWarning'] = filters.warning; - this.panelState['showError'] = filters.error; - this.panelState['categories'] = filters.categories; + this.panelState.filter = filters.text; + this.panelState.showTrace = filters.trace; + this.panelState.showDebug = filters.debug; + this.panelState.showInfo = filters.info; + this.panelState.showWarning = filters.warning; + this.panelState.showError = filters.error; + this.panelState.categories = filters.categories; this.memento.saveMemento(); super.saveState(); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 5bfdf4536f1..076761078d5 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -67,7 +67,11 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common const $ = DOM.$; -export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorPane { +interface IKeybindingsEditorMemento { + searchHistory?: string[]; +} + +export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorPane { static readonly ID: string = 'workbench.editor.keybindings'; @@ -365,7 +369,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP ariaLabelledBy: 'keybindings-editor-aria-label-element', recordEnter: true, quoteRecordedKeys: true, - history: new Set(this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] ?? []), + history: new Set((this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory ?? []), inputBoxStyles: getInputBoxStyle({ inputBorder: settingsTextInputBorder }) @@ -578,14 +582,14 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP this.renderKeybindingsEntries(this.searchWidget.hasFocus()); this.searchHistoryDelayer.trigger(() => { this.searchWidget.inputBox.addToHistory(); - this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory(); + (this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory = this.searchWidget.inputBox.getHistory(); this.saveState(); }); } public clearKeyboardShortcutSearchHistory(): void { this.searchWidget.inputBox.clearHistory(); - this.getMemento(StorageScope.PROFILE, StorageTarget.USER)['searchHistory'] = this.searchWidget.inputBox.getHistory(); + (this.getMemento(StorageScope.PROFILE, StorageTarget.USER)).searchHistory = this.searchWidget.inputBox.getHistory(); this.saveState(); } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 94cedd1af7e..c90211bf5f9 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -52,7 +52,7 @@ import { ResourceListDnDHandler } from '../../../browser/dnd.js'; import { ResourceLabels } from '../../../browser/labels.js'; import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js'; import { IEditorPane } from '../../../common/editor.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { NotebookEditor } from '../../notebook/browser/notebookEditor.js'; import { ExcludePatternInputWidget, IncludePatternInputWidget } from './patternInputWidget.js'; @@ -93,6 +93,35 @@ export enum SearchViewPosition { Panel } +interface ISearchViewStateQuery { + contentPattern?: string; + replaceText?: string | false; + regex?: boolean; + wholeWords?: boolean; + caseSensitive?: boolean; + filePatterns?: string; + folderExclusions?: string; + folderIncludes?: string; + onlyOpenEditors?: boolean; + queryDetailsExpanded?: string | boolean; + useExcludesAndIgnoreFiles?: boolean; + preserveCase?: boolean; + searchHistory?: string[]; + replaceHistory?: string[]; + isInNotebookMarkdownInput?: boolean; + isInNotebookMarkdownPreview?: boolean; + isInNotebookCellInput?: boolean; + isInNotebookCellOutput?: boolean; +} + +interface ISearchViewState { + query?: ISearchViewStateQuery; + view?: { + showReplace?: boolean; + treeLayout?: boolean; + }; +} + const SEARCH_CANCELLED_MESSAGE = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); const DEBOUNCE_DELAY = 75; export class SearchView extends ViewPane { @@ -104,7 +133,7 @@ export class SearchView extends ViewPane { private container!: HTMLElement; private queryBuilder: QueryBuilder; private viewModel: ISearchModel; - private memento: Memento; + private memento: Memento; private viewletVisible: IContextKey; private inputBoxFocused: IContextKey; @@ -131,7 +160,7 @@ export class SearchView extends ViewPane { private tree!: WorkbenchCompressibleAsyncDataTree; private treeLabels!: ResourceLabels; - private viewletState: MementoObject; + private viewletState: ISearchViewState; private messagesElement!: HTMLElement; private readonly messageDisposables: DisposableStore = new DisposableStore(); private searchWidgetsContainerElement!: HTMLElement; @@ -280,7 +309,7 @@ export class SearchView extends ViewPane { this.triggerQueryDelayer = this._register(new Delayer(0)); this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this); - this.isTreeLayoutViewVisible = this.viewletState['view.treeLayout'] ?? (this.searchConfig.defaultViewMode === ViewMode.Tree); + this.isTreeLayoutViewVisible = this.viewletState.view?.treeLayout ?? (this.searchConfig.defaultViewMode === ViewMode.Tree); this._refreshResultsScheduler = this._register(new RunOnceScheduler(this._updateResults.bind(this), 80)); @@ -431,16 +460,16 @@ export class SearchView extends ViewPane { this.createSearchWidget(this.searchWidgetsContainerElement); const history = this.searchHistoryService.load(); - const filePatterns = this.viewletState['query.filePatterns'] || ''; - const patternExclusions = this.viewletState['query.folderExclusions'] || ''; + const filePatterns = this.viewletState.query?.filePatterns || ''; + const patternExclusions = this.viewletState.query?.folderExclusions || ''; const patternExclusionsHistory: string[] = history.exclude || []; - const patternIncludes = this.viewletState['query.folderIncludes'] || ''; + const patternIncludes = this.viewletState.query?.folderIncludes || ''; const patternIncludesHistory: string[] = history.include || []; - const onlyOpenEditors = this.viewletState['query.onlyOpenEditors'] || false; + const onlyOpenEditors = this.viewletState.query?.onlyOpenEditors || false; - const queryDetailsExpanded = this.viewletState['query.queryDetailsExpanded'] || ''; - const useExcludesAndIgnoreFiles = typeof this.viewletState['query.useExcludesAndIgnoreFiles'] === 'boolean' ? - this.viewletState['query.useExcludesAndIgnoreFiles'] : true; + const queryDetailsExpanded = this.viewletState.query?.queryDetailsExpanded || ''; + const useExcludesAndIgnoreFiles = typeof this.viewletState.query?.useExcludesAndIgnoreFiles === 'boolean' ? + this.viewletState.query.useExcludesAndIgnoreFiles : true; this.queryDetails = dom.append(this.searchWidgetsContainerElement, $('.query-details')); @@ -590,22 +619,21 @@ export class SearchView extends ViewPane { } private createSearchWidget(container: HTMLElement): void { - const contentPattern = this.viewletState['query.contentPattern'] || ''; - const replaceText = this.viewletState['query.replaceText'] || ''; - const isRegex = this.viewletState['query.regex'] === true; - const isWholeWords = this.viewletState['query.wholeWords'] === true; - const isCaseSensitive = this.viewletState['query.caseSensitive'] === true; + const contentPattern = this.viewletState.query?.contentPattern || ''; + const replaceText = this.viewletState.query?.replaceText || ''; + const isRegex = this.viewletState.query?.regex === true; + const isWholeWords = this.viewletState.query?.wholeWords === true; + const isCaseSensitive = this.viewletState.query?.caseSensitive === true; const history = this.searchHistoryService.load(); - const searchHistory = history.search || this.viewletState['query.searchHistory'] || []; - const replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || []; - const showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true; - const preserveCase = this.viewletState['query.preserveCase'] === true; - - const isInNotebookMarkdownInput = this.viewletState['query.isInNotebookMarkdownInput'] ?? true; - const isInNotebookMarkdownPreview = this.viewletState['query.isInNotebookMarkdownPreview'] ?? true; - const isInNotebookCellInput = this.viewletState['query.isInNotebookCellInput'] ?? true; - const isInNotebookCellOutput = this.viewletState['query.isInNotebookCellOutput'] ?? true; + const searchHistory = history.search || this.viewletState.query?.searchHistory || []; + const replaceHistory = history.replace || this.viewletState.query?.replaceHistory || []; + const showReplace = typeof this.viewletState.view?.showReplace === 'boolean' ? this.viewletState.view.showReplace : true; + const preserveCase = this.viewletState.query?.preserveCase === true; + const isInNotebookMarkdownInput = this.viewletState.query?.isInNotebookMarkdownInput ?? true; + const isInNotebookMarkdownPreview = this.viewletState.query?.isInNotebookMarkdownPreview ?? true; + const isInNotebookCellInput = this.viewletState.query?.isInNotebookCellInput ?? true; + const isInNotebookCellOutput = this.viewletState.query?.isInNotebookCellOutput ?? true; this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, { value: contentPattern, @@ -1460,14 +1488,15 @@ export class SearchView extends ViewPane { } toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean, reverse?: boolean): void { - const cls = 'more'; - show = typeof show === 'undefined' ? !this.queryDetails.classList.contains(cls) : Boolean(show); - this.viewletState['query.queryDetailsExpanded'] = show; + show = typeof show === 'undefined' ? !this.queryDetails.classList.contains('more') : Boolean(show); + if (!this.viewletState.query) { + this.viewletState.query = {}; + } + this.viewletState.query.queryDetailsExpanded = show; skipLayout = Boolean(skipLayout); - if (show) { this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true'); - this.queryDetails.classList.add(cls); + this.queryDetails.classList.add('more'); if (moveFocus) { if (reverse) { this.inputPatternExcludes.focus(); @@ -1479,7 +1508,7 @@ export class SearchView extends ViewPane { } } else { this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false'); - this.queryDetails.classList.remove(cls); + this.queryDetails.classList.remove('more'); if (moveFocus) { this.searchWidget.focus(); } @@ -2320,6 +2349,10 @@ export class SearchView extends ViewPane { const useExcludesAndIgnoreFiles = this.inputPatternExcludes?.useExcludesAndIgnoreFiles() ?? true; const preserveCase = this.viewModel.preserveCase; + if (!this.viewletState.query) { + this.viewletState.query = {}; + } + if (this.searchWidget.searchInput) { const isRegex = this.searchWidget.searchInput.getRegex(); const isWholeWords = this.searchWidget.searchInput.getWholeWords(); @@ -2331,27 +2364,32 @@ export class SearchView extends ViewPane { const isInNotebookMarkdownInput = this.searchWidget.getNotebookFilters().markupInput; const isInNotebookMarkdownPreview = this.searchWidget.getNotebookFilters().markupPreview; - this.viewletState['query.contentPattern'] = contentPattern; - this.viewletState['query.regex'] = isRegex; - this.viewletState['query.wholeWords'] = isWholeWords; - this.viewletState['query.caseSensitive'] = isCaseSensitive; + this.viewletState.query.contentPattern = contentPattern; + this.viewletState.query.regex = isRegex; + this.viewletState.query.wholeWords = isWholeWords; + this.viewletState.query.caseSensitive = isCaseSensitive; - this.viewletState['query.isInNotebookMarkdownInput'] = isInNotebookMarkdownInput; - this.viewletState['query.isInNotebookMarkdownPreview'] = isInNotebookMarkdownPreview; - this.viewletState['query.isInNotebookCellInput'] = isInNotebookCellInput; - this.viewletState['query.isInNotebookCellOutput'] = isInNotebookCellOutput; + this.viewletState.query.isInNotebookMarkdownInput = isInNotebookMarkdownInput; + this.viewletState.query.isInNotebookMarkdownPreview = isInNotebookMarkdownPreview; + this.viewletState.query.isInNotebookCellInput = isInNotebookCellInput; + this.viewletState.query.isInNotebookCellOutput = isInNotebookCellOutput; } - this.viewletState['query.folderExclusions'] = patternExcludes; - this.viewletState['query.folderIncludes'] = patternIncludes; - this.viewletState['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles; - this.viewletState['query.preserveCase'] = preserveCase; - this.viewletState['query.onlyOpenEditors'] = onlyOpenEditors; + this.viewletState.query.folderExclusions = patternExcludes; + this.viewletState.query.folderIncludes = patternIncludes; + this.viewletState.query.useExcludesAndIgnoreFiles = useExcludesAndIgnoreFiles; + this.viewletState.query.preserveCase = preserveCase; + this.viewletState.query.onlyOpenEditors = onlyOpenEditors; const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown(); - this.viewletState['view.showReplace'] = isReplaceShown; - this.viewletState['view.treeLayout'] = this.isTreeLayoutViewVisible; - this.viewletState['query.replaceText'] = isReplaceShown && this.searchWidget.getReplaceValue(); + + if (!this.viewletState.view) { + this.viewletState.view = {}; + } + + this.viewletState.view.showReplace = isReplaceShown; + this.viewletState.view.treeLayout = this.isTreeLayoutViewVisible; + this.viewletState.query.replaceText = isReplaceShown && this.searchWidget.getReplaceValue(); this._saveSearchHistoryService(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index e514db6bb60..16046e1e4e7 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -64,7 +64,7 @@ export class SearchEditorInput extends EditorInput { return capabilities; } - private memento: Memento; + private memento: Memento<{ searchConfig: SearchConfiguration }>; private dirty: boolean = false; @@ -362,7 +362,7 @@ export const getOrMakeSearchEditorInput = ( const reuseOldSettings = searchEditorSettings.reusePriorSearchConfiguration; const defaultNumberOfContextLines = searchEditorSettings.defaultNumberOfContextLines; - const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE).searchConfig : {}; + const priorConfig = reuseOldSettings ? new Memento<{ searchConfig?: SearchConfiguration }>(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE).searchConfig ?? {} : {}; const defaultConfig = defaultSearchConfig(); const config = { ...defaultConfig, ...priorConfig, ...existingData.config }; diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 49f49e0c258..936b635aa6c 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -18,7 +18,7 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IWebviewPortMapping } from '../../../../platform/webview/common/webviewPortMapping.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; /** * Set when the find widget in a webview in a webview is visible. @@ -348,8 +348,8 @@ export interface IOverlayWebview extends IWebview { */ export class WebviewOriginStore { - private readonly _memento: Memento; - private readonly _state: MementoObject; + private readonly _memento: Memento>; + private readonly _state: Record; constructor( rootStorageKey: string, diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 5ec6e750839..cfb107654c8 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -20,7 +20,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ViewPane, ViewPaneShowActions } from '../../../browser/parts/views/viewPane.js'; import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { IViewBadge, IViewDescriptorService } from '../../../common/views.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ExtensionKeyedWebviewOriginStore, IOverlayWebview, IWebviewService, WebviewContentPurpose } from '../../webview/browser/webview.js'; @@ -34,6 +34,10 @@ const storageKeys = { webviewState: 'webviewState', } as const; +interface WebviewViewState { + [storageKeys.webviewState]?: string | undefined; +} + export class WebviewViewPane extends ViewPane { private static _originStore?: ExtensionKeyedWebviewOriginStore; @@ -57,8 +61,8 @@ export class WebviewViewPane extends ViewPane { private badge: IViewBadge | undefined; private readonly activity = this._register(new MutableDisposable()); - private readonly memento: Memento; - private readonly viewState: MementoObject; + private readonly memento: Memento; + private readonly viewState: WebviewViewState; private readonly extensionId?: ExtensionIdentifier; private _repositionTimeout?: Timeout; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index ea52ece81c1..d672ea7b95e 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -130,7 +130,7 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ private readonly _onDidProgressStep = new Emitter(); readonly onDidProgressStep: Event = this._onDidProgressStep.event; - private memento: Memento; + private memento: Memento>; private stepProgress: Record; private sessionEvents = new Set(); diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index b542b8d7344..cdea22c73a4 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -21,7 +21,7 @@ import { INativeHostService } from '../../../platform/native/common/native.js'; import { Codicon } from '../../../base/common/codicons.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js'; -import { Action2, IAction2Options, MenuId } from '../../../platform/actions/common/actions.js'; +import { Action2, MenuId } from '../../../platform/actions/common/actions.js'; import { Categories } from '../../../platform/action/common/actionCommonCategories.js'; import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js'; import { KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -70,10 +70,6 @@ abstract class BaseZoomAction extends Action2 { private static readonly ZOOM_LEVEL_SETTING_KEY = 'window.zoomLevel'; private static readonly ZOOM_PER_WINDOW_SETTING_KEY = 'window.zoomPerWindow'; - constructor(desc: Readonly) { - super(desc); - } - protected async setZoomLevel(accessor: ServicesAccessor, levelOrReset: number | true): Promise { const configurationService = accessor.get(IConfigurationService); @@ -220,10 +216,6 @@ abstract class BaseSwitchWindow extends Action2 { alwaysVisible: true }; - constructor(desc: Readonly) { - super(desc); - } - protected abstract isQuickNavigate(): boolean; override async run(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts index 5acf8788abb..bd903f55933 100644 --- a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts @@ -13,7 +13,7 @@ import { INativeWorkbenchEnvironmentService } from '../../../services/environmen import { IHostService } from '../../../services/host/browser/host.js'; import { isMacintosh, isWindows, isLinux, isBigSurOrNewer } from '../../../../base/common/platform.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; -import { BrowserTitlebarPart as BrowserTitlebarPart, BrowserTitleService, IAuxiliaryTitlebarPart } from '../../../browser/parts/titlebar/titlebarPart.js'; +import { BrowserTitlebarPart, BrowserTitleService, IAuxiliaryTitlebarPart } from '../../../browser/parts/titlebar/titlebarPart.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; @@ -125,11 +125,9 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { private onUpdateAppIconDragBehavior(): void { const setting = this.configurationService.getValue('window.doubleClickIconToClose'); if (setting && this.appIcon) { - // eslint-disable-next-line local/code-no-any-casts - (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; + (this.appIcon.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'no-drag'; } else if (this.appIcon) { - // eslint-disable-next-line local/code-no-any-casts - (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; + (this.appIcon.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'drag'; } } diff --git a/src/vs/workbench/services/assignment/common/assignmentService.ts b/src/vs/workbench/services/assignment/common/assignmentService.ts index d57ebc9207b..6d82a59b340 100644 --- a/src/vs/workbench/services/assignment/common/assignmentService.ts +++ b/src/vs/workbench/services/assignment/common/assignmentService.ts @@ -6,7 +6,7 @@ import { localize } from '../../../../nls.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import type { IKeyValueStorage, IExperimentationTelemetry, ExperimentationService as TASClient } from 'tas-client-umd'; -import { MementoObject, Memento } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryData } from '../../../../base/common/actions.js'; @@ -33,14 +33,14 @@ export interface IWorkbenchAssignmentService extends IAssignmentService { class MementoKeyValueStorage implements IKeyValueStorage { - private readonly mementoObj: MementoObject; + private readonly mementoObj: Record; - constructor(private readonly memento: Memento) { + constructor(private readonly memento: Memento>) { this.mementoObj = memento.getMemento(StorageScope.APPLICATION, StorageTarget.MACHINE); } async getValue(key: string, defaultValue?: T | undefined): Promise { - const value = await this.mementoObj[key]; + const value = await this.mementoObj[key] as T | undefined; return value || defaultValue; } @@ -136,7 +136,7 @@ export class WorkbenchAssignmentService extends Disposable implements IAssignmen this.telemetry = this._register(new WorkbenchAssignmentServiceTelemetry(telemetryService, productService)); this._register(this.telemetry.onDidUpdateAssignmentContext(() => this._onDidRefetchAssignments.fire())); - this.keyValueStorage = new MementoKeyValueStorage(new Memento('experiment.service.memento', storageService)); + this.keyValueStorage = new MementoKeyValueStorage(new Memento>('experiment.service.memento', storageService)); // For development purposes, configure the delay until tas local tas treatment ovverrides are available const overrideDelaySetting = configurationService.getValue('experiments.overrideDelay'); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index 443f02dde9f..c99f5309eea 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -237,7 +237,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // Normal Menu Item else { let type: 'radio' | 'checkbox' | undefined = undefined; - if (!!entry.checked) { + if (entry.checked) { if (typeof delegate.getCheckedActionsRepresentation === 'function') { type = delegate.getCheckedActionsRepresentation(entry); } else { @@ -262,7 +262,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } }; - const keybinding = !!delegate.getKeyBinding ? delegate.getKeyBinding(entry) : this.keybindingService.lookupKeybinding(entry.id); + const keybinding = delegate.getKeyBinding ? delegate.getKeyBinding(entry) : this.keybindingService.lookupKeybinding(entry.id); if (keybinding) { const electronAccelerator = keybinding.getElectronAccelerator(); if (electronAccelerator) { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 988cada21b0..20fb991dde8 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -308,7 +308,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } protected getFileSystemSchema(options: { availableFileSystems?: readonly string[]; defaultUri?: URI }): string { - return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(options.defaultUri?.scheme); + return options.availableFileSystems?.[0] || this.getSchemeFilterForWindow(options.defaultUri?.scheme); } abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise; diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 4c7a927bd43..478f44398b8 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -639,7 +639,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog { } catch (e) { // do nothing } - if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { + if (stat?.isDirectory && (resources.basename(valueUri) !== '.') && this.endsWithSlash(value)) { valueUri = this.tryAddTrailingSeparatorToDirectory(valueUri, stat); return await this.updateItems(valueUri) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; } else if (this.endsWithSlash(value)) { @@ -662,7 +662,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog { } catch (e) { // do nothing } - if (statWithoutTrailing && statWithoutTrailing.isDirectory) { + if (statWithoutTrailing?.isDirectory) { this.badPath = undefined; inputUriDirname = this.tryAddTrailingSeparatorToDirectory(inputUriDirname, statWithoutTrailing); return await this.updateItems(inputUriDirname, false, resources.basename(valueUri)) ? UpdateResult.UpdatedWithTrailing : UpdateResult.Updated; @@ -859,7 +859,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog { } if (this.requiresTrailing) { // save - if (stat && stat.isDirectory) { + if (stat?.isDirectory) { // Can't do this this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolder', 'The folder already exists. Please use a new file name.'); return Promise.resolve(false); diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 821ba3e3ae9..51a1ac08b9b 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -605,7 +605,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver }; // If the user has already made a choice for this editor we don't want to ask them again - if (storedChoices[globForResource] && storedChoices[globForResource].find(editorID => editorID === currentEditor.editorId)) { + if (storedChoices[globForResource]?.find(editorID => editorID === currentEditor.editorId)) { return; } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index a50ad52689b..ec1a621d29e 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -70,7 +70,7 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi const result: [string, string][] = []; for (const entry of logLevelFromPayload.split(',')) { const matches = EXTENSION_IDENTIFIER_WITH_LOG_REGEX.exec(entry); - if (matches && matches[1] && matches[2]) { + if (matches?.[1] && matches[2]) { result.push([matches[1], matches[2]]); } } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index dddecccdc52..02e6d4591aa 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -416,7 +416,7 @@ export class BrowserHostService extends Disposable implements IHostService { private preservePayload(isEmptyWindow: boolean, options?: IOpenWindowOptions): Array | undefined { // Selectively copy payload: for now only extension debugging properties are considered - const newPayload: Array = new Array(); + const newPayload: Array = []; if (!isEmptyWindow && this.environmentService.extensionDevelopmentLocationURI) { newPayload.push(['extensionDevelopmentPath', this.environmentService.extensionDevelopmentLocationURI.toString()]); diff --git a/src/vs/workbench/services/host/electron-browser/nativeHostService.ts b/src/vs/workbench/services/host/electron-browser/nativeHostService.ts index cb7dcc810e2..e0e7d669aa2 100644 --- a/src/vs/workbench/services/host/electron-browser/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/nativeHostService.ts @@ -112,7 +112,7 @@ class WorkbenchHostService extends Disposable implements IHostService { private doOpenWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise { const remoteAuthority = this.environmentService.remoteAuthority; - if (!!remoteAuthority) { + if (remoteAuthority) { toOpen.forEach(openable => openable.label = openable.label || this.getRecentLabel(openable)); if (options?.remoteAuthority === undefined) { diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index c1ffd0a11e3..7aa12c81fa2 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -138,7 +138,7 @@ export class LabelService extends Disposable implements ILabelService { private readonly _onDidChangeFormatters = this._register(new Emitter({ leakWarningThreshold: 400 })); readonly onDidChangeFormatters = this._onDidChangeFormatters.event; - private readonly storedFormattersMemento: Memento; + private readonly storedFormattersMemento: Memento; private readonly storedFormatters: IStoredFormatters; private os: OperatingSystem; private userHome: URI | undefined; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index e58cd1d0ddf..a9cb5d55ba2 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -147,7 +147,7 @@ export class ProgressService extends Disposable implements IProgressService { const [options, progress] = this.windowProgressStack[idx]; const progressTitle = options.title; - const progressMessage = progress.value && progress.value.message; + const progressMessage = progress.value?.message; const progressCommand = (options).command; let text: string; let title: string; diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index e0662097ef8..4938ebb966b 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -313,7 +313,7 @@ export class ViewsService extends Disposable implements IViewsService { const compositeDescriptor = this.getComposite(viewContainer.id, location!); if (compositeDescriptor) { const paneComposite = await this.openComposite(compositeDescriptor.id, location!) as IPaneComposite | undefined; - if (paneComposite && paneComposite.openView) { + if (paneComposite?.openView) { return paneComposite.openView(id, focus) || null; } else if (focus) { paneComposite?.focus(); @@ -424,7 +424,7 @@ export class ViewsService extends Disposable implements IViewsService { f1: true }); } - public async run(serviceAccessor: ServicesAccessor): Promise { + public async run(serviceAccessor: ServicesAccessor): Promise { const editorGroupService = serviceAccessor.get(IEditorGroupsService); const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); const layoutService = serviceAccessor.get(IWorkbenchLayoutService); @@ -512,7 +512,7 @@ export class ViewsService extends Disposable implements IViewsService { } }); } - public async run(serviceAccessor: ServicesAccessor, options?: { preserveFocus?: boolean }): Promise { + public async run(serviceAccessor: ServicesAccessor, options?: { preserveFocus?: boolean }): Promise { const editorGroupService = serviceAccessor.get(IEditorGroupsService); const viewDescriptorService = serviceAccessor.get(IViewDescriptorService); const layoutService = serviceAccessor.get(IWorkbenchLayoutService); @@ -686,8 +686,7 @@ export class ViewsService extends Disposable implements IViewsService { } private createViewPaneContainer(element: HTMLElement, viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation, disposables: DisposableStore, instantiationService: IInstantiationService): ViewPaneContainer { - // eslint-disable-next-line local/code-no-any-casts - const viewPaneContainer: ViewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor.ctor, ...(viewContainer.ctorDescriptor.staticArguments || [])); + const viewPaneContainer: ViewPaneContainer = instantiationService.createInstance(viewContainer.ctorDescriptor.ctor, ...(viewContainer.ctorDescriptor.staticArguments || [])); this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); disposables.add(toDisposable(() => this.viewPaneContainers.delete(viewPaneContainer.getId()))); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 017d52d081e..37562ac6420 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -184,7 +184,7 @@ export class FileWorkingCopyManager { const result = await this.saveAs(workingCopy.resource, undefined, options); - return result ? true : false; + return !!result; }, fileService, labelService, logService, workingCopyBackupService, workingCopyService )); diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index c12163d118e..eedb4a2c02c 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -572,7 +572,7 @@ export class StoredFileWorkingCopy extend }, true /* dirty (resolved from backup) */); // Restore orphaned flag based on state - if (backup.meta && backup.meta.orphaned) { + if (backup.meta?.orphaned) { this.setOrphaned(true); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 8ced7d7bbff..f747a3468ab 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -528,10 +528,6 @@ export class InMemoryWorkingCopyBackupService extends Disposable implements IWor private backups = new ResourceMap<{ typeId: string; content: VSBuffer; meta?: IWorkingCopyBackupMeta }>(); - constructor() { - super(); - } - hasBackupSync(identifier: IWorkingCopyIdentifier, versionId?: number): boolean { const backupResource = this.toBackupResource(identifier); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index be885f55a7d..13df68b01a2 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -355,7 +355,7 @@ export abstract class AbstractWorkspaceEditingService extends Disposable impleme abstract enterWorkspace(workspaceUri: URI): Promise; protected async doEnterWorkspace(workspaceUri: URI): Promise { - if (!!this.environmentService.extensionTestsLocationURI) { + if (this.environmentService.extensionTestsLocationURI) { throw new Error('Entering a new workspace is not possible in tests.'); } @@ -381,7 +381,7 @@ export abstract class AbstractWorkspaceEditingService extends Disposable impleme private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - const targetWorkspaceConfiguration: any = {}; + const targetWorkspaceConfiguration: Record = {}; for (const key of this.configurationService.keys().workspace) { if (configurationProperties[key]) { if (filter && !filter(configurationProperties[key])) { diff --git a/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts index b0f2c10fda3..aa2eb2f78dc 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts @@ -16,7 +16,7 @@ import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform export const IWorkspaceIdentityService = createDecorator('IWorkspaceIdentityService'); export interface IWorkspaceIdentityService { _serviceBrand: undefined; - matches(folders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((obj: any) => unknown) | false>; + matches(folders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((obj: unknown) => unknown) | false>; getWorkspaceStateFolders(cancellationToken: CancellationToken): Promise; } @@ -40,7 +40,7 @@ export class WorkspaceIdentityService implements IWorkspaceIdentityService { return workspaceStateFolders; } - async matches(incomingWorkspaceFolders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((value: any) => unknown) | false> { + async matches(incomingWorkspaceFolders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((value: unknown) => unknown) | false> { const incomingToCurrentWorkspaceFolderUris: { [key: string]: string } = {}; const incomingIdentitiesToIncomingWorkspaceFolders: { [key: string]: string } = {}; @@ -107,14 +107,13 @@ export class WorkspaceIdentityService implements IWorkspaceIdentityService { // Recursively look for any URIs in the provided object and // replace them with the URIs of the current workspace folders - const uriReplacer = (obj: any, depth = 0) => { + const uriReplacer = (obj: unknown, depth = 0) => { if (!obj || depth > 200) { return obj; } if (obj instanceof VSBuffer || obj instanceof Uint8Array) { - // eslint-disable-next-line local/code-no-any-casts - return obj; + return obj; } if (URI.isUri(obj)) { @@ -129,7 +128,7 @@ export class WorkspaceIdentityService implements IWorkspaceIdentityService { // walk object for (const key in obj) { if (Object.hasOwnProperty.call(obj, key)) { - obj[key] = uriReplacer(obj[key], depth + 1); + (obj as Record)[key] = uriReplacer((obj as Record)[key], depth + 1); } } } diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index e11ae3c24e2..0ab8fb637dd 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -17,7 +17,7 @@ import { isVirtualResource } from '../../../../platform/workspace/common/virtual import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ISingleFolderWorkspaceIdentifier, isSavedWorkspace, isSingleFolderWorkspaceIdentifier, isTemporaryWorkspace, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, toWorkspaceIdentifier, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse, IWorkspaceTrustEnablementService } from '../../../../platform/workspace/common/workspaceTrust.js'; -import { Memento, MementoObject } from '../../../common/memento.js'; +import { Memento } from '../../../common/memento.js'; import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { isEqualAuthority } from '../../../../base/common/resources.js'; @@ -850,10 +850,15 @@ class WorkspaceTrustTransitionManager extends Disposable { } } +interface WorkspaceTrustMementoData { + acceptsOutOfWorkspaceFiles?: boolean; + isEmptyWorkspaceTrusted?: boolean | undefined; +} + class WorkspaceTrustMemento { - private readonly _memento?: Memento; - private readonly _mementoObject: MementoObject; + private readonly _memento?: Memento; + private readonly _mementoObject: WorkspaceTrustMementoData; private readonly _acceptsOutOfWorkspaceFilesKey = 'acceptsOutOfWorkspaceFiles'; private readonly _isEmptyWorkspaceTrustedKey = 'isEmptyWorkspaceTrusted'; diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts index 5aacdf29e02..cfdb4074f6c 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts @@ -9,7 +9,7 @@ import { InstantiationType, registerSingleton } from '../../../../platform/insta import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { INativeHostService } from '../../../../platform/native/common/native.js'; -// @ts-ignore: interface is implemented via proxy +// @ts-expect-error: interface is implemented via proxy export class NativeWorkspacesService implements IWorkspacesService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a12332aba7d..f9a599137ff 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1715,12 +1715,12 @@ export class TestEditorPart extends MainEditorPart implements IEditorGroupsServi } clearState(): void { - const workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + const workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as Record; for (const key of Object.keys(workspaceMemento)) { delete workspaceMemento[key]; } - const profileMemento = this.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); + const profileMemento = this.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE) as Record; for (const key of Object.keys(profileMemento)) { delete profileMemento[key]; } diff --git a/src/vs/workbench/test/common/memento.test.ts b/src/vs/workbench/test/common/memento.test.ts index 6b7a565e931..389d3d624ba 100644 --- a/src/vs/workbench/test/common/memento.test.ts +++ b/src/vs/workbench/test/common/memento.test.ts @@ -26,7 +26,7 @@ suite('Memento', () => { }); test('Loading and Saving Memento with Scopes', () => { - const myMemento = new Memento('memento.test', storage); + const myMemento = new Memento<{ foo: number[] | string }>('memento.test', storage); // Application let memento = myMemento.getMemento(StorageScope.APPLICATION, StorageTarget.MACHINE); @@ -101,7 +101,7 @@ suite('Memento', () => { }); test('Save and Load', () => { - const myMemento = new Memento('memento.test', storage); + const myMemento = new Memento<{ foo: number[] | string }>('memento.test', storage); // Profile let memento = myMemento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); @@ -165,8 +165,8 @@ suite('Memento', () => { }); test('Save and Load - 2 Components with same id', () => { - const myMemento = new Memento('memento.test', storage); - const myMemento2 = new Memento('memento.test', storage); + const myMemento = new Memento('memento.test', storage); + const myMemento2 = new Memento('memento.test', storage); // Profile let memento = myMemento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); @@ -207,7 +207,7 @@ suite('Memento', () => { }); test('Clear Memento', () => { - let myMemento = new Memento('memento.test', storage); + let myMemento = new Memento<{ foo: string; bar: string }>('memento.test', storage); // Profile let profileMemento = myMemento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); From 78a5f35f17f5de76074bd87d7698ac15d7d8330a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:28:35 -0700 Subject: [PATCH 0826/4355] Pick up latest dompurify For #269674 --- package-lock.json | 11 +- package.json | 2 +- src/vs/base/browser/domSanitize.ts | 14 +- src/vs/base/browser/dompurify/cgmanifest.json | 4 +- src/vs/base/browser/dompurify/dompurify.d.ts | 567 ++++++++++++----- src/vs/base/browser/dompurify/dompurify.js | 573 ++++++------------ 6 files changed, 636 insertions(+), 535 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b4836edeb1..bc2c719b5b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,7 @@ "@types/node": "22.x", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", "@types/wicg-file-system-access": "^2020.9.6", @@ -2155,10 +2155,11 @@ "dev": true }, "node_modules/@types/trusted-types": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", - "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", - "dev": true + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/tunnel": { "version": "0.0.3", diff --git a/package.json b/package.json index d62c6643171..4d8ac853e05 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "@types/node": "22.x", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", "@types/wicg-file-system-access": "^2020.9.6", diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 9b88e3c0dfa..506c8c02435 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -6,7 +6,7 @@ import { Schemas } from '../common/network.js'; import { reset } from './dom.js'; // eslint-disable-next-line no-restricted-imports -import dompurify from './dompurify/dompurify.js'; +import dompurify, * as DomPurifyTypes from './dompurify/dompurify.js'; /** * List of safe, non-input html tags. @@ -225,7 +225,7 @@ const defaultDomPurifyConfig = Object.freeze({ ALLOWED_ATTR: [...defaultAllowedAttrs], // We sanitize the src/href attributes later if needed ALLOW_UNKNOWN_PROTOCOLS: true, -} satisfies dompurify.Config); +} satisfies DomPurifyTypes.Config); /** * Sanitizes an html string. @@ -243,7 +243,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'trusted'): TrustedHTML; function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom' | 'trusted'): TrustedHTML | DocumentFragment { try { - const resolvedConfig: dompurify.Config = { ...defaultDomPurifyConfig }; + const resolvedConfig: DomPurifyTypes.Config = { ...defaultDomPurifyConfig }; if (config?.allowedTags) { if (config.allowedTags.override) { @@ -339,7 +339,11 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine const selfClosingTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; -function replaceWithPlainTextHook(element: Element, data: dompurify.SanitizeElementHookEvent, _config: dompurify.Config) { +const replaceWithPlainTextHook: DomPurifyTypes.UponSanitizeElementHook = (element, data, _config) => { + if (!(element instanceof Element)) { + return; + } + if (!data.allowedTags[data.tagName] && data.tagName !== 'body') { const replacement = convertTagToPlaintext(element); if (element.nodeType === Node.COMMENT_NODE) { @@ -351,7 +355,7 @@ function replaceWithPlainTextHook(element: Element, data: dompurify.SanitizeElem element.parentElement?.replaceChild(replacement, element); } } -} +}; export function convertTagToPlaintext(element: Element): DocumentFragment { let startTagText: string; diff --git a/src/vs/base/browser/dompurify/cgmanifest.json b/src/vs/base/browser/dompurify/cgmanifest.json index 0038d843f74..3e623ee66f5 100644 --- a/src/vs/base/browser/dompurify/cgmanifest.json +++ b/src/vs/base/browser/dompurify/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "dompurify", "repositoryUrl": "https://github.com/cure53/DOMPurify", - "commitHash": "15f54ed66d99a824d8d3030f711ff82af68ad191" + "commitHash": "eaa0bdb26a1d0164af587d9059b98269008faece" } }, "license": "Apache 2.0", - "version": "3.1.7" + "version": "3.2.7" } ], "version": 1 diff --git a/src/vs/base/browser/dompurify/dompurify.d.ts b/src/vs/base/browser/dompurify/dompurify.d.ts index 2c5b6396d8a..f6a8b19745d 100644 --- a/src/vs/base/browser/dompurify/dompurify.d.ts +++ b/src/vs/base/browser/dompurify/dompurify.d.ts @@ -1,146 +1,439 @@ -// Type definitions for DOM Purify 3.0 -// Project: https://github.com/cure53/DOMPurify -// Definitions by: Dave Taylor https://github.com/davetayls -// Samira Bazuzi -// FlowCrypt -// Exigerr -// Piotr Błażejewicz -// Nicholas Ellul -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Minimum TypeScript Version: 4.5 +/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */ -export default DOMPurify; +import type { TrustedTypePolicy, TrustedHTML, TrustedTypesWindow } from 'trusted-types/lib/index.d.ts'; -declare const DOMPurify: createDOMPurifyI; - -type WindowLike = Pick< - typeof globalThis, - | 'NodeFilter' - | 'Node' - | 'Element' - | 'HTMLTemplateElement' - | 'DocumentFragment' - | 'HTMLFormElement' - | 'DOMParser' - | 'NamedNodeMap' ->; - -interface createDOMPurifyI extends DOMPurify.DOMPurifyI { - (window?: Window | WindowLike): DOMPurify.DOMPurifyI; -} - -declare namespace DOMPurify { - interface DOMPurifyI { - sanitize(source: string | Node): string; - sanitize(source: string | Node, config: Config & { RETURN_TRUSTED_TYPE: true }): TrustedHTML; - sanitize( - source: string | Node, - config: Config & { RETURN_DOM_FRAGMENT?: false | undefined; RETURN_DOM?: false | undefined }, - ): string; - sanitize(source: string | Node, config: Config & { RETURN_DOM_FRAGMENT: true }): DocumentFragment; - sanitize(source: string | Node, config: Config & { RETURN_DOM: true }): HTMLElement; - sanitize(source: string | Node, config: Config): string | HTMLElement | DocumentFragment; - - addHook( - hook: 'uponSanitizeElement', - cb: (currentNode: Element, data: SanitizeElementHookEvent, config: Config) => void, - ): void; - addHook( - hook: 'uponSanitizeAttribute', - cb: (currentNode: Element, data: SanitizeAttributeHookEvent, config: Config) => void, - ): void; - addHook(hook: HookName, cb: (currentNode: Element, data: HookEvent, config: Config) => void): void; - - setConfig(cfg: Config): void; - clearConfig(): void; - isValidAttribute(tag: string, attr: string, value: string): boolean; - - removeHook(entryPoint: HookName): void; - removeHooks(entryPoint: HookName): void; - removeAllHooks(): void; - - version: string; - removed: any[]; - isSupported: boolean; - } - - interface Config { - ADD_ATTR?: string[] | undefined; - ADD_DATA_URI_TAGS?: string[] | undefined; - ADD_TAGS?: string[] | undefined; - ADD_URI_SAFE_ATTR?: string[] | undefined; - ALLOW_ARIA_ATTR?: boolean | undefined; - ALLOW_DATA_ATTR?: boolean | undefined; - ALLOW_UNKNOWN_PROTOCOLS?: boolean | undefined; - ALLOW_SELF_CLOSE_IN_ATTR?: boolean | undefined; - ALLOWED_ATTR?: string[] | undefined; - ALLOWED_TAGS?: string[] | undefined; - ALLOWED_NAMESPACES?: string[] | undefined; - ALLOWED_URI_REGEXP?: RegExp | undefined; - FORBID_ATTR?: string[] | undefined; - FORBID_CONTENTS?: string[] | undefined; - FORBID_TAGS?: string[] | undefined; - FORCE_BODY?: boolean | undefined; - IN_PLACE?: boolean | undefined; - KEEP_CONTENT?: boolean | undefined; +/** + * Configuration to control DOMPurify behavior. + */ +interface Config { + /** + * Extend the existing array of allowed attributes. + */ + ADD_ATTR?: string[] | undefined; + /** + * Extend the existing array of elements that can use Data URIs. + */ + ADD_DATA_URI_TAGS?: string[] | undefined; + /** + * Extend the existing array of allowed tags. + */ + ADD_TAGS?: string[] | undefined; + /** + * Extend the existing array of elements that are safe for URI-like values (be careful, XSS risk). + */ + ADD_URI_SAFE_ATTR?: string[] | undefined; + /** + * Allow ARIA attributes, leave other safe HTML as is (default is true). + */ + ALLOW_ARIA_ATTR?: boolean | undefined; + /** + * Allow HTML5 data attributes, leave other safe HTML as is (default is true). + */ + ALLOW_DATA_ATTR?: boolean | undefined; + /** + * Allow external protocol handlers in URL attributes (default is false, be careful, XSS risk). + * By default only `http`, `https`, `ftp`, `ftps`, `tel`, `mailto`, `callto`, `sms`, `cid` and `xmpp` are allowed. + */ + ALLOW_UNKNOWN_PROTOCOLS?: boolean | undefined; + /** + * Decide if self-closing tags in attributes are allowed. + * Usually removed due to a mXSS issue in jQuery 3.0. + */ + ALLOW_SELF_CLOSE_IN_ATTR?: boolean | undefined; + /** + * Allow only specific attributes. + */ + ALLOWED_ATTR?: string[] | undefined; + /** + * Allow only specific elements. + */ + ALLOWED_TAGS?: string[] | undefined; + /** + * Allow only specific namespaces. Defaults to: + * - `http://www.w3.org/1999/xhtml` + * - `http://www.w3.org/2000/svg` + * - `http://www.w3.org/1998/Math/MathML` + */ + ALLOWED_NAMESPACES?: string[] | undefined; + /** + * Allow specific protocols handlers in URL attributes via regex (be careful, XSS risk). + * Default RegExp: + * ``` + * /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; + * ``` + */ + ALLOWED_URI_REGEXP?: RegExp | undefined; + /** + * Define how custom elements are handled. + */ + CUSTOM_ELEMENT_HANDLING?: { /** - * change the default namespace from HTML to something different + * Regular expression or function to match to allowed elements. + * Default is null (disallow any custom elements). */ - NAMESPACE?: string | undefined; - PARSER_MEDIA_TYPE?: string | undefined; - RETURN_DOM_FRAGMENT?: boolean | undefined; + tagNameCheck?: RegExp | ((tagName: string) => boolean) | null | undefined; /** - * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false` - * might cause XSS from attacks hidden in closed shadowroots in case the browser - * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/ + * Regular expression or function to match to allowed attributes. + * Default is null (disallow any attributes not on the allow list). */ - RETURN_DOM_IMPORT?: boolean | undefined; - RETURN_DOM?: boolean | undefined; - RETURN_TRUSTED_TYPE?: boolean | undefined; - SAFE_FOR_TEMPLATES?: boolean | undefined; - SANITIZE_DOM?: boolean | undefined; - /** @default false */ - SANITIZE_NAMED_PROPS?: boolean | undefined; - USE_PROFILES?: - | false - | { - mathMl?: boolean | undefined; - svg?: boolean | undefined; - svgFilters?: boolean | undefined; - html?: boolean | undefined; - } - | undefined; - WHOLE_DOCUMENT?: boolean | undefined; - CUSTOM_ELEMENT_HANDLING?: { - tagNameCheck?: RegExp | ((tagName: string) => boolean) | null | undefined; - attributeNameCheck?: RegExp | ((lcName: string) => boolean) | null | undefined; - allowCustomizedBuiltInElements?: boolean | undefined; - }; - } - - type HookName = - | 'beforeSanitizeElements' - | 'uponSanitizeElement' - | 'afterSanitizeElements' - | 'beforeSanitizeAttributes' - | 'uponSanitizeAttribute' - | 'afterSanitizeAttributes' - | 'beforeSanitizeShadowDOM' - | 'uponSanitizeShadowNode' - | 'afterSanitizeShadowDOM'; - - type HookEvent = SanitizeElementHookEvent | SanitizeAttributeHookEvent | null; + attributeNameCheck?: RegExp | ((attributeName: string, tagName?: string) => boolean) | null | undefined; + /** + * Allow custom elements derived from built-ins if they pass `tagNameCheck`. Default is false. + */ + allowCustomizedBuiltInElements?: boolean | undefined; + }; + /** + * Add attributes to block-list. + */ + FORBID_ATTR?: string[] | undefined; + /** + * Add child elements to be removed when their parent is removed. + */ + FORBID_CONTENTS?: string[] | undefined; + /** + * Add elements to block-list. + */ + FORBID_TAGS?: string[] | undefined; + /** + * Glue elements like style, script or others to `document.body` and prevent unintuitive browser behavior in several edge-cases (default is false). + */ + FORCE_BODY?: boolean | undefined; + /** + * Map of non-standard HTML element names to support. Map to true to enable support. For example: + * + * ``` + * HTML_INTEGRATION_POINTS: { foreignobject: true } + * ``` + */ + HTML_INTEGRATION_POINTS?: Record | undefined; + /** + * Sanitize a node "in place", which is much faster depending on how you use DOMPurify. + */ + IN_PLACE?: boolean | undefined; + /** + * Keep an element's content when the element is removed (default is true). + */ + KEEP_CONTENT?: boolean | undefined; + /** + * Map of MathML element names to support. Map to true to enable support. For example: + * + * ``` + * MATHML_TEXT_INTEGRATION_POINTS: { mtext: true } + * ``` + */ + MATHML_TEXT_INTEGRATION_POINTS?: Record | undefined; + /** + * Change the default namespace from HTML to something different. + */ + NAMESPACE?: string | undefined; + /** + * Change the parser type so sanitized data is treated as XML and not as HTML, which is the default. + */ + PARSER_MEDIA_TYPE?: DOMParserSupportedType | undefined; + /** + * Return a DOM `DocumentFragment` instead of an HTML string (default is false). + */ + RETURN_DOM_FRAGMENT?: boolean | undefined; + /** + * Return a DOM `HTMLBodyElement` instead of an HTML string (default is false). + */ + RETURN_DOM?: boolean | undefined; + /** + * Return a TrustedHTML object instead of a string if possible. + */ + RETURN_TRUSTED_TYPE?: boolean | undefined; + /** + * Strip `{{ ... }}`, `${ ... }` and `<% ... %>` to make output safe for template systems. + * Be careful please, this mode is not recommended for production usage. + * Allowing template parsing in user-controlled HTML is not advised at all. + * Only use this mode if there is really no alternative. + */ + SAFE_FOR_TEMPLATES?: boolean | undefined; + /** + * Change how e.g. comments containing risky HTML characters are treated. + * Be very careful, this setting should only be set to `false` if you really only handle + * HTML and nothing else, no SVG, MathML or the like. + * Otherwise, changing from `true` to `false` will lead to XSS in this or some other way. + */ + SAFE_FOR_XML?: boolean | undefined; + /** + * Use DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here). + */ + SANITIZE_DOM?: boolean | undefined; + /** + * Enforce strict DOM Clobbering protection via namespace isolation (default is false). + * When enabled, isolates the namespace of named properties (i.e., `id` and `name` attributes) + * from JS variables by prefixing them with the string `user-content-` + */ + SANITIZE_NAMED_PROPS?: boolean | undefined; + /** + * Supplied policy must define `createHTML` and `createScriptURL`. + */ + TRUSTED_TYPES_POLICY?: TrustedTypePolicy | undefined; + /** + * Controls categories of allowed elements. + * + * Note that the `USE_PROFILES` setting will override the `ALLOWED_TAGS` setting + * so don't use them together. + */ + USE_PROFILES?: false | UseProfilesConfig | undefined; + /** + * Return entire document including tags (default is false). + */ + WHOLE_DOCUMENT?: boolean | undefined; +} +/** + * Defines categories of allowed elements. + */ +interface UseProfilesConfig { + /** + * Allow all safe MathML elements. + */ + mathMl?: boolean | undefined; + /** + * Allow all safe SVG elements. + */ + svg?: boolean | undefined; + /** + * Allow all save SVG Filters. + */ + svgFilters?: boolean | undefined; + /** + * Allow all safe HTML elements. + */ + html?: boolean | undefined; +} - interface SanitizeElementHookEvent { - tagName: string; - allowedTags: { [key: string]: boolean }; - } +declare const _default: DOMPurify; - interface SanitizeAttributeHookEvent { - attrName: string; - attrValue: string; - keepAttr: boolean; - allowedAttributes: { [key: string]: boolean }; - forceKeepAttr?: boolean | undefined; - } +interface DOMPurify { + /** + * Creates a DOMPurify instance using the given window-like object. Defaults to `window`. + */ + (root?: WindowLike): DOMPurify; + /** + * Version label, exposed for easier checks + * if DOMPurify is up to date or not + */ + version: string; + /** + * Array of elements that DOMPurify removed during sanitation. + * Empty if nothing was removed. + */ + removed: Array; + /** + * Expose whether this browser supports running the full DOMPurify. + */ + isSupported: boolean; + /** + * Set the configuration once. + * + * @param cfg configuration object + */ + setConfig(cfg?: Config): void; + /** + * Removes the configuration. + */ + clearConfig(): void; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized TrustedHTML. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_TRUSTED_TYPE: true; + }): TrustedHTML; + /** + * Provides core sanitation functionality. + * + * @param dirty DOM node + * @param cfg object + * @returns Sanitized DOM node. + */ + sanitize(dirty: Node, cfg: Config & { + IN_PLACE: true; + }): Node; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized DOM node. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_DOM: true; + }): Node; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized document fragment. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_DOM_FRAGMENT: true; + }): DocumentFragment; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized string. + */ + sanitize(dirty: string | Node, cfg?: Config): string; + /** + * Checks if an attribute value is valid. + * Uses last set config, if any. Otherwise, uses config defaults. + * + * @param tag Tag name of containing element. + * @param attr Attribute name. + * @param value Attribute value. + * @returns Returns true if `value` is valid. Otherwise, returns false. + */ + isValidAttribute(tag: string, attr: string, value: string): boolean; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: DocumentFragmentHookName, hookFunction: DocumentFragmentHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: 'uponSanitizeElement', hookFunction: UponSanitizeElementHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: 'uponSanitizeAttribute', hookFunction: UponSanitizeAttributeHook): void; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: BasicHookName, hookFunction?: NodeHook): NodeHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: ElementHookName, hookFunction?: ElementHook): ElementHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: DocumentFragmentHookName, hookFunction?: DocumentFragmentHook): DocumentFragmentHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: 'uponSanitizeElement', hookFunction?: UponSanitizeElementHook): UponSanitizeElementHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: 'uponSanitizeAttribute', hookFunction?: UponSanitizeAttributeHook): UponSanitizeAttributeHook | undefined; + /** + * Removes all DOMPurify hooks at a given entryPoint + * + * @param entryPoint entry point for the hooks to remove + */ + removeHooks(entryPoint: HookName): void; + /** + * Removes all DOMPurify hooks. + */ + removeAllHooks(): void; } +/** + * An element removed by DOMPurify. + */ +interface RemovedElement { + /** + * The element that was removed. + */ + element: Node; +} +/** + * An element removed by DOMPurify. + */ +interface RemovedAttribute { + /** + * The attribute that was removed. + */ + attribute: Attr | null; + /** + * The element that the attribute was removed. + */ + from: Node; +} +type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'uponSanitizeShadowNode'; +type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes'; +type DocumentFragmentHookName = 'beforeSanitizeShadowDOM' | 'afterSanitizeShadowDOM'; +type UponSanitizeElementHookName = 'uponSanitizeElement'; +type UponSanitizeAttributeHookName = 'uponSanitizeAttribute'; +type HookName = BasicHookName | ElementHookName | DocumentFragmentHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName; +type NodeHook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void; +type ElementHook = (this: DOMPurify, currentNode: Element, hookEvent: null, config: Config) => void; +type DocumentFragmentHook = (this: DOMPurify, currentNode: DocumentFragment, hookEvent: null, config: Config) => void; +type UponSanitizeElementHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeElementHookEvent, config: Config) => void; +type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Element, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void; +interface UponSanitizeElementHookEvent { + tagName: string; + allowedTags: Record; +} +interface UponSanitizeAttributeHookEvent { + attrName: string; + attrValue: string; + keepAttr: boolean; + allowedAttributes: Record; + forceKeepAttr: boolean | undefined; +} +/** + * A `Window`-like object containing the properties and types that DOMPurify requires. + */ +type WindowLike = Pick & { + document?: Document; + MozNamedAttrMap?: typeof window.NamedNodeMap; +} & Pick; + +export { type Config, type DOMPurify, type DocumentFragmentHook, type ElementHook, type HookName, type NodeHook, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike, _default as default }; diff --git a/src/vs/base/browser/dompurify/dompurify.js b/src/vs/base/browser/dompurify/dompurify.js index af102433b9c..c0dbc8f1cab 100644 --- a/src/vs/base/browser/dompurify/dompurify.js +++ b/src/vs/base/browser/dompurify/dompurify.js @@ -1,4 +1,4 @@ -/*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */ +/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */ const { entries, @@ -27,18 +27,26 @@ if (!seal) { }; } if (!apply) { - apply = function apply(fun, thisValue, args) { - return fun.apply(thisValue, args); + apply = function apply(func, thisArg) { + for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + args[_key - 2] = arguments[_key]; + } + return func.apply(thisArg, args); }; } if (!construct) { - construct = function construct(Func, args) { + construct = function construct(Func) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } return new Func(...args); }; } const arrayForEach = unapply(Array.prototype.forEach); +const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf); const arrayPop = unapply(Array.prototype.pop); const arrayPush = unapply(Array.prototype.push); +const arraySplice = unapply(Array.prototype.splice); const stringToLowerCase = unapply(String.prototype.toLowerCase); const stringToString = unapply(String.prototype.toString); const stringMatch = unapply(String.prototype.match); @@ -48,44 +56,44 @@ const stringTrim = unapply(String.prototype.trim); const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty); const regExpTest = unapply(RegExp.prototype.test); const typeErrorCreate = unconstruct(TypeError); - /** * Creates a new function that calls the given function with a specified thisArg and arguments. * - * @param {Function} func - The function to be wrapped and called. - * @returns {Function} A new function that calls the given function with a specified thisArg and arguments. + * @param func - The function to be wrapped and called. + * @returns A new function that calls the given function with a specified thisArg and arguments. */ function unapply(func) { return function (thisArg) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; + if (thisArg instanceof RegExp) { + thisArg.lastIndex = 0; + } + for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { + args[_key3 - 1] = arguments[_key3]; } return apply(func, thisArg, args); }; } - /** * Creates a new function that constructs an instance of the given constructor function with the provided arguments. * - * @param {Function} func - The constructor function to be wrapped and called. - * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments. + * @param func - The constructor function to be wrapped and called. + * @returns A new function that constructs an instance of the given constructor function with the provided arguments. */ -function unconstruct(func) { +function unconstruct(Func) { return function () { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; } - return construct(func, args); + return construct(Func, args); }; } - /** * Add properties to a lookup table * - * @param {Object} set - The set to which elements will be added. - * @param {Array} array - The array containing elements to be added to the set. - * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set. - * @returns {Object} The modified set with added elements. + * @param set - The set to which elements will be added. + * @param array - The array containing elements to be added to the set. + * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set. + * @returns The modified set with added elements. */ function addToSet(set, array) { let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase; @@ -112,12 +120,11 @@ function addToSet(set, array) { } return set; } - /** * Clean up an array to harden against CSPP * - * @param {Array} array - The array to be cleaned. - * @returns {Array} The cleaned version of the array + * @param array - The array to be cleaned. + * @returns The cleaned version of the array */ function cleanArray(array) { for (let index = 0; index < array.length; index++) { @@ -128,12 +135,11 @@ function cleanArray(array) { } return array; } - /** * Shallow clone an object * - * @param {Object} object - The object to be cloned. - * @returns {Object} A new object that copies the original. + * @param object - The object to be cloned. + * @returns A new object that copies the original. */ function clone(object) { const newObject = create(null); @@ -151,13 +157,12 @@ function clone(object) { } return newObject; } - /** * This method automatically checks if the prop is function or getter and behaves accordingly. * - * @param {Object} object - The object to look up the getter function in its prototype chain. - * @param {String} prop - The property name for which to find the getter function. - * @returns {Function} The getter function found in the prototype chain or a fallback function. + * @param object - The object to look up the getter function in its prototype chain. + * @param prop - The property name for which to find the getter function. + * @returns The getter function found in the prototype chain or a fallback function. */ function lookupGetter(object, prop) { while (object !== null) { @@ -178,25 +183,21 @@ function lookupGetter(object, prop) { return fallbackValue; } -const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); - -// SVG -const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); +const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); +const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'slot', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); - // List of SVG elements that are disallowed by default. // We still need to know them so that we can do namespace // checks properly in case one wants to add them to // allow-list. const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']); - // Similarly to SVG, we want to know all MathML elements, // even those that we disallow by default. const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); const text = freeze(['#text']); -const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']); +const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']); const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']); const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); @@ -204,10 +205,10 @@ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:x // eslint-disable-next-line unicorn/better-regex const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); -const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); -const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape +const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex +const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape -const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape +const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape ); const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex @@ -217,18 +218,19 @@ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i); var EXPRESSIONS = /*#__PURE__*/Object.freeze({ __proto__: null, - MUSTACHE_EXPR: MUSTACHE_EXPR, - ERB_EXPR: ERB_EXPR, - TMPLIT_EXPR: TMPLIT_EXPR, - DATA_ATTR: DATA_ATTR, ARIA_ATTR: ARIA_ATTR, - IS_ALLOWED_URI: IS_ALLOWED_URI, - IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, ATTR_WHITESPACE: ATTR_WHITESPACE, + CUSTOM_ELEMENT: CUSTOM_ELEMENT, + DATA_ATTR: DATA_ATTR, DOCTYPE_NAME: DOCTYPE_NAME, - CUSTOM_ELEMENT: CUSTOM_ELEMENT + ERB_EXPR: ERB_EXPR, + IS_ALLOWED_URI: IS_ALLOWED_URI, + IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, + MUSTACHE_EXPR: MUSTACHE_EXPR, + TMPLIT_EXPR: TMPLIT_EXPR }); +/* eslint-disable @typescript-eslint/indent */ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType const NODE_TYPE = { element: 1, @@ -249,20 +251,18 @@ const NODE_TYPE = { const getGlobal = function getGlobal() { return typeof window === 'undefined' ? null : window; }; - /** * Creates a no-op policy for internal use only. * Don't export this function outside this module! - * @param {TrustedTypePolicyFactory} trustedTypes The policy factory. - * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). - * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types + * @param trustedTypes The policy factory. + * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). + * @return The policy created (or null, if Trusted Types * are not supported or creating the policy failed). */ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) { if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') { return null; } - // Allow the callers to control the unique policy name // by adding a data-tt-policy-suffix to the script element with the DOMPurify. // Policy creation with duplicate names throws in Trusted Types. @@ -289,22 +289,25 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType return null; } }; +const _createHooksMap = function _createHooksMap() { + return { + afterSanitizeAttributes: [], + afterSanitizeElements: [], + afterSanitizeShadowDOM: [], + beforeSanitizeAttributes: [], + beforeSanitizeElements: [], + beforeSanitizeShadowDOM: [], + uponSanitizeAttribute: [], + uponSanitizeElement: [], + uponSanitizeShadowNode: [] + }; +}; function createDOMPurify() { let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); const DOMPurify = root => createDOMPurify(root); - - /** - * Version label, exposed for easier checks - * if DOMPurify is up to date or not - */ - DOMPurify.version = '3.1.7'; - - /** - * Array of elements that DOMPurify removed during sanitation. - * Empty if nothing was removed. - */ + DOMPurify.version = '3.2.7'; DOMPurify.removed = []; - if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) { + if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) { // Not running in a browser, provide a factory function // so that you can pass your own Window DOMPurify.isSupported = false; @@ -332,7 +335,6 @@ function createDOMPurify() { const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); const getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); const getParentNode = lookupGetter(ElementPrototype, 'parentNode'); - // As per issue #47, the web-components registry is inherited by a // new document created via createHTMLDocument. As per the spec // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) @@ -356,8 +358,7 @@ function createDOMPurify() { const { importNode } = originalDocument; - let hooks = {}; - + let hooks = _createHooksMap(); /** * Expose whether this browser supports running the full DOMPurify. */ @@ -375,22 +376,18 @@ function createDOMPurify() { let { IS_ALLOWED_URI: IS_ALLOWED_URI$1 } = EXPRESSIONS; - /** * We consider the elements and attributes below to be safe. Ideally * don't add any new ones but feel free to remove unwanted ones. */ - /* allowed element names */ let ALLOWED_TAGS = null; const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]); - /* Allowed attribute names */ let ALLOWED_ATTR = null; const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]); - /* - * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. + * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements. * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. @@ -415,65 +412,49 @@ function createDOMPurify() { value: false } })); - /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ let FORBID_TAGS = null; - /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ let FORBID_ATTR = null; - /* Decide if ARIA attributes are okay */ let ALLOW_ARIA_ATTR = true; - /* Decide if custom data attributes are okay */ let ALLOW_DATA_ATTR = true; - /* Decide if unknown protocols are okay */ let ALLOW_UNKNOWN_PROTOCOLS = false; - /* Decide if self-closing tags in attributes are allowed. * Usually removed due to a mXSS issue in jQuery 3.0 */ let ALLOW_SELF_CLOSE_IN_ATTR = true; - /* Output should be safe for common template engines. * This means, DOMPurify removes data attributes, mustaches and ERB */ let SAFE_FOR_TEMPLATES = false; - /* Output should be safe even for XML used within HTML and alike. * This means, DOMPurify removes comments when containing risky content. */ let SAFE_FOR_XML = true; - /* Decide if document with ... should be returned */ let WHOLE_DOCUMENT = false; - /* Track whether config is already set on this instance of DOMPurify. */ let SET_CONFIG = false; - /* Decide if all elements (e.g. style, script) must be children of * document.body. By default, browsers might move them to document.head */ let FORCE_BODY = false; - /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported). * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead */ let RETURN_DOM = false; - /* Decide if a DOM `DocumentFragment` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported) */ let RETURN_DOM_FRAGMENT = false; - /* Try to return a Trusted Type object instead of a string, return a string in * case Trusted Types are not supported */ let RETURN_TRUSTED_TYPE = false; - /* Output should be free from DOM clobbering attacks? * This sanitizes markups named with colliding, clobberable built-in DOM APIs. */ let SANITIZE_DOM = true; - /* Achieve full DOM Clobbering protection by isolating the namespace of named * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. * @@ -489,25 +470,19 @@ function createDOMPurify() { */ let SANITIZE_NAMED_PROPS = false; const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; - /* Keep element content when removing element? */ let KEEP_CONTENT = true; - /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead * of importing it into a new Document and returning a sanitized copy */ let IN_PLACE = false; - /* Allow usage of profiles like html, svg and mathMl */ let USE_PROFILES = {}; - /* Tags to ignore content of when KEEP_CONTENT is true */ let FORBID_CONTENTS = null; const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); - /* Tags that are safe for data: URIs */ let DATA_URI_TAGS = null; const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); - /* Attributes safe for values like "javascript:" */ let URI_SAFE_ATTRIBUTES = null; const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); @@ -517,32 +492,33 @@ function createDOMPurify() { /* Document namespace */ let NAMESPACE = HTML_NAMESPACE; let IS_EMPTY_INPUT = false; - /* Allowed XHTML+XML namespaces */ let ALLOWED_NAMESPACES = null; const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); - + let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); + let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); + // Certain elements are allowed in both SVG and HTML + // namespace. We need to specify them explicitly + // so that they don't get erroneously deleted from + // HTML namespace. + const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); /* Parsing of strict XHTML documents */ let PARSER_MEDIA_TYPE = null; const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; const DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; let transformCaseFunc = null; - /* Keep a reference to config to pass to hooks */ let CONFIG = null; - /* Ideally, do not touch anything below this line */ /* ______________________________________________ */ - const formElement = document.createElement('form'); const isRegexOrFunction = function isRegexOrFunction(testValue) { return testValue instanceof RegExp || testValue instanceof Function; }; - /** * _parseConfig * - * @param {Object} cfg optional config literal + * @param cfg optional config literal */ // eslint-disable-next-line complexity const _parseConfig = function _parseConfig() { @@ -550,42 +526,26 @@ function createDOMPurify() { if (CONFIG && CONFIG === cfg) { return; } - /* Shield configuration object from tampering */ if (!cfg || typeof cfg !== 'object') { cfg = {}; } - /* Shield configuration object from prototype pollution */ cfg = clone(cfg); PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE; - // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; - /* Set configuration parameters */ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; - URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), - // eslint-disable-line indent - cfg.ADD_URI_SAFE_ATTR, - // eslint-disable-line indent - transformCaseFunc // eslint-disable-line indent - ) // eslint-disable-line indent - : DEFAULT_URI_SAFE_ATTRIBUTES; - DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), - // eslint-disable-line indent - cfg.ADD_DATA_URI_TAGS, - // eslint-disable-line indent - transformCaseFunc // eslint-disable-line indent - ) // eslint-disable-line indent - : DEFAULT_DATA_URI_TAGS; + URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES; + DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS; FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; - FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {}; - FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; + FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({}); + FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({}); USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false; ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true @@ -604,6 +564,8 @@ function createDOMPurify() { IN_PLACE = cfg.IN_PLACE || false; // Default false IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI; NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; + MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS; + HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS; CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; @@ -620,7 +582,6 @@ function createDOMPurify() { if (RETURN_DOM_FRAGMENT) { RETURN_DOM = true; } - /* Parse profile info */ if (USE_PROFILES) { ALLOWED_TAGS = addToSet({}, text); @@ -645,7 +606,6 @@ function createDOMPurify() { addToSet(ALLOWED_ATTR, xml); } } - /* Merge configuration parameters */ if (cfg.ADD_TAGS) { if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { @@ -668,17 +628,14 @@ function createDOMPurify() { } addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); } - /* Add #text in case KEEP_CONTENT is set to true */ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; } - /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ if (WHOLE_DOCUMENT) { addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); } - /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ if (ALLOWED_TAGS.table) { addToSet(ALLOWED_TAGS, ['tbody']); @@ -691,10 +648,8 @@ function createDOMPurify() { if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') { throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); } - // Overwrite existing TrustedTypes policy. trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; - // Sign local variables required by `sanitize`. emptyHTML = trustedTypesPolicy.createHTML(''); } else { @@ -702,13 +657,11 @@ function createDOMPurify() { if (trustedTypesPolicy === undefined) { trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript); } - // If creating the internal policy succeeded sign internal variables. if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { emptyHTML = trustedTypesPolicy.createHTML(''); } } - // Prevent further manipulation of configuration. // Not available in IE8, Safari 5, etc. if (freeze) { @@ -716,30 +669,19 @@ function createDOMPurify() { } CONFIG = cfg; }; - const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); - const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); - - // Certain elements are allowed in both SVG and HTML - // namespace. We need to specify them explicitly - // so that they don't get erroneously deleted from - // HTML namespace. - const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); - /* Keep track of all possible SVG and MathML tags * so that we can perform the namespace checks * correctly. */ const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]); const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]); - /** - * @param {Element} element a DOM element whose namespace is being checked - * @returns {boolean} Return false if the element has a + * @param element a DOM element whose namespace is being checked + * @returns Return false if the element has a * namespace that a spec-compliant parser would never * return. Return true otherwise. */ const _checkValidNamespace = function _checkValidNamespace(element) { let parent = getParentNode(element); - // In JSDOM, if we're inside shadow DOM, then parentNode // can be null. We just simulate parent in this case. if (!parent || !parent.tagName) { @@ -760,14 +702,12 @@ function createDOMPurify() { if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'svg'; } - // The only way to switch from MathML to SVG is via` // svg if parent is either or MathML // text integration points. if (parent.namespaceURI === MATHML_NAMESPACE) { return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); } - // We only allow elements that are defined in SVG // spec. All others are disallowed in SVG namespace. return Boolean(ALL_SVG_TAGS[tagName]); @@ -779,13 +719,11 @@ function createDOMPurify() { if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'math'; } - // The only way to switch from SVG to MathML is via // and HTML integration points if (parent.namespaceURI === SVG_NAMESPACE) { return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; } - // We only allow elements that are defined in MathML // spec. All others are disallowed in MathML namespace. return Boolean(ALL_MATHML_TAGS[tagName]); @@ -800,28 +738,24 @@ function createDOMPurify() { if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { return false; } - // We disallow tags that are specific for MathML // or SVG and should never appear in HTML namespace return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); } - // For XHTML and XML documents that support custom namespaces if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { return true; } - // The code should never reach this place (this means // that the element somehow got namespace that is not // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). // Return false just in case. return false; }; - /** * _forceRemove * - * @param {Node} node a DOM node + * @param node a DOM node */ const _forceRemove = function _forceRemove(node) { arrayPush(DOMPurify.removed, { @@ -834,46 +768,43 @@ function createDOMPurify() { remove(node); } }; - /** * _removeAttribute * - * @param {String} name an Attribute name - * @param {Node} node a DOM node + * @param name an Attribute name + * @param element a DOM node */ - const _removeAttribute = function _removeAttribute(name, node) { + const _removeAttribute = function _removeAttribute(name, element) { try { arrayPush(DOMPurify.removed, { - attribute: node.getAttributeNode(name), - from: node + attribute: element.getAttributeNode(name), + from: element }); } catch (_) { arrayPush(DOMPurify.removed, { attribute: null, - from: node + from: element }); } - node.removeAttribute(name); - - // We void attribute values for unremovable "is"" attributes - if (name === 'is' && !ALLOWED_ATTR[name]) { + element.removeAttribute(name); + // We void attribute values for unremovable "is" attributes + if (name === 'is') { if (RETURN_DOM || RETURN_DOM_FRAGMENT) { try { - _forceRemove(node); + _forceRemove(element); } catch (_) {} } else { try { - node.setAttribute(name, ''); + element.setAttribute(name, ''); } catch (_) {} } } }; - /** * _initDocument * - * @param {String} dirty a string of dirty markup - * @return {Document} a DOM, filled with the dirty markup + * @param dirty - a string of dirty markup + * @return a DOM, filled with the dirty markup */ const _initDocument = function _initDocument(dirty) { /* Create a HTML document */ @@ -900,7 +831,6 @@ function createDOMPurify() { doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); } catch (_) {} } - /* Use createHTMLDocument in case DOMParser is not available */ if (!doc || !doc.documentElement) { doc = implementation.createDocument(NAMESPACE, 'template', null); @@ -914,112 +844,86 @@ function createDOMPurify() { if (dirty && leadingWhitespace) { body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); } - /* Work on whole document or just its body */ if (NAMESPACE === HTML_NAMESPACE) { return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; } return WHOLE_DOCUMENT ? doc.documentElement : body; }; - /** * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. * - * @param {Node} root The root element or node to start traversing on. - * @return {NodeIterator} The created NodeIterator + * @param root The root element or node to start traversing on. + * @return The created NodeIterator */ const _createNodeIterator = function _createNodeIterator(root) { return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null); }; - /** * _isClobbered * - * @param {Node} elm element to check for clobbering attacks - * @return {Boolean} true if clobbered, false if safe + * @param element element to check for clobbering attacks + * @return true if clobbered, false if safe */ - const _isClobbered = function _isClobbered(elm) { - return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function'); + const _isClobbered = function _isClobbered(element) { + return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function'); }; - /** * Checks whether the given object is a DOM node. * - * @param {Node} object object to check whether it's a DOM node - * @return {Boolean} true is object is a DOM node + * @param value object to check whether it's a DOM node + * @return true is object is a DOM node */ - const _isNode = function _isNode(object) { - return typeof Node === 'function' && object instanceof Node; + const _isNode = function _isNode(value) { + return typeof Node === 'function' && value instanceof Node; }; - - /** - * _executeHook - * Execute user configurable hooks - * - * @param {String} entryPoint Name of the hook's entry point - * @param {Node} currentNode node to work on with the hook - * @param {Object} data additional hook parameters - */ - const _executeHook = function _executeHook(entryPoint, currentNode, data) { - if (!hooks[entryPoint]) { - return; - } - arrayForEach(hooks[entryPoint], hook => { + function _executeHooks(hooks, currentNode, data) { + arrayForEach(hooks, hook => { hook.call(DOMPurify, currentNode, data, CONFIG); }); - }; - + } /** * _sanitizeElements * * @protect nodeName * @protect textContent * @protect removeChild - * - * @param {Node} currentNode to check for permission to exist - * @return {Boolean} true if node was killed, false if left alive + * @param currentNode to check for permission to exist + * @return true if node was killed, false if left alive */ const _sanitizeElements = function _sanitizeElements(currentNode) { let content = null; - /* Execute a hook if present */ - _executeHook('beforeSanitizeElements', currentNode, null); - + _executeHooks(hooks.beforeSanitizeElements, currentNode, null); /* Check if element is clobbered or can clobber */ if (_isClobbered(currentNode)) { _forceRemove(currentNode); return true; } - /* Now let's check the element's type and name */ const tagName = transformCaseFunc(currentNode.nodeName); - /* Execute a hook if present */ - _executeHook('uponSanitizeElement', currentNode, { + _executeHooks(hooks.uponSanitizeElement, currentNode, { tagName, allowedTags: ALLOWED_TAGS }); - /* Detect mXSS attempts abusing namespace confusion */ - if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { + if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) { _forceRemove(currentNode); return true; } - /* Remove any occurrence of processing instructions */ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) { _forceRemove(currentNode); return true; } - /* Remove any kind of possibly harmful comments */ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) { _forceRemove(currentNode); return true; } - /* Remove element if anything forbids its presence */ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { /* Check if we have a custom element to handle */ @@ -1031,7 +935,6 @@ function createDOMPurify() { return false; } } - /* Keep content except for bad-listed elements */ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { const parentNode = getParentNode(currentNode) || currentNode.parentNode; @@ -1048,19 +951,16 @@ function createDOMPurify() { _forceRemove(currentNode); return true; } - /* Check whether element has a valid namespace */ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { _forceRemove(currentNode); return true; } - /* Make sure that older browsers don't get fallback-tag mXSS */ if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) { _forceRemove(currentNode); return true; } - /* Sanitize element content to be template-safe */ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { /* Get the element's text content */ @@ -1075,19 +975,17 @@ function createDOMPurify() { currentNode.textContent = content; } } - /* Execute a hook if present */ - _executeHook('afterSanitizeElements', currentNode, null); + _executeHooks(hooks.afterSanitizeElements, currentNode, null); return false; }; - /** * _isValidAttribute * - * @param {string} lcTag Lowercase tag name of containing element. - * @param {string} lcName Lowercase attribute name. - * @param {string} value Attribute value. - * @return {Boolean} Returns true if `value` is valid, otherwise false. + * @param lcTag Lowercase tag name of containing element. + * @param lcName Lowercase attribute name. + * @param value Attribute value. + * @return Returns true if `value` is valid, otherwise false. */ // eslint-disable-next-line complexity const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { @@ -1095,7 +993,6 @@ function createDOMPurify() { if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { return false; } - /* Allow valid data-* attributes: At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) @@ -1105,7 +1002,7 @@ function createDOMPurify() { // First condition does a very basic check if a) it's basically a valid custom element tagname AND // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck - _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || + _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || // Alternative, second condition checks if it's an `is`-attribute, AND // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { @@ -1117,19 +1014,17 @@ function createDOMPurify() { } else ; return true; }; - /** * _isBasicCustomElement * checks if at least one dash is included in tagName, and it's not the first char * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name * - * @param {string} tagName name of the tag of the node to sanitize - * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false. + * @param tagName name of the tag of the node to sanitize + * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false. */ const _isBasicCustomElement = function _isBasicCustomElement(tagName) { return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT); }; - /** * _sanitizeAttributes * @@ -1138,27 +1033,26 @@ function createDOMPurify() { * @protect removeAttribute * @protect setAttribute * - * @param {Node} currentNode to sanitize + * @param currentNode to sanitize */ const _sanitizeAttributes = function _sanitizeAttributes(currentNode) { /* Execute a hook if present */ - _executeHook('beforeSanitizeAttributes', currentNode, null); + _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null); const { attributes } = currentNode; - /* Check if we have attributes; if not we might have a text node */ - if (!attributes) { + if (!attributes || _isClobbered(currentNode)) { return; } const hookEvent = { attrName: '', attrValue: '', keepAttr: true, - allowedAttributes: ALLOWED_ATTR + allowedAttributes: ALLOWED_ATTR, + forceKeepAttr: undefined }; let l = attributes.length; - /* Go backwards over all attributes; safely remove bad ones */ while (l--) { const attr = attributes[l]; @@ -1168,65 +1062,60 @@ function createDOMPurify() { value: attrValue } = attr; const lcName = transformCaseFunc(name); - let value = name === 'value' ? attrValue : stringTrim(attrValue); - + const initValue = attrValue; + let value = name === 'value' ? initValue : stringTrim(initValue); /* Execute a hook if present */ hookEvent.attrName = lcName; hookEvent.attrValue = value; hookEvent.keepAttr = true; hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set - _executeHook('uponSanitizeAttribute', currentNode, hookEvent); + _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent); value = hookEvent.attrValue; - + /* Full DOM Clobbering protection via namespace isolation, + * Prefix id and name attributes with `user-content-` + */ + if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { + // Remove the attribute with this value + _removeAttribute(name, currentNode); + // Prefix the value and later re-create the attribute with the sanitized value + value = SANITIZE_NAMED_PROPS_PREFIX + value; + } + /* Work around a security issue with comments inside attributes */ + if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title|textarea)/i, value)) { + _removeAttribute(name, currentNode); + continue; + } + /* Make sure we cannot easily use animated hrefs, even if animations are allowed */ + if (lcName === 'attributename' && stringMatch(value, 'href')) { + _removeAttribute(name, currentNode); + continue; + } /* Did the hooks approve of the attribute? */ if (hookEvent.forceKeepAttr) { continue; } - - /* Remove attribute */ - _removeAttribute(name, currentNode); - /* Did the hooks approve of the attribute? */ if (!hookEvent.keepAttr) { + _removeAttribute(name, currentNode); continue; } - /* Work around a security issue in jQuery 3.0 */ if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { _removeAttribute(name, currentNode); continue; } - /* Sanitize attribute content to be template-safe */ if (SAFE_FOR_TEMPLATES) { arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { value = stringReplace(value, expr, ' '); }); } - /* Is `value` valid for this attribute? */ const lcTag = transformCaseFunc(currentNode.nodeName); if (!_isValidAttribute(lcTag, lcName, value)) { - continue; - } - - /* Full DOM Clobbering protection via namespace isolation, - * Prefix id and name attributes with `user-content-` - */ - if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { - // Remove the attribute with this value - _removeAttribute(name, currentNode); - - // Prefix the value and later re-create the attribute with the sanitized value - value = SANITIZE_NAMED_PROPS_PREFIX + value; - } - - /* Work around a security issue with comments inside attributes */ - if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) { _removeAttribute(name, currentNode); continue; } - /* Handle attributes that require Trusted Types */ if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') { if (namespaceURI) ; else { @@ -1244,67 +1133,53 @@ function createDOMPurify() { } } } - /* Handle invalid data-* attribute set by try-catching it */ - try { - if (namespaceURI) { - currentNode.setAttributeNS(namespaceURI, name, value); - } else { - /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ - currentNode.setAttribute(name, value); - } - if (_isClobbered(currentNode)) { - _forceRemove(currentNode); - } else { - arrayPop(DOMPurify.removed); + if (value !== initValue) { + try { + if (namespaceURI) { + currentNode.setAttributeNS(namespaceURI, name, value); + } else { + /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ + currentNode.setAttribute(name, value); + } + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + } else { + arrayPop(DOMPurify.removed); + } + } catch (_) { + _removeAttribute(name, currentNode); } - } catch (_) {} + } } - /* Execute a hook if present */ - _executeHook('afterSanitizeAttributes', currentNode, null); + _executeHooks(hooks.afterSanitizeAttributes, currentNode, null); }; - /** * _sanitizeShadowDOM * - * @param {DocumentFragment} fragment to iterate over recursively + * @param fragment to iterate over recursively */ const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { let shadowNode = null; const shadowIterator = _createNodeIterator(fragment); - /* Execute a hook if present */ - _executeHook('beforeSanitizeShadowDOM', fragment, null); + _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null); while (shadowNode = shadowIterator.nextNode()) { /* Execute a hook if present */ - _executeHook('uponSanitizeShadowNode', shadowNode, null); - + _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null); /* Sanitize tags and elements */ - if (_sanitizeElements(shadowNode)) { - continue; - } - + _sanitizeElements(shadowNode); + /* Check attributes next */ + _sanitizeAttributes(shadowNode); /* Deep shadow DOM detected */ if (shadowNode.content instanceof DocumentFragment) { _sanitizeShadowDOM(shadowNode.content); } - - /* Check attributes, sanitize if necessary */ - _sanitizeAttributes(shadowNode); } - /* Execute a hook if present */ - _executeHook('afterSanitizeShadowDOM', fragment, null); + _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null); }; - - /** - * Sanitize - * Public method providing core sanitation functionality - * - * @param {String|Node} dirty string or DOM node - * @param {Object} cfg object - */ // eslint-disable-next-line complexity DOMPurify.sanitize = function (dirty) { let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; @@ -1319,7 +1194,6 @@ function createDOMPurify() { if (IS_EMPTY_INPUT) { dirty = ''; } - /* Stringify, in case dirty is an object */ if (typeof dirty !== 'string' && !_isNode(dirty)) { if (typeof dirty.toString === 'function') { @@ -1331,20 +1205,16 @@ function createDOMPurify() { throw typeErrorCreate('toString is not a function'); } } - /* Return dirty HTML if DOMPurify cannot run */ if (!DOMPurify.isSupported) { return dirty; } - /* Assign config vars */ if (!SET_CONFIG) { _parseConfig(cfg); } - /* Clean up removed elements */ DOMPurify.removed = []; - /* Check if dirty is correctly typed for IN_PLACE */ if (typeof dirty === 'string') { IN_PLACE = false; @@ -1378,45 +1248,34 @@ function createDOMPurify() { dirty.indexOf('<') === -1) { return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; } - /* Initialize the document to work on */ body = _initDocument(dirty); - /* Check we have a DOM node from the data */ if (!body) { return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; } } - /* Remove first element node (ours) if FORCE_BODY is set */ if (body && FORCE_BODY) { _forceRemove(body.firstChild); } - /* Get node iterator */ const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); - /* Now start iterating over the created document */ while (currentNode = nodeIterator.nextNode()) { /* Sanitize tags and elements */ - if (_sanitizeElements(currentNode)) { - continue; - } - + _sanitizeElements(currentNode); + /* Check attributes next */ + _sanitizeAttributes(currentNode); /* Shadow DOM detected, sanitize it */ if (currentNode.content instanceof DocumentFragment) { _sanitizeShadowDOM(currentNode.content); } - - /* Check attributes, sanitize if necessary */ - _sanitizeAttributes(currentNode); } - /* If we sanitized `dirty` in-place, return it. */ if (IN_PLACE) { return dirty; } - /* Return sanitized string or DOM */ if (RETURN_DOM) { if (RETURN_DOM_FRAGMENT) { @@ -1441,12 +1300,10 @@ function createDOMPurify() { return returnNode; } let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; - /* Serialize doctype if allowed */ if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { serializedHTML = '\n' + serializedHTML; } - /* Sanitize final string template-safe */ if (SAFE_FOR_TEMPLATES) { arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { @@ -1455,39 +1312,15 @@ function createDOMPurify() { } return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; }; - - /** - * Public method to set the configuration once - * setConfig - * - * @param {Object} cfg configuration object - */ DOMPurify.setConfig = function () { let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _parseConfig(cfg); SET_CONFIG = true; }; - - /** - * Public method to remove the configuration - * clearConfig - * - */ DOMPurify.clearConfig = function () { CONFIG = null; SET_CONFIG = false; }; - - /** - * Public method to check if an attribute value is valid. - * Uses last set config, if any. Otherwise, uses config defaults. - * isValidAttribute - * - * @param {String} tag Tag name of containing element. - * @param {String} attr Attribute name. - * @param {String} value Attribute value. - * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. - */ DOMPurify.isValidAttribute = function (tag, attr, value) { /* Initialize shared config vars if necessary. */ if (!CONFIG) { @@ -1497,54 +1330,24 @@ function createDOMPurify() { const lcName = transformCaseFunc(attr); return _isValidAttribute(lcTag, lcName, value); }; - - /** - * AddHook - * Public method to add DOMPurify hooks - * - * @param {String} entryPoint entry point for the hook to add - * @param {Function} hookFunction function to execute - */ DOMPurify.addHook = function (entryPoint, hookFunction) { if (typeof hookFunction !== 'function') { return; } - hooks[entryPoint] = hooks[entryPoint] || []; arrayPush(hooks[entryPoint], hookFunction); }; - - /** - * RemoveHook - * Public method to remove a DOMPurify hook at a given entryPoint - * (pops it from the stack of hooks if more are present) - * - * @param {String} entryPoint entry point for the hook to remove - * @return {Function} removed(popped) hook - */ - DOMPurify.removeHook = function (entryPoint) { - if (hooks[entryPoint]) { - return arrayPop(hooks[entryPoint]); + DOMPurify.removeHook = function (entryPoint, hookFunction) { + if (hookFunction !== undefined) { + const index = arrayLastIndexOf(hooks[entryPoint], hookFunction); + return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0]; } + return arrayPop(hooks[entryPoint]); }; - - /** - * RemoveHooks - * Public method to remove all DOMPurify hooks at a given entryPoint - * - * @param {String} entryPoint entry point for the hooks to remove - */ DOMPurify.removeHooks = function (entryPoint) { - if (hooks[entryPoint]) { - hooks[entryPoint] = []; - } + hooks[entryPoint] = []; }; - - /** - * RemoveAllHooks - * Public method to remove all DOMPurify hooks - */ DOMPurify.removeAllHooks = function () { - hooks = {}; + hooks = _createHooksMap(); }; return DOMPurify; } From f6e367b78643f7de0a4e562dce424ab325cd95df Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:48:34 -0700 Subject: [PATCH 0827/4355] Fix issues from trusted types update --- eslint.config.js | 9 +++++++-- src/typings/vscode-globals-ttp.d.ts | 2 ++ src/vs/amdX.ts | 1 + src/vs/base/browser/trustedTypes.ts | 1 + src/vs/workbench/workbench.web.main.ts | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 1caf79fa44a..d738fcd08ba 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1004,7 +1004,11 @@ export default tseslint.config( { 'target': 'src/vs/base/~', 'restrictions': [ - 'vs/base/~' + 'vs/base/~', + { + 'when': 'hasBrowser', + 'pattern': 'trusted-types/**' + }, // Typings ] }, { @@ -1340,7 +1344,8 @@ export default tseslint.config( { 'target': 'src/vs/amdX.ts', 'restrictions': [ - 'vs/base/common/*' + 'vs/base/common/*', + 'trusted-types/**', // Typings ] }, { diff --git a/src/typings/vscode-globals-ttp.d.ts b/src/typings/vscode-globals-ttp.d.ts index b91080ec741..5c67bc1e19f 100644 --- a/src/typings/vscode-globals-ttp.d.ts +++ b/src/typings/vscode-globals-ttp.d.ts @@ -5,6 +5,8 @@ // AMD2ESM migration relevant +import type { TrustedTypePolicy } from 'trusted-types/lib/index.js'; + declare global { var _VSCODE_WEB_PACKAGE_TTP: Pick( diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 383909739c6..93752573d4c 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -73,7 +73,7 @@ } globalThis._VSCODE_FILE_ROOT = baseUrl; - const trustedTypesPolicy: Pick, 'name' | 'createScriptURL'> | undefined = require.getConfig().trustedTypesPolicy; + const trustedTypesPolicy: Pick, 'name' | 'createScriptURL'> | undefined = require.getConfig().trustedTypesPolicy; if (trustedTypesPolicy) { globalThis._VSCODE_WEB_PACKAGE_TTP = trustedTypesPolicy; } From c2af8f92a097ddbad5c8ddafb8067db4c187536f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Oct 2025 17:52:30 +0200 Subject: [PATCH 0828/4355] debt - update no-any search (#270040) --- .vscode/searches/no-any-casts.code-search | 1127 +++++++++------------ 1 file changed, 477 insertions(+), 650 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index c5f6c0b09cf..0c87e934d05 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,77 +1,49 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -1133 results - 453 files +1052 results - 407 files -.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts: - 64: // eslint-disable-next-line local/code-no-any-casts - 69: // eslint-disable-next-line local/code-no-any-casts - 136: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/code-no-reader-after-await.ts: - 62: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/code-no-static-self-ref.ts: - 36: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/code-no-test-async-suite.ts: - 23: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/code-no-unexternalized-strings.ts: - 84: // eslint-disable-next-line local/code-no-any-casts - 132: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/vscode-dts-provider-naming.ts: - 35: // eslint-disable-next-line local/code-no-any-casts - -.eslint-plugin-local/vscode-dts-vscode-in-comments.ts: - 43: // eslint-disable-next-line local/code-no-any-casts - 45: // eslint-disable-next-line local/code-no-any-casts - -build/azure-pipelines/upload-cdn.js: - 108: // eslint-disable-next-line local/code-no-any-casts - -build/azure-pipelines/upload-cdn.ts: +vscode • build/azure-pipelines/upload-cdn.ts: 115: // eslint-disable-next-line local/code-no-any-casts -build/azure-pipelines/common/publish.ts: +vscode • build/azure-pipelines/common/publish.ts: 524: // eslint-disable-next-line local/code-no-any-casts -build/lib/compilation.ts: +vscode • build/lib/compilation.ts: 153: // eslint-disable-next-line local/code-no-any-casts -build/lib/extensions.ts: +vscode • build/lib/extensions.ts: 120: // eslint-disable-next-line local/code-no-any-casts 219: // eslint-disable-next-line local/code-no-any-casts -build/lib/fetch.ts: +vscode • build/lib/fetch.ts: 57: // eslint-disable-next-line local/code-no-any-casts -build/lib/monaco-api.ts: +vscode • build/lib/monaco-api.ts: 221: // eslint-disable-next-line local/code-no-any-casts 328: // eslint-disable-next-line local/code-no-any-casts -build/lib/nls.ts: +vscode • build/lib/nls.ts: 43: // eslint-disable-next-line local/code-no-any-casts 507: // eslint-disable-next-line local/code-no-any-casts 513: // eslint-disable-next-line local/code-no-any-casts -build/lib/optimize.ts: +vscode • build/lib/optimize.ts: 254: // eslint-disable-next-line local/code-no-any-casts -build/lib/propertyInitOrderChecker.ts: +vscode • build/lib/propertyInitOrderChecker.ts: 254: // eslint-disable-next-line local/code-no-any-casts 256: // eslint-disable-next-line local/code-no-any-casts 258: // eslint-disable-next-line local/code-no-any-casts -build/lib/reporter.ts: +vscode • build/lib/reporter.ts: 108: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts 117: // eslint-disable-next-line local/code-no-any-casts -build/lib/task.ts: +vscode • build/lib/task.ts: 27: // eslint-disable-next-line local/code-no-any-casts -build/lib/treeshaking.ts: +vscode • build/lib/treeshaking.ts: 310: // eslint-disable-next-line local/code-no-any-casts 314: // eslint-disable-next-line local/code-no-any-casts 318: // eslint-disable-next-line local/code-no-any-casts @@ -81,113 +53,113 @@ build/lib/treeshaking.ts: 922: // eslint-disable-next-line local/code-no-any-casts 924: // eslint-disable-next-line local/code-no-any-casts -build/lib/util.ts: +vscode • build/lib/util.ts: 132: // eslint-disable-next-line local/code-no-any-casts 357: // eslint-disable-next-line local/code-no-any-casts -build/lib/mangle/index.ts: +vscode • build/lib/mangle/index.ts: 273: // eslint-disable-next-line local/code-no-any-casts -build/lib/tsb/builder.ts: +vscode • build/lib/tsb/builder.ts: 62: // eslint-disable-next-line local/code-no-any-casts 84: // eslint-disable-next-line local/code-no-any-casts 226: // eslint-disable-next-line local/code-no-any-casts 245: // eslint-disable-next-line local/code-no-any-casts 593: // eslint-disable-next-line local/code-no-any-casts -build/lib/tsb/index.ts: +vscode • build/lib/tsb/index.ts: 134: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts -build/lib/watch/watch-win32.ts: +vscode • build/lib/watch/watch-win32.ts: 50: // eslint-disable-next-line local/code-no-any-casts -build/linux/debian/install-sysroot.ts: +vscode • build/linux/debian/install-sysroot.ts: 85: // eslint-disable-next-line local/code-no-any-casts -build/win32/explorer-dll-fetcher.ts: +vscode • build/win32/explorer-dll-fetcher.ts: 63: // eslint-disable-next-line local/code-no-any-casts -extensions/css-language-features/client/src/cssClient.ts: +vscode • extensions/css-language-features/client/src/cssClient.ts: 86: // eslint-disable-next-line local/code-no-any-casts -extensions/css-language-features/server/src/cssServer.ts: +vscode • extensions/css-language-features/server/src/cssServer.ts: 71: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 171: // eslint-disable-next-line local/code-no-any-casts -extensions/emmet/src/emmetCommon.ts: +vscode • extensions/emmet/src/emmetCommon.ts: 187: // eslint-disable-next-line local/code-no-any-casts 189: // eslint-disable-next-line local/code-no-any-casts -extensions/emmet/src/updateImageSize.ts: +vscode • extensions/emmet/src/updateImageSize.ts: 276: // eslint-disable-next-line local/code-no-any-casts 278: // eslint-disable-next-line local/code-no-any-casts -extensions/emmet/src/util.ts: +vscode • extensions/emmet/src/util.ts: 357: // eslint-disable-next-line local/code-no-any-casts -extensions/git/src/commands.ts: +vscode • extensions/git/src/commands.ts: 2384: // eslint-disable-next-line local/code-no-any-casts 2387: // eslint-disable-next-line local/code-no-any-casts 5373: // eslint-disable-next-line local/code-no-any-casts -extensions/git/src/emoji.ts: +vscode • extensions/git/src/emoji.ts: 27: // eslint-disable-next-line local/code-no-any-casts -extensions/git/src/git.ts: +vscode • extensions/git/src/git.ts: 311: // eslint-disable-next-line local/code-no-any-casts 2976: // eslint-disable-next-line local/code-no-any-casts 2978: // eslint-disable-next-line local/code-no-any-casts -extensions/git/src/main.ts: +vscode • extensions/git/src/main.ts: 87: // eslint-disable-next-line local/code-no-any-casts -extensions/git/src/util.ts: +vscode • extensions/git/src/util.ts: 42: // eslint-disable-next-line local/code-no-any-casts 114: // eslint-disable-next-line local/code-no-any-casts 241: // eslint-disable-next-line local/code-no-any-casts -extensions/git-base/src/api/api1.ts: +vscode • extensions/git-base/src/api/api1.ts: 17: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts -extensions/github-authentication/src/node/crypto.ts: +vscode • extensions/github-authentication/src/node/crypto.ts: 8: // eslint-disable-next-line local/code-no-any-casts -extensions/grunt/src/main.ts: +vscode • extensions/grunt/src/main.ts: 123: // eslint-disable-next-line local/code-no-any-casts -extensions/gulp/src/main.ts: +vscode • extensions/gulp/src/main.ts: 153: // eslint-disable-next-line local/code-no-any-casts 156: // eslint-disable-next-line local/code-no-any-casts -extensions/html-language-features/client/src/htmlClient.ts: +vscode • extensions/html-language-features/client/src/htmlClient.ts: 182: // eslint-disable-next-line local/code-no-any-casts -extensions/html-language-features/server/src/htmlServer.ts: +vscode • extensions/html-language-features/server/src/htmlServer.ts: 137: // eslint-disable-next-line local/code-no-any-casts 140: // eslint-disable-next-line local/code-no-any-casts 545: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/deserializers.ts: +vscode • extensions/ipynb/src/deserializers.ts: 23: // eslint-disable-next-line local/code-no-any-casts 294: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/helper.ts: +vscode • extensions/ipynb/src/helper.ts: 14: // eslint-disable-next-line local/code-no-any-casts 18: // eslint-disable-next-line local/code-no-any-casts 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 25: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/serializers.ts: +vscode • extensions/ipynb/src/serializers.ts: 40: // eslint-disable-next-line local/code-no-any-casts 61: // eslint-disable-next-line local/code-no-any-casts 403: // eslint-disable-next-line local/code-no-any-casts 405: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/test/notebookModelStoreSync.test.ts: +vscode • extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 79: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts @@ -203,112 +175,55 @@ extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 424: // eslint-disable-next-line local/code-no-any-casts 459: // eslint-disable-next-line local/code-no-any-casts -extensions/jake/src/main.ts: +vscode • extensions/jake/src/main.ts: 123: // eslint-disable-next-line local/code-no-any-casts 126: // eslint-disable-next-line local/code-no-any-casts -extensions/json-language-features/client/src/jsonClient.ts: +vscode • extensions/json-language-features/client/src/jsonClient.ts: 775: // eslint-disable-next-line local/code-no-any-casts -extensions/json-language-features/server/src/jsonServer.ts: +vscode • extensions/json-language-features/server/src/jsonServer.ts: 144: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/notebook/index.ts: +vscode • extensions/markdown-language-features/notebook/index.ts: 383: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/preview-src/index.ts: +vscode • extensions/markdown-language-features/preview-src/index.ts: 26: // eslint-disable-next-line local/code-no-any-casts 253: // eslint-disable-next-line local/code-no-any-casts 444: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/src/markdownEngine.ts: +vscode • extensions/markdown-language-features/src/markdownEngine.ts: 146: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: +vscode • extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: 53: // eslint-disable-next-line local/code-no-any-casts -extensions/microsoft-authentication/src/common/telemetryReporter.ts: +vscode • extensions/microsoft-authentication/src/common/telemetryReporter.ts: 97: // eslint-disable-next-line local/code-no-any-casts -extensions/notebook-renderers/src/test/notebookRenderer.test.ts: +vscode • extensions/notebook-renderers/src/index.ts: + 68: // eslint-disable-next-line local/code-no-any-casts + +vscode • extensions/notebook-renderers/src/test/notebookRenderer.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts -extensions/npm/src/tasks.ts: +vscode • extensions/npm/src/tasks.ts: 61: // eslint-disable-next-line local/code-no-any-casts 64: // eslint-disable-next-line local/code-no-any-casts -extensions/terminal-suggest/src/test/completions/cd.test.ts: - 11: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/code.test.ts: - 80: // eslint-disable-next-line local/code-no-any-casts - 269: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts: - 15: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/git.test.ts: - 14: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts: - 54: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts: - 22: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts: - 19: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts: - 13: // eslint-disable-next-line local/code-no-any-casts - -extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts: - 20: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/src/languageFeatures/completions.ts: - 780: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts: - 108: // eslint-disable-next-line local/code-no-any-casts - 111: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts: - 186: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/src/tsServer/server.ts: - 286: // eslint-disable-next-line local/code-no-any-casts - 302: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/src/utils/platform.ts: - 13: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/web/src/serverHost.ts: - 32: // eslint-disable-next-line local/code-no-any-casts - 45: // eslint-disable-next-line local/code-no-any-casts - 47: // eslint-disable-next-line local/code-no-any-casts - 51: // eslint-disable-next-line local/code-no-any-casts - 53: // eslint-disable-next-line local/code-no-any-casts - 55: // eslint-disable-next-line local/code-no-any-casts - 57: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/web/src/webServer.ts: - 16: // eslint-disable-next-line local/code-no-any-casts - -extensions/typescript-language-features/web/src/workerSession.ts: - 34: // eslint-disable-next-line local/code-no-any-casts - -extensions/vscode-api-tests/src/extension.ts: +vscode • extensions/vscode-api-tests/src/extension.ts: 10: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: 181: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts: 33: // eslint-disable-next-line local/code-no-any-casts 48: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts: 24: // eslint-disable-next-line local/code-no-any-casts 26: // eslint-disable-next-line local/code-no-any-casts 28: // eslint-disable-next-line local/code-no-any-casts @@ -316,39 +231,39 @@ extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts: 32: // eslint-disable-next-line local/code-no-any-casts 34: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: 59: // eslint-disable-next-line local/code-no-any-casts 76: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: 18: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts: 44: // eslint-disable-next-line local/code-no-any-casts 50: // eslint-disable-next-line local/code-no-any-casts 463: // eslint-disable-next-line local/code-no-any-casts -scripts/playground-server.ts: +vscode • scripts/playground-server.ts: 257: // eslint-disable-next-line local/code-no-any-casts 336: // eslint-disable-next-line local/code-no-any-casts 352: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/browser.ts: +vscode • src/vs/base/browser/browser.ts: 134: // eslint-disable-next-line local/code-no-any-casts 141: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/canIUse.ts: +vscode • src/vs/base/browser/canIUse.ts: 36: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/deviceAccess.ts: +vscode • src/vs/base/browser/deviceAccess.ts: 26: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/dom.ts: +vscode • src/vs/base/browser/dom.ts: 430: // eslint-disable-next-line local/code-no-any-casts 719: // eslint-disable-next-line local/code-no-any-casts 1325: // eslint-disable-next-line local/code-no-any-casts @@ -364,141 +279,135 @@ src/vs/base/browser/dom.ts: 2444: // eslint-disable-next-line local/code-no-any-casts 2529: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/fastDomNode.ts: - 294: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/mouseEvent.ts: +vscode • src/vs/base/browser/mouseEvent.ts: 100: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts 155: // eslint-disable-next-line local/code-no-any-casts 157: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/trustedTypes.ts: +vscode • src/vs/base/browser/trustedTypes.ts: 19: // eslint-disable-next-line local/code-no-any-casts 31: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/webWorkerFactory.ts: +vscode • src/vs/base/browser/webWorkerFactory.ts: 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 43: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/grid/grid.ts: +vscode • src/vs/base/browser/ui/grid/grid.ts: 66: // eslint-disable-next-line local/code-no-any-casts 873: // eslint-disable-next-line local/code-no-any-casts 875: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/grid/gridview.ts: +vscode • src/vs/base/browser/ui/grid/gridview.ts: 196: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/sash/sash.ts: +vscode • src/vs/base/browser/ui/sash/sash.ts: 491: // eslint-disable-next-line local/code-no-any-casts 497: // eslint-disable-next-line local/code-no-any-casts 503: // eslint-disable-next-line local/code-no-any-casts 505: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/tree/abstractTree.ts: +vscode • src/vs/base/browser/ui/tree/abstractTree.ts: 171: // eslint-disable-next-line local/code-no-any-casts 175: // eslint-disable-next-line local/code-no-any-casts 1186: // eslint-disable-next-line local/code-no-any-casts 1212: // eslint-disable-next-line local/code-no-any-casts 2241: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/tree/asyncDataTree.ts: +vscode • src/vs/base/browser/ui/tree/asyncDataTree.ts: 305: // eslint-disable-next-line local/code-no-any-casts 417: // eslint-disable-next-line local/code-no-any-casts 434: // eslint-disable-next-line local/code-no-any-casts 438: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/tree/indexTreeModel.ts: +vscode • src/vs/base/browser/ui/tree/indexTreeModel.ts: 89: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/console.ts: +vscode • src/vs/base/common/console.ts: 134: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/controlFlow.ts: +vscode • src/vs/base/common/controlFlow.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/decorators.ts: +vscode • src/vs/base/common/decorators.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/errors.ts: +vscode • src/vs/base/common/errors.ts: 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/hotReload.ts: +vscode • src/vs/base/common/hotReload.ts: 97: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/hotReloadHelpers.ts: +vscode • src/vs/base/common/hotReloadHelpers.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/lifecycle.ts: +vscode • src/vs/base/common/lifecycle.ts: 236: // eslint-disable-next-line local/code-no-any-casts 246: // eslint-disable-next-line local/code-no-any-casts 257: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/map.ts: - 124: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/common/marshalling.ts: +vscode • src/vs/base/common/marshalling.ts: 53: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts 57: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/network.ts: +vscode • src/vs/base/common/network.ts: 416: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/objects.ts: +vscode • src/vs/base/common/objects.ts: 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/process.ts: +vscode • src/vs/base/common/process.ts: 12: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/skipList.ts: +vscode • src/vs/base/common/skipList.ts: 38: // eslint-disable-next-line local/code-no-any-casts 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/types.ts: +vscode • src/vs/base/common/types.ts: 58: // eslint-disable-next-line local/code-no-any-casts 66: // eslint-disable-next-line local/code-no-any-casts 268: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/uriIpc.ts: +vscode • src/vs/base/common/uriIpc.ts: 33: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/verifier.ts: +vscode • src/vs/base/common/verifier.ts: 82: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/changeTracker.ts: +vscode • src/vs/base/common/observableInternal/changeTracker.ts: 34: // eslint-disable-next-line local/code-no-any-casts 42: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/debugLocation.ts: +vscode • src/vs/base/common/observableInternal/debugLocation.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/debugName.ts: +vscode • src/vs/base/common/observableInternal/debugName.ts: 106: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/set.ts: +vscode • src/vs/base/common/observableInternal/set.ts: 51: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/experimental/reducer.ts: +vscode • src/vs/base/common/observableInternal/experimental/reducer.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: +vscode • src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: +vscode • src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: 12: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/debugger/rpc.ts: +vscode • src/vs/base/common/observableInternal/logging/debugger/rpc.ts: 94: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/derived.ts: +vscode • src/vs/base/common/observableInternal/observables/derived.ts: 38: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts 124: // eslint-disable-next-line local/code-no-any-casts @@ -506,252 +415,202 @@ src/vs/base/common/observableInternal/observables/derived.ts: 160: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/derivedImpl.ts: +vscode • src/vs/base/common/observableInternal/observables/derivedImpl.ts: 313: // eslint-disable-next-line local/code-no-any-casts 414: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/observableFromEvent.ts: +vscode • src/vs/base/common/observableInternal/observables/observableFromEvent.ts: 151: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/reactions/autorunImpl.ts: +vscode • src/vs/base/common/observableInternal/reactions/autorunImpl.ts: 185: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/utils/utilsCancellation.ts: +vscode • src/vs/base/common/observableInternal/utils/utilsCancellation.ts: 78: // eslint-disable-next-line local/code-no-any-casts 83: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/worker/webWorker.ts: - 356: // eslint-disable-next-line local/code-no-any-casts - 362: // eslint-disable-next-line local/code-no-any-casts - 375: // eslint-disable-next-line local/code-no-any-casts - 383: // eslint-disable-next-line local/code-no-any-casts - 464: // eslint-disable-next-line local/code-no-any-casts - 470: // eslint-disable-next-line local/code-no-any-casts - 483: // eslint-disable-next-line local/code-no-any-casts - 491: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/node/ports.ts: +vscode • src/vs/base/node/ports.ts: 182: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/node/unc.ts: - 20: // eslint-disable-next-line local/code-no-any-casts - 94: // eslint-disable-next-line local/code-no-any-casts - 103: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/parts/ipc/common/ipc.ts: +vscode • src/vs/base/parts/ipc/common/ipc.ts: 599: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/parts/ipc/node/ipc.net.ts: +vscode • src/vs/base/parts/ipc/node/ipc.net.ts: 730: // eslint-disable-next-line local/code-no-any-casts 784: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/parts/ipc/test/node/ipc.net.test.ts: +vscode • src/vs/base/parts/ipc/test/node/ipc.net.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts 652: // eslint-disable-next-line local/code-no-any-casts 785: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/browser/dom.test.ts: +vscode • src/vs/base/test/browser/dom.test.ts: 407: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/buffer.test.ts: +vscode • src/vs/base/test/common/buffer.test.ts: 501: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/decorators.test.ts: +vscode • src/vs/base/test/common/decorators.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/filters.test.ts: +vscode • src/vs/base/test/common/filters.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/glob.test.ts: +vscode • src/vs/base/test/common/glob.test.ts: 497: // eslint-disable-next-line local/code-no-any-casts 518: // eslint-disable-next-line local/code-no-any-casts 763: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/json.test.ts: +vscode • src/vs/base/test/common/json.test.ts: 52: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/mock.ts: +vscode • src/vs/base/test/common/mock.ts: 13: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/oauth.test.ts: +vscode • src/vs/base/test/common/oauth.test.ts: 1087: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/snapshot.ts: +vscode • src/vs/base/test/common/snapshot.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/timeTravelScheduler.ts: +vscode • src/vs/base/test/common/timeTravelScheduler.ts: 268: // eslint-disable-next-line local/code-no-any-casts 278: // eslint-disable-next-line local/code-no-any-casts 311: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts 333: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/troubleshooting.ts: +vscode • src/vs/base/test/common/troubleshooting.ts: 50: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/editor.api.ts: +vscode • src/vs/editor/editor.api.ts: 44: // eslint-disable-next-line local/code-no-any-casts 46: // eslint-disable-next-line local/code-no-any-casts 51: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/browser/editorDom.ts: - 245: // eslint-disable-next-line local/code-no-any-casts - 385: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/config/editorConfiguration.ts: - 146: // eslint-disable-next-line local/code-no-any-casts - 291: // eslint-disable-next-line local/code-no-any-casts - 345: // eslint-disable-next-line local/code-no-any-casts - 347: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/config/migrateOptions.ts: - 215: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/controller/mouseHandler.ts: - 248: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/controller/mouseTarget.ts: - 153: // eslint-disable-next-line local/code-no-any-casts - 993: // eslint-disable-next-line local/code-no-any-casts - 997: // eslint-disable-next-line local/code-no-any-casts - 1001: // eslint-disable-next-line local/code-no-any-casts - 1044: // eslint-disable-next-line local/code-no-any-casts - 1097: // eslint-disable-next-line local/code-no-any-casts - 1100: // eslint-disable-next-line local/code-no-any-casts - 1120: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/controller/pointerHandler.ts: - 57: // eslint-disable-next-line local/code-no-any-casts - 98: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/controller/editContext/native/editContextFactory.ts: - 13: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: - 81: // eslint-disable-next-line local/code-no-any-casts - 85: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/gpu/gpuUtils.ts: - 52: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/gpu/viewGpuContext.ts: - 226: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/services/hoverService/hoverService.ts: - 423: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts: - 1032: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: - 179: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: - 477: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/diffEditor/utils.ts: - 184: // eslint-disable-next-line local/code-no-any-casts - 194: // eslint-disable-next-line local/code-no-any-casts - 302: // eslint-disable-next-line local/code-no-any-casts - 309: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: - 75: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: - 100: // eslint-disable-next-line local/code-no-any-casts - 103: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/languages.ts: - 460: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/textModelEditSource.ts: - 59: // eslint-disable-next-line local/code-no-any-casts - 69: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/config/editorOptions.ts: - 1174: // eslint-disable-next-line local/code-no-any-casts - 1211: // eslint-disable-next-line local/code-no-any-casts - 1392: // eslint-disable-next-line local/code-no-any-casts - 1428: // eslint-disable-next-line local/code-no-any-casts - 4785: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/core/edits/stringEdit.ts: - 23: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: - 26: // eslint-disable-next-line local/code-no-any-casts - 30: // eslint-disable-next-line local/code-no-any-casts - 51: // eslint-disable-next-line local/code-no-any-casts - 56: // eslint-disable-next-line local/code-no-any-casts - 64: // eslint-disable-next-line local/code-no-any-casts - 72: // eslint-disable-next-line local/code-no-any-casts - 98: // eslint-disable-next-line local/code-no-any-casts - 100: // eslint-disable-next-line local/code-no-any-casts - 125: // eslint-disable-next-line local/code-no-any-casts - 130: // eslint-disable-next-line local/code-no-any-casts - 135: // eslint-disable-next-line local/code-no-any-casts - 140: // eslint-disable-next-line local/code-no-any-casts - 152: // eslint-disable-next-line local/code-no-any-casts - 157: // eslint-disable-next-line local/code-no-any-casts - 174: // eslint-disable-next-line local/code-no-any-casts - 176: // eslint-disable-next-line local/code-no-any-casts - 195: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/common/services/editorBaseApi.ts: - 37: // eslint-disable-next-line local/code-no-any-casts - 42: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: +vscode • src/vs/editor/browser/config/editorConfiguration.ts: + 147: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/controller/mouseTarget.ts: + 992: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 996: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1000: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1043: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1096: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1099: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1119: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: + 81: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 85: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/gpu/gpuUtils.ts: + 52: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/gpu/viewGpuContext.ts: + 226: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: + 179: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: + 477: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/widget/diffEditor/utils.ts: + 184: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 195: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 303: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 310: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: + 75: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: + 100: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 103: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/common/textModelEditSource.ts: + 59: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 70: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/common/core/edits/stringEdit.ts: + 24: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: + 26: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 30: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 51: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 56: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 64: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 72: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 99: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 101: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 126: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 131: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 136: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 141: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 153: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 158: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 175: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 177: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 196: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +vscode • src/vs/editor/contrib/codeAction/browser/codeActionController.ts: + 113: // eslint-disable-next-line local/code-no-any-casts + +vscode • src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: 100: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: +vscode • src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: 232: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: +vscode • src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: 200: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: +vscode • src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: 97: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/find/browser/findModel.ts: +vscode • src/vs/editor/contrib/find/browser/findModel.ts: 556: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/find/test/browser/findController.test.ts: +vscode • src/vs/editor/contrib/find/test/browser/findController.test.ts: 79: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: + 639: // eslint-disable-next-line local/code-no-any-casts + +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: 274: // eslint-disable-next-line local/code-no-any-casts 296: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: 346: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: 23: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: 163: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: 240: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: +vscode • src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: 797: // eslint-disable-next-line local/code-no-any-casts 816: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/browser/standaloneEditor.ts: +vscode • src/vs/editor/standalone/browser/standaloneEditor.ts: 504: // eslint-disable-next-line local/code-no-any-casts 506: // eslint-disable-next-line local/code-no-any-casts 508: // eslint-disable-next-line local/code-no-any-casts @@ -790,7 +649,7 @@ src/vs/editor/standalone/browser/standaloneEditor.ts: 614: // eslint-disable-next-line local/code-no-any-casts 619: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/browser/standaloneLanguages.ts: +vscode • src/vs/editor/standalone/browser/standaloneLanguages.ts: 753: // eslint-disable-next-line local/code-no-any-casts 755: // eslint-disable-next-line local/code-no-any-casts 757: // eslint-disable-next-line local/code-no-any-casts @@ -829,25 +688,25 @@ src/vs/editor/standalone/browser/standaloneLanguages.ts: 849: // eslint-disable-next-line local/code-no-any-casts 851: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/common/monarch/monarchCompile.ts: +vscode • src/vs/editor/standalone/common/monarch/monarchCompile.ts: 461: // eslint-disable-next-line local/code-no-any-casts 539: // eslint-disable-next-line local/code-no-any-casts 556: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/browser/testCodeEditor.ts: +vscode • src/vs/editor/test/browser/testCodeEditor.ts: 279: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/browser/config/editorConfiguration.test.ts: +vscode • src/vs/editor/test/browser/config/editorConfiguration.test.ts: 90: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/textModel.test.ts: +vscode • src/vs/editor/test/common/model/textModel.test.ts: 1167: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/textModelWithTokens.test.ts: +vscode • src/vs/editor/test/common/model/textModelWithTokens.test.ts: 272: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/tokenStore.test.ts: +vscode • src/vs/editor/test/common/model/tokenStore.test.ts: 57: // eslint-disable-next-line local/code-no-any-casts 78: // eslint-disable-next-line local/code-no-any-casts 100: // eslint-disable-next-line local/code-no-any-casts @@ -858,112 +717,109 @@ src/vs/editor/test/common/model/tokenStore.test.ts: 203: // eslint-disable-next-line local/code-no-any-casts 234: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/contextkey/common/contextkey.ts: +vscode • src/vs/platform/contextkey/common/contextkey.ts: 939: // eslint-disable-next-line local/code-no-any-casts 1213: // eslint-disable-next-line local/code-no-any-casts 1273: // eslint-disable-next-line local/code-no-any-casts 1334: // eslint-disable-next-line local/code-no-any-casts 1395: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/contextkey/test/common/contextkey.test.ts: +vscode • src/vs/platform/contextkey/test/common/contextkey.test.ts: 96: // eslint-disable-next-line local/code-no-any-casts 98: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts: +vscode • src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts: 93: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/dnd/browser/dnd.ts: +vscode • src/vs/platform/dnd/browser/dnd.ts: 469: // eslint-disable-next-line local/code-no-any-casts 471: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/environment/test/node/argv.test.ts: +vscode • src/vs/platform/environment/test/node/argv.test.ts: 47: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: +vscode • src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: 243: // eslint-disable-next-line local/code-no-any-casts 245: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: +vscode • src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: 405: // eslint-disable-next-line local/code-no-any-casts 407: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: +vscode • src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: 73: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/files/browser/htmlFileSystemProvider.ts: - 311: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/platform/files/browser/htmlFileSystemProvider.ts: + 311: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/platform/files/test/node/diskFileService.integrationTest.ts: +vscode • src/vs/platform/files/test/node/diskFileService.integrationTest.ts: 106: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 112: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/hover/test/browser/nullHoverService.ts: - 16: // eslint-disable-next-line local/code-no-any-casts - -src/vs/platform/instantiation/common/instantiation.ts: +vscode • src/vs/platform/instantiation/common/instantiation.ts: 93: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts 98: // eslint-disable-next-line local/code-no-any-casts 100: // eslint-disable-next-line local/code-no-any-casts 114: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/instantiation/common/instantiationService.ts: +vscode • src/vs/platform/instantiation/common/instantiationService.ts: 328: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/instantiation/test/common/instantiationServiceMock.ts: +vscode • src/vs/platform/instantiation/test/common/instantiationServiceMock.ts: 61: // eslint-disable-next-line local/code-no-any-casts 164: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/list/browser/listService.ts: +vscode • src/vs/platform/list/browser/listService.ts: 877: // eslint-disable-next-line local/code-no-any-casts 918: // eslint-disable-next-line local/code-no-any-casts 965: // eslint-disable-next-line local/code-no-any-casts 1012: // eslint-disable-next-line local/code-no-any-casts 1057: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/mcp/test/common/mcpManagementService.test.ts: +vscode • src/vs/platform/mcp/test/common/mcpManagementService.test.ts: 879: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/observable/common/wrapInHotClass.ts: +vscode • src/vs/platform/observable/common/wrapInHotClass.ts: 12: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/observable/common/wrapInReloadableClass.ts: +vscode • src/vs/platform/observable/common/wrapInReloadableClass.ts: 31: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/policy/node/nativePolicyService.ts: +vscode • src/vs/platform/policy/node/nativePolicyService.ts: 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/product/common/product.ts: +vscode • src/vs/platform/product/common/product.ts: 18: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/profiling/common/profilingTelemetrySpec.ts: +vscode • src/vs/platform/profiling/common/profilingTelemetrySpec.ts: 73: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/quickinput/browser/tree/quickTree.ts: +vscode • src/vs/platform/quickinput/browser/tree/quickTree.ts: 73: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/quickinput/test/browser/quickinput.test.ts: +vscode • src/vs/platform/quickinput/test/browser/quickinput.test.ts: 69: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/browser/browserSocketFactory.ts: +vscode • src/vs/platform/remote/browser/browserSocketFactory.ts: 89: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/common/remoteAgentConnection.ts: +vscode • src/vs/platform/remote/common/remoteAgentConnection.ts: 784: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: +vscode • src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/request/electron-utility/requestService.ts: +vscode • src/vs/platform/request/electron-utility/requestService.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/telemetry/test/browser/telemetryService.test.ts: +vscode • src/vs/platform/telemetry/test/browser/telemetryService.test.ts: 281: // eslint-disable-next-line local/code-no-any-casts 312: // eslint-disable-next-line local/code-no-any-casts 336: // eslint-disable-next-line local/code-no-any-casts @@ -975,84 +831,77 @@ src/vs/platform/telemetry/test/browser/telemetryService.test.ts: 694: // eslint-disable-next-line local/code-no-any-casts 738: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/terminal/common/terminalProfiles.ts: - 119: // eslint-disable-next-line local/code-no-any-casts - 121: // eslint-disable-next-line local/code-no-any-casts - -src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts: - 644: // eslint-disable-next-line local/code-no-any-casts - -src/vs/platform/terminal/node/terminalProcess.ts: - 545: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/platform/terminal/node/terminalProcess.ts: + 546: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/userDataSync/common/extensionsSync.ts: +vscode • src/vs/platform/userDataSync/common/extensionsSync.ts: 60: // eslint-disable-next-line local/code-no-any-casts 64: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: +vscode • src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: 22: // eslint-disable-next-line local/code-no-any-casts -src/vs/server/node/extensionHostConnection.ts: +vscode • src/vs/server/node/extensionHostConnection.ts: 240: // eslint-disable-next-line local/code-no-any-casts -src/vs/server/node/remoteExtensionHostAgentServer.ts: +vscode • src/vs/server/node/remoteExtensionHostAgentServer.ts: 765: // eslint-disable-next-line local/code-no-any-casts 767: // eslint-disable-next-line local/code-no-any-casts 769: // eslint-disable-next-line local/code-no-any-casts -src/vs/server/node/server.main.ts: +vscode • src/vs/server/node/server.main.ts: 20: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/workbench.web.main.internal.ts: +vscode • src/vs/workbench/workbench.web.main.internal.ts: 198: // eslint-disable-next-line local/code-no-any-casts 223: // eslint-disable-next-line local/code-no-any-casts 225: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/workbench.web.main.ts: +vscode • src/vs/workbench/workbench.web.main.ts: 58: // eslint-disable-next-line local/code-no-any-casts 60: // eslint-disable-next-line local/code-no-any-casts 82: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadChatAgents2.ts: +vscode • src/vs/workbench/api/browser/mainThreadChatAgents2.ts: 365: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadExtensionService.ts: +vscode • src/vs/workbench/api/browser/mainThreadExtensionService.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: +vscode • src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: 1000: // eslint-disable-next-line local/code-no-any-casts 1011: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadQuickOpen.ts: +vscode • src/vs/workbench/api/browser/mainThreadQuickOpen.ts: 195: // eslint-disable-next-line local/code-no-any-casts 198: // eslint-disable-next-line local/code-no-any-casts 203: // eslint-disable-next-line local/code-no-any-casts 216: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadTelemetry.ts: +vscode • src/vs/workbench/api/browser/mainThreadTelemetry.ts: 58: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadTreeViews.ts: +vscode • src/vs/workbench/api/browser/mainThreadTreeViews.ts: 378: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/viewsExtensionPoint.ts: +vscode • src/vs/workbench/api/browser/viewsExtensionPoint.ts: 528: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHost.api.impl.ts: +vscode • src/vs/workbench/api/common/extHost.api.impl.ts: 161: // eslint-disable-next-line local/code-no-any-casts 315: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts 563: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHost.protocol.ts: +vscode • src/vs/workbench/api/common/extHost.protocol.ts: 2106: // eslint-disable-next-line local/code-no-any-casts 2108: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostComments.ts: +vscode • src/vs/workbench/api/common/extHostComments.ts: 672: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostDebugService.ts: +vscode • src/vs/workbench/api/common/extHostDebugService.ts: 243: // eslint-disable-next-line local/code-no-any-casts 491: // eslint-disable-next-line local/code-no-any-casts 493: // eslint-disable-next-line local/code-no-any-casts @@ -1061,55 +910,55 @@ src/vs/workbench/api/common/extHostDebugService.ts: 770: // eslint-disable-next-line local/code-no-any-casts 778: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: +vscode • src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: 65: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostExtensionActivator.ts: +vscode • src/vs/workbench/api/common/extHostExtensionActivator.ts: 405: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostExtensionService.ts: +vscode • src/vs/workbench/api/common/extHostExtensionService.ts: 566: // eslint-disable-next-line local/code-no-any-casts 1009: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageFeatures.ts: +vscode • src/vs/workbench/api/common/extHostLanguageFeatures.ts: 197: // eslint-disable-next-line local/code-no-any-casts 714: // eslint-disable-next-line local/code-no-any-casts 735: // eslint-disable-next-line local/code-no-any-casts 748: // eslint-disable-next-line local/code-no-any-casts 771: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageModels.ts: +vscode • src/vs/workbench/api/common/extHostLanguageModels.ts: 174: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageModelTools.ts: +vscode • src/vs/workbench/api/common/extHostLanguageModelTools.ts: 221: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostMcp.ts: +vscode • src/vs/workbench/api/common/extHostMcp.ts: 163: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts 168: // eslint-disable-next-line local/code-no-any-casts 170: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostSCM.ts: +vscode • src/vs/workbench/api/common/extHostSCM.ts: 374: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostSearch.ts: +vscode • src/vs/workbench/api/common/extHostSearch.ts: 221: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTerminalService.ts: +vscode • src/vs/workbench/api/common/extHostTerminalService.ts: 1287: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTimeline.ts: +vscode • src/vs/workbench/api/common/extHostTimeline.ts: 160: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts 169: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTunnelService.ts: +vscode • src/vs/workbench/api/common/extHostTunnelService.ts: 133: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTypeConverters.ts: +vscode • src/vs/workbench/api/common/extHostTypeConverters.ts: 462: // eslint-disable-next-line local/code-no-any-casts 855: // eslint-disable-next-line local/code-no-any-casts 3141: // eslint-disable-next-line local/code-no-any-casts @@ -1122,21 +971,21 @@ src/vs/workbench/api/common/extHostTypeConverters.ts: 3155: // eslint-disable-next-line local/code-no-any-casts 3162: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTypes.ts: +vscode • src/vs/workbench/api/common/extHostTypes.ts: 3175: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extensionHostProcess.ts: +vscode • src/vs/workbench/api/node/extensionHostProcess.ts: 107: // eslint-disable-next-line local/code-no-any-casts 119: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extHostConsoleForwarder.ts: +vscode • src/vs/workbench/api/node/extHostConsoleForwarder.ts: 31: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extHostMcpNode.ts: +vscode • src/vs/workbench/api/node/extHostMcpNode.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/proxyResolver.ts: +vscode • src/vs/workbench/api/node/proxyResolver.ts: 92: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts @@ -1145,21 +994,21 @@ src/vs/workbench/api/node/proxyResolver.ts: 132: // eslint-disable-next-line local/code-no-any-casts 373: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: 870: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: +vscode • src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: 75: // eslint-disable-next-line local/code-no-any-casts 164: // eslint-disable-next-line local/code-no-any-casts 173: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostCommands.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostCommands.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: 750: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts 48: // eslint-disable-next-line local/code-no-any-casts 50: // eslint-disable-next-line local/code-no-any-casts @@ -1167,17 +1016,17 @@ src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 54: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: 1068: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: 164: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 107: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 111: // eslint-disable-next-line local/code-no-any-casts @@ -1185,33 +1034,33 @@ src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 121: // eslint-disable-next-line local/code-no-any-casts 128: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTesting.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTesting.test.ts: 640: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: 265: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 327: // eslint-disable-next-line local/code-no-any-casts 340: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: 771: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTypes.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTypes.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts 209: // eslint-disable-next-line local/code-no-any-casts 211: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: 541: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: 115: // eslint-disable-next-line local/code-no-any-casts 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: 141: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts 230: // eslint-disable-next-line local/code-no-any-casts @@ -1222,32 +1071,32 @@ src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: 508: // eslint-disable-next-line local/code-no-any-casts 512: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts 93: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: 115: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: 60: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/extensionHostMain.test.ts: +vscode • src/vs/workbench/api/test/common/extensionHostMain.test.ts: 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: +vscode • src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/testRPCProtocol.ts: +vscode • src/vs/workbench/api/test/common/testRPCProtocol.ts: 36: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/node/extHostSearch.test.ts: +vscode • src/vs/workbench/api/test/node/extHostSearch.test.ts: 177: // eslint-disable-next-line local/code-no-any-casts 1004: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/worker/extensionHostWorker.ts: +vscode • src/vs/workbench/api/worker/extensionHostWorker.ts: 83: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 87: // eslint-disable-next-line local/code-no-any-casts @@ -1261,71 +1110,71 @@ src/vs/workbench/api/worker/extensionHostWorker.ts: 106: // eslint-disable-next-line local/code-no-any-casts 158: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/worker/extHostConsoleForwarder.ts: +vscode • src/vs/workbench/api/worker/extHostConsoleForwarder.ts: 20: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/layout.ts: +vscode • src/vs/workbench/browser/layout.ts: 428: // eslint-disable-next-line local/code-no-any-casts 430: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/window.ts: +vscode • src/vs/workbench/browser/window.ts: 145: // eslint-disable-next-line local/code-no-any-casts 155: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/workbench.ts: +vscode • src/vs/workbench/browser/workbench.ts: 208: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/actions/developerActions.ts: +vscode • src/vs/workbench/browser/actions/developerActions.ts: 779: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/parts/editor/editorStatus.ts: +vscode • src/vs/workbench/browser/parts/editor/editorStatus.ts: 1377: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/parts/titlebar/titlebarPart.ts: +vscode • src/vs/workbench/browser/parts/titlebar/titlebarPart.ts: 494: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/parts/views/viewPaneContainer.ts: +vscode • src/vs/workbench/browser/parts/views/viewPaneContainer.ts: 687: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: +vscode • src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: 54: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: +vscode • src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: 29: // eslint-disable-next-line local/code-no-any-casts 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatViewPane.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatViewPane.ts: 304: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts: 125: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts: 244: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: 88: // eslint-disable-next-line local/code-no-any-casts 90: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: 126: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatAgents.ts: +vscode • src/vs/workbench/contrib/chat/common/chatAgents.ts: 746: // eslint-disable-next-line local/code-no-any-casts 748: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatModel.ts: - 1213: // eslint-disable-next-line local/code-no-any-casts - 1526: // eslint-disable-next-line local/code-no-any-casts - 1858: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/chat/common/chatModel.ts: + 1214: // eslint-disable-next-line local/code-no-any-casts + 1531: // eslint-disable-next-line local/code-no-any-casts + 1863: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatModes.ts: +vscode • src/vs/workbench/contrib/chat/common/chatModes.ts: 236: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: +vscode • src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: 439: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: 30: // eslint-disable-next-line local/code-no-any-casts 35: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts @@ -1346,19 +1195,19 @@ src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test 1532: // eslint-disable-next-line local/code-no-any-casts 1537: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: 41: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: 35: // eslint-disable-next-line local/code-no-any-casts 144: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts: 981: // eslint-disable-next-line local/code-no-any-casts 1012: // eslint-disable-next-line local/code-no-any-casts 1519: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: +vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 48: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts 87: // eslint-disable-next-line local/code-no-any-casts @@ -1383,118 +1232,118 @@ src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 333: // eslint-disable-next-line local/code-no-any-casts 349: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/common/chatService.test.ts: +vscode • src/vs/workbench/contrib/chat/test/common/chatService.test.ts: 149: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: +vscode • src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts: +vscode • src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts: 168: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: +vscode • src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: 189: // eslint-disable-next-line local/code-no-any-casts 196: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: +vscode • src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: 93: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/browser/debugSession.ts: +vscode • src/vs/workbench/contrib/debug/browser/debugSession.ts: 1193: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: +vscode • src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: 46: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: +vscode • src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: 813: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/common/debugger.ts: +vscode • src/vs/workbench/contrib/debug/common/debugger.ts: 168: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/common/debugUtils.ts: +vscode • src/vs/workbench/contrib/debug/common/debugUtils.ts: 66: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: 450: // eslint-disable-next-line local/code-no-any-casts 466: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts 124: // eslint-disable-next-line local/code-no-any-casts 206: // eslint-disable-next-line local/code-no-any-casts 261: // eslint-disable-next-line local/code-no-any-casts 461: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts 129: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: 76: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/repl.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/repl.test.ts: 139: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: +vscode • src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: 50: // eslint-disable-next-line local/code-no-any-casts 62: // eslint-disable-next-line local/code-no-any-casts 70: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/common/mockDebug.ts: +vscode • src/vs/workbench/contrib/debug/test/common/mockDebug.ts: 695: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: +vscode • src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: +vscode • src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: 147: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: +vscode • src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: 1182: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: +vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 101: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: +vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: 100: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: +vscode • src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: +vscode • src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: 94: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: +vscode • src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: 160: // eslint-disable-next-line local/code-no-any-casts - 625: // eslint-disable-next-line local/code-no-any-casts - 674: // eslint-disable-next-line local/code-no-any-casts + 662: // eslint-disable-next-line local/code-no-any-casts + 711: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: +vscode • src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markers/browser/markersTable.ts: +vscode • src/vs/workbench/contrib/markers/browser/markersTable.ts: 343: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: +vscode • src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: 143: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mcp/common/uriTemplate.ts: +vscode • src/vs/workbench/contrib/mcp/common/uriTemplate.ts: 159: // eslint-disable-next-line local/code-no-any-casts 191: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: +vscode • src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: 68: // eslint-disable-next-line local/code-no-any-casts 212: // eslint-disable-next-line local/code-no-any-casts 218: // eslint-disable-next-line local/code-no-any-casts @@ -1503,10 +1352,10 @@ src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: 271: // eslint-disable-next-line local/code-no-any-casts 280: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: +vscode • src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: +vscode • src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: 18: // eslint-disable-next-line local/code-no-any-casts 52: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts @@ -1517,101 +1366,104 @@ src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: 231: // eslint-disable-next-line local/code-no-any-casts 254: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mergeEditor/browser/utils.ts: +vscode • src/vs/workbench/contrib/mergeEditor/browser/utils.ts: 89: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: +vscode • src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: 69: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: +vscode • src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: 309: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: +vscode • src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: 127: // eslint-disable-next-line local/code-no-any-casts 199: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: +vscode • src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: 74: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: +vscode • src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: 122: // eslint-disable-next-line local/code-no-any-casts 1462: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: 329: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: 170: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: 424: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: +vscode • src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: 563: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: +vscode • src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: 1095: // eslint-disable-next-line local/code-no-any-casts 1137: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: 50: // eslint-disable-next-line local/code-no-any-casts 66: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: 284: // eslint-disable-next-line local/code-no-any-casts 308: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: 37: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: 26: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: 652: // eslint-disable-next-line local/code-no-any-casts 654: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchActionsFind.ts: +vscode • src/vs/workbench/contrib/scm/browser/activity.ts: + 154: // eslint-disable-next-line local/code-no-any-casts + +vscode • src/vs/workbench/contrib/search/browser/searchActionsFind.ts: 460: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchMessage.ts: +vscode • src/vs/workbench/contrib/search/browser/searchMessage.ts: 51: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: +vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: 306: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: +vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: 299: // eslint-disable-next-line local/code-no-any-casts 301: // eslint-disable-next-line local/code-no-any-casts 318: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: +vscode • src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: 201: // eslint-disable-next-line local/code-no-any-casts 229: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: 205: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: 155: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: 328: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts: +vscode • src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts: 157: // eslint-disable-next-line local/code-no-any-casts 168: // eslint-disable-next-line local/code-no-any-casts 284: // eslint-disable-next-line local/code-no-any-casts @@ -1635,7 +1487,7 @@ src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts: 810: // eslint-disable-next-line local/code-no-any-casts 813: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: +vscode • src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: 1491: // eslint-disable-next-line local/code-no-any-casts 1500: // eslint-disable-next-line local/code-no-any-casts 1539: // eslint-disable-next-line local/code-no-any-casts @@ -1651,48 +1503,33 @@ src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: 3723: // eslint-disable-next-line local/code-no-any-casts 3782: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/common/problemMatcher.ts: +vscode • src/vs/workbench/contrib/tasks/common/problemMatcher.ts: 361: // eslint-disable-next-line local/code-no-any-casts 374: // eslint-disable-next-line local/code-no-any-casts 1015: // eslint-disable-next-line local/code-no-any-casts 1906: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: +vscode • src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: 1720: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/common/tasks.ts: +vscode • src/vs/workbench/contrib/tasks/common/tasks.ts: 655: // eslint-disable-next-line local/code-no-any-casts 696: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: +vscode • src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts 86: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts: - 290: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminal/browser/terminalInstance.ts: - 2145: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts: - 448: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts: - 636: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts: - 244: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 445: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: 55: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: 101: // eslint-disable-next-line local/code-no-any-casts 127: // eslint-disable-next-line local/code-no-any-casts 172: // eslint-disable-next-line local/code-no-any-casts @@ -1701,7 +1538,7 @@ src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integratio 261: // eslint-disable-next-line local/code-no-any-casts 275: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 58: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1717,7 +1554,7 @@ src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 165: // eslint-disable-next-line local/code-no-any-casts 178: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: 31: // eslint-disable-next-line local/code-no-any-casts 37: // eslint-disable-next-line local/code-no-any-casts 45: // eslint-disable-next-line local/code-no-any-casts @@ -1734,7 +1571,7 @@ src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilitySt 132: // eslint-disable-next-line local/code-no-any-casts 141: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: 59: // eslint-disable-next-line local/code-no-any-casts 67: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1744,46 +1581,46 @@ src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integ 107: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 70: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: 71: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: 379: // eslint-disable-next-line local/code-no-any-casts 833: // eslint-disable-next-line local/code-no-any-casts 857: // eslint-disable-next-line local/code-no-any-casts 862: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: +vscode • src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: 94: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: 102: // eslint-disable-next-line local/code-no-any-casts 108: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: 152: // eslint-disable-next-line local/code-no-any-casts 351: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: 242: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: 150: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 553: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts @@ -1795,127 +1632,123 @@ src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDete 136: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: +vscode • src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: 409: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: +vscode • src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: 685: // eslint-disable-next-line local/code-no-any-casts 711: // eslint-disable-next-line local/code-no-any-casts 786: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: +vscode • src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: +vscode • src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: +vscode • src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/common/observableUtils.ts: +vscode • src/vs/workbench/contrib/testing/common/observableUtils.ts: 17: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/common/testingStates.ts: +vscode • src/vs/workbench/contrib/testing/common/testingStates.ts: 78: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: +vscode • src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: +vscode • src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: +vscode • src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: 35: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: +vscode • src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts 154: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: +vscode • src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: 27: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: +vscode • src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: +vscode • src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: 210: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/common/testStubs.ts: +vscode • src/vs/workbench/contrib/testing/test/common/testStubs.ts: 85: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/themes/browser/themes.contribution.ts: +vscode • src/vs/workbench/contrib/themes/browser/themes.contribution.ts: 614: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/timeline/browser/timelinePane.ts: +vscode • src/vs/workbench/contrib/timeline/browser/timelinePane.ts: 824: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: +vscode • src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: 68: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: +vscode • src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: 600: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts: - 128: // eslint-disable-next-line local/code-no-any-casts - 131: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/services/assignment/common/assignmentFilters.ts: +vscode • src/vs/workbench/services/assignment/common/assignmentFilters.ts: 104: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: +vscode • src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: 236: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: +vscode • src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: 81: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 306: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/configurationResolver/common/variableResolver.ts: +vscode • src/vs/workbench/services/configurationResolver/common/variableResolver.ts: 93: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: +vscode • src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: 516: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/driver/browser/driver.ts: +vscode • src/vs/workbench/services/driver/browser/driver.ts: 193: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: +vscode • src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: 61: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/common/extensionsRegistry.ts: +vscode • src/vs/workbench/services/extensions/common/extensionsRegistry.ts: 229: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: +vscode • src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 54: // eslint-disable-next-line local/code-no-any-casts 97: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 107: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: +vscode • src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: 185: // eslint-disable-next-line local/code-no-any-casts 222: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: +vscode • src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/keybinding/browser/keybindingService.ts: +vscode • src/vs/workbench/services/keybinding/browser/keybindingService.ts: 876: // eslint-disable-next-line local/code-no-any-casts 888: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts: +vscode • src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts: 46: // eslint-disable-next-line local/code-no-any-casts 402: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/remote/common/tunnelModel.ts: +vscode • src/vs/workbench/services/remote/common/tunnelModel.ts: 255: // eslint-disable-next-line local/code-no-any-casts 260: // eslint-disable-next-line local/code-no-any-casts 262: // eslint-disable-next-line local/code-no-any-casts @@ -1923,32 +1756,29 @@ src/vs/workbench/services/remote/common/tunnelModel.ts: 335: // eslint-disable-next-line local/code-no-any-casts 398: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/search/common/search.ts: +vscode • src/vs/workbench/services/search/common/search.ts: 628: // eslint-disable-next-line local/code-no-any-casts 631: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/search/node/rawSearchService.ts: +vscode • src/vs/workbench/services/search/node/rawSearchService.ts: 438: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: +vscode • src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: 44: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: +vscode • src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: 147: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/browser/fileIconThemeData.ts: +vscode • src/vs/workbench/services/themes/browser/fileIconThemeData.ts: 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/browser/productIconThemeData.ts: +vscode • src/vs/workbench/services/themes/browser/productIconThemeData.ts: 123: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/common/colorThemeData.ts: +vscode • src/vs/workbench/services/themes/common/colorThemeData.ts: 650: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/views/browser/viewsService.ts: - 689: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: +vscode • src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: 67: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts @@ -1972,7 +1802,7 @@ src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: 755: // eslint-disable-next-line local/code-no-any-casts 833: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: +vscode • src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: 25: // eslint-disable-next-line local/code-no-any-casts 27: // eslint-disable-next-line local/code-no-any-casts 335: // eslint-disable-next-line local/code-no-any-casts @@ -1982,61 +1812,58 @@ src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: 645: // eslint-disable-next-line local/code-no-any-casts 678: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts: - 116: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts: +vscode • src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts: 112: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/part.test.ts: +vscode • src/vs/workbench/test/browser/part.test.ts: 133: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/window.test.ts: +vscode • src/vs/workbench/test/browser/window.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/workbenchTestServices.ts: +vscode • src/vs/workbench/test/browser/workbenchTestServices.ts: 305: // eslint-disable-next-line local/code-no-any-casts 697: // eslint-disable-next-line local/code-no-any-casts 1064: // eslint-disable-next-line local/code-no-any-casts 1967: // eslint-disable-next-line local/code-no-any-casts 1985: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: 98: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: 131: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts 127: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/common/resources.test.ts: +vscode • src/vs/workbench/test/common/resources.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/common/workbenchTestServices.ts: +vscode • src/vs/workbench/test/common/workbenchTestServices.ts: 291: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/electron-browser/workbenchTestServices.ts: +vscode • src/vs/workbench/test/electron-browser/workbenchTestServices.ts: 255: // eslint-disable-next-line local/code-no-any-casts -test/automation/src/code.ts: +vscode • test/automation/src/code.ts: 127: // eslint-disable-next-line local/code-no-any-casts -test/automation/src/terminal.ts: +vscode • test/automation/src/terminal.ts: 315: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/application.ts: +vscode • test/mcp/src/application.ts: 309: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/playwright.ts: +vscode • test/mcp/src/playwright.ts: 17: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/automationTools/problems.ts: +vscode • test/mcp/src/automationTools/problems.ts: 76: // eslint-disable-next-line local/code-no-any-casts From 2007228fd1ddf7ef7059ad5c1f81229b7f1d67c6 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 2 Oct 2025 17:58:00 -0700 Subject: [PATCH 0829/4355] Support switching to custom model via url --- .../contrib/chat/browser/chatSetup.ts | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 0f4287c529c..d87e243fe74 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -65,7 +65,7 @@ import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAge import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../services/chat/common/chatEntitlementService.js'; import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../common/chatModel.js'; -import { ChatMode } from '../common/chatModes.js'; +import { ChatMode, IChatModeService } from '../common/chatModes.js'; import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; @@ -961,7 +961,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr }); } - override async run(accessor: ServicesAccessor, mode?: ChatModeKind, options?: { forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous }): Promise { + override async run(accessor: ServicesAccessor, mode?: ChatModeKind | string, options?: { forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous }): Promise { const viewsService = accessor.get(IViewsService); const layoutService = accessor.get(IWorkbenchLayoutService); const instantiationService = accessor.get(IInstantiationService); @@ -1197,7 +1197,26 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const params = new URLSearchParams(url.query); this.telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined }); - await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, validateChatMode(params.get('mode'))); + const modeParam = params.get('mode'); + let modeToUse: ChatModeKind | string | undefined = validateChatMode(modeParam); + + // If it's not a builtin mode, check if it's a valid custom mode + if (!modeToUse && modeParam) { + const chatModeService = this.instantiationService.invokeFunction(accessor => accessor.get(IChatModeService)); + // check if the given param is a valid mode ID + let foundModel = chatModeService.findModeById(modeParam); + if (!foundModel) { + // if not, check if the given param is a valid mode name, note the name is case sensitive + foundModel = chatModeService.findModeByName(modeParam); + } + + if (foundModel) { + modeToUse = foundModel.id; + } + } + + // execute the command to change the mode in panel, note that the command only support model IDs, not names + await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); return true; } From 05c85a05eb803c346c16609e5d16263a87115785 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 3 Oct 2025 15:22:05 -0700 Subject: [PATCH 0830/4355] Updated per code reviews --- .../contrib/chat/browser/chatSetup.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index d87e243fe74..83a08dc8831 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -69,7 +69,7 @@ import { ChatMode, IChatModeService } from '../common/chatModes.js'; import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode } from '../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; import { ILanguageModelsService } from '../common/languageModels.js'; import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID, CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from './actions/chatActions.js'; import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; @@ -1198,24 +1198,23 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr this.telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined }); const modeParam = params.get('mode'); - let modeToUse: ChatModeKind | string | undefined = validateChatMode(modeParam); - - // If it's not a builtin mode, check if it's a valid custom mode - if (!modeToUse && modeParam) { + let modeToUse: ChatModeKind | string | undefined; + if (modeParam) { const chatModeService = this.instantiationService.invokeFunction(accessor => accessor.get(IChatModeService)); + // check if the given param is a valid mode ID - let foundModel = chatModeService.findModeById(modeParam); - if (!foundModel) { + let foundMode = chatModeService.findModeById(modeParam); + if (!foundMode) { // if not, check if the given param is a valid mode name, note the name is case sensitive - foundModel = chatModeService.findModeByName(modeParam); + foundMode = chatModeService.findModeByName(modeParam); } - if (foundModel) { - modeToUse = foundModel.id; + if (foundMode) { + modeToUse = foundMode.id; } } - // execute the command to change the mode in panel, note that the command only support model IDs, not names + // execute the command to change the mode in panel, note that the command only support mode IDs, not names await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); return true; From 4aa07c50dc1de4c0dd894aada8737fda4944fc8a Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 3 Oct 2025 15:28:58 -0700 Subject: [PATCH 0831/4355] fix: update chat mode name checks to be case insensitive --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 2 +- src/vs/workbench/contrib/chat/common/chatModes.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 83a08dc8831..55e43e9fde7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1205,7 +1205,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr // check if the given param is a valid mode ID let foundMode = chatModeService.findModeById(modeParam); if (!foundMode) { - // if not, check if the given param is a valid mode name, note the name is case sensitive + // if not, check if the given param is a valid mode name, note the name is case insensitive foundMode = chatModeService.findModeByName(modeParam); } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 5c87741a8d2..dde15ebd1e3 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -172,7 +172,8 @@ export class ChatModeService extends Disposable implements IChatModeService { } findModeByName(name: string): IChatMode | undefined { - return this.getBuiltinModes().find(mode => mode.name === name) ?? this.getCustomModes().find(mode => mode.name === name); + const lowerCasedName = name.toLocaleLowerCase(); + return this.getBuiltinModes().find(mode => mode.name.toLocaleLowerCase() === lowerCasedName) ?? this.getCustomModes().find(mode => mode.name.toLocaleLowerCase() === lowerCasedName); } private getBuiltinModes(): IChatMode[] { From e6cffb21362b6d7addb977ff7db14f0681228780 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:03:06 +0200 Subject: [PATCH 0832/4355] Update grammars (#270045) --- extensions/csharp/cgmanifest.json | 2 +- .../csharp/syntaxes/csharp.tmLanguage.json | 8 +- extensions/fsharp/cgmanifest.json | 2 +- .../fsharp/syntaxes/fsharp.tmLanguage.json | 4 +- extensions/latex/cgmanifest.json | 4 +- .../latex/syntaxes/LaTeX.tmLanguage.json | 6018 +++++++++-------- extensions/swift/cgmanifest.json | 2 +- .../swift/syntaxes/swift.tmLanguage.json | 53 +- 8 files changed, 3164 insertions(+), 2929 deletions(-) diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 58ae5ece50a..de6d5f6d89c 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "1381bedfb087c18aca67af8278050d11bc9d9349" + "commitHash": "c32388ec18690abefb37cbaffa687a338c87d016" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 1afcc3053b6..007fb719459 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/1381bedfb087c18aca67af8278050d11bc9d9349", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/c32388ec18690abefb37cbaffa687a338c87d016", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -3423,7 +3423,7 @@ ] }, "interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{]|^)((?:\\{\\{)*)(\\{)(?=[^\\{])", "beginCaptures": { "1": { @@ -3578,7 +3578,7 @@ } }, "raw-interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{]|^)((?:\\{)*)(\\{)(?=[^\\{])", "beginCaptures": { "1": { @@ -3601,7 +3601,7 @@ ] }, "double-raw-interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{][^\\{]|^)((?:\\{)*)(\\{\\{)(?=[^\\{])", "beginCaptures": { "1": { diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index c36d14e1d88..d5c42026169 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "be3c51d2c28d3aaacd89ecd067e766bebe387f89" + "commitHash": "0cb968a4b8fdb2e0656b95342cdffbeff04a1248" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 97f607a6b22..fd1b6b09aa1 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/ionide/ionide-fsgrammar/commit/be3c51d2c28d3aaacd89ecd067e766bebe387f89", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/0cb968a4b8fdb2e0656b95342cdffbeff04a1248", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -1831,7 +1831,7 @@ "patterns": [ { "name": "keyword.control.directive.fsharp", - "match": "\\s?(#if|#elif|#elseif|#else|#endif|#light|#nowarn)", + "match": "\\s?(#if|#elif|#elseif|#else|#endif|#light|#nowarn|#warnon)", "captures": {} } ] diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index 89ba7c1b8b5..c23c98bd760 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "e5d3d4b46731ed37119a5296c12eee7669284eeb" + "commitHash": "84ce12aa6be384369ff218ac25efb27e6f34e78c" } }, "license": "MIT", - "version": "1.14.0", + "version": "1.15.0", "description": "The files in syntaxes/ were originally part of https://github.com/James-Yu/LaTeX-Workshop. They have been extracted in the hope that they can useful outside of the LaTeX-Workshop extension.", "licenseDetail": [ "Copyright (c) vscode-latex-basics authors", diff --git a/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/extensions/latex/syntaxes/LaTeX.tmLanguage.json index 42780b65056..0ff855afddc 100644 --- a/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -4,16 +4,81 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/e5d3d4b46731ed37119a5296c12eee7669284eeb", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/84ce12aa6be384369ff218ac25efb27e6f34e78c", "name": "LaTeX", "scopeName": "text.tex.latex", "patterns": [ { - "comment": "This scope identifies partially typed commands such as `\\tab`. We use this to trigger “Command Completion” only when it makes sense.", "match": "(?<=\\\\[\\w@]|\\\\[\\w@]{2}|\\\\[\\w@]{3}|\\\\[\\w@]{4}|\\\\[\\w@]{5}|\\\\[\\w@]{6})\\s", + "comment": "This scope identifies partially typed commands such as `\\tab`. We use this to trigger “Command Completion” only when it makes sense.", "name": "meta.space-after-command.latex" }, { + "include": "#songs-env" + }, + { + "include": "#embedded-code-env" + }, + { + "include": "#verbatim-env" + }, + { + "include": "#document-env" + }, + { + "include": "#all-balanced-env" + }, + { + "include": "#documentclass-usepackage-macro" + }, + { + "include": "#input-macro" + }, + { + "include": "#sections-macro" + }, + { + "include": "#hyperref-macro" + }, + { + "include": "#newcommand-macro" + }, + { + "include": "#text-font-macro" + }, + { + "include": "#citation-macro" + }, + { + "include": "#references-macro" + }, + { + "include": "#label-macro" + }, + { + "include": "#verb-macro" + }, + { + "include": "#inline-code-macro" + }, + { + "include": "#all-other-macro" + }, + { + "include": "#display-math" + }, + { + "include": "#inline-math" + }, + { + "include": "#column-specials" + }, + { + "include": "text.tex" + } + ], + "repository": { + "documentclass-usepackage-macro": { "begin": "((\\\\)(?:usepackage|documentclass))\\b(?=\\[|\\{)", "beginCaptures": { "1": { @@ -56,7 +121,38 @@ } ] }, - { + "document-env": { + "patterns": [ + { + "match": "(\\s*\\\\begin\\{document\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] + } + }, + "comment": "These two patterns match the \\begin{document} and \\end{document} commands, so that the environment matching pattern following them will ignore those commands.", + "name": "meta.function.begin-document.latex" + }, + { + "match": "(\\s*\\\\end\\{document\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] + } + }, + "name": "meta.function.end-document.latex" + } + ] + }, + "input-macro": { "begin": "((\\\\)(?:include|input))(\\{)", "beginCaptures": { "1": { @@ -82,7 +178,7 @@ } ] }, - { + "sections-macro": { "begin": "((\\\\)((?:sub){0,2}section|(?:sub)?paragraph|chapter|part|addpart|addchap|addsec|minisec|frametitle)(?:\\*)?)((?:\\[[^\\[]*?\\]){0,2})(\\{)", "beginCaptures": { "1": { @@ -120,2234 +216,2377 @@ } ] }, - { - "begin": "(\\s*\\\\begin\\{songs\\}\\{.*\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "contentName": "meta.data.environment.songs.latex", - "end": "(\\\\end\\{songs\\}(?:\\s*\\n)?)", - "name": "meta.function.environment.songs.latex", - "patterns": [ - { - "include": "text.tex.latex#songs-chords" - } - ] - }, - { - "comment": "This scope applies songs-environment coloring between \\\\beginsong and \\\\endsong. Useful in separate files without \\\\begin{songs}.", - "begin": "\\s*((\\\\)beginsong)(?=\\{)", - "captures": { - "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "((\\\\)endsong)(?:\\s*\\n)?", - "name": "meta.function.environment.song.latex", - "patterns": [ - { - "include": "#multiline-arg-no-highlight" - }, - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]|\\}))\\s*", - "end": "\\s*(?=\\\\endsong)", - "contentName": "meta.data.environment.song.latex", - "patterns": [ - { - "include": "text.tex.latex#songs-chords" - } - ] - } - ] - }, - { - "begin": "(?:^\\s*)?\\\\begin\\{(lstlisting|minted|pyglist)\\}(?=\\[|\\{)", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "end": "\\\\end\\{\\1\\}", + "text-font-macro": { "patterns": [ { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:asy|asymptote))(\\})", + "begin": "((\\\\)emph)(\\{)", "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.emph.latex" }, "2": { - "name": "variable.parameter.function.latex" + "name": "punctuation.definition.function.latex" }, "3": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.emph.begin.latex" + } + }, + "contentName": "markup.italic.emph.latex", + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.emph.end.latex" } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.asy", + "name": "meta.function.emph.latex", "patterns": [ { - "include": "source.asy" + "include": "text.tex#braces" + }, + { + "include": "$self" } ] }, { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:bash))(\\})", - "beginCaptures": { + "begin": "((\\\\)textit)(\\{)", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.textit.latex" }, "2": { - "name": "variable.parameter.function.latex" + "name": "punctuation.definition.function.latex" }, "3": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.textit.begin.latex" } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.shell", + "comment": "We put the keyword in a capture and name this capture, so that disabling spell checking for “keyword” won't be inherited by the argument to \\textit{...}.\n\nPut specific matches for particular LaTeX keyword.functions before the last two more general functions", + "contentName": "markup.italic.textit.latex", + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.textit.end.latex" + } + }, + "name": "meta.function.textit.latex", "patterns": [ { - "include": "source.shell" + "include": "text.tex#braces" + }, + { + "include": "$self" } ] }, { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:c|cpp))(\\})", - "beginCaptures": { + "begin": "((\\\\)textbf)(\\{)", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.textbf.latex" }, "2": { - "name": "variable.parameter.function.latex" + "name": "punctuation.definition.function.latex" }, "3": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.textbf.begin.latex" + } + }, + "contentName": "markup.bold.textbf.latex", + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.textbf.end.latex" } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.cpp.embedded.latex", + "name": "meta.function.textbf.latex", "patterns": [ { - "include": "source.cpp.embedded.latex" + "include": "text.tex#braces" + }, + { + "include": "$self" } ] }, { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:css))(\\})", - "beginCaptures": { + "begin": "((\\\\)texttt)(\\{)", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.texttt.latex" }, "2": { - "name": "variable.parameter.function.latex" + "name": "punctuation.definition.function.latex" }, "3": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.texttt.begin.latex" + } + }, + "contentName": "markup.raw.texttt.latex", + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.texttt.end.latex" } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.css", + "name": "meta.function.texttt.latex", "patterns": [ { - "include": "source.css" + "include": "text.tex#braces" + }, + { + "include": "$self" } ] - }, + } + ] + }, + "songs-env": { + "patterns": [ { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:gnuplot))(\\})", - "beginCaptures": { + "begin": "(\\s*\\\\begin\\{songs\\}\\{.*\\})", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.gnuplot", + "contentName": "meta.data.environment.songs.latex", + "end": "(\\\\end\\{songs\\}(?:\\s*\\n)?)", + "name": "meta.function.environment.songs.latex", "patterns": [ { - "include": "source.gnuplot" + "include": "text.tex.latex#songs-chords" } ] }, { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:hs|haskell))(\\})", - "beginCaptures": { + "comment": "This scope applies songs-environment coloring between \\\\beginsong and \\\\endsong. Useful in separate files without \\\\begin{songs}.", + "begin": "\\s*((\\\\)beginsong)(?=\\{)", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.be.latex" }, "2": { - "name": "variable.parameter.function.latex" + "name": "punctuation.definition.function.latex" }, "3": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "4": { "name": "punctuation.definition.arguments.end.latex" } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.haskell", + "end": "((\\\\)endsong)(?:\\s*\\n)?", + "name": "meta.function.environment.song.latex", "patterns": [ { - "include": "source.haskell" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:html))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "include": "#multiline-arg-no-highlight" }, - "2": { - "name": "variable.parameter.function.latex" + { + "include": "#multiline-optional-arg-no-highlight" }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "text.html", - "patterns": [ { - "include": "text.html.basic" + "begin": "(?:\\G|(?<=\\]|\\}))\\s*", + "end": "\\s*(?=\\\\endsong)", + "contentName": "meta.data.environment.song.latex", + "patterns": [ + { + "include": "text.tex.latex#songs-chords" + } + ] } ] - }, + } + ] + }, + "embedded-code-env": { + "patterns": [ { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:java))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.end.latex" + "begin": "(?:^\\s*)?\\\\begin\\{(lstlisting|minted|pyglist)\\}(?=\\[|\\{)", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.java", + "end": "\\\\end\\{\\1\\}", "patterns": [ { - "include": "source.java" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:jl|julia))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "include": "#multiline-optional-arg-no-highlight" }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.julia", - "patterns": [ { - "include": "source.julia" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:js|javascript))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:asy|asymptote))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.asy", + "patterns": [ + { + "include": "source.asy" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.js", - "patterns": [ { - "include": "source.js" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:lua))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:bash))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.shell", + "patterns": [ + { + "include": "source.shell" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.lua", - "patterns": [ { - "include": "source.lua" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:py|python|sage))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:c|cpp))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.cpp.embedded.latex", + "patterns": [ + { + "include": "source.cpp.embedded.latex" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.python", - "patterns": [ { - "include": "source.python" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:rb|ruby))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:css))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.css", + "patterns": [ + { + "include": "source.css" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.ruby", - "patterns": [ { - "include": "source.ruby" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:rust))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:gnuplot))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.gnuplot", + "patterns": [ + { + "include": "source.gnuplot" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.rust", - "patterns": [ { - "include": "source.rust" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:ts|typescript))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:hs|haskell))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.haskell", + "patterns": [ + { + "include": "source.haskell" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.ts", - "patterns": [ { - "include": "source.ts" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:xml))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:html))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "text.html", + "patterns": [ + { + "include": "text.html.basic" + } + ] }, - "2": { - "name": "variable.parameter.function.latex" + { + "begin": "(?:\\G|(?<=\\]))(\\{)((?:java))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.java", + "patterns": [ + { + "include": "source.java" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "text.xml", - "patterns": [ { - "include": "text.xml" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)((?:yaml))(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:jl|julia))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ] }, - "2": { - "name": "variable.parameter.function.latex" + { + "begin": "(?:\\G|(?<=\\]))(\\{)((?:js|javascript))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.js", + "patterns": [ + { + "include": "source.js" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", - "contentName": "source.yaml", - "patterns": [ { - "include": "source.yaml" - } - ] - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)([a-zA-Z]*)(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" + "begin": "(?:\\G|(?<=\\]))(\\{)((?:lua))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.lua", + "patterns": [ + { + "include": "source.lua" + } + ] }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "meta.function.embedded.latex", - "end": "^\\s*(?=\\\\end\\{(?:lstlisting|minted|pyglist)\\})", - "name": "meta.embedded.block.generic.latex" - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:asy|asycode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:asy|asycode)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:asy|asycode)\\*?\\})", - "contentName": "source.asymptote", - "patterns": [ - { - "include": "source.asymptote" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:cppcode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:cppcode)\\*?\\}", - "captures": { - "0": { - "patterns": [ { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:cppcode)\\*?\\})", - "contentName": "source.cpp.embedded.latex", - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:py|python|sage))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ] + }, { - "include": "source.cpp.embedded.latex" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:dot2tex|dotcode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:dot2tex|dotcode)\\*?\\}", - "captures": { - "0": { - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:rb|ruby))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.ruby", + "patterns": [ + { + "include": "source.ruby" + } + ] + }, { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:dot2tex|dotcode)\\*?\\})", - "contentName": "source.dot", - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:rust))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.rust", + "patterns": [ + { + "include": "source.rust" + } + ] + }, { - "include": "source.dot" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:gnuplot)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:gnuplot)\\*?\\}", - "captures": { - "0": { - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:ts|typescript))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.ts", + "patterns": [ + { + "include": "source.ts" + } + ] + }, { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:gnuplot)\\*?\\})", - "contentName": "source.gnuplot", - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:xml))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "text.xml", + "patterns": [ + { + "include": "text.xml" + } + ] + }, { - "include": "source.gnuplot" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:hscode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:hscode)\\*?\\}", - "captures": { - "0": { - "patterns": [ + "begin": "(?:\\G|(?<=\\]))(\\{)((?:yaml))(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "end": "^\\s*(?=\\\\end\\{(?:minted|lstlisting|pyglist)\\})", + "contentName": "source.yaml", + "patterns": [ + { + "include": "source.yaml" + } + ] + }, { - "include": "#begin-env-tokenizer" + "begin": "(?:\\G|(?<=\\]))(\\{)([a-zA-Z]*)(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "meta.function.embedded.latex", + "end": "^\\s*(?=\\\\end\\{(?:lstlisting|minted|pyglist)\\})", + "name": "meta.embedded.block.generic.latex" } ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:hscode)\\*?\\})", - "contentName": "source.haskell", - "patterns": [ - { - "include": "source.haskell" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\})", - "contentName": "source.java", - "patterns": [ - { - "include": "source.java" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\})", - "contentName": "source.julia", - "patterns": [ - { - "include": "source.julia" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\})", - "contentName": "source.julia", - "patterns": [ - { - "include": "source.julia" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:luacode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:luacode)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:luacode)\\*?\\})", - "contentName": "source.lua", - "patterns": [ - { - "include": "source.lua" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\})", - "contentName": "source.python", - "patterns": [ - { - "include": "source.python" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\})", - "contentName": "source.python", - "patterns": [ - { - "include": "source.python" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\})", - "contentName": "source.python", - "patterns": [ - { - "include": "source.python" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:scalacode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:scalacode)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:scalacode)\\*?\\})", - "contentName": "source.scala", - "patterns": [ - { - "include": "source.scala" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", - "end": "\\s*\\\\end\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\}", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "variable.parameter.function.latex" - }, - { - "begin": "^(?=\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\})", - "contentName": "source.python", - "patterns": [ - { - "include": "source.python" - } - ] - } - ] - }, - { - "begin": "\\s*\\\\begin\\{((?:[a-zA-Z]*code|lstlisting|minted|pyglist)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "contentName": "meta.function.embedded.latex", - "end": "\\\\end\\{\\1\\}(?:\\s*\\n)?", - "name": "meta.embedded.block.generic.latex" - }, - { - "begin": "((?:^\\s*)?\\\\begin\\{((?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?))\\})(?:\\[[^\\]]*\\]){,2}(?=\\{)", - "captures": { - "1": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "end": "(\\\\end\\{\\2\\})", - "patterns": [ - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:asy|asymptote)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:asy|asycode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:asy|asycode)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, - "patterns": [ - { - "include": "text.tex#braces" - }, - { - "include": "$self" - } - ] + "contentName": "variable.parameter.function.latex" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.asy", + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:asy|asycode)\\*?\\})", + "contentName": "source.asymptote", "patterns": [ { - "include": "source.asy" + "include": "source.asymptote" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:bash)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:cppcode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:cppcode)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:cppcode)\\*?\\})", + "contentName": "source.cpp.embedded.latex", "patterns": [ { - "include": "text.tex#braces" - }, + "include": "source.cpp.embedded.latex" + } + ] + } + ] + }, + { + "begin": "\\s*\\\\begin\\{(?:dot2tex|dotcode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:dot2tex|dotcode)\\*?\\}", + "captures": { + "0": { + "patterns": [ { - "include": "$self" + "include": "#macro-with-args-tokenizer" } ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.shell", + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:dot2tex|dotcode)\\*?\\})", + "contentName": "source.dot", "patterns": [ { - "include": "source.shell" + "include": "source.dot" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:c|cpp)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:gnuplot)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:gnuplot)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:gnuplot)\\*?\\})", + "contentName": "source.gnuplot", "patterns": [ { - "include": "text.tex#braces" - }, + "include": "source.gnuplot" + } + ] + } + ] + }, + { + "begin": "\\s*\\\\begin\\{(?:hscode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:hscode)\\*?\\}", + "captures": { + "0": { + "patterns": [ { - "include": "$self" + "include": "#macro-with-args-tokenizer" } ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.cpp.embedded.latex", + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:hscode)\\*?\\})", + "contentName": "source.haskell", "patterns": [ { - "include": "source.cpp.embedded.latex" + "include": "source.haskell" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:css)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\})", + "contentName": "source.java", "patterns": [ { - "include": "text.tex#braces" - }, + "include": "source.java" + } + ] + } + ] + }, + { + "begin": "\\s*\\\\begin\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ { - "include": "$self" + "include": "#macro-with-args-tokenizer" } ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.css", + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\})", + "contentName": "source.julia", "patterns": [ { - "include": "source.css" + "include": "source.julia" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:gnuplot)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?\\})", + "contentName": "source.julia", "patterns": [ { - "include": "text.tex#braces" - }, + "include": "source.julia" + } + ] + } + ] + }, + { + "begin": "\\s*\\\\begin\\{(?:luacode|luadraw)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:luacode|luadraw)\\*?\\}", + "captures": { + "0": { + "patterns": [ { - "include": "$self" + "include": "#macro-with-args-tokenizer" } ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.gnuplot", + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:luacode|luadraw)\\*?\\})", + "contentName": "source.lua", "patterns": [ { - "include": "source.gnuplot" + "include": "source.lua" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:hs|haskell)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?\\})", + "contentName": "source.python", "patterns": [ { - "include": "text.tex#braces" - }, + "include": "source.python" + } + ] + } + ] + }, + { + "begin": "\\s*\\\\begin\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ { - "include": "$self" + "include": "#macro-with-args-tokenizer" + } + ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" } - ] + }, + "contentName": "variable.parameter.function.latex" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.haskell", + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?\\})", + "contentName": "source.python", "patterns": [ { - "include": "source.haskell" + "include": "source.python" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:html)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, - "patterns": [ - { - "include": "text.tex#braces" - }, - { - "include": "$self" - } - ] + "contentName": "variable.parameter.function.latex" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "text.html", + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?\\})", + "contentName": "source.python", "patterns": [ { - "include": "text.html.basic" + "include": "source.python" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:java)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:scalacode)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:scalacode)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, - "patterns": [ - { - "include": "text.tex#braces" - }, - { - "include": "$self" - } - ] + "contentName": "variable.parameter.function.latex" }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.java", + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:scalacode)\\*?\\})", + "contentName": "source.scala", "patterns": [ { - "include": "source.java" + "include": "source.scala" } ] } ] }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:jl|julia)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" + "begin": "\\s*\\\\begin\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", "endCaptures": { "1": { "name": "punctuation.definition.arguments.end.latex" } }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?\\})", + "contentName": "source.python", "patterns": [ { - "include": "text.tex#braces" - }, - { - "include": "$self" + "include": "source.python" } ] - }, - { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.julia", + } + ] + }, + { + "begin": "\\s*\\\\begin\\{((?:[a-zA-Z]*code|lstlisting|minted|pyglist)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?", + "captures": { + "0": { "patterns": [ { - "include": "source.julia" + "include": "#macro-with-args-tokenizer" } ] } - ] + }, + "contentName": "meta.function.embedded.latex", + "end": "\\\\end\\{\\1\\}(?:\\s*\\n)?", + "name": "meta.embedded.block.generic.latex" }, { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:js|javascript)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { + "begin": "((?:^\\s*)?\\\\begin\\{((?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?))\\})(?:\\[[^\\]]*\\]){,2}(?=\\{)", + "captures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, + "end": "(\\\\end\\{\\2\\})", "patterns": [ { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:asy|asymptote)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.asy", + "patterns": [ + { + "include": "source.asy" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.js", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:bash)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.js" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.shell", + "patterns": [ + { + "include": "source.shell" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:lua)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:c|cpp)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.cpp.embedded.latex", + "patterns": [ + { + "include": "source.cpp.embedded.latex" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.lua", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:css)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.lua" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.css", + "patterns": [ + { + "include": "source.css" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:py|python|sage)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:gnuplot)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.gnuplot", + "patterns": [ + { + "include": "source.gnuplot" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.python", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:hs|haskell)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.python" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.haskell", + "patterns": [ + { + "include": "source.haskell" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:rb|ruby)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:html)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "text.html", + "patterns": [ + { + "include": "text.html.basic" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.ruby", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:java)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.ruby" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.java", + "patterns": [ + { + "include": "source.java" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:rust)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:jl|julia)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.rust", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:js|javascript)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.rust" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.js", + "patterns": [ + { + "include": "source.js" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:ts|typescript)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:lua)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.lua", + "patterns": [ + { + "include": "source.lua" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.ts", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:py|python|sage)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.ts" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:xml)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:rb|ruby)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.ruby", + "patterns": [ + { + "include": "source.ruby" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "text.xml", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:rust)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "text.xml" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.rust", + "patterns": [ + { + "include": "source.rust" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:yaml)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:ts|typescript)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.ts", + "patterns": [ + { + "include": "source.ts" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "source.yaml", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:xml)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "source.yaml" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "text.xml", + "patterns": [ + { + "include": "text.xml" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:tikz|tikzpicture)", - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:yaml)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "source.yaml", + "patterns": [ + { + "include": "source.yaml" + } + ] } ] }, { - "begin": "^(\\s*)", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "contentName": "text.tex.latex", + "begin": "\\G(\\{)(?:__|[a-z\\s]*)(?i:tikz|tikzpicture)", + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, "patterns": [ { - "include": "text.tex.latex" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "^(\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "contentName": "text.tex.latex", + "patterns": [ + { + "include": "text.tex.latex" + } + ] } ] - } - ] - }, - { - "begin": "\\G(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "patterns": [ + }, { - "begin": "\\G", - "end": "(\\})\\s*$", - "endCaptures": { + "begin": "\\G(\\{)", + "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.end.latex" + "name": "punctuation.definition.arguments.begin.latex" } }, + "end": "(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", "patterns": [ { - "include": "text.tex#braces" + "begin": "\\G", + "end": "(\\})\\s*$", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "patterns": [ + { + "include": "text.tex#braces" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "^(\\s*)", + "contentName": "meta.function.embedded.latex", + "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", + "name": "meta.embedded.block.generic.latex" } ] - }, - { - "begin": "^(\\s*)", - "contentName": "meta.function.embedded.latex", - "end": "^\\s*(?=\\\\end\\{(?:RobExt)?(?:CacheMeCode|PlaceholderPathFromCode\\*?|PlaceholderFromCode\\*?|SetPlaceholderCode\\*?)\\})", - "name": "meta.embedded.block.generic.latex" - } - ] - } - ] - }, - { - "begin": "(?:^\\s*)?\\\\begin\\{(terminal\\*?)\\}(?=\\[|\\{)", - "captures": { - "0": { - "patterns": [ - { - "include": "#begin-env-tokenizer" } ] - } - }, - "end": "\\\\end\\{\\1\\}", - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?:\\G|(?<=\\]))(\\{)([a-zA-Z]*)(\\})", - "beginCaptures": { - "1": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "2": { - "name": "variable.parameter.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "meta.function.embedded.latex", - "end": "^\\s*(?=\\\\end\\{terminal\\*?\\})", - "name": "meta.embedded.block.generic.latex" - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:asy|asymptote)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { + "begin": "(?:^\\s*)?\\\\begin\\{(terminal\\*?)\\}(?=\\[|\\{)", + "captures": { "0": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "contentName": "source.asy", + "end": "\\\\end\\{\\1\\}", "patterns": [ { - "include": "source.asy" + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)([a-zA-Z]*)(\\})", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "meta.function.embedded.latex", + "end": "^\\s*(?=\\\\end\\{terminal\\*?\\})", + "name": "meta.embedded.block.generic.latex" } ] } ] }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:bash)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, + "verbatim-env": { "patterns": [ { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "begin": "(\\s*\\\\begin\\{((?:fboxv|boxedv|V|v|spv)erbatim\\*?)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "contentName": "source.shell", - "patterns": [ - { - "include": "source.shell" - } - ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:c|cpp)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" + "contentName": "markup.raw.verbatim.latex", + "end": "(\\\\end\\{\\2\\})", + "name": "meta.function.verbatim.latex" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "begin": "(\\s*\\\\begin\\{VerbatimOut\\}\\{[^\\}]*\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "contentName": "source.cpp.embedded.latex", - "patterns": [ - { - "include": "source.cpp.embedded.latex" - } - ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:css)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" + "contentName": "markup.raw.verbatim.latex", + "end": "(\\\\end\\{\\VerbatimOut\\})", + "name": "meta.function.verbatim.latex" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "begin": "(\\s*\\\\begin\\{alltt\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "contentName": "source.css", + "contentName": "markup.raw.verbatim.latex", + "end": "(\\\\end\\{alltt\\})", + "name": "meta.function.alltt.latex", "patterns": [ { - "include": "source.css" + "captures": { + "1": { + "name": "punctuation.definition.function.latex" + } + }, + "match": "(\\\\)[A-Za-z]+", + "name": "support.function.general.latex" } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:gnuplot)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "begin": "(\\s*\\\\begin\\{([Cc]omment)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] } }, - "contentName": "source.gnuplot", - "patterns": [ - { - "include": "source.gnuplot" - } - ] + "contentName": "comment.line.percentage.latex", + "end": "(\\\\end\\{\\2\\})", + "name": "meta.function.verbatim.latex" } ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:hs|haskell)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, + }, + "hyperref-macro": { "patterns": [ { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "(?:\\s*)((\\\\)(?:href|hyperref|hyperimage))(?=\\[|\\{)", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" + "1": { + "name": "support.function.url.latex" } }, + "comment": "Captures \\command[option]{url}{optional category}{optional name}{text}", + "end": "(\\})", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.arguments.end.latex" } }, - "contentName": "source.haskell", + "name": "meta.function.hyperlink.latex", "patterns": [ { - "include": "source.haskell" + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)([^}]*)(\\})(?:\\{[^}]*\\}){2}?(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "markup.underline.link.latex" + }, + "3": { + "name": "punctuation.definition.arguments.end.latex" + }, + "4": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "contentName": "meta.variable.parameter.function.latex", + "end": "(?=\\})", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "(?:\\G|(?<=\\]))(?:(\\{)[^}]*(\\}))?(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "2": { + "name": "punctuation.definition.arguments.end.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "contentName": "meta.variable.parameter.function.latex", + "end": "(?=\\})", + "patterns": [ + { + "include": "$self" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:html)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { + "match": "(?:\\s*)((\\\\)(?:url|path))(\\{)([^}]*)(\\})", + "captures": { + "1": { + "name": "support.function.url.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { + }, + "4": { + "name": "markup.underline.link.latex" + }, + "5": { "name": "punctuation.definition.arguments.end.latex" } }, - "contentName": "text.html", - "patterns": [ - { - "include": "text.html.basic" - } - ] + "name": "meta.function.link.url.latex" } ] }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:java)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, + "inline-code-macro": { "patterns": [ { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { + "begin": "((\\\\)addplot)(?:\\+?)((?:\\[[^\\[]*\\]))*\\s*(gnuplot)\\s*((?:\\[[^\\[]*\\]))*\\s*(\\{)", + "captures": { + "1": { + "name": "support.function.be.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "4": { + "name": "variable.parameter.function.latex" + }, + "5": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "6": { "name": "punctuation.definition.arguments.begin.latex" } }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "source.java", + "end": "\\s*(\\};)", "patterns": [ { - "include": "source.java" + "begin": "%", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.latex" + } + }, + "end": "$\\n?", + "name": "comment.line.percentage.latex" + }, + { + "include": "source.gnuplot" } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:jl|julia)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { + "match": "((\\\\)(?:mint|mintinline))((?:\\[[^\\[]*?\\])?)(\\{)[a-zA-Z]*(\\})(?:(?:([^a-zA-Z\\{])(.*?)(\\6))|(?:(\\{)(.*?)(\\})))", + "captures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "4": { "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { + }, + "5": { "name": "punctuation.definition.arguments.end.latex" + }, + "6": { + "name": "punctuation.definition.verb.latex" + }, + "7": { + "name": "markup.raw.verb.latex" + }, + "8": { + "name": "punctuation.definition.verb.latex" + }, + "9": { + "name": "punctuation.definition.verb.latex" + }, + "10": { + "name": "markup.raw.verb.latex" + }, + "11": { + "name": "punctuation.definition.verb.latex" } }, - "contentName": "source.julia", - "patterns": [ - { - "include": "source.julia" - } - ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:js|javascript)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" + "name": "meta.function.verb.latex" }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" + "match": "((\\\\)[a-z]+inline)((?:\\[[^\\[]*?\\])?)(?:(?:([^a-zA-Z\\{])(.*?)(\\4))|(?:(\\{)(.*?)(\\})))", + "captures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "4": { + "name": "punctuation.definition.verb.latex" + }, + "5": { + "name": "markup.raw.verb.latex" + }, + "6": { + "name": "punctuation.definition.verb.latex" + }, + "7": { + "name": "punctuation.definition.verb.latex" + }, + "8": { + "name": "markup.raw.verb.latex" + }, + "9": { + "name": "punctuation.definition.verb.latex" + } + }, + "name": "meta.function.verb.latex" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" + "match": "((\\\\)(?:(?:py|pycon|pylab|pylabcon|sympy|sympycon)[cv]?|pyq|pycq|pyif))((?:\\[[^\\[]*?\\])?)(?:(?:([^a-zA-Z\\{])(.*?)(\\4))|(?:(\\{)(.*?)(\\})))", + "captures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "4": { + "name": "punctuation.definition.verb.latex" + }, + "5": { + "name": "source.python", + "patterns": [ + { + "include": "source.python" + } + ] + }, + "6": { + "name": "punctuation.definition.verb.latex" + }, + "7": { + "name": "punctuation.definition.verb.latex" + }, + "8": { + "name": "source.python", + "patterns": [ + { + "include": "source.python" + } + ] + }, + "9": { + "name": "punctuation.definition.verb.latex" } }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "name": "meta.function.verb.latex" + }, + { + "match": "((\\\\)(?:jl|julia)[cv]?)((?:\\[[^\\[]*?\\])?)(?:(?:([^a-zA-Z\\{])(.*?)(\\4))|(?:(\\{)(.*?)(\\})))", + "captures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { + "patterns": [ + { + "include": "#optional-arg-bracket" + } + ] + }, + "4": { + "name": "punctuation.definition.verb.latex" + }, + "5": { + "name": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ] + }, + "6": { + "name": "punctuation.definition.verb.latex" + }, + "7": { + "name": "punctuation.definition.verb.latex" + }, + "8": { + "name": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ] + }, + "9": { + "name": "punctuation.definition.verb.latex" } }, - "contentName": "source.js", - "patterns": [ - { - "include": "source.js" - } - ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:lua)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" + "name": "meta.function.verb.latex" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)(?:directlua|luadirect|luaexec))(\\{)", "beginCaptures": { - "0": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + }, + "3": { "name": "punctuation.definition.arguments.begin.latex" } }, + "end": "\\}", "endCaptures": { "0": { "name": "punctuation.definition.arguments.end.latex" @@ -2357,1363 +2596,1355 @@ "patterns": [ { "include": "source.lua" - } - ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:py|python|sage)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" - }, - { - "begin": "(?<=\\])(\\{)", - "end": "\\}", - "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "source.python", - "patterns": [ + }, { - "include": "source.python" + "include": "text.tex#braces" } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:rb|ruby)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:asy|asymptote)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "source.ruby", "patterns": [ { - "include": "source.ruby" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.asy", + "patterns": [ + { + "include": "source.asy" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:rust)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:bash)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "source.rust", "patterns": [ { - "include": "source.rust" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.shell", + "patterns": [ + { + "include": "source.shell" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:ts|typescript)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:c|cpp)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "source.ts", "patterns": [ { - "include": "source.ts" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.cpp.embedded.latex", + "patterns": [ + { + "include": "source.cpp.embedded.latex" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:xml)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:css)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "text.xml", "patterns": [ { - "include": "text.xml" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.css", + "patterns": [ + { + "include": "source.css" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:yaml)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:gnuplot)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "source.yaml", "patterns": [ { - "include": "source.yaml" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.gnuplot", + "patterns": [ + { + "include": "source.gnuplot" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[(?i:tikz|tikzpicture)\\b|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:hs|haskell)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "text.tex.latex", "patterns": [ { - "include": "text.tex.latex" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.haskell", + "patterns": [ + { + "include": "source.haskell" + } + ] } ] - } - ] - }, - { - "begin": "((\\\\)cacheMeCode)(?=\\[|\\{)", - "end": "(?<=\\})", - "beginCaptures": { - "1": { - "name": "support.function.verb.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - } - }, - "patterns": [ - { - "include": "text.tex.latex#multiline-optional-arg-no-highlight" }, { - "begin": "(?<=\\])(\\{)", - "end": "\\}", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:html)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "contentName": "meta.embedded.block.generic.latex", "patterns": [ { - "include": "text.tex#braces" - } - ] - } - ] - }, - { - "begin": "((\\\\)addplot)(?:\\+?)((?:\\[[^\\[]*\\]))*\\s*(gnuplot)\\s*((?:\\[[^\\[]*\\]))*\\s*(\\{)", - "captures": { - "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "patterns": [ + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, { - "include": "#optional-arg-bracket" + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "text.html", + "patterns": [ + { + "include": "text.html.basic" + } + ] } ] }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { + { + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:java)\\b|\\{)", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" + } + }, "patterns": [ { - "include": "#optional-arg-bracket" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.java", + "patterns": [ + { + "include": "source.java" + } + ] } ] }, - "6": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "\\s*(\\};)", - "patterns": [ { - "begin": "%", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:jl|julia)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.latex" + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } }, - "end": "$\\n?", - "name": "comment.line.percentage.latex" - }, - { - "include": "source.gnuplot" - } - ] - }, - { - "begin": "(\\s*\\\\begin\\{((?:fboxv|boxedv|V|v|spv)erbatim\\*?)\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "contentName": "markup.raw.verbatim.latex", - "end": "(\\\\end\\{\\2\\})", - "name": "meta.function.verbatim.latex" - }, - { - "begin": "(\\s*\\\\begin\\{VerbatimOut\\}\\{[^\\}]*\\})", - "captures": { - "1": { "patterns": [ { - "include": "#begin-env-tokenizer" - } - ] - } - }, - "contentName": "markup.raw.verbatim.latex", - "end": "(\\\\end\\{\\VerbatimOut\\})", - "name": "meta.function.verbatim.latex" - }, - { - "begin": "(\\s*\\\\begin\\{alltt\\})", - "captures": { - "1": { - "patterns": [ + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, { - "include": "#begin-env-tokenizer" + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ] } ] - } - }, - "contentName": "markup.raw.verbatim.latex", - "end": "(\\\\end\\{alltt\\})", - "name": "meta.function.alltt.latex", - "patterns": [ + }, { - "captures": { + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:js|javascript)\\b|\\{)", + "end": "(?<=\\})", + "beginCaptures": { "1": { + "name": "support.function.verb.latex" + }, + "2": { "name": "punctuation.definition.function.latex" } }, - "match": "(\\\\)[A-Za-z]+", - "name": "support.function.general.latex" - } - ] - }, - { - "begin": "(\\s*\\\\begin\\{([Cc]omment)\\})", - "captures": { - "1": { "patterns": [ { - "include": "#begin-env-tokenizer" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.js", + "patterns": [ + { + "include": "source.js" + } + ] } ] - } - }, - "contentName": "comment.line.percentage.latex", - "end": "(\\\\end\\{\\2\\})", - "name": "meta.function.verbatim.latex" - }, - { - "begin": "(?:\\s*)((\\\\)(?:href|hyperref|hyperimage))(?=\\[|\\{)", - "beginCaptures": { - "1": { - "name": "support.function.url.latex" - } - }, - "comment": "Captures \\command[option]{url}{optional category}{optional name}{text}", - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "name": "meta.function.hyperlink.latex", - "patterns": [ - { - "include": "#multiline-optional-arg-no-highlight" }, { - "begin": "(?:\\G|(?<=\\]))(\\{)([^}]*)(\\})(?:\\{[^}]*\\}){2}?(\\{)", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:lua)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.verb.latex" }, "2": { - "name": "markup.underline.link.latex" - }, - "3": { - "name": "punctuation.definition.arguments.end.latex" - }, - "4": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "punctuation.definition.function.latex" } }, - "contentName": "meta.variable.parameter.function.latex", - "end": "(?=\\})", "patterns": [ { - "include": "$self" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.lua", + "patterns": [ + { + "include": "source.lua" + } + ] } ] }, { - "begin": "(?:\\G|(?<=\\]))(?:(\\{)[^}]*(\\}))?(\\{)", + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:py|python|sage)\\b|\\{)", + "end": "(?<=\\})", "beginCaptures": { "1": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "support.function.verb.latex" }, "2": { - "name": "punctuation.definition.arguments.end.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" + "name": "punctuation.definition.function.latex" } }, - "contentName": "meta.variable.parameter.function.latex", - "end": "(?=\\})", "patterns": [ { - "include": "$self" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ] } ] - } - ] - }, - { - "captures": { - "1": { - "name": "support.function.url.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "markup.underline.link.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "match": "(?:\\s*)((\\\\)(?:url|path))(\\{)([^}]*)(\\})", - "name": "meta.function.link.url.latex" - }, - { - "captures": { - "1": { - "patterns": [ - { - "include": "#begin-env-tokenizer" + { + "begin": "((\\\\)cacheMeCode)(?=\\[(?i:rb|ruby)\\b|\\{)", + "end": "(?<=\\})", + "beginCaptures": { + "1": { + "name": "support.function.verb.latex" + }, + "2": { + "name": "punctuation.definition.function.latex" } - ] - } - }, - "comment": "These two patterns match the \\begin{document} and \\end{document} commands, so that the environment matching pattern following them will ignore those commands.", - "match": "(\\s*\\\\begin\\{document\\})", - "name": "meta.function.begin-document.latex" - }, - { - "captures": { - "1": { + }, "patterns": [ { - "include": "#begin-env-tokenizer" + "include": "text.tex.latex#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?<=\\])(\\{)", + "end": "\\}", + "beginCaptures": { + "0": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "source.ruby", + "patterns": [ + { + "include": "source.ruby" + } + ] } ] - } - }, - "match": "(\\s*\\\\end\\{document\\})", - "name": "meta.function.end-document.latex" - }, - { - "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:\\+?array|equation|(?:IEEE|sub)?eqnarray|multline|align|aligned|alignat|alignedat|flalign|flaligned|flalignat|split|gather|gathered|\\+?cases|(?:display)?math|\\+?[a-zA-Z]*matrix|[pbBvV]?NiceMatrix|[pbBvV]?NiceArray|(?:(?:arg)?(?:mini|maxi)))(?:\\*|!)?)(\\})(\\s*\\n)?", - "captures": { - "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "contentName": "meta.math.block.latex support.class.math.block.environment.latex", - "end": "(?:\\s*)((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?", - "name": "meta.function.environment.math.latex", - "patterns": [ - { - "match": "(?]*>)?((?:\\[[^\\]]*\\])*)(\\{)", "captures": { "1": { - "name": "storage.type.function.latex" + "name": "keyword.control.cite.latex" }, "2": { - "name": "punctuation.definition.function.latex" + "name": "punctuation.definition.keyword.latex" }, "3": { - "name": "punctuation.definition.begin.latex" + "patterns": [ + { + "include": "#autocites-arg" + } + ] }, "4": { - "name": "support.function.general.latex" + "patterns": [ + { + "include": "#optional-arg-angle-no-highlight" + } + ] }, "5": { - "name": "punctuation.definition.function.latex" - }, - "6": { - "name": "punctuation.definition.end.latex" - } - }, - "match": "((\\\\)(?:newcommand|renewcommand|(?:re)?newrobustcmd|DeclareRobustCommand))\\*?({)((\\\\)[^}]*)(})" - }, - { - "begin": "((\\\\)marginpar)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.marginpar.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { "patterns": [ { - "include": "#optional-arg-bracket" + "include": "#optional-arg-bracket-no-highlight" } ] }, - "4": { - "name": "punctuation.definition.marginpar.begin.latex" + "6": { + "name": "punctuation.definition.arguments.begin.latex" } }, - "contentName": "meta.paragraph.margin.latex", "end": "\\}", "endCaptures": { "0": { - "name": "punctuation.definition.marginpar.end.latex" + "name": "punctuation.definition.arguments.end.latex" } }, + "name": "meta.citation.latex", "patterns": [ { - "include": "text.tex#braces" + "match": "((%).*)$", + "captures": { + "1": { + "name": "comment.line.percentage.tex" + }, + "2": { + "name": "punctuation.definition.comment.tex" + } + } }, { - "include": "$self" + "match": "[\\p{Alphabetic}\\p{Number}:.-]+", + "name": "constant.other.reference.citation.latex" } ] }, - { - "begin": "((\\\\)footnote)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.footnote.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { + "references-macro": { + "patterns": [ + { + "begin": "((\\\\)(?:\\w*[rR]ef\\*?))(?:\\[[^\\]]*\\])?(\\{)", + "beginCaptures": { + "1": { + "name": "keyword.control.ref.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "name": "meta.reference.label.latex", "patterns": [ { - "include": "#optional-arg-bracket" + "match": "[\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+", + "name": "constant.other.reference.label.latex" } ] }, - "4": { - "name": "punctuation.definition.footnote.begin.latex" - } - }, - "contentName": "entity.name.footnote.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.footnote.end.latex" - } - }, - "patterns": [ { - "include": "text.tex#braces" + "match": "((\\\\)(?:\\w*[rR]efrange\\*?))(?:\\[[^\\]]*\\])?(\\{)([\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+)(\\})(\\{)([\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+)(\\})", + "captures": { + "1": { + "name": "keyword.control.ref.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "4": { + "name": "constant.other.reference.label.latex" + }, + "5": { + "name": "punctuation.definition.arguments.end.latex" + }, + "6": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "7": { + "name": "constant.other.reference.label.latex" + }, + "8": { + "name": "punctuation.definition.arguments.end.latex" + } + } }, { - "include": "$self" + "begin": "((\\\\)bibentry)(\\{)", + "captures": { + "1": { + "name": "keyword.control.cite.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "name": "meta.citation.latex", + "patterns": [ + { + "match": "[\\p{Alphabetic}\\p{Number}:.]+", + "name": "constant.other.reference.citation.latex" + } + ] } ] }, - { - "begin": "((\\\\)emph)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.emph.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.emph.begin.latex" - } - }, - "contentName": "markup.italic.emph.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.emph.end.latex" - } - }, - "name": "meta.function.emph.latex", + "display-math": { "patterns": [ { - "include": "text.tex#braces" + "begin": "\\\\\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\\\\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" - } - ] - }, - { - "begin": "((\\\\)textit)(\\{)", - "captures": { - "1": { - "name": "support.function.textit.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.textit.begin.latex" - } - }, - "comment": "We put the keyword in a capture and name this capture, so that disabling spell checking for “keyword” won't be inherited by the argument to \\textit{...}.\n\nPut specific matches for particular LaTeX keyword.functions before the last two more general functions", - "contentName": "markup.italic.textit.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.textit.end.latex" + "begin": "\\$\\$", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\$\\$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "match": "\\\\\\$", + "name": "constant.character.escape.latex" + }, + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] } - }, - "name": "meta.function.textit.latex", + ] + }, + "inline-math": { "patterns": [ { - "include": "text.tex#braces" + "begin": "\\\\\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\\\\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "\\$(?!\\$)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.tex" + } + }, + "end": "(?]*>)?((?:\\[[^\\]]*\\])*)(\\{)", - "captures": { - "1": { - "name": "keyword.control.cite.latex" + { + "begin": "(\\s*\\\\begin\\{(tabular[xy*]?|xltabular|longtable|(?:long)?tabu|(?:long|tall)?tblr|NiceTabular[X*]?|booktabs)\\}(\\s*\\n)?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] + } + }, + "contentName": "meta.data.environment.tabular.latex", + "end": "(\\s*\\\\end\\{(\\2)\\}(?:\\s*\\n)?)", + "name": "meta.function.environment.tabular.latex", + "patterns": [ + { + "match": "(?)(\\{)\\$(\\})", "captures": { "1": { "name": "punctuation.definition.column-specials.begin.latex" @@ -3861,14 +4082,8 @@ "name": "punctuation.definition.column-specials.end.latex" } }, - "match": "(?:<|>)(\\{)\\$(\\})", "name": "meta.column-specials.latex" }, - { - "include": "text.tex" - } - ], - "repository": { "autocites-arg": { "patterns": [ { @@ -3908,7 +4123,8 @@ } ] }, - "begin-env-tokenizer": { + "macro-with-args-tokenizer": { + "match": "\\s*((\\\\)(?:\\p{Alphabetic}+))(\\{)(\\\\?\\p{Alphabetic}+\\*?)(\\})(?:(\\[)([^\\]]*)(\\])){,2}(?:(\\{)([^{}]*)(\\}))?", "captures": { "1": { "name": "support.function.be.latex" @@ -3947,42 +4163,7 @@ "11": { "name": "punctuation.definition.arguments.end.latex" } - }, - "match": "\\s*((\\\\)(?:begin|end))(\\{)(\\p{Alphabetic}+\\*?)(\\})(?:(\\[)([^\\]]*)(\\])){,2}(?:(\\{)([^{}]*)(\\}))?" - }, - "definition-label": { - "begin": "((\\\\)z?label)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "keyword.control.label.latex" - }, - "2": { - "name": "punctuation.definition.keyword.latex" - }, - "3": { - "patterns": [ - { - "include": "#optional-arg-bracket" - } - ] - }, - "4": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "name": "meta.definition.label.latex", - "patterns": [ - { - "match": "[\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+", - "name": "variable.parameter.definition.label.latex" - } - ] + } }, "multiline-optional-arg": { "begin": "\\G\\[", @@ -4039,9 +4220,58 @@ } }, "name": "meta.parameter.latex", + "comment": "Do not look for balanced expressions, ie environments, inside a command argument", "patterns": [ { - "include": "$self" + "include": "#documentclass-usepackage-macro" + }, + { + "include": "#input-macro" + }, + { + "include": "#sections-macro" + }, + { + "include": "#hyperref-macro" + }, + { + "include": "#newcommand-macro" + }, + { + "include": "#text-font-macro" + }, + { + "include": "#citation-macro" + }, + { + "include": "#references-macro" + }, + { + "include": "#label-macro" + }, + { + "include": "#verb-macro" + }, + { + "include": "#inline-code-macro" + }, + { + "include": "#all-other-macro" + }, + { + "include": "#display-math" + }, + { + "include": "#inline-math" + }, + { + "include": "#column-specials" + }, + { + "include": "text.tex#braces" + }, + { + "include": "text.tex" } ] }, diff --git a/extensions/swift/cgmanifest.json b/extensions/swift/cgmanifest.json index a9cdafc3b03..ecd2705da2a 100644 --- a/extensions/swift/cgmanifest.json +++ b/extensions/swift/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jtbandes/swift-tmlanguage", "repositoryUrl": "https://github.com/jtbandes/swift-tmlanguage", - "commitHash": "0897d8939a82ddcf6533e9f318e5942b1265416b" + "commitHash": "45ac01d47c6d63402570c2c36bcfbadbd1c7bca6" } }, "license": "MIT" diff --git a/extensions/swift/syntaxes/swift.tmLanguage.json b/extensions/swift/syntaxes/swift.tmLanguage.json index 1d6b18c44ed..a8bbe5d00b4 100644 --- a/extensions/swift/syntaxes/swift.tmLanguage.json +++ b/extensions/swift/syntaxes/swift.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jtbandes/swift-tmlanguage/commit/0897d8939a82ddcf6533e9f318e5942b1265416b", + "version": "https://github.com/jtbandes/swift-tmlanguage/commit/45ac01d47c6d63402570c2c36bcfbadbd1c7bca6", "name": "Swift", "scopeName": "source.swift", "comment": "See swift.tmbundle/grammar-test.swift for test cases.", @@ -939,6 +939,17 @@ { "include": "#declarations-available-types" }, + { + "include": "#literals-numeric" + }, + { + "name": "support.variable.inferred.swift", + "match": "\\b_\\b" + }, + { + "name": "keyword.other.inline-array.swift", + "match": "(?<=\\s)\\bof\\b(?=\\s+[\\p{L}_\\d\\p{N}\\p{M}\\[(])" + }, { "begin": ":", "end": "(?=\\]|[>){}])", @@ -980,28 +991,24 @@ }, "declarations-extension": { "name": "meta.definition.type.$1.swift", - "begin": "\\b(extension)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "begin": "\\b(extension)\\s+", "end": "(?<=\\})", "beginCaptures": { "1": { "name": "storage.type.$1.swift" - }, - "2": { + } + }, + "patterns": [ + { "name": "entity.name.type.swift", + "begin": "\\G(?!\\s*[:{\\n])", + "end": "(?=\\s*[:{\\n])|(?!\\G)(?=\\s*where\\b)", "patterns": [ { "include": "#declarations-available-types" } ] }, - "3": { - "name": "punctuation.definition.identifier.swift" - }, - "4": { - "name": "punctuation.definition.identifier.swift" - } - }, - "patterns": [ { "include": "#comments" }, @@ -1159,8 +1166,8 @@ }, "patterns": [ { - "match": "\\bsending\\b", - "name": "storage.modifier.swift" + "name": "storage.modifier.swift", + "match": "\\bsending\\b" }, { "include": "#declarations-available-types" @@ -1754,8 +1761,8 @@ "end": "(?=[,)])", "patterns": [ { - "match": "\\bsending\\b", - "name": "storage.modifier.swift" + "name": "storage.modifier.swift", + "match": "\\bsending\\b" }, { "include": "#declarations-available-types" @@ -2834,6 +2841,10 @@ "name": "keyword.control.loop.swift", "match": "(? Date: Mon, 6 Oct 2025 09:00:50 -0700 Subject: [PATCH 0833/4355] Updated per comment, inject the chatModelService into ChatSetupContribution --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 55e43e9fde7..a9ddac01102 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -857,6 +857,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr @ICommandService private readonly commandService: ICommandService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IChatEntitlementService chatEntitlementService: ChatEntitlementService, + @IChatModeService private readonly chatModeService: IChatModeService, @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @@ -1200,7 +1201,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const modeParam = params.get('mode'); let modeToUse: ChatModeKind | string | undefined; if (modeParam) { - const chatModeService = this.instantiationService.invokeFunction(accessor => accessor.get(IChatModeService)); + const chatModeService = this.chatModeService; // check if the given param is a valid mode ID let foundMode = chatModeService.findModeById(modeParam); From 93afee51ac429e94255f98856bf7202b313c7844 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 6 Oct 2025 09:08:56 -0700 Subject: [PATCH 0834/4355] add 'hiddenAfterComplete' option to tool's presentation prop --- src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index ade1cf62e88..e3c786f1d1b 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -239,7 +239,7 @@ declare module 'vscode' { export interface PreparedToolInvocation { pastTenseMessage?: string | MarkdownString; - presentation?: 'hidden' | undefined; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; } export class ExtendedLanguageModelToolResult extends LanguageModelToolResult { From c5c2e160771e15be797ac88008cc0142a75aa4ba Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:18:07 -0700 Subject: [PATCH 0835/4355] Fixing tests --- src/vs/base/browser/domSanitize.ts | 52 ++++++++++--------- ...kdownRenderer_self-closing_elements.0.snap | 2 +- ..._supportHtml_with_one-line_markdown.1.snap | 2 +- ..._Support_Test_Basic_inline_equation.0.snap | 2 +- ...ender_math_at_start_and_end_of_line.0.snap | 2 +- ...h_special_characters_around_dollars.0.snap | 2 +- ..._blocks_immediately_after_paragraph.0.snap | 4 +- ...t_inline_equation_wrapped_in_parans.0.snap | 2 +- 8 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 506c8c02435..8d3b1aebc63 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -339,34 +339,36 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine const selfClosingTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; -const replaceWithPlainTextHook: DomPurifyTypes.UponSanitizeElementHook = (element, data, _config) => { - if (!(element instanceof Element)) { - return; - } - +const replaceWithPlainTextHook: DomPurifyTypes.UponSanitizeElementHook = (node, data, _config) => { if (!data.allowedTags[data.tagName] && data.tagName !== 'body') { - const replacement = convertTagToPlaintext(element); - if (element.nodeType === Node.COMMENT_NODE) { - // Workaround for https://github.com/cure53/DOMPurify/issues/1005 - // The comment will be deleted in the next phase. However if we try to remove it now, it will cause - // an exception. Instead we insert the text node before the comment. - element.parentElement?.insertBefore(replacement, element); - } else { - element.parentElement?.replaceChild(replacement, element); + const replacement = convertTagToPlaintext(node); + if (replacement) { + if (node.nodeType === Node.COMMENT_NODE) { + // Workaround for https://github.com/cure53/DOMPurify/issues/1005 + // The comment will be deleted in the next phase. However if we try to remove it now, it will cause + // an exception. Instead we insert the text node before the comment. + node.parentElement?.insertBefore(replacement, node); + } else { + node.parentElement?.replaceChild(replacement, node); + } } } }; -export function convertTagToPlaintext(element: Element): DocumentFragment { +export function convertTagToPlaintext(node: Node): DocumentFragment | undefined { + if (!node.ownerDocument) { + return; + } + let startTagText: string; let endTagText: string | undefined; - if (element.nodeType === Node.COMMENT_NODE) { - startTagText = ``; - } else { - const tagName = element.tagName.toLowerCase(); + if (node.nodeType === Node.COMMENT_NODE) { + startTagText = ``; + } else if (node instanceof Element) { + const tagName = node.tagName.toLowerCase(); const isSelfClosing = selfClosingTags.includes(tagName); - const attrString = element.attributes.length ? - ' ' + Array.from(element.attributes) + const attrString = node.attributes.length ? + ' ' + Array.from(node.attributes) .map(attr => `${attr.name}="${attr.value}"`) .join(' ') : ''; @@ -374,16 +376,18 @@ export function convertTagToPlaintext(element: Element): DocumentFragment { if (!isSelfClosing) { endTagText = ``; } + } else { + return; } const fragment = document.createDocumentFragment(); - const textNode = element.ownerDocument.createTextNode(startTagText); + const textNode = node.ownerDocument.createTextNode(startTagText); fragment.appendChild(textNode); - while (element.firstChild) { - fragment.appendChild(element.firstChild); + while (node.firstChild) { + fragment.appendChild(node.firstChild); } - const endTagTextNode = endTagText ? element.ownerDocument.createTextNode(endTagText) : undefined; + const endTagTextNode = endTagText ? node.ownerDocument.createTextNode(endTagText) : undefined; if (endTagTextNode) { fragment.appendChild(endTagTextNode); } diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap index 2a77a1cc216..87269426a9b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap @@ -1,3 +1,3 @@
-

<area>



<input value="test" type="text">

\ No newline at end of file +

<area>



<input type="text" value="test">

\ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap index 295e1e5c849..78e0242dfba 100644 --- a/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap +++ b/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap @@ -1,6 +1,6 @@
    -
  1. hello test text
  2. +
  3. hello test text
\ No newline at end of file diff --git a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Basic_inline_equation.0.snap b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Basic_inline_equation.0.snap index ad9d5317e61..3af85838d9a 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Basic_inline_equation.0.snap +++ b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Basic_inline_equation.0.snap @@ -1 +1 @@ -

Hello 12\frac{1}{2}21 World!

\ No newline at end of file +

Hello 12\frac{1}{2}21 World!

\ No newline at end of file diff --git a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_at_start_and_end_of_line.0.snap b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_at_start_and_end_of_line.0.snap index 9a1d26e5911..d12c80ba96d 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_at_start_and_end_of_line.0.snap +++ b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_at_start_and_end_of_line.0.snap @@ -1 +1 @@ -

12\frac{1}{2}21 at start, and at end x2x^2x2

\ No newline at end of file +

12\frac{1}{2}21 at start, and at end x2x^2x2

\ No newline at end of file diff --git a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_with_special_characters_around_dollars.0.snap b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_with_special_characters_around_dollars.0.snap index da07975a44e..42ca2f57169 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_with_special_characters_around_dollars.0.snap +++ b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_still_render_math_with_special_characters_around_dollars.0.snap @@ -1 +1 @@ -

Hello (12\frac{1}{2}21) and [x2x^2x2] work fine

\ No newline at end of file +

Hello (12\frac{1}{2}21) and [x2x^2x2] work fine

\ No newline at end of file diff --git a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_blocks_immediately_after_paragraph.0.snap b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_blocks_immediately_after_paragraph.0.snap index eb2857f9905..5d089f4c241 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_blocks_immediately_after_paragraph.0.snap +++ b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_blocks_immediately_after_paragraph.0.snap @@ -1,4 +1,4 @@ -

Block example:

ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}ex2dx=πex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}ex2dx=π +M834 80h400000v40h-400000z"> diff --git a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_inline_equation_wrapped_in_parans.0.snap b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_inline_equation_wrapped_in_parans.0.snap index 02dd6ab4283..5c29aad77f0 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_inline_equation_wrapped_in_parans.0.snap +++ b/src/vs/workbench/contrib/markdown/test/browser/__snapshots__/Markdown_Katex_Support_Test_Should_support_inline_equation_wrapped_in_parans.0.snap @@ -1 +1 @@ -

Hello (12\frac{1}{2}21) World!

\ No newline at end of file +

Hello (12\frac{1}{2}21) World!

\ No newline at end of file From ddc3c9a9d60aeb33ce94da9e1aac8998c78f13d1 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:31:27 -0700 Subject: [PATCH 0836/4355] Update more tests --- src/vs/base/test/browser/markdownRenderer.test.ts | 8 ++++---- .../test/browser/markdownDocumentRenderer.test.ts | 2 +- ...ease_notes_renderer_Should_render_code_settings.0.snap | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 785f69925fa..072d1871b29 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -166,7 +166,7 @@ suite('MarkdownRenderer', () => { mds.appendMarkdown(`[$(zap)-link](#link)`); const result: HTMLElement = store.add(renderMarkdown(mds)).element; - assert.strictEqual(result.innerHTML, `

-link

`); + assert.strictEqual(result.innerHTML, `

-link

`); }); test('render icon in table', () => { @@ -186,7 +186,7 @@ suite('MarkdownRenderer', () => { --link +-link `); @@ -253,7 +253,7 @@ suite('MarkdownRenderer', () => { }); const result: HTMLElement = store.add(renderMarkdown(md)).element; - assert.strictEqual(result.innerHTML, `

command1 command2

`); + assert.strictEqual(result.innerHTML, `

command1 command2

`); }); test('Should remove relative links if there is no base url', () => { @@ -274,7 +274,7 @@ suite('MarkdownRenderer', () => { md.baseUri = URI.parse('https://example.com/path/'); const result = store.add(renderMarkdown(md)).element; - assert.strictEqual(result.innerHTML, `

text bar

`); + assert.strictEqual(result.innerHTML, `

text bar

`); }); suite('PlaintextMarkdownRender', () => { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts index 262ec4e287e..71bf5c415ce 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownDocumentRenderer.test.ts @@ -36,6 +36,6 @@ suite('Markdown Document Renderer Test', () => { } }); - assert.strictEqual(result.toString(), `

alt

\n`); + assert.strictEqual(result.toString(), `

alt

\n`); }); }); diff --git a/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_code_settings.0.snap b/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_code_settings.0.snap index c7a7c8a34f7..711462fca00 100644 --- a/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_code_settings.0.snap +++ b/src/vs/workbench/contrib/update/test/browser/__snapshots__/Release_notes_renderer_Should_render_code_settings.0.snap @@ -1,7 +1,7 @@ -

Here is a setting: +

Here is a setting: editor.wordWrap - and another + and another editor.wordWrap

From 5200c03339a1a2f517619c5b1dccfe840de4c9e2 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:34:35 -0700 Subject: [PATCH 0837/4355] Update caller --- src/vs/base/browser/markdownRenderer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 87ec4b8ec64..b96a0e80d25 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -229,7 +229,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } else { if (options.sanitizerConfig?.replaceWithPlaintext) { const replacement = convertTagToPlaintext(input); - input.parentElement?.replaceChild(replacement, input); + if (replacement) { + input.parentElement?.replaceChild(replacement, input); + } else { + input.remove(); + } } else { input.remove(); } From 8f5995ff8511e8f663504fd5602ae0e6f688c31d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:45:54 -0700 Subject: [PATCH 0838/4355] Use `replaceWith` in to avoid not null assertions Switches from `node.parent.replaceChild(x, node)` to `node.replaceWith(x)`. This lets us avoid having to check that the parent exists --- src/vs/editor/browser/view/viewLayer.ts | 2 +- .../chatContentParts/chatConfirmationWidget.ts | 2 +- .../chat/browser/chatMarkdownDecorationsRenderer.ts | 12 +++--------- .../contrib/comments/browser/commentsTreeViewer.ts | 4 ++-- .../browser/view/renderers/webviewPreloads.ts | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index 3f1b0905954..1a20b814445 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -546,7 +546,7 @@ class ViewLayerRenderer { if (wasInvalid[i]) { const source = hugeDomNode.firstChild; const lineDomNode = line.getDomNode()!; - lineDomNode.parentNode!.replaceChild(source, lineDomNode); + lineDomNode.replaceWith(source); line.setDomNode(source); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 01c5c5a5270..78da1161cd0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -64,7 +64,7 @@ export class ChatQueryTitlePart extends Disposable { const previousEl = this._renderedTitle.value?.element; if (previousEl?.parentElement) { - previousEl.parentElement.replaceChild(next.element, previousEl); + previousEl.replaceWith(next.element); } else { this.element.appendChild(next.element); // unreachable? } diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 4dc6c08187c..7366a025d39 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -135,9 +135,7 @@ export class ChatMarkdownDecorationsRenderer { } if (args) { - a.parentElement!.replaceChild( - this.renderAgentWidget(args, store), - a); + a.replaceWith(this.renderAgentWidget(args, store)); } } else if (href.startsWith(agentSlashRefUrl)) { let args: ISlashCommandWidgetArgs | undefined; @@ -148,9 +146,7 @@ export class ChatMarkdownDecorationsRenderer { } if (args) { - a.parentElement!.replaceChild( - this.renderSlashCommandWidget(a.textContent!, args, store), - a); + a.replaceWith(this.renderSlashCommandWidget(a.textContent!, args, store)); } } else if (href.startsWith(decorationRefUrl)) { let args: IDecorationWidgetArgs | undefined; @@ -158,9 +154,7 @@ export class ChatMarkdownDecorationsRenderer { args = JSON.parse(decodeURIComponent(href.slice(decorationRefUrl.length + 1))); } catch (e) { } - a.parentElement!.replaceChild( - this.renderResourceWidget(a.textContent!, args, store), - a); + a.replaceWith(this.renderResourceWidget(a.textContent!, args, store)); } else if (href.startsWith(contentRefUrl)) { this.renderFileWidget(content, href, a, store); } else if (href.startsWith('command:')) { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index d77c6fa3d97..ffd3835b774 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -250,12 +250,12 @@ export class CommentNodeRenderer implements IListRenderer const image = images[i]; const textDescription = dom.$(''); textDescription.textContent = image.alt ? nls.localize('imageWithLabel', "Image: {0}", image.alt) : nls.localize('image', "Image"); - image.parentNode!.replaceChild(textDescription, image); + image.replaceWith(textDescription); } const headings = [...renderedComment.element.getElementsByTagName('h1'), ...renderedComment.element.getElementsByTagName('h2'), ...renderedComment.element.getElementsByTagName('h3'), ...renderedComment.element.getElementsByTagName('h4'), ...renderedComment.element.getElementsByTagName('h5'), ...renderedComment.element.getElementsByTagName('h6')]; for (const heading of headings) { const textNode = document.createTextNode(heading.textContent || ''); - heading.parentNode!.replaceChild(textNode, heading); + heading.replaceWith(textNode); } while ((renderedComment.element.children.length > 1) && (renderedComment.element.firstElementChild?.tagName === 'HR')) { renderedComment.element.removeChild(renderedComment.element.firstElementChild); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 09527e4364f..18e618ea07e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -831,7 +831,7 @@ async function webviewPreloads(ctx: PreloadContext) { // Remove a highlight element created with wrapNodeInHighlight. function _removeHighlight(highlightElement: Element) { if (highlightElement.childNodes.length === 1) { - highlightElement.parentNode?.replaceChild(highlightElement.firstChild!, highlightElement); + highlightElement.replaceWith(highlightElement.firstChild!); } else { // If the highlight somehow contains multiple nodes now, move them all. while (highlightElement.firstChild) { From f4fe2163a9fd472a6ad7e2a89c505ac53efda17f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:08:20 -0700 Subject: [PATCH 0839/4355] Remove `any` casts in some built-in extensions For #269213 --- extensions/grunt/src/main.ts | 3 +-- extensions/gulp/src/main.ts | 6 ++---- extensions/jake/src/main.ts | 6 ++---- extensions/npm/src/tasks.ts | 6 ++---- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts index f2965cb1222..b94b00c4462 100644 --- a/extensions/grunt/src/main.ts +++ b/extensions/grunt/src/main.ts @@ -120,8 +120,7 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - // eslint-disable-next-line local/code-no-any-casts - const taskDefinition = _task.definition; + const taskDefinition = _task.definition; const gruntTask = taskDefinition.task; if (gruntTask) { const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index fa0ce2a520c..c83bdb65897 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -150,11 +150,9 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - // eslint-disable-next-line local/code-no-any-casts - const gulpTask = (_task.definition).task; + const gulpTask = _task.definition.task; if (gulpTask) { - // eslint-disable-next-line local/code-no-any-casts - const kind: GulpTaskDefinition = (_task.definition); + const kind = _task.definition as GulpTaskDefinition; const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options)); return task; diff --git a/extensions/jake/src/main.ts b/extensions/jake/src/main.ts index 28ed7062ab9..654cc951b9c 100644 --- a/extensions/jake/src/main.ts +++ b/extensions/jake/src/main.ts @@ -120,11 +120,9 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - // eslint-disable-next-line local/code-no-any-casts - const jakeTask = (_task.definition).task; + const jakeTask = _task.definition.task; if (jakeTask) { - // eslint-disable-next-line local/code-no-any-casts - const kind: JakeTaskDefinition = (_task.definition); + const kind = _task.definition as JakeTaskDefinition; const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, jakeTask, 'jake', new vscode.ShellExecution(await this._jakeCommand, [jakeTask], options)); return task; diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 474213f3695..ba833705cb4 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -58,11 +58,9 @@ export class NpmTaskProvider implements TaskProvider { } public async resolveTask(_task: Task): Promise { - // eslint-disable-next-line local/code-no-any-casts - const npmTask = (_task.definition).script; + const npmTask = _task.definition.script; if (npmTask) { - // eslint-disable-next-line local/code-no-any-casts - const kind: INpmTaskDefinition = (_task.definition); + const kind = _task.definition as INpmTaskDefinition; let packageJsonUri: Uri; if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) { // scope is required to be a WorkspaceFolder for resolveTask From 4d32398e4255f642d0e7fb53f39652105d5fc87e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:10:49 -0700 Subject: [PATCH 0840/4355] Fix checking $traceid for empty args --- .../src/tsServer/requestQueue.ts | 2 +- .../src/tsServer/server.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/src/tsServer/requestQueue.ts b/extensions/typescript-language-features/src/tsServer/requestQueue.ts index 204e386c1bc..aa461128098 100644 --- a/extensions/typescript-language-features/src/tsServer/requestQueue.ts +++ b/extensions/typescript-language-features/src/tsServer/requestQueue.ts @@ -86,7 +86,7 @@ export class RequestQueue { return false; } - public createRequest(command: string, args: any): Proto.Request { + public createRequest(command: string, args: unknown): Proto.Request { return { seq: this.sequenceNumber++, type: 'request', diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 25dae6d8772..38c62493377 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -49,7 +49,7 @@ export interface ITypeScriptServer { * @return A list of all execute requests. If there are multiple entries, the first item is the primary * request while the rest are secondary ones. */ - executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined>; + executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined>; dispose(): void; } @@ -284,7 +284,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { } this._requestQueue.enqueue(requestInfo); - const traceId = (args as RequestArgs).$traceId; + const traceId = (args as RequestArgs | undefined)?.$traceId; if (args && typeof traceId === 'string') { const queueLength = this._requestQueue.length - 1; const pendingResponses = this._pendingResponses.size; @@ -406,7 +406,7 @@ class RequestRouter { public execute( command: keyof TypeScriptRequests, - args: any, + args: unknown, executeInfo: ExecuteInfo, ): Array> | undefined> { if (RequestRouter.sharedCommands.has(command) && typeof executeInfo.executionTarget === 'undefined') { @@ -538,7 +538,7 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ this.mainServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { + public executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { return this.router.execute(command, args, executeInfo); } } @@ -679,7 +679,7 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ this.semanticServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { + public executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { return this.router.execute(command, args, executeInfo); } } From 1f68f040cbd331f986b96d47f69803890c140608 Mon Sep 17 00:00:00 2001 From: dileepyavan <52841896+dileepyavan@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:22:04 -0700 Subject: [PATCH 0841/4355] Dileep y/267145 (#269788) * fix for 267145 * fix for 267145: correcting variable name * Update src/vs/workbench/contrib/debug/browser/repl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Connor Peet Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/debug/browser/repl.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index fbfde1cb8a3..532f70db7dd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -1193,7 +1193,9 @@ registerAction2(class extends Action2 { if (selectedText && selectedText.length > 0) { return clipboardService.writeText(selectedText); } else if (element) { - return clipboardService.writeText(await this.tryEvaluateAndCopy(debugService, element) || element.toString()); + const retValue = await this.tryEvaluateAndCopy(debugService, element); + const textToCopy = retValue || removeAnsiEscapeCodes(element.toString()); + return clipboardService.writeText(textToCopy); } } From 366f234a6e37cfe26b54f582852562f6b89e151a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 6 Oct 2025 12:28:48 -0500 Subject: [PATCH 0842/4355] Fix some 'as any' (#270060) * Fix any cast * Fix some 'as any' --- .../browser/languageModelToolsService.test.ts | 25 +++++++++++-------- .../chat/test/common/chatService.test.ts | 7 ------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index a4e0f3ff39b..29606524fc6 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -8,24 +8,24 @@ import { Barrier } from '../../../../../base/common/async.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { CancellationError, isCancellationError } from '../../../../../base/common/errors.js'; +import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { TestAccessibilityService } from '../../../../../platform/accessibility/test/common/testAccessibilityService.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { ConfigurationTarget, IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { ContextKeyService } from '../../../../../platform/contextkey/browser/contextKeyService.js'; import { ContextKeyEqualsExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js'; import { IChatModel } from '../../common/chatModel.js'; import { IChatService, IChatToolInputInvocationData } from '../../common/chatService.js'; -import { IToolData, IToolImpl, IToolInvocation, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; -import { MockChatService } from '../common/mockChatService.js'; -import { IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js'; -import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { ChatConfiguration } from '../../common/constants.js'; -import { URI } from '../../../../../base/common/uri.js'; +import { isToolResultInputOutputDetails, IToolData, IToolImpl, IToolInvocation, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; +import { MockChatService } from '../common/mockChatService.js'; // --- Test helpers to reduce repetition and improve readability --- @@ -978,8 +978,8 @@ suite('LanguageModelToolsService', () => { // Should have tool result details because alwaysDisplayInputOutput = true assert.ok(result.toolResultDetails, 'should have toolResultDetails'); - // eslint-disable-next-line local/code-no-any-casts - const details = result.toolResultDetails as any; // Type assertion needed for test + const details = result.toolResultDetails; + assert.ok(isToolResultInputOutputDetails(details)); // Test formatToolInput - should be formatted JSON const expectedInputJson = JSON.stringify(input, undefined, 2); @@ -1009,8 +1009,7 @@ suite('LanguageModelToolsService', () => { configurationService: () => configurationService }, store); instaService.stub(IChatService, chatService); - // eslint-disable-next-line local/code-no-any-casts - instaService.stub(ITelemetryService, testTelemetryService as any); + instaService.stub(ITelemetryService, testTelemetryService); const testService = store.add(instaService.createInstance(LanguageModelToolsService)); // Test successful invocation telemetry @@ -1516,8 +1515,12 @@ suite('LanguageModelToolsService', () => { // Change the correct configuration key configurationService.setUserConfiguration('chat.extensionTools.enabled', false); // Fire the configuration change event manually - // eslint-disable-next-line local/code-no-any-casts - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true, affectedKeys: new Set(['chat.extensionTools.enabled']) } as any as IConfigurationChangeEvent); + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: new Set(['chat.extensionTools.enabled']), + change: null!, + source: ConfigurationTarget.USER + } satisfies IConfigurationChangeEvent); // Wait a bit for the scheduler await new Promise(resolve => setTimeout(resolve, 800)); diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 07be047ccf9..63d98466c50 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -24,7 +24,6 @@ import { ILogService, NullLogService } from '../../../../../platform/log/common/ import { IStorageService } from '../../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; -import { IUserDataProfilesService } from '../../../../../platform/userDataProfile/common/userDataProfile.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js'; import { NullWorkbenchAssignmentService } from '../../../../services/assignment/test/common/nullAssignmentService.js'; @@ -146,12 +145,6 @@ suite('ChatService', () => { instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IEnvironmentService, { workspaceStorageHome: URI.file('/test/path/to/workspaceStorage') }); instantiationService.stub(ILifecycleService, { onWillShutdown: Event.None }); - // eslint-disable-next-line local/code-no-any-casts - instantiationService.stub(IUserDataProfilesService, { - defaultProfile: { - globalStorageHome: URI.file('/test/path/to/globalStorage') - } - } as any); instantiationService.stub(IChatEditingService, new class extends mock() { override startOrContinueGlobalEditingSession(): Promise { return Promise.resolve(Disposable.None as IChatEditingSession); From fec31469e09255d15399c70906ec60d1b9f0a5f5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 6 Oct 2025 12:30:44 -0500 Subject: [PATCH 0843/4355] Add CODENOTIFY (#270062) --- .github/CODENOTIFY | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 5fcbcb0e346..8bc2fe18cdc 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -91,6 +91,7 @@ src/vs/workbench/electron-browser/** @bpasero # Workbench Contributions src/vs/workbench/contrib/files/** @bpasero +src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno From eb19dbe2ef486802af916e7e005a2588d42b1d2b Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:33:43 -0700 Subject: [PATCH 0844/4355] Remove a few more `any` casts For #269213 --- src/vs/base/browser/dom.ts | 7 +++---- src/vs/base/test/browser/dom.test.ts | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 1f9c1ac8ecc..3abc4f9484a 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -421,14 +421,13 @@ export interface IEventMerger { } const MINIMUM_TIME_MS = 8; -const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: Event | null, currentEvent: Event) { +function DEFAULT_EVENT_MERGER(_lastEvent: unknown, currentEvent: T) { return currentEvent; -}; +} class TimeoutThrottledDomListener extends Disposable { - // eslint-disable-next-line local/code-no-any-casts - constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger = DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) { + constructor(node: Node, type: string, handler: (event: R) => void, eventMerger: IEventMerger = DEFAULT_EVENT_MERGER as IEventMerger, minimumTimeMs: number = MINIMUM_TIME_MS) { super(); let lastEvent: R | null = null; diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 6db7750eeab..a7975897761 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -404,8 +404,7 @@ suite('dom', () => { suite('SafeTriangle', () => { const fakeElement = (left: number, right: number, top: number, bottom: number): HTMLElement => { - // eslint-disable-next-line local/code-no-any-casts - return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as any; + return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as HTMLElement; }; test('works', () => { From 66a3043829e0c561b362500138e6ec69ddad3db6 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:36:35 -0700 Subject: [PATCH 0845/4355] Fix `any` cast in code actions For #269213 --- .../contrib/codeAction/browser/codeActionController.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 569dd89c616..f4fda50ed74 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -109,9 +109,8 @@ export class CodeActionController extends Disposable implements IEditorContribut const actionItem = actions.validActions[0]; const command = actionItem.action.command; if (command && command.id === 'inlineChat.start') { - if (command.arguments && command.arguments.length >= 1) { - // eslint-disable-next-line local/code-no-any-casts - command.arguments[0] = { ...(command.arguments[0] as any), autoSend: false }; + if (command.arguments && command.arguments.length >= 1 && command.arguments[0]) { + command.arguments[0] = { ...command.arguments[0], autoSend: false }; } } await this.applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); From a2f4c46df519df0390a26a6a2b840ea7115ca9ec Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 6 Oct 2025 10:39:31 -0700 Subject: [PATCH 0846/4355] updated per comments. --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index a9ddac01102..d5ff0449f74 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1201,13 +1201,11 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const modeParam = params.get('mode'); let modeToUse: ChatModeKind | string | undefined; if (modeParam) { - const chatModeService = this.chatModeService; - // check if the given param is a valid mode ID - let foundMode = chatModeService.findModeById(modeParam); + let foundMode = this.chatModeService.findModeById(modeParam); if (!foundMode) { // if not, check if the given param is a valid mode name, note the name is case insensitive - foundMode = chatModeService.findModeByName(modeParam); + foundMode = this.chatModeService.findModeByName(modeParam); } if (foundMode) { @@ -1215,7 +1213,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr } } - // execute the command to change the mode in panel, note that the command only support mode IDs, not names + // execute the command to change the mode in panel, note that the command only supports mode IDs, not names await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); return true; From 9986b31449f6c04bf2120c43e73be2af1b751a8e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:12:41 -0700 Subject: [PATCH 0847/4355] Fix some `any` casts and `any` usage in instantiation service For #269213 --- .../instantiation/common/descriptors.ts | 4 +-- .../instantiation/common/instantiation.ts | 26 +++++++++---------- .../common/instantiationService.ts | 10 +++---- .../test/common/instantiationServiceMock.ts | 6 ++--- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/instantiation/common/descriptors.ts b/src/vs/platform/instantiation/common/descriptors.ts index cd758ffe6a5..e259eba13f2 100644 --- a/src/vs/platform/instantiation/common/descriptors.ts +++ b/src/vs/platform/instantiation/common/descriptors.ts @@ -6,10 +6,10 @@ export class SyncDescriptor { readonly ctor: any; - readonly staticArguments: any[]; + readonly staticArguments: unknown[]; readonly supportsDelayedInstantiation: boolean; - constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) { + constructor(ctor: new (...args: any[]) => T, staticArguments: unknown[] = [], supportsDelayedInstantiation: boolean = false) { this.ctor = ctor; this.staticArguments = staticArguments; this.supportsDelayedInstantiation = supportsDelayedInstantiation; diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 967d4affff5..1d798650cd3 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -16,9 +16,14 @@ export namespace _util { export const DI_TARGET = '$di$target'; export const DI_DEPENDENCIES = '$di$dependencies'; - export function getServiceDependencies(ctor: any): { id: ServiceIdentifier; index: number }[] { + export function getServiceDependencies(ctor: DI_TARGET_OBJ): { id: ServiceIdentifier; index: number }[] { return ctor[DI_DEPENDENCIES] || []; } + + export interface DI_TARGET_OBJ extends Function { + [DI_TARGET]: Function; + [DI_DEPENDENCIES]: { id: ServiceIdentifier; index: number }[]; + } } // --- interfaces ------ @@ -86,19 +91,15 @@ export interface IInstantiationService { */ export interface ServiceIdentifier { (...args: any[]): void; - type: T; } -function storeServiceDependency(id: Function, target: Function, index: number): void { - // eslint-disable-next-line local/code-no-any-casts - if ((target as any)[_util.DI_TARGET] === target) { - // eslint-disable-next-line local/code-no-any-casts - (target as any)[_util.DI_DEPENDENCIES].push({ id, index }); + +function storeServiceDependency(id: ServiceIdentifier, target: Function, index: number): void { + if ((target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] === target) { + (target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES].push({ id, index }); } else { - // eslint-disable-next-line local/code-no-any-casts - (target as any)[_util.DI_DEPENDENCIES] = [{ id, index }]; - // eslint-disable-next-line local/code-no-any-casts - (target as any)[_util.DI_TARGET] = target; + (target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES] = [{ id, index }]; + (target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] = target; } } @@ -111,8 +112,7 @@ export function createDecorator(serviceId: string): ServiceIdentifier { return _util.serviceIds.get(serviceId)!; } - // eslint-disable-next-line local/code-no-any-casts - const id = function (target: Function, key: string, index: number) { + const id: ServiceIdentifier = function (target: Function, key: string, index: number) { if (arguments.length !== 3) { throw new Error('@IServiceName-decorator can only be used to decorate a parameter'); } diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 93c82303dbc..d7af9d0428c 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -122,7 +122,7 @@ export class InstantiationService implements IInstantiationService { createInstance(descriptor: SyncDescriptor0): T; createInstance unknown, R extends InstanceType>(ctor: Ctor, ...args: GetLeadingNonServiceArgs>): R; - createInstance(ctorOrDescriptor: any | SyncDescriptor, ...rest: any[]): unknown { + createInstance(ctorOrDescriptor: any | SyncDescriptor, ...rest: unknown[]): unknown { this._throwIfDisposed(); let _trace: Trace; @@ -138,11 +138,11 @@ export class InstantiationService implements IInstantiationService { return result; } - private _createInstance(ctor: any, args: any[] = [], _trace: Trace): T { + private _createInstance(ctor: any, args: unknown[] = [], _trace: Trace): T { // arguments defined by service decorators const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index); - const serviceArgs: any[] = []; + const serviceArgs: unknown[] = []; for (const dependency of serviceDependencies) { const service = this._getOrCreateServiceInstance(dependency.id, _trace); if (!service) { @@ -286,7 +286,7 @@ export class InstantiationService implements IInstantiationService { return this._getServiceInstanceOrDescriptor(id); } - private _createServiceInstanceWithOwner(id: ServiceIdentifier, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T { + private _createServiceInstanceWithOwner(id: ServiceIdentifier, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T { if (this._services.get(id) instanceof SyncDescriptor) { return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose); } else if (this._parent) { @@ -296,7 +296,7 @@ export class InstantiationService implements IInstantiationService { } } - private _createServiceInstance(id: ServiceIdentifier, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set): T { + private _createServiceInstance(id: ServiceIdentifier, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set): T { if (!supportsDelayedInstantiation) { // eager instantiation const result = this._createInstance(ctor, args, _trace); diff --git a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts index fad3a54cdd4..1ec6848af6a 100644 --- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts +++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts @@ -58,8 +58,7 @@ export class TestInstantiationService extends InstantiationService implements ID const property = typeof arg2 === 'string' ? arg2 : arg3; const value = typeof arg2 === 'string' ? arg3 : arg4; - // eslint-disable-next-line local/code-no-any-casts - const stubObject = this._create(serviceMock, { stub: true }, service && !property); + const stubObject = this._create(serviceMock, { stub: true }, service && !property); if (property) { if (stubObject[property]) { if (stubObject[property].hasOwnProperty('restore')) { @@ -161,8 +160,7 @@ export function createServices(disposables: DisposableStore, services: ServiceId const define = (id: ServiceIdentifier, ctorOrInstance: T | (new (...args: any[]) => T)) => { if (!serviceCollection.has(id)) { if (typeof ctorOrInstance === 'function') { - // eslint-disable-next-line local/code-no-any-casts - serviceCollection.set(id, new SyncDescriptor(ctorOrInstance as any)); + serviceCollection.set(id, new SyncDescriptor(ctorOrInstance as new (...args: any[]) => T)); } else { serviceCollection.set(id, ctorOrInstance); } From b4b3cacd74d9bfb7fcebfa5bdf506c98ecd4d8b9 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:19:46 -0700 Subject: [PATCH 0848/4355] debt: remove any casts (#270074) --- extensions/emmet/src/emmetCommon.ts | 8 +++----- extensions/emmet/src/updateImageSize.ts | 12 +++++++----- extensions/emmet/src/util.ts | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index 2b02ff48023..da364e19fa4 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -171,7 +171,7 @@ function refreshCompletionProviders(_: vscode.ExtensionContext) { return undefined; } const item = items.items[0]; - if (!item) { + if (!item || !item.insertText) { return undefined; } const range = item.range as vscode.Range; @@ -184,10 +184,8 @@ function refreshCompletionProviders(_: vscode.ExtensionContext) { return [ { - // eslint-disable-next-line local/code-no-any-casts - insertText: item.insertText as any, - // eslint-disable-next-line local/code-no-any-casts - filterText: item.label as any, + insertText: item.insertText, + filterText: item.label, range } ]; diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index e3469598eb5..204388f0055 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -273,10 +273,12 @@ function getAttributeQuote(editor: TextEditor, attr: Attribute): string { */ function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssToken | undefined { const offset = editor.document.offsetAt(pos); - // eslint-disable-next-line local/code-no-any-casts - for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) { - // eslint-disable-next-line local/code-no-any-casts - iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => { + if (!('parsedValue' in node) || !Array.isArray(node.parsedValue)) { + return undefined; + } + + for (let i = 0, il = node.parsedValue.length, url; i < il; i++) { + iterateCSSToken(node.parsedValue[i], (token: CssToken) => { if (token.type === 'url' && token.start <= offset && token.end >= offset) { url = token; return false; @@ -288,7 +290,7 @@ function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssTok return url; } } - return; + return undefined; } /** diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index 5e55e1d7c09..e934df84e71 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -354,8 +354,7 @@ export function getFlatNode(root: FlatNode | undefined, offset: number, includeN || (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) { return getFlatNodeChildren(child.children) ?? child; } - // eslint-disable-next-line local/code-no-any-casts - else if ('close' in child) { + else if ('close' in child) { // We have an HTML node in this case. // In case this node is an invalid unpaired HTML node, // we still want to search its children From bbe85db18866ec9880bfddd06f5bf3f3a2d7f96b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:23:03 +0200 Subject: [PATCH 0849/4355] Git - remove usages of `any` (#270075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - remove usages of `any` * 💄 fix check --- extensions/git/src/commands.ts | 8 ++------ extensions/git/src/emoji.ts | 3 +-- extensions/git/src/git.ts | 13 +++++-------- extensions/git/src/main.ts | 3 +-- extensions/git/src/util.ts | 7 +++++-- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 40a5045f8ec..eacc616b3ef 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2381,12 +2381,8 @@ export class CommandCenter { let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); // migration - // eslint-disable-next-line local/code-no-any-casts - if (promptToSaveFilesBeforeCommit as any === true) { - promptToSaveFilesBeforeCommit = 'always'; - // eslint-disable-next-line local/code-no-any-casts - } else if (promptToSaveFilesBeforeCommit as any === false) { - promptToSaveFilesBeforeCommit = 'never'; + if (typeof promptToSaveFilesBeforeCommit === 'boolean') { + promptToSaveFilesBeforeCommit = promptToSaveFilesBeforeCommit ? 'always' : 'never'; } let enableSmartCommit = config.get('enableSmartCommit') === true; diff --git a/extensions/git/src/emoji.ts b/extensions/git/src/emoji.ts index 7b1c459faf1..7c41ce6952e 100644 --- a/extensions/git/src/emoji.ts +++ b/extensions/git/src/emoji.ts @@ -24,8 +24,7 @@ export async function ensureEmojis() { async function loadEmojiMap() { const context = getExtensionContext(); - // eslint-disable-next-line local/code-no-any-casts - const uri = (Uri as any).joinPath(context.extensionUri, 'resources', 'emojis.json'); + const uri = Uri.joinPath(context.extensionUri, 'resources', 'emojis.json'); emojiMap = JSON.parse(new TextDecoder('utf8').decode(await workspace.fs.readFile(uri))); } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f055788f034..bb3685a53e4 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from 'url'; import which from 'which'; import { EventEmitter } from 'events'; import * as filetype from 'file-type'; -import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback } from './util'; +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback, Mutable } from './util'; import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode'; import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions } from './api/git'; import * as byline from 'byline'; @@ -307,9 +307,8 @@ export class GitError extends Error { stderr: this.stderr }, null, 2); - if (this.error) { - // eslint-disable-next-line local/code-no-any-casts - result += (this.error).stack; + if (this.error?.stack) { + result += this.error.stack; } return result; @@ -2973,10 +2972,8 @@ export class Repository { const result = await this.exec(['rev-list', '--left-right', '--count', `${branch.name}...${branch.upstream.remote}/${branch.upstream.name}`]); const [ahead, behind] = result.stdout.trim().split('\t'); - // eslint-disable-next-line local/code-no-any-casts - (branch as any).ahead = Number(ahead) || 0; - // eslint-disable-next-line local/code-no-any-casts - (branch as any).behind = Number(behind) || 0; + (branch as Mutable).ahead = Number(ahead) || 0; + (branch as Mutable).behind = Number(behind) || 0; } catch { } } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index b15af43bcd2..e48e2e339b5 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -84,8 +84,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const git = new Git({ gitPath: info.path, - // eslint-disable-next-line local/code-no-any-casts - userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`, + userAgent: `git/${info.version} (${os.version() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`, version: info.version, env: environment, }); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 3e156557ebf..83346c1756b 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -15,6 +15,10 @@ export const isRemote = env.remoteName !== undefined; export const isLinux = process.platform === 'linux'; export const isLinuxSnap = isLinux && !!process.env['SNAP'] && !!process.env['SNAP_REVISION']; +export type Mutable = { + -readonly [P in keyof T]: T[P] +}; + export function log(...args: any[]): void { console.log.apply(console, ['git:', ...args]); } @@ -238,8 +242,7 @@ export function readBytes(stream: Readable, bytes: number): Promise { bytesRead += bytesToRead; if (bytesRead === bytes) { - // eslint-disable-next-line local/code-no-any-casts - (stream as any).destroy(); // Will trigger the close event eventually + stream.destroy(); // Will trigger the close event eventually } }); From 11c1e8cd3461dc192d551b88a4a080515316adbf Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:30:03 -0700 Subject: [PATCH 0850/4355] Update most `IAction.run` calls to use `unknown` instead of `any` for args Part of #269213 This prevents accessing arbitrary properties on the args without first doing a type check or type assertion --- .../browser/actionWidgetDropdown.ts | 2 +- src/vs/platform/actions/common/actions.ts | 4 +- src/vs/platform/commands/common/commands.ts | 2 +- src/vs/platform/terminal/node/ptyService.ts | 2 +- .../api/node/extensionHostProcess.ts | 2 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/panel/panelActions.ts | 2 +- .../browser/parts/titlebar/titlebarActions.ts | 16 +++---- .../workbench/browser/parts/views/viewPane.ts | 4 +- .../browser/parts/views/viewPaneContainer.ts | 4 +- .../chat/browser/actions/chatActions.ts | 12 +++--- .../chat/browser/actions/chatClearActions.ts | 6 +-- .../browser/actions/chatCodeblockActions.ts | 10 ++--- .../browser/actions/chatContextActions.ts | 16 +++---- .../chat/browser/actions/chatCopyActions.ts | 6 +-- .../browser/actions/chatDeveloperActions.ts | 4 +- .../browser/actions/chatExecuteActions.ts | 42 +++++++++---------- .../browser/actions/chatFileTreeActions.ts | 4 +- .../chat/browser/actions/chatImportExport.ts | 2 +- .../chat/browser/actions/chatMoveActions.ts | 6 +-- .../actions/chatPromptNavigationActions.ts | 4 +- .../chat/browser/actions/chatTitleActions.ts | 10 ++--- .../chat/browser/actions/chatToolActions.ts | 4 +- .../browser/actions/manageModelsActions.ts | 2 +- .../browser/chatEditing/chatEditingActions.ts | 40 +++++++++--------- .../chatEditing/chatEditingEditorActions.ts | 16 +++---- .../browser/contrib/chatDynamicVariables.ts | 2 +- .../browser/contrib/chatInputCompletions.ts | 4 +- .../actions/chatDeveloperActions.ts | 2 +- .../actions/voiceChatActions.ts | 4 +- .../browser/commentsEditorContribution.ts | 16 +++---- .../debug/browser/debugEditorActions.ts | 2 +- .../browser/editSessions.contribution.ts | 2 +- .../browser/extensions.contribution.ts | 4 +- .../inlineChat/browser/inlineChatActions.ts | 12 +++--- .../mergeEditor/browser/commands/commands.ts | 6 +-- .../multiDiffEditor/browser/actions.ts | 2 +- .../contrib/outline/notebookOutline.ts | 6 +-- .../chat/notebook.chat.contribution.ts | 10 ++--- .../browser/controller/layoutActions.ts | 2 +- .../controller/notebookIndentationActions.ts | 10 ++--- .../browser/diff/notebookDiffActions.ts | 8 ++-- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../notebook/browser/notebookLogger.ts | 4 +- .../contrib/scm/browser/scm.contribution.ts | 2 +- .../contrib/scm/browser/scmHistoryViewPane.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../search/browser/searchActionsNav.ts | 7 ++-- .../search/browser/searchActionsTopBar.ts | 14 +++---- .../browser/searchEditor.contribution.ts | 2 +- .../share/browser/share.contribution.ts | 2 +- .../terminal.accessibility.contribution.ts | 2 +- .../testing/browser/testExplorerActions.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 4 +- 54 files changed, 182 insertions(+), 181 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts b/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts index 8201a47fcad..a7fb2453fc6 100644 --- a/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts +++ b/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts @@ -124,7 +124,7 @@ export class ActionWidgetDropdown extends BaseDropdown { actionBarActions = actionBarActions.map(action => ({ ...action, - run: async (...args: any[]) => { + run: async (...args: unknown[]) => { this.actionWidgetService.hide(); return action.run(...args); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 4e4ae925681..e6c137fb587 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -576,7 +576,7 @@ export class MenuItemAction implements IAction { } - run(...args: any[]): Promise { + run(...args: unknown[]): Promise { let runArgs: any[] = []; if (this._options?.arg) { @@ -649,7 +649,7 @@ export interface IAction2F1RequiredOptions { export abstract class Action2 { constructor(readonly desc: Readonly) { } - abstract run(accessor: ServicesAccessor, ...args: any[]): void; + abstract run(accessor: ServicesAccessor, ...args: unknown[]): void; } export function registerAction2(ctor: { new(): Action2 }): IDisposable { diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index b6e7bc6fe50..7a1a25ad74f 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -93,7 +93,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR constraints.push(arg.constraint); } const actualHandler = idOrCommand.handler; - idOrCommand.handler = function (accessor, ...args: any[]) { + idOrCommand.handler = function (accessor, ...args: unknown[]) { validateConstraints(args, constraints); return actualHandler(accessor, ...args); }; diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index ac1404223fa..1ddab07849a 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -42,7 +42,7 @@ export function traceRpc(_target: any, key: string, descriptor: any) { } const fnKey = 'value'; const fn = descriptor.value; - descriptor[fnKey] = async function (...args: any[]) { + descriptor[fnKey] = async function (...args: unknown[]) { if (this.traceRpcArgs.logService.getLevel() === LogLevel.Trace) { this.traceRpcArgs.logService.trace(`[RPC Request] PtyService#${fn.name}(${args.map(e => JSON.stringify(e)).join(', ')})`); } diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 7ab576f0a08..006c4a85a2b 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -120,7 +120,7 @@ function patchProcess(allowExit: boolean) { process.on = function (event: string, listener: (...args: any[]) => void) { if (event === 'uncaughtException') { const actualListener = listener; - listener = function (...args: any[]) { + listener = function (...args: unknown[]) { try { return actualListener.apply(undefined, args); } catch { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 9dd178b4526..f6ec4f02860 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -735,7 +735,7 @@ registerAction2(class FocusAndSelectBreadcrumbs extends Action2 { f1: true }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { focusAndSelectHandler(accessor, true); } }); @@ -754,7 +754,7 @@ registerAction2(class FocusBreadcrumbs extends Action2 { f1: true }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { focusAndSelectHandler(accessor, false); } }); diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index b1c398c1fef..1391a22f7a6 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -343,7 +343,7 @@ class MoveViewsBetweenPanelsAction extends Action2 { super(desc); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const viewDescriptorService = accessor.get(IViewDescriptorService); const layoutService = accessor.get(IWorkbenchLayoutService); const viewsService = accessor.get(IViewsService); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index f8e5e74bf6b..0ef3554a764 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -43,7 +43,7 @@ export class ToggleTitleBarConfigAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); const value = configService.getValue(this.section); configService.updateValue(this.section, !value); @@ -80,7 +80,7 @@ registerAction2(class ToggleCustomTitleBar extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); } @@ -98,7 +98,7 @@ registerAction2(class ToggleCustomTitleBarWindowed extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.WINDOWED); } @@ -134,7 +134,7 @@ class ToggleCustomTitleBar extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); const contextKeyService = accessor.get(IContextKeyService); const titleBarVisibility = configService.getValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY); @@ -170,7 +170,7 @@ registerAction2(class ShowCustomTitleBar extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO); } @@ -186,7 +186,7 @@ registerAction2(class HideCustomTitleBar extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.NEVER); } @@ -202,7 +202,7 @@ registerAction2(class HideCustomTitleBar extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); configService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.WINDOWED); } @@ -228,7 +228,7 @@ registerAction2(class ToggleEditorActions extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(accessor: ServicesAccessor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); const storageService = accessor.get(IStorageService); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index a047efbb82f..0eba1e6e0b9 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -850,7 +850,7 @@ export abstract class ViewAction extends Action2 { this.desc = desc; } - run(accessor: ServicesAccessor, ...args: any[]): unknown { + run(accessor: ServicesAccessor, ...args: unknown[]): unknown { const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId); if (view) { return this.runInView(accessor, view, ...args); @@ -858,5 +858,5 @@ export abstract class ViewAction extends Action2 { return undefined; } - abstract runInView(accessor: ServicesAccessor, view: T, ...args: any[]): unknown; + abstract runInView(accessor: ServicesAccessor, view: T, ...args: unknown[]): unknown; } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index f823ecfd051..477760e9d33 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -1170,7 +1170,7 @@ export abstract class ViewPaneContainerAction exte this.desc = desc; } - run(accessor: ServicesAccessor, ...args: any[]): unknown { + run(accessor: ServicesAccessor, ...args: unknown[]): unknown { const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(this.desc.viewPaneContainerId); if (viewPaneContainer) { return this.runInViewPaneContainer(accessor, viewPaneContainer, ...args); @@ -1178,7 +1178,7 @@ export abstract class ViewPaneContainerAction exte return undefined; } - abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: any[]): unknown; + abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: unknown[]): unknown; } class MoveViewPosition extends Action2 { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d5aa9983ebc..d6194928afb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1184,7 +1184,7 @@ export function registerChatActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const editorService = accessor.get(IEditorService); const editorGroupService = accessor.get(IEditorGroupsService); @@ -1221,9 +1221,9 @@ export function registerChatActions() { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const widgetService = accessor.get(IChatWidgetService); - const context: { widget?: IChatWidget } | undefined = args[0]; + const context = args[0] as { widget?: IChatWidget } | undefined; const widget = context?.widget ?? widgetService.lastFocusedWidget; if (!widget) { return; @@ -1258,7 +1258,7 @@ export function registerChatActions() { f1: true, }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const historyService = accessor.get(IChatWidgetHistoryService); historyService.clearHistory(); } @@ -1274,7 +1274,7 @@ export function registerChatActions() { f1: true, }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const editorGroupsService = accessor.get(IEditorGroupsService); const chatService = accessor.get(IChatService); const instantiationService = accessor.get(IInstantiationService); @@ -1394,7 +1394,7 @@ export function registerChatActions() { ] }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const widgetService = accessor.get(IChatWidgetService); widgetService.lastFocusedWidget?.focusInput(); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index a2eaf09a586..19b12e18f3d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -53,7 +53,7 @@ export function registerNewChatActions() { precondition: ChatContextKeys.enabled, }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { announceChatCleared(accessor.get(IAccessibilitySignalService)); await clearChatEditor(accessor); } @@ -106,8 +106,8 @@ export function registerNewChatActions() { } - async run(accessor: ServicesAccessor, ...args: any[]) { - const executeCommandContext: INewEditSessionActionContext | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + const executeCommandContext = args[0] as INewEditSessionActionContext | undefined; // Context from toolbar or lastFocusedWidget const context = getEditingSessionContext(accessor, args); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 6ef6dae952b..863d366db7d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -67,7 +67,7 @@ function isResponseFiltered(context: ICodeBlockActionContext) { } abstract class ChatCodeBlockAction extends Action2 { - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { let context = args[0]; if (!isCodeBlockActionContext(context)) { const codeEditorService = accessor.get(ICodeEditorService); @@ -143,7 +143,7 @@ export function registerChatCodeBlockActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0]; if (!isCodeBlockActionContext(context) || isResponseFiltered(context)) { return; @@ -549,7 +549,7 @@ export function registerChatCodeBlockActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateCodeBlocks(accessor); } }); @@ -571,7 +571,7 @@ export function registerChatCodeBlockActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateCodeBlocks(accessor, true); } }); @@ -611,7 +611,7 @@ function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): export function registerChatCodeCompareBlockActions() { abstract class ChatCompareCodeBlockAction extends Action2 { - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0]; if (!isCodeCompareBlockActionContext(context)) { return; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 6f0f67dc931..b7bfac015d0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -73,7 +73,7 @@ async function withChatView(accessor: ServicesAccessor): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const instaService = accessor.get(IInstantiationService); const widget = await instaService.invokeFunction(withChatView); if (!widget) { @@ -82,9 +82,9 @@ abstract class AttachResourceAction extends Action2 { return instaService.invokeFunction(this.runWithWidget.bind(this), widget, ...args); } - abstract runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: any[]): Promise; + abstract runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: unknown[]): Promise; - protected _getResources(accessor: ServicesAccessor, ...args: any[]): URI[] { + protected _getResources(accessor: ServicesAccessor, ...args: unknown[]): URI[] { const editorService = accessor.get(IEditorService); const contexts = isEditorCommandsContext(args[1]) ? this._getEditorResources(accessor, args) : Array.isArray(args[1]) ? args[1] : [args[0]]; @@ -109,7 +109,7 @@ abstract class AttachResourceAction extends Action2 { return files; } - private _getEditorResources(accessor: ServicesAccessor, ...args: any[]): URI[] { + private _getEditorResources(accessor: ServicesAccessor, ...args: unknown[]): URI[] { const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); return resolvedContext.groupedEditors @@ -175,7 +175,7 @@ class AttachFileToChatAction extends AttachResourceAction { }); } - override async runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: any[]): Promise { + override async runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: unknown[]): Promise { const files = this._getResources(accessor, ...args); if (!files.length) { return; @@ -215,7 +215,7 @@ class AttachFolderToChatAction extends AttachResourceAction { }); } - override async runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: any[]): Promise { + override async runWithWidget(accessor: ServicesAccessor, widget: IChatWidget, ...args: unknown[]): Promise { const folders = this._getResources(accessor, ...args); if (!folders.length) { return; @@ -414,7 +414,7 @@ export class AttachContextAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const instantiationService = accessor.get(IInstantiationService); const widgetService = accessor.get(IChatWidgetService); @@ -422,7 +422,7 @@ export class AttachContextAction extends Action2 { const keybindingService = accessor.get(IKeybindingService); const contextPickService = accessor.get(IChatContextPickService); - const context: { widget?: IChatWidget; placeholder?: string } | undefined = args[0]; + const context = args[0] as { widget?: IChatWidget; placeholder?: string } | undefined; const widget = context?.widget ?? widgetService.lastFocusedWidget; if (!widget) { return; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index ce8fbad377e..f690000ec9f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -29,7 +29,7 @@ export function registerChatCopyActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const clipboardService = accessor.get(IClipboardService); const chatWidgetService = accessor.get(IChatWidgetService); const widget = chatWidgetService.lastFocusedWidget; @@ -61,12 +61,12 @@ export function registerChatCopyActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const chatWidgetService = accessor.get(IChatWidgetService); const clipboardService = accessor.get(IClipboardService); const widget = chatWidgetService.lastFocusedWidget; - let item: ChatTreeItem | undefined = args[0]; + let item = args[0] as ChatTreeItem | undefined; if (!isChatTreeItem(item)) { item = widget?.getFocus(); if (!item) { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts index b533b006bfb..abfb0d71c44 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts @@ -31,7 +31,7 @@ class LogChatInputHistoryAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const chatWidgetService = accessor.get(IChatWidgetService); chatWidgetService.lastFocusedWidget?.logInputHistory(); } @@ -51,7 +51,7 @@ class LogChatIndexAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const chatService = accessor.get(IChatService); chatService.logChatIndex(); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 5738fbd62bb..684524f887f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -53,8 +53,8 @@ export interface IChatExecuteActionContext { } abstract class SubmitAction extends Action2 { - async run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const telemetryService = accessor.get(ITelemetryService); const widgetService = accessor.get(IChatWidgetService); const widget = context?.widget ?? widgetService.lastFocusedWidget; @@ -247,8 +247,8 @@ export class ChatDelegateToEditSessionAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const context: IChatExecuteActionContext | undefined = args[0]; + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const instantiationService = accessor.get(IInstantiationService); const inlineWidget = context?.widget ?? widgetService.lastFocusedWidget; @@ -314,7 +314,7 @@ class ToggleChatModeAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const commandService = accessor.get(ICommandService); const configurationService = accessor.get(IConfigurationService); const instaService = accessor.get(IInstantiationService); @@ -383,7 +383,7 @@ class SwitchToNextModelAction extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; widget?.input.switchToNextModel(); @@ -424,7 +424,7 @@ class OpenModelPickerAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; if (widget) { @@ -467,7 +467,7 @@ export class OpenModePickerAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; if (widget) { @@ -490,8 +490,8 @@ class ChangeChatModelAction extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { - const modelInfo: Pick = args[0]; + override run(accessor: ServicesAccessor, ...args: unknown[]): void { + const modelInfo = args[0] as Pick; // Type check the arg assertType(typeof modelInfo.vendor === 'string' && typeof modelInfo.id === 'string' && typeof modelInfo.family === 'string'); const widgetService = accessor.get(IChatWidgetService); @@ -567,8 +567,8 @@ class SubmitWithoutDispatchingAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const widget = context?.widget ?? widgetService.lastFocusedWidget; @@ -760,7 +760,7 @@ export class CreateRemoteAgentJobAction extends Action2 { return relativePaths; } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const contextKeyService = accessor.get(IContextKeyService); const remoteJobCreatingKey = ChatContextKeys.remoteJobCreating.bindTo(contextKeyService); @@ -971,8 +971,8 @@ export class ChatSubmitWithCodebaseAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const widget = context?.widget ?? widgetService.lastFocusedWidget; @@ -1029,8 +1029,8 @@ class SendToNewChatAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const dialogService = accessor.get(IDialogService); @@ -1080,8 +1080,8 @@ export class CancelAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const widget = context?.widget ?? widgetService.lastFocusedWidget; if (!widget) { @@ -1125,8 +1125,8 @@ export class CancelEdit extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; + run(accessor: ServicesAccessor, ...args: unknown[]) { + const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); const widget = context?.widget ?? widgetService.lastFocusedWidget; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts index 41ddea9c443..425f3539daa 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts @@ -30,7 +30,7 @@ export function registerChatFileTreeActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateTrees(accessor, false); } }); @@ -51,7 +51,7 @@ export function registerChatFileTreeActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateTrees(accessor, true); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index ff7747762b2..de5f0c49ecc 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -78,7 +78,7 @@ export function registerChatExportActions() { f1: true, }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const fileDialogService = accessor.get(IFileDialogService); const fileService = accessor.get(IFileService); const editorService = accessor.get(IEditorService); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index df17e6dc252..4d25bee34f1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -46,7 +46,7 @@ export function registerMoveActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0]; executeMoveToAction(accessor, MoveToNewLocation.Editor, isChatViewTitleActionContext(context) ? context.sessionId : undefined); } @@ -69,7 +69,7 @@ export function registerMoveActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0]; executeMoveToAction(accessor, MoveToNewLocation.Window, isChatViewTitleActionContext(context) ? context.sessionId : undefined); } @@ -86,7 +86,7 @@ export function registerMoveActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { return moveToSidebar(accessor); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts index 99c5456a65e..290a232e7d9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatPromptNavigationActions.ts @@ -30,7 +30,7 @@ export function registerChatPromptNavigationActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateUserPrompts(accessor, false); } }); @@ -51,7 +51,7 @@ export function registerChatPromptNavigationActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { navigateUserPrompts(accessor, true); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index aaa33c2de9c..90beba71336 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -54,7 +54,7 @@ export function registerChatTitleActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const item = args[0]; if (!isResponseVM(item)) { return; @@ -101,7 +101,7 @@ export function registerChatTitleActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const item = args[0]; if (!isResponseVM(item)) { return; @@ -153,7 +153,7 @@ export function registerChatTitleActions() { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const item = args[0]; if (!isResponseVM(item)) { return; @@ -199,7 +199,7 @@ export function registerChatTitleActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const chatWidgetService = accessor.get(IChatWidgetService); let item = args[0]; @@ -285,7 +285,7 @@ export function registerChatTitleActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const item = args[0]; if (!isResponseVM(item)) { return; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index e6a1b6200f7..c079d2365c6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -46,7 +46,7 @@ export const SkipToolConfirmationActionId = 'workbench.action.chat.skipTool'; abstract class ToolConfirmationAction extends Action2 { protected abstract getReason(): ConfirmedReason; - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const chatWidgetService = accessor.get(IChatWidgetService); const widget = chatWidgetService.lastFocusedWidget; const lastItem = widget?.viewModel?.getItems().at(-1); @@ -126,7 +126,7 @@ class ConfigureToolsAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const instaService = accessor.get(IInstantiationService); const chatWidgetService = accessor.get(IChatWidgetService); diff --git a/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts b/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts index 8cb286f68be..7ec0500de58 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts @@ -44,7 +44,7 @@ export class ManageModelsAction extends Action2 { f1: true }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const languageModelsService = accessor.get(ILanguageModelsService); const quickInputService = accessor.get(IQuickInputService); const commandService = accessor.get(ICommandService); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index d9f782fc7e7..ad98ed0bc39 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -44,7 +44,7 @@ export abstract class EditingSessionAction extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const context = getEditingSessionContext(accessor, args); if (!context || !context.editingSession) { return; @@ -53,7 +53,7 @@ export abstract class EditingSessionAction extends Action2 { return this.runEditingSessionAction(accessor, context.editingSession, context.chatWidget, ...args); } - abstract runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): any; + abstract runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]): any; } /** @@ -83,7 +83,7 @@ export function getEditingSessionContext(accessor: ServicesAccessor, args: any[] abstract class WorkingSetAction extends EditingSessionAction { - runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]) { const uris: URI[] = []; if (URI.isUri(args[0])) { @@ -213,7 +213,7 @@ export class ChatEditingAcceptAllAction extends EditingSessionAction { }); } - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]) { await editingSession.accept(); } } @@ -244,7 +244,7 @@ export class ChatEditingDiscardAllAction extends EditingSessionAction { }); } - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]) { await discardAllEditsWithConfirmation(accessor, editingSession); } } @@ -297,7 +297,7 @@ export class ChatEditingShowChangesAction extends EditingSessionAction { }); } - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]): Promise { await editingSession.show(); } } @@ -401,8 +401,8 @@ registerAction2(class RemoveAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - let item: ChatTreeItem | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + let item = args[0] as ChatTreeItem | undefined; const chatWidgetService = accessor.get(IChatWidgetService); const configurationService = accessor.get(IConfigurationService); const widget = chatWidgetService.lastFocusedWidget; @@ -450,8 +450,8 @@ registerAction2(class RestoreCheckpointAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - let item: ChatTreeItem | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + let item = args[0] as ChatTreeItem | undefined; const chatWidgetService = accessor.get(IChatWidgetService); const widget = chatWidgetService.lastFocusedWidget; if (!isResponseVM(item) && !isRequestVM(item)) { @@ -491,8 +491,8 @@ registerAction2(class RestoreLastCheckpoint extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - let item: ChatTreeItem | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + let item = args[0] as ChatTreeItem | undefined; const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); const widget = chatWidgetService.lastFocusedWidget; @@ -551,8 +551,8 @@ registerAction2(class EditAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - let item: ChatTreeItem | undefined = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + let item = args[0] as ChatTreeItem | undefined; const chatWidgetService = accessor.get(IChatWidgetService); const widget = chatWidgetService.lastFocusedWidget; if (!isResponseVM(item) && !isRequestVM(item)) { @@ -584,8 +584,8 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const context: { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined = args[0]; + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const context = args[0] as { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined; if (!context?.sessionId) { return; } @@ -610,8 +610,8 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const context: { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined = args[0]; + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const context = args[0] as { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined; if (!context?.sessionId) { return; } @@ -651,7 +651,7 @@ registerAction2(class ResolveSymbolsContextAction extends EditingSessionAction { }); } - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]): Promise { if (args.length === 0 || !isLocation(args[0])) { return; } @@ -738,7 +738,7 @@ export class ViewPreviousEditsAction extends EditingSessionAction { }); } - override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: unknown[]): Promise { await editingSession.show(true); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index b17d6c243ed..7ad127ad5ea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -20,7 +20,7 @@ import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_FOCUSED } from '../../../notebook/common/notebookContextKeys.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; import { ctxHasEditorModification, ctxIsCurrentlyBeingModified, ctxIsGlobalEditingSession, ctxReviewModeEnabled } from './chatEditingEditorContextKeys.js'; @@ -34,7 +34,7 @@ abstract class ChatEditingEditorAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]) { + override async run(accessor: ServicesAccessor, ...args: unknown[]) { const instaService = accessor.get(IInstantiationService); const chatEditingService = accessor.get(IChatEditingService); @@ -59,7 +59,7 @@ abstract class ChatEditingEditorAction extends Action2 { return instaService.invokeFunction(this.runChatEditingCommand.bind(this), session, entry, ctrl, ...args); } - abstract runChatEditingCommand(accessor: ServicesAccessor, session: IChatEditingSession, entry: IModifiedFileEntry, integration: IModifiedFileEntryEditorIntegration, ...args: any[]): Promise | void; + abstract runChatEditingCommand(accessor: ServicesAccessor, session: IChatEditingSession, entry: IModifiedFileEntry, integration: IModifiedFileEntryEditorIntegration, ...args: unknown[]): Promise | void; } abstract class NavigateAction extends ChatEditingEditorAction { @@ -251,14 +251,14 @@ abstract class AcceptRejectHunkAction extends ChatEditingEditorAction { ); } - override async runChatEditingCommand(accessor: ServicesAccessor, session: IChatEditingSession, entry: IModifiedFileEntry, ctrl: IModifiedFileEntryEditorIntegration, ...args: any[]): Promise { + override async runChatEditingCommand(accessor: ServicesAccessor, session: IChatEditingSession, entry: IModifiedFileEntry, ctrl: IModifiedFileEntryEditorIntegration, ...args: unknown[]): Promise { const instaService = accessor.get(IInstantiationService); if (this._accept) { - await ctrl.acceptNearestChange(args[0]); + await ctrl.acceptNearestChange(args[0] as IModifiedFileEntryChangeHunk | undefined); } else { - await ctrl.rejectNearestChange(args[0]); + await ctrl.rejectNearestChange(args[0] as IModifiedFileEntryChangeHunk | undefined); } if (entry.changesCount.get() === 0) { @@ -297,8 +297,8 @@ class ToggleDiffAction extends ChatEditingEditorAction { }); } - override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, _entry: IModifiedFileEntry, integration: IModifiedFileEntryEditorIntegration, ...args: any[]): Promise | void { - integration.toggleDiff(args[0]); + override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, _entry: IModifiedFileEntry, integration: IModifiedFileEntryEditorIntegration, ...args: unknown[]): Promise | void { + integration.toggleDiff(args[0] as IModifiedFileEntryChangeHunk | undefined); } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 4ebdeae3f35..5041b54718f 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -209,7 +209,7 @@ export class AddDynamicVariableAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0]; if (!isAddDynamicVariableContext(context)) { return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 77433626877..53593d63127 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -569,8 +569,8 @@ class AssignSelectedAgentAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - const arg: AssignSelectedAgentActionArgs = args[0]; + async run(accessor: ServicesAccessor, ...args: unknown[]) { + const arg = args[0] as AssignSelectedAgentActionArgs | undefined; if (!arg || !arg.widget || !arg.agent) { return; } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts index d9f8fb12271..72b00481cda 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/chatDeveloperActions.ts @@ -29,7 +29,7 @@ class OpenChatStorageFolderAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const chatService = accessor.get(IChatService); const nativeHostService = accessor.get(INativeHostService); const storagePath = chatService.getChatStorageFolder(); diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index 06eea8a31d7..3b485002c27 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -889,7 +889,7 @@ export class ReadChatResponseAloud extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const instantiationService = accessor.get(IInstantiationService); const chatWidgetService = accessor.get(IChatWidgetService); @@ -1000,7 +1000,7 @@ export class StopReadChatItemAloud extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { ChatSynthesizerSessions.getInstance(accessor.get(IInstantiationService)).stop(); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 9c44f25179a..810eec3e263 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -92,7 +92,7 @@ registerAction2(class extends Action2 { } }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return; @@ -129,7 +129,7 @@ registerAction2(class extends Action2 { } }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return; @@ -205,7 +205,7 @@ registerAction2(class extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return; @@ -237,7 +237,7 @@ registerAction2(class extends Action2 { }] }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const commentService = accessor.get(ICommentService); const enable = commentService.isCommentingEnabled; commentService.enableCommenting(!enable); @@ -301,7 +301,7 @@ registerAction2(class extends Action2 { precondition: CommentContextKeys.activeCursorHasComment, }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return; @@ -348,7 +348,7 @@ registerAction2(class extends Action2 { }] }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { getActiveController(accessor)?.collapseAll(); } }); @@ -371,7 +371,7 @@ registerAction2(class extends Action2 { }] }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { getActiveController(accessor)?.expandAll(); } }); @@ -394,7 +394,7 @@ registerAction2(class extends Action2 { }] }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { getActiveController(accessor)?.expandUnresolved(); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 81b57c8b357..6edaa7cb866 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -285,7 +285,7 @@ class ToggleDisassemblyViewSourceCodeAction extends Action2 { }); } - run(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): void { + run(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { const configService = accessor.get(IConfigurationService); if (configService) { const value = configService.getValue('debug').disassemblyView.showSourceCode; diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index bde2c8dc01e..d28fa1cc182 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -302,7 +302,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo super(showOutputChannelCommand); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const outputChannel = accessor.get(IOutputService); void outputChannel.showChannel(editSessionsLogId); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 1bc0b4bd102..b78c1385ac9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -516,7 +516,7 @@ async function runAction(action: IAction): Promise { type IExtensionActionOptions = IAction2Options & { menuTitles?: { [id: string]: string }; - run(accessor: ServicesAccessor, ...args: any[]): Promise; + run(accessor: ServicesAccessor, ...args: unknown[]): Promise; }; class ExtensionsContributions extends Disposable implements IWorkbenchContribution { @@ -1942,7 +1942,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi menu: menusWithOutTitles }); } - run(accessor: ServicesAccessor, ...args: any[]): Promise { + run(accessor: ServicesAccessor, ...args: unknown[]): Promise { return extensionActionOptions.run(accessor, ...args); } })); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 4849a2b0621..1fb92a65714 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -82,7 +82,7 @@ export class StartSessionAction extends Action2 { }] }); } - override run(accessor: ServicesAccessor, ...args: any[]): any { + override run(accessor: ServicesAccessor, ...args: unknown[]): any { const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor(); @@ -244,7 +244,7 @@ export abstract class AbstractInline1ChatAction extends EditorAction2 { this.runInlineChatCommand(accessor, ctrl, editor, ..._args); } - abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void; + abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: unknown[]): void; } export class ArrowOutUpAction extends AbstractInline1ChatAction { @@ -458,7 +458,7 @@ export class MoveToNextHunk extends AbstractInline1ChatAction { }); } - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: unknown[]): void { ctrl.moveHunk(true); } } @@ -478,7 +478,7 @@ export class MoveToPreviousHunk extends AbstractInline1ChatAction { }); } - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: unknown[]): void { ctrl.moveHunk(false); } } @@ -613,7 +613,7 @@ abstract class AbstractInline2ChatAction extends EditorAction2 { this.runInlineChatCommand(accessor, ctrl, editor, ..._args); } - abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void; + abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, editor: ICodeEditor, ...args: unknown[]): void; } class KeepOrUndoSessionAction extends AbstractInline2ChatAction { @@ -706,7 +706,7 @@ export class CloseSessionAction2 extends AbstractInline2ChatAction { }); } - runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void { + runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: unknown[]): void { const inlineChatSessions = accessor.get(IInlineChatSessionService); if (editor.hasModel()) { const textModel = editor.getModel(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 0a104f16990..2c55896b547 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -58,7 +58,7 @@ abstract class MergeEditorAction2 extends Action2 { super(desc); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { const vm = activeEditorPane.viewModel.get(); @@ -79,7 +79,7 @@ abstract class MergeEditorAction2 extends Action2 { } } - abstract runWithMergeEditor(context: MergeEditorAction2Args, accessor: ServicesAccessor, ...args: any[]): unknown; + abstract runWithMergeEditor(context: MergeEditorAction2Args, accessor: ServicesAccessor, ...args: unknown[]): unknown; } export class OpenMergeEditor extends Action2 { @@ -592,7 +592,7 @@ export class AcceptAllCombination extends MergeEditorAction2 { }); } - override runWithMergeEditor(context: MergeEditorAction2Args, accessor: ServicesAccessor, ...args: any[]) { + override runWithMergeEditor(context: MergeEditorAction2Args, accessor: ServicesAccessor, ...args: unknown[]) { const { viewModel } = context; const modifiedBaseRanges = viewModel.model.modifiedBaseRanges.get(); const model = viewModel.model; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index cf1c2885f0e..86aeac17ca8 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -35,7 +35,7 @@ export class GoToFileAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 48d5bc785cb..c74b6d6062a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -915,7 +915,7 @@ registerAction2(class ToggleShowMarkdownHeadersOnly extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const configurationService = accessor.get(IConfigurationService); const showMarkdownHeadersOnly = configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); configurationService.updateValue(NotebookSetting.outlineShowMarkdownHeadersOnly, !showMarkdownHeadersOnly); @@ -939,7 +939,7 @@ registerAction2(class ToggleCodeCellEntries extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const configurationService = accessor.get(IConfigurationService); const showCodeCells = configurationService.getValue(NotebookSetting.outlineShowCodeCells); configurationService.updateValue(NotebookSetting.outlineShowCodeCells, !showCodeCells); @@ -963,7 +963,7 @@ registerAction2(class ToggleCodeCellSymbolEntries extends Action2 { }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { const configurationService = accessor.get(IConfigurationService); const showCodeCellSymbols = configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); configurationService.updateValue(NotebookSetting.outlineShowCodeCellSymbols, !showCodeCellSymbols); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index 6fbfafefd69..a28bba2155a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -163,7 +163,7 @@ export class SelectAndInsertKernelVariableAction extends Action2 { static readonly ID = 'notebook.chat.selectAndInsertKernelVariable'; - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const editorService = accessor.get(IEditorService); const notebookKernelService = accessor.get(INotebookKernelService); const quickInputService = accessor.get(IQuickInputService); @@ -174,14 +174,14 @@ export class SelectAndInsertKernelVariableAction extends Action2 { return; } - const context = args[0]; + const context = args[0] as { widget: IChatWidget; range?: Range; variable?: string } | undefined; if (!context || !('widget' in context) || !('range' in context)) { return; } - const widget = context.widget; - const range = context.range; - const variable = context.variable; + const widget = context.widget; + const range = context.range; + const variable = context.variable; if (variable !== undefined) { this.addVariableReference(widget, variable, range, false); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index a278485249f..235545bf8d1 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -155,7 +155,7 @@ registerAction2(class ToggleCellToolbarPositionFromEditorTitle extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { return accessor.get(ICommandService).executeCommand('notebook.toggleCellToolbarPosition', ...args); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts index 0f4975a9348..bdc52d491cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -30,7 +30,7 @@ export class NotebookIndentUsingTabs extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { changeNotebookIndentation(accessor, false, false); } } @@ -46,7 +46,7 @@ export class NotebookIndentUsingSpaces extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { changeNotebookIndentation(accessor, true, false); } } @@ -62,7 +62,7 @@ export class NotebookChangeTabDisplaySize extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { changeNotebookIndentation(accessor, true, true); } } @@ -78,7 +78,7 @@ export class NotebookIndentationToSpacesAction extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { convertNotebookIndentation(accessor, true); } } @@ -94,7 +94,7 @@ export class NotebookIndentationToTabsAction extends Action2 { }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { convertNotebookIndentation(accessor, false); } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index e3438e83bd7..91be76a2a68 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -196,7 +196,7 @@ registerAction2(class GoToFileAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; @@ -266,7 +266,7 @@ registerAction2(class extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; @@ -310,7 +310,7 @@ registerAction2(class extends Action2 { } ); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; @@ -353,7 +353,7 @@ registerAction2(class extends Action2 { } ); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const uri = args[0] as URI; const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4a075ca0244..189741c25c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -477,7 +477,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _debugFlag: boolean = false; - private _debug(...args: any[]) { + private _debug(...args: unknown[]) { if (!this._debugFlag) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookLogger.ts b/src/vs/workbench/contrib/notebook/browser/notebookLogger.ts index 4cf10ce8e79..09d8d635261 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookLogger.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookLogger.ts @@ -18,14 +18,14 @@ class NotebookLogger { // }, 1000000); } - debug(...args: any[]) { + debug(...args: unknown[]) { const date = new Date(); console.log(`${date.getSeconds()}:${date.getMilliseconds().toString().padStart(3, '0')}`, `frame #${this._frameId}: `, ...args); } } const instance = new NotebookLogger(); -export function notebookDebug(...args: any[]) { +export function notebookDebug(...args: unknown[]) { instance.debug(...args); } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index a2d4dc91c14..fa0903c081c 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -664,7 +664,7 @@ registerAction2(class extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const commandService = accessor.get(ICommandService); const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index f4239227667..d67712de4c6 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -2018,7 +2018,7 @@ export class SCMHistoryViewPane extends ViewPane { } }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: unknown[]): void { const commandService = accessor.get(ICommandService); commandService.executeCommand(actionId, ...args, historyItemRef.id); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index d32554a552d..7fa433eb6d5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1351,7 +1351,7 @@ registerAction2(class extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const commandService = accessor.get(ICommandService); const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index da1c42c240f..97186a83988 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -40,13 +40,14 @@ registerAction2(class ToggleQueryDetailsAction extends Action2 { }, }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { + const options = args[0] as { show?: boolean } | undefined; const contextService = accessor.get(IContextKeyService).getContext(getActiveElement()); if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { - (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(args[0]?.show); + (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(options?.show); } else if (contextService.getValue(Constants.SearchContext.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); - assertReturnsDefined(searchView).toggleQueryDetails(undefined, args[0]?.show); + assertReturnsDefined(searchView).toggleQueryDetails(undefined, options?.show); } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index ce60af79ee4..a85da686835 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -83,7 +83,7 @@ registerAction2(class RefreshAction extends Action2 { }] }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { return refreshSearch(accessor); } }); @@ -105,7 +105,7 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { }] }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { return collapseDeepestExpandedLevel(accessor); } }); @@ -127,7 +127,7 @@ registerAction2(class ExpandAllAction extends Action2 { }] }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { return expandAll(accessor); } }); @@ -149,7 +149,7 @@ registerAction2(class ClearSearchResultsAction extends Action2 { }] }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { return clearSearchResults(accessor); } }); @@ -172,7 +172,7 @@ registerAction2(class ViewAsTreeAction extends Action2 { }] }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { await searchView.setTreeView(true); @@ -197,7 +197,7 @@ registerAction2(class ViewAsListAction extends Action2 { }] }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { await searchView.setTreeView(false); @@ -221,7 +221,7 @@ registerAction2(class SearchWithAIAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { + async run(accessor: ServicesAccessor, ...args: unknown[]) { const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.requestAIResults(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 5d21181b3a1..39bd4e4abb4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -565,7 +565,7 @@ registerAction2(class OpenSearchEditorAction extends Action2 { }] }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { return openSearchEditor(accessor); } }); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index 3566792aa9d..66b56f96b27 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -103,7 +103,7 @@ class ShareWorkbenchContribution extends Disposable { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const shareService = accessor.get(IShareService); const activeEditor = accessor.get(IEditorService)?.activeEditor; const resourceUri = (activeEditor && EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY })) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 9200de5d255..eea2f252247 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -289,7 +289,7 @@ class FocusAccessibleBufferAction extends Action2 { ] }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const terminalService = accessor.get(ITerminalService); const terminal = await terminalService.getActiveOrCreateInstance(); if (!terminal?.xterm) { diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 84b51b4740d..7e2eddff7a8 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1843,7 +1843,7 @@ abstract class TestNavigationAction extends SymbolNavigationAction { protected testService!: ITestService; // little hack... protected uriIdentityService!: IUriIdentityService; - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]) { this.testService = accessor.get(ITestService); this.uriIdentityService = accessor.get(IUriIdentityService); return super.runEditorCommand(accessor, editor, ...args); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 67ba6677f38..4fe094b5ae3 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -1290,7 +1290,7 @@ class TimelinePaneCommands extends Disposable { } }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { pane.reset(); } })); @@ -1362,7 +1362,7 @@ class TimelinePaneCommands extends Disposable { toggled: ContextKeyExpr.regex(`timelineExcludeSources`, new RegExp(`\\b${escapeRegExpCharacters(source.id)}\\b`)).negate() }); } - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { if (excluded.has(source.id)) { excluded.delete(source.id); } else { From cc61ece43363646b991e46f075781784374cb319 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:41:01 -0700 Subject: [PATCH 0851/4355] Update caller side of command service to use `unknown` instead of `any` for args Part of #269213 Updates the command service to use `unknown` for args. Does not yet update `ICommandHandler` as this will require many more changes --- .../editor/standalone/browser/standaloneServices.ts | 2 +- src/vs/editor/test/browser/editorTestServices.ts | 2 +- .../test/browser/services/openerService.test.ts | 2 +- src/vs/platform/commands/common/commands.ts | 12 ++++++------ .../test/common/abstractKeybindingService.test.ts | 4 ++-- src/vs/workbench/api/browser/mainThreadCommands.ts | 2 +- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostCommands.ts | 8 ++++---- .../test/browser/notebookExecutionService.test.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- .../services/commands/common/commandService.ts | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 220776c7154..12814316f23 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -382,7 +382,7 @@ export class StandaloneCommandService implements ICommandService { this._instantiationService = instantiationService; } - public executeCommand(id: string, ...args: any[]): Promise { + public executeCommand(id: string, ...args: unknown[]): Promise { const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index 861a8f778d2..4567ca51837 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -69,7 +69,7 @@ export class TestCommandService implements ICommandService { this._instantiationService = instantiationService; } - public executeCommand(id: string, ...args: any[]): Promise { + public executeCommand(id: string, ...args: unknown[]): Promise { const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index 8e35ac41078..2046fc54d4e 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -24,7 +24,7 @@ suite('OpenerService', function () { declare readonly _serviceBrand: undefined; onWillExecuteCommand = () => Disposable.None; onDidExecuteCommand = () => Disposable.None; - executeCommand(id: string, ...args: any[]): Promise { + executeCommand(id: string, ...args: unknown[]): Promise { lastCommand = { id, args }; return Promise.resolve(undefined); } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index b6e7bc6fe50..cb3546671b0 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -15,15 +15,15 @@ import { createDecorator, ServicesAccessor } from '../../instantiation/common/in export const ICommandService = createDecorator('commandService'); export interface ICommandEvent { - commandId: string; - args: any[]; + readonly commandId: string; + readonly args: unknown[]; } export interface ICommandService { readonly _serviceBrand: undefined; - onWillExecuteCommand: Event; - onDidExecuteCommand: Event; - executeCommand(commandId: string, ...args: any[]): Promise; + readonly onWillExecuteCommand: Event; + readonly onDidExecuteCommand: Event; + executeCommand(commandId: string, ...args: unknown[]): Promise; } export type ICommandsMap = Map; @@ -93,7 +93,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR constraints.push(arg.constraint); } const actualHandler = idOrCommand.handler; - idOrCommand.handler = function (accessor, ...args: any[]) { + idOrCommand.handler = function (accessor, ...args: unknown[]) { validateConstraints(args, constraints); return actualHandler(accessor, ...args); }; diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index b8ac90774ed..0887aad785a 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -104,7 +104,7 @@ suite('AbstractKeybindingService', () => { let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; let currentContextValue: IContext | null = null; - let executeCommandCalls: { commandId: string; args: any[] }[] = null!; + let executeCommandCalls: { commandId: string; args: unknown[] }[] = null!; let showMessageCalls: { sev: Severity; message: any }[] = null!; let statusMessageCalls: string[] | null = null; let statusMessageCallsDisposed: string[] | null = null; @@ -148,7 +148,7 @@ suite('AbstractKeybindingService', () => { _serviceBrand: undefined, onWillExecuteCommand: () => Disposable.None, onDidExecuteCommand: () => Disposable.None, - executeCommand: (commandId: string, ...args: any[]): Promise => { + executeCommand: (commandId: string, ...args: unknown[]): Promise => { executeCommandCalls.push({ commandId: commandId, args: args diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index 9e92c453fc2..9384b9e7cd9 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -78,7 +78,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { } } - async $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise { + async $executeCommand(id: string, args: unknown[] | SerializableObjectWithBuffers, retry: boolean): Promise { if (args instanceof SerializableObjectWithBuffers) { args = args.value; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e79863b6f5a..4821f351962 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -120,7 +120,7 @@ export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; $unregisterCommand(id: string): void; $fireCommandActivationEvent(id: string): void; - $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise; + $executeCommand(id: string, args: unknown[] | SerializableObjectWithBuffers, retry: boolean): Promise; $getCommands(): Promise; } diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index d914f21a29d..161e19baa3a 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -171,12 +171,12 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } - executeCommand(id: string, ...args: any[]): Promise { + executeCommand(id: string, ...args: unknown[]): Promise { this._logService.trace('ExtHostCommands#executeCommand', id); return this._doExecuteCommand(id, args, true); } - private async _doExecuteCommand(id: string, args: any[], retry: boolean): Promise { + private async _doExecuteCommand(id: string, args: unknown[], retry: boolean): Promise { if (this._commands.has(id)) { // - We stay inside the extension host and support @@ -229,7 +229,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { } } - private async _executeContributedCommand(id: string, args: any[], annotateError: boolean): Promise { + private async _executeContributedCommand(id: string, args: unknown[], annotateError: boolean): Promise { const command = this._commands.get(id); if (!command) { throw new Error('Unknown command'); @@ -310,7 +310,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } - $executeContributedCommand(id: string, ...args: any[]): Promise { + $executeContributedCommand(id: string, ...args: unknown[]): Promise { this._logService.trace('ExtHostCommands#$executeContributedCommand', id); const cmdHandler = this._commands.get(id); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 2113cf4fe1e..72a70c4523d 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -79,7 +79,7 @@ suite('NotebookExecutionService', () => { }); instantiationService.stub(ICommandService, new class extends mock() { - override executeCommand(_commandId: string, ..._args: any[]) { + override executeCommand(_commandId: string, ..._args: unknown[]) { return Promise.resolve(undefined); } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index d32554a552d..9fc07692b0d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3138,7 +3138,7 @@ export class SCMActionButton implements IDisposable { clearNode(this.container); } - private async executeCommand(commandId: string, ...args: any[]): Promise { + private async executeCommand(commandId: string, ...args: unknown[]): Promise { try { await this.commandService.executeCommand(commandId, ...args); } catch (ex) { diff --git a/src/vs/workbench/services/commands/common/commandService.ts b/src/vs/workbench/services/commands/common/commandService.ts index 93d163142cc..05aea74d96d 100644 --- a/src/vs/workbench/services/commands/common/commandService.ts +++ b/src/vs/workbench/services/commands/common/commandService.ts @@ -49,7 +49,7 @@ export class CommandService extends Disposable implements ICommandService { return notCancellablePromise(this._starActivation); } - async executeCommand(id: string, ...args: any[]): Promise { + async executeCommand(id: string, ...args: unknown[]): Promise { this._logService.trace('CommandService#executeCommand', id); const activationEvent = `onCommand:${id}`; @@ -89,7 +89,7 @@ export class CommandService extends Disposable implements ICommandService { return this._tryExecuteCommand(id, args); } - private _tryExecuteCommand(id: string, args: any[]): Promise { + private _tryExecuteCommand(id: string, args: unknown[]): Promise { const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); From c37cfe47adc3f7f8b7e03dd4e6d1802b8f9d5137 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:42:53 -0700 Subject: [PATCH 0852/4355] Don't remove `type` property --- src/vs/platform/instantiation/common/instantiation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 1d798650cd3..f1453db885e 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -91,6 +91,7 @@ export interface IInstantiationService { */ export interface ServiceIdentifier { (...args: any[]): void; + type: T; } @@ -112,12 +113,12 @@ export function createDecorator(serviceId: string): ServiceIdentifier { return _util.serviceIds.get(serviceId)!; } - const id: ServiceIdentifier = function (target: Function, key: string, index: number) { + const id = function (target: Function, key: string, index: number) { if (arguments.length !== 3) { throw new Error('@IServiceName-decorator can only be used to decorate a parameter'); } storeServiceDependency(id, target, index); - }; + } as ServiceIdentifier; id.toString = () => serviceId; From 8845fc4b5c9744c04a48025b2b10efe96e33f012 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:28:27 -0700 Subject: [PATCH 0853/4355] Remove any casts in auth extensions (#270089) --- extensions/github-authentication/src/node/crypto.ts | 3 +-- .../microsoft-authentication/src/common/telemetryReporter.ts | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/extensions/github-authentication/src/node/crypto.ts b/extensions/github-authentication/src/node/crypto.ts index fb3b121ec77..27b3cafd8b1 100644 --- a/extensions/github-authentication/src/node/crypto.ts +++ b/extensions/github-authentication/src/node/crypto.ts @@ -5,5 +5,4 @@ import { webcrypto } from 'crypto'; -// eslint-disable-next-line local/code-no-any-casts -export const crypto = webcrypto as any as Crypto; +export const crypto = webcrypto; diff --git a/extensions/microsoft-authentication/src/common/telemetryReporter.ts b/extensions/microsoft-authentication/src/common/telemetryReporter.ts index ce1d36b2621..67b202982ce 100644 --- a/extensions/microsoft-authentication/src/common/telemetryReporter.ts +++ b/extensions/microsoft-authentication/src/common/telemetryReporter.ts @@ -86,7 +86,7 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio this._telemetryReporter.sendTelemetryEvent('logoutFailed'); } - sendTelemetryErrorEvent(error: unknown): void { + sendTelemetryErrorEvent(error: Error | string): void { let errorMessage: string | undefined; let errorName: string | undefined; let errorCode: string | undefined; @@ -94,8 +94,7 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio if (typeof error === 'string') { errorMessage = error; } else { - // eslint-disable-next-line local/code-no-any-casts - const authError: AuthError = error as any; + const authError: AuthError = error as AuthError; // don't set error message or stack because it contains PII errorCode = authError.errorCode; errorCorrelationId = authError.correlationId; From 98dd6045f57b8094639b6721c97d9975f1c1c3b8 Mon Sep 17 00:00:00 2001 From: Giovanni Magliocchetti <62136803+obrobrio2000@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:30:17 +0200 Subject: [PATCH 0854/4355] testing: add "Go to Next/Previous Uncovered Line" navigation for Test Coverage (#269505) * Add navigation for uncovered lines in test coverage Implements [#258967](https://github.com/obrobrio2000/vscode/issues/258967) - Add commands to navigate to next/previous uncovered lines - Add navigation buttons to coverage toolbar widget - Add keyboard shortcuts (Alt+F9 / Shift+Alt+F9) - Navigation wraps around at file boundaries - Buttons appear in coverage toolbar and editor title menu This feature helps developers efficiently review coverage gaps by allowing them to jump between uncovered lines without manual scrolling through the file. Signed-off-by: Giovanni Magliocchetti * Update src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix navigation logic and add sorting for uncovered lines - Add proper sorting of missed lines by line number - Fix corrupted control flow in previous/back navigation - Add missing variable declarations - Remove duplicate/incorrect conditional blocks This fixes the navigation to work correctly with properly ordered missed lines. Signed-off-by: Giovanni Magliocchetti * feat: add navigation commands for uncovered lines in code coverage Signed-off-by: Giovanni Magliocchetti * fix: add missing ID property and remove unused method - Add static ID property to CodeCoverageDecorations class - Remove unused isMissedLine method (logic handled inline) - Fixes TypeScript compilation errors Signed-off-by: Giovanni Magliocchetti * fix: update CodeCoverageDecorations ID to use Testing constant Signed-off-by: Giovanni Magliocchetti * fix: refactor navigation titles for uncovered lines to use constants Signed-off-by: Giovanni Magliocchetti --------- Signed-off-by: Giovanni Magliocchetti Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../browser/codeCoverageDecorations.ts | 171 +++++++++++++++++- .../contrib/testing/common/constants.ts | 2 + 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index ba44bf738aa..422c4978530 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -42,7 +42,7 @@ import { IQuickInputButton, IQuickInputService, QuickPickInput } from '../../../ import { ActiveEditorContext } from '../../../common/contextkeys.js'; import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; import { getTestingConfiguration, TestingConfigKeys } from '../common/configuration.js'; -import { TestCommandId } from '../common/constants.js'; +import { TestCommandId, Testing } from '../common/constants.js'; import { FileCoverage } from '../common/testCoverage.js'; import { ITestCoverageService } from '../common/testCoverageService.js'; import { TestId } from '../common/testId.js'; @@ -58,8 +58,12 @@ const CLASS_MISS = 'coverage-deco-miss'; const TOGGLE_INLINE_COMMAND_TEXT = localize('testing.toggleInlineCoverage', 'Toggle Inline'); const TOGGLE_INLINE_COMMAND_ID = 'testing.toggleInlineCoverage'; const BRANCH_MISS_INDICATOR_CHARS = 4; +const GO_TO_NEXT_MISSED_LINE_TITLE = localize2('testing.goToNextMissedLine', "Go to Next Uncovered Line"); +const GO_TO_PREVIOUS_MISSED_LINE_TITLE = localize2('testing.goToPreviousMissedLine', "Go to Previous Uncovered Line"); export class CodeCoverageDecorations extends Disposable implements IEditorContribution { + public static readonly ID = Testing.CoverageDecorationsContributionId; + private loadingCancellation?: CancellationTokenSource; private readonly displayedStore = this._register(new DisposableStore()); private readonly hoveredStore = this._register(new DisposableStore()); @@ -249,6 +253,82 @@ export class CodeCoverageDecorations extends Disposable implements IEditorContri })); } + /** + * Navigate to the next missed (uncovered) line from the current cursor position. + * @returns true if navigation occurred, false if no missed line was found + */ + public goToNextMissedLine(): boolean { + return this.navigateToMissedLine(true); + } + + /** + * Navigate to the previous missed (uncovered) line from the current cursor position. + * @returns true if navigation occurred, false if no missed line was found + */ + public goToPreviousMissedLine(): boolean { + return this.navigateToMissedLine(false); + } + + private navigateToMissedLine(next: boolean): boolean { + const model = this.editor.getModel(); + const position = this.editor.getPosition(); + if (!model || !position || !this.details) { + return false; + } + + const currentLine = position.lineNumber; + let closestBefore: { lineNumber: number; range: Range } | undefined; + let closestAfter: { lineNumber: number; range: Range } | undefined; + let firstMissed: { lineNumber: number; range: Range } | undefined; + let lastMissed: { lineNumber: number; range: Range } | undefined; + + // Find the closest missed line before and after the current position + for (const [, { detail, options }] of this.decorationIds) { + // Check if this is a missed line (CLASS_MISS in lineNumberClassName) + if (options.lineNumberClassName?.includes(CLASS_MISS)) { + const range = detail.range; + if (range.isEmpty()) { + continue; + } + + const lineNumber = range.startLineNumber; + const missedLine = { lineNumber, range }; + + // Track first and last missed lines for wrap-around + if (!firstMissed || lineNumber < firstMissed.lineNumber) { + firstMissed = missedLine; + } + if (!lastMissed || lineNumber > lastMissed.lineNumber) { + lastMissed = missedLine; + } + + // Track closest before and after current line + if (lineNumber < currentLine) { + if (!closestBefore || lineNumber > closestBefore.lineNumber) { + closestBefore = missedLine; + } + } else if (lineNumber > currentLine) { + if (!closestAfter || lineNumber < closestAfter.lineNumber) { + closestAfter = missedLine; + } + } + } + } + + // Determine target line based on direction + const targetLine = next + ? (closestAfter || firstMissed) // Next: closest after, or wrap to first + : (closestBefore || lastMissed); // Previous: closest before, or wrap to last + + if (targetLine) { + this.editor.setPosition(new Position(targetLine.lineNumber, 1)); + this.editor.revealLineInCenter(targetLine.lineNumber); + return true; + } + + return false; + } + private async apply(model: ITextModel, coverage: FileCoverage, testId: TestId | undefined, showInlineByDefault: boolean) { const details = this.details = await this.loadDetails(coverage, testId, model); if (!details) { @@ -638,6 +718,23 @@ class CoverageToolbarWidget extends Disposable implements IOverlayWidget { this.actionBar.push(toggleAction); + // Navigation buttons for missed coverage lines + this.actionBar.push(new ActionWithIcon( + 'goToPreviousMissed', + GO_TO_PREVIOUS_MISSED_LINE_TITLE.value, + Codicon.arrowUp, + undefined, + () => this.commandService.executeCommand(TestCommandId.CoverageGoToPreviousMissedLine), + )); + + this.actionBar.push(new ActionWithIcon( + 'goToNextMissed', + GO_TO_NEXT_MISSED_LINE_TITLE.value, + Codicon.arrowDown, + undefined, + () => this.commandService.executeCommand(TestCommandId.CoverageGoToNextMissedLine), + )); + if (current.testId) { const testItem = current.coverage.fromResult.getTestById(current.testId.toString()); assert(!!testItem, 'got coverage for an unreported test'); @@ -904,6 +1001,78 @@ registerAction2(class ToggleCoverageInExplorer extends Action2 { } }); +registerAction2(class GoToNextMissedCoverageLine extends Action2 { + constructor() { + super({ + id: TestCommandId.CoverageGoToNextMissedLine, + title: GO_TO_NEXT_MISSED_LINE_TITLE, + metadata: { + description: localize2('testing.goToNextMissedLineDesc', 'Navigate to the next line that is not covered by tests.') + }, + category: Categories.Test, + icon: Codicon.arrowDown, + f1: true, + precondition: TestingContextKeys.hasCoverageInFile, + keybinding: { + when: ActiveEditorContext, + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.Alt | KeyCode.F9, + }, + menu: [ + { id: MenuId.CommandPalette, when: TestingContextKeys.isTestCoverageOpen }, + { id: MenuId.EditorTitle, when: TestingContextKeys.hasCoverageInFile, group: 'coverage@2' }, + ] + }); + } + + run(accessor: ServicesAccessor): void { + const codeEditorService = accessor.get(ICodeEditorService); + const activeEditor = codeEditorService.getActiveCodeEditor(); + if (!activeEditor) { + return; + } + + const contribution = activeEditor.getContribution(CodeCoverageDecorations.ID); + contribution?.goToNextMissedLine(); + } +}); + +registerAction2(class GoToPreviousMissedCoverageLine extends Action2 { + constructor() { + super({ + id: TestCommandId.CoverageGoToPreviousMissedLine, + title: GO_TO_PREVIOUS_MISSED_LINE_TITLE, + metadata: { + description: localize2('testing.goToPreviousMissedLineDesc', 'Navigate to the previous line that is not covered by tests.') + }, + category: Categories.Test, + icon: Codicon.arrowUp, + f1: true, + precondition: TestingContextKeys.hasCoverageInFile, + keybinding: { + when: ActiveEditorContext, + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F9, + }, + menu: [ + { id: MenuId.CommandPalette, when: TestingContextKeys.isTestCoverageOpen }, + { id: MenuId.EditorTitle, when: TestingContextKeys.hasCoverageInFile, group: 'coverage@3' }, + ] + }); + } + + run(accessor: ServicesAccessor): void { + const codeEditorService = accessor.get(ICodeEditorService); + const activeEditor = codeEditorService.getActiveCodeEditor(); + if (!activeEditor) { + return; + } + + const contribution = activeEditor.getContribution(CodeCoverageDecorations.ID); + contribution?.goToPreviousMissedLine(); + } +}); + class ActionWithIcon extends Action { constructor(id: string, title: string, public readonly icon: ThemeIcon, enabled: boolean | undefined, run: () => void) { super(id, title, undefined, enabled, run); diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index a059ba43be1..3918f0adc3a 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -67,6 +67,8 @@ export const enum TestCommandId { CoverageCurrentFile = 'testing.coverageCurrentFile', CoverageFilterToTest = 'testing.coverageFilterToTest', CoverageFilterToTestInEditor = 'testing.coverageFilterToTestInEditor', + CoverageGoToNextMissedLine = 'testing.coverage.goToNextMissedLine', + CoverageGoToPreviousMissedLine = 'testing.coverage.goToPreviousMissedLine', CoverageLastRun = 'testing.coverageLastRun', CoverageSelectedAction = 'testing.coverageSelected', CoverageToggleInExplorer = 'testing.toggleCoverageInExplorer', From 3cd82f950f24a942eeffbf003db43a00f985fb8e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:30:38 +0000 Subject: [PATCH 0855/4355] Add Escape key keybinding to stop terminal voice dictation (#269712) --- src/vs/workbench/contrib/terminal/common/terminal.ts | 1 + .../terminalContrib/voice/browser/terminalVoiceActions.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index a04eb03f6f3..a51e741f597 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -542,6 +542,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TerminalCommandId.Toggle, TerminalCommandId.FocusHover, AccessibilityCommandId.OpenAccessibilityHelp, + TerminalCommandId.StopVoice, 'workbench.action.tasks.rerunForActiveTerminal', 'editor.action.toggleTabFocusMode', 'notifications.hideList', diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index a8227af1871..c79577cb4a4 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -10,6 +10,8 @@ import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/cont import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; @@ -73,6 +75,10 @@ export function registerTerminalVoiceActions() { title: localize2('workbench.action.terminal.stopDictation', "Stop Dictation in Terminal"), precondition: TerminalContextKeys.terminalDictationInProgress, f1: true, + keybinding: { + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + 100 + }, run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); TerminalVoiceSession.getInstance(instantiationService).stop(true); From 246f142d1f3a75d8279ccae93e26608dbc24ffab Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 6 Oct 2025 12:45:34 -0700 Subject: [PATCH 0856/4355] PR feedback --- .../extensions/browser/fileBasedRecommendations.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index fc866de3ed8..e255c015c12 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -155,13 +155,14 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const matchedRecommendations: IStringDictionary = {}; const unmatchedRecommendations: IStringDictionary = {}; let listenOnLanguageChange = false; - let languageId = model.getLanguageId(); + const languageId = model.getLanguageId(); - // Avoid language-specific recommendations for untitled files when language is auto-detected. - if (uri.scheme === Schemas.untitled) { + // Avoid language-specific recommendations for untitled files when language is auto-detected except when the file is large. + let allowLanguageMatch = true; + if (uri.scheme === Schemas.untitled && model.getValueLength() < 1000) { const untitledModel = this.untitledTextEditorService.get(uri); if (untitledModel && !untitledModel.hasLanguageSetExplicitly) { - languageId = PLAINTEXT_LANGUAGE_ID; + allowLanguageMatch = false; } } @@ -179,7 +180,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { conditionsByPattern.push(condition); } - if (isLanguageCondition) { + if (isLanguageCondition && allowLanguageMatch) { if ((condition).languages.includes(languageId)) { languageMatched = true; } From bb5ef99f2857010964831c8ed5ec1cc03cf6dbfb Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 6 Oct 2025 12:58:25 -0700 Subject: [PATCH 0857/4355] testing: lm failure messages not included for certain exts (#270091) Fixes #269958 --- .../contrib/testing/common/testingChatAgentTool.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts index 3fbd70929e9..b44c8358d0a 100644 --- a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts +++ b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts @@ -178,7 +178,7 @@ class RunTestTool implements IToolImpl { private async _buildSummary(result: LiveTestResult, mode: Mode, coverageFiles: string[] | undefined): Promise { const failures = result.counts[TestResultState.Errored] + result.counts[TestResultState.Failed]; - let str = ``; + let str = `\n`; if (failures !== 0) { str += await this._getFailureDetails(result); } @@ -245,6 +245,7 @@ class RunTestTool implements IToolImpl { private async _getFailureDetails(result: LiveTestResult): Promise { let str = ''; + let hadMessages = false; for (const failure of result.tests) { if (!isFailedState(failure.ownComputedState)) { continue; @@ -256,6 +257,8 @@ class RunTestTool implements IToolImpl { // Extract detailed failure information from error messages for (const task of failure.tasks) { for (const message of task.messages.filter(m => m.type === TestMessageType.Error)) { + hadMessages = true; + // Add expected/actual outputs if available if (message.expected !== undefined && message.actual !== undefined) { str += `\n${message.expected}\n\n`; @@ -288,6 +291,14 @@ class RunTestTool implements IToolImpl { str += `\n`; } + + if (!hadMessages) { // some adapters don't have any per-test messages and just output + const output = result.tasks.map(t => t.output.getRange(0, t.output.length).toString().trim()).join('\n'); + if (output) { + str += `\n${output}\n\n`; + } + } + return str; } From be88ab8f42c4b4447604920f71b5502db07b8761 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 6 Oct 2025 13:12:11 -0700 Subject: [PATCH 0858/4355] testing: fix running test message tense (#270092) Fixes #269149 --- src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts index b44c8358d0a..e38dbc31cb0 100644 --- a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts +++ b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts @@ -172,7 +172,7 @@ class RunTestTool implements IToolImpl { return { content: content as Mutable, - toolResultMessage: getTestProgressText(collectTestStateCounts(true, [result])), + toolResultMessage: getTestProgressText(collectTestStateCounts(false, [result])), }; } From eb8570fdfde5b61f7c1023a5e1882949d3d7c338 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Oct 2025 16:30:19 -0400 Subject: [PATCH 0859/4355] Revert "Put terminal voice actions in separate group (#269596)" (#270085) This reverts commit 77a9703074275daa8d82531d9a47922ae6f315a8. --- .../contrib/terminal/browser/terminalMenus.ts | 26 +++++++++++++++++++ .../voice/browser/terminalVoiceActions.ts | 21 ++------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index fa028e082ac..27e74b241a7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -521,6 +521,32 @@ export function setupTerminalMenus(): void { isHiddenByDefault: true }, }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StartVoice, + title: localize('workbench.action.terminal.startVoice', "Start Dictation"), + }, + group: 'navigation', + order: 9, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress.toNegated()), + isHiddenByDefault: true + }, + }, + { + id: MenuId.ViewTitle, + item: { + command: { + id: TerminalCommandId.StopVoice, + title: localize('workbench.action.terminal.stopVoice', "Stop Dictation"), + }, + group: 'navigation', + order: 9, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress), + isHiddenByDefault: true + }, + }, ] ); diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts index c79577cb4a4..3e185e3336c 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoiceActions.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { localize, localize2 } from '../../../../../nls.js'; -import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; @@ -15,7 +14,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js'; import { HasSpeechProvider, SpeechToTextInProgress } from '../../../speech/common/speechService.js'; import { registerActiveInstanceAction, sharedWhenClause } from '../../../terminal/browser/terminalActions.js'; -import { TERMINAL_VIEW_ID, TerminalCommandId } from '../../../terminal/common/terminal.js'; +import { TerminalCommandId } from '../../../terminal/common/terminal.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalVoiceSession } from './terminalVoice.js'; @@ -60,14 +59,6 @@ export function registerTerminalVoiceActions() { await run(); } }, - menu: [ - { - id: MenuId.ViewTitle, - group: 'voice', - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress.toNegated()), - isHiddenByDefault: true - }, - ] }); registerActiveInstanceAction({ @@ -82,14 +73,6 @@ export function registerTerminalVoiceActions() { run: (activeInstance, c, accessor) => { const instantiationService = accessor.get(IInstantiationService); TerminalVoiceSession.getInstance(instantiationService).stop(true); - }, - menu: [ - { - id: MenuId.ViewTitle, - group: 'voice', - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), TerminalContextKeys.terminalDictationInProgress), - isHiddenByDefault: true - }, - ] + } }); } From 75ff485ed963e2edd6c007f4686fe8cd7a716db7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 6 Oct 2025 16:44:25 -0400 Subject: [PATCH 0860/4355] rm ID from terminal completion provider (#270094) --- .../terminal-suggest/src/terminalSuggestMain.ts | 2 +- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- .../api/common/extHostTerminalService.ts | 16 ++++++++-------- ...code.proposed.terminalCompletionProvider.d.ts | 3 +-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 4f12b258652..9db6122f729 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -250,7 +250,7 @@ export async function activate(context: vscode.ExtensionContext) { const machineId = await vscode.env.machineId; const remoteAuthority = vscode.env.remoteName; - context.subscriptions.push(vscode.window.registerTerminalCompletionProvider('terminal-suggest', { + context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({ async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: vscode.TerminalCompletionContext, token: vscode.CancellationToken): Promise { currentTerminalEnv = terminal.shellIntegration?.env?.value ?? process.env; if (token.isCancellationRequested) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index cac3d4cc155..cfe5c39b099 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -877,9 +877,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerTerminalProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable { return extHostTerminalService.registerProfileProvider(extension, id, provider); }, - registerTerminalCompletionProvider(id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { + registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { checkProposedApiEnabled(extension, 'terminalCompletionProvider'); - return extHostTerminalService.registerTerminalCompletionProvider(extension, id, provider, ...triggerCharacters); + return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters); }, registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable { checkProposedApiEnabled(extension, 'terminalQuickFixProvider'); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 9730650d1fa..270e3633e85 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -57,7 +57,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection; getTerminalById(id: number): ExtHostTerminal | null; getTerminalIdByApiObject(apiTerminal: vscode.Terminal): number | null; - registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable; + registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable; } interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableCollection { @@ -757,15 +757,15 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } - public registerTerminalCompletionProvider(extension: IExtensionDescription, id: string, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { - if (this._completionProviders.has(id)) { - throw new Error(`Terminal completion provider "${id}" already registered`); + public registerTerminalCompletionProvider(extension: IExtensionDescription, provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { + if (this._completionProviders.has(extension.identifier.value)) { + throw new Error(`Terminal completion provider "${extension.identifier.value}" already registered`); } - this._completionProviders.set(id, provider); - this._proxy.$registerCompletionProvider(id, extension.identifier.value, ...triggerCharacters); + this._completionProviders.set(extension.identifier.value, provider); + this._proxy.$registerCompletionProvider(extension.identifier.value, extension.identifier.value, ...triggerCharacters); return new VSCodeDisposable(() => { - this._completionProviders.delete(id); - this._proxy.$unregisterCompletionProvider(id); + this._completionProviders.delete(extension.identifier.value); + this._proxy.$unregisterCompletionProvider(extension.identifier.value); }); } diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 1a0196f683c..37152b7145f 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -133,7 +133,6 @@ declare module 'vscode' { export namespace window { /** * Register a completion provider for terminals. - * @param id The unique identifier of the terminal provider, used as a settings key and shown in the information hover of the suggest widget. * @param provider The completion provider. * @returns A {@link Disposable} that unregisters this provider when being disposed. * @@ -146,7 +145,7 @@ declare module 'vscode' { * } * }); */ - export function registerTerminalCompletionProvider(id: string, provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; + export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; } /** From ff4c31cbeceaa5e5721742088845615fb8c5c11c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:59:38 -0700 Subject: [PATCH 0861/4355] Reduce `any` usage in ts extension Follow up on #269213 --- .../src/commands/commandManager.ts | 2 +- .../src/commands/configurePlugin.ts | 2 +- .../src/commands/tsserverRequests.ts | 6 +++--- .../src/languageFeatures/completions.ts | 2 +- .../src/languageFeatures/diagnostics.ts | 2 +- .../src/languageFeatures/organizeImports.ts | 2 +- .../src/logging/logger.ts | 8 ++++---- .../src/logging/tracer.ts | 2 +- .../src/tsServer/bufferSyncSupport.ts | 6 +++--- .../src/tsServer/plugins.ts | 8 ++++---- .../src/tsServer/server.ts | 10 +++++----- .../src/tsServer/versionProvider.electron.ts | 2 +- .../src/typescriptServiceClient.ts | 15 ++++++++++----- .../src/utils/async.ts | 2 +- .../web/src/serverHost.ts | 6 +++--- 15 files changed, 40 insertions(+), 35 deletions(-) diff --git a/extensions/typescript-language-features/src/commands/commandManager.ts b/extensions/typescript-language-features/src/commands/commandManager.ts index 92b5158560c..1fdec800012 100644 --- a/extensions/typescript-language-features/src/commands/commandManager.ts +++ b/extensions/typescript-language-features/src/commands/commandManager.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; export interface Command { readonly id: string; - execute(...args: any[]): void | any; + execute(...args: unknown[]): void | unknown; } export class CommandManager { diff --git a/extensions/typescript-language-features/src/commands/configurePlugin.ts b/extensions/typescript-language-features/src/commands/configurePlugin.ts index 356738294ad..5a6a4478506 100644 --- a/extensions/typescript-language-features/src/commands/configurePlugin.ts +++ b/extensions/typescript-language-features/src/commands/configurePlugin.ts @@ -13,7 +13,7 @@ export class ConfigurePluginCommand implements Command { private readonly pluginManager: PluginManager, ) { } - public execute(pluginId: string, configuration: any) { + public execute(pluginId: string, configuration: unknown) { this.pluginManager.setConfiguration(pluginId, configuration); } } diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts index 556e8582300..77c07d658af 100644 --- a/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { TypeScriptRequests } from '../typescriptService'; +import { ExecConfig, TypeScriptRequests } from '../typescriptService'; import TypeScriptServiceClientHost from '../typeScriptServiceClientHost'; import { nulToken } from '../utils/cancellation'; import { Lazy } from '../utils/lazy'; @@ -26,7 +26,7 @@ export class TSServerRequestCommand implements Command { private readonly lazyClientHost: Lazy ) { } - public async execute(command: keyof TypeScriptRequests, args?: any, config?: any, token?: vscode.CancellationToken): Promise { + public async execute(command: keyof TypeScriptRequests, args?: unknown, config?: ExecConfig, token?: vscode.CancellationToken): Promise { if (!isCancellationToken(token)) { token = nulToken; } @@ -35,7 +35,7 @@ export class TSServerRequestCommand implements Command { const hasFile = requestArgs.file instanceof vscode.Uri; const hasTraceId = typeof requestArgs.$traceId === 'string'; if (hasFile || hasTraceId) { - const newArgs = { ...args }; + const newArgs = { file: undefined as string | undefined, ...args }; if (hasFile) { const client = this.lazyClientHost.value.serviceClient; newArgs.file = client.toOpenTsFilePath(requestArgs.file); diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index a06d5c171bc..7ac732d3f86 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -56,7 +56,7 @@ class MyCompletionItem extends vscode.CompletionItem { public readonly document: vscode.TextDocument, public readonly tsEntry: Proto.CompletionEntry, private readonly completionContext: CompletionContext, - public readonly metadata: any | undefined, + public readonly metadata: unknown | undefined, client: ITypeScriptServiceClient, defaultCommitCharacters: readonly string[] | undefined, ) { diff --git a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index d86f64637c6..632baf01ff9 100644 --- a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -285,7 +285,7 @@ export class DiagnosticsManager extends Disposable { private readonly _diagnostics: ResourceMap; private readonly _settings = new DiagnosticSettings(); private readonly _currentDiagnostics: vscode.DiagnosticCollection; - private readonly _pendingUpdates: ResourceMap; + private readonly _pendingUpdates: ResourceMap; private readonly _updateDelay = 50; diff --git a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts index 82199438563..25c3a92c2c2 100644 --- a/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts +++ b/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts @@ -57,7 +57,7 @@ class DidOrganizeImportsCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute(): Promise { + public async execute(): Promise { /* __GDPR__ "organizeImports.execute" : { "owner": "mjbvz", diff --git a/extensions/typescript-language-features/src/logging/logger.ts b/extensions/typescript-language-features/src/logging/logger.ts index 1d33a75a30b..98b4e6fb813 100644 --- a/extensions/typescript-language-features/src/logging/logger.ts +++ b/extensions/typescript-language-features/src/logging/logger.ts @@ -16,17 +16,17 @@ export class Logger { return this.output.value.logLevel; } - public info(message: string, ...args: any[]): void { + public info(message: string, ...args: unknown[]): void { this.output.value.info(message, ...args); } - public trace(message: string, ...args: any[]): void { + public trace(message: string, ...args: unknown[]): void { this.output.value.trace(message, ...args); } - public error(message: string, data?: any): void { + public error(message: string, data?: unknown): void { // See https://github.com/microsoft/TypeScript/issues/10496 - if (data && data.message === 'No content available.') { + if (data && (data as { message?: string }).message === 'No content available.') { return; } this.output.value.error(message, ...(data ? [data] : [])); diff --git a/extensions/typescript-language-features/src/logging/tracer.ts b/extensions/typescript-language-features/src/logging/tracer.ts index e273181075d..0aa9a937cd1 100644 --- a/extensions/typescript-language-features/src/logging/tracer.ts +++ b/extensions/typescript-language-features/src/logging/tracer.ts @@ -32,7 +32,7 @@ export default class Tracer extends Disposable { } } - public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any { + public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): void { if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`); } diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 670412b3cb1..04e068916ed 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -349,15 +349,15 @@ class GetErrRequest { } } - private areProjectDiagnosticsEnabled() { + private areProjectDiagnosticsEnabled(): boolean { return this.client.configuration.enableProjectDiagnostics && this.client.capabilities.has(ClientCapability.Semantic); } - private areRegionDiagnosticsEnabled() { + private areRegionDiagnosticsEnabled(): boolean { return this.client.configuration.enableRegionDiagnostics && this.client.apiVersion.gte(API.v560); } - public cancel(): any { + public cancel(): void { if (!this._done) { this._token.cancel(); } diff --git a/extensions/typescript-language-features/src/tsServer/plugins.ts b/extensions/typescript-language-features/src/tsServer/plugins.ts index 6036e4c968b..a4f56f03e4e 100644 --- a/extensions/typescript-language-features/src/tsServer/plugins.ts +++ b/extensions/typescript-language-features/src/tsServer/plugins.ts @@ -26,7 +26,7 @@ namespace TypeScriptServerPlugin { } export class PluginManager extends Disposable { - private readonly _pluginConfigurations = new Map(); + private readonly _pluginConfigurations = new Map(); private _plugins?: Map>; @@ -54,15 +54,15 @@ export class PluginManager extends Disposable { private readonly _onDidUpdatePlugins = this._register(new vscode.EventEmitter()); public readonly onDidChangePlugins = this._onDidUpdatePlugins.event; - private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string; config: {} }>()); + private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string; config: unknown }>()); public readonly onDidUpdateConfig = this._onDidUpdateConfig.event; - public setConfiguration(pluginId: string, config: {}) { + public setConfiguration(pluginId: string, config: unknown) { this._pluginConfigurations.set(pluginId, config); this._onDidUpdateConfig.fire({ pluginId, config }); } - public configurations(): IterableIterator<[string, {}]> { + public configurations(): IterableIterator<[string, unknown]> { return this._pluginConfigurations.entries(); } diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 38c62493377..58b94ff46bf 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -39,7 +39,7 @@ export type TsServerLog = export interface ITypeScriptServer { readonly onEvent: vscode.Event; readonly onExit: vscode.Event; - readonly onError: vscode.Event; + readonly onError: vscode.Event; readonly tsServerLog: TsServerLog | undefined; @@ -125,7 +125,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this._tsServerLog; } @@ -528,7 +528,7 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this.mainServer.tsServerLog; } @@ -666,10 +666,10 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ private readonly _onEvent = this._register(new vscode.EventEmitter()); public readonly onEvent = this._onEvent.event; - private readonly _onExit = this._register(new vscode.EventEmitter()); + private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this.semanticServer.tsServerLog; } diff --git a/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts b/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts index 239519e6f6a..12cb1ccadb4 100644 --- a/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts @@ -183,7 +183,7 @@ export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider } const contents = fs.readFileSync(fileName).toString(); - let desc: any = null; + let desc: any; try { desc = JSON.parse(contents); } catch (err) { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 57395ce3999..dc66ab49fed 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -444,12 +444,17 @@ export default class TypeScriptServiceClient extends Disposable implements IType typeScriptVersionSource: version.source, }); - handle.onError((err: Error) => { + handle.onError((err: unknown) => { if (this.token !== mytoken) { // this is coming from an old process return; } + if (!(err instanceof Error)) { + this.logger.error('TSServer got unknown error type:', err); + return; + } + if (err) { vscode.window.showErrorMessage(vscode.l10n.t("TypeScript language server exited with error. Error message is: {0}", err.message || err.name)); } @@ -851,7 +856,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return vscode.workspace.getWorkspaceFolder(resource)?.uri; } - public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { + public execute(command: keyof TypeScriptRequests, args: unknown, token: vscode.CancellationToken, config?: ExecConfig): Promise> { let executions: Array> | undefined> | undefined; if (config?.cancelOnResourceChange) { @@ -907,7 +912,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return executions[0]!; } - public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void { + public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: unknown): void { this.executeImpl(command, args, { isAsync: false, token: undefined, @@ -923,7 +928,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType })[0]!; } - private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; requireSemantic?: boolean }): Array> | undefined> { + private executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; requireSemantic?: boolean }): Array> | undefined> { const serverState = this.serverState; if (serverState.type === ServerState.Type.Running) { this.bufferSyncSupport.beforeCommand(command); @@ -1230,7 +1235,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.telemetryReporter.logTelemetry(telemetryData.telemetryEventName, properties); } - private configurePlugin(pluginName: string, configuration: {}): any { + private configurePlugin(pluginName: string, configuration: unknown): void { this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }); } } diff --git a/extensions/typescript-language-features/src/utils/async.ts b/extensions/typescript-language-features/src/utils/async.ts index 0d3b8a74f2c..fa5a04e3aa1 100644 --- a/extensions/typescript-language-features/src/utils/async.ts +++ b/extensions/typescript-language-features/src/utils/async.ts @@ -61,7 +61,7 @@ export class Delayer { } } -export function setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { +export function setImmediate(callback: (...args: unknown[]) => void, ...args: unknown[]): Disposable { if (global.setImmediate) { const handle = global.setImmediate(callback, ...args); return { dispose: () => global.clearImmediate(handle) }; diff --git a/extensions/typescript-language-features/web/src/serverHost.ts b/extensions/typescript-language-features/web/src/serverHost.ts index f8a405837b9..fdc617868b5 100644 --- a/extensions/typescript-language-features/web/src/serverHost.ts +++ b/extensions/typescript-language-features/web/src/serverHost.ts @@ -74,16 +74,16 @@ function createServerHost( return { watchFile: watchManager.watchFile.bind(watchManager), watchDirectory: watchManager.watchDirectory.bind(watchManager), - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { + setTimeout(callback: (...args: unknown[]) => void, ms: number, ...args: unknown[]): unknown { return setTimeout(callback, ms, ...args); }, clearTimeout(timeoutId: any): void { clearTimeout(timeoutId); }, - setImmediate(callback: (...args: any[]) => void, ...args: any[]): any { + setImmediate(callback: (...args: unknown[]) => void, ...args: unknown[]): unknown { return this.setTimeout(callback, 0, ...args); }, - clearImmediate(timeoutId: any): void { + clearImmediate(timeoutId: unknown): void { this.clearTimeout(timeoutId); }, importPlugin: async (root, moduleName) => { From 2b636917cea6bf222ba780cd18989c12c13fd5b8 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:12:10 -0700 Subject: [PATCH 0862/4355] Replace more `...args: any[]` with unknown Follow up on #269213 Typing only change to make `args` type safer --- src/vs/base/common/lifecycle.ts | 4 +- .../contrib/rename/browser/renameWidget.ts | 2 +- .../browser/standaloneCodeEditor.ts | 2 +- .../standalone/browser/standaloneEditor.ts | 2 +- .../standalone/browser/standaloneWebWorker.ts | 2 +- src/vs/platform/log/common/log.ts | 82 +++++++++---------- src/vs/platform/log/common/logService.ts | 10 +-- .../test/common/telemetryLogAppender.test.ts | 10 +-- .../terminal/common/terminalLogService.ts | 10 +-- .../userDataSync/common/userDataSyncLog.ts | 10 +-- src/vs/server/node/serverServices.ts | 10 +-- src/vs/workbench/api/common/extHostOutput.ts | 10 +-- .../browser/extHostMessagerService.test.ts | 10 +-- .../chatEditing/chatEditingEditorActions.ts | 2 +- .../common/editSessionsLogService.ts | 10 +-- .../inlineChat/browser/inlineChatActions.ts | 24 +++--- .../browser/inlineChatController.ts | 4 +- .../browser/inlineChatCurrentLine.ts | 2 +- .../electron-browser/inlineChatActions.ts | 2 +- .../contrib/cellCommands/cellCommands.ts | 6 +- .../browser/controller/coreActions.ts | 2 +- .../browser/controller/executeActions.ts | 10 +-- .../browser/preferences.contribution.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 2 +- 24 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index c02a023cb00..ff7684f382b 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -685,7 +685,7 @@ export abstract class ReferenceCollection { private readonly references: Map = new Map(); - acquire(key: string, ...args: any[]): IReference { + acquire(key: string, ...args: unknown[]): IReference { let reference = this.references.get(key); if (!reference) { @@ -706,7 +706,7 @@ export abstract class ReferenceCollection { return { object, dispose }; } - protected abstract createReferencedObject(key: string, ...args: any[]): T; + protected abstract createReferencedObject(key: string, ...args: unknown[]): T; protected abstract destroyReferencedObject(key: string, object: T): void; } diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index c59f382e763..340d9dc2e83 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -595,7 +595,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } private async _updateRenameCandidates(candidates: ProviderResult[], currentName: string, token: CancellationToken) { - const trace = (...args: any[]) => this._trace('_updateRenameCandidates', ...args); + const trace = (...args: unknown[]) => this._trace('_updateRenameCandidates', ...args); trace('start'); const namesListResults = await raceCancellation(Promise.allSettled(candidates), token); diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index dffab646d2b..32dbaa2265c 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -335,7 +335,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ); const contextMenuGroupId = _descriptor.contextMenuGroupId || null; const contextMenuOrder = _descriptor.contextMenuOrder || 0; - const run = (_accessor?: ServicesAccessor, ...args: any[]): Promise => { + const run = (_accessor?: ServicesAccessor, ...args: unknown[]): Promise => { return Promise.resolve(_descriptor.run(this, ...args)); }; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 6a9604e9038..6d5cbfebc1f 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -137,7 +137,7 @@ export function addEditorAction(descriptor: IActionDescriptor): IDisposable { } const precondition = ContextKeyExpr.deserialize(descriptor.precondition); - const run = (accessor: ServicesAccessor, ...args: any[]): void | Promise => { + const run = (accessor: ServicesAccessor, ...args: unknown[]): void | Promise => { return EditorCommand.runEditorCommand(accessor, args, precondition, (accessor, editor, args) => Promise.resolve(descriptor.run(editor, ...args))); }; diff --git a/src/vs/editor/standalone/browser/standaloneWebWorker.ts b/src/vs/editor/standalone/browser/standaloneWebWorker.ts index 74f1a2770b9..43f1cc93608 100644 --- a/src/vs/editor/standalone/browser/standaloneWebWorker.ts +++ b/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -68,7 +68,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement if (typeof prop !== 'string') { throw new Error(`Not supported`); } - return (...args: any[]) => { + return (...args: unknown[]) => { return proxy.$fmr(prop, args); }; } diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 054ebeb45b6..1124bc8f828 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -45,11 +45,11 @@ export interface ILogger extends IDisposable { getLevel(): LogLevel; setLevel(level: LogLevel): void; - trace(message: string, ...args: any[]): void; - debug(message: string, ...args: any[]): void; - info(message: string, ...args: any[]): void; - warn(message: string, ...args: any[]): void; - error(message: string | Error, ...args: any[]): void; + trace(message: string, ...args: unknown[]): void; + debug(message: string, ...args: unknown[]): void; + info(message: string, ...args: unknown[]): void; + warn(message: string, ...args: unknown[]): void; + error(message: string | Error, ...args: unknown[]): void; /** * An operation to flush the contents. Can be synchronous. @@ -281,11 +281,11 @@ export abstract class AbstractLogger extends Disposable implements ILogger { return this.checkLogLevel(level); } - abstract trace(message: string, ...args: any[]): void; - abstract debug(message: string, ...args: any[]): void; - abstract info(message: string, ...args: any[]): void; - abstract warn(message: string, ...args: any[]): void; - abstract error(message: string | Error, ...args: any[]): void; + abstract trace(message: string, ...args: unknown[]): void; + abstract debug(message: string, ...args: unknown[]): void; + abstract info(message: string, ...args: unknown[]): void; + abstract warn(message: string, ...args: unknown[]): void; + abstract error(message: string | Error, ...args: unknown[]): void; abstract flush(): void; } @@ -299,31 +299,31 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL return this.logAlways || super.checkLogLevel(level); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { this.log(LogLevel.Trace, format([message, ...args], true)); } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { this.log(LogLevel.Debug, format([message, ...args])); } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { this.log(LogLevel.Info, format([message, ...args])); } } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { this.log(LogLevel.Warning, format([message, ...args])); } } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { if (message instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; @@ -351,7 +351,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { this.useColors = !isWindows; } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); @@ -361,7 +361,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); @@ -371,7 +371,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); @@ -381,7 +381,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } } - warn(message: string | Error, ...args: any[]): void { + warn(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn(`\x1b[93m[main ${now()}]\x1b[0m`, message, ...args); @@ -391,7 +391,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } } - error(message: string, ...args: any[]): void { + error(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error(`\x1b[91m[main ${now()}]\x1b[0m`, message, ...args); @@ -414,7 +414,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { this.setLevel(logLevel); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log('%cTRACE', 'color: #888', message, ...args); @@ -424,7 +424,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args); @@ -434,7 +434,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log('%c INFO', 'color: #33f', message, ...args); @@ -444,7 +444,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } } - warn(message: string | Error, ...args: any[]): void { + warn(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn('%c WARN', 'color: #993', message, ...args); @@ -454,7 +454,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } } - error(message: string, ...args: any[]): void { + error(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error('%c ERR', 'color: #f33', message, ...args); @@ -477,31 +477,31 @@ export class AdapterLogger extends AbstractLogger implements ILogger { this.setLevel(logLevel); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]); } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]); } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]); } } - warn(message: string | Error, ...args: any[]): void { + warn(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]); } } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]); } @@ -536,31 +536,31 @@ export class MultiplexLogger extends AbstractLogger implements ILogger { super.setLevel(level); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { for (const logger of this.loggers) { logger.trace(message, ...args); } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { for (const logger of this.loggers) { logger.debug(message, ...args); } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { for (const logger of this.loggers) { logger.info(message, ...args); } } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { for (const logger of this.loggers) { logger.warn(message, ...args); } } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { for (const logger of this.loggers) { logger.error(message, ...args); } @@ -740,12 +740,12 @@ export class NullLogger implements ILogger { readonly onDidChangeLogLevel: Event = new Emitter().event; setLevel(level: LogLevel): void { } getLevel(): LogLevel { return LogLevel.Info; } - trace(message: string, ...args: any[]): void { } - debug(message: string, ...args: any[]): void { } - info(message: string, ...args: any[]): void { } - warn(message: string, ...args: any[]): void { } - error(message: string | Error, ...args: any[]): void { } - critical(message: string | Error, ...args: any[]): void { } + trace(message: string, ...args: unknown[]): void { } + debug(message: string, ...args: unknown[]): void { } + info(message: string, ...args: unknown[]): void { } + warn(message: string, ...args: unknown[]): void { } + error(message: string | Error, ...args: unknown[]): void { } + critical(message: string | Error, ...args: unknown[]): void { } dispose(): void { } flush(): void { } } diff --git a/src/vs/platform/log/common/logService.ts b/src/vs/platform/log/common/logService.ts index e76c4c30d7a..ee201810ae1 100644 --- a/src/vs/platform/log/common/logService.ts +++ b/src/vs/platform/log/common/logService.ts @@ -31,23 +31,23 @@ export class LogService extends Disposable implements ILogService { return this.logger.getLevel(); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { this.logger.trace(message, ...args); } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { this.logger.debug(message, ...args); } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { this.logger.info(message, ...args); } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { this.logger.warn(message, ...args); } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { this.logger.error(message, ...args); } diff --git a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index 2a41688bc8d..1b4f4a1d274 100644 --- a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts @@ -20,31 +20,31 @@ class TestTelemetryLogger extends AbstractLogger implements ILogger { this.setLevel(logLevel); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { this.logs.push(message + JSON.stringify(args)); } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { this.logs.push(message); } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { this.logs.push(message); } } - warn(message: string | Error, ...args: any[]): void { + warn(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { this.logs.push(message.toString()); } } - error(message: string, ...args: any[]): void { + error(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { this.logs.push(message); } diff --git a/src/vs/platform/terminal/common/terminalLogService.ts b/src/vs/platform/terminal/common/terminalLogService.ts index c536b084517..4588544e17d 100644 --- a/src/vs/platform/terminal/common/terminalLogService.ts +++ b/src/vs/platform/terminal/common/terminalLogService.ts @@ -38,11 +38,11 @@ export class TerminalLogService extends Disposable implements ITerminalLogServic setLevel(level: LogLevel): void { this._logger.setLevel(level); } flush(): void { this._logger.flush(); } - trace(message: string, ...args: any[]): void { this._logger.trace(this._formatMessage(message), args); } - debug(message: string, ...args: any[]): void { this._logger.debug(this._formatMessage(message), args); } - info(message: string, ...args: any[]): void { this._logger.info(this._formatMessage(message), args); } - warn(message: string, ...args: any[]): void { this._logger.warn(this._formatMessage(message), args); } - error(message: string | Error, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { this._logger.trace(this._formatMessage(message), args); } + debug(message: string, ...args: unknown[]): void { this._logger.debug(this._formatMessage(message), args); } + info(message: string, ...args: unknown[]): void { this._logger.info(this._formatMessage(message), args); } + warn(message: string, ...args: unknown[]): void { this._logger.warn(this._formatMessage(message), args); } + error(message: string | Error, ...args: unknown[]): void { if (message instanceof Error) { this._logger.error(this._formatMessage(''), message, args); return; diff --git a/src/vs/platform/userDataSync/common/userDataSyncLog.ts b/src/vs/platform/userDataSync/common/userDataSyncLog.ts index 464be1fa944..ff00d8624bf 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncLog.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncLog.ts @@ -22,23 +22,23 @@ export class UserDataSyncLogService extends AbstractLogger implements IUserDataS this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${USER_DATA_SYNC_LOG_ID}.log`), { id: USER_DATA_SYNC_LOG_ID, name: localize('userDataSyncLog', "Settings Sync") })); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { this.logger.trace(message, ...args); } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { this.logger.debug(message, ...args); } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { this.logger.info(message, ...args); } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { this.logger.warn(message, ...args); } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { this.logger.error(message, ...args); } diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 1e6758f1c88..48aed965931 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -304,7 +304,7 @@ class ServerLogger extends AbstractLogger { this.useColors = Boolean(process.stdout.isTTY); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); @@ -314,7 +314,7 @@ class ServerLogger extends AbstractLogger { } } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); @@ -324,7 +324,7 @@ class ServerLogger extends AbstractLogger { } } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); @@ -334,7 +334,7 @@ class ServerLogger extends AbstractLogger { } } - warn(message: string | Error, ...args: any[]): void { + warn(message: string | Error, ...args: unknown[]): void { if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn(`\x1b[93m[${now()}]\x1b[0m`, message, ...args); @@ -344,7 +344,7 @@ class ServerLogger extends AbstractLogger { } } - error(message: string, ...args: any[]): void { + error(message: string, ...args: unknown[]): void { if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error(`\x1b[91m[${now()}]\x1b[0m`, message, ...args); diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index 6b8ba4c1595..1d37a34b7ba 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -257,23 +257,23 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { ...this.createExtHostOutputChannel(name, channelPromise, channelDisposables), get logLevel() { return logLevel; }, onDidChangeLogLevel: onDidChangeLogLevel.event, - trace(value: string, ...args: any[]): void { + trace(value: string, ...args: unknown[]): void { validate(); channelPromise.then(channel => channel.trace(value, ...args)); }, - debug(value: string, ...args: any[]): void { + debug(value: string, ...args: unknown[]): void { validate(); channelPromise.then(channel => channel.debug(value, ...args)); }, - info(value: string, ...args: any[]): void { + info(value: string, ...args: unknown[]): void { validate(); channelPromise.then(channel => channel.info(value, ...args)); }, - warn(value: string, ...args: any[]): void { + warn(value: string, ...args: unknown[]): void { validate(); channelPromise.then(channel => channel.warn(value, ...args)); }, - error(value: Error | string, ...args: any[]): void { + error(value: Error | string, ...args: unknown[]): void { validate(); channelPromise.then(channel => channel.error(value, ...args)); } diff --git a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index bedd41eb4db..23f3ef10a48 100644 --- a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -19,7 +19,7 @@ const emptyCommandService: ICommandService = { _serviceBrand: undefined, onWillExecuteCommand: () => Disposable.None, onDidExecuteCommand: () => Disposable.None, - executeCommand: (commandId: string, ...args: any[]): Promise => { + executeCommand: (commandId: string, ...args: unknown[]): Promise => { return Promise.resolve(undefined); } }; @@ -27,16 +27,16 @@ const emptyCommandService: ICommandService = { const emptyNotificationService = new class implements INotificationService { declare readonly _serviceBrand: undefined; onDidChangeFilter: Event = Event.None; - notify(...args: any[]): never { + notify(...args: unknown[]): never { throw new Error('not implemented'); } - info(...args: any[]): never { + info(...args: unknown[]): never { throw new Error('not implemented'); } - warn(...args: any[]): never { + warn(...args: unknown[]): never { throw new Error('not implemented'); } - error(...args: any[]): never { + error(...args: unknown[]): never { throw new Error('not implemented'); } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 7ad127ad5ea..32f636aebf8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -338,7 +338,7 @@ export class ReviewChangesAction extends ChatEditingEditorAction { }); } - override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, entry: IModifiedFileEntry, _integration: IModifiedFileEntryEditorIntegration, ..._args: any[]): void { + override runChatEditingCommand(_accessor: ServicesAccessor, _session: IChatEditingSession, entry: IModifiedFileEntry, _integration: IModifiedFileEntryEditorIntegration, ..._args: unknown[]): void { entry.enableReviewModeUntilSettled(); } } diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts index 08ff7d57671..4be8234c928 100644 --- a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts @@ -23,23 +23,23 @@ export class EditSessionsLogService extends AbstractLogger implements IEditSessi this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${editSessionsLogId}.log`), { id: editSessionsLogId, name: localize('cloudChangesLog', "Cloud Changes"), group: windowLogGroup })); } - trace(message: string, ...args: any[]): void { + trace(message: string, ...args: unknown[]): void { this.logger.trace(message, ...args); } - debug(message: string, ...args: any[]): void { + debug(message: string, ...args: unknown[]): void { this.logger.debug(message, ...args); } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { this.logger.info(message, ...args); } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { this.logger.warn(message, ...args); } - error(message: string | Error, ...args: any[]): void { + error(message: string | Error, ...args: unknown[]): void { this.logger.error(message, ...args); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 1fb92a65714..4fd0a8b1a74 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -105,7 +105,7 @@ export class StartSessionAction extends Action2 { }); } - private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { const ctrl = InlineChatController.get(editor); if (!ctrl) { @@ -146,7 +146,7 @@ export class FocusInlineChat extends EditorAction2 { }); } - override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { InlineChatController.get(editor)?.focus(); } } @@ -167,7 +167,7 @@ export class UnstashSessionAction extends EditorAction2 { }); } - override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { const ctrl = InlineChatController1.get(editor); if (ctrl) { const session = ctrl.unstashLastSession(); @@ -208,7 +208,7 @@ export abstract class AbstractInline1ChatAction extends EditorAction2 { }); } - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { const editorService = accessor.get(IEditorService); const logService = accessor.get(ILogService); @@ -260,7 +260,7 @@ export class ArrowOutUpAction extends AbstractInline1ChatAction { }); } - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): void { ctrl.arrowOut(true); } } @@ -278,7 +278,7 @@ export class ArrowOutDownAction extends AbstractInline1ChatAction { }); } - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): void { ctrl.arrowOut(false); } } @@ -371,7 +371,7 @@ export class RerunAction extends AbstractInline1ChatAction { }); } - override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { + override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise { const chatService = accessor.get(IChatService); const chatWidgetService = accessor.get(IChatWidgetService); const model = ctrl.chatWidget.viewModel?.model; @@ -417,7 +417,7 @@ export class CloseAction extends AbstractInline1ChatAction { }); } - async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise { ctrl.cancelSession(); } } @@ -438,7 +438,7 @@ export class ConfigureInlineChatAction extends AbstractInline1ChatAction { }); } - async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { + async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]): Promise { accessor.get(IPreferencesService).openSettings({ query: 'inlineChat' }); } } @@ -512,7 +512,7 @@ export class ViewInChatAction extends AbstractInline1ChatAction { } }); } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]) { + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: unknown[]) { return ctrl.viewInChat(); } } @@ -577,7 +577,7 @@ abstract class AbstractInline2ChatAction extends EditorAction2 { }); } - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { const editorService = accessor.get(IEditorService); const logService = accessor.get(ILogService); @@ -642,7 +642,7 @@ class KeepOrUndoSessionAction extends AbstractInline2ChatAction { }); } - override async runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ..._args: any[]): Promise { + override async runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ..._args: unknown[]): Promise { const inlineChatSessions = accessor.get(IInlineChatSessionService); if (!editor.hasModel()) { return; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index dab7d18a8d1..eb42a9bde57 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -315,7 +315,7 @@ export class InlineChatController1 implements IEditorContribution { this._log('DISPOSED controller'); } - private _log(message: string | Error, ...more: any[]): void { + private _log(message: string | Error, ...more: unknown[]): void { if (message instanceof Error) { this._logService.error(message, ...more); } else { @@ -715,7 +715,7 @@ export class InlineChatController1 implements IEditorContribution { } if (e.kind === 'move') { assertType(this._session); - const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); + const log: typeof this._log = (msg: string, ...args: unknown[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); log('move was requested', e.target, e.range); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index dc0266c2baf..d416e8b6ef7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -123,7 +123,7 @@ export class ShowInlineChatHintAction extends EditorAction2 { }); } - override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: [uri: URI, position: IPosition, ...rest: any[]]) { + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: [uri: URI, position: IPosition, ...rest: unknown[]]) { if (!editor.hasModel()) { return; } diff --git a/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts index 1f0e9638235..914993f57ae 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-browser/inlineChatActions.ts @@ -38,7 +38,7 @@ export class HoldToSpeak extends EditorAction2 { }); } - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: unknown[]) { const ctrl = InlineChatController.get(editor); if (ctrl) { holdForSpeech(accessor, ctrl, this); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 2abc28493e9..239be611260 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -369,7 +369,7 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -395,7 +395,7 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -465,7 +465,7 @@ registerAction2(class extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 84b255c0183..399b121e1e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -207,7 +207,7 @@ export abstract class NotebookMultiCellAction extends Action2 { super(desc); } - parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index f52fbf3c8d4..639109ff03b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -285,7 +285,7 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -342,7 +342,7 @@ registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -389,7 +389,7 @@ registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -424,7 +424,7 @@ registerAction2(class ExecuteCellFocusContainer extends NotebookMultiCellAction }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } @@ -502,7 +502,7 @@ registerAction2(class CancelExecuteCell extends NotebookMultiCellAction { }); } - override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined { + override parseArgs(accessor: ServicesAccessor, ...args: unknown[]): INotebookCommandContext | undefined { return parseMultiCellExecutionArgs(accessor, ...args); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 2b32a1538da..ccf0c627bc1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -1264,7 +1264,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon for (const folder of this.workspaceContextService.getWorkspace().folders) { const commandId = `_workbench.openFolderSettings.${folder.uri.toString()}`; if (!CommandsRegistry.getCommand(commandId)) { - CommandsRegistry.registerCommand(commandId, (accessor: ServicesAccessor, ...args: any[]) => { + CommandsRegistry.registerCommand(commandId, (accessor: ServicesAccessor, ...args: unknown[]) => { const groupId = getEditorGroupFromArguments(accessor, args)?.id; if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) { return this.preferencesService.openWorkspaceSettings({ jsonEditor: false, groupId }); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 4fe094b5ae3..6591a385f8f 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -1296,7 +1296,7 @@ class TimelinePaneCommands extends Disposable { })); this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor', - (accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor + (accessor: ServicesAccessor, ...args: unknown[]) => pane.followActiveEditor = !pane.followActiveEditor )); this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ From 266ef47fe64fad809d4966e3697f4f0d09dbcb9d Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 6 Oct 2025 14:37:21 -0700 Subject: [PATCH 0863/4355] PR feedback --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index e255c015c12..258c6e9ebd0 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -34,6 +34,9 @@ const promptedRecommendationsStorageKey = 'fileBasedRecommendations/promptedReco const recommendationsStorageKey = 'extensionsAssistant/recommendations'; const milliSecondsInADay = 1000 * 60 * 60 * 24; +// Minimum length of untitled file to allow triggering extension recommendations for auto-detected language. +const untitledFileRecommendationsMinLength = 1000; + export class FileBasedRecommendations extends ExtensionRecommendations { private readonly fileOpenRecommendations: IStringDictionary; @@ -159,7 +162,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { // Avoid language-specific recommendations for untitled files when language is auto-detected except when the file is large. let allowLanguageMatch = true; - if (uri.scheme === Schemas.untitled && model.getValueLength() < 1000) { + if (uri.scheme === Schemas.untitled && model.getValueLength() < untitledFileRecommendationsMinLength) { const untitledModel = this.untitledTextEditorService.get(uri); if (untitledModel && !untitledModel.hasLanguageSetExplicitly) { allowLanguageMatch = false; From 2414493f6e5decdda58eb8378bf3dc9a9ce16a37 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:13:09 -0700 Subject: [PATCH 0864/4355] cleanup: remove notebook chat controller (#270047) * first pass, one bug fix * remove unused code --- .../browser/contrib/navigation/arrow.ts | 5 +- .../controller/chat/cellChatActions.ts | 460 +-------- .../controller/chat/notebookChatContext.ts | 12 - .../controller/chat/notebookChatController.ts | 949 ------------------ .../browser/controller/executeActions.ts | 16 - .../browser/controller/insertCellActions.ts | 3 +- .../browser/view/cellParts/cellContextKeys.ts | 27 +- .../notebook/common/notebookContextKeys.ts | 1 - 8 files changed, 9 insertions(+), 1464 deletions(-) delete mode 100644 src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index 9a94c4a3f2b..e07c0d65973 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -18,7 +18,6 @@ import { ServicesAccessor } from '../../../../../../platform/instantiation/commo import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { Registry } from '../../../../../../platform/registry/common/platform.js'; import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; -import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from '../../controller/chat/notebookChatContext.js'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from '../../controller/coreActions.js'; import { CellEditState } from '../../notebookBrowser.js'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY } from '../../../common/notebookCommon.js'; @@ -237,7 +236,7 @@ registerAction2(class extends NotebookAction { weight: KeybindingWeight.WorkbenchContrib }, { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, weight: KeybindingWeight.WorkbenchContrib } @@ -269,7 +268,7 @@ registerAction2(class extends NotebookAction { weight: KeybindingWeight.WorkbenchContrib }, { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 7c52036cb9a..55310ea8efb 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -5,9 +5,7 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { KeyChord, KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js'; -import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; import { localize, localize2 } from '../../../../../../nls.js'; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../../platform/accessibility/common/accessibility.js'; import { MenuId, MenuRegistry, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; @@ -15,14 +13,12 @@ import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/con import { InputFocusedContextKey } from '../../../../../../platform/contextkey/common/contextkeys.js'; import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, InlineChatResponseType, MENU_INLINE_CHAT_WIDGET_STATUS } from '../../../../inlineChat/common/inlineChat.js'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_HAS_AGENT, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js'; -import { NotebookChatController } from './notebookChatController.js'; -import { CELL_TITLE_CELL_GROUP_ID, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getContextFromActiveEditor, getEditorFromArgsOrActivePane } from '../coreActions.js'; +import { CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, InlineChatResponseType, MENU_INLINE_CHAT_WIDGET_STATUS } from '../../../../inlineChat/common/inlineChat.js'; +import { CTX_NOTEBOOK_CHAT_HAS_AGENT } from './notebookChatContext.js'; +import { INotebookActionContext, NotebookAction, getContextFromActiveEditor, getEditorFromArgsOrActivePane } from '../coreActions.js'; import { insertNewCell } from '../insertCellActions.js'; -import { CellEditState } from '../../notebookBrowser.js'; -import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from '../../../common/notebookCommon.js'; -import { IS_COMPOSITE_NOTEBOOK, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from '../../../common/notebookContextKeys.js'; +import { CellKind, NotebookSetting } from '../../../common/notebookCommon.js'; +import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from '../../../common/notebookContextKeys.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; @@ -30,283 +26,6 @@ import { ChatContextKeys } from '../../../../chat/common/chatContextKeys.js'; import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; import { EditorAction2 } from '../../../../../../editor/browser/editorExtensions.js'; -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.accept', - title: localize2('notebook.cell.chat.accept', "Make Request"), - icon: Codicon.send, - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()), - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Enter - }, - menu: { - id: MENU_CELL_CHAT_INPUT, - group: 'navigation', - order: 1, - when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.negate() - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.acceptInput(); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: 'notebook.cell.chat.arrowOutUp', - title: localize('arrowUp', 'Cursor Up'), - keybinding: { - when: ContextKeyExpr.and( - CTX_NOTEBOOK_CELL_CHAT_FOCUSED, - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_FIRST, - NOTEBOOK_CELL_EDITOR_FOCUSED.negate(), - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate() - ), - weight: KeybindingWeight.EditorCore + 7, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const editor = context.notebookEditor; - const activeCell = context.cell; - - const idx = editor.getCellIndex(activeCell); - if (typeof idx !== 'number') { - return; - } - - if (idx < 1 || editor.getLength() === 0) { - // we don't do loop - return; - } - - const newCell = editor.cellAt(idx - 1); - const newFocusMode = newCell.cellKind === CellKind.Markup && newCell.getEditState() === CellEditState.Preview ? 'container' : 'editor'; - const focusEditorLine = newCell.textBuffer.getLineCount(); - await editor.focusNotebookCell(newCell, newFocusMode, { focusEditorLine: focusEditorLine }); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.arrowOutDown', - title: localize('arrowDown', 'Cursor Down'), - keybinding: { - when: ContextKeyExpr.and( - CTX_NOTEBOOK_CELL_CHAT_FOCUSED, - CTX_INLINE_CHAT_FOCUSED, - CTX_INLINE_CHAT_INNER_CURSOR_LAST, - NOTEBOOK_CELL_EDITOR_FOCUSED.negate(), - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate() - ), - weight: KeybindingWeight.EditorCore + 7, - primary: KeyMod.CtrlCmd | KeyCode.DownArrow - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - await NotebookChatController.get(context.notebookEditor)?.focusNext(); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: 'notebook.cell.focusChatWidget', - title: localize('focusChatWidget', 'Focus Chat Widget'), - keybinding: { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ContextKeyExpr.and( - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), - ), - EditorContextKeys.isEmbeddedDiffEditor.negate() - ), - weight: KeybindingWeight.EditorCore + 7, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const index = context.notebookEditor.getCellIndex(context.cell); - await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'above'); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: 'notebook.cell.focusNextChatWidget', - title: localize('focusNextChatWidget', 'Focus Next Cell Chat Widget'), - keybinding: { - when: ContextKeyExpr.and( - CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate(), - ContextKeyExpr.and( - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), - ), - EditorContextKeys.isEmbeddedDiffEditor.negate() - ), - weight: KeybindingWeight.EditorCore + 7, - primary: KeyMod.CtrlCmd | KeyCode.DownArrow - }, - f1: false, - precondition: ContextKeyExpr.or( - ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK.negate(), NOTEBOOK_CELL_EDITOR_FOCUSED), - ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()), - ) - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const index = context.notebookEditor.getCellIndex(context.cell); - await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'below'); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.stop', - title: localize2('notebook.cell.chat.stop', "Stop Request"), - icon: Codicon.debugStop, - menu: { - id: MENU_CELL_CHAT_INPUT, - group: 'navigation', - order: 1, - when: CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.close', - title: localize2('notebook.cell.chat.close', "Close Chat"), - icon: Codicon.close, - menu: { - id: MENU_CELL_CHAT_WIDGET, - group: 'navigation', - order: 2 - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.dismiss(false); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.acceptChanges', - title: localize2('apply1', "Accept Changes"), - shortTitle: localize('apply2', 'Accept'), - icon: Codicon.check, - tooltip: localize('apply3', 'Accept Changes'), - keybinding: [ - { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()), - weight: KeybindingWeight.EditorContrib + 10, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - }, - { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, NOTEBOOK_CELL_EDITOR_FOCUSED.negate()), - weight: KeybindingWeight.EditorCore + 10, - primary: KeyCode.Escape - }, - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.not(InputFocusedContextKey), - NOTEBOOK_CELL_EDITOR_FOCUSED.negate(), - CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') - ), - primary: KeyMod.CtrlCmd | KeyCode.Enter, - weight: KeybindingWeight.WorkbenchContrib - } - ], - menu: [ - { - id: MENU_CELL_CHAT_WIDGET_STATUS, - group: '0_main', - order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages), - } - ], - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.acceptSession(); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.discard', - title: localize('discard', 'Discard'), - icon: Codicon.discard, - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT.negate(), NOTEBOOK_CELL_EDITOR_FOCUSED.negate()), - weight: KeybindingWeight.EditorContrib, - primary: KeyCode.Escape - }, - menu: { - id: MENU_CELL_CHAT_WIDGET_STATUS, - group: '0_main', - order: 1 - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.discard(); - } -}); - interface IInsertCellWithChatArgs extends INotebookActionContext { input?: string; autoSend?: boolean; @@ -501,175 +220,6 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { ) }); -registerAction2(class extends NotebookAction { - constructor() { - super({ - id: 'notebook.cell.chat.focus', - title: localize('focusNotebookChat', 'Focus Chat'), - keybinding: [ - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.not(InputFocusedContextKey), - CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('above') - ), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib - }, - { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.not(InputFocusedContextKey), - CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib - } - ], - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - NotebookChatController.get(context.notebookEditor)?.focus(); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super({ - id: 'notebook.cell.chat.focusNextCell', - title: localize('focusNextCell', 'Focus Next Cell'), - keybinding: [ - { - when: ContextKeyExpr.and( - CTX_NOTEBOOK_CELL_CHAT_FOCUSED, - CTX_INLINE_CHAT_FOCUSED, - ), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib - } - ], - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - NotebookChatController.get(context.notebookEditor)?.focusNext(); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super({ - id: 'notebook.cell.chat.focusPreviousCell', - title: localize('focusPreviousCell', 'Focus Previous Cell'), - keybinding: [ - { - when: ContextKeyExpr.and( - CTX_NOTEBOOK_CELL_CHAT_FOCUSED, - CTX_INLINE_CHAT_FOCUSED, - ), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib - } - ], - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { - NotebookChatController.get(context.notebookEditor)?.focusAbove(); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.previousFromHistory', - title: localize2('notebook.cell.chat.previousFromHistory', "Previous From History"), - precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - weight: KeybindingWeight.EditorCore + 10, - primary: KeyCode.UpArrow, - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.populateHistory(true); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: 'notebook.cell.chat.nextFromHistory', - title: localize2('notebook.cell.chat.nextFromHistory', "Next From History"), - precondition: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - weight: KeybindingWeight.EditorCore + 10, - primary: KeyCode.DownArrow - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - NotebookChatController.get(context.notebookEditor)?.populateHistory(false); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: 'notebook.cell.chat.restore', - title: localize2('notebookActions.restoreCellprompt', "Generate"), - icon: Codicon.sparkle, - menu: { - id: MenuId.NotebookCellTitle, - group: CELL_TITLE_CELL_GROUP_ID, - order: 0, - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), - CTX_NOTEBOOK_CHAT_HAS_AGENT, - NOTEBOOK_CELL_GENERATED_BY_CHAT, - ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) - ) - }, - f1: false - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const cell = context.cell; - - if (!cell) { - return; - } - - const notebookEditor = context.notebookEditor; - const controller = NotebookChatController.get(notebookEditor); - - if (!controller) { - return; - } - - const prompt = controller.getPromptFromCache(cell); - - if (prompt) { - controller.restore(cell, prompt); - } - } -}); - - export class AcceptChangesAndRun extends EditorAction2 { constructor() { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index c09bdb176b5..66c60504d98 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -4,18 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../../../nls.js'; -import { MenuId } from '../../../../../../platform/actions/common/actions.js'; import { RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js'; -export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); -export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); -export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat")); -export const CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION = new RawContextKey<'above' | 'below' | ''>('notebookChatOuterFocusPosition', '', localize('notebookChatOuterFocusPosition', "Whether the focus of the notebook editor is above or below the cell chat")); - -export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); -export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); -export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); -export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); -export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); - export const CTX_NOTEBOOK_CHAT_HAS_AGENT = new RawContextKey('notebookChatAgentRegistered', false, localize('notebookChatAgentRegistered', "Whether a chat agent for notebook is registered")); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts deleted file mode 100644 index 205d040c15b..00000000000 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ /dev/null @@ -1,949 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension, IFocusTracker, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame, trackFocus } from '../../../../../../base/browser/dom.js'; -import { CancelablePromise, DeferredPromise, Queue, createCancelablePromise, disposableTimeout } from '../../../../../../base/common/async.js'; -import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js'; -import { Emitter } from '../../../../../../base/common/event.js'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { LRUCache } from '../../../../../../base/common/map.js'; -import { Schemas } from '../../../../../../base/common/network.js'; -import { MovingAverage } from '../../../../../../base/common/numbers.js'; -import { isEqual } from '../../../../../../base/common/resources.js'; -import { StopWatch } from '../../../../../../base/common/stopwatch.js'; -import { assertType } from '../../../../../../base/common/types.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { IActiveCodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; -import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; -import { ISingleEditOperation } from '../../../../../../editor/common/core/editOperation.js'; -import { Position } from '../../../../../../editor/common/core/position.js'; -import { Selection } from '../../../../../../editor/common/core/selection.js'; -import { TextEdit } from '../../../../../../editor/common/languages.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; -import { ICursorStateComputer, ITextModel } from '../../../../../../editor/common/model.js'; -import { IEditorWorkerService } from '../../../../../../editor/common/services/editorWorker.js'; -import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { localize } from '../../../../../../nls.js'; -import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; -import { ChatModel, IChatModel } from '../../../../chat/common/chatModel.js'; -import { IChatService } from '../../../../chat/common/chatService.js'; -import { countWords } from '../../../../chat/common/chatWordCounter.js'; -import { ChatAgentLocation } from '../../../../chat/common/constants.js'; -import { ProgressingEditsOptions } from '../../../../inlineChat/browser/inlineChatStrategies.js'; -import { InlineChatWidget } from '../../../../inlineChat/browser/inlineChatWidget.js'; -import { asProgressiveEdit, performAsyncTextEdit } from '../../../../inlineChat/browser/utils.js'; -import { CellKind } from '../../../common/notebookCommon.js'; -import { INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js'; -import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from '../../notebookBrowser.js'; -import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; -import { insertCell, runDeleteAction } from '../cellOperations.js'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_WIDGET_STATUS } from './notebookChatContext.js'; - -class NotebookChatWidget extends Disposable implements INotebookViewZone { - set afterModelPosition(afterModelPosition: number) { - this.notebookViewZone.afterModelPosition = afterModelPosition; - } - - get afterModelPosition(): number { - return this.notebookViewZone.afterModelPosition; - } - - set heightInPx(heightInPx: number) { - this.notebookViewZone.heightInPx = heightInPx; - } - - get heightInPx(): number { - return this.notebookViewZone.heightInPx; - } - - private _editingCell: ICellViewModel | null = null; - - get editingCell() { - return this._editingCell; - } - - constructor( - private readonly _notebookEditor: INotebookEditor, - readonly id: string, - readonly notebookViewZone: INotebookViewZone, - readonly domNode: HTMLElement, - readonly widgetContainer: HTMLElement, - readonly inlineChatWidget: InlineChatWidget, - readonly parentEditor: CodeEditorWidget, - private readonly _languageService: ILanguageService, - ) { - super(); - - const updateHeight = () => { - if (this.heightInPx === inlineChatWidget.contentHeight) { - return; - } - - this.heightInPx = inlineChatWidget.contentHeight; - this._notebookEditor.changeViewZones(accessor => { - accessor.layoutZone(id); - }); - this._layoutWidget(inlineChatWidget, widgetContainer); - }; - - this._register(inlineChatWidget.onDidChangeHeight(() => { - updateHeight(); - })); - - this._register(inlineChatWidget.chatWidget.onDidChangeHeight(() => { - updateHeight(); - })); - - this.heightInPx = inlineChatWidget.contentHeight; - this._layoutWidget(inlineChatWidget, widgetContainer); - } - - layout() { - this._layoutWidget(this.inlineChatWidget, this.widgetContainer); - } - - restoreEditingCell(initEditingCell: ICellViewModel) { - this._editingCell = initEditingCell; - - const decorationIds = this._notebookEditor.deltaCellDecorations([], [{ - handle: this._editingCell.handle, - options: { className: 'nb-chatGenerationHighlight', outputClassName: 'nb-chatGenerationHighlight' } - }]); - - this._register(toDisposable(() => { - this._notebookEditor.deltaCellDecorations(decorationIds, []); - })); - } - - hasFocus() { - return this.inlineChatWidget.hasFocus(); - } - - focus() { - this.updateNotebookEditorFocusNSelections(); - this.inlineChatWidget.focus(); - } - - updateNotebookEditorFocusNSelections() { - this._notebookEditor.focusContainer(true); - this._notebookEditor.setFocus({ start: this.afterModelPosition, end: this.afterModelPosition }); - this._notebookEditor.setSelections([{ - start: this.afterModelPosition, - end: this.afterModelPosition - }]); - } - - getEditingCell() { - return this._editingCell; - } - - async getOrCreateEditingCell(): Promise<{ cell: ICellViewModel; editor: IActiveCodeEditor } | undefined> { - if (this._editingCell) { - const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; - if (codeEditor?.hasModel()) { - return { - cell: this._editingCell, - editor: codeEditor - }; - } else { - return undefined; - } - } - - if (!this._notebookEditor.hasModel()) { - return undefined; - } - - const widgetHasFocus = this.inlineChatWidget.hasFocus(); - - this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above'); - - if (!this._editingCell) { - return undefined; - } - - await this._notebookEditor.revealFirstLineIfOutsideViewport(this._editingCell); - - // update decoration - const decorationIds = this._notebookEditor.deltaCellDecorations([], [{ - handle: this._editingCell.handle, - options: { className: 'nb-chatGenerationHighlight', outputClassName: 'nb-chatGenerationHighlight' } - }]); - - this._register(toDisposable(() => { - this._notebookEditor.deltaCellDecorations(decorationIds, []); - })); - - if (widgetHasFocus) { - this.focus(); - } - - const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; - if (codeEditor?.hasModel()) { - return { - cell: this._editingCell, - editor: codeEditor - }; - } - - return undefined; - } - - async discardChange() { - if (this._notebookEditor.hasModel() && this._editingCell) { - // remove the cell from the notebook - runDeleteAction(this._notebookEditor, this._editingCell); - } - } - - private _layoutWidget(inlineChatWidget: InlineChatWidget, widgetContainer: HTMLElement) { - const layoutConfiguration = this._notebookEditor.notebookOptions.getLayoutConfiguration(); - const rightMargin = layoutConfiguration.cellRightMargin; - const leftMargin = this._notebookEditor.notebookOptions.getCellEditorContainerLeftMargin(); - const maxWidth = 640; - const width = Math.min(maxWidth, this._notebookEditor.getLayoutInfo().width - leftMargin - rightMargin); - - inlineChatWidget.layout(new Dimension(width, this.heightInPx)); - inlineChatWidget.domNode.style.width = `${width}px`; - widgetContainer.style.left = `${leftMargin}px`; - } - - override dispose() { - this._notebookEditor.changeViewZones(accessor => { - accessor.removeZone(this.id); - }); - this.domNode.remove(); - super.dispose(); - } -} - -export interface INotebookCellTextModelLike { uri: URI; viewType: string } -class NotebookCellTextModelLikeId { - static str(k: INotebookCellTextModelLike): string { - return `${k.viewType}/${k.uri.toString()}`; - } - static obj(s: string): INotebookCellTextModelLike { - const idx = s.indexOf('/'); - return { - viewType: s.substring(0, idx), - uri: URI.parse(s.substring(idx + 1)) - }; - } -} - -export class NotebookChatController extends Disposable implements INotebookEditorContribution { - static id: string = 'workbench.notebook.chatController'; - static counter: number = 0; - - public static get(editor: INotebookEditor): NotebookChatController | null { - return editor.getContribution(NotebookChatController.id); - } - - // History - private static _storageKey = 'inline-chat-history'; - private static _promptHistory: string[] = []; - private _historyOffset: number = -1; - private _historyCandidate: string = ''; - private _historyUpdate: (prompt: string) => void; - private _promptCache = new LRUCache(1000, 0.7); - private readonly _onDidChangePromptCache = this._register(new Emitter<{ cell: URI }>()); - readonly onDidChangePromptCache = this._onDidChangePromptCache.event; - - private _strategy: EditStrategy | undefined; - private _sessionCtor: CancelablePromise | undefined; - private _activeRequestCts?: CancellationTokenSource; - private readonly _ctxHasActiveRequest: IContextKey; - private readonly _ctxCellWidgetFocused: IContextKey; - private readonly _ctxUserDidEdit: IContextKey; - private readonly _ctxOuterFocusPosition: IContextKey<'above' | 'below' | ''>; - private readonly _userEditingDisposables = this._register(new DisposableStore()); - private readonly _widgetDisposableStore = this._register(new DisposableStore()); - private _focusTracker: IFocusTracker | undefined; - private _widget: NotebookChatWidget | undefined; - - private readonly _model: MutableDisposable = this._register(new MutableDisposable()); - constructor( - private readonly _notebookEditor: INotebookEditor, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @IModelService private readonly _modelService: IModelService, - @ILanguageService private readonly _languageService: ILanguageService, - @INotebookExecutionStateService private _executionStateService: INotebookExecutionStateService, - @IStorageService private readonly _storageService: IStorageService, - @IChatService private readonly _chatService: IChatService - ) { - super(); - this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); - this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); - this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService); - this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService); - - this._registerFocusTracker(); - - NotebookChatController._promptHistory = JSON.parse(this._storageService.get(NotebookChatController._storageKey, StorageScope.PROFILE, '[]')); - this._historyUpdate = (prompt: string) => { - const idx = NotebookChatController._promptHistory.indexOf(prompt); - if (idx >= 0) { - NotebookChatController._promptHistory.splice(idx, 1); - } - NotebookChatController._promptHistory.unshift(prompt); - this._historyOffset = -1; - this._historyCandidate = ''; - this._storageService.store(NotebookChatController._storageKey, JSON.stringify(NotebookChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER); - }; - } - - private _registerFocusTracker() { - this._register(this._notebookEditor.onDidChangeFocus(() => { - if (!this._widget) { - this._ctxOuterFocusPosition.set(''); - return; - } - - const widgetIndex = this._widget.afterModelPosition; - const focus = this._notebookEditor.getFocus().start; - - if (focus + 1 === widgetIndex) { - this._ctxOuterFocusPosition.set('above'); - } else if (focus === widgetIndex) { - this._ctxOuterFocusPosition.set('below'); - } else { - this._ctxOuterFocusPosition.set(''); - } - })); - } - - run(index: number, input: string | undefined, autoSend: boolean | undefined): void { - if (this._widget) { - if (this._widget.afterModelPosition !== index) { - const window = getWindow(this._widget.domNode); - this._disposeWidget(); - - scheduleAtNextAnimationFrame(window, () => { - this._createWidget(index, input, autoSend, undefined); - }); - } - - return; - } - - this._createWidget(index, input, autoSend, undefined); - // TODO: reveal widget to the center if it's out of the viewport - } - - restore(editingCell: ICellViewModel, input: string) { - if (!this._notebookEditor.hasModel()) { - return; - } - - const index = this._notebookEditor.textModel.cells.indexOf(editingCell.model); - - if (index < 0) { - return; - } - - if (this._widget) { - if (this._widget.afterModelPosition !== index) { - this._disposeWidget(); - const window = getWindow(this._widget.domNode); - - scheduleAtNextAnimationFrame(window, () => { - this._createWidget(index, input, false, editingCell); - }); - } - - return; - } - - this._createWidget(index, input, false, editingCell); - } - - private _disposeWidget() { - this._widget?.dispose(); - this._widget = undefined; - this._widgetDisposableStore.clear(); - - this._historyOffset = -1; - this._historyCandidate = ''; - } - - - private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined, initEditingCell: ICellViewModel | undefined) { - if (!this._notebookEditor.hasModel()) { - return; - } - - // Clear the widget if it's already there - this._widgetDisposableStore.clear(); - - const viewZoneContainer = document.createElement('div'); - viewZoneContainer.classList.add('monaco-editor'); - const widgetContainer = document.createElement('div'); - widgetContainer.style.position = 'absolute'; - viewZoneContainer.appendChild(widgetContainer); - - this._focusTracker = this._widgetDisposableStore.add(trackFocus(viewZoneContainer)); - this._widgetDisposableStore.add(this._focusTracker.onDidFocus(() => { - this._updateNotebookEditorFocusNSelections(); - })); - - const fakeParentEditorElement = document.createElement('div'); - - const fakeParentEditor = this._widgetDisposableStore.add(this._instantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - }, - { isSimpleWidget: true } - )); - - const inputBoxFragment = `notebook-chat-input-${NotebookChatController.counter++}`; - const notebookUri = this._notebookEditor.textModel.uri; - const inputUri = notebookUri.with({ scheme: Schemas.untitled, fragment: inputBoxFragment }); - const result: ITextModel = this._modelService.createModel('', null, inputUri, false); - fakeParentEditor.setModel(result); - - const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance( - InlineChatWidget, - { - location: ChatAgentLocation.Notebook, - resolveData: () => { - const sessionInputUri = this.getSessionInputUri(); - if (!sessionInputUri) { - return undefined; - } - return { - type: ChatAgentLocation.Notebook, - sessionInputUri - }; - } - }, - { - statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - chatWidgetViewOptions: { - rendererOptions: { - renderTextEditsAsSummary: (uri) => { - return isEqual(uri, this._widget?.parentEditor.getModel()?.uri) - || isEqual(uri, this._notebookEditor.textModel?.uri); - } - }, - menus: { - telemetrySource: 'notebook-generate-cell' - } - } - } - )); - inlineChatWidget.placeholder = localize('default.placeholder', "Ask or edit in context"); - inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); - widgetContainer.appendChild(inlineChatWidget.domNode); - - - this._notebookEditor.changeViewZones(accessor => { - const notebookViewZone = { - afterModelPosition: index, - heightInPx: 80, - domNode: viewZoneContainer - }; - - const id = accessor.addZone(notebookViewZone); - this._scrollWidgetIntoView(index); - - this._widget = new NotebookChatWidget( - this._notebookEditor, - id, - notebookViewZone, - viewZoneContainer, - widgetContainer, - inlineChatWidget, - fakeParentEditor, - this._languageService - ); - - if (initEditingCell) { - this._widget.restoreEditingCell(initEditingCell); - this._updateUserEditingState(); - } - - this._ctxCellWidgetFocused.set(true); - - disposableTimeout(() => { - this._focusWidget(); - }, 0, this._store); - - this._sessionCtor = createCancelablePromise(async token => { - await this._startSession(token); - assertType(this._model.value); - const model = this._model.value; - this._widget?.inlineChatWidget.setChatModel(model); - - if (fakeParentEditor.hasModel()) { - - if (this._widget) { - this._focusWidget(); - } - - if (this._widget && input) { - this._widget.inlineChatWidget.value = input; - - if (autoSend) { - this.acceptInput(); - } - } - } - }); - }); - } - - private async _startSession(token: CancellationToken) { - if (!this._model.value) { - this._model.value = this._chatService.startSession(ChatAgentLocation.EditorInline, token); - - if (!this._model.value) { - throw new Error('Failed to start chat session'); - } - } - - this._strategy = new EditStrategy(); - } - - private _scrollWidgetIntoView(index: number) { - if (index === 0 || this._notebookEditor.getLength() === 0) { - // the cell is at the beginning of the notebook - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0); - } else { - // the cell is at the end of the notebook - const previousCell = this._notebookEditor.cellAt(Math.min(index - 1, this._notebookEditor.getLength() - 1)); - if (previousCell) { - const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell); - const cellHeight = this._notebookEditor.getHeightOfElement(previousCell); - - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight + 48 /** center of the dialog */); - } - } - } - - private _focusWidget() { - if (!this._widget) { - return; - } - - this._updateNotebookEditorFocusNSelections(); - this._widget.focus(); - } - - private _updateNotebookEditorFocusNSelections() { - if (!this._widget) { - return; - } - - this._widget.updateNotebookEditorFocusNSelections(); - } - - hasSession(chatModel: IChatModel) { - return this._model.value === chatModel; - } - - getSessionInputUri() { - return this._widget?.parentEditor.getModel()?.uri; - } - - async acceptInput() { - assertType(this._widget); - await this._sessionCtor; - assertType(this._model.value); - assertType(this._strategy); - - const lastInput = this._widget.inlineChatWidget.value; - this._historyUpdate(lastInput); - - const editor = this._widget.parentEditor; - const textModel = editor.getModel(); - - if (!editor.hasModel() || !textModel) { - return; - } - - if (this._widget.editingCell && this._widget.editingCell.textBuffer.getLength() > 0) { - // it already contains some text, clear it - const ref = await this._widget.editingCell.resolveTextModel(); - ref.setValue(''); - } - - const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined; - if (editingCellIndex !== undefined) { - this._notebookEditor.setSelections([{ - start: editingCellIndex, - end: editingCellIndex + 1 - }]); - } else { - // Update selection to the widget index - this._notebookEditor.setSelections([{ - start: this._widget.afterModelPosition, - end: this._widget.afterModelPosition - }]); - } - - this._ctxHasActiveRequest.set(true); - - this._activeRequestCts?.cancel(); - this._activeRequestCts = new CancellationTokenSource(); - - const store = new DisposableStore(); - - try { - this._ctxHasActiveRequest.set(true); - - const progressiveEditsQueue = new Queue(); - const progressiveEditsClock = StopWatch.create(); - const progressiveEditsAvgDuration = new MovingAverage(); - const progressiveEditsCts = new CancellationTokenSource(this._activeRequestCts.token); - - const responsePromise = new DeferredPromise(); - const response = await this._widget.inlineChatWidget.chatWidget.acceptInput(); - if (response) { - let lastLength = 0; - - store.add(response.onDidChange(e => { - if (response.isCanceled) { - progressiveEditsCts.cancel(); - responsePromise.complete(); - return; - } - - if (response.isComplete) { - responsePromise.complete(); - return; - } - - const edits = response.response.value.map(part => { - if (part.kind === 'textEditGroup' - // && isEqual(part.uri, this._session?.textModelN.uri) - ) { - return part.edits; - } else { - return []; - } - }).flat(); - - const newEdits = edits.slice(lastLength); - // console.log('NEW edits', newEdits, edits); - if (newEdits.length === 0) { - return; // NO change - } - lastLength = edits.length; - progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed()); - progressiveEditsClock.reset(); - - progressiveEditsQueue.queue(async () => { - for (const edits of newEdits) { - await this._makeChanges(edits, { - duration: progressiveEditsAvgDuration.value, - token: progressiveEditsCts.token - }); - } - }); - })); - } - - await responsePromise.p; - await progressiveEditsQueue.whenIdle(); - - this._userEditingDisposables.clear(); - // monitor user edits - const editingCell = this._widget.getEditingCell(); - if (editingCell) { - this._userEditingDisposables.add(editingCell.model.onDidChangeContent(() => this._updateUserEditingState())); - this._userEditingDisposables.add(editingCell.model.onDidChangeLanguage(() => this._updateUserEditingState())); - this._userEditingDisposables.add(editingCell.model.onDidChangeMetadata(() => this._updateUserEditingState())); - this._userEditingDisposables.add(editingCell.model.onDidChangeInternalMetadata(() => this._updateUserEditingState())); - this._userEditingDisposables.add(editingCell.model.onDidChangeOutputs(() => this._updateUserEditingState())); - this._userEditingDisposables.add(this._executionStateService.onDidChangeExecution(e => { - if (e.type === NotebookExecutionType.cell && e.affectsCell(editingCell.uri)) { - this._updateUserEditingState(); - } - })); - } - } catch (e) { - } finally { - store.dispose(); - - this._ctxHasActiveRequest.set(false); - this._widget.inlineChatWidget.updateInfo(''); - this._widget.inlineChatWidget.updateToolbar(true); - } - } - - private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { - assertType(this._strategy); - assertType(this._widget); - - const editingCell = await this._widget.getOrCreateEditingCell(); - - if (!editingCell) { - return; - } - - const editor = editingCell.editor; - - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits); - // this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); - - if (moreMinimalEdits?.length === 0) { - // nothing left to do - return; - } - - const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits; - const editOperations = actualEdits.map(TextEdit.asEditOperation); - - try { - if (opts) { - await this._strategy.makeProgressiveChanges(editor, editOperations, opts); - } else { - await this._strategy.makeChanges(editor, editOperations); - } - } finally { - } - } - - private _updateUserEditingState() { - this._ctxUserDidEdit.set(true); - } - - async acceptSession() { - assertType(this._model); - assertType(this._strategy); - - const editor = this._widget?.parentEditor; - if (!editor?.hasModel()) { - return; - } - - const editingCell = this._widget?.getEditingCell(); - - if (editingCell && this._notebookEditor.hasModel()) { - const cellId = NotebookCellTextModelLikeId.str({ uri: editingCell.uri, viewType: this._notebookEditor.textModel.viewType }); - if (this._widget?.inlineChatWidget.value) { - this._promptCache.set(cellId, this._widget.inlineChatWidget.value); - } - this._onDidChangePromptCache.fire({ cell: editingCell.uri }); - } - - try { - this._model.clear(); - } catch (_err) { } - - this.dismiss(false); - } - - async focusAbove() { - if (!this._widget) { - return; - } - - const index = this._widget.afterModelPosition; - const prev = index - 1; - if (prev < 0) { - return; - } - - const cell = this._notebookEditor.cellAt(prev); - if (!cell) { - return; - } - - await this._notebookEditor.focusNotebookCell(cell, 'editor'); - } - - async focusNext() { - if (!this._widget) { - return; - } - - const index = this._widget.afterModelPosition; - const cell = this._notebookEditor.cellAt(index); - if (!cell) { - return; - } - - await this._notebookEditor.focusNotebookCell(cell, 'editor'); - } - - hasFocus() { - return this._widget?.hasFocus() ?? false; - } - - focus() { - this._focusWidget(); - } - - focusNearestWidget(index: number, direction: 'above' | 'below') { - switch (direction) { - case 'above': - if (this._widget?.afterModelPosition === index) { - this._focusWidget(); - } - break; - case 'below': - if (this._widget?.afterModelPosition === index + 1) { - this._focusWidget(); - } - break; - default: - break; - } - } - - populateHistory(up: boolean) { - if (!this._widget) { - return; - } - - const len = NotebookChatController._promptHistory.length; - if (len === 0) { - return; - } - - if (this._historyOffset === -1) { - // remember the current value - this._historyCandidate = this._widget.inlineChatWidget.value; - } - - const newIdx = this._historyOffset + (up ? 1 : -1); - if (newIdx >= len) { - // reached the end - return; - } - - let entry: string; - if (newIdx < 0) { - entry = this._historyCandidate; - this._historyOffset = -1; - } else { - entry = NotebookChatController._promptHistory[newIdx]; - this._historyOffset = newIdx; - } - - this._widget.inlineChatWidget.value = entry; - this._widget.inlineChatWidget.selectAll(); - } - - async cancelCurrentRequest(discard: boolean) { - this._activeRequestCts?.cancel(); - } - - getEditingCell() { - return this._widget?.getEditingCell(); - } - - discard() { - this._activeRequestCts?.cancel(); - this._widget?.discardChange(); - this.dismiss(true); - } - - dismiss(discard: boolean) { - const widget = this._widget; - const widgetIndex = widget?.afterModelPosition; - const currentFocus = this._notebookEditor.getFocus(); - const isWidgetFocused = currentFocus.start === widgetIndex && currentFocus.end === widgetIndex; - - if (widget && isWidgetFocused) { - // change focus only when the widget is focused - const editingCell = widget.getEditingCell(); - const shouldFocusEditingCell = editingCell && !discard; - const shouldFocusTopCell = widgetIndex === 0 && this._notebookEditor.getLength() > 0; - const shouldFocusAboveCell = widgetIndex !== 0 && this._notebookEditor.cellAt(widgetIndex - 1); - - if (shouldFocusEditingCell) { - this._notebookEditor.focusNotebookCell(editingCell, 'container'); - } else if (shouldFocusTopCell) { - this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container'); - } else if (shouldFocusAboveCell) { - this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(widgetIndex - 1)!, 'container'); - } - } - - this._ctxCellWidgetFocused.set(false); - this._ctxUserDidEdit.set(false); - this._sessionCtor?.cancel(); - this._sessionCtor = undefined; - this._model.clear(); - this._widget?.dispose(); - this._widget = undefined; - this._widgetDisposableStore.clear(); - } - - // check if a cell is generated by prompt by checking prompt cache - isCellGeneratedByChat(cell: ICellViewModel) { - if (!this._notebookEditor.hasModel()) { - // no model attached yet - return false; - } - - const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType }); - return this._promptCache.has(cellId); - } - - // get prompt from cache - getPromptFromCache(cell: ICellViewModel) { - if (!this._notebookEditor.hasModel()) { - // no model attached yet - return undefined; - } - - const cellId = NotebookCellTextModelLikeId.str({ uri: cell.uri, viewType: this._notebookEditor.textModel.viewType }); - return this._promptCache.get(cellId); - } - public override dispose(): void { - this.dismiss(false); - super.dispose(); - } -} - -export class EditStrategy { - private _editCount: number = 0; - - constructor() { - } - - async makeProgressiveChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { - // push undo stop before first edit - if (++this._editCount === 1) { - editor.pushUndoStop(); - } - - const durationInSec = opts.duration / 1000; - for (const edit of edits) { - const wordCount = countWords(edit.text ?? ''); - const speed = wordCount / durationInSec; - // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(new WindowIntervalTimer(), edit, speed, opts.token)); - } - } - - async makeChanges(editor: IActiveCodeEditor, edits: ISingleEditOperation[]): Promise { - const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { - let last: Position | null = null; - for (const edit of undoEdits) { - last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last; - // this._inlineDiffDecorations.collectEditOperation(edit); - } - return last && [Selection.fromPositions(last)]; - }; - - // push undo stop before first edit - if (++this._editCount === 1) { - editor.pushUndoStop(); - } - editor.executeEdits('inline-chat-live', edits, cursorStateComputerAndInlineDiffCollection); - } -} - - -registerNotebookContribution(NotebookChatController.id, NotebookChatController); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 639109ff03b..852f234ca14 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -18,7 +18,6 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { IDebugService } from '../../../debug/common/debug.js'; import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js'; import { insertCell } from './cellOperations.js'; -import { NotebookChatController } from './chat/notebookChatController.js'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from './coreActions.js'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, IFocusNotebookCellOptions, ScrollToRevealBehavior } from '../notebookBrowser.js'; import * as icons from '../notebookIcons.js'; @@ -297,21 +296,6 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true }); } - const chatController = NotebookChatController.get(context.notebookEditor); - const editingCell = chatController?.getEditingCell(); - if (chatController?.hasFocus() && editingCell) { - const group = editorGroupsService.activeGroup; - - if (group) { - if (group.activeEditor) { - group.pinEditor(group.activeEditor); - } - } - - await context.notebookEditor.executeNotebookCells([editingCell]); - return; - } - await runCell(editorGroupsService, context, editorService); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index b7bdf36f347..7f798a85f97 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -17,7 +17,6 @@ import { INotebookActionContext, NotebookAction } from './coreActions.js'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from '../../common/notebookContextKeys.js'; import { CellViewModel } from '../viewModel/notebookViewModelImpl.js'; import { CellKind, NotebookSetting } from '../../common/notebookCommon.js'; -import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from './chat/notebookChatContext.js'; import { INotebookKernelHistoryService } from '../../common/notebookKernelService.js'; const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; @@ -114,7 +113,7 @@ registerAction2(class InsertCodeCellBelowAction extends InsertCellCommand { title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated()), weight: KeybindingWeight.WorkbenchContrib }, menu: { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index 1ea7c34b6bc..351d117ed2f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -7,14 +7,13 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec import { autorun } from '../../../../../../base/common/observable.js'; import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { NotebookChatController } from '../../controller/chat/notebookChatController.js'; import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditorDelegate } from '../../notebookBrowser.js'; import { CellViewModelStateChangeEvent } from '../../notebookViewEvents.js'; import { CellContentPart } from '../cellPart.js'; import { CodeCellViewModel } from '../../viewModel/codeCellViewModel.js'; import { MarkupCellViewModel } from '../../viewModel/markupCellViewModel.js'; import { NotebookCellExecutionState } from '../../../common/notebookCommon.js'; -import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from '../../../common/notebookContextKeys.js'; +import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from '../../../common/notebookContextKeys.js'; import { INotebookExecutionStateService, NotebookExecutionType } from '../../../common/notebookExecutionStateService.js'; export class CellContextKeyPart extends CellContentPart { @@ -47,7 +46,6 @@ export class CellContextKeyManager extends Disposable { private cellOutputCollapsed!: IContextKey; private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>; private cellResource!: IContextKey; - private cellGeneratedByChat!: IContextKey; private cellHasErrorDiagnostics!: IContextKey; private markdownEditMode!: IContextKey; @@ -74,7 +72,6 @@ export class CellContextKeyManager extends Disposable { this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService); - this.cellGeneratedByChat = NOTEBOOK_CELL_GENERATED_BY_CHAT.bindTo(this._contextKeyService); this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService); this.cellHasErrorDiagnostics = NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS.bindTo(this._contextKeyService); @@ -121,21 +118,10 @@ export class CellContextKeyManager extends Disposable { this.updateForEditState(); this.updateForCollapseState(); this.updateForOutputs(); - this.updateForChat(); this.cellLineNumbers.set(this.element!.lineNumbers); this.cellResource.set(this.element!.uri.toString()); }); - - const chatController = NotebookChatController.get(this.notebookEditor); - - if (chatController) { - this.elementDisposables.add(chatController.onDidChangePromptCache(e => { - if (e.cell.toString() === this.element!.uri.toString()) { - this.updateForChat(); - } - })); - } } private onDidChangeState(e: CellViewModelStateChangeEvent) { @@ -236,15 +222,4 @@ export class CellContextKeyManager extends Disposable { this.cellHasOutputs.set(false); } } - - private updateForChat() { - const chatController = NotebookChatController.get(this.notebookEditor); - - if (!chatController || !this.element) { - this.cellGeneratedByChat.set(false); - return; - } - - this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element)); - } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index 57cb304453e..f4eb63f37dc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -55,7 +55,6 @@ export const NOTEBOOK_CELL_OUTPUT_MIMETYPE = new RawContextKey('notebook export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); export const NOTEBOOK_CELL_RESOURCE = new RawContextKey('notebookCellResource', ''); -export const NOTEBOOK_CELL_GENERATED_BY_CHAT = new RawContextKey('notebookCellGenerateByChat', false); export const NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS = new RawContextKey('notebookCellHasErrorDiagnostics', false); export const NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT = new RawContextKey('notebookCellOutputMimeTypeListForChat', []); From d9136e807b85c23a009c0937de5bb9b2019fa9fe Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:19:59 -0700 Subject: [PATCH 0865/4355] Enable WWW-Authenticate Step-up (#269884) * Enable WWW-Authenticate Step-up Fixes https://github.com/microsoft/vscode/issues/269883 * Add scopes back in --- src/vs/base/common/oauth.ts | 161 +++++++++++++++--- src/vs/base/test/common/oauth.test.ts | 12 ++ src/vs/workbench/api/browser/mainThreadMcp.ts | 5 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostMcp.ts | 71 +++++--- 5 files changed, 203 insertions(+), 48 deletions(-) diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index 4b332685a8c..db7d985de4c 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -259,6 +259,107 @@ export interface IAuthorizationServerMetadata { code_challenge_methods_supported?: string[]; } +/** + * Request for the dynamic client registration endpoint as per RFC 7591. + */ +export interface IAuthorizationDynamicClientRegistrationRequest { + /** + * OPTIONAL. Array of redirection URI strings for use in redirect-based flows + * such as the authorization code and implicit flows. + */ + redirect_uris?: string[]; + + /** + * OPTIONAL. String indicator of the requested authentication method for the token endpoint. + * Values: "none", "client_secret_post", "client_secret_basic". + * Default is "client_secret_basic". + */ + token_endpoint_auth_method?: string; + + /** + * OPTIONAL. Array of OAuth 2.0 grant type strings that the client can use at the token endpoint. + * Default is ["authorization_code"]. + */ + grant_types?: string[]; + + /** + * OPTIONAL. Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint. + * Default is ["code"]. + */ + response_types?: string[]; + + /** + * OPTIONAL. Human-readable string name of the client to be presented to the end-user during authorization. + */ + client_name?: string; + + /** + * OPTIONAL. URL string of a web page providing information about the client. + */ + client_uri?: string; + + /** + * OPTIONAL. URL string that references a logo for the client. + */ + logo_uri?: string; + + /** + * OPTIONAL. String containing a space-separated list of scope values that the client can use when requesting access tokens. + */ + scope?: string; + + /** + * OPTIONAL. Array of strings representing ways to contact people responsible for this client, typically email addresses. + */ + contacts?: string[]; + + /** + * OPTIONAL. URL string that points to a human-readable terms of service document for the client. + */ + tos_uri?: string; + + /** + * OPTIONAL. URL string that points to a human-readable privacy policy document. + */ + policy_uri?: string; + + /** + * OPTIONAL. URL string referencing the client's JSON Web Key (JWK) Set document. + */ + jwks_uri?: string; + + /** + * OPTIONAL. Client's JSON Web Key Set document value. + */ + jwks?: object; + + /** + * OPTIONAL. A unique identifier string assigned by the client developer or software publisher. + */ + software_id?: string; + + /** + * OPTIONAL. A version identifier string for the client software. + */ + software_version?: string; + + /** + * OPTIONAL. A software statement containing client metadata values about the client software as claims. + */ + software_statement?: string; + + /** + * OPTIONAL. Application type. Usually "native" for OAuth clients. + * https://openid.net/specs/openid-connect-registration-1_0.html + */ + application_type?: 'native' | 'web' | string; + + /** + * OPTIONAL. Additional metadata fields as defined by extensions. + */ + [key: string]: unknown; +} + /** * Response from the dynamic client registration endpoint. */ @@ -749,33 +850,35 @@ export async function fetchDynamicRegistration(serverMetadata: IAuthorizationSer if (!serverMetadata.registration_endpoint) { throw new Error('Server does not support dynamic registration'); } + + const requestBody: IAuthorizationDynamicClientRegistrationRequest = { + client_name: clientName, + client_uri: 'https://code.visualstudio.com', + grant_types: serverMetadata.grant_types_supported + ? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt)) + : grantTypesSupported, + response_types: ['code'], + redirect_uris: [ + 'https://insiders.vscode.dev/redirect', + 'https://vscode.dev/redirect', + 'http://127.0.0.1/', + // Added these for any server that might do + // only exact match on the redirect URI even + // though the spec says it should not care + // about the port. + `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` + ], + scope: scopes?.join(AUTH_SCOPE_SEPARATOR), + token_endpoint_auth_method: 'none', + application_type: 'native' + }; + const response = await fetch(serverMetadata.registration_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - client_name: clientName, - client_uri: 'https://code.visualstudio.com', - grant_types: serverMetadata.grant_types_supported - ? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt)) - : grantTypesSupported, - response_types: ['code'], - redirect_uris: [ - 'https://insiders.vscode.dev/redirect', - 'https://vscode.dev/redirect', - 'http://127.0.0.1/', - // Added these for any server that might do - // only exact match on the redirect URI even - // though the spec says it should not care - // about the port. - `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` - ], - scope: scopes?.join(AUTH_SCOPE_SEPARATOR), - token_endpoint_auth_method: 'none', - // https://openid.net/specs/openid-connect-registration-1_0.html - application_type: 'native' - }) + body: JSON.stringify(requestBody) }); if (!response.ok) { @@ -936,17 +1039,25 @@ export function getClaimsFromJWT(token: string): IAuthorizationJWTClaims { * Checks if two scope lists are equivalent, regardless of order. * This is useful for comparing OAuth scopes where the order should not matter. * - * @param scopes1 First list of scopes to compare - * @param scopes2 Second list of scopes to compare + * @param scopes1 First list of scopes to compare (can be undefined) + * @param scopes2 Second list of scopes to compare (can be undefined) * @returns true if the scope lists contain the same scopes (order-independent), false otherwise * * @example * ```typescript * scopesMatch(['read', 'write'], ['write', 'read']) // Returns: true * scopesMatch(['read'], ['write']) // Returns: false + * scopesMatch(undefined, undefined) // Returns: true + * scopesMatch(['read'], undefined) // Returns: false * ``` */ -export function scopesMatch(scopes1: readonly string[], scopes2: readonly string[]): boolean { +export function scopesMatch(scopes1: readonly string[] | undefined, scopes2: readonly string[] | undefined): boolean { + if (scopes1 === scopes2) { + return true; + } + if (!scopes1 || !scopes2) { + return false; + } if (scopes1.length !== scopes2.length) { return false; } diff --git a/src/vs/base/test/common/oauth.test.ts b/src/vs/base/test/common/oauth.test.ts index acdcc429a06..d846ac2d929 100644 --- a/src/vs/base/test/common/oauth.test.ts +++ b/src/vs/base/test/common/oauth.test.ts @@ -310,6 +310,18 @@ suite('OAuth', () => { const scopes2 = ['scope2', 'scope1', 'scope1']; assert.strictEqual(scopesMatch(scopes1, scopes2), true); }); + + test('scopesMatch should handle undefined values', () => { + assert.strictEqual(scopesMatch(undefined, undefined), true); + assert.strictEqual(scopesMatch(['read'], undefined), false); + assert.strictEqual(scopesMatch(undefined, ['write']), false); + }); + + test('scopesMatch should handle mixed undefined and empty arrays', () => { + assert.strictEqual(scopesMatch([], undefined), false); + assert.strictEqual(scopesMatch(undefined, []), false); + assert.strictEqual(scopesMatch([], []), true); + }); }); suite('Utility Functions', () => { diff --git a/src/vs/workbench/api/browser/mainThreadMcp.ts b/src/vs/workbench/api/browser/mainThreadMcp.ts index 2dcf4444bb0..3f1cc964e13 100644 --- a/src/vs/workbench/api/browser/mainThreadMcp.ts +++ b/src/vs/workbench/api/browser/mainThreadMcp.ts @@ -178,14 +178,13 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { this._servers.get(id)?.pushMessage(message); } - async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, errorOnUserInteraction?: boolean): Promise { + async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise { const server = this._serverDefinitions.get(id); if (!server) { return undefined; } - const authorizationServer = URI.revive(authServerComponents); - const scopesSupported = resourceMetadata?.scopes_supported || serverMetadata.scopes_supported || []; + const scopesSupported = scopes ?? resourceMetadata?.scopes_supported ?? serverMetadata.scopes_supported ?? []; let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer); if (!providerId) { const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, serverMetadata, resourceMetadata); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4821f351962..f7ca67e2a19 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3041,7 +3041,7 @@ export interface MainThreadMcpShape { $onDidReceiveMessage(id: number, message: string): void; $upsertMcpCollection(collection: McpCollectionDefinition.FromExtHost, servers: McpServerDefinition.Serialized[]): void; $deleteMcpCollection(collectionId: string): void; - $getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, errorOnUserInteraction?: boolean): Promise; + $getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise; } export interface MainThreadDataChannelsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 0c6baa4ede8..851551174d7 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -8,7 +8,7 @@ import { DeferredPromise, raceCancellationError, Sequencer, timeout } from '../. import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; import { CancellationError } from '../../../base/common/errors.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; -import { AUTH_SERVER_METADATA_DISCOVERY_PATH, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, isAuthorizationServerMetadata, OPENID_CONNECT_DISCOVERY_PATH, parseWWWAuthenticateHeader } from '../../../base/common/oauth.js'; +import { AUTH_SCOPE_SEPARATOR, AUTH_SERVER_METADATA_DISCOVERY_PATH, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, isAuthorizationServerMetadata, OPENID_CONNECT_DISCOVERY_PATH, parseWWWAuthenticateHeader, scopesMatch } from '../../../base/common/oauth.js'; import { SSEParser } from '../../../base/common/sseParser.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { ConfigurationTarget } from '../../../platform/configuration/common/configuration.js'; @@ -215,6 +215,7 @@ export class McpHTTPHandle extends Disposable { authorizationServer: URI; serverMetadata: IAuthorizationServerMetadata; resourceMetadata?: IAuthorizationProtectedResourceMetadata; + scopes?: string[]; }; constructor( @@ -337,22 +338,11 @@ export class McpHTTPHandle extends Disposable { private async _populateAuthMetadata(mcpUrl: string, originalResponse: CommonResponse): Promise { // If there is a resource_metadata challenge, use that to get the oauth server. This is done in 2 steps. // First, extract the resource_metada challenge from the WWW-Authenticate header (if available) - let resourceMetadataChallenge: string | undefined; - if (originalResponse.headers.has('WWW-Authenticate')) { - const authHeader = originalResponse.headers.get('WWW-Authenticate')!; - const challenges = parseWWWAuthenticateHeader(authHeader); - for (const challenge of challenges) { - if (challenge.scheme === 'Bearer' && challenge.params['resource_metadata']) { - this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${challenge.params['resource_metadata']}`); - resourceMetadataChallenge = challenge.params['resource_metadata']; - break; - } - } - } + const { resourceMetadataChallenge, scopesChallenge: scopesChallengeFromHeader } = this._parseWWWAuthenticateHeader(originalResponse); // Second, fetch the resource metadata either from the challenge URL or from well-known URIs let serverMetadataUrl: string | undefined; - let scopesSupported: string[] | undefined; let resource: IAuthorizationProtectedResourceMetadata | undefined; + let scopesChallenge = scopesChallengeFromHeader; try { const resourceMetadata = await fetchResourceMetadata(mcpUrl, resourceMetadataChallenge, { sameOriginHeaders: { @@ -365,7 +355,7 @@ export class McpHTTPHandle extends Disposable { // Consider using one that has an auth provider first, over the dynamic flow serverMetadataUrl = resourceMetadata.authorization_servers?.[0]; this._log(LogLevel.Debug, `Using auth server metadata url: ${serverMetadataUrl}`); - scopesSupported = resourceMetadata.scopes_supported; + scopesChallenge ??= resourceMetadata.scopes_supported; resource = resourceMetadata; } catch (e) { this._log(LogLevel.Debug, `Could not fetch resource metadata: ${String(e)}`); @@ -389,7 +379,8 @@ export class McpHTTPHandle extends Disposable { this._authMetadata = { authorizationServer: URI.parse(serverMetadataUrl), serverMetadata: serverMetadataResponse, - resourceMetadata: resource + resourceMetadata: resource, + scopes: scopesChallenge }; return; } catch (e) { @@ -398,11 +389,11 @@ export class McpHTTPHandle extends Disposable { // If there's no well-known server metadata, then use the default values based off of the url. const defaultMetadata = getDefaultMetadataForUrl(new URL(baseUrl)); - defaultMetadata.scopes_supported = scopesSupported ?? defaultMetadata.scopes_supported ?? []; this._authMetadata = { authorizationServer: URI.parse(baseUrl), serverMetadata: defaultMetadata, - resourceMetadata: resource + resourceMetadata: resource, + scopes: scopesChallenge }; this._log(LogLevel.Info, 'Using default auth metadata'); } @@ -663,7 +654,7 @@ export class McpHTTPHandle extends Disposable { private async _addAuthHeader(headers: Record) { if (this._authMetadata) { try { - const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata, this._errorOnUserInteraction); + const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata, this._authMetadata.scopes, this._errorOnUserInteraction); if (token) { headers['Authorization'] = `Bearer ${token}`; } @@ -684,6 +675,34 @@ export class McpHTTPHandle extends Disposable { } } + private _parseWWWAuthenticateHeader(response: CommonResponse): { resourceMetadataChallenge: string | undefined; scopesChallenge: string[] | undefined } { + let resourceMetadataChallenge: string | undefined; + let scopesChallenge: string[] | undefined; + if (response.headers.has('WWW-Authenticate')) { + const authHeader = response.headers.get('WWW-Authenticate')!; + const challenges = parseWWWAuthenticateHeader(authHeader); + for (const challenge of challenges) { + if (challenge.scheme === 'Bearer') { + if (!resourceMetadataChallenge && challenge.params['resource_metadata']) { + resourceMetadataChallenge = challenge.params['resource_metadata']; + this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${resourceMetadataChallenge}`); + } + if (!scopesChallenge && challenge.params['scope']) { + const scopes = challenge.params['scope'].split(AUTH_SCOPE_SEPARATOR); + if (scopes.length) { + this._log(LogLevel.Debug, `Found scope challenge in WWW-Authenticate header: ${challenge.params['scope']}`); + scopesChallenge = scopes; + } + } + if (resourceMetadataChallenge && scopesChallenge) { + break; + } + } + } + } + return { resourceMetadataChallenge, scopesChallenge }; + } + private async _getErrText(res: CommonResponse) { try { return await res.text(); @@ -696,6 +715,7 @@ export class McpHTTPHandle extends Disposable { * Helper method to perform fetch with 401 authentication retry logic. * If the initial request returns 401 and we don't have auth metadata, * it will populate the auth metadata and retry once. + * If we already have auth metadata, check if the scopes changed and update them. */ private async _fetchWithAuthRetry(mcpUrl: string, init: MinimalRequestInit, headers: Record): Promise { const doFetch = () => this._fetch(mcpUrl, init); @@ -710,6 +730,19 @@ export class McpHTTPHandle extends Disposable { init.headers = headers; res = await doFetch(); } + } else { + // We have auth metadata, but got a 401. Check if the scopes changed. + const { scopesChallenge } = this._parseWWWAuthenticateHeader(res); + if (!scopesMatch(scopesChallenge, this._authMetadata.scopes)) { + this._log(LogLevel.Debug, `Scopes changed from ${JSON.stringify(this._authMetadata.scopes)} to ${JSON.stringify(scopesChallenge)}, updating and retrying`); + this._authMetadata.scopes = scopesChallenge; + await this._addAuthHeader(headers); + if (headers['Authorization']) { + // Update the headers in the init object + init.headers = headers; + res = await doFetch(); + } + } } } return res; From 223e3f2f2a64f19fdc45886218a836b039b396c7 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:44:12 -0700 Subject: [PATCH 0866/4355] PR Feedback :lipstick: (#270106) * PR Feedback :lipstick: From https://github.com/microsoft/vscode/pull/269884 * Update src/vs/base/common/oauth.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/api/common/extHostMcp.ts --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/base/common/oauth.ts | 3 ++- src/vs/workbench/api/browser/mainThreadMcp.ts | 14 +++++++------- src/vs/workbench/api/common/extHostMcp.ts | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index db7d985de4c..3fd447667cc 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -260,7 +260,8 @@ export interface IAuthorizationServerMetadata { } /** - * Request for the dynamic client registration endpoint as per RFC 7591. + * Request for the dynamic client registration endpoint. + * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2 */ export interface IAuthorizationDynamicClientRegistrationRequest { /** diff --git a/src/vs/workbench/api/browser/mainThreadMcp.ts b/src/vs/workbench/api/browser/mainThreadMcp.ts index 3f1cc964e13..dda2b38aac5 100644 --- a/src/vs/workbench/api/browser/mainThreadMcp.ts +++ b/src/vs/workbench/api/browser/mainThreadMcp.ts @@ -184,7 +184,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { return undefined; } const authorizationServer = URI.revive(authServerComponents); - const scopesSupported = scopes ?? resourceMetadata?.scopes_supported ?? serverMetadata.scopes_supported ?? []; + const resolvedScopes = scopes ?? resourceMetadata?.scopes_supported ?? serverMetadata.scopes_supported ?? []; let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer); if (!providerId) { const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, serverMetadata, resourceMetadata); @@ -193,7 +193,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { } providerId = provider.id; } - const sessions = await this._authenticationService.getSessions(providerId, scopesSupported, { authorizationServer: authorizationServer }, true); + const sessions = await this._authenticationService.getSessions(providerId, resolvedScopes, { authorizationServer: authorizationServer }, true); const accountNamePreference = this.authenticationMcpServersService.getAccountPreference(server.id, providerId); let matchingAccountPreferenceSession: AuthenticationSession | undefined; if (accountNamePreference) { @@ -204,12 +204,12 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { if (sessions.length) { // If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function. if (matchingAccountPreferenceSession && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, matchingAccountPreferenceSession.account.label, server.id)) { - this.authenticationMCPServerUsageService.addAccountUsage(providerId, matchingAccountPreferenceSession.account.label, scopesSupported, server.id, server.label); + this.authenticationMCPServerUsageService.addAccountUsage(providerId, matchingAccountPreferenceSession.account.label, resolvedScopes, server.id, server.label); return matchingAccountPreferenceSession.accessToken; } // If we only have one account for a single auth provider, lets just check if it's allowed and return it if it is. if (!provider.supportsMultipleAccounts && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, sessions[0].account.label, server.id)) { - this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, scopesSupported, server.id, server.label); + this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, resolvedScopes, server.id, server.label); return sessions[0].accessToken; } } @@ -228,7 +228,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { throw new UserInteractionRequiredError('authentication'); } session = provider.supportsMultipleAccounts - ? await this.authenticationMcpServersService.selectSession(providerId, server.id, server.label, scopesSupported, sessions) + ? await this.authenticationMcpServersService.selectSession(providerId, server.id, server.label, resolvedScopes, sessions) : sessions[0]; } else { @@ -239,7 +239,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { do { session = await this._authenticationService.createSession( providerId, - scopesSupported, + resolvedScopes, { activateImmediate: true, account: accountToCreate, @@ -254,7 +254,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { this.authenticationMCPServerAccessService.updateAllowedMcpServers(providerId, session.account.label, [{ id: server.id, name: server.label, allowed: true }]); this.authenticationMcpServersService.updateAccountPreference(server.id, providerId, session.account); - this.authenticationMCPServerUsageService.addAccountUsage(providerId, session.account.label, scopesSupported, server.id, server.label); + this.authenticationMCPServerUsageService.addAccountUsage(providerId, session.account.label, resolvedScopes, server.id, server.label); return session.accessToken; } diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 851551174d7..457a8dea83f 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -688,7 +688,7 @@ export class McpHTTPHandle extends Disposable { this._log(LogLevel.Debug, `Found resource_metadata challenge in WWW-Authenticate header: ${resourceMetadataChallenge}`); } if (!scopesChallenge && challenge.params['scope']) { - const scopes = challenge.params['scope'].split(AUTH_SCOPE_SEPARATOR); + const scopes = challenge.params['scope'].split(AUTH_SCOPE_SEPARATOR).filter(s => s.trim().length); if (scopes.length) { this._log(LogLevel.Debug, `Found scope challenge in WWW-Authenticate header: ${challenge.params['scope']}`); scopesChallenge = scopes; From 26b72210d57d250147070b4ba1030c72ce86d316 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:52:17 -0700 Subject: [PATCH 0867/4355] chore: add tag for CG tracking (#270108) --- src/vs/base/browser/dompurify/cgmanifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/dompurify/cgmanifest.json b/src/vs/base/browser/dompurify/cgmanifest.json index 0038d843f74..c49d9232f08 100644 --- a/src/vs/base/browser/dompurify/cgmanifest.json +++ b/src/vs/base/browser/dompurify/cgmanifest.json @@ -6,7 +6,8 @@ "git": { "name": "dompurify", "repositoryUrl": "https://github.com/cure53/DOMPurify", - "commitHash": "15f54ed66d99a824d8d3030f711ff82af68ad191" + "commitHash": "15f54ed66d99a824d8d3030f711ff82af68ad191", + "tag": "3.1.7" } }, "license": "Apache 2.0", From fd10f9c20797e9569105052cb788ac34714615c5 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 7 Oct 2025 07:56:36 +0900 Subject: [PATCH 0868/4355] chore: reduce any usage in build/ (#270109) --- build/lib/fetch.js | 3 +-- build/lib/fetch.ts | 3 +-- build/linux/debian/install-sysroot.js | 3 +-- build/linux/debian/install-sysroot.ts | 3 +-- build/win32/explorer-dll-fetcher.js | 4 ++-- build/win32/explorer-dll-fetcher.ts | 12 +++++++++--- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/build/lib/fetch.js b/build/lib/fetch.js index 398860ac746..e4705101569 100644 --- a/build/lib/fetch.js +++ b/build/lib/fetch.js @@ -48,8 +48,7 @@ async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { try { const response = await fetch(url, { ...options.nodeFetchOptions, - // eslint-disable-next-line local/code-no-any-casts - signal: controller.signal /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (verbose) { (0, fancy_log_1.default)(`Fetch completed: Status ${response.status}. Took ${ansi_colors_1.default.magenta(`${new Date().getTime() - startTime} ms`)}`); diff --git a/build/lib/fetch.ts b/build/lib/fetch.ts index 81c715991c7..970887b3e55 100644 --- a/build/lib/fetch.ts +++ b/build/lib/fetch.ts @@ -54,8 +54,7 @@ export async function fetchUrl(url: string, options: IFetchOptions, retries = 10 try { const response = await fetch(url, { ...options.nodeFetchOptions, - // eslint-disable-next-line local/code-no-any-casts - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (verbose) { log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index 8ad94877788..1134130d780 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -73,8 +73,7 @@ async function fetchUrl(options, retries = 10, retryDelay = 1000) { try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, - // eslint-disable-next-line local/code-no-any-casts - signal: controller.signal /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (response.ok && (response.status >= 200 && response.status < 300)) { console.log(`Fetch completed: Status ${response.status}.`); diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index de0ba341eec..4b7ebd1b846 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -82,8 +82,7 @@ async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000) try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, - // eslint-disable-next-line local/code-no-any-casts - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (response.ok && (response.status >= 200 && response.status < 300)) { console.log(`Fetch completed: Status ${response.status}.`); diff --git a/build/win32/explorer-dll-fetcher.js b/build/win32/explorer-dll-fetcher.js index f7d7c49700d..1b160974324 100644 --- a/build/win32/explorer-dll-fetcher.js +++ b/build/win32/explorer-dll-fetcher.js @@ -13,6 +13,7 @@ const debug_1 = __importDefault(require("debug")); const path_1 = __importDefault(require("path")); const get_1 = require("@electron/get"); const product_json_1 = __importDefault(require("../../product.json")); +const product = product_json_1.default; const d = (0, debug_1.default)('explorer-dll-fetcher'); async function downloadExplorerDll(outDir, quality = 'stable', targetArch = 'x64') { const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code'; @@ -53,8 +54,7 @@ async function main(outputDir) { if (!outputDir) { throw new Error('Required build env not set'); } - // eslint-disable-next-line local/code-no-any-casts - await downloadExplorerDll(outputDir, product_json_1.default.quality, arch); + await downloadExplorerDll(outputDir, product.quality, arch); } if (require.main === module) { main(process.argv[2]).catch(err => { diff --git a/build/win32/explorer-dll-fetcher.ts b/build/win32/explorer-dll-fetcher.ts index 2c9868db7d2..33e21b4e4a8 100644 --- a/build/win32/explorer-dll-fetcher.ts +++ b/build/win32/explorer-dll-fetcher.ts @@ -9,7 +9,14 @@ import fs from 'fs'; import debug from 'debug'; import path from 'path'; import { downloadArtifact } from '@electron/get'; -import product from '../../product.json'; +import productJson from '../../product.json'; + +interface ProductConfiguration { + quality?: string; + [key: string]: unknown; +} + +const product: ProductConfiguration = productJson; const d = debug('explorer-dll-fetcher'); @@ -60,8 +67,7 @@ async function main(outputDir?: string): Promise { throw new Error('Required build env not set'); } - // eslint-disable-next-line local/code-no-any-casts - await downloadExplorerDll(outputDir, (product as any).quality, arch); + await downloadExplorerDll(outputDir, product.quality, arch); } if (require.main === module) { From 28a34f213fba4a31cb7dd973b0b383c807828f3e Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 6 Oct 2025 16:18:24 -0700 Subject: [PATCH 0869/4355] use `toLowerCase` instead of `toLocaleLowerCase` --- src/vs/workbench/contrib/chat/common/chatModes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index dde15ebd1e3..127242fe147 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -172,8 +172,8 @@ export class ChatModeService extends Disposable implements IChatModeService { } findModeByName(name: string): IChatMode | undefined { - const lowerCasedName = name.toLocaleLowerCase(); - return this.getBuiltinModes().find(mode => mode.name.toLocaleLowerCase() === lowerCasedName) ?? this.getCustomModes().find(mode => mode.name.toLocaleLowerCase() === lowerCasedName); + const lowerCasedName = name.toLowerCase(); + return this.getBuiltinModes().find(mode => mode.name.toLowerCase() === lowerCasedName) ?? this.getCustomModes().find(mode => mode.name.toLowerCase() === lowerCasedName); } private getBuiltinModes(): IChatMode[] { From b0f6eb283510fa627e0d42338d044a83652c13ea Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:38:43 -0700 Subject: [PATCH 0870/4355] Pick up more editor compile settings from `tsconfig.monaco` Right now for editor builds, we have the `target` defined in `tsconfig.monaco` and overrides in `standalone.js`. This second one is really easy to miss I think all of target/environment settings should be taking from the normal `tsconfig.monaco.json` instead --- build/lib/standalone.js | 2 -- build/lib/standalone.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 94aea68a9be..a56cbf3deff 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -75,13 +75,11 @@ function extractEditor(options) { } tsConfig.compilerOptions = compilerOptions; tsConfig.compilerOptions.sourceMap = true; - tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = options.tsOutDir; compilerOptions.noEmit = false; compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; - compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); // Take the extra included .d.ts files from `tsconfig.monaco.json` diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index b18908dcb03..9e44d52908d 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -42,14 +42,12 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str } tsConfig.compilerOptions = compilerOptions; tsConfig.compilerOptions.sourceMap = true; - tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = options.tsOutDir; compilerOptions.noEmit = false; compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; - compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; From 7ef8315e1db9f22a7d1b0c93b8ddf5f36bc6caca Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:45:45 -0700 Subject: [PATCH 0871/4355] Use `moduleResolution: classic` --- src/tsconfig.monaco.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index b9e497bf275..d1c93b6a672 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -9,7 +9,7 @@ ], "paths": {}, "module": "esnext", - "moduleResolution": "bundler", + "moduleResolution": "classic", "removeComments": false, "preserveConstEnums": true, "target": "ES2022", From 4b93e56560920692c2dcf8a87c65cbe888c15ab0 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 6 Oct 2025 18:09:45 -0700 Subject: [PATCH 0872/4355] feat: disable send button when input is empty --- .../chat/browser/actions/chatExecuteActions.ts | 11 +++++++++++ .../workbench/contrib/chat/browser/chatInputPart.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 684524f887f..92e5b2e6d68 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -162,6 +162,10 @@ export class ChatSubmitAction extends SubmitAction { constructor() { const menuCondition = ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask); + const precondition = ContextKeyExpr.and( + ChatContextKeys.inputHasText, + whenNotInProgress, + ); super({ id: ChatSubmitAction.ID, @@ -169,6 +173,7 @@ export class ChatSubmitAction extends SubmitAction { f1: false, category: CHAT_CATEGORY, icon: Codicon.send, + precondition, toggled: { condition: ChatContextKeys.lockedToCodingAgent, icon: Codicon.sendToRemoteAgent, @@ -507,6 +512,10 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { constructor() { const menuCondition = ChatContextKeys.chatModeKind.notEqualsTo(ChatModeKind.Ask); + const precondition = ContextKeyExpr.and( + ChatContextKeys.inputHasText, + whenNotInProgress + ); super({ id: ChatEditingSessionSubmitAction.ID, @@ -514,6 +523,7 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { f1: false, category: CHAT_CATEGORY, icon: Codicon.send, + precondition, menu: [ { id: MenuId.ChatExecuteSecondary, @@ -586,6 +596,7 @@ export class CreateRemoteAgentJobAction extends Action2 { constructor() { const precondition = ContextKeyExpr.and( + ChatContextKeys.inputHasText, whenNotInProgress, ChatContextKeys.remoteJobCreating.negate(), ); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 53fa57009d9..0f1dbae09ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1199,6 +1199,18 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.overflowWidgetsDomNode?.classList.add('hideSuggestTextIcons'); this._inputEditorElement.classList.add('hideSuggestTextIcons'); + // Prevent Enter key from creating new lines when input is empty + this._register(this._inputEditor.onKeyDown((e) => { + if (e.keyCode === KeyCode.Enter && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) { + const model = this._inputEditor.getModel(); + const inputHasText = !!model && model.getValue().trim().length > 0; + if (!inputHasText) { + e.preventDefault(); + e.stopPropagation(); + } + } + })); + this._register(this._inputEditor.onDidChangeModelContent(() => { const currentHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); if (currentHeight !== this.inputEditorHeight) { From 953a0432012aa223baf738a442fbde1d64dd4ced Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 6 Oct 2025 21:13:18 -0700 Subject: [PATCH 0873/4355] mcp: fix sampling config for user servers overwriting (#270125) Fixes #264853 --- src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts index d8b1dc0348e..adfb958e818 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpSamplingService.ts @@ -282,7 +282,7 @@ export class McpSamplingService extends Disposable implements IMcpSamplingServic } } - return { value: undefined, mapping: undefined, key, target: leastSpecificConfig, resource }; + return { value: undefined, mapping: getConfigValueInTarget(configValue, leastSpecificConfig), key, target: leastSpecificConfig, resource }; } public async updateConfig(server: IMcpServer, mutate: (r: IMcpServerSamplingConfiguration) => unknown) { From 856f1dee21539d1de55baa14d3ad35b335367dbc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:47:45 -0700 Subject: [PATCH 0874/4355] Make `ICommandHandler` default to `...args: unknown[]` This catches cases where the command definition doesn't specify any parameter types. I also updated `registerCommand` to not produce errors for any handlers that do have explicit types --- src/vs/platform/commands/common/commands.ts | 12 +++++------- .../keybinding/common/keybindingsRegistry.ts | 6 +++--- .../workbench/contrib/debug/browser/debugCommands.ts | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index cb3546671b0..f7b45e5fa48 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -28,13 +28,11 @@ export interface ICommandService { export type ICommandsMap = Map; -export interface ICommandHandler { - (accessor: ServicesAccessor, ...args: any[]): void; -} +export type ICommandHandler = (accessor: ServicesAccessor, ...args: Args) => void; -export interface ICommand { +export interface ICommand { id: string; - handler: ICommandHandler; + handler: ICommandHandler; metadata?: ICommandMetadata | null; } @@ -59,8 +57,8 @@ export interface ICommandMetadata { export interface ICommandRegistry { onDidRegisterCommand: Event; - registerCommand(id: string, command: ICommandHandler): IDisposable; - registerCommand(command: ICommand): IDisposable; + registerCommand(id: string, command: ICommandHandler): IDisposable; + registerCommand(command: ICommand): IDisposable; registerCommandAlias(oldId: string, newId: string): IDisposable; getCommand(id: string): ICommand | undefined; getCommands(): ICommandsMap; diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index eeabcc5578c..05660234e61 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -67,15 +67,15 @@ export const enum KeybindingWeight { ExternalExtension = 400 } -export interface ICommandAndKeybindingRule extends IKeybindingRule { - handler: ICommandHandler; +export interface ICommandAndKeybindingRule extends IKeybindingRule { + handler: ICommandHandler; metadata?: ICommandMetadata | null; } export interface IKeybindingsRegistry { registerKeybindingRule(rule: IKeybindingRule): IDisposable; setExtensionKeybindings(rules: IExtensionKeybindingRule[]): void; - registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): IDisposable; + registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): IDisposable; getDefaultKeybindings(): IKeybindingItem[]; } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 7714b20cab7..21e23e4d98f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -625,7 +625,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -async function stopHandler(accessor: ServicesAccessor, _: string, context: CallStackContext | unknown, disconnect: boolean, suspend?: boolean): Promise { +async function stopHandler(accessor: ServicesAccessor, _: unknown, context: CallStackContext | unknown, disconnect: boolean, suspend?: boolean): Promise { const debugService = accessor.get(IDebugService); let session: IDebugSession | undefined; if (isSessionContext(context)) { From d54f6fc1556839d1564ba4382b3b05d88d4628e9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Oct 2025 09:29:12 +0200 Subject: [PATCH 0875/4355] debt - reduce `any` type (#270139) --- .github/CODENOTIFY | 1 + .vscode/searches/no-any-casts.code-search | 178 ++---------------- eslint.config.js | 3 +- src/vs/base/common/process.ts | 3 +- src/vs/base/parts/ipc/common/ipc.ts | 3 +- src/vs/base/parts/ipc/node/ipc.net.ts | 55 +++--- src/vs/platform/dnd/browser/dnd.ts | 14 +- src/vs/platform/product/common/product.ts | 7 +- src/vs/server/node/server.main.ts | 3 +- .../browser/actions/developerActions.ts | 7 +- .../workbench/browser/actions/listCommands.ts | 4 +- .../actions/widgetNavigationCommands.ts | 6 +- .../browser/actions/workspaceCommands.ts | 8 +- src/vs/workbench/browser/codeeditor.ts | 2 +- src/vs/workbench/browser/layout.ts | 6 +- .../parts/editor/breadcrumbsControl.ts | 8 +- .../browser/parts/editor/breadcrumbsModel.ts | 6 +- .../browser/parts/editor/breadcrumbsPicker.ts | 30 +-- .../parts/editor/diffEditorCommands.ts | 12 +- .../browser/parts/editor/editorPane.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 5 +- .../browser/parts/titlebar/menubarControl.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 6 +- .../workbench/browser/parts/views/treeView.ts | 2 +- .../browser/parts/views/viewFilter.ts | 4 +- .../browser/parts/views/viewPaneContainer.ts | 3 +- src/vs/workbench/browser/web.api.ts | 8 +- src/vs/workbench/browser/web.factory.ts | 2 +- src/vs/workbench/browser/window.ts | 6 +- .../browser/workbench.contribution.ts | 12 +- src/vs/workbench/browser/workbench.ts | 7 +- .../chatMcpServersInteractionContentPart.ts | 3 +- .../chatMultiDiffContentPart.ts | 3 +- .../contrib/chat/browser/chatViewPane.ts | 3 +- .../contrib/chat/common/chatModes.ts | 3 +- .../languageConfigurationExtensionPoint.ts | 3 +- .../contrib/timeline/browser/timelinePane.ts | 3 +- 37 files changed, 146 insertions(+), 289 deletions(-) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 8bc2fe18cdc..d4b5414e616 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -5,6 +5,7 @@ src/vs/base/common/glob.ts @bpasero src/vs/base/common/path.ts @bpasero src/vs/base/common/stream.ts @bpasero src/vs/base/browser/domSanitize.ts @mjbvz +src/vs/base/browser/** @bpasero src/vs/base/node/pfs.ts @bpasero src/vs/base/node/unc.ts @bpasero src/vs/base/parts/contextmenu/** @bpasero diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index 0c87e934d05..7eda54ff93b 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,6 +1,6 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -1052 results - 407 files +990 results - 368 files vscode • build/azure-pipelines/upload-cdn.ts: 115: // eslint-disable-next-line local/code-no-any-casts @@ -15,9 +15,6 @@ vscode • build/lib/extensions.ts: 120: // eslint-disable-next-line local/code-no-any-casts 219: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/fetch.ts: - 57: // eslint-disable-next-line local/code-no-any-casts - vscode • build/lib/monaco-api.ts: 221: // eslint-disable-next-line local/code-no-any-casts 328: // eslint-disable-next-line local/code-no-any-casts @@ -74,12 +71,6 @@ vscode • build/lib/tsb/index.ts: vscode • build/lib/watch/watch-win32.ts: 50: // eslint-disable-next-line local/code-no-any-casts -vscode • build/linux/debian/install-sysroot.ts: - 85: // eslint-disable-next-line local/code-no-any-casts - -vscode • build/win32/explorer-dll-fetcher.ts: - 63: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/css-language-features/client/src/cssClient.ts: 86: // eslint-disable-next-line local/code-no-any-casts @@ -88,52 +79,17 @@ vscode • extensions/css-language-features/server/src/cssServer.ts: 74: // eslint-disable-next-line local/code-no-any-casts 171: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/emmet/src/emmetCommon.ts: - 187: // eslint-disable-next-line local/code-no-any-casts - 189: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/emmet/src/updateImageSize.ts: - 276: // eslint-disable-next-line local/code-no-any-casts - 278: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/emmet/src/util.ts: - 357: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/git/src/commands.ts: - 2384: // eslint-disable-next-line local/code-no-any-casts - 2387: // eslint-disable-next-line local/code-no-any-casts - 5373: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/git/src/emoji.ts: - 27: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/git/src/git.ts: - 311: // eslint-disable-next-line local/code-no-any-casts - 2976: // eslint-disable-next-line local/code-no-any-casts - 2978: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/git/src/main.ts: - 87: // eslint-disable-next-line local/code-no-any-casts + 5369: // eslint-disable-next-line local/code-no-any-casts vscode • extensions/git/src/util.ts: - 42: // eslint-disable-next-line local/code-no-any-casts - 114: // eslint-disable-next-line local/code-no-any-casts - 241: // eslint-disable-next-line local/code-no-any-casts + 46: // eslint-disable-next-line local/code-no-any-casts + 118: // eslint-disable-next-line local/code-no-any-casts vscode • extensions/git-base/src/api/api1.ts: 17: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/github-authentication/src/node/crypto.ts: - 8: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/grunt/src/main.ts: - 123: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/gulp/src/main.ts: - 153: // eslint-disable-next-line local/code-no-any-casts - 156: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/html-language-features/client/src/htmlClient.ts: 182: // eslint-disable-next-line local/code-no-any-casts @@ -175,10 +131,6 @@ vscode • extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 424: // eslint-disable-next-line local/code-no-any-casts 459: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/jake/src/main.ts: - 123: // eslint-disable-next-line local/code-no-any-casts - 126: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/json-language-features/client/src/jsonClient.ts: 775: // eslint-disable-next-line local/code-no-any-casts @@ -199,9 +151,6 @@ vscode • extensions/markdown-language-features/src/markdownEngine.ts: vscode • extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: 53: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/microsoft-authentication/src/common/telemetryReporter.ts: - 97: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/notebook-renderers/src/index.ts: 68: // eslint-disable-next-line local/code-no-any-casts @@ -209,10 +158,6 @@ vscode • extensions/notebook-renderers/src/test/notebookRenderer.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/npm/src/tasks.ts: - 61: // eslint-disable-next-line local/code-no-any-casts - 64: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/vscode-api-tests/src/extension.ts: 10: // eslint-disable-next-line local/code-no-any-casts @@ -264,20 +209,19 @@ vscode • src/vs/base/browser/deviceAccess.ts: 92: // eslint-disable-next-line local/code-no-any-casts vscode • src/vs/base/browser/dom.ts: - 430: // eslint-disable-next-line local/code-no-any-casts - 719: // eslint-disable-next-line local/code-no-any-casts - 1325: // eslint-disable-next-line local/code-no-any-casts - 1520: // eslint-disable-next-line local/code-no-any-casts - 1660: // eslint-disable-next-line local/code-no-any-casts - 2013: // eslint-disable-next-line local/code-no-any-casts - 2116: // eslint-disable-next-line local/code-no-any-casts - 2128: // eslint-disable-next-line local/code-no-any-casts - 2291: // eslint-disable-next-line local/code-no-any-casts - 2297: // eslint-disable-next-line local/code-no-any-casts - 2325: // eslint-disable-next-line local/code-no-any-casts - 2437: // eslint-disable-next-line local/code-no-any-casts - 2444: // eslint-disable-next-line local/code-no-any-casts - 2529: // eslint-disable-next-line local/code-no-any-casts + 718: // eslint-disable-next-line local/code-no-any-casts + 1324: // eslint-disable-next-line local/code-no-any-casts + 1519: // eslint-disable-next-line local/code-no-any-casts + 1659: // eslint-disable-next-line local/code-no-any-casts + 2012: // eslint-disable-next-line local/code-no-any-casts + 2115: // eslint-disable-next-line local/code-no-any-casts + 2127: // eslint-disable-next-line local/code-no-any-casts + 2290: // eslint-disable-next-line local/code-no-any-casts + 2296: // eslint-disable-next-line local/code-no-any-casts + 2324: // eslint-disable-next-line local/code-no-any-casts + 2436: // eslint-disable-next-line local/code-no-any-casts + 2443: // eslint-disable-next-line local/code-no-any-casts + 2528: // eslint-disable-next-line local/code-no-any-casts vscode • src/vs/base/browser/mouseEvent.ts: 100: // eslint-disable-next-line local/code-no-any-casts @@ -362,9 +306,6 @@ vscode • src/vs/base/common/network.ts: vscode • src/vs/base/common/objects.ts: 75: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/process.ts: - 12: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/base/common/skipList.ts: 38: // eslint-disable-next-line local/code-no-any-casts 47: // eslint-disable-next-line local/code-no-any-casts @@ -432,22 +373,12 @@ vscode • src/vs/base/common/observableInternal/utils/utilsCancellation.ts: vscode • src/vs/base/node/ports.ts: 182: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/parts/ipc/common/ipc.ts: - 599: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/base/parts/ipc/node/ipc.net.ts: - 730: // eslint-disable-next-line local/code-no-any-casts - 784: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/base/parts/ipc/test/node/ipc.net.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts 652: // eslint-disable-next-line local/code-no-any-casts 785: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/browser/dom.test.ts: - 407: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/base/test/common/buffer.test.ts: 501: // eslint-disable-next-line local/code-no-any-casts @@ -470,7 +401,7 @@ vscode • src/vs/base/test/common/mock.ts: 22: // eslint-disable-next-line local/code-no-any-casts vscode • src/vs/base/test/common/oauth.test.ts: - 1087: // eslint-disable-next-line local/code-no-any-casts + 1099: // eslint-disable-next-line local/code-no-any-casts vscode • src/vs/base/test/common/snapshot.ts: 123: // eslint-disable-next-line local/code-no-any-casts @@ -560,9 +491,6 @@ vscode • src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree 177: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 196: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/contrib/codeAction/browser/codeActionController.ts: - 113: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: 100: // eslint-disable-next-line local/code-no-any-casts @@ -731,10 +659,6 @@ vscode • src/vs/platform/contextkey/test/common/contextkey.test.ts: vscode • src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts: 93: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/dnd/browser/dnd.ts: - 469: // eslint-disable-next-line local/code-no-any-casts - 471: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/platform/environment/test/node/argv.test.ts: 47: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts @@ -758,20 +682,9 @@ vscode • src/vs/platform/files/test/node/diskFileService.integrationTest.ts: 109: // eslint-disable-next-line local/code-no-any-casts 112: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/instantiation/common/instantiation.ts: - 93: // eslint-disable-next-line local/code-no-any-casts - 95: // eslint-disable-next-line local/code-no-any-casts - 98: // eslint-disable-next-line local/code-no-any-casts - 100: // eslint-disable-next-line local/code-no-any-casts - 114: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/platform/instantiation/common/instantiationService.ts: 328: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/instantiation/test/common/instantiationServiceMock.ts: - 61: // eslint-disable-next-line local/code-no-any-casts - 164: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/platform/list/browser/listService.ts: 877: // eslint-disable-next-line local/code-no-any-casts 918: // eslint-disable-next-line local/code-no-any-casts @@ -794,10 +707,6 @@ vscode • src/vs/platform/observable/common/wrapInReloadableClass.ts: vscode • src/vs/platform/policy/node/nativePolicyService.ts: 47: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/product/common/product.ts: - 18: // eslint-disable-next-line local/code-no-any-casts - 59: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/platform/profiling/common/profilingTelemetrySpec.ts: 73: // eslint-disable-next-line local/code-no-any-casts @@ -849,9 +758,6 @@ vscode • src/vs/server/node/remoteExtensionHostAgentServer.ts: 767: // eslint-disable-next-line local/code-no-any-casts 769: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/server/node/server.main.ts: - 20: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/workbench.web.main.internal.ts: 198: // eslint-disable-next-line local/code-no-any-casts 223: // eslint-disable-next-line local/code-no-any-casts @@ -1113,28 +1019,8 @@ vscode • src/vs/workbench/api/worker/extensionHostWorker.ts: vscode • src/vs/workbench/api/worker/extHostConsoleForwarder.ts: 20: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/browser/layout.ts: - 428: // eslint-disable-next-line local/code-no-any-casts - 430: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/browser/window.ts: - 145: // eslint-disable-next-line local/code-no-any-casts - 155: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/browser/workbench.ts: - 208: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/browser/actions/developerActions.ts: - 779: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/browser/parts/editor/editorStatus.ts: - 1377: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/browser/parts/titlebar/titlebarPart.ts: - 494: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/browser/parts/views/viewPaneContainer.ts: - 687: // eslint-disable-next-line local/code-no-any-casts + 781: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any vscode • src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: 54: // eslint-disable-next-line local/code-no-any-casts @@ -1143,15 +1029,6 @@ vscode • src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: 29: // eslint-disable-next-line local/code-no-any-casts 39: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/browser/chatViewPane.ts: - 304: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts: - 125: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts: - 244: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: 88: // eslint-disable-next-line local/code-no-any-casts 90: // eslint-disable-next-line local/code-no-any-casts @@ -1168,9 +1045,6 @@ vscode • src/vs/workbench/contrib/chat/common/chatModel.ts: 1531: // eslint-disable-next-line local/code-no-any-casts 1863: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/common/chatModes.ts: - 236: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: 439: // eslint-disable-next-line local/code-no-any-casts @@ -1202,11 +1076,6 @@ vscode • src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts 35: // eslint-disable-next-line local/code-no-any-casts 144: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts: - 981: // eslint-disable-next-line local/code-no-any-casts - 1012: // eslint-disable-next-line local/code-no-any-casts - 1519: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 48: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1232,15 +1101,9 @@ vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 333: // eslint-disable-next-line local/code-no-any-casts 349: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/common/chatService.test.ts: - 149: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts: - 168: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: 189: // eslint-disable-next-line local/code-no-any-casts 196: // eslint-disable-next-line local/code-no-any-casts @@ -1688,9 +1551,6 @@ vscode • src/vs/workbench/contrib/testing/test/common/testStubs.ts: vscode • src/vs/workbench/contrib/themes/browser/themes.contribution.ts: 614: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/timeline/browser/timelinePane.ts: - 824: // eslint-disable-next-line local/code-no-any-casts - vscode • src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: 68: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts diff --git a/eslint.config.js b/eslint.config.js index 7790d8f5464..f4367638adb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -206,7 +206,6 @@ export default tseslint.config( 'src/vs/base/parts/contextmenu/**', 'src/vs/editor/browser/**', 'src/vs/editor/common/**', - // 'src/vs/base/parts/ipc/**', 'src/vs/base/parts/sandbox/**', 'src/vs/base/parts/storage/**', 'src/vs/platform/auxiliaryWindow/**', @@ -266,7 +265,7 @@ export default tseslint.config( 'src/vs/workbench/services/workingCopy/**', 'src/vs/workbench/services/workspaces/**', 'src/vs/workbench/common/**', - // 'src/vs/workbench/browser/**', + 'src/vs/workbench/browser/**', 'src/vs/workbench/electron-browser/**', 'src/vs/workbench/contrib/files/**', 'src/vs/workbench/contrib/chat/browser/chatSetup.ts', diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 885ecfa3fdc..b5ac53c0c07 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -9,8 +9,7 @@ let safeProcess: Omit & { arch: string | undefined }; declare const process: INodeProcess; // Native sandbox environment -// eslint-disable-next-line local/code-no-any-casts -const vscodeGlobal = (globalThis as any).vscode; +const vscodeGlobal = (globalThis as { vscode?: { process?: INodeProcess } }).vscode; if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { const sandboxProcess: INodeProcess = vscodeGlobal.process; safeProcess = { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 5d3f878144c..093726c2b74 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -596,8 +596,7 @@ export class ChannelClient implements IChannelClient, IDisposable { case ResponseType.PromiseError: { this.handlers.delete(id); const error = new Error(response.data.message); - // eslint-disable-next-line local/code-no-any-casts - (error).stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; + error.stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; error.name = response.data.name; e(error); break; diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 3d645af32dc..e375c62d979 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -95,12 +95,12 @@ export class NodeSocket implements ISocket { public readonly debugLabel: string; public readonly socket: Socket; - private readonly _errorListener: (err: any) => void; + private readonly _errorListener: (err: NodeJS.ErrnoException) => void; private readonly _closeListener: (hadError: boolean) => void; private readonly _endListener: () => void; private _canWrite = true; - public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void { SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data); } @@ -108,7 +108,7 @@ export class NodeSocket implements ISocket { this.debugLabel = debugLabel; this.socket = socket; this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'NodeSocket' }); - this._errorListener = (err: any) => { + this._errorListener = (err: NodeJS.ErrnoException) => { this.traceSocketEvent(SocketDiagnosticsEventType.Error, { code: err?.code, message: err?.message }); if (err) { if (err.code === 'EPIPE') { @@ -198,7 +198,7 @@ export class NodeSocket implements ISocket { // > accept and buffer chunk even if it has not been allowed to drain. try { this.traceSocketEvent(SocketDiagnosticsEventType.Write, buffer); - this.socket.write(buffer.buffer, (err: any) => { + this.socket.write(buffer.buffer, (err: NodeJS.ErrnoException | null | undefined) => { if (err) { if (err.code === 'EPIPE') { // An EPIPE exception at the wrong time can lead to a renderer process crash @@ -277,7 +277,7 @@ const enum ReadState { } interface ISocketTracer { - traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; + traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void; } interface FrameOptions { @@ -315,7 +315,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT return this._flowManager.recordedInflateBytes; } - public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void { this.socket.traceSocketEvent(type, data); } @@ -726,9 +726,8 @@ class ZlibInflateStream extends Disposable { ) { super(); this._zlibInflate = createInflateRaw(options); - this._zlibInflate.on('error', (err) => { - // eslint-disable-next-line local/code-no-any-casts - this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); + this._zlibInflate.on('error', (err: Error) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err as NodeJS.ErrnoException)?.code }); this._onError.fire(err); }); this._zlibInflate.on('data', (data: Buffer) => { @@ -780,9 +779,8 @@ class ZlibDeflateStream extends Disposable { this._zlibDeflate = createDeflateRaw({ windowBits: 15 }); - this._zlibDeflate.on('error', (err) => { - // eslint-disable-next-line local/code-no-any-casts - this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); + this._zlibDeflate.on('error', (err: Error) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err as NodeJS.ErrnoException)?.code }); this._onError.fire(err); }); this._zlibDeflate.on('data', (data: Buffer) => { @@ -932,28 +930,35 @@ export class Server extends IPCServer { export function serve(port: number): Promise; export function serve(namedPipe: string): Promise; -export function serve(hook: any): Promise { - return new Promise((c, e) => { +export function serve(hook: number | string): Promise { + return new Promise((resolve, reject) => { const server = createServer(); - server.on('error', e); + server.on('error', reject); server.listen(hook, () => { - server.removeListener('error', e); - c(new Server(server)); + server.removeListener('error', reject); + resolve(new Server(server)); }); }); } export function connect(options: { host: string; port: number }, clientId: string): Promise; -export function connect(port: number, clientId: string): Promise; export function connect(namedPipe: string, clientId: string): Promise; -export function connect(hook: any, clientId: string): Promise { - return new Promise((c, e) => { - const socket = createConnection(hook, () => { - socket.removeListener('error', e); - c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); - }); +export function connect(hook: { host: string; port: number } | string, clientId: string): Promise { + return new Promise((resolve, reject) => { + let socket: Socket; + + const callbackHandler = () => { + socket.removeListener('error', reject); + resolve(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); + }; + + if (typeof hook === 'string') { + socket = createConnection(hook, callbackHandler); + } else { + socket = createConnection(hook, callbackHandler); + } - socket.once('error', e); + socket.once('error', reject); }); } diff --git a/src/vs/platform/dnd/browser/dnd.ts b/src/vs/platform/dnd/browser/dnd.ts index e9de36f33f9..c3d84d46232 100644 --- a/src/vs/platform/dnd/browser/dnd.ts +++ b/src/vs/platform/dnd/browser/dnd.ts @@ -461,15 +461,21 @@ export function extractNotebookCellOutputDropData(e: DragEvent): NotebookCellOut return getDataAsJSON(e, CodeDataTransfers.NOTEBOOK_CELL_OUTPUT, undefined); } +interface IElectronWebUtils { + vscode?: { + webUtils?: { + getPathForFile(file: File): string; + }; + }; +} + /** * A helper to get access to Electrons `webUtils.getPathForFile` function * in a safe way without crashing the application when running in the web. */ export function getPathForFile(file: File): string | undefined { - // eslint-disable-next-line local/code-no-any-casts - if (isNative && typeof (globalThis as any).vscode?.webUtils?.getPathForFile === 'function') { - // eslint-disable-next-line local/code-no-any-casts - return (globalThis as any).vscode.webUtils.getPathForFile(file); + if (isNative && typeof (globalThis as IElectronWebUtils).vscode?.webUtils?.getPathForFile === 'function') { + return (globalThis as IElectronWebUtils).vscode?.webUtils?.getPathForFile(file); } return undefined; diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 4dac23b9ae3..6f093e9be94 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -15,8 +15,7 @@ import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandbo let product: IProductConfiguration; // Native sandbox environment -// eslint-disable-next-line local/code-no-any-casts -const vscodeGlobal = (globalThis as any).vscode; +const vscodeGlobal = (globalThis as { vscode?: { context?: { configuration(): ISandboxConfiguration | undefined } } }).vscode; if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.context !== 'undefined') { const configuration: ISandboxConfiguration | undefined = vscodeGlobal.context.configuration(); if (configuration) { @@ -56,8 +55,8 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { else { // Built time configuration (do NOT modify) - // eslint-disable-next-line local/code-no-any-casts - product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as any; + // eslint-disable-next-line local/code-no-dangerous-type-assertions + product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as unknown as IProductConfiguration; // Running out of sources if (Object.keys(product).length === 0) { diff --git a/src/vs/server/node/server.main.ts b/src/vs/server/node/server.main.ts index 843817c6787..0d1a1ddd810 100644 --- a/src/vs/server/node/server.main.ts +++ b/src/vs/server/node/server.main.ts @@ -17,8 +17,7 @@ import product from '../../platform/product/common/product.js'; import * as perf from '../../base/common/performance.js'; perf.mark('code/server/codeLoaded'); -// eslint-disable-next-line local/code-no-any-casts -(global).vscodeServerCodeLoadedTime = performance.now(); +(global as unknown as { vscodeServerCodeLoadedTime?: number }).vscodeServerCodeLoadedTime = performance.now(); const errorReporter: ErrorReporter = { onMultipleValues: (id: string, usedValue: string) => { diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index a9e5dc08d17..055d2df357d 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -373,7 +373,7 @@ class ToggleScreencastModeAction extends Action2 { ToggleScreencastModeAction.disposable = disposables; } - private _isKbFound(resolutionResult: ResolutionResult): resolutionResult is { kind: ResultKind.KbFound; commandId: string | null; commandArgs: any; isBubble: boolean } { + private _isKbFound(resolutionResult: ResolutionResult): resolutionResult is { kind: ResultKind.KbFound; commandId: string | null; commandArgs: unknown; isBubble: boolean } { return resolutionResult.kind === ResultKind.KbFound; } @@ -745,7 +745,9 @@ class PolicyDiagnosticsAction extends Action2 { const excludedProperties = configurationRegistry.getExcludedConfigurationProperties(); if (policyConfigurations.size > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const appliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const notAppliedPolicy: Array<{ name: string; key: string; property: any; inspection: any }> = []; for (const [policyName, settingKey] of policyConfigurations) { @@ -776,9 +778,10 @@ class PolicyDiagnosticsAction extends Action2 { try { const policyServiceConstructorName = policyService.constructor.name; if (policyServiceConstructorName === 'MultiplexPolicyService') { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const multiplexService = policyService as any; if (multiplexService.policyServices) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const componentServices = multiplexService.policyServices as ReadonlyArray; for (const service of componentServices) { if (service.getPolicyValue && service.getPolicyValue(policyName) !== undefined) { diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 8a003981985..54f9c844d23 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -256,7 +256,7 @@ function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unkno } } -function revealFocusedStickyScroll(tree: ObjectTree | DataTree | AsyncDataTree, postRevealAction?: (focus: any) => void): void { +function revealFocusedStickyScroll(tree: ObjectTree | DataTree | AsyncDataTree, postRevealAction?: (focus: unknown) => void): void { const focus = tree.getStickyScrollFocus(); if (focus.length === 0) { @@ -700,7 +700,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyI), when: WorkbenchListFocusContextKey, - handler: async (accessor: ServicesAccessor, ...args: any[]) => { + handler: async (accessor: ServicesAccessor) => { const listService = accessor.get(IListService); const lastFocusedList = listService.lastFocusedList; if (!lastFocusedList) { diff --git a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts index 3ce130c2c32..4239c6c81d6 100644 --- a/src/vs/workbench/browser/actions/widgetNavigationCommands.ts +++ b/src/vs/workbench/browser/actions/widgetNavigationCommands.ts @@ -33,8 +33,8 @@ interface INavigableContainer { } interface IFocusNotifier { - readonly onDidFocus: Event; - readonly onDidBlur: Event; + readonly onDidFocus: Event; + readonly onDidBlur: Event; } function handleFocusEventsGroup(group: readonly IFocusNotifier[], handler: (isFocus: boolean) => void, onPartFocusChange?: (index: number, state: string) => void): IDisposable { @@ -88,7 +88,7 @@ class NavigableContainerManager implements IDisposable { return this.configurationService.getValue('workbench.navigibleContainer.enableDebug'); } - private log(msg: string, ...args: any[]): void { + private log(msg: string, ...args: unknown[]): void { if (this.debugEnabled) { this.logService.debug(msg, ...args); } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index c9a3c2ddd5a..8904b2620b6 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -207,7 +207,7 @@ CommandsRegistry.registerCommand({ args: [ { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', - constraint: (value: any) => value === undefined || value === null || value instanceof URI + constraint: (value: unknown) => value === undefined || value === null || value instanceof URI }, { name: 'options', @@ -220,7 +220,7 @@ CommandsRegistry.registerCommand({ '`forceTempProfile`: Whether to use a temporary profile when opening the folder/workspace. Defaults to false. ' + '`filesToOpen`: An array of files to open in the new window. Defaults to an empty array. ' + 'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', - constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' + constraint: (value: unknown) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } ] } @@ -254,7 +254,7 @@ CommandsRegistry.registerCommand({ name: 'options', description: '(optional) Options. Object with the following properties: ' + '`reuseWindow`: Whether to open a new window or the same. Defaults to opening in a new window. ', - constraint: (value: any) => value === undefined || typeof value === 'object' + constraint: (value: unknown) => value === undefined || typeof value === 'object' } ] } @@ -283,7 +283,7 @@ CommandsRegistry.registerCommand({ metadata: { description: 'Removes an entry with the given path from the recently opened list.', args: [ - { name: 'path', description: 'URI or URI string to remove from recently opened.', constraint: (value: any) => typeof value === 'string' || value instanceof URI } + { name: 'path', description: 'URI or URI string to remove from recently opened.', constraint: (value: unknown) => typeof value === 'string' || value instanceof URI } ] } }); diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index 437e01b37a6..a6fed60780e 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -54,7 +54,7 @@ export class RangeHighlightDecorations extends Disposable { this.rangeHighlightDecorationId = null; } - highlightRange(range: IRangeHighlightDecoration, editor?: any) { + highlightRange(range: IRangeHighlightDecoration, editor?: unknown) { editor = editor ?? this.getEditor(range); if (isCodeEditor(editor)) { this.doHighlightRange(editor, range); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6dd113b5524..0e400bf0567 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -425,10 +425,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(this.hostService.onDidChangeActiveWindow(() => this.onActiveWindowChanged())); // WCO changes - // eslint-disable-next-line local/code-no-any-casts - if (isWeb && typeof (navigator as any).windowControlsOverlay === 'object') { - // eslint-disable-next-line local/code-no-any-casts - this._register(addDisposableListener((navigator as any).windowControlsOverlay, 'geometrychange', () => this.onDidChangeWCO())); + if (isWeb && typeof (navigator as { windowControlsOverlay?: EventTarget }).windowControlsOverlay === 'object') { + this._register(addDisposableListener((navigator as unknown as { windowControlsOverlay: EventTarget }).windowControlsOverlay, 'geometrychange', () => this.onDidChangeWCO())); } // Auxiliary windows diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index f6ec4f02860..16f677bae4c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -43,7 +43,7 @@ import { DraggedEditorIdentifier, fillEditorsDragData } from '../../dnd.js'; import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from '../../labels.js'; import { BreadcrumbsConfig, IBreadcrumbsService } from './breadcrumbs.js'; import { BreadcrumbsModel, FileElement, OutlineElement2 } from './breadcrumbsModel.js'; -import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from './breadcrumbsPicker.js'; +import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker } from './breadcrumbsPicker.js'; import { IEditorGroupView } from './editor.js'; import './media/breadcrumbscontrol.css'; import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; @@ -494,7 +494,7 @@ export class BreadcrumbsControl { } // show picker - let picker: BreadcrumbsPicker; + let picker: BreadcrumbsFilePicker | BreadcrumbsOutlinePicker; let pickerAnchor: { x: number; y: number }; interface IHideData { didPick?: boolean; source?: BreadcrumbsControl } @@ -931,8 +931,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // IOutline: check if this the outline and iff so reveal element const input = tree.getInput(); - if (input && typeof (>input).outlineKind === 'string') { - return (>input).reveal(element, { + if (input && typeof (>input).outlineKind === 'string') { + return (>input).reveal(element, { pinned: true, preserveFocus: false }, true, false); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 1c4aaef090a..5449a72fd58 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -28,8 +28,8 @@ type FileInfo = { path: FileElement[]; folder?: IWorkspaceFolder }; export class OutlineElement2 { constructor( - readonly element: IOutline | any, - readonly outline: IOutline + readonly element: IOutline | unknown, + readonly outline: IOutline ) { } } @@ -41,7 +41,7 @@ export class BreadcrumbsModel { private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>; private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>; - private readonly _currentOutline = new MutableDisposable>(); + private readonly _currentOutline = new MutableDisposable>(); private readonly _outlineDisposables = new DisposableStore(); private readonly _onDidUpdate = new Emitter(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index fd784e6f69b..9d73eb63d3e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -43,17 +43,17 @@ interface ILayoutInfo { type Tree = WorkbenchDataTree | WorkbenchAsyncDataTree; export interface SelectEvent { - target: any; + target: unknown; browserEvent: UIEvent; } -export abstract class BreadcrumbsPicker { +export abstract class BreadcrumbsPicker { protected readonly _disposables = new DisposableStore(); protected readonly _domNode: HTMLDivElement; protected _arrow!: HTMLDivElement; protected _treeContainer!: HTMLDivElement; - protected _tree!: Tree; + protected _tree!: Tree; protected _fakeEvent = new UIEvent('fakeEvent'); protected _layoutInfo!: ILayoutInfo; @@ -145,9 +145,9 @@ export abstract class BreadcrumbsPicker { restoreViewState(): void { } protected abstract _setInput(element: FileElement | OutlineElement2): Promise; - protected abstract _createTree(container: HTMLElement, input: any): Tree; - protected abstract _previewElement(element: any): IDisposable; - protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise; + protected abstract _createTree(container: HTMLElement, input: unknown): Tree; + protected abstract _previewElement(element: unknown): IDisposable; + protected abstract _revealElement(element: unknown, options: IEditorOptions, sideBySide: boolean): Promise; } @@ -340,7 +340,7 @@ export class FileSorter implements ITreeSorter { } } -export class BreadcrumbsFilePicker extends BreadcrumbsPicker { +export class BreadcrumbsFilePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, @@ -418,7 +418,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { tree.domFocus(); } - protected _previewElement(_element: any): IDisposable { + protected _previewElement(_element: unknown): IDisposable { return Disposable.None; } @@ -458,14 +458,14 @@ class OutlineTreeSorter implements ITreeSorter { } } -export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { +export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker, unknown> { protected _createTree(container: HTMLElement, input: OutlineElement2) { const { config } = input.outline; return this._instantiationService.createInstance( - WorkbenchDataTree, any, FuzzyScore>, + WorkbenchDataTree, unknown, FuzzyScore>, 'BreadcrumbsOutlinePicker', container, config.delegate, @@ -487,7 +487,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { const viewState = input.outline.captureViewState(); this.restoreViewState = () => { viewState.dispose(); }; - const tree = this._tree as WorkbenchDataTree, any, FuzzyScore>; + const tree = this._tree as WorkbenchDataTree, unknown, FuzzyScore>; tree.setInput(input.outline); if (input.element !== input.outline) { @@ -499,14 +499,14 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { return Promise.resolve(); } - protected _previewElement(element: any): IDisposable { - const outline: IOutline = this._tree.getInput(); + protected _previewElement(element: unknown): IDisposable { + const outline: IOutline = this._tree.getInput()!; return outline.preview(element); } - protected async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise { + protected async _revealElement(element: unknown, options: IEditorOptions, sideBySide: boolean): Promise { this._onWillPickElement.fire(); - const outline: IOutline = this._tree.getInput(); + const outline: IOutline = this._tree.getInput()!; await outline.reveal(element, options, sideBySide, false); return true; } diff --git a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts index d898f689e24..ca925a7e824 100644 --- a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts @@ -98,7 +98,7 @@ export function registerDiffEditorCommands(): void { }); - function getActiveTextDiffEditor(accessor: ServicesAccessor, args: any[]): TextDiffEditor | undefined { + function getActiveTextDiffEditor(accessor: ServicesAccessor, args: unknown[]): TextDiffEditor | undefined { const editorService = accessor.get(IEditorService); const resource = args.length > 0 && args[0] instanceof URI ? args[0] : undefined; @@ -111,7 +111,7 @@ export function registerDiffEditorCommands(): void { return undefined; } - function navigateInDiffEditor(accessor: ServicesAccessor, args: any[], next: boolean): void { + function navigateInDiffEditor(accessor: ServicesAccessor, args: unknown[], next: boolean): void { const activeTextDiffEditor = getActiveTextDiffEditor(accessor, args); if (activeTextDiffEditor) { @@ -125,7 +125,7 @@ export function registerDiffEditorCommands(): void { Toggle } - function focusInDiffEditor(accessor: ServicesAccessor, args: any[], mode: FocusTextDiffEditorMode): void { + function focusInDiffEditor(accessor: ServicesAccessor, args: unknown[], mode: FocusTextDiffEditorMode): void { const activeTextDiffEditor = getActiveTextDiffEditor(accessor, args); if (activeTextDiffEditor) { @@ -146,7 +146,7 @@ export function registerDiffEditorCommands(): void { } } - function toggleDiffSideBySide(accessor: ServicesAccessor, args: any[]): void { + function toggleDiffSideBySide(accessor: ServicesAccessor, args: unknown[]): void { const configService = accessor.get(ITextResourceConfigurationService); const activeTextDiffEditor = getActiveTextDiffEditor(accessor, args); @@ -158,7 +158,7 @@ export function registerDiffEditorCommands(): void { configService.updateValue(m.uri, key, !val); } - function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor, args: any[]): void { + function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor, args: unknown[]): void { const configService = accessor.get(ITextResourceConfigurationService); const activeTextDiffEditor = getActiveTextDiffEditor(accessor, args); @@ -170,7 +170,7 @@ export function registerDiffEditorCommands(): void { configService.updateValue(m.uri, key, !val); } - async function swapDiffSides(accessor: ServicesAccessor, args: any[]): Promise { + async function swapDiffSides(accessor: ServicesAccessor, args: unknown[]): Promise { const editorService = accessor.get(IEditorService); const diffEditor = getActiveTextDiffEditor(accessor, args); diff --git a/src/vs/workbench/browser/parts/editor/editorPane.ts b/src/vs/workbench/browser/parts/editor/editorPane.ts index c7be4a6cd63..03b48aeb551 100644 --- a/src/vs/workbench/browser/parts/editor/editorPane.ts +++ b/src/vs/workbench/browser/parts/editor/editorPane.ts @@ -57,7 +57,7 @@ export abstract class EditorPane extends Co //#endregion - private static readonly EDITOR_MEMENTOS = new Map>(); + private static readonly EDITOR_MEMENTOS = new Map>(); get minimumWidth() { return DEFAULT_EDITOR_MIN_DIMENSIONS.width; } get maximumWidth() { return DEFAULT_EDITOR_MAX_DIMENSIONS.width; } @@ -174,7 +174,7 @@ export abstract class EditorPane extends Co EditorPane.EDITOR_MEMENTOS.set(mementoKey, editorMemento); } - return editorMemento; + return editorMemento as IEditorMemento; } getViewState(): object | undefined { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 77bfedeabb0..25d85dbd034 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1145,7 +1145,7 @@ export class ChangeLanguageAction extends Action2 { args: [ { name: localize('changeLanguageMode.arg.name', "The name of the language mode to change to."), - constraint: (value: any) => typeof value === 'string', + constraint: (value: unknown) => typeof value === 'string', } ] } @@ -1374,8 +1374,7 @@ export class ChangeLanguageAction extends Action2 { // If the association is already being made in the workspace, make sure to target workspace settings let target = ConfigurationTarget.USER; - // eslint-disable-next-line local/code-no-any-casts - if (fileAssociationsConfig.workspaceValue && !!(fileAssociationsConfig.workspaceValue as any)[associationKey]) { + if (fileAssociationsConfig.workspaceValue && fileAssociationsConfig.workspaceValue[associationKey as keyof typeof fileAssociationsConfig.workspaceValue]) { target = ConfigurationTarget.WORKSPACE; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 58fcea42032..07e61ab30e0 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -721,7 +721,7 @@ export class CustomMenubarControl extends MenubarControl { const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.mnemonicTitle ?? action.item.title.value; - webNavigationActions.push(new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, async (event?: any) => { + webNavigationActions.push(new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, async (event?: unknown) => { this.commandService.executeCommand(action.id, event); })); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index e5b4388310b..690f6528c86 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -491,9 +491,9 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Check if the locale is RTL, macOS will move traffic lights in RTL locales // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - // eslint-disable-next-line local/code-no-any-casts - const localeInfo = safeIntl.Locale(platformLocale).value as any; - if (localeInfo?.textInfo?.direction === 'rtl') { + const localeInfo = safeIntl.Locale(platformLocale).value; + const textInfo = (localeInfo as { textInfo?: unknown }).textInfo; + if (textInfo && typeof textInfo === 'object' && 'direction' in textInfo && textInfo.direction === 'rtl') { primaryWindowControlsLocation = 'right'; } } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 96a49da894f..4e8a7941e85 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1704,7 +1704,7 @@ class TreeMenus implements IDisposable { return groups; } - public getElementOverlayContexts(element: ITreeItem): Map { + public getElementOverlayContexts(element: ITreeItem): Map { return new Map([ ['view', this.id], ['viewItem', element.contextValue] diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index ee6f952a249..bf01e9fae4c 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -177,7 +177,7 @@ export class FilterWidget extends Widget { inputBox.value = this.options.text; } this._register(inputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(inputBox)))); - this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, inputBox))); + this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => this.onInputKeyDown(e))); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.CLICK, (e) => { @@ -238,7 +238,7 @@ export class FilterWidget extends Widget { } } - private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) { + private onInputKeyDown(event: StandardKeyboardEvent) { let handled = false; if (event.equals(KeyCode.Tab) && !this.toolbar.isEmpty()) { this.toolbar.focus(); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 477760e9d33..466feac47bd 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -684,8 +684,7 @@ export class ViewPaneContainer extends Comp } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { - // eslint-disable-next-line local/code-no-any-casts - return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.staticArguments || []), options) as ViewPane; + return this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.staticArguments || []), options) as ViewPane; } getView(id: string): ViewPane | undefined { diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 24f54228122..499ab8b2e09 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -37,7 +37,7 @@ export interface IWorkbench { * @param rest Parameters passed to the command function. * @return A promise that resolves to the returned value of the given command. */ - executeCommand(command: string, ...args: any[]): Promise; + executeCommand(command: string, ...args: unknown[]): Promise; }; logger: { @@ -296,7 +296,7 @@ export interface IWorkbenchConstructionOptions { /** * Optional configuration default overrides contributed to the workbench. */ - readonly configurationDefaults?: Record; + readonly configurationDefaults?: Record; //#endregion @@ -437,7 +437,7 @@ export type ExtensionId = string; export type MarketplaceExtension = ExtensionId | { readonly id: ExtensionId; preRelease?: boolean; migrateStorageFrom?: ExtensionId }; export interface ICommonTelemetryPropertiesResolver { - (): { [key: string]: any }; + (): { [key: string]: unknown }; } export interface IExternalUriResolver { @@ -563,7 +563,7 @@ export interface ICommand { * Note: arguments and return type should be serializable so that they can * be exchanged across processes boundaries. */ - handler: (...args: any[]) => unknown; + handler: (...args: unknown[]) => unknown; } export interface IWelcomeBanner { diff --git a/src/vs/workbench/browser/web.factory.ts b/src/vs/workbench/browser/web.factory.ts index 0ebe2713def..e342f838f40 100644 --- a/src/vs/workbench/browser/web.factory.ts +++ b/src/vs/workbench/browser/web.factory.ts @@ -85,7 +85,7 @@ export namespace commands { /** * {@linkcode IWorkbench.commands IWorkbench.commands.executeCommand} */ - export async function executeCommand(command: string, ...args: any[]): Promise { + export async function executeCommand(command: string, ...args: unknown[]): Promise { const workbench = await workbenchPromise.p; return workbench.commands.executeCommand(command, ...args); diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index ed0a72d9766..74521fd57d2 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -142,8 +142,7 @@ export abstract class BaseWindow extends Disposable { // this can happen for timeouts on unfocused windows let didClear = false; - // eslint-disable-next-line local/code-no-any-casts - const handle = (window as any).vscodeOriginalSetTimeout.apply(this, [(...args: unknown[]) => { + const handle = (window as { vscodeOriginalSetTimeout?: typeof window.setTimeout }).vscodeOriginalSetTimeout?.apply(this, [(...args: unknown[]) => { if (didClear) { return; } @@ -152,8 +151,7 @@ export abstract class BaseWindow extends Disposable { const timeoutDisposable = toDisposable(() => { didClear = true; - // eslint-disable-next-line local/code-no-any-casts - (window as any).vscodeOriginalClearTimeout(handle); + (window as { vscodeOriginalClearTimeout?: typeof window.clearTimeout }).vscodeOriginalClearTimeout?.apply(this, [handle]); timeoutDisposables.delete(timeoutDisposable); }); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index d09ab3f2642..7881300d195 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -901,7 +901,7 @@ const registry = Registry.as(ConfigurationExtensions.Con Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ - key: 'workbench.activityBar.visible', migrateFn: (value: any) => { + key: 'workbench.activityBar.visible', migrateFn: (value: unknown) => { const result: ConfigurationKeyValuePairs = []; if (value !== undefined) { result.push(['workbench.activityBar.visible', { value: undefined }]); @@ -915,7 +915,7 @@ Registry.as(Extensions.ConfigurationMigration) Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ - key: LayoutSettings.ACTIVITY_BAR_LOCATION, migrateFn: (value: any) => { + key: LayoutSettings.ACTIVITY_BAR_LOCATION, migrateFn: (value: unknown) => { const results: ConfigurationKeyValuePairs = []; if (value === 'side') { results.push([LayoutSettings.ACTIVITY_BAR_LOCATION, { value: ActivityBarPosition.DEFAULT }]); @@ -926,7 +926,7 @@ Registry.as(Extensions.ConfigurationMigration) Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ - key: 'workbench.editor.doubleClickTabToToggleEditorGroupSizes', migrateFn: (value: any) => { + key: 'workbench.editor.doubleClickTabToToggleEditorGroupSizes', migrateFn: (value: unknown) => { const results: ConfigurationKeyValuePairs = []; if (typeof value === 'boolean') { value = value ? 'expand' : 'off'; @@ -935,7 +935,7 @@ Registry.as(Extensions.ConfigurationMigration) return results; } }, { - key: LayoutSettings.EDITOR_TABS_MODE, migrateFn: (value: any) => { + key: LayoutSettings.EDITOR_TABS_MODE, migrateFn: (value: unknown) => { const results: ConfigurationKeyValuePairs = []; if (typeof value === 'boolean') { value = value ? EditorTabsMode.MULTIPLE : EditorTabsMode.SINGLE; @@ -944,7 +944,7 @@ Registry.as(Extensions.ConfigurationMigration) return results; } }, { - key: 'workbench.editor.tabCloseButton', migrateFn: (value: any) => { + key: 'workbench.editor.tabCloseButton', migrateFn: (value: unknown) => { const result: ConfigurationKeyValuePairs = []; if (value === 'left' || value === 'right') { result.push(['workbench.editor.tabActionLocation', { value }]); @@ -954,7 +954,7 @@ Registry.as(Extensions.ConfigurationMigration) return result; } }, { - key: 'zenMode.hideTabs', migrateFn: (value: any) => { + key: 'zenMode.hideTabs', migrateFn: (value: unknown) => { const result: ConfigurationKeyValuePairs = [['zenMode.hideTabs', { value: undefined }]]; if (value === true) { result.push(['zenMode.showTabs', { value: 'single' }]); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index d62a4d5d73c..a486daa8b8b 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -205,10 +205,9 @@ export class Workbench extends Layout { const lifecycleService = accessor.get(ILifecycleService); // TODO@Sandeep debt around cyclic dependencies - // eslint-disable-next-line local/code-no-any-casts - const configurationService = accessor.get(IConfigurationService) as any; - if (typeof configurationService.acquireInstantiationService === 'function') { - configurationService.acquireInstantiationService(instantiationService); + const configurationService = accessor.get(IConfigurationService); + if (configurationService && 'acquireInstantiationService' in configurationService) { + (configurationService as { acquireInstantiationService: (instantiationService: unknown) => void }).acquireInstantiationService(instantiationService); } // Signal to lifecycle that services are set diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index 0a3abf6af06..fc383d736de 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -122,8 +122,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements hasSameContent(other: IChatRendererContent): boolean { // Simple implementation that checks if it's the same type - // eslint-disable-next-line local/code-no-any-casts - return (other as any).kind === 'mcpServersInteractionRequired'; + return other.kind === 'mcpServersInteractionRequired'; } addDisposable(disposable: IDisposable): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index 71c145a747a..5eef3ba2147 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -241,8 +241,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent hasSameContent(other: IChatRendererContent): boolean { return other.kind === 'multiDiffData' && - // eslint-disable-next-line local/code-no-any-casts - (other as any).multiDiffData?.resources?.length === this.content.multiDiffData.resources.length; + other.multiDiffData?.resources?.length === this.content.multiDiffData.resources.length; } addDisposable(disposable: IDisposable): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 3b41a95bfc7..ca264af96e7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -301,8 +301,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const newViewState = viewState ?? this._widget.getViewState(); for (const [key, value] of Object.entries(newViewState)) { // Assign all props to the memento so they get saved - // eslint-disable-next-line local/code-no-any-casts - (this.viewState as any)[key] = value; + (this.viewState as Record)[key] = value; } } } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 5c87741a8d2..0c451c7f65c 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -233,8 +233,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { return false; } - // eslint-disable-next-line local/code-no-any-casts - const mode = data as any; + const mode = data as IChatModeData; return typeof mode.id === 'string' && typeof mode.name === 'string' && typeof mode.kind === 'string' && diff --git a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts index fd04b5c4d04..9b12b00eb96 100644 --- a/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts @@ -165,8 +165,7 @@ export class LanguageConfigurationFileHandler extends Disposable { result = result || {}; result.lineComment = source.lineComment; } else if (types.isObject(source.lineComment)) { - // eslint-disable-next-line local/code-no-any-casts - const lineCommentObj = source.lineComment as any; + const lineCommentObj = source.lineComment; if (typeof lineCommentObj.comment === 'string') { result = result || {}; result.lineComment = { diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 6591a385f8f..f16522ceb4e 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -821,8 +821,7 @@ export class TimelinePane extends ViewPane { return; } - // eslint-disable-next-line local/code-no-any-casts - this.tree.setChildren(null, this.getItems() as any); + this.tree.setChildren(null, this.getItems()); this._isEmpty = !this.hasVisibleItems; if (this.uri === undefined) { From 0411310eb9333f5935944405924d6011a658dc5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:35:15 +0900 Subject: [PATCH 0876/4355] Remove some any usage in terminal/tasks Part of #269213 --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 26b6147d098..e2f059487e5 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -907,7 +907,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } })); watchingProblemMatcher.aboutToStart(); - let delayer: Async.Delayer | undefined = undefined; + let delayer: Async.Delayer | undefined = undefined; [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder); if (error) { @@ -1006,7 +1006,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { break; } } - let delayer: Async.Delayer | undefined = undefined; + let delayer: Async.Delayer | undefined = undefined; for (let i = bufferLines.length - 1; i >= 0; i--) { watchingProblemMatcher.processLine(bufferLines[i]); if (!delayer) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c791df527b4..3786d581547 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -305,7 +305,7 @@ export class TerminalService extends Disposable implements ITerminalService { } mark('code/terminal/willReconnect'); - let reconnectedPromise: Promise; + let reconnectedPromise: Promise; if (isPersistentRemote) { reconnectedPromise = this._reconnectToRemoteTerminals(); } else if (enableTerminalReconnection) { From 4bee013c711a7ddf819817b9eada34fd80443323 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:41:07 +0900 Subject: [PATCH 0877/4355] Enable capability type narrowing and generic access Part of #269213 --- .../common/capabilities/capabilities.ts | 25 +++++++++-- .../capabilities/terminalCapabilityStore.ts | 42 ++++++++++++++----- .../test/browser/terminalEvents.test.ts | 0 3 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index bb62e2ee8f0..9b290431012 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -63,6 +63,7 @@ export interface ITerminalCapabilityStore { * Fired when a capability is added. The event data for this is only the * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual * capability. + * @deprecated Use {@link createOnDidAddCapabilityOfTypeEvent} */ readonly onDidAddCapabilityType: Event; @@ -70,18 +71,32 @@ export interface ITerminalCapabilityStore { * Fired when a capability is removed. The event data for this is only the * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual * capability. + * @deprecated Use {@link createOnDidRemoveCapabilityOfTypeEvent} */ readonly onDidRemoveCapabilityType: Event; - /** * Fired when a capability is added. */ - readonly onDidAddCapability: Event>; + readonly onDidAddCapability: Event; /** * Fired when a capability is removed. + */ + readonly onDidRemoveCapability: Event; + + /** + * Create an event that's fired when a specific capability type is added. Use this over + * {@link onDidAddCapability} when the generic type needs to be retained. + * @param type The capability type. */ - readonly onDidRemoveCapability: Event>; + createOnDidAddCapabilityOfTypeEvent(type: T): Event; + + /** + * Create an event that's fired when a specific capability type is removed. Use this over + * {@link onDidRemoveCapability} when the generic type needs to be retained. + * @param type The capability type. + */ + createOnDidRemoveCapabilityOfTypeEvent(type: T): Event; /** * Gets whether the capability exists in the store. @@ -99,6 +114,10 @@ export interface TerminalCapabilityChangeEvent { capability: ITerminalCapabilityImplMap[T]; } +export type AnyTerminalCapabilityChangeEvent = { + [K in TerminalCapability]: TerminalCapabilityChangeEvent +}[TerminalCapability]; + /** * Maps capability types to their implementation, enabling strongly typed fetching of * implementations. diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index dfa008c0653..3af48a297a8 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -3,22 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from '../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, TerminalCapabilityChangeEvent } from './capabilities.js'; +import { ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent } from './capabilities.js'; export class TerminalCapabilityStore extends Disposable implements ITerminalCapabilityStore { - private _map: Map = new Map(); + private _map: Map = new Map(); private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidRemoveCapability = this._register(new Emitter>()); + private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; - private readonly _onDidAddCapability = this._register(new Emitter>()); + createOnDidRemoveCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } + + private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; + createOnDidAddCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } get items(): IterableIterator { return this._map.keys(); @@ -27,7 +34,7 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa add(capability: T, impl: ITerminalCapabilityImplMap[T]) { this._map.set(capability, impl); this._onDidAddCapabilityType.fire(capability); - this._onDidAddCapability.fire({ id: capability, capability: impl }); + this._onDidAddCapability.fire(createCapabilityEvent(capability, impl)); } get(capability: T): ITerminalCapabilityImplMap[T] | undefined { @@ -42,7 +49,7 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa } this._map.delete(capability); this._onDidRemoveCapabilityType.fire(capability); - this._onDidAddCapability.fire({ id: capability, capability: impl }); + this._onDidRemoveCapability.fire(createCapabilityEvent(capability, impl)); } has(capability: TerminalCapability) { @@ -58,11 +65,19 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidRemoveCapability = this._register(new Emitter>()); + private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; - private readonly _onDidAddCapability = this._register(new Emitter>()); + + private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; + createOnDidRemoveCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } + createOnDidAddCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } + get items(): IterableIterator { return this._items(); } @@ -100,7 +115,7 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT this._stores.push(store); for (const capability of store.items) { this._onDidAddCapabilityType.fire(capability); - this._onDidAddCapability.fire({ id: capability, capability: store.get(capability)! }); + this._onDidAddCapability.fire(createCapabilityEvent(capability, store.get(capability)!)); } this._register(store.onDidAddCapabilityType(e => this._onDidAddCapabilityType.fire(e))); this._register(store.onDidAddCapability(e => this._onDidAddCapability.fire(e))); @@ -108,3 +123,10 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT this._register(store.onDidRemoveCapability(e => this._onDidRemoveCapability.fire(e))); } } + +function createCapabilityEvent(capability: T, impl: ITerminalCapabilityImplMap[T]): AnyTerminalCapabilityChangeEvent { + // HACK: This cast is required to convert a generic type to a discriminated union, this is + // necessary in order to enable type narrowing on the event consumer side. + // eslint-disable-next-line local/code-no-dangerous-type-assertions + return { id: capability, capability: impl } as AnyTerminalCapabilityChangeEvent; +} diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts new file mode 100644 index 00000000000..e69de29bb2d From e74fc3eaf8c689578462f9ed5f8a3e87f1e7561d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:54:35 +0900 Subject: [PATCH 0878/4355] Add unit tests for event multiplexer --- .../terminal/browser/terminalEvents.ts | 12 +- .../test/browser/terminalEvents.test.ts | 340 ++++++++++++++++++ 2 files changed, 344 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEvents.ts b/src/vs/workbench/contrib/terminal/browser/terminalEvents.ts index 46b4481f968..39d0d9af21b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEvents.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEvents.ts @@ -47,12 +47,10 @@ export function createInstanceCapabilityEventMultiplexer Event.map(instance.capabilities.onDidAddCapability, changeEvent => ({ instance, changeEvent })) + instance => Event.map(instance.capabilities.createOnDidAddCapabilityOfTypeEvent(capabilityId), changeEvent => ({ instance, changeEvent })) )); store.add(addCapabilityMultiplexer.event(e => { - if (e.changeEvent.id === capabilityId) { - addCapability(e.instance, e.changeEvent.capability); - } + addCapability(e.instance, e.changeEvent); })); // Removed capabilities @@ -60,12 +58,10 @@ export function createInstanceCapabilityEventMultiplexer Event.map(instance.capabilities.onDidRemoveCapability, changeEvent => ({ instance, changeEvent })) + instance => Event.map(instance.capabilities.createOnDidRemoveCapabilityOfTypeEvent(capabilityId), changeEvent => ({ instance, changeEvent })) )); store.add(removeCapabilityMultiplexer.event(e => { - if (e.changeEvent.id === capabilityId) { - capabilityListeners.get(e.instance.instanceId)?.deleteAndDispose(e.changeEvent.id); - } + capabilityListeners.get(e.instance.instanceId)?.deleteAndDispose(e.changeEvent); })); return { diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts index e69de29bb2d..7b4bb303d89 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalEvents.test.ts @@ -0,0 +1,340 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { Emitter } from '../../../../../base/common/event.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ICwdDetectionCapability, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { TerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; +import { createInstanceCapabilityEventMultiplexer } from '../../browser/terminalEvents.js'; +import { ITerminalInstance } from '../../browser/terminal.js'; + +// Mock implementations for testing +class MockCwdDetectionCapability implements ICwdDetectionCapability { + readonly type = TerminalCapability.CwdDetection; + readonly cwds: string[] = []; + + private readonly _onDidChangeCwd = new Emitter(); + readonly onDidChangeCwd = this._onDidChangeCwd.event; + + getCwd(): string { + return this.cwds[this.cwds.length - 1] || ''; + } + + updateCwd(cwd: string): void { + this.cwds.push(cwd); + this._onDidChangeCwd.fire(cwd); + } + + fireEvent(cwd: string): void { + this.updateCwd(cwd); + } + + dispose(): void { + this._onDidChangeCwd.dispose(); + } +} + + + +function createMockTerminalInstance(instanceId: number, capabilities: TerminalCapabilityStore): ITerminalInstance { + const instance = { + instanceId, + capabilities + } as unknown as ITerminalInstance; + return instance; +} + +suite('Terminal Events', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + suite('createInstanceCapabilityEventMultiplexer', () => { + test('should handle existing instances with capabilities', () => { + const capability = store.add(new MockCwdDetectionCapability()); + const capabilities = store.add(new TerminalCapabilityStore()); + capabilities.add(TerminalCapability.CwdDetection, capability); + const instance = createMockTerminalInstance(1, capabilities); + + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventFired = false; + let capturedData: { instance: ITerminalInstance; data: string } | undefined; + + store.add(multiplexer.event(e => { + eventFired = true; + capturedData = e; + })); + + capability.fireEvent('test-data'); + + strictEqual(eventFired, true, 'Event should be fired'); + strictEqual(capturedData?.instance, instance, 'Event should contain correct instance'); + strictEqual(capturedData?.data, 'test-data', 'Event should contain correct data'); + }); + + test('should handle instances without capabilities', () => { + const capabilities = store.add(new TerminalCapabilityStore()); + const instance = createMockTerminalInstance(1, capabilities); + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventFired = false; + store.add(multiplexer.event(() => { + eventFired = true; + })); + + strictEqual(eventFired, false, 'No event should be fired for instances without capabilities'); + }); + + test('should handle adding new instances', () => { + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventFired = false; + let capturedData: { instance: ITerminalInstance; data: string } | undefined; + + store.add(multiplexer.event(e => { + eventFired = true; + capturedData = e; + })); + + // Add a new instance with capability + const capability = store.add(new MockCwdDetectionCapability()); + const capabilities = store.add(new TerminalCapabilityStore()); + const instance = createMockTerminalInstance(2, capabilities); + + onAddInstance.fire(instance); + + // Add capability to the instance after it's added to the multiplexer + capabilities.add(TerminalCapability.CwdDetection, capability); + + // Fire an event from the capability + capability.fireEvent('new-instance-data'); + + strictEqual(eventFired, true, 'Event should be fired from new instance'); + strictEqual(capturedData?.instance, instance, 'Event should contain correct new instance'); + strictEqual(capturedData?.data, 'new-instance-data', 'Event should contain correct data'); + }); + + test('should handle removing instances', () => { + const capability = store.add(new MockCwdDetectionCapability()); + const capabilities = store.add(new TerminalCapabilityStore()); + capabilities.add(TerminalCapability.CwdDetection, capability); + const instance = createMockTerminalInstance(3, capabilities); + + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventCount = 0; + store.add(multiplexer.event(() => { + eventCount++; + })); + + // Fire event before removal + capability.fireEvent('before-removal'); + strictEqual(eventCount, 1, 'Event should be fired before removal'); + + // Remove the instance + onRemoveInstance.fire(instance); + + // Fire event after removal - should not be received + capability.fireEvent('after-removal'); + strictEqual(eventCount, 1, 'Event should not be fired after instance removal'); + }); + + test('should handle adding capabilities to existing instances', () => { + const capabilities = store.add(new TerminalCapabilityStore()); + const instance = createMockTerminalInstance(4, capabilities); + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventFired = false; + let capturedData: { instance: ITerminalInstance; data: string } | undefined; + + store.add(multiplexer.event(e => { + eventFired = true; + capturedData = e; + })); + + // Add capability to existing instance + const capability = store.add(new MockCwdDetectionCapability()); + capabilities.add(TerminalCapability.CwdDetection, capability); + + // Fire an event from the newly added capability + capability.fireEvent('added-capability-data'); + + strictEqual(eventFired, true, 'Event should be fired from newly added capability'); + strictEqual(capturedData?.instance, instance, 'Event should contain correct instance'); + strictEqual(capturedData?.data, 'added-capability-data', 'Event should contain correct data'); + }); + + test('should handle removing capabilities from existing instances', () => { + const capability = store.add(new MockCwdDetectionCapability()); + const capabilities = store.add(new TerminalCapabilityStore()); + capabilities.add(TerminalCapability.CwdDetection, capability); + const instance = createMockTerminalInstance(5, capabilities); + + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventCount = 0; + store.add(multiplexer.event(() => { + eventCount++; + })); + + // Fire event before removing capability + capability.fireEvent('before-capability-removal'); + strictEqual(eventCount, 1, 'Event should be fired before capability removal'); + + // Remove the capability + capabilities.remove(TerminalCapability.CwdDetection); // Fire event after capability removal - should not be received + capability.fireEvent('after-capability-removal'); + strictEqual(eventCount, 1, 'Event should not be fired after capability removal'); + }); + + test('should handle multiple instances with same capability', () => { + const capability1 = store.add(new MockCwdDetectionCapability()); + const capability2 = store.add(new MockCwdDetectionCapability()); + const capabilities1 = store.add(new TerminalCapabilityStore()); + const capabilities2 = store.add(new TerminalCapabilityStore()); + capabilities1.add(TerminalCapability.CwdDetection, capability1); + capabilities2.add(TerminalCapability.CwdDetection, capability2); + const instance1 = createMockTerminalInstance(6, capabilities1); + const instance2 = createMockTerminalInstance(7, capabilities2); + + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [instance1, instance2], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + const events: Array<{ instance: ITerminalInstance; data: string }> = []; + store.add(multiplexer.event(e => { + events.push(e); + })); + + // Fire events from both capabilities + capability1.fireEvent('data-from-instance1'); + capability2.fireEvent('data-from-instance2'); + + strictEqual(events.length, 2, 'Both events should be received'); + strictEqual(events[0].instance, instance1, 'First event should be from instance1'); + strictEqual(events[0].data, 'data-from-instance1', 'First event should have correct data'); + strictEqual(events[1].instance, instance2, 'Second event should be from instance2'); + strictEqual(events[1].data, 'data-from-instance2', 'Second event should have correct data'); + }); + + test('should properly dispose all resources', () => { + const testStore = new DisposableStore(); + const capability = testStore.add(new MockCwdDetectionCapability()); + const capabilities = testStore.add(new TerminalCapabilityStore()); + capabilities.add(TerminalCapability.CwdDetection, capability); + const instance = createMockTerminalInstance(8, capabilities); + + const onAddInstance = testStore.add(new Emitter()); + const onRemoveInstance = testStore.add(new Emitter()); + + const multiplexer = testStore.add(createInstanceCapabilityEventMultiplexer( + [instance], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventCount = 0; + testStore.add(multiplexer.event(() => { + eventCount++; + })); + + // Fire event before disposal + capability.fireEvent('before-disposal'); + strictEqual(eventCount, 1, 'Event should be fired before disposal'); + + // Dispose everything + testStore.dispose(); + + // Fire event after disposal - should not be received + capability.fireEvent('after-disposal'); + strictEqual(eventCount, 1, 'Event should not be fired after disposal'); + }); + + test('should handle empty current instances array', () => { + const onAddInstance = store.add(new Emitter()); + const onRemoveInstance = store.add(new Emitter()); + + const multiplexer = store.add(createInstanceCapabilityEventMultiplexer( + [], + onAddInstance.event, + onRemoveInstance.event, + TerminalCapability.CwdDetection, + (cap) => cap.onDidChangeCwd + )); + + let eventFired = false; + store.add(multiplexer.event(() => { + eventFired = true; + })); + + // No instances, so no events should be fired initially + strictEqual(eventFired, false, 'No events should be fired with empty instances array'); + }); + }); +}); From db121d8147f7c8ae8eaabe9116fc387006bdc447 Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Tue, 7 Oct 2025 01:15:01 -0700 Subject: [PATCH 0879/4355] Update chat sessions hover to not block the items (#269780) * Show hover on the side instead of above in chat sessions * Minor formatting * formatting update * Apply PR feedback * pass viewId from sessionsView * nits --------- Co-authored-by: vijay upadya Co-authored-by: BeniBenj --- .../chatSessions/view/chatSessionsView.ts | 7 ++-- .../chatSessions/view/sessionsTreeRenderer.ts | 38 ++++++++++++++++--- .../chatSessions/view/sessionsViewPane.ts | 3 +- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 017bc082ea7..ae25b928388 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -266,13 +266,14 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { orderedProviders.forEach(({ provider, displayName, baseOrder, when }) => { // Only register if not already registered if (!this.registeredViewDescriptors.has(provider.chatSessionType)) { + const viewId = `${VIEWLET_ID}.${provider.chatSessionType}`; const viewDescriptor: IViewDescriptor = { - id: `${VIEWLET_ID}.${provider.chatSessionType}`, + id: viewId, name: { value: displayName, original: displayName, }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [provider, this.sessionTracker]), + ctorDescriptor: new SyncDescriptor(SessionsViewPane, [provider, this.sessionTracker, viewId]), canToggleVisibility: true, canMoveView: true, order: baseOrder, // Use computed order based on priority and alphabetical sorting @@ -301,7 +302,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { value: nls.localize('chat.sessions.gettingStarted', "Getting Started"), original: 'Getting Started', }, - ctorDescriptor: new SyncDescriptor(SessionsViewPane, [null, this.sessionTracker]), + ctorDescriptor: new SyncDescriptor(SessionsViewPane, [null, this.sessionTracker, gettingStartedViewId]), canToggleVisibility: true, canMoveView: true, order: 1000, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 8fb06af78db..fb42c6cc8f5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -30,9 +30,11 @@ import { IHoverService } from '../../../../../../platform/hover/browser/hover.js import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import product from '../../../../../../platform/product/common/product.js'; import { defaultInputBoxStyles } from '../../../../../../platform/theme/browser/defaultStyles.js'; +import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; +import { IWorkbenchLayoutService, Position } from '../../../../../services/layout/browser/layoutService.js'; +import { ViewContainerLocation, IEditableData } from '../../../../../common/views.js'; import { IResourceLabel, ResourceLabels } from '../../../../../browser/labels.js'; import { IconLabel } from '../../../../../../base/browser/ui/iconLabel/iconLabel.js'; -import { IEditableData } from '../../../../../common/views.js'; import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; import { IChatService } from '../../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../../common/chatSessionsService.js'; @@ -112,6 +114,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer ({ + content: tooltipContent, + appearance: { showPointer: true }, + position: { hoverPosition: this.getHoverPosition() } + }), { groupId: 'chat.sessions' }) ); } else if (tooltipContent && typeof tooltipContent === 'object' && 'markdown' in tooltipContent) { templateData.elementDisposable.add( - this.hoverService.setupDelayedHover(templateData.container, { content: tooltipContent.markdown }) + this.hoverService.setupDelayedHover(templateData.container, () => ({ + content: tooltipContent.markdown, + appearance: { showPointer: true }, + position: { hoverPosition: this.getHoverPosition() } + }), { groupId: 'chat.sessions' }) ); } } @@ -288,9 +312,11 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer ({ + content: nls.localize('chat.sessions.lastActivity', 'Last Activity: {0}', fullDateTime), + appearance: { showPointer: true }, + position: { hoverPosition: this.getHoverPosition() } + }), { groupId: 'chat.sessions' }) ); } } else { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index a2de824f5cf..94a16016ad1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -81,6 +81,7 @@ export class SessionsViewPane extends ViewPane { constructor( private readonly provider: IChatSessionItemProvider, private readonly sessionTracker: ChatSessionTracker, + private readonly viewId: string, options: IViewPaneOptions, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -296,7 +297,7 @@ export class SessionsViewPane extends ViewPane { const accessibilityProvider = new SessionsAccessibilityProvider(); // Use the existing ResourceLabels service for consistent styling - const renderer = this.instantiationService.createInstance(SessionsRenderer); + const renderer = this.instantiationService.createInstance(SessionsRenderer, this.viewDescriptorService.getViewLocationById(this.viewId)); this._register(renderer); const getResourceForElement = (element: ChatSessionItemWithProvider): URI | null => { From e69dbbc1968de5e43b02b9d9459174872391dbbb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:18:07 +0900 Subject: [PATCH 0880/4355] Add command detection event This makes up a large amount of use, makes sense to have an explicit event. --- .../common/capabilities/capabilities.ts | 7 ++- .../capabilities/terminalCapabilityStore.ts | 49 ++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 9b290431012..7022f83802f 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -63,7 +63,7 @@ export interface ITerminalCapabilityStore { * Fired when a capability is added. The event data for this is only the * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual * capability. - * @deprecated Use {@link createOnDidAddCapabilityOfTypeEvent} + * @deprecated Use {@link onDidAddCapability} or {@link onDidAddCommandDetectionCapability} */ readonly onDidAddCapabilityType: Event; @@ -71,7 +71,7 @@ export interface ITerminalCapabilityStore { * Fired when a capability is removed. The event data for this is only the * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual * capability. - * @deprecated Use {@link createOnDidRemoveCapabilityOfTypeEvent} + * @deprecated Use {@link onDidRemoveCapability} or {@link onDidRemoveCommandDetectionCapability} */ readonly onDidRemoveCapabilityType: Event; /** @@ -84,6 +84,9 @@ export interface ITerminalCapabilityStore { */ readonly onDidRemoveCapability: Event; + readonly onDidAddCommandDetectionCapability: Event; + readonly onDidRemoveCommandDetectionCapability: Event; + /** * Create an event that's fired when a specific capability type is added. Use this over * {@link onDidAddCapability} when the generic type needs to be retained. diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index 3af48a297a8..e009a6f7fae 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -5,32 +5,39 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent } from './capabilities.js'; +import { ICommandDetectionCapability, ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent } from './capabilities.js'; export class TerminalCapabilityStore extends Disposable implements ITerminalCapabilityStore { private _map: Map = new Map(); - private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); - readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; + private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); + readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; + private readonly _onDidAddCapability = this._register(new Emitter()); + readonly onDidAddCapability = this._onDidAddCapability.event; private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; - createOnDidRemoveCapabilityOfTypeEvent(type: T): Event { - return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); - } - private readonly _onDidAddCapability = this._register(new Emitter()); - readonly onDidAddCapability = this._onDidAddCapability.event; - createOnDidAddCapabilityOfTypeEvent(type: T): Event { - return Event.map(Event.filter(this.onDidAddCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + get onDidAddCommandDetectionCapability() { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); + } + get onDidRemoveCommandDetectionCapability() { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); } get items(): IterableIterator { return this._map.keys(); } + createOnDidRemoveCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } + createOnDidAddCapabilityOfTypeEvent(type: T): Event { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); + } + add(capability: T, impl: ITerminalCapabilityImplMap[T]) { this._map.set(capability, impl); this._onDidAddCapabilityType.fire(capability); @@ -60,16 +67,26 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa export class TerminalCapabilityStoreMultiplexer extends Disposable implements ITerminalCapabilityStore { readonly _stores: ITerminalCapabilityStore[] = []; - private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); - readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; + private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); + readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; + private readonly _onDidAddCapability = this._register(new Emitter()); + readonly onDidAddCapability = this._onDidAddCapability.event; private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; - private readonly _onDidAddCapability = this._register(new Emitter()); - readonly onDidAddCapability = this._onDidAddCapability.event; + get onDidAddCommandDetectionCapability() { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); + } + get onDidRemoveCommandDetectionCapability() { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); + } + + get items(): IterableIterator { + return this._items(); + } createOnDidRemoveCapabilityOfTypeEvent(type: T): Event { return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); @@ -78,10 +95,6 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT return Event.map(Event.filter(this.onDidAddCapability, e => e.id === type), e => e.capability as ITerminalCapabilityImplMap[T]); } - get items(): IterableIterator { - return this._items(); - } - private *_items(): IterableIterator { for (const store of this._stores) { for (const c of store.items) { From f3bfd67c534db5dac336315a187f5adafc02d0c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:23:35 +0900 Subject: [PATCH 0881/4355] Remove onDidRemoveCapabilityType event --- .../common/capabilities/capabilities.ts | 9 +---- .../capabilities/terminalCapabilityStore.ts | 6 --- .../terminal/browser/terminalEditorService.ts | 4 +- .../contrib/terminal/browser/terminalGroup.ts | 4 +- .../terminal/browser/terminalInstance.ts | 38 +++++++++---------- .../terminal/browser/xterm/decorationAddon.ts | 4 +- .../terminalCapabilityStore.test.ts | 4 +- .../suggest/browser/terminalSuggestAddon.ts | 4 +- 8 files changed, 30 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 7022f83802f..9b35103d6c6 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -67,13 +67,6 @@ export interface ITerminalCapabilityStore { */ readonly onDidAddCapabilityType: Event; - /** - * Fired when a capability is removed. The event data for this is only the - * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual - * capability. - * @deprecated Use {@link onDidRemoveCapability} or {@link onDidRemoveCommandDetectionCapability} - */ - readonly onDidRemoveCapabilityType: Event; /** * Fired when a capability is added. */ @@ -84,6 +77,8 @@ export interface ITerminalCapabilityStore { */ readonly onDidRemoveCapability: Event; + // TODO: Add onDidChangeCapabilities convenience event + readonly onDidAddCommandDetectionCapability: Event; readonly onDidRemoveCommandDetectionCapability: Event; diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index e009a6f7fae..b1caadc7788 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -12,8 +12,6 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); - readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; @@ -55,7 +53,6 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa return; } this._map.delete(capability); - this._onDidRemoveCapabilityType.fire(capability); this._onDidRemoveCapability.fire(createCapabilityEvent(capability, impl)); } @@ -69,8 +66,6 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT private readonly _onDidAddCapabilityType = this._register(new Emitter()); readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidRemoveCapabilityType = this._register(new Emitter()); - readonly onDidRemoveCapabilityType = this._onDidRemoveCapabilityType.event; private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; @@ -132,7 +127,6 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT } this._register(store.onDidAddCapabilityType(e => this._onDidAddCapabilityType.fire(e))); this._register(store.onDidAddCapability(e => this._onDidAddCapability.fire(e))); - this._register(store.onDidRemoveCapabilityType(e => this._onDidRemoveCapabilityType.fire(e))); this._register(store.onDidRemoveCapability(e => this._onDidRemoveCapability.fire(e))); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index d2e893be6af..97e43abc2bb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -182,8 +182,8 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this._instanceDisposables.set(inputKey, [ instance.onDidFocus(this._onDidFocusInstance.fire, this._onDidFocusInstance), instance.onDisposed(this._onDidDisposeInstance.fire, this._onDidDisposeInstance), - instance.capabilities.onDidAddCapabilityType(() => this._onDidChangeInstanceCapability.fire(instance)), - instance.capabilities.onDidRemoveCapabilityType(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidAddCapability(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidRemoveCapability(() => this._onDidChangeInstanceCapability.fire(instance)), ]); this.instances.push(instance); this._onDidChangeInstances.fire(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 2a3613c910e..9663cdef8a3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -361,8 +361,8 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._setActiveInstance(instance); this._onDidFocusInstance.fire(instance); }), - instance.capabilities.onDidAddCapabilityType(() => this._onDidChangeInstanceCapability.fire(instance)), - instance.capabilities.onDidRemoveCapabilityType(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidAddCapability(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidRemoveCapability(() => this._onDidChangeInstanceCapability.fire(instance)), ]); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 0b4b058377c..879ebf0b3fe 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -459,40 +459,38 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._terminalShellIntegrationEnabledContextKey = TerminalContextKeys.terminalShellIntegrationEnabled.bindTo(scopedContextKeyService); this._logService.trace(`terminalInstance#ctor (instanceId: ${this.instanceId})`, this._shellLaunchConfig); - this._register(this.capabilities.onDidAddCapabilityType(e => this._logService.debug('terminalInstance added capability', e))); - this._register(this.capabilities.onDidRemoveCapabilityType(e => this._logService.debug('terminalInstance removed capability', e))); + this._register(this.capabilities.onDidAddCapability(e => this._logService.debug('terminalInstance added capability', e.id))); + this._register(this.capabilities.onDidRemoveCapability(e => this._logService.debug('terminalInstance removed capability', e.id))); const capabilityListeners = this._register(new DisposableMap()); - this._register(this.capabilities.onDidAddCapabilityType(capability => { - capabilityListeners.get(capability)?.dispose(); - if (capability === TerminalCapability.CwdDetection) { - const cwdDetection = this.capabilities.get(capability); - if (cwdDetection) { - capabilityListeners.set(capability, cwdDetection.onDidChangeCwd(e => { + this._register(this.capabilities.onDidAddCapability(e => { + capabilityListeners.get(e.id)?.dispose(); + switch (e.id) { + case TerminalCapability.CwdDetection: { + capabilityListeners.set(e.id, e.capability.onDidChangeCwd(e => { this._cwd = e; this._setTitle(this.title, TitleEventSource.Config); })); + break; } - } - if (capability === TerminalCapability.CommandDetection) { - const commandDetection = this.capabilities.get(capability); - if (commandDetection) { - commandDetection.promptInputModel.setShellType(this.shellType); - capabilityListeners.set(capability, Event.any( - commandDetection.onPromptTypeChanged, - commandDetection.promptInputModel.onDidStartInput, - commandDetection.promptInputModel.onDidChangeInput, - commandDetection.promptInputModel.onDidFinishInput + case TerminalCapability.CommandDetection: { + e.capability.promptInputModel.setShellType(this.shellType); + capabilityListeners.set(e.id, Event.any( + e.capability.onPromptTypeChanged, + e.capability.promptInputModel.onDidStartInput, + e.capability.promptInputModel.onDidChangeInput, + e.capability.promptInputModel.onDidFinishInput )(() => { this._labelComputer?.refreshLabel(this); refreshShellIntegrationInfoStatus(this); })); + break; } } })); this._register(this.onDidChangeShellType(() => refreshShellIntegrationInfoStatus(this))); - this._register(this.capabilities.onDidRemoveCapabilityType(capability => { - capabilityListeners.get(capability)?.dispose(); + this._register(this.capabilities.onDidRemoveCapability(e => { + capabilityListeners.get(e.id)?.dispose(); })); // Resolve just the icon ahead of time so that it shows up immediately in the tabs. This is diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 2d8d764405b..8d8793fdeea 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -72,8 +72,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco })); this._register(this._themeService.onDidColorThemeChange(() => this._refreshStyles(true))); this._updateDecorationVisibility(); - this._register(this._capabilities.onDidAddCapabilityType(c => this._createCapabilityDisposables(c))); - this._register(this._capabilities.onDidRemoveCapabilityType(c => this._removeCapabilityDisposables(c))); + this._register(this._capabilities.onDidAddCapability(c => this._createCapabilityDisposables(c.id))); + this._register(this._capabilities.onDidRemoveCapability(c => this._removeCapabilityDisposables(c.id))); this._register(lifecycleService.onWillShutdown(() => this._disposeAllDecorations())); } diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts index 4869bcb88c9..be16b7269e4 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts @@ -19,7 +19,7 @@ suite('TerminalCapabilityStore', () => { setup(() => { capabilityStore = store.add(new TerminalCapabilityStore()); store.add(capabilityStore.onDidAddCapabilityType(e => addEvents.push(e))); - store.add(capabilityStore.onDidRemoveCapabilityType(e => removeEvents.push(e))); + store.add(capabilityStore.onDidRemoveCapability(e => removeEvents.push(e.id))); addEvents = []; removeEvents = []; }); @@ -73,7 +73,7 @@ suite('TerminalCapabilityStoreMultiplexer', () => { store = new DisposableStore(); multiplexer = store.add(new TerminalCapabilityStoreMultiplexer()); multiplexer.onDidAddCapabilityType(e => addEvents.push(e)); - multiplexer.onDidRemoveCapabilityType(e => removeEvents.push(e)); + multiplexer.onDidRemoveCapability(e => removeEvents.push(e.id)); store1 = store.add(new TerminalCapabilityStore()); store2 = store.add(new TerminalCapabilityStore()); addEvents = []; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index b9fb6c090b0..d958334b688 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -209,8 +209,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } this._register(Event.runAndSubscribe(Event.any( - this._capabilities.onDidAddCapabilityType, - this._capabilities.onDidRemoveCapabilityType + this._capabilities.onDidAddCapability, + this._capabilities.onDidRemoveCapability ), () => { const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection); if (commandDetection) { From 4a0f4c587ce5a89514b6f5a911a753d79f7c0355 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:30:46 +0900 Subject: [PATCH 0882/4355] Remove onDidAddCapabilityType event --- .../common/capabilities/capabilities.ts | 10 ++------ .../capabilities/terminalCapabilityStore.ts | 23 +++++++++++-------- .../mainThreadTerminalShellIntegration.ts | 2 +- .../terminal/browser/terminalInstance.ts | 16 +++++-------- .../terminal/browser/terminalService.ts | 3 ++- .../terminalCapabilityStore.test.ts | 4 ++-- .../browser/toolTerminalCreator.ts | 20 ++++++++-------- .../terminal.developer.contribution.ts | 6 ++--- .../browser/terminal.history.contribution.ts | 16 ++++--------- .../quickFix/browser/quickFixAddon.ts | 6 ++--- .../lspTerminalModelContentProvider.ts | 9 +++----- 11 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 9b35103d6c6..0d3c2a5f583 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -59,14 +59,6 @@ export interface ITerminalCapabilityStore { */ readonly items: IterableIterator; - /** - * Fired when a capability is added. The event data for this is only the - * {@link TerminalCapability} type, use {@link onDidAddCapability} to access the actual - * capability. - * @deprecated Use {@link onDidAddCapability} or {@link onDidAddCommandDetectionCapability} - */ - readonly onDidAddCapabilityType: Event; - /** * Fired when a capability is added. */ @@ -81,6 +73,8 @@ export interface ITerminalCapabilityStore { readonly onDidAddCommandDetectionCapability: Event; readonly onDidRemoveCommandDetectionCapability: Event; + readonly onDidAddCwdDetectionCapability: Event; + readonly onDidRemoveCwdDetectionCapability: Event; /** * Create an event that's fired when a specific capability type is added. Use this over diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index b1caadc7788..ede832bf43b 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -5,14 +5,11 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { ICommandDetectionCapability, ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent } from './capabilities.js'; +import { ICommandDetectionCapability, ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent, type ICwdDetectionCapability } from './capabilities.js'; export class TerminalCapabilityStore extends Disposable implements ITerminalCapabilityStore { private _map: Map = new Map(); - private readonly _onDidAddCapabilityType = this._register(new Emitter()); - readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; private readonly _onDidRemoveCapability = this._register(new Emitter()); @@ -24,6 +21,12 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa get onDidRemoveCommandDetectionCapability() { return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); } + get onDidAddCwdDetectionCapability() { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection), e => e.capability as ICwdDetectionCapability); + } + get onDidRemoveCwdDetectionCapability() { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection), () => void 0); + } get items(): IterableIterator { return this._map.keys(); @@ -38,7 +41,6 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa add(capability: T, impl: ITerminalCapabilityImplMap[T]) { this._map.set(capability, impl); - this._onDidAddCapabilityType.fire(capability); this._onDidAddCapability.fire(createCapabilityEvent(capability, impl)); } @@ -64,9 +66,6 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa export class TerminalCapabilityStoreMultiplexer extends Disposable implements ITerminalCapabilityStore { readonly _stores: ITerminalCapabilityStore[] = []; - private readonly _onDidAddCapabilityType = this._register(new Emitter()); - readonly onDidAddCapabilityType = this._onDidAddCapabilityType.event; - private readonly _onDidAddCapability = this._register(new Emitter()); readonly onDidAddCapability = this._onDidAddCapability.event; private readonly _onDidRemoveCapability = this._register(new Emitter()); @@ -78,6 +77,12 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT get onDidRemoveCommandDetectionCapability() { return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); } + get onDidAddCwdDetectionCapability() { + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection), e => e.capability as ICwdDetectionCapability); + } + get onDidRemoveCwdDetectionCapability() { + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection), () => void 0); + } get items(): IterableIterator { return this._items(); @@ -122,10 +127,8 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT add(store: ITerminalCapabilityStore) { this._stores.push(store); for (const capability of store.items) { - this._onDidAddCapabilityType.fire(capability); this._onDidAddCapability.fire(createCapabilityEvent(capability, store.get(capability)!)); } - this._register(store.onDidAddCapabilityType(e => this._onDidAddCapabilityType.fire(e))); this._register(store.onDidAddCapability(e => this._onDidAddCapability.fire(e))); this._register(store.onDidRemoveCapability(e => this._onDidRemoveCapability.fire(e))); } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts index 2d5ede8fd61..dd836bdac4c 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts @@ -45,7 +45,7 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma // onDidChangeTerminalShellIntegration via command detection const onDidAddCommandDetection = this._store.add(this._terminalService.createOnInstanceEvent(instance => { return Event.map( - Event.filter(instance.capabilities.onDidAddCapabilityType, e => e === TerminalCapability.CommandDetection), + instance.capabilities.onDidAddCommandDetectionCapability, () => instance ); })).event; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 879ebf0b3fe..e9c79b32bf1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -894,11 +894,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._updateProcessCwd(); } }); - this._register(this.capabilities.onDidAddCapabilityType(e => { - if (e === TerminalCapability.CwdDetection) { - onKeyListener?.dispose(); - onKeyListener = undefined; - } + this._register(this.capabilities.onDidAddCwdDetectionCapability(() => { + onKeyListener?.dispose(); + onKeyListener = undefined; })); } @@ -921,11 +919,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const store = new DisposableStore(); await Promise.race([ new Promise(r => { - store.add(this.capabilities.onDidAddCapabilityType(e => { - if (e === TerminalCapability.CommandDetection) { - commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); - r(); - } + store.add(this.capabilities.onDidAddCommandDetectionCapability(e => { + commandDetection = e; + r(); })); }), timeout(2000), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c791df527b4..c25afa5f94f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -159,7 +159,8 @@ export class TerminalService extends Disposable implements ITerminalService { @memoize get onAnyInstanceSelectionChange() { return this._register(this.createOnInstanceEvent(e => e.onDidChangeSelection)).event; } @memoize get onAnyInstanceTitleChange() { return this._register(this.createOnInstanceEvent(e => e.onTitleChanged)).event; } @memoize get onAnyInstanceShellTypeChanged() { return this._register(this.createOnInstanceEvent(e => Event.map(e.onDidChangeShellType, () => e))).event; } - @memoize get onAnyInstanceAddedCapabilityType() { return this._register(this.createOnInstanceEvent(e => e.capabilities.onDidAddCapabilityType)).event; } + @memoize get onAnyInstanceAddedCapabilityType() { return this._register(this.createOnInstanceEvent(e => Event.map(e.capabilities.onDidAddCapability, e => e.id))).event; } + constructor( @IContextKeyService private _contextKeyService: IContextKeyService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts index be16b7269e4..e8b55236d66 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts @@ -18,7 +18,7 @@ suite('TerminalCapabilityStore', () => { setup(() => { capabilityStore = store.add(new TerminalCapabilityStore()); - store.add(capabilityStore.onDidAddCapabilityType(e => addEvents.push(e))); + store.add(capabilityStore.onDidAddCapability(e => addEvents.push(e.id))); store.add(capabilityStore.onDidRemoveCapability(e => removeEvents.push(e.id))); addEvents = []; removeEvents = []; @@ -72,7 +72,7 @@ suite('TerminalCapabilityStoreMultiplexer', () => { setup(() => { store = new DisposableStore(); multiplexer = store.add(new TerminalCapabilityStoreMultiplexer()); - multiplexer.onDidAddCapabilityType(e => addEvents.push(e)); + multiplexer.onDidAddCapability(e => addEvents.push(e.id)); multiplexer.onDidRemoveCapability(e => removeEvents.push(e.id)); store1 = store.add(new TerminalCapabilityStore()); store2 = store.add(new TerminalCapabilityStore()); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index 3fe7af37d57..a2dc81323d1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -202,17 +202,15 @@ export class ToolTerminalCreator { result.complete(ShellIntegrationQuality.Basic); }, 200)); } else { - store.add(instance.capabilities.onDidAddCapabilityType(e => { - if (e === TerminalCapability.CommandDetection) { - siNoneTimer.clear(); - // When command detection lights up, allow up to 200ms for the rich command - // detection sequence to come in before declaring it as basic shell - // integration. - store.add(disposableTimeout(() => { - this._logService.info(`ToolTerminalCreator#_waitForShellIntegration: Timed out 200ms, using basic SI (via listener)`); - result.complete(ShellIntegrationQuality.Basic); - }, 200)); - } + store.add(instance.capabilities.onDidAddCommandDetectionCapability(e => { + siNoneTimer.clear(); + // When command detection lights up, allow up to 200ms for the rich command + // detection sequence to come in before declaring it as basic shell + // integration. + store.add(disposableTimeout(() => { + this._logService.info(`ToolTerminalCreator#_waitForShellIntegration: Timed out 200ms, using basic SI (via listener)`); + result.complete(ShellIntegrationQuality.Basic); + }, 200)); })); } } diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index a2e38224ecc..e8808bcdb5e 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -342,10 +342,8 @@ class DevModeContribution extends Disposable implements ITerminalContribution { return; } this._state = DevModeContributionState.WaitingForCapability; - this._activeDevModeDisposables.value = this._ctx.instance.capabilities.onDidAddCapabilityType(e => { - if (e === TerminalCapability.CommandDetection) { - this._updateDevMode(); - } + this._activeDevModeDisposables.value = this._ctx.instance.capabilities.onDidAddCommandDetectionCapability(e => { + this._updateDevMode(); }); } } else { diff --git a/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts b/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts index 308705d3124..02f7760f068 100644 --- a/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/history/browser/terminal.history.contribution.ts @@ -46,24 +46,16 @@ class TerminalHistoryContribution extends Disposable implements ITerminalContrib this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(contextKeyService); - this._register(_ctx.instance.capabilities.onDidAddCapabilityType(e => { - switch (e) { + this._register(_ctx.instance.capabilities.onDidAddCapability(e => { + switch (e.id) { case TerminalCapability.CwdDetection: { - const cwdDetection = _ctx.instance.capabilities.get(TerminalCapability.CwdDetection); - if (!cwdDetection) { - return; - } - this._register(cwdDetection.onDidChangeCwd(e => { + this._register(e.capability.onDidChangeCwd(e => { this._instantiationService.invokeFunction(getDirectoryHistory)?.add(e, { remoteAuthority: _ctx.instance.remoteAuthority }); })); break; } case TerminalCapability.CommandDetection: { - const commandDetection = _ctx.instance.capabilities.get(TerminalCapability.CommandDetection); - if (!commandDetection) { - return; - } - this._register(commandDetection.onCommandFinished(e => { + this._register(e.capability.onCommandFinished(e => { if (e.command.trim().length > 0) { this._instantiationService.invokeFunction(getCommandHistory)?.add(e.command, { shellType: _ctx.instance.shellType }); } diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 64b05a73771..681c5c4bbc1 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -99,10 +99,8 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, if (commandDetectionCapability) { this._registerCommandHandlers(); } else { - this._register(this._capabilities.onDidAddCapabilityType(c => { - if (c === TerminalCapability.CommandDetection) { - this._registerCommandHandlers(); - } + this._register(this._capabilities.onDidAddCommandDetectionCapability(() => { + this._registerCommandHandlers(); })); } this._register(this._quickFixService.onDidRegisterProvider(result => this.registerCommandFinishedListener(convertToQuickFixOptions(result)))); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspTerminalModelContentProvider.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspTerminalModelContentProvider.ts index ed2fde0bec3..97da2dba72f 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspTerminalModelContentProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspTerminalModelContentProvider.ts @@ -122,13 +122,10 @@ export class LspTerminalModelContentProvider extends Disposable implements ILspT attachListener(); // Listen to onDidAddCapabilityType because command detection is not available until later - this._register(this._capabilitiesStore.onDidAddCapabilityType(e => { - if (e === TerminalCapability.CommandDetection) { - this._commandDetection = this._capabilitiesStore.get(TerminalCapability.CommandDetection); - attachListener(); - } + this._register(this._capabilitiesStore.onDidAddCommandDetectionCapability(e => { + this._commandDetection = e; + attachListener(); })); - } async provideTextContent(resource: URI): Promise { From 3e1af7fb4e352979f685e9a667cc533a9a8b10b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:34:38 +0900 Subject: [PATCH 0883/4355] Add onDidChangeCapabilities --- .../terminal/common/capabilities/capabilities.ts | 5 +++++ .../common/capabilities/terminalCapabilityStore.ts | 10 ++++++++++ .../contrib/terminal/browser/terminalEditorService.ts | 3 +-- .../contrib/terminal/browser/terminalGroup.ts | 3 +-- .../accessibility/browser/textAreaSyncAddon.ts | 3 +-- .../suggest/browser/terminalSuggestAddon.ts | 5 +---- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 0d3c2a5f583..4b78489ee3d 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -69,6 +69,11 @@ export interface ITerminalCapabilityStore { */ readonly onDidRemoveCapability: Event; + /** + * Fired when a capability if added or removed. + */ + readonly onDidChangeCapabilities: Event; + // TODO: Add onDidChangeCapabilities convenience event readonly onDidAddCommandDetectionCapability: Event; diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index ede832bf43b..cf116800ddd 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -15,6 +15,11 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; + readonly onDidChangeCapabilities = Event.map(Event.any( + this._onDidAddCapability.event, + this._onDidRemoveCapability.event + ), () => void 0); + get onDidAddCommandDetectionCapability() { return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); } @@ -71,6 +76,11 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT private readonly _onDidRemoveCapability = this._register(new Emitter()); readonly onDidRemoveCapability = this._onDidRemoveCapability.event; + readonly onDidChangeCapabilities = Event.map(Event.any( + this._onDidAddCapability.event, + this._onDidRemoveCapability.event + ), () => void 0); + get onDidAddCommandDetectionCapability() { return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 97e43abc2bb..6190a6693fa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -182,8 +182,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this._instanceDisposables.set(inputKey, [ instance.onDidFocus(this._onDidFocusInstance.fire, this._onDidFocusInstance), instance.onDisposed(this._onDidDisposeInstance.fire, this._onDidDisposeInstance), - instance.capabilities.onDidAddCapability(() => this._onDidChangeInstanceCapability.fire(instance)), - instance.capabilities.onDidRemoveCapability(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidChangeCapabilities(() => this._onDidChangeInstanceCapability.fire(instance)), ]); this.instances.push(instance); this._onDidChangeInstances.fire(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 9663cdef8a3..2a1769e883b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -361,8 +361,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._setActiveInstance(instance); this._onDidFocusInstance.fire(instance); }), - instance.capabilities.onDidAddCapability(() => this._onDidChangeInstanceCapability.fire(instance)), - instance.capabilities.onDidRemoveCapability(() => this._onDidChangeInstanceCapability.fire(instance)), + instance.capabilities.onDidChangeCapabilities(() => this._onDidChangeInstanceCapability.fire(instance)), ]); } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts index d1eff94ce31..a442b245eb9 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts @@ -30,8 +30,7 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { super(); this._register(Event.runAndSubscribe(Event.any( - this._capabilities.onDidAddCapability, - this._capabilities.onDidRemoveCapability, + this._capabilities.onDidChangeCapabilities, this._accessibilityService.onDidChangeScreenReaderOptimized, ), () => { this._refreshListeners(); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index d958334b688..94144856bb8 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -208,10 +208,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest }); } - this._register(Event.runAndSubscribe(Event.any( - this._capabilities.onDidAddCapability, - this._capabilities.onDidRemoveCapability - ), () => { + this._register(Event.runAndSubscribe(this._capabilities.onDidChangeCapabilities, () => { const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection); if (commandDetection) { if (this._promptInputModel !== commandDetection.promptInputModel) { From 4052714576d64a4c6152618a2a657d7ed04c4b36 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:37:18 +0900 Subject: [PATCH 0884/4355] Document interface --- .../platform/terminal/common/capabilities/capabilities.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 4b78489ee3d..6d218ce431e 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -74,11 +74,13 @@ export interface ITerminalCapabilityStore { */ readonly onDidChangeCapabilities: Event; - // TODO: Add onDidChangeCapabilities convenience event - + /** Fired when the command detection capability is added. */ readonly onDidAddCommandDetectionCapability: Event; + /** Fired when the command detection capability is removed. */ readonly onDidRemoveCommandDetectionCapability: Event; + /** Fired when the cwd detection capability is added. */ readonly onDidAddCwdDetectionCapability: Event; + /** Fired when the cwd detection capability is removed. */ readonly onDidRemoveCwdDetectionCapability: Event; /** From 50c8599aedf9409ff00e90f4fb99118de31ae2a8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 7 Oct 2025 10:38:10 +0200 Subject: [PATCH 0885/4355] update distro (#270148) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d62c6643171..9c97d57a761 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "15be134acbf860e28d3a27345b6c825f3c0e1c0a", + "distro": "83bba123096af4c03a5f4419dc4e7f33d14e354c", "author": { "name": "Microsoft Corporation" }, @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From c1a281fb7af7fc3ec6c9f4bba88fa0c743b69f4a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:02:24 +0200 Subject: [PATCH 0886/4355] SCM - remove the usage of `any` (#270151) --- src/vs/workbench/contrib/scm/browser/activity.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 2f84d3bf308..b527318ed40 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -151,17 +151,14 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe // Get a repository agnostic name for the status bar action, derive this from the // first command argument which is in the form of "./" - // eslint-disable-next-line local/code-no-any-casts - let repoAgnosticActionName = command.arguments?.[0]; - if (repoAgnosticActionName && typeof repoAgnosticActionName === 'string') { - repoAgnosticActionName = repoAgnosticActionName - .substring(0, repoAgnosticActionName.lastIndexOf('/')) + let repoAgnosticActionName = ''; + if (typeof command.arguments?.[0] === 'string') { + repoAgnosticActionName = command.arguments[0] + .substring(0, command.arguments[0].lastIndexOf('/')) .replace(/^(?:git\.|remoteHub\.)/, ''); if (repoAgnosticActionName.length > 1) { repoAgnosticActionName = repoAgnosticActionName[0].toLocaleUpperCase() + repoAgnosticActionName.slice(1); } - } else { - repoAgnosticActionName = ''; } const statusbarEntry: IStatusbarEntry = { From cfdbea423cd642236955ef29dca105cd5bf5e1ee Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:07:37 +0200 Subject: [PATCH 0887/4355] Try to not render above cursor when not allowing code shifting (#270152) * Try to not render above cursor when not allowing code shifting * support multiple cursors --- .../browser/model/inlineCompletionsModel.ts | 1 + .../view/inlineEdits/inlineEditWithChanges.ts | 1 + .../view/inlineEdits/inlineEditsModel.ts | 1 + .../view/inlineEdits/inlineEditsView.ts | 35 ++++++++++++++++++- .../inlineEdits/inlineEditsViewProducer.ts | 2 +- 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index b4af6407075..9683a5eaad5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -64,6 +64,7 @@ export class InlineCompletionsModel extends Disposable { // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. private readonly _selectedInlineCompletionId = observableValue(this, undefined); public readonly primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1)); + public readonly allPositions = derived(this, reader => this._positions.read(reader)); private _isAcceptingPartially = false; private readonly _appearedInsideViewport = derived(this, reader => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts index 2b1d9609b4b..675e9911fb0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts @@ -31,6 +31,7 @@ export class InlineEditWithChanges { public readonly originalText: AbstractText, public readonly edit: TextEdit, public readonly cursorPosition: Position, + public readonly multiCursorPositions: readonly Position[], public readonly commands: readonly InlineCompletionCommand[], public readonly inlineCompletion: InlineSuggestionItem ) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts index 5f035a6ab9b..a99ca8009b4 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts @@ -98,6 +98,7 @@ export class GhostTextIndicator { new StringText(''), new TextEdit([inlineCompletion.getSingleTextEdit()]), model.primaryPosition.get(), + model.allPositions.get(), inlineCompletion.source.inlineSuggestions.commands ?? [], inlineCompletion ), diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index ef0b3e1bfea..64e4fbdb7be 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -474,7 +474,16 @@ export class InlineEditsView extends Disposable { private determineRenderState(model: IInlineEditModel, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText) { const inlineEdit = model.inlineEdit; - const view = this.determineView(model, reader, diff, newText); + let view = this.determineView(model, reader, diff, newText); + + if (this._willRenderAboveCursor(reader, inlineEdit, view)) { + switch (view) { + case InlineCompletionViewKind.LineReplacement: + case InlineCompletionViewKind.WordReplacements: + view = InlineCompletionViewKind.SideBySide; + break; + } + } this._previousView = { id: this.getCacheId(model), view, editorWidth: this._editor.getLayoutInfo().width, timestamp: Date.now() }; @@ -560,6 +569,30 @@ export class InlineEditsView extends Disposable { return undefined; } + private _willRenderAboveCursor(reader: IReader, inlineEdit: InlineEditWithChanges, view: InlineCompletionViewKind): boolean { + const useCodeShifting = this._useCodeShifting.read(reader); + if (useCodeShifting === 'always') { + return false; + } + + for (const cursorPosition of inlineEdit.multiCursorPositions) { + if (view === InlineCompletionViewKind.WordReplacements && + cursorPosition.lineNumber === inlineEdit.originalLineRange.startLineNumber + 1 + ) { + return true; + } + + if (view === InlineCompletionViewKind.LineReplacement && + cursorPosition.lineNumber >= inlineEdit.originalLineRange.endLineNumberExclusive && + cursorPosition.lineNumber < inlineEdit.modifiedLineRange.endLineNumberExclusive + inlineEdit.modifiedLineRange.length + ) { + return true; + } + } + + return false; + } + private _viewHasBeenShownLongerThan(durationMs: number): boolean { const viewCreationTime = this._previousView?.timestamp; if (!viewCreationTime) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts index cbfaa632b25..8cb79a3f028 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts @@ -47,7 +47,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c const diffEdits = new TextEdit(edits); const text = new TextModelText(textModel); - return new InlineEditWithChanges(text, diffEdits, model.primaryPosition.read(undefined), inlineEdit.commands, inlineEdit.inlineCompletion); + return new InlineEditWithChanges(text, diffEdits, model.primaryPosition.read(undefined), model.allPositions.read(undefined), inlineEdit.commands, inlineEdit.inlineCompletion); }); private readonly _inlineEditModel = derived(this, reader => { From 7d91a62d62f020c6ef78eadcfde8b1c421247fec Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:19:07 +0900 Subject: [PATCH 0888/4355] Prefer lazy events --- .../capabilities/terminalCapabilityStore.ts | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index cf116800ddd..ae0892e1b44 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { memoize } from '../../../../base/common/decorators.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { ICommandDetectionCapability, ITerminalCapabilityImplMap, ITerminalCapabilityStore, TerminalCapability, type AnyTerminalCapabilityChangeEvent, type ICwdDetectionCapability } from './capabilities.js'; @@ -11,14 +12,17 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa private _map: Map = new Map(); private readonly _onDidAddCapability = this._register(new Emitter()); - readonly onDidAddCapability = this._onDidAddCapability.event; + get onDidAddCapability() { return this._onDidAddCapability.event; } private readonly _onDidRemoveCapability = this._register(new Emitter()); - readonly onDidRemoveCapability = this._onDidRemoveCapability.event; + get onDidRemoveCapability() { return this._onDidRemoveCapability.event; } - readonly onDidChangeCapabilities = Event.map(Event.any( - this._onDidAddCapability.event, - this._onDidRemoveCapability.event - ), () => void 0); + @memoize + get onDidChangeCapabilities() { + return Event.map(Event.any( + this._onDidAddCapability.event, + this._onDidRemoveCapability.event + ), () => void 0); + } get onDidAddCommandDetectionCapability() { return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); @@ -72,14 +76,17 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT readonly _stores: ITerminalCapabilityStore[] = []; private readonly _onDidAddCapability = this._register(new Emitter()); - readonly onDidAddCapability = this._onDidAddCapability.event; + get onDidAddCapability() { return this._onDidAddCapability.event; } private readonly _onDidRemoveCapability = this._register(new Emitter()); - readonly onDidRemoveCapability = this._onDidRemoveCapability.event; + get onDidRemoveCapability() { return this._onDidRemoveCapability.event; } - readonly onDidChangeCapabilities = Event.map(Event.any( - this._onDidAddCapability.event, - this._onDidRemoveCapability.event - ), () => void 0); + @memoize + get onDidChangeCapabilities() { + return Event.map(Event.any( + this._onDidAddCapability.event, + this._onDidRemoveCapability.event + ), () => void 0); + } get onDidAddCommandDetectionCapability() { return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); From 487166588bef7cc905743deec31a492c12f1d35e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:29:56 +0200 Subject: [PATCH 0889/4355] Workspace - remove the use of `any` in tests (#270159) --- .../workspaces/test/common/workspaceTrust.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts b/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts index 1590684cd58..9463527ef12 100644 --- a/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts +++ b/src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts @@ -23,13 +23,14 @@ import { UriIdentityService } from '../../../../../platform/uriIdentity/common/u import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService, WORKSPACE_TRUST_STORAGE_KEY } from '../../common/workspaceTrust.js'; import { TestContextService, TestStorageService, TestWorkspaceTrustEnablementService } from '../../../../test/common/workbenchTestServices.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { Mutable } from '../../../../../base/common/types.js'; suite('Workspace Trust', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; let configurationService: TestConfigurationService; - let environmentService: IWorkbenchEnvironmentService; + let environmentService: Mutable; setup(async () => { instantiationService = store.add(new TestInstantiationService()); @@ -109,8 +110,7 @@ suite('Workspace Trust', () => { const trustInfo: IWorkspaceTrustInfo = { uriTrustInfo: [{ uri: URI.parse('file:///Folder'), trusted: true }] }; storageService.store(WORKSPACE_TRUST_STORAGE_KEY, JSON.stringify(trustInfo), StorageScope.APPLICATION, StorageTarget.MACHINE); - // eslint-disable-next-line local/code-no-any-casts - (environmentService as any).filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/file.txt') }]; + environmentService.filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/file.txt') }]; instantiationService.stub(IWorkbenchEnvironmentService, { ...environmentService }); workspaceService.setWorkspace(new Workspace('empty-workspace')); @@ -122,8 +122,7 @@ suite('Workspace Trust', () => { test('empty workspace - trusted, open untrusted file', async () => { await configurationService.setUserConfiguration('security', getUserSettings(true, true)); - // eslint-disable-next-line local/code-no-any-casts - (environmentService as any).filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/foo.txt') }]; + environmentService.filesToOpenOrCreate = [{ fileUri: URI.parse('file:///Folder/foo.txt') }]; instantiationService.stub(IWorkbenchEnvironmentService, { ...environmentService }); workspaceService.setWorkspace(new Workspace('empty-workspace')); From cde7c66781e67eca13ae4d5bac444ca26f403eb6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:31:02 +0900 Subject: [PATCH 0890/4355] Add tests and ensure events are memoized/cleaned --- .../capabilities/terminalCapabilityStore.ts | 30 +++++++----- .../terminalCapabilityStore.test.ts | 49 ++++++++++++++----- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts index ae0892e1b44..bd2ea5efc9e 100644 --- a/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts +++ b/src/vs/platform/terminal/common/capabilities/terminalCapabilityStore.ts @@ -21,20 +21,23 @@ export class TerminalCapabilityStore extends Disposable implements ITerminalCapa return Event.map(Event.any( this._onDidAddCapability.event, this._onDidRemoveCapability.event - ), () => void 0); + ), () => void 0, this._store); } - + @memoize get onDidAddCommandDetectionCapability() { - return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection, this._store), e => e.capability as ICommandDetectionCapability, this._store); } + @memoize get onDidRemoveCommandDetectionCapability() { - return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection, this._store), () => void 0, this._store); } + @memoize get onDidAddCwdDetectionCapability() { - return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection), e => e.capability as ICwdDetectionCapability); + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection, this._store), e => e.capability as ICwdDetectionCapability, this._store); } + @memoize get onDidRemoveCwdDetectionCapability() { - return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection), () => void 0); + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection, this._store), () => void 0, this._store); } get items(): IterableIterator { @@ -85,20 +88,23 @@ export class TerminalCapabilityStoreMultiplexer extends Disposable implements IT return Event.map(Event.any( this._onDidAddCapability.event, this._onDidRemoveCapability.event - ), () => void 0); + ), () => void 0, this._store); } - + @memoize get onDidAddCommandDetectionCapability() { - return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection), e => e.capability as ICommandDetectionCapability); + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CommandDetection, this._store), e => e.capability as ICommandDetectionCapability, this._store); } + @memoize get onDidRemoveCommandDetectionCapability() { - return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection), () => void 0); + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CommandDetection, this._store), () => void 0, this._store); } + @memoize get onDidAddCwdDetectionCapability() { - return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection), e => e.capability as ICwdDetectionCapability); + return Event.map(Event.filter(this.onDidAddCapability, e => e.id === TerminalCapability.CwdDetection, this._store), e => e.capability as ICwdDetectionCapability, this._store); } + @memoize get onDidRemoveCwdDetectionCapability() { - return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection), () => void 0); + return Event.map(Event.filter(this.onDidRemoveCapability, e => e.id === TerminalCapability.CwdDetection, this._store), () => void 0, this._store); } get items(): IterableIterator { diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts index e8b55236d66..c8ceaade120 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { deepStrictEqual } from 'assert'; -import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { deepStrictEqual, strictEqual } from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; -import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { TerminalCapability, type ITerminalCapabilityStore } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalCapabilityStore, TerminalCapabilityStoreMultiplexer } from '../../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; suite('TerminalCapabilityStore', () => { @@ -24,8 +23,6 @@ suite('TerminalCapabilityStore', () => { removeEvents = []; }); - teardown(() => capabilityStore.dispose()); - test('should fire events when capabilities are added', () => { assertEvents(addEvents, []); // eslint-disable-next-line local/code-no-any-casts @@ -59,10 +56,21 @@ suite('TerminalCapabilityStore', () => { capabilityStore.remove(TerminalCapability.CwdDetection); deepStrictEqual(Array.from(capabilityStore.items), [TerminalCapability.NaiveCwdDetection]); }); + test('ensure events are memoized', () => { + for (const getEvent of getDerivedEventGetters(capabilityStore)) { + strictEqual(getEvent(), getEvent()); + } + }); + test('ensure events are cleaned up', () => { + for (const getEvent of getDerivedEventGetters(capabilityStore)) { + store.add(getEvent()(() => { })); + } + }); }); suite('TerminalCapabilityStoreMultiplexer', () => { - let store: DisposableStore; + const store = ensureNoDisposablesAreLeakedInTestSuite(); + let multiplexer: TerminalCapabilityStoreMultiplexer; let store1: TerminalCapabilityStore; let store2: TerminalCapabilityStore; @@ -70,20 +78,15 @@ suite('TerminalCapabilityStoreMultiplexer', () => { let removeEvents: TerminalCapability[]; setup(() => { - store = new DisposableStore(); multiplexer = store.add(new TerminalCapabilityStoreMultiplexer()); - multiplexer.onDidAddCapability(e => addEvents.push(e.id)); - multiplexer.onDidRemoveCapability(e => removeEvents.push(e.id)); + store.add(multiplexer.onDidAddCapability(e => addEvents.push(e.id))); + store.add(multiplexer.onDidRemoveCapability(e => removeEvents.push(e.id))); store1 = store.add(new TerminalCapabilityStore()); store2 = store.add(new TerminalCapabilityStore()); addEvents = []; removeEvents = []; }); - teardown(() => store.dispose()); - - ensureNoDisposablesAreLeakedInTestSuite(); - test('should fire events when capabilities are enabled', async () => { assertEvents(addEvents, []); multiplexer.add(store1); @@ -144,9 +147,29 @@ suite('TerminalCapabilityStoreMultiplexer', () => { store1.remove(TerminalCapability.CwdDetection); deepStrictEqual(multiplexer.has(TerminalCapability.CwdDetection), false); }); + test('ensure events are memoized', () => { + for (const getEvent of getDerivedEventGetters(multiplexer)) { + strictEqual(getEvent(), getEvent()); + } + }); + test('ensure events are cleaned up', () => { + for (const getEvent of getDerivedEventGetters(multiplexer)) { + store.add(getEvent()(() => { })); + } + }); }); function assertEvents(actual: TerminalCapability[], expected: TerminalCapability[]) { deepStrictEqual(actual, expected); actual.length = 0; } + +function getDerivedEventGetters(capabilityStore: ITerminalCapabilityStore) { + return [ + () => capabilityStore.onDidChangeCapabilities, + () => capabilityStore.onDidAddCommandDetectionCapability, + () => capabilityStore.onDidRemoveCommandDetectionCapability, + () => capabilityStore.onDidAddCwdDetectionCapability, + () => capabilityStore.onDidRemoveCwdDetectionCapability, + ]; +} From 6a450555cc8bd4df57a2df86b8c6f94260d53d5a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 7 Oct 2025 11:38:20 +0200 Subject: [PATCH 0891/4355] remove old versions and adopt to latest (#270153) * remove usage of old versions * remove any usage * adopt latest version * update status --- .../platform/mcp/common/mcpGalleryService.ts | 959 ++---------------- 1 file changed, 65 insertions(+), 894 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index efaa31eb4c9..4bf369ff346 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -61,865 +61,27 @@ interface IGalleryMcpServersResult { interface IRawGalleryMcpServer { readonly name: string; readonly description: string; - readonly repository?: { - readonly source: string; - readonly url: string; - readonly id?: string; - readonly readme?: string; - }; - readonly version: string; - readonly status?: GalleryMcpServerStatus; - readonly websiteUrl?: string; - readonly createdAt: string; - readonly updatedAt: string; - readonly packages?: readonly IMcpServerPackage[]; - readonly remotes?: ReadonlyArray; - readonly registryInfo: IMcpRegistryInfo; - readonly githubInfo?: IGitHubInfo; -} - -interface IGalleryMcpServerDataSerializer { - toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined; - toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined; -} - -namespace McpServerOldSchema { - - interface RawGalleryMcpServerInput { - readonly description?: string; - readonly is_required?: boolean; - readonly format?: 'string' | 'number' | 'boolean' | 'filepath'; - readonly value?: string; - readonly is_secret?: boolean; - readonly default?: string; - readonly choices?: readonly string[]; - } - - interface RawGalleryMcpServerVariableInput extends RawGalleryMcpServerInput { - readonly variables?: Record; - } - - interface RawGalleryMcpServerPositionalArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'positional'; - readonly value_hint?: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerNamedArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'named'; - readonly name: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerKeyValueInput extends RawGalleryMcpServerVariableInput { - readonly name: string; - readonly value?: string; - } - - type RawGalleryMcpServerArgument = RawGalleryMcpServerPositionalArgument | RawGalleryMcpServerNamedArgument; - - interface McpServerDeprecatedRemote { - readonly transport_type?: 'streamable' | 'sse'; - readonly transport?: 'streamable' | 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - type RawGalleryMcpServerRemotes = ReadonlyArray; - - type RawGalleryTransport = RawGalleryStdioTransport | RawGalleryStreamableHttpTransport | RawGallerySseTransport; - - interface RawGalleryStdioTransport { - readonly type: 'stdio'; - } - - interface RawGalleryStreamableHttpTransport { - readonly type: 'streamable-http'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface RawGallerySseTransport { - readonly type: 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface RawGalleryMcpServerPackage { - readonly registry_name: string; - readonly name: string; - readonly registry_type: 'npm' | 'pypi' | 'docker-hub' | 'nuget' | 'remote' | 'mcpb'; - readonly registry_base_url?: string; - readonly identifier: string; - readonly version: string; - readonly file_sha256?: string; - readonly transport?: RawGalleryTransport; - readonly package_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly runtime_hint?: string; - readonly runtime_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly environment_variables?: ReadonlyArray; - } - - interface RawGalleryMcpServer { - readonly server: { - readonly id: string; - readonly name: string; - readonly description: string; - readonly version_detail: { - readonly version: string; - readonly release_date: string; - readonly is_latest: boolean; - }; - readonly repository?: { - readonly source: string; - readonly url: string; - readonly id?: string; - readonly subfolder?: string; - readonly readme?: string; - }; - readonly created_at: string; - readonly updated_at: string; - readonly packages?: readonly RawGalleryMcpServerPackage[]; - readonly remotes?: RawGalleryMcpServerRemotes; - }; - readonly 'x-io.modelcontextprotocol.registry': { - readonly id: string; - readonly is_latest: boolean; - readonly published_at: string; - readonly updated_at: string; - readonly release_date?: string; - }; - readonly 'x-github'?: IGitHubInfo; - } - - interface RawGalleryMcpServersResult { - readonly metadata?: { - readonly count: number; - readonly total?: number; - readonly next_cursor?: string; - }; - readonly servers: readonly RawGalleryMcpServer[]; - } - - class Serializer implements IGalleryMcpServerDataSerializer { - - public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { - if (!input || !Array.isArray(input.servers)) { - return undefined; - } - - const from = input; - - const servers: IRawGalleryMcpServer[] = []; - for (const server of from.servers) { - const rawServer = this.toRawGalleryMcpServer(server); - if (!rawServer) { - return undefined; - } - servers.push(rawServer); - } - - return { - metadata: from.metadata, - servers - }; - } - - public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || !input.server) { - return undefined; - } - - const from = input; - const registryInfo = from['x-io.modelcontextprotocol.registry']; - - function convertServerInput(input: RawGalleryMcpServerInput): IMcpServerInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - }; - } - - function convertVariables(variables: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(variables)) { - result[key] = convertServerInput(value); - } - return result; - } - - function convertServerArgument(arg: RawGalleryMcpServerArgument): IMcpServerArgument { - if (arg.type === 'positional') { - return { - ...arg, - valueHint: arg.value_hint, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - return { - ...arg, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - - function convertKeyValueInput(input: RawGalleryMcpServerKeyValueInput): IMcpServerKeyValueInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - variables: input.variables ? convertVariables(input.variables) : undefined, - }; - } - - function convertTransport(input: RawGalleryTransport): Transport | undefined { - switch (input.type) { - case 'stdio': - return { - type: TransportType.STDIO, - }; - case 'streamable-http': - return { - type: TransportType.STREAMABLE_HTTP, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - case 'sse': - return { - type: TransportType.SSE, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - default: - return undefined; - } - } - - function convertRegistryType(input: string): RegistryType { - switch (input) { - case 'npm': - return RegistryType.NODE; - case 'docker': - case 'docker-hub': - case 'oci': - return RegistryType.DOCKER; - case 'pypi': - return RegistryType.PYTHON; - case 'nuget': - return RegistryType.NUGET; - case 'mcpb': - return RegistryType.MCPB; - default: - return RegistryType.NODE; - } - } - - return { - name: from.server.name, - description: from.server.description, - repository: from.server.repository ? { - url: from.server.repository.url, - source: from.server.repository.source, - id: from.server.repository.id, - readme: from.server.repository.readme - } : undefined, - version: from.server.version_detail.version, - createdAt: from.server.created_at, - updatedAt: from.server.updated_at, - packages: from.server.packages?.map(p => ({ - identifier: p.identifier ?? p.name, - registryType: convertRegistryType(p.registry_type ?? p.registry_name), - version: p.version, - fileSha256: p.file_sha256, - registryBaseUrl: p.registry_base_url, - transport: p.transport ? convertTransport(p.transport) : undefined, - packageArguments: p.package_arguments?.map(convertServerArgument), - runtimeHint: p.runtime_hint, - runtimeArguments: p.runtime_arguments?.map(convertServerArgument), - environmentVariables: p.environment_variables?.map(convertKeyValueInput), - })), - remotes: from.server.remotes?.map(remote => { - const type = (remote).type ?? (remote).transport_type ?? (remote).transport; - return { - type: type === TransportType.SSE ? TransportType.SSE : TransportType.STREAMABLE_HTTP, - url: remote.url, - headers: remote.headers?.map(convertKeyValueInput) - }; - }), - registryInfo: { - id: registryInfo.id, - isLatest: registryInfo.is_latest, - publishedAt: registryInfo.published_at, - updatedAt: registryInfo.updated_at, - }, - githubInfo: from['x-github'], - }; - } - } - - export const SERIALIZER = new Serializer(); -} - -namespace McpServer1ESSchema { - - interface RawGalleryMcpServerInput { - readonly description?: string; - readonly is_required?: boolean; - readonly format?: 'string' | 'number' | 'boolean' | 'filepath'; - readonly value?: string; - readonly is_secret?: boolean; - readonly default?: string; - readonly choices?: readonly string[]; - } - - interface RawGalleryMcpServerVariableInput extends RawGalleryMcpServerInput { - readonly variables?: Record; - } - - interface RawGalleryMcpServerPositionalArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'positional'; - readonly value_hint?: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerNamedArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'named'; - readonly name: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerKeyValueInput extends RawGalleryMcpServerVariableInput { - readonly name: string; - readonly value?: string; - } - - type RawGalleryMcpServerArgument = RawGalleryMcpServerPositionalArgument | RawGalleryMcpServerNamedArgument; - - interface McpServerDeprecatedRemote { - readonly transport_type?: 'streamable' | 'sse'; - readonly transport?: 'streamable' | 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - type RawGalleryMcpServerRemotes = ReadonlyArray; - - type RawGalleryTransport = RawGalleryStdioTransport | RawGalleryStreamableHttpTransport | RawGallerySseTransport; - - interface RawGalleryStdioTransport { - readonly type: 'stdio'; - } - - interface RawGalleryStreamableHttpTransport { - readonly type: 'streamable-http'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface RawGallerySseTransport { - readonly type: 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface RawGalleryMcpServerPackage { - readonly registry_name: string; - readonly name: string; - readonly registry_type: 'npm' | 'pypi' | 'docker-hub' | 'nuget' | 'remote' | 'mcpb'; - readonly registry_base_url?: string; - readonly identifier: string; - readonly version: string; - readonly file_sha256?: string; - readonly transport?: RawGalleryTransport; - readonly package_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly runtime_hint?: string; - readonly runtime_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly environment_variables?: ReadonlyArray; - } - - interface RawGalleryMcpServer { - readonly id: string; - readonly name: string; - readonly description: string; - readonly version_detail: { - readonly version: string; - readonly release_date: string; - readonly is_latest: boolean; - }; - readonly repository?: { - readonly source: string; - readonly url: string; - readonly id?: string; - readonly subfolder?: string; - readonly readme?: string; - }; - readonly created_at: string; - readonly updated_at: string; - readonly packages?: readonly RawGalleryMcpServerPackage[]; - readonly remotes?: RawGalleryMcpServerRemotes; - } - - interface RawGalleryMcpServersResult { - readonly metadata?: { - readonly count: number; - readonly total?: number; - readonly next_cursor?: string; - }; - readonly servers: readonly RawGalleryMcpServer[]; - } - - class Serializer implements IGalleryMcpServerDataSerializer { - - public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { - if (!input || !Array.isArray(input.servers)) { - return undefined; - } - - const from = input; - - const servers: IRawGalleryMcpServer[] = []; - for (const server of from.servers) { - const rawServer = this.toRawGalleryMcpServer(server); - if (!rawServer) { - return undefined; - } - servers.push(rawServer); - } - - return { - metadata: from.metadata, - servers - }; - } - - public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || input.server || input.$schema) { - return undefined; - } - - const from = input; - - function convertServerInput(input: RawGalleryMcpServerInput): IMcpServerInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - }; - } - - function convertVariables(variables: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(variables)) { - result[key] = convertServerInput(value); - } - return result; - } - - function convertServerArgument(arg: RawGalleryMcpServerArgument): IMcpServerArgument { - if (arg.type === 'positional') { - return { - ...arg, - valueHint: arg.value_hint, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - return { - ...arg, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - - function convertKeyValueInput(input: RawGalleryMcpServerKeyValueInput): IMcpServerKeyValueInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - variables: input.variables ? convertVariables(input.variables) : undefined, - }; - } - - function convertTransport(input: RawGalleryTransport): Transport | undefined { - switch (input.type) { - case 'stdio': - return { - type: TransportType.STDIO, - }; - case 'streamable-http': - return { - type: TransportType.STREAMABLE_HTTP, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - case 'sse': - return { - type: TransportType.SSE, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - default: - return undefined; - } - } - - function convertRegistryType(input: string): RegistryType { - switch (input) { - case 'npm': - return RegistryType.NODE; - case 'docker': - case 'docker-hub': - case 'oci': - return RegistryType.DOCKER; - case 'pypi': - return RegistryType.PYTHON; - case 'nuget': - return RegistryType.NUGET; - case 'mcpb': - return RegistryType.MCPB; - default: - return RegistryType.NODE; - } - } - - return { - name: from.name, - description: from.description, - repository: from.repository ? { - url: from.repository.url, - source: from.repository.source, - id: from.repository.id, - readme: from.repository.readme - } : undefined, - version: from.version_detail.version, - createdAt: from.created_at, - updatedAt: from.updated_at, - packages: from.packages?.map(p => ({ - identifier: p.identifier ?? p.name, - registryType: convertRegistryType(p.registry_type ?? p.registry_name), - version: p.version, - fileSha256: p.file_sha256, - registryBaseUrl: p.registry_base_url, - transport: p.transport ? convertTransport(p.transport) : undefined, - packageArguments: p.package_arguments?.map(convertServerArgument), - runtimeHint: p.runtime_hint, - runtimeArguments: p.runtime_arguments?.map(convertServerArgument), - environmentVariables: p.environment_variables?.map(convertKeyValueInput), - })), - remotes: from.remotes?.map(remote => { - const type = (remote).type ?? (remote).transport_type ?? (remote).transport; - return { - type: type === TransportType.SSE ? TransportType.SSE : TransportType.STREAMABLE_HTTP, - url: remote.url, - headers: remote.headers?.map(convertKeyValueInput) - }; - }), - registryInfo: { - id: from.id, - isLatest: true, - updatedAt: from.updated_at, - }, - }; - } - } - - export const SERIALIZER = new Serializer(); -} - -namespace McpServerSchemaVersion_2025_01_09 { - - export const VERSION = '2025-09-01'; - export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/${VERSION}/server.schema.json`; - - interface RawGalleryMcpServerInput { - readonly description?: string; - readonly is_required?: boolean; - readonly format?: 'string' | 'number' | 'boolean' | 'filepath'; - readonly value?: string; - readonly is_secret?: boolean; - readonly default?: string; - readonly choices?: readonly string[]; - } - - interface RawGalleryMcpServerVariableInput extends RawGalleryMcpServerInput { - readonly variables?: Record; - } - - interface RawGalleryMcpServerPositionalArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'positional'; - readonly value_hint?: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerNamedArgument extends RawGalleryMcpServerVariableInput { - readonly type: 'named'; - readonly name: string; - readonly is_repeated?: boolean; - } - - interface RawGalleryMcpServerKeyValueInput extends RawGalleryMcpServerVariableInput { - readonly name: string; - readonly value?: string; - } - - type RawGalleryMcpServerArgument = RawGalleryMcpServerPositionalArgument | RawGalleryMcpServerNamedArgument; - - interface McpServerDeprecatedRemote { - readonly transport_type?: 'streamable' | 'sse'; - readonly transport?: 'streamable' | 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - type RawGalleryMcpServerRemotes = ReadonlyArray; - - type RawGalleryTransport = StdioTransport | StreamableHttpTransport | SseTransport; - - interface StdioTransport { - readonly type: 'stdio'; - } - - interface StreamableHttpTransport { - readonly type: 'streamable-http' | 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface SseTransport { - readonly type: 'sse'; - readonly url: string; - readonly headers?: ReadonlyArray; - } - - interface RawGalleryMcpServerPackage { - readonly registry_name: string; - readonly name: string; - readonly registry_type: 'npm' | 'pypi' | 'docker-hub' | 'nuget' | 'remote' | 'mcpb'; - readonly registry_base_url?: string; - readonly identifier: string; - readonly version: string; - readonly file_sha256?: string; - readonly transport?: RawGalleryTransport; - readonly package_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly runtime_hint?: string; - readonly runtime_arguments?: readonly RawGalleryMcpServerArgument[]; - readonly environment_variables?: ReadonlyArray; - } - - interface RawGalleryMcpServer { - readonly $schema: string; - readonly name: string; - readonly description: string; - readonly status?: 'active' | 'deprecated'; - readonly repository?: { - readonly source: string; - readonly url: string; - readonly id?: string; - readonly readme?: string; - }; - readonly version_detail: { - readonly version: string; - readonly release_date: string; - readonly is_latest: boolean; - }; - readonly created_at: string; - readonly updated_at: string; - readonly packages?: readonly RawGalleryMcpServerPackage[]; - readonly remotes?: RawGalleryMcpServerRemotes; - readonly _meta: { - readonly 'io.modelcontextprotocol.registry': { - readonly id: string; - readonly is_latest: boolean; - readonly published_at: string; - readonly updated_at: string; - readonly release_date?: string; - }; - readonly github?: IGitHubInfo; - }; - } - - interface RawGalleryMcpServersResult { - readonly metadata?: { - readonly count: number; - readonly total?: number; - readonly next_cursor?: string; - }; - readonly servers: readonly RawGalleryMcpServer[]; - } - - class Serializer implements IGalleryMcpServerDataSerializer { - - public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { - if (!input || !Array.isArray(input.servers)) { - return undefined; - } - - const from = input; - - const servers: IRawGalleryMcpServer[] = []; - for (const server of from.servers) { - const rawServer = this.toRawGalleryMcpServer(server); - if (!rawServer) { - return undefined; - } - servers.push(rawServer); - } - - return { - metadata: from.metadata, - servers - }; - } - - public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || (input).$schema !== McpServerSchemaVersion_2025_01_09.SCHEMA) { - return undefined; - } - - const from = input; - const registryInfo = from._meta?.['io.modelcontextprotocol.registry']; - - function convertServerInput(input: RawGalleryMcpServerInput): IMcpServerInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - }; - } - - function convertVariables(variables: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(variables)) { - result[key] = convertServerInput(value); - } - return result; - } - - function convertServerArgument(arg: RawGalleryMcpServerArgument): IMcpServerArgument { - if (arg.type === 'positional') { - return { - ...arg, - valueHint: arg.value_hint, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - return { - ...arg, - isRepeated: arg.is_repeated, - isRequired: arg.is_required, - isSecret: arg.is_secret, - variables: arg.variables ? convertVariables(arg.variables) : undefined, - }; - } - - function convertKeyValueInput(input: RawGalleryMcpServerKeyValueInput): IMcpServerKeyValueInput { - return { - ...input, - isRequired: input.is_required, - isSecret: input.is_secret, - variables: input.variables ? convertVariables(input.variables) : undefined, - }; - } - - function convertTransport(input: RawGalleryTransport): Transport | undefined { - switch (input.type) { - case 'stdio': - return { - type: TransportType.STDIO, - }; - case 'streamable-http': - return { - type: TransportType.STREAMABLE_HTTP, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - case 'sse': - return { - type: TransportType.SSE, - url: input.url, - headers: input.headers?.map(convertKeyValueInput), - }; - default: - return undefined; - } - } - - function convertRegistryType(input: string): RegistryType { - switch (input) { - case 'npm': - return RegistryType.NODE; - case 'docker': - case 'docker-hub': - case 'oci': - return RegistryType.DOCKER; - case 'pypi': - return RegistryType.PYTHON; - case 'nuget': - return RegistryType.NUGET; - case 'mcpb': - return RegistryType.MCPB; - default: - return RegistryType.NODE; - } - } - - return { - name: from.name, - description: from.description, - repository: from.repository ? { - url: from.repository.url, - source: from.repository.source, - id: from.repository.id, - readme: from.repository.readme - } : undefined, - version: from.version_detail.version, - createdAt: from.created_at, - updatedAt: from.updated_at, - packages: from.packages?.map(p => ({ - identifier: p.identifier ?? p.name, - registryType: convertRegistryType(p.registry_type ?? p.registry_name), - version: p.version, - fileSha256: p.file_sha256, - registryBaseUrl: p.registry_base_url, - transport: p.transport ? convertTransport(p.transport) : undefined, - packageArguments: p.package_arguments?.map(convertServerArgument), - runtimeHint: p.runtime_hint, - runtimeArguments: p.runtime_arguments?.map(convertServerArgument), - environmentVariables: p.environment_variables?.map(convertKeyValueInput), - })), - remotes: from.remotes?.map(remote => { - const type = (remote).type ?? (remote).transport_type ?? (remote).transport; - return { - type: type === TransportType.SSE ? TransportType.SSE : TransportType.STREAMABLE_HTTP, - url: remote.url, - headers: remote.headers?.map(convertKeyValueInput) - }; - }), - registryInfo: { - id: registryInfo.id, - isLatest: registryInfo.is_latest, - publishedAt: registryInfo.published_at, - updatedAt: registryInfo.updated_at, - }, - githubInfo: from._meta.github, - }; - } - } + readonly version: string; + readonly title?: string; + readonly repository?: { + readonly source: string; + readonly url: string; + readonly id?: string; + readonly readme?: string; + }; + readonly status?: GalleryMcpServerStatus; + readonly websiteUrl?: string; + readonly createdAt?: string; + readonly updatedAt?: string; + readonly packages?: readonly IMcpServerPackage[]; + readonly remotes?: ReadonlyArray; + readonly registryInfo: IMcpRegistryInfo; + readonly githubInfo?: IGitHubInfo; +} - export const SERIALIZER = new Serializer(); +interface IGalleryMcpServerDataSerializer { + toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined; + toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined; } namespace McpServerSchemaVersion_2025_07_09 { @@ -1205,9 +367,9 @@ namespace McpServerSchemaVersion_2025_07_09 { export const SERIALIZER = new Serializer(); } -namespace McpServerSchemaVersion_2025_16_09 { +namespace McpServerSchemaVersion_2025_29_09 { - export const VERSION = '2025-16-09'; + export const VERSION = '2025-09-29'; export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/${VERSION}/server.schema.json`; interface RawGalleryMcpServerInput { @@ -1217,6 +379,7 @@ namespace McpServerSchemaVersion_2025_16_09 { readonly value?: string; readonly isSecret?: boolean; readonly default?: string; + readonly placeholder?: string; readonly choices?: readonly string[]; } @@ -1264,13 +427,12 @@ namespace McpServerSchemaVersion_2025_16_09 { } interface RawGalleryMcpServerPackage { - readonly registryName: string; readonly registryType: RegistryType; - readonly registryBaseUrl?: string; readonly identifier: string; readonly version: string; + readonly transport: RawGalleryTransport; + readonly registryBaseUrl?: string; readonly fileSha256?: string; - readonly transport?: RawGalleryTransport; readonly packageArguments?: readonly RawGalleryMcpServerArgument[]; readonly runtimeHint?: string; readonly runtimeArguments?: readonly RawGalleryMcpServerArgument[]; @@ -1281,19 +443,24 @@ namespace McpServerSchemaVersion_2025_16_09 { readonly $schema: string; readonly name: string; readonly description: string; - readonly status?: GalleryMcpServerStatus; + readonly version: string; + readonly title?: string; readonly repository?: { readonly source: string; readonly url: string; readonly id?: string; readonly readme?: string; }; - readonly version: string; readonly websiteUrl?: string; - readonly createdAt: string; - readonly updatedAt: string; readonly packages?: readonly RawGalleryMcpServerPackage[]; readonly remotes?: RawGalleryMcpServerRemotes; + readonly _meta: { + readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; + }; + } + + interface RawGalleryMcpServerInfo { + readonly server: RawGalleryMcpServer; readonly _meta: { readonly 'io.modelcontextprotocol.registry/official': { readonly id: string; @@ -1301,8 +468,8 @@ namespace McpServerSchemaVersion_2025_16_09 { readonly publishedAt: string; readonly updatedAt: string; readonly releaseDate?: string; + readonly status?: GalleryMcpServerStatus; }; - readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; }; } @@ -1312,7 +479,7 @@ namespace McpServerSchemaVersion_2025_16_09 { readonly total?: number; readonly next_cursor?: string; }; - readonly servers: readonly RawGalleryMcpServer[]; + readonly servers: readonly RawGalleryMcpServerInfo[]; } class Serializer implements IGalleryMcpServerDataSerializer { @@ -1340,30 +507,33 @@ namespace McpServerSchemaVersion_2025_16_09 { } public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || (input).$schema !== McpServerSchemaVersion_2025_16_09.SCHEMA) { + if (!input || !(input).server) { return undefined; } - const from = input; + if ((input).server.$schema && (input).server.$schema !== McpServerSchemaVersion_2025_29_09.SCHEMA) { + return undefined; + } + + const from = input; return { - name: from.name, - description: from.description, - repository: from.repository ? { - url: from.repository.url, - source: from.repository.source, - id: from.repository.id, - readme: from.repository.readme + name: from.server.name, + description: from.server.description, + version: from.server.version, + title: from.server.title, + repository: from.server.repository ? { + url: from.server.repository.url, + source: from.server.repository.source, + id: from.server.repository.id, + readme: from.server.repository.readme } : undefined, - version: from.version, - status: from.status, - websiteUrl: from.websiteUrl, - createdAt: from.createdAt, - updatedAt: from.updatedAt, - packages: from.packages, - remotes: from.remotes, - registryInfo: from._meta?.['io.modelcontextprotocol.registry/official'], - githubInfo: from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github, + websiteUrl: from.server.websiteUrl, + packages: from.server.packages, + remotes: from.server.remotes, + status: from._meta['io.modelcontextprotocol.registry/official'].status, + registryInfo: from._meta['io.modelcontextprotocol.registry/official'], + githubInfo: from.server._meta['io.modelcontextprotocol.registry/publisher-provided']?.github, }; } } @@ -1415,10 +585,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService super(); this.galleryMcpServerDataSerializers = new Map(); this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_07_09.VERSION, McpServerSchemaVersion_2025_07_09.SERIALIZER); - this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_01_09.VERSION, McpServerSchemaVersion_2025_01_09.SERIALIZER); - this.galleryMcpServerDataSerializers.set('old', McpServerOldSchema.SERIALIZER); - this.galleryMcpServerDataSerializers.set('1es', McpServer1ESSchema.SERIALIZER); - this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_16_09.VERSION, McpServerSchemaVersion_2025_16_09.SERIALIZER); + this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_29_09.VERSION, McpServerSchemaVersion_2025_29_09.SERIALIZER); } isEnabled(): boolean { @@ -1549,10 +716,12 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService private toGalleryMcpServer(server: IRawGalleryMcpServer, manifest: IMcpGalleryManifest | null): IGalleryMcpServer { let publisher = ''; - let displayName = ''; + let displayName = server.title; if (server.githubInfo?.name) { - displayName = server.githubInfo.name.split('-').map(s => s.toLowerCase() === 'mcp' ? 'MCP' : s.toLowerCase() === 'github' ? 'GitHub' : uppercaseFirstLetter(s)).join(' '); + if (!displayName) { + displayName = server.githubInfo.name.split('-').map(s => s.toLowerCase() === 'mcp' ? 'MCP' : s.toLowerCase() === 'github' ? 'GitHub' : uppercaseFirstLetter(s)).join(' '); + } publisher = server.githubInfo.name_with_owner.split('/')[0]; } else { const nameParts = server.name.split('/'); @@ -1562,7 +731,9 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService publisher = domainParts[domainParts.length - 1]; // Always take the last part as owner } } - displayName = nameParts[nameParts.length - 1].split('-').map(s => uppercaseFirstLetter(s)).join(' '); + if (!displayName) { + displayName = nameParts[nameParts.length - 1].split('-').map(s => uppercaseFirstLetter(s)).join(' '); + } } if (server.githubInfo?.display_name) { From 62efd7e71cb60870e9f73bbaf69e1aed4f44f7a9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:54:24 +0200 Subject: [PATCH 0892/4355] SCM - more `any` cleanup (#270168) --- src/vs/workbench/api/common/extHostSCM.ts | 6 ++---- .../api/common/extHostTypeConverters.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 79e44be2273..43850a724c6 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -22,7 +22,7 @@ import { ExtensionIdentifierMap, IExtensionDescription } from '../../../platform import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; -import { MarkdownString } from './extHostTypeConverters.js'; +import { MarkdownString, SourceControlInputBoxValidationType } from './extHostTypeConverters.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ExtHostDocuments } from './extHostDocuments.js'; import { Schemas } from '../../../base/common/network.js'; @@ -370,9 +370,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) { checkProposedApiEnabled(this._extension, 'scmValidation'); - - // eslint-disable-next-line local/code-no-any-casts - this.#proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); + this.#proxy.$showValidationMessage(this._sourceControlHandle, message, SourceControlInputBoxValidationType.from(type)); } $onInputBoxValueChange(value: string): void { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e1fd1f64351..16882715d67 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -66,6 +66,7 @@ import { CommandsConverter } from './extHostCommands.js'; import { getPrivateApiFor } from './extHostTestingPrivateApi.js'; import * as types from './extHostTypes.js'; import { LanguageModelDataPart, LanguageModelPromptTsxPart, LanguageModelTextPart } from './extHostTypes.js'; +import { InputValidationType } from '../../contrib/scm/common/scm.js'; export namespace Command { @@ -3733,3 +3734,18 @@ export namespace McpServerDefinition { ); } } + +export namespace SourceControlInputBoxValidationType { + export function from(type: number): InputValidationType { + switch (type) { + case types.SourceControlInputBoxValidationType.Error: + return InputValidationType.Error; + case types.SourceControlInputBoxValidationType.Warning: + return InputValidationType.Warning; + case types.SourceControlInputBoxValidationType.Information: + return InputValidationType.Information; + default: + throw new Error('Unknown SourceControlInputBoxValidationType'); + } + } +} From ab8a18c04285e8d6507385e6857c4379081973ae Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 7 Oct 2025 11:47:15 +0200 Subject: [PATCH 0893/4355] chore: remove any-casts from keybindingService.ts --- .../keybinding/browser/keybindingService.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 4f78c1ac217..86a3d7785d0 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -846,7 +846,7 @@ class KeybindingsJsonSchema { private readonly commandsSchemas: IJSONSchema[] = []; private readonly commandsEnum: string[] = []; private readonly removalCommandsEnum: string[] = []; - private readonly commandsEnumDescriptions: (string | undefined)[] = []; + private readonly commandsEnumDescriptions: string[] = []; private readonly schema: IJSONSchema = { id: KeybindingsJsonSchema.schemaId, type: 'array', @@ -873,8 +873,7 @@ class KeybindingsJsonSchema { 'commandNames': { 'type': 'string', 'enum': this.commandsEnum, - // eslint-disable-next-line local/code-no-any-casts - 'enumDescriptions': this.commandsEnumDescriptions, + 'enumDescriptions': this.commandsEnumDescriptions, 'description': nls.localize('keybindings.json.command', "Name of the command to execute"), }, 'commandType': { @@ -885,8 +884,7 @@ class KeybindingsJsonSchema { { 'type': 'string', 'enum': this.removalCommandsEnum, - // eslint-disable-next-line local/code-no-any-casts - 'enumDescriptions': this.commandsEnumDescriptions, + 'enumDescriptions': this.commandsEnumDescriptions, 'description': nls.localize('keybindings.json.removalCommand', "Name of the command to remove keyboard shortcut for"), }, { @@ -962,7 +960,11 @@ class KeybindingsJsonSchema { knownCommands.add(commandId); this.commandsEnum.push(commandId); - this.commandsEnumDescriptions.push(isLocalizedString(description) ? description.value : description); + this.commandsEnumDescriptions.push( + description === undefined + ? '' // `enumDescriptions` is an array of strings, so we can't use undefined + : (isLocalizedString(description) ? description.value : description) + ); // Also add the negative form for keybinding removal this.removalCommandsEnum.push(`-${commandId}`); From 9de55d2cb13d17c26d74c306704c5d89a90e72ad Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 7 Oct 2025 11:56:53 +0200 Subject: [PATCH 0894/4355] chore: remove any-casts from keybindingLayoutService.ts --- .../services/keybinding/browser/keyboardLayoutService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts index 71a79c0b8c2..2fa44d2bbc5 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts @@ -43,8 +43,7 @@ export class BrowserKeyboardMapperFactoryBase extends Disposable { protected _keymapInfos: KeymapInfo[]; protected _mru: KeymapInfo[]; private _activeKeymapInfo: KeymapInfo | null; - // eslint-disable-next-line local/code-no-any-casts - private keyboardLayoutMapAllowed: boolean = (navigator as any).keyboard !== undefined; + private keyboardLayoutMapAllowed: boolean = (navigator as INavigatorWithKeyboard).keyboard !== undefined; get activeKeymap(): KeymapInfo | null { return this._activeKeymapInfo; @@ -399,8 +398,7 @@ export class BrowserKeyboardMapperFactoryBase extends Disposable { private async _getBrowserKeyMapping(keyboardEvent?: IKeyboardEvent): Promise { if (this.keyboardLayoutMapAllowed) { try { - // eslint-disable-next-line local/code-no-any-casts - return await (navigator as any).keyboard.getLayoutMap().then((e: any) => { + return await (navigator as INavigatorWithKeyboard).keyboard.getLayoutMap().then((e: any) => { const ret: IKeyboardMapping = {}; for (const key of e) { ret[key[0]] = { From 117e99ef245b0bf3ea8098d8a978a3771d23fd46 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Oct 2025 13:35:43 +0200 Subject: [PATCH 0895/4355] fix some --- src/vs/editor/contrib/links/browser/getLinks.ts | 2 +- .../workbench/browser/parts/editor/editorCommands.ts | 8 ++++---- .../files/browser/editors/textFileSaveErrorHandler.ts | 10 +++++++--- src/vs/workbench/contrib/files/browser/fileCommands.ts | 4 ++-- .../contrib/timeline/browser/timeline.contribution.ts | 6 +++++- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/links/browser/getLinks.ts b/src/vs/editor/contrib/links/browser/getLinks.ts index fed24b5b1a8..2f0c5e73fcd 100644 --- a/src/vs/editor/contrib/links/browser/getLinks.ts +++ b/src/vs/editor/contrib/links/browser/getLinks.ts @@ -187,7 +187,7 @@ CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...arg } // resolve links - for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) { + for (let i = 0; i < Math.min(resolveCount as number, list.links.length); i++) { await list.links[i].resolve(CancellationToken.None); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 51128210543..8e8da3e9b32 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -166,7 +166,7 @@ function registerActiveEditorMoveCopyCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args) => moveCopySelectedEditors(true, args, accessor), + handler: (accessor, args) => moveCopySelectedEditors(true, args as SelectedEditorsMoveCopyArguments | undefined, accessor), metadata: { description: localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), args: [ @@ -185,7 +185,7 @@ function registerActiveEditorMoveCopyCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args) => moveCopySelectedEditors(false, args, accessor), + handler: (accessor, args) => moveCopySelectedEditors(false, args as SelectedEditorsMoveCopyArguments | undefined, accessor), metadata: { description: localize('editorCommand.activeEditorCopy.description', "Copy the active editor by groups"), args: [ @@ -595,10 +595,10 @@ interface OpenMultiFileDiffEditorOptions { } function registerOpenEditorAtIndexCommands(): void { - const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => { + const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: unknown): void => { const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; - if (activeEditorPane) { + if (activeEditorPane && typeof editorIndex === 'number') { const editor = activeEditorPane.group.getEditorByIndex(editorIndex); if (editor) { editorService.openEditor(editor); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index c8c1f5e3314..ce11123d046 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -407,17 +407,21 @@ class ConfigureSaveConflictAction extends Action { } } -export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { +export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: unknown) => { return acceptOrRevertLocalChangesCommand(accessor, resource, true); }; -export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { +export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: unknown) => { return acceptOrRevertLocalChangesCommand(accessor, resource, false); }; -async function acceptOrRevertLocalChangesCommand(accessor: ServicesAccessor, resource: URI, accept: boolean) { +async function acceptOrRevertLocalChangesCommand(accessor: ServicesAccessor, resource: unknown, accept: boolean) { const editorService = accessor.get(IEditorService); + if (!URI.isUri(resource)) { + return; + } + const editorPane = editorService.activeEditorPane; if (!editorPane) { return; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 267b609b778..c8fc8aa2dd2 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -245,7 +245,7 @@ async function resourcesToClipboard(resources: URI[], relative: boolean, clipboa } } -const copyPathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => { +const copyPathCommandHandler: ICommandHandler = async (accessor, resource: unknown) => { const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService)); }; @@ -272,7 +272,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: copyPathCommandHandler }); -const copyRelativePathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => { +const copyRelativePathCommandHandler: ICommandHandler = async (accessor, resource: unknown) => { const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService)); }; diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index 535b637dd54..acf3e73a07c 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -21,6 +21,7 @@ import { ResourceContextKey } from '../../../common/contextkeys.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; +import { URI } from '../../../../base/common/uri.js'; const timelineViewIcon = registerIcon('timeline-view-icon', Codicon.history, localize('timelineViewIcon', 'View icon of the timeline view.')); const timelineOpenIcon = registerIcon('timeline-open', Codicon.history, localize('timelineOpenIcon', 'Icon for the open timeline action.')); @@ -72,7 +73,10 @@ namespace OpenTimelineAction { export function handler(): ICommandHandler { return (accessor, arg) => { const service = accessor.get(ITimelineService); - return service.setUri(arg); + + if (URI.isUri(arg)) { + return service.setUri(arg); + } }; } } From 6e5e327127b4f827a6db57b91f145bbd9255296f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 7 Oct 2025 13:38:19 +0200 Subject: [PATCH 0896/4355] more --- src/vs/editor/contrib/format/browser/format.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/editor/contrib/format/browser/format.ts b/src/vs/editor/contrib/format/browser/format.ts index 1d8ec55cf00..858b4d201d7 100644 --- a/src/vs/editor/contrib/format/browser/format.ts +++ b/src/vs/editor/contrib/format/browser/format.ts @@ -460,10 +460,17 @@ export function getOnTypeFormattingEdits( }); } +function isFormattingOptions(obj: any): obj is FormattingOptions { + const candidate = obj as FormattingOptions | undefined; + + return !!candidate && typeof candidate === 'object' && typeof candidate.tabSize === 'number' && typeof candidate.insertSpaces === 'boolean'; +} + CommandsRegistry.registerCommand('_executeFormatRangeProvider', async function (accessor, ...args) { const [resource, range, options] = args; assertType(URI.isUri(resource)); assertType(Range.isIRange(range)); + assertType(isFormattingOptions(options)); const resolverService = accessor.get(ITextModelService); const workerService = accessor.get(IEditorWorkerService); @@ -479,6 +486,7 @@ CommandsRegistry.registerCommand('_executeFormatRangeProvider', async function ( CommandsRegistry.registerCommand('_executeFormatDocumentProvider', async function (accessor, ...args) { const [resource, options] = args; assertType(URI.isUri(resource)); + assertType(isFormattingOptions(options)); const resolverService = accessor.get(ITextModelService); const workerService = accessor.get(IEditorWorkerService); @@ -496,6 +504,7 @@ CommandsRegistry.registerCommand('_executeFormatOnTypeProvider', async function assertType(URI.isUri(resource)); assertType(Position.isIPosition(position)); assertType(typeof ch === 'string'); + assertType(isFormattingOptions(options)); const resolverService = accessor.get(ITextModelService); const workerService = accessor.get(IEditorWorkerService); From c244c79cd42e3af9b30b4f317b6a00029c5f4d58 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:14:52 +0200 Subject: [PATCH 0897/4355] Git - remove the usages of `any` (#270179) * Git - remove the usages of `any` * Remove debug message --- .vscode/searches/no-any-casts.code-search | 7 ------- extensions/git/src/commands.ts | 3 +-- extensions/git/src/decorationProvider.ts | 12 +++++++----- extensions/git/src/util.ts | 9 ++------- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index 7eda54ff93b..8fe495b9315 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -79,13 +79,6 @@ vscode • extensions/css-language-features/server/src/cssServer.ts: 74: // eslint-disable-next-line local/code-no-any-casts 171: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/git/src/commands.ts: - 5369: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/git/src/util.ts: - 46: // eslint-disable-next-line local/code-no-any-casts - 118: // eslint-disable-next-line local/code-no-any-casts - vscode • extensions/git-base/src/api/api1.ts: 17: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index eacc616b3ef..35621e5d982 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5366,8 +5366,7 @@ export class CommandCenter { }; // patch this object, so people can call methods directly - // eslint-disable-next-line local/code-no-any-casts - (this as any)[key] = result; + (this as Record)[key] = result; return result; } diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 2e72a1e4114..763d4aaa751 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; +import { filterEvent, dispose, anyEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; import { Change, GitErrorCodes, Status } from './api/git'; function equalSourceControlHistoryItemRefs(ref1?: SourceControlHistoryItemRef, ref2?: SourceControlHistoryItemRef): boolean { @@ -25,17 +25,19 @@ class GitIgnoreDecorationProvider implements FileDecorationProvider { private static Decoration: FileDecoration = { color: new ThemeColor('gitDecoration.ignoredResourceForeground') }; - readonly onDidChangeFileDecorations: Event; + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + private queue = new Map> }>(); private disposables: Disposable[] = []; constructor(private model: Model) { - this.onDidChangeFileDecorations = fireEvent(anyEvent( + const onDidChangeRepository = anyEvent( filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)), model.onDidOpenRepository, model.onDidCloseRepository - )); - + ); + this.disposables.push(onDidChangeRepository(() => this._onDidChangeDecorations.fire(undefined))); this.disposables.push(window.registerFileDecorationProvider(this)); } diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 83346c1756b..7600a2e46ac 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -42,11 +42,6 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { export const EmptyDisposable = toDisposable(() => null); -export function fireEvent(event: Event): Event { - // eslint-disable-next-line local/code-no-any-casts - return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(_ => (listener as any).call(thisArgs), null, disposables); -} - export function mapEvent(event: Event, map: (i: I) => O): Event { return (listener: (e: O) => any, thisArgs?: any, disposables?: Disposable[]) => event(i => listener.call(thisArgs, map(i)), null, disposables); } @@ -115,8 +110,8 @@ export function once(fn: (...args: any[]) => any): (...args: any[]) => any { export function assign(destination: T, ...sources: any[]): T { for (const source of sources) { - // eslint-disable-next-line local/code-no-any-casts - Object.keys(source).forEach(key => (destination as any)[key] = source[key]); + Object.keys(source).forEach(key => + (destination as Record)[key] = source[key]); } return destination; From a728047525d2d7eea20aad858fac8dc2ff89b30f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Oct 2025 14:15:51 +0200 Subject: [PATCH 0898/4355] fix any-casts in snippet-tests, https://github.com/microsoft/vscode/issues/269213 (#270181) --- .../test/browser/snippetsService.test.ts | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 7531f6f5848..6dd26435753 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -154,8 +154,7 @@ suite('SnippetsService', function () { label: 'bar', description: 'barTest' }); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 1); + assert.strictEqual((result.suggestions[0].range as CompletionItemRanges).insert.startColumn, 1); assert.strictEqual(result.suggestions[0].insertText, 'barCodeSnippet'); }); @@ -165,8 +164,7 @@ suite('SnippetsService', function () { label: 'bar', description: 'barTest' }); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((completions.items[0].completion.range as any).insert.startColumn, 1); + assert.strictEqual((completions.items[0].completion.range as CompletionItemRanges).insert.startColumn, 1); assert.strictEqual(completions.items[0].completion.insertText, 'barCodeSnippet'); }); @@ -281,15 +279,13 @@ suite('SnippetsService', function () { description: 'barTest' }); assert.strictEqual(result.suggestions[0].insertText, 's1'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 5); + assert.strictEqual((result.suggestions[0].range as CompletionItemRanges).insert.startColumn, 5); assert.deepStrictEqual(result.suggestions[1].label, { label: 'bar-bar', description: 'name' }); assert.strictEqual(result.suggestions[1].insertText, 's2'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((result.suggestions[1].range as any).insert.startColumn, 1); + assert.strictEqual((result.suggestions[1].range as CompletionItemRanges).insert.startColumn, 1); }); const completions = await asCompletionModel(model, new Position(1, 6), provider); @@ -299,15 +295,13 @@ suite('SnippetsService', function () { description: 'name' }); assert.strictEqual(completions.items[0].completion.insertText, 's2'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((completions.items[0].completion.range as any).insert.startColumn, 1); + assert.strictEqual((completions.items[0].completion.range as CompletionItemRanges).insert.startColumn, 1); assert.deepStrictEqual(completions.items[1].completion.label, { label: 'bar', description: 'barTest' }); assert.strictEqual(completions.items[1].completion.insertText, 's1'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((completions.items[1].completion.range as any).insert.startColumn, 5); + assert.strictEqual((completions.items[1].completion.range as CompletionItemRanges).insert.startColumn, 5); } }); @@ -337,25 +331,21 @@ suite('SnippetsService', function () { model = instantiateTextModel(instantiationService, '\t { assert.strictEqual(result.suggestions.length, 1); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2); + assert.strictEqual((result.suggestions[0].range as CompletionItemRanges).insert.startColumn, 2); }); const completions2 = await asCompletionModel(model, new Position(1, 4), provider); assert.strictEqual(completions2.items.length, 1); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((completions2.items[0].completion.range as any).insert.startColumn, 2); + assert.strictEqual((completions2.items[0].completion.range as CompletionItemRanges).insert.startColumn, 2); model.dispose(); model = instantiateTextModel(instantiationService, 'a { assert.strictEqual(result.suggestions.length, 1); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2); + assert.strictEqual((result.suggestions[0].range as CompletionItemRanges).insert.startColumn, 2); }); const completions3 = await asCompletionModel(model, new Position(1, 4), provider); assert.strictEqual(completions3.items.length, 1); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((completions3.items[0].completion.range as any).insert.startColumn, 2); + assert.strictEqual((completions3.items[0].completion.range as CompletionItemRanges).insert.startColumn, 2); model.dispose(); }); @@ -656,8 +646,7 @@ suite('SnippetsService', function () { let result = await provider.provideCompletionItems(model, new Position(1, 3), defaultCompletionContext)!; assert.strictEqual(result.suggestions.length, 1); let [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.startColumn, 2); + assert.strictEqual((first.range as CompletionItemRanges).insert.startColumn, 2); let completions = await asCompletionModel(model, new Position(1, 3), provider); assert.strictEqual(completions.items.length, 1); @@ -670,8 +659,7 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.startColumn, 1); + assert.strictEqual((first.range as CompletionItemRanges).insert.startColumn, 1); assert.strictEqual(completions.items.length, 1); assert.strictEqual(completions.items[0].editStart.column, 1); @@ -698,10 +686,8 @@ suite('SnippetsService', function () { let result = await provider.provideCompletionItems(model, new Position(1, 3), defaultCompletionContext)!; assert.strictEqual(result.suggestions.length, 1); let [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.endColumn, 3); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).replace.endColumn, 9); + assert.strictEqual((first.range as CompletionItemRanges).insert.endColumn, 3); + assert.strictEqual((first.range as CompletionItemRanges).replace.endColumn, 9); let completions = await asCompletionModel(model, new Position(1, 3), provider); assert.strictEqual(completions.items.length, 1); @@ -714,10 +700,8 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.endColumn, 3); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).replace.endColumn, 3); + assert.strictEqual((first.range as CompletionItemRanges).insert.endColumn, 3); + assert.strictEqual((first.range as CompletionItemRanges).replace.endColumn, 3); completions = await asCompletionModel(model, new Position(1, 3), provider); assert.strictEqual(completions.items.length, 1); @@ -730,10 +714,8 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.endColumn, 1); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).replace.endColumn, 9); + assert.strictEqual((first.range as CompletionItemRanges).insert.endColumn, 1); + assert.strictEqual((first.range as CompletionItemRanges).replace.endColumn, 9); completions = await asCompletionModel(model, new Position(1, 1), provider); assert.strictEqual(completions.items.length, 1); @@ -765,10 +747,8 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); const [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.endColumn, 9); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).replace.endColumn, 9); + assert.strictEqual((first.range as CompletionItemRanges).insert.endColumn, 9); + assert.strictEqual((first.range as CompletionItemRanges).replace.endColumn, 9); assert.strictEqual(completions.items.length, 1); assert.strictEqual(completions.items[0].editInsertEnd.column, 9); @@ -807,11 +787,9 @@ suite('SnippetsService', function () { assert.strictEqual(result.suggestions.length, 1); const [first] = result.suggestions; - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).insert.endColumn, 5); + assert.strictEqual((first.range as CompletionItemRanges).insert.endColumn, 5); // This is 6 because it should eat the `]` at the end of the text even if cursor is before it - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((first.range as any).replace.endColumn, 6); + assert.strictEqual((first.range as CompletionItemRanges).replace.endColumn, 6); assert.strictEqual(completions.items.length, 1); assert.strictEqual(completions.items[0].editInsertEnd.column, 5); From 0d1a3c7c7c3fe6afb7350026a4012357e4050184 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Oct 2025 14:16:31 +0200 Subject: [PATCH 0899/4355] fix some any-casts in `vsocde-api-tests` (#270186) https://github.com/microsoft/vscode/issues/269213 --- .../singlefolder-tests/configuration.test.ts | 8 +++----- .../src/singlefolder-tests/env.test.ts | 20 +++++++------------ .../src/singlefolder-tests/workspace.test.ts | 12 +++++------ extensions/vscode-api-tests/src/utils.ts | 4 ++++ 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index eceec72ee86..0884e1fa95f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { assertNoRpc } from '../utils'; +import { assertNoRpc, Mutable } from '../utils'; suite('vscode API - configuration', () => { @@ -30,8 +30,7 @@ suite('vscode API - configuration', () => { assert.strictEqual(config['config0'], true); assert.strictEqual(config['config4'], ''); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (config)['config4'] = 'valuevalue'); + assert.throws(() => (config as Mutable)['config4'] = 'valuevalue'); assert.ok(config.has('nested.config1')); assert.strictEqual(config.get('nested.config1'), 42); @@ -45,7 +44,6 @@ suite('vscode API - configuration', () => { assert.ok(config.has('get')); assert.strictEqual(config.get('get'), 'get-prop'); assert.deepStrictEqual(config['get'], config.get); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => config['get'] = 'get-prop'); + assert.throws(() => (config as Mutable)['get'] = 'get-prop' as unknown as typeof config.get); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index 19b43fdf066..124c6a5030a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { env, ExtensionKind, extensions, UIKind, Uri } from 'vscode'; -import { assertNoRpc } from '../utils'; +import { assertNoRpc, Mutable } from '../utils'; suite('vscode API - env', () => { @@ -21,18 +21,12 @@ suite('vscode API - env', () => { }); test('env is readonly', function () { - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).language = '234'); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).appRoot = '234'); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).appName = '234'); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).machineId = '234'); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).sessionId = '234'); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (env as any).shell = '234'); + assert.throws(() => (env as Mutable).language = '234'); + assert.throws(() => (env as Mutable).appRoot = '234'); + assert.throws(() => (env as Mutable).appName = '234'); + assert.throws(() => (env as Mutable).machineId = '234'); + assert.throws(() => (env as Mutable).sessionId = '234'); + assert.throws(() => (env as Mutable).shell = '234'); }); test('env.remoteName', function () { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 74dcf4a6925..3bcaecb2a71 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import { basename, join, posix } from 'path'; import * as vscode from 'vscode'; import { TestFS } from '../memfs'; -import { assertNoRpc, closeAllEditors, createRandomFile, delay, deleteFile, disposeAll, pathEquals, revertAllDirty, rndName, testFs, withLogDisabled } from '../utils'; +import { assertNoRpc, closeAllEditors, createRandomFile, delay, deleteFile, disposeAll, Mutable, pathEquals, revertAllDirty, rndName, testFs, withLogDisabled } from '../utils'; suite('vscode API - workspace', () => { @@ -41,14 +41,13 @@ suite('vscode API - workspace', () => { test('textDocuments', () => { assert.ok(Array.isArray(vscode.workspace.textDocuments)); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (vscode.workspace).textDocuments = null); + assert.throws(() => (vscode.workspace as Mutable).textDocuments = null as unknown as vscode.TextDocument[]); }); test('rootPath', () => { assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); - // eslint-disable-next-line local/code-no-any-casts - assert.throws(() => (vscode.workspace as any).rootPath = 'farboo'); + + assert.throws(() => (vscode.workspace as Mutable).rootPath = 'farboo'); }); test('workspaceFile', () => { @@ -460,8 +459,7 @@ suite('vscode API - workspace', () => { const registration = vscode.workspace.registerTextDocumentContentProvider('foo', { provideTextDocumentContent(_uri) { - // eslint-disable-next-line local/code-no-any-casts - return 123; + return 123 as unknown as string; } }); return vscode.workspace.openTextDocument(vscode.Uri.parse('foo://auth/path')).then(() => { diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 4bf02420715..7e3e4106ab5 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -250,3 +250,7 @@ export class DeferredPromise { }); } } + +export type Mutable = { + -readonly [P in keyof T]: T[P]; +}; From 22fd06f4d3126fbf8470de5b8a1185684ccb8f7c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 7 Oct 2025 13:22:26 +0100 Subject: [PATCH 0900/4355] codicon refresh iteration 1. --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 90728 -> 118456 bytes src/vs/base/common/codiconsLibrary.ts | 10 +++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 0989abf8ef1af54f0928ae2bbeee9c76cda8394b..11318b0fb7d27510ff53e5f83addedb1c4081236 100644 GIT binary patch literal 118456 zcmeFa37lM2l|O#(+v~mB*IugE>aI$ifCNDxB1RGs zF)T)nhzk(`VO+orB1=SMa2*V;h{&igj51X{j$(#!9F_m~-1DlcI|<8welzp?&*wi$ zpSo4A-g4J-zGu0ogcL$}VyDo>tYxRHUDW#i&K*L?5gc8!>XhF8;>lw@_`C<7*PnC2 zhHdA3=f;l<5jZGB)3!}puQ+f1&$oO=i0(Qe-}AlA=Wf_|+TQ)Eaqcfc!<%tHTc@|+ zJiu zUspc=S3bA$tjXmof8uujcRzP>9sX~=|L<%!ct~|^{dYe1|7QRG@3U)T{y(iR{wK}R z|E#(md7mRc|9^1|U%ieH?SvHw6A%glo-lVSyd#XOaFnpn43t1EIBpmx<8xd@aYQR! zTiR3lX6f7IKzZfF*}v0X0jepjFI`uGJ1LFN6BmY4h z5FeMDyhnaXeo_9Fct+kS{~F-OKxVyQS$tPm^3$!OPRalY6ht`P4K?-V=5ZgGuxueescPh2PVi0j1- z;zn_k_<;DJ_>kBqJ|aFQJ|R9O?htp1yTxCL&x+59&x~aur79P4X1E zMy`{m$~Vi?xj~*S&k-Z?TzQ_{EVsxD(-SV^Y3-VrhK;9=0%CE?8 z$ZyH-$S34?<&*Mz^84}!@`v&#@~83{`7`-*`3w0=`Mi8VmgR)}C($8)BVUrg7hjSY zSuY#J#o}$^YGCMH;_VpecZ>IkcZmzcG%;Ovi`&Fq;(19ZzfN2xqjI6VLVj7EBz`Qf zlkbyD_ndE9Qw+@QjxALp#R!$SQOHDe`6K9Ci#aqNoxm$cx zjLY|mH-UF_i<9KA{HDl@)nb$Qq_|gnQ=TR_%6E!y$iD}#B)#ST@Bf0-0htNz2Ki^= zqfFqlqO^fYCQ+WvBrigF4iosMC~agCPog}RNuGi7JSO=N%1umiIm*pU^1~?4XOgo} zZearV6(zbaNUTM7z`95lS>pAi@GAnjauY zQc_V7Ny&mAdf}q(@c=eqI5eG zWV9&V!31e7O4J4rak$Z}D-n+Z}~l&CL2knf`OStdw&QTiMcWWFd- zUxOh1Md=GnzyYE}eGLL45T$#WfDJ^6`Wgh3AWAgPK)?&4M12YZauB8an1CTfiN*;8 zG$Bef_8{O2QTj3y5QZqx{06Bx{~+FjfI39!AtvAtQF@pONJNwlGKu3*euW9>M3iV= zf`C&*>8ngYETZ%%6R?XYeT@kyMwA|70-h12zhMHh5v9kOfN@0W>r6m9qVx?W;2u%> zCKC{lD1Dm=SV)wfU;-)4KZjfv7vnSjegiPi=P2u+lJ#ssV; zO3yL@wTaT-F#*4c($ATILqV#JfU_?=RkqKx~l>U(kxKWf!OhAyLRAvH}6r~9!ph{8tCnn%a zQTh!Nkftd8GZQeUDE*cR=u?z_#{?WIN-r@1k&4pqnSf12=`a&eswn*n6Y#1iz03sU zDoX##1Pm)me_#@iqWm`|;961oBNGs=DE&JVu&yZmi3zAzl>W>F{3}X-VFD5srB|4M ziAj7Y0y>7WzyzEu${G_8vncCK0%KJ+n1G^1*<=Eq7G;YG$Xb+bCSYt)c9?*+MLEC( z+%3u;6A-v42bqAyMLEO-R4&S4Cg5{XjxZ6QFGrbx*+n_V1oSS-aVCL&DkqpEeV=5K zDU?3RG~W9}cJFeANj9RS&mfsaSp`*~WsXS>pzL6hLnu3$mD2JKkTTm96f^r#?ycFefCi!-hCosvYP@c#n--VL;9VB<6T)`yYjq)TWc{R$FO!7S_X$(N} z{U~V+K;$1PpUfn$M@i!Wk~gBH@c_xaDAzK{522*70m++D(%69HM^T=}ByT}U;{=i) zM@i!ZlAl0%29vxE<(W+K(nIg55!sY3_mq^~d#0guTl*Fu^Jz$~Q8>J|W6CF~L$H z${%2Y%|ev-GQoNw${%DB(^1k~2f?Bt${%KeZ9|mzF~Q0q${%48&!eRE0D|R1lt0Qu zw!HE!Ot6NC^2eA2jT@~Ekc^_FwE>a~QGSw1(iq;#B)^PuKNBn|qWmc)@ne)!Cy=Cm z`ZSZI{=1z?EfL4x?xXPL-)Uj7^t zEI6Y4c_#69D8IlYTT$M_Bl%q<#U(2T?x2B%eh|pF#2|ln*hKFB1#gYqj(lE&o`CQ0k& zt4y#ziSnaN;wqG1W0L=j@-Zg)JCuLJB!7$YaVAOk_&SrMdk{|nNxH{3nIzrgTTGJf z@ogqa_n`WLVC@p+zh#2mOO&5r5*tx|mkG8oQ9i^ZsQ;d1f_+SszsCeinJ9ms2{toP z{s9xLXQKQ=CfLzLnfeO^i<&4?e}Q0I6J_dC5UgyX{1YbF+eDe>JqVUJQT{0tY;dCd z3=^zzqWm)^*yTj|SteNMM49FdNKl{uoC#JtQT_!J#UYe`$plNDDAT+I!KNq5&ojZg zC(3`%BHPB_?U3{5_L&Q66TJA(a2ZB*Q3Q zX2LB+`CpkRx}p3BCRi>-`QMlX)t$x<1Z$=!)3}0Q*A(SHF;T=pnc4t?ty7e#4Iqkn zDARpGA|M2st4Ib>;^9QnM5!^sIw~g6HX_(b#RS?bWs=)bwlTq`EGF8S z2xm@EpMu0*lpRd+R+ODgusw?j8UqkTs!SA^V2>6P)0o8fQBG%)FQV*Xg0)&qbTf(N zC}%Lqcc7#;fMClO6V#6&ShdAOFB3dZVqzAPq`LGm!R9R{`kBPfP!2G`4lX9Bk3g`9 ziwWvS5NzXOVwee5axqb4g1uZ!j4;8|B_?Jw!G`70;ZF~Q3qCQfCdD5r_j zm?Vu4^&<#A2r)r*0>K+0CaA6;_$9;yjRy!G3Nb-_4T7&iOi(>Q@LGroY6D2nebzHc zdJc^rNNz+);|+p$L;OxMiEp4p-w~0=1OHBhZu&X+OyX|U+?p?`e$7->;68gZ?Ny>{`2||45SCv z4BR>JtHJicI|qL?G(5C_SPYL0|E74b_=l0xN3I!}m_0aq=j<2e+&MQqcj?@F=QYf` zX5P=|pEmzTqbo-5U$B0`Bggd|ck@DH;f{s(FR~VmF50o^&f}BEpLqPv%yECmcHQ^b=nmyLRl4EB37T!%3H%^qZ9@ue@(n zde!}_yH;;`ll!I{PL7|v`{apJ>Q1@tly9u*UvprsvG%UDht>txZC>}tsr{$!J@vPz zwV$@-w5Q+Pb$amhx1aun)1N-0=Zue^@vSpwoVo4H`_4Rk*2c457+*1d=lG%Vm*3L$ zmh0Z~`1;`b(e)Ruzis^w*8ge4;tiK?xNSq}?B=tVpMCM!kDqhmIWKQK@!Zwte(${Y z^LC&2(55|`zOh+sK6CS-%`cz7;{03Ae|gLBmW#LCyycNCFTZuxTX$~_Zar=5{aatU zVEF~tUGU6>vo5^-BJH9ZE_!-fe%saCliRP}{)daZF245SZ@ulVOVXEo=#t-Dy7%o} zZ@=X-?Xuy^c3$?x6dO_ zefzz4Xm{+nQ%m3=&XIFpruFv0f&zgI7 z-gEoC(R;Vvd*2tgeDTgN{`|n`flChj=&!H->lg0R?rXkp&V8rdx8uI2zSRGv+wafc z|MZuqeffb0ZhLU~gO5IR(?icZeB8s2A8bFk``~ZBGWeB)kBCPWJ@U*~JHEQ_tA`#P z|62NM8^89{V<$d#+hZ?cog1{+WdpR!AkagBya9>!uuL|~{!uyDBkjRa+21UaVcFgz zGyTIugZXaRZl#jxfua84!F;=wOz&$MXl@=D9cXD8xGgtt`MjKZ*_F+Q1M33ed^VCy zHYbzPZoxTx;`R>zq};q2t@Umwx{k2OJAr6RuUF-Li-0%HaW2 zDwVYHH!6ov&mGy`Y_@k~R%7F=HFe$LU`Wq2X9}5wo=9bT6OFS*{I8{z!ZCc)oY6zU zaCaT9Q;(w;aTutw5l^IcHOU+`ZY~~^?jN2n2dKH-a-Zo~YYfvg)>w|Y#;`17&7ak4 z^&?KG^{Wm4Qx5)<`M7Jl)r?36x=pSyh(`3Mn`F|mQ)#P^YtPSzKYR%1*eO11zQlQG zAZkF5-(gtNG)CxAw?>19f>Adhvx(h_#(10F>6!L?^{VkH-TkUi_ zmq{nB_QEt>AJH94pSFLRZaH{A<>NKFqs`V#N1rxLcTBvcGb$f-Jd23uTn3gBV4Gql z-H(T&*CQBA)%OLOSsBc^>ijEI=Okvr`d&z)UV2E2>6k5e&PZ)aeaN4Tm~Gee>N-v} z^_J7A)u*?hB5$8GM{6_pnDs&jR67G*Vs%UIfXP+KSUp;0jl6-!Nm>P1DX-mv%2{Z_ zG5U-8s#j7EwNxgljj7sSG;F60cfnPkHh4@FESe_@@%CM0C&J>9P8rikAB9{_hYW0#&7bt_Nw*n z*TyioHpi%RrGC))pI7xmk7I7Ag*${-@R%Yk=Lhpxhn>{66gWc)Ex#tOX?bd7>U82~ zr>F2G^8#>!C)K@PW&XddQ^&2QUir48?mzWjliTJqR@LS^k5P+Py;r4;7)`Jw%=iTr z?hFnxJa}{)A3R1)S-xO?MA1fQT#2I>5>>WfA34On1b9rhExlzg@sqtRx@CjG01$K8 zJLx+aqJ!X1R*Qyt1~#N^sJ!W|d{vzf)%nWOMzx35c%_}km`lAhY)8zHsrQ3kT2#Hn z44s*HrCHQ_EIctDcuY6u#=3B!F?@~{@yw01B+l{DE#Ag}3veE)3#`-k=@w4O7D5|M zFJhg8DWGliTGHM*Ze1we91FPXh_~0|w@uL$ohiGJDrAN-g;YeQGDEU{;dyiJlH=@h0sPkOF_xxffR{- zg+)dYjF~SE`SYJpQDz8VL--Vj?SV`haV+i+dg%{83hphmD)DBjDD$COH@GP&X) zVA3d1)o`(Zj%af zG5t)pxh@<}zm2%=fL*D0-4HFocT>$z3mfORYNn>QC;Uc?NjH!Pg>!_G2BQg8{j^L) zhv`6yT9BvqdG7fm)L6T{2d0{uKG1+@qv;B_R(TOgyIKp-$3-51VuqVgEYNWDV8+{p z;S3OZlg#``Rq-&@bFf-(^BrMhlxjU+NB5@V;W~izbYe_Z?eOFZqrVee%Nwc23vC{e za40F$7!op^`E&Ob%-woKT@gp0G!>eqKNUzz$YltX3>6q50e!0}G_>!?xqw?Iyr_q< z?`V!=%1FC9{xVGyU?#43*zr7P6Y7t@UY;zKy8Hv`QYTu;M^)9}Gg#!dWvk^f zR7|P$lq>klYAhHSY05cf$aFk67LG0q1oJc9c-);iJs6F6-7h(zj%+CFxB)MgiH2=6 z=$tSTX^7;bnOKEyHmLPGlSU1S652=1htmHr>jnOy&hBEXX&2~NKQ-pFWec{0?(+UeT>~a``0o68)00tl;^SQy| zaIr^q3E1({25nx;25zoeVVPQ#2ePUdZFgCshE;v9>Z z#H5V@LJbb9!7nPcnic`32}YErG!$+-48TIO8fOAvsgnR#Dre;jc4b~|P9}*x2k{Rq zeKWE3>XDmCyQq)H>v2?1MIx!sSKd^|mEseExF-6jgNK4SzL(-C zcsjwX>7fKZdbZt?Z?SC;UnD})Y2r)|$JKdyJZRuL$90;c!RBDJzN217>B@*>8}X3l z*$s4hLpZI|o$^{L46~AHK;Fn$DixEi!=J+`El;=C(_y~C_Owvku$_pyn`PV9*|L*F z#;SOIJrITRl45Rpi0;e45;hOfyDQ-hUk^9tF>F4hPeV$xaAN(nVgjR2DVumN^{?W~ z^ycUzRRs3&Hn0sn0aTpO!I0W?>*D&@zF0lDX~f;OryKXoTIFSn1ZLk3mUH1e& zIGsN|gzHQ#v49x)f`n#TxY5>FeSK^r9ogtcBCdKjhc3mv)NB7SKAJZE!+9eai zjLpGEG#{}d@ETQXtw0Jj_J|{+vUUV!4Lg@7E({S*Gpshbi;9)etUud=6b=Lu^x`>r zdt;#!2h@wON4EZST4Lu_Jb!W zepFeP0pOt7G}bT+K|Nw48sQFVUnT{nWDln?<`Bw|w=IzP^a-biWdTyBkcY&zY_KK3 z0y~ch(+dVo-{R~{8oWwb!XmQ0APrrg+eVXk1T)bw=x#r;~Ga-El*@27s?84c*fqro$1<&bN8Zo@E#j-E1>-+}Y9Rk_yH4*S5KY z$GwabwbG^=hq#x98Fd?7Gh&3BTH6PbIr|2~u-#P1({ORUVP#AM*VKh(&24mb&oqKY z6pAH)&{6&5<3$W?SfZPJ0S+--c>R9rdn5gHkXzxHbBP6`$ILCS*GpgX_}yidSwp6i!|*~nXw0OyosCx3Zp

m}ag5bMAQW3BPKrz0(Xpw})LR!UH_T8Z+1Qwj0Dhb1bT1CQI{s%}KL9ji z=mP_Iv5f<|-mz|nX{__wdfPlnbM1g(9!RyN@K=$+GZF383{CJNL=N2yDFGSGa0v@F zRZM4W*aehPfGReSp@v{84`Dth$qEB-TId_Z#HJmOqq3kHing`@odbBGu@TM6^#P)z z)x8?o7O)7^A5cqTveDPKQTnQ*khTkQUt6RFya4zsnQS5?R80)JHfb=6E%{d;OULqfZb4zf zEcp)%>kYdMmy|Bo*kxMQ2IBRKD>rBsrlEv>_F{TnpL54B@rc4k0YH&hT1D z(J1yn2c>Z(N~&c_OO?cJWoQS9|1jz!KVdVLEMUn{p&gP-VNH986)51hJj^)QH~aca zr@GLFu=KFLcokGv<N9t*jf$09D8Sj$+`6`7X9HWvrcNQh&B)&cPjc4V^^j4aQY zs+YpoO0C?B5VL27&eFa~{9y#{3cK52*J1xP1pKep^%phmtk9Xdb22So_#LePxgJfu z?-~+0dpH{a@D-j7rKpo&Ma>-oYylP~wGZpA-cK6E%$s!k*LcnzT^}G}*z;bUe4*e8 zm@{NciNPA$i-_8jDAFmxeo(A#U>8Cv{b1~D+68B}U-R3-Ykqr%a;NBVYE%%;f(Q1| ztfrR-{{!C;T!CXpPf(u)TXcf!Sghohg|CGaFLe}$@Wo}yD}+arI@YXrlJs++Oj19< zAC8?&=tneFwYSy?Uc>$n45PZ#iIO`NWaD4DUZsL^*`c%7268MMUbwylXm8H z5{T%A*_KO~ihRCYD~V$?QFuf}13aRH)>4TksFs#e5ZPd@?r+z$AL2mI2I?Y&wboYXW5@UZQC}I{CGFMw7&#SI$Uy4s2=aWh{}+=Cj!ZxJX?zN}eXV zU**JO-YPrkPDh`htQG3+o9M>*>=@m+E?ODazW~d%DgCcDu`oY)vDR!>3y=arv-Q(X zr8#@*sXzl$Km8j(0lvhl!IzeP#3$bmMY>jmcH;tBX9kATwjXanu@*J;38R^3rJpsL z9@6}w3B|mpD_RbPP(Z&?2k}yA%VDt}u`_gc8Z92X5oPvNFSLrWrf4-OtdALeSk(uH zN*G}18q~rf8K1M2=2jbi*czwQ@GS`7SXkHQIqB%8abo%7R6&{rm zC?26=hj6dNlzYY7o9beLK&-B*y`%P4)*pF2@%k}{v=LoFvd_gj2y#wl!Oy3OlxAr|QFWw@}c9S;hE5;V2Dh~%!>6j6Kry{k_O9YHqIu%M+`Vwa!#6Eb6Sl|mVH!~Ez zMGnViZTLI8gi>zr_NjdU9v300O=04cEX;H##R=MZXu=}Z%p54JBaMLE! zn1z)^3kr*87DB6NS-Alh9Lse!d9gz6+d`~B%h@sc+R5g^wY1VL>na#*{YzoWRs3Sg z^&U)WbmPEUFNPowy3Z=U65S(A%14C$nTfH^z&dms?PG^l%S-_ZWQQy$5^6>n-%HI%N!YMEs2~tUy8Cv7OHX zAUNXc5$-5z8}xXkYc(Lsa303~a*Vw1eo|ux*BJCBh*(yo3e{PWA{AOG@^c|v8G@4& z3v)uDbOq4i=&MD8-~-_W!s{x^0lq&#LEN0C53D2>vCO%Xk=XOd}e6I7m_(f44)A#eF3%*G&SaX%5CoS0^O=+H z(A-RSK;zghMi{&m(fAZ)K|!Zv7RIou@Cw8RqHz$Rup{O%q#3jf$3_vXZufy>^pToI zw&L2jllGC>muZ709Iva3hov5mWut9%de_Kw({-(;?dbxdD&Se}DWuPU-i5zT<$AzK zrKyF7+cJh#pR2>jW@B|ZEt_wLi6oY~9{%9Uwa1fMi|{B*PY=;#8cO39|Ho}A`1sey zjXs*iD(C}Oe8X*00brQAn4+j3<{UNgjWjAn@m^Ybkhs7g<~L%=pv`JkxlR7=8Apy1 z17+}D7^~GdQOvGy+rZe>YQ2YC1u##vZP2^sbm?_%=-oXuG?l8=K}@;5{c)Y1I1w;eDYudn-LTpijR*!xqW*){$#uwBVpr7-LT$c-PI&m)e1oCkm3!x zrEdgaR%fRQ_7qhJ&Nv_Xl4_(DW2ST$*&+jQP|R6I@0w!4m80+tvTt3!XZmy+fnH5h zv8|iA)cB~h8WZEmMpn-oljDi(^}%SdSiys*D!xt^f^d^|D1a)ERvlN%j@Mok86JYL zWFjIQZYyH3Y-I(oA!Sb4Ua7{gyVh*>bP8AQ-KJ_9zM2J9MJg-kBnC_766 z`Vz5y6>33JpCkml2yn4M#2oomVTithT0n9ELPp68wZMi3VZj*!Q^8dOP_ zGzE_^1Pi(CGVm%YYyxA8?MT;f!2kJ;H0P0cr(+~411`KxOYyB^xYDt)25l1}VJtuc z910Y3*;$PN`}4MYP?;~mX zqZ+wg3o1ljsU3S#6urTMz}GiDXEsX!h=Uk{Mh-BP=#3&5pnvit?eu!c8BfMo=+xvM zt@&oihWWI!nb+SWzyKLR0!~Rea38929%Dy}If9aCNIB|+G{O`<7f6YRa!)!UCd!|o zS*@Y|&tlyKqhuX_M)@|@;O#CIiKkv>TL#YPRmzpD2Yys|PeI9EWhFag8oLb>+Sn#& z$*_{bL?%%n7Ch07|IQSbK2EDuHJy0cx1FD?VT6`&;_wNaI&#Z8gP1<*;#$-bavi7`9tTS>Zh5V z(Dbz?4S$eMK?oZFufd=$>-jED-u6cF`Oct?Hxj`>{CUi?$LT@iwzsWwc0NDupN;#D zbauW^*RxZP-)mzz(kPy}%8eq}muOzwzz zn07wbCOa;`;vf|ezdkma2_0buvNPOGb`*9Nu`h{0FS(M)a1EsgOb#qi{i!ezgex#S z1ZLv3Oc2pONsTTh0LGBqQxsPT6y=XO^eM7%gF((E_p9Q6YJn9M4DW@&0~;kPJDy!J zn3L0D2q=x}I9PaJGJr54xJwNzf{>fk@h=$&!;e7DBpdMBw$;J+Yau6Lm}%H_U|cqh z7E_0}M+Uu^F_<7AI!A-KD{u+{R5xtRjCt)jsT-z_wHKFg5y3s>o4tY1$-+J5fP6_k zUek3g>4q@Rs7AIP4kTf42d>Eg?4T~{@ID8F7D?fG#%!!%)X1nQaKqYlp62=+ON#o!FS8xfl$zPPxn=%*c=CT3jp3)eRidCgI&_;An_U>Qvka zFFulgwZ0z&yseIw(txWRj^0ceWH2@vbAkaxB^|5ZZsDFqBodd(%3xEY&g`iFr-RBv|b;)1aO5;7f$(qmVE2l|}u0%0*HnAw3K(tfI;Q@ezuM zX+(Cf{^1NQIkngnh^OhQzEx9@1hv}Tfr~IhRYV9TEtEUhIce4r-&cpzDf(QPylPRw z#w1OwVAN7NxMLkZr=1SKay}PfSwZN9I3?hPzl1ljAbpi=#9OkV`j^J&{a8&J973Np z(lHq2tlk7+W%QwWbwAdvJ~-2`qW*0<9Pd2;;kemsoP^~-r_P75ksgQZEehN~YRuV|(Erh1V%bdsEoYQFFevQ)8R@wi!&J#)+e6%8vK%tmWp306nqS(=(EIF@U>YV*^7LeK^UC62uVh9 zZ3W_IVT6kSu_5vsp9s5A1UNXFYWkar2D$?yd*N45i+Pw2g=-Y5Hsgd6X$ zqZouhqB9U34%zVzP^a2d))5XFn(MU;fV0x(EiK^axp{Ny+GJ~QTbtbmIGZ3XG1y3A z>xvHC14C_h#9>hlcy_2G4!TS2I@6ti<_ql>&((}j7*DvHZqwU3tF7X*X`s7R>?JJA zGpg-aEkjeEp1HERrhuJ8w^!-=RtnnA+@ z+BRh~pFcn?15c;{Py-QhP;rfh03?sOOOWt@vb%N5Ch(?Zz)I`Efd_L4x}k|OXXc0;J?9;+2Ps;7f=o9ws*#eVIxY&_ZHi_^j-&FiThkpP3O;LgwUP zNJeHw;IsDJR5%n%B4=_if}lLZY-_2HMS&I|b3)NvKBnujd@dULfHNx+>`G|*`(oWQ zT0O6IMt7U%6|4Z%2n1@G4@w(BeyIdpHzCKcf(gsgRzokrh6>v)FGS~!_Z{{VrNZI!z>ajY2kk6MS#*z+=myf&9$6~YKVR74ye z8jgJq=!{{tCs(aD>KjBF=c(7AS=l)i3klSqbTf1jzVAifr2!X!8Z$gwd8{X!pnQ$E z>V9110r&-zZE$Mn!7$w8=s_(UG$wbGsmpLg2a@nCI~Zzca?NNcimubOL|1S>BKiFd zG#=@{siPxPMonwAW>Od=R5irfo5>`CVXR=JIhKIsNY`PiNW_}qF0<<+8HAq)EXP1F zf@g$dhIPO*qnSi9To|qG)7e6{BSgO(`8;LsozkzDQ;v!Mx}HU6SGQ)?999?>%u{H( z5E&$6=^Kg~Vk00dH!Y+XArK|?pV|FUCzWw65^;u#aSrHqG;E@$5K*G=0}e#O7KQHk zXIk<$y$`obt{5i<^8{B(5^)&j=|9u| zHrP%~Y5WJrpt9Yc`RGL2aX{7u`cI1m{~;C&U>F3{0p4JQfTIXpqc4f&fCGcXZYYRk z2wS5EV8O;#Au2LZHW(XW`;fvuBx>Jl+cp62!f%V_aU6kWYA$(4$p5~%ZSAc1X@L-X26^D{zw*|gv;U8Sv(-6 zV)4YT!ro|w$FE5S+uMUlevJo_jiozA5&e)V(O|Hf9yjHoeg!b~eg9Ms>~3jjpF0sR z_c2%%n3YV5?^RX+8`}dFASH#-rj27k%)!AlHj6nZm9Suf{8 z=5z}im%vv%W0m#x=>|j+fIS81o924R3i6LJPQ8)R?4gi-x&;Cm|&2E*gpq`0S+-lng(LihY9Uq;?@A5O?H@6 zLBU7jD>YO@;(`2@gdPzlsu6bdop36#N`Em^300;{C>WeA z^+=ta35Ba5QBBSf0+s}K)b7t@XzCn=R0~AOgOJgJDc8BQ5%--Q%>aP%0?;q5O{zL9 z8)!A{$mpTZw$m|PrcDj$L+CFVB@iU7`p9@PVa3wPMCjv$bdpv)4%CTx3S4B?Md~f+ znu$y_f-kjx$4!Q`OG!_5tfuMMlf4>In~@0pMj-y-_d>$3fu4XJBIhBu>AS%9Z$RHy zq#mvBVt;0~vS_`ItVPVFio~2GL^H#MiilMLmtg2nUsV!MfuS}HG`I@65wO9P0||<= z`U>gH`jmup6-%FX5oxysR!bvSu+f67QM8BSAsQO8Z(1_tOoYiV?62*uk~iiu{SlB$FaJo zFkil!QqV(7CJz=!Dgm}l`MMMa0skR*eQn@-$@WyLeR_K`+1>;%3I3qXz?)(1_2NxY zxXIn1E%i`0R47mP!V3|69$gsra2$vt98EVi1&k<^0?Y2kzJ*w)`x*ak)7w*Bv}XsK z9!z5f-7#RbEz`^xb_oHAMi2}YivvbkUZ^1wb4c%UVv&ZJ7dBFXHai&hLPi+7+fDsL zt`Sbg;=vB$7i!+l1XiION=uO~Z>zFwRb~&ZayS)uD$xgQjs_G23xUz187?G*fBEtd zOaZQ{DGFr@U)A(Z#*d2YP^aNLRx*ouOQDy+{H0R_P@Pg7VYQU7Ki9=G_098|>eGoj zvPD<5OT&t{k@gvi#Sph20}5vqtI?U|*h=ACR>$llvMXR^N!84f(ph=;UREL#L1>m{ zy_>T#!H@%>7xL||T5;lK&lyyhtVGpt3^$z*sM(ikc~ zq}nG;@p}|62a{iBfTKgG9}x>fom#PC57ahVuybOpKCupA9TYP6Vc(bqcaDmcBiGa~ zE|0@vdafq^j?^!M5D3!WITdSElXdtZ+k4IH+y<7T@V4LbAayRq#D=i7EQ z$IM5t_NK;Qj@Rt6*JH5ZIiY1*i9hr3doHu!^;t`KH;eFHQSgYS_R`Ss*=o=1sD@oP zh~GoROg^k(?lt4IA_fo|7p%!)Ub;{maC%OKo=7{1n8l%X3R`SOlpd@{+7GoEI^qe5 zp+kt8`dhKl%=0qMQ46sb+dyQ$;;&5b7(9N!menBAW4YKh4wqtU^UX~jo0~Q*hj+3bfthy!U;9No zfRt@eiGtz4b57XWlhm~29xEL1$BTTu4t~L=h+itX9peRSL@|ZfKXN?fu?Cx!*7wByS zzBhX=q65~LMnb~BO&Wn=Co&1JP*|S4fMovxw0amNV84U%hIe*A!#gDQFwCByjh&rv z-d{fp9<*6-UUebY5JpfN3|gj!8@kYouvx+h8xfpFd0Fs^O^yx@(l>Z6b~W0;psf-{V0|f` zjbHN%fXnPqI=h`OP#hKpiu^M8G*nsl2_n~!$ZXx%~zJP59 zw*H^@|2zA^Y6s9IYq8Xhr@lWPDR9g+Rw7}|{oj86AKlMNZX&bV^N=qoWW?zK_n9p8 zYnwBa3(^mlp)eB?oRnZ;QebqF9chO0Xit54t`;o4i+ytASc4F!6lP13wX(MSy42I} zd`+ndg~+N9Gs-hXE@ApGj1QA405-r8t>kZ<^nTLQw<#%Z%_d2X8j#F{D+>Qgc(Msu zfNYI+4oezMM8pToV+jGKARlHN9To*YxHois;i>ks|{tm00Z8OkP)Nn;uPY!aq=s>UPUh;lFG5Q+Ce z`fV`kZNy%s`ZI`@=~j%NRs*(1s5ml0?KyM@snj&A95_@&uzxKPhE19f8@nsptDrAd z4grc)mKZci00En1F0?EJ{=$MwL3<>FA(1JUhKeCjdkRQm(_~7$rw>UEkZg^z9{{K> zl2Djc=+?nByJ@nB8X^S!V~AKR0|^O?g-E$Hu#<#@wMXG8zU;tJ_=l2YVJ9`x0z8HE zHq9=?`Fl0fOz3I>&XLx^P`v zTipiIax?D(MvHiTx;Yzh|7%IkuQT(-e-<#!6M8Q zIv`DR=4eVqge9b=7~;DOOd_PrFCgoQ3SB3&B1Q`SO%-~}i^Z0$m@S259}>V=^K8Jy zqzo1G`Pe6~Auoj%NyQuG8L>KK4RrLVSv>(xtU@dmu6K2y&nT&dAuU{hamT+9x77<_ zBd*y=doE5%Hl||*&DjAD(md=Q*CP$nX2t4a6yXrXcB;zvmR<~U&@T@mmXSGYn);Wzm)C{uYcGAHq}W1NCFdj9FgYd(tSDhe?30x8{J2^g7ijWQ zLdv{a#=+m*x`w*A>&ELE>H}a~m`7m?YYK%5Vj_G~@|Vh}>n zc4(6}s9dQ4WMm*4)u55XP{Tq6SfnyTCcoQ3xiR)pC>WHSgi&iA`Jw}8HR_!}9|VYx zm3YD|s}`grb9lxv_k zXw^`$AwJ=IP%$xw5doYjgp@%>CA_gBGwM}3>v?_118G}QYna|QqkbRGOU6kksXPgI zZHlL)iX(1r>}i5tYQP9p4-NlVa-wDdCr1WZNieO*`cX_BW}NWG*|>jyievNCzIyCT z#Lh?+nUAnyDN-yTje&mtK35>$r?^_ah6&$^rB)CDQB9GT4-LqdL8b3jpycH z>uCy)s`cs7FZRG(OReLk)%0N!!CT3^3W@aPiiS$-?)T6v@A8+_imGy{7L`A4_}!@z zbb=n09Y;-ZA3oLA1NAh_7IXkZsA3~&p;T3vKkAJ$24;rH;;tW0X7YhRSAD881Je?s z2{wXDWx=R8QPvF&U75OM2c1Qy+GaYN%pt%9*>t*+jY<6Vb1JOLyuvTIdn)Hu7g|r+ z(UeCQC%F)P7Nr7G?0r+7+6NDv-cJUt4&?OepnZ57r#$VleoezUrq;2YIREwzI+uo- zF>$31_$`)W)Pcma@8d&tNcHeV?h%zh9jcG#3LQ}kq-N>ZPF+LKw^1R~+1C9!{QIPF zLV%)5y;-wk)AS=o0I*Ap4)X!yMP1cyWoYq}nX3Oa$LV+)rEDTf>CD3JYmCzv!o4t3 zAx?6JKH@oAk$1G~v?A9CH^U`gS&=OJi1AT&Pb8y*;T$SMVIyaq_7g8z2I{!ekVxgl)psBm5yZb(d}vQ>t_qsuVzgkh*v$j}i|m(8BuLNKWFnux8hFc)YL_fLrxfo1k1vkVCfJcg zW!uk5ns$=EX^5PxboeSs`*I1Zv7XQX%s?KFA+^SR8G<>Hf!xAW zA}NOYL;v*C&BEwUu!6jBgn5P*(SEG6l8t!>F)=YG-{Tz9fNKvM0PF|7tTs*|aqPR@8rtr7G2^n5UB@ad7(R4KA!Pq-bvj$=6 zg`dqI8tAT;hTWHRZ3Y8`OQYKZHU;<7MTqcE+W|QKvvA5$%1+u@14PU`^m(jd@*!1t zC~5)r1jI6xJE(P6*-ZmBhCE*YRP-{R;O-iFIU7HvLZ}umV;E)N?n8<2 zdfW;ib5&(*E4*nQdXRQ%y+$?U4kpQ5~bARryTRaIyfVU2(h}w6@+-HV2pygD)U20W1-ml$6s3D1!vj_L%Co zD6l=ow^rbs17to>nMf#qD!#|d5zj}2{?y%G$9g`A6A~-0^6)&*u46xWk-sgV>Qifg z<*R^fk8A1o;yTHzJp{8q@n~h21fNLTM^4}tNuB{Lyz1;3qOEvp69;;amI4qxv3$1T z_{V6xS0dpKhTk|1KyRi*9B_`CR5yqI2x=1+p)P49J+c7y6YIJi!?_rG^EKc zngHtzeOT{=WW_isD<)+x!W#_b7_|g1&lH_>M5Q~pA$SW7^+Ctei@Je5LMk|U(xqyU z)J8nCNhuOahlnT(DE-nC^zW%F4;UMq2u4q^9#T@tHVQ}b6z*`WbCT93q>)y+q@#;r zc^xz7LDd_R=H6o+#wOt^=AxuOlsWpKW$f5tSWi~Jpy-A*_}MCa(F+#m>!3)Eqx=cP z3XpZ3lGc#@iZL*mpemdc@{tmH%#9TCO@NngA#>1ptgSaXcPTBYGfZ0<@|Mp7_-w)s zfN|nZ*c@DD8&!X94jghJcFu27nYFM79{Oz+ee+<_z-BlyU4?(aHo@U8s2{{3 zdauG+(Rb=CEFCnP0BhL5>7Pnb40j7QRoq5 zaK~Oi%gv-5Vzp*CYU_%L&~&KbF9He#_!4$#14Vl)O3H06>`kb^A+$; z;u!(zS_B%Q12ID}lus&)g0-=FbDoz@@Pf*8wd!<#4qqUNA29A9+gl&(Cufp3HUVa< z{)j6p2M&grw`&d%j3Ywkj0Moj7bv;Tsr!)62*$|>EGkG2PVgC5k@8CVYe5+@p_$lk zJZoa%0@xi9x9fM3F<6_e4gK2Y_;jBKkq&y_35BiO96buxl9U?#pd=R#3Ow z3{CI!j3~m!kBhh;3Wr0H?nt0Me#w-?g_T66$4Hq-DKQZgp)!T4{D&%WC~zn?1K1V< z%A^+Yw~PkD;bH9gq-X2FSuUP!xOq2n9D>%PhW9~KZG;{kug^}&pIFUbIw|KR=TD@u z@v$m+VN}J|vDJ7=zmm3xzP0_HFG?>wJ@Uaxjb4fd2O`~}N~hq2@P{Tjdz3RL?JDF1F{Patbb1;(H{jAv>Ie>qY77#Aau1_-bx z6$J$XG^l9g?`mT*12)HL^xMT#GhH2%rw|YUnOo4ZGYR96TA{jfXiaV35s)oO zAFS#suef`aCRfCQ{kl1a`e9chixg56jJB(`i3oiB2octaNKX&Hy5u8*R!ue$F0A&E zvMpxdwZ`w+c2;vAvL>PGT^t^=$0tn`ulS1vgT{0o882ZA^Q=hdMAr#jCGF2EP5r78R-;K)f!R;r%{#AIjP;W_w_ z8c6c}v#K7y(go|GR~JMoK8XNR-A>maaSFaiF7P$mzi13Pdk<#(?`MlGWedAShr z6h{bq9BJ5*%Re1Q$P?82Ca~t(39*dk1@Kctkq~0H<0iCd{JaeIIw4RV+3}V%^(tRd zeGI-N@`s>({uscXOmiu#@{&+KF6s{7pb5r6OcR`w7$&;v2!q&$r33Jje z>@}xsuARg@I`LHK)4r7ic~dI36J*<>lqs(C^K{*9(0P?BmCj?GUCFZoQ-Mmqp;C0! zva(Q;F5ezV_LQJPhs@X*n`f9&&=R0&?+-kd0HG2fmV3b_FzHnkYTq67_&fS^m_u-= zj04+({?5rbZCb<~3BrLE*yka~NUP(FHP1x6Fm{yF?sOC_EO^Jp7V7pm>>St&^%rzT zAQbcV*&zo~+l^?gVB^c0=hH9j9~ zvQ+>bB5abPGgNAZ0^&k3=V8k&;P=qLV3@@y-a~9C*Z#;D*tDj8Dr|l*iPYjq(~r>f zV3Qj}ZrT*3=LkjvPAXI1_ zZn9IsWMi<-K|VgjGM*5{FQY}gkPG`E(tu!*yCGV?+%5+)s^UjMbf{vGv_+73B30SX zd_`4Odpzx$C!u1XAA$I!-G8cLqxFdhf=49%=zw84p=fGQRsM9CdypHq7cnCEyAE|x zJ9nruAvca%A%2sp@8KsaRCQBG;M7>iQlup=sm?9Ja!JI-4np%B!>*Z%kD8E&N=Hs8 zu3~NQH6S+OD&sgySITljW)_6?VqzgAlXjo7wn|%)5XUi?KR~}Z>=`Y2S^{uAf#U+v zO*b^CJm&UvOB#Q91*xQ4ME;~qYC$R;-2(K`(%)iIcBD>#LF_*<8t@iLm1x1i{Gvr@ z_q36P!3Fn-B)o`+zcFlwj7Ot!Y>k92o35`YTUb!F7Dp?6RwdGjaso%q&EAM&yqOsHeNR2ESruyV+<;5j46_T*9G$YIygOc@jN>Z?wDl1;G{aW(Wyo515L`ho(ml~IshAWz9-sU5Obz-B~B6fj?g2AH+JfcTfh6>a!M6*@feY7Fjzk+(gHdBR)<}U^8KHHraP9XDO#C-rSYkTs#xyH5oBW~9Xt zv122FAE-SxwuKpk+omvBP%%sc0b>Immpcq&5KIwb;S^I$6`7%Al1wTlDJBt>nhBap zO^Q)w5>GJs{nxkmIp?-ovOHCZbo)H^*=L_`e~FI;X+C+M4|^1(aAC7AaW&$nR1s~i88&Znqqw}ZWZUS1K|WNP#MduX|;U;`F+dOqqJRPyt^p4 z&F${WHZK8Av;cEmH6wLjnkDV=z>X!nltqvs<*8FKj&D5nHH`FSOhoTcI;F0J`wW6qzM4(bne zst4&;ffB*etr*AhnzrG!Y-~(cFPcA2c`|KmvK4H?3XS+x3;2!R8p0W^*Dd1JRVSpi zgu#Aa=Z>_34-QNmOExtpR`3VD%Pl#5Zesu0nYv(kcTMD=9Jo=_9GkocxASZf)k~;Q z3R}8Od2dUim+>5QCqCFW4+fC5vLR|u)sDmNF}J=Q8TM`_IeDw?nFst5>6=#&-)vPs zBlpEOi@@0IH=jn5pV?Wb{-85fNGNAxOv;7Kc*$@fadQ^gRo_Z}=BzrRtn?fGduZB- zTerU=VzibrU$N9vNg-eb&0a`lxQ?ByvF_>URusPP0Ks7C=(ZJQh2sh zyOYVg?J6Gfn70avjTh#M^t`D>Uc6Fwb2rFZXI3lo)9tG1WSx9N5`q`y z#^qr(AAMTi>c(v+VknQJ_kDb)*(*@>CuTV^N5TOF#)r70^I4{5yzzp(22 ztG{p^{dAr66FqMiyLx5IkNvHNR}9bIMoI@VsneaRumrm&ye*z7cNeLMord=(c7;`VN|Uco%Qf99eVew-*P*d0~}JsO_;;@zg?=w13-e@Q(OcV7mCx;wz)lT$+)q;yBdjv2B2FV3R_o)^M+s|x7xa`Nr} zo?#w*7$v`R=v}(si+`iM%IC>}IJN3{oiG4_9f!)Se5gxDqzYtcCR9x|kiw4^APvVh zQ$z?d+B-fjdA8!aEmfs<`Op9lVSmkVG7hL6d8qsNRcsf1xLb2 zn;|K$Y-=llr@$X)v<+=%M7`1CS85okMXViIOI`i}#}5B9(`$|=+eXbX&BpYdR;Jz5 zACH3-KbNZ$t{9L=7S1obg!a_zRWp7sNx+-Ull}V2OO`R4&BG}=s{N9`VA@V`?pzQ* z%0>Ky3{mIy@DD=^U|-f#ISD@a%rno-KN_RNIUHe@ac-dH!y4bSux4;rgjcHLcUPR> zM0e)&J!rjh#A%~t7C1*M7A4Q~JAU^{x=~C^T7-SmGRwun3=s+Saq#H#y?w5Cp$b@kc}m%LHd?cA&X<>a<+MiiWq6f zXMQT=&77ezw;NkPy~WhgEHLY4k{2-Y{p?8R5OqY!1er2Mo~GwrLO}<%!F8u&FA%>Y zbwPHx=?r1?Ifp)^FvjmnyLXAQ^>mSR)#RbRY{@zrpqQk6gdi?$LmNOd>e=_G9cmCp zVeImGtg+Bvtk)O&W4%9Me&m@UZ*2!_03={tg{I8m0qwi|gV$S>mz{M)HYgO`2L&mq%t6p=Jkpz2%VjSYWSd@&Kh zRQR61yCT@_URW|bp1Poj_blkzWSIFHg zJYpp}g5v}$?Hk{ZQ%Hx6Lf7Vy471$&7+iUllI7f>H75_=*UEHtT%AjOL5E8<4ud@6 z>={T`YLmAl{T8MsXQlSy%Txbhl)ctziAF<8r#{Le3Y^1goaJxMJKdB_iGOuKDT@O< zn6ZBr-0G~4LqLERS>SL}=;3c_WGhq!JK~CRh zylUbm2mv_9v{Dr|urR3UO6a(wu&HytLa3}Hn|x8K7?j2zQEx$&gQRwT3w>(#Sgz3u z=lZh&RiNA9%#qo!Q(s>9n=>jP{kJR40oNiFNDMLj47= zRrfp9aQ+C+%p>#RgLiGNXMbVz>PSAjnA|(*ILp7IO7W+uLwc9# z)T4!NtDJYEMNYsoz@XOuj22@i4o1koSvf{$7@1akub|Pm)i;Ai#uE4puBUdk zAi><+B2$PlAO6Tq65DE}1Bf_!lF0Ub2+i|%6S5|3|^me=T} z<9<^bv#-2-xZh57G{Ldh4~pG_jd&CyyQhn@}YXc;)6gJ{ZD!N^6B){#mVfrMJx z-x!U5?%H0m(eWOcjBv|8iw%TyrWFk3z@w(md1qk5ydRuPsrPhSADc;D9 zKj|FB-UDvEeH^t3d*m(diQBWufPQJEgy zq2XF+*sCnC)H)k-t<;~$$(ATYSC)#bGo%LmS}ED33dM+`4Lv(7`(biiW)yy9$SUGp z^E{@=<#s~cgXVrD3b&2DkCd8iZBtX;n+EbO{;VcM8P zRZ6S>(ld@7jCK1os!%a^w~OS+>}uw>bccJes^89?(dN7nJbJ8S5xf?IxYlygqSo$vEa&vXhU6OjGz zn8TsTuF2>`KRnmGMmZ6o)7-L6`o=!%7GD+=9=8$bwn-arskf?aGWCm95Q+E2u+E)P zl!)o>u3V{BOF=%$4WTF!V-AOsO*H42RCd2a0dKl=Iyc<2gWN`U$gL=UohG>MPhLe5 zH>daaF7$2~({TCREz%%v@yj>4sWais@=SokG`lS#_Z)*D3*`$gj&H?iS#~Fi8%*zH zd>^xDyNaI649-+}0X5rn*@7VHd@r~0LB%0TSAo0$%n(9fh>~CVE_d;#qY|C$)I=>3 zf5;Wizy3`ibHPVfz_aFU<;gz)%5SKyp;Sm;XF@rHVT-yr>U(meb;z7!VuMU4Ofn;-daH9fM_Sg@a5giVA;6V_w&cw9}klkgy*gxz`?E7J3pRkL^1SVAtIORHuV z>YDx+i(z-T2}m^nXCf6Sn3GF>zX)u_;0P|s37rxth44o58)R;hy`6!|NMN^cm%AL^ z?Q%tyCy_1P#u8l4$S+2nay?(H!P2icx@mY{nQ&g(jQbmg?THEmi2yT z>zuEExoen7Gbb0KXr{`ICjmekWeq^5^TgtK$wIBeh6w?!P_B0CfIs! zqNI70pon9)EYXH6#z36M8k*(;^IiEPnZmM!nYHEzNC{efGAm2pfWj{p3Pt8nE83Is zXOvu@KZBd$%vF33SFV5&PllWk0k{8YeZU!4n&eqR?)iWF?Z5ptTxjK-`61WSS+<$x zI-M-K#E@#@BDG;E%0Uh0?hj%YtY`p=P1*$?_9F9luz}&&uWDGN+U#xZwX1%g1hB#B zg@AmY*G?Yv8+SZ*ajSc5kNO9clZeV)#j!9om;q!j&c{njaXzHaH~?h{Ftt;L1QjbM zC)o%|oB%PI$yzT^xDiRzUhUn>wf3|AV8)c zm*G;^iseea5r-Jwm;Slf|k-OXIoWiIZy! zwc5hk$rHmlQ-56zL5*LD@sqq-Cz|>k*5^aUQ9+$yzBzRtO^}&M_ya~Jq?%oX8hKb? ztje7;7~aag+Ric9)4?r3`R-N@D*av`qU)CYYkYe!9MR?%wifYh`u;6+M)^n>%v9;f zU^z)v;gWZ&C+&ENGtS->1a|>OS2-gJ*Ti&9lFe>)CI|;srO65ty~wF#rYYm>Dz*xz znuA7tt#Mhky;(lx5}(d+put~@V+)Nzlh>apw2JBjhkeD~aiSG(hky1SWOll{AgWo1 zsYaa6YxB*EOZb{PpaMmtQ1DGOLJ4)fPP0C-wL18y|B5tYmBMs9T?uAl$dJq8n%`*J z&6&E)gO|Ja5dTI2Y|lR{otUsj^agQ31ie11Q&&v4o>UVrJ5BjTegf)G6s*}P=oj>Z z^5J7I97l7yAB_lSlr z8vd~j83H3t%&&$BFW>u&#Nc*nnDY*I=wm75@S=^>VmBpo+QD6 z_;>@B&b8^q#(i>c-`AVRgGBH`WNFuX4G8eBZP0kh@!9#_z0yqXYv6>cRH9PRd7s+L zF?`95*Ee4rKFkV!0U0QcLu#xkE94%bT(Zw%dS15N26f=0;Lm`t!{E(8!A z;RL8W!&XXY2D`=BKJHt6(LO$eJ#Z;8*V3ur@FzG+mG z;Kn!q{4X?{|DvRB&WeY=nB=}F_6ZMPz1B!_&x)1uY%aNag*RE!fhP~7?UV%Jkb;jg zp-ebty7&$b0~lJ*S=&@ZAmaeAPXlrrGXO-@ErB^pB=zz>#m|F>;j#*7j)RBwc2j+4 z#H2^~*niluWdlA8g3V8=?R=M?9AghPVu=+2v4h+p0%Ff?n}$W{_Xv9KAVE}^ECUus z##w)FWU^>o8n$D|uo);>!mF3nr0e+FJbZrg!SJWRc?j4wvJnd}SkRkA_;u#Xl-Nn& z)X-S?!1hJhMf&2M?!0|>-smEj;e63PnhXVid=v0oW!lxZ=mJ8p~C3yHZI_q0@jc&Q{oR0Evol8Dv2le>Xa^Wd{jEC~o zz<gI={#G`KH#v0G_4FAxOew6BJb2T$FS#+Y zfvOb`+(5g>LvPZ#CvLLV^tws;w@v+cwyp(iWaH#k_yj~`Yn>QT1*Whays>kEq}}+T z<8Grp2plpY<>W{XzMFOy4t<++qsx@Q&_+(BsAbs5P>erOt9|73>DT$_SE+p6Lh*qx z)w0srF|Th1dA>^q`9I)%0f4N zeDmmxk3tyvOPjZCEuiX6AFm3{5NVH4kn&Jv&lD5Fp*a%V5jqJ3tl*IOle4DOk=UU@ z&N|Wj9EG*fg7fJvU_p4U&xCm_tKCiOoVcAuj*Vt)TaOG9<^b>&^$~#qx)vLg3MbTT6($gZSo>Uu8>ebVwq?;nSl7 z_R-TJa>41BHRM7GH2xv-roacYhPif_J5lS*3A}7=rP4B$j9_iCv!At~WJj`~QeKQC ztnS#O<^PKs@KTU_DU6ic8DY-e7e#+J3Xe5JJBKXU%|?T-L!{>)vB4bdjFX{Z08e6LJRmi+YG zbfMjB9;i}0p?CyL5twnyMs0)auEav)O#Ah;4>r zb5LmH&*Zn`6Q$jJqp%hW6H1DI;2#|X&x~+1*|Y2Ly$|z+mCL2O&ZgBCKZg+j|DexD z$k|rg)s+L$fN7fA4?7npa=d(L10<|1i=L*d)fSalVnGPZW7mjTB_0oMArqWN}F>{CP6ljl#;6FD-6O5&zIpisVyN9!|vt;MIjw8Eyx|ymz|2TWj*-f8MK^a zUmk}uXC_{MID@;}k3i5-yI0*~QhWq=Me+jYr{8RxIrCNbjzgv9nKM5xwb6m9)Dvgj zX|)#Pre-9|Nct(d6yw>)BTSB;k=DezVd_CH9wl&F03u{dhNEZMCM_b9UKqwZT@pSJ z_JCnnDk$+WU_fRb9uNrfb0KxAbN=)(ke~b3dr|PB;whdiHK0x=n2?qejE82YwgP#9 zP&QI&ESJLCxpm1j7w0tN%~{@D)y#R6%27W&^;2XAXYKR{Y2Y1PXiBI^lVwv;=4LGv zRWe2$P;E>>2M`KTZJmn1hky&69Cq31UDashj|+gIsalJ0ME9*6MQJz1s@{V=X%`{c z&I{{a4)=%+|c(xtiH!RQBoKdF}G+L&I_p9XOn z-4$)Uo0|8<9^X?NxlXv^^u^IUW2UL@%7c;}W=pg>nXzWe*9xl(eT20>)&o%CBxMlo zh-=dqWHwW>#ktO@iQsSNo!(; zJPV83`wz&|REvpE7htT}%?gy`p zWleL8E@gQ{#scd|CWsccQeANBJLphY;KDx*?Ma{A&g^#RS1eiS#(lW=$XTj~_4@=z zLvBo>7uc$~DW0_coqjXpmEa~Se#CTW9xzG8`gB_jQ(rixXrrbHQ1QUFqdiP078Bd2 zVXTxY1$V4q_CL%F2%Eho#7Bm|jpxUftm`X07Ed}jUz51|ac;JKDQ1AffcD52M0X%&kU zIJHFih3F%Aq&Ju?U<(Bvdk=A)beH3@2Yup#VghQNv2bzuUh!jandsd0KT^% zhJ)jus#CfA*J@fd!aK!#%FI6OMC-s>z>p5wg+!K3wSOwB0xMW!*Eky|gtslU$J zA?$$7JVh$4E)ffJgk)~*eQ)i!lXeuU#}&a$GieJ9zvI(&+VPcFw*v;@%jm$kNWe9; z4=J$+OV<`BdmIbcxD(RKPLhC&N&Z*5Uz|$bpM=5B9}ft&&QZz0;~&TU6Q2*FgKI3} zF#H8Q1_yo+oxqKNmw_{a+t46`liHIr3`X|cX}rPrU~h=iuxaMSPA1e-8I>KBK|g^# zZN)?)wGTKqS)B&*AMzirCs6{)Bj+sjY)Ho6p! z+6k(kPNf|pFYk^lb$h=NN;SUZq~4p|n`@2_aP7_KZyhLY6MS1)is$rtaO~Yn#i>AWK@Wr zR1k@KLASsE^5x5a`oRz4jj&_Wjk!(~UHKqxAf1A8^+0cFiIo*A83!=~iC?1dU@rIu z0XD?w5K?l+=PzGK?m4r+zk1;U-J#$cJR9X8pMU>_kkFNV-=~ytd_qffV|f#YM^o1* zkyI%0Qyc5>{aaoRySeY}?)UIXN`G&4{-f?y;$Ug%Kjz-<(ib-{R5F`FHe(Bh^N?IH zNZ4^2gAv1s&1{c9qUE$`bOQe3J@_1>x1>^^7%dyN=QRIi^pDrRp3{HSK9{Rc8oXR{9~c_Z3Ur!~WW_Xvnc7>4RF!jB*mh&`*aG0sBsX zF*2-UccwZ`joi5?p<+HBY*H7u6XwiKW)V1QrbRf>;)%?uFF9c%Rs_0K*}WUMyE0q) za2{33OP9w<{WQ<)yJ0MqNI?ygaV4AHWMSsGcINrq?~)7vV$PWZeA0R`*;sRP=dIqP}D+(6@w3+OK4(wwV<>C3P z1WV?uCm4@u0_eND#k5ZE9zdm7zj|=c9As15eXw!qO8xuHiJ#rG>4#OS$uIE>ubPiw zJg+esMFhCXROI@jiTJAdzIp?S&929KRMS%y0;=Y#$Ej$8G+qactRmGII4y^p-bcDF zQxI{~Q<=X){D!QfM)xs4`OE63(S3~Nisqv*!lBcecR^g?f@tie^R*|h;H6T8YOEIZ zM<64C4HjWXlPBg|4}C;6l>{Ffb6QIc-CNQWWPbC;>4x!;%7)*`CeXyB+8@ogqs${_ z(?4?akci`KW?mjox8JW6Kdjj{srSByY=ZK|O}3@wN+Z#{n2=3}$7He}+*)&E$nT8% zvc{~w36eLG(US<>hV|RzWwY)bwf)fAl^QpRA~`)tD2CerGJ(4_O5(BeLuj#+i_-e^ zJ)_=C`*Lz=TbJH9?o8G$o%xlU_2muoaYA}&uwN~?()m3Hsb=z~ze``9?g(ff%}orJ z^mn&LS=2S_Cp$Bno7t9k7^gk`K_AiKkp-KI6GDZPL;R!ww{@aVR}RQGlJ zUeo+r@>!D+(^id4O)XUT3jH>Y3$3I>#{i|X{m103r7QJnm3Sio;K3&dSSks_rfIX_CyQFb1L~JDQgh> z2~n*c92X``X{A|T>XpmgY8fVcxvLUW)8*P+!Jj=9b}8vHTg0>CS6f|*O@#SRkUP0p zf!382<8eU4Tk7*+;s=ce1i)~<9^`ZLs*Et*#swP^EVaE={6DXsCdcst@ zgs&nKY_5p}2Mp7;u2h8&IckOWy`wt2G+WIlV4M5EoJk%sTom+hkdlnGRDcs#&i80% zB`B0y{jb$#tJT>WUZE&%x5;49exwla-|uj8b*>QjnX$_>D81pn;vBjF+)Ec$F(lG zCM#v~7SB*txmt$Vr)qma4CSO%A;~mZMEn9Y(ORV$`y~RD3k{HWs&zvmEfTCuE=i1f zl4*ItsS8zQ%qB-FUt_XSI%P6pwn}`HkB!=f6+WLXisuJ0))(Or3L@}yNiPni$0lWy zALM#V_2x=xKPiw*rdaiZ5_mQ>AN?h!9Z9+cwj|BDF0pHVVXju5Zo-~oCcbq%0k0r4 zLcjd`Jh0}i0e%I@f)=W>3(kQXGHRcLYYmhUQrV-ZR#9P7^lNu$+)&9yvn8hfHL_rU zR(uCYG#L)@C^22t*HlfD41g@K=&K-PDj)oqC5)Ihq0qrUIv#zXpKScQn|n@5obz6; zRj7{QmnxnWc54*K{aW(lR#>Tf>3izP;p#R zG<#*wyIA_U4SA77azO*H{4jo2;`P&X6s} zWAaXnXe7CcRx$bdZuXCu(glE<@)XjMGrpe?-4AI7*Lgnl7Pz}$4YFpg$U@H*8GxJ$ zCLl#v)VUn;h;R1D)lb6wUV@&vt|mTIOHeo!k_z`Ells*#%FSb4)uXqoH2lr7P2BhE#w(VouN>_AV+GF*ren=xXuf zJW5#1G@fnDc{9zEJH_z9fUsPY-xEjKuyWfc2+s;vPZYf97^;xB{cXt`_swcCkLepE z)a9LSejLMOafP?{L&LwHmC}!ngt>T=aK)7l@@;7;46tA*%6U5FlF4rAaev%`^H0)J zy>_Zw#rvBunpr=+KEn?>id6Jm6i%WJ7D*!F z%5boQcU)Ss3)msA3?yfMwBMFo*6`aHYi@|JztZU!dWC*xIn8yy&|abF+e&)@vR8e2 zdS!aLjzQ2Wm%ZtQ?S&)97Z;Ocaq;*O-ZW_4!f<{e3>W5y3)Ab+e;nqfv3_< zv0hZ27Wk}hwN#h0O)?7Rl`ccnhdil^$Y^ZIiN^hyr)5Wo4n0pl4l2Gg8Xc1{^>C`t zCj0bBPg%S7R$yI-!wTSZ&LdCh2-}x zT}r&mmp$+CFnrts&ai%hTcUcN$oEd|UHN)c-QA4}r%x9aPx=0-hh*ta{66xnhD?7Deg}G zu+vXwkQE8uw>J0fIDfbM-5oqZct>U6U%nS#^4aFW!I$qnOJ*Y<@8cI)rvrcRdxPIP z{*TCmoBLvv?hAR=PUaCY?p~jy7Q8)n(=WehztA{1`1|^WY#!-aZNr(D z=@AkpcX9~d4RkK<4+H-MSEnt(#`xuj20u0M`=9N9|7ZK3{ijj**uBVA ziUrv7V+RMYjGoy~*T4E<^m|QtOYHWMh%$!7iQJ}${2-K8jD5wPDulC0G^+oyZNEP%x6F@90Lne;hj)^d%!}Ud`<+i}ll-Iw zm#q2<-Lxk1Kz!zscN{JI39z9R*nD_}QJwk@2vsX&T}scAMrL{I?Va$0T~(Tn1+MTk zq`jNtGqZPAJDI!j>fQ$kzEUL+0th>C`~k{SBPOAdOqlHyF|ab7e#T48AF(O4%t1+#a*GTd%xu`3 z9q@pm!=GdXE8~zTo$R+~J#U)YG%qT7{pxbJxe(oo18FgWaQz~#`5^caXxY`y%Y{lV zxQE8Y+klI5g&=< zusxsFrQ0Rc9_6Clg-^!V4Rgx)LS>iKzpH(0Zmm#eza(9dHu+|;o)%x3E*10et*~H8 zLyr!|sYH{LdtbF$7Q4%h2D?ZB?K2EESKBqCI73;+8V+KZUKk&WL0j}5P+qmA&J8-{ zQC9n;`jqLEhc(Pp0%s~7iAJRyn7ViP^^N&Xv%IHlq@D(p9{Wq7o_nV@866FlB(BL{ z6r!&BW^@3gN8!aWE^=Fa4pmw7zO`6r=H_$F!cYrqSWf1|oS#d|?tS{o*r$6qS2hV$`qlHUTB2ri(XU%-0saeAfWJ0*L+q^d=hFFGSY zlU{5Tf5j+^Jb|vq$U_<-1a|kD%V~wKXdzZ8M9scZ@slhJ^LDnWoL4-|k~i69@Y7uo zXz9D4F`p<))03Pns5Z!ddB(=csT78aneO2>t?$;Nr(7Pbv}D8EZq_i;Yo{%{TrZM! z+!BRfdfgUgy3q_+wkr9aV|9x2oeIv8b~M^uZT@d7^O4+LiqzQCU`y6(^I8O!T1rYo z0(q3ur!-fLp#zaKd?~nw4!+8NclhDYNN)ZNXw)ApB~67Dq7zZ9AmxHC1MXOywgAaXyO%cif(>uU8o4(s^fIQ5@8}a*AB=`=j=x=+E4?>l(KLJlu@~=K zEPxj;hh)i$liAy5lQ`Jqj1MLc#kKhH$Kpo(81-COM-ORI-&(pGDqoUok!%VK@a|4f z2)1@0OV!D#@9z*P+s*w(6y0(*3~8vFPcG-pDWV_WCCyM<8!eX-h>%y^4ElZ;-^@p7 z+uxz>THm(OY}*Y@@Tp})82|hWY~3zslR>g*&1Xpxxvkz#BR_rmIl0$9V%)|jzsysn*YM`UrkYiz)`8C4*47x}ykE1fu-vXwRy#=pRCN)~17OuYXdd{G2c zDv!oWYqu{+iPN9`A{JM?1m!UiQ=knS(zY`#Be%A61mTSh zyvK4vOi4_h^U@dFyDw3>LD7BX_+v=qg7QZY3rGfArB$pECj2AL82YF0%vn;`XrWLOi6O-S(JveH7AQi~9A_KjK)t=)beqwtFv(-{ zg9lBK)alIhmx6`2^fz*)aBoRbG)sG7e9R9qm2*7!#|TkQ7htC|UW~jZVF7u_qj0xg zBwk@RTs{grl~|PE=!en$Ob)npJbs;yv$#`=!}&ht3>e2dAv7_15u0waOeGToER%&f zZXR8*1hG{_v$sqQ!<9IFtJ@^PJ%wtVz-6o20%CK0z#F{T-+Ha|UEJ?nv`oMei6)fo ze}gOxtZcwIK_X$BcsR1Z+f=C+pV6T?1o-u_O9;&&t#-=`#0lh1Cf{Q*f&vAFSd)aB zXkhTXB$%g2B`DN1z+Xx&?OSH`r%JVEwieV!sz0+MNe&|6?#K2SqFg2lnCP~FeD`$c zJBR&lRY!QeUef#>$zyWMfQgqHud4&AqIY{yN3jW zWV$lkGd=H>e1X>g(fv_;X;WzdE~KU97RO`TEWe}CMz6cjX}qIcUwrwr?^*uArwaMQ zVN3qFgSu_bZ7e*>S35WbCS;g$cOOAxnUMH&j|A)lDa|x3o8h*KtDcGayIbYkDT4XkX#D}TAnw{)F+Hs(Tn^Z~Jd+8e!KB_B_3SB0m!&x%D+Ufm$ z${#ow2Kdw_Mz+2gGqSU;dlGNX_ezcS)OOIt`eNm{u6gw

4c|+cD}?fxstxy=kowgV^mg zg?Xb=?B0|7f*?oqQG1{{*-H{88~B{Bp(5m7nK&QlR;^2-%Vek{8Hav7mU3&QoS)%r zK5h<-fw+%=#eO)ms;6iohQErJz0ce~hek#2XnX`&|EsIW+fS&O8#|4p)bTO9!%#zd zyi3=1T-TcP(Rn)Ny8AIDICx4A@4kGkS$ALk$?@R+RPDhRp(`zAj9urUaE!=g_0#Hs&#YRJ4ipr-WH zD%j0X0bTr0Q7s_kAqzz5$w`&Lqtsnt$gzW$aaZk;$%unn;uR=`k;3*h5efUqUf|re zq(kruN|nZiMx_MPfH){ZB-{1s;>@G7i`Dwo=jxbWyW_Q%Ucqd=_n(O$cdB!zbG=+eg^Qk1CCN97Fu^AlD9~aLe!Z4e6KM*U99|8!PsEGYE;juDVN7bH5r_5r;j|_q59-Ir#@hu8cAcx5T>RZ z0sMzLGLa-hqTf}c-9a^)q(54flF;CrFs^p?Eko)NpW z`m_8E-}ai=+ULobeu-$tD--$;Lo9)+Wbf`3THyGVkOK@m{`$`9wK-wsqVY@7MPi|v zM;v`fxmjNO-F?#Y;QaUiVgsldK~9fhM55T>3`YDBgFc_C78jy~7>uV*%kQ+M>@M4< z(G5dl%Xs~vXC_`#mGNuTb>xC@fe$~`N}mh4s{h|(PJNAZoH)ULER2373~TF%MstW! zx%Gw@+P+esB70aBD`g9fO1*{z6zX)&LemKA+>B2%iFSda24d`xT{D9Pu#*Kq*^V;k zBT{y{WZS_m?qVeD>|OEw4d^d%z$4U>j!#Go0n~F!o*n+zPqtc5icXY1f6Dit%y~~g z`B=I9*!N$F-}UrQ;WWf}_FM2RbMwTz`z3?xK5`^Uj$oaA0vvMu@{hbDe*Dqnw-M&L za{P(dU;Q{LfzxlA++Pnr+R3R~h;RjoWSl`povq7FVZxXTGTw=|&~{R|TZo3NYD2nZ zqCs4e&qbN2pJNpS_awep z*YG0MCO9T#^fm58xgM+nHhq4BT|Y|HB{XzxcSbtkGRD|#SYd}tSU)SX< z^CGhkU9D}Qd*fevb*=4jF&s^qy+c2oL9Nrrhr3{io_`j4LSM00bGwIIF;PhSL%Slr zpT*Hidpyomo7`9mPtIiIDO=f2+>OK^M*YMQ?N;$fgJnCD-ZxIz`!Q_&AyA`}X1{!$ zuJL(@gX=mxoIOb8(Tntf&71MOn3#UF8kIVPL*BGq)h2b**#a1>4y97zZB zB9MsG9a*AIu@f7-bvj<#RONit$YsRWSh(ndL%Za^2saeFhf(+pT= zcJ>UBz1`)kQ`e>y3XB2yVW>eMCQfcZ+R&opy* z{t|*kbAbP%?*0#IjkUI2;VnQy*3M1W#Lv-qS2h6+lrx-v)6FI62vy^Z>_{#+Js@kZ z%m?bz5XGrIbchb9yV0T8+UefXZo3UTk+J;iKN0==Pbi$ZO`4ioA%0KY64p=@ASuMS z`Tty{G*yT5+ttD~)z{?V_b(LUM<0y~bv<0TP`C?6m|o#Ni}X=s-JI%l^mPS`V}O1z zVeyy?CEg(oA~r3&K4+sLv)6sLsx}jhpg$!?`=YdKqWG5gyFGHeT*buRm|fVu^)y9d zbKz4@9$R7$d2eoe1y8?(mBlzO?=L^|q(5`Cr|KF0;^|wr7iKreJi4--1Niioj!my; z{+=Rnle4nM_5c=#vP1gH0ipuOBh^DSQ8&C5Y;&+wWG~$oMGtI*@wsyO9D(!?M3H(X zNx}yqAPt8>@L-tymA_DhvU@a7dR~%YE>cJPJ;e%sLw~9KP}%dA_%QQff@>ufDWT;r zm1(#WvoC3@LZ;(D0s1koAcR!;`l|Q^)sn%^OD<>gc6i^hz+dz8_&cbglPi^rLE(I1 zE*|VeezP5+&b){YCvlj`KZ!QuN+sTm zHgK_}q4kTGQx6>8v};i?NBX`~jN4rBeoN};XfDr;HvuOvv72k2kXqHXd071*3>#c3 zjrEnIfqAiwYa0Mc+cQ`@nIP;u`pl}9n7esL+5NUTU?DJ^D4pyIyy&Rum4}k#!+N_i zUGU==j`P|aJhzb0U;l%^&BNwt5c~Mn(b;GDkYHTjueKX=zeP$1ieJ=+c*cTZxyU7& zam%mxvuj+$yN&Dob`T6JZ0CG%+xYBf(@*{C48)0S-*0*^aHfK_-X$j_!12a6tgPl^Oji z2&vT}`zocy(}c-zO0HkKc3{%q9uD^R#|dXHq={#MBn07Hkspx<2jEbBvsQXadw^0X z$IXTXdRRK;Mq|}kAOfPWY3KtZ8{mokON;Y(H7an!Z9b*mH>CGjJO=tD9$rijaU7I3 zJg2ZKuyp2nPcnAifC^y5jhdA=7Mq`5+_Hy*dMsn za?};))!&4{uMj>R1Q-~(+|u6>e({C-1F{!K?{c-Y-M)qTibOs;^~}^KXvuDO2UNkZ z2vleBB-@~kIEiyGT2J~z!PO%BX=WKLA;=GO&8&X%!0UX=^b0w?^J|Kk_j=}ZyeSm~QA(!iti5eCbJa~K$tBYYR z^j9%ffHIg!Ywklx%6|EdN&y?@HLk+a%0c_$dw&Lgky|KLTjdA~n347iW%5(=%PK+| z--iAx41;{b(f4_zqmf-y{uB4%T(^L#4upb@kvnD=D%Nhi5DBaK1|b6h^rQ*@m+G_j zrHQ?^#-6EQ$RL8iBw__5FyADDoc4x_wsX#V4Mlz$P>M8oIT&6sO9jtoq$Lw#(K81; zj+*RGDHRiH{c|pB)D_C1RrpR^ko(WQq&|_AC9ieTb@#IA5qmak5iOc3%uWrc$q4OM zxB4b^Zu*h;kr;?_T1hAB4g9c{IB?}XH#_)O`MBPno+or84r={&kdIfym3Z~BaX*u* zwFBqjuC$huifp9R%mLGOPrb)Fv%@yYX?c3sz+d_}KIiL^yE5(42|g0nGJhE!CN@v) z>jqTDOjNoTk9kZ8kDyDSzwsf-9s3LmE`b$;^FZfJe(QO@k_$TxfHTIblqV)aSGnQP z21!HErt(67!PcPg=L*7Y4}Fh5Fo&yG@@B2xOryLVrB1SVS_>LvhQhM9~dgh+D343TK3FzP?o5{ zbuWSElX!a|EpQ7eZ$%vV;4XWzce#_9<#73FG2&~DMvX&3h5w-aK&{FJHL}Lb@~uVx zxIv7MQb`f+A+JTP@qlc>cBJq4B#ZxT4_-W+c<)EqNIl5EuzUPC{8441<#*l(*A6yO z{n*BOT~{n0Svyj}*#4YUklK0!ITH5H9O)gaV^hV!!qL`|vaVcTZ%Myso#{!`G?3C0 zn9OYIk$Hgrg@J_6z;rY7*O?ECGFTuP-FSCT_#Qzumau1Q=u9kdu1x0Tv}69UgJ<#f4x%U$*{{5$C-TiR3)IYmcUDLyMPL}hnw z8m{x;)H_t|fQ5G}z7MyeM&x*`G;MAyDpsJ)(az}5tB;KOZRj@Pv2G*f$9SU{h9|xL zelGl~)(8*CK#8}{*gYior0<->`@Flq2i?14?z8_gh4aDmmqBDQQ2!~m5KXCj+{qPp za*ncPx_H7Y0r4?9VQp!Cfh6-~RfIvCCu3YB+C z4qUD;Z`5wRMSZnG%e9T=`Yn7d4o6shAI zADjBIsn4D&q*K2WuXF+l9XW?cx~+2pBFUGnl-c;52R`wiQ6)bp`RCI_t~l+;(nDqB zVtfUx=$VTnSDw~}iPx86W$g#)51@b@-`5Cf$(_N*&Cz~EQ z^$05PlmCgAOz8i_Tyj@orx$ebXW=St*6SDf-Kgz3zuDb}8RIxaq=g1-_bDqf3R zbh@nYKjD6CsOYd=>mG%z$rjPbvnT=GxoO4 zMRtB9|IE=%Y*@}~k=V`%7k1ExCK=P# zv8J@oaA)gY^U%t6F}At^qXDr+%WjuBuWelbI#&F?ys`r;b468iHWutI5}6@r3X~SR z_NB3xza@T)%~nVH*)hrxQ8q*eio>UraD)$NX5vOlbG4V--DIXj| zAq3UsuF)`z-N`PnOuN#vHuSERf=ySwWHLOpfxoPnY;1@x_7y#KYIcYlav2Jl5--q= zgJv;4U|Dx+WD~vWo?HQ6wb}V4k9)_7tP~#w-C^sbMQ1!{CI|iM4cF#?!>{fLG-;e?*jYdI|oG{d7H~E^( zxh^^m7gFTEokG7TIq6^pAz;5xE8OMa)OFCz-SWyH2-6Gb*p$fTa3hIq(wTNY*!=NY zCoY|#%6h`;L9Nak{h{7!)eDp^ik1mmB;5brT)j|eL|2Q+S0y^h4e_6rTT*4>e52KB z#DGD3lQqmpByS_}KqVHvT2&%j^F?v4x@PH;JHLkjz#yGBbMzN0wzpG$cNI+wYeC4YTawG8nx2Inre=kdWOTONqLP%_c-h|h5zSXXkah+0~q?B<> z##CVm-}b-H5#B}^Y6{#;A5jegoE-)71KU;U*|#B9((Wq+y(6=Eb>H>Z>ZNqwn)=u1 z3sdAf6Z$-=a4M(IAy(H%DKzoe+$rh2>@<%}D7yB;6?IH1f^0`)bQ|zng`clc3{75SZwJA_m{U4a=2{xMiiY# zB_U2Zmm_gllSF&c{2A6H0&nqjsVG;lJjf^z>>Vx7dz2G(8T}q5T@M7X6>{Mz$Yjxz z`ebr`me3B*f;B%*&p20<6B1nmjs>~XPnxHhVCbejpgBA=S)>p_qi-=aQ7}<0%-k|N zLh~5oLVjV^6&TpQHh?u6vBu;e!s3`Q+^DciiM><0w^IQxkKw`w$Eh@`?218EiU(hL z#q4u?xayyg*E9fg=CYMF8Kq}@CDpCB+e!~W|L)LPg;3Eu_Jp;cem~RwwLaId)5QeU zM!jQL3Y(gt>Y5DgbPlG&&F63U_8$&{9e0mF>h7*>^IPLyIR3sja^Jti7P&ibH>Y4Q z?bK`YyXv^Ldv)LUt_2iSdhJ^t;a@X&#m>QCP`*rv8mL=R_+_fu=mK{s425+uT7WqI zZE|ylM%O@T?ZhyNrK_P%XX@Eiis6>zU{p~|NPR-(>|iw19sd>PE5%qTuH%x%zY&G8OBIx)&iqq4GbNzGQn# z7kx=AF5w$eDKix_-RWO*s^Z21JQ}UlCu}8Rqqteh{M^6kCIhn6$1`a!U>_x56$Is-d1yS_>M@b!`_Smk@{kaE^ncTrt|GVsPoKMwC zSE;aowvimMmtVE)5k|}31G3I|Xn&2*qd6rXr)u}Uiy7>6`VrewMk_I(Y$PZ^=Jc?w zy3JjfK|8^ZumLb^E4x7lGNVc`wPX(4i$DK~LN$!*y+?ZWI3z_-uF|@rMcif-{6r9a zz{3yNgj;W_%HvYI|S*oJLvvMMr zbv~4nH*@snup>chulA z_j2y!?)ZbfFXX2ijp@cPq@ZIyzDo(QHXEhVY@rHjthCHk{K7+JP$AqkuCZ5%A5Arx z)x1ynl^qW31i^N7NMXn%_l|t26R$r5FTbrrN-uhpKq>Jw%K;saCa*CPr|Y13H8bYX5Hfbprx-ZINh;fkE#SOSN_1O9C=Q$IJKlv=IXq);!l!eGe{ zFQD{%G@fohQ6%bNfhr~lh*oVTUP!|BKTHW(7ssZ2a||zkz1ATc8|ebK<7ll|hp|>j zP@BrF{C1xDG1c-`WwG;l&lOdlh(hU(5#^CT-eietx7}sh!Xb!q1d4J?qy3-FbD+`* zEYaOG#6)sNgwk}ex)13%EK_cvo8H63ug^PLS=8z@R?4$20ypO$k+&OII2ln@y?|Rn#Pl$6{qi<% z=us2Pw_Bnz$SYRxK(>TZ3t%jZECxuQd6DY! z*R&bph$A1;a8-Df2fT%A8azwU4{ae^SPTCaV)<9eAiSHyqa3V}z`giYEvSpR4po7% z-n~0laU%Px>G^(E#9N+aE|Kyn9cDidW`6=%0Lvi*PZ{x7WmMl|HgGSydqWliU`$u< zVBpBGr{|?5Xv;zxs0${RKnLZCBg$qok;8KwC z6XCHakIoTAUsG?!_72xx<0!HLskP4AXd~ElHzou_PPIMOJo+|)0*@x8SWVMIwUo5+ zD_(Q$jw1h)9oWk6tkm;_H&C-zWvr)*cO>D(uv{&Nmjc>!z4&$CyGV>f=v`6@!0X|c zNO4^bcW}(I>y&B}+o_*x~<0*!}6iDi3Qw+|>4Rh>5`|QO0>XH(Y)QCCnhus9z9A!ctnqVB_7E;U; zF=bvMR?up^w=KpNvxUhrL}I$-!aQ=@huca3&#h|bSl{=Gk9hulEKOoy^I9eOIL^O& z98-p=Ntm_fNR$2ZjQ^v@sWg6+P45!xWW)0|+}db1b4SOLxdhlkagl_{h3Sfbu||7Y zd+$WHbV(yg<0f0}rg%KL>7h<+fE<{zkpllIaEz|<5o_zA=D&Zuy#e?T=8Bf!aB5!D zo;KK@7hKHI+(Y-FyZF6vbY+oMrBib^UP65@pNZnr@*s2P(7K*Ln5wp40&Ca6`)IW>}JTJ`_;=FR5b-!N5+bgE9>|?GKCUj%d3E zm=j-f)22v4@K1k-6h;KmGI(d zA}0BS@_w~a6QXDl1~*AOOCZ-t!R3eu@brKwL1|$7FpC0W#JFFyH`cKwra^^-76_HZ z9~`_dirOl9L&Ufux3O`7-M!>4VCLoC`_O_8Ds%19|I;Z}pAcq-m&0o`ukPbxe}cRi zk+IacJq#yfG4D5bl1h}ky+=&CS8UJO+%+D0%pKipEOdf5OP1675Ayj(3Y9J@F&wVqBLsvM7*(g!YE1j=V|!MOC$(AA9*~2=4Wv-fY@&_; zqOPHt`ih(4s1wXhNKE z3lL1*WvJ9!Te73YH%(Xa;tqO8N zL@1(rZRdly+Gj9`^x;psC*mx1U0fc^d_TP^3vN6%vW0K841k8qaYwPq54I?J+qKiu zk1`CXN2=PbQAn+P&0k1GPRd}>IjJP=s{LXyah=Pe;g=7;!|}BCyrF@tM!b8^!RYLsautOd3`QCk4ZO>Wyn5%34cWC4xi^kA8d5rOe!B`yG9pa@grH_Kz} zj%1mY{eOtl4Sm3yUZxXUE2M`)SG&)gxYk+pvNvoM%FGL{$j?;rEnI58-{o8Uq#d>P z5ZLt#T$cWzIn^A$Lsl$)8m^#nIQx|xb%yN_+a7~~ficZ*cAM~X!Y^hloswarI`^F7-1qRRZSpttPTNe|z^r=o+YzGj&_ zm}&wXSo;TJOLXJCp~HXs6rp-We+7S)Y{&E8{O0@*sAXO^44$R@9ad}ZqaV%v#5n;{=n_^OrAb3OJojlSM_n% zl1_m+2BO^UEb6@(C2gMh>u>l%A7reE)7r24lIj~in)L3@rZg%hD!<7uFIt8+JT6RP zuymX;e99+!^qCl^w|KL+#UPQ!ztu(6VzThZPiA>ro;Eo2eGIQc9@a{F&bvTwaGfh4=blMq;MJ|uK(y*43d)O{J@D?FFqYA zFMpB95jXopiiDma3LtY(ISvx_1`VV@EIfX)&=XLg4W{7d$ZpBSW9TE8FN6|sAE3CL ztvy0#g&wthbt=0ETLVgTR8_XX3NHy`WB;HR*hcI(xN%)T-LBhkAA1MX%w%zZB2fE- zOx<)TJckAzjSw8$9U{gdm%AV2o(+O+k8%#WXNE2!-hdu=hm>+#I+|NYk|_o3qFdl{ zJTdh?<8`z9C>0~#Q8t^CzW5n?DrMfbhmqB^CsWWKOq7t`H&fEI-Uhig)?#X0IK(uz z8ZKsicBIXr?`7A2iTS0Qqs@{oDwCbtQd)BTt1Bpo7qa;D>cue&_xX&Do1+&-+*}A> z=`}8nUXiIh{RPa9q_}Di5<~9Q_^kvn!bYpAp8D+6 z=cj%dNhIdF)FMmohs(l%GszKvLg_@v09Gcn|D5Sl%HNq2u%k6BXo0}OPAqI9Ad`{r=U{MgAQ`82C0E8bufSur6*(2BXH40wP$zC41QXM6$nFmad zW5`8bz6qJ0lpTDEke_45qWC0-PC9fB1y~X$XujHrO9i}036-j2>QvM#EXF$9YN5g> z#oXu<+)FKfhI$oByr=+dW$Ua-scOb~lrPV3&C;e~)QF0-WtOwOILs$4nylRNL5rL) zF$?adr{&q4ynq-$yjTdOaxIRkjcOFv%19BMTIC=pCsmvWhd*kP0ejzNf^4cmy|HaZ z43#3aZlfM2dPv7@+;XftdI*EE1toRVxBZ)(qm2~0TE;BsUn#YNrJAhuw zZ7l}vlINsN@*pRObgF}#@U$#t&Ml`O6KKYw_B3f_#IPevMpcVs4Ri0a&(q_XuCKhp zbHpK%J&V*hxND_x=2TgkJ%RsG3*5BZ6tA|6GKz0NXSk1z05o3Owx&~qI7!&sxm7Qh zZ_N;Bi`1WkHg|;RmIHiR2rirnyEF6`DM#v*`iMhsLxFx=3&01|M?r9e`ys1S`-XxB z;60~Ii^T3kH53w(@{^_9s2UL+N9pN?YY& zWwk{L2hTgQJcy#f@@g)uwN@*z!h*LII!CLyT=i%-zcjb9GPm?)3d*!4QQAIPj4GA4 z7cd~mfw+3UNm|5i^ZY7oJq!xihQar(dqE8>zrdRo_*e39k9ChlX%l1kgcNvr&y*y<&tVs*shj7clOqL><^L}7o zKFp)s{ZZL9d@0j&g}=BqjpRI}S}Bxjjg(NmL)ByjjQIE|PP)5I`8)C|T3k}vGHHyL zfBUAbQg$xQnXG9yX_Ui1)uzNEl};OLz|jPVI8yZ?MpO(DY<5G>ffgJSyE`)DY4RcO z=0}`=%eb9Eer9eaZy9vsMyqk)JXjaTfzRI@$3~~_ts~}-Em3y~BOLw<`h>m(6P*(2 z=M+`Fcqmrd+2-ARxBn#jt&b-MPNFu1#@rDJJXiL@3I|%{l7=vMh zVnbp83rBfQucfKdw-6$2`LBrRL<+g2-P24VodjrPsWR87bwE(swd-PVz|&BMF3?TG zR)BTIl&VAEONvJPu%KI!`~}ZkCMWGhc|9vdfc9GXNn~HmSY-7rtnP=ggr=IZ{zer1F<}TZfWv9jM5==T z^S1lg*r^wHWDF?fm%0KGv1b(v1_!j?xa2@6mRQ#BeEn)%YF=#!oW0|jXCCxCc#g$c zBcT?abix?)aJ8r`XHobUhV2jhG$HCgFU5nwWiVY5r5!XcLyzQTQWV^X7*DeNp2=nD zBAH$hd#yAoO#2WTgG(`%3Nk0zjNhiY4EDLVlYTUE?wZ-GX)}uW`6a~)XLvdeRis_1{G@w&Ot0J3e^s0Im+A~suhN*AS(&CJ{p^fCy)rY?sPw{z z-Qjh2u6z8f`#!_vY+|j;V`R*m+GE#|#MM8c=&nZB7*8^hj8x`NxI%iW?@r@%)KLdh zRfvXj&{OC8qaxIdpcuE1(79GjwHp}L1uypPke7=f-AMiS-y;SACPqX}dDusgg<~(0 zYKcmYx>42MgU2D{sQ+Q4AzuW+_6hPzua6eqjtiqh=4N&z@wlo#?jVBg$G)dN!R@5Jv%Lu+<_yun8}6xBfBmHQ#(S^JI=7i!FV<_Ny0-dU znEcT9(5F~Dd7_w!$0Q+s`s$7M^BpXzdXYxxr-M3T2Y!D3r{8$*pFaG{u>JF8i4-Sy zHAhu@^av7Vw4)(DcM+;J5zr%HPm5KoCI*s6e zGct;u8nDUBja2H-MRT1Ehu_TWsz8Zo}F8Lz>f_?D?JGt(sEv_Y^872;zW zp7?2`er1hmWx17C@rOWYJ`6Ma-MbJ-{+Zk=AHZ8nb|YhBbjN^PLfTs*`#gKG;Jv* zw6rv(Eu|^5yOZ5%W@jdIB-sk6v>c)cq7)RlL@q&vs(^wwC@K~Y)Ou4-#2f4R6Zn0; z&-1>syGdL1=byhE?>x`*UeEg+-}`tu%(PO)3>&WO61B*QPN04V{cI}$QXw9{F-^7_ zVPU|zZY+sk?=;t>gn`RR?8&gc7WI6HfNg3d9BoEeh=eN(!571*O9-+E`31#iDiB1f-sb@Wg;d?>0yR~;pR7%a0Bx;mm_SX>+r5ql9u0_G>jD^4P& zB6}9XSh%_2qmzy^c{J=^Nfq=g3y(}1(#L1*p?bFN{1>n06&9~#R0<#0-9tlyD3FUi z{b(2=oE?O{>cL4Jj^erawBvBras00z{}l|IgU&O3{#oUJ^MVIIgf2-zd(s<72+?Hu zvqDP|}MuCmV?*Wf=ocDe`Fb9od{A}*D(m@G?%y+}?cBC`;Kl>Pi&E0;z>yyh)cRS7*IMYRIz{> zGr$HGivZjX_h}Ol#dbq%0;KdN61~e*g12ph!$l<{Ork5+ojV+y5ZMx&)vqH9uy{e@ zB@crYZlG7Z#n__@qiDDqtOhKZxiQ;Fd$1Xq9dKVT*9xc#KLytGib6FI-E4JZa&be3N~G*7cg_gz_z?l{I^gl6uEK~xReT} zgCyWhO2dm%!Qd$MV?mlyp@}Pl&g{k|pB_Cn^3X_XB$66Qt%O36lc6|wb7fk7@CZnS zc+fHXyotM*tOPz>1R6~TPmCNJ**lUPeHFI~Xfbljq5mM3n;#eODlFN=*2Q1lSbqyc z0MmC_VL%m2+$-cB1|iKM$T4f3D(Fd+x_$svRBy;Z$V?t2XQZk+qU+8 z9+%7okQ`_tmE4W9v{=E6ODFklvkH)a%53!DM{xwSt2p}odI@1)Sf z0(Q+!GCl@x5B)2+vViT2Pzch0BeMejNMtnj8V5LZ(S;dyk(Py*ppd^a8YAd+#0|bI zTu25+wz*;b+B>Iru(~Et6b01z-LQ2EDQP;gKkdFPsDY*Fo5_oaltGsj{MgX1cgE@i{XkVLM+dJQS5ea zV@P)K3pMEIEANm{Sx2JbhoaF_68qNvW;uB(;H->eF7B2thyytzF7_v8PXnIk#Lp7;y$odb7_XVK zs5mfsBCMG2EROcW3fdRuOOHkg8Y&=FN&b|An5xnDJ4AAY0+2hio`m0P_5A*QeD(4FJ!)O zOTgkSfKQ*zvCBjT!@J;Yh(5&pKq5mQ3v7;KBd`M*^|e^*-;Rfj<<0~e%bvg($^v7% zL2Q|ip7-0WJa5o$ji&(SvQ)un;;LKRxb}dN0YA)E!Ffr7_Z$7am$1wLa0@Fq@wk-C zB@;4E5U8+M=gxgbngJP-ms+-c@QB_0_9&r%2K95qXfStiJW`VI4V)UR%?TBUW`B=B2%m#@#^ZMegLh(Pj@&0nYs8&J7rU^4 zPF4pFfrAeP^s+V&;Zd-BVuh4rb>*;Lf0yfSN3*noa`?p|J>@u4I@F}VDI`O3s)_Dt zhW8hX8sZIO1nI+kk?9|dhwu%K%T8zB^z4I_p^MFoZUih>-@;Kwq#;nFTj5E~YRzOsJ2Ih1$48 zorK^(=uTKA!l99t=D?F5-}7*?kyd+DM8IN>9yF?O+CQJ+Tn>sO@v( zg{P)Q()sCPb}}>^+BHUup(nZCC_Ccmhl%!MZVQCSJAKZPBowFi%Gg`}QO5on(G={I zXq(WD8$Ng*u=0qDJgPszt=`lKfh<#G?%~$FN}sTno(#WatF{%t2`>}d^q!OF{33n} z(z6qCgV02m-hq3Dm3IrQm;hIbwEM+GxF5<1waO^V-uKqKw`(Aj3>Xx~Q;580uu%v4 zmj~psWIVD|gWO@oL;DNJ@vY$`-(m~FMd_}=<8Rw8>LM{tl(3NKhXUKSi!$kM8`UCg zbb97WPZfz?X=`pDNCruG&BI@B?%Y0jVo>u$#v^cz4elTTpt)?X#XuYQ?J0bL7O;fE zDh(-<+NmwKga>@w;EXgMkdDuuvC<8~tTxY+pm>OuLa8P}OPsCTnkU8cfq7u0P zNU&>{eDPia+y}y+70zwYA}PF`tQ;afQ)@yLpe+krA)fMbOJGl6j_lK$S~jh}?a;o) zwhr_ses_=_Rw4&1EF(%2nn+SXNZt;%ilK-OdC`@PX^?!+?87YlBw5r0J4SZk0~j2h z$w#B7qmv^#_JBTFoJQ5YNJhS7a1r5JbYw~_OLt-;8hV@rW>`C#`|wLi@|?f}QJtHP zFGjAP9tlT+&Y4h}{Mk#yLY^)Lu_Z+#5+JgeN6USl^_em}`z1u^042%1_`B=UUr^fP z*3n+P?afld0BKUz+N7C+k}ZdvFN@2Zi4XgjXX|0oMX_}z`Ok{7e2XJHsS{m^SLCzko9uP1B| z>FiOvNoZ#oy|klLNw;F*PvLy?AUm$a&ctE05m>zp1r;TqFLAZ$WCap=yY!ePV zC5Lw;$F^@=Ub%?P&m~uj4A|mVmvPu3{u!6V$xCVwDBqS8N=+mytfqs|aWt9iv6l(Y zj%gW@J?hnn!3G~Wx(n-DgDgmBIJYexxD}v_J-ZY)4~BQ0vO((@A-*E6#&(YuN)i|p zGpIdd+u)0+%n@X1qeciZ8G^0X`#$bgzLG;@v<*&6JSykdyArv=LKqj*TLCZg#RKzK z%J*WkX-WsokL=q6668yEEIR`4P|$hFYI3Z*?McFnmJnW&`#e`F$x#HCkCsX$E9HCw zgGK9Ii-cxE8jqYjdB5C|H8(yamopc3O-g?wNLR`4u@HjC5HvaKnk3n>3E_ajE@Eja z6t+k?5emsBbsra)0Sgs=JE^wriKUOGiYj&w{w({f%$S za?Oy$fE>yh<8DJxczzuS&MbgqXiY{|OKv>P+48gA>*YPo3ds;hy!#20KS&NgZn5wG z=nQ6N2G{2dWZ15ET9Qh(Qb~&gC9#`O=85vn4P9UhK^Iv6+id}^6^!M}6f;>8v3C$+S4uEpjBe#qw8O4M;IFcXE$F7;YTHTtv zmASUE(=2eS2>iYUI~=}Kc$5JPHb8Z*$b=QX;}|Iz zyI;bE7mMjrTn}zEN$2i$Q=xk?Y5;rv)qF5GA52FcjHLPXi|-7hs3e&hPSs)~8$pwY zv_nzBTtcjhbtn-FtE8KJ*+F7Qub`itcarXd_+k?EVm^S8jyB-L$~h$;V!-(2?*jVG znGBMPLoNK@7xOn084izcBAk&zrxWDxY?eio@OZIIJ`;yt zj}YEDm(6`L@>C@9)R#YyD-ET@e{1L~_&pIQlGVDOh=tdw+wjkRk-ASUsulI1>M8Mu zB2&pi@zA`>vPEvj8h0hqbvX$W=P?ccbKL*w?xwc6a*o>w8cKFIJ+%SER{iR zE)t3oQi)IzsB!?MINn+u<_NF>8q*7y>>m`SeSWS0q!lGQI53Zp>pI0d+(X5={bbXg zEASfbM3o_hJilc;l#8Xgd|{RdoxEyxu0&Qpo;FtmZj0y)F@Jt;hK)x!gix_OoS!QY z7{x6}H_IVY!&gBPwt#)<1%w7_dHe{D=>p4;{k*V$9t9=cVZnwt;M;&m*4=jma*TI{ z7?py2N{OF^nc4k?+5N?XGh_i{I1ZNQW(>K)ZA>^KymE>F{x}?o9vT%AcBSQg`MG@u z=Jtbd<`231<`4`nyh`jKpNpUd?Lj&*OFTuw%_BKRQh~@sbm&AW$DCIqwLwu2nx+}| z5n9C5UR({Jg~GHVZ4Lz!*z`EV?!^8H9ta7CcvxgWL5G0haW}y(z{&vGz8K*1k{I!+ zQQ;_nLTZe6VFt}(uq8m?BM(5HYs$QqUwMuQZ#jXDacNj*Lzt;zY42nU*8cyapIXU0SB->5O)G}M{tMqk|Z?2 z2oR8$Lc$_2rw8H6@##>U2sk*EG9r-EBCZs5BT-oGE95fil6eguur0%9U}ZHNVQ{dU zlFso0+Peym@Wq0gutg{3?d?gRYjZ(?_no4%6l{8F);)W^ANGf zV9Iv!P#|}R#Mk&-1j5naC)GM~SQSH-2DGxUkTA09yGeBfZ%|}GV5&8uQ;Fo=Vv<2- z$}k>AJ`a;+|KB(Ck)g*4dTTmRE2B?$qobp$5ThVd}M!Usi-{xd=&ufhpr1i zn`m|_RftyL+cph2=ON^k%n!rwZdZ9w0{`IE1+2roTo)j^EIVcf3eOT4etfdX)u zZV?g@W=@Mj(iOt%5c+pKiVnH^j|J!nXQU(e2tf#KXNQ7F^#Z6wh~{~bWLzg^%lw;{ zAhx{PnE%gA3a$~)B7<_olg|8uE&)XGXq=Fas=a~ItbdC@H^3M^P=$XN>oo?C8DKGn zfXe(JG-m>x$&ia34)SqwaYiEIOUQZ?;UTlC@Btt zeTY3jws2U?uzA7Oa9cdsC}>zY4)i$ajRujT(+#@;nM+LLWijeRRMssHL9A=ukPZie zHxLgIM;oXEn#^*sg)KyO1iEQy&jRzJl%4KYl6j zM?zp&{*hn@$VH0m)LFL0Kpp^QUVx5(C44kP>a?i)SvT5WUhYTt|E?*Mh^@cO38CGM zoO*)(iM_zKp)#`Sn|Pit=p0BqL4D;=8RS|xfI8eiEhciMGQ>RFqfp*AH+u}@009;* zpkel3iql2@y>CQ*M)ntGqM2-BZi<|6RqC{+b42?}h0+;LJkD2bjYe|FD#b*Cf@BB~M#=LCh3ht%PDL<=r9tD!oKrEAz)0wy?$Q_1@iQ#-ICKpBHsdOAYSSqSU z^J(TP#tX($&MgD;%gvr4X46fNN`v|-i4yrPfQTU<7+Tb z#~~o%8N4j98%$p$s@=B}|TG(sHYlc}e-IlRo(Zd`aGL#FSW4sZh(|CwK z+i1Q1nFediSWTe=708S^8i3u-gls3-LJX9!M&<Bl#ViW0^;%o z_o+*UC^ZZwWbPg&(i+Cl z2oMks2a*{=vEgXL;vn=bus-xC(rgK&h>;|$b0iSji2xk5pT>MSN377phlw?KIQ>;f z256C3B9og6pELozPll&*BuvA18M5grG4;XKRs}~=v0-;oK>nmV97~PR6u3h`4iTM> zi2oT`S`QUSV=oLIx$Fy9;2^3GAI8CRDSHG7CP>4>acTB#5E(;dNIZUq7KmcSwlD#> z864RiW*G=*Hb7Kt!|bY&@sr_01R(&`Zg<&uvk#Y$Q)bCe{(O4uM2mVnb0RaYiw1Pw z&wv0)u$K}IP4F(Aj-H67b>V$aiB81Wk%WWq%M^>5_i^P3`6{DiUiE%4cYB58g?$gp z+uB19{prx>$>WMPN@QeKgNOd+eq2?8bHQwu1HLMcV+eV(y!vJWEN|TVdG3~b3&wQM zv(hhqlxzI!ZR0`h! zUvx=dPy6ro`)XkZq$hSQS+U2Sb<4Ao;<9+4?Cupw_lIuJJ?HT8Q>SvzyXN@agC(w5B#K0ONvkWg3-#~z*iH`(WZOxLBWAJ5x{srf#q&M5|7_# z6DKIio=Le`-qyH-E*ZPl~GN)A0`;+0^et=q;Wr5#zBF z1$-EH4e27XBsLMy$uq$;Z34I?2qEo2Ujx0&b`+XTY-+jB+5ib7xMZVEgBYix9)y=G*_KjZme0G3Fr#?^sgIpcJFZ+E~2@=1Yf6 zygZgFl$4+|0I`7CjmUZaO5c2~i=dH}!8i`|CM+#bHu3+ht`P}B(C8Ci=I_9rn3zg9 zY7Qk8yDOW$|HzSNJNM{Tb)-B{;cg9cf@1Wbfa`-sKM2+`8yhw!LWt4{31CS4!q^cM z;CNvgaFHYMiT8R3ZvJqtboi)=AvuArL3FD;ka^fxXR}}`$(&CdDH1wIy$3adkhr(20MNp;nF^fKALhj1prB=`ORe@*ncCm7tFp+`r>mDA5 z8uRU+#Jd!o95Bk~7b8ID8R)F4u_Q9@H+a)d?ZP2u#)q`PDJ0mUw}*^Dwo8y4Hp{}1 zfKV{VjrXcK!ljmcpE0?Dt+frN&7Lp2bMFboorNhe+?ZN$+}+yQA;Z7J(Yvo;;j@!t zF<2Cl910tUJca}M8)OzpQ$nFk28T-|GcH1!P@YXgjDSGsapW+EGLrrMkTbmtA4H55 z1G`=g?m@7V*^?3R3`_z7pMaB`NmA3{-9!-xvxOpK$Hoz!iq;pIM`Zi+wnxG!M12@O zjKA$+Xt;+Yj>k?zYl&!0m_7!El2GyTm4^H>4ibV_`C=M`KovhE1m5PFS+S6ih&<;0 z`2~sn<6pz&Ty14?aGS0~G!}al;R3Nmv23SQ*&XssvxclGlL!%!@G7K&;pn)d;=x>G z97~>PCV(^CcIkn7~L8e&OHoL2~aqRG3^jMyH)*`WB4RW7kU z%=(q2tN<#p1UCT71ct)kO3baw-mlDH`O-k!pl`#(Zx*cFApw36xg+gExrGJL8;xDb zMm@Awz*CV$%MX+s()swXUg1r@QBs~KY10dsXN&KX7Jn~SQ#L-BSRbIscnJ57a$0IpR3!dd{ z=Gp?nSpcycxLyR%l9K+vee)%czV$}%rYpuJCy}#yJiPth?$E3MS6{pVF-(%Y0mLve z0NLFPlf>fXXA`8cE(ii_Xa_0)i~l!cb(W1b$r@odp_4HVlb4;EjJL9u}v`$M9wX>VQ8#9}7y#$$2^!>&19?2&m z;W|yCWjukNB^74jYyZA6m5+Ki%x=qiFw9?%xBQR#zBPxmzi>s70Pl3L^ei86dZRNY z8jP`w1&}}67yQV^3LXhMVpR*#?Fh}v_<#LBji148sA?>9{s-glB3&27tOT^f5(S)X z{J5AA0l|QRXZebq`LXo&k;KHl{X5Xt-E$aq8A|q`J}j05&-yKmWMX7{X7tKjbjSX^ zsTXXAEY-(E7q^}JgQB~!Yf50(l<}Ro{F}US_LKueQOeJX3ff8zJXm&8wsr|v845fi zT)@OmX?XN>>7;R~Hiu0ewVS@3&VMqO6Pr1kOVxv;;)f~zNVl7ej`juib-r1&^vHTw zdaxAGI6f0;Y^+%C!Pa3S^|9!_yAXp!ZBs=o<&QMLwz}aFmE(eLiri109K1|Qh5yE*+-Am&r z!8l)X{o#c@w>V)W2gX>_#9Nxcvg4OWy1j( zy_3cx+rSS2D8SEcqUEqMma<1?8~WOsI|o>EAi=L9o0b>@jvy}Mn-L9uTx{1sf8rkE zo%`@c9l@=+_-J%nKKAO^WIpFsti9z*d&P9U@hQ zCNbU}B3CNNmMDL}<+F++cqaUnlTJG?Mynrrj36aW2Mzqaz@Nu}Kn=?P?T~RHbpjl* zi}~ie+)ON%ami@&PPFtQ{4sqpIo@_bBOR4^Sqrm9pa&qAS+H``BC{YJ5Z^)Ltbshh zQyGhW{w%TyA)q)VB$Sj9nTgC`L4cAdOutN~4&%rn>G8aRnWA-*ef;D}!>6Cb_`uNw z0mfnxBPt>Q2i;FaGr?)u%hSP36p!JJ3hu`{@v>n4A#@}lFysz;I7}I$_*vKjNWuuY zhMokTZiCMo${~5RYtzKdM<8W?EjBiuk%GA&mN)|r8R$%2(Oh<0CbKPzmEpt$+m2QO zkMY{i*q>8kESzbdMn{4y#n=>7h;dQ?LL7%5TIH=9T8E=}D1ztFVY%}RoVNgwN~7fk zW@1`*NJb3F3G{%~u8B?2A*_eokl8JGwptyAH|qYgWr{w&e zQ~7M5z)ptCbF=YicDA5MAuJ{<&cRd)liox$9{&=P7j&M&fJP!PK84=U6%B|PTOFI; zI~~9K5ke2nfoDw>>m_|tJ6C!*uMb{EP>+7k0P;Jd{# zzKl1^O>o|?VcqdjW&mU>h_CN6B#SmsLdMSjYWQ*NhmAZL027)KejR8ba>`OtGuF_{ zNTNJ?%onQBXoZ_>H7-R5VK?CE;Y3W3gzWkN0S~j}aE{1_P7|;p7mY3o8r|eW46a0k zcEqung^8XMh;7Rux;CkW-?ZUva`P6sYvE>a%VP%p5btRzw7XiUI4cf>@79i)5dsz&x!#((p|xCF?e*GM@7*g` z)EbS}o~70rnu*rXGPI`rHi=wU!waKNtiCk2WcF__~i;Pga|Nd4o`;z>Y$oehtz^PjMu}} z(A?JoWW~DfxVm1QP&cR>)lKR->Spy^b&GnQI;ozoZdJFb7pU9S9qNVZPP`=U!cXFE zb&tANy%>Lqm#F*IY4w15sj8|)wWO9+O`XAqVpY}gqd2P?YE3m&OSRQG)lpqMEBflZ zx}Ywqb@hFW>XYhI>L=Atsh?IqqkdNXocek7 zY4sWP3+fluXVovMUsk`OKBsI>?()NiZbQBSDfRllcxU;Tml zL-j}MkJT5|lj={@pQ=Aof3E&Q{iXVn`YZKi_1Efe)ZePVQ-81iLH(opiuxyJ`JdIl zsDD-erv6?1hx$M2Kh;;&*VNbb5TF;m3)cIXtpmqNj}6xwBfdd`dZ^1G%kjN>pPtkE^#OfQ z&+9`ZhCNKKysPy!`dWQdAJfrC+4)*7xXp^^5g=`X%~)eOf=DU#hEmQ7`FbUDIduieA-q{h&Up8+uJQ zbxXJPIo;7+-P3)2USH4`^}2pYe}jIRez|^y{zm;x`kVDD^~3rR{VM%x{TlsR{Vn=+ z`djt4>DTLT*Kg2oguZx_{!aZ}`px8Ce2ado{%-v}`fd7q_1pD3^gH#t^t<)<>G$aG z*YDNu)9=?G&>z%4pg*KPtbb7dkp5x)5&cp9nEny{qx#46$MnbbC-jf&pU|JwpVB|6 ze@g$f{u%wV`seh|>rd;?=wHyks6VTJN&m9`75zE=tNL;MdHrkp*Y$7c-_&2wzoma$ z|Bik_|E~T${rmb4^dIU!(toVKsGrn-qW@I?nf`PA7y2*tm-JuhFYCY7f203a|DFDO z{SW#d^;h&i>8JER54qK)rB=V$8?G+(>gTJyT4k|QtDbGQ>djs^P+eYj0jaC?^R;NT z(`j9(EVg>98CzVcca|Ep%5v*Mb8AbZcBYrza!secvf4{+Y3jGVC(8X^QErUctIQo~ zTkft^8x8ZGEe&$D_hNg6xhJ{#p217H7pm>BJ=1T8s=Z!yX*FJbsNbRgweziZH)txc zMZUe(sdRg-cF2~)i(mj=-b1>6U1Wn1-8})9F%WCI4t)`iSK}E(t;h!;MAN6bfw!@-nb7`lwR;wÐRX8 ztE^Qo*4OF}ZPe>cQ%`#5J2ghXQ%iWu>rB1Ts4eHb8VG)+Rt=1`{93(PX?I#Hyr4U{!c1A-zoN-AAikP> zd~KN*+oyD!t(L3^zuI3~;t_+#_M6r7)q114*r*L2oc3Q+|p4v_^e!rdPIS zHsGav7|&b2#GXS;6dvXjz*zCHwEeO( zjn>MhJBOd{{N>-#Z+83bcB{iwChQ%I0Bft}-A}DoZ}tI9GIGhmC8lvuwUd*!XJyIT z<&m`w4K_q&alInHG27Ix_Pc($Q|t7bIomFfquS|J&UC74HLpWnTfx7Am$3!?jumY) zw=JDDcX?aD>&!;G_h$2aZ$)%_wRUBZ8#mX%nl10c%z5DR=F_qeqy9Tv?Vu_0vhGGc)Yj;dcUPy@gS1#0>Ao>vB19FQQs9{${(3IWU#id4nxfZQmY*(Ah zvhhL`Sz77Z#h#!w7M9)XT;exGYwI3zSq@MP17??HMq+?`7EQfb?W{*kSpad=l=#AW zHEfFQa1tBG)u)kwMSR%qaLca9cd5Bp_|~? zgN)z@+!eAl=S-uz66CPP-B1Z;x?NKOUCM7l4%}wJaMG(a>JSZ`b^m0mU2DoS&85x? zPjgm4Th2-y+Gz!34HW>K>(S8dMOOse0p;s*&J>xkT13icYQ3e^u#_M#x~7Tcwo)rf zQ@anT(W#y5v!yKhUVfT(D8*haf7$iRt!Axod8=TdZI?B#v?{$;1v;d;QtM7!cDh-+ z;GaxidNL&qY)J^RYP;Q7Pi)lNt+x3hb5`0{`-^5=LAZXc(US+riMqgQ!AD*l9QGhH zAF|cpN~5*NXPS=6B4=wb$YvWvZL7?B+!nh`#?q?0(y1;kvf24!`3$|IRCvzUUW;;u5eEKFw=@+!63>eL?s z)Pi+7EaD)a)anK6&BfNm5Zew6@6?-TT9ny)J(r(0Ul*%)yMX3pnUj>`|`<$q}-}qJ<;&e7(262x%t6IPx^@mc8&!Eb+ZHpkLCfL*ewfIpOK1Jh z+;V~k`rX~!KI%QuZ!@nkPr3B*_6(2lzh}!We(%{Fy?&SO$dCiGT@Tb`8p5tE_8UxR zzt=ibznI!+G2ndD6(0Q=8C;Ptuv36^2Rg`+8YclGfWBVblv*9YoXn5~R~{v7uj+Mb zHG85D5;NCD8?ZE$?kbSW6^fhv7lsV28uD4_QHx1JYt@xztruB?RAlp1mmMiZ*Q#xA z@8qQDFI}%KzPT+xB`iFf@T7sYrFLYkw!;1ep1DFOgM=JePC)}Gqigjg?~q<7KEiLU?pk_2HLw6 zg$#-|TNSV9Ku^~)au9a2(qncQ-07iia; zasGp=1dsOCL$(s5f9n;?emU(%e zPzl~7umC}6(wH?Xr&Fml&)1Pp$R6c!ET)d|p~6G*2&pEzwVvSZZbgWtuwM%rpc3k? z)@zMrw+r12Ef6-p?21rVw)A?i+e9dof?I?xVzJb~s7tkEx7C;V6IPs~ccZYea>$s2JcHF+~|QTUxC)&cs9_LZ=GCttw+_D*yvz8BPD(5<;u)pd#{yly@P$=WWei)~f^D)+%eYHAt7(rY8U1 z+IdFUufW?jmebxHc)08P|uV4wNDp?HC^j< zK6=UTFblg=h4k^?3pvXM=JVA~ zU8D@$++3=7Y?LFFAjD0h76AB#n5dOnFAUvzRuEVO6;TbQwOvcl4McO0VTce`tEb>Kz|W&Z9nYa!Rs$V1tU-k_g2BR zEb?e?m5oOyI^7KP?b>2j@R^6IIO(^fY!PbHE?OQSr~2(+kM+|H_F&dl6Fr7RctOLj zY}AAdN)FB$f+IGl_SMd>n6Tv5#!w%EwXvM*H!mZH zIVCusA3>YZ4LM8FpS_B?C&eB!jIaN7emzXV@RO1AYd-l&$rC$7DYEwso}~!Z6naBi z0eK=b)#umr`A+IQQ;x(BVj7aJ(+%0Wa{=zv^`ub23sqRG=!MmKuLe9|Z(LYqUshx@ xMC4Gp085aebk>}%upE&ZEN^P*H_tYiyWGY#OuoI!eW=x16L4jI>GIIf{{^7BhdclP literal 90728 zcmeFa3z!?#c{hH}NSd2wMxz-?v(jp{D`_R|&AZ;Kw7#tIVq@?c+ZY4p;stZF0RvvH z#vwqk!#%_Vve1wwgwg~jBq21VBseK;5|Yr=Bq<@}`my9jq-~lIH_b;A_#w*ymdBcf)1LX}9 z8}R)Od_U`)i?{9C`UT#~nD%ML(m&q5^P2OG<9~l1V_i2eo~rCPZ`-+B?tS1y?E4}x zvI85W(*rH|d=O~gaq$(qkGbacS8%>r#+0w@y!4!HFM9XB%2@F}#sWXRc-!t>0hhl6 zzc*q3woA5MeBRx!er7%6tDeBN-|o8f@+&^H{)*!mKju-!njdFqJMY1scz^x%*I(nk zv}NHRf5qQ|t>-^{Qu55mvVUe_am2NSM{l4#iQ?Gb^IpCPUsWd2R{YVqU*T6W&;LeS zb^K%Ob^p8nJH9VwV@z6g%(~6Y+PUqDOIV5p7yQ2PGv9f^_H8)7IFe&O`juk~XL5aO zkazR_EQD{*vTx$Ot4{YbiyhCK)ZD zSHD`*YU^iD{S|*5)=c%R>TT6`SHD{QS}k1LD7N8*|+$E{81P^LENnl8`WSY z?4Stl#9_^>g=JVP+sQ6wm#|COW$bd6V|muj3arRFSug8@ZPw2Q*dQBbBdp9SYz14! zj$$j>I6IoHX2-C#Y#m$APGINZ&bG51>}qxmdmFos-N^Q_ce0z<&FmI-8@rv|!QRF0 zWbbBovG=fh*n8QZu>08u*n{lP*oWCi*u(4q`zZSudxU+QeUg2O{RR6p`%CsX`z!Vt zSiPTPpJ#u~o??H)zQ7K_n)?U#ZT3C(efGcD410cQTpTp1N+xdn3B7QNun_tRz@hN^8znt&kZ{yeUxAW`xJNV7~ z7Je(go!`m#^LzMv`82sxA_bFJN*0n2mFWpMLxq{=0E3EUgNX;m;6`U;VzH!1lz@? z*bQtsdk4FMKgO7|0Q3`zRqvr@8skB zeSCtS#xCUli>=|E>;d)yZ{bV$>1+`{j;~}(*iyEE{}ul(e~y2X{{#CHzk*-QN7=Rf zANfD=U+~ZI&-1_LPw~Iuf6EWDC_9I*Vvq8lvOVl&_Al%=?4Q{w>|}Nli?g4zFS0MQ z3)pUc4SRxJ#S-j0?7Oh6+gO%2vl#y#AK^OtE&Jc_34X_34}T zu_1m8@8@0oe0Das_$B;3>~6N7-_LJjf6xAoeU1Nw|Ck-icf&_HmwkihcowtsXy&r# z*fcwiZR01h)7e?<*K7+rgFVJg{xCa}U%-CFUSUu30{amAJr8i1oyJaOldOl|!0u&R z`Chh>b;IvCo)7XbvA>1PB;Vx!^*@6c;9Viuz<*6LQ-EY}b(;W>fSxVDcY>ZH02#@u z=L)cAK+hB4TR_hj;P7v&+XeV)P&x+ScY$6YK>lg1lYGg-z&f$1f3Ru4rEp8 zZ-Al`Rq79b^nUeE1jrt!67K-g`_=mepfy>Q+6_Q|vMRL?fF@l9|7omR{f*^v_Gp7zW~?)tokVd zSOl#4m;h`8R;B&`U?s3B^)Uc@fmQ!f0G0!*(l`cSL$K;!3Ba0Q)h7gCSFq}51YlvX zDvdJ$wg#&{DFCa3RSycl{$SP53BVFz)z1sCqd@;!0M-esJ|zGEG0IV!l z{k8z?EmnO&0G1c4en$W{7^{9)0M;0*eop{)8LNI@02Ugn{y+e>8ms;*F04zCHoe_Xd$Eq(0z`A4A9|^$DW7QuEz~W=op9sMAW7VGuzzSs5p9#PoWYw1i zU>UON&jny3vT9WT)*`Fc1YkF^>Z|}PNLKwv0oanP`U?SAm8|+t0;{ha`#G5dP~*t)FxngFa`R()Ln_Ag9b0a(JU#sy##vzjCT>zLI7 z0wpq;(pvKfp0a)Oy zW(mL+XSIj`ta4Vf1z?}ET2ug*I;%MX3_Py60-Vkr6X0}QT!7PY2?0*OdjgzjlK{oM zYe@l4G$p`^HVg0!Xj*{c@U<2J-VRE?0lWxGzX7}i+9p7ra4jov%0JI>$%b;{#fN}#hIxoPNf_4b-WuTn`dsnI?zP|{CH4m z6TsJlQkww20hHPV@Do7$1^6aVY9GKigANMtlR$?A_{pHd0{j%v5dnS%DBTahw}R6B z0DKa3RDhoexN9|U2$cE^fUnAG z)Mo(MmNn`#fd4rt^%=lF0!n=b@Q;F?E5JVvO8p1$PlBE=z&`~_eF^Z-g6TiJm9q1(j{O>`jzX5&-l=>Us&w^5a1N`ft)ZYOA z1}OD6z`q50g#iCH=#>Ke1<0D6r8{~;)i8-TwEO5+CLGoUnX z0RA#4jT?af9P~N?UIo2gfY(585THC??HvOAm!LNa@Lz%M72po&J^}86zEgn5L2nX3 z#DLXkECPrfu-YvGh$65WjYWXbHE$C@WP#Og7eI`G)$R~r#HV)&Anw3wG+qHjAXx3) z0*FPh+I|5%;F##Z!$ZEe7V6TDxN`Q0FR|L2O`fC9m0zE9iRZ!{! z0C7lG`;7o1lB`C32C!E_sT}~Kl&nVW02tjj{RSX%$!c^AfEXsLQJVm&RH*%40C7!L zqxJ!aaIzZR1Hf=y+^!FfeP+>a0$ywu?Is`+%4P!sh>5aU`VKJk=Pd4r!1JKE9)Y)k z(zyV{PT4H3M?e&n&C>M%#8cTUcuhcLmCc3)5MyPth5-92s40NBE1R_h*e1}30N)2{ z3m_`XW-)&V>}Q~k0PhBM1^AaiF%Afv&W|xoU=yGia|A?m*{mnPo&;?YK$MrwQo8{* z1WNY<@MA!m1$aLw@d)5upe+K33$xjb0J1Y|wpD=BJ+%q&OF**%h##}roB-LGv&3J3 z?FVfa;P-y zMFNOhv)Nt&M6lUxp8)#?=wbm*?WOw%5Z`9A0|LlIvDrZZ#Jt%o@eV-ro6QajAP&xE zM+6WNXR~Di#KzfdMSyJs9Th;loXsu~K;)dwE)`&Bfi4q3G@Z>Z7eHK{&C*x|5MgJt zD+JhMphpRC8fz;B5PxU0;{xnVP--`Tm^_BzmyI65#=o90kum#R(({rri0t=kroNmS3O0FaMSNOYM4lv3*(l zCGGp$Ki&Rvffa5jJX(0Z*j^kj?kL_;e5Cl(l2mFhZ7S_6J=&pnJl64C$L~5jIyZLS z()nW7*$#!lvx|&HS1dZ%EA@`|ez^B5y}#*G``Y_9_C444 z%3^DA*W%raA75PUZ|c9T|91md3_LaP+rix6gM(igT0ivC@Z|6>M|O`qUDnGVDF3h$ ztE{cuGTJrz(CAl|oW117OVdlYEPY_Y<~aTbWz=-Em|5vhnHh&yGKL^s=M(AN|c$Y*o{$#jDn@ zx@y(?Rz0=qrPXY8X7#ewn^*5yec$RwS3ke{@G-_Q?Z-?W^S)zd*Q{Fe^<(X0hmM^- z_7lfGbKF_S?LO|lCeEGMH}U?7$0uG|XRRAr_t|yT&Fg{SE66u0OnCXv2;TPi&}eEN(n`67G>cAWIoNxwUJ{N(9VtW(ZD<GGj2NL@iV@^b!h9xt&@{Ylh;i?cxKy~??3aUvo@V|cw5i5 zbGJRQ?Wbp7b@tcK={e`7b3Sp-H_!Rax$DopOkcY1 z(&u;SyEg86->w&@*wnz(RhP+^bzFA#We;BV^kuJH-f{WoFMs8Vbyw`Z;`3Ktb>#zB zwOw__RZm~_i>o`X{=n7W-JRKe_U?yvSFahl=FV##yXJR$?%6YY?d)|AUU%raSFWGD z{-*06yJ75xlW(~5hL_)wdB^S>6F08A@r)aH-gxbecicF;w`cFpy^rtx-M*dsruY5$ zofqCD-!yX5EjNAkrr+GW?&dph{_f4M-m>$SM{jxM)^)e;y!E5EK7ZTd+t%Oi+VQcDvAjI5NU$KP%qC}JRVE5^2ERjB!HD%sXP`O9LR7l;mM`q z(3mukkzy8CSkZcMdq4n_?v911ESo%3)k)sk|< zvZ@9{x@JT}PAtFd*kyTlx^s0$$Lh}Pvbdf~>G5S}Hz|75(nI06rD{#LYca>vRLhA+ ztssuIPUqZe(v(mjsHk?#O2u?Nma=r$QiH*uqBTXhvpChhET3Q2o`N#;Ge3}#DMSNP zBap>TQBJ84&&Tt_ z`QgFg!8li@++Dle1N62F^(Ov)DOi31^k|%{vNCoo$CW%@rD43vgUTQ%aByN`tETCP zG+omUzi{xNGc&W--Coh&rQuc4pWWva{nPnx4y+B>Xk|~qDLiGcRQBRZsa)wWD;2Zj zpdfoe%u%bO!y3g0FbbN`Z?^)wG(mZzvWG_F_#+l$(38N|3%f&D`ufr``6|?ea z>)_a6CWE(DH`z1Lll0&2>DYN;(JYrEt%I%g9a@w1-;&UtM_kjR{8X84wp>!;UKxWh zW=Xx$8291}JL^p+@3ih{-cjx!} z745fzgIgZ)?ZD)uwf+lVSl_YL+G?(M>ifZem=Wj0=<&zVSNqI9v(;_At*_6_?AXyN zt_vIXS${t^0q*|Sf>8u zLBZVP8%*&pMQ`jokOvS?<$SJJLRHtuKth3t%@vpP!ML0l7%2^p6mxP5mlNLL7%ySw zVO-?z*9|YKHFu=B-DJC5R%HmNq|K9#?j*UZMLoj}J59#jv6hzDO@y zf-g`eUII76+a@Gk*&^%GI!Ov?p>jx-55^U=x;#R90Bq*H(g?KWK)EExV`Xn-c&M1qRVKsgL`$>ewimgk zad($<9Ez6I%N#F~j)#+7i`q0N7WJIS2EA#0INRj7sy?b|$FAQH2uggybtdi%X*+ex=Hd00({#*+h~s5xR0x@?gRc?DT==tv`4$p8M@oLf^GbOTgX>sl zm%BC?)(`68Tcl$9C8d@dTYBH+UrUch_XpQ+STiv?I)7do<6X$m!&fWI`FO0A=gT7) zd}ER~I8rW_ik8&EV-fBR#xMe*dP>D%1=q~S66JxBA&kdZ;;^Qx%3ms)>9s}zAw^bV z#THu$tK*5*#JCn#CKZDh!Y6v!Y;QJupA<|bWAS9N)6T?oNmC>)na%C7&O{<>MiPmL znN1`v4y$T-AMM`j^8j8K=3^JKadC{TUQ`ZLD7){VFY zTi{Kq9tg!Vc4su&;wBQEvG!(Dx+}n4C+3PntwoJyvZ0BDVC);N*V1K>XqI+6zYsE z%{MxO>OnO4UlkZU&Jm;F9G{h!h(VHZX~di#_li!oIVy<;dhvSyr-*gU@urk$Z7>mT zwu%@tjky4=aTs+r396C7&>u)ZNGiTHnlHr%F-V7C4pA3(^T`#hZMJDUxlC7=6DxF= z?3m*gD+g0eW;_&ZiY8mOta9vJ%x2T4)6TIi`yB0#puF`|YtN)2nWs&U ziob&u@yC^bVFdIib=%gTd?FAyVBH^(K4cc1QK#Zo9M^RnA)is#nnH}w!nmOZm0^`A zP%p!S)ra-y@yAEqRo1F$p41M@C!Z`I{@%pIH|sKl?8r69^1K~<>(9fiM`u}*k_mv4 zgpNb;@KS%VG+ZnX4~Y+W@8)1KY~y~&g(BX|{bShKgOtq8?X#RiI~7a)y;3)<7RXHh zlwLkTHgpCC69ndzBO-pb3Ac8I^CH(0BP^4DYx<`Cx!! zYQezsHjb=?iJ7#nr?qKapH923 z)urjW)@@c?cjByWS<~b$1Q8qd0r_P)RG6Jk{S!+WhP|FF-0HqvLKxXw{VW?^F zaz{oxv{X{dbg0djNx@Js#^J+;!m6W&14&KR!aS^*?fH}zHf0U-HskV4*bd{xWj8aF zXQrpH$^@VVv ztt}Cz!&1$L-5jUGxMfEy9986rX4`1S8UgHJd!8M(c_QhR(ve8IM!M$OTXu@jUdg@W)M`^Lz{tJ zj_5OrjVwoH;*IDtjCUT9op}rv87TOA>4|gWRA3SCGR%c7Up+!_B5CCJX zsBY6eHbwP-3dS0WW$I`$I1!ZOU^v_70RAVfC{rs8fxhPIkGtpq}n z;DWeYXnD;}n5kAP^T{pF*5#+4$ zJm*^DP9zd*k2V=bSTpPsIy&~BIu?-Sz*yOKZL1{|Oc}Obj0CwFhJ;5N3vEH1b`j#V zG02Q#Y1M%Xzey%L(uEG-tmUraXx3I`KmfSUfM4|_#Fmf2hM*0h?f zt!5@e?_8DwiWHpIv$1eEt@LRJv^-8Gg;XoR70s%jMGl&JObP@foES3~C*JQBz4|G= z#>sF#^a=dBIv+x)ici2NUqe*}BB1E`rX!RAOnDgg6nblL2%(z@0w5Ir^k5ueAcIq0 zeVY-pr3;7<*j9`ploD<;yp z>rgCxgcvv@VO;BQI>~0W5OS7M0UipJ13~WJxX)4I1x7!tR!A*LQ3RhgpU=~`Sk^5& zLy#+&7eQS;rc#-1@U-+gHS~=a-eWDXhHl@#$=vAZ zo#6<5`}xOD)Q@}U;U}Qz4a_=t=@>9bb z9;0dy-{w&KYg+z1X@UGgMi}x72ecwQ2)Q8)3eun@isM2A_`f2M;D0;DbWJm5HBZm| ztu=F2$~4==4}Id>3o+AdwnDHSicZnpku_7MwT`x#r_pCjTEESR1pf`S83r5O@$?DC z7NmnO-DI3{ib2h#He(OsQzOS9eyr~~x6iXU6YYZ=L9p?2{t4c=&tb%WX&>Y#20f+z ztJoFi5iv`$*~$KbbqY~Y2P{;<8J|M9&RJw6I_l<_Pem3*m_&#W_B9#P9++lP0j zdide#eGWbzz$xqf^b)H5DYG;HIp8H?l;o3hnE6NzQW9{C!)tQWp+F#%cJ-M5F%;6H zF!>j35g!q5)}=GMMGuyIx=n(fzvnikDOvhCSSQ&bb5c`~T?XQS=e zNXFmPl)=ybXKM3&jJk_f%DKgy(od)Ex1a)G(;(PDK?>3v2uLh=c}HHp(W*~JG7-y6 zMi2uCs0iD(w5I7>0G?7nRl>SqS|`4-h3%r{TtZ+$jU>&MRx6#*BNRozH!IR=S*D?f z6*cgQH{L@1))v~?1~eb)ja-pb5_vNPTHQCi$&LCgOL5f|3fOJ#0nvFzmc|A{x}h z*o4LP5@cl@cZo6PCsRen3=PR)hz%r4)3JOkmhYgq)h!(@c=b=zzhmPBG8fp`CRXYT z|4Tt+ayeFP`F<9KIbIuXgD0zEAW$}x_%8J3Lq4BgNIfX z2P7hunVz5n!#80AtqG)N_2Pm05ww5fHFs~A$K++Zs9HuwAL5;2J-#?EWrwqIz=K;k z7{}W|7-o^Swg}9#%$K?R+N%T-??;ivR{w3UJK>IN&L|{B!v;=3RwfYP=%L6a<&(*{ zf@mbyocKoU9HuaZA9V5*TngF*Y6Q~P&zF_e2>J=JvU~yx1Zi_`k(5$Re^*HLmIV- zOz)f}3)KkAxSsYdFO?wLhkXzFK`Yg4Hsb}|)(N==y|vGQ>Y8whdnfkoyT@v7ZtSrK z5*$C%?-P3~`@j#Or(Q-iF3IXSPAlH@Dr@y>YnA_gYvTjHHs%O8_#1c-NTif8=3rG0 z)37V$;UB}OC>2Y{OTg^(W2zJ|f=n9m#;`^46P=_qU*yOpc#$}40T^(t&9ZEVLNQa9 z!%dc^St-p>!p%|3(u&=!XEwP_W(V@}ysaQw5bpBw2xmj}!*6lo*yQ==HKEdAPQIhfaavv4Xfpx|3)xr?@V|lI zgQA&t9Lc@w_tN}(GnajC9>KWFF-70NS<=TCD5T3sH;cLHA?8Dt27E`DT?>p1-~3X1 z3xsS95=*5?mlpWUGr<8Q?h3;0WT>ZHGh7N7q!u`IaL)HN^7ETbli1H5<6dv{kSD!s{z2@vuI`rO|8*@3ZbcLDAnVnqG4`_YL zh<4zEfsU*`-9;PCP79=ihIQSJh(Z3ySkX*&1kO5ta$KOarN1BLA$M5=16JjU=w$Q@ z{R4i1h)>Q^pCmV%EH$*Y2mFe|MqP>O;jOef)!6SQud=3xlnMXc5u0{7 zUy3z1#|*h6Rf*@~2M=VuQAEGtzS{<5xU4E_NH=ty(&>t<8we5qM>g$p)1~HQYb+NV zMOLRNo84I9QO^$>iueP`H>%|k(j9`FY}&VyYbXzxc(XX$>p;RCDa6EiX?#-NYHqC5 zhYLmv4H#b;LS7CJ0yJ{Cy;nzCs!!Vo94c(>+p}xeXi*!V!p*1khY>1A9R0w=o@w{t z>50u61l_#dWl*KQD|EQf=gP3KT^5)q?S4JaLc`?OGk$#!4G(>8jL-kO()uuGOD#b~JQXL~_=0xlU-s6hA}SH+)^ICydkF9~ zfd&>=eKgF8~!!tGg^8bl__YKYFgS(xL=i&V2hTJ$Cpad zDotCHx6|4^2s_05NRfi*0`hetScGWATu`STEqWK?55a&gzol~xcfH|*$F!-Sgvg4v zSMOEg%}BHb192@94@{uSLz~oda#~;dPKlyWen`o~v50m2p{NqZ54@ndTy~JHg*-;R zR6U-7Q9{ypuvHRFB!>ES4nCA7+yw4Wo6+4vY3(YyTWwm?CdT)H30vIeI07*EzIkGw zHcpM&s^jHOOrWieaZ7TU{C^Acpb6u)gw<62^mGEGGkjYL1Cj#vUJI&Rke-71L}_A- zP+xkZbkA1}%dvYI4TTP&(7v+^1vQ1j0Sv-IQG@)}?2*u3X9l@)xSm>?%MOD7h~vY3 z)6x{%CpkW@jNqV(lh!~cX$N9UW3DQgS$Wb_UL09nw9bno^dn=i;TtkIeuT|W&LWAU z>NCB1hZ2@T6s0*dmt2MBxIvS5Anw-@&~!h%vS&eVbpBeTtKM2qy-72@Q5&Jn$o=8T zp^G5LdizsdV)L>L_z%uO(YYFLC;sB4?o70W@h-DxlCyse3tm&~@UScGg;#I5Ud(uv~Uu)5(t zb7xe7Lq_@^cRziw8MA$Df7DH!1ltrx(O8<-FJ%AZ{R%a&kJD=qiG;W69!f;aby3JtV{4NXmy=UITMVshZYp?=Z-~+Bf^!zNy;GsNf zmT64LhyqYR0FitYiBe2}{!u;(rUeXf}X21#$ENY(u6OKChIK zF4(?Xx&N>EnfZV2wO8k7weX{n7!oWMg;wMSbS;rw30sSczG5 z3awN`?yg9l2{8t#Yb9?{txi31KF1(vr*eXFb7jx3{u-hDlbCEJ3f`bt98s2qsvkBY z;&K#+^b{UgaQi5^up$9Ozr(U(n5h;$WSe1K;zlg%AjxJ}2(g2ql4S(V8zi^Oz?V%Z zphlf^5$S^XeBz;%OKvde1OiSl=teWHVJpdqha?niGNhg4aL|e(*(Yz5LawY*GAZiW zp;#;|gR6)eb;2ht#X|O)`l*rJxZG+*B1$A9z^~97__c%9L;iPhOv#ZGO`Zb{IE=Tl z0-FHST0sT+0Q|%MN;Z17>1ahqb)!LWEvpwC&9y`Lm@(b|dbZx87)8wuYKmr9$Iu5= zM?af$K0yq6axK<+osWFPb(pjMG|kb>^o!jv{qS!{OwPdpf8JtpV*L64FkX?b!?fD_(ezRCq7RW`4m!iKYFigFK;YtBls@i}>O3t~5%yJ3Cm?{<_GqKhL zU4)!7gzQl94Fc4(>H;*1gzcOFMNahy2^zSwAwrLDc1=w)-4Q{Wqpb$jy!a&_;)5;+ zqtRek3)yx^3orQm$B9`tD8wuY8Y?{3kh7a(MlNT>#Jgh{c29TIz#k;LZ5Z8jxg#!G zZvaF~UABN z@~IM*y68sxG40_nhq%Ugj)wG3$Y+(K1{Zc{#;U8hBV~N&rcUfDhv7H`BIf<#bl>Q3 z?GAUP?yiqG6Vs}k#%6lUhR%YLi}+GIXgfMdXq7T_ays${C4Dq1qXs~gH537Ok))EH zKi`{l9#&!gcS&%JbJBPJm2w&jsKTv_km3EW)^u0%bi`YVq?|mb8RL-U4`LfQ0q$v3lT?jv6$lRSo8fczzQlqC7k$O?&U zEXp9E^aln985LMYKoR!koX$?`cSwe>;CqGHa)4}wU={<;Fh+SG(xiB%HD^pAA#6q} zWPp*WkVnx$AZ8vp;E1l(}uVo*luD z(txVROzFyItiMp@mQJiz*obPPNW}mD2>dm9F8sw5?B`4I9E&?30caYFs0o-+EH~yo zLhzm_tA}7E#W(_9`+CebWn~3SUZ@1Y#5XF?x^=ryfwodidQLqKDWZ}N)p+Rj_@C*q zTt_w*%f^y1qesw3Lha7maAcAw>xl= zAYu+`M+`+pR!C0hC|5x-k?d>7%_xzi0!Zl8Fm!5AN2#MlIyC~FswmK@5z?uaT((f& z73*>miO?RDPe{0UAc-C2x;*ZHZM+n95#&2u01p}2HjHI*1(YM*2pXse@#8ld-%?|I zBMe(-v*NQAorUA65U%d{c!@GGmn@H55uUc6=!(gABdp;*LxD zYr=C7x1*B$Z5YRmd!$+Kk6fX{hD!@rN{1^Sx18fQ1NzQH+=r7jjZ0CW<8- zkk6IpxLlTfqXTTNE3A_RD+U^z{Wp|SgQv%V-K5|QEJ79jNKkgNu_X;wmk_D)J(9ii z3dX7{E>->%;$XrNa}CrR!52Z58hjBH56bfz%ICApBX#HPM)X5N-#K(DXB!z8rW;GB zD#@4*H&l;lQ7wZ4E+b<{g*tx3ldJ?1nFw@FxaX}xA?Sh1s2;mVW0 zb`H8o9^kJYYxZOL%~;_|dA_>RqG%5?pgF8^Maq+MSIUUu=bYj5BJ)2#f-&en(r2a3 zc*k*?j^+BMVMe?RJhWsibRNDtnf6SS<3nRqCQ9D^3Qt?{#obzyi625C7cN&v*y?WV z+m6F%rL0&8>!;u9@df0FDZW4vY`?kzku;b zkmD34r^D)A+J?7XZauH}tf-YBPiu15u4%AS9QpLIDq2K}ENBT=UAM?ZK9jl}if2bTQSI z?n@^Mi+kJMK+}z8M=%tPMIN8?l;3yA8yr12h2q zH6ku@sCa%yePwPOaWjABU=3rwMAkgI-Z$r|9>c@j6yN&zaH5k=6Z`4#ZLmh>8uvYdHpORS?sIBaGBj93vhoPw~pWkAd_Pyge$QMzjG+ zD-UUdCPN-$N>IiPM^La8_2XXr)@U%o)EEciNgoL9sjN!N*MzqIl(sb^)Qo@9-Z%^PD9*?B| zLOpranQdO5eQx5IGrdb};<^~ii3RucpS>e0hr+$Y^MBN>xYmK4C*SG*QTOpNb;>^0 z8$EN?T>CrWtB5{1>Ob2fWzl*Boy-6B4M_1L`r?TjE__hat;P++srwdmL{g=W2*i)* zh_mby5=U>pN)B8{p)b(~cxLI3d_9#$B>{tj+L+%cq>+3}C_&}+G{$yDJK8~uD%YiV89bA}YnHFlU(CBsW6)v%iHT8g}FeXfc67Mr5qwoE#rFvE|b<9XmSm=#osfW^Xthze0|@UtiP^qkzYWJxBoq-6$258|&b z-^BhHVYK5;WE#^UdIV_;0RoKFkp4jp+#lJ0|z;TwXt3{EoVZ{Pe{1%sb|e zo2x^k>OKrYWpwM-twTdY)02~1JBNlLQhdP;?LzfxI9@5?r^&+r&LQNF;J@Grl2s&5 zr4e6=4G+fWm`bcg#Lx3YCkReQ6O=En%ZD^%bsYJfRd@>X$&l1#Bz2GzL=D3bVqFST{?qr z&r``2T@{z!(%LE*Y=X~t$fl1`I!5j@2IUO~BH4dWOq{jgSq zvRK1NBu%*PSR}GHaRGGUx0Hj*R$ELUpAK7AotghrBT>I zQDle9RO^SH#Aw9yRgf`-!CoGr4_JQ+{Y56af?sJ53RReZgWkj_jc-#@~_+w2; zg_E)bO|Ib#YB**o{uu(Wu)~UdNsG~v2f{6CJRVh(MlvL8Y73mDa72yLDg0$_!WK#4 zP8)S4aoE*ZAvGAM}O~AzLPj4B_9%l+vMi z6jcCW%GAfhP03PTb@W98c_r1?A91ywCOu#9I;;by9(|`f5o=W)NAZ$MVW7t~5p&gD zJg_D6A$0rF#3E8I{!L>xABKe1;0rtS3`*Rsj786o#U6rO@dE1GJXoOA3bY%lJx;B# zAhKZvxZ5P|Y2jNh2nDuLsoR&GlpM{rX|aN8n_Z=ZoariP5!st^D3Px^Q^=g6zUE*e zmobp4QM=q|INIKwp{FXCSjbkFW0+?qJ$t`g=Yqe!=?#4Nr{a=fBc*o?40d{5t5zq{ z*2=s!T+UBtdd=o^xG8JuZGEZsrCvOpo3PT!LNN-JE08guAZxlVJGGt* zrt=ZkS>ec&aC_n5AAaL;>~|QSA*venB?Bri${Q2Y(~0yYhr{Q3=Nccs8%f$mzS-ag z+S}&$D*BDQ3oMWx#uJ1@Z_YWv*|fd5xn-zL;w@5BskbvcyxzIerdrg*Qxkn|0vSM* zh2>%iH|^}Ft!*$Eg-)wN9_Zv5!2vJPNG%~ECF(W@64Ux|vHo;_?6^KE)ND;$gRfUy zf$uBD$=`Smte^HMrwrTDd(%0Z#W{Ggagv$(N#1b3S@0C?AfJk2XH+?fNSZIN5&gzE zqV!?-E$^6aEN;KmE#trSX+ycJf6sVMkm6UKF&85TlL87mK6n@d+j0KtvB$VV9}c=r zk2U64H|#f#8m^Vd1Dr~|;b9t+YJ^~<+8KPrVoI7|{Uk?yahZb(CD~alQF$OOvBWW; zevFFDYl=ys-U^W@tQE&IJcft5PcNDsC9~+iUtzbKIfM7~;*8y>HnZ@&HH&u|T|5%v zy*=SPQsZ_@t|fZWsi{J0&;4Apl31^qjNFbToJL0nou;$H+;8ACJkx49tx4W(bV?D& zv84{2CYf=pwlv3ijA#pdb45(K^o%4w}1X(RbujHtu+) zP??-0#``tvGy5tN6aKY@A4`4MxZi*8Zr}Pg-{KDIF_;wW05P`2D(d-27g|Z9qcIa` z)X?`LR`%mX4%EaR|09aBCMK#hqIGQwMYR6t{$Q~H2M9_FfB(XDTG!(G){O51`eq}! zBls{pu8l8t%v?T+sPi`SZ20!so*o-91(eI<`Ept^pEu4nw?%t;P__{@QDIiMVW^JB zK(%EWHuF(9KSneR6<;z_dF7FV0v-yfKXN>Lr7)}vBTNj7$M;TfV@mwM6j>>bGv!WB z()w+TmA!V{X^~@iAYL@&;nwX0zQz$oazb7K|Nq&{R;nU)4%J6c#j@hGUSD;!zFq!j zB(Cw9xZuxa)GseW&HqaJe}Y^u9+4p3VKK**6&yN&+$RMw6>OScM@hc~`~w<0kMZATZw?;c+(C={GOOR2-GGYq;KVsF{uivG^q1rzGf-cW zCrN(QU|_g*vHLBByWX;I!@RTW$(Am_Xm1X$A{iG+tO@jr?WiyzR>Zh zp1}Rhe^+VNv0*`5ll6Xec+|rskyJqgg1UxR?4o`~6ew|EX@F0(l zh1Uy6!~P)o8TMDBERc3ok6WLbuthQYCww99R@|AXsi{5CNi)7!|NZu@d$F1jk0or} zHzK_=IA0TDl#5V0MBC#{(pI-gvA^9*QjYgi|W==sr;ctr(MiMiiyRg zSjG-0k)qR`EhdvvG}2OZi;*@jo3o3-MdqT$i=Gr|cb2JZPT{vnrDH=-NtTzZJ$+y!Gq{YlF zd$1CaoAqD=#}gh!Mk6F=WD0TM5hX;&Fs0|iU+A(&hxB4GwP;bQSky~!(KV}xRAx^P z@)$)+OVq!HRobE5pc)vUe(i&S|6nLk=QKf6c4#1Dh9%1340~Q>*eWyH0Y$ThEh>@$crYd zmd?mpN|fKQBcPt$qotfyHyR1+g;8iT1#&(=m|BbonvEjyL9>tu;zH5rclE zl3%;j%dyO9I^NRK)zT6-9VZ2)pT?4SzscI@+WCe(+0vQGbhbDyc1gKoP2J6Y>+*{h z%{7B$n_pi+vNDd$G6Ei0#ZdMjB{4qoFy-TZ@pnm?%Md3he*r^;yexV?B0V$<4}B{k zrP0V1L(uC7vQ0$}fs)H<+I^PoWb&<1+cXnk8h7#743nEpj*c`8a^(YVb91S=8A_uC zhfFp#=~`OJV{Cm&!y{{vvCFq;GS+Mwj&6D`R`q(Oj-_LEB0`6iz)AmqAAkWxwjad{ zsJ^`u&$d}k|ASs5c2M_pa8G2KDG)_DxP`E4@aQ!qH#89Q*G2h1l}mF!`M>bsn0%L} z9YSPgU*(;V-bkc%l}XFcQF0X1Re!@Ke?v--eP>J}%k=R_UaZ_=nv0OW<}s&$*Vw?o zq+u*VIUSF=?bxsaL2>*wWCyj~MwEb_VE2YrL%(8L`fe{AX%Spo(5NYGyi&Ow#i1yw z2dvilHlT*7QW?gBD+*ZQXco=F})2WA&~|WxH^+GFH69GB!k7TF-F% z`pC6|>z0(plulP0#}a|V+QjG-d6@h3t(ioJx#S$XCGGTXM$QfUf*aIE>Qf8zgZ!5c z*sf*2Qn1b+GGSqTP%B+?}X=iF#Sr4j!DA%~%`{kqPPJ8qdUHCIZM|T|M40voVL*yM1z8b322hqZJa8 zQ`)4nwc?JtDwaP+i<*(<6+EUX9Kn;X5~E>%t(uTkr z4V?flI_^>Xp)g@`;{)F5xk>tos(KN%6w5&9qwa23)m2mKs->zrwZmO`@FErKu<(>l z3;r&wumgI?!rG#cRf$AK@tlOp#8$LuzCAi(c4_1@7kKiB^p(knk0WH|Hv|C~XaxiT z$qDd~$SmJ=z3;X-6pR_u=v9uLQ0XO3v)}N%72ENv^-40Am zy8(ODJ}zZ;nO#T? zQ^l%JDCDo;3oP3N5;`RhOGU8-b_kBL;FE&jQ2&WP@qYYR#K9lKqu0Yu(89B)0(v;s z8pZRef|_pQaa=kcAZ4~TYbX?^l#^CiY9qI7>OljADyRm8J#OH6I#CVooEF3iYds{V zT`Qc{A_*ijOx3c2O@)|(W6X%9>fwliAY@`qK~a(7atqC|)b)yqr>v$UK_nXTwx;39 zrb`ovlo^p?o}oLfI`RXgG$enHguw?|Ym5I`l5BhmXcOVOxogV28vlHPg(tgqnODo! z)RcAg+_6^USkhtsGs1o1(lJ+=y9N1>IW=YOewmKnUFZETAS3*1mc=C%arBF8CyW!q zC;Zv*>x>h_n_geLHhjVfht{nNZ`wp-81;+27z-3rt;SCzbG=fLoY+uPlm`nvAx?`evARkZ^b$2&CPJFXzS==HWLj zHHKUZZmy7ZGzUoP(lZ0d_JcbLY2i1{*Sklw4T`D^P%aS7a<0bYpTt7g1#Lo^3p7K) zDMkP%b3fT-cliT-)@YSAgrnB_lW9mlHlm;?xS*8n`4BPx_EH zpvCo_QF&5xH`>bflVp31=6>$8t*d4G;-b9I=tXc=KlCL#Pzcz{X#ZpY;RSIWx<{c~ z@H|Za0=^)^jnbKEeIY)f54e53tsq`N4`jsYujYGXQY6Zr!*pg%d-A<4Qvur{)qxWW z{`DXh=fciA3i$$>m6W)l+;3xuNO9EMkq722p4jg8`?oAqc} zbyLO57LGVfr4zws@DDo*G=gvjO|yn>PWf46x<|+z@C#E7x&}hce}WRmL%9USLWKlM z33CdC1bzr0kFN;Gh#*D7NSg~Ce@sv4W+B=XShNTg!G*~GVeZYN8@uj1Pd+^0ZNPf~ z-UINk6C?E^4z}lB`HYMV9PxY%NvZWLLZ-VcG7)lH+s|_Be4OXR^8C ztnNRY9@j}{lJQKYJEzo1Ium!#nbXZoFVpfllXP5}llF=2`TXt$NS3xJJ#*#^7p!l& z@4kC~_qTk1-=6~uOeb4T%^tCMi`Cn$9XV3#_PiCZ*TLwnbbHR}xLGPSW1-;01&HF0jw!{@Lh;N5!JrqR=C7lpZ zaj(c)C*CO$Q3~xWtfRb=_%5s9+GgTG*ZnbcZYPb(b9LX7)~+3JP{ z(+ORB0YV>l1J>rv(Ay+s*b51-;ED`Ga&!`dwY3Afm!LL=_J`KJqygw$O7raDD-U7~R@HAUOHe09UU6JmzL{**jSYHdrY_q(WEpJ@wcgMV`%k2oLHIx@D4evuXI z0E`q0)rV9Ne=Xd2!wAO`$2E^f{7HGv(%m6|X%t&+*>&C`AkOg_AVU?dQ zOTe$(j_N@nm5#Hn8#-nY;)T8g-~|3_i1Pj*@G_?BROf_bHtNn`!oiR1l!0W%2>O_@9=Z_mfF;nk?8j@Z8W&=C~8)V`NuAnqkR3 z1JHV;m})xk3XT%$8)N zw#NN`k+Bk6h_7-$O(I)&j2e3COfTAw>H#ar{w&OxIiK`hHU5GqD38Y9O`os}IT>=< zbmHsdL8}$$qs#akuF3RMgZZ~gKY@E2_L6p8AMB*nk}ZA2QY_482RlhvRu0ZTlxR_l z5ct>`#kO=$|6pFw4 zh;vVBivmbP?5jS#q^l$? z4FyO70rDzcrl;`)lU9H9;_PeBo*c3vWyze;-2gW-wF`hk(l!4=I}+zJd1!C)Al{TB zrXGKX#1qxO`(wn0ZkVBzFt$FB46;fHXT5D!fK1Y6hB`08(bKEf=c#PN-#^e=-R;2s zIiU21&U3d}{{)ROfk$iT>5vGTA~lm2jCtjiXY`{4h;Wd^0-+Fr8_8YUkV+siV@dqD_^K2-44l}=C)$>ncu$6o zunkg@MQ$Gd+tHH0;co=1D$KKytWH$T@0-0gcg2aOO;KOamDJljZR4 z8D@&bTV&7=Mq1;T&qHzqS^o?f4%E`qjUY*Q%^1W z8yo&3ue>xl*T_Z}%tTr7Jb|RZMpr7iLS>;IVv#AMj2MZ%Y!66%7RPgcd40jZRilL7 zRh#}ke>|IA&Cld#R@)m-jW2tvr>dQ~i;>t4-!rjr<;+v9S|hzxy|DK5?czYju_6sH z%ErXlQ^Wye^`trwrY&YE6-xk>Dl?}~j{0hk^`r|FqHRek3=A2Jcfwng9Z(xvqNb^Y zHPKM?muxYhR0>~!m+RuS;^WnrLjs~O$b$)6WKX6@15K6uyqhMpW#uFL@L}M?mJNeC zMIHQ7iX3b7!MsoEx?>3WG!Sajwqeuu9N_16D__Zz_Mh}}#lzEu91vyq z65k7VuGbW&x{!=t|j^FXOUnmFsc}&BxLRN2_`1F=!+UXQle9J zmn?R1B}kv}NUuO&l{NG|Q2vF|vMf+@h7^0O&NWl1APtQ?#Nn{{yJGUCd8R6b;WTrl zob8g<=#ha22F9oNQU+10aU71R=6vPwEmDZ`aO>!_mrPbT1X-$-a-sSca1*Hv0O%y+ zB>kut=c57+!Bi?^CXA>6Ia@FElV;!@PA0vXqvN!NYaY#y5jz><6}<@&Epz=K2J2!7 zW8eDv29b_yZg}BXP3K)F%kgrcMX$b3t$MSKf)d1 z@CoXSkbGI<7SA3%xm2$&ojf|L!c@u#Q+7fIuU-=!c#(De40(EYi+3u*Ikg8AA<*2T zAH>hmmXWnAHS$*A@@M|?TEMI%E36I#%VhD9OjK+)Of5t>7_8F35`XfG6KfL_Ya8+8p*r1F(v>D~g5O>T-r(tB`dc{IjSmvwI5KVR&FqxnWJJF?$8HvqM z5pb8nEC*m?o6%yhG_%2YS99Dpra4pD+aC6-xY+6D~icpe@IKGwtf_r_0NbLe=6>WZ) zy2I?~vA{}3HIrkW(8W+|)10dM>^l?Fe)>NDzF;zJ58Q$~QzI7@?$Ld%EvhT6%_zEq zb~qWZZQ7rnsEn6-ac#ymA;1)^P1;l5Zx8&(61y7fz_%;EMn!Y$8rD@@!^j7%hgV^4 zca`SZU=5Y1ugZl|{tlp@8o?-W7KuxW6H2{S5u_e9jN1J}xpb?Q&i>^!w+lA75v{%A zc{iUP&x~iX(f{}*$N7?z|J>&qpKzQ{xcLu!;G^H`IN$5w+8y>`3mIeKagy9w(Rn2) zj2>HNL4}v8@@;UokNe{{2b;n8_i_`~LigL(fBSJ_@0;U~ce(NrzNM;;BiwdUk3p&p z4v&Y5BYLVm{Fm42uqdV5-R@?$t6!)CgW&CS`CaQA=qqWT9JR`vmda`(Q$?b_UgsR_ z!1}kp8Fjpl=XL7Q_V9UY_}mSzf4eh+BY*j-3s5e+a^)YZNHTy*@jLMI(l8%p#NR63 zLZ!Ef?so9?7HD05VAGSI>v9?0tLl-X=-8%?35vxry6=7b87GiY;(=2~#pW96H(cex z*V+|{-gD#UGYOe##pQapUhle>WIVW?FnoT*NqOc~pM=bA0M9}Yc>TN@I9}LMY?fYU zN57BO6{n@!r8MP%cwwEpir~bvg3hC!sExq71d{AzV#SCl%IH;V70-wNp;o0vVv<$k z*=KqFV`Ly~MFhZLyY}oH!PlJq%ro+0Z!X*P3LKuVJOet8J_VXUe?`2|Accr5pe>*` z3Biaw%P(^LRc-KL=TL1(x<%7_{O#yTl-RJns6aASML(xdnN$Ob?a@Focl`(?Z_ax5 zSE@f-OM#9JCxD?=Zs$Q8X#w7<{^Fz zT)eQbun29ItLisl*I$^+8B;GBEoU=LL7=`tE$-Jm~m~M&HeB22OwR<6Zac zXD#c~54&q|DiuemI8I$2+AG|vmly}we-d3Ax!y%t+)O2Zu<0)>_{|?o=dX2=sb;W{ zrc`n+gP>LS8R%(^mg$OQ3hgSST?+MPo;0!1aTE!{OgWkj{6eabv#dqHT(jjcQ;Y`3 zYlUV$;a9set#Bd}rm2~^xLmAM&GBl!8;+OxF)C)1E1x`FEmsl&0M{e4Q`Pi0lZ7k> zQ8CQp*6ANx3NTuJB9v6#12*7Dqo(ihcMo+LWblF4J%PIRq-Gh)%FfQt==nON_A@g` zJWw2)BtHU<&@VotzF>F_^>+3}-&VQD+zu5C7AfCL^;i}W%%TE?f}l4FA6ssn=#WZv z@gnDHeeAcEtp`tX9HodVyp%+Di(oycSh1FL)!9LeifNY$l2~k)cog{KlFhh`SM3!* zl3*FSFh@wGSv>cY44z~co)n>^IG+9@gpf3q&V_BaIhzW5p_QFD<+j6o_J|ddk`r3Z zynM&IUYN?M?{c}jwBPIFH|$W9Ls=QM`P~IMP%ahLI2QE=LU{H^r#Y%UaeIB#W}Fm} z1rR$Aur|nyHYo|#CNsh&amDTh=A6kARcM-+CkUWD=in13{0cT@ZR*hBP91kwa_RPy znb}{{pB+9tRnH|Ns=rRPTd_T3pZK^AQT|>fb=;nc#tROsK({cp;`S!Pn@%_#`N-y@ zBBHS_*HX`iVa3y)zCAlkaPo2wl>I3cK6Z8)bWZ{!q;kr>mbVe_8wK58$eYd9ldyCPFtD&W=9 zOZG?IkGkz5DHT!=6`5Xa%edEV6a4k9V=vG)%Dl1wQetHhXGlR_us8t!!!sg(i}+K- zOA;?)qq45(Y;jf+mX(umv=fG06#R2J_LNh=i&YOK|u=X;`dEsV^&rC+4Tis(v@N>9D@6?7;>% z?t%ULiaoAJHbrCkp=K=M=1}6MW9N?%J0fF}r!Ouf^MSMJEWt~J`BM62`8;7B$bwEEQO+Wjf zo^`|Ij*(mxE`>TX{uZ&c5_By6FN2I87eRc=z&wV_Ms5&ndr=2ihy}kw+7fqE zB!oq%?N8%&CTYz#{dCrbj~b06v6aj>Iem-wmwlR#;`4ViE791|2|EK4xJ0m`S*nP{8P@Z zY!c<;m?y#~w!>+7jL9Wn0*PwI-u#PuSyVMK^Z^kq4^S#F#P`2}*8n5g!gorZHPWLU z>klAhA`q|@HJR{bJPHYC{AkDN*jH>GkB&QhI!@H(D^@lmo{Vnv!FW6VVAO5t53W!y zJ_IWn;Gl>$carm2yoWo(_YKpwtjdv3*XuuWqz-CMT?!8>c2~wSmYmOV^vq>O*$6Kw zh1YSLAvoJ&bD-XmMLNJ~NIL6{RNbHRo_*GP-~Y0OrvCKATh2~q?Mlyya>chyJna>d zGq0lNHp+M9?z}VSoH=u9fd;r*a?m>L{SzEt6P?)^qi$vKV>!&E35@@T%wQQPHsH?#)KYEh0hY*8n=K(6h9Ms1>5s&?IY zP2_YR03hR~(NU2YWG$AqDsueclF9(U#jmQkd~qjwOG#1%N^kkQ+9W-6pqEseMos!6 z?cwLO`uIQ}$^N6h*$LN0NtQ-%DX(!Yued+bzr)@T1~sFi`ZKzMhK&~Z8u#{f&bt=v zjLy5undRP&^qE(Ty948h)2Wt*T<0s#^~ydc6a+X5%VfCE<;#~Zp444@a@gtL+K^BV|o!X-Lgp7a+U* zZH``yh$>!!gGO24&}(h$ZPpop`kcZCC$eSloCnVnpTb9#1ppGW6dpL|mU4HwQFz#D zXKqckGfSt#Gyin-;kP9=<9_YxRXDj*zsLXY{-*Vtl=jgd9vDBuw*rD-k69t^0n;N{ z1MC+Xu;x4>Zm*cc#fV0cJ!uM9R^p1gI^{G`DZ3?VqSfQ^uwg?0RW(zSUe<#UJ(J2s z#SHw3c5+*1ubvCTdTx9?cfVN<52MsNM%HWMrbv(H$j z(9Fy_ai19Hxla{?teHjc;?YQCe46lOMfMm1ZMW=l*|yxu_hi7DnSa2wK*Vh(2I3(Z zB+Lx$?v?iBLCiXINNPWkjfa6iYa}a==oP;bf<^Jipb-^!Sv*kirIMrvzm!~(Y{RA? zS4Uh^Ctw?2rqVHtRMuQVMb6|FoJ@Wl*I)@)sx%20af#?t(ka=tWDpV-wM$O&5&Rmt zv=~4bKeA=QPMe)%qA3Mq6J-3ksU~!@FtKMKVa-OxpD_~?84~O$IgJJ2x3H+pmQTn~ zJ|LDjkuX1^Hq2!G6d+hM?}Xu4+{7fw>xLr-(}(qAFVoC(er~6phhu+Dzev`S)K}ED z4G}9H3%p@a?O%KUe4}f|AD8&Y@Hpt; zQwmQllYlFc;fLE$adqkx5$xHbRO%84d4F#k40rXB3gxIo5*ehx{*1S-heGZ`qzrNw zLijbKn8TQ8d_f-aM-P5^G_1nIcs<|DV|Ij5ElkQT$sDdJbE=%=^Seeb^fvi$e;ckr z%koj(RJ0}07(~?`&=D6mFYfGI-9?g#9xblR;o82xJ*%te7e)TrQT=x*uF6V$?HXyv z(PcDf*>7K#>Y70zn(=$+d)XNr@bC~~F@QhEv&5uOFl|M;bIMpkK`LbvtYqz&V7kl> zp)*4WVm9M9D=nW&%uBzk^n;ZyQ%6}axbm^03;1As-(LQz{`nK)nnuEA3RnykstrgC z7@T$|F9gE+8)Z?$*v1#~lVrThUW_b8H0P#vhcRZaPg@DRsB0003WzE*u@YqND&w48 znJvY%f0P)$lxBu}S!!Lw>PF<(Ou%(41C<{hEmbF*S*X;x;rhzB(z#>$o)CePdlFr_ zBCKE6uI=BIp+9+Vd!+@Ax>W9p+)o*0n^&&DyK;5y+BH1Cqq`$)3H&e{N9WU8X$|`Y zpN!mD3^5+%=EzaydGsDlYV^~&w*PZ|SULIX@OdLXG!s^0!DoSN;gxNwUJx8KrPu@NH6i?ke&LuPpbOZE-9Ews z2b*M*3o2sN3IE)K9C5N>WR7{qGDiB0ks#yIU0869)})nlaC^9B*C~cL{ar+E>RVo> zI5{0Y_Z(eP3ycX$p*;S$4;Rv*l-kzM7A#y!#gIj#(j2a_*N6HCS#Jk551dwHQK-xT zMe2!nC8u)wcI!asCjhx*SQl>k!|gCV-GR&E)bVf(^pY*p!3iPbhGwj&6ghBymU zyJ|F>&#}3)j-<>I7HDxw+G)jGy*C#>%q&?&=rH z|7y43e9Z@o<(1K5|eU7H*G+XKg?^@w`7KgCOj9|Z;<;!Auw&jHS; zU2ju|3D$o1wa>J_X1_##!pbG`Oh5vN5g8gpE5z{>51#AQm?}UX4rK%5^PQ=|>a|Nx zJn@7*f8-MRou!S9)z#6lTE^tq@v%pc2G*j2B$4q&eG*nCs5YhWOLvmh2>niogL-ZK zZVop$iAHR?s?<}P0tfhi_qH6p3sCi?I)sroo)(-COAydC)Vil#*Kn)eBQUO;kMPpK z7ycpM80Lp_Ghp5le(Yi-WJPCl&4=Au=zNcU$%jU6Hv8~DGTy<`L$ptpDJ!y{bjv`O zowMXNnP;6c(Zgru<+EARxzbWXG&mI0)YkXA`Lg>~0jQbx^P!Q=-mk|Fv-|%eY^V~Gnx0AS=Mt4Np;#t=YquB{_M|w?X>rEUNfDwQaLgz zyr~m-uQLh9nO*;p6QjN^7!C{!An+t-T(B|V4pHfsVclS9vW0vBF^ba-OLf4<&)rTrBnQO*(6r+i~%mwhkKk{sE2xRzL@1A;u=dN!zc zi>*d;qC7j94=3l!@ytXXoxv{ot*Vzzu^}m37Uh34M)lb&;heD4tazEUle@p^A>g9P zns4NqhYoie)KKz6cF0f6LXIAyQ#22iI7;SyV5G`{4@ZC#S6ah9orNc8I3GL1+Sh%a zjoI(~%l-dr5TGiuIHi-0ePir9pi7R?Pp~Thh+5>gz)0enF@}U;L&)8M*Cn_kEVLLP zSQ~+ z6x9{dc_(YZE-g||{2ld=?6U}ve}%y&`V9@+uIWtmYKt6F8&IhMpq zM4Rl-nN%&;REx`4JwF-MDJ@%TP>VEMFJvMwDp$*q8>Nf2^Y!Sg;H}O^b?`A!JrTr> zoL?4xkFuX@#DVTl>_?5k?bAC4&)7RysN%KHZtrk?Uhm}3G zac0Bt7wrZA%=z=@e{X5&xvhM$>e!Q$j#DXCZZn!qqa;iR`9l=R9lgirGiN6J#l?51M}OkX6Rp#yTYwCH z+}AJeYelVK(O)>ihyG&FlC@9k-Qs(dIJ?v`=HsC+m!Zpz6t|Epf#@O15hrT%MJcR0 z-=+Ne+BehR9AJkO4>eoGi#!i&(> zi7SDb%h|ffMse1kFqBsc4BqhTl~tTi=zGF(Gnbas6eG8|;|@Q$j7cyrpXPE`=3U9( zs6`K~N&ZTlM>ToDGbE{{aM20RdL6g>f^4X3I_vRPF=B-S6x|MY5w{t0n&Bp~HQw9Y z0g0dw;`+)Rx!fHq?(N5p*>iJW@mno_q9u>5Yqwk1t=rwLw;lVRjwLql@LLn3FNb4G zI0w^=XvHjoT|$_Mj+V129M3+;fp8I~V1I}h>R;EaY|M^}Kr}DF>p@xM^WOQ6JHM&& zw|3l17uUHfI}*f`y3cDnms~Mi^|k5qHst=&r+X*v!6(9r1p&o}az^l>`dl^5Un5EN zlF_=Yvb)xP;-r0?^3p@98o9Uo$CuoF;xNW2uU#$;HOD3-z!2sQQwaT4U9g;Pl;eJW zOJ%r6(J03qucDj{EVD_zu8dlzK>9SFO-C472oWLwpU9?2)foM$PU)e9hN7 zNo(9+@D}{>1NfTi*dJpxX3@84@nBU_F~vR;N_nNuh{>WM*#{3i8_iYS9{;zxh0vqO zoqqbfmAqTydG5~SzUHefK*gmDFD$see(Jme=Ce2Vw4YR4tot0FOuLbVD$gUL&#OB- zu<~6bEMp6??N{9^SA;Ju!kL3ENZ-#Iv)mCK)zN${QjuHZh?en+YOL1g*u7w|A42DQ zjH~B{FbHn=7uN$4T*YxrzC*V>me6EqO3jqb`^&PGP`@a2syLryy2@^HcjaGX{>Zu> zK10VQf5P?>;bTTb=-1bT)dk)+`IDl^{7{VG8alzc`X7FV3w`v%e(Vi`hy>m%SFV8Q z1tq<$d^ICnua0Bv_rplGy~^8tgGWFeki+gMQxIu}e^>wP$4kU*pss9lvc2{`^a0a353HSmqgicFQU=%QvQXEMap~5l5hs*d_wSNdjPX{fR zM=vPStui4j2mfKX5mrJsW#fn`=Ck9~x7Nn9`R$2ZIpd?lcqJe>MGvleKU2=#=}_|| zlNmn!o&!-1$}

R4PZR2(&p)+&WyQ;F{wV%ZFPZqC8Ee5&=(89v(-uo^;Z@KVQ!8 z{SB|Vf?O}COe?`8O0s5x<#B+?4;FE;3xK(ZQxL;P77-aU_Nt^fP^RamOo5CS3{rPC zI06Gf+D&KDVC=VwkI?NGVcCbL5v9r7Hqv3Kn8VRM>mPAKN}pPnqbF0w)*lulM$WZs zpG-=^Wjc{9;MIU(L*_yioPQ5z?ZB12S5}C-4T7xG6)OwcUi}jJ|@0yM3U?SDFb^+ zY(eIYe2e1Bj>P+Q!7bUehpjJYD88{rNRclgTG9zphnk^P*0>1)i zf&3YqfS-p`j)JFAFln1*%Z(DB_JX+}N;?MFTKFmKG*xf7D+w47tT|9lE0dFkiN0Gl z?Mb$?gI{-b=DbVBrlK1AQ*o()L&K#q-o;_)3L+($$&wLbr+jkEsi|5miJ#tbelhaB z$&7Q@F8~ZlP4^RtOI{&3=4568?+|-%W;4!l|ISlrG}Ukr)X!LOunm*D!Pf*Pb4o?>~cYAi!L|<88W0KE~x)aajbH)Ml)ISs%xQe5$50P4fkVitHncu(jfV4>bdGumC94q_jn7bh2eS` zq6f4Ymm8vELijYK4?xe0~08IkD!% z>?u)?bhPp@kdr2(!)5rw!YnW?W!^ZG&m z0sr19|G_uK@cO+E%C?_*v(&DmF^D~Cj*0dve$O!(41>{B-D8oVFoLk;5X>79cHF!Z z>?D+;nwL}>)X#sEL1#rJO=~;w`Wm1& zvb-|zQQh_h*a07FtfY%hk{`({op3YgPjQ(Wr&<0PF88&5(xnZCGpVu5)}Al+AjtBF_*EI81usGd06ra zq7kzvVOjx5t#o50S*;S%v?OsP5vJniw6zEo+Jil@I^sFbDZ+b70R<7|B#ppr0^6ja zcZ#61Tq9Isk5ZLVT$m+9m6JFd1xq5F$cva!O+33iYBd5%4Qcf-%eqD`5ZD+IF|vBy z_=qYi%JbfTJi+>gUeNJEa$1J}Ytei-k9dg$_=5Fv@#1;UJ3sorii?(?O|%$c-5<&y z`YA<@O-*GYEwhLg^+ag*moN;)1Ec`;@WNm(t)fs*lK2rb=dRvi9iq0vvSnOw=c7mM z<;luyWu_I?7Fx*!WmKv&6{ER)wRXt5<56q5?YS4Wqt*=DOfK7hh@&3@_C&I^P#a%v z4r7BhzMHy4)5#ygTh$Is^gU)aJ%YjWg;CZJcwhZL09+Mrs8lu!!^ql?RAH`D(T|A#upA;)wW?Jozdlv8vk0i@ZcpJxP>Z4 zt;h0+vwKeP(b1a|=d2st9o*t?jSfC{)&KTSd z8*Zl&Hac!S>K@BXjz<@a%9^`TpE1|h&6)a!3qNhf^6Pm(sB(O*>-b}Tf%TT<3_Z0D zl0i?LWI4t}P(@y3+6@OdPwie2ol@9(htFS#vL}OREuV`eEL(c!a8wG?L0BtP@(HUkK77)&ZS&;dvtc$93jc66jDK)G$P`bm1Q`N1!+0wF@=8l`-$E6Ai&cp0 z77JhLUOnE6!rYa-3<23)10l4tLp2Ik8ibs{33ZhlK3|K%Z9ZSad>Y<|Z{YQn^Rc?_ zqY0tk#K+`@m*Ljt)eDy|@AcI|%phI(iuxD|e>k8uBinhGOuLI+QUHMus;JY;yAjz= z7s0WK9MSP(3~BK=ORpDM#t`~AE&Bi4MAR;YrT%fxFGcN|d(-yCkDhx=XfHi>`E3@v z=&>$*E=$CPb!B*Q?UQqX2oZ#QzYEAnlH*Vi0N6|JGc*FEhV#hGokWaFuDDIoNl9|; zarEP-)CW3vaO;@B!>;5JGl@!sBTFFrE4xM5PU(H zq;P+Cv%W9kd!Q~<%mGPbCbH)3u(}bFA+f=jU^kjZf=TLu<0f$FG<6Gh3zM+T|G%8=Yk2fz@APf-|uvaR^{Uzv_cewp(V3 zirvLpV}AZ~Q+sa~=1MP@mZ#t}pYHbWskINj9?j>h}Ufgj{@fzKwJxFAmFgLh@~r^yTOvp!5oq6IKc$cf2!EBV#j z?-#plJG(fWr;vOhH_icxR#16&{O&-#@-yQT>{Kz>BVIfz8Rl z5go9)7eJOntpoohw^yxHK;5&V7TASw1iL4eG z&A&)SCBJ#bo*1$aX!(tvt3Hz{Vc0J@6rp)Z5-MMM2_Ptjv-ZVppF6kR?Sdo_&mBK_ z^VYiE>-z6`48uY?5yu1bSlzVlu03m$*Ze~5>|^T%)f zp@RJULE$*RqqUjp5<|=lEo=7`st8Zr07s%JEQ4=bg4Vy9udbh-NllHXDxEM{ompO# zr|VF4bfQzo$I}aE`d;X?7nWxmc21sT8fp6hSDDU3LZ8;59?qhpJ#y(+!v#SoEexM1 z&uiGn8o5R@*Eo8#k!$ku5h7h$rW`Rs5Xh1{Bbla3FTqbk=fT%1U5d{Jhu;m0lm_w0 zD#eYkV?x^aPxId)|M}&|3-=s-kN0QAsbX>)w0=Iu3CkO1mh7pMI}?LVmVqt|8lwP(e%=4qN!V%w(@3Y$lOQ2@ zzLx>pJ3E_9g_&c1m~?SHQ!&T3DD<8}HL_Fv`IYgxHEM(%lN13NEjT9w96jN9oif3J(V)P7NLPK=~F%kIGa4yDzSWA>@Nd-v>80gA-G| zm0UB*caI;QJZ2>C@;CkCx!m||mepJDMn^dYaQopnJPe+0g_(F9=Aj3uqVRkbU z1ur75djugpBw-jjMXT(r>0CdIW-;5z0x2Jep`%9<`_pIAg)lEj?}{*GYV-Q~mCgGs zALe;8zIoQWA`}6*gY?f9m0{+i5QV_T=YfvW>UN{`_3h3M{#rZ^ zU$!k+N+T2^0YS-uI+1&RyIxvUx|`pym46$CT*O&FGdHK& zvhoIPvB7)IE4~5J`Z_qri7%6WE%4YA^q1Hq>H`+~qAY6IEvyw-iSn%i^oYch?j^lq zskG;Fme4?)%mt~Hl~j;R7VAacIQFu{oS-xbs>=8tR?9S>|26Viqj3 zS~abLS+0}6QZ@@tVAjgXSdcz(vRp+z7R&7tn?ST)G2vudv6{bPqQ%&_%B3P1BXLn-y2LNTdeF1Q?(K_rZW#lP zKdE&Xw-Gvx(Ioqv_apC-?2hldVGhnAlMc?l-%falfA<_9K}y=Jx!#)j6oC*+>Kpb+ zOef{v%H$G{Y)8-T&%I~|JRw2T{fW5~y?r8b?|Vv3NY^C~H)ltXjAkM`M?exmC#h3- z5MBH%i9?dOFOI*2c_@(#{h@wZMNbpnZ?OPhZ)EmZEA`addd-=$HXN~=uNsS6AJ#KQ z?wQ7F==o7m=X>*W;7HaA3{}lZ zad?n^q6KnM3uIyyQdLV#7Fo0|A~#18bjLfp`Rn`AmF#}Y$H|a%ed{X#BUpy6L|4_F znWsK&M~PtZIzt$kK&;wdhG`)c?hyLhmm8A1FF2^7H%XktsB4U88L9ZCWVBN97SQs? zS9A03rAxbWFwO`%0H@Mbch#oAeLf>bHR1{Z8)nXj0oF zB`Ds1F`VzV@&Ra*^ORWN`1x1~(Phwtbc7jcp_h0S6Id$QBb!fT$V#`pqk|r6Lj&@L zcXJryE>t=m7PXIue!kp_!in+)o}yNHo_Z_g+*v`uUyz3#DVz=6sg?-N4exF-(~wO< zH|g4muaI+QOvK7gl;RM>7Y6BQ#ORNevKJ+6le?Ie6tm$NkufR!x;5}PbK=)FWHhwx z;&_5(Se#tqwI`i=bx?%mQ`YDKqlMqTI9>;0v?D%OjokcPXX+l;ykM`n6H)xl#W=Fx zZP(({r*p;j)WW>kjHlw;JJDCH6jjssH!5*7;jY=xQrt7unVUabuCYTDFBVUi*|cfS zFHE(IxGC2@>#806)Ns5=%@C=;)yJ+P!(~{rYMC5)s0zvc?ve<^U_tDG4;bBStN?rn z7+og?6|fg5j9e-iJ>?atEOM)zT0Zg=z-8-!^Hh)W9#9CjTvt_$%a2q4Z9|6=zjCW* z=DfMgWe7DdqK=wW8(B;}L?1)5oI&5pONwCFJfj1P^z~?*OYES_Y&=f8m13;gTT~6! z)nDjWzESCYvhKW2tEJwR7#+rQ)a9{sF&JdsmYsXOHcRa*$Gc2`!IEN5iz%Cn_?b6o zvX)tRI5jdwu&b}_B8&^ zcaAH*K5a9`V}HX;$brvWlDtq^$%zgyr15f9ki64_d${X~XD3)rSo0Ry=XHLAT-#(-JO}W4HSeACU zjOICJp+x0}-DOMumGsZB#|ZJgZjWsRSDt$CGm%+2Bn3*`nZ z%7P~n981DFzK!_=3wvyo>ed$oo_dvs%`b+?`E#9%U9)a&{loZMb(2qFcbs>>aP5oh z>!mCljaydT>`D-9&S9({^z5IS_wdubeG z%eZy1v*w}geg0Ipd#V23^-TS}=e@|xKAttA@qqM(*5)Q9*seyK>+5y@y>)->y|6ho zvX9H4I;bBBZ{{9zr+sPM?*`*d`JB~-f0z-d<2JgYx!3^`G#eEuR|^$$ zUVsOSCA*EmFVfZH~8Ycd$aX>i7-#>`1d&(E;QE7rTmm?te>Ww*rB2K zetRMgv-f)Oy&f+r{(Ux+Iz1dZaC}<6zt^23-)+liVeL;aI%tuU+)*s3`W|=oJgb74 zFGTq1tVGQDh*7@^VMQ5Rg&4E%L0y&Y~G38_G8oeFA_{bwg|6}!%m9W|{ zFXX+bo`>bgj622ptW%qmQfV+#oy%{gPems)VX2ictWq8N2EYXP22t5a*^OWe40byvLO;B}UZ zgehTW6p@5Uv_~5FCO5-+3wO6)AnA1XT#6!+Zk+_*)G{mKqcGwE=o zsBO$du_*7@20sl`ao&5BxD-kkscw>IEY*YGF)Gz} zY8#vIj!y7osd zDkIh?4Q<-rl;ZYB*u6`kzrB~BA75+V;gM@4Y+_=BYeUzVzD^swXoWj>T>0T8yHi?e zw^@+7Jv_$*ES8ZMvGTSP_k^1ARof7TXPf;^E@q16yQq|4zS}HTie^Jr3v=8oW>7!H zx_9nk13pd3hivdJ1ifr4Ep{Bx;-1m1^jl zf|YM`t&%A(9ux|(iL%b^nm6D;>SLf{P>R5aUkWL@Vr^wm1lO&bx9{5AJbLcYW-vGJ z|9c~v_Pl@Qw%o{_j)KoS<$qcEwaQ$iQze9(7* z>xNlKjUT)9bT8RFe#@z^L;Ei{aOh*S6PduDP)xMZE`V@|-%qI)N|cE6Pd-+@Kk4|% z?vBozn2+jCak3E>GeMoomde+5MMxh?IdqMUT&5e`z6odbH_L^G5enL=E^gm+KtGoTR?(s6N1?L}2g_ z$fAR3ucC=^y?fC`K4&qLvKKn#)rLsyvY8FR>47CdJg8VAK$)$mM7aa1PKZ^sHfLs! zM%~VOXH%zWQSW-ayK{Rf=yn6q(~!Z`I+nbVS>>Gr^Pplc*eoD;=sJlQ59hRmT~OUb zn-!c4`u`h-U4n8IW~$Inh3QU7LYTFwnpq6Pl3AImTVW`lhYgDGT+=DmYx?OtQWsqR z*Zeq>$W`DsqU`h|CUqRkQw{UI$@)|!QJQL|ymT?2sNJDA{yqKl%zmD#)<0z|%@WTW z8(^@Au_6GrJ%c47a##IxwPFg1c_aBuZ?L5@gVfgVNcmpENd{h31d(M|JaRA-L`f{t zKeDd=)T#nwqvFtqgz7AX$rGfg2a__hfCy6*QiTjC2}duVQVfXnw3P8UY@=0=AT~^8 zl+$)8)Ke8+Wjo2ICg2YFl!``{UA69or&sR<_ZMZC_K&YWb@onrK&O};I}IDt$NxTk zg3~}Q8SNY|f^;9Z{!RCj+WHko_=mczh!scN6Q~KxAj!h; zrEpu4q_&vGDsKYYr_p6L7(JHhCA*f!#=GvT2^gSip--Yc?ie@vJ$~&GxsYq7z ztleY!R0#7-D&BjBW!T$9-E9K*-!j}x z#cjthT%%(EeUXmvOxH_5xt220@KKcVlvsvq&p=s2D%kl_m?r8buo&A+=Zt@XwoIA^ zphb#u!4H-|QAwHQgzFVqK~qv{mOM5q6X9xgY8kE%{|2JQv(vF-W}3dAxxg9(L>XtG zmUkpffoeg;i2xXkBh?Dn2e`el4lJRe>9G)=qP6mAmd?jO=*p+PPnnsF**FFEW-e`z z(6GKv3W<@P1O9w!ww82H%u*S4)p1togxM1yHd3GstfQ`x_LE0WTcCQHO_EZr(?^m% zY*cdtC*jQ4&WT1c**M|YGrS$)({vl(u!Y7OHRta*HRF8L7g=IX)8eHgfb@F3L1xgX z6XuO~Hxf+U#B2A%dy>h~)z|*{QGr^#-sQi?7+v#VIM)SNX`+V>xeNx3`8ScQhqGYe zO`$2TZoYXSCH*t>9TO)K>_;7qbPid79W2($K7QI~0dQL~ZGrZjhb_D{22UkZrO0EA({;+( zj~zkt1%ZG!$(~OP;y?A5D*ohFyX$Xuf};Dja=`oc*1c<)j{5y<`9$S|OMYdtJQ;L0 z{ch=L*Q+cAmG-7Po$hAVGTqFFDkli%jr7JUBS|8uw~@v5uv83&0S%rXQQ0M1T&!d5 zZnhEclxq~QuubZ;fUs076>;0%)2)@>FEgaKghNoIuKG$oQD2F#c&ED1p;k||P%jt- zGsvdfC))fO9?)|qJ3gBecklS}2}O)HRCmS7H*nx=lVvD?m`@Mi6D1Rzj8Q3c7OGC* zCn-u&1C?V;9l!1P)L$dfGfr-%m2mp;b=OozuWVhFc!o& zCwOn=my1P>RvsV51q!zCVDIc))8xO=t`s1 zY{|(L2L`5qI(=l@BSS!(R0@Zx=Fr$*Fuv0&I!j-XoPa+G5<7c9*v0;I{$%#>MDWSx zp*Vlr>L+eFdGa*0tBupGA1>WqbD#L(t;JpsmzWaBsJi4MNRI~AKRiU|A^tLVO{Gzwm5O{kgyAmtR|S-Y(Ft4=Dr--lBqVePJpG^YU=i>QBV*+t7l?$p z0WBY@F&Exl!*G)Kxp_O`p!>LSfS(#hWa%AqxAzwpe}@f#so|gI9+nbLD$ynCl{AKP zLGrEk>+ii{F5uM&bT)| zCH~+bEkxTpOmB#rpJ6$O@f#54jnS-G378h5UXOTGH+MYuYILZ$x%0tRdS*J?ZWR)_ z=Hz%Gk*#=@I7no?T(^Xqe$z<4{l$rC7-H6<%@28rY_r{p61m1?>#Utf7po;QdRquZ zJum-%RyT*4_y2a?oJ=13;$YnzCYN>n|HHaDOib~W>;4v>hT`juME0xyVR%DUC;r7p zChNvE5&q01d6EZc-S*zGNNt{zI%T6dT*nh7ydj6tEH)J9(O85{D4N2gO0YxT4e+oj zsjDU#r{VOR8vRNJa*qXrAx);+Bz_`_Xo)$9#3VHbp+`ek@mgb5;E%RFq>oBmv+jD* zf8wp-JemLVPx%uQqranOCI=mqP4b@&A10o9qOvIqAIA$^NG$1rX(yY806f_-ZG7JZ z%TkHSCo@lcN)8ovMhD`E7jT(CV1V%k`gYMPXNv$fUfEwPO}FLfmrKLQ4W+-a1;4*nHuIK|Pq92!6R<3nnJ7f>A%8Oo72n zo~KS$=dQXx8B7GVnm-Xt?%zwflQ(fzVy>(lvPLj>St5~Q5(SZnXpg4>3LG!_f;TXo z=%qwL2Ev8YlZanGh!RfwG^8cP{NXUO8n1!NO;ZQ56W-i%f0(L}DNs6Q^5(Y5Q5>^* zM=F(V1xS0>SX@|ubD`pOPX^6E*o&OhTD+Rcwwk!?$@;jZ>HKxtOa)yod80!;r-WJC zB!UjoJ_XdzvZM&29-m{^8J`l1)4DwRu|QsATps*KzH5bxHGoR0d$m(KXl0s-hI=y_ zn}Si7s=x=CZM0$pj>F{WGP*DA{`P z!BiViSA`M{k@WA~pC9JXWDcaz%#dwksJt6Vcp)5o3&CvaZ)K8DIs~M&VOR-h_sW%{ zubm%LuY8xBZBIp(wW80Cp({twCPTiSz zx(`uVKAWrO9%@vfF4S{>+6Wk19X0oRs~~uodJ4}2;djim>~TuTiI7>>gGYe3?+I$h zmtm(IR>i4KFT|DU>fZb2^6^?wh@ac_cmGq6Yc4g4ey8IXD~HQh%ZDraO>?oC3(|kU zCL>uejm;5ZU8;)8oJ`}HIls+{eq*zM_ntIXJGl6((~F1wTyE*8Zq75!8=9rR`we~W zQo$=%8;ecO|2EFP*CzQ~7<2eh`15`3U1XEs%4URLVZ?1MW{CJGNyYEW98;?Rb5N~2 z!znH%(YIkC*!83`0Jy46{)MPeKSNij%Ei0U8U>U2i#qFF`5_E08OQp*^yrT~=Z7gR z5+j1ySHi9(iw%FH4L*q(BxzXU%@Xqt=#@nZ1fBIaZ?``BxIZ0EcjAOm$iuZG8eP`E z!%TF}zVED5CQsyR&EOUUq4`n&?bBXivgj2%ndu1?)H!sgVRp~Hf87JboXS?R3F{VR zdJk;_rF#jxk&3CkPA=}g=R}mzJq+^*2L#CF)mgfmx}eHW?Xx~@EBp84afGJC zCgvviTid6N`ibW9Nimq&L2jpxuZzAgs>6UC%Wz^aV>9X-SzNy9J^qudTmSBJGtYcc z{rvj5;K9kv^f)E1J7{?$o50a_=+Puj#7t%p$-b~(Prp{Z_wpUy$c>L( z5N+>GLI*KeF5wUnorYLE6RQE2C25nCdF*OQUKkX_g~NA?1D0ILvS?3=_ghlfgvnci zM1W+Cv4@?|d6?7~9`uX53Bh1XVG~G(K1G75TZr=)&Bep3F=dpX_0EyU=A>-4d>Zfw!=UOu1R3R8B*b;nlu7l z`yW|>HQ{{PH2cW_WPXtB^U|hRqu6Ksz9BA)C_zCdm$7oSKt_#(clnMS^-C?dzQ-~BEP{ww3i7-SOU$Q}Wb zcFefbxX-xXc*xi?o-m#?o-y9fIGiNUFLW>L3dXM` zgR{SgKi^(h$NRu}Ec#m7(*pZ(uLmV2ZGzVK zXc{4b0h_G$iVMq&OU$4Cs$C#KcJK*o-Qac;ZQQ8k{5FgcAlKn4!9XG?<^Z^Gs1F4D z6Vm`)n+HU%kRK6&0cj%f3u*-!@X3>01nY?fMPe#huM12>RFB?fKa|*kai1+5V9580zQpu z!EUt-K|2nrfFX%xK$0^pK5Loj(5;mvv_h1_P4lV6NmnIC1M?rJlxwRH1Hp-4^fb(NbRgJWyiCOZhs05SAJ3cZ&;wmn?Qq5Ja z$bxHz+OC9t)p;?0==Owjn2~92Jq7L=ZR{)y90y`Dll0Fl6BIVX5P+7Nh72~j?||)O zbSA(|Fwjg7I#!O6q=-%GrX;OVGD5cCU~&iO zdz#QJ!_AYDZYgF!!b1)*7CDee0>ga-PahOHjCh7MSC8|7$-?p}4HFVQW(s4IkDQc` z(4p;a!uAT}ntNsFTdfS)Etx3J_<#Vc&@GgF??<^J#;#l~P3BsqqO^2`^c6*==odAI zq^cOJ&|07$t2C`NDSIRvE@q4Br})`W-cZZMK-~3lzZZ&!2B`a=^#PV=egXJ?TK2T z^~4!;1FicP_q02u(c#y&e{(KCbvcHEJ1(l07RznaD$PL7NJf&z%G9AXN^OB5?8iOD|E0OxkCO zE?{V%XCA$~M+WuHNP`hNY%lkYMX_h~B)GcV?eBdZBL=!Xi#3)1-sIv*2oFV1v=JQb z|0|w|`TM1^Je8M9lU_db7h6G)ew_Dwnu{#U%UZd-1C#I{8{y4eWN7(tK;Yl{X1-LK zD3#u!pWdIWMozLx>T(boWP$QcB5qWcN_lsD5@YWpt+t5=3Wz!PbHCq3KbL;jSu`UP z8+%Hiy+lF9z|yuhkH6vf`~FGcR2lNd?XJrwC%k3+CAz<>*wun83snXn$-~etU^Aoh z4_~7|z|K2cac8{BY%LpnqIsejoC+FGmKebCm5cTI#f?LUE?t?K*|-Vs&rL&nOxn2! z7uLJ!c}2k7Cb>>LWBPFsKpskm0QiB5s=N{XsJ10MBW^SW20>6Nue?BELFz%7C2Xad zB0@e;@>UgGNA5!GQje(E;^i$XPm*ndaI@`rUYQaz8q*eI6C=%3e z^nZ7DCU9~b<)PoIowMiMnLV_Jw4-BpC0Qf+(6N#Y^6_M2WH8u{!zJO_ncms<&ZX|2 zU9ErwFeHT7gd{`|63h_@aR@m`0wE?hCV_w)2q6a~U=j!kx#1=6y~HH{uey5=%dx}T zpEUi|(N)z|Uwv17Rh1jg581ex4&{e)K}?w;c?bE+Q^k=KXKRdb%Ab^gdrxI>XauKJ z3}=HC{ENI|HqGYWGX7|JE1$RCpDoVFe%$Q)C6p(+Omd{BZrqP&Ry_o<#p9oATZ6w# z9Hn>8=usQ?+q8I{&IE==_zK>Z?Ayk!2P`5cJtPc?LqW%j*(nYa`4*bN^(=^Hhb;Ep zr>x@n>~L&5GW6TeW{VexxPj(Cy3C;liEUF8@5^V;rb;632YR%O+YWZH%1N?8xjST+ z_(pBU#3|S4?Lu}uitSUL;g13W5@!*3^cj|HWPhV{IbyN zh=4dIj*W7r5CXqBEkNultoEX~A5w__?28i!%)MijX9Ayqk0$l@tg7`EcOcIS3|Vk)1_s@Ty? zLGQM%7@m54?gSce0hgz1xi=9W1&jRp;M~~WLM$^pxjlE{+SCZH-pTUJ&dCI;PE#L( zxhC|1n`6nr)2|(yE@UeO9JY4nIQ<}>%T1^D=tB1B_1i}dydrm9YBYraqGQ+Pu9HT= zcA>a|Oey?G^kKQdWsi(`uu2)){bkU_rX{vstjnRX>m%hd2F!D_nbfmW8IETY+x6+w zJub}TiiwAC?9C+0<%i9`@}-A`wi^PE1?y3%RlEn-WS9I*>fYi1Nv9~;qWUKW{!{RJ24;sKiibYp z@#s;GMnS(rKZIR!whhdZAPd;X5Q|0sL(PL_VgZnY1CFZkc%DVUWP1CaE2{fTBm0Zl zF{F<;1hbgPW^)-NNHnaNl^dTf7F8@YVvP=`SZ*j4zmm!)lErE6Fx-~SW_2c?MS(!H z(dks4=71gf9LM%pBP`1xXQg?j`B%0!ub&1U7+9QeM-T(N@qk$OgV>GzBD7aybd*&Z zF-|^SIXP@`S5kWTnLF7EVOi?LaeVORt&8(#XD)FR-`O;Yr_*PH4LWzjmw zpE*`_-U@5A&PYQ~^O(n)jkWYXVHO*>8d+ekF+ITdV}}oW0sB18PQmAk;2=mVC@ZGD z$d~l-ofWRQvAD(uPMWUl!h1bkxv9dj+gt)vnx6-+!S2(QTjZi2Nu056sR*>IVlqEc z=K4SO+tQ|hpGcpG-JbKl`Dn4cvLS@6W#%F#ma`FBq;AHERzQ?wVTOk7KJq}B6|i0G z!3duViBqQ%&&4i$;`r_HQ>Ws$9|xTO!09rpXE~{rd;&Df$8ZFwR?E>-#>Z^DHfN_v zTf|Z=c4|2_RAJZK*{WHx`Xf`b)%17-p}S^O0mISg`ebZ=p- zG?trVYi4dPw>S4(dUHlDZF&IQnor|^f8%8_n6hVP)c9O(^p3l><>tP2#~q`&xw+he z>1C@lW43}>GZ%w!`M5#BG>$`NpJBiUlkDFZ!6S&CMr*__B~~8@)TR}hBWEY}?M@D- z4szh78OqsNxjl0_IZ~diB=>I*ZqO8Gpb)1-9!O(Z7daZcKBz0yhwsN1g^gsaq%k#F zUzj^8r*~rYnLEl=UZV!!x%Eb;M+*6T7Nv4LpD(bFpTF`ft4+y#>eJ! zXM!12I>a)7+?a%8(TLcgM_3@3JBG}(`v}%CxuIOMavd0hW!sg=Gv+V|EN-mCfu3*7 zRjL*@PaLe|rVm-u)7GKEeR})U;J(Dv_Qc-7?b`?UCa0!GU`j*KE4eN^l@S{WOU7mc5?XA zCaZBUX967BVW=fo&Aeu2ixLFxfI-ak;Kq!%MFT&b3y1Dx{^s2E6S;6WL-fdlCs*E_ zzhNRegDS$&qh{`Wdl(wj{6E9J*p}8j4RTe27Dn?ZxJNd_xePmLWk@ZIW`a;-s0UlI zH$cQ0{1!!$*`DSmmifT9_nSt{DL-`a?CEm)H7wT6meObCqBQsbdxj1oeaP9Ja4Xq` z%%U8$W8>V;4G$z%Y$3jEB~E3&bOyTKOuu_*@18MT+PQbn=wNBSTA16Gtzdy_CS!L# zG(BD}Sr7YJJ+v#zbjP*X!{xl39>X7N7(u;PppA0+(UaAURSvT_rZOB>|8hGwY7WVm*olHuyBN>U6GC#`sfDiOct7s7}-Xo2UXh*`D|Oc!g=;gN8abDG7TWAeR8tTE3(Zx?&TA8Iy;l$7F*^9Bc!I_HM==SrvFtri_*dbM zZIU8ALS#OXz!RbohLy13E7vQ@dMGUr%Q=F{5u7y4wIZK{Sq$E@-&8laXAj0ocERmm zz#4jWEOy~2tn-dH13o1bycJ87f*+E@-A(7+gmM^(s{@B4=*#;Mlx5zEQy*^j6 zm-TvDgUbOJgSUyT`DKl>U-NQ= z2sj?dGoq)`Ji1jdkc0xE}_$OBx6J2g5!K8jW$4>XGlo9I>f(EN+C z2d+Ml-Iq@9ivTi22ZB>+AmpvzAAkAko}L8-Fvj&oJ= z2&>zAz2Agn2UmQ|Ak$Lgd?+>jH;E-Nmc5IT}$UpS4Y!^`LLM~~)b z-LIP!P0sU?ZTp-*3M;WV<};nVDw&x^mXsw9Y)34^Xb2-GJP#i9S;pZ$f?_ersRhKd z%o4M3Ok^E|8~5(Px})PGcv!F&I+V9$33y0EXg1Ym($6Vz+V5QMD0_I&<^x_l!&>4V zw8Pt&iNf@Zu>)F_kIAOiICp57bwXJ~z=c)3Eg6X&v9V{y*_tn|E(JBMPpUVk*msv? z_wiUZlPQl+?;K)Z;;tPNWh=97bi~R|yl!M)DLpzmk}mBVwb+BWqaY^}l+uS&spn6n zQhE>fH@%oMlK32!hP8V3=Ue2aWpdjH z=8W;l%C6FQQr$DBlgVr7vt{rspf%C=IRoc5eIX7~8(+vqPH^tYzK}(eb%hBcCEBLZ znLt%;%wmmmH_ND)U<1VrU?^-VdXkX>a<017e=OABL=ZkmW_L7nTqLI$i)j2!lKM zLShFH|DL*!SQvlmPD*6+XlyLHNnKn*C7KsXlISzCWi!P}B#2s*nm~}PICAH=--8^B zn1mK{IE$#L{KHxC|GSTFNycVoVyT&#lsuEz5uOrXo~|!4so7amQuvH$Job|v_2F&p&TWUvcWj-Pt3r%#mYy2=nsTTy<_tZaFfpe(Ur#XvglS zZ->~op0fUJXU;rz6 zaAsguW$WFBQ}1@}u$%o>%?(50<8IsaoNlLb)xoO{hf#Me*%irOS?qRve%#94j@PfX zDtG$*hSROw)NR;HzTZ1>=#b>FTdnTFdbd4LALtG=1|0M3u-;$~G&#Gv#Z3k76VkTk zyqlaZw;4FR-x;W&uZb3N_zCaa#rlO!YE*uOzLNYtaRRwK?|d90yIU?a)U(l5rUJt$*rYux^M&1HOXGT}q{ur7|k3 za!4WtRm2)pV$apE;?NTO(h%F2LmbGC>?^)06}40CQoGe2HKRDcOzlAvelw$swL&9 zd)2aPskZ8BRdrsisSE05>gDPc>bul;t5>R5saLD})N9mh)%U3H zRj*Uur(UnVAGf9l)Em?fs5hz~R6nGCSiMR8h`OlWtlpx2RJ~RGnEG+`Hua!-yLyLu zr+SzA3H5IE9`%#zz3QjbL+Yp1`_#{<_p6^(Kc{|P{et>{`bG6i>X+3A)rZuts1K`O zRS&C=s9#gRu6{%Prur@Q+v<1JN7WMzt6)tA&`>dWdc)nBQvsIRKOR$o(pqyAQXUHzT< zd-V_MAJsppe`bWgp}wj9xB6H0Z|eW3|EvC=`gipo>T&fg^`CkGjw)+d#xDssjug&` zmd@y`&as`Upo@A)*LUbU^zq^^$hE>euP-)34XxukY6n=r`ye&~MZ~sDDWRuzr*N5q(j=S-(a9sD7*d zG5zEEZTdm|cKr_hPW>+Z6Z+lyJ^Cm0d-YH0hxAYD_vxR}@7F)8e@_3r{ssL3{fqjS z^e^iV>JRB((I3{osvp)L(Z8mDUH^vuP5oQ?xApJnkLpMC$Mo;&-_sx0pU}UrKdC>Z zFX>11ALvi(&*(qYpVfb)Kd1j#|B3!n{b%~~`V0Ec^M!ZX^q2Kt>c7%o(O=bn zt-q%KM*pq;y8b);_xd07Kk9$d|E&K-e`6q7tJk~zjz3(h`_4+ux2p@TU0d#TosRDf z)*6i@@r72cvm9IKwi^6&ms9f7 zuDQ#Z1-DjTwtaai7r{22>82N4$6l=0JR7K6YXJF4pjFRy2N(LyRK3=+JB^x~187y9 z4lKy&G=osL?>8BwW?tT}dUnTi7(lyJU#j_4uSdmuLq>(b5V-vbt?+>doj?ZM^?AnwyX74*Ru;@pj|ufw4DnZ zVW(rl`7qtJ>37%8g^0E2v|4tf9EM=)O}iSzWjDt~0W&gD?f@aS<~DJO;8jyc-|@lc zn%hW&$2C{JKT&Tvz1|uTv^(e!f%1g=4burN1}Yl_`#lN7O(2D2VOP7Jmob2AuQ>K< zOyESlySQlEh$yXAtrwJPNx){tbvpuf-D;bhNyc}<>DUuI+ja+xalCrp6X7Kq(bDBf zx%DWw#=MI{{SMvPZLQc1DOQTh^X%0;uWF{cO+$kqcqv&VsZj6soK}}^X?4L_b|i*j zgGl8lhcb|_I((zupyHrZuhZ?y@JGRZy-pG9#r8Y3m73G4 zEwt?QfCwp(lRVXoa^XzPq@tig0m%a0l8RHNqC7G%OT zY$lita3O-2HEqJ{DgIoLvOy2!izt}ksI{v74z)Rpj@_7gs(h8p5~T^7-X3|<40|q% zUF>%ZzHEAR{J+gKeOXGY+uZc#@ROxq{vG{}*YEYZE@PMr-k=9SDm#3ikneQ*kRR!} z{CXA>xgHEgr5L0NN(XpR3L;t{e05>1D$h(1)vNWr2zG6^-zf+2LLX|bUtM%-Z98mG z7&jW_pfaXd)UdH2n%C;f=B*I-!^UjHhc%n>LyYizyH{P{#m!;3VmJISlMWT$oGch< zMRj(2aRXABw+usBCm(#yic_bRe1==At~d?5%jay0i$d-0g443ChO@X>g%{h6tO1>7 z2Rg}^NIaCopkWy5w|uA9vV)AS-KI(5Tdv)q!bZlt<-r45c1+-;j0tlkYi5ndj0D5O zK)^?0l9H1jC{?Sr##g6cc}~xBysX{s`D@ixrvoF;7&;N4L)^eg`+U9CZ`hW7zE|rs zWX2@`!KtEs-WRR~Mg_B*EK!7IuZ6s$F$kD~el}z%GEj7|#p%@CwX}hS!deF6)0|q$ zfW5|I-T+>If$r(I?9m{&)&|X%p{p*0sq3x{ZHbmZP{0MJ7n4w2e!LvNK2iY>7*Zk# z#THw&W}Kh4onD0Lo6$`g2p%ZUoc!=wKyLG4sO2C7xNA{jw`X^xOp`U%q_kKQUKDFO zh(b*`FsvPV<`d!hR#V6t)ZCFY12RB%THr<7uP+%0q-xq@64&b^Hn{e=J`*Qk#f>M4 zN8a=8k<0Qox*dD;@>pRqJ1&cEcB_83icHaI+TPS<$sKz&N-RDxu^p7|n^D)CGG1=F3d%_!9Jr2t8Agdz*O3B$0cdl}|L5R5ll-330=G*nQT2|(W( zWVeD?8SHETdW=DRDcN*u3kytXp1q~2h&>SqwaNvt%pD)>h?@{^AV1jdP}A-9As=gW z|6)F{Qie#7!4#iC%xx|=)g|Q_>rNUCzbuK_q;Vmv08|Ra{e%$FSbk8T4TrlvM z(^>2SBOdxm9zDJ;<9HqfxFN%m4`A00k;(+UghP=|J#nv4#v0K$3)6kZ<27=4*mY93{p6@KKZOtZ) zC~nDylvdgjVcL`392K?NgJMTt!o069N1wSRfdZrEZjK)hON`=7HKvp&79XTgO!Pfl zUWr=I$!E1tRjS?+#49N>G;=Y9 z#91=}v51}l3yBGsXxEw@+fTO<4Vg`~Mob{9UF(IDsVrcG%VAu0bKD?-l&E5&Y7VyR zy>#1dGNB=GNfC@;K{3IvxS^a@+o^}83<9Rx!UBBqCfbf@ZP_-=3p6&fB_{l}*bbR) z$OXN4yYD&mSiA4rc|-cvrTvryh0a(8FjPMYDAJGB=~hE9hWu;~$&YA}RiELatL;=2 zlj_307Hf4oD^nd|+^8GbuG_42oC~HK6QDv5Z0Nx@5sFj28nR2h*2x$ZARt3Afi1|D zOe8W12zd}uBN2e{o>S{|(>(`v%xL+8J*Si9KRipgsK1s70vX!3Rtn>iW= zv0|u2Nlk{$EcRT~K2cVy-nqV@KuD?M@GH+w^wxZaCS|x5LlZyOrynvdSR%B0732d8 zj4bSB;NzfF6|&@ILpV_Lf{^gukh6oXkp!ekOL5n3+UE-*9%1o79zg}O-9!*J zj}>aP`4Gf3L>AsNFz>;kD6j{!Yk?C1Xm~HB5sNLES9*PKN!~YM^r!$1x{B5lFRVF& zyd~P%>r$`CMRB(a;ZL}rzU`OXE}B#*Is{}rvnguO^}LwdUsy{cB?nZyXdw80qz)y+ z2$LnC;t~@jTM{s$nwRowjEQ`10C+KZW<2B%kZh3_=R%lyjKrS}xFjhTF0AFlKuAe4 zQBW)?0X&S6q9I~J*zF)3JWV1?21(qi-C1!kHprZ$IPk_5JyUcdWgpe11a-l79>2G!fSD_gKeka(Zw=h>NYI3Zs)yjUq(zc2DjVF zN0yUdf+WLW(d)I4QZ`sL`6MEuIw=qt;$XKd(IBdZx~2^%y%s=Awbo)r3`^lO$$5Rq zNuMrRMSjV8Yi(>tRcur>`Z5SG`7um%qO>};CvQCn_90gEeTa99v3mRd&LRhRYf|(Vy25^VfZJ{jVJOOvT+16YCdvRJki}n%nk_Pt8x`htMC$URW_Z2} zl0-!j>eqcteNj;>HP;a%14VV})liv=Ng$5!)3OJlg$c&7Y5OVU(q&<4X%>NORM(!J zqfwaQ5YsRuEM?b2^>(AM9zZ^TaOT&;(D(lN@_Mi*WXxMahuSF3Z}-n{6wqJ5DBDj( zZ&3Yt7%RrJl)nTo=JPCniFrvQTyF^Zx4qyAdkZ-iiBU`;0Fg!moTdOt>i6P4=;g(I zm(R%2W618IId>x@Vp4uR%?Ko!^flTO$*F z1lm@k-0xf_E|X-Kf4?JK)Y`DN7{ZtH(zG diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index bcac789ddd8..48292758fa6 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -82,7 +82,6 @@ export const codiconsLibrary = { vm: register('vm', 0xea7a), deviceDesktop: register('device-desktop', 0xea7a), file: register('file', 0xea7b), - fileText: register('file-text', 0xea7b), more: register('more', 0xea7c), ellipsis: register('ellipsis', 0xea7c), kebabHorizontal: register('kebab-horizontal', 0xea7c), @@ -616,4 +615,13 @@ export const codiconsLibrary = { collection: register('collection', 0xec57), newCollection: register('new-collection', 0xec58), thinking: register('thinking', 0xec59), + build: register('build', 0xec5a), + commentDiscussionQuote: register('comment-discussion-quote', 0xec5b), + cursor: register('cursor', 0xec5c), + eraser: register('eraser', 0xec5d), + fileText: register('file-text', 0xec5e), + gitLens: register('git-lens', 0xec5f), + quotes: register('quotes', 0xec60), + rename: register('rename', 0xec61), + runWithDeps: register('run-with-deps', 0xec62), } as const; From 3f8450af13fdd04df720841ff0e6d23773438d41 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 7 Oct 2025 14:53:53 +0200 Subject: [PATCH 0901/4355] fix any --- src/vs/base/browser/ui/tree/abstractTree.ts | 77 +++++++++---------- src/vs/base/browser/ui/tree/asyncDataTree.ts | 30 ++++---- src/vs/base/browser/ui/tree/dataTree.ts | 2 +- src/vs/base/browser/ui/tree/indexTreeModel.ts | 3 +- src/vs/base/browser/ui/tree/objectTree.ts | 4 +- .../preferences/browser/settingsTree.ts | 2 +- 6 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 9e52df92023..56e01ac37ef 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -168,12 +168,10 @@ function asListOptions(modelProvider: () => ITreeModel | IListTouchEvent); }, isSelectionRangeChangeEvent(e) { - // eslint-disable-next-line local/code-no-any-casts - return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element }! as IListMouseEvent | IListTouchEvent); } }, accessibilityProvider: options.accessibilityProvider && { @@ -821,7 +819,7 @@ class FindWidget extends Disposable { constructor( container: HTMLElement, - private tree: AbstractTree, + private tree: AbstractTree, contextViewProvider: IContextViewProvider, placeholder: string, toggleContributions: ITreeFindToggleContribution[] = [], @@ -987,7 +985,7 @@ export abstract class AbstractFindController implements IDisposa protected readonly disposables = new DisposableStore(); constructor( - protected tree: AbstractTree, + protected tree: AbstractTree, protected filter: IFindFilter, protected readonly contextViewProvider: IContextViewProvider, protected readonly options: IAbstractFindControllerOptions = {} @@ -1129,7 +1127,7 @@ export class FindController extends AbstractFindController, + tree: AbstractTree, protected override filter: FindFilter, contextViewProvider: IContextViewProvider, options: IFindControllerOptions = {} @@ -1183,8 +1181,7 @@ export class FindController extends AbstractFindController !FuzzyScore.isDefault(node.filterData as any as FuzzyScore)); + this.tree.focusNext(0, true, undefined, (node) => !FuzzyScore.isDefault(node.filterData as unknown as FuzzyScore)); } const focus = this.tree.getFocus(); @@ -1209,8 +1206,7 @@ export class FindController extends AbstractFindController extends Disposable { private readonly tree: AbstractTree, private readonly model: ITreeModel, private readonly view: List>, - renderers: TreeRenderer[], + renderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, options: IAbstractTreeOptions = {}, ) { @@ -1658,7 +1654,7 @@ class StickyScrollWidget implements IDisposable { container: HTMLElement, private readonly view: List>, private readonly tree: AbstractTree, - private readonly treeRenderers: TreeRenderer[], + private readonly treeRenderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, private readonly accessibilityProvider: IListAccessibilityProvider | undefined, ) { @@ -2144,7 +2140,7 @@ class StickyScrollFocus extends Disposable { } } -function asTreeMouseEvent(event: IListMouseEvent>): ITreeMouseEvent { +function asTreeMouseEvent(event: IListMouseEvent>): ITreeMouseEvent { let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown; if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-twistie', 'monaco-tl-row')) { @@ -2162,7 +2158,7 @@ function asTreeMouseEvent(event: IListMouseEvent>): ITreeMo }; } -function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { +function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { const isStickyScroll = isStickyScrollContainer(event.browserEvent.target as HTMLElement); return { @@ -2186,7 +2182,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly mouseWheelScrollSensitivity?: number; readonly fastScrollSensitivity?: number; readonly expandOnDoubleClick?: boolean; - readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T + readonly expandOnlyOnTwistieClick?: boolean | ((e: unknown) => boolean); // e is the tree element (T) readonly enableStickyScroll?: boolean; readonly stickyScrollMaxItemCount?: number; readonly paddingTop?: number; @@ -2202,7 +2198,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly findWidgetEnabled?: boolean; readonly findWidgetStyles?: IFindWidgetStyles; readonly defaultFindVisibility?: TreeVisibility | ((e: T) => TreeVisibility); - readonly stickyScrollDelegate?: IStickyScrollDelegate; + readonly stickyScrollDelegate?: IStickyScrollDelegate; readonly disableExpandOnSpacebar?: boolean; // defaults to false } @@ -2217,14 +2213,14 @@ function dfs(node: ITreeNode, fn: (node: ITreeNo */ class Trait { - private nodes: ITreeNode[] = []; + private nodes: ITreeNode[] = []; private elements: T[] | undefined; private readonly _onDidChange = new Emitter>(); readonly onDidChange = this._onDidChange.event; - private _nodeSet: Set> | undefined; - private get nodeSet(): Set> { + private _nodeSet: Set> | undefined; + private get nodeSet(): Set> { if (!this._nodeSet) { this._nodeSet = this.createNodeSet(); } @@ -2233,20 +2229,19 @@ class Trait { } constructor( - private getFirstViewElementWithTrait: () => ITreeNode | undefined, + private getFirstViewElementWithTrait: () => ITreeNode | undefined, private identityProvider?: IIdentityProvider ) { } - set(nodes: ITreeNode[], browserEvent?: UIEvent): void { - // eslint-disable-next-line local/code-no-any-casts - if (!(browserEvent as any)?.__forceEvent && equals(this.nodes, nodes)) { + set(nodes: ITreeNode[], browserEvent?: UIEvent): void { + if (!((browserEvent as UIEvent & { __forceEvent?: boolean })?.__forceEvent) && equals(this.nodes, nodes)) { return; } this._set(nodes, false, browserEvent); } - private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { + private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { this.nodes = [...nodes]; this.elements = undefined; this._nodeSet = undefined; @@ -2265,32 +2260,32 @@ class Trait { return [...this.elements]; } - getNodes(): readonly ITreeNode[] { + getNodes(): readonly ITreeNode[] { return this.nodes; } - has(node: ITreeNode): boolean { + has(node: ITreeNode): boolean { return this.nodeSet.has(node); } - onDidModelSplice({ insertedNodes, deletedNodes }: ITreeModelSpliceEvent): void { + onDidModelSplice({ insertedNodes, deletedNodes }: ITreeModelSpliceEvent): void { if (!this.identityProvider) { const set = this.createNodeSet(); - const visit = (node: ITreeNode) => set.delete(node); + const visit = (node: ITreeNode) => set.delete(node); deletedNodes.forEach(node => dfs(node, visit)); this.set([...set.values()]); return; } const deletedNodesIdSet = new Set(); - const deletedNodesVisitor = (node: ITreeNode) => deletedNodesIdSet.add(this.identityProvider!.getId(node.element).toString()); + const deletedNodesVisitor = (node: ITreeNode) => deletedNodesIdSet.add(this.identityProvider!.getId(node.element).toString()); deletedNodes.forEach(node => dfs(node, deletedNodesVisitor)); - const insertedNodesMap = new Map>(); - const insertedNodesVisitor = (node: ITreeNode) => insertedNodesMap.set(this.identityProvider!.getId(node.element).toString(), node); + const insertedNodesMap = new Map>(); + const insertedNodesVisitor = (node: ITreeNode) => insertedNodesMap.set(this.identityProvider!.getId(node.element).toString(), node); insertedNodes.forEach(node => dfs(node, insertedNodesVisitor)); - const nodes: ITreeNode[] = []; + const nodes: ITreeNode[] = []; for (const node of this.nodes) { const id = this.identityProvider.getId(node.element).toString(); @@ -2318,8 +2313,8 @@ class Trait { this._set(nodes, true); } - private createNodeSet(): Set> { - const set = new Set>(); + private createNodeSet(): Set> { + const set = new Set>(); for (const node of this.nodes) { set.add(node); @@ -2472,7 +2467,7 @@ class TreeNodeList extends List> user: string, container: HTMLElement, virtualDelegate: IListVirtualDelegate>, - renderers: IListRenderer[], + renderers: IListRenderer, unknown>[], private focusTrait: Trait, private selectionTrait: Trait, private anchorTrait: Trait, @@ -2560,7 +2555,7 @@ export const enum AbstractTreePart { export abstract class AbstractTree implements IDisposable { protected view: TreeNodeList; - private renderers: TreeRenderer[]; + private renderers: TreeRenderer[]; protected model: ITreeModel; private treeDelegate: ComposedTreeDelegate>; private focus: Trait; @@ -2632,7 +2627,7 @@ export abstract class AbstractTree implements IDisposable private readonly _user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private _options: IAbstractTreeOptions = {} ) { if (_options.keyboardNavigationLabelProvider && (_options.findWidgetEnabled ?? true)) { @@ -2646,7 +2641,7 @@ export abstract class AbstractTree implements IDisposable const activeNodes = this.disposables.add(new EventCollection(this.onDidChangeActiveNodesRelay.event)); const renderedIndentGuides = new SetMap, HTMLDivElement>(); - this.renderers = renderers.map(r => new TreeRenderer(r, this.model, this.onDidChangeCollapseStateRelay.event, activeNodes, renderedIndentGuides, _options)); + this.renderers = renderers.map(r => new TreeRenderer(r, this.model, this.onDidChangeCollapseStateRelay.event, activeNodes, renderedIndentGuides, _options)); for (const r of this.renderers) { this.disposables.add(r); } @@ -3208,11 +3203,11 @@ export abstract class AbstractTree implements IDisposable const set = new Set>(); for (const node of this.focus.getNodes()) { - set.add(node); + set.add(node as ITreeNode); } for (const node of this.selection.getNodes()) { - set.add(node); + set.add(node as ITreeNode); } activeNodesEmitter.fire([...set.values()]); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 66a71e33c7f..cdb1d672a14 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from '../list/list.js'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListTouchEvent, IListVirtualDelegate } from '../list/list.js'; import { ElementsDragAndDropData, ListViewTargetSector } from '../list/listView.js'; import { IListStyles } from '../list/listWidget.js'; -import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, FindFilter, FindController, ITreeFindToggleChangeEvent, IFindControllerOptions } from './abstractTree.js'; +import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, FindFilter, FindController, ITreeFindToggleChangeEvent, IFindControllerOptions, IStickyScrollDelegate, AbstractTree } from './abstractTree.js'; import { ICompressedTreeElement, ICompressedTreeNode } from './compressedObjectTreeModel.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from './objectTree.js'; @@ -302,8 +302,7 @@ class AsyncFindController extends FindController, TFilterData>, ) { - // eslint-disable-next-line local/code-no-any-casts - super(tree as any, filter, contextViewProvider, options); + super(tree as unknown as AbstractTree, filter, contextViewProvider, options); // Always make sure to end the session before disposing this.disposables.add(toDisposable(async () => { if (this.activeSession) { @@ -414,8 +413,7 @@ class AsyncFindController extends FindController(options?: IAsyncDataTreeOpt dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd), multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { - // eslint-disable-next-line local/code-no-any-casts - return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); + return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element }! as IListMouseEvent | IListTouchEvent); }, isSelectionRangeChangeEvent(e) { - // eslint-disable-next-line local/code-no-any-casts - return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element }! as IListMouseEvent | IListTouchEvent); } }, accessibilityProvider: options.accessibilityProvider && { @@ -477,10 +473,10 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt sorter: undefined, expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : ( typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( - e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) + ((e: IAsyncDataTreeNode) => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T)) as ((e: unknown) => boolean) ) ), - defaultFindVisibility: e => { + defaultFindVisibility: (e: IAsyncDataTreeNode) => { if (e.hasChildren && e.stale) { return TreeVisibility.Visible; } else if (typeof options.defaultFindVisibility === 'number') { @@ -490,11 +486,10 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt } else { return (options.defaultFindVisibility as ((e: T) => TreeVisibility))(e.element as T); } - } + }, + stickyScrollDelegate: options.stickyScrollDelegate as IStickyScrollDelegate, TFilterData> | undefined }; -} - -export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } +} export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } export interface IAsyncDataTreeUpdateChildrenOptions extends IObjectTreeSetChildrenOptions { } export interface IAsyncDataTreeOptions extends IAsyncDataTreeOptionsUpdate, Pick, Exclude, 'collapseByDefault'>> { @@ -1481,7 +1476,8 @@ function asCompressibleObjectTreeOptions(options?: IComp getCompressedNodeKeyboardNavigationLabel(els) { return options.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T)); } - } + }, + stickyScrollDelegate: objectTreeOptions.stickyScrollDelegate as IStickyScrollDelegate, TFilterData> | undefined }; } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 09eec7634ba..5acc18dc7a2 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -167,7 +167,7 @@ export class DataTree extends AbstractTree): ITreeModel { + protected createModel(user: string, options: IDataTreeOptions): ITreeModel { return new ObjectTreeModel(user, options); } } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 46ceb3fc696..a7a2f738145 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -86,8 +86,7 @@ interface CollapsedStateUpdate { type CollapseStateUpdate = CollapsibleStateUpdate | CollapsedStateUpdate; function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is CollapsibleStateUpdate { - // eslint-disable-next-line local/code-no-any-casts - return typeof (update as any).collapsible === 'boolean'; + return 'collapsible' in update; } export class IndexTreeModel, TFilterData = void> implements ITreeModel { diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index f554eca9a88..7b603cf35fe 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -81,7 +81,7 @@ export class ObjectTree, TFilterData = void> extends return this.model.has(element); } - protected createModel(user: string, options: IObjectTreeOptions): ITreeModel { + protected createModel(user: string, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(user, options); } } @@ -297,7 +297,7 @@ export class CompressibleObjectTree, TFilterData = vo this.model.setChildren(element, children, options); } - protected override createModel(user: string, options: ICompressibleObjectTreeOptions): ITreeModel { + protected override createModel(user: string, options: ICompressibleObjectTreeOptions): ITreeModel { return new CompressibleObjectTreeModel(user, options); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 26efc9fc90c..cda89c19f68 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -2619,7 +2619,7 @@ export class SettingsTree extends WorkbenchObjectTree { })); } - protected override createModel(user: string, options: IObjectTreeOptions): ITreeModel { + protected override createModel(user: string, options: IObjectTreeOptions): ITreeModel { return new NonCollapsibleObjectTreeModel(user, options); } } From 626370b3f7c33fc452bef393590c324c18aa54ba Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Oct 2025 15:07:24 +0200 Subject: [PATCH 0902/4355] no more any-casts in `tsb` land, https://github.com/microsoft/vscode/issues/269213 (#270192) --- build/lib/tsb/builder.js | 7 +------ build/lib/tsb/builder.ts | 32 +++++++++++++++----------------- build/lib/tsb/index.js | 2 -- build/lib/tsb/index.ts | 6 ++---- build/lib/util.js | 5 ++--- build/lib/util.ts | 9 ++++----- 6 files changed, 24 insertions(+), 37 deletions(-) diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 47e23763965..1c0640a11e7 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -75,7 +75,6 @@ function createTypeScriptBuilder(config, projectFile, cmd) { host.getCompilationSettings().declaration = true; function file(file) { // support gulp-sourcemaps - // eslint-disable-next-line local/code-no-any-casts if (file.sourceMap) { emitSourceMapsInStream = false; } @@ -96,8 +95,7 @@ function createTypeScriptBuilder(config, projectFile, cmd) { } } function isExternalModule(sourceFile) { - // eslint-disable-next-line local/code-no-any-casts - return sourceFile.externalModuleIndicator + return !!sourceFile.externalModuleIndicator || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); } function build(out, onError, token = CancellationToken.None) { @@ -221,7 +219,6 @@ function createTypeScriptBuilder(config, projectFile, cmd) { if (didChange) { [tsSMC, inputSMC].forEach((consumer) => { consumer.sources.forEach((sourceFile) => { - // eslint-disable-next-line local/code-no-any-casts smg._sources.add(sourceFile); const sourceContent = consumer.sourceContentFor(sourceFile); if (sourceContent !== null) { @@ -237,7 +234,6 @@ function createTypeScriptBuilder(config, projectFile, cmd) { // }); } } - // eslint-disable-next-line local/code-no-any-casts vinyl.sourceMap = sourceMap; } } @@ -521,7 +517,6 @@ class LanguageServiceHost { let result = this._snapshots[filename]; if (!result && resolve) { try { - // eslint-disable-next-line local/code-no-any-casts result = new VinylScriptSnapshot(new vinyl_1.default({ path: filename, contents: fs_1.default.readFileSync(filename), diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 6defdcd1b5d..64081ac4797 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -59,8 +59,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str function file(file: Vinyl): void { // support gulp-sourcemaps - // eslint-disable-next-line local/code-no-any-casts - if ((file).sourceMap) { + if (file.sourceMap) { emitSourceMapsInStream = false; } @@ -81,8 +80,10 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } function isExternalModule(sourceFile: ts.SourceFile): boolean { - // eslint-disable-next-line local/code-no-any-casts - return (sourceFile).externalModuleIndicator + interface SourceFileWithModuleIndicator extends ts.SourceFile { + externalModuleIndicator?: unknown; + } + return !!(sourceFile as SourceFileWithModuleIndicator).externalModuleIndicator || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); } @@ -221,18 +222,19 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str if (didChange) { + interface SourceMapGeneratorWithSources extends SourceMapGenerator { + _sources: { add(source: string): void }; + } + [tsSMC, inputSMC].forEach((consumer) => { - (consumer).sources.forEach((sourceFile: any) => { - // eslint-disable-next-line local/code-no-any-casts - (smg)._sources.add(sourceFile); + (consumer).sources.forEach((sourceFile: string) => { + (smg as SourceMapGeneratorWithSources)._sources.add(sourceFile); const sourceContent = consumer.sourceContentFor(sourceFile); if (sourceContent !== null) { smg.setSourceContent(sourceFile, sourceContent); } }); - }); - - sourceMap = JSON.parse(smg.toString()); + }); sourceMap = JSON.parse(smg.toString()); // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { @@ -242,12 +244,9 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } } - // eslint-disable-next-line local/code-no-any-casts - (vinyl).sourceMap = sourceMap; + (vinyl as Vinyl & { sourceMap?: RawSourceMap }).sourceMap = sourceMap; } - } - - files.push(vinyl); + } files.push(vinyl); } resolve({ @@ -590,8 +589,7 @@ class LanguageServiceHost implements ts.LanguageServiceHost { let result = this._snapshots[filename]; if (!result && resolve) { try { - // eslint-disable-next-line local/code-no-any-casts - result = new VinylScriptSnapshot(new Vinyl({ + result = new VinylScriptSnapshot(new Vinyl({ path: filename, contents: fs.readFileSync(filename), base: this.getCompilationSettings().outDir, diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index 67c82c8bb9e..af10bf8ce19 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -134,12 +134,10 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) const transpiler = !config.transpileWithEsbuild ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) : new transpiler_1.ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); - // eslint-disable-next-line local/code-no-any-casts result = (() => createTranspileStream(transpiler)); } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - // eslint-disable-next-line local/code-no-any-casts result = ((token) => createCompileStream(_builder, token)); } result.src = (opts) => { diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 1157ca6be9d..165ad1ce3b8 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -131,12 +131,10 @@ export function create( const transpiler = !config.transpileWithEsbuild ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); - // eslint-disable-next-line local/code-no-any-casts - result = (() => createTranspileStream(transpiler)); + result = (() => createTranspileStream(transpiler)) as IncrementalCompiler; } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - // eslint-disable-next-line local/code-no-any-casts - result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); + result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)) as IncrementalCompiler; } result.src = (opts?: { cwd?: string; base?: string }) => { diff --git a/build/lib/util.js b/build/lib/util.js index e1c162040dc..fc392a12699 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -116,8 +116,8 @@ function fixWin32DirectoryPermissions() { function setExecutableBit(pattern) { const setBit = event_stream_1.default.mapSync(f => { if (!f.stat) { - // eslint-disable-next-line local/code-no-any-casts - f.stat = { isFile() { return true; } }; + const stat = { isFile() { return true; }, mode: 0 }; + f.stat = stat; } f.stat.mode = /* 100755 */ 33261; return f; @@ -289,7 +289,6 @@ function rebase(count) { }); } function filter(fn) { - // eslint-disable-next-line local/code-no-any-casts const result = event_stream_1.default.through(function (data) { if (fn(data)) { this.emit('data', data); diff --git a/build/lib/util.ts b/build/lib/util.ts index f193f2c865d..1c47418297b 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -129,8 +129,8 @@ export function fixWin32DirectoryPermissions(): NodeJS.ReadWriteStream { export function setExecutableBit(pattern?: string | string[]): NodeJS.ReadWriteStream { const setBit = es.mapSync(f => { if (!f.stat) { - // eslint-disable-next-line local/code-no-any-casts - f.stat = { isFile() { return true; } } as any; + const stat: Pick = { isFile() { return true; }, mode: 0 }; + f.stat = stat as fs.Stats; } f.stat!.mode = /* 100755 */ 33261; return f; @@ -354,14 +354,13 @@ export interface FilterStream extends NodeJS.ReadWriteStream { } export function filter(fn: (data: any) => boolean): FilterStream { - // eslint-disable-next-line local/code-no-any-casts - const result = es.through(function (data) { + const result = es.through(function (data) { if (fn(data)) { this.emit('data', data); } else { result.restore.push(data); } - }); + }) as unknown as FilterStream; result.restore = es.through(); return result; From aa85a08e6b4352c32a62b70b6b5699c5f84e5fe6 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:09:55 +0200 Subject: [PATCH 0903/4355] Update src/vs/base/browser/ui/tree/abstractTree.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/base/browser/ui/tree/abstractTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 56e01ac37ef..2c4afb452a9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -2234,7 +2234,8 @@ class Trait { ) { } set(nodes: ITreeNode[], browserEvent?: UIEvent): void { - if (!((browserEvent as UIEvent & { __forceEvent?: boolean })?.__forceEvent) && equals(this.nodes, nodes)) { + const event = browserEvent as UIEvent & { __forceEvent?: boolean }; + if (!(event?.__forceEvent) && equals(this.nodes, nodes)) { return; } From 71b9266955277ebb24d08232fa279898293be114 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:10:51 +0200 Subject: [PATCH 0904/4355] Update src/vs/base/browser/ui/tree/asyncDataTree.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index cdb1d672a14..ba324c8278a 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -489,7 +489,8 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt }, stickyScrollDelegate: options.stickyScrollDelegate as IStickyScrollDelegate, TFilterData> | undefined }; -} export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } +} +export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } export interface IAsyncDataTreeUpdateChildrenOptions extends IObjectTreeSetChildrenOptions { } export interface IAsyncDataTreeOptions extends IAsyncDataTreeOptionsUpdate, Pick, Exclude, 'collapseByDefault'>> { From fbecdfc17378c25064e16942095ddc9418146f2d Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 7 Oct 2025 15:24:33 +0200 Subject: [PATCH 0905/4355] :lipstick: --- src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 2c4afb452a9..d00b38b2b7a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -2182,7 +2182,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly mouseWheelScrollSensitivity?: number; readonly fastScrollSensitivity?: number; readonly expandOnDoubleClick?: boolean; - readonly expandOnlyOnTwistieClick?: boolean | ((e: unknown) => boolean); // e is the tree element (T) + readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is the tree element (T) readonly enableStickyScroll?: boolean; readonly stickyScrollMaxItemCount?: number; readonly paddingTop?: number; From 0a042bf3f8b3e65d6f98500af056cb860beeedb5 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 7 Oct 2025 14:38:01 +0100 Subject: [PATCH 0906/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118456 -> 118456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 11318b0fb7d27510ff53e5f83addedb1c4081236..976c79594a0d43bdb886a6d164e9a11313b82ca9 100644 GIT binary patch delta 41 zcmV+^0M`Gwod>v`2aury`?*h%q(uWQ*iC`#wd?`uumLrf*0BMN0X4T|vH?^ap>-0y delta 41 zcmV+^0M`Gwod>v`2aury`ngV#q(uWS*iV7%wd?`uumLoe*0BMN0W`N{vH?^ap@I^< From 38f88425449822223995039968910694e6c921f8 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 7 Oct 2025 14:40:25 +0100 Subject: [PATCH 0907/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118456 -> 118456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 976c79594a0d43bdb886a6d164e9a11313b82ca9..11318b0fb7d27510ff53e5f83addedb1c4081236 100644 GIT binary patch delta 41 zcmV+^0M`Gwod>v`2aury`ngV#q(uWS*iV7%wd?`uumLoe*0BMN0W`N{vH?^ap@I^< delta 41 zcmV+^0M`Gwod>v`2aury`?*h%q(uWQ*iC`#wd?`uumLrf*0BMN0X4T|vH?^ap>-0y From d4af723cd190ff94e3af7f503b219d4c6ca03044 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 7 Oct 2025 14:45:19 +0100 Subject: [PATCH 0908/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118456 -> 118456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 11318b0fb7d27510ff53e5f83addedb1c4081236..976c79594a0d43bdb886a6d164e9a11313b82ca9 100644 GIT binary patch delta 41 zcmV+^0M`Gwod>v`2aury`?*h%q(uWQ*iC`#wd?`uumLrf*0BMN0X4T|vH?^ap>-0y delta 41 zcmV+^0M`Gwod>v`2aury`ngV#q(uWS*iV7%wd?`uumLoe*0BMN0W`N{vH?^ap@I^< From aabd4ec6a711298beffb4b06d5eaf6db88cce67a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Oct 2025 16:42:38 +0200 Subject: [PATCH 0909/4355] no more any-casts in `propertyInitOrderChecker`, https://github.com/microsoft/vscode/issues/269213 (#270198) --- build/lib/propertyInitOrderChecker.js | 11 ++++------ build/lib/propertyInitOrderChecker.ts | 29 +++++++++++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/build/lib/propertyInitOrderChecker.js b/build/lib/propertyInitOrderChecker.js index a1da1835bd9..a8dab0f29fe 100644 --- a/build/lib/propertyInitOrderChecker.js +++ b/build/lib/propertyInitOrderChecker.js @@ -229,16 +229,13 @@ function* findAllReferencesInClass(node) { } } } -// NOTE: The following uses TypeScript internals and are subject to change from version to version. function findAllReferences(node) { const sourceFile = node.getSourceFile(); const position = node.getStart(); - // eslint-disable-next-line local/code-no-any-casts - const name = ts.getTouchingPropertyName(sourceFile, position); - // eslint-disable-next-line local/code-no-any-casts - const options = { use: ts.FindAllReferences.FindReferencesUse.References }; - // eslint-disable-next-line local/code-no-any-casts - return ts.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; + const tsInternal = ts; + const name = tsInternal.getTouchingPropertyName(sourceFile, position); + const options = { use: tsInternal.FindAllReferences.FindReferencesUse.References }; + return tsInternal.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; } var DefinitionKind; (function (DefinitionKind) { diff --git a/build/lib/propertyInitOrderChecker.ts b/build/lib/propertyInitOrderChecker.ts index c5254884899..eab53477e11 100644 --- a/build/lib/propertyInitOrderChecker.ts +++ b/build/lib/propertyInitOrderChecker.ts @@ -248,15 +248,32 @@ function* findAllReferencesInClass(node: ts.Node): Generator { // NOTE: The following uses TypeScript internals and are subject to change from version to version. +interface TypeScriptInternals { + getTouchingPropertyName(sourceFile: ts.SourceFile, position: number): ts.Node; + FindAllReferences: { + FindReferencesUse: { + References: number; + }; + Core: { + getReferencedSymbolsForNode( + position: number, + node: ts.Node, + program: ts.Program, + sourceFiles: readonly ts.SourceFile[], + cancellationToken: ts.CancellationToken, + options: { use: number } + ): readonly SymbolAndEntries[] | undefined; + }; + }; +} + function findAllReferences(node: ts.Node): readonly SymbolAndEntries[] { const sourceFile = node.getSourceFile(); const position = node.getStart(); - // eslint-disable-next-line local/code-no-any-casts - const name: ts.Node = (ts as any).getTouchingPropertyName(sourceFile, position); - // eslint-disable-next-line local/code-no-any-casts - const options = { use: (ts as any).FindAllReferences.FindReferencesUse.References }; - // eslint-disable-next-line local/code-no-any-casts - return (ts as any).FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; + const tsInternal = ts as unknown as TypeScriptInternals; + const name: ts.Node = tsInternal.getTouchingPropertyName(sourceFile, position); + const options = { use: tsInternal.FindAllReferences.FindReferencesUse.References }; + return tsInternal.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; } interface SymbolAndEntries { From dfbe620ed95f9b08742a6c049f4821faf1d387e3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 7 Oct 2025 10:56:54 -0400 Subject: [PATCH 0910/4355] Cleanup any usage (#270203) --- .vscode/searches/no-any-casts.code-search | 822 ++++++++---------- .../browser/forwardingTelemetryService.ts | 3 +- .../test/browser/telemetryService.test.ts | 30 +- .../api/browser/mainThreadTelemetry.ts | 3 +- 4 files changed, 377 insertions(+), 481 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index 8fe495b9315..6b76b6156e0 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,46 +1,46 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -990 results - 368 files +925 results - 351 files -vscode • build/azure-pipelines/upload-cdn.ts: +build/azure-pipelines/upload-cdn.ts: 115: // eslint-disable-next-line local/code-no-any-casts -vscode • build/azure-pipelines/common/publish.ts: +build/azure-pipelines/common/publish.ts: 524: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/compilation.ts: +build/lib/compilation.ts: 153: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/extensions.ts: +build/lib/extensions.ts: 120: // eslint-disable-next-line local/code-no-any-casts 219: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/monaco-api.ts: +build/lib/monaco-api.ts: 221: // eslint-disable-next-line local/code-no-any-casts 328: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/nls.ts: +build/lib/nls.ts: 43: // eslint-disable-next-line local/code-no-any-casts 507: // eslint-disable-next-line local/code-no-any-casts 513: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/optimize.ts: +build/lib/optimize.ts: 254: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/propertyInitOrderChecker.ts: +build/lib/propertyInitOrderChecker.ts: 254: // eslint-disable-next-line local/code-no-any-casts 256: // eslint-disable-next-line local/code-no-any-casts 258: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/reporter.ts: +build/lib/reporter.ts: 108: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts 117: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/task.ts: +build/lib/task.ts: 27: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/treeshaking.ts: +build/lib/treeshaking.ts: 310: // eslint-disable-next-line local/code-no-any-casts 314: // eslint-disable-next-line local/code-no-any-casts 318: // eslint-disable-next-line local/code-no-any-casts @@ -50,65 +50,50 @@ vscode • build/lib/treeshaking.ts: 922: // eslint-disable-next-line local/code-no-any-casts 924: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/util.ts: - 132: // eslint-disable-next-line local/code-no-any-casts - 357: // eslint-disable-next-line local/code-no-any-casts - -vscode • build/lib/mangle/index.ts: +build/lib/mangle/index.ts: 273: // eslint-disable-next-line local/code-no-any-casts -vscode • build/lib/tsb/builder.ts: - 62: // eslint-disable-next-line local/code-no-any-casts - 84: // eslint-disable-next-line local/code-no-any-casts - 226: // eslint-disable-next-line local/code-no-any-casts - 245: // eslint-disable-next-line local/code-no-any-casts - 593: // eslint-disable-next-line local/code-no-any-casts - -vscode • build/lib/tsb/index.ts: - 134: // eslint-disable-next-line local/code-no-any-casts - 138: // eslint-disable-next-line local/code-no-any-casts - -vscode • build/lib/watch/watch-win32.ts: +build/lib/watch/watch-win32.ts: 50: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/css-language-features/client/src/cssClient.ts: +extensions/css-language-features/client/src/cssClient.ts: 86: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/css-language-features/server/src/cssServer.ts: +extensions/css-language-features/server/src/cssServer.ts: 71: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 171: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/git-base/src/api/api1.ts: +extensions/git-base/src/api/api1.ts: 17: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/html-language-features/client/src/htmlClient.ts: +extensions/html-language-features/client/src/htmlClient.ts: 182: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/html-language-features/server/src/htmlServer.ts: +extensions/html-language-features/server/src/htmlServer.ts: 137: // eslint-disable-next-line local/code-no-any-casts 140: // eslint-disable-next-line local/code-no-any-casts 545: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/ipynb/src/deserializers.ts: +extensions/ipynb/src/deserializers.ts: 23: // eslint-disable-next-line local/code-no-any-casts 294: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/ipynb/src/helper.ts: +extensions/ipynb/src/helper.ts: 14: // eslint-disable-next-line local/code-no-any-casts 18: // eslint-disable-next-line local/code-no-any-casts 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 25: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/ipynb/src/serializers.ts: +extensions/ipynb/src/serializers.ts: 40: // eslint-disable-next-line local/code-no-any-casts 61: // eslint-disable-next-line local/code-no-any-casts 403: // eslint-disable-next-line local/code-no-any-casts 405: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/ipynb/src/test/notebookModelStoreSync.test.ts: +extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 79: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts @@ -124,84 +109,67 @@ vscode • extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 424: // eslint-disable-next-line local/code-no-any-casts 459: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/json-language-features/client/src/jsonClient.ts: +extensions/json-language-features/client/src/jsonClient.ts: 775: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/json-language-features/server/src/jsonServer.ts: +extensions/json-language-features/server/src/jsonServer.ts: 144: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/markdown-language-features/notebook/index.ts: +extensions/markdown-language-features/notebook/index.ts: 383: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/markdown-language-features/preview-src/index.ts: +extensions/markdown-language-features/preview-src/index.ts: 26: // eslint-disable-next-line local/code-no-any-casts 253: // eslint-disable-next-line local/code-no-any-casts 444: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/markdown-language-features/src/markdownEngine.ts: +extensions/markdown-language-features/src/markdownEngine.ts: 146: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: +extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: 53: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/notebook-renderers/src/index.ts: +extensions/notebook-renderers/src/index.ts: 68: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/notebook-renderers/src/test/notebookRenderer.test.ts: +extensions/notebook-renderers/src/test/notebookRenderer.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/extension.ts: +extensions/vscode-api-tests/src/extension.ts: 10: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: +extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: 181: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts: - 33: // eslint-disable-next-line local/code-no-any-casts - 48: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts: - 24: // eslint-disable-next-line local/code-no-any-casts - 26: // eslint-disable-next-line local/code-no-any-casts - 28: // eslint-disable-next-line local/code-no-any-casts - 30: // eslint-disable-next-line local/code-no-any-casts - 32: // eslint-disable-next-line local/code-no-any-casts - 34: // eslint-disable-next-line local/code-no-any-casts - -vscode • extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: +extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: 59: // eslint-disable-next-line local/code-no-any-casts 76: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: +extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: +extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: 18: // eslint-disable-next-line local/code-no-any-casts -vscode • extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts: - 44: // eslint-disable-next-line local/code-no-any-casts - 50: // eslint-disable-next-line local/code-no-any-casts - 463: // eslint-disable-next-line local/code-no-any-casts - -vscode • scripts/playground-server.ts: +scripts/playground-server.ts: 257: // eslint-disable-next-line local/code-no-any-casts 336: // eslint-disable-next-line local/code-no-any-casts 352: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/browser.ts: +src/vs/base/browser/browser.ts: 134: // eslint-disable-next-line local/code-no-any-casts 141: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/canIUse.ts: +src/vs/base/browser/canIUse.ts: 36: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/deviceAccess.ts: +src/vs/base/browser/deviceAccess.ts: 26: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/dom.ts: +src/vs/base/browser/dom.ts: 718: // eslint-disable-next-line local/code-no-any-casts 1324: // eslint-disable-next-line local/code-no-any-casts 1519: // eslint-disable-next-line local/code-no-any-casts @@ -216,132 +184,132 @@ vscode • src/vs/base/browser/dom.ts: 2443: // eslint-disable-next-line local/code-no-any-casts 2528: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/mouseEvent.ts: +src/vs/base/browser/mouseEvent.ts: 100: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts 155: // eslint-disable-next-line local/code-no-any-casts 157: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/trustedTypes.ts: +src/vs/base/browser/trustedTypes.ts: 19: // eslint-disable-next-line local/code-no-any-casts 31: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/webWorkerFactory.ts: +src/vs/base/browser/webWorkerFactory.ts: 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 43: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/grid/grid.ts: +src/vs/base/browser/ui/grid/grid.ts: 66: // eslint-disable-next-line local/code-no-any-casts 873: // eslint-disable-next-line local/code-no-any-casts 875: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/grid/gridview.ts: +src/vs/base/browser/ui/grid/gridview.ts: 196: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/sash/sash.ts: +src/vs/base/browser/ui/sash/sash.ts: 491: // eslint-disable-next-line local/code-no-any-casts 497: // eslint-disable-next-line local/code-no-any-casts 503: // eslint-disable-next-line local/code-no-any-casts 505: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/tree/abstractTree.ts: +src/vs/base/browser/ui/tree/abstractTree.ts: 171: // eslint-disable-next-line local/code-no-any-casts 175: // eslint-disable-next-line local/code-no-any-casts 1186: // eslint-disable-next-line local/code-no-any-casts 1212: // eslint-disable-next-line local/code-no-any-casts 2241: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/tree/asyncDataTree.ts: +src/vs/base/browser/ui/tree/asyncDataTree.ts: 305: // eslint-disable-next-line local/code-no-any-casts 417: // eslint-disable-next-line local/code-no-any-casts 434: // eslint-disable-next-line local/code-no-any-casts 438: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/browser/ui/tree/indexTreeModel.ts: +src/vs/base/browser/ui/tree/indexTreeModel.ts: 89: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/console.ts: +src/vs/base/common/console.ts: 134: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/controlFlow.ts: +src/vs/base/common/controlFlow.ts: 57: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/decorators.ts: +src/vs/base/common/decorators.ts: 57: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/errors.ts: +src/vs/base/common/errors.ts: 142: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/hotReload.ts: +src/vs/base/common/hotReload.ts: 97: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/hotReloadHelpers.ts: +src/vs/base/common/hotReloadHelpers.ts: 39: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/lifecycle.ts: +src/vs/base/common/lifecycle.ts: 236: // eslint-disable-next-line local/code-no-any-casts 246: // eslint-disable-next-line local/code-no-any-casts 257: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/marshalling.ts: +src/vs/base/common/marshalling.ts: 53: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts 57: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/network.ts: +src/vs/base/common/network.ts: 416: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/objects.ts: +src/vs/base/common/objects.ts: 75: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/skipList.ts: +src/vs/base/common/skipList.ts: 38: // eslint-disable-next-line local/code-no-any-casts 47: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/types.ts: +src/vs/base/common/types.ts: 58: // eslint-disable-next-line local/code-no-any-casts 66: // eslint-disable-next-line local/code-no-any-casts 268: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/uriIpc.ts: +src/vs/base/common/uriIpc.ts: 33: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/verifier.ts: +src/vs/base/common/verifier.ts: 82: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/changeTracker.ts: +src/vs/base/common/observableInternal/changeTracker.ts: 34: // eslint-disable-next-line local/code-no-any-casts 42: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts 80: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/debugLocation.ts: +src/vs/base/common/observableInternal/debugLocation.ts: 19: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/debugName.ts: +src/vs/base/common/observableInternal/debugName.ts: 106: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/set.ts: +src/vs/base/common/observableInternal/set.ts: 51: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/experimental/reducer.ts: +src/vs/base/common/observableInternal/experimental/reducer.ts: 39: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: +src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: 80: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: +src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: 12: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/logging/debugger/rpc.ts: +src/vs/base/common/observableInternal/logging/debugger/rpc.ts: 94: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/observables/derived.ts: +src/vs/base/common/observableInternal/observables/derived.ts: 38: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts 124: // eslint-disable-next-line local/code-no-any-casts @@ -349,78 +317,78 @@ vscode • src/vs/base/common/observableInternal/observables/derived.ts: 160: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/observables/derivedImpl.ts: +src/vs/base/common/observableInternal/observables/derivedImpl.ts: 313: // eslint-disable-next-line local/code-no-any-casts 414: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/observables/observableFromEvent.ts: +src/vs/base/common/observableInternal/observables/observableFromEvent.ts: 151: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/reactions/autorunImpl.ts: +src/vs/base/common/observableInternal/reactions/autorunImpl.ts: 185: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/common/observableInternal/utils/utilsCancellation.ts: +src/vs/base/common/observableInternal/utils/utilsCancellation.ts: 78: // eslint-disable-next-line local/code-no-any-casts 83: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/node/ports.ts: +src/vs/base/node/ports.ts: 182: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/parts/ipc/test/node/ipc.net.test.ts: +src/vs/base/parts/ipc/test/node/ipc.net.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts 652: // eslint-disable-next-line local/code-no-any-casts 785: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/buffer.test.ts: +src/vs/base/test/common/buffer.test.ts: 501: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/decorators.test.ts: +src/vs/base/test/common/decorators.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/filters.test.ts: +src/vs/base/test/common/filters.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/glob.test.ts: +src/vs/base/test/common/glob.test.ts: 497: // eslint-disable-next-line local/code-no-any-casts 518: // eslint-disable-next-line local/code-no-any-casts 763: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/json.test.ts: +src/vs/base/test/common/json.test.ts: 52: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/mock.ts: +src/vs/base/test/common/mock.ts: 13: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/oauth.test.ts: +src/vs/base/test/common/oauth.test.ts: 1099: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/snapshot.ts: +src/vs/base/test/common/snapshot.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/timeTravelScheduler.ts: +src/vs/base/test/common/timeTravelScheduler.ts: 268: // eslint-disable-next-line local/code-no-any-casts 278: // eslint-disable-next-line local/code-no-any-casts 311: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts 333: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/base/test/common/troubleshooting.ts: +src/vs/base/test/common/troubleshooting.ts: 50: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/editor.api.ts: +src/vs/editor/editor.api.ts: 44: // eslint-disable-next-line local/code-no-any-casts 46: // eslint-disable-next-line local/code-no-any-casts 51: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/browser/config/editorConfiguration.ts: +src/vs/editor/browser/config/editorConfiguration.ts: 147: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/controller/mouseTarget.ts: +src/vs/editor/browser/controller/mouseTarget.ts: 992: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 996: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 1000: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any @@ -429,43 +397,43 @@ vscode • src/vs/editor/browser/controller/mouseTarget.ts: 1099: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 1119: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: +src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: 81: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 85: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/gpu/gpuUtils.ts: +src/vs/editor/browser/gpu/gpuUtils.ts: 52: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/gpu/viewGpuContext.ts: +src/vs/editor/browser/gpu/viewGpuContext.ts: 226: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: +src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: 179: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: +src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: 477: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/widget/diffEditor/utils.ts: +src/vs/editor/browser/widget/diffEditor/utils.ts: 184: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 195: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 303: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 310: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: +src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: 75: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: +src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: 100: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 103: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/common/textModelEditSource.ts: +src/vs/editor/common/textModelEditSource.ts: 59: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 70: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/common/core/edits/stringEdit.ts: +src/vs/editor/common/core/edits/stringEdit.ts: 24: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: +src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: 26: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 30: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 51: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any @@ -484,54 +452,54 @@ vscode • src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree 177: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 196: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: +src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: 100: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: +src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: 232: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: +src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: 200: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: +src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: 97: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/find/browser/findModel.ts: +src/vs/editor/contrib/find/browser/findModel.ts: 556: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/find/test/browser/findController.test.ts: +src/vs/editor/contrib/find/test/browser/findController.test.ts: 79: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: +src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: 56: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: - 639: // eslint-disable-next-line local/code-no-any-casts +src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: + 640: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: +src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: 274: // eslint-disable-next-line local/code-no-any-casts 296: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: 346: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: 15: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: +src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: 23: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: +src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: 163: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: +src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: 240: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: +src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: 797: // eslint-disable-next-line local/code-no-any-casts 816: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/standalone/browser/standaloneEditor.ts: +src/vs/editor/standalone/browser/standaloneEditor.ts: 504: // eslint-disable-next-line local/code-no-any-casts 506: // eslint-disable-next-line local/code-no-any-casts 508: // eslint-disable-next-line local/code-no-any-casts @@ -570,7 +538,7 @@ vscode • src/vs/editor/standalone/browser/standaloneEditor.ts: 614: // eslint-disable-next-line local/code-no-any-casts 619: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/standalone/browser/standaloneLanguages.ts: +src/vs/editor/standalone/browser/standaloneLanguages.ts: 753: // eslint-disable-next-line local/code-no-any-casts 755: // eslint-disable-next-line local/code-no-any-casts 757: // eslint-disable-next-line local/code-no-any-casts @@ -609,25 +577,25 @@ vscode • src/vs/editor/standalone/browser/standaloneLanguages.ts: 849: // eslint-disable-next-line local/code-no-any-casts 851: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/standalone/common/monarch/monarchCompile.ts: +src/vs/editor/standalone/common/monarch/monarchCompile.ts: 461: // eslint-disable-next-line local/code-no-any-casts 539: // eslint-disable-next-line local/code-no-any-casts 556: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/test/browser/testCodeEditor.ts: +src/vs/editor/test/browser/testCodeEditor.ts: 279: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/test/browser/config/editorConfiguration.test.ts: +src/vs/editor/test/browser/config/editorConfiguration.test.ts: 90: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/test/common/model/textModel.test.ts: +src/vs/editor/test/common/model/textModel.test.ts: 1167: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/test/common/model/textModelWithTokens.test.ts: +src/vs/editor/test/common/model/textModelWithTokens.test.ts: 272: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/editor/test/common/model/tokenStore.test.ts: +src/vs/editor/test/common/model/tokenStore.test.ts: 57: // eslint-disable-next-line local/code-no-any-casts 78: // eslint-disable-next-line local/code-no-any-casts 100: // eslint-disable-next-line local/code-no-any-casts @@ -638,169 +606,151 @@ vscode • src/vs/editor/test/common/model/tokenStore.test.ts: 203: // eslint-disable-next-line local/code-no-any-casts 234: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/contextkey/common/contextkey.ts: +src/vs/platform/contextkey/common/contextkey.ts: 939: // eslint-disable-next-line local/code-no-any-casts 1213: // eslint-disable-next-line local/code-no-any-casts 1273: // eslint-disable-next-line local/code-no-any-casts 1334: // eslint-disable-next-line local/code-no-any-casts 1395: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/contextkey/test/common/contextkey.test.ts: +src/vs/platform/contextkey/test/common/contextkey.test.ts: 96: // eslint-disable-next-line local/code-no-any-casts 98: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts: - 93: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/platform/environment/test/node/argv.test.ts: +src/vs/platform/environment/test/node/argv.test.ts: 47: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: +src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: 243: // eslint-disable-next-line local/code-no-any-casts 245: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: +src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: 405: // eslint-disable-next-line local/code-no-any-casts 407: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: +src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: 73: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/files/browser/htmlFileSystemProvider.ts: +src/vs/platform/files/browser/htmlFileSystemProvider.ts: 311: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/platform/files/test/node/diskFileService.integrationTest.ts: +src/vs/platform/files/test/node/diskFileService.integrationTest.ts: 106: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 112: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/instantiation/common/instantiationService.ts: +src/vs/platform/instantiation/common/instantiationService.ts: 328: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/list/browser/listService.ts: +src/vs/platform/list/browser/listService.ts: 877: // eslint-disable-next-line local/code-no-any-casts 918: // eslint-disable-next-line local/code-no-any-casts 965: // eslint-disable-next-line local/code-no-any-casts 1012: // eslint-disable-next-line local/code-no-any-casts 1057: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/mcp/test/common/mcpManagementService.test.ts: +src/vs/platform/mcp/test/common/mcpManagementService.test.ts: 879: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/observable/common/wrapInHotClass.ts: +src/vs/platform/observable/common/wrapInHotClass.ts: 12: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/observable/common/wrapInReloadableClass.ts: +src/vs/platform/observable/common/wrapInReloadableClass.ts: 31: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/policy/node/nativePolicyService.ts: +src/vs/platform/policy/node/nativePolicyService.ts: 47: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/profiling/common/profilingTelemetrySpec.ts: +src/vs/platform/profiling/common/profilingTelemetrySpec.ts: 73: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/quickinput/browser/tree/quickTree.ts: +src/vs/platform/quickinput/browser/tree/quickTree.ts: 73: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/quickinput/test/browser/quickinput.test.ts: +src/vs/platform/quickinput/test/browser/quickinput.test.ts: 69: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/remote/browser/browserSocketFactory.ts: +src/vs/platform/remote/browser/browserSocketFactory.ts: 89: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/remote/common/remoteAgentConnection.ts: +src/vs/platform/remote/common/remoteAgentConnection.ts: 784: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: +src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: 19: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/request/electron-utility/requestService.ts: +src/vs/platform/request/electron-utility/requestService.ts: 15: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/telemetry/test/browser/telemetryService.test.ts: - 281: // eslint-disable-next-line local/code-no-any-casts - 312: // eslint-disable-next-line local/code-no-any-casts - 336: // eslint-disable-next-line local/code-no-any-casts - 344: // eslint-disable-next-line local/code-no-any-casts - 396: // eslint-disable-next-line local/code-no-any-casts - 459: // eslint-disable-next-line local/code-no-any-casts - 559: // eslint-disable-next-line local/code-no-any-casts - 624: // eslint-disable-next-line local/code-no-any-casts - 694: // eslint-disable-next-line local/code-no-any-casts - 738: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/platform/terminal/node/terminalProcess.ts: +src/vs/platform/terminal/node/terminalProcess.ts: 546: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/userDataSync/common/extensionsSync.ts: +src/vs/platform/userDataSync/common/extensionsSync.ts: 60: // eslint-disable-next-line local/code-no-any-casts 64: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: +src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: 22: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/server/node/extensionHostConnection.ts: +src/vs/server/node/extensionHostConnection.ts: 240: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/server/node/remoteExtensionHostAgentServer.ts: +src/vs/server/node/remoteExtensionHostAgentServer.ts: 765: // eslint-disable-next-line local/code-no-any-casts 767: // eslint-disable-next-line local/code-no-any-casts 769: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/workbench.web.main.internal.ts: +src/vs/workbench/workbench.web.main.internal.ts: 198: // eslint-disable-next-line local/code-no-any-casts 223: // eslint-disable-next-line local/code-no-any-casts 225: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/workbench.web.main.ts: +src/vs/workbench/workbench.web.main.ts: 58: // eslint-disable-next-line local/code-no-any-casts 60: // eslint-disable-next-line local/code-no-any-casts 82: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/mainThreadChatAgents2.ts: +src/vs/workbench/api/browser/mainThreadChatAgents2.ts: 365: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/mainThreadExtensionService.ts: +src/vs/workbench/api/browser/mainThreadExtensionService.ts: 57: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: +src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: 1000: // eslint-disable-next-line local/code-no-any-casts 1011: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/mainThreadQuickOpen.ts: +src/vs/workbench/api/browser/mainThreadQuickOpen.ts: 195: // eslint-disable-next-line local/code-no-any-casts 198: // eslint-disable-next-line local/code-no-any-casts 203: // eslint-disable-next-line local/code-no-any-casts 216: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/mainThreadTelemetry.ts: - 58: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/api/browser/mainThreadTreeViews.ts: +src/vs/workbench/api/browser/mainThreadTreeViews.ts: 378: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/browser/viewsExtensionPoint.ts: +src/vs/workbench/api/browser/viewsExtensionPoint.ts: 528: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHost.api.impl.ts: +src/vs/workbench/api/common/extHost.api.impl.ts: 161: // eslint-disable-next-line local/code-no-any-casts 315: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts 563: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHost.protocol.ts: +src/vs/workbench/api/common/extHost.protocol.ts: 2106: // eslint-disable-next-line local/code-no-any-casts 2108: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostComments.ts: +src/vs/workbench/api/common/extHostComments.ts: 672: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostDebugService.ts: +src/vs/workbench/api/common/extHostDebugService.ts: 243: // eslint-disable-next-line local/code-no-any-casts 491: // eslint-disable-next-line local/code-no-any-casts 493: // eslint-disable-next-line local/code-no-any-casts @@ -809,82 +759,79 @@ vscode • src/vs/workbench/api/common/extHostDebugService.ts: 770: // eslint-disable-next-line local/code-no-any-casts 778: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: +src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: 65: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostExtensionActivator.ts: +src/vs/workbench/api/common/extHostExtensionActivator.ts: 405: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostExtensionService.ts: +src/vs/workbench/api/common/extHostExtensionService.ts: 566: // eslint-disable-next-line local/code-no-any-casts 1009: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostLanguageFeatures.ts: +src/vs/workbench/api/common/extHostLanguageFeatures.ts: 197: // eslint-disable-next-line local/code-no-any-casts 714: // eslint-disable-next-line local/code-no-any-casts 735: // eslint-disable-next-line local/code-no-any-casts 748: // eslint-disable-next-line local/code-no-any-casts 771: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostLanguageModels.ts: +src/vs/workbench/api/common/extHostLanguageModels.ts: 174: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostLanguageModelTools.ts: +src/vs/workbench/api/common/extHostLanguageModelTools.ts: 221: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostMcp.ts: +src/vs/workbench/api/common/extHostMcp.ts: 163: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts 168: // eslint-disable-next-line local/code-no-any-casts 170: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostSCM.ts: - 374: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/api/common/extHostSearch.ts: +src/vs/workbench/api/common/extHostSearch.ts: 221: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostTerminalService.ts: +src/vs/workbench/api/common/extHostTerminalService.ts: 1287: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostTimeline.ts: +src/vs/workbench/api/common/extHostTimeline.ts: 160: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts 169: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostTunnelService.ts: +src/vs/workbench/api/common/extHostTunnelService.ts: 133: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/common/extHostTypeConverters.ts: - 462: // eslint-disable-next-line local/code-no-any-casts - 855: // eslint-disable-next-line local/code-no-any-casts - 3141: // eslint-disable-next-line local/code-no-any-casts - 3143: // eslint-disable-next-line local/code-no-any-casts - 3145: // eslint-disable-next-line local/code-no-any-casts - 3147: // eslint-disable-next-line local/code-no-any-casts - 3149: // eslint-disable-next-line local/code-no-any-casts - 3151: // eslint-disable-next-line local/code-no-any-casts - 3153: // eslint-disable-next-line local/code-no-any-casts - 3155: // eslint-disable-next-line local/code-no-any-casts - 3162: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/api/common/extHostTypes.ts: +src/vs/workbench/api/common/extHostTypeConverters.ts: + 463: // eslint-disable-next-line local/code-no-any-casts + 856: // eslint-disable-next-line local/code-no-any-casts + 3142: // eslint-disable-next-line local/code-no-any-casts + 3144: // eslint-disable-next-line local/code-no-any-casts + 3146: // eslint-disable-next-line local/code-no-any-casts + 3148: // eslint-disable-next-line local/code-no-any-casts + 3150: // eslint-disable-next-line local/code-no-any-casts + 3152: // eslint-disable-next-line local/code-no-any-casts + 3154: // eslint-disable-next-line local/code-no-any-casts + 3156: // eslint-disable-next-line local/code-no-any-casts + 3163: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTypes.ts: 3175: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/node/extensionHostProcess.ts: +src/vs/workbench/api/node/extensionHostProcess.ts: 107: // eslint-disable-next-line local/code-no-any-casts 119: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/node/extHostConsoleForwarder.ts: +src/vs/workbench/api/node/extHostConsoleForwarder.ts: 31: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/node/extHostMcpNode.ts: +src/vs/workbench/api/node/extHostMcpNode.ts: 57: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/node/proxyResolver.ts: +src/vs/workbench/api/node/proxyResolver.ts: 92: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts @@ -893,21 +840,21 @@ vscode • src/vs/workbench/api/node/proxyResolver.ts: 132: // eslint-disable-next-line local/code-no-any-casts 373: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: +src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: 870: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: +src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: 75: // eslint-disable-next-line local/code-no-any-casts 164: // eslint-disable-next-line local/code-no-any-casts 173: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostCommands.test.ts: +src/vs/workbench/api/test/browser/extHostCommands.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: +src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: 750: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: +src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts 48: // eslint-disable-next-line local/code-no-any-casts 50: // eslint-disable-next-line local/code-no-any-casts @@ -915,17 +862,17 @@ vscode • src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 54: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: +src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: +src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: 1068: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: +src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: 164: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: +src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 107: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 111: // eslint-disable-next-line local/code-no-any-casts @@ -933,33 +880,33 @@ vscode • src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 121: // eslint-disable-next-line local/code-no-any-casts 128: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostTesting.test.ts: +src/vs/workbench/api/test/browser/extHostTesting.test.ts: 640: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: +src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: 265: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 327: // eslint-disable-next-line local/code-no-any-casts 340: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: +src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: 771: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostTypes.test.ts: +src/vs/workbench/api/test/browser/extHostTypes.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts 209: // eslint-disable-next-line local/code-no-any-casts 211: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: +src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: 541: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: +src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: 115: // eslint-disable-next-line local/code-no-any-casts 122: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: +src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: 141: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts 230: // eslint-disable-next-line local/code-no-any-casts @@ -970,32 +917,32 @@ vscode • src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: 508: // eslint-disable-next-line local/code-no-any-casts 512: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: +src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts 93: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: +src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: 115: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: +src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: 60: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/common/extensionHostMain.test.ts: +src/vs/workbench/api/test/common/extensionHostMain.test.ts: 80: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: +src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/common/testRPCProtocol.ts: +src/vs/workbench/api/test/common/testRPCProtocol.ts: 36: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/test/node/extHostSearch.test.ts: +src/vs/workbench/api/test/node/extHostSearch.test.ts: 177: // eslint-disable-next-line local/code-no-any-casts 1004: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/worker/extensionHostWorker.ts: +src/vs/workbench/api/worker/extensionHostWorker.ts: 83: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 87: // eslint-disable-next-line local/code-no-any-casts @@ -1009,39 +956,39 @@ vscode • src/vs/workbench/api/worker/extensionHostWorker.ts: 106: // eslint-disable-next-line local/code-no-any-casts 158: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/api/worker/extHostConsoleForwarder.ts: +src/vs/workbench/api/worker/extHostConsoleForwarder.ts: 20: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/browser/actions/developerActions.ts: +src/vs/workbench/browser/actions/developerActions.ts: 781: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -vscode • src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: +src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: 54: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: +src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: 29: // eslint-disable-next-line local/code-no-any-casts 39: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: +src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: 88: // eslint-disable-next-line local/code-no-any-casts 90: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: +src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: 126: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/common/chatAgents.ts: +src/vs/workbench/contrib/chat/common/chatAgents.ts: 746: // eslint-disable-next-line local/code-no-any-casts 748: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/common/chatModel.ts: +src/vs/workbench/contrib/chat/common/chatModel.ts: 1214: // eslint-disable-next-line local/code-no-any-casts 1531: // eslint-disable-next-line local/code-no-any-casts 1863: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: +src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: 439: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: +src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: 30: // eslint-disable-next-line local/code-no-any-casts 35: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts @@ -1062,14 +1009,14 @@ vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNoteboo 1532: // eslint-disable-next-line local/code-no-any-casts 1537: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: +src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: 41: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: +src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: 35: // eslint-disable-next-line local/code-no-any-casts 144: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: +src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 48: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts 87: // eslint-disable-next-line local/code-no-any-casts @@ -1094,112 +1041,112 @@ vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 333: // eslint-disable-next-line local/code-no-any-casts 349: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: +src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: +src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: 189: // eslint-disable-next-line local/code-no-any-casts 196: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: +src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: 93: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/browser/debugSession.ts: +src/vs/workbench/contrib/debug/browser/debugSession.ts: 1193: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: +src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: 46: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: +src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: 813: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/common/debugger.ts: +src/vs/workbench/contrib/debug/common/debugger.ts: 168: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/common/debugUtils.ts: +src/vs/workbench/contrib/debug/common/debugUtils.ts: 66: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: +src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: 450: // eslint-disable-next-line local/code-no-any-casts 466: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: +src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts 124: // eslint-disable-next-line local/code-no-any-casts 206: // eslint-disable-next-line local/code-no-any-casts 261: // eslint-disable-next-line local/code-no-any-casts 461: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: +src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts 129: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: +src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: 76: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: +src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: 19: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: +src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/browser/repl.test.ts: +src/vs/workbench/contrib/debug/test/browser/repl.test.ts: 139: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: +src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: 50: // eslint-disable-next-line local/code-no-any-casts 62: // eslint-disable-next-line local/code-no-any-casts 70: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/debug/test/common/mockDebug.ts: +src/vs/workbench/contrib/debug/test/common/mockDebug.ts: 695: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: +src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: 15: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: +src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: 147: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: +src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: 1182: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 101: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: 100: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: +src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: +src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: 94: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: +src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: 160: // eslint-disable-next-line local/code-no-any-casts 662: // eslint-disable-next-line local/code-no-any-casts 711: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: +src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/markers/browser/markersTable.ts: +src/vs/workbench/contrib/markers/browser/markersTable.ts: 343: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: +src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: 143: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mcp/common/uriTemplate.ts: +src/vs/workbench/contrib/mcp/common/uriTemplate.ts: 159: // eslint-disable-next-line local/code-no-any-casts 191: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: +src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: 68: // eslint-disable-next-line local/code-no-any-casts 212: // eslint-disable-next-line local/code-no-any-casts 218: // eslint-disable-next-line local/code-no-any-casts @@ -1208,10 +1155,10 @@ vscode • src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: 271: // eslint-disable-next-line local/code-no-any-casts 280: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: +src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: 53: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: +src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: 18: // eslint-disable-next-line local/code-no-any-casts 52: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts @@ -1222,128 +1169,101 @@ vscode • src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: 231: // eslint-disable-next-line local/code-no-any-casts 254: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mergeEditor/browser/utils.ts: +src/vs/workbench/contrib/mergeEditor/browser/utils.ts: 89: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: +src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: 69: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: +src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: 309: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: +src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: 127: // eslint-disable-next-line local/code-no-any-casts 199: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: +src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: 74: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: +src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: 122: // eslint-disable-next-line local/code-no-any-casts 1462: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: +src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: 329: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: +src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: 170: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: +src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: 75: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: +src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: 424: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: +src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: 563: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: +src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: 1095: // eslint-disable-next-line local/code-no-any-casts 1137: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: +src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: 50: // eslint-disable-next-line local/code-no-any-casts 66: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: +src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: 284: // eslint-disable-next-line local/code-no-any-casts 308: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: +src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: 37: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: +src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: +src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: 26: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: +src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: 652: // eslint-disable-next-line local/code-no-any-casts 654: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/scm/browser/activity.ts: - 154: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/contrib/search/browser/searchActionsFind.ts: +src/vs/workbench/contrib/search/browser/searchActionsFind.ts: 460: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/search/browser/searchMessage.ts: +src/vs/workbench/contrib/search/browser/searchMessage.ts: 51: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: +src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: 306: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: +src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: 299: // eslint-disable-next-line local/code-no-any-casts 301: // eslint-disable-next-line local/code-no-any-casts 318: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: +src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: 201: // eslint-disable-next-line local/code-no-any-casts 229: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: +src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: 205: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: +src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: 155: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: +src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: 328: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts: - 157: // eslint-disable-next-line local/code-no-any-casts - 168: // eslint-disable-next-line local/code-no-any-casts - 284: // eslint-disable-next-line local/code-no-any-casts - 291: // eslint-disable-next-line local/code-no-any-casts - 302: // eslint-disable-next-line local/code-no-any-casts - 309: // eslint-disable-next-line local/code-no-any-casts - 340: // eslint-disable-next-line local/code-no-any-casts - 345: // eslint-disable-next-line local/code-no-any-casts - 352: // eslint-disable-next-line local/code-no-any-casts - 357: // eslint-disable-next-line local/code-no-any-casts - 659: // eslint-disable-next-line local/code-no-any-casts - 673: // eslint-disable-next-line local/code-no-any-casts - 701: // eslint-disable-next-line local/code-no-any-casts - 703: // eslint-disable-next-line local/code-no-any-casts - 717: // eslint-disable-next-line local/code-no-any-casts - 719: // eslint-disable-next-line local/code-no-any-casts - 733: // eslint-disable-next-line local/code-no-any-casts - 735: // eslint-disable-next-line local/code-no-any-casts - 768: // eslint-disable-next-line local/code-no-any-casts - 770: // eslint-disable-next-line local/code-no-any-casts - 810: // eslint-disable-next-line local/code-no-any-casts - 813: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: +src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: 1491: // eslint-disable-next-line local/code-no-any-casts 1500: // eslint-disable-next-line local/code-no-any-casts 1539: // eslint-disable-next-line local/code-no-any-casts @@ -1359,33 +1279,33 @@ vscode • src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: 3723: // eslint-disable-next-line local/code-no-any-casts 3782: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/tasks/common/problemMatcher.ts: +src/vs/workbench/contrib/tasks/common/problemMatcher.ts: 361: // eslint-disable-next-line local/code-no-any-casts 374: // eslint-disable-next-line local/code-no-any-casts 1015: // eslint-disable-next-line local/code-no-any-casts 1906: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: +src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: 1720: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/tasks/common/tasks.ts: +src/vs/workbench/contrib/tasks/common/tasks.ts: 655: // eslint-disable-next-line local/code-no-any-casts 696: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: +src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts 86: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: +src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 445: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: +src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: 55: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: +src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: 101: // eslint-disable-next-line local/code-no-any-casts 127: // eslint-disable-next-line local/code-no-any-casts 172: // eslint-disable-next-line local/code-no-any-casts @@ -1394,7 +1314,7 @@ vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProfileService 261: // eslint-disable-next-line local/code-no-any-casts 275: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: +src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 58: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1410,7 +1330,7 @@ vscode • src/vs/workbench/contrib/terminal/test/browser/terminalService.test.t 165: // eslint-disable-next-line local/code-no-any-casts 178: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: +src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: 31: // eslint-disable-next-line local/code-no-any-casts 37: // eslint-disable-next-line local/code-no-any-casts 45: // eslint-disable-next-line local/code-no-any-casts @@ -1427,7 +1347,7 @@ vscode • src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalC 132: // eslint-disable-next-line local/code-no-any-casts 141: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: +src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: 59: // eslint-disable-next-line local/code-no-any-casts 67: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1437,46 +1357,46 @@ vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegration 107: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: +src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 70: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: +src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: 71: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: 379: // eslint-disable-next-line local/code-no-any-casts 833: // eslint-disable-next-line local/code-no-any-casts 857: // eslint-disable-next-line local/code-no-any-casts 862: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: +src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: 94: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: +src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: 102: // eslint-disable-next-line local/code-no-any-casts 108: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: +src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: 152: // eslint-disable-next-line local/code-no-any-casts 351: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: 242: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: 150: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 553: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts @@ -1488,120 +1408,112 @@ vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalW 136: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: +src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: 409: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: +src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: 685: // eslint-disable-next-line local/code-no-any-casts 711: // eslint-disable-next-line local/code-no-any-casts 786: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: +src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: 53: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: +src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: 39: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: +src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: 75: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: +src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/common/observableUtils.ts: +src/vs/workbench/contrib/testing/common/observableUtils.ts: 17: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/common/testingStates.ts: +src/vs/workbench/contrib/testing/common/testingStates.ts: 78: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: +src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: 19: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: +src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: 122: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: +src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: 35: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: +src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts 154: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: +src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: 27: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: +src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: +src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: 210: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/testing/test/common/testStubs.ts: +src/vs/workbench/contrib/testing/test/common/testStubs.ts: 85: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/themes/browser/themes.contribution.ts: +src/vs/workbench/contrib/themes/browser/themes.contribution.ts: 614: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: +src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: 68: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: +src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: 600: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/assignment/common/assignmentFilters.ts: +src/vs/workbench/services/assignment/common/assignmentFilters.ts: 104: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: +src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: 236: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: +src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: 81: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 306: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/configurationResolver/common/variableResolver.ts: +src/vs/workbench/services/configurationResolver/common/variableResolver.ts: 93: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: +src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: 516: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/driver/browser/driver.ts: +src/vs/workbench/services/driver/browser/driver.ts: 193: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: +src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: 61: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/extensions/common/extensionsRegistry.ts: +src/vs/workbench/services/extensions/common/extensionsRegistry.ts: 229: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: +src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 54: // eslint-disable-next-line local/code-no-any-casts 97: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 107: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: +src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: 185: // eslint-disable-next-line local/code-no-any-casts 222: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: +src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: 47: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/keybinding/browser/keybindingService.ts: - 876: // eslint-disable-next-line local/code-no-any-casts - 888: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts: - 46: // eslint-disable-next-line local/code-no-any-casts - 402: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/services/remote/common/tunnelModel.ts: +src/vs/workbench/services/remote/common/tunnelModel.ts: 255: // eslint-disable-next-line local/code-no-any-casts 260: // eslint-disable-next-line local/code-no-any-casts 262: // eslint-disable-next-line local/code-no-any-casts @@ -1609,29 +1521,29 @@ vscode • src/vs/workbench/services/remote/common/tunnelModel.ts: 335: // eslint-disable-next-line local/code-no-any-casts 398: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/search/common/search.ts: +src/vs/workbench/services/search/common/search.ts: 628: // eslint-disable-next-line local/code-no-any-casts 631: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/search/node/rawSearchService.ts: +src/vs/workbench/services/search/node/rawSearchService.ts: 438: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: +src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: 44: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: +src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: 147: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/themes/browser/fileIconThemeData.ts: +src/vs/workbench/services/themes/browser/fileIconThemeData.ts: 122: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/themes/browser/productIconThemeData.ts: +src/vs/workbench/services/themes/browser/productIconThemeData.ts: 123: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/themes/common/colorThemeData.ts: +src/vs/workbench/services/themes/common/colorThemeData.ts: 650: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: +src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: 67: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts @@ -1655,7 +1567,7 @@ vscode • src/vs/workbench/services/views/test/browser/viewContainerModel.test. 755: // eslint-disable-next-line local/code-no-any-casts 833: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: +src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: 25: // eslint-disable-next-line local/code-no-any-casts 27: // eslint-disable-next-line local/code-no-any-casts 335: // eslint-disable-next-line local/code-no-any-casts @@ -1665,58 +1577,54 @@ vscode • src/vs/workbench/services/views/test/browser/viewDescriptorService.te 645: // eslint-disable-next-line local/code-no-any-casts 678: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts: - 112: // eslint-disable-next-line local/code-no-any-casts - 125: // eslint-disable-next-line local/code-no-any-casts - -vscode • src/vs/workbench/test/browser/part.test.ts: +src/vs/workbench/test/browser/part.test.ts: 133: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/browser/window.test.ts: +src/vs/workbench/test/browser/window.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/browser/workbenchTestServices.ts: +src/vs/workbench/test/browser/workbenchTestServices.ts: 305: // eslint-disable-next-line local/code-no-any-casts 697: // eslint-disable-next-line local/code-no-any-casts 1064: // eslint-disable-next-line local/code-no-any-casts 1967: // eslint-disable-next-line local/code-no-any-casts 1985: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: +src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: 98: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: +src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: 131: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: +src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts 127: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/common/resources.test.ts: +src/vs/workbench/test/common/resources.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 72: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/common/workbenchTestServices.ts: +src/vs/workbench/test/common/workbenchTestServices.ts: 291: // eslint-disable-next-line local/code-no-any-casts -vscode • src/vs/workbench/test/electron-browser/workbenchTestServices.ts: +src/vs/workbench/test/electron-browser/workbenchTestServices.ts: 255: // eslint-disable-next-line local/code-no-any-casts -vscode • test/automation/src/code.ts: +test/automation/src/code.ts: 127: // eslint-disable-next-line local/code-no-any-casts -vscode • test/automation/src/terminal.ts: +test/automation/src/terminal.ts: 315: // eslint-disable-next-line local/code-no-any-casts -vscode • test/mcp/src/application.ts: +test/mcp/src/application.ts: 309: // eslint-disable-next-line local/code-no-any-casts -vscode • test/mcp/src/playwright.ts: +test/mcp/src/playwright.ts: 17: // eslint-disable-next-line local/code-no-any-casts -vscode • test/mcp/src/automationTools/problems.ts: +test/mcp/src/automationTools/problems.ts: 76: // eslint-disable-next-line local/code-no-any-casts diff --git a/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts index f35b594a62a..e27a067eaf8 100644 --- a/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts +++ b/src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts @@ -90,8 +90,7 @@ export class DataChannelForwardingTelemetryService extends InterceptingTelemetry } if (forward) { - // eslint-disable-next-line local/code-no-any-casts - dataChannelService.getDataChannel('editTelemetry').sendData({ eventName, data: data as any }); + dataChannelService.getDataChannel('editTelemetry').sendData({ eventName, data: data ?? {} }); } }); } diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index ce4927e5820..0db5a78d02d 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -278,8 +278,7 @@ suite('TelemetryService', () => { const errorTelemetry = new ErrorTelemetry(service); const testError = new Error('test'); - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)('Error Message', 'file.js', 2, 42, testError); + (mainWindow.onerror)('Error Message', 'file.js', 2, 42, testError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.alwaysCalledWithExactly('Error Message', 'file.js', 2, 42, testError), true); @@ -309,8 +308,7 @@ suite('TelemetryService', () => { const personInfoWithSpaces = settings.personalInfo.slice(0, 2) + ' ' + settings.personalInfo.slice(2); const dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError); + mainWindow.onerror('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -333,16 +331,14 @@ suite('TelemetryService', () => { let dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); + mainWindow.onerror('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); dangerousFilenameError = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); + mainWindow.onerror('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 2); assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); @@ -393,8 +389,7 @@ suite('TelemetryService', () => { const dangerousPathWithoutImportantInfoError: any = new Error('dangerousPathWithoutImportantInfo'); dangerousPathWithoutImportantInfoError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)(settings.dangerousPathWithoutImportantInfo, 'test.js', 2, 42, dangerousPathWithoutImportantInfoError); + mainWindow.onerror(settings.dangerousPathWithoutImportantInfo, 'test.js', 2, 42, dangerousPathWithoutImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -456,8 +451,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); + mainWindow.onerror(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -556,8 +550,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); + mainWindow.onerror(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -621,8 +614,7 @@ suite('TelemetryService', () => { const missingModelError: any = new Error('missingModelMessage'); missingModelError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)(settings.missingModelMessage, 'test.js', 2, 42, missingModelError); + mainWindow.onerror(settings.missingModelMessage, 'test.js', 2, 42, missingModelError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -691,8 +683,7 @@ suite('TelemetryService', () => { const noSuchFileError: any = new Error('noSuchFileMessage'); noSuchFileError.stack = settings.stack; - // eslint-disable-next-line local/code-no-any-casts - (mainWindow.onerror)(settings.noSuchFileMessage, 'test.js', 2, 42, noSuchFileError); + mainWindow.onerror(settings.noSuchFileMessage, 'test.js', 2, 42, noSuchFileError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -735,8 +726,7 @@ suite('TelemetryService', () => { }, new class extends TestConfigurationService { override onDidChangeConfiguration = emitter.event; override getValue() { - // eslint-disable-next-line local/code-no-any-casts - return telemetryLevel as any; + return telemetryLevel; } }(), TestProductService); diff --git a/src/vs/workbench/api/browser/mainThreadTelemetry.ts b/src/vs/workbench/api/browser/mainThreadTelemetry.ts index 0ea93af7fef..7f49a22c20f 100644 --- a/src/vs/workbench/api/browser/mainThreadTelemetry.ts +++ b/src/vs/workbench/api/browser/mainThreadTelemetry.ts @@ -55,8 +55,7 @@ export class MainThreadTelemetry extends Disposable implements MainThreadTelemet } $publicLog2> = never, T extends IGDPRProperty = never>(eventName: string, data?: StrictPropertyCheck): void { - // eslint-disable-next-line local/code-no-any-casts - this.$publicLog(eventName, data as any); + this.$publicLog(eventName, data); } } From 28dd2520f2924394b634b8a5ab4b849fdfdee4a9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 7 Oct 2025 11:21:56 -0400 Subject: [PATCH 0911/4355] remove note role for chat footer element so it's aria label is announced on windows (#270219) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index d3d4b5ffea3..b93bdfa63f7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -437,7 +437,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Tue, 7 Oct 2025 12:00:47 -0400 Subject: [PATCH 0912/4355] Clear bad model picker preference state (#270223) --- .../contrib/chat/common/languageModels.ts | 21 ++++++++---- .../chat/test/common/languageModels.test.ts | 5 +-- .../test/browser/inlineChatController.test.ts | 4 +-- .../test/browser/workbenchTestServices.ts | 34 ++----------------- .../test/common/workbenchTestServices.ts | 32 +++++++++++++++++ 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 9ad8357fe3c..4d9fb7aac40 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -19,6 +19,7 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; import { ChatContextKeys } from './chatContextKeys.js'; @@ -316,8 +317,7 @@ export class LanguageModelsService implements ILanguageModelsService { private readonly _modelCache = new Map(); private readonly _vendors = new Map(); private readonly _resolveLMSequencer = new SequencerByKey(); - private readonly _modelPickerUserPreferences: Record = {}; // We use a record instead of a map for better serialization when storing - + private _modelPickerUserPreferences: Record = {}; private readonly _hasUserSelectableModels: IContextKey; private readonly _onLanguageModelChange = this._store.add(new Emitter()); readonly onDidChangeLanguageModels: Event = this._onLanguageModelChange.event; @@ -326,12 +326,21 @@ export class LanguageModelsService implements ILanguageModelsService { @IExtensionService private readonly _extensionService: IExtensionService, @ILogService private readonly _logService: ILogService, @IStorageService private readonly _storageService: IStorageService, - @IContextKeyService _contextKeyService: IContextKeyService + @IContextKeyService _contextKeyService: IContextKeyService, + @IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService, ) { this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(_contextKeyService); this._modelPickerUserPreferences = this._storageService.getObject>('chatModelPickerPreferences', StorageScope.PROFILE, this._modelPickerUserPreferences); + // TODO @lramos15 - Remove after a few releases, as this is just cleaning a bad storage state + const entitlementChangeHandler = () => { + if ((this._chatEntitlementService.entitlement === ChatEntitlement.Business || this._chatEntitlementService.entitlement === ChatEntitlement.Enterprise) && !this._chatEntitlementService.isInternal) { + this._modelPickerUserPreferences = {}; + this._storageService.store('chatModelPickerPreferences', this._modelPickerUserPreferences, StorageScope.PROFILE, StorageTarget.USER); + } + }; - + entitlementChangeHandler(); + this._store.add(this._chatEntitlementService.onDidChangeEntitlement(entitlementChangeHandler)); this._store.add(this.onDidChangeLanguageModels(() => { this._hasUserSelectableModels.set(this._modelCache.size > 0 && Array.from(this._modelCache.values()).some(model => model.isUserSelectable)); @@ -357,7 +366,7 @@ export class LanguageModelsService implements ILanguageModelsService { } this._vendors.set(item.vendor, item); // Have some models we want from this vendor, so activate the extension - if (this._hasStoredModelForvendor(item.vendor)) { + if (this._hasStoredModelForVendor(item.vendor)) { this._extensionService.activateByEvent(`onLanguageModelChatProvider:${item.vendor}`); } } @@ -370,7 +379,7 @@ export class LanguageModelsService implements ILanguageModelsService { })); } - private _hasStoredModelForvendor(vendor: string): boolean { + private _hasStoredModelForVendor(vendor: string): boolean { return Object.keys(this._modelPickerUserPreferences).some(modelId => { return modelId.startsWith(vendor); }); diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts index e38cabb7e82..8eb2df0933d 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts @@ -15,7 +15,7 @@ import { IExtensionService, nullExtensionDescription } from '../../../../service import { ExtensionsRegistry } from '../../../../services/extensions/common/extensionsRegistry.js'; import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../common/modelPicker/modelPickerWidget.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; -import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { TestChatEntitlementService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { Event } from '../../../../../base/common/event.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; @@ -37,7 +37,8 @@ suite('LanguageModels', function () { }, new NullLogService(), new TestStorageService(), - new MockContextKeyService() + new MockContextKeyService(), + new TestChatEntitlementService() ); const ext = ExtensionsRegistry.getExtensionPoints().find(e => e.name === languageModelChatProviderExtensionPoint.name)!; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index a721c38bc66..c11c34bb272 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -46,8 +46,8 @@ import { NullWorkbenchAssignmentService } from '../../../../services/assignment/ import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { TextModelResolverService } from '../../../../services/textmodelResolver/common/textModelResolverService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { TestChatEntitlementService, TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; -import { TestContextService, TestExtensionService } from '../../../../test/common/workbenchTestServices.js'; +import { TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { TestChatEntitlementService, TestContextService, TestExtensionService } from '../../../../test/common/workbenchTestServices.js'; import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatInputBoxContentProvider } from '../../../chat/browser/chatEdinputInputContentProvider.js'; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f9a599137ff..1950e6f1f7b 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -16,7 +16,6 @@ import { isValidBasename } from '../../../base/common/extpath.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; import { Schemas } from '../../../base/common/network.js'; -import { observableValue } from '../../../base/common/observable.js'; import { posix, win32 } from '../../../base/common/path.js'; import { IProcessEnvironment, isWindows, OperatingSystem } from '../../../base/common/platform.js'; import { env } from '../../../base/common/process.js'; @@ -139,7 +138,7 @@ import { TerminalEditorInput } from '../../contrib/terminal/browser/terminalEdit import { IEnvironmentVariableService } from '../../contrib/terminal/common/environmentVariable.js'; import { EnvironmentVariableService } from '../../contrib/terminal/common/environmentVariableService.js'; import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService, type ITerminalConfiguration } from '../../contrib/terminal/common/terminal.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../services/chat/common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../services/chat/common/chatEntitlementService.js'; import { IDecoration, IDecorationData, IDecorationsProvider, IDecorationsService, IResourceDecorationChangeEvent } from '../../services/decorations/common/decorations.js'; import { CodeEditorService } from '../../services/editor/browser/codeEditorService.js'; import { EditorPaneService } from '../../services/editor/browser/editorPaneService.js'; @@ -185,7 +184,7 @@ import { InMemoryWorkingCopyBackupService } from '../../services/workingCopy/com import { IWorkingCopyEditorService, WorkingCopyEditorService } from '../../services/workingCopy/common/workingCopyEditorService.js'; import { IWorkingCopyFileService, WorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; import { IWorkingCopyService, WorkingCopyService } from '../../services/workingCopy/common/workingCopyService.js'; -import { TestContextService, TestExtensionService, TestFileService, TestHistoryService, TestLoggerService, TestMarkerService, TestProductService, TestStorageService, TestTextResourcePropertiesService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from '../common/workbenchTestServices.js'; +import { TestChatEntitlementService, TestContextService, TestExtensionService, TestFileService, TestHistoryService, TestLoggerService, TestMarkerService, TestProductService, TestStorageService, TestTextResourcePropertiesService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from '../common/workbenchTestServices.js'; // Backcompat export export { TestFileService }; @@ -2174,35 +2173,6 @@ export async function workbenchTeardown(instantiationService: IInstantiationServ }); } -export class TestChatEntitlementService implements IChatEntitlementService { - - _serviceBrand: undefined; - - readonly organisations: undefined; - readonly isInternal = false; - readonly sku = undefined; - - readonly onDidChangeQuotaExceeded = Event.None; - readonly onDidChangeQuotaRemaining = Event.None; - readonly quotas = {}; - - update(token: CancellationToken): Promise { - throw new Error('Method not implemented.'); - } - - readonly onDidChangeSentiment = Event.None; - readonly sentimentObs = observableValue({}, {}); - readonly sentiment = {}; - - readonly onDidChangeEntitlement = Event.None; - entitlement: ChatEntitlement = ChatEntitlement.Unknown; - readonly entitlementObs = observableValue({}, ChatEntitlement.Unknown); - - readonly anonymous = false; - onDidChangeAnonymous = Event.None; - readonly anonymousObs = observableValue({}, false); -} - export class TestContextMenuService implements IContextMenuService { _serviceBrand: undefined; diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 003454192ab..9c6dc49a77b 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -11,6 +11,7 @@ import { Iterable } from '../../../base/common/iterator.js'; import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../base/common/map.js'; import { Schemas } from '../../../base/common/network.js'; +import { observableValue } from '../../../base/common/observable.js'; import { join } from '../../../base/common/path.js'; import { isLinux, isMacintosh } from '../../../base/common/platform.js'; import { basename, isEqual, isEqualOrParent } from '../../../base/common/resources.js'; @@ -31,6 +32,7 @@ import { TestWorkspace } from '../../../platform/workspace/test/common/testWorks import { GroupIdentifier, IRevertOptions, ISaveOptions, SaveReason } from '../../common/editor.js'; import { EditorInput } from '../../common/editor/editorInput.js'; import { IActivity, IActivityService } from '../../services/activity/common/activity.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../services/chat/common/chatEntitlementService.js'; import { NullExtensionService } from '../../services/extensions/common/extensions.js'; import { IAutoSaveConfiguration, IAutoSaveMode, IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js'; import { IHistoryService } from '../../services/history/common/history.js'; @@ -747,3 +749,33 @@ export class InMemoryTestFileService extends TestFileService { return createFileStat(resource, this.readonly); } } + +export class TestChatEntitlementService implements IChatEntitlementService { + + _serviceBrand: undefined; + + readonly organisations: undefined; + readonly isInternal = false; + readonly sku = undefined; + + readonly onDidChangeQuotaExceeded = Event.None; + readonly onDidChangeQuotaRemaining = Event.None; + readonly quotas = {}; + + update(token: CancellationToken): Promise { + throw new Error('Method not implemented.'); + } + + readonly onDidChangeSentiment = Event.None; + readonly sentimentObs = observableValue({}, {}); + readonly sentiment = {}; + + readonly onDidChangeEntitlement = Event.None; + entitlement: ChatEntitlement = ChatEntitlement.Unknown; + readonly entitlementObs = observableValue({}, ChatEntitlement.Unknown); + + readonly anonymous = false; + onDidChangeAnonymous = Event.None; + readonly anonymousObs = observableValue({}, false); +} + From 818df99c9ba65e8c24a39ce3fe94f115e2f3ca49 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:01:55 +0000 Subject: [PATCH 0913/4355] Fix terminal voice indicator to move with dictated text (#269713) --- .../voice/browser/terminalVoice.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts index b1c0519e813..2ac51a88a6f 100644 --- a/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminalContrib/voice/browser/terminalVoice.ts @@ -109,6 +109,7 @@ export class TerminalVoiceSession extends Disposable { case SpeechToTextStatus.Recognizing: { this._updateInput(e); this._renderGhostText(e); + this._updateDecoration(); if (voiceTimeout > 0) { this._acceptTranscriptionScheduler!.cancel(); } @@ -133,7 +134,9 @@ export class TerminalVoiceSession extends Disposable { this._sendText(); } this._ghostText = undefined; + this._decoration?.dispose(); this._decoration = undefined; + this._marker?.dispose(); this._marker = undefined; this._ghostTextMarker = undefined; this._cancellationTokenSource?.cancel(); @@ -164,25 +167,40 @@ export class TerminalVoiceSession extends Disposable { return; } const onFirstLine = xterm.buffer.active.cursorY === 0; + + // Calculate x position based on current cursor position and input length + const inputLength = this._input.length; + const xPosition = xterm.buffer.active.cursorX + inputLength; + this._marker = activeInstance.registerMarker(onFirstLine ? 0 : -1); if (!this._marker) { return; } - this._disposables.add(this._marker); this._decoration = xterm.registerDecoration({ marker: this._marker, layer: 'top', - x: xterm.buffer.active.cursorX ?? 0, + x: xPosition, }); - if (this._decoration) { - this._disposables.add(this._decoration); + if (!this._decoration) { + this._marker.dispose(); + this._marker = undefined; + return; } - this._decoration?.onRender((e: HTMLElement) => { + this._decoration.onRender((e: HTMLElement) => { e.classList.add(...ThemeIcon.asClassNameArray(Codicon.micFilled), 'terminal-voice', 'recording'); e.style.transform = onFirstLine ? 'translate(10px, -2px)' : 'translate(-6px, -5px)'; }); } + private _updateDecoration(): void { + // Dispose the old decoration and recreate it at the new position + this._decoration?.dispose(); + this._marker?.dispose(); + this._decoration = undefined; + this._marker = undefined; + this._createDecoration(); + } + private _setInactive(): void { this._decoration?.element?.classList.remove('recording'); } From c825dc936cda425921bbba68c00a2ac6a833a003 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 7 Oct 2025 11:57:04 -0500 Subject: [PATCH 0914/4355] Fix some 'as any' (#270239) --- .../api/browser/mainThreadChatAgents2.ts | 3 +- .../browser/mainThreadChatSessions.test.ts | 43 +++++++++++-------- .../contrib/chat/common/chatAgents.ts | 33 ++++++++------ .../test/common/chatRequestParser.test.ts | 17 +++----- .../chat/test/common/mockChatVariables.ts | 14 +++++- 5 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index b428977a2eb..f3b7b7bb092 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -362,8 +362,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA kind: CompletionItemKind.Text, detail: v.detail, documentation: v.documentation, - // eslint-disable-next-line local/code-no-any-casts - command: { id: AddDynamicVariableAction.ID, title: '', arguments: [{ id: v.id, widget, range: rangeAfterInsert, variableData: revive(v.value) as any, command: v.command } satisfies IAddDynamicVariableContext] } + command: { id: AddDynamicVariableAction.ID, title: '', arguments: [{ id: v.id, widget, range: rangeAfterInsert, variableData: revive(v.value), command: v.command } satisfies IAddDynamicVariableContext] } } satisfies CompletionItem; }); diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 2f185ea9541..1fc04f7656a 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -17,7 +17,7 @@ import { TestInstantiationService } from '../../../../platform/instantiation/tes import { ILogService, NullLogService } from '../../../../platform/log/common/log.js'; import { ChatSessionsService } from '../../../contrib/chat/browser/chatSessions.contribution.js'; import { IChatAgentRequest } from '../../../contrib/chat/common/chatAgents.js'; -import { IChatProgress } from '../../../contrib/chat/common/chatService.js'; +import { IChatProgress, IChatProgressMessage } from '../../../contrib/chat/common/chatService.js'; import { IChatSessionItem, IChatSessionsService } from '../../../contrib/chat/common/chatSessionsService.js'; import { ChatAgentLocation } from '../../../contrib/chat/common/constants.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -138,8 +138,7 @@ suite('ObservableChatSession', function () { // Verify history was loaded assert.strictEqual(session.history.length, 2); assert.strictEqual(session.history[0].type, 'request'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[0] as any).prompt, 'Previous question'); + assert.strictEqual(session.history[0].prompt, 'Previous question'); assert.strictEqual(session.history[1].type, 'response'); // Verify capabilities were set up @@ -212,8 +211,14 @@ suite('ObservableChatSession', function () { assert.ok(session.requestHandler); - // eslint-disable-next-line local/code-no-any-casts - const request = { requestId: 'req1', prompt: 'Test prompt' } as any; + const request: IChatAgentRequest = { + requestId: 'req1', + sessionId: 'test-session', + agentId: 'test-agent', + message: 'Test prompt', + location: ChatAgentLocation.Chat, + variables: { variables: [] } + }; const progressCallback = sinon.stub(); await session.requestHandler!(request, progressCallback, [], CancellationToken.None); @@ -227,8 +232,14 @@ suite('ObservableChatSession', function () { assert.ok(session.requestHandler); - // eslint-disable-next-line local/code-no-any-casts - const request = { requestId: 'req1', prompt: 'Test prompt' } as any; + const request: IChatAgentRequest = { + requestId: 'req1', + sessionId: 'test-session', + agentId: 'test-agent', + message: 'Test prompt', + location: ChatAgentLocation.Chat, + variables: { variables: [] } + }; const progressCallback = sinon.stub(); let resolveRequest: () => void; @@ -304,17 +315,13 @@ suite('ObservableChatSession', function () { // Verify all history was loaded correctly assert.strictEqual(session.history.length, 4); assert.strictEqual(session.history[0].type, 'request'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[0] as any).prompt, 'First question'); + assert.strictEqual(session.history[0].prompt, 'First question'); assert.strictEqual(session.history[1].type, 'response'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[1].parts[0] as any).content.value, 'First answer'); + assert.strictEqual((session.history[1].parts[0] as IChatProgressMessage).content.value, 'First answer'); assert.strictEqual(session.history[2].type, 'request'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[2] as any).prompt, 'Second question'); + assert.strictEqual(session.history[2].prompt, 'Second question'); assert.strictEqual(session.history[3].type, 'response'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[3].parts[0] as any).content.value, 'Second answer'); + assert.strictEqual((session.history[3].parts[0] as IChatProgressMessage).content.value, 'Second answer'); // Session should be complete since it has no capabilities assert.strictEqual(session.isCompleteObs.get(), true); @@ -505,12 +512,10 @@ suite('MainThreadChatSessions', function () { // Verify all history items are correctly loaded assert.strictEqual(session.history[0].type, 'request'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[0] as any).prompt, 'First question'); + assert.strictEqual(session.history[0].prompt, 'First question'); assert.strictEqual(session.history[1].type, 'response'); assert.strictEqual(session.history[2].type, 'request'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((session.history[2] as any).prompt, 'Second question'); + assert.strictEqual(session.history[2].prompt, 'Second question'); assert.strictEqual(session.history[3].type, 'response'); // Session should be complete since it has no active capabilities diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index f24a0c0b57f..4e3190d4992 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -739,28 +739,35 @@ export function getFullyQualifiedId(chatAgentData: IChatAgentData): string { return `${chatAgentData.extensionId.value}.${chatAgentData.id}`; } -export function reviveSerializedAgent(raw: ISerializableChatAgentData): IChatAgentData { - const agent = 'name' in raw ? +/** + * There was a period where serialized chat agent data used 'id' instead of 'name'. + * Don't copy this pattern, serialized data going forward should be versioned with strict interfaces. + */ +interface IOldSerializedChatAgentData extends Omit { + id: string; + extensionPublisher?: string; +} + +export function reviveSerializedAgent(raw: ISerializableChatAgentData | IOldSerializedChatAgentData): IChatAgentData { + const normalized: ISerializableChatAgentData = 'name' in raw ? raw : { - // eslint-disable-next-line local/code-no-any-casts - ...(raw as any), - // eslint-disable-next-line local/code-no-any-casts - name: (raw as any).id, + ...raw, + name: raw.id, }; // Fill in required fields that may be missing from old data - if (!('extensionPublisherId' in agent)) { - agent.extensionPublisherId = agent.extensionPublisher ?? ''; + if (!normalized.extensionPublisherId) { + normalized.extensionPublisherId = (raw as IOldSerializedChatAgentData).extensionPublisher ?? ''; } - if (!('extensionDisplayName' in agent)) { - agent.extensionDisplayName = ''; + if (!normalized.extensionDisplayName) { + normalized.extensionDisplayName = ''; } - if (!('extensionId' in agent)) { - agent.extensionId = new ExtensionIdentifier(''); + if (!normalized.extensionId) { + normalized.extensionId = new ExtensionIdentifier(''); } - return revive(agent); + return revive(normalized); } diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 05e0b5ad8d1..d77620366fb 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MockObject, mockObject } from '../../../../../base/test/common/mock.js'; +import { mockObject } from '../../../../../base/test/common/mock.js'; import { assertSnapshot } from '../../../../../base/test/common/snapshot.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -22,6 +22,7 @@ import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { MockChatService } from './mockChatService.js'; +import { MockChatVariablesService } from './mockChatVariables.js'; import { MockPromptsService } from './mockPromptsService.js'; suite('ChatRequestParser', () => { @@ -30,7 +31,7 @@ suite('ChatRequestParser', () => { let instantiationService: TestInstantiationService; let parser: ChatRequestParser; - let variableService: MockObject; + let variableService: MockChatVariablesService; setup(async () => { instantiationService = testDisposables.add(new TestInstantiationService()); instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); @@ -41,12 +42,8 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); instantiationService.stub(IPromptsService, testDisposables.add(new MockPromptsService())); - variableService = mockObject()(); - variableService.getDynamicVariables.returns([]); - variableService.getSelectedToolAndToolSets.returns([]); - - // eslint-disable-next-line local/code-no-any-casts - instantiationService.stub(IChatVariablesService, variableService as any); + variableService = new MockChatVariablesService(); + instantiationService.stub(IChatVariablesService, variableService); }); test('plain text', async () => { @@ -333,7 +330,7 @@ suite('ChatRequestParser', () => { // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); - variableService.getSelectedToolAndToolSets.returns(new Map([ + variableService.setSelectedToolAndToolSets('1', new Map([ [{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true], [{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true] ] satisfies [IToolData | ToolSet, boolean][])); @@ -349,7 +346,7 @@ suite('ChatRequestParser', () => { // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); - variableService.getSelectedToolAndToolSets.returns(new Map([ + variableService.setSelectedToolAndToolSets('1', new Map([ [{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true], [{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true] ] satisfies [IToolData | ToolSet, boolean][])); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index d7ff97ea203..c65e3177dc9 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -9,12 +9,22 @@ import { IToolAndToolSetEnablementMap } from '../../common/languageModelToolsSer export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; + private _dynamicVariables = new Map(); + private _selectedToolAndToolSets = new Map(); + getDynamicVariables(sessionId: string): readonly IDynamicVariable[] { - return []; + return this._dynamicVariables.get(sessionId) ?? []; } getSelectedToolAndToolSets(sessionId: string): IToolAndToolSetEnablementMap { - return new Map(); + return this._selectedToolAndToolSets.get(sessionId) ?? new Map(); + } + + setDynamicVariables(sessionId: string, variables: readonly IDynamicVariable[]): void { + this._dynamicVariables.set(sessionId, variables); } + setSelectedToolAndToolSets(sessionId: string, tools: IToolAndToolSetEnablementMap): void { + this._selectedToolAndToolSets.set(sessionId, tools); + } } From efcd2b6ac9b5d9224cf4618b085ae1d38a8a5c7d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:05:43 +0000 Subject: [PATCH 0915/4355] Fix: Rerun task not working for npm tasks in monorepos (#270080) --- .../contrib/tasks/browser/abstractTaskService.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 18c713292d8..b2b27772c9f 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2219,6 +2219,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } } + + // If task wasn't found in workspace configuration, check contributed tasks from providers + // This is important for tasks from extensions like npm, which are ContributedTasks + if (ContributedTask.is(originalTask)) { + // The type filter ensures only the matching provider is called (e.g., only npm provider for npm tasks) + // This is the same pattern used in tryResolveTask as a fallback + const allTasks = await this.tasks({ type: originalTask.type }); + for (const task of allTasks) { + if (task._id === originalTask._id) { + return task; + } + } + } + return undefined; } From 1b3e2c150796cad4bbd00395491d1f57e43e10a3 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 7 Oct 2025 10:18:57 -0700 Subject: [PATCH 0916/4355] Do not show Auto Detect language choice for empty files (#269781) * Do not show Auto Detect language choice for really short files * PR feedback * PR feedback * Update src/vs/workbench/browser/parts/editor/editorStatus.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * PR feedback * :lipstick: --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/parts/editor/editorStatus.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 25d85dbd034..b1fa24fead0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1225,11 +1225,11 @@ export class ChangeLanguageAction extends Action2 { picks.unshift(configureLanguageAssociations); } - // Offer to "Auto Detect" - const autoDetectLanguage: IQuickPickItem = { - label: localize('autoDetect', "Auto Detect") - }; - picks.unshift(autoDetectLanguage); + // Offer to "Auto Detect", but only if the document is not empty. + const autoDetectLanguage: IQuickPickItem = { label: localize('autoDetect', "Auto Detect") }; + if (textModel && textModel.getValueLength() > 0) { + picks.unshift(autoDetectLanguage); + } const pick = typeof languageMode === 'string' ? { label: languageMode } : await quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); if (!pick) { From f7f7a215488545be07dad7f43e5f7350b8e2738e Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 7 Oct 2025 10:19:03 -0700 Subject: [PATCH 0917/4355] Update src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts Co-authored-by: Sandeep Somavarapu --- .../extensions/browser/fileBasedRecommendations.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 258c6e9ebd0..50cfce53372 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -160,14 +160,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations { let listenOnLanguageChange = false; const languageId = model.getLanguageId(); - // Avoid language-specific recommendations for untitled files when language is auto-detected except when the file is large. - let allowLanguageMatch = true; - if (uri.scheme === Schemas.untitled && model.getValueLength() < untitledFileRecommendationsMinLength) { - const untitledModel = this.untitledTextEditorService.get(uri); - if (untitledModel && !untitledModel.hasLanguageSetExplicitly) { - allowLanguageMatch = false; - } - } + // Allow language-specific recommendations for untitled files when language is auto-detected only when the file is large. + const allowLanguageMatch = this.untitledTextEditorService.get(uri)?.hasLanguageSetExplicitly ? model.getValueLength() > 1000 : true; for (const [extensionId, conditions] of extensionRecommendationEntries) { const conditionsByPattern: IFileOpenCondition[] = []; From 19c6f5c55acb28f379ef9203065654315d75c64c Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 7 Oct 2025 10:24:01 -0700 Subject: [PATCH 0918/4355] Fix build break from suggestion commit. --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index 50cfce53372..cd998dee3a6 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -161,7 +161,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const languageId = model.getLanguageId(); // Allow language-specific recommendations for untitled files when language is auto-detected only when the file is large. - const allowLanguageMatch = this.untitledTextEditorService.get(uri)?.hasLanguageSetExplicitly ? model.getValueLength() > 1000 : true; + const allowLanguageMatch = this.untitledTextEditorService.get(uri)?.hasLanguageSetExplicitly ? + model.getValueLength() > untitledFileRecommendationsMinLength : true; for (const [extensionId, conditions] of extensionRecommendationEntries) { const conditionsByPattern: IFileOpenCondition[] = []; From a646590ebb35196cfcafd066179887f5710e964e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 7 Oct 2025 19:30:19 +0200 Subject: [PATCH 0919/4355] remove any usage (#270146) * remove any usage * comment * fix compilation --- eslint.config.js | 24 ++++++++++++++ .../common/mcpGalleryManifestServiceIpc.ts | 11 ++++--- .../platform/mcp/common/mcpGalleryService.ts | 33 ++++++++++--------- .../platform/mcp/common/mcpManagementIpc.ts | 32 +++++++++--------- .../node/nativeMcpDiscoveryHelperChannel.ts | 11 ++++--- .../test/common/mcpManagementService.test.ts | 33 ++++++++++++++++--- 6 files changed, 100 insertions(+), 44 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index f4367638adb..c9663165783 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -210,18 +210,28 @@ export default tseslint.config( 'src/vs/base/parts/storage/**', 'src/vs/platform/auxiliaryWindow/**', 'src/vs/platform/backup/**', + // 'src/vs/platform/configuration/**', 'src/vs/platform/editor/**', 'src/vs/platform/environment/**', + // 'src/vs/platform/extensionManagement/**', + // 'src/vs/platform/extensionRecommendations/**', + // 'src/vs/platform/extensionResourceLoader/**', 'src/vs/platform/dialogs/**', 'src/vs/platform/files/**', 'src/vs/platform/ipc/**', 'src/vs/platform/launch/**', 'src/vs/platform/lifecycle/**', + // 'src/vs/platform/log/**', + 'src/vs/platform/mcp/**', 'src/vs/platform/menubar/**', 'src/vs/platform/native/**', + // 'src/vs/platform/policy/**', 'src/vs/platform/sharedProcess/**', 'src/vs/platform/state/**', 'src/vs/platform/storage/**', + // 'src/vs/platform/userData/**', + // 'src/vs/platform/userDataProfile/**', + // 'src/vs/platform/userDataSync/**', 'src/vs/platform/utilityProcess/**', 'src/vs/platform/window/**', 'src/vs/platform/windows/**', @@ -239,13 +249,18 @@ export default tseslint.config( 'src/server-cli.ts', 'src/server-main.ts', 'src/vs/code/**', + // 'src/vs/workbench/services/accounts/**', 'src/vs/workbench/services/activity/**', 'src/vs/workbench/services/auxiliaryWindow/**', 'src/vs/workbench/services/chat/**', + // 'src/vs/workbench/services/configuration/**', 'src/vs/workbench/services/contextmenu/**', 'src/vs/workbench/services/dialogs/**', 'src/vs/workbench/services/editor/**', 'src/vs/workbench/services/environment/**', + // 'src/vs/workbench/services/extensionManagement/**', + // 'src/vs/workbench/services/extensionRecommendations/**', + // 'src/vs/workbench/services/extensions/**', 'src/vs/workbench/services/files/**', 'src/vs/workbench/services/filesConfiguration/**', 'src/vs/workbench/services/history/**', @@ -253,13 +268,21 @@ export default tseslint.config( 'src/vs/workbench/services/label/**', 'src/vs/workbench/services/layout/**', 'src/vs/workbench/services/lifecycle/**', + // 'src/vs/workbench/services/log/**', + // 'src/vs/workbench/services/mcp/**', 'src/vs/workbench/services/notification/**', + // 'src/vs/workbench/services/output/**', 'src/vs/workbench/services/path/**', + // 'src/vs/workbench/services/policies/**', + // 'src/vs/workbench/services/preferences/**', 'src/vs/workbench/services/progress/**', 'src/vs/workbench/services/storage/**', 'src/vs/workbench/services/textfile/**', 'src/vs/workbench/services/textmodelResolver/**', 'src/vs/workbench/services/untitled/**', + // 'src/vs/workbench/services/userData/**', + // 'src/vs/workbench/services/userDataProfile/**', + // 'src/vs/workbench/services/userDataSync/**', 'src/vs/workbench/services/utilityProcess/**', 'src/vs/workbench/services/views/**', 'src/vs/workbench/services/workingCopy/**', @@ -270,6 +293,7 @@ export default tseslint.config( 'src/vs/workbench/contrib/files/**', 'src/vs/workbench/contrib/chat/browser/chatSetup.ts', 'src/vs/workbench/contrib/chat/browser/chatStatus.ts', + // 'src/vs/workbench/contrib/mcp/**', ], ignores: ['**/*.test.ts', '**/*.integrationTest.ts'], languageOptions: { diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts b/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts index 616aa21ea32..5e2ae40d4e0 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts @@ -6,7 +6,7 @@ import { Barrier } from '../../../base/common/async.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; -import { IPCServer } from '../../../base/parts/ipc/common/ipc.js'; +import { IChannelServer } from '../../../base/parts/ipc/common/ipc.js'; import { IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; export class McpGalleryManifestIPCService extends Disposable implements IMcpGalleryManifestService { @@ -26,13 +26,16 @@ export class McpGalleryManifestIPCService extends Disposable implements IMcpGall return this._mcpGalleryManifest ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable; } - constructor(server: IPCServer) { + constructor(server: IChannelServer) { super(); server.registerChannel('mcpGalleryManifest', { listen: () => Event.None, - call: async (context: any, command: string, args?: any): Promise => { + call: async (context: unknown, command: string, args?: unknown): Promise => { switch (command) { - case 'setMcpGalleryManifest': return Promise.resolve(this.setMcpGalleryManifest(args[0])); + case 'setMcpGalleryManifest': { + const manifest = Array.isArray(args) ? args[0] as IMcpGalleryManifest | null : null; + return Promise.resolve(this.setMcpGalleryManifest(manifest)) as T; + } } throw new Error('Invalid call'); } diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 4bf369ff346..bc632213111 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -80,10 +80,11 @@ interface IRawGalleryMcpServer { } interface IGalleryMcpServerDataSerializer { - toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined; - toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined; + toRawGalleryMcpServerResult(input: unknown): IRawGalleryMcpServersResult | undefined; + toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined; } + namespace McpServerSchemaVersion_2025_07_09 { export const VERSION = '2025-07-09'; @@ -189,7 +190,7 @@ namespace McpServerSchemaVersion_2025_07_09 { readonly updated_at: string; readonly release_date?: string; }; - readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; + readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; }; } @@ -204,8 +205,8 @@ namespace McpServerSchemaVersion_2025_07_09 { class Serializer implements IGalleryMcpServerDataSerializer { - public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { - if (!input || !Array.isArray(input.servers)) { + public toRawGalleryMcpServerResult(input: unknown): IRawGalleryMcpServersResult | undefined { + if (!input || typeof input !== 'object' || !Array.isArray((input as RawGalleryMcpServersResult).servers)) { return undefined; } @@ -226,8 +227,8 @@ namespace McpServerSchemaVersion_2025_07_09 { }; } - public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || (input).$schema !== McpServerSchemaVersion_2025_07_09.SCHEMA) { + public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { + if (!input || typeof input !== 'object' || (input).$schema !== McpServerSchemaVersion_2025_07_09.SCHEMA) { return undefined; } @@ -359,7 +360,7 @@ namespace McpServerSchemaVersion_2025_07_09 { publishedAt: registryInfo.published_at, updatedAt: registryInfo.updated_at, }, - githubInfo: from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github, + githubInfo: from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, }; } } @@ -455,7 +456,7 @@ namespace McpServerSchemaVersion_2025_29_09 { readonly packages?: readonly RawGalleryMcpServerPackage[]; readonly remotes?: RawGalleryMcpServerRemotes; readonly _meta: { - readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; + readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; }; } @@ -484,8 +485,8 @@ namespace McpServerSchemaVersion_2025_29_09 { class Serializer implements IGalleryMcpServerDataSerializer { - public toRawGalleryMcpServerResult(input: any): IRawGalleryMcpServersResult | undefined { - if (!input || !Array.isArray(input.servers)) { + public toRawGalleryMcpServerResult(input: unknown): IRawGalleryMcpServersResult | undefined { + if (!input || typeof input !== 'object' || !Array.isArray((input as RawGalleryMcpServersResult).servers)) { return undefined; } @@ -506,8 +507,8 @@ namespace McpServerSchemaVersion_2025_29_09 { }; } - public toRawGalleryMcpServer(input: any): IRawGalleryMcpServer | undefined { - if (!input || !(input).server) { + public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { + if (!input || typeof input !== 'object' || !(input).server) { return undefined; } @@ -533,7 +534,7 @@ namespace McpServerSchemaVersion_2025_29_09 { remotes: from.server.remotes, status: from._meta['io.modelcontextprotocol.registry/official'].status, registryInfo: from._meta['io.modelcontextprotocol.registry/official'], - githubInfo: from.server._meta['io.modelcontextprotocol.registry/publisher-provided']?.github, + githubInfo: from.server._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, }; } } @@ -900,7 +901,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return this.toGalleryMcpServer(server, mcpGalleryManifest); } - private serializeMcpServer(data: any): IRawGalleryMcpServer | undefined { + private serializeMcpServer(data: unknown): IRawGalleryMcpServer | undefined { for (const [, serializer] of this.galleryMcpServerDataSerializers) { const result = serializer.toRawGalleryMcpServer(data); if (result) { @@ -910,7 +911,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return undefined; } - private serializeMcpServersResult(data: any): IRawGalleryMcpServersResult | undefined { + private serializeMcpServersResult(data: unknown): IRawGalleryMcpServersResult | undefined { for (const [, serializer] of this.galleryMcpServerDataSerializers) { const result = serializer.toRawGalleryMcpServerResult(data); if (result) { diff --git a/src/vs/platform/mcp/common/mcpManagementIpc.ts b/src/vs/platform/mcp/common/mcpManagementIpc.ts index 4790599c82c..733319fd216 100644 --- a/src/vs/platform/mcp/common/mcpManagementIpc.ts +++ b/src/vs/platform/mcp/common/mcpManagementIpc.ts @@ -9,6 +9,7 @@ import { URI, UriComponents } from '../../../base/common/uri.js'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from '../../../base/common/uriIpc.js'; import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; import { ILogService } from '../../log/common/log.js'; +import { RemoteAgentConnectionContext } from '../../remote/common/remoteAgentEnvironment.js'; import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpManagementService, IInstallableMcpServer, InstallMcpServerEvent, InstallMcpServerResult, InstallOptions, UninstallMcpServerEvent, UninstallOptions, IAllowedMcpServersService } from './mcpManagement.js'; import { AbstractMcpManagementService } from './mcpManagementService.js'; @@ -37,14 +38,14 @@ function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): UR return transformer ? transformer.transformOutgoingURI(uri) : uri; } -export class McpManagementChannel implements IServerChannel { +export class McpManagementChannel implements IServerChannel { readonly onInstallMcpServer: Event; readonly onDidInstallMcpServers: Event; readonly onDidUpdateMcpServers: Event; readonly onUninstallMcpServer: Event; readonly onDidUninstallMcpServer: Event; - constructor(private service: IMcpManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) { + constructor(private service: IMcpManagementService, private getUriTransformer: (requestContext: TContext) => IURITransformer | null) { this.onInstallMcpServer = Event.buffer(service.onInstallMcpServer, true); this.onDidInstallMcpServers = Event.buffer(service.onDidInstallMcpServers, true); this.onDidUpdateMcpServers = Event.buffer(service.onDidUpdateMcpServers, true); @@ -52,13 +53,13 @@ export class McpManagementChannel implements IServerChannel { this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, true); } - listen(context: any, event: string): Event { + listen(context: TContext, event: string): Event { const uriTransformer = this.getUriTransformer(context); switch (event) { case 'onInstallMcpServer': { return Event.map(this.onInstallMcpServer, event => { return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; - }); + }) as Event; } case 'onDidInstallMcpServers': { return Event.map(this.onDidInstallMcpServers, results => @@ -66,7 +67,7 @@ export class McpManagementChannel implements IServerChannel { ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local, mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer) - }))); + }))) as Event; } case 'onDidUpdateMcpServers': { return Event.map(this.onDidUpdateMcpServers, results => @@ -74,41 +75,42 @@ export class McpManagementChannel implements IServerChannel { ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local, mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer) - }))); + }))) as Event; } case 'onUninstallMcpServer': { return Event.map(this.onUninstallMcpServer, event => { return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; - }); + }) as Event; } case 'onDidUninstallMcpServer': { return Event.map(this.onDidUninstallMcpServer, event => { return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; - }); + }) as Event; } } throw new Error('Invalid listen'); } - async call(context: any, command: string, args?: any): Promise { + async call(context: TContext, command: string, args?: unknown): Promise { const uriTransformer: IURITransformer | null = this.getUriTransformer(context); + const argsArray = Array.isArray(args) ? args : []; switch (command) { case 'getInstalled': { - const mcpServers = await this.service.getInstalled(transformIncomingURI(args[0], uriTransformer)); - return mcpServers.map(e => transformOutgoingExtension(e, uriTransformer)); + const mcpServers = await this.service.getInstalled(transformIncomingURI(argsArray[0], uriTransformer)); + return mcpServers.map(e => transformOutgoingExtension(e, uriTransformer)) as T; } case 'install': { - return this.service.install(args[0], transformIncomingOptions(args[1], uriTransformer)); + return this.service.install(argsArray[0], transformIncomingOptions(argsArray[1], uriTransformer)) as T; } case 'installFromGallery': { - return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer)); + return this.service.installFromGallery(argsArray[0], transformIncomingOptions(argsArray[1], uriTransformer)) as T; } case 'uninstall': { - return this.service.uninstall(transformIncomingServer(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); + return this.service.uninstall(transformIncomingServer(argsArray[0], uriTransformer), transformIncomingOptions(argsArray[1], uriTransformer)) as T; } case 'updateMetadata': { - return this.service.updateMetadata(transformIncomingServer(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer)); + return this.service.updateMetadata(transformIncomingServer(argsArray[0], uriTransformer), argsArray[1], transformIncomingURI(argsArray[2], uriTransformer)) as T; } } diff --git a/src/vs/platform/mcp/node/nativeMcpDiscoveryHelperChannel.ts b/src/vs/platform/mcp/node/nativeMcpDiscoveryHelperChannel.ts index b09f36ba58e..ba686da6fc2 100644 --- a/src/vs/platform/mcp/node/nativeMcpDiscoveryHelperChannel.ts +++ b/src/vs/platform/mcp/node/nativeMcpDiscoveryHelperChannel.ts @@ -6,25 +6,26 @@ import { Event } from '../../../base/common/event.js'; import { IURITransformer, transformOutgoingURIs } from '../../../base/common/uriIpc.js'; import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { RemoteAgentConnectionContext } from '../../remote/common/remoteAgentEnvironment.js'; import { INativeMcpDiscoveryHelperService } from '../common/nativeMcpDiscoveryHelper.js'; -export class NativeMcpDiscoveryHelperChannel implements IServerChannel { +export class NativeMcpDiscoveryHelperChannel implements IServerChannel { constructor( - private getUriTransformer: undefined | ((requestContext: any) => IURITransformer), + private readonly getUriTransformer: undefined | ((requestContext: RemoteAgentConnectionContext) => IURITransformer), @INativeMcpDiscoveryHelperService private nativeMcpDiscoveryHelperService: INativeMcpDiscoveryHelperService ) { } - listen(context: any, event: string): Event { + listen(context: RemoteAgentConnectionContext, event: string): Event { throw new Error('Invalid listen'); } - async call(context: any, command: string, args?: any): Promise { + async call(context: RemoteAgentConnectionContext, command: string, args?: unknown): Promise { const uriTransformer = this.getUriTransformer?.(context); switch (command) { case 'load': { const result = await this.nativeMcpDiscoveryHelperService.load(); - return uriTransformer ? transformOutgoingURIs(result, uriTransformer) : result; + return (uriTransformer ? transformOutgoingURIs(result, uriTransformer) : result) as T; } } throw new Error('Invalid call'); diff --git a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts index d528842930c..daf8d844e70 100644 --- a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts +++ b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts @@ -870,19 +870,44 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { } }); - test('named argument with missing name should generate notice', () => { + test('named argument with no name should generate notice', () => { + const manifest = { + packages: [{ + registryType: RegistryType.NODE, + identifier: 'test-server', + version: '1.0.0', + runtimeArguments: [{ + type: 'named', + value: 'some-value', + isRepeated: false + }] + }] + }; + + const result = service.getMcpServerConfigurationFromManifest(manifest as IGalleryMcpServerConfiguration, RegistryType.NODE); + + // Should generate a notice about the missing name + assert.strictEqual(result.notices.length, 1); + assert.ok(result.notices[0].includes('Named argument is missing a name')); + assert.ok(result.notices[0].includes('some-value')); // Should include the argument details in JSON format + + if (result.mcpServerConfiguration.config.type === McpServerType.LOCAL) { + assert.deepStrictEqual(result.mcpServerConfiguration.config.args, ['test-server@1.0.0']); + } + }); + + test('named argument with empty name should generate notice', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', version: '1.0.0', - // eslint-disable-next-line local/code-no-any-casts runtimeArguments: [{ type: 'named', - // name is intentionally missing/undefined + name: '', value: 'some-value', isRepeated: false - } as any] // Cast to any to bypass TypeScript validation for this test case + }] }] }; From ce3c3a3b199bd5d883486133b8e174089d300426 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 7 Oct 2025 10:53:25 -0700 Subject: [PATCH 0920/4355] debug: fix instantiationService has been disposed (#270245) Fixes #226059 --- .../contrib/debug/browser/breakpointEditorContribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 7a2073c81d4..a8f46c2e44b 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -680,6 +680,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi dispose(): void { this.breakpointWidget?.dispose(); + this.setDecorationsScheduler.dispose(); this.editor.removeDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId)); dispose(this.toDispose); } From ef4000ad7f3fdbc67815281c7cd08e1b7358f2a2 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 7 Oct 2025 11:04:20 -0700 Subject: [PATCH 0921/4355] also prevent enter key when a request is in progress --- src/vs/base/browser/keyboardEvent.ts | 7 +++++++ .../contrib/chat/browser/chatInputPart.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 4e98a3b12d8..a446ba43390 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -114,6 +114,13 @@ export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string { return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`; } +/** + * Checks if a keyboard event has no modifier keys pressed + */ +export function hasNoModifierKeys(e: IKeyboardEvent): boolean { + return !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey; +} + export class StandardKeyboardEvent implements IKeyboardEvent { readonly _standardKeyboardEventBrand = true; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 0f1dbae09ad..53ea272a85e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { addDisposableListener } from '../../../../base/browser/dom.js'; import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; -import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { hasNoModifierKeys, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/button.js'; @@ -1162,6 +1162,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); ChatContextKeys.inChatInput.bindTo(inputScopedContextKeyService).set(true); this.currentlyEditingInputKey = ChatContextKeys.currentlyEditingInput.bindTo(inputScopedContextKeyService); + // Track whether a request is currently being processed so we can suppress plain Enter behavior + const requestInProgressKey = ChatContextKeys.requestInProgress.bindTo(inputScopedContextKeyService); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); @@ -1199,12 +1201,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.overflowWidgetsDomNode?.classList.add('hideSuggestTextIcons'); this._inputEditorElement.classList.add('hideSuggestTextIcons'); - // Prevent Enter key from creating new lines when input is empty + // Prevent Enter key from creating new lines when input is empty or a request is currently in progress. this._register(this._inputEditor.onKeyDown((e) => { - if (e.keyCode === KeyCode.Enter && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) { - const model = this._inputEditor.getModel(); - const inputHasText = !!model && model.getValue().trim().length > 0; - if (!inputHasText) { + if (e.keyCode === KeyCode.Enter && hasNoModifierKeys(e)) { + const inputHasText = this.inputEditorHasText?.get(); + if (!inputHasText || requestInProgressKey.get()) { + // Suppress newline insertion to mirror disabled send state e.preventDefault(); e.stopPropagation(); } From d1d275ca091ccebdb248ae53c657b1b3978f3611 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 7 Oct 2025 14:41:36 -0400 Subject: [PATCH 0922/4355] address terminal completion provider API feedback (#270249) commit api feedback --- .../terminal-suggest/src/completions/gh.ts | 2 +- .../terminal-suggest/src/completions/git.ts | 18 +- .../terminal-suggest/src/fig/figInterface.ts | 70 +++---- .../src/terminalSuggestMain.ts | 42 ++-- .../src/test/terminalSuggestMain.test.ts | 14 +- .../terminal-suggest/src/test/tokens.test.ts | 66 +++---- extensions/terminal-suggest/src/tokens.ts | 4 +- .../api/browser/mainThreadTerminalService.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 14 +- .../api/common/extHostTypeConverters.ts | 12 +- src/vs/workbench/api/common/extHostTypes.ts | 22 +-- .../browser/terminalCompletionService.ts | 86 ++++----- .../browser/terminalCompletionService.test.ts | 180 +++++++++--------- ...e.proposed.terminalCompletionProvider.d.ts | 50 +++-- 14 files changed, 300 insertions(+), 288 deletions(-) diff --git a/extensions/terminal-suggest/src/completions/gh.ts b/extensions/terminal-suggest/src/completions/gh.ts index 0c06cc35d7f..ea163beeb7f 100644 --- a/extensions/terminal-suggest/src/completions/gh.ts +++ b/extensions/terminal-suggest/src/completions/gh.ts @@ -50,7 +50,7 @@ const postProcessRemoteBranches: Fig.Generator["postProcess"] = (out) => { return { name, description: "Branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, priority: 75, }; }); diff --git a/extensions/terminal-suggest/src/completions/git.ts b/extensions/terminal-suggest/src/completions/git.ts index 13c7739407a..7c0f74fb843 100644 --- a/extensions/terminal-suggest/src/completions/git.ts +++ b/extensions/terminal-suggest/src/completions/git.ts @@ -89,7 +89,7 @@ const postProcessBranches = name: branch.replace("*", "").trim(), description: "Current branch", priority: 100, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` }; } else if (parts[0] === "+") { // Branch checked out in another worktree. @@ -112,7 +112,7 @@ const postProcessBranches = return { name, description, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, priority: 75, }; }) @@ -148,7 +148,7 @@ export const gitGenerators = { return lines.map((line) => { return { name: line.substring(0, hashLength), - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Commit}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, description: line.substring(descriptionStart), }; }); @@ -194,7 +194,7 @@ export const gitGenerators = { return output.split("\n").map((line) => { return { name: line.substring(0, 7), - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Commit}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, description: line.substring(7), }; }); @@ -217,7 +217,7 @@ export const gitGenerators = { // account for conventional commit messages name: file.split(":").slice(2).join(":"), insertValue: file.split(":")[0], - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Stash}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmStash}`, }; }); }, @@ -329,7 +329,7 @@ export const gitGenerators = { return Object.keys(remoteURLs).map((remote) => { return { name: remote, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Remote}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmRemote}`, description: "Remote", }; }); @@ -347,7 +347,7 @@ export const gitGenerators = { postProcess: function (output) { return output.split("\n").map((tag) => ({ name: tag, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Tag}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmTag}` })); }, } satisfies Fig.Generator, @@ -8117,7 +8117,7 @@ const completionSpec: Fig.Spec = { { name: "-", description: "Switch to the last used branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` }, { name: "--", @@ -9283,7 +9283,7 @@ const completionSpec: Fig.Spec = { { name: "-", description: "Switch to the last used branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` }, ], }, diff --git a/extensions/terminal-suggest/src/fig/figInterface.ts b/extensions/terminal-suggest/src/fig/figInterface.ts index 35a8e12747b..387eaa340ef 100644 --- a/extensions/terminal-suggest/src/fig/figInterface.ts +++ b/extensions/terminal-suggest/src/fig/figInterface.ts @@ -20,8 +20,8 @@ import { asArray, availableSpecs } from '../terminalSuggestMain'; import { IFigExecuteExternals } from './execute'; export interface IFigSpecSuggestionsResult { - filesRequested: boolean; - foldersRequested: boolean; + showFiles: boolean; + showFolders: boolean; fileExtensions?: string[]; hasCurrentArg: boolean; items: vscode.TerminalCompletionItem[]; @@ -29,7 +29,7 @@ export interface IFigSpecSuggestionsResult { export async function getFigSuggestions( specs: Fig.Spec[], - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, availableCommands: ICompletionResource[], currentCommandAndArgString: string, tokenType: TokenType, @@ -40,8 +40,8 @@ export async function getFigSuggestions( token?: vscode.CancellationToken, ): Promise { const result: IFigSpecSuggestionsResult = { - filesRequested: false, - foldersRequested: false, + showFiles: false, + showFolders: false, hasCurrentArg: false, items: [], }; @@ -75,7 +75,7 @@ export async function getFigSuggestions( if (availableCommand.kind !== vscode.TerminalCompletionItemKind.Alias) { const description = getFixSuggestionDescription(spec); result.items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, currentCommandAndArgString, { label: { label: specLabel, description }, @@ -106,8 +106,8 @@ export async function getFigSuggestions( const completionItemResult = await getFigSpecSuggestions(actualSpec, terminalContext, currentCommandAndArgString, shellIntegrationCwd, env, name, executeExternals, token); result.hasCurrentArg ||= !!completionItemResult?.hasCurrentArg; if (completionItemResult) { - result.filesRequested ||= completionItemResult.filesRequested; - result.foldersRequested ||= completionItemResult.foldersRequested; + result.showFiles ||= completionItemResult.showFiles; + result.showFolders ||= completionItemResult.showFolders; result.fileExtensions ||= completionItemResult.fileExtensions; if (completionItemResult.items) { result.items = result.items.concat(completionItemResult.items); @@ -120,7 +120,7 @@ export async function getFigSuggestions( async function getFigSpecSuggestions( spec: Fig.Spec, - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, prefix: string, shellIntegrationCwd: vscode.Uri | undefined, env: Record, @@ -128,11 +128,11 @@ async function getFigSpecSuggestions( executeExternals: IFigExecuteExternals, token?: vscode.CancellationToken, ): Promise { - let filesRequested = false; - let foldersRequested = false; + let showFiles = false; + let showFolders = false; let fileExtensions: string[] | undefined; - const command = getCommand(terminalContext.commandLine, {}, terminalContext.cursorPosition); + const command = getCommand(terminalContext.commandLine, {}, terminalContext.cursorIndex); if (!command || !shellIntegrationCwd) { return; } @@ -153,14 +153,14 @@ async function getFigSpecSuggestions( } if (completionItemResult) { - filesRequested = completionItemResult.filesRequested; - foldersRequested = completionItemResult.foldersRequested; + showFiles = completionItemResult.showFiles; + showFolders = completionItemResult.showFolders; fileExtensions = completionItemResult.fileExtensions; } return { - filesRequested, - foldersRequested, + showFiles: showFiles, + showFolders: showFolders, fileExtensions, hasCurrentArg: !!parsedArguments.currentArg, items, @@ -173,14 +173,14 @@ export async function collectCompletionItemResult( command: Command, parsedArguments: ArgumentParserResult, prefix: string, - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, shellIntegrationCwd: vscode.Uri | undefined, env: Record, items: vscode.TerminalCompletionItem[], executeExternals: IFigExecuteExternals -): Promise<{ filesRequested: boolean; foldersRequested: boolean; fileExtensions: string[] | undefined } | undefined> { - let filesRequested = false; - let foldersRequested = false; +): Promise<{ showFiles: boolean; showFolders: boolean; fileExtensions: string[] | undefined } | undefined> { + let showFiles = false; + let showFolders = false; let fileExtensions: string[] | undefined; const addSuggestions = async (specArgs: SpecArg[] | Record | undefined, kind: vscode.TerminalCompletionItemKind, parsedArguments?: ArgumentParserResult) => { @@ -188,7 +188,7 @@ export async function collectCompletionItemResult( const generators = parsedArguments.currentArg.generators; const initialFigState: FigState = { buffer: terminalContext.commandLine, - cursorLocation: terminalContext.cursorPosition, + cursorLocation: terminalContext.cursorIndex, cwd: shellIntegrationCwd?.fsPath ?? null, processUserIsIn: null, sshContextString: null, @@ -222,12 +222,12 @@ export async function collectCompletionItemResult( for (const generatorResult of generatorResults) { for (const item of (await generatorResult?.request) ?? []) { if (item.type === 'file') { - filesRequested = true; - foldersRequested = true; + showFiles = true; + showFolders = true; fileExtensions = item._internal?.fileExtensions as string[] | undefined; } if (item.type === 'folder') { - foldersRequested = true; + showFolders = true; } if (!item.name) { @@ -239,7 +239,7 @@ export async function collectCompletionItemResult( } for (const label of suggestionLabels) { items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, prefix, { label }, item.displayName, @@ -256,16 +256,16 @@ export async function collectCompletionItemResult( const templates = Array.isArray(generator.template) ? generator.template : [generator.template]; for (const template of templates) { if (template === 'filepaths') { - filesRequested = true; + showFiles = true; } else if (template === 'folders') { - foldersRequested = true; + showFolders = true; } } } } } if (!specArgs) { - return { filesRequested, foldersRequested }; + return { showFiles, showFolders }; } const flagsToExclude = kind === vscode.TerminalCompletionItemKind.Flag ? parsedArguments?.passedOptions.map(option => option.name).flat() : undefined; @@ -304,7 +304,7 @@ export async function collectCompletionItemResult( items.push( createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, prefix, { label: detail ? { label, detail } : label @@ -343,7 +343,7 @@ export async function collectCompletionItemResult( await addSuggestions(parsedArguments.completionObj.options, vscode.TerminalCompletionItemKind.Flag, parsedArguments); } - return { filesRequested, foldersRequested, fileExtensions }; + return { showFiles, showFolders, fileExtensions }; } function convertEnvRecordToArray(env: Record): EnvironmentVariable[] { @@ -372,11 +372,11 @@ export function getFigSuggestionLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion function convertIconToKind(icon: string | undefined): vscode.TerminalCompletionItemKind | undefined { switch (icon) { - case 'vscode://icon?type=10': return vscode.TerminalCompletionItemKind.Commit; - case 'vscode://icon?type=11': return vscode.TerminalCompletionItemKind.Branch; - case 'vscode://icon?type=12': return vscode.TerminalCompletionItemKind.Tag; - case 'vscode://icon?type=13': return vscode.TerminalCompletionItemKind.Stash; - case 'vscode://icon?type=14': return vscode.TerminalCompletionItemKind.Remote; + case 'vscode://icon?type=10': return vscode.TerminalCompletionItemKind.ScmCommit; + case 'vscode://icon?type=11': return vscode.TerminalCompletionItemKind.ScmBranch; + case 'vscode://icon?type=12': return vscode.TerminalCompletionItemKind.ScmTag; + case 'vscode://icon?type=13': return vscode.TerminalCompletionItemKind.ScmStash; + case 'vscode://icon?type=14': return vscode.TerminalCompletionItemKind.ScmRemote; case 'vscode://icon?type=15': return vscode.TerminalCompletionItemKind.PullRequest; case 'vscode://icon?type=16': return vscode.TerminalCompletionItemKind.PullRequestDone; default: return undefined; diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 9db6122f729..45805d9def4 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -274,7 +274,7 @@ export async function activate(context: vscode.ExtensionContext) { } // Order is important here, add shell globals first so they are prioritized over path commands const commands = [...shellGlobals, ...commandsInPath.completionResources]; - const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorPosition, terminalShellType); + const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorIndex, terminalShellType); const pathSeparator = isWindows ? '\\' : '/'; const tokenType = getTokenType(terminalContext, terminalShellType); const result = await Promise.race([ @@ -305,11 +305,11 @@ export async function activate(context: vscode.ExtensionContext) { } const cwd = result.cwd ?? terminal.shellIntegration?.cwd; - if (cwd && (result.filesRequested || result.foldersRequested)) { + if (cwd && (result.showFiles || result.showFolders)) { const globPattern = createFileGlobPattern(result.fileExtensions); return new vscode.TerminalCompletionList(result.items, { - filesRequested: result.filesRequested, - foldersRequested: result.foldersRequested, + showFiles: result.showFiles, + showDirectories: result.showFolders, globPattern, cwd, }); @@ -370,7 +370,7 @@ export async function resolveCwdFromCurrentCommandString(currentCommandString: s // Retrurns the string that represents the current command and its arguments up to the cursor position. // Uses shell specific separators to determine the current command and its arguments. -export function getCurrentCommandAndArgs(commandLine: string, cursorPosition: number, shellType: TerminalShellType | undefined): string { +export function getCurrentCommandAndArgs(commandLine: string, cursorIndex: number, shellType: TerminalShellType | undefined): string { // Return an empty string if the command line is empty after trimming if (commandLine.trim() === '') { @@ -378,12 +378,12 @@ export function getCurrentCommandAndArgs(commandLine: string, cursorPosition: nu } // Check if cursor is not at the end and there's non-whitespace after the cursor - if (cursorPosition < commandLine.length && /\S/.test(commandLine[cursorPosition])) { + if (cursorIndex < commandLine.length && /\S/.test(commandLine[cursorIndex])) { return ''; } // Extract the part of the line up to the cursor position - const beforeCursor = commandLine.slice(0, cursorPosition); + const beforeCursor = commandLine.slice(0, cursorIndex); const resetChars = shellType ? shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars : defaultShellTypeResetChars; // Find the last reset character before the cursor @@ -419,10 +419,10 @@ export async function getCompletionItemsFromSpecs( name: string, token?: vscode.CancellationToken, executeExternals?: IFigExecuteExternals, -): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; fileExtensions?: string[]; cwd?: vscode.Uri }> { +): Promise<{ items: vscode.TerminalCompletionItem[]; showFiles: boolean; showFolders: boolean; fileExtensions?: string[]; cwd?: vscode.Uri }> { let items: vscode.TerminalCompletionItem[] = []; - let filesRequested = false; - let foldersRequested = false; + let showFiles = false; + let showFolders = false; let hasCurrentArg = false; let fileExtensions: string[] | undefined; @@ -455,8 +455,8 @@ export async function getCompletionItemsFromSpecs( const result = await getFigSuggestions(specs, terminalContext, availableCommands, currentCommandString, tokenType, shellIntegrationCwd, env, name, executeExternalsWithFallback, token); if (result) { hasCurrentArg ||= result.hasCurrentArg; - filesRequested ||= result.filesRequested; - foldersRequested ||= result.foldersRequested; + showFiles ||= result.showFiles; + showFolders ||= result.showFolders; fileExtensions = result.fileExtensions; if (result.items) { items = items.concat(result.items); @@ -472,7 +472,7 @@ export async function getCompletionItemsFromSpecs( const labelWithoutExtension = isWindows ? commandTextLabel.replace(/\.[^ ]+$/, '') : commandTextLabel; if (!labels.has(labelWithoutExtension)) { items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, currentCommandString, command, command.detail, @@ -491,23 +491,23 @@ export async function getCompletionItemsFromSpecs( existingItem.detail ??= command.detail; } } - filesRequested = true; - foldersRequested = true; + showFiles = true; + showFolders = true; } // For arguments when no fig suggestions are found these are fallback suggestions - else if (!items.length && !filesRequested && !foldersRequested && !hasCurrentArg) { + else if (!items.length && !showFiles && !showFolders && !hasCurrentArg) { if (terminalContext.allowFallbackCompletions) { - filesRequested = true; - foldersRequested = true; + showFiles = true; + showFolders = true; } } let cwd: vscode.Uri | undefined; - if (shellIntegrationCwd && (filesRequested || foldersRequested)) { + if (shellIntegrationCwd && (showFiles || showFolders)) { cwd = await resolveCwdFromCurrentCommandString(currentCommandString, shellIntegrationCwd); } - return { items, filesRequested, foldersRequested, fileExtensions, cwd }; + return { items, showFiles: showFiles, showFolders: showFolders, fileExtensions, cwd }; } function getEnvAsRecord(shellIntegrationEnv: ITerminalEnvironment): Record { @@ -564,7 +564,7 @@ export function sanitizeProcessEnvironment(env: Record, ...prese }); } -function createFileGlobPattern(fileExtensions?: string[]): vscode.GlobPattern | undefined { +function createFileGlobPattern(fileExtensions?: string[]): string | undefined { if (!fileExtensions || fileExtensions.length === 0) { return undefined; } diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 5f3fb96a72c..e60099c6089 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -91,11 +91,11 @@ suite('Terminal Suggest', () => { } test(`'${testSpec.input}' -> ${expectedString}`, async () => { const commandLine = testSpec.input.split('|')[0]; - const cursorPosition = testSpec.input.indexOf('|'); - const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorPosition, undefined); - const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; - const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; - const terminalContext = { commandLine, cursorPosition, allowFallbackCompletions: true }; + const cursorIndex = testSpec.input.indexOf('|'); + const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorIndex, undefined); + const showFiles = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; + const showFolders = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; + const terminalContext = { commandLine, cursorIndex, allowFallbackCompletions: true }; const result = await getCompletionItemsFromSpecs( completionSpecs, terminalContext, @@ -118,8 +118,8 @@ suite('Terminal Suggest', () => { }).sort(), (testSpec.expectedCompletions ?? []).sort() ); - strictEqual(result.filesRequested, filesRequested, 'Files requested different than expected, got: ' + result.filesRequested); - strictEqual(result.foldersRequested, foldersRequested, 'Folders requested different than expected, got: ' + result.foldersRequested); + strictEqual(result.showFiles, showFiles, 'Show files different than expected, got: ' + result.showFiles); + strictEqual(result.showFolders, showFolders, 'Show folders different than expected, got: ' + result.showFolders); if (testSpec.expectedResourceRequests?.cwd) { strictEqual(result.cwd?.fsPath, testSpec.expectedResourceRequests.cwd.fsPath, 'Non matching cwd'); } diff --git a/extensions/terminal-suggest/src/test/tokens.test.ts b/extensions/terminal-suggest/src/test/tokens.test.ts index fb3ea702bab..dc583a28b08 100644 --- a/extensions/terminal-suggest/src/test/tokens.test.ts +++ b/extensions/terminal-suggest/src/test/tokens.test.ts @@ -10,91 +10,91 @@ import { TerminalShellType } from '../terminalSuggestMain'; suite('Terminal Suggest', () => { test('simple command', () => { - strictEqual(getTokenType({ commandLine: 'echo', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo', cursorIndex: 'echo'.length }, undefined), TokenType.Command); }); test('simple argument', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hello'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo hello'.length }, undefined), TokenType.Argument); }); test('simple command, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo'.length }, undefined), TokenType.Command); }); test('simple argument, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hel'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo hel'.length }, undefined), TokenType.Argument); }); suite('reset to command', () => { test('|', () => { - strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorPosition: 'echo hello | '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorIndex: 'echo hello | '.length }, undefined), TokenType.Command); }); test(';', () => { - strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorPosition: 'echo hello; '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorIndex: 'echo hello; '.length }, undefined), TokenType.Command); }); test('&&', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorPosition: 'echo hello && '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorIndex: 'echo hello && '.length }, undefined), TokenType.Command); }); test('||', () => { - strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorPosition: 'echo hello || '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorIndex: 'echo hello || '.length }, undefined), TokenType.Command); }); }); suite('pwsh', () => { test('simple command', () => { - strictEqual(getTokenType({ commandLine: 'Write-Host', cursorPosition: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'Write-Host', cursorIndex: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('simple argument', () => { - strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorPosition: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorIndex: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorPosition: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorIndex: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('arguments after reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorIndex: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('; reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello; `, cursorPosition: `Write-Host hello; `.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: `Write-Host hello; `, cursorIndex: `Write-Host hello; `.length }, TerminalShellType.PowerShell), TokenType.Command); }); suite('multiple commands on the line', () => { test('multiple commands, cursor at end', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, undefined), TokenType.Command); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Bash), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, TerminalShellType.Bash), TokenType.Command); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Zsh), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, TerminalShellType.Zsh), TokenType.Command); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.Fish), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; ech'.length }, TerminalShellType.Fish), TokenType.Command); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; ech'.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('multiple commands, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, undefined), TokenType.Argument); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Bash), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, TerminalShellType.Bash), TokenType.Argument); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Zsh), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, TerminalShellType.Zsh), TokenType.Argument); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.Fish), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; echo w'.length }, TerminalShellType.Fish), TokenType.Argument); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; echo w'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('multiple commands, cursor at end with reset char', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, undefined), TokenType.Command); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Bash), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, TerminalShellType.Bash), TokenType.Command); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Zsh), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, TerminalShellType.Zsh), TokenType.Command); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.Fish), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo world; '.length }, TerminalShellType.Fish), TokenType.Command); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo world; '.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('multiple commands, cursor mid text with reset char', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, undefined), TokenType.Argument); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Bash), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, TerminalShellType.Bash), TokenType.Argument); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Zsh), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, TerminalShellType.Zsh), TokenType.Argument); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.Fish), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo worl'.length }, TerminalShellType.Fish), TokenType.Argument); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo worl'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); }); }); diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts index cadab15ba64..11a23f7ad43 100644 --- a/extensions/terminal-suggest/src/tokens.ts +++ b/extensions/terminal-suggest/src/tokens.ts @@ -19,9 +19,9 @@ export const shellTypeResetChars = new Map([ export const defaultShellTypeResetChars = shellTypeResetChars.get(TerminalShellType.Bash)!; -export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { +export function getTokenType(ctx: { commandLine: string; cursorIndex: number }, shellType: TerminalShellType | undefined): TokenType { const commandLine = ctx.commandLine; - const cursorPosition = ctx.cursorPosition; + const cursorPosition = ctx.cursorIndex; const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars; // Check for reset char before the current word diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 64afd9475cf..533e27a5d6e 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -278,18 +278,18 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._completionProviders.set(id, this._terminalCompletionService.registerTerminalCompletionProvider(extensionIdentifier, id, { id, provideCompletions: async (commandLine, cursorPosition, allowFallbackCompletions, token) => { - const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorPosition, allowFallbackCompletions }, token); + const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorIndex: cursorPosition, allowFallbackCompletions }, token); if (!completions) { return undefined; } - if (completions.resourceRequestConfig) { - const { cwd, globPattern, ...rest } = completions.resourceRequestConfig; + if (completions.resourceOptions) { + const { cwd, globPattern, ...rest } = completions.resourceOptions; return { items: completions.items?.map(c => ({ provider: `ext:${id}`, ...c, })), - resourceRequestConfig: { + resourceOptions: { ...rest, cwd, globPattern diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f7ca67e2a19..7177c83cdc0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2476,7 +2476,7 @@ export interface ITerminalCommandDto { export interface ITerminalCompletionContextDto { commandLine: string; - cursorPosition: number; + cursorIndex: number; allowFallbackCompletions: boolean; } @@ -2508,7 +2508,7 @@ export class TerminalCompletionListDto TerminalCompletionItemDto.from(i)), - resourceRequestConfig: completions.resourceRequestConfig ? TerminalResourceRequestConfig.from(completions.resourceRequestConfig, pathSeparator) : undefined, + resourceOptions: completions.resourceOptions ? TerminalCompletionResourceOptions.from(completions.resourceOptions, pathSeparator) : undefined, }; } } -export namespace TerminalResourceRequestConfig { - export function from(resourceRequestConfig: vscode.TerminalResourceRequestConfig, pathSeparator: string): extHostProtocol.TerminalResourceRequestConfigDto { +export namespace TerminalCompletionResourceOptions { + export function from(resourceOptions: vscode.TerminalCompletionResourceOptions, pathSeparator: string): extHostProtocol.TerminalCompletionResourceOptionsDto { return { - ...resourceRequestConfig, + ...resourceOptions, pathSeparator, - cwd: resourceRequestConfig.cwd, - globPattern: GlobPattern.from(resourceRequestConfig.globPattern) ?? undefined + cwd: resourceOptions.cwd, + globPattern: GlobPattern.from(resourceOptions.globPattern) ?? undefined }; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index ff5bf7abeb4..ca7116aba8b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -929,11 +929,11 @@ export enum TerminalCompletionItemKind { Flag = 7, SymbolicLinkFile = 8, SymbolicLinkFolder = 9, - Commit = 10, - Branch = 11, - Tag = 12, - Stash = 13, - Remote = 14, + ScmCommit = 10, + ScmBranch = 11, + ScmTag = 12, + ScmStash = 13, + ScmRemote = 14, PullRequest = 15, PullRequestDone = 16, } @@ -971,7 +971,7 @@ export class TerminalCompletionList { /** * Resources should be shown in the completions list */ - resourceRequestConfig?: TerminalResourceRequestConfig; + resourceOptions?: TerminalCompletionResourceOptions; /** * The completion items. @@ -47,15 +47,15 @@ export class TerminalCompletionList { * @param items The completion items. * @param isIncomplete The list is not complete. */ - constructor(items?: ITerminalCompletion[], resourceRequestConfig?: TerminalResourceRequestConfig) { + constructor(items?: ITerminalCompletion[], resourceOptions?: TerminalCompletionResourceOptions) { this.items = items; - this.resourceRequestConfig = resourceRequestConfig; + this.resourceOptions = resourceOptions; } } -export interface TerminalResourceRequestConfig { - filesRequested?: boolean; - foldersRequested?: boolean; +export interface TerminalCompletionResourceOptions { + showFiles?: boolean; + showFolders?: boolean; globPattern?: string | IRelativePattern; cwd: UriComponents; pathSeparator: string; @@ -223,8 +223,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (Array.isArray(completions)) { return completionItems; } - if (completions.resourceRequestConfig) { - const resourceCompletions = await this.resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition, `core:path:ext:${provider.id}`, capabilities, shellType); + if (completions.resourceOptions) { + const resourceCompletions = await this.resolveResources(completions.resourceOptions, promptValue, cursorPosition, `core:path:ext:${provider.id}`, capabilities, shellType); this._logService.trace(`TerminalCompletionService#_collectCompletions dedupe`); if (resourceCompletions) { const labels = new Set(completionItems.map(c => c.label)); @@ -245,22 +245,22 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return results.filter(result => !!result).flat(); } - async resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number, provider: string, capabilities: ITerminalCapabilityStore, shellType?: TerminalShellType): Promise { + async resolveResources(resourceOptions: TerminalCompletionResourceOptions, promptValue: string, cursorPosition: number, provider: string, capabilities: ITerminalCapabilityStore, shellType?: TerminalShellType): Promise { this._logService.trace(`TerminalCompletionService#resolveResources`); - const useWindowsStylePath = resourceRequestConfig.pathSeparator === '\\'; + const useWindowsStylePath = resourceOptions.pathSeparator === '\\'; if (useWindowsStylePath) { // for tests, make sure the right path separator is used - promptValue = promptValue.replaceAll(/[\\/]/g, resourceRequestConfig.pathSeparator); + promptValue = promptValue.replaceAll(/[\\/]/g, resourceOptions.pathSeparator); } // Files requested implies folders requested since the file could be in any folder. We could // provide diagnostics when a folder is provided where a file is expected. - const foldersRequested = (resourceRequestConfig.foldersRequested || resourceRequestConfig.filesRequested) ?? false; - const filesRequested = resourceRequestConfig.filesRequested ?? false; - const globPattern = resourceRequestConfig.globPattern ?? undefined; + const showFolders = (resourceOptions.showFolders || resourceOptions.showFiles) ?? false; + const showFiles = resourceOptions.showFiles ?? false; + const globPattern = resourceOptions.globPattern ?? undefined; - if (!foldersRequested && !filesRequested) { + if (!showFolders && !showFiles) { return; } @@ -288,7 +288,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } lastSlashIndex = Math.max(lastBackslashIndex, lastWord.lastIndexOf('/')); } else { - lastSlashIndex = lastWord.lastIndexOf(resourceRequestConfig.pathSeparator); + lastSlashIndex = lastWord.lastIndexOf(resourceOptions.pathSeparator); } // The _complete_ folder of the last word. For example if the last word is `./src/file`, @@ -304,9 +304,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo let lastWordFolderResource: URI | string | undefined; const lastWordFolderHasDotPrefix = !!lastWordFolder.match(/^\.\.?[\\\/]/); const lastWordFolderHasTildePrefix = !!lastWordFolder.match(/^~[\\\/]?/); - const isAbsolutePath = getIsAbsolutePath(shellType, resourceRequestConfig.pathSeparator, lastWordFolder, useWindowsStylePath); + const isAbsolutePath = getIsAbsolutePath(shellType, resourceOptions.pathSeparator, lastWordFolder, useWindowsStylePath); const type = lastWordFolderHasTildePrefix ? 'tilde' : isAbsolutePath ? 'absolute' : 'relative'; - const cwd = URI.revive(resourceRequestConfig.cwd); + const cwd = URI.revive(resourceOptions.cwd); switch (type) { case 'tilde': { @@ -374,7 +374,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // - (tilde) `~/|` -> `~/` // - (tilde) `~/src/|` -> `~/src/` this._logService.trace(`TerminalCompletionService#resolveResources cwd`); - if (foldersRequested) { + if (showFolders) { let label: string; switch (type) { case 'tilde': { @@ -388,7 +388,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo case 'relative': { label = '.'; if (lastWordFolder.length > 0) { - label = addPathRelativePrefix(lastWordFolder, resourceRequestConfig, lastWordFolderHasDotPrefix); + label = addPathRelativePrefix(lastWordFolder, resourceOptions, lastWordFolderHasDotPrefix); } break; } @@ -397,7 +397,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind: TerminalCompletionItemKind.Folder, - detail: getFriendlyPath(this._labelService, lastWordFolderResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), + detail: getFriendlyPath(this._labelService, lastWordFolderResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -412,13 +412,13 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo await Promise.all(stat.children.map(child => (async () => { let kind: TerminalCompletionItemKind | undefined; let detail: string | undefined = undefined; - if (foldersRequested && child.isDirectory) { + if (showFolders && child.isDirectory) { if (child.isSymbolicLink) { kind = TerminalCompletionItemKind.SymbolicLinkFolder; } else { kind = TerminalCompletionItemKind.Folder; } - } else if (filesRequested && child.isFile) { + } else if (showFiles && child.isFile) { if (child.isSymbolicLink) { kind = TerminalCompletionItemKind.SymbolicLinkFile; } else { @@ -430,18 +430,18 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } let label = lastWordFolder; - if (label.length > 0 && !label.endsWith(resourceRequestConfig.pathSeparator)) { - label += resourceRequestConfig.pathSeparator; + if (label.length > 0 && !label.endsWith(resourceOptions.pathSeparator)) { + label += resourceOptions.pathSeparator; } label += child.name; if (type === 'relative') { - label = addPathRelativePrefix(label, resourceRequestConfig, lastWordFolderHasDotPrefix); + label = addPathRelativePrefix(label, resourceOptions, lastWordFolderHasDotPrefix); } - if (child.isDirectory && !label.endsWith(resourceRequestConfig.pathSeparator)) { - label += resourceRequestConfig.pathSeparator; + if (child.isDirectory && !label.endsWith(resourceOptions.pathSeparator)) { + label += resourceOptions.pathSeparator; } - label = escapeTerminalCompletionLabel(label, shellType, resourceRequestConfig.pathSeparator); + label = escapeTerminalCompletionLabel(label, shellType, resourceOptions.pathSeparator); if (child.isFile && globPattern) { const filePath = child.resource.fsPath; @@ -456,7 +456,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo try { const realpath = await this._fileService.realpath(child.resource); if (realpath && !isEqual(child.resource, realpath)) { - detail = `${getFriendlyPath(this._labelService, child.resource, resourceRequestConfig.pathSeparator, kind, shellType)} -> ${getFriendlyPath(this._labelService, realpath, resourceRequestConfig.pathSeparator, kind, shellType)}`; + detail = `${getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType)} -> ${getFriendlyPath(this._labelService, realpath, resourceOptions.pathSeparator, kind, shellType)}`; } } catch (error) { // Ignore errors resolving symlink targets - they may be dangling links @@ -467,7 +467,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind, - detail: detail ?? getFriendlyPath(this._labelService, child.resource, resourceRequestConfig.pathSeparator, kind, shellType), + detail: detail ?? getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -477,7 +477,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // // - (relative) `|` -> `/foo/vscode` (CDPATH has /foo which contains vscode folder) this._logService.trace(`TerminalCompletionService#resolveResources CDPATH`); - if (type === 'relative' && foldersRequested) { + if (type === 'relative' && showFolders) { if (promptValue.startsWith('cd ')) { const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath); if (config === 'absolute' || config === 'relative') { @@ -498,9 +498,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo ? basename(child.resource.fsPath) : shellType === WindowsShellType.GitBash ? windowsToGitBashPath(child.resource.fsPath) - : getFriendlyPath(this._labelService, child.resource, resourceRequestConfig.pathSeparator, kind, shellType); + : getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType); const detail = useRelative - ? `CDPATH ${getFriendlyPath(this._labelService, child.resource, resourceRequestConfig.pathSeparator, kind, shellType)}` + ? `CDPATH ${getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType)}` : `CDPATH`; resourceCompletions.push({ label, @@ -524,17 +524,17 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // - (relative) `|` -> `../` // - (relative) `./src/|` -> `./src/../` this._logService.trace(`TerminalCompletionService#resolveResources parent dir`); - if (type === 'relative' && foldersRequested) { - let label = `..${resourceRequestConfig.pathSeparator}`; + if (type === 'relative' && showFolders) { + let label = `..${resourceOptions.pathSeparator}`; if (lastWordFolder.length > 0) { - label = addPathRelativePrefix(lastWordFolder + label, resourceRequestConfig, lastWordFolderHasDotPrefix); + label = addPathRelativePrefix(lastWordFolder + label, resourceOptions, lastWordFolderHasDotPrefix); } - const parentDir = URI.joinPath(cwd, '..' + resourceRequestConfig.pathSeparator); + const parentDir = URI.joinPath(cwd, '..' + resourceOptions.pathSeparator); resourceCompletions.push({ label, provider, kind: TerminalCompletionItemKind.Folder, - detail: getFriendlyPath(this._labelService, parentDir, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), + detail: getFriendlyPath(this._labelService, parentDir, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -560,7 +560,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label: '~', provider, kind: TerminalCompletionItemKind.Folder, - detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(this._labelService, homeResource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.Folder, shellType), + detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(this._labelService, homeResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); @@ -597,12 +597,12 @@ function getFriendlyPath(labelService: ILabelService, uri: URI, pathSeparator: s * Normalize suggestion to add a ./ prefix to the start of the path if there isn't one already. We * may want to change this behavior in the future to go with whatever format the user has. */ -function addPathRelativePrefix(text: string, resourceRequestConfig: Pick, lastWordFolderHasDotPrefix: boolean): string { +function addPathRelativePrefix(text: string, resourceOptions: Pick, lastWordFolderHasDotPrefix: boolean): string { if (!lastWordFolderHasDotPrefix) { - if (text.startsWith(resourceRequestConfig.pathSeparator)) { + if (text.startsWith(resourceOptions.pathSeparator)) { return `.${text}`; } - return `.${resourceRequestConfig.pathSeparator}${text}`; + return `.${resourceOptions.pathSeparator}${text}`; } return text; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 5094ba0acae..27a681094ae 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -5,7 +5,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { IFileService, IFileStatWithMetadata, IResolveMetadataFileOptions } from '../../../../../../platform/files/common/files.js'; -import { TerminalCompletionService, TerminalResourceRequestConfig, type ITerminalCompletionProvider } from '../../browser/terminalCompletionService.js'; +import { TerminalCompletionService, TerminalCompletionResourceOptions, type ITerminalCompletionProvider } from '../../browser/terminalCompletionService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import assert, { fail } from 'assert'; import { isWindows, type IProcessEnvironment } from '../../../../../../base/common/platform.js'; @@ -147,13 +147,13 @@ suite('TerminalCompletionService', () => { }); suite('resolveResources should return undefined', () => { - test('if neither filesRequested nor foldersRequested are true', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + test('if neither showFiles nor showFolders are true', async () => { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), pathSeparator }; validResources = [URI.parse('file:///test')]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ', 3, provider, capabilities); assert(!result); }); }); @@ -168,12 +168,12 @@ suite('TerminalCompletionService', () => { }); test('| should return root-level completions', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, '', 1, provider, capabilities); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -184,12 +184,12 @@ suite('TerminalCompletionService', () => { }); test('./| should return folder completions', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './', 3, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -199,12 +199,12 @@ suite('TerminalCompletionService', () => { }); test('cd ./| should return folder completions', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./', 5, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -213,12 +213,12 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 3, replacementLength: 2 }); }); test('cd ./f| should return folder completions', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./f', 6, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -240,13 +240,13 @@ suite('TerminalCompletionService', () => { }); test('./| should handle hidden files and folders', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './', 2, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -259,13 +259,13 @@ suite('TerminalCompletionService', () => { }); test('./h| should handle hidden files and folders', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './h', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './h', 3, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -279,7 +279,7 @@ suite('TerminalCompletionService', () => { }); suite('~ -> $HOME', () => { - let resourceRequestConfig: TerminalResourceRequestConfig; + let resourceOptions: TerminalCompletionResourceOptions; let shellEnvDetection: ShellEnvDetectionCapability; setup(() => { @@ -290,10 +290,10 @@ suite('TerminalCompletionService', () => { }, true); capabilities.add(TerminalCapability.ShellEnvDetection, shellEnvDetection); - resourceRequestConfig = { + resourceOptions = { cwd: URI.parse('file:///test/folder1'),// Updated to reflect home directory - filesRequested: true, - foldersRequested: true, + showFiles: true, + showFolders: true, pathSeparator }; validResources = [ @@ -312,20 +312,20 @@ suite('TerminalCompletionService', () => { }); test('~| should return completion for ~', async () => { - assertPartialCompletionsExist(await terminalCompletionService.resolveResources(resourceRequestConfig, '~', 1, provider, capabilities), [ + assertPartialCompletionsExist(await terminalCompletionService.resolveResources(resourceOptions, '~', 1, provider, capabilities), [ { label: '~', detail: '/home/' }, ], { replacementIndex: 0, replacementLength: 1 }); }); test('~/| should return folder completions relative to $HOME', async () => { - assertCompletions(await terminalCompletionService.resolveResources(resourceRequestConfig, '~/', 2, provider, capabilities), [ + assertCompletions(await terminalCompletionService.resolveResources(resourceOptions, '~/', 2, provider, capabilities), [ { label: '~/', detail: '/home/' }, { label: '~/vscode/', detail: '/home/vscode/' }, ], { replacementIndex: 0, replacementLength: 2 }); }); test('~/vscode/| should return folder completions relative to $HOME/vscode', async () => { - assertCompletions(await terminalCompletionService.resolveResources(resourceRequestConfig, '~/vscode/', 9, provider, capabilities), [ + assertCompletions(await terminalCompletionService.resolveResources(resourceOptions, '~/vscode/', 9, provider, capabilities), [ { label: '~/vscode/', detail: '/home/vscode/' }, { label: '~/vscode/foo/', detail: '/home/vscode/foo/' }, { label: '~/vscode/bar.txt', detail: '/home/vscode/bar.txt', kind: TerminalCompletionItemKind.File }, @@ -341,9 +341,9 @@ suite('TerminalCompletionService', () => { if (isWindows) { test('C:/Foo/| absolute paths on Windows', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///C:'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///C:/Foo')]; @@ -351,7 +351,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///C:/Foo/Bar'), isDirectory: true, isFile: false }, { resource: URI.parse('file:///C:/Foo/Baz.txt'), isDirectory: false, isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'C:/Foo/', 7, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'C:/Foo/', 7, provider, capabilities); assertCompletions(result, [ { label: 'C:/Foo/', detail: 'C:/Foo/' }, @@ -359,16 +359,16 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 0, replacementLength: 7 }); }); test('c:/foo/| case insensitivity on Windows', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///c:'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///c:/foo')]; childResources = [ { resource: URI.parse('file:///c:/foo/Bar'), isDirectory: true, isFile: false } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'c:/foo/', 7, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'c:/foo/', 7, provider, capabilities); assertCompletions(result, [ // Note that the detail is normalizes drive letters to capital case intentionally @@ -378,9 +378,9 @@ suite('TerminalCompletionService', () => { }); } else { test('/foo/| absolute paths NOT on Windows', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///foo')]; @@ -388,7 +388,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///foo/Bar'), isDirectory: true, isFile: false }, { resource: URI.parse('file:///foo/Baz.txt'), isDirectory: false, isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/foo/', 5, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, '/foo/', 5, provider, capabilities); assertCompletions(result, [ { label: '/foo/', detail: '/foo/' }, @@ -399,9 +399,9 @@ suite('TerminalCompletionService', () => { if (isWindows) { test('.\\folder | Case insensitivity should resolve correctly on Windows', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///C:/test'), - foldersRequested: true, + showFolders: true, pathSeparator: '\\' }; @@ -411,7 +411,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///C:/test/anotherFolder/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.\\folder', 8, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, '.\\folder', 8, provider, capabilities); assertCompletions(result, [ { label: '.\\', detail: 'C:\\test\\' }, @@ -422,9 +422,9 @@ suite('TerminalCompletionService', () => { }); } else { test('./folder | Case sensitivity should resolve correctly on Mac/Unix', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator: '/' }; validResources = [URI.parse('file:///test')]; @@ -433,7 +433,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/foldera/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder', 8, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './folder', 8, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -445,9 +445,9 @@ suite('TerminalCompletionService', () => { } test('| Empty input should resolve to current directory', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -455,7 +455,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, '', 0, provider, capabilities); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -467,9 +467,9 @@ suite('TerminalCompletionService', () => { }); test('./| should handle large directories with many results gracefully', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -477,7 +477,7 @@ suite('TerminalCompletionService', () => { resource: URI.parse(`file:///test/folder${i}/`), isDirectory: true })); - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './', 2, provider, capabilities); assert(result); // includes the 1000 folders + ./ and ./../ @@ -487,9 +487,9 @@ suite('TerminalCompletionService', () => { }); test('./folder| should include current folder with trailing / is missing', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -497,7 +497,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder1', 10, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, './folder1', 10, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -508,9 +508,9 @@ suite('TerminalCompletionService', () => { }); test('folder/| should normalize current and parent folders', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, + showFolders: true, pathSeparator }; validResources = [ @@ -523,7 +523,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'test/', 5, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'test/', 5, provider, capabilities); assertCompletions(result, [ { label: './test/', detail: '/test/' }, @@ -551,13 +551,13 @@ suite('TerminalCompletionService', () => { test('cd | should show paths from $CDPATH (relative)', async () => { configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative'); - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ', 3, provider, capabilities); assertPartialCompletionsExist(result, [ { label: 'folder1', detail: 'CDPATH /cdpath_value/folder1/' }, @@ -566,13 +566,13 @@ suite('TerminalCompletionService', () => { test('cd | should show paths from $CDPATH (absolute)', async () => { configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'absolute'); - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ', 3, provider, capabilities); assertPartialCompletionsExist(result, [ { label: '/cdpath_value/folder1/', detail: 'CDPATH' }, @@ -602,13 +602,13 @@ suite('TerminalCompletionService', () => { { resource: URI.parse(`${uriPathPrefix}cdpath2_value/inner_dir/file1.txt`), isFile: true }, ]; - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse(`${uriPathPrefix}test`), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ', 3, provider, capabilities); const finalPrefix = isWindows ? 'C:\\' : '/'; assertPartialCompletionsExist(result, [ @@ -637,10 +637,10 @@ suite('TerminalCompletionService', () => { }); test('resolveResources with c:/ style absolute path for Git Bash', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator: '/' }; validResources = [ @@ -652,7 +652,7 @@ suite('TerminalCompletionService', () => { { resource: URI.file('C:\\Users\\foo\\bar'), isDirectory: true, isFile: false }, { resource: URI.file('C:\\Users\\foo\\baz.txt'), isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'C:/Users/foo/', 13, provider, capabilities, WindowsShellType.GitBash); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'C:/Users/foo/', 13, provider, capabilities, WindowsShellType.GitBash); assertCompletions(result, [ { label: 'C:/Users/foo/', detail: 'C:\\Users\\foo\\' }, { label: 'C:/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' }, @@ -660,10 +660,10 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 0, replacementLength: 13 }, '/'); }); test('resolveResources with cwd as Windows path (relative)', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator: '/' }; validResources = [ @@ -675,7 +675,7 @@ suite('TerminalCompletionService', () => { { resource: URI.file('C:\\Users\\foo\\bar'), isDirectory: true }, { resource: URI.file('C:\\Users\\foo\\baz.txt'), isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities, WindowsShellType.GitBash); + const result = await terminalCompletionService.resolveResources(resourceOptions, './', 2, provider, capabilities, WindowsShellType.GitBash); assertCompletions(result, [ { label: './', detail: 'C:\\Users\\foo\\' }, { label: './bar/', detail: 'C:\\Users\\foo\\bar\\' }, @@ -685,10 +685,10 @@ suite('TerminalCompletionService', () => { }); test('resolveResources with cwd as Windows path (absolute)', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator: '/' }; validResources = [ @@ -700,7 +700,7 @@ suite('TerminalCompletionService', () => { { resource: URI.file('C:\\Users\\foo\\bar'), isDirectory: true }, { resource: URI.file('C:\\Users\\foo\\baz.txt'), isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/c/Users/foo/', 13, provider, capabilities, WindowsShellType.GitBash); + const result = await terminalCompletionService.resolveResources(resourceOptions, '/c/Users/foo/', 13, provider, capabilities, WindowsShellType.GitBash); assertCompletions(result, [ { label: '/c/Users/foo/', detail: 'C:\\Users\\foo\\' }, { label: '/c/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' }, @@ -712,11 +712,11 @@ suite('TerminalCompletionService', () => { if (!isWindows) { suite('symlink support', () => { test('should include symlink target information in completions', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), pathSeparator, - filesRequested: true, - foldersRequested: true + showFiles: true, + showFolders: true }; validResources = [URI.parse('file:///test')]; @@ -729,7 +729,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/regular-folder'), isDirectory: true }, ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'ls ', 3, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, 'ls ', 3, provider, capabilities); // Find the symlink completion const symlinkFileCompletion = result?.find(c => c.label === './symlink-file'); @@ -741,10 +741,10 @@ suite('TerminalCompletionService', () => { } suite('completion label escaping', () => { test('| should escape special characters in file/folder names for POSIX shells', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { + const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - foldersRequested: true, - filesRequested: true, + showFolders: true, + showFiles: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -754,7 +754,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/!special$chars&/'), isDirectory: true }, { resource: URI.parse('file:///test/!special$chars2&'), isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceOptions, '', 0, provider, capabilities); assertCompletions(result, [ { label: '.', detail: '/test/' }, diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 37152b7145f..4b3836f7a16 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -93,17 +93,29 @@ declare module 'vscode' { Method = 2, Alias = 3, Argument = 4, + /** + * An option, for example in `code --locale`, `--locale` is the option + */ Option = 5, + /** + * The value of an option, for example in `code --locale en-US`, `en-US` is the option value + */ OptionValue = 6, + /** + * A flag, for example in `git commit --amend"`, `--amend` is the flag + */ Flag = 7, SymbolicLinkFile = 8, SymbolicLinkFolder = 9, - Commit = 10, - Branch = 11, - Tag = 12, - Stash = 13, - Remote = 14, + ScmCommit = 10, + ScmBranch = 11, + ScmTag = 12, + ScmStash = 13, + ScmRemote = 14, PullRequest = 15, + /** + * The pull request has been closed + */ PullRequestDone = 16, } @@ -118,16 +130,16 @@ declare module 'vscode' { /** * The complete terminal command line. */ - commandLine: string; + readonly commandLine: string; /** * The index of the cursor in the command line. */ - cursorPosition: number; + readonly cursorIndex: number; /** - * Whether completions should be provided when it is not clear to what type of completion is - * well known. + * Whether completions should be provided when none are explicitly suggested. This will display + * fallback suggestions like files and folders. */ - allowFallbackCompletions: boolean; + readonly allowFallbackCompletions: boolean; } export namespace window { @@ -155,14 +167,14 @@ declare module 'vscode' { * @example Create a completion list that requests files for the terminal cwd * const list = new TerminalCompletionList([ * { label: 'ls', replacementIndex: 0, replacementLength: 0, kind: TerminalCompletionItemKind.Method } - * ], { filesRequested: true, cwd: Uri.file('/home/user') }); + * ], { showFiles: true, cwd: Uri.file('/home/user') }); */ export class TerminalCompletionList { /** * Resources that should be shown in the completions list for the cwd of the terminal. */ - resourceRequestConfig?: TerminalResourceRequestConfig; + resourceOptions?: TerminalCompletionResourceOptions; /** * The completion items. @@ -173,9 +185,9 @@ declare module 'vscode' { * Creates a new completion list. * * @param items The completion items. - * @param resourceRequestConfig Indicates which resources should be shown as completions for the cwd of the terminal. + * @param resourceOptions Indicates which resources should be shown as completions for the cwd of the terminal. */ - constructor(items?: T[], resourceRequestConfig?: TerminalResourceRequestConfig); + constructor(items: T[], resourceOptions?: TerminalCompletionResourceOptions); } @@ -185,19 +197,19 @@ declare module 'vscode' { * When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and * folders that match {@link globPattern} from the provided {@link cwd}. */ - export interface TerminalResourceRequestConfig { + export interface TerminalCompletionResourceOptions { /** * Show files as completion items. */ - filesRequested?: boolean; + showFiles?: boolean; /** * Show folders as completion items. */ - foldersRequested?: boolean; + showDirectories?: boolean; /** - * A {@link GlobPattern glob pattern} that controls which files suggest should surface. + * A glob pattern string that controls which files suggest should surface. Note that this will only apply if {@param showFiles} or {@param showDirectories} is set to true. */ - globPattern?: GlobPattern; + globPattern?: string; /** * The cwd from which to request resources. */ From 8f8af8dd4057e3c9b59dc52896d94b1e2687505c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:45:27 -0700 Subject: [PATCH 0923/4355] Add edu issue label and command --- .github/commands.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index 84e5668ed8e..374010f98ee 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -586,7 +586,9 @@ "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/253132, if the bug you are experiencing is not there, please comment on this closed issue thread so we can re-open it.", - "assign": ["TylerLeonhardt"] + "assign": [ + "TylerLeonhardt" + ] }, { "type": "label", @@ -600,7 +602,7 @@ "type": "label", "name": "~chat-billing", "removeLabel": "~chat-billing", - "addLabel":"chat-billing", + "addLabel": "chat-billing", "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/252230. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." @@ -609,7 +611,7 @@ "type": "label", "name": "~chat-infinite-response-loop", "removeLabel": "~chat-infinite-response-loop", - "addLabel":"chat-infinite-response-loop", + "addLabel": "chat-infinite-response-loop", "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/253134. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." @@ -632,5 +634,24 @@ "samvantran", "thispaul" ] + }, + { + "type": "label", + "name": "*edu", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because it seems to be about coursework or grading. This issue tracker is for issues about VS Code itself. For coursework-related issues, or issues related to your course's specific VS Code setup, please consider engaging directly with your course instructor.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "edu", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*edu" } ] From f772f6510b2e27b1eb3f0ac157dc5855683225d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 7 Oct 2025 13:13:08 -0700 Subject: [PATCH 0924/4355] Fix condition --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index cd998dee3a6..cf50a4f8c55 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -161,8 +161,11 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const languageId = model.getLanguageId(); // Allow language-specific recommendations for untitled files when language is auto-detected only when the file is large. - const allowLanguageMatch = this.untitledTextEditorService.get(uri)?.hasLanguageSetExplicitly ? - model.getValueLength() > untitledFileRecommendationsMinLength : true; + const untitledModel = this.untitledTextEditorService.get(uri); + const allowLanguageMatch = + !untitledModel || + untitledModel.hasLanguageSetExplicitly || + model.getValueLength() > untitledFileRecommendationsMinLength; for (const [extensionId, conditions] of extensionRecommendationEntries) { const conditionsByPattern: IFileOpenCondition[] = []; From 9c75c91ce1ca34467c63659274a2cbfa7d010785 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 7 Oct 2025 13:30:00 -0700 Subject: [PATCH 0925/4355] testing: improve performance when switching between files with many tests (#270264) Closes #235819 --- src/vs/base/common/async.ts | 6 +++ .../testing/browser/testExplorerActions.ts | 48 ++++++++++--------- .../testing/browser/testingDecorations.ts | 2 +- .../contrib/testing/common/testService.ts | 45 ++++++++++++++--- .../testing/common/testingChatAgentTool.ts | 6 ++- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 50656fa1035..5e4cd533c9a 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1715,6 +1715,12 @@ const enum DeferredOutcome { */ export class DeferredPromise { + public static fromPromise(promise: Promise): DeferredPromise { + const deferred = new DeferredPromise(); + deferred.settleWith(promise); + return deferred; + } + private completeCallback!: ValueCallback; private errorCallback!: (err: unknown) => void; private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T }; diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 7e2eddff7a8..e2bf7c6fd05 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1005,28 +1005,30 @@ async function getTestsAtCursor(testService: ITestService, uriIdentityService: I let bestNodesBefore: InternalTestItem[] = []; let bestRangeBefore: Range | undefined; - for await (const test of testsInFile(testService, uriIdentityService, uri)) { - if (!test.item.range || filter?.(test) === false) { - continue; - } + for await (const tests of testsInFile(testService, uriIdentityService, uri)) { + for (const test of tests) { + if (!test.item.range || filter?.(test) === false) { + continue; + } - const irange = Range.lift(test.item.range); - if (irange.containsPosition(position)) { - if (bestRange && Range.equalsRange(test.item.range, bestRange)) { - // check that a parent isn't already included (#180760) - if (!bestNodes.some(b => TestId.isChild(b.item.extId, test.item.extId))) { - bestNodes.push(test); + const irange = Range.lift(test.item.range); + if (irange.containsPosition(position)) { + if (bestRange && Range.equalsRange(test.item.range, bestRange)) { + // check that a parent isn't already included (#180760) + if (!bestNodes.some(b => TestId.isChild(b.item.extId, test.item.extId))) { + bestNodes.push(test); + } + } else { + bestRange = irange; + bestNodes = [test]; + } + } else if (Position.isBefore(irange.getStartPosition(), position)) { + if (!bestRangeBefore || bestRangeBefore.getStartPosition().isBefore(irange.getStartPosition())) { + bestRangeBefore = irange; + bestNodesBefore = [test]; + } else if (irange.equalsRange(bestRangeBefore) && !bestNodesBefore.some(b => TestId.isChild(b.item.extId, test.item.extId))) { + bestNodesBefore.push(test); } - } else { - bestRange = irange; - bestNodes = [test]; - } - } else if (Position.isBefore(irange.getStartPosition(), position)) { - if (!bestRangeBefore || bestRangeBefore.getStartPosition().isBefore(irange.getStartPosition())) { - bestRangeBefore = irange; - bestNodesBefore = [test]; - } else if (irange.equalsRange(bestRangeBefore) && !bestNodesBefore.some(b => TestId.isChild(b.item.extId, test.item.extId))) { - bestNodesBefore.push(test); } } } @@ -1255,8 +1257,10 @@ abstract class ExecuteTestsInCurrentFile extends Action2 { const testService = accessor.get(ITestService); const discovered: InternalTestItem[] = []; for (const uri of files) { - for await (const file of testsInFile(testService, uriIdentity, uri, undefined, true)) { - discovered.push(file); + for await (const files of testsInFile(testService, uriIdentity, uri, undefined, true)) { + for (const file of files) { + discovered.push(file); + } } } diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index b037046a20a..50fb364e603 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -526,7 +526,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio this.decorations.syncDecorations(uri); (async () => { - for await (const _test of testsInFile(this.testService, this.uriIdentityService, uri, false)) { + for await (const _tests of testsInFile(this.testService, this.uriIdentityService, uri, false)) { // consume the iterator so that all tests in the file get expanded. Or // at least until the URI changes. If new items are requested, changes // will be trigged in the `onDidProcessDiff` callback. diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 91a257bb99f..18586421e9d 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { assert } from '../../../../base/common/assert.js'; +import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { Iterable } from '../../../../base/common/iterator.js'; @@ -172,8 +173,12 @@ export const waitForTestToBeIdle = (testService: ITestService, test: Incremental * Iterator that expands to and iterates through tests in the file. Iterates * in strictly descending order. */ -export const testsInFile = async function* (testService: ITestService, ident: IUriIdentityService, uri: URI, waitForIdle = true, descendInFile = true): AsyncIterable { - const queue = new LinkedList>(); +export const testsInFile = async function* (testService: ITestService, ident: IUriIdentityService, uri: URI, waitForIdle = true, descendInFile = true): AsyncIterable { + // In this function we go to a bit of effort to avoid awaiting unnecessarily + // and bulking the test collections we do collect for consumers. This fixes + // a performance issue (#235819) where a large number of tests in a file + // would cause a long delay switching editors. + const queue = new LinkedList | DeferredPromise>>(); const existing = [...testService.collection.getNodeByUrl(uri)].sort((a, b) => a.item.extId.length - b.item.extId.length); @@ -191,8 +196,23 @@ export const testsInFile = async function* (testService: ITestService, ident: IU queue.push(existing.length ? existing.map(e => e.item.extId) : testService.collection.rootIds); let n = 0; + let gather: IncrementalTestCollectionItem[] = []; while (queue.size > 0) { - for (const id of queue.pop()!) { + const next = queue.pop()!; + let ids: Iterable; + if (!(next instanceof DeferredPromise)) { + ids = next; + } else if (next.isSettled) { + ids = next.value || Iterable.empty(); + } else { + if (gather.length) { + yield gather; + gather = []; + } + ids = await next.p; + } + + for (const id of ids) { n++; const test = testService.collection.getNodeById(id); if (!test) { @@ -205,7 +225,7 @@ export const testsInFile = async function* (testService: ITestService, ident: IU } if (ident.extUri.isEqual(uri, test.item.uri)) { - yield test; + gather.push(test); if (!descendInFile) { continue; @@ -213,19 +233,30 @@ export const testsInFile = async function* (testService: ITestService, ident: IU } if (ident.extUri.isEqualOrParent(uri, test.item.uri)) { + let prom: Promise | undefined; if (test.expand === TestItemExpandState.Expandable) { - await testService.collection.expand(test.item.extId, 1); + prom = testService.collection.expand(test.item.extId, 1); } if (waitForIdle) { - await waitForTestToBeIdle(testService, test); + if (prom) { + prom = prom.then(() => waitForTestToBeIdle(testService, test)); + } else if (test.item.busy) { + prom = waitForTestToBeIdle(testService, test); + } } - if (test.children.size) { + if (prom) { + queue.push(DeferredPromise.fromPromise(prom.then(() => test.children))); + } else if (test.children.size) { queue.push(test.children); } } } } + + if (gather.length) { + yield gather; + } }; /** diff --git a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts index e38dbc31cb0..521c94a648f 100644 --- a/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts +++ b/src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts @@ -423,8 +423,10 @@ class RunTestTool implements IToolImpl { const tests: IncrementalTestCollectionItem[] = []; for (const uri of uris) { - for await (const file of testsInFile(this._testService, this._uriIdentityService, uri, undefined, false)) { - tests.push(file); + for await (const files of testsInFile(this._testService, this._uriIdentityService, uri, undefined, false)) { + for (const file of files) { + tests.push(file); + } } } From 5f1ffa860027234fc2a77aae371af29d1e386df0 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 7 Oct 2025 13:34:02 -0700 Subject: [PATCH 0926/4355] feat: telemetry log of tool invocation and preparation times --- .../chat/browser/languageModelToolsService.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 75e4f4f9cc0..54b3a6ef5fe 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -8,6 +8,7 @@ import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { assertNever } from '../../../../base/common/assert.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { encodeBase64 } from '../../../../base/common/buffer.js'; +import { StopWatch } from '../../../../base/common/stopwatch.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; @@ -291,6 +292,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo let requestId: string | undefined; let store: DisposableStore | undefined; let toolResult: IToolResult | undefined; + let prepareTimeMs: number | undefined; + let invocationTimeMs: number | undefined; try { if (dto.context) { store = new DisposableStore(); @@ -323,7 +326,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo })); token = source.token; + const prepareWatch = StopWatch.create(); const prepared = await this.prepareToolInvocation(tool, dto, token); + prepareTimeMs = prepareWatch.elapsed(); toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId, dto.fromSubAgent); trackedCall.invocation = toolInvocation; @@ -361,7 +366,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } } } else { + const prepareWatch = StopWatch.create(); const prepared = await this.prepareToolInvocation(tool, dto, token); + prepareTimeMs = prepareWatch.elapsed(); if (prepared?.confirmationMessages && !(await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace))) { const result = await this._dialogService.confirm({ message: renderAsPlaintext(prepared.confirmationMessages.title), detail: renderAsPlaintext(prepared.confirmationMessages.message) }); if (!result.confirmed) { @@ -376,11 +383,13 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw new CancellationError(); } + const invokeWatch = StopWatch.create(); toolResult = await tool.impl.invoke(dto, countTokens, { report: step => { toolInvocation?.acceptProgress(step); } }, token); + invocationTimeMs = invokeWatch.elapsed(); this.ensureToolDetails(dto, toolResult, tool.data); this._telemetryService.publicLog2( @@ -391,6 +400,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toolId: tool.data.id, toolExtensionId: tool.data.source.type === 'extension' ? tool.data.source.extensionId.value : undefined, toolSourceKind: tool.data.source.type, + prepareTimeMs, + invocationTimeMs, }); return toolResult; } catch (err) { @@ -403,6 +414,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toolId: tool.data.id, toolExtensionId: tool.data.source.type === 'extension' ? tool.data.source.extensionId.value : undefined, toolSourceKind: tool.data.source.type, + prepareTimeMs, + invocationTimeMs, }); this._logService.error(`[LanguageModelToolsService#invokeTool] Error from tool ${dto.toolId} with parameters ${JSON.stringify(dto.parameters)}:\n${toErrorMessage(err, true)}`); @@ -801,6 +814,8 @@ type LanguageModelToolInvokedEvent = { toolId: string; toolExtensionId: string | undefined; toolSourceKind: string; + prepareTimeMs?: number; + invocationTimeMs?: number; }; type LanguageModelToolInvokedClassification = { @@ -809,6 +824,8 @@ type LanguageModelToolInvokedClassification = { toolId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the tool used.' }; toolExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that contributed the tool.' }; toolSourceKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source (mcp/extension/internal) of the tool.' }; + prepareTimeMs?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time spent in prepareToolInvocation method in milliseconds.' }; + invocationTimeMs?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time spent in tool invoke method in milliseconds.' }; owner: 'roblourens'; comment: 'Provides insight into the usage of language model tools.'; }; From 4af4e9545ef7188e15b87a7e153bc8d5479fc406 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 7 Oct 2025 13:54:58 -0700 Subject: [PATCH 0927/4355] refactor: replace prepare and invocation time tracking with StopWatch instances --- .../chat/browser/languageModelToolsService.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 54b3a6ef5fe..639f8df959e 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -292,8 +292,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo let requestId: string | undefined; let store: DisposableStore | undefined; let toolResult: IToolResult | undefined; - let prepareTimeMs: number | undefined; - let invocationTimeMs: number | undefined; + let prepareTimeWatch: StopWatch | undefined; + let invocationTimeWatch: StopWatch | undefined; try { if (dto.context) { store = new DisposableStore(); @@ -326,9 +326,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo })); token = source.token; - const prepareWatch = StopWatch.create(); + const prepareTimeWatch = StopWatch.create(true); const prepared = await this.prepareToolInvocation(tool, dto, token); - prepareTimeMs = prepareWatch.elapsed(); + prepareTimeWatch.stop(); toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId, dto.fromSubAgent); trackedCall.invocation = toolInvocation; @@ -366,9 +366,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } } } else { - const prepareWatch = StopWatch.create(); + const prepareTimeWatch = StopWatch.create(true); const prepared = await this.prepareToolInvocation(tool, dto, token); - prepareTimeMs = prepareWatch.elapsed(); + prepareTimeWatch.stop(); if (prepared?.confirmationMessages && !(await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace))) { const result = await this._dialogService.confirm({ message: renderAsPlaintext(prepared.confirmationMessages.title), detail: renderAsPlaintext(prepared.confirmationMessages.message) }); if (!result.confirmed) { @@ -383,13 +383,13 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw new CancellationError(); } - const invokeWatch = StopWatch.create(); + invocationTimeWatch = StopWatch.create(true); toolResult = await tool.impl.invoke(dto, countTokens, { report: step => { toolInvocation?.acceptProgress(step); } }, token); - invocationTimeMs = invokeWatch.elapsed(); + invocationTimeWatch.stop(); this.ensureToolDetails(dto, toolResult, tool.data); this._telemetryService.publicLog2( @@ -400,8 +400,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toolId: tool.data.id, toolExtensionId: tool.data.source.type === 'extension' ? tool.data.source.extensionId.value : undefined, toolSourceKind: tool.data.source.type, - prepareTimeMs, - invocationTimeMs, + prepareTimeMs: prepareTimeWatch?.elapsed(), + invocationTimeMs: invocationTimeWatch?.elapsed(), }); return toolResult; } catch (err) { @@ -414,8 +414,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo toolId: tool.data.id, toolExtensionId: tool.data.source.type === 'extension' ? tool.data.source.extensionId.value : undefined, toolSourceKind: tool.data.source.type, - prepareTimeMs, - invocationTimeMs, + prepareTimeMs: prepareTimeWatch?.elapsed(), + invocationTimeMs: invocationTimeWatch?.elapsed(), }); this._logService.error(`[LanguageModelToolsService#invokeTool] Error from tool ${dto.toolId} with parameters ${JSON.stringify(dto.parameters)}:\n${toErrorMessage(err, true)}`); From 10a353b63682eb708a64583888c706d1661f21e9 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:11:47 -0700 Subject: [PATCH 0928/4355] switch to async producer for variable Provider (#270230) switch to async producer --- .../api/browser/mainThreadNotebookKernels.ts | 35 ++++++++----------- .../notebookVariableCommands.ts | 10 +++--- .../notebookVariablesDataSource.ts | 15 ++++---- .../chat/notebook.chat.contribution.ts | 4 +-- .../notebook/common/notebookKernelService.ts | 4 +-- .../browser/notebookExecutionService.test.ts | 6 ++-- .../notebookExecutionStateService.test.ts | 6 ++-- .../browser/notebookKernelHistory.test.ts | 6 ++-- .../browser/notebookKernelService.test.ts | 6 ++-- .../notebookVariablesDataSource.test.ts | 26 +++++++------- 10 files changed, 57 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 2a7e0a513e3..e69273a0ca6 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -20,7 +20,7 @@ import { IKernelSourceActionProvider, INotebookKernel, INotebookKernelChangeEven import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js'; import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol.js'; import { INotebookService } from '../../contrib/notebook/common/notebookService.js'; -import { AsyncIterableObject, AsyncIterableSource } from '../../../base/common/async.js'; +import { AsyncIterableEmitter, AsyncIterableProducer } from '../../../base/common/async.js'; abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); @@ -101,7 +101,7 @@ abstract class MainThreadKernel implements INotebookKernel { abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - abstract provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; + abstract provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer; } class MainThreadKernelDetectionTask implements INotebookKernelDetectionTask { @@ -227,11 +227,11 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape } private variableRequestIndex = 0; - private variableRequestMap = new Map>(); + private variableRequestMap = new Map>(); $receiveVariable(requestId: string, variable: VariablesResult) { - const source = this.variableRequestMap.get(requestId); - if (source) { - source.emitOne(variable); + const emitter = this.variableRequestMap.get(requestId); + if (emitter) { + emitter.emitOne(variable); } } @@ -246,23 +246,18 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise { await that._proxy.$cancelCells(handle, uri, handles); } - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer { const requestId = `${handle}variables${that.variableRequestIndex++}`; - if (that.variableRequestMap.has(requestId)) { - return that.variableRequestMap.get(requestId)!.asyncIterable; - } - const source = new AsyncIterableSource(); - that.variableRequestMap.set(requestId, source); - that._proxy.$provideVariables(handle, requestId, notebookUri, parentId, kind, start, token).then(() => { - source.resolve(); - that.variableRequestMap.delete(requestId); - }).catch((err) => { - source.reject(err); - that.variableRequestMap.delete(requestId); - }); + return new AsyncIterableProducer(async emitter => { + that.variableRequestMap.set(requestId, emitter); - return source.asyncIterable; + try { + await that._proxy.$provideVariables(handle, requestId, notebookUri, parentId, kind, start, token); + } finally { + that.variableRequestMap.delete(requestId); + } + }); } }(data, this._languageService); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index f0641036e70..0ee9c85e08a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -59,10 +59,12 @@ registerAction2(class extends Action2 { const selectedKernel = notebookKernelService.getMatchingKernel(notebookTextModel).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { - const variables = selectedKernel.provideVariables(notebookTextModel.uri, undefined, 'named', 0, CancellationToken.None); - return await variables - .map(variable => { return variable; }) - .toPromise(); + const variableIterable = selectedKernel.provideVariables(notebookTextModel.uri, undefined, 'named', 0, CancellationToken.None); + const collected: VariablesResult[] = []; + for await (const variable of variableIterable) { + collected.push(variable); + } + return collected; } return []; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index f3474e3035b..238e6c8485e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -70,10 +70,9 @@ export class NotebookVariableDataSource implements IAsyncDataSource { return this.createVariableElement(variable, parent.notebook); }) - .toPromise(); - children = children.concat(childNodes); + for await (const variable of variables) { + children.push(this.createVariableElement(variable, parent.notebook)); + } } if (parent.indexedChildrenCount > 0) { const childNodes = await this.getIndexedChildren(parent, selectedKernel); @@ -145,9 +144,11 @@ export class NotebookVariableDataSource implements IAsyncDataSource { return this.createVariableElement(variable, notebook); }) - .toPromise(); + const varElements: INotebookVariableElement[] = []; + for await (const variable of variables) { + varElements.push(this.createVariableElement(variable, notebook)); + } + return varElements; } return []; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index a28bba2155a..9a22155e2d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -131,7 +131,7 @@ class NotebookChatContribution extends Disposable implements IWorkbenchContribut return; } - const variables = await selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); + const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); for await (const variable of variables) { if (pattern && !variable.name.toLowerCase().includes(pattern)) { @@ -195,7 +195,7 @@ export class SelectAndInsertKernelVariableAction extends Action2 { return; } - const variables = await selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); + const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, CancellationToken.None); const quickPickItems: IQuickPickItem[] = []; for await (const variable of variables) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 1f246ef44e7..f44a802ba0c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAction } from '../../../../base/common/actions.js'; -import { AsyncIterableObject } from '../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; @@ -70,7 +70,7 @@ export interface INotebookKernel { executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer; } export const enum ProxyKernelState { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 72a70c4523d..1394b04c927 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import * as sinon from 'sinon'; -import { AsyncIterableObject } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -183,8 +183,8 @@ class TestNotebookKernel implements INotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { - return AsyncIterableObject.EMPTY; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer { + return AsyncIterableProducer.EMPTY; } executeNotebookCellsRequest(): Promise { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 6cfaeb99408..2c2e5b5b18e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { AsyncIterableObject, DeferredPromise } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer, DeferredPromise } from '../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -371,8 +371,8 @@ class TestNotebookKernel implements INotebookKernel { supportedLanguages: string[] = []; async executeNotebookCellsRequest(): Promise { } async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { } - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { - return AsyncIterableObject.EMPTY; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer { + return AsyncIterableProducer.EMPTY; } constructor(opts?: { languages?: string[]; id?: string }) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts index be58fe1c8e0..f2ba9495de9 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -22,7 +22,7 @@ import { IApplicationStorageValueChangeEvent, IProfileStorageValueChangeEvent, I import { INotebookLoggingService } from '../../common/notebookLoggingService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { AsyncIterableObject } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../../base/common/async.js'; suite('NotebookKernelHistoryService', () => { @@ -186,8 +186,8 @@ class TestNotebookKernel implements INotebookKernel { cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { - return AsyncIterableObject.EMPTY; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer { + return AsyncIterableProducer.EMPTY; } constructor(opts?: { languages?: string[]; label?: string; notebookType?: string }) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index ba96e44b1df..e13168ec0c7 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -20,7 +20,7 @@ import { IMenu, IMenuService } from '../../../../../platform/actions/common/acti import { TransientOptions } from '../../common/notebookCommon.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { AsyncIterableObject } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../../base/common/async.js'; suite('NotebookKernelService', () => { @@ -199,8 +199,8 @@ class TestNotebookKernel implements INotebookKernel { cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject { - return AsyncIterableObject.EMPTY; + provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableProducer { + return AsyncIterableProducer.EMPTY; } constructor(opts?: { languages?: string[]; label?: string; viewType?: string }) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts index ecd7842a833..91b5b5ca741 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { AsyncIterableObject, AsyncIterableSource } from '../../../../../base/common/async.js'; +import { AsyncIterableProducer } from '../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { URI } from '../../../../../base/common/uri.js'; import { mock } from '../../../../../base/test/common/mock.js'; @@ -29,21 +29,19 @@ suite('NotebookVariableDataSource', () => { kind: 'named' | 'indexed', start: number, token: CancellationToken - ): AsyncIterableObject { + ): AsyncIterableProducer { provideVariablesCalled = true; - const source = new AsyncIterableSource(); - for (let i = 0; i < results.length; i++) { - if (token.isCancellationRequested) { - break; + return new AsyncIterableProducer((emitter) => { + for (let i = 0; i < results.length; i++) { + if (token.isCancellationRequested) { + break; + } + if (results[i].action) { + results[i].action!(); + } + emitter.emitOne(results[i]); } - if (results[i].action) { - results[i].action!(); - } - source.emitOne(results[i]); - } - - setTimeout(() => source.resolve(), 0); - return source.asyncIterable; + }); } }; From e6c3063c700d81f66fa1d0e41a31d749d71639cc Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Tue, 7 Oct 2025 14:44:17 -0700 Subject: [PATCH 0929/4355] Add "First Time Setup" prompt (#269433) --- .github/prompts/setup-environment.prompt.md | 82 +++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .github/prompts/setup-environment.prompt.md diff --git a/.github/prompts/setup-environment.prompt.md b/.github/prompts/setup-environment.prompt.md new file mode 100644 index 00000000000..e35870dd477 --- /dev/null +++ b/.github/prompts/setup-environment.prompt.md @@ -0,0 +1,82 @@ +--- +mode: agent +description: First Time Setup +tools: ['runCommands', 'runTasks/runTask', 'search', 'todos', 'fetch'] +--- + +# Role +You are my setup automation assistant. Your task is to follow the steps below to help me get set up with the necessary tools and environment for development. Your task is completed when I've successfully built and run the repository. Use a TODO to track progress. + +# Steps +1. Find setup instructions in README.md and CONTRIBUTING.md at the root of the repository. Fetch any other documentation they recommend. + +2. Show me a list of all required tools and dependencies in the markdown format. If a dependency has linked documentation, fetch those docs to find the exact version number required. Remember that link and that version for step 4. Do not display system requirements. + +## 🛠️ Required Tools +- **Node.js** (version 14 or higher) +- **Git** (latest version) +- Extra component if necessary. + + +3. Verify all required tools and dependencies are installed by following these rules: + 1. For all tools that should exist on the PATH, check their versions. `toolA --version; toolB --version; [...] toolZ --version` + 2. For tools not traditionally on the PATH: + 1. Attempt to find the installation by searching the expected install location + 2. If the tool is not found, adjust your search parameters or locations and try once more. Consider if the tool is installed with a package manager. + 3. If the second location fails, mark it as missing. + +4. Display a summary of what I have and what I need to install. In the markdown format. If a section is empty, omit it. + + +## Installation Summary + +### ✅ Already Installed +- Node.js (version 16.13.0) ⚠️ Note: You have X version but this project specifies Y. + +### ❌ Not Installed +- ❌ Git (need version 2.30 or higher) + - [Link to downloads page] + +### ❓ Unable to Verify +- ToolName - [Reason why it couldn't be verified] + - [Manual verification instructions steps] + + +5. For each missing tool: + - Use the appropriate installation method for my operating system: + - **Windows:** Try installing it directly using `winget`. + - Example: `winget install --id Git.Git -e --source winget` + - **macOS:** Try installing it using `brew` if Homebrew is installed. + - Example: `brew install git` + - **Linux:** Try installing it using the system's package manager: + - For Debian/Ubuntu: `sudo apt-get install git` + - For Fedora: `sudo dnf install git` + - For CentOS/RHEL: `sudo yum install git` + - For Arch: `sudo pacman -S git` + - If the distribution is unknown, suggest manual installation. + - You MUST install the required versions found in step 2. + - For tools that may be managed by version managers (like `Node.js`), try installing them using the version manager if installed. + - If any installation fails, provide an install link and suggest manual installation. + - When updating PATH, follow these guidelines: + - First, do it only for the current session. + - Once installation is verified, add it permanently to the PATH. + - Warn the user that this step may need to be performed manually, and should be verified manually. Provide simple steps to do so. + - If a restart may be required, remind the user. + +6. If any tools were installed, show an installation summary. Otherwise, skip this step. +7. Provide steps on building the repository, and then perform those steps. +8. If the repository is an application: + - Provide steps on running the application + - Try to run the application via a launch configuration if it exists, otherwise try running it yourself. +9. Show me a recap of what was newly installed. +10. Finally, update the README.md or CONTRIBUTING.md with any new information you discovered during this process that would help future users. + +# Guidelines + +- Instead of displaying commands to run, execute them directly. +- Output in markdown for human readability. +- Skip optional tooling. +- Keep all responses specific to my operating system. +- IMPORTANT: Documentation may be out of date. Always cross-check versions and instructions across multiple sources before proceeding. Update relevant files to the latest information as needed. +- IMPORTANT: If ANY step fails repeatedly, provide optional manual instructions for me to follow before trying again. +- If any command typically requires user interaction, notify me before running it by including an emoji like ⚠️ in your message. From 80c4d67b8f0f6d7f593d8a8042f275dfa54bab10 Mon Sep 17 00:00:00 2001 From: amunger <2019016+amunger@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:09:39 -0700 Subject: [PATCH 0930/4355] provide types for api command --- src/vs/workbench/api/browser/mainThreadNotebook.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index d841a2d2deb..9730c2d9d9c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -18,7 +18,7 @@ import { INotebookCellStatusBarItemProvider, INotebookContributionData, INoteboo import { INotebookService, SimpleNotebookProviderInfo } from '../../contrib/notebook/common/notebookService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExtHostContext, ExtHostNotebookShape, MainContext, MainThreadNotebookShape } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostNotebookShape, MainContext, MainThreadNotebookShape, NotebookDataDto } from '../common/extHost.protocol.js'; import { IRelativePattern } from '../../../base/common/glob.js'; import { revive } from '../../../base/common/marshalling.js'; import { INotebookFileMatchNoModel } from '../../contrib/search/common/searchNotebookHelpers.js'; @@ -210,11 +210,10 @@ CommandsRegistry.registerCommand('_executeDataToNotebook', async (accessor, ...a return new SerializableObjectWithBuffers(NotebookDto.toNotebookDataDto(dto)); }); -CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, ...args) => { +CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, args: [string, SerializableObjectWithBuffers]) => { const [notebookType, dto] = args; assertType(typeof notebookType === 'string', 'string'); - assertType(typeof dto === 'object'); const notebookService = accessor.get(INotebookService); const info = await notebookService.withNotebookDataProvider(notebookType); From f7aba72d1b87ea81d823e41da08e240155bdbf9a Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:05:36 -0700 Subject: [PATCH 0931/4355] test iteration through provided variables (#270275) * test iteration through provided variables * fix cancellation test --- .../browser/TestMainThreadNotebookKernels.ts | 103 ++++++++++++++ .../mainThreadVariableProvider.test.ts | 134 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 src/vs/workbench/api/test/browser/TestMainThreadNotebookKernels.ts create mode 100644 src/vs/workbench/api/test/browser/mainThreadVariableProvider.test.ts diff --git a/src/vs/workbench/api/test/browser/TestMainThreadNotebookKernels.ts b/src/vs/workbench/api/test/browser/TestMainThreadNotebookKernels.ts new file mode 100644 index 00000000000..f1510fece68 --- /dev/null +++ b/src/vs/workbench/api/test/browser/TestMainThreadNotebookKernels.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mock } from '../../../test/common/workbenchTestServices.js'; +import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js'; +import { INotebookKernel, INotebookKernelService } from '../../../contrib/notebook/common/notebookKernelService.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { INotebookCellExecution, INotebookExecution, INotebookExecutionStateService } from '../../../contrib/notebook/common/notebookExecutionStateService.js'; +import { INotebookService } from '../../../contrib/notebook/common/notebookService.js'; +import { INotebookEditorService } from '../../../contrib/notebook/browser/services/notebookEditorService.js'; +import { Event } from '../../../../base/common/event.js'; +import { MainThreadNotebookKernels } from '../../browser/mainThreadNotebookKernels.js'; +import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; + +export class TestMainThreadNotebookKernels extends Disposable { + private readonly instantiationService: TestInstantiationService; + private readonly registeredKernels = new Map(); + private mainThreadNotebookKernels: MainThreadNotebookKernels; + private kernelHandle = 0; + + constructor(extHostContext: IExtHostContext) { + super(); + this.instantiationService = this._register(new TestInstantiationService()); + this.setupDefaultStubs(); + this.mainThreadNotebookKernels = this._register(this.instantiationService.createInstance(MainThreadNotebookKernels, extHostContext)); + } + + private setupDefaultStubs(): void { + this.instantiationService.stub(ILanguageService, new class extends mock() { + override getRegisteredLanguageIds() { + return ['typescript', 'javascript', 'python']; + } + }); + + this.instantiationService.stub(INotebookKernelService, new class extends mock() { + constructor(private builder: TestMainThreadNotebookKernels) { + super(); + } + + override registerKernel(kernel: INotebookKernel) { + this.builder.registeredKernels.set(kernel.id, kernel); + return Disposable.None; + } + override onDidChangeSelectedNotebooks = Event.None; + override getMatchingKernel() { + return { + selected: undefined, + suggestions: [], + all: [], + hidden: [] + }; + } + }(this)); + + this.instantiationService.stub(INotebookExecutionStateService, new class extends mock() { + override createCellExecution(): INotebookCellExecution { + return new class extends mock() { }; + } + override createExecution(): INotebookExecution { + return new class extends mock() { }; + } + }); + + this.instantiationService.stub(INotebookService, new class extends mock() { + override getNotebookTextModel() { + return undefined; + } + }); + + this.instantiationService.stub(INotebookEditorService, new class extends mock() { + override listNotebookEditors() { + return []; + } + override onDidAddNotebookEditor = Event.None; + override onDidRemoveNotebookEditor = Event.None; + }); + } + + get instance(): MainThreadNotebookKernels { + return this.mainThreadNotebookKernels; + } + + async addKernel(id: string): Promise { + const handle = this.kernelHandle++; + await this.instance.$addKernel(handle, { + id, + notebookType: 'test-notebook', + extensionId: new ExtensionIdentifier('test.extension'), + extensionLocation: { scheme: 'test', path: '/test' }, + label: 'Test Kernel', + description: 'A test kernel', + hasVariableProvider: true + }); + } + + getKernel(id: string): INotebookKernel | undefined { + return this.registeredKernels.get(id); + } +} diff --git a/src/vs/workbench/api/test/browser/mainThreadVariableProvider.test.ts b/src/vs/workbench/api/test/browser/mainThreadVariableProvider.test.ts new file mode 100644 index 00000000000..568505d28e2 --- /dev/null +++ b/src/vs/workbench/api/test/browser/mainThreadVariableProvider.test.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { TestMainThreadNotebookKernels } from './TestMainThreadNotebookKernels.js'; +import { ExtHostNotebookKernelsShape } from '../../common/extHost.protocol.js'; +import { mock } from '../../../test/common/workbenchTestServices.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { SingleProxyRPCProtocol } from '../common/testRPCProtocol.js'; +import { AsyncIterableProducer } from '../../../../base/common/async.js'; +import { VariablesResult } from '../../../contrib/notebook/common/notebookKernelService.js'; + +type variableGetter = () => Promise; + +suite('MainThreadNotebookKernelVariableProvider', function () { + let mainThreadKernels: TestMainThreadNotebookKernels; + let variables: (VariablesResult | variableGetter)[]; + + teardown(function () { + }); + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + setup(async function () { + const proxy = new class extends mock() { + override async $provideVariables(handle: number, requestId: string, notebookUri: UriComponents, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): Promise { + for (const variable of variables) { + if (token.isCancellationRequested) { + return; + } + const result = typeof variable === 'function' + ? await variable() + : variable; + mainThreadKernels.instance.$receiveVariable(requestId, result); + } + } + }; + const extHostContext = SingleProxyRPCProtocol(proxy); + variables = []; + mainThreadKernels = store.add(new TestMainThreadNotebookKernels(extHostContext)); + }); + + test('get variables from kernel', async function () { + await mainThreadKernels.addKernel('test-kernel'); + + const kernel = mainThreadKernels.getKernel('test-kernel'); + assert.ok(kernel, 'Kernel should be registered'); + + variables.push(createVariable(1)); + variables.push(createVariable(2)); + const vars = kernel.provideVariables(URI.file('nb.ipynb'), undefined, 'named', 0, CancellationToken.None); + + await verifyVariables(vars, [1, 2]); + }); + + test('get variables twice', async function () { + await mainThreadKernels.addKernel('test-kernel'); + + const kernel = mainThreadKernels.getKernel('test-kernel'); + assert.ok(kernel, 'Kernel should be registered'); + + variables.push(createVariable(1)); + variables.push(createVariable(2)); + const vars = kernel.provideVariables(URI.file('nb.ipynb'), undefined, 'named', 0, CancellationToken.None); + const vars2 = kernel.provideVariables(URI.file('nb.ipynb'), undefined, 'named', 0, CancellationToken.None); + + await verifyVariables(vars, [1, 2]); + await verifyVariables(vars2, [1, 2]); + }); + + test('gets all variables async', async function () { + await mainThreadKernels.addKernel('test-kernel'); + + const kernel = mainThreadKernels.getKernel('test-kernel'); + assert.ok(kernel, 'Kernel should be registered'); + + variables.push(createVariable(1)); + const result = createVariable(2); + variables.push(async () => { + await new Promise(resolve => setTimeout(resolve, 5)); + return result; + }); + variables.push(createVariable(3)); + const vars = kernel.provideVariables(URI.file('nb.ipynb'), undefined, 'named', 0, CancellationToken.None); + + await verifyVariables(vars, [1, 2, 3]); + }); + + test('cancel while getting variables', async function () { + await mainThreadKernels.addKernel('test-kernel'); + + const kernel = mainThreadKernels.getKernel('test-kernel'); + assert.ok(kernel, 'Kernel should be registered'); + + variables.push(createVariable(1)); + const result = createVariable(2); + variables.push(async () => { + await new Promise(resolve => setTimeout(resolve, 50)); + return result; + }); + variables.push(createVariable(3)); + const cancellation = new CancellationTokenSource(); + const vars = kernel.provideVariables(URI.file('nb.ipynb'), undefined, 'named', 0, cancellation.token); + cancellation.cancel(); + + await verifyVariables(vars, [1, 2]); + }); +}); + +async function verifyVariables(variables: AsyncIterableProducer, expectedIds: number[]) { + let varIx = 0; + + for await (const variable of variables) { + assert.ok(expectedIds[varIx], 'more variables than expected'); + assert.strictEqual(variable.id, expectedIds[varIx++]); + } +} + +function createVariable(id: number) { + return { + id, + name: `var${id}`, + value: `${id}`, + type: 'number', + expression: `var${id}`, + hasNamedChildren: false, + indexedChildrenCount: 0, + extensionId: 'extension-id1', + }; +} From f16892aa45eaccc85f0101e63b148562e6008caf Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 7 Oct 2025 18:11:48 -0500 Subject: [PATCH 0932/4355] Fix quick chat (#270280) Fix quick chat (#270097) Fix #270087 --- build/lib/stylelint/vscode-known-variables.json | 11 +++++++++-- src/vs/workbench/contrib/chat/browser/media/chat.css | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index c55438ff451..3ff1a9f624b 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -979,6 +979,13 @@ "--vscode-action-item-auto-timeout", "--monaco-editor-warning-decoration", "--animation-opacity", - "--chat-setup-dialog-glow-angle" + "--chat-setup-dialog-glow-angle", + "--vscode-chat-font-family", + "--vscode-chat-font-size-body-l", + "--vscode-chat-font-size-body-m", + "--vscode-chat-font-size-body-s", + "--vscode-chat-font-size-body-xl", + "--vscode-chat-font-size-body-xs", + "--vscode-chat-font-size-body-xxl" ] -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index cbae3584b50..24563ad2c4b 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1654,6 +1654,10 @@ have to be updated for changes to the rules above, or to support more deeply nes /* #region Quick Chat */ +.quick-input-widget .interactive-session { + width: 100%; +} + .quick-input-widget .interactive-session .interactive-input-part { padding: 8px 6px 8px 6px; margin: 0 3px; From 45d6e6dc7858745cd39dcb26178497e2a7758d87 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 7 Oct 2025 16:30:13 -0700 Subject: [PATCH 0933/4355] Ignore character accents when searching in commands picker (#270248) * Ignore character accents when searching in commands picker * Report filter string normalization length change via telemetry --- .../quickinput/browser/commandsQuickAccess.ts | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index d2c32d03774..fec1e3088ac 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -7,7 +7,7 @@ import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } f import { CancellationToken } from '../../../base/common/cancellation.js'; import { toErrorMessage } from '../../../base/common/errorMessage.js'; import { isCancellationError } from '../../../base/common/errors.js'; -import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from '../../../base/common/filters.js'; +import { IMatch, matchesContiguousSubString, matchesPrefix, matchesWords, or } from '../../../base/common/filters.js'; import { createSingleCallFunction } from '../../../base/common/functional.js'; import { Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; import { LRUCache } from '../../../base/common/map.js'; @@ -25,6 +25,7 @@ import { IQuickAccessProviderRunOptions } from '../common/quickAccess.js'; import { IQuickPickSeparator } from '../common/quickInput.js'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { removeAccents } from '../../../base/common/normalization.js'; export interface ICommandQuickPick extends IPickerQuickAccessItem { readonly commandId: string; @@ -33,6 +34,10 @@ export interface ICommandQuickPick extends IPickerQuickAccessItem { readonly commandDescription?: ILocalizedString; tfIdfScore?: number; readonly args?: any[]; + + // These fields are lazy initialized during filtering process. + labelNoAccents?: string; + aliasNoAccents?: string; } export interface ICommandsQuickAccessOptions extends IPickerQuickAccessProviderOptions { @@ -90,11 +95,19 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc .slice(0, AbstractCommandsQuickAccessProvider.TFIDF_MAX_RESULTS); }); + const noAccentsFilter = this.normalizeForFiltering(filter); + // Filter const filteredCommandPicks: ICommandQuickPick[] = []; for (const commandPick of allCommandPicks) { - const labelHighlights = AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label) ?? undefined; - const aliasHighlights = commandPick.commandAlias ? AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias) ?? undefined : undefined; + commandPick.labelNoAccents ??= this.normalizeForFiltering(commandPick.label); + const labelHighlights = AbstractCommandsQuickAccessProvider.WORD_FILTER(noAccentsFilter, commandPick.labelNoAccents) ?? undefined; + + let aliasHighlights: IMatch[] | undefined; + if (commandPick.commandAlias) { + commandPick.aliasNoAccents ??= this.normalizeForFiltering(commandPick.commandAlias); + aliasHighlights = AbstractCommandsQuickAccessProvider.WORD_FILTER(noAccentsFilter, commandPick.aliasNoAccents) ?? undefined; + } // Add if matching in label or alias if (labelHighlights || aliasHighlights) { @@ -300,6 +313,37 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return chunk; } + + /** + * Normalizes a string for filtering by removing accents, but only if the result has the same length, otherwise + * returns the original string. + */ + private normalizeForFiltering(value: string): string { + const withoutAccents = removeAccents(value); + if (withoutAccents.length !== value.length) { + type QuickAccessTelemetry = { + originalLength: number; + normalizedLength: number; + }; + + type QuickAccessTelemetryMeta = { + originalLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Length of the original filter string' }; + normalizedLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Length of the normalized filter string' }; + owner: 'dmitriv'; + comment: 'Helps to gain insights on cases where the normalized filter string length differs from the original'; + }; + + this.telemetryService.publicLog2('QuickAccess:FilterLengthMismatch', { + originalLength: value.length, + normalizedLength: withoutAccents.length + }); + + return value; + } else { + return withoutAccents; + } + } + protected abstract getCommandPicks(token: CancellationToken): Promise>; protected abstract hasAdditionalCommandPicks(filter: string, token: CancellationToken): boolean; From 28db49ae9468da791e4ebedde59bde5e0d9794e2 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 7 Oct 2025 16:36:42 -0700 Subject: [PATCH 0934/4355] fix shadowing variables. --- .../contrib/chat/browser/languageModelToolsService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 639f8df959e..9db4ea2b050 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -326,7 +326,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo })); token = source.token; - const prepareTimeWatch = StopWatch.create(true); + prepareTimeWatch = StopWatch.create(true); const prepared = await this.prepareToolInvocation(tool, dto, token); prepareTimeWatch.stop(); @@ -366,7 +366,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } } } else { - const prepareTimeWatch = StopWatch.create(true); + prepareTimeWatch = StopWatch.create(true); const prepared = await this.prepareToolInvocation(tool, dto, token); prepareTimeWatch.stop(); if (prepared?.confirmationMessages && !(await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace))) { From 68fb92d6e0876f3cdb6bab387c5653dc8381b502 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:56:14 -0700 Subject: [PATCH 0935/4355] Use real maps for monaco-api/treeshaking Working on understanding this code and trying to modernize it a little --- build/lib/monaco-api.js | 27 +++++++++++++------------- build/lib/monaco-api.ts | 35 ++++++++++++++++------------------ build/lib/treeshaking.js | 35 +++++++++++++++++----------------- build/lib/treeshaking.ts | 41 ++++++++++++++++++++-------------------- 4 files changed, 68 insertions(+), 70 deletions(-) diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index 265addfd098..a19f31262aa 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -553,10 +553,10 @@ class DeclarationResolver { return new CacheEntry(this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5), mtime); } const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - const fileMap = { - 'file.ts': fileContents - }; - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); + const fileMap = new Map([ + ['file.ts', fileContents] + ]); + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {})); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime); } @@ -582,9 +582,10 @@ class TypeScriptLanguageServiceHost { return this._compilerOptions; } getScriptFileNames() { - return ([] - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files))); + return [ + ...this._libs.keys(), + ...this._files.keys(), + ]; } getScriptVersion(_fileName) { return '1'; @@ -593,11 +594,11 @@ class TypeScriptLanguageServiceHost { return '1'; } getScriptSnapshot(fileName) { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); + if (this._files.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)); } - else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + else if (this._libs.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)); } else { return this._ts.ScriptSnapshot.fromString(''); @@ -616,10 +617,10 @@ class TypeScriptLanguageServiceHost { return fileName === this.getDefaultLibFileName(this._compilerOptions); } readFile(path, _encoding) { - return this._files[path] || this._libs[path]; + return this._files.get(path) || this._libs.get(path); } fileExists(path) { - return path in this._files || path in this._libs; + return this._files.has(path) || this._libs.has(path); } } function execute() { diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index feee8a93a20..fe8b5c7f9e2 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -652,10 +652,10 @@ export class DeclarationResolver { ); } const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - const fileMap: IFileMap = { - 'file.ts': fileContents - }; - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); + const fileMap: IFileMap = new Map([ + ['file.ts', fileContents] + ]); + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {})); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry( this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), @@ -670,10 +670,8 @@ export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | } - - -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } +type ILibMap = Map; +type IFileMap = Map; class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { @@ -695,11 +693,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return this._compilerOptions; } getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); + return [ + ...this._libs.keys(), + ...this._files.keys(), + ]; } getScriptVersion(_fileName: string): string { return '1'; @@ -708,10 +705,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return '1'; } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + if (this._files.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)!); + } else if (this._libs.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)!); } else { return this._ts.ScriptSnapshot.fromString(''); } @@ -729,10 +726,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return fileName === this.getDefaultLibFileName(this._compilerOptions); } readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; + return this._files.get(path) || this._libs.get(path); } fileExists(path: string): boolean { - return path in this._files || path in this._libs; + return this._files.has(path) || this._libs.has(path); } } diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 5b339091e07..2792969d9a2 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -70,12 +70,12 @@ function createTypeScriptLanguageService(ts, options) { const FILES = discoverAndReadFiles(ts, options); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; + FILES.set(`inlineEntryPoint.${index}.ts`, inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path_1.default.join(options.sourcesRoot, typing); - FILES[typing] = fs_1.default.readFileSync(filePath).toString(); + FILES.set(typing, fs_1.default.readFileSync(filePath).toString()); }); // Resolve libs const RESOLVED_LIBS = processLibFiles(ts, options); @@ -87,7 +87,7 @@ function createTypeScriptLanguageService(ts, options) { * Read imports and follow them until all files have been handled */ function discoverAndReadFiles(ts, options) { - const FILES = {}; + const FILES = new Map(); const in_queue = Object.create(null); const queue = []; const enqueue = (moduleId) => { @@ -109,7 +109,7 @@ function discoverAndReadFiles(ts, options) { const dts_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); if (fs_1.default.existsSync(dts_filename)) { const dts_filecontents = fs_1.default.readFileSync(dts_filename).toString(); - FILES[`${moduleId}.d.ts`] = dts_filecontents; + FILES.set(`${moduleId}.d.ts`, dts_filecontents); continue; } const js_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.js'); @@ -135,7 +135,7 @@ function discoverAndReadFiles(ts, options) { } enqueue(importedModuleId); } - FILES[`${moduleId}.ts`] = ts_filecontents; + FILES.set(`${moduleId}.ts`, ts_filecontents); } return FILES; } @@ -144,15 +144,15 @@ function discoverAndReadFiles(ts, options) { */ function processLibFiles(ts, options) { const stack = [...options.compilerOptions.lib]; - const result = {}; + const result = new Map(); while (stack.length > 0) { const filename = `lib.${stack.shift().toLowerCase()}.d.ts`; const key = `defaultLib:${filename}`; - if (!result[key]) { + if (!result.has(key)) { // add this file const filepath = path_1.default.join(TYPESCRIPT_LIB_FOLDER, filename); const sourceText = fs_1.default.readFileSync(filepath).toString(); - result[key] = sourceText; + result.set(key, sourceText); // precess dependencies and "recurse" const info = ts.preProcessFile(sourceText); for (const ref of info.libReferenceDirectives) { @@ -181,9 +181,10 @@ class TypeScriptLanguageServiceHost { return this._compilerOptions; } getScriptFileNames() { - return ([] - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files))); + return [ + ...this._libs.keys(), + ...this._files.keys(), + ]; } getScriptVersion(_fileName) { return '1'; @@ -192,11 +193,11 @@ class TypeScriptLanguageServiceHost { return '1'; } getScriptSnapshot(fileName) { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); + if (this._files.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)); } - else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + else if (this._libs.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)); } else { return this._ts.ScriptSnapshot.fromString(''); @@ -215,10 +216,10 @@ class TypeScriptLanguageServiceHost { return fileName === this.getDefaultLibFileName(this._compilerOptions); } readFile(path, _encoding) { - return this._files[path] || this._libs[path]; + return this._files.get(path) || this._libs.get(path); } fileExists(path) { - return path in this._files || path in this._libs; + return this._files.has(path) || this._libs.has(path); } } //#endregion diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index f2b40f463ac..1267f7a8ab3 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -114,13 +114,13 @@ function createTypeScriptLanguageService(ts: typeof import('typescript'), option // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; + FILES.set(`inlineEntryPoint.${index}.ts`, inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path.join(options.sourcesRoot, typing); - FILES[typing] = fs.readFileSync(filePath).toString(); + FILES.set(typing, fs.readFileSync(filePath).toString()); }); // Resolve libs @@ -136,7 +136,7 @@ function createTypeScriptLanguageService(ts: typeof import('typescript'), option * Read imports and follow them until all files have been handled */ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): IFileMap { - const FILES: IFileMap = {}; + const FILES: IFileMap = new Map(); const in_queue: { [module: string]: boolean } = Object.create(null); const queue: string[] = []; @@ -163,7 +163,7 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); if (fs.existsSync(dts_filename)) { const dts_filecontents = fs.readFileSync(dts_filename).toString(); - FILES[`${moduleId}.d.ts`] = dts_filecontents; + FILES.set(`${moduleId}.d.ts`, dts_filecontents); continue; } @@ -196,7 +196,7 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha enqueue(importedModuleId); } - FILES[`${moduleId}.ts`] = ts_filecontents; + FILES.set(`${moduleId}.ts`, ts_filecontents); } return FILES; @@ -208,16 +208,16 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): ILibMap { const stack: string[] = [...options.compilerOptions.lib]; - const result: ILibMap = {}; + const result: ILibMap = new Map(); while (stack.length > 0) { const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; const key = `defaultLib:${filename}`; - if (!result[key]) { + if (!result.has(key)) { // add this file const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); const sourceText = fs.readFileSync(filepath).toString(); - result[key] = sourceText; + result.set(key, sourceText); // precess dependencies and "recurse" const info = ts.preProcessFile(sourceText); @@ -230,8 +230,8 @@ function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingO return result; } -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } +type ILibMap = Map; +type IFileMap = Map; /** * A TypeScript language service host @@ -256,11 +256,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return this._compilerOptions; } getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); + return [ + ...this._libs.keys(), + ...this._files.keys(), + ]; } getScriptVersion(_fileName: string): string { return '1'; @@ -269,10 +268,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return '1'; } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + if (this._files.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)!); + } else if (this._libs.has(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)!); } else { return this._ts.ScriptSnapshot.fromString(''); } @@ -290,10 +289,10 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return fileName === this.getDefaultLibFileName(this._compilerOptions); } readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; + return this._files.get(path) || this._libs.get(path); } fileExists(path: string): boolean { - return path in this._files || path in this._libs; + return this._files.has(path) || this._libs.has(path); } } //#endregion From 670605c1dd498e1871a9ea6117d9109285d39824 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 7 Oct 2025 17:20:38 -0700 Subject: [PATCH 0936/4355] Provide toolMetadata with todo tool responses (#270282) * Add toolMetadata to handleReadOperation for warning messages * Remove unnecessary newline in toolMetadata warnings message --- .../contrib/chat/common/tools/manageTodoListTool.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts index 1d41c7a7301..f98bc47f21b 100644 --- a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts @@ -339,7 +339,10 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { content: [{ kind: 'text', value: `Successfully wrote todo list${warnings.length ? '\n\n' + warnings.join('\n') : ''}` - }] + }], + toolMetadata: { + warnings: warnings + } }; } From 8989d61bb6f5632f795e390ab6901d86b1502b21 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 7 Oct 2025 19:32:23 -0500 Subject: [PATCH 0937/4355] Fix command arg param --- .../contrib/chat/browser/contrib/chatInputCompletions.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 53593d63127..0364d20f09c 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -14,6 +14,7 @@ import { ResourceSet } from '../../../../../base/common/map.js'; import { Schemas } from '../../../../../base/common/network.js'; import { basename } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { ICodeEditor, getCodeEditor, isCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; @@ -875,7 +876,10 @@ class BuiltinDynamicCompletions extends Disposable { return result; }); - this._register(CommandsRegistry.registerCommand(BuiltinDynamicCompletions.addReferenceCommand, (_services, arg) => this.cmdAddReference(arg))); + this._register(CommandsRegistry.registerCommand(BuiltinDynamicCompletions.addReferenceCommand, (_services, arg) => { + assertType(arg instanceof ReferenceArgument); + return this.cmdAddReference(arg); + })); } private findActiveCodeEditor(): ICodeEditor | undefined { From 7de163a36daed0e14b3ab87425edadcf7c0d68b2 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 7 Oct 2025 17:33:13 -0700 Subject: [PATCH 0938/4355] always swallow the enter key for inserting new lines. --- .../contrib/chat/browser/chatInputPart.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 53ea272a85e..33be311567a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1162,8 +1162,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); ChatContextKeys.inChatInput.bindTo(inputScopedContextKeyService).set(true); this.currentlyEditingInputKey = ChatContextKeys.currentlyEditingInput.bindTo(inputScopedContextKeyService); - // Track whether a request is currently being processed so we can suppress plain Enter behavior - const requestInProgressKey = ChatContextKeys.requestInProgress.bindTo(inputScopedContextKeyService); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); @@ -1201,15 +1199,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.overflowWidgetsDomNode?.classList.add('hideSuggestTextIcons'); this._inputEditorElement.classList.add('hideSuggestTextIcons'); - // Prevent Enter key from creating new lines when input is empty or a request is currently in progress. + // Prevent Enter key from creating new lines - but allow keybinding service to still handle the event + // We need to prevent Monaco's default Enter behavior while still allowing the VS Code keybinding service + // to receive and process the Enter key for ChatSubmitAction this._register(this._inputEditor.onKeyDown((e) => { if (e.keyCode === KeyCode.Enter && hasNoModifierKeys(e)) { - const inputHasText = this.inputEditorHasText?.get(); - if (!inputHasText || requestInProgressKey.get()) { - // Suppress newline insertion to mirror disabled send state - e.preventDefault(); - e.stopPropagation(); - } + // Only prevent the default Monaco behavior (newline insertion) + // Do NOT call stopPropagation() so the keybinding service can still process this event + e.preventDefault(); } })); From f9ea0442d33294806c08f8be8bb868c1ea3300a9 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Tue, 7 Oct 2025 18:59:01 -0700 Subject: [PATCH 0939/4355] Enabled F3 and Shift+F3 in notebooks. --- .../contrib/find/browser/findController.ts | 119 +++++++----- .../find/test/browser/findController.test.ts | 8 +- .../browser/contrib/find/notebookFind.ts | 181 +++++++++++++++++- .../contrib/find/notebookFindWidget.ts | 26 ++- 4 files changed, 278 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 605e2ae7e54..6970a3ac45f 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -684,63 +684,98 @@ export abstract class MatchFindAction extends EditorAction { protected abstract _run(controller: CommonFindController): boolean; } -export class NextMatchFindAction extends MatchFindAction { +export const NextMatchFindAction = registerMultiEditorAction(new MultiEditorAction({ + id: FIND_IDS.NextMatchFindAction, + label: nls.localize2('findNextMatchAction', "Find Next"), + precondition: undefined, + kbOpts: [{ + kbExpr: EditorContextKeys.focus, + primary: KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, + weight: KeybindingWeight.EditorContrib + }, { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + }] +})); - constructor() { - super({ - id: FIND_IDS.NextMatchFindAction, - label: nls.localize2('findNextMatchAction', "Find Next"), - precondition: undefined, - kbOpts: [{ - kbExpr: EditorContextKeys.focus, - primary: KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, - weight: KeybindingWeight.EditorContrib - }, { - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), - primary: KeyCode.Enter, - weight: KeybindingWeight.EditorContrib - }] - }); +NextMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise => { + const controller = CommonFindController.get(editor); + if (!controller) { + return; } - protected _run(controller: CommonFindController): boolean { + const runMatch = (): boolean => { const result = controller.moveToNextMatch(); if (result) { controller.editor.pushUndoStop(); return true; } - return false; + }; + + if (!runMatch()) { + await controller.start({ + forceRevealReplace: false, + seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none', + seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection', + seedSearchStringFromGlobalClipboard: true, + shouldFocus: FindStartFocusAction.NoFocusChange, + shouldAnimate: true, + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop + }); + runMatch(); } -} +}); -export class PreviousMatchFindAction extends MatchFindAction { +export const PreviousMatchFindAction = registerMultiEditorAction(new MultiEditorAction({ + id: FIND_IDS.PreviousMatchFindAction, + label: nls.localize2('findPreviousMatchAction', "Find Previous"), + precondition: undefined, + kbOpts: [{ + kbExpr: EditorContextKeys.focus, + primary: KeyMod.Shift | KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] }, + weight: KeybindingWeight.EditorContrib + }, { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + }] +})); - constructor() { - super({ - id: FIND_IDS.PreviousMatchFindAction, - label: nls.localize2('findPreviousMatchAction', "Find Previous"), - precondition: undefined, - kbOpts: [{ - kbExpr: EditorContextKeys.focus, - primary: KeyMod.Shift | KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] }, - weight: KeybindingWeight.EditorContrib - }, { - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), - primary: KeyMod.Shift | KeyCode.Enter, - weight: KeybindingWeight.EditorContrib - } - ] - }); +PreviousMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise => { + const controller = CommonFindController.get(editor); + if (!controller) { + return; } - protected _run(controller: CommonFindController): boolean { - return controller.moveToPrevMatch(); + const runMatch = (): boolean => { + const result = controller.moveToPrevMatch(); + if (result) { + controller.editor.pushUndoStop(); + return true; + } + return false; + }; + + if (!runMatch()) { + await controller.start({ + forceRevealReplace: false, + seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none', + seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection', + seedSearchStringFromGlobalClipboard: true, + shouldFocus: FindStartFocusAction.NoFocusChange, + shouldAnimate: true, + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop + }); + runMatch(); } -} +}); export class MoveToMatchFindAction extends EditorAction { @@ -989,8 +1024,6 @@ registerEditorContribution(CommonFindController.ID, FindController, EditorContri registerEditorAction(StartFindWithArgsAction); registerEditorAction(StartFindWithSelectionAction); -registerEditorAction(NextMatchFindAction); -registerEditorAction(PreviousMatchFindAction); registerEditorAction(MoveToMatchFindAction); registerEditorAction(NextSelectionMatchFindAction); registerEditorAction(PreviousSelectionMatchFindAction); diff --git a/src/vs/editor/contrib/find/test/browser/findController.test.ts b/src/vs/editor/contrib/find/test/browser/findController.test.ts index ea683d5b9a4..38aaa3d492f 100644 --- a/src/vs/editor/contrib/find/test/browser/findController.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findController.test.ts @@ -168,7 +168,7 @@ suite('FindController', () => { // The cursor is at the very top, of the file, at the first ABC const findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); const findState = findController.getState(); - const nextMatchFindAction = new NextMatchFindAction(); + const nextMatchFindAction = NextMatchFindAction; // I hit Ctrl+F to show the Find dialog await executeAction(instantiationService, editor, StartFindAction); @@ -220,7 +220,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, async (editor) => { clipboardState = ''; const findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - const nextMatchFindAction = new NextMatchFindAction(); + const nextMatchFindAction = NextMatchFindAction; editor.setPosition({ lineNumber: 1, @@ -245,7 +245,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, async (editor, _, instantiationService) => { clipboardState = ''; const findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - const nextMatchFindAction = new NextMatchFindAction(); + const nextMatchFindAction = NextMatchFindAction; editor.setSelection(new Selection(1, 9, 1, 13)); @@ -268,7 +268,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, async (editor, _, instantiationService) => { const testRegexString = 'tes.'; const findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); - const nextMatchFindAction = new NextMatchFindAction(); + const nextMatchFindAction = NextMatchFindAction; findController.toggleRegex(); findController.setSearchString(testRegexString); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index 94e37788a69..7bd8a16c161 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -13,15 +13,16 @@ import { ICodeEditorService } from '../../../../../../editor/browser/services/co import { EditorOption } from '../../../../../../editor/common/config/editorOptions.js'; import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; -import { FindStartFocusAction, getSelectionSearchString, IFindStartOptions, StartFindAction, StartFindReplaceAction } from '../../../../../../editor/contrib/find/browser/findController.js'; +import { FindStartFocusAction, getSelectionSearchString, IFindStartOptions, NextMatchFindAction, PreviousMatchFindAction, StartFindAction, StartFindReplaceAction } from '../../../../../../editor/contrib/find/browser/findController.js'; import { localize2 } from '../../../../../../nls.js'; import { Action2, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; + import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IShowNotebookFindWidgetOptions, NotebookFindContrib } from './notebookFindWidget.js'; import { INotebookCommandContext, NotebookMultiCellAction } from '../../controller/coreActions.js'; -import { getNotebookEditorFromEditorPane } from '../../notebookBrowser.js'; +import { getNotebookEditorFromEditorPane, INotebookEditor } from '../../notebookBrowser.js'; import { registerNotebookContribution } from '../../notebookEditorExtensions.js'; import { CellUri, NotebookFindScopeType } from '../../../common/notebookCommon.js'; import { INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from '../../../common/notebookContextKeys.js'; @@ -116,11 +117,7 @@ function getSearchStringOptions(editor: ICodeEditor, opts: IFindStartOptions) { return undefined; } - -StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - +function isNotebookEditor(accessor: ServicesAccessor, editor: INotebookEditor | undefined, codeEditor: ICodeEditor) { if (!editor) { return false; } @@ -135,12 +132,22 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: const textEditor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); if (editor.hasModel() && textEditor && textEditor.hasModel() && notebookContainsTextModel(editor.textModel.uri, textEditor.getModel())) { // the active text editor is in notebook editor + return true; } else { return false; } } + return true; +} - const controller = editor.getContribution(NotebookFindContrib.id); +function openFindWidget(controller: NotebookFindContrib | undefined, editor: INotebookEditor | undefined, codeEditor: ICodeEditor | undefined) { + if (!editor || !codeEditor || !controller) { + return false; + } + + if (!codeEditor.hasModel()) { + return false; + } const searchStringOptions = getSearchStringOptions(codeEditor, { forceRevealReplace: false, @@ -167,6 +174,18 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: controller.show(searchStringOptions?.searchString, options); return true; +} + +StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!isNotebookEditor(accessor, editor, codeEditor)) { + return false; + } + + const controller = editor?.getContribution(NotebookFindContrib.id); + return openFindWidget(controller, editor, codeEditor); }); StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { @@ -201,3 +220,149 @@ StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeE return false; }); + +NextMatchFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!isNotebookEditor(accessor, editor, codeEditor)) { + return false; + } + + const controller = editor?.getContribution(NotebookFindContrib.id); + if (!controller) { + return false; + } + + // Check if find widget is already visible + if (controller.isVisible()) { + // Find widget is open, navigate to next match + controller.findNext(); + return true; + } else { + // Find widget is not open, open it with search string (similar to StartFindAction) + return openFindWidget(controller, editor, codeEditor); + } +}); + +PreviousMatchFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!isNotebookEditor(accessor, editor, codeEditor)) { + return false; + } + + const controller = editor?.getContribution(NotebookFindContrib.id); + if (!controller) { + return false; + } + + // Check if find widget is already visible + if (controller.isVisible()) { + // Find widget is open, navigate to previous match + controller.findPrevious(); + return true; + } else { + // Find widget is not open, open it with search string (similar to StartFindAction) + return openFindWidget(controller, editor, codeEditor); + } +}); + +// Widget-focused keybindings - these handle F3/Shift+F3 when the notebook find widget has focus +// This follows the same pattern as the text editor which has separate keybindings for widget focus + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.findNext.fromWidget', + title: localize2('notebook.findNext.fromWidget', 'Find Next (from Find Widget)'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED + ), + primary: KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, + weight: KeybindingWeight.WorkbenchContrib + 1 // Higher priority than editor + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookFindContrib.id); + if (controller && controller.isVisible()) { + controller.findNext(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.findPrevious.fromWidget', + title: localize2('notebook.findPrevious.fromWidget', 'Find Previous (from Find Widget)'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED + ), + primary: KeyMod.Shift | KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] }, + weight: KeybindingWeight.WorkbenchContrib + 1 // Higher priority than editor + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookFindContrib.id); + if (controller && controller.isVisible()) { + controller.findPrevious(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.findNext.enter', + title: localize2('notebook.findNext.enter', 'Find Next (Enter)'), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED + ), + primary: KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib + 1 // Higher priority than editor + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookFindContrib.id); + if (controller && controller.isVisible()) { + controller.findNext(); + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index 546efce7200..94ae3bfb1dd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -76,6 +76,22 @@ export class NotebookFindContrib extends Disposable implements INotebookEditorCo replace(searchString: string | undefined) { return this._widget.value.replace(searchString); } + + isVisible(): boolean { + return this._widget.rawValue?.isVisible ?? false; + } + + findNext(): void { + if (this._widget.rawValue) { + this._widget.value.findNext(); + } + } + + findPrevious(): void { + if (this._widget.rawValue) { + this._widget.value.findPrevious(); + } + } } class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution { @@ -183,6 +199,14 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi this._findModel.find({ previous }); } + public findNext(): void { + this.find(false); + } + + public findPrevious(): void { + this.find(true); + } + protected replaceOne() { if (!this._notebookEditor.hasModel()) { return; @@ -275,7 +299,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi await this._findModel.research(); } this.findIndex(options.matchIndex); - } else { + } else if (options?.focus !== false) { this._findInput.select(); } From 0bcc625c206b8be996d2a5498822d25cd80ab962 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:09:37 +0900 Subject: [PATCH 0940/4355] Add missing semi-colon --- .../terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts index ffc5805f401..d2b2f3b0b72 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts @@ -55,7 +55,7 @@ const core = (terminal: Terminal): IXtermCore => { _core: IXtermCore; } return (terminal as XtermWithCore)._core; -} +}; const flushOutput = (terminal: Terminal) => { // TODO: Flushing output is not possible anymore without async }; From b8880b85efc2cd99ac3c36446e8ba2532deec413 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:12:24 +0900 Subject: [PATCH 0941/4355] Cover missing monaco-workbench case --- src/vs/editor/browser/services/hoverService/hoverService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 676e4b31545..0be16fdf68e 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -603,7 +603,7 @@ registerSingleton(IHoverService, HoverService, InstantiationType.Delayed); registerThemingParticipant((theme, collector) => { const hoverBorder = theme.getColor(editorHoverBorder); if (hoverBorder) { - collector.addRule(`.monaco-workbench .workbench-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); - collector.addRule(`.monaco-workbench .workbench-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); + collector.addRule(`.monaco-hover.workbench-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); + collector.addRule(`.monaco-hover.workbench-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`); } }); From 949deddf72cdc37383349bcb4decb63382b9ca10 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Wed, 8 Oct 2025 05:41:13 +0300 Subject: [PATCH 0942/4355] fix: memory leak in getTerminalActionBarArgs (#269516) * todo * clear disposables * Discard changes to src/vs/workbench/contrib/terminal/browser/terminalMenus.ts --- src/vs/workbench/contrib/terminal/browser/terminalEditor.ts | 1 + src/vs/workbench/contrib/terminal/browser/terminalView.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index d8c997ef51c..0bbc6c9e896 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -152,6 +152,7 @@ export class TerminalEditor extends EditorPane { } private _updateTabActionBar(profiles: ITerminalProfile[]): void { + this._disposableStore.clear(); const actions = getTerminalActionBarArgs(TerminalLocation.Editor, profiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu, this._disposableStore); this._newDropdown.value?.update(actions.dropdownAction, actions.dropdownMenuActions); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 50bfba8865a..9e6bf5a1c64 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -290,6 +290,7 @@ export class TerminalViewPane extends ViewPane { } case TerminalCommandId.New: { if (action instanceof MenuItemAction) { + this._disposableStore.clear(); const actions = getTerminalActionBarArgs(TerminalLocation.Panel, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu, this._disposableStore); this._newDropdown.value = this._instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, { hoverDelegate: options.hoverDelegate, @@ -318,6 +319,7 @@ export class TerminalViewPane extends ViewPane { } private _updateTabActionBar(profiles: ITerminalProfile[]): void { + this._disposableStore.clear(); const actions = getTerminalActionBarArgs(TerminalLocation.Panel, profiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu, this._disposableStore); this._newDropdown.value?.update(actions.dropdownAction, actions.dropdownMenuActions); } From 0d826f594e95fc043234ca7e1e2db56d19086165 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:10:37 +0900 Subject: [PATCH 0943/4355] Remove environmentChangesIndicator setting It's no longer referenced. Fixes #190891 --- src/vs/platform/terminal/common/terminal.ts | 1 - src/vs/workbench/contrib/terminal/common/terminal.ts | 1 - .../contrib/terminal/common/terminalConfiguration.ts | 11 ----------- 3 files changed, 13 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index fddc83070b5..c272a8ff496 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -87,7 +87,6 @@ export const enum TerminalSettingId { EnvMacOs = 'terminal.integrated.env.osx', EnvLinux = 'terminal.integrated.env.linux', EnvWindows = 'terminal.integrated.env.windows', - EnvironmentChangesIndicator = 'terminal.integrated.environmentChangesIndicator', EnvironmentChangesRelaunch = 'terminal.integrated.environmentChangesRelaunch', ShowExitAlert = 'terminal.integrated.showExitAlert', SplitCwd = 'terminal.integrated.splitCwd', diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index a51e741f597..bb315cedd2f 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -174,7 +174,6 @@ export interface ITerminalConfiguration { osx: { [key: string]: string }; windows: { [key: string]: string }; }; - environmentChangesIndicator: 'off' | 'on' | 'warnonly'; environmentChangesRelaunch: boolean; showExitAlert: boolean; splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index d92d577f5e5..ebd5bd55d8d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -458,17 +458,6 @@ const terminalConfiguration: IStringDictionary = { }, default: {} }, - [TerminalSettingId.EnvironmentChangesIndicator]: { - markdownDescription: localize('terminal.integrated.environmentChangesIndicator', "Whether to display the environment changes indicator on each terminal which explains whether extensions have made, or want to make changes to the terminal's environment."), - type: 'string', - enum: ['off', 'on', 'warnonly'], - enumDescriptions: [ - localize('terminal.integrated.environmentChangesIndicator.off', "Disable the indicator."), - localize('terminal.integrated.environmentChangesIndicator.on', "Enable the indicator."), - localize('terminal.integrated.environmentChangesIndicator.warnonly', "Only show the warning indicator when a terminal's environment is 'stale', not the information indicator that shows a terminal has had its environment modified by an extension."), - ], - default: 'warnonly' - }, [TerminalSettingId.EnvironmentChangesRelaunch]: { markdownDescription: localize('terminal.integrated.environmentChangesRelaunch', "Whether to relaunch terminals automatically if extensions want to contribute to their environment and have not been interacted with yet."), type: 'boolean', From 73782ae065791f284e4eab2f7c21922f7edd9046 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:01:25 +0900 Subject: [PATCH 0944/4355] Remove deprecated managedhover from findwidget + toggle Part of #243348 --- src/vs/base/browser/ui/findinput/findInput.ts | 11 +++--- .../browser/ui/findinput/findInputToggles.ts | 11 +++--- .../base/browser/ui/findinput/replaceInput.ts | 3 +- src/vs/base/browser/ui/hover/hover.ts | 16 +++++++++ src/vs/base/browser/ui/toggle/toggle.ts | 19 ++++++---- .../services/hoverService/hoverWidget.ts | 20 ++++++++++- .../contrib/find/browser/findOptionsWidget.ts | 10 +++--- .../editor/contrib/find/browser/findWidget.ts | 36 +++++++++++-------- .../browser/find/simpleFindWidget.ts | 6 ++++ .../contrib/find/notebookFindReplaceWidget.ts | 11 ++++++ 10 files changed, 102 insertions(+), 41 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 2182b0bd2cb..c830b3e9ab3 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -16,8 +16,8 @@ import { KeyCode } from '../../../common/keyCodes.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js'; -import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IHistory } from '../../../common/history.js'; +import type { IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputOptions { @@ -114,13 +114,12 @@ export class FindInput extends Widget { history: options.history })); - const hoverDelegate = this._register(createInstantHoverDelegate()); - if (this.showCommonFindToggles) { + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'find-input' }; this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.regex.onChange(viaKeyboard => { @@ -137,7 +136,7 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: appendWholeWordsLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.wholeWords.onChange(viaKeyboard => { @@ -151,7 +150,7 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: appendCaseSensitiveLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.caseSensitive.onChange(viaKeyboard => { diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index c13fcafe5f8..a594c4c3c77 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { Toggle } from '../toggle/toggle.js'; import { Codicon } from '../../../common/codicons.js'; import * as nls from '../../../../nls.js'; +import type { IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputToggleOpts { readonly appendTitle: string; @@ -15,7 +14,7 @@ export interface IFindInputToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); @@ -28,7 +27,7 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -42,7 +41,7 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -56,7 +55,7 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 018a26fc1bd..3062cfecc57 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,7 +16,6 @@ import { Emitter, Event } from '../../../common/event.js'; import { KeyCode } from '../../../common/keyCodes.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IHistory } from '../../../common/history.js'; @@ -46,7 +45,7 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground, diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 6c682c3c083..50510f6e9a6 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -148,6 +148,17 @@ export interface IHoverWidget extends IDisposable { readonly isDisposed: boolean; } +export const enum HoverStyle { + /** + * The hover is anchored below the element with a pointer above it pointing at the target. + */ + Pointer = 1, + /** + * The hover is anchored to the bottom right of the cursor's location. + */ + Mouse = 2, +} + export interface IHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the @@ -205,6 +216,11 @@ export interface IHoverOptions { */ trapFocus?: boolean; + /** + * The style of the hover, this sets default values of {@link position} and {@link appearance}: + */ + style?: HoverStyle; + /** * Options that defines where the hover is positioned. */ diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 34e08dba3f1..7dce08a5b69 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -11,10 +11,9 @@ import { ThemeIcon } from '../../../common/themables.js'; import { $, addDisposableListener, EventType, isActiveElement } from '../../dom.js'; import { IKeyboardEvent } from '../../keyboardEvent.js'; import { BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js'; -import type { IManagedHover } from '../hover/hover.js'; +import { HoverStyle, IHoverLifecycleOptions } from '../hover/hover.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { Widget } from '../widget.js'; import './toggle.css'; @@ -24,7 +23,11 @@ export interface IToggleOpts extends IToggleStyles { readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; + // TODO: Remove this, the previous default was mouse, so anything not mouse needs to be explicit + /** @deprecated Prefer hoverStyle and hoverLifecycleOptions instead */ readonly hoverDelegate?: IHoverDelegate; + readonly hoverStyle?: HoverStyle; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } export interface IToggleStyles { @@ -128,16 +131,17 @@ export class Toggle extends Widget { get onKeyDown(): Event { return this._onKeyDown.event; } private readonly _opts: IToggleOpts; + private _title: string; private _icon: ThemeIcon | undefined; readonly domNode: HTMLElement; private _checked: boolean; - private _hover: IManagedHover; constructor(opts: IToggleOpts) { super(); this._opts = opts; + this._title = this._opts.title; this._checked = this._opts.isChecked; const classes = ['monaco-custom-toggle']; @@ -153,15 +157,18 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this._hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); + this._register(getBaseLayerHoverDelegate().setupDelayedHover(this.domNode, () => ({ + content: this._title, + style: this._opts.hoverStyle ?? HoverStyle.Mouse, + }), this._opts.hoverLifecycleOptions)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; } this.domNode.setAttribute('role', 'checkbox'); this.domNode.setAttribute('aria-checked', String(this._checked)); - this.domNode.setAttribute('aria-label', this._opts.title); + this.setTitle(this._opts.title); this.applyStyles(); this.onclick(this.domNode, (ev) => { @@ -245,7 +252,7 @@ export class Toggle extends Widget { } setTitle(newTitle: string): void { - this._hover.update(newTitle); + this._title = newTitle; this.domNode.setAttribute('aria-label', newTitle); } diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index a8784044f9e..65614b6f1a2 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -23,7 +23,7 @@ import { localize } from '../../../../nls.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { status } from '../../../../base/browser/ui/aria/aria.js'; -import type { IHoverOptions, IHoverTarget, IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; +import { HoverStyle, type IHoverOptions, type IHoverTarget, type IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; import { TimeoutTimer } from '../../../../base/common/async.js'; import { isNumber } from '../../../../base/common/types.js'; @@ -113,6 +113,24 @@ export class HoverWidget extends Widget implements IHoverWidget { this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target); + if (options.style) { + switch (options.style) { + case HoverStyle.Pointer: { + options.appearance ??= {}; + options.appearance.compact ??= true; + options.appearance.showPointer ??= true; + options.position ??= {}; + options.position.hoverPosition ??= HoverPosition.BELOW; + break; + } + case HoverStyle.Mouse: { + options.appearance ??= {}; + options.appearance.compact ??= true; + break; + } + } + } + this._hoverPointer = options.appearance?.showPointer ? $('div.workbench-hover-pointer') : undefined; this._hover = this._register(new BaseHoverWidget(!options.appearance?.skipFadeInAnimation)); this._hover.containerDomNode.classList.add('workbench-hover'); diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 137f792ff50..57bb5c1c951 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -13,7 +13,7 @@ import { FIND_IDS } from './findModel.js'; import { FindReplaceState } from './findState.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from '../../../../platform/theme/common/colorRegistry.js'; -import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import type { IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -53,12 +53,12 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), }; - const hoverDelegate = this._register(createInstantHoverDelegate()); + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'find-options-widget' }; this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), isChecked: this._state.matchCase, - hoverDelegate, + hoverLifecycleOptions, ...toggleStyles })); this._domNode.appendChild(this.caseSensitive.domNode); @@ -71,7 +71,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), isChecked: this._state.wholeWord, - hoverDelegate, + hoverLifecycleOptions, ...toggleStyles })); this._domNode.appendChild(this.wholeWords.domNode); @@ -84,7 +84,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.regex = this._register(new RegexToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), isChecked: this._state.isRegex, - hoverDelegate, + hoverLifecycleOptions, ...toggleStyles })); this._domNode.appendChild(this.regex.domNode); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 97d96df256f..4635b24532a 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -41,10 +41,10 @@ import { isHighContrast } from '../../../../platform/theme/common/theme.js'; import { assertReturnsDefined } from '../../../../base/common/types.js'; import { defaultInputBoxStyles, defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { Selection } from '../../../common/core/selection.js'; -import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IHistory } from '../../../../base/common/history.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; +import type { IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); @@ -999,14 +999,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._matchesCount.className = 'matchesCount'; this._updateMatchesCount(); - // Create a scoped hover delegate for all find related buttons - const hoverDelegate = this._register(createInstantHoverDelegate()); + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'find-widget' }; // Previous button this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), icon: findPreviousMatchIcon, - hoverDelegate, + hoverLifecycleOptions, onTrigger: () => { assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1016,7 +1015,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), icon: findNextMatchIcon, - hoverDelegate, + hoverLifecycleOptions, onTrigger: () => { assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1037,7 +1036,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false, - hoverDelegate: hoverDelegate, + hoverLifecycleOptions, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), @@ -1072,7 +1071,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), icon: widgetClose, - hoverDelegate, + hoverLifecycleOptions, onTrigger: () => { this._state.change({ isRevealed: false, searchScope: null }, false); }, @@ -1134,14 +1133,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } })); - // Create scoped hover delegate for replace actions - const replaceHoverDelegate = this._register(createInstantHoverDelegate()); - // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), icon: findReplaceIcon, - hoverDelegate: replaceHoverDelegate, + hoverLifecycleOptions, onTrigger: () => { this._controller.replace(); }, @@ -1157,7 +1153,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction), icon: findReplaceAllIcon, - hoverDelegate: replaceHoverDelegate, + hoverLifecycleOptions, onTrigger: () => { this._controller.replaceAll(); } @@ -1300,7 +1296,7 @@ export interface ISimpleButtonOpts { readonly label: string; readonly className?: string; readonly icon?: ThemeIcon; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; readonly onTrigger: () => void; readonly onKeyDown?: (e: IKeyboardEvent) => void; } @@ -1330,7 +1326,17 @@ export class SimpleButton extends Widget { this._domNode.className = className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); - this._register(hoverService.setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); + this._register(hoverService.setupDelayedHover(this._domNode, { + content: this._opts.label, + appearance: { + compact: true, + showPointer: true, + }, + position: { + hoverPosition: HoverPosition.BELOW + }, + }, opts.hoverLifecycleOptions)); + // this._register(hoverService.setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); this.onclick(this._domNode, (e) => { this._opts.onTrigger(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 673572ba51e..eff5ea8254c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -26,6 +26,7 @@ import { defaultInputBoxStyles, defaultToggleStyles } from '../../../../../platf import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from '../../../../../base/browser/ui/sash/sash.js'; import { registerColor } from '../../../../../platform/theme/common/colorRegistry.js'; import type { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import type { IHoverLifecycleOptions } from '../../../../../base/browser/ui/hover/hover.js'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -140,9 +141,12 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa this.findFirst(); })); + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'simple-find-widget' }; + this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + (options.previousMatchActionId ? this._getKeybinding(options.previousMatchActionId) : ''), icon: findPreviousMatchIcon, + hoverLifecycleOptions, onTrigger: () => { this.find(true); } @@ -151,6 +155,7 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa this.nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + (options.nextMatchActionId ? this._getKeybinding(options.nextMatchActionId) : ''), icon: findNextMatchIcon, + hoverLifecycleOptions, onTrigger: () => { this.find(false); } @@ -159,6 +164,7 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + (options.closeWidgetActionId ? this._getKeybinding(options.closeWidgetActionId) : ''), icon: widgetClose, + hoverLifecycleOptions, onTrigger: () => { this.hide(); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 522a4ccb825..3a2294650a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -49,6 +49,7 @@ import { IShowNotebookFindWidgetOptions } from './notebookFindWidget.js'; import { ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookDeltaDecoration, INotebookEditor } from '../../notebookBrowser.js'; import { NotebookFindScopeType, NotebookSetting } from '../../../common/notebookCommon.js'; import { ICellRange } from '../../../common/notebookRange.js'; +import type { IHoverLifecycleOptions } from '../../../../../../base/browser/ui/hover/hover.js'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); @@ -375,10 +376,14 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._domNode.appendChild(progressContainer); const isInteractiveWindow = contextKeyService.getContextKeyValue('notebookType') === 'interactive'; + + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'simple-find-widget' }; + // Toggle replace button this._toggleReplaceBtn = this._register(new SimpleButton({ label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, className: 'codicon toggle left', + hoverLifecycleOptions, onTrigger: isInteractiveWindow ? () => { } : () => { this._isReplaceVisible = !this._isReplaceVisible; @@ -466,6 +471,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, icon: findPreviousMatchIcon, + hoverLifecycleOptions, onTrigger: () => { this.find(true); } @@ -474,6 +480,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL, icon: findNextMatchIcon, + hoverLifecycleOptions, onTrigger: () => { this.find(false); } @@ -483,6 +490,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE, isChecked: false, + hoverLifecycleOptions, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), @@ -535,6 +543,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL, icon: widgetClose, + hoverLifecycleOptions, onTrigger: () => { this.hide(); } @@ -604,6 +613,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL, icon: findReplaceIcon, + hoverLifecycleOptions, onTrigger: () => { this.replaceOne(); } @@ -613,6 +623,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL, icon: findReplaceAllIcon, + hoverLifecycleOptions, onTrigger: () => { this.replaceAll(); } From c5cb367cb57692f16d6c52c799c93bd95c8565ba Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:15:03 +0900 Subject: [PATCH 0945/4355] Cover tree find/filter --- src/vs/base/browser/ui/findinput/findInput.ts | 3 ++- .../browser/ui/findinput/findInputToggles.ts | 5 ++++- src/vs/base/browser/ui/tree/abstractTree.ts | 16 +++++++++------- src/vs/editor/contrib/find/browser/findWidget.ts | 3 ++- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index c830b3e9ab3..2c9c47f4d5e 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -38,6 +38,7 @@ export interface IFindInputOptions { readonly toggleStyles: IToggleStyles; readonly inputBoxStyles: IInputBoxStyles; readonly history?: IHistory; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -115,7 +116,7 @@ export class FindInput extends Widget { })); if (this.showCommonFindToggles) { - const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'find-input' }; + const hoverLifecycleOptions: IHoverLifecycleOptions = options?.hoverLifecycleOptions || { groupId: 'find-input' }; this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index a594c4c3c77..fc8f79dfd85 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -6,7 +6,7 @@ import { Toggle } from '../toggle/toggle.js'; import { Codicon } from '../../../common/codicons.js'; import * as nls from '../../../../nls.js'; -import type { IHoverLifecycleOptions } from '../hover/hover.js'; +import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputToggleOpts { readonly appendTitle: string; @@ -27,6 +27,7 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -41,6 +42,7 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -55,6 +57,7 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d00b38b2b7a..a6d64e469b8 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,11 +33,10 @@ import { clamp } from '../../../common/numbers.js'; import { ScrollEvent } from '../../../common/scrollable.js'; import './media/tree.css'; import { localize } from '../../../../nls.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; -import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { autorun, constObservable } from '../../../common/observable.js'; import { alert } from '../aria/aria.js'; import { IMouseWheelEvent } from '../../mouseEvent.js'; +import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -708,7 +707,7 @@ class TreeFindToggle extends Toggle { readonly id: string; - constructor(contribution: ITreeFindToggleContribution, opts: IToggleStyles, hoverDelegate?: IHoverDelegate) { + constructor(contribution: ITreeFindToggleContribution, opts: IToggleStyles, hoverLifecycleOptions?: IHoverLifecycleOptions) { super({ icon: contribution.icon, title: contribution.title, @@ -716,7 +715,8 @@ class TreeFindToggle extends Toggle { inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground, - hoverDelegate, + hoverStyle: HoverStyle.Pointer, + hoverLifecycleOptions, }); this.id = contribution.id; @@ -840,8 +840,9 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - const toggleHoverDelegate = this._register(createInstantHoverDelegate()); - this.toggles = toggleContributions.map(contribution => this._register(new TreeFindToggle(contribution, styles.toggleStyles, toggleHoverDelegate))); + // const toggleHoverDelegate = this._register(createInstantHoverDelegate()); + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'abstract-tree' }; + this.toggles = toggleContributions.map(contribution => this._register(new TreeFindToggle(contribution, styles.toggleStyles, hoverLifecycleOptions))); this.onDidToggleChange = Event.any(...this.toggles.map(toggle => Event.map(toggle.onChange, () => ({ id: toggle.id, isChecked: toggle.checked })))); const history = options?.history || []; @@ -852,7 +853,8 @@ class FindWidget extends Disposable { showCommonFindToggles: false, inputBoxStyles: styles.inputBoxStyles, toggleStyles: styles.toggleStyles, - history: new Set(history) + history: new Set(history), + hoverLifecycleOptions, })); this.actionbar = this._register(new ActionBar(this.elements.actionbar)); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 4635b24532a..465206e5121 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -44,7 +44,7 @@ import { Selection } from '../../../common/core/selection.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IHistory } from '../../../../base/common/history.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import type { IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { HoverStyle, type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); @@ -1036,6 +1036,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false, + hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), From dd36ce93506aafff0d3c1f81ab7063c70677f430 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:21:13 +0900 Subject: [PATCH 0946/4355] Migrate showContextToggle in search editor --- src/vs/workbench/contrib/search/browser/searchWidget.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 77b589490ec..03d3632c51f 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -45,6 +45,7 @@ import { SearchFindInput } from './searchFindInput.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { NotebookFindScopeType } from '../../notebook/common/notebookCommon.js'; +import { HoverStyle } from '../../../../base/browser/ui/hover/hover.js'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; @@ -465,7 +466,8 @@ export class SearchWidget extends Widget { isChecked: false, title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId)), icon: searchShowContextIcon, - hoverDelegate: getDefaultHoverDelegate('element'), + hoverStyle: HoverStyle.Pointer, + hoverLifecycleOptions: { groupId: 'search-widget' }, ...defaultToggleStyles }); this._register(this.showContextToggle.onChange(() => this.onContextLinesChanged())); From 57a1fbd9cc8bbe39b00a79372c47dd003414005a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:29:31 +0900 Subject: [PATCH 0947/4355] Pass lifecycle options into nb find filters This doesn't work without adoption in IBaseActionViewItemOptions --- src/vs/workbench/contrib/search/browser/searchWidget.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 03d3632c51f..96eb749a5ff 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -396,6 +396,7 @@ export class SearchWidget extends Widget { private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void { const history = options.searchHistory || []; + const hoverLifecycleOptions = { groupId: 'search-widget' }; const inputOptions: IFindInputOptions = { label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search'), validation: (value: string) => this.validateSearchInput(value), @@ -409,7 +410,8 @@ export class SearchWidget extends Widget { flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT, showCommonFindToggles: true, inputBoxStyles: options.inputBoxStyles, - toggleStyles: options.toggleStyles + toggleStyles: options.toggleStyles, + hoverLifecycleOptions, }; const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box')); @@ -467,7 +469,7 @@ export class SearchWidget extends Widget { title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId)), icon: searchShowContextIcon, hoverStyle: HoverStyle.Pointer, - hoverLifecycleOptions: { groupId: 'search-widget' }, + hoverLifecycleOptions, ...defaultToggleStyles }); this._register(this.showContextToggle.onChange(() => this.onContextLinesChanged())); From 15baeaa77975a1058b487f6df601e7fbe0eff967 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:31:02 +0900 Subject: [PATCH 0948/4355] Adopt in patterns input widget --- .../workbench/contrib/search/browser/patternInputWidget.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 3e233ca2802..9df598afc0b 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { HoverStyle } from '../../../../base/browser/ui/hover/hover.js'; export interface IOptions { placeholder?: string; @@ -221,7 +221,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { icon: Codicon.book, title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, - hoverDelegate: getDefaultHoverDelegate('element'), + hoverStyle: HoverStyle.Pointer, ...defaultToggleStyles })); this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { @@ -274,7 +274,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { actionClassName: 'useExcludesAndIgnoreFiles', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, - hoverDelegate: getDefaultHoverDelegate('element'), + hoverStyle: HoverStyle.Pointer, ...defaultToggleStyles })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { From 6a660317a1cf9f202b0f48a7a9189930b9bae39b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:55:05 +0900 Subject: [PATCH 0949/4355] Remove hoverDelegate from ICheckboxStyles --- src/vs/base/browser/ui/toggle/toggle.ts | 8 ++++---- src/vs/workbench/contrib/chat/browser/chatStatus.ts | 2 +- src/vs/workbench/contrib/mcp/browser/mcpCommands.ts | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 7dce08a5b69..f3a511a869d 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -43,7 +43,7 @@ export interface ICheckboxStyles { readonly checkboxDisabledBackground: string | undefined; readonly checkboxDisabledForeground: string | undefined; readonly size?: number; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } export const unthemedToggleStyles = { @@ -69,7 +69,7 @@ export class ToggleActionViewItem extends BaseActionViewItem { inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, - hoverDelegate: options.hoverDelegate + hoverDelegate: options.hoverDelegate, })); this._register(this.toggle.onChange(() => { this._action.checked = !!this.toggle && this.toggle.checked; @@ -323,7 +323,7 @@ abstract class BaseCheckbox extends Widget { export class Checkbox extends BaseCheckbox { constructor(title: string, isChecked: boolean, styles: ICheckboxStyles) { - const toggle = new Toggle({ title, isChecked, icon: Codicon.check, actionClassName: BaseCheckbox.CLASS_NAME, hoverDelegate: styles.hoverDelegate, ...unthemedToggleStyles }); + const toggle = new Toggle({ title, isChecked, icon: Codicon.check, actionClassName: BaseCheckbox.CLASS_NAME, hoverLifecycleOptions: styles.hoverLifecycleOptions, ...unthemedToggleStyles }); super(toggle, toggle.domNode, styles); this._register(toggle); @@ -375,7 +375,7 @@ export class TriStateCheckbox extends BaseCheckbox { isChecked: _state === true, icon, actionClassName: Checkbox.CLASS_NAME, - hoverDelegate: styles.hoverDelegate, + hoverLifecycleOptions: styles.hoverLifecycleOptions, ...unthemedToggleStyles }); super( diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 7024192bd84..673fc8ab3e9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -715,7 +715,7 @@ class ChatStatusDashboard extends Disposable { } private createSetting(container: HTMLElement, settingIdsToReEvaluate: string[], label: string, accessor: ISettingsAccessor, disposables: DisposableStore): Checkbox { - const checkbox = disposables.add(new Checkbox(label, Boolean(accessor.readSetting()), { ...defaultCheckboxStyles, hoverDelegate: nativeHoverDelegate })); + const checkbox = disposables.add(new Checkbox(label, Boolean(accessor.readSetting()), { ...defaultCheckboxStyles })); container.appendChild(checkbox.domNode); const settingLabel = append(container, $('span.setting-label', undefined, label)); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index 3cb8356533f..571fe414124 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -30,7 +30,6 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../platform/files/common/files.js'; -import { nativeHoverDelegate } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { mcpAutoStartConfig, McpAutoStartValue } from '../../../../platform/mcp/common/mcpManagement.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; @@ -605,7 +604,7 @@ export class MCPServerActionRendering extends Disposable implements IWorkbenchCo const checkbox = store.add(new Checkbox( settingLabelStr, config.get() !== McpAutoStartValue.Never, - { ...defaultCheckboxStyles, hoverDelegate: nativeHoverDelegate } + { ...defaultCheckboxStyles } )); checkboxContainer.appendChild(checkbox.domNode); From 2d66eadf7e9adc59b06fc996c85ca2c0e3e12010 Mon Sep 17 00:00:00 2001 From: avarayr <7735415+avarayr@users.noreply.github.com> Date: Wed, 8 Oct 2025 02:15:34 -0400 Subject: [PATCH 0950/4355] fix: Increase workbench border radius on macos tahoe (#270236) * fix: increase workbench border radius on macos tahoe * chore: add TODO * chore: fix format --------- Co-authored-by: Robo --- src/vs/base/common/platform.ts | 4 ++++ src/vs/workbench/browser/media/style.css | 4 ++++ .../parts/statusbar/media/statusbarpart.css | 5 +++++ .../browser/media/processExplorer.css | 6 ++++++ src/vs/workbench/electron-browser/desktop.main.ts | 14 +++++++++++--- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index cf76f7ce0e1..10bb68dfd97 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -278,3 +278,7 @@ export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); export function isBigSurOrNewer(osVersion: string): boolean { return parseFloat(osVersion) >= 20; } + +export function isTahoeOrNewer(osVersion: string): boolean { + return parseFloat(osVersion) >= 25; +} diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 923f442b2f4..7f9dc676745 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -73,6 +73,10 @@ body { border-radius: 10px; /* macOS Big Sur increased rounded corners size */ } +.monaco-workbench.border.mac.macos-tahoe-or-newer { + border-radius: 12px; /* macOS Tahoe increased rounded corners size even more */ +} + .monaco-workbench img { border: 0; } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index b3396ad9530..73ac7aa6719 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -27,6 +27,11 @@ border-bottom-right-radius: 10px; border-bottom-left-radius: 10px; } +.monaco-workbench.mac:not(.fullscreen).macos-tahoe-or-newer .part.statusbar:focus { + /* macOS Tahoe increased rounded corners size even more */ + border-bottom-right-radius: 12px; + border-bottom-left-radius: 12px; +} .monaco-workbench .part.statusbar:not(:focus).status-border-top::after { /* Top border only visible unless focused to make room for focus outline */ diff --git a/src/vs/workbench/contrib/processExplorer/browser/media/processExplorer.css b/src/vs/workbench/contrib/processExplorer/browser/media/processExplorer.css index 1990ca43167..1f176a45dff 100644 --- a/src/vs/workbench/contrib/processExplorer/browser/media/processExplorer.css +++ b/src/vs/workbench/contrib/processExplorer/browser/media/processExplorer.css @@ -67,6 +67,12 @@ border-bottom-left-radius: 10px; } +.mac:not(.fullscreen).macos-tahoe-or-newer .process-explorer .monaco-list:focus::before { + /* macOS Tahoe increased rounded corners size even more */ + border-bottom-right-radius: 12px; + border-bottom-left-radius: 12px; +} + .process-explorer .monaco-list-row:first-of-type { border-bottom: 1px solid var(--vscode-tree-tableColumnsBorder); } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 8e2c74424aa..99d19844e01 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -45,7 +45,7 @@ import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService } from import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../platform/workspace/common/workspaceTrust.js'; import { safeStringify } from '../../base/common/objects.js'; import { IUtilityProcessWorkerWorkbenchService, UtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-browser/utilityProcessWorkerWorkbenchService.js'; -import { isBigSurOrNewer, isCI, isMacintosh } from '../../base/common/platform.js'; +import { isBigSurOrNewer, isCI, isMacintosh, isTahoeOrNewer } from '../../base/common/platform.js'; import { Schemas } from '../../base/common/network.js'; import { DiskFileSystemProvider } from '../services/files/electron-browser/diskFileSystemProvider.js'; import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; @@ -153,8 +153,16 @@ export class DesktopMain extends Disposable { } private getExtraClasses(): string[] { - if (isMacintosh && isBigSurOrNewer(this.configuration.os.release)) { - return ['macos-bigsur-or-newer']; + if (isMacintosh) { + // TODO: Revisit the border radius values till Electron v40 adoption + // Refs https://github.com/electron/electron/issues/47514 and + // https://github.com/microsoft/vscode/pull/270236#issuecomment-3379301185 + if (isTahoeOrNewer(this.configuration.os.release)) { + return ['macos-tahoe-or-newer']; + } + if (isBigSurOrNewer(this.configuration.os.release)) { + return ['macos-bigsur-or-newer']; + } } return []; From 91eff0f597756a80cac97b5d90113538567b80be Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:51:49 +0200 Subject: [PATCH 0951/4355] Remove some anys (#270326) --- .vscode/searches/no-any-casts.code-search | 216 +++++------------- src/vs/base/node/ports.ts | 9 +- .../model/tokens/treeSitter/tokenStore.ts | 6 +- .../test/common/model/tokenStore.test.ts | 69 +++--- .../api/browser/mainThreadTreeViews.ts | 3 +- .../workbench/api/common/extHostComments.ts | 9 +- .../api/common/extHostTunnelService.ts | 3 +- .../api/test/browser/extHostTreeViews.test.ts | 3 +- .../comments/browser/commentThreadWidget.ts | 6 +- .../test/browser/commentsView.test.ts | 6 +- .../test/browser/releaseNotesRenderer.test.ts | 6 +- .../common/variableResolver.ts | 3 +- .../configurationResolverService.test.ts | 3 +- .../services/remote/common/tunnelModel.ts | 18 +- 14 files changed, 114 insertions(+), 246 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index 6b76b6156e0..35e1695868c 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,6 +1,6 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -925 results - 351 files +863 results - 326 files build/azure-pipelines/upload-cdn.ts: 115: // eslint-disable-next-line local/code-no-any-casts @@ -27,11 +27,6 @@ build/lib/nls.ts: build/lib/optimize.ts: 254: // eslint-disable-next-line local/code-no-any-casts -build/lib/propertyInitOrderChecker.ts: - 254: // eslint-disable-next-line local/code-no-any-casts - 256: // eslint-disable-next-line local/code-no-any-casts - 258: // eslint-disable-next-line local/code-no-any-casts - build/lib/reporter.ts: 108: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts @@ -41,14 +36,14 @@ build/lib/task.ts: 27: // eslint-disable-next-line local/code-no-any-casts build/lib/treeshaking.ts: - 310: // eslint-disable-next-line local/code-no-any-casts - 314: // eslint-disable-next-line local/code-no-any-casts - 318: // eslint-disable-next-line local/code-no-any-casts - 322: // eslint-disable-next-line local/code-no-any-casts - 691: // eslint-disable-next-line local/code-no-any-casts - 920: // eslint-disable-next-line local/code-no-any-casts - 922: // eslint-disable-next-line local/code-no-any-casts - 924: // eslint-disable-next-line local/code-no-any-casts + 309: // eslint-disable-next-line local/code-no-any-casts + 313: // eslint-disable-next-line local/code-no-any-casts + 317: // eslint-disable-next-line local/code-no-any-casts + 321: // eslint-disable-next-line local/code-no-any-casts + 690: // eslint-disable-next-line local/code-no-any-casts + 919: // eslint-disable-next-line local/code-no-any-casts + 921: // eslint-disable-next-line local/code-no-any-casts + 923: // eslint-disable-next-line local/code-no-any-casts build/lib/mangle/index.ts: 273: // eslint-disable-next-line local/code-no-any-casts @@ -213,22 +208,6 @@ src/vs/base/browser/ui/sash/sash.ts: 503: // eslint-disable-next-line local/code-no-any-casts 505: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/tree/abstractTree.ts: - 171: // eslint-disable-next-line local/code-no-any-casts - 175: // eslint-disable-next-line local/code-no-any-casts - 1186: // eslint-disable-next-line local/code-no-any-casts - 1212: // eslint-disable-next-line local/code-no-any-casts - 2241: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/ui/tree/asyncDataTree.ts: - 305: // eslint-disable-next-line local/code-no-any-casts - 417: // eslint-disable-next-line local/code-no-any-casts - 434: // eslint-disable-next-line local/code-no-any-casts - 438: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/ui/tree/indexTreeModel.ts: - 89: // eslint-disable-next-line local/code-no-any-casts - src/vs/base/common/console.ts: 134: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts @@ -331,9 +310,6 @@ src/vs/base/common/observableInternal/utils/utilsCancellation.ts: 78: // eslint-disable-next-line local/code-no-any-casts 83: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/node/ports.ts: - 182: // eslint-disable-next-line local/code-no-any-casts - src/vs/base/parts/ipc/test/node/ipc.net.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts @@ -595,17 +571,6 @@ src/vs/editor/test/common/model/textModel.test.ts: src/vs/editor/test/common/model/textModelWithTokens.test.ts: 272: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/tokenStore.test.ts: - 57: // eslint-disable-next-line local/code-no-any-casts - 78: // eslint-disable-next-line local/code-no-any-casts - 100: // eslint-disable-next-line local/code-no-any-casts - 120: // eslint-disable-next-line local/code-no-any-casts - 139: // eslint-disable-next-line local/code-no-any-casts - 158: // eslint-disable-next-line local/code-no-any-casts - 177: // eslint-disable-next-line local/code-no-any-casts - 203: // eslint-disable-next-line local/code-no-any-casts - 234: // eslint-disable-next-line local/code-no-any-casts - src/vs/platform/contextkey/common/contextkey.ts: 939: // eslint-disable-next-line local/code-no-any-casts 1213: // eslint-disable-next-line local/code-no-any-casts @@ -650,9 +615,6 @@ src/vs/platform/list/browser/listService.ts: 1012: // eslint-disable-next-line local/code-no-any-casts 1057: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/mcp/test/common/mcpManagementService.test.ts: - 879: // eslint-disable-next-line local/code-no-any-casts - src/vs/platform/observable/common/wrapInHotClass.ts: 12: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts @@ -715,9 +677,6 @@ src/vs/workbench/workbench.web.main.ts: 82: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadChatAgents2.ts: - 365: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/browser/mainThreadExtensionService.ts: 57: // eslint-disable-next-line local/code-no-any-casts @@ -731,9 +690,6 @@ src/vs/workbench/api/browser/mainThreadQuickOpen.ts: 203: // eslint-disable-next-line local/code-no-any-casts 216: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadTreeViews.ts: - 378: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/browser/viewsExtensionPoint.ts: 528: // eslint-disable-next-line local/code-no-any-casts @@ -747,9 +703,6 @@ src/vs/workbench/api/common/extHost.protocol.ts: 2106: // eslint-disable-next-line local/code-no-any-casts 2108: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostComments.ts: - 672: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/common/extHostDebugService.ts: 243: // eslint-disable-next-line local/code-no-any-casts 491: // eslint-disable-next-line local/code-no-any-casts @@ -801,9 +754,6 @@ src/vs/workbench/api/common/extHostTimeline.ts: 166: // eslint-disable-next-line local/code-no-any-casts 169: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTunnelService.ts: - 133: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/common/extHostTypeConverters.ts: 463: // eslint-disable-next-line local/code-no-any-casts 856: // eslint-disable-next-line local/code-no-any-casts @@ -889,9 +839,6 @@ src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: 327: // eslint-disable-next-line local/code-no-any-casts 340: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTreeViews.test.ts: - 771: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/test/browser/extHostTypes.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts @@ -906,17 +853,6 @@ src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: 115: // eslint-disable-next-line local/code-no-any-casts 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts: - 141: // eslint-disable-next-line local/code-no-any-casts - 215: // eslint-disable-next-line local/code-no-any-casts - 230: // eslint-disable-next-line local/code-no-any-casts - 307: // eslint-disable-next-line local/code-no-any-casts - 310: // eslint-disable-next-line local/code-no-any-casts - 313: // eslint-disable-next-line local/code-no-any-casts - 316: // eslint-disable-next-line local/code-no-any-casts - 508: // eslint-disable-next-line local/code-no-any-casts - 512: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts 93: // eslint-disable-next-line local/code-no-any-casts @@ -976,10 +912,6 @@ src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: 126: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatAgents.ts: - 746: // eslint-disable-next-line local/code-no-any-casts - 748: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/chat/common/chatModel.ts: 1214: // eslint-disable-next-line local/code-no-any-casts 1531: // eslint-disable-next-line local/code-no-any-casts @@ -1017,40 +949,32 @@ src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: 144: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: - 48: // eslint-disable-next-line local/code-no-any-casts - 75: // eslint-disable-next-line local/code-no-any-casts - 87: // eslint-disable-next-line local/code-no-any-casts - 99: // eslint-disable-next-line local/code-no-any-casts - 111: // eslint-disable-next-line local/code-no-any-casts - 123: // eslint-disable-next-line local/code-no-any-casts - 135: // eslint-disable-next-line local/code-no-any-casts - 145: // eslint-disable-next-line local/code-no-any-casts - 157: // eslint-disable-next-line local/code-no-any-casts - 167: // eslint-disable-next-line local/code-no-any-casts - 179: // eslint-disable-next-line local/code-no-any-casts - 189: // eslint-disable-next-line local/code-no-any-casts - 201: // eslint-disable-next-line local/code-no-any-casts - 211: // eslint-disable-next-line local/code-no-any-casts - 256: // eslint-disable-next-line local/code-no-any-casts - 267: // eslint-disable-next-line local/code-no-any-casts - 278: // eslint-disable-next-line local/code-no-any-casts - 289: // eslint-disable-next-line local/code-no-any-casts - 300: // eslint-disable-next-line local/code-no-any-casts - 311: // eslint-disable-next-line local/code-no-any-casts - 322: // eslint-disable-next-line local/code-no-any-casts - 333: // eslint-disable-next-line local/code-no-any-casts - 349: // eslint-disable-next-line local/code-no-any-casts + 72: // eslint-disable-next-line local/code-no-any-casts + 84: // eslint-disable-next-line local/code-no-any-casts + 96: // eslint-disable-next-line local/code-no-any-casts + 108: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + 132: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + 154: // eslint-disable-next-line local/code-no-any-casts + 164: // eslint-disable-next-line local/code-no-any-casts + 176: // eslint-disable-next-line local/code-no-any-casts + 186: // eslint-disable-next-line local/code-no-any-casts + 198: // eslint-disable-next-line local/code-no-any-casts + 208: // eslint-disable-next-line local/code-no-any-casts + 253: // eslint-disable-next-line local/code-no-any-casts + 264: // eslint-disable-next-line local/code-no-any-casts + 275: // eslint-disable-next-line local/code-no-any-casts + 286: // eslint-disable-next-line local/code-no-any-casts + 297: // eslint-disable-next-line local/code-no-any-casts + 308: // eslint-disable-next-line local/code-no-any-casts + 319: // eslint-disable-next-line local/code-no-any-casts + 330: // eslint-disable-next-line local/code-no-any-casts + 346: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts: - 189: // eslint-disable-next-line local/code-no-any-casts - 196: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts: - 93: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/debug/browser/debugSession.ts: 1193: // eslint-disable-next-line local/code-no-any-casts @@ -1264,20 +1188,20 @@ src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: 328: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: - 1491: // eslint-disable-next-line local/code-no-any-casts - 1500: // eslint-disable-next-line local/code-no-any-casts - 1539: // eslint-disable-next-line local/code-no-any-casts - 1585: // eslint-disable-next-line local/code-no-any-casts - 1739: // eslint-disable-next-line local/code-no-any-casts - 1786: // eslint-disable-next-line local/code-no-any-casts - 1789: // eslint-disable-next-line local/code-no-any-casts - 2641: // eslint-disable-next-line local/code-no-any-casts - 2822: // eslint-disable-next-line local/code-no-any-casts - 3554: // eslint-disable-next-line local/code-no-any-casts - 3588: // eslint-disable-next-line local/code-no-any-casts - 3594: // eslint-disable-next-line local/code-no-any-casts - 3723: // eslint-disable-next-line local/code-no-any-casts - 3782: // eslint-disable-next-line local/code-no-any-casts + 1492: // eslint-disable-next-line local/code-no-any-casts + 1501: // eslint-disable-next-line local/code-no-any-casts + 1540: // eslint-disable-next-line local/code-no-any-casts + 1586: // eslint-disable-next-line local/code-no-any-casts + 1740: // eslint-disable-next-line local/code-no-any-casts + 1787: // eslint-disable-next-line local/code-no-any-casts + 1790: // eslint-disable-next-line local/code-no-any-casts + 2656: // eslint-disable-next-line local/code-no-any-casts + 2837: // eslint-disable-next-line local/code-no-any-casts + 3569: // eslint-disable-next-line local/code-no-any-casts + 3603: // eslint-disable-next-line local/code-no-any-casts + 3609: // eslint-disable-next-line local/code-no-any-casts + 3738: // eslint-disable-next-line local/code-no-any-casts + 3797: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/tasks/common/problemMatcher.ts: 361: // eslint-disable-next-line local/code-no-any-casts @@ -1374,17 +1298,10 @@ src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTermin 857: // eslint-disable-next-line local/code-no-any-casts 862: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts: - 94: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: 102: // eslint-disable-next-line local/code-no-any-casts 108: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts: - 152: // eslint-disable-next-line local/code-no-any-casts - 351: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: 242: // eslint-disable-next-line local/code-no-any-casts @@ -1408,23 +1325,12 @@ src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDete 136: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts: - 409: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: - 685: // eslint-disable-next-line local/code-no-any-casts - 711: // eslint-disable-next-line local/code-no-any-casts - 786: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/terminalContrib/typeAhead/browser/terminalTypeAheadAddon.ts: - 53: // eslint-disable-next-line local/code-no-any-casts + 790: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution.ts: - 75: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts @@ -1464,10 +1370,6 @@ src/vs/workbench/contrib/testing/test/common/testStubs.ts: src/vs/workbench/contrib/themes/browser/themes.contribution.ts: 614: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts: - 68: // eslint-disable-next-line local/code-no-any-casts - 91: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: 600: // eslint-disable-next-line local/code-no-any-casts @@ -1482,12 +1384,6 @@ src/vs/workbench/services/configurationResolver/common/configurationResolverExpr 106: // eslint-disable-next-line local/code-no-any-casts 306: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/configurationResolver/common/variableResolver.ts: - 93: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts: - 516: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/services/driver/browser/driver.ts: 193: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts @@ -1513,14 +1409,6 @@ src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/remote/common/tunnelModel.ts: - 255: // eslint-disable-next-line local/code-no-any-casts - 260: // eslint-disable-next-line local/code-no-any-casts - 262: // eslint-disable-next-line local/code-no-any-casts - 298: // eslint-disable-next-line local/code-no-any-casts - 335: // eslint-disable-next-line local/code-no-any-casts - 398: // eslint-disable-next-line local/code-no-any-casts - src/vs/workbench/services/search/common/search.ts: 628: // eslint-disable-next-line local/code-no-any-casts 631: // eslint-disable-next-line local/code-no-any-casts @@ -1584,11 +1472,11 @@ src/vs/workbench/test/browser/window.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/test/browser/workbenchTestServices.ts: - 305: // eslint-disable-next-line local/code-no-any-casts - 697: // eslint-disable-next-line local/code-no-any-casts - 1064: // eslint-disable-next-line local/code-no-any-casts - 1967: // eslint-disable-next-line local/code-no-any-casts - 1985: // eslint-disable-next-line local/code-no-any-casts + 304: // eslint-disable-next-line local/code-no-any-casts + 696: // eslint-disable-next-line local/code-no-any-casts + 1063: // eslint-disable-next-line local/code-no-any-casts + 1966: // eslint-disable-next-line local/code-no-any-casts + 1984: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: 98: // eslint-disable-next-line local/code-no-any-casts @@ -1609,7 +1497,7 @@ src/vs/workbench/test/common/resources.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/test/common/workbenchTestServices.ts: - 291: // eslint-disable-next-line local/code-no-any-casts + 293: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/test/electron-browser/workbenchTestServices.ts: 255: // eslint-disable-next-line local/code-no-any-casts diff --git a/src/vs/base/node/ports.ts b/src/vs/base/node/ports.ts index 9cf88a47c84..1bd338695e0 100644 --- a/src/vs/base/node/ports.ts +++ b/src/vs/base/node/ports.ts @@ -151,6 +151,10 @@ export function isPortFree(port: number, timeout: number): Promise { return findFreePortFaster(port, 0, timeout).then(port => port !== 0); } +interface ServerError { + code?: string; +} + /** * Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener. */ @@ -178,9 +182,8 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo server.on('listening', () => { doResolve(startPort, resolve); }); - server.on('error', err => { - // eslint-disable-next-line local/code-no-any-casts - if (err && ((err).code === 'EADDRINUSE' || (err).code === 'EACCES') && (countTried < giveUpAfter)) { + server.on('error', (err: ServerError) => { + if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES') && (countTried < giveUpAfter)) { startPort++; countTried++; server.listen(startPort, hostname); diff --git a/src/vs/editor/common/model/tokens/treeSitter/tokenStore.ts b/src/vs/editor/common/model/tokens/treeSitter/tokenStore.ts index c21c5cea7c3..616c4a85387 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/tokenStore.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/tokenStore.ts @@ -6,7 +6,8 @@ import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { ITextModel } from '../../../model.js'; -class ListNode implements IDisposable { +// Exported for tests +export class ListNode implements IDisposable { parent?: ListNode; private readonly _children: Node[] = []; get children(): ReadonlyArray { return this._children; } @@ -93,7 +94,8 @@ export enum TokenQuality { type Node = ListNode | LeafNode; -interface LeafNode { +// Exported for tests +export interface LeafNode { readonly length: number; token: number; tokenQuality: TokenQuality; diff --git a/src/vs/editor/test/common/model/tokenStore.test.ts b/src/vs/editor/test/common/model/tokenStore.test.ts index a87a693d0b9..82d82741e43 100644 --- a/src/vs/editor/test/common/model/tokenStore.test.ts +++ b/src/vs/editor/test/common/model/tokenStore.test.ts @@ -6,7 +6,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { TextModel } from '../../../common/model/textModel.js'; -import { TokenQuality, TokenStore } from '../../../common/model/tokens/treeSitter/tokenStore.js'; +import { LeafNode, ListNode, TokenQuality, TokenStore } from '../../../common/model/tokens/treeSitter/tokenStore.js'; suite('TokenStore', () => { let textModel: TextModel; @@ -54,8 +54,7 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 2, token: 4 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const root = store.root as any; + const root = store.root as ListNode; assert.ok(root.children); assert.strictEqual(root.children.length, 2); assert.strictEqual(root.children[0].length, 4); @@ -75,14 +74,13 @@ suite('TokenStore', () => { { startOffsetInclusive: 7, length: 1, token: 8 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const root = store.root as any; + const root = store.root as ListNode; assert.ok(root.children); assert.strictEqual(root.children.length, 2); - assert.ok(root.children[0].children); - assert.strictEqual(root.children[0].children.length, 2); - assert.ok(root.children[0].children[0].children); - assert.strictEqual(root.children[0].children[0].children.length, 2); + assert.ok((root.children[0] as ListNode).children); + assert.strictEqual((root.children[0] as ListNode).children.length, 2); + assert.ok(((root.children[0] as ListNode).children[0] as ListNode).children); + assert.strictEqual(((root.children[0] as ListNode).children[0] as ListNode).children.length, 2); }); test('updates single token in middle', () => { @@ -97,11 +95,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 3, token: 4 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const tokens = store.root as any; - assert.strictEqual(tokens.children[0].token, 1); - assert.strictEqual(tokens.children[1].token, 4); - assert.strictEqual(tokens.children[2].token, 3); + const tokens = store.root as ListNode; + assert.strictEqual((tokens.children[0] as LeafNode).token, 1); + assert.strictEqual((tokens.children[1] as LeafNode).token, 4); + assert.strictEqual((tokens.children[2] as LeafNode).token, 3); }); test('updates multiple consecutive tokens', () => { @@ -117,11 +114,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 3, token: 5 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const tokens = store.root as any; - assert.strictEqual(tokens.children[0].token, 1); - assert.strictEqual(tokens.children[1].token, 4); - assert.strictEqual(tokens.children[2].token, 5); + const tokens = store.root as ListNode; + assert.strictEqual((tokens.children[0] as LeafNode).token, 1); + assert.strictEqual((tokens.children[1] as LeafNode).token, 4); + assert.strictEqual((tokens.children[2] as LeafNode).token, 5); }); test('updates tokens at start of document', () => { @@ -136,11 +132,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 0, length: 3, token: 4 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const tokens = store.root as any; - assert.strictEqual(tokens.children[0].token, 4); - assert.strictEqual(tokens.children[1].token, 2); - assert.strictEqual(tokens.children[2].token, 3); + const tokens = store.root as ListNode; + assert.strictEqual((tokens.children[0] as LeafNode).token, 4); + assert.strictEqual((tokens.children[1] as LeafNode).token, 2); + assert.strictEqual((tokens.children[2] as LeafNode).token, 3); }); test('updates tokens at end of document', () => { @@ -155,11 +150,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 6, length: 3, token: 4 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const tokens = store.root as any; - assert.strictEqual(tokens.children[0].token, 1); - assert.strictEqual(tokens.children[1].token, 2); - assert.strictEqual(tokens.children[2].token, 4); + const tokens = store.root as ListNode; + assert.strictEqual((tokens.children[0] as LeafNode).token, 1); + assert.strictEqual((tokens.children[1] as LeafNode).token, 2); + assert.strictEqual((tokens.children[2] as LeafNode).token, 4); }); test('updates length of tokens', () => { @@ -174,11 +168,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 5, token: 4 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const tokens = store.root as any; - assert.strictEqual(tokens.children[0].token, 1); + const tokens = store.root as ListNode; + assert.strictEqual((tokens.children[0] as LeafNode).token, 1); assert.strictEqual(tokens.children[0].length, 3); - assert.strictEqual(tokens.children[1].token, 4); + assert.strictEqual((tokens.children[1] as LeafNode).token, 4); assert.strictEqual(tokens.children[1].length, 5); }); @@ -200,11 +193,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 3, length: 3, token: 9 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const root = store.root as any; + const root = store.root as ListNode; // Verify the structure remains balanced assert.strictEqual(root.children.length, 3); - assert.strictEqual(root.children[0].children.length, 2); + assert.strictEqual((root.children[0] as ListNode).children.length, 2); // Verify the lengths are updated correctly assert.strictEqual(root.children[0].length, 2); // First 2 tokens @@ -231,11 +223,10 @@ suite('TokenStore', () => { { startOffsetInclusive: 16, length: 4, token: 10 } ], TokenQuality.Accurate); - // eslint-disable-next-line local/code-no-any-casts - const root = store.root as any; + const root = store.root as ListNode; // Verify the structure remains balanced assert.strictEqual(root.children.length, 2); - assert.strictEqual(root.children[0].children.length, 2); + assert.strictEqual((root.children[0] as ListNode).children.length, 2); // Verify the lengths are updated correctly assert.strictEqual(root.children[0].length, 12); diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index ba8801fcdef..1b6172fa9a7 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -375,8 +375,7 @@ class TreeViewDataProvider implements ITreeViewDataProvider { const properties = distinct([...Object.keys(current instanceof ResolvableTreeItem ? current.asTreeItem() : current), ...Object.keys(treeItem)]); for (const property of properties) { - // eslint-disable-next-line local/code-no-any-casts - (current)[property] = (treeItem)[property]; + (current as { [key: string]: any })[property] = (treeItem as { [key: string]: any })[property]; } if (current instanceof ResolvableTreeItem) { current.resetResolve(); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 45585a81c67..826b79501be 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -669,7 +669,6 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo proxy.$registerCommentController(this.handle, _id, _label, this._extension.identifier.value); const that = this; - // eslint-disable-next-line local/code-no-any-casts this.value = Object.freeze({ id: that.id, label: that.label, @@ -680,12 +679,12 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; }, set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; }, // get activeComment(): vscode.Comment | undefined { return that.activeComment; }, - get activeCommentThread(): vscode.CommentThread2 | undefined { return that.activeCommentThread; }, - createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 { - return that.createCommentThread(uri, range, comments).value; + get activeCommentThread(): vscode.CommentThread | undefined { return that.activeCommentThread as vscode.CommentThread | undefined; }, + createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread { + return that.createCommentThread(uri, range, comments).value as vscode.CommentThread; }, dispose: () => { that.dispose(); }, - }) as any; // TODO @alexr00 remove this cast when the proposed API is stable + }); this._localDisposables = []; this._localDisposables.push({ diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index b5e9cdcfff7..de79ab6d959 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -130,8 +130,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe providedAttributes = await provider.provider.providePortAttributes({ port, pid, commandLine }, cancellationToken); } catch (e) { // Call with old signature for breaking API change - // eslint-disable-next-line local/code-no-any-casts - providedAttributes = await (provider.provider.providePortAttributes as any as (port: number, pid: number | undefined, commandLine: string | undefined, token: vscode.CancellationToken) => vscode.ProviderResult)(port, pid, commandLine, cancellationToken); + providedAttributes = await (provider.provider.providePortAttributes as unknown as (port: number, pid: number | undefined, commandLine: string | undefined, token: vscode.CancellationToken) => vscode.ProviderResult)(port, pid, commandLine, cancellationToken); } return { providedAttributes, port }; })))); diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index cbf59d9402e..88d9928ac79 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -768,8 +768,7 @@ suite('ExtHostTreeView', function () { function getTreeItem(key: string, highlights?: [number, number][]): TreeItem { const treeElement = getTreeElement(key); return { - // eslint-disable-next-line local/code-no-any-casts - label: { label: labels[key] || key, highlights }, + label: { label: labels[key] || key, highlights }, collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None }; } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index dddab1726e5..21b84b481ea 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -186,15 +186,13 @@ export class CommentThreadWidget extends let hasMouse = false; let hasFocus = false; this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_ENTER, (e) => { - // eslint-disable-next-line local/code-no-any-casts - if ((e).toElement === this.container) { + if (e.relatedTarget === this.container) { hasMouse = true; this.updateCurrentThread(hasMouse, hasFocus); } }, true)); this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_LEAVE, (e) => { - // eslint-disable-next-line local/code-no-any-casts - if ((e).fromElement === this.container) { + if (e.relatedTarget === this.container) { hasMouse = false; this.updateCurrentThread(hasMouse, hasFocus); } diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index c0084c6a1b8..1fae7a60cf8 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -11,7 +11,7 @@ import { CommentService, ICommentController, ICommentInfo, ICommentService, INot import { Comment, CommentInput, CommentReaction, CommentThread, CommentThreadCollapsibleState, CommentThreadState } from '../../../../../editor/common/languages.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; -import { IViewContainerModel, IViewDescriptor, IViewDescriptorService, ViewContainer, ViewContainerLocation } from '../../../../common/views.js'; +import { IViewContainerModel, IViewDescriptor, IViewDescriptorService, IViewPaneContainer, ViewContainer, ViewContainerLocation } from '../../../../common/views.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IContextViewService } from '../../../../../platform/contextview/browser/contextView.js'; @@ -21,6 +21,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { URI, UriComponents } from '../../../../../base/common/uri.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { NullHoverService } from '../../../../../platform/hover/test/browser/nullHoverService.js'; +import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; class TestCommentThread implements CommentThread { isDocumentCommentThread(): this is CommentThread { @@ -90,8 +91,7 @@ export class TestViewDescriptorService implements Partial }; } getViewContainerModel(viewContainer: ViewContainer): IViewContainerModel { diff --git a/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts index 2749e126697..dddde3072ee 100644 --- a/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts +++ b/src/vs/workbench/contrib/update/test/browser/releaseNotesRenderer.test.ts @@ -65,8 +65,7 @@ Navigation End --> instantiationService.stub(IPreferencesService, >{ _serviceBrand: undefined, onDidDefaultSettingsContentChanged: new Emitter().event, - // eslint-disable-next-line local/code-no-any-casts - userSettingsResource: undefined as any, + userSettingsResource: URI.parse('test://test'), workspaceSettingsResource: null, getFolderSettingsResource: () => null, createPreferencesEditorModel: async () => null, @@ -88,8 +87,7 @@ Navigation End --> getSetting: (id: string) => { if (id === testSettingId) { // Provide the minimal fields accessed by SimpleSettingRenderer - // eslint-disable-next-line local/code-no-any-casts - return { + return { key: testSettingId, value: 'off', type: 'string' diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index dae9636d9ea..0daf72b7086 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -90,8 +90,7 @@ export abstract class AbstractVariableResolverService implements IConfigurationR } } - // eslint-disable-next-line local/code-no-any-casts - return expr.toObject() as any; + return expr.toObject() as (T extends ConfigurationResolverExpression ? R : T); } public resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: any): Promise { diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index c2455f2884e..615f4fc9bca 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -513,8 +513,7 @@ suite('Configuration Resolver Service', () => { assert.deepStrictEqual(Object.keys(result), Object.keys(expected)); Object.keys(result).forEach(property => { - // eslint-disable-next-line local/code-no-any-casts - const expectedProperty = (expected)[property]; + const expectedProperty = (expected as Record)[property]; if (isObject(result[property])) { assert.deepStrictEqual({ ...result[property] }, expectedProperty); } else { diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index 6b042d65e72..ea8629af1be 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -252,15 +252,12 @@ export class PortsAttributes extends Disposable { } private hasStartEnd(value: number | PortRange | RegExp | HostAndPort): value is PortRange { - // eslint-disable-next-line local/code-no-any-casts - return ((value).start !== undefined) && ((value).end !== undefined); + return (value as Partial).start !== undefined && (value as Partial).end !== undefined; } private hasHostAndPort(value: number | PortRange | RegExp | HostAndPort): value is HostAndPort { - // eslint-disable-next-line local/code-no-any-casts - return ((value).host !== undefined) && ((value).port !== undefined) - // eslint-disable-next-line local/code-no-any-casts - && isString((value).host) && isNumber((value).port); + return ((value as Partial).host !== undefined) && ((value as Partial).port !== undefined) + && isString((value as Partial).host) && isNumber((value as Partial).port); } private findNextIndex(port: number, host: string, commandLine: string | undefined, attributes: PortAttributes[], fromIndex: number): number { @@ -295,8 +292,7 @@ export class PortsAttributes extends Disposable { if (attributesKey === undefined) { continue; } - // eslint-disable-next-line local/code-no-any-casts - const setting = (settingValue)[attributesKey]; + const setting = (settingValue as Record)[attributesKey]; let key: number | PortRange | RegExp | HostAndPort | undefined = undefined; if (Number(attributesKey)) { key = Number(attributesKey); @@ -332,8 +328,7 @@ export class PortsAttributes extends Disposable { }); } - // eslint-disable-next-line local/code-no-any-casts - const defaults = this.configurationService.getValue(PortsAttributes.DEFAULTS); + const defaults = this.configurationService.getValue(PortsAttributes.DEFAULTS) as Partial | undefined; if (defaults) { this.defaultPortAttributes = { elevateIfNeeded: defaults.elevateIfNeeded, @@ -395,8 +390,7 @@ export class PortsAttributes extends Disposable { newRemoteValue[`${port}`] = {}; } for (const attribute in attributes) { - // eslint-disable-next-line local/code-no-any-casts - newRemoteValue[`${port}`][attribute] = (attributes)[attribute]; + newRemoteValue[`${port}`][attribute] = (attributes as Record)[attribute]; } return this.configurationService.updateValue(PortsAttributes.SETTING, newRemoteValue, target); From d5b9cdb4f5fdd7c1e040e235ff4639e3ddfc0930 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Oct 2025 11:14:15 +0200 Subject: [PATCH 0952/4355] fix https://github.com/microsoft/vscode/issues/260092 (#270211) --- .../contrib/suggest/browser/suggestWidget.ts | 13 ++-------- .../suggest/browser/suggestWidgetDetails.ts | 24 +++++++------------ 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index fac1fb1de21..012df469c9d 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -26,8 +26,7 @@ import { IContextKey, IContextKeyService } from '../../../../platform/contextkey import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { activeContrastBorder, editorForeground, editorWidgetBackground, editorWidgetBorder, listFocusHighlightForeground, listHighlightForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js'; -import { isHighContrast } from '../../../../platform/theme/common/theme.js'; -import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { CompletionModel } from './completionModel.js'; import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; import { CompletionItem, Context as SuggestContext, suggestWidgetStatusbarMenu } from './suggest.js'; @@ -274,9 +273,6 @@ export class SuggestWidget implements IDisposable { const applyStatusBarStyle = () => this.element.domNode.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).showStatusBar); applyStatusBarStyle(); - this._disposables.add(_themeService.onDidColorThemeChange(t => this._onThemeChange(t))); - this._onThemeChange(_themeService.getColorTheme()); - this._disposables.add(this._list.onMouseDown(e => this._onListMouseDownOrTap(e))); this._disposables.add(this._list.onTap(e => this._onListMouseDownOrTap(e))); this._disposables.add(this._list.onDidChangeSelection(e => this._onListSelection(e))); @@ -362,10 +358,6 @@ export class SuggestWidget implements IDisposable { } } - private _onThemeChange(theme: IColorTheme) { - this._details.widget.borderWidth = isHighContrast(theme.type) ? 2 : 1; - } - private _onListFocus(e: IListEvent): void { if (this._ignoreFocusEvents) { return; @@ -921,7 +913,7 @@ export class SuggestWidget implements IDisposable { const fontInfo = this.editor.getOption(EditorOption.fontInfo); const itemHeight = clamp(this.editor.getOption(EditorOption.suggestLineHeight) || fontInfo.lineHeight, 8, 1000); const statusBarHeight = !this.editor.getOption(EditorOption.suggest).showStatusBar || this._state === State.Empty || this._state === State.Loading ? 0 : itemHeight; - const borderWidth = this._details.widget.borderWidth; + const borderWidth = this._details.widget.getLayoutInfo().borderWidth; const borderHeight = 2 * borderWidth; return { @@ -1045,4 +1037,3 @@ export class SuggestContentWidget implements IContentWidget { this._position = position; } } - diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 66101cdbd68..be4d67feb15 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -4,18 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../browser/editorBrowser.js'; -import { EditorOption } from '../../../common/config/editorOptions.js'; -import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; import * as nls from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { isHighContrast } from '../../../../platform/theme/common/theme.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../browser/editorBrowser.js'; +import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { EditorOption } from '../../../common/config/editorOptions.js'; import { CompletionItem } from './suggest.js'; export function canExpandCompletionItem(item: CompletionItem | undefined): boolean { @@ -42,12 +44,12 @@ export class SuggestDetailsWidget { private readonly _markdownRenderer: MarkdownRenderer; private readonly _renderDisposeable = new DisposableStore(); - private _borderWidth: number = 1; private _size = new dom.Dimension(330, 0); constructor( private readonly _editor: ICodeEditor, @IInstantiationService instaService: IInstantiationService, + @IThemeService private readonly _themeService: IThemeService, ) { this.domNode = dom.$('.suggest-details'); this.domNode.classList.add('no-docs'); @@ -106,7 +108,7 @@ export class SuggestDetailsWidget { getLayoutInfo() { const lineHeight = this._editor.getOption(EditorOption.suggestLineHeight) || this._editor.getOption(EditorOption.fontInfo).lineHeight; - const borderWidth = this._borderWidth; + const borderWidth = isHighContrast(this._themeService.getColorTheme().type) ? 2 : 1; const borderHeight = borderWidth * 2; return { lineHeight, @@ -252,14 +254,6 @@ export class SuggestDetailsWidget { this.scrollUp(80); } - set borderWidth(width: number) { - this._borderWidth = width; - } - - get borderWidth() { - return this._borderWidth; - } - focus() { this.domNode.focus(); } From cfacd02308f06dbc002224bb98462debbb9f5f8a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 8 Oct 2025 11:18:16 +0200 Subject: [PATCH 0953/4355] Do not prefix `Folders:` when explorer merges with container --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 255dcf11f83..c06f824c2bc 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -185,6 +185,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { private decorationsProvider: ExplorerDecorationsProvider | undefined; private readonly delegate: IExplorerViewContainerDelegate | undefined; + public override get singleViewPaneContainerTitle(): string { + return this.name; + } + constructor( options: IExplorerViewPaneOptions, @IContextMenuService contextMenuService: IContextMenuService, From 091fc947a92cd41322240b55182d0f26c9ddd63c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:09:01 +0200 Subject: [PATCH 0954/4355] Update src/vs/workbench/contrib/files/browser/views/explorerView.ts Co-authored-by: Benjamin Pasero --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index c06f824c2bc..98bb86cdefe 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -185,7 +185,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { private decorationsProvider: ExplorerDecorationsProvider | undefined; private readonly delegate: IExplorerViewContainerDelegate | undefined; - public override get singleViewPaneContainerTitle(): string { + override get singleViewPaneContainerTitle(): string { return this.name; } From b7393f3bc372e55132a4b9fbafd1871ab7a5094d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Oct 2025 12:10:21 +0200 Subject: [PATCH 0955/4355] Clicking on a change pill should preserve focus in chat (fix #260281) (#270341) * Clicking on a change pill should preserve focus in chat (fix #260281) * remove unused code * :lipstick: --- .../chatMarkdownContentPart.ts | 121 ++++++++++-------- .../browser/chatEditing/chatEditingActions.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 8 +- .../chat/browser/chatInlineAnchorWidget.ts | 9 +- 4 files changed, 73 insertions(+), 67 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 8793c5fc2b6..f1f3c6fbc4a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import '../media/chatCodeBlockPill.css'; +import './media/chatMarkdownPart.css'; import * as dom from '../../../../../base/browser/dom.js'; import { allowedMarkdownHtmlAttributes, MarkdownRendererMarkedOptions, type MarkdownRenderOptions } from '../../../../../base/browser/markdownRenderer.js'; import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; @@ -37,7 +39,7 @@ import { FileKind } from '../../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; -import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; import { IAiEditTelemetryService } from '../../../editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.js'; import { MarkedKatexSupport } from '../../../markdown/browser/markedKatexSupport.js'; import { IMarkdownVulnerability } from '../../common/annotations.js'; @@ -53,11 +55,11 @@ import { ChatMarkdownDecorationsRenderer } from '../chatMarkdownDecorationsRende import { allowedChatMarkdownHtmlTags } from '../chatMarkdownRenderer.js'; import { ChatEditorOptions } from '../chatOptions.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions, localFileLanguageId, parseLocalFileData } from '../codeBlockPart.js'; -import '../media/chatCodeBlockPill.css'; import { IDisposableReference, ResourcePool } from './chatCollections.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; import { ChatExtensionsContentPart } from './chatExtensionsContentPart.js'; -import './media/chatMarkdownPart.css'; +import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; const $ = dom.$; @@ -66,21 +68,21 @@ export interface IChatMarkdownContentPartOptions { } export class ChatMarkdownContentPart extends Disposable implements IChatContentPart { - private static idPool = 0; - public readonly codeblocksPartId = String(++ChatMarkdownContentPart.idPool); - public readonly domNode: HTMLElement; + private static ID_POOL = 0; + + readonly codeblocksPartId = String(++ChatMarkdownContentPart.ID_POOL); + readonly domNode: HTMLElement; + private readonly allRefs: IDisposableReference[] = []; private readonly _onDidChangeHeight = this._register(new Emitter()); - public readonly onDidChangeHeight = this._onDidChangeHeight.event; + readonly onDidChangeHeight = this._onDidChangeHeight.event; - public readonly codeblocks: IChatCodeBlockInfo[] = []; + readonly codeblocks: IChatCodeBlockInfo[] = []; private readonly mathLayoutParticipants = new Set<() => void>(); - private _isDisposed = false; - constructor( private readonly markdown: IChatMarkdownContent, context: IChatContentPartRenderContext, @@ -103,7 +105,9 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP const element = context.element; const inUndoStop = (findLast(context.content, e => e.kind === 'undoStop', context.contentIndex) as IChatUndoStop | undefined)?.id; - // We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering + // We release editors in order so that it's more likely that the same editor will + // be assigned if this element is re-rendered right away, like it often is during + // progressive rendering const orderedDisposablesList: IDisposable[] = []; // Need to track the index of the codeblock within the response so it can have a unique ID, @@ -116,7 +120,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP const enableMath = configurationService.getValue(ChatConfiguration.EnableMath); const doRenderMarkdown = () => { - if (this._isDisposed) { + if (this._store.isDisposed) { return; } @@ -127,7 +131,8 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP })]) : []; - // Enables github-flavored-markdown + line breaks with single newlines (which matches typical expectations but isn't "proper" in markdown) + // Enables github-flavored-markdown + line breaks with single newlines + // (which matches typical expectations but isn't "proper" in markdown) const markedOpts: MarkdownRendererMarkedOptions = { gfm: true, breaks: true, @@ -201,13 +206,13 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP readonly languageId = languageId; readonly editDeltaInfo = EditDeltaInfo.fromText(text); codemapperUri = undefined; // will be set async - public get uri() { + get uri() { // here we must do a getter because the ref.object is rendered // async and the uri might be undefined when it's read immediately return ref.object.uri; } readonly uriPromise = textModel.then(model => model.uri); - public focus() { + focus() { ref.object.focus(); } }(); @@ -234,11 +239,11 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP readonly isStreaming = !isCodeBlockComplete; readonly codemapperUri = codeblockEntry?.codemapperUri; readonly chatSessionId = element.sessionId; - public get uri() { + get uri() { return undefined; } readonly uriPromise = Promise.resolve(undefined); - public focus() { + focus() { return ref.object.element.focus(); } readonly languageId = languageId; @@ -310,7 +315,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP console.error('Failed to load MarkedKatexSupport extension:', e); }).finally(() => { doRenderMarkdown(); - if (!this._isDisposed) { + if (!this._store.isDisposed) { this._onDidChangeHeight.fire(); } }); @@ -319,11 +324,6 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP } } - override dispose(): void { - this._isDisposed = true; - super.dispose(); - } - private renderCodeBlockPill(sessionId: string, requestId: string, inUndoStop: string | undefined, codemapperUri: URI | undefined, isStreaming: boolean): IDisposableReference { const codeBlock = this.instantiationService.createInstance(CollapsedCodeBlock, sessionId, requestId, inUndoStop); if (codemapperUri) { @@ -381,7 +381,7 @@ export class EditorPool extends Disposable { private readonly _pool: ResourcePool; - public inUse(): Iterable { + inUse(): Iterable { return this._pool.inUse; } @@ -420,19 +420,17 @@ export function codeblockHasClosingBackticks(str: string): boolean { export class CollapsedCodeBlock extends Disposable { - public readonly element: HTMLElement; + readonly element: HTMLElement; + + private _uri: URI | undefined; + get uri(): URI | undefined { return this._uri; } private readonly hover = this._register(new MutableDisposable()); private tooltip: string | undefined; - private _uri: URI | undefined; - public get uri(): URI | undefined { - return this._uri; - } - - private _currentDiff: IEditSessionEntryDiff | undefined; + private currentDiff: IEditSessionEntryDiff | undefined; - private readonly _progressStore = this._store.add(new DisposableStore()); + private readonly progressStore = this._store.add(new DisposableStore()); constructor( private readonly sessionId: string, @@ -449,45 +447,66 @@ export class CollapsedCodeBlock extends Disposable { @IChatService private readonly chatService: IChatService, ) { super(); + this.element = $('.chat-codeblock-pill-widget'); this.element.tabIndex = 0; this.element.classList.add('show-file-icons'); this.element.role = 'button'; - this._register(dom.addDisposableListener(this.element, 'click', () => this._showDiff())); - this._register(dom.addDisposableListener(this.element, 'keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - this._showDiff(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(dom.addDisposableListener(this.element, dom.EventType.DBLCLICK, e => { + this.showDiff({ preserveFocus: false, pinned: true, sideBySide: e.ctrlKey || e.metaKey || e.altKey }); + })); + this._register(dom.addDisposableListener(this.element, dom.EventType.CLICK, e => { + this.showDiff({ preserveFocus: true, pinned: e.button === 1 /* middle click */, sideBySide: e.ctrlKey || e.metaKey || e.altKey }); + })); + this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + this.showDiff({ preserveFocus: false, pinned: false, sideBySide: false }); } })); - this._register(dom.addDisposableListener(this.element, dom.EventType.CONTEXT_MENU, domEvent => { - const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); - dom.EventHelper.stop(domEvent, true); + + this._register(dom.addDisposableListener(this.element, dom.EventType.CONTEXT_MENU, e => { + const event = new StandardMouseEvent(dom.getWindow(e), e); + dom.EventHelper.stop(e, true); this.contextMenuService.showContextMenu({ contextKeyService: this.contextKeyService, getAnchor: () => event, getActions: () => { - const menu = this.menuService.getMenuActions(MenuId.ChatEditingCodeBlockContext, this.contextKeyService, { arg: { sessionId, requestId, uri: this.uri, stopId: inUndoStop } }); + const menu = this.menuService.getMenuActions(MenuId.ChatEditingCodeBlockContext, this.contextKeyService, { + arg: { + sessionId: this.sessionId, + requestId: this.requestId, + uri: this.uri, + stopId: this.inUndoStop + } + }); + return getFlatContextMenuActions(menu); }, }); })); } - private _showDiff(): void { - if (this._currentDiff) { + private showDiff({ preserveFocus, pinned, sideBySide }: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean }): void { + if (this.currentDiff) { this.editorService.openEditor({ - original: { resource: this._currentDiff.originalURI }, - modified: { resource: this._currentDiff.modifiedURI }, - options: { transient: true }, - }); + original: { resource: this.currentDiff.originalURI }, + modified: { resource: this.currentDiff.modifiedURI }, + options: { preserveFocus, pinned } + }, sideBySide ? SIDE_GROUP : undefined); } else if (this.uri) { - this.editorService.openEditor({ resource: this.uri }); + this.editorService.openEditor({ resource: this.uri, options: { preserveFocus, pinned } }, sideBySide ? SIDE_GROUP : undefined); } } render(uri: URI, isStreaming?: boolean): void { - this._progressStore.clear(); + this.progressStore.clear(); this._uri = uri; @@ -525,7 +544,7 @@ export class CollapsedCodeBlock extends Disposable { const labelAdded = this.element.querySelector('.label-added') ?? this.element.appendChild(dom.$('span.label-added')); const labelRemoved = this.element.querySelector('.label-removed') ?? this.element.appendChild(dom.$('span.label-removed')); if (changes && !changes?.identical && !changes?.quitEarly) { - this._currentDiff = changes; + this.currentDiff = changes; labelAdded.textContent = `+${changes.added}`; labelRemoved.textContent = `-${changes.removed}`; const insertionsFragment = changes.added === 1 ? localize('chat.codeblock.insertions.one', "1 insertion") : localize('chat.codeblock.insertions', "{0} insertions", changes.added); @@ -539,8 +558,7 @@ export class CollapsedCodeBlock extends Disposable { let diffBetweenStops: IObservable | undefined; // Show a percentage progress that is driven by the rewrite - - this._progressStore.add(autorun(r => { + this.progressStore.add(autorun(r => { if (!editSession) { editSession = session?.editingSessionObs?.promiseResult.read(r)?.data; modifiedEntry = editSession?.getEntry(uri); @@ -576,6 +594,7 @@ export class CollapsedCodeBlock extends Disposable { private updateTooltip(tooltip: string): void { this.tooltip = tooltip; + if (!this.hover.value) { this.hover.value = this.hoverService.setupDelayedHover(this.element, () => ( { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index ad98ed0bc39..63b8ba4e937 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -627,7 +627,7 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { const snapshot = chatEditingService.getEditingSession(chatModel.sessionId)?.getSnapshotUri(context.requestId, context.uri, context.stopId); if (snapshot) { - const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot)', basename(context.uri)), options: { transient: true, activation: EditorActivation.ACTIVATE } }); + const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot)', basename(context.uri)), options: { activation: EditorActivation.ACTIVATE } }); if (isCodeEditor(editor)) { editor.updateOptions({ readOnly: true }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index c8c8dbc5390..df47ec3a0f7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -233,7 +233,6 @@ export class ChatEditorModel extends Disposable { private _onWillDispose = this._register(new Emitter()); readonly onWillDispose = this._onWillDispose.event; - private _isDisposed = false; private _isResolved = false; constructor( @@ -249,12 +248,7 @@ export class ChatEditorModel extends Disposable { } isDisposed(): boolean { - return this._isDisposed; - } - - override dispose(): void { - super.dispose(); - this._isDisposed = true; + return this._store.isDisposed; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index cbe883756d1..6009c6cddd1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -76,8 +76,6 @@ export class InlineAnchorWidget extends Disposable { readonly data: ContentRefData; - private _isDisposed = false; - constructor( private readonly element: HTMLAnchorElement | HTMLElement, public readonly inlineReference: IChatContentInlineReference, @@ -170,7 +168,7 @@ export class InlineAnchorWidget extends Disposable { console.error(e); } - if (this._isDisposed) { + if (this._store.isDisposed) { return; } @@ -216,11 +214,6 @@ export class InlineAnchorWidget extends Disposable { } } - override dispose(): void { - this._isDisposed = true; - super.dispose(); - } - getHTMLElement(): HTMLElement { return this.element; } From c52b823e4ff886b1d7ab34dfd8266debd5b95249 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Oct 2025 12:11:09 +0200 Subject: [PATCH 0956/4355] Fix #270285 by reverting 83ece74489047036a0ae32e0abca2f555b1150e (#270334) --- .../extensionManagement/common/extensionGalleryService.ts | 2 +- .../platform/extensionManagement/common/extensionManagement.ts | 1 + .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 87fe5f5f2c7..06eb30de472 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -594,7 +594,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions; const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken; - const resourceApi = await this.getResourceApi(extensionGalleryManifest); + const resourceApi = options.updateCheck ? await this.getResourceApi(extensionGalleryManifest) : undefined; const result = resourceApi ? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token) : await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index cd5c1945c5a..94271968ab4 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -384,6 +384,7 @@ export interface IExtensionQueryOptions { compatible?: boolean; queryAllVersions?: boolean; source?: string; + updateCheck?: boolean; } export interface IExtensionGalleryCapabilities { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 8574e038520..53e03180167 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1937,7 +1937,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension count: infos.length, }); this.logService.trace(`Checking updates for extensions`, infos.map(e => e.id).join(', ')); - const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None); + const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion(), updateCheck: true }, CancellationToken.None); if (galleryExtensions.length) { await this.syncInstalledExtensionsWithGallery(galleryExtensions, infos); } From dd0eb9cecd6cca12ab368343436c52e7b9c22a45 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 8 Oct 2025 11:45:39 +0100 Subject: [PATCH 0957/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118456 -> 118660 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 976c79594a0d43bdb886a6d164e9a11313b82ca9..c8922d182bb013621212e70b56441805769d6275 100644 GIT binary patch delta 3854 zcma)84Nz3qy+8kR?$_>J_WKK1Kz8LW0xqyDyP$vws0c_@K4OTX5UWU#s4>JC(xpla z!AMXV)*8R+GX`y%D%fBwC8@FLJjQv?mzT+OtaUPdolJ+xIGw(XlQJ3G|GkS%rqh}C zcJ7>e?z!ilJ?D3RALrUBvHy%XTyCg;_3&OovfTs@ojh8XvFgA7_9sH%OG3okUC%t# zwkO%d5#rAwgw5`3+qa7)zyQ8?6G9erx9$Ai$lUw&1g>1gwy$?R^X%SlxLbcFF!9dO zYkAz)w$Pa?Uv_d6o%}Ezc?kBAdt4XZ%kf@4abNnc)aa+KD%YsH%>6_6!$fc5dr9`B zhsoU@gJ-R0Fhxq~N|{crPn}EqbitwY^{=v-l zMeaqnvaDJ4S$$b!Ss!P8o-Ji7*&A~LIjuQEIrkQKFTNG<28MHKZcXm~+#9)H=P7wT zdDrshqWLxX`+`e?mx2!pS_+OA%!aZ;+d{L2S%v!x{}`?c_eO$6q^P9mbkR(4Uh%%- zn@cj6%r0HOba3fcC9NehrQXt$W$v=!vbkmF%W3(Z^1I8cmrpOBtEj0sP%&LGTe+cf zsPbmzTvbQa{S|d9hF3gX`5&uFR*h9JsBWzOY<24D=(!qe&FR`DwYTcZ>Ta(oS#zyE zSU+0-^b)T$nUO&~auwiY(jfRJfosF|iB~70<3(Zx{XPX~9-ud{< zhRh8u8_qppexkjls^#65FP=0%x$VhMHb&h0TG@?VE3G z{$@+nmYyvaw%lpcN83Vey=|Ab%|g;OW5A9ZyenRdo$_-QStDvv22(Zfkc(_uXff zJoE7`^RCui*LT={IW_sFtK6!r0^WD#1+ZW~b9oTp8 zg`yV*UYOZ$-`~7{^hM)~9WT!Ix_j$-hh8dw>B382eZMEFMBnSH@9XQE?tAcq@gK|` zXg+Y~p!Hx^fA7o2m&;$ib*S#php*(la%n&s*gA0eRq55C!REow4p$t0=kWd4mb~`P z5&e;_BOeb1hOWP^yncR|8{RW~?`Xx*xnmom$A*vndBihv?v1iH{xCWd%Zpt)Zau#7 z_=O)yKRSNGcw*0qxi@dVId?Mel>1cYsne(aczWUKL#J<_37k1`Hs|d3&(8jM|BtVq z%R1L}ZtmaqohRp8&riQ)e(S_r4=-%FF!nZmyYcPWvF@?E7eg0&FMfKd`O?f!T7UAz zPrvxtv7i0rvh(tR%bV_9QLfw_caN_fzdZiM#L9_3U9Gv=akc;I*{jo6KmU2j&yT;S zfA6))vdN)qsqahgzk6M|{@@qozql}!H8n6b`^&~(o|yJdZ<`+bp!|cu55D;@^x@54 z_5Nz+M#+uKA4wn8PdKyxJtr!HNX#UOEF^iPl&mIAgxGvuAqDKtr4S64LP#+|VJN&5 z96qlSu?yKySP*nN#DZWrq-29vusdD&LA-lN@rmDgh*IP!8Aw(apZ5kU5TP3 z)-lFb^86?jd0xj`WJwqmrH+v|_g8pEr35aEQBlmKGHU{XtE9BjVBs3MgpDKndmo>; z-TS|ShOLogkq{vWoC$pXfC^>MDMtK?U;W>pOMOlQr`-hDAyP&aF%?wcmw|7$QKltS zPDsy9hk;Q+k}WztFVaz-?-6zMs3?N;d#V>jK?c5Xx5+K(^^)7@PEU80H8QXm5(6xs z6QGIdS*1ZVtm6{Q2`2@ePH;^;-akyW>3PVSe*p3=9R6?7aEM+(%r6XSFg@DjY>0$| zu2d?Bg4d64?5;KuR^38KYj5fc&^FEJf{s*N!5(^u|L z(2WcX`rwI)cLtAEnHXjfAz#5_l1}`jW+e5aWHU0k6#PmqxYQNp!pP2#GP)v?)4a>< zw8_#gNzOHxfpU_U=dwdh98&;)3KIq92pP=Cs$_e}!30Q@B9dXqJ{r)V_bMtS{7&3|M}fvj!9mln>ZfjD+#oT^1_(FFLWprJ4-xsw5Y^uaUj)jZXK2I zG+m_niH9sAOaFy@gzw5H_SY4V=0aJN0*$D$5QImv5I4mZuY{ziN_wwepN2wJy(AZC zaGvxPF2&_i0)CgvZ(uCnK#}ZL#)_CoG+x7_X{`9areQaNH=u$SM2ADAtB`5ZO@mn1 zbD-ObfcaJR{;O%a{DJv-tU}f!n;jY~Dj>|EqR9SVOK`>VSAkvqXmZsmnAK+q8sl;h zN{Lv=Me;k~K+()HE{cenD{9WTVy~=+BPKLTQSoc0lMC4p^ESXj7Fq_eng+-S`<0Mb zSP+gB#z`4;ra%E2SN1$jmO>%w{@1cu#!C$r%#2K+u`>;jOcNkB)d1-NBv8C!|I+}= zO(1fsDej5H=>T^vmfr|oCKf}ixe*pF;<-C-a3V$-<`6Pj3pO>?D$oZ^vb-jqK8aL_ zU2KG8W}r0on?^{{Qz}!Y7BqIh5sEn~a9OeJCdlBdLM4keHi0*ZTQGcZR`YF)#sxLf|su8^jsR=Ac>?1TXq0ZdoxWGMPi?5-C$P zFxhbcHj7aglE{n}A0*3A3Yt5iC!o7K(K^E>Fd|>Pit3`6u1*uEcB`VGT3INJYmB3% z9(00EkY(!;OAtDs886`y1SLtO7i5ELD(TgC6anA4UoJ!nTq3q0$B=Pe zB^oDOK_@5SMoPTRZMRrl7K`1T#Lzng#*7vKoetY1z2&e@qM!$!WiTFelu9~|PxdGC zT%IVn1qo~l_A4$b0)Yy=7XjnEyg)^7<^rh}sbQ#<7G!##!td9q*ssGuN_n?Qwu?6P ztxdGcCbt~6FwVm`j(Ip{3F84%JwxKDvczn)nkg+RqP?2XY*sSqIR@{iK`8dw6*vN+ K$*tqi==nPp1vH@I1q7l*z*s^yXh^CAufcdp6k^d> zM1$C{md3Y>NwWP@6R|+{{FG=S?G!DyTMaPy`YJO0Le`7H%!{&eJQA+vMN z)yF@)+r}LIj{oIojC}}4Z!qmgzlR0*t{S~3{64jFP?NM~FXL_UUhv+Y^~$VkNf&$} z-yf3I@FS%P9D(x!0 zHFtQPZQjy(ryomtZ2x07%2t^YGEfJQ~F8O$A&(epLt(DiS998{Q*Oqn0mR+iDs(w&&pys>U-r9$ccRYS? z`O4*EE7TR^bvboyPe@N3Ug=udz4C6ox4x(Ti-ussnMS5@r18=!%c^y&&OI4?vj53% zSNm7Dt-kt$oF+@tOHIFSy82Z9Q*Se{NcTi0HAw))uzt<}%fJ$Lze|MT6?fBeJV zAAYfpTGzbp(z*xhYu691f6x|b+uC-l?OfY~7YbkKZnw0rZXa*IzoBYF&xUIo3pbwJ zL~lC0>FQ>E^WH7)Eqk{-+?uoXz}AZ|j&!IUKk2x)t^cK*mj<`f+aue%ws(HHqk6~i zPHJb}&ioraHU3lDn37^>$r&wd>W3 zdo%Zr?ETY^=5|+iU*BijH@H7(|Ka`LzqaGGD+huH_8hqK_MiW9 zD0wJ0boUo+zqo(0?&Rrr=y!VG`F42a@Zj*&yG!4_KGHIB>6CtIP&plXLymflD4~RU!#qJd(N$}dd85!O+oG5bI zj~iIV?vaZKTgtI?BauiWxPWQk6lL^}-FE->NpsH_R-M6Ba1tkM~~2+ry!5Ypq5c9sCtYzg5}(45b$#;;8uf?G6+TH z0ng!g^RPp@77m1Q@5Mn30d`63sSMnjijT*wnz1Uw;d&HH84mP>U)4=Co{-yz+jLk$ z9>NMl2WSO1O*3M$MTiQjW3xlWC=ntHn+3VR;;77J*iAGrS%l+>>QfYk;}}JDs)XlA z7EKtEQvfU@;9$#PyQ76cG`wjc9;(u5=#%?)G{*iGcrO-iLq3=Xo<-#a?rtS2575wVnG`0 z%TQPh0R>6GQx<9#<);=?b@3|;Agj~R13G7NYaSrokw4R?9f=kvqN3-*bR!0W=K>9y zu4#I{p=pNZOmjNZ0;w)nYKqTDPBwh3TXp%ZKDQx@DRpk2&pnqVL5tIAsq1i942Dih zqM1{gkv}uVfH^u3hX>M}3l(z$UyT4(Ii#RWQ+)22;uA%u9J4rF$+DBoN}?y;TnNpu z{?Q|tgF-epK}GchgXRNo1772~d_XFhXPyeYp&NmSZs3aG#|YTKol2V$NB)?5(^4T= zPKWe%7Udh`7zUsc#cXwp0}_WsXX$v~5>S_M9$=6M0RKy~+z4tT!Z9*OvQ1Tt$aaQp z;8clW2;pE+77ajW&Ge@|O{QR)JXnbnFcZCt;00PYO=uRSQzaCY#YaQPl90)Z6fp3l zr=cU-c=}REtCs74u42(#L`gJ(nPOi1uOrMxDi>Lh_q0L;x|H6XM+9-UX)Xx00H9zR z9%^`Y{NPeZDo5)zdGHSii%^PMQ7Yhi{|+Pnh7#~b18WJ+Y%`L z16cbECor_z3Gu|!D#23@#B?|AKOmWa#*jNRTaamicoI_ccoG8iBsA{#64{t^FR+xdqA^bNNyqiegpW33{3oY4Ui%+Aen39NBr9cC}nCyj*HJ}ge+zm zD{=8vjo_c?Z-ixeoq36jok3+$v#BB~j3J<+8YXAIjYdJCO++=5BBY!Ssv3_}nMvW% z4Bn$`TjfL z%#w2|7L!@g=ERYR7YHXiJx&Wx(;`g^nDjppr$lk;&WNH<+}aDz@SV8{z#`?OJP4K7 zqIgsquNkK67Jyk{{D{QpvL)7FnV3E$_+>1%o9Q>#yJ)2#YBvn~1QG9}tNzbza>G;% zonWC1GUdQ43l+sH$v^0q$U{-Qw;=BZob|jUCGi(|XN#o)wT;??(|g|V&7A^>vlCTVZro!ybE9y2$#htV&Ry@3JM5P zNeTLOiR1qnjxf9+Tb)X}P-Ag|!%`!pD^6>pEbt8Bm^{L9q@1w8dhN2CPcey`!nJ#G$j8W#xoO2 From 8712b3d21879ecae185179a8df24827ec4686d18 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 8 Oct 2025 15:00:41 +0200 Subject: [PATCH 0958/4355] editors - introduce and adopt `registerOpenEditorListeners` (#270349) * editors - introduce and adopt `registerOpenEditorListeners` * cleanup * . --- src/vs/platform/editor/browser/editor.ts | 74 +++++++++++++++++++ .../chat/browser/chatAttachmentWidgets.ts | 59 +++++++-------- .../chatMarkdownContentPart.ts | 24 ++---- 3 files changed, 107 insertions(+), 50 deletions(-) create mode 100644 src/vs/platform/editor/browser/editor.ts diff --git a/src/vs/platform/editor/browser/editor.ts b/src/vs/platform/editor/browser/editor.ts new file mode 100644 index 00000000000..3e5f4456130 --- /dev/null +++ b/src/vs/platform/editor/browser/editor.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, EventHelper, EventType, getWindow } from '../../../base/browser/dom.js'; +import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; +import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; +import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js'; +import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { isMacintosh } from '../../../base/common/platform.js'; +import { IEditorOptions } from '../common/editor.js'; + +//#region Editor Open Event Listeners + +export interface IOpenEditorOptions { + readonly editorOptions: IEditorOptions; + readonly openToSide: boolean; +} + +export function registerOpenEditorListeners(element: HTMLElement, onOpenEditor: (options: IOpenEditorOptions) => void): IDisposable { + const disposables = new DisposableStore(); + + disposables.add(addDisposableListener(element, EventType.CLICK, e => { + if (e.detail === 2) { + return; // ignore double click as it is handled below + } + + EventHelper.stop(e, true); + onOpenEditor(toOpenEditorOptions(new StandardMouseEvent(getWindow(element), e))); + })); + + disposables.add(addDisposableListener(element, EventType.DBLCLICK, e => { + EventHelper.stop(e, true); + + onOpenEditor(toOpenEditorOptions(new StandardMouseEvent(getWindow(element), e), true)); + })); + + disposables.add(addDisposableListener(element, EventType.KEY_DOWN, e => { + const options = toOpenEditorOptions(new StandardKeyboardEvent(e)); + if (!options) { + return; + } + + EventHelper.stop(e, true); + onOpenEditor(options); + })); + + return disposables; +} + +export function toOpenEditorOptions(event: StandardMouseEvent, isDoubleClick?: boolean): IOpenEditorOptions; +export function toOpenEditorOptions(event: StandardKeyboardEvent): IOpenEditorOptions | undefined; +export function toOpenEditorOptions(event: StandardMouseEvent | StandardKeyboardEvent): IOpenEditorOptions | undefined; +export function toOpenEditorOptions(event: StandardMouseEvent | StandardKeyboardEvent, isDoubleClick?: boolean): IOpenEditorOptions | undefined { + if (event instanceof StandardKeyboardEvent) { + let preserveFocus: boolean | undefined = undefined; + if (event.equals(KeyCode.Enter) || (isMacintosh && event.equals(KeyMod.CtrlCmd | KeyCode.DownArrow))) { + preserveFocus = false; + } else if (event.equals(KeyCode.Space)) { + preserveFocus = true; + } + + if (typeof preserveFocus === 'undefined') { + return; + } + + return { editorOptions: { preserveFocus, pinned: !preserveFocus }, openToSide: false }; + } else { + return { editorOptions: { preserveFocus: !isDoubleClick, pinned: isDoubleClick || event.middleButton }, openToSide: event.ctrlKey || event.metaKey || event.altKey }; + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 86317f824ce..313b5e9bf19 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -33,6 +33,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { fillInSymbolsDragData } from '../../../../platform/dnd/browser/dnd.js'; +import { IOpenEditorOptions, registerOpenEditorListeners } from '../../../../platform/editor/browser/editor.js'; import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js'; import { FileKind, IFileService } from '../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; @@ -43,7 +44,7 @@ import { FolderThemeIcon, IThemeService } from '../../../../platform/theme/commo import { fillEditorsDragData } from '../../../browser/dnd.js'; import { IFileLabelOptions, IResourceLabel, ResourceLabels } from '../../../browser/labels.js'; import { ResourceContextKey } from '../../../common/contextkeys.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; import { CellUri } from '../../notebook/common/notebookCommon.js'; @@ -128,31 +129,19 @@ abstract class AbstractChatAttachmentWidget extends Disposable { protected addResourceOpenHandlers(resource: URI, range: IRange | undefined): void { this.element.style.cursor = 'pointer'; - this._register(dom.addDisposableListener(this.element, dom.EventType.CLICK, async (e: MouseEvent) => { - dom.EventHelper.stop(e, true); + + this._register(registerOpenEditorListeners(this.element, async options => { if (this.attachment.kind === 'directory') { - await this.openResource(resource, true); + await this.openResource(resource, options, true); } else { - await this.openResource(resource, false, range); - } - })); - - this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, async (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { - dom.EventHelper.stop(e, true); - if (this.attachment.kind === 'directory') { - await this.openResource(resource, true); - } else { - await this.openResource(resource, false, range); - } + await this.openResource(resource, options, false, range); } })); } - protected async openResource(resource: URI, isDirectory: true): Promise; - protected async openResource(resource: URI, isDirectory: false, range: IRange | undefined): Promise; - protected async openResource(resource: URI, isDirectory?: boolean, range?: IRange): Promise { + protected async openResource(resource: URI, options: Partial, isDirectory: true): Promise; + protected async openResource(resource: URI, options: Partial, isDirectory: false, range: IRange | undefined): Promise; + protected async openResource(resource: URI, openOptions: Partial, isDirectory?: boolean, range?: IRange): Promise { if (isDirectory) { // Reveal Directory in explorer this.commandService.executeCommand(revealInSideBarCommand.id, resource); @@ -163,7 +152,11 @@ abstract class AbstractChatAttachmentWidget extends Disposable { const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; const options: OpenInternalOptions = { fromUserGesture: true, - editorOptions: { ...openTextEditorOptions, preserveFocus: true }, + openToSide: openOptions.openToSide, + editorOptions: { + ...openTextEditorOptions, + ...openOptions.editorOptions + }, }; await this.openerService.open(resource, options); this._onDidOpen.fire(); @@ -274,7 +267,7 @@ export class ImageAttachmentWidget extends AbstractChatAttachmentWidget { resource = ref && URI.isUri(ref) ? ref : undefined; const clickHandler = async () => { if (resource) { - await this.openResource(resource, false, undefined); + await this.openResource(resource, { editorOptions: { preserveFocus: true } }, false, undefined); } }; @@ -714,7 +707,7 @@ export class NotebookCellOutputChatAttachmentWidget extends AbstractChatAttachme ariaLabel = this.getAriaLabel(attachment); } - const clickHandler = async () => await this.openResource(resource, false, undefined); + const clickHandler = async () => await this.openResource(resource, { editorOptions: { preserveFocus: true } }, false, undefined); const currentLanguageModelName = this.currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this.currentLanguageModel.identifier)?.name ?? this.currentLanguageModel.identifier : undefined; const buffer = this.getOutputItem(resource, attachment)?.data.buffer ?? new Uint8Array(); this._register(createImageElements(resource, attachment.name, attachment.name, this.element, buffer, this.hoverService, ariaLabel, currentLanguageModelName, clickHandler, this.currentLanguageModel, attachment.omittedState)); @@ -848,16 +841,17 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment this.attachClearButton(); } - protected override async openResource(resource: URI, isDirectory: true): Promise; - protected override async openResource(resource: URI, isDirectory: false, range: IRange | undefined): Promise; - protected override async openResource(resource: URI, isDirectory?: boolean, range?: IRange): Promise { + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory: true): Promise; + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory: false, range: IRange | undefined): Promise; + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory?: boolean, range?: IRange): Promise { const attachment = this.attachment as ISCMHistoryItemChangeVariableEntry; const historyItem = attachment.historyItem; await this.editorService.openEditor({ resource, label: `${basename(resource.path)} (${historyItem.displayId ?? historyItem.id})`, - }); + options: { ...options.editorOptions } + }, options.openToSide ? SIDE_GROUP : undefined); } } @@ -887,9 +881,9 @@ export class SCMHistoryItemChangeRangeAttachmentWidget extends AbstractChatAttac this.attachClearButton(); } - protected override async openResource(resource: URI, isDirectory: true): Promise; - protected override async openResource(resource: URI, isDirectory: false, range: IRange | undefined): Promise; - protected override async openResource(resource: URI, isDirectory?: boolean, range?: IRange): Promise { + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory: true): Promise; + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory: false, range: IRange | undefined): Promise; + protected override async openResource(resource: URI, options: IOpenEditorOptions, isDirectory?: boolean, range?: IRange): Promise { const attachment = this.attachment as ISCMHistoryItemChangeRangeVariableEntry; const historyItemChangeStart = attachment.historyItemChangeStart; const historyItemChangeEnd = attachment.historyItemChangeEnd; @@ -900,8 +894,9 @@ export class SCMHistoryItemChangeRangeAttachmentWidget extends AbstractChatAttac await this.editorService.openEditor({ original: { resource: historyItemChangeStart.uri }, modified: { resource: historyItemChangeEnd.uri }, - label: `${originalUriTitle} ↔ ${modifiedUriTitle}` - }); + label: `${originalUriTitle} ↔ ${modifiedUriTitle}`, + options: { ...options.editorOptions } + }, options.openToSide ? SIDE_GROUP : undefined); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index f1f3c6fbc4a..e46a03c6a52 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -58,8 +58,7 @@ import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions, localFileLangua import { IDisposableReference, ResourcePool } from './chatCollections.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; import { ChatExtensionsContentPart } from './chatExtensionsContentPart.js'; -import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; -import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { IOpenEditorOptions, registerOpenEditorListeners } from '../../../../../platform/editor/browser/editor.js'; const $ = dom.$; @@ -457,18 +456,7 @@ export class CollapsedCodeBlock extends Disposable { } private registerListeners(): void { - this._register(dom.addDisposableListener(this.element, dom.EventType.DBLCLICK, e => { - this.showDiff({ preserveFocus: false, pinned: true, sideBySide: e.ctrlKey || e.metaKey || e.altKey }); - })); - this._register(dom.addDisposableListener(this.element, dom.EventType.CLICK, e => { - this.showDiff({ preserveFocus: true, pinned: e.button === 1 /* middle click */, sideBySide: e.ctrlKey || e.metaKey || e.altKey }); - })); - this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, e => { - const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { - this.showDiff({ preserveFocus: false, pinned: false, sideBySide: false }); - } - })); + this._register(registerOpenEditorListeners(this.element, e => this.showDiff(e))); this._register(dom.addDisposableListener(this.element, dom.EventType.CONTEXT_MENU, e => { const event = new StandardMouseEvent(dom.getWindow(e), e); @@ -493,15 +481,15 @@ export class CollapsedCodeBlock extends Disposable { })); } - private showDiff({ preserveFocus, pinned, sideBySide }: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean }): void { + private showDiff({ editorOptions: options, openToSide }: IOpenEditorOptions): void { if (this.currentDiff) { this.editorService.openEditor({ original: { resource: this.currentDiff.originalURI }, modified: { resource: this.currentDiff.modifiedURI }, - options: { preserveFocus, pinned } - }, sideBySide ? SIDE_GROUP : undefined); + options + }, openToSide ? SIDE_GROUP : undefined); } else if (this.uri) { - this.editorService.openEditor({ resource: this.uri, options: { preserveFocus, pinned } }, sideBySide ? SIDE_GROUP : undefined); + this.editorService.openEditor({ resource: this.uri, options }, openToSide ? SIDE_GROUP : undefined); } } From 406bfd087713492ca6638191fe8a0e34aabb0b80 Mon Sep 17 00:00:00 2001 From: Daniel Bayley Date: Tue, 7 Oct 2025 20:42:19 +0100 Subject: [PATCH 0959/4355] Add `TM_DIRECTORY_BASE` snippets variable --- src/vs/editor/contrib/snippet/browser/snippetVariables.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/editor/contrib/snippet/browser/snippetVariables.ts b/src/vs/editor/contrib/snippet/browser/snippetVariables.ts index 615e1174fca..e9b9c49a09f 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetVariables.ts @@ -41,6 +41,7 @@ export const KnownSnippetVariableNames = Object.freeze<{ [key: string]: true }>( 'TM_FILENAME': true, 'TM_FILENAME_BASE': true, 'TM_DIRECTORY': true, + 'TM_DIRECTORY_BASE': true, 'TM_FILEPATH': true, 'CURSOR_INDEX': true, // 0-offset 'CURSOR_NUMBER': true, // 1-offset @@ -185,6 +186,12 @@ export class ModelBasedVariableResolver implements VariableResolver { } return this._labelService.getUriLabel(dirname(this._model.uri)); + } else if (name === 'TM_DIRECTORY_BASE') { + if (path.dirname(this._model.uri.fsPath) === '.') { + return ''; + } + return path.basename(path.dirname(this._model.uri.fsPath)); + } else if (name === 'TM_FILEPATH') { return this._labelService.getUriLabel(this._model.uri); } else if (name === 'RELATIVE_FILEPATH') { From a251c62c040ead170bc757d2c4cbdc6b9033a368 Mon Sep 17 00:00:00 2001 From: Daniel Bayley Date: Wed, 8 Oct 2025 14:10:10 +0100 Subject: [PATCH 0960/4355] Add tests for `TM_DIRECTORY_BASE` snippets variable --- .../contrib/snippet/test/browser/snippetVariables.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index 276ed3376f6..93ec32ae65e 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts @@ -76,6 +76,7 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'TM_FILENAME', 'text.txt'); if (!isWindows) { assertVariableResolve(resolver, 'TM_DIRECTORY', '/foo/files'); + assertVariableResolve(resolver, 'TM_DIRECTORY_BASE', 'files'); assertVariableResolve(resolver, 'TM_FILEPATH', '/foo/files/text.txt'); } @@ -86,6 +87,7 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'TM_FILENAME', 'ghi'); if (!isWindows) { assertVariableResolve(resolver, 'TM_DIRECTORY', '/abc/def'); + assertVariableResolve(resolver, 'TM_DIRECTORY_BASE', 'def'); assertVariableResolve(resolver, 'TM_FILEPATH', '/abc/def/ghi'); } @@ -94,6 +96,7 @@ suite('Snippet Variables Resolver', function () { disposables.add(createTextModel('', undefined, undefined, URI.parse('mem:fff.ts'))) ); assertVariableResolve(resolver, 'TM_DIRECTORY', ''); + assertVariableResolve(resolver, 'TM_DIRECTORY_BASE', ''); assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts'); disposables.dispose(); From f9db81765e82be6e475fba7f9b2baed099b0382e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Oct 2025 15:27:55 +0200 Subject: [PATCH 0961/4355] Extension-contributed prompts/instructions/modes should not edit files in the extensions folder (#270359) --- .../service/promptsServiceImpl.ts | 32 ++++++++++++++----- .../service/promptsService.test.ts | 23 +++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index b393d1cec1c..45ae2157870 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -31,6 +31,7 @@ import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; import { dirname, isEqual } from '../../../../../../base/common/resources.js'; import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; import { Delayer } from '../../../../../../base/common/async.js'; +import { IFilesConfigurationService } from '../../../../../services/filesConfiguration/common/filesConfigurationService.js'; /** * Provides prompt services. @@ -55,9 +56,9 @@ export class PromptsService extends Disposable implements IPromptsService { * Contributed files from extensions keyed by prompt type then name. */ private readonly contributedFiles = { - [PromptsType.prompt]: new ResourceMap(), - [PromptsType.instructions]: new ResourceMap(), - [PromptsType.mode]: new ResourceMap(), + [PromptsType.prompt]: new ResourceMap>(), + [PromptsType.instructions]: new ResourceMap>(), + [PromptsType.mode]: new ResourceMap>(), }; /** @@ -73,7 +74,8 @@ export class PromptsService extends Disposable implements IPromptsService { @IUserDataProfileService private readonly userDataService: IUserDataProfileService, @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService ) { super(); @@ -125,10 +127,11 @@ export class PromptsService extends Disposable implements IPromptsService { const prompts = await Promise.all([ this.fileLocator.listFiles(type, PromptsStorage.user, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.user, type } satisfies IUserPromptPath))), - this.fileLocator.listFiles(type, PromptsStorage.local, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.local, type } satisfies ILocalPromptPath))) + this.fileLocator.listFiles(type, PromptsStorage.local, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.local, type } satisfies ILocalPromptPath))), + this.getExtensionContributions(type) ]); - return [...prompts.flat(), ...this.contributedFiles[type].values()]; + return [...prompts.flat()]; } public async listPromptFilesForStorage(type: PromptsType, storage: PromptsStorage, token: CancellationToken): Promise { @@ -138,7 +141,7 @@ export class PromptsService extends Disposable implements IPromptsService { switch (storage) { case PromptsStorage.extension: - return Promise.resolve(Array.from(this.contributedFiles[type].values())); + return this.getExtensionContributions(type); case PromptsStorage.local: return this.fileLocator.listFiles(type, PromptsStorage.local, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.local, type } satisfies ILocalPromptPath))); case PromptsStorage.user: @@ -148,6 +151,10 @@ export class PromptsService extends Disposable implements IPromptsService { } } + private async getExtensionContributions(type: PromptsType): Promise { + return Promise.all(this.contributedFiles[type].values()); + } + public getSourceFolders(type: PromptsType): readonly IPromptPath[] { if (!PromptsConfig.enabled(this.configurationService)) { return []; @@ -301,7 +308,16 @@ export class PromptsService extends Disposable implements IPromptsService { // keep first registration per extension (handler filters duplicates per extension already) return Disposable.None; } - bucket.set(uri, { uri, name, description, storage: PromptsStorage.extension, type, extension } satisfies IExtensionPromptPath); + const entryPromise = (async () => { + try { + await this.filesConfigService.updateReadonly(uri, true); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + this.logger.error(`[registerContributedFile] Failed to make prompt file readonly: ${uri}`, msg); + } + return { uri, name, description, storage: PromptsStorage.extension, type, extension } satisfies IExtensionPromptPath; + })(); + bucket.set(uri, entryPromise); const updateModesIfRequired = () => { if (type === PromptsType.mode) { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 92451e535d0..ac66856fcbc 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -37,6 +37,8 @@ import { IUserDataProfileService } from '../../../../../../services/userDataProf import { ITelemetryService } from '../../../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../../../platform/telemetry/common/telemetryUtils.js'; import { Event } from '../../../../../../../base/common/event.js'; +import { IFilesConfigurationService } from '../../../../../../services/filesConfiguration/common/filesConfigurationService.js'; +import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; suite('PromptsService', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -86,6 +88,8 @@ suite('PromptsService', () => { const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); disposables.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); + instaService.stub(IFilesConfigurationService, { updateReadonly: () => Promise.resolve() }); + service = disposables.add(instaService.createInstance(PromptsService)); instaService.stub(IPromptsService, service); }); @@ -812,5 +816,24 @@ suite('PromptsService', () => { 'Must get custom chat modes.', ); }); + + test('Contributed prompt file', async () => { + const uri = URI.parse('file://extensions/my-extension/textMate.instructions.md'); + const extension = {} as IExtensionDescription; + const registered = service.registerContributedFile(PromptsType.instructions, + "TextMate Instructions", + "Instructions to follow when authoring TextMate grammars", + uri, + extension + ); + + const actual = await service.listPromptFiles(PromptsType.instructions, CancellationToken.None); + assert.strictEqual(actual.length, 1); + assert.strictEqual(actual[0].uri.toString(), uri.toString()); + assert.strictEqual(actual[0].name, "TextMate Instructions"); + assert.strictEqual(actual[0].storage, PromptsStorage.extension); + assert.strictEqual(actual[0].type, PromptsType.instructions); + registered.dispose(); + }); }); }); From f1f79541b2307e02b5a260ea8cc61584891c2b4c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 8 Oct 2025 09:55:52 -0400 Subject: [PATCH 0962/4355] fix command ids (#270364) fix #269745 --- .../welcomeGettingStarted/common/gettingStartedContent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 2c462d516f3..64b06614e3e 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -470,7 +470,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'dictation', title: localize('gettingStarted.dictation.title', "Use dictation to write code and text in the editor and terminal"), - description: localize('gettingStarted.dictation.description.interpolated', "Dictation allows you to write code and text using your voice. It can be activated with the Voice: Start Dictation in Editor command.\n{0}\n For dictation in the terminal, use the Voice: Start Dictation in Terminal and Voice: Stop Dictation in Terminal commands.\n{1}\n{2}", Button(localize('toggleDictation', "Voice: Start Dictation in Editor"), 'command:workbench.action.editorDictation.start'), Button(localize('terminalStartDictation', "Terminal: Start Dictation in Terminal"), 'command:workbench.action.terminal.startVoiceDictation'), Button(localize('terminalStopDictation', "Terminal: Stop Dictation in Terminal"), 'command:workbench.action.terminal.stopVoiceDictation')), + description: localize('gettingStarted.dictation.description.interpolated', "Dictation allows you to write code and text using your voice. It can be activated with the Voice: Start Dictation in Editor command.\n{0}\n For dictation in the terminal, use the Voice: Start Dictation in Terminal and Voice: Stop Dictation in Terminal commands.\n{1}\n{2}", Button(localize('toggleDictation', "Voice: Start Dictation in Editor"), 'command:workbench.action.editorDictation.start'), Button(localize('terminalStartDictation', "Terminal: Start Dictation in Terminal"), 'command:workbench.action.terminal.startVoice'), Button(localize('terminalStopDictation', "Terminal: Stop Dictation in Terminal"), 'command:workbench.action.terminal.stopVoice')), when: 'hasSpeechProvider', media: { type: 'markdown', path: 'empty' } } From 5250788a9b1f26085e9540f8893eb287043bc55d Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 8 Oct 2025 10:03:50 -0400 Subject: [PATCH 0963/4355] extract `setupRecreatingStartMarker` from terminal execution strategies (#270368) --- .../executeStrategy/basicExecuteStrategy.ts | 37 +++++---------- .../executeStrategy/noneExecuteStrategy.ts | 36 ++++----------- .../executeStrategy/richExecuteStrategy.ts | 35 ++++----------- .../executeStrategy/strategyHelpers.ts | 45 +++++++++++++++++++ 4 files changed, 73 insertions(+), 80 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/strategyHelpers.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts index 32354ccce8a..68b83061852 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts @@ -6,13 +6,14 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { isNumber } from '../../../../../../base/common/types.js'; import type { ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; import { trackIdleOnPrompt, waitForIdle, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js'; import type { IMarker as IXtermMarker } from '@xterm/xterm'; import { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; +import { setupRecreatingStartMarker } from './strategyHelpers.js'; /** * This strategy is used when shell integration is enabled, but rich command detection was not @@ -54,6 +55,7 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { async execute(commandLine: string, token: CancellationToken): Promise { const store = new DisposableStore(); + try { const idlePromptPromise = trackIdleOnPrompt(this._instance, 1000, store); const onDone = Promise.race([ @@ -88,32 +90,13 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { this._log('Waiting for idle'); await waitForIdle(this._instance.onData, 1000); - // Record where the command started. If the marker gets disposed, re-create it where - // the cursor is. This can happen in prompts where they clear the line and rerender it - // like powerlevel10k's transient prompt - const markerListener = new MutableDisposable(); - const recreateStartMarker = () => { - if (store.isDisposed) { - return; - } - const marker = xterm.raw.registerMarker(); - this._startMarker.value = marker ?? undefined; - this._onDidCreateStartMarker.fire(marker); - if (!marker) { - markerListener.clear(); - return; - } - markerListener.value = marker.onDispose(() => { - this._log(`Start marker was disposed, recreating`); - recreateStartMarker(); - }); - }; - recreateStartMarker(); - store.add(toDisposable(() => { - markerListener.dispose(); - this._startMarker.clear(); - this._onDidCreateStartMarker.fire(undefined); - })); + setupRecreatingStartMarker( + xterm, + this._startMarker, + m => this._onDidCreateStartMarker.fire(m), + store, + this._log.bind(this) + ); if (this._hasReceivedUserInput()) { this._log('Command timed out, sending SIGINT and retrying'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts index e310b642056..7592c35f769 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/noneExecuteStrategy.ts @@ -6,11 +6,12 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; import { waitForIdle, waitForIdleWithPromptHeuristics, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js'; import type { IMarker as IXtermMarker } from '@xterm/xterm'; import { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; +import { setupRecreatingStartMarker } from './strategyHelpers.js'; /** * This strategy is used when no shell integration is available. There are very few extension APIs @@ -54,32 +55,13 @@ export class NoneExecuteStrategy implements ITerminalExecuteStrategy { throw new CancellationError(); } - // Record where the command started. If the marker gets disposed, re-create it where - // the cursor is. This can happen in prompts where they clear the line and rerender it - // like powerlevel10k's transient prompt - const markerListener = new MutableDisposable(); - const recreateStartMarker = () => { - if (store.isDisposed) { - return; - } - const marker = xterm.raw.registerMarker(); - this._startMarker.value = marker ?? undefined; - this._onDidCreateStartMarker.fire(marker); - if (!marker) { - markerListener.clear(); - return; - } - markerListener.value = marker.onDispose(() => { - this._log(`Start marker was disposed, recreating`); - recreateStartMarker(); - }); - }; - recreateStartMarker(); - store.add(toDisposable(() => { - markerListener.dispose(); - this._startMarker.clear(); - this._onDidCreateStartMarker.fire(undefined); - })); + setupRecreatingStartMarker( + xterm, + this._startMarker, + m => this._onDidCreateStartMarker.fire(m), + store, + this._log.bind(this) + ); if (this._hasReceivedUserInput()) { this._log('Command timed out, sending SIGINT and retrying'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts index f12d84be7a1..242b829bfce 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts @@ -6,13 +6,14 @@ import type { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; -import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { isNumber } from '../../../../../../base/common/types.js'; import type { ICommandDetectionCapability, ITerminalCommand } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { trackIdleOnPrompt, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js'; import type { IMarker as IXtermMarker } from '@xterm/xterm'; +import { setupRecreatingStartMarker } from './strategyHelpers.js'; /** * This strategy is used when the terminal has rich shell integration/command detection is @@ -58,31 +59,13 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy { }), ]); - // Record where the command started. If the marker gets disposed, re-created it where - // the cursor is. This can happen in prompts where they clear the line and rerender it - // like powerlevel10k's transient prompt - const markerListener = new MutableDisposable(); - const recreateStartMarker = () => { - if (store.isDisposed) { - return; - } - const marker = xterm.raw.registerMarker(); - this._startMarker.value = marker ?? undefined; - this._onDidCreateStartMarker.fire(marker); - if (!marker) { - markerListener.clear(); - return; - } - markerListener.value = marker.onDispose(() => { - recreateStartMarker(); - }); - }; - recreateStartMarker(); - store.add(toDisposable(() => { - markerListener.dispose(); - this._startMarker.clear(); - this._onDidCreateStartMarker.fire(undefined); - })); + setupRecreatingStartMarker( + xterm, + this._startMarker, + m => this._onDidCreateStartMarker.fire(m), + store, + this._log.bind(this) + ); // Execute the command this._log(`Executing command line \`${commandLine}\``); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/strategyHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/strategyHelpers.ts new file mode 100644 index 00000000000..3ab743aa2b6 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/strategyHelpers.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; +import type { IMarker as IXtermMarker } from '@xterm/xterm'; + +/** + * Sets up a recreating start marker which is resilient to prompts that clear/re-render (eg. transient + * or powerlevel10k style prompts). The marker is recreated at the cursor position whenever the + * existing marker is disposed. The caller is responsible for adding the startMarker to the store. + */ +export function setupRecreatingStartMarker( + xterm: { raw: { registerMarker(): IXtermMarker | undefined } }, + startMarker: MutableDisposable, + fire: (marker: IXtermMarker | undefined) => void, + store: DisposableStore, + log?: (message: string) => void, +): void { + const markerListener = new MutableDisposable(); + const recreateStartMarker = () => { + if (store.isDisposed) { + return; + } + const marker = xterm.raw.registerMarker(); + startMarker.value = marker ?? undefined; + fire(marker); + if (!marker) { + markerListener.clear(); + return; + } + markerListener.value = marker.onDispose(() => { + log?.('Start marker was disposed, recreating'); + recreateStartMarker(); + }); + }; + recreateStartMarker(); + store.add(toDisposable(() => { + markerListener.dispose(); + startMarker.clear(); + fire(undefined); + })); + store.add(startMarker); +} From f4c2700d456d7ae517b7fe5e9c8057b31bd11322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 8 Oct 2025 16:05:31 +0200 Subject: [PATCH 0964/4355] cleanup any casts (#270371) related to #269213 cc @mjbvz @jrieken --- .vscode/searches/no-any-casts.code-search | 75 ++++------------------- build/azure-pipelines/common/publish.js | 23 +++---- build/azure-pipelines/common/publish.ts | 25 ++++---- build/azure-pipelines/upload-cdn.js | 4 +- build/azure-pipelines/upload-cdn.ts | 4 +- build/lib/compilation.js | 1 - build/lib/compilation.ts | 5 +- build/lib/extensions.js | 2 - build/lib/extensions.ts | 6 +- build/lib/mangle/index.js | 1 - build/lib/mangle/index.ts | 9 ++- build/lib/monaco-api.js | 2 - build/lib/monaco-api.ts | 22 ++++--- build/lib/nls.js | 6 +- build/lib/nls.ts | 15 ++--- build/lib/optimize.js | 4 +- build/lib/optimize.ts | 14 ++++- build/lib/reporter.js | 9 ++- build/lib/reporter.ts | 20 +++--- build/lib/task.js | 6 +- build/lib/task.ts | 6 +- build/lib/treeshaking.js | 22 ++----- build/lib/treeshaking.ts | 47 +++++++------- build/lib/util.js | 49 +++++++++++++++ build/lib/util.ts | 51 +++++++++++++++ build/lib/watch/watch-win32.js | 1 - build/lib/watch/watch-win32.ts | 3 +- 27 files changed, 235 insertions(+), 197 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index 35e1695868c..b6d6fd03c1c 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,55 +1,6 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -863 results - 326 files - -build/azure-pipelines/upload-cdn.ts: - 115: // eslint-disable-next-line local/code-no-any-casts - -build/azure-pipelines/common/publish.ts: - 524: // eslint-disable-next-line local/code-no-any-casts - -build/lib/compilation.ts: - 153: // eslint-disable-next-line local/code-no-any-casts - -build/lib/extensions.ts: - 120: // eslint-disable-next-line local/code-no-any-casts - 219: // eslint-disable-next-line local/code-no-any-casts - -build/lib/monaco-api.ts: - 221: // eslint-disable-next-line local/code-no-any-casts - 328: // eslint-disable-next-line local/code-no-any-casts - -build/lib/nls.ts: - 43: // eslint-disable-next-line local/code-no-any-casts - 507: // eslint-disable-next-line local/code-no-any-casts - 513: // eslint-disable-next-line local/code-no-any-casts - -build/lib/optimize.ts: - 254: // eslint-disable-next-line local/code-no-any-casts - -build/lib/reporter.ts: - 108: // eslint-disable-next-line local/code-no-any-casts - 113: // eslint-disable-next-line local/code-no-any-casts - 117: // eslint-disable-next-line local/code-no-any-casts - -build/lib/task.ts: - 27: // eslint-disable-next-line local/code-no-any-casts - -build/lib/treeshaking.ts: - 309: // eslint-disable-next-line local/code-no-any-casts - 313: // eslint-disable-next-line local/code-no-any-casts - 317: // eslint-disable-next-line local/code-no-any-casts - 321: // eslint-disable-next-line local/code-no-any-casts - 690: // eslint-disable-next-line local/code-no-any-casts - 919: // eslint-disable-next-line local/code-no-any-casts - 921: // eslint-disable-next-line local/code-no-any-casts - 923: // eslint-disable-next-line local/code-no-any-casts - -build/lib/mangle/index.ts: - 273: // eslint-disable-next-line local/code-no-any-casts - -build/lib/watch/watch-win32.ts: - 50: // eslint-disable-next-line local/code-no-any-casts +838 results - 314 files extensions/css-language-features/client/src/cssClient.ts: 86: // eslint-disable-next-line local/code-no-any-casts @@ -1255,21 +1206,21 @@ src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 178: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: - 31: // eslint-disable-next-line local/code-no-any-casts - 37: // eslint-disable-next-line local/code-no-any-casts - 45: // eslint-disable-next-line local/code-no-any-casts + 28: // eslint-disable-next-line local/code-no-any-casts + 34: // eslint-disable-next-line local/code-no-any-casts + 42: // eslint-disable-next-line local/code-no-any-casts + 50: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts - 56: // eslint-disable-next-line local/code-no-any-casts - 91: // eslint-disable-next-line local/code-no-any-casts 94: // eslint-disable-next-line local/code-no-any-casts - 102: // eslint-disable-next-line local/code-no-any-casts - 104: // eslint-disable-next-line local/code-no-any-casts - 114: // eslint-disable-next-line local/code-no-any-casts + 97: // eslint-disable-next-line local/code-no-any-casts + 105: // eslint-disable-next-line local/code-no-any-casts + 107: // eslint-disable-next-line local/code-no-any-casts 117: // eslint-disable-next-line local/code-no-any-casts - 127: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts 130: // eslint-disable-next-line local/code-no-any-casts - 132: // eslint-disable-next-line local/code-no-any-casts - 141: // eslint-disable-next-line local/code-no-any-casts + 133: // eslint-disable-next-line local/code-no-any-casts + 135: // eslint-disable-next-line local/code-no-any-casts + 144: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: 59: // eslint-disable-next-line local/code-no-any-casts @@ -1326,7 +1277,7 @@ src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDete 142: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: - 790: // eslint-disable-next-line local/code-no-any-casts + 787: // eslint-disable-next-line local/code-no-any-casts src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: 39: // eslint-disable-next-line local/code-no-any-casts diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index f93cbfe1c37..1886bd4c939 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -255,18 +255,19 @@ class ESRPReleaseService { return await res.json(); } async generateJwsToken(message) { + // Create header with properly typed properties, then override x5c with the non-standard string format + const header = { + alg: 'RS256', + crit: ['exp', 'x5t'], + // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) + exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, + // Release service uses hex format, not base64url :roll_eyes: + x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), + }; + // The Release service expects x5c as a '.' separated string, not the standard array format + header['x5c'] = this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'); return jws_1.default.sign({ - header: { - alg: 'RS256', - crit: ['exp', 'x5t'], - // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) - exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, - // Release service uses hex format, not base64url :roll_eyes: - x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), - // Release service uses a '.' separated string, not an array of strings :roll_eyes: - // eslint-disable-next-line local/code-no-any-casts - x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'), - }, + header, payload: message, privateKey: this.requestSigningKey, }); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 78770786d33..7ff87dbee89 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -512,18 +512,21 @@ class ESRPReleaseService { } private async generateJwsToken(message: ReleaseRequestMessage): Promise { + // Create header with properly typed properties, then override x5c with the non-standard string format + const header: jws.Header = { + alg: 'RS256', + crit: ['exp', 'x5t'], + // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) + exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, + // Release service uses hex format, not base64url :roll_eyes: + x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), + }; + + // The Release service expects x5c as a '.' separated string, not the standard array format + (header as Record)['x5c'] = this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'); + return jws.sign({ - header: { - alg: 'RS256', - crit: ['exp', 'x5t'], - // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) - exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, - // Release service uses hex format, not base64url :roll_eyes: - x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), - // Release service uses a '.' separated string, not an array of strings :roll_eyes: - // eslint-disable-next-line local/code-no-any-casts - x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.') as any, - }, + header, payload: message, privateKey: this.requestSigningKey, }); diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index b19e78001d8..603fbdbf95f 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -14,6 +14,7 @@ const gulp_filter_1 = __importDefault(require("gulp-filter")); const gulp_gzip_1 = __importDefault(require("gulp-gzip")); const mime_1 = __importDefault(require("mime")); const identity_1 = require("@azure/identity"); +const util_1 = require("../lib/util"); const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); @@ -105,8 +106,7 @@ async function main() { const listing = new vinyl_1.default({ path: 'files.txt', contents: Buffer.from(files.join('\n')), - // eslint-disable-next-line local/code-no-any-casts - stat: { mode: 0o666 } + stat: new util_1.VinylStat({ mode: 0o666 }) }); const filesOut = event_stream_1.default.readArray([listing]) .pipe((0, gulp_gzip_1.default)({ append: false })) diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 2ea6b7934fc..ead60d4b6cc 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -10,6 +10,7 @@ import filter from 'gulp-filter'; import gzip from 'gulp-gzip'; import mime from 'mime'; import { ClientAssertionCredential } from '@azure/identity'; +import { VinylStat } from '../lib/util'; const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; @@ -112,8 +113,7 @@ async function main(): Promise { const listing = new Vinyl({ path: 'files.txt', contents: Buffer.from(files.join('\n')), - // eslint-disable-next-line local/code-no-any-casts - stat: { mode: 0o666 } as any + stat: new VinylStat({ mode: 0o666 }) }); const filesOut = es.readArray([listing]) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index fe4408136bb..fb326dfd2b1 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -160,7 +160,6 @@ function compileTask(src, out, build, options = {}) { // free resources (await newContentsByFileName).clear(); this.push(null); - // eslint-disable-next-line local/code-no-any-casts ts2tsMangler = undefined; }); } diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 40ff81b406d..d0c54b76b7c 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -134,7 +134,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { // mangle: TypeScript to TypeScript let mangleStream = es.through(); if (build && !options.disableMangle) { - let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); + let ts2tsMangler: Mangler | undefined = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); mangleStream = es.through(async function write(data: File & { sourceMap?: RawSourceMap }) { type TypeScriptExt = typeof ts & { normalizePath(path: string): string }; @@ -150,8 +150,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { (await newContentsByFileName).clear(); this.push(null); - // eslint-disable-next-line local/code-no-any-casts - (ts2tsMangler) = undefined; + ts2tsMangler = undefined; }); } diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 108bc84b487..c80a1be1a84 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -151,7 +151,6 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) { path: filePath, stat: fs_1.default.statSync(filePath), base: extensionPath, - // eslint-disable-next-line local/code-no-any-casts contents: fs_1.default.createReadStream(filePath) })); // check for a webpack configuration files, then invoke webpack @@ -236,7 +235,6 @@ function fromLocalNormal(extensionPath) { path: filePath, stat: fs_1.default.statSync(filePath), base: extensionPath, - // eslint-disable-next-line local/code-no-any-casts contents: fs_1.default.createReadStream(filePath) })); event_stream_1.default.readArray(files).pipe(result); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 31b19a90ee2..4779ddba03a 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -117,8 +117,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, path: filePath, stat: fs.statSync(filePath), base: extensionPath, - // eslint-disable-next-line local/code-no-any-casts - contents: fs.createReadStream(filePath) as any + contents: fs.createReadStream(filePath) })); // check for a webpack configuration files, then invoke webpack @@ -216,8 +215,7 @@ function fromLocalNormal(extensionPath: string): Stream { path: filePath, stat: fs.statSync(filePath), base: extensionPath, - // eslint-disable-next-line local/code-no-any-casts - contents: fs.createReadStream(filePath) as any + contents: fs.createReadStream(filePath) })); es.readArray(files).pipe(result); diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index c8daa84ae51..40a9f7bbe66 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -242,7 +242,6 @@ class ClassData { } } function isNameTakenInFile(node, name) { - // eslint-disable-next-line local/code-no-any-casts const identifiers = node.getSourceFile().identifiers; if (identifiers instanceof Map) { if (identifiers.has(name)) { diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index 38eafa2f300..02050d2e6a2 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -269,9 +269,14 @@ class ClassData { } } +declare module 'typescript' { + interface SourceFile { + identifiers?: Map; + } +} + function isNameTakenInFile(node: ts.Node, name: string): boolean { - // eslint-disable-next-line local/code-no-any-casts - const identifiers = (node.getSourceFile()).identifiers; + const identifiers = node.getSourceFile().identifiers; if (identifiers instanceof Map) { if (identifiers.has(name)) { return true; diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index a19f31262aa..ccec8702540 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -187,7 +187,6 @@ function format(ts, text, endl) { // Parse the source text const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); // Get the formatting edits on the input sources - // eslint-disable-next-line local/code-no-any-casts const edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); // Apply the edits on the input code return applyEdits(text, edits); @@ -285,7 +284,6 @@ function format(ts, text, endl) { function getRuleProvider(options) { // Share this between multiple formatters using the same options. // This represents the bulk of the space the formatter uses. - // eslint-disable-next-line local/code-no-any-casts return ts.formatting.getFormatContext(options); } function applyEdits(text, edits) { diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index fe8b5c7f9e2..86045569f38 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -206,7 +206,14 @@ function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sou return result; } -function format(ts: typeof import('typescript'), text: string, endl: string): string { +interface Formatting { + getFormatContext(options: ts.FormatCodeSettings): TContext; + formatDocument(file: ts.SourceFile, ruleProvider: TContext, options: ts.FormatCodeSettings): ts.TextChange[]; +} + +type Typescript = typeof import('typescript') & { readonly formatting: Formatting }; + +function format(ts: Typescript, text: string, endl: string): string { const REALLY_FORMAT = false; text = preformat(text, endl); @@ -218,8 +225,7 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); // Get the formatting edits on the input sources - // eslint-disable-next-line local/code-no-any-casts - const edits = (ts).formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); + const edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); // Apply the edits on the input code return applyEdits(text, edits); @@ -325,8 +331,8 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st function getRuleProvider(options: ts.FormatCodeSettings) { // Share this between multiple formatters using the same options. // This represents the bulk of the space the formatter uses. - // eslint-disable-next-line local/code-no-any-casts - return (ts as any).formatting.getFormatContext(options); + + return ts.formatting.getFormatContext(options); } function applyEdits(text: string, edits: ts.TextChange[]): string { @@ -382,7 +388,7 @@ interface IEnumEntry { text: string; } -function generateDeclarationFile(ts: typeof import('typescript'), recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { +function generateDeclarationFile(ts: Typescript, recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; const lines = recipe.split(endl); @@ -557,7 +563,7 @@ export interface IMonacoDeclarationResult { isTheSame: boolean; } -function _run(ts: typeof import('typescript'), sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null { +function _run(ts: Typescript, sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null { const recipe = fs.readFileSync(RECIPE_PATH).toString(); const t = generateDeclarationFile(ts, recipe, sourceFileGetter); if (!t) { @@ -666,7 +672,7 @@ export class DeclarationResolver { export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | null { const sourceFileGetter = (moduleId: string) => resolver.getDeclarationSourceFile(moduleId); - return _run(resolver.ts, sourceFileGetter); + return _run(resolver.ts as Typescript, sourceFileGetter); } diff --git a/build/lib/nls.js b/build/lib/nls.js index adb40668fe4..492dbdae8ce 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -32,7 +32,6 @@ function collect(ts, node, fn) { return result; } function clone(object) { - // eslint-disable-next-line local/code-no-any-casts const result = {}; for (const id in object) { result[id] = object[id]; @@ -390,11 +389,8 @@ var _nls; const moduleId = javascriptFile.relative .replace(/\.js$/, '') .replace(/\\/g, '/'); - const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), - // eslint-disable-next-line local/code-no-any-casts - javascriptFile.sourceMap, options); + const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options); const result = fileFrom(javascriptFile, javascript); - // eslint-disable-next-line local/code-no-any-casts result.sourceMap = sourcemap; if (nlsKeys) { _nls.moduleToNLSKeys[moduleId] = nlsKeys; diff --git a/build/lib/nls.ts b/build/lib/nls.ts index ecef100e578..1cfb1cbd580 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -11,7 +11,7 @@ import sm from 'source-map'; import path from 'path'; import sort from 'gulp-sort'; -type FileSourceMap = File & { sourceMap: sm.RawSourceMap }; +type FileWithSourcemap = File & { sourceMap: sm.RawSourceMap }; enum CollectStepResult { Yes, @@ -40,12 +40,11 @@ function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.N } function clone(object: T): T { - // eslint-disable-next-line local/code-no-any-casts - const result = {} as any as T; + const result: Record = {}; for (const id in object) { result[id] = object[id]; } - return result; + return result as T; } /** @@ -56,7 +55,7 @@ export function nls(options: { preserveEnglish: boolean }): NodeJS.ReadWriteStre const input = through(); const output = input .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files - .pipe(through(function (f: FileSourceMap) { + .pipe(through(function (f: FileWithSourcemap) { if (!f.sourceMap) { return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); } @@ -504,14 +503,12 @@ module _nls { ts, typescript, javascriptFile.contents!.toString(), - // eslint-disable-next-line local/code-no-any-casts - (javascriptFile).sourceMap, + javascriptFile.sourceMap, options ); const result = fileFrom(javascriptFile, javascript); - // eslint-disable-next-line local/code-no-any-casts - (result).sourceMap = sourcemap; + result.sourceMap = sourcemap; if (nlsKeys) { moduleToNLSKeys[moduleId] = nlsKeys; diff --git a/build/lib/optimize.js b/build/lib/optimize.js index f52140e0784..fbc455b1cd1 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -213,9 +213,7 @@ function minifyTask(src, sourceMapBaseUrl) { cb(undefined, f); } }, cb); - }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, - // eslint-disable-next-line local/code-no-any-casts - gulp_sourcemaps_1.default.write('./', { + }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, gulp_sourcemaps_1.default.write('./', { sourceMappingURL, sourceRoot: undefined, includeContent: true, diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 1d55ccc47ba..7dde3204703 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -16,6 +16,17 @@ import sourcemaps from 'gulp-sourcemaps'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; +declare module 'gulp-sourcemaps' { + interface WriteOptions { + addComment?: boolean; + includeContent?: boolean; + sourceRoot?: string | WriteMapper; + sourceMappingURL?: ((f: any) => string); + sourceMappingURLPrefix?: string | WriteMapper; + clone?: boolean | CloneOptions; + } +} + const REPO_ROOT_PATH = path.join(__dirname, '../..'); export interface IBundleESMTaskOpts { @@ -251,13 +262,12 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => svgFilter, svgmin(), svgFilter.restore, - // eslint-disable-next-line local/code-no-any-casts sourcemaps.write('./', { sourceMappingURL, sourceRoot: undefined, includeContent: true, addComment: true - } as any), + }), gulp.dest(src + '-min'), (err: any) => cb(err)); }; diff --git a/build/lib/reporter.js b/build/lib/reporter.js index e8570ef6d5a..f07b7626199 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -75,6 +75,9 @@ try { catch (err) { // ignore } +class ReporterError extends Error { + __reporter__ = true; +} function createReporter(id) { const errorLog = getErrorLog(id); const errors = []; @@ -87,15 +90,11 @@ function createReporter(id) { return event_stream_1.default.through(undefined, function () { errorLog.onEnd(); if (emitError && errors.length > 0) { - // eslint-disable-next-line local/code-no-any-casts if (!errors.__logged__) { errorLog.log(); } - // eslint-disable-next-line local/code-no-any-casts errors.__logged__ = true; - const err = new Error(`Found ${errors.length} errors`); - // eslint-disable-next-line local/code-no-any-casts - err.__reporter__ = true; + const err = new ReporterError(`Found ${errors.length} errors`); this.emit('error', err); } else { diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index 37e69c95204..5ea8cb14e74 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -87,10 +87,18 @@ export interface IReporter { end(emitError: boolean): NodeJS.ReadWriteStream; } +class ReporterError extends Error { + __reporter__ = true; +} + +interface Errors extends Array { + __logged__?: boolean; +} + export function createReporter(id?: string): IReporter { const errorLog = getErrorLog(id); - const errors: string[] = []; + const errors: Errors = []; errorLog.allErrors.push(errors); const result = (err: string) => errors.push(err); @@ -105,17 +113,13 @@ export function createReporter(id?: string): IReporter { errorLog.onEnd(); if (emitError && errors.length > 0) { - // eslint-disable-next-line local/code-no-any-casts - if (!(errors as any).__logged__) { + if (!errors.__logged__) { errorLog.log(); } - // eslint-disable-next-line local/code-no-any-casts - (errors as any).__logged__ = true; + errors.__logged__ = true; - const err = new Error(`Found ${errors.length} errors`); - // eslint-disable-next-line local/code-no-any-casts - (err as any).__reporter__ = true; + const err = new ReporterError(`Found ${errors.length} errors`); this.emit('error', err); } else { this.emit('end'); diff --git a/build/lib/task.js b/build/lib/task.js index 9a149ec7f0c..23b8d968584 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -13,11 +13,7 @@ exports.define = define; const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); function _isPromise(p) { - // eslint-disable-next-line local/code-no-any-casts - if (typeof p.then === 'function') { - return true; - } - return false; + return typeof p.then === 'function'; } function _renderTime(time) { return `${Math.round(time)} ms`; diff --git a/build/lib/task.ts b/build/lib/task.ts index 1afc34d4d90..76c2002296b 100644 --- a/build/lib/task.ts +++ b/build/lib/task.ts @@ -24,11 +24,7 @@ export interface CallbackTask extends BaseTask { export type Task = PromiseTask | StreamTask | CallbackTask; function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { - // eslint-disable-next-line local/code-no-any-casts - if (typeof (p).then === 'function') { - return true; - } - return false; + return typeof (p as Promise).then === 'function'; } function _renderTime(time: number): string { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 2792969d9a2..d66b307d13b 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -231,19 +231,15 @@ var NodeColor; NodeColor[NodeColor["Black"] = 2] = "Black"; })(NodeColor || (NodeColor = {})); function getColor(node) { - // eslint-disable-next-line local/code-no-any-casts return node.$$$color || 0 /* NodeColor.White */; } function setColor(node, color) { - // eslint-disable-next-line local/code-no-any-casts node.$$$color = color; } function markNeededSourceFile(node) { - // eslint-disable-next-line local/code-no-any-casts node.$$$neededSourceFile = true; } function isNeededSourceFile(node) { - // eslint-disable-next-line local/code-no-any-casts return Boolean(node.$$$neededSourceFile); } function nodeOrParentIsBlack(node) { @@ -566,12 +562,10 @@ function markNodes(ts, languageService, options) { if (nodeOrParentIsBlack(node)) { continue; } - // eslint-disable-next-line local/code-no-any-casts - const symbol = node.symbol; - if (!symbol) { + if (!node.symbol) { continue; } - const aliased = checker.getAliasedSymbol(symbol); + const aliased = checker.getAliasedSymbol(node.symbol); if (aliased.declarations && aliased.declarations.length > 0) { if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { setColor(node, 2 /* NodeColor.Black */); @@ -774,12 +768,6 @@ class SymbolImportTuple { * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ function getRealNodeSymbol(ts, checker, node) { - // eslint-disable-next-line local/code-no-any-casts - const getPropertySymbolsFromContextualType = ts.getPropertySymbolsFromContextualType; - // eslint-disable-next-line local/code-no-any-casts - const getContainingObjectLiteralElement = ts.getContainingObjectLiteralElement; - // eslint-disable-next-line local/code-no-any-casts - const getNameFromPropertyName = ts.getNameFromPropertyName; // Go to the original declaration for cases: // // (1) when the aliased symbol was declared in the location(parent). @@ -846,7 +834,7 @@ function getRealNodeSymbol(ts, checker, node) { // bar(({pr/*goto*/op1})=>{}); if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); + const name = ts.getNameFromPropertyName(node); const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { @@ -869,11 +857,11 @@ function getRealNodeSymbol(ts, checker, node) { // } // function Foo(arg: Props) {} // Foo( { pr/*1*/op1: 10, prop2: false }) - const element = getContainingObjectLiteralElement(node); + const element = ts.getContainingObjectLiteralElement(node); if (element) { const contextualType = element && checker.getContextualType(element.parent); if (contextualType) { - const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); + const propertySymbols = ts.getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); if (propertySymbols) { symbol = propertySymbols[0]; } diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 1267f7a8ab3..7dab87bf7e8 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -305,21 +305,31 @@ const enum NodeColor { Black = 2 } +type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; + +declare module 'typescript' { + interface Node { + $$$color?: NodeColor; + $$$neededSourceFile?: boolean; + symbol?: ts.Symbol; + } + + function getContainingObjectLiteralElement(node: ts.Node): ObjectLiteralElementWithName | undefined; + function getNameFromPropertyName(name: ts.PropertyName): string | undefined; + function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean): ReadonlyArray; +} + function getColor(node: ts.Node): NodeColor { - // eslint-disable-next-line local/code-no-any-casts - return (node).$$$color || NodeColor.White; + return node.$$$color || NodeColor.White; } function setColor(node: ts.Node, color: NodeColor): void { - // eslint-disable-next-line local/code-no-any-casts - (node).$$$color = color; + node.$$$color = color; } function markNeededSourceFile(node: ts.SourceFile): void { - // eslint-disable-next-line local/code-no-any-casts - (node).$$$neededSourceFile = true; + node.$$$neededSourceFile = true; } function isNeededSourceFile(node: ts.SourceFile): boolean { - // eslint-disable-next-line local/code-no-any-casts - return Boolean((node).$$$neededSourceFile); + return Boolean(node.$$$neededSourceFile); } function nodeOrParentIsBlack(node: ts.Node): boolean { while (node) { @@ -687,12 +697,10 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language if (nodeOrParentIsBlack(node)) { continue; } - // eslint-disable-next-line local/code-no-any-casts - const symbol: ts.Symbol | undefined = (node).symbol; - if (!symbol) { + if (!node.symbol) { continue; } - const aliased = checker.getAliasedSymbol(symbol); + const aliased = checker.getAliasedSymbol(node.symbol); if (aliased.declarations && aliased.declarations.length > 0) { if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { setColor(node, NodeColor.Black); @@ -914,15 +922,6 @@ class SymbolImportTuple { */ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): SymbolImportTuple[] { - // Use some TypeScript internals to avoid code duplication - type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; - // eslint-disable-next-line local/code-no-any-casts - const getPropertySymbolsFromContextualType: (node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean) => ReadonlyArray = (ts).getPropertySymbolsFromContextualType; - // eslint-disable-next-line local/code-no-any-casts - const getContainingObjectLiteralElement: (node: ts.Node) => ObjectLiteralElementWithName | undefined = (ts).getContainingObjectLiteralElement; - // eslint-disable-next-line local/code-no-any-casts - const getNameFromPropertyName: (name: ts.PropertyName) => string | undefined = (ts).getNameFromPropertyName; - // Go to the original declaration for cases: // // (1) when the aliased symbol was declared in the location(parent). @@ -997,7 +996,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec // bar(({pr/*goto*/op1})=>{}); if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); + const name = ts.getNameFromPropertyName(node); const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { @@ -1020,11 +1019,11 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec // } // function Foo(arg: Props) {} // Foo( { pr/*1*/op1: 10, prop2: false }) - const element = getContainingObjectLiteralElement(node); + const element = ts.getContainingObjectLiteralElement(node); if (element) { const contextualType = element && checker.getContextualType(element.parent); if (contextualType) { - const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); + const propertySymbols = ts.getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); if (propertySymbols) { symbol = propertySymbols[0]; } diff --git a/build/lib/util.js b/build/lib/util.js index fc392a12699..d907b3e6322 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.VinylStat = void 0; exports.incremental = incremental; exports.debounce = debounce; exports.fixWin32DirectoryPermissions = fixWin32DirectoryPermissions; @@ -312,4 +313,52 @@ function getElectronVersion() { const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)[1]; return { electronVersion, msBuildId }; } +class VinylStat { + dev; + ino; + mode; + nlink; + uid; + gid; + rdev; + size; + blksize; + blocks; + atimeMs; + mtimeMs; + ctimeMs; + birthtimeMs; + atime; + mtime; + ctime; + birthtime; + constructor(stat) { + this.dev = stat.dev ?? 0; + this.ino = stat.ino ?? 0; + this.mode = stat.mode ?? 0; + this.nlink = stat.nlink ?? 0; + this.uid = stat.uid ?? 0; + this.gid = stat.gid ?? 0; + this.rdev = stat.rdev ?? 0; + this.size = stat.size ?? 0; + this.blksize = stat.blksize ?? 0; + this.blocks = stat.blocks ?? 0; + this.atimeMs = stat.atimeMs ?? 0; + this.mtimeMs = stat.mtimeMs ?? 0; + this.ctimeMs = stat.ctimeMs ?? 0; + this.birthtimeMs = stat.birthtimeMs ?? 0; + this.atime = stat.atime ?? new Date(0); + this.mtime = stat.mtime ?? new Date(0); + this.ctime = stat.ctime ?? new Date(0); + this.birthtime = stat.birthtime ?? new Date(0); + } + isFile() { return true; } + isDirectory() { return false; } + isBlockDevice() { return false; } + isCharacterDevice() { return false; } + isSymbolicLink() { return false; } + isFIFO() { return false; } + isSocket() { return false; } +} +exports.VinylStat = VinylStat; //# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/build/lib/util.ts b/build/lib/util.ts index 1c47418297b..5f3b2f67333 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -379,3 +379,54 @@ export function getElectronVersion(): Record { const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; return { electronVersion, msBuildId }; } + +export class VinylStat implements fs.Stats { + + readonly dev: number; + readonly ino: number; + readonly mode: number; + readonly nlink: number; + readonly uid: number; + readonly gid: number; + readonly rdev: number; + readonly size: number; + readonly blksize: number; + readonly blocks: number; + readonly atimeMs: number; + readonly mtimeMs: number; + readonly ctimeMs: number; + readonly birthtimeMs: number; + readonly atime: Date; + readonly mtime: Date; + readonly ctime: Date; + readonly birthtime: Date; + + constructor(stat: Partial) { + this.dev = stat.dev ?? 0; + this.ino = stat.ino ?? 0; + this.mode = stat.mode ?? 0; + this.nlink = stat.nlink ?? 0; + this.uid = stat.uid ?? 0; + this.gid = stat.gid ?? 0; + this.rdev = stat.rdev ?? 0; + this.size = stat.size ?? 0; + this.blksize = stat.blksize ?? 0; + this.blocks = stat.blocks ?? 0; + this.atimeMs = stat.atimeMs ?? 0; + this.mtimeMs = stat.mtimeMs ?? 0; + this.ctimeMs = stat.ctimeMs ?? 0; + this.birthtimeMs = stat.birthtimeMs ?? 0; + this.atime = stat.atime ?? new Date(0); + this.mtime = stat.mtime ?? new Date(0); + this.ctime = stat.ctime ?? new Date(0); + this.birthtime = stat.birthtime ?? new Date(0); + } + + isFile(): boolean { return true; } + isDirectory(): boolean { return false; } + isBlockDevice(): boolean { return false; } + isCharacterDevice(): boolean { return false; } + isSymbolicLink(): boolean { return false; } + isFIFO(): boolean { return false; } + isSocket(): boolean { return false; } +} diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index 1355055e768..4113d93526e 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -42,7 +42,6 @@ function watch(root) { path: changePathFull, base: root }); - // eslint-disable-next-line local/code-no-any-casts file.event = toChangeType(changeType); result.emit('data', file); } diff --git a/build/lib/watch/watch-win32.ts b/build/lib/watch/watch-win32.ts index e8f505c10de..38cbdea80b2 100644 --- a/build/lib/watch/watch-win32.ts +++ b/build/lib/watch/watch-win32.ts @@ -47,8 +47,7 @@ function watch(root: string): Stream { path: changePathFull, base: root }); - // eslint-disable-next-line local/code-no-any-casts - (file).event = toChangeType(changeType); + file.event = toChangeType(changeType); result.emit('data', file); } }); From d80f9db4db61ac669de32192f5823fe9ba300bf6 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 8 Oct 2025 15:13:29 +0100 Subject: [PATCH 0965/4355] update letterpress icons to align with fluent design --- .../parts/editor/media/letterpress-dark.svg | 34 ++----------------- .../parts/editor/media/letterpress-hcDark.svg | 34 ++----------------- .../editor/media/letterpress-hcLight.svg | 34 ++----------------- .../parts/editor/media/letterpress-light.svg | 32 ++--------------- 4 files changed, 8 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg index 5d6fca4300c..90b799da3b3 100644 --- a/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg +++ b/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg @@ -1,33 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg index 4e32c94cf93..c3978773a56 100644 --- a/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg +++ b/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg @@ -1,33 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg index 4edccd11a28..1e86d85c604 100644 --- a/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg +++ b/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg @@ -1,33 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg index 86f01932099..a45c3e6defc 100644 --- a/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg +++ b/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg @@ -1,31 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + From 3ec367371fdb761eaf951bfeeb9b68b61272d21c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Oct 2025 16:24:18 +0200 Subject: [PATCH 0966/4355] undo (#270377) --- .../extensionManagement/common/extensionGalleryService.ts | 2 +- .../platform/extensionManagement/common/extensionManagement.ts | 1 - .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 06eb30de472..87fe5f5f2c7 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -594,7 +594,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions; const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken; - const resourceApi = options.updateCheck ? await this.getResourceApi(extensionGalleryManifest) : undefined; + const resourceApi = await this.getResourceApi(extensionGalleryManifest); const result = resourceApi ? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token) : await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 94271968ab4..cd5c1945c5a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -384,7 +384,6 @@ export interface IExtensionQueryOptions { compatible?: boolean; queryAllVersions?: boolean; source?: string; - updateCheck?: boolean; } export interface IExtensionGalleryCapabilities { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 53e03180167..8574e038520 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1937,7 +1937,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension count: infos.length, }); this.logService.trace(`Checking updates for extensions`, infos.map(e => e.id).join(', ')); - const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion(), updateCheck: true }, CancellationToken.None); + const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None); if (galleryExtensions.length) { await this.syncInstalledExtensionsWithGallery(galleryExtensions, infos); } From 7dc8f0ce391969b59d07c453a099cf02528dc64d Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 8 Oct 2025 07:53:24 -0700 Subject: [PATCH 0967/4355] StringText: do not have two same PositionOffsetTransformer (#270013) --- src/vs/editor/common/core/text/abstractText.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/core/text/abstractText.ts b/src/vs/editor/common/core/text/abstractText.ts index 71ae73e12ea..eaa67df3acf 100644 --- a/src/vs/editor/common/core/text/abstractText.ts +++ b/src/vs/editor/common/core/text/abstractText.ts @@ -6,11 +6,11 @@ import { assert } from '../../../../base/common/assert.js'; import { splitLines } from '../../../../base/common/strings.js'; import { Position } from '../position.js'; -import { PositionOffsetTransformer } from './positionToOffsetImpl.js'; import { Range } from '../range.js'; import { LineRange } from '../ranges/lineRange.js'; -import { TextLength } from '../text/textLength.js'; import { OffsetRange } from '../ranges/offsetRange.js'; +import { TextLength } from '../text/textLength.js'; +import { PositionOffsetTransformer } from './positionToOffsetImpl.js'; export abstract class AbstractText { abstract getValueOfRange(range: Range): string; @@ -122,4 +122,9 @@ export class StringText extends AbstractText { get length(): TextLength { return this._t.textLength; } + + // Override the getTransformer method to return the cached transformer + override getTransformer() { + return this._t; + } } From 1c647f480891c3bc1c7a69a4065cb83b59257367 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 8 Oct 2025 07:58:38 -0700 Subject: [PATCH 0968/4355] refactor: rename hasNoModifierKeys to hasAnyModifierKeys and update usage --- src/vs/base/browser/keyboardEvent.ts | 6 +++--- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index a446ba43390..e7e2b682735 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -115,10 +115,10 @@ export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string { } /** - * Checks if a keyboard event has no modifier keys pressed + * Checks if a keyboard event has any modifier keys pressed */ -export function hasNoModifierKeys(e: IKeyboardEvent): boolean { - return !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey; +export function hasAnyModifierKeys(e: IKeyboardEvent): boolean { + return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey; } export class StandardKeyboardEvent implements IKeyboardEvent { diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 33be311567a..641c1ae78fc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { addDisposableListener } from '../../../../base/browser/dom.js'; import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; -import { hasNoModifierKeys, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { hasAnyModifierKeys, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/button.js'; @@ -1203,7 +1203,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // We need to prevent Monaco's default Enter behavior while still allowing the VS Code keybinding service // to receive and process the Enter key for ChatSubmitAction this._register(this._inputEditor.onKeyDown((e) => { - if (e.keyCode === KeyCode.Enter && hasNoModifierKeys(e)) { + if (e.keyCode === KeyCode.Enter && !hasAnyModifierKeys(e)) { // Only prevent the default Monaco behavior (newline insertion) // Do NOT call stopPropagation() so the keybinding service can still process this event e.preventDefault(); From fc5f6ff7f2934e98c51872efd1fd7d32a44fef78 Mon Sep 17 00:00:00 2001 From: Daniel Bayley Date: Wed, 8 Oct 2025 16:03:56 +0100 Subject: [PATCH 0969/4355] Document `TM_DIRECTORY_BASE` snippets variable --- src/vs/editor/contrib/snippet/browser/snippet.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/snippet/browser/snippet.md b/src/vs/editor/contrib/snippet/browser/snippet.md index 78019ef9852..a5836e270e9 100644 --- a/src/vs/editor/contrib/snippet/browser/snippet.md +++ b/src/vs/editor/contrib/snippet/browser/snippet.md @@ -27,6 +27,7 @@ With `$name` or `${name:default}` you can insert the value of a variable. When a * `TM_FILENAME` The filename of the current document * `TM_FILENAME_BASE` The filename of the current document without its extensions * `TM_DIRECTORY` The directory of the current document +* `TM_DIRECTORY_BASE` The base directory name of the current document * `TM_FILEPATH` The full file path of the current document * `RELATIVE_FILEPATH` The relative (to the opened workspace or folder) file path of the current document * `CLIPBOARD` The contents of your clipboard From 03c62c3d05d5c5c04a2b925af894c9f1c111dd4d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:51:56 +0200 Subject: [PATCH 0970/4355] Fix separator CSS hack (#270380) * fix separator css hack * . * . --- src/vs/base/browser/ui/toolbar/toolbar.ts | 7 ++++++- src/vs/workbench/browser/parts/compositePart.ts | 3 ++- .../browser/parts/media/paneCompositePart.css | 10 ---------- src/vs/workbench/browser/parts/paneCompositePart.ts | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 64b0c137d70..b493b10af6c 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -7,7 +7,7 @@ import { IContextMenuProvider } from '../../contextmenu.js'; import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js'; import { AnchorAlignment } from '../contextview/contextview.js'; import { DropdownMenuActionViewItem } from '../dropdown/dropdownActionViewItem.js'; -import { Action, IAction, IActionRunner, SubmenuAction } from '../../../common/actions.js'; +import { Action, IAction, IActionRunner, Separator, SubmenuAction } from '../../../common/actions.js'; import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.js'; import { EventMultiplexer } from '../../../common/event.js'; @@ -31,6 +31,7 @@ export interface IToolBarOptions { allowContextMenu?: boolean; skipTelemetry?: boolean; hoverDelegate?: IHoverDelegate; + trailingSeparator?: boolean; /** * If true, toggled primary items are highlighted with a background color. @@ -203,6 +204,10 @@ export class ToolBar extends Disposable { primaryActionsToSet.push(this.toggleMenuAction); } + if (primaryActionsToSet.length > 0 && this.options.trailingSeparator) { + primaryActionsToSet.push(new Separator()); + } + primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) }); }); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 097fae86071..fc1d8133d11 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -413,7 +413,8 @@ export abstract class CompositePart this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions..."), telemetrySource: this.nameForTelemetry, - hoverDelegate: this.toolbarHoverDelegate + hoverDelegate: this.toolbarHoverDelegate, + trailingSeparator: true })); this.collectCompositeActions()(); diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 9618a42d740..de625937628 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -3,16 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .pane-composite-part:not(.empty) > .title.has-composite-bar.has-actions > .global-actions .actions-container::before { - /* Separate global actions from view actions unless pane is empty */ - content: ''; - width: 1px; - height: 16px; - background-color: var(--vscode-disabledForeground); - margin-right: 8px; - margin-left: 4px; -} - .monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .actions-container { justify-content: flex-end; } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index af2a9da5350..44b676f49ac 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -643,7 +643,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart Date: Wed, 8 Oct 2025 10:52:17 -0500 Subject: [PATCH 0971/4355] Disable openChatEditedFiles by default (#270382) Fix #267867 --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index bfc42b2a58c..86a1f2cbcd9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -805,7 +805,7 @@ const configuration: IConfigurationNode = { }, 'accessibility.openChatEditedFiles': { 'type': 'boolean', - 'default': true, + 'default': false, 'markdownDescription': localize('accessibility.openChatEditedFiles', "Controls whether files should be opened when the chat agent has applied edits to them.") }, 'accessibility.verboseChatProgressUpdates': { From b1e37822fb3ade6fbe37a9ca469ccf4eb4f292fe Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Oct 2025 18:10:41 +0200 Subject: [PATCH 0972/4355] fix `hideOnRequest` mode so that it doesn't show the inline chat for "good" responses (#270386) Looks like `ChatResponse#onDidChange` fires with a modified response array after completion... --- .../contrib/inlineChat/browser/inlineChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index eb42a9bde57..e9d5aa5d6a1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1417,6 +1417,8 @@ export class InlineChatController2 implements IEditorContribution { return; } + responseListener.value = undefined; // listen only ONCE + const shouldShow = response.isCanceled // cancelled || response.result?.errorDetails // errors || !response.response.value.find(part => part.kind === 'textEditGroup' From d65085a01eb838c57ed4966ac35bbf5271af775a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 8 Oct 2025 18:22:34 +0200 Subject: [PATCH 0973/4355] Fixes https://github.com/microsoft/vscode/issues/270363 --- src/vs/base/common/cache.ts | 33 +++++++++++++ .../browser/telemetry/arcTelemetrySender.ts | 12 ++--- .../telemetry/editSourceTrackingImpl.ts | 47 +++++++++++-------- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts index 9bdb47acd26..2e4e17c6295 100644 --- a/src/vs/base/common/cache.ts +++ b/src/vs/base/common/cache.ts @@ -118,3 +118,36 @@ export class CachedFunction { return value; } } + +/** + * Uses an unbounded cache to memoize the results of the given function. +*/ +export class WeakCachedFunction { + private readonly _map = new WeakMap(); + + private readonly _fn: (arg: TArg) => TComputed; + private readonly _computeKey: (arg: TArg) => unknown; + + constructor(fn: (arg: TArg) => TComputed); + constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); + constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { + if (typeof arg1 === 'function') { + this._fn = arg1; + this._computeKey = identity; + } else { + this._fn = arg2!; + this._computeKey = arg1.getCacheKey; + } + } + + public get(arg: TArg): TComputed { + const key = this._computeKey(arg) as WeakKey; + if (this._map.has(key)) { + return this._map.get(key)!; + } + + const value = this._fn(arg); + this._map.set(key, value); + return value; + } +} diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts index e4f74931aa8..7509a1e8273 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts @@ -6,7 +6,7 @@ import { TimeoutTimer } from '../../../../../base/common/async.js'; import { onUnexpectedError } from '../../../../../base/common/errors.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../base/common/lifecycle.js'; -import { IObservableWithChange, runOnChange } from '../../../../../base/common/observable.js'; +import { IObservable, IObservableWithChange, runOnChange } from '../../../../../base/common/observable.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { AnnotatedStringEdit, BaseStringEdit } from '../../../../../editor/common/core/edits/stringEdit.js'; import { StringText } from '../../../../../editor/common/core/text/abstractText.js'; @@ -23,7 +23,7 @@ import { ProviderId } from '../../../../../editor/common/languages.js'; export class InlineEditArcTelemetrySender extends Disposable { constructor( docWithAnnotatedEdits: IDocumentWithAnnotatedEdits, - scmRepoBridge: ScmRepoBridge | undefined, + scmRepoBridge: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -155,7 +155,7 @@ export class AiEditTelemetryAdapter extends Disposable { export class ChatArcTelemetrySender extends Disposable { constructor( docWithAnnotatedEdits: IDocumentWithAnnotatedEdits, - scmRepoBridge: ScmRepoBridge | undefined, + scmRepoBridge: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -290,7 +290,7 @@ export class ArcTelemetryReporter { private readonly _documentValueBeforeTrackedEdit: StringText, private readonly _document: { value: IObservableWithChange }, // _markedEdits -> document.value - private readonly _gitRepo: ScmRepoBridge | undefined, + private readonly _gitRepo: IObservable, private readonly _trackedEdit: BaseStringEdit, private readonly _sendTelemetryEvent: (res: EditTelemetryData) => void, @@ -307,7 +307,7 @@ export class ArcTelemetryReporter { this._initialLineCounts = this._arcTracker.getLineCountInfo(); - this._initialBranchName = this._gitRepo?.headBranchNameObs.get(); + this._initialBranchName = this._gitRepo.get()?.headBranchNameObs.get(); for (let i = 0; i < this._timesMs.length; i++) { const timeMs = this._timesMs[i]; @@ -334,7 +334,7 @@ export class ArcTelemetryReporter { } private _report(timeMs: number): void { - const currentBranch = this._gitRepo?.headBranchNameObs.get(); + const currentBranch = this._gitRepo.get()?.headBranchNameObs.get(); const didBranchChange = currentBranch !== this._initialBranchName; const currentLineCounts = this._arcTracker.getLineCountInfo(); diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts index eb8abb6dd96..a49cf205c86 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts @@ -6,7 +6,7 @@ import { reverseOrder, compareBy, numberComparator, sumBy } from '../../../../../base/common/arrays.js'; import { IntervalTimer, TimeoutTimer } from '../../../../../base/common/async.js'; import { toDisposable, Disposable } from '../../../../../base/common/lifecycle.js'; -import { mapObservableArrayCached, derived, IObservable, observableSignal, runOnChange } from '../../../../../base/common/observable.js'; +import { mapObservableArrayCached, derived, IObservable, observableSignal, runOnChange, IReader, autorun, observableSignalFromEvent } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -17,6 +17,8 @@ import { AiEditTelemetryAdapter, ChatArcTelemetrySender, InlineEditArcTelemetryS import { createDocWithJustReason, EditSource } from '../helpers/documentWithAnnotatedEdits.js'; import { DocumentEditSourceTracker, TrackedEdit } from './editTracker.js'; import { sumByCategory } from '../helpers/utils.js'; +import { WeakCachedFunction } from '../../../../../base/common/cache.js'; +import { Event } from '../../../../../base/common/event.js'; export class EditSourceTrackingImpl extends Disposable { public readonly docsState; @@ -42,7 +44,7 @@ class TrackedDocumentInfo extends Disposable { public readonly longtermTracker: IObservable | undefined>; public readonly windowedTracker: IObservable | undefined>; - private readonly _repo: Promise; + private readonly _repo: IObservable; constructor( private readonly _doc: AnnotatedDocument, @@ -53,6 +55,8 @@ class TrackedDocumentInfo extends Disposable { ) { super(); + this._repo = derived(this, reader => this._scm.getRepo(_doc.document.uri, reader)); + const docWithJustReason = createDocWithJustReason(_doc.documentWithAnnotations, this._store); const longtermResetSignal = observableSignal('resetSignal'); @@ -80,29 +84,26 @@ class TrackedDocumentInfo extends Disposable { longtermReason = 'closed'; }, 10 * 60 * 60 * 1000); - (async () => { - const repo = await this._scm.getRepo(_doc.document.uri); - if (this._store.isDisposed) { - return; - } - // Reset on branch change or commit + // Reset on branch change or commit + this._store.add(autorun(reader => { + const repo = this._repo.read(reader); if (repo) { - this._store.add(runOnChange(repo.headCommitHashObs, () => { + reader.store.add(runOnChange(repo.headCommitHashObs, () => { longtermReason = 'hashChange'; longtermResetSignal.trigger(undefined); longtermReason = 'closed'; })); - this._store.add(runOnChange(repo.headBranchNameObs, () => { + reader.store.add(runOnChange(repo.headBranchNameObs, () => { longtermReason = 'branchChange'; longtermResetSignal.trigger(undefined); longtermReason = 'closed'; })); } + })); - this._store.add(this._instantiationService.createInstance(InlineEditArcTelemetrySender, _doc.documentWithAnnotations, repo)); - this._store.add(this._instantiationService.createInstance(ChatArcTelemetrySender, _doc.documentWithAnnotations, repo)); - this._store.add(this._instantiationService.createInstance(AiEditTelemetryAdapter, _doc.documentWithAnnotations)); - })(); + this._store.add(this._instantiationService.createInstance(InlineEditArcTelemetrySender, _doc.documentWithAnnotations, this._repo)); + this._store.add(this._instantiationService.createInstance(ChatArcTelemetrySender, _doc.documentWithAnnotations, this._repo)); + this._store.add(this._instantiationService.createInstance(AiEditTelemetryAdapter, _doc.documentWithAnnotations)); const resetSignal = observableSignal('resetSignal'); @@ -129,7 +130,6 @@ class TrackedDocumentInfo extends Disposable { return t; }).recomputeInitiallyAndOnChange(this._store); - this._repo = this._scm.getRepo(_doc.document.uri); } async sendTelemetry(mode: 'longterm' | '5minWindow', trigger: string, t: DocumentEditSourceTracker) { @@ -299,22 +299,29 @@ class TrackedDocumentInfo extends Disposable { externalModifiedCount: sums.external ?? 0, totalModifiedCharactersInFinalState, languageId: this._doc.document.languageId.get(), - isTrackedByGit: this._repo.then(async (repo) => !!repo && !await repo.isIgnored(this._doc.document.uri)), + isTrackedByGit: this._repo.get()?.isIgnored(this._doc.document.uri), }; } } class ScmBridge { + private readonly _repos = new WeakCachedFunction((repo: ISCMRepository) => new ScmRepoBridge(repo)); + + private readonly _reposChangedSignal; + constructor( - @ISCMService private readonly _scmService: ISCMService - ) { } + @ISCMService private readonly _scmService: ISCMService, + ) { + this._reposChangedSignal = observableSignalFromEvent(this, Event.any(this._scmService.onDidAddRepository, this._scmService.onDidRemoveRepository)); + } - public async getRepo(uri: URI): Promise { + public getRepo(uri: URI, reader: IReader | undefined): ScmRepoBridge | undefined { + this._reposChangedSignal.read(reader); const repo = this._scmService.getRepository(uri); if (!repo) { return undefined; } - return new ScmRepoBridge(repo); + return this._repos.get(repo); } } From 809fee573c29e89a1bdbc9635901e4aeb4efa8a8 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:57:01 +0200 Subject: [PATCH 0974/4355] Fix tunnelView --- src/vs/platform/tunnel/common/tunnel.ts | 5 +++++ src/vs/workbench/contrib/remote/browser/tunnelView.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 1b0592103ac..bf5d39b625d 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -27,6 +27,11 @@ export interface RemoteTunnel { dispose(silent?: boolean): Promise; } +export function isRemoteTunnel(something: unknown): something is RemoteTunnel { + const asTunnel: Partial = something as Partial; + return !!(asTunnel.tunnelRemotePort && asTunnel.tunnelRemoteHost && asTunnel.localAddress && asTunnel.privacy && asTunnel.dispose); +} + export interface TunnelOptions { remoteAddress: { port: number; host: string }; localAddressPort?: number; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 075956b8e39..9b0241b7a2d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -36,7 +36,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { URI } from '../../../../base/common/uri.js'; -import { isAllInterfaces, isLocalhost, ITunnelService, RemoteTunnel, TunnelPrivacyId, TunnelProtocol } from '../../../../platform/tunnel/common/tunnel.js'; +import { isAllInterfaces, isLocalhost, isRemoteTunnel, ITunnelService, RemoteTunnel, TunnelPrivacyId, TunnelProtocol } from '../../../../platform/tunnel/common/tunnel.js'; import { TunnelPrivacy } from '../../../../platform/remote/common/remoteAuthorityResolver.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -1326,7 +1326,7 @@ export namespace OpenPortInBrowserAction { let key: string | undefined; if (isITunnelItem(arg)) { key = makeAddress(arg.remoteHost, arg.remotePort); - } else if (arg.tunnelRemoteHost && arg.tunnelRemotePort) { + } else if (isRemoteTunnel(arg)) { key = makeAddress(arg.tunnelRemoteHost, arg.tunnelRemotePort); } if (key) { @@ -1355,7 +1355,7 @@ export namespace OpenPortInPreviewAction { let key: string | undefined; if (isITunnelItem(arg)) { key = makeAddress(arg.remoteHost, arg.remotePort); - } else if (arg.tunnelRemoteHost && arg.tunnelRemotePort) { + } else if (isRemoteTunnel(arg)) { key = makeAddress(arg.tunnelRemoteHost, arg.tunnelRemotePort); } if (key) { From 73947d8d87182e23c938433fa8ea4f3bf6ab6c25 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 8 Oct 2025 17:58:29 +0100 Subject: [PATCH 0975/4355] Update foreground color for operators and semantic tokens in dark_plus theme for higher contrast ratio --- extensions/theme-defaults/themes/dark_plus.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 8328860a9ff..29a82195861 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -83,7 +83,7 @@ "entity.name.operator" ], "settings": { - "foreground": "#C586C0" + "foreground": "#CE92A4" } }, { @@ -197,7 +197,7 @@ } ], "semanticTokenColors": { - "newOperator":"#C586C0", + "newOperator":"#CE92A4", "stringLiteral":"#ce9178", "customLiteral": "#DCDCAA", "numberLiteral": "#b5cea8", From d83502b5b160d9932004be0688fdf5fd32d2fafd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 8 Oct 2025 10:00:54 -0700 Subject: [PATCH 0976/4355] chat: improve mcp autostart experience (#270392) * chat: improve mcp autostart experience Closes #269016. When autostarting MCP servers: - The 'working' spinner is immediate - Instead of a notif, I show the servers being started in chat if they take more than 3s, along with a "Skip" button. I don't love double spinners but I don't see a way to get rid of that in a nice way - Interaction-required servers are prompted once per chat per server. Nicer than we were doing before -- now if you add a new server that needs interaction we'll show the indicator for that server even if we told you about other servers earlier in the session. * cleanup and fix bug --- .../chatMcpServersInteractionContentPart.ts | 196 ++++++++++++++---- .../chatProgressContentPart.ts | 30 ++- .../contrib/chat/browser/chatListRenderer.ts | 10 +- .../contrib/chat/common/chatModel.ts | 6 +- .../contrib/chat/common/chatService.ts | 47 ++++- .../contrib/chat/common/chatServiceImpl.ts | 24 +-- .../contrib/chat/common/chatViewModel.ts | 4 +- .../contrib/mcp/browser/mcp.contribution.ts | 3 +- .../contrib/mcp/browser/mcpCommands.ts | 15 ++ .../contrib/mcp/common/mcpCommandIds.ts | 11 +- .../contrib/mcp/common/mcpService.ts | 119 +++++------ .../workbench/contrib/mcp/common/mcpTypes.ts | 18 +- .../test/common/mcpResourceFilesystem.test.ts | 6 +- .../contrib/mcp/test/common/testMcpService.ts | 8 +- 14 files changed, 342 insertions(+), 155 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index fc383d736de..cafb2af6149 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -4,21 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; +import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; -import { markdownCommandLink, MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { escapeMarkdownSyntaxTokens, markdownCommandLink, MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { Lazy } from '../../../../../base/common/lazy.js'; +import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderResult, MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { McpCommandIds } from '../../../mcp/common/mcpCommandIds.js'; -import { IMcpService } from '../../../mcp/common/mcpTypes.js'; +import { IAutostartResult, IMcpService } from '../../../mcp/common/mcpTypes.js'; import { startServerAndWaitForLiveTools } from '../../../mcp/common/mcpTypesUtils.js'; -import { IChatMcpServersInteractionRequired } from '../../common/chatService.js'; -import { IChatRendererContent } from '../../common/chatViewModel.js'; -import { IChatContentPart } from './chatContentParts.js'; +import { IChatMcpServersStarting } from '../../common/chatService.js'; +import { IChatRendererContent, IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; +import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; +import { ChatProgressContentPart } from './chatProgressContentPart.js'; import './media/chatMcpServersInteractionContent.css'; export class ChatMcpServersInteractionContentPart extends Disposable implements IChatContentPart { @@ -26,8 +30,25 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements private readonly _onDidChangeHeight = this._register(new Emitter()); public readonly onDidChangeHeight = this._onDidChangeHeight.event; + private workingProgressPart: ChatProgressContentPart | undefined; + private interactionContainer: HTMLElement | undefined; + private readonly interactionMd = this._register(new MutableDisposable()); + private readonly markdownRenderer: MarkdownRenderer; + private readonly showSpecificServersScheduler = this._register(new RunOnceScheduler(() => this.updateDetailedProgress(this.data.state!.get()), 2500)); + private readonly previousParts = new Lazy(() => { + if (!isResponseVM(this.context.element)) { + return []; + } + + return this.context.element.session.getItems() + .filter((r, i): r is IChatResponseViewModel => isResponseVM(r) && i < this.context.elementIndex) + .flatMap(i => i.response.value.filter(c => c.kind === 'mcpServersStarting')) + .map(p => p.state?.get()); + }); + constructor( - private readonly data: IChatMcpServersInteractionRequired, + private readonly data: IChatMcpServersStarting, + private readonly context: IChatContentPartRenderContext, @IMcpService private readonly mcpService: IMcpService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly _openerService: IOpenerService, @@ -35,33 +56,131 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements super(); this.domNode = dom.$('.chat-mcp-servers-interaction'); - if (!data.isDone) { - this.render(); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + + // Listen to autostart state changes if available + if (data.state) { + this._register(autorun(reader => { + const state = data.state!.read(reader); + this.updateForState(state); + })); + } + } + + private updateForState(state: IAutostartResult): void { + // Handle working progress state + if (state.working && !this.workingProgressPart) { + if (!this.showSpecificServersScheduler.isScheduled()) { + this.showSpecificServersScheduler.schedule(); + } + } else if (!state.working && this.workingProgressPart) { + this.workingProgressPart.domNode.remove(); + this.workingProgressPart = undefined; + this.showSpecificServersScheduler.cancel(); + } else if (state.working && this.workingProgressPart) { + this.updateDetailedProgress(state); + } + + const requiringInteraction = state.serversRequiringInteraction.filter(s => { + // don't note interaction for a server we already started + if (this.data.didStartServerIds?.includes(s.id)) { + return false; + } + + // don't note interaction for a server we previously noted interaction for + if (this.previousParts.value.some(p => p?.serversRequiringInteraction.some(s2 => s.id === s2.id))) { + return false; + } + + return true; + }); + + if (requiringInteraction.length > 0) { + if (!this.interactionMd.value) { + this.renderInteractionRequired(requiringInteraction); + } else { + this.updateInteractionRequired(this.interactionMd.value.element, requiringInteraction); + } + } else if (requiringInteraction.length === 0 && this.interactionContainer) { + this.interactionContainer.remove(); + this.interactionContainer = undefined; + } + + this._onDidChangeHeight.fire(); + } + + private createServerCommandLinks(servers: Array<{ id: string; label: string }>): string { + return servers.map(s => markdownCommandLink({ + title: '`' + escapeMarkdownSyntaxTokens(s.label) + '`', + id: McpCommandIds.ServerOptions, + arguments: [s.id], + }, false)).join(', '); + } + + private updateDetailedProgress(state: IAutostartResult): void { + const skipText = markdownCommandLink({ + title: localize('mcp.skip.link', 'Skip?'), + id: McpCommandIds.SkipCurrentAutostart, + }); + + let content: MarkdownString; + if (state.starting.length === 0) { + content = new MarkdownString(undefined, { isTrusted: true }).appendText(localize('mcp.working.mcp', 'Activating MCP extensions...') + ' ').appendMarkdown(skipText); + } else { + // Update to show specific server names as command links + const serverLinks = this.createServerCommandLinks(state.starting); + content = new MarkdownString(undefined, { isTrusted: true }).appendMarkdown(localize('mcp.starting.servers', 'Starting MCP servers {0}...', serverLinks) + ' ').appendMarkdown(skipText); } + + if (this.workingProgressPart) { + this.workingProgressPart.updateMessage(content); + } else { + this.workingProgressPart = this._register(this.instantiationService.createInstance( + ChatProgressContentPart, + { kind: 'progressMessage', content }, + this.markdownRenderer, + this.context, + true, // forceShowSpinner + true, // forceShowMessage + undefined // icon + )); + this.domNode.appendChild(this.workingProgressPart.domNode); + } + + this._onDidChangeHeight.fire(); } - private render(): void { - const container = dom.$('.chat-mcp-servers-interaction-hint'); + private renderInteractionRequired(serversRequiringInteraction: Array<{ id: string; label: string; errorMessage?: string }>): void { + this.interactionContainer = dom.$('.chat-mcp-servers-interaction-hint'); // Create subtle hint message const messageContainer = dom.$('.chat-mcp-servers-message'); const icon = dom.$('.chat-mcp-servers-icon'); icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.mcp)); - const count = this.data.servers.length; + const { messageMd } = this.createInteractionMessage(serversRequiringInteraction); - const markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); - const links = this.data.servers.map(s => markdownCommandLink({ - title: '`' + s.serverLabel + '`', - id: McpCommandIds.ServerOptions, - arguments: [s.serverId], - }, false)).join(', '); + messageContainer.appendChild(icon); + messageContainer.appendChild(messageMd.element); + + this.interactionContainer.appendChild(messageContainer); + this.domNode.prepend(this.interactionContainer); + } + + private updateInteractionRequired(oldElement: HTMLElement, serversRequiringInteraction: Array<{ id: string; label: string; errorMessage?: string }>): void { + const { messageMd } = this.createInteractionMessage(serversRequiringInteraction); + oldElement.replaceWith(messageMd.element); + } + + private createInteractionMessage(serversRequiringInteraction: Array<{ id: string; label: string; errorMessage?: string }>) { + const count = serversRequiringInteraction.length; + const links = this.createServerCommandLinks(serversRequiringInteraction); const content = count === 1 ? localize('mcp.start.single', 'The MCP server {0} may have new tools and requires interaction to start. [Start it now?]({1})', links, '#start') : localize('mcp.start.multiple', 'The MCP servers {0} may have new tools and require interaction to start. [Start them now?]({1})', links, '#start'); const str = new MarkdownString(content, { isTrusted: true }); - const messageMd = markdownRenderer.render(str, { + const messageMd = this.interactionMd.value = this.markdownRenderer.render(str, { asyncRenderCallback: () => this._onDidChangeHeight.fire(), actionHandler: (content) => { if (!content.startsWith('command:')) { @@ -75,41 +194,48 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements const startLink = [...messageMd.element.querySelectorAll('a')].find(a => !a.getAttribute('data-href')?.startsWith('command:')); if (!startLink) { // Should not happen - return; + return { messageMd, startLink: undefined }; } startLink.setAttribute('role', 'button'); startLink.href = ''; - messageContainer.appendChild(icon); - messageContainer.appendChild(messageMd.element); - - container.appendChild(messageContainer); - this.domNode.appendChild(container); + return { messageMd, startLink }; } private async _start(startLink: HTMLElement) { - // Update to starting state startLink.style.pointerEvents = 'none'; startLink.style.opacity = '0.7'; try { + if (!this.data.state) { + return; + } + + const state = this.data.state.get(); + const serversToStart = state.serversRequiringInteraction; + // Start servers in sequence with progress updates - for (let i = 0; i < this.data.servers.length; i++) { - const serverInfo = this.data.servers[i]; - startLink.textContent = localize('mcp.starting', "Starting {0}...", serverInfo.serverLabel); + for (let i = 0; i < serversToStart.length; i++) { + const serverInfo = serversToStart[i]; + startLink.textContent = localize('mcp.starting', "Starting {0}...", serverInfo.label); this._onDidChangeHeight.fire(); - const server = this.mcpService.servers.get().find(s => s.definition.id === serverInfo.serverId); + const server = this.mcpService.servers.get().find(s => s.definition.id === serverInfo.id); if (server) { await startServerAndWaitForLiveTools(server, { promptType: 'all-untrusted' }); + + this.data.didStartServerIds ??= []; + this.data.didStartServerIds.push(serverInfo.id); } } - // Remove the component after successful start - this.data.isDone = true; - this.domNode.remove(); + // Remove the interaction container after successful start + if (this.interactionContainer) { + this.interactionContainer.remove(); + this.interactionContainer = undefined; + } } catch (error) { // Reset link on error startLink.style.pointerEvents = ''; @@ -122,7 +248,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements hasSameContent(other: IChatRendererContent): boolean { // Simple implementation that checks if it's the same type - return other.kind === 'mcpServersInteractionRequired'; + return other.kind === 'mcpServersStarting'; } addDisposable(disposable: IDisposable): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index fbe1c57e861..2ea5e3c16be 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -7,9 +7,9 @@ import { $, append } from '../../../../../base/browser/dom.js'; import { alert } from '../../../../../base/browser/ui/aria/aria.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; import { IChatProgressMessage, IChatTask, IChatTaskSerialized } from '../../common/chatService.js'; @@ -24,10 +24,11 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP private readonly showSpinner: boolean; private readonly isHidden: boolean; + private readonly renderedMessage = this._register(new MutableDisposable()); constructor( progress: IChatProgressMessage | IChatTask | IChatTaskSerialized, - renderer: MarkdownRenderer, + private readonly renderer: MarkdownRenderer, context: IChatContentPartRenderContext, forceShowSpinner: boolean | undefined, forceShowMessage: boolean | undefined, @@ -52,7 +53,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP alert(progress.content.value); } const codicon = icon ? icon : this.showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin') : Codicon.check; - const result = this._register(renderer.render(progress.content)); + const result = this.renderer.render(progress.content); result.element.classList.add('progress-step'); renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store); @@ -61,6 +62,27 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP iconElement.classList.add(...ThemeIcon.asClassNameArray(codicon)); append(this.domNode, iconElement); append(this.domNode, result.element); + this.renderedMessage.value = result; + } + + updateMessage(content: MarkdownString): void { + if (this.isHidden) { + return; + } + + // Render the new message + const result = this._register(this.renderer.render(content)); + result.element.classList.add('progress-step'); + renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store); + + // Replace the old message container with the new one + if (this.renderedMessage.value) { + this.renderedMessage.value.element.replaceWith(result.element); + } else { + this.domNode.appendChild(result.element); + } + + this.renderedMessage.value = result; } hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index b93bdfa63f7..aabb086c293 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -53,7 +53,7 @@ import { IChatAgentMetadata } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatConfirmation, IChatContentReference, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMcpServersInteractionRequired, IChatMultiDiffData, IChatPullRequestContent, IChatTask, IChatTaskSerialized, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop } from '../common/chatService.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatErrorLevel, IChatChangesSummary, IChatConfirmation, IChatContentReference, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatPullRequestContent, IChatTask, IChatTaskSerialized, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop } from '../common/chatService.js'; import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { getNWords } from '../common/chatWordCounter.js'; @@ -768,7 +768,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer part.kind === 'toolInvocation' && !part.isComplete)) || (lastPart.kind === 'progressTask' && lastPart.deferred.isSettled) || - lastPart.kind === 'prepareToolInvocation' || lastPart.kind === 'mcpServersInteractionRequired' + lastPart.kind === 'prepareToolInvocation' || lastPart.kind === 'mcpServersStarting' ) { return true; } @@ -1252,7 +1252,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer; } -export interface IChatMcpServersInteractionRequired { - kind: 'mcpServersInteractionRequired'; - isDone?: boolean; - servers: Array<{ - serverId: string; - serverLabel: string; - errorMessage?: string; - }>; - startCommand: Command; +export interface IChatMcpServersStarting { + readonly kind: 'mcpServersStarting'; + readonly state?: IObservable; // not hydrated when serialized + didStartServerIds?: string[]; +} + +export class ChatMcpServersStarting implements IChatMcpServersStarting { + public readonly kind = 'mcpServersStarting'; + + public didStartServerIds?: string[] = []; + + public get isEmpty() { + const s = this.state.get(); + return !s.working && s.serversRequiringInteraction.length === 0; + } + + constructor(public readonly state: IObservable) { } + + wait() { + return new Promise(resolve => { + autorunSelfDisposable(reader => { + const s = this.state.read(reader); + if (!s.working) { + reader.dispose(); + resolve(s); + } + }); + }); + } + + toJSON(): IChatMcpServersStarting { + return { kind: 'mcpServersStarting', didStartServerIds: this.didStartServerIds }; + } } export interface IChatPrepareToolInvocationPart { @@ -459,7 +484,7 @@ export type IChatProgress = | IChatThinkingPart | IChatTaskSerialized | IChatElicitationRequest - | IChatMcpServersInteractionRequired; + | IChatMcpServersStarting; export interface IChatFollowup { kind: 'reply'; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 546d8345c42..024df8d11b8 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -30,7 +30,7 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRe import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, chatSubcommandLeader, getPromptText, IParsedChatRequest } from './chatParserTypes.js'; import { ChatRequestParser } from './chatRequestParser.js'; -import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; +import { ChatMcpServersStarting, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; import { ChatRequestTelemetry, ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { IChatSessionsService } from './chatSessionsService.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; @@ -95,8 +95,6 @@ export class ChatService extends Disposable implements IChatService { private readonly _chatServiceTelemetry: ChatServiceTelemetry; private readonly _chatSessionStore: ChatSessionStore; - private _mcpServersInteractionMessageShown = new WeakSet(); - readonly requestInProgressObs: IObservable; public get edits2Enabled(): boolean { @@ -856,10 +854,7 @@ export class ChatService extends Disposable implements IChatService { const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!; const command = detectedCommand ?? agentSlashCommandPart?.command; - const [, autostartResult] = await Promise.all([ - this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`), - this.mcpService.autostart(token), - ]); + await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); // Recompute history in case the agent or command changed const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); @@ -871,17 +866,10 @@ export class ChatService extends Disposable implements IChatService { completeResponseCreated(); // Check if there are MCP servers requiring interaction and show message if not shown yet - if (!this._mcpServersInteractionMessageShown.has(model) && autostartResult.serversRequiringInteraction.length > 0) { - this._mcpServersInteractionMessageShown.add(model); - progressCallback([{ - kind: 'mcpServersInteractionRequired', - servers: autostartResult.serversRequiringInteraction, - startCommand: { - id: 'mcp.startServersWithInteraction', - title: localize('chat.startMcpServers', 'Start MCP Servers'), - arguments: [] - } - }]); + const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token)); + if (!autostartResult.isEmpty) { + progressCallback([autostartResult]); + await autostartResult.wait(); } const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index cc53dc0e242..fb8f5eeae9d 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -17,7 +17,7 @@ import { getFullyQualifiedId, IChatAgentCommand, IChatAgentData, IChatAgentNameS import { IChatModel, IChatProgressRenderableResponseContent, IChatRequestDisablement, IChatRequestModel, IChatResponseModel, IChatTextEditGroup, IResponse } from './chatModel.js'; import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { IParsedChatRequest } from './chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatChangesSummary, IChatCodeCitation, IChatContentReference, IChatFollowup, IChatMcpServersInteractionRequired, IChatProgressMessage, IChatResponseErrorDetails, IChatTask, IChatUsedContext } from './chatService.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatChangesSummary, IChatCodeCitation, IChatContentReference, IChatFollowup, IChatMcpServersStarting, IChatProgressMessage, IChatResponseErrorDetails, IChatTask, IChatUsedContext } from './chatService.js'; import { countWords } from './chatWordCounter.js'; import { CodeBlockModelCollection } from './codeBlockModelCollection.js'; @@ -180,7 +180,7 @@ export interface IChatChangesSummaryPart { /** * Type for content parts rendered by IChatListRenderer (not necessarily in the model) */ -export type IChatRendererContent = IChatProgressRenderableResponseContent | IChatReferences | IChatCodeCitations | IChatErrorDetailsPart | IChatChangesSummaryPart | IChatWorkingProgress | IChatMcpServersInteractionRequired; +export type IChatRendererContent = IChatProgressRenderableResponseContent | IChatReferences | IChatCodeCitations | IChatErrorDetailsPart | IChatChangesSummaryPart | IChatWorkingProgress | IChatMcpServersStarting; export interface IChatLiveUpdateData { totalTime: number; diff --git a/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts b/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts index 4bbd3637b70..433046be5f9 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts @@ -33,7 +33,7 @@ import { McpSamplingService } from '../common/mcpSamplingService.js'; import { McpService } from '../common/mcpService.js'; import { IMcpElicitationService, IMcpSamplingService, IMcpService, IMcpWorkbenchService } from '../common/mcpTypes.js'; import { McpAddContextContribution } from './mcpAddContextContribution.js'; -import { AddConfigurationAction, EditStoredInput, ListMcpServerCommand, McpBrowseCommand, McpBrowseResourcesCommand, McpConfigureSamplingModels, McpConfirmationServerOptionsCommand, MCPServerActionRendering, McpServerOptionsCommand, McpStartPromptingServerCommand, OpenRemoteUserMcpResourceCommand, OpenUserMcpResourceCommand, OpenWorkspaceFolderMcpResourceCommand, OpenWorkspaceMcpResourceCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, RestartServer, ShowConfiguration, ShowInstalledMcpServersCommand, ShowOutput, StartServer, StopServer } from './mcpCommands.js'; +import { AddConfigurationAction, EditStoredInput, ListMcpServerCommand, McpBrowseCommand, McpBrowseResourcesCommand, McpConfigureSamplingModels, McpConfirmationServerOptionsCommand, MCPServerActionRendering, McpServerOptionsCommand, McpSkipCurrentAutostartCommand, McpStartPromptingServerCommand, OpenRemoteUserMcpResourceCommand, OpenUserMcpResourceCommand, OpenWorkspaceFolderMcpResourceCommand, OpenWorkspaceMcpResourceCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, RestartServer, ShowConfiguration, ShowInstalledMcpServersCommand, ShowOutput, StartServer, StopServer } from './mcpCommands.js'; import { McpDiscovery } from './mcpDiscovery.js'; import { McpElicitationService } from './mcpElicitationService.js'; import { McpLanguageFeatures } from './mcpLanguageFeatures.js'; @@ -84,6 +84,7 @@ registerAction2(ShowInstalledMcpServersCommand); registerAction2(McpBrowseResourcesCommand); registerAction2(McpConfigureSamplingModels); registerAction2(McpStartPromptingServerCommand); +registerAction2(McpSkipCurrentAutostartCommand); registerWorkbenchContribution2('mcpActionRendering', MCPServerActionRendering, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2('mcpAddContext', McpAddContextContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index 3cb8356533f..eccabee943a 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -1092,3 +1092,18 @@ export class McpStartPromptingServerCommand extends Action2 { SuggestController.get(editor)?.triggerSuggest(); } } + +export class McpSkipCurrentAutostartCommand extends Action2 { + constructor() { + super({ + id: McpCommandIds.SkipCurrentAutostart, + title: localize2('mcp.skipCurrentAutostart', "Skip Current Autostart"), + category, + f1: false, + }); + } + + async run(accessor: ServicesAccessor): Promise { + accessor.get(IMcpService).cancelAutostart(); + } +} diff --git a/src/vs/workbench/contrib/mcp/common/mcpCommandIds.ts b/src/vs/workbench/contrib/mcp/common/mcpCommandIds.ts index aaa2381c3c5..c68b5acedbc 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpCommandIds.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpCommandIds.ts @@ -10,16 +10,15 @@ export const enum McpCommandIds { AddConfiguration = 'workbench.mcp.addConfiguration', Browse = 'workbench.mcp.browseServers', BrowsePage = 'workbench.mcp.browseServersPage', - ShowInstalled = 'workbench.mcp.showInstalledServers', - OpenUserMcp = 'workbench.mcp.openUserMcpJson', - OpenRemoteUserMcp = 'workbench.mcp.openRemoteUserMcpJson', - OpenWorkspaceFolderMcp = 'workbench.mcp.openWorkspaceFolderMcpJson', - OpenWorkspaceMcp = 'workbench.mcp.openWorkspaceMcpJson', BrowseResources = 'workbench.mcp.browseResources', ConfigureSamplingModels = 'workbench.mcp.configureSamplingModels', EditStoredInput = 'workbench.mcp.editStoredInput', InstallFromActivation = 'workbench.mcp.installFromActivation', ListServer = 'workbench.mcp.listServer', + OpenRemoteUserMcp = 'workbench.mcp.openRemoteUserMcpJson', + OpenUserMcp = 'workbench.mcp.openUserMcpJson', + OpenWorkspaceFolderMcp = 'workbench.mcp.openWorkspaceFolderMcpJson', + OpenWorkspaceMcp = 'workbench.mcp.openWorkspaceMcpJson', RemoveStoredInput = 'workbench.mcp.removeStoredInput', ResetCachedTools = 'workbench.mcp.resetCachedTools', ResetTrust = 'workbench.mcp.resetTrust', @@ -27,7 +26,9 @@ export const enum McpCommandIds { ServerOptions = 'workbench.mcp.serverOptions', ServerOptionsInConfirmation = 'workbench.mcp.serverOptionsInConfirmation', ShowConfiguration = 'workbench.mcp.showConfiguration', + ShowInstalled = 'workbench.mcp.showInstalledServers', ShowOutput = 'workbench.mcp.showOutput', + SkipCurrentAutostart = 'workbench.mcp.skipAutostart', StartPromptForServer = 'workbench.mcp.startPromptForServer', StartServer = 'workbench.mcp.startServer', StopServer = 'workbench.mcp.stopServer', diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index 37dd02d33a9..b65817d4898 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -5,19 +5,16 @@ import { RunOnceScheduler } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { autorun, IObservable, observableValue, transaction } from '../../../../base/common/observable.js'; -import { localize } from '../../../../nls.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, IObservable, ISettableObservable, observableValue, transaction } from '../../../../base/common/observable.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { mcpAutoStartConfig, McpAutoStartValue } from '../../../../platform/mcp/common/mcpManagement.js'; -import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { McpServer, McpServerMetadataCache } from './mcpServer.js'; -import { IMcpServer, IMcpService, IAutostartResult, McpCollectionDefinition, McpConnectionState, McpServerCacheState, McpServerDefinition, McpStartServerInteraction, McpToolName, UserInteractionRequiredError } from './mcpTypes.js'; +import { IAutostartResult, IMcpServer, IMcpService, McpCollectionDefinition, McpConnectionState, McpDefinitionReference, McpServerCacheState, McpServerDefinition, McpStartServerInteraction, McpToolName, UserInteractionRequiredError } from './mcpTypes.js'; import { startServerAndWaitForLiveTools } from './mcpTypesUtils.js'; type IMcpServerRec = { object: IMcpServer; toolPrefix: string }; @@ -26,6 +23,7 @@ export class McpService extends Disposable implements IMcpService { declare _serviceBrand: undefined; + private readonly _currentAutoStarts = new Set(); private readonly _servers = observableValue(this, []); public readonly servers: IObservable = this._servers.map(servers => servers.map(s => s.object)); @@ -38,8 +36,6 @@ export class McpService extends Disposable implements IMcpService { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry, @ILogService private readonly _logService: ILogService, - @IProgressService private readonly progressService: IProgressService, - @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -59,77 +55,82 @@ export class McpService extends Disposable implements IMcpService { })); } - public async autostart(token?: CancellationToken): Promise { + public cancelAutostart(): void { + for (const cts of this._currentAutoStarts) { + cts.cancel(); + } + } + + public autostart(_token?: CancellationToken): IObservable { const autoStartConfig = this.configurationService.getValue(mcpAutoStartConfig); if (autoStartConfig === McpAutoStartValue.Never) { - return { serversRequiringInteraction: [] }; + return observableValue(this, IAutostartResult.Empty); } + const state = observableValue(this, { working: true, starting: [], serversRequiringInteraction: [] }); + const store = new DisposableStore(); + + const cts = store.add(new CancellationTokenSource(_token)); + this._currentAutoStarts.add(cts); + store.add(toDisposable(() => { + this._currentAutoStarts.delete(cts); + })); + store.add(cts.token.onCancellationRequested(() => { + state.set(IAutostartResult.Empty, undefined); + })); + + this._autostart(autoStartConfig, state, cts.token).finally(() => store.dispose()); + + return state; + } + + private async _autostart(autoStartConfig: McpAutoStartValue, state: ISettableObservable, token: CancellationToken) { await this.activateCollections(); + if (token.isCancellationRequested) { + return; + } // don't try re-running errored servers, let the user choose if they want that const candidates = this.servers.get().filter(s => s.connectionState.get().state !== McpConnectionState.Kind.Error); - let todo: IMcpServer[] = []; + let todo = new Set(); if (autoStartConfig === McpAutoStartValue.OnlyNew) { - todo = candidates.filter(s => s.cacheState.get() === McpServerCacheState.Unknown); + todo = new Set(candidates.filter(s => s.cacheState.get() === McpServerCacheState.Unknown)); } else if (autoStartConfig === McpAutoStartValue.NewAndOutdated) { - todo = candidates.filter(s => { + todo = new Set(candidates.filter(s => { const c = s.cacheState.get(); return c === McpServerCacheState.Unknown || c === McpServerCacheState.Outdated; - }); + })); } - if (!todo.length) { - return { serversRequiringInteraction: [] }; + if (!todo.size) { + state.set(IAutostartResult.Empty, undefined); + return; } - const serversRequiringInteraction: Array<{ serverId: string; serverLabel: string; errorMessage?: string }> = []; const interaction = new McpStartServerInteraction(); - const cts = new CancellationTokenSource(token); - - await this.progressService.withProgress( - { - location: ProgressLocation.Notification, - cancellable: true, - delay: 5_000, - total: todo.length, - buttons: [ - localize('mcp.autostart.send', 'Skip Waiting'), - localize('mcp.autostart.configure', 'Configure'), - ] - }, - report => { - const remaining = new Set(todo); - const doReport = () => report.report({ message: localize('mcp.autostart.progress', 'Waiting for MCP server "{0}" to start...', [...remaining].map(r => r.definition.label).join('", "')), total: todo.length, increment: 1 }); - doReport(); - return Promise.all(todo.map(async server => { - try { - await startServerAndWaitForLiveTools(server, { interaction, errorOnUserInteraction: true }, cts.token); - } catch (error) { - if (error instanceof UserInteractionRequiredError) { - serversRequiringInteraction.push({ - serverId: server.definition.id, - serverLabel: server.definition.label, - errorMessage: error.message - }); - } - } - remaining.delete(server); - doReport(); - })); - }, - btn => { - if (btn === 1) { - this.commandService.executeCommand('workbench.action.openSettings', mcpAutoStartConfig); + const requiringInteraction: (McpDefinitionReference & { errorMessage?: string })[] = []; + + const update = () => state.set({ + working: todo.size > 0, + starting: [...todo].map(t => t.definition), + serversRequiringInteraction: requiringInteraction, + }, undefined); + + await Promise.all([...todo].map(async (server, i) => { + try { + await startServerAndWaitForLiveTools(server, { interaction, errorOnUserInteraction: true }, token); + } catch (error) { + if (error instanceof UserInteractionRequiredError) { + requiringInteraction.push({ id: server.definition.id, label: server.definition.label, errorMessage: error.message }); } - cts.cancel(); - }, - ); - - cts.dispose(); + } - return { serversRequiringInteraction }; + if (!token.isCancellationRequested) { + todo.delete(server); + update(); + } + })); } public resetCaches(): void { diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index b7a900b2dcf..716cb749e94 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -199,12 +199,15 @@ export namespace McpServerDefinitionVariableReplacement { } } +/** An observable of the auto-starting servers. When 'starting' is empty, the operation is complete. */ export interface IAutostartResult { - serversRequiringInteraction: Array<{ - serverId: string; - serverLabel: string; - errorMessage?: string; - }>; + working: boolean; + starting: McpDefinitionReference[]; + serversRequiringInteraction: Array; +} + +export namespace IAutostartResult { + export const Empty: IAutostartResult = { working: false, starting: [], serversRequiringInteraction: [] }; } export interface IMcpService { @@ -221,7 +224,10 @@ export interface IMcpService { readonly lazyCollectionState: IObservable<{ state: LazyCollectionState; collections: McpCollectionDefinition[] }>; /** Auto-starts pending servers based on user settings. */ - autostart(token?: CancellationToken): Promise; + autostart(token?: CancellationToken): IObservable; + + /** Cancels any current autostart @internal */ + cancelAutostart(): void; /** Activatese extensions and runs their MCP servers. */ activateCollections(): Promise; diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts index a38c8803da1..83022603a12 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { Barrier, timeout } from '../../../../../base/common/async.js'; import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { NullCommandService } from '../../../../../platform/commands/test/common/nullCommandService.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { FileChangeType, FileSystemProviderErrorCode, FileType, IFileChange, IFileService } from '../../../../../platform/files/common/files.js'; import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; @@ -18,6 +17,7 @@ import { IStorageService } from '../../../../../platform/storage/common/storage. import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js'; import { TestContextService, TestLoggerService, TestProductService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { IMcpRegistry } from '../../common/mcpRegistryTypes.js'; import { McpResourceFilesystem } from '../../common/mcpResourceFilesystem.js'; @@ -25,7 +25,6 @@ import { McpService } from '../../common/mcpService.js'; import { IMcpService } from '../../common/mcpTypes.js'; import { MCP } from '../../common/modelContextProtocol.js'; import { TestMcpMessageTransport, TestMcpRegistry } from './mcpRegistryTypes.js'; -import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js'; suite('Workbench - MCP - ResourceFilesystem', () => { @@ -50,8 +49,7 @@ suite('Workbench - MCP - ResourceFilesystem', () => { const registry = new TestMcpRegistry(parentInsta1); const parentInsta2 = ds.add(parentInsta1.createChild(new ServiceCollection([IMcpRegistry, registry]))); - // eslint-disable-next-line local/code-no-any-casts - const mcpService = ds.add(new McpService(parentInsta2, registry, new NullLogService(), {} as any, NullCommandService, new TestConfigurationService())); + const mcpService = ds.add(new McpService(parentInsta2, registry, new NullLogService(), new TestConfigurationService())); mcpService.updateCollectedServers(); const instaService = ds.add(parentInsta2.createChild(new ServiceCollection( diff --git a/src/vs/workbench/contrib/mcp/test/common/testMcpService.ts b/src/vs/workbench/contrib/mcp/test/common/testMcpService.ts index e2b7329226e..8479338a5b3 100644 --- a/src/vs/workbench/contrib/mcp/test/common/testMcpService.ts +++ b/src/vs/workbench/contrib/mcp/test/common/testMcpService.ts @@ -16,8 +16,12 @@ export class TestMcpService implements IMcpService { } - autostart(): Promise { - return Promise.resolve({ serversRequiringInteraction: [] }); + cancelAutostart(): void { + + } + + autostart() { + return observableValue(this, { working: false, starting: [], serversRequiringInteraction: [] }); } public lazyCollectionState = observableValue(this, { state: LazyCollectionState.AllKnown, collections: [] }); From 383842fe0db0dd12792215938a36feeecf7b0756 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:12:47 -0700 Subject: [PATCH 0977/4355] Re-use TypeScriptLanguageServiceHostfor monaco and treeshaker build steps For #270408 There are currently duplicated `LanguageServiceHost` definitions in our build folder. As far as I can tell they are the same except for the default lib name As part of #270408 I'll have to touch the service hosts and having a single definition will make this easier --- build/lib/monaco-api.js | 60 +------------------ build/lib/monaco-api.ts | 66 +-------------------- build/lib/treeshaking.js | 63 +------------------- build/lib/treeshaking.ts | 68 +--------------------- build/lib/typeScriptLanguageServiceHost.js | 67 +++++++++++++++++++++ build/lib/typeScriptLanguageServiceHost.ts | 67 +++++++++++++++++++++ 6 files changed, 142 insertions(+), 249 deletions(-) create mode 100644 build/lib/typeScriptLanguageServiceHost.js create mode 100644 build/lib/typeScriptLanguageServiceHost.ts diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index ccec8702540..06e3d63029f 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -14,6 +14,7 @@ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); +const typeScriptLanguageServiceHost_1 = require("./typeScriptLanguageServiceHost"); const dtsv = '3'; const tsfmt = require('../../tsfmt.json'); const SRC = path_1.default.join(__dirname, '../../src'); @@ -554,7 +555,7 @@ class DeclarationResolver { const fileMap = new Map([ ['file.ts', fileContents] ]); - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {})); + const service = this.ts.createLanguageService(new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {}, 'defaultLib:es5')); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime); } @@ -564,63 +565,6 @@ function run3(resolver) { const sourceFileGetter = (moduleId) => resolver.getDeclarationSourceFile(moduleId); return _run(resolver.ts, sourceFileGetter); } -class TypeScriptLanguageServiceHost { - _ts; - _libs; - _files; - _compilerOptions; - constructor(ts, libs, files, compilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - // --- language service host --------------- - getCompilationSettings() { - return this._compilerOptions; - } - getScriptFileNames() { - return [ - ...this._libs.keys(), - ...this._files.keys(), - ]; - } - getScriptVersion(_fileName) { - return '1'; - } - getProjectVersion() { - return '1'; - } - getScriptSnapshot(fileName) { - if (this._files.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)); - } - else if (this._libs.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)); - } - else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName) { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory() { - return ''; - } - getDefaultLibFileName(_options) { - return 'defaultLib:es5'; - } - isDefaultLibFileName(fileName) { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path, _encoding) { - return this._files.get(path) || this._libs.get(path); - } - fileExists(path) { - return this._files.has(path) || this._libs.has(path); - } -} function execute() { const r = run3(new DeclarationResolver(new FSProvider())); if (!r) { diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 86045569f38..1978b93f2e0 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -8,6 +8,7 @@ import type * as ts from 'typescript'; import path from 'path'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; +import { IFileMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost'; const dtsv = '3'; @@ -661,7 +662,7 @@ export class DeclarationResolver { const fileMap: IFileMap = new Map([ ['file.ts', fileContents] ]); - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {})); + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {}, 'defaultLib:es5')); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry( this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), @@ -676,69 +677,6 @@ export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | } -type ILibMap = Map; -type IFileMap = Map; - -class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return [ - ...this._libs.keys(), - ...this._files.keys(), - ]; - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)!); - } else if (this._libs.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)!); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:es5'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files.get(path) || this._libs.get(path); - } - fileExists(path: string): boolean { - return this._files.has(path) || this._libs.has(path); - } -} - export function execute(): IMonacoDeclarationResult { const r = run3(new DeclarationResolver(new FSProvider())); if (!r) { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index d66b307d13b..8085f5ea02d 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -11,6 +11,7 @@ exports.shake = shake; *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); +const typeScriptLanguageServiceHost_1 = require("./typeScriptLanguageServiceHost"); const TYPESCRIPT_LIB_FOLDER = path_1.default.dirname(require.resolve('typescript/lib/lib.d.ts')); var ShakeLevel; (function (ShakeLevel) { @@ -80,7 +81,7 @@ function createTypeScriptLanguageService(ts, options) { // Resolve libs const RESOLVED_LIBS = processLibFiles(ts, options); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); + const host = new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions, 'defaultLib:lib.d.ts'); return ts.createLanguageService(host); } /** @@ -162,66 +163,6 @@ function processLibFiles(ts, options) { } return result; } -/** - * A TypeScript language service host - */ -class TypeScriptLanguageServiceHost { - _ts; - _libs; - _files; - _compilerOptions; - constructor(ts, libs, files, compilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - // --- language service host --------------- - getCompilationSettings() { - return this._compilerOptions; - } - getScriptFileNames() { - return [ - ...this._libs.keys(), - ...this._files.keys(), - ]; - } - getScriptVersion(_fileName) { - return '1'; - } - getProjectVersion() { - return '1'; - } - getScriptSnapshot(fileName) { - if (this._files.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)); - } - else if (this._libs.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)); - } - else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName) { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory() { - return ''; - } - getDefaultLibFileName(_options) { - return 'defaultLib:lib.d.ts'; - } - isDefaultLibFileName(fileName) { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path, _encoding) { - return this._files.get(path) || this._libs.get(path); - } - fileExists(path) { - return this._files.has(path) || this._libs.has(path); - } -} //#endregion //#region Tree Shaking var NodeColor; diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 7dab87bf7e8..a3424d366e6 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -6,6 +6,7 @@ import fs from 'fs'; import path from 'path'; import type * as ts from 'typescript'; +import { IFileMap, ILibMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost'; const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); @@ -128,7 +129,7 @@ function createTypeScriptLanguageService(ts: typeof import('typescript'), option const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); + const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions, 'defaultLib:lib.d.ts'); return ts.createLanguageService(host); } @@ -230,71 +231,6 @@ function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingO return result; } -type ILibMap = Map; -type IFileMap = Map; - -/** - * A TypeScript language service host - */ -class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return [ - ...this._libs.keys(), - ...this._files.keys(), - ]; - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files.get(fileName)!); - } else if (this._libs.has(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs.get(fileName)!); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:lib.d.ts'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files.get(path) || this._libs.get(path); - } - fileExists(path: string): boolean { - return this._files.has(path) || this._libs.has(path); - } -} //#endregion //#region Tree Shaking diff --git a/build/lib/typeScriptLanguageServiceHost.js b/build/lib/typeScriptLanguageServiceHost.js new file mode 100644 index 00000000000..f7e198e0a4b --- /dev/null +++ b/build/lib/typeScriptLanguageServiceHost.js @@ -0,0 +1,67 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TypeScriptLanguageServiceHost = void 0; +/** + * A TypeScript language service host + */ +class TypeScriptLanguageServiceHost { + ts; + libs; + files; + compilerOptions; + defaultLibName; + constructor(ts, libs, files, compilerOptions, defaultLibName) { + this.ts = ts; + this.libs = libs; + this.files = files; + this.compilerOptions = compilerOptions; + this.defaultLibName = defaultLibName; + } + // --- language service host --------------- + getCompilationSettings() { + return this.compilerOptions; + } + getScriptFileNames() { + return [ + ...this.libs.keys(), + ...this.files.keys(), + ]; + } + getScriptVersion(_fileName) { + return '1'; + } + getProjectVersion() { + return '1'; + } + getScriptSnapshot(fileName) { + if (this.files.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.files.get(fileName)); + } + else if (this.libs.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.libs.get(fileName)); + } + else { + return this.ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(_fileName) { + return this.ts.ScriptKind.TS; + } + getCurrentDirectory() { + return ''; + } + getDefaultLibFileName(_options) { + return this.defaultLibName; + } + isDefaultLibFileName(fileName) { + return fileName === this.getDefaultLibFileName(this.compilerOptions); + } + readFile(path, _encoding) { + return this.files.get(path) || this.libs.get(path); + } + fileExists(path) { + return this.files.has(path) || this.libs.has(path); + } +} +exports.TypeScriptLanguageServiceHost = TypeScriptLanguageServiceHost; +//# sourceMappingURL=typeScriptLanguageServiceHost.js.map \ No newline at end of file diff --git a/build/lib/typeScriptLanguageServiceHost.ts b/build/lib/typeScriptLanguageServiceHost.ts new file mode 100644 index 00000000000..c6d20f0f32b --- /dev/null +++ b/build/lib/typeScriptLanguageServiceHost.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import ts from 'typescript'; + +export type ILibMap = Map; +export type IFileMap = Map; + +/** + * A TypeScript language service host + */ +export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { + + constructor( + private readonly ts: typeof import('typescript'), + private readonly libs: ILibMap, + private readonly files: IFileMap, + private readonly compilerOptions: ts.CompilerOptions, + private readonly defaultLibName: string, + ) { } + + // --- language service host --------------- + getCompilationSettings(): ts.CompilerOptions { + return this.compilerOptions; + } + getScriptFileNames(): string[] { + return [ + ...this.libs.keys(), + ...this.files.keys(), + ]; + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + if (this.files.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.files.get(fileName)!); + } else if (this.libs.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.libs.get(fileName)!); + } else { + return this.ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(_fileName: string): ts.ScriptKind { + return this.ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(_options: ts.CompilerOptions): string { + return this.defaultLibName; + } + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this.compilerOptions); + } + readFile(path: string, _encoding?: string): string | undefined { + return this.files.get(path) || this.libs.get(path); + } + fileExists(path: string): boolean { + return this.files.has(path) || this.libs.has(path); + } +} From 004897d31b8c85603d44b7169d7339e1b3dd9129 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 8 Oct 2025 13:19:04 -0400 Subject: [PATCH 0978/4355] fix abstract task service --- .../tasks/browser/abstractTaskService.ts | 18 +++++++++--------- src/vs/workbench/contrib/tasks/common/tasks.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 18c713292d8..8f63795e8b4 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -47,7 +47,7 @@ import { ITextFileService } from '../../../services/textfile/common/textfiles.js import { ITerminalGroupService, ITerminalService } from '../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../terminal/common/terminal.js'; -import { ConfiguringTask, ContributedTask, CustomTask, ExecutionEngine, InMemoryTask, InstancePolicy, ITaskEvent, ITaskIdentifier, ITaskInactiveEvent, ITaskProcessEndedEvent, ITaskSet, JsonSchemaVersion, KeyedTaskIdentifier, RerunAllRunningTasksCommandId, RuntimeType, Task, TASK_RUNNING_STATE, TaskDefinition, TaskEventKind, TaskGroup, TaskRunSource, TaskSettingId, TaskSorter, TaskSourceKind, TasksSchemaProperties, USER_TASKS_GROUP_KEY } from '../common/tasks.js'; +import { ConfiguringTask, ContributedTask, CustomTask, ExecutionEngine, InMemoryTask, InstancePolicy, ITaskConfig, ITaskEvent, ITaskIdentifier, ITaskInactiveEvent, ITaskProcessEndedEvent, ITaskSet, JsonSchemaVersion, KeyedTaskIdentifier, RerunAllRunningTasksCommandId, RuntimeType, Task, TASK_RUNNING_STATE, TaskDefinition, TaskEventKind, TaskGroup, TaskRunSource, TaskSettingId, TaskSorter, TaskSourceKind, TasksSchemaProperties, USER_TASKS_GROUP_KEY } from '../common/tasks.js'; import { ChatAgentLocation, ChatModeKind } from '../../chat/common/constants.js'; import { CustomExecutionSupportedContext, ICustomizationProperties, IProblemMatcherRunOptions, ITaskFilter, ITaskProvider, ITaskService, IWorkspaceFolderTaskResult, ProcessExecutionSupportedContext, ServerlessWebContext, ShellExecutionSupportedContext, TaskCommandsRegistered, TaskExecutionSupportedContext, TasksAvailableContext } from '../common/taskService.js'; import { ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers, VerifiedTask } from '../common/taskSystem.js'; @@ -590,7 +590,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private async _registerCommands(): Promise { CommandsRegistry.registerCommand({ id: 'workbench.action.tasks.runTask', - handler: async (accessor, arg) => { + handler: async (accessor, arg?: string | ITaskIdentifier) => { if (await this._trust()) { await this._runTaskCommand(arg); } @@ -626,25 +626,25 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); - CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', async (accessor, arg) => { + CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', async (accessor) => { if (await this._trust()) { this._reRunTaskCommand(); } }); - CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', async (accessor, arg) => { + CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', async (accessor, arg?: string | ITaskIdentifier) => { if (await this._trust()) { this._runRestartTaskCommand(arg); } }); - CommandsRegistry.registerCommand(RerunAllRunningTasksCommandId, async (accessor, arg) => { + CommandsRegistry.registerCommand(RerunAllRunningTasksCommandId, async (accessor) => { if (await this._trust()) { this._runRerunAllRunningTasksCommand(); } }); - CommandsRegistry.registerCommand('workbench.action.tasks.terminate', async (accessor, arg) => { + CommandsRegistry.registerCommand('workbench.action.tasks.terminate', async (accessor, arg?: string | ITaskIdentifier) => { if (await this._trust()) { this._runTerminateCommand(arg); } @@ -3397,7 +3397,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }, this._runConfigureDefaultTestTask, this._runTest); } - private _runTerminateCommand(arg?: any): void { + private _runTerminateCommand(arg?: string | ITaskIdentifier): void { if (arg === 'terminateAll') { this._terminateAll(); return; @@ -3464,7 +3464,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private async _runRestartTaskCommand(arg?: any): Promise { + private async _runRestartTaskCommand(arg?: string | ITaskIdentifier): Promise { const activeTasks = await this.getActiveTasks(); @@ -3924,7 +3924,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!CustomTask.is(task)) { return; } - const configElement: any = { + const configElement: ITaskConfig = { label: task._label }; const oldTaskTypes = new Set(['gulp', 'jake', 'grunt']); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index c4fcc2dfd54..035f4b820fe 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -439,6 +439,18 @@ export interface ITaskSourceConfigElement { element: any; } +export interface ITaskConfig { + label: string; + task?: CommandString; + type?: string; + command?: string | CommandString; + args?: string[] | CommandString[]; + presentation?: IPresentationOptions; + isBackground?: boolean; + problemMatcher?: string | string[]; + group?: string | TaskGroup; +} + interface IBaseTaskSource { readonly kind: string; readonly label: string; From 77ed2193662eb70ffabebef4e3c1018b3a4f1db0 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:22:21 -0700 Subject: [PATCH 0979/4355] Convert some `any -> unknown` in build scripts --- build/azure-pipelines/common/publish.ts | 6 +++--- build/azure-pipelines/distro/mixin-npm.ts | 2 +- build/azure-pipelines/distro/mixin-quality.ts | 2 +- build/azure-pipelines/upload-nlsmetadata.ts | 2 +- build/azure-pipelines/upload-sourcemaps.ts | 3 ++- build/lib/compilation.ts | 2 +- build/lib/i18n.ts | 6 +++--- build/lib/monaco-api.ts | 2 +- build/lib/tsb/transpiler.ts | 2 +- build/lib/tsb/utils.ts | 2 +- build/lib/typings/event-stream.d.ts | 4 ++-- 11 files changed, 17 insertions(+), 16 deletions(-) diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 7ff87dbee89..e8a6776ceb1 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -315,7 +315,7 @@ function getCertificatesFromPFX(pfx: string): string[] { class ESRPReleaseService { static async create( - log: (...args: any[]) => void, + log: (...args: unknown[]) => void, tenantId: string, clientId: string, authCertificatePfx: string, @@ -350,7 +350,7 @@ class ESRPReleaseService { private static API_URL = 'https://api.esrp.microsoft.com/api/v3/releaseservices/clients/'; private constructor( - private readonly log: (...args: any[]) => void, + private readonly log: (...args: unknown[]) => void, private readonly clientId: string, private readonly accessToken: string, private readonly requestSigningCertificates: string[], @@ -848,7 +848,7 @@ async function processArtifact( artifact: Artifact, filePath: string ) { - const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); + const log = (...args: unknown[]) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { diff --git a/build/azure-pipelines/distro/mixin-npm.ts b/build/azure-pipelines/distro/mixin-npm.ts index 6e32f10db50..f98f6e6b55d 100644 --- a/build/azure-pipelines/distro/mixin-npm.ts +++ b/build/azure-pipelines/distro/mixin-npm.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import path from 'path'; const { dirs } = require('../../npm/dirs') as { dirs: string[] }; -function log(...args: any[]): void { +function log(...args: unknown[]): void { console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } diff --git a/build/azure-pipelines/distro/mixin-quality.ts b/build/azure-pipelines/distro/mixin-quality.ts index 29c90f00a65..c8ed6886b79 100644 --- a/build/azure-pipelines/distro/mixin-quality.ts +++ b/build/azure-pipelines/distro/mixin-quality.ts @@ -23,7 +23,7 @@ interface Product { readonly webBuiltInExtensions?: IBuiltInExtension[]; } -function log(...args: any[]): void { +function log(...args: unknown[]): void { console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 1a4f2665617..468a9341a7b 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -134,7 +134,7 @@ function main(): Promise { } })) .on('end', () => c()) - .on('error', (err: any) => e(err)); + .on('error', (err: unknown) => e(err)); }); } diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index 0c51827fef4..612a57c9da2 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -10,6 +10,7 @@ import vfs from 'vinyl-fs'; import * as util from '../lib/util'; import { getProductionDependencies } from '../lib/dependencies'; import { ClientAssertionCredential } from '@azure/identity'; +import Stream from 'stream'; const azure = require('gulp-azure-storage'); const root = path.dirname(path.dirname(__dirname)); @@ -28,7 +29,7 @@ function src(base: string, maps = `${base}/**/*.map`) { } function main(): Promise { - const sources: any[] = []; + const sources: Stream[] = []; // vscode client maps (default) if (!base) { diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index d0c54b76b7c..a8b72914925 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -249,7 +249,7 @@ class MonacoGenerator { return r; } - private _log(message: any, ...rest: any[]): void { + private _log(message: any, ...rest: unknown[]): void { fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); } diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 13cefe51dad..b4766f725b7 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -18,7 +18,7 @@ import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL1 const REPO_ROOT_PATH = path.join(__dirname, '../..'); -function log(message: any, ...rest: any[]): void { +function log(message: any, ...rest: unknown[]): void { fancyLog(ansiColors.green('[i18n]'), message, ...rest); } @@ -68,7 +68,7 @@ interface LocalizeInfo { } module LocalizeInfo { - export function is(value: any): value is LocalizeInfo { + export function is(value: unknown): value is LocalizeInfo { const candidate = value as LocalizeInfo; return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); } @@ -744,7 +744,7 @@ export function prepareI18nPackFiles(resultingTranslationPaths: TranslationPath[ const parsePromises: Promise[] = []; const mainPack: I18nPack = { version: i18nPackVersion, contents: {} }; const extensionsPacks: Record = {}; - const errors: any[] = []; + const errors: unknown[] = []; return through(function (this: ThroughStream, xlf: File) { let project = path.basename(path.dirname(path.dirname(xlf.relative))); // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 86045569f38..2dfe52558aa 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -17,7 +17,7 @@ const SRC = path.join(__dirname, '../../src'); export const RECIPE_PATH = path.join(__dirname, '../monaco/monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); -function logErr(message: any, ...rest: any[]): void { +function logErr(message: any, ...rest: unknown[]): void { fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); } diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 16a3b347538..0d8d5fdf821 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -200,7 +200,7 @@ export class TscTranspiler implements ITranspiler { private _workerPool: TranspileWorker[] = []; private _queue: Vinyl[] = []; - private _allJobs: Promise[] = []; + private _allJobs: Promise[] = []; constructor( logFn: (topic: string, message: string) => void, diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts index 16f93d6838f..7f0bbdd5f23 100644 --- a/build/lib/tsb/utils.ts +++ b/build/lib/tsb/utils.ts @@ -5,7 +5,7 @@ export namespace strings { - export function format(value: string, ...rest: any[]): string { + export function format(value: string, ...rest: unknown[]): string { return value.replace(/({\d+})/g, function (match) { const index = Number(match.substring(1, match.length - 1)); return String(rest[index]) || match; diff --git a/build/lib/typings/event-stream.d.ts b/build/lib/typings/event-stream.d.ts index 2b021ef258e..2b9679bfc82 100644 --- a/build/lib/typings/event-stream.d.ts +++ b/build/lib/typings/event-stream.d.ts @@ -23,5 +23,5 @@ declare module "event-stream" { function mapSync(cb: (data: I) => O): ThroughStream; function map(cb: (data: I, cb: (err?: Error, data?: O) => void) => O): ThroughStream; - function readable(asyncFunction: (this: ThroughStream, ...args: any[]) => any): any; -} \ No newline at end of file + function readable(asyncFunction: (this: ThroughStream, ...args: unknown[]) => any): any; +} From 3680ac1371899fa8becb9573b00d7092dac074d1 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 8 Oct 2025 11:03:35 -0700 Subject: [PATCH 0980/4355] Use the existing ModifierKeyEmitter to check modifier keys --- src/vs/base/browser/keyboardEvent.ts | 7 ------- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index e7e2b682735..4e98a3b12d8 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -114,13 +114,6 @@ export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string { return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`; } -/** - * Checks if a keyboard event has any modifier keys pressed - */ -export function hasAnyModifierKeys(e: IKeyboardEvent): boolean { - return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey; -} - export class StandardKeyboardEvent implements IKeyboardEvent { readonly _standardKeyboardEventBrand = true; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 641c1ae78fc..3491bb03c06 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; -import { addDisposableListener } from '../../../../base/browser/dom.js'; +import { addDisposableListener, ModifierKeyEmitter } from '../../../../base/browser/dom.js'; import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; -import { hasAnyModifierKeys, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/button.js'; @@ -1203,7 +1203,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // We need to prevent Monaco's default Enter behavior while still allowing the VS Code keybinding service // to receive and process the Enter key for ChatSubmitAction this._register(this._inputEditor.onKeyDown((e) => { - if (e.keyCode === KeyCode.Enter && !hasAnyModifierKeys(e)) { + if (e.keyCode === KeyCode.Enter && !ModifierKeyEmitter.getInstance().isModifierPressed) { // Only prevent the default Monaco behavior (newline insertion) // Do NOT call stopPropagation() so the keybinding service can still process this event e.preventDefault(); From a5da191779887f061f85205121790acb27802dae Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:13:36 -0700 Subject: [PATCH 0981/4355] Use param expansion with default to reduce warning in set -u nounset mode (#270310) Use parameter expansion with default to reduce warning in set -u nounset mode --- .../terminal/common/scripts/shellIntegration-bash.sh | 8 ++++---- .../terminal/common/scripts/shellIntegration-rc.zsh | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh index be8afcefd0d..8ec742e124f 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh @@ -15,7 +15,7 @@ vsc_env_values=() use_associative_array=0 bash_major_version=${BASH_VERSINFO[0]} -__vscode_shell_env_reporting="$VSCODE_SHELL_ENV_REPORTING" +__vscode_shell_env_reporting="${VSCODE_SHELL_ENV_REPORTING:-}" unset VSCODE_SHELL_ENV_REPORTING envVarsToReport=() @@ -93,9 +93,9 @@ fi # Register Python shell activate hooks # Prevent multiple activation with guard -if [ -z "$VSCODE_PYTHON_AUTOACTIVATE_GUARD" ]; then +if [ -z "${VSCODE_PYTHON_AUTOACTIVATE_GUARD:-}" ]; then export VSCODE_PYTHON_AUTOACTIVATE_GUARD=1 - if [ -n "$VSCODE_PYTHON_BASH_ACTIVATE" ] && [ "$TERM_PROGRAM" = "vscode" ]; then + if [ -n "${VSCODE_PYTHON_BASH_ACTIVATE:-}" ] && [ "$TERM_PROGRAM" = "vscode" ]; then # Prevent crashing by negating exit code if ! builtin eval "$VSCODE_PYTHON_BASH_ACTIVATE"; then __vsc_activation_status=$? @@ -184,7 +184,7 @@ fi # Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL # configuration is used __vsc_regex_histcontrol=".*(erasedups|ignoreboth|ignoredups).*" -if [[ "$HISTCONTROL" =~ $__vsc_regex_histcontrol ]]; then +if [[ "${HISTCONTROL:-}" =~ $__vsc_regex_histcontrol ]]; then __vsc_history_verify=0 else __vsc_history_verify=1 diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh index 5745c53f3e2..dc52dd8c7f9 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh @@ -70,9 +70,9 @@ fi # Register Python shell activate hooks # Prevent multiple activation with guard -if [ -z "$VSCODE_PYTHON_AUTOACTIVATE_GUARD" ]; then +if [ -z "${VSCODE_PYTHON_AUTOACTIVATE_GUARD:-}" ]; then export VSCODE_PYTHON_AUTOACTIVATE_GUARD=1 - if [ -n "$VSCODE_PYTHON_ZSH_ACTIVATE" ] && [ "$TERM_PROGRAM" = "vscode" ]; then + if [ -n "${VSCODE_PYTHON_ZSH_ACTIVATE:-}" ] && [ "$TERM_PROGRAM" = "vscode" ]; then # Prevent crashing by negating exit code if ! builtin eval "$VSCODE_PYTHON_ZSH_ACTIVATE"; then __vsc_activation_status=$? @@ -82,11 +82,11 @@ if [ -z "$VSCODE_PYTHON_AUTOACTIVATE_GUARD" ]; then fi # Report prompt type -if [ -n "$P9K_SSH" ] || [ -n "$P9K_TTY" ]; then +if [ -n "${P9K_SSH:-}" ] || [ -n "${P9K_TTY:-}" ]; then builtin printf '\e]633;P;PromptType=p10k\a' -elif [ -n "$ZSH" ] && [ -n "$ZSH_VERSION" ] && (( ${+functions[omz]} )); then +elif [ -n "${ZSH:-}" ] && [ -n "$ZSH_VERSION" ] && (( ${+functions[omz]} )); then builtin printf '\e]633;P;PromptType=oh-my-zsh\a' -elif [ -n "$STARSHIP_SESSION_KEY" ]; then +elif [ -n "${STARSHIP_SESSION_KEY:-}" ]; then builtin printf '\e]633;P;PromptType=starship\a' fi @@ -132,7 +132,7 @@ __vsc_current_command="" __vsc_nonce="$VSCODE_NONCE" unset VSCODE_NONCE -__vscode_shell_env_reporting="$VSCODE_SHELL_ENV_REPORTING" +__vscode_shell_env_reporting="${VSCODE_SHELL_ENV_REPORTING:-}" unset VSCODE_SHELL_ENV_REPORTING envVarsToReport=() From 43305ef182fcbda4ffe022da5b87c49c5b38d0a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 8 Oct 2025 20:41:56 +0200 Subject: [PATCH 0982/4355] fix #270399 (#270402) * fix #270399 * fix format --- .../common/extensionGalleryService.ts | 169 +++++------- .../common/extensionGalleryService.test.ts | 250 +++++++++++++++++- .../common/extensionGalleryService.ts | 4 +- 3 files changed, 321 insertions(+), 102 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 87fe5f5f2c7..c215865a168 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -29,7 +29,6 @@ import { IStorageService } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { StopWatch } from '../../../base/common/stopwatch.js'; import { format2 } from '../../../base/common/strings.js'; -import { IAssignmentService } from '../../assignment/common/assignment.js'; import { ExtensionGalleryResourceType, Flag, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from './extensionGalleryManifest.js'; import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js'; @@ -444,6 +443,38 @@ export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], p return versions; } +export function filterLatestExtensionVersionsForTargetPlatform(versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[]): IRawGalleryExtensionVersion[] { + const latestVersions: IRawGalleryExtensionVersion[] = []; + + let preReleaseVersionFoundForTargetPlatform: boolean = false; + let releaseVersionFoundForTargetPlatform: boolean = false; + for (const version of versions) { + const versionTargetPlatform = getTargetPlatformForExtensionVersion(version); + const isCompatibleWithTargetPlatform = isTargetPlatformCompatible(versionTargetPlatform, allTargetPlatforms, targetPlatform); + + // Always include versions that are NOT compatible with the target platform + if (!isCompatibleWithTargetPlatform) { + latestVersions.push(version); + continue; + } + + // For compatible versions, only include the first (latest) of each type + if (isPreReleaseVersion(version)) { + if (!preReleaseVersionFoundForTargetPlatform) { + preReleaseVersionFoundForTargetPlatform = true; + latestVersions.push(version); + } + } else { + if (!releaseVersionFoundForTargetPlatform) { + releaseVersionFoundForTargetPlatform = true; + latestVersions.push(version); + } + } + } + + return latestVersions; +} + function setTelemetry(extension: IGalleryExtension, index: number, querySource?: string): void { /* __GDPR__FRAGMENT__ "GalleryExtensionTelemetryData2" : { @@ -555,7 +586,6 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle constructor( storageService: IStorageService | undefined, - private readonly assignmentService: IAssignmentService | undefined, @IRequestService private readonly requestService: IRequestService, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -714,17 +744,33 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle } await Promise.all(toFetchLatest.map(async extensionInfo => { - let galleryExtension: IGalleryExtension | null | 'NOT_FOUND'; + let galleryExtension: IGalleryExtension | string; try { galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token); - if (galleryExtension === 'NOT_FOUND') { - if (extensionInfo.uuid) { - // Fallback to query if extension with UUID is not found. Probably extension is renamed. - toQuery.push(extensionInfo); - } - return; - } - if (galleryExtension) { + if (isString(galleryExtension)) { + // fallback to query + this.telemetryService.publicLog2< + { + extension: string; + preRelease: boolean; + compatible: boolean; + errorCode: string; + }, + { + owner: 'sandy081'; + comment: 'Report the fallback to the Marketplace query for fetching extensions'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' }; + preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' }; + compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' }; + errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' }; + }>('galleryService:fallbacktoquery', { + extension: extensionInfo.id, + preRelease: !!extensionInfo.preRelease, + compatible: !!options.compatible, + errorCode: galleryExtension + }); + toQuery.push(extensionInfo); + } else { result.push(galleryExtension); } } catch (error) { @@ -772,18 +818,20 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle return result; } - private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise { + private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise { const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token); if (!rawGalleryExtension) { return 'NOT_FOUND'; } + const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM; const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension); - const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion( + const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion( rawGalleryExtension, + filterLatestExtensionVersionsForTargetPlatform(rawGalleryExtension.versions, targetPlatform, allTargetPlatforms), { - targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, + targetPlatform, compatible: !!options.compatible, productVersion: options.productVersion ?? { version: this.productService.version, @@ -796,7 +844,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService); } - return null; + return 'NOT_COMPATIBLE'; } async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { @@ -1022,17 +1070,6 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle } private async queryGalleryExtensions(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> { - if ( - this.productService.quality !== 'stable' - && (await this.assignmentService?.getTreatment('useLatestPrereleaseAndStableVersionFlag')) - ) { - return this.queryGalleryExtensionsUsingIncludeLatestPrereleaseAndStableVersionFlag(query, criteria, extensionGalleryManifest, token); - } - - return this.queryGalleryExtensionsWithAllVersionsAsFallback(query, criteria, extensionGalleryManifest, token); - } - - private async queryGalleryExtensionsWithAllVersionsAsFallback(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> { const flags = query.flags; /** @@ -1069,8 +1106,9 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension); const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId }; const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease; - const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion( + const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion( rawGalleryExtension, + rawGalleryExtension.versions, { compatible: criteria.compatible, targetPlatform: criteria.targetPlatform, @@ -1104,8 +1142,9 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle continue; } } - const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion( + const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion( rawGalleryExtension, + rawGalleryExtension.versions, { compatible: criteria.compatible, targetPlatform: criteria.targetPlatform, @@ -1156,75 +1195,9 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total }; } - private async queryGalleryExtensionsUsingIncludeLatestPrereleaseAndStableVersionFlag(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> { - - /** - * If versions criteria exist, then remove latest flags and add all versions flag. - */ - if (criteria.versions?.length) { - query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly && flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeVersions); - } - - /** - * If the query does not specify all versions flag, handle latest versions. - */ - else if (!query.flags.includes(Flag.IncludeVersions)) { - const includeLatest = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : criteria.includePreRelease.every(({ includePreRelease }) => includePreRelease); - query = includeLatest ? query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeLatestVersionOnly) : query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeLatestPrereleaseAndStableVersionOnly); - } - - /** - * If all versions flag is set, remove latest flags. - */ - if (query.flags.includes(Flag.IncludeVersions) && (query.flags.includes(Flag.IncludeLatestVersionOnly) || query.flags.includes(Flag.IncludeLatestPrereleaseAndStableVersionOnly))) { - query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly && flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeVersions); - } - - /** - * Add necessary extension flags - */ - query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties); - const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token); - - const extensions: IGalleryExtension[] = []; - for (let index = 0; index < rawGalleryExtensions.length; index++) { - const rawGalleryExtension = rawGalleryExtensions[index]; - const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId }; - const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension); - if (criteria.compatible) { - // Skip looking for all versions if requested for a web-compatible extension and it is not a web extension. - if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) { - continue; - } - // Skip looking for all versions if the extension is not allowed. - if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) { - continue; - } - } - - const version = criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version - ?? ((isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease) ? VersionKind.Latest : VersionKind.Release); - const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion( - rawGalleryExtension, - { - compatible: criteria.compatible, - targetPlatform: criteria.targetPlatform, - productVersion: criteria.productVersion, - version - }, - allTargetPlatforms - ); - if (rawGalleryExtensionVersion) { - extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context)); - } - } - - return { extensions, total }; - } - - private async getRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise { + private async getValidRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise { const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId }; - const rawGalleryExtensionVersions = sortExtensionVersions(rawGalleryExtension.versions, criteria.targetPlatform); + const rawGalleryExtensionVersions = sortExtensionVersions(versions, criteria.targetPlatform); if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) { return null; @@ -1905,7 +1878,7 @@ export class ExtensionGalleryService extends AbstractExtensionGalleryService { @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, @IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService, ) { - super(storageService, undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); + super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); } } @@ -1922,6 +1895,6 @@ export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensi @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, @IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService, ) { - super(undefined, undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); + super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); } } diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 3fa0d8a0f4d..5675146a9cd 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -11,7 +11,7 @@ import { mock } from '../../../../base/test/common/mock.js'; import { IConfigurationService } from '../../../configuration/common/configuration.js'; import { TestConfigurationService } from '../../../configuration/test/common/testConfigurationService.js'; import { IEnvironmentService } from '../../../environment/common/environment.js'; -import { IRawGalleryExtensionVersion, sortExtensionVersions } from '../../common/extensionGalleryService.js'; +import { IRawGalleryExtensionVersion, sortExtensionVersions, filterLatestExtensionVersionsForTargetPlatform } from '../../common/extensionGalleryService.js'; import { IFileService } from '../../../files/common/files.js'; import { FileService } from '../../../files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; @@ -110,4 +110,252 @@ suite('Extension Gallery Service', () => { function aExtensionVersion(version: string, targetPlatform?: TargetPlatform): IRawGalleryExtensionVersion { return { version, targetPlatform } as IRawGalleryExtensionVersion; } + + function aPreReleaseExtensionVersion(version: string, targetPlatform?: TargetPlatform): IRawGalleryExtensionVersion { + return { + version, + targetPlatform, + properties: [{ key: 'Microsoft.VisualStudio.Code.PreRelease', value: 'true' }] + } as IRawGalleryExtensionVersion; + } + + suite('filterLatestExtensionVersionsForTargetPlatform', () => { + + test('should return empty array for empty input', () => { + const result = filterLatestExtensionVersionsForTargetPlatform([], TargetPlatform.WIN32_X64, [TargetPlatform.WIN32_X64]); + assert.deepStrictEqual(result, []); + }); + + test('should return single version when only one version provided', () => { + const versions = [aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64)]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64]; + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + assert.deepStrictEqual(result, versions); + }); + + test('should filter out duplicate target platforms for release versions', () => { + const version1 = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const version2 = aExtensionVersion('0.9.0', TargetPlatform.WIN32_X64); // Same platform, older version + const versions = [version1, version2]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should only include the first version (latest) for this platform + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0], version1); + }); + + test('should include one version per target platform for release versions', () => { + const version1 = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const version2 = aExtensionVersion('1.0.0', TargetPlatform.DARWIN_X64); + const version3 = aExtensionVersion('1.0.0', TargetPlatform.LINUX_X64); + const versions = [version1, version2, version3]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64, TargetPlatform.LINUX_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should include all three versions: WIN32_X64 (compatible, first of type) + DARWIN_X64 & LINUX_X64 (non-compatible) + assert.strictEqual(result.length, 3); + assert.ok(result.includes(version1)); // Compatible with target platform + assert.ok(result.includes(version2)); // Non-compatible, included + assert.ok(result.includes(version3)); // Non-compatible, included + }); + + test('should separate release and pre-release versions', () => { + const releaseVersion = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const preReleaseVersion = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64); + const versions = [releaseVersion, preReleaseVersion]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should include both since they are different types (release vs pre-release) + assert.strictEqual(result.length, 2); + assert.ok(result.includes(releaseVersion)); + assert.ok(result.includes(preReleaseVersion)); + }); + + test('should filter duplicate pre-release versions by target platform', () => { + const preRelease1 = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64); + const preRelease2 = aPreReleaseExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); // Same platform, older + const versions = [preRelease1, preRelease2]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should only include the first pre-release version for this platform + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0], preRelease1); + }); + + test('should handle versions without target platform (UNDEFINED)', () => { + const version1 = aExtensionVersion('1.0.0'); // No target platform specified + const version2 = aExtensionVersion('0.9.0'); // No target platform specified + const versions = [version1, version2]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should only include the first version since they both have UNDEFINED platform + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0], version1); + }); + + test('should handle mixed release and pre-release versions across multiple platforms', () => { + const releaseWin = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const releaseMac = aExtensionVersion('1.0.0', TargetPlatform.DARWIN_X64); + const preReleaseWin = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.WIN32_X64); + const preReleaseMac = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.DARWIN_X64); + const oldReleaseWin = aExtensionVersion('0.9.0', TargetPlatform.WIN32_X64); // Should be filtered out + + const versions = [releaseWin, releaseMac, preReleaseWin, preReleaseMac, oldReleaseWin]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should include: WIN32_X64 compatible (release + prerelease) + DARWIN_X64 non-compatible (all versions) + assert.strictEqual(result.length, 4); + assert.ok(result.includes(releaseWin)); // Compatible release + assert.ok(result.includes(releaseMac)); // Non-compatible, included + assert.ok(result.includes(preReleaseWin)); // Compatible pre-release + assert.ok(result.includes(preReleaseMac)); // Non-compatible, included + assert.ok(!result.includes(oldReleaseWin)); // Filtered (older compatible release) + }); + + test('should handle complex scenario with multiple versions and platforms', () => { + const versions = [ + aExtensionVersion('2.0.0', TargetPlatform.WIN32_X64), + aExtensionVersion('2.0.0', TargetPlatform.DARWIN_X64), + aExtensionVersion('1.9.0', TargetPlatform.WIN32_X64), // Older release, same platform + aPreReleaseExtensionVersion('2.1.0', TargetPlatform.WIN32_X64), + aPreReleaseExtensionVersion('2.0.5', TargetPlatform.WIN32_X64), // Older pre-release, same platform + aPreReleaseExtensionVersion('2.1.0', TargetPlatform.LINUX_X64), + aExtensionVersion('2.0.0'), // No platform specified + aPreReleaseExtensionVersion('2.1.0'), // Pre-release, no platform specified + ]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64, TargetPlatform.LINUX_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Expected for WIN32_X64 target platform: + // - Compatible (WIN32_X64 + UNDEFINED): Only first release and first pre-release + // - Non-compatible: DARWIN_X64 release, LINUX_X64 pre-release + // Total: 4 versions (1 compatible release + 1 compatible pre-release + 2 non-compatible) + assert.strictEqual(result.length, 4); + + // Check specific versions are included + assert.ok(result.includes(versions[0])); // 2.0.0 WIN32_X64 (first compatible release) + assert.ok(result.includes(versions[1])); // 2.0.0 DARWIN_X64 (non-compatible) + assert.ok(result.includes(versions[3])); // 2.1.0 WIN32_X64 (first compatible pre-release) + assert.ok(result.includes(versions[5])); // 2.1.0 LINUX_X64 (non-compatible) + }); + + test('should handle UNDEFINED platform interaction with specific platforms', () => { + // Test how UNDEFINED platform interacts with specific platforms + const versions = [ + aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64), + aExtensionVersion('1.0.0'), // UNDEFINED platform - compatible with all + ]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Both are compatible with WIN32_X64, but only the first of each type should be included + // Since both are release versions, only the first one should be included + assert.strictEqual(result.length, 1); + assert.ok(result.includes(versions[0])); // WIN32_X64 should be included (first release) + }); + + test('should handle higher version with specific platform vs lower version with universal platform', () => { + // Scenario: newer version for specific platform vs older version with universal compatibility + const higherVersionSpecificPlatform = aExtensionVersion('2.0.0', TargetPlatform.WIN32_X64); + const lowerVersionUniversal = aExtensionVersion('1.5.0'); // UNDEFINED/universal platform + + const versions = [higherVersionSpecificPlatform, lowerVersionUniversal]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Both are compatible with WIN32_X64, but only the first release version should be included + assert.strictEqual(result.length, 1); + assert.ok(result.includes(higherVersionSpecificPlatform)); // First compatible release + assert.ok(!result.includes(lowerVersionUniversal)); // Filtered (second compatible release) + }); + + test('should handle lower version with specific platform vs higher version with universal platform', () => { + // Reverse scenario: older version for specific platform vs newer version with universal compatibility + const lowerVersionSpecificPlatform = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const higherVersionUniversal = aExtensionVersion('2.0.0'); // UNDEFINED/universal platform + + const versions = [lowerVersionSpecificPlatform, higherVersionUniversal]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Both are compatible with WIN32_X64, but only the first release version should be included + assert.strictEqual(result.length, 1); + assert.ok(result.includes(lowerVersionSpecificPlatform)); // First compatible release + assert.ok(!result.includes(higherVersionUniversal)); // Filtered (second compatible release) + }); + + test('should handle multiple specific platforms vs universal platform with version differences', () => { + // Complex scenario with multiple platforms and universal compatibility + const versions = [ + aExtensionVersion('2.0.0', TargetPlatform.WIN32_X64), // Highest version, specific platform + aExtensionVersion('1.9.0', TargetPlatform.DARWIN_X64), // Lower version, different specific platform + aExtensionVersion('1.8.0'), // Lowest version, universal platform + aExtensionVersion('1.7.0', TargetPlatform.WIN32_X64), // Even older, same platform as first - should be filtered + ]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64, TargetPlatform.LINUX_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Should include: + // - 2.0.0 WIN32_X64 (first compatible release for WIN32_X64) + // - 1.9.0 DARWIN_X64 (non-compatible, included) + // - 1.8.0 UNDEFINED (second compatible release, filtered) + // Should NOT include: + // - 1.7.0 WIN32_X64 (third compatible release, filtered) + assert.strictEqual(result.length, 2); + assert.ok(result.includes(versions[0])); // 2.0.0 WIN32_X64 + assert.ok(result.includes(versions[1])); // 1.9.0 DARWIN_X64 + assert.ok(!result.includes(versions[2])); // 1.8.0 UNDEFINED should be filtered + assert.ok(!result.includes(versions[3])); // 1.7.0 WIN32_X64 should be filtered + }); + + test('should include universal platform when no specific platforms conflict', () => { + // Test where universal platform is included because no specific platforms conflict + const universalVersion = aExtensionVersion('1.0.0'); // UNDEFINED/universal platform + const specificVersion = aExtensionVersion('1.0.0', TargetPlatform.LINUX_ARM64); + + const versions = [universalVersion, specificVersion]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64]; // Note: LINUX_ARM64 not in target platforms + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // Universal is compatible with WIN32_X64, specific version is not compatible + // So we should get: universal (first compatible release) + specific (non-compatible) + assert.strictEqual(result.length, 2); + assert.ok(result.includes(universalVersion)); // Compatible with WIN32_X64 + assert.ok(result.includes(specificVersion)); // Non-compatible, included + }); + + test('should preserve order of input when no filtering occurs', () => { + const version1 = aExtensionVersion('1.0.0', TargetPlatform.WIN32_X64); + const version2 = aExtensionVersion('1.0.0', TargetPlatform.DARWIN_X64); + const version3 = aPreReleaseExtensionVersion('1.1.0', TargetPlatform.LINUX_X64); + const versions = [version1, version2, version3]; + const allTargetPlatforms = [TargetPlatform.WIN32_X64, TargetPlatform.DARWIN_X64, TargetPlatform.LINUX_X64]; + + const result = filterLatestExtensionVersionsForTargetPlatform(versions, TargetPlatform.WIN32_X64, allTargetPlatforms); + + // For WIN32_X64 target: version1 (compatible release) + version2, version3 (non-compatible) + assert.strictEqual(result.length, 3); + assert.ok(result.includes(version1)); // Compatible release + assert.ok(result.includes(version2)); // Non-compatible, included + assert.ok(result.includes(version3)); // Non-compatible, included + }); + + }); }); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionGalleryService.ts b/src/vs/workbench/services/extensionManagement/common/extensionGalleryService.ts index 00e133b6d03..5b4ece05a57 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionGalleryService.ts @@ -13,14 +13,12 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { IRequestService } from '../../../../platform/request/common/request.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { AbstractExtensionGalleryService } from '../../../../platform/extensionManagement/common/extensionGalleryService.js'; -import { IWorkbenchAssignmentService } from '../../assignment/common/assignmentService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js'; export class WorkbenchExtensionGalleryService extends AbstractExtensionGalleryService { constructor( @IStorageService storageService: IStorageService, - @IWorkbenchAssignmentService assignmentService: IWorkbenchAssignmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService, @IEnvironmentService environmentService: IEnvironmentService, @@ -31,7 +29,7 @@ export class WorkbenchExtensionGalleryService extends AbstractExtensionGallerySe @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, @IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService, ) { - super(storageService, assignmentService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); + super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService); } } From d93841e0cd19f0860cc19bd2132649ac1eaa4403 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:02:34 -0700 Subject: [PATCH 0983/4355] Remove extra type assertion --- .../contrib/comments/browser/commentThreadZoneWidget.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 11261bbffa9..8f8d00cd435 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -22,7 +22,6 @@ import { EditorOption, IEditorOptions } from '../../../../editor/common/config/e import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { CommentThreadWidget } from './commentThreadWidget.js'; -import { ICellRange } from '../../notebook/common/notebookRange.js'; import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar, getCommentThreadStateBorderColor } from './commentColors.js'; import { peekViewBorder } from '../../../../editor/contrib/peekView/browser/peekView.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -263,14 +262,14 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget protected _fillContainer(container: HTMLElement): void { this.setCssClass('review-widget'); this._commentThreadWidget = this._scopedInstantiationService.createInstance( - CommentThreadWidget, + CommentThreadWidget, container, this.editor, this._uniqueOwner, this.editor.getModel()!.uri, this._contextKeyService, this._scopedInstantiationService, - this._commentThread as unknown as languages.CommentThread, + this._commentThread, this._pendingComment, this._pendingEdits, { editor: this.editor, codeBlockFontSize: '', codeBlockFontFamily: this.configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily }, @@ -303,7 +302,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return this.collapse(true); } } - ) as unknown as CommentThreadWidget; + ); this._disposables.add(this._commentThreadWidget); } From d6fb12af85e79d796be5ae9c2603c841ffb63e66 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 8 Oct 2025 12:08:40 -0700 Subject: [PATCH 0984/4355] extract hasModifierKeys function and use it in ModifierKeyEmitter --- src/vs/base/browser/dom.ts | 4 ++-- src/vs/base/browser/keyboardEvent.ts | 9 +++++++++ src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 3abc4f9484a..3419b4aef26 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -5,7 +5,7 @@ import * as browser from './browser.js'; import { BrowserFeatures } from './canIUse.js'; -import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; +import { hasModifierKeys, IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent.js'; import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from '../common/async.js'; import { BugIndicatingError, onUnexpectedError } from '../common/errors.js'; @@ -1813,7 +1813,7 @@ export class ModifierKeyEmitter extends event.Emitter { } get isModifierPressed(): boolean { - return this._keyStatus.altKey || this._keyStatus.ctrlKey || this._keyStatus.metaKey || this._keyStatus.shiftKey; + return hasModifierKeys(this._keyStatus); } /** diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 4e98a3b12d8..b0ba04a66f5 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -114,6 +114,15 @@ export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string { return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`; } +export function hasModifierKeys(keyStatus: { + readonly ctrlKey: boolean; + readonly shiftKey: boolean; + readonly altKey: boolean; + readonly metaKey: boolean; +}): boolean { + return keyStatus.ctrlKey || keyStatus.shiftKey || keyStatus.altKey || keyStatus.metaKey; +} + export class StandardKeyboardEvent implements IKeyboardEvent { readonly _standardKeyboardEventBrand = true; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 3491bb03c06..2f77a6be54f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; -import { addDisposableListener, ModifierKeyEmitter } from '../../../../base/browser/dom.js'; +import { addDisposableListener } from '../../../../base/browser/dom.js'; import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; -import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { hasModifierKeys, StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/button.js'; @@ -1203,7 +1203,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // We need to prevent Monaco's default Enter behavior while still allowing the VS Code keybinding service // to receive and process the Enter key for ChatSubmitAction this._register(this._inputEditor.onKeyDown((e) => { - if (e.keyCode === KeyCode.Enter && !ModifierKeyEmitter.getInstance().isModifierPressed) { + if (e.keyCode === KeyCode.Enter && !hasModifierKeys(e)) { // Only prevent the default Monaco behavior (newline insertion) // Do NOT call stopPropagation() so the keybinding service can still process this event e.preventDefault(); From 7cae04b021c544517c4b0271a3b9cead27fb9fc7 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:09:44 -0700 Subject: [PATCH 0985/4355] Remove markdown code block font family option For #206228 Always use the configuration service to look up the editor defaults instead of requiring it from each caller --- .../browser/services/hoverService/hoverWidget.ts | 7 +------ .../markdownRenderer/browser/markdownRenderer.ts | 10 ++++++---- .../comments/browser/commentThreadZoneWidget.ts | 5 ++--- .../notebook/browser/view/cellParts/cellComments.ts | 8 +------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index a8784044f9e..28aeb40b10a 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -10,8 +10,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IEditorOptions } from '../../../common/config/editorOptions.js'; -import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; @@ -167,10 +165,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } else { const markdown = options.content; - const mdRenderer = this._instantiationService.createInstance( - MarkdownRenderer, - { codeBlockFontFamily: this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily } - ); + const mdRenderer = this._instantiationService.createInstance(MarkdownRenderer, {}); const { element, dispose } = mdRenderer.render(markdown, { actionHandler: (content) => this._linkHandler(content), diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 0c7ee7981b6..5b47ff5eabe 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -8,8 +8,10 @@ import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTyp import { onUnexpectedError } from '../../../../../base/common/errors.js'; import { IMarkdownString, MarkdownStringTrustedOptions } from '../../../../../base/common/htmlContent.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { EditorOption } from '../../../../common/config/editorOptions.js'; +import { EditorOption, IEditorOptions } from '../../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../common/config/fontInfo.js'; import { ILanguageService } from '../../../../common/languages/language.js'; import { PLAINTEXT_LANGUAGE_ID } from '../../../../common/languages/modesRegistry.js'; import { tokenizeToString } from '../../../../common/languages/textToHtmlTokenizer.js'; @@ -23,7 +25,6 @@ export interface IMarkdownRenderResult extends IDisposable { export interface IMarkdownRendererOptions { readonly editor?: ICodeEditor; - readonly codeBlockFontFamily?: string; readonly codeBlockFontSize?: string; } @@ -41,6 +42,7 @@ export class MarkdownRenderer { constructor( private readonly _options: IMarkdownRendererOptions, + @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, ) { } @@ -78,8 +80,8 @@ export class MarkdownRenderer { if (this._options.editor) { const fontInfo = this._options.editor.getOption(EditorOption.fontInfo); applyFontInfo(element, fontInfo); - } else if (this._options.codeBlockFontFamily) { - element.style.fontFamily = this._options.codeBlockFontFamily; + } else { + element.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; } if (this._options.codeBlockFontSize !== undefined) { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 11261bbffa9..2515825e9db 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -18,8 +18,7 @@ import { IColorTheme, IThemeService } from '../../../../platform/theme/common/th import { CommentGlyphWidget } from './commentGlyphWidget.js'; import { ICommentService } from './commentService.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; -import { EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; -import { EDITOR_FONT_DEFAULTS } from '../../../../editor/common/config/fontInfo.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { CommentThreadWidget } from './commentThreadWidget.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; @@ -273,7 +272,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread as unknown as languages.CommentThread, this._pendingComment, this._pendingEdits, - { editor: this.editor, codeBlockFontSize: '', codeBlockFontFamily: this.configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily }, + { editor: this.editor, codeBlockFontSize: '' }, this._commentOptions, { actionRunner: async () => { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index c84fb57697b..1ecb319844c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -5,10 +5,7 @@ import { coalesce } from '../../../../../../base/common/arrays.js'; import { DisposableMap, DisposableStore } from '../../../../../../base/common/lifecycle.js'; -import { IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; -import { EDITOR_FONT_DEFAULTS } from '../../../../../../editor/common/config/fontInfo.js'; import * as languages from '../../../../../../editor/common/languages.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IThemeService } from '../../../../../../platform/theme/common/themeService.js'; @@ -29,7 +26,6 @@ export class CellComments extends CellContentPart { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ICommentService private readonly commentService: ICommentService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -65,9 +61,7 @@ export class CellComments extends CellContentPart { commentThread, undefined, undefined, - { - codeBlockFontFamily: this.configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily - }, + {}, undefined, { actionRunner: () => { From aa475f0552933e4ebc97b0096f03d447fcc1bdbf Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:17:10 -0700 Subject: [PATCH 0986/4355] Update callers --- .../contrib/hover/browser/glyphHoverWidget.ts | 8 +++--- .../hover/browser/markdownHoverParticipant.ts | 26 +++++++------------ .../inlayHints/browser/inlayHintsHover.ts | 10 +++---- .../browser/hintsWidget/hoverParticipant.ts | 6 +---- .../browser/parameterHintsWidget.ts | 8 +++--- .../browser/unicodeHighlighter.ts | 10 +++---- .../chat/browser/chatMarkdownRenderer.ts | 4 ++- .../comments/browser/commentThreadBody.ts | 6 +---- 8 files changed, 27 insertions(+), 51 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index d40ff7189c5..eafecdf72a3 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -8,13 +8,12 @@ import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.j import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ICodeEditor, IEditorMouseEvent, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; -import { ILanguageService } from '../../../common/languages/language.js'; import { HoverOperation, HoverResult, HoverStartMode } from './hoverOperation.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { HoverWidget } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IHoverWidget } from './hoverTypes.js'; import { IHoverMessage, LaneOrLineNumber, GlyphHoverComputer, GlyphHoverComputerOptions } from './glyphHoverComputer.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; @@ -36,8 +35,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov constructor( editor: ICodeEditor, - @ILanguageService languageService: ILanguageService, - @IOpenerService openerService: IOpenerService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); this._editor = editor; @@ -48,7 +46,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov this._hover = this._register(new HoverWidget(true)); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - this._markdownRenderer = new MarkdownRenderer({ editor: this._editor }, languageService, openerService); + this._markdownRenderer = instantiationService.createInstance(MarkdownRenderer, { editor: this._editor }); this._hoverOperation = this._register(new HoverOperation(this._editor, new GlyphHoverComputer(this._editor))); this._register(this._hoverOperation.onResult((result) => this._withResult(result))); diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index 213e86b137c..b2dde12690e 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -14,11 +14,9 @@ import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { IModelDecoration, ITextModel } from '../../../common/model.js'; -import { ILanguageService } from '../../../common/languages/language.js'; import { HoverAnchor, HoverAnchorType, HoverRangeAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from './hoverTypes.js'; import * as nls from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { Hover, HoverContext, HoverProvider, HoverVerbosityAction } from '../../../common/languages.js'; @@ -36,6 +34,7 @@ import { getHoverProviderResultsAsAsyncIterable } from './getHover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { HoverStartSource } from './hoverOperation.js'; import { ScrollEvent } from '../../../../base/common/scrollable.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.')); @@ -87,8 +86,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { hoverPartsContainer: DocumentFragment, private readonly _hoverParticipant: MarkdownHoverParticipant, private readonly _editor: ICodeEditor, - private readonly _languageService: ILanguageService, - private readonly _openerService: IOpenerService, private readonly _commandService: ICommandService, private readonly _keybindingService: IKeybindingService, private readonly _hoverService: IHoverService, private readonly _configurationService: IConfigurationService, + private readonly _instantiationService: IInstantiationService, private readonly _onFinishedRendering: () => void, ) { this.renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering); @@ -315,8 +311,7 @@ class MarkdownRenderedHoverParts implements IRenderedHoverParts { const renderedMarkdownHover = renderMarkdown( this._editor, markdownHover, - this._languageService, - this._openerService, + this._instantiationService, onFinishedRendering, ); return renderedMarkdownHover; @@ -476,8 +471,7 @@ export function renderMarkdownHovers( context: IEditorHoverRenderContext, markdownHovers: MarkdownHover[], editor: ICodeEditor, - languageService: ILanguageService, - openerService: IOpenerService, + instantiationService: IInstantiationService, ): IRenderedHoverParts { // Sort hover parts to keep them stable since they might come in async, out-of-order @@ -487,8 +481,7 @@ export function renderMarkdownHovers( const renderedHoverPart = renderMarkdown( editor, markdownHover, - languageService, - openerService, + instantiationService, context.onContentsChanged, ); context.fragment.appendChild(renderedHoverPart.hoverElement); @@ -500,8 +493,7 @@ export function renderMarkdownHovers( function renderMarkdown( editor: ICodeEditor, markdownHover: MarkdownHover, - languageService: ILanguageService, - openerService: IOpenerService, + instantiationService: IInstantiationService, onFinishedRendering: () => void, ): IRenderedHoverPart { const disposables = new DisposableStore(); @@ -515,7 +507,7 @@ function renderMarkdown( } const markdownHoverElement = $('div.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = new MarkdownRenderer({ editor }, languageService, openerService); + const renderer = instantiationService.createInstance(MarkdownRenderer, { editor }); const renderedContents = disposables.add(renderer.render(markdownString, { asyncRenderCallback: () => { diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index 91527289754..8548d64f8f1 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -11,13 +11,11 @@ import { Position } from '../../../common/core/position.js'; import { IModelDecoration } from '../../../common/model.js'; import { ModelDecorationInjectedTextOptions } from '../../../common/model/textModel.js'; import { HoverAnchor, HoverForeignElementAnchor, IEditorHoverParticipant } from '../../hover/browser/hoverTypes.js'; -import { ILanguageService } from '../../../common/languages/language.js'; import { ITextModelService } from '../../../common/services/resolverService.js'; import { getHoverProviderResultsAsAsyncIterable } from '../../hover/browser/getHover.js'; import { MarkdownHover, MarkdownHoverParticipant } from '../../hover/browser/markdownHoverParticipant.js'; import { RenderedInlayHintLabelPart, InlayHintsController } from './inlayHintsController.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { localize } from '../../../../nls.js'; @@ -28,6 +26,7 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { HoverStartSource } from '../../hover/browser/hoverOperation.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( @@ -46,16 +45,15 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor constructor( editor: ICodeEditor, - @ILanguageService languageService: ILanguageService, - @IOpenerService openerService: IOpenerService, + @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IHoverService hoverService: IHoverService, @IConfigurationService configurationService: IConfigurationService, @ITextModelService private readonly _resolverService: ITextModelService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - @ICommandService commandService: ICommandService + @ICommandService commandService: ICommandService, ) { - super(editor, languageService, openerService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService); + super(editor, instantiationService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService); } suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts index 84650683bbe..fa36c48fff8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts @@ -10,7 +10,6 @@ import { autorun, autorunWithStore, constObservable } from '../../../../../base/ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Range } from '../../../../common/core/range.js'; -import { ILanguageService } from '../../../../common/languages/language.js'; import { IModelDecoration } from '../../../../common/model.js'; import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js'; import { InlineCompletionsController } from '../controller/inlineCompletionsController.js'; @@ -19,7 +18,6 @@ import { MarkdownRenderer } from '../../../../browser/widget/markdownRenderer/br import * as nls from '../../../../../nls.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { GhostTextView } from '../view/ghostText/ghostTextView.js'; @@ -45,8 +43,6 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan constructor( private readonly _editor: ICodeEditor, - @ILanguageService private readonly _languageService: ILanguageService, - @IOpenerService private readonly _openerService: IOpenerService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -154,7 +150,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' })); - const renderer = new MarkdownRenderer({ editor: this._editor }, this._languageService, this._openerService); + const renderer = this._instantiationService.createInstance(MarkdownRenderer, { editor: this._editor }); const render = (code: string) => { const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); const renderedContents = disposables.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), { diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index f31efc2284b..307167c7723 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -17,16 +17,15 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from '../../../common/config/editorOptions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import * as languages from '../../../common/languages.js'; -import { ILanguageService } from '../../../common/languages/language.js'; import { IMarkdownRenderResult, MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ParameterHintsModel } from './parameterHintsModel.js'; import { Context } from './provideSignatureHelp.js'; import * as nls from '../../../../nls.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { listHighlightForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; @@ -60,12 +59,11 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private readonly editor: ICodeEditor, private readonly model: ParameterHintsModel, @IContextKeyService contextKeyService: IContextKeyService, - @IOpenerService openerService: IOpenerService, - @ILanguageService languageService: ILanguageService + @IInstantiationService instantiationService: IInstantiationService ) { super(); - this.markdownRenderer = new MarkdownRenderer({ editor }, languageService, openerService); + this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer, { editor }); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 3aba528ff7f..3a0552623da 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -20,14 +20,12 @@ import { IModelDecoration, IModelDeltaDecoration, ITextModel, TrackedRangeSticki import { ModelDecorationOptions } from '../../../common/model/textModel.js'; import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighterReasonKind, UnicodeTextModelHighlighter } from '../../../common/services/unicodeTextModelHighlighter.js'; import { IEditorWorkerService, IUnicodeHighlightsResult } from '../../../common/services/editorWorker.js'; -import { ILanguageService } from '../../../common/languages/language.js'; import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverParts } from '../../hover/browser/hoverTypes.js'; import { MarkdownHover, renderMarkdownHovers } from '../../hover/browser/markdownHoverParticipant.js'; import { BannerController } from './bannerController.js'; import * as nls from '../../../../nls.js'; import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; @@ -419,10 +417,8 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa constructor( private readonly _editor: ICodeEditor, - @ILanguageService private readonly _languageService: ILanguageService, - @IOpenerService private readonly _openerService: IOpenerService, - ) { - } + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { } computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] { if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) { @@ -513,7 +509,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IRenderedHoverParts { - return renderMarkdownHovers(context, hoverParts, this._editor, this._languageService, this._openerService); + return renderMarkdownHovers(context, hoverParts, this._editor, this._instantiationService); } public getAccessibleContent(hoverPart: MarkdownHover): string { diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index 01331bca22e..ce7ce8c95f7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -12,6 +12,7 @@ import { URI } from '../../../../base/common/uri.js'; import { IMarkdownRendererOptions, IMarkdownRenderResult, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; @@ -68,11 +69,12 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { options: IMarkdownRendererOptions | undefined, @ILanguageService languageService: ILanguageService, @IOpenerService openerService: IOpenerService, + @IConfigurationService configurationService: IConfigurationService, @IHoverService private readonly hoverService: IHoverService, @IFileService private readonly fileService: IFileService, @ICommandService private readonly commandService: ICommandService, ) { - super(options ?? {}, languageService, openerService); + super(options ?? {}, configurationService, languageService, openerService); } override render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IMarkdownRenderResult { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 65d3c83394e..ccb8fd5da3f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -16,8 +16,6 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { URI } from '../../../../base/common/uri.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; import { IMarkdownRendererOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; @@ -52,8 +50,6 @@ export class CommentThreadBody extends D private _scopedInstatiationService: IInstantiationService, private _parentCommentThreadWidget: ICommentThreadWidget, @ICommentService private commentService: ICommentService, - @IOpenerService private openerService: IOpenerService, - @ILanguageService private languageService: ILanguageService, ) { super(); @@ -62,7 +58,7 @@ export class CommentThreadBody extends D this.commentService.setActiveEditingCommentThread(this._commentThread); })); - this._markdownRenderer = new MarkdownRenderer(this._options, this.languageService, this.openerService); + this._markdownRenderer = this._scopedInstatiationService.createInstance(MarkdownRenderer, this._options); } focus(commentUniqueId?: number) { From 9bccb627893a9078abe1f47e17b369330b04b5b3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 8 Oct 2025 15:22:02 -0400 Subject: [PATCH 0987/4355] Move tool progress announcements from `LanguageModelToolsService` -> `ChatListRenderer` (#270078) --- .../browser/accessibilityConfiguration.ts | 4 ++ .../chatProgressContentPart.ts | 8 ++- .../chatToolInvocationPart.ts | 3 +- .../chatToolProgressPart.ts | 53 +++++++++++++++++-- .../contrib/chat/browser/chatListRenderer.ts | 19 +++++-- .../chat/browser/languageModelToolsService.ts | 15 +----- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 86a1f2cbcd9..73726461da2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -563,6 +563,10 @@ const configuration: IConfigurationNode = { 'accessibility.signals.progress': { ...signalFeatureBase, 'description': localize('accessibility.signals.progress', "Plays a signal - sound (audio cue) and/or announcement (alert) - on loop while progress is occurring."), + 'default': { + 'sound': 'auto', + 'announcement': 'off' + }, 'properties': { 'sound': { 'description': localize('accessibility.signals.progress.sound', "Plays a sound on loop while progress is occurring."), diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 2ea5e3c16be..923ede3e0f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -18,6 +18,8 @@ import { ChatTreeItem } from '../chat.js'; import { renderFileWidgets } from '../chatInlineAnchorWidget.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; import { IChatMarkdownAnchorService } from './chatMarkdownAnchorService.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; export class ChatProgressContentPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -35,6 +37,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP icon: ThemeIcon | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -47,7 +50,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP return; } - if (this.showSpinner) { + if (this.showSpinner && !this.configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { // TODO@roblourens is this the right place for this? // this step is in progress, communicate it to SR users alert(progress.content.value); @@ -127,12 +130,13 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, @IChatMarkdownAnchorService chatMarkdownAnchorService: IChatMarkdownAnchorService, + @IConfigurationService configurationService: IConfigurationService ) { const progressMessage: IChatProgressMessage = { kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; - super(progressMessage, renderer, context, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService); + super(progressMessage, renderer, context, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index c206004e12f..c8454d6ed9c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -52,6 +52,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa private readonly editorPool: EditorPool, private readonly currentWidthDelegate: () => number, private readonly codeBlockModelCollection: CodeBlockModelCollection, + private readonly announcedToolProgressKeys: Set | undefined, private readonly codeBlockStartIndex: number, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -198,7 +199,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa ); } - return this.instantiationService.createInstance(ChatToolProgressSubPart, this.toolInvocation, this.context, this.renderer); + return this.instantiationService.createInstance(ChatToolProgressSubPart, this.toolInvocation, this.context, this.renderer, this.announcedToolProgressKeys); } hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index 35b31f20efd..fa723001a7c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../../base/browser/dom.js'; +import { status } from '../../../../../../base/browser/ui/aria/aria.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { autorun } from '../../../../../../base/common/observable.js'; import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressMessage, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; +import { AccessibilityWorkbenchSettingId } from '../../../../accessibility/browser/accessibilityConfiguration.js'; import { IChatCodeBlockInfo } from '../../chat.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatProgressContentPart } from '../chatProgressContentPart.js'; @@ -23,7 +26,9 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, private readonly context: IChatContentPartRenderContext, private readonly renderer: MarkdownRenderer, + private readonly announcedToolProgressKeys: Set | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(toolInvocation); @@ -32,7 +37,10 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { private createProgressPart(): HTMLElement { if (this.toolInvocation.isComplete && this.toolIsConfirmed && this.toolInvocation.pastTenseMessage) { - const part = this.renderProgressContent(this.toolInvocation.pastTenseMessage); + const key = this.getAnnouncementKey('complete'); + const completionContent = this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage; + const shouldAnnounce = this.toolInvocation.kind === 'toolInvocation' && this.hasMeaningfulContent(this.toolInvocation.pastTenseMessage) ? this.computeShouldAnnounce(key) : false; + const part = this.renderProgressContent(completionContent, shouldAnnounce); this._register(part); return part.domNode; } else { @@ -40,7 +48,10 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { const progressObservable = this.toolInvocation.kind === 'toolInvocation' ? this.toolInvocation.progress : undefined; this._register(autorun(reader => { const progress = progressObservable?.read(reader); - const part = reader.store.add(this.renderProgressContent(progress?.message || this.toolInvocation.invocationMessage)); + const key = this.getAnnouncementKey('progress'); + const progressContent = progress?.message ?? this.toolInvocation.invocationMessage; + const shouldAnnounce = this.toolInvocation.kind === 'toolInvocation' && this.hasMeaningfulContent(progress?.message) ? this.computeShouldAnnounce(key) : false; + const part = reader.store.add(this.renderProgressContent(progressContent, shouldAnnounce)); dom.reset(container, part.domNode); })); return container; @@ -57,7 +68,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { return this.toolInvocation.isConfirmed.type !== ToolConfirmKind.Denied; } - private renderProgressContent(content: IMarkdownString | string) { + private renderProgressContent(content: IMarkdownString | string, shouldAnnounce: boolean) { if (typeof content === 'string') { content = new MarkdownString().appendText(content); } @@ -67,6 +78,42 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { content }; + if (shouldAnnounce) { + this.provideScreenReaderStatus(content); + } + return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon()); } + + private getAnnouncementKey(kind: 'progress' | 'complete'): string { + return `${kind}:${this.toolInvocation.toolCallId}`; + } + + private computeShouldAnnounce(key: string): boolean { + if (!this.announcedToolProgressKeys) { + return false; + } + if (!this.configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { + return false; + } + if (this.announcedToolProgressKeys.has(key)) { + return false; + } + this.announcedToolProgressKeys.add(key); + return true; + } + + private provideScreenReaderStatus(content: IMarkdownString | string): void { + const message = typeof content === 'string' ? content : content.value; + status(message); + } + + private hasMeaningfulContent(content: IMarkdownString | string | undefined): boolean { + if (!content) { + return false; + } + + const text = typeof content === 'string' ? content : content.value; + return text.trim().length > 0; + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index aabb086c293..57b69259da0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -197,6 +197,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer(); + constructor( editorOptions: ChatEditorOptions, private rendererOptions: IChatListItemRendererOptions, @@ -212,7 +218,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.kind === other.kind); @@ -1406,7 +1419,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._currentLayoutWidth, this._toolInvocationCodeBlockCollection, codeBlockStartIndex); + const part = this.instantiationService.createInstance(ChatToolInvocationPart, toolInvocation, context, this.renderer, this._contentReferencesListPool, this._toolEditorPool, () => this._currentLayoutWidth, this._toolInvocationCodeBlockCollection, this._announcedToolProgressKeys, codeBlockStartIndex); part.addDisposable(part.onDidChangeHeight(() => { this.updateItemHeight(templateData); })); @@ -1536,7 +1549,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Wed, 8 Oct 2025 12:31:37 -0700 Subject: [PATCH 0988/4355] Move `IRenderedMarkdown` into base This type already implicitly existed. With this change we just make sure we re-use it here too --- src/vs/base/browser/markdownRenderer.ts | 9 ++++++--- .../widget/markdownRenderer/browser/markdownRenderer.ts | 9 ++------- .../parameterHints/browser/parameterHintsWidget.ts | 5 +++-- src/vs/workbench/browser/parts/views/treeView.ts | 6 +++--- .../browser/chatContentParts/chatConfirmationWidget.ts | 6 +++--- .../chatMcpServersInteractionContentPart.ts | 5 +++-- .../browser/chatContentParts/chatProgressContentPart.ts | 5 +++-- .../browser/chatContentParts/chatThinkingContentPart.ts | 5 +++-- .../contrib/chat/browser/chatMarkdownRenderer.ts | 8 ++++---- .../browser/viewsWelcome/chatViewWelcomeController.ts | 5 +++-- src/vs/workbench/contrib/comments/browser/commentNode.ts | 5 +++-- 11 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 87ec4b8ec64..c5b5b3d9cca 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -9,7 +9,7 @@ import { markdownEscapeEscapedIcons } from '../common/iconLabels.js'; import { defaultGenerator } from '../common/idGenerator.js'; import { KeyCode } from '../common/keyCodes.js'; import { Lazy } from '../common/lazy.js'; -import { DisposableStore } from '../common/lifecycle.js'; +import { DisposableStore, IDisposable } from '../common/lifecycle.js'; import * as marked from '../common/marked/marked.js'; import { parse } from '../common/marshalling.js'; import { FileAccess, Schemas } from '../common/network.js'; @@ -115,13 +115,17 @@ const defaultMarkedRenderers = Object.freeze({ }, }); +export interface IRenderedMarkdown extends IDisposable { + readonly element: HTMLElement; +} + /** * Low-level way create a html element from a markdown string. * * **Note** that for most cases you should be using {@link import('../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js').MarkdownRenderer MarkdownRenderer} * which comes with support for pretty code block rendering and which uses the default way of handling links. */ -export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, target?: HTMLElement): { element: HTMLElement; dispose: () => void } { +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, target?: HTMLElement): IRenderedMarkdown { const disposables = new DisposableStore(); let isDisposed = false; @@ -984,4 +988,3 @@ function completeTable(tokens: marked.Token[]): marked.Token[] | undefined { return undefined; } - diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 5b47ff5eabe..d385bf73e77 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MarkdownRenderOptions, renderMarkdown } from '../../../../../base/browser/markdownRenderer.js'; +import { IRenderedMarkdown, MarkdownRenderOptions, renderMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTypes.js'; import { onUnexpectedError } from '../../../../../base/common/errors.js'; import { IMarkdownString, MarkdownStringTrustedOptions } from '../../../../../base/common/htmlContent.js'; -import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { EditorOption, IEditorOptions } from '../../../../common/config/editorOptions.js'; @@ -19,10 +18,6 @@ import { applyFontInfo } from '../../../config/domFontInfo.js'; import { ICodeEditor } from '../../../editorBrowser.js'; import './renderedMarkdown.css'; -export interface IMarkdownRenderResult extends IDisposable { - readonly element: HTMLElement; -} - export interface IMarkdownRendererOptions { readonly editor?: ICodeEditor; readonly codeBlockFontSize?: string; @@ -47,7 +42,7 @@ export class MarkdownRenderer { @IOpenerService private readonly _openerService: IOpenerService, ) { } - render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IMarkdownRenderResult { + render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown { const rendered = renderMarkdown(markdown, { codeBlockRenderer: (alias, value) => this.renderCodeBlock(alias, value), actionHandler: (link, mdStr) => this.openMarkdownLink(link, mdStr), diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index 307167c7723..2a375689e99 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -17,7 +17,8 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from '../../../common/config/editorOptions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import * as languages from '../../../common/languages.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { ParameterHintsModel } from './parameterHintsModel.js'; import { Context } from './provideSignatureHelp.js'; import * as nls from '../../../../nls.js'; @@ -270,7 +271,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { this.domNodes.scrollbar.scanDomNode(); } - private renderMarkdownDocs(markdown: IMarkdownString): IMarkdownRenderResult { + private renderMarkdownDocs(markdown: IMarkdownString): IRenderedMarkdown { const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(markdown, { asyncRenderCallback: () => { this.domNodes?.scrollbar.scanDomNode(); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 4e8a7941e85..f147f5d4c57 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -6,7 +6,7 @@ import { DataTransfers, IDragAndDropData } from '../../../../base/browser/dnd.js'; import * as DOM from '../../../../base/browser/dom.js'; import * as cssJs from '../../../../base/browser/cssValue.js'; -import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; +import { IRenderedMarkdown, renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; @@ -70,7 +70,7 @@ import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { ITreeViewsDnDService } from '../../../../editor/common/services/treeViewsDndService.js'; import { DraggedTreeItemsIdentifier } from '../../../../editor/common/services/treeViewsDnd.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import type { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; import { parseLinkedText } from '../../../../base/common/linkedText.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; @@ -893,7 +893,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private processMessage(message: IMarkdownString, disposables: DisposableStore): HTMLElement { const lines = message.value.split('\n'); - const result: (IMarkdownRenderResult | HTMLElement)[] = []; + const result: (IRenderedMarkdown | HTMLElement)[] = []; let hasFoundButton = false; for (const line of lines) { const linkedText = parseLinkedText(line); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 78da1161cd0..0a68bb37623 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; -import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js'; +import { IRenderedMarkdown, renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js'; import { Button, ButtonWithDropdown, IButton, IButtonOptions } from '../../../../../base/browser/ui/button/button.js'; import { Action, Separator } from '../../../../../base/common/actions.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import type { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; @@ -49,7 +49,7 @@ export interface IChatConfirmationWidgetOptions { export class ChatQueryTitlePart extends Disposable { private readonly _onDidChangeHeight = this._register(new Emitter()); public readonly onDidChangeHeight = this._onDidChangeHeight.event; - private readonly _renderedTitle = this._register(new MutableDisposable()); + private readonly _renderedTitle = this._register(new MutableDisposable()); public get title() { return this._title; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index cafb2af6149..e703942d141 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -12,7 +12,8 @@ import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRenderResult, MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; @@ -32,7 +33,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements private workingProgressPart: ChatProgressContentPart | undefined; private interactionContainer: HTMLElement | undefined; - private readonly interactionMd = this._register(new MutableDisposable()); + private readonly interactionMd = this._register(new MutableDisposable()); private readonly markdownRenderer: MarkdownRenderer; private readonly showSpecificServersScheduler = this._register(new RunOnceScheduler(() => this.updateDetailedProgress(this.data.state!.get()), 2500)); private readonly previousParts = new Lazy(() => { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 923ede3e0f1..4a736c6d756 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -9,7 +9,8 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; import { IChatProgressMessage, IChatTask, IChatTaskSerialized } from '../../common/chatService.js'; @@ -26,7 +27,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP private readonly showSpinner: boolean; private readonly isHidden: boolean; - private readonly renderedMessage = this._register(new MutableDisposable()); + private readonly renderedMessage = this._register(new MutableDisposable()); constructor( progress: IChatProgressMessage | IChatTask | IChatTaskSerialized, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index a728e8adaf5..2df7db26c99 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -11,7 +11,8 @@ import { ChatTreeItem } from '../chat.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { MarkdownRenderer, IMarkdownRenderResult } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js'; import { localize } from '../../../../../nls.js'; import { ButtonWithIcon } from '../../../../../base/browser/ui/button/button.js'; @@ -42,7 +43,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen private defaultTitle = localize('chat.thinking.header', 'Thinking...'); private readonly renderer: MarkdownRenderer; private textContainer!: HTMLElement; - private markdownResult: IMarkdownRenderResult | undefined; + private markdownResult: IRenderedMarkdown | undefined; private wrapper!: HTMLElement; private perItemCollapsedMode: boolean = false; private fixedScrollingMode: boolean = false; diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index ce7ce8c95f7..dad628c9cd6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { $ } from '../../../../base/browser/dom.js'; -import { MarkdownRenderOptions } from '../../../../base/browser/markdownRenderer.js'; +import { IRenderedMarkdown, MarkdownRenderOptions } from '../../../../base/browser/markdownRenderer.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; -import { IMarkdownRendererOptions, IMarkdownRenderResult, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -77,7 +77,7 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { super(options ?? {}, configurationService, languageService, openerService); } - override render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IMarkdownRenderResult { + override render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown { options = { ...options, sanitizerConfig: { @@ -114,7 +114,7 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { return this.attachCustomHover(result); } - private attachCustomHover(result: IMarkdownRenderResult): IMarkdownRenderResult { + private attachCustomHover(result: IRenderedMarkdown): IRenderedMarkdown { const store = new DisposableStore(); result.element.querySelectorAll('a').forEach((element) => { if (element.title) { diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index b972bc483ce..bece3fa1fa4 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -12,7 +12,8 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -286,7 +287,7 @@ export class ChatViewWelcomePart extends Disposable { })); } - private renderMarkdownMessageContent(renderer: MarkdownRenderer, content: IMarkdownString, options: IChatViewWelcomeRenderOptions | undefined): IMarkdownRenderResult { + private renderMarkdownMessageContent(renderer: MarkdownRenderer, content: IMarkdownString, options: IChatViewWelcomeRenderOptions | undefined): IRenderedMarkdown { const messageResult = this._register(renderer.render(content)); const firstLink = options?.firstLinkToButton ? messageResult.element.querySelector('a') : undefined; if (firstLink) { diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 6292feecf74..85d6e137ce0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -10,7 +10,8 @@ import { ActionsOrientation, ActionBar } from '../../../../base/browser/ui/actio import { Action, IAction, Separator, ActionRunner } from '../../../../base/common/actions.js'; import { Disposable, DisposableStore, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; -import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ICommentService } from './commentService.js'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor.js'; @@ -60,7 +61,7 @@ export class CommentNode extends Disposable { private _domNode: HTMLElement; private _body: HTMLElement; private _avatar: HTMLElement; - private readonly _md: MutableDisposable = this._register(new MutableDisposable()); + private readonly _md: MutableDisposable = this._register(new MutableDisposable()); private _plainText: HTMLElement | undefined; private _clearTimeout: Timeout | null; From 0f5bcca45d6959f4318a40f4571ee959a1f07129 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Oct 2025 22:05:47 +0200 Subject: [PATCH 0989/4355] fix prompt file link validation for URL links (#270427) --- .../languageProviders/promptValidator.ts | 26 ++++++++------- .../promptSyntax/service/newPromptsParser.ts | 2 +- .../promptSytntax/promptValidator.test.ts | 32 ++++++++++++++++++- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index f8cf779af10..b4772e2e4a6 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -26,6 +26,7 @@ import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IPromptsService } from '../service/promptsService.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; + const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; export class PromptValidator { @@ -34,7 +35,7 @@ export class PromptValidator { @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, @IChatModeService private readonly chatModeService: IChatModeService, @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, ) { } public async validate(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { @@ -57,17 +58,20 @@ export class PromptValidator { report(toMarker(localize('promptValidator.invalidFileReference', "Invalid file reference '{0}'.", ref.content), ref.range, MarkerSeverity.Warning)); continue; } - fileReferenceChecks.push((async () => { - try { - const exists = await this.fileService.exists(resolved); - if (exists) { - return; + if (promptAST.uri.scheme === resolved.scheme) { + // only validate if the link is in the file system of the prompt file + fileReferenceChecks.push((async () => { + try { + const exists = await this.fileService.exists(resolved); + if (exists) { + return; + } + } catch { } - } catch { - } - const loc = this.labelService.getUriLabel(resolved); - report(toMarker(localize('promptValidator.fileNotFound', "File '{0}' not found at '{1}'.", ref.content, loc), ref.range, MarkerSeverity.Warning)); - })()); + const loc = this.labelService.getUriLabel(resolved); + report(toMarker(localize('promptValidator.fileNotFound', "File '{0}' not found at '{1}'.", ref.content, loc), ref.range, MarkerSeverity.Warning)); + })()); + } } // Validate variable references (tool or toolset names) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index aaabfdd7c42..09fa9079953 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -285,7 +285,7 @@ export class PromptBody { try { if (path.startsWith('/')) { return this.uri.with({ path }); - } else if (path.match(/^[a-zA-Z]:\\/)) { + } else if (path.match(/^[a-zA-Z]+:\//)) { return URI.parse(path); } else { const dirName = dirname(this.uri); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index 2ea1af35f78..0791ec0a449 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -35,6 +35,9 @@ suite('PromptValidator', () => { let instaService: TestInstantiationService; + const existingRef1 = URI.parse('myFs://test/reference1.md'); + const existingRef2 = URI.parse('myFs://test/reference2.md'); + setup(async () => { const testConfigService = new TestConfigurationService(); @@ -81,7 +84,7 @@ suite('PromptValidator', () => { instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); - const existingFiles = new ResourceSet([URI.parse('myFs://test/reference1.md'), URI.parse('myFs://test/reference2.md')]); + const existingFiles = new ResourceSet([existingRef1, existingRef2]); instaService.stub(IFileService, { exists(uri: URI) { return Promise.resolve(existingFiles.has(uri)); @@ -348,6 +351,33 @@ suite('PromptValidator', () => { ]); }); + test('body with http link', async () => { + const content = [ + '---', + 'description: "HTTP Link"', + '---', + 'Here is a [http link](http://example.com).' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, [], 'Expected no validation issues'); + }); + + test('body with url link', async () => { + const nonExistingRef = existingRef1.with({ path: '/nonexisting' }); + const content = [ + '---', + 'description: "URL Links"', + '---', + `Here is a [url link](${existingRef1.toString()}).`, + `Here is a [url link](${nonExistingRef.toString()}).` + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + const messages = markers.map(m => m.message).sort(); + assert.deepStrictEqual(messages, [ + "File 'myFs://test/nonexisting' not found at '/nonexisting'.", + ]); + }); + test('body with unknown tool variable reference warns', async () => { const content = [ '---', From 68dc5a6d0d89294e5a127d0a887569f5719dc56d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:06:47 -0700 Subject: [PATCH 0990/4355] Make sure `out-monaco-*` is ignored for eslint --- .eslint-ignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.eslint-ignore b/.eslint-ignore index 6f55ce40f69..c65ccc2baac 100644 --- a/.eslint-ignore +++ b/.eslint-ignore @@ -25,9 +25,8 @@ **/extensions/vscode-api-tests/testWorkspace2/** **/fixtures/** **/node_modules/** -**/out-*/**/*.js -**/out-editor-*/** -**/out/**/*.js +**/out/** +**/out-*/** **/src/**/dompurify.js **/src/**/marked.js **/src/**/semver.js From cf1a93be3f3fba5ef75f7368d6f076db5e579899 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Oct 2025 22:09:33 +0200 Subject: [PATCH 0991/4355] Markdown: Open Preview - doesn't work for *.prompt.md files (#270417) --- .../markdown-language-features/package.json | 30 +++++++++---------- .../src/extension.shared.ts | 3 +- .../src/languageFeatures/diagnostics.ts | 3 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 922fe087af5..7a478ee717b 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -181,13 +181,13 @@ "command": "markdown.editor.insertLinkFromWorkspace", "title": "%markdown.editor.insertLinkFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !activeEditorIsReadonly" }, { "command": "markdown.editor.insertImageFromWorkspace", "title": "%markdown.editor.insertImageFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !activeEditorIsReadonly" } ], "menus": { @@ -204,7 +204,7 @@ "editor/title": [ { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused && !hasCustomMarkdownPreview", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused && !hasCustomMarkdownPreview", "alt": "markdown.showPreview", "group": "navigation" }, @@ -232,24 +232,24 @@ "explorer/context": [ { "command": "markdown.showPreview", - "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !hasCustomMarkdownPreview", "group": "navigation" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/", "group": "4_search" } ], "editor/title/context": [ { "command": "markdown.showPreview", - "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !hasCustomMarkdownPreview", "group": "1_open" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown" + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/" } ], "commandPalette": [ @@ -263,17 +263,17 @@ }, { "command": "markdown.showPreview", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showLockedPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { @@ -283,7 +283,7 @@ }, { "command": "markdown.showPreviewSecuritySelector", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewSecuritySelector", @@ -295,7 +295,7 @@ }, { "command": "markdown.preview.refresh", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.preview.refresh", @@ -303,7 +303,7 @@ }, { "command": "markdown.findAllFileReferences", - "when": "editorLangId == markdown" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/" } ] }, @@ -312,13 +312,13 @@ "command": "markdown.showPreview", "key": "shift+ctrl+v", "mac": "shift+cmd+v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewToSide", "key": "ctrl+k v", "mac": "cmd+k v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" } ], "configuration": { diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index e062666c748..6f245bd8ab7 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -21,6 +21,7 @@ import { ExtensionContentSecurityPolicyArbiter } from './preview/security'; import { loadDefaultTelemetryReporter } from './telemetryReporter'; import { MdLinkOpener } from './util/openDocumentLink'; import { registerUpdatePastedLinks } from './languageFeatures/updateLinksOnPaste'; +import { markdownLanguageIds } from './util/file'; export function activateShared( context: vscode.ExtensionContext, @@ -54,7 +55,7 @@ function registerMarkdownLanguageFeatures( commandManager: CommandManager, parser: IMdParser, ): vscode.Disposable { - const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; + const selector: vscode.DocumentSelector = markdownLanguageIds; return vscode.Disposable.from( // Language features registerDiagnosticSupport(selector, commandManager), diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 793ea7f64ec..095e8f25c12 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { CommandManager } from '../commandManager'; +import { isMarkdownFile } from '../util/file'; // Copied from markdown language service @@ -88,7 +89,7 @@ function registerMarkdownStatusItem(selector: vscode.DocumentSelector, commandMa const update = () => { const activeDoc = vscode.window.activeTextEditor?.document; - const markdownDoc = activeDoc?.languageId === 'markdown' ? activeDoc : undefined; + const markdownDoc = activeDoc && isMarkdownFile(activeDoc) ? activeDoc : undefined; const enabled = vscode.workspace.getConfiguration('markdown', markdownDoc).get(enabledSettingId); if (enabled) { From a9b07902dd8f3f03dd1da1a64a09104c909ab9d7 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:18:29 -0700 Subject: [PATCH 0992/4355] Move all markdown renderer options into `render` call For #206228 This makes the MarkdownRenderer very close to a service interface. The next step will be converting it into a real service --- .../services/hoverService/hoverWidget.ts | 2 +- .../browser/markdownRenderer.ts | 21 +++++++++---------- .../contrib/hover/browser/glyphHoverWidget.ts | 4 ++-- .../hover/browser/markdownHoverParticipant.ts | 3 ++- .../browser/hintsWidget/hoverParticipant.ts | 3 ++- .../browser/parameterHintsWidget.ts | 3 ++- .../suggest/browser/suggestWidgetDetails.ts | 3 ++- .../browser/bannerController.ts | 2 +- .../browser/parts/banner/bannerPart.ts | 2 +- .../browser/parts/dialogs/dialogHandler.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 2 +- .../chatConfirmationWidget.ts | 4 ++-- .../chatMcpServersInteractionContentPart.ts | 2 +- .../chatThinkingContentPart.ts | 2 +- .../chatToolInputOutputContentPart.ts | 2 +- .../chatTerminalToolProgressPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 2 +- .../chat/browser/chatMarkdownRenderer.ts | 5 ++--- .../contrib/chat/browser/chatQuick.ts | 2 +- .../chatSessions/view/sessionsTreeRenderer.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 2 +- .../contrib/chat/browser/chatStatus.ts | 2 +- .../viewsWelcome/chatViewWelcomeController.ts | 2 +- .../test/browser/chatMarkdownRenderer.test.ts | 2 +- .../contrib/comments/browser/commentNode.ts | 7 ++++--- .../comments/browser/commentThreadBody.ts | 9 ++++---- .../comments/browser/commentThreadWidget.ts | 4 ++-- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../preferences/browser/settingsTree.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../testResultsView/testResultsOutput.ts | 2 +- .../testing/browser/testingExplorerView.ts | 2 +- .../browser/gettingStarted.ts | 2 +- .../browser/simpleSuggestWidgetDetails.ts | 2 +- 34 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 28aeb40b10a..ce44c98a63b 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -165,7 +165,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } else { const markdown = options.content; - const mdRenderer = this._instantiationService.createInstance(MarkdownRenderer, {}); + const mdRenderer = this._instantiationService.createInstance(MarkdownRenderer); const { element, dispose } = mdRenderer.render(markdown, { actionHandler: (content) => this._linkHandler(content), diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index d385bf73e77..98ec9b2b59a 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -18,7 +18,7 @@ import { applyFontInfo } from '../../../config/domFontInfo.js'; import { ICodeEditor } from '../../../editorBrowser.js'; import './renderedMarkdown.css'; -export interface IMarkdownRendererOptions { +export interface IMarkdownRendererExtraOptions { readonly editor?: ICodeEditor; readonly codeBlockFontSize?: string; } @@ -36,15 +36,14 @@ export class MarkdownRenderer { }); constructor( - private readonly _options: IMarkdownRendererOptions, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, ) { } - render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown { + render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { const rendered = renderMarkdown(markdown, { - codeBlockRenderer: (alias, value) => this.renderCodeBlock(alias, value), + codeBlockRenderer: (alias, value) => this.renderCodeBlock(alias, value, options ?? {}), actionHandler: (link, mdStr) => this.openMarkdownLink(link, mdStr), ...options, }, outElement); @@ -52,15 +51,15 @@ export class MarkdownRenderer { return rendered; } - private async renderCodeBlock(languageAlias: string | undefined, value: string): Promise { + private async renderCodeBlock(languageAlias: string | undefined, value: string, options: IMarkdownRendererExtraOptions): Promise { // In markdown, // it is possible that we stumble upon language aliases (e.g.js instead of javascript) // it is possible no alias is given in which case we fall back to the current editor lang let languageId: string | undefined | null; if (languageAlias) { languageId = this._languageService.getLanguageIdByLanguageName(languageAlias); - } else if (this._options.editor) { - languageId = this._options.editor.getModel()?.getLanguageId(); + } else if (options.editor) { + languageId = options.editor.getModel()?.getLanguageId(); } if (!languageId) { languageId = PLAINTEXT_LANGUAGE_ID; @@ -72,15 +71,15 @@ export class MarkdownRenderer { element.innerHTML = (MarkdownRenderer._ttpTokenizer?.createHTML(html) ?? html) as string; // use "good" font - if (this._options.editor) { - const fontInfo = this._options.editor.getOption(EditorOption.fontInfo); + if (options.editor) { + const fontInfo = options.editor.getOption(EditorOption.fontInfo); applyFontInfo(element, fontInfo); } else { element.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; } - if (this._options.codeBlockFontSize !== undefined) { - element.style.fontSize = this._options.codeBlockFontSize; + if (options.codeBlockFontSize !== undefined) { + element.style.fontSize = options.codeBlockFontSize; } return element; diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index eafecdf72a3..c93a4049b0b 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -46,7 +46,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov this._hover = this._register(new HoverWidget(true)); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - this._markdownRenderer = instantiationService.createInstance(MarkdownRenderer, { editor: this._editor }); + this._markdownRenderer = instantiationService.createInstance(MarkdownRenderer); this._hoverOperation = this._register(new HoverOperation(this._editor, new GlyphHoverComputer(this._editor))); this._register(this._hoverOperation.onResult((result) => this._withResult(result))); @@ -148,7 +148,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov for (const msg of messages) { const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderedContents = this._renderDisposeables.add(this._markdownRenderer.render(msg.value)); + const renderedContents = this._renderDisposeables.add(this._markdownRenderer.render(msg.value, { editor: this._editor })); hoverContentsElement.appendChild(renderedContents.element); fragment.appendChild(markdownHoverElement); } diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index b2dde12690e..bd562e5be7f 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -507,9 +507,10 @@ function renderMarkdown( } const markdownHoverElement = $('div.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = instantiationService.createInstance(MarkdownRenderer, { editor }); + const renderer = instantiationService.createInstance(MarkdownRenderer); const renderedContents = disposables.add(renderer.render(markdownString, { + editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; onFinishedRendering(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts index fa36c48fff8..93bd4d92c93 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts @@ -150,10 +150,11 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' })); - const renderer = this._instantiationService.createInstance(MarkdownRenderer, { editor: this._editor }); + const renderer = this._instantiationService.createInstance(MarkdownRenderer); const render = (code: string) => { const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); const renderedContents = disposables.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), { + editor: this._editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; context.onContentsChanged(); diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index 2a375689e99..5e90fb215c9 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -64,7 +64,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { ) { super(); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer, { editor }); + this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); @@ -273,6 +273,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private renderMarkdownDocs(markdown: IMarkdownString): IRenderedMarkdown { const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(markdown, { + editor: this.editor, asyncRenderCallback: () => { this.domNodes?.scrollbar.scanDomNode(); } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index be4d67feb15..e61661b8098 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -54,7 +54,7 @@ export class SuggestDetailsWidget { this.domNode = dom.$('.suggest-details'); this.domNode.classList.add('no-docs'); - this._markdownRenderer = instaService.createInstance(MarkdownRenderer, { editor: _editor }); + this._markdownRenderer = instaService.createInstance(MarkdownRenderer); this._body = dom.$('.body'); @@ -177,6 +177,7 @@ export class SuggestDetailsWidget { this._docs.classList.add('markdown-docs'); dom.clearNode(this._docs); const renderedContents = this._markdownRenderer.render(documentation, { + editor: this._editor, asyncRenderCallback: () => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts index e23a826de08..65173478a43 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts @@ -62,7 +62,7 @@ class Banner extends Disposable { ) { super(); - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); this.element = $('div.editor-banner'); this.element.tabIndex = 0; diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index 2d8db2820c4..f3f7dffd046 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -72,7 +72,7 @@ export class BannerPart extends Part implements IBannerService { ) { super(Parts.BANNER_PART, { hasTitle: false }, themeService, storageService, layoutService); - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); } protected override createContentArea(parent: HTMLElement): HTMLElement { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 32a2f00f190..9ee726f2133 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -40,7 +40,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { ) { super(); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); } async prompt(prompt: IPrompt): Promise> { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index f147f5d4c57..67144723d13 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -949,7 +949,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this._messageValue.disposables.dispose(); } if (isMarkdownString(message) && !this.markdownRenderer) { - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); } if (isMarkdownString(message)) { const disposables = new DisposableStore(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 0a68bb37623..f14ba28bbe0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -161,7 +161,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { ]); configureAccessibilityContainer(elements.container, title, message); this._domNode = elements.root; - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, @@ -374,7 +374,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { this._domNode = elements.root; this._buttonsDomNode = elements.buttons; - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index e703942d141..2db31595b5b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -57,7 +57,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements super(); this.domNode = dom.$('.chat-mcp-servers-interaction'); - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); // Listen to autostart state changes if available if (data.state) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 2df7db26c99..229df927050 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -67,7 +67,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen super(extractedTitle, context); - this.renderer = instantiationService.createInstance(MarkdownRenderer, {}); + this.renderer = instantiationService.createInstance(MarkdownRenderer); this.id = content.id; const mode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? 'none'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index b52a35e54ee..8be89ec49fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -120,7 +120,7 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { titleEl.root, title, subtitle, - _instantiationService.createInstance(MarkdownRenderer, {}), + _instantiationService.createInstance(MarkdownRenderer), )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 02ad59b19fb..fedfd58cca9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -57,7 +57,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart const command = terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original; - const markdownRenderer = instantiationService.createInstance(MarkdownRenderer, {}); + const markdownRenderer = instantiationService.createInstance(MarkdownRenderer); const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, elements.title, diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 57b69259da0..108ed1182ac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -222,7 +222,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { let testRenderer: ChatMarkdownRenderer; setup(() => { const instantiationService = store.add(workbenchInstantiationService(undefined, store)); - testRenderer = instantiationService.createInstance(ChatMarkdownRenderer, {}); + testRenderer = instantiationService.createInstance(ChatMarkdownRenderer); }); test('simple', async () => { diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 85d6e137ce0..13d7406932a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -10,7 +10,7 @@ import { ActionsOrientation, ActionBar } from '../../../../base/browser/ui/actio import { Action, IAction, Separator, ActionRunner } from '../../../../base/common/actions.js'; import { Disposable, DisposableStore, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ICommentService } from './commentService.js'; @@ -107,7 +107,8 @@ export class CommentNode extends Disposable { private owner: string, private resource: URI, private parentThread: ICommentThreadWidget, - private markdownRenderer: MarkdownRenderer, + private readonly markdownRenderer: MarkdownRenderer, + private readonly markdownRendererOptions: IMarkdownRendererExtraOptions, @IInstantiationService private instantiationService: IInstantiationService, @ICommentService private commentService: ICommentService, @INotificationService private notificationService: INotificationService, @@ -210,7 +211,7 @@ export class CommentNode extends Disposable { this._plainText = dom.append(this._body, dom.$('.comment-body-plainstring')); this._plainText.innerText = body; } else { - this._md.value = this.markdownRenderer.render(body); + this._md.value = this.markdownRenderer.render(body, this.markdownRendererOptions); this._body.appendChild(this._md.value.element); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index ccb8fd5da3f..0ad913a4fec 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -15,7 +15,7 @@ import { CommentNode } from './commentNode.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; -import { IMarkdownRendererOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; @@ -44,7 +44,7 @@ export class CommentThreadBody extends D readonly owner: string, readonly parentResourceUri: URI, readonly container: HTMLElement, - private _options: IMarkdownRendererOptions, + private _markdownRendererOptions: IMarkdownRendererExtraOptions, private _commentThread: languages.CommentThread, private _pendingEdits: { [key: number]: languages.PendingComment } | undefined, private _scopedInstatiationService: IInstantiationService, @@ -58,7 +58,7 @@ export class CommentThreadBody extends D this.commentService.setActiveEditingCommentThread(this._commentThread); })); - this._markdownRenderer = this._scopedInstatiationService.createInstance(MarkdownRenderer, this._options); + this._markdownRenderer = this._scopedInstatiationService.createInstance(MarkdownRenderer); } focus(commentUniqueId?: number) { @@ -284,7 +284,8 @@ export class CommentThreadBody extends D this.owner, this.parentResourceUri, this._parentCommentThreadWidget, - this._markdownRenderer) as unknown as CommentNode; + this._markdownRenderer, + this._markdownRendererOptions) as unknown as CommentNode; const disposables: DisposableStore = new DisposableStore(); disposables.add(newCommentNode.onDidClick(clickedNode => diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 21b84b481ea..da6c048f027 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -10,7 +10,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import * as languages from '../../../../editor/common/languages.js'; -import { IMarkdownRendererOptions } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { CommentMenus } from './commentMenus.js'; @@ -69,7 +69,7 @@ export class CommentThreadWidget extends private _commentThread: languages.CommentThread, private _pendingComment: languages.PendingComment | undefined, private _pendingEdits: { [key: number]: languages.PendingComment } | undefined, - private _markdownOptions: IMarkdownRendererOptions, + private _markdownOptions: IMarkdownRendererExtraOptions, private _commentOptions: languages.CommentOptions | undefined, private _containerDelegate: { actionRunner: (() => void) | null; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 47284918de8..06a1cec93b3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -316,7 +316,7 @@ export class InlineChatWidget { this._elements.disclaimerLabel.classList.toggle('hidden', !showDisclaimer); if (showDisclaimer) { - const markdown = this._instantiationService.createInstance(MarkdownRenderer, {}); + const markdown = this._instantiationService.createInstance(MarkdownRenderer); const renderedMarkdown = disposables.add(markdown.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true }))); this._elements.disclaimerLabel.appendChild(renderedMarkdown.element); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index cda89c19f68..2b9a3eaac70 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -845,7 +845,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre ) { super(); - this.markdownRenderer = _instantiationService.createInstance(MarkdownRenderer, {}); + this.markdownRenderer = _instantiationService.createInstance(MarkdownRenderer); this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService); this._register(this._configService.onDidChangeConfiguration(e => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 6cee7026358..c6f009b3980 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2052,7 +2052,7 @@ class SCMInputWidget { this.contextViewService.hideContextView(); })); - const renderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + const renderer = this.instantiationService.createInstance(MarkdownRenderer); const renderedMarkdown = renderer.render(message, { actionHandler: (link, mdStr) => { openLinkFromMarkdown(this.openerService, link, mdStr.isTrusted); diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index fcf662e332c..6f601b80d7b 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -255,7 +255,7 @@ export class DiffContentProvider extends Disposable implements IPeekOutputRender export class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRenderer { private readonly markdown = new Lazy( - () => this.instantiationService.createInstance(MarkdownRenderer, {}), + () => this.instantiationService.createInstance(MarkdownRenderer), ); private readonly rendered = this._register(new DisposableStore()); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 89d49ff6eea..19fc257092a 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -1457,7 +1457,7 @@ class ErrorRenderer implements ITreeRenderer Date: Wed, 8 Oct 2025 13:48:42 -0700 Subject: [PATCH 0993/4355] Add Snooze in gutter view for next edit suggestions (#270420) --- .../view/inlineEdits/components/gutterIndicatorMenu.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts index 50411838174..0e563f96455 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts @@ -105,6 +105,13 @@ export class GutterIndicatorMenuContent { })) ); + const snooze = option(createOptionArgs({ + id: 'snooze', + title: localize('snooze', "Snooze"), + icon: Codicon.bellSlash, + commandId: 'editor.action.inlineSuggest.snooze' + })); + const settings = option(createOptionArgs({ id: 'settings', title: localize('settings', "Settings"), @@ -132,6 +139,7 @@ export class GutterIndicatorMenuContent { reject, toggleCollapsedMode, extensionCommands.length ? separator() : undefined, + snooze, settings, ...extensionCommands, From 8be46892f7f7f7a15456199099d5c0e25a9a65ef Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 8 Oct 2025 22:50:28 +0200 Subject: [PATCH 0994/4355] fix trailing sep in sidebar --- .../browser/parts/auxiliarybar/auxiliaryBarPart.ts | 1 + src/vs/workbench/browser/parts/compositePart.ts | 10 ++++++++-- src/vs/workbench/browser/parts/paneCompositePart.ts | 5 ++--- src/vs/workbench/browser/parts/panel/panelPart.ts | 2 +- src/vs/workbench/browser/parts/sidebar/sidebarPart.ts | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 18ed28fd725..6791fd41d89 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -98,6 +98,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { Parts.AUXILIARYBAR_PART, { hasTitle: true, + trailingSeparator: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0, }, AuxiliaryBarPart.activeViewSettingsKey, diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index fc1d8133d11..e879ad7e21e 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -56,6 +56,10 @@ interface CompositeItem { readonly progress: IProgressIndicator; } +export interface ICompositePartOptions extends IPartOptions { + readonly trailingSeparator?: boolean; +} + export abstract class CompositePart extends Part { protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite; focus: boolean }>()); @@ -76,6 +80,7 @@ export abstract class CompositePart, private paneFocusContextKey: IContextKey, diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 7c40295ff5d..585bc16cde2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -84,7 +84,7 @@ export class PanelPart extends AbstractPaneCompositePart { ) { super( Parts.PANEL_PART, - { hasTitle: true }, + { hasTitle: true, trailingSeparator: true }, PanelPart.activePanelSettingsKey, ActivePanelContext.bindTo(contextKeyService), PanelFocusContext.bindTo(contextKeyService), diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 0b5bd9d49a4..470b69dea83 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -83,7 +83,7 @@ export class SidebarPart extends AbstractPaneCompositePart { ) { super( Parts.SIDEBAR_PART, - { hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, + { hasTitle: true, trailingSeparator: false, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }, SidebarPart.activeViewletSettingsKey, ActiveViewletContext.bindTo(contextKeyService), SidebarFocusContext.bindTo(contextKeyService), From eeb229428103c253662073898afc2d3e2c315dde Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:04:55 -0700 Subject: [PATCH 0995/4355] Add `MarkdownRendererService` Adds a new Markdown renderer service which simplifies handling the most common markdown rendering flows. Also adopts it everywhere except chat --- .../services/hoverService/hoverWidget.ts | 8 ++--- .../browser/markdownRenderer.ts | 36 +++++++++++++++++++ .../contrib/hover/browser/glyphHoverWidget.ts | 9 ++--- .../hover/browser/markdownHoverParticipant.ts | 20 +++++------ .../inlayHints/browser/inlayHintsHover.ts | 6 ++-- .../browser/hintsWidget/hoverParticipant.ts | 6 ++-- .../browser/parameterHintsWidget.ts | 10 ++---- .../suggest/browser/suggestWidgetDetails.ts | 9 ++--- .../browser/bannerController.ts | 9 ++--- .../browser/unicodeHighlighter.ts | 5 +-- .../browser/parts/banner/bannerPart.ts | 8 ++--- .../parts/dialogs/dialog.web.contribution.ts | 7 ++-- .../browser/parts/dialogs/dialogHandler.ts | 11 +++--- .../workbench/browser/parts/views/treeView.ts | 16 ++++----- .../chatSessions/view/sessionsTreeRenderer.ts | 10 ++---- .../contrib/chat/browser/chatStatus.ts | 7 ++-- .../contrib/comments/browser/commentNode.ts | 6 ++-- .../comments/browser/commentThreadBody.ts | 8 ++--- .../preferences/browser/settingsTree.ts | 9 ++--- .../contrib/scm/browser/scmViewPane.ts | 10 +++--- .../testResultsView/testResultsOutput.ts | 14 ++++---- .../testing/browser/testingExplorerView.ts | 11 +++--- .../browser/gettingStarted.ts | 7 ++-- .../parts/dialogs/dialog.contribution.ts | 6 ++-- .../browser/simpleSuggestWidgetDetails.ts | 11 +++--- 25 files changed, 128 insertions(+), 131 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index ce44c98a63b..83f43b73847 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -14,8 +14,7 @@ import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAcc import { Widget } from '../../../../base/browser/ui/widget.js'; import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownRenderer, openLinkFromMarkdown } from '../../widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../widget/markdownRenderer/browser/markdownRenderer.js'; import { isMarkdownString } from '../../../../base/common/htmlContent.js'; import { localize } from '../../../../nls.js'; import { isMacintosh } from '../../../../base/common/platform.js'; @@ -100,7 +99,7 @@ export class HoverWidget extends Widget implements IHoverWidget { @IKeybindingService private readonly _keybindingService: IKeybindingService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IOpenerService private readonly _openerService: IOpenerService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IMarkdownRendererService private readonly _markdownRenderer: IMarkdownRendererService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(); @@ -165,9 +164,8 @@ export class HoverWidget extends Widget implements IHoverWidget { } else { const markdown = options.content; - const mdRenderer = this._instantiationService.createInstance(MarkdownRenderer); - const { element, dispose } = mdRenderer.render(markdown, { + const { element, dispose } = this._markdownRenderer.render(markdown, { actionHandler: (content) => this._linkHandler(content), asyncRenderCallback: () => { contentsElement.classList.add('code-hover-contents'); diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 98ec9b2b59a..996ce988b14 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -8,6 +8,8 @@ import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTyp import { onUnexpectedError } from '../../../../../base/common/errors.js'; import { IMarkdownString, MarkdownStringTrustedOptions } from '../../../../../base/common/htmlContent.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { EditorOption, IEditorOptions } from '../../../../common/config/editorOptions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../../common/config/fontInfo.js'; @@ -90,6 +92,40 @@ export class MarkdownRenderer { } } +export const IMarkdownRendererService = createDecorator('markdownRendererService'); + +export interface IMarkdownRendererService { + readonly _serviceBrand: undefined; + + /** + * Renders markdown with codeblocks with the editor mechanics. + */ + render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown; +} + + +class MarkdownRendererService implements IMarkdownRendererService { + declare readonly _serviceBrand: undefined; + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageService private readonly _languageService: ILanguageService, + @IOpenerService private readonly _openerService: IOpenerService, + ) { } + + render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { + const renderer = new MarkdownRenderer( + this._configurationService, + this._languageService, + this._openerService + ); + return renderer.render(markdown, options, outElement); + } +} + +registerSingleton(IMarkdownRendererService, MarkdownRendererService, InstantiationType.Delayed); + + export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined, skipValidation?: boolean): Promise { try { return await openerService.open(link, { diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index c93a4049b0b..0f30bf9b794 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -5,7 +5,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ICodeEditor, IEditorMouseEvent, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; import { HoverOperation, HoverResult, HoverStartMode } from './hoverOperation.js'; @@ -13,7 +13,6 @@ import { HoverWidget } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IHoverWidget } from './hoverTypes.js'; import { IHoverMessage, LaneOrLineNumber, GlyphHoverComputer, GlyphHoverComputerOptions } from './glyphHoverComputer.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; @@ -27,7 +26,6 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov private _isVisible: boolean; private _messages: IHoverMessage[]; - private readonly _markdownRenderer: MarkdownRenderer; private readonly _hoverOperation: HoverOperation; private readonly _renderDisposeables = this._register(new DisposableStore()); @@ -35,7 +33,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov constructor( editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { super(); this._editor = editor; @@ -46,7 +44,6 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov this._hover = this._register(new HoverWidget(true)); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); - this._markdownRenderer = instantiationService.createInstance(MarkdownRenderer); this._hoverOperation = this._register(new HoverOperation(this._editor, new GlyphHoverComputer(this._editor))); this._register(this._hoverOperation.onResult((result) => this._withResult(result))); @@ -148,7 +145,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov for (const msg of messages) { const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderedContents = this._renderDisposeables.add(this._markdownRenderer.render(msg.value, { editor: this._editor })); + const renderedContents = this._renderDisposeables.add(this._markdownRendererService.render(msg.value, { editor: this._editor })); hoverContentsElement.appendChild(renderedContents.element); fragment.appendChild(markdownHoverElement); } diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index bd562e5be7f..eaa2a87f166 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -8,7 +8,7 @@ import { asArray, compareBy, numberComparator } from '../../../../base/common/ar import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Position } from '../../../common/core/position.js'; @@ -34,7 +34,6 @@ import { getHoverProviderResultsAsAsyncIterable } from './getHover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { HoverStartSource } from './hoverOperation.js'; import { ScrollEvent } from '../../../../base/common/scrollable.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.')); @@ -86,7 +85,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { private readonly _keybindingService: IKeybindingService, private readonly _hoverService: IHoverService, private readonly _configurationService: IConfigurationService, - private readonly _instantiationService: IInstantiationService, + private readonly _markdownRendererService: IMarkdownRendererService, private readonly _onFinishedRendering: () => void, ) { this.renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering); @@ -311,7 +310,7 @@ class MarkdownRenderedHoverParts implements IRenderedHoverParts { const renderedMarkdownHover = renderMarkdown( this._editor, markdownHover, - this._instantiationService, + this._markdownRendererService, onFinishedRendering, ); return renderedMarkdownHover; @@ -471,7 +470,7 @@ export function renderMarkdownHovers( context: IEditorHoverRenderContext, markdownHovers: MarkdownHover[], editor: ICodeEditor, - instantiationService: IInstantiationService, + markdownRendererService: IMarkdownRendererService, ): IRenderedHoverParts { // Sort hover parts to keep them stable since they might come in async, out-of-order @@ -481,7 +480,7 @@ export function renderMarkdownHovers( const renderedHoverPart = renderMarkdown( editor, markdownHover, - instantiationService, + markdownRendererService, context.onContentsChanged, ); context.fragment.appendChild(renderedHoverPart.hoverElement); @@ -493,7 +492,7 @@ export function renderMarkdownHovers( function renderMarkdown( editor: ICodeEditor, markdownHover: MarkdownHover, - instantiationService: IInstantiationService, + markdownRendererService: IMarkdownRendererService, onFinishedRendering: () => void, ): IRenderedHoverPart { const disposables = new DisposableStore(); @@ -507,9 +506,8 @@ function renderMarkdown( } const markdownHoverElement = $('div.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = instantiationService.createInstance(MarkdownRenderer); - const renderedContents = disposables.add(renderer.render(markdownString, { + const renderedContents = disposables.add(markdownRendererService.render(markdownString, { editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index 8548d64f8f1..8ec046bb684 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -26,7 +26,7 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { HoverStartSource } from '../../hover/browser/hoverOperation.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( @@ -45,7 +45,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor constructor( editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IKeybindingService keybindingService: IKeybindingService, @IHoverService hoverService: IHoverService, @IConfigurationService configurationService: IConfigurationService, @@ -53,7 +53,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @ICommandService commandService: ICommandService, ) { - super(editor, instantiationService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService); + super(editor, markdownRendererService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService); } suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts index 93bd4d92c93..0e72582406e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts @@ -14,7 +14,7 @@ import { IModelDecoration } from '../../../../common/model.js'; import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js'; import { InlineCompletionsController } from '../controller/inlineCompletionsController.js'; import { InlineSuggestionHintsContentWidget } from './inlineCompletionsHintsWidget.js'; -import { MarkdownRenderer } from '../../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import * as nls from '../../../../../nls.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -46,6 +46,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { } @@ -150,10 +151,9 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' })); - const renderer = this._instantiationService.createInstance(MarkdownRenderer); const render = (code: string) => { const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); - const renderedContents = disposables.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), { + const renderedContents = disposables.add(this._markdownRendererService.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), { editor: this._editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index 5e90fb215c9..02ff76fea80 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -17,7 +17,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from '../../../common/config/editorOptions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import * as languages from '../../../common/languages.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { ParameterHintsModel } from './parameterHintsModel.js'; import { Context } from './provideSignatureHelp.js'; @@ -26,7 +26,6 @@ import { IContextKey, IContextKeyService } from '../../../../platform/contextkey import { listHighlightForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; const $ = dom.$; @@ -37,7 +36,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private static readonly ID = 'editor.widget.parameterHintsWidget'; - private readonly markdownRenderer: MarkdownRenderer; private readonly renderDisposeables = this._register(new DisposableStore()); private readonly keyVisible: IContextKey; private readonly keyMultipleSignatures: IContextKey; @@ -60,12 +58,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private readonly editor: ICodeEditor, private readonly model: ParameterHintsModel, @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); - this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); } @@ -272,7 +268,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } private renderMarkdownDocs(markdown: IMarkdownString): IRenderedMarkdown { - const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(markdown, { + const renderedContents = this.renderDisposeables.add(this.markdownRendererService.render(markdown, { editor: this.editor, asyncRenderCallback: () => { this.domNodes?.scrollbar.scanDomNode(); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index e61661b8098..65fa98ba47d 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -12,11 +12,10 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import * as nls from '../../../../nls.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { isHighContrast } from '../../../../platform/theme/common/theme.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../browser/editorBrowser.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { CompletionItem } from './suggest.js'; @@ -42,19 +41,17 @@ export class SuggestDetailsWidget { private readonly _docs: HTMLElement; private readonly _disposables = new DisposableStore(); - private readonly _markdownRenderer: MarkdownRenderer; private readonly _renderDisposeable = new DisposableStore(); private _size = new dom.Dimension(330, 0); constructor( private readonly _editor: ICodeEditor, - @IInstantiationService instaService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { this.domNode = dom.$('.suggest-details'); this.domNode.classList.add('no-docs'); - this._markdownRenderer = instaService.createInstance(MarkdownRenderer); this._body = dom.$('.body'); @@ -176,7 +173,7 @@ export class SuggestDetailsWidget { } else if (documentation) { this._docs.classList.add('markdown-docs'); dom.clearNode(this._docs); - const renderedContents = this._markdownRenderer.render(documentation, { + const renderedContents = this._markdownRendererService.render(documentation, { editor: this._editor, asyncRenderCallback: () => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts index 65173478a43..03b183a44bf 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts @@ -9,7 +9,7 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { Action } from '../../../../base/common/actions.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILinkDescriptor, Link } from '../../../../platform/opener/browser/link.js'; @@ -51,19 +51,16 @@ export class BannerController extends Disposable { class Banner extends Disposable { public element: HTMLElement; - private readonly markdownRenderer: MarkdownRenderer; - private messageActionsContainer: HTMLElement | undefined; private actionBar: ActionBar | undefined; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); - this.element = $('div.editor-banner'); this.element.tabIndex = 0; } @@ -86,7 +83,7 @@ class Banner extends Disposable { return element; } - return this.markdownRenderer.render(message).element; + return this.markdownRendererService.render(message).element; } public clear() { diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 3a0552623da..2372f7cbe59 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -32,6 +32,7 @@ import { IWorkspaceTrustManagementService } from '../../../../platform/workspace import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { safeIntl } from '../../../../base/common/date.js'; import { isModelDecorationInComment, isModelDecorationInString, isModelDecorationVisible } from '../../../common/viewModel/viewModelDecoration.js'; +import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.')); @@ -417,7 +418,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa constructor( private readonly _editor: ICodeEditor, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { } computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] { @@ -509,7 +510,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa } public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IRenderedHoverParts { - return renderMarkdownHovers(context, hoverParts, this._editor, this._instantiationService); + return renderMarkdownHovers(context, hoverParts, this._editor, this._markdownRendererService); } public getAccessibleContent(hoverPart: MarkdownHover): string { diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index f3f7dffd046..d7c8978c55b 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -20,7 +20,7 @@ import { Link } from '../../../../platform/opener/browser/link.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Emitter } from '../../../../base/common/event.js'; import { IBannerItem, IBannerService } from '../../../services/banner/browser/bannerService.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -56,7 +56,6 @@ export class BannerPart extends Part implements IBannerService { //#endregion private item: IBannerItem | undefined; - private readonly markdownRenderer: MarkdownRenderer; private visible = false; private actionBar: ActionBar | undefined; @@ -69,10 +68,9 @@ export class BannerPart extends Part implements IBannerService { @IStorageService storageService: IStorageService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(Parts.BANNER_PART, { hasTitle: false }, themeService, storageService, layoutService); - - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); } protected override createContentArea(parent: HTMLElement): HTMLElement { @@ -140,7 +138,7 @@ export class BannerPart extends Part implements IBannerService { return element; } - return this.markdownRenderer.render(message).element; + return this.markdownRendererService.render(message).element; } private setVisibility(visible: boolean): void { diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index ace5f4e84ef..9c2d931342f 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -18,6 +18,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { Lazy } from '../../../../base/common/lazy.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { createBrowserAboutDialogDetails } from '../../../../platform/dialogs/browser/dialog.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { @@ -36,12 +37,12 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @IInstantiationService instantiationService: IInstantiationService, @IProductService private productService: IProductService, @IClipboardService clipboardService: IClipboardService, - @IOpenerService openerService: IOpenerService + @IOpenerService openerService: IOpenerService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, ) { super(); - this.impl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, clipboardService, openerService)); - + this.impl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, clipboardService, openerService, markdownRendererService)); this.model = (this.dialogService as DialogService).model; this._register(this.model.onWillShowDialog(() => { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 9ee726f2133..f3b071f2b34 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -13,7 +13,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { createWorkbenchDialogOptions } from '../../../../platform/dialogs/browser/dialog.js'; @@ -28,19 +28,16 @@ export class BrowserDialogHandler extends AbstractDialogHandler { 'editor.action.clipboardPasteAction' ]; - private readonly markdownRenderer: MarkdownRenderer; - constructor( @ILogService private readonly logService: ILogService, @ILayoutService private readonly layoutService: ILayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IClipboardService private readonly clipboardService: IClipboardService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); - - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); } async prompt(prompt: IPrompt): Promise> { @@ -97,7 +94,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { const renderBody = customOptions ? (parent: HTMLElement) => { parent.classList.add(...(customOptions.classes || [])); customOptions.markdownDetails?.forEach(markdownDetail => { - const result = dialogDisposables.add(this.markdownRenderer.render(markdownDetail.markdown, { + const result = dialogDisposables.add(this.markdownRendererService.render(markdownDetail.markdown, { actionHandler: markdownDetail.actionHandler || ((link, mdStr) => { return openLinkFromMarkdown(this.openerService, link, mdStr.isTrusted, true /* skip URL validation to prevent another dialog from showing which is unsupported */); }), diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 67144723d13..2c1c090c806 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -70,7 +70,7 @@ import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { ITreeViewsDnDService } from '../../../../editor/common/services/treeViewsDndService.js'; import { DraggedTreeItemsIdentifier } from '../../../../editor/common/services/treeViewsDnd.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import type { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; import { parseLinkedText } from '../../../../base/common/linkedText.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; @@ -233,7 +233,6 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private _container: HTMLElement | undefined; private root: ITreeItem; - private markdownRenderer: MarkdownRenderer | undefined; private elementsToRefresh: ITreeItem[] = []; private lastSelection: readonly ITreeItem[] = []; private lastActive: ITreeItem; @@ -283,7 +282,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IActivityService private readonly activityService: IActivityService, @ILogService private readonly logService: ILogService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); this.root = new Root(); @@ -926,7 +926,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { result.push(buttonContainer); } else { hasFoundButton = false; - const rendered = this.markdownRenderer!.render(new MarkdownString(line, { isTrusted: message.isTrusted, supportThemeIcons: message.supportThemeIcons, supportHtml: message.supportHtml })); + const rendered = this.markdownRendererService.render(new MarkdownString(line, { isTrusted: message.isTrusted, supportThemeIcons: message.supportThemeIcons, supportHtml: message.supportHtml })); result.push(rendered.element); disposables.add(rendered); } @@ -948,9 +948,6 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { if (isRenderedMessageValue(this._messageValue)) { this._messageValue.disposables.dispose(); } - if (isMarkdownString(message) && !this.markdownRenderer) { - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); - } if (isMarkdownString(message)) { const disposables = new DisposableStore(); const renderedMessage = this.processMessage(message, disposables); @@ -1771,9 +1768,10 @@ export class CustomTreeView extends AbstractTreeView { @IActivityService activityService: IActivityService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService logService: ILogService, - @IOpenerService openerService: IOpenerService + @IOpenerService openerService: IOpenerService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, ) { - super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService, activityService, logService, openerService); + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService, activityService, logService, openerService, markdownRendererService); } protected activate() { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 9b07cf3a39c..53d0553d8b5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -19,7 +19,7 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../.. import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import Severity from '../../../../../../base/common/severity.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import * as nls from '../../../../../../nls.js'; import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; @@ -27,7 +27,6 @@ import { IConfigurationService } from '../../../../../../platform/configuration/ import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IContextViewService } from '../../../../../../platform/contextview/browser/contextView.js'; import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import product from '../../../../../../platform/product/common/product.js'; import { defaultInputBoxStyles } from '../../../../../../platform/theme/browser/defaultStyles.js'; import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; @@ -111,7 +110,6 @@ export class GettingStartedRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'session'; - private markdownRenderer: MarkdownRenderer; constructor( private readonly viewLocation: ViewContainerLocation | null, @@ -121,15 +119,13 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer extends Disposable { private owner: string, private resource: URI, private parentThread: ICommentThreadWidget, - private readonly markdownRenderer: MarkdownRenderer, private readonly markdownRendererOptions: IMarkdownRendererExtraOptions, @IInstantiationService private instantiationService: IInstantiationService, @ICommentService private commentService: ICommentService, @@ -118,6 +117,7 @@ export class CommentNode extends Disposable { @IHoverService private hoverService: IHoverService, @IKeybindingService private keybindingService: IKeybindingService, @ITextModelService private readonly textModelService: ITextModelService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); @@ -211,7 +211,7 @@ export class CommentNode extends Disposable { this._plainText = dom.append(this._body, dom.$('.comment-body-plainstring')); this._plainText.innerText = body; } else { - this._md.value = this.markdownRenderer.render(body, this.markdownRendererOptions); + this._md.value = this.markdownRendererService.render(body, this.markdownRendererOptions); this._body.appendChild(this._md.value.element); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 0ad913a4fec..4f76fac5722 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -15,7 +15,7 @@ import { CommentNode } from './commentNode.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; -import { IMarkdownRendererExtraOptions, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; @@ -29,7 +29,6 @@ export class CommentThreadBody extends D onDidResize = this._onDidResize.event; private _commentDisposable = new DisposableMap, DisposableStore>(); - private _markdownRenderer: MarkdownRenderer; get length() { return this._commentThread.comments ? this._commentThread.comments.length : 0; @@ -49,7 +48,7 @@ export class CommentThreadBody extends D private _pendingEdits: { [key: number]: languages.PendingComment } | undefined, private _scopedInstatiationService: IInstantiationService, private _parentCommentThreadWidget: ICommentThreadWidget, - @ICommentService private commentService: ICommentService, + @ICommentService private readonly commentService: ICommentService, ) { super(); @@ -57,8 +56,6 @@ export class CommentThreadBody extends D // TODO @rebornix, limit T to IRange | ICellRange this.commentService.setActiveEditingCommentThread(this._commentThread); })); - - this._markdownRenderer = this._scopedInstatiationService.createInstance(MarkdownRenderer); } focus(commentUniqueId?: number) { @@ -284,7 +281,6 @@ export class CommentThreadBody extends D this.owner, this.parentResourceUri, this._parentCommentThreadWidget, - this._markdownRenderer, this._markdownRendererOptions) as unknown as CommentNode; const disposables: DisposableStore = new DisposableStore(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 2b9a3eaac70..040b9b0013b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -34,7 +34,7 @@ import { isIOS } from '../../../../base/common/platform.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; import { isDefined, isUndefinedOrNull } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { localize } from '../../../../nls.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; @@ -824,8 +824,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre protected readonly _onApplyFilter = this._register(new Emitter()); readonly onApplyFilter: Event = this._onApplyFilter.event; - private readonly markdownRenderer: MarkdownRenderer; - constructor( private readonly settingActions: IAction[], private readonly disposableActionFactory: (setting: ISetting, settingTarget: SettingsTarget) => IAction[], @@ -842,11 +840,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre @IProductService protected readonly _productService: IProductService, @ITelemetryService protected readonly _telemetryService: ITelemetryService, @IHoverService protected readonly _hoverService: IHoverService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { super(); - this.markdownRenderer = _instantiationService.createInstance(MarkdownRenderer); - this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService); this._register(this._configService.onDidChangeConfiguration(e => { this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService); @@ -1029,7 +1026,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre // Rewrite `#editor.fontSize#` to link format text = fixSettingLinks(text); - const renderedMarkdown = disposables.add(this.markdownRenderer.render({ value: text, isTrusted: true }, { + const renderedMarkdown = disposables.add(this._markdownRendererService.render({ value: text, isTrusted: true }, { actionHandler: (content: string) => { if (content.startsWith('#')) { const e: ISettingLinkClickEvent = { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index c6f009b3980..e756f9470cc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -72,7 +72,7 @@ import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.j import { Selection } from '../../../../editor/common/core/selection.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; import { createActionViewItem, getFlatActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Button, ButtonWithDescription, ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { RepositoryContextKeys } from './scmViewService.js'; @@ -1845,14 +1845,15 @@ class SCMInputWidget { container: HTMLElement, overflowWidgetsDomNode: HTMLElement, @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, @IModelService private modelService: IModelService, @IKeybindingService private keybindingService: IKeybindingService, @IConfigurationService private configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IContextViewService private readonly contextViewService: IContextViewService, @IOpenerService private readonly openerService: IOpenerService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { this.element = append(container, $('.scm-editor')); this.editorContainer = append(this.element, $('.scm-editor-container')); @@ -2052,8 +2053,7 @@ class SCMInputWidget { this.contextViewService.hideContextView(); })); - const renderer = this.instantiationService.createInstance(MarkdownRenderer); - const renderedMarkdown = renderer.render(message, { + const renderedMarkdown = this.markdownRendererService.render(message, { actionHandler: (link, mdStr) => { openLinkFromMarkdown(this.openerService, link, mdStr.isTrusted); this.element.style.borderBottomLeftRadius = '2px'; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index 6f601b80d7b..0c567285838 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -8,7 +8,6 @@ import { Delayer } from '../../../../../base/common/async.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { Event } from '../../../../../base/common/event.js'; import { Iterable } from '../../../../../base/common/iterator.js'; -import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, combinedDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { ScrollEvent } from '../../../../../base/common/scrollable.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -17,7 +16,7 @@ import { CodeEditorWidget } from '../../../../../editor/browser/widget/codeEdito import { EmbeddedCodeEditorWidget } from '../../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { DiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/diffEditorWidget.js'; import { EmbeddedDiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IDiffEditorOptions, IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; @@ -254,14 +253,15 @@ export class DiffContentProvider extends Disposable implements IPeekOutputRender export class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRenderer { - private readonly markdown = new Lazy( - () => this.instantiationService.createInstance(MarkdownRenderer), - ); + private readonly rendered = this._register(new DisposableStore()); private element?: HTMLElement; - constructor(private readonly container: HTMLElement, @IInstantiationService private readonly instantiationService: IInstantiationService) { + constructor( + private readonly container: HTMLElement, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, + ) { super(); this._register(toDisposable(() => this.clear())); } @@ -278,7 +278,7 @@ export class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRe } - const rendered = this.rendered.add(this.markdown.value.render(message.message, {})); + const rendered = this.rendered.add(this.markdownRendererService.render(message.message, {})); rendered.element.style.userSelect = 'text'; rendered.element.classList.add('preview-text'); this.container.appendChild(rendered.element); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 19fc257092a..7f6ad610685 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -30,7 +30,7 @@ import { fuzzyContains } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { MenuEntryActionViewItem, createActionViewItem, getActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; @@ -1451,14 +1451,11 @@ interface IErrorTemplateData { class ErrorRenderer implements ITreeRenderer { static readonly ID = 'error'; - private readonly renderer: MarkdownRenderer; constructor( @IHoverService private readonly hoverService: IHoverService, - @IInstantiationService instantionService: IInstantiationService, - ) { - this.renderer = instantionService.createInstance(MarkdownRenderer); - } + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, + ) { } get templateId(): string { return ErrorRenderer.ID; @@ -1475,7 +1472,7 @@ class ErrorRenderer implements ITreeRenderer new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, clipboardService, openerService)); + this.browserImpl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, clipboardService, openerService, markdownRendererService)); this.nativeImpl = new Lazy(() => new NativeDialogHandler(logService, nativeHostService, clipboardService)); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts index 5596910b014..ae36b6b3ca5 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts @@ -13,7 +13,7 @@ import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resi import * as nls from '../../../../nls.js'; import { SimpleCompletionItem } from './simpleCompletionItem.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ISimpleSuggestWidgetFontInfo } from './simpleSuggestWidgetRenderer.js'; @@ -41,8 +41,6 @@ export class SimpleSuggestDetailsWidget { private readonly _docs: HTMLElement; private readonly _disposables = new DisposableStore(); - private readonly _markdownRenderer: MarkdownRenderer; - private readonly _renderDisposeable = this._disposables.add(new DisposableStore()); private _borderWidth: number = 1; private _size = new dom.Dimension(330, 0); @@ -51,13 +49,12 @@ export class SimpleSuggestDetailsWidget { private readonly _getFontInfo: () => ISimpleSuggestWidgetFontInfo, onDidFontInfoChange: Event, private readonly _getAdvancedExplainModeDetails: () => string | undefined, - @IInstantiationService instaService: IInstantiationService + @IInstantiationService instaService: IInstantiationService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { this.domNode = dom.$('.suggest-details'); this.domNode.classList.add('no-docs'); - this._markdownRenderer = instaService.createInstance(MarkdownRenderer); - this._body = dom.$('.body'); this._scrollbar = new DomScrollableElement(this._body, { @@ -182,7 +179,7 @@ export class SimpleSuggestDetailsWidget { } else if (documentation) { this._docs.classList.add('markdown-docs'); dom.clearNode(this._docs); - const renderedContents = this._markdownRenderer.render(documentation, { + const renderedContents = this.markdownRendererService.render(documentation, { asyncRenderCallback: () => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); From 212f9cbbe86f0d48c7e3922ed44f0938b114cb10 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 8 Oct 2025 14:24:28 -0700 Subject: [PATCH 0996/4355] Fixed issue with findFiles2 returning dups (#263911 and #263400) --- .../workbench/api/common/extHostWorkspace.ts | 15 ++++++++++- .../api/test/browser/extHostWorkspace.test.ts | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index b93ce34773d..c97d4211b74 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -552,7 +552,20 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac token).then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []) ) ?? []); - return result.flat(); + const flatResult = result.flat(); + + // Dedupe entries in a flat array + const extUri = new ExtUri(uri => ignorePathCasing(uri, this._extHostFileSystemInfo)); + const uriMap = new Map(); + + for (const uri of flatResult) { + const key = extUri.getComparisonKey(uri); + if (!uriMap.has(key)) { + uriMap.set(key, uri); + } + } + + return Array.from(uriMap.values()); } findTextInFiles2(query: vscode.TextSearchQuery2, options: vscode.FindTextInFilesOptions2 | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): vscode.FindTextInFilesResponse { diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index bc9a41a6ee7..09190276e41 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -776,6 +776,32 @@ suite('ExtHostWorkspace', function () { }); }); + test('no dups', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.includePattern, undefined); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + return Promise.resolve([URI.file(root + '/main.py')]); + } + }); + + // Only add the root directory as a workspace folder - main.py will be a file within it + const folders = [aWorkspaceFolderData(URI.file(root), 0)]; + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: folders, name: 'Test' }, new NullLogService()); + + return ws.findFiles2(['**/main.py', '**/main.py/**'], {}, new ExtensionIdentifier('test')).then((uris) => { + assert(mainThreadCalled, 'mainThreadCalled'); + assert.equal(uris.length, 1); + assert.equal(uris[0].toString(), URI.file(root + '/main.py').toString()); + }); + }); + test('with cancelled token', () => { const root = '/project/foo'; const rpcProtocol = new TestRPCProtocol(); From 853c0dd13a141d7c1c5167a8ad6dab3771edbcc1 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 8 Oct 2025 15:12:33 -0700 Subject: [PATCH 0997/4355] Created helper functions for find widget actions. --- .../browser/contrib/find/notebookFind.ts | 119 +++++++----------- 1 file changed, 43 insertions(+), 76 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index 7bd8a16c161..4e5749468b7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -176,6 +176,44 @@ function openFindWidget(controller: NotebookFindContrib | undefined, editor: INo return true; } +function findWidgetAction(accessor: ServicesAccessor, codeEditor: ICodeEditor, next: boolean): boolean { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!isNotebookEditor(accessor, editor, codeEditor)) { + return false; + } + + const controller = editor?.getContribution(NotebookFindContrib.id); + if (!controller) { + return false; + } + + // Check if find widget is already visible + if (controller.isVisible()) { + // Find widget is open, navigate + next ? controller.findNext() : controller.findPrevious(); + return true; + } else { + // Find widget is not open, open it with search string (similar to StartFindAction) + return openFindWidget(controller, editor, codeEditor); + } +} + +async function runFind(accessor: ServicesAccessor, next: boolean): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookFindContrib.id); + if (controller && controller.isVisible()) { + next ? controller.findNext() : controller.findPrevious(); + } +} + StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); @@ -222,56 +260,15 @@ StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeE }); NextMatchFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!isNotebookEditor(accessor, editor, codeEditor)) { - return false; - } - - const controller = editor?.getContribution(NotebookFindContrib.id); - if (!controller) { - return false; - } - - // Check if find widget is already visible - if (controller.isVisible()) { - // Find widget is open, navigate to next match - controller.findNext(); - return true; - } else { - // Find widget is not open, open it with search string (similar to StartFindAction) - return openFindWidget(controller, editor, codeEditor); - } + return findWidgetAction(accessor, codeEditor, true); }); PreviousMatchFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!isNotebookEditor(accessor, editor, codeEditor)) { - return false; - } - - const controller = editor?.getContribution(NotebookFindContrib.id); - if (!controller) { - return false; - } - - // Check if find widget is already visible - if (controller.isVisible()) { - // Find widget is open, navigate to previous match - controller.findPrevious(); - return true; - } else { - // Find widget is not open, open it with search string (similar to StartFindAction) - return openFindWidget(controller, editor, codeEditor); - } + return findWidgetAction(accessor, codeEditor, false); }); // Widget-focused keybindings - these handle F3/Shift+F3 when the notebook find widget has focus // This follows the same pattern as the text editor which has separate keybindings for widget focus - registerAction2(class extends Action2 { constructor() { super({ @@ -290,17 +287,7 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return; - } - - const controller = editor.getContribution(NotebookFindContrib.id); - if (controller && controller.isVisible()) { - controller.findNext(); - } + return runFind(accessor, true); } }); @@ -322,17 +309,7 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return; - } - - const controller = editor.getContribution(NotebookFindContrib.id); - if (controller && controller.isVisible()) { - controller.findPrevious(); - } + return runFind(accessor, false); } }); @@ -353,16 +330,6 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return; - } - - const controller = editor.getContribution(NotebookFindContrib.id); - if (controller && controller.isVisible()) { - controller.findNext(); - } + return runFind(accessor, true); } }); From b0091129f40fcb0a39722edd48244b8c484c7020 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:18:41 -0700 Subject: [PATCH 0998/4355] Move folder reveal logic into general workbench behavior This registers an opener for folders in the workspace. When you try opening a folder in the workspace, we reveal it in the explorer Previously this was done on a case by case basis. I think it makes sense to always have this behavior at the workbench layer --- .../chat/browser/chatMarkdownRenderer.ts | 19 -------- .../opener/browser/opener.contribution.ts | 47 +++++++++++++++++++ src/vs/workbench/workbench.common.main.ts | 2 + 3 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 src/vs/workbench/contrib/opener/browser/opener.contribution.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index cc9d669370f..1e9f96ff301 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -8,16 +8,12 @@ import { IRenderedMarkdown, MarkdownRenderOptions } from '../../../../base/brows import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { URI } from '../../../../base/common/uri.js'; import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import product from '../../../../platform/product/common/product.js'; -import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../files/browser/fileConstants.js'; export const allowedChatMarkdownHtmlTags = Object.freeze([ 'b', @@ -70,8 +66,6 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { @IOpenerService openerService: IOpenerService, @IConfigurationService configurationService: IConfigurationService, @IHoverService private readonly hoverService: IHoverService, - @IFileService private readonly fileService: IFileService, - @ICommandService private readonly commandService: ICommandService, ) { super(configurationService, languageService, openerService); } @@ -131,17 +125,4 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { } }; } - - protected override async openMarkdownLink(link: string, markdown: IMarkdownString) { - try { - const uri = URI.parse(link); - if ((await this.fileService.stat(uri)).isDirectory) { - return this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, uri); - } - } catch { - // noop - } - - return super.openMarkdownLink(link, markdown); - } } diff --git a/src/vs/workbench/contrib/opener/browser/opener.contribution.ts b/src/vs/workbench/contrib/opener/browser/opener.contribution.ts new file mode 100644 index 00000000000..881318a804d --- /dev/null +++ b/src/vs/workbench/contrib/opener/browser/opener.contribution.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IOpener, IOpenerService, OpenExternalOptions, OpenInternalOptions } from '../../../../platform/opener/common/opener.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../files/browser/fileConstants.js'; + +class WorkbenchOpenerContribution extends Disposable implements IOpener { + public static readonly ID = 'workbench.contrib.opener'; + + constructor( + @IOpenerService openerService: IOpenerService, + @ICommandService private readonly commandService: ICommandService, + @IFileService private readonly fileService: IFileService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + ) { + super(); + + this._register(openerService.registerOpener(this)); + } + + async open(link: URI | string, options?: OpenInternalOptions | OpenExternalOptions): Promise { + try { + const uri = typeof link === 'string' ? URI.parse(link) : link; + if (this.workspaceContextService.isInsideWorkspace(uri)) { + if ((await this.fileService.stat(uri)).isDirectory) { + await this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, uri); + return true; + } + } + } catch { + // noop + } + + return false; + } +} + + +registerWorkbenchContribution2(WorkbenchOpenerContribution.ID, WorkbenchOpenerContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index e488669f075..71f45d748a8 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -421,5 +421,7 @@ import './contrib/dropOrPasteInto/browser/dropOrPasteInto.contribution.js'; // Edit Telemetry import './contrib/editTelemetry/browser/editTelemetry.contribution.js'; +// Opener +import './contrib/opener/browser/opener.contribution.js'; //#endregion From dd78f286b9ad107e9c7bfd9a1fb443673289cf1b Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 8 Oct 2025 15:28:05 -0700 Subject: [PATCH 0999/4355] Added helper function for match next action. --- .../contrib/find/browser/findController.ts | 68 +++++++------------ 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 6970a3ac45f..806cb3a6e5b 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -684,30 +684,14 @@ export abstract class MatchFindAction extends EditorAction { protected abstract _run(controller: CommonFindController): boolean; } -export const NextMatchFindAction = registerMultiEditorAction(new MultiEditorAction({ - id: FIND_IDS.NextMatchFindAction, - label: nls.localize2('findNextMatchAction', "Find Next"), - precondition: undefined, - kbOpts: [{ - kbExpr: EditorContextKeys.focus, - primary: KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, - weight: KeybindingWeight.EditorContrib - }, { - kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), - primary: KeyCode.Enter, - weight: KeybindingWeight.EditorContrib - }] -})); - -NextMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise => { +async function matchFindAction(editor: ICodeEditor, next: boolean): Promise { const controller = CommonFindController.get(editor); if (!controller) { return; } const runMatch = (): boolean => { - const result = controller.moveToNextMatch(); + const result = next ? controller.moveToNextMatch() : controller.moveToPrevMatch(); if (result) { controller.editor.pushUndoStop(); return true; @@ -728,6 +712,26 @@ NextMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, edit }); runMatch(); } +} + +export const NextMatchFindAction = registerMultiEditorAction(new MultiEditorAction({ + id: FIND_IDS.NextMatchFindAction, + label: nls.localize2('findNextMatchAction', "Find Next"), + precondition: undefined, + kbOpts: [{ + kbExpr: EditorContextKeys.focus, + primary: KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, + weight: KeybindingWeight.EditorContrib + }, { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + }] +})); + +NextMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise => { + return matchFindAction(editor, true); }); @@ -748,33 +752,7 @@ export const PreviousMatchFindAction = registerMultiEditorAction(new MultiEditor })); PreviousMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise => { - const controller = CommonFindController.get(editor); - if (!controller) { - return; - } - - const runMatch = (): boolean => { - const result = controller.moveToPrevMatch(); - if (result) { - controller.editor.pushUndoStop(); - return true; - } - return false; - }; - - if (!runMatch()) { - await controller.start({ - forceRevealReplace: false, - seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none', - seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection', - seedSearchStringFromGlobalClipboard: true, - shouldFocus: FindStartFocusAction.NoFocusChange, - shouldAnimate: true, - updateSearchScope: false, - loop: editor.getOption(EditorOption.find).loop - }); - runMatch(); - } + return matchFindAction(editor, false); }); export class MoveToMatchFindAction extends EditorAction { From b2de925bcdb58c424f356256c01b8f580437fea2 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:31:46 +0000 Subject: [PATCH 1000/4355] Fix MCP Prompt Input - Prevent empty optional fields from using previous suggestions (#270229) * Initial plan * Fix empty optional field erroneously using previous suggestion Prevent activeItems from being carried over when moving to a new argument field. The bug occurred because the autorun would preserve the previously active item (e.g., "exampleTwo" from fieldTwo) when starting a new field (fieldThree). This caused pressing Enter on an empty optional field to incorrectly use that preserved active item. The fix tracks the first autorun execution for each field and skips activeItems preservation on the first run, ensuring a clean state when starting a new argument field. Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * Add documentation for cross-field contamination fix Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> * fix it properly --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com> Co-authored-by: Connor Peet --- src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts index 0e9245e02a2..c9df1efb3ee 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpPromptArgumentPick.ts @@ -152,10 +152,11 @@ export class McpPromptArgumentPick extends Disposable { quickPick.items = items; const lastActive = items.find(i => previouslyActive.some(a => a.id === i.id)) as PickItem | undefined; + const serverSuggestions = asyncPicks[0].observer; // Keep any selection state, but otherwise select the first completion item, and avoid default-selecting the top item unless there are no compltions if (lastActive) { quickPick.activeItems = [lastActive]; - } else if (items.length > 2) { + } else if (serverSuggestions.read(reader).picks?.length) { quickPick.activeItems = [items[3] as PickItem]; } else if (busy) { quickPick.activeItems = []; From 5c00a3b6c7da313793feb3866c4b8899635a0254 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 8 Oct 2025 17:41:05 -0500 Subject: [PATCH 1001/4355] Disable hiding toolbar items in chat (#270443) Fix #270307 --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 53fa57009d9..505411826d6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1239,7 +1239,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.inputActionsToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, MenuId.ChatInput, { telemetrySource: this.options.menus.telemetrySource, menuOptions: { shouldForwardArgs: true }, - hiddenItemStrategy: HiddenItemStrategy.Ignore, + hiddenItemStrategy: HiddenItemStrategy.NoHide, hoverDelegate, actionViewItemProvider: (action, options) => { if (action.id === ChatOpenModelPickerActionId && action instanceof MenuItemAction) { @@ -1282,7 +1282,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge shouldForwardArgs: true }, hoverDelegate, - hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu + hiddenItemStrategy: HiddenItemStrategy.NoHide, actionViewItemProvider: (action, options) => { if (this.location === ChatAgentLocation.Chat || this.location === ChatAgentLocation.EditorInline) { if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID || action.id === ChatEditingSessionSubmitAction.ID || action.id === ChatDelegateToEditSessionAction.ID) && action instanceof MenuItemAction) { @@ -1365,7 +1365,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge telemetrySource: this.options.menus.telemetrySource, label: true, menuOptions: { shouldForwardArgs: true, renderShortTitle: true }, - hiddenItemStrategy: HiddenItemStrategy.Ignore, + hiddenItemStrategy: HiddenItemStrategy.NoHide, hoverDelegate, actionViewItemProvider: (action, options) => { if (action.id === 'workbench.action.chat.attachContext') { From e6d796af907d1c37d2ca518658b7a43074065e27 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 8 Oct 2025 15:42:16 -0700 Subject: [PATCH 1002/4355] Updated name of helper function --- .../contrib/notebook/browser/contrib/find/notebookFind.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index 4e5749468b7..2cb2f275fe3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -117,7 +117,7 @@ function getSearchStringOptions(editor: ICodeEditor, opts: IFindStartOptions) { return undefined; } -function isNotebookEditor(accessor: ServicesAccessor, editor: INotebookEditor | undefined, codeEditor: ICodeEditor) { +function isNotebookEditorValidForSearch(accessor: ServicesAccessor, editor: INotebookEditor | undefined, codeEditor: ICodeEditor) { if (!editor) { return false; } @@ -180,7 +180,7 @@ function findWidgetAction(accessor: ServicesAccessor, codeEditor: ICodeEditor, n const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!isNotebookEditor(accessor, editor, codeEditor)) { + if (!isNotebookEditorValidForSearch(accessor, editor, codeEditor)) { return false; } @@ -218,7 +218,7 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!isNotebookEditor(accessor, editor, codeEditor)) { + if (!isNotebookEditorValidForSearch(accessor, editor, codeEditor)) { return false; } From 0bece4825b6c227bba810fbe8b2089e34b39a512 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:03:42 -0700 Subject: [PATCH 1003/4355] Replace `MarkdownRenderer` Final step of refactoring in this space. This change: - Introduces an `IMarkdownRenderer` for cases where a top level component needs to control how other components render markdown - Deletes `MarkdownRenderer` - Makes chat adopt either `IMarkdownRenderer` for stuff in a response, or `IMarkdownRendererService` for rendering non-response content --- .../browser/markdownRenderer.ts | 79 +++++++++---------- ...erer.ts => chatContentMarkdownRenderer.ts} | 15 ++-- .../chatConfirmationWidget.ts | 30 ++++--- .../chatErrorConfirmationPart.ts | 4 +- .../chatContentParts/chatErrorContentPart.ts | 6 +- .../chatMarkdownContentPart.ts | 6 +- .../chatMcpServersInteractionContentPart.ts | 9 +-- .../chatProgressContentPart.ts | 12 +-- .../chatContentParts/chatQuotaExceededPart.ts | 4 +- .../chatContentParts/chatTaskContentPart.ts | 6 +- .../chatThinkingContentPart.ts | 7 +- .../chatToolInputOutputContentPart.ts | 2 - .../chatTerminalToolConfirmationSubPart.ts | 4 +- .../chatTerminalToolProgressPart.ts | 8 +- .../chatToolConfirmationSubPart.ts | 4 +- .../chatToolInvocationPart.ts | 4 +- .../chatToolProgressPart.ts | 4 +- .../contrib/chat/browser/chatListRenderer.ts | 28 +++---- .../contrib/chat/browser/chatQuick.ts | 6 +- .../chatSessions/view/sessionsTreeRenderer.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 11 ++- .../viewsWelcome/chatViewWelcomeController.ts | 17 ++-- .../test/browser/chatMarkdownRenderer.test.ts | 6 +- .../inlineChat/browser/inlineChatWidget.ts | 9 ++- 24 files changed, 132 insertions(+), 151 deletions(-) rename src/vs/workbench/contrib/chat/browser/{chatMarkdownRenderer.ts => chatContentMarkdownRenderer.ts} (87%) diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 996ce988b14..e1288404751 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -20,16 +20,43 @@ import { applyFontInfo } from '../../../config/domFontInfo.js'; import { ICodeEditor } from '../../../editorBrowser.js'; import './renderedMarkdown.css'; +/** + * Renders markdown to HTML. + * + * This interface allows a upper level component to pass a custom markdown renderer to sub-components. + * + * If you want to render markdown content in a standard way, prefer using the {@linkcode IMarkdownRendererService}. + */ +export interface IMarkdownRenderer { + render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown; +} + + export interface IMarkdownRendererExtraOptions { readonly editor?: ICodeEditor; readonly codeBlockFontSize?: string; } + +export const IMarkdownRendererService = createDecorator('markdownRendererService'); + /** - * Markdown renderer that can render codeblocks with the editor mechanics. This - * renderer should always be preferred. + * Service that renders markdown content in a standard manner. + * + * Unlike the lower-level {@linkcode renderMarkdown} function, this includes built-in support for features such as syntax + * highlighting of code blocks and link handling. + * + * This service should be preferred for rendering markdown in most cases. */ -export class MarkdownRenderer { +export interface IMarkdownRendererService extends IMarkdownRenderer { + readonly _serviceBrand: undefined; + + render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown; +} + + +class MarkdownRendererService implements IMarkdownRendererService { + declare readonly _serviceBrand: undefined; private static _ttpTokenizer = createTrustedTypesPolicy('tokenizeToString', { createHTML(html: string) { @@ -46,7 +73,9 @@ export class MarkdownRenderer { render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { const rendered = renderMarkdown(markdown, { codeBlockRenderer: (alias, value) => this.renderCodeBlock(alias, value, options ?? {}), - actionHandler: (link, mdStr) => this.openMarkdownLink(link, mdStr), + actionHandler: (link, mdStr) => { + return openLinkFromMarkdown(this._openerService, link, mdStr.isTrusted); + }, ...options, }, outElement); rendered.element.classList.add('rendered-markdown'); @@ -70,7 +99,7 @@ export class MarkdownRenderer { const element = document.createElement('span'); - element.innerHTML = (MarkdownRenderer._ttpTokenizer?.createHTML(html) ?? html) as string; + element.innerHTML = (MarkdownRendererService._ttpTokenizer?.createHTML(html) ?? html) as string; // use "good" font if (options.editor) { @@ -86,46 +115,8 @@ export class MarkdownRenderer { return element; } - - protected async openMarkdownLink(link: string, markdown: IMarkdownString) { - await openLinkFromMarkdown(this._openerService, link, markdown.isTrusted); - } -} - -export const IMarkdownRendererService = createDecorator('markdownRendererService'); - -export interface IMarkdownRendererService { - readonly _serviceBrand: undefined; - - /** - * Renders markdown with codeblocks with the editor mechanics. - */ - render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown; -} - - -class MarkdownRendererService implements IMarkdownRendererService { - declare readonly _serviceBrand: undefined; - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ILanguageService private readonly _languageService: ILanguageService, - @IOpenerService private readonly _openerService: IOpenerService, - ) { } - - render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { - const renderer = new MarkdownRenderer( - this._configurationService, - this._languageService, - this._openerService - ); - return renderer.render(markdown, options, outElement); - } } -registerSingleton(IMarkdownRendererService, MarkdownRendererService, InstantiationType.Delayed); - - export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined, skipValidation?: boolean): Promise { try { return await openerService.open(link, { @@ -151,3 +142,5 @@ function toAllowCommandsOption(isTrusted: boolean | MarkdownStringTrustedOptions return false; // Block commands } + +registerSingleton(IMarkdownRendererService, MarkdownRendererService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts similarity index 87% rename from src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts rename to src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts index 1e9f96ff301..0e53363cec1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts @@ -8,7 +8,7 @@ import { IRenderedMarkdown, MarkdownRenderOptions } from '../../../../base/brows import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer, IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; @@ -58,19 +58,18 @@ export const allowedChatMarkdownHtmlTags = Object.freeze([ ]); /** - * This wraps the MarkdownRenderer and applies sanitizer options needed for Chat. + * This wraps the MarkdownRenderer and applies sanitizer options needed for chat content. */ -export class ChatMarkdownRenderer extends MarkdownRenderer { +export class ChatContentMarkdownRenderer implements IMarkdownRenderer { constructor( @ILanguageService languageService: ILanguageService, @IOpenerService openerService: IOpenerService, @IConfigurationService configurationService: IConfigurationService, @IHoverService private readonly hoverService: IHoverService, - ) { - super(configurationService, languageService, openerService); - } + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, + ) { } - override render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown { + render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown { options = { ...options, sanitizerConfig: { @@ -93,7 +92,7 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { value: `\n\n${markdown.value}`, } : markdown; - const result = super.render(mdWithBody, options, outElement); + const result = this.markdownRendererService.render(mdWithBody, options, outElement); // In some cases, the renderer can return top level text nodes but our CSS expects // all text to be in a

for margin to be applied properly. diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index f14ba28bbe0..3a6f723da43 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import type { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; @@ -76,7 +76,7 @@ export class ChatQueryTitlePart extends Disposable { private readonly element: HTMLElement, private _title: IMarkdownString | string, subtitle: string | IMarkdownString | undefined, - private readonly _renderer: MarkdownRenderer, + @IMarkdownRendererService private readonly _renderer: IMarkdownRendererService, ) { super(); @@ -127,7 +127,6 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { } private readonly messageElement: HTMLElement; - protected readonly markdownRenderer: MarkdownRenderer; private readonly title: string | IMarkdownString; private readonly silent: boolean; @@ -136,6 +135,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { constructor( options: IChatConfirmationWidgetOptions, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IMarkdownRendererService protected readonly _markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IHostService private readonly _hostService: IHostService, @@ -161,14 +161,12 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { ]); configureAccessibilityContainer(elements.container, title, message); this._domNode = elements.root; - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, elements.title, title, - subtitle, - this.markdownRenderer, + subtitle )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); @@ -284,19 +282,20 @@ export class SimpleChatConfirmationWidget extends BaseSimpleChatConfirmationW private readonly _container: HTMLElement, options: IChatConfirmationWidgetOptions, @IInstantiationService instantiationService: IInstantiationService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(options, instantiationService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); this.updateMessage(options.message); } public updateMessage(message: string | IMarkdownString): void { this._renderedMessage?.remove(); - const renderedMessage = this._register(this.markdownRenderer.render( + const renderedMessage = this._register(this._markdownRendererService.render( typeof message === 'string' ? new MarkdownString(message) : message, { asyncRenderCallback: () => this._onDidChangeHeight.fire() } )); @@ -337,7 +336,6 @@ abstract class BaseChatConfirmationWidget extends Disposable { } private readonly messageElement: HTMLElement; - protected readonly markdownRenderer: MarkdownRenderer; private readonly title: string | IMarkdownString; private readonly notification = this._register(new MutableDisposable()); @@ -345,6 +343,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { constructor( options: IChatConfirmationWidget2Options, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IMarkdownRendererService protected readonly markdownRendererService: IMarkdownRendererService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IHostService private readonly _hostService: IHostService, @@ -374,14 +373,11 @@ abstract class BaseChatConfirmationWidget extends Disposable { this._domNode = elements.root; this._buttonsDomNode = elements.buttons; - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); - const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, elements.title, new MarkdownString(icon ? `$(${icon.id}) ${typeof title === 'string' ? title : title.value}` : typeof title === 'string' ? title : title.value), subtitle, - this.markdownRenderer, )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); @@ -456,7 +452,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { protected renderMessage(element: HTMLElement | IMarkdownString | string, listContainer: HTMLElement): void { if (!dom.isHTMLElement(element)) { - const messageElement = this._register(this.markdownRenderer.render( + const messageElement = this._register(this.markdownRendererService.render( typeof element === 'string' ? new MarkdownString(element) : element, { asyncRenderCallback: () => this._onDidChangeHeight.fire() } )); @@ -512,19 +508,20 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget { private readonly _container: HTMLElement, options: IChatConfirmationWidget2Options, @IInstantiationService instantiationService: IInstantiationService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(options, instantiationService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); this.renderMessage(options.message, this._container); } public updateMessage(message: string | IMarkdownString): void { this._renderedMessage?.remove(); - const renderedMessage = this._register(this.markdownRenderer.render( + const renderedMessage = this._register(this.markdownRendererService.render( typeof message === 'string' ? new MarkdownString(message) : message, { asyncRenderCallback: () => this._onDidChangeHeight.fire() } )); @@ -537,13 +534,14 @@ export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget< container: HTMLElement, options: IChatConfirmationWidget2Options, @IInstantiationService instantiationService: IInstantiationService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(options, instantiationService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); this.renderMessage(options.message, container); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts index 1d4f9d18389..ed0dc1ab2d3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts @@ -8,7 +8,7 @@ import { Button, IButtonOptions } from '../../../../../base/browser/ui/button/bu import { Emitter } from '../../../../../base/common/event.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ChatErrorLevel, IChatResponseErrorDetailsConfirmationButton, IChatSendRequestOptions, IChatService } from '../../common/chatService.js'; @@ -30,7 +30,7 @@ export class ChatErrorConfirmationContentPart extends Disposable implements ICha content: IMarkdownString, private readonly errorDetails: IChatErrorDetailsPart, confirmationButtons: IChatResponseErrorDetailsConfirmationButton[], - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, @IChatWidgetService chatWidgetService: IChatWidgetService, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts index fe6bb9b874e..65e103ecaf2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts @@ -8,7 +8,7 @@ import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels. import { Codicon } from '../../../../../base/common/codicons.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ChatErrorLevel } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { IChatContentPart } from './chatContentParts.js'; @@ -22,7 +22,7 @@ export class ChatErrorContentPart extends Disposable implements IChatContentPart kind: ChatErrorLevel, content: IMarkdownString, private readonly errorDetails: IChatRendererContent, - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, ) { super(); @@ -40,7 +40,7 @@ export class ChatErrorWidget extends Disposable { constructor( kind: ChatErrorLevel, content: IMarkdownString, - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, ) { super(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index e46a03c6a52..eab7a4dba76 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -21,7 +21,7 @@ import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js'; import { equalsIgnoreCase } from '../../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../../editor/common/model.js'; @@ -52,7 +52,7 @@ import { ChatConfiguration } from '../../common/constants.js'; import { IChatCodeBlockInfo } from '../chat.js'; import { IChatRendererDelegate } from '../chatListRenderer.js'; import { ChatMarkdownDecorationsRenderer } from '../chatMarkdownDecorationsRenderer.js'; -import { allowedChatMarkdownHtmlTags } from '../chatMarkdownRenderer.js'; +import { allowedChatMarkdownHtmlTags } from '../chatContentMarkdownRenderer.js'; import { ChatEditorOptions } from '../chatOptions.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions, localFileLanguageId, parseLocalFileData } from '../codeBlockPart.js'; import { IDisposableReference, ResourcePool } from './chatCollections.js'; @@ -88,7 +88,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP private readonly editorPool: EditorPool, fillInIncompleteTokens = false, codeBlockStartIndex = 0, - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, markdownRenderOptions: MarkdownRenderOptions | undefined, currentWidth: number, private readonly codeBlockModelCollection: CodeBlockModelCollection, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index 2db31595b5b..89673fde664 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -12,7 +12,7 @@ import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -34,7 +34,6 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements private workingProgressPart: ChatProgressContentPart | undefined; private interactionContainer: HTMLElement | undefined; private readonly interactionMd = this._register(new MutableDisposable()); - private readonly markdownRenderer: MarkdownRenderer; private readonly showSpecificServersScheduler = this._register(new RunOnceScheduler(() => this.updateDetailedProgress(this.data.state!.get()), 2500)); private readonly previousParts = new Lazy(() => { if (!isResponseVM(this.context.element)) { @@ -53,11 +52,11 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements @IMcpService private readonly mcpService: IMcpService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly _openerService: IOpenerService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { super(); this.domNode = dom.$('.chat-mcp-servers-interaction'); - this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer); // Listen to autostart state changes if available if (data.state) { @@ -139,7 +138,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements this.workingProgressPart = this._register(this.instantiationService.createInstance( ChatProgressContentPart, { kind: 'progressMessage', content }, - this.markdownRenderer, + this._markdownRendererService, this.context, true, // forceShowSpinner true, // forceShowMessage @@ -181,7 +180,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements ? localize('mcp.start.single', 'The MCP server {0} may have new tools and requires interaction to start. [Start it now?]({1})', links, '#start') : localize('mcp.start.multiple', 'The MCP servers {0} may have new tools and require interaction to start. [Start them now?]({1})', links, '#start'); const str = new MarkdownString(content, { isTrusted: true }); - const messageMd = this.interactionMd.value = this.markdownRenderer.render(str, { + const messageMd = this.interactionMd.value = this._markdownRendererService.render(str, { asyncRenderCallback: () => this._onDidChangeHeight.fire(), actionHandler: (content) => { if (!content.startsWith('command:')) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 4a736c6d756..ca503f0d28f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -9,7 +9,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; @@ -31,7 +31,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP constructor( progress: IChatProgressMessage | IChatTask | IChatTaskSerialized, - private readonly renderer: MarkdownRenderer, + private readonly chatContentMarkdownRenderer: IMarkdownRenderer, context: IChatContentPartRenderContext, forceShowSpinner: boolean | undefined, forceShowMessage: boolean | undefined, @@ -57,7 +57,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP alert(progress.content.value); } const codicon = icon ? icon : this.showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin') : Codicon.check; - const result = this.renderer.render(progress.content); + const result = this.chatContentMarkdownRenderer.render(progress.content); result.element.classList.add('progress-step'); renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store); @@ -75,7 +75,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP } // Render the new message - const result = this._register(this.renderer.render(content)); + const result = this._register(this.chatContentMarkdownRenderer.render(content)); result.element.classList.add('progress-step'); renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store); @@ -127,7 +127,7 @@ export class ChatCustomProgressPart { export class ChatWorkingProgressContentPart extends ChatProgressContentPart implements IChatContentPart { constructor( _workingProgress: { kind: 'working' }, - renderer: MarkdownRenderer, + chatContentMarkdownRenderer: IMarkdownRenderer, context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, @IChatMarkdownAnchorService chatMarkdownAnchorService: IChatMarkdownAnchorService, @@ -137,7 +137,7 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; - super(progressMessage, renderer, context, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); + super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts index 0c43e1b645f..71dc0ab0071 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts @@ -12,7 +12,7 @@ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { assertType } from '../../../../../base/common/types.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; @@ -47,7 +47,7 @@ export class ChatQuotaExceededPart extends Disposable implements IChatContentPar constructor( element: IChatResponseViewModel, private readonly content: IChatErrorDetailsPart, - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, @IChatWidgetService chatWidgetService: IChatWidgetService, @ICommandService commandService: ICommandService, @ITelemetryService telemetryService: ITelemetryService, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 8ce898d086e..3ae00bd8694 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -6,7 +6,7 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js'; import { IChatTask, IChatTaskSerialized } from '../../common/chatService.js'; @@ -23,7 +23,7 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart constructor( private readonly task: IChatTask | IChatTaskSerialized, contentReferencesListPool: CollapsibleListPool, - renderer: MarkdownRenderer, + chatContentMarkdownRenderer: IMarkdownRenderer, context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, ) { @@ -41,7 +41,7 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart true; this.isSettled = isSettled; const showSpinner = !isSettled && !context.element.isComplete; - const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, renderer, context, showSpinner, true, undefined)); + const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined)); this.domNode = progressPart.domNode; this.onDidChangeHeight = Event.None; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 229df927050..69bd01797ea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -11,7 +11,7 @@ import { ChatTreeItem } from '../chat.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js'; import { localize } from '../../../../../nls.js'; @@ -41,7 +41,6 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen private currentThinkingValue: string; private currentTitle: string; private defaultTitle = localize('chat.thinking.header', 'Thinking...'); - private readonly renderer: MarkdownRenderer; private textContainer!: HTMLElement; private markdownResult: IRenderedMarkdown | undefined; private wrapper!: HTMLElement; @@ -60,6 +59,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { const initialText = extractTextFromPart(content); const extractedTitle = extractTitleFromThinkingContent(initialText) @@ -67,7 +67,6 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen super(extractedTitle, context); - this.renderer = instantiationService.createInstance(MarkdownRenderer); this.id = content.id; const mode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? 'none'; @@ -222,7 +221,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen } clearNode(this.textContainer); - this.markdownResult = this._register(this.renderer.render(new MarkdownString(cleanedContent))); + this.markdownResult = this._register(this.markdownRendererService.render(new MarkdownString(cleanedContent))); this.textContainer.appendChild(this.markdownResult.element); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index 8be89ec49fa..8ed2dc40970 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -14,7 +14,6 @@ import { basename, joinPath } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { localize, localize2 } from '../../../../../nls.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; @@ -120,7 +119,6 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { titleEl.root, title, subtitle, - _instantiationService.createInstance(MarkdownRenderer), )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 1498a87d612..879e4fde1c4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -16,7 +16,7 @@ import Severity from '../../../../../../base/common/severity.js'; import { isObject } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; @@ -69,7 +69,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS toolInvocation: IChatToolInvocation, terminalData: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData, private readonly context: IChatContentPartRenderContext, - private readonly renderer: MarkdownRenderer, + private readonly renderer: IMarkdownRenderer, private readonly editorPool: EditorPool, private readonly currentWidthDelegate: () => number, private readonly codeBlockModelCollection: CodeBlockModelCollection, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index fedfd58cca9..914063727a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -6,9 +6,8 @@ import { h } from '../../../../../../base/browser/dom.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { isMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; import { IPreferencesService, type IOpenSettingsOptions } from '../../../../../services/preferences/common/preferences.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, type IChatMarkdownContent, type IChatTerminalToolInvocationData, type ILegacyChatTerminalToolInvocationData } from '../../../common/chatService.js'; @@ -38,13 +37,12 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, terminalData: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData, context: IChatContentPartRenderContext, - renderer: MarkdownRenderer, + renderer: IMarkdownRenderer, editorPool: EditorPool, currentWidthDelegate: () => number, codeBlockStartIndex: number, codeBlockModelCollection: CodeBlockModelCollection, @IInstantiationService instantiationService: IInstantiationService, - @IOpenerService openerService: IOpenerService, ) { super(toolInvocation); @@ -57,13 +55,11 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart const command = terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original; - const markdownRenderer = instantiationService.createInstance(MarkdownRenderer); const titlePart = this._register(instantiationService.createInstance( ChatQueryTitlePart, elements.title, new MarkdownString(`$(${Codicon.terminal.id})\n\n\`\`\`${terminalData.language}\n${command}\n\`\`\``, { supportThemeIcons: true }), undefined, - markdownRenderer, )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index 3a3c25bfc22..ef92ac605e7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -12,7 +12,7 @@ import { count } from '../../../../../../base/common/strings.js'; import { isEmptyObject } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { ElementSizeObserver } from '../../../../../../editor/browser/config/elementSizeObserver.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { localize } from '../../../../../../nls.js'; @@ -48,7 +48,7 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { constructor( toolInvocation: IChatToolInvocation, private readonly context: IChatContentPartRenderContext, - private readonly renderer: MarkdownRenderer, + private readonly renderer: IMarkdownRenderer, private readonly editorPool: EditorPool, private readonly currentWidthDelegate: () => number, private readonly codeBlockModelCollection: CodeBlockModelCollection, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index c8454d6ed9c..40e88cef9a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../base/common/event.js'; import { markdownCommandLink, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../../../nls.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; @@ -47,7 +47,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa constructor( private readonly toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, private readonly context: IChatContentPartRenderContext, - private readonly renderer: MarkdownRenderer, + private readonly renderer: IMarkdownRenderer, private readonly listPool: CollapsibleListPool, private readonly editorPool: EditorPool, private readonly currentWidthDelegate: () => number, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index fa723001a7c..a29c30f20c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { status } from '../../../../../../base/browser/ui/aria/aria.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { autorun } from '../../../../../../base/common/observable.js'; -import { MarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressMessage, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; @@ -25,7 +25,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { constructor( toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, private readonly context: IChatContentPartRenderContext, - private readonly renderer: MarkdownRenderer, + private readonly renderer: IMarkdownRenderer, private readonly announcedToolProgressKeys: Set | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 108ed1182ac..9286a53114e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -30,7 +30,7 @@ import { FileAccess } from '../../../../base/common/network.js'; import { clamp } from '../../../../base/common/numbers.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { IMenuEntryActionViewItemOptions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; @@ -86,7 +86,7 @@ import { ChatThinkingContentPart } from './chatContentParts/chatThinkingContentP import { ChatTreeContentPart, TreePool } from './chatContentParts/chatTreeContentPart.js'; import { ChatToolInvocationPart } from './chatContentParts/toolInvocationParts/chatToolInvocationPart.js'; import { ChatMarkdownDecorationsRenderer } from './chatMarkdownDecorationsRenderer.js'; -import { ChatMarkdownRenderer } from './chatMarkdownRenderer.js'; +import { ChatContentMarkdownRenderer } from './chatContentMarkdownRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; import { ChatCodeBlockContentProvider, CodeBlockPart } from './codeBlockPart.js'; import { ChatAnonymousRateLimitedPart } from './chatContentParts/chatAnonymousRateLimitedPart.js'; @@ -155,7 +155,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer(); - private readonly renderer: MarkdownRenderer; + private readonly chatContentMarkdownRenderer: IMarkdownRenderer; private readonly markdownDecorationsRenderer: ChatMarkdownDecorationsRenderer; protected readonly _onDidClickFollowup = this._register(new Emitter()); readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; @@ -222,7 +222,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer errorPart.dispose(), domNode: errorPart.domNode, @@ -1291,7 +1291,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.updateItemHeight(templateData))); return renderedError; } else if (content.errorDetails.isRateLimited && this.chatEntitlementService.anonymous) { @@ -1299,12 +1299,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.updateItemHeight(templateData))); return errorConfirmation; } else { const level = content.errorDetails.level ?? ChatErrorLevel.Error; - return this.instantiationService.createInstance(ChatErrorContentPart, level, new MarkdownString(content.errorDetails.message), content, this.renderer); + return this.instantiationService.createInstance(ChatErrorContentPart, level, new MarkdownString(content.errorDetails.message), content, this.chatContentMarkdownRenderer); } } @@ -1419,7 +1419,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._currentLayoutWidth, this._toolInvocationCodeBlockCollection, this._announcedToolProgressKeys, codeBlockStartIndex); + const part = this.instantiationService.createInstance(ChatToolInvocationPart, toolInvocation, context, this.chatContentMarkdownRenderer, this._contentReferencesListPool, this._toolEditorPool, () => this._currentLayoutWidth, this._toolInvocationCodeBlockCollection, this._announcedToolProgressKeys, codeBlockStartIndex); part.addDisposable(part.onDidChangeHeight(() => { this.updateItemHeight(templateData); })); @@ -1444,7 +1444,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { this.updateItemHeight(templateData); })); @@ -1492,7 +1492,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.editRequests') === 'inline' && this.rendererOptions.editable) { diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 5077636246b..6e4a8ccde71 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -12,7 +12,7 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { autorun } from '../../../../base/common/observable.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Selection } from '../../../../editor/common/core/selection.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { MenuId } from '../../../../platform/actions/common/actions.js'; import { localize } from '../../../../nls.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -162,6 +162,7 @@ class QuickChat extends Disposable { @ILayoutService private readonly layoutService: ILayoutService, @IViewsService private readonly viewsService: IViewsService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); } @@ -261,8 +262,7 @@ class QuickChat extends Disposable { disclaimerElement.classList.toggle('hidden', !showDisclaimer); if (showDisclaimer) { - const markdown = this.instantiationService.createInstance(MarkdownRenderer); - const renderedMarkdown = disposables.add(markdown.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true }))); + const renderedMarkdown = disposables.add(this.markdownRendererService.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true }))); disclaimerElement.appendChild(renderedMarkdown.element); } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 53d0553d8b5..23f195cfa50 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -39,7 +39,7 @@ import { IChatService } from '../../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../../common/chatSessionsService.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { IChatWidgetService } from '../../chat.js'; -import { allowedChatMarkdownHtmlTags } from '../../chatMarkdownRenderer.js'; +import { allowedChatMarkdownHtmlTags } from '../../chatContentMarkdownRenderer.js'; import { ChatSessionItemWithProvider, extractTimestamp, getSessionItemContextOverlay, isLocalChatSessionItem, processSessionsWithTimeGrouping } from '../common.js'; import '../../media/chatSessions.css'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index ba6271dd47b..503be92d784 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -24,7 +24,7 @@ import { equalsIgnoreCase } from '../../../../base/common/strings.js'; import { isObject } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -637,7 +637,7 @@ class ChatSetup { let instance = ChatSetup.instance; if (!instance) { instance = ChatSetup.instance = instantiationService.invokeFunction(accessor => { - return new ChatSetup(context, controller, instantiationService, accessor.get(ITelemetryService), accessor.get(IWorkbenchLayoutService), accessor.get(IKeybindingService), accessor.get(IChatEntitlementService) as ChatEntitlementService, accessor.get(ILogService), accessor.get(IConfigurationService), accessor.get(IViewsService), accessor.get(IWorkspaceTrustRequestService)); + return new ChatSetup(context, controller, accessor.get(ITelemetryService), accessor.get(IWorkbenchLayoutService), accessor.get(IKeybindingService), accessor.get(IChatEntitlementService) as ChatEntitlementService, accessor.get(ILogService), accessor.get(IConfigurationService), accessor.get(IViewsService), accessor.get(IWorkspaceTrustRequestService), accessor.get(IMarkdownRendererService)); }); } @@ -651,7 +651,6 @@ class ChatSetup { private constructor( private readonly context: ChatEntitlementContext, private readonly controller: Lazy, - @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -659,7 +658,8 @@ class ChatSetup { @ILogService private readonly logService: ILogService, @IConfigurationService private readonly configurationService: IConfigurationService, @IViewsService private readonly viewsService: IViewsService, - @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService + @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { } skipDialog(): void { @@ -833,7 +833,6 @@ class ChatSetup { private createDialogFooter(disposables: DisposableStore, options?: { forceAnonymous?: ChatSetupAnonymous }): HTMLElement { const element = $('.chat-setup-dialog-footer'); - const markdown = this.instantiationService.createInstance(MarkdownRenderer); let footer: string; if (options?.forceAnonymous || this.telemetryService.telemetryLevel === TelemetryLevel.NONE) { @@ -841,7 +840,7 @@ class ChatSetup { } else { footer = localize({ key: 'settings', comment: ['{Locked="["}', '{Locked="]({1})"}', '{Locked="]({2})"}', '{Locked="]({4})"}', '{Locked="]({5})"}'] }, "By continuing, you agree to {0}'s [Terms]({1}) and [Privacy Statement]({2}). {3} Copilot may show [public code]({4}) suggestions and use your data to improve the product. You can change these [settings]({5}) anytime.", defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl, defaultChat.provider.default.name, defaultChat.publicCodeMatchesUrl, defaultChat.manageSettingsUrl); } - element.appendChild($('p', undefined, disposables.add(markdown.render(new MarkdownString(footer, { isTrusted: true }))).element)); + element.appendChild($('p', undefined, disposables.add(this.markdownRendererService.render(new MarkdownString(footer, { isTrusted: true }))).element)); return element; } diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index fb1b7d8ea6d..c3bf9be362e 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -12,7 +12,7 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -136,18 +136,17 @@ export class ChatViewWelcomePart extends Disposable { public readonly content: IChatViewWelcomeContent, options: IChatViewWelcomeRenderOptions | undefined, @IOpenerService private openerService: IOpenerService, - @IInstantiationService private instantiationService: IInstantiationService, @ILogService private logService: ILogService, @IChatWidgetService private chatWidgetService: IChatWidgetService, @ITelemetryService private telemetryService: ITelemetryService, @IConfigurationService private configurationService: IConfigurationService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); this.element = dom.$('.chat-welcome-view'); try { - const renderer = this.instantiationService.createInstance(MarkdownRenderer); // Icon const icon = dom.append(this.element, $('.chat-welcome-view-icon')); @@ -170,7 +169,7 @@ export class ChatViewWelcomePart extends Disposable { const message = dom.append(this.element, content.isNew ? $('.chat-welcome-new-view-message') : $('.chat-welcome-view-message')); message.classList.toggle('empty-state', expEmptyState); - const messageResult = this.renderMarkdownMessageContent(renderer, content.message, options); + const messageResult = this.renderMarkdownMessageContent(content.message, options); dom.append(message, messageResult.element); if (content.isNew && content.inputPart) { @@ -184,7 +183,7 @@ export class ChatViewWelcomePart extends Disposable { if (typeof content.additionalMessage === 'string') { disclaimers.textContent = content.additionalMessage; } else { - const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options); + const additionalMessageResult = this.renderMarkdownMessageContent(content.additionalMessage, options); disclaimers.appendChild(additionalMessageResult.element); } } @@ -253,7 +252,7 @@ export class ChatViewWelcomePart extends Disposable { // Tips if (content.tips) { const tips = dom.append(this.element, $('.chat-welcome-view-tips')); - const tipsResult = this._register(renderer.render(content.tips)); + const tipsResult = this._register(this.markdownRendererService.render(content.tips)); tips.appendChild(tipsResult.element); } @@ -263,7 +262,7 @@ export class ChatViewWelcomePart extends Disposable { if (typeof content.additionalMessage === 'string') { additionalMsg.textContent = content.additionalMessage; } else { - const additionalMessageResult = this.renderMarkdownMessageContent(renderer, content.additionalMessage, options); + const additionalMessageResult = this.renderMarkdownMessageContent(content.additionalMessage, options); additionalMsg.appendChild(additionalMessageResult.element); } } @@ -287,8 +286,8 @@ export class ChatViewWelcomePart extends Disposable { })); } - private renderMarkdownMessageContent(renderer: MarkdownRenderer, content: IMarkdownString, options: IChatViewWelcomeRenderOptions | undefined): IRenderedMarkdown { - const messageResult = this._register(renderer.render(content)); + private renderMarkdownMessageContent(content: IMarkdownString, options: IChatViewWelcomeRenderOptions | undefined): IRenderedMarkdown { + const messageResult = this._register(this.markdownRendererService.render(content)); const firstLink = options?.firstLinkToButton ? messageResult.element.querySelector('a') : undefined; if (firstLink) { const target = firstLink.getAttribute('data-href'); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts index 02257383f25..1c26d03fe08 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts @@ -6,16 +6,16 @@ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { assertSnapshot } from '../../../../../base/test/common/snapshot.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { ChatMarkdownRenderer } from '../../browser/chatMarkdownRenderer.js'; +import { ChatContentMarkdownRenderer } from '../../browser/chatContentMarkdownRenderer.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; suite('ChatMarkdownRenderer', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); - let testRenderer: ChatMarkdownRenderer; + let testRenderer: ChatContentMarkdownRenderer; setup(() => { const instantiationService = store.add(workbenchInstantiationService(undefined, store)); - testRenderer = instantiationService.createInstance(ChatMarkdownRenderer); + testRenderer = instantiationService.createInstance(ChatContentMarkdownRenderer); }); test('simple', async () => { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 06a1cec93b3..c4e82b03efc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -14,7 +14,7 @@ import { DisposableStore, MutableDisposable, toDisposable } from '../../../../ba import { autorun, constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { EditorOption, IComputedEditorOptions } from '../../../../editor/common/config/editorOptions.js'; import { LineRange } from '../../../../editor/common/core/ranges/lineRange.js'; import { Position } from '../../../../editor/common/core/position.js'; @@ -127,6 +127,7 @@ export class InlineChatWidget { @IChatService private readonly _chatService: IChatService, @IHoverService private readonly _hoverService: IHoverService, @IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService, + @IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService, ) { this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget)); const scopedInstaService = _instantiationService.createChild( @@ -316,8 +317,7 @@ export class InlineChatWidget { this._elements.disclaimerLabel.classList.toggle('hidden', !showDisclaimer); if (showDisclaimer) { - const markdown = this._instantiationService.createInstance(MarkdownRenderer); - const renderedMarkdown = disposables.add(markdown.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true }))); + const renderedMarkdown = disposables.add(this._markdownRendererService.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true }))); this._elements.disclaimerLabel.appendChild(renderedMarkdown.element); } @@ -546,6 +546,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { @IHoverService hoverService: IHoverService, @ILayoutService layoutService: ILayoutService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, ) { const overflowWidgetsNode = layoutService.getContainer(getWindow(_parentEditor.getContainerDomNode())).appendChild($('.inline-chat-overflow.monaco-editor')); super(location, { @@ -554,7 +555,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { ...options.chatWidgetViewOptions, editorOverflowWidgetsDomNode: overflowWidgetsNode } - }, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService, chatEntitlementService); + }, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService, chatEntitlementService, markdownRendererService); this._store.add(toDisposable(() => { overflowWidgetsNode.remove(); From 0f762fd42ee9c19691bf308395c286b595522b6f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 9 Oct 2025 10:10:25 +1100 Subject: [PATCH 1004/4355] Removed `(from Find Widget)` and `(Enter)` from the key binding titles, as the context isn't useful in the titles --- .../contrib/notebook/browser/contrib/find/notebookFind.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index 2cb2f275fe3..1315bc70422 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -273,7 +273,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.findNext.fromWidget', - title: localize2('notebook.findNext.fromWidget', 'Find Next (from Find Widget)'), + title: localize2('notebook.findNext.fromWidget', 'Find Next'), keybinding: { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, @@ -295,7 +295,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.findPrevious.fromWidget', - title: localize2('notebook.findPrevious.fromWidget', 'Find Previous (from Find Widget)'), + title: localize2('notebook.findPrevious.fromWidget', 'Find Previous'), keybinding: { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, @@ -317,7 +317,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.findNext.enter', - title: localize2('notebook.findNext.enter', 'Find Next (Enter)'), + title: localize2('notebook.findNext.enter', 'Find Next'), keybinding: { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, From 22bc3379569cbd19bc2b357e7d92b796e05dcfde Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:10:55 -0700 Subject: [PATCH 1005/4355] Also make sure MarkdownRendererService exists for tests --- .../browser/widget/markdownRenderer/browser/markdownRenderer.ts | 2 +- src/vs/workbench/test/browser/workbenchTestServices.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index e1288404751..2f3fffd5023 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -55,7 +55,7 @@ export interface IMarkdownRendererService extends IMarkdownRenderer { } -class MarkdownRendererService implements IMarkdownRendererService { +export class MarkdownRendererService implements IMarkdownRendererService { declare readonly _serviceBrand: undefined; private static _ttpTokenizer = createTrustedTypesPolicy('tokenizeToString', { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 1950e6f1f7b..1d20a4dcaab 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -26,6 +26,7 @@ import { assertReturnsDefined, upcast } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { ICodeEditor } from '../../../editor/browser/editorBrowser.js'; import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js'; +import { IMarkdownRendererService, MarkdownRendererService } from '../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Position as EditorPosition, IPosition } from '../../../editor/common/core/position.js'; import { Range } from '../../../editor/common/core/range.js'; import { Selection } from '../../../editor/common/core/selection.js'; @@ -372,6 +373,7 @@ export function workbenchInstantiationService( instantiationService.stub(ICustomEditorLabelService, disposables.add(new CustomEditorLabelService(configService, workspaceContextService))); instantiationService.stub(IHoverService, NullHoverService); instantiationService.stub(IChatEntitlementService, new TestChatEntitlementService()); + instantiationService.stub(IMarkdownRendererService, instantiationService.createInstance(MarkdownRendererService)); return instantiationService; } From d751a3d55f8809e4f891f1bb7172e18867d6926f Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:49:23 -0700 Subject: [PATCH 1006/4355] Add device code flow when not brokered (#270453) fixes https://github.com/microsoft/vscode/issues/270452 --- .../src/common/loopbackClientAndOpener.ts | 7 +- .../src/common/publicClientCache.ts | 3 +- .../test/loopbackClientAndOpener.test.ts | 7 +- .../src/node/authProvider.ts | 16 ++-- .../src/node/cachedPublicClientApplication.ts | 83 ++++++++++++++++- .../src/node/flows.ts | 44 +++++++-- .../src/node/test/flows.test.ts | 89 +++++++++++++++++++ 7 files changed, 223 insertions(+), 26 deletions(-) create mode 100644 extensions/microsoft-authentication/src/node/test/flows.test.ts diff --git a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts index e68663efe43..3d4c56723ea 100644 --- a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts +++ b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts @@ -19,6 +19,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { constructor( private readonly _uriHandler: UriEventHandler, private readonly _redirectUri: string, + private readonly _callbackUri: Uri, private readonly _logger: LogOutputChannel ) { } @@ -44,9 +45,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { } async openBrowser(url: string): Promise { - const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); - - if (isSupportedClient(callbackUri)) { + if (isSupportedClient(this._callbackUri)) { void this._getCodeResponseFromUriHandler(); } else { // Unsupported clients will be shown the code in the browser, but it will not redirect back since this @@ -55,7 +54,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { void this._getCodeResponseFromQuickPick(); } - const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`); + const uri = Uri.parse(url + `&state=${encodeURI(this._callbackUri.toString(true))}`); await env.openExternal(uri); } diff --git a/extensions/microsoft-authentication/src/common/publicClientCache.ts b/extensions/microsoft-authentication/src/common/publicClientCache.ts index dfbca6b5057..f39ae8297f9 100644 --- a/extensions/microsoft-authentication/src/common/publicClientCache.ts +++ b/extensions/microsoft-authentication/src/common/publicClientCache.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { AccountInfo, AuthenticationResult, InteractiveRequest, RefreshTokenRequest, SilentFlowRequest } from '@azure/msal-node'; +import type { AccountInfo, AuthenticationResult, InteractiveRequest, RefreshTokenRequest, SilentFlowRequest, DeviceCodeRequest } from '@azure/msal-node'; import type { Disposable, Event } from 'vscode'; export interface ICachedPublicClientApplication { @@ -10,6 +10,7 @@ export interface ICachedPublicClientApplication { onDidRemoveLastAccount: Event; acquireTokenSilent(request: SilentFlowRequest): Promise; acquireTokenInteractive(request: InteractiveRequest): Promise; + acquireTokenByDeviceCode(request: Omit): Promise; acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise; removeAccount(account: AccountInfo): Promise; accounts: AccountInfo[]; diff --git a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts index 69d7afaa38a..c64f122c022 100644 --- a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts +++ b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts @@ -22,7 +22,7 @@ suite('UriHandlerLoopbackClient', () => { envStub.openExternal.resolves(true); envStub.asExternalUri.callThrough(); uriHandler = new UriEventHandler(); - client = new UriHandlerLoopbackClient(uriHandler, redirectUri, window.createOutputChannel('test', { log: true })); + client = new UriHandlerLoopbackClient(uriHandler, redirectUri, callbackUri, window.createOutputChannel('test', { log: true })); }); teardown(() => { @@ -35,8 +35,6 @@ suite('UriHandlerLoopbackClient', () => { const testUrl = 'http://example.com?foo=5'; await client.openBrowser(testUrl); - - assert.ok(envStub.asExternalUri.calledOnce); assert.ok(envStub.openExternal.calledOnce); const expectedUri = Uri.parse(testUrl + `&state=${encodeURI(callbackUri.toString(true))}`); @@ -52,7 +50,8 @@ suite('UriHandlerLoopbackClient', () => { }); }); - suite('listenForAuthCode', () => { + // Skipped for now until `listenForAuthCode` is refactored to not show quick pick + suite.skip('listenForAuthCode', () => { test('should return auth code from URL', async () => { const code = '1234'; const state = '5678'; diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index c2145f75bcf..ac57bf0680b 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { AccountInfo, AuthenticationResult, ClientAuthError, ClientAuthErrorCodes, ServerError, SilentFlowRequest } from '@azure/msal-node'; -import { AuthenticationChallenge, AuthenticationConstraint, AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationProviderSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, EventEmitter, ExtensionContext, ExtensionKind, l10n, LogOutputChannel, Uri, window } from 'vscode'; +import { AuthenticationChallenge, AuthenticationConstraint, AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationProviderSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, env, EventEmitter, ExtensionContext, ExtensionKind, l10n, LogOutputChannel, Uri, window } from 'vscode'; import { Environment } from '@azure/ms-rest-azure-env'; import { CachedPublicClientApplicationManager } from './publicClientCache'; import { UriEventHandler } from '../UriEventHandler'; @@ -16,7 +16,7 @@ import { IStoredSession } from '../AADHelper'; import { ExtensionHost, getMsalFlows } from './flows'; import { base64Decode } from './buffer'; import { Config } from '../common/config'; -import { DEFAULT_REDIRECT_URI } from '../common/env'; +import { isSupportedClient } from '../common/env'; const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad'; const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'; @@ -210,10 +210,12 @@ export class MsalAuthProvider implements AuthenticationProvider { }; const isNodeEnvironment = typeof process !== 'undefined' && typeof process?.versions?.node === 'string'; + const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); const flows = getMsalFlows({ extensionHost: isNodeEnvironment ? this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote : ExtensionHost.WebWorker, + supportedClient: isSupportedClient(callbackUri), isBrokerSupported: cachedPca.isBrokerAvailable }); @@ -235,7 +237,8 @@ export class MsalAuthProvider implements AuthenticationProvider { loginHint: options.account?.label, windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined, logger: this._logger, - uriHandler: this._uriHandler + uriHandler: this._uriHandler, + callbackUri }); const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes); @@ -346,11 +349,13 @@ export class MsalAuthProvider implements AuthenticationProvider { }; const isNodeEnvironment = typeof process !== 'undefined' && typeof process?.versions?.node === 'string'; + const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); const flows = getMsalFlows({ extensionHost: isNodeEnvironment ? this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote : ExtensionHost.WebWorker, - isBrokerSupported: cachedPca.isBrokerAvailable + isBrokerSupported: cachedPca.isBrokerAvailable, + supportedClient: isSupportedClient(callbackUri) }); const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString(); @@ -373,7 +378,8 @@ export class MsalAuthProvider implements AuthenticationProvider { windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined, logger: this._logger, uriHandler: this._uriHandler, - claims: scopeData.claims + claims: scopeData.claims, + callbackUri }; const result = await flow.trigger(authRequest); diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index 5008ce7f82d..1f0e528c99f 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PublicClientApplication, AccountInfo, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel, RefreshTokenRequest, BrokerOptions } from '@azure/msal-node'; +import { PublicClientApplication, AccountInfo, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel, RefreshTokenRequest, BrokerOptions, DeviceCodeRequest } from '@azure/msal-node'; import { NativeBrokerPlugin } from '@azure/msal-node-extensions'; -import { Disposable, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter, workspace } from 'vscode'; -import { raceCancellationAndTimeoutError } from '../common/async'; +import { Disposable, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter, workspace, env, Uri, UIKind } from 'vscode'; +import { DeferredPromise, raceCancellationAndTimeoutError } from '../common/async'; import { SecretStorageCachePlugin } from '../common/cachePlugin'; import { MsalLoggerOptions } from '../common/loggerOptions'; import { ICachedPublicClientApplication } from '../common/publicClientCache'; @@ -53,6 +53,8 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica let broker: BrokerOptions | undefined; if (process.platform !== 'win32' && process.platform !== 'darwin') { this._logger.info(`[${this._clientId}] Native Broker is only available on Windows and macOS`); + } else if (env.uiKind === UIKind.Web) { + this._logger.info(`[${this._clientId}] Native Broker is not available in web UI`); } else if (workspace.getConfiguration('microsoft-authentication').get<'msal' | 'msal-no-broker'>('implementation') === 'msal-no-broker') { this._logger.info(`[${this._clientId}] Native Broker disabled via settings`); } else { @@ -228,6 +230,81 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica return result; } + async acquireTokenByDeviceCode(request: Omit): Promise { + this._logger.debug(`[acquireTokenByDeviceCode] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}]`); + const result = await this._sequencer.queue(async () => { + const deferredPromise = new DeferredPromise(); + const result = await Promise.race([ + this._pca.acquireTokenByDeviceCode({ + ...request, + deviceCodeCallback: (response) => void this._deviceCodeCallback(response, deferredPromise) + }), + deferredPromise.p + ]); + await deferredPromise.complete(result); + // Force an update so that the account cache is updated. + // TODO:@TylerLeonhardt The problem is, we use the sequencer for + // change events but we _don't_ use it for the accounts cache. + // We should probably use it for the accounts cache as well. + await this._update(); + return result; + }); + if (result) { + if (this.isBrokerAvailable && result.account) { + await this._accountAccess.setAllowedAccess(result.account, true); + } + } + return result; + } + + private async _deviceCodeCallback( + // MSAL doesn't expose this type... + response: Parameters[0], + deferredPromise: DeferredPromise + ): Promise { + const button = l10n.t('Copy & Continue to Microsoft'); + const modalResult = await window.showInformationMessage( + l10n.t({ message: 'Your Code: {0}', args: [response.userCode], comment: ['The {0} will be a code, e.g. 123-456'] }), + { + modal: true, + detail: l10n.t('To finish authenticating, navigate to Microsoft and paste in the above one-time code.') + }, button); + + if (modalResult !== button) { + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] User cancelled the device code flow.`); + deferredPromise.cancel(); + return; + } + + await env.clipboard.writeText(response.userCode); + await env.openExternal(Uri.parse(response.verificationUri)); + await window.withProgress({ + location: ProgressLocation.Notification, + cancellable: true, + title: l10n.t({ + message: 'Open [{0}]({0}) in a new tab and paste your one-time code: {1}', + args: [response.verificationUri, response.userCode], + comment: [ + 'The [{0}]({0}) will be a url and the {1} will be a code, e.g. 123456', + '{Locked="[{0}]({0})"}' + ] + }) + }, async (_, token) => { + const disposable = token.onCancellationRequested(() => { + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] Device code flow cancelled by user.`); + deferredPromise.cancel(); + }); + try { + await deferredPromise.p; + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] Device code flow completed successfully.`); + } catch (error) { + // Ignore errors here, they are handled at a higher scope + } finally { + disposable.dispose(); + } + }); + } + removeAccount(account: AccountInfo): Promise { if (this.isBrokerAvailable) { return this._accountAccess.setAllowedAccess(account, false); diff --git a/extensions/microsoft-authentication/src/node/flows.ts b/extensions/microsoft-authentication/src/node/flows.ts index 2457f69cba0..4a3c877691b 100644 --- a/extensions/microsoft-authentication/src/node/flows.ts +++ b/extensions/microsoft-authentication/src/node/flows.ts @@ -22,12 +22,15 @@ export const enum ExtensionHost { interface IMsalFlowOptions { supportsRemoteExtensionHost: boolean; supportsWebWorkerExtensionHost: boolean; + supportsUnsupportedClient: boolean; + supportsBroker: boolean; } interface IMsalFlowTriggerOptions { cachedPca: ICachedPublicClientApplication; authority: string; scopes: string[]; + callbackUri: Uri; loginHint?: string; windowHandle?: Buffer; logger: LogOutputChannel; @@ -45,7 +48,9 @@ class DefaultLoopbackFlow implements IMsalFlow { label = 'default'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: false, - supportsWebWorkerExtensionHost: false + supportsWebWorkerExtensionHost: false, + supportsUnsupportedClient: true, + supportsBroker: true }; async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise { @@ -73,12 +78,14 @@ class UrlHandlerFlow implements IMsalFlow { label = 'protocol handler'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: false + supportsWebWorkerExtensionHost: false, + supportsUnsupportedClient: false, + supportsBroker: false }; - async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger, uriHandler }: IMsalFlowTriggerOptions): Promise { + async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger, uriHandler, callbackUri }: IMsalFlowTriggerOptions): Promise { logger.info('Trying protocol handler flow...'); - const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, logger); + const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, callbackUri, logger); let redirectUri: string | undefined; if (cachedPca.isBrokerAvailable && process.platform === 'darwin') { redirectUri = Config.macOSBrokerRedirectUri; @@ -97,13 +104,34 @@ class UrlHandlerFlow implements IMsalFlow { } } +class DeviceCodeFlow implements IMsalFlow { + label = 'device code'; + options: IMsalFlowOptions = { + supportsRemoteExtensionHost: true, + supportsWebWorkerExtensionHost: false, + supportsUnsupportedClient: true, + supportsBroker: false + }; + + async trigger({ cachedPca, authority, scopes, claims, logger }: IMsalFlowTriggerOptions): Promise { + logger.info('Trying device code flow...'); + const result = await cachedPca.acquireTokenByDeviceCode({ scopes, authority, claims }); + if (!result) { + throw new Error('Device code flow did not return a result'); + } + return result; + } +} + const allFlows: IMsalFlow[] = [ new DefaultLoopbackFlow(), - new UrlHandlerFlow() + new UrlHandlerFlow(), + new DeviceCodeFlow() ]; export interface IMsalFlowQuery { extensionHost: ExtensionHost; + supportedClient: boolean; isBrokerSupported: boolean; } @@ -119,12 +147,10 @@ export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] { useFlow &&= flow.options.supportsWebWorkerExtensionHost; break; } + useFlow &&= flow.options.supportsBroker || !query.isBrokerSupported; + useFlow &&= flow.options.supportsUnsupportedClient || query.supportedClient; if (useFlow) { flows.push(flow); - if (query.isBrokerSupported) { - // If broker is supported, only use the first valid flow - return flows; - } } } return flows; diff --git a/extensions/microsoft-authentication/src/node/test/flows.test.ts b/extensions/microsoft-authentication/src/node/test/flows.test.ts new file mode 100644 index 00000000000..1cd4bd6077a --- /dev/null +++ b/extensions/microsoft-authentication/src/node/test/flows.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { getMsalFlows, ExtensionHost, IMsalFlowQuery } from '../flows'; + +suite('getMsalFlows', () => { + test('should return all flows for local extension host with supported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: true, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 3); + assert.strictEqual(flows[0].label, 'default'); + assert.strictEqual(flows[1].label, 'protocol handler'); + assert.strictEqual(flows[2].label, 'device code'); + }); + + test('should return only default flow for local extension host with supported client and broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: true, + isBrokerSupported: true + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'default'); + }); + + test('should return protocol handler and device code flows for remote extension host with supported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Remote, + supportedClient: true, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 2); + assert.strictEqual(flows[0].label, 'protocol handler'); + assert.strictEqual(flows[1].label, 'device code'); + }); + + test('should return no flows for web worker extension host', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.WebWorker, + supportedClient: true, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 0); + }); + + test('should return only default and device code flows for local extension host with unsupported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: false, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 2); + assert.strictEqual(flows[0].label, 'default'); + assert.strictEqual(flows[1].label, 'device code'); + }); + + test('should return only device code flow for remote extension host with unsupported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Remote, + supportedClient: false, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'device code'); + }); + + test('should return default flow for local extension host with unsupported client and broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: false, + isBrokerSupported: true + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'default'); + }); +}); From 82a29a2b2596b17c3be03b0e0229cf94eb9bd6ea Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:01:24 -0700 Subject: [PATCH 1007/4355] Use css to override font-size in rendered markdown --- .../markdownRenderer/browser/markdownRenderer.ts | 14 ++++++-------- .../comments/browser/commentThreadZoneWidget.ts | 2 +- .../contrib/comments/browser/media/review.css | 5 +++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 2f3fffd5023..1366f491352 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isHTMLElement } from '../../../../../base/browser/dom.js'; import { IRenderedMarkdown, MarkdownRenderOptions, renderMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTypes.js'; import { onUnexpectedError } from '../../../../../base/common/errors.js'; @@ -34,7 +35,6 @@ export interface IMarkdownRenderer { export interface IMarkdownRendererExtraOptions { readonly editor?: ICodeEditor; - readonly codeBlockFontSize?: string; } @@ -97,9 +97,11 @@ export class MarkdownRendererService implements IMarkdownRendererService { } const html = await tokenizeToString(this._languageService, value, languageId); - const element = document.createElement('span'); - - element.innerHTML = (MarkdownRendererService._ttpTokenizer?.createHTML(html) ?? html) as string; + const content = MarkdownRendererService._ttpTokenizer ? MarkdownRendererService._ttpTokenizer.createHTML(html) ?? html : html; + const element = new DOMParser().parseFromString(content as string, 'text/html').body.firstChild; + if (!isHTMLElement(element)) { + return document.createElement('span'); + } // use "good" font if (options.editor) { @@ -109,10 +111,6 @@ export class MarkdownRendererService implements IMarkdownRendererService { element.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; } - if (options.codeBlockFontSize !== undefined) { - element.style.fontSize = options.codeBlockFontSize; - } - return element; } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 26da43104a8..10ad6c77372 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -271,7 +271,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread, this._pendingComment, this._pendingEdits, - { editor: this.editor, codeBlockFontSize: '' }, + { editor: this.editor, }, this._commentOptions, { actionRunner: async () => { diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index f0b9a064d8d..19e5fd127cd 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -246,6 +246,11 @@ max-width: 100%; } +.review-widget .body .comment-body .monaco-tokenized-source { + font-size: inherit !important; + line-height: auto !important; +} + .review-widget .body .comment-form-container { margin: 8px 20px; } From 8ecbeecef6e7c9576b049683cc8eb52f35b07696 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:12:22 -0700 Subject: [PATCH 1008/4355] Back to using innerHTML --- .../markdownRenderer/browser/markdownRenderer.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 1366f491352..1014f9e41ed 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -98,20 +98,23 @@ export class MarkdownRendererService implements IMarkdownRendererService { const html = await tokenizeToString(this._languageService, value, languageId); const content = MarkdownRendererService._ttpTokenizer ? MarkdownRendererService._ttpTokenizer.createHTML(html) ?? html : html; - const element = new DOMParser().parseFromString(content as string, 'text/html').body.firstChild; - if (!isHTMLElement(element)) { + + const root = document.createElement('span'); + root.innerHTML = content as string; + const codeElement = root.querySelector('.monaco-tokenized-source'); + if (!isHTMLElement(codeElement)) { return document.createElement('span'); } // use "good" font if (options.editor) { const fontInfo = options.editor.getOption(EditorOption.fontInfo); - applyFontInfo(element, fontInfo); + applyFontInfo(codeElement, fontInfo); } else { - element.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; + codeElement.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; } - return element; + return root; } } From 3643025b3aaa69c9e37948eaa6c1c41585914a85 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 8 Oct 2025 17:43:26 -0700 Subject: [PATCH 1009/4355] fix: debugger is unresponsive to repl evaluation after switching --- .../contrib/debug/browser/debugCommands.ts | 7 ++---- .../workbench/contrib/debug/browser/repl.ts | 9 ++----- .../contrib/debug/common/debugUtils.ts | 24 ++++++++++++++++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 7714b20cab7..9d644774d9f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -38,7 +38,7 @@ import { IExtensionsWorkbenchService } from '../../extensions/common/extensions. import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; import { CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_IN_DEBUG_MODE, CONTEXT_IN_DEBUG_REPL, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, DataBreakpointSetType, EDITOR_CONTRIBUTION_ID, getStateLabel, IConfig, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugEditorContribution, IDebugService, IDebugSession, IEnablement, IExceptionBreakpoint, isFrameDeemphasized, IStackFrame, IThread, REPL_VIEW_ID, State, VIEWLET_ID } from '../common/debug.js'; import { Breakpoint, DataBreakpoint, Expression, FunctionBreakpoint, Thread, Variable } from '../common/debugModel.js'; -import { saveAllBeforeDebugStart } from '../common/debugUtils.js'; +import { saveAllBeforeDebugStart, resolveChildSession } from '../common/debugUtils.js'; import { showLoadedScriptMenu } from '../common/loadedScriptsPicker.js'; import { openBreakpointSource } from './breakpointsView.js'; import { showDebugSessionMenu } from './debugSessionPicker.js'; @@ -711,10 +711,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor, session: IDebugSession) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const stoppedChildSession = debugService.getModel().getSessions().find(s => s.parentSession === session && s.state === State.Stopped); - if (stoppedChildSession && session.state !== State.Stopped) { - session = stoppedChildSession; - } + session = resolveChildSession(session, debugService.getModel().getSessions()); await debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); const stackFrame = debugService.getViewModel().focusedStackFrame; if (stackFrame) { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 532f70db7dd..59b4f8c5db9 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -69,6 +69,7 @@ import { AccessibilityCommandId } from '../../accessibility/common/accessibility import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js'; import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State, getStateLabel } from '../common/debug.js'; import { Variable } from '../common/debugModel.js'; +import { resolveChildSession } from '../common/debugUtils.js'; import { ReplEvaluationResult, ReplGroup } from '../common/replModel.js'; import { FocusSessionActionViewItem } from './debugActionViewItems.js'; import { DEBUG_COMMAND_CATEGORY, FOCUS_REPL_ID } from './debugCommands.js'; @@ -1047,13 +1048,7 @@ registerAction2(class extends ViewAction { const debugService = accessor.get(IDebugService); // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event if (session && session.state !== State.Inactive && session !== debugService.getViewModel().focusedSession) { - if (session.state !== State.Stopped) { - // Focus child session instead if it is stopped #112595 - const stopppedChildSession = debugService.getModel().getSessions().find(s => s.parentSession === session && s.state === State.Stopped); - if (stopppedChildSession) { - session = stopppedChildSession; - } - } + session = resolveChildSession(session, debugService.getModel().getSessions()); await debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); } // Need to select the session in the view since the focussed session might not have changed diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 7eb37eaf10d..2a51f3fca64 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from '../../../../base/common/strings.js'; -import { IDebuggerContribution, IDebugSession, IConfigPresentation } from './debug.js'; +import { IDebuggerContribution, IDebugSession, IConfigPresentation, State } from './debug.js'; import { URI as uri } from '../../../../base/common/uri.js'; import { isAbsolute } from '../../../../base/common/path.js'; import { deepClone } from '../../../../base/common/objects.js'; @@ -392,3 +392,25 @@ export async function saveAllBeforeDebugStart(configurationService: IConfigurati export const sourcesEqual = (a: DebugProtocol.Source | undefined, b: DebugProtocol.Source | undefined): boolean => !a || !b ? a === b : a.name === b.name && a.path === b.path && a.sourceReference === b.sourceReference; + +/** + * Resolves the best child session to focus when a parent session is selected. + * Always prefer child sessions over parent wrapper sessions to ensure console responsiveness. + * Fixes issue #152407: Using debug console picker when not paused leaves console unresponsive. + */ +export function resolveChildSession(session: IDebugSession, allSessions: readonly IDebugSession[]): IDebugSession { + // Always focus child session instead of parent wrapper session #152407 + const childSessions = allSessions.filter(s => s.parentSession === session); + if (childSessions.length > 0) { + // Prefer stopped child session if available #112595 + const stoppedChildSession = childSessions.find(s => s.state === State.Stopped); + if (stoppedChildSession) { + return stoppedChildSession; + } else { + // If no stopped child, focus the first available child session + return childSessions[0]; + } + } + // Return the original session if it has no children + return session; +} From bcbd0b4a9877c49ece744f86f4dc1e2b73477e98 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:20:16 -0700 Subject: [PATCH 1010/4355] Remove quick pick flow for protocol handler (#270455) Remove quick pick flow Since we have device code flow, that's better. ref https://github.com/microsoft/vscode/issues/270452 --- .../microsoft-authentication/src/AADHelper.ts | 32 +---- .../src/common/loopbackClientAndOpener.ts | 111 ++---------------- .../test/loopbackClientAndOpener.test.ts | 2 +- 3 files changed, 15 insertions(+), 130 deletions(-) diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 8117b0b0f5b..1246b2ec40e 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -452,8 +452,8 @@ export class AzureActiveDirectoryService { if (isSupportedEnvironment(callbackUri)) { existingPromise = this.handleCodeResponse(scopeData); } else { - inputBox = vscode.window.createInputBox(); - existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData); + // This code path shouldn't be hit often, so just surface an error. + throw new Error('Unsupported environment for authentication'); } this._codeExchangePromises.set(scopeData.scopeStr, existingPromise); } @@ -744,34 +744,6 @@ export class AzureActiveDirectoryService { }); } - private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { - this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with input box`); - inputBox.ignoreFocusOut = true; - inputBox.title = vscode.l10n.t('Microsoft Authentication'); - inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.'); - inputBox.placeholder = vscode.l10n.t('Paste authorization code here...'); - return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { - inputBox.show(); - inputBox.onDidAccept(async () => { - const code = inputBox.value; - if (code) { - inputBox.dispose(); - const session = await this.exchangeCodeForSession(code, verifier, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' sending session changed event because session was added.`); - this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); - resolve(session); - } - }); - inputBox.onDidHide(() => { - if (!inputBox.value) { - inputBox.dispose(); - reject('Cancelled'); - } - }); - }); - } - private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { this._logger.trace(`[${scopeData.scopeStr}] Exchanging login code for session`); let token: IToken | undefined; diff --git a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts index 3d4c56723ea..f7e41805d70 100644 --- a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts +++ b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts @@ -5,17 +5,14 @@ import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node'; import type { UriEventHandler } from '../UriEventHandler'; -import { Disposable, env, l10n, LogOutputChannel, Uri, window } from 'vscode'; -import { DeferredPromise, toPromise } from './async'; -import { isSupportedClient } from './env'; +import { env, LogOutputChannel, Uri } from 'vscode'; +import { toPromise } from './async'; export interface ILoopbackClientAndOpener extends ILoopbackClient { openBrowser(url: string): Promise; } export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { - private _responseDeferred: DeferredPromise | undefined; - constructor( private readonly _uriHandler: UriEventHandler, private readonly _redirectUri: string, @@ -24,14 +21,16 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { ) { } async listenForAuthCode(): Promise { - await this._responseDeferred?.cancel(); - this._responseDeferred = new DeferredPromise(); - const result = await this._responseDeferred.p; - this._responseDeferred = undefined; - if (result) { - return result; - } - throw new Error('No valid response received for authorization code.'); + const url = await toPromise(this._uriHandler.event); + this._logger.debug(`Received URL event. Authority: ${url.authority}`); + const result = new URL(url.toString(true)); + return { + code: result.searchParams.get('code') ?? undefined, + state: result.searchParams.get('state') ?? undefined, + error: result.searchParams.get('error') ?? undefined, + error_description: result.searchParams.get('error_description') ?? undefined, + error_uri: result.searchParams.get('error_uri') ?? undefined, + }; } getRedirectUri(): string { @@ -45,93 +44,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { } async openBrowser(url: string): Promise { - if (isSupportedClient(this._callbackUri)) { - void this._getCodeResponseFromUriHandler(); - } else { - // Unsupported clients will be shown the code in the browser, but it will not redirect back since this - // isn't a supported client. Instead, they will copy that code in the browser and paste it in an input box - // that will be shown to them by the extension. - void this._getCodeResponseFromQuickPick(); - } - const uri = Uri.parse(url + `&state=${encodeURI(this._callbackUri.toString(true))}`); await env.openExternal(uri); } - - private async _getCodeResponseFromUriHandler(): Promise { - if (!this._responseDeferred) { - throw new Error('No listener for auth code'); - } - const url = await toPromise(this._uriHandler.event); - this._logger.debug(`Received URL event. Authority: ${url.authority}`); - const result = new URL(url.toString(true)); - - this._responseDeferred?.complete({ - code: result.searchParams.get('code') ?? undefined, - state: result.searchParams.get('state') ?? undefined, - error: result.searchParams.get('error') ?? undefined, - error_description: result.searchParams.get('error_description') ?? undefined, - error_uri: result.searchParams.get('error_uri') ?? undefined, - }); - } - - private async _getCodeResponseFromQuickPick(): Promise { - if (!this._responseDeferred) { - throw new Error('No listener for auth code'); - } - const inputBox = window.createInputBox(); - inputBox.ignoreFocusOut = true; - inputBox.title = l10n.t('Microsoft Authentication'); - inputBox.prompt = l10n.t('Provide the authorization code to complete the sign in flow.'); - inputBox.placeholder = l10n.t('Paste authorization code here...'); - inputBox.show(); - const code = await new Promise((resolve) => { - let resolvedValue: string | undefined = undefined; - const disposable = Disposable.from( - inputBox, - inputBox.onDidAccept(async () => { - if (!inputBox.value) { - inputBox.validationMessage = l10n.t('Authorization code is required.'); - return; - } - const code = inputBox.value; - resolvedValue = code; - resolve(code); - inputBox.hide(); - }), - inputBox.onDidChangeValue(() => { - inputBox.validationMessage = undefined; - }), - inputBox.onDidHide(() => { - disposable.dispose(); - if (!resolvedValue) { - resolve(undefined); - } - }) - ); - Promise.allSettled([this._responseDeferred?.p]).then(() => disposable.dispose()); - }); - // Something canceled the original deferred promise, so just return. - if (this._responseDeferred.isSettled) { - return; - } - if (code) { - this._logger.debug('Received auth code from quick pick'); - this._responseDeferred.complete({ - code, - state: undefined, - error: undefined, - error_description: undefined, - error_uri: undefined - }); - return; - } - this._responseDeferred.complete({ - code: undefined, - state: undefined, - error: 'User cancelled', - error_description: 'User cancelled', - error_uri: undefined - }); - } } diff --git a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts index c64f122c022..31375af860f 100644 --- a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts +++ b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts @@ -51,7 +51,7 @@ suite('UriHandlerLoopbackClient', () => { }); // Skipped for now until `listenForAuthCode` is refactored to not show quick pick - suite.skip('listenForAuthCode', () => { + suite('listenForAuthCode', () => { test('should return auth code from URL', async () => { const code = '1234'; const state = '5678'; From a396c670674a63dffad495d85c47b60651bfaff5 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:23:51 -0700 Subject: [PATCH 1011/4355] Move MarkdownRenderer into `platform` layer For #206228 This moves the MarkdownRendererService from `editor` into `platform` As part of this I had to add a `setDefaultCodeBlockRenderer` method that lets the editor register syntax highlighting support --- .../services/hoverService/hoverWidget.ts | 2 +- .../editorMarkdownCodeBlockRenderer.ts | 71 ++++++++++++++++ .../contrib/hover/browser/glyphHoverWidget.ts | 4 +- .../hover/browser/markdownHoverParticipant.ts | 4 +- .../inlayHints/browser/inlayHintsHover.ts | 2 +- .../browser/hintsWidget/hoverParticipant.ts | 4 +- .../message/browser/messageController.ts | 2 +- .../browser/parameterHintsWidget.ts | 4 +- .../suggest/browser/suggestWidgetDetails.ts | 4 +- .../browser/bannerController.ts | 2 +- .../browser/unicodeHighlighter.ts | 2 +- .../browser/standaloneCodeEditor.ts | 8 +- .../markdown}/browser/markdownRenderer.ts | 80 +++++-------------- .../browser/parts/banner/bannerPart.ts | 2 +- .../parts/dialogs/dialog.web.contribution.ts | 2 +- .../browser/parts/dialogs/dialogHandler.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 2 +- src/vs/workbench/browser/workbench.ts | 6 ++ .../browser/chatContentMarkdownRenderer.ts | 2 +- .../chatConfirmationWidget.ts | 2 +- .../chatErrorConfirmationPart.ts | 2 +- .../chatContentParts/chatErrorContentPart.ts | 2 +- .../chatMarkdownContentPart.ts | 2 +- .../chatMcpServersInteractionContentPart.ts | 2 +- .../chatProgressContentPart.ts | 2 +- .../chatContentParts/chatQuotaExceededPart.ts | 2 +- .../chatContentParts/chatTaskContentPart.ts | 2 +- .../chatThinkingContentPart.ts | 2 +- .../chatTerminalToolConfirmationSubPart.ts | 2 +- .../chatTerminalToolProgressPart.ts | 2 +- .../chatToolConfirmationSubPart.ts | 2 +- .../chatToolInvocationPart.ts | 2 +- .../chatToolProgressPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 2 +- .../contrib/chat/browser/chatQuick.ts | 2 +- .../chatSessions/view/sessionsTreeRenderer.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 2 +- .../contrib/chat/browser/chatStatus.ts | 2 +- .../viewsWelcome/chatViewWelcomeController.ts | 2 +- .../contrib/comments/browser/commentNode.ts | 2 +- .../comments/browser/commentThreadBody.ts | 2 +- .../comments/browser/commentThreadWidget.ts | 2 +- .../browser/commentThreadZoneWidget.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../preferences/browser/settingsTree.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../testResultsView/testResultsOutput.ts | 2 +- .../testing/browser/testingExplorerView.ts | 2 +- .../browser/gettingStarted.ts | 2 +- .../parts/dialogs/dialog.contribution.ts | 2 +- .../browser/simpleSuggestWidgetDetails.ts | 2 +- .../test/browser/workbenchTestServices.ts | 2 +- 52 files changed, 157 insertions(+), 114 deletions(-) create mode 100644 src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts rename src/vs/{editor/browser/widget/markdownRenderer => platform/markdown}/browser/markdownRenderer.ts (50%) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 83f43b73847..5db8f80e381 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -14,7 +14,7 @@ import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAcc import { Widget } from '../../../../base/browser/ui/widget.js'; import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { IMarkdownRendererService, openLinkFromMarkdown } from '../../widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { isMarkdownString } from '../../../../base/common/htmlContent.js'; import { localize } from '../../../../nls.js'; import { isMacintosh } from '../../../../base/common/platform.js'; diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts new file mode 100644 index 00000000000..a07f4f0354e --- /dev/null +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isHTMLElement } from '../../../../../base/browser/dom.js'; +import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTypes.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IMarkdownCodeBlockRenderer, IMarkdownRendererExtraOptions } from '../../../../../platform/markdown/browser/markdownRenderer.js'; +import { EditorOption, IEditorOptions } from '../../../../common/config/editorOptions.js'; +import { EDITOR_FONT_DEFAULTS } from '../../../../common/config/fontInfo.js'; +import { ILanguageService } from '../../../../common/languages/language.js'; +import { PLAINTEXT_LANGUAGE_ID } from '../../../../common/languages/modesRegistry.js'; +import { tokenizeToString } from '../../../../common/languages/textToHtmlTokenizer.js'; +import { applyFontInfo } from '../../../config/domFontInfo.js'; +import { isCodeEditor } from '../../../editorBrowser.js'; +import './renderedMarkdown.css'; + +/** + * Renders markdown code blocks using the editor's tokenization and font settings. + */ +export class EditorMarkdownCodeBlockRenderer implements IMarkdownCodeBlockRenderer { + + private static _ttpTokenizer = createTrustedTypesPolicy('tokenizeToString', { + createHTML(html: string) { + return html; + } + }); + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageService private readonly _languageService: ILanguageService, + ) { } + + public async renderCodeBlock(languageAlias: string | undefined, value: string, options: IMarkdownRendererExtraOptions): Promise { + const editor = isCodeEditor(options.context) ? options.context : undefined; + + // In markdown, + // it is possible that we stumble upon language aliases (e.g.js instead of javascript) + // it is possible no alias is given in which case we fall back to the current editor lang + let languageId: string | undefined | null; + if (languageAlias) { + languageId = this._languageService.getLanguageIdByLanguageName(languageAlias); + } else if (editor) { + languageId = editor.getModel()?.getLanguageId(); + } + if (!languageId) { + languageId = PLAINTEXT_LANGUAGE_ID; + } + const html = await tokenizeToString(this._languageService, value, languageId); + + const content = EditorMarkdownCodeBlockRenderer._ttpTokenizer ? EditorMarkdownCodeBlockRenderer._ttpTokenizer.createHTML(html) ?? html : html; + + const root = document.createElement('span'); + root.innerHTML = content as string; + const codeElement = root.querySelector('.monaco-tokenized-source'); + if (!isHTMLElement(codeElement)) { + return document.createElement('span'); + } + + // use "good" font + if (editor) { + const fontInfo = editor.getOption(EditorOption.fontInfo); + applyFontInfo(codeElement, fontInfo); + } else { + codeElement.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; + } + + return root; + } +} diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index 0f30bf9b794..4302560f260 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -5,7 +5,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { ICodeEditor, IEditorMouseEvent, IOverlayWidget, IOverlayWidgetPosition, MouseTargetType } from '../../../browser/editorBrowser.js'; import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js'; import { HoverOperation, HoverResult, HoverStartMode } from './hoverOperation.js'; @@ -145,7 +145,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov for (const msg of messages) { const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderedContents = this._renderDisposeables.add(this._markdownRendererService.render(msg.value, { editor: this._editor })); + const renderedContents = this._renderDisposeables.add(this._markdownRendererService.render(msg.value, { context: this._editor })); hoverContentsElement.appendChild(renderedContents.element); fragment.appendChild(markdownHoverElement); } diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index eaa2a87f166..cff1168f829 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -8,7 +8,7 @@ import { asArray, compareBy, numberComparator } from '../../../../base/common/ar import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Position } from '../../../common/core/position.js'; @@ -508,7 +508,7 @@ function renderMarkdown( const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); const renderedContents = disposables.add(markdownRendererService.render(markdownString, { - editor, + context: editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; onFinishedRendering(); diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index 8ec046bb684..e9248765204 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -26,7 +26,7 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { HoverStartSource } from '../../hover/browser/hoverOperation.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts index 0e72582406e..7693d88fcfd 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts @@ -14,7 +14,7 @@ import { IModelDecoration } from '../../../../common/model.js'; import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js'; import { InlineCompletionsController } from '../controller/inlineCompletionsController.js'; import { InlineSuggestionHintsContentWidget } from './inlineCompletionsHintsWidget.js'; -import { IMarkdownRendererService } from '../../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import * as nls from '../../../../../nls.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -154,7 +154,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan const render = (code: string) => { const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); const renderedContents = disposables.add(this._markdownRendererService.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), { - editor: this._editor, + context: this._editor, asyncRenderCallback: () => { hoverContentsElement.className = 'hover-contents code-hover-contents'; context.onContentsChanged(); diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index 7e1b994a6e2..f2280564b02 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -16,7 +16,7 @@ import { IPosition } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { IEditorContribution, ScrollType } from '../../../common/editorCommon.js'; import { PositionAffinity } from '../../../common/model.js'; -import { openLinkFromMarkdown } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { openLinkFromMarkdown } from '../../../../platform/markdown/browser/markdownRenderer.js'; import * as nls from '../../../../nls.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index 02ff76fea80..a85c301ae19 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -17,7 +17,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from '../../../common/config/editorOptions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../common/config/fontInfo.js'; import * as languages from '../../../common/languages.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { ParameterHintsModel } from './parameterHintsModel.js'; import { Context } from './provideSignatureHelp.js'; @@ -269,7 +269,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { private renderMarkdownDocs(markdown: IMarkdownString): IRenderedMarkdown { const renderedContents = this.renderDisposeables.add(this.markdownRendererService.render(markdown, { - editor: this.editor, + context: this.editor, asyncRenderCallback: () => { this.domNodes?.scrollbar.scanDomNode(); } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 65fa98ba47d..72fcb2b85fc 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -15,7 +15,7 @@ import * as nls from '../../../../nls.js'; import { isHighContrast } from '../../../../platform/theme/common/theme.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../browser/editorBrowser.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { CompletionItem } from './suggest.js'; @@ -174,7 +174,7 @@ export class SuggestDetailsWidget { this._docs.classList.add('markdown-docs'); dom.clearNode(this._docs); const renderedContents = this._markdownRendererService.render(documentation, { - editor: this._editor, + context: this._editor, asyncRenderCallback: () => { this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight); this._onDidChangeContents.fire(this); diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts index 03b183a44bf..7eab959fcd6 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/bannerController.ts @@ -9,7 +9,7 @@ import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { Action } from '../../../../base/common/actions.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILinkDescriptor, Link } from '../../../../platform/opener/browser/link.js'; diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 2372f7cbe59..3f8ecb82ecb 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -32,7 +32,7 @@ import { IWorkspaceTrustManagementService } from '../../../../platform/workspace import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { safeIntl } from '../../../../base/common/date.js'; import { isModelDecorationInComment, isModelDecorationInString, isModelDecorationVisible } from '../../../common/viewModel/viewModelDecoration.js'; -import { IMarkdownRendererService } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.')); diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 32dbaa2265c..c3cf79fccc2 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -42,6 +42,8 @@ import { mainWindow } from '../../../base/browser/window.js'; import { setHoverDelegateFactory } from '../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IHoverService, WorkbenchHoverDelegate } from '../../../platform/hover/browser/hover.js'; import { setBaseLayerHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate2.js'; +import { IMarkdownRendererService } from '../../../platform/markdown/browser/markdownRenderer.js'; +import { EditorMarkdownCodeBlockRenderer } from '../../browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js'; /** * Description of an action contribution @@ -280,6 +282,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, ) { const options = { ..._options }; options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel; @@ -295,6 +298,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, { instantHover: enableInstantHover }, {})); setBaseLayerHoverDelegate(hoverService); + + markdownRendererService.setDefaultCodeBlockRenderer(instantiationService.createInstance(EditorMarkdownCodeBlockRenderer)); } public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null { @@ -427,6 +432,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @ILanguageService languageService: ILanguageService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, false); @@ -439,7 +445,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon } const _model: ITextModel | null | undefined = options.model; delete options.model; - super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, hoverService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService); + super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, hoverService, keybindingService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService, markdownRendererService); this._configurationService = configurationService; this._standaloneThemeService = themeService; diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/platform/markdown/browser/markdownRenderer.ts similarity index 50% rename from src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts rename to src/vs/platform/markdown/browser/markdownRenderer.ts index 1014f9e41ed..fed8adf5d36 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/platform/markdown/browser/markdownRenderer.ts @@ -3,23 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isHTMLElement } from '../../../../../base/browser/dom.js'; -import { IRenderedMarkdown, MarkdownRenderOptions, renderMarkdown } from '../../../../../base/browser/markdownRenderer.js'; -import { createTrustedTypesPolicy } from '../../../../../base/browser/trustedTypes.js'; -import { onUnexpectedError } from '../../../../../base/common/errors.js'; -import { IMarkdownString, MarkdownStringTrustedOptions } from '../../../../../base/common/htmlContent.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; -import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { EditorOption, IEditorOptions } from '../../../../common/config/editorOptions.js'; -import { EDITOR_FONT_DEFAULTS } from '../../../../common/config/fontInfo.js'; -import { ILanguageService } from '../../../../common/languages/language.js'; -import { PLAINTEXT_LANGUAGE_ID } from '../../../../common/languages/modesRegistry.js'; -import { tokenizeToString } from '../../../../common/languages/textToHtmlTokenizer.js'; -import { applyFontInfo } from '../../../config/domFontInfo.js'; -import { ICodeEditor } from '../../../editorBrowser.js'; -import './renderedMarkdown.css'; +import { IRenderedMarkdown, MarkdownRenderOptions, renderMarkdown } from '../../../base/browser/markdownRenderer.js'; +import { onUnexpectedError } from '../../../base/common/errors.js'; +import { IMarkdownString, MarkdownStringTrustedOptions } from '../../../base/common/htmlContent.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { IOpenerService } from '../../opener/common/opener.js'; /** * Renders markdown to HTML. @@ -32,9 +21,15 @@ export interface IMarkdownRenderer { render(markdown: IMarkdownString, options?: MarkdownRenderOptions, outElement?: HTMLElement): IRenderedMarkdown; } - export interface IMarkdownRendererExtraOptions { - readonly editor?: ICodeEditor; + /** + * The context in which the markdown is being rendered. + */ + readonly context?: unknown; +} + +export interface IMarkdownCodeBlockRenderer { + renderCodeBlock(languageAlias: string | undefined, value: string, options: IMarkdownRendererExtraOptions): Promise; } @@ -52,27 +47,23 @@ export interface IMarkdownRendererService extends IMarkdownRenderer { readonly _serviceBrand: undefined; render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown; + + setDefaultCodeBlockRenderer(renderer: IMarkdownCodeBlockRenderer): void; } export class MarkdownRendererService implements IMarkdownRendererService { declare readonly _serviceBrand: undefined; - private static _ttpTokenizer = createTrustedTypesPolicy('tokenizeToString', { - createHTML(html: string) { - return html; - } - }); + private _defaultCodeBlockRenderer: IMarkdownCodeBlockRenderer | undefined; constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ILanguageService private readonly _languageService: ILanguageService, @IOpenerService private readonly _openerService: IOpenerService, ) { } render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { const rendered = renderMarkdown(markdown, { - codeBlockRenderer: (alias, value) => this.renderCodeBlock(alias, value, options ?? {}), + codeBlockRenderer: (alias, value) => this._defaultCodeBlockRenderer?.renderCodeBlock(alias, value, options ?? {}) ?? Promise.resolve(document.createElement('span')), actionHandler: (link, mdStr) => { return openLinkFromMarkdown(this._openerService, link, mdStr.isTrusted); }, @@ -82,39 +73,8 @@ export class MarkdownRendererService implements IMarkdownRendererService { return rendered; } - private async renderCodeBlock(languageAlias: string | undefined, value: string, options: IMarkdownRendererExtraOptions): Promise { - // In markdown, - // it is possible that we stumble upon language aliases (e.g.js instead of javascript) - // it is possible no alias is given in which case we fall back to the current editor lang - let languageId: string | undefined | null; - if (languageAlias) { - languageId = this._languageService.getLanguageIdByLanguageName(languageAlias); - } else if (options.editor) { - languageId = options.editor.getModel()?.getLanguageId(); - } - if (!languageId) { - languageId = PLAINTEXT_LANGUAGE_ID; - } - const html = await tokenizeToString(this._languageService, value, languageId); - - const content = MarkdownRendererService._ttpTokenizer ? MarkdownRendererService._ttpTokenizer.createHTML(html) ?? html : html; - - const root = document.createElement('span'); - root.innerHTML = content as string; - const codeElement = root.querySelector('.monaco-tokenized-source'); - if (!isHTMLElement(codeElement)) { - return document.createElement('span'); - } - - // use "good" font - if (options.editor) { - const fontInfo = options.editor.getOption(EditorOption.fontInfo); - applyFontInfo(codeElement, fontInfo); - } else { - codeElement.style.fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; - } - - return root; + setDefaultCodeBlockRenderer(renderer: IMarkdownCodeBlockRenderer): void { + this._defaultCodeBlockRenderer = renderer; } } diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index d7c8978c55b..a0d5ce1c3b7 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -20,7 +20,7 @@ import { Link } from '../../../../platform/opener/browser/link.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Emitter } from '../../../../base/common/event.js'; import { IBannerItem, IBannerService } from '../../../services/banner/browser/bannerService.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index 9c2d931342f..bba15b35fbb 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { Lazy } from '../../../../base/common/lazy.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { createBrowserAboutDialogDetails } from '../../../../platform/dialogs/browser/dialog.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index f3b071f2b34..c5e4d18e457 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -13,7 +13,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { createWorkbenchDialogOptions } from '../../../../platform/dialogs/browser/dialog.js'; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 2c1c090c806..ce6cc95dfbb 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -70,7 +70,7 @@ import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { ITreeViewsDnDService } from '../../../../editor/common/services/treeViewsDndService.js'; import { DraggedTreeItemsIdentifier } from '../../../../editor/common/services/treeViewsDnd.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import type { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; import { parseLinkedText } from '../../../../base/common/linkedText.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index a486daa8b8b..711f8e18b20 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -48,6 +48,8 @@ import { AccessibilityProgressSignalScheduler } from '../../platform/accessibili import { setProgressAcccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js'; import { AccessibleViewRegistry } from '../../platform/accessibility/browser/accessibleViewRegistry.js'; import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js'; +import { IMarkdownRendererService } from '../../platform/markdown/browser/markdownRenderer.js'; +import { EditorMarkdownCodeBlockRenderer } from '../../editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js'; export interface IWorkbenchOptions { @@ -138,6 +140,10 @@ export class Workbench extends Layout { const hoverService = accessor.get(IHoverService); const dialogService = accessor.get(IDialogService); const notificationService = accessor.get(INotificationService) as NotificationService; + const markdownRendererService = accessor.get(IMarkdownRendererService); + + // Set code block renderer for markdown rendering + markdownRendererService.setDefaultCodeBlockRenderer(instantiationService.createInstance(EditorMarkdownCodeBlockRenderer)); // Default Hover Delegate must be registered before creating any workbench/layout components // as these possibly will use the default hover delegate diff --git a/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts index 0e53363cec1..35a73bf0c03 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentMarkdownRenderer.ts @@ -8,7 +8,7 @@ import { IRenderedMarkdown, MarkdownRenderOptions } from '../../../../base/brows import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { IMarkdownRenderer, IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer, IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 3a6f723da43..7fd15d52073 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import type { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts index ed0dc1ab2d3..3f7e1e92ddb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts @@ -8,7 +8,7 @@ import { Button, IButtonOptions } from '../../../../../base/browser/ui/button/bu import { Emitter } from '../../../../../base/common/event.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ChatErrorLevel, IChatResponseErrorDetailsConfirmationButton, IChatSendRequestOptions, IChatService } from '../../common/chatService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts index 65e103ecaf2..b9b5206d3b8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorContentPart.ts @@ -8,7 +8,7 @@ import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels. import { Codicon } from '../../../../../base/common/codicons.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { ChatErrorLevel } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { IChatContentPart } from './chatContentParts.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index eab7a4dba76..3a92acc4922 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -21,7 +21,7 @@ import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js'; import { equalsIgnoreCase } from '../../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../../editor/common/model.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index 89673fde664..cc945f903c6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -12,7 +12,7 @@ import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index ca503f0d28f..df7d3c31fb7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -9,7 +9,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts index 71dc0ab0071..bb8971f5d79 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts @@ -12,7 +12,7 @@ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { assertType } from '../../../../../base/common/types.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 3ae00bd8694..4a8f126270c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -6,7 +6,7 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { IMarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js'; import { IChatTask, IChatTaskSerialized } from '../../common/chatService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 69bd01797ea..e9d623d1601 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -11,7 +11,7 @@ import { ChatTreeItem } from '../chat.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js'; import { localize } from '../../../../../nls.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 879e4fde1c4..86977790131 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -16,7 +16,7 @@ import Severity from '../../../../../../base/common/severity.js'; import { isObject } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; -import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 914063727a4..f5f91c9aaa5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -6,7 +6,7 @@ import { h } from '../../../../../../base/browser/dom.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { isMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IPreferencesService, type IOpenSettingsOptions } from '../../../../../services/preferences/common/preferences.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index ef92ac605e7..0e0c4f7880c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -12,7 +12,7 @@ import { count } from '../../../../../../base/common/strings.js'; import { isEmptyObject } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { ElementSizeObserver } from '../../../../../../editor/browser/config/elementSizeObserver.js'; -import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { localize } from '../../../../../../nls.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 40e88cef9a4..75f87b4e3df 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../base/common/event.js'; import { markdownCommandLink, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../../../nls.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index a29c30f20c0..5a5f1e42dee 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -7,7 +7,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { status } from '../../../../../../base/browser/ui/aria/aria.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { autorun } from '../../../../../../base/common/observable.js'; -import { IMarkdownRenderer } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatProgressMessage, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 9286a53114e..f9cfef850e9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -30,7 +30,7 @@ import { FileAccess } from '../../../../base/common/network.js'; import { clamp } from '../../../../base/common/numbers.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { IMarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRenderer } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { IMenuEntryActionViewItemOptions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 6e4a8ccde71..1ccca58e6cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -12,7 +12,7 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { autorun } from '../../../../base/common/observable.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Selection } from '../../../../editor/common/core/selection.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { MenuId } from '../../../../platform/actions/common/actions.js'; import { localize } from '../../../../nls.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 23f195cfa50..0a1cd0890ef 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -19,7 +19,7 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../.. import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import Severity from '../../../../../../base/common/severity.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { IMarkdownRendererService } from '../../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import * as nls from '../../../../../../nls.js'; import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 503be92d784..cd841757869 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -24,7 +24,7 @@ import { equalsIgnoreCase } from '../../../../base/common/strings.js'; import { isObject } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index f13d9cd17f8..d67ef327324 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -44,7 +44,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { URI } from '../../../../base/common/uri.js'; import { IInlineCompletionsService } from '../../../../editor/browser/services/inlineCompletionsService.js'; import { IChatSessionsService } from '../common/chatSessionsService.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; const gaugeForeground = registerColor('gauge.foreground', { diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index c3bf9be362e..a7c0b481b4a 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -12,7 +12,7 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 0aaf8d87922..e0c772782f6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -10,7 +10,7 @@ import { ActionsOrientation, ActionBar } from '../../../../base/browser/ui/actio import { Action, IAction, Separator, ActionRunner } from '../../../../base/common/actions.js'; import { Disposable, DisposableStore, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; -import { IMarkdownRendererExtraOptions, IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions, IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ICommentService } from './commentService.js'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 4f76fac5722..9fd66298b75 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -15,7 +15,7 @@ import { CommentNode } from './commentNode.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; -import { IMarkdownRendererExtraOptions } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { ICellRange } from '../../notebook/common/notebookRange.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index da6c048f027..d60094c6f00 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -10,7 +10,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import * as languages from '../../../../editor/common/languages.js'; -import { IMarkdownRendererExtraOptions } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererExtraOptions } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { CommentMenus } from './commentMenus.js'; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 10ad6c77372..21051fe928c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -271,7 +271,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread, this._pendingComment, this._pendingEdits, - { editor: this.editor, }, + { context: this.editor, }, this._commentOptions, { actionRunner: async () => { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index c4e82b03efc..6b744fc22cb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -14,7 +14,7 @@ import { DisposableStore, MutableDisposable, toDisposable } from '../../../../ba import { autorun, constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { EditorOption, IComputedEditorOptions } from '../../../../editor/common/config/editorOptions.js'; import { LineRange } from '../../../../editor/common/core/ranges/lineRange.js'; import { Position } from '../../../../editor/common/core/position.js'; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 040b9b0013b..e9f0ed424fa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -34,7 +34,7 @@ import { isIOS } from '../../../../base/common/platform.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; import { isDefined, isUndefinedOrNull } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { localize } from '../../../../nls.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e756f9470cc..359bbf4c5d0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -72,7 +72,7 @@ import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.j import { Selection } from '../../../../editor/common/core/selection.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; import { createActionViewItem, getFlatActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { Button, ButtonWithDescription, ButtonWithDropdown } from '../../../../base/browser/ui/button/button.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { RepositoryContextKeys } from './scmViewService.js'; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index 0c567285838..5a7a19cb2bf 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -16,7 +16,7 @@ import { CodeEditorWidget } from '../../../../../editor/browser/widget/codeEdito import { EmbeddedCodeEditorWidget } from '../../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { DiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/diffEditorWidget.js'; import { EmbeddedDiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; -import { IMarkdownRendererService } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IDiffEditorOptions, IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 7f6ad610685..e111e2f44c8 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -30,7 +30,7 @@ import { fuzzyContains } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { MenuEntryActionViewItem, createActionViewItem, getActionBarActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index ca08163b409..1c86c5a1f6f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -28,7 +28,7 @@ import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import './media/gettingStarted.css'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { localize } from '../../../../nls.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; diff --git a/src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts index ae9246bacf4..729fc617379 100644 --- a/src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-browser/parts/dialogs/dialog.contribution.ts @@ -22,7 +22,7 @@ import { Lazy } from '../../../../base/common/lazy.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { createNativeAboutDialogDetails } from '../../../../platform/dialogs/electron-browser/dialog.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts index ae36b6b3ca5..214dfb4c047 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts @@ -13,7 +13,7 @@ import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resi import * as nls from '../../../../nls.js'; import { SimpleCompletionItem } from './simpleCompletionItem.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { IMarkdownRendererService } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ISimpleSuggestWidgetFontInfo } from './simpleSuggestWidgetRenderer.js'; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 1d20a4dcaab..9879d14d965 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -26,7 +26,7 @@ import { assertReturnsDefined, upcast } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { ICodeEditor } from '../../../editor/browser/editorBrowser.js'; import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js'; -import { IMarkdownRendererService, MarkdownRendererService } from '../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { IMarkdownRendererService, MarkdownRendererService } from '../../../platform/markdown/browser/markdownRenderer.js'; import { Position as EditorPosition, IPosition } from '../../../editor/common/core/position.js'; import { Range } from '../../../editor/common/core/range.js'; import { Selection } from '../../../editor/common/core/selection.js'; From 951289f9411e73b7feefbb8aeb874b5c6b3f97ea Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Oct 2025 08:57:22 +0200 Subject: [PATCH 1012/4355] =?UTF-8?q?Show=20=E2=9C=A8=20for=20rename=20sug?= =?UTF-8?q?gestions=20OOTB=20(fix=20#270489)=20(#270490)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/rename/browser/renameWidget.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 145 ++++++++++++------ 2 files changed, 101 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 340d9dc2e83..f2bc9d613f2 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -930,7 +930,7 @@ class InputWithButton implements IDisposable { this._buttonNode.className = 'rename-suggestions-button'; this._buttonNode.setAttribute('tabindex', '0'); - this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate new name suggestions"); + this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate New Name Suggestions"); this._buttonCancelHoverText = nls.localize('cancelRenameSuggestionsButton', "Cancel"); this._buttonHoverContent = this._buttonGenHoverText; this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 503be92d784..6af48705f3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -76,6 +76,10 @@ import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { NewSymbolName, NewSymbolNameTriggerKind } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { IRange } from '../../../../editor/common/core/range.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -145,7 +149,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { }); } - static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): { disposable: IDisposable } { + static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): IDisposable { return instantiationService.invokeFunction(accessor => { const chatAgentService = accessor.get(IChatAgentService); @@ -174,9 +178,9 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { canBeReferencedInPrompt: true, toolReferenceName: 'new', when: ContextKeyExpr.true(), - }).disposable); + })); - return { disposable: disposables }; + return disposables; }); } @@ -454,7 +458,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ - disableChatViewReveal: true, // we are already in a chat context + disableChatViewReveal: true, // we are already in a chat context forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively }); } catch (error) { @@ -583,7 +587,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { class SetupTool extends Disposable implements IToolImpl { - static registerTool(instantiationService: IInstantiationService, toolData: IToolData): { tool: SetupTool; disposable: IDisposable } { + static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable { return instantiationService.invokeFunction(accessor => { const toolService = accessor.get(ILanguageModelToolsService); @@ -592,7 +596,7 @@ class SetupTool extends Disposable implements IToolImpl { const tool = instantiationService.createInstance(SetupTool); disposables.add(toolService.registerTool(toolData, tool)); - return { tool, disposable: disposables }; + return disposables; }); } @@ -614,6 +618,41 @@ class SetupTool extends Disposable implements IToolImpl { } } +class DefaultNewSymbolNamesProvider extends Disposable { + + static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): IDisposable { + return instantiationService.invokeFunction(accessor => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + + const disposables = new DisposableStore(); + + const provider = disposables.add(instantiationService.createInstance(DefaultNewSymbolNamesProvider, context, controller)); + disposables.add(languageFeaturesService.newSymbolNamesProvider.register('*', provider)); + + return disposables; + }); + } + + constructor( + private readonly context: ChatEntitlementContext, + private readonly controller: Lazy, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + ) { + super(); + } + + async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise { + await this.instantiationService.invokeFunction(accessor => { + return ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ + forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined + }); + }); + + return []; + } +} + enum ChatSetupStrategy { Canceled = 0, DefaultSetup = 1, @@ -886,53 +925,69 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr return; // TODO@bpasero eventually remove this when we figured out extension activation issues } - const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload - const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); - const updateRegistration = () => { - if (!context.state.hidden && !context.state.disabled) { - - // Default Agents (always, even if installed to allow for speedy requests right on startup) - if (!defaultAgentDisposables.value) { - const disposables = defaultAgentDisposables.value = new DisposableStore(); - - // Panel Agents - const panelAgentDisposables = disposables.add(new DisposableStore()); - for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) { - const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller); - panelAgentDisposables.add(disposable); - panelAgentDisposables.add(agent.onUnresolvableError(() => { - const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); - if (panelAgentHasGuidance) { - // An unresolvable error from our agent registrations means that - // Copilot is unhealthy for some reason. We clear our panel - // registration to give Copilot a chance to show a custom message - // to the user from the views and stop pretending as if there was - // a functional agent. - this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.'); - panelAgentDisposables.dispose(); - } - })); + + // Agent + Tools + { + const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload + const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); + if (!context.state.hidden && !context.state.disabled) { + + // Default Agents (always, even if installed to allow for speedy requests right on startup) + if (!defaultAgentDisposables.value) { + const disposables = defaultAgentDisposables.value = new DisposableStore(); + + // Panel Agents + const panelAgentDisposables = disposables.add(new DisposableStore()); + for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) { + const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller); + panelAgentDisposables.add(disposable); + panelAgentDisposables.add(agent.onUnresolvableError(() => { + const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); + if (panelAgentHasGuidance) { + // An unresolvable error from our agent registrations means that + // Copilot is unhealthy for some reason. We clear our panel + // registration to give Copilot a chance to show a custom message + // to the user from the views and stop pretending as if there was + // a functional agent. + this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.'); + panelAgentDisposables.dispose(); + } + })); + } + + // Inline Agents + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable); } - // Inline Agents - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable); + // Built-In Agent + Tool (unless installed, signed-in and enabled) + if ((!context.state.installed || context.state.entitlement === ChatEntitlement.Unknown || context.state.entitlement === ChatEntitlement.Unresolved) && !vscodeAgentDisposables.value) { + const disposables = vscodeAgentDisposables.value = new DisposableStore(); + disposables.add(SetupAgent.registerBuiltInAgents(this.instantiationService, context, controller)); + } + } else { + defaultAgentDisposables.clear(); + vscodeAgentDisposables.clear(); } - // Built-In Agent + Tool (unless installed, signed-in and enabled) - if ((!context.state.installed || context.state.entitlement === ChatEntitlement.Unknown || context.state.entitlement === ChatEntitlement.Unresolved) && !vscodeAgentDisposables.value) { - const disposables = vscodeAgentDisposables.value = new DisposableStore(); - disposables.add(SetupAgent.registerBuiltInAgents(this.instantiationService, context, controller).disposable); + if (context.state.installed && !context.state.disabled) { + vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list } - } else { - defaultAgentDisposables.clear(); - vscodeAgentDisposables.clear(); } - if (context.state.installed && !context.state.disabled) { - vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list + // Rename Provider + { + const renameProviderDisposables = markAsSingleton(new MutableDisposable()); + + if (!context.state.installed && !context.state.hidden && !context.state.disabled) { + if (!renameProviderDisposables.value) { + renameProviderDisposables.value = DefaultNewSymbolNamesProvider.registerProvider(this.instantiationService, context, controller); + } + } else { + renameProviderDisposables.clear(); + } } }; From 6e16d95fd4ebb65da2a03e133da073059c58deee Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 9 Oct 2025 17:06:47 +0900 Subject: [PATCH 1013/4355] chore: retry entitlement signing failure (#270324) --- build/darwin/sign.js | 25 ++++++++++++++++++++++++- build/darwin/sign.ts | 31 ++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/build/darwin/sign.js b/build/darwin/sign.js index 5824fdef8f5..af8dad69b34 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -35,6 +35,29 @@ function getEntitlementsForFile(filePath) { } return path_1.default.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'); } +async function retrySignOnKeychainError(fn, maxRetries = 3) { + let lastError; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } + catch (error) { + lastError = error; + // Check if this is the specific keychain error we want to retry + const errorMessage = error instanceof Error ? error.message : String(error); + const isKeychainError = errorMessage.includes('The specified item could not be found in the keychain.'); + if (!isKeychainError || attempt === maxRetries) { + throw error; + } + console.log(`Signing attempt ${attempt} failed with keychain error, retrying...`); + console.log(`Error: ${errorMessage}`); + const delay = 1000 * Math.pow(2, attempt - 1); + console.log(`Waiting ${Math.round(delay)}ms before retry ${attempt}/${maxRetries}...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + throw lastError; +} async function main(buildDir) { const tempDir = process.env['AGENT_TEMPDIRECTORY']; const arch = process.env['VSCODE_ARCH']; @@ -86,7 +109,7 @@ async function main(buildDir) { `${infoPlistPath}` ]); } - await (0, osx_sign_1.sign)(appOpts); + await retrySignOnKeychainError(() => (0, osx_sign_1.sign)(appOpts)); } if (require.main === module) { main(process.argv[2]).catch(async err => { diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 83f18c6a5a7..ca3ced9138a 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -33,6 +33,35 @@ function getEntitlementsForFile(filePath: string): string { return path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'); } +async function retrySignOnKeychainError(fn: () => Promise, maxRetries: number = 3): Promise { + let lastError: Error | undefined; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error as Error; + + // Check if this is the specific keychain error we want to retry + const errorMessage = error instanceof Error ? error.message : String(error); + const isKeychainError = errorMessage.includes('The specified item could not be found in the keychain.'); + + if (!isKeychainError || attempt === maxRetries) { + throw error; + } + + console.log(`Signing attempt ${attempt} failed with keychain error, retrying...`); + console.log(`Error: ${errorMessage}`); + + const delay = 1000 * Math.pow(2, attempt - 1); + console.log(`Waiting ${Math.round(delay)}ms before retry ${attempt}/${maxRetries}...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw lastError; +} + async function main(buildDir?: string): Promise { const tempDir = process.env['AGENT_TEMPDIRECTORY']; const arch = process.env['VSCODE_ARCH']; @@ -90,7 +119,7 @@ async function main(buildDir?: string): Promise { ]); } - await sign(appOpts); + await retrySignOnKeychainError(() => sign(appOpts)); } if (require.main === module) { From 455b8a5925be303d9b95408dec42c8fc9717177b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Oct 2025 10:13:28 +0200 Subject: [PATCH 1014/4355] Inline edit toolbar is tiny (fix microsoft/vscode-copilot#17191) (#270423) --- .../chatEditingCodeEditorIntegration.ts | 12 ++++++++ .../chatEditing/chatEditingEditorActions.ts | 28 ++++++++++++++++--- .../notebook/overlayToolbarDecorator.ts | 12 ++++++++ .../browser/media/chatEditorController.css | 2 +- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index 9e17289b839..68204ef98a2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -40,6 +40,8 @@ import { IChatAgentService } from '../../common/chatAgents.js'; import { IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { isTextDiffEditorForEntry } from './chatEditing.js'; +import { ActionViewItem } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { AcceptHunkAction, RejectHunkAction } from './chatEditingEditorActions.js'; export interface IDocumentDiff2 extends IDocumentDiff { @@ -690,6 +692,16 @@ class DiffHunkWidget implements IOverlayWidget, IModifiedFileEntryChangeHunk { renderShortTitle: true, arg: this, }, + actionViewItemProvider: (action, options) => { + if (action.id === AcceptHunkAction.ID || action.id === RejectHunkAction.ID) { + return new class extends ActionViewItem { + constructor() { + super(undefined, action, { ...options, keybindingNotRenderedWithLabel: true, icon: false, label: true }); + } + }; + } + return undefined; + } }); this._store.add(toolbar); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 32f636aebf8..1f401c1054d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -226,15 +226,17 @@ export class RejectAction extends KeepOrUndoAction { } } +const acceptHunkId = 'chatEditor.action.acceptHunk'; +const undoHunkId = 'chatEditor.action.undoHunk'; abstract class AcceptRejectHunkAction extends ChatEditingEditorAction { constructor(private readonly _accept: boolean) { super( { - id: _accept ? 'chatEditor.action.acceptHunk' : 'chatEditor.action.undoHunk', + id: _accept ? acceptHunkId : undoHunkId, title: _accept ? localize2('acceptHunk', 'Keep this Change') : localize2('undo', 'Undo this Change'), + shortTitle: _accept ? localize2('acceptHunkShort', 'Keep') : localize2('undoShort', 'Undo'), precondition: ContextKeyExpr.and(ctxHasEditorModification, ctxIsCurrentlyBeingModified.negate()), - icon: _accept ? Codicon.check : Codicon.discard, f1: true, keybinding: { when: ContextKeyExpr.or(EditorContextKeys.focus, NOTEBOOK_CELL_LIST_FOCUSED), @@ -268,6 +270,24 @@ abstract class AcceptRejectHunkAction extends ChatEditingEditorAction { } } +export class AcceptHunkAction extends AcceptRejectHunkAction { + + static readonly ID = acceptHunkId; + + constructor() { + super(true); + } +} + +export class RejectHunkAction extends AcceptRejectHunkAction { + + static readonly ID = undoHunkId; + + constructor() { + super(false); + } +} + class ToggleDiffAction extends ChatEditingEditorAction { constructor() { super({ @@ -422,8 +442,8 @@ export function registerChatEditorActions() { registerAction2(AcceptAction); registerAction2(RejectAction); registerAction2(AcceptAllEditsAction); - registerAction2(class AcceptHunkAction extends AcceptRejectHunkAction { constructor() { super(true); } }); - registerAction2(class RejectHunkAction extends AcceptRejectHunkAction { constructor() { super(false); } }); + registerAction2(AcceptHunkAction); + registerAction2(RejectHunkAction); registerAction2(ToggleDiffAction); registerAction2(ToggleAccessibleDiffViewAction); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts index 9e6acbd4fb4..d78c5157738 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ActionViewItem } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { MenuWorkbenchToolBar, HiddenItemStrategy } from '../../../../../../platform/actions/browser/toolbar.js'; @@ -14,6 +15,7 @@ import { CellEditState, INotebookEditor } from '../../../../notebook/browser/not import { NotebookTextModel } from '../../../../notebook/common/model/notebookTextModel.js'; import { CellKind } from '../../../../notebook/common/notebookCommon.js'; import { IModifiedFileEntryChangeHunk } from '../../../common/chatEditingService.js'; +import { AcceptHunkAction, RejectHunkAction } from '../chatEditingEditorActions.js'; import { ICellDiffInfo } from './notebookCellChanges.js'; @@ -120,6 +122,16 @@ export class OverlayToolbarDecorator extends Disposable { } } satisfies IModifiedFileEntryChangeHunk, }, + actionViewItemProvider: (action, options) => { + if (action.id === AcceptHunkAction.ID || action.id === RejectHunkAction.ID) { + return new class extends ActionViewItem { + constructor() { + super(undefined, action, { ...options, keybindingNotRenderedWithLabel: true, icon: false, label: true }); + } + }; + } + return undefined; + } }); this.overlayDisposables.add(toolbarWidget); diff --git a/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css index 3c07814d88e..a574066762a 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css @@ -22,9 +22,9 @@ } .chat-diff-change-content-widget .monaco-action-bar .action-item .action-label { - height: 14px; border-radius: 2px; color: var(--vscode-button-foreground); + padding: 2px 5px; } .chat-diff-change-content-widget .monaco-action-bar .action-item .action-label.codicon { From 3cd596e34322ed4ec6e5f3727f7a0879a84c89f6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 9 Oct 2025 11:39:55 +0200 Subject: [PATCH 1015/4355] fix command handler arg, https://github.com/microsoft/vscode/pull/270134 --- src/vs/base/common/types.ts | 9 ++- src/vs/base/test/common/types.test.ts | 79 +++++++++++++++++++ .../smartSelect/browser/smartSelect.ts | 5 +- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index f57a992d606..cef83d93e3a 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -16,7 +16,14 @@ export function isString(str: unknown): str is string { * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. */ export function isStringArray(value: unknown): value is string[] { - return Array.isArray(value) && (value).every(elem => isString(elem)); + return isArrayOf(value, isString); +} + +/** + * @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard. + */ +export function isArrayOf(value: unknown, check: (item: unknown) => item is T): value is T[] { + return Array.isArray(value) && value.every(check); } /** diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 7195c458a57..50139125802 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -104,6 +104,85 @@ suite('Types', () => { assert(types.isString('foo')); }); + test('isStringArray', () => { + assert(!types.isStringArray(undefined)); + assert(!types.isStringArray(null)); + assert(!types.isStringArray(5)); + assert(!types.isStringArray('foo')); + assert(!types.isStringArray(true)); + assert(!types.isStringArray({})); + assert(!types.isStringArray(/test/)); + assert(!types.isStringArray(new RegExp(''))); + assert(!types.isStringArray(new Date())); + assert(!types.isStringArray(assert)); + assert(!types.isStringArray(function foo() { /**/ })); + assert(!types.isStringArray({ foo: 'bar' })); + assert(!types.isStringArray([1, 2, 3])); + assert(!types.isStringArray([1, 2, '3'])); + assert(!types.isStringArray(['foo', 'bar', 5])); + assert(!types.isStringArray(['foo', null, 'bar'])); + assert(!types.isStringArray(['foo', undefined, 'bar'])); + + assert(types.isStringArray([])); + assert(types.isStringArray(['foo'])); + assert(types.isStringArray(['foo', 'bar'])); + assert(types.isStringArray(['foo', 'bar', 'baz'])); + }); + + test('isArrayOf', () => { + // Basic non-array values + assert(!types.isArrayOf(undefined, types.isString)); + assert(!types.isArrayOf(null, types.isString)); + assert(!types.isArrayOf(5, types.isString)); + assert(!types.isArrayOf('foo', types.isString)); + assert(!types.isArrayOf(true, types.isString)); + assert(!types.isArrayOf({}, types.isString)); + assert(!types.isArrayOf(/test/, types.isString)); + assert(!types.isArrayOf(new RegExp(''), types.isString)); + assert(!types.isArrayOf(new Date(), types.isString)); + assert(!types.isArrayOf(assert, types.isString)); + assert(!types.isArrayOf(function foo() { /**/ }, types.isString)); + assert(!types.isArrayOf({ foo: 'bar' }, types.isString)); + + // Arrays with wrong types + assert(!types.isArrayOf([1, 2, 3], types.isString)); + assert(!types.isArrayOf([1, 2, '3'], types.isString)); + assert(!types.isArrayOf(['foo', 'bar', 5], types.isString)); + assert(!types.isArrayOf(['foo', null, 'bar'], types.isString)); + assert(!types.isArrayOf(['foo', undefined, 'bar'], types.isString)); + + // Valid string arrays + assert(types.isArrayOf([], types.isString)); + assert(types.isArrayOf(['foo'], types.isString)); + assert(types.isArrayOf(['foo', 'bar'], types.isString)); + assert(types.isArrayOf(['foo', 'bar', 'baz'], types.isString)); + + // Valid number arrays + assert(types.isArrayOf([], types.isNumber)); + assert(types.isArrayOf([1], types.isNumber)); + assert(types.isArrayOf([1, 2, 3], types.isNumber)); + assert(!types.isArrayOf([1, 2, '3'], types.isNumber)); + + // Valid boolean arrays + assert(types.isArrayOf([], types.isBoolean)); + assert(types.isArrayOf([true], types.isBoolean)); + assert(types.isArrayOf([true, false, true], types.isBoolean)); + assert(!types.isArrayOf([true, 1, false], types.isBoolean)); + + // Valid function arrays + assert(types.isArrayOf([], types.isFunction)); + assert(types.isArrayOf([assert], types.isFunction)); + assert(types.isArrayOf([assert, function foo() { /**/ }], types.isFunction)); + assert(!types.isArrayOf([assert, 'foo'], types.isFunction)); + + // Custom type guard + const isEven = (n: unknown): n is number => types.isNumber(n) && n % 2 === 0; + assert(types.isArrayOf([], isEven)); + assert(types.isArrayOf([2, 4, 6], isEven)); + assert(!types.isArrayOf([2, 3, 4], isEven)); + assert(!types.isArrayOf([1, 3, 5], isEven)); + }); + test('isNumber', () => { assert(!types.isNumber(undefined)); assert(!types.isNumber(null)); diff --git a/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts b/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts index 520e71d28bf..c2c067878b4 100644 --- a/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts @@ -27,7 +27,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; import { ITextModelService } from '../../../common/services/resolverService.js'; -import { assertType } from '../../../../base/common/types.js'; +import { assertType, isArrayOf } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; class SelectionRanges { @@ -307,12 +307,13 @@ CommandsRegistry.registerCommand('_executeSelectionRangeProvider', async functio const [resource, positions] = args; assertType(URI.isUri(resource)); + assertType(isArrayOf(positions, p => Position.isIPosition(p))); const registry = accessor.get(ILanguageFeaturesService).selectionRangeProvider; const reference = await accessor.get(ITextModelService).createModelReference(resource); try { - return provideSelectionRanges(registry, reference.object.textEditorModel, positions, { selectLeadingAndTrailingWhitespace: true, selectSubwords: true }, CancellationToken.None); + return provideSelectionRanges(registry, reference.object.textEditorModel, positions.map(Position.lift), { selectLeadingAndTrailingWhitespace: true, selectSubwords: true }, CancellationToken.None); } finally { reference.dispose(); } From 9cf4f6eb6ef0eaa887ec2aae35dda7dea521ae0c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 9 Oct 2025 11:06:22 +0100 Subject: [PATCH 1016/4355] Update color definitions for keyword control in YAML and TypeScript test files - Changed the color for "keyword.control" from #C586C0 to #CE92A4 in various test JSON files for better visibility across themes. --- .../test/colorize-results/basic_java.json | 12 +- .../colorize-results/issue-28354_php.json | 4 +- .../test/colorize-results/makefile.json | 64 +-- .../test/colorize-results/test-173336_sh.json | 12 +- .../test/colorize-results/test-23630_cpp.json | 24 +- .../test/colorize-results/test-23850_cpp.json | 24 +- .../test/colorize-results/test-241001_ts.json | 8 +- .../test/colorize-results/test-6611_rs.json | 8 +- .../test/colorize-results/test-78769_cpp.json | 8 +- .../test-freeze-56377_py.json | 12 +- .../colorize-results/test-issue11_ts.json | 28 +- .../colorize-results/test-issue241715_ts.json | 32 +- .../colorize-results/test-issue5431_ts.json | 4 +- .../colorize-results/test-issue5465_ts.json | 8 +- .../colorize-results/test-keywords_ts.json | 4 +- .../test/colorize-results/test2_pl.json | 80 ++-- .../test/colorize-results/test6916_js.json | 16 +- .../test/colorize-results/test_bat.json | 18 +- .../test/colorize-results/test_c.json | 36 +- .../test/colorize-results/test_cc.json | 24 +- .../test/colorize-results/test_clj.json | 28 +- .../test/colorize-results/test_coffee.json | 24 +- .../test/colorize-results/test_cpp.json | 44 +- .../test/colorize-results/test_cs.json | 16 +- .../test/colorize-results/test_cshtml.json | 64 +-- .../test/colorize-results/test_css.json | 32 +- .../test/colorize-results/test_cu.json | 136 +++--- .../test/colorize-results/test_dart.json | 4 +- .../test/colorize-results/test_go.json | 8 +- .../test/colorize-results/test_groovy.json | 56 +-- .../colorize-results/test_handlebars.json | 36 +- .../test/colorize-results/test_hbs.json | 24 +- .../test/colorize-results/test_hlsl.json | 4 +- .../test/colorize-results/test_jl.json | 44 +- .../test/colorize-results/test_js.json | 16 +- .../test/colorize-results/test_jsx.json | 12 +- .../test/colorize-results/test_less.json | 32 +- .../test/colorize-results/test_lua.json | 32 +- .../test/colorize-results/test_m.json | 32 +- .../test/colorize-results/test_mm.json | 32 +- .../test/colorize-results/test_php.json | 20 +- .../test/colorize-results/test_pl.json | 32 +- .../test/colorize-results/test_ps1.json | 44 +- .../test/colorize-results/test_py.json | 120 ++--- .../test/colorize-results/test_r.json | 4 +- .../test/colorize-results/test_rb.json | 48 +- .../test/colorize-results/test_rst.json | 104 ++--- .../test/colorize-results/test_scss.json | 424 +++++++++--------- .../test/colorize-results/test_sh.json | 34 +- .../test/colorize-results/test_swift.json | 20 +- .../test/colorize-results/test_tex.json | 28 +- .../test/colorize-results/test_ts.json | 88 ++-- .../test/colorize-results/test_vb.json | 36 +- .../test/colorize-results/test_yaml.json | 16 +- .../test-241001_ts.json | 8 +- .../test-issue11_ts.json | 28 +- .../test-issue241715_ts.json | 32 +- .../test-issue5431_ts.json | 4 +- .../test-issue5465_ts.json | 8 +- .../test-keywords_ts.json | 4 +- .../test_css.json | 16 +- .../colorize-tree-sitter-results/test_ts.json | 88 ++-- 62 files changed, 1154 insertions(+), 1154 deletions(-) diff --git a/extensions/vscode-colorize-tests/test/colorize-results/basic_java.json b/extensions/vscode-colorize-tests/test/colorize-results/basic_java.json index 71a5a901280..843740c186f 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/basic_java.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/basic_java.json @@ -1697,12 +1697,12 @@ "c": "for", "t": "source.java meta.class.java meta.class.body.java meta.method.java meta.method.body.java keyword.control.java", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2243,12 +2243,12 @@ "c": "return", "t": "source.java meta.class.java meta.class.body.java meta.method.java meta.method.body.java keyword.control.java", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2817,12 +2817,12 @@ "c": "new", "t": "source.java meta.class.java meta.class.body.java meta.method.java meta.method.body.java keyword.control.new.java", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/issue-28354_php.json b/extensions/vscode-colorize-tests/test/colorize-results/issue-28354_php.json index 77914003b54..642bf6542ff 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/issue-28354_php.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/issue-28354_php.json @@ -115,12 +115,12 @@ "c": "foreach", "t": "text.html.php meta.embedded.block.html source.js meta.embedded.block.php source.php keyword.control.foreach.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/makefile.json b/extensions/vscode-colorize-tests/test/colorize-results/makefile.json index b03ac95a7f9..db94f1063e4 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/makefile.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/makefile.json @@ -1193,12 +1193,12 @@ "c": "@", "t": "source.makefile meta.scope.recipe.makefile keyword.control.@.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1235,12 +1235,12 @@ "c": "@-+-+", "t": "source.makefile meta.scope.recipe.makefile keyword.control.@-+-+.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1291,12 +1291,12 @@ "c": "@", "t": "source.makefile meta.scope.recipe.makefile keyword.control.@.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1487,12 +1487,12 @@ "c": "@-", "t": "source.makefile meta.scope.recipe.makefile keyword.control.@-.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1515,12 +1515,12 @@ "c": "define", "t": "source.makefile meta.scope.conditional.makefile keyword.control.define.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2075,12 +2075,12 @@ "c": "endef", "t": "source.makefile meta.scope.conditional.makefile keyword.control.override.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2089,12 +2089,12 @@ "c": "ifeq", "t": "source.makefile meta.scope.conditional.makefile keyword.control.ifeq.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2355,12 +2355,12 @@ "c": "endif", "t": "source.makefile meta.scope.conditional.makefile keyword.control.endif.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2369,12 +2369,12 @@ "c": "-include", "t": "source.makefile keyword.control.include.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2453,12 +2453,12 @@ "c": "ifeq", "t": "source.makefile meta.scope.conditional.makefile keyword.control.ifeq.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2775,12 +2775,12 @@ "c": "endif", "t": "source.makefile meta.scope.conditional.makefile keyword.control.endif.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3237,12 +3237,12 @@ "c": "ifeq", "t": "source.makefile meta.scope.conditional.makefile keyword.control.ifeq.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3419,12 +3419,12 @@ "c": "else ifeq", "t": "source.makefile meta.scope.conditional.makefile keyword.control.else.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3601,12 +3601,12 @@ "c": "else", "t": "source.makefile meta.scope.conditional.makefile keyword.control.else.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3755,12 +3755,12 @@ "c": "endif", "t": "source.makefile meta.scope.conditional.makefile keyword.control.endif.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4931,12 +4931,12 @@ "c": "export", "t": "source.makefile keyword.control.export.makefile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json index c1875bfa986..720a867ca74 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-173336_sh.json @@ -213,12 +213,12 @@ "c": "if", "t": "source.shell meta.scope.if-block.shell keyword.control.if.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -437,12 +437,12 @@ "c": "then", "t": "source.shell meta.scope.if-block.shell keyword.control.then.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -815,12 +815,12 @@ "c": "fi", "t": "source.shell meta.scope.if-block.shell keyword.control.fi.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-23630_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test-23630_cpp.json index 79e4727a417..222b60af2e0 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-23630_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-23630_cpp.json @@ -3,12 +3,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -73,12 +73,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -87,12 +87,12 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -129,12 +129,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -143,12 +143,12 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-23850_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test-23850_cpp.json index a5e6addc3b1..61251a59c3c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-23850_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-23850_cpp.json @@ -3,12 +3,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -59,12 +59,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -73,12 +73,12 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -115,12 +115,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -129,12 +129,12 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-241001_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-241001_ts.json index 67b874115af..e2b376a059c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-241001_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-241001_ts.json @@ -955,12 +955,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3027,12 +3027,12 @@ "c": "import", "t": "source.ts new.expr.ts keyword.control.import.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-6611_rs.json b/extensions/vscode-colorize-tests/test/colorize-results/test-6611_rs.json index 9897e09a654..a8dc0e0fd28 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-6611_rs.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-6611_rs.json @@ -381,12 +381,12 @@ "c": "for", "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -689,12 +689,12 @@ "c": "for", "t": "source.rust keyword.control.rust", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json index 16438692b78..a1a55fd67db 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json @@ -3,12 +3,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-freeze-56377_py.json b/extensions/vscode-colorize-tests/test/colorize-results/test-freeze-56377_py.json index 432ecde8cce..994f91b2ae1 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-freeze-56377_py.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-freeze-56377_py.json @@ -269,12 +269,12 @@ "c": "for", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -325,12 +325,12 @@ "c": "in", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -493,12 +493,12 @@ "c": "if", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-issue11_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-issue11_ts.json index 68717cc4939..957fe515fb7 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-issue11_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-issue11_ts.json @@ -115,12 +115,12 @@ "c": "if", "t": "source.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -465,12 +465,12 @@ "c": "for", "t": "source.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "for", "t": "source.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1025,12 +1025,12 @@ "c": "for", "t": "source.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1305,12 +1305,12 @@ "c": "for", "t": "source.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1473,12 +1473,12 @@ "c": "for", "t": "source.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3055,12 +3055,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-issue241715_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-issue241715_ts.json index da9e674e46c..9489f1d5b91 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-issue241715_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-issue241715_ts.json @@ -633,12 +633,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1081,12 +1081,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1711,12 +1711,12 @@ "c": "export", "t": "source.ts meta.interface.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2159,12 +2159,12 @@ "c": "return", "t": "source.ts meta.arrow.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2257,12 +2257,12 @@ "c": "export", "t": "source.ts meta.function.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2523,12 +2523,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3475,12 +3475,12 @@ "c": "export", "t": "source.ts meta.function.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3853,12 +3853,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-issue5431_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-issue5431_ts.json index c1988500c97..e093fe01582 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-issue5431_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-issue5431_ts.json @@ -591,12 +591,12 @@ "c": "return", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-issue5465_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-issue5465_ts.json index 4282511c5e8..b238ed5c61a 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-issue5465_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-issue5465_ts.json @@ -129,12 +129,12 @@ "c": "yield", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -227,12 +227,12 @@ "c": "yield", "t": "source.ts meta.function.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-keywords_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test-keywords_ts.json index a365ac3d098..091238d6e12 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-keywords_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-keywords_ts.json @@ -3,12 +3,12 @@ "c": "export", "t": "source.ts meta.var.expr.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test2_pl.json b/extensions/vscode-colorize-tests/test/colorize-results/test2_pl.json index 7d7b3bbe3e5..dde3a9855c8 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test2_pl.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test2_pl.json @@ -3,12 +3,12 @@ "c": "die", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -129,12 +129,12 @@ "c": "unless", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -353,12 +353,12 @@ "c": "i", "t": "source.perl string.regexp.find.perl punctuation.definition.string.perl keyword.control.regexp-option.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -619,12 +619,12 @@ "c": "i", "t": "source.perl string.regexp.find.perl punctuation.definition.string.perl keyword.control.regexp-option.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "while", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1291,12 +1291,12 @@ "c": "next", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1319,12 +1319,12 @@ "c": "unless", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1529,12 +1529,12 @@ "c": "next", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1557,12 +1557,12 @@ "c": "unless", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1725,12 +1725,12 @@ "c": "$", "t": "source.perl string.regexp.find.perl keyword.control.anchor.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2131,12 +2131,12 @@ "c": "next", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2159,12 +2159,12 @@ "c": "unless", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2285,12 +2285,12 @@ "c": "$", "t": "source.perl string.regexp.find.perl keyword.control.anchor.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2439,12 +2439,12 @@ "c": "next", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2467,12 +2467,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2901,12 +2901,12 @@ "c": "for", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3419,12 +3419,12 @@ "c": "next", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3447,12 +3447,12 @@ "c": "unless", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3629,12 +3629,12 @@ "c": "for", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3965,12 +3965,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test6916_js.json b/extensions/vscode-colorize-tests/test/colorize-results/test6916_js.json index 4fa10b042fa..5183b00921b 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test6916_js.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test6916_js.json @@ -3,12 +3,12 @@ "c": "for", "t": "source.js keyword.control.loop.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -227,12 +227,12 @@ "c": "for", "t": "source.js meta.block.js keyword.control.loop.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -423,12 +423,12 @@ "c": "if", "t": "source.js meta.block.js meta.block.js keyword.control.conditional.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -535,12 +535,12 @@ "c": "return", "t": "source.js meta.block.js meta.block.js keyword.control.flow.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_bat.json b/extensions/vscode-colorize-tests/test/colorize-results/test_bat.json index 853018d8458..4611be77db0 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_bat.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_bat.json @@ -199,12 +199,12 @@ "c": "if", "t": "source.batchfile keyword.control.conditional.batchfile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -283,12 +283,12 @@ "c": "call", "t": "source.batchfile keyword.control.statement.batchfile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -381,12 +381,12 @@ "c": "if", "t": "source.batchfile keyword.control.conditional.batchfile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -563,12 +563,12 @@ "c": "call", "t": "source.batchfile keyword.control.statement.batchfile", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -685,4 +685,4 @@ "light_modern": "keyword: #0000FF" } } -] +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_c.json b/extensions/vscode-colorize-tests/test/colorize-results/test_c.json index fbbf86a1c70..ecf56307bae 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_c.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_c.json @@ -87,12 +87,12 @@ "c": "#", "t": "source.c meta.preprocessor.include.c keyword.control.directive.include.c punctuation.definition.directive.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -101,12 +101,12 @@ "c": "include", "t": "source.c meta.preprocessor.include.c keyword.control.directive.include.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -171,12 +171,12 @@ "c": "#", "t": "source.c meta.preprocessor.include.c keyword.control.directive.include.c punctuation.definition.directive.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -185,12 +185,12 @@ "c": "include", "t": "source.c meta.preprocessor.include.c keyword.control.directive.include.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1179,12 +1179,12 @@ "c": "if", "t": "source.c meta.block.c keyword.control.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2089,12 +2089,12 @@ "c": "else", "t": "source.c meta.block.c keyword.control.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2117,12 +2117,12 @@ "c": "if", "t": "source.c meta.block.c keyword.control.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2691,12 +2691,12 @@ "c": "else", "t": "source.c meta.block.c keyword.control.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3489,12 +3489,12 @@ "c": "return", "t": "source.c meta.block.c keyword.control.c", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json index 19e19cec621..446f09c02c7 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json @@ -3,12 +3,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.if.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "if", "t": "source.cpp keyword.control.directive.conditional.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -269,12 +269,12 @@ "c": "for", "t": "source.cpp keyword.control.for.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -829,12 +829,12 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -843,12 +843,12 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1655,12 +1655,12 @@ "c": "new", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp keyword.operator.wordlike.cpp keyword.operator.new.cpp", "r": { - "dark_plus": "source.cpp keyword.operator.new: #C586C0", + "dark_plus": "source.cpp keyword.operator.new: #CE92A4", "light_plus": "source.cpp keyword.operator.new: #AF00DB", "dark_vs": "keyword.operator.new: #569CD6", "light_vs": "keyword.operator.new: #0000FF", "hc_black": "source.cpp keyword.operator.new: #C586C0", - "dark_modern": "source.cpp keyword.operator.new: #C586C0", + "dark_modern": "source.cpp keyword.operator.new: #CE92A4", "hc_light": "source.cpp keyword.operator.new: #B5200D", "light_modern": "source.cpp keyword.operator.new: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_clj.json b/extensions/vscode-colorize-tests/test/colorize-results/test_clj.json index 23597c44358..99786f9fec9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_clj.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_clj.json @@ -45,12 +45,12 @@ "c": "require", "t": "source.clojure meta.expression.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -199,12 +199,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -885,12 +885,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2383,12 +2383,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2565,12 +2565,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3167,12 +3167,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3685,12 +3685,12 @@ "c": "def", "t": "source.clojure meta.expression.clojure meta.definition.global.clojure keyword.control.clojure", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_coffee.json b/extensions/vscode-colorize-tests/test/colorize-results/test_coffee.json index 52efa92c373..7f79a5a6251 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_coffee.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_coffee.json @@ -507,12 +507,12 @@ "c": "extends", "t": "source.coffee meta.class.coffee keyword.control.inheritance.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -941,12 +941,12 @@ "c": "while", "t": "source.coffee keyword.control.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1221,12 +1221,12 @@ "c": "for", "t": "source.coffee keyword.control.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1249,12 +1249,12 @@ "c": "in", "t": "source.coffee keyword.control.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1599,12 +1599,12 @@ "c": "for", "t": "source.coffee keyword.control.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1627,12 +1627,12 @@ "c": "in", "t": "source.coffee keyword.control.coffee", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json index 652cc4824eb..7c95c4badca 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json @@ -31,12 +31,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -45,12 +45,12 @@ "c": "include", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -115,12 +115,12 @@ "c": "using", "t": "source.cpp meta.using-namespace.cpp keyword.other.using.directive.cpp", "r": { - "dark_plus": "keyword.other.using: #C586C0", + "dark_plus": "keyword.other.using: #CE92A4", "light_plus": "keyword.other.using: #AF00DB", "dark_vs": "keyword: #569CD6", "light_vs": "keyword: #0000FF", "hc_black": "keyword.other.using: #C586C0", - "dark_modern": "keyword.other.using: #C586C0", + "dark_modern": "keyword.other.using: #CE92A4", "hc_light": "keyword.other.using: #B5200D", "light_modern": "keyword.other.using: #AF00DB" } @@ -199,12 +199,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -213,12 +213,12 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -801,12 +801,12 @@ "c": "return", "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.body.function.definition.cpp keyword.control.return.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1347,12 +1347,12 @@ "c": "operator", "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp keyword.other.operator.overload.cpp", "r": { - "dark_plus": "keyword.other.operator: #C586C0", + "dark_plus": "keyword.other.operator: #CE92A4", "light_plus": "keyword.other.operator: #AF00DB", "dark_vs": "keyword: #569CD6", "light_vs": "keyword: #0000FF", "hc_black": "keyword.other.operator: #C586C0", - "dark_modern": "keyword.other.operator: #C586C0", + "dark_modern": "keyword.other.operator: #CE92A4", "hc_light": "keyword.other.operator: #B5200D", "light_modern": "keyword.other.operator: #AF00DB" } @@ -1501,12 +1501,12 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1515,12 +1515,12 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2775,12 +2775,12 @@ "c": "if", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp keyword.control.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3027,12 +3027,12 @@ "c": "return", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp keyword.control.return.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json index 7f4bbb7758b..293dfcbe2e7 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json @@ -3,12 +3,12 @@ "c": "using", "t": "source.cs keyword.other.directive.using.cs", "r": { - "dark_plus": "keyword.other.directive.using: #C586C0", + "dark_plus": "keyword.other.directive.using: #CE92A4", "light_plus": "keyword.other.directive.using: #AF00DB", "dark_vs": "keyword: #569CD6", "light_vs": "keyword: #0000FF", "hc_black": "keyword.other.directive.using: #C586C0", - "dark_modern": "keyword.other.directive.using: #C586C0", + "dark_modern": "keyword.other.directive.using: #CE92A4", "hc_light": "keyword.other.directive.using: #B5200D", "light_modern": "keyword.other.directive.using: #AF00DB" } @@ -423,12 +423,12 @@ "c": "if", "t": "source.cs keyword.control.conditional.if.cs", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1487,12 +1487,12 @@ "c": "foreach", "t": "source.cs keyword.control.loop.foreach.cs", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1585,12 +1585,12 @@ "c": "in", "t": "source.cs keyword.control.loop.in.cs", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json index 4307f03ed1e..fbfec42db62 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json @@ -3,12 +3,12 @@ "c": "@", "t": "text.html.cshtml meta.structure.razor.codeblock keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "{", "t": "text.html.cshtml meta.structure.razor.codeblock keyword.control.razor.directive.codeblock.open", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -311,12 +311,12 @@ "c": "@", "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.comment.razor keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -325,12 +325,12 @@ "c": "*", "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.comment.razor keyword.control.razor.comment.star", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -367,12 +367,12 @@ "c": "*", "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.comment.razor keyword.control.razor.comment.star", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -381,12 +381,12 @@ "c": "@", "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.comment.razor keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -409,12 +409,12 @@ "c": "if", "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor keyword.control.conditional.if.cs", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1473,12 +1473,12 @@ "c": "}", "t": "text.html.cshtml meta.structure.razor.codeblock keyword.control.razor.directive.codeblock.close", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3993,12 +3993,12 @@ "c": "@", "t": "text.html.cshtml meta.comment.razor keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4007,12 +4007,12 @@ "c": "*", "t": "text.html.cshtml meta.comment.razor keyword.control.razor.comment.star", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4049,12 +4049,12 @@ "c": "*", "t": "text.html.cshtml meta.comment.razor keyword.control.razor.comment.star", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4063,12 +4063,12 @@ "c": "@", "t": "text.html.cshtml meta.comment.razor keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4133,12 +4133,12 @@ "c": "@", "t": "text.html.cshtml meta.expression.implicit.cshtml keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4259,12 +4259,12 @@ "c": "@", "t": "text.html.cshtml meta.expression.explicit.cshtml keyword.control.cshtml.transition", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4273,12 +4273,12 @@ "c": "(", "t": "text.html.cshtml meta.expression.explicit.cshtml keyword.control.cshtml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4357,12 +4357,12 @@ "c": ")", "t": "text.html.cshtml meta.expression.explicit.cshtml keyword.control.cshtml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_css.json b/extensions/vscode-colorize-tests/test/colorize-results/test_css.json index 71722fd00db..4bb1be19ba2 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_css.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_css.json @@ -297,12 +297,12 @@ "c": "@", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css punctuation.definition.keyword.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -311,12 +311,12 @@ "c": "import", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -395,12 +395,12 @@ "c": "@", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css punctuation.definition.keyword.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -409,12 +409,12 @@ "c": "import", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -535,12 +535,12 @@ "c": "@", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css punctuation.definition.keyword.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -549,12 +549,12 @@ "c": "import", "t": "source.css meta.at-rule.import.css keyword.control.at-rule.import.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4917,12 +4917,12 @@ "c": "@", "t": "source.css meta.at-rule.header.css keyword.control.at-rule.css punctuation.definition.keyword.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4931,12 +4931,12 @@ "c": "property", "t": "source.css meta.at-rule.header.css keyword.control.at-rule.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json index e926933337e..075453241e2 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json @@ -3,12 +3,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "include", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -87,12 +87,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -101,12 +101,12 @@ "c": "include", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -171,12 +171,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -185,12 +185,12 @@ "c": "include", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -255,12 +255,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -269,12 +269,12 @@ "c": "include", "t": "source.cuda-cpp meta.preprocessor.include.cuda-cpp keyword.control.directive.include.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -339,12 +339,12 @@ "c": "#", "t": "source.cuda-cpp keyword.control.directive.conditional.if.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -353,12 +353,12 @@ "c": "if", "t": "source.cuda-cpp keyword.control.directive.conditional.if.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -381,12 +381,12 @@ "c": "defined", "t": "source.cuda-cpp meta.preprocessor.conditional.cuda-cpp keyword.control.directive.conditional.defined.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -437,12 +437,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp keyword.control.directive.undef.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -451,12 +451,12 @@ "c": "undef", "t": "source.cuda-cpp meta.preprocessor.undef.cuda-cpp keyword.control.directive.undef.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -493,12 +493,12 @@ "c": "#", "t": "source.cuda-cpp keyword.control.directive.endif.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -507,12 +507,12 @@ "c": "endif", "t": "source.cuda-cpp keyword.control.directive.endif.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -521,12 +521,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -535,12 +535,12 @@ "c": "define", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -661,12 +661,12 @@ "c": "do", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.do.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "if", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp keyword.control.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1571,12 +1571,12 @@ "c": "while", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.while.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1627,12 +1627,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1641,12 +1641,12 @@ "c": "define", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1767,12 +1767,12 @@ "c": "do", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.do.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1963,12 +1963,12 @@ "c": "if", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp keyword.control.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2873,12 +2873,12 @@ "c": "while", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.while.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2929,12 +2929,12 @@ "c": "#", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp punctuation.definition.directive.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2943,12 +2943,12 @@ "c": "define", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp keyword.control.directive.define.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5799,12 +5799,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6345,12 +6345,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8221,12 +8221,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8991,12 +8991,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -9663,12 +9663,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13891,12 +13891,12 @@ "c": "for", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp keyword.control.for.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -14157,12 +14157,12 @@ "c": "if", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp keyword.control.if.cuda-cpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_dart.json b/extensions/vscode-colorize-tests/test/colorize-results/test_dart.json index dc43f74e3c8..5f2fa73661c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_dart.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_dart.json @@ -129,12 +129,12 @@ "c": "async", "t": "source.dart keyword.control.dart", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json index d6b2ef38ebb..7473403342c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json @@ -45,12 +45,12 @@ "c": "import", "t": "source.go keyword.control.import.go", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -983,12 +983,12 @@ "c": "if", "t": "source.go keyword.control.go", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_groovy.json b/extensions/vscode-colorize-tests/test/colorize-results/test_groovy.json index 2fb630ed130..5bda16b6a10 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_groovy.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_groovy.json @@ -325,12 +325,12 @@ "c": "new", "t": "source.groovy keyword.control.new.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3153,12 +3153,12 @@ "c": "new", "t": "source.groovy meta.method-call.groovy keyword.control.new.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4665,12 +4665,12 @@ "c": "assert", "t": "source.groovy meta.declaration.assertion.groovy keyword.control.assert.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5827,12 +5827,12 @@ "c": "if", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5995,12 +5995,12 @@ "c": "else", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6023,12 +6023,12 @@ "c": "if", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6191,12 +6191,12 @@ "c": "else", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6737,12 +6737,12 @@ "c": "assert", "t": "source.groovy meta.declaration.assertion.groovy keyword.control.assert.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7409,12 +7409,12 @@ "c": "for", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7703,12 +7703,12 @@ "c": "for", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8207,12 +8207,12 @@ "c": "for", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8879,12 +8879,12 @@ "c": "for", "t": "source.groovy keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12617,12 +12617,12 @@ "c": "return", "t": "source.groovy meta.definition.method.groovy meta.method.body.java keyword.control.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13107,12 +13107,12 @@ "c": "assert", "t": "source.groovy meta.declaration.assertion.groovy keyword.control.assert.groovy", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_handlebars.json b/extensions/vscode-colorize-tests/test/colorize-results/test_handlebars.json index 2ee4ccd1f3b..82ca1b1508d 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_handlebars.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_handlebars.json @@ -297,12 +297,12 @@ "c": "#if", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -577,12 +577,12 @@ "c": "else", "t": "text.html.handlebars meta.function.inline.else.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "/if", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -885,12 +885,12 @@ "c": "#unless", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1151,12 +1151,12 @@ "c": "/unless", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1389,12 +1389,12 @@ "c": "#each", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1613,12 +1613,12 @@ "c": "/each", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1991,12 +1991,12 @@ "c": "#each", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2621,12 +2621,12 @@ "c": "/each", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_hbs.json b/extensions/vscode-colorize-tests/test/colorize-results/test_hbs.json index b79247facaa..dd42bf5eb97 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_hbs.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_hbs.json @@ -255,12 +255,12 @@ "c": "#each", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -885,12 +885,12 @@ "c": "/each", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1389,12 +1389,12 @@ "c": "#if", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1669,12 +1669,12 @@ "c": "/if", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2173,12 +2173,12 @@ "c": "#each", "t": "text.html.handlebars meta.function.block.start.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2425,12 +2425,12 @@ "c": "/each", "t": "text.html.handlebars meta.function.block.end.handlebars support.constant.handlebars keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_hlsl.json b/extensions/vscode-colorize-tests/test/colorize-results/test_hlsl.json index 5325987d5da..481bb17d78a 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_hlsl.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_hlsl.json @@ -311,12 +311,12 @@ "c": "return", "t": "source.hlsl keyword.control.hlsl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json b/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json index deb5a66740a..8d8bb3c3dc0 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_jl.json @@ -143,12 +143,12 @@ "c": "end", "t": "source.julia keyword.control.end.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2103,12 +2103,12 @@ "c": "return", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2145,12 +2145,12 @@ "c": "for", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2257,12 +2257,12 @@ "c": "for", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2495,12 +2495,12 @@ "c": "if", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3083,12 +3083,12 @@ "c": "return", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3125,12 +3125,12 @@ "c": "end", "t": "source.julia keyword.control.end.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3153,12 +3153,12 @@ "c": "end", "t": "source.julia keyword.control.end.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3181,12 +3181,12 @@ "c": "end", "t": "source.julia keyword.control.end.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3209,12 +3209,12 @@ "c": "return", "t": "source.julia keyword.control.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3251,12 +3251,12 @@ "c": "end", "t": "source.julia keyword.control.end.julia", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_js.json b/extensions/vscode-colorize-tests/test/colorize-results/test_js.json index c4bb55a1e22..5a3e62e3ac9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_js.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_js.json @@ -1669,12 +1669,12 @@ "c": "return", "t": "source.js meta.function.expression.js meta.block.js keyword.control.flow.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2887,12 +2887,12 @@ "c": "return", "t": "source.js meta.function.expression.js meta.block.js keyword.control.flow.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4119,12 +4119,12 @@ "c": "for", "t": "source.js meta.function.js meta.block.js keyword.control.loop.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4847,12 +4847,12 @@ "c": "return", "t": "source.js meta.function.js meta.block.js keyword.control.flow.js", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_jsx.json b/extensions/vscode-colorize-tests/test/colorize-results/test_jsx.json index ed8845d9816..c1afc2165a9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_jsx.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_jsx.json @@ -311,12 +311,12 @@ "c": "return", "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.flow.js.jsx", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1585,12 +1585,12 @@ "c": "if", "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.conditional.js.jsx", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1949,12 +1949,12 @@ "c": "return", "t": "source.js.jsx meta.var.expr.js.jsx meta.objectliteral.js.jsx meta.object.member.js.jsx meta.function.expression.js.jsx meta.block.js.jsx keyword.control.flow.js.jsx", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_less.json b/extensions/vscode-colorize-tests/test/colorize-results/test_less.json index a66224dd9e6..073c538de82 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_less.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_less.json @@ -3,12 +3,12 @@ "c": "@", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "import", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -101,12 +101,12 @@ "c": "@", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -115,12 +115,12 @@ "c": "import", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -241,12 +241,12 @@ "c": "@", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -255,12 +255,12 @@ "c": "import", "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -703,12 +703,12 @@ "c": "when", "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less keyword.control.conditional.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1221,12 +1221,12 @@ "c": "when", "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less keyword.control.conditional.less", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_lua.json b/extensions/vscode-colorize-tests/test/colorize-results/test_lua.json index c7a93a0446f..e56e3ebedc2 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_lua.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_lua.json @@ -59,12 +59,12 @@ "c": "function", "t": "source.lua meta.function.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -171,12 +171,12 @@ "c": "if", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -283,12 +283,12 @@ "c": "then", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -311,12 +311,12 @@ "c": "return", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -367,12 +367,12 @@ "c": "else", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -395,12 +395,12 @@ "c": "return", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -577,12 +577,12 @@ "c": "end", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -605,12 +605,12 @@ "c": "end", "t": "source.lua keyword.control.lua", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_m.json b/extensions/vscode-colorize-tests/test/colorize-results/test_m.json index c6c7e2d6278..0c0e4587fd6 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_m.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_m.json @@ -59,12 +59,12 @@ "c": "#", "t": "source.objc meta.preprocessor.include.objc keyword.control.directive.import.objc punctuation.definition.directive.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -73,12 +73,12 @@ "c": "import", "t": "source.objc meta.preprocessor.include.objc keyword.control.directive.import.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -143,12 +143,12 @@ "c": "#", "t": "source.objc meta.preprocessor.include.objc keyword.control.directive.import.objc punctuation.definition.directive.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -157,12 +157,12 @@ "c": "import", "t": "source.objc meta.preprocessor.include.objc keyword.control.directive.import.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1809,12 +1809,12 @@ "c": "if", "t": "source.objc meta.implementation.objc meta.scope.implementation.objc meta.function-with-body.objc meta.block.objc meta.bracketed.objc meta.function-call.objc meta.block.objc keyword.control.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2159,12 +2159,12 @@ "c": "return", "t": "source.objc meta.implementation.objc meta.scope.implementation.objc meta.function-with-body.objc meta.block.objc keyword.control.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4021,12 +4021,12 @@ "c": "return", "t": "source.objc meta.implementation.objc meta.scope.implementation.objc meta.function-with-body.objc meta.block.objc keyword.control.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4077,12 +4077,12 @@ "c": "return", "t": "source.objc meta.implementation.objc meta.scope.implementation.objc meta.function-with-body.objc meta.block.objc keyword.control.objc", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_mm.json b/extensions/vscode-colorize-tests/test/colorize-results/test_mm.json index eeb16d5b737..da24fb8688d 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_mm.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_mm.json @@ -59,12 +59,12 @@ "c": "#", "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp punctuation.definition.directive.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -73,12 +73,12 @@ "c": "import", "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -143,12 +143,12 @@ "c": "#", "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp punctuation.definition.directive.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -157,12 +157,12 @@ "c": "import", "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1697,12 +1697,12 @@ "c": "if", "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp keyword.control.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2075,12 +2075,12 @@ "c": "return", "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3811,12 +3811,12 @@ "c": "return", "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3867,12 +3867,12 @@ "c": "return", "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_php.json b/extensions/vscode-colorize-tests/test/colorize-results/test_php.json index ea79b0e2006..200544164a5 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_php.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_php.json @@ -1347,12 +1347,12 @@ "c": "for", "t": "text.html.php meta.embedded.block.php source.php keyword.control.for.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2075,12 +2075,12 @@ "c": "if", "t": "text.html.php meta.embedded.block.php source.php keyword.control.if.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2411,12 +2411,12 @@ "c": "else", "t": "text.html.php meta.embedded.block.php source.php keyword.control.else.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3419,12 +3419,12 @@ "c": "for", "t": "text.html.php meta.embedded.block.php source.php keyword.control.for.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3783,12 +3783,12 @@ "c": "if", "t": "text.html.php meta.embedded.block.php source.php keyword.control.if.php", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_pl.json b/extensions/vscode-colorize-tests/test/colorize-results/test_pl.json index 1a80edc135c..b10e49c8246 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_pl.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_pl.json @@ -3,12 +3,12 @@ "c": "use", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -395,12 +395,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -689,12 +689,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1613,12 +1613,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1907,12 +1907,12 @@ "c": "return", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2061,12 +2061,12 @@ "c": "while", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2523,12 +2523,12 @@ "c": "while", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2775,12 +2775,12 @@ "c": "if", "t": "source.perl keyword.control.perl", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json b/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json index 7fee15950e9..b919dfdabda 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_ps1.json @@ -143,12 +143,12 @@ "c": "try", "t": "source.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -535,12 +535,12 @@ "c": "return", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "catch", "t": "source.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -801,12 +801,12 @@ "c": "throw", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1081,12 +1081,12 @@ "c": "param", "t": "source.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1361,12 +1361,12 @@ "c": "foreach", "t": "source.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1431,12 +1431,12 @@ "c": "in", "t": "source.powershell meta.scriptblock.powershell meta.group.simple.subexpression.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1613,12 +1613,12 @@ "c": "if", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2285,12 +2285,12 @@ "c": "if", "t": "source.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2817,12 +2817,12 @@ "c": "if", "t": "source.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3167,12 +3167,12 @@ "c": "else", "t": "source.powershell keyword.control.powershell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_py.json b/extensions/vscode-colorize-tests/test/colorize-results/test_py.json index e8d718cad72..28d7010a42b 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_py.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_py.json @@ -3,12 +3,12 @@ "c": "from", "t": "source.python keyword.control.import.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -31,12 +31,12 @@ "c": "import", "t": "source.python keyword.control.import.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -759,12 +759,12 @@ "c": "return", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1109,12 +1109,12 @@ "c": "for", "t": "source.python meta.function.python meta.function.parameters.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1165,12 +1165,12 @@ "c": "in", "t": "source.python meta.function.python meta.function.parameters.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1221,12 +1221,12 @@ "c": "if", "t": "source.python meta.function.python meta.function.parameters.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1333,12 +1333,12 @@ "c": "else", "t": "source.python meta.function.python meta.function.parameters.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1501,12 +1501,12 @@ "c": "pass", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1515,12 +1515,12 @@ "c": "pass", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1683,12 +1683,12 @@ "c": "for", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1711,12 +1711,12 @@ "c": "in", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1823,12 +1823,12 @@ "c": "yield", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3237,12 +3237,12 @@ "c": "if", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3335,12 +3335,12 @@ "c": "return", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3391,12 +3391,12 @@ "c": "elif", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3629,12 +3629,12 @@ "c": "return", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3825,12 +3825,12 @@ "c": "else", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3867,12 +3867,12 @@ "c": "return", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5127,12 +5127,12 @@ "c": "if", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5519,12 +5519,12 @@ "c": "return", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6093,12 +6093,12 @@ "c": "while", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6163,12 +6163,12 @@ "c": "try", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6429,12 +6429,12 @@ "c": "break", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6457,12 +6457,12 @@ "c": "except", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6611,12 +6611,12 @@ "c": "async", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6639,12 +6639,12 @@ "c": "with", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6695,12 +6695,12 @@ "c": "as", "t": "source.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7479,12 +7479,12 @@ "c": ">>> ", "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7521,12 +7521,12 @@ "c": "... ", "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7563,12 +7563,12 @@ "c": "... ", "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_r.json b/extensions/vscode-colorize-tests/test/colorize-results/test_r.json index 4d56b651660..17fbf383edd 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_r.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_r.json @@ -451,12 +451,12 @@ "c": "function", "t": "source.r meta.function.r keyword.control.r", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_rb.json b/extensions/vscode-colorize-tests/test/colorize-results/test_rb.json index 032617573e4..b93bf251b67 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_rb.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_rb.json @@ -115,12 +115,12 @@ "c": "module", "t": "source.ruby meta.module.ruby keyword.control.module.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -297,12 +297,12 @@ "c": "class", "t": "source.ruby meta.class.ruby keyword.control.class.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1375,12 +1375,12 @@ "c": "def", "t": "source.ruby meta.function.method.with-arguments.ruby keyword.control.def.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1669,12 +1669,12 @@ "c": "super", "t": "source.ruby keyword.control.pseudo-method.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2047,12 +2047,12 @@ "c": "if", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2243,12 +2243,12 @@ "c": "unless", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3125,12 +3125,12 @@ "c": "if", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3433,12 +3433,12 @@ "c": "else", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3573,12 +3573,12 @@ "c": "end", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4091,12 +4091,12 @@ "c": "end", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4119,12 +4119,12 @@ "c": "end", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4133,12 +4133,12 @@ "c": "end", "t": "source.ruby keyword.control.ruby", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json b/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json index aa8468c4d8a..825becadb30 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json @@ -87,12 +87,12 @@ "c": "1. ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -115,12 +115,12 @@ "c": "2. ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -143,12 +143,12 @@ "c": " - ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -171,12 +171,12 @@ "c": " - ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -199,12 +199,12 @@ "c": "3. ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -269,12 +269,12 @@ "c": "::", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -479,12 +479,12 @@ "c": "| ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -507,12 +507,12 @@ "c": "| ", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -535,12 +535,12 @@ "c": "+-------------+--------------+", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -549,12 +549,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -577,12 +577,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -605,12 +605,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -619,12 +619,12 @@ "c": "+=============+==============+", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -633,12 +633,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -661,12 +661,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -689,12 +689,12 @@ "c": "|", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -703,12 +703,12 @@ "c": "+-------------+--------------+", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -717,12 +717,12 @@ "c": "============ ============", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -745,12 +745,12 @@ "c": "============ ============", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -773,12 +773,12 @@ "c": "============ ============", "t": "source.rst keyword.control.table", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -829,12 +829,12 @@ "c": ">>>", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1221,12 +1221,12 @@ "c": ".. image::", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1277,12 +1277,12 @@ "c": ":sub:", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1305,12 +1305,12 @@ "c": ":sup:", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1431,12 +1431,12 @@ "c": "..", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1487,12 +1487,12 @@ "c": "replace::", "t": "source.rst keyword.control", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json index 933ebf2ba5c..dfae042fd1b 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json @@ -115,12 +115,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.charset.scss keyword.control.at-rule.charset.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -129,12 +129,12 @@ "c": "charset", "t": "source.css.scss meta.at-rule.charset.scss keyword.control.at-rule.charset.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7059,12 +7059,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.function.scss keyword.control.at-rule.function.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7073,12 +7073,12 @@ "c": "function", "t": "source.css.scss meta.at-rule.function.scss keyword.control.at-rule.function.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7199,12 +7199,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7213,12 +7213,12 @@ "c": "return", "t": "source.css.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7787,12 +7787,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7801,12 +7801,12 @@ "c": "import", "t": "source.css.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8025,12 +8025,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8039,12 +8039,12 @@ "c": "import", "t": "source.css.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8333,12 +8333,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8347,12 +8347,12 @@ "c": "import", "t": "source.css.scss meta.property-list.scss meta.at-rule.import.scss keyword.control.at-rule.import.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8655,12 +8655,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.media.scss keyword.control.at-rule.media.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8669,12 +8669,12 @@ "c": "media", "t": "source.css.scss meta.property-list.scss meta.at-rule.media.scss keyword.control.at-rule.media.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -9425,12 +9425,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -9439,12 +9439,12 @@ "c": "extend", "t": "source.css.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10069,12 +10069,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10083,12 +10083,12 @@ "c": "extend", "t": "source.css.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10237,12 +10237,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.warn.scss keyword.control.warn.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10251,12 +10251,12 @@ "c": "debug", "t": "source.css.scss meta.at-rule.warn.scss keyword.control.warn.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10293,12 +10293,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10307,12 +10307,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10461,12 +10461,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10475,12 +10475,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10601,12 +10601,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.property-list.scss meta.at-rule.warn.scss keyword.control.warn.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10615,12 +10615,12 @@ "c": "warn", "t": "source.css.scss meta.property-list.scss meta.property-list.scss meta.at-rule.warn.scss keyword.control.warn.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10951,12 +10951,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10965,12 +10965,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -11091,12 +11091,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.property-list.scss meta.at-rule.warn.scss keyword.control.warn.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -11105,12 +11105,12 @@ "c": "warn", "t": "source.css.scss meta.property-list.scss meta.property-list.scss meta.at-rule.warn.scss keyword.control.warn.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -11833,12 +11833,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -11847,12 +11847,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12197,12 +12197,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12211,12 +12211,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12505,12 +12505,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12519,12 +12519,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12883,12 +12883,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12897,12 +12897,12 @@ "c": "if", "t": "source.css.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13121,12 +13121,12 @@ "c": "@", "t": "source.css.scss meta.property-list.scss meta.at-rule.else.scss keyword.control.else.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13135,12 +13135,12 @@ "c": "else ", "t": "source.css.scss meta.property-list.scss meta.at-rule.else.scss keyword.control.else.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13331,12 +13331,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.for.scss keyword.control.for.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13345,12 +13345,12 @@ "c": "for", "t": "source.css.scss meta.at-rule.for.scss keyword.control.for.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13401,12 +13401,12 @@ "c": "from", "t": "source.css.scss meta.at-rule.for.scss keyword.control.operator", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13457,12 +13457,12 @@ "c": "through", "t": "source.css.scss meta.at-rule.for.scss keyword.control.operator", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13877,12 +13877,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss keyword.control.each.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13891,12 +13891,12 @@ "c": "each", "t": "source.css.scss meta.at-rule.each.scss keyword.control.each.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -13947,12 +13947,12 @@ "c": "in", "t": "source.css.scss meta.at-rule.each.scss keyword.control.operator", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -14479,12 +14479,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss keyword.control.while.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -14493,12 +14493,12 @@ "c": "while", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss keyword.control.while.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15109,12 +15109,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.function.scss keyword.control.at-rule.function.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15123,12 +15123,12 @@ "c": "function", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.function.scss keyword.control.at-rule.function.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15291,12 +15291,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.for.scss keyword.control.for.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15305,12 +15305,12 @@ "c": "for", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.for.scss keyword.control.for.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15361,12 +15361,12 @@ "c": "from", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.for.scss keyword.control.operator", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15417,12 +15417,12 @@ "c": "to", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.for.scss keyword.control.operator", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15501,12 +15501,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -15515,12 +15515,12 @@ "c": "if", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16005,12 +16005,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16019,12 +16019,12 @@ "c": "return", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16173,12 +16173,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16187,12 +16187,12 @@ "c": "return", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.return.scss keyword.control.return.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16299,12 +16299,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16313,12 +16313,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16915,12 +16915,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -16929,12 +16929,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17139,12 +17139,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17153,12 +17153,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17755,12 +17755,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17769,12 +17769,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17937,12 +17937,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17951,12 +17951,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -18413,12 +18413,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -18427,12 +18427,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -18889,12 +18889,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -18903,12 +18903,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19561,12 +19561,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19575,12 +19575,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19743,12 +19743,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19757,12 +19757,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19925,12 +19925,12 @@ "c": "@content", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.content.scss keyword.control.content.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -19995,12 +19995,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20009,12 +20009,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20331,12 +20331,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.if.scss keyword.control.if.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20345,12 +20345,12 @@ "c": "if", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.if.scss keyword.control.if.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20429,12 +20429,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20443,12 +20443,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20919,12 +20919,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.page.scss keyword.control.at-rule.page.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -20933,12 +20933,12 @@ "c": "page", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.at-rule.page.scss keyword.control.at-rule.page.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -21787,12 +21787,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -21801,12 +21801,12 @@ "c": "extend", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22473,12 +22473,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22487,12 +22487,12 @@ "c": "mixin", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.mixin.scss keyword.control.at-rule.mixin.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22641,12 +22641,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22655,12 +22655,12 @@ "c": "extend", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22767,12 +22767,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -22781,12 +22781,12 @@ "c": "extend", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.extend.scss keyword.control.at-rule.extend.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -23243,12 +23243,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -23257,12 +23257,12 @@ "c": "include", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.property-list.scss meta.at-rule.include.scss keyword.control.at-rule.include.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -23439,12 +23439,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.fontface.scss keyword.control.at-rule.fontface.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -23453,12 +23453,12 @@ "c": "font-face", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.fontface.scss keyword.control.at-rule.fontface.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -24531,12 +24531,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -24545,12 +24545,12 @@ "c": "-webkit-keyframes", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -24937,12 +24937,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -24951,12 +24951,12 @@ "c": "-moz-keyframes", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -25721,12 +25721,12 @@ "c": "@", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss punctuation.definition.keyword.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -25735,12 +25735,12 @@ "c": "keyframes", "t": "source.css.scss meta.at-rule.each.scss meta.at-rule.while.scss meta.property-list.scss meta.at-rule.keyframes.scss keyword.control.at-rule.keyframes.scss", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json index 0dde2e0748e..fc06d9f4d9a 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_sh.json @@ -31,12 +31,12 @@ "c": "if", "t": "source.shell meta.scope.if-block.shell keyword.control.if.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -297,12 +297,12 @@ "c": "then", "t": "source.shell meta.scope.if-block.shell keyword.control.then.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1235,12 +1235,12 @@ "c": "else", "t": "source.shell meta.scope.if-block.shell keyword.control.else.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1529,12 +1529,12 @@ "c": "fi", "t": "source.shell meta.scope.if-block.shell keyword.control.fi.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2607,12 +2607,12 @@ "c": "if", "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell keyword.control.if.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2873,12 +2873,12 @@ "c": "then", "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell keyword.control.then.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3055,12 +3055,12 @@ "c": "else", "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell keyword.control.else.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3237,12 +3237,12 @@ "c": "fi", "t": "source.shell meta.function.shell meta.function.body.shell meta.scope.if-block.shell keyword.control.fi.shell", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3345,4 +3345,4 @@ "light_modern": "string: #A31515" } } -] +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_swift.json b/extensions/vscode-colorize-tests/test/colorize-results/test_swift.json index 8b1b64f71f5..c84a0185f31 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_swift.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_swift.json @@ -647,12 +647,12 @@ "c": "for", "t": "source.swift meta.definition.function.swift meta.definition.function.body.swift keyword.control.loop.swift", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -675,12 +675,12 @@ "c": "in", "t": "source.swift meta.definition.function.swift meta.definition.function.body.swift keyword.control.loop.swift", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -731,12 +731,12 @@ "c": "if", "t": "source.swift meta.definition.function.swift meta.definition.function.body.swift keyword.control.branch.swift", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -857,12 +857,12 @@ "c": "return", "t": "source.swift meta.definition.function.swift meta.definition.function.body.swift keyword.control.transfer.swift", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -969,12 +969,12 @@ "c": "return", "t": "source.swift meta.definition.function.swift meta.definition.function.body.swift keyword.control.transfer.swift", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json b/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json index 7cc786089fd..f2b21f30a78 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json @@ -3,12 +3,12 @@ "c": "\\", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -17,12 +17,12 @@ "c": "documentclass", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -115,12 +115,12 @@ "c": "\\", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -129,12 +129,12 @@ "c": "usepackage", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -185,12 +185,12 @@ "c": "\\", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -199,12 +199,12 @@ "c": "usepackage", "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -507,12 +507,12 @@ "c": "\\\\", "t": "text.tex.latex keyword.control.newline.tex", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_ts.json b/extensions/vscode-colorize-tests/test/colorize-results/test_ts.json index 68504d08d8e..c87cf0f039c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_ts.json @@ -171,12 +171,12 @@ "c": "export", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1347,12 +1347,12 @@ "c": "export", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts keyword.control.export.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4021,12 +4021,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4539,12 +4539,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.arrow.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5533,12 +5533,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.arrow.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6807,12 +6807,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7171,12 +7171,12 @@ "c": "else", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7199,12 +7199,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7451,12 +7451,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7857,12 +7857,12 @@ "c": "for", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8193,12 +8193,12 @@ "c": "for", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.block.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8543,12 +8543,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.block.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8781,12 +8781,12 @@ "c": "continue", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.block.ts meta.block.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8823,12 +8823,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.block.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -9327,12 +9327,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -9705,12 +9705,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10223,12 +10223,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10293,12 +10293,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -10769,12 +10769,12 @@ "c": "for", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -11259,12 +11259,12 @@ "c": "for", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts meta.block.ts keyword.control.loop.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12085,12 +12085,12 @@ "c": "return", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.flow.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -12365,12 +12365,12 @@ "c": "if", "t": "source.ts meta.namespace.declaration.ts meta.block.ts meta.class.ts meta.method.declaration.ts meta.block.ts keyword.control.conditional.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_vb.json b/extensions/vscode-colorize-tests/test/colorize-results/test_vb.json index 2d8b7a5fbf2..6706deb203d 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_vb.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_vb.json @@ -1137,12 +1137,12 @@ "c": "Do", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1165,12 +1165,12 @@ "c": "While", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1585,12 +1585,12 @@ "c": "If", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1795,12 +1795,12 @@ "c": "Then", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2369,12 +2369,12 @@ "c": "If", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2397,12 +2397,12 @@ "c": "Then", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2425,12 +2425,12 @@ "c": "Exit Sub", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2691,12 +2691,12 @@ "c": "End If", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2747,12 +2747,12 @@ "c": "Loop", "t": "source.asp.vb.net keyword.control.asp", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json b/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json index 0908e19e3ea..6ca362c7a18 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json @@ -115,12 +115,12 @@ "c": "&", "t": "source.yaml meta.stream.yaml meta.document.yaml meta.block.sequence.yaml meta.mapping.yaml meta.map.value.yaml keyword.control.flow.anchor.yaml punctuation.definition.anchor.yaml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -507,12 +507,12 @@ "c": "*", "t": "source.yaml meta.stream.yaml meta.document.yaml meta.block.sequence.yaml meta.mapping.yaml meta.map.value.yaml keyword.control.flow.alias.yaml punctuation.definition.alias.yaml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -647,12 +647,12 @@ "c": "*", "t": "source.yaml meta.stream.yaml meta.document.yaml meta.block.sequence.yaml meta.mapping.yaml meta.map.value.yaml keyword.control.flow.alias.yaml punctuation.definition.alias.yaml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -829,12 +829,12 @@ "c": "*", "t": "source.yaml meta.stream.yaml meta.document.yaml meta.block.sequence.yaml meta.mapping.yaml meta.map.value.yaml keyword.control.flow.alias.yaml punctuation.definition.alias.yaml", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-241001_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-241001_ts.json index a5a8c33bf22..2c4294c18a9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-241001_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-241001_ts.json @@ -577,12 +577,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2047,12 +2047,12 @@ "c": "import", "t": "new.expr.ts keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue11_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue11_ts.json index 5e8481b6289..fef5b2f06f4 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue11_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue11_ts.json @@ -73,12 +73,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -367,12 +367,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -591,12 +591,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -815,12 +815,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1039,12 +1039,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1179,12 +1179,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2299,12 +2299,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue241715_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue241715_ts.json index c7c13ff004c..879e9e26c2c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue241715_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue241715_ts.json @@ -409,12 +409,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -703,12 +703,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1137,12 +1137,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1459,12 +1459,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1529,12 +1529,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -1711,12 +1711,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2313,12 +2313,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2579,12 +2579,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5431_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5431_ts.json index 2a122f89471..067fee14bc1 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5431_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5431_ts.json @@ -409,12 +409,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5465_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5465_ts.json index 100bf5c3d07..1cfeec33c2e 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5465_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-issue5465_ts.json @@ -87,12 +87,12 @@ "c": "yield", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -157,12 +157,12 @@ "c": "yield", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-keywords_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-keywords_ts.json index 6753ae3fa4c..c6617c9f426 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-keywords_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test-keywords_ts.json @@ -3,12 +3,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json index 73b86795d8a..e7bf6aacd02 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json @@ -101,12 +101,12 @@ "c": "@import", "t": "keyword.control.at-rule.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -157,12 +157,12 @@ "c": "@import", "t": "keyword.control.at-rule.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -255,12 +255,12 @@ "c": "@import", "t": "keyword.control.at-rule.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3307,12 +3307,12 @@ "c": "@property", "t": "keyword.control.at-rule.css", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_ts.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_ts.json index b31e6daa154..d4c0361052c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_ts.json +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_ts.json @@ -59,12 +59,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -801,12 +801,12 @@ "c": "export", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2579,12 +2579,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -2943,12 +2943,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -3671,12 +3671,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4609,12 +4609,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4833,12 +4833,12 @@ "c": "else", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -4847,12 +4847,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5015,12 +5015,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5253,12 +5253,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5477,12 +5477,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5701,12 +5701,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5841,12 +5841,12 @@ "c": "continue", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -5869,12 +5869,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6233,12 +6233,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6457,12 +6457,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6765,12 +6765,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -6807,12 +6807,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7171,12 +7171,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -7493,12 +7493,12 @@ "c": "for", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8109,12 +8109,12 @@ "c": "return", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } @@ -8277,12 +8277,12 @@ "c": "if", "t": "keyword.control.ts", "r": { - "dark_plus": "keyword.control: #C586C0", + "dark_plus": "keyword.control: #CE92A4", "light_plus": "keyword.control: #AF00DB", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #CE92A4", "hc_light": "keyword.control: #B5200D", "light_modern": "keyword.control: #AF00DB" } From f8ad8ecb29a024a82a4a9163e8a6b83ab325ae65 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 9 Oct 2025 13:01:23 +0100 Subject: [PATCH 1017/4355] Add high contrast theme support for settings editor links and buttons --- .../preferences/browser/media/settingsEditor2.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 241b4758fe6..7179f57eefc 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -588,6 +588,16 @@ color: var(--vscode-textLink-activeForeground); } +/* High contrast theme support - ensure proper color visibility */ +.monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, +.monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button, +.monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a > code, +.monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, +.monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button, +.monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a > code { + color: var(--vscode-textPreformat-foreground); +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:hover, .settings-editor > .settings-body .settings-tree-container .edit-in-settings-button:hover { cursor: pointer; From 5859cf7661ea1265eeebaf75addd7e6da6e560d8 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 9 Oct 2025 13:06:50 +0100 Subject: [PATCH 1018/4355] Isolate high contrast theme styles for only settings editor links --- .../contrib/preferences/browser/media/settingsEditor2.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 7179f57eefc..ed0beac4ec2 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -589,11 +589,7 @@ } /* High contrast theme support - ensure proper color visibility */ -.monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, -.monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button, .monaco-workbench.hc-light .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a > code, -.monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, -.monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button, .monaco-workbench.hc-black .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a > code { color: var(--vscode-textPreformat-foreground); } From 33b1d77b034ea548b2b43101c3b81782fecfab3e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Oct 2025 17:26:30 +0200 Subject: [PATCH 1019/4355] remove any type (#270549) * remove any type * remove any type --- .github/prompts/no-any.prompt.md | 4 +- eslint.config.js | 4 +- .../contrib/mcp/browser/mcpMigration.ts | 2 +- .../contrib/mcp/browser/mcpServerActions.ts | 28 ++++----- .../contrib/mcp/browser/mcpServerEditor.ts | 8 +-- .../mcpLanguageModelToolContribution.ts | 2 +- .../contrib/mcp/common/mcpSamplingLog.ts | 2 +- .../contrib/mcp/common/uriTemplate.ts | 6 +- .../mcp/test/common/mcpRegistry.test.ts | 58 ++++++++++-------- .../mcp/test/common/mcpRegistryTypes.ts | 2 +- .../test/common/mcpResourceFilesystem.test.ts | 61 +++++++++++-------- .../mcp/test/common/mcpSamplingLog.test.ts | 33 ++++------ .../common/mcpServerRequestHandler.test.ts | 6 +- .../mcp/test/common/uriTemplate.test.ts | 2 +- 14 files changed, 113 insertions(+), 105 deletions(-) diff --git a/.github/prompts/no-any.prompt.md b/.github/prompts/no-any.prompt.md index 8abaee77660..f5a4b595e5c 100644 --- a/.github/prompts/no-any.prompt.md +++ b/.github/prompts/no-any.prompt.md @@ -7,4 +7,6 @@ I am trying to minimize the usage of `any` types in our TypeScript codebase. Find usages of the TypeScript `any` type in this file and replace it with the right type based on usages in the file. You are NOT allowed to disable ESLint rules or add `// @ts-ignore` comments to the code. -You are NOT allowed to add more `any` types to the code. +You are NOT allowed to add more `any` types to the code even if you think it is necessary or they are legitimate. + +If there are tests associated to the changes you made, please run those tests to ensure everything is working correctly diff --git a/eslint.config.js b/eslint.config.js index c9663165783..6c65c69287b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -269,7 +269,7 @@ export default tseslint.config( 'src/vs/workbench/services/layout/**', 'src/vs/workbench/services/lifecycle/**', // 'src/vs/workbench/services/log/**', - // 'src/vs/workbench/services/mcp/**', + 'src/vs/workbench/services/mcp/**', 'src/vs/workbench/services/notification/**', // 'src/vs/workbench/services/output/**', 'src/vs/workbench/services/path/**', @@ -293,7 +293,7 @@ export default tseslint.config( 'src/vs/workbench/contrib/files/**', 'src/vs/workbench/contrib/chat/browser/chatSetup.ts', 'src/vs/workbench/contrib/chat/browser/chatStatus.ts', - // 'src/vs/workbench/contrib/mcp/**', + 'src/vs/workbench/contrib/mcp/**', ], ignores: ['**/*.test.ts', '**/*.integrationTest.ts'], languageOptions: { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpMigration.ts b/src/vs/workbench/contrib/mcp/browser/mcpMigration.ts index 3c0166e9594..8937abd98b5 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpMigration.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpMigration.ts @@ -125,7 +125,7 @@ export class McpConfigMigrationContribution extends Disposable implements IWorkb private async parseMcpConfig(settingsFile: URI): Promise { try { const content = await this.fileService.readFile(settingsFile); - const settingsObject: IStringDictionary = parse(content.value.toString()); + const settingsObject: IStringDictionary = parse(content.value.toString()); if (!isObject(settingsObject)) { return undefined; } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts index fa8283fee7d..85454c91530 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts @@ -62,7 +62,7 @@ export abstract class DropDownAction extends McpServerAction { return this._actionViewItem; } - public override run(actionGroups: IAction[][]): Promise { + public override run(actionGroups: IAction[][]): Promise { this._actionViewItem?.showMenu(actionGroups); return Promise.resolve(); } @@ -129,7 +129,7 @@ export class InstallAction extends McpServerAction { this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true; } - override async run(): Promise { + override async run(): Promise { if (!this.mcpServer) { return; } @@ -201,7 +201,7 @@ export class UninstallAction extends McpServerAction { this.label = localize('uninstall', "Uninstall"); } - override async run(): Promise { + override async run(): Promise { if (!this.mcpServer) { return; } @@ -264,7 +264,7 @@ export class ManageMcpServerAction extends DropDownAction { return groups; } - override async run(): Promise { + override async run(): Promise { return super.run(await this.getActionGroups()); } @@ -306,7 +306,7 @@ export class StartServerAction extends McpServerAction { this.label = localize('start', "Start Server"); } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -354,7 +354,7 @@ export class StopServerAction extends McpServerAction { this.label = localize('stop', "Stop Server"); } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -401,7 +401,7 @@ export class RestartServerAction extends McpServerAction { this.label = localize('restart', "Restart Server"); } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -543,7 +543,7 @@ export class ShowServerOutputAction extends McpServerAction { this.label = localize('output', "Show Output"); } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -584,7 +584,7 @@ export class ShowServerConfigurationAction extends McpServerAction { this.enabled = true; } - override async run(): Promise { + override async run(): Promise { if (!this.mcpServer?.local) { return; } @@ -618,7 +618,7 @@ export class ShowServerJsonConfigurationAction extends McpServerAction { this.enabled = true; } - override async run(): Promise { + override async run(): Promise { const configurationTarget = this.getConfigurationTarget(); if (!configurationTarget) { return; @@ -671,7 +671,7 @@ export class ConfigureModelAccessAction extends McpServerAction { this.label = localize('mcp.configAccess', 'Configure Model Access'); } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -718,7 +718,7 @@ export class ShowSamplingRequestsAction extends McpServerAction { this.enabled = true; } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -772,7 +772,7 @@ export class BrowseResourcesAction extends McpServerAction { this.enabled = true; } - override async run(): Promise { + override async run(): Promise { const server = this.getServer(); if (!server) { return; @@ -894,7 +894,7 @@ export class McpServerStatusAction extends McpServerAction { this._onDidChangeStatus.fire(); } - override async run(): Promise { + override async run(): Promise { if (this._status[0]?.icon === trustIcon) { return this.commandService.executeCommand('workbench.trust.manage'); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index e8c1abea319..0a582c2e636 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -40,7 +40,7 @@ import { IMcpServerContainer, IMcpServerEditorOptions, IMcpWorkbenchService, IWo import { StarredWidget, McpServerIconWidget, McpServerStatusWidget, McpServerWidget, onClick, PublisherWidget, McpServerScopeBadgeWidget, LicenseWidget } from './mcpServerWidgets.js'; import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction, UninstallAction } from './mcpServerActions.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; -import { ILocalMcpServer, IGalleryMcpServerConfiguration, IMcpServerPackage, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js'; +import { ILocalMcpServer, IGalleryMcpServerConfiguration, IMcpServerPackage, IMcpServerKeyValueInput, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { McpServerType } from '../../../../platform/mcp/common/mcpPlatformTypes.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -785,7 +785,7 @@ export class McpServerEditor extends EditorPane { append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('runtimeargs', "Runtime Arguments:")), $('code.detail-value', undefined, argStrings.join(' ')))); } if (pkg.environmentVariables && pkg.environmentVariables.length > 0) { - const envStrings = pkg.environmentVariables.map((envVar: any) => `${envVar.name}=${envVar.value}`); + const envStrings = pkg.environmentVariables.map((envVar: IMcpServerKeyValueInput) => `${envVar.name}=${envVar.value ?? ''}`); append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('environmentVariables', "Environment Variables:")), $('code.detail-value', undefined, envStrings.join(' ')))); } if (i < packages.length - 1) { @@ -804,7 +804,7 @@ export class McpServerEditor extends EditorPane { append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('transport', "Transport:")), $('.detail-value', undefined, remote.type))); } if (remote.headers && remote.headers.length > 0) { - const headerStrings = remote.headers.map((header: any) => `${header.name}: ${header.value}`); + const headerStrings = remote.headers.map((header: IMcpServerKeyValueInput) => `${header.name}: ${header.value ?? ''}`); append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('headers', "Headers:")), $('.detail-value', undefined, headerStrings.join(', ')))); } } @@ -840,7 +840,7 @@ export class McpServerEditor extends EditorPane { this.layoutParticipants.forEach(p => p.layout()); } - private onError(err: any): void { + private onError(err: Error): void { if (isCancellationError(err)) { return; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index face26194f1..7ecad35937a 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -222,7 +222,7 @@ class McpToolImplementation implements IToolImpl { content: [] }; - const callResult = await this._tool.callWithProgress(invocation.parameters as Record, progress, { chatRequestId: invocation.chatRequestId, chatSessionId: invocation.context?.sessionId }, token); + const callResult = await this._tool.callWithProgress(invocation.parameters as Record, progress, { chatRequestId: invocation.chatRequestId, chatSessionId: invocation.context?.sessionId }, token); const details: IToolResultInputOutputDetails = { input: JSON.stringify(invocation.parameters, undefined, 2), output: [], diff --git a/src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts b/src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts index 04ea2af681d..b62c531719b 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts @@ -17,7 +17,7 @@ const enum Constants { SamplingLastNMessage = 30, } -interface ISamplingStoredData { +export interface ISamplingStoredData { // UTC day ordinal of the first bin in the bins head: number; // Requests per day, max length of `Constants.SamplingRetentionDays` diff --git a/src/vs/workbench/contrib/mcp/common/uriTemplate.ts b/src/vs/workbench/contrib/mcp/common/uriTemplate.ts index ac2b47059dd..4734dc0d9f1 100644 --- a/src/vs/workbench/contrib/mcp/common/uriTemplate.ts +++ b/src/vs/workbench/contrib/mcp/common/uriTemplate.ts @@ -156,8 +156,7 @@ export class UriTemplate { const pairs: string[] = []; for (const k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { - // eslint-disable-next-line local/code-no-any-casts - const thisVal = String((value as any)[k]); + const thisVal = String((value as Record)[k]); if (isParam) { pairs.push(k + '=' + thisVal); } else if (isForm || isFormCont) { @@ -188,8 +187,7 @@ export class UriTemplate { for (const k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { pairs.push(k); - // eslint-disable-next-line local/code-no-any-casts - pairs.push(String((value as any)[k])); + pairs.push(String((value as Record)[k])); } } // For label, param, form, join as keys=semi,;,dot,.,comma,, (no encoding of , or ;) diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts index cd1e2b475a7..14d419314c2 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts @@ -9,9 +9,9 @@ import { timeout } from '../../../../../base/common/async.js'; import { ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; import { upcast } from '../../../../../base/common/types.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { ConfigurationTarget, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { IDialogService, IPrompt } from '../../../../../platform/dialogs/common/dialogs.js'; import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { ILogger, ILoggerService, ILogService, NullLogger, NullLogService } from '../../../../../platform/log/common/log.js'; @@ -20,8 +20,9 @@ import { IProductService } from '../../../../../platform/product/common/productS import { ISecretStorageService } from '../../../../../platform/secrets/common/secrets.js'; import { TestSecretStorageService } from '../../../../../platform/secrets/test/common/testSecretStorageService.js'; import { IStorageService, StorageScope } from '../../../../../platform/storage/common/storage.js'; +import { IWorkspaceFolderData } from '../../../../../platform/workspace/common/workspace.js'; import { IConfigurationResolverService } from '../../../../services/configurationResolver/common/configurationResolver.js'; -import { ConfigurationResolverExpression } from '../../../../services/configurationResolver/common/configurationResolverExpression.js'; +import { ConfigurationResolverExpression, Replacement } from '../../../../services/configurationResolver/common/configurationResolverExpression.js'; import { IOutputService } from '../../../../services/output/common/output.js'; import { TestLoggerService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { McpRegistry } from '../../common/mcpRegistry.js'; @@ -30,7 +31,7 @@ import { McpServerConnection } from '../../common/mcpServerConnection.js'; import { LazyCollectionState, McpCollectionDefinition, McpServerDefinition, McpServerLaunch, McpServerTransportStdio, McpServerTransportType, McpServerTrust, McpStartServerInteraction } from '../../common/mcpTypes.js'; import { TestMcpMessageTransport } from './mcpRegistryTypes.js'; -class TestConfigurationResolverService implements Partial { +class TestConfigurationResolverService { declare readonly _serviceBrand: undefined; private interactiveCounter = 0; @@ -44,7 +45,7 @@ class TestConfigurationResolverService implements Partial { + resolveAsync(folder: IWorkspaceFolderData | undefined, value: T): Promise { const parsed = ConfigurationResolverExpression.parse(value); for (const variable of parsed.unresolved()) { const resolved = this.resolvedVariables.get(variable.inner); @@ -56,7 +57,7 @@ class TestConfigurationResolverService implements Partial, target?: ConfigurationTarget): Promise | undefined> { + resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: unknown, section?: string, variables?: Record, target?: ConfigurationTarget): Promise | undefined> { const parsed = ConfigurationResolverExpression.parse(config); // For testing, we simulate interaction by returning a map with some variables const result = new Map(); @@ -65,8 +66,13 @@ class TestConfigurationResolverService implements Partial { +class TestDialogService { declare readonly _serviceBrand: undefined; - private _promptResult: boolean | undefined; + private _promptResult: boolean | undefined = true; private _promptSpy: sinon.SinonStub; constructor() { @@ -114,7 +120,7 @@ class TestDialogService implements Partial { return this._promptSpy; } - prompt(options: any): Promise { + prompt(options: IPrompt): Promise<{ result?: T }> { return this._promptSpy(options); } } @@ -209,14 +215,20 @@ suite('Workbench - MCP - Registry', () => { assert.strictEqual(registry.collections.get().length, 1); configurationService.setUserConfiguration(mcpAccessConfig, McpAccessValue.None); - // eslint-disable-next-line local/code-no-any-casts - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); - - assert.strictEqual(registry.collections.get().length, 0); + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: new Set([mcpAccessConfig]), + change: { keys: [mcpAccessConfig], overrides: [] }, + source: ConfigurationTarget.USER + } as IConfigurationChangeEvent); assert.strictEqual(registry.collections.get().length, 0); configurationService.setUserConfiguration(mcpAccessConfig, McpAccessValue.All); - // eslint-disable-next-line local/code-no-any-casts - configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any); + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: new Set([mcpAccessConfig]), + change: { keys: [mcpAccessConfig], overrides: [] }, + source: ConfigurationTarget.USER + } as IConfigurationChangeEvent); }); test('registerDelegate adds delegate to registry', () => { @@ -259,17 +271,14 @@ suite('Workbench - MCP - Registry', () => { assert.ok(connection); assert.strictEqual(connection.definition, definition); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((connection.launchDefinition as any).command, '/test/workspace/cmd'); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((connection.launchDefinition as any).env.PATH, 'interactiveValue0'); + assert.strictEqual((connection.launchDefinition as unknown as { command: string }).command, '/test/workspace/cmd'); + assert.strictEqual((connection.launchDefinition as unknown as { env: { PATH: string } }).env.PATH, 'interactiveValue0'); connection.dispose(); const connection2 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger, trustNonceBearer }) as McpServerConnection; assert.ok(connection2); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((connection2.launchDefinition as any).env.PATH, 'interactiveValue0'); + assert.strictEqual((connection2.launchDefinition as unknown as { env: { PATH: string } }).env.PATH, 'interactiveValue0'); connection2.dispose(); registry.clearSavedInputs(StorageScope.WORKSPACE); @@ -277,8 +286,7 @@ suite('Workbench - MCP - Registry', () => { const connection3 = await registry.resolveConnection({ collectionRef: testCollection, definitionRef: definition, logger, trustNonceBearer }) as McpServerConnection; assert.ok(connection3); - // eslint-disable-next-line local/code-no-any-casts - assert.strictEqual((connection3.launchDefinition as any).env.PATH, 'interactiveValue4'); + assert.strictEqual((connection3.launchDefinition as unknown as { env: { PATH: string } }).env.PATH, 'interactiveValue4'); connection3.dispose(); }); diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts b/src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts index 97e44ac70d3..8b9d8e89c7d 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts @@ -62,7 +62,7 @@ export class TestMcpMessageTransport extends Disposable implements IMcpMessageTr * The responder receives the sent message and should return a response object, * which will be simulated as a server response. */ - public setResponder(method: string, responder: (message: any) => MCP.JSONRPCMessage | undefined): void { + public setResponder(method: string, responder: (message: unknown) => MCP.JSONRPCMessage | undefined): void { if (!this._responders) { this._responders = new Map(); } diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts index 83022603a12..543ab09965b 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts @@ -8,7 +8,7 @@ import { Barrier, timeout } from '../../../../../base/common/async.js'; import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { FileChangeType, FileSystemProviderErrorCode, FileType, IFileChange, IFileService } from '../../../../../platform/files/common/files.js'; +import { FileChangeType, FileSystemProviderErrorCode, FileType, IFileChange, IFileService, toFileSystemProviderErrorCode } from '../../../../../platform/files/common/files.js'; import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { ILoggerService, NullLogService } from '../../../../../platform/log/common/log.js'; @@ -65,12 +65,13 @@ suite('Workbench - MCP - ResourceFilesystem', () => { test('reads a basic file', async () => { transport.setResponder('resources/read', msg => { - assert.strictEqual(msg.params.uri, 'custom://hello/world.txt'); + const request = msg as { id: string | number; params: { uri: string } }; + assert.strictEqual(request.params.uri, 'custom://hello/world.txt'); return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { - contents: [{ uri: msg.params.uri, text: 'Hello World' }], + contents: [{ uri: request.params.uri, text: 'Hello World' }], } satisfies MCP.ReadResourceResult }; }); @@ -81,12 +82,13 @@ suite('Workbench - MCP - ResourceFilesystem', () => { test('stat returns file information', async () => { transport.setResponder('resources/read', msg => { - assert.strictEqual(msg.params.uri, 'custom://hello/world.txt'); + const request = msg as { id: string | number; params: { uri: string } }; + assert.strictEqual(request.params.uri, 'custom://hello/world.txt'); return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { - contents: [{ uri: msg.params.uri, text: 'Hello World' }], + contents: [{ uri: request.params.uri, text: 'Hello World' }], } satisfies MCP.ReadResourceResult }; }); @@ -98,9 +100,10 @@ suite('Workbench - MCP - ResourceFilesystem', () => { test('stat returns directory information', async () => { transport.setResponder('resources/read', msg => { - assert.strictEqual(msg.params.uri, 'custom://hello'); + const request = msg as { id: string | number; params: { uri: string } }; + assert.strictEqual(request.params.uri, 'custom://hello'); return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { contents: [ @@ -119,8 +122,9 @@ suite('Workbench - MCP - ResourceFilesystem', () => { test('stat throws FileNotFound for nonexistent resources', async () => { transport.setResponder('resources/read', msg => { + const request = msg as { id: string | number }; return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { contents: [], @@ -130,15 +134,16 @@ suite('Workbench - MCP - ResourceFilesystem', () => { await assert.rejects( () => fs.stat(URI.parse('mcp-resource://746573742D736572766572/custom/nonexistent.txt')), - (err: any) => err.code === FileSystemProviderErrorCode.FileNotFound + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.FileNotFound ); }); test('readdir returns directory contents', async () => { transport.setResponder('resources/read', msg => { - assert.strictEqual(msg.params.uri, 'custom://hello/dir'); + const request = msg as { id: string | number; params: { uri: string } }; + assert.strictEqual(request.params.uri, 'custom://hello/dir'); return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { contents: [ @@ -160,29 +165,31 @@ suite('Workbench - MCP - ResourceFilesystem', () => { test('readdir throws when reading a file as directory', async () => { transport.setResponder('resources/read', msg => { + const request = msg as { id: string | number; params: { uri: string } }; return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { - contents: [{ uri: msg.params.uri, text: 'This is a file' }], + contents: [{ uri: request.params.uri, text: 'This is a file' }], } satisfies MCP.ReadResourceResult }; }); await assert.rejects( () => fs.readdir(URI.parse('mcp-resource://746573742D736572766572/custom/hello/file.txt')), - (err: any) => err.code === FileSystemProviderErrorCode.FileNotADirectory + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.FileNotADirectory ); }); test('watch file emits change events', async () => { // Set up the responder for resource reading transport.setResponder('resources/read', msg => { + const request = msg as { id: string | number; params: { uri: string } }; return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: { - contents: [{ uri: msg.params.uri, text: 'File content' }], + contents: [{ uri: request.params.uri, text: 'File content' }], } satisfies MCP.ReadResourceResult }; }); @@ -191,9 +198,10 @@ suite('Workbench - MCP - ResourceFilesystem', () => { // Set up the responder for resource subscription transport.setResponder('resources/subscribe', msg => { + const request = msg as { id: string | number }; didSubscribe.open(); return { - id: msg.id, + id: request.id, jsonrpc: '2.0', result: {}, }; @@ -243,12 +251,13 @@ suite('Workbench - MCP - ResourceFilesystem', () => { const blobBase64 = 'SGVsbG8gV29ybGQgYXMgQmxvYg=='; // "Hello World as Blob" in base64 transport.setResponder('resources/read', msg => { - assert.strictEqual(msg.params.uri, 'custom://hello/blob.bin'); + const params = (msg as { id: string | number; params: { uri: string } }); + assert.strictEqual(params.params.uri, 'custom://hello/blob.bin'); return { - id: msg.id, + id: params.id, jsonrpc: '2.0', result: { - contents: [{ uri: msg.params.uri, blob: blobBase64 }], + contents: [{ uri: params.params.uri, blob: blobBase64 }], } satisfies MCP.ReadResourceResult }; }); @@ -262,22 +271,22 @@ suite('Workbench - MCP - ResourceFilesystem', () => { await assert.rejects( async () => fs.writeFile(uri, new Uint8Array(), { create: true, overwrite: true, atomic: false, unlock: false }), - (err: any) => err.code === FileSystemProviderErrorCode.NoPermissions + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.NoPermissions ); await assert.rejects( async () => fs.delete(uri, { recursive: false, useTrash: false, atomic: false }), - (err: any) => err.code === FileSystemProviderErrorCode.NoPermissions + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.NoPermissions ); await assert.rejects( async () => fs.mkdir(uri), - (err: any) => err.code === FileSystemProviderErrorCode.NoPermissions + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.NoPermissions ); await assert.rejects( async () => fs.rename(uri, URI.parse('mcp-resource://746573742D736572766572/custom/hello/newfile.txt'), { overwrite: false }), - (err: any) => err.code === FileSystemProviderErrorCode.NoPermissions + (err: Error) => toFileSystemProviderErrorCode(err) === FileSystemProviderErrorCode.NoPermissions ); }); }); diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts index 52ecaa585bd..8fc8e47f925 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts @@ -10,18 +10,17 @@ import { StorageScope } from "../../../../../platform/storage/common/storage.js"; import { TestStorageService } from "../../../../test/common/workbenchTestServices.js"; -import { McpSamplingLog } from "../../common/mcpSamplingLog.js"; +import { ISamplingStoredData, McpSamplingLog } from "../../common/mcpSamplingLog.js"; import { IMcpServer } from "../../common/mcpTypes.js"; suite("MCP - Sampling Log", () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); - // eslint-disable-next-line local/code-no-any-casts const fakeServer: IMcpServer = { definition: { id: "testServer" }, readDefinitions: () => ({ get: () => ({ collection: { scope: StorageScope.APPLICATION } }), }), - } as any; + } as IMcpServer; let log: McpSamplingLog; let storage: TestStorageService; @@ -49,8 +48,7 @@ suite("MCP - Sampling Log", () => { // storage.testEmitWillSaveState(WillSaveStateReason.NONE); await storage.flush(); assert.deepStrictEqual( - // eslint-disable-next-line local/code-no-any-casts - (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any), + (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as unknown), [ [ "testServer", @@ -92,8 +90,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; + const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, any][])[0][1]; // Verify the bin for the current day has 2 requests assert.strictEqual(data.bins[0], 2); @@ -125,8 +122,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; + const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1]; // Verify the bins: day 2 should have 1 request, day 1 should have 1 request assert.strictEqual(data.bins[0], 1); // day 2 @@ -144,8 +140,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; + const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1]; // Verify the bins have shifted correctly assert.strictEqual(updatedData.bins[0], 1); // day 7 @@ -165,13 +160,12 @@ suite("MCP - Sampling Log", () => { } await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; + const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1]; // Verify only the last 30 requests are kept assert.strictEqual(data.lastReqs.length, 30); - assert.strictEqual(data.lastReqs[0].request[0].content.text, "request 34"); - assert.strictEqual(data.lastReqs[29].request[0].content.text, "request 5"); + assert.strictEqual((data.lastReqs[0].request[0].content as { type: "text"; text: string }).text, "request 34"); + assert.strictEqual((data.lastReqs[29].request[0].content as { type: "text"; text: string }).text, "request 5"); }); test("handles different content types", async () => { @@ -217,8 +211,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any)[0][1]; + const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1]; // Verify all requests are stored correctly assert.strictEqual(data.lastReqs.length, 3); @@ -228,13 +221,12 @@ suite("MCP - Sampling Log", () => { }); test("handles multiple servers", async () => { - // eslint-disable-next-line local/code-no-any-casts const fakeServer2: IMcpServer = { definition: { id: "testServer2" }, readDefinitions: () => ({ get: () => ({ collection: { scope: StorageScope.APPLICATION } }), }), - } as any; + } as IMcpServer; log.add( fakeServer, @@ -251,8 +243,7 @@ suite("MCP - Sampling Log", () => { ); await storage.flush(); - // eslint-disable-next-line local/code-no-any-casts - const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as any); + const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][]); // Verify both servers have their data stored assert.strictEqual(storageData.length, 2); diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts index 4d19b07bcc4..6e76897229f 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts @@ -197,9 +197,9 @@ suite('Workbench - MCP - ServerRequestHandler', () => { try { await requestPromise; assert.fail('Expected error was not thrown'); - } catch (e: any) { - assert.strictEqual(e.message, 'MPC -32601: Resource not found'); - assert.strictEqual(e.code, MCP.METHOD_NOT_FOUND); + } catch (e: unknown) { + assert.strictEqual((e as Error).message, 'MPC -32601: Resource not found'); + assert.strictEqual((e as { code: number }).code, MCP.METHOD_NOT_FOUND); } }); diff --git a/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts b/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts index 7433d08f5a5..6746710e0ff 100644 --- a/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts @@ -13,7 +13,7 @@ suite('UriTemplate', () => { /** * Helper function to test template parsing and component extraction */ - function testParsing(template: string, expectedComponents: any[]) { + function testParsing(template: string, expectedComponents: unknown[]) { const templ = UriTemplate.parse(template); assert.deepStrictEqual(templ.components.filter(c => typeof c === 'object'), expectedComponents); return templ; From db4938fa5099ed24e8e88935c7a3609952ece61f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:45:04 -0700 Subject: [PATCH 1020/4355] Avoid using custom markdown actionHandlers in a few cases Tries to use the standard markdown actionHandler in more cases As part of this, switches some callers from the low level `renderMarkdown` function to the `IMarkdownRendererService` --- .../services/hoverService/hoverWidget.ts | 12 ++---- .../markdown/browser/markdownRenderer.ts | 20 +++++++--- .../browser/extensionFeaturesTab.ts | 37 +++++++------------ .../extensions/browser/extensionsWidgets.ts | 11 ++---- .../contrib/mcp/browser/mcpServerWidgets.ts | 11 ++---- .../contrib/mcp/browser/mcpServersView.ts | 23 +++++------- 6 files changed, 48 insertions(+), 66 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 5db8f80e381..a734f50acba 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -13,8 +13,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Widget } from '../../../../base/browser/ui/widget.js'; import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { IMarkdownRendererService, openLinkFromMarkdown } from '../../../../platform/markdown/browser/markdownRenderer.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { isMarkdownString } from '../../../../base/common/htmlContent.js'; import { localize } from '../../../../nls.js'; import { isMacintosh } from '../../../../base/common/platform.js'; @@ -49,7 +48,7 @@ export class HoverWidget extends Widget implements IHoverWidget { private readonly _hoverPointer: HTMLElement | undefined; private readonly _hoverContainer: HTMLElement; private readonly _target: IHoverTarget; - private readonly _linkHandler: (url: string) => void; + private readonly _linkHandler: ((url: string) => void) | undefined; private _isDisposed: boolean = false; private _hoverPosition: HoverPosition; @@ -98,15 +97,12 @@ export class HoverWidget extends Widget implements IHoverWidget { options: IHoverOptions, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IOpenerService private readonly _openerService: IOpenerService, @IMarkdownRendererService private readonly _markdownRenderer: IMarkdownRendererService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(); - this._linkHandler = options.linkHandler || (url => { - return openLinkFromMarkdown(this._openerService, url, isMarkdownString(options.content) ? options.content.isTrusted : undefined); - }); + this._linkHandler = options.linkHandler; this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target); @@ -166,7 +162,7 @@ export class HoverWidget extends Widget implements IHoverWidget { const markdown = options.content; const { element, dispose } = this._markdownRenderer.render(markdown, { - actionHandler: (content) => this._linkHandler(content), + actionHandler: this._linkHandler, asyncRenderCallback: () => { contentsElement.classList.add('code-hover-contents'); this.layout(); diff --git a/src/vs/platform/markdown/browser/markdownRenderer.ts b/src/vs/platform/markdown/browser/markdownRenderer.ts index fed8adf5d36..1217633b485 100644 --- a/src/vs/platform/markdown/browser/markdownRenderer.ts +++ b/src/vs/platform/markdown/browser/markdownRenderer.ts @@ -62,13 +62,21 @@ export class MarkdownRendererService implements IMarkdownRendererService { ) { } render(markdown: IMarkdownString, options?: MarkdownRenderOptions & IMarkdownRendererExtraOptions, outElement?: HTMLElement): IRenderedMarkdown { - const rendered = renderMarkdown(markdown, { - codeBlockRenderer: (alias, value) => this._defaultCodeBlockRenderer?.renderCodeBlock(alias, value, options ?? {}) ?? Promise.resolve(document.createElement('span')), - actionHandler: (link, mdStr) => { + const resolvedOptions = { ...options }; + + if (!resolvedOptions.actionHandler) { + resolvedOptions.actionHandler = (link, mdStr) => { return openLinkFromMarkdown(this._openerService, link, mdStr.isTrusted); - }, - ...options, - }, outElement); + }; + } + + if (!resolvedOptions.codeBlockRenderer) { + resolvedOptions.codeBlockRenderer = (alias, value) => { + return this._defaultCodeBlockRenderer?.renderCodeBlock(alias, value, resolvedOptions ?? {}) ?? Promise.resolve(document.createElement('span')); + }; + } + + const rendered = renderMarkdown(markdown, resolvedOptions, outElement); rendered.element.classList.add('rendered-markdown'); return rendered; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 6ae42e60fc3..3cd48a5bd37 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -17,9 +17,7 @@ import { getExtensionId } from '../../../../platform/extensionManagement/common/ import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { defaultButtonStyles, defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; -import { getErrorMessage, onUnexpectedError } from '../../../../base/common/errors.js'; -import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { getErrorMessage } from '../../../../base/common/errors.js'; import { PANEL_SECTION_BORDER } from '../../../common/theme.js'; import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; @@ -39,6 +37,7 @@ import { ResolvedKeybinding } from '../../../../base/common/keybindings.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { foreground, chartAxis, chartGuide, chartLine } from '../../../../platform/theme/common/colorRegistry.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; interface IExtensionFeatureElementRenderer extends IExtensionFeatureRenderer { type: 'element'; @@ -52,9 +51,9 @@ class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeat constructor( @IExtensionService private readonly extensionService: IExtensionService, - @IOpenerService private readonly openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); } @@ -152,15 +151,11 @@ class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeat } private renderMarkdown(markdown: IMarkdownString, container: HTMLElement, disposables: DisposableStore): void { - const { element } = disposables.add(renderMarkdown( - { - value: markdown.value, - isTrusted: markdown.isTrusted, - supportThemeIcons: true - }, - { - actionHandler: (content) => this.openerService.open(content, { allowCommands: !!markdown.isTrusted }).catch(onUnexpectedError), - })); + const { element } = disposables.add(this.markdownRendererService.render({ + value: markdown.value, + isTrusted: markdown.isTrusted, + supportThemeIcons: true + })); append(container, element); } @@ -547,10 +542,10 @@ class ExtensionFeatureView extends Disposable { private readonly extensionId: ExtensionIdentifier, private readonly manifest: IExtensionManifest, readonly feature: IExtensionFeatureDescriptor, - @IOpenerService private readonly openerService: IOpenerService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, @IDialogService private readonly dialogService: IDialogService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); @@ -702,15 +697,11 @@ class ExtensionFeatureView extends Disposable { } private renderMarkdown(markdown: IMarkdownString, container: HTMLElement): void { - const { element } = this._register(renderMarkdown( - { - value: markdown.value, - isTrusted: markdown.isTrusted, - supportThemeIcons: true - }, - { - actionHandler: (content) => this.openerService.open(content, { allowCommands: !!markdown.isTrusted }).catch(onUnexpectedError), - })); + const { element } = this._register(this.markdownRendererService.render({ + value: markdown.value, + isTrusted: markdown.isTrusted, + supportThemeIcons: true + })); append(container, element); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index e3863b16ae0..012c6840c2c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -32,9 +32,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import Severity from '../../../../base/common/severity.js'; import { Color } from '../../../../base/common/color.js'; -import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; -import { onUnexpectedError } from '../../../../base/common/errors.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; @@ -51,6 +49,7 @@ import { IExplorerService } from '../../files/browser/files.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { VIEW_ID as EXPLORER_VIEW_ID } from '../../files/common/files.js'; import { IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -998,7 +997,7 @@ export class ExtensionStatusWidget extends ExtensionWidget { constructor( private readonly container: HTMLElement, private readonly extensionStatusAction: ExtensionStatusAction, - @IOpenerService private readonly openerService: IOpenerService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); this.render(); @@ -1023,11 +1022,7 @@ export class ExtensionStatusWidget extends ExtensionWidget { markdown.appendText(`\n`); } } - const rendered = disposables.add(renderMarkdown(markdown, { - actionHandler: (content) => { - this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); - }, - })); + const rendered = disposables.add(this.markdownRendererService.render(markdown)); append(this.container, rendered.element); } this._onDidRender.fire(); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts index 6761a41c515..47dbe902e79 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts @@ -24,14 +24,13 @@ import { McpServerStatusAction } from './mcpServerActions.js'; import { reset } from '../../../../base/browser/dom.js'; import { mcpLicenseIcon, mcpServerIcon, mcpServerRemoteIcon, mcpServerWorkspaceIcon, mcpStarredIcon } from './mcpServerIcons.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; -import { onUnexpectedError } from '../../../../base/common/errors.js'; import { ExtensionHoverOptions, ExtensionIconBadge } from '../../extensions/browser/extensionsWidgets.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchManagementService.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { registerColor } from '../../../../platform/theme/common/colorUtils.js'; import { textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export abstract class McpServerWidget extends Disposable implements IMcpServerContainer { private _mcpServer: IWorkbenchMcpServer | null = null; @@ -451,7 +450,7 @@ export class McpServerStatusWidget extends McpServerWidget { constructor( private readonly container: HTMLElement, private readonly extensionStatusAction: McpServerStatusAction, - @IOpenerService private readonly openerService: IOpenerService, + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, ) { super(); this.render(); @@ -476,11 +475,7 @@ export class McpServerStatusWidget extends McpServerWidget { markdown.appendText(`\n`); } } - const rendered = disposables.add(renderMarkdown(markdown, { - actionHandler: (content) => { - this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); - } - })); + const rendered = disposables.add(this.markdownRendererService.render(markdown)); dom.append(this.container, rendered.element); } this._onDidRender.fire(); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 5a12c9d54f3..38eaf863569 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -32,7 +32,6 @@ import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidg import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { IAllowedMcpServersService, mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; -import { URI } from '../../../../base/common/uri.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; @@ -40,7 +39,6 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { DefaultViewsContext, SearchMcpServersContext } from '../../extensions/common/extensions.js'; import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js'; -import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js'; @@ -53,6 +51,7 @@ import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js'; import { IMcpGalleryManifestService, McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { ProductQualityContext } from '../../../../platform/contextkey/common/contextkeys.js'; import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; export interface McpServerListViewOptions { showWelcome?: boolean; @@ -100,6 +99,7 @@ export class McpServersListView extends AbstractExtensionsListView { - this.openerService.open(URI.parse(content), { allowCommands: ['workbench.action.openSettings'] }); - } - })); + const markdownResult = this._register(this.markdownRendererService.render( + new MarkdownString( + localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from VS Code to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), + { isTrusted: { enabledCommands: ['workbench.action.openSettings'] } }, + ) + .appendMarkdown('\n\n') + .appendMarkdown(localize('mcp.gallery.enableDialog.setting', "This feature is currently in preview. You can disable it anytime using the setting {0}.", settingsCommandLink)), + )); description.appendChild(markdownResult.element); const buttonContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-button-container')); From 32d307723257d10887185352ec6c0c6882690481 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:25:24 -0700 Subject: [PATCH 1021/4355] Run `npm ci` & point to README in vscode-playwright-mcp (#270603) Fixes https://github.com/microsoft/vscode/issues/270534 --- test/mcp/README.md | 5 ++--- test/mcp/package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/mcp/README.md b/test/mcp/README.md index a3c8a896c2d..5c02ee0f09d 100644 --- a/test/mcp/README.md +++ b/test/mcp/README.md @@ -123,12 +123,11 @@ test/mcp/ ## Troubleshooting ### Server Won't Start -- Ensure Code - OSS's Core and Extension builds are running (they should start automatically) -- Check that port 33418 is not already in use +- Ensure Code - OSS has been built and run at least once (via F5 or `code.sh`) - Verify all dependencies are installed with `npm install` ### Browser Automation Issues -- Ensure Code - OSS has been built and run at least once +- Ensure Code - OSS has been built and run at least once (via F5 or `code.sh`) - Check the server logs for Playwright-related errors - Verify the test repository is properly cloned diff --git a/test/mcp/package.json b/test/mcp/package.json index 1b2e14de4be..1f9d1ad7f1c 100644 --- a/test/mcp/package.json +++ b/test/mcp/package.json @@ -9,7 +9,7 @@ "watch-automation": "cd ../automation && npm run watch", "watch-mcp": "node ../../node_modules/typescript/bin/tsc --watch --preserveWatchOutput", "watch": "npm-run-all -lp watch-automation watch-mcp", - "start-stdio": "npm run -s compile && node ./out/stdio.js" + "start-stdio": "echo 'Starting vscode-playwright-mcp... For customization & troubleshooting, see ./test/mcp/README.md' && npm ci && npm run -s compile && node ./out/stdio.js" }, "dependencies": { "@modelcontextprotocol/sdk": "1.18.1", From 320f804fef45567fd9a4d4ff39853c177bbf7d71 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 9 Oct 2025 10:57:36 -0700 Subject: [PATCH 1022/4355] debug: only show exception widget in active editor group --- .../debug/browser/debugEditorContribution.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 6a807336974..fad576a4fc1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -52,6 +52,7 @@ import { ExceptionWidget } from './exceptionWidget.js'; import { CONTEXT_EXCEPTION_WIDGET_VISIBLE, IDebugConfiguration, IDebugEditorContribution, IDebugService, IDebugSession, IExceptionInfo, IExpression, IStackFrame, State } from '../common/debug.js'; import { Expression } from '../common/debugModel.js'; import { IHostService } from '../../../services/host/browser/host.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons @@ -279,7 +280,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IContextKeyService contextKeyService: IContextKeyService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, - @ILanguageFeatureDebounceService featureDebounceService: ILanguageFeatureDebounceService + @ILanguageFeatureDebounceService featureDebounceService: ILanguageFeatureDebounceService, + @IEditorService private readonly editorService: IEditorService ) { this.oldDecorations = this.editor.createDecorationsCollection(); this.debounceInfo = featureDebounceService.for(languageFeaturesService.inlineValuesProvider, 'InlineValues', { min: DEAFULT_INLINE_DEBOUNCE_DELAY }); @@ -584,9 +586,18 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (this.exceptionWidget && !sameUri) { this.closeExceptionWidget(); } else if (sameUri) { - const exceptionInfo = await focusedSf.thread.exceptionInfo; - if (exceptionInfo) { - this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); + // Only show exception widget in the active editor, not all editors with the same file + const activeControl = this.editorService.activeTextEditorControl; + const isActiveEditor = activeControl === this.editor; + + if (isActiveEditor) { + const exceptionInfo = await focusedSf.thread.exceptionInfo; + if (exceptionInfo) { + this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); + } + } else { + // For non-active editors, close any existing exception widget + this.closeExceptionWidget(); } } } From d39085dbffddb7488cb50bf78f541781e158efe6 Mon Sep 17 00:00:00 2001 From: Bryan Chen <41454397+bryanchen-d@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:11:46 -0700 Subject: [PATCH 1023/4355] Tweak the comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../workbench/contrib/debug/browser/debugEditorContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index fad576a4fc1..2cd76e98579 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -586,7 +586,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (this.exceptionWidget && !sameUri) { this.closeExceptionWidget(); } else if (sameUri) { - // Only show exception widget in the active editor, not all editors with the same file + // Only show exception widget in the active editor to prevent disrupting workflow in multiple editor groups with the same file const activeControl = this.editorService.activeTextEditorControl; const isActiveEditor = activeControl === this.editor; From 46b60ac2ce4fce4eab4f45d488b1b0942d5a0427 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 9 Oct 2025 11:22:49 -0700 Subject: [PATCH 1024/4355] chore: remove any casts in testing, mcp, debug (#270616) Ref #269213 --- src/vs/base/test/common/mock.ts | 11 ++++++++ .../browser/extensionHostDebugService.ts | 3 +-- .../contrib/debug/browser/rawDebugSession.ts | 3 +-- .../workbench/contrib/debug/common/debug.ts | 2 ++ .../contrib/debug/common/debugUtils.ts | 3 +-- .../contrib/debug/common/debugger.ts | 3 +-- .../debug/test/browser/callStack.test.ts | 22 +++++++-------- .../debug/test/browser/mockDebugModel.ts | 5 ++-- .../debug/test/common/debugModel.test.ts | 12 ++++----- .../contrib/debug/test/common/mockDebug.ts | 5 ++-- .../testResultsView/testResultsOutput.ts | 6 ++--- .../contrib/testing/common/observableUtils.ts | 3 +-- .../contrib/testing/common/testingStates.ts | 3 +-- .../browser/codeCoverageDecorations.test.ts | 4 +-- .../nameProjection.test.ts | 17 ++++++------ .../treeProjection.test.ts | 27 +++++++++---------- .../testing/test/common/testCoverage.test.ts | 5 ++-- .../test/common/testProfileService.test.ts | 5 ++-- .../test/common/testResultService.test.ts | 6 ++--- .../contrib/testing/test/common/testStubs.ts | 3 +-- 20 files changed, 76 insertions(+), 72 deletions(-) diff --git a/src/vs/base/test/common/mock.ts b/src/vs/base/test/common/mock.ts index 8662c4fdac2..0e7ba6901af 100644 --- a/src/vs/base/test/common/mock.ts +++ b/src/vs/base/test/common/mock.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { SinonStub, stub } from 'sinon'; +import { DeepPartial } from '../../common/types.js'; export interface Ctor { new(): T; @@ -34,3 +35,13 @@ export const mockObject = () => = {}>(p }, }); }; + +/** + * Shortcut for type-safe partials in mocks. A shortcut for `obj as Partial as T`. + */ +export function upcastPartial(partial: Partial): T { + return partial as T; +} +export function upcastDeepPartial(partial: DeepPartial): T { + return partial as T; +} diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index dc1554b242e..1e48c939054 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -43,8 +43,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName); } else { // Extension host debugging not supported in serverless. - // eslint-disable-next-line local/code-no-any-casts - channel = { call: async () => undefined, listen: () => Event.None } as any; + channel = { call: async () => Promise.resolve(undefined!), listen: () => Event.None }; } super(channel); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index ecb876f695c..3ef3a621372 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -810,8 +810,7 @@ export class RawDebugSession implements IDisposable { this.notificationService.error(userMessage); } const result = new errors.ErrorNoTelemetry(userMessage); - // eslint-disable-next-line local/code-no-any-casts - (result).showUser = error?.showUser; + (result as { showUser?: boolean }).showUser = error?.showUser; return result; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 4487bd487aa..c70c0e15fdc 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -960,6 +960,8 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut hiddenWhen?: string; deprecated?: string; strings?: { [key in DebuggerString]: string }; + /** @deprecated */ + uiMessages?: { [key in DebuggerString]: string }; } export interface IBreakpointContribution { diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 7eb37eaf10d..6b6af608669 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -63,8 +63,7 @@ export function getExtensionHostDebugSession(session: IDebugSession): IDebugSess } if (type === 'vslsShare') { - // eslint-disable-next-line local/code-no-any-casts - type = (session.configuration).adapterProxy.configuration.type; + type = (session.configuration as { adapterProxy?: { configuration?: { type?: string } } }).adapterProxy?.configuration?.type || type; } if (equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost')) { diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index d77e2ff9378..55453c8b282 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -165,8 +165,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { } get strings() { - // eslint-disable-next-line local/code-no-any-casts - return this.debuggerContribution.strings ?? (this.debuggerContribution as any).uiMessages; + return this.debuggerContribution.strings ?? this.debuggerContribution.uiMessages; } interestedInLanguage(languageId: string): boolean { diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 9d34b3bf9b8..2d0dd6821ab 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -8,12 +8,14 @@ import * as sinon from 'sinon'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { Constants } from '../../../../../base/common/uint.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +import { upcastDeepPartial, upcastPartial } from '../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { TestAccessibilityService } from '../../../../../platform/accessibility/test/common/testAccessibilityService.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; +import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { createDecorationsForStackFrame } from '../../browser/callStackEditorContribution.js'; import { getContext, getContextForContributedActions, getSpecificSourceName } from '../../browser/callStackView.js'; import { debugStackframe, debugStackframeFocused } from '../../browser/debugIcons.js'; @@ -22,17 +24,17 @@ import { DebugSession } from '../../browser/debugSession.js'; import { IDebugService, IDebugSessionOptions, State } from '../../common/debug.js'; import { DebugModel, StackFrame, Thread } from '../../common/debugModel.js'; import { Source } from '../../common/debugSource.js'; -import { createMockDebugModel, mockUriIdentityService } from './mockDebugModel.js'; import { MockRawSession } from '../common/mockDebug.js'; +import { createMockDebugModel, mockUriIdentityService } from './mockDebugModel.js'; +import { RawDebugSession } from '../../browser/rawDebugSession.js'; -// eslint-disable-next-line local/code-no-any-casts -const mockWorkspaceContextService = { +const mockWorkspaceContextService = upcastDeepPartial({ getWorkspace: () => { return { folders: [] }; } -} as any; +}); export function createTestSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, options, { @@ -121,8 +123,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); - // eslint-disable-next-line local/code-no-any-casts - session['raw'] = mockRawSession; + session.raw = upcastPartial(mockRawSession); model.rawUpdate({ sessionId: session.getId(), @@ -203,8 +204,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); - // eslint-disable-next-line local/code-no-any-casts - session['raw'] = mockRawSession; + session.raw = upcastPartial(mockRawSession); // Stopped event with all threads stopped model.rawUpdate({ @@ -258,8 +258,7 @@ suite('Debug - CallStack', () => { disposables.add(session); model.addSession(session); - // eslint-disable-next-line local/code-no-any-casts - session['raw'] = mockRawSession; + session.raw = upcastPartial(mockRawSession); // Add the threads model.rawUpdate({ @@ -458,8 +457,7 @@ suite('Debug - CallStack', () => { model.addSession(runningSession); model.addSession(session); - // eslint-disable-next-line local/code-no-any-casts - session['raw'] = mockRawSession; + session.raw = upcastPartial(mockRawSession); model.rawUpdate({ sessionId: session.getId(), diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts index 1548373a729..6392f1373fb 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { upcastPartial } from '../../../../../base/test/common/mock.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js'; +import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; import { TestFileService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { DebugModel } from '../../common/debugModel.js'; import { MockDebugStorage } from '../common/mockDebug.js'; @@ -16,6 +18,5 @@ export const mockUriIdentityService = new UriIdentityService(fileService); export function createMockDebugModel(disposable: Pick): DebugModel { const storage = disposable.add(new TestStorageService()); const debugStorage = disposable.add(new MockDebugStorage(storage)); - // eslint-disable-next-line local/code-no-any-casts - return disposable.add(new DebugModel(debugStorage, { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); + return disposable.add(new DebugModel(debugStorage, upcastPartial({ isDirty: (e: unknown) => false }), mockUriIdentityService, new NullLogService())); } diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index cf3b44cc521..33bee9716c8 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -6,12 +6,14 @@ import assert from 'assert'; import { DeferredPromise } from '../../../../../base/common/async.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; -import { mockObject } from '../../../../../base/test/common/mock.js'; +import { mockObject, upcastDeepPartial, upcastPartial } from '../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { NullLogService } from '../../../../../platform/log/common/log.js'; +import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; +import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { IDebugSession } from '../../common/debug.js'; import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Thread } from '../../common/debugModel.js'; import { MockDebugStorage } from './mockDebug.js'; -import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; suite('DebugModel', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -47,8 +49,7 @@ suite('DebugModel', () => { const topFrameDeferred = new DeferredPromise(); const wholeStackDeferred = new DeferredPromise(); const fakeThread = mockObject()({ - // eslint-disable-next-line local/code-no-any-casts - session: { capabilities: { supportsDelayedStackTraceLoading: true } } as any, + session: upcastDeepPartial({ capabilities: { supportsDelayedStackTraceLoading: true } }), getCallStack: () => [], getStaleCallStack: () => [], }); @@ -59,8 +60,7 @@ suite('DebugModel', () => { const disposable = new DisposableStore(); const storage = disposable.add(new TestStorageService()); - // eslint-disable-next-line local/code-no-any-casts - const model = new DebugModel(disposable.add(new MockDebugStorage(storage)), { isDirty: (e: any) => false }, undefined!, new NullLogService()); + const model = new DebugModel(disposable.add(new MockDebugStorage(storage)), upcastPartial({ isDirty: (e: unknown) => false }), undefined!, new NullLogService()); disposable.add(model); let top1Resolved = false; diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index d77ad30c097..e559c23fa11 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -523,7 +523,7 @@ export class MockRawSession { throw new Error('not implemented'); } - disconnect(restart?: boolean): Promise { + disconnect(): Promise { throw new Error('not implemented'); } @@ -692,7 +692,6 @@ export class MockDebugAdapter extends AbstractDebugAdapter { export class MockDebugStorage extends DebugStorage { constructor(storageService: IStorageService) { - // eslint-disable-next-line local/code-no-any-casts - super(storageService, undefined as any, undefined as any, new NullLogService()); + super(storageService, undefined!, undefined!, new NullLogService()); } } diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index 5a7a19cb2bf..79d44323bfe 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -119,10 +119,8 @@ function applyEditorMirrorOptions(base: T, cfg: IConfi let changed = false; const patch: Partial = {}; for (const [key, value] of Object.entries(configuration)) { - // eslint-disable-next-line local/code-no-any-casts - if (!immutable.has(key) && (base as any)[key] !== value) { - // eslint-disable-next-line local/code-no-any-casts - (patch as any)[key] = value; + if (!immutable.has(key) && (base as Record)[key] !== value) { + (patch as Record)[key] = value; changed = true; } } diff --git a/src/vs/workbench/contrib/testing/common/observableUtils.ts b/src/vs/workbench/contrib/testing/common/observableUtils.ts index 69461210334..1278e7c9422 100644 --- a/src/vs/workbench/contrib/testing/common/observableUtils.ts +++ b/src/vs/workbench/contrib/testing/common/observableUtils.ts @@ -14,8 +14,7 @@ export function onObservableChange(observable: IObservableWithChange(_observable: IObservableWithChange, change: TChange) { - // eslint-disable-next-line local/code-no-any-casts - callback(change as any as T); + callback(change as unknown as T); } }; diff --git a/src/vs/workbench/contrib/testing/common/testingStates.ts b/src/vs/workbench/contrib/testing/common/testingStates.ts index 49793c9e4e7..fd84d553371 100644 --- a/src/vs/workbench/contrib/testing/common/testingStates.ts +++ b/src/vs/workbench/contrib/testing/common/testingStates.ts @@ -75,6 +75,5 @@ export type TestStateCount = { [K in TestResultState]: number }; export const makeEmptyCounts = (): TestStateCount => { // shh! don't tell anyone this is actually an array! - // eslint-disable-next-line local/code-no-any-casts - return new Uint32Array(statesInOrder.length) as any as { [K in TestResultState]: number }; + return new Uint32Array(statesInOrder.length) as unknown as TestStateCount; }; diff --git a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts index 59bedb10289..9f1894a58d7 100644 --- a/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts @@ -12,12 +12,12 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import * as assert from 'assert'; import { CoverageDetailsModel } from '../../browser/codeCoverageDecorations.js'; import { CoverageDetails, DetailType } from '../../common/testTypes.js'; +import { upcastPartial } from '../../../../../base/test/common/mock.js'; suite('Code Coverage Decorations', () => { ensureNoDisposablesAreLeakedInTestSuite(); - // eslint-disable-next-line local/code-no-any-casts - const textModel = { getValueInRange: () => '' } as any as ITextModel; + const textModel = upcastPartial({ getValueInRange: () => '' }); const assertRanges = async (model: CoverageDetailsModel) => await assertSnapshot(model.ranges.map(r => ({ range: r.range.toString(), count: r.metadata.detail.type === DetailType.Branch ? r.metadata.detail.detail.branches![r.metadata.detail.branch].count : r.metadata.detail.count, diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts index 888bc6730c4..4ee1dbf8455 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Emitter } from '../../../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../../../base/common/event.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { ListProjection } from '../../../browser/explorerProjections/listProjection.js'; import { TestId } from '../../../common/testId.js'; @@ -12,11 +12,13 @@ import { TestResultItemChange } from '../../../common/testResult.js'; import { TestDiffOpType, TestItemExpandState } from '../../../common/testTypes.js'; import { TestTreeTestHarness } from '../testObjectTree.js'; import { TestTestItem } from '../../common/testStubs.js'; +import { upcastPartial } from '../../../../../../base/test/common/mock.js'; +import { ITestResultService } from '../../../common/testResultService.js'; suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { let harness: TestTreeTestHarness; let onTestChanged: Emitter; - let resultsService: any; + let resultsService: ITestResultService; teardown(() => { harness.dispose(); @@ -26,14 +28,13 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => { setup(() => { onTestChanged = new Emitter(); - resultsService = { - onResultsChanged: () => undefined, + resultsService = upcastPartial({ + onResultsChanged: Event.None, onTestChanged: onTestChanged.event, - getStateById: () => ({ state: { state: 0 }, computedState: 0 }), - }; + getStateById: () => undefined, + }); - // eslint-disable-next-line local/code-no-any-casts - harness = new TestTreeTestHarness(l => new ListProjection({}, l, resultsService as any)); + harness = new TestTreeTestHarness(l => new ListProjection({}, l, resultsService)); }); test('renders initial tree', () => { diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts index 80a6985d7cc..1646cc01bb3 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { Emitter } from '../../../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../../../base/common/event.js'; import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { TreeProjection } from '../../../browser/explorerProjections/treeProjection.js'; @@ -13,6 +13,8 @@ import { TestResultItemChange, TestResultItemChangeReason } from '../../../commo import { TestDiffOpType, TestItemExpandState, TestResultItem, TestResultState } from '../../../common/testTypes.js'; import { TestTreeTestHarness } from '../testObjectTree.js'; import { TestTestItem } from '../../common/testStubs.js'; +import { upcastPartial } from '../../../../../../base/test/common/mock.js'; +import { ITestResultService } from '../../../common/testResultService.js'; class TestHierarchicalByLocationProjection extends TreeProjection { } @@ -20,7 +22,7 @@ class TestHierarchicalByLocationProjection extends TreeProjection { suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { let harness: TestTreeTestHarness; let onTestChanged: Emitter; - let resultsService: any; + let resultsService: ITestResultService; let ds: DisposableStore; teardown(() => { @@ -32,15 +34,14 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { setup(() => { ds = new DisposableStore(); onTestChanged = ds.add(new Emitter()); - resultsService = { + resultsService = upcastPartial({ results: [], - onResultsChanged: () => undefined, + onResultsChanged: Event.None, onTestChanged: onTestChanged.event, - getStateById: () => ({ state: { state: 0 }, computedState: 0 }), - }; + getStateById: () => undefined, + }); - // eslint-disable-next-line local/code-no-any-casts - harness = ds.add(new TestTreeTestHarness(l => new TestHierarchicalByLocationProjection({}, l, resultsService as any))); + harness = ds.add(new TestTreeTestHarness(l => new TestHierarchicalByLocationProjection({}, l, resultsService))); }); test('renders initial tree', async () => { @@ -131,11 +132,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { }); // Applies the change: - resultsService.getStateById = () => [undefined, resultInState(TestResultState.Queued)]; + resultsService.getStateById = () => [undefined!, resultInState(TestResultState.Queued)]; onTestChanged.fire({ reason: TestResultItemChangeReason.OwnStateChange, - // eslint-disable-next-line local/code-no-any-casts - result: null as any, + result: undefined!, previousState: TestResultState.Unset, item: resultInState(TestResultState.Queued), previousOwnDuration: undefined, @@ -148,11 +148,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { ]); // Falls back if moved into unset state: - resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)]; + resultsService.getStateById = () => [undefined!, resultInState(TestResultState.Failed)]; onTestChanged.fire({ reason: TestResultItemChangeReason.OwnStateChange, - // eslint-disable-next-line local/code-no-any-casts - result: null as any, + result: undefined!, previousState: TestResultState.Queued, item: resultInState(TestResultState.Unset), previousOwnDuration: undefined, diff --git a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts index d697e55cb86..c97e4ec22cf 100644 --- a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts @@ -11,6 +11,8 @@ import { onObservableChange } from '../../common/observableUtils.js'; import { ICoverageAccessor, TestCoverage } from '../../common/testCoverage.js'; import { LiveTestResult } from '../../common/testResult.js'; import { IFileCoverage } from '../../common/testTypes.js'; +import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js'; +import { upcastDeepPartial, upcastPartial } from '../../../../../base/test/common/mock.js'; suite('TestCoverage', () => { let sandbox: SinonSandbox; @@ -24,8 +26,7 @@ suite('TestCoverage', () => { coverageAccessor = { getCoverageDetails: sandbox.stub().resolves([]), }; - // eslint-disable-next-line local/code-no-any-casts - testCoverage = new TestCoverage({} as LiveTestResult, 'taskId', { extUri: { ignorePathCasing: () => true } } as any, coverageAccessor); + testCoverage = new TestCoverage({} as LiveTestResult, 'taskId', upcastDeepPartial({ extUri: upcastPartial({ ignorePathCasing: () => true }) }), coverageAccessor); }); teardown(() => { diff --git a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts index c301d1cbc93..2cdbf7a11f6 100644 --- a/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts @@ -12,6 +12,8 @@ import { MockContextKeyService } from '../../../../../platform/keybinding/test/c import { TestProfileService } from '../../common/testProfileService.js'; import { ITestRunProfile, TestRunProfileBitset } from '../../common/testTypes.js'; import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { upcastPartial } from '../../../../../base/test/common/mock.js'; +import { IMainThreadTestController } from '../../common/testService.js'; suite('Workbench - TestProfileService', () => { let t: TestProfileService; @@ -46,8 +48,7 @@ suite('Workbench - TestProfileService', () => { ...profile, }; - // eslint-disable-next-line local/code-no-any-casts - t.addProfile({ id: 'ctrlId' } as any, p); + t.addProfile(upcastPartial({ id: 'ctrlId' }), p); return p; }; diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index b31e6bf0ffa..5ade88fce1e 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { timeout } from '../../../../../base/common/async.js'; +import { RunOnceScheduler, timeout } from '../../../../../base/common/async.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; @@ -21,6 +21,7 @@ import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestResultState import { makeEmptyCounts } from '../../common/testingStates.js'; import { TestTestCollection, getInitializedMainTestCollection, testStubs } from './testStubs.js'; import { TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { upcastPartial } from '../../../../../base/test/common/mock.js'; suite('Workbench - Test Results Service', () => { const getLabelsIn = (it: Iterable) => [...it].map(t => t.item.label).sort(); @@ -207,8 +208,7 @@ suite('Workbench - Test Results Service', () => { let results: TestResultService; class TestTestResultService extends TestResultService { - // eslint-disable-next-line local/code-no-any-casts - protected override persistScheduler = { schedule: () => this.persistImmediately() } as any; + protected override persistScheduler = upcastPartial({ schedule: () => this.persistImmediately() }); } setup(() => { diff --git a/src/vs/workbench/contrib/testing/test/common/testStubs.ts b/src/vs/workbench/contrib/testing/test/common/testStubs.ts index ba1a2f5e47f..8ccf00a5ebc 100644 --- a/src/vs/workbench/contrib/testing/test/common/testStubs.ts +++ b/src/vs/workbench/contrib/testing/test/common/testStubs.ts @@ -82,8 +82,7 @@ export class TestTestItem implements ITestItemLike { export class TestTestCollection extends TestItemCollection { constructor(controllerId = 'ctrlId') { const root = new TestTestItem(new TestId([controllerId]), 'root'); - // eslint-disable-next-line local/code-no-any-casts - (root as any)._isRoot = true; + (root as TestTestItem & { _isRoot: boolean })._isRoot = true; super({ controllerId, From 4e518fed1621789f50d5edce390a2b535bd55ac9 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 9 Oct 2025 11:56:20 -0700 Subject: [PATCH 1025/4355] fix: search in open editors for files with square brackets in names --- .../services/search/common/queryBuilder.ts | 4 ++-- .../search/test/browser/queryBuilder.test.ts | 2 ++ .../search/test/common/queryBuilder.test.ts | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index b75fb3f366a..afa8a1a52c2 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -313,11 +313,11 @@ export class QueryBuilder { } const relPath = path.relative(searchRoot.fsPath, file.fsPath); - assertReturnsDefined(folderQuery.includePattern)[relPath.replace(/\\/g, '/')] = true; + assertReturnsDefined(folderQuery.includePattern)[escapeGlobPattern(relPath.replace(/\\/g, '/'))] = true; } else { if (file.fsPath) { hasIncludedFile = true; - includePattern[file.fsPath] = true; + includePattern[escapeGlobPattern(file.fsPath)] = true; } } }); diff --git a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts index 5adf99af9d0..93beb96a072 100644 --- a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts @@ -1201,6 +1201,8 @@ suite('QueryBuilder', () => { }); assert.strictEqual(query.folderQueries.length, 1); }); + + }); }); function makeExcludePatternFromPatterns(...patterns: string[]): { diff --git a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts index 3a5c91e4686..6767f0b8ddb 100644 --- a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; +import * as glob from '../../../../../base/common/glob.js'; import { isWindows } from '../../../../../base/common/platform.js'; import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; @@ -29,4 +30,27 @@ suite('QueryBuilderCommon', () => { const actual = resolveResourcesForSearchIncludes([URI.file(isWindows ? "C:\\testWorkspace\\pages\\blog\\[postId]" : "/testWorkspace/pages/blog/[postId]")], context); assert.deepStrictEqual(actual, ["./pages/blog/[[]postId[]]"]); }); + + test('escapeGlobPattern properly escapes square brackets for literal matching', () => { + // This test verifies the fix for issue #233049 where files with square brackets in names + // were not found when using "Search Only in Open Editors" + + // Test file name with square brackets + const fileName = 'file[test].txt'; + + // Function matching the one used in queryBuilder.ts + function escapeGlobPattern(path: string): string { + return path.replace(/([?*[\]])/g, '[$1]'); + } + + // Without escaping, the pattern treats [test] as a character class + const unescapedResult = glob.match(fileName, fileName); + assert.strictEqual(unescapedResult, false, 'Unescaped pattern should not match due to character class interpretation'); + + // With escaping, the pattern matches literally + const escapedPattern = escapeGlobPattern(fileName); + const escapedResult = glob.match(escapedPattern, fileName); + assert.strictEqual(escapedResult, true, 'Escaped pattern should match literally'); + assert.strictEqual(escapedPattern, 'file[[]test[]].txt', 'Pattern should have escaped brackets'); + }); }); From 7ab28d031ab12742271edacb0dd13cceacbe5784 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 9 Oct 2025 13:57:52 -0500 Subject: [PATCH 1026/4355] Don't require agent mode to set default model experiment (#270620) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 505411826d6..2511a8260a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -824,7 +824,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - if (typeof defaultLanguageModelTreatment === 'string' && this._currentModeObservable.get().kind === ChatModeKind.Agent) { + if (typeof defaultLanguageModelTreatment === 'string') { this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); this.logService.trace(`Applying default language model from experiment: ${defaultLanguageModelTreatment}`); this.setExpModelOrWait(defaultLanguageModelTreatment); From 4b893e2af2ce9b8e72b8e69b3edd72b511344619 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 9 Oct 2025 12:03:47 -0700 Subject: [PATCH 1027/4355] remove unnecessary blanks --- .../workbench/services/search/test/browser/queryBuilder.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts index 93beb96a072..5adf99af9d0 100644 --- a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts @@ -1201,8 +1201,6 @@ suite('QueryBuilder', () => { }); assert.strictEqual(query.folderQueries.length, 1); }); - - }); }); function makeExcludePatternFromPatterns(...patterns: string[]): { From 1042375c6cdef8ea00ab220de5ed0e7a4fc8bc76 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 9 Oct 2025 12:12:58 -0700 Subject: [PATCH 1028/4355] Add a button to view extension details from Managed Trusted Extensions picker (#270450) * Add a button to view extension details from Managed Trusted Extensions picker * PR feedback --- ...manageTrustedExtensionsForAccountAction.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts index e55bd4a94be..69a0f341c29 100644 --- a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts @@ -16,6 +16,7 @@ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../. import { AllowedExtension, IAuthenticationService } from '../../../../services/authentication/common/authentication.js'; import { IAuthenticationQueryService, IAccountQuery } from '../../../../services/authentication/common/authenticationQuery.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; +import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; export class ManageTrustedExtensionsForAccountAction extends Action2 { constructor() { @@ -39,13 +40,24 @@ interface TrustedExtensionsQuickPickItem extends IQuickPickItem { } class ManageTrustedExtensionsForAccountActionImpl { + private readonly _viewDetailsButton = { + tooltip: localize('viewExtensionDetails', "View extension details"), + iconClass: ThemeIcon.asClassName(Codicon.eye), + }; + + private readonly _managePreferencesButton = { + tooltip: localize('accountPreferences', "Manage account preferences for this extension"), + iconClass: ThemeIcon.asClassName(Codicon.settingsGear), + }; + constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IDialogService private readonly _dialogService: IDialogService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService, - @ICommandService private readonly _commandService: ICommandService + @ICommandService private readonly _commandService: ICommandService, + @IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService ) { } async run(options?: { providerId: string; accountLabel: string }) { @@ -139,10 +151,11 @@ class ManageTrustedExtensionsForAccountActionImpl { const otherExtensions = filteredExtensions.filter(e => !e.trusted); const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); + const _toQuickPickItem = this._toQuickPickItem.bind(this); return [ - ...otherExtensions.sort(sortByLastUsed).map(this._toQuickPickItem), + ...otherExtensions.sort(sortByLastUsed).map(_toQuickPickItem), { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") } satisfies IQuickPickSeparator, - ...trustedExtensions.sort(sortByLastUsed).map(this._toQuickPickItem) + ...trustedExtensions.sort(sortByLastUsed).map(_toQuickPickItem) ]; } @@ -163,10 +176,7 @@ class ManageTrustedExtensionsForAccountActionImpl { description, tooltip, disabled, - buttons: [{ - tooltip: localize('accountPreferences', "Manage account preferences for this extension"), - iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - }], + buttons: [this._viewDetailsButton, this._managePreferencesButton], picked: extension.allowed === undefined || extension.allowed }; } @@ -198,9 +208,13 @@ class ManageTrustedExtensionsForAccountActionImpl { disposableStore.add(quickPick.onDidHide(() => disposableStore.dispose())); disposableStore.add(quickPick.onDidCustom(() => quickPick.hide())); - disposableStore.add(quickPick.onDidTriggerItemButton(e => - this._commandService.executeCommand('_manageAccountPreferencesForExtension', e.item.extension.id, accountQuery.providerId) - )); + disposableStore.add(quickPick.onDidTriggerItemButton(e => { + if (e.button === this._managePreferencesButton) { + this._commandService.executeCommand('_manageAccountPreferencesForExtension', e.item.extension.id, accountQuery.providerId); + } else if (e.button === this._viewDetailsButton) { + this._extensionsWorkbenchService.open(e.item.extension.id); + } + })); return quickPick; } From d118d57ec0c40762fbe834de1948a1f47ad5ebd5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Oct 2025 21:25:26 +0200 Subject: [PATCH 1029/4355] prompts - cleanup (#270622) --- .github/prompts/implement.prompt.md | 2 +- .github/prompts/plan.prompt.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/implement.prompt.md index adae22dd715..35b57c1a45d 100644 --- a/.github/prompts/implement.prompt.md +++ b/.github/prompts/implement.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Implement the plan' -tools: ['edit', 'notebooks', 'search', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests'] +tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] --- Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally. diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index 74d843a13ab..9756bba5661 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Start planning' -tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos', 'github/get_issue', 'github/get_issue_comments', 'github/get_me'] +tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'github/get_issue', 'github/get_issue_comments', 'github/get_me', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos'] --- Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: * Understand the context of the bug or feature by reading the issue description and comments. From e0b35bf4fcfc7b8a650b7a1b1509dcb8b2116058 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Oct 2025 21:26:53 +0200 Subject: [PATCH 1030/4355] chat - make the plus icon a dropdown for more options in the view (#270621) --- .../browser/menuEntryActionViewItem.ts | 29 ++++++++++-------- src/vs/platform/actions/common/actions.ts | 19 +++++++++++- .../chat/browser/actions/chatActions.ts | 18 +++++++---- .../chat/browser/actions/chatClearActions.ts | 30 +++++++++++++------ .../debug/browser/debug.contribution.ts | 2 +- .../browser/controller/layoutActions.ts | 1 - 6 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 316a4d9b9dd..32695b91c24 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -425,7 +425,7 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { export interface IDropdownWithDefaultActionViewItemOptions extends IDropdownMenuActionViewItemOptions { renderKeybindingWithDefaultActionLabel?: boolean; - persistLastActionId?: boolean; + togglePrimaryAction?: boolean; } export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { @@ -456,7 +456,7 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { // determine default action let defaultAction: IAction | undefined; - const defaultActionId = options?.persistLastActionId ? _storageService.get(this._storageKey, StorageScope.WORKSPACE) : undefined; + const defaultActionId = options?.togglePrimaryAction ? _storageService.get(this._storageKey, StorageScope.WORKSPACE) : undefined; if (defaultActionId) { defaultAction = submenuAction.actions.find(a => defaultActionId === a.id); } @@ -475,15 +475,17 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { }; this._dropdown = this._register(new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions)); - this._register(this._dropdown.actionRunner.onDidRun((e: IRunEvent) => { - if (e.action instanceof MenuItemAction) { - this.update(e.action); - } - })); + if (options?.togglePrimaryAction) { + this._register(this._dropdown.actionRunner.onDidRun((e: IRunEvent) => { + if (e.action instanceof MenuItemAction) { + this.update(e.action); + } + })); + } } private update(lastAction: MenuItemAction): void { - if (this._options?.persistLastActionId) { + if (this._options?.togglePrimaryAction) { this._storageService.store(this._storageKey, lastAction.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); } @@ -609,12 +611,13 @@ export function createActionViewItem(instaService: IInstantiationService, action } else if (action instanceof SubmenuItemAction) { if (action.item.isSelection) { return instaService.createInstance(SubmenuEntrySelectActionViewItem, action); + } else if (action.item.isSplitButton) { + return instaService.createInstance(DropdownWithDefaultActionViewItem, action, { + ...options, + togglePrimaryAction: typeof action.item.isSplitButton !== 'boolean' ? action.item.isSplitButton.togglePrimaryAction : false, + }); } else { - if (action.item.rememberDefaultAction) { - return instaService.createInstance(DropdownWithDefaultActionViewItem, action, { ...options, persistLastActionId: true }); - } else { - return instaService.createInstance(SubmenuEntryActionViewItem, action, options); - } + return instaService.createInstance(SubmenuEntryActionViewItem, action, options); } } else { return undefined; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e6c137fb587..604bd28c3e1 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -35,7 +35,23 @@ export interface ISubmenuItem { group?: 'navigation' | string; order?: number; isSelection?: boolean; - rememberDefaultAction?: boolean; // for dropdown menu: if true the last executed action is remembered as the default action + /** + * A split button shows the first action + * as primary action and the rest of the + * actions in a dropdown. + * + * Use `togglePrimaryAction` to promote + * the action that was last used to be + * the primary action and remember that + * choice. + */ + isSplitButton?: boolean | { + /** + * Will update the primary action based + * on the action that was last run. + */ + togglePrimaryAction: true; + }; } export function isIMenuItem(item: any): item is IMenuItem { @@ -250,6 +266,7 @@ export class MenuId { static readonly ChatInlineSymbolAnchorContext = new MenuId('ChatInlineSymbolAnchorContext'); static readonly ChatMessageCheckpoint: MenuId = new MenuId('ChatMessageCheckpoint'); static readonly ChatMessageRestoreCheckpoint: MenuId = new MenuId('ChatMessageRestoreCheckpoint'); + static readonly ChatNewMenu = new MenuId('ChatNewMenu'); static readonly ChatEditingCodeBlockContext = new MenuId('ChatEditingCodeBlockContext'); static readonly ChatTitleBarMenu = new MenuId('ChatTitleBarMenu'); static readonly ChatAttachmentsContext = new MenuId('ChatAttachmentsContext'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d6194928afb..6d605472d43 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1062,15 +1062,19 @@ export function registerChatActions() { category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 1, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyN, when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatEditor) }, - menu: { + menu: [{ id: MenuId.ChatTitleBarMenu, group: 'b_new', order: 0 - } + }, { + id: MenuId.ChatNewMenu, + group: '2_new', + order: 2 + }], }); } @@ -1088,11 +1092,15 @@ export function registerChatActions() { f1: true, category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, - menu: { + menu: [{ id: MenuId.ChatTitleBarMenu, group: 'b_new', order: 1 - } + }, { + id: MenuId.ChatNewMenu, + group: '2_new', + order: 3 + }] }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 19b12e18f3d..d09a0189464 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; import { localize2 } from '../../../../../nls.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; @@ -36,13 +36,27 @@ export interface INewEditSessionActionContext { agentMode?: boolean; /** - * Whether the inputValue is partial and should wait for further user input. If false or not set, the prompt is sent immediately. + * Whether the inputValue is partial and should wait for further user input. + * If false or not set, the prompt is sent immediately. */ isPartialQuery?: boolean; } export function registerNewChatActions() { - // This action was previously used for the editor gutter toolbar, but now ACTION_ID_NEW_CHAT is also used for that scenario + + // Add "New Chat" submenu to Chat view menu + MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + submenu: MenuId.ChatNewMenu, + title: localize2('chat.newEdits.label', "New Chat"), + icon: Codicon.plus, + when: ContextKeyExpr.equals('view', ChatViewId), + group: 'navigation', + order: -1, + isSplitButton: true + }); + + // This action was previously used for the editor gutter toolbar, but now + // ACTION_ID_NEW_CHAT is also used for that scenario registerAction2(class NewChatEditorAction extends Action2 { constructor() { super({ @@ -74,15 +88,13 @@ export function registerNewChatActions() { group: 'z_clear' }, { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', ChatViewId), - group: 'navigation', - order: -1, + id: MenuId.ChatNewMenu, + group: '1_open', + order: 1, alt: { id: ACTION_ID_OPEN_CHAT, title: localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.newFile, - precondition: ChatContextKeys.enabled + icon: Codicon.newFile } }, ...[MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].map(id => ({ diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 5d15a927c79..5967e7595e2 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -253,7 +253,7 @@ if (isMacintosh) { // Editor Title Menu's "Run/Debug" dropdown item -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, rememberDefaultAction: true, title: nls.localize2('run', "Run or Debug..."), icon: icons.debugRun, group: 'navigation', order: -1 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, isSplitButton: { togglePrimaryAction: true }, title: nls.localize2('run', "Run or Debug..."), icon: icons.debugRun, group: 'navigation', order: -1 }); // Debug menu diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 235545bf8d1..e1bc6e74511 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -105,7 +105,6 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 { MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.NotebookEditorLayoutConfigure, - rememberDefaultAction: false, title: localize2('customizeNotebook', "Customize Notebook..."), icon: Codicon.gear, group: 'navigation', From efdc6b10425846db33dddcc0a4a70d583127a9ac Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:02:28 -0400 Subject: [PATCH 1031/4355] Add package.json contribution for terminal completion provider descriptions (#270597) --- extensions/terminal-suggest/package.json | 10 +++++- src/vs/platform/terminal/common/terminal.ts | 6 ++++ .../contrib/terminal/common/terminal.ts | 24 +++++++++++++ .../common/terminalExtensionPoints.ts | 23 +++++++++++- .../terminalProfileService.integrationTest.ts | 3 ++ .../browser/terminal.suggest.contribution.ts | 35 ++++++++++++++++--- .../common/terminalSuggestConfiguration.ts | 19 ++++++---- .../terminalSuggestConfiguration.test.ts | 7 +++- 8 files changed, 112 insertions(+), 15 deletions(-) diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index 6e6fb77c146..c99bd03e54f 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -24,7 +24,15 @@ "category": "Terminal", "title": "%terminal.integrated.suggest.clearCachedGlobals%" } - ] + ], + "terminal": { + "completionProviders": [ + { + "id": "terminal-suggest", + "description": "Provides completions for terminal commands, arguments, flags, and file paths based upon the Fig spec." + } + ] + } }, "scripts": { "compile": "npx gulp compile-extension:terminal-suggest", diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index c272a8ff496..9aa86595e56 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -973,8 +973,14 @@ export interface IDecorationAddon { registerMenuItems(command: ITerminalCommand, items: IAction[]): IDisposable; } +export interface ITerminalCompletionProviderContribution { + id: string; + description?: string; +} + export interface ITerminalContributions { profiles?: ITerminalProfileContribution[]; + completionProviders?: ITerminalCompletionProviderContribution[]; } export const enum ShellIntegrationStatus { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index bb315cedd2f..69d040320cf 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -688,6 +688,30 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor(terminalContributionsDescriptor); +export interface IExtensionTerminalCompletionProvider extends ITerminalCompletionProviderContribution { + extensionIdentifier: string; +} + export interface ITerminalContributionService { readonly _serviceBrand: undefined; readonly terminalProfiles: ReadonlyArray; + readonly terminalCompletionProviders: ReadonlyArray; + readonly onDidChangeTerminalCompletionProviders: Event; } export const ITerminalContributionService = createDecorator('terminalContributionsService'); @@ -26,6 +33,12 @@ export class TerminalContributionService implements ITerminalContributionService private _terminalProfiles: ReadonlyArray = []; get terminalProfiles() { return this._terminalProfiles; } + private _terminalCompletionProviders: ReadonlyArray = []; + get terminalCompletionProviders() { return this._terminalCompletionProviders; } + + private readonly _onDidChangeTerminalCompletionProviders = new Emitter(); + readonly onDidChangeTerminalCompletionProviders = this._onDidChangeTerminalCompletionProviders.event; + constructor() { terminalsExtPoint.setHandler(contributions => { this._terminalProfiles = contributions.map(c => { @@ -33,6 +46,14 @@ export class TerminalContributionService implements ITerminalContributionService return { ...e, extensionIdentifier: c.description.identifier.value }; }) || []; }).flat(); + + this._terminalCompletionProviders = contributions.map(c => { + return c.value?.completionProviders?.map(p => { + return { ...p, extensionIdentifier: c.description.identifier.value }; + }) || []; + }).flat(); + + this._onDidChangeTerminalCompletionProviders.fire(); }); } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts index 4aabb18be40..78af0742bd8 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts @@ -89,6 +89,9 @@ class TestTerminalExtensionService extends TestExtensionService { class TestTerminalContributionService implements ITerminalContributionService { _serviceBrand: undefined; terminalProfiles: readonly IExtensionTerminalProfile[] = []; + terminalCompletionProviders: readonly import('../../common/terminalExtensionPoints.js').IExtensionTerminalCompletionProvider[] = []; + private _onDidChangeTerminalCompletionProviders = new Emitter(); + readonly onDidChangeTerminalCompletionProviders = this._onDidChangeTerminalCompletionProviders.event; setProfiles(profiles: IExtensionTerminalProfile[]): void { this.terminalProfiles = profiles; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 98fcf529db2..3a4ff1bbe08 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -21,8 +21,9 @@ import { registerActiveInstanceAction, registerTerminalAction } from '../../../t import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalSuggestCommandId } from '../common/terminal.suggest.js'; -import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration } from '../common/terminalSuggestConfiguration.js'; +import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration, type ITerminalSuggestProviderInfo } from '../common/terminalSuggestConfiguration.js'; import { ITerminalCompletionService, TerminalCompletionService } from './terminalCompletionService.js'; +import { ITerminalContributionService } from '../../../terminal/common/terminalExtensionPoints.js'; import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; import { SuggestAddon } from './terminalSuggestAddon.js'; import { TerminalClipboardContribution } from '../../clipboard/browser/terminal.clipboard.contribution.js'; @@ -468,20 +469,44 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { } constructor( - @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService + @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, + @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService ) { super(); this._register(this._terminalCompletionService.onDidChangeProviders(() => { this._updateConfiguration(); })); + this._register(this._terminalContributionService.onDidChangeTerminalCompletionProviders(() => { + this._updateConfiguration(); + })); // Initial configuration this._updateConfiguration(); } private _updateConfiguration(): void { - const providers = Array.from(this._terminalCompletionService.providers); - const providerIds = providers.map(p => p.id).filter((id): id is string => typeof id === 'string'); - registerTerminalSuggestProvidersConfiguration(providerIds); + const providerInfos: ITerminalSuggestProviderInfo[] = []; + + // Add statically declared providers from package.json contributions + for (const contributedProvider of this._terminalContributionService.terminalCompletionProviders) { + providerInfos.push({ + id: contributedProvider.id, + description: contributedProvider.description + }); + } + + // Add dynamically registered providers (that aren't already declared statically) + const staticProviderIds = new Set(providerInfos.map(p => p.id)); + const dynamicProviders = Array.from(this._terminalCompletionService.providers); + for (const provider of dynamicProviders) { + if (provider.id && !staticProviderIds.has(provider.id)) { + providerInfos.push({ + id: provider.id, + description: undefined + }); + } + } + + registerTerminalSuggestProvidersConfiguration(providerInfos); } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 6ad53d9a8a7..d23f9691546 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -188,9 +188,14 @@ export const terminalSuggestConfiguration: IStringDictionary(ConfigurationExtensions.Configuration); const oldProvidersConfiguration = terminalSuggestProvidersConfiguration; @@ -203,21 +208,21 @@ export function registerTerminalSuggestProvidersConfiguration(availableProviders }; providersProperties[lspProviderId] ??= { type: 'boolean', - description: localize('suggest.provider.lsp.description', "Enable or disable the LSP-based provider. This enables language server protocol-specific argument completion."), + description: localize('suggest.provider.lsp.description', "Provides completions for language server protocol-specific arguments."), default: product.quality !== 'stable', }; if (availableProviders) { - for (const providerId of availableProviders) { - if (providerId in defaultValue) { + for (const provider of availableProviders) { + if (provider.id in defaultValue) { continue; } - providersProperties[providerId] = { + providersProperties[provider.id] = { type: 'boolean', - description: localize('suggest.provider.description', "Whether to enable this provider."), + description: provider.description || localize('suggest.provider.description', "Whether to enable this provider."), default: true }; - defaultValue[providerId] = true; + defaultValue[provider.id] = true; } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts index ba7bba7c856..a413045b90d 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts @@ -15,7 +15,12 @@ suite('Terminal Suggest Dynamic Configuration', () => { registerTerminalSuggestProvidersConfiguration([]); // Test with some providers - const providers = ['terminal-suggest', 'builtinPwsh', 'lsp', 'custom-provider']; + const providers = [ + { id: 'terminal-suggest', description: 'Provides intelligent completions for terminal commands' }, + { id: 'builtinPwsh', description: 'PowerShell completion provider' }, + { id: 'lsp' }, + { id: 'custom-provider' } + ]; registerTerminalSuggestProvidersConfiguration(providers); // Test with empty providers From 245265d15e6ac69a5750576820259ea41f8789d9 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:59:00 -0700 Subject: [PATCH 1032/4355] Mark most `Event` properties as `readonly` We want to prevent mistaken changes that do something like this: ```ts foo.onEvent = () => { ... }; ``` When they almost always mean: ```ts foo.onEvent(() => { ... }) ``` --- src/vs/base/browser/history.ts | 4 +- src/vs/base/browser/ui/list/listWidget.ts | 2 +- src/vs/base/browser/ui/tree/tree.ts | 2 +- src/vs/base/common/history.ts | 2 +- src/vs/base/common/worker/webWorker.ts | 4 +- src/vs/base/parts/ipc/common/ipc.ts | 4 +- src/vs/base/parts/ipc/test/common/ipc.test.ts | 2 +- .../base/parts/ipc/test/node/testService.ts | 4 +- src/vs/base/test/common/event.test.ts | 2 +- src/vs/editor/browser/editorBrowser.ts | 6 +- .../services/inlineCompletionsService.ts | 2 +- .../editorMarkdownCodeBlockRenderer.ts | 3 +- .../common/config/editorConfiguration.ts | 4 +- src/vs/editor/common/config/editorZoom.ts | 2 +- .../common/diff/documentDiffProvider.ts | 2 +- src/vs/editor/common/editorCommon.ts | 2 +- src/vs/editor/common/languages.ts | 22 +++--- src/vs/editor/common/languages/language.ts | 6 +- .../languageConfigurationRegistry.ts | 2 +- src/vs/editor/common/model.ts | 2 +- .../editor/common/model/decorationProvider.ts | 2 +- .../common/services/markerDecorations.ts | 2 +- .../services/textResourceConfiguration.ts | 2 +- src/vs/editor/common/textModelBracketPairs.ts | 2 +- .../inlineEdits/inlineEditsViewInterface.ts | 2 +- .../browser/stickyScrollController.ts | 2 +- .../browser/stickyScrollProvider.ts | 2 +- .../standalone/browser/standaloneServices.ts | 2 +- .../diff/testDiffProviderFactoryService.ts | 2 +- src/vs/monaco.d.ts | 10 +-- .../accessibility/browser/accessibleView.ts | 4 +- .../actions/browser/actionViewItemService.ts | 4 +- src/vs/platform/commands/common/commands.ts | 2 +- .../configuration/common/configuration.ts | 2 +- .../platform/contextkey/common/contextkey.ts | 2 +- src/vs/platform/dialogs/common/dialogs.ts | 4 +- .../common/extensionManagementIpc.ts | 10 +-- .../test/common/instantiationService.test.ts | 12 +-- .../platform/keybinding/common/keybinding.ts | 2 +- src/vs/platform/label/common/label.ts | 2 +- src/vs/platform/log/common/log.ts | 2 +- .../platform/quickinput/browser/quickInput.ts | 6 +- .../quickinput/browser/quickInputList.ts | 8 +- .../quickinput/browser/tree/quickTree.ts | 2 +- .../platform/quickinput/common/quickInput.ts | 2 +- src/vs/platform/secrets/common/secrets.ts | 4 +- .../common/capabilities/capabilities.ts | 2 +- src/vs/platform/terminal/common/terminal.ts | 20 ++--- src/vs/platform/terminal/node/ptyHost.ts | 4 +- src/vs/platform/tunnel/common/tunnel.ts | 4 +- .../webview/common/webviewManagerService.ts | 2 +- src/vs/platform/windows/node/windowTracker.ts | 4 +- .../test/electron-main/windowsFinder.test.ts | 14 ++-- .../workspace/common/workspaceTrust.ts | 4 +- .../api/browser/mainThreadLanguageModels.ts | 2 +- .../api/browser/statusBarExtensionPoint.ts | 2 +- .../api/common/extHostDebugService.ts | 12 +-- .../api/common/extHostExtensionService.ts | 2 +- .../workbench/api/common/extHostNotebook.ts | 4 +- src/vs/workbench/api/common/extHostTask.ts | 12 +-- .../browser/extHostMessagerService.test.ts | 4 +- src/vs/workbench/browser/web.api.ts | 2 +- src/vs/workbench/common/views.ts | 8 +- .../chatConfirmationWidget.ts | 2 +- .../browser/viewsWelcome/chatViewsWelcome.ts | 2 +- .../contrib/chat/common/chatModes.ts | 2 +- .../contrib/chat/common/chatService.ts | 8 +- .../chat/common/languageModelToolsService.ts | 2 +- .../contrib/chat/common/languageModels.ts | 4 +- .../chat/test/common/mockChatService.ts | 6 +- .../common/mockLanguageModelToolsService.ts | 2 +- .../test/browser/commentsView.test.ts | 16 ++-- .../debug/browser/debugAdapterManager.ts | 2 +- .../workbench/contrib/debug/common/debug.ts | 36 ++++----- .../extensions/browser/extensionsViews.ts | 2 +- .../contrib/extensions/common/extensions.ts | 4 +- .../browser/inlineChatSessionService.ts | 10 +-- .../browser/interactiveDocumentService.ts | 4 +- .../contrib/mcp/browser/mcpServersView.ts | 2 +- .../browser/diff/notebookDiffEditorBrowser.ts | 10 +-- .../browser/diff/unchangedEditorRegions.ts | 2 +- .../notebook/browser/notebookBrowser.ts | 6 +- .../browser/services/notebookEditorService.ts | 4 +- .../browser/services/notebookServiceImpl.ts | 2 +- .../notebook/browser/view/notebookCellList.ts | 2 +- .../browser/view/notebookRenderingCommon.ts | 10 +-- .../browser/view/renderers/webviewPreloads.ts | 2 +- .../browser/viewModel/baseCellViewModel.ts | 2 +- .../viewModel/notebookViewModelImpl.ts | 2 +- .../viewParts/notebookEditorToolbar.ts | 2 +- .../contrib/notebook/common/notebookCommon.ts | 18 ++--- .../common/notebookExecutionStateService.ts | 4 +- .../notebookRendererMessagingService.ts | 2 +- .../contrib/notebookCellDiagnostics.test.ts | 2 +- .../preferences/browser/keybindingWidgets.ts | 2 +- .../contrib/remote/browser/remote.ts | 2 +- .../search/common/searchHistoryService.ts | 2 +- .../tasks/common/taskDefinitionRegistry.ts | 2 +- .../contrib/tasks/common/taskService.ts | 10 +-- .../contrib/tasks/common/taskSystem.ts | 2 +- .../contrib/terminal/browser/terminal.ts | 54 +++++++------- .../terminal/common/environmentVariable.ts | 2 +- .../contrib/terminal/common/terminal.ts | 16 ++-- .../test/browser/terminalInstance.test.ts | 6 +- .../browser/terminalProcessManager.test.ts | 6 +- .../executeStrategy/executeStrategy.ts | 2 +- .../quickFix/browser/quickFix.ts | 6 +- .../browser/explorerProjections/index.ts | 2 +- .../testResultsView/testResultsOutput.ts | 2 +- .../testResultsView/testResultsTree.ts | 6 +- .../contrib/testing/common/observableValue.ts | 2 +- .../contrib/testing/common/testService.ts | 2 +- .../common/testingContinuousRunService.ts | 2 +- .../testing/common/testingDecorations.ts | 2 +- .../contrib/timeline/common/timeline.ts | 8 +- .../url/browser/trustedDomainService.ts | 4 +- .../test/browser/mockTrustedDomainService.ts | 2 +- .../browser/authenticationMcpService.ts | 2 +- .../authentication/common/authentication.ts | 2 +- .../configurationResolverService.test.ts | 2 +- .../decorations/browser/decorationsService.ts | 2 +- .../test/browser/decorationsService.test.ts | 2 +- .../common/extensionRecommendations.ts | 4 +- .../common/workspaceExtensionsConfig.ts | 2 +- .../services/extensions/common/extensions.ts | 22 +++--- .../common/inlineCompletionsUnification.ts | 2 +- .../label/test/common/mockLabelService.ts | 2 +- .../common/languageStatusService.ts | 2 +- .../services/outline/browser/outline.ts | 2 +- .../services/output/common/output.ts | 2 +- .../remote/common/remoteExplorerService.ts | 8 +- .../services/search/node/rawSearchService.ts | 2 +- .../common/embedderTerminalService.ts | 6 +- .../themes/common/workbenchThemeService.ts | 6 +- .../common/userActivityService.ts | 2 +- .../workingCopy/common/workingCopyHistory.ts | 12 +-- .../test/browser/workbenchTestServices.ts | 74 +++++++++---------- .../test/common/workbenchTestServices.ts | 6 +- .../electron-browser/workbenchTestServices.ts | 18 ++--- ...ode.proposed.chatParticipantAdditions.d.ts | 4 +- .../vscode.proposed.dataChannels.d.ts | 2 +- ...e.proposed.inlineCompletionsAdditions.d.ts | 2 +- .../vscode.proposed.notebookKernelSource.d.ts | 2 +- ...ode.proposed.notebookVariableProvider.d.ts | 2 +- src/vscode-dts/vscode.proposed.resolvers.d.ts | 8 +- .../vscode.proposed.scmHistoryProvider.d.ts | 4 +- src/vscode-dts/vscode.proposed.timeline.d.ts | 2 +- src/vscode-dts/vscode.proposed.tunnels.d.ts | 2 +- 148 files changed, 402 insertions(+), 403 deletions(-) diff --git a/src/vs/base/browser/history.ts b/src/vs/base/browser/history.ts index c5e9d036604..a7e8972ea2e 100644 --- a/src/vs/base/browser/history.ts +++ b/src/vs/base/browser/history.ts @@ -13,8 +13,8 @@ export interface IHistoryNavigationWidget { showNextValue(): void; - onDidFocus: Event; + readonly onDidFocus: Event; - onDidBlur: Event; + readonly onDidBlur: Event; } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index fcad5f33c17..f4e96e204e2 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -859,7 +859,7 @@ export interface IListAccessibilityProvider extends IListViewAccessibilityPro getWidgetAriaLabel(): string | IObservable; getWidgetRole?(): AriaRole; getAriaLevel?(element: T): number | undefined; - onDidChangeActiveDescendant?: Event; + readonly onDidChangeActiveDescendant?: Event; getActiveDescendantId?(element: T): string | undefined; } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 2ef6d7ceab2..a3474b3dfa3 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -167,7 +167,7 @@ export interface ITreeRenderer exte renderElement(element: ITreeNode, index: number, templateData: TTemplateData, details?: ITreeElementRenderDetails): void; disposeElement?(element: ITreeNode, index: number, templateData: TTemplateData, details?: ITreeElementRenderDetails): void; renderTwistie?(element: T, twistieElement: HTMLElement): boolean; - onDidChangeTwistieState?: Event; + readonly onDidChangeTwistieState?: Event; } export interface ITreeEvent { diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 3c23e8e3a29..1e34d03e159 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -15,7 +15,7 @@ export interface IHistory { clear(): void; forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void; replace?(t: T[]): void; - onDidChange?: Event; + readonly onDidChange?: Event; } export class HistoryNavigator implements INavigator { diff --git a/src/vs/base/common/worker/webWorker.ts b/src/vs/base/common/worker/webWorker.ts index 24626610752..46856d37583 100644 --- a/src/vs/base/common/worker/webWorker.ts +++ b/src/vs/base/common/worker/webWorker.ts @@ -15,8 +15,8 @@ const INITIALIZE = '$initialize'; export interface IWebWorker extends IDisposable { getId(): number; - onMessage: Event; - onError: Event; + readonly onMessage: Event; + readonly onError: Event; postMessage(message: Message, transfer: ArrayBuffer[]): void; } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 093726c2b74..1bdee44e9af 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -98,7 +98,7 @@ interface IHandler { export interface IMessagePassingProtocol { send(buffer: VSBuffer): void; - onMessage: Event; + readonly onMessage: Event; /** * Wait for the write buffer (if applicable) to become empty. */ @@ -784,7 +784,7 @@ export class ChannelClient implements IChannelClient, IDisposable { export interface ClientConnectionEvent { protocol: IMessagePassingProtocol; - onDidClientDisconnect: Event; + readonly onDidClientDisconnect: Event; } interface Connection extends Client { diff --git a/src/vs/base/parts/ipc/test/common/ipc.test.ts b/src/vs/base/parts/ipc/test/common/ipc.test.ts index 1d5029273d8..8e937c51f34 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -108,7 +108,7 @@ interface ITestService { marshall(uri: URI): Promise; context(): Promise; - onPong: Event; + readonly onPong: Event; } class TestService implements ITestService { diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index 99b01eee2d6..099c4ea6ab9 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -12,7 +12,7 @@ export interface IMarcoPoloEvent { } export interface ITestService { - onMarco: Event; + readonly onMarco: Event; marco(): Promise; pong(ping: string): Promise<{ incoming: string; outgoing: string }>; cancelMe(): Promise; @@ -21,7 +21,7 @@ export interface ITestService { export class TestService implements ITestService { private readonly _onMarco = new Emitter(); - onMarco: Event = this._onMarco.event; + readonly onMarco: Event = this._onMarco.event; marco(): Promise { this._onMarco.fire({ answer: 'polo' }); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 63940c789ff..002bb687f41 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -34,7 +34,7 @@ namespace Samples { private readonly _onDidChange = new Emitter(); - onDidChange: Event = this._onDidChange.event; + readonly onDidChange: Event = this._onDidChange.event; setText(value: string) { //... diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index df4e55ca1c6..c2cb0d3d596 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -281,7 +281,7 @@ export interface IOverlayWidget { /** * Event fired when the widget layout changes. */ - onDidLayout?: Event; + readonly onDidLayout?: Event; /** * Render this overlay widget in a location where it could overflow the editor's view dom node. */ @@ -898,14 +898,14 @@ export interface ICodeEditor extends editorCommon.IEditor { * @internal * @event */ - onDidChangeLineHeight: Event; + readonly onDidChangeLineHeight: Event; /** * An event emitted when the font of the editor has changed. * @internal * @event */ - onDidChangeFont: Event; + readonly onDidChangeFont: Event; /** * Get value of the current model attached to this editor. diff --git a/src/vs/editor/browser/services/inlineCompletionsService.ts b/src/vs/editor/browser/services/inlineCompletionsService.ts index 1d5eed4fbdc..260d617e7af 100644 --- a/src/vs/editor/browser/services/inlineCompletionsService.ts +++ b/src/vs/editor/browser/services/inlineCompletionsService.ts @@ -21,7 +21,7 @@ export const IInlineCompletionsService = createDecorator; + readonly onDidChangeIsSnoozing: Event; /** * Get the remaining time (in ms) for which inline completions should be snoozed, diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts index a07f4f0354e..9ba8e0ceee4 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.ts @@ -35,8 +35,7 @@ export class EditorMarkdownCodeBlockRenderer implements IMarkdownCodeBlockRender public async renderCodeBlock(languageAlias: string | undefined, value: string, options: IMarkdownRendererExtraOptions): Promise { const editor = isCodeEditor(options.context) ? options.context : undefined; - // In markdown, - // it is possible that we stumble upon language aliases (e.g.js instead of javascript) + // In markdown, it is possible that we stumble upon language aliases (e.g.js instead of javascript). // it is possible no alias is given in which case we fall back to the current editor lang let languageId: string | undefined | null; if (languageAlias) { diff --git a/src/vs/editor/common/config/editorConfiguration.ts b/src/vs/editor/common/config/editorConfiguration.ts index 494440bc5aa..007afcf8b35 100644 --- a/src/vs/editor/common/config/editorConfiguration.ts +++ b/src/vs/editor/common/config/editorConfiguration.ts @@ -25,11 +25,11 @@ export interface IEditorConfiguration extends IDisposable { /** * The `options` have changed (quick event) */ - onDidChangeFast: Event; + readonly onDidChangeFast: Event; /** * The `options` have changed (slow event) */ - onDidChange: Event; + readonly onDidChange: Event; /** * Get the raw options as they were passed in to the editor * and merged with all calls to `updateOptions`. diff --git a/src/vs/editor/common/config/editorZoom.ts b/src/vs/editor/common/config/editorZoom.ts index f7ee332e52c..03d4276e2f6 100644 --- a/src/vs/editor/common/config/editorZoom.ts +++ b/src/vs/editor/common/config/editorZoom.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; export interface IEditorZoom { - onDidChangeZoomLevel: Event; + readonly onDidChangeZoomLevel: Event; getZoomLevel(): number; setZoomLevel(zoomLevel: number): void; } diff --git a/src/vs/editor/common/diff/documentDiffProvider.ts b/src/vs/editor/common/diff/documentDiffProvider.ts index 6f6f06a9d73..3e9c82fd55e 100644 --- a/src/vs/editor/common/diff/documentDiffProvider.ts +++ b/src/vs/editor/common/diff/documentDiffProvider.ts @@ -23,7 +23,7 @@ export interface IDocumentDiffProvider { * Is fired when settings of the diff algorithm change that could alter the result of the diffing computation. * Any user of this provider should recompute the diff when this event is fired. */ - onDidChange: Event; + readonly onDidChange: Event; } /** diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index dbfa4aacad5..df56a1beab5 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -546,7 +546,7 @@ export interface IEditorDecorationsCollection { * An event emitted when decorations change in the editor, * but the change is not caused by us setting or clearing the collection. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Get the decorations count. */ diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 6ca9a13c230..4fdbf3ba67d 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -2143,7 +2143,7 @@ export interface CommentWidget { commentThread: CommentThread; comment?: Comment; input: string; - onDidChangeInput: Event; + readonly onDidChangeInput: Event; } /** @@ -2173,19 +2173,19 @@ export interface CommentThread { label: string | undefined; contextValue: string | undefined; comments: ReadonlyArray | undefined; - onDidChangeComments: Event; + readonly onDidChangeComments: Event; collapsibleState?: CommentThreadCollapsibleState; initialCollapsibleState?: CommentThreadCollapsibleState; - onDidChangeInitialCollapsibleState: Event; + readonly onDidChangeInitialCollapsibleState: Event; state?: CommentThreadState; applicability?: CommentThreadApplicability; canReply: boolean | CommentAuthorInformation; input?: CommentInput; - onDidChangeInput: Event; - onDidChangeLabel: Event; - onDidChangeCollapsibleState: Event; - onDidChangeState: Event; - onDidChangeCanReply: Event; + readonly onDidChangeInput: Event; + readonly onDidChangeLabel: Event; + readonly onDidChangeCollapsibleState: Event; + readonly onDidChangeState: Event; + readonly onDidChangeCanReply: Event; isDisposed: boolean; isTemplate: boolean; } @@ -2384,14 +2384,14 @@ export interface SemanticTokensEdits { } export interface DocumentSemanticTokensProvider { - onDidChange?: Event; + readonly onDidChange?: Event; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: model.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; } export interface DocumentRangeSemanticTokensProvider { - onDidChange?: Event; + readonly onDidChange?: Event; getLegend(): SemanticTokensLegend; provideDocumentRangeSemanticTokens(model: model.ITextModel, range: Range, token: CancellationToken): ProviderResult; } @@ -2448,7 +2448,7 @@ export interface ITokenizationRegistry { * - a tokenization support is registered, unregistered or changed. * - the color map is changed. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Fire a change event for a language. diff --git a/src/vs/editor/common/languages/language.ts b/src/vs/editor/common/languages/language.ts index 065ab092af2..79e7d7bb3f7 100644 --- a/src/vs/editor/common/languages/language.ts +++ b/src/vs/editor/common/languages/language.ts @@ -57,7 +57,7 @@ export interface ILanguageService { * **Note**: Basic language features refers to language configuration related features. * **Note**: This event is a superset of `onDidRequestRichLanguageFeatures` */ - onDidRequestBasicLanguageFeatures: Event; + readonly onDidRequestBasicLanguageFeatures: Event; /** * An event emitted when rich language features are requested for the first time. @@ -66,12 +66,12 @@ export interface ILanguageService { * **Note**: Rich language features refers to tokenizers, language features based on providers, etc. * **Note**: This event is a subset of `onDidRequestRichLanguageFeatures` */ - onDidRequestRichLanguageFeatures: Event; + readonly onDidRequestRichLanguageFeatures: Event; /** * An event emitted when languages have changed. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Register a language. diff --git a/src/vs/editor/common/languages/languageConfigurationRegistry.ts b/src/vs/editor/common/languages/languageConfigurationRegistry.ts index a545b571a8e..0dffed073d4 100644 --- a/src/vs/editor/common/languages/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/languages/languageConfigurationRegistry.ts @@ -35,7 +35,7 @@ export interface ICommentsConfiguration { export interface ILanguageConfigurationService { readonly _serviceBrand: undefined; - onDidChange: Event; + readonly onDidChange: Event; /** * @param priority Use a higher number for higher priority diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index f125778ace5..28862df0b5d 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1503,7 +1503,7 @@ export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperati * `lineNumber` is 1 based. */ export interface IReadonlyTextBuffer { - onDidChangeContent: Event; + readonly onDidChangeContent: Event; equals(other: ITextBuffer): boolean; mightContainRTL(): boolean; mightContainUnusualLineTerminators(): boolean; diff --git a/src/vs/editor/common/model/decorationProvider.ts b/src/vs/editor/common/model/decorationProvider.ts index 0cb68740e2d..e3c146831de 100644 --- a/src/vs/editor/common/model/decorationProvider.ts +++ b/src/vs/editor/common/model/decorationProvider.ts @@ -25,5 +25,5 @@ export interface DecorationProvider { */ getAllDecorations(ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[]; - onDidChange: Event; + readonly onDidChange: Event; } diff --git a/src/vs/editor/common/services/markerDecorations.ts b/src/vs/editor/common/services/markerDecorations.ts index 66f1943b000..97325a68441 100644 --- a/src/vs/editor/common/services/markerDecorations.ts +++ b/src/vs/editor/common/services/markerDecorations.ts @@ -16,7 +16,7 @@ export const IMarkerDecorationsService = createDecorator; + readonly onDidChangeMarker: Event; getMarker(uri: URI, decoration: IModelDecoration): IMarker | null; diff --git a/src/vs/editor/common/services/textResourceConfiguration.ts b/src/vs/editor/common/services/textResourceConfiguration.ts index 70975287424..a10748168e6 100644 --- a/src/vs/editor/common/services/textResourceConfiguration.ts +++ b/src/vs/editor/common/services/textResourceConfiguration.ts @@ -36,7 +36,7 @@ export interface ITextResourceConfigurationService { /** * Event that fires when the configuration changes. */ - onDidChangeConfiguration: Event; + readonly onDidChangeConfiguration: Event; /** * Fetches the value of the section for the given resource by applying language overrides. diff --git a/src/vs/editor/common/textModelBracketPairs.ts b/src/vs/editor/common/textModelBracketPairs.ts index 03fc83cff1f..7122df4f532 100644 --- a/src/vs/editor/common/textModelBracketPairs.ts +++ b/src/vs/editor/common/textModelBracketPairs.ts @@ -14,7 +14,7 @@ export interface IBracketPairsTextModelPart { /** * Is fired when bracket pairs change, either due to a text or a settings change. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Gets all bracket pairs that intersect the given position. diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts index 78b67872f42..0f7f8cb25f2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts @@ -18,7 +18,7 @@ export enum InlineEditTabAction { export interface IInlineEditsView { isHovered: IObservable; minEditorScrollHeight?: IObservable; - onDidClick: Event; + readonly onDidClick: Event; } export interface IInlineEditHost { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 15047ea258e..85a58109537 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -44,7 +44,7 @@ export interface IStickyScrollController { findScrollWidgetState(): StickyScrollWidgetState; dispose(): void; selectEditor(): void; - onDidChangeStickyScrollHeight: Event<{ height: number }>; + readonly onDidChangeStickyScrollHeight: Event<{ height: number }>; } export class StickyScrollController extends Disposable implements IEditorContribution, IStickyScrollController { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts index 2a5fc270681..d204d662ae6 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts @@ -50,7 +50,7 @@ export interface IStickyLineCandidateProvider { /** * Event triggered when sticky scroll changes. */ - onDidChangeStickyScroll: Event; + readonly onDidChangeStickyScroll: Event; } export class StickyLineCandidateProvider extends Disposable implements IStickyLineCandidateProvider { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 12814316f23..f715f0ebae2 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -1009,7 +1009,7 @@ class StandaloneWorkspaceTrustManagementService implements IWorkspaceTrustManage private _neverEmitter = new Emitter(); public readonly onDidChangeTrust: Event = this._neverEmitter.event; - onDidChangeTrustedFolders: Event = this._neverEmitter.event; + readonly onDidChangeTrustedFolders: Event = this._neverEmitter.event; public readonly workspaceResolved = Promise.resolve(); public readonly workspaceTrustInitialized = Promise.resolve(); public readonly acceptsOutOfWorkspaceFiles = true; diff --git a/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts index 7d889f5012a..8fa1294404e 100644 --- a/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts +++ b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts @@ -29,5 +29,5 @@ class SyncDocumentDiffProvider implements IDocumentDiffProvider { }); } - onDidChange: Event = () => toDisposable(() => { }); + readonly onDidChange: Event = () => toDisposable(() => { }); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3e5743e3f20..6ecf342cb0b 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2880,7 +2880,7 @@ declare namespace monaco.editor { * An event emitted when decorations change in the editor, * but the change is not caused by us setting or clearing the collection. */ - onDidChange: IEvent; + readonly onDidChange: IEvent; /** * Get the decorations count. */ @@ -5687,7 +5687,7 @@ declare namespace monaco.editor { /** * Event fired when the widget layout changes. */ - onDidLayout?: IEvent; + readonly onDidLayout?: IEvent; /** * Render this overlay widget in a location where it could overflow the editor's view dom node. */ @@ -6505,7 +6505,7 @@ declare namespace monaco.editor { export const EditorZoom: IEditorZoom; export interface IEditorZoom { - onDidChangeZoomLevel: IEvent; + readonly onDidChangeZoomLevel: IEvent; getZoomLevel(): number; setZoomLevel(zoomLevel: number): void; } @@ -8492,14 +8492,14 @@ declare namespace monaco.languages { } export interface DocumentSemanticTokensProvider { - onDidChange?: IEvent; + readonly onDidChange?: IEvent; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: editor.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; } export interface DocumentRangeSemanticTokensProvider { - onDidChange?: IEvent; + readonly onDidChange?: IEvent; getLegend(): SemanticTokensLegend; provideDocumentRangeSemanticTokens(model: editor.ITextModel, range: Range, token: CancellationToken): ProviderResult; } diff --git a/src/vs/platform/accessibility/browser/accessibleView.ts b/src/vs/platform/accessibility/browser/accessibleView.ts index 1c8cb6a7df3..2846d16bd94 100644 --- a/src/vs/platform/accessibility/browser/accessibleView.ts +++ b/src/vs/platform/accessibility/browser/accessibleView.ts @@ -100,7 +100,7 @@ export interface IAccessibleViewContentProvider extends IBasicContentProvider, I /** * Note that this will only take effect if the provider has an ID. */ - onDidRequestClearLastProvider?: Event; + readonly onDidRequestClearLastProvider?: Event; } @@ -201,5 +201,5 @@ export interface IBasicContentProvider extends IDisposable { actions?: IAction[]; providePreviousContent?(): void; provideNextContent?(): void; - onDidChangeContent?: Event; + readonly onDidChangeContent?: Event; } diff --git a/src/vs/platform/actions/browser/actionViewItemService.ts b/src/vs/platform/actions/browser/actionViewItemService.ts index 19ffaa0afc2..bc598e830b7 100644 --- a/src/vs/platform/actions/browser/actionViewItemService.ts +++ b/src/vs/platform/actions/browser/actionViewItemService.ts @@ -24,7 +24,7 @@ export interface IActionViewItemService { _serviceBrand: undefined; - onDidChange: Event; + readonly onDidChange: Event; register(menu: MenuId, submenu: MenuId, provider: IActionViewItemFactory, event?: Event): IDisposable; register(menu: MenuId, commandId: string, provider: IActionViewItemFactory, event?: Event): IDisposable; @@ -36,7 +36,7 @@ export interface IActionViewItemService { export class NullActionViewItemService implements IActionViewItemService { _serviceBrand: undefined; - onDidChange: Event = Event.None; + readonly onDidChange: Event = Event.None; register(menu: MenuId, commandId: string | MenuId, provider: IActionViewItemFactory, event?: Event): IDisposable { return Disposable.None; diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index cb3546671b0..90848fb8388 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -58,7 +58,7 @@ export interface ICommandMetadata { } export interface ICommandRegistry { - onDidRegisterCommand: Event; + readonly onDidRegisterCommand: Event; registerCommand(id: string, command: ICommandHandler): IDisposable; registerCommand(command: ICommand): IDisposable; registerCommandAlias(oldId: string, newId: string): IDisposable; diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 5c62d9edde3..7ab5f84c82f 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -150,7 +150,7 @@ export interface IConfigurationUpdateOptions { export interface IConfigurationService { readonly _serviceBrand: undefined; - onDidChangeConfiguration: Event; + readonly onDidChangeConfiguration: Event; getConfigurationData(): IConfigurationData | null; diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 86f7286130f..d0e209de23c 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -2054,7 +2054,7 @@ export type IScopedContextKeyService = IContextKeyService & IDisposable; export interface IContextKeyService { readonly _serviceBrand: undefined; - onDidChangeContext: Event; + readonly onDidChangeContext: Event; bufferChangeEvents(callback: Function): void; createKey(key: string, defaultValue: T | undefined): IContextKey; diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 28cb5995ec5..f80914ca0b5 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -456,12 +456,12 @@ export interface IDialogService { /** * An event that fires when a dialog is about to show. */ - onWillShowDialog: Event; + readonly onWillShowDialog: Event; /** * An event that fires when a dialog did show (closed). */ - onDidShowDialog: Event; + readonly onDidShowDialog: Event; /** * Ask the user for confirmation with a modal dialog. diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index d395dcbf76e..1c69184f454 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -46,11 +46,11 @@ function transformOutgoingExtension(extension: ILocalExtension, transformer: IUR export class ExtensionManagementChannel implements IServerChannel { - onInstallExtension: Event; - onDidInstallExtensions: Event; - onUninstallExtension: Event; - onDidUninstallExtension: Event; - onDidUpdateExtensionMetadata: Event; + readonly onInstallExtension: Event; + readonly onDidInstallExtensions: Event; + readonly onUninstallExtension: Event; + readonly onDidUninstallExtension: Event; + readonly onDidUpdateExtensionMetadata: Event; constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) { this.onInstallExtension = Event.buffer(service.onInstallExtension, true); diff --git a/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/src/vs/platform/instantiation/test/common/instantiationService.test.ts index a4d07c1b2b6..3696ada44e8 100644 --- a/src/vs/platform/instantiation/test/common/instantiationService.test.ts +++ b/src/vs/platform/instantiation/test/common/instantiationService.test.ts @@ -467,7 +467,7 @@ suite('Instantiation Service', () => { const A = createDecorator('A'); interface A { _serviceBrand: undefined; - onDidDoIt: Event; + readonly onDidDoIt: Event; doIt(): void; } @@ -477,7 +477,7 @@ suite('Instantiation Service', () => { _doIt = 0; _onDidDoIt = new Emitter(); - onDidDoIt: Event = this._onDidDoIt.event; + readonly onDidDoIt: Event = this._onDidDoIt.event; constructor() { created = true; @@ -531,7 +531,7 @@ suite('Instantiation Service', () => { const A = createDecorator('A'); interface A { _serviceBrand: undefined; - onDidDoIt: Event; + readonly onDidDoIt: Event; doIt(): void; noop(): void; } @@ -542,7 +542,7 @@ suite('Instantiation Service', () => { _doIt = 0; _onDidDoIt = new Emitter(); - onDidDoIt: Event = this._onDidDoIt.event; + readonly onDidDoIt: Event = this._onDidDoIt.event; constructor() { created = true; @@ -599,7 +599,7 @@ suite('Instantiation Service', () => { const A = createDecorator('A'); interface A { _serviceBrand: undefined; - onDidDoIt: Event; + readonly onDidDoIt: Event; doIt(): void; } let created = false; @@ -608,7 +608,7 @@ suite('Instantiation Service', () => { _doIt = 0; _onDidDoIt = new Emitter(); - onDidDoIt: Event = this._onDidDoIt.event; + readonly onDidDoIt: Event = this._onDidDoIt.event; constructor() { created = true; diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 72f0d6c9640..401691890b2 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -45,7 +45,7 @@ export interface IKeybindingService { readonly inChordMode: boolean; - onDidUpdateKeybindings: Event; + readonly onDidUpdateKeybindings: Event; /** * Returns none, one or many (depending on keyboard layout)! diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index c8d520ae357..1da00285dc8 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -30,7 +30,7 @@ export interface ILabelService { getSeparator(scheme: string, authority?: string): '/' | '\\'; registerFormatter(formatter: ResourceLabelFormatter): IDisposable; - onDidChangeFormatters: Event; + readonly onDidChangeFormatters: Event; /** * Registers a formatter that's cached for the machine beyond the lifecycle diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 1124bc8f828..449bb439209 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -41,7 +41,7 @@ export enum LogLevel { export const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info; export interface ILogger extends IDisposable { - onDidChangeLogLevel: Event; + readonly onDidChangeLogLevel: Event; getLevel(): LogLevel; setLevel(level: LogLevel): void; diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 76e9a19cd1f..5c2e06fc968 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -121,9 +121,9 @@ export interface QuickInputUI { progressBar: ProgressBar; list: QuickInputList; tree: QuickInputTreeController; - onDidAccept: Event; - onDidCustom: Event; - onDidTriggerButton: Event; + readonly onDidAccept: Event; + readonly onDidCustom: Event; + readonly onDidTriggerButton: Event; ignoreFocusOut: boolean; keyMods: Writeable; show(controller: QuickInput): void; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index b2ca9949c46..8023f851035 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -674,16 +674,16 @@ export class QuickInputList extends Disposable { readonly onLeave: Event = this._onLeave.event; private readonly _visibleCountObservable = observableValue('VisibleCount', 0); - onChangedVisibleCount: Event = Event.fromObservable(this._visibleCountObservable, this._store); + readonly onChangedVisibleCount: Event = Event.fromObservable(this._visibleCountObservable, this._store); private readonly _allVisibleCheckedObservable = observableValue('AllVisibleChecked', false); - onChangedAllVisibleChecked: Event = Event.fromObservable(this._allVisibleCheckedObservable, this._store); + readonly onChangedAllVisibleChecked: Event = Event.fromObservable(this._allVisibleCheckedObservable, this._store); private readonly _checkedCountObservable = observableValue('CheckedCount', 0); - onChangedCheckedCount: Event = Event.fromObservable(this._checkedCountObservable, this._store); + readonly onChangedCheckedCount: Event = Event.fromObservable(this._checkedCountObservable, this._store); private readonly _checkedElementsObservable = observableValueOpts({ equalsFn: equals }, new Array()); - onChangedCheckedElements: Event = Event.fromObservable(this._checkedElementsObservable, this._store); + readonly onChangedCheckedElements: Event = Event.fromObservable(this._checkedElementsObservable, this._store); private readonly _onButtonTriggered = new Emitter>(); onButtonTriggered = this._onButtonTriggered.event; diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 7bd42115367..9411cf61a8c 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -29,7 +29,7 @@ export class QuickTree extends QuickInput implements I readonly onDidChangeActive = Event.fromObservable(this._activeItems, this._store); private readonly _onDidChangeCheckedLeafItems = new Emitter(); - onDidChangeCheckedLeafItems: Event = this._onDidChangeCheckedLeafItems.event; + readonly onDidChangeCheckedLeafItems: Event = this._onDidChangeCheckedLeafItems.event; readonly onDidAccept: Event; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 45c7a430f74..d6254d0000e 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -702,7 +702,7 @@ export interface IQuickInputToggle { * Event that is fired when the toggle value changes. * The boolean value indicates whether the change was triggered via keyboard. */ - onChange: Event; + readonly onChange: Event; } /** diff --git a/src/vs/platform/secrets/common/secrets.ts b/src/vs/platform/secrets/common/secrets.ts index 137150d946e..ee75762fe22 100644 --- a/src/vs/platform/secrets/common/secrets.ts +++ b/src/vs/platform/secrets/common/secrets.ts @@ -24,7 +24,7 @@ export interface ISecretStorageProvider { export interface ISecretStorageService extends ISecretStorageProvider { readonly _serviceBrand: undefined; - onDidChangeSecret: Event; + readonly onDidChangeSecret: Event; } export class BaseSecretStorageService extends Disposable implements ISecretStorageService { @@ -33,7 +33,7 @@ export class BaseSecretStorageService extends Disposable implements ISecretStora private readonly _storagePrefix = 'secret://'; protected readonly onDidChangeSecretEmitter = this._register(new Emitter()); - onDidChangeSecret: Event = this.onDidChangeSecretEmitter.event; + readonly onDidChangeSecret: Event = this.onDidChangeSecretEmitter.event; protected readonly _sequencer = new SequencerByKey(); diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 6d218ce431e..1c42d5efe6e 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -187,7 +187,7 @@ export interface ICommandInvalidationRequest { export interface IBufferMarkCapability { type: TerminalCapability.BufferMarkDetection; markers(): IterableIterator; - onMarkAdded: Event; + readonly onMarkAdded: Event; addMark(properties?: IMarkProperties): void; getMark(id: string): IMarker | undefined; } diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 9aa86595e56..a2132c2e911 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -760,12 +760,12 @@ export interface ITerminalChildProcess { */ shouldPersist: boolean; - onProcessData: Event; - onProcessReady: Event; - onProcessReplayComplete?: Event; - onDidChangeProperty: Event>; - onProcessExit: Event; - onRestoreCommands?: Event; + readonly onProcessData: Event; + readonly onProcessReady: Event; + readonly onProcessReplayComplete?: Event; + readonly onDidChangeProperty: Event>; + readonly onProcessExit: Event; + readonly onRestoreCommands?: Event; /** * Starts the process. @@ -1109,19 +1109,19 @@ export interface ITerminalBackend extends ITerminalBackendPtyServiceContribution * Fired when the ptyHost process becomes non-responsive, this should disable stdin for all * terminals using this pty host connection and mark them as disconnected. */ - onPtyHostUnresponsive: Event; + readonly onPtyHostUnresponsive: Event; /** * Fired when the ptyHost process becomes responsive after being non-responsive. Allowing * previously disconnected terminals to reconnect. */ - onPtyHostResponsive: Event; + readonly onPtyHostResponsive: Event; /** * Fired when the ptyHost has been restarted, this is used as a signal for listening terminals * that its pty has been lost and will remain disconnected. */ - onPtyHostRestart: Event; + readonly onPtyHostRestart: Event; - onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>; + readonly onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>; attachToProcess(id: number): Promise; attachToRevivedProcess(id: number): Promise; diff --git a/src/vs/platform/terminal/node/ptyHost.ts b/src/vs/platform/terminal/node/ptyHost.ts index 7900f8c8ec4..8345d21d50b 100644 --- a/src/vs/platform/terminal/node/ptyHost.ts +++ b/src/vs/platform/terminal/node/ptyHost.ts @@ -14,8 +14,8 @@ export interface IPtyHostConnection { } export interface IPtyHostStarter extends IDisposable { - onRequestConnection?: Event; - onWillShutdown?: Event; + readonly onRequestConnection?: Event; + readonly onWillShutdown?: Event; /** * Creates a pty host and connects to it. diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 1b0592103ac..7ef842ad8a1 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -107,7 +107,7 @@ export interface ITunnel { /** * Implementers of Tunnel should fire onDidDispose when dispose is called. */ - onDidDispose: Event; + readonly onDidDispose: Event; dispose(): Promise | void; } @@ -202,7 +202,7 @@ export function isPortPrivileged(port: number, host: string, os: OperatingSystem export class DisposableTunnel { private _onDispose: Emitter = new Emitter(); - onDidDispose: Event = this._onDispose.event; + readonly onDidDispose: Event = this._onDispose.event; constructor( public readonly remoteAddress: { port: number; host: string }, diff --git a/src/vs/platform/webview/common/webviewManagerService.ts b/src/vs/platform/webview/common/webviewManagerService.ts index ae380e0591c..d0c81645d0b 100644 --- a/src/vs/platform/webview/common/webviewManagerService.ts +++ b/src/vs/platform/webview/common/webviewManagerService.ts @@ -33,7 +33,7 @@ export interface FoundInFrameResult { export interface IWebviewManagerService { _serviceBrand: unknown; - onFoundInFrame: Event; + readonly onFoundInFrame: Event; setIgnoreMenuShortcuts(id: WebviewWebContentsId | WebviewWindowId, enabled: boolean): Promise; diff --git a/src/vs/platform/windows/node/windowTracker.ts b/src/vs/platform/windows/node/windowTracker.ts index 6c2306a4280..835f5c39028 100644 --- a/src/vs/platform/windows/node/windowTracker.ts +++ b/src/vs/platform/windows/node/windowTracker.ts @@ -15,8 +15,8 @@ export class ActiveWindowManager extends Disposable { private activeWindowId: number | undefined; constructor({ onDidOpenMainWindow, onDidFocusMainWindow, getActiveWindowId }: { - onDidOpenMainWindow: Event; - onDidFocusMainWindow: Event; + readonly onDidOpenMainWindow: Event; + readonly onDidFocusMainWindow: Event; getActiveWindowId(): Promise; }) { super(); diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index a6edf301a4a..2a95195c06b 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -34,15 +34,15 @@ suite('WindowsFinder', () => { function createTestCodeWindow(options: { lastFocusTime: number; openedFolderUri?: URI; openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow { return new class implements ICodeWindow { - onWillLoad: Event = Event.None; + readonly onWillLoad: Event = Event.None; onDidMaximize = Event.None; onDidUnmaximize = Event.None; - onDidTriggerSystemContextMenu: Event<{ x: number; y: number }> = Event.None; - onDidSignalReady: Event = Event.None; - onDidClose: Event = Event.None; - onDidDestroy: Event = Event.None; - onDidEnterFullScreen: Event = Event.None; - onDidLeaveFullScreen: Event = Event.None; + readonly onDidTriggerSystemContextMenu: Event<{ x: number; y: number }> = Event.None; + readonly onDidSignalReady: Event = Event.None; + readonly onDidClose: Event = Event.None; + readonly onDidDestroy: Event = Event.None; + readonly onDidEnterFullScreen: Event = Event.None; + readonly onDidLeaveFullScreen: Event = Event.None; whenClosedOrLoaded: Promise = Promise.resolve(); id: number = -1; win: Electron.BrowserWindow = null!; diff --git a/src/vs/platform/workspace/common/workspaceTrust.ts b/src/vs/platform/workspace/common/workspaceTrust.ts index 2d3ea5d1e37..36ebbb9523c 100644 --- a/src/vs/platform/workspace/common/workspaceTrust.ts +++ b/src/vs/platform/workspace/common/workspaceTrust.ts @@ -36,8 +36,8 @@ export const IWorkspaceTrustManagementService = createDecorator; - onDidChangeTrustedFolders: Event; + readonly onDidChangeTrust: Event; + readonly onDidChangeTrustedFolders: Event; readonly workspaceResolved: Promise; readonly workspaceTrustInitialized: Promise; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index be59789e94d..2f67a6ad620 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -237,7 +237,7 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { // Important for updating the UI private _onDidChangeSessions: Emitter = new Emitter(); - onDidChangeSessions: Event = this._onDidChangeSessions.event; + readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; private _session: AuthenticationSession | undefined; diff --git a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts index fa0d7fd2307..ca43443fec3 100644 --- a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts +++ b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts @@ -48,7 +48,7 @@ export const enum StatusBarUpdateKind { export interface IExtensionStatusBarItemService { readonly _serviceBrand: undefined; - onDidChange: Event; + readonly onDidChange: Event; setOrUpdateEntry(id: string, statusId: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined | IManagedHoverTooltipMarkdownString, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): StatusBarUpdateKind; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 674854b6f7e..ace7fa5e93c 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -38,15 +38,15 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { readonly _serviceBrand: undefined; - onDidStartDebugSession: Event; - onDidTerminateDebugSession: Event; - onDidChangeActiveDebugSession: Event; + readonly onDidStartDebugSession: Event; + readonly onDidTerminateDebugSession: Event; + readonly onDidChangeActiveDebugSession: Event; activeDebugSession: vscode.DebugSession | undefined; activeDebugConsole: vscode.DebugConsole; - onDidReceiveDebugSessionCustomEvent: Event; - onDidChangeBreakpoints: Event; + readonly onDidReceiveDebugSessionCustomEvent: Event; + readonly onDidChangeBreakpoints: Event; breakpoints: vscode.Breakpoint[]; - onDidChangeActiveStackItem: Event; + readonly onDidChangeActiveStackItem: Event; activeStackItem: vscode.DebugThread | vscode.DebugStackFrame | undefined; addBreakpoints(breakpoints0: readonly vscode.Breakpoint[]): Promise; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index f962eaa70fe..215f56cc66f 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -1162,7 +1162,7 @@ export interface IExtHostExtensionService extends AbstractExtHostExtensionServic registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable; getRemoteExecServer(authority: string): Promise; - onDidChangeRemoteConnectionData: Event; + readonly onDidChangeRemoteConnectionData: Event; getRemoteConnectionData(): IRemoteConnectionData | null; } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index ceeea93712d..e6ef77dbc0d 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -65,9 +65,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } private _onDidOpenNotebookDocument = new Emitter(); - onDidOpenNotebookDocument: Event = this._onDidOpenNotebookDocument.event; + readonly onDidOpenNotebookDocument: Event = this._onDidOpenNotebookDocument.event; private _onDidCloseNotebookDocument = new Emitter(); - onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; + readonly onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 34fe6fc1a11..6ac41d12fbd 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -36,12 +36,12 @@ export interface IExtHostTask extends ExtHostTaskShape { readonly _serviceBrand: undefined; taskExecutions: vscode.TaskExecution[]; - onDidStartTask: Event; - onDidEndTask: Event; - onDidStartTaskProcess: Event; - onDidEndTaskProcess: Event; - onDidStartTaskProblemMatchers: Event; - onDidEndTaskProblemMatchers: Event; + readonly onDidStartTask: Event; + readonly onDidEndTask: Event; + readonly onDidStartTaskProcess: Event; + readonly onDidEndTaskProcess: Event; + readonly onDidStartTaskProblemMatchers: Event; + readonly onDidEndTaskProblemMatchers: Event; registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable; registerTaskSystem(scheme: string, info: tasks.ITaskSystemInfoDTO): void; diff --git a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index 23f3ef10a48..1beb5234ef7 100644 --- a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -26,7 +26,7 @@ const emptyCommandService: ICommandService = { const emptyNotificationService = new class implements INotificationService { declare readonly _serviceBrand: undefined; - onDidChangeFilter: Event = Event.None; + readonly onDidChangeFilter: Event = Event.None; notify(...args: unknown[]): never { throw new Error('not implemented'); } @@ -65,7 +65,7 @@ class EmptyNotificationService implements INotificationService { constructor(private withNotify: (notification: INotification) => void) { } - onDidChangeFilter: Event = Event.None; + readonly onDidChangeFilter: Event = Event.None; notify(notification: INotification): INotificationHandle { this.withNotify(notification); diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 499ab8b2e09..dcb9af34371 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -521,7 +521,7 @@ export interface ITunnel { /** * Implementers of Tunnel should fire onDidDispose when dispose is called. */ - onDidDispose: Event; + readonly onDidDispose: Event; dispose(): Promise | void; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 8cfa0047966..1f629e73c32 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -845,7 +845,7 @@ export class NoTreeViewError extends Error { export interface ITreeViewDataProvider { readonly isTreeEmpty?: boolean; - onDidChangeEmpty?: Event; + readonly onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; getChildrenBatch?(element?: ITreeItem[]): Promise; } @@ -865,9 +865,9 @@ export interface IEditableData { } export interface IViewPaneContainer { - onDidAddViews: Event; - onDidRemoveViews: Event; - onDidChangeViewVisibility: Event; + readonly onDidAddViews: Event; + readonly onDidRemoveViews: Event; + readonly onDidChangeViewVisibility: Event; readonly views: IView[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 7fd15d52073..fd67aa14e0e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -33,7 +33,7 @@ export interface IChatConfirmationButton { tooltip?: string; data: T; disabled?: boolean; - onDidChangeDisablement?: Event; + readonly onDidChangeDisablement?: Event; moreActions?: (IChatConfirmationButton | Separator)[]; } diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts index 5ac303d0d20..feff1d07c51 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts @@ -22,7 +22,7 @@ export interface IChatViewsWelcomeDescriptor { } export interface IChatViewsWelcomeContributionRegistry { - onDidChange: Event; + readonly onDidChange: Event; get(): ReadonlyArray; register(descriptor: IChatViewsWelcomeDescriptor): void; } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 8f83ac95bae..aa345b2874b 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -24,7 +24,7 @@ export interface IChatModeService { readonly _serviceBrand: undefined; // TODO expose an observable list of modes - onDidChangeChatModes: Event; + readonly onDidChangeChatModes: Event; getModes(): { builtin: readonly IChatMode[]; custom: readonly IChatMode[] }; findModeById(id: string): IChatMode | undefined; findModeByName(name: string): IChatMode | undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 4a5565ead60..828b59cfcb2 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -176,7 +176,7 @@ export interface IChatProgressMessage { export interface IChatTask extends IChatTaskDto { deferred: DeferredPromise; progress: (IChatWarningMessage | IChatContentReference)[]; - onDidAddProgress: Event; + readonly onDidAddProgress: Event; add(progress: IChatWarningMessage | IChatContentReference): void; complete: (result: string | void) => void; @@ -725,7 +725,7 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitRequest: Event<{ chatSessionId: string }>; + readonly onDidSubmitRequest: Event<{ chatSessionId: string }>; isEnabled(location: ChatAgentLocation): boolean; hasSessions(): boolean; @@ -757,9 +757,9 @@ export interface IChatService { getChatStorageFolder(): URI; logChatIndex(): void; - onDidPerformUserAction: Event; + readonly onDidPerformUserAction: Event; notifyUserAction(event: IChatUserActionEvent): void; - onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }>; + readonly onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }>; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 563bcd07829..45a19c5b828 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -316,7 +316,7 @@ export type CountTokensCallback = (input: string, token: CancellationToken) => P export interface ILanguageModelToolsService { _serviceBrand: undefined; - onDidChangeTools: Event; + readonly onDidChangeTools: Event; registerToolData(toolData: IToolData): IDisposable; registerToolImplementation(id: string, tool: IToolImpl): IDisposable; registerTool(toolData: IToolData, tool: IToolImpl): IDisposable; diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 4d9fb7aac40..aeaebeda60b 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -206,7 +206,7 @@ export interface ILanguageModelChatResponse { } export interface ILanguageModelChatProvider { - onDidChange: Event; + readonly onDidChange: Event; provideLanguageModelChatInfo(options: { silent: boolean }, token: CancellationToken): Promise; sendChatRequest(modelId: string, messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, token: CancellationToken): Promise; provideTokenCount(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise; @@ -240,7 +240,7 @@ export interface ILanguageModelsService { readonly _serviceBrand: undefined; // TODO @lramos15 - Make this a richer event in the future. Right now it just indicates some change happened, but not what - onDidChangeLanguageModels: Event; + readonly onDidChangeLanguageModels: Event; updateModelPickerPreference(modelIdentifier: string, showInModelPicker: boolean): void; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index da9afffb06c..defe6776a67 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -21,7 +21,7 @@ export class MockChatService implements IChatService { _serviceBrand: undefined; editingSessions = []; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitRequest: Event<{ chatSessionId: string }> = Event.None; + readonly onDidSubmitRequest: Event<{ chatSessionId: string }> = Event.None; private sessions = new Map(); @@ -90,11 +90,11 @@ export class MockChatService implements IChatService { throw new Error('Method not implemented.'); } - onDidPerformUserAction: Event = undefined!; + readonly onDidPerformUserAction: Event = undefined!; notifyUserAction(event: IChatUserActionEvent): void { throw new Error('Method not implemented.'); } - onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }> = undefined!; + readonly onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }> = undefined!; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index c795fc769c9..d97873b3eb2 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -20,7 +20,7 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService cancelToolCallsForRequest(requestId: string): void { } - onDidChangeTools: Event = Event.None; + readonly onDidChangeTools: Event = Event.None; registerToolData(toolData: IToolData): IDisposable { return Disposable.None; diff --git a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index 1fae7a60cf8..aa3537a4c6d 100644 --- a/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -34,15 +34,15 @@ class TestCommentThread implements CommentThread { public readonly range: IRange, public readonly comments: Comment[]) { } - onDidChangeComments: Event = new Emitter().event; - onDidChangeInitialCollapsibleState: Event = new Emitter().event; + readonly onDidChangeComments: Event = new Emitter().event; + readonly onDidChangeInitialCollapsibleState: Event = new Emitter().event; canReply: boolean = false; - onDidChangeInput: Event = new Emitter().event; - onDidChangeRange: Event = new Emitter().event; - onDidChangeLabel: Event = new Emitter().event; - onDidChangeCollapsibleState: Event = new Emitter().event; - onDidChangeState: Event = new Emitter().event; - onDidChangeCanReply: Event = new Emitter().event; + readonly onDidChangeInput: Event = new Emitter().event; + readonly onDidChangeRange: Event = new Emitter().event; + readonly onDidChangeLabel: Event = new Emitter().event; + readonly onDidChangeCollapsibleState: Event = new Emitter().event; + readonly onDidChangeState: Event = new Emitter().event; + readonly onDidChangeCanReply: Event = new Emitter().event; isDisposed: boolean = false; isTemplate: boolean = false; label: string | undefined = undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 0a130630a40..7504a9c9d32 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -38,7 +38,7 @@ import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/c const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); export interface IAdapterManagerDelegate { - onDidNewSession: Event; + readonly onDidNewSession: Event; configurationManager(): IConfigurationManager; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index c70c0e15fdc..385ce2bfff9 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -720,17 +720,17 @@ export interface IViewModel extends ITreeElement { isMultiSessionView(): boolean; - onDidFocusSession: Event; - onDidFocusThread: Event<{ thread: IThread | undefined; explicit: boolean; session: IDebugSession | undefined }>; - onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>; - onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>; - onDidEvaluateLazyExpression: Event; + readonly onDidFocusSession: Event; + readonly onDidFocusThread: Event<{ thread: IThread | undefined; explicit: boolean; session: IDebugSession | undefined }>; + readonly onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>; + readonly onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>; + readonly onDidEvaluateLazyExpression: Event; /** * Fired when `setVisualizedExpression`, to migrate elements currently * rendered as `original` to the `replacement`. */ - onDidChangeVisualization: Event<{ original: IExpression; replacement: IExpression }>; - onWillUpdateViews: Event; + readonly onDidChangeVisualization: Event<{ original: IExpression; replacement: IExpression }>; + readonly onWillUpdateViews: Event; evaluateLazyExpression(expression: IExpressionContainer): void; } @@ -762,16 +762,16 @@ export interface IDebugModel extends ITreeElement { getWatchExpressions(): ReadonlyArray; registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]): void; getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[]; - onDidChangeBreakpoints: Event; - onDidChangeCallStack: Event; + readonly onDidChangeBreakpoints: Event; + readonly onDidChangeCallStack: Event; /** * The expression has been added, removed, or repositioned. */ - onDidChangeWatchExpressions: Event; + readonly onDidChangeWatchExpressions: Event; /** * The expression's value has changed. */ - onDidChangeWatchExpressionValue: Event; + readonly onDidChangeWatchExpressionValue: Event; fetchCallstack(thread: IThread, levels?: number): Promise; } @@ -1022,12 +1022,12 @@ export interface IConfigurationManager { /** * Allows to register on change of selected debug configuration. */ - onDidSelectConfiguration: Event; + readonly onDidSelectConfiguration: Event; /** * Allows to register on change of selected debug configuration. */ - onDidChangeConfigurationProviders: Event; + readonly onDidChangeConfigurationProviders: Event; hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean; getDynamicProviders(): Promise<{ label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig; label: string } | undefined> }[]>; @@ -1045,7 +1045,7 @@ export enum DebuggerString { export interface IAdapterManager { - onDidRegisterDebugger: Event; + readonly onDidRegisterDebugger: Event; hasEnabledDebuggers(): boolean; getDebugAdapterDescriptor(session: IDebugSession): Promise; @@ -1139,19 +1139,19 @@ export interface IDebugService { /** * Allows to register on debug state changes. */ - onDidChangeState: Event; + readonly onDidChangeState: Event; /** * Allows to register on sessions about to be created (not yet fully initialised). * This is fired exactly one time for any given session. */ - onWillNewSession: Event; + readonly onWillNewSession: Event; /** * Fired when a new debug session is started. This may fire multiple times * for a single session due to restarts. */ - onDidNewSession: Event; + readonly onDidNewSession: Event; /** * Allows to register on end session events. @@ -1159,7 +1159,7 @@ export interface IDebugService { * Contains a boolean indicating whether the session will restart. If restart * is true, the session should not considered to be dead yet. */ - onDidEndSession: Event<{ session: IDebugSession; restart: boolean }>; + readonly onDidEndSession: Event<{ session: IDebugSession; restart: boolean }>; /** * Gets the configuration manager. diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 07dc9c0355d..50c0f87a277 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -85,7 +85,7 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState { export interface ExtensionsListViewOptions { server?: IExtensionManagementServer; flexibleHeight?: boolean; - onDidChangeTitle?: Event; + readonly onDidChangeTitle?: Event; hideBadge?: boolean; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 0d988c32b71..b2494536468 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -203,8 +203,8 @@ export interface IExtensionContainer extends IDisposable { } export interface IExtensionsViewState { - onFocus: Event; - onBlur: Event; + readonly onFocus: Event; + readonly onBlur: Event; filters: { featureId?: string; }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index c20396091f0..3ccd951ca2e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -42,10 +42,10 @@ export interface IInlineChatSession2 { export interface IInlineChatSessionService { _serviceBrand: undefined; - onWillStartSession: Event; - onDidMoveSession: Event; - onDidStashSession: Event; - onDidEndSession: Event; + readonly onWillStartSession: Event; + readonly onDidMoveSession: Event; + readonly onDidStashSession: Event; + readonly onDidEndSession: Event; createSession(editor: IActiveCodeEditor, options: { wholeRange?: IRange; session?: Session; headless?: boolean }, token: CancellationToken): Promise; @@ -68,5 +68,5 @@ export interface IInlineChatSessionService { createSession2(editor: ICodeEditor, uri: URI, token: CancellationToken): Promise; getSession2(uri: URI): IInlineChatSession2 | undefined; - onDidChangeSessions: Event; + readonly onDidChangeSessions: Event; } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts b/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts index 61306699c3c..a7679ab6cf4 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts @@ -12,8 +12,8 @@ export const IInteractiveDocumentService = createDecorator; - onWillRemoveInteractiveDocument: Event<{ notebookUri: URI; inputUri: URI }>; + readonly onWillAddInteractiveDocument: Event<{ notebookUri: URI; inputUri: URI; languageId: string }>; + readonly onWillRemoveInteractiveDocument: Event<{ notebookUri: URI; inputUri: URI }>; willCreateInteractiveDocument(notebookUri: URI, inputUri: URI, languageId: string): void; willRemoveInteractiveDocument(notebookUri: URI, inputUri: URI): void; } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 38eaf863569..dde1b6ddf8b 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -61,7 +61,7 @@ interface IQueryResult { model: IPagedModel; disposables: DisposableStore; showWelcomeContent?: boolean; - onDidChangeModel?: Event>; + readonly onDidChangeModel?: Event>; } type Message = { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 3e435fad667..2e8d1197697 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -34,9 +34,9 @@ export interface INotebookTextDiffEditor { readonly textModel?: NotebookTextModel; inlineNotebookEditor: INotebookEditor | undefined; readonly currentChangedIndex: IObservable; - onMouseUp: Event<{ readonly event: MouseEvent; readonly target: IDiffElementViewModelBase }>; - onDidScroll: Event; - onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel; output: ICellOutputViewModel }>; + readonly onMouseUp: Event<{ readonly event: MouseEvent; readonly target: IDiffElementViewModelBase }>; + readonly onDidScroll: Event; + readonly onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel; output: ICellOutputViewModel }>; getOverflowContainerDomNode(): HTMLElement; getLayoutInfo(): NotebookLayoutInfo; getScrollTop(): number; @@ -112,7 +112,7 @@ export interface NotebookDocumentDiffElementRenderTemplate extends CellDiffCommo } export interface IDiffCellMarginOverlay extends IDisposable { - onAction: Event; + readonly onAction: Event; show(): void; hide(): void; } @@ -185,7 +185,7 @@ export interface INotebookDiffViewModel extends IDisposable { /** * Triggered when ever there's a change in the view model items. */ - onDidChangeItems: Event; + readonly onDidChangeItems: Event; /** * Computes the differences and generates the viewmodel. * If view models are generated, then the onDidChangeItems is triggered. diff --git a/src/vs/workbench/contrib/notebook/browser/diff/unchangedEditorRegions.ts b/src/vs/workbench/contrib/notebook/browser/diff/unchangedEditorRegions.ts index fdfce77d716..bcf8dbed6b5 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/unchangedEditorRegions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/unchangedEditorRegions.ts @@ -14,7 +14,7 @@ export type UnchangedEditorRegionOptions = { minimumLineCount: number; revealLineCount: number; }; - onDidChangeEnablement: Event; + readonly onDidChangeEnablement: Event; }; export function getUnchangedRegionSettings(configurationService: IConfigurationService): (Readonly & IDisposable) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 49beeb5e261..d2433fb11d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -493,9 +493,9 @@ export interface INotebookViewModel { readonly viewCells: ICellViewModel[]; layoutInfo: NotebookLayoutInfo | null; viewType: string; - onDidChangeViewCells: Event; - onDidChangeSelection: Event; - onDidFoldingStateChanged: Event; + readonly onDidChangeViewCells: Event; + readonly onDidChangeSelection: Event; + readonly onDidFoldingStateChanged: Event; getNearestVisibleCellIndexUpwards(index: number): number; getTrackedRange(id: string): ICellRange | null; setTrackedRange(id: string | null, newRange: ICellRange | null, newStickiness: TrackedRangeStickiness): string | null; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index c87d4ec7cc9..67ef7245398 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -24,8 +24,8 @@ export interface INotebookEditorService { retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; retrieveAllExistingWidgets(): IBorrowValue[]; - onDidAddNotebookEditor: Event; - onDidRemoveNotebookEditor: Event; + readonly onDidAddNotebookEditor: Event; + readonly onDidRemoveNotebookEditor: Event; addNotebookEditor(editor: INotebookEditor): void; removeNotebookEditor(editor: INotebookEditor): void; getNotebookEditor(editorId: string): INotebookEditor | undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 7cb95efe2a6..4fbeb3f99b7 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -570,7 +570,7 @@ export class NotebookService extends Disposable implements INotebookService { readonly onWillRemoveViewType; private readonly _onDidChangeEditorTypes; - onDidChangeEditorTypes: Event; + readonly onDidChangeEditorTypes: Event; private _cutItems: NotebookCellTextModel[] | undefined; private _lastClipboardIsCopy: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 0c7a1eb0bb3..c9d5a62989d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -114,7 +114,7 @@ export class NotebookCellList extends WorkbenchList implements ID private readonly _onDidChangeVisibleRanges = this._localDisposableStore.add(new Emitter()); - onDidChangeVisibleRanges: Event = this._onDidChangeVisibleRanges.event; + readonly onDidChangeVisibleRanges: Event = this._onDidChangeVisibleRanges.event; private _visibleRanges: ICellRange[] = []; get visibleRanges() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 9d81ac29180..c52ee4b80f7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -32,11 +32,11 @@ export interface INotebookCellList extends ICoordinatesConverter { element(index: number): ICellViewModel | undefined; elementAt(position: number): ICellViewModel | undefined; elementHeight(element: ICellViewModel): number; - onWillScroll: Event; - onDidScroll: Event; - onDidChangeFocus: Event>; - onDidChangeContentHeight: Event; - onDidChangeVisibleRanges: Event; + readonly onWillScroll: Event; + readonly onDidScroll: Event; + readonly onDidChangeFocus: Event>; + readonly onDidChangeContentHeight: Event; + readonly onDidChangeVisibleRanges: Event; visibleRanges: ICellRange[]; scrollTop: number; scrollHeight: number; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 18e618ea07e..cefc8954106 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -53,7 +53,7 @@ type Listener = { fn: (evt: T) => void; thisArg: unknown }; interface EmitterLike { fire(data: T): void; - event: Event; + readonly event: Event; } interface PreloadStyles { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 6611dd78eaf..27bb736468a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -117,7 +117,7 @@ export abstract class BaseCellViewModel extends Disposable { private readonly _textModelRefChangeDisposable = this._register(new MutableDisposable()); private readonly _cellDecorationsChanged = this._register(new Emitter<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }>()); - onCellDecorationsChanged: Event<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }> = this._cellDecorationsChanged.event; + readonly onCellDecorationsChanged: Event<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }> = this._cellDecorationsChanged.event; private _resolvedDecorations = new Map(); - onDidFoldingStateChanged: Event = this._onDidFoldingStateChanged.event; + readonly onDidFoldingStateChanged: Event = this._onDidFoldingStateChanged.event; private _hiddenRanges: ICellRange[] = []; private _focused: boolean = true; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 5825537d3cf..91c2ebf389f 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -243,7 +243,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { } } private readonly _onDidChangeVisibility = this._register(new Emitter()); - onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; get useGlobalToolbar(): boolean { return this._useGlobalToolbar; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 487f1630a49..e18c0a8b871 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -235,7 +235,7 @@ export interface ICellOutput { * Alternative output id that's reused when the output is updated. */ alternativeOutputId: string; - onDidChangeData: Event; + readonly onDidChangeData: Event; replaceData(items: IOutputDto): void; appendData(items: IOutputItemDto[]): void; appendedSinceVersion(versionId: number, mime: string): VSBuffer | undefined; @@ -277,13 +277,13 @@ export interface ICell { getHashValue(): number; textBuffer: IReadonlyTextBuffer; textModel?: ITextModel; - onDidChangeTextModel: Event; + readonly onDidChangeTextModel: Event; getValue(): string; - onDidChangeOutputs?: Event; - onDidChangeOutputItems?: Event; - onDidChangeLanguage: Event; - onDidChangeMetadata: Event; - onDidChangeInternalMetadata: Event; + readonly onDidChangeOutputs?: Event; + readonly onDidChangeOutputItems?: Event; + readonly onDidChangeLanguage: Event; + readonly onDidChangeMetadata: Event; + readonly onDidChangeInternalMetadata: Event; } export interface INotebookSnapshotOptions { @@ -305,8 +305,8 @@ export interface INotebookTextModel extends INotebookTextModelLike { createSnapshot(options: INotebookSnapshotOptions): NotebookData; restoreSnapshot(snapshot: NotebookData, transientOptions?: TransientOptions): void; applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo?: boolean): boolean; - onDidChangeContent: Event; - onWillDispose: Event; + readonly onDidChangeContent: Event; + readonly onWillDispose: Event; } export type NotebookCellTextModelSplice = [ diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts index 8ad13f68f51..c5c1f12c40d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts @@ -83,8 +83,8 @@ export const INotebookExecutionStateService = createDecorator; - onDidChangeLastRunFailState: Event; + readonly onDidChangeExecution: Event; + readonly onDidChangeLastRunFailState: Event; forceCancelNotebookExecutions(notebookUri: URI): void; getCellExecutionsForNotebook(notebook: URI): INotebookCellExecution[]; diff --git a/src/vs/workbench/contrib/notebook/common/notebookRendererMessagingService.ts b/src/vs/workbench/contrib/notebook/common/notebookRendererMessagingService.ts index d7e62a46889..30543806a90 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookRendererMessagingService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookRendererMessagingService.ts @@ -15,7 +15,7 @@ export interface INotebookRendererMessagingService { /** * Event that fires when a message should be posted to extension hosts. */ - onShouldPostMessage: Event<{ editorId: string; rendererId: string; message: unknown }>; + readonly onShouldPostMessage: Event<{ editorId: string; rendererId: string; message: unknown }>; /** * Prepares messaging for the given renderer ID. diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts index f99fceca3b8..b7a87cdbc51 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts @@ -55,7 +55,7 @@ suite('notebookCellDiagnostics', () => { interface ITestMarkerService extends IMarkerService { markers: ResourceMap; - onMarkersUpdated: Event; + readonly onMarkersUpdated: Event; } setup(function () { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 9c476d5c609..18206f1ab62 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -149,7 +149,7 @@ export class DefineKeybindingWidget extends Widget { private _onHide = this._register(new Emitter()); private _onDidChange = this._register(new Emitter()); - onDidChange: Event = this._onDidChange.event; + readonly onDidChange: Event = this._onDidChange.event; private _onShowExistingKeybindings = this._register(new Emitter()); readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index f391f5d1979..c053d998e6d 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -57,7 +57,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; interface IViewModel { - onDidChangeHelpInformation: Event; + readonly onDidChangeHelpInformation: Event; helpInformation: HelpInformation[]; } diff --git a/src/vs/workbench/contrib/search/common/searchHistoryService.ts b/src/vs/workbench/contrib/search/common/searchHistoryService.ts index 49d85fdf4b8..12177a37745 100644 --- a/src/vs/workbench/contrib/search/common/searchHistoryService.ts +++ b/src/vs/workbench/contrib/search/common/searchHistoryService.ts @@ -10,7 +10,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta export interface ISearchHistoryService { readonly _serviceBrand: undefined; - onDidClearHistory: Event; + readonly onDidClearHistory: Event; clearHistory(): void; load(): ISearchHistoryValues; save(history: ISearchHistoryValues): void; diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 1605209c7f1..153e1af45ff 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -103,7 +103,7 @@ export interface ITaskDefinitionRegistry { get(key: string): Tasks.ITaskDefinition; all(): Tasks.ITaskDefinition[]; getJsonSchema(): IJSONSchema; - onDefinitionsChanged: Event; + readonly onDefinitionsChanged: Event; } class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 10e3cc0907e..f7eee8acc64 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -63,11 +63,11 @@ export interface IWorkspaceFolderTaskResult extends IWorkspaceTaskResult { export interface ITaskService { readonly _serviceBrand: undefined; - onDidStateChange: Event; + readonly onDidStateChange: Event; /** Fired when task providers are registered or unregistered */ - onDidChangeTaskProviders: Event; + readonly onDidChangeTaskProviders: Event; isReconnected: boolean; - onDidReconnectToTasks: Event; + readonly onDidReconnectToTasks: Event; supportsMultipleTaskExecutions: boolean; configureAction(): Action; @@ -103,8 +103,8 @@ export interface ITaskService { registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable; registerTaskSystem(scheme: string, taskSystemInfo: ITaskSystemInfo): void; - onDidChangeTaskSystemInfo: Event; - onDidChangeTaskConfig: Event; + readonly onDidChangeTaskSystemInfo: Event; + readonly onDidChangeTaskConfig: Event; readonly hasTaskSystemInfo: boolean; registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): void; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index 8bd94d8b3f6..1b8a9a4ad4d 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -136,7 +136,7 @@ export interface ITaskSystemInfoResolver { } export interface ITaskSystem { - onDidStateChange: Event; + readonly onDidStateChange: Event; reconnect(task: Task, resolver: ITaskResolver): ITaskExecuteResult; run(task: Task, resolver: ITaskResolver): ITaskExecuteResult; rerun(): ITaskExecuteResult | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 74f4a8ef137..26fc1a6cae9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -67,12 +67,12 @@ export interface ITerminalInstanceService { /** * An event that's fired when a terminal instance is created. */ - onDidCreateInstance: Event; + readonly onDidCreateInstance: Event; /** * An event that's fired when a new backend is registered. */ - onDidRegisterBackend: Event; + readonly onDidRegisterBackend: Event; /** * Helper function to convert a shell launch config, a profile or undefined into its equivalent @@ -740,52 +740,52 @@ export interface ITerminalInstance extends IBaseTerminalInstance { /** * An event that fires when the terminal instance's title changes. */ - onTitleChanged: Event; + readonly onTitleChanged: Event; /** * An event that fires when the terminal instance's icon changes. */ - onIconChanged: Event<{ instance: ITerminalInstance; userInitiated: boolean }>; + readonly onIconChanged: Event<{ instance: ITerminalInstance; userInitiated: boolean }>; /** * An event that fires when the terminal instance is disposed. */ - onDisposed: Event; + readonly onDisposed: Event; - onProcessIdReady: Event; - onProcessReplayComplete: Event; - onRequestExtHostProcess: Event; - onDimensionsChanged: Event; - onMaximumDimensionsChanged: Event; - onDidChangeHasChildProcesses: Event; + readonly onProcessIdReady: Event; + readonly onProcessReplayComplete: Event; + readonly onRequestExtHostProcess: Event; + readonly onDimensionsChanged: Event; + readonly onMaximumDimensionsChanged: Event; + readonly onDidChangeHasChildProcesses: Event; - onDidFocus: Event; - onDidRequestFocus: Event; - onDidBlur: Event; - onDidInputData: Event; - onDidChangeSelection: Event; - onDidExecuteText: Event; - onDidChangeTarget: Event; - onDidSendText: Event; - onDidChangeShellType: Event; - onDidChangeVisibility: Event; + readonly onDidFocus: Event; + readonly onDidRequestFocus: Event; + readonly onDidBlur: Event; + readonly onDidInputData: Event; + readonly onDidChangeSelection: Event; + readonly onDidExecuteText: Event; + readonly onDidChangeTarget: Event; + readonly onDidSendText: Event; + readonly onDidChangeShellType: Event; + readonly onDidChangeVisibility: Event; /** * An event that fires when a terminal is dropped on this instance via drag and drop. */ - onRequestAddInstanceToGroup: Event; + readonly onRequestAddInstanceToGroup: Event; /** * Attach a listener to the raw data stream coming from the pty, including ANSI escape * sequences. */ - onData: Event; - onWillData: Event; + readonly onData: Event; + readonly onWillData: Event; /** * Attach a listener to the binary data stream coming from xterm and going to pty */ - onBinary: Event; + readonly onBinary: Event; /** * Attach a listener to listen for new lines added to this terminal instance. @@ -797,14 +797,14 @@ export interface ITerminalInstance extends IBaseTerminalInstance { * is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR * characters. */ - onLineData: Event; + readonly onLineData: Event; /** * Attach a listener that fires when the terminal's pty process exits. The number in the event * is the processes' exit code, an exit code of undefined means the process was killed as a result of * the ITerminalInstance being disposed. */ - onExit: Event; + readonly onExit: Event; /** * The exit code or undefined when the terminal process hasn't yet exited or diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index 32b1e45c4da..5d894d89797 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -32,7 +32,7 @@ export interface IEnvironmentVariableService { * An event that is fired when an extension's environment variable collection changes, the event * provides the new merged collection. */ - onDidChangeCollections: Event; + readonly onDidChangeCollections: Event; /** * Sets an extension's environment variable collection. diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 69d040320cf..31221d50263 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -79,7 +79,7 @@ export interface ITerminalProfileService { refreshAvailableProfiles(): void; getDefaultProfileName(): string | undefined; getDefaultProfile(os?: OperatingSystem): ITerminalProfile | undefined; - onDidChangeAvailableProfiles: Event; + readonly onDidChangeAvailableProfiles: Event; getContributedDefaultProfile(shellLaunchConfig: IShellLaunchConfig): Promise; registerContributedProfile(args: IRegisterContributedProfileArgs): Promise; getContributedProfileProvider(extensionIdentifier: string, id: string): ITerminalProfileProvider | undefined; @@ -337,13 +337,13 @@ export interface ITerminalProcessExtHostProxy extends IDisposable { emitReady(pid: number, cwd: string, windowsPty: IProcessReadyWindowsPty | undefined): void; emitExit(exitCode: number | undefined): void; - onInput: Event; - onBinary: Event; - onResize: Event<{ cols: number; rows: number }>; - onAcknowledgeDataEvent: Event; - onShutdown: Event; - onRequestInitialCwd: Event; - onRequestCwd: Event; + readonly onInput: Event; + readonly onBinary: Event; + readonly onResize: Event<{ cols: number; rows: number }>; + readonly onAcknowledgeDataEvent: Event; + readonly onShutdown: Event; + readonly onRequestInitialCwd: Event; + readonly onRequestCwd: Event; } export interface IStartExtensionTerminalRequest { diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 35b659a1793..985965e9108 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -70,9 +70,9 @@ class TestTerminalChildProcess extends Disposable implements ITerminalChildProce throw new Error('Method not implemented.'); } - onProcessOverrideDimensions?: Event | undefined; - onProcessResolvedShellLaunchConfig?: Event | undefined; - onDidChangeHasChildProcesses?: Event | undefined; + readonly onProcessOverrideDimensions?: Event | undefined; + readonly onProcessResolvedShellLaunchConfig?: Event | undefined; + readonly onDidChangeHasChildProcesses?: Event | undefined; onDidChangeProperty = Event.None; onProcessData = Event.None; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts index 957182d9efa..910390dee43 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts @@ -26,9 +26,9 @@ class TestTerminalChildProcess implements ITerminalChildProcess { throw new Error('Method not implemented.'); } - onProcessOverrideDimensions?: Event | undefined; - onProcessResolvedShellLaunchConfig?: Event | undefined; - onDidChangeHasChildProcesses?: Event | undefined; + readonly onProcessOverrideDimensions?: Event | undefined; + readonly onProcessResolvedShellLaunchConfig?: Event | undefined; + readonly onDidChangeHasChildProcesses?: Event | undefined; onDidChangeProperty = Event.None; onProcessData = Event.None; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts index 4206c45bf32..0b317bbbe5c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts @@ -18,7 +18,7 @@ export interface ITerminalExecuteStrategy { */ execute(commandLine: string, token: CancellationToken): Promise; - onDidCreateStartMarker: Event; + readonly onDidCreateStartMarker: Event; } export interface ITerminalExecuteStrategyResult { diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix.ts index 98153d2eac8..a9a831f7182 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFix.ts @@ -14,9 +14,9 @@ import { ITerminalCommand } from '../../../../../platform/terminal/common/capabi export const ITerminalQuickFixService = createDecorator('terminalQuickFixService'); export interface ITerminalQuickFixService { - onDidRegisterProvider: Event; - onDidRegisterCommandSelector: Event; - onDidUnregisterProvider: Event; + readonly onDidRegisterProvider: Event; + readonly onDidRegisterCommandSelector: Event; + readonly onDidUnregisterProvider: Event; readonly _serviceBrand: undefined; readonly extensionQuickFixes: Promise>; providers: Map; diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index b781d5b157a..f728e605ea8 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -26,7 +26,7 @@ export interface ITestTreeProjection extends IDisposable { /** * Event that fires when the projection changes. */ - onUpdate: Event; + readonly onUpdate: Event; /** * State to use for applying default collapse state of items. diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts index 79d44323bfe..631bd2a2e87 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts @@ -67,7 +67,7 @@ class SimpleDiffEditorModel extends EditorModel { export interface IPeekOutputRenderer extends IDisposable { - onDidContentSizeChange?: Event; + readonly onDidContentSizeChange?: Event; onScrolled?(evt: ScrollEvent): void; /** Updates the displayed test. Should clear if it cannot display the test. */ update(subject: InspectSubject): Promise; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 71f668b5fce..4fb500ae21e 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -57,7 +57,7 @@ interface ITreeElement { context: unknown; id: string; label: string; - onDidChange: Event; + readonly onDidChange: Event; labelWithIcons?: readonly (HTMLSpanElement | string)[]; icon?: ThemeIcon; description?: string; @@ -69,7 +69,7 @@ interface ITreeElement { context: unknown; id: string; label: string; - onDidChange: Event; + readonly onDidChange: Event; labelWithIcons?: readonly (HTMLSpanElement | string)[]; icon?: ThemeIcon; description?: string; @@ -301,7 +301,7 @@ export class OutputPeekTree extends Disposable { constructor( container: HTMLElement, - onDidReveal: Event<{ subject: InspectSubject; preserveFocus: boolean }>, + readonly onDidReveal: Event<{ subject: InspectSubject; preserveFocus: boolean }>, options: { showRevealLocationOnMessages: boolean; locationForProgress: string }, @IContextMenuService private readonly contextMenuService: IContextMenuService, @ITestResultService results: ITestResultService, diff --git a/src/vs/workbench/contrib/testing/common/observableValue.ts b/src/vs/workbench/contrib/testing/common/observableValue.ts index 6a66ea19b8e..963db3d6e09 100644 --- a/src/vs/workbench/contrib/testing/common/observableValue.ts +++ b/src/vs/workbench/contrib/testing/common/observableValue.ts @@ -8,7 +8,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { StoredValue } from './storedValue.js'; export interface IObservableValue { - onDidChange: Event; + readonly onDidChange: Event; readonly value: T; } diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 18586421e9d..d121f2f1368 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -47,7 +47,7 @@ export interface IMainThreadTestHostProxy { } export interface IMainThreadTestCollection extends AbstractIncrementalTestCollection { - onBusyProvidersChange: Event; + readonly onBusyProvidersChange: Event; /** * Number of providers working to discover tests. diff --git a/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts b/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts index fec6792ad2d..ccb27d63e7a 100644 --- a/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts +++ b/src/vs/workbench/contrib/testing/common/testingContinuousRunService.ts @@ -33,7 +33,7 @@ export interface ITestingContinuousRunService { * Fired when a test is added or removed from continous run, or when * enablement is changed globally. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Gets whether continous run is specifically enabled for the given test ID. diff --git a/src/vs/workbench/contrib/testing/common/testingDecorations.ts b/src/vs/workbench/contrib/testing/common/testingDecorations.ts index 0bfa2aa56ff..b338a8c420e 100644 --- a/src/vs/workbench/contrib/testing/common/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/common/testingDecorations.ts @@ -19,7 +19,7 @@ export interface ITestingDecorationsService { * Fires when something happened to change decorations in an editor. * Interested consumers should call {@link syncDecorations} to update them. */ - onDidChange: Event; + readonly onDidChange: Event; /** * Signals the code underlying a test message has changed, and it should diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index c1a991668cd..d8ccecc8111 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -96,7 +96,7 @@ export interface Timeline { } export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { - onDidChange?: Event; + readonly onDidChange?: Event; provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken): Promise; } @@ -140,9 +140,9 @@ export interface TimelineRequest { export interface ITimelineService { readonly _serviceBrand: undefined; - onDidChangeProviders: Event; - onDidChangeTimeline: Event; - onDidChangeUri: Event; + readonly onDidChangeProviders: Event; + readonly onDidChangeTimeline: Event; + readonly onDidChangeUri: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainService.ts b/src/vs/workbench/contrib/url/browser/trustedDomainService.ts index a5f3100c137..4580237df9f 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainService.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainService.ts @@ -17,7 +17,7 @@ export const ITrustedDomainService = createDecorator('ITr export interface ITrustedDomainService { _serviceBrand: undefined; - onDidChangeTrustedDomains: Event; + readonly onDidChangeTrustedDomains: Event; isValid(resource: URI): boolean; } @@ -27,7 +27,7 @@ export class TrustedDomainService extends Disposable implements ITrustedDomainSe private _staticTrustedDomainsResult!: WindowIdleValue; private _onDidChangeTrustedDomains: Emitter = this._register(new Emitter()); - onDidChangeTrustedDomains: Event = this._onDidChangeTrustedDomains.event; + readonly onDidChangeTrustedDomains: Event = this._onDidChangeTrustedDomains.event; constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/url/test/browser/mockTrustedDomainService.ts b/src/vs/workbench/contrib/url/test/browser/mockTrustedDomainService.ts index d36e21289d1..ef6defb98fd 100644 --- a/src/vs/workbench/contrib/url/test/browser/mockTrustedDomainService.ts +++ b/src/vs/workbench/contrib/url/test/browser/mockTrustedDomainService.ts @@ -14,7 +14,7 @@ export class MockTrustedDomainService implements ITrustedDomainService { constructor(private readonly _trustedDomains: string[] = []) { } - onDidChangeTrustedDomains: Event = Event.None; + readonly onDidChangeTrustedDomains: Event = Event.None; isValid(resource: URI): boolean { return isURLDomainTrusted(resource, this._trustedDomains); diff --git a/src/vs/workbench/services/authentication/browser/authenticationMcpService.ts b/src/vs/workbench/services/authentication/browser/authenticationMcpService.ts index f2050a312e2..b1ea1f1978d 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationMcpService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationMcpService.ts @@ -44,7 +44,7 @@ export interface IAuthenticationMcpService { * * A session preference is changed (because it's deprecated) * * A session preference is removed (because it's deprecated) */ - onDidChangeAccountPreference: Event<{ mcpServerIds: string[]; providerId: string }>; + readonly onDidChangeAccountPreference: Event<{ mcpServerIds: string[]; providerId: string }>; /** * Returns the accountName (also known as account.label) to pair with `IAuthenticationMCPServerAccessService` to get the account preference * @param providerId The authentication provider id diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index 2a60f4fecb9..d299c5f9e1d 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -313,7 +313,7 @@ export interface IAuthenticationExtensionsService { * * A session preference is changed (because it's deprecated) * * A session preference is removed (because it's deprecated) */ - onDidChangeAccountPreference: Event<{ extensionIds: string[]; providerId: string }>; + readonly onDidChangeAccountPreference: Event<{ extensionIds: string[]; providerId: string }>; /** * Returns the accountName (also known as account.label) to pair with `IAuthenticationAccessService` to get the account preference * @param providerId The authentication provider id diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 615f4fc9bca..f1958c90006 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -802,7 +802,7 @@ class MockLabelService implements ILabelService { registerCachedFormatter(formatter: ResourceLabelFormatter): IDisposable { throw new Error('Method not implemented.'); } - onDidChangeFormatters: Event = new Emitter().event; + readonly onDidChangeFormatters: Event = new Emitter().event; } class MockPathService implements IPathService { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index d68cad08bed..a5870190f44 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -250,7 +250,7 @@ export class DecorationsService implements IDecorationsService { private readonly _onDidChangeDecorationsDelayed = this._store.add(new DebounceEmitter({ merge: all => all.flat() })); private readonly _onDidChangeDecorations = this._store.add(new Emitter()); - onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; private readonly _provider = new LinkedList(); private readonly _decorationStyles: DecorationStyles; diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 8786cafe627..849afb7465f 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -182,7 +182,7 @@ suite('DecorationsService', function () { const provider = new class implements IDecorationsProvider { _onDidChange = new Emitter(); - onDidChange: Event = this._onDidChange.event; + readonly onDidChange: Event = this._onDidChange.event; label: string = 'foo'; diff --git a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts index 740d77b67aa..107013cbc6d 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/extensionRecommendations.ts @@ -52,10 +52,10 @@ export const IExtensionIgnoredRecommendationsService = createDecorator; + readonly onDidChangeIgnoredRecommendations: Event; readonly ignoredRecommendations: string[]; - onDidChangeGlobalIgnoredRecommendation: Event; + readonly onDidChangeGlobalIgnoredRecommendation: Event; readonly globalIgnoredRecommendations: string[]; toggleGlobalIgnoredRecommendation(extensionId: string, ignore: boolean): void; } diff --git a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts index fcaf2fd9c41..a48ce69a12b 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts @@ -32,7 +32,7 @@ export const IWorkspaceExtensionsConfigService = createDecorator; + readonly onDidChangeExtensionsConfigs: Event; getExtensionsConfigs(): Promise; getRecommendations(): Promise; getUnwantedRecommendations(): Promise; diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 33cff1715aa..642bfde8c3a 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -418,19 +418,19 @@ export interface IExtensionService { * * @returns the extensions that got registered */ - onDidRegisterExtensions: Event; + readonly onDidRegisterExtensions: Event; /** * @event * Fired when extensions status changes. * The event contains the ids of the extensions that have changed. */ - onDidChangeExtensionsStatus: Event; + readonly onDidChangeExtensionsStatus: Event; /** * Fired when the available extensions change (i.e. when extensions are added or removed). */ - onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>; + readonly onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>; /** * All registered extensions. @@ -443,19 +443,19 @@ export interface IExtensionService { /** * An event that is fired when activation happens. */ - onWillActivateByEvent: Event; + readonly onWillActivateByEvent: Event; /** * An event that is fired when an extension host changes its * responsive-state. */ - onDidChangeResponsiveChange: Event; + readonly onDidChangeResponsiveChange: Event; /** * Fired before stop of extension hosts happens. Allows listeners to veto against the * stop to prevent it from happening. */ - onWillStop: Event; + readonly onWillStop: Event; /** * Send an activation event and activate interested extensions. @@ -593,12 +593,12 @@ export function toExtensionDescription(extension: IExtension, isUnderDevelopment export class NullExtensionService implements IExtensionService { declare readonly _serviceBrand: undefined; - onDidRegisterExtensions: Event = Event.None; - onDidChangeExtensionsStatus: Event = Event.None; + readonly onDidRegisterExtensions: Event = Event.None; + readonly onDidChangeExtensionsStatus: Event = Event.None; onDidChangeExtensions = Event.None; - onWillActivateByEvent: Event = Event.None; - onDidChangeResponsiveChange: Event = Event.None; - onWillStop: Event = Event.None; + readonly onWillActivateByEvent: Event = Event.None; + readonly onDidChangeResponsiveChange: Event = Event.None; + readonly onWillStop: Event = Event.None; readonly extensions = []; activateByEvent(_activationEvent: string): Promise { return Promise.resolve(undefined); } activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts index 918f9c90d28..47dee57b47e 100644 --- a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts +++ b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts @@ -23,7 +23,7 @@ export interface IInlineCompletionsUnificationService { readonly _serviceBrand: undefined; readonly state: IInlineCompletionsUnificationState; - onDidStateChange: Event; + readonly onDidStateChange: Event; } const CODE_UNIFICATION_PREFIX = 'cmp-cht-'; diff --git a/src/vs/workbench/services/label/test/common/mockLabelService.ts b/src/vs/workbench/services/label/test/common/mockLabelService.ts index d2ee200c074..1ac38537f12 100644 --- a/src/vs/workbench/services/label/test/common/mockLabelService.ts +++ b/src/vs/workbench/services/label/test/common/mockLabelService.ts @@ -37,5 +37,5 @@ export class MockLabelService implements ILabelService { registerFormatter(formatter: ResourceLabelFormatter): IDisposable { return Disposable.None; } - onDidChangeFormatters: Event = new Emitter().event; + readonly onDidChangeFormatters: Event = new Emitter().event; } diff --git a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts index 74bfa68e6a8..94edbbdfe39 100644 --- a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts +++ b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts @@ -39,7 +39,7 @@ export interface ILanguageStatusService { _serviceBrand: undefined; - onDidChange: Event; + readonly onDidChange: Event; addStatus(status: ILanguageStatus): IDisposable; diff --git a/src/vs/workbench/services/outline/browser/outline.ts b/src/vs/workbench/services/outline/browser/outline.ts index 56d7b77183f..ea4a480f25f 100644 --- a/src/vs/workbench/services/outline/browser/outline.ts +++ b/src/vs/workbench/services/outline/browser/outline.ts @@ -25,7 +25,7 @@ export const enum OutlineTarget { export interface IOutlineService { _serviceBrand: undefined; - onDidChange: Event; + readonly onDidChange: Event; canCreateOutline(editor: IEditorPane): boolean; createOutline(editor: IEditorPane, target: OutlineTarget, token: CancellationToken): Promise | undefined>; registerOutlineCreator(creator: IOutlineCreator): IDisposable; diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index f5f30494024..42355d4c432 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -109,7 +109,7 @@ export interface IOutputService { /** * Allows to register on active output channel change. */ - onActiveOutputChannel: Event; + readonly onActiveOutputChannel: Event; /** * Register a compound log channel with the given channels. diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index cbf78719a20..80e97b5d87c 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -125,12 +125,12 @@ export enum PortsEnablement { export interface IRemoteExplorerService { readonly _serviceBrand: undefined; - onDidChangeTargetType: Event; + readonly onDidChangeTargetType: Event; targetType: string[]; - onDidChangeHelpInformation: Event[]>; + readonly onDidChangeHelpInformation: Event[]>; helpInformation: IExtensionPointUser[]; readonly tunnelModel: TunnelModel; - onDidChangeEditable: Event<{ tunnel: ITunnelItem; editId: TunnelEditId } | undefined>; + readonly onDidChangeEditable: Event<{ tunnel: ITunnelItem; editId: TunnelEditId } | undefined>; setEditable(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData | null): void; getEditableData(tunnelItem: ITunnelItem | undefined, editId?: TunnelEditId): IEditableData | undefined; forward(tunnelProperties: TunnelProperties, attributes?: Attributes | null): Promise; @@ -140,7 +140,7 @@ export interface IRemoteExplorerService { onFoundNewCandidates(candidates: CandidatePort[]): void; restore(): Promise; enablePortsFeatures(viewOnly: boolean): void; - onEnabledPortsFeatures: Event; + readonly onEnabledPortsFeatures: Event; portsFeaturesEnabled: PortsEnablement; readonly namedProcesses: Map; } diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 374f5c622c6..4f44448101b 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -408,7 +408,7 @@ interface ICacheRow { // TODO@roblou - never actually canceled promise: CancelablePromise<[ISearchEngineSuccess, IRawFileMatch[]]>; resolved: boolean; - event: Event; + readonly event: Event; } class Cache { diff --git a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts index f1001facbc1..c93386a16de 100644 --- a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts +++ b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts @@ -39,9 +39,9 @@ export interface IEmbedderTerminalOptions { * See Pseudoterminal on the vscode API for usage. */ export interface IEmbedderTerminalPty { - onDidWrite: Event; - onDidClose?: Event; - onDidChangeName?: Event; + readonly onDidWrite: Event; + readonly onDidClose?: Event; + readonly onDidChangeName?: Event; open(): void; close(): void; diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 6ba25af1b33..9c0f9e254d3 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -380,7 +380,7 @@ export interface IWorkbenchThemeService extends IThemeService { getColorTheme(): IWorkbenchColorTheme; getColorThemes(): Promise; getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; - onDidColorThemeChange: Event; + readonly onDidColorThemeChange: Event; getPreferredColorScheme(): ColorScheme | undefined; @@ -388,13 +388,13 @@ export interface IWorkbenchThemeService extends IThemeService { getFileIconTheme(): IWorkbenchFileIconTheme; getFileIconThemes(): Promise; getMarketplaceFileIconThemes(publisher: string, name: string, version: string): Promise; - onDidFileIconThemeChange: Event; + readonly onDidFileIconThemeChange: Event; setProductIconTheme(iconThemeId: string | undefined | IWorkbenchProductIconTheme, settingsTarget: ThemeSettingTarget): Promise; getProductIconTheme(): IWorkbenchProductIconTheme; getProductIconThemes(): Promise; getMarketplaceProductIconThemes(publisher: string, name: string, version: string): Promise; - onDidProductIconThemeChange: Event; + readonly onDidProductIconThemeChange: Event; } export interface IThemeScopedColorCustomizations { diff --git a/src/vs/workbench/services/userActivity/common/userActivityService.ts b/src/vs/workbench/services/userActivity/common/userActivityService.ts index 2379d3a16b5..788173cf013 100644 --- a/src/vs/workbench/services/userActivity/common/userActivityService.ts +++ b/src/vs/workbench/services/userActivity/common/userActivityService.ts @@ -67,7 +67,7 @@ export class UserActivityService extends Disposable implements IUserActivityServ public isActive = true; /** @inheritdoc */ - onDidChangeIsActive: Event = this.changeEmitter.event; + readonly onDidChangeIsActive: Event = this.changeEmitter.event; constructor(@IInstantiationService instantiationService: IInstantiationService) { super(); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts index 03c2286cfb3..a432e7024b4 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts @@ -83,32 +83,32 @@ export interface IWorkingCopyHistoryService { /** * An event when an entry is added to the history. */ - onDidAddEntry: Event; + readonly onDidAddEntry: Event; /** * An event when an entry is changed in the history. */ - onDidChangeEntry: Event; + readonly onDidChangeEntry: Event; /** * An event when an entry is replaced in the history. */ - onDidReplaceEntry: Event; + readonly onDidReplaceEntry: Event; /** * An event when an entry is removed from the history. */ - onDidRemoveEntry: Event; + readonly onDidRemoveEntry: Event; /** * An event when entries are moved in history. */ - onDidMoveEntries: Event; + readonly onDidMoveEntries: Event; /** * An event when all entries are removed from the history. */ - onDidRemoveEntries: Event; + readonly onDidRemoveEntries: Event; /** * Adds a new entry to the history for the given working copy diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 9879d14d965..bcf4471762a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -555,7 +555,7 @@ export class TestDecorationsService implements IDecorationsService { declare readonly _serviceBrand: undefined; - onDidChangeDecorations: Event = Event.None; + readonly onDidChangeDecorations: Event = Event.None; registerDecorationsProvider(_provider: IDecorationsProvider): IDisposable { return Disposable.None; } getDecoration(_uri: URI, _includeChildren: boolean, _overwrite?: IDecorationData): IDecoration | undefined { return undefined; } @@ -630,12 +630,12 @@ export class TestLayoutService implements IWorkbenchLayoutService { containers = [mainWindow.document.body]; activeContainer: HTMLElement = mainWindow.document.body; - onDidChangeZenMode: Event = Event.None; - onDidChangeMainEditorCenteredLayout: Event = Event.None; - onDidChangeWindowMaximized: Event<{ windowId: number; maximized: boolean }> = Event.None; - onDidChangePanelPosition: Event = Event.None; - onDidChangePanelAlignment: Event = Event.None; - onDidChangePartVisibility: Event = Event.None; + readonly onDidChangeZenMode: Event = Event.None; + readonly onDidChangeMainEditorCenteredLayout: Event = Event.None; + readonly onDidChangeWindowMaximized: Event<{ windowId: number; maximized: boolean }> = Event.None; + readonly onDidChangePanelPosition: Event = Event.None; + readonly onDidChangePanelAlignment: Event = Event.None; + readonly onDidChangePartVisibility: Event = Event.None; onDidLayoutMainContainer = Event.None; onDidLayoutActiveContainer = Event.None; onDidLayoutContainer = Event.None; @@ -701,8 +701,8 @@ const activeViewlet: PaneComposite = {} as any; export class TestPaneCompositeService extends Disposable implements IPaneCompositePartService { declare readonly _serviceBrand: undefined; - onDidPaneCompositeOpen: Event<{ composite: IPaneComposite; viewContainerLocation: ViewContainerLocation }>; - onDidPaneCompositeClose: Event<{ composite: IPaneComposite; viewContainerLocation: ViewContainerLocation }>; + readonly onDidPaneCompositeOpen: Event<{ composite: IPaneComposite; viewContainerLocation: ViewContainerLocation }>; + readonly onDidPaneCompositeClose: Event<{ composite: IPaneComposite; viewContainerLocation: ViewContainerLocation }>; private parts = new Map(); @@ -853,17 +853,17 @@ export class TestEditorGroupsService implements IEditorGroupsService { windowId = mainWindow.vscodeWindowId; - onDidCreateAuxiliaryEditorPart: Event = Event.None; - onDidChangeActiveGroup: Event = Event.None; - onDidActivateGroup: Event = Event.None; - onDidAddGroup: Event = Event.None; - onDidRemoveGroup: Event = Event.None; - onDidMoveGroup: Event = Event.None; - onDidChangeGroupIndex: Event = Event.None; - onDidChangeGroupLabel: Event = Event.None; - onDidChangeGroupLocked: Event = Event.None; - onDidChangeGroupMaximized: Event = Event.None; - onDidLayout: Event = Event.None; + readonly onDidCreateAuxiliaryEditorPart: Event = Event.None; + readonly onDidChangeActiveGroup: Event = Event.None; + readonly onDidActivateGroup: Event = Event.None; + readonly onDidAddGroup: Event = Event.None; + readonly onDidRemoveGroup: Event = Event.None; + readonly onDidMoveGroup: Event = Event.None; + readonly onDidChangeGroupIndex: Event = Event.None; + readonly onDidChangeGroupLabel: Event = Event.None; + readonly onDidChangeGroupLocked: Event = Event.None; + readonly onDidChangeGroupMaximized: Event = Event.None; + readonly onDidLayout: Event = Event.None; onDidChangeEditorPartOptions = Event.None; onDidScroll = Event.None; onWillDispose = Event.None; @@ -949,16 +949,16 @@ export class TestEditorGroupView implements IEditorGroupView { isEmpty = true; - onWillDispose: Event = Event.None; - onDidModelChange: Event = Event.None; - onWillCloseEditor: Event = Event.None; - onDidCloseEditor: Event = Event.None; - onDidOpenEditorFail: Event = Event.None; - onDidFocus: Event = Event.None; - onDidChange: Event<{ width: number; height: number }> = Event.None; - onWillMoveEditor: Event = Event.None; - onWillOpenEditor: Event = Event.None; - onDidActiveEditorChange: Event = Event.None; + readonly onWillDispose: Event = Event.None; + readonly onDidModelChange: Event = Event.None; + readonly onWillCloseEditor: Event = Event.None; + readonly onDidCloseEditor: Event = Event.None; + readonly onDidOpenEditorFail: Event = Event.None; + readonly onDidFocus: Event = Event.None; + readonly onDidChange: Event<{ width: number; height: number }> = Event.None; + readonly onWillMoveEditor: Event = Event.None; + readonly onWillOpenEditor: Event = Event.None; + readonly onDidActiveEditorChange: Event = Event.None; getEditors(_order?: EditorsOrder): readonly EditorInput[] { return []; } findEditors(_resource: URI): readonly EditorInput[] { return []; } @@ -1030,13 +1030,13 @@ export class TestEditorService extends Disposable implements EditorServiceImpl { declare readonly _serviceBrand: undefined; - onDidActiveEditorChange: Event = Event.None; - onDidVisibleEditorsChange: Event = Event.None; - onDidEditorsChange: Event = Event.None; - onWillOpenEditor: Event = Event.None; - onDidCloseEditor: Event = Event.None; - onDidOpenEditorFail: Event = Event.None; - onDidMostRecentlyActiveEditorsChange: Event = Event.None; + readonly onDidActiveEditorChange: Event = Event.None; + readonly onDidVisibleEditorsChange: Event = Event.None; + readonly onDidEditorsChange: Event = Event.None; + readonly onWillOpenEditor: Event = Event.None; + readonly onDidCloseEditor: Event = Event.None; + readonly onDidOpenEditorFail: Event = Event.None; + readonly onDidMostRecentlyActiveEditorsChange: Event = Event.None; private _activeTextEditorControl: ICodeEditor | IDiffEditor | undefined; public get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { return this._activeTextEditorControl; } diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 9c6dc49a77b..c5c6e145e08 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -265,9 +265,9 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService { declare readonly _serviceBrand: undefined; - onWillRunWorkingCopyFileOperation: Event = Event.None; - onDidFailWorkingCopyFileOperation: Event = Event.None; - onDidRunWorkingCopyFileOperation: Event = Event.None; + readonly onWillRunWorkingCopyFileOperation: Event = Event.None; + readonly onDidFailWorkingCopyFileOperation: Event = Event.None; + readonly onDidRunWorkingCopyFileOperation: Event = Event.None; addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { return Disposable.None; } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 3aa421153b4..05f88477f79 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -65,17 +65,17 @@ export class TestNativeHostService implements INativeHostService { readonly windowId = -1; - onDidOpenMainWindow: Event = Event.None; - onDidMaximizeWindow: Event = Event.None; - onDidUnmaximizeWindow: Event = Event.None; - onDidFocusMainWindow: Event = Event.None; - onDidBlurMainWindow: Event = Event.None; - onDidFocusMainOrAuxiliaryWindow: Event = Event.None; - onDidBlurMainOrAuxiliaryWindow: Event = Event.None; - onDidResumeOS: Event = Event.None; + readonly onDidOpenMainWindow: Event = Event.None; + readonly onDidMaximizeWindow: Event = Event.None; + readonly onDidUnmaximizeWindow: Event = Event.None; + readonly onDidFocusMainWindow: Event = Event.None; + readonly onDidBlurMainWindow: Event = Event.None; + readonly onDidFocusMainOrAuxiliaryWindow: Event = Event.None; + readonly onDidBlurMainOrAuxiliaryWindow: Event = Event.None; + readonly onDidResumeOS: Event = Event.None; onDidChangeColorScheme = Event.None; onDidChangePassword = Event.None; - onDidTriggerWindowSystemContextMenu: Event<{ windowId: number; x: number; y: number }> = Event.None; + readonly onDidTriggerWindowSystemContextMenu: Event<{ windowId: number; x: number; y: number }> = Event.None; onDidChangeWindowFullScreen = Event.None; onDidChangeWindowAlwaysOnTop = Event.None; onDidChangeDisplay = Event.None; diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index f5b8bd00728..778d6d7a407 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -6,7 +6,7 @@ declare module 'vscode' { export interface ChatParticipant { - onDidPerformAction: Event; + readonly onDidPerformAction: Event; } /** @@ -442,7 +442,7 @@ declare module 'vscode' { * Event that fires when a request is paused or unpaused. * Chat requests are initially unpaused in the {@link requestHandler}. */ - onDidChangePauseState: Event; + readonly onDidChangePauseState: Event; } export interface ChatParticipantPauseStateEvent { diff --git a/src/vscode-dts/vscode.proposed.dataChannels.d.ts b/src/vscode-dts/vscode.proposed.dataChannels.d.ts index 593c2de5955..edadd6373f0 100644 --- a/src/vscode-dts/vscode.proposed.dataChannels.d.ts +++ b/src/vscode-dts/vscode.proposed.dataChannels.d.ts @@ -10,7 +10,7 @@ declare module 'vscode' { } export interface DataChannel { - onDidReceiveData: Event>; + readonly onDidReceiveData: Event>; } export interface DataChannelEvent { diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index 14d8c69f1df..ec929299fdf 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -131,7 +131,7 @@ declare module 'vscode' { // eslint-disable-next-line local/vscode-dts-provider-naming handleListEndOfLifetime?(list: InlineCompletionList, reason: InlineCompletionsDisposeReason): void; - onDidChange?: Event; + readonly onDidChange?: Event; // #region Deprecated methods diff --git a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts index f20565262a6..1e79bf30640 100644 --- a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts @@ -25,7 +25,7 @@ declare module 'vscode' { /** * An optional event to signal that the kernel source actions have changed. */ - onDidChangeNotebookKernelSourceActions?: Event; + readonly onDidChangeNotebookKernelSourceActions?: Event; /** * Provide kernel source actions */ diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 8567a9d4e81..1ab099dbaf7 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -21,7 +21,7 @@ declare module 'vscode' { } export interface NotebookVariableProvider { - onDidChangeVariables: Event; + readonly onDidChangeVariables: Event; /** When parent is undefined, this is requesting global Variables. When a variable is passed, it's requesting child props of that Variable. */ provideVariables(notebook: NotebookDocument, parent: Variable | undefined, kind: NotebookVariablesRequestKind, start: number, token: CancellationToken): AsyncIterable; diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index 5df4f6fc1c5..5135427c680 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -34,9 +34,9 @@ declare module 'vscode' { } export interface ManagedMessagePassing { - onDidReceiveMessage: Event; - onDidClose: Event; - onDidEnd: Event; + readonly onDidReceiveMessage: Event; + readonly onDidClose: Event; + readonly onDidEnd: Event; send: (data: Uint8Array) => void; end: () => void; @@ -102,7 +102,7 @@ declare module 'vscode' { export interface Tunnel extends TunnelDescription { // Implementers of Tunnel should fire onDidDispose when dispose is called. - onDidDispose: Event; + readonly onDidDispose: Event; dispose(): void | Thenable; } diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 944cdd3935c..cf5f3c8e326 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -19,12 +19,12 @@ declare module 'vscode' { * Fires when the current history item refs (local, remote, base) * change after a user action (ex: commit, checkout, fetch, pull, push) */ - onDidChangeCurrentHistoryItemRefs: Event; + readonly onDidChangeCurrentHistoryItemRefs: Event; /** * Fires when history item refs change */ - onDidChangeHistoryItemRefs: Event; + readonly onDidChangeHistoryItemRefs: Event; provideHistoryItemRefs(historyItemRefs: string[] | undefined, token: CancellationToken): ProviderResult; provideHistoryItems(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; diff --git a/src/vscode-dts/vscode.proposed.timeline.d.ts b/src/vscode-dts/vscode.proposed.timeline.d.ts index cca46f71766..ee925aab8aa 100644 --- a/src/vscode-dts/vscode.proposed.timeline.d.ts +++ b/src/vscode-dts/vscode.proposed.timeline.d.ts @@ -122,7 +122,7 @@ declare module 'vscode' { * An optional event to signal that the timeline for a source has changed. * To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`. */ - onDidChange?: Event; + readonly onDidChange?: Event; /** * An identifier of the source of the timeline items. This can be used to filter sources. diff --git a/src/vscode-dts/vscode.proposed.tunnels.d.ts b/src/vscode-dts/vscode.proposed.tunnels.d.ts index 1f83bbbeb90..8e8497fa49d 100644 --- a/src/vscode-dts/vscode.proposed.tunnels.d.ts +++ b/src/vscode-dts/vscode.proposed.tunnels.d.ts @@ -35,7 +35,7 @@ declare module 'vscode' { export interface Tunnel extends TunnelDescription { // Implementers of Tunnel should fire onDidDispose when dispose is called. - onDidDispose: Event; + readonly onDidDispose: Event; dispose(): void | Thenable; } From c611432c5f3d1b945f1e5bddf711990cf7cf5d03 Mon Sep 17 00:00:00 2001 From: Sergei Druzhkov Date: Fri, 10 Oct 2025 00:04:54 +0300 Subject: [PATCH 1033/4355] Fix disassembly view (#270361) Co-authored-by: Dmitriy Vasyura --- src/vs/workbench/contrib/debug/browser/disassemblyView.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index c06b63cf155..3fabe31ebfa 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -270,6 +270,9 @@ export class DisassemblyView extends EditorPane { } this._register(this._disassembledInstructions.onDidScroll(e => { + if (this._disassembledInstructions?.row(0) === disassemblyNotAvailable) { + return; + } if (this._loadingLock) { return; } @@ -281,11 +284,10 @@ export class DisassemblyView extends EditorPane { if (loaded > 0) { this._disassembledInstructions!.reveal(prevTop + loaded, 0); } - this._loadingLock = false; - }); + }).finally(() => { this._loadingLock = false; }); } else if (e.oldScrollTop < e.scrollTop && e.scrollTop + e.height > e.scrollHeight - e.height) { this._loadingLock = true; - this.scrollDown_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD).then(() => { this._loadingLock = false; }); + this.scrollDown_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD).finally(() => { this._loadingLock = false; }); } })); From 683d1e36fb9dd2aaaf989306cebb407af68af8b5 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Thu, 9 Oct 2025 15:32:16 -0700 Subject: [PATCH 1034/4355] Prompt Updates: Remove think, fully qualify github mcp (#270656) --- .github/chatmodes/Plan.chatmode.md | 2 +- .github/prompts/implement.prompt.md | 2 +- .github/prompts/plan.prompt.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/chatmodes/Plan.chatmode.md b/.github/chatmodes/Plan.chatmode.md index 18b6881452f..552daed6373 100644 --- a/.github/chatmodes/Plan.chatmode.md +++ b/.github/chatmodes/Plan.chatmode.md @@ -1,6 +1,6 @@ --- description: Research and draft an implementation plan -tools: ['executePrompt', 'usages', 'problems', 'githubRepo', 'github.vscode-pull-request-github/activePullRequest', 'search', 'github/get_issue', 'github/get_issue_comments', 'github/get_issue', 'github/get_issue_comments', 'fetch'] +tools: ['executePrompt', 'usages', 'problems', 'githubRepo', 'github.vscode-pull-request-github/activePullRequest', 'search', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'fetch'] --- You are pairing with the user to create a clear, detailed, and actionable plan for the given task, iterating through a of gathering context and drafting the plan for review. diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/implement.prompt.md index 35b57c1a45d..190ff25f9ec 100644 --- a/.github/prompts/implement.prompt.md +++ b/.github/prompts/implement.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Implement the plan' -tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] +tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] --- Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally. diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index 9756bba5661..bff5fa18743 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Start planning' -tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'github/get_issue', 'github/get_issue_comments', 'github/get_me', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos'] +tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'github/github-mcp-server/get_me', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos'] --- Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: * Understand the context of the bug or feature by reading the issue description and comments. From a3619258ff0624148e271d50b7e54fd2baadb0a2 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 9 Oct 2025 16:03:19 -0700 Subject: [PATCH 1035/4355] Add prompt support to QuickPick (#270497) * Add prompt support to QuickPick * Update src/vs/platform/quickinput/browser/quickInput.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix incorrect suggestion * PR feedback * Merge branch 'main' into dev/dmitriv/fix-78335 * PR feedback * Merge branch 'main' into dev/dmitriv/fix-78335 * Fix build break. --- .../common/extensionsApiProposals.ts | 3 ++ .../platform/quickinput/browser/quickInput.ts | 32 ++++++++++++++----- .../browser/quickInputController.ts | 1 + .../platform/quickinput/common/quickInput.ts | 10 ++++++ .../workbench/api/common/extHost.protocol.ts | 2 ++ .../workbench/api/common/extHostQuickOpen.ts | 18 ++++++++++- .../vscode.proposed.quickPickPrompt.d.ts | 23 +++++++++++++ 7 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 54f7802a485..f415759ae6c 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -329,6 +329,9 @@ const _allApiProposals = { quickPickItemTooltip: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', }, + quickPickPrompt: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts', + }, quickPickSortByLabel: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', }, diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 5c2e06fc968..bb46541a8cd 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -170,7 +170,7 @@ export abstract class QuickInput extends Disposable implements IQuickInput { private buttonsUpdated = false; private _toggles: IQuickInputToggle[] = []; private togglesUpdated = false; - protected noValidationMessage = QuickInput.noPromptMessage; + protected noValidationMessage: string | undefined = QuickInput.noPromptMessage; private _validationMessage: string | undefined; private _lastValidationMessage: string | undefined; private _severity: Severity = Severity.Ignore; @@ -472,12 +472,14 @@ export abstract class QuickInput extends Disposable implements IQuickInput { if (this._lastValidationMessage !== validationMessage) { this._lastValidationMessage = validationMessage; dom.reset(this.ui.message); - renderQuickInputDescription(validationMessage, this.ui.message, { - callback: (content) => { - this.ui.linkOpenerDelegate(content); - }, - disposables: this.visibleDisposables, - }); + if (validationMessage) { + renderQuickInputDescription(validationMessage, this.ui.message, { + callback: (content) => { + this.ui.linkOpenerDelegate(content); + }, + disposables: this.visibleDisposables, + }); + } } if (this._lastSeverity !== this.severity) { this._lastSeverity = this.severity; @@ -585,6 +587,11 @@ export class QuickPick { */ placeHolder?: string; + /** + * the text to display underneath the input box + */ + prompt?: string; + /** * an optional flag to include the description when filtering the picks */ @@ -499,6 +504,11 @@ export interface IQuickPick(); private _selectedItems: T[] = []; private readonly _onDidChangeSelectionEmitter = new Emitter(); @@ -668,6 +674,16 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this.update({ keepScrollPosition }); } + get prompt() { + return this._prompt; + } + + set prompt(prompt: string | undefined) { + checkProposedApiEnabled(this._extension, 'quickPickPrompt'); + this._prompt = prompt; + this.update({ prompt }); + } + get activeItems() { return this._activeItems; } diff --git a/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts b/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts new file mode 100644 index 00000000000..2049cd5ff5f --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/78335 + + export interface QuickPick extends QuickInput { + /** + * An optional prompt text providing some ask or explanation to the user. + */ + prompt: string | undefined; + } + + export interface QuickPickOptions { + /** + * An optional prompt text providing some ask or explanation to the user. + */ + prompt?: string; + } +} From 691bcc63e64898057b6947be840b9f1eaa8d4690 Mon Sep 17 00:00:00 2001 From: dileepyavan <52841896+dileepyavan@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:34:40 -0700 Subject: [PATCH 1036/4355] Refreshing cached MCP servers when the extension MCP's are disabled. (#270476) * Changes to remove the disabled extensionMCP server from cached servers. * Adding tests for extensionMCPDiscovery * removing extra lines * removing extra lines * adding more tests --- .../common/discovery/extensionMcpDiscovery.ts | 70 ++-- .../contrib/mcp/common/mcpRegistry.ts | 2 +- .../discovery/extensionMcpDiscovery.test.ts | 394 ++++++++++++++++++ 3 files changed, 436 insertions(+), 30 deletions(-) create mode 100644 src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts diff --git a/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts b/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts index 004919f456f..34c6457d0ce 100644 --- a/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts +++ b/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts @@ -13,6 +13,7 @@ import { IMcpCollectionContribution } from '../../../../../platform/extensions/c import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; +import { ExtensionPointUserDelta, IExtensionPointUser } from '../../../../services/extensions/common/extensionsRegistry.js'; import { mcpActivationEvent, mcpContributionPoint } from '../mcpConfiguration.js'; import { IMcpRegistry } from '../mcpRegistryTypes.js'; import { extensionPrefixedIdentifier, McpServerDefinition, McpServerTrust } from '../mcpTypes.js'; @@ -29,6 +30,7 @@ const _mcpExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi const enum PersistWhen { CollectionExists, Always, + Delete } export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { @@ -38,6 +40,7 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { private readonly _extensionCollectionIdsToPersist = new Map(); private readonly cachedServers: { [collcetionId: string]: IServerCacheEntry }; private readonly _conditionalCollections = this._register(new DisposableMap()); + private _extensionCollections?: DisposableMap; constructor( @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry, @@ -51,6 +54,12 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { this._register(storageService.onWillSaveState(() => { let updated = false; for (const [collectionId, behavior] of this._extensionCollectionIdsToPersist.entries()) { + if (behavior === PersistWhen.Delete) { + updated = true; + delete this.cachedServers[collectionId]; + this._extensionCollectionIdsToPersist.delete(collectionId); + continue; + } const collection = this._mcpRegistry.collections.get().find(c => c.id === collectionId); let defs = collection?.serverDefinitions.get(); if (!collection || collection.lazy) { @@ -66,48 +75,51 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { this.cachedServers[collectionId] = { servers: defs.map(McpServerDefinition.toSerialized) }; } } - if (updated) { storageService.store(cacheKey, this.cachedServers, StorageScope.WORKSPACE, StorageTarget.MACHINE); } })); } - public start(): void { - const extensionCollections = this._register(new DisposableMap()); - this._register(_mcpExtensionPoint.setHandler((_extensions, delta) => { - const { added, removed } = delta; + this._extensionCollections = this._register(new DisposableMap()); + this._register(_mcpExtensionPoint.setHandler(this.handleExtensionChange.bind(this))); + } - for (const collections of removed) { - for (const coll of collections.value) { - const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); - extensionCollections.deleteAndDispose(id); - this._conditionalCollections.deleteAndDispose(id); - } + protected handleExtensionChange(_extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta) { + const extensionCollections = this._extensionCollections!; + for (const collections of delta.removed) { + for (const coll of collections.value) { + const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); + this.deleteCollection(id); } + } - for (const collections of added) { - - if (!ExtensionMcpDiscovery._validate(collections)) { - continue; - } - - for (const coll of collections.value) { - const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); - this._extensionCollectionIdsToPersist.set(id, PersistWhen.CollectionExists); - - // Handle conditional collections with 'when' clause - if (coll.when) { - this._registerConditionalCollection(id, coll, collections, extensionCollections); - } else { - // Register collection immediately if no 'when' clause - this._registerCollection(id, coll, collections, extensionCollections); - } + for (const collections of delta.added) { + if (!ExtensionMcpDiscovery._validate(collections)) { + continue; + } + for (const coll of collections.value) { + const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); + this._extensionCollectionIdsToPersist.set(id, PersistWhen.CollectionExists); + + // Handle conditional collections with 'when' clause + if (coll.when) { + this._registerConditionalCollection(id, coll, collections, extensionCollections); + } else { + // Register collection immediately if no 'when' clause + this._registerCollection(id, coll, collections, extensionCollections); } } - })); + } } + protected deleteCollection(id: string) { + this._extensionCollections!.deleteAndDispose(id); + this._conditionalCollections.deleteAndDispose(id); + this._extensionCollectionIdsToPersist.set(id, PersistWhen.Delete); + } + + private _registerCollection( id: string, coll: IMcpCollectionContribution, diff --git a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts index 6d95d0f068a..7e419e7ab0d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts @@ -119,7 +119,7 @@ export class McpRegistry extends Disposable implements IMcpRegistry { return { dispose: () => { const currentCollections = this._collections.get(); - this._collections.set(currentCollections.filter(c => c !== collection), undefined); + this._collections.set(currentCollections.filter(c => c.id !== collection.id), undefined); } }; } diff --git a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts new file mode 100644 index 00000000000..8544a63fe0e --- /dev/null +++ b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts @@ -0,0 +1,394 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { Event } from '../../../../../../base/common/event.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { TestStorageService, TestExtensionService } from '../../../../../test/common/workbenchTestServices.js'; +import { ExtensionMcpDiscovery } from '../../../common/discovery/extensionMcpDiscovery.js'; +import { ServiceCollection } from '../../../../../../platform/instantiation/common/serviceCollection.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IStorageService } from '../../../../../../platform/storage/common/storage.js'; +import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { MockContextKeyService } from '../../../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { IMcpRegistry } from '../../../common/mcpRegistryTypes.js'; +import { TestMcpRegistry } from '../mcpRegistryTypes.js'; +import { ExtensionMessageCollector, ExtensionPointUserDelta, IExtensionPointUser } from '../../../../../services/extensions/common/extensionsRegistry.js'; +import { IMcpCollectionContribution, IExtensionDescription, ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; + +// #region Test Helper Classes + +/** + * Testable version of ExtensionMcpDiscovery that exposes protected methods + */ +class TestableExtensionMcpDiscovery extends ExtensionMcpDiscovery { + public override handleExtensionChange( + extensions: readonly IExtensionPointUser[], + delta: ExtensionPointUserDelta + ): void { + super.handleExtensionChange(extensions, delta); + } + + public override deleteCollection(id: string) { + super.deleteCollection(id); + } +} + +/** + * Test implementation of IContextKeyService for MCP discovery tests + * Provides configurable context matching for testing conditional collections + */ +class TestContextKeyService extends MockContextKeyService { + private _contextMatchesRules: boolean = true; + + public override contextMatchesRules(): boolean { + return this._contextMatchesRules; + } + + public override get onDidChangeContext() { + return Event.None; + } + + public setContextMatchesRules(value: boolean): void { + this._contextMatchesRules = value; + } + + public override dispose(): void { + // Test implementation - no cleanup needed + } +} + +// #endregion + + +/** + * Test suite for ExtensionMcpDiscovery + * + * This class manages the discovery and registration of MCP (Model Context Protocol) + * collections from VS Code extensions. It handles: + * - Extension point registration and validation + * - Collection registration with conditional support ("when" clauses) + * - Caching of server definitions + * - Extension lifecycle (add/remove) + */ +suite('ExtensionMcpDiscovery', () => { + // #region Test State + interface TestFixture { + extensionMcpDiscovery: TestableExtensionMcpDiscovery; + storageService: TestStorageService; + extensionService: TestExtensionService; + contextKeyService: TestContextKeyService; + mcpRegistry: TestMcpRegistry; + } + + let fixture: TestFixture; + const store = ensureNoDisposablesAreLeakedInTestSuite(); + // #endregion + + // #region Helper Methods + /** + * Creates a fresh ExtensionMcpDiscovery instance with new services + * Useful for testing constructor behavior in isolation + */ + function createDiscoveryInstance(): ExtensionMcpDiscovery { + const services = new ServiceCollection( + [IStorageService, fixture.storageService], + [IExtensionService, fixture.extensionService], + [IContextKeyService, fixture.contextKeyService] + ); + const serviceInstantiation = store.add(new TestInstantiationService(services)); + const registry = new TestMcpRegistry(serviceInstantiation); + const instance = store.add(serviceInstantiation.createChild(new ServiceCollection([IMcpRegistry, registry]))); + return store.add(instance.createInstance(ExtensionMcpDiscovery)); + } + + /** + * Sets up the test fixture with all required services + */ + function setupTestFixture(): TestFixture { + const storageService = store.add(new TestStorageService()); + const extensionService = new TestExtensionService(); + const contextKeyService = new TestContextKeyService(); + + const services = new ServiceCollection( + [IStorageService, storageService], + [IExtensionService, extensionService], + [IContextKeyService, contextKeyService] + ); + const serviceInstantiation = store.add(new TestInstantiationService(services)); + const mcpRegistry = new TestMcpRegistry(serviceInstantiation); + + const instance = store.add(serviceInstantiation.createChild(new ServiceCollection([IMcpRegistry, mcpRegistry]))); + const extensionMcpDiscovery = store.add(instance.createInstance(TestableExtensionMcpDiscovery)); + + return { + extensionMcpDiscovery, + storageService, + extensionService, + contextKeyService, + mcpRegistry + }; + } + // #endregion + + // #region Test Setup/Teardown + setup(() => { + fixture = setupTestFixture(); + }); + + teardown(() => { + sinon.restore(); + }); + // #endregion + + // #region Basic Functionality Tests + suite('Basic Functionality', () => { + test('should start without throwing errors', () => { + assert.doesNotThrow(() => { + fixture.extensionMcpDiscovery.start(); + }, 'start() should not throw any errors'); + }); + }); + // #endregion + + // #region Constructor Tests + suite('Constructor Behavior', () => { + test('should register onWillSaveState listener during construction', () => { + // Spy on the storage service's onWillSaveState method + const onWillSaveStateSpy = sinon.spy(fixture.storageService, 'onWillSaveState'); + + // Create a new instance to test the constructor behavior + createDiscoveryInstance(); + + // Verify that onWillSaveState was called to register a listener + assert.ok(onWillSaveStateSpy.calledOnce, 'onWillSaveState should be called to register a listener'); + assert.strictEqual( + typeof onWillSaveStateSpy.getCall(0).args[0], + 'function', + 'onWillSaveState should be called with a function callback' + ); + + onWillSaveStateSpy.restore(); + }); + + test('should not call storageService.store when onWillSaveState event is emitted with no changes', () => { + // Spy on the storage service's store method + const storeSpy = sinon.spy(fixture.storageService, 'store'); + + // Create a new instance to ensure the event handler is registered + createDiscoveryInstance(); + + // Trigger the onWillSaveState event (no collections have been modified) + fixture.storageService.testEmitWillSaveState(0 /* WillSaveStateReason.NONE */); + + // Verify that store was not called since there are no changes to persist + assert.ok(storeSpy.notCalled, 'storageService.store should not be called when there are no changes to persist'); + + storeSpy.restore(); + }); + }); + // #endregion + + // #region Extension Point Handler Tests + suite('Extension Point Handler', () => { + /** + * Creates mock extension point user data for testing + */ + function createMockExtensionPointUser( + id: string, + label: string, + extensionId: string = 'test.extension', + when?: string + ): IExtensionPointUser { + const contribution: IMcpCollectionContribution = { + id, + label, + ...(when && { when }) + }; + + // Create a mock collector that satisfies the ExtensionMessageCollector interface + const mockCollector = { + error: sinon.stub(), + warn: sinon.stub(), + info: sinon.stub(), + _messageHandler: sinon.stub(), + _extension: null, + _extensionPointId: 'test-point', + _msg: [] + } as unknown as ExtensionMessageCollector; // Use any for the mock to avoid complex type requirements + + return { + value: [contribution], + description: { + identifier: new ExtensionIdentifier(extensionId) + } as IExtensionDescription, + collector: mockCollector + }; + } + + /** + * Creates a mock ExtensionPointUserDelta for testing + */ + function createMockDelta( + added: IExtensionPointUser[] = [], + removed: IExtensionPointUser[] = [] + ): ExtensionPointUserDelta { + return { + added, + removed + } as ExtensionPointUserDelta; + } + + test('should handle added extensions without when clause', () => { + // Start the discovery to initialize internal state + fixture.extensionMcpDiscovery.start(); + + // Spy on the mcpRegistry to verify collection registration + const registerCollectionStub = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); + + // Create mock extension data + const mockExtension = createMockExtensionPointUser('test-collection', 'Test Collection'); + const delta = createMockDelta([mockExtension], []); + + //spy on registercollection + + // Call the method under test + fixture.extensionMcpDiscovery.handleExtensionChange([], delta); + + // Verify that registerCollection was called for the added extension + assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for added extension'); + + const registrationCall = registerCollectionStub.getCall(0); + const registrationArgs = registrationCall.args[0]; + assert.strictEqual(registrationArgs.id, 'test.extension/test-collection', 'Collection should be registered with prefixed ID'); + assert.strictEqual(registrationArgs.label, 'Test Collection', 'Collection should have correct label'); + + registerCollectionStub.restore(); + }); + + test('should handle added extensions with when clause', () => { + // Start the discovery to initialize internal state + fixture.extensionMcpDiscovery.start(); + + // Configure context to match the when clause + fixture.contextKeyService.setContextMatchesRules(true); + + // Spy on the mcpRegistry to verify collection registration + const registerCollectionStub = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); + + // Create mock extension data with when clause + const mockExtension = createMockExtensionPointUser( + 'conditional-collection', + 'Conditional Collection', + 'test.extension', + 'config.someFlag' + ); + const delta = createMockDelta([mockExtension], []); + + // Call the method under test + fixture.extensionMcpDiscovery.handleExtensionChange([], delta); + + // Verify that registerCollection was called for the conditional extension + assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for conditional extension when context matches'); + assert.strictEqual(registerCollectionStub.getCall(0).args[0].id, 'test.extension/conditional-collection', 'Collection should be registered with prefixed ID'); + assert.strictEqual(registerCollectionStub.getCall(0).args[0].label, 'Conditional Collection', 'Collection should have correct label'); + + registerCollectionStub.restore(); + }); + + test('should handle removed extensions', () => { + // Start the discovery to initialize internal state + fixture.extensionMcpDiscovery.start(); + + // First add an extension to be removed later + const mockExtension = createMockExtensionPointUser('removable-collection', 'Removable Collection'); + const addDelta = createMockDelta([mockExtension], []); + + // Spy on registry to monitor any new registrations during removal + const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); + fixture.extensionMcpDiscovery.handleExtensionChange([], addDelta); + + registerCollectionSpy.resetHistory(); // Clear any previous calls + + //spy on deleteCollection method. + const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery, "deleteCollection"); + // Now remove the extension + const removeDelta = createMockDelta([], [mockExtension]); + fixture.extensionMcpDiscovery.handleExtensionChange([], removeDelta); + + // Verify that no new registrations occurred during removal + assert.ok(registerCollectionSpy.notCalled, 'No new collections should be registered when extensions are removed'); + assert.ok(deleteCollectionSpy.calledOnce, 'deleteCollection should be called once for removed extension'); + assert.strictEqual(deleteCollectionSpy.getCall(0).args[0], 'test.extension/removable-collection', 'deleteCollection should be called with correct ID'); + registerCollectionSpy.restore(); + deleteCollectionSpy.restore(); + }); + + test('should skip invalid extensions', () => { + // Start the discovery to initialize internal state + fixture.extensionMcpDiscovery.start(); + + const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); + + // Create mock extension data with invalid structure (empty id should fail validation) + const invalidExtension = createMockExtensionPointUser('', 'Invalid Collection'); // Empty id + const invalidDelta = createMockDelta([invalidExtension], []); + + // Call the method under test + fixture.extensionMcpDiscovery.handleExtensionChange([], invalidDelta); + + // Verify that registerCollection was not called for invalid extension + assert.ok(registerCollectionSpy.notCalled, 'registerCollection should not be called for invalid extensions'); + + registerCollectionSpy.restore(); + }); + + test('should handle mixed add and remove operations', () => { + // Start the discovery to initialize internal state + fixture.extensionMcpDiscovery.start(); + + // First add an extension to be removed later + const existingExtension = createMockExtensionPointUser('existing-collection', 'Existing Collection'); + const addExistingDelta = createMockDelta([existingExtension], []); + // Prepare spy for the mixed operation + const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); + fixture.extensionMcpDiscovery.handleExtensionChange([], addExistingDelta); + // Create mixed delta: add new extension, remove existing one + const newExtension = createMockExtensionPointUser('new-collection', 'New Collection', 'new.extension'); + const mixedDelta = createMockDelta([newExtension], [existingExtension]); + + // Reset spy call counts after initial setup + registerCollectionSpy.resetHistory(); + + //spy on deleteCollection method. + const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery, "deleteCollection"); + + // Call the method under test + fixture.extensionMcpDiscovery.handleExtensionChange([], mixedDelta); + + // Verify new collection was registered (we can't easily test removal with current test setup) + assert.ok(registerCollectionSpy.calledOnce, 'New collection should be registered'); + + const registrationCall = registerCollectionSpy.getCall(0); + assert.strictEqual(registrationCall.args[0].id, 'new.extension/new-collection', 'New collection should have correct prefixed ID'); + assert.ok(deleteCollectionSpy.calledOnce, 'deleteCollection should be called once for removed extension'); + assert.strictEqual(deleteCollectionSpy.getCall(0).args[0], 'test.extension/existing-collection', 'deleteCollection should be called with correct ID'); + + registerCollectionSpy.restore(); + }); + }); + // #endregion + + // #region Future Test Suites + // TODO: Add test suites for: + // - Collection Validation Details + // - Conditional Collections Context Changes + // - Storage Persistence Edge Cases + // - Error Handling + // #endregion + +}); From cbbcf0dfb08ab9c4bfd267596b8af11a7403a3be Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Oct 2025 00:52:20 +0000 Subject: [PATCH 1037/4355] Fix MCP authentication fallback when authorization server returns non-JSON responses (#270579) * Initial plan * Add Content-Type check before JSON parsing in _getAuthorizationServerMetadata Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Improve comment explaining Content-Type check fallback behavior Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Fix: Check both status AND content-type for all metadata URL attempts Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Refactor: Try parsing JSON payload instead of checking Content-Type Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Refactor: Use early returns instead of nested if blocks Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * add a log --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Co-authored-by: Tyler Leonhardt --- src/vs/workbench/api/common/extHostMcp.ts | 90 ++++++++++++++--------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 457a8dea83f..6cac9aa64ad 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -398,6 +398,22 @@ export class McpHTTPHandle extends Disposable { this._log(LogLevel.Info, 'Using default auth metadata'); } + private async _tryParseAuthServerMetadata(response: CommonResponse): Promise { + if (response.status !== 200) { + return undefined; + } + try { + const body = await response.json(); + if (isAuthorizationServerMetadata(body)) { + return body; + } + } catch { + // Failed to parse as JSON or not valid metadata + this._log(LogLevel.Debug, 'Failed to parse authorization server metadata'); + } + return undefined; + } + private async _getAuthorizationServerMetadata(authorizationServer: string, addtionalHeaders: Record): Promise { // For the oauth server metadata discovery path, we _INSERT_ // the well known path after the origin and before the path. @@ -414,44 +430,48 @@ export class McpHTTPHandle extends Disposable { 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION, } }); - if (authServerMetadataResponse.status !== 200) { - // Try fetching the OpenID Connect Discovery with path insertion. - // For issuer URLs with path components, this inserts the well-known path - // after the origin and before the path. - const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; - this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path insertion: ${openidPathInsertionUrl} ...`); - authServerMetadataResponse = await this._fetch(openidPathInsertionUrl, { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION - } - }); - if (authServerMetadataResponse.status !== 200) { - // Try fetching the other discovery URL. For the openid metadata discovery - // path, we _ADD_ the well known path after the existing path. - // https://datatracker.ietf.org/doc/html/rfc8414#section-3 - const openidPathAdditionUrl = URI.joinPath(URI.parse(authorizationServer), OPENID_CONNECT_DISCOVERY_PATH).toString(true); - this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path addition: ${openidPathAdditionUrl} ...`); - authServerMetadataResponse = await this._fetch(openidPathAdditionUrl, { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION - } - }); - if (authServerMetadataResponse.status !== 200) { - throw new Error(`Failed to fetch authorization server metadata: ${authServerMetadataResponse.status} ${await this._getErrText(authServerMetadataResponse)}`); - } + let metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); + if (metadata) { + return metadata; + } + + // Try fetching the OpenID Connect Discovery with path insertion. + // For issuer URLs with path components, this inserts the well-known path + // after the origin and before the path. + const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path insertion: ${openidPathInsertionUrl} ...`); + authServerMetadataResponse = await this._fetch(openidPathInsertionUrl, { + method: 'GET', + headers: { + ...addtionalHeaders, + 'Accept': 'application/json', + 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION } + }); + metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); + if (metadata) { + return metadata; } - const body = await authServerMetadataResponse.json(); - if (isAuthorizationServerMetadata(body)) { - return body; + + // Try fetching the other discovery URL. For the openid metadata discovery + // path, we _ADD_ the well known path after the existing path. + // https://datatracker.ietf.org/doc/html/rfc8414#section-3 + const openidPathAdditionUrl = URI.joinPath(URI.parse(authorizationServer), OPENID_CONNECT_DISCOVERY_PATH).toString(true); + this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path addition: ${openidPathAdditionUrl} ...`); + authServerMetadataResponse = await this._fetch(openidPathAdditionUrl, { + method: 'GET', + headers: { + ...addtionalHeaders, + 'Accept': 'application/json', + 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION + } + }); + metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); + if (metadata) { + return metadata; } - throw new Error(`Invalid authorization server metadata: ${JSON.stringify(body)}`); + + throw new Error(`Failed to fetch authorization server metadata: ${authServerMetadataResponse.status} ${await this._getErrText(authServerMetadataResponse)}`); } private async _handleSuccessfulStreamableHttp(res: CommonResponse, message: string) { From 50e2e985b5c433a795c06871a7dbcb545aa3b73b Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:58:58 -0700 Subject: [PATCH 1038/4355] Retry MCP Auth on a 401 (#270675) * Retry Auth on a 401 If we have an Authorization header and still get a 401 when trying to connect to an MCP Server, we should retry with a new auth registration. This fix was for the local MCP dev loop, where you may use an in-memory OAuth server. If you kill the server and start it again, any tokens will be rejected. This PR allows us to react to that by re-doing the auth flow. * Update src/vs/workbench/api/common/extHostMcp.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/api/browser/mainThreadMcp.ts | 22 ++++++++++++------ .../workbench/api/common/extHost.protocol.ts | 14 ++++++++++- src/vs/workbench/api/common/extHostMcp.ts | 23 ++++++++++++++++--- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadMcp.ts b/src/vs/workbench/api/browser/mainThreadMcp.ts index dda2b38aac5..aa848a44304 100644 --- a/src/vs/workbench/api/browser/mainThreadMcp.ts +++ b/src/vs/workbench/api/browser/mainThreadMcp.ts @@ -8,10 +8,9 @@ import { disposableTimeout } from '../../../base/common/async.js'; import { CancellationError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable, DisposableMap, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js'; -import { IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata } from '../../../base/common/oauth.js'; import { ISettableObservable, observableValue } from '../../../base/common/observable.js'; import Severity from '../../../base/common/severity.js'; -import { URI, UriComponents } from '../../../base/common/uri.js'; +import { URI } from '../../../base/common/uri.js'; import * as nls from '../../../nls.js'; import { ContextKeyExpr, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; import { IDialogService, IPromptButton } from '../../../platform/dialogs/common/dialogs.js'; @@ -24,11 +23,12 @@ import { IAuthenticationMcpAccessService } from '../../services/authentication/b import { IAuthenticationMcpService } from '../../services/authentication/browser/authenticationMcpService.js'; import { IAuthenticationMcpUsageService } from '../../services/authentication/browser/authenticationMcpUsageService.js'; import { AuthenticationSession, AuthenticationSessionAccount, IAuthenticationService } from '../../services/authentication/common/authentication.js'; +import { IDynamicAuthenticationProviderStorageService } from '../../services/authentication/common/dynamicAuthenticationProviderStorage.js'; import { ExtensionHostKind, extensionHostKindToString } from '../../services/extensions/common/extensionHostKind.js'; import { IExtensionService } from '../../services/extensions/common/extensions.js'; import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { Proxied } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExtHostContext, ExtHostMcpShape, MainContext, MainThreadMcpShape } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostMcpShape, IMcpAuthenticationDetails, IMcpAuthenticationOptions, MainContext, MainThreadMcpShape } from '../common/extHost.protocol.js'; @extHostNamedCustomer(MainContext.MainThreadMcp) export class MainThreadMcp extends Disposable implements MainThreadMcpShape { @@ -51,6 +51,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { @IAuthenticationMcpService private readonly authenticationMcpServersService: IAuthenticationMcpService, @IAuthenticationMcpAccessService private readonly authenticationMCPServerAccessService: IAuthenticationMcpAccessService, @IAuthenticationMcpUsageService private readonly authenticationMCPServerUsageService: IAuthenticationMcpUsageService, + @IDynamicAuthenticationProviderStorageService private readonly _dynamicAuthenticationProviderStorageService: IDynamicAuthenticationProviderStorageService, @IExtensionService private readonly _extensionService: IExtensionService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { @@ -178,16 +179,23 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { this._servers.get(id)?.pushMessage(message); } - async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise { + async $getTokenFromServerMetadata(id: number, authDetails: IMcpAuthenticationDetails, { errorOnUserInteraction, forceNewRegistration }: IMcpAuthenticationOptions = {}): Promise { const server = this._serverDefinitions.get(id); if (!server) { return undefined; } - const authorizationServer = URI.revive(authServerComponents); - const resolvedScopes = scopes ?? resourceMetadata?.scopes_supported ?? serverMetadata.scopes_supported ?? []; + const authorizationServer = URI.revive(authDetails.authorizationServer); + const resolvedScopes = authDetails.scopes ?? authDetails.resourceMetadata?.scopes_supported ?? authDetails.authorizationServerMetadata.scopes_supported ?? []; let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer); + if (forceNewRegistration && providerId) { + this._authenticationService.unregisterAuthenticationProvider(providerId); + // TODO: Encapsulate this and the unregister in one call in the auth service + await this._dynamicAuthenticationProviderStorageService.removeDynamicProvider(providerId); + providerId = undefined; + } + if (!providerId) { - const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, serverMetadata, resourceMetadata); + const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, authDetails.authorizationServerMetadata, authDetails.resourceMetadata); if (!provider) { return undefined; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9ffea9255f6..e567cff832a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3038,13 +3038,25 @@ export interface ExtHostMcpShape { $waitForInitialCollectionProviders(): Promise; } +export interface IMcpAuthenticationDetails { + authorizationServer: UriComponents; + authorizationServerMetadata: IAuthorizationServerMetadata; + resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined; + scopes: string[] | undefined; +} + +export interface IMcpAuthenticationOptions { + errorOnUserInteraction?: boolean; + forceNewRegistration?: boolean; +} + export interface MainThreadMcpShape { $onDidChangeState(id: number, state: McpConnectionState): void; $onDidPublishLog(id: number, level: LogLevel, log: string): void; $onDidReceiveMessage(id: number, message: string): void; $upsertMcpCollection(collection: McpCollectionDefinition.FromExtHost, servers: McpServerDefinition.Serialized[]): void; $deleteMcpCollection(collectionId: string): void; - $getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, scopes: string[] | undefined, errorOnUserInteraction?: boolean): Promise; + $getTokenFromServerMetadata(id: number, authDetails: IMcpAuthenticationDetails, options?: IMcpAuthenticationOptions): Promise; } export interface MainThreadDataChannelsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 6cac9aa64ad..15299e93e1c 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -18,7 +18,7 @@ import { canLog, ILogService, LogLevel } from '../../../platform/log/common/log. import { StorageScope } from '../../../platform/storage/common/storage.js'; import { extensionPrefixedIdentifier, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch, McpServerTransportHTTP, McpServerTransportType, UserInteractionRequiredError } from '../../contrib/mcp/common/mcpTypes.js'; import { MCP } from '../../contrib/mcp/common/modelContextProtocol.js'; -import { ExtHostMcpShape, IStartMcpOptions, MainContext, MainThreadMcpShape } from './extHost.protocol.js'; +import { ExtHostMcpShape, IMcpAuthenticationDetails, IStartMcpOptions, MainContext, MainThreadMcpShape } from './extHost.protocol.js'; import { IExtHostInitDataService } from './extHostInitDataService.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import * as Convert from './extHostTypeConverters.js'; @@ -671,10 +671,22 @@ export class McpHTTPHandle extends Disposable { } while (!chunk.done); } - private async _addAuthHeader(headers: Record) { + private async _addAuthHeader(headers: Record, forceNewRegistration?: boolean) { if (this._authMetadata) { try { - const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata, this._authMetadata.scopes, this._errorOnUserInteraction); + const authDetails: IMcpAuthenticationDetails = { + authorizationServer: this._authMetadata.authorizationServer.toJSON(), + authorizationServerMetadata: this._authMetadata.serverMetadata, + resourceMetadata: this._authMetadata.resourceMetadata, + scopes: this._authMetadata.scopes + }; + const token = await this._proxy.$getTokenFromServerMetadata( + this._id, + authDetails, + { + errorOnUserInteraction: this._errorOnUserInteraction, + forceNewRegistration + }); if (token) { headers['Authorization'] = `Bearer ${token}`; } @@ -765,6 +777,11 @@ export class McpHTTPHandle extends Disposable { } } } + // If we have an Authorization header and still get a 401, we should retry with a new auth registration + if (headers['Authorization'] && res.status === 401) { + await this._addAuthHeader(headers, true); + res = await doFetch(); + } return res; } From bad1b34c88ef534d546ec42c5a5611950a878cde Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:04:26 -0700 Subject: [PATCH 1039/4355] Update chat code block languages when languages change Fixes #263216 --- .../chat/common/codeBlockModelCollection.ts | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts index a04748fc1a8..fb378c94ab4 100644 --- a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -9,6 +9,7 @@ import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js'; import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { extractCodeblockUrisFromText, extractVulnerabilitiesFromText, IMarkdownVulnerability } from './annotations.js'; @@ -33,6 +34,7 @@ export class CodeBlockModelCollection extends Disposable { private readonly _models = new Map>; vulns: readonly IMarkdownVulnerability[]; + inLanguageId: string | undefined; codemapperUri?: URI; isEdit?: boolean; }>(); @@ -50,6 +52,20 @@ export class CodeBlockModelCollection extends Disposable { @ITextModelService private readonly textModelService: ITextModelService, ) { super(); + + this._register(this.languageService.onDidChange(async () => { + for (const entry of this._models.values()) { + if (!entry.inLanguageId) { + continue; + } + + const model = (await entry.model).object; + const existingLanguageId = model.getLanguageId(); + if (!existingLanguageId || existingLanguageId === PLAINTEXT_LANGUAGE_ID) { + this.trySetTextModelLanguage(entry.inLanguageId, model.textEditorModel); + } + } + })); } public override dispose(): void { @@ -81,6 +97,7 @@ export class CodeBlockModelCollection extends Disposable { this._models.set(this.getKey(sessionId, chat, codeBlockIndex), { model: model, vulns: [], + inLanguageId: undefined, codemapperUri: undefined, }); @@ -113,18 +130,7 @@ export class CodeBlockModelCollection extends Disposable { updateSync(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): CodeBlockEntry { const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); - const extractedVulns = extractVulnerabilitiesFromText(content.text); - const newText = fixCodeText(extractedVulns.newText, content.languageId); - this.setVulns(sessionId, chat, codeBlockIndex, extractedVulns.vulnerabilities); - - const codeblockUri = extractCodeblockUrisFromText(newText); - if (codeblockUri) { - this.setCodemapperUri(sessionId, chat, codeBlockIndex, codeblockUri.uri, codeblockUri.isEdit); - } - - if (content.isComplete) { - this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); - } + this.updateInternalCodeBlockEntry(content, sessionId, chat, codeBlockIndex); return this.get(sessionId, chat, codeBlockIndex) ?? entry; } @@ -140,19 +146,7 @@ export class CodeBlockModelCollection extends Disposable { async update(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): Promise { const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); - const extractedVulns = extractVulnerabilitiesFromText(content.text); - let newText = fixCodeText(extractedVulns.newText, content.languageId); - this.setVulns(sessionId, chat, codeBlockIndex, extractedVulns.vulnerabilities); - - const codeblockUri = extractCodeblockUrisFromText(newText); - if (codeblockUri) { - this.setCodemapperUri(sessionId, chat, codeBlockIndex, codeblockUri.uri, codeblockUri.isEdit); - newText = codeblockUri.textWithoutResult; - } - - if (content.isComplete) { - this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); - } + const newText = this.updateInternalCodeBlockEntry(content, sessionId, chat, codeBlockIndex); const textModel = await entry.model; if (!textModel || textModel.isDisposed()) { @@ -161,10 +155,7 @@ export class CodeBlockModelCollection extends Disposable { } if (content.languageId) { - const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(content.languageId); - if (vscodeLanguageId && vscodeLanguageId !== textModel.getLanguageId()) { - textModel.setLanguage(vscodeLanguageId); - } + this.trySetTextModelLanguage(content.languageId, textModel); } const currentText = textModel.getValue(EndOfLinePreference.LF); @@ -185,18 +176,39 @@ export class CodeBlockModelCollection extends Disposable { return entry; } - private setCodemapperUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, codemapperUri: URI, isEdit?: boolean) { + private updateInternalCodeBlockEntry(content: CodeBlockContent, sessionId: string, chat: IChatResponseViewModel | IChatRequestViewModel, codeBlockIndex: number) { const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); if (entry) { - entry.codemapperUri = codemapperUri; - entry.isEdit = isEdit; + entry.inLanguageId = content.languageId; } - } - private setVulns(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, vulnerabilities: IMarkdownVulnerability[]) { - const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); + const extractedVulns = extractVulnerabilitiesFromText(content.text); + let newText = fixCodeText(extractedVulns.newText, content.languageId); if (entry) { - entry.vulns = vulnerabilities; + entry.vulns = extractedVulns.vulnerabilities; + } + + const codeblockUri = extractCodeblockUrisFromText(newText); + if (codeblockUri) { + if (entry) { + entry.codemapperUri = codeblockUri.uri; + entry.isEdit = codeblockUri.isEdit; + } + + newText = codeblockUri.textWithoutResult; + } + + if (content.isComplete) { + this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); + } + + return newText; + } + + private trySetTextModelLanguage(inLanguageId: string, textModel: ITextModel) { + const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(inLanguageId); + if (vscodeLanguageId && vscodeLanguageId !== textModel.getLanguageId()) { + textModel.setLanguage(vscodeLanguageId); } } From fe2ff4f9d511a83c2d1ff749f73bd5c10b8fd6a0 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 9 Oct 2025 19:34:44 -0700 Subject: [PATCH 1040/4355] Fix chat session view menu context key. --- .../contrib/chat/browser/chatSessions/view/sessionsViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 94a16016ad1..25922c11aa2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -141,7 +141,7 @@ export class SessionsViewPane extends ViewPane { icon: Codicon.plus, }, undefined, undefined, undefined, undefined); - const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.contextKeyService); + const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.scopedContextKeyService); const actions = menu.getActions({ shouldForwardArgs: true }); const primaryActions = getActionBarActions( From 9ebd5bfca1a8a3f077d487b29d0da316a3cd5f19 Mon Sep 17 00:00:00 2001 From: dileepyavan <52841896+dileepyavan@users.noreply.github.com> Date: Thu, 9 Oct 2025 21:37:42 -0700 Subject: [PATCH 1041/4355] Fixes build issue caused by extensionMcpDiscovery tests (#270695) * Changes to remove the disabled extensionMCP server from cached servers. * Adding tests for extensionMCPDiscovery * removing extra lines * removing extra lines * adding more tests * Fixing the tests for build error --- .../discovery/extensionMcpDiscovery.test.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts index 8544a63fe0e..029a6f9fcd8 100644 --- a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts @@ -26,16 +26,14 @@ import { IMcpCollectionContribution, IExtensionDescription, ExtensionIdentifier * Testable version of ExtensionMcpDiscovery that exposes protected methods */ class TestableExtensionMcpDiscovery extends ExtensionMcpDiscovery { - public override handleExtensionChange( + + public handleExtensionChangeTest( extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta ): void { super.handleExtensionChange(extensions, delta); } - public override deleteCollection(id: string) { - super.deleteCollection(id); - } } /** @@ -257,7 +255,7 @@ suite('ExtensionMcpDiscovery', () => { //spy on registercollection // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChange([], delta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], delta); // Verify that registerCollection was called for the added extension assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for added extension'); @@ -290,7 +288,7 @@ suite('ExtensionMcpDiscovery', () => { const delta = createMockDelta([mockExtension], []); // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChange([], delta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], delta); // Verify that registerCollection was called for the conditional extension assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for conditional extension when context matches'); @@ -310,15 +308,15 @@ suite('ExtensionMcpDiscovery', () => { // Spy on registry to monitor any new registrations during removal const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - fixture.extensionMcpDiscovery.handleExtensionChange([], addDelta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], addDelta); registerCollectionSpy.resetHistory(); // Clear any previous calls //spy on deleteCollection method. - const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery, "deleteCollection"); + const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery as unknown as { deleteCollection: sinon.SinonSpy }, "deleteCollection"); // Now remove the extension const removeDelta = createMockDelta([], [mockExtension]); - fixture.extensionMcpDiscovery.handleExtensionChange([], removeDelta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], removeDelta); // Verify that no new registrations occurred during removal assert.ok(registerCollectionSpy.notCalled, 'No new collections should be registered when extensions are removed'); @@ -339,7 +337,7 @@ suite('ExtensionMcpDiscovery', () => { const invalidDelta = createMockDelta([invalidExtension], []); // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChange([], invalidDelta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], invalidDelta); // Verify that registerCollection was not called for invalid extension assert.ok(registerCollectionSpy.notCalled, 'registerCollection should not be called for invalid extensions'); @@ -356,7 +354,7 @@ suite('ExtensionMcpDiscovery', () => { const addExistingDelta = createMockDelta([existingExtension], []); // Prepare spy for the mixed operation const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - fixture.extensionMcpDiscovery.handleExtensionChange([], addExistingDelta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], addExistingDelta); // Create mixed delta: add new extension, remove existing one const newExtension = createMockExtensionPointUser('new-collection', 'New Collection', 'new.extension'); const mixedDelta = createMockDelta([newExtension], [existingExtension]); @@ -365,10 +363,10 @@ suite('ExtensionMcpDiscovery', () => { registerCollectionSpy.resetHistory(); //spy on deleteCollection method. - const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery, "deleteCollection"); + const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery as unknown as { deleteCollection: sinon.SinonSpy }, "deleteCollection"); // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChange([], mixedDelta); + fixture.extensionMcpDiscovery.handleExtensionChangeTest([], mixedDelta); // Verify new collection was registered (we can't easily test removal with current test setup) assert.ok(registerCollectionSpy.calledOnce, 'New collection should be registered'); From f1b211e5ebf03457cd55e869dc33af09e681e358 Mon Sep 17 00:00:00 2001 From: dileepyavan <52841896+dileepyavan@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:40:21 -0700 Subject: [PATCH 1042/4355] Fixing failed tests in build (#270704) * Changes to remove the disabled extensionMCP server from cached servers. * Adding tests for extensionMCPDiscovery * removing extra lines * removing extra lines * adding more tests * Fixing the tests for build error * Fixing the tests for build error --- .../common/discovery/extensionMcpDiscovery.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts index 029a6f9fcd8..0e66e150ab8 100644 --- a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts @@ -312,18 +312,13 @@ suite('ExtensionMcpDiscovery', () => { registerCollectionSpy.resetHistory(); // Clear any previous calls - //spy on deleteCollection method. - const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery as unknown as { deleteCollection: sinon.SinonSpy }, "deleteCollection"); // Now remove the extension const removeDelta = createMockDelta([], [mockExtension]); fixture.extensionMcpDiscovery.handleExtensionChangeTest([], removeDelta); // Verify that no new registrations occurred during removal assert.ok(registerCollectionSpy.notCalled, 'No new collections should be registered when extensions are removed'); - assert.ok(deleteCollectionSpy.calledOnce, 'deleteCollection should be called once for removed extension'); - assert.strictEqual(deleteCollectionSpy.getCall(0).args[0], 'test.extension/removable-collection', 'deleteCollection should be called with correct ID'); registerCollectionSpy.restore(); - deleteCollectionSpy.restore(); }); test('should skip invalid extensions', () => { @@ -362,9 +357,6 @@ suite('ExtensionMcpDiscovery', () => { // Reset spy call counts after initial setup registerCollectionSpy.resetHistory(); - //spy on deleteCollection method. - const deleteCollectionSpy = sinon.spy(fixture.extensionMcpDiscovery as unknown as { deleteCollection: sinon.SinonSpy }, "deleteCollection"); - // Call the method under test fixture.extensionMcpDiscovery.handleExtensionChangeTest([], mixedDelta); @@ -373,8 +365,6 @@ suite('ExtensionMcpDiscovery', () => { const registrationCall = registerCollectionSpy.getCall(0); assert.strictEqual(registrationCall.args[0].id, 'new.extension/new-collection', 'New collection should have correct prefixed ID'); - assert.ok(deleteCollectionSpy.calledOnce, 'deleteCollection should be called once for removed extension'); - assert.strictEqual(deleteCollectionSpy.getCall(0).args[0], 'test.extension/existing-collection', 'deleteCollection should be called with correct ID'); registerCollectionSpy.restore(); }); From d8b078709ba22088ce8709dd94dc344db768d6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 10 Oct 2025 10:16:53 +0200 Subject: [PATCH 1043/4355] undo focus input change (#270724) related to #268553 and #270705 --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 1fec72e18ab..f5fbae30215 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -944,7 +944,8 @@ export class ChatWidget extends Disposable implements IChatWidget { // reset the input in welcome view if it was rendered in experimental mode if (this.viewModel?.getItems().length) { this.resetWelcomeViewInput(); - this.focusInput(); + // TODO@bhavyaus + // this.focusInput(); } if (treeItems.length > 0) { From d9d9335d2cc718145263bc61e1762627ca682918 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:58:11 +0900 Subject: [PATCH 1044/4355] Fix auto approve links for new rules Fixes #270736 --- .../chatTerminalToolConfirmationSubPart.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 879e4fde1c4..12a256ee9a8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -9,7 +9,7 @@ import { Separator } from '../../../../../../base/common/actions.js'; import { asArray } from '../../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { ErrorNoTelemetry } from '../../../../../../base/common/errors.js'; -import { MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { createCommandUri, MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { thenIfNotDisposed, thenRegisterOrDispose } from '../../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../../base/common/network.js'; import Severity from '../../../../../../base/common/severity.js'; @@ -41,6 +41,7 @@ import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatCo import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; +import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; export const enum TerminalToolConfirmationStorageKeys { TerminalAutoApproveWarningAccepted = 'chat.tools.terminal.autoApprove.warningAccepted' @@ -269,13 +270,19 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS await this.configurationService.updateValue(TerminalContribSettingId.AutoApprove, newValue, ConfigurationTarget.USER); function formatRuleLinks(newRules: ITerminalNewAutoApproveRule[]): string { return newRules.map(e => { - return `[\`${e.key}\`](settings_${ConfigurationTarget.USER} "${localize('ruleTooltip', 'View rule in settings')}")`; + const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, ConfigurationTarget.USER); + return `[\`${e.key}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; }).join(', '); } + const mdTrustSettings = { + isTrusted: { + enabledCommands: [openTerminalSettingsLinkCommandId] + } + }; if (newRules.length === 1) { - terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule', 'Auto approve rule {0} added', formatRuleLinks(newRules))}_`); + terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule', 'Auto approve rule {0} added', formatRuleLinks(newRules))}_`, mdTrustSettings); } else if (newRules.length > 1) { - terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule.plural', 'Auto approve rules {0} added', formatRuleLinks(newRules))}_`); + terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule.plural', 'Auto approve rules {0} added', formatRuleLinks(newRules))}_`, mdTrustSettings); } toolConfirmKind = ToolConfirmKind.UserAction; break; From 32036c573c5c68741dec31d606745117d426397a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Oct 2025 12:02:22 +0200 Subject: [PATCH 1045/4355] immediately close inline chat UI when session is cancelled (#270742) fixes https://github.com/microsoft/vscode/issues/265579 --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index e9d5aa5d6a1..d5cded15883 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1192,6 +1192,7 @@ export class InlineChatController1 implements IEditorContribution { }); } + this._resetWidget(); this._messages.fire(Message.CANCEL_SESSION); } From be32f09868e4675ae4c4c0554b413a8253dac884 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 10 Oct 2025 19:12:15 +0900 Subject: [PATCH 1046/4355] Use ^ and $ for new matchCommandLine rules Fixes #267669 --- .../chatAgentTools/browser/runInTerminalHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index e5c7b079e81..153a231f5c1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -7,7 +7,7 @@ import { Separator } from '../../../../../base/common/actions.js'; import { coalesce } from '../../../../../base/common/arrays.js'; import { posix as pathPosix, win32 as pathWin32 } from '../../../../../base/common/path.js'; import { OperatingSystem } from '../../../../../base/common/platform.js'; -import { removeAnsiEscapeCodes } from '../../../../../base/common/strings.js'; +import { escapeRegExpCharacters, removeAnsiEscapeCodes } from '../../../../../base/common/strings.js'; import { localize } from '../../../../../nls.js'; import type { TerminalNewAutoApproveButtonData } from '../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import type { ToolConfirmationAction } from '../../../chat/common/languageModelToolsService.js'; @@ -141,7 +141,7 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str data: { type: 'newRule', rule: { - key: commandLine, + key: `/^${escapeRegExpCharacters(commandLine)}$/`, value: { approve: true, matchCommandLine: true From ebd670832157c4cb5e5a6634a2aa4ad064eebc21 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 10 Oct 2025 11:25:53 +0100 Subject: [PATCH 1047/4355] Increased activity bar icon size --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118660 -> 118608 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index c8922d182bb013621212e70b56441805769d6275..a4057c5db5b37d3ba0ee0169741cae8ae66f27b6 100644 GIT binary patch delta 5262 zcmb_feQ;D&mcRGi_x<|g_3QUuhwh}i)9G~QJ0acyNj&Lj$0P%T2rhw#afodvh0>?u?Tza>ky#! zkJ&$_lD?nkoqO*&=bqp1Zhk|!a6lN$PA-^Jf)L)05L(``x~-?<+|K<7Nv9CX>gnu$ zX63a@yPFUy%s_Zx)v8C@Ry6M(S_rb2;1{O@OcMz}8{|=V)#~2$4f?Hr1AX5kBwXlz zyrZq_DWMmkXc0o>yVY&$dx#xxh4~DS53Xrj{m8B%!PO4~KlVKSWbY03{8fZU*6%u5 z#-6E&@7eXe58p_M_#ZJl@)iCYjDx8wI11%CGRJCnTIF=B45xn27Fw+eXAm^PlGZPH z>-xw7&XxW+WeKda-y;A@H^avY3EsMux{3`;CdVpQz<5LIis-g_L(ub%oRa4Glj8-p zf`)<(1;Yhr3cf023WJ4BMUkSWqJg3>i&qx^w!~Yqwd8tfed*fLk4mqW`ODUn9WT2! zwSH<(dA$5^`L&9sikB-c$0D)z*yYMd<E&jTRX{u}5-*oN4l@E?L zhnt(44?JXhsI{f8|)qUX#L3!gZ%HokV%+VLm(ColId>iykQMNeILy6NfhbtUWC*S)tc`Q!Sh z*Z=OBqGvWfb9#fgVfKb?&oa+0efIYoZ5!)0Zh9{E+`i|&?dwWi@Jr~W_LolW2=6$)Gjr#@o!19f4gS-vs$Exi&rj~&vHMEOn;P0PW6z~m{&KIr zcgNlzUafm|+pFKdw(+%V`&)PLv?hwnSQ?m^CLwgpO2P}&L8a>9T+`0 zI{rcEgT4>0ezK+ww9B!AW@-Wqfv_xMI+>@Msv>BDGAal?og6pJ@SFxC{IDR*V~9hPs0N9M zt#;O7HLU>$YZpm+N^}qg6iO^lY!YKB7iC$r)?*|a;aLgVK1EfPVmi?%*h#H1jmV@0 zb8JV4qLLQYHgc>lkK}u>jxtaP#n5gv1a_&cFyiH&%2=cj(oc~i;Z`PJNjF-@rhBcoesJ~X! zq^d~$S$ zSwZ6|W`&Q=7eXs~S3eY)obx!a0cM`;gWaLeQ~T4Jdcx8apN@41)*1|&&B;;l7F{V8 z5p)M{X#u%%({SR{e^h&u0`}BOZZ8A{SSUl|p`kbwD0wYIQdDSo-BK-$wL4<&?7VFE zJ(G)rMM1+Eb{dARQ+FB$b>`je13sS?rfR~9>h`meoQ~IJf}#lNCXD#qs*)Dcd_Gv( z=XFnhldbF7lhbZ5ovt;5L9-hcb!HnnxxFCK>Ed(?u#u7NJKxrlL7a>HC=ZpQX=nzT z3)Z*pE!^O{gE{SvLY#<4Ed$a!EkMwN?Gpy|5VdQx(vuMQ5M29MEsn#DH+38o%LR{+ zrz(v!s>f-;M8*GXWVsHG>u6z<+(nLUX@PFm;;BR+QwjXj>Yo*HswhU8$-9< z{)d(hZn34q~xk3 zi*mOa6cms{BkvtMW_Qv{nu|(M0@a~;s3q0)adqm@$2rMHccz;$M8&S_J~yLtykdtD zyQ0{$c}cQK($CV>yma+Ww0?el)GD-4^{ncURea`fyB$1l@NS>3+XdcW!Jit{X;(A? zO)u4ClV+r=c)BW%)bd|p(TqHqEcMOk#&4OYgi$hmBa$O|INVg~a>;0e#j zf=$Olz~zBl0O+CWd3kPCW;yAy?kzN3oa`*7*Sgp#JK*L!Tt3q!x>TUQl7yX_^XHPN z%RZL_Zl^=Bmr&6ugb(0RPnBj=P*jb+C>^9zI7I^gxFbYF#vTP>|!vcdIv3<50 zbGI;fCe?R7FIfuV;bbC0DWGvwa6tZ}DN^_N8CpYA#56(6p<4Zi7f2IIVGfq zl>3Q;5eADILav&o1yjwuN(mDN!{VlsumX=6f(a|Kq7Vpx9Yhwk0HzRF;xug_;8X~k zWC=%D28#^C6IJGU*sYP{5d|ZZUz8s*dC`+&;M}strW#l1FAB3N^URz71FM918;38beW(Sl3vd<4R;g+KotdmiSSiYht<=v8@MpCvV~KbIfPm5# zkIv_WU3?-aMoCJ;vE{GPC;0eO%B zBK$7S?sV%J87wzz7It&9M9$J+IDJ&1kLnoo1T?0Rh@=tB7iCnN=CE-bPa5(AHa@~| zah5Cg<*7EOB=aO|=1epa{%PkF?I$>(0(>Nl?niB?69Rtk{1DDadeTTp5iTccQL)?P zq#P171x-lYX_SS8ew(aCJoKCvdZwWj%pg!>bkKc5ss=JNQA5W9C<}>Tb8v%HrJH3M z2p}f6ROE`mKE>R2*tVitpC8EQ*w0{)LKJ9%Mt-_C4pvf?i$Jm(7#7%18u%v5Fvq?I zyo_TnvfOBXkmbg{_TW+j;46ZHs0dX7bTy+Ds0VFC+tDDUWHInzG!jb#k>y!{r8Fr3 zk5_suIC?@$T>Q~_Hpw-~@}LZ_Eo z7B~uq_zCX&7uDzyde`;;{xpOhOP+fYTv4 z(I-Z8Q&dN?0r1{JVHD+!eL*OgQ(k#;C!z5za(8my#U>~K%aPYD7W%#B)dFvK% zS&GIc@F$dTKyckY9=KodK&k;U6i)yi$4YPrj^)P?FQ2MWBNz_^;I$uc+Gi5D-*W|C zvH6PQ#XeuLPYXV1cLpnAHV_XQQFSKJH)akm*n^s{#Oo~qDI2x^*t7-s2p`JC`RM1U z7d?jtfV_cG<6uV$a}e^>#?eS>)>vs$h(>rP!&6)Ue2BuPN||`VW5#QlI7Nb5#`FMQ z1Q=d`R6%Joggtd5&6wEqGA!o293$y0pCi~XkNM1ifB~NPAkXSD!)4wU;J66^XO?qP zA_*b|Ub_!~&2V$X`YgO(yRZw zClK)HelnZ*b&3|ybo(?JdY%B%KG{(CNH7q0^5LLVyU45DbVR@-mQs5HMgs zL>|G85S5p_dd)C^fQWz$V~{X{OhkvF+?p)A%R1w3mFrT})|4}qvMhJXVyUb$`=9PO zJMQeJ(&zD?|2+QJ_y70yaoT%=9vG9F+ZaIz??nhLUACsJYuV8LV+bjO2<3OJ?A*Mf zY1f_&2$lH|e)(9(Q*F!VA38Y)c<;h5(E$XZMJ$9d@T0LEYc_0pJM%*rj5H!AN}g{n4IU9B1!KXd%1Xl?X-^j`JC>ciE;vC`Oz*l@fw zz9s&<#I!_rGFpRb>S~VH3{9w-uxY~0+Hmdg#JLl9PyD8?wQguqVA8RAU;Tmlk;$ji zJHzqRshO^s#~W)KZ_TQob^Fn}N3S+ToBEpW&t5otWX{?-?>yG@*e7#a<_A}uvod-It?OHw!Uh8`}&WbMbF;d z(6r&!bD`&kHnwm4h-v?Vs&%?da_7eo1?2%1gIip7ruauT;Hq zeka?xeCNeoY}dZsExZ4DPs5%Id%oIRyZ6GYwpTk}{diww-?jar{ihF*1M3gmIoNP; z%P}l zzkdAn-yI)&{N>}fPee`}Iaz-4g_FZ?Y{m?#R@sEP;XJIM zJhTNt)QzK|;>kD>tHdEWAImU{$Let*F2J&m(^_|@PhBKZyhxJzBt<5o>~Jf63Z+VC zTHo4`zb(onv0Ai=^8`tLNDxKgAyp?*vuv93#Pol3KQZvu3n8(z98;8of+&vC=naID z!9ZvduJ%N|@kFRBbF4ksfhrT(ZMkJE%CLr2hWTo#;WJd`Vrq?3?J!x=%*$RV_!lW;7g zVVGK8PcQ)Qn20@@c>zvutM>~LoIY9~&|-;+IBEf#90q_|mtn*L+m7@fjfM$S9Udye6)8u1IF2mfq^_V}{n@mK(m=ke4 z=&g<>xUglo805^y)$v%!>#0ss* zTiHlt=>4gwM4l)6ay(&C3=0f?LRe&24>PY)gbj5n7v!AXaM+62r@%T-Ca_~AY=r~r za+U!zY#7}NSx1H+!W5j;j|x#Gh%^Pw1UC%@bR5AT4>wl<7bqh^aVnXJ71LlGgoCGp z;Bo-sCzdSs1mjsl(RejjGsIn&+pG-#8}qU;f^Ms^JE=qIQ=N9Dh_S|k3XzIqMEK8i zg^e<)AyQ-GRFSqeaKkk`0Cgx1r`ksy%&^zkbfJg^ee6dCllU%{-ONPRz}b&XZIm9C z;0PHF{N~Ncs*e5(yO9$juLRYCK+xO6+mV!Kh(R+F31gs_X0qxPwI2 z5vfFVn&8_VqNdC>@(iZ>jQ_M7z-3>>o&SPF-sGGehCS@aBl0Q523#}k%+_J}1}^mX zJM;=%fySW-szFmx6IzsB{Xs1brcZwmOf`G_ZoyJi&F#*2i(BCb=5^;qM65!awwCt zT}`&DESc`f9u=NmGIsQ+U>$h&{OixF%!kLIPJqSqzx=vJhl)d$;0DEM|4=A} zsnhE!k|hW;Rk9pTMKuH}J55?rLUUD_icA5}a9EOxC7?T0MPe`#1yPViN&!l3%;6De zp)1G90o7)BN=n^^N@Qi&omb&=N;YSND>h=NnkZAblmhaR7 zcyyaynGI(`L`DG0WDGw7kN^(uAXFlnLw5Sg^W3=}po$Kg%|Qe~${8Skw+2fixExoc zH{J@Tctq+#h;lj0u*Shi1K02t2pps|9I6By3hIy-ES?&Y)k%*o1S4X-7%dPrf%Zij zgRUtHnybB~Nt*zFL~?iykx+OL+$%An(4r!$5QT_9fILG)!*YlOQ(Yw_1$jjTm{tT( zlG7$r*hM_!dX5nOT7=i#-b3U4uTa!b!du<5^xp? zq$7$#c&{ixaw(caH54o=l88ayumFcMK~NP)1r|J&`ir%SNVvc-3c`_!Vn0O#=l}k- zNXiXKavZ8hvmtJH29hZ#iF!aa0Axvsb3iwU9-xLGGeMT}0&NryAd3cNDurU?v@(@&u4{|Sk8{mPTqUzaVOA^HlT?&Qb}Yv#L{lQ; zDr{IPjewqFBN8OF>{eY>AZv-tm4|c8&5)xC12CNd63zgn2Bon?rsI<$4p!Y9J2$2eY|r zbOS&-*HS(%{$67te$M|lOYy}=I9P*q1duF52qaCD;Tfu`ipUnVehvaHaJic)gN|-eJ_Dig-<~X{bfY*gZg`jbc_(A zjBAyfBKlVYNDo(TdU2(vC>IA&5vo9wKx>bp1?VZX3H2cOR5G)Gh~Mh^?%l-*q><&IM2Gq zMyje~BN5=_ys|*07$8kirmo5sJ9bg<@`JoKheKfh3PUDI%XYvuY{Cv^!|I+LJG!8p z0`fS%#H4lES&hnSCiJ?X%<83TmkC6kawG*ypyZASo{g_0PXS0=dA|yOSCIO1YPLhY zfs#<1Mhmc8!+{7+1^}r5jjW`Xmy4A+k&H!fjae8fG&9X~U)bvn`wD#xPz?$Xnb;TZ zH%HgZ!l!-Vs<01Z_d~pRYXG*e4+)w3c)jXF)8M`<)92tLa>|Yk^cdQO4xyu9>ZrFM zQzBRMs>SkJU~i}apo}TNR0g=@#k3AEO6B4ROd=2~GGq}ziENbNzYx zp`nSD;C*m1Kg@!{sXzb*SQiU%Dv^Yf`1VjS1uTJt6rvoHxRZIgw{W-U(>S6E7;sN? zin3q=?CF&1B9KgutW1YhBKZ9RXVcjan0~+`Iy9yLAnPt_w^3V)GDe}dGp7cy>2yL# zt-+Ei+rD3sndXLj7*ymTD9RzIeTIPz5fjm*iohY=od=InC6}baWAcC-Q=%|8ycrS~ z5#3B76ae}EU~2?SH-S$yv4d$KmgF#~q`P#98V(NZ8gq!EgX!#lFPRPpuHi2V`Az;J zpK;~c`S^gGqEHx>p%?`6!(e8x6%VaRV5pZg zBM>t$SI7zE=DIW=Y08qzvb@=LSN4D_A1}crP&J}+?^WpUUXOzh!ypiMYr9A$zg)^T(d9K0i w=hmQeJpb(4?H6SxSRNi^`2w>t;V!S_-)4MIp1H-wOGbYOZ?Bx#i{EYdZ`dhd4FCWD From 42ec48a04d6e73e62a22bd9bcab499e0a0177d07 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 10 Oct 2025 14:56:26 +0100 Subject: [PATCH 1048/4355] Update button background color for high contrast mode and add border to chat action bar --- src/vs/platform/theme/common/colors/inputColors.ts | 2 +- .../contrib/chat/browser/media/chatEditorController.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/colors/inputColors.ts b/src/vs/platform/theme/common/colors/inputColors.ts index f6a1348a6ae..7608c078e0f 100644 --- a/src/vs/platform/theme/common/colors/inputColors.ts +++ b/src/vs/platform/theme/common/colors/inputColors.ts @@ -118,7 +118,7 @@ export const buttonSeparator = registerColor('button.separator', nls.localize('buttonSeparator', "Button separator color.")); export const buttonBackground = registerColor('button.background', - { dark: '#0E639C', light: '#007ACC', hcDark: null, hcLight: '#0F4A85' }, + { dark: '#0E639C', light: '#007ACC', hcDark: Color.black, hcLight: '#0F4A85' }, nls.localize('buttonBackground', "Button background color.")); export const buttonHoverBackground = registerColor('button.hoverBackground', diff --git a/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css index a574066762a..8be9fd6ba29 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css @@ -19,6 +19,7 @@ border-radius: 2px; background-color: var(--vscode-button-background); color: var(--vscode-button-foreground); + border: 1px solid var(--vscode-contrastBorder); } .chat-diff-change-content-widget .monaco-action-bar .action-item .action-label { From a31b263154ccc458df78499fb2c9ad4cb1541353 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 10 Oct 2025 10:39:28 -0400 Subject: [PATCH 1049/4355] pass back profile instead of just shell (#270605) --- .../browser/tools/runInTerminalTool.ts | 28 +++++++++---------- .../test/browser/runInTerminalTool.test.ts | 14 ++++++---- 2 files changed, 22 insertions(+), 20 deletions(-) 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 ba40c47162d..aefa4413896 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -616,7 +616,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Terminal init - protected async _getCopilotShellOrProfile(): Promise { + protected async _getCopilotProfile(): Promise { const os = await this._osBackend; // Check for chat agent terminal profile first @@ -626,25 +626,25 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // When setting is null, use the previous behavior - const defaultShell = await this._terminalProfileResolverService.getDefaultShell({ + const defaultProfile = await this._terminalProfileResolverService.getDefaultProfile({ os, remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority }); // Force pwsh over cmd as cmd doesn't have shell integration - if (basename(defaultShell) === 'cmd.exe') { - return 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; + if (basename(defaultProfile.path) === 'cmd.exe') { + return { + ...defaultProfile, + path: 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', + profileName: 'PowerShell' + }; } - return defaultShell; + return defaultProfile; } private async _getCopilotShell(): Promise { - const shellOrProfile = await this._getCopilotShellOrProfile(); - if (typeof shellOrProfile === 'string') { - return shellOrProfile; - } - return shellOrProfile.path; + return (await this._getCopilotProfile()).path; } private _getChatTerminalProfile(os: OperatingSystem): ITerminalProfile | undefined { @@ -682,8 +682,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private async _initBackgroundTerminal(chatSessionId: string, termId: string, token: CancellationToken): Promise { this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`); - const shellOrProfile = await this._getCopilotShellOrProfile(); - const toolTerminal = await this._terminalToolCreator.createTerminal(shellOrProfile, token); + const profile = await this._getCopilotProfile(); + const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { @@ -701,8 +701,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._terminalToolCreator.refreshShellIntegrationQuality(cachedTerminal); return cachedTerminal; } - const shellOrProfile = await this._getCopilotShellOrProfile(); - const toolTerminal = await this._terminalToolCreator.createTerminal(shellOrProfile, token); + const profile = await this._getCopilotProfile(); + const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 8c29b60e111..7672927a8a9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -23,6 +23,7 @@ import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import { count } from '../../../../../../base/common/strings.js'; +import { ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -30,8 +31,8 @@ class TestRunInTerminalTool extends RunInTerminalTool { get commandLineAutoApprover() { return this._commandLineAutoApprover; } get sessionTerminalAssociations() { return this._sessionTerminalAssociations; } - getCopilotShellOrProfile() { - return this._getCopilotShellOrProfile(); + getCopilotProfile() { + return this._getCopilotProfile(); } setBackendOs(os: OperatingSystem) { this._osBackend = Promise.resolve(os); @@ -70,7 +71,7 @@ suite('RunInTerminalTool', () => { onDidDisposeSession: chatServiceDisposeEmitter.event }); instantiationService.stub(ITerminalProfileResolverService, { - getDefaultShell: async () => 'pwsh' + getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) }); storageService = instantiationService.get(IStorageService); @@ -951,7 +952,7 @@ suite('RunInTerminalTool', () => { const customProfile = Object.freeze({ path: 'C:\\Windows\\System32\\powershell.exe', args: ['-NoProfile'] }); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, customProfile); - const result = await runInTerminalTool.getCopilotShellOrProfile(); + const result = await runInTerminalTool.getCopilotProfile(); strictEqual(result, customProfile); }); @@ -959,8 +960,9 @@ suite('RunInTerminalTool', () => { runInTerminalTool.setBackendOs(OperatingSystem.Linux); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); - const result = await runInTerminalTool.getCopilotShellOrProfile(); - strictEqual(result, 'pwsh'); // From the mock ITerminalProfileResolverService + const result = await runInTerminalTool.getCopilotProfile(); + strictEqual(typeof result, 'object'); + strictEqual((result as ITerminalProfile).path, 'pwsh'); // From the mock ITerminalProfileResolverService }); }); }); From 22deaf61a50ecf9f21137164c7afecbd30f5cd93 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 10 Oct 2025 11:15:17 -0400 Subject: [PATCH 1050/4355] remove comments from code block before running in the terminal (#270779) --- .../browser/actions/chatCodeblockActions.ts | 3 +- .../contrib/chat/common/codeBlockCleaning.ts | 56 +++++++++++++++++++ .../test/common/codeBlockCleaning.test.ts | 49 ++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 863d366db7d..2a5db594042 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -39,6 +39,7 @@ import { IChatCodeBlockContextProviderService, IChatWidgetService } from '../cha import { DefaultChatTextEditor, ICodeBlockActionContext, ICodeCompareBlockActionContext } from '../codeBlockPart.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { ApplyCodeBlockOperation, InsertCodeBlockOperation } from './codeBlockOperations.js'; +import { stripCommentsForShellExecution } from '../../common/codeBlockCleaning.js'; const shellLangIds = [ 'fish', @@ -482,7 +483,7 @@ export function registerChatCodeBlockActions() { terminalGroupService.showPanel(true); } - terminal.runCommand(context.code, false); + terminal.runCommand(stripCommentsForShellExecution(context.code), false); if (isResponseVM(context.element)) { chatService.notifyUserAction({ diff --git a/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts b/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts new file mode 100644 index 00000000000..6a03100e4c4 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Strip comments from a shell-like code snippet before executing it in the terminal. + * + * Behaviour: + * - Removes /* *\/ block comments (non-nested) across lines. + * - Removes // line comments unless part of a URL protocol pattern like `http://` or `file://`. + * - Removes # line comments when the hash is the first non-whitespace character (or at column 0), + * but preserves shebangs (#!) and leaves inline hashes that are part of tokens. + * - Preserves shebang line entirely. + * - Trims leading/trailing blank lines and surrounding whitespace at the very end. + */ +export function stripCommentsForShellExecution(code: string): string { + // Fast return for empty input + if (!code) { + return ''; + } + + // 1) Remove block comments + const withoutBlocks = code.replace(/\/\*[\s\S]*?\*\//g, ''); + + // 2) Process each line for // and # + const lines = withoutBlocks.split(/\r?\n/); + const processed = lines.map(line => { + // Preserve shebangs (e.g. #!/usr/bin/env bash) + if (/^\s*#!/.test(line)) { + return line; + } + + // Remove '//' comments unless inside a protocol pattern '://' + const slashIdx = line.indexOf('//'); + if (slashIdx >= 0) { + const protoIdx = line.lastIndexOf('://', slashIdx); + if (protoIdx === -1) { + line = line.slice(0, slashIdx); + } + } + + // Remove shell-style '#' comments unless it's a shebang (already handled) + // Treat as comment only if at start or preceded by whitespace + const hashIdx = line.indexOf('#'); + if (hashIdx >= 0) { + if (hashIdx === 0 || /\s/.test(line[hashIdx - 1])) { + line = line.slice(0, hashIdx); + } + } + + return line; + }); + + return processed.join('\n').trim(); +} diff --git a/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts b/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts new file mode 100644 index 00000000000..9abf0d0aae7 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { stripCommentsForShellExecution } from '../../common/codeBlockCleaning.js'; + +suite('ChatCodeBlockCleaning', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('removes block comments', () => { + const input = 'echo 1; /* remove this */\necho 2'; + assert.strictEqual(stripCommentsForShellExecution(input), 'echo 1; \necho 2'); + }); + + test('removes line // comments but keeps protocols', () => { + const input = 'echo 1 // comment\n# full hash comment\nhttp://example.com//still'; + const expected = 'echo 1 \n\nhttp://example.com//still'; + assert.strictEqual(stripCommentsForShellExecution(input), expected); + }); + + test('preserves shebang', () => { + const input = '#!/usr/bin/env bash\n# comment\necho 1'; + const expected = '#!/usr/bin/env bash\n\necho 1'; + assert.strictEqual(stripCommentsForShellExecution(input), expected); + }); + + test('hash mid-line not preceded by space is preserved', () => { + const input = 'VAR=foo#bar\necho done'; + assert.strictEqual(stripCommentsForShellExecution(input), 'VAR=foo#bar\necho done'); + }); + + test('hash preceded by whitespace becomes comment', () => { + const input = 'echo 1 # trailing comment'; + assert.strictEqual(stripCommentsForShellExecution(input), 'echo 1'); + }); + + test('empty input', () => { + assert.strictEqual(stripCommentsForShellExecution(''), ''); + }); + + test('comment above input', () => { + const input = '# comment\necho 1'; + const expected = 'echo 1'; + assert.strictEqual(stripCommentsForShellExecution(input), expected); + }); +}); From bc7b093dd2e85153a6779903d721769cdfa4e86b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 10 Oct 2025 11:31:41 -0400 Subject: [PATCH 1051/4355] use chat function (#270780) --- .../workbench/contrib/chat/browser/chatAccessibilityService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 5ff60940c6d..9ece65d4f73 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableMap, DisposableStore } from '../../../../base/com import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { AccessibilityProgressSignalScheduler } from '../../../../platform/accessibilitySignal/browser/progressAccessibilitySignalScheduler.js'; -import { IChatAccessibilityService } from './chat.js'; +import { IChatAccessibilityService, showChatWidgetInViewOrEditor } from './chat.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; @@ -128,6 +128,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi disposables.add(Event.once(notification.onClick)(async () => { await this._hostService.focus(targetWindow, { mode: FocusMode.Force }); + await this._instantiationService.invokeFunction(showChatWidgetInViewOrEditor, widget); widget.input.focus(); disposables.dispose(); this.notifications.delete(disposables); From 09575e1f1b83fd3893d9abf97e690345f57ad4d8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:34:06 +0000 Subject: [PATCH 1052/4355] Fix terminal tool incorrectly suggesting to send literal "any key" text (#270426) --- .../browser/tools/monitoring/outputMonitor.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 6971e62fe38..a22daf83ec4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -393,7 +393,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } const lastFiveLines = execution.getOutput(this._lastPromptMarker).trimEnd().split('\n').slice(-5).join('\n'); const promptText = - `Analyze the following terminal output. If it contains a prompt requesting user input (such as a confirmation, selection, or yes/no question) and that prompt has NOT already been answered, extract the prompt text. The prompt may ask to choose from a set. If so, extract the possible options as a JSON object with keys 'prompt', 'options' (an array of strings or an object with option to description mappings), and 'freeFormInput': false. If no options are provided, and free form input is requested, for example: Password:, return the word freeFormInput. For example, if the options are "[Y] Yes [A] Yes to All [N] No [L] No to All [C] Cancel", the option to description mappings would be {"Y": "Yes", "A": "Yes to All", "N": "No", "L": "No to All", "C": "Cancel"}. If there is no such prompt, return null. If the option is ambiguous, like "any key", return null. + `Analyze the following terminal output. If it contains a prompt requesting user input (such as a confirmation, selection, or yes/no question) and that prompt has NOT already been answered, extract the prompt text. The prompt may ask to choose from a set. If so, extract the possible options as a JSON object with keys 'prompt', 'options' (an array of strings or an object with option to description mappings), and 'freeFormInput': false. If no options are provided, and free form input is requested, for example: Password:, return the word freeFormInput. For example, if the options are "[Y] Yes [A] Yes to All [N] No [L] No to All [C] Cancel", the option to description mappings would be {"Y": "Yes", "A": "Yes to All", "N": "No", "L": "No to All", "C": "Cancel"}. If there is no such prompt, return null. If the option is ambiguous or non-specific (like "any key" or "some key"), return null. Examples: 1. Output: "Do you want to overwrite? (y/n)" Response: {"prompt": "Do you want to overwrite?", "options": ["y", "n"], "freeFormInput": false} @@ -413,6 +413,12 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { 6. Output: "Continue [y/N]" Response: {"prompt": "Continue", "options": ["y", "N"], "freeFormInput": false} + 7. Output: "Press any key to close the terminal." + Response: null + + 8. Output: "Terminal will be reused by tasks, press any key to close it." + Response: null + Alternatively, the prompt may request free form input, for example: 1. Output: "Enter your username:" Response: {"prompt": "Enter your username:", "freeFormInput": true, "options": []} @@ -438,10 +444,25 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if (this._lastPrompt === obj.prompt) { return; } + // Filter out non-specific options like "any key" + const NON_SPECIFIC_OPTIONS = new Set(['any key', 'some key', 'a key']); + const isNonSpecificOption = (option: string): boolean => { + const lowerOption = option.toLowerCase().trim(); + return NON_SPECIFIC_OPTIONS.has(lowerOption); + }; if (Array.isArray(obj.options) && obj.options.every(isString)) { - return { prompt: obj.prompt, options: obj.options, detectedRequestForFreeFormInput: obj.freeFormInput }; + const filteredOptions = obj.options.filter(opt => !isNonSpecificOption(opt)); + if (filteredOptions.length === 0) { + return undefined; + } + return { prompt: obj.prompt, options: filteredOptions, detectedRequestForFreeFormInput: obj.freeFormInput }; } else if (isObject(obj.options) && Object.values(obj.options).every(isString)) { - return { prompt: obj.prompt, options: Object.keys(obj.options), descriptions: Object.values(obj.options), detectedRequestForFreeFormInput: obj.freeFormInput }; + const keys = Object.keys(obj.options).filter(key => !isNonSpecificOption(key)); + if (keys.length === 0) { + return undefined; + } + const descriptions = keys.map(key => (obj.options as Record)[key]); + return { prompt: obj.prompt, options: keys, descriptions, detectedRequestForFreeFormInput: obj.freeFormInput }; } } } From 57409952f0622a281c5249673684c29911bdbf86 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:01:16 -0700 Subject: [PATCH 1053/4355] re-enable tests to look for failure (#270299) * re-enable tests to look for failure * edit to focus back in the cell * make command available for smoke test --- .../browser/contrib/navigation/arrow.ts | 11 +++++----- .../notebook/common/notebookContextKeys.ts | 1 + test/automation/src/notebook.ts | 12 ++++++---- .../smoke/src/areas/notebook/notebook.test.ts | 22 +++++++++++-------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index e07c0d65973..4c8432c86f8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js'; import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { EditorExtensionsRegistry } from '../../../../../../editor/browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; -import { localize } from '../../../../../../nls.js'; +import { localize, localize2 } from '../../../../../../nls.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../../platform/accessibility/common/accessibility.js'; import { Action2, registerAction2 } from '../../../../../../platform/actions/common/actions.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../../../platform/configuration/common/configurationRegistry.js'; @@ -21,7 +21,7 @@ import { InlineChatController } from '../../../../inlineChat/browser/inlineChatC import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from '../../controller/coreActions.js'; import { CellEditState } from '../../notebookBrowser.js'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NOTEBOOK_EDITOR_CURSOR_LINE_BOUNDARY } from '../../../common/notebookCommon.js'; -import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED, IS_COMPOSITE_NOTEBOOK } from '../../../common/notebookContextKeys.js'; +import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED, IS_COMPOSITE_NOTEBOOK, NOTEBOOK_OR_COMPOSITE_IS_ACTIVE_EDITOR } from '../../../common/notebookContextKeys.js'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; @@ -296,9 +296,10 @@ registerAction2(class extends NotebookCellAction { constructor() { super({ id: FOCUS_IN_OUTPUT_COMMAND_ID, - title: localize('focusOutput', 'Focus In Active Cell Output'), + title: localize2('focusOutput', 'Focus In Active Cell Output'), + f1: true, keybinding: [{ - when: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK.negate(), IsWindowsContext), + when: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK.negate(), IsWindowsContext, NOTEBOOK_CELL_HAS_OUTPUTS), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib }, { @@ -306,7 +307,7 @@ registerAction2(class extends NotebookCellAction { mac: { primary: KeyMod.WinCtrl | KeyMod.CtrlCmd | KeyCode.DownArrow, }, weight: KeybindingWeight.WorkbenchContrib }], - precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS) + precondition: NOTEBOOK_OR_COMPOSITE_IS_ACTIVE_EDITOR }); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index f4eb63f37dc..3da98a7e90c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -18,6 +18,7 @@ export const MOST_RECENT_REPL_EDITOR = new RawContextKey('mostRecentRepl export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', NOTEBOOK_EDITOR_ID); export const INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', INTERACTIVE_WINDOW_EDITOR_ID); export const REPL_NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', REPL_EDITOR_ID); +export const NOTEBOOK_OR_COMPOSITE_IS_ACTIVE_EDITOR = ContextKeyExpr.or(NOTEBOOK_IS_ACTIVE_EDITOR, INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR, REPL_NOTEBOOK_IS_ACTIVE_EDITOR); export const IS_COMPOSITE_NOTEBOOK = new RawContextKey('isCompositeNotebook', false); // Editor keys diff --git a/test/automation/src/notebook.ts b/test/automation/src/notebook.ts index 091e9f3656b..2910c0187d5 100644 --- a/test/automation/src/notebook.ts +++ b/test/automation/src/notebook.ts @@ -7,7 +7,9 @@ import { Code } from './code'; import { QuickAccess } from './quickaccess'; import { QuickInput } from './quickinput'; -const activeRowSelector = `.notebook-editor .monaco-list-row.focused`; +const anyRowSelector = `.notebook-editor .monaco-list-row`; +const activeRowSelector = `${anyRowSelector}.focused`; +const activeMarkdownRowSelector = `${activeRowSelector}.markdown-cell-row`; export class Notebook { @@ -18,11 +20,13 @@ export class Notebook { } async openNotebook() { + await this.code.whenWorkbenchRestored(); await this.quickAccess.openFileQuickAccessAndWait('notebook.ipynb', 1); await this.quickInput.selectQuickInputElement(0); - await this.code.waitForElement(activeRowSelector); + await this.code.waitForElement(anyRowSelector); await this.focusFirstCell(); + await this.code.waitForElement(activeRowSelector); } async focusNextCell() { @@ -68,7 +72,7 @@ export class Notebook { } async waitForMarkdownContents(markdownSelector: string, text: string): Promise { - const selector = `${activeRowSelector} .markdown ${markdownSelector}`; + const selector = `${activeMarkdownRowSelector} ${markdownSelector}`; await this.code.waitForTextContent(selector, text); } @@ -86,7 +90,7 @@ export class Notebook { async focusInCellOutput(): Promise { await this.quickAccess.runCommand('notebook.cell.focusInOutput'); - await this.code.waitForActiveElement('webview, .webview'); + await this.code.waitForActiveElement('iframe.webview.ready'); } async focusOutCellOutput(): Promise { diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index 130b49a1473..f59c74ec2e6 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -25,6 +25,7 @@ export function setup(logger: Logger) { cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder }); }); + // the heap snapshot fails to parse it.skip('check heap leaks', async function () { const app = this.app as Application; await app.profiler.checkHeapLeaks(['NotebookTextModel', 'NotebookCellTextModel', 'NotebookEventDispatcher'], async () => { @@ -34,7 +35,7 @@ export function setup(logger: Logger) { }); }); - it.skip('check object leaks', async function () { + it('check object leaks', async function () { const app = this.app as Application; await app.profiler.checkObjectLeaks(['NotebookTextModel', 'NotebookCellTextModel', 'NotebookEventDispatcher'], async () => { await app.workbench.notebook.openNotebook(); @@ -43,7 +44,7 @@ export function setup(logger: Logger) { }); }); - it.skip('inserts/edits code cell', async function () { + it('inserts/edits code cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.focusNextCell(); @@ -59,28 +60,31 @@ export function setup(logger: Logger) { await app.workbench.notebook.insertNotebookCell('markdown'); await app.workbench.notebook.waitForTypeInEditor('## hello2! '); await app.workbench.notebook.stopEditingCell(); - await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!'); + // TODO: markdown row selectors haven't been updated to look in the webview + await app.workbench.notebook.waitForMarkdownContents('', ''); }); - it.skip('moves focus as it inserts/deletes a cell', async function () { + it('moves focus as it inserts/deletes a cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.insertNotebookCell('code'); await app.workbench.notebook.waitForActiveCellEditorContents(''); await app.workbench.notebook.stopEditingCell(); await app.workbench.notebook.deleteActiveCell(); - await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell'); + await app.workbench.notebook.editCell(); + await app.workbench.notebook.waitForTypeInEditor('## hello2!'); }); - it.skip('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139270 + it('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139270 const app = this.app as Application; await app.workbench.notebook.openNotebook(); - await app.workbench.notebook.executeActiveCell(); + // first cell is a code cell that already has output await app.workbench.notebook.focusInCellOutput(); - await app.workbench.notebook.focusOutCellOutput(); - await app.workbench.notebook.waitForActiveCellEditorContents('code()'); + await app.workbench.notebook.editCell(); + await app.workbench.notebook.waitForActiveCellEditorContents('print(1)'); }); + // broken: there is no kernel available to execute code it.skip('cell action execution', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139270 const app = this.app as Application; await app.workbench.notebook.openNotebook(); From 4538d521a783050a92a7894ff24b9273ffa04233 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:41:33 -0700 Subject: [PATCH 1054/4355] Fix last errors --- .../contrib/colorPicker/browser/colorPickerContribution.ts | 6 +++++- .../browser/promptSyntax/promptToolsCodeLensProvider.ts | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerContribution.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerContribution.ts index 68f4e89edc8..9ac764c49b2 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerContribution.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerContribution.ts @@ -43,7 +43,11 @@ CommandsRegistry.registerCommand('_executeDocumentColorProvider', function (acce CommandsRegistry.registerCommand('_executeColorPresentationProvider', function (accessor, ...args) { const [color, context] = args; - const { uri, range } = context; + if (!context) { + return; + } + + const { uri, range } = context as { uri?: unknown; range?: unknown }; if (!(uri instanceof URI) || !Array.isArray(color) || color.length !== 4 || !Range.isIRange(range)) { throw illegalArgument(); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 28f213135e1..03ede182c37 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -19,6 +19,7 @@ import { IPromptsService } from '../../common/promptSyntax/service/promptsServic import { registerEditorFeature } from '../../../../../editor/common/editorFeatures.js'; import { PromptFileRewriter } from './promptFileRewriter.js'; import { Range } from '../../../../../editor/common/core/range.js'; +import { IEditorModel } from '../../../../../editor/common/editorCommon.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { @@ -38,8 +39,9 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider this._register(CommandsRegistry.registerCommand(this.cmdId, (_accessor, ...args) => { const [first, second, third] = args; - if (isITextModel(first) && Range.isIRange(second) && Array.isArray(third)) { - this.updateTools(first, Range.lift(second), third); + const model = first as IEditorModel; + if (isITextModel(model) && Range.isIRange(second) && Array.isArray(third)) { + this.updateTools(model as ITextModel, Range.lift(second), third); } })); } From 32fbf1d72e2ff27c046c7f6b425140408a7e00fa Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:52:12 -0700 Subject: [PATCH 1055/4355] skip all but the simplest (#270790) --- test/smoke/src/areas/notebook/notebook.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index f59c74ec2e6..790d7dea627 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -44,7 +44,7 @@ export function setup(logger: Logger) { }); }); - it('inserts/edits code cell', async function () { + it.skip('inserts/edits code cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.focusNextCell(); @@ -64,7 +64,7 @@ export function setup(logger: Logger) { await app.workbench.notebook.waitForMarkdownContents('', ''); }); - it('moves focus as it inserts/deletes a cell', async function () { + it.skip('moves focus as it inserts/deletes a cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.insertNotebookCell('code'); @@ -75,7 +75,7 @@ export function setup(logger: Logger) { await app.workbench.notebook.waitForTypeInEditor('## hello2!'); }); - it('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139270 + it.skip('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139270 const app = this.app as Application; await app.workbench.notebook.openNotebook(); // first cell is a code cell that already has output From 26134ef4a787abdadaa53258ed36150af0fc5ee6 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:55:35 -0700 Subject: [PATCH 1056/4355] Fix test --- .../workbench/api/test/browser/extHostApiCommands.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index dde4280941e..d31d14478da 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -65,6 +65,7 @@ import { TestInstantiationService } from '../../../../platform/instantiation/tes import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js'; import { timeout } from '../../../../base/common/async.js'; +import { FormattingOptions } from '../../../../editor/common/languages.js'; function assertRejects(fn: () => Promise, message: string = 'Expected rejection') { return fn().then(() => assert.ok(false, message), _err => assert.ok(true)); @@ -298,7 +299,10 @@ suite('ExtHostLanguageFeatureCommands', function () { })); await rpcProtocol.sync(); - const edits = await commands.executeCommand('vscode.executeFormatDocumentProvider', model.uri); + const edits = await commands.executeCommand('vscode.executeFormatDocumentProvider', model.uri, { + insertSpaces: false, + tabSize: 4, + } satisfies FormattingOptions); assert.strictEqual(edits.length, 1); }); From 15f50a68c90258ec5a842930dcf1451af60c172a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:12:52 -0700 Subject: [PATCH 1057/4355] Fix arguments --- src/vs/workbench/api/browser/mainThreadNotebook.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 9730c2d9d9c..4aec2291c33 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -210,9 +210,7 @@ CommandsRegistry.registerCommand('_executeDataToNotebook', async (accessor, ...a return new SerializableObjectWithBuffers(NotebookDto.toNotebookDataDto(dto)); }); -CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, args: [string, SerializableObjectWithBuffers]) => { - - const [notebookType, dto] = args; +CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, notebookType: string, dto: SerializableObjectWithBuffers) => { assertType(typeof notebookType === 'string', 'string'); const notebookService = accessor.get(INotebookService); From 63173cdabcbb9f199e520d2632a19ea9bc4fccdc Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:35:41 -0700 Subject: [PATCH 1058/4355] add logging service to notebook text model to trace edit activity (#270786) * add logging service to text model and small cleanup * log output edits * less redundant category * Update src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../services/notebookLoggingServiceImpl.ts | 4 ++ .../common/model/notebookCellTextModel.ts | 36 ++++++++++------ .../common/model/notebookTextModel.ts | 41 +++++++++++++------ .../notebook/common/notebookLoggingService.ts | 1 + .../test/browser/notebookViewModel.test.ts | 5 ++- .../test/browser/testNotebookEditor.ts | 32 ++++++++++++++- 6 files changed, 92 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts index 93e248e3c74..40081064446 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts @@ -24,6 +24,10 @@ export class NotebookLoggingService extends Disposable implements INotebookLoggi this._logger = this._register(loggerService.createLogger(logChannelId, { name: nls.localize('renderChannelName', "Notebook"), group: windowLogGroup })); } + trace(category: string, output: string): void { + this._logger.trace(`[${category}] ${output}`); + } + debug(category: string, output: string): void { this._logger.debug(`[${category}] ${output}`); } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 21e5e9a987f..e712afb30a5 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -15,12 +15,13 @@ import { createTextBuffer, TextModel } from '../../../../../editor/common/model/ import { PLAINTEXT_LANGUAGE_ID } from '../../../../../editor/common/languages/modesRegistry.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js'; -import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellDto2, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientCellMetadata, TransientOptions } from '../notebookCommon.js'; +import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellDto2, ICellOutput, IOutputItemDto, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientCellMetadata, TransientOptions } from '../notebookCommon.js'; import { ThrottledDelayer } from '../../../../../base/common/async.js'; import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js'; import { toFormattedString } from '../../../../../base/common/jsonFormatter.js'; import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { splitLines } from '../../../../../base/common/strings.js'; +import { INotebookLoggingService } from '../notebookLoggingService.js'; export class NotebookCellTextModel extends Disposable implements ICell { private readonly _onDidChangeTextModel = this._register(new Emitter()); @@ -190,26 +191,34 @@ export class NotebookCellTextModel extends Disposable implements ICell { private _hasLanguageSetExplicitly: boolean = false; get hasLanguageSetExplicitly(): boolean { return this._hasLanguageSetExplicitly; } + private _source: string; + private _language: string; + private _mime: string | undefined; + public readonly cellKind: CellKind; + public readonly collapseState: NotebookCellCollapseState | undefined; + constructor( readonly uri: URI, public readonly handle: number, - private readonly _source: string, - private _language: string, - private _mime: string | undefined, - public readonly cellKind: CellKind, - outputs: IOutputDto[], - metadata: NotebookCellMetadata | undefined, - internalMetadata: NotebookCellInternalMetadata | undefined, - public readonly collapseState: NotebookCellCollapseState | undefined, + cell: ICellDto2, public readonly transientOptions: TransientOptions, private readonly _languageService: ILanguageService, private readonly _defaultEOL: model.DefaultEndOfLine, + defaultCollapseConfig: NotebookCellDefaultCollapseConfig | undefined, private readonly _languageDetectionService: ILanguageDetectionService | undefined = undefined, + private readonly _notebookLoggingService: INotebookLoggingService ) { super(); - this._outputs = outputs.map(op => new NotebookCellOutputTextModel(op)); - this._metadata = metadata ?? {}; - this._internalMetadata = internalMetadata ?? {}; + this._source = cell.source; + this._language = cell.language; + this._mime = cell.mime; + this.cellKind = cell.cellKind; + // Compute collapse state: use cell's state if provided, otherwise use default config for this cell kind + const defaultConfig = cell.cellKind === CellKind.Code ? defaultCollapseConfig?.codeCell : defaultCollapseConfig?.markupCell; + this.collapseState = cell.collapseState ?? (defaultConfig ?? undefined); + this._outputs = cell.outputs.map(op => new NotebookCellOutputTextModel(op)); + this._metadata = cell.metadata ?? {}; + this._internalMetadata = cell.internalMetadata ?? {}; } enableAutoLanguageDetection() { @@ -322,6 +331,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { } spliceNotebookCellOutputs(splice: NotebookCellOutputsSplice): void { + this._notebookLoggingService.trace('textModelEdits', `splicing outputs at ${splice.start} length: ${splice.deleteCount} with ${splice.newOutputs.length} new outputs`); if (splice.deleteCount > 0 && splice.newOutputs.length > 0) { const commonLen = Math.min(splice.deleteCount, splice.newOutputs.length); // update @@ -349,6 +359,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { return false; } + this._notebookLoggingService.trace('textModelEdits', `replacing an output item at index ${outputIndex}`); const output = this.outputs[outputIndex]; // convert to dto and dispose the cell output model output.replaceData({ @@ -369,6 +380,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { } const output = this.outputs[outputIndex]; + this._notebookLoggingService.trace('textModelEdits', `${append ? 'appending' : 'replacing'} ${items.length} output items to for output index ${outputIndex}`); if (append) { output.appendData(items); } else { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index e2b18cb470d..9b553ef1de8 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -23,8 +23,9 @@ import { IModelContentChangedEvent } from '../../../../../editor/common/textMode import { IResourceUndoRedoElement, IUndoRedoElement, IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, UndoRedoGroup } from '../../../../../platform/undoRedo/common/undoRedo.js'; import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js'; import { SnapshotContext } from '../../../../services/workingCopy/common/fileWorkingCopy.js'; -import { CellEditType, CellKind, CellUri, diff, ICell, ICellDto2, ICellEditOperation, ICellOutput, INotebookSnapshotOptions, INotebookTextModel, IOutputDto, IOutputItemDto, ISelectionState, NotebookCellCollapseState, NotebookCellDefaultCollapseConfig, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, NullablePartialNotebookCellInternalMetadata, NullablePartialNotebookCellMetadata, TransientOptions } from '../notebookCommon.js'; +import { CellEditType, CellUri, diff, ICell, ICellDto2, ICellEditOperation, ICellOutput, INotebookSnapshotOptions, INotebookTextModel, IOutputDto, IOutputItemDto, ISelectionState, NotebookCellDefaultCollapseConfig, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata, NotebookTextModelChangedEvent, NotebookTextModelWillAddRemoveEvent, NullablePartialNotebookCellInternalMetadata, NullablePartialNotebookCellMetadata, TransientOptions } from '../notebookCommon.js'; import { INotebookExecutionStateService } from '../notebookExecutionStateService.js'; +import { INotebookLoggingService } from '../notebookLoggingService.js'; import { CellMetadataEdit, MoveCellEdit, SpliceCellsEdit } from './cellEdit.js'; import { NotebookCellOutputTextModel } from './notebookCellOutputTextModel.js'; import { NotebookCellTextModel } from './notebookCellTextModel.js'; @@ -252,6 +253,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel @ILanguageService private readonly _languageService: ILanguageService, @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + @INotebookLoggingService private readonly _notebookLoggingService: INotebookLoggingService ) { super(); this.transientOptions = options; @@ -309,6 +311,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._overwriteAlternativeVersionId(alternativeVersionId); } ); + + this._notebookLoggingService.trace('notebookTextModel', `Initialized notebook text model for ${uri.toString()}`); } setCellCollapseDefault(collapseConfig: NotebookCellDefaultCollapseConfig | undefined) { @@ -323,9 +327,17 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const mainCells = cells.map(cell => { const cellHandle = this._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); - const collapseState = this._getDefaultCollapseState(cell); - return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.mime, cell.cellKind, cell.outputs, cell.metadata, cell.internalMetadata, collapseState, this.transientOptions, this._languageService, - this._modelService.getCreationOptions(cell.language, cellUri, false).defaultEOL, this._languageDetectionService); + return new NotebookCellTextModel( + cellUri, + cellHandle, + cell, + this.transientOptions, + this._languageService, + this._modelService.getCreationOptions(cell.language, cellUri, false).defaultEOL, + this._defaultCollapseConfig, + this._languageDetectionService, + this._notebookLoggingService + ); }); for (let i = 0; i < mainCells.length; i++) { @@ -621,6 +633,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean { + this._notebookLoggingService.trace('textModelEdits', `Begin applying ${rawEdits.length} raw edits`); this._pauseableEmitter.pause(); try { this._operationManager.pushStackElement(this._alternativeVersionId, undefined); @@ -648,6 +661,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel // Broadcast changes this._pauseableEmitter.fire({ rawEvents: [], versionId: this.versionId, synchronous: synchronous, endSelectionState: endSelections }); + this._notebookLoggingService.trace('textModelEdits', `End applying ${rawEdits.length} raw edits`); } } } finally { @@ -821,11 +835,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return mergedEdits; } - private _getDefaultCollapseState(cellDto: ICellDto2): NotebookCellCollapseState | undefined { - const defaultConfig = cellDto.cellKind === CellKind.Code ? this._defaultCollapseConfig?.codeCell : this._defaultCollapseConfig?.markupCell; - return cellDto.collapseState ?? (defaultConfig ?? undefined); - } - private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void { if (count === 0 && cellDtos.length === 0) { @@ -849,13 +858,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const cells = cellDtos.map(cellDto => { const cellHandle = this._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); - const collapseState = this._getDefaultCollapseState(cellDto); + if (!cellDto.outputs) { + cellDto.outputs = []; + } const cell = new NotebookCellTextModel( - cellUri, cellHandle, - cellDto.source, cellDto.language, cellDto.mime, cellDto.cellKind, cellDto.outputs || [], cellDto.metadata, cellDto.internalMetadata, collapseState, this.transientOptions, + cellUri, + cellHandle, + cellDto, + this.transientOptions, this._languageService, this._modelService.getCreationOptions(cellDto.language, cellUri, false).defaultEOL, - this._languageDetectionService + this._defaultCollapseConfig, + this._languageDetectionService, + this._notebookLoggingService ); const textModel = this._modelService.getModel(cellUri); if (textModel && textModel instanceof TextModel) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts b/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts index 148f31569ec..f851c332755 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts @@ -13,4 +13,5 @@ export interface INotebookLoggingService { warn(category: string, output: string): void; error(category: string, output: string): void; debug(category: string, output: string): void; + trace(category: string, output: string): void; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index d12be746e53..44c613b094f 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -32,6 +32,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { mainWindow } from '../../../../../base/browser/window.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js'; +import { INotebookLoggingService } from '../../common/notebookLoggingService.js'; suite('NotebookViewModel', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -45,6 +46,7 @@ suite('NotebookViewModel', () => { let languageService: ILanguageService; let languageDetectionService: ILanguageDetectionService; let notebookExecutionStateService: INotebookExecutionStateService; + let notebookLogger: INotebookLoggingService; suiteSetup(() => { disposables = new DisposableStore(); @@ -56,6 +58,7 @@ suite('NotebookViewModel', () => { languageService = instantiationService.get(ILanguageService); languageDetectionService = instantiationService.get(ILanguageDetectionService); notebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); + notebookLogger = instantiationService.get(INotebookLoggingService); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); @@ -64,7 +67,7 @@ suite('NotebookViewModel', () => { suiteTeardown(() => disposables.dispose()); test('ctor', function () { - const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService, notebookExecutionStateService); + const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService, notebookExecutionStateService, notebookLogger); const model = new NotebookEditorTestModel(notebook); const options = new NotebookOptions(mainWindow, false, undefined, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService)); const eventDispatcher = new NotebookEventDispatcher(); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 37eb0b35ad1..8731f5e2c3f 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -71,6 +71,16 @@ import { INotebookOutlineEntryFactory, NotebookOutlineEntryFactory } from '../.. import { IOutlineService } from '../../../../services/outline/browser/outline.js'; import { DefaultEndOfLine } from '../../../../../editor/common/model.js'; import { ITextResourcePropertiesService } from '../../../../../editor/common/services/textResourceConfiguration.js'; +import { INotebookLoggingService } from '../../common/notebookLoggingService.js'; + +class NullNotebookLoggingService implements INotebookLoggingService { + _serviceBrand: undefined; + info(category: string, output: string): void { } + warn(category: string, output: string): void { } + error(category: string, output: string): void { } + debug(category: string, output: string): void { } + trace(category: string, message: string): void { } +} export class TestCell extends NotebookCellTextModel { constructor( @@ -82,7 +92,26 @@ export class TestCell extends NotebookCellTextModel { outputs: IOutputDto[], languageService: ILanguageService, ) { - super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, Mimes.text, cellKind, outputs, undefined, undefined, undefined, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, languageService, DefaultEndOfLine.LF); + super( + CellUri.generate(URI.parse('test:///fake/notebook'), handle), + handle, + { + source, + language, + mime: Mimes.text, + cellKind, + outputs, + metadata: undefined, + internalMetadata: undefined, + collapseState: undefined + }, + { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, + languageService, + DefaultEndOfLine.LF, + undefined, // defaultCollapseConfig + undefined, // languageDetectionService + new NullNotebookLoggingService() + ); } } @@ -207,6 +236,7 @@ export function setupInstantiationService(disposables: Pick() { override registerOutlineCreator() { return { dispose() { } }; } }); instantiationService.stub(INotebookCellOutlineDataSourceFactory, instantiationService.createInstance(NotebookCellOutlineDataSourceFactory)); instantiationService.stub(INotebookOutlineEntryFactory, instantiationService.createInstance(NotebookOutlineEntryFactory)); + instantiationService.stub(INotebookLoggingService, new NullNotebookLoggingService()); instantiationService.stub(ILanguageDetectionService, new class MockLanguageDetectionService implements ILanguageDetectionService { _serviceBrand: undefined; From 257898b1bccf5a6055c21848a7badfa7abb4ef27 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 10 Oct 2025 15:31:10 -0400 Subject: [PATCH 1059/4355] don't play diff sound deleted unless it's relevant in accessible view (#270808) fixes #257095 --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 8e9cd9ddeaf..047c4a377e6 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -200,6 +200,9 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi } private _playDiffSignals(): void { + if (this._currentProvider?.id !== AccessibleViewProviderId.DiffEditor && this._currentProvider?.id !== AccessibleViewProviderId.InlineCompletions) { + return; + } const position = this._editorWidget.getPosition(); const model = this._editorWidget.getModel(); if (!position || !model) { From 2b572da8cad0c2f1caaf6e15565782b54045f160 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 10 Oct 2025 13:06:42 -0700 Subject: [PATCH 1060/4355] Change icon on the 'View extension details' button from eye to Info --- .../browser/actions/manageTrustedExtensionsForAccountAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts index 69a0f341c29..713147789f4 100644 --- a/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts @@ -42,7 +42,7 @@ interface TrustedExtensionsQuickPickItem extends IQuickPickItem { class ManageTrustedExtensionsForAccountActionImpl { private readonly _viewDetailsButton = { tooltip: localize('viewExtensionDetails', "View extension details"), - iconClass: ThemeIcon.asClassName(Codicon.eye), + iconClass: ThemeIcon.asClassName(Codicon.info), }; private readonly _managePreferencesButton = { From 32064a768b4ce5fcd6d154dca1414ec817d12e82 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:48:30 -0700 Subject: [PATCH 1061/4355] Move Authorization Server Metadata fetching to oauth.ts (#270797) * Move Authorization Server Metadata fetching to oauth.ts * Simplified MCP code * Keep all oauth stuff in one place * tests! * Update src/vs/base/test/common/oauth.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/base/common/oauth.ts | 114 ++++++ src/vs/base/test/common/oauth.test.ts | 438 ++++++++++++++++++++++ src/vs/workbench/api/common/extHostMcp.ts | 90 +---- 3 files changed, 562 insertions(+), 80 deletions(-) diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index 3fd447667cc..00dae1f86ec 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -1186,3 +1186,117 @@ export async function fetchResourceMetadata( throw new AggregateError(errors, 'Failed to fetch resource metadata from all attempted URLs'); } } + +export interface IFetchAuthorizationServerMetadataOptions { + /** + * Headers to include in the requests + */ + additionalHeaders?: Record; + /** + * Optional custom fetch implementation (defaults to global fetch) + */ + fetch?: IFetcher; +} + +/** Helper to try parsing the response as authorization server metadata */ +async function tryParseAuthServerMetadata(response: CommonResponse): Promise { + if (response.status !== 200) { + return undefined; + } + try { + const body = await response.json(); + if (isAuthorizationServerMetadata(body)) { + return body; + } + } catch { + // Failed to parse as JSON or not valid metadata + } + return undefined; +} + +/** Helper to get error text from response */ +async function getErrText(res: CommonResponse): Promise { + try { + return await res.text(); + } catch { + return res.statusText; + } +} + +/** + * Fetches and validates OAuth 2.0 authorization server metadata from the given authorization server URL. + * + * This function tries multiple discovery endpoints in the following order: + * 1. OAuth 2.0 Authorization Server Metadata with path insertion (RFC 8414) + * 2. OpenID Connect Discovery with path insertion + * 3. OpenID Connect Discovery with path addition + * + * Path insertion: For issuer URLs with path components (e.g., https://example.com/tenant), + * the well-known path is inserted after the origin and before the path: + * https://example.com/.well-known/oauth-authorization-server/tenant + * + * Path addition: The well-known path is simply appended to the existing path: + * https://example.com/tenant/.well-known/openid-configuration + * + * @param authorizationServer The authorization server URL (issuer identifier) + * @param options Configuration options for the fetch operation + * @returns Promise that resolves to the validated authorization server metadata + * @throws Error if all discovery attempts fail or the response is invalid + * + * @see https://datatracker.ietf.org/doc/html/rfc8414#section-3 + */ +export async function fetchAuthorizationServerMetadata( + authorizationServer: string, + options: IFetchAuthorizationServerMetadataOptions = {} +): Promise { + const { + additionalHeaders = {}, + fetch: fetchImpl = fetch + } = options; + + const authorizationServerUrl = new URL(authorizationServer); + const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname; + + const doFetch = async (url: string): Promise<{ metadata: IAuthorizationServerMetadata | undefined; rawResponse: CommonResponse }> => { + const rawResponse = await fetchImpl(url, { + method: 'GET', + headers: { + ...additionalHeaders, + 'Accept': 'application/json' + } + }); + const metadata = await tryParseAuthServerMetadata(rawResponse); + return { metadata, rawResponse }; + }; + + // For the oauth server metadata discovery path, we _INSERT_ + // the well known path after the origin and before the path. + // https://datatracker.ietf.org/doc/html/rfc8414#section-3 + const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + let result = await doFetch(pathToFetch); + if (result.metadata) { + return result.metadata; + } + + // Try fetching the OpenID Connect Discovery with path insertion. + // For issuer URLs with path components, this inserts the well-known path + // after the origin and before the path. + const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + result = await doFetch(openidPathInsertionUrl); + if (result.metadata) { + return result.metadata; + } + + // Try fetching the other discovery URL. For the openid metadata discovery + // path, we _ADD_ the well known path after the existing path. + // https://datatracker.ietf.org/doc/html/rfc8414#section-3 + const openidPathAdditionUrl = authorizationServer.endsWith('/') + ? authorizationServer + OPENID_CONNECT_DISCOVERY_PATH.substring(1) // Remove leading slash if authServer ends with slash + : authorizationServer + OPENID_CONNECT_DISCOVERY_PATH; + result = await doFetch(openidPathAdditionUrl); + if (result.metadata) { + return result.metadata; + } + + throw new Error(`Failed to fetch authorization server metadata: ${result.rawResponse.status} ${await getErrText(result.rawResponse)}`); +} diff --git a/src/vs/base/test/common/oauth.test.ts b/src/vs/base/test/common/oauth.test.ts index d846ac2d929..57a6ad3b16a 100644 --- a/src/vs/base/test/common/oauth.test.ts +++ b/src/vs/base/test/common/oauth.test.ts @@ -18,6 +18,7 @@ import { parseWWWAuthenticateHeader, fetchDynamicRegistration, fetchResourceMetadata, + fetchAuthorizationServerMetadata, scopesMatch, IAuthorizationJWTClaims, IAuthorizationServerMetadata, @@ -1319,4 +1320,441 @@ suite('OAuth', () => { assert.strictEqual(headers['X-Custom-Header'], 'value'); }); }); + + suite('fetchAuthorizationServerMetadata', () => { + let sandbox: sinon.SinonSandbox; + let fetchStub: sinon.SinonStub; + + setup(() => { + sandbox = sinon.createSandbox(); + fetchStub = sandbox.stub(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test('should successfully fetch metadata from OAuth discovery endpoint with path insertion', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 1); + // Should try OAuth discovery with path insertion: https://auth.example.com/.well-known/oauth-authorization-server/tenant + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + assert.strictEqual(fetchStub.firstCall.args[1].method, 'GET'); + }); + + test('should fallback to OpenID Connect discovery with path insertion', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + // First call fails, second succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 2); + // First attempt: OAuth discovery + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + // Second attempt: OpenID Connect discovery with path insertion + assert.strictEqual(fetchStub.secondCall.args[0], 'https://auth.example.com/.well-known/openid-configuration/tenant'); + }); + + test('should fallback to OpenID Connect discovery with path addition', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + // First two calls fail, third succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onThirdCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 3); + // First attempt: OAuth discovery + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + // Second attempt: OpenID Connect discovery with path insertion + assert.strictEqual(fetchStub.secondCall.args[0], 'https://auth.example.com/.well-known/openid-configuration/tenant'); + // Third attempt: OpenID Connect discovery with path addition + assert.strictEqual(fetchStub.thirdCall.args[0], 'https://auth.example.com/tenant/.well-known/openid-configuration'); + }); + + test('should handle authorization server at root without extra path', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + authorization_endpoint: 'https://auth.example.com/authorize', + token_endpoint: 'https://auth.example.com/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 1); + // For root URLs, no extra path is added + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server'); + }); + + test('should handle authorization server with trailing slash', async () => { + const authorizationServer = 'https://auth.example.com/tenant/'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should include additional headers in all requests', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const additionalHeaders = { + 'X-Custom-Header': 'custom-value', + 'Authorization': 'Bearer token123' + }; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub, additionalHeaders }); + + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['X-Custom-Header'], 'custom-value'); + assert.strictEqual(headers['Authorization'], 'Bearer token123'); + assert.strictEqual(headers['Accept'], 'application/json'); + }); + test('should throw error when all discovery endpoints fail', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + + fetchStub.resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata: 404 Not Found/ + ); + + // Should have tried all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should handle invalid JSON response', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.resolves({ + status: 200, + json: async () => { throw new Error('Invalid JSON'); }, + text: async () => 'Invalid JSON', + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata/ + ); + }); + + test('should handle valid JSON but invalid metadata structure', async () => { + const authorizationServer = 'https://auth.example.com'; + const invalidMetadata = { + // Missing required 'issuer' field + authorization_endpoint: 'https://auth.example.com/authorize' + }; + + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata), + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata/ + ); + }); + + test('should use global fetch when custom fetch is not provided', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + // eslint-disable-next-line local/code-no-any-casts + const globalFetchStub = sandbox.stub(globalThis, 'fetch').resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + } as any); + + const result = await fetchAuthorizationServerMetadata(authorizationServer); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(globalFetchStub.callCount, 1); + }); + + test('should handle network fetch failure', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.rejects(new Error('Network error')); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Network error/ + ); + }); + + test('should handle response.text() failure in error case', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.resolves({ + status: 500, + text: async () => { throw new Error('Cannot read text'); }, + statusText: 'Internal Server Error', + json: async () => { throw new Error('Cannot read json'); } + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata: 500 Internal Server Error/ + ); + }); + + test('should correctly handle path addition with trailing slash', async () => { + const authorizationServer = 'https://auth.example.com/tenant/'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/', + response_types_supported: ['code'] + }; + + // First two calls fail, third succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onThirdCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 3); + // Third attempt should correctly handle trailing slash (not double-slash) + assert.strictEqual(fetchStub.thirdCall.args[0], 'https://auth.example.com/tenant/.well-known/openid-configuration'); + }); + + test('should handle deeply nested paths', async () => { + const authorizationServer = 'https://auth.example.com/tenant/org/sub'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/org/sub', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 1); + // Should correctly insert well-known path with nested paths + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant/org/sub'); + }); + + test('should handle 200 response with non-metadata JSON', async () => { + const authorizationServer = 'https://auth.example.com'; + const invalidResponse = { + error: 'not_supported', + message: 'Metadata not available' + }; + + fetchStub.resolves({ + status: 200, + json: async () => invalidResponse, + text: async () => JSON.stringify(invalidResponse), + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata/ + ); + + // Should try all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should validate metadata according to isAuthorizationServerMetadata', async () => { + const authorizationServer = 'https://auth.example.com'; + // Valid metadata with all required fields + const validMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + authorization_endpoint: 'https://auth.example.com/authorize', + token_endpoint: 'https://auth.example.com/token', + jwks_uri: 'https://auth.example.com/jwks', + registration_endpoint: 'https://auth.example.com/register', + response_types_supported: ['code', 'token'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => validMetadata, + text: async () => JSON.stringify(validMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, validMetadata); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should handle URLs with query parameters', async () => { + const authorizationServer = 'https://auth.example.com/tenant?version=v2'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant?version=v2', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should handle empty additionalHeaders', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub, additionalHeaders: {} }); + + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['Accept'], 'application/json'); + }); + }); }); diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 15299e93e1c..f5cd5fc926d 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -8,7 +8,7 @@ import { DeferredPromise, raceCancellationError, Sequencer, timeout } from '../. import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; import { CancellationError } from '../../../base/common/errors.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; -import { AUTH_SCOPE_SEPARATOR, AUTH_SERVER_METADATA_DISCOVERY_PATH, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, isAuthorizationServerMetadata, OPENID_CONNECT_DISCOVERY_PATH, parseWWWAuthenticateHeader, scopesMatch } from '../../../base/common/oauth.js'; +import { AUTH_SCOPE_SEPARATOR, fetchAuthorizationServerMetadata, fetchResourceMetadata, getDefaultMetadataForUrl, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, parseWWWAuthenticateHeader, scopesMatch } from '../../../base/common/oauth.js'; import { SSEParser } from '../../../base/common/sseParser.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { ConfigurationTarget } from '../../../platform/configuration/common/configuration.js'; @@ -365,16 +365,21 @@ export class McpHTTPHandle extends Disposable { // If we are not given a resource_metadata, see if the well-known server metadata is available // on the base url. - let addtionalHeaders: Record = {}; + let additionalHeaders: Record = {}; if (!serverMetadataUrl) { serverMetadataUrl = baseUrl; // Maintain the launch headers when talking to the MCP origin. - addtionalHeaders = { - ...Object.fromEntries(this._launch.headers) + additionalHeaders = { + ...Object.fromEntries(this._launch.headers), + 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION }; } try { - const serverMetadataResponse = await this._getAuthorizationServerMetadata(serverMetadataUrl, addtionalHeaders); + this._log(LogLevel.Debug, `Fetching auth server metadata for: ${serverMetadataUrl} ...`); + const serverMetadataResponse = await fetchAuthorizationServerMetadata(serverMetadataUrl, { + additionalHeaders, + fetch: (url, init) => this._fetch(url, init) + }); this._log(LogLevel.Info, 'Populated auth metadata'); this._authMetadata = { authorizationServer: URI.parse(serverMetadataUrl), @@ -398,81 +403,6 @@ export class McpHTTPHandle extends Disposable { this._log(LogLevel.Info, 'Using default auth metadata'); } - private async _tryParseAuthServerMetadata(response: CommonResponse): Promise { - if (response.status !== 200) { - return undefined; - } - try { - const body = await response.json(); - if (isAuthorizationServerMetadata(body)) { - return body; - } - } catch { - // Failed to parse as JSON or not valid metadata - this._log(LogLevel.Debug, 'Failed to parse authorization server metadata'); - } - return undefined; - } - - private async _getAuthorizationServerMetadata(authorizationServer: string, addtionalHeaders: Record): Promise { - // For the oauth server metadata discovery path, we _INSERT_ - // the well known path after the origin and before the path. - // https://datatracker.ietf.org/doc/html/rfc8414#section-3 - const authorizationServerUrl = new URL(authorizationServer); - const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname; - const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath; - this._log(LogLevel.Debug, `Fetching auth server metadata url with path insertion: ${pathToFetch} ...`); - let authServerMetadataResponse = await this._fetch(pathToFetch, { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION, - } - }); - let metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); - if (metadata) { - return metadata; - } - - // Try fetching the OpenID Connect Discovery with path insertion. - // For issuer URLs with path components, this inserts the well-known path - // after the origin and before the path. - const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; - this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path insertion: ${openidPathInsertionUrl} ...`); - authServerMetadataResponse = await this._fetch(openidPathInsertionUrl, { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION - } - }); - metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); - if (metadata) { - return metadata; - } - - // Try fetching the other discovery URL. For the openid metadata discovery - // path, we _ADD_ the well known path after the existing path. - // https://datatracker.ietf.org/doc/html/rfc8414#section-3 - const openidPathAdditionUrl = URI.joinPath(URI.parse(authorizationServer), OPENID_CONNECT_DISCOVERY_PATH).toString(true); - this._log(LogLevel.Debug, `Fetching fallback openid connect discovery url with path addition: ${openidPathAdditionUrl} ...`); - authServerMetadataResponse = await this._fetch(openidPathAdditionUrl, { - method: 'GET', - headers: { - ...addtionalHeaders, - 'Accept': 'application/json', - 'MCP-Protocol-Version': MCP.LATEST_PROTOCOL_VERSION - } - }); - metadata = await this._tryParseAuthServerMetadata(authServerMetadataResponse); - if (metadata) { - return metadata; - } - - throw new Error(`Failed to fetch authorization server metadata: ${authServerMetadataResponse.status} ${await this._getErrText(authServerMetadataResponse)}`); - } private async _handleSuccessfulStreamableHttp(res: CommonResponse, message: string) { if (res.status === 202) { From ca9fa4fbac6ac2ae8ff4aeb65a160a849a09223a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 10 Oct 2025 17:07:07 -0400 Subject: [PATCH 1062/4355] prevent right prompt from being considered ghost text (#269732) fix #269730 --- .../capabilities/commandDetection/promptInputModel.ts | 5 +++++ .../commandDetection/promptInputModel.test.ts | 3 --- .../xterm/recordings/basic/macos_zsh_p10k_ls_one_time.ts | 8 ++++---- .../xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts index c8df3d68cf0..15da82b2144 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts @@ -546,6 +546,11 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { // Retrieve the positions of all cells with the same style as `lastNonWhitespaceCell` const positionsWithGhostStyle = styleMap.get(this._getCellStyleAsString(lastNonWhitespaceCell)); if (positionsWithGhostStyle) { + // Ghost text must start at the cursor or one char after (e.g. a space), + // preventing right-prompt styles from being misdetected as ghost text. + if (positionsWithGhostStyle[0] > buffer.cursorX + 1) { + return -1; + } // Ensure these positions are contiguous for (let i = 1; i < positionsWithGhostStyle.length; i++) { if (positionsWithGhostStyle[i] !== positionsWithGhostStyle[i - 1] + 1) { diff --git a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts index 7e2f8ffb2ab..2d0c801066e 100644 --- a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts +++ b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts @@ -215,9 +215,6 @@ suite('PromptInputModel', () => { await writePromise('foo\x1b[38;2;255;0;0m bar\x1b[0m\x1b[4D'); await assertPromptInput('foo|[ bar]'); - - await writePromise('\x1b[2D'); - await assertPromptInput('f|oo[ bar]'); }); test('no ghost text when foreground color matches earlier text', async () => { await writePromise('$ '); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/basic/macos_zsh_p10k_ls_one_time.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/basic/macos_zsh_p10k_ls_one_time.ts index 0ad1e7d9179..242b6dfb37f 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/basic/macos_zsh_p10k_ls_one_time.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/basic/macos_zsh_p10k_ls_one_time.ts @@ -35,7 +35,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "| [─╯]" + "data": "| ─╯" }, { "type": "input", @@ -47,7 +47,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "l| [─╯]" + "data": "l| ─╯" }, { "type": "input", @@ -59,7 +59,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "ls| [─╯]" + "data": "ls| ─╯" }, { "type": "input", @@ -71,7 +71,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "ls " + "data": "ls ─╯" }, { "type": "output", diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts index 5483ff1b48b..3e524eda4c3 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts @@ -324,7 +324,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "echo b|[]" + "data": "echo b|" }, { "type": "output", @@ -491,7 +491,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "echo c|[]" + "data": "echo c|" }, { "type": "output", From 1eee7ae2309a61475ef8598c5332068ec0742c07 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:02:03 -0700 Subject: [PATCH 1063/4355] Switch monaco to off of `moduleResolution: classic` Fixes #270408 Trying to move some of the monaco related checks/tconfigs off of `moduleResolution: classic`. This legacy config is causing a lot of pain while trying to update the trusted-types typings, which is itself blocking picking up the latest dompurify I initially tried a more scoped change but just could not get it working. So instead I ended up trying to rework our `LanguageServiceHost` to be more standard --- build/gulpfile.editor.js | 3 - build/lib/monaco-api.js | 7 +- build/lib/monaco-api.ts | 7 +- build/lib/standalone.js | 37 +++--- build/lib/standalone.ts | 38 +++--- build/lib/treeshaking.js | 102 +++------------- build/lib/treeshaking.ts | 129 +++------------------ build/lib/typeScriptLanguageServiceHost.js | 47 ++++---- build/lib/typeScriptLanguageServiceHost.ts | 34 +++--- build/monaco/monaco.d.ts.recipe | 98 ++++++++-------- build/monaco/monaco.usage.recipe | 10 +- package-lock.json | 11 +- package.json | 4 +- src/tsconfig.monaco.json | 2 +- 14 files changed, 177 insertions(+), 352 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 39f83320051..6d8ff1d3745 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -54,9 +54,6 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { importIgnorePattern: /\.css$/, destRoot: path.join(root, 'out-editor-src'), tsOutDir: '../out-monaco-editor-core/esm/vs', - redirects: { - '@vscode/tree-sitter-wasm': '../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter', - } }); }); diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index 06e3d63029f..3d9a4c7ca10 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -336,7 +336,7 @@ function generateDeclarationFile(ts, recipe, sourceFileGetter) { usage.push(`var b: any;`); const generateUsageImport = (moduleId) => { const importName = 'm' + (++usageCounter); - usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); + usageImports.push(`import * as ${importName} from './${moduleId}';`); return importName; }; const enums = []; @@ -538,6 +538,9 @@ class DeclarationResolver { if (/\.d\.ts$/.test(moduleId)) { return path_1.default.join(SRC, moduleId); } + if (/\.js$/.test(moduleId)) { + return path_1.default.join(SRC, moduleId.replace(/\.js$/, '.ts')); + } return path_1.default.join(SRC, `${moduleId}.ts`); } _getDeclarationSourceFile(moduleId) { @@ -555,7 +558,7 @@ class DeclarationResolver { const fileMap = new Map([ ['file.ts', fileContents] ]); - const service = this.ts.createLanguageService(new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {}, 'defaultLib:es5')); + const service = this.ts.createLanguageService(new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(this.ts, fileMap, {})); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime); } diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index f3bdfa3a693..e0622bcd336 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -406,7 +406,7 @@ function generateDeclarationFile(ts: Typescript, recipe: string, sourceFileGette const generateUsageImport = (moduleId: string) => { const importName = 'm' + (++usageCounter); - usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); + usageImports.push(`import * as ${importName} from './${moduleId}';`); return importName; }; @@ -641,6 +641,9 @@ export class DeclarationResolver { if (/\.d\.ts$/.test(moduleId)) { return path.join(SRC, moduleId); } + if (/\.js$/.test(moduleId)) { + return path.join(SRC, moduleId.replace(/\.js$/, '.ts')); + } return path.join(SRC, `${moduleId}.ts`); } @@ -662,7 +665,7 @@ export class DeclarationResolver { const fileMap: IFileMap = new Map([ ['file.ts', fileContents] ]); - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, new Map(), fileMap, {}, 'defaultLib:es5')); + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, fileMap, {})); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry( this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), diff --git a/build/lib/standalone.js b/build/lib/standalone.js index a56cbf3deff..21271247c38 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -44,8 +44,6 @@ exports.extractEditor = extractEditor; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const tss = __importStar(require("./treeshaking")); -const REPO_ROOT = path_1.default.join(__dirname, '../../'); -const SRC_DIR = path_1.default.join(REPO_ROOT, 'src'); const dirCache = {}; function writeFile(filePath, contents) { function ensureDirs(dirPath) { @@ -84,21 +82,11 @@ function extractEditor(options) { console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); // Take the extra included .d.ts files from `tsconfig.monaco.json` options.typings = tsConfig.include.filter(includedFile => /\.d\.ts$/.test(includedFile)); - // Add extra .d.ts files from `node_modules/@types/` - if (Array.isArray(options.compilerOptions?.types)) { - options.compilerOptions.types.forEach((type) => { - if (type === '@webgpu/types') { - options.typings.push(`../node_modules/${type}/dist/index.d.ts`); - } - else { - options.typings.push(`../node_modules/@types/${type}/index.d.ts`); - } - }); - } const result = tss.shake(options); for (const fileName in result) { if (result.hasOwnProperty(fileName)) { - writeFile(path_1.default.join(options.destRoot, fileName), result[fileName]); + const relativePath = path_1.default.relative(options.sourcesRoot, fileName); + writeFile(path_1.default.join(options.destRoot, relativePath), result[fileName]); } } const copied = {}; @@ -107,12 +95,20 @@ function extractEditor(options) { return; } copied[fileName] = true; - const srcPath = path_1.default.join(options.sourcesRoot, fileName); - const dstPath = path_1.default.join(options.destRoot, fileName); - writeFile(dstPath, fs_1.default.readFileSync(srcPath)); + if (path_1.default.isAbsolute(fileName)) { + const relativePath = path_1.default.relative(options.sourcesRoot, fileName); + const dstPath = path_1.default.join(options.destRoot, relativePath); + writeFile(dstPath, fs_1.default.readFileSync(fileName)); + } + else { + const srcPath = path_1.default.join(options.sourcesRoot, fileName); + const dstPath = path_1.default.join(options.destRoot, fileName); + writeFile(dstPath, fs_1.default.readFileSync(srcPath)); + } }; const writeOutputFile = (fileName, contents) => { - writeFile(path_1.default.join(options.destRoot, fileName), contents); + const relativePath = path_1.default.isAbsolute(fileName) ? path_1.default.relative(options.sourcesRoot, fileName) : fileName; + writeFile(path_1.default.join(options.destRoot, relativePath), contents); }; for (const fileName in result) { if (result.hasOwnProperty(fileName)) { @@ -147,8 +143,7 @@ function transportCSS(module, enqueue, write) { if (!/\.css/.test(module)) { return false; } - const filename = path_1.default.join(SRC_DIR, module); - const fileContents = fs_1.default.readFileSync(filename).toString(); + const fileContents = fs_1.default.readFileSync(module).toString(); const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); write(module, newContents); @@ -163,7 +158,7 @@ function transportCSS(module, enqueue, write) { return relativeFontPath; } const imagePath = path_1.default.join(path_1.default.dirname(module), url); - const fileContents = fs_1.default.readFileSync(path_1.default.join(SRC_DIR, imagePath)); + const fileContents = fs_1.default.readFileSync(imagePath); const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; let DATA = ';base64,' + fileContents.toString('base64'); if (!forceBase64 && /\.svg$/.test(url)) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 9e44d52908d..4534dee4f0c 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -7,9 +7,6 @@ import fs from 'fs'; import path from 'path'; import * as tss from './treeshaking'; -const REPO_ROOT = path.join(__dirname, '../../'); -const SRC_DIR = path.join(REPO_ROOT, 'src'); - const dirCache: { [dir: string]: boolean } = {}; function writeFile(filePath: string, contents: Buffer | string): void { @@ -57,21 +54,11 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str // Take the extra included .d.ts files from `tsconfig.monaco.json` options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); - // Add extra .d.ts files from `node_modules/@types/` - if (Array.isArray(options.compilerOptions?.types)) { - options.compilerOptions.types.forEach((type: string) => { - if (type === '@webgpu/types') { - options.typings.push(`../node_modules/${type}/dist/index.d.ts`); - } else { - options.typings.push(`../node_modules/@types/${type}/index.d.ts`); - } - }); - } - const result = tss.shake(options); for (const fileName in result) { if (result.hasOwnProperty(fileName)) { - writeFile(path.join(options.destRoot, fileName), result[fileName]); + const relativePath = path.relative(options.sourcesRoot, fileName); + writeFile(path.join(options.destRoot, relativePath), result[fileName]); } } const copied: { [fileName: string]: boolean } = {}; @@ -80,12 +67,20 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str return; } copied[fileName] = true; - const srcPath = path.join(options.sourcesRoot, fileName); - const dstPath = path.join(options.destRoot, fileName); - writeFile(dstPath, fs.readFileSync(srcPath)); + + if (path.isAbsolute(fileName)) { + const relativePath = path.relative(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, relativePath); + writeFile(dstPath, fs.readFileSync(fileName)); + } else { + const srcPath = path.join(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, fileName); + writeFile(dstPath, fs.readFileSync(srcPath)); + } }; const writeOutputFile = (fileName: string, contents: string | Buffer) => { - writeFile(path.join(options.destRoot, fileName), contents); + const relativePath = path.isAbsolute(fileName) ? path.relative(options.sourcesRoot, fileName) : fileName; + writeFile(path.join(options.destRoot, relativePath), contents); }; for (const fileName in result) { if (result.hasOwnProperty(fileName)) { @@ -127,8 +122,7 @@ function transportCSS(module: string, enqueue: (module: string) => void, write: return false; } - const filename = path.join(SRC_DIR, module); - const fileContents = fs.readFileSync(filename).toString(); + const fileContents = fs.readFileSync(module).toString(); const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); @@ -146,7 +140,7 @@ function transportCSS(module: string, enqueue: (module: string) => void, write: } const imagePath = path.join(path.dirname(module), url); - const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); + const fileContents = fs.readFileSync(imagePath); const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; let DATA = ';base64,' + fileContents.toString('base64'); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 8085f5ea02d..c78cd40e7d9 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -12,7 +12,6 @@ exports.shake = shake; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const typeScriptLanguageServiceHost_1 = require("./typeScriptLanguageServiceHost"); -const TYPESCRIPT_LIB_FOLDER = path_1.default.dirname(require.resolve('typescript/lib/lib.d.ts')); var ShakeLevel; (function (ShakeLevel) { ShakeLevel[ShakeLevel["Files"] = 0] = "Files"; @@ -68,101 +67,26 @@ function shake(options) { //#region Discovery, LanguageService & Setup function createTypeScriptLanguageService(ts, options) { // Discover referenced files - const FILES = discoverAndReadFiles(ts, options); + const FILES = new Map(); + // Add entrypoints + options.entryPoints.forEach(entryPoint => { + const filePath = path_1.default.join(options.sourcesRoot, `${entryPoint}.ts`); + FILES.set(filePath, fs_1.default.readFileSync(filePath).toString()); + }); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES.set(`inlineEntryPoint.${index}.ts`, inlineEntryPoint); + FILES.set(path_1.default.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`), inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path_1.default.join(options.sourcesRoot, typing); - FILES.set(typing, fs_1.default.readFileSync(filePath).toString()); + FILES.set(filePath, fs_1.default.readFileSync(filePath).toString()); }); - // Resolve libs - const RESOLVED_LIBS = processLibFiles(ts, options); - const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - const host = new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions, 'defaultLib:lib.d.ts'); + const basePath = path_1.default.join(options.sourcesRoot, '..'); + const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, basePath).options; + const host = new typeScriptLanguageServiceHost_1.TypeScriptLanguageServiceHost(ts, FILES, compilerOptions); return ts.createLanguageService(host); } -/** - * Read imports and follow them until all files have been handled - */ -function discoverAndReadFiles(ts, options) { - const FILES = new Map(); - const in_queue = Object.create(null); - const queue = []; - const enqueue = (moduleId) => { - // To make the treeshaker work on windows... - moduleId = moduleId.replace(/\\/g, '/'); - if (in_queue[moduleId]) { - return; - } - in_queue[moduleId] = true; - queue.push(moduleId); - }; - options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); - while (queue.length > 0) { - const moduleId = queue.shift(); - let redirectedModuleId = moduleId; - if (options.redirects[moduleId]) { - redirectedModuleId = options.redirects[moduleId]; - } - const dts_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); - if (fs_1.default.existsSync(dts_filename)) { - const dts_filecontents = fs_1.default.readFileSync(dts_filename).toString(); - FILES.set(`${moduleId}.d.ts`, dts_filecontents); - continue; - } - const js_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.js'); - if (fs_1.default.existsSync(js_filename)) { - // This is an import for a .js file, so ignore it... - continue; - } - const ts_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.ts'); - const ts_filecontents = fs_1.default.readFileSync(ts_filename).toString(); - const info = ts.preProcessFile(ts_filecontents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - if (options.importIgnorePattern.test(importedFileName)) { - // Ignore *.css imports - continue; - } - let importedModuleId = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { - importedModuleId = path_1.default.join(path_1.default.dirname(moduleId), importedModuleId); - if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension - importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); - } - } - enqueue(importedModuleId); - } - FILES.set(`${moduleId}.ts`, ts_filecontents); - } - return FILES; -} -/** - * Read lib files and follow lib references - */ -function processLibFiles(ts, options) { - const stack = [...options.compilerOptions.lib]; - const result = new Map(); - while (stack.length > 0) { - const filename = `lib.${stack.shift().toLowerCase()}.d.ts`; - const key = `defaultLib:${filename}`; - if (!result.has(key)) { - // add this file - const filepath = path_1.default.join(TYPESCRIPT_LIB_FOLDER, filename); - const sourceText = fs_1.default.readFileSync(filepath).toString(); - result.set(key, sourceText); - // precess dependencies and "recurse" - const info = ts.preProcessFile(sourceText); - for (const ref of info.libReferenceDirectives) { - stack.push(ref.fileName); - } - } - } - return result; -} //#endregion //#region Tree Shaking var NodeColor; @@ -410,9 +334,9 @@ function markNodes(ts, languageService, options) { } enqueueFile(fullPath); } - options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); + options.entryPoints.forEach(moduleId => enqueueFile(path_1.default.join(options.sourcesRoot, moduleId + '.ts'))); // Add fake usage files - options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); + options.inlineEntryPoints.forEach((_, index) => enqueueFile(path_1.default.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`))); let step = 0; const checker = program.getTypeChecker(); while (black_queue.length > 0 || gray_queue.length > 0) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index a3424d366e6..df0f288fc30 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -6,9 +6,7 @@ import fs from 'fs'; import path from 'path'; import type * as ts from 'typescript'; -import { IFileMap, ILibMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost'; - -const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); +import { IFileMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost'; enum ShakeLevel { Files = 0, @@ -58,7 +56,7 @@ export interface ITreeShakingOptions { */ importIgnorePattern: RegExp; - redirects: { [module: string]: string }; + // redirects: { [module: string]: string }; } export interface ITreeShakingResult { @@ -111,126 +109,31 @@ export function shake(options: ITreeShakingOptions): ITreeShakingResult { //#region Discovery, LanguageService & Setup function createTypeScriptLanguageService(ts: typeof import('typescript'), options: ITreeShakingOptions): ts.LanguageService { // Discover referenced files - const FILES = discoverAndReadFiles(ts, options); + const FILES: IFileMap = new Map(); + + // Add entrypoints + options.entryPoints.forEach(entryPoint => { + const filePath = path.join(options.sourcesRoot, `${entryPoint}.ts`); + FILES.set(filePath, fs.readFileSync(filePath).toString()); + }); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES.set(`inlineEntryPoint.${index}.ts`, inlineEntryPoint); + FILES.set(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`), inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path.join(options.sourcesRoot, typing); - FILES.set(typing, fs.readFileSync(filePath).toString()); + FILES.set(filePath, fs.readFileSync(filePath).toString()); }); - // Resolve libs - const RESOLVED_LIBS = processLibFiles(ts, options); - - const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions, 'defaultLib:lib.d.ts'); + const basePath = path.join(options.sourcesRoot, '..'); + const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, basePath).options; + const host = new TypeScriptLanguageServiceHost(ts, FILES, compilerOptions); return ts.createLanguageService(host); } -/** - * Read imports and follow them until all files have been handled - */ -function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): IFileMap { - const FILES: IFileMap = new Map(); - - const in_queue: { [module: string]: boolean } = Object.create(null); - const queue: string[] = []; - - const enqueue = (moduleId: string) => { - // To make the treeshaker work on windows... - moduleId = moduleId.replace(/\\/g, '/'); - if (in_queue[moduleId]) { - return; - } - in_queue[moduleId] = true; - queue.push(moduleId); - }; - - options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); - - while (queue.length > 0) { - const moduleId = queue.shift()!; - let redirectedModuleId: string = moduleId; - if (options.redirects[moduleId]) { - redirectedModuleId = options.redirects[moduleId]; - } - - const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); - if (fs.existsSync(dts_filename)) { - const dts_filecontents = fs.readFileSync(dts_filename).toString(); - FILES.set(`${moduleId}.d.ts`, dts_filecontents); - continue; - } - - - const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); - if (fs.existsSync(js_filename)) { - // This is an import for a .js file, so ignore it... - continue; - } - - const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); - - const ts_filecontents = fs.readFileSync(ts_filename).toString(); - const info = ts.preProcessFile(ts_filecontents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - - if (options.importIgnorePattern.test(importedFileName)) { - // Ignore *.css imports - continue; - } - - let importedModuleId = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { - importedModuleId = path.join(path.dirname(moduleId), importedModuleId); - if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension - importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); - } - } - enqueue(importedModuleId); - } - - FILES.set(`${moduleId}.ts`, ts_filecontents); - } - - return FILES; -} - -/** - * Read lib files and follow lib references - */ -function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): ILibMap { - - const stack: string[] = [...options.compilerOptions.lib]; - const result: ILibMap = new Map(); - - while (stack.length > 0) { - const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; - const key = `defaultLib:${filename}`; - if (!result.has(key)) { - // add this file - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - const sourceText = fs.readFileSync(filepath).toString(); - result.set(key, sourceText); - - // precess dependencies and "recurse" - const info = ts.preProcessFile(sourceText); - for (const ref of info.libReferenceDirectives) { - stack.push(ref.fileName); - } - } - } - - return result; -} - //#endregion //#region Tree Shaking @@ -529,9 +432,9 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language enqueueFile(fullPath); } - options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); + options.entryPoints.forEach(moduleId => enqueueFile(path.join(options.sourcesRoot, moduleId + '.ts'))); // Add fake usage files - options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); + options.inlineEntryPoints.forEach((_, index) => enqueueFile(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`))); let step = 0; diff --git a/build/lib/typeScriptLanguageServiceHost.js b/build/lib/typeScriptLanguageServiceHost.js index f7e198e0a4b..58aeeb2de09 100644 --- a/build/lib/typeScriptLanguageServiceHost.js +++ b/build/lib/typeScriptLanguageServiceHost.js @@ -1,21 +1,26 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeScriptLanguageServiceHost = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const typescript_1 = __importDefault(require("typescript")); +const node_fs_1 = __importDefault(require("node:fs")); /** * A TypeScript language service host */ class TypeScriptLanguageServiceHost { ts; - libs; - files; + topLevelFiles; compilerOptions; - defaultLibName; - constructor(ts, libs, files, compilerOptions, defaultLibName) { + constructor(ts, topLevelFiles, compilerOptions) { this.ts = ts; - this.libs = libs; - this.files = files; + this.topLevelFiles = topLevelFiles; this.compilerOptions = compilerOptions; - this.defaultLibName = defaultLibName; } // --- language service host --------------- getCompilationSettings() { @@ -23,8 +28,8 @@ class TypeScriptLanguageServiceHost { } getScriptFileNames() { return [ - ...this.libs.keys(), - ...this.files.keys(), + ...this.topLevelFiles.keys(), + this.ts.getDefaultLibFilePath(this.compilerOptions) ]; } getScriptVersion(_fileName) { @@ -34,14 +39,11 @@ class TypeScriptLanguageServiceHost { return '1'; } getScriptSnapshot(fileName) { - if (this.files.has(fileName)) { - return this.ts.ScriptSnapshot.fromString(this.files.get(fileName)); - } - else if (this.libs.has(fileName)) { - return this.ts.ScriptSnapshot.fromString(this.libs.get(fileName)); + if (this.topLevelFiles.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.topLevelFiles.get(fileName)); } else { - return this.ts.ScriptSnapshot.fromString(''); + return typescript_1.default.ScriptSnapshot.fromString(node_fs_1.default.readFileSync(fileName).toString()); } } getScriptKind(_fileName) { @@ -51,16 +53,19 @@ class TypeScriptLanguageServiceHost { return ''; } getDefaultLibFileName(_options) { - return this.defaultLibName; - } - isDefaultLibFileName(fileName) { - return fileName === this.getDefaultLibFileName(this.compilerOptions); + return this.ts.getDefaultLibFilePath(_options); } readFile(path, _encoding) { - return this.files.get(path) || this.libs.get(path); + if (this.topLevelFiles.get(path)) { + return this.topLevelFiles.get(path); + } + return typescript_1.default.sys.readFile(path, _encoding); } fileExists(path) { - return this.files.has(path) || this.libs.has(path); + if (this.topLevelFiles.has(path)) { + return true; + } + return typescript_1.default.sys.fileExists(path); } } exports.TypeScriptLanguageServiceHost = TypeScriptLanguageServiceHost; diff --git a/build/lib/typeScriptLanguageServiceHost.ts b/build/lib/typeScriptLanguageServiceHost.ts index c6d20f0f32b..eebd9e70b74 100644 --- a/build/lib/typeScriptLanguageServiceHost.ts +++ b/build/lib/typeScriptLanguageServiceHost.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import ts from 'typescript'; +import fs from 'node:fs'; +// import path from 'node:path'; -export type ILibMap = Map; export type IFileMap = Map; /** @@ -15,10 +16,8 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { constructor( private readonly ts: typeof import('typescript'), - private readonly libs: ILibMap, - private readonly files: IFileMap, + private readonly topLevelFiles: IFileMap, private readonly compilerOptions: ts.CompilerOptions, - private readonly defaultLibName: string, ) { } // --- language service host --------------- @@ -27,8 +26,8 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { } getScriptFileNames(): string[] { return [ - ...this.libs.keys(), - ...this.files.keys(), + ...this.topLevelFiles.keys(), + this.ts.getDefaultLibFilePath(this.compilerOptions) ]; } getScriptVersion(_fileName: string): string { @@ -38,12 +37,10 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return '1'; } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this.files.has(fileName)) { - return this.ts.ScriptSnapshot.fromString(this.files.get(fileName)!); - } else if (this.libs.has(fileName)) { - return this.ts.ScriptSnapshot.fromString(this.libs.get(fileName)!); + if (this.topLevelFiles.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.topLevelFiles.get(fileName)!); } else { - return this.ts.ScriptSnapshot.fromString(''); + return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); } } getScriptKind(_fileName: string): ts.ScriptKind { @@ -53,15 +50,18 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return ''; } getDefaultLibFileName(_options: ts.CompilerOptions): string { - return this.defaultLibName; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this.compilerOptions); + return this.ts.getDefaultLibFilePath(_options); } readFile(path: string, _encoding?: string): string | undefined { - return this.files.get(path) || this.libs.get(path); + if (this.topLevelFiles.get(path)) { + return this.topLevelFiles.get(path); + } + return ts.sys.readFile(path, _encoding); } fileExists(path: string): boolean { - return this.files.has(path) || this.libs.has(path); + if (this.topLevelFiles.has(path)) { + return true; + } + return ts.sys.fileExists(path); } } diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 43b91bbb80e..b1f676796af 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -70,27 +70,27 @@ declare namespace monaco { dispose(): void; } -#include(vs/platform/markers/common/markers): MarkerTag, MarkerSeverity -#include(vs/base/common/cancellation): CancellationTokenSource, CancellationToken -#include(vs/base/common/uri): URI, UriComponents -#include(vs/base/common/keyCodes): KeyCode -#include(vs/editor/common/services/editorBaseApi): KeyMod -#include(vs/base/common/htmlContent): IMarkdownString, MarkdownStringTrustedOptions -#include(vs/base/browser/keyboardEvent): IKeyboardEvent -#include(vs/base/browser/mouseEvent): IMouseEvent -#include(vs/editor/common/editorCommon): IScrollEvent -#include(vs/editor/common/core/position): IPosition, Position -#include(vs/editor/common/core/range): IRange, Range -#include(vs/editor/common/core/selection): ISelection, Selection, SelectionDirection -#include(vs/editor/common/languages): Token +#include(vs/platform/markers/common/markers.js): MarkerTag, MarkerSeverity +#include(vs/base/common/cancellation.js): CancellationTokenSource, CancellationToken +#include(vs/base/common/uri.js): URI, UriComponents +#include(vs/base/common/keyCodes.js): KeyCode +#include(vs/editor/common/services/editorBaseApi.js): KeyMod +#include(vs/base/common/htmlContent.js): IMarkdownString, MarkdownStringTrustedOptions +#include(vs/base/browser/keyboardEvent.js): IKeyboardEvent +#include(vs/base/browser/mouseEvent.js): IMouseEvent +#include(vs/editor/common/editorCommon.js): IScrollEvent +#include(vs/editor/common/core/position.js): IPosition, Position +#include(vs/editor/common/core/range.js): IRange, Range +#include(vs/editor/common/core/selection.js): ISelection, Selection, SelectionDirection +#include(vs/editor/common/languages.js): Token } declare namespace monaco.editor { -#includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token): -#include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors -#include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule -#include(vs/editor/standalone/browser/standaloneWebWorker): MonacoWebWorker, IInternalWebWorkerOptions -#include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor +#includeAll(vs/editor/standalone/browser/standaloneEditor.js;languages.Token=>Token): +#include(vs/editor/standalone/common/standaloneTheme.js): BuiltinTheme, IStandaloneThemeData, IColors +#include(vs/editor/common/languages/supports/tokenization.js): ITokenThemeRule +#include(vs/editor/standalone/browser/standaloneWebWorker.js): MonacoWebWorker, IInternalWebWorkerOptions +#include(vs/editor/standalone/browser/standaloneCodeEditor.js): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor export interface ICommandHandler { (...args: any[]): void; } @@ -101,27 +101,27 @@ export interface ILocalizedString { export interface ICommandMetadata { readonly description: ILocalizedString | string; } -#include(vs/platform/contextkey/common/contextkey): IContextKey, ContextKeyValue -#include(vs/editor/standalone/browser/standaloneServices): IEditorOverrideServices -#include(vs/platform/markers/common/markers): IMarker, IMarkerData, IRelatedInformation -#include(vs/editor/standalone/browser/colorizer): IColorizerOptions, IColorizerElementOptions -#include(vs/base/common/scrollable): ScrollbarVisibility -#include(vs/base/common/themables): ThemeColor, ThemeIcon -#include(vs/editor/common/core/editOperation): ISingleEditOperation -#include(vs/editor/common/core/wordHelper): IWordAtPosition -#includeAll(vs/editor/common/model): IScrollEvent -#include(vs/editor/common/diff/legacyLinesDiffComputer): IChange, ICharChange, ILineChange -#include(vs/editor/common/core/2d/dimension): IDimension -#includeAll(vs/editor/common/editorCommon): IScrollEvent -#includeAll(vs/editor/common/textModelEvents): -#include(vs/editor/common/model/mirrorTextModel): IModelContentChange -#includeAll(vs/editor/common/cursorEvents): -#include(vs/platform/accessibility/common/accessibility): AccessibilitySupport -#includeAll(vs/editor/common/config/editorOptions): -#include(vs/editor/browser/config/editorConfiguration): IEditorConstructionOptions -#includeAll(vs/editor/browser/editorBrowser;editorCommon.=>): -#include(vs/editor/common/config/fontInfo): FontInfo, BareFontInfo -#include(vs/editor/common/config/editorZoom): EditorZoom, IEditorZoom +#include(vs/platform/contextkey/common/contextkey.js): IContextKey, ContextKeyValue +#include(vs/editor/standalone/browser/standaloneServices.js): IEditorOverrideServices +#include(vs/platform/markers/common/markers.js): IMarker, IMarkerData, IRelatedInformation +#include(vs/editor/standalone/browser/colorizer.js): IColorizerOptions, IColorizerElementOptions +#include(vs/base/common/scrollable.js): ScrollbarVisibility +#include(vs/base/common/themables.js): ThemeColor, ThemeIcon +#include(vs/editor/common/core/editOperation.js): ISingleEditOperation +#include(vs/editor/common/core/wordHelper.js): IWordAtPosition +#includeAll(vs/editor/common/model.js): IScrollEvent +#include(vs/editor/common/diff/legacyLinesDiffComputer.js): IChange, ICharChange, ILineChange +#include(vs/editor/common/core/2d/dimension.js): IDimension +#includeAll(vs/editor/common/editorCommon.js): IScrollEvent +#includeAll(vs/editor/common/textModelEvents.js): +#include(vs/editor/common/model/mirrorTextModel.js): IModelContentChange +#includeAll(vs/editor/common/cursorEvents.js): +#include(vs/platform/accessibility/common/accessibility.js): AccessibilitySupport +#includeAll(vs/editor/common/config/editorOptions.js): +#include(vs/editor/browser/config/editorConfiguration.js): IEditorConstructionOptions +#includeAll(vs/editor/browser/editorBrowser.js;editorCommon.=>): +#include(vs/editor/common/config/fontInfo.js): FontInfo, BareFontInfo +#include(vs/editor/common/config/editorZoom.js): EditorZoom, IEditorZoom //compatibility: export type IReadOnlyModel = ITextModel; @@ -130,21 +130,21 @@ export type IModel = ITextModel; declare namespace monaco.languages { -#include(vs/editor/common/textModelEditSource): EditDeltaInfo -#include(vs/base/common/glob): IRelativePattern -#include(vs/editor/common/languageSelector): LanguageSelector, LanguageFilter -#includeAll(vs/editor/standalone/browser/standaloneLanguages;languages.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): -#includeAll(vs/editor/common/languages/languageConfiguration): -#includeAll(vs/editor/common/languages;IMarkerData=>editor.IMarkerData;ISingleEditOperation=>editor.ISingleEditOperation;model.=>editor.;ThemeIcon=>editor.ThemeIcon): Token -#include(vs/editor/common/languages/language): ILanguageExtensionPoint -#includeAll(vs/editor/standalone/common/monarch/monarchTypes): +#include(vs/editor/common/textModelEditSource.js): EditDeltaInfo +#include(vs/base/common/glob.js): IRelativePattern +#include(vs/editor/common/languageSelector.js): LanguageSelector, LanguageFilter +#includeAll(vs/editor/standalone/browser/standaloneLanguages.js;languages.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): +#includeAll(vs/editor/common/languages/languageConfiguration.js): +#includeAll(vs/editor/common/languages.js;IMarkerData=>editor.IMarkerData;ISingleEditOperation=>editor.ISingleEditOperation;model.=>editor.;ThemeIcon=>editor.ThemeIcon): Token +#include(vs/editor/common/languages/language.js): ILanguageExtensionPoint +#includeAll(vs/editor/standalone/common/monarch/monarchTypes.js): } declare namespace monaco.worker { -#include(vs/editor/common/model/mirrorTextModel): IMirrorTextModel -#includeAll(vs/editor/common/services/editorWebWorker;): +#include(vs/editor/common/model/mirrorTextModel.js): IMirrorTextModel +#includeAll(vs/editor/common/services/editorWebWorker.js;): } diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index 9e96a68568a..b0526a4506e 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -1,12 +1,12 @@ // This file is adding references to various symbols which should not be removed via tree shaking -import { IObservable } from './vs/base/common/observable'; +import { IObservable } from './vs/base/common/observable.js'; -import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation'; -import { start } from './vs/editor/editor.worker.start'; -import { SyncDescriptor0 } from './vs/platform/instantiation/common/descriptors'; -import * as editorAPI from './vs/editor/editor.api'; +import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation.js'; +import { start } from './vs/editor/editor.worker.start.js'; +import { SyncDescriptor0 } from './vs/platform/instantiation/common/descriptors.js'; +import * as editorAPI from './vs/editor/editor.api.js'; (function () { var a: any; diff --git a/package-lock.json b/package-lock.json index 4b4836edeb1..0bda4675df5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,7 @@ "@types/trusted-types": "^1.0.6", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", - "@types/wicg-file-system-access": "^2020.9.6", + "@types/wicg-file-system-access": "^2023.10.7", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", @@ -2197,10 +2197,11 @@ } }, "node_modules/@types/wicg-file-system-access": { - "version": "2020.9.6", - "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.6.tgz", - "integrity": "sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==", - "dev": true + "version": "2023.10.7", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz", + "integrity": "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/windows-foreground-love": { "version": "0.3.0", diff --git a/package.json b/package.json index 9c97d57a761..1ce8f67d781 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@types/trusted-types": "^1.0.6", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", - "@types/wicg-file-system-access": "^2020.9.6", + "@types/wicg-file-system-access": "^2023.10.7", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index d1c93b6a672..ade7058f149 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -9,7 +9,7 @@ ], "paths": {}, "module": "esnext", - "moduleResolution": "classic", + "moduleResolution": "nodenext", "removeComments": false, "preserveConstEnums": true, "target": "ES2022", From 4dbe01368ca21862cb619daddf52313d34a69341 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:46:52 -0700 Subject: [PATCH 1064/4355] Use setTimeout0 to defer setting Count til element is fully shown for now (#270825) This is a workaround for now for an accessibility bug that should be addressed. The way that the checked count works is that it's a `CountBadge` widget wrapped in an `aria-live` that reports when the text content (aka the number) changes. However, when the QuickPick is initially shown to the user, the resolved count is done so before actually made visible... `aria-live` does not read out anything that is changed when hidden. I think a proper change here would be to have a separate `aria-live` element that is always off screen (so "technically" visible)... that can be updated when a Count is made visible... but this small fix is fine to at least give screen reader users a count. Fixes https://github.com/microsoft/vscode/issues/258617 --- src/vs/platform/quickinput/browser/quickInputController.ts | 7 +++++-- src/vs/platform/quickinput/browser/tree/quickTree.ts | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 6f08f5344a5..7f002ac337e 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -29,7 +29,7 @@ import { autorun, observableValue } from '../../../base/common/observable.js'; import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; -import { Platform, platform } from '../../../base/common/platform.js'; +import { Platform, platform, setTimeout0 } from '../../../base/common/platform.js'; import { getWindowControlsStyle, WindowControlsStyle } from '../../window/common/window.js'; import { getZoomFactor } from '../../../base/browser/browser.js'; import { TriStateCheckbox } from '../../../base/browser/ui/toggle/toggle.js'; @@ -227,7 +227,10 @@ export class QuickInputController extends Disposable { visibleCount.setCount(c); })); this._register(list.onChangedCheckedCount(c => { - count.setCount(c); + // TODO@TylerLeonhardt: Without this setTimeout, the screen reader will not read out + // the final count of checked items correctly. Investigate a better way + // to do this. ref https://github.com/microsoft/vscode/issues/258617 + setTimeout0(() => count.setCount(c)); })); this._register(list.onLeave(() => { // Defer to avoid the input field reacting to the triggering key. diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 9411cf61a8c..44db5bdc24c 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -5,6 +5,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { autorun, IReader, observableValue } from '../../../../base/common/observable.js'; +import { setTimeout0 } from '../../../../base/common/platform.js'; import { localize } from '../../../../nls.js'; import { IQuickTree, IQuickTreeItem, IQuickTreeItemButtonEvent, QuickInputType, QuickPickFocus } from '../../common/quickInput.js'; import { QuickInput, QuickInputUI, Visibilities } from '../quickInput.js'; @@ -135,7 +136,10 @@ export class QuickTree extends QuickInput implements I super.show(); // TODO: Why have show() bubble up while update() trickles down? // Intial state - this.ui.count.setCount(this.ui.tree.getCheckedLeafItems().length); + // TODO@TylerLeonhardt: Without this setTimeout, the screen reader will not read out + // the final count of checked items correctly. Investigate a better way + // to do this. ref https://github.com/microsoft/vscode/issues/258617 + setTimeout0(() => this.ui.count.setCount(this.ui.tree.getCheckedLeafItems().length)); const checkAllState = getParentNodeState([...this.ui.tree.tree.getNode().children]); if (this.ui.checkAll.checked !== checkAllState) { this.ui.checkAll.checked = checkAllState; From a3f9369c717f0c3d681b4891512caea6f4980a01 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Fri, 10 Oct 2025 17:07:10 -0700 Subject: [PATCH 1065/4355] Enables adding editor selection as context when using inline editor chat --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 1 + .../workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 236262563ba..1980b07c4d8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -190,6 +190,7 @@ configurationRegistry.registerConfiguration({ }, default: { 'panel': 'always', + 'editor': 'always', } }, 'chat.implicitContext.suggestedContext': { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 85a7c8b047b..64ff3aeb7df 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -86,6 +86,7 @@ export class InlineChatZoneWidget extends ZoneWidget { telemetrySource: 'interactiveEditorWidget-toolbar', inputSideToolbar: MENU_INLINE_CHAT_SIDE }, + enableImplicitContext: true, ...options, rendererOptions: { renderTextEditsAsSummary: (uri) => { From 1026e35b16eb6a0cfdca82d52e83256c738cb712 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 10 Oct 2025 18:16:13 -0700 Subject: [PATCH 1066/4355] Add support for navigating to absolute character offset in the editor with "::" shortcut --- .../browser/gotoLineQuickAccess.ts | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 4d720513f9f..191c1abe650 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -3,29 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Toggle } from '../../../../base/browser/ui/toggle/toggle.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { localize } from '../../../../nls.js'; +import { IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from '../../../../platform/theme/common/colors/inputColors.js'; +import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { getCodeEditor } from '../../../browser/editorBrowser.js'; import { EditorOption, RenderLineNumbersType } from '../../../common/config/editorOptions.js'; import { IPosition } from '../../../common/core/position.js'; import { IRange } from '../../../common/core/range.js'; import { IEditor, ScrollType } from '../../../common/editorCommon.js'; import { AbstractEditorNavigationQuickAccessProvider, IQuickAccessTextEditorContext } from './editorNavigationQuickAccess.js'; -import { localize } from '../../../../nls.js'; -import { IQuickPick, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; + private zeroBasedOffset = false; constructor() { super({ canAcceptInBackground: true }); } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line or offset."); picker.items = [{ label }]; picker.ariaLabel = label; @@ -55,7 +60,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // React to picker changes const updatePickerAndEditor = () => { - const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length)); + const position = this.parsePosition(editor, picker.value.trim().substring(AbstractGotoLineQuickAccessProvider.PREFIX.length)); const label = this.getPickLabel(editor, position.lineNumber, position.column); // Picker @@ -81,6 +86,25 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // Decorate this.addDecorations(editor, range); }; + + // Add a toggle to switch between 1- and 0-based offsets. + const toggle = new Toggle({ + title: localize('gotoLineToggle', "Use zero-based offset"), + icon: Codicon.symbolNumber, + isChecked: this.zeroBasedOffset, + inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), + inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), + inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) + }); + + disposables.add( + toggle.onChange(() => { + this.zeroBasedOffset = !this.zeroBasedOffset; + updatePickerAndEditor(); + })); + + picker.toggles = [toggle]; + updatePickerAndEditor(); disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor())); @@ -110,6 +134,24 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor private parsePosition(editor: IEditor, value: string): IPosition { + // Support :: notation to navigate to a specific offset in the model. + if (value.startsWith(':')) { + let offset = parseInt(value.substring(1), 10); + if (!isNaN(offset)) { + const model = this.getModel(editor); + if (model) { + if (offset >= 0) { + // If offset is 1-based, we need to convert to model's 0-based. + offset -= this.zeroBasedOffset ? 0 : 1; + } else { + // Offset from the end of the buffer + offset += model.getValueLength(); + } + return model.getPositionAt(offset); + } + } + } + // Support line-col formats of `line,col`, `line:col`, `line#col` const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); const endLine = this.lineCount(editor) + 1; From cd5348abcdcaac0c1b9a8f9da515aa34b1d4cdbd Mon Sep 17 00:00:00 2001 From: ritesh006 Date: Tue, 9 Sep 2025 18:02:22 +0530 Subject: [PATCH 1067/4355] ts-codelens: implementations for overrides + test --- .../typescript-language-features/package.json | 6 ++ .../package.nls.json | 1 + .../codeLens/implementationsCodeLens.ts | 65 ++++++++++++++----- .../smoke/implementationsCodeLens.test.ts | 47 ++++++++++++++ 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b48ba9ea67a..0a86124c140 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -218,6 +218,12 @@ "description": "%typescript.implementationsCodeLens.showOnInterfaceMethods%", "scope": "window" }, + "typescript.implementationsCodeLens.showOnClassMethods": { + "type": "boolean", + "default": false, + "description": "%typescript.implementationsCodeLens.showOnClassMethods.desc%", + "scope": "window" + }, "typescript.reportStyleChecksAsWarnings": { "type": "boolean", "default": true, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index ceb221b137b..106e965b3f6 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -56,6 +56,7 @@ "typescript.referencesCodeLens.showOnAllFunctions": "Enable/disable references CodeLens on all functions in TypeScript files.", "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. This CodeLens shows the implementers of an interface.", "typescript.implementationsCodeLens.showOnInterfaceMethods": "Enable/disable implementations CodeLens on interface methods.", + "typescript.implementationsCodeLens.showOnClassMethods": "Enable/disable showing 'implementations' CodeLens above class methods.", "typescript.openTsServerLog.title": "Open TS Server log", "typescript.restartTsServer": "Restart TS Server", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version...", diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index 6088292f8d0..ed4627707f7 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -25,7 +25,8 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip super(client, _cachedResponse); this._register( vscode.workspace.onDidChangeConfiguration(evt => { - if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`)) { + if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`) || + evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnClassMethods`)) { this.changeEmitter.fire(); } }) @@ -69,9 +70,12 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip } private getCommand(locations: vscode.Location[], codeLens: ReferencesCodeLens): vscode.Command | undefined { + if (!locations.length) { + return undefined; + } return { title: this.getTitle(locations), - command: locations.length ? 'editor.action.showReferences' : '', + command: 'editor.action.showReferences', arguments: [codeLens.document, codeLens.range.start, locations] }; } @@ -87,23 +91,52 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined ): vscode.Range | undefined { - if (item.kind === PConst.Kind.method && parent && parent.kind === PConst.Kind.interface && vscode.workspace.getConfiguration(this.language.id).get('implementationsCodeLens.showOnInterfaceMethods')) { + const cfg = vscode.workspace.getConfiguration(this.language.id); + + // Keep the class node itself so we enter children + if (item.kind === PConst.Kind.class) { return getSymbolRange(document, item); } - switch (item.kind) { - case PConst.Kind.interface: - return getSymbolRange(document, item); - - case PConst.Kind.class: - case PConst.Kind.method: - case PConst.Kind.memberVariable: - case PConst.Kind.memberGetAccessor: - case PConst.Kind.memberSetAccessor: - if (item.kindModifiers.match(/\babstract\b/g)) { - return getSymbolRange(document, item); - } - break; + + // Keep the interface node itself so we enter children + if (item.kind === PConst.Kind.interface) { + return getSymbolRange(document, item); } + + // Interface members (behind existing setting) + if ( + item.kind === PConst.Kind.method && + parent?.kind === PConst.Kind.interface && + cfg.get('implementationsCodeLens.showOnInterfaceMethods') + ) { + return getSymbolRange(document, item); + } + + // Skip private methods (cannot be overridden) + if (item.kind === PConst.Kind.method && /\bprivate\b/.test(item.kindModifiers ?? '')) { + return undefined; + } + + // Abstract members (always show) + if ( + (item.kind === PConst.Kind.method || + item.kind === PConst.Kind.memberVariable || + item.kind === PConst.Kind.memberGetAccessor || + item.kind === PConst.Kind.memberSetAccessor) && + /\babstract\b/.test(item.kindModifiers ?? '') + ) { + return getSymbolRange(document, item); + } + + // Class methods (behind new setting; default off) + if ( + item.kind === PConst.Kind.method && + parent?.kind === PConst.Kind.class && + cfg.get('implementationsCodeLens.showOnClassMethods', false) + ) { + return getSymbolRange(document, item); + } + return undefined; } } diff --git a/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts b/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts new file mode 100644 index 00000000000..3fed30cb6be --- /dev/null +++ b/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { joinLines, withRandomFileEditor } from "../testUtils"; + +suite("TypeScript Implementations CodeLens", () => { + test("should show implementations code lens for overridden methods", async () => { + await withRandomFileEditor( + joinLines( + "abstract class A {", + " foo() {}", + "}", + "class B extends A {", + " foo() {}", + "}", + ), + "ts", + async (editor: vscode.TextEditor, doc: vscode.TextDocument) => { + assert.strictEqual( + editor.document, + doc, + "Editor and document should match", + ); + + const lenses = await vscode.commands.executeCommand( + "vscode.executeCodeLensProvider", + doc.uri, + ); + + const fooLens = lenses?.find((lens) => + doc.getText(lens.range).includes("foo"), + ); + + assert.ok(fooLens, "Expected a CodeLens above foo()"); + assert.match( + fooLens!.command?.title ?? "", + /1 implementation/, + 'Expected lens to show "1 implementation"', + ); + }, + ); + }); +}); From 5f881010a0871ab02c145eac646397a8d78ac3a7 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 10 Oct 2025 23:55:15 -0700 Subject: [PATCH 1068/4355] Add setting to turn off "Ask GitHub Copilot" option in command palette (#270458) * Added setting to turn off "Ask GitHub Copilot" option in command palette * Update src/vs/workbench/browser/quickaccess.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * PR feedback * Update src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts Co-authored-by: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Update src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts Co-authored-by: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> * Remove experimental from showAskChat setting * PR feedback --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> --- src/vs/workbench/browser/quickaccess.ts | 2 + .../browser/workbench.contribution.ts | 6 +++ .../browser/commandsQuickAccess.ts | 43 ++++++++++++------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index 43e82650601..3bdba440b52 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -29,6 +29,7 @@ export interface IWorkbenchQuickAccessConfiguration { readonly commandPalette: { readonly history: number; readonly preserveInput: boolean; + readonly showAskInChat: boolean; readonly experimental: { readonly suggestCommands: boolean; readonly enableNaturalLanguageSearch: boolean; @@ -53,6 +54,7 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan quickInputService.navigate(!!next, quickNavigate); }; } + export class PickerEditorState extends Disposable { private _editorViewState: { editor: EditorInput; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 7881300d195..a971e7e18c7 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -490,6 +490,12 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('askChatLocation.quickChat', "Ask chat questions in Quick Chat.") ] }, + 'workbench.commandPalette.showAskInChat': { + 'type': 'boolean', + tags: ['experimental'], + 'description': localize('showAskInChat', "Controls whether the command palette shows 'Ask in Chat' option at the bottom."), + 'default': true + }, 'workbench.commandPalette.experimental.enableNaturalLanguageSearch': { 'type': 'boolean', tags: ['experimental'], diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index f2565eaec58..7ff6b778cca 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -98,6 +98,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce return { preserveInput: commandPaletteConfig.preserveInput, + showAskInChat: commandPaletteConfig.showAskInChat, experimental: commandPaletteConfig.experimental }; } @@ -158,29 +159,39 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce return []; } - let additionalPicks; - + let additionalPicks: (ICommandQuickPick | IQuickPickSeparator)[] = []; try { // Wait a bit to see if the user is still typing await timeout(CommandsQuickAccessProvider.AI_RELATED_INFORMATION_DEBOUNCE, token); additionalPicks = await this.getRelatedInformationPicks(allPicks, picksSoFar, filter, token); } catch (e) { - return []; + // Ignore and continue to add "Ask in Chat" option } - if (picksSoFar.length || additionalPicks.length) { - additionalPicks.push({ - type: 'separator' - }); - } - - const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); - if (defaultAgent) { - additionalPicks.push({ - label: localize('askXInChat', "Ask {0}: {1}", defaultAgent.fullName, filter), - commandId: this.configuration.experimental.askChatLocation === 'quickChat' ? ASK_QUICK_QUESTION_ACTION_ID : CHAT_OPEN_ACTION_ID, - args: [filter] - }); + // If enabled in settings, add "Ask in Chat" option after a separator (if needed). + if (this.configuration.showAskInChat) { + const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); + if (defaultAgent) { + if (picksSoFar.length || additionalPicks.length) { + additionalPicks.push({ + type: 'separator' + }); + } + + additionalPicks.push({ + label: localize('commandsQuickAccess.askInChat', "Ask in Chat: {0}", filter), + commandId: this.configuration.experimental.askChatLocation === 'quickChat' ? ASK_QUICK_QUESTION_ACTION_ID : CHAT_OPEN_ACTION_ID, + args: [filter], + buttons: [{ + iconClass: ThemeIcon.asClassName(Codicon.gear), + tooltip: localize('commandsQuickAccess.configureAskInChatSetting', "Configure visibility"), + }], + trigger: () => { + void this.preferencesService.openSettings({ jsonEditor: false, query: 'workbench.commandPalette.showAskInChat' }); + return TriggerAction.CLOSE_PICKER; + }, + }); + } } return additionalPicks; From 8818bb339f8bbd11d565bc8016a265ec70e66c10 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 11 Oct 2025 20:00:54 +0200 Subject: [PATCH 1069/4355] chat - fix height compute of tree (#270876) The `gap: 4px` directive for the chat input part needs to be accounted for. In addition, the tree container itself should not require to get a height applied provided its parent list container has a proper height. This fixes issues where the blue outline box would be cut off at the bottom. --- .github/CODENOTIFY | 4 +++- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 4 +--- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index d4b5414e616..c9b5a7afe86 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -92,7 +92,9 @@ src/vs/workbench/electron-browser/** @bpasero # Workbench Contributions src/vs/workbench/contrib/files/** @bpasero -src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens +src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens @bpasero +src/vs/workbench/contrib/chat/browser/chatInputPart.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatWidget.ts @bpasero src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 2511a8260a4..65e9aaf5352 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1921,7 +1921,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge followupsHeight: this.followupsContainer.offsetHeight, inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, - inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : 28, + inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (3 * 4) /* flex gap: todo|edits|input */), attachmentsHeight: this.attachmentsHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index f5fbae30215..4f79d5c281f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2606,8 +2606,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this.container.classList.contains('new-welcome-view')) { this.inputPart.layout(layoutHeight, Math.min(width, 650)); - } - else { + } else { this.inputPart.layout(layoutHeight, width); } @@ -2622,7 +2621,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.listContainer.style.setProperty('--chat-current-response-min-height', contentHeight * .75 + 'px'); } this.tree.layout(contentHeight, width); - this.tree.getHTMLElement().style.height = `${contentHeight}px`; // Push the welcome message down so it doesn't change position // when followups, attachments or working set appear diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 24563ad2c4b..d9cdc248ac7 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -757,7 +757,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-editing-session { - margin-bottom: -4px; + margin-bottom: -4px; /* reset the 4px gap of the container for editing sessions */ width: 100%; position: relative; } From 57b3363cd81d7159e49b5156c011eb6266d9ce77 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 11 Oct 2025 20:09:13 +0200 Subject: [PATCH 1070/4355] chat - less use of agent name (#270897) --- .../chatEditing/chatEditingCodeEditorIntegration.ts | 8 +------- .../chat/browser/chatEditing/chatEditingServiceImpl.ts | 8 ++------ .../notebook/chatEditingNotebookEditorIntegration.ts | 8 +------- .../contrib/tasks/browser/abstractTaskService.ts | 5 ++--- 4 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index 68204ef98a2..e769749a75b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -36,9 +36,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../../scm/common/quickDiff.js'; -import { IChatAgentService } from '../../common/chatAgents.js'; import { IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; -import { ChatAgentLocation } from '../../common/constants.js'; import { isTextDiffEditorForEntry } from './chatEditing.js'; import { ActionViewItem } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { AcceptHunkAction, RejectHunkAction } from './chatEditingEditorActions.js'; @@ -73,7 +71,6 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito private readonly _editor: ICodeEditor, documentDiffInfo: IObservable, renderDiffImmediately: boolean, - @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IEditorService private readonly _editorService: IEditorService, @IAccessibilitySignalService private readonly _accessibilitySignalsService: IAccessibilitySignalService, @IInstantiationService instantiationService: IInstantiationService, @@ -619,14 +616,11 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito // Use the 'show' argument to control the diff state if provided if (show !== undefined ? show : !isDiffEditor) { // Open DIFF editor - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; const diffEditor = await this._editorService.openEditor({ original: { resource: this._entry.originalURI }, modified: { resource: this._entry.modifiedURI }, options: { selection }, - label: defaultAgentName - ? localize('diff.agent', '{0} (changes from {1})', basename(this._entry.modifiedURI), defaultAgentName) - : localize('diff.generic', '{0} (changes from chat)', basename(this._entry.modifiedURI)) + label: localize('diff.generic', '{0} (changes from chat)', basename(this._entry.modifiedURI)) }); if (diffEditor && diffEditor.input) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index 7a1bde53981..f6dafc422ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -36,11 +36,9 @@ import { ILifecycleService } from '../../../../services/lifecycle/common/lifecyc import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; import { CellUri } from '../../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; -import { IChatAgentService } from '../../common/chatAgents.js'; import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingAgentSupportsReadonlyReferencesContextKey, chatEditingResourceContextKey, ChatEditingSessionState, IChatEditingService, IChatEditingSession, IChatRelatedFile, IChatRelatedFilesProvider, IModifiedFileEntry, inChatEditingSessionContextKey, IStreamingEdits, ModifiedFileEntryState, parseChatMultiDiffUri } from '../../common/chatEditingService.js'; import { ChatModel, IChatResponseModel, isCellTextEditOperation } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { ChatAgentLocation } from '../../common/constants.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { ChatEditingSession } from './chatEditingSession.js'; @@ -411,8 +409,7 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider readonly onDidChange: Event; constructor( - private readonly _sessions: IObservable, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + private readonly _sessions: IObservable ) { super(); this.onDidChange = Event.any( @@ -432,11 +429,10 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider } const isModified = this._modifiedUris.get().some(e => e.toString() === uri.toString()); if (isModified) { - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; return { weight: 1000, letter: Codicon.diffModified, - tooltip: defaultAgentName ? localize('chatEditing.modified', "Pending changes from {0}", defaultAgentName) : localize('chatEditing.modified2', "Pending changes from chat"), + tooltip: localize('chatEditing.modified2', "Pending changes from chat"), bubble: true }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts index d8486785aaf..c4719375670 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts @@ -27,9 +27,7 @@ import { INotebookEditorService } from '../../../../notebook/browser/services/no import { NotebookCellTextModel } from '../../../../notebook/common/model/notebookCellTextModel.js'; import { NotebookTextModel } from '../../../../notebook/common/model/notebookTextModel.js'; import { CellKind } from '../../../../notebook/common/notebookCommon.js'; -import { IChatAgentService } from '../../../common/chatAgents.js'; import { IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration } from '../../../common/chatEditingService.js'; -import { ChatAgentLocation } from '../../../common/constants.js'; import { ChatEditingCodeEditorIntegration, IDocumentDiff2 } from '../chatEditingCodeEditorIntegration.js'; import { ChatEditingModifiedNotebookEntry } from '../chatEditingModifiedNotebookEntry.js'; import { countChanges, ICellDiffInfo, sortCellChanges } from './notebookCellChanges.js'; @@ -118,7 +116,6 @@ class ChatEditingNotebookEditorWidgetIntegration extends Disposable implements I private readonly cellChanges: IObservable, @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService, @INotebookEditorService notebookEditorService: INotebookEditorService, @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, @ILogService private readonly logService: ILogService, @@ -655,13 +652,10 @@ class ChatEditingNotebookEditorWidgetIntegration extends Disposable implements I } async toggleDiff(_change: IModifiedFileEntryChangeHunk | undefined, _show?: boolean): Promise { - const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat)?.fullName; const diffInput: IResourceDiffEditorInput = { original: { resource: this._entry.originalURI }, modified: { resource: this._entry.modifiedURI }, - label: defaultAgentName - ? localize('diff.agent', '{0} (changes from {1})', basename(this._entry.modifiedURI), defaultAgentName) - : localize('diff.generic', '{0} (changes from chat)', basename(this._entry.modifiedURI)) + label: localize('diff.generic', '{0} (changes from chat)', basename(this._entry.modifiedURI)) }; await this._editorService.openEditor(diffInput); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 8b4be017f7a..44835396e86 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -818,10 +818,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const defaultAgent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); - const providerName = defaultAgent?.fullName; - if (providerName) { + if (defaultAgent) { actions.push({ - label: nls.localize('troubleshootWithChat', "Fix with {0}", providerName), + label: nls.localize('troubleshootWithChat', "Fix with AI"), run: async () => { this._commandService.executeCommand(CHAT_OPEN_ACTION_ID, { mode: ChatModeKind.Agent, From 6412b1d8f5e6ee5a10265807e863256c3e2c09ef Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 11 Oct 2025 20:10:44 +0200 Subject: [PATCH 1071/4355] chat - give a proper name to the file that provides "New..." chat actions (#270899) --- .../{chatClearActions.ts => chatNewActions.ts} | 16 ++++++---------- .../contrib/chat/browser/chat.contribution.ts | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) rename src/vs/workbench/contrib/chat/browser/actions/{chatClearActions.ts => chatNewActions.ts} (95%) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts similarity index 95% rename from src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts rename to src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts index d09a0189464..83f3f8c6d62 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts @@ -24,6 +24,7 @@ import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_OPEN_CHAT, CH import { clearChatEditor } from './chatClear.js'; export interface INewEditSessionActionContext { + /** * An initial prompt to write to the chat. */ @@ -55,8 +56,6 @@ export function registerNewChatActions() { isSplitButton: true }); - // This action was previously used for the editor gutter toolbar, but now - // ACTION_ID_NEW_CHAT is also used for that scenario registerAction2(class NewChatEditorAction extends Action2 { constructor() { super({ @@ -68,7 +67,10 @@ export function registerNewChatActions() { }); } async run(accessor: ServicesAccessor, ...args: unknown[]) { - announceChatCleared(accessor.get(IAccessibilitySignalService)); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); + + accessibilitySignalService.playSignal(AccessibilitySignal.clear); + await clearChatEditor(accessor); } }); @@ -117,7 +119,6 @@ export function registerNewChatActions() { }); } - async run(accessor: ServicesAccessor, ...args: unknown[]) { const executeCommandContext = args[0] as INewEditSessionActionContext | undefined; @@ -135,7 +136,7 @@ export function registerNewChatActions() { return; } - announceChatCleared(accessibilitySignalService); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); await editingSession?.stop(); widget.clear(); @@ -163,7 +164,6 @@ export function registerNewChatActions() { }); CommandsRegistry.registerCommandAlias(ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_NEW_CHAT); - registerAction2(class UndoChatEditInteractionAction extends EditingSessionAction { constructor() { super({ @@ -254,7 +254,3 @@ export function registerNewChatActions() { } }); } - -function announceChatCleared(accessibilitySignalService: IAccessibilitySignalService): void { - accessibilitySignalService.playSignal(AccessibilitySignal.clear); -} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 236262563ba..055e6790796 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -63,7 +63,7 @@ import { IVoiceChatService, VoiceChatService } from '../common/voiceChatService. import { registerChatAccessibilityActions } from './actions/chatAccessibilityActions.js'; import { AgentChatAccessibilityHelp, EditsChatAccessibilityHelp, PanelChatAccessibilityHelp, QuickChatAccessibilityHelp } from './actions/chatAccessibilityHelp.js'; import { ACTION_ID_NEW_CHAT, CopilotTitleBarMenuRendering, registerChatActions } from './actions/chatActions.js'; -import { registerNewChatActions } from './actions/chatClearActions.js'; +import { registerNewChatActions } from './actions/chatNewActions.js'; import { CodeBlockActionRendering, registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from './actions/chatCodeblockActions.js'; import { ChatContextContributions } from './actions/chatContext.js'; import { registerChatContextActions } from './actions/chatContextActions.js'; From da5341960035abab03944ad203c9f74136164c43 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:50:44 -0700 Subject: [PATCH 1072/4355] Add myself to CODENOTIFY & a helpful prompt file (#270918) * Add myself to CODENOTIFY & a helpful prompt file :rocket: * no mcp searches --- .github/CODENOTIFY | 29 ++++++++++ .github/prompts/codenotify.prompt.md | 81 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 .github/prompts/codenotify.prompt.md diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index c9b5a7afe86..d8591437bf6 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -2,6 +2,7 @@ src/vs/base/common/extpath.ts @bpasero src/vs/base/common/fuzzyScorer.ts @bpasero src/vs/base/common/glob.ts @bpasero +src/vs/base/common/oauth.ts @TylerLeonhardt src/vs/base/common/path.ts @bpasero src/vs/base/common/stream.ts @bpasero src/vs/base/browser/domSanitize.ts @mjbvz @@ -10,6 +11,7 @@ src/vs/base/node/pfs.ts @bpasero src/vs/base/node/unc.ts @bpasero src/vs/base/parts/contextmenu/** @bpasero src/vs/base/parts/ipc/** @bpasero +src/vs/base/parts/quickinput/** @TylerLeonhardt src/vs/base/parts/sandbox/** @bpasero src/vs/base/parts/storage/** @bpasero @@ -33,6 +35,8 @@ src/vs/platform/launch/** @bpasero src/vs/platform/lifecycle/** @bpasero src/vs/platform/menubar/** @bpasero src/vs/platform/native/** @bpasero +src/vs/platform/quickinput/** @TylerLeonhardt +src/vs/platform/secrets/** @TylerLeonhardt src/vs/platform/sharedProcess/** @bpasero src/vs/platform/state/** @bpasero src/vs/platform/storage/** @bpasero @@ -60,6 +64,7 @@ src/vs/code/** @bpasero @deepak1556 # Workbench Services src/vs/workbench/services/activity/** @bpasero +src/vs/workbench/services/authentication/** @TylerLeonhardt src/vs/workbench/services/auxiliaryWindow/** @bpasero src/vs/workbench/services/chat/** @bpasero src/vs/workbench/services/contextmenu/** @bpasero @@ -71,6 +76,7 @@ src/vs/workbench/services/filesConfiguration/** @bpasero src/vs/workbench/services/history/** @bpasero src/vs/workbench/services/host/** @bpasero src/vs/workbench/services/label/** @bpasero +src/vs/workbench/services/languageDetection/** @TylerLeonhardt src/vs/workbench/services/layout/** @bpasero src/vs/workbench/services/lifecycle/** @bpasero src/vs/workbench/services/notification/** @bpasero @@ -91,10 +97,33 @@ src/vs/workbench/browser/** @bpasero src/vs/workbench/electron-browser/** @bpasero # Workbench Contributions +src/vs/workbench/contrib/authentication/** @TylerLeonhardt src/vs/workbench/contrib/files/** @bpasero src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens @bpasero src/vs/workbench/contrib/chat/browser/chatInputPart.ts @bpasero src/vs/workbench/contrib/chat/browser/chatWidget.ts @bpasero src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero +src/vs/workbench/contrib/localization/** @TylerLeonhardt +src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @TylerLeonhardt src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno + +# Build +build/lib/i18n.ts @TylerLeonhardt + +# Editor contrib +src/vs/editor/standalone/browser/quickInput/** @TylerLeonhardt + +# Workbench API +src/vs/workbench/api/common/extHostLocalizationService.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/node/extHostAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostMcp.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadMcp.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostQuickOpen.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadSecretState.ts @TylerLeonhardt + +# Extensions +extensions/microsoft-authentication/** @TylerLeonhardt +extensions/github-authentication/** @TylerLeonhardt diff --git a/.github/prompts/codenotify.prompt.md b/.github/prompts/codenotify.prompt.md new file mode 100644 index 00000000000..15f679fc1b1 --- /dev/null +++ b/.github/prompts/codenotify.prompt.md @@ -0,0 +1,81 @@ +--- +mode: agent +tools: ['edit', 'search', 'runCommands', 'fetch', 'todos'] +--- + +# Add My Contributions to CODENOTIFY + +This prompt helps you add your code contributions to the `.github/CODENOTIFY` file based on git blame history. + +## Instructions + +**Before running this prompt, provide the following information:** + +1. **Your GitHub handle:** (e.g., `@YOURHANDLE`) +2. **Alternative usernames in git blame:** (e.g., `Erich Gamma`, `ALIAS@microsoft.com`, or any other names/emails that might appear in git commits) + +## What This Prompt Does + +This prompt will: +1. Search through the repository's git blame history for files you've significantly contributed to +2. Analyze which files and directories have your contributions +3. **Follow the existing structure** in the `.github/CODENOTIFY` file, here are some examples: + - `src/vs/base/common/**` → Add to **Base Utilities** section + - `src/vs/base/browser/ui/**` → Add to **Base Widgets** section + - `src/vs/base/parts/**` → Add to **Base Utilities** section + - `src/vs/platform/**` → Add to **Platform** section + - `src/bootstrap-*.ts`, `src/main.ts`, etc. → Add to **Bootstrap** section + - `src/vs/code/**` → Add to **Electron Main** section + - `src/vs/workbench/services/**` → Add to **Workbench Services** section + - `src/vs/workbench/common/**`, `src/vs/workbench/browser/**` → Add to **Workbench Core** section + - `src/vs/workbench/contrib/**` → Add to **Workbench Contributions** section + - `src/vs/workbench/api/**` → Add to **Workbench API** section + - `extensions/**` → Add to **Extensions** section +4. Add appropriate entries in the format: + - Individual files: `path/to/file.ts @yourusername` + - Directories: `path/to/directory/** @yourusername` +5. Place entries within existing sections, maintaining alphabetical or logical order +6. Create new sections only if contributions don't fit existing categories +7. Avoid duplicating existing entries + +## Expected Output Format + +Entries will be added to **existing sections** based on their path. For example: + +``` +# Base Utilities +src/vs/base/common/extpath.ts @bpasero +src/vs/base/common/oauth.ts @yourusername # ← Your contribution added here +src/vs/base/parts/quickinput/** @yourusername # ← Your contribution added here + +# Platform +src/vs/platform/quickinput/** @yourusername # ← Your contribution added here +src/vs/platform/secrets/** @yourusername # ← Your contribution added here + +# Workbench Services +src/vs/workbench/services/authentication/** @yourusername # ← Your contribution added here + +# Workbench Contributions +src/vs/workbench/contrib/authentication/** @yourusername # ← Your contribution added here +src/vs/workbench/contrib/localization/** @yourusername # ← Your contribution added here +``` + +If you have contributions that don't fit existing sections (e.g., `foo/bar/**`), new sections can be created at the end: + +``` +# Foo Bar +foo/bar/baz/** @yourusername +foo/bar/biz/** @yourusername +``` + +## Notes + +- **CRITICAL**: Entries must be added to the appropriate existing section based on their path +- Respect the existing organizational structure of the CODENOTIFY file +- If you're already listed for certain files/directories, those won't be duplicated +- Use `**` wildcard for directories where you've touched multiple files +- Maintain alphabetical or logical order within each section + +--- + +**Now, provide your GitHub handle and any alternative usernames found in git blame, and I'll help you update the CODENOTIFY file.** From 859fe62377b371d77704888dd2665a3400031a50 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 11 Oct 2025 21:05:22 +0200 Subject: [PATCH 1073/4355] debt - apply action runner properly for `DropdownWithDefaultActionViewItem` (#270927) --- .../actions/browser/menuEntryActionViewItem.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 32695b91c24..e8cb06cf76b 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js'; import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from '../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js'; -import { ActionRunner, IAction, IRunEvent, Separator, SubmenuAction } from '../../../base/common/actions.js'; +import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator, SubmenuAction } from '../../../base/common/actions.js'; import { Event } from '../../../base/common/event.js'; import { UILabelProvider } from '../../../base/common/keybindingLabels.js'; import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; @@ -519,6 +519,17 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { this._dropdown.setActionContext(newContext); } + override set actionRunner(actionRunner: IActionRunner) { + super.actionRunner = actionRunner; + + this._defaultAction.actionRunner = actionRunner; + this._dropdown.actionRunner = actionRunner; + } + + override get actionRunner(): IActionRunner { + return super.actionRunner; + } + override render(container: HTMLElement): void { this._container = container; super.render(this._container); From 1fa220253322df45013535e347490921e5435240 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:56:18 +0800 Subject: [PATCH 1074/4355] remove border from plan on empty state (#270953) remove border from plan --- .../chat/browser/media/chatViewWelcome.css | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index 49c73335127..cdaed0b43cd 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -15,18 +15,30 @@ /* Container for chat widget welcome message and interactive session variants */ .interactive-session { - position: relative; /* Enable absolute positioning for child elements */ + position: relative; + /* Enable absolute positioning for child elements */ display: flex; flex-direction: column; height: 100%; &.new-welcome-view { .interactive-input-part { - .dropdown-action-container { display: none; } - .chat-attachments-container { display: none; } + .dropdown-action-container { + display: none; + } + + .chat-attachments-container { + display: none; + } + } + + .chat-input-toolbars > .chat-input-toolbar > div { + display: none; + } + + .chat-input-toolbars .action-item:not(:has(.monaco-dropdown-with-primary)) { + display: none; } - .chat-input-toolbars > .chat-input-toolbar > div { display: none; } - .chat-input-toolbars .action-item:not(:has(.monaco-dropdown-with-primary)) { display: none; } } /* chat welcome container */ @@ -37,20 +49,27 @@ justify-content: center; overflow: hidden; flex: 1; - position: relative; /* Allow absolute positioning of prompts */ + position: relative; + /* Allow absolute positioning of prompts */ &.has-chat-history { /* Reintroduce minimal layout so welcome block centers vertically when history is shown */ flex-direction: column; height: 100%; + /* Keep default align-items/justify-content from base (center) so history list sits above, then auto margins center welcome */ - div.chat-welcome-view { align-self: center; margin-top: auto; margin-bottom: auto; } + div.chat-welcome-view { + align-self: center; + margin-top: auto; + margin-bottom: auto; + } } } .new-welcome-view & > .chat-welcome-view-input-part { max-width: 650px; - margin-bottom: 8px; /* Reduced margin to make room for prompts below */ + margin-bottom: 8px; + /* Reduced margin to make room for prompts below */ } } @@ -115,7 +134,8 @@ div.chat-welcome-view { a { color: var(--vscode-textLink-foreground); } - p{ + + p { margin-top: 8px; margin-bottom: 8px; } @@ -167,6 +187,7 @@ div.chat-welcome-view { margin-top: 8px; padding: 0 12px; font-size: 12px; + a { color: var(--vscode-textLink-foreground); } @@ -178,7 +199,10 @@ div.chat-welcome-view { margin: -16px auto; max-width: 400px; padding: 0 12px; - a { color: var(--vscode-textLink-foreground); } + + a { + color: var(--vscode-textLink-foreground); + } } } @@ -192,7 +216,8 @@ div.chat-welcome-view { flex-wrap: wrap; justify-content: flex-start; row-gap: 8px; - padding: 32px 12px 12px 12px; /* Extra top padding for title */ + padding: 32px 12px 12px 12px; + /* Extra top padding for title */ .chat-welcome-view-suggested-prompts-title { position: absolute; @@ -241,7 +266,6 @@ div.chat-welcome-view { > .chat-welcome-view-suggested-prompt:hover { background-color: var(--vscode-list-hoverBackground); - border-color: var(--vscode-focusBorder); } } From bf56edffb59c43fa0de636c3aa1d548770b168b8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 13 Oct 2025 15:27:40 +1100 Subject: [PATCH 1075/4355] Limit Notebook Cell Editor to Viewport Height (#267089) Fix cell editor height based on visible lines in editor --- .../browser/view/cellParts/codeCell.ts | 346 ++++++++++++--- .../test/browser/view/cellPart.test.ts | 404 ++++++++++++++++++ 2 files changed, 702 insertions(+), 48 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 7694b7f4c44..c3ce0b1db55 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// allow-any-unicode-comment-file + import { localize } from '../../../../../../nls.js'; import * as DOM from '../../../../../../base/browser/dom.js'; import { raceCancellation } from '../../../../../../base/common/async.js'; @@ -22,7 +24,6 @@ import { CodeActionController } from '../../../../../../editor/contrib/codeActio import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; -import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; import { INotebookExecutionStateService } from '../../../common/notebookExecutionStateService.js'; import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, IActiveNotebookEditorDelegate } from '../../notebookBrowser.js'; import { CodeCellViewModel, outputDisplayLimit } from '../../viewModel/codeCellViewModel.js'; @@ -32,6 +33,8 @@ import { CodeCellRenderTemplate, collapsedCellTTPolicy } from '../notebookRender import { CellEditorOptions } from './cellEditorOptions.js'; import { CellOutputContainer } from './cellOutput.js'; import { CollapsedCodeCellExecutionIcon } from './codeCellExecutionIcon.js'; +import { INotebookLoggingService } from '../../../common/notebookLoggingService.js'; + export class CodeCell extends Disposable { private _outputContainerRenderer: CellOutputContainer; @@ -44,7 +47,9 @@ export class CodeCell extends Disposable { private _collapsedExecutionIcon: CollapsedCodeCellExecutionIcon; private _cellEditorOptions: CellEditorOptions; - + private _useNewApproachForEditorLayout = true; + private readonly _cellLayout: CodeCellLayout; + private readonly _debug: (output: string) => void; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, private readonly viewCell: CodeCellViewModel, @@ -52,20 +57,25 @@ export class CodeCell extends Disposable { private readonly editorPool: NotebookCellEditorPool, @IInstantiationService private readonly instantiationService: IInstantiationService, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IOpenerService openerService: IOpenerService, @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private configurationService: IConfigurationService, @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, + @INotebookLoggingService notebookLogService: INotebookLoggingService, ) { super(); + const cellIndex = this.notebookEditor.getCellIndex(this.viewCell); + const debugPrefix = `[Cell ${cellIndex}]`; + const debug = this._debug = (output: string) => { + notebookLogService.debug('CellLayout', `${debugPrefix} ${output}`); + }; this._cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: outputDisplayLimit }); this.cellParts = this._register(templateData.cellParts.concatContentPart([this._cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); - // this.viewCell.layoutInfo.editorHeight or estimation when this.viewCell.layoutInfo.editorHeight === 0 - const editorHeight = this.calculateInitEditorHeight(); - this.initializeEditor(editorHeight); + const initialEditorDimension = { height: this.calculateInitEditorHeight(), width: this.viewCell.layoutInfo.editorWidth }; + this._cellLayout = new CodeCellLayout(this._useNewApproachForEditorLayout, notebookEditor, viewCell, templateData, { debug }, initialEditorDimension); + this.initializeEditor(initialEditorDimension); this._renderedInputCollapseState = false; // editor is always expanded initially this.registerNotebookEditorListeners(); @@ -106,19 +116,20 @@ export class CodeCell extends Disposable { } })); + this.updateEditorOptions(); + this.updateEditorForFocusModeChange(false); + this.updateForOutputHover(); + this.updateForOutputFocus(); + this.cellParts.scheduleRenderCell(this.viewCell); this._register(toDisposable(() => { this.cellParts.unrenderCell(this.viewCell); })); - this.updateEditorOptions(); - this.updateEditorForFocusModeChange(false); - this.updateForOutputHover(); - this.updateForOutputFocus(); // Render Outputs - this.viewCell.editorHeight = editorHeight; + this.viewCell.editorHeight = initialEditorDimension.height; this._outputContainerRenderer.render(); this._renderedOutputCollapseState = false; // the output is always rendered initially // Need to do this after the intial renderOutput @@ -189,14 +200,10 @@ export class CodeCell extends Disposable { return editorHeight; } - private initializeEditor(initEditorHeight: number) { - const width = this.viewCell.layoutInfo.editorWidth; - this.layoutEditor( - { - width: width, - height: initEditorHeight - } - ); + private initializeEditor(dimension: IDimension) { + this._debug(`Initialize Editor ${dimension.height} x ${dimension.width}, Scroll Top = ${this.notebookEditor.scrollTop}`); + this._cellLayout.layoutEditor('init'); + this.layoutEditor(dimension); const cts = new CancellationTokenSource(); this._register({ dispose() { cts.dispose(true); } }); @@ -227,14 +234,14 @@ export class CodeCell extends Disposable { this.viewCell.focusMode === CellFocusMode.Editor && (this.notebookEditor.hasEditorFocus() || this.notebookEditor.getDomNode().ownerDocument.activeElement === this.notebookEditor.getDomNode().ownerDocument.body)) // Don't steal focus from other workbench parts, but if body has focus, we can take it { - this.templateData.editor?.focus(); + this.templateData.editor.focus(); } }; focusEditorIfNeeded(); - const realContentHeight = this.templateData.editor?.getContentHeight(); - if (realContentHeight !== undefined && realContentHeight !== initEditorHeight) { - this.onCellEditorHeightChange(realContentHeight); + const realContentHeight = this.templateData.editor.getContentHeight(); + if (realContentHeight !== dimension.height) { + this.onCellEditorHeightChange('onDidResolveTextModel'); } if (this._isDisposed) { @@ -262,22 +269,28 @@ export class CodeCell extends Disposable { const padding = this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata, this.viewCell.uri); const options = editor.getOptions(); if (options.get(EditorOption.readOnly) !== isReadonly || options.get(EditorOption.padding) !== padding) { - editor.updateOptions({ readOnly: this.notebookEditor.isReadOnly, padding: this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata, this.viewCell.uri) }); + editor.updateOptions({ + readOnly: this.notebookEditor.isReadOnly, padding: this.notebookEditor.notebookOptions.computeEditorPadding(this.viewCell.internalMetadata, this.viewCell.uri) + }); } } private registerNotebookEditorListeners() { this._register(this.notebookEditor.onDidScroll(() => { this.adjustEditorPosition(); + this._cellLayout.layoutEditor('nbDidScroll'); })); this._register(this.notebookEditor.onDidChangeLayout(() => { this.adjustEditorPosition(); - this.onCellWidthChange(); + this.onCellWidthChange('nbLayoutChange'); })); } private adjustEditorPosition() { + if (this._useNewApproachForEditorLayout) { + return; + } const extraOffset = - 6 /** distance to the top of the cell editor, which is 6px under the focus indicator */ - 1 /** border */; const min = 0; @@ -302,7 +315,7 @@ export class CodeCell extends Disposable { min; this.templateData.editorPart.style.top = `${top}px`; // scroll the editor with top - this.templateData.editor?.setScrollTop(top); + this.templateData.editor.setScrollTop(top); } private registerViewCellLayoutChange() { @@ -310,7 +323,7 @@ export class CodeCell extends Disposable { if (e.outerWidth !== undefined) { const layoutInfo = this.templateData.editor.getLayoutInfo(); if (layoutInfo.width !== this.viewCell.layoutInfo.editorWidth) { - this.onCellWidthChange(); + this.onCellWidthChange('viewCellLayoutChange'); this.adjustEditorPosition(); } } @@ -321,12 +334,41 @@ export class CodeCell extends Disposable { this._register(this.templateData.editor.onDidContentSizeChange((e) => { if (e.contentHeightChanged) { if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { - this.onCellEditorHeightChange(e.contentHeight); + this.onCellEditorHeightChange(`onDidContentSizeChange: ${e.contentHeight}`); this.adjustEditorPosition(); } } })); + if (this._useNewApproachForEditorLayout) { + this._register(this.templateData.editor.onDidScrollChange(e => { + if (this._cellLayout.editorVisibility === 'Invisible' || !this.templateData.editor.hasTextFocus()) { + return; + } + if (this._cellLayout._lastChangedEditorScrolltop === e.scrollTop || this._cellLayout.isUpdatingLayout) { + return; + } + const scrollTop = this.notebookEditor.scrollTop; + const diff = e.scrollTop - (this._cellLayout._lastChangedEditorScrolltop ?? 0); + if (this._cellLayout.editorVisibility === 'Full (Small Viewport)' && typeof this._cellLayout._lastChangedEditorScrolltop === 'number') { + this._debug(`Scroll Change (1) = ${e.scrollTop} changed by ${diff} (notebook scrollTop: ${scrollTop}, setEditorScrollTop: ${e.scrollTop})`); + // this.templateData.editor.setScrollTop(e.scrollTop); + } else if (this._cellLayout.editorVisibility === 'Bottom Clipped' && typeof this._cellLayout._lastChangedEditorScrolltop === 'number') { + this._debug(`Scroll Change (2) = ${e.scrollTop} changed by ${diff} (notebook scrollTop: ${scrollTop}, setNotebookScrollTop: ${scrollTop + e.scrollTop})`); + this.notebookEditor.setScrollTop(scrollTop + e.scrollTop); + } else if (this._cellLayout.editorVisibility === 'Top Clipped' && typeof this._cellLayout._lastChangedEditorScrolltop === 'number') { + const newScrollTop = scrollTop + diff - 1; + this._debug(`Scroll Change (3) = ${e.scrollTop} changed by ${diff} (notebook scrollTop: ${scrollTop}, setNotebookScrollTop?: ${newScrollTop})`); + if (scrollTop !== newScrollTop) { + this.notebookEditor.setScrollTop(newScrollTop); + } + } else { + this._debug(`Scroll Change (4) = ${e.scrollTop} changed by ${diff} (notebook scrollTop: ${scrollTop})`); + this._cellLayout._lastChangedEditorScrolltop = undefined; + } + })); + } + this._register(this.templateData.editor.onDidChangeCursorSelection((e) => { if ( // do not reveal the cell into view if this selection change was caused by restoring editors @@ -344,7 +386,10 @@ export class CodeCell extends Disposable { const layoutContentHeight = this.viewCell.layoutInfo.editorHeight; if (contentHeight !== layoutContentHeight) { - this.onCellEditorHeightChange(contentHeight); + if (!this._useNewApproachForEditorLayout) { + this._debug(`onDidChangeCursorSelection`); + this.onCellEditorHeightChange('onDidChangeCursorSelection'); + } if (this._isDisposed) { return; @@ -393,10 +438,10 @@ export class CodeCell extends Disposable { private updateEditorForFocusModeChange(sync: boolean) { if (this.shouldPreserveEditor()) { if (sync) { - this.templateData.editor?.focus(); + this.templateData.editor.focus(); } else { this._register(DOM.runAtThisOrScheduleAtNextAnimationFrame(DOM.getWindow(this.templateData.container), () => { - this.templateData.editor?.focus(); + this.templateData.editor.focus(); })); } } @@ -539,6 +584,9 @@ export class CodeCell extends Disposable { } private layoutEditor(dimension: IDimension): void { + if (this._useNewApproachForEditorLayout) { + return; + } const editorLayout = this.notebookEditor.getLayoutInfo(); const maxHeight = Math.min( editorLayout.height @@ -546,38 +594,49 @@ export class CodeCell extends Disposable { - 26 /** notebook toolbar */, dimension.height ); - this.templateData.editor?.layout({ + this._debug(`Layout Editor: Width = ${dimension.width}, Height = ${maxHeight} (Requested: ${dimension.height}, Editor Layout Height: ${editorLayout.height}, Sticky: ${editorLayout.stickyHeight})`); + this.templateData.editor.layout({ width: dimension.width, height: maxHeight }, true); } - private onCellWidthChange(): void { - if (!this.templateData.editor.hasModel()) { - return; + private onCellWidthChange(dbgReasonForChange: string): void { + this._debug(`Cell Editor Width Change, ${dbgReasonForChange}, Content Height = ${this.templateData.editor.getContentHeight()}`); + const height = this.templateData.editor.getContentHeight(); + if (this.templateData.editor.hasModel()) { + this._debug(`**** Updating Cell Editor Height (1), ContentHeight: ${height}, CodeCellLayoutInfo.EditorWidth ${this.viewCell.layoutInfo.editorWidth}, EditorLayoutInfo ${this.templateData.editor.getLayoutInfo().height} ****`); + this.viewCell.editorHeight = height; + this.relayoutCell(); + this.layoutEditor( + { + width: this.viewCell.layoutInfo.editorWidth, + height + } + ); + } else { + this._debug(`Cell Editor Width Change without model, return (1), ContentHeight: ${height}, CodeCellLayoutInfo.EditorWidth ${this.viewCell.layoutInfo.editorWidth}, EditorLayoutInfo ${this.templateData.editor.getLayoutInfo().height}`); } - - const realContentHeight = this.templateData.editor.getContentHeight(); - this.viewCell.editorHeight = realContentHeight; - this.relayoutCell(); - this.layoutEditor( - { - width: this.viewCell.layoutInfo.editorWidth, - height: realContentHeight - } - ); + this._cellLayout.layoutEditor(dbgReasonForChange); } - private onCellEditorHeightChange(newHeight: number): void { + private onCellEditorHeightChange(dbgReasonForChange: string): void { + const height = this.templateData.editor.getContentHeight(); + if (!this.templateData.editor.hasModel()) { + this._debug(`Cell Editor Height Change without model, return (2), ContentHeight: ${height}, CodeCellLayoutInfo.EditorWidth ${this.viewCell.layoutInfo.editorWidth}, EditorLayoutInfo ${this.templateData.editor.getLayoutInfo()}`); + } + this._debug(`Cell Editor Height Change (${dbgReasonForChange}): ${height}`); + this._debug(`**** Updating Cell Editor Height (2), ContentHeight: ${height}, CodeCellLayoutInfo.EditorWidth ${this.viewCell.layoutInfo.editorWidth}, EditorLayoutInfo ${this.templateData.editor.getLayoutInfo().height} ****`); const viewLayout = this.templateData.editor.getLayoutInfo(); - this.viewCell.editorHeight = newHeight; + this.viewCell.editorHeight = height; this.relayoutCell(); this.layoutEditor( { width: viewLayout.width, - height: newHeight + height } ); + this._cellLayout.layoutEditor(dbgReasonForChange); } relayoutCell() { @@ -601,3 +660,194 @@ export class CodeCell extends Disposable { super.dispose(); } } + +export class CodeCellLayout { + private _editorVisibility?: 'Full' | 'Top Clipped' | 'Bottom Clipped' | 'Full (Small Viewport)' | 'Invisible'; + public get editorVisibility() { + return this._editorVisibility; + } + private _isUpdatingLayout?: boolean; + public get isUpdatingLayout() { + return this._isUpdatingLayout; + } + public _previousScrollBottom?: number; + public _lastChangedEditorScrolltop?: number; + private _initialized: boolean = false; + constructor( + private readonly _enabled: boolean, + private readonly notebookEditor: IActiveNotebookEditorDelegate, + private readonly viewCell: CodeCellViewModel, + private readonly templateData: CodeCellRenderTemplate, + private readonly _logService: { debug: (output: string) => void }, + private readonly _initialEditorDimension: IDimension + ) { + } + /** + * Dynamically lays out the code cell's Monaco editor to simulate a "sticky" run/exec area while + * constraining the visible editor height to the notebook viewport. It adjusts two things: + * - The absolute `top` offset of the editor part inside the cell (so the run / execution order + * area remains visible for a limited vertical travel band ~45px). + * - The editor's layout height plus the editor's internal scroll position (`editorScrollTop`) to + * crop content when the cell is partially visible (top or bottom clipped) or when content is + * taller than the viewport. + * + * --------------------------------------------------------------------------- + * SECTION 1. OVERALL NOTEBOOK VIEW (EACH CELL HAS AN 18px GAP ABOVE IT) + * Legend: + * GAP (between cells & before first cell) ............. 18px + * CELL PADDING (top & bottom inside cell) ............. 6px + * STATUS BAR HEIGHT (typical) ......................... 22px + * LINE HEIGHT (logic clamp) ........................... 21px + * BORDER/OUTLINE HEIGHT (visual conceal adjustment) ... 1px + * EDITOR_HEIGHT (example visible editor) .............. 200px (capped by viewport) + * EDITOR_CONTENT_HEIGHT (example full content) ........ 380px (e.g. 50 lines) + * extraOffset = -(CELL_PADDING + BORDER_HEIGHT) ....... -7 + * + * (The list ensures the editor's laid out height never exceeds viewport height.) + * + * ┌────────────────────────────── Notebook Viewport (scrolling container) ────────────────────────────┐ + * │ (scrollTop) │ + * │ │ + * │ 18px GAP (top spacing before first cell) │ + * │ ▼ │ + * │ ┌──────── Cell A Outer Container ────────────────────────────────────────────────────────────┐ │ + * │ │ ▲ 6px top padding │ │ + * │ │ │ │ │ + * │ │ │ ┌─ Execution Order / Run Column (~45px vertical travel band)─┐ ┌─ Editor Part ───────┐ │ │ + * │ │ │ │ (Run button, execution # label) │ │ Visible Lines ... │ │ │ + * │ │ │ │ │ │ │ │ │ + * │ │ │ │ │ │ EDITOR_HEIGHT=200px │ │ │ + * │ │ │ │ │ │ (Content=380px) │ │ │ + * │ │ │ └────────────────────────────────────────────────────────────┘ └─────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ │ ┌─ Status Bar (22px) ─────────────────────────────────────────────────────────────────┐ │ │ + * │ │ │ │ language | indent | selection info | kernel/status bits ... │ │ │ + * │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ ▼ 6px bottom padding │ │ + * │ └────────────────────────────────────────────────────────────────────────────────────────────┘ │ + * │ 18px GAP │ + * │ ┌──────── Cell B Outer Container ────────────────────────────────────────────────────────────┐ │ + * │ │ (same structure as Cell A) │ │ + * │ └────────────────────────────────────────────────────────────────────────────────────────────┘ │ + * │ │ + * │ (scrollBottom) │ + * └───────────────────────────────────────────────────────────────────────────────────────────────────┘ + * + * SECTION 2. SINGLE CELL STRUCTURE (VERTICAL LAYERS) + * + * Inter-Cell GAP (18px) + * ┌─────────────────────────────── Cell Wrapper (

  • ) ──────────────────────────────┐ + * │ ┌──────────────────────────── .cell-inner-container ───────────────────────────┐ │ + * │ │ 6px top padding │ │ + * │ │ │ │ + * │ │ ┌─ Left Gutter (Run / Exec / Focus Border) ─┬──────── Editor Part ─────────┐ │ │ + * │ │ │ Sticky vertical travel (~45px allowance) │ (Monaco surface) │ │ │ + * │ │ │ │ Visible height 200px │ │ │ + * │ │ │ │ Content height 380px │ │ │ + * │ │ └─────────────────────────────────────────┴────────────────────────────────┘ │ │ + * │ │ │ │ + * │ │ ┌─ Status Bar (22px) ──────────────────────────────────────────────────────┐ │ │ + * │ │ │ language | indent | selection | kernel | state │ │ │ + * │ │ └──────────────────────────────────────────────────────────────────────────┘ │ │ + * │ │ 6px bottom padding │ │ + * │ └──────────────────────────────────────────────────────────────────────────────┘ │ + * │ (Outputs region begins at outputContainerOffset below input area) │ + * └──────────────────────────────────────────────────────────────────────────────────┘ + */ + public layoutEditor(reason: string) { + if (!this._enabled) { + return; + } + const element = this.templateData.editorPart; + if (this.viewCell.isInputCollapsed) { + element.style.top = ''; + return; + } + + const LINE_HEIGHT = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight; // 21; + const CELL_TOP_MARGIN = this.viewCell.layoutInfo.topMargin; + const CELL_OUTLINE_WIDTH = this.viewCell.layoutInfo.outlineWidth; // 1 extra px for border (we don't want to be able to see the cell border when scrolling up); + const STATUSBAR_HEIGHT = this.viewCell.layoutInfo.statusBarHeight; + + + const editor = this.templateData.editor; + const editorLayout = this.templateData.editor.getLayoutInfo(); + const editorHeight = this.viewCell.layoutInfo.editorHeight; + const scrollTop = this.notebookEditor.scrollTop; + const elementTop = this.notebookEditor.getAbsoluteTopOfElement(this.viewCell); + const elementBottom = this.notebookEditor.getAbsoluteBottomOfElement(this.viewCell); + const elementHeight = this.notebookEditor.getHeightOfElement(this.viewCell); + const gotContentHeight = editor.getContentHeight(); + const editorContentHeight = Math.max((gotContentHeight === -1 ? editor.getLayoutInfo().height : gotContentHeight), gotContentHeight === -1 ? this._initialEditorDimension.height : gotContentHeight); // || this.calculatedEditorHeight || 0; + const editorBottom = elementTop + this.viewCell.layoutInfo.outputContainerOffset; + const scrollBottom = this.notebookEditor.scrollBottom; + // When loading, scrollBottom -scrollTop === 0; + const viewportHeight = scrollBottom - scrollTop === 0 ? this.notebookEditor.getLayoutInfo().height : scrollBottom - scrollTop; + const outputContainerOffset = this.viewCell.layoutInfo.outputContainerOffset; + const scrollDirection: 'down' | 'up' = typeof this._previousScrollBottom === 'number' ? (scrollBottom < this._previousScrollBottom ? 'up' : 'down') : 'down'; + this._previousScrollBottom = scrollBottom; + + let top = Math.max(0, scrollTop - elementTop - CELL_TOP_MARGIN - CELL_OUTLINE_WIDTH); + const possibleEditorHeight = editorHeight - top; + if (possibleEditorHeight < LINE_HEIGHT) { + top = top - (LINE_HEIGHT - possibleEditorHeight) - CELL_OUTLINE_WIDTH; + } + + let height = editorContentHeight; + let editorScrollTop = 0; + if (scrollTop <= (elementTop + CELL_TOP_MARGIN)) { + const minimumEditorHeight = LINE_HEIGHT + this.notebookEditor.notebookOptions.getLayoutConfiguration().editorTopPadding; + if (scrollBottom >= editorBottom) { + height = clamp(editorContentHeight, minimumEditorHeight, editorContentHeight); + this._editorVisibility = 'Full'; + } else { + height = clamp(scrollBottom - (elementTop + CELL_TOP_MARGIN) - STATUSBAR_HEIGHT, minimumEditorHeight, editorContentHeight) + (2 * CELL_OUTLINE_WIDTH); // We don't want bottom border to be visible.; + this._editorVisibility = 'Bottom Clipped'; + editorScrollTop = 0; + } + } else { + if (viewportHeight <= editorContentHeight && scrollBottom <= editorBottom) { + const minimumEditorHeight = LINE_HEIGHT + this.notebookEditor.notebookOptions.getLayoutConfiguration().editorTopPadding; + height = clamp(viewportHeight - STATUSBAR_HEIGHT, minimumEditorHeight, editorContentHeight - STATUSBAR_HEIGHT) + (2 * CELL_OUTLINE_WIDTH); // We don't want bottom border to be visible. + this._editorVisibility = 'Full (Small Viewport)'; + editorScrollTop = top; + } else { + const minimumEditorHeight = LINE_HEIGHT; + height = clamp(editorContentHeight - (scrollTop - (elementTop + CELL_TOP_MARGIN)), minimumEditorHeight, editorContentHeight); + // Check if the cell is visible. + if (scrollTop > editorBottom) { + this._editorVisibility = 'Invisible'; + } else { + this._editorVisibility = 'Top Clipped'; + } + editorScrollTop = editorContentHeight - height; + } + } + + this._logService.debug(`${reason} (${this._editorVisibility})`); + this._logService.debug(`=> Editor Top = ${top}px (editHeight = ${editorHeight}, editContentHeight: ${editorContentHeight})`); + this._logService.debug(`=> eleTop = ${elementTop}, eleBottom = ${elementBottom}, eleHeight = ${elementHeight}`); + this._logService.debug(`=> scrollTop = ${scrollTop}, top = ${top}`); + this._logService.debug(`=> cellTopMargin = ${CELL_TOP_MARGIN}, cellBottomMargin = ${this.viewCell.layoutInfo.topMargin}, cellOutline = ${CELL_OUTLINE_WIDTH}`); + this._logService.debug(`=> scrollBottom: ${scrollBottom}, editBottom: ${editorBottom}, viewport: ${viewportHeight}, scroll: ${scrollDirection}, contOffset: ${outputContainerOffset})`); + this._logService.debug(`=> Editor Height = ${height}px, Width: ${editorLayout.width}px, Initial Width: ${this._initialEditorDimension.width}, EditorScrollTop = ${editorScrollTop}px, StatusbarHeight = ${STATUSBAR_HEIGHT}, lineHeight = ${this.notebookEditor.getLayoutInfo().fontInfo.lineHeight}`); + + try { + this._isUpdatingLayout = true; + element.style.top = `${top}px`; + editor.layout({ + width: this._initialized ? editorLayout.width : this._initialEditorDimension.width, + height + }, true); + if (editorScrollTop >= 0) { + this._lastChangedEditorScrolltop = editorScrollTop; + editor.setScrollTop(editorScrollTop); + } + } finally { + this._initialized = true; + this._isUpdatingLayout = false; + this._logService.debug('Updated Editor Layout'); + } + } +} diff --git a/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts new file mode 100644 index 00000000000..a9d87888531 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts @@ -0,0 +1,404 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from "assert"; +import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../../base/test/common/utils.js"; +import { CodeCellRenderTemplate } from "../../../browser/view/notebookRenderingCommon.js"; +import { CodeCellViewModel } from "../../../browser/viewModel/codeCellViewModel.js"; +import { CodeCellLayout } from "../../../browser/view/cellParts/codeCell.js"; +import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; +import { CodeCellLayoutInfo, IActiveNotebookEditorDelegate } from '../../../browser/notebookBrowser.js'; + +suite("CellPart", () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test("CodeCellLayout editor visibility states", () => { + /** + * We construct a very small mock around the parts that `CodeCellLayout` touches. The goal + * is to validate the branching logic that sets `_editorVisibility` without mutating any + * production code. Each scenario sets up geometry & scroll values then invokes + * `layoutEditor()` and asserts the resulting visibility classification. + */ + + interface TestScenario { + name: string; + scrollTop: number; + viewportHeight: number; + editorContentHeight: number; + editorHeight: number; // viewCell.layoutInfo.editorHeight + outputContainerOffset: number; // elementTop + this offset => editorBottom + expected: string; // CodeCellLayout.editorVisibility + postScrollTop?: number; // expected editor scrollTop written into stub editor + elementTop: number; // now scenario-specific for clarity + elementHeight: number; // scenario-specific container height + expectedTop: number; // expected computed CSS top (numeric px) + expectedEditorScrollTop: number; // expected argument passed to editor.setScrollTop + } + + const DEFAULT_ELEMENT_TOP = 100; // absolute top of the cell in notebook coordinates + const DEFAULT_ELEMENT_HEIGHT = 900; // arbitrary, large enough not to constrain + const STATUSBAR = 22; + const TOP_MARGIN = 6; // mirrors layoutInfo.topMargin usage + const OUTLINE = 1; + + const scenarios: TestScenario[] = [ + { + name: "Full", + scrollTop: 0, + viewportHeight: 400, + editorContentHeight: 300, + editorHeight: 300, + outputContainerOffset: 300, // editorBottom = 100 + 300 = 400, fully inside viewport (scrollBottom=400) + expected: "Full", + elementTop: DEFAULT_ELEMENT_TOP, + elementHeight: DEFAULT_ELEMENT_HEIGHT, + expectedTop: 0, + expectedEditorScrollTop: 0, + }, + { + name: "Bottom Clipped", + scrollTop: 0, + viewportHeight: 350, // scrollBottom=350 < editorBottom(400) + editorContentHeight: 300, + editorHeight: 300, + outputContainerOffset: 300, + expected: "Bottom Clipped", + elementTop: DEFAULT_ELEMENT_TOP, + elementHeight: DEFAULT_ELEMENT_HEIGHT, + expectedTop: 0, + expectedEditorScrollTop: 0, + }, + { + name: "Full (Small Viewport)", + scrollTop: DEFAULT_ELEMENT_TOP + TOP_MARGIN + 20, // scrolled into the cell body + viewportHeight: 220, // small vs content + editorContentHeight: 500, // larger than viewport so we clamp + editorHeight: 500, + outputContainerOffset: 600, // editorBottom=700 > scrollBottom + expected: "Full (Small Viewport)", + elementTop: DEFAULT_ELEMENT_TOP, + elementHeight: DEFAULT_ELEMENT_HEIGHT, + expectedTop: 19, // (scrollTop - elementTop - topMargin - outlineWidth) = (100+6+20 -100 -6 -1) + expectedEditorScrollTop: 19, + }, + { + name: "Top Clipped", + scrollTop: DEFAULT_ELEMENT_TOP + TOP_MARGIN + 40, // scrolled further down but not past bottom + viewportHeight: 600, // larger than content height below (forces branch for Top Clipped) + editorContentHeight: 200, + editorHeight: 200, + outputContainerOffset: 450, // editorBottom=550; scrollBottom= scrollTop+viewportHeight = > 550? (540+600=1140) but we only need scrollTop < editorBottom + expected: "Top Clipped", + elementTop: DEFAULT_ELEMENT_TOP, + elementHeight: DEFAULT_ELEMENT_HEIGHT, + expectedTop: 39, // (100+6+40 -100 -6 -1) + expectedEditorScrollTop: 40, // contentHeight(200) - computed height(160) + }, + { + name: "Invisible", + scrollTop: DEFAULT_ELEMENT_TOP + 1000, // well below editor bottom + viewportHeight: 400, + editorContentHeight: 300, + editorHeight: 300, + outputContainerOffset: 300, // editorBottom=400 < scrollTop + expected: "Invisible", + elementTop: DEFAULT_ELEMENT_TOP, + elementHeight: DEFAULT_ELEMENT_HEIGHT, + expectedTop: 278, // adjusted after ensuring minimum line height when possibleEditorHeight < LINE_HEIGHT + expectedEditorScrollTop: 279, // contentHeight(300) - clamped height(21) + }, + ]; + + for (const s of scenarios) { + // Fresh stub objects per scenario + const editorScrollState: { scrollTop: number } = { scrollTop: 0 }; + const stubEditor = { + layoutCalls: [] as { width: number; height: number }[], + _lastScrollTopSet: -1, + getLayoutInfo: () => ({ width: 600, height: s.editorHeight }), + getContentHeight: () => s.editorContentHeight, + layout: (dim: { width: number; height: number }) => { + stubEditor.layoutCalls.push(dim); + }, + setScrollTop: (v: number) => { + editorScrollState.scrollTop = v; + stubEditor._lastScrollTopSet = v; + }, + hasModel: () => true, + }; + + const editorPart = { style: { top: "" } }; + const template: Partial = { + editor: stubEditor as unknown as ICodeEditor, + editorPart: editorPart as unknown as HTMLElement, + }; + + // viewCell stub with only needed pieces + const viewCell: Partial = { + isInputCollapsed: false, + layoutInfo: { + // values referenced in layout logic + statusBarHeight: STATUSBAR, + topMargin: TOP_MARGIN, + outlineWidth: OUTLINE, + editorHeight: s.editorHeight, + outputContainerOffset: s.outputContainerOffset, + } as unknown as CodeCellLayoutInfo, + }; + + // notebook editor stub + let scrollBottom = s.scrollTop + s.viewportHeight; + const notebookEditor = { + scrollTop: s.scrollTop, + get scrollBottom() { + return scrollBottom; + }, + setScrollTop: (v: number) => { + notebookEditor.scrollTop = v; + scrollBottom = v + s.viewportHeight; + }, + getLayoutInfo: () => ({ + fontInfo: { lineHeight: 21 }, + height: s.viewportHeight, + stickyHeight: 0, + }), + getAbsoluteTopOfElement: () => s.elementTop, + getAbsoluteBottomOfElement: () => + s.elementTop + s.outputContainerOffset, + getHeightOfElement: () => s.elementHeight, + notebookOptions: { + getLayoutConfiguration: () => ({ editorTopPadding: 6 }), + }, + }; + + const layout = new CodeCellLayout( + /* enabled */ true, + notebookEditor as unknown as IActiveNotebookEditorDelegate, + viewCell as CodeCellViewModel, + template as CodeCellRenderTemplate, + { + debug: () => { + /* no-op */ + }, + }, + { width: 600, height: s.editorHeight } + ); + + layout.layoutEditor(s.name); + assert.strictEqual( + layout.editorVisibility, + s.expected, + `Scenario '${s.name}' (scrollTop=${s.scrollTop}) expected visibility ${s.expected} but got ${layout.editorVisibility}` + ); + const actualTop = parseInt( + (editorPart.style.top || "0").replace(/px$/, "") + ); // style.top always like 'NNNpx' + assert.strictEqual( + actualTop, + s.expectedTop, + `Scenario '${s.name}' (scrollTop=${s.scrollTop}) expected top ${s.expectedTop}px but got ${editorPart.style.top}` + ); + assert.strictEqual( + stubEditor._lastScrollTopSet, + s.expectedEditorScrollTop, + `Scenario '${s.name}' (scrollTop=${s.scrollTop}) expected editor.setScrollTop(${s.expectedEditorScrollTop}) but got ${stubEditor._lastScrollTopSet}` + ); + + // Basic sanity: style.top should always be set when visible states other than Full (handled) or Invisible. + if (s.expected !== "Invisible") { + assert.notStrictEqual( + editorPart.style.top, + "", + `Scenario '${s.name}' should set a top style value` + ); + } else { + // Invisible still sets a top; just ensure layout ran + assert.ok( + editorPart.style.top !== undefined, + "Invisible scenario still performs a layout" + ); + } + } + }); + + test("Scrolling", () => { + /** + * Pixel-by-pixel scroll test to validate `CodeCellLayout` calculations for: + * - editorPart.style.top + * - editorVisibility classification + * - editor internal scrollTop passed to setScrollTop + * + * We intentionally mirror the production math in a helper (duplication acceptable in test) so + * that any divergence is caught. Constants chosen to exercise all state transitions. + */ + const LINE_HEIGHT = 21; // from getLayoutInfo().fontInfo.lineHeight in stubs + const CELL_TOP_MARGIN = 6; + const CELL_OUTLINE_WIDTH = 1; + const STATUSBAR_HEIGHT = 22; + const VIEWPORT_HEIGHT = 300; // notebook viewport height + const ELEMENT_TOP = 100; // absolute top + const EDITOR_CONTENT_HEIGHT = 800; // tall content so we get clipping and small viewport states + const EDITOR_HEIGHT = EDITOR_CONTENT_HEIGHT; // initial layoutInfo.editorHeight + const OUTPUT_CONTAINER_OFFSET = 800; // bottom of editor region relative to elementTop + const ELEMENT_HEIGHT = 1200; // large container + + function clamp(v: number, min: number, max: number) { + return Math.min(Math.max(v, min), max); + } + + function computeExpected(scrollTop: number) { + const scrollBottom = scrollTop + VIEWPORT_HEIGHT; + const viewportHeight = VIEWPORT_HEIGHT; + const editorBottom = ELEMENT_TOP + OUTPUT_CONTAINER_OFFSET; + let top = Math.max( + 0, + scrollTop - ELEMENT_TOP - CELL_TOP_MARGIN - CELL_OUTLINE_WIDTH + ); + const possibleEditorHeight = EDITOR_HEIGHT - top; + if (possibleEditorHeight < LINE_HEIGHT) { + top = top - (LINE_HEIGHT - possibleEditorHeight) - CELL_OUTLINE_WIDTH; + } + let height = EDITOR_CONTENT_HEIGHT; + let visibility: string = "Full"; + let editorScrollTop = 0; + if (scrollTop <= ELEMENT_TOP + CELL_TOP_MARGIN) { + const minimumEditorHeight = LINE_HEIGHT + 6; // editorTopPadding from configuration stub (6) + if (scrollBottom >= editorBottom) { + height = clamp( + EDITOR_CONTENT_HEIGHT, + minimumEditorHeight, + EDITOR_CONTENT_HEIGHT + ); + visibility = "Full"; + } else { + height = + clamp( + scrollBottom - (ELEMENT_TOP + CELL_TOP_MARGIN) - STATUSBAR_HEIGHT, + minimumEditorHeight, + EDITOR_CONTENT_HEIGHT + ) + + 2 * CELL_OUTLINE_WIDTH; + visibility = "Bottom Clipped"; + editorScrollTop = 0; + } + } else { + if ( + viewportHeight <= EDITOR_CONTENT_HEIGHT && + scrollBottom <= editorBottom + ) { + const minimumEditorHeight = LINE_HEIGHT + 6; // editorTopPadding + height = + clamp( + viewportHeight - STATUSBAR_HEIGHT, + minimumEditorHeight, + EDITOR_CONTENT_HEIGHT - STATUSBAR_HEIGHT + ) + + 2 * CELL_OUTLINE_WIDTH; + visibility = "Full (Small Viewport)"; + editorScrollTop = top; + } else { + const minimumEditorHeight = LINE_HEIGHT; + height = clamp( + EDITOR_CONTENT_HEIGHT - + (scrollTop - (ELEMENT_TOP + CELL_TOP_MARGIN)), + minimumEditorHeight, + EDITOR_CONTENT_HEIGHT + ); + if (scrollTop > editorBottom) { + visibility = "Invisible"; + } else { + visibility = "Top Clipped"; + } + editorScrollTop = EDITOR_CONTENT_HEIGHT - height; + } + } + return { top, visibility, editorScrollTop }; + } + + // Shared stubs (we'll mutate scrollTop each iteration) – we re-create layout each iteration to reset internal state changes + for ( + let scrollTop = 0; + scrollTop <= VIEWPORT_HEIGHT + OUTPUT_CONTAINER_OFFSET + 20; + scrollTop++ + ) { + const expected = computeExpected(scrollTop); + const scrollBottom = scrollTop + VIEWPORT_HEIGHT; + const stubEditor = { + _lastScrollTopSet: -1, + getLayoutInfo: () => ({ width: 600, height: EDITOR_HEIGHT }), + getContentHeight: () => EDITOR_CONTENT_HEIGHT, + layout: () => { + /* no-op */ + }, + setScrollTop: (v: number) => { + stubEditor._lastScrollTopSet = v; + }, + hasModel: () => true, + }; + const editorPart = { style: { top: "" } }; + const template: Partial = { + editor: stubEditor as unknown as ICodeEditor, + editorPart: editorPart as unknown as HTMLElement, + }; + const viewCell: Partial = { + isInputCollapsed: false, + layoutInfo: { + statusBarHeight: STATUSBAR_HEIGHT, + topMargin: CELL_TOP_MARGIN, + outlineWidth: CELL_OUTLINE_WIDTH, + editorHeight: EDITOR_HEIGHT, + outputContainerOffset: OUTPUT_CONTAINER_OFFSET, + } as unknown as CodeCellLayoutInfo, + }; + const notebookEditor = { + scrollTop, + get scrollBottom() { + return scrollBottom; + }, + setScrollTop: (v: number) => { + /* notebook scroll changes are not the focus here */ + }, + getLayoutInfo: () => ({ + fontInfo: { lineHeight: LINE_HEIGHT }, + height: VIEWPORT_HEIGHT, + stickyHeight: 0, + }), + getAbsoluteTopOfElement: () => ELEMENT_TOP, + getAbsoluteBottomOfElement: () => ELEMENT_TOP + OUTPUT_CONTAINER_OFFSET, + getHeightOfElement: () => ELEMENT_HEIGHT, + notebookOptions: { + getLayoutConfiguration: () => ({ editorTopPadding: 6 }), + }, + }; + const layout = new CodeCellLayout( + true, + notebookEditor as unknown as IActiveNotebookEditorDelegate, + viewCell as CodeCellViewModel, + template as CodeCellRenderTemplate, + { debug: () => { } }, + { width: 600, height: EDITOR_HEIGHT } + ); + layout.layoutEditor("scroll"); + const actualTop = parseInt( + (editorPart.style.top || "0").replace(/px$/, "") + ); + assert.strictEqual( + actualTop, + expected.top, + `scrollTop=${scrollTop}: expected top ${expected.top}, got ${actualTop}` + ); + assert.strictEqual( + layout.editorVisibility, + expected.visibility, + `scrollTop=${scrollTop}: expected visibility ${expected.visibility}, got ${layout.editorVisibility}` + ); + assert.strictEqual( + stubEditor._lastScrollTopSet, + expected.editorScrollTop, + `scrollTop=${scrollTop}: expected editorScrollTop ${expected.editorScrollTop}, got ${stubEditor._lastScrollTopSet}` + ); + } + }); +}); From 51d83e06c3fc630ef2d4f02bd2712668927553f5 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:21:14 -0700 Subject: [PATCH 1076/4355] Fix compile --- build/lib/standalone.js | 13 ++++++------- build/lib/standalone.ts | 13 ++++++------- build/lib/treeshaking.ts | 2 -- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 21271247c38..270ae2d3d58 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -90,19 +90,19 @@ function extractEditor(options) { } } const copied = {}; - const copyFile = (fileName) => { + const copyFile = (fileName, toFileName) => { if (copied[fileName]) { return; } copied[fileName] = true; if (path_1.default.isAbsolute(fileName)) { const relativePath = path_1.default.relative(options.sourcesRoot, fileName); - const dstPath = path_1.default.join(options.destRoot, relativePath); + const dstPath = path_1.default.join(options.destRoot, toFileName ?? relativePath); writeFile(dstPath, fs_1.default.readFileSync(fileName)); } else { const srcPath = path_1.default.join(options.sourcesRoot, fileName); - const dstPath = path_1.default.join(options.destRoot, fileName); + const dstPath = path_1.default.join(options.destRoot, toFileName ?? fileName); writeFile(dstPath, fs_1.default.readFileSync(srcPath)); } }; @@ -134,10 +134,9 @@ function extractEditor(options) { } delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); - [ - 'vs/loader.js', - 'typings/css.d.ts' - ].forEach(copyFile); + copyFile('vs/loader.js'); + copyFile('typings/css.d.ts'); + copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts'); } function transportCSS(module, enqueue, write) { if (!/\.css/.test(module)) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 4534dee4f0c..9bbcbae94f8 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -62,7 +62,7 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str } } const copied: { [fileName: string]: boolean } = {}; - const copyFile = (fileName: string) => { + const copyFile = (fileName: string, toFileName?: string) => { if (copied[fileName]) { return; } @@ -70,11 +70,11 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str if (path.isAbsolute(fileName)) { const relativePath = path.relative(options.sourcesRoot, fileName); - const dstPath = path.join(options.destRoot, relativePath); + const dstPath = path.join(options.destRoot, toFileName ?? relativePath); writeFile(dstPath, fs.readFileSync(fileName)); } else { const srcPath = path.join(options.sourcesRoot, fileName); - const dstPath = path.join(options.destRoot, fileName); + const dstPath = path.join(options.destRoot, toFileName ?? fileName); writeFile(dstPath, fs.readFileSync(srcPath)); } }; @@ -110,10 +110,9 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); - [ - 'vs/loader.js', - 'typings/css.d.ts' - ].forEach(copyFile); + copyFile('vs/loader.js'); + copyFile('typings/css.d.ts'); + copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts'); } function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index df0f288fc30..37572229f01 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -55,8 +55,6 @@ export interface ITreeShakingOptions { * regex pattern to ignore certain imports e.g. `.css` imports */ importIgnorePattern: RegExp; - - // redirects: { [module: string]: string }; } export interface ITreeShakingResult { From df89e361191237deb10c1894808c8a0c235d33ae Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:29:56 -0700 Subject: [PATCH 1077/4355] Update monaco output --- src/vs/monaco.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6ecf342cb0b..cad4767ed96 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5356,7 +5356,7 @@ declare namespace monaco.editor { renderWhitespace: IEditorOption; revealHorizontalRightPadding: IEditorOption; roundedSelection: IEditorOption; - rulers: IEditorOption; + rulers: IEditorOption; scrollbar: IEditorOption; scrollBeyondLastColumn: IEditorOption; scrollBeyondLastLine: IEditorOption; @@ -5385,12 +5385,12 @@ declare namespace monaco.editor { tabCompletion: IEditorOption; tabIndex: IEditorOption; trimWhitespaceOnDelete: IEditorOption; - unicodeHighlight: IEditorOption; + unicodeHighlight: IEditorOption>>; unusualLineTerminators: IEditorOption; useShadowDOM: IEditorOption; useTabStops: IEditorOption; wordBreak: IEditorOption; - wordSegmenterLocales: IEditorOption; + wordSegmenterLocales: IEditorOption; wordSeparators: IEditorOption; wordWrap: IEditorOption; wordWrapBreakAfterCharacters: IEditorOption; From 2a4a9b7a66c095ee07079fe2ff83403628ccb302 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:43:58 -0700 Subject: [PATCH 1078/4355] Support new fs typings --- .../workbench/services/dialogs/browser/fileDialogService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 70d9dc6f979..f75fa27784e 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -142,10 +142,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil private getFilePickerTypes(filters?: FileFilter[]): FilePickerAcceptType[] | undefined { return filters?.filter(filter => { return !((filter.extensions.length === 1) && ((filter.extensions[0] === '*') || filter.extensions[0] === '')); - }).map(filter => { - const accept: Record = {}; + }).map((filter): FilePickerAcceptType => { + const accept: Record = {}; const extensions = filter.extensions.filter(ext => (ext.indexOf('-') < 0) && (ext.indexOf('*') < 0) && (ext.indexOf('_') < 0)); - accept[getMediaOrTextMime(`fileName.${filter.extensions[0]}`) ?? 'text/plain'] = extensions.map(ext => ext.startsWith('.') ? ext : `.${ext}`); + accept[(getMediaOrTextMime(`fileName.${filter.extensions[0]}`) ?? 'text/plain') as MIMEType] = extensions.map(ext => ext.startsWith('.') ? ext : `.${ext}`) as FileExtension[]; return { description: filter.name, accept From 48884a0dc00822f4d205896d28425c38b51ebba3 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:59:31 -0700 Subject: [PATCH 1079/4355] Use nodenext for module too --- src/tsconfig.monaco.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index ade7058f149..cd3d0d860b9 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -7,8 +7,7 @@ "trusted-types", "wicg-file-system-access" ], - "paths": {}, - "module": "esnext", + "module": "nodenext", "moduleResolution": "nodenext", "removeComments": false, "preserveConstEnums": true, From 6c42761e58ff1579eb16c02f286573a22e501639 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 23:46:42 -0700 Subject: [PATCH 1080/4355] Copy out marked and dompurify too --- build/gulpfile.editor.js | 10 +++++++--- build/lib/standalone.js | 3 +++ build/lib/standalone.ts | 6 +++++- build/lib/treeshaking.js | 14 ++++++++++---- build/lib/treeshaking.ts | 15 +++++++++++---- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 6d8ff1d3745..1136430dd99 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -41,15 +41,19 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { standalone.extractEditor({ sourcesRoot: path.join(root, 'src'), entryPoints: [ - 'vs/editor/editor.main', - 'vs/editor/editor.worker.start', - 'vs/editor/common/services/editorWebWorkerMain', + 'vs/editor/editor.main.ts', + 'vs/editor/editor.worker.start.ts', + 'vs/editor/common/services/editorWebWorkerMain.ts', ], inlineEntryPoints: [ apiusages, extrausages ], typings: [], + additionalFilesToCopyOut: [ + 'vs/base/browser/dompurify/dompurify.js', + 'vs/base/common/marked/marked.js', + ], shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /\.css$/, destRoot: path.join(root, 'out-editor-src'), diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 270ae2d3d58..b153e70348b 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -134,6 +134,9 @@ function extractEditor(options) { } delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + options.additionalFilesToCopyOut?.forEach((file) => { + copyFile(file); + }); copyFile('vs/loader.js'); copyFile('typings/css.d.ts'); copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts'); diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 9bbcbae94f8..3f62e4f1e40 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -26,7 +26,7 @@ function writeFile(filePath: string, contents: Buffer | string): void { fs.writeFileSync(filePath, contents); } -export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string }): void { +export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string; additionalFilesToCopyOut?: string[]; }): void { const ts = require('typescript') as typeof import('typescript'); const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); @@ -110,6 +110,10 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + options.additionalFilesToCopyOut?.forEach((file) => { + copyFile(file); + }); + copyFile('vs/loader.js'); copyFile('typings/css.d.ts'); copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts'); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index c78cd40e7d9..a679446f60b 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -70,7 +70,7 @@ function createTypeScriptLanguageService(ts, options) { const FILES = new Map(); // Add entrypoints options.entryPoints.forEach(entryPoint => { - const filePath = path_1.default.join(options.sourcesRoot, `${entryPoint}.ts`); + const filePath = path_1.default.join(options.sourcesRoot, entryPoint); FILES.set(filePath, fs_1.default.readFileSync(filePath).toString()); }); // Add fake usage files @@ -327,14 +327,20 @@ function markNodes(ts, languageService, options) { if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension importText = importText.substr(0, importText.length - 3); } - fullPath = path_1.default.join(path_1.default.dirname(nodeSourceFile.fileName), importText) + '.ts'; + fullPath = path_1.default.join(path_1.default.dirname(nodeSourceFile.fileName), importText); } else { - fullPath = importText + '.ts'; + fullPath = importText; + } + if (fs_1.default.existsSync(fullPath + '.ts')) { + fullPath = fullPath + '.ts'; + } + else { + fullPath = fullPath + '.js'; } enqueueFile(fullPath); } - options.entryPoints.forEach(moduleId => enqueueFile(path_1.default.join(options.sourcesRoot, moduleId + '.ts'))); + options.entryPoints.forEach(moduleId => enqueueFile(path_1.default.join(options.sourcesRoot, moduleId))); // Add fake usage files options.inlineEntryPoints.forEach((_, index) => enqueueFile(path_1.default.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`))); let step = 0; diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 37572229f01..9c2fcc11925 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -111,7 +111,7 @@ function createTypeScriptLanguageService(ts: typeof import('typescript'), option // Add entrypoints options.entryPoints.forEach(entryPoint => { - const filePath = path.join(options.sourcesRoot, `${entryPoint}.ts`); + const filePath = path.join(options.sourcesRoot, entryPoint); FILES.set(filePath, fs.readFileSync(filePath).toString()); }); @@ -423,14 +423,21 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension importText = importText.substr(0, importText.length - 3); } - fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; + fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText); } else { - fullPath = importText + '.ts'; + fullPath = importText; } + + if (fs.existsSync(fullPath + '.ts')) { + fullPath = fullPath + '.ts'; + } else { + fullPath = fullPath + '.js'; + } + enqueueFile(fullPath); } - options.entryPoints.forEach(moduleId => enqueueFile(path.join(options.sourcesRoot, moduleId + '.ts'))); + options.entryPoints.forEach(moduleId => enqueueFile(path.join(options.sourcesRoot, moduleId))); // Add fake usage files options.inlineEntryPoints.forEach((_, index) => enqueueFile(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`))); From cc51a6897aa1d27ca9731b49ae38cad08fa4d811 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Sun, 12 Oct 2025 23:54:53 -0700 Subject: [PATCH 1081/4355] Fix lint error --- build/lib/standalone.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 3f62e4f1e40..5f2104cb4c6 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -26,7 +26,7 @@ function writeFile(filePath: string, contents: Buffer | string): void { fs.writeFileSync(filePath, contents); } -export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string; additionalFilesToCopyOut?: string[]; }): void { +export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string; additionalFilesToCopyOut?: string[] }): void { const ts = require('typescript') as typeof import('typescript'); const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); From 79ff5507aaf5ee366ed5f16608f698e88dea8880 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:06:59 +0800 Subject: [PATCH 1082/4355] some thinking part fixes for gemini 2.5 (#271064) --- .../chatContentParts/chatThinkingContentPart.ts | 12 +++--------- .../contrib/chat/browser/chatListRenderer.ts | 6 ++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index e9d623d1601..25cb28515ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -347,15 +347,9 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen const otherId = other?.id; const thisId = this.id; - // one off case where we have no ids, we compare text instead. - if (otherId === undefined && thisId === undefined) { - const rawValue = other.value; - const otherValueStr = typeof rawValue === 'string' ? rawValue : Array.isArray(rawValue) ? rawValue.join('') : ''; - const otherValueNormalized = otherValueStr.trim(); - return this.parseContent(otherValueNormalized) === this.currentThinkingValue; - } - - return otherId !== thisId; + const otherValueRaw = Array.isArray(other.value) ? other.value.join('') : (other.value ?? ''); + const isEqual = this.parseContent(otherValueRaw.trim()) === this.currentThinkingValue; + return isEqual || otherId !== thisId; } override dispose(): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f9cfef850e9..1e454c1e64f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -289,6 +289,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Mon, 13 Oct 2025 10:38:53 +0200 Subject: [PATCH 1083/4355] New chat dropdown alt behaviour (fix #271077) (#271083) --- .../contrib/chat/browser/actions/chatNewActions.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts index 83f3f8c6d62..3f3fa75481f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts @@ -20,7 +20,7 @@ import { ChatModeKind } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { EditingSessionAction, getEditingSessionContext } from '../chatEditing/chatEditingActions.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_OPEN_CHAT, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js'; +import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js'; import { clearChatEditor } from './chatClear.js'; export interface INewEditSessionActionContext { @@ -93,11 +93,6 @@ export function registerNewChatActions() { id: MenuId.ChatNewMenu, group: '1_open', order: 1, - alt: { - id: ACTION_ID_OPEN_CHAT, - title: localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.newFile - } }, ...[MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].map(id => ({ id, From 5dfa168f84d8bb74b51f0df23a4a0d2fc70aead8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Oct 2025 12:42:45 +0200 Subject: [PATCH 1084/4355] adopt to latest version of mcp registry (#271087) --- .../platform/mcp/common/mcpGalleryManifest.ts | 3 +- .../mcp/common/mcpGalleryManifestService.ts | 8 +- .../platform/mcp/common/mcpGalleryService.ts | 131 +++++------------- src/vs/platform/mcp/common/mcpManagement.ts | 7 +- .../mcp/common/mcpManagementService.ts | 13 +- .../platform/mcp/node/mcpManagementService.ts | 4 +- .../contrib/mcp/browser/mcpServerEditor.ts | 6 +- .../mcp/browser/mcpWorkbenchService.ts | 36 ++--- .../workbench/contrib/mcp/common/mcpTypes.ts | 1 - 9 files changed, 70 insertions(+), 139 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index 9c108ab7780..adb6659341e 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -10,7 +10,8 @@ export const enum McpGalleryResourceType { McpServersQueryService = 'McpServersQueryService', McpServersSearchService = 'McpServersSearchService', McpServerWebUri = 'McpServerWebUriTemplate', - McpServerResourceUri = 'McpServerResourceUriTemplate', + McpServerVersionUri = 'McpServerVersionUriTemplate', + McpServerLatestVersionUri = 'McpServerLatestVersionUriTemplate', McpServerNamedResourceUri = 'McpServerNamedResourceUriTemplate', PublisherUriTemplate = 'PublisherUriTemplate', ContactSupportUri = 'ContactSupportUri', diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index 7557e32fea0..c0424d3f3eb 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -42,8 +42,12 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery type: McpGalleryResourceType.McpServersQueryService }, { - id: `${serversUrl}/{id}`, - type: McpGalleryResourceType.McpServerResourceUri + id: `${serversUrl}/{name}/versions/{version}`, + type: McpGalleryResourceType.McpServerVersionUri + }, + { + id: `${serversUrl}/{name}/versions/latest`, + type: McpGalleryResourceType.McpServerLatestVersionUri } ]; diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index bc632213111..dc097b71ffb 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -13,16 +13,14 @@ import { localize } from '../../../nls.js'; import { IFileService } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; import { asJson, asText, IRequestService } from '../../request/common/request.js'; -import { GalleryMcpServerStatus, IGalleryMcpServer, IGalleryMcpServerConfiguration, IMcpGalleryService, IMcpServerArgument, IMcpServerInput, IMcpServerKeyValueInput, IMcpServerPackage, IQueryOptions, RegistryType, SseTransport, StreamableHttpTransport, Transport, TransportType } from './mcpManagement.js'; +import { GalleryMcpServerStatus, IGalleryMcpServer, IMcpGalleryService, IMcpServerArgument, IMcpServerInput, IMcpServerKeyValueInput, IMcpServerPackage, IQueryOptions, RegistryType, SseTransport, StreamableHttpTransport, Transport, TransportType } from './mcpManagement.js'; import { IMcpGalleryManifestService, McpGalleryManifestStatus, getMcpGalleryManifestResourceUri, McpGalleryResourceType, IMcpGalleryManifest } from './mcpGalleryManifest.js'; import { IPageIterator, IPager, PageIteratorPager, singlePagePager } from '../../../base/common/paging.js'; import { CancellationError } from '../../../base/common/errors.js'; -import { basename } from '../../../base/common/path.js'; interface IMcpRegistryInfo { - readonly id: string; readonly isLatest: boolean; - readonly publishedAt?: string; + readonly publishedAt: string; readonly updatedAt: string; } @@ -355,7 +353,6 @@ namespace McpServerSchemaVersion_2025_07_09 { }; }), registryInfo: { - id: registryInfo.id, isLatest: registryInfo.is_latest, publishedAt: registryInfo.published_at, updatedAt: registryInfo.updated_at, @@ -464,12 +461,10 @@ namespace McpServerSchemaVersion_2025_29_09 { readonly server: RawGalleryMcpServer; readonly _meta: { readonly 'io.modelcontextprotocol.registry/official': { - readonly id: string; + readonly status: GalleryMcpServerStatus; readonly isLatest: boolean; readonly publishedAt: string; readonly updatedAt: string; - readonly releaseDate?: string; - readonly status?: GalleryMcpServerStatus; }; }; } @@ -629,19 +624,15 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService }); } - async getMcpServersFromGallery(urls: string[]): Promise { + async getMcpServersFromGallery(names: string[]): Promise { const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); if (!mcpGalleryManifest) { return []; } const mcpServers: IGalleryMcpServer[] = []; - await Promise.allSettled(urls.map(async url => { - const mcpServerUrl = this.getServerUrl(basename(url), mcpGalleryManifest); - if (mcpServerUrl !== url) { - return; - } - const mcpServer = await this.getMcpServer(mcpServerUrl); + await Promise.allSettled(names.map(async name => { + const mcpServer = await this.getMcpServerByName(name, mcpGalleryManifest); if (mcpServer) { mcpServers.push(mcpServer); } @@ -650,36 +641,21 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return mcpServers; } - async getMcpServerConfiguration(gallery: IGalleryMcpServer, token: CancellationToken): Promise { - if (gallery.configuration) { - return gallery.configuration; - } - - if (!gallery.url) { - throw new Error(`No manifest URL found for ${gallery.name}`); - } - - const context = await this.requestService.request({ - type: 'GET', - url: gallery.url, - }, token); - - const result = await asJson(context); - if (!result) { - throw new Error(`Failed to fetch configuration from ${gallery.url}`); - } - - const server = this.serializeMcpServer(result); - if (!server) { - throw new Error(`Failed to serialize MCP server data from ${gallery.url}`, result); + private async getMcpServerByName(name: string, mcpGalleryManifest: IMcpGalleryManifest): Promise { + const mcpServerUrl = this.getLatestServerVersionUrl(name, mcpGalleryManifest); + if (mcpServerUrl) { + const mcpServer = await this.getMcpServer(mcpServerUrl); + if (mcpServer) { + return mcpServer; + } } - const configuration = this.toGalleryMcpServerConfiguration(server.packages, server.remotes); - if (!configuration) { - throw new Error(`Failed to fetch configuration for ${gallery.url}`); + const byNameUrl = this.getNamedServerUrl(name, mcpGalleryManifest); + if (byNameUrl) { + return this.getMcpServer(byNameUrl); } - return configuration; + return undefined; } async getReadme(gallery: IGalleryMcpServer, token: CancellationToken): Promise { @@ -746,15 +722,13 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService dark: server.githubInfo.owner_avatar_url } : undefined; - const serverUrl = manifest ? this.getServerUrl(server.registryInfo.id, manifest) : undefined; const webUrl = manifest ? this.getWebUrl(server.name, manifest) : undefined; const publisherUrl = manifest ? this.getPublisherUrl(publisher, manifest) : undefined; return { - id: server.registryInfo.id, name: server.name, displayName, - url: serverUrl, + galleryUrl: manifest?.url, webUrl, description: server.description, status: server.status ?? GalleryMcpServerStatus.Active, @@ -770,17 +744,10 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService license: server.githubInfo?.license, starsCount: server.githubInfo?.stargazer_count, topics: server.githubInfo?.topics, - configuration: this.toGalleryMcpServerConfiguration(server.packages, server.remotes) - }; - } - - private toGalleryMcpServerConfiguration(packages?: readonly IMcpServerPackage[], remotes?: ReadonlyArray): IGalleryMcpServerConfiguration | undefined { - if (!packages && !remotes) { - return undefined; - } - return { - packages, - remotes + configuration: { + packages: server.packages, + remotes: server.remotes + } }; } @@ -860,45 +827,9 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService if (!mcpGalleryManifest) { mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); - if (mcpGalleryManifest && mcpServerUrl !== this.getServerUrl(basename(mcpServerUrl), mcpGalleryManifest)) { - mcpGalleryManifest = null; - } } - return this.toGalleryMcpServer(server, mcpGalleryManifest); - } - - async getMcpServerByName(name: string): Promise { - const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); - if (!mcpGalleryManifest) { - return undefined; - } - - const mcpServerUrl = this.getNamedServerUrl(name, mcpGalleryManifest); - if (!mcpServerUrl) { - return undefined; - } - - const context = await this.requestService.request({ - type: 'GET', - url: mcpServerUrl, - }, CancellationToken.None); - - if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) { - return undefined; - } - - const data = await asJson(context); - if (!data) { - return undefined; - } - - const server = this.serializeMcpServer(data); - if (!server) { - throw new Error(`Failed to serialize MCP server from ${mcpServerUrl}`, data); - } - - return this.toGalleryMcpServer(server, mcpGalleryManifest); + return this.toGalleryMcpServer(server, mcpGalleryManifest && mcpServerUrl.startsWith(mcpGalleryManifest.url) ? mcpGalleryManifest : null); } private serializeMcpServer(data: unknown): IRawGalleryMcpServer | undefined { @@ -921,14 +852,6 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return undefined; } - private getServerUrl(id: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { - const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerResourceUri); - if (!resourceUriTemplate) { - return undefined; - } - return format2(resourceUriTemplate, { id }); - } - private getNamedServerUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { const namedResourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerNamedResourceUri); if (!namedResourceUriTemplate) { @@ -937,6 +860,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return format2(namedResourceUriTemplate, { name }); } + private getLatestServerVersionUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + const latestVersionResourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerLatestVersionUri); + if (!latestVersionResourceUriTemplate) { + return undefined; + } + return format2(latestVersionResourceUriTemplate, { name }); + } + private getSearchUrl(mcpGalleryManifest: IMcpGalleryManifest): string | undefined { return getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServersSearchService); } diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index f6c64204ef9..8cf71bb530c 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -126,14 +126,13 @@ export const enum GalleryMcpServerStatus { } export interface IGalleryMcpServer { - readonly id: string; readonly name: string; readonly displayName: string; readonly description: string; readonly version: string; readonly isLatest: boolean; readonly status: GalleryMcpServerStatus; - readonly url?: string; + readonly galleryUrl?: string; readonly webUrl?: string; readonly codicon?: string; readonly icon?: { @@ -143,7 +142,7 @@ export interface IGalleryMcpServer { readonly lastUpdated?: number; readonly publishDate?: number; readonly repositoryUrl?: string; - readonly configuration?: IGalleryMcpServerConfiguration; + readonly configuration: IGalleryMcpServerConfiguration; readonly readmeUrl?: string; readonly readme?: string; readonly publisher: string; @@ -169,8 +168,6 @@ export interface IMcpGalleryService { query(options?: IQueryOptions, token?: CancellationToken): Promise>; getMcpServersFromGallery(urls: string[]): Promise; getMcpServer(url: string): Promise; - getMcpServerByName(name: string): Promise; - getMcpServerConfiguration(extension: IGalleryMcpServer, token: CancellationToken): Promise; getReadme(extension: IGalleryMcpServer, token: CancellationToken): Promise; } diff --git a/src/vs/platform/mcp/common/mcpManagementService.ts b/src/vs/platform/mcp/common/mcpManagementService.ts index 6d67ad27850..8c9cff671fe 100644 --- a/src/vs/platform/mcp/common/mcpManagementService.ts +++ b/src/vs/platform/mcp/common/mcpManagementService.ts @@ -28,7 +28,6 @@ import { IMcpResourceScannerService, McpResourceTarget } from './mcpResourceScan export interface ILocalMcpServerInfo { name: string; version?: string; - id?: string; displayName?: string; galleryUrl?: string; description?: string; @@ -510,12 +509,11 @@ export class McpUserResourceManagementService extends AbstractMcpResourceManagem } protected async updateMetadataFromGallery(gallery: IGalleryMcpServer): Promise { - const manifest = await this.mcpGalleryService.getMcpServerConfiguration(gallery, CancellationToken.None); + const manifest = gallery.configuration; const location = this.getLocation(gallery.name, gallery.version); const manifestPath = this.uriIdentityService.extUri.joinPath(location, 'manifest.json'); const local: ILocalMcpServerInfo = { - id: gallery.id, - galleryUrl: gallery.url, + galleryUrl: gallery.galleryUrl, name: gallery.name, displayName: gallery.displayName, description: gallery.description, @@ -548,6 +546,13 @@ export class McpUserResourceManagementService extends AbstractMcpResourceManagem try { const content = await this.fileService.readFile(manifestLocation); storedMcpServerInfo = JSON.parse(content.value.toString()) as ILocalMcpServerInfo; + + // migrate + if (storedMcpServerInfo.galleryUrl?.includes('/v0/')) { + storedMcpServerInfo.galleryUrl = storedMcpServerInfo.galleryUrl.substring(0, storedMcpServerInfo.galleryUrl.indexOf('/v0/')); + await this.fileService.writeFile(manifestLocation, VSBuffer.fromString(JSON.stringify(storedMcpServerInfo))); + } + storedMcpServerInfo.location = location; readmeUrl = this.uriIdentityService.extUri.joinPath(location, 'README.md'); if (!await this.fileService.exists(readmeUrl)) { diff --git a/src/vs/platform/mcp/node/mcpManagementService.ts b/src/vs/platform/mcp/node/mcpManagementService.ts index 06e26df8772..aed19e04a5e 100644 --- a/src/vs/platform/mcp/node/mcpManagementService.ts +++ b/src/vs/platform/mcp/node/mcpManagementService.ts @@ -26,7 +26,7 @@ export class McpUserResourceManagementService extends CommonMcpUserResourceManag } override async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise { - this.logService.trace('MCP Management Service: installGallery', server.url); + this.logService.trace('MCP Management Service: installGallery', server.name, server.galleryUrl); this._onInstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource }); @@ -44,7 +44,7 @@ export class McpUserResourceManagementService extends CommonMcpUserResourceManag name: server.name, config: { ...mcpServerConfiguration.config, - gallery: server.url ?? true, + gallery: server.galleryUrl ?? true, version: server.version }, inputs: mcpServerConfiguration.inputs diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 0a582c2e636..74eeffbd521 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -340,10 +340,10 @@ export class McpServerEditor extends EditorPane { template.mcpServer = mcpServer; template.name.textContent = mcpServer.label; - template.name.classList.toggle('clickable', !!mcpServer.gallery?.webUrl || !!mcpServer.url); + template.name.classList.toggle('clickable', !!mcpServer.gallery?.webUrl); template.description.textContent = mcpServer.description; - if (mcpServer.url) { - this.transientDisposables.add(onClick(template.name, () => this.openerService.open(URI.parse(mcpServer.gallery?.webUrl ?? mcpServer.url!)))); + if (mcpServer.gallery?.webUrl) { + this.transientDisposables.add(onClick(template.name, () => this.openerService.open(URI.parse(mcpServer.gallery?.webUrl!)))); } this.renderNavbar(mcpServer, template, preserveFocus); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index 392be895939..b9a39dacc34 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -60,7 +60,7 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { } get id(): string { - return this.local?.id ?? this.gallery?.id ?? this.installable?.name ?? this.name; + return this.local?.id ?? this.gallery?.name ?? this.installable?.name ?? this.name; } get name(): string { @@ -106,10 +106,6 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { return this.gallery?.license; } - get url(): string | undefined { - return this.gallery?.url; - } - get repository(): string | undefined { return this.gallery?.repositoryUrl; } @@ -156,7 +152,7 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { } if (this.gallery) { - return this.mcpGalleryService.getMcpServerConfiguration(this.gallery, token); + return this.gallery.configuration; } throw new Error('No manifest available'); @@ -293,32 +289,32 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } private async syncInstalledMcpServers(resetGallery?: boolean): Promise { - const galleryMcpServerUrls: string[] = []; + const names: string[] = []; for (const installed of this.local) { if (installed.local?.source !== 'gallery') { continue; } if (installed.local.galleryUrl) { - galleryMcpServerUrls.push(installed.local.galleryUrl); + names.push(installed.local.name); } } - if (galleryMcpServerUrls.length) { - const galleryServers = await this.mcpGalleryService.getMcpServersFromGallery(galleryMcpServerUrls); + if (names.length) { + const galleryServers = await this.mcpGalleryService.getMcpServersFromGallery(names); if (galleryServers.length) { - await this.syncInstalledMcpServersWithGallery(galleryServers, false, resetGallery); + await this.syncInstalledMcpServersWithGallery(galleryServers, resetGallery); } } } - private async syncInstalledMcpServersWithGallery(gallery: IGalleryMcpServer[], vscodeGallery: boolean, resetGallery?: boolean): Promise { - const galleryMap = new Map(gallery.map(server => [vscodeGallery ? server.name : (server.url ?? server.name), server])); + private async syncInstalledMcpServersWithGallery(gallery: IGalleryMcpServer[], resetGallery?: boolean): Promise { + const galleryMap = new Map(gallery.map(server => [server.name, server])); for (const mcpServer of this.local) { if (!mcpServer.local) { continue; } - const key = vscodeGallery ? mcpServer.local.name : mcpServer.local.galleryUrl; + const key = mcpServer.local.name; const galleryServer = key ? galleryMap.get(key) : undefined; if (!galleryServer) { if (mcpServer.gallery && resetGallery) { @@ -327,9 +323,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } continue; } - if (!vscodeGallery) { - mcpServer.gallery = galleryServer; - } + mcpServer.gallery = galleryServer; if (!mcpServer.local.manifest) { mcpServer.local = await this.mcpManagementService.updateMetadata(mcpServer.local, galleryServer); } @@ -358,7 +352,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this._local = installed.map(i => { const existing = this._local.find(local => { if (i.galleryUrl) { - return local.local?.galleryUrl === i.galleryUrl; + return local.local?.name === i.name; } return local.id === i.id; }); @@ -655,7 +649,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this.logService.info(`MCP server '${url}' not found`); return true; } - const local = this.local.find(e => e.url === url) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore @@ -666,12 +660,12 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ private async handleMcpServerByName(name: string): Promise { try { - const gallery = await this.mcpGalleryService.getMcpServerByName(name); + const [gallery] = await this.mcpGalleryService.getMcpServersFromGallery([name]); if (!gallery) { this.logService.info(`MCP server '${name}' not found`); return true; } - const local = this.local.find(e => e.url === gallery.url) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 716cb749e94..c4482878fde 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -696,7 +696,6 @@ export interface IWorkbenchMcpServer { readonly publisherDisplayName?: string; readonly starsCount?: number; readonly license?: string; - readonly url?: string; readonly repository?: string; readonly config?: IMcpServerConfiguration | undefined; readonly readmeUrl?: URI; From 76e41e32a707d5904ed652b789b011f0825035a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Oct 2025 12:43:42 +0200 Subject: [PATCH 1085/4355] fix #270769 (#271093) --- src/vs/workbench/contrib/mcp/browser/mcpServersView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index dde1b6ddf8b..6fb327ca7c2 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -543,7 +543,7 @@ export class McpServersViewsContribution extends Disposable implements IWorkbenc when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledMcpServersContext.toNegated(), ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`).negate(), ProductQualityContext.isEqualTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`).negate()), weight: 40, order: 4, - canToggleVisibility: false + canToggleVisibility: true }, { id: 'workbench.views.mcp.welcomeView', From 17454fcaf45654788240b01496c9e79fe7102752 Mon Sep 17 00:00:00 2001 From: Lee Murray Date: Mon, 13 Oct 2025 13:02:11 +0100 Subject: [PATCH 1086/4355] Update codicon font to latest version (#271106) Update codicon font file to latest version Co-authored-by: mrleemurray --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118608 -> 118376 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index a4057c5db5b37d3ba0ee0169741cae8ae66f27b6..ad6519d9dd7c15d0ce2b9ca70a2dd9acb0d3ce07 100644 GIT binary patch delta 2601 zcmZ8i3s6&68ouY;XKrqio11rlfFUFrB7_hUUIs;^ij-1(6cKs(LW+n;5%CeEiWc9* zMXHF8A}Zbb092}o)NwnE<7^qnaUI9)u#V$0UB+>cbsdMUd!o|X&7Iu)&wtMOAK&-i z?+&)o!!7jw>0F|rsvpC`wHVl5dmueb5&Pz!7zRc#j2$`eI>w~;?SO>sBEZ7DHVIVAq`(Y0p!YSN_XW`X&KR!%MCQ^we5|Zg;3q?@{ zR6DvJ)3J0h{fMb#9*gos2U$CNS!@;eahY73WQ(*|Hd(epwv%UhE1$-f@C|a8yj0#I z@0Pz50)=FuRTvb86}-Zw_|dC$Dj%uRRNZQWI!}F9ldL(YP1H7O$8{mPZoQx%4=4>} z12Y1522nvJLA{1_!>i!CA=@Tp8coKVp<7I;riWqmVFPBHxy;;bzFEi9rlr^ z#HbR7-ErJ85}g^nH+sk!;>>jp#e~F^$2@e!yDHq1-7jMuu?J)OQ1UsRQ1RTpyjBt)HDSdvK0+&fzpRt!b`f?yYoJ`oPD|k1u3cGxlaY|0L^^ z=b6hg+dfVCbRa7|t8<=yUh2G_d1Lbn<_|4!EV#d5d|~{;riCM)6@1p8ZOG2dZeBz! z%Fc<;Y0G)Em|C2>`1azLxjDJ5xsR6^mo(-Hc{O?W^2YN``Pup2p8U~*_=3`cmV&`T ztk7CmQP^HMSfnjVFRCg!UNpM2dg=JG%;mP_b<6LqaIGj^=~-F5va?uP+)&)PidnUA zRp+YFl8BP|B{e0TB_p3t|9qfSSz1wgZZ)wwb#>F~k+OoazBOrU4z77z?kOK!>si~q z_Gv{*#kO_J*4?eltnBc9k@3a7s+6ifR}HPVuCH1@R-IYhQT=*D;)ePS12s%dZOy}t zxf>fc_HDv8b#B&fUa@(2OS5;fx8FNjYp*TdYTjD5^>$rGU47lqm(RD=ZR_4XeS3es zv%YPIVaKK&4|bY%Zu$y-b-N+0;qtDKT}8Xj?GD+!W%tmY_&q20M(jPj&#T>6z3=J% zto@e{lpVO!$TgNWHv0?*ul^~o$=1|(h&xnysIQr6E@^)Kb@|uBhs%$|A1Oc5-mO$s)=8G8@8!q1LG#h%df6>>+q<*6&s}0KZMyW|%NMR-SN8YRd>`_C^VQ<3o!7M2TCTnB&F%g1 zI(Plx^#^_WzRte!8)Y}z`(6Fden|Y`+)d-nnwv;iFarHCpa*)4!*rMhvtz#S@V!N$ z80J<=fiVI&J-}fAZle^?(H;P?Ig6MGoekIFq=i)6>Mu?koqotC`vD2QP@80D9WpLBmtK*5}1vY zG8oX5QbWf$T?xRU4gxNRCdO$F2SzFgDAftTMq4pwI@f2nqy6O_6JOF|A<488{w`GcXn>89d5?EPoz3&*ejy?@xrX zi5XtyJH2rVV0=3^hKK_>p64QIj-#=zqA$D31pF-rHb5f|!m&jN0$})23dtxuN7Dk z3n)q(JSkW!fpVz?m$DiXKkJ+IBFr32K#7PAfNUTy(J5F~&`6L+BGX7D+7P+I$i*_O zNaQPd@vU`YNT#CkD8x!J^s#aKQwJjsqN5y1&;ycy!{s!G`fA5?(=>=Yil$`nUEv<+ zMn%Us-4y+M)$VAA3(<_4p`*}fAr?`qZgkLRpB>;uL0vI!e^d`}9+o2fA%#N-APHF% z1pQyXhbBym)N@27vO+=~DJ zBqbHWkO??Rh#Ul}g_^)p3^*oUEQ9!b92E>D>j3aQJsjpm+`r`?`xoM0hncWA1n&P4 z?1#z&Vw@jB^*g+{e=Mjch-PE(AG?(DNx$`I`~xazI20_nC(}&*fNfMDXB6c1?+Q%+ zu%EZ%A;Y>1|9QMJ{>>XegQ=Jei^Wo~ObiP{QXqu}ppgRfX3{YpG_FW5)Ebo(7|?8( zEgm$b4wu{G+tX+C0$Qrot3`w$Aq8=mnio`ffGGf134$8enF1+6@cV@zO0NAYE0c3L zLYCuWMZDjXF&w{AB&BdpE@S0?k!U!`>-0*T70VXmK_;!%6ogk0fhHYPn+zl?R>@(& zoZt4}M3(&B(XxrqV)>PUd@#x8otSbxCc}b}8hJ2G5jr6>4%al`G;5R;>d=hLw|b2d@EwUU0n7e66UmV*pw}L0L;Zv%QGV6HD=A_L(i37yBtZe) ldv!ub{`P*moLlx*TO3h1Ln5i6aR-tnhN7+<+YK%z{|`kSk4OLj delta 2744 zcmY*Z4Ny~87QXkszx=$sJo1vf5JHFv289SABq0Kg5z!*0)FO?)MT`~^DI%g$MMEtG zixz+4FI7q@Qj4}lq2OAJlsZnwVI0PF9H(O)$8lW8?YNfRaU8~F_lCIIopdf0O0QcFryXgSFJ4H#MJC>Fsshi zo?XsCr6$D*ju~*yqz3Mb00eU`)??;oz(Q+Ay$#0cj@;+cgUpBzhP|Xu|x>{ zLDyn?I$qQFUX*T$4K{!?5Wx)C2(Kd}Dn|#<1$2*)5XnRlv6UDm9+8P;E_sw9sRF8v zme2wE6n^eA8B95IN3vORUs@tPEOW@t%Wd)@wvg>o)N|#^L}i(>nP+($pU+qDd!js1 zl~HX`{ZY?UMpcfgU3FdcNKgqL;g_J=t-hnl*Ys;m+7j&zF-JVC^Xd-jCZf&J{d%>2 zBBs*78WtFuVALAx64}Ju#I1=Pi9?CMCZQx#lHX>v`E8B1n=?vhTu#;}?@As|$xaDYr(8&RVmI0= z?LGFf)a=wMhuhKR7)$e~9ZY-Rv^oRM2QI5?v+J(g=dMjpPJiz4cv?MIX4+=f%pA&y z&v=koklC0ymQ|c}^)>Tr$Gtl5KJWOfP9O4B`)<$9ojo{vJUcIYXZB$BqdAM`w9L6Y zXFR7Y=keV9x%=i$%)1tRJ?r&Txu)Er+?(@_^V{?IyyI_p-ng2dm4EHcj5qrW>;(r4 zp1!sCt)~mh7j(a!_x82I!ou?lO$+lE4lW!oDlK~Oj^~}*e&qN0kNd|Kl`gunIDWBz zaoZAhN%2zO((a}Ama)qM%SM+yUtY4jbNRz}r@ec$SXW$M{Oby|B5_6Wis0aiXC=Op z>XMFMuQC`gqOIn(;Dc*}k>x zTIbs4wYSQh%BZ+!okN?m1T<-PU3^&?fITfXg19b4zC+qvDjy>c?Yptxv;WC~f&(oF9*6Xy_Cr~R?jHX3i26v&k*7y}N1KoSeyr};SnJ}}yPtJ` zcK5jU_{50?CmK#%ZIiWCwDo_k{=B|j)xNrY^ox=&+B^7;!j8LN7JvDmGpDn&^XbX@ zllQykb+vRo{3_$C(NjgIhQHQ-9r*e}cUE`L>73x{k#9!6tNrfQch7sudM=-3&kmoR zIOjfh_}oaZt@m1=y|19JysxpZv+v6H=I?iW|D?ZqAbz0cJbC`{Uk+YiFWmmY`oo@! z{Kd+Pqd#W+SU)Hk%pYvIWW7{%>E4ijsQahDPd&rt;lsm^e|7=@Nc;m(5A}cr(I5fX zK`1Gyr8FLZbcYu@lcAY|Y9kcPG~>>M?lgxHYSauKUg*N!t%ekXA;n{liLn>yk~mgD zh(<9%6bM0MND*S}o)>ONYw#NO7)3(Ca!EQI2S9I(t?Qj=YVrsoR0AC_11C5D+VSqL zG)?G_4>hqaC*IegjfEC5J;Ca9JK~^Lt%S7M>P#0yi?*+u7JrMBQhK37(CY;sLFLMM zUfv<+Zy94_2}*+E2_hazC}Je^+xDD2n82~6uP)6aXOlYGO0|Z z)9VBoQnH8^I3=Np)etmIQH&Hq9RW#FN$3zoDde;UC;&^qZZH6VKr=YERGb_PC*p9=g6?!{3W4)eB8$rl6Pzg!b7IAc(TW<&Bs*S| zjzKObrsq}GsT^c1W(-YBcO`@)!ca@9nZ{)ek08XF)24;@i3=O4j#i*2FVP(B3hur0Mh@ecpC_OAfy-wHMLGhUNV$AUl+vR zf3;=!l1@#k$BdffqM$;IGso%J6vT5Xjwdw+jq?@N2DKnX z9Ilm;kj^FS1f($YWS`2+YE4LLQ2w8sRWGG(Qusm7srN@p5dtl^TsvqfthtlVRJa`G*)6C#sD^9${2#O(rcig&36# z{}U|{k4(-_C5(Io-WQo}MZN*Rz{6rV#pa4!PJBav3K&5$@PKTb#Gm`(EQlLrx@a-e zYHj$UI;{zz{adD#WTRKz5cfvn^Q9z3k>S^0U$jr$Lmy2x&&k(G^r|C<8zeE0l2CXw Zy=b2?s$ Date: Mon, 13 Oct 2025 14:43:58 +0100 Subject: [PATCH 1087/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118608 -> 118408 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index a4057c5db5b37d3ba0ee0169741cae8ae66f27b6..2181ae6247250e652174aecdb13e8acd0f541d59 100644 GIT binary patch delta 3050 zcmZ`*dr(tn7Qf&3-B)gIlAD|4-T)!N5E2X+AcTaM!6H&iDW#TDAIQrWr4gx8i_aiS zky7e|xYVLjwTejF6@e8^I6EDe>2$h`<8~N_={l~n<5<`A58Cb*(AMtECX<_! zbIy0Z^Z1=}GSS1d9b*nG;!ignc#a?vj0AY|_`$+BUHUKoAP8`WAef23rZrVrfyv>6 zEmm=01alwOZ`#r@Lw@BAHxegcM}@6j3(6YE0XlL`1cI`l7A z7y5}|Lmw+Nov^h!f&1ouKpTz6$C+`WuDw)RAg$4rlV`4N6m%ihn8<*TILm7ZPxMF-L`z&^|;o!tM){Do&B(Vz>()@ zi?5t7&A;O0ocYd1=W*wlb25QQFej8G#v~ReHYH9hs9Z4S(z}{n_mi@df`O#rq}$2H z zaVgWBIhD0Et10VF_VVm23r!2V{Gz|ze?O-uXJ%1g(RWYfJ~i^x{oK6Vow*~q(~Fla zZd*LI_-mNA1OWKz_%%2QCo&WUUQs>h0rBlzuKhs|jSPzmvl>5>TrF>~YX>aMw%k?i$ zEO#xhT;8`rT2ZsIVCC@2JFBEsfmPSb=(5_fKb75E?OuJd+*;mVK3&0Acq(ctf>$eH zWkKc6%8x3is)VY{s^+Q-Ra4dR)n(PK)dOqbnzpt4+RAm=>pIs>ug_nq(_nO{& z*xhub;7HSv3rBm7-aTeJR(q_!hwmxv8Gb+J{lVkp@%j_QiA5&{PTV`$bn@p9T0fXR zm3OM^bj<0N(?9gOdfR%hpQ%4{zpuLQ(ph+R<=Gqk#r@O44+9?u&Kb`&o;y0g4pa{G zoi9J%eg2z4_h8N7)uGy$*szium*|j&EQ!4@<A1ngE?ZvhOgmzgw=Hw(C>XyA3rX&!q5u+q^$FJ%F*FV<@Jg@k4+TnIwJ zT`uUchtBPigIS0{84zYbmPT$jLI#2FqqRtYGUrbtF7>ll*83Q7eONt&Lol?5fEW0ge+r6U%mBZ@^SZcK&m z(F%M8gcae7@NG&~Foe$Rm=D;{-W@SY6E6sS62tQhF;xABPg8>u4xc5oL>1AE!^cre zZm-AASnVFKThh}qPK6hz#9?>XV>JK=j2RBQm7z62jeo`_0ZcQr1xT1f%aSam=HN<6 zm?2AUA3hr{T&e|N+UIpJ*o>9dK-!uFTp$4iHQ1cb50aqQhmX3wIne9205iw}IoJZD z1{O#&U4kUV7z_fdR0>Mv79%79Wh5c1gCvT9v;t6oQk&Eipdm{l6pcuzV<8#&q#B}r zr$)h9wP0A!D$|rwBmt=q6!;|1P%=l0N~j=3nv-do`+u_0uvalsM%XJssnSDAMd}e1 zWk5j5P!<$;Ss-Ypp z4Cc~ocT9$E+?zlD3`qiHjHV&4ft=15X%smH2WZ-8nP)MopjNFyYEGum+0c?74}+X&xWV{N}YU3>4gdb_bJ!XTvNiQ+@bg zD0yjA5F7QTVN8Xg=jm4C27)w#5I_oY*a&`UND|?{5CD@1kQgl_9p>i(5+a6&Yz7m~ z0^mfRq!3^jK~{lq0yK(9g&Jy93;?P4Tm-@uPOgPYngt|)2m%CI%E@E9fTvh&8xz<% zLn|S8NI^^pVLvns2PuSI2>tcn2Hy$*07a`6Fy?VODTo;)_Jw|&OC{jhOr-{*5{xLE z=q&(*e&~)1;@Z#FpZF)Pe*|G8GH`PLr@-M<`9YfJ$yEK8UKEA}J&nt3wfsIxwUF~` zjMm>}1%pG!fp0bPf+wnt9ptT|wsp?I!YAXIg@=Y1GXLl1hRk1n0Ss6`BogUF9#KpX zad?)ip&7_(JPxc(njgaz4}3{BsDT+{!|w3oVc_=q{GoTotU%>nGIy~v-Hig@}5P0mep2iW{F*&fO`?BxL zd%w5ud%y4Zy|3jMJ=sMc@H1044hAqRQICP9^7ewcy!PLJ#W3(QhS8IiTQ_c~*hbg3 zH(E3&8_?*3>aDNUJ|rgYVPLSXeZ)nKrFz~w`SXu3yqgdPuj3nu@Qc(-;p;?V_+KD4 zd{@SYf1&D-Js*u32G7cO#D*HN&jAD(pb1=tCRhOv!ZYwDF2kL8F}@QY$L|mcL>_U3 zB*;Rti;_`6>I8aj(HV3FeO(O2p3WQ;uSy!t2Ud$Cc^gjI|{Yf%*0d~8RK$eYpg7`GIrEdXqt}u-rO{&$YQgcw>HG*#b2A-GWVj*X{)w%+D_S* z+FR!Z=j-QRNMI835_Tr^B#b5ekO&jai2;Yr5pXm)t}G~9aNcQfwm7Gga*{$dNoSJo zxlFDq*MMs(IVZU~#go#PGL;%gJ(POeZF2|Rx6^EC+tY4%{GPgWXZl00*W2m6u+Xuv zcHvk?e8%m}!px@3sjQN$i%(geI_lH=4)~_Cd;QQ~74wWy*Z;fcNVW& z+_w1q;_2M-+`CH(mK<0zv-DEv>8z(u7rbGoBqT zbQK;dy#L&)=k71BSl<79{_~fLii$>7m{%067+o=4TvmMh1@8;j0x;kY91TpZEL(YD zRs5>Js;XJ47HT`RDu4UE+*G{f|xUO_v@4BB}ocH395`9TS$q(z{`h@i*>qDdK zAC&q_Yf5`cuWX<;xHi;n_7Y<6#M-F&seT~S*xP;sT=;THFnsx9M{`IQ}&|9r*uO4BRfRq3m$s%~!eZ=I;t zRd1>ud$s7*u^LZJdCi5_sMl`S7S~?h<_K+@+#c9|VTW@^aL4C69@K5GyZpN2_4?OG zcgl9=?`+)#cdgrXy`HJ}*YDl!-rc$Tr#%%RL#V$Yr=hmtRKuM&PP}n%Z^7QlH)U@I z8*APo-txRP-jvfc+-z&^Zk}mb)6&;6^LArvLF>dm@4lXWxAwdC_q>C>6MW}vTYTHV zyT*4zo$pQ`C_nIhySM$`!NP-W2k(Xr;qLdc-n-H9yTjVUZHMn4@gHeD^7H%k?@x8E z>b&tm{|7gY>W{iwT#>nZBF@o~w=w|jGY zdwcI6Z#aIdZ)sm!-%p=pd@^~W_{8|9hEIc^p6So(ANVXc^x4E06JOSSdG*VO1LXtf zPckRRPtKh3oa#6=G3Xe)G~^m89I6;<8tNUo@RjAOJzw1$t~nilx^{#Zx%>M=XP7hB zzP5e6|15X5>g?n<8Q(OF%0>%D+s@g}RiC>#W*F=LHu&wpxMjR!{O%vz7={t(hXDgH zUlvzM8oI_h*J!On?6J=yJp zNQ^d=7ikcI!Pz+~ICFIieqJqU$ zY!p=K8~{Bco>1d@NRlcgrBuq`u5kO* ze7_ASWnRX@N1631m8e#!AfXa93I!jn;*1Iy<03>O&&Mc9ihydZC`a@1dJ-p;tenJU zN;>?Pskve2KRydZ62Sl#V2iLqY&EtIJB?vHMWeGNqr)Kup-hmJm2OMI(fQQSn&ty` zH_{twC{cByV4Y>h2h$NK&5h`Ja_VFj&{hkArlzOar4c`@~5` za!NMzfo!y;+ZG3q2U-MUfg1H_)$D5N3vT3*1Vgmzq_turO>?3OM6oJdEjP$ns8-Pc z5CF-LW}r;Vau7lqLWMyfIF_d&gH)l=Fd>4(AtVV_i6kO}^8kTI<0QacIckEyNk}3Q zLKFj>fCR-N8=@4TA%zwJh!OMflyx*A8i~YGOiqs|+z0M(EXB)Uh+%<(M>5tZfTUwuOR!NuCB|9e^h^@u zSPjb&f>Cfkq1veB<&Z_OQseR^xC;j)VxAb%SQwod%8ly(lC$Qq)J-glxQWQy@R$=$ zSrjj0P)^w57$kLo4`$rI`EnGhGHPJM7GNGM8_PrI4>z0>La8a9rw|2cZkvS?v`D{N zDiI)2)98Aiy<8s~h_A@l9<#%2;CaJrw~;qIvN$EOJ!(NP34%J(9hYpA@OLpTPSl$4 zeB7kfnaw(6@-Z3(`X^c=GYTa)7dLSc_)w&~8uotCW!a~x@rQ>VOGzVqPo< z9pcyJC>x;qlrCB=lvXDts@rA{x9^x!nggE{Ayks^57!VRNl33hJ*rx$blsV)S+lR> l@JS((>Jq+`l#!@4KdM?Wn)l_iRZG(>b Date: Mon, 13 Oct 2025 14:46:39 +0100 Subject: [PATCH 1088/4355] Update chat configuration icon to use the gear icon --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 6d605472d43..03bf8449179 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1640,7 +1640,7 @@ Update \`.github/copilot-instructions.md\` for the user, then ask for feedback o title: localize2('config.label', "Configure Chat..."), group: 'navigation', when: ContextKeyExpr.equals('view', ChatViewId), - icon: Codicon.settingsGear, + icon: Codicon.gear, order: 6 }); } From f4fd003f103dd6fb63a347a32294d2fd0b709ed7 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 13 Oct 2025 15:27:03 +0100 Subject: [PATCH 1089/4355] Update codicon font file to latest version --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118408 -> 118420 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 2181ae6247250e652174aecdb13e8acd0f541d59..2d5fc3a2eeb3d21ddc146836c630e1fca73901ba 100644 GIT binary patch delta 1884 zcmaKqaZD3;8^`Z=ZLdeKKwFM06ew`Tt81a{N@)?WgDMV6=Q?Cnks+{J5fN);3{k1_ zVr9r`VZMS04i%X)h6sqPtBQn}#t^eChL~k9A^yQ@m@$Mc#1LZME9&+pZ}Q$5fC^v&}z~!GgoJGS{o(&DF_0u><155uQI0>Au#M~ z9ak}Z?r-+odU2R?UO_mB4H=;C=WIQ@iv0|&L!Sp(gE~1{jt^5|Q(OzTKe#cZDpV0# z8S3Wocq*QmXXknN8GHx-3g63L6_A1)L9bv&@G^`D%Ltps)9`2E=5Q~OKv;>V!W>~| zgdw6M5{{%Iy`qRHILZ;ti!O+E$8clp&X`GYzW8c`hlsRpt)>*4l=vGrxQ#(^1 zZb;lvy!(MED(mp(%8(XV{an+az+GJng;$-0-dVlWyS4EGER zn|5q+Z<^Y)vblWo%WPA&C;Ro5*$+)0j_0UytU2?!irgV%oN*v8C+}C2$u#?s@uP=Z zGq?7BjC@>fM$C0)uk(|_PiFJW^8MRX+lsb5F5nc@6fA$5^XUtVz+$!xY=^hkY@gYo z*a*0(t{1N__7pGd7VJ*jUA6mh30h(< zX($;knJ*)z`xPblvx z_wN($tK8?WKr1#^R8)8>{QKekmi@DpvdX5)sRN<|g$D+z;HsLcCkIOo_8)v@FR;%a zDme7$(5j=*(RsM;@bhY0^~4v}FBWPFYbI-!zs&ryrIu4`tDQK)JCc8->&R@KxbA%2 z;!(%Zo}*8W5y$+_a3(F7Ph6E;wIzx-Ob8x-R;a9=rVNit8%#YRlDyUSn_9HR@XPwHJMwK6l^T zcQxOwTra;qaRa?ka^qRQrGMdj#}AI1;+yq1uMC9@*@gyhS#R~+dODmsTs8c7#6I%N z$l@q>bp7a-(elwL{q4BhZDaV@!#l*CzH!fw+8+nK^CURQwFJ_bO_R=Ad+xA7D#m3jXMJ@n;YR1P=!TT!OP$ER>5y%0&Vg zVPz3<1Te4=SWt{&6o&pIUJ*|}4hmie!z2mAc{u3|2SF$tDUUQJF@r)0A~cAZWa6MG z<`V|~jfdz93A}&^h#(O}LNZ7NWkA^w6h@Hwkg2IByQ@i4iI0fLukE;@FnQ5>6#li}Q;f5EUMPyotGG79*z zKq`L(hM*YWM7c1`NOuM?*2q!13Lz8=DIsXx8tnhB$-5VhY3p`eCwIOd=ryjtP9kz!3=qll4Xm`W>(#42pm@ kycyX3D+}yOem7t*AIszNc-%aUU;KY(LC_s50uucH0oMN^djJ3c delta 1869 zcmaKqe@s)?0>{s}^xl@X)=PifR;;xYY_ULF`i_ilkl%>H=y-kju~ zoOAN|obR`_i|X&9Hf1sUd`$-cND=^exu-c#OG$Un1e@#QW5xi0%brt}RV$QdivSJ} zHIJz%V-GvL_ZKRq-cAg|>{vhfzM>kbPU=fq7422DExMJiqK`7oK=z_3CGtaDqkR2y53 zld00wiq!EmQQBPk-gIC3LPk->lT2Y|n~7}-n3g{1{$M4`o%L(BIeRR7$(&=ZF^`$& zcOTsC-yPb$l;g;Gy(f21V9#>y%!j!j4)2xkwe6kTm$+}x5^L$-Z{Gh)UT)sZft&-A z`DyvxAAye?R$#5N2E88_d^~foKW z6h-!;flrA~oyC^oVDZ8c;)wgmYzbE4D)~pr+t1XW-LXk*0o(jh=27iY=g}!UVz=09 z>_6D&O7YUP(z?>o(m6+>qr}nR82TLf+<%OD%zixMc+2tm6W-htHD&o_{<5%B+??U(#Qyy);{At81%UtOxZi^-nHaFHbk* zGz70Gt~6eG?brGnzh-|u8z>Guy{fu;=4z->)p)IO@f*uG!`DpL2CpYx_g{b4p%Zm(U#G6r(M`y-~RlD;Kuoz^qan$-sKM8Ez2$6tTn=DJwhb)(Y-XV3m5?~sP$T@1mhBX9aYBT^7!!vZ6?&3 zU^>(rWD*wK2B{d44!ML{uhXdYN=TJRbs8@AFMeE{A}&rG9nI=tu|@1C1f}z31RFuo zRGyeHqA0>HpfRM3yOCvzxCAzYTm(ua5K)rLz0uK-FXN}ApfsGv!)a*B0S1l602m-2 z7z<+o4RC=H=z$6Bdk<5l((v_$cnOuS*T^E^+ab1rXo5Nop9Iw^;ToiDRmQ+KNM!M( z+;%9_t#f;|5Fv?00v4O7jH01Y8tVVh1v>H{xfEe9Vo}P)2#Q2WIO`hwAw8P2i4~8a zv?wjkS>-1n57$Q~co79@#E?NRH%4@+5(OIo%7#=dkbs>a69BJa9rVu*V2)%PQki1XQ~Lj$U9(r~l2u05W)*L( zM!L2?_klc`!@wD2{s{^Zo9qpwZgc<=krJekTOQIF3|bj!Kz_=##()uz@!!udCP*PE z+J8r3GOdjx6iS6cTYD1BRs7kV=+It5>(bLg5kR`XRO$hM9@uOfw_q@c%pPfd_*LQ~w1aYaX!x From 052d769933f763885b4e87725d5334c9b6dae4e1 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:16:58 -0700 Subject: [PATCH 1090/4355] Small cleanup --- build/lib/typeScriptLanguageServiceHost.js | 8 ++++---- build/lib/typeScriptLanguageServiceHost.ts | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build/lib/typeScriptLanguageServiceHost.js b/build/lib/typeScriptLanguageServiceHost.js index 58aeeb2de09..d90ad86f9be 100644 --- a/build/lib/typeScriptLanguageServiceHost.js +++ b/build/lib/typeScriptLanguageServiceHost.js @@ -52,14 +52,14 @@ class TypeScriptLanguageServiceHost { getCurrentDirectory() { return ''; } - getDefaultLibFileName(_options) { - return this.ts.getDefaultLibFilePath(_options); + getDefaultLibFileName(options) { + return this.ts.getDefaultLibFilePath(options); } - readFile(path, _encoding) { + readFile(path, encoding) { if (this.topLevelFiles.get(path)) { return this.topLevelFiles.get(path); } - return typescript_1.default.sys.readFile(path, _encoding); + return typescript_1.default.sys.readFile(path, encoding); } fileExists(path) { if (this.topLevelFiles.has(path)) { diff --git a/build/lib/typeScriptLanguageServiceHost.ts b/build/lib/typeScriptLanguageServiceHost.ts index eebd9e70b74..faa11d44da3 100644 --- a/build/lib/typeScriptLanguageServiceHost.ts +++ b/build/lib/typeScriptLanguageServiceHost.ts @@ -5,7 +5,6 @@ import ts from 'typescript'; import fs from 'node:fs'; -// import path from 'node:path'; export type IFileMap = Map; @@ -49,14 +48,14 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { getCurrentDirectory(): string { return ''; } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return this.ts.getDefaultLibFilePath(_options); + getDefaultLibFileName(options: ts.CompilerOptions): string { + return this.ts.getDefaultLibFilePath(options); } - readFile(path: string, _encoding?: string): string | undefined { + readFile(path: string, encoding?: string): string | undefined { if (this.topLevelFiles.get(path)) { return this.topLevelFiles.get(path); } - return ts.sys.readFile(path, _encoding); + return ts.sys.readFile(path, encoding); } fileExists(path: string): boolean { if (this.topLevelFiles.has(path)) { From bb0294ffca5583a768e5ea44350ecc4e9c6e3791 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Mon, 13 Oct 2025 18:42:07 +0300 Subject: [PATCH 1091/4355] fix: memory leak in chat welcome (#271121) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 4f79d5c281f..8e9db483ed5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -314,6 +314,8 @@ export class ChatWidget extends Disposable implements IChatWidget { private welcomeMessageContainer!: HTMLElement; private readonly welcomePart: MutableDisposable = this._register(new MutableDisposable()); + private readonly welcomeContextMenuDisposable: MutableDisposable = this._register(new MutableDisposable()); + private readonly historyViewStore = this._register(new DisposableStore()); private readonly chatTodoListWidget: ChatTodoListWidget; private historyList: WorkbenchList | undefined; @@ -1066,7 +1068,7 @@ export class ChatWidget extends Disposable implements IChatWidget { dom.append(this.welcomeMessageContainer, this.welcomePart.value.element); // Add right-click context menu to the entire welcome container - this._register(dom.addDisposableListener(this.welcomeMessageContainer, dom.EventType.CONTEXT_MENU, (e) => { + this.welcomeContextMenuDisposable.value = dom.addDisposableListener(this.welcomeMessageContainer, dom.EventType.CONTEXT_MENU, (e) => { e.preventDefault(); e.stopPropagation(); this.contextMenuService.showContextMenu({ @@ -1078,7 +1080,7 @@ export class ChatWidget extends Disposable implements IChatWidget { getAnchor: () => ({ x: e.clientX, y: e.clientY }), getActionsContext: () => ({}) }); - })); + }); } } From c01707eb412ad6b7ae18fb047269a4e37745f4c2 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 13 Oct 2025 17:43:17 +0100 Subject: [PATCH 1092/4355] fix: adjust padding in count badge CSS for better alignment --- src/vs/base/browser/ui/countBadge/countBadge.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index eb0c0837ee9..757790208c4 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 3px 6px; + padding: 3px 3px; border-radius: 11px; font-size: 11px; min-width: 18px; From 6734b945cdb7992e5960bab2299e5dde097e77fe Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 13 Oct 2025 17:43:31 +0100 Subject: [PATCH 1093/4355] fix: simplify padding in count badge CSS for improved layout --- src/vs/base/browser/ui/countBadge/countBadge.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index 757790208c4..f9bd9e11eae 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 3px 3px; + padding: 3px; border-radius: 11px; font-size: 11px; min-width: 18px; From a114264f0fac58dc5461eb27a731d790ee029cac Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 13 Oct 2025 09:44:53 -0700 Subject: [PATCH 1094/4355] fix: export escapeGlobPattern function and update tests to use it --- src/vs/workbench/services/search/common/queryBuilder.ts | 2 +- .../services/search/test/common/queryBuilder.test.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index afa8a1a52c2..68487ac7bf6 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -691,7 +691,7 @@ function normalizeGlobPattern(pattern: string): string { * given the input "//?/C:/A?.txt", this would produce output '//[?]/C:/A[?].txt', * which may not be desirable in some cases. Use with caution if UNC paths could be expected. */ -function escapeGlobPattern(path: string): string { +export function escapeGlobPattern(path: string): string { return path.replace(/([?*[\]])/g, '[$1]'); } diff --git a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts index 6767f0b8ddb..0508e090ca2 100644 --- a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts @@ -9,7 +9,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { testWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js'; -import { resolveResourcesForSearchIncludes } from '../../common/queryBuilder.js'; +import { escapeGlobPattern, resolveResourcesForSearchIncludes } from '../../common/queryBuilder.js'; import { TestContextService } from '../../../../test/common/workbenchTestServices.js'; suite('QueryBuilderCommon', () => { @@ -38,11 +38,6 @@ suite('QueryBuilderCommon', () => { // Test file name with square brackets const fileName = 'file[test].txt'; - // Function matching the one used in queryBuilder.ts - function escapeGlobPattern(path: string): string { - return path.replace(/([?*[\]])/g, '[$1]'); - } - // Without escaping, the pattern treats [test] as a character class const unescapedResult = glob.match(fileName, fileName); assert.strictEqual(unescapedResult, false, 'Unescaped pattern should not match due to character class interpretation'); From d3843ad9130b686c5cdebda81377c0848517022c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 13 Oct 2025 17:54:45 +0100 Subject: [PATCH 1095/4355] fix: correct padding in count badge CSS for improved layout for longer numbers --- src/vs/base/browser/ui/countBadge/countBadge.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css index f9bd9e11eae..9fb4d0bee48 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 3px; + padding: 3px 5px; border-radius: 11px; font-size: 11px; min-width: 18px; From a9f7d7d6d9c5b5fa8559bc909e9fb9a0f9a2f5c4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:58:06 -0700 Subject: [PATCH 1096/4355] Remove `compile-extensions-build-legacy` task This doesn't seem to be used anymore --- build/gulpfile.extensions.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 7826f48490b..67ee16c27d9 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -170,22 +170,12 @@ const tasks = compilations.map(function (tsconfigFile) { .pipe(gulp.dest(out)); })); - const compileBuildTask = task.define(`compile-build-extension-${name}`, task.series(cleanTask, () => { - const pipeline = createPipeline(true, true); - const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); - const input = es.merge(nonts, pipeline.tsProjectSrc()); - - return input - .pipe(pipeline()) - .pipe(gulp.dest(out)); - })); - // Tasks gulp.task(transpileTask); gulp.task(compileTask); gulp.task(watchTask); - return { transpileTask, compileTask, watchTask, compileBuildTask }; + return { transpileTask, compileTask, watchTask }; }); const transpileExtensionsTask = task.define('transpile-extensions', task.parallel(...tasks.map(t => t.transpileTask))); @@ -199,9 +189,6 @@ const watchExtensionsTask = task.define('watch-extensions', task.parallel(...tas gulp.task(watchExtensionsTask); exports.watchExtensionsTask = watchExtensionsTask; -const compileExtensionsBuildLegacyTask = task.define('compile-extensions-build-legacy', task.parallel(...tasks.map(t => t.compileBuildTask))); -gulp.task(compileExtensionsBuildLegacyTask); - //#region Extension media const compileExtensionMediaTask = task.define('compile-extension-media', () => ext.buildExtensionMedia(false)); From 01991f0023e8aa8ec9fe5355bafb3c62114bf0ca Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 13 Oct 2025 11:02:14 -0700 Subject: [PATCH 1097/4355] Fix handleRemovals in keybinding to remove the rule if the 'when' condition covers the default keybinding rule. --- .../keybinding/common/keybindingResolver.ts | 9 ++++-- .../contrib/chat/browser/chatInputPart.ts | 30 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index f1592b35785..e85eb5c70dd 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { implies, ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService, expressionsAreEqualWithConstantSubstitution } from '../../contextkey/common/contextkey.js'; +import { implies, ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService } from '../../contextkey/common/contextkey.js'; import { ResolvedKeybindingItem } from './resolvedKeybindingItem.js'; //#region resolution-result @@ -103,7 +103,12 @@ export class KeybindingResolver { if (!defaultKb.when) { return false; } - if (!expressionsAreEqualWithConstantSubstitution(when, defaultKb.when)) { + + // Check if `when` is entirely included in `defaultKb.when` + // e.g. `when` is `a && b`, and `defaultKb.when` is `a && b && c` + // this means the removal `when` is more general than the `defaultKb.when` + // so the removal applies + if (!KeybindingResolver.whenIsEntirelyIncluded(defaultKb.when, when)) { return false; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 2f77a6be54f..2c5837c51ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -50,7 +50,7 @@ import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/but import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; @@ -1199,14 +1199,32 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.overflowWidgetsDomNode?.classList.add('hideSuggestTextIcons'); this._inputEditorElement.classList.add('hideSuggestTextIcons'); - // Prevent Enter key from creating new lines - but allow keybinding service to still handle the event - // We need to prevent Monaco's default Enter behavior while still allowing the VS Code keybinding service - // to receive and process the Enter key for ChatSubmitAction + // Prevent Enter key from creating new lines - but respect user's custom keybindings + // Only prevent default behavior if ChatSubmitAction is bound to Enter AND its precondition is met this._register(this._inputEditor.onKeyDown((e) => { if (e.keyCode === KeyCode.Enter && !hasModifierKeys(e)) { - // Only prevent the default Monaco behavior (newline insertion) + // Check if ChatSubmitAction has a keybinding for plain Enter in the current context + // This respects user's custom keybindings that disable the submit action + const chatSubmitKeybinding = this.keybindingService.lookupKeybinding(ChatSubmitAction.ID, this.contextKeyService); + + // Only prevent default if the keybinding exists AND matches plain Enter (no modifiers) + if (chatSubmitKeybinding) { + const chords = chatSubmitKeybinding.getDispatchChords(); + const isPlainEnter = chords.length === 1 && chords[0] === 'Enter'; + + if (isPlainEnter) { + // Also check if the action's precondition is met (e.g., inputHasText) + const commandAction = MenuRegistry.getCommand(ChatSubmitAction.ID); + const preconditionMet = !commandAction?.precondition || + this.contextKeyService.contextMatchesRules(commandAction.precondition); + + // Only prevent default if both keybinding exists and precondition is met + if (preconditionMet) { + e.preventDefault(); + } + } + } // Do NOT call stopPropagation() so the keybinding service can still process this event - e.preventDefault(); } })); From 98b069c0412b8a1bcba8c0ab0dd556cfa59b019d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:03:20 -0700 Subject: [PATCH 1098/4355] Work towards getting isolated built-in extension compiles For #271167 This makes it so our built-in extensions can mostly be built using `tsc` on the command line. Previously the extensions were picking up a lot of typing info from the root `node_modules` that meant they weren't truly independent --- .../configuration-editing/tsconfig.json | 3 + .../client/tsconfig.json | 3 + .../css-language-features/server/package.json | 2 +- .../server/tsconfig.json | 3 + extensions/debug-auto-launch/tsconfig.json | 3 + extensions/debug-server-ready/tsconfig.json | 3 + extensions/emmet/tsconfig.json | 6 +- extensions/extension-editing/tsconfig.json | 2 +- extensions/git/package.json | 2 +- extensions/git/tsconfig.json | 3 +- extensions/github-authentication/package.json | 33 +++++----- extensions/grunt/tsconfig.json | 3 + extensions/gulp/tsconfig.json | 3 + .../client/tsconfig.json | 3 + .../server/package.json | 2 +- .../server/tsconfig.json | 3 + extensions/ipynb/package-lock.json | 20 +++++- extensions/ipynb/package.json | 3 +- extensions/ipynb/tsconfig.json | 8 ++- extensions/jake/tsconfig.json | 3 + .../client/tsconfig.json | 3 + .../server/package.json | 2 +- .../server/tsconfig.json | 3 + .../notebook/tsconfig.json | 7 ++- .../package-lock.json | 9 +-- .../markdown-language-features/package.json | 2 +- .../preview-src/tsconfig.json | 9 ++- .../markdown-language-features/tsconfig.json | 6 +- .../markdown-math/notebook/tsconfig.json | 6 ++ extensions/markdown-math/tsconfig.json | 5 +- extensions/media-preview/tsconfig.json | 5 +- extensions/merge-conflict/tsconfig.json | 3 + .../chat-webview-src/tsconfig.json | 6 ++ .../mermaid-chat-features/package-lock.json | 62 +++---------------- extensions/mermaid-chat-features/package.json | 3 +- .../mermaid-chat-features/tsconfig.json | 6 ++ .../notebook-renderers/package-lock.json | 20 ++++-- extensions/notebook-renderers/package.json | 4 +- .../notebook-renderers/src/htmlHelper.ts | 6 +- extensions/notebook-renderers/tsconfig.json | 7 +++ extensions/npm/tsconfig.json | 3 + .../php-language-features/tsconfig.json | 5 +- extensions/references-view/tsconfig.json | 3 + extensions/search-result/package-lock.json | 20 ++++++ extensions/search-result/package.json | 3 + extensions/search-result/tsconfig.json | 3 + .../simple-browser/preview-src/tsconfig.json | 3 + extensions/simple-browser/tsconfig.json | 5 +- extensions/terminal-suggest/package-lock.json | 20 ++++++ extensions/terminal-suggest/package.json | 3 + extensions/terminal-suggest/tsconfig.json | 11 ++-- extensions/tunnel-forwarding/tsconfig.json | 3 + .../tsconfig.json | 3 + extensions/vscode-api-tests/package-lock.json | 11 ++-- extensions/vscode-api-tests/package.json | 24 +++---- extensions/vscode-api-tests/tsconfig.json | 7 ++- .../vscode-colorize-perf-tests/tsconfig.json | 3 + .../vscode-colorize-tests/tsconfig.json | 3 + extensions/vscode-test-resolver/tsconfig.json | 3 + 59 files changed, 295 insertions(+), 126 deletions(-) diff --git a/extensions/configuration-editing/tsconfig.json b/extensions/configuration-editing/tsconfig.json index 3013ee54227..7106538eb99 100644 --- a/extensions/configuration-editing/tsconfig.json +++ b/extensions/configuration-editing/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/css-language-features/client/tsconfig.json b/extensions/css-language-features/client/tsconfig.json index 5284e093858..51303a368a2 100644 --- a/extensions/css-language-features/client/tsconfig.json +++ b/extensions/css-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index c4b51d1d877..a13b2d0e55d 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -16,7 +16,7 @@ "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/css-language-features/server/tsconfig.json b/extensions/css-language-features/server/tsconfig.json index 4f24a50855c..0b49ec72b8f 100644 --- a/extensions/css-language-features/server/tsconfig.json +++ b/extensions/css-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/debug-auto-launch/tsconfig.json b/extensions/debug-auto-launch/tsconfig.json index bfcf873c749..69c1f9755bf 100644 --- a/extensions/debug-auto-launch/tsconfig.json +++ b/extensions/debug-auto-launch/tsconfig.json @@ -5,6 +5,9 @@ "downlevelIteration": true, "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/debug-server-ready/tsconfig.json b/extensions/debug-server-ready/tsconfig.json index 9bf747283ca..66064e1e9f2 100644 --- a/extensions/debug-server-ready/tsconfig.json +++ b/extensions/debug-server-ready/tsconfig.json @@ -5,6 +5,9 @@ "downlevelIteration": true, "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/emmet/tsconfig.json b/extensions/emmet/tsconfig.json index 994fa239537..a6353d515d3 100644 --- a/extensions/emmet/tsconfig.json +++ b/extensions/emmet/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "exclude": [ "node_modules", diff --git a/extensions/extension-editing/tsconfig.json b/extensions/extension-editing/tsconfig.json index 3714bd09958..796a159a61c 100644 --- a/extensions/extension-editing/tsconfig.json +++ b/extensions/extension-editing/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "./out", "typeRoots": [ - "node_modules/@types" + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/git/package.json b/extensions/git/package.json index 16909716ad0..03013a0f90f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3732,7 +3732,7 @@ }, "devDependencies": { "@types/byline": "4.2.31", - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/picomatch": "2.3.0", "@types/which": "3.0.0" diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 8bc4c420ef6..3fdc14300e5 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -4,7 +4,8 @@ "outDir": "./out", "typeRoots": [ "./node_modules/@types" - ] + ], + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index ea49c06f406..6d22dfe1a6a 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -48,23 +48,24 @@ ] } ], - "configuration": [{ - "title": "%config.github-enterprise.title%", - "properties": { - "github-enterprise.uri": { - "type": "string", - "markdownDescription": "%config.github-enterprise.uri.description%", - "pattern": "^(?:$|(https?)://(?!github\\.com).*)" - }, - "github-authentication.useElectronFetch": { - "type": "boolean", - "default": true, - "scope": "application", - "markdownDescription": "%config.github-authentication.useElectronFetch.description%" + "configuration": [ + { + "title": "%config.github-enterprise.title%", + "properties": { + "github-enterprise.uri": { + "type": "string", + "markdownDescription": "%config.github-enterprise.uri.description%", + "pattern": "^(?:$|(https?)://(?!github\\.com).*)" + }, + "github-authentication.useElectronFetch": { + "type": "boolean", + "default": true, + "scope": "application", + "markdownDescription": "%config.github-authentication.useElectronFetch.description%" + } } } - } - ] + ] }, "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", @@ -82,7 +83,7 @@ "vscode-tas-client": "^0.1.84" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-fetch": "^2.5.7" }, diff --git a/extensions/grunt/tsconfig.json b/extensions/grunt/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/extensions/grunt/tsconfig.json +++ b/extensions/grunt/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/gulp/tsconfig.json b/extensions/gulp/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/extensions/gulp/tsconfig.json +++ b/extensions/gulp/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 615adaeea04..051d5823fe5 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 00f6532b7ee..1eb6a452937 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -17,7 +17,7 @@ "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/html-language-features/server/tsconfig.json b/extensions/html-language-features/server/tsconfig.json index 4f24a50855c..0b49ec72b8f 100644 --- a/extensions/html-language-features/server/tsconfig.json +++ b/extensions/html-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/ipynb/package-lock.json b/extensions/ipynb/package-lock.json index 7042d6a22b2..8dc9c5a8a42 100644 --- a/extensions/ipynb/package-lock.json +++ b/extensions/ipynb/package-lock.json @@ -14,7 +14,8 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3" + "@types/markdown-it": "12.2.3", + "@types/node": "^22.18.10" }, "engines": { "vscode": "^1.57.0" @@ -65,6 +66,16 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -72,6 +83,13 @@ "engines": { "node": ">=8" } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 69f458bc7d4..89a24e5cc15 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -166,7 +166,8 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3" + "@types/markdown-it": "12.2.3", + "@types/node": "^22.18.10" }, "repository": { "type": "git", diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index ee21f68d22a..39ab6fc882d 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -2,7 +2,13 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "lib": ["dom"] + "lib": [ + "ES2024", + "DOM" + ], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/jake/tsconfig.json b/extensions/jake/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/extensions/jake/tsconfig.json +++ b/extensions/jake/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/json-language-features/client/tsconfig.json b/extensions/json-language-features/client/tsconfig.json index cf91914c874..bc775d950e5 100644 --- a/extensions/json-language-features/client/tsconfig.json +++ b/extensions/json-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index bfcba8726e4..be78d121a8a 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -20,7 +20,7 @@ "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/json-language-features/server/tsconfig.json b/extensions/json-language-features/server/tsconfig.json index 424b140b4a0..07433e08b62 100644 --- a/extensions/json-language-features/server/tsconfig.json +++ b/extensions/json-language-features/server/tsconfig.json @@ -9,6 +9,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index ec71045301f..77ee816cd75 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -9,6 +9,11 @@ "ES2024", "DOM", "DOM.Iterable" - ] + ], + "types": [], + "typeRoots": [ + "../node_modules/@types" + ], + "skipLibCheck": true } } diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index d8169c35e67..f83258f3751 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -24,7 +24,7 @@ }, "devDependencies": { "@types/dompurify": "^3.0.5", - "@types/lodash.throttle": "^4.1.3", + "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", @@ -184,10 +184,11 @@ "dev": true }, "node_modules/@types/lodash.throttle": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.3.tgz", - "integrity": "sha512-FUm7uMuYRX7dzqmgX02bxdBwC75owUxGA4dDKtFePDLJ6N1ofXxkRX3NhJV8wOrNs/wCjaY6sDVJrD1lbyERoQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", + "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", "dev": true, + "license": "MIT", "dependencies": { "@types/lodash": "*" } diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 7a478ee717b..27689d6bd99 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -781,7 +781,7 @@ }, "devDependencies": { "@types/dompurify": "^3.0.5", - "@types/lodash.throttle": "^4.1.3", + "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json index e401dbe43fb..4001ffeb068 100644 --- a/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/extensions/markdown-language-features/preview-src/tsconfig.json @@ -8,7 +8,14 @@ "es2024", "DOM", "DOM.Iterable" - ] + ], + "types": [ + "vscode-webview" + ], + "typeRoots": [ + "../node_modules/@types" + ], + "skipLibCheck": true }, "typeAcquisition": { "include": [ diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index fcd79775de5..0e7a865e1f5 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/extensions/markdown-math/notebook/tsconfig.json b/extensions/markdown-math/notebook/tsconfig.json index ae9e837683c..def3077d238 100644 --- a/extensions/markdown-math/notebook/tsconfig.json +++ b/extensions/markdown-math/notebook/tsconfig.json @@ -8,6 +8,12 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/extensions/markdown-math/tsconfig.json b/extensions/markdown-math/tsconfig.json index c5194e2e33c..40e645a1ed6 100644 --- a/extensions/markdown-math/tsconfig.json +++ b/extensions/markdown-math/tsconfig.json @@ -2,7 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [] + "types": [], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/media-preview/tsconfig.json b/extensions/media-preview/tsconfig.json index fcd79775de5..796a159a61c 100644 --- a/extensions/media-preview/tsconfig.json +++ b/extensions/media-preview/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/merge-conflict/tsconfig.json b/extensions/merge-conflict/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/extensions/merge-conflict/tsconfig.json +++ b/extensions/merge-conflict/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json index 72282fb0c7d..a57ffcaeba0 100644 --- a/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json +++ b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json @@ -7,6 +7,12 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json index 09481d2b835..18394643a0c 100644 --- a/extensions/mermaid-chat-features/package-lock.json +++ b/extensions/mermaid-chat-features/package-lock.json @@ -13,8 +13,7 @@ "mermaid": "^11.11.0" }, "devDependencies": { - "@types/jsdom": "^21.1.7", - "@types/node": "^24" + "@types/node": "^22.18.10" }, "engines": { "vscode": "^1.104.0" @@ -389,35 +388,16 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, - "node_modules/@types/jsdom": { - "version": "21.1.7", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", - "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, "node_modules/@types/node": { - "version": "24.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz", - "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==", + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.12.0" + "undici-types": "~6.21.0" } }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1036,19 +1016,6 @@ "@types/trusted-types": "^2.0.7" } }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/exsolve": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", @@ -1235,19 +1202,6 @@ "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", "license": "MIT" }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -1361,9 +1315,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json index 30c57f90548..2992cbe6c20 100644 --- a/extensions/mermaid-chat-features/package.json +++ b/extensions/mermaid-chat-features/package.json @@ -79,8 +79,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "devDependencies": { - "@types/jsdom": "^21.1.7", - "@types/node": "^24" + "@types/node": "^22.18.10" }, "dependencies": { "dompurify": "^3.2.6", diff --git a/extensions/mermaid-chat-features/tsconfig.json b/extensions/mermaid-chat-features/tsconfig.json index 11e3e9a5d5f..7e1920f289e 100644 --- a/extensions/mermaid-chat-features/tsconfig.json +++ b/extensions/mermaid-chat-features/tsconfig.json @@ -2,6 +2,12 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/notebook-renderers/package-lock.json b/extensions/notebook-renderers/package-lock.json index 8dbc5f5ad4c..f2f7af8abf8 100644 --- a/extensions/notebook-renderers/package-lock.json +++ b/extensions/notebook-renderers/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@types/jsdom": "^21.1.0", + "@types/node": "^22.18.10", "@types/vscode-notebook-renderer": "^1.60.0", "jsdom": "^21.1.1" }, @@ -38,10 +39,14 @@ } }, "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", - "dev": true + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/tough-cookie": { "version": "4.0.2", @@ -577,6 +582,13 @@ "node": ">= 0.8.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index b4a5236f7d8..77c042ee663 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -5,7 +5,7 @@ "publisher": "vscode", "version": "1.0.0", "license": "MIT", - "icon": "media/icon.png", + "icon": "media/icon.png", "engines": { "vscode": "^1.57.0" }, @@ -46,9 +46,9 @@ "watch": "npx gulp compile-watch:notebook-renderers", "build-notebook": "node ./esbuild.mjs" }, - "dependencies": {}, "devDependencies": { "@types/jsdom": "^21.1.0", + "@types/node": "^22.18.10", "@types/vscode-notebook-renderer": "^1.60.0", "jsdom": "^21.1.1" }, diff --git a/extensions/notebook-renderers/src/htmlHelper.ts b/extensions/notebook-renderers/src/htmlHelper.ts index 819a3a640af..dd22022ce3d 100644 --- a/extensions/notebook-renderers/src/htmlHelper.ts +++ b/extensions/notebook-renderers/src/htmlHelper.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ export const ttPolicy = (typeof window !== 'undefined') ? - window.trustedTypes?.createPolicy('notebookRenderer', { - createHTML: value => value, - createScript: value => value, + (window as Window & { trustedTypes?: any }).trustedTypes?.createPolicy('notebookRenderer', { + createHTML: (value: string) => value, + createScript: (value: string) => value, }) : undefined; diff --git a/extensions/notebook-renderers/tsconfig.json b/extensions/notebook-renderers/tsconfig.json index 3472d5bbaa7..07c5ef470f5 100644 --- a/extensions/notebook-renderers/tsconfig.json +++ b/extensions/notebook-renderers/tsconfig.json @@ -3,7 +3,14 @@ "compilerOptions": { "outDir": "./out", "lib": [ + "es2024", "dom" + ], + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/npm/tsconfig.json b/extensions/npm/tsconfig.json index ec12eb547b3..0c84fcc94e3 100644 --- a/extensions/npm/tsconfig.json +++ b/extensions/npm/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/php-language-features/tsconfig.json b/extensions/php-language-features/tsconfig.json index 7234fdfeb97..29a69e99d98 100644 --- a/extensions/php-language-features/tsconfig.json +++ b/extensions/php-language-features/tsconfig.json @@ -4,7 +4,10 @@ "outDir": "./out", "types": [ "node" - ] + ], + "typeRoots": [ + "./node_modules/@types" + ], }, "include": [ "src/**/*", diff --git a/extensions/references-view/tsconfig.json b/extensions/references-view/tsconfig.json index f0f7c00adf5..796a159a61c 100644 --- a/extensions/references-view/tsconfig.json +++ b/extensions/references-view/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/search-result/package-lock.json b/extensions/search-result/package-lock.json index 4fbe8b97ef8..f85d9e265e0 100644 --- a/extensions/search-result/package-lock.json +++ b/extensions/search-result/package-lock.json @@ -8,9 +8,29 @@ "name": "search-result", "version": "1.0.0", "license": "MIT", + "devDependencies": { + "@types/node": "^22.18.10" + }, "engines": { "vscode": "^1.39.0" } + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 6582db3e782..155ed6ae658 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -55,5 +55,8 @@ "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" + }, + "devDependencies": { + "@types/node": "^22.18.10" } } diff --git a/extensions/search-result/tsconfig.json b/extensions/search-result/tsconfig.json index f0f7c00adf5..796a159a61c 100644 --- a/extensions/search-result/tsconfig.json +++ b/extensions/search-result/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/simple-browser/preview-src/tsconfig.json b/extensions/simple-browser/preview-src/tsconfig.json index 72282fb0c7d..e8e5336a66b 100644 --- a/extensions/simple-browser/preview-src/tsconfig.json +++ b/extensions/simple-browser/preview-src/tsconfig.json @@ -7,6 +7,9 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/extensions/simple-browser/tsconfig.json b/extensions/simple-browser/tsconfig.json index bd370826678..60b13d71670 100644 --- a/extensions/simple-browser/tsconfig.json +++ b/extensions/simple-browser/tsconfig.json @@ -2,7 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [] + "types": [], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/terminal-suggest/package-lock.json b/extensions/terminal-suggest/package-lock.json index 239e60b0c25..2fb1083a7f4 100644 --- a/extensions/terminal-suggest/package-lock.json +++ b/extensions/terminal-suggest/package-lock.json @@ -8,9 +8,29 @@ "name": "terminal-suggest", "version": "1.0.1", "license": "MIT", + "devDependencies": { + "@types/node": "^22.18.10" + }, "engines": { "vscode": "^1.95.0" } + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index c99bd03e54f..2151c48ae6f 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -47,5 +47,8 @@ "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" + }, + "devDependencies": { + "@types/node": "^22.18.10" } } diff --git a/extensions/terminal-suggest/tsconfig.json b/extensions/terminal-suggest/tsconfig.json index f1439c32819..f7eeab9a794 100644 --- a/extensions/terminal-suggest/tsconfig.json +++ b/extensions/terminal-suggest/tsconfig.json @@ -2,13 +2,16 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "esModuleInterop": true, - "types": [ - "node" + "typeRoots": [ + "./node_modules/@types" ], + "esModuleInterop": true, // Needed to suppress warnings in upstream completions "noImplicitReturns": false, - "noUnusedParameters": false + "noUnusedParameters": false, + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/tunnel-forwarding/tsconfig.json b/extensions/tunnel-forwarding/tsconfig.json index 4769a9faec8..f6631c8c88d 100644 --- a/extensions/tunnel-forwarding/tsconfig.json +++ b/extensions/tunnel-forwarding/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "downlevelIteration": true, "types": [ "node" diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index c34a9ffb7bf..7300af07c43 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "esModuleInterop": true, "types": [ "node" diff --git a/extensions/vscode-api-tests/package-lock.json b/extensions/vscode-api-tests/package-lock.json index 204a2f14655..8e0b07da13a 100644 --- a/extensions/vscode-api-tests/package-lock.json +++ b/extensions/vscode-api-tests/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "MIT", "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-forge": "^1.3.11", "node-forge": "^1.3.1", @@ -20,10 +20,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 250ec7ae327..74c7eacd7e1 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -80,7 +80,9 @@ } ], "modes": [ - "agent", "ask", "edit" + "agent", + "ask", + "edit" ] }, { @@ -91,15 +93,15 @@ } ], "languageModelTools": [ - { - "name": "requires_confirmation_tool", - "toolReferenceName": "requires_confirmation_tool", - "displayName": "Requires Confirmation Tool", - "modelDescription": "A noop tool to trigger confirmation.", - "canBeReferencedInPrompt": true, - "icon": "$(files)", - "inputSchema": {} - } + { + "name": "requires_confirmation_tool", + "toolReferenceName": "requires_confirmation_tool", + "displayName": "Requires Confirmation Tool", + "modelDescription": "A noop tool to trigger confirmation.", + "canBeReferencedInPrompt": true, + "icon": "$(files)", + "inputSchema": {} + } ], "configuration": { "type": "object", @@ -266,7 +268,7 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-api-tests ./tsconfig.json" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-forge": "^1.3.11", "node-forge": "^1.3.1", diff --git a/extensions/vscode-api-tests/tsconfig.json b/extensions/vscode-api-tests/tsconfig.json index 3ef85d919ec..6cfdb070b4e 100644 --- a/extensions/vscode-api-tests/tsconfig.json +++ b/extensions/vscode-api-tests/tsconfig.json @@ -2,9 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [ - "node" - ] + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/extensions/vscode-colorize-perf-tests/tsconfig.json b/extensions/vscode-colorize-perf-tests/tsconfig.json index 7234fdfeb97..20f72eae51d 100644 --- a/extensions/vscode-colorize-perf-tests/tsconfig.json +++ b/extensions/vscode-colorize-perf-tests/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ] diff --git a/extensions/vscode-colorize-tests/tsconfig.json b/extensions/vscode-colorize-tests/tsconfig.json index 7234fdfeb97..20f72eae51d 100644 --- a/extensions/vscode-colorize-tests/tsconfig.json +++ b/extensions/vscode-colorize-tests/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ] diff --git a/extensions/vscode-test-resolver/tsconfig.json b/extensions/vscode-test-resolver/tsconfig.json index d1c0f9d9f50..087654f228a 100644 --- a/extensions/vscode-test-resolver/tsconfig.json +++ b/extensions/vscode-test-resolver/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ], From 016689b572ebf98e4ac8ae77e8a63f2ea529cbf9 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:05:52 -0700 Subject: [PATCH 1099/4355] Remove duplicate --- extensions/terminal-suggest/tsconfig.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/terminal-suggest/tsconfig.json b/extensions/terminal-suggest/tsconfig.json index f7eeab9a794..11a7532e161 100644 --- a/extensions/terminal-suggest/tsconfig.json +++ b/extensions/terminal-suggest/tsconfig.json @@ -8,10 +8,7 @@ "esModuleInterop": true, // Needed to suppress warnings in upstream completions "noImplicitReturns": false, - "noUnusedParameters": false, - "typeRoots": [ - "./node_modules/@types" - ] + "noUnusedParameters": false }, "include": [ "src/**/*", From 0e30f334b6901206efdf59a6c8a66c84344f6399 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 13 Oct 2025 11:12:07 -0700 Subject: [PATCH 1100/4355] fix the chord value for matching [Enter] --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 2c5837c51ba..fce78d79741 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1210,7 +1210,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Only prevent default if the keybinding exists AND matches plain Enter (no modifiers) if (chatSubmitKeybinding) { const chords = chatSubmitKeybinding.getDispatchChords(); - const isPlainEnter = chords.length === 1 && chords[0] === 'Enter'; + const isPlainEnter = chords.length === 1 && chords[0] === '[Enter]'; if (isPlainEnter) { // Also check if the action's precondition is met (e.g., inputHasText) From 162806006af24b5e593ee83ee6a7443dd3d9a4c4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:13:12 -0700 Subject: [PATCH 1101/4355] Update a few package-locks for mocha --- .../css-language-features/server/package-lock.json | 11 ++++++----- extensions/git/package-lock.json | 11 ++++++----- extensions/github-authentication/package-lock.json | 11 ++++++----- .../html-language-features/server/package-lock.json | 11 ++++++----- .../json-language-features/server/package-lock.json | 11 ++++++----- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/extensions/css-language-features/server/package-lock.json b/extensions/css-language-features/server/package-lock.json index bb1e339a985..91d89604285 100644 --- a/extensions/css-language-features/server/package-lock.json +++ b/extensions/css-language-features/server/package-lock.json @@ -15,7 +15,7 @@ "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -23,10 +23,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/git/package-lock.json b/extensions/git/package-lock.json index 4f119e2c3f8..45e3989e801 100644 --- a/extensions/git/package-lock.json +++ b/extensions/git/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/byline": "4.2.31", - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/picomatch": "2.3.0", "@types/which": "3.0.0" @@ -176,10 +176,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/github-authentication/package-lock.json b/extensions/github-authentication/package-lock.json index 60fbdfe141c..b9aa790d966 100644 --- a/extensions/github-authentication/package-lock.json +++ b/extensions/github-authentication/package-lock.json @@ -14,7 +14,7 @@ "vscode-tas-client": "^0.1.84" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-fetch": "^2.5.7" }, @@ -147,10 +147,11 @@ "license": "MIT" }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/html-language-features/server/package-lock.json b/extensions/html-language-features/server/package-lock.json index 2b3d422fb87..90b03405061 100644 --- a/extensions/html-language-features/server/package-lock.json +++ b/extensions/html-language-features/server/package-lock.json @@ -17,7 +17,7 @@ "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -25,10 +25,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/json-language-features/server/package-lock.json b/extensions/json-language-features/server/package-lock.json index f2ff4832170..f16745454d9 100644 --- a/extensions/json-language-features/server/package-lock.json +++ b/extensions/json-language-features/server/package-lock.json @@ -20,7 +20,7 @@ "vscode-json-languageserver": "bin/vscode-json-languageserver" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -28,10 +28,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", From 0fd0e53adb944f7efbebc7048696fa2df94e195e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 13 Oct 2025 19:03:55 +0200 Subject: [PATCH 1102/4355] Avoids use of variable before declaration --- .../widget/diffEditor/features/hideUnchangedRegionsFeature.ts | 2 +- .../browser/controller/inlineCompletionsController.ts | 2 +- .../browser/hintsWidget/inlineCompletionsHintsWidget.ts | 2 +- .../inlineCompletions/browser/view/ghostText/ghostTextView.ts | 2 +- .../browser/view/inlineEdits/inlineEditsViewProducer.ts | 2 +- .../contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts | 2 +- .../browser/inlineCompletionLanguageStatusBarContribution.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index 0f829bfdd9e..a55e05ee97e 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -32,7 +32,7 @@ import { IObservableViewZone, PlaceholderViewZone, ViewZoneOverlayWidget, applyO */ export class HideUnchangedRegionsFeature extends Disposable { private static readonly _breadcrumbsSourceFactory = observableValue<((textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource)>( - HideUnchangedRegionsFeature, () => ({ + this, () => ({ dispose() { }, getBreadcrumbItems(startRange, reader) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 5508ab07ba8..ce4ffa430a8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -42,7 +42,7 @@ import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; export class InlineCompletionsController extends Disposable { private static readonly _instances = new Set(); - public static hot = createHotClass(InlineCompletionsController); + public static hot = createHotClass(this); public static ID = 'editor.contrib.inlineCompletionsController'; /** diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts index adcfd26eb70..52b393c1949 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts @@ -118,7 +118,7 @@ const inlineSuggestionHintsNextIcon = registerIcon('inline-suggestion-hints-next const inlineSuggestionHintsPreviousIcon = registerIcon('inline-suggestion-hints-previous', Codicon.chevronLeft, localize('parameterHintsPreviousIcon', 'Icon for show previous parameter hint.')); export class InlineSuggestionHintsContentWidget extends Disposable implements IContentWidget { - public static readonly hot = createHotClass(InlineSuggestionHintsContentWidget); + public static readonly hot = createHotClass(this); private static _dropDownVisible = false; public static get dropDownVisible() { return this._dropDownVisible; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts index 88ce4ca138f..21235c7f9e0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts @@ -52,7 +52,7 @@ const GHOST_TEXT_CLASS_NAME = 'ghost-text'; export class GhostTextView extends Disposable { private readonly _isDisposed; private readonly _editorObs; - public static hot = createHotClass(GhostTextView); + public static hot = createHotClass(this); private _warningState; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts index 8cb79a3f028..caa4419eeea 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts @@ -21,7 +21,7 @@ import { InlineEditsView } from './inlineEditsView.js'; import { InlineEditTabAction } from './inlineEditsViewInterface.js'; export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This class is no longer a diff producer. Rename it or get rid of it - public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); + public static readonly hot = createHotClass(this); private readonly _editorObs: ObservableCodeEditor; diff --git a/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts b/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts index 12f1948bd42..16248f06f26 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts @@ -21,7 +21,7 @@ import type { AiStatsFeature } from './aiStatsFeature.js'; import './media.css'; export class AiStatsStatusBar extends Disposable { - public static readonly hot = createHotClass(AiStatsStatusBar); + public static readonly hot = createHotClass(this); constructor( private readonly _aiStatsFeature: AiStatsFeature, diff --git a/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletionLanguageStatusBarContribution.ts b/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletionLanguageStatusBarContribution.ts index 8177c0b0e52..45af71b0fb4 100644 --- a/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletionLanguageStatusBarContribution.ts +++ b/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletionLanguageStatusBarContribution.ts @@ -15,7 +15,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { ILanguageStatusService } from '../../../services/languageStatus/common/languageStatusService.js'; export class InlineCompletionLanguageStatusBarContribution extends Disposable implements IWorkbenchContribution { - public static readonly hot = createHotClass(InlineCompletionLanguageStatusBarContribution); + public static readonly hot = createHotClass(this); public static Id = 'vs.contrib.inlineCompletionLanguageStatusBarContribution'; public static readonly languageStatusBarDisposables = new Set(); From a357cc798b9d9ccbfb1679870ea3b2f3360650cf Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:34:07 -0700 Subject: [PATCH 1103/4355] Call out explicit list of types for terminal suggest --- extensions/terminal-suggest/tsconfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/terminal-suggest/tsconfig.json b/extensions/terminal-suggest/tsconfig.json index 11a7532e161..1265a62536f 100644 --- a/extensions/terminal-suggest/tsconfig.json +++ b/extensions/terminal-suggest/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "types": [ + "node" + ], "typeRoots": [ "./node_modules/@types" ], From e3c854fc90745241af42d9b371adda8c00e94036 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 13 Oct 2025 11:49:54 -0700 Subject: [PATCH 1104/4355] Hide mcp server auto start messaging in contributed chat sessions. --- .../workbench/contrib/chat/common/chatServiceImpl.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 024df8d11b8..db068ca9fad 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -865,11 +865,13 @@ export class ChatService extends Disposable implements IChatService { } completeResponseCreated(); - // Check if there are MCP servers requiring interaction and show message if not shown yet - const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token)); - if (!autostartResult.isEmpty) { - progressCallback([autostartResult]); - await autostartResult.wait(); + // MCP autostart: only run for native VS Code sessions (sidebar, new editors) but not for extension contributed sessions that have inputType set. + if (!model.inputType) { + const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token)); + if (!autostartResult.isEmpty) { + progressCallback([autostartResult]); + await autostartResult.wait(); + } } const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); From eb3c1d31a9cccf855cf8ecc3a8ed4a3ba66ccdea Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 13 Oct 2025 14:55:18 -0400 Subject: [PATCH 1105/4355] improve right prompt detection (#271158) --- .../commandDetection/promptInputModel.ts | 29 +++++++++++++++++-- .../commandDetection/promptInputModel.test.ts | 10 +++++++ .../rich/windows11_pwsh7_echo_3_times.ts | 4 +-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts index 15da82b2144..328f5f941ef 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts @@ -546,9 +546,9 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { // Retrieve the positions of all cells with the same style as `lastNonWhitespaceCell` const positionsWithGhostStyle = styleMap.get(this._getCellStyleAsString(lastNonWhitespaceCell)); if (positionsWithGhostStyle) { - // Ghost text must start at the cursor or one char after (e.g. a space), - // preventing right-prompt styles from being misdetected as ghost text. - if (positionsWithGhostStyle[0] > buffer.cursorX + 1) { + // Ghost text must start at the cursor or one char after (e.g. a space) + // To account for cursor movement, we also ensure there are not 5+ spaces preceding the ghost text position + if (positionsWithGhostStyle[0] > buffer.cursorX + 1 && this._isPositionRightPrompt(line, positionsWithGhostStyle[0])) { return -1; } // Ensure these positions are contiguous @@ -583,6 +583,29 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { return ghostTextIndex >= cursorIndex ? ghostTextIndex : -1; } + /** + * 5+ spaces preceding the position, following the command start, + * indicates that we're likely in a right prompt at the current position + */ + private _isPositionRightPrompt(line: IBufferLine, position: number): boolean { + let count = 0; + for (let i = position - 1; i >= this._commandStartX; i--) { + const cell = line.getCell(i); + // treat missing cell or whitespace-only cell as empty; reset count on first non-empty + if (!cell || cell.getChars().trim().length === 0) { + count++; + // If we've already found 5 consecutive empties we can early-return + if (count >= 5) { + return true; + } + } else { + // consecutive sequence broken + count = 0; + } + } + return false; + } + private _getCellStyleAsString(cell: IBufferCell): string { return `${cell.getFgColor()}${cell.getBgColor()}${cell.isBold()}${cell.isItalic()}${cell.isDim()}${cell.isUnderline()}${cell.isBlink()}${cell.isInverse()}${cell.isInvisible()}${cell.isStrikethrough()}${cell.isOverline()}${cell.getFgColorMode()}${cell.getBgColorMode()}`; } diff --git a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts index 2d0c801066e..f87ab1f0ce5 100644 --- a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts +++ b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts @@ -215,6 +215,9 @@ suite('PromptInputModel', () => { await writePromise('foo\x1b[38;2;255;0;0m bar\x1b[0m\x1b[4D'); await assertPromptInput('foo|[ bar]'); + + await writePromise('\x1b[2D'); + await assertPromptInput('f|oo[ bar]'); }); test('no ghost text when foreground color matches earlier text', async () => { await writePromise('$ '); @@ -425,6 +428,13 @@ suite('PromptInputModel', () => { await assertPromptInput(`find . -name test|`); }); }); + test('Does not detect right prompt as ghost text', async () => { + await writePromise('$ '); + fireCommandStart(); + await assertPromptInput('|'); + await writePromise('cmd' + ' '.repeat(6) + '\x1b[38;2;255;0;0mRP\x1b[0m\x1b[8D'); + await assertPromptInput('cmd|' + ' '.repeat(6) + 'RP'); + }); }); test('wide input (Korean)', async () => { diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts index 3e524eda4c3..5483ff1b48b 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/recordings/rich/windows11_pwsh7_echo_3_times.ts @@ -324,7 +324,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "echo b|" + "data": "echo b|[]" }, { "type": "output", @@ -491,7 +491,7 @@ export const events = [ }, { "type": "promptInputChange", - "data": "echo c|" + "data": "echo c|[]" }, { "type": "output", From ab54a72364f84dbd593eabae2d744c52495d4fc7 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:59:50 -0700 Subject: [PATCH 1106/4355] Pick up latest `@types/trusted-types` Should unblock #270041 --- package-lock.json | 11 ++++++----- package.json | 2 +- src/typings/vscode-globals-ttp.d.ts | 7 +++---- src/vs/amdX.ts | 4 +--- src/vs/base/browser/trustedTypes.ts | 6 ++++-- src/vs/workbench/workbench.web.main.ts | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0bda4675df5..f26fb7c358c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,7 @@ "@types/node": "22.x", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", "@types/wicg-file-system-access": "^2023.10.7", @@ -2155,10 +2155,11 @@ "dev": true }, "node_modules/@types/trusted-types": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", - "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", - "dev": true + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/tunnel": { "version": "0.0.3", diff --git a/package.json b/package.json index 1ce8f67d781..509fd3a40c7 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "@types/node": "22.x", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", "@types/wicg-file-system-access": "^2023.10.7", diff --git a/src/typings/vscode-globals-ttp.d.ts b/src/typings/vscode-globals-ttp.d.ts index b91080ec741..b79cf938c68 100644 --- a/src/typings/vscode-globals-ttp.d.ts +++ b/src/typings/vscode-globals-ttp.d.ts @@ -7,10 +7,9 @@ declare global { - var _VSCODE_WEB_PACKAGE_TTP: Pick, 'name' | 'createScriptURL'> | undefined; + var _VSCODE_WEB_PACKAGE_TTP: Pick | undefined; } // fake export to make global work -export { } +export { }; + diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index b2f9fd3fa91..374d4f19faf 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -38,9 +38,7 @@ class AMDModuleImporter { private readonly _defineCalls: DefineCall[] = []; private _state = AMDModuleImporterState.Uninitialized; - private _amdPolicy: Pick, 'name' | 'createScriptURL'> | undefined; + private _amdPolicy: Pick | undefined; constructor() { } diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts index 9610e7606a9..c7d977b505b 100644 --- a/src/vs/base/browser/trustedTypes.ts +++ b/src/vs/base/browser/trustedTypes.ts @@ -5,16 +5,18 @@ import { onUnexpectedError } from '../common/errors.js'; +type TrustedTypePolicyOptions = import('trusted-types/lib/index.d.ts').TrustedTypePolicyOptions; + export function createTrustedTypesPolicy( policyName: string, policyOptions?: Options, -): undefined | Pick, 'name' | Extract> { +): undefined | Pick> { interface IMonacoEnvironment { createTrustedTypesPolicy( policyName: string, policyOptions?: Options, - ): undefined | Pick, 'name' | Extract>; + ): undefined | Pick>; } // eslint-disable-next-line local/code-no-any-casts const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 383909739c6..79a78b6f9f0 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -73,7 +73,7 @@ } globalThis._VSCODE_FILE_ROOT = baseUrl; - const trustedTypesPolicy: Pick, 'name' | 'createScriptURL'> | undefined = require.getConfig().trustedTypesPolicy; + const trustedTypesPolicy: Pick | undefined = require.getConfig().trustedTypesPolicy; if (trustedTypesPolicy) { globalThis._VSCODE_WEB_PACKAGE_TTP = trustedTypesPolicy; } From d2af782f2e591618e95ff499aef1955fd3b89809 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:31:58 -0700 Subject: [PATCH 1107/4355] Fix terminal-suggest not running npm install --- build/npm/dirs.js | 1 + extensions/terminal-suggest/.npmrc | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 extensions/terminal-suggest/.npmrc diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 33c18196dd4..4965b73c4ed 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -42,6 +42,7 @@ const dirs = [ 'extensions/search-result', 'extensions/simple-browser', 'extensions/tunnel-forwarding', + 'extensions/terminal-suggest', 'extensions/typescript-language-features', 'extensions/vscode-api-tests', 'extensions/vscode-colorize-tests', diff --git a/extensions/terminal-suggest/.npmrc b/extensions/terminal-suggest/.npmrc new file mode 100644 index 00000000000..a9c57709666 --- /dev/null +++ b/extensions/terminal-suggest/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 From 79659fb54b87632cf064114fa78bbf5d62e2ca06 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:39:00 -0700 Subject: [PATCH 1108/4355] Remove old `downlevelIteration` compile option We're targeting modern es versions so I don't think we need this anymore --- extensions/debug-auto-launch/tsconfig.json | 1 - extensions/debug-server-ready/tsconfig.json | 1 - extensions/tunnel-forwarding/tsconfig.json | 1 - 3 files changed, 3 deletions(-) diff --git a/extensions/debug-auto-launch/tsconfig.json b/extensions/debug-auto-launch/tsconfig.json index bfcf873c749..7234fdfeb97 100644 --- a/extensions/debug-auto-launch/tsconfig.json +++ b/extensions/debug-auto-launch/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, "types": [ "node" ] diff --git a/extensions/debug-server-ready/tsconfig.json b/extensions/debug-server-ready/tsconfig.json index 9bf747283ca..c80fe0d4ca9 100644 --- a/extensions/debug-server-ready/tsconfig.json +++ b/extensions/debug-server-ready/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, "types": [ "node" ] diff --git a/extensions/tunnel-forwarding/tsconfig.json b/extensions/tunnel-forwarding/tsconfig.json index 4769a9faec8..efd9a54dbfa 100644 --- a/extensions/tunnel-forwarding/tsconfig.json +++ b/extensions/tunnel-forwarding/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, "types": [ "node" ] From fc83262282bd2fb5f9a106f85af4570583c4c177 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 13 Oct 2025 13:46:57 -0700 Subject: [PATCH 1109/4355] can use tool --- .../contrib/chat/browser/chatEditorInput.ts | 4 ++-- src/vs/workbench/contrib/chat/common/chatModel.ts | 8 +++++++- .../workbench/contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 15 ++++++++------- .../contrib/chat/test/common/chatModel.test.ts | 8 ++++---- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index df47ec3a0f7..f72ca1ce276 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -194,9 +194,9 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (typeof this.sessionId === 'string') { this.model = await this.chatService.getOrRestoreSession(this.sessionId) - ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, inputType); + ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); } else if (!this.options.target) { - this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, inputType); + this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: true, inputType: inputType }); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 369e5a82350..5c7970f9685 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1441,9 +1441,14 @@ export class ChatModel extends Disposable implements IChatModel { return this._initialLocation; } + private readonly _canUseTools: boolean = true; + get canUseTools(): boolean { + return this._canUseTools; + } + constructor( initialData: ISerializableChatData | IExportableChatData | undefined, - initialModelProps: { initialLocation: ChatAgentLocation; inputType?: string }, + initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; inputType?: string }, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @@ -1469,6 +1474,7 @@ export class ChatModel extends Disposable implements IChatModel { this._inputType = initialData?.inputType ?? initialModelProps.inputType; this._initialLocation = initialData?.initialLocation ?? initialModelProps.initialLocation; + this._canUseTools = initialModelProps.canUseTools; const lastResponse = observableFromEvent(this, this.onDidChange, () => this._requests.at(-1)?.response); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 828b59cfcb2..e60fe7a8ad5 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -729,7 +729,7 @@ export interface IChatService { isEnabled(location: ChatAgentLocation): boolean; hasSessions(): boolean; - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, inputType?: string): ChatModel; + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, options?: { canUseTools?: boolean; inputType?: string }): ChatModel; getSession(sessionId: string): IChatModel | undefined; getOrRestoreSession(sessionId: string): Promise; getPersistedSessionTitle(sessionId: string): string | undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index db068ca9fad..1c5d664f520 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -328,13 +328,13 @@ export class ChatService extends Disposable implements IChatService { await this._chatSessionStore.clearAllSessions(); } - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true, inputType?: string): ChatModel { + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true, options?: { canUseTools?: boolean; inputType?: string }): ChatModel { this.trace('startSession'); - return this._startSession(undefined, location, isGlobalEditingSession, token, inputType); + return this._startSession(undefined, location, isGlobalEditingSession, token, options); } - private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, inputType?: string): ChatModel { - const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, inputType }); + private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, options?: { canUseTools?: boolean; inputType?: string }): ChatModel { + const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, canUseTools: options?.canUseTools ?? true, inputType: options?.inputType }); if (location === ChatAgentLocation.Chat) { model.startEditingSession(isGlobalEditingSession); } @@ -400,7 +400,7 @@ export class ChatService extends Disposable implements IChatService { return undefined; } - const session = this._startSession(sessionData, sessionData.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); + const session = this._startSession(sessionData, sessionData.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None, { canUseTools: true }); const isTransferred = this.transferredSessionData?.sessionId === sessionId; if (isTransferred) { @@ -470,7 +470,8 @@ export class ChatService extends Disposable implements IChatService { const chatSessionType = parsed.chatSessionType; const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, CancellationToken.None); - const model = this._startSession(undefined, location, true, CancellationToken.None, chatSessionType); + // Contributed sessions do not use UI tools + const model = this._startSession(undefined, location, true, CancellationToken.None, { canUseTools: false, inputType: chatSessionType }); // Record mapping from internal model session id to external contributed chat session identity this._modelToExternalSession.set(model.sessionId, { chatSessionType, chatSessionId: parsed.sessionId }); if (!this._contentProviderSessionModels.has(chatSessionType)) { @@ -866,7 +867,7 @@ export class ChatService extends Disposable implements IChatService { completeResponseCreated(); // MCP autostart: only run for native VS Code sessions (sidebar, new editors) but not for extension contributed sessions that have inputType set. - if (!model.inputType) { + if (model.canUseTools) { const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token)); if (!autostartResult.isEmpty) { progressCallback([autostartResult]); diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 0c8ff8ebde0..5f5d94305fa 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -40,7 +40,7 @@ suite('ChatModel', () => { }); test('removeRequest', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat, canUseTools: true })); const text = 'hello'; model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -52,8 +52,8 @@ suite('ChatModel', () => { }); test('adoptRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.EditorInline })); - const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.EditorInline, canUseTools: true })); + const model2 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat, canUseTools: true })); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0); @@ -76,7 +76,7 @@ suite('ChatModel', () => { }); test('addCompleteRequest', async function () { - const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat })); + const model1 = testDisposables.add(instantiationService.createInstance(ChatModel, undefined, { initialLocation: ChatAgentLocation.Chat, canUseTools: true })); const text = 'hello'; const request1 = model1.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }, 0, undefined, undefined, undefined, undefined, undefined, undefined, true); From 1c390a83f20d872a7c3127fbea2aec6021cdb6ac Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 13 Oct 2025 13:48:02 -0700 Subject: [PATCH 1110/4355] Update src/vs/workbench/contrib/chat/browser/chatEditorInput.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index f72ca1ce276..bfeb8de21b7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -196,7 +196,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler this.model = await this.chatService.getOrRestoreSession(this.sessionId) ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); } else if (!this.options.target) { - this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: true, inputType: inputType }); + this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType, inputType: inputType }); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } From cc66fca78a9c12bb936534b7801ac2aecaf8b8ce Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Mon, 13 Oct 2025 14:06:28 -0700 Subject: [PATCH 1111/4355] Filter unsupported items from 'Add Context' quick pick (#270660) --- .../contrib/chat/browser/actions/chatContextActions.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index b7bfac015d0..40d8d46b6e9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -51,6 +51,9 @@ import { resizeImage } from '../imageUtils.js'; import { registerPromptActions } from '../promptSyntax/promptFileActions.js'; import { CHAT_CATEGORY } from './chatActions.js'; +// Schemes that should not be available for chat context attach +const UNSUPPORTED_CONTEXT_SCHEMES = new Set(['webview-panel', 'walkThrough', 'vscode-settings']); + export function registerChatContextActions() { registerAction2(AttachContextAction); registerAction2(AttachFileToChatAction); @@ -455,6 +458,12 @@ export class AttachContextAction extends Action2 { const commandService = accessor.get(ICommandService); const providerOptions: AnythingQuickAccessProviderRunOptions = { + filter: (pick) => { + if (isIQuickPickItemWithResource(pick) && pick.resource) { + return !UNSUPPORTED_CONTEXT_SCHEMES.has(pick.resource.scheme); + } + return true; + }, additionPicks, handleAccept: async (item: IQuickPickServicePickItem | IContextPickItemItem, isBackgroundAccept: boolean) => { From 05a1af230bb54e7722584e3dc049be2ab45521da Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 13 Oct 2025 14:43:22 -0700 Subject: [PATCH 1112/4355] Persist the setting in confirmation. --- .../browser/gotoLineQuickAccess.ts | 22 ++++++++++++++----- .../browser/workbench.contribution.ts | 6 +++++ .../quickaccess/gotoLineQuickAccess.ts | 9 ++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 191c1abe650..5d74d82c183 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -23,12 +23,20 @@ interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; - private zeroBasedOffset = false; + private _useZeroBasedOffset = false; constructor() { super({ canAcceptInBackground: true }); } + get useZeroBasedOffset() { + return this._useZeroBasedOffset; + } + + set useZeroBasedOffset(value: boolean) { + this._useZeroBasedOffset = value; + } + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line or offset."); @@ -60,9 +68,13 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // React to picker changes const updatePickerAndEditor = () => { - const position = this.parsePosition(editor, picker.value.trim().substring(AbstractGotoLineQuickAccessProvider.PREFIX.length)); + const inputText = picker.value.trim().substring(AbstractGotoLineQuickAccessProvider.PREFIX.length); + const position = this.parsePosition(editor, inputText); const label = this.getPickLabel(editor, position.lineNumber, position.column); + // Show toggle only when input text starts with '::'. + toggle.visible = inputText.startsWith(':'); + // Picker picker.items = [{ lineNumber: position.lineNumber, @@ -91,7 +103,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor const toggle = new Toggle({ title: localize('gotoLineToggle', "Use zero-based offset"), icon: Codicon.symbolNumber, - isChecked: this.zeroBasedOffset, + isChecked: this.useZeroBasedOffset, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) @@ -99,7 +111,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor disposables.add( toggle.onChange(() => { - this.zeroBasedOffset = !this.zeroBasedOffset; + this.useZeroBasedOffset = !this.useZeroBasedOffset; updatePickerAndEditor(); })); @@ -142,7 +154,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor if (model) { if (offset >= 0) { // If offset is 1-based, we need to convert to model's 0-based. - offset -= this.zeroBasedOffset ? 0 : 1; + offset -= this.useZeroBasedOffset ? 0 : 1; } else { // Offset from the end of the buffer offset += model.getValueLength(); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index a971e7e18c7..d049eee038f 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -512,6 +512,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), 'default': false }, + 'workbench.quickOpen.useZeroBasedOffset': { + 'type': 'boolean', + tags: ['experimental'], + 'description': localize('workbench.quickOpen.useZeroBasedOffset', "Controls whether 'Go To' character offset in Quick Open will use zero-based offset (default is one-based)."), + 'default': false + }, 'workbench.settings.openDefaultSettings': { 'type': 'boolean', 'description': localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 37963ed5fba..6bc88d6274d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -23,6 +23,7 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { + private static zeroBasedOffsetSetting = 'workbench.quickOpen.useZeroBasedOffset'; protected readonly onDidActiveTextEditorControlChange: Event; constructor( @@ -34,6 +35,14 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv this.onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; } + override get useZeroBasedOffset(): boolean { + return this.configurationService.getValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting); + } + + override set useZeroBasedOffset(value: boolean) { + this.configurationService.updateValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting, value); + } + private get configuration() { const editorConfig = this.configurationService.getValue().workbench?.editor; From 27057890da26ec96d2956242c2973ed3f04596eb Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 13 Oct 2025 14:47:46 -0700 Subject: [PATCH 1113/4355] revert the change in keybindingResolver --- .../keybinding/common/keybindingResolver.ts | 9 ++------- .../contrib/chat/browser/chatInputPart.ts | 14 +++----------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index e85eb5c70dd..f1592b35785 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { implies, ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService } from '../../contextkey/common/contextkey.js'; +import { implies, ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService, expressionsAreEqualWithConstantSubstitution } from '../../contextkey/common/contextkey.js'; import { ResolvedKeybindingItem } from './resolvedKeybindingItem.js'; //#region resolution-result @@ -103,12 +103,7 @@ export class KeybindingResolver { if (!defaultKb.when) { return false; } - - // Check if `when` is entirely included in `defaultKb.when` - // e.g. `when` is `a && b`, and `defaultKb.when` is `a && b && c` - // this means the removal `when` is more general than the `defaultKb.when` - // so the removal applies - if (!KeybindingResolver.whenIsEntirelyIncluded(defaultKb.when, when)) { + if (!expressionsAreEqualWithConstantSubstitution(when, defaultKb.when)) { return false; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index fce78d79741..f6777f6d9ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -50,7 +50,7 @@ import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/but import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; @@ -1213,18 +1213,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const isPlainEnter = chords.length === 1 && chords[0] === '[Enter]'; if (isPlainEnter) { - // Also check if the action's precondition is met (e.g., inputHasText) - const commandAction = MenuRegistry.getCommand(ChatSubmitAction.ID); - const preconditionMet = !commandAction?.precondition || - this.contextKeyService.contextMatchesRules(commandAction.precondition); - - // Only prevent default if both keybinding exists and precondition is met - if (preconditionMet) { - e.preventDefault(); - } + // Do NOT call stopPropagation() so the keybinding service can still process this event + e.preventDefault(); } } - // Do NOT call stopPropagation() so the keybinding service can still process this event } })); From 3e3ab629695a7b6645e3e3f0770b60bfd4c89f50 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 13 Oct 2025 14:49:54 -0700 Subject: [PATCH 1114/4355] Show offset-specific prompt when in offset mode. --- .../quickAccess/browser/gotoLineQuickAccess.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 5d74d82c183..28d7b1cc342 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -69,11 +69,12 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // React to picker changes const updatePickerAndEditor = () => { const inputText = picker.value.trim().substring(AbstractGotoLineQuickAccessProvider.PREFIX.length); + const inOffsetMode = inputText.startsWith(':'); const position = this.parsePosition(editor, inputText); - const label = this.getPickLabel(editor, position.lineNumber, position.column); + const label = this.getPickLabel(editor, position.lineNumber, position.column, inOffsetMode); // Show toggle only when input text starts with '::'. - toggle.visible = inputText.startsWith(':'); + toggle.visible = inOffsetMode; // Picker picker.items = [{ @@ -174,7 +175,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor }; } - private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined): string { + private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined, inOffsetMode: boolean): string { // Location valid: indicate this as picker label if (this.isValidLineNumber(editor, lineNumber)) { @@ -187,6 +188,12 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // Location invalid: show generic label const position = editor.getPosition() || { lineNumber: 1, column: 1 }; + + // When in offset mode, prompt for an offset. + if (inOffsetMode) { + return localize('gotoLineOffsetLabel', "Current Line: {0}, Character: {1}. Type a character offset to navigate to.", position.lineNumber, position.column); + } + const lineCount = this.lineCount(editor); if (lineCount > 1) { return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount); From a353cf58528263f2cd10f6551b46db551852bd2e Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 13 Oct 2025 15:03:24 -0700 Subject: [PATCH 1115/4355] Update toggle icon to symbolArray for now. --- .../editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 28d7b1cc342..ad30eabd328 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -103,7 +103,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // Add a toggle to switch between 1- and 0-based offsets. const toggle = new Toggle({ title: localize('gotoLineToggle', "Use zero-based offset"), - icon: Codicon.symbolNumber, + icon: Codicon.symbolArray, isChecked: this.useZeroBasedOffset, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), From f197aadde1089f9466334e1f4544463c0bb58a7d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:52:33 -0700 Subject: [PATCH 1116/4355] Fix output for microsoft-authentication For #271167 All out of extensions normally target `out`. `dist` is for webpack --- extensions/microsoft-authentication/tsconfig.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 58a583fb0f1..f9713b530cc 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -1,14 +1,10 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", + "outDir": "./out", "noFallthroughCasesInSwitch": true, "noUnusedLocals": false, - "outDir": "dist", - "resolveJsonModule": true, - "rootDir": "src", "skipLibCheck": true, - "sourceMap": true, "lib": [ "WebWorker" ] From 8882ed69e70b71a32cf1fbdef55f1140aa5052c1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Oct 2025 18:02:56 -0700 Subject: [PATCH 1117/4355] =?UTF-8?q?Make=20agent=20mode=20the=20real=20de?= =?UTF-8?q?fault=20=F0=9F=9A=80=20(#270592)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make agent mode the real default 🚀 Fix #268166 * Fix default mode * Fix --- src/vs/workbench/contrib/chat/browser/chat.ts | 2 ++ .../contrib/chat/browser/chatInputPart.ts | 24 +++++++++---------- .../contrib/chat/browser/chatQuick.ts | 20 +++++++++++----- .../contrib/chat/browser/chatWidget.ts | 11 +++++---- .../browser/inlineChatController.ts | 4 +++- .../inlineChat/browser/inlineChatWidget.ts | 2 ++ .../browser/inlineChatZoneWidget.ts | 2 ++ .../chat/browser/terminalChatWidget.ts | 4 +++- 8 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ba20ff26f9c..6f5294575ef 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -19,6 +19,7 @@ import { IWorkbenchLayoutService } from '../../../services/layout/browser/layout import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatAgentCommand, IChatAgentData } from '../common/chatAgents.js'; import { IChatResponseModel } from '../common/chatModel.js'; +import { IChatMode } from '../common/chatModes.js'; import { IParsedChatRequest } from '../common/chatParserTypes.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; import { IChatElicitationRequest, IChatLocationData, IChatSendRequestOptions } from '../common/chatService.js'; @@ -179,6 +180,7 @@ export interface IChatWidgetViewOptions { enableWorkingSet?: 'explicit' | 'implicit'; supportsChangingModes?: boolean; dndContainer?: HTMLElement; + defaultMode?: IChatMode; } export interface IChatViewViewContext { diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 65e9aaf5352..9755efed11d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -12,6 +12,7 @@ import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/button.js'; import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../base/common/actions.js'; import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; @@ -22,10 +23,11 @@ import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; -import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js'; +import { autorun, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { isEqual } from '../../../../base/common/resources.js'; import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; +import { assertType } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; @@ -68,6 +70,7 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { ResourceLabels } from '../../../browser/labels.js'; import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; +import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; @@ -75,7 +78,6 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd import { IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup } from '../common/chatService.js'; @@ -94,19 +96,17 @@ import { ChatAttachmentModel } from './chatAttachmentModel.js'; import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; import { IDisposableReference } from './chatContentParts/chatCollections.js'; import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; +import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; import { ChatDragAndDrop } from './chatDragAndDrop.js'; import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js'; import { ChatFollowups } from './chatFollowups.js'; import { ChatSelectedTools } from './chatSelectedTools.js'; -import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; import { IChatViewState } from './chatWidget.js'; import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js'; import { resizeImage } from './imageUtils.js'; import { IModelPickerDelegate, ModelPickerActionItem } from './modelPicker/modelPickerActionItem.js'; import { IModePickerDelegate, ModePickerActionItem } from './modelPicker/modePickerActionItem.js'; -import { assertType } from '../../../../base/common/types.js'; -import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; const $ = dom.$; @@ -118,7 +118,8 @@ export interface IChatInputStyles { listBackground: string; } -interface IChatInputPartOptions { +export interface IChatInputPartOptions { + defaultMode?: IChatMode; renderFollowups: boolean; renderStyle?: 'compact'; menus: { @@ -310,7 +311,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidChangeCurrentChatMode: Emitter; readonly onDidChangeCurrentChatMode: Event; - private readonly _currentModeObservable = observableValue('currentMode', ChatMode.Ask); + private readonly _currentModeObservable: ISettableObservable; public get currentModeKind(): ChatModeKind { const mode = this._currentModeObservable.get(); return mode.kind === ChatModeKind.Agent && !this.agentService.hasToolsAgent ? @@ -441,11 +442,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeCurrentLanguageModel = this._register(new Emitter()); this._onDidChangeCurrentChatMode = this._register(new Emitter()); this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; - this._currentModeObservable.set(ChatMode.Ask, undefined); this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); this._chatEditsActionsDisposables = this._register(new DisposableStore()); this._chatEditsDisposables = this._register(new DisposableStore()); this._attemptedWorkingSetEntriesCount = 0; + this._currentModeObservable = observableValue('currentMode', this.options.defaultMode ?? ChatMode.Agent); this._register(this.editorService.onDidActiveEditorChange(() => { this._indexOfLastOpenedContext = -1; @@ -798,13 +799,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const storageKey = this.getDefaultModeExperimentStorageKey(); const hasSetDefaultMode = this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false); if (!hasSetDefaultMode) { - const isFree = this.entitlementService.entitlement === ChatEntitlement.Free; - const defaultModeKey = isFree ? 'chat.defaultModeFree' : 'chat.defaultMode'; - const defaultLanguageModelKey = isFree ? 'chat.defaultLanguageModelFree' : 'chat.defaultLanguageModel'; const isAnonymous = this.entitlementService.anonymous; Promise.all([ - this.experimentService.getTreatment(defaultModeKey), - this.experimentService.getTreatment(defaultLanguageModelKey), + this.experimentService.getTreatment('chat.defaultMode'), + this.experimentService.getTreatment('chat.defaultLanguageModel'), ]).then(([defaultModeTreatment, defaultLanguageModelTreatment]) => { if (isAnonymous) { // be deterministic for anonymous users diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 1ccca58e6cf..8220b7357f8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -9,28 +9,29 @@ import { disposableTimeout } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { autorun } from '../../../../base/common/observable.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../base/common/observable.js'; import { Selection } from '../../../../editor/common/core/selection.js'; -import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; import { localize } from '../../../../nls.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; +import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; +import product from '../../../../platform/product/common/product.js'; import { IQuickInputService, IQuickWidget } from '../../../../platform/quickinput/common/quickInput.js'; import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from '../../../../platform/theme/common/colorRegistry.js'; -import product from '../../../../platform/product/common/product.js'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; +import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ChatModel, isCellTextEditOperation } from '../common/chatModel.js'; +import { ChatMode } from '../common/chatModes.js'; import { IParsedChatRequest } from '../common/chatParserTypes.js'; import { IChatProgress, IChatService } from '../common/chatService.js'; import { ChatAgentLocation } from '../common/constants.js'; import { IQuickChatOpenOptions, IQuickChatService, showChatView } from './chat.js'; import { ChatWidget } from './chatWidget.js'; -import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; export class QuickChatService extends Disposable implements IQuickChatService { readonly _serviceBrand: undefined; @@ -229,7 +230,14 @@ class QuickChat extends Disposable { ChatWidget, ChatAgentLocation.Chat, { isQuickChat: true }, - { autoScroll: true, renderInputOnTop: true, renderStyle: 'compact', menus: { inputSideToolbar: MenuId.ChatInputSide, telemetrySource: 'chatQuick' }, enableImplicitContext: true }, + { + autoScroll: true, + renderInputOnTop: true, + renderStyle: 'compact', + menus: { inputSideToolbar: MenuId.ChatInputSide, telemetrySource: 'chatQuick' }, + enableImplicitContext: true, + defaultMode: ChatMode.Ask + }, { listForeground: quickInputForeground, listBackground: quickInputBackground, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8e9db483ed5..f1172b9152b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -19,8 +19,8 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, thenIfNotDisposed, toDisposable } from '../../../../base/common/lifecycle.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, thenIfNotDisposed, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; import { autorun, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; @@ -53,13 +53,13 @@ import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/w import { EditorResourceAccessor } from '../../../../workbench/common/editor.js'; import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js'; import { ViewContainerLocation } from '../../../common/views.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { checkModeOption } from '../common/chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; import { IChatModel, IChatResponseModel } from '../common/chatModel.js'; import { IChatModeService } from '../common/chatModes.js'; @@ -84,7 +84,7 @@ import { ChatTreeItem, ChatViewId, IChatAcceptInputOptions, IChatAccessibilitySe import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; -import { ChatInputPart, IChatInputStyles } from './chatInputPart.js'; +import { ChatInputPart, IChatInputPartOptions, IChatInputStyles } from './chatInputPart.js'; import { ChatListDelegate, ChatListItemRenderer, IChatListItemTemplate, IChatRendererDelegate } from './chatListRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; import { ChatViewPane } from './chatViewPane.js'; @@ -685,7 +685,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const renderFollowups = this.viewOptions.renderFollowups ?? false; const renderStyle = this.viewOptions.renderStyle; this.createInput(this.container, { renderFollowups, renderStyle }); - this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Ask); + this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Agent); } } @@ -2015,7 +2015,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'compact' | 'minimal' }): void { - const commonConfig = { + const commonConfig: IChatInputPartOptions = { renderFollowups: options?.renderFollowups ?? true, renderStyle: options?.renderStyle === 'minimal' ? 'compact' : options?.renderStyle, menus: { @@ -2029,6 +2029,7 @@ export class ChatWidget extends Disposable implements IChatWidget { supportsChangingModes: this.viewOptions.supportsChangingModes, dndContainer: this.viewOptions.dndContainer, widgetViewKindTag: this.getWidgetViewKindTag(), + defaultMode: this.viewOptions.defaultMode }; if (this.viewModel?.editing) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d5cded15883..f3507ed1b11 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -52,6 +52,7 @@ import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { ChatMode } from '../../chat/common/chatModes.js'; import { IChatService } from '../../chat/common/chatService.js'; import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; @@ -1338,7 +1339,8 @@ export class InlineChatController2 implements IEditorContribution { enableWorkingSet: 'implicit', rendererOptions: { renderTextEditsAsSummary: _uri => true - } + }, + defaultMode: ChatMode.Ask }, { editor: this._editor, notebookEditor }, ); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 6b744fc22cb..3b321059d02 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -56,6 +56,7 @@ import { isResponseVM } from '../../chat/common/chatViewModel.js'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from '../common/inlineChat.js'; import { HunkInformation, Session } from './inlineChatSession.js'; import './media/inlineChat.css'; +import { ChatMode } from '../../chat/common/chatModes.js'; export interface InlineChatWidgetViewState { editorViewState: ICodeEditorViewState; @@ -164,6 +165,7 @@ export class InlineChatWidget { return true; }, dndContainer: this._elements.root, + defaultMode: ChatMode.Ask, ..._options.chatWidgetViewOptions }, { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 85a7c8b047b..024f3d9aa02 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -21,6 +21,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ILogService } from '../../../../platform/log/common/log.js'; import { IChatWidgetViewOptions } from '../../chat/browser/chat.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; +import { ChatMode } from '../../chat/common/chatModes.js'; import { isResponseVM } from '../../chat/common/chatViewModel.js'; import { INotebookEditor } from '../../notebook/browser/notebookBrowser.js'; import { ACTION_REGENERATE_RESPONSE, ACTION_REPORT_ISSUE, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_SIDE, MENU_INLINE_CHAT_WIDGET_SECONDARY, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js'; @@ -95,6 +96,7 @@ export class InlineChatZoneWidget extends ZoneWidget { renderDetectedCommandsWithRequest: true, ...options?.rendererOptions }, + defaultMode: ChatMode.Ask } }); this._disposables.add(this.widget); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 1e138d397fe..b531eb39c50 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -29,6 +29,7 @@ import { ITerminalInstance, type IXtermTerminal } from '../../../terminal/browse import { TerminalStickyScrollContribution } from '../../stickyScroll/browser/terminalStickyScrollContribution.js'; import './media/terminalChatWidget.css'; import { MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; +import { ChatMode } from '../../../chat/common/chatModes.js'; const enum Constants { HorizontalMargin = 10, @@ -141,7 +142,8 @@ export class TerminalChatWidget extends Disposable { telemetrySource: 'terminal-inline-chat', executeToolbar: MenuId.ChatExecute, inputSideToolbar: MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR, - } + }, + defaultMode: ChatMode.Ask } }, ); From d39007c3c22586a587d0f2ee5631abb1542fd766 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 13 Oct 2025 20:17:53 -0700 Subject: [PATCH 1118/4355] Revert default prompt text. --- .../editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index ad30eabd328..1e1989a454c 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -38,7 +38,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line or offset."); + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); picker.items = [{ label }]; picker.ariaLabel = label; From a3fc87930654143c8c775d2a1a791ac985cf3db2 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 13 Oct 2025 23:34:25 -0400 Subject: [PATCH 1119/4355] Add experimental setting to show all models (#271178) Co-authored-by: Zhichao Li <57812115+zhichli@users.noreply.github.com> --- src/vs/workbench/contrib/chat/common/languageModels.ts | 5 +++++ .../contrib/chat/test/common/languageModels.test.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index aeaebeda60b..b672e7f9dbb 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -14,6 +14,7 @@ import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -327,6 +328,7 @@ export class LanguageModelsService implements ILanguageModelsService { @ILogService private readonly _logService: ILogService, @IStorageService private readonly _storageService: IStorageService, @IContextKeyService _contextKeyService: IContextKeyService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService, ) { this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(_contextKeyService); @@ -418,6 +420,9 @@ export class LanguageModelsService implements ILanguageModelsService { lookupLanguageModel(modelIdentifier: string): ILanguageModelChatMetadata | undefined { const model = this._modelCache.get(modelIdentifier); + if (model && this._configurationService.getValue('chat.experimentalShowAllModels')) { + return { ...model, isUserSelectable: true }; + } if (model && this._modelPickerUserPreferences[modelIdentifier] !== undefined) { return { ...model, isUserSelectable: this._modelPickerUserPreferences[modelIdentifier] }; } diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts index 8eb2df0933d..2c7590b1569 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts @@ -18,6 +18,7 @@ import { ExtensionIdentifier } from '../../../../../platform/extensions/common/e import { TestChatEntitlementService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; import { Event } from '../../../../../base/common/event.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; suite('LanguageModels', function () { @@ -38,6 +39,7 @@ suite('LanguageModels', function () { new NullLogService(), new TestStorageService(), new MockContextKeyService(), + new TestConfigurationService(), new TestChatEntitlementService() ); From 79820a886d5b9ae27230c34940342e5edcc0c546 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 14 Oct 2025 16:59:25 +0900 Subject: [PATCH 1120/4355] chore: avoid reporting PendingMigrationError to error telemetry (#271252) --- src/vs/platform/telemetry/common/errorTelemetry.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/telemetry/common/errorTelemetry.ts b/src/vs/platform/telemetry/common/errorTelemetry.ts index 93c46089503..d7641e1c7c7 100644 --- a/src/vs/platform/telemetry/common/errorTelemetry.ts +++ b/src/vs/platform/telemetry/common/errorTelemetry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { binarySearch } from '../../../base/common/arrays.js'; -import { errorHandler, ErrorNoTelemetry } from '../../../base/common/errors.js'; +import { errorHandler, ErrorNoTelemetry, PendingMigrationError } from '../../../base/common/errors.js'; import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; import { safeStringify } from '../../../base/common/objects.js'; import { FileOperationError } from '../../files/common/files.js'; @@ -89,7 +89,11 @@ export default abstract class BaseErrorTelemetry { // If it's the no telemetry error it doesn't get logged // TOOD @lramos15 hacking in FileOperation error because it's too messy to adopt ErrorNoTelemetry. A better solution should be found - if (ErrorNoTelemetry.isErrorNoTelemetry(err) || err instanceof FileOperationError || (typeof err?.message === 'string' && err.message.includes('Unable to read file'))) { + // + // Explicitly filter out PendingMigrationError for https://github.com/microsoft/vscode/issues/250648#issuecomment-3394040431 + // We don't inherit from ErrorNoTelemetry to preserve the name used in reporting for exthostdeprecatedapiusage event. + // TODO(deepak1556): remove when PendingMigrationError is no longer needed. + if (ErrorNoTelemetry.isErrorNoTelemetry(err) || err instanceof FileOperationError || PendingMigrationError.is(err) || (typeof err?.message === 'string' && err.message.includes('Unable to read file'))) { return; } From 6311ddf0725237fd52beea8bae7134ba87ef6add Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Oct 2025 12:00:39 +0200 Subject: [PATCH 1121/4355] lines operations: refactor: replace a string with static variable --- .../contrib/linesOperations/browser/linesOperations.ts | 6 ++++-- .../contrib/debug/browser/debugEditorContribution.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index c5d6bdef41b..a8f8abdea01 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -638,9 +638,10 @@ class OutdentLinesAction extends EditorAction { } export class InsertLineBeforeAction extends EditorAction { + public static readonly ID = 'editor.action.insertLineBefore'; constructor() { super({ - id: 'editor.action.insertLineBefore', + id: InsertLineBeforeAction.ID, label: nls.localize2('lines.insertBefore', "Insert Line Above"), precondition: EditorContextKeys.writable, kbOpts: { @@ -662,9 +663,10 @@ export class InsertLineBeforeAction extends EditorAction { } export class InsertLineAfterAction extends EditorAction { + public static readonly ID = 'editor.action.insertLineAfter'; constructor() { super({ - id: 'editor.action.insertLineAfter', + id: InsertLineAfterAction.ID, label: nls.localize2('lines.insertAfter', "Insert Line Below"), precondition: EditorContextKeys.writable, kbOpts: { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 2cd76e98579..d2d5051ca28 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -54,6 +54,7 @@ import { Expression } from '../common/debugModel.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { InsertLineAfterAction } from '../../../../editor/contrib/linesOperations/browser/linesOperations.js'; const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added @@ -689,7 +690,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { }); } this.editor.setPosition(position); - return this.commandService.executeCommand('editor.action.insertLineAfter'); + return this.commandService.executeCommand(InsertLineAfterAction.ID); }; await insertLine(configurationsArrayPosition); From 98a6d519d76c2671b3a82a1b257935639a227fd4 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Oct 2025 12:01:23 +0200 Subject: [PATCH 1122/4355] inline completions controller: refactor: extract `commands` set to not create it every time --- .../controller/inlineCompletionsController.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index ce4ffa430a8..a2672963123 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -214,16 +214,16 @@ export class InlineCompletionsController extends Disposable { } })); + // These commands don't trigger onDidType. + const triggerCommands = new Set([ + CoreEditingCommands.Tab.id, + CoreEditingCommands.DeleteLeft.id, + CoreEditingCommands.DeleteRight.id, + inlineSuggestCommitId, + 'acceptSelectedSuggestion', + ]); this._register(this._commandService.onDidExecuteCommand((e) => { - // These commands don't trigger onDidType. - const commands = new Set([ - CoreEditingCommands.Tab.id, - CoreEditingCommands.DeleteLeft.id, - CoreEditingCommands.DeleteRight.id, - inlineSuggestCommitId, - 'acceptSelectedSuggestion', - ]); - if (commands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { + if (triggerCommands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { let noDelay = false; if (e.commandId === inlineSuggestCommitId) { noDelay = true; From ce2764a98c93ef6976a9bdc899f45cdb9dda5b8c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Oct 2025 12:02:30 +0200 Subject: [PATCH 1123/4355] inline completions controller: feat: trigger on `insertLineAfter/Before` commands --- .../browser/controller/inlineCompletionsController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index a2672963123..ac190701678 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -30,6 +30,7 @@ import { Range } from '../../../../common/core/range.js'; import { CursorChangeReason } from '../../../../common/cursorEvents.js'; import { ILanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js'; import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; +import { InsertLineAfterAction, InsertLineBeforeAction } from '../../../linesOperations/browser/linesOperations.js'; import { InlineSuggestionHintsContentWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js'; import { TextModelChangeRecorder } from '../model/changeRecorder.js'; import { InlineCompletionsModel } from '../model/inlineCompletionsModel.js'; @@ -221,6 +222,8 @@ export class InlineCompletionsController extends Disposable { CoreEditingCommands.DeleteRight.id, inlineSuggestCommitId, 'acceptSelectedSuggestion', + InsertLineAfterAction.ID, + InsertLineBeforeAction.ID, ]); this._register(this._commandService.onDidExecuteCommand((e) => { if (triggerCommands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { From 443cc8e89fddecff13335bc68cabd4c3b33d9757 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:35:48 +0800 Subject: [PATCH 1124/4355] align thinking with tool confirmation (#271282) --- .../chatThinkingContentPart.ts | 32 +++++++++---------- .../contrib/chat/browser/media/chat.css | 25 +++++---------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 25cb28515ba..8677e7354b9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { $, clearNode } from '../../../../../base/browser/dom.js'; +import * as dom from '../../../../../base/browser/dom.js'; import { IChatThinkingPart } from '../../common/chatService.js'; import { IChatContentPartRenderContext, IChatContentPart } from './chatContentParts.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; @@ -50,7 +51,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen private fixedScrollViewport: HTMLElement | undefined; private fixedContainer: HTMLElement | undefined; private headerButton: ButtonWithIcon | undefined; - private caret: HTMLElement | undefined; + private statusIcon: HTMLElement | undefined; private lastExtractedTitle: string | undefined; private hasMultipleItems: boolean = false; @@ -131,9 +132,11 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen const button = this.headerButton = this._register(new ButtonWithIcon(header, {})); button.label = this.defaultTitle; - button.icon = ThemeIcon.modify(Codicon.loading, 'spin'); - this.caret = $('.codicon.codicon-chevron-right.chat-thinking-fixed-caret'); - button.element.appendChild(this.caret); + button.icon = Codicon.chevronRight; + this.statusIcon = $('.chat-thinking-fixed-title-icon'); + const spinnerEl = dom.h(ThemeIcon.asCSSSelector(ThemeIcon.modify(Codicon.loading, 'spin'))); + this.statusIcon.appendChild(spinnerEl.root); + button.element.appendChild(this.statusIcon); this.fixedScrollViewport = this.wrapper; this.textContainer = $('.chat-thinking-item.markdown-content'); @@ -166,9 +169,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen } this.fixedCollapsed = collapsed; this.fixedContainer.classList.toggle('collapsed', collapsed); - if (this.caret) { - this.caret.classList.toggle('codicon-chevron-right', collapsed); - this.caret.classList.toggle('codicon-chevron-down', !collapsed); + if (this.headerButton) { + this.headerButton.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown; } if (this.fixedCollapsed && userInitiated) { const fixedScrollViewport = this.fixedScrollViewport ?? this.wrapper; @@ -278,7 +280,11 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen } if (this.headerButton) { this.headerButton.label = finalLabel; - this.headerButton.icon = Codicon.passFilled; + } + if (this.statusIcon && this.fixedContainer) { + this.fixedContainer.classList.add('finished'); + this.setFixedCollapsedState(true); + this.statusIcon.replaceChildren(dom.h(ThemeIcon.asCSSSelector(Codicon.check)).root); } this.currentTitle = finalLabel; @@ -296,14 +302,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen this.currentTitle = suffix; } - if (this.fixedScrollingMode) { - if (this.fixedContainer) { - this.fixedContainer.classList.add('finished'); - this.setFixedCollapsedState(true); - if (this.headerButton) { - this.headerButton.icon = Codicon.passFilled; - } - } + if (!this.fixedScrollingMode && this.statusIcon) { + this.statusIcon.replaceChildren(dom.h(ThemeIcon.asCSSSelector(Codicon.check)).root); } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d9cdc248ac7..71d8afdd5c6 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2909,8 +2909,8 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; gap: 4px; border: 1px solid var(--vscode-chat-requestBorder); - color: var(--vscode-descriptionForeground); - font-weight: 600; + color: var(--vscode-interactive-session-foreground); + opacity: 0.85; outline: none; margin: 3px; border: none; @@ -2920,20 +2920,14 @@ have to be updated for changes to the rules above, or to support more deeply nes outline-offset: -1px; } - .codicon.chat-thinking-fixed-caret { + .chat-thinking-fixed-title-icon { + font-size: 16px; margin-left: auto; - font-size: 12px; - } - - .chat-thinking-fixed-spinner { - font-size: 12px; - opacity: 0.8; + line-height: 0; } - /* temp icon while we wait for brain icon */ - .chat-thinking-fixed-complete { - font-size: 12px; - color: var(--vscode-testing-iconPassed); + .chat-thinking-fixed-title-icon .codicon-check { + color: var(--vscode-debugIcon-startForeground) !important; } .monaco-button { @@ -2942,7 +2936,7 @@ have to be updated for changes to the rules above, or to support more deeply nes width: 100%; border: none; outline: none; - padding-left: 10px; + padding: 3px 8px; &:hover { background: var(--vscode-toolbar-hoverBackground); @@ -2996,9 +2990,6 @@ have to be updated for changes to the rules above, or to support more deeply nes display: block; } - &.finished .chat-thinking-fixed-header .codicon-pass-filled { - display: none; - } } /* item and dot rendering */ From a2067fe643b9e75bd4d148dac5cfbd41f89f98ca Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Tue, 14 Oct 2025 15:32:50 +0300 Subject: [PATCH 1125/4355] fix: memory leak in sticky scroll (#271102) * fix: memory leak in sticky scroll * simplify --------- Co-authored-by: Benjamin Pasero --- .../editor/contrib/stickyScroll/browser/stickyScrollWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 8ab887c91ac..74235c230cc 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -51,7 +51,7 @@ const STICKY_IS_FOLDING_ICON_ATTR = 'data-sticky-is-folding-icon'; export class StickyScrollWidget extends Disposable implements IOverlayWidget { - private readonly _foldingIconStore = new DisposableStore(); + private readonly _foldingIconStore = this._register(new DisposableStore()); private readonly _rootDomNode: HTMLElement = document.createElement('div'); private readonly _lineNumbersDomNode: HTMLElement = document.createElement('div'); private readonly _linesDomNodeScrollable: HTMLElement = document.createElement('div'); @@ -118,7 +118,6 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { updateScrollLeftPosition(); this._updateWidgetWidth(); })); - this._register(this._foldingIconStore); updateScrollLeftPosition(); this._register(this._editor.onDidLayoutChange((e) => { @@ -283,6 +282,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { if (showFoldingControls !== 'mouseover') { return; } + this._foldingIconStore.clear(); this._foldingIconStore.add(dom.addDisposableListener(this._lineNumbersDomNode, dom.EventType.MOUSE_ENTER, () => { this._isOnGlyphMargin = true; this._setFoldingIconsVisibility(true); From 2f4b3543fc502fe4541ed56a5934213f8dde924a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Oct 2025 15:24:42 +0200 Subject: [PATCH 1126/4355] chat - fix bad setup disposable handling (#271308) * chat - fix bad setup disposable handling * run without setup when anonymous --- .github/CODENOTIFY | 4 +--- .../contrib/chat/browser/chatSetup.ts | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index d8591437bf6..21889363f06 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -99,9 +99,7 @@ src/vs/workbench/electron-browser/** @bpasero # Workbench Contributions src/vs/workbench/contrib/authentication/** @TylerLeonhardt src/vs/workbench/contrib/files/** @bpasero -src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens @bpasero -src/vs/workbench/contrib/chat/browser/chatInputPart.ts @bpasero -src/vs/workbench/contrib/chat/browser/chatWidget.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero src/vs/workbench/contrib/localization/** @TylerLeonhardt diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 6ca183d2c32..8bbac50ec42 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -249,7 +249,16 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } private async doInvoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise { - if (!this.context.state.installed || this.context.state.disabled || this.context.state.untrusted || this.context.state.entitlement === ChatEntitlement.Available || this.context.state.entitlement === ChatEntitlement.Unknown) { + if ( + !this.context.state.installed || // Extension not installed: run setup to install + this.context.state.disabled || // Extension disabled: run setup to enable + this.context.state.untrusted || // Workspace untrusted: run setup to ask for trust + this.context.state.entitlement === ChatEntitlement.Available || // Entitlement available: run setup to sign up + ( + this.context.state.entitlement === ChatEntitlement.Unknown && // Entitlement unknown: run setup to sign in / sign up + !this.chatEntitlementService.anonymous // unless anonymous access is enabled + ) + ) { return this.doInvokeWithSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService); } @@ -925,12 +934,15 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr return; // TODO@bpasero eventually remove this when we figured out extension activation issues } + const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload + const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); + + const renameProviderDisposables = markAsSingleton(new MutableDisposable()); + const updateRegistration = () => { // Agent + Tools { - const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload - const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); if (!context.state.hidden && !context.state.disabled) { // Default Agents (always, even if installed to allow for speedy requests right on startup) @@ -979,8 +991,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr // Rename Provider { - const renameProviderDisposables = markAsSingleton(new MutableDisposable()); - if (!context.state.installed && !context.state.hidden && !context.state.disabled) { if (!renameProviderDisposables.value) { renameProviderDisposables.value = DefaultNewSymbolNamesProvider.registerProvider(this.instantiationService, context, controller); From 33637aa81d91e5aca2aef223f5f3b10a4e8bd33b Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 14 Oct 2025 17:12:26 +0200 Subject: [PATCH 1127/4355] inline completions controller: feat: trigger on `'editor.action.nextMatchFindAction'` commands --- .../browser/controller/inlineCompletionsController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index ac190701678..97ae55daa2f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -30,6 +30,7 @@ import { Range } from '../../../../common/core/range.js'; 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 { InsertLineAfterAction, InsertLineBeforeAction } from '../../../linesOperations/browser/linesOperations.js'; import { InlineSuggestionHintsContentWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js'; import { TextModelChangeRecorder } from '../model/changeRecorder.js'; @@ -224,6 +225,7 @@ export class InlineCompletionsController extends Disposable { 'acceptSelectedSuggestion', InsertLineAfterAction.ID, InsertLineBeforeAction.ID, + FIND_IDS.NextMatchFindAction, ]); this._register(this._commandService.onDidExecuteCommand((e) => { if (triggerCommands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { From 524609d3bdcd17a676317e7a3d5020f1159e9095 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:13:14 +0200 Subject: [PATCH 1128/4355] Remove chat.checkpoints.showFileChanges from workspace settings (#271275) * Initial plan * Remove chat.checkpoints.showFileChanges from workspace settings Co-authored-by: justschen <54879025+justschen@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: justschen <54879025+justschen@users.noreply.github.com> --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f93aff6802..9176c8db754 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -215,7 +215,6 @@ // "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654 "editor.aiStats.enabled": true, // Team selfhosting on ai stats - "chat.checkpoints.showFileChanges": true, "chat.emptyState.history.enabled": true, "github.copilot.chat.advanced.taskTools.enabled": true, "chat.promptFilesRecommendations": { From 5ca043bbf8e567864e1b6f0a26bb91117de73a0e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Oct 2025 17:14:01 +0200 Subject: [PATCH 1129/4355] chat - reorder and rename configure dropdown (#270956) --- .../workbench/contrib/chat/browser/actions/chatActions.ts | 6 +++--- .../chat/browser/promptSyntax/attachInstructionsAction.ts | 6 +++--- .../contrib/chat/browser/promptSyntax/chatModeActions.ts | 4 ++-- .../contrib/chat/browser/promptSyntax/runPromptAction.ts | 2 +- .../contrib/chat/browser/tools/toolSetsContribution.ts | 2 +- src/vs/workbench/contrib/mcp/browser/mcpCommands.ts | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 03bf8449179..8a29c68125a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1566,7 +1566,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.generateInstructions', title: localize2('generateInstructions', "Generate Workspace Instructions File"), - shortTitle: localize2('generateInstructions.short', "Generate Agent Instructions"), + shortTitle: localize2('generateInstructions.short', "Generate Chat Instructions"), category: CHAT_CATEGORY, icon: Codicon.sparkle, f1: true, @@ -1574,7 +1574,7 @@ export function registerChatActions() { menu: { id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), - order: 13, + order: 11, group: '1_level' } }); @@ -1624,7 +1624,7 @@ Update \`.github/copilot-instructions.md\` for the user, then ask for feedback o id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), order: 15, - group: '2_configure' + group: '3_configure' } }); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index 426dfe690ad..b1596dff6fa 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -140,7 +140,7 @@ class ManageInstructionsFilesAction extends Action2 { super({ id: CONFIGURE_INSTRUCTIONS_ACTION_ID, title: localize2('configure-instructions', "Configure Instructions..."), - shortTitle: localize2('configure-instructions.short', "Instructions"), + shortTitle: localize2('configure-instructions.short', "Chat Instructions"), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -148,8 +148,8 @@ class ManageInstructionsFilesAction extends Action2 { menu: { id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), - order: 11, - group: '0_level' + order: 10, + group: '1_level' } }); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts index e9140e5a5d8..4126bfc1b5c 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts @@ -64,7 +64,7 @@ class ManageModeAction extends ConfigModeActionImpl { super({ id: CONFIGURE_MODES_ACTION_ID, title: localize2('configure-modes', "Configure Chat Modes..."), - shortTitle: localize('configure-modes.short', "Modes"), + shortTitle: localize('configure-modes.short', "Chat Modes"), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -73,7 +73,7 @@ class ManageModeAction extends ConfigModeActionImpl { { id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), - order: 12, + order: 10, group: '0_level' } ] diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts index 6148b8f20d0..4d8ee257b2d 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts @@ -255,7 +255,7 @@ class ManagePromptFilesAction extends Action2 { menu: { id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), - order: 10, + order: 11, group: '0_level' }, }); diff --git a/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts b/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts index 2900c238db7..7d0bfe9a8ed 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/toolSetsContribution.ts @@ -327,7 +327,7 @@ export class ConfigureToolSets extends Action2 { id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.equals('view', ChatViewId), order: 11, - group: '0_level' + group: '2_level' }, }); } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index eccabee943a..f238ef01c4e 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -900,8 +900,8 @@ MenuRegistry.appendMenuItem(CHAT_CONFIG_MENU_ID, { title: localize2('mcp.servers', "MCP Servers") }, when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), - order: 14, - group: '0_level' + order: 10, + group: '2_level' }); abstract class OpenMcpResourceCommand extends Action2 { From f13a27eac84b143f6ff61cbd4274d87e8ff3afdc Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 14 Oct 2025 08:58:41 -0700 Subject: [PATCH 1130/4355] chore: fix tag versions (#271166) --- cgmanifest.json | 15 +++++++++------ extensions/docker/cgmanifest.json | 7 ++++--- extensions/java/cgmanifest.json | 5 +++-- extensions/terminal-suggest/cgmanifest.json | 3 ++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/cgmanifest.json b/cgmanifest.json index dc33f707d98..199532ab318 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -516,7 +516,8 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "f8fe6858549f75a4b4e9633abf39dd2038dbf496" + "commitHash": "f8fe6858549f75a4b4e9633abf39dd2038dbf496", + "tag": "22.19.0" } }, "isOnlyProductionDependency": true, @@ -528,7 +529,8 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "9a2b4f84be2f4bcc468a63ef93520e60790b8f3c" + "commitHash": "9a2b4f84be2f4bcc468a63ef93520e60790b8f3c", + "tag": "37.6.0" } }, "isOnlyProductionDependency": true, @@ -587,12 +589,12 @@ "git": { "name": "spdlog original", "repositoryUrl": "https://github.com/gabime/spdlog", - "commitHash": "4fba14c79f356ae48d6141c561bf9fd7ba33fabd" + "commitHash": "7e635fca68d014934b4af8a1cf874f63989352b7" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "0.14.0" + "version": "1.12.0" }, { "component": { @@ -634,12 +636,13 @@ "git": { "name": "ripgrep", "repositoryUrl": "https://github.com/BurntSushi/ripgrep", - "commitHash": "973de50c9ef451da2cfcdfa86f2b2711d8d6ff48" + "commitHash": "af6b6c543b224d348a8876f0c06245d9ea7929c5", + "tag": "13.0.0" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "0.10.0" + "version": "13.0.0" }, { "name": "@vscode/win32-app-container-tokens", diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index 8462de7dd72..ddc1abe35f2 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -6,13 +6,14 @@ "git": { "name": "language-docker", "repositoryUrl": "https://github.com/moby/moby", - "commitHash": "c2029cb2574647e4bc28ed58486b8e85883eedb9" + "commitHash": "c2029cb2574647e4bc28ed58486b8e85883eedb9", + "tag": "28.0.0" } }, "license": "Apache-2.0", "description": "The file syntaxes/docker.tmLanguage was included from https://github.com/moby/moby/blob/master/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage.", - "version": "0.0.0" + "version": "28.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index ecfeb0eb668..a62f6cdd1aa 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,7 +6,8 @@ "git": { "name": "redhat-developer/vscode-java", "repositoryUrl": "https://github.com/redhat-developer/vscode-java", - "commitHash": "f09b712f5d6d6339e765f58c8dfab3f78a378183" + "commitHash": "f09b712f5d6d6339e765f58c8dfab3f78a378183", + "tag": "1.26.0" } }, "license": "MIT", @@ -48,4 +49,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/terminal-suggest/cgmanifest.json b/extensions/terminal-suggest/cgmanifest.json index ead3479e667..410da24da16 100644 --- a/extensions/terminal-suggest/cgmanifest.json +++ b/extensions/terminal-suggest/cgmanifest.json @@ -88,7 +88,8 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/zsh-users/zsh", - "commitHash": "435cb1b748ce1f2f5c38edc1d64f4ee2424f9b3a" + "commitHash": "435cb1b748ce1f2f5c38edc1d64f4ee2424f9b3a", + "tag": "5.9" } }, "version": "5.9", From 0e24f4d9f43c3d0cae0e3111c97aae242fe8a1c3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 14 Oct 2025 12:06:56 -0400 Subject: [PATCH 1131/4355] Add telemetry to model picker (#271320) --- .../modelPicker/modelPickerActionItem.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts index dfb757db94c..a094206450b 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts @@ -20,6 +20,7 @@ import { IKeybindingService } from '../../../../../platform/keybinding/common/ke import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../common/modelPicker/modelPickerWidget.js'; import { ManageModelsAction } from '../actions/manageModelsActions.js'; import { IActionProvider } from '../../../../../base/browser/ui/dropdown/dropdown.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; export interface IModelPickerDelegate { readonly onDidChangeModel: Event; @@ -28,7 +29,20 @@ export interface IModelPickerDelegate { getModels(): ILanguageModelChatMetadataAndIdentifier[]; } -function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate): IActionWidgetDropdownActionProvider { +type ChatModelChangeClassification = { + owner: 'lramos15'; + comment: 'Reporting when the model picker is switched'; + fromModel?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The previous chat model' }; + toModel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The new chat model' }; +}; + +type ChatModelChangeEvent = { + fromModel: string | undefined; + toModel: string; +}; + + +function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate, telemetryService: ITelemetryService): IActionWidgetDropdownActionProvider { return { getActions: () => { return delegate.getModels().map(model => { @@ -43,6 +57,10 @@ function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate): I tooltip: model.metadata.tooltip ?? model.metadata.name, label: model.metadata.name, run: () => { + telemetryService.publicLog2('chat.modelChange', { + fromModel: delegate.getCurrentModel()?.identifier, + toModel: model.identifier + }); delegate.setModel(model); } } satisfies IActionWidgetDropdownAction; @@ -109,6 +127,7 @@ export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { @ICommandService commandService: ICommandService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService ) { // Modify the original action with a different label and make it show the current model const actionWithLabel: IAction = { @@ -119,7 +138,7 @@ export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { }; const modelPickerActionWidgetOptions: Omit = { - actionProvider: modelDelegateToWidgetActionsProvider(delegate), + actionProvider: modelDelegateToWidgetActionsProvider(delegate, telemetryService), actionBarActionProvider: getModelPickerActionBarActionProvider(commandService, chatEntitlementService) }; From 5167c1405940137015fdccf38e94f8dddaf3a418 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Oct 2025 18:17:45 +0200 Subject: [PATCH 1132/4355] chat - properly apply height to last element in list (#271322) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index f1172b9152b..41b543415f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2600,6 +2600,7 @@ export class ChatWidget extends Disposable implements IChatWidget { layout(height: number, width: number): void { width = Math.min(width, 950); + const heightUpdated = this.bodyDimension && this.bodyDimension.height !== height; this.bodyDimension = new dom.Dimension(width, height); const layoutHeight = this._dynamicMessageLayoutData?.enabled ? this._dynamicMessageLayoutData.maxHeight : height; @@ -2616,12 +2617,16 @@ export class ChatWidget extends Disposable implements IChatWidget { const inputHeight = this.inputPart.inputPartHeight; const chatTodoListWidgetHeight = this.chatTodoListWidget.height; const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight - 2; + const lastItem = this.viewModel?.getItems().at(-1); const contentHeight = Math.max(0, height - inputHeight - chatTodoListWidgetHeight); if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal') { this.listContainer.style.removeProperty('--chat-current-response-min-height'); } else { this.listContainer.style.setProperty('--chat-current-response-min-height', contentHeight * .75 + 'px'); + if (heightUpdated && lastItem) { + this.tree.updateElementHeight(lastItem, undefined); + } } this.tree.layout(contentHeight, width); @@ -2640,7 +2645,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.renderer.layout(width); - const lastItem = this.viewModel?.getItems().at(-1); const lastResponseIsRendering = isResponseVM(lastItem) && lastItem.renderData; if (lastElementVisible && (!lastResponseIsRendering || checkModeOption(this.input.currentModeKind, this.viewOptions.autoScroll))) { this.scrollToEnd(); From e176814a4983758aecfee548950ba844538736b5 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:19:40 +0800 Subject: [PATCH 1133/4355] fix already in transaction error (#271237) * fix already in transaction error * add back listener for header clicking * only fire when button is pressed --- .../chatContentParts/chatThinkingContentPart.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 8677e7354b9..1f25a906707 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -91,14 +91,12 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen if (header) { header.remove(); this.domNode.classList.add('chat-thinking-no-outer-header'); - this._onDidChangeHeight.fire(); } } else if (this.fixedScrollingMode) { const header = this.domNode.querySelector('.chat-used-context-label'); if (header) { header.remove(); this.domNode.classList.add('chat-thinking-no-outer-header', 'chat-thinking-fixed-mode'); - this._onDidChangeHeight.fire(); } this.currentTitle = this.defaultTitle; } @@ -145,7 +143,10 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen this.fixedContainer.appendChild(header); this.fixedContainer.appendChild(this.wrapper); - this._register(button.onDidClick(() => this.setFixedCollapsedState(!this.fixedCollapsed, true))); + this._register(button.onDidClick(() => { + this.setFixedCollapsedState(!this.fixedCollapsed, true); + this._onDidChangeHeight.fire(); + })); if (this.currentThinkingValue) { this.renderMarkdown(this.currentThinkingValue); @@ -178,7 +179,6 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen fixedScrollViewport.scrollTop = fixedScrollViewport.scrollHeight; } } - this._onDidChangeHeight.fire(); } private createThinkingItemContainer(): void { @@ -196,12 +196,12 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen if (this.headerButton) { this.headerButton.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown; } - this._onDidChangeHeight.fire(); }; - const toggle = () => setPerItemCollapsedState(!body.classList.contains('hidden')); - - this._register(button.onDidClick(() => toggle())); + this._register(button.onDidClick(() => { + setPerItemCollapsedState(!body.classList.contains('hidden')); + this._onDidChangeHeight.fire(); + })); itemWrapper.appendChild(header); itemWrapper.appendChild(body); From 68182d58e86ed519faaefbe06953c299a5e17fda Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:34:53 -0700 Subject: [PATCH 1134/4355] Revert some extra changes --- eslint.config.js | 9 ++------- src/typings/vscode-globals-ttp.d.ts | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c5750c967b2..6c65c69287b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1033,11 +1033,7 @@ export default tseslint.config( { 'target': 'src/vs/base/~', 'restrictions': [ - 'vs/base/~', - { - 'when': 'hasBrowser', - 'pattern': 'trusted-types/**' - }, // Typings + 'vs/base/~' ] }, { @@ -1373,8 +1369,7 @@ export default tseslint.config( { 'target': 'src/vs/amdX.ts', 'restrictions': [ - 'vs/base/common/*', - 'trusted-types/**', // Typings + 'vs/base/common/*' ] }, { diff --git a/src/typings/vscode-globals-ttp.d.ts b/src/typings/vscode-globals-ttp.d.ts index f561252c89d..b79cf938c68 100644 --- a/src/typings/vscode-globals-ttp.d.ts +++ b/src/typings/vscode-globals-ttp.d.ts @@ -5,8 +5,6 @@ // AMD2ESM migration relevant -import type { TrustedTypePolicy } from 'trusted-types/lib/index.js'; - declare global { var _VSCODE_WEB_PACKAGE_TTP: Pick | undefined; From 5942e4a5f92e92003e34ea1a25797ea891cf2e2e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:37:01 -0700 Subject: [PATCH 1135/4355] Add assertion --- src/vs/base/browser/domSanitize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 8d3b1aebc63..4653d0e3338 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -330,7 +330,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine return dompurify.sanitize(untrusted, { ...resolvedConfig, RETURN_TRUSTED_TYPE: true - }); + }) as TrustedHTML; } } finally { dompurify.removeAllHooks(); From 23b8cc999b05fd20b4a4674a4762911d792c6b4c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:43:09 -0700 Subject: [PATCH 1136/4355] Fix trusted types --- src/vs/base/browser/domSanitize.ts | 2 +- src/vs/base/browser/trustedTypes.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/domSanitize.ts b/src/vs/base/browser/domSanitize.ts index 4653d0e3338..942bf6e8315 100644 --- a/src/vs/base/browser/domSanitize.ts +++ b/src/vs/base/browser/domSanitize.ts @@ -330,7 +330,7 @@ function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefine return dompurify.sanitize(untrusted, { ...resolvedConfig, RETURN_TRUSTED_TYPE: true - }) as TrustedHTML; + }) as unknown as TrustedHTML; // Cast from lib TrustedHTML to global TrustedHTML } } finally { dompurify.removeAllHooks(); diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts index 31ce554b71c..c7d977b505b 100644 --- a/src/vs/base/browser/trustedTypes.ts +++ b/src/vs/base/browser/trustedTypes.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { TrustedTypePolicy } from 'trusted-types/lib/index.js'; import { onUnexpectedError } from '../common/errors.js'; type TrustedTypePolicyOptions = import('trusted-types/lib/index.d.ts').TrustedTypePolicyOptions; From 1fa20447af440bed5e5a36c30146345e32b4b235 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 14 Oct 2025 18:45:43 +0200 Subject: [PATCH 1137/4355] Only forward copilot events to the copilot extension (#271330) --- .../telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts index aebe5cff8d7..2d3784af1cb 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts @@ -8,7 +8,7 @@ import { EditSuggestionId } from '../../../../../../editor/common/textModelEditS import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; import { TelemetryTrustedValue } from '../../../../../../platform/telemetry/common/telemetryUtils.js'; -import { DataChannelForwardingTelemetryService } from '../../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; +import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../../../../../platform/dataChannel/browser/forwardingTelemetryService.js'; import { IAiEditTelemetryService, IEditTelemetryCodeAcceptedData, IEditTelemetryCodeSuggestedData } from './aiEditTelemetryService.js'; export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { @@ -86,6 +86,8 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { modeId: data.modeId, modelId: new TelemetryTrustedValue(data.modelId), applyCodeBlockSuggestionId: data.applyCodeBlockSuggestionId as unknown as string, + + ...forwardToChannelIf(isCopilotLikeExtension(data.source?.extensionId)), }); return suggestionId; @@ -166,6 +168,8 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { modelId: new TelemetryTrustedValue(data.modelId), applyCodeBlockSuggestionId: data.applyCodeBlockSuggestionId as unknown as string, acceptanceMethod: data.acceptanceMethod, + + ...forwardToChannelIf(isCopilotLikeExtension(data.source?.extensionId)), }); } } From 0267af2c7d9e038f3a3696db86c6a5b7a4f1751c Mon Sep 17 00:00:00 2001 From: Selva Ganesh M Date: Tue, 14 Oct 2025 22:16:46 +0530 Subject: [PATCH 1138/4355] fix(editor)(#261780): transform UPPER_CASE to PascalCase (#262959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(editor)(#261780): transform UPPER_CASE to PascalCase - Updated PascalCaseAction to handle ALL_CAPS words correctly - Ensures FOO_BAR -> FooBar and FOO BAR A -> FooBarA - Added tests for ALL_CAPS and mixed cases * Improve PascalCase conversion with Unicode support - Switch to \p{Lu} with 'mu' flags for broader uppercase detection - Should better handle non-ASCII scripts beyond basic Latin - Added representative tests: École, ΩmegaCase, ДомТест --- .../browser/linesOperations.ts | 18 +++++++-- .../test/browser/linesOperations.test.ts | 37 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index c5d6bdef41b..6e109c76e47 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1268,6 +1268,7 @@ export class CamelCaseAction extends AbstractCaseAction { export class PascalCaseAction extends AbstractCaseAction { public static wordBoundary = new BackwardsCompatibleRegExp('[_ \\t-]', 'gm'); public static wordBoundaryToMaintain = new BackwardsCompatibleRegExp('(?<=\\.)', 'gm'); + public static upperCaseWordMatcher = new BackwardsCompatibleRegExp('^\\p{Lu}+$', 'mu'); constructor() { super({ @@ -1280,19 +1281,28 @@ export class PascalCaseAction extends AbstractCaseAction { protected _modifyText(text: string, wordSeparators: string): string { const wordBoundary = PascalCaseAction.wordBoundary.get(); const wordBoundaryToMaintain = PascalCaseAction.wordBoundaryToMaintain.get(); + const upperCaseWordMatcher = PascalCaseAction.upperCaseWordMatcher.get(); - if (!wordBoundary || !wordBoundaryToMaintain) { + if (!wordBoundary || !wordBoundaryToMaintain || !upperCaseWordMatcher) { // cannot support this return text; } const wordsWithMaintainBoundaries = text.split(wordBoundaryToMaintain); - const words = wordsWithMaintainBoundaries.map((word: string) => word.split(wordBoundary)).flat(); - return words.map((word: string) => word.substring(0, 1).toLocaleUpperCase() + word.substring(1)) - .join(''); + const words = wordsWithMaintainBoundaries.map(word => word.split(wordBoundary)).flat(); + + return words.map(word => { + const normalizedWord = word.charAt(0).toLocaleUpperCase() + word.slice(1); + const isAllCaps = normalizedWord.length > 1 && upperCaseWordMatcher.test(normalizedWord); + if (isAllCaps) { + return normalizedWord.charAt(0) + normalizedWord.slice(1).toLocaleLowerCase(); + } + return normalizedWord; + }).join(''); } } + export class KebabCaseAction extends AbstractCaseAction { public static isSupported(): boolean { diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index e3d8a23c129..5560f78f70d 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -1208,6 +1208,12 @@ suite('Editor Contrib - Line Operations', () => { 'Capital_Snake_Case', 'parseHTML4String', 'Kebab-Case', + 'FOO_BAR', + 'FOO BAR A', + 'xML_HTTP-reQUEsT', + 'ÉCOLE', + 'ΩMEGA_CASE', + 'ДОМ_ТЕСТ', ], {}, (editor) => { const model = editor.getModel()!; const pascalCaseAction = new PascalCaseAction(); @@ -1266,6 +1272,37 @@ suite('Editor Contrib - Line Operations', () => { executeAction(pascalCaseAction, editor); assert.strictEqual(model.getValueInRange(new Selection(9, 1, 10, 11)), 'ParseHTML4String\nKebabCase'); assertSelection(editor, new Selection(9, 1, 10, 10)); + + editor.setSelection(new Selection(11, 1, 11, 8)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(11), 'FooBar'); + assertSelection(editor, new Selection(11, 1, 11, 7)); + + editor.setSelection(new Selection(12, 1, 12, 10)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(12), 'FooBarA'); + assertSelection(editor, new Selection(12, 1, 12, 8)); + + editor.setSelection(new Selection(13, 1, 13, 17)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(13), 'XmlHttpReQUEsT'); + assertSelection(editor, new Selection(13, 1, 13, 15)); + + editor.setSelection(new Selection(14, 1, 14, 6)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(14), 'École'); + assertSelection(editor, new Selection(14, 1, 14, 6)); + + editor.setSelection(new Selection(15, 1, 15, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(15), 'ΩmegaCase'); + assertSelection(editor, new Selection(15, 1, 15, 10)); + + editor.setSelection(new Selection(16, 1, 16, 9)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(16), 'ДомТест'); + assertSelection(editor, new Selection(16, 1, 16, 8)); + } ); }); From f3fb28734505cb6f446b31557aa109e8bf221813 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 14 Oct 2025 09:57:32 -0700 Subject: [PATCH 1139/4355] used lookupKeybindings instead of lookupKeybinding() --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index f6777f6d9ad..72d17daf28a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1205,16 +1205,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (e.keyCode === KeyCode.Enter && !hasModifierKeys(e)) { // Check if ChatSubmitAction has a keybinding for plain Enter in the current context // This respects user's custom keybindings that disable the submit action - const chatSubmitKeybinding = this.keybindingService.lookupKeybinding(ChatSubmitAction.ID, this.contextKeyService); - - // Only prevent default if the keybinding exists AND matches plain Enter (no modifiers) - if (chatSubmitKeybinding) { - const chords = chatSubmitKeybinding.getDispatchChords(); + for (const keybinding of this.keybindingService.lookupKeybindings(ChatSubmitAction.ID)) { + const chords = keybinding.getDispatchChords(); const isPlainEnter = chords.length === 1 && chords[0] === '[Enter]'; - if (isPlainEnter) { // Do NOT call stopPropagation() so the keybinding service can still process this event e.preventDefault(); + break; } } } From a007b0870dddcb971501047f61031f59ffbd348c Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 14 Oct 2025 10:03:04 -0700 Subject: [PATCH 1140/4355] Updated JDoc comment on prompt for quick pick. (#271349) --- src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts b/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts index 2049cd5ff5f..52b04a34c83 100644 --- a/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts +++ b/src/vscode-dts/vscode.proposed.quickPickPrompt.d.ts @@ -10,6 +10,7 @@ declare module 'vscode' { export interface QuickPick extends QuickInput { /** * An optional prompt text providing some ask or explanation to the user. + * Shown below the input box and above the quick pick items. */ prompt: string | undefined; } @@ -17,6 +18,7 @@ declare module 'vscode' { export interface QuickPickOptions { /** * An optional prompt text providing some ask or explanation to the user. + * Shown below the input box and above the quick pick items. */ prompt?: string; } From 8f2c73dbcac58cf955aadcbcd19524e4398ce694 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:06:03 -0700 Subject: [PATCH 1141/4355] Remove one more extra trusted type import --- src/vs/amdX.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index 9744eaf2051..374d4f19faf 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { TrustedTypePolicy } from 'trusted-types/lib/index.js'; import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath, Schemas, VSCODE_AUTHORITY } from './base/common/network.js'; import * as platform from './base/common/platform.js'; import { IProductConfiguration } from './base/common/product.js'; From d22e62803f7850381179d5113347954f29965c54 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Oct 2025 10:38:44 -0700 Subject: [PATCH 1142/4355] Terminal suggest addon should always multiply line height config (#270827) --- .../suggest/browser/terminalSuggestAddon.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index f900b9b808d..567624f5a2c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -29,7 +29,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ISimpleSuggestWidgetFontInfo } from '../../../../services/suggest/browser/simpleSuggestWidgetRenderer.js'; import { ITerminalConfigurationService } from '../../../terminal/browser/terminal.js'; -import { GOLDEN_LINE_HEIGHT_RATIO, MINIMUM_LINE_HEIGHT } from '../../../../../editor/common/config/fontInfo.js'; +import { GOLDEN_LINE_HEIGHT_RATIO } from '../../../../../editor/common/config/fontInfo.js'; import { TerminalCompletionModel } from './terminalCompletionModel.js'; import { TerminalCompletionItem, TerminalCompletionItemKind, type ITerminalCompletion } from './terminalCompletionItem.js'; import { localize } from '../../../../../nls.js'; @@ -718,17 +718,16 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest const letterSpacing: number = font.letterSpacing; const fontWeight: string = this._configurationService.getValue('editor.fontWeight'); - if (lineHeight <= 1) { - lineHeight = GOLDEN_LINE_HEIGHT_RATIO * fontSize; - } else if (lineHeight < MINIMUM_LINE_HEIGHT) { - // Values too small to be line heights in pixels are in ems. - lineHeight = lineHeight * fontSize; - } + // Unlike editor suggestions, line height in terminal is always multiplied to the font size. + // Make sure that we still enforce a minimum line height to avoid content from being clipped. + // See https://github.com/microsoft/vscode/issues/255851 + lineHeight = lineHeight * fontSize; // Enforce integer, minimum constraints lineHeight = Math.round(lineHeight); - if (lineHeight < MINIMUM_LINE_HEIGHT) { - lineHeight = MINIMUM_LINE_HEIGHT; + const minTerminalLineHeight = GOLDEN_LINE_HEIGHT_RATIO * fontSize; + if (lineHeight < minTerminalLineHeight) { + lineHeight = minTerminalLineHeight; } const fontInfo = { From 8df16c735f696305ad053b241b81229e266a845f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:58:39 -0700 Subject: [PATCH 1143/4355] Fix web build Temporarily reverting change to unblock web build --- extensions/markdown-language-features/tsconfig.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index 0e7a865e1f5..77399c62ad6 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -2,9 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "typeRoots": [ - "./node_modules/@types" - ], "skipLibCheck": true }, "include": [ From 359fa23b39bb37e5157e99c2bc5fc81088dd8431 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 14 Oct 2025 15:12:48 -0400 Subject: [PATCH 1144/4355] First round of caching of the model picker (#271376) --- .../browser/actions/chatExecuteActions.ts | 1 - .../contrib/chat/browser/chatInputPart.ts | 39 ++++++++----------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 684524f887f..4dc621a3369 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -413,7 +413,6 @@ class OpenModelPickerAction extends Action2 { when: ContextKeyExpr.and( ChatContextKeys.lockedToCodingAgent.negate(), - ChatContextKeys.languageModelsAreUserSelectable, ContextKeyExpr.or( ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat), ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditorInline), diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 9755efed11d..567f17128a1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -510,8 +510,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this.languageModelsService.onDidChangeLanguageModels(() => { // We've changed models and the current one is no longer available. Select a new one - const selectedModel = this._currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this._currentLanguageModel.identifier) : undefined; - if (this._currentLanguageModel && (!selectedModel || !selectedModel.isUserSelectable)) { + const selectedModel = this._currentLanguageModel ? this.getModels().find(m => m.identifier === this._currentLanguageModel?.identifier) : undefined; + if (this._currentLanguageModel && (!selectedModel || !selectedModel.metadata.isUserSelectable)) { this.setCurrentLanguageModelToDefault(); } })); @@ -555,20 +555,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } private initSelectedModel() { - let persistedSelection = this.storageService.get(this.getSelectedModelStorageKey(), StorageScope.APPLICATION); - if (persistedSelection && persistedSelection.startsWith('github.copilot-chat/')) { - // Convert the persisted selection to make it backwards comptabile with the old LM API. TODO @lramos15 - Remove this after a bit - persistedSelection = persistedSelection.replace('github.copilot-chat/', 'copilot/'); - this.storageService.store(this.getSelectedModelStorageKey(), persistedSelection, StorageScope.APPLICATION, StorageTarget.USER); - } + const persistedSelection = this.storageService.get(this.getSelectedModelStorageKey(), StorageScope.APPLICATION); const persistedAsDefault = this.storageService.getBoolean(this.getSelectedModelIsDefaultStorageKey(), StorageScope.APPLICATION, persistedSelection === 'copilot/gpt-4.1'); if (persistedSelection) { - const model = this.languageModelsService.lookupLanguageModel(persistedSelection); + const model = this.getModels().find(m => m.identifier === persistedSelection); if (model) { // Only restore the model if it wasn't the default at the time of storing or it is now the default - if (!persistedAsDefault || model.isDefault) { - this.setCurrentLanguageModel({ metadata: model, identifier: persistedSelection }); + if (!persistedAsDefault || model.metadata.isDefault) { + this.setCurrentLanguageModel(model); this.checkModelSupported(); } } else { @@ -698,22 +693,20 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } private getModels(): ILanguageModelChatMetadataAndIdentifier[] { - const models = this.languageModelsService.getLanguageModelIds() - .map(modelId => ({ identifier: modelId, metadata: this.languageModelsService.lookupLanguageModel(modelId)! })) - .filter(entry => entry.metadata?.isUserSelectable && this.modelSupportedForDefaultAgent(entry)); + const cachedModels = this.storageService.getObject('chat.cachedLanguageModels', StorageScope.APPLICATION, []); + let models = this.languageModelsService.getLanguageModelIds() + .map(modelId => ({ identifier: modelId, metadata: this.languageModelsService.lookupLanguageModel(modelId)! })); + if (models.length === 0 || models.some(m => m.metadata.isDefault) === false) { + models = cachedModels; + } else { + this.storageService.store('chat.cachedLanguageModels', models, StorageScope.APPLICATION, StorageTarget.MACHINE); + } models.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); - return models; + return models.filter(entry => entry.metadata?.isUserSelectable && this.modelSupportedForDefaultAgent(entry)); } private setCurrentLanguageModelToDefault() { - const defaultLanguageModelId = this.languageModelsService.getLanguageModelIds().find(id => this.languageModelsService.lookupLanguageModel(id)?.isDefault); - const hasUserSelectableLanguageModels = this.languageModelsService.getLanguageModelIds().find(id => { - const model = this.languageModelsService.lookupLanguageModel(id); - return model?.isUserSelectable; - }); - const defaultModel = hasUserSelectableLanguageModels && defaultLanguageModelId ? - { metadata: this.languageModelsService.lookupLanguageModel(defaultLanguageModelId)!, identifier: defaultLanguageModelId } : - undefined; + const defaultModel = this.getModels().find(m => m.metadata.isDefault); if (defaultModel) { this.setCurrentLanguageModel(defaultModel); } From 98ad0f55dbfd0239a1c0510a0529020fbd3072ae Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:14:28 -0700 Subject: [PATCH 1145/4355] Proper fix for missing node types in web build Using `path` in web for these cases is ok (but not ideal) because webpack shims it out --- .../package-lock.json | 18 ++++++++++++++++++ .../markdown-language-features/package.json | 1 + .../src/languageFeatures/linkUpdater.ts | 6 +++--- .../tsconfig.browser.json | 4 +--- .../markdown-language-features/tsconfig.json | 3 +++ 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index f83258f3751..af0abe0ad6a 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -26,6 +26,7 @@ "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", + "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", @@ -209,6 +210,16 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz", @@ -577,6 +588,13 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-jsonrpc": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 27689d6bd99..0e38bae746c 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -783,6 +783,7 @@ "@types/dompurify": "^3.0.5", "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", + "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", diff --git a/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts index 50a4d90c570..5d42a033842 100644 --- a/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts +++ b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; import * as picomatch from 'picomatch'; import * as vscode from 'vscode'; import { TextDocumentEdit } from 'vscode-languageclient'; +import { Utils } from 'vscode-uri'; import { MdLanguageClient } from '../client/client'; import { Delayer } from '../util/async'; import { noopToken } from '../util/cancellation'; @@ -137,7 +137,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable { const choice = await vscode.window.showInformationMessage( newResources.length === 1 - ? vscode.l10n.t("Update Markdown links for '{0}'?", path.basename(newResources[0].fsPath)) + ? vscode.l10n.t("Update Markdown links for '{0}'?", Utils.basename(newResources[0])) : this._getConfirmMessage(vscode.l10n.t("Update Markdown links for the following {0} files?", newResources.length), newResources), { modal: true, }, rejectItem, acceptItem, alwaysItem, neverItem); @@ -197,7 +197,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable { const paths = [start]; paths.push(''); - paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath))); + paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => Utils.basename(r))); if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { diff --git a/extensions/markdown-language-features/tsconfig.browser.json b/extensions/markdown-language-features/tsconfig.browser.json index e4c0db0fd10..dbacbb22fdf 100644 --- a/extensions/markdown-language-features/tsconfig.browser.json +++ b/extensions/markdown-language-features/tsconfig.browser.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.json", - "compilerOptions": { - "types": [] - }, + "compilerOptions": {}, "exclude": [ "./src/test/**" ] diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index 77399c62ad6..0e7a865e1f5 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "skipLibCheck": true }, "include": [ From 80163d7e4213724f61bf2ab605603865400043ca Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 14 Oct 2025 13:09:30 -0700 Subject: [PATCH 1146/4355] mcp: fix invalid 'activating extensions' state after autostart is already done (#271389) Closes #271317 --- .../chatMcpServersInteractionContentPart.ts | 13 ++++++------- src/vs/workbench/contrib/mcp/common/mcpService.ts | 14 +++++++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index cc945f903c6..96359dbf9cc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -68,16 +68,15 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements } private updateForState(state: IAutostartResult): void { - // Handle working progress state - if (state.working && !this.workingProgressPart) { + if (!state.working) { + this.workingProgressPart?.domNode.remove(); + this.workingProgressPart = undefined; + this.showSpecificServersScheduler.cancel(); + } else if (!this.workingProgressPart) { if (!this.showSpecificServersScheduler.isScheduled()) { this.showSpecificServersScheduler.schedule(); } - } else if (!state.working && this.workingProgressPart) { - this.workingProgressPart.domNode.remove(); - this.workingProgressPart = undefined; - this.showSpecificServersScheduler.cancel(); - } else if (state.working && this.workingProgressPart) { + } else if (this.workingProgressPart) { this.updateDetailedProgress(state); } diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index b65817d4898..96014024bc9 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -85,7 +85,8 @@ export class McpService extends Disposable implements IMcpService { } private async _autostart(autoStartConfig: McpAutoStartValue, state: ISettableObservable, token: CancellationToken) { - await this.activateCollections(); + await this._activateCollections(); + if (token.isCancellationRequested) { return; } @@ -143,10 +144,7 @@ export class McpService extends Disposable implements IMcpService { } public async activateCollections(): Promise { - const collections = await this._mcpRegistry.discoverCollections(); - const collectionIds = new Set(collections.map(c => c.id)); - - this.updateCollectedServers(); + const collectionIds = await this._activateCollections(); // Discover any newly-collected servers with unknown tools const todo: Promise[] = []; @@ -162,6 +160,12 @@ export class McpService extends Disposable implements IMcpService { await Promise.all(todo); } + private async _activateCollections() { + const collections = await this._mcpRegistry.discoverCollections(); + this.updateCollectedServers(); + return new Set(collections.map(c => c.id)); + } + public updateCollectedServers() { const prefixGenerator = new McpPrefixGenerator(); const definitions = this._mcpRegistry.collections.get().flatMap(collectionDefinition => From 213c722f8664338bb0e0dae60fb5ebda1b87d1f3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 14 Oct 2025 17:30:36 -0400 Subject: [PATCH 1147/4355] Send first request to selected model (#271398) * Send first request to selected model * Remove console logs --- src/vs/workbench/api/common/extHostLanguageModels.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 1889789fc4c..dce627ed19a 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -345,8 +345,8 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { const model = this._localModels.get(modelId); if (!model) { - // model gone? is this an error on us? - return; + // model gone? is this an error on us? Try to resolve model again + return (await this.selectLanguageModels(extension, { id: modelId }))[0]; } // make sure auth information is correct diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 8bbac50ec42..69135c53f4b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -378,7 +378,8 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { await chatService.resendRequest(requestModel, { ...widget?.getModeRequestOptions(), - modeInfo + modeInfo, + userSelectedModelId: widget?.input.currentLanguageModel }); } From 3da68ef571537dc954ef5f3411c469c2cd486e88 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:42:49 -0700 Subject: [PATCH 1148/4355] use full associated resource (#271394) * use full associated resource * refactor * fix derived class --- .../notebook/common/notebookEditorInput.ts | 29 ++++++++++++++----- .../replNotebook/browser/replEditorInput.ts | 8 +++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index f6c4b05d618..bb25e50d878 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -8,7 +8,7 @@ import { GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions, EditorInput import { EditorInput } from '../../../common/editor/editorInput.js'; import { INotebookService, SimpleNotebookProviderInfo } from './notebookService.js'; import { URI } from '../../../../base/common/uri.js'; -import { isEqual, joinPath } from '../../../../base/common/resources.js'; +import { isEqual, toLocalResource } from '../../../../base/common/resources.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { INotebookEditorModelResolverService } from './notebookEditorModelResolverService.js'; @@ -31,6 +31,8 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js'; import { ICustomEditorLabelService } from '../../../services/editor/common/customEditorLabelService.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { IPathService } from '../../../services/path/common/pathService.js'; export interface NotebookEditorInputOptions { startDirty?: boolean; @@ -71,7 +73,9 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { @IExtensionService extensionService: IExtensionService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService + @ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IPathService private readonly pathService: IPathService ) { super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService); this._defaultDirtyState = !!options.startDirty; @@ -207,7 +211,10 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return undefined; } - const pathCandidate = this.hasCapability(EditorInputCapabilities.Untitled) ? await this._suggestName(provider, this.labelService.getUriBasenameLabel(this.resource)) : this.editorModelReference.object.resource; + const pathCandidate = this.hasCapability(EditorInputCapabilities.Untitled) + ? await this._suggestName(provider) + : this.editorModelReference.object.resource; + let target: URI | undefined; if (this.editorModelReference.object.hasAssociatedFilePath()) { target = pathCandidate; @@ -241,8 +248,13 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return await this.editorModelReference.object.saveAs(target); } - private async _suggestName(provider: NotebookProviderInfo, suggestedFilename: string) { - // guess file extensions + private async _suggestName(provider: NotebookProviderInfo) { + const resource = this.ensureProviderExtension(provider); + const remoteAuthority = this.environmentService.remoteAuthority; + return toLocalResource(resource, remoteAuthority, this.pathService.defaultUriScheme); + } + + private ensureProviderExtension(provider: NotebookProviderInfo) { const firstSelector = provider.selectors[0]; let selectorStr = firstSelector && typeof firstSelector === 'string' ? firstSelector : undefined; if (!selectorStr && firstSelector) { @@ -252,17 +264,18 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } } + const resource = this.resource; if (selectorStr) { const matches = /^\*\.([A-Za-z_-]*)$/.exec(selectorStr); if (matches && matches.length > 1) { const fileExt = matches[1]; - if (!suggestedFilename.endsWith(fileExt)) { - return joinPath(await this._fileDialogService.defaultFilePath(), suggestedFilename + '.' + fileExt); + if (!resource.path.endsWith(fileExt)) { + return resource.with({ path: resource.path + '.' + fileExt }); } } } - return joinPath(await this._fileDialogService.defaultFilePath(), suggestedFilename); + return resource; } // called when users rename a notebook document diff --git a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts index 271af8eac54..af08dd64faa 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts @@ -26,6 +26,8 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { localize } from '../../../../nls.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { IPathService } from '../../../services/path/common/pathService.js'; const replTabIcon = registerIcon('repl-editor-label-icon', Codicon.debugLineByLine, localize('replEditorLabelIcon', 'Icon of the REPL editor label.')); @@ -52,9 +54,11 @@ export class ReplEditorInput extends NotebookEditorInput implements ICompositeNo @ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService, @IInteractiveHistoryService public readonly historyService: IInteractiveHistoryService, @ITextModelService private readonly _textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IPathService pathService: IPathService ) { - super(resource, undefined, 'jupyter-notebook', {}, _notebookService, _notebookModelResolverService, _fileDialogService, labelService, fileService, filesConfigurationService, extensionService, editorService, textResourceConfigurationService, customEditorLabelService); + super(resource, undefined, 'jupyter-notebook', {}, _notebookService, _notebookModelResolverService, _fileDialogService, labelService, fileService, filesConfigurationService, extensionService, editorService, textResourceConfigurationService, customEditorLabelService, environmentService, pathService); this.isScratchpad = resource.scheme === 'untitled' && configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; this.label = label ?? this.createEditorLabel(resource); } From 9116007eee6eaf55be5c1bafe4c417fea5c371b6 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 15 Oct 2025 06:12:05 +0800 Subject: [PATCH 1149/4355] finalize `languageModelDataPart` and tools api (#265537) * finalize datapart and tools api * remove test fo languagemodel part 2 * delete languagemodeldata part, move to thinking for now * bump version of chatprovider * update api comments * add aliases for languyagemodelToolresult and part * Add to impl * fix some finalization commentx * remove extra instance check Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @mjbvz Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> * Apply suggestion from @mjbvz Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> * Apply suggestion from @mjbvz Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> * Apply suggestion from @mjbvz Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> * Apply suggestion from @mjbvz Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Matt Bierner <12821956+mjbvz@users.noreply.github.com> --- .../common/extensionsApiProposals.ts | 6 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostLanguageModelTools.ts | 4 +- .../api/common/extHostTypeConverters.ts | 75 ++++++-- src/vs/workbench/api/common/extHostTypes.ts | 43 ++--- .../api/test/browser/extHostTypes.test.ts | 17 -- src/vscode-dts/vscode.d.ts | 67 ++++++- ...scode.proposed.chatParticipantPrivate.d.ts | 2 +- ...vscode.proposed.languageModelDataPart.d.ts | 164 ------------------ ...de.proposed.languageModelThinkingPart.d.ts | 62 +++++++ 10 files changed, 203 insertions(+), 239 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index f415759ae6c..4cc3d85620b 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -48,7 +48,7 @@ const _allApiProposals = { }, chatParticipantPrivate: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts', - version: 10 + version: 11 }, chatProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', @@ -255,10 +255,6 @@ const _allApiProposals = { languageModelCapabilities: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelCapabilities.d.ts', }, - languageModelDataPart: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts', - version: 3 - }, languageModelSystem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts', }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index cfe5c39b099..f72d478be71 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1892,7 +1892,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelChatMessage: extHostTypes.LanguageModelChatMessage, LanguageModelChatMessage2: extHostTypes.LanguageModelChatMessage2, LanguageModelToolResultPart: extHostTypes.LanguageModelToolResultPart, - LanguageModelToolResultPart2: extHostTypes.LanguageModelToolResultPart2, + LanguageModelToolResultPart2: extHostTypes.LanguageModelToolResultPart, LanguageModelTextPart: extHostTypes.LanguageModelTextPart, LanguageModelTextPart2: extHostTypes.LanguageModelTextPart, LanguageModelPartAudience: extHostTypes.LanguageModelPartAudience, diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 40acc9265f1..f5ab2f61e83 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -129,7 +129,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape }, token); const dto: Dto = result instanceof SerializableObjectWithBuffers ? result.value : result; - return typeConvert.LanguageModelToolResult2.to(revive(dto)); + return typeConvert.LanguageModelToolResult.to(revive(dto)); } finally { this._tokenCountFuncs.delete(callId); } @@ -224,7 +224,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape throw new CancellationError(); } - return typeConvert.LanguageModelToolResult2.from(extensionResult, item.extension); + return typeConvert.LanguageModelToolResult.from(extensionResult, item.extension); } private async getModel(modelId: string, extension: IExtensionDescription): Promise { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index c7b06c813bb..6571d8f86d8 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2322,9 +2322,11 @@ export namespace LanguageModelChatMessage { if (c.type === 'text') { return new LanguageModelTextPart(c.value, c.audience); } else if (c.type === 'tool_result') { - const content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[] = coalesce(c.value.map(part => { + const content: (LanguageModelTextPart | LanguageModelPromptTsxPart | LanguageModelDataPart)[] = coalesce(c.value.map(part => { if (part.type === 'text') { return new types.LanguageModelTextPart(part.value, part.audience); + } else if (part.type === 'data') { + return new types.LanguageModelDataPart(part.data.buffer, part.mimeType); } else if (part.type === 'prompt_tsx') { return new types.LanguageModelPromptTsxPart(part.value); } else { @@ -2333,8 +2335,9 @@ export namespace LanguageModelChatMessage { })); return new types.LanguageModelToolResultPart(c.toolCallId, content, c.isError); } else if (c.type === 'image_url') { - // Non-stable types - return undefined; + return new types.LanguageModelDataPart(c.value.data.buffer, c.value.mimeType); + } else if (c.type === 'data') { + return new types.LanguageModelDataPart(c.data.buffer, c.mimeType); } else if (c.type === 'tool_use') { return new types.LanguageModelToolCallPart(c.toolCallId, c.name, c.parameters); } @@ -2374,6 +2377,13 @@ export namespace LanguageModelChatMessage { type: 'prompt_tsx', value: part.value, } satisfies IChatResponsePromptTsxPart; + } else if (part instanceof types.LanguageModelDataPart) { + return { + type: 'data', + mimeType: part.mimeType, + data: VSBuffer.wrap(part.data), + audience: part.audience + } satisfies IChatResponseDataPart; } else { // Strip unknown parts return undefined; @@ -2381,6 +2391,25 @@ export namespace LanguageModelChatMessage { })), isError: c.isError }; + } else if (c instanceof types.LanguageModelDataPart) { + if (isImageDataPart(c)) { + const value: chatProvider.IChatImageURLPart = { + mimeType: c.mimeType as chatProvider.ChatImageMimeType, + data: VSBuffer.wrap(c.data), + }; + + return { + type: 'image_url', + value: value + }; + } else { + return { + type: 'data', + mimeType: c.mimeType, + data: VSBuffer.wrap(c.data), + audience: c.audience + } satisfies IChatMessageDataPart; + } } else if (c instanceof types.LanguageModelToolCallPart) { return { type: 'tool_use', @@ -2429,7 +2458,7 @@ export namespace LanguageModelChatMessage2 { return new types.LanguageModelPromptTsxPart(part.value); } }); - return new types.LanguageModelToolResultPart2(c.toolCallId, content, c.isError); + return new types.LanguageModelToolResultPart(c.toolCallId, content, c.isError); } else if (c.type === 'image_url') { return new types.LanguageModelDataPart(c.value.data.buffer, c.value.mimeType); } else if (c.type === 'data') { @@ -2456,7 +2485,7 @@ export namespace LanguageModelChatMessage2 { } const content = messageContent.map((c): chatProvider.IChatMessagePart => { - if ((c instanceof types.LanguageModelToolResultPart2) || (c instanceof types.LanguageModelToolResultPart)) { + if (c instanceof types.LanguageModelToolResultPart) { return { type: 'tool_result', toolCallId: c.callId, @@ -2546,12 +2575,14 @@ export namespace LanguageModelChatMessage2 { } function isImageDataPart(part: types.LanguageModelDataPart): boolean { - switch (part.mimeType) { - case types.ChatImageMimeType.PNG: - case types.ChatImageMimeType.JPEG: - case types.ChatImageMimeType.GIF: - case types.ChatImageMimeType.WEBP: - case types.ChatImageMimeType.BMP: + const mime = typeof part.mimeType === 'string' ? part.mimeType.toLowerCase() : ''; + switch (mime) { + case 'image/png': + case 'image/jpeg': + case 'image/jpg': + case 'image/gif': + case 'image/webp': + case 'image/bmp': return true; default: return false; @@ -3553,24 +3584,27 @@ export namespace LanguageModelToolResult { return new types.LanguageModelToolResult(result.content.map(item => { if (item.kind === 'text') { return new types.LanguageModelTextPart(item.value, item.audience); + } else if (item.kind === 'data') { + return new types.LanguageModelDataPart(item.value.data.buffer, item.value.mimeType, item.audience); } else { return new types.LanguageModelPromptTsxPart(item.value); } })); } - export function from(result: vscode.ExtendedLanguageModelToolResult, extension: IExtensionDescription): Dto { + export function from(result: vscode.ExtendedLanguageModelToolResult, extension: IExtensionDescription): Dto | SerializableObjectWithBuffers> { if (result.toolResultMessage) { checkProposedApiEnabled(extension, 'chatParticipantPrivate'); } - const checkAudienceApi = (item: LanguageModelTextPart) => { + const checkAudienceApi = (item: LanguageModelTextPart | LanguageModelDataPart) => { if (item.audience) { checkProposedApiEnabled(extension, 'languageModelToolResultAudience'); } }; - return { + let hasBuffers = false; + const dto: Dto = { content: result.content.map(item => { if (item instanceof types.LanguageModelTextPart) { checkAudienceApi(item); @@ -3584,6 +3618,17 @@ export namespace LanguageModelToolResult { kind: 'promptTsx', value: item.value, }; + } else if (item instanceof types.LanguageModelDataPart) { + checkAudienceApi(item); + hasBuffers = true; + return { + kind: 'data', + value: { + mimeType: item.mimeType, + data: VSBuffer.wrap(item.data) + }, + audience: item.audience + }; } else { throw new Error('Unknown LanguageModelToolResult part type'); } @@ -3591,6 +3636,8 @@ export namespace LanguageModelToolResult { toolResultMessage: MarkdownString.fromStrict(result.toolResultMessage), toolResultDetails: result.toolResultDetails?.map(detail => URI.isUri(detail) ? detail : Location.from(detail as vscode.Location)), }; + + return hasBuffers ? new SerializableObjectWithBuffers(dto) : dto; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index ca7116aba8b..fb0e0bcb5ad 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3467,15 +3467,6 @@ export class LanguageModelToolResultPart implements vscode.LanguageModelToolResu } } -export class LanguageModelToolResultPart2 extends LanguageModelToolResultPart implements vscode.LanguageModelToolResultPart2 { - - declare content: (LanguageModelTextPart | LanguageModelPromptTsxPart | LanguageModelDataPart | unknown)[]; - - constructor(callId: string, content: (LanguageModelTextPart | LanguageModelPromptTsxPart | LanguageModelDataPart | unknown)[], isError?: boolean) { - super(callId, content, isError); - this.content = content; - } -} export enum ChatErrorLevel { Info = 0, @@ -3485,19 +3476,19 @@ export enum ChatErrorLevel { export class LanguageModelChatMessage implements vscode.LanguageModelChatMessage { - static User(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[], name?: string): LanguageModelChatMessage { + static User(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage { return new LanguageModelChatMessage(LanguageModelChatMessageRole.User, content, name); } - static Assistant(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[], name?: string): LanguageModelChatMessage { + static Assistant(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage { return new LanguageModelChatMessage(LanguageModelChatMessageRole.Assistant, content, name); } role: vscode.LanguageModelChatMessageRole; - private _content: (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[] = []; + private _content: (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[] = []; - set content(value: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[]) { + set content(value: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[]) { if (typeof value === 'string') { // we changed this and still support setting content with a string property. this keep the API runtime stable // despite the breaking change in the type definition. @@ -3507,13 +3498,13 @@ export class LanguageModelChatMessage implements vscode.LanguageModelChatMessage } } - get content(): (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[] { + get content(): (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[] { return this._content; } name: string | undefined; - constructor(role: vscode.LanguageModelChatMessageRole, content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart)[], name?: string) { + constructor(role: vscode.LanguageModelChatMessageRole, content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string) { this.role = role; this.content = content; this.name = name; @@ -3522,19 +3513,19 @@ export class LanguageModelChatMessage implements vscode.LanguageModelChatMessage export class LanguageModelChatMessage2 implements vscode.LanguageModelChatMessage2 { - static User(content: string | (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage2 { + static User(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage2 { return new LanguageModelChatMessage2(LanguageModelChatMessageRole.User, content, name); } - static Assistant(content: string | (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage2 { + static Assistant(content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[], name?: string): LanguageModelChatMessage2 { return new LanguageModelChatMessage2(LanguageModelChatMessageRole.Assistant, content, name); } role: vscode.LanguageModelChatMessageRole; - private _content: (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] = []; + private _content: (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] = []; - set content(value: string | (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[]) { + set content(value: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[]) { if (typeof value === 'string') { // we changed this and still support setting content with a string property. this keep the API runtime stable // despite the breaking change in the type definition. @@ -3544,12 +3535,12 @@ export class LanguageModelChatMessage2 implements vscode.LanguageModelChatMessag } } - get content(): (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] { + get content(): (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] { return this._content; } // Temp to avoid breaking changes - set content2(value: (string | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart)[] | undefined) { + set content2(value: (string | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart)[] | undefined) { if (value) { this.content = value.map(part => { if (typeof part === 'string') { @@ -3560,7 +3551,7 @@ export class LanguageModelChatMessage2 implements vscode.LanguageModelChatMessag } } - get content2(): (string | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] | undefined { + get content2(): (string | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[] | undefined { return this.content.map(part => { if (part instanceof LanguageModelTextPart) { return part.value; @@ -3571,7 +3562,7 @@ export class LanguageModelChatMessage2 implements vscode.LanguageModelChatMessag name: string | undefined; - constructor(role: vscode.LanguageModelChatMessageRole, content: string | (LanguageModelTextPart | LanguageModelToolResultPart2 | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[], name?: string) { + constructor(role: vscode.LanguageModelChatMessageRole, content: string | (LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart | LanguageModelThinkingPart)[], name?: string) { this.role = role; this.content = content; this.name = name; @@ -3627,8 +3618,8 @@ export class LanguageModelDataPart implements vscode.LanguageModelDataPart2 { this.audience = audience; } - static image(data: Uint8Array, mimeType: ChatImageMimeType): vscode.LanguageModelDataPart { - return new LanguageModelDataPart(data, mimeType as string); + static image(data: Uint8Array, mimeType: string): vscode.LanguageModelDataPart { + return new LanguageModelDataPart(data, mimeType); } static json(value: object, mime: string = 'text/x-json'): vscode.LanguageModelDataPart { @@ -3767,7 +3758,7 @@ export class LanguageModelError extends Error { } export class LanguageModelToolResult { - constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[]) { } + constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart | LanguageModelDataPart)[]) { } toJSON() { return { diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts index c5123e1e23c..389e49c3c32 100644 --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -788,21 +788,4 @@ suite('ExtHostTypes', function () { m.content = 'Hello'; assert.deepStrictEqual(m.content, [new types.LanguageModelTextPart('Hello')]); }); - - test('LanguageModelToolResultPart2 instanceof LanguageModelToolResultPart', function () { - // Test that LanguageModelToolResultPart2 extends LanguageModelToolResultPart for instanceof checks - const part1 = new types.LanguageModelToolResultPart('call1', [new types.LanguageModelTextPart('text')]); - const part2 = new types.LanguageModelToolResultPart2('call2', [new types.LanguageModelTextPart('text')]); - - // Basic instanceof checks - assert.ok(part1 instanceof types.LanguageModelToolResultPart); - assert.ok(part2 instanceof types.LanguageModelToolResultPart, 'LanguageModelToolResultPart2 should be instanceof LanguageModelToolResultPart'); - assert.ok(part2 instanceof types.LanguageModelToolResultPart2); - - // Verify properties are accessible - assert.strictEqual(part1.callId, 'call1'); - assert.strictEqual(part2.callId, 'call2'); - assert.strictEqual(part1.isError, false); - assert.strictEqual(part2.isError, false); - }); }); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index acb71475e55..e39bac4a6e6 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -20023,7 +20023,7 @@ declare module 'vscode' { * @param content The content of the message. * @param name The optional name of a user for the message. */ - static User(content: string | Array, name?: string): LanguageModelChatMessage; + static User(content: string | Array, name?: string): LanguageModelChatMessage; /** * Utility to create a new assistant message. @@ -20031,7 +20031,7 @@ declare module 'vscode' { * @param content The content of the message. * @param name The optional name of a user for the message. */ - static Assistant(content: string | Array, name?: string): LanguageModelChatMessage; + static Assistant(content: string | Array, name?: string): LanguageModelChatMessage; /** * The role of this message. @@ -20097,7 +20097,7 @@ declare module 'vscode' { * } * ``` */ - stream: AsyncIterable; + stream: AsyncIterable; /** * This is equivalent to filtering everything except for text parts from a {@link LanguageModelChatResponse.stream}. @@ -20545,12 +20545,12 @@ declare module 'vscode' { /** * The various message types which a {@linkcode LanguageModelChatProvider} can emit in the chat response stream */ - export type LanguageModelResponsePart = LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart; + export type LanguageModelResponsePart = LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart; /** * The various message types which can be sent via {@linkcode LanguageModelChat.sendRequest } and processed by a {@linkcode LanguageModelChatProvider} */ - export type LanguageModelInputPart = LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart; + export type LanguageModelInputPart = LanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart | LanguageModelDataPart; /** * A LanguageModelChatProvider implements access to language models, which users can then use through the chat view, or through extension API by acquiring a LanguageModelChat. @@ -20825,13 +20825,13 @@ declare module 'vscode' { /** * The value of the tool result. */ - content: Array; + content: Array; /** * @param callId The ID of the tool call. * @param content The content of the tool result. */ - constructor(callId: string, content: Array); + constructor(callId: string, content: Array); } /** @@ -20876,13 +20876,62 @@ declare module 'vscode' { * the future. * @see {@link lm.invokeTool}. */ - content: Array; + content: Array; /** * Create a LanguageModelToolResult * @param content A list of tool result content parts */ - constructor(content: Array); + constructor(content: Array); + } + + /** + * A language model response part containing arbitrary data, returned from a {@link LanguageModelChatResponse}. + */ + export class LanguageModelDataPart { + /** + * Create a new {@linkcode LanguageModelDataPart} for an image. + * @param data Binary image data + * @param mimeType The MIME type of the image. Common values are `image/png` and `image/jpeg`. + */ + static image(data: Uint8Array, mime: string): LanguageModelDataPart; + + /** + * Create a new {@linkcode LanguageModelDataPart} for a json. + * + * *Note* that this function is not expecting "stringified JSON" but + * an object that can be stringified. This function will throw an error + * when the passed value cannot be JSON-stringified. + * @param value A JSON-stringifyable value. + * @param mimeType Optional MIME type, defaults to `application/json` + */ + static json(value: any, mime?: string): LanguageModelDataPart; + + /** + * Create a new {@linkcode LanguageModelDataPart} for text. + * + * *Note* that an UTF-8 encoder is used to create bytes for the string. + * @param value Text data + * @param mimeType The MIME type if any. Common values are `text/plain` and `text/markdown`. + */ + static text(value: string, mime?: string): LanguageModelDataPart; + + /** + * The mime type which determines how the data property is interpreted. + */ + mimeType: string; + + /** + * The byte data for this part. + */ + data: Uint8Array; + + /** + * Construct a generic data part with the given content. + * @param data The byte data for this part. + * @param mimeType The mime type of the data. + */ + constructor(data: Uint8Array, mimeType: string); } /** diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index e3c786f1d1b..b8f290ec476 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 10 +// version: 11 declare module 'vscode' { diff --git a/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts b/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts deleted file mode 100644 index 4d491a66ca8..00000000000 --- a/src/vscode-dts/vscode.proposed.languageModelDataPart.d.ts +++ /dev/null @@ -1,164 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// version: 3 - -declare module 'vscode' { - - export interface LanguageModelChat { - sendRequest(messages: Array, options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable; - countTokens(text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token?: CancellationToken): Thenable; - } - - /** - * Represents a message in a chat. Can assume different roles, like user or assistant. - */ - export class LanguageModelChatMessage2 { - - /** - * Utility to create a new user message. - * - * @param content The content of the message. - * @param name The optional name of a user for the message. - */ - static User(content: string | Array, name?: string): LanguageModelChatMessage2; - - /** - * Utility to create a new assistant message. - * - * @param content The content of the message. - * @param name The optional name of a user for the message. - */ - static Assistant(content: string | Array, name?: string): LanguageModelChatMessage2; - - /** - * The role of this message. - */ - role: LanguageModelChatMessageRole; - - /** - * A string or heterogeneous array of things that a message can contain as content. Some parts may be message-type - * specific for some models. - */ - content: Array; - - /** - * The optional name of a user for this message. - */ - name: string | undefined; - - /** - * Create a new user message. - * - * @param role The role of the message. - * @param content The content of the message. - * @param name The optional name of a user for the message. - */ - constructor(role: LanguageModelChatMessageRole, content: string | Array, name?: string); - } - - /** - * A language model response part containing arbitrary data, returned from a {@link LanguageModelChatResponse}. - */ - export class LanguageModelDataPart { - /** - * Factory function to create a `LanguageModelDataPart` for an image. - * @param data Binary image data - * @param mimeType The MIME type of the image - */ - // TODO@API just use string, no enum required - static image(data: Uint8Array, mimeType: ChatImageMimeType): LanguageModelDataPart; - - static json(value: any, mime?: string): LanguageModelDataPart; - - static text(value: string, mime?: string): LanguageModelDataPart; - - /** - * The mime type which determines how the data property is interpreted. - */ - mimeType: string; - - /** - * The data of the part. - */ - data: Uint8Array; - - /** - * Construct a generic data part with the given content. - * @param value The data of the part. - */ - constructor(data: Uint8Array, mimeType: string); - } - - /** - * Enum for supported image MIME types. - */ - export enum ChatImageMimeType { - PNG = 'image/png', - JPEG = 'image/jpeg', - GIF = 'image/gif', - WEBP = 'image/webp', - BMP = 'image/bmp', - } - - /** - * The result of a tool call. This is the counterpart of a {@link LanguageModelToolCallPart tool call} and - * it can only be included in the content of a User message - */ - export class LanguageModelToolResultPart2 { - /** - * The ID of the tool call. - * - * *Note* that this should match the {@link LanguageModelToolCallPart.callId callId} of a tool call part. - */ - callId: string; - - /** - * The value of the tool result. - */ - content: Array; - - /** - * @param callId The ID of the tool call. - * @param content The content of the tool result. - */ - constructor(callId: string, content: Array); - } - - - /** - * A tool that can be invoked by a call to a {@link LanguageModelChat}. - */ - export interface LanguageModelTool { - /** - * Invoke the tool with the given input and return a result. - * - * The provided {@link LanguageModelToolInvocationOptions.input} has been validated against the declared schema. - */ - invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken): ProviderResult; - } - - /** - * A result returned from a tool invocation. If using `@vscode/prompt-tsx`, this result may be rendered using a `ToolResult`. - */ - export class LanguageModelToolResult2 { - /** - * A list of tool result content parts. Includes `unknown` becauses this list may be extended with new content types in - * the future. - * @see {@link lm.invokeTool}. - */ - content: Array; - - /** - * Create a LanguageModelToolResult - * @param content A list of tool result content parts - */ - constructor(content: Array); - } - - export namespace lm { - export function invokeTool(name: string, options: LanguageModelToolInvocationOptions, token?: CancellationToken): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts b/src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts index b26ff971868..7c5da1aa622 100644 --- a/src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts @@ -46,4 +46,66 @@ declare module 'vscode' { */ stream: AsyncIterable; } + + export interface LanguageModelChat { + sendRequest(messages: Array, options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable; + countTokens(text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token?: CancellationToken): Thenable; + } + + /** + * Represents a message in a chat. Can assume different roles, like user or assistant. + */ + export class LanguageModelChatMessage2 { + + /** + * Utility to create a new user message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ + static User(content: string | Array, name?: string): LanguageModelChatMessage2; + + /** + * Utility to create a new assistant message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ + static Assistant(content: string | Array, name?: string): LanguageModelChatMessage2; + + /** + * The role of this message. + */ + role: LanguageModelChatMessageRole; + + /** + * A string or heterogeneous array of things that a message can contain as content. Some parts may be message-type + * specific for some models. + */ + content: Array; + + /** + * The optional name of a user for this message. + */ + name: string | undefined; + + /** + * Create a new user message. + * + * @param role The role of the message. + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ + constructor(role: LanguageModelChatMessageRole, content: string | Array, name?: string); + } + + /** + * Temporary alias for LanguageModelToolResultPart to avoid breaking changes in chat. + */ + export class LanguageModelToolResultPart2 extends LanguageModelToolResultPart { } + + /** + * Temporary alias for LanguageModelToolResult to avoid breaking changes in chat. + */ + export class LanguageModelToolResult2 extends LanguageModelToolResult { } } From 62fdb96028eb57f92ef8fce7564f8f0bfac7162e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Oct 2025 00:46:26 +0200 Subject: [PATCH 1150/4355] adopt to mcp registry v0.1 (#271405) --- .../platform/mcp/common/mcpGalleryManifest.ts | 2 +- .../platform/mcp/common/mcpGalleryService.ts | 59 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index adb6659341e..d75a920a351 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -26,7 +26,7 @@ export type McpGalleryManifestResource = { }; export interface IMcpGalleryManifest { - readonly version?: string; + readonly version: string; readonly url: string; readonly resources: readonly McpGalleryManifestResource[]; } diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index dc097b71ffb..d223dc5c0b7 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -83,10 +83,10 @@ interface IGalleryMcpServerDataSerializer { } -namespace McpServerSchemaVersion_2025_07_09 { +namespace McpServerSchemaVersion_v0 { - export const VERSION = '2025-07-09'; - export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/${VERSION}/server.schema.json`; + export const VERSION = 'v0'; + export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json`; interface RawGalleryMcpServerInput { readonly description?: string; @@ -226,7 +226,7 @@ namespace McpServerSchemaVersion_2025_07_09 { } public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { - if (!input || typeof input !== 'object' || (input).$schema !== McpServerSchemaVersion_2025_07_09.SCHEMA) { + if (!input || typeof input !== 'object' || (input).$schema !== McpServerSchemaVersion_v0.SCHEMA) { return undefined; } @@ -365,10 +365,9 @@ namespace McpServerSchemaVersion_2025_07_09 { export const SERIALIZER = new Serializer(); } -namespace McpServerSchemaVersion_2025_29_09 { +namespace McpServerSchemaVersion_v0_1 { - export const VERSION = '2025-09-29'; - export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/${VERSION}/server.schema.json`; + export const VERSION = 'v0.1'; interface RawGalleryMcpServerInput { readonly description?: string; @@ -507,10 +506,6 @@ namespace McpServerSchemaVersion_2025_29_09 { return undefined; } - if ((input).server.$schema && (input).server.$schema !== McpServerSchemaVersion_2025_29_09.SCHEMA) { - return undefined; - } - const from = input; return { @@ -580,8 +575,8 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService ) { super(); this.galleryMcpServerDataSerializers = new Map(); - this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_07_09.VERSION, McpServerSchemaVersion_2025_07_09.SERIALIZER); - this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_2025_29_09.VERSION, McpServerSchemaVersion_2025_29_09.SERIALIZER); + this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_v0.VERSION, McpServerSchemaVersion_v0.SERIALIZER); + this.galleryMcpServerDataSerializers.set(McpServerSchemaVersion_v0_1.VERSION, McpServerSchemaVersion_v0_1.SERIALIZER); } isEnabled(): boolean { @@ -796,7 +791,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return { servers: [] }; } - const result = this.serializeMcpServersResult(data); + const result = this.serializeMcpServersResult(data, mcpGalleryManifest); if (!result) { throw new Error(`Failed to serialize MCP servers result from ${mcpGalleryUrl}`, data); @@ -820,36 +815,30 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return undefined; } - const server = this.serializeMcpServer(data); + if (!mcpGalleryManifest) { + mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); + } + mcpGalleryManifest = mcpGalleryManifest && mcpServerUrl.startsWith(mcpGalleryManifest.url) ? mcpGalleryManifest : null; + + const server = this.serializeMcpServer(data, mcpGalleryManifest); if (!server) { throw new Error(`Failed to serialize MCP server from ${mcpServerUrl}`, data); } - if (!mcpGalleryManifest) { - mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); - } + return this.toGalleryMcpServer(server, mcpGalleryManifest); + } - return this.toGalleryMcpServer(server, mcpGalleryManifest && mcpServerUrl.startsWith(mcpGalleryManifest.url) ? mcpGalleryManifest : null); + private serializeMcpServer(data: unknown, mcpGalleryManifest: IMcpGalleryManifest | null): IRawGalleryMcpServer | undefined { + return this.getSerializer(mcpGalleryManifest)?.toRawGalleryMcpServer(data); } - private serializeMcpServer(data: unknown): IRawGalleryMcpServer | undefined { - for (const [, serializer] of this.galleryMcpServerDataSerializers) { - const result = serializer.toRawGalleryMcpServer(data); - if (result) { - return result; - } - } - return undefined; + private serializeMcpServersResult(data: unknown, mcpGalleryManifest: IMcpGalleryManifest | null): IRawGalleryMcpServersResult | undefined { + return this.getSerializer(mcpGalleryManifest)?.toRawGalleryMcpServerResult(data); } - private serializeMcpServersResult(data: unknown): IRawGalleryMcpServersResult | undefined { - for (const [, serializer] of this.galleryMcpServerDataSerializers) { - const result = serializer.toRawGalleryMcpServerResult(data); - if (result) { - return result; - } - } - return undefined; + private getSerializer(mcpGalleryManifest: IMcpGalleryManifest | null): IGalleryMcpServerDataSerializer | undefined { + const version = mcpGalleryManifest?.version ?? 'v0'; + return this.galleryMcpServerDataSerializers.get(version); } private getNamedServerUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { From 286a752f5ebce04eff8a3407ea32b196a6774c60 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:06:20 -0700 Subject: [PATCH 1151/4355] Use CIMD if supported (#271403) * Use CIMD if supported If the Authorization Server we are auth'ing against supports the Client ID Metadata auth flow, we use the client id metadata url from product.json as the client id in auth flows. Fixes https://github.com/microsoft/vscode/issues/270811 * Update src/vs/workbench/api/browser/mainThreadAuthentication.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package.json | 2 +- src/vs/base/common/oauth.ts | 7 +++++++ src/vs/base/common/product.ts | 1 + src/vs/workbench/api/browser/mainThreadAuthentication.ts | 8 +++++++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 509fd3a40c7..ebd3fa44f35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "83bba123096af4c03a5f4419dc4e7f33d14e354c", + "distro": "cf7a6d44d8299723d955936df569ea424e6b68fd", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/common/oauth.ts b/src/vs/base/common/oauth.ts index 00dae1f86ec..c808bf818b9 100644 --- a/src/vs/base/common/oauth.ts +++ b/src/vs/base/common/oauth.ts @@ -257,6 +257,13 @@ export interface IAuthorizationServerMetadata { * OPTIONAL. JSON array containing a list of PKCE code challenge methods supported. */ code_challenge_methods_supported?: string[]; + + /** + * OPTIONAL. Boolean flag indicating whether the authorization server supports the + * client_id_metadata document. + * ref https://datatracker.ietf.org/doc/html/draft-parecki-oauth-client-id-metadata-document-03 + */ + client_id_metadata_document_supported?: boolean; } /** diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index c9be42c6a66..2b552bdde7c 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -218,6 +218,7 @@ export interface IProductConfiguration { readonly chatEntitlementUrl: string; readonly mcpRegistryDataUrl: string; }; + readonly authClientIdMetadataUrl?: string; readonly 'configurationSync.store'?: ConfigurationSyncStore; diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index f15906e5c35..af3845c9307 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -28,6 +28,7 @@ import { IAuthorizationTokenResponse } from '../../../base/common/oauth.js'; import { IDynamicAuthenticationProviderStorageService } from '../../services/authentication/common/dynamicAuthenticationProviderStorage.js'; import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js'; import { IQuickInputService } from '../../../platform/quickinput/common/quickInput.js'; +import { IProductService } from '../../../platform/product/common/productService.js'; export interface AuthenticationInteractiveOptions { detail?: string; @@ -112,6 +113,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu constructor( extHostContext: IExtHostContext, + @IProductService private readonly productService: IProductService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IAuthenticationExtensionsService private readonly authenticationExtensionsService: IAuthenticationExtensionsService, @IAuthenticationAccessService private readonly authenticationAccessService: IAuthenticationAccessService, @@ -153,11 +155,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu // Auth Provider Id is a combination of the authorization server and the resource, if provided. const authProviderId = resource ? `${authorizationServer.toString(true)} ${resource.resource}` : authorizationServer.toString(true); const clientDetails = await this.dynamicAuthProviderStorageService.getClientRegistration(authProviderId); - const clientId = clientDetails?.clientId; + let clientId = clientDetails?.clientId; const clientSecret = clientDetails?.clientSecret; let initialTokens: (IAuthorizationTokenResponse & { created_at: number })[] | undefined = undefined; if (clientId) { initialTokens = await this.dynamicAuthProviderStorageService.getSessionsForDynamicAuthProvider(authProviderId, clientId); + // If we don't already have a client id, check if the server supports the Client Id Metadata flow (see docs on the property) + // and add the "client id" if so. + } else if (serverMetadata.client_id_metadata_document_supported) { + clientId = this.productService.authClientIdMetadataUrl; } return await this._proxy.$registerDynamicAuthProvider( authorizationServer, From a93b8efb1220ea01a1c200de343012c42f215d69 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 14 Oct 2025 16:20:58 -0700 Subject: [PATCH 1152/4355] Respect editor auto-closing/quotes/surround settings in chat input. --- .../contrib/chat/browser/chatInputPart.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 567f17128a1..231d332e33e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -498,6 +498,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (e.affectsConfiguration('editor.wordSegmenterLocales')) { newOptions.wordSegmenterLocales = this.configurationService.getValue('editor.wordSegmenterLocales'); } + if (e.affectsConfiguration('editor.autoClosingBrackets')) { + newOptions.autoClosingBrackets = this.configurationService.getValue('editor.autoClosingBrackets'); + } + if (e.affectsConfiguration('editor.autoClosingQuotes')) { + newOptions.autoClosingQuotes = this.configurationService.getValue('editor.autoClosingQuotes'); + } + if (e.affectsConfiguration('editor.autoSurround')) { + newOptions.autoSurround = this.configurationService.getValue('editor.autoSurround'); + } this.inputEditor.updateOptions(newOptions); })); @@ -510,8 +519,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this.languageModelsService.onDidChangeLanguageModels(() => { // We've changed models and the current one is no longer available. Select a new one - const selectedModel = this._currentLanguageModel ? this.getModels().find(m => m.identifier === this._currentLanguageModel?.identifier) : undefined; - if (this._currentLanguageModel && (!selectedModel || !selectedModel.metadata.isUserSelectable)) { + const selectedModel = this._currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this._currentLanguageModel.identifier) : undefined; + if (this._currentLanguageModel && (!selectedModel || !selectedModel.isUserSelectable)) { this.setCurrentLanguageModelToDefault(); } })); @@ -1171,6 +1180,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.cursorWidth = 1; options.wrappingStrategy = 'advanced'; options.bracketPairColorization = { enabled: false }; + // Respect user's editor settings for auto-closing and auto-surrounding behavior + options.autoClosingBrackets = this.configurationService.getValue('editor.autoClosingBrackets'); + options.autoClosingQuotes = this.configurationService.getValue('editor.autoClosingQuotes'); + options.autoSurround = this.configurationService.getValue('editor.autoSurround'); options.suggest = { showIcons: true, showSnippets: false, From 7ecd15f2294a2d6dadf9f53da273fe5390e14b00 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 14 Oct 2025 16:28:59 -0700 Subject: [PATCH 1153/4355] Removed unexpected change. --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 231d332e33e..867df135d81 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -519,8 +519,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this.languageModelsService.onDidChangeLanguageModels(() => { // We've changed models and the current one is no longer available. Select a new one - const selectedModel = this._currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this._currentLanguageModel.identifier) : undefined; - if (this._currentLanguageModel && (!selectedModel || !selectedModel.isUserSelectable)) { + const selectedModel = this._currentLanguageModel ? this.getModels().find(m => m.identifier === this._currentLanguageModel?.identifier) : undefined; + if (this._currentLanguageModel && (!selectedModel || !selectedModel.metadata.isUserSelectable)) { this.setCurrentLanguageModelToDefault(); } })); From 84b1589954c53bda117bd14dd7bbb7e406c07826 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 14 Oct 2025 16:43:29 -0700 Subject: [PATCH 1154/4355] PR feedback --- .../browser/gotoLineQuickAccess.ts | 25 +++++++------------ .../quickaccess/gotoLineQuickAccess.ts | 17 ++++++------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 1e1989a454c..972420ad578 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -23,20 +23,11 @@ interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; - private _useZeroBasedOffset = false; - constructor() { + constructor(private useZeroBasedOffset: { value: boolean } = { value: false }) { super({ canAcceptInBackground: true }); } - get useZeroBasedOffset() { - return this._useZeroBasedOffset; - } - - set useZeroBasedOffset(value: boolean) { - this._useZeroBasedOffset = value; - } - protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); @@ -104,7 +95,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor const toggle = new Toggle({ title: localize('gotoLineToggle', "Use zero-based offset"), icon: Codicon.symbolArray, - isChecked: this.useZeroBasedOffset, + isChecked: this.useZeroBasedOffset.value, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) @@ -112,7 +103,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor disposables.add( toggle.onChange(() => { - this.useZeroBasedOffset = !this.useZeroBasedOffset; + this.useZeroBasedOffset.value = !this.useZeroBasedOffset.value; updatePickerAndEditor(); })); @@ -153,10 +144,12 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor if (!isNaN(offset)) { const model = this.getModel(editor); if (model) { - if (offset >= 0) { - // If offset is 1-based, we need to convert to model's 0-based. - offset -= this.useZeroBasedOffset ? 0 : 1; - } else { + const reverse = offset < 0; + if (!this.useZeroBasedOffset.value) { + // Convert 1-based offset to model's 0-based. + offset -= Math.sign(offset); + } + if (reverse) { // Offset from the end of the buffer offset += model.getValueLength(); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 6bc88d6274d..67f604b17f6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -31,18 +31,17 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(); + super({ + get value() { + return configurationService.getValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting); + }, + set value(value: boolean) { + configurationService.updateValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting, value); + } + }); this.onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; } - override get useZeroBasedOffset(): boolean { - return this.configurationService.getValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting); - } - - override set useZeroBasedOffset(value: boolean) { - this.configurationService.updateValue(GotoLineQuickAccessProvider.zeroBasedOffsetSetting, value); - } - private get configuration() { const editorConfig = this.configurationService.getValue().workbench?.editor; From 83e551bc80c381ff971e365173655ab4c445c83d Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 14 Oct 2025 20:09:25 -0700 Subject: [PATCH 1155/4355] Fix todolist widget clearing (#271427) --- .../chatContentParts/chatTodoListWidget.ts | 1 + .../contrib/chat/browser/chatInputPart.ts | 4 ++++ .../contrib/chat/browser/chatWidget.ts | 20 +++++++++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 26a4180f830..768077f35e3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -144,6 +144,7 @@ export class ChatTodoListWidget extends Disposable { return; } + this.domNode.style.display = 'none'; const currentTodos = this.chatTodoListService.getTodos(sessionId); const shouldClear = force || !currentTodos.some(todo => todo.status !== 'completed'); if (shouldClear) { diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 567f17128a1..ecf05af8f64 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1606,6 +1606,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._chatInputTodoListWidget.value.render(chatSessionId); } + clearTodoListWidget(sessionId: string | undefined, force: boolean): void { + this._chatInputTodoListWidget.value?.clear(sessionId, force); + } + async renderChatEditingSessionState(chatEditingSession: IChatEditingSession | null) { dom.setVisibility(Boolean(chatEditingSession), this.chatEditingSessionWidgetContainer); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 41b543415f1..b14f706afeb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -67,7 +67,6 @@ import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, import { ChatRequestParser } from '../common/chatRequestParser.js'; import { IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; import { IChatSlashCommandService } from '../common/chatSlashCommands.js'; -import { IChatTodoListService } from '../common/chatTodoListService.js'; import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, PromptFileVariableKind, toPromptFileVariableEntry } from '../common/chatVariableEntries.js'; import { ChatViewModel, IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { IChatInputState } from '../common/chatWidgetHistoryService.js'; @@ -475,7 +474,6 @@ export class ChatWidget extends Disposable implements IChatWidget { @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IChatModeService private readonly chatModeService: IChatModeService, - @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, @IChatLayoutService private readonly chatLayoutService: IChatLayoutService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, @ICommandService private readonly commandService: ICommandService, @@ -914,7 +912,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Unlock coding agent when clearing this.unlockFromCodingAgent(); this._onDidClear.fire(); - this.chatTodoListWidget.clear(this.viewModel?.sessionId, true); + this.clearTodoListWidget(this.viewModel?.sessionId); } public toggleHistoryVisibility(): void { @@ -1255,8 +1253,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private renderChatTodoListWidget(): void { const sessionId = this.viewModel?.sessionId; - if (!sessionId) { - this.chatTodoListWidget.render(sessionId); + if (!sessionId || !this._isReady) { return; } @@ -1278,11 +1275,12 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - // Handle 'default' - render the widget if there are todos - const todos = this.chatTodoListService.getTodos(sessionId); - if (todos.length > 0) { - this.chatTodoListWidget.render(sessionId); - } + this.chatTodoListWidget.render(sessionId); + } + + private clearTodoListWidget(sessionId: string | undefined, force: boolean = false): void { + this.chatTodoListWidget.clear(sessionId, force); + this.inputPart.clearTodoListWidget(sessionId, force); } private _getGenerateInstructionsMessage(): IMarkdownString { @@ -2244,7 +2242,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); } if (e.kind === 'addRequest' || e.kind === 'removeRequest') { - this.chatTodoListWidget.clear(model.sessionId, e.kind === 'removeRequest' /*force*/); + this.clearTodoListWidget(model.sessionId, e.kind === 'removeRequest' /*force*/); } })); From aa462d08c7283cd4b284a0b98a2efb3e9593f8b9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 14 Oct 2025 21:26:27 -0700 Subject: [PATCH 1156/4355] Add languageModelProxy proposal (#271436) --- .../common/extensionsApiProposals.ts | 3 ++ .../workbench/api/common/extHost.api.impl.ts | 8 +++++ .../api/common/extHostLanguageModels.ts | 33 +++++++++++++++++++ ...scode.proposed.chatParticipantPrivate.d.ts | 12 +++++++ .../vscode.proposed.languageModelProxy.d.ts | 20 +++++++++++ 5 files changed, 76 insertions(+) create mode 100644 src/vscode-dts/vscode.proposed.languageModelProxy.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 4cc3d85620b..52179154197 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -255,6 +255,9 @@ const _allApiProposals = { languageModelCapabilities: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelCapabilities.d.ts', }, + languageModelProxy: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts', + }, languageModelSystem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts', }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f72d478be71..463fe9c7201 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1552,6 +1552,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerLanguageModelChatProvider: (vendor, provider) => { return extHostLanguageModels.registerLanguageModelChatProvider(extension, vendor, provider); }, + getModelProxy: () => { + checkProposedApiEnabled(extension, 'languageModelProxy'); + return extHostLanguageModels.getModelProxy(extension); + }, + registerLanguageModelProxyProvider: (provider) => { + checkProposedApiEnabled(extension, 'chatParticipantPrivate'); + return extHostLanguageModels.registerLanguageModelProxyProvider(extension, provider); + }, // --- embeddings get embeddingModels() { checkProposedApiEnabled(extension, 'embeddings'); diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index dce627ed19a..0342f7285bd 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -121,6 +121,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); private readonly _ignoredFileProviders = new Map(); + private _languageModelProxyProvider: vscode.LanguageModelProxyProvider | undefined; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -607,6 +608,27 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return this._proxy.$fileIsIgnored(uri, token); } + async getModelProxy(extension: IExtensionDescription): Promise { + checkProposedApiEnabled(extension, 'languageModelProxy'); + + if (!this._languageModelProxyProvider) { + this._logService.warn('[LanguageModelProxy] No LanguageModelProxyProvider registered'); + return undefined; + } + + const requestingExtensionId = ExtensionIdentifier.toKey(extension.identifier); + try { + const result = await Promise.resolve(this._languageModelProxyProvider.provideModelProxy(requestingExtensionId, CancellationToken.None)); + if (result) { + return result; + } + } catch (err) { + this._logService.error(`[LanguageModelProxy] Provider ${ExtensionIdentifier.toKey(extension.identifier)} failed`, err); + } + + return undefined; + } + async $isFileIgnored(handle: number, uri: UriComponents, token: CancellationToken): Promise { const provider = this._ignoredFileProviders.get(handle); if (!provider) { @@ -627,4 +649,15 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { this._ignoredFileProviders.delete(handle); }); } + + registerLanguageModelProxyProvider(extension: IExtensionDescription, provider: vscode.LanguageModelProxyProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'chatParticipantPrivate'); + + this._languageModelProxyProvider = provider; + return toDisposable(() => { + if (this._languageModelProxyProvider === provider) { + this._languageModelProxyProvider = undefined; + } + }); + } } diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index b8f290ec476..9f90021eeba 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -285,4 +285,16 @@ declare module 'vscode' { } // #endregion + + // #region LanguageModelProxyProvider + + export interface LanguageModelProxyProvider { + provideModelProxy(forExtensionId: string, token: CancellationToken): ProviderResult; + } + + export namespace lm { + export function registerLanguageModelProxyProvider(provider: LanguageModelProxyProvider): Disposable; + } + + // #endregion } diff --git a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts new file mode 100644 index 00000000000..32fcc0b0181 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + export interface LanguageModelProxyInfo { + readonly uri: Uri; + readonly key: string; + } + + export namespace lm { + /** + * Returns undefined if + * - The user is not logged in, or isn't the right SKU, with expected model access + * - The server fails to start for some reason + */ + export function getModelProxy(): Thenable; + } +} From 94e15a9bb978824c140732249ddbed4fe865d6ed Mon Sep 17 00:00:00 2001 From: Elijah King Date: Tue, 14 Oct 2025 21:40:52 -0700 Subject: [PATCH 1157/4355] revised prompt titles and tile width (#271399) * fixed prompt styling and full width * fixed font size * left right padding -2px --- .../contrib/chat/browser/chatWidget.ts | 2 +- .../chat/browser/media/chatViewWelcome.css | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index b14f706afeb..c570ca99cce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1520,7 +1520,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Build the final result array for (const { promptName } of topPrompts) { const description = this.promptDescriptionsCache.get(promptName); - const commandLabel = localize('chatWidget.promptFile.commandLabel', "/{0}", promptName); + const commandLabel = localize('chatWidget.promptFile.commandLabel', "{0}", promptName); const descriptionText = description?.trim() ? description : undefined; result.push({ icon: Codicon.run, diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index cdaed0b43cd..de042ba4d46 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -99,7 +99,6 @@ div.chat-welcome-view { & > .chat-welcome-view-title { font-size: 24px; margin-top: 5px; - font-weight: 500; text-align: center; line-height: normal; padding: 0 8px; @@ -215,8 +214,8 @@ div.chat-welcome-view { display: flex; flex-wrap: wrap; justify-content: flex-start; - row-gap: 8px; - padding: 32px 12px 12px 12px; + gap: 8px; + padding: 32px 16px 8px 16px; /* Extra top padding for title */ .chat-welcome-view-suggested-prompts-title { @@ -224,7 +223,6 @@ div.chat-welcome-view { top: 8px; left: 16px; font-size: 11px; - font-weight: normal; text-transform: uppercase; letter-spacing: 0.5px; color: var(--vscode-descriptionForeground); @@ -242,13 +240,14 @@ div.chat-welcome-view { background-color: var(--vscode-editorWidget-background); cursor: pointer; border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); - max-width: 260px; + max-width: 100%; width: fit-content; - margin: 0 4px; + margin: 0; + box-sizing: border-box; + flex: 0 0 auto; & > .chat-welcome-view-suggested-prompt-title { - font-size: 14px; - font-weight: 600; + font-size: 13px; color: var(--vscode-editorWidget-foreground); white-space: nowrap; } @@ -259,7 +258,7 @@ div.chat-welcome-view { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1; + flex: 0 1 auto; min-width: 0; } } @@ -312,7 +311,6 @@ div.chat-welcome-view { } .chat-welcome-history-header-title { - font-weight: 600; font-size: 11px; padding-left: 8px; } From f030344cf19e76e6b47d2d8ab003780a7fdb8171 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:42:44 -0700 Subject: [PATCH 1158/4355] Fix for empty name in chat sessions history (#270657) * Fix for empty name in chat sessions history * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/common/chatSessionStore.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatSessionStore.ts b/src/vs/workbench/contrib/chat/common/chatSessionStore.ts index a424fb36806..106924c254a 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionStore.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionStore.ts @@ -441,13 +441,11 @@ function isChatSessionIndex(data: unknown): data is IChatSessionIndexData { } function getSessionMetadata(session: ChatModel | ISerializableChatData): IChatSessionEntryMetadata { - const title = session instanceof ChatModel ? - session.customTitle || (session.getRequests().length > 0 ? ChatModel.getDefaultTitle(session.getRequests()) : '') : - session.customTitle ?? (session.requests.length > 0 ? ChatModel.getDefaultTitle(session.requests) : ''); + const title = session.customTitle || (session instanceof ChatModel ? session.title : undefined); return { sessionId: session.sessionId, - title, // Empty string for sessions without content - UI will handle display + title: title || localize('newChat', "New Chat"), lastMessageDate: session.lastMessageDate, isImported: session.isImported, initialLocation: session.initialLocation, From 179cd04f2a05766ef518dc9e612d0f0b6118ee62 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 15 Oct 2025 16:10:40 +0900 Subject: [PATCH 1159/4355] ci: upload logs from azure test pipeline (#270843) --- .../darwin/product-build-darwin-ci.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/build/azure-pipelines/darwin/product-build-darwin-ci.yml b/build/azure-pipelines/darwin/product-build-darwin-ci.yml index 6dfc202c3ce..3920c4ec799 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-ci.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-ci.yml @@ -10,6 +10,29 @@ jobs: timeoutInMinutes: 30 variables: VSCODE_ARCH: arm64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() steps: - template: ./steps/product-build-darwin-compile.yml@self parameters: From c099047be6edff8160ea5e0fb085bdfeeb519770 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:40:11 -0700 Subject: [PATCH 1160/4355] Update tests and small tweaks --- .../typescript-language-features/package.json | 4 +- .../package.nls.json | 2 +- .../codeLens/implementationsCodeLens.ts | 48 ++--- .../smoke/implementationsCodeLens.test.ts | 186 +++++++++++++++--- .../src/test/smoke/referencesCodeLens.test.ts | 4 +- 5 files changed, 179 insertions(+), 65 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 0a86124c140..2a04d409f29 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -218,10 +218,10 @@ "description": "%typescript.implementationsCodeLens.showOnInterfaceMethods%", "scope": "window" }, - "typescript.implementationsCodeLens.showOnClassMethods": { + "typescript.implementationsCodeLens.showOnAllClassMethods": { "type": "boolean", "default": false, - "description": "%typescript.implementationsCodeLens.showOnClassMethods.desc%", + "description": "%typescript.implementationsCodeLens.showOnAllClassMethods.desc%", "scope": "window" }, "typescript.reportStyleChecksAsWarnings": { diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 106e965b3f6..26b99390645 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -56,7 +56,7 @@ "typescript.referencesCodeLens.showOnAllFunctions": "Enable/disable references CodeLens on all functions in TypeScript files.", "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. This CodeLens shows the implementers of an interface.", "typescript.implementationsCodeLens.showOnInterfaceMethods": "Enable/disable implementations CodeLens on interface methods.", - "typescript.implementationsCodeLens.showOnClassMethods": "Enable/disable showing 'implementations' CodeLens above class methods.", + "typescript.implementationsCodeLens.showOnAllClassMethods": "Enable/disable showing implementations CodeLens above all class methods instead of only on abstract methods.", "typescript.openTsServerLog.title": "Open TS Server log", "typescript.restartTsServer": "Restart TS Server", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version...", diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index ed4627707f7..252dc0345c3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -26,7 +26,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip this._register( vscode.workspace.onDidChangeConfiguration(evt => { if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`) || - evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnClassMethods`)) { + evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnAllClassMethods`)) { this.changeEmitter.fire(); } }) @@ -70,12 +70,9 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip } private getCommand(locations: vscode.Location[], codeLens: ReferencesCodeLens): vscode.Command | undefined { - if (!locations.length) { - return undefined; - } return { title: this.getTitle(locations), - command: 'editor.action.showReferences', + command: locations.length ? 'editor.action.showReferences' : '', arguments: [codeLens.document, codeLens.range.start, locations] }; } @@ -93,47 +90,42 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip ): vscode.Range | undefined { const cfg = vscode.workspace.getConfiguration(this.language.id); - // Keep the class node itself so we enter children - if (item.kind === PConst.Kind.class) { - return getSymbolRange(document, item); - } - - // Keep the interface node itself so we enter children + // Always show on interfaces if (item.kind === PConst.Kind.interface) { return getSymbolRange(document, item); } - // Interface members (behind existing setting) + // Always show on abstract classes/properties if ( - item.kind === PConst.Kind.method && - parent?.kind === PConst.Kind.interface && - cfg.get('implementationsCodeLens.showOnInterfaceMethods') + (item.kind === PConst.Kind.class || + item.kind === PConst.Kind.method || + item.kind === PConst.Kind.memberVariable || + item.kind === PConst.Kind.memberGetAccessor || + item.kind === PConst.Kind.memberSetAccessor) && + /\babstract\b/.test(item.kindModifiers ?? '') ) { return getSymbolRange(document, item); } - // Skip private methods (cannot be overridden) - if (item.kind === PConst.Kind.method && /\bprivate\b/.test(item.kindModifiers ?? '')) { - return undefined; - } - - // Abstract members (always show) + // If configured, show interface members if ( - (item.kind === PConst.Kind.method || - item.kind === PConst.Kind.memberVariable || - item.kind === PConst.Kind.memberGetAccessor || - item.kind === PConst.Kind.memberSetAccessor) && - /\babstract\b/.test(item.kindModifiers ?? '') + item.kind === PConst.Kind.method && + parent?.kind === PConst.Kind.interface && + cfg.get('implementationsCodeLens.showOnInterfaceMethods', false) ) { return getSymbolRange(document, item); } - // Class methods (behind new setting; default off) + + // If configured show on all class methods if ( item.kind === PConst.Kind.method && parent?.kind === PConst.Kind.class && - cfg.get('implementationsCodeLens.showOnClassMethods', false) + cfg.get('implementationsCodeLens.showOnAllClassMethods', false) ) { + if (/\bprivate\b/.test(item.kindModifiers ?? '')) { + return undefined; + } return getSymbolRange(document, item); } diff --git a/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts b/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts index 3fed30cb6be..463f9a202fe 100644 --- a/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts +++ b/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts @@ -5,42 +5,164 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { joinLines, withRandomFileEditor } from "../testUtils"; +import { disposeAll } from '../../utils/dispose'; +import { joinLines, withRandomFileEditor } from '../testUtils'; +import { updateConfig, VsCodeConfiguration } from './referencesCodeLens.test'; -suite("TypeScript Implementations CodeLens", () => { - test("should show implementations code lens for overridden methods", async () => { +const Config = { + referencesCodeLens: 'typescript.referencesCodeLens.enabled', + implementationsCodeLens: 'typescript.implementationsCodeLens.enabled', + showOnAllClassMethods: 'typescript.implementationsCodeLens.showOnAllClassMethods', +}; + +function getCodeLenses(doc: vscode.TextDocument) { + return vscode.commands.executeCommand('vscode.executeCodeLensProvider', doc.uri); +} + +suite('TypeScript Implementations CodeLens', () => { + const configDefaults = Object.freeze({ + [Config.referencesCodeLens]: false, + [Config.implementationsCodeLens]: true, + [Config.showOnAllClassMethods]: false, + }); + + const _disposables: vscode.Disposable[] = []; + let oldConfig: { [key: string]: any } = {}; + + setup(async () => { + // the tests assume that typescript features are registered + await vscode.extensions.getExtension('vscode.typescript-language-features')!.activate(); + + // Save off config and apply defaults + oldConfig = await updateConfig(configDefaults); + }); + + teardown(async () => { + disposeAll(_disposables); + + // Restore config + await updateConfig(oldConfig); + + return vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('Should show on interfaces and abstract classes', async () => { + await withRandomFileEditor( + joinLines( + 'interface IFoo {}', + 'class Foo implements IFoo {}', + 'abstract class AbstractBase {}', + 'class Concrete extends AbstractBase {}' + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 2); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected interface IFoo to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 2, 'Expected abstract class AbstractBase to have a CodeLens'); + }, + ); + }); + + test('Should show on abstract methods, properties, and getters', async () => { + await withRandomFileEditor( + joinLines( + 'abstract class Base {', + ' abstract method(): void;', + ' abstract property: string;', + ' abstract get getter(): number;', + '}', + 'class Derived extends Base {', + ' method() {}', + ' property = "test";', + ' get getter() { return 42; }', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 4); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected abstract class to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected abstract method to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 2, 'Expected abstract property to have a CodeLens'); + assert.strictEqual(lenses?.[3].range.start.line, 3, 'Expected abstract getter to have a CodeLens'); + }, + ); + }); + + test('Should not show implementations on methods by default', async () => { await withRandomFileEditor( joinLines( - "abstract class A {", - " foo() {}", - "}", - "class B extends A {", - " foo() {}", - "}", + 'abstract class A {', + ' foo() {}', + '}', + 'class B extends A {', + ' foo() {}', + '}', ), - "ts", - async (editor: vscode.TextEditor, doc: vscode.TextDocument) => { - assert.strictEqual( - editor.document, - doc, - "Editor and document should match", - ); - - const lenses = await vscode.commands.executeCommand( - "vscode.executeCodeLensProvider", - doc.uri, - ); - - const fooLens = lenses?.find((lens) => - doc.getText(lens.range).includes("foo"), - ); - - assert.ok(fooLens, "Expected a CodeLens above foo()"); - assert.match( - fooLens!.command?.title ?? "", - /1 implementation/, - 'Expected lens to show "1 implementation"', - ); + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 1); + }, + ); + }); + + test('should show on all methods when showOnAllClassMethods is enabled', async () => { + await updateConfig({ + [Config.showOnAllClassMethods]: true + }); + + await withRandomFileEditor( + joinLines( + 'abstract class A {', + ' foo() {}', + '}', + 'class B extends A {', + ' foo() {}', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 3); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected class A to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected method A.foo to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 4, 'Expected method B.foo to have a CodeLens'); + }, + ); + }); + + test('should not show on private methods when showOnAllClassMethods is enabled', async () => { + await updateConfig({ + [Config.showOnAllClassMethods]: true + }); + + await withRandomFileEditor( + joinLines( + 'abstract class A {', + ' public foo() {}', + ' private bar() {}', + ' protected baz() {}', + '}', + 'class B extends A {', + ' public foo() {}', + ' protected baz() {}', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 5); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected class A to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected method A.foo to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 3, 'Expected method A.baz to have a CodeLens'); + assert.strictEqual(lenses?.[3].range.start.line, 6, 'Expected method B.foo to have a CodeLens'); + assert.strictEqual(lenses?.[4].range.start.line, 7, 'Expected method B.baz to have a CodeLens'); }, ); }); diff --git a/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts b/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts index f6259e0d276..7e068249a2e 100644 --- a/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts +++ b/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts @@ -10,9 +10,9 @@ import { createTestEditor, wait } from '../../test/testUtils'; import { disposeAll } from '../../utils/dispose'; -type VsCodeConfiguration = { [key: string]: any }; +export type VsCodeConfiguration = { [key: string]: any }; -async function updateConfig(newConfig: VsCodeConfiguration): Promise { +export async function updateConfig(newConfig: VsCodeConfiguration): Promise { const oldConfig: VsCodeConfiguration = {}; const config = vscode.workspace.getConfiguration(undefined); for (const configKey of Object.keys(newConfig)) { From c3bcc160ce6fc4f458c87347b018208f2fe28581 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:42:59 -0700 Subject: [PATCH 1161/4355] Doc fixes --- .../src/languageFeatures/codeLens/implementationsCodeLens.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index 252dc0345c3..d012a6ab9d9 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -107,7 +107,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip return getSymbolRange(document, item); } - // If configured, show interface members + // If configured, show on interface methods if ( item.kind === PConst.Kind.method && parent?.kind === PConst.Kind.interface && @@ -117,12 +117,13 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip } - // If configured show on all class methods + // If configured, show on all class methods if ( item.kind === PConst.Kind.method && parent?.kind === PConst.Kind.class && cfg.get('implementationsCodeLens.showOnAllClassMethods', false) ) { + // But not private ones as these can never be overridden if (/\bprivate\b/.test(item.kindModifiers ?? '')) { return undefined; } From 407983b4c0e61f2073e5030aa47ed2c1226e8f92 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 09:45:23 +0200 Subject: [PATCH 1162/4355] debt - more any reduction (#271458) --- .vscode/searches/no-any-casts.code-search | 823 ++++++++---------- src/vs/base/browser/browser.ts | 6 +- src/vs/base/browser/canIUse.ts | 3 +- src/vs/base/browser/deviceAccess.ts | 55 +- src/vs/base/common/objects.ts | 5 +- .../contextmenu/browser/contextmenu.ts | 7 +- .../assignment/common/assignmentFilters.ts | 3 +- 7 files changed, 416 insertions(+), 486 deletions(-) diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search index b6d6fd03c1c..ff6b2a40f13 100644 --- a/.vscode/searches/no-any-casts.code-search +++ b/.vscode/searches/no-any-casts.code-search @@ -1,45 +1,45 @@ # Query: // eslint-disable-next-line local/code-no-any-casts -838 results - 314 files +785 results - 287 files -extensions/css-language-features/client/src/cssClient.ts: +vscode • extensions/css-language-features/client/src/cssClient.ts: 86: // eslint-disable-next-line local/code-no-any-casts -extensions/css-language-features/server/src/cssServer.ts: +vscode • extensions/css-language-features/server/src/cssServer.ts: 71: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 171: // eslint-disable-next-line local/code-no-any-casts -extensions/git-base/src/api/api1.ts: +vscode • extensions/git-base/src/api/api1.ts: 17: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts -extensions/html-language-features/client/src/htmlClient.ts: +vscode • extensions/html-language-features/client/src/htmlClient.ts: 182: // eslint-disable-next-line local/code-no-any-casts -extensions/html-language-features/server/src/htmlServer.ts: +vscode • extensions/html-language-features/server/src/htmlServer.ts: 137: // eslint-disable-next-line local/code-no-any-casts 140: // eslint-disable-next-line local/code-no-any-casts 545: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/deserializers.ts: +vscode • extensions/ipynb/src/deserializers.ts: 23: // eslint-disable-next-line local/code-no-any-casts 294: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/helper.ts: +vscode • extensions/ipynb/src/helper.ts: 14: // eslint-disable-next-line local/code-no-any-casts 18: // eslint-disable-next-line local/code-no-any-casts 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 25: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/serializers.ts: +vscode • extensions/ipynb/src/serializers.ts: 40: // eslint-disable-next-line local/code-no-any-casts 61: // eslint-disable-next-line local/code-no-any-casts 403: // eslint-disable-next-line local/code-no-any-casts 405: // eslint-disable-next-line local/code-no-any-casts -extensions/ipynb/src/test/notebookModelStoreSync.test.ts: +vscode • extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 79: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts @@ -55,67 +55,55 @@ extensions/ipynb/src/test/notebookModelStoreSync.test.ts: 424: // eslint-disable-next-line local/code-no-any-casts 459: // eslint-disable-next-line local/code-no-any-casts -extensions/json-language-features/client/src/jsonClient.ts: +vscode • extensions/json-language-features/client/src/jsonClient.ts: 775: // eslint-disable-next-line local/code-no-any-casts -extensions/json-language-features/server/src/jsonServer.ts: +vscode • extensions/json-language-features/server/src/jsonServer.ts: 144: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/notebook/index.ts: +vscode • extensions/markdown-language-features/notebook/index.ts: 383: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/preview-src/index.ts: +vscode • extensions/markdown-language-features/preview-src/index.ts: 26: // eslint-disable-next-line local/code-no-any-casts 253: // eslint-disable-next-line local/code-no-any-casts 444: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/src/markdownEngine.ts: +vscode • extensions/markdown-language-features/src/markdownEngine.ts: 146: // eslint-disable-next-line local/code-no-any-casts -extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: - 53: // eslint-disable-next-line local/code-no-any-casts +vscode • extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: + 54: // eslint-disable-next-line local/code-no-any-casts -extensions/notebook-renderers/src/index.ts: +vscode • extensions/notebook-renderers/src/index.ts: 68: // eslint-disable-next-line local/code-no-any-casts -extensions/notebook-renderers/src/test/notebookRenderer.test.ts: +vscode • extensions/notebook-renderers/src/test/notebookRenderer.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts 137: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/extension.ts: +vscode • extensions/vscode-api-tests/src/extension.ts: 10: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts: 181: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts: 59: // eslint-disable-next-line local/code-no-any-casts 76: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: +vscode • extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts: 18: // eslint-disable-next-line local/code-no-any-casts -scripts/playground-server.ts: +vscode • scripts/playground-server.ts: 257: // eslint-disable-next-line local/code-no-any-casts 336: // eslint-disable-next-line local/code-no-any-casts 352: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/browser.ts: - 134: // eslint-disable-next-line local/code-no-any-casts - 141: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/canIUse.ts: - 36: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/deviceAccess.ts: - 26: // eslint-disable-next-line local/code-no-any-casts - 63: // eslint-disable-next-line local/code-no-any-casts - 92: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/browser/dom.ts: +vscode • src/vs/base/browser/dom.ts: 718: // eslint-disable-next-line local/code-no-any-casts 1324: // eslint-disable-next-line local/code-no-any-casts 1519: // eslint-disable-next-line local/code-no-any-casts @@ -130,116 +118,113 @@ src/vs/base/browser/dom.ts: 2443: // eslint-disable-next-line local/code-no-any-casts 2528: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/mouseEvent.ts: +vscode • src/vs/base/browser/mouseEvent.ts: 100: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts 155: // eslint-disable-next-line local/code-no-any-casts 157: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/trustedTypes.ts: - 19: // eslint-disable-next-line local/code-no-any-casts - 31: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/base/browser/trustedTypes.ts: + 21: // eslint-disable-next-line local/code-no-any-casts + 33: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/webWorkerFactory.ts: +vscode • src/vs/base/browser/webWorkerFactory.ts: 20: // eslint-disable-next-line local/code-no-any-casts 22: // eslint-disable-next-line local/code-no-any-casts 43: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/grid/grid.ts: +vscode • src/vs/base/browser/ui/grid/grid.ts: 66: // eslint-disable-next-line local/code-no-any-casts 873: // eslint-disable-next-line local/code-no-any-casts 875: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/grid/gridview.ts: +vscode • src/vs/base/browser/ui/grid/gridview.ts: 196: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/browser/ui/sash/sash.ts: +vscode • src/vs/base/browser/ui/sash/sash.ts: 491: // eslint-disable-next-line local/code-no-any-casts 497: // eslint-disable-next-line local/code-no-any-casts 503: // eslint-disable-next-line local/code-no-any-casts 505: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/console.ts: +vscode • src/vs/base/common/console.ts: 134: // eslint-disable-next-line local/code-no-any-casts 138: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/controlFlow.ts: +vscode • src/vs/base/common/controlFlow.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/decorators.ts: +vscode • src/vs/base/common/decorators.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/errors.ts: +vscode • src/vs/base/common/errors.ts: 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/hotReload.ts: +vscode • src/vs/base/common/hotReload.ts: 97: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/hotReloadHelpers.ts: +vscode • src/vs/base/common/hotReloadHelpers.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/lifecycle.ts: +vscode • src/vs/base/common/lifecycle.ts: 236: // eslint-disable-next-line local/code-no-any-casts 246: // eslint-disable-next-line local/code-no-any-casts 257: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/marshalling.ts: +vscode • src/vs/base/common/marshalling.ts: 53: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts 57: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/network.ts: +vscode • src/vs/base/common/network.ts: 416: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/objects.ts: - 75: // eslint-disable-next-line local/code-no-any-casts - -src/vs/base/common/skipList.ts: +vscode • src/vs/base/common/skipList.ts: 38: // eslint-disable-next-line local/code-no-any-casts 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/types.ts: - 58: // eslint-disable-next-line local/code-no-any-casts - 66: // eslint-disable-next-line local/code-no-any-casts - 268: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/base/common/types.ts: + 65: // eslint-disable-next-line local/code-no-any-casts + 73: // eslint-disable-next-line local/code-no-any-casts + 275: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/uriIpc.ts: +vscode • src/vs/base/common/uriIpc.ts: 33: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/verifier.ts: +vscode • src/vs/base/common/verifier.ts: 82: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/changeTracker.ts: +vscode • src/vs/base/common/observableInternal/changeTracker.ts: 34: // eslint-disable-next-line local/code-no-any-casts 42: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/debugLocation.ts: +vscode • src/vs/base/common/observableInternal/debugLocation.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/debugName.ts: +vscode • src/vs/base/common/observableInternal/debugName.ts: 106: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/set.ts: +vscode • src/vs/base/common/observableInternal/set.ts: 51: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/experimental/reducer.ts: +vscode • src/vs/base/common/observableInternal/experimental/reducer.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: +vscode • src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: +vscode • src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: 12: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/logging/debugger/rpc.ts: +vscode • src/vs/base/common/observableInternal/logging/debugger/rpc.ts: 94: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/derived.ts: +vscode • src/vs/base/common/observableInternal/observables/derived.ts: 38: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts 124: // eslint-disable-next-line local/code-no-any-casts @@ -247,75 +232,76 @@ src/vs/base/common/observableInternal/observables/derived.ts: 160: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/derivedImpl.ts: +vscode • src/vs/base/common/observableInternal/observables/derivedImpl.ts: 313: // eslint-disable-next-line local/code-no-any-casts 414: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/observables/observableFromEvent.ts: +vscode • src/vs/base/common/observableInternal/observables/observableFromEvent.ts: 151: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/reactions/autorunImpl.ts: +vscode • src/vs/base/common/observableInternal/reactions/autorunImpl.ts: 185: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/common/observableInternal/utils/utilsCancellation.ts: +vscode • src/vs/base/common/observableInternal/utils/utilsCancellation.ts: 78: // eslint-disable-next-line local/code-no-any-casts 83: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/parts/ipc/test/node/ipc.net.test.ts: +vscode • src/vs/base/parts/ipc/test/node/ipc.net.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 92: // eslint-disable-next-line local/code-no-any-casts 652: // eslint-disable-next-line local/code-no-any-casts 785: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/buffer.test.ts: +vscode • src/vs/base/test/common/buffer.test.ts: 501: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/decorators.test.ts: +vscode • src/vs/base/test/common/decorators.test.ts: 130: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/filters.test.ts: +vscode • src/vs/base/test/common/filters.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/glob.test.ts: +vscode • src/vs/base/test/common/glob.test.ts: 497: // eslint-disable-next-line local/code-no-any-casts 518: // eslint-disable-next-line local/code-no-any-casts 763: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/json.test.ts: +vscode • src/vs/base/test/common/json.test.ts: 52: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/mock.ts: - 13: // eslint-disable-next-line local/code-no-any-casts - 22: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/base/test/common/mock.ts: + 14: // eslint-disable-next-line local/code-no-any-casts + 23: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/oauth.test.ts: - 1099: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/base/test/common/oauth.test.ts: + 1100: // eslint-disable-next-line local/code-no-any-casts + 1572: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/snapshot.ts: +vscode • src/vs/base/test/common/snapshot.ts: 123: // eslint-disable-next-line local/code-no-any-casts 125: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/timeTravelScheduler.ts: +vscode • src/vs/base/test/common/timeTravelScheduler.ts: 268: // eslint-disable-next-line local/code-no-any-casts 278: // eslint-disable-next-line local/code-no-any-casts 311: // eslint-disable-next-line local/code-no-any-casts 317: // eslint-disable-next-line local/code-no-any-casts 333: // eslint-disable-next-line local/code-no-any-casts -src/vs/base/test/common/troubleshooting.ts: +vscode • src/vs/base/test/common/troubleshooting.ts: 50: // eslint-disable-next-line local/code-no-any-casts 55: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/editor.api.ts: +vscode • src/vs/editor/editor.api.ts: 44: // eslint-disable-next-line local/code-no-any-casts 46: // eslint-disable-next-line local/code-no-any-casts 51: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/browser/config/editorConfiguration.ts: +vscode • src/vs/editor/browser/config/editorConfiguration.ts: 147: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/controller/mouseTarget.ts: +vscode • src/vs/editor/browser/controller/mouseTarget.ts: 992: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 996: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 1000: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any @@ -324,43 +310,43 @@ src/vs/editor/browser/controller/mouseTarget.ts: 1099: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 1119: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: +vscode • src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: 81: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 85: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/gpu/gpuUtils.ts: +vscode • src/vs/editor/browser/gpu/gpuUtils.ts: 52: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/gpu/viewGpuContext.ts: +vscode • src/vs/editor/browser/gpu/viewGpuContext.ts: 226: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: +vscode • src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: 179: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: +vscode • src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: 477: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/widget/diffEditor/utils.ts: +vscode • src/vs/editor/browser/widget/diffEditor/utils.ts: 184: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 195: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 303: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 310: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: +vscode • src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: 75: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: +vscode • src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: 100: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 103: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/common/textModelEditSource.ts: +vscode • src/vs/editor/common/textModelEditSource.ts: 59: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 70: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/common/core/edits/stringEdit.ts: +vscode • src/vs/editor/common/core/edits/stringEdit.ts: 24: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: +vscode • src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: 26: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 30: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 51: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any @@ -379,54 +365,51 @@ src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: 177: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any 196: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: +vscode • src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: 100: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/contextmenu/browser/contextmenu.ts: - 232: // eslint-disable-next-line local/code-no-any-casts - -src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: +vscode • src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: 200: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: +vscode • src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: 97: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/find/browser/findModel.ts: +vscode • src/vs/editor/contrib/find/browser/findModel.ts: 556: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/find/test/browser/findController.test.ts: +vscode • src/vs/editor/contrib/find/test/browser/findController.test.ts: 79: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: 640: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: 274: // eslint-disable-next-line local/code-no-any-casts 296: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: 346: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: +vscode • src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: 23: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: 163: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: +vscode • src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: 240: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: - 797: // eslint-disable-next-line local/code-no-any-casts - 816: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: + 794: // eslint-disable-next-line local/code-no-any-casts + 813: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/browser/standaloneEditor.ts: +vscode • src/vs/editor/standalone/browser/standaloneEditor.ts: 504: // eslint-disable-next-line local/code-no-any-casts 506: // eslint-disable-next-line local/code-no-any-casts 508: // eslint-disable-next-line local/code-no-any-casts @@ -465,7 +448,7 @@ src/vs/editor/standalone/browser/standaloneEditor.ts: 614: // eslint-disable-next-line local/code-no-any-casts 619: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/browser/standaloneLanguages.ts: +vscode • src/vs/editor/standalone/browser/standaloneLanguages.ts: 753: // eslint-disable-next-line local/code-no-any-casts 755: // eslint-disable-next-line local/code-no-any-casts 757: // eslint-disable-next-line local/code-no-any-casts @@ -504,157 +487,157 @@ src/vs/editor/standalone/browser/standaloneLanguages.ts: 849: // eslint-disable-next-line local/code-no-any-casts 851: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/standalone/common/monarch/monarchCompile.ts: +vscode • src/vs/editor/standalone/common/monarch/monarchCompile.ts: 461: // eslint-disable-next-line local/code-no-any-casts 539: // eslint-disable-next-line local/code-no-any-casts 556: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/browser/testCodeEditor.ts: +vscode • src/vs/editor/test/browser/testCodeEditor.ts: 279: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/browser/config/editorConfiguration.test.ts: +vscode • src/vs/editor/test/browser/config/editorConfiguration.test.ts: 90: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/textModel.test.ts: +vscode • src/vs/editor/test/common/model/textModel.test.ts: 1167: // eslint-disable-next-line local/code-no-any-casts -src/vs/editor/test/common/model/textModelWithTokens.test.ts: +vscode • src/vs/editor/test/common/model/textModelWithTokens.test.ts: 272: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/contextkey/common/contextkey.ts: +vscode • src/vs/platform/contextkey/common/contextkey.ts: 939: // eslint-disable-next-line local/code-no-any-casts 1213: // eslint-disable-next-line local/code-no-any-casts 1273: // eslint-disable-next-line local/code-no-any-casts 1334: // eslint-disable-next-line local/code-no-any-casts 1395: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/contextkey/test/common/contextkey.test.ts: +vscode • src/vs/platform/contextkey/test/common/contextkey.test.ts: 96: // eslint-disable-next-line local/code-no-any-casts 98: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/environment/test/node/argv.test.ts: +vscode • src/vs/platform/environment/test/node/argv.test.ts: 47: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: +vscode • src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: 243: // eslint-disable-next-line local/code-no-any-casts 245: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: +vscode • src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts: 405: // eslint-disable-next-line local/code-no-any-casts 407: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: +vscode • src/vs/platform/extensionManagement/common/implicitActivationEvents.ts: 73: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/files/browser/htmlFileSystemProvider.ts: +vscode • src/vs/platform/files/browser/htmlFileSystemProvider.ts: 311: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/platform/files/test/node/diskFileService.integrationTest.ts: +vscode • src/vs/platform/files/test/node/diskFileService.integrationTest.ts: 106: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 112: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/instantiation/common/instantiationService.ts: +vscode • src/vs/platform/instantiation/common/instantiationService.ts: 328: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/list/browser/listService.ts: +vscode • src/vs/platform/list/browser/listService.ts: 877: // eslint-disable-next-line local/code-no-any-casts 918: // eslint-disable-next-line local/code-no-any-casts 965: // eslint-disable-next-line local/code-no-any-casts 1012: // eslint-disable-next-line local/code-no-any-casts 1057: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/observable/common/wrapInHotClass.ts: +vscode • src/vs/platform/observable/common/wrapInHotClass.ts: 12: // eslint-disable-next-line local/code-no-any-casts 40: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/observable/common/wrapInReloadableClass.ts: +vscode • src/vs/platform/observable/common/wrapInReloadableClass.ts: 31: // eslint-disable-next-line local/code-no-any-casts 38: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/policy/node/nativePolicyService.ts: +vscode • src/vs/platform/policy/node/nativePolicyService.ts: 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/profiling/common/profilingTelemetrySpec.ts: +vscode • src/vs/platform/profiling/common/profilingTelemetrySpec.ts: 73: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/quickinput/browser/tree/quickTree.ts: - 73: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/platform/quickinput/browser/tree/quickTree.ts: + 74: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/quickinput/test/browser/quickinput.test.ts: +vscode • src/vs/platform/quickinput/test/browser/quickinput.test.ts: 69: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/browser/browserSocketFactory.ts: +vscode • src/vs/platform/remote/browser/browserSocketFactory.ts: 89: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/common/remoteAgentConnection.ts: +vscode • src/vs/platform/remote/common/remoteAgentConnection.ts: 784: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: +vscode • src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: 19: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/request/electron-utility/requestService.ts: +vscode • src/vs/platform/request/electron-utility/requestService.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/terminal/node/terminalProcess.ts: +vscode • src/vs/platform/terminal/node/terminalProcess.ts: 546: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/userDataSync/common/extensionsSync.ts: +vscode • src/vs/platform/userDataSync/common/extensionsSync.ts: 60: // eslint-disable-next-line local/code-no-any-casts 64: // eslint-disable-next-line local/code-no-any-casts -src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: +vscode • src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: 22: // eslint-disable-next-line local/code-no-any-casts -src/vs/server/node/extensionHostConnection.ts: +vscode • src/vs/server/node/extensionHostConnection.ts: 240: // eslint-disable-next-line local/code-no-any-casts -src/vs/server/node/remoteExtensionHostAgentServer.ts: +vscode • src/vs/server/node/remoteExtensionHostAgentServer.ts: 765: // eslint-disable-next-line local/code-no-any-casts 767: // eslint-disable-next-line local/code-no-any-casts 769: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/workbench.web.main.internal.ts: +vscode • src/vs/workbench/workbench.web.main.internal.ts: 198: // eslint-disable-next-line local/code-no-any-casts 223: // eslint-disable-next-line local/code-no-any-casts 225: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/workbench.web.main.ts: +vscode • src/vs/workbench/workbench.web.main.ts: 58: // eslint-disable-next-line local/code-no-any-casts 60: // eslint-disable-next-line local/code-no-any-casts 82: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadExtensionService.ts: +vscode • src/vs/workbench/api/browser/mainThreadExtensionService.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: - 1000: // eslint-disable-next-line local/code-no-any-casts - 1011: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: + 1013: // eslint-disable-next-line local/code-no-any-casts + 1024: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/mainThreadQuickOpen.ts: +vscode • src/vs/workbench/api/browser/mainThreadQuickOpen.ts: 195: // eslint-disable-next-line local/code-no-any-casts 198: // eslint-disable-next-line local/code-no-any-casts 203: // eslint-disable-next-line local/code-no-any-casts 216: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/browser/viewsExtensionPoint.ts: +vscode • src/vs/workbench/api/browser/viewsExtensionPoint.ts: 528: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHost.api.impl.ts: +vscode • src/vs/workbench/api/common/extHost.api.impl.ts: 161: // eslint-disable-next-line local/code-no-any-casts 315: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts 563: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHost.protocol.ts: - 2106: // eslint-disable-next-line local/code-no-any-casts - 2108: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/api/common/extHost.protocol.ts: + 2109: // eslint-disable-next-line local/code-no-any-casts + 2111: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostDebugService.ts: +vscode • src/vs/workbench/api/common/extHostDebugService.ts: 243: // eslint-disable-next-line local/code-no-any-casts 491: // eslint-disable-next-line local/code-no-any-casts 493: // eslint-disable-next-line local/code-no-any-casts @@ -663,76 +646,76 @@ src/vs/workbench/api/common/extHostDebugService.ts: 770: // eslint-disable-next-line local/code-no-any-casts 778: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: +vscode • src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: 65: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostExtensionActivator.ts: +vscode • src/vs/workbench/api/common/extHostExtensionActivator.ts: 405: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostExtensionService.ts: +vscode • src/vs/workbench/api/common/extHostExtensionService.ts: 566: // eslint-disable-next-line local/code-no-any-casts 1009: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageFeatures.ts: +vscode • src/vs/workbench/api/common/extHostLanguageFeatures.ts: 197: // eslint-disable-next-line local/code-no-any-casts 714: // eslint-disable-next-line local/code-no-any-casts 735: // eslint-disable-next-line local/code-no-any-casts 748: // eslint-disable-next-line local/code-no-any-casts 771: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageModels.ts: - 174: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/api/common/extHostLanguageModels.ts: + 175: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostLanguageModelTools.ts: +vscode • src/vs/workbench/api/common/extHostLanguageModelTools.ts: 221: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostMcp.ts: +vscode • src/vs/workbench/api/common/extHostMcp.ts: 163: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts 168: // eslint-disable-next-line local/code-no-any-casts 170: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostSearch.ts: +vscode • src/vs/workbench/api/common/extHostSearch.ts: 221: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTerminalService.ts: +vscode • src/vs/workbench/api/common/extHostTerminalService.ts: 1287: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTimeline.ts: +vscode • src/vs/workbench/api/common/extHostTimeline.ts: 160: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts 169: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/common/extHostTypeConverters.ts: +vscode • src/vs/workbench/api/common/extHostTypeConverters.ts: 463: // eslint-disable-next-line local/code-no-any-casts 856: // eslint-disable-next-line local/code-no-any-casts - 3142: // eslint-disable-next-line local/code-no-any-casts - 3144: // eslint-disable-next-line local/code-no-any-casts - 3146: // eslint-disable-next-line local/code-no-any-casts - 3148: // eslint-disable-next-line local/code-no-any-casts - 3150: // eslint-disable-next-line local/code-no-any-casts - 3152: // eslint-disable-next-line local/code-no-any-casts - 3154: // eslint-disable-next-line local/code-no-any-casts - 3156: // eslint-disable-next-line local/code-no-any-casts - 3163: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/api/common/extHostTypes.ts: + 3173: // eslint-disable-next-line local/code-no-any-casts + 3175: // eslint-disable-next-line local/code-no-any-casts + 3177: // eslint-disable-next-line local/code-no-any-casts + 3179: // eslint-disable-next-line local/code-no-any-casts + 3181: // eslint-disable-next-line local/code-no-any-casts + 3183: // eslint-disable-next-line local/code-no-any-casts + 3185: // eslint-disable-next-line local/code-no-any-casts + 3187: // eslint-disable-next-line local/code-no-any-casts + 3194: // eslint-disable-next-line local/code-no-any-casts + +vscode • src/vs/workbench/api/common/extHostTypes.ts: 3175: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extensionHostProcess.ts: +vscode • src/vs/workbench/api/node/extensionHostProcess.ts: 107: // eslint-disable-next-line local/code-no-any-casts 119: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extHostConsoleForwarder.ts: +vscode • src/vs/workbench/api/node/extHostConsoleForwarder.ts: 31: // eslint-disable-next-line local/code-no-any-casts 53: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/extHostMcpNode.ts: +vscode • src/vs/workbench/api/node/extHostMcpNode.ts: 57: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/node/proxyResolver.ts: +vscode • src/vs/workbench/api/node/proxyResolver.ts: 92: // eslint-disable-next-line local/code-no-any-casts 95: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts @@ -741,21 +724,21 @@ src/vs/workbench/api/node/proxyResolver.ts: 132: // eslint-disable-next-line local/code-no-any-casts 373: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: - 870: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: + 874: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: +vscode • src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: 75: // eslint-disable-next-line local/code-no-any-casts 164: // eslint-disable-next-line local/code-no-any-casts 173: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostCommands.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostCommands.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: 750: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts 48: // eslint-disable-next-line local/code-no-any-casts 50: // eslint-disable-next-line local/code-no-any-casts @@ -763,17 +746,17 @@ src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: 54: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: 1068: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: 164: // eslint-disable-next-line local/code-no-any-casts 166: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 107: // eslint-disable-next-line local/code-no-any-casts 109: // eslint-disable-next-line local/code-no-any-casts 111: // eslint-disable-next-line local/code-no-any-casts @@ -781,55 +764,55 @@ src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: 121: // eslint-disable-next-line local/code-no-any-casts 128: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTesting.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTesting.test.ts: 640: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: 265: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 327: // eslint-disable-next-line local/code-no-any-casts 340: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostTypes.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostTypes.test.ts: 87: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts 209: // eslint-disable-next-line local/code-no-any-casts 211: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: +vscode • src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: 541: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: 115: // eslint-disable-next-line local/code-no-any-casts 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts 93: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: 115: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: +vscode • src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: 60: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/extensionHostMain.test.ts: +vscode • src/vs/workbench/api/test/common/extensionHostMain.test.ts: 80: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: +vscode • src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: 86: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/common/testRPCProtocol.ts: +vscode • src/vs/workbench/api/test/common/testRPCProtocol.ts: 36: // eslint-disable-next-line local/code-no-any-casts 163: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/test/node/extHostSearch.test.ts: +vscode • src/vs/workbench/api/test/node/extHostSearch.test.ts: 177: // eslint-disable-next-line local/code-no-any-casts 1004: // eslint-disable-next-line local/code-no-any-casts 1050: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/worker/extensionHostWorker.ts: +vscode • src/vs/workbench/api/worker/extensionHostWorker.ts: 83: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 87: // eslint-disable-next-line local/code-no-any-casts @@ -843,35 +826,35 @@ src/vs/workbench/api/worker/extensionHostWorker.ts: 106: // eslint-disable-next-line local/code-no-any-casts 158: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/api/worker/extHostConsoleForwarder.ts: +vscode • src/vs/workbench/api/worker/extHostConsoleForwarder.ts: 20: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/browser/actions/developerActions.ts: +vscode • src/vs/workbench/browser/actions/developerActions.ts: 781: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any -src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: +vscode • src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: 54: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: +vscode • src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: 29: // eslint-disable-next-line local/code-no-any-casts 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: + 86: // eslint-disable-next-line local/code-no-any-casts 88: // eslint-disable-next-line local/code-no-any-casts - 90: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: +vscode • src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: 126: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatModel.ts: +vscode • src/vs/workbench/contrib/chat/common/chatModel.ts: 1214: // eslint-disable-next-line local/code-no-any-casts - 1531: // eslint-disable-next-line local/code-no-any-casts - 1863: // eslint-disable-next-line local/code-no-any-casts + 1537: // eslint-disable-next-line local/code-no-any-casts + 1869: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: - 439: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: + 437: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: 30: // eslint-disable-next-line local/code-no-any-casts 35: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts @@ -892,14 +875,14 @@ src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test 1532: // eslint-disable-next-line local/code-no-any-casts 1537: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: 41: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: +vscode • src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts: 35: // eslint-disable-next-line local/code-no-any-casts 144: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: +vscode • src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts 84: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts @@ -923,273 +906,219 @@ src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: 330: // eslint-disable-next-line local/code-no-any-casts 346: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: +vscode • src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: 16: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/browser/debugSession.ts: +vscode • src/vs/workbench/contrib/debug/browser/debugSession.ts: 1193: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts: - 46: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/browser/rawDebugSession.ts: - 813: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/common/debugger.ts: - 168: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/common/debugUtils.ts: - 66: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: 450: // eslint-disable-next-line local/code-no-any-casts 466: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/callStack.test.ts: - 28: // eslint-disable-next-line local/code-no-any-casts - 124: // eslint-disable-next-line local/code-no-any-casts - 206: // eslint-disable-next-line local/code-no-any-casts - 261: // eslint-disable-next-line local/code-no-any-casts - 461: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: 92: // eslint-disable-next-line local/code-no-any-casts 129: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: 76: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts: - 19: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/browser/repl.test.ts: +vscode • src/vs/workbench/contrib/debug/test/browser/repl.test.ts: 139: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: - 50: // eslint-disable-next-line local/code-no-any-casts - 62: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: 70: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/debug/test/common/mockDebug.ts: - 695: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: +vscode • src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: 15: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: +vscode • src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: 147: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: +vscode • src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: 1182: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: +vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 101: // eslint-disable-next-line local/code-no-any-casts 103: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: +vscode • src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: 100: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 104: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: +vscode • src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: 46: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: +vscode • src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: 94: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: +vscode • src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: 160: // eslint-disable-next-line local/code-no-any-casts 662: // eslint-disable-next-line local/code-no-any-casts 711: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: +vscode • src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markers/browser/markersTable.ts: +vscode • src/vs/workbench/contrib/markers/browser/markersTable.ts: 343: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: +vscode • src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: 143: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mcp/common/uriTemplate.ts: - 159: // eslint-disable-next-line local/code-no-any-casts - 191: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/mcp/test/common/mcpRegistry.test.ts: - 68: // eslint-disable-next-line local/code-no-any-casts - 212: // eslint-disable-next-line local/code-no-any-casts - 218: // eslint-disable-next-line local/code-no-any-casts - 262: // eslint-disable-next-line local/code-no-any-casts - 264: // eslint-disable-next-line local/code-no-any-casts - 271: // eslint-disable-next-line local/code-no-any-casts - 280: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/mcp/test/common/mcpResourceFilesystem.test.ts: - 53: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts: - 18: // eslint-disable-next-line local/code-no-any-casts - 52: // eslint-disable-next-line local/code-no-any-casts - 95: // eslint-disable-next-line local/code-no-any-casts - 128: // eslint-disable-next-line local/code-no-any-casts - 147: // eslint-disable-next-line local/code-no-any-casts - 168: // eslint-disable-next-line local/code-no-any-casts - 220: // eslint-disable-next-line local/code-no-any-casts - 231: // eslint-disable-next-line local/code-no-any-casts - 254: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/mergeEditor/browser/utils.ts: +vscode • src/vs/workbench/contrib/mergeEditor/browser/utils.ts: 89: // eslint-disable-next-line local/code-no-any-casts 99: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: +vscode • src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: 69: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: +vscode • src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts: 309: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: +vscode • src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts: 127: // eslint-disable-next-line local/code-no-any-casts 199: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: +vscode • src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: 74: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: +vscode • src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: 122: // eslint-disable-next-line local/code-no-any-casts 1462: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts: 329: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts: 170: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts: 75: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: +vscode • src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts: 424: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: - 563: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts: + 575: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: - 1095: // eslint-disable-next-line local/code-no-any-casts - 1137: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts: + 1110: // eslint-disable-next-line local/code-no-any-casts + 1152: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/notebookCellLayoutManager.test.ts: 50: // eslint-disable-next-line local/code-no-any-casts 66: // eslint-disable-next-line local/code-no-any-casts 85: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts: 284: // eslint-disable-next-line local/code-no-any-casts 308: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts: 37: // eslint-disable-next-line local/code-no-any-casts 91: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts: 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/diff/editorHeightCalculator.test.ts: 26: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: +vscode • src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiff.test.ts: 652: // eslint-disable-next-line local/code-no-any-casts 654: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchActionsFind.ts: +vscode • src/vs/workbench/contrib/search/browser/searchActionsFind.ts: 460: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchMessage.ts: +vscode • src/vs/workbench/contrib/search/browser/searchMessage.ts: 51: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: +vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: 306: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: +vscode • src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: 299: // eslint-disable-next-line local/code-no-any-casts 301: // eslint-disable-next-line local/code-no-any-casts 318: // eslint-disable-next-line local/code-no-any-casts 324: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: +vscode • src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: 201: // eslint-disable-next-line local/code-no-any-casts 229: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: 205: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: 155: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: +vscode • src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: 328: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: - 1492: // eslint-disable-next-line local/code-no-any-casts - 1501: // eslint-disable-next-line local/code-no-any-casts - 1540: // eslint-disable-next-line local/code-no-any-casts - 1586: // eslint-disable-next-line local/code-no-any-casts - 1740: // eslint-disable-next-line local/code-no-any-casts - 1787: // eslint-disable-next-line local/code-no-any-casts - 1790: // eslint-disable-next-line local/code-no-any-casts - 2656: // eslint-disable-next-line local/code-no-any-casts - 2837: // eslint-disable-next-line local/code-no-any-casts - 3569: // eslint-disable-next-line local/code-no-any-casts - 3603: // eslint-disable-next-line local/code-no-any-casts - 3609: // eslint-disable-next-line local/code-no-any-casts - 3738: // eslint-disable-next-line local/code-no-any-casts - 3797: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/tasks/common/problemMatcher.ts: +vscode • src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: + 1491: // eslint-disable-next-line local/code-no-any-casts + 1500: // eslint-disable-next-line local/code-no-any-casts + 1539: // eslint-disable-next-line local/code-no-any-casts + 1585: // eslint-disable-next-line local/code-no-any-casts + 1739: // eslint-disable-next-line local/code-no-any-casts + 1786: // eslint-disable-next-line local/code-no-any-casts + 1789: // eslint-disable-next-line local/code-no-any-casts + 2655: // eslint-disable-next-line local/code-no-any-casts + 2836: // eslint-disable-next-line local/code-no-any-casts + 3568: // eslint-disable-next-line local/code-no-any-casts + 3602: // eslint-disable-next-line local/code-no-any-casts + 3608: // eslint-disable-next-line local/code-no-any-casts + 3737: // eslint-disable-next-line local/code-no-any-casts + 3796: // eslint-disable-next-line local/code-no-any-casts + +vscode • src/vs/workbench/contrib/tasks/common/problemMatcher.ts: 361: // eslint-disable-next-line local/code-no-any-casts 374: // eslint-disable-next-line local/code-no-any-casts 1015: // eslint-disable-next-line local/code-no-any-casts 1906: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: +vscode • src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: 1720: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/common/tasks.ts: - 655: // eslint-disable-next-line local/code-no-any-casts - 696: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/tasks/common/tasks.ts: + 667: // eslint-disable-next-line local/code-no-any-casts + 708: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: +vscode • src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: 84: // eslint-disable-next-line local/code-no-any-casts 86: // eslint-disable-next-line local/code-no-any-casts 89: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts: 99: // eslint-disable-next-line local/code-no-any-casts 445: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProcessManager.test.ts: 55: // eslint-disable-next-line local/code-no-any-casts 96: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: - 101: // eslint-disable-next-line local/code-no-any-casts - 127: // eslint-disable-next-line local/code-no-any-casts - 172: // eslint-disable-next-line local/code-no-any-casts - 227: // eslint-disable-next-line local/code-no-any-casts - 244: // eslint-disable-next-line local/code-no-any-casts - 261: // eslint-disable-next-line local/code-no-any-casts - 275: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.integrationTest.ts: + 104: // eslint-disable-next-line local/code-no-any-casts + 130: // eslint-disable-next-line local/code-no-any-casts + 175: // eslint-disable-next-line local/code-no-any-casts + 230: // eslint-disable-next-line local/code-no-any-casts + 247: // eslint-disable-next-line local/code-no-any-casts + 264: // eslint-disable-next-line local/code-no-any-casts + 278: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 58: // eslint-disable-next-line local/code-no-any-casts 65: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1205,7 +1134,7 @@ src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts: 165: // eslint-disable-next-line local/code-no-any-casts 178: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts: 28: // eslint-disable-next-line local/code-no-any-casts 34: // eslint-disable-next-line local/code-no-any-casts 42: // eslint-disable-next-line local/code-no-any-casts @@ -1222,7 +1151,7 @@ src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilitySt 135: // eslint-disable-next-line local/code-no-any-casts 144: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integrationTest.ts: 59: // eslint-disable-next-line local/code-no-any-casts 67: // eslint-disable-next-line local/code-no-any-casts 75: // eslint-disable-next-line local/code-no-any-casts @@ -1232,39 +1161,39 @@ src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.integ 107: // eslint-disable-next-line local/code-no-any-casts 165: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: +vscode • src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 70: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: 71: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: 40: // eslint-disable-next-line local/code-no-any-casts 56: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: - 379: // eslint-disable-next-line local/code-no-any-casts - 833: // eslint-disable-next-line local/code-no-any-casts - 857: // eslint-disable-next-line local/code-no-any-casts - 862: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts: + 380: // eslint-disable-next-line local/code-no-any-casts + 834: // eslint-disable-next-line local/code-no-any-casts + 858: // eslint-disable-next-line local/code-no-any-casts + 863: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: 102: // eslint-disable-next-line local/code-no-any-casts 108: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: 242: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: 150: // eslint-disable-next-line local/code-no-any-casts 290: // eslint-disable-next-line local/code-no-any-casts 553: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 69: // eslint-disable-next-line local/code-no-any-casts @@ -1276,113 +1205,77 @@ src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDete 136: // eslint-disable-next-line local/code-no-any-casts 142: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: - 787: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts: + 786: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: +vscode • src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: 39: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts: - 123: // eslint-disable-next-line local/code-no-any-casts - 125: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/common/observableUtils.ts: - 17: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/common/testingStates.ts: - 78: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/browser/codeCoverageDecorations.test.ts: - 19: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: +vscode • src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/testing/test/browser/explorerProjections/nameProjection.test.ts: - 35: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts: - 42: // eslint-disable-next-line local/code-no-any-casts - 137: // eslint-disable-next-line local/code-no-any-casts - 154: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts: - 27: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/common/testProfileService.test.ts: - 49: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/common/testResultService.test.ts: - 210: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/testing/test/common/testStubs.ts: - 85: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/contrib/themes/browser/themes.contribution.ts: +vscode • src/vs/workbench/contrib/themes/browser/themes.contribution.ts: 614: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: +vscode • src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: 600: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/assignment/common/assignmentFilters.ts: - 104: // eslint-disable-next-line local/code-no-any-casts - -src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: +vscode • src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: 236: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: +vscode • src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: 81: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 306: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/driver/browser/driver.ts: +vscode • src/vs/workbench/services/driver/browser/driver.ts: 193: // eslint-disable-next-line local/code-no-any-casts 215: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: +vscode • src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts: 61: // eslint-disable-next-line local/code-no-any-casts 63: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/common/extensionsRegistry.ts: +vscode • src/vs/workbench/services/extensions/common/extensionsRegistry.ts: 229: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: +vscode • src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: 49: // eslint-disable-next-line local/code-no-any-casts 54: // eslint-disable-next-line local/code-no-any-casts 97: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts 107: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: +vscode • src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: 185: // eslint-disable-next-line local/code-no-any-casts 222: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: +vscode • src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: 47: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/search/common/search.ts: +vscode • src/vs/workbench/services/search/common/search.ts: 628: // eslint-disable-next-line local/code-no-any-casts 631: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/search/node/rawSearchService.ts: +vscode • src/vs/workbench/services/search/node/rawSearchService.ts: 438: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: +vscode • src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: 44: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: +vscode • src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: 147: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/browser/fileIconThemeData.ts: +vscode • src/vs/workbench/services/themes/browser/fileIconThemeData.ts: 122: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/browser/productIconThemeData.ts: +vscode • src/vs/workbench/services/themes/browser/productIconThemeData.ts: 123: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/themes/common/colorThemeData.ts: +vscode • src/vs/workbench/services/themes/common/colorThemeData.ts: 650: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: +vscode • src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: 67: // eslint-disable-next-line local/code-no-any-casts 74: // eslint-disable-next-line local/code-no-any-casts 102: // eslint-disable-next-line local/code-no-any-casts @@ -1406,7 +1299,7 @@ src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: 755: // eslint-disable-next-line local/code-no-any-casts 833: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: +vscode • src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: 25: // eslint-disable-next-line local/code-no-any-casts 27: // eslint-disable-next-line local/code-no-any-casts 335: // eslint-disable-next-line local/code-no-any-casts @@ -1416,54 +1309,54 @@ src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: 645: // eslint-disable-next-line local/code-no-any-casts 678: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/part.test.ts: +vscode • src/vs/workbench/test/browser/part.test.ts: 133: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/window.test.ts: +vscode • src/vs/workbench/test/browser/window.test.ts: 42: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/workbenchTestServices.ts: - 304: // eslint-disable-next-line local/code-no-any-casts - 696: // eslint-disable-next-line local/code-no-any-casts - 1063: // eslint-disable-next-line local/code-no-any-casts - 1966: // eslint-disable-next-line local/code-no-any-casts - 1984: // eslint-disable-next-line local/code-no-any-casts +vscode • src/vs/workbench/test/browser/workbenchTestServices.ts: + 305: // eslint-disable-next-line local/code-no-any-casts + 698: // eslint-disable-next-line local/code-no-any-casts + 1065: // eslint-disable-next-line local/code-no-any-casts + 1968: // eslint-disable-next-line local/code-no-any-casts + 1986: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: 98: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: 131: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: +vscode • src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: 95: // eslint-disable-next-line local/code-no-any-casts 106: // eslint-disable-next-line local/code-no-any-casts 113: // eslint-disable-next-line local/code-no-any-casts 120: // eslint-disable-next-line local/code-no-any-casts 127: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/common/resources.test.ts: +vscode • src/vs/workbench/test/common/resources.test.ts: 51: // eslint-disable-next-line local/code-no-any-casts 59: // eslint-disable-next-line local/code-no-any-casts 72: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/common/workbenchTestServices.ts: +vscode • src/vs/workbench/test/common/workbenchTestServices.ts: 293: // eslint-disable-next-line local/code-no-any-casts -src/vs/workbench/test/electron-browser/workbenchTestServices.ts: +vscode • src/vs/workbench/test/electron-browser/workbenchTestServices.ts: 255: // eslint-disable-next-line local/code-no-any-casts -test/automation/src/code.ts: +vscode • test/automation/src/code.ts: 127: // eslint-disable-next-line local/code-no-any-casts -test/automation/src/terminal.ts: +vscode • test/automation/src/terminal.ts: 315: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/application.ts: +vscode • test/mcp/src/application.ts: 309: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/playwright.ts: +vscode • test/mcp/src/playwright.ts: 17: // eslint-disable-next-line local/code-no-any-casts -test/mcp/src/automationTools/problems.ts: +vscode • test/mcp/src/automationTools/problems.ts: 76: // eslint-disable-next-line local/code-no-any-casts diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 13b937daf32..5a9ad48587f 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -131,13 +131,11 @@ export function isStandalone(): boolean { // e.g. visible is true even in fullscreen mode where the controls are hidden // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/visible export function isWCOEnabled(): boolean { - // eslint-disable-next-line local/code-no-any-casts - return (navigator as any)?.windowControlsOverlay?.visible; + return !!(navigator as Navigator & { windowControlsOverlay?: { visible: boolean } })?.windowControlsOverlay?.visible; } // Returns the bounding rect of the titlebar area if it is supported and defined // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { - // eslint-disable-next-line local/code-no-any-casts - return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); + return (targetWindow.navigator as Navigator & { windowControlsOverlay?: { getTitlebarAreaRect: () => DOMRect } })?.windowControlsOverlay?.getTitlebarAreaRect(); } diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index 39fe06ce9d8..d7c129abb27 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -33,8 +33,7 @@ export const BrowserFeatures = { return KeyboardSupport.Always; } - // eslint-disable-next-line local/code-no-any-casts - if ((navigator).keyboard || browser.isSafari) { + if ((navigator as Navigator & { keyboard?: unknown }).keyboard || browser.isSafari) { return KeyboardSupport.FullScreen; } diff --git a/src/vs/base/browser/deviceAccess.ts b/src/vs/base/browser/deviceAccess.ts index 9114a5c3d8d..793825e98fe 100644 --- a/src/vs/base/browser/deviceAccess.ts +++ b/src/vs/base/browser/deviceAccess.ts @@ -5,6 +5,27 @@ // https://wicg.github.io/webusb/ +interface UsbDevice { + readonly deviceClass: number; + readonly deviceProtocol: number; + readonly deviceSubclass: number; + readonly deviceVersionMajor: number; + readonly deviceVersionMinor: number; + readonly deviceVersionSubminor: number; + readonly manufacturerName?: string; + readonly productId: number; + readonly productName?: string; + readonly serialNumber?: string; + readonly usbVersionMajor: number; + readonly usbVersionMinor: number; + readonly usbVersionSubminor: number; + readonly vendorId: number; +} + +interface USB { + requestDevice(options: { filters: unknown[] }): Promise; +} + export interface UsbDeviceData { readonly deviceClass: number; readonly deviceProtocol: number; @@ -23,8 +44,7 @@ export interface UsbDeviceData { } export async function requestUsbDevice(options?: { filters?: unknown[] }): Promise { - // eslint-disable-next-line local/code-no-any-casts - const usb = (navigator as any).usb; + const usb = (navigator as Navigator & { usb?: USB }).usb; if (!usb) { return undefined; } @@ -54,14 +74,26 @@ export async function requestUsbDevice(options?: { filters?: unknown[] }): Promi // https://wicg.github.io/serial/ +interface SerialPortInfo { + readonly usbVendorId?: number | undefined; + readonly usbProductId?: number | undefined; +} + +interface SerialPort { + getInfo(): SerialPortInfo; +} + +interface Serial { + requestPort(options: { filters: unknown[] }): Promise; +} + export interface SerialPortData { readonly usbVendorId?: number | undefined; readonly usbProductId?: number | undefined; } export async function requestSerialPort(options?: { filters?: unknown[] }): Promise { - // eslint-disable-next-line local/code-no-any-casts - const serial = (navigator as any).serial; + const serial = (navigator as Navigator & { serial?: Serial }).serial; if (!serial) { return undefined; } @@ -80,6 +112,18 @@ export async function requestSerialPort(options?: { filters?: unknown[] }): Prom // https://wicg.github.io/webhid/ +interface HidDevice { + readonly opened: boolean; + readonly vendorId: number; + readonly productId: number; + readonly productName: string; + readonly collections: []; +} + +interface HID { + requestDevice(options: { filters: unknown[] }): Promise; +} + export interface HidDeviceData { readonly opened: boolean; readonly vendorId: number; @@ -89,8 +133,7 @@ export interface HidDeviceData { } export async function requestHidDevice(options?: { filters?: unknown[] }): Promise { - // eslint-disable-next-line local/code-no-any-casts - const hid = (navigator as any).hid; + const hid = (navigator as Navigator & { hid?: HID }).hid; if (!hid) { return undefined; } diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index f25572a931e..e8e60b1ea92 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -69,11 +69,10 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): throw new Error('Cannot clone recursive data-structure'); } seen.add(obj); - const r2 = {}; + const r2: Record = {}; for (const i2 in obj) { if (_hasOwnProperty.call(obj, i2)) { - // eslint-disable-next-line local/code-no-any-casts - (r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen); + r2[i2] = _cloneAndChange(obj[i2], changer, seen); } } seen.delete(obj); diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 547f5a7800f..a1b8b00bd48 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -229,10 +229,9 @@ export class ContextMenuController implements IEditorContribution { return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true }); } - // eslint-disable-next-line local/code-no-any-casts - const customActionViewItem = action; - if (typeof customActionViewItem.getActionViewItem === 'function') { - return customActionViewItem.getActionViewItem(); + const customAction = action as IAction & { getActionViewItem?: () => ActionViewItem }; + if (typeof customAction.getActionViewItem === 'function') { + return customAction.getActionViewItem(); } return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true }); diff --git a/src/vs/workbench/services/assignment/common/assignmentFilters.ts b/src/vs/workbench/services/assignment/common/assignmentFilters.ts index a7f586e085d..57535dbd67d 100644 --- a/src/vs/workbench/services/assignment/common/assignmentFilters.ts +++ b/src/vs/workbench/services/assignment/common/assignmentFilters.ts @@ -101,8 +101,7 @@ export class CopilotAssignmentFilterProvider extends Disposable implements IExpe copilotExtensionVersion = copilotExtension?.version; copilotChatExtensionVersion = copilotChatExtension?.version; - // eslint-disable-next-line local/code-no-any-casts - copilotCompletionsVersion = (copilotChatExtension as any)?.completionsCoreVersion; + copilotCompletionsVersion = (copilotChatExtension as typeof copilotChatExtension & { completionsCoreVersion?: string })?.completionsCoreVersion; } catch (error) { this._logService.error('Failed to update extension version assignments', error); } From e91079c1c6910f8c11253e1388185dfad56fab6a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 09:49:39 +0200 Subject: [PATCH 1163/4355] chat - fix leaks in setup (#271459) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 69135c53f4b..b3542d7dcbc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -595,7 +595,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } -class SetupTool extends Disposable implements IToolImpl { +class SetupTool implements IToolImpl { static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable { return instantiationService.invokeFunction(accessor => { @@ -628,7 +628,7 @@ class SetupTool extends Disposable implements IToolImpl { } } -class DefaultNewSymbolNamesProvider extends Disposable { +class DefaultNewSymbolNamesProvider { static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): IDisposable { return instantiationService.invokeFunction(accessor => { @@ -636,7 +636,7 @@ class DefaultNewSymbolNamesProvider extends Disposable { const disposables = new DisposableStore(); - const provider = disposables.add(instantiationService.createInstance(DefaultNewSymbolNamesProvider, context, controller)); + const provider = instantiationService.createInstance(DefaultNewSymbolNamesProvider, context, controller); disposables.add(languageFeaturesService.newSymbolNamesProvider.register('*', provider)); return disposables; @@ -649,7 +649,6 @@ class DefaultNewSymbolNamesProvider extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, ) { - super(); } async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise { From 9fd3e2a63f6d82b7044f7511c7c357a2ebdbed92 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 09:52:01 +0200 Subject: [PATCH 1164/4355] status - make icon size `16px` to align with the rest of workbench (#271460) --- src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 73ac7aa6719..cee2a874fad 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -164,7 +164,6 @@ .monaco-workbench .part.statusbar > .items-container > .statusbar-item span.codicon { text-align: center; - font-size: 14px; color: inherit; } From 663e913a7abc4bec2826c7cf818745b04b824486 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 10:04:03 +0200 Subject: [PATCH 1165/4355] remote - only use `remote` kind for status entry when connected to a remote (#271461) --- src/vs/workbench/contrib/remote/browser/remoteIndicator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 7f1e082e7fd..732e59e4ae2 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -562,7 +562,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr const properties: IStatusbarEntry = { name: nls.localize('remoteHost', "Remote Host"), - kind: this.networkState === 'offline' ? 'offline' : 'remote', + kind: this.networkState === 'offline' ? 'offline' : this.remoteAuthority ? 'remote' : undefined, ariaLabel, text, showProgress, From 4b1087a5bacc0de41dcaf042ec860da032741a37 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 10:09:14 +0200 Subject: [PATCH 1166/4355] Cannot read properties of null (reading 'webContents') (fix #271157) (#271463) * Cannot read properties of null (reading 'webContents') (fix #271157) * polish --- src/vs/platform/windows/electron-main/windowImpl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 1254f93b7e2..abcd9ad3d53 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1576,7 +1576,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private async startCollectingJScallStacks(): Promise { if (!this.jsCallStackCollector.isTriggered()) { - const stack = await this._win.webContents.mainFrame.collectJavaScriptCallStack(); + const stack = await this._win?.webContents.mainFrame.collectJavaScriptCallStack(); // Increment the count for this stack trace if (stack) { @@ -1604,7 +1604,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // If the stack appears more than 20 percent of the time, log it // to the error telemetry as UnresponsiveSampleError. if (Math.round((count * 100) / this.jsCallStackEffectiveSampleCount) > 20) { - const fakeError = new UnresponsiveError(stack, this.id, this.win?.webContents.getOSProcessId()); + const fakeError = new UnresponsiveError(stack, this.id, this._win?.webContents.getOSProcessId()); errorHandler.onUnexpectedError(fakeError); } logMessage += `<${count}> ${stack}\n`; From e1b305adb7c87f49382419af1b619ff42779b866 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Oct 2025 10:25:45 +0200 Subject: [PATCH 1167/4355] support openSearch api (#271468) --- src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts | 6 ++++++ src/vs/workbench/contrib/mcp/common/mcpTypes.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index b9a39dacc34..a087f9b596f 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -40,6 +40,7 @@ import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpWorkbenchService, IW import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { IMcpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { IPager, singlePagePager } from '../../../../base/common/paging.js'; +import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; interface IMcpServerStateProvider { (mcpWorkbenchServer: McpWorkbenchServer): T; @@ -192,6 +193,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IURLService urlService: IURLService, ) { super(); @@ -674,6 +676,10 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return true; } + async openSearch(searchValue: string, preserveFoucs?: boolean): Promise { + await this.extensionsWorkbenchService.openSearch(`@mcp ${searchValue}`, preserveFoucs); + } + async open(extension: IWorkbenchMcpServer, options?: IEditorOptions): Promise { await this.editorService.openEditor(this.instantiationService.createInstance(McpServerEditorInput, extension), options, ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index c4482878fde..7afabf87bc1 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -717,6 +717,7 @@ export interface IMcpWorkbenchService { uninstall(mcpServer: IWorkbenchMcpServer): Promise; getMcpConfigPath(arg: IWorkbenchLocalMcpServer): IMcpConfigPath | undefined; getMcpConfigPath(arg: URI): Promise; + openSearch(searchValue: string, preserveFoucs?: boolean): Promise; open(extension: IWorkbenchMcpServer | string, options?: IMcpServerEditorOptions): Promise; } From 472fe20604032e312592fac94cac9838ae03597a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:32:15 +0200 Subject: [PATCH 1168/4355] Engineering - fix hard link error during SBOM generation (#271469) --- .../linux/steps/product-build-linux-compile.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index 73f3a9de3ed..2dcefe9a68f 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -396,4 +396,9 @@ steps: mkdir -p $(Build.ArtifactStagingDirectory)/out/snap mv $(SNAP_PATH) $(Build.ArtifactStagingDirectory)/out/snap/$(SNAP_PACKAGE_NAME) fi + + # SBOM generation uses hard links which are not supported by the Linux kernel + # for files that have the SUID bit set, so we need to remove the SUID bit from + # the chrome-sandbox file. + chmod u-s $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox displayName: Move artifacts to out directory From 1c35a4d05ca05a9218a4887d96ad314f9ea04a97 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 01:51:43 -0700 Subject: [PATCH 1169/4355] Enforce single quote string usage in extension tests Single quoted string usage is already enforced everywhere except our tests. Having this inconsistent style can confuse contributors and code generation Starting with converting over tests in the `extensions` dir --- extensions/emmet/src/test/completion.test.ts | 4 +- extensions/github/src/test/github.test.ts | 10 +-- .../src/test/linkify.test.ts | 2 +- .../src/test/notebookRenderer.test.ts | 28 ++++---- .../src/fig/shell-parser/test/command.test.ts | 36 +++++----- .../src/test/unit/functionCallSnippet.test.ts | 10 +-- .../src/test/unit/textRendering.test.ts | 70 +++++++++---------- .../singlefolder-tests/documentPaste.test.ts | 2 +- .../src/singlefolder-tests/ipynb.test.ts | 24 +++---- .../src/singlefolder-tests/terminal.test.ts | 2 +- .../src/colorizer.test.ts | 2 +- 11 files changed, 95 insertions(+), 95 deletions(-) diff --git a/extensions/emmet/src/test/completion.test.ts b/extensions/emmet/src/test/completion.test.ts index 4f74ba92e25..bf61f338f21 100644 --- a/extensions/emmet/src/test/completion.test.ts +++ b/extensions/emmet/src/test/completion.test.ts @@ -41,14 +41,14 @@ suite('Tests for completion in CSS embedded in HTML', () => { { label: 'widows: ;', documentation: `widows: |;` } ]); } catch (e) { - assert.strictEqual(e.message, "Didn't find completion item with label widows: ;"); + assert.strictEqual(e.message, `Didn't find completion item with label widows: ;`); } try { await testCompletionProvider('css', `.foo { wid| }`, [ { label: 'widows: ;', documentation: `widows: |;` } ]); } catch (e) { - assert.strictEqual(e.message, "Didn't find completion item with label widows: ;"); + assert.strictEqual(e.message, `Didn't find completion item with label widows: ;`); } await testCompletionProvider('css', `.foo { wido| }`, [ { label: 'widows: ;', documentation: `widows: |;` } diff --git a/extensions/github/src/test/github.test.ts b/extensions/github/src/test/github.test.ts index db0eba515cb..838fd37923e 100644 --- a/extensions/github/src/test/github.test.ts +++ b/extensions/github/src/test/github.test.ts @@ -39,11 +39,11 @@ suite('github smoke test', function () { }); test('selecting non-default quick-pick item should correspond to a template', async () => { - const template0 = Uri.file("some-imaginary-template-0"); - const template1 = Uri.file("some-imaginary-template-1"); + const template0 = Uri.file('some-imaginary-template-0'); + const template1 = Uri.file('some-imaginary-template-1'); const templates = [template0, template1]; - const pick = pickPullRequestTemplate(Uri.file("/"), templates); + const pick = pickPullRequestTemplate(Uri.file('/'), templates); await commands.executeCommand('workbench.action.quickOpenSelectNext'); await commands.executeCommand('workbench.action.quickOpenSelectNext'); @@ -53,9 +53,9 @@ suite('github smoke test', function () { }); test('selecting first quick-pick item should return undefined', async () => { - const templates = [Uri.file("some-imaginary-file")]; + const templates = [Uri.file('some-imaginary-file')]; - const pick = pickPullRequestTemplate(Uri.file("/"), templates); + const pick = pickPullRequestTemplate(Uri.file('/'), templates); await commands.executeCommand('workbench.action.quickOpenSelectNext'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); diff --git a/extensions/notebook-renderers/src/test/linkify.test.ts b/extensions/notebook-renderers/src/test/linkify.test.ts index cae8f569423..d24145a2b62 100644 --- a/extensions/notebook-renderers/src/test/linkify.test.ts +++ b/extensions/notebook-renderers/src/test/linkify.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { JSDOM } from "jsdom"; +import { JSDOM } from 'jsdom'; import { LinkDetector, linkify } from '../linkify'; const dom = new JSDOM(); diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index def7a1282ab..999116152c8 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { activate } from '..'; import { RendererApi } from 'vscode-notebook-renderer'; import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes'; -import { JSDOM } from "jsdom"; +import { JSDOM } from 'jsdom'; import { LinkDetector } from '../linkify'; const dom = new JSDOM(); @@ -16,12 +16,12 @@ global.document = dom.window.document; suite('Notebook builtin output renderer', () => { const error = { - name: "TypeError", - message: "Expected type `str`, but received type ``", - stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)" + - "\u001b[1;32mc:\\src\\test\\ws1\\testing.py\u001b[0m in \u001b[0;36mline 2\n\u001b[0;32m 35\u001b[0m \u001b[39m# %%\u001b[39;00m\n\u001b[1;32m----> 36\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mTypeError\u001b[39;00m(\u001b[39m'\u001b[39m\u001b[39merror = f\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mExpected type `str`, but received type `\u001b[39m\u001b[39m{\u001b[39m\u001b[39mtype(name)}`\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m'\u001b[39m)\n" + - "\u001b[1;31mTypeError\u001b[0m: Expected type `str`, but received type ``\"" + name: 'TypeError', + message: 'Expected type `str`, but received type ``', + stack: '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m' + + '\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)' + + '\u001b[1;32mc:\\src\\test\\ws1\\testing.py\u001b[0m in \u001b[0;36mline 2\n\u001b[0;32m 35\u001b[0m \u001b[39m# %%\u001b[39;00m\n\u001b[1;32m----> 36\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mTypeError\u001b[39;00m(\u001b[39m\'\u001b[39m\u001b[39merror = f\u001b[39m\u001b[39m"\u001b[39m\u001b[39mExpected type `str`, but received type `\u001b[39m\u001b[39m{\u001b[39m\u001b[39mtype(name)}`\u001b[39m\u001b[39m"\u001b[39m\u001b[39m\'\u001b[39m)\n' + + '\u001b[1;31mTypeError\u001b[0m: Expected type `str`, but received type ``"' }; const errorMimeType = 'application/vnd.code.notebook.error'; @@ -489,13 +489,13 @@ suite('Notebook builtin output renderer', () => { }); const rawIPythonError = { - name: "NameError", - message: "name 'x' is not defined", - stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)" + - "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mmyfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n" + - "Cell \u001b[1;32mIn[1], line 2\u001b[0m, in \u001b[0;36mmyfunc\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmyfunc\u001b[39m():\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mx\u001b[49m)\n" + - "\u001b[1;31mNameError\u001b[0m: name 'x' is not defined" + name: 'NameError', + message: `name 'x' is not defined`, + stack: '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m' + + '\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)' + + 'Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mmyfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n' + + 'Cell \u001b[1;32mIn[1], line 2\u001b[0m, in \u001b[0;36mmyfunc\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmyfunc\u001b[39m():\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mx\u001b[49m)\n' + + `\u001b[1;31mNameError\u001b[0m: name 'x' is not defined` }; test(`Should clean up raw IPython error stack traces`, async () => { diff --git a/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts b/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts index cc050512374..46d22dd6533 100644 --- a/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts +++ b/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts @@ -4,35 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual } from 'node:assert'; -import { getCommand, Command } from "../command"; +import { getCommand, Command } from '../command'; -suite("fig/shell-parser/ getCommand", () => { +suite('fig/shell-parser/ getCommand', () => { const aliases = { - woman: "man", - quote: "'q'", - g: "git", + woman: 'man', + quote: `'q'`, + g: 'git', }; const getTokenText = (command: Command | null) => command?.tokens.map((token) => token.text) ?? []; - test("works without matching aliases", () => { - deepStrictEqual(getTokenText(getCommand("git co ", {})), ["git", "co", ""]); - deepStrictEqual(getTokenText(getCommand("git co ", aliases)), ["git", "co", ""]); - deepStrictEqual(getTokenText(getCommand("woman ", {})), ["woman", ""]); - deepStrictEqual(getTokenText(getCommand("another string ", aliases)), [ - "another", - "string", - "", + test('works without matching aliases', () => { + deepStrictEqual(getTokenText(getCommand('git co ', {})), ['git', 'co', '']); + deepStrictEqual(getTokenText(getCommand('git co ', aliases)), ['git', 'co', '']); + deepStrictEqual(getTokenText(getCommand('woman ', {})), ['woman', '']); + deepStrictEqual(getTokenText(getCommand('another string ', aliases)), [ + 'another', + 'string', + '', ]); }); - test("works with regular aliases", () => { + test('works with regular aliases', () => { // Don't change a single token. - deepStrictEqual(getTokenText(getCommand("woman", aliases)), ["woman"]); + deepStrictEqual(getTokenText(getCommand('woman', aliases)), ['woman']); // Change first token if length > 1. - deepStrictEqual(getTokenText(getCommand("woman ", aliases)), ["man", ""]); + deepStrictEqual(getTokenText(getCommand('woman ', aliases)), ['man', '']); // Don't change later tokens. - deepStrictEqual(getTokenText(getCommand("man woman ", aliases)), ["man", "woman", ""]); + deepStrictEqual(getTokenText(getCommand('man woman ', aliases)), ['man', 'woman', '']); // Handle quotes - deepStrictEqual(getTokenText(getCommand("quote ", aliases)), ["q", ""]); + deepStrictEqual(getTokenText(getCommand('quote ', aliases)), ['q', '']); }); }); diff --git a/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts index 00f006a05a4..f0f1874a639 100644 --- a/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts @@ -139,7 +139,7 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'alpha', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'beta', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'gamma', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:alpha}, ${2:beta}, ${3:gamma}$4)$0'); }); @@ -148,7 +148,7 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'alpha', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'beta', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'gamma', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:alpha}$2)$0'); }); @@ -158,9 +158,9 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "b", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "c", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, - { "text": "d", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "e", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, - { "text": "g", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "h", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'a', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'b', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'c', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, + { 'text': 'd', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'e', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'f', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, + { 'text': 'g', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'h', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:a}, ${2:b}, ${3:c}, ${4:d}, ${5:e}, ${6:f}$7)$0'); }); diff --git a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts index 278bf9de412..f4245b59297 100644 --- a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts +++ b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts @@ -29,7 +29,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "https://api.jquery.com/bind/#bind-eventType-eventData-handler", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'https://api.jquery.com/bind/#bind-eventType-eventData-handler', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -40,7 +40,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo abc xyz', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar b a z', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -51,7 +51,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@linkcode ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@linkplain ", "kind": "link" }, { "text": "http://www.example.com/bar", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@linkcode ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@linkplain ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -64,7 +64,7 @@ suite('typescript.previewer', () => { { name: 'param', // a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z - text: [{ "text": "a", "kind": "parameterName" }, { "text": " ", "kind": "space" }, { "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + text: [{ 'text': 'a', 'kind': 'parameterName' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo abc xyz', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar b a z', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], } ], noopToResource), '*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); @@ -133,23 +133,23 @@ suite('typescript.previewer', () => { assert.strictEqual( tagsToMarkdown([ { - "name": "example", - "text": [ + 'name': 'example', + 'text': [ { - "text": "1 + 1 ", - "kind": "text" + 'text': '1 + 1 ', + 'kind': 'text' }, { - "text": "{@link ", - "kind": "link" + 'text': '{@link ', + 'kind': 'link' }, { - "text": "foo", - "kind": "linkName" + 'text': 'foo', + 'kind': 'linkName' }, { - "text": "}", - "kind": "link" + 'text': '}', + 'kind': 'link' } ] } @@ -160,19 +160,19 @@ suite('typescript.previewer', () => { test('Should render @linkcode symbol name as code', () => { assert.strictEqual( asPlainTextWithLinks([ - { "text": "a ", "kind": "text" }, - { "text": "{@linkcode ", "kind": "link" }, + { 'text': 'a ', 'kind': 'text' }, + { 'text': '{@linkcode ', 'kind': 'link' }, { - "text": "dog", - "kind": "linkName", - "target": { - "file": "/path/file.ts", - "start": { "line": 7, "offset": 5 }, - "end": { "line": 7, "offset": 13 } + 'text': 'dog', + 'kind': 'linkName', + 'target': { + 'file': '/path/file.ts', + 'start': { 'line': 7, 'offset': 5 }, + 'end': { 'line': 7, 'offset': 13 } } } as SymbolDisplayPart, - { "text": "}", "kind": "link" }, - { "text": " b", "kind": "text" } + { 'text': '}', 'kind': 'link' }, + { 'text': ' b', 'kind': 'text' } ], noopToResource), 'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); }); @@ -180,20 +180,20 @@ suite('typescript.previewer', () => { test('Should render @linkcode text as code', () => { assert.strictEqual( asPlainTextWithLinks([ - { "text": "a ", "kind": "text" }, - { "text": "{@linkcode ", "kind": "link" }, + { 'text': 'a ', 'kind': 'text' }, + { 'text': '{@linkcode ', 'kind': 'link' }, { - "text": "dog", - "kind": "linkName", - "target": { - "file": "/path/file.ts", - "start": { "line": 7, "offset": 5 }, - "end": { "line": 7, "offset": 13 } + 'text': 'dog', + 'kind': 'linkName', + 'target': { + 'file': '/path/file.ts', + 'start': { 'line': 7, 'offset': 5 }, + 'end': { 'line': 7, 'offset': 13 } } } as SymbolDisplayPart, - { "text": "husky", "kind": "linkText" }, - { "text": "}", "kind": "link" }, - { "text": " b", "kind": "text" } + { 'text': 'husky', 'kind': 'linkText' }, + { 'text': '}', 'kind': 'link' }, + { 'text': ' b', 'kind': 'text' } ], noopToResource), 'a [`husky`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts index b4212bb6103..39179d0d1e3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts @@ -208,7 +208,7 @@ suite.skip('vscode API - Copy Paste', function () { }); function reverseString(str: string) { - return str.split("").reverse().join(""); + return str.split('').reverse().join(''); } function getNextDocumentText(disposables: vscode.Disposable[], doc: vscode.TextDocument): Promise { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts index dafd4df1e68..5cfdf8fe8f2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts @@ -9,24 +9,24 @@ import * as vscode from 'vscode'; import { assertNoRpc, closeAllEditors, createRandomFile } from '../utils'; const ipynbContent = JSON.stringify({ - "cells": [ + 'cells': [ { - "cell_type": "markdown", - "source": ["## Header"], - "metadata": {} + 'cell_type': 'markdown', + 'source': ['## Header'], + 'metadata': {} }, { - "cell_type": "code", - "execution_count": 2, - "source": ["print('hello 1')\n", "print('hello 2')"], - "outputs": [ + 'cell_type': 'code', + 'execution_count': 2, + 'source': [`print('hello 1')\n`, `print('hello 2')`], + 'outputs': [ { - "output_type": "stream", - "name": "stdout", - "text": ["hello 1\n", "hello 2\n"] + 'output_type': 'stream', + 'name': 'stdout', + 'text': ['hello 1\n', 'hello 2\n'] } ], - "metadata": {} + 'metadata': {} } ] }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index bb0c59bd32a..ae71c459d29 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -28,7 +28,7 @@ import { assertNoRpc, poll } from '../utils'; // Disable env var relaunch for tests to prevent terminals relaunching themselves await config.update('environmentChangesRelaunch', false, ConfigurationTarget.Global); // Disable local echo in case it causes any problems in remote tests - await config.update('localEchoEnabled', "off", ConfigurationTarget.Global); + await config.update('localEchoEnabled', 'off', ConfigurationTarget.Global); await config.update('shellIntegration.enabled', false); }); diff --git a/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts b/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts index 7e1df20ca29..2076a96d6b3 100644 --- a/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts +++ b/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts @@ -131,7 +131,7 @@ suite('Tokenization Performance', () => { suiteSetup(async function () { originalSettingValue = workspace.getConfiguration('editor').get('experimental.preferTreeSitter'); - await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', ["typescript"], ConfigurationTarget.Global); + await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', ['typescript'], ConfigurationTarget.Global); }); suiteTeardown(async function () { await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', originalSettingValue, ConfigurationTarget.Global); From 41cc8c5b2c81385642f68d2fd961036c43168bb8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Oct 2025 11:02:26 +0200 Subject: [PATCH 1170/4355] remove some resources from additional details - align with github mcp registry (#271474) --- .../contrib/mcp/browser/mcpServerEditor.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 74eeffbd521..6b3c30a545d 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -903,28 +903,7 @@ class AdditionalDetailsWidget extends Disposable { const supportUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.ContactSupportUri); if (supportUri) { try { - resources.push([localize('support', "Support"), ThemeIcon.fromId(Codicon.commentDiscussion.id), URI.parse(supportUri)]); - } catch (error) {/* Ignore */ } - } - - const privacyUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.PrivacyPolicyUri); - if (privacyUri) { - try { - resources.push([localize('privacy', "Privacy Policy"), ThemeIcon.fromId(Codicon.law.id), URI.parse(privacyUri)]); - } catch (error) {/* Ignore */ } - } - - const termsUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.TermsOfServiceUri); - if (termsUri) { - try { - resources.push([localize('terms', "Terms of Service"), ThemeIcon.fromId(Codicon.law.id), URI.parse(termsUri)]); - } catch (error) {/* Ignore */ } - } - - const reportUri = getMcpGalleryManifestResourceUri(manifest, McpGalleryResourceType.ReportUri); - if (reportUri) { - try { - resources.push([localize('report', "Report abuse"), ThemeIcon.fromId(Codicon.report.id), URI.parse(reportUri)]); + resources.push([localize('support', "Contact Support"), ThemeIcon.fromId(Codicon.commentDiscussion.id), URI.parse(supportUri)]); } catch (error) {/* Ignore */ } } } From 8d8a2936f43d1199c7256b64b9e0cd668a038e7a Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Wed, 15 Oct 2025 12:05:50 +0300 Subject: [PATCH 1171/4355] fix: memory leak in output view (#221605) * fix: memory leak in output view * simplify code --- src/vs/workbench/contrib/output/browser/outputView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index cf69f38c55e..e0d64c4acd2 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -190,7 +190,7 @@ export class OutputViewPane extends FilterViewPane { const input = this.createInput(channel); if (!this.editor.input || !input.matches(this.editor.input)) { this.editorPromise?.cancel(); - this.editorPromise = createCancelablePromise(token => this.editor.setInput(this.createInput(channel), { preserveFocus: true }, Object.create(null), token)); + this.editorPromise = createCancelablePromise(token => this.editor.setInput(input, { preserveFocus: true }, Object.create(null), token)); } } From a72525b82a1e9d5f1f99333634882f2c7c3751d9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:33:05 +0200 Subject: [PATCH 1172/4355] Engineering - use sudo to remove the SUID bit (#271483) --- .../azure-pipelines/linux/steps/product-build-linux-compile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index 2dcefe9a68f..950faecccef 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -400,5 +400,5 @@ steps: # SBOM generation uses hard links which are not supported by the Linux kernel # for files that have the SUID bit set, so we need to remove the SUID bit from # the chrome-sandbox file. - chmod u-s $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox + sudo chmod u-s $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox displayName: Move artifacts to out directory From 56d04418cc40471a8d329401d8dc91c3b6913975 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 11:33:44 +0200 Subject: [PATCH 1173/4355] chore - update `@vscode/iconv-lite-umd` to `0.7.1` (#271472) * chore - update `@vscode/iconv-lite-umd` to `0.7.1` * fix ci --- build/package-lock.json | 11 ++++++----- build/package.json | 2 +- package-lock.json | 9 +++++---- package.json | 2 +- remote/package-lock.json | 9 +++++---- remote/package.json | 2 +- remote/web/package-lock.json | 9 +++++---- remote/web/package.json | 2 +- 8 files changed, 25 insertions(+), 21 deletions(-) diff --git a/build/package-lock.json b/build/package-lock.json index 9faa5f88112..990cbe9aa7a 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -47,7 +47,7 @@ "@types/vinyl": "^2.0.12", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/ripgrep": "^1.15.13", "@vscode/vsce": "3.6.1", "ansi-colors": "^3.2.3", @@ -1923,10 +1923,11 @@ } }, "node_modules/@vscode/iconv-lite-umd": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==", - "dev": true + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.1.tgz", + "integrity": "sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==", + "dev": true, + "license": "MIT" }, "node_modules/@vscode/ripgrep": { "version": "1.15.14", diff --git a/build/package.json b/build/package.json index 2ea36955282..fbbf5264776 100644 --- a/build/package.json +++ b/build/package.json @@ -41,7 +41,7 @@ "@types/vinyl": "^2.0.12", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/ripgrep": "^1.15.13", "@vscode/vsce": "3.6.1", "ansi-colors": "^3.2.3", diff --git a/package-lock.json b/package-lock.json index f26fb7c358c..75c113f18e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/policy-watcher": "^1.3.2", "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", @@ -2922,9 +2922,10 @@ } }, "node_modules/@vscode/iconv-lite-umd": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.1.tgz", + "integrity": "sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==", + "license": "MIT" }, "node_modules/@vscode/l10n-dev": { "version": "0.0.35", diff --git a/package.json b/package.json index ebd3fa44f35..929140ae9fb 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/policy-watcher": "^1.3.2", "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", diff --git a/remote/package-lock.json b/remote/package-lock.json index a6a9e253204..749a327de23 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -12,7 +12,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", @@ -128,9 +128,10 @@ } }, "node_modules/@vscode/iconv-lite-umd": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.1.tgz", + "integrity": "sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==", + "license": "MIT" }, "node_modules/@vscode/proxy-agent": { "version": "0.35.0", diff --git a/remote/package.json b/remote/package.json index 38e9037c747..4572aeabb03 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "parcel-bundler/watcher#1ca032aa8339260a8a3bcf825c3a1a71e3e43542", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 80f4f0c7dc7..0165458a666 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.102", @@ -72,9 +72,10 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@vscode/iconv-lite-umd": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.1.tgz", + "integrity": "sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==", + "license": "MIT" }, "node_modules/@vscode/tree-sitter-wasm": { "version": "0.1.4", diff --git a/remote/web/package.json b/remote/web/package.json index 5e6c923b3b7..aa642d61472 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,7 +5,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.102", From 3ca2d59c38113839d225ea9ad6055d1d5e3758a9 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Wed, 15 Oct 2025 11:44:24 +0200 Subject: [PATCH 1174/4355] Update activation event linter message (#269156) --- extensions/extension-editing/src/extensionLinter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index be7eea1a49b..187100b563f 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 as VS Code will generate these automatically from your package.json contribution declarations."); +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:"); From 3a81fd82ec92eb75448f48bd48bf5860110cf80e Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 15 Oct 2025 11:01:31 +0100 Subject: [PATCH 1175/4355] feat(codicons): add 'debug-connected' icon (0xec63) and update codicon.ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118420 -> 118592 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 2d5fc3a2eeb3d21ddc146836c630e1fca73901ba..a7d78f82517a27e034068d155819630aae7e6372 100644 GIT binary patch delta 27737 zcmb7t34omQ`TzHM&w1au@0q=KW_KppB%AE)6`OlEA`yZl;tF!CxI$7AS22Wyq8f%Q zN}LU)buAWEO;t7gP(N)|P(?3hwvJX&TK#{X_d9z9{rSg!-sipF@A=%{an%jx&h6&b z`uw7W%|g&+LWmWsPg=QtP3?*23t{aNqWaup*PglN*h9PD7h=LxL0b~X9ka6c&?|3R zihJJxI*!8$Wtp17{$oJHaVKp!W8u&LQ^flQg)m=RyKeQ$E2`haady5C>iZ|HJY&5Y zpo?)mf&1%DUU||nRfSIo1QCOo!fHV4EQR8aiFyoBE4BcC> z_140_>EOcaTDrg5wVCg%C_H9`+qXDh5!!h|b@nEL+Cu1Cgr;$QLn44PO7Y_2WyN0< zpDtOYMT4sgn!Q*4U%0ALj837G#sAWg^kZ5`*H9~M7t83c^ewuI3gY{siw+joi@#Dz z>`gxs-xhItTRb4P(+Si=T{MmUSNuin6uW5`-6?LNN9b1Z54x9bqx@N-wOT@9_cyWR_ zQG8RJBl2RC*di_#-x8OIOT}g4a`7FpRa_yi6a{g$*e0$K*NPj&jp9~uySPK#C4L}& zDDD>bhSVoU3@71AwCixi_a)baY|4HRZ|VsQXMr=BQ?=PnnXEjqju_`sWgLT z(j4lhy=fjT5?g37Eup2NftJysbQm2$%V{O8qSe$($Iu!&j*h1j=_FcD-=I@PA8n-k zX>>ZBL1)tWw23aD3u!Z5OqbJlXe(VwS5qH-m#(K9=tjDUzDGOgcDjS^5)aT1=x(}? z`sqO$pr6oAdYb-+o}*vV3-luWihfPMp_k|tdX0WZuhX0K7X5+#M1P@o={XK>s>G>cgSb%46r08A=)McYMdEyMvKS}EQ?s~D+$sJ@uVSDj#F-SLxpX!? zN(YMH(zod{nol>=L3Fe@iT)_&(`4~I@q0?sY+50j=>XbCOcT?^f%F*to_5o3>1FXO zoki!+EO8$FjXtEm(-V~c89hlqr>E!_^ivTO$I!mwAGl2G@deImZ%U(aj^KMc!9QvrF1D>C*GwNdX}CS`_VS>Q+h|V z(|*)RQ)sQ|r6_HnYek>9if*AR#IMDR;#X9nqF6xZ(l^B#@iL90toR)*5MlAExK8XV zR?{KkNU=(MDh?M%iH9jd_lT9WPVDoE_@{V|CWsxRkxgE)TpS@*h)Hyj_^vpbE)k2x zL@_}uqP^%Dkr4-pHNzf^vt0lk$M6p*w{4eM+5_BZ!8VUL_=&=&C5cD_+x(4)k32FsBK?1Bq6i<{8 z%Rtu(k>?Hl6$d9t(6>PO;{aU+x=sSzM-I<6ZxY2*CBUUbaf5{5 z_ivN{=Mu%!B*fm}B*oKl0Dzr|;u#WPY@&FkgouNlB>`q9ir;39aYC~lGf_Y}noB)~^S@j?l)8+5Y-?E>8*0WK?w7fFa) zKrfb{M?f!;01p<$OC`X*MDa2Sx)=1w?_dMaZJ=8v=zh>EB$-0HYU0z8e6`7e%fL0Ol`>cS(rT`X390 z335ObeYg#;vrC_XI#86t|$N!SFf1SGsD{zHN_yW&R@kouzdu>|D5D1IUVO+Xa?DFK~8 z6hD>7Lp>11&m^EIh~mE_pfZT!za^kNh~noGP$NX~KN3(Vpgu@IwGbs}c?>8SqNGSb z-4G>J0*Z$yX%Yh6Qqs}?*a#>iqGU)wEfFPC0t$*Kxg?;fh>|72UoE*MpuUKbM*@nB zD0wBI(uk5z0?Lgj`6ZZ7lmZgsBlLeMC;{C^ltK~$Em8_g5YvbR#X+MItRRx@o z*8r*poV7L@M;s1CFe$YVnd*r}4BMo@kMKuw_h0)Qrh@(Tb~txNm@fO4Sq z64VBolAw0bv;=j4@;3mQ3d-LAXa;6`DT4!mW`brVXb$K&3F-zNFF|{Qa-jg42RcCl z8lWg~0RiR{B`zR9i$R+uXbI?K322IudO44`j-a*F}bbVaF00y>i@&6S{ypxlxG^kD38Qv!54=sps32IzbV zIumq(1f37MuLNxZT_`~pfbJ(j7lQ6DL7PFjdjPiTN(V~N<)8;~|6@bn!2$ObKwCk% zuK>Cdl=}*xt3eNzAg&E}6+qtwJw$@82jy-9=mt>kHh^vfJzRor0_83Q=zBc>xeo!_ z0eYka-41$`1l<98v;=GbQCcCvN=k|Q9H1Y7a-RcqHz@ZxK=*-ip99nn%6$&dgP`2y zYta8RfSqF{=qI4ZNzhJE?tg%u20cN7{s)xD0zl7!u9cu)g7UZk=mpS|CFn)abrSR| zP#!M;{TejS0|ubqfSw{jFM*yaL9c*rkf7H%OnK1!nY+@jVtje2VmTZ(sv|a=84i)330v$J6A}ElMC~!(=E+7x>^EOpeSvV zfITQmeG=l2pw~#ytA*>U(|K;@>m}G8De>qBXfEh>3F211QGy-?y-5OAr6}DjA$|*b ziv)2+Zk1r?Pl-zd(0tJENf1AWiIe9I9fX71CFp3-J0t`KWa;}7Fjz&2s{#mq(OnWS zVnyi(5-?{)iCYg4zX!crg3_S(NYHH1dnIUvNQ=^aH~<8<-~AGF04Ps8fc7c8QIpPd zU3o+R^cd*F62zZ*M1ppMa{Bu?>59`Mzh)3^FBw%lg(oP9+ z9_UZGov@+5;ou1g`VjPI67+Y_Cnbol|6GFj`co3b*MA{Fe0`S$@pWbr0P*!_Bw*c( z(*H=n&c|@)k^|xx(B~vz{EO1_65=7yUrMlxuJnQgTmz!?zY=f~h|-G^a2JTuuO#3& z5GCd+09**7#7qT%GeMMIVgzt-D0aA60dYuSYh9dm(9$as@HU9js}k@#h|+5k@IZ*t z?w$qqV$FYJQbq!rUZNzqV$%8;Ol>o5PTiu(clg3Uzk{*ruxEx z_3=F4RFt6Upd|@7Iz)-v1Q7oL{i_7wu=K738KB%g0I8r{Ie>J~4ck)>90M*pF*rj4&O0$UQvz;0F*r*?JPbOU#{f1I!ND8}c=^O&w}e;;+9Lta zpBS7gAwB`!TLR8NF*r{`JO{du1RR87aK41t0m`)ph^Gcu4j`Myz(O1V@Fa>s9#8;$ ziehko33wO9;35gJ0`ve0!QFG91bmNT@E{3zA;sWg32`*&5(#)F#o$s2_$b97&%T2T z8#0x7e*2*k#O-{T1pJp`klPP{CsPb^NdWjX#UPgufOk_2az6m#CQxo;03J^<$X^G< z)1WIPh~LM}3ed5j=(mZyq4RLS9Sy)qDn1|yu?tj@V8>};VzxQ2eXh^fuQP*ZtTLi5kD=yE3rQDL~>$sY4Yug zd6l`!TdHPPS`2EIz+BCcA?54Y$UTXSqf^R~{gryTMnW#*Be&XkonkSvx>}y`z{KVub zlW&<^oKi7m)s$MK%-8cQL>3^QFc*dPG8)n`#D=}-+?1{54 znf>XU-Z?LH&+FdQ{bG;ZGq2~Ao;T*k=I%Fl)7K5g$`&r8hPvQO_mFU+4l|AF~$ zFF0Yr{rk4lEZyYlHkSh;04qbETr;t3a@uXZc+x8;SDbv@$&atgt=qc3 zYW z{w3$XxT$&5{hQvtKwNP6g(qD2ug%SyAKlWm9`qtT(%)R8v zOQ&3V&84qgmbvVj%Rc({q2Ip$@`}rMd?)mst>5|E))iZyy<*B0Z(lj@%1u|kRqz$A zx;k<7W7{s-_I}@*zUTATB(7O?&6C$Iz4n>wy03fYyH|bpsqcPz{aZIQ+_3D1?Kix> zefIWCw!eMj(i;bFnt9WmH+S89*3HFRCf@SUt!=kHc3Z`5XWjPf_m+L{(H&JgdUrg2 zyYKef?ihE+wmSyD-}e2>?)2Tc>8{va*Z=VPd*MCm(k`zVG92{UrC3+kW!e&NV-+`RN%y{qTvCpLptLp8muH zH@QB8{!l4IxG(6V%FH(1)Q`|~T{{9h%T--hmys^%0^ zR~qhZP;^ttjXk_rF>x8^8yfhgoXU6TrkYNxX8(ngF4gSMpl`*+WG=#-W{MOhNfuVZB>eHs7;D;d6_nix<-OeR-#Cis;R5v@%BT8cAADR^F~tJ`wN6{?QpaonaG zvn-#gjWdIM_4X(pluw`K!K2|o9M4Al2YEK4LOrC+TNDn4EmuZ!SuRh|wXfH|2p15P z2A77WjtW(atbo)Hm&+TerI?Q&WhQ%4d$#RG+nNRH0Iq z`vkXDeRc%bx1o^a#wQo~z5BX?o{=&Hxn%fTv<-!GL8_JuMQ#*oyqF>&$vWE&G+s2G z?rfVuZTXqh8t>@Lq|wIJyBGnHv?jToH~gBvv$d&lMSZ1;p}LzZUR zc03acD7rQ+TosdNsoH7KRQMRiJ9A?i`m$q2g6Eu7NPpk_PU3fFbi<)`rYX*7nun62UC zQ}~n1JnHFH?n?6vkEc3lxeeX&COS|Cl!RP7rW*r+p->3gRtt7;GO zxV@@jksXilW7Q~((|Asa!f3aS!t~C)^0|)AtWiyosCgWJ3k~3|MD@H0>n2R6n(R)dymsNFT%J$ir(^b!3@Bwat%BtCTz_xy-s9aq(RL`q z$bgDtel+LNdD-^1W}Xjw^i{d1&{*ixnS^P?#f<+m5X&@=>84|3i-l?%YD1nyBuf|MaQXt1Uc~l5i1BL!=plsjXug(IUgBO$VdH@8Q;TfC87yBtF9@>yKUc_R^Ta3gQ~_no=F zl9wMy;sZ^h6-fho3uuwy^w@93hZ?ChJM>YWTRg`)hjwsfk2AxUb~|O_jdygpZpT17 z49EdrMMjtXVJ`K?ptD7*;UUZkCVP!JDxd5>ea;dk4;x}P@^Yf0Q_MlvXHtGLYN%rt zI7qCub0$ru&Uk0a^dZrtv!gW^$AAD|F(SB{>jY#|$BlFfqXySEC>~`zsfM9gQ*6ah zRh8z3LWlTmRZ$Jqv@P9qxk3q-9kxBVoYy0vsK*ucxm~Vb>H0?1P*k<5${;e-Y-?*y z2m4Y;Q#D&r!c|}dhOTw_LknF&(}J=*G$^Xzo~(h(aXsRs*PdB zld33+SJm)Z?4nR8j1til#V~xFWa2X{MMGEUxU3mol!?3sK2N%;Ds;t&T#fAcVHAZ6 z1THni&t+5Z2WQt6$a1&?jg1lW)oneCh8w$@(43sFR70&TIj7P0Yo-~)XQFG}^mtt8iW!fCw(C}LDF$i@0^;Xq?N#9M`Fx+{B zG7x$VeWjp3$MhpbWk>G)fA{2*nq{J1mbO91zYQpXs_7VS+A>sE18dgR{tx%L)l$LK z3fCQUX8z4>!DKSXn{#AI*RIvo398q{9NOhoC)j?;tWDb=^;zax+ux=PT~*rrZSpDI z(D6rZ`6_-NtFt(HUK3hCKI*rd{Pwo7&yT}Nm*tQ6E!T3}--Stq=9(uT9iH4scXJmW zU3^&nZ(RO|zhu+PvoVDk(a^wyHQm5kD|BF}s|{)OTl|D`%Mw}6+!(U-i%&ps<4q}f z8^bQyo2D_glS?(^l7J7Lc2L;c#IyOF`Z|vq$al^i-$XiSwh?E(?$K zTK*ftcuGDRkdN`RP{z122#x%at&q-+LTPcul;5bPrp%h9Xr?-A)~NkAzi>ugEnKo} zcK=5QmniAtmpR&$-%%b|zuN~HFGm+Hd@oa|Jv5O=BY=mpKyJ%)j@lo6eq;#lgs{hG z+vT^{Vw|mYhHDO8dx+daH#YF0GmdM9&#}hgs2yX*-%gCQT@(Gb^E#L8$)Fspy^{1u zGTh7kzc}~aT-FLo+dgnJPDsdEqAC@!xIX&~*Nj_SxIT*M_@_O<9t**Dup zI_6KqT~}lIyJfjfk{!?YriS|7EX-g2>H;2tUd{^(4|Ba{#ca>jC=s8sEj_K$+q(Ch zaiRJN@knEp-(S^8?s0SG<-C>C((wuPq20T1Y8{MOWVnsBW%+!>4r{a+IRqP>p&Z; z0bh^uz=+oretEPzZ>;@;0_jFU~4QvS=RE7s%cWhvZQ1Ars~k% zRiAPcno|vGS{M>C#oaI;kGQo~$k65hH|)WRl0TRyi_5EnAJx1 z1*w!}E;TLPrA53;y^)|{lU8lIeK2QGwWQCTQz;CJn1BPp~WECm{vivONAx|aG~7@8S?gy@J>DIxPwF-XMgQup7eNXC3~IPv)C&00JNGcU%n^%*IZrgs zRs{DEX4LWxp+q9Y8`J0Bz`7|mr?c+3lsBKsYkb4@@ zm~{-RsW5Tir|xtZ-4Hv2NXwc-Mvi0RV9Z3BL7ME$!M!tC_A$38-zM##_P+HSFzlRO z$i?n6?di+_(Go5TEyB$*T~+Ip9Got7o?xfuw<_H+S6#42S9`b-olO!A<}`VE=Pk3$ zLr-#>d430k8(yN({+3@qA&57*poY~2PXfnHlY0MKS3PaQwyT0&*UXum zjb$5g2K-JAYhueLJ%d`At!E&;W2Ushr7NqGdj ziJI9Nsrht7&#&h~EFN1eX$5u~`b5Wqb6MVq<*KP3rJbu^s7JXEdB(F1JfU#j4V6b` zdGf{~WIIhvXlJ&Qg3v+KJjdm#H{Bap5=~ug{vf3&4Krg6THpRN_jjCy18&@Ao`XA6 zCYo-wX`fP$3k@@)C-W`re7th|jUE<39>{#ev%-CUorQ%6!-|l(t**VUu6;Id>MENn zE1Nrb6J>R0$T2y3pWNk}@x<=(&3NKs+=CH3`cAoD_TljK_d`yf#>s4r%`o&}0pz6# zkTne!MREW}vblIPjqX8nWHZ?e8x0t*ogJO93ghTMusO6*93z*tH_X0xEFJ@6%)&M4 zS5g_=oboF^d|@&fCdN0|O)Nf3F-&%!#c`D7SxLHGmG{RoA**guQ_>CB)9?Vk%4O=B zp;p882)am%LTbe|as_c*shF@5N%JN=Is^nnm2;N~-OI8p-HPaj8~3?FQFDqbJZLtm z!%r=A`x?zD^5&gOm+Olb;vqd+Rn;>53_l8;*>M44(o`T*tN+bgj|v5oro;Ovd&A~ogFzzbJgkReBlYYLfxy1X;# zcmicJDH9U~L4e7aN2XyKavm8x*PKbmf&;Trx?;ew;9SDA%$iwtHbLvegP7!g1=AAq zSLx_v|5L{-g}EEt39}S|wDVl)HBqP*Jm;MGR;_9-%ro8N%r~BV zmE~C(A9EK>aOW;BTqL9Ks=OLZnBUvp-wMz{vw*2u*HE>u;x z?8@_mN%LSv@@%vXy-H5T8Vz0oXRb#6>(zQwaa%f1Q#aI0o~Ev(t@1P-bG~up0$XZ4 z@`NMh8{vv?g~M+qB>e$}!G{xh*3ySHbvW`IrNa?uakzXDooO{4q1YaG%vgjO97hWd zHp0fLifD~2hQmJiO?fAyM^xJkVD#jc^e$P_ZJD`X&a`{#aR!b%a(g@xcoS^19w#B} z#(Uv2#=dE>wPUefKCZsLKGkE19AEEK>0-bd0A$0zF%5ogLw6m3|AGh57K zgPctwSQoIt;ZcYGJlhJ!4RP4n3WYZd!hXOE=2JX9lj3|b-2uZSldYjF;!cQ9wZ`Jj zgy0Dt94*OQ0liBPxM~$;ChUa^jAlh?HhSgRsm%tC8=G`HRDGhYH`&$MQTt;BTs&Uc z6G#Q{S0ChUU}NR*srssrk*En(1rnDH?+wqaiEw$(f}+w3?$H0v^FCMJ4V@7|jZRDr zOfY5ztbSp7ND;@;i`m4g;xMkZ`+!G6U4a0UFsc0?AkuKX?RUNYUisi8b z-bCD5u3&*A{$Vcj`;qhVTkKmR=miZbhy#YlD__$26U?wuMCX4=`{}%pa)NIpIR9HtsV+F(iV z==|kK28RHcy|Ao@(qE1SEFAKE1(RLyi<0VUa$$WQ{LAd8+SRJ4-kDx#_pQ$HXz$iW z*lpmILG~!9x+6H2t3Ja_CN+4Wx>zKLLjKjYj-Tz_S0@#^CV2MSF1-+lvcUmwdY~Sa zHJ=Md)Sm4u?Hq^3Qo}Uera5m!JK8v+O^L#(ul;eH8)idVAl6 zP;4%+{$9xcu4-w$W4pL5MRA!f$C|Ot?rvY?0?me~RN{?>rp?a8G-Wp{pS?CrX4Qnh zxfenM8@7FF;Y{p>0-z{0w&v^1s{Mce-CW(7j4q55B+Cp372}SR7NY~KgN>D8;xL(6 zlxdgBv?&8$2n0jr;@}dLGH1iu83&4YGYD_^@ugoI8K?Sb@KVhttULl|wd( z)*7}mWM9RHIB>Ln#F5%m$1l8m-&zQ~mpri?_;Jz}oCv#PsqL|s8=hfY z8hqbxzawYC=xu7Gw!!jU++f2RX5sN-u6s-axZt18_2%HB#?I(EI&MFI+vd$^0kk_e zx>UBfopPlE<}4ajBHZwfuFi6^VUkqUBpV#=S*kv^JnV}crS2Ab}_XozXOpdS%F^II91*K)|j=ZP1y+O%P4G_{~mR zjl8^Y?t6&?M*@*>Z%C#P9eyr}vB84?6$cs9Zv3`n(0S?DMtgSL+RgGaLtJ}!*u!DN0}LFB=Xeafx$K^aj($UV+AxpKI{}Iycf=lD z3KMiZ#;x>mwMai!2iy^5iySfu!fBKXBa@B}HF5h7Vb_lCb!Q~>jv5}Q!h;HKG4dE1 zaDKTlX5FfB*q);md3(cS3?0m2^e;|r_?qOGo%k?ZP?`2ooxF27!XG>}dFGBZ?oQc) zt1*|c$Nf^4%#P3l2MG_=MmA?_T+T{x+c$?R;>^0`W13cuf<5~A-RVp7U%N@Zev4pK zj-D^zUR+mHhpRfeRC4OtLsk91Y6=gZ^d^tg)M>;oUdw;K2geWDg_G!7(}F#N(6MFS+rZ97|eg$+nrxjo#nT z!{?w1LlN%z6j#gl_2hbo?l-OO+~%@Sho5p_2luT^zo~+<)ZhAn3yyvi*n{*|&hQwm zCdbWq%s^g{0pCbAgE{HAj4)ggAcro|3O5)VU)c_+TGmh$sYTglCFN71Ez&(d4I1bR zjxcqStSX`|L@}cgI38S4xIU6DBn9MM6^5o?4LwVVCJn`nD0I*j^(2tD<$^=Ph*s8A zMcw50x)FYiYNIo@7WgX+Qw;=E)2Q%ko(jXL@Mx7Pj2%}(v68-I&~gP_QMalfBPZ&1 z$Ey6n8g~o7cqrfIO;OnU?>pS=@S7r5V3~Da;iJFT<)z?_vT=$(j_btR(y#`1# zu^EcENT*?rt}c6xxfRS7#}-FGq;)ug!KM-0y7GN8g5kuoJKE8LPC)xwuZ~0qUCnc( z0*;q*NFE`29sYj!lA&e6E97Jcz`v|02D~w?hGP5BW^QO^P;$LdkK*@u5RFZ^uw~cH zBt$~ucx}_s<-jtY#M1`Hm7S;ouEQr?&JXyjrsTg?g1~{K=(@*Js)iaHZ_zBc9QEOb zCfk*>y{fV=d%)etew|PS_lz?!-@>>|U=nc}+a9Ben+vAgP%C3Fc8=;9$I`)YjJ&GH zDr+jowujd@)oBf=^-Z;-n~#T_<$BH;Cv8Imh0E`*sXN^%{@l@Zbp{q2AH)2ku=t~N zo+TGTVx#nKX2GyL*!2qT?$`uiz3fqu;;Vx_QYl?M#BoY5U}a+gbf!-oX6(0}%N=E| zji+2}2^`|Mz!$KBvOq6O853)AD*HeAXqAD5`DnT*jI>GYiACC$W0a4Tcuv+4r1!95 zFeG)QM=2NERozt0uC-olR>DKk-D|VH?`+E6ireei&jSm>Nsh|-BK7=0eI6mJG6!Od z-Dk0Z?r3d}m%#;pbPNAL#whm{^oX(?98m$FrkG>P%qAsnM145&!Y-?5wUgC^lr1D@ zRX6i@x0tQW5q;tA@Mh_z+ENoqIkANZ0u13)qy{x{JO|h;iFi{fZ$uQ{`}a<0^d6)< zRp57Ikb9TmxjKeH!?zSSvmHnnLYF#LBP_;`4>!%jEw{9yo;>_;7p-1R4}#gQfKjo+ z{{47W9j^uL<=isM?VzWrNp-ciyUw)DMQucFi#YsVW=39!?;S%AU^+oFq1n5%ny z@Es?mjyDhBE4{t;4~IYYj_fV&GKBZljt=BiZ5j2U{_X#1H~c)W+?ZKdWWtGNeQ)WM zIog092ZMA57&^YVHknG|kM*~R-@m`6`zz}zdCX?2q3l#=^cOh3)XkeCeC}?JAz`yb zS79FEk4Id0Mw>HW3|srHhK~eL*A&RhcGz6g0Vn^k8I z^4mnX@Z7q}JiJz;TvLf21iI^4L9Lv@Zy)V%I=ov|>-l8W3@+>N2y~J*0$?0X+!o+u z5P$5r!7za*XLP=He6*(U1iz|h5(^&3LN!n2=g1pOv6x9Wlt7<5X=qVI@<{t zbqm~l=*XqlyllfQ~Zkuu+M9261u+>FV%%nzqrmJ>~e7!%6PF-RVh zamSX37`;%cnviK}l0C&nx+KCmJ)zKfo|r$w6SK)5vu4(~JRVm~DwT41y>WlOOS(-BT7}5O+c5 z%MNq4wr$x0r|yWdn!B7!<@mO`$`l8qLex#nUYsEIS{6U>6I*7G-Nue<7@PVWI1~Q3 zfz7^8>S^V`PgC?Rl|4|+P{HS;L32pU=jb1Hq7ls87N~O$(`#pCt_AuWkAdhwU&P%Q%)o=9lZ5Ny*tA&+iq(Lj0lFB%)_>}9{5+uRrncr3$ZXa-X9 zG_AZJ@OYfW=0GerGJHo(47B^ehDJKYhHxhpKbMER=FXvbKkMgmik6+NPT~fP+^ZC& zhv(*u8O#R`bGAL@bKitf*Tbi0Oy`Vr!(kFUhr1n?AXCCV!>{Ui@Fh9qd6Y#`8Tit6 zai8tdboNW4?FaUsLdOSU5G-_fzeecYsraY`D@U%o|bI=hl z_GYJdb;qeW8;qGJNiczz=Bjhme2{G7BS+xinPhOg!vg*M;B~S^yvAUa_Vn}y zgG0Usj(^XRoeV4}PLAHfg^%{#F`okYT)dp3F{EX6raOlOZ#(ot$M!)4 zquojU$|2>d7g0~)-^H}Wf#J`y1Ecu>*)G>)^9j)V`2&LIP?{V zb)5naEZUd0YL4kcbySDJ+Q9YU>}8~$JMk-wxK4BfyRt?noly0`9&h!Ot`@|UtHWvE zQWYu3pC`sm9v_d7pFA#6z0{WuSM$}bDb-%j!3r5J^_s-EMEMppW!a3GNCU?lKvhb9 zXx_-c?TuKS{t14Y5X*$obj(C^F%+7GV;hnR6u1ed_!Vdi(9lt|mMnCrT4L>iIgvD@ zaS6w1lkY&t)?~c`_rhLoZ{bNKM;TmIIMZr&#+?9epL;RYDbuKL(qc*^uy6{N)MOaB zP|-Nsf^(vx@z%bh7gIgdABWxR_yG6#sap82+#1i=Shpq#8}oz-bood%RvON z3NWz-n6rvzLDNDmyvf=42IqMxFp@od4hAwp2{3WpZd`;ZiQdgEI}7QYmxw}y-_Q=N?vF%oTENcC1!6aOK(!!!(J@niLXHTGmH zN(APOwFM{u^cvXgZ2rSNAkV>LfODF5UocpXoo#s{GhZwrb6~|?W4tY86Tt@+TEd%NU#RJ)TD4q+C zI&bC!Okhnl(9&uKH=+`j%Z*nkubpeQU!%ctNW!l@a!32lTb;zf7_S(p||F?0&e zfWFbJqXWV6VjW(jb2|*efrpmTBkGJQtShg8!3puLI_8>U`LB+)QDRFL9mp=%@Kx=X&V=^d{ zm)Wts*{%EVMlrDbFj|f1E*SifGkL^$$b2k15+`aM@+zlu<@cdjjvmz&bj*(BWf43i zP6uEoL7;xoVZG)RshNK_3Y!$sq`7jLaDqc~~A!J^W z<9HZ(T}P}|sey+Kp2sY~B@At4nvg$UWIK>^ap6yj#_CY{Q)FCTri?eLK8! z=6Kr|)GM_J!`tSZNoH8FrXitLpJpjxbJ85sv%A~fx}YIFzm3HP|L_U6vJj$?-JG1n z^~G}FgZKjE)t7$$7Jl)Ue{-0h@;7SOnrh5UR+`~URFI59c@s;ikcr2sDp*H_bQa{j zWHV+vb4~KAd(_e8*1Nhi72Z%ZfZDB+X9hy8iaxjB+M}KrT4)0gH-X)*X>;p|cFfTD zcT$G*15PgT4`^#>jS9;bW2qL|9{Y#`@jElC_(vu=QNl5I;gVzv5GoMTXq==Q7DH({ z#*lo2DS{LVge#;Vax`lAy@sX3&`x$}C(c5H4~EX<9ZN1AfGUjeiO+)LAQ1{6KeN5A z30AKUsVrazSm0UF+}T`-++r*Ulnk2_NZ?l5AdkAsCnX_wb!%-fhH$o`c&rFs8V*z? ztMSs^iBL3vH8Z-Vczg&TWB8(H3{4WM_M3o8_!FfP2?vu}UXDHlKIF zvZ02nyv){ws~2ur<54rLoFdT95S_NE!JIOl5S$Vn|bx zdTl%O!t}5L?5I@;nSl#P%_TMz*l^C(+a|}?c@x~_?3IHTj`y&<{|?_Fjlf(kITEi3 zbM`Eagg$q&$T}zJtP|Uq;sdE%sl3Jlk7_tI(bEOo;^%sVteUM z%pUNLZ9&HqjcKy6w|Ar6vy11A8*`&~V=u0tbC+L2hZ$k%Bl--wOf}pYN8mSu*Y!QK zmFfc=>~tKrZCGgo1E=rbTWL~0+lqy*&~@0rgC)n7Y3n+TQ@2em7z_K+pZ-uZZg4%k zSY?M})AFBfK|e(%GO7rnfz)uUs86I5`sAo?P>{^=xGe{%!CAs+Ky1jYNmrZ4a6cbo zR;OzYQH&GOtTWXU;SPy;;gJoneIGD_wl`Ke$B%3Z_@j8^pMxZRR+yk8^#-sSwWkN` zVQ%C%bq#!c1wCLmzZT)bT%UpFlcK|M-V9gR@z{Nxut9lR9Uf)xb)+&f5;N$n*;IB* zS>{Nz!12Ub)f`m#%D}-u&$aX~xh^b>IPU>SZatoVJh~I1Pm`BH^YqHJBSHuVen%|Y z0`DvWL+B*VKFD;K(!C0MMY>rrW?2Ps;zZB~9LlE5u*v$;C5W>Uw2zIcdqI_vDEUSWWintNcYd@-D!xbl6a4)bEc>=aAe)s580}gexs~rtQcw zL})D6m_5EOK_IzG5k)ecC?V(>?;B_?kEQIWikq<2-Lf(pwwd34NNg}zF9DH0MC(Q}# zZNL%`(Q|%+VIM=E&aXgGTvmM(+N%7jMi`7v?h96d<8?_&scBx0j#pxKE4Vbgiiq2? zNpy<2eMNLvz9stA!t(3Y0RblD^f{ZaSz4LM=R6b|R)mMjwZJKtigFcR*qO*nlj`f0 zWITSqf*B|Wy1s5v$}4qvJpxN_C-ma1S!;q~u~77tn(?CoMqD;iGYldOcg1XjrzGQ3 z9Az9saVGtj0z&0aHchItLYbH!i4*l$AqZ<5|8(rAITwCt;mbPMwCkJVnV?lSv1wd| zCt8VwN~&bngwhFMmy0Tk%R;Rdf%(?HH*SO9C2b;0%}HMxijINr_3)^b>*2PIjLTd% zT>s9f#D)+w#W3SF9K%I=xW3+5RObwSe6T#&p`LmWu{@A}65sLNu?vb_dC08kJMVk6 zd=aY`tYa)6+RhHN`w(Z&$*dD+$T}zLgYK>FU=Tkk<25}V54M%= z;6T$3+ANt<5LF$|;mSs^Zbz+vdBpl|dmHDyIjbR|lthdnvnCeJLJZ5MNM*=%l*xPBLG0k79KJaBg2l~C2YpkP(& z$Y#W~My%Y?Ro)DAeINg?7HP%u3XwX@FxL4Ti-cv*=%3)&<2O2nf03isNk(E56e47l z9WyLy5&Sa1?Hup7Q=K6-AC>D3C9FK$({A|4^a!&0AZ|?6aJzIEC`i+R@#cbl<@T|k z!N(bLdILmzI1pIsbHD6%bW81JOaeo{z5MeX>NI~yhnfY?nySGWHNiwASDg$&W!5a$yX^}-sLPVDQjR5s^zu# z$cpA)s9^IJY2ffWmW44yG5a{*+>kzF1+TJm0FHjvD;OyPjjqKII(aA!_$dVaAoo+J68yc z6S?+yh~1uXxRP&`gKj)#9L6524Kjd*9sDLlFcCbzTuW_C)q_Lz<+>k-k%|mky$AXq zepteE=~xq^v9bvD1Mkty>G-Lv;JDEZAln7a0=Eq}L5FM*$x)n|H^81d0NoYud+lzj z!%tPkzgVxYtkwmPXz*ErU*VTZm_R4_!kIJv@2WkB_c)Kw{*vd1-#Gqj)NJ6QduW+T z9}m2KKfMjB|0CsN<&%M@9-=HH=IXWUPCaJeua7`(di}k}tlD_&q}A(AKKYo{1D+q# i78iaFVBP8y2d?}%{f_!h{{>y4p0w_?0c{s;HU1x$QuSB> delta 27679 zcmaid31C#^x%T&-edf%*W|^#$%m9G|vP=T93yK5~6j4#5xB%`z(JC&Zf@l?S84GHy zyH?byC2Fk2R=w6*Yq5*hT5GMfz1BOE-&*Unt=Hv$-tU`9f?6-;Ip3T!XZhB5dEWPo zPu*f)|84u4ru@?Wb|Gk+5aPHKPG7a|gcq*9SqRS_A?ntxIqlrl^>(0Jh_)(0mp-ue zq*W&#aouf);M_NX+*%yaR_G1*jPpgy+S50lv!L&@zuSk>Csg>978Op>W579s zZ|hOL@QJmtP;G73+S6P znZ6|sqatmkYv~&C9Wjj#5jTkvHHaKNF0K?w`Y*9t+)Qg|HuX{;?G^teekguJKc?@9 z+vqX6P5hm{OSjWQ^eBCg?h~)kJv4~EFocPExxy#>A|Qeylt9&LMV+V@4dM)OrdTJ| zi;d!}!W-U6`T61iu}~}$OT-~!wOA`o7N>}F#d+caago>}wu;Nd<>DLS3b9RGCAN#J z#WmtuQ4rUQ9b%{WmiV@~UED415xc~F;(qaf_^x!J7nKX;$(*D#hw$Q<}l$Oz?~2m@c79=}Ov0SJBmU z9bHd1(2aBx-Av!6JLpcji|(d-#ctX~_tS&)Fzu#CXb(L}&(ZVr0=-B-rF?rEN>X*6YQzTdHF1fUDK?9P6q@%^H!V)%?tA~7Kl1HS(pYoKUw3sDo zM73BhelGUXW^pK8PCLagR`*ZoXQH19;z#th=%ItCn zB3&S!qd!69ScPSQPYX|xSvWXoc_8ulAAM372qyC}A&SQ<(3vPtP=Fv1#S<08ODIoL zprcT(R-h+Pu2CS!&f;1H`X+ucrdw|0A#x;-m3s9FN(VqAn!$y?+8Hhi{kwX;;g|JB2j`85XJvcfI1MxXBD6r zM3H*~fQk^s=M0lG;P|5E{4N)%sFfW8t%9!>z7OB7#KfDRMI z-zq?xi6ZwZ0KJAu`aA3ZP;sL8x&oA)DE?jnYEKm3P=Epy#Xl%O6^i1U3Q&ro_(uh( zM^SuB0g6%-|D*twDT;qqfbtZ@|6*X0LXV2#+X~RAqWBjD=vGmDM*&(^6#rWR`c@Q+ z3edcwSW;+N$L;;qAD1NE{8$uL6Q-C$W@<#%81?(pUSQw(DDZthcC0#+hiPBII7_O3u z@yABM5)ma!0XB&!*$S{uM9EQrogzve1z0Si zu$(A`6vPJ@|58{1hL0#k6a>1e6jdPpKBho%lx0u?pUYrdx|CF)T9mvGPzq(W0@b0c zQJ{L1d|nVTPLyc{>O$F~ zKs_j16=)jPe<_0YYZ9H12_x#j>JjUjzr0=0B9x3oB|z-vR8qQ zL)oW5t58l=pyN@Z)eKmrqO>26KUyyMGM%20&|3a&G{13d-3EbUMm8 z3Umfa?j-NL z=z5eqJOJH*l7|PN8&MviKsTY}p#tb;lsr@beH-Ob3Umjr|D&-3(48ocQJ}j}4k*yw zD34Wu10YJrDX@`J;;{v27fK#mfbK`hV++uOC{I+Nhf$uSKzuC^?P_!(J%SHw6lf1h z9%q1_M0v6TJ%^IV9H8e>o~l4Epgc{1UPQ@r0id6wTpeXHB5O1O6 zc@NO5h5PE#dG7w36)1#~XE{J~P~M_IJQTMo&=AVo6yQ>d((MZ3KT+~i0piBqsX*Kb zcPY?(ly@r--}@e3#@Nsj?0iQ7o~bBtl>u=YN?suVyj4-+o&f~Qn)?;t!HUuY3h-q` z>AMQz_b4A!phlDrDbNg*4=cduO^ed^umgy8l)DvZ5z0XYnpb$YKAk@pd)#(_zK`;8 z1$rGN?*sG-$|n@)Wt2ZqfD9i2MGB3dB?VM+$JZMd>L8aUsg5xu3A1 zzhdVZ1^OGx|4^X!P(G_be3Kt55Z~lE1;X{EpC}OD<9P+*d;C;^_#RwA0It0#{Y(K) zKBoJN*a5_eD1WX1&tH`GDhQTFFDZZx5T#!zfEN&@Un+nh5T#!!fGZHCUn_t$5GC$0 z0Q`X{agPCD5=4o6^%YD_kP4#o8wIhVa7$w{|1S1kQvlTXRG_ISOA25)L}^$7e1|A;KLFxWl-vk_43s>;01-+aRDg7pJlFtPC|OtDN6rP5TBv^rviu;QTmsH;L7s=03csPi3bJ{w-%N( z?{|<-fMQV~6Q!mAT1E_`nhX#$Vi-+f2=v!5p9fF|C92P$bm3Rc9r@+ha}~hih+)1v zAoij}8yH}A#4y^x5Qm~fM>3cO9>$ztfcFu@VFiIs9*!tbJIbg6VRj716u=LO;W(BQ zZ$v+KFw6{)Mq)UrAh3*vs}w*aiQ#Gm(Sx!^feu1ht3cf-x#<8C%)@mGpq|8Vy@EIa zWrG4mv809@u>;V0luZiYsKjuy0vlh$X$5f|$`%E>6=kaexGXWueG7oq62n;q@LOVd zoPziz%JB;H4$29PJRbZPd}vb;3s6o}pz~02D*%vVVwk%U08J)_I~2q#C?_irS7wR= zNHj6rsUUubvP%KPni%F@0zk2e;ld{^PX4#}kW&EXCWdypDuBZi!?P5`V<>0y9KeQn!pu!4nG-kpXs440DeH;x3fjAb@zuEL0$e=KxPY05qZ) zUZem*Q4B9u0JSKFmneW_6vGEA2p*oL3LqZE@G=EZkYe}{1u=l~PzBJEVtBa%2ud-` zJ$YE+l1y!$pLB!*@mO%50U$5MFh3mtjVXq?RsaZ1G0c4rfZ7zp+^+ygPBF|+1VDF+ zVXiG8_&&!e5I={<3ZN5FVr=Vq171}8jTFRlDA7+0`cq*+wmolZ<|6YB%WJK+9>9O^ z*fZ@j?6;gv&O4qHJvVuC-aYPQ_gUWx-+lfqfir^b!PA1*(9+Pl&^6)d;SJ$C z!cT-hjxUzxOe(zER~?U%QI)G@bX zL&p;x?@g|se8%L5C%->s?v!<%xz2k!-|t${b$!>{-I?y=yWj4~^qigVc{4XVcTR76 z@5g<8eK++zH+ACFjZ>eR)-vtw{g&*vb-(wg4@`eyM)QoDXI9O;X68q;?wYO5-Z1<1 zISc0eaLz|_7tXz8?hoe{_djC)?fXBw|3~xI&U<(MqWRa%|MY-24qSBL;|nG)IB~(- z2elvc@WRH05A-kSe{IpCMQ`9mik zdi0@tmwT6=z5K(&792LbBC=xXiU$tY4nOal4OudsSl9>{XYq z+Oz8Ie@1<=M}i{o&VVe|_6I-g8!;^VGSa za}PQ9=5ycJRKID*ruWXF__ z<>EIlIq{O4FZpnbwdIB_FI>9f(pR=F-1@|2-IrZ=*}IodyZpLuRDI*kD~`M3#Vd1H zo^$21+j85k-}d2EORsu-`@HQBU)_H7_Nza>X6-dEUc2Smx2|ix?vm>sEOcN0i*Ih+ zF?YwkJDYc2vGe5{>TkFxe`Dyztv7yp)7Eb-{nplR?Ya4`n?L;agm16=_QSVCZaMmv zCvMH$`uJ_$+s?k-y8VFLAG`gZcWk}WxO3Z`#k&^Xb@SbsyRW!=_@3^2w%_ySch3IK zNB6G1_o-dZuH$ySb>E8niuZ5$?uLizAG+|NJ01=^y!zn>zjxyIZu{P=yZd&Zv3u{} zy1`!!zW0dlk#UdAKj4wIk39Qm5iq3yKS6q zIrzNK?pEE=rfR06j~k~uCO+~R<=w%%CS7UhA5f^!*_m7XjBZl$>+);n~4 zQ@QLI^Bwv5U<2yWpy9C{c)+U4hmOiugO7GTM$;yuVQt`J<}d}`8@KGt3`)nXAaz-G z+O`t=Q6ibP+7Won;fv->cWyuGtS2jN?|dUf_4!?12O%iOsxkGtYTYv1bu;G;>E?Lb z%Y8Ipm}D6p$!vQjnas3jlTrWCk$9v&GB*)9+8=KCV4i82=KT4QpzHBYFuWcQ1!A59 zUH^R3G4Xd>vV2`Kn~dX%Nd2L>K_c7`MgSWB@{qqa0=`)RMc&&Lj|Iu9N2IiKCPJr~ z-kz?`Tz968nypwO*@LeeDBYX0yOIeD2Q$5Ey!Lp@2)dixpka--$y2Vg*_iQI^DIxz z$|~KoHd&_r!F<1apeN?{S88WrxaZH$bIsl0#5Tk0HMl~uR#XEQdxGdhSW@)%Sn2X} zXHfS{>gr79dNb+f03yAze`ATxTo(20AiFu!(-pJQy)lbBx-;>C2Z6=ieu|}ICVIo? zR=3qeG|QZmXiU&t)6yb`3^;Dabv8G(H{owXMr+ly=4MUnk2S~1<7m}w)e9#Xo?s#o z^ca&Cc^${yljKvIxUv>+WMykaGb%h8QPlZ`OL8qnI@_Bo?8-If)0mR_1gHGecpL9) z7t4np=_h?_qp!hpx!>Ou_4+K!8?2eGYC=h#&=bOu%F~;9M(h0i3|G(VZe5bCZ%7~Q z_XTvzCAVg@ROi<&rd=p_!`h9!5a5b-OzkonvNhfCY4-=i8@RtY+{(r?TChB zcJ;=)den@M7Zy$L%nyWoeZEjMlt_ejgc1}FW%@0n&#?MjXNTkZgB_L;F|3Z@OjWvJ zc|7dyP5HuMUn&tCkXH=^6RA{+1<$$;$90_!k6hl~h$_OamydegFtSACq^gI#85Zfn zN7Mgy0#`kRz;_Ew=w=>BNR>`>ST19ylj(9(;x?A2T$Me`qqVisEm34cM7P|wC+u!< z!+TUgHV&z8Q`CpYh27Lz-VPoxbFr4^2RV3b4H%M%Vg?u&*o?g_0;B+_EUV8QnMsL} zBfZ_IQ)WyZM{Ct9yPB(eyoG7(@kDv!IvMQw94F@ZR`G|7$p@YNM=DyoSJgem%?NMi zFI`WQI;(c<9(l^UhecDwZI=yI?Yj*O0nekiGtoea0A)HT zZ3i$9W?>#A?JgXzqZ}#?iPD@!-!?vK<|%C8pjuYk^_< zgR$D$SkTWhc)S~dz>a*P>$?qGH}tNqJhry6Ti07wY%+}%Zc|5-OFT7p8|LnKQyhPl zSuhDiS3T&%Gr_0afYG;N$#i7~#CsD-8=6I`6|PxVl3P+w$)03ArZgmZkTTgR-8pU$ zBP(0lU=U6##1^EruISF%+o`5dKi@aAgKp5Y^I3j(WXNc+g5H!b6!H}{jdaa1eS{CL zW$1|-jeMqqpuu6@`Sz#vPEGpvAWnRNFHXCMd zv+J!j$?LR)e4BltR(Vs6@2c|_x70?TjD;MqO{;7 zA+NK>4QEHc&4#ne7E;%a6@iE0!RUnxA(+dTLS##MIrjQ-5R#6(v*$23+<|(c5(leG zSyr2$eFiJQrb&h84mmO}ZP~rtqP@JPM?bCO-HZV%RvNL=Smn=HDcIh`-^x?^E!)Ch zpBf(&sH3@Foy9irzE}6DR&hO`_dok=@RmcD(Hs`!LFnEooPYu04DleXIMy9qJa3^u z%6bo0TpW`x3)_^fs2LC-al5zhho@@udsx%255?o5sOzk^UDsakxG%x@T;q+Hmq*bl z4>iDyb~J3Uy0Jwq8>GqHpMVPvorz6m9ji9poU{hlV#%kXmzxpqJay*SJJi?n9nchM z1s2|4(KonaMTy#MZXSBLq6Q~JHyy-@OVJT&sM;8$!s$n}<RjcS7tzkOL1CkpVkQsz8N7a0HCY@x>&UX1sjn#fu`cLo;Ok-Pow4*Ei z>`YZXjP4GT>+Du_=*WbSC0RZj16moDt1yr*h1IB{p9_1A{+*maKIlkPvt5I{fRu8^;Oo{T7z=}?Uld(&P ziV{*fiuDw$C>E;Ftl1t2RrLo6nd?nTVTnG-wo8f2>TN$NpCrdTZTDcts^hs{$l>x# zfDu#=Wf$ksPZnNS=HO< zS89GIYM2Q~xD0o8gQ-LPkk1Vp-BDf-(=;+2&!N0dya8)c*lkXeZkReGQ-q+iAb`sn z3sR4j*ggm7earBBW16mOF|XgSVCZ*ROAx$c8MG zF>JUjWI;+Ai7*wF(3g@QY=WsDgN;_0xijJQu}gFei>1Pg*I$^&?P`$YniW$aen4lo z!Y+g=XN#Z>dY#28Zf}oS%2L)g5KbA9z-+zupaao;nBthYys|N4S%yi6o5h#(!eM!n z?K*)LE8>}HhQ0ZEf0d^t*6t)i$8dk_?{liP7TwsNJ9)r%J3Qze4`z~Sc+jQzWErh; z7hCI*6_DFskK%5Qi2}jd-NIUS~c0c$fntx8S?>vp@q0GI+p_ zpW4+mFs6LSJO+o|tAqlaJFGk=cxv;>bgoyqQLIzObVed67bcp&9E3!f)o8+)=4cA~8FpfL^C%Oti5oY132xHr@xTu7c$>?%f1^fq?A);S zP`m=GkR)97Nrg9WsXZwJTMQ<2a|iX>@IX7+O2ZkRKBo%O(dl!1QpC<)?fgvF;|C;| zSbA*9{Kas1K|$^I;S8Ktg#rS9wd0$GFX9Kp!A|upp1(wrRJte}aqF$pvGT*MnhljD zWvRweH5pf(raI7;H7HjjNXZ0qF{qoYAXhcZ8^~TrT?ktUU?qDYY^x{E)v*3+YjjJ8 zO`fXJVU~^A&+`GYE7w6vQ$$K>x#|0|OZK@1cOY9AA4~ZVLH6cGaS=v7z_S)~49je$ zW!lNkVTi3{b5;snG$5PFW@hEFG%){rA*j*E5DoaynUpdZpOcAXCY{ORWYz%9K`nrj zEgNS-wj`3Xm^Y|LcVWF~aDNnq2Cr;$FyV(BGi^7*CRu2PH);|*NfU;#$Er2;kgXXp z>?QJ$j^Na)O^9DI-6|j41@d6h=^@AUns(UqCv@HNq4&%xU&Mx0?9seI(~fMfX^VPW zIwz<6RSofeo}~Slc6%U7eNn^X(L4bc;>h+Ku7P>0O28(sH7)XJMkHZs5#2STnwf~; z`5w(Q>tJP%o)J!JuE!=V=#LOZOuuW`Ue~94Yr#`_?fG>boh{yITXndh%0GaGffkmB zp==7J5Dc0j?K`O(neUQx2Ac|yN@Z83hb@VGg)w07+?En)WKs6Op=F~FE3<3`_M1iq z4{YVuw&Jl+eP!7T>!Bi%VLhjQ@XvP~ zOgfyi!oZz9lqsBcS0+Ev>2{LNgphAJTljlA9xCstaThm?HEl?22tJbDu6cCItFyFp*6TB(sTfu^h%5jIsgrFaB;dtSBqZsJwDI zIy!1r$qU_jbr2pKb_;8`AZNabn$K!X(fzW_DJIn+hu)2Q3+7G8)S*n`ySC z9hPoRcMv|?mRfigmSv)A787JRkW6&4k;U{Yga&8W`rMlY!>Yc^fAPyb zs1}IOsa&g&>yK%s--zjEZCoeUHXBTKcFiW!bV-jxwj}s#!}#*A4I|@dKChJ|O8P>Y z;}3>BDc1-E{pzrGk~a2uFyzO(Z#+(d<0KjI$lYQZLJAAm4vRvvb|&I6yR1pUhGT{E z@@D&&XI%ykU6;*kTv}pDP?!(_T~i9no~X{R5Bc22gq$qOYGA6?sWKDn)I~6N`uYd@ zy>q~4wnni=`XzwCHj|oQCe0dr=ZR=c>OUVeZynZC8?q^8ig{u&r*`aXJal57vF$JuO3y}VGvAfz z#&l}H+}aOHA%I3_;nLwCRG^(ilm&A!4Rfvwx;u-jGuaUpS>M%Q`7KSGme(}P-(u)B z!DL9+n;fSVY9kqJpT@_1IAz8>U7rVnB8W3gV_cIwX-YC!qZ=9N)kxL37pK}bt_XYG zn0InC6A6!t47i{jVtf?e*5E({e=!`Zk7T0we7-m4>R!ab{`^wf$$LQSvRBnk0gcyH zC$Fh?4J+^}x3M7!>hnlg8ApH^O*S_8oVTFP_`?x8j`8=7&x2Wl5INSgx>xI$-Ytp6 z35}qh!1R*3{p`=(f7!BS{_3P$TpZFAo+kES1fzWre_6W+)`}fX1i_>Dyc+7`CAi~z(C)kFu zpsmW(_p+Wn#uqUS!@(jwhL;pJ(L|`(G_CdUJvF__XM|?986*bNf0$>=q42=RMwUr$ zLK5}$)Mazrj(v%lXdm=YJO(q@j&nuJJ_Z;E<|$PJAjPE{f=v(PdSQ$QN^~tR$4KA@wnYE^4M$vu!fVBet8kHeUacqW*1^BHjVqEG zI~_YDEp)${xmc+xqQffhLoE8qS+6Ywa6I875(eT8Xqm}Vw;LC09?~i6G!z_bZsgA?ehmTvo z9!zYd3ev?7B5kv=@MbZVXVSuwMlExm@iF7Y7k$ZS;f5n)H8R$+o6>Ob%hA9J9jE+R zj$$QQ{jv~C@6%8DF`VG&v(dGO;pyl(o-$vo`RC_-cP1GJ!-66(#-AOv2C zb46v(+#k-%a9>47R0dqM#;k0aFiRq`h5Uqa!{TFaLFGlX91%1QA<8%!SNwA$j%hp& z;duc!j@?w$ljkm6R;xJ=qq4hqij1DA39VM0lt?@x->b}qF*BrZWb%y1q`=g8RD;-d zk=x)RCjt}5(g#N8&QQSJ$Wy0cwO1O6;fi+U$MOvrCE3h4%VTLEVQ!3>68jFz*q%`H zT~WJukkYvPAP&TSiPjqtQe9)!S%>VpvG=X?@JQ|D0;+j$`tmd3v5#FWvU^S(>6mi2 z$iAo^(>n`B`vq$X{c_KkZYkXN-o!EClhNj;Qu}Jq|D(B>hw;Sd1cHL8$k12E##+ZV zw=#kD<}vB9Cce;zOr52(7OTx12U}qt>^mhZLEQ43Wa(cH>w>XU;<}uHp(a2SQR^%s zx=0#n9SwKvmuI#u-II+IsMu!-&h@jZCp`!UKhvFd}#gcQrNXQ=$bq@6_UnOPnVJcB|7&kg$* zwjmZ3%tCh3x)9=J7c1L6Q|qkP;>dr=wrk*8<5NHbN&6#{eRb6Tt4n&waExRXvzVSL z4yfhpJmlD#Q)j}F`Hlhdx2n$4d_m0{t*WVtz+5E@ZgCUH1}z-$>j7`l)OU@NZHI*G z3`f%=5ybk8y0GrC>foFib(TkOFf_l%TdR3fA%At$>y0G+IN}c`{r;-PaJ1Ri=XgPs zh{C}iJnYsWATSwT=xO4Hg7<$K3xAy#n8S2@68tDk#-OGeI#YvH54WZl7XL^qkw73V z$4u6q5+HD-+%>(pu(0r3wfU|o80HF;wJbPG&p!{f`b8b%v`e} z+3>lDOX{{ybEewdA>@s~D$~ifLrFhzUj#iho&abXoA5&thF*=hzdrdC&9!7C7U61+ zf97R?9D#A%8&AdEZOhf@pD@{ME5!8(j*$Nv^kO+40CoxvFo^3TVeOh$z@s%*dJ=&K z#AQ)8tI~}w*sW=9P}7&g7eya`jlgu3WAlYWZ%!f6rmU1pm~EWwG8MD!b2Ekekh!$6 zeK^|hI?pfb&zK6$4f{4f5Slg8_~w1O3lk{nrI&-rsbfYQHT5+|!dq2M7maP0oFYLM zfFn}1u<{@2e6lMC1(KFT3p_!H2VRu$BbfKZXfJ0q1W5vWU8c9xMkla=l(7s1V+oed zY|hb}g`nLB^1QGszn@Uaci~ecBYv>7?q_l2ayD$Uvd$m;=Ra0kOz@|{lr%!W^`Y}B zJP&lIv>BBFt(+Q;Cv~wMZO7Be&&iQp$`J1Nj;7*xo7$VAU>Im2;*I4BZib)$->{-K zHozwi#1gfhD%jQR2Y8|(-vFOE;0qP5{_H83$hG)+mKw-75zTNEOSv9w(RX7_z)ObL zmxDav@Rt;6J>N3TmEh>&oV(<89y#nVhXw|SRvN}i8I++aU7u!#J*)VEt2|*dn9G^o z&Q5Q`WKEmAn8nzLqC(ez5ep%Wa~wLR%;WQ;*@>`Kc8|@m<$Y?9kZW1Sl;v`~U|g2F@)YTsPneUbjOI)?7DcuT3^NF0#*9>#LKZwM>52N-}e~ z*Y7k{t+8Y79GoM>knW?4J$YCG5yb0zz%`2AYz~p3fCf()V+w+?17nT(--G7XYaGt5 zLk*=<9Z#0cDj6VV8zg?$1UK50uC@bpt)U5QYIPZLf4DjT!`wXetZ z=hz8eeHinkWc~}mlX@_a%tdC;>X6Uw-)=XPV<@T+{n83(} z96K;GbZSP02xszIs5o;&J+(AV`QJXZD0?|zBh3iVE?GtYDkUG}#8t%&gBkv@ImHeH z>?vjk;^<{|sbOF=9<#&LQ-m+cC?Gl0VDYS=n$rQ0>)h+ph)2r2?Q& zwf!{h3{dLXXlDe+_ZvBT2EL@}tWD#Q)OT&$h^6Alwn3s-*6-pLQ9GsuwAyjF2~Ed+ z9@h12z`#AC_Vkgn^Y*6l*IKX!(xlR>mAt$OSecyqE2-x%$;=y-#jqH$TFMC$(qEEZ zQ_NZpEj^cX+twnBxdoh^XXp*HX0rR7??^U9gQ13aQ#6oF2BOi{RtUf`DSA-IqZxX; z4XddtoU%e~^<9DJoanaxL_>lrEoCDTZP_Vkl%(xEP`1lCF8n#M^0}zX&SlLsFF%(h z$h~rt3ir8r<+cD}CRE&aNiMUf5$`0~yvM zOlL?#Hfv;#2Y=r)^WQ~Es}-SVAsNO-MB9Kh0lUC74uB#CVS-;0`*(5Ds}w`H9<+D> z7ujBpa;c92bS+P;P-19uhBkQFjNn<(1XpAx$26&ucF>_I`B+|{SejTQuuRydTh799 z$wmC8!;&SCk9_Py-a8DOFebF4FX@q6M4(NL4|0c&j$W7Gk+xf#eG(DeD$+B{rl3XdQEKDQSQwtD&;PVs2 zUR629k`POkO&VPKtumF03vp6yj4!}-xEz;ZryT6S&$V$M#fHwFiv$ok{$b=+R3oa1 zm(8I1DxMi3A1>#B`Dk9*B7r2w@ws;*3h2$o5z#E`fI-%^+qyXX;PKT^Wsp zEp$|7h(Idt`#9kSk7D}>b_zwJ zoBO&f=O_o4$_1t3p~_v1_Ba94Iid>spWJv#4vkDawb7&l`AtNQRg^24L<~UDugdk@ z`JIIn$|K_#RY&7S4bJSSAtEOd5~k|4E)|V~^t*zLDR@r=;x1tLY}kst%wamV;*)LF zTSzKLgzy}0z|5*XP6sh}J5`>gAXboctS1BBrC0>Uc*h34c#@$9EGy)jE9H+&08Zf^ zcC)F~2Hgg)%HY{66WRMco~7PEQb+D6sG6mQ>*F^#MtR0GGnI%kPd2WB6Vo+sd7D2 zc&hc_6aMyt(bVbULsxGSLl8^s{L3NK*qy{w!+_uqcfr<%tD!5=0|}YB#j5)*@Nlu`S=W^wgvJys3Db)$glAqvg_dZ zk3A1gx?h9zMJ647(hrU}lJ(?+2x)f~xB}8Vco3hVJ9w&bkfwWe2j}2x96~-n;>b4s zX^7;&&jN>{4tsUY&fE4>+!`@&7tcnLpJju6VB2cUF&pfrX}tDL!$v+TL}!%MCUiN! ztS}c-9|;1B6^&obxzRG^%>&ATOfT+jq0*Y>)hzO%#H$GGZIcn=MpGUs^t_7)%hO!$ z4;Nj)8U^CQ3?iIk1~{Y90p3q=_#tRE-+_A_X@=#m(P(1=Bss>z11AQRzyMhm za&N#=crA5Ehii>P`{TjWa0Z%;9zrE`)AXQkSk>_%jE`m*rgz3#odReo#~UiTBpMb& zs}G~^>$BMJOTiR896F6BNE*`(|8azk=M(#&n68+@QxYsiIHex1+~Gg<_0FS@q?Y{C zhO34ARiP-zA4^L$c^ub-UOn2hrcbwYWZYCIEsrni^Buz;IBY1)v5;>)*GVR{AhKv$ ztz>y(HRI)!NVz(-*hmD@c_5I2){(1$6{Zx-zhDScEMj%8xv_zX6Dog*J5bg3Ha&Kd>D3J9lt~vW%3ffE5y(~%qC2> zS)juBXpmSKATJEaUZ(qedfhD8V(rkqK5Ob!%ZH!WY&RWy*2G%FGh?Eyd2^1F14B7+ zh6iaN{LaHP54nD0(rn8$bYzVidDogf$?&`6Y43G3r_FX|)EP$I499N6ZpHLemHf&o z(pS~z%b(4U&dX<`+ZCl1#&3VrRJ4SF8Gt@6K8o2b{i`uPVJ@lF8S${=3<{Rg}975lPS zYh9f7Glf%tSUt0A*_-1W)+FAKVz(FBKv;XAX3Ja!9Nu{B79($(wrRFP0-#`95CzgN zxbUlkncDW+n4cr}c$LWZ)WrFH+jcOd^pKYB$I&MieV1V zzz;5P4>hwqQKn00Zs#LsQfIz5S6-9NS#C43ZaBWgX;AhQI3e9h?orsDnH^rF%yK;m z=`ygqAjv`VE#q8Ur~?+52HQ0#EX%cj2_6C&M{vX)Pb3zLIQV2hZXzw#1G#8euUb`1 zSzMPAfv}H>U=EY<-7)& z+z{QPG8~X6z}W))`iIIIK>ASTk9-odVRcSxD_*?ikGzStastxG&d{OfGBpV&YUdf} zQ?-|=VHJVedF}bsaY$w)I0?DfuY$zhD3=-DLZf7MAUR2CmXQz@hon+`aB6@5F$gPr zBQmEDqRFqXd=F~N}Y#|s-dNh9fgitpi^cZ~P6NPP=u z?_o0+N)}c`vIU7a z{meTc#XYri=f%kO%k6ydVw#wj=~5gxliv79qI+4S?*d&4wZy!*@`AD7udEHVt&^`e zrZm|2FbJdqPSt|4_V4;uW-h=c*qEa5E_mV_Fu*&JV#T*<#?-~3sq*c|ewbRi zK6LsebiAEUmVM+-W<6Uaj|>|VPJv7jX4jtMa_rYLA&XpKwr86&FjhIa31+2YxmEH6 zxo+gDaE)H0C(32KMhkhb^0-tfFG6zkM5gleR#;O`UOU?|=J3M8=3jwCczdLVcpJFd z>kl|`(+{e$73XS=_42}H+&;@1ja~)v)iT)!Hj%m%|X&gZMGKg)!9}Rs9B_4B*Rnt`jCbR!HQNJ%`l(r=5dXVq}7-7 zJB{O-Prwb9aStwIgR&|J`a8S=&f&!sHjD7~%Jfhqf|yue zpLRY^ByTh_bnJEX2y*~wa3)j0RNK)=ztS5{TUh-eV z4^*a;catPFcypXuZ=bNZ;g+$4PH_mP8fSyD9}{?C_Y*5w3_WxseXQQ#iXuUC33SBC z{BwWs29pTFa-w1aDT|z(*v-j(oC|?pCt!NM7aWg#!&5p#GGD@y4jX(Ynbuz}c|vDD zPtYVsV&X~A9eL?6_bg*W9flBshcMO=`nm?u%CY-a2S>t^4u%ybE@#+kz8IVRl|x8x z_pKkx`b1$nqHD^6;SRiniD!gc!g$BX(4!MzJR>_HJRb)vmsjMF6bJJ?k<2~$(Wr|oF zYvl54Hn1&_q*9Ks)Rv<#`ZrY=<|Wr@O0LBmoQod<_#=<3I|uj48nk%%toK*hqSm$~|&8}Q>yBaAe>3s@;givY*eGbM|75@d_& z?Kdqv)UED+t-GdW=n#}gGag&iW%QP5RL?E3Z?4Sz09#Au5 zeuyN-0rFIyd5}bcE8Ua8q|uSwf%FcZrDRL8jnm~l-Y9n)+79c1wDF<%ryq^Bd;un> zfootdsTa(3yrX03P*|9+DZX`?9jy%OywQHh!@7|jq56SIM3%7_QZ{d8Kcu503=qE? zF*c&}xz z2te{^eK0|^C12T^(*k(mCtNLG{^N`XyT_H)Cp}7WleQ{0Gq}Tas#`l$lj`=i_(N zIZMLuoOEpZD<90+rs34!S7c%q6GAbcH(Dc=8xAH+9dFVl3`hGm5WC2>vv=tFtstDVp__K$ z|D2&nSp+#j$~K7Eh+)7pi;lWwGdRMD01F(jo)Pt*h2aJI9PbL?eZ(9R&f^*W*r{?B zK;LF_4V4s0&d_y8H+OelTe3qhko#6N#9WITZXQ%jy-8wrUl zCp19)?4x{Uf%N6J!cc^_pMb$qcGYveGRMY7@a#oy$rSl>9T33K=Z@aoW?1c$bbV60 zWwdP^-`qSt1BdN?7SEkhXs0yoS`e*iSap-~GlE3H8IxezMVOA6obB}cJF{xa%6D24 zQ1fjZp^oAOL5!JP9JYmgHT%^Vh&8ir?MxuwjbH-eD|Dqa1rhcGd#o%% zdEjbURp`=@x{g;h9gp9W;e7rSfL8=Dr5uDg5UDUhvEp{n9Hhz0kJddnQuE3BI{twf zk7qtiVUL9zR1ke^B15Q|HiGkr8RK`Cq}dKZ09Ucx$}|T&DsAKxCfP<95UP#xm#n~| z^ki%dL<8;Yeux_Lsu8FiH)7%>@T_g)8#Cqsra4a=e*i2hJ(c?*}#{`Y^4~>4TwD2I(Dr=!hZ8 s>O+@4ffR_Lp(kmxGc@Zt`aMz!*KIiSl#@=_xHI)UT|4y1^R&(S|B1BFq5uE@ diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 48292758fa6..623d79c0289 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -624,4 +624,5 @@ export const codiconsLibrary = { quotes: register('quotes', 0xec60), rename: register('rename', 0xec61), runWithDeps: register('run-with-deps', 0xec62), + debugConnected: register('debug-connected', 0xec63), } as const; From 0a70ffec1cb7d85eb0a4bcbd95cc34fe325512d2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 12:25:41 +0200 Subject: [PATCH 1176/4355] chat - tweaks to accept/undo buttons (#271488) Do not explicitly set foreground color for buttons, otherwise hover effect will not apply the color due to a less specific CSS rule. --- src/vs/workbench/contrib/chat/browser/media/chat.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 71d8afdd5c6..fc45901f765 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -883,13 +883,10 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-editing-session .monaco-button { - height: 17px; + height: 18px; width: fit-content; padding: 2px 6px; font-size: 11px; - background-color: var(--vscode-button-background); - border: 1px solid var(--vscode-button-border); - color: var(--vscode-button-foreground); } .interactive-session .chat-editing-session .chat-editing-session-toolbar-actions .monaco-button:hover { From 969455e7db659bbcff06a9704e13ae2c650ed8c7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Oct 2025 12:46:07 +0200 Subject: [PATCH 1177/4355] fix #270818 (#271494) --- .../contrib/mcp/browser/mcpServerActions.ts | 261 +++++++++++++++--- .../contrib/mcp/browser/mcpServersView.ts | 11 +- .../mcp/browser/mcpWorkbenchService.ts | 8 +- .../common/mcpWorkbenchManagementService.ts | 104 ++++++- 4 files changed, 323 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts index 85454c91530..e268560603c 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts @@ -27,7 +27,12 @@ import { McpCommandIds } from '../common/mcpCommandIds.js'; import { IMcpRegistry } from '../common/mcpRegistryTypes.js'; import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerEnablementState, McpServerInstallState } from '../common/mcpTypes.js'; import { startServerByFilter } from '../common/mcpTypesUtils.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; +import { IQuickInputService, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; export abstract class McpServerAction extends Action implements IMcpServerContainer { @@ -157,6 +162,160 @@ export class InstallAction extends McpServerAction { } } +export class InstallInWorkspaceAction extends McpServerAction { + + static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`; + private static readonly HIDE = `${this.CLASS} hide`; + + constructor( + private readonly editor: boolean, + @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, + @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IMcpService private readonly mcpService: IMcpService, + ) { + super('extensions.installWorkspace', localize('installInWorkspace', "Install (Workspace)"), InstallAction.CLASS, false); + this.update(); + } + + update(): void { + this.enabled = false; + this.class = InstallInWorkspaceAction.HIDE; + if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { + return; + } + if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + return; + } + if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + return; + } + this.class = InstallAction.CLASS; + this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true; + } + + override async run(): Promise { + if (!this.mcpServer) { + return; + } + + if (!this.editor) { + this.mcpWorkbenchService.open(this.mcpServer, { preserveFocus: true }); + alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label)); + } + + const target = await this.getConfigurationTarget(); + if (!target) { + return; + } + + type McpServerInstallClassification = { + owner: 'sandy081'; + comment: 'Used to understand if the action to install the MCP server is used.'; + name?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The gallery name of the MCP server being installed' }; + }; + type McpServerInstall = { + name?: string; + }; + this.telemetryService.publicLog2('mcp:action:install:workspace', { name: this.mcpServer.gallery?.name }); + + const installed = await this.mcpWorkbenchService.install(this.mcpServer, { target }); + await startServerByFilter(this.mcpService, s => { + return s.definition.label === installed.name; + }); + } + + private async getConfigurationTarget(): Promise { + type OptionQuickPickItem = QuickPickItem & { target?: ConfigurationTarget | IWorkspaceFolder }; + const options: OptionQuickPickItem[] = []; + + for (const folder of this.workspaceService.getWorkspace().folders) { + options.push({ target: folder, label: folder.name, description: localize('install in workspace folder', "Workspace Folder") }); + } + + if (this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + if (options.length > 0) { + options.push({ type: 'separator' }); + } + options.push({ target: ConfigurationTarget.WORKSPACE, label: localize('mcp.target.workspace', "Workspace") }); + } + + if (options.length === 1) { + return options[0].target; + } + + const targetPick = await this.quickInputService.pick(options, { + title: localize('mcp.target.title', "Choose where to install the MCP server"), + }); + + return (targetPick as OptionQuickPickItem)?.target; + } +} + +export class InstallInRemoteAction extends McpServerAction { + + static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`; + private static readonly HIDE = `${this.CLASS} hide`; + + constructor( + private readonly editor: boolean, + @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILabelService private readonly labelService: ILabelService, + @IMcpService private readonly mcpService: IMcpService, + ) { + super('extensions.installRemote', localize('installInRemote', "Install (Remote)"), InstallAction.CLASS, false); + this.update(); + } + + update(): void { + this.enabled = false; + this.class = InstallInRemoteAction.HIDE; + if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { + return; + } + if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + return; + } + if (!this.environmentService.remoteAuthority) { + return; + } + this.class = InstallAction.CLASS; + this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true; + const remoteLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority); + this.label = localize('installInRemoteLabel', "Install ({0})", remoteLabel); + } + + override async run(): Promise { + if (!this.mcpServer) { + return; + } + + if (!this.editor) { + this.mcpWorkbenchService.open(this.mcpServer); + alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label)); + } + + type McpServerInstallClassification = { + owner: 'sandy081'; + comment: 'Used to understand if the action to install the MCP server is used.'; + name?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The gallery name of the MCP server being installed' }; + }; + type McpServerInstall = { + name?: string; + }; + this.telemetryService.publicLog2('mcp:action:install:remote', { name: this.mcpServer.gallery?.name }); + + const installed = await this.mcpWorkbenchService.install(this.mcpServer, { target: ConfigurationTarget.USER_REMOTE }); + await startServerByFilter(this.mcpService, s => { + return s.definition.label === installed.name; + }); + } + +} + export class InstallingLabelAction extends McpServerAction { private static readonly LABEL = localize('installing', "Installing"); @@ -209,6 +368,58 @@ export class UninstallAction extends McpServerAction { } } +export function getContextMenuActions(mcpServer: IWorkbenchMcpServer, isEditorAction: boolean, instantiationService: IInstantiationService): IAction[][] { + return instantiationService.invokeFunction(accessor => { + const workspaceService = accessor.get(IWorkspaceContextService); + const environmentService = accessor.get(IWorkbenchEnvironmentService); + + const groups: McpServerAction[][] = []; + const isInstalled = mcpServer.installState === McpServerInstallState.Installed; + + if (isInstalled) { + groups.push([ + instantiationService.createInstance(StartServerAction), + ]); + groups.push([ + instantiationService.createInstance(StopServerAction), + instantiationService.createInstance(RestartServerAction), + ]); + groups.push([ + instantiationService.createInstance(AuthServerAction), + ]); + groups.push([ + instantiationService.createInstance(ShowServerOutputAction), + instantiationService.createInstance(ShowServerConfigurationAction), + instantiationService.createInstance(ShowServerJsonConfigurationAction), + ]); + groups.push([ + instantiationService.createInstance(ConfigureModelAccessAction), + instantiationService.createInstance(ShowSamplingRequestsAction), + ]); + groups.push([ + instantiationService.createInstance(BrowseResourcesAction), + ]); + if (!isEditorAction) { + groups.push([ + instantiationService.createInstance(UninstallAction), + ]); + } + } else { + const installGroup = []; + if (workspaceService.getWorkbenchState() !== WorkbenchState.EMPTY) { + installGroup.push(instantiationService.createInstance(InstallInWorkspaceAction, isEditorAction)); + } + if (environmentService.remoteAuthority) { + installGroup.push(instantiationService.createInstance(InstallInRemoteAction, isEditorAction)); + } + groups.push(installGroup); + } + groups.forEach(group => group.forEach(extensionAction => extensionAction.mcpServer = mcpServer)); + + return groups; + }); +} + export class ManageMcpServerAction extends DropDownAction { static readonly ID = 'mcpServer.manage'; @@ -226,52 +437,20 @@ export class ManageMcpServerAction extends DropDownAction { this.update(); } - async getActionGroups(): Promise { - const groups: IAction[][] = []; - groups.push([ - this.instantiationService.createInstance(StartServerAction), - ]); - groups.push([ - this.instantiationService.createInstance(StopServerAction), - this.instantiationService.createInstance(RestartServerAction), - ]); - groups.push([ - this.instantiationService.createInstance(AuthServerAction), - ]); - groups.push([ - this.instantiationService.createInstance(ShowServerOutputAction), - this.instantiationService.createInstance(ShowServerConfigurationAction), - this.instantiationService.createInstance(ShowServerJsonConfigurationAction), - ]); - groups.push([ - this.instantiationService.createInstance(ConfigureModelAccessAction), - this.instantiationService.createInstance(ShowSamplingRequestsAction), - ]); - groups.push([ - this.instantiationService.createInstance(BrowseResourcesAction), - ]); - if (!this.isEditorAction) { - groups.push([ - this.instantiationService.createInstance(UninstallAction), - ]); - } - groups.forEach(group => group.forEach(extensionAction => { - if (extensionAction instanceof McpServerAction) { - extensionAction.mcpServer = this.mcpServer; - } - })); - - return groups; - } - override async run(): Promise { - return super.run(await this.getActionGroups()); + return super.run(this.mcpServer ? getContextMenuActions(this.mcpServer, this.isEditorAction, this.instantiationService) : []); } update(): void { this.class = ManageMcpServerAction.HideManageExtensionClass; this.enabled = false; - if (this.mcpServer) { + if (!this.mcpServer) { + return; + } + if (this.isEditorAction) { + this.enabled = true; + this.class = ManageMcpServerAction.Class; + } else { this.enabled = !!this.mcpServer.local; this.class = this.enabled ? ManageMcpServerAction.Class : ManageMcpServerAction.HideManageExtensionClass; } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 6fb327ca7c2..af63377febb 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -27,7 +27,7 @@ import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPan import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; import { IViewDescriptorService, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServerEnablementState, McpServerInstallState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; -import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js'; +import { DropDownAction, getContextMenuActions, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js'; import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js'; import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -173,14 +173,9 @@ export class McpServersListView extends AbstractExtensionsListView): Promise { if (e.element) { const disposables = new DisposableStore(); - const manageExtensionAction = disposables.add(this.instantiationService.createInstance(ManageMcpServerAction, false)); - const extension = e.element ? this.mcpWorkbenchService.local.find(local => local.id === e.element!.id) || e.element + const mcpServer = e.element ? this.mcpWorkbenchService.local.find(local => local.id === e.element!.id) || e.element : e.element; - manageExtensionAction.mcpServer = extension; - let groups: IAction[][] = []; - if (manageExtensionAction.enabled) { - groups = await manageExtensionAction.getActionGroups(); - } + const groups: IAction[][] = getContextMenuActions(mcpServer, false, this.instantiationService); const actions: IAction[] = []; for (const menuActions of groups) { for (const menuAction of menuActions) { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index a087f9b596f..5dd6a3f44ec 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -32,7 +32,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { MCP_CONFIGURATION_KEY, WORKSPACE_STANDALONE_CONFIGURATIONS } from '../../../services/configuration/common/configuration.js'; import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; -import { DidUninstallWorkbenchMcpServerEvent, IWorkbenchLocalMcpServer, IWorkbenchMcpManagementService, IWorkbenchMcpServerInstallResult, LocalMcpServerScope, REMOTE_USER_CONFIG_ID, USER_CONFIG_ID, WORKSPACE_CONFIG_ID, WORKSPACE_FOLDER_CONFIG_ID_PREFIX } from '../../../services/mcp/common/mcpWorkbenchManagementService.js'; +import { DidUninstallWorkbenchMcpServerEvent, IWorkbenchLocalMcpServer, IWorkbenchMcpManagementService, IWorkbenchMcpServerInstallResult, IWorkbencMcpServerInstallOptions, LocalMcpServerScope, REMOTE_USER_CONFIG_ID, USER_CONFIG_ID, WORKSPACE_CONFIG_ID, WORKSPACE_FOLDER_CONFIG_ID_PREFIX } from '../../../services/mcp/common/mcpWorkbenchManagementService.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; import { mcpConfigurationSection } from '../common/mcpConfiguration.js'; import { McpServerInstallData, McpServerInstallClassification } from '../common/mcpServer.js'; @@ -431,19 +431,19 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' MCP Server because it is not available in this setup.", mcpServer.label)); } - async install(server: IWorkbenchMcpServer): Promise { + async install(server: IWorkbenchMcpServer, installOptions?: IWorkbencMcpServerInstallOptions): Promise { if (!(server instanceof McpWorkbenchServer)) { throw new Error('Invalid server instance'); } if (server.installable) { const installable = server.installable; - return this.doInstall(server, () => this.mcpManagementService.install(installable)); + return this.doInstall(server, () => this.mcpManagementService.install(installable, installOptions)); } if (server.gallery) { const gallery = server.gallery; - return this.doInstall(server, () => this.mcpManagementService.installFromGallery(gallery)); + return this.doInstall(server, () => this.mcpManagementService.installFromGallery(gallery, installOptions)); } throw new Error('No installable server found'); diff --git a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts index 6f5461fd567..b16a5566091 100644 --- a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts +++ b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; -import { ILocalMcpServer, IMcpManagementService, IGalleryMcpServer, InstallOptions, InstallMcpServerEvent, UninstallMcpServerEvent, DidUninstallMcpServerEvent, InstallMcpServerResult, IInstallableMcpServer, IMcpGalleryService, UninstallOptions, IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js'; +import { ILocalMcpServer, IMcpManagementService, IGalleryMcpServer, InstallOptions, InstallMcpServerEvent, UninstallMcpServerEvent, DidUninstallMcpServerEvent, InstallMcpServerResult, IInstallableMcpServer, IMcpGalleryService, UninstallOptions, IAllowedMcpServersService, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js'; import { IInstantiationService, refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; import { Emitter, Event } from '../../../../base/common/event.js'; @@ -24,6 +24,7 @@ import { AbstractMcpManagementService, AbstractMcpResourceManagementService, ILo import { IFileService } from '../../../../platform/files/common/files.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMcpServerConfiguration } from '../../../../platform/mcp/common/mcpPlatformTypes.js'; export const USER_CONFIG_ID = 'usrlocal'; export const REMOTE_USER_CONFIG_ID = 'usrremote'; @@ -356,8 +357,32 @@ export class WorkbenchMcpManagementService extends AbstractMcpManagementService return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User); } - async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise { + async installFromGallery(server: IGalleryMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise { options = options ?? {}; + + if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) { + const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]); + if (!mcpResource) { + throw new Error(`Illegal target: ${options.target}`); + } + options.mcpResource = mcpResource; + const result = await this.workspaceMcpManagementService.installFromGallery(server, options); + return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace); + } + + if (options.target === ConfigurationTarget.USER_REMOTE) { + if (!this.remoteMcpManagementService) { + throw new Error(`Illegal target: ${options.target}`); + } + options.mcpResource = await this.getRemoteMcpResource(options.mcpResource); + const result = await this.remoteMcpManagementService.installFromGallery(server, options); + return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser); + } + + if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) { + throw new Error(`Illegal target: ${options.target}`); + } + if (!options.mcpResource) { options.mcpResource = this.userDataProfileService.currentProfile.mcpResource; } @@ -427,8 +452,42 @@ class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagemen super(mcpResource, target, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService); } - override installFromGallery(): Promise { - throw new Error('Not supported'); + override async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise { + this.logService.trace('MCP Management Service: installGallery', server.name, server.galleryUrl); + + this._onInstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource }); + + try { + const packageType = options?.packageType ?? server.configuration.packages?.[0]?.registryType ?? RegistryType.REMOTE; + + const { mcpServerConfiguration, notices } = this.getMcpServerConfigurationFromManifest(server.configuration, packageType); + + if (notices.length > 0) { + this.logService.warn(`MCP Management Service: Warnings while installing ${server.name}`, notices); + } + + const installable: IInstallableMcpServer = { + name: server.name, + config: { + ...mcpServerConfiguration.config, + gallery: server.galleryUrl ?? true, + version: server.version + }, + inputs: mcpServerConfiguration.inputs + }; + + await this.mcpResourceScannerService.addMcpServers([installable], this.mcpResource, this.target); + + await this.updateLocal(); + const local = (await this.getInstalled()).find(s => s.name === server.name); + if (!local) { + throw new Error(`Failed to install MCP server: ${server.name}`); + } + return local; + } catch (e) { + this._onDidInstallMcpServers.fire([{ name: server.name, source: server, error: e, mcpResource: this.mcpResource }]); + throw e; + } } override updateMetadata(): Promise { @@ -439,8 +498,28 @@ class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagemen throw new Error('Not supported'); } - protected override async getLocalServerInfo(): Promise { - return undefined; + protected override async getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise { + if (!mcpServerConfig.gallery) { + return undefined; + } + + const [mcpServer] = await this.mcpGalleryService.getMcpServersFromGallery([name]); + if (!mcpServer) { + return undefined; + } + + return { + name: mcpServer.name, + version: mcpServerConfig.version, + displayName: mcpServer.displayName, + description: mcpServer.description, + galleryUrl: mcpServer.galleryUrl, + manifest: mcpServer.configuration, + publisher: mcpServer.publisher, + publisherDisplayName: mcpServer.publisherDisplayName, + repositoryUrl: mcpServer.repositoryUrl, + icon: mcpServer.icon, + }; } override canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString { @@ -617,8 +696,17 @@ class WorkspaceMcpManagementService extends AbstractMcpManagementService impleme return mcpManagementServiceItem.service.uninstall(server, options); } - installFromGallery(): Promise { - throw new Error('Not supported'); + installFromGallery(gallery: IGalleryMcpServer, options?: InstallOptions): Promise { + if (!options?.mcpResource) { + throw new Error('MCP resource is required'); + } + + const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource); + if (!mcpManagementServiceItem) { + throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`); + } + + return mcpManagementServiceItem.service.installFromGallery(gallery, options); } updateMetadata(): Promise { From 2b00e85c86411c4d26896c489273d91a61767856 Mon Sep 17 00:00:00 2001 From: Mingpan Date: Wed, 15 Oct 2025 12:55:47 +0200 Subject: [PATCH 1178/4355] Copy selection for deleted chunk in inline diff (#267991) Copy selection for deleted chunk in inline diff --- .../editor/browser/controller/mouseTarget.ts | 3 +- .../diffEditorViewZones/copySelection.ts | 185 ++++++++++++++++++ .../diffEditorViewZones.ts | 5 +- .../inlineDiffDeletedCodeMargin.ts | 33 ++-- .../browser/widget/diffEditor/style.css | 11 ++ 5 files changed, 218 insertions(+), 19 deletions(-) create mode 100644 src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 2d8f3f28d5e..20f1a352b8f 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -481,7 +481,8 @@ class HitTestRequest extends BareHitTestRequest { return MouseTarget.createMargin(type, this.target, this._getMouseColumn(position), position, range, detail); } public fulfillViewZone(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, position: Position, detail: IMouseTargetViewZoneData): IMouseTargetViewZone { - return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(position), position, detail); + // Always return the usual mouse column for a view zone. + return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(), position, detail); } public fulfillContentText(position: Position, range: EditorRange | null, detail: IMouseTargetContentTextData): IMouseTargetContentText { return MouseTarget.createContentText(this.target, this._getMouseColumn(position), position, range, detail); diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts new file mode 100644 index 00000000000..b37f822b78d --- /dev/null +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, getDomNodePagePosition } from '../../../../../../base/browser/dom.js'; +import { Action } from '../../../../../../base/common/actions.js'; +import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { ICodeEditor, MouseTargetType } from '../../../../../browser/editorBrowser.js'; +import { Position } from '../../../../../common/core/position.js'; +import { Range } from '../../../../../common/core/range.js'; +import { DetailedLineRangeMapping } from '../../../../../common/diff/rangeMapping.js'; +import { ITextModel } from '../../../../../common/model.js'; +import { localize } from '../../../../../../nls.js'; +import { IClipboardService } from '../../../../../../platform/clipboard/common/clipboardService.js'; + +export interface IEnableViewZoneCopySelectionOptions { + /** The view zone HTML element that contains the deleted codes. */ + domNode: HTMLElement; + + /** Returns the current view zone ID. */ + getViewZoneId: () => string; + + /** The diff entry for the current view zone. */ + diffEntry: DetailedLineRangeMapping; + + /** The original text model, to get the original text based on selection. */ + originalModel: ITextModel; + + /** The line height of the editor, to calculate the line number offset. */ + editorLineHeight: number; + + /** The actual view lines for each editor line (considers line-wrapping). */ + viewLineCounts: number[]; + + /** The editor to listen to mouse events on. */ + editor: ICodeEditor; + + /** + * The function to show the context menu. + * + * @param anchor The anchor position for the context menu. + * @param baseActions The base actions to show in the context menu, which will + * include the "Copy Selection" option if any text is selected in this + * view zone. + * @param onHide The function to call when the context menu is dismissed. + */ + showContextMenu: + (anchor: { x: number; y: number }, baseActions?: Action[], + onHide?: () => void) => void; + + /** The clipboard service to write the selected text to. */ + clipboardService: IClipboardService; +} + +export function enableCopySelection(options: IEnableViewZoneCopySelectionOptions): DisposableStore { + const { + domNode, + getViewZoneId, + diffEntry, + originalModel, + editorLineHeight, + viewLineCounts, + editor, + showContextMenu, + clipboardService, + } = options; + let lastMouseDownPosition: Position | undefined; + const viewZoneDisposable = new DisposableStore(); + + viewZoneDisposable.add(editor.onMouseDown(e => { + if (!e.event.leftButton) { + return; + } + if (e.target.type !== MouseTargetType.CONTENT_VIEW_ZONE && + e.target.type !== MouseTargetType.GUTTER_VIEW_ZONE) { + return; + } + if (e.target.detail.viewZoneId !== getViewZoneId()) { + return; + } + + const lineNumberOffset = calculateLineNumberOffset(e.event.browserEvent.y, domNode, editorLineHeight, viewLineCounts); + const lineNumber = diffEntry.original.startLineNumber + lineNumberOffset; + lastMouseDownPosition = new Position(lineNumber, getRealColumnNumber(e.target.mouseColumn, lineNumber, originalModel)); + })); + + viewZoneDisposable.add(editor.onMouseUp(e => { + if (!lastMouseDownPosition) { + return; + } + const mouseDownPosition = lastMouseDownPosition; + lastMouseDownPosition = undefined; + + if (!e.event.leftButton) { + return; + } + if ((e.target.type !== MouseTargetType.CONTENT_VIEW_ZONE && + e.target.type !== MouseTargetType.GUTTER_VIEW_ZONE) || + e.target.detail.viewZoneId !== getViewZoneId()) { + return; + } + + const lineNumberOffset = calculateLineNumberOffset(e.event.browserEvent.y, domNode, editorLineHeight, viewLineCounts); + const lineNumber = diffEntry.original.startLineNumber + lineNumberOffset; + const mouseUpPosition = new Position(lineNumber, getRealColumnNumber(e.target.mouseColumn, lineNumber, originalModel)); + + const range = mouseUpPosition.isBefore(mouseDownPosition) ? + Range.fromPositions(mouseUpPosition, mouseDownPosition) : + Range.fromPositions(mouseDownPosition, mouseUpPosition); + const selectedText = originalModel.getValueInRange(range); + const onCopy = + viewZoneDisposable.add(addDisposableListener(domNode, 'copy', (e) => { + e.preventDefault(); + clipboardService.writeText(selectedText); + })); + + showContextMenu( + { x: e.event.posx, y: e.event.posy + editorLineHeight }, + selectedText ? + [new Action( + 'diff.clipboard.copySelectedDeletedContent', + localize( + 'diff.clipboard.copySelectedDeletedContent.label', + 'Copy Selection'), + undefined, true, + async () => clipboardService.writeText(selectedText))] : + [], + () => { + onCopy.dispose(); + }); + })); + return viewZoneDisposable; +} + +/** + * Calculate the line number offset of the given browser event y-coordinate in a + * view zone. + * + * @param y The y-coordinate position of the browser event. + * @param viewZoneNode The view zone HTML element. + * @param editorLineHeight The line height of the editor. + * @param viewLineCounts The actual view lines for each editor line (considers + * line-wrapping). + * @return The line number offset in the given view zone. + */ +function calculateLineNumberOffset( + y: number, + viewZoneNode: HTMLElement, + editorLineHeight: number, + viewLineCounts: number[], +): number { + const { top } = getDomNodePagePosition(viewZoneNode); + const offset = y - top; + const lineNumberOffset = Math.floor(offset / editorLineHeight); + + let acc = 0; + for (let i = 0; i < viewLineCounts.length; i++) { + acc += viewLineCounts[i]; + if (lineNumberOffset < acc) { + return i; + } + } + return lineNumberOffset; +} + +/** + * Get the real column number of the given mouse column in a view zone. This + * compensates for different tab sizes. + * + * @param mouseColumn The mouse column position of the browser event. + * @param lineNumber The line number of the original text model. + * @param textModel The original text model. + * @return The real column number of the given mouse column in the view zone. + */ +function getRealColumnNumber( + mouseColumn: number, + lineNumber: number, + textModel: ITextModel, +) { + const lineContent = textModel.getLineContent(lineNumber); + let i = 0; + while (i < lineContent.length && lineContent[i] === '\t') { i++; } + return Math.max(1, mouseColumn - i * (textModel.getOptions().tabSize - 1)); +} diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index a5b5d8c9540..60393ea42d2 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -199,7 +199,7 @@ export class DiffEditorViewZones extends Disposable { originalModelTokenizationCompleted.read(reader); // Update view-zones once tokenization completes const deletedCodeDomNode = document.createElement('div'); - deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text'); + deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'line-delete-selectable', 'monaco-mouse-cursor-text'); const originalModel = this._editors.original.getModel()!; // `a.originalRange` can be out of bound when the diff has not been updated yet. // In this case, we do an early return. @@ -241,6 +241,7 @@ export class DiffEditorViewZones extends Disposable { new InlineDiffDeletedCodeMargin( () => assertReturnsDefined(zoneId), marginDomNode, + deletedCodeDomNode, this._editors.modified, a.diff, this._diffEditorWidget, @@ -273,7 +274,7 @@ export class DiffEditorViewZones extends Disposable { marginDomNode, setZoneId(id) { zoneId = id; }, showInHiddenAreas: true, - suppressMouseDown: true, + suppressMouseDown: false, }); } diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts index fa63055ff08..4a5c6dcf00e 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts @@ -18,6 +18,7 @@ import { EndOfLineSequence, ITextModel } from '../../../../../common/model.js'; import { localize } from '../../../../../../nls.js'; import { IClipboardService } from '../../../../../../platform/clipboard/common/clipboardService.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { enableCopySelection } from './copySelection.js'; export class InlineDiffDeletedCodeMargin extends Disposable { private readonly _diffActions: HTMLElement; @@ -38,6 +39,7 @@ export class InlineDiffDeletedCodeMargin extends Disposable { constructor( private readonly _getViewZoneId: () => string, private readonly _marginDomNode: HTMLElement, + private readonly _deletedCodeDomNode: HTMLElement, private readonly _modifiedEditor: CodeEditorWidget, private readonly _diff: DetailedLineRangeMapping, private readonly _editor: DiffEditorWidget, @@ -64,12 +66,13 @@ export class InlineDiffDeletedCodeMargin extends Disposable { let currentLineNumberOffset = 0; const useShadowDOM = _modifiedEditor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035 - const showContextMenu = (x: number, y: number) => { + const showContextMenu = (anchor: { x: number; y: number }, baseActions?: Action[], onHide?: () => void) => { this._contextMenuService.showContextMenu({ domForShadowRoot: useShadowDOM ? _modifiedEditor.getDomNode() ?? undefined : undefined, - getAnchor: () => ({ x, y }), + getAnchor: () => anchor, + onHide, getActions: () => { - const actions: Action[] = []; + const actions: Action[] = baseActions ?? []; const isDeletion = _diff.modified.isEmpty; // default action @@ -135,7 +138,7 @@ export class InlineDiffDeletedCodeMargin extends Disposable { const { top, height } = getDomNodePagePosition(this._diffActions); const pad = Math.floor(lineHeight / 3); e.preventDefault(); - showContextMenu(e.posx, top + height + pad); + showContextMenu({ x: e.posx, y: top + height + pad }); })); this._register(_modifiedEditor.onMouseMove((e: IEditorMouseEvent) => { @@ -147,18 +150,16 @@ export class InlineDiffDeletedCodeMargin extends Disposable { } })); - this._register(_modifiedEditor.onMouseDown((e: IEditorMouseEvent) => { - if (!e.event.leftButton) { return; } - - if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === MouseTargetType.GUTTER_VIEW_ZONE) { - const viewZoneId = e.target.detail.viewZoneId; - - if (viewZoneId === this._getViewZoneId()) { - e.event.preventDefault(); - currentLineNumberOffset = this._updateLightBulbPosition(this._marginDomNode, e.event.browserEvent.y, lineHeight); - showContextMenu(e.event.posx, e.event.posy + lineHeight); - } - } + this._register(enableCopySelection({ + domNode: this._deletedCodeDomNode, + getViewZoneId: () => this._getViewZoneId(), + diffEntry: _diff, + originalModel: this._originalTextModel, + editorLineHeight: lineHeight, + viewLineCounts: this._viewLineCounts, + editor: _modifiedEditor, + showContextMenu, + clipboardService: _clipboardService, })); } diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index 8a461de93ef..6e27efbe75a 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -426,3 +426,14 @@ margin: 0 4px; } } + +.monaco-editor .line-delete-selectable { + user-select: text !important; + -webkit-user-select: text !important; + z-index: 1 !important; +} + +.line-delete-selectable .view-line { + user-select: text !important; + -webkit-user-select: text !important; +} From 70c82b00a938a82ec62ff80c7f72f4b94b898ca2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 15 Oct 2025 12:58:20 +0200 Subject: [PATCH 1179/4355] Fixes https://github.com/microsoft/monaco-editor/issues/4659 (#271497) --- .../editor/browser/widget/diffEditor/diffEditorWidget.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index d128b85c182..607a104123e 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -92,7 +92,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, - @ICodeEditorService codeEditorService: ICodeEditorService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IEditorProgressService private readonly _editorProgressService: IEditorProgressService, ) { @@ -180,7 +180,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { }); this._diffValue = this._diffModel.map((m, r) => m?.diff.read(r)); this.onDidUpdateDiff = Event.fromObservableLight(this._diffValue); - codeEditorService.willCreateDiffEditor(); + this._codeEditorService.willCreateDiffEditor(); this._contextKeyService.createKey('isInDiffEditor', true); @@ -344,7 +344,10 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._createDiffEditorContributions(); - codeEditorService.addDiffEditor(this); + this._codeEditorService.addDiffEditor(this); + this._register(toDisposable(() => { + this._codeEditorService.removeDiffEditor(this); + })); this._gutter = derivedDisposable(this, reader => { return this._options.shouldRenderGutterMenu.read(reader) From fb41f69a941d707ca233dded698299ce98bdbfa7 Mon Sep 17 00:00:00 2001 From: Piotr Rajnisz <56397164+rajniszp@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:19:51 +0200 Subject: [PATCH 1180/4355] i18n: add missing localizations for monaco-editor (#268038) Add missing localizations --- build/gulpfile.editor.js | 2 +- build/lib/i18n.js | 7 ++++--- build/lib/i18n.ts | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 1136430dd99..5d8d47677a6 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -75,7 +75,7 @@ const compileEditorESMTask = task.define('compile-editor-esm', () => { .pipe(i18n.processNlsFiles({ out, fileHeader: BUNDLED_FILE_HEADER, - languages: i18n.defaultLanguages, + languages: [...i18n.defaultLanguages, ...i18n.extraLanguages], })) .pipe(filter(['**', '!**/inlineEntryPoint*', '!**/tsconfig.json', '!**/loader.js'])) .pipe(gulp.dest(out)) diff --git a/build/lib/i18n.js b/build/lib/i18n.js index a2d7e8720fd..e0dafe44aaf 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -41,11 +41,12 @@ exports.defaultLanguages = [ { id: 'ru', folderName: 'rus' }, { id: 'it', folderName: 'ita' } ]; -// languages requested by the community to non-stable builds +// languages requested by the community exports.extraLanguages = [ { id: 'pt-br', folderName: 'ptb' }, - { id: 'hu', folderName: 'hun' }, - { id: 'tr', folderName: 'trk' } + { id: 'tr', folderName: 'trk' }, + { id: 'cs' }, + { id: 'pl' } ]; var LocalizeInfo; (function (LocalizeInfo) { diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index b4766f725b7..4506b2e3cd0 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -44,11 +44,12 @@ export const defaultLanguages: Language[] = [ { id: 'it', folderName: 'ita' } ]; -// languages requested by the community to non-stable builds +// languages requested by the community export const extraLanguages: Language[] = [ { id: 'pt-br', folderName: 'ptb' }, - { id: 'hu', folderName: 'hun' }, - { id: 'tr', folderName: 'trk' } + { id: 'tr', folderName: 'trk' }, + { id: 'cs' }, + { id: 'pl' } ]; interface Item { From 36f84aa3bcd066c52759c37037018fa5db4828ed Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 15 Oct 2025 13:24:02 +0200 Subject: [PATCH 1181/4355] fix compile errors, fyi @roblourens (#271503) --- extensions/mermaid-chat-features/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/mermaid-chat-features/tsconfig.json b/extensions/mermaid-chat-features/tsconfig.json index 7e1920f289e..35a9a9ad8a0 100644 --- a/extensions/mermaid-chat-features/tsconfig.json +++ b/extensions/mermaid-chat-features/tsconfig.json @@ -15,6 +15,7 @@ "../../src/vscode-dts/vscode.proposed.chatOutputRenderer.d.ts", "../../src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts", "../../src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts", - "../../src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts" + "../../src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts", + "../../src/vscode-dts/vscode.proposed.languageModelProxy.d.ts" ] } From aceb7fe621c4cedf6c2f13d5b577e5a2217c9078 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 15 Oct 2025 07:58:26 -0400 Subject: [PATCH 1182/4355] Fix model restoration logic (#271509) --- src/vs/workbench/api/common/extHostLanguageModels.ts | 4 +--- src/vs/workbench/contrib/chat/common/languageModels.ts | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 0342f7285bd..8e701dcf0b5 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -171,9 +171,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return []; } this._clearModelCache(vendor); - // TODO @lramos15 - Remove this old prepare method support in debt week - // eslint-disable-next-line local/code-no-any-casts - const modelInformation: vscode.LanguageModelChatInformation[] = (data.provider.provideLanguageModelChatInformation ? await data.provider.provideLanguageModelChatInformation(options, token) : await (data.provider as any).prepareLanguageModelChatInformation(options, token)) ?? []; + const modelInformation: vscode.LanguageModelChatInformation[] = await data.provider.provideLanguageModelChatInformation(options, token) ?? []; const modelMetadataAndIdentifier: ILanguageModelChatMetadataAndIdentifier[] = modelInformation.map((m): ILanguageModelChatMetadataAndIdentifier => { let auth; if (m.requiresAuthorization && isProposedApiEnabled(data.extension, 'chatProvider')) { diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index b672e7f9dbb..6ebea2be968 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -505,8 +505,9 @@ export class LanguageModelsService implements ILanguageModelsService { this._providers.set(vendor, provider); - // TODO @lramos15 - Smarter restore logic. Don't resolve models for all providers, but only those which were known to need restoring - this._resolveLanguageModels(vendor, true); + if (this._hasStoredModelForVendor(vendor)) { + this._resolveLanguageModels(vendor, true); + } const modelChangeListener = provider.onDidChange(async () => { await this._resolveLanguageModels(vendor, true); From a1d4cfa3d8dca89fbd2d4d510941d0853e44bdd8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:22:12 +0200 Subject: [PATCH 1183/4355] SCM - graph hover provided by extension (#271519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - 💄 consolidate git blame and timeline hover code * Git - Delete code that was commented out * SCM - graph hover provided by extension * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * More fixes * More fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- extensions/git/src/historyProvider.ts | 41 +++++----- extensions/github/package.json | 7 -- src/vs/platform/actions/common/actions.ts | 1 - .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 4 +- .../chat/browser/chatAttachmentWidgets.ts | 17 ++++- .../contrib/scm/browser/scmHistoryViewPane.ts | 45 ++--------- src/vs/workbench/contrib/scm/browser/util.ts | 75 ------------------- .../workbench/contrib/scm/common/history.ts | 2 + .../actions/common/menusExtensionPoint.ts | 6 -- .../vscode.proposed.scmHistoryProvider.d.ts | 1 + 11 files changed, 52 insertions(+), 148 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 77512ecb465..cf4fd96f749 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -12,7 +12,7 @@ import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from import { emojify, ensureEmojis } from './emoji'; import { Commit, CommitShortStat } from './git'; import { OperationKind, OperationResult } from './operation'; -import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; +import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { throttle } from './decorators'; function compareSourceControlHistoryItemRef(ref1: SourceControlHistoryItemRef, ref2: SourceControlHistoryItemRef): number { @@ -282,6 +282,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const commitAvatars = await provideSourceControlHistoryItemAvatar( this.historyItemDetailProviderRegistry, this.repository, avatarQuery); + const remoteHoverCommands = await provideSourceControlHistoryItemHoverCommands(this.historyItemDetailProviderRegistry, this.repository) ?? []; + await ensureEmojis(); const historyItems: SourceControlHistoryItem[] = []; @@ -293,6 +295,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const avatarUrl = commitAvatars?.get(commit.hash); const references = this._resolveHistoryItemRefs(commit); + const commands: Command[][] = [ + getHistoryItemHoverCommitHashCommands(Uri.file(this.repository.root), commit.hash), + processHistoryItemRemoteHoverCommands(remoteHoverCommands, commit.hash) + ]; + + const tooltip = getHistoryItemHover(avatarUrl, commit.authorName, commit.authorEmail, commit.authorDate ?? commit.commitDate, messageWithLinks, commit.shortStat, commands); + historyItems.push({ id: commit.hash, parentIds: commit.parents, @@ -304,7 +313,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec displayId: truncate(commit.hash, this.commitShortHashLength, false), timestamp: commit.authorDate?.getTime(), statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, - references: references.length !== 0 ? references : undefined + references: references.length !== 0 ? references : undefined, + tooltip } satisfies SourceControlHistoryItem); } @@ -663,6 +673,17 @@ export function getHistoryItemHover(authorAvatar: string | undefined, authorName markdownString.appendMarkdown(`\n\n---\n\n`); } + // References + // TODO@lszomoru - move these to core + // if (references && references.length > 0) { + // markdownString.appendMarkdown((references ?? []).map(ref => { + // console.log(ref); + // const labelIconId = ref.icon instanceof ThemeIcon ? ref.icon.id : ''; + // return ` $(${labelIconId}) ${ref.name}  `; + // }).join('  ')); + // markdownString.appendMarkdown(`\n\n---\n\n`); + // } + // Commands if (commands && commands.length > 0) { for (let index = 0; index < commands.length; index++) { @@ -676,21 +697,5 @@ export function getHistoryItemHover(authorAvatar: string | undefined, authorName } } - // markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash, documentUri]))} "${l10n.t('Open Commit')}")`); - // markdownString.appendMarkdown(' '); - // markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); - - // // Remote hover commands - // if (commands && commands.length > 0) { - // markdownString.appendMarkdown('  |  '); - - // const remoteCommandsMarkdown = commands - // .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - // markdownString.appendMarkdown(remoteCommandsMarkdown.join(' ')); - // } - - // markdownString.appendMarkdown('  |  '); - // markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`); - return markdownString; } diff --git a/extensions/github/package.json b/extensions/github/package.json index 726a882ebc3..cd70cfea26b 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -157,13 +157,6 @@ "group": "0_view@2" } ], - "scm/historyItem/hover": [ - { - "command": "github.graph.openOnGitHub", - "when": "github.hasGitHubRepo", - "group": "1_open@1" - } - ], "timeline/item/context": [ { "command": "github.timeline.openOnGitHub", diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 604bd28c3e1..77343d49aea 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -145,7 +145,6 @@ export class MenuId { static readonly SCMHistoryTitle = new MenuId('SCMHistoryTitle'); static readonly SCMHistoryItemContext = new MenuId('SCMHistoryItemContext'); static readonly SCMHistoryItemChangeContext = new MenuId('SCMHistoryItemChangeContext'); - static readonly SCMHistoryItemHover = new MenuId('SCMHistoryItemHover'); static readonly SCMHistoryItemRefContext = new MenuId('SCMHistoryItemRefContext'); static readonly SCMQuickDiffDecorations = new MenuId('SCMQuickDiffDecorations'); static readonly SCMTitle = new MenuId('SCMTitle'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e567cff832a..195117012b7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1661,6 +1661,7 @@ export interface SCMHistoryItemDto { readonly deletions: number; }; readonly references?: SCMHistoryItemRefDto[]; + readonly tooltip?: string | IMarkdownString | undefined; } export interface SCMHistoryItemChangeDto { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 43850a724c6..01eb10ff0ba 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -75,11 +75,13 @@ function getHistoryItemIconDto(icon: vscode.Uri | { light: vscode.Uri; dark: vsc function toSCMHistoryItemDto(historyItem: vscode.SourceControlHistoryItem): SCMHistoryItemDto { const authorIcon = getHistoryItemIconDto(historyItem.authorIcon); + const tooltip = MarkdownString.fromStrict(historyItem.tooltip); + const references = historyItem.references?.map(r => ({ ...r, icon: getHistoryItemIconDto(r.icon) })); - return { ...historyItem, authorIcon, references }; + return { ...historyItem, authorIcon, references, tooltip }; } function toSCMHistoryItemRefDto(historyItemRef?: vscode.SourceControlHistoryItemRef): SCMHistoryItemRefDto | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 313b5e9bf19..9624b1bf3be 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -49,7 +49,7 @@ import { IPreferencesService } from '../../../services/preferences/common/prefer import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; import { CellUri } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; -import { getHistoryItemEditorTitle, getHistoryItemHoverContent } from '../../scm/browser/util.js'; +import { getHistoryItemEditorTitle } from '../../scm/browser/util.js'; import { IChatContentReference } from '../common/chatService.js'; import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry, ISCMHistoryItemChangeRangeVariableEntry } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; @@ -790,7 +790,12 @@ export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget this.element.style.cursor = 'pointer'; this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); - this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, () => getHistoryItemHoverContent(themeService, attachment.historyItem), { trapFocus: true })); + const historyItem = attachment.historyItem; + const hoverContent = { + markdown: historyItem.tooltip ?? historyItem.message, + markdownNotSupportedFallback: historyItem.message + } satisfies IManagedHoverTooltipMarkdownString; + this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); this._store.add(dom.addDisposableListener(this.element, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, true); @@ -835,7 +840,13 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment this.label.setFile(attachment.value, { fileKind: FileKind.FILE, hidePath: true, nameSuffix }); this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); - this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, () => getHistoryItemHoverContent(themeService, attachment.historyItem), { trapFocus: true })); + + const historyItem = attachment.historyItem; + const hoverContent = { + markdown: historyItem.tooltip ?? historyItem.message, + markdownNotSupportedFallback: historyItem.message + } satisfies IManagedHoverTooltipMarkdownString; + this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); this.addResourceOpenHandlers(attachment.value, undefined); this.attachClearButton(); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index d67712de4c6..8a36faae018 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -5,7 +5,7 @@ import './media/scm.css'; import { $, append, h, reset } from '../../../../base/browser/dom.js'; -import { IHoverAction, IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { IHoverOptions, IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; import { IIdentityProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; @@ -29,7 +29,7 @@ import { IFileIconTheme, IThemeService } from '../../../../platform/theme/common import { IViewPaneOptions, ViewAction, ViewPane, ViewPaneShowActions } from '../../../browser/parts/views/viewPane.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverLabelForeground, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; -import { getHistoryItemEditorTitle, getHistoryItemHoverContent, getProviderKey, isSCMHistoryItemChangeNode, isSCMHistoryItemChangeViewModelTreeElement, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; +import { getHistoryItemEditorTitle, getProviderKey, isSCMHistoryItemChangeNode, isSCMHistoryItemChangeViewModelTreeElement, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService, ViewMode } from '../common/scm.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; @@ -53,7 +53,6 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { clamp } from '../../../../base/common/numbers.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { compare } from '../../../../base/common/strings.js'; -import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; @@ -408,7 +407,6 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer('scm.graph.badges', 'filter', this._configurationService); } @@ -445,9 +442,11 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer item[1]); - - return [ - { - commandId: 'workbench.scm.action.graph.copyHistoryItemId', - iconClass: 'codicon.codicon-copy', - label: historyItem.displayId ?? historyItem.id, - run: () => this._clipboardService.writeText(historyItem.id) - }, - ...actions.map(action => { - const iconClass = ThemeIcon.isThemeIcon(action.item.icon) - ? ThemeIcon.asClassNameArray(action.item.icon).join('.') - : undefined; - - return { - commandId: action.id, - label: action.label, - iconClass, - run: () => action.run(historyItem) - }; - }) satisfies IHoverAction[] - ]; - } - private _processMatches(historyItemViewModel: ISCMHistoryItemViewModel, filterData: LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] { if (!filterData) { return [undefined, undefined]; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 784f81ec934..e982ea2ac36 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../nls.js'; -import * as platform from '../../../../base/common/platform.js'; import { ISCMHistoryItem, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from '../common/scm.js'; import { IMenu, MenuItemAction } from '../../../../platform/actions/common/actions.js'; @@ -20,14 +18,6 @@ import { Command } from '../../../../editor/common/languages.js'; import { reset } from '../../../../base/browser/dom.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IResourceNode, ResourceTree } from '../../../../base/common/resourceTree.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { URI } from '../../../../base/common/uri.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { fromNow, safeIntl } from '../../../../base/common/date.js'; -import { historyItemHoverAdditionsForeground, historyItemHoverDefaultLabelBackground, historyItemHoverDefaultLabelForeground, historyItemHoverDeletionsForeground, historyItemHoverLabelForeground } from './scmHistory.js'; -import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; export function isSCMViewService(element: any): element is ISCMViewService { return Array.isArray((element as ISCMViewService).repositories) && Array.isArray((element as ISCMViewService).visibleRepositories); @@ -156,68 +146,3 @@ export function getRepositoryResourceCount(provider: ISCMProvider): number { export function getHistoryItemEditorTitle(historyItem: ISCMHistoryItem): string { return `${historyItem.displayId ?? historyItem.id} - ${historyItem.subject}`; } - -export function getHistoryItemHoverContent(themeService: IThemeService, historyItem: ISCMHistoryItem): IManagedHoverTooltipMarkdownString { - const colorTheme = themeService.getColorTheme(); - const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); - - if (historyItem.author) { - const icon = URI.isUri(historyItem.authorIcon) - ? `![${historyItem.author}](${historyItem.authorIcon.toString()}|width=20,height=20)` - : ThemeIcon.isThemeIcon(historyItem.authorIcon) - ? `$(${historyItem.authorIcon.id})` - : '$(account)'; - - if (historyItem.authorEmail) { - const emailTitle = localize('emailLinkTitle', "Email"); - markdown.appendMarkdown(`${icon} [**${historyItem.author}**](mailto:${historyItem.authorEmail} "${emailTitle} ${historyItem.author}")`); - } else { - markdown.appendMarkdown(`${icon} **${historyItem.author}**`); - } - - if (historyItem.timestamp) { - const dateFormatter = safeIntl.DateTimeFormat(platform.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }).value; - markdown.appendMarkdown(`, $(history) ${fromNow(historyItem.timestamp, true, true)} (${dateFormatter.format(historyItem.timestamp)})`); - } - - markdown.appendMarkdown('\n\n'); - } - - markdown.appendMarkdown(`${historyItem.message.replace(/\r\n|\r|\n/g, '\n\n')}\n\n`); - - if (historyItem.statistics) { - markdown.appendMarkdown(`---\n\n`); - - markdown.appendMarkdown(`${historyItem.statistics.files === 1 ? - localize('fileChanged', "{0} file changed", historyItem.statistics.files) : - localize('filesChanged', "{0} files changed", historyItem.statistics.files)}`); - - if (historyItem.statistics.insertions) { - const additionsForegroundColor = colorTheme.getColor(historyItemHoverAdditionsForeground); - markdown.appendMarkdown(`, ${historyItem.statistics.insertions === 1 ? - localize('insertion', "{0} insertion{1}", historyItem.statistics.insertions, '(+)') : - localize('insertions', "{0} insertions{1}", historyItem.statistics.insertions, '(+)')}`); - } - - if (historyItem.statistics.deletions) { - const deletionsForegroundColor = colorTheme.getColor(historyItemHoverDeletionsForeground); - markdown.appendMarkdown(`, ${historyItem.statistics.deletions === 1 ? - localize('deletion', "{0} deletion{1}", historyItem.statistics.deletions, '(-)') : - localize('deletions', "{0} deletions{1}", historyItem.statistics.deletions, '(-)')}`); - } - } - - if ((historyItem.references ?? []).length > 0) { - markdown.appendMarkdown(`\n\n---\n\n`); - markdown.appendMarkdown((historyItem.references ?? []).map(ref => { - const labelIconId = ThemeIcon.isThemeIcon(ref.icon) ? ref.icon.id : ''; - - const labelBackgroundColor = ref.color ? asCssVariable(ref.color) : asCssVariable(historyItemHoverDefaultLabelBackground); - const labelForegroundColor = ref.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(historyItemHoverDefaultLabelForeground); - - return ` $(${labelIconId}) ${ref.name}  `; - }).join('  ')); - } - - return { markdown, markdownNotSupportedFallback: historyItem.message }; -} diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 77e8b714319..def84c84214 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; @@ -73,6 +74,7 @@ export interface ISCMHistoryItem { readonly timestamp?: number; readonly statistics?: ISCMHistoryItemStatistics; readonly references?: ISCMHistoryItemRef[]; + readonly tooltip?: string | IMarkdownString | undefined; } export interface ISCMHistoryItemGraphNode { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 2d4a20c1070..d8ae0d506ff 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -192,12 +192,6 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.historyItemContext', "The Source Control history item context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, - { - key: 'scm/historyItem/hover', - id: MenuId.SCMHistoryItemHover, - description: localize('menus.historyItemHover', "The Source Control history item hover menu"), - proposed: 'contribSourceControlHistoryItemMenu' - }, { key: 'scm/historyItemRef/context', id: MenuId.SCMHistoryItemRefContext, diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index cf5f3c8e326..5f0a8eb257c 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -61,6 +61,7 @@ declare module 'vscode' { readonly timestamp?: number; readonly statistics?: SourceControlHistoryItemStatistics; readonly references?: SourceControlHistoryItemRef[]; + readonly tooltip?: string | MarkdownString | undefined; } export interface SourceControlHistoryItemRef { From 04079a1c9287e963848014bbdc1239f927974dee Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:32:24 -0700 Subject: [PATCH 1184/4355] Upgrade managed hover to delayed hover in chat widgets Part of #270314 --- .../chat/browser/chatAttachmentWidgets.ts | 110 +++++++++--------- .../chatAttachmentsContentPart.ts | 30 +++-- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 9624b1bf3be..13d3f00fc0f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -8,10 +8,12 @@ import { $ } from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; -import { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; -import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import type { IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Codicon } from '../../../../base/common/codicons.js'; import * as event from '../../../../base/common/event.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; @@ -56,6 +58,17 @@ import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; +const commonHoverOptions: Partial = { + appearance: { + compact: true, + showPointer: true, + }, + position: { + hoverPosition: HoverPosition.BELOW + }, + trapFocus: true, +}; + abstract class AbstractChatAttachmentWidget extends Disposable { public readonly element: HTMLElement; public readonly label: IResourceLabel; @@ -75,14 +88,13 @@ abstract class AbstractChatAttachmentWidget extends Disposable { private readonly options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - protected readonly hoverDelegate: IHoverDelegate, protected readonly currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, @ICommandService protected readonly commandService: ICommandService, @IOpenerService protected readonly openerService: IOpenerService, ) { super(); this.element = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); - this.label = contextResourceLabels.create(this.element, { supportIcons: true, hoverDelegate, hoverTargetOverride: this.element }); + this.label = contextResourceLabels.create(this.element, { supportIcons: true, hoverTargetOverride: this.element }); this._register(this.label); this.element.tabIndex = 0; this.element.role = 'button'; @@ -111,7 +123,7 @@ abstract class AbstractChatAttachmentWidget extends Disposable { const clearButton = new Button(this.element, { supportIcons: true, - hoverDelegate: this.hoverDelegate, + hoverDelegate: createInstantHoverDelegate(), title: localize('chat.attachment.clearButton', "Remove from context") }); clearButton.element.tabIndex = -1; @@ -179,7 +191,6 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IThemeService private readonly themeService: IThemeService, @@ -187,7 +198,7 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const fileBasename = basename(resource.path); const fileDirname = dirname(resource.path); @@ -196,7 +207,7 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { if (attachment.omittedState === OmittedState.Full) { ariaLabel = localize('chat.omittedFileAttachment', "Omitted this file: {0}", attachment.name); - this.renderOmittedWarning(friendlyName, ariaLabel, hoverDelegate); + this.renderOmittedWarning(friendlyName, ariaLabel); } else { const fileOptions: IFileLabelOptions = { hidePath: true, title: correspondingContentReference?.options?.status?.description }; this.label.setFile(resource, attachment.kind === 'file' ? { @@ -220,7 +231,7 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { this.attachClearButton(); } - private renderOmittedWarning(friendlyName: string, ariaLabel: string, hoverDelegate: IHoverDelegate) { + private renderOmittedWarning(friendlyName: string, ariaLabel: string) { const pillIcon = dom.$('div.chat-attached-context-pill', {}, dom.$('span.codicon.codicon-warning')); const textLabel = dom.$('span.chat-attached-context-custom-text', {}, friendlyName); this.element.appendChild(pillIcon); @@ -231,7 +242,10 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { this.element.classList.add('warning'); hoverElement.textContent = localize('chat.fileAttachmentHover', "{0} does not support this file type.", this.currentLanguageModel ? this.languageModelsService.lookupLanguageModel(this.currentLanguageModel.identifier)?.name : this.currentLanguageModel ?? 'This model'); - this._register(this.hoverService.setupManagedHover(hoverDelegate, this.element, hoverElement, { trapFocus: true })); + this._register(this.hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: hoverElement, + })); } } @@ -244,7 +258,6 @@ export class ImageAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, @@ -252,7 +265,7 @@ export class ImageAttachmentWidget extends AbstractChatAttachmentWidget { @IInstantiationService instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); let ariaLabel: string; if (attachment.omittedState === OmittedState.Full) { @@ -367,13 +380,12 @@ export class PasteAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); this.element.ariaLabel = ariaLabel; @@ -395,13 +407,11 @@ export class PasteAttachmentWidget extends AbstractChatAttachmentWidget { this.element.style.position = 'relative'; const sourceUri = attachment.copiedFrom?.uri; - const hoverContent: IManagedHoverTooltipMarkdownString = { - markdown: { - value: `${sourceUri ? this.instantiationService.invokeFunction(accessor => accessor.get(ILabelService).getUriLabel(sourceUri, { relative: true })) : attachment.fileName}\n\n---\n\n\`\`\`${attachment.language}\n\n${attachment.code}\n\`\`\``, - }, - markdownNotSupportedFallback: attachment.code, - }; - this._register(this.hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); + const hoverContent = new MarkdownString(`${sourceUri ? this.instantiationService.invokeFunction(accessor => accessor.get(ILabelService).getUriLabel(sourceUri, { relative: true })) : attachment.fileName}\n\n---\n\n\`\`\`${attachment.language}\n\n${attachment.code}\n\`\`\``); + this._register(this.hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: hoverContent, + })); const copiedFromResource = attachment.copiedFrom?.uri; if (copiedFromResource) { @@ -423,13 +433,12 @@ export class DefaultChatAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const attachmentLabel = attachment.fullName ?? attachment.name; const withIcon = attachment.icon?.id ? `$(${attachment.icon.id})\u00A0${attachmentLabel}` : attachmentLabel; @@ -471,13 +480,12 @@ export class PromptFileAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @ILabelService private readonly labelService: ILabelService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); this.hintElement = dom.append(this.element, dom.$('span.prompt-type')); @@ -551,13 +559,12 @@ export class PromptTextAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IPreferencesService preferencesService: IPreferencesService, @IHoverService hoverService: IHoverService ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); if (attachment.settingId) { const openSettings = () => preferencesService.openSettings({ jsonEditor: false, query: `@id:${attachment.settingId}` }); @@ -578,8 +585,10 @@ export class PromptTextAttachmentWidget extends AbstractChatAttachmentWidget { } this.label.setLabel(localize('instructions.label', 'Additional Instructions'), undefined, undefined); - this._register(hoverService.setupManagedHover(hoverDelegate, this.element, attachment.value, { trapFocus: true })); - + this._register(hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: attachment.value, + })); } } @@ -591,13 +600,12 @@ export class ToolSetOrToolItemAttachmentWidget extends AbstractChatAttachmentWid options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ILanguageModelToolsService toolsService: ILanguageModelToolsService, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IHoverService hoverService: IHoverService ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const toolOrToolSet = Iterable.find(toolsService.getTools(), tool => tool.id === attachment.id) ?? Iterable.find(toolsService.toolSets.get(), toolSet => toolSet.id === attachment.id); @@ -625,7 +633,10 @@ export class ToolSetOrToolItemAttachmentWidget extends AbstractChatAttachmentWid } if (hoverContent) { - this._register(hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); + this._register(hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: hoverContent, + })); } this.attachClearButton(); @@ -642,7 +653,6 @@ export class NotebookCellOutputChatAttachmentWidget extends AbstractChatAttachme options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, @@ -650,7 +660,7 @@ export class NotebookCellOutputChatAttachmentWidget extends AbstractChatAttachme @INotebookService private readonly notebookService: INotebookService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); switch (attachment.mimeType) { case 'application/vnd.code.notebook.error': { @@ -739,12 +749,11 @@ export class ElementChatAttachmentWidget extends AbstractChatAttachmentWidget { options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IEditorService editorService: IEditorService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const ariaLabel = localize('chat.elementAttachment', "Attached element, {0}", attachment.name); this.element.ariaLabel = ariaLabel; @@ -777,13 +786,12 @@ export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IHoverService hoverService: IHoverService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); this.label.setLabel(attachment.name, undefined); @@ -791,11 +799,11 @@ export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); const historyItem = attachment.historyItem; - const hoverContent = { - markdown: historyItem.tooltip ?? historyItem.message, - markdownNotSupportedFallback: historyItem.message - } satisfies IManagedHoverTooltipMarkdownString; - this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); + const hoverContent = historyItem.tooltip ?? historyItem.message; + this._store.add(hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: hoverContent, + })); this._store.add(dom.addDisposableListener(this.element, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, true); @@ -827,14 +835,13 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IHoverService hoverService: IHoverService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @IEditorService private readonly editorService: IEditorService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const nameSuffix = `\u00A0$(${Codicon.gitCommit.id})${attachment.historyItem.displayId ?? attachment.historyItem.id}`; this.label.setFile(attachment.value, { fileKind: FileKind.FILE, hidePath: true, nameSuffix }); @@ -842,11 +849,11 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); const historyItem = attachment.historyItem; - const hoverContent = { - markdown: historyItem.tooltip ?? historyItem.message, - markdownNotSupportedFallback: historyItem.message - } satisfies IManagedHoverTooltipMarkdownString; - this._store.add(hoverService.setupManagedHover(hoverDelegate, this.element, hoverContent, { trapFocus: true })); + const hoverContent = historyItem.tooltip ?? historyItem.message; + this._store.add(hoverService.setupDelayedHover(this.element, { + ...commonHoverOptions, + content: hoverContent, + })); this.addResourceOpenHandlers(attachment.value, undefined); this.attachClearButton(); @@ -873,12 +880,11 @@ export class SCMHistoryItemChangeRangeAttachmentWidget extends AbstractChatAttac options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, container: HTMLElement, contextResourceLabels: ResourceLabels, - hoverDelegate: IHoverDelegate, @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @IEditorService private readonly editorService: IEditorService, ) { - super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService); + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService); const historyItemStartId = attachment.historyItemChangeStart.historyItem.displayId ?? attachment.historyItemChangeStart.historyItem.id; const historyItemEndId = attachment.historyItemChangeEnd.historyItem.displayId ?? attachment.historyItemChangeEnd.historyItem.id; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index 131cbf43732..2dee68f7d4b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; -import { createInstantHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { basename } from '../../../../../base/common/path.js'; @@ -58,13 +57,12 @@ export class ChatAttachmentsContentPart extends Disposable { private initAttachedContext(container: HTMLElement) { dom.clearNode(container); this.attachedContextDisposables.clear(); - const hoverDelegate = this.attachedContextDisposables.add(createInstantHoverDelegate()); const visibleAttachments = this.getVisibleAttachments(); const hasMoreAttachments = this.limit && this.variables.length > this.limit && !this._showingAll; for (const attachment of visibleAttachments) { - this.renderAttachment(attachment, container, hoverDelegate); + this.renderAttachment(attachment, container); } if (hasMoreAttachments) { @@ -116,7 +114,7 @@ export class ChatAttachmentsContentPart extends Disposable { this.attachedContextDisposables.add({ dispose: () => showMoreButton.remove() }); } - private renderAttachment(attachment: IChatRequestVariableEntry, container: HTMLElement, hoverDelegate: any) { + private renderAttachment(attachment: IChatRequestVariableEntry, container: HTMLElement) { const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; const correspondingContentReference = this.contentReferences.find((ref) => (typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name) || (URI.isUri(ref.reference) && basename(ref.reference.path) === attachment.name)); @@ -125,36 +123,36 @@ export class ChatAttachmentsContentPart extends Disposable { let widget; if (attachment.kind === 'tool' || attachment.kind === 'toolset') { - widget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isElementVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isImageVariableEntry(attachment)) { attachment.omittedState = isAttachmentPartialOrOmitted ? OmittedState.Full : attachment.omittedState; - widget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isPromptFileVariableEntry(attachment)) { if (attachment.automaticallyAdded) { return; // Skip automatically added prompt files } - widget = this.instantiationService.createInstance(PromptFileAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(PromptFileAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isPromptTextVariableEntry(attachment)) { if (attachment.automaticallyAdded) { return; // Skip automatically added prompt text } - widget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { - widget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isPasteVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (resource && isNotebookOutputVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isSCMHistoryItemVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { - widget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else { - widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate); + widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } let ariaLabel: string | null = null; From 6f141e6e8160f8d60049a1d67cd96e32c8216a84 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:51:41 -0700 Subject: [PATCH 1185/4355] Use groupId in chat attachment hovers --- .../chat/browser/chatAttachmentWidgets.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 13d3f00fc0f..2d63ad8b8a5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -8,7 +8,7 @@ import { $ } from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; -import type { IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IHoverLifecycleOptions, IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Codicon } from '../../../../base/common/codicons.js'; @@ -68,6 +68,9 @@ const commonHoverOptions: Partial = { }, trapFocus: true, }; +const commonHoverLifecycleOptions: IHoverLifecycleOptions = { + groupId: 'chat-attachments', +}; abstract class AbstractChatAttachmentWidget extends Disposable { public readonly element: HTMLElement; @@ -245,7 +248,7 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { this._register(this.hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: hoverElement, - })); + }, commonHoverLifecycleOptions)); } } @@ -411,7 +414,7 @@ export class PasteAttachmentWidget extends AbstractChatAttachmentWidget { this._register(this.hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: hoverContent, - })); + }, commonHoverLifecycleOptions)); const copiedFromResource = attachment.copiedFrom?.uri; if (copiedFromResource) { @@ -588,7 +591,7 @@ export class PromptTextAttachmentWidget extends AbstractChatAttachmentWidget { this._register(hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: attachment.value, - })); + }, commonHoverLifecycleOptions)); } } @@ -636,7 +639,7 @@ export class ToolSetOrToolItemAttachmentWidget extends AbstractChatAttachmentWid this._register(hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: hoverContent, - })); + }, commonHoverLifecycleOptions)); } this.attachClearButton(); @@ -803,7 +806,7 @@ export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget this._store.add(hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: hoverContent, - })); + }, commonHoverLifecycleOptions)); this._store.add(dom.addDisposableListener(this.element, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, true); @@ -853,7 +856,7 @@ export class SCMHistoryItemChangeAttachmentWidget extends AbstractChatAttachment this._store.add(hoverService.setupDelayedHover(this.element, { ...commonHoverOptions, content: hoverContent, - })); + }, commonHoverLifecycleOptions)); this.addResourceOpenHandlers(attachment.value, undefined); this.attachClearButton(); From 62ff0cceb433b4447e4459509bb75cdf1cc115c7 Mon Sep 17 00:00:00 2001 From: Baptiste Girardeau Date: Wed, 15 Oct 2025 07:53:12 -0700 Subject: [PATCH 1186/4355] fix: resolve renamed paths on merge editor (#254677) --- extensions/git/src/commands.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 35621e5d982..301f6bdef9f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -862,23 +862,32 @@ export class CommandCenter { } try { - const [head, rebaseOrMergeHead, diffBetween] = await Promise.all([ + const [head, rebaseOrMergeHead, oursDiff, theirsDiff] = await Promise.all([ repo.getCommit('HEAD'), isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD'), - await repo.diffBetween(isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD', 'HEAD') + await repo.diffBetween(isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD', 'HEAD'), + await repo.diffBetween('HEAD', isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD') ]); - const diffFile = diffBetween?.find(diff => diff.uri.fsPath === uri.fsPath); + + const oursDiffFile = oursDiff?.find(diff => diff.uri.fsPath === uri.fsPath); + const theirsDiffFile = theirsDiff?.find(diff => diff.uri.fsPath === uri.fsPath); // ours (current branch and commit) current.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); current.description = '$(git-commit) ' + head.hash.substring(0, 7); - current.uri = toGitUri(uri, head.hash); + if (theirsDiffFile) { + // use the original uri in case the file was renamed by theirs + current.uri = toGitUri(theirsDiffFile.originalUri, head.hash); + } else { + current.uri = toGitUri(uri, head.hash); + } // theirs incoming.detail = rebaseOrMergeHead.refNames.join(', '); incoming.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); - if (diffFile) { - incoming.uri = toGitUri(diffFile.originalUri, rebaseOrMergeHead.hash); + if (oursDiffFile) { + // use the original uri in case the file was renamed by ours + incoming.uri = toGitUri(oursDiffFile.originalUri, rebaseOrMergeHead.hash); } else { incoming.uri = toGitUri(uri, rebaseOrMergeHead.hash); } From a842708eabc2226bf01749da7acfce1c27476229 Mon Sep 17 00:00:00 2001 From: Aaron Munger <2019016+amunger@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:57:31 -0700 Subject: [PATCH 1187/4355] give inline notebook chat its own setting for using v2 controller (#270658) * make notebook inline v2 usage independent * try to adjust v1 vs v2 context keys * fix conditionals, delay cell editor check * fix v1 context key, editor still not always identified as cell editor * create alternate inline chat contribs for notebooks * Revert "create alternate inline chat contribs for notebooks" This reverts commit f8667269cdc2ca932a85e38ca819a0813ef3ec60. * fix helper method, use it to get the notebook Editor * override new method for test * unused import --- .../browser/inlineChat.contribution.ts | 6 +-- .../inlineChat/browser/inlineChatActions.ts | 22 ++++----- .../browser/inlineChatController.ts | 49 +++++++------------ .../browser/inlineChatCurrentLine.ts | 6 +-- .../browser/inlineChatSessionServiceImpl.ts | 13 ++++- .../contrib/inlineChat/common/inlineChat.ts | 20 ++++++-- .../test/browser/inlineChatController.test.ts | 5 +- .../browser/services/notebookEditorService.ts | 2 + .../services/notebookEditorServiceImpl.ts | 12 +++++ 9 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index ab86b66a756..1b6ef5d874e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -7,7 +7,7 @@ import { EditorContributionInstantiation, registerEditorContribution } from '../ import { IMenuItem, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { InlineChatController, InlineChatController1, InlineChatController2 } from './inlineChatController.js'; import * as InlineChatActions from './inlineChatActions.js'; -import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, INLINE_CHAT_ID, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js'; +import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_V1_ENABLED, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, INLINE_CHAT_ID, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; @@ -57,7 +57,7 @@ const editActionMenuItem: IMenuItem = { ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING, - CTX_INLINE_CHAT_HAS_AGENT + CTX_INLINE_CHAT_V1_ENABLED ), }; @@ -72,7 +72,7 @@ const generateActionMenuItem: IMenuItem = { ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING.toNegated(), - CTX_INLINE_CHAT_HAS_AGENT + CTX_INLINE_CHAT_V1_ENABLED ), }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 4fd0a8b1a74..5db963748e7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diff import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { InlineChatController, InlineChatController1, InlineChatController2, InlineChatRunOptions } from './inlineChatController.js'; -import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, CTX_INLINE_CHAT_HAS_AGENT2, MENU_INLINE_CHAT_SIDE } from '../common/inlineChat.js'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, MENU_INLINE_CHAT_SIDE, CTX_INLINE_CHAT_V2_ENABLED, CTX_INLINE_CHAT_V1_ENABLED } from '../common/inlineChat.js'; import { ctxHasEditorModification, ctxHasRequestInProgress, ctxIsGlobalEditingSession, ctxRequestCount } from '../../chat/browser/chatEditing/chatEditingEditorContextKeys.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; @@ -49,7 +49,7 @@ export function setHoldForSpeech(holdForSpeech: IHoldForSpeech) { } const inlineChatContextKey = ContextKeyExpr.and( - ContextKeyExpr.or(CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2), + ContextKeyExpr.or(CTX_INLINE_CHAT_V1_ENABLED, CTX_INLINE_CHAT_V2_ENABLED), CTX_INLINE_CHAT_POSSIBLE, EditorContextKeys.writable, EditorContextKeys.editorSimpleInput.negate() @@ -189,10 +189,10 @@ export abstract class AbstractInline1ChatAction extends EditorAction2 { const massageMenu = (menu: IAction2Options['menu'] | undefined) => { if (Array.isArray(menu)) { for (const entry of menu) { - entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, entry.when); + entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_V1_ENABLED, entry.when); } } else if (menu) { - menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, menu.when); + menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_V1_ENABLED, menu.when); } }; if (Array.isArray(desc.menu)) { @@ -204,7 +204,7 @@ export abstract class AbstractInline1ChatAction extends EditorAction2 { super({ ...desc, category: AbstractInline1ChatAction.category, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, desc.precondition) + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_V1_ENABLED, desc.precondition) }); } @@ -558,10 +558,10 @@ abstract class AbstractInline2ChatAction extends EditorAction2 { const massageMenu = (menu: IAction2Options['menu'] | undefined) => { if (Array.isArray(menu)) { for (const entry of menu) { - entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, entry.when); + entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_V2_ENABLED, entry.when); } } else if (menu) { - menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, menu.when); + menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_V2_ENABLED, menu.when); } }; if (Array.isArray(desc.menu)) { @@ -573,7 +573,7 @@ abstract class AbstractInline2ChatAction extends EditorAction2 { super({ ...desc, category: AbstractInline2ChatAction.category, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, desc.precondition) + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_V2_ENABLED, desc.precondition) }); } @@ -637,7 +637,7 @@ class KeepOrUndoSessionAction extends AbstractInline2ChatAction { id: MENU_INLINE_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ContextKeyExpr.greater(ctxRequestCount.key, 0), ctxHasEditorModification), + when: ContextKeyExpr.and(ContextKeyExpr.greater(ctxRequestCount.key, 0), ctxHasEditorModification), }] }); } @@ -696,12 +696,12 @@ export class CloseSessionAction2 extends AbstractInline2ChatAction { menu: [{ id: MENU_INLINE_CHAT_SIDE, group: 'navigation', - when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ctxRequestCount.isEqualTo(0)), + when: ContextKeyExpr.and(ctxRequestCount.isEqualTo(0)), }, { id: MENU_INLINE_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ctxHasEditorModification.negate()), + when: ContextKeyExpr.and(ctxHasEditorModification.negate()), }] }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index f3507ed1b11..b20bfbdec5a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -56,7 +56,6 @@ import { ChatMode } from '../../chat/common/chatModes.js'; import { IChatService } from '../../chat/common/chatService.js'; import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { INotebookEditor } from '../../notebook/browser/notebookBrowser.js'; import { isNotebookContainingCellEditor as isNotebookWithCellEditor } from '../../notebook/browser/notebookEditor.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; @@ -128,12 +127,14 @@ export class InlineChatController implements IEditorContribution { constructor( editor: ICodeEditor, @IConfigurationService configurationService: IConfigurationService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService ) { - const inlineChat2 = observableConfigValue(InlineChatConfigKeys.EnableV2, false, configurationService); + const notebookAgent = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, configurationService); this._delegate = derived(r => { - if (inlineChat2.read(r)) { + const isNotebookCell = !!this._notebookEditorService.getNotebookForPossibleCell(editor); + if (isNotebookCell ? notebookAgent.read(r) : inlineChat2.read(r)) { return InlineChatController2.get(editor)!; } else { return InlineChatController1.get(editor)!; @@ -251,15 +252,9 @@ export class InlineChatController1 implements IEditorContribution { // check if this editor is part of a notebook editor // and iff so, use the notebook location but keep the resolveData // talk about editor data - let notebookEditor: INotebookEditor | undefined; - for (const editor of notebookEditorService.listNotebookEditors()) { - for (const [, codeEditor] of editor.codeEditors) { - if (codeEditor === this._editor) { - notebookEditor = editor; - location.location = ChatAgentLocation.Notebook; - break; - } - } + const notebookEditor = notebookEditorService.getNotebookForPossibleCell(this._editor); + if (!!notebookEditor) { + location.location = ChatAgentLocation.Notebook; } const zone = _instaService.createInstance(InlineChatZoneWidget, location, undefined, { editor: this._editor, notebookEditor }); @@ -1310,27 +1305,17 @@ export class InlineChatController2 implements IEditorContribution { // inline chat in notebooks // check if this editor is part of a notebook editor // if so, update the location and use the notebook specific widget - let notebookEditor: INotebookEditor | undefined; - for (const editor of this._notebookEditorService.listNotebookEditors()) { - for (const [, codeEditor] of editor.codeEditors) { - if (codeEditor === this._editor) { - location.location = ChatAgentLocation.Notebook; - notebookEditor = editor; - // set location2 so that the notebook agent intent is used - if (configurationService.getValue(InlineChatConfigKeys.notebookAgent)) { - location.resolveData = () => { - assertType(this._editor.hasModel()); - - return { - type: ChatAgentLocation.Notebook, - sessionInputUri: this._editor.getModel().uri, - }; - }; - } + const notebookEditor = this._notebookEditorService.getNotebookForPossibleCell(this._editor); + if (!!notebookEditor) { + location.location = ChatAgentLocation.Notebook; + location.resolveData = () => { + assertType(this._editor.hasModel()); - break; - } - } + return { + type: ChatAgentLocation.Notebook, + sessionInputUri: this._editor.getModel().uri, + }; + }; } const result = this._instaService.createInstance(InlineChatZoneWidget, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index d416e8b6ef7..563f6043a52 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -37,7 +37,7 @@ import { IChatAgentService } from '../../chat/common/chatAgents.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; import { MODE_FILE_EXTENSION } from '../../chat/common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/promptTypes.js'; -import { ACTION_START, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; +import { ACTION_START, CTX_INLINE_CHAT_V1_ENABLED, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { AbstractInline1ChatAction } from './inlineChatActions.js'; import { InlineChatController } from './inlineChatController.js'; import './media/inlineChat.css'; @@ -66,7 +66,7 @@ export class InlineChatExpandLineAction extends EditorAction2 { category: AbstractInline1ChatAction.category, title: localize2('startWithCurrentLine', "Start in Editor with Current Line"), f1: true, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_V1_ENABLED, EditorContextKeys.writable), keybinding: [{ when: CTX_INLINE_CHAT_SHOWING_HINT, weight: KeybindingWeight.WorkbenchContrib + 1, @@ -119,7 +119,7 @@ export class ShowInlineChatHintAction extends EditorAction2 { category: AbstractInline1ChatAction.category, title: localize2('showHint', "Show Inline Chat Hint"), f1: false, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_V1_ENABLED, EditorContextKeys.writable), }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 9f00ec5f076..f87efcc4d43 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -35,7 +35,7 @@ import { IChatAgentService } from '../../chat/common/chatAgents.js'; import { ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; import { IChatService } from '../../chat/common/chatService.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_POSSIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; +import { CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT, CTX_INLINE_CHAT_HAS_NOTEBOOK_INLINE, CTX_INLINE_CHAT_POSSIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession.js'; import { IInlineChatSession2, IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService.js'; @@ -417,6 +417,8 @@ export class InlineChatEnabler { private readonly _ctxHasProvider: IContextKey; private readonly _ctxHasProvider2: IContextKey; + private readonly _ctxHasNotebookInline: IContextKey; + private readonly _ctxHasNotebookProvider: IContextKey; private readonly _ctxPossible: IContextKey; private readonly _store = new DisposableStore(); @@ -429,10 +431,14 @@ export class InlineChatEnabler { ) { this._ctxHasProvider = CTX_INLINE_CHAT_HAS_AGENT.bindTo(contextKeyService); this._ctxHasProvider2 = CTX_INLINE_CHAT_HAS_AGENT2.bindTo(contextKeyService); + this._ctxHasNotebookInline = CTX_INLINE_CHAT_HAS_NOTEBOOK_INLINE.bindTo(contextKeyService); + this._ctxHasNotebookProvider = CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT.bindTo(contextKeyService); this._ctxPossible = CTX_INLINE_CHAT_POSSIBLE.bindTo(contextKeyService); const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline)); const inlineChat2Obs = observableConfigValue(InlineChatConfigKeys.EnableV2, false, configService); + const notebookAgentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.Notebook)); + const notebookAgentConfigObs = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, configService); this._store.add(autorun(r => { const v2 = inlineChat2Obs.read(r); @@ -449,6 +455,11 @@ export class InlineChatEnabler { } })); + this._store.add(autorun(r => { + this._ctxHasNotebookInline.set(!notebookAgentConfigObs.read(r) && !!agentObs.read(r)); + this._ctxHasNotebookProvider.set(notebookAgentConfigObs.read(r) && !!notebookAgentObs.read(r)); + })); + const updateEditor = () => { const ctrl = editorService.activeEditorPane?.getControl(); const isCodeEditorLike = isCodeEditor(ctrl) || isDiffEditor(ctrl) || isCompositeEditor(ctrl); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 07bbbc8de2c..f700838a728 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -6,9 +6,10 @@ import { localize } from '../../../../nls.js'; import { MenuId } from '../../../../platform/actions/common/actions.js'; import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { diffInserted, diffRemoved, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from '../../../../platform/theme/common/colorRegistry.js'; +import { NOTEBOOK_IS_ACTIVE_EDITOR } from '../../notebook/common/notebookContextKeys.js'; // settings @@ -79,12 +80,12 @@ Registry.as(Extensions.Configuration).registerConfigurat } }, [InlineChatConfigKeys.notebookAgent]: { - markdownDescription: localize('notebookAgent', "Enable agent-like behavior for inline chat widget in notebooks. Depends on the `#inlineChat.enableV2#` setting being enabled."), + markdownDescription: localize('notebookAgent', "Enable agent-like behavior for inline chat widget in notebooks."), default: false, type: 'boolean', tags: ['experimental'], experiment: { - mode: 'auto' + mode: 'startup' } } } @@ -104,7 +105,9 @@ export const enum InlineChatResponseType { export const CTX_INLINE_CHAT_POSSIBLE = new RawContextKey('inlineChatPossible', false, localize('inlineChatHasPossible', "Whether a provider for inline chat exists and whether an editor for inline chat is open")); export const CTX_INLINE_CHAT_HAS_AGENT = new RawContextKey('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists")); -export const CTX_INLINE_CHAT_HAS_AGENT2 = new RawContextKey('inlineChatHasEditsAgent', false, localize('inlineChatHasEditsAgent', "Whether an agent for inliine for interactive editors exists")); +export const CTX_INLINE_CHAT_HAS_AGENT2 = new RawContextKey('inlineChatHasEditsAgent', false, localize('inlineChatHasEditsAgent', "Whether an agent for inline for interactive editors exists")); +export const CTX_INLINE_CHAT_HAS_NOTEBOOK_INLINE = new RawContextKey('inlineChatHasNotebookInline', false, localize('inlineChatHasNotebookInline', "Whether an agent for notebook cells exists")); +export const CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT = new RawContextKey('inlineChatHasNotebookAgent', false, localize('inlineChatHasNotebookAgent', "Whether an agent for notebook cells exists")); export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible")); export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused")); export const CTX_INLINE_CHAT_EDITING = new RawContextKey('inlineChatEditing', true, localize('inlineChatEditing', "Whether the user is currently editing or generating code in the inline chat")); @@ -119,6 +122,15 @@ export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey('inl export const CTX_INLINE_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('inlineChatRequestInProgress', false, localize('inlineChatRequestInProgress', "Whether an inline chat request is currently in progress")); export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey('inlineChatResponseType', InlineChatResponseType.None, localize('inlineChatResponseTypes', "What type was the responses have been receieved, nothing yet, just messages, or messaged and local edits")); +export const CTX_INLINE_CHAT_V1_ENABLED = ContextKeyExpr.or( + ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR.negate(), CTX_INLINE_CHAT_HAS_AGENT), + ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CTX_INLINE_CHAT_HAS_NOTEBOOK_INLINE) +); + +export const CTX_INLINE_CHAT_V2_ENABLED = ContextKeyExpr.or( + ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR.negate(), CTX_INLINE_CHAT_HAS_AGENT2), + ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT) +); // --- (selected) action identifier diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index c11c34bb272..3f91aecb961 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -13,7 +13,7 @@ import { constObservable, IObservable } from '../../../../../base/common/observa import { assertType } from '../../../../../base/common/types.js'; import { mock } from '../../../../../base/test/common/mock.js'; import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; -import { IActiveCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { IActiveCodeEditor, ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { IDiffProviderFactoryService } from '../../../../../editor/browser/widget/diffEditor/diffProviderFactoryService.js'; import { EditOperation } from '../../../../../editor/common/core/editOperation.js'; import { Range } from '../../../../../editor/common/core/range.js'; @@ -207,6 +207,9 @@ suite('InlineChatController', function () { }], [INotebookEditorService, new class extends mock() { override listNotebookEditors() { return []; } + override getNotebookForPossibleCell(editor: ICodeEditor) { + return undefined; + } }], [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()], [ILanguageModelsService, new SyncDescriptor(LanguageModelsService)], diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index 67ef7245398..6789d0e0fa3 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -10,6 +10,7 @@ import { Event } from '../../../../../base/common/event.js'; import { Dimension } from '../../../../../base/browser/dom.js'; import { NotebookEditorWidget } from '../notebookEditorWidget.js'; import { URI } from '../../../../../base/common/uri.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; export const INotebookEditorService = createDecorator('INotebookEditorWidgetService'); @@ -30,5 +31,6 @@ export interface INotebookEditorService { removeNotebookEditor(editor: INotebookEditor): void; getNotebookEditor(editorId: string): INotebookEditor | undefined; listNotebookEditors(): readonly INotebookEditor[]; + getNotebookForPossibleCell(editor: ICodeEditor): INotebookEditor | undefined; updateReplContextKey(uri: string): void; } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index d9523d8ecee..d57424d08f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -22,6 +22,7 @@ import { InteractiveWindowOpen, MOST_RECENT_REPL_EDITOR } from '../../common/not import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; import { IEditorProgressService } from '../../../../../platform/progress/common/progress.js'; import { NotebookDiffEditorInput } from '../../common/notebookDiffEditorInput.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; export class NotebookEditorWidgetService implements INotebookEditorService { @@ -288,6 +289,17 @@ export class NotebookEditorWidgetService implements INotebookEditorService { return [...this._notebookEditors].map(e => e[1]); } + getNotebookForPossibleCell(candidate: ICodeEditor): INotebookEditor | undefined { + for (const editor of this._notebookEditors.values()) { + for (const [, codeEditor] of editor.codeEditors) { + if (codeEditor === candidate) { + return editor; + } + } + } + return undefined; + } + updateReplContextKey(uri: string): void { this._mostRecentRepl.set(uri); } From 27db6613dfcb6a10aead09b3e2b054c6d4dfba61 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:58:03 -0700 Subject: [PATCH 1188/4355] Fix compile --- .../contrib/chat/browser/chatInputPart.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index ecf05af8f64..130935da97f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1397,7 +1397,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachedContextDisposables.value = store; dom.clearNode(container); - const hoverDelegate = store.add(createInstantHoverDelegate()); store.add(dom.addStandardDisposableListener(this.attachmentsContainer, dom.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { this.handleAttachmentNavigation(e); @@ -1429,29 +1428,29 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge let attachmentWidget; const options = { shouldFocusClearButton, supportsDeletion: true }; if (attachment.kind === 'tool' || attachment.kind === 'toolset') { - attachmentWidget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(ToolSetOrToolItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (resource && isNotebookOutputVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isPromptFileVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(PromptFileAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(PromptFileAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isPromptTextVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, options, container, this._contextResourceLabels); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { - attachmentWidget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isImageVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isElementVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isPasteVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isSCMHistoryItemVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isSCMHistoryItemChangeVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { - attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else { - attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate); + attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels); } if (shouldFocusClearButton) { From bc01289e90a71b236160d540bc8a647def4fe1c4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:09:02 +0200 Subject: [PATCH 1189/4355] Git - limit the timeline/blame/graph hover commands (#271530) --- extensions/git/src/historyProvider.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index cf4fd96f749..68cd1b2fbd9 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -626,7 +626,9 @@ export function processHistoryItemRemoteHoverCommands(commands: Command[], hash: export function getHistoryItemHover(authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string, shortStats: CommitShortStat | undefined, commands: Command[][] | undefined): MarkdownString { const markdownString = new MarkdownString('', true); - markdownString.isTrusted = true; + markdownString.isTrusted = { + enabledCommands: commands?.flat().map(c => c.command) ?? [] + }; if (authorName) { const avatar = authorAvatar ? `![${authorName}](${authorAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)'; From a7efa0e6b5c571212558e1624bbbaa208c3afb95 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 15 Oct 2025 08:37:03 -0700 Subject: [PATCH 1190/4355] chore: bump cgmanifest hash (#271392) --- extensions/docker/cgmanifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index ddc1abe35f2..942ba14ebd8 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -6,13 +6,13 @@ "git": { "name": "language-docker", "repositoryUrl": "https://github.com/moby/moby", - "commitHash": "c2029cb2574647e4bc28ed58486b8e85883eedb9", - "tag": "28.0.0" + "commitHash": "bea959c7b793b32a893820b97c4eadc7c87fabb0", + "tag": "28.3.3" } }, "license": "Apache-2.0", "description": "The file syntaxes/docker.tmLanguage was included from https://github.com/moby/moby/blob/master/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage.", - "version": "28.0.0" + "version": "28.3.3" } ], "version": 1 From 0c7c358d32d2b0125a86f0899605712431fd8e7e Mon Sep 17 00:00:00 2001 From: imbant <13634731015@sina.cn> Date: Wed, 15 Oct 2025 23:38:14 +0800 Subject: [PATCH 1191/4355] fix(chat): correct file icon rendering in Files & Folders picker (#255384) * fix(chat): correct file icon rendering in Files & Folders picker File icons were not displaying in the chat context Files & Folders picker while folder icons worked correctly. This was due to using iconClass instead of iconClasses property. The quick-input component has two icon rendering paths: - iconClass: applies classes to icon container element - iconClasses: applies classes to IconLabel component File icon themes require the specific CSS context provided by IconLabel, so file icons must use iconClasses to render properly. Changes: - Add iconClasses property to IChatContextPickerPickItem interface - Change FilesAndFoldersPickerPick to use iconClasses instead of iconClass - File icons now render correctly using proper DOM structure Fixes file icon display issue in chat context picker while maintaining backward compatibility with existing iconClass usage for simple codicons. * make API more explicit * fix it --------- Co-authored-by: Benjamin Pasero --- src/vs/platform/quickinput/common/quickInput.ts | 8 ++++++++ .../contrib/chat/browser/chatContextPickService.ts | 5 +++-- .../workbench/contrib/search/browser/searchChatContext.ts | 4 +--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 99c7cc65390..3716a235669 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -43,8 +43,16 @@ export interface IQuickItem { * Whether the item is displayed with a strikethrough. */ strikethrough?: boolean; + /** + * Icon classes to be passed on as `IIconLabelValueOptions` + * to the underlying `IconLabel` widget. + */ iconClasses?: readonly string[]; iconPath?: { dark: URI; light?: URI }; + /** + * Icon class to be assigned to the quick item container + * directly. + */ iconClass?: string; highlights?: IQuickItemHighlights; buttons?: readonly IQuickInputButton[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts index 9da828eaa3f..7abdb109478 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContextPickService.ts @@ -9,14 +9,15 @@ import { compare } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { isObject } from '../../../../base/common/types.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; +import { IQuickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatWidget } from './chat.js'; -export interface IChatContextPickerPickItem { +export interface IChatContextPickerPickItem extends Partial { label: string; iconClass?: string; + iconClasses?: readonly string[]; description?: string; detail?: string; disabled?: boolean; diff --git a/src/vs/workbench/contrib/search/browser/searchChatContext.ts b/src/vs/workbench/contrib/search/browser/searchChatContext.ts index 666fac35b89..c1c7c7311cd 100644 --- a/src/vs/workbench/contrib/search/browser/searchChatContext.ts +++ b/src/vs/workbench/contrib/search/browser/searchChatContext.ts @@ -209,9 +209,7 @@ class FilesAndFoldersPickerPick implements IChatContextPickerItem { return { label: basename(resource), description: this._labelService.getUriLabel(dirname(resource), { relative: true }), - iconClass: kind === FileKind.FILE - ? getIconClasses(this._modelService, this._languageService, resource, FileKind.FILE).join(' ') - : ThemeIcon.asClassName(Codicon.folder), + iconClasses: getIconClasses(this._modelService, this._languageService, resource, kind), asAttachment: () => { return { kind: kind === FileKind.FILE ? 'file' : 'directory', From 084cf12c5ae2473290d4ef71b0133477c40240fd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 08:42:49 -0700 Subject: [PATCH 1192/4355] Add noHistory argument to cursorMove command to skip navigation history (#270385) * Add noHistory argument to cursorMove command Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --- src/vs/editor/browser/coreCommands.ts | 8 ++++++-- src/vs/editor/common/cursor/cursorMoveCommands.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index 186c83dfe5b..5669797012b 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -31,6 +31,7 @@ import { IViewModel } from '../common/viewModel.js'; import { ISelection } from '../common/core/selection.js'; import { getActiveElement, isEditableElement } from '../../base/browser/dom.js'; import { EnterOperation } from '../common/cursor/cursorTypeEditOperations.js'; +import { TextEditorSelectionSource } from '../../platform/editor/common/editor.js'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -604,13 +605,16 @@ export namespace CoreNavigationCommands { } private _runCursorMove(viewModel: IViewModel, source: string | null | undefined, args: CursorMove_.ParsedArguments): void { + // If noHistory is true, use PROGRAMMATIC source to prevent adding to navigation history + const effectiveSource = args.noHistory ? TextEditorSelectionSource.PROGRAMMATIC : source; + viewModel.model.pushStackElement(); viewModel.setCursorStates( - source, + effectiveSource, CursorChangeReason.Explicit, CursorMoveImpl._move(viewModel, viewModel.getCursorStates(), args) ); - viewModel.revealAllCursors(source, true); + viewModel.revealAllCursors(effectiveSource, true); } private static _move(viewModel: IViewModel, cursors: CursorState[], args: CursorMove_.ParsedArguments): PartialCursorState[] | null { diff --git a/src/vs/editor/common/cursor/cursorMoveCommands.ts b/src/vs/editor/common/cursor/cursorMoveCommands.ts index 3aa8b5a0f4f..a447e3a8f75 100644 --- a/src/vs/editor/common/cursor/cursorMoveCommands.ts +++ b/src/vs/editor/common/cursor/cursorMoveCommands.ts @@ -604,6 +604,10 @@ export namespace CursorMove { return false; } + if (!types.isUndefined(cursorMoveArg.noHistory) && !types.isBoolean(cursorMoveArg.noHistory)) { + return false; + } + return true; }; @@ -626,6 +630,7 @@ export namespace CursorMove { \`\`\` * 'value': Number of units to move. Default is '1'. * 'select': If 'true' makes the selection. Default is 'false'. + * 'noHistory': If 'true' does not add the movement to navigation history. Default is 'false'. `, constraint: isCursorMoveArgs, schema: { @@ -647,6 +652,10 @@ export namespace CursorMove { 'select': { 'type': 'boolean', 'default': false + }, + 'noHistory': { + 'type': 'boolean', + 'default': false } } } @@ -697,6 +706,7 @@ export namespace CursorMove { select?: boolean; by?: string; value?: number; + noHistory?: boolean; } export function parse(args: Partial): ParsedArguments | null { @@ -777,7 +787,8 @@ export namespace CursorMove { direction: direction, unit: unit, select: (!!args.select), - value: (args.value || 1) + value: (args.value || 1), + noHistory: (!!args.noHistory) }; } @@ -786,6 +797,7 @@ export namespace CursorMove { unit: Unit; select: boolean; value: number; + noHistory: boolean; } export interface SimpleMoveArguments { From 351866ef1a70a15bb8df1fee84dba84adcbfd687 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:58:39 -0700 Subject: [PATCH 1193/4355] chore: use dompurify 3.2.7 (#271538) --- extensions/markdown-language-features/package-lock.json | 8 ++++---- extensions/markdown-language-features/package.json | 2 +- extensions/mermaid-chat-features/package-lock.json | 8 ++++---- extensions/mermaid-chat-features/package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index af0abe0ad6a..bb129cc3624 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "dompurify": "^3.2.4", + "dompurify": "^3.2.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", @@ -385,9 +385,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", - "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0e38bae746c..3157b4855fe 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -767,7 +767,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "dompurify": "^3.2.4", + "dompurify": "^3.2.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json index 18394643a0c..185afcde646 100644 --- a/extensions/mermaid-chat-features/package-lock.json +++ b/extensions/mermaid-chat-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "dompurify": "^3.2.6", + "dompurify": "^3.2.7", "mermaid": "^11.11.0" }, "devDependencies": { @@ -1008,9 +1008,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json index 2992cbe6c20..d7856516218 100644 --- a/extensions/mermaid-chat-features/package.json +++ b/extensions/mermaid-chat-features/package.json @@ -82,7 +82,7 @@ "@types/node": "^22.18.10" }, "dependencies": { - "dompurify": "^3.2.6", + "dompurify": "^3.2.7", "mermaid": "^11.11.0" } } From 6aeb5973b6c6c3c6f207ebf35536f7377f1e66df Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:06:36 -0700 Subject: [PATCH 1194/4355] Enforce single quote string usage in main tests For #271473 Single quoted string usage is already enforced everywhere except our tests. Having this inconsistent style can confuse contributors, tooling, and code generation Automated eslint fix pass + manual review --- src/vs/base/test/common/async.test.ts | 2 +- src/vs/base/test/common/envfile.test.ts | 6 +- src/vs/base/test/common/jsonParse.test.ts | 138 +- src/vs/base/test/common/jsonSchema.test.ts | 82 +- .../test/common/observables/debug.test.ts | 2 +- .../common/observables/observable.test.ts | 422 +- src/vs/base/test/common/uri.test.ts | 6 +- src/vs/base/test/common/yaml.test.ts | 26 +- .../test/browser/indentation.test.ts | 180 +- .../browser/indentationLineProcessor.test.ts | 12 +- .../test/browser/computeGhostText.test.ts | 4 +- .../test/browser/getSecondaryEdits.test.ts | 2 +- .../test/browser/graph.test.ts | 160 +- .../test/browser/inlineCompletions.test.ts | 38 +- .../test/browser/inlineEdits.test.ts | 12 +- .../test/browser/snippetController2.test.ts | 8 +- .../test/browser/wordPartOperations.test.ts | 8 +- .../config/editorConfiguration.test.ts | 10 +- .../test/browser/controller/cursor.test.ts | 8 +- .../browser/controller/textAreaInput.test.ts | 28 +- .../browser/widget/diffEditorWidget.test.ts | 4 +- .../widget/observableCodeEditor.test.ts | 130 +- .../core/positionOffsetTransformer.test.ts | 58 +- .../defaultDocumentColorsComputer.test.ts | 8 +- .../beforeEditPositionMapper.test.ts | 2 +- .../getBracketPairsInRange.test.ts | 76 +- .../common/services/editorWebWorker.test.ts | 8 +- .../diffing/defaultLinesDiffComputer.test.ts | 20 +- .../contextkey/test/common/parser.test.ts | 60 +- .../contextkey/test/common/scanner.test.ts | 68 +- .../common/abstractKeybindingService.test.ts | 4 +- .../api/test/browser/extHostTypes.test.ts | 16 +- .../browser/mainThreadManagedSockets.test.ts | 2 +- .../api/test/common/extensionHostMain.test.ts | 6 +- .../browser/languageModelToolsService.test.ts | 6 +- .../promptSytntax/promptValidator.test.ts | 136 +- .../service/newPromptsParser.test.ts | 76 +- .../service/promptsService.test.ts | 10 +- .../codeEditor/test/node/autoindent.test.ts | 40 +- .../debug/test/browser/baseDebugView.test.ts | 2 +- .../debug/test/browser/breakpoints.test.ts | 26 +- .../debug/test/browser/variablesView.test.ts | 2 +- .../test/browser/watchExpressionView.test.ts | 2 +- .../mcp/test/common/mcpSamplingLog.test.ts | 156 +- .../mcp/test/common/uriTemplate.test.ts | 34 +- .../mergeEditor/test/browser/model.test.ts | 114 +- .../NotebookEditorWidgetService.test.ts | 2 +- .../browser/diff/notebookDiffService.test.ts | 23572 ++++++++-------- .../test/browser/view/cellPart.test.ts | 64 +- .../search/test/browser/searchModel.test.ts | 2 +- .../search/test/browser/searchResult.test.ts | 2 +- .../test/common/taskConfiguration.test.ts | 6 +- .../test/browser/terminalInstance.test.ts | 10 +- .../xterm/shellIntegrationAddon.test.ts | 4 +- .../browser/commandLineAutoApprover.test.ts | 326 +- .../test/browser/runInTerminalTool.test.ts | 8 +- .../browser/terminalCompletionModel.test.ts | 6 +- ...terminalCompletionService.escaping.test.ts | 2 +- .../treeProjection.test.ts | 16 +- .../testing/test/common/testCoverage.test.ts | 24 +- .../test/browser/configurationService.test.ts | 36 +- .../configurationResolverService.test.ts | 8 +- .../test/browser/historyService.test.ts | 2 +- .../search/test/browser/queryBuilder.test.ts | 56 +- .../search/test/common/queryBuilder.test.ts | 8 +- 65 files changed, 13187 insertions(+), 13187 deletions(-) diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index c0048417319..354382b54ea 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import * as async from '../../common/async.js'; -import * as MicrotaskDelay from "../../common/symbols.js"; +import * as MicrotaskDelay from '../../common/symbols.js'; import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js'; import { isCancellationError } from '../../common/errors.js'; import { Event } from '../../common/event.js'; diff --git a/src/vs/base/test/common/envfile.test.ts b/src/vs/base/test/common/envfile.test.ts index 753a8c6b534..e8713e25856 100644 --- a/src/vs/base/test/common/envfile.test.ts +++ b/src/vs/base/test/common/envfile.test.ts @@ -93,14 +93,14 @@ suite('parseEnvFile', () => { assert.strictEqual(parsed.get('DOUBLE_QUOTES_SPACED'), ' double quotes '); assert.strictEqual(parsed.get('DOUBLE_QUOTES_INSIDE_SINGLE'), 'double "quotes" work inside single quotes'); assert.strictEqual(parsed.get('DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET'), '{ port: $MONGOLAB_PORT}'); - assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_DOUBLE'), "single 'quotes' work inside double quotes"); + assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_DOUBLE'), `single 'quotes' work inside double quotes`); assert.strictEqual(parsed.get('BACKTICKS_INSIDE_SINGLE'), '`backticks` work inside single quotes'); assert.strictEqual(parsed.get('BACKTICKS_INSIDE_DOUBLE'), '`backticks` work inside double quotes'); assert.strictEqual(parsed.get('BACKTICKS'), 'backticks'); assert.strictEqual(parsed.get('BACKTICKS_SPACED'), ' backticks '); assert.strictEqual(parsed.get('DOUBLE_QUOTES_INSIDE_BACKTICKS'), 'double "quotes" work inside backticks'); - assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_BACKTICKS'), "single 'quotes' work inside backticks"); - assert.strictEqual(parsed.get('DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS'), "double \"quotes\" and single 'quotes' work inside backticks"); + assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_BACKTICKS'), `single 'quotes' work inside backticks`); + assert.strictEqual(parsed.get('DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS'), `double "quotes" and single 'quotes' work inside backticks`); assert.strictEqual(parsed.get('EXPAND_NEWLINES'), 'expand\nnew\nlines'); assert.strictEqual(parsed.get('DONT_EXPAND_UNQUOTED'), 'dontexpand\\nnewlines'); assert.strictEqual(parsed.get('DONT_EXPAND_SQUOTED'), 'dontexpand\\nnewlines'); diff --git a/src/vs/base/test/common/jsonParse.test.ts b/src/vs/base/test/common/jsonParse.test.ts index 83e520fad22..4fab789adee 100644 --- a/src/vs/base/test/common/jsonParse.test.ts +++ b/src/vs/base/test/common/jsonParse.test.ts @@ -12,127 +12,127 @@ suite('JSON Parse', () => { test('Line comment', () => { const content: string = [ - "{", - " \"prop\": 10 // a comment", - "}", + '{', + ' "prop": 10 // a comment', + '}', ].join('\n'); const expected = [ - "{", - " \"prop\": 10 ", - "}", + '{', + ' "prop": 10 ', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF', () => { const content: string = [ - "{", - "}", - "// a comment" + '{', + '}', + '// a comment' ].join('\n'); const expected = [ - "{", - "}", - "" + '{', + '}', + '' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - \\r\\n', () => { const content: string = [ - "{", - " \"prop\": 10 // a comment", - "}", + '{', + ' "prop": 10 // a comment', + '}', ].join('\r\n'); const expected = [ - "{", - " \"prop\": 10 ", - "}", + '{', + ' "prop": 10 ', + '}', ].join('\r\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF - \\r\\n', () => { const content: string = [ - "{", - "}", - "// a comment" + '{', + '}', + '// a comment' ].join('\r\n'); const expected = [ - "{", - "}", - "" + '{', + '}', + '' ].join('\r\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - single line', () => { const content: string = [ - "{", - " /* before */\"prop\": 10/* after */", - "}", + '{', + ' /* before */"prop": 10/* after */', + '}', ].join('\n'); const expected = [ - "{", - " \"prop\": 10", - "}", + '{', + ' "prop": 10', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - multi line', () => { const content: string = [ - "{", - " /**", - " * Some comment", - " */", - " \"prop\": 10", - "}", + '{', + ' /**', + ' * Some comment', + ' */', + ' "prop": 10', + '}', ].join('\n'); const expected = [ - "{", - " ", - " \"prop\": 10", - "}", + '{', + ' ', + ' "prop": 10', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - shortest match', () => { - const content = "/* abc */ */"; - const expected = " */"; + const content = '/* abc */ */'; + const expected = ' */'; assert.strictEqual(stripComments(content), expected); }); test('No strings - double quote', () => { const content: string = [ - "{", - " \"/* */\": 10", - "}" + '{', + ' "/* */": 10', + '}' ].join('\n'); const expected: string = [ - "{", - " \"/* */\": 10", - "}" + '{', + ' "/* */": 10', + '}' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('No strings - single quote', () => { const content: string = [ - "{", - " '/* */': 10", - "}" + '{', + ` '/* */': 10`, + '}' ].join('\n'); const expected: string = [ - "{", - " '/* */': 10", - "}" + '{', + ` '/* */': 10`, + '}' ].join('\n'); assert.strictEqual(stripComments(content), expected); }); test('Trailing comma in object', () => { const content: string = [ - "{", + '{', ` "a": 10,`, - "}" + '}' ].join('\n'); const expected: string = [ - "{", + '{', ` "a": 10`, - "}" + '}' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); @@ -148,16 +148,16 @@ suite('JSON Parse', () => { test('Trailing comma', () => { const content: string = [ - "{", - " \"propA\": 10, // a comment", - " \"propB\": false, // a trailing comma", - "}", + '{', + ' "propA": 10, // a comment', + ' "propB": false, // a trailing comma', + '}', ].join('\n'); const expected = [ - "{", - " \"propA\": 10,", - " \"propB\": false", - "}", + '{', + ' "propA": 10,', + ' "propB": false', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); @@ -186,9 +186,9 @@ suite('JSON Parse', () => { } `; assert.deepEqual(parse(content), { - "enable-crash-reporter": true, - "crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7", - "locale": "en" + 'enable-crash-reporter': true, + 'crash-reporter-id': 'aaaaab31-7453-4506-97d0-93411b2c21c7', + 'locale': 'en' }); }); }); diff --git a/src/vs/base/test/common/jsonSchema.test.ts b/src/vs/base/test/common/jsonSchema.test.ts index da678c56675..aad3607ad37 100644 --- a/src/vs/base/test/common/jsonSchema.test.ts +++ b/src/vs/base/test/common/jsonSchema.test.ts @@ -79,7 +79,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { c: { @@ -155,7 +155,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { b: { @@ -270,44 +270,44 @@ suite('JSON Schema', () => { }; const expected: IJSONSchema = { - "type": "object", - "properties": { - "a": { - "type": "object", - "oneOf": [ + 'type': 'object', + 'properties': { + 'a': { + 'type': 'object', + 'oneOf': [ { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "$ref": "#/$defs/_1" + '$ref': '#/$defs/_1' } ] }, { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "properties": { - "river": { - "type": "string" + 'properties': { + 'river': { + 'type': 'string' } } } ] }, { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "properties": { - "mountain": { - "type": "string" + 'properties': { + 'mountain': { + 'type': 'string' } } } @@ -315,30 +315,30 @@ suite('JSON Schema', () => { } ] }, - "b": { - "type": "object", - "properties": { - "street": { - "$ref": "#/$defs/_1" + 'b': { + 'type': 'object', + 'properties': { + 'street': { + '$ref': '#/$defs/_1' } } } }, - "$defs": { - "_0": { - "properties": { - "name": { - "type": "string" + '$defs': { + '_0': { + 'properties': { + 'name': { + 'type': 'string' }, - "description": { - "type": "string" + 'description': { + 'type': 'string' } } }, - "_1": { - "properties": { - "street": { - "type": "string" + '_1': { + 'properties': { + 'street': { + 'type': 'string' } } } @@ -415,7 +415,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { b: { @@ -428,7 +428,7 @@ suite('JSON Schema', () => { } } }, - "_1": { + '_1': { type: 'object', properties: { d: { @@ -536,13 +536,13 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'array', items: { $ref: '#/$defs/_2' } }, - "_1": { + '_1': { type: 'object', properties: { b: { @@ -550,7 +550,7 @@ suite('JSON Schema', () => { } } }, - "_2": { + '_2': { type: 'object', properties: { c: { diff --git a/src/vs/base/test/common/observables/debug.test.ts b/src/vs/base/test/common/observables/debug.test.ts index d3bebb9126e..5be20a336d6 100644 --- a/src/vs/base/test/common/observables/debug.test.ts +++ b/src/vs/base/test/common/observables/debug.test.ts @@ -51,7 +51,7 @@ suite('debug', () => { let idx = 0; assert.deepStrictEqual( debugGetDependencyGraph(myComputed3, { debugNamePostProcessor: name => `name${++idx}` }), - "* derived name1:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived name2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived name3:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue name4:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue name5:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue name6 (already listed)\n\t\t\t\t* observableValue name7 (already listed)\n\t\t* observableValue name8 (already listed)\n\t\t* observableValue name9 (already listed)", + '* derived name1:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived name2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived name3:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue name4:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue name5:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue name6 (already listed)\n\t\t\t\t* observableValue name7 (already listed)\n\t\t* observableValue name8 (already listed)\n\t\t* observableValue name9 (already listed)', ); }); }); diff --git a/src/vs/base/test/common/observables/observable.test.ts b/src/vs/base/test/common/observables/observable.test.ts index 3cd85109708..c1814dfe7b1 100644 --- a/src/vs/base/test/common/observables/observable.test.ts +++ b/src/vs/base/test/common/observables/observable.test.ts @@ -97,22 +97,22 @@ suite('observables', () => { })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 0 + 0 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: 0 + 0 = 0', + 'myAutorun(myDerived: 0)', ]); observable1.set(1, undefined); // and on changes... assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 1 + 0 = 1", - "myAutorun(myDerived: 1)", + 'myDerived.recompute: 1 + 0 = 1', + 'myAutorun(myDerived: 1)', ]); observable2.set(1, undefined); // ... of any dependency. assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 1 + 1 = 2", - "myAutorun(myDerived: 2)", + 'myDerived.recompute: 1 + 1 = 2', + 'myAutorun(myDerived: 2)', ]); // Now we change multiple observables in a transaction to batch process the effects. @@ -127,8 +127,8 @@ suite('observables', () => { // deriveds are only recomputed on demand. // (Note that you cannot see the intermediate value when `obs1 == 5` and `obs2 == 1`) assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 5 + 5 = 10", - "myAutorun(myDerived: 10)", + 'myDerived.recompute: 5 + 5 = 10', + 'myAutorun(myDerived: 10)', ]); transaction((tx) => { @@ -139,7 +139,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); }); // Now the autorun didn't run again, because its dependency changed from 10 to 10 (= no change). - assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: 6 + 4 = 10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myDerived.recompute: 6 + 4 = 10'])); }); test('read during transaction', () => { @@ -162,8 +162,8 @@ suite('observables', () => { })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 0 + 0 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: 0 + 0 = 0', + 'myAutorun(myDerived: 0)', ]); transaction((tx) => { @@ -171,7 +171,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); myDerived.get(); // This forces a (sync) recomputation of the current value! - assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: -10 + 0 = -10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myDerived.recompute: -10 + 0 = -10'])); // This means, that even in transactions you can assume that all values you can read with `get` and `read` are up-to-date. // Read these values just might cause additional (potentially unneeded) recomputations. @@ -180,8 +180,8 @@ suite('observables', () => { }); // This autorun runs again, because its dependency changed from 0 to -10 and then back to 0. assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: -10 + 10 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: -10 + 10 = 0', + 'myAutorun(myDerived: 0)', ]); }); @@ -268,35 +268,35 @@ suite('observables', () => { log.log(`value: ${computedSum.get()}`); // Those deriveds are recomputed on demand, i.e. when someone reads them. assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); log.log(`value: ${computedSum.get()}`); // ... and then cached again - assert.deepStrictEqual(log.getAndClearEntries(), (["value: 10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['value: 10'])); disposable.dispose(); // Don't forget to dispose the keepAlive to prevent memory leaks! log.log(`value: ${computedSum.get()}`); // Which disables the cache again assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); log.log(`value: ${computedSum.get()}`); assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); // Why don't we just always keep the cache alive? @@ -395,38 +395,38 @@ suite('observables', () => { log.log(`myAutorun.run(myComputed3: ${myComputed3.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)", - "myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)", - "myComputed3.recompute(myComputed2: 0 + myObservable1: 0 + myObservable2: 0 = 0)", - "myAutorun.run(myComputed3: 0)", + 'myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)', + 'myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)', + 'myComputed3.recompute(myComputed2: 0 + myObservable1: 0 + myObservable2: 0 = 0)', + 'myAutorun.run(myComputed3: 0)', ]); myObservable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 1 + myObservable2: 0 = 1)", - "myComputed2.recompute(myComputed1: 1 + myObservable1: 1 + myObservable2: 0 = 2)", - "myComputed3.recompute(myComputed2: 2 + myObservable1: 1 + myObservable2: 0 = 3)", - "myAutorun.run(myComputed3: 3)", + 'myComputed1.recompute(myObservable1: 1 + myObservable2: 0 = 1)', + 'myComputed2.recompute(myComputed1: 1 + myObservable1: 1 + myObservable2: 0 = 2)', + 'myComputed3.recompute(myComputed2: 2 + myObservable1: 1 + myObservable2: 0 = 3)', + 'myAutorun.run(myComputed3: 3)', ]); transaction((tx) => { myObservable1.set(2, tx); myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 2 + myObservable2: 0 = 2)", - "myComputed2.recompute(myComputed1: 2 + myObservable1: 2 + myObservable2: 0 = 4)", + 'myComputed1.recompute(myObservable1: 2 + myObservable2: 0 = 2)', + 'myComputed2.recompute(myComputed1: 2 + myObservable1: 2 + myObservable2: 0 = 4)', ]); myObservable1.set(3, tx); myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 3 + myObservable2: 0 = 3)", - "myComputed2.recompute(myComputed1: 3 + myObservable1: 3 + myObservable2: 0 = 6)", + 'myComputed1.recompute(myObservable1: 3 + myObservable2: 0 = 3)', + 'myComputed2.recompute(myComputed1: 3 + myObservable1: 3 + myObservable2: 0 = 6)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed3.recompute(myComputed2: 6 + myObservable1: 3 + myObservable2: 0 = 9)", - "myAutorun.run(myComputed3: 9)", + 'myComputed3.recompute(myComputed2: 6 + myObservable1: 3 + myObservable2: 0 = 9)', + 'myAutorun.run(myComputed3: 9)', ]); }); @@ -482,22 +482,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "subscribed handler 0", - "compute value undefined", - "autorun, value: undefined", + 'subscribed handler 0', + 'compute value undefined', + 'autorun, value: undefined', ]); setValue(1); assert.deepStrictEqual(log.getAndClearEntries(), [ - "compute value 1", - "autorun, value: 1" + 'compute value 1', + 'autorun, value: 1' ]); autorunDisposable.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "unsubscribed handler 0" + 'unsubscribed handler 0' ]); }); @@ -593,26 +593,26 @@ suite('observables', () => { log.log(`myAutorun: ${value}`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed.recompute", - "myObs1.firstObserverAdded", - "myObs1.get", - "myAutorun: 0", + 'myComputed.recompute', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myAutorun: 0', ]); transaction(tx => { myObs1.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), (["myObs1.set (value 1)"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myObs1.set (value 1)'])); shouldReadObservable.set(false, tx); assert.deepStrictEqual(log.getAndClearEntries(), ([])); myComputed.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed.recompute", - "myObs1.lastObserverRemoved", + 'myComputed.recompute', + 'myObs1.lastObserverRemoved', ]); }); - assert.deepStrictEqual(log.getAndClearEntries(), (["myAutorun: 1"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myAutorun: 1'])); }); test('avoid recomputation of deriveds that are no longer read', () => { @@ -640,41 +640,41 @@ suite('observables', () => { } })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.firstObserverAdded", - "myObsShouldRead.get", - "myObs1.firstObserverAdded", - "myObs1.get", - "myComputed1(myObs1: 0): Computed 0", - "myAutorun(shouldRead: true, myComputed1: 0): run", + 'myObsShouldRead.firstObserverAdded', + 'myObsShouldRead.get', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myComputed1(myObs1: 0): Computed 0', + 'myAutorun(shouldRead: true, myComputed1: 0): run', ]); transaction(tx => { myObsShouldRead.set(false, tx); myObs1.set(1, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.set (value false)", - "myObs1.set (value 1)", + 'myObsShouldRead.set (value false)', + 'myObs1.set (value 1)', ]); }); // myComputed1 should not be recomputed here, even though its dependency myObs1 changed! assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.get", - "myAutorun(shouldRead: false): run", - "myObs1.lastObserverRemoved", + 'myObsShouldRead.get', + 'myAutorun(shouldRead: false): run', + 'myObs1.lastObserverRemoved', ]); transaction(tx => { myObsShouldRead.set(true, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.set (value true)", + 'myObsShouldRead.set (value true)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.get", - "myObs1.firstObserverAdded", - "myObs1.get", - "myComputed1(myObs1: 1): Computed 1", - "myAutorun(shouldRead: true, myComputed1: 1): run", + 'myObsShouldRead.get', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myComputed1(myObs1: 1): Computed 1', + 'myAutorun(shouldRead: true, myComputed1: 1): run', ]); }); @@ -715,8 +715,8 @@ suite('observables', () => { log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); transaction((tx) => { @@ -727,7 +727,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)" + 'myDerived.read(myObservable: 0)' ]); }); @@ -746,8 +746,8 @@ suite('observables', () => { log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); transaction((tx) => { @@ -756,15 +756,15 @@ suite('observables', () => { myDerived.get(); // This marks the auto-run as changed assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 2)" + 'myDerived.read(myObservable: 2)' ]); myObservable.set(0, tx); assert.deepStrictEqual(log.getAndClearEntries(), []); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); }); }); @@ -780,28 +780,28 @@ suite('observables', () => { /** @description autorun */ if (observable1.read(reader) >= 2) { assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.set (value 2)", - "myObservable1.get", + 'myObservable1.set (value 2)', + 'myObservable1.get', ]); myObservable2.read(reader); // First time this observable is read assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable2.firstObserverAdded", - "myObservable2.get", + 'myObservable2.firstObserverAdded', + 'myObservable2.get', ]); d.dispose(); // Disposing removes all observers assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.lastObserverRemoved", - "myObservable2.lastObserverRemoved", + 'myObservable1.lastObserverRemoved', + 'myObservable2.lastObserverRemoved', ]); myObservable3.read(reader); // This does not subscribe the observable, because the autorun is disposed assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable3.get", + 'myObservable3.get', ]); } }); @@ -882,28 +882,28 @@ suite('observables', () => { log.log(`myAutorun(myComputed: ${value})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myComputed(myObservable: 0): start computing", - "myComputed(myObservable: 0): finished computing", - "myAutorun(myComputed: 0)" + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myComputed(myObservable: 0): start computing', + 'myComputed(myObservable: 0): finished computing', + 'myAutorun(myComputed: 0)' ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "myComputed(myObservable: 1): start computing", - "myObservable.set (value 2)", - "myComputed(myObservable: 1): finished computing", - "myObservable.get", - "myComputed(myObservable: 2): start computing", - "myObservable.set (value 3)", - "myComputed(myObservable: 2): finished computing", - "myObservable.get", - "myComputed(myObservable: 3): start computing", - "myComputed(myObservable: 3): finished computing", - "myAutorun(myComputed: 3)", + 'myObservable.set (value 1)', + 'myObservable.get', + 'myComputed(myObservable: 1): start computing', + 'myObservable.set (value 2)', + 'myComputed(myObservable: 1): finished computing', + 'myObservable.get', + 'myComputed(myObservable: 2): start computing', + 'myObservable.set (value 3)', + 'myComputed(myObservable: 2): finished computing', + 'myObservable.get', + 'myComputed(myObservable: 3): start computing', + 'myComputed(myObservable: 3): finished computing', + 'myAutorun(myComputed: 3)', ]); }); @@ -921,30 +921,30 @@ suite('observables', () => { log.log(`myAutorun(myObservable: ${value}): end`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myAutorun(myObservable: 0): start", - "myAutorun(myObservable: 0): end", + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myAutorun(myObservable: 0): start', + 'myAutorun(myObservable: 0): end', ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "myAutorun(myObservable: 1): start", - "myObservable.set (value 2)", - "myAutorun(myObservable: 1): end", - "myObservable.get", - "myAutorun(myObservable: 2): start", - "myObservable.set (value 3)", - "myAutorun(myObservable: 2): end", - "myObservable.get", - "myAutorun(myObservable: 3): start", - "myObservable.set (value 4)", - "myAutorun(myObservable: 3): end", - "myObservable.get", - "myAutorun(myObservable: 4): start", - "myAutorun(myObservable: 4): end", + 'myObservable.set (value 1)', + 'myObservable.get', + 'myAutorun(myObservable: 1): start', + 'myObservable.set (value 2)', + 'myAutorun(myObservable: 1): end', + 'myObservable.get', + 'myAutorun(myObservable: 2): start', + 'myObservable.set (value 3)', + 'myAutorun(myObservable: 2): end', + 'myObservable.get', + 'myAutorun(myObservable: 3): start', + 'myObservable.set (value 4)', + 'myAutorun(myObservable: 3): end', + 'myObservable.get', + 'myAutorun(myObservable: 4): start', + 'myAutorun(myObservable: 4): end', ]); }); @@ -972,36 +972,36 @@ suite('observables', () => { log.log(`myAutorun(myDerived2: ${value})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myDerived1(myObservable: 0): start computing", - "myDerived2(myDerived1: 0): start computing", - "myAutorun(myDerived2: 0)", + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myDerived1(myObservable: 0): start computing', + 'myDerived2(myDerived1: 0): start computing', + 'myAutorun(myDerived2: 0)', ]); transaction(tx => { myObservable.set(1, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", + 'myObservable.set (value 1)', ]); myDerived2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.get", - "myDerived1(myObservable: 1): start computing", - "myDerived2(myDerived1: 1): start computing", + 'myObservable.get', + 'myDerived1(myObservable: 1): start computing', + 'myDerived2(myDerived1: 1): start computing', ]); myObservable.set(2, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 2)", + 'myObservable.set (value 2)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.get", - "myDerived1(myObservable: 2): start computing", - "myDerived2(myDerived1: 2): start computing", - "myAutorun(myDerived2: 2)", + 'myObservable.get', + 'myDerived1(myObservable: 2): start computing', + 'myDerived2(myDerived1: 2): start computing', + 'myAutorun(myDerived2: 2)', ]); }); @@ -1031,30 +1031,30 @@ suite('observables', () => { log.log(`myAutorun(myDerived3: ${val})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.firstObserverAdded", - "myObservable1.get", - "myObservable2.firstObserverAdded", - "myObservable2.get", - "myDerived2.computed(myObservable2: 0)", - "myDerived3.computed(myDerived1: 0, myDerived2: 0)", - "myAutorun(myDerived3: 0 + 0)", + 'myObservable1.firstObserverAdded', + 'myObservable1.get', + 'myObservable2.firstObserverAdded', + 'myObservable2.get', + 'myDerived2.computed(myObservable2: 0)', + 'myDerived3.computed(myDerived1: 0, myDerived2: 0)', + 'myAutorun(myDerived3: 0 + 0)', ]); transaction(tx => { myObservable1.set(1, tx); // Mark myDerived 3 as stale assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.set (value 1)", + 'myObservable1.set (value 1)', ]); myObservable2.set(10, tx); // This is a non-change. myDerived3 should not be marked as possibly-depedency-changed! assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable2.set (value 10)", + 'myObservable2.set (value 10)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.get", - "myObservable2.get", - "myDerived2.computed(myObservable2: 10)", + 'myObservable1.get', + 'myObservable2.get', + 'myDerived2.computed(myObservable2: 10)', 'myDerived3.computed(myDerived1: 1, myDerived2: 0)', 'myAutorun(myDerived3: 1 + 0)', ]); @@ -1146,11 +1146,11 @@ suite('observables', () => { i++; emitter.fire(2); - assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 2"]); + assert.deepStrictEqual(log.getAndClearEntries(), ['event fired 2']); i++; emitter.fire(3); - assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 3"]); + assert.deepStrictEqual(log.getAndClearEntries(), ['event fired 3']); d.dispose(); }); @@ -1347,17 +1347,17 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "error: foobar" + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'error: foobar' ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 1)', + 'myObservable.get', + 'error: foobar', ]); d.dispose(); @@ -1380,24 +1380,24 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", + 'myObservable.firstObserverAdded', + 'myObservable.get', ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 1)', + 'myObservable.get', + 'error: foobar', ]); myObservable.set(2, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 2)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 2)', + 'myObservable.get', + 'error: foobar', ]); d.dispose(); @@ -1436,15 +1436,15 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.computed start", - "myObservable.firstObserverAdded", - "myObservable.get", - "myObservable.set (value 1)", - "myDerived.computed end", - "myDerived.computed start", - "myObservable.get", - "myDerived.computed end", - "recomputeInitiallyAndOnChange, myDerived: 1", + 'myDerived.computed start', + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myObservable.set (value 1)', + 'myDerived.computed end', + 'myDerived.computed start', + 'myObservable.get', + 'myDerived.computed end', + 'recomputeInitiallyAndOnChange, myDerived: 1', ]); myDerived.get(); @@ -1521,12 +1521,12 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "autorun start", - "d1.computed start", - "d2.computed start", - "Error: Cyclic deriveds are not supported yet!", - "d1.computed end", - "autorun end" + 'autorun start', + 'd1.computed start', + 'd2.computed start', + 'Error: Cyclic deriveds are not supported yet!', + 'd1.computed end', + 'autorun end' ])); disp.dispose(); @@ -1573,9 +1573,9 @@ suite('observables', () => { })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "createInitial", + 'createInitial', 'update {"changes":[],"myObservable1":5,"myObservable2":9}', - "update -> 14", + 'update -> 14', 'autorun {"changes":[],"sum":14}', ]); @@ -1585,9 +1585,9 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "update {\"changes\":[{\"key\":\"myObservable1\",\"change\":1},{\"key\":\"myObservable2\",\"change\":3}],\"myObservable1\":6,\"myObservable2\":12}", - "update -> 18", - "autorun {\"changes\":[{\"key\":\"sum\",\"change\":4}],\"sum\":18}" + 'update {"changes":[{"key":"myObservable1","change":1},{"key":"myObservable2","change":3}],"myObservable1":6,"myObservable2":12}', + 'update -> 18', + 'autorun {"changes":[{"key":"sum","change":4}],"sum":18}' ])); transaction(tx => { @@ -1598,18 +1598,18 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "update {\"changes\":[{\"key\":\"myObservable1\",\"change\":1}],\"myObservable1\":7,\"myObservable2\":12}", - "update -> 19", - "sum.get() 19", - "update {\"changes\":[{\"key\":\"myObservable2\",\"change\":3}],\"myObservable1\":7,\"myObservable2\":15}", - "update -> 22", - "autorun {\"changes\":[{\"key\":\"sum\",\"change\":1}],\"sum\":22}" + 'update {"changes":[{"key":"myObservable1","change":1}],"myObservable1":7,"myObservable2":12}', + 'update -> 19', + 'sum.get() 19', + 'update {"changes":[{"key":"myObservable2","change":3}],"myObservable1":7,"myObservable2":15}', + 'update -> 22', + 'autorun {"changes":[{"key":"sum","change":1}],"sum":22}' ])); store.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "disposeFinal 22" + 'disposeFinal 22' ])); }); }); @@ -1633,22 +1633,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 0", - "a: 0" + 'computed 0', + 'a: 0' ])); observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 0 disposed", - "computed 1", - "a: 1" + 'computed1: 0 disposed', + 'computed 1', + 'a: 1' ])); a.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 1 disposed" + 'computed1: 1 disposed' ])); }); @@ -1670,22 +1670,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 0", - "a: 0" + 'computed 0', + 'a: 0' ])); observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 1", - "computed1: 0 disposed", - "a: 1" + 'computed 1', + 'computed1: 0 disposed', + 'a: 1' ])); a.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 1 disposed" + 'computed1: 1 disposed' ])); }); }); @@ -1724,7 +1724,7 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "runOnChange [\"signal1: foo, signal2Derived: bar (derived)\"]" + 'runOnChange ["signal1: foo, signal2Derived: bar (derived)"]' ])); @@ -1733,7 +1733,7 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "runOnChange [\"signal2Derived: baz (derived)\"]" + 'runOnChange ["signal2Derived: baz (derived)"]' ])); disp.dispose(); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 323789a764e..0e64d16cb80 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -472,8 +472,8 @@ suite('URI', () => { }), true); assert.strictEqual(URI.isUri(1), false); - assert.strictEqual(URI.isUri("1"), false); - assert.strictEqual(URI.isUri("http://sample.com"), false); + assert.strictEqual(URI.isUri('1'), false); + assert.strictEqual(URI.isUri('http://sample.com'), false); assert.strictEqual(URI.isUri(null), false); assert.strictEqual(URI.isUri(undefined), false); }); @@ -487,7 +487,7 @@ suite('URI', () => { assert.strictEqual(isUriComponents(1), false); assert.strictEqual(isUriComponents(true), false); - assert.strictEqual(isUriComponents("true"), false); + assert.strictEqual(isUriComponents('true'), false); assert.strictEqual(isUriComponents({}), false); assert.strictEqual(isUriComponents({ scheme: '' }), true); // valid components but INVALID uri assert.strictEqual(isUriComponents({ scheme: 'fo' }), true); diff --git a/src/vs/base/test/common/yaml.test.ts b/src/vs/base/test/common/yaml.test.ts index 2a778e22e6b..e91c611c03e 100644 --- a/src/vs/base/test/common/yaml.test.ts +++ b/src/vs/base/test/common/yaml.test.ts @@ -759,7 +759,7 @@ suite('YAML Parser', () => { }, [ { - message: "Duplicate key 'key'", + message: `Duplicate key 'key'`, code: 'duplicateKey', start: pos(1, 0), end: pos(1, 3) @@ -874,7 +874,7 @@ suite('YAML Parser', () => { test('empty object with only colons', () => { // Test object with empty values assertValidParse( - ["key1:", "key2:", "key3:"], + ['key1:', 'key2:', 'key3:'], { type: 'object', start: pos(0, 0), end: pos(2, 5), properties: [ { @@ -943,11 +943,11 @@ suite('YAML Parser', () => { // Test malformed arrays that might cause position advancement issues assertValidParse( [ - "key: [", - "", - "", - "", - "" + 'key: [', + '', + '', + '', + '' ], { type: 'object', start: pos(0, 0), end: pos(5, 0), properties: [ @@ -965,11 +965,11 @@ suite('YAML Parser', () => { // Test structures that might appear self-referential assertValidParse( [ - "a:", - " b:", - " a:", - " b:", - " value: test" + 'a:', + ' b:', + ' a:', + ' b:', + ' value: test' ], { type: 'object', start: pos(0, 0), end: pos(4, 19), properties: [ @@ -1014,7 +1014,7 @@ suite('YAML Parser', () => { test('array with empty lines', () => { // Test arrays spanning multiple lines with empty lines assertValidParse( - ["arr: [", "", "item1,", "", "item2", "", "]"], + ['arr: [', '', 'item1,', '', 'item2', '', ']'], { type: 'object', start: pos(0, 0), end: pos(6, 1), properties: [ { diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index b4019cbea94..0dcfd898c47 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -353,7 +353,7 @@ suite('Indent With Tab - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(1, 1, 3, 5)); editor.executeCommands('editor.action.indentLines', TypeOperations.indent(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); assert.strictEqual(model.getValue(), [ @@ -377,7 +377,7 @@ suite('Indent With Tab - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(1, 1, 5, 2)); editor.executeCommands('editor.action.indentLines', TypeOperations.indent(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); assert.strictEqual(model.getValue(), [ @@ -419,7 +419,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { test('issue #119225: Do not add extra leading space when pasting JSDoc', () => { - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { @@ -466,7 +466,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { test('issue #167299: Blank line removes indent', () => { - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { @@ -610,7 +610,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { // https://github.com/microsoft/vscode/issues/181065 - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { @@ -850,12 +850,12 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { // https://github.com/microsoft/vscode/issues/208215 - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { viewModel.type('const add1 = (n) =>'); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const add1 = (n) =>', ' ', @@ -874,9 +874,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 9, 3, 9)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3, 4, 5];', 'array.map(', @@ -890,15 +890,15 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { // https://github.com/microsoft/vscode/issues/116843 - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { viewModel.type([ 'const add1 = (n) =>', ' n + 1;', ].join('\n')); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const add1 = (n) =>', ' n + 1;', @@ -919,7 +919,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 1, 3, 1)); viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1003,9 +1003,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 12, 2, 12)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3, 4, 5];', 'array.map(_', @@ -1026,9 +1026,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 19, 2, 19)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'function f() {', ' if (condition)', @@ -1036,8 +1036,8 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { '}', ].join('\n')); - viewModel.type("return;"); - viewModel.type("\n", 'keyboard'); + viewModel.type('return;'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'function f() {', ' if (condition)', @@ -1059,7 +1059,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [{ startIndex: 0, standardTokenType: StandardTokenType.Comment }], [{ startIndex: 0, standardTokenType: StandardTokenType.Comment }], @@ -1067,7 +1067,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ]; disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); editor.setSelection(new Selection(2, 23, 2, 23)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ '/**', 'indentation done for {', @@ -1086,20 +1086,20 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 19, 1, 19)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (/*condition*/)', ' ' ].join('\n')); - viewModel.type("{", 'keyboard'); + viewModel.type('{', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (/*condition*/)', '{}' ].join('\n')); editor.setSelection(new Selection(2, 2, 2, 2)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (/*condition*/)', '{', @@ -1124,9 +1124,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 14, 1, 14)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array =', ' ' @@ -1147,9 +1147,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 7, 2, 7)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3];', 'array.', @@ -1170,10 +1170,10 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 7, 2, 7)); - viewModel.type("\n", 'keyboard'); - viewModel.type("."); + viewModel.type('\n', 'keyboard'); + viewModel.type('.'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3]', ' .' @@ -1194,9 +1194,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 24, 2, 24)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3]', ' .filter(() => true)', @@ -1219,9 +1219,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 5, 3, 5)); - viewModel.type("."); + viewModel.type('.'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3]', ' .filter(() => true)', @@ -1242,9 +1242,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 25, 2, 25)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'const array = [1, 2, 3]', ' .filter(() => true);', @@ -1261,16 +1261,16 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { const model = createTextModel('function foo() {}', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 17, 1, 17)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'function foo() {', ' ', '}', ].join('\n')); editor.setSelection(new Selection(2, 5, 2, 5)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'function foo() {', ' ', @@ -1292,9 +1292,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 14, 3, 14)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ '{', ' for(;;)', @@ -1318,9 +1318,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(4, 1, 4, 1)); - viewModel.type("}", 'keyboard'); + viewModel.type('}', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (true) {', ' console.log("a")', @@ -1340,15 +1340,15 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 5, 2, 5)); - viewModel.type("{}", 'keyboard'); + viewModel.type('{}', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (true)', '{}', ].join('\n')); editor.setSelection(new Selection(2, 2, 2, 2)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (true)', '{', @@ -1368,9 +1368,9 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "keep", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'keep', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 5, 2, 5)); - viewModel.type("}", 'keyboard'); + viewModel.type('}', 'keyboard'); assert.strictEqual(model.getValue(), [ 'foo {', '}', @@ -1409,21 +1409,21 @@ suite('Auto Indent On Type - Ruby', () => { // https://github.com/microsoft/vscode/issues/198350 - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { - viewModel.type("def foo\n i"); - viewModel.type("n", 'keyboard'); - assert.strictEqual(model.getValue(), "def foo\n in"); - viewModel.type(" ", 'keyboard'); - assert.strictEqual(model.getValue(), "def foo\nin "); - - viewModel.model.setValue(""); - viewModel.type(" # in"); - assert.strictEqual(model.getValue(), " # in"); - viewModel.type(" ", 'keyboard'); - assert.strictEqual(model.getValue(), " # in "); + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { + viewModel.type('def foo\n i'); + viewModel.type('n', 'keyboard'); + assert.strictEqual(model.getValue(), 'def foo\n in'); + viewModel.type(' ', 'keyboard'); + assert.strictEqual(model.getValue(), 'def foo\nin '); + + viewModel.model.setValue(''); + viewModel.type(' # in'); + assert.strictEqual(model.getValue(), ' # in'); + viewModel.type(' ', 'keyboard'); + assert.strictEqual(model.getValue(), ' # in '); }); }); @@ -1434,15 +1434,15 @@ suite('Auto Indent On Type - Ruby', () => { // https://github.com/microsoft/vscode/issues/199846 // explanation: happening because the # is detected probably as a comment - const model = createTextModel("", languageId, {}); + const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { - viewModel.type("method('#foo') do"); - viewModel.type("\n", 'keyboard'); + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { + viewModel.type(`method('#foo') do`); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ - "method('#foo') do", - " " + `method('#foo') do`, + ' ' ].join('\n')); }); }); @@ -1478,10 +1478,10 @@ suite('Auto Indent On Type - PHP', () => { // https://github.com/microsoft/vscode/issues/199050 - const model = createTextModel("preg_replace('{');", languageId, {}); + const model = createTextModel(`preg_replace('{');`, languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -1491,10 +1491,10 @@ suite('Auto Indent On Type - PHP', () => { ]; disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); editor.setSelection(new Selection(1, 54, 1, 54)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ - "preg_replace('{');", - "" + `preg_replace('{');`, + '' ].join('\n')); }); }); @@ -1542,7 +1542,7 @@ suite('Auto Indent On Paste - Go', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 1, 3, 1)); const text = ' '; const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); @@ -1598,9 +1598,9 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 20, 2, 20)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'int WINAPI WinMain(bool instance,', ' int nshowcmd) {', @@ -1620,9 +1620,9 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 20, 1, 20)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (true) { // jaja', ' ', @@ -1641,9 +1641,9 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "none", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'none', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 3, 2, 3)); - viewModel.type("}", 'keyboard'); + viewModel.type('}', 'keyboard'); assert.strictEqual(model.getValue(), [ 'int func() {', ' }', @@ -1694,9 +1694,9 @@ suite('Auto Indent On Type - HTML', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 48, 2, 48)); - viewModel.type("\n", 'keyboard'); + viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ '
    ',
     				'  foo //I press  at the end of this line',
    @@ -1748,9 +1748,9 @@ suite('Auto Indent On Type - Visual Basic', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			editor.setSelection(new Selection(3, 10, 3, 10));
    -			viewModel.type("f", 'keyboard');
    +			viewModel.type('f', 'keyboard');
     			assert.strictEqual(model.getValue(), [
     				'if True then',
     				'    Some code',
    @@ -1801,9 +1801,9 @@ suite('Auto Indent On Type - Latex', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => {
     			editor.setSelection(new Selection(2, 9, 2, 9));
    -			viewModel.type("{", 'keyboard');
    +			viewModel.type('{', 'keyboard');
     			assert.strictEqual(model.getValue(), [
     				'\\begin{theorem}',
     				'\\end{}',
    @@ -1851,9 +1851,9 @@ suite('Auto Indent On Type - Lua', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel) => {
     			editor.setSelection(new Selection(1, 28, 1, 28));
    -			viewModel.type("\n", 'keyboard');
    +			viewModel.type('\n', 'keyboard');
     			assert.strictEqual(model.getValue(), [
     				'print("asdf function asdf")',
     				''
    diff --git a/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts
    index 20a08cd14b7..58685f2e186 100644
    --- a/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts
    +++ b/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts
    @@ -50,7 +50,7 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [[
     				{ startIndex: 0, standardTokenType: StandardTokenType.Other },
     				{ startIndex: 16, standardTokenType: StandardTokenType.String },
    @@ -74,7 +74,7 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [
     				[
     					{ startIndex: 0, standardTokenType: StandardTokenType.Other },
    @@ -104,7 +104,7 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [
     				[
     					{ startIndex: 0, standardTokenType: StandardTokenType.Other },
    @@ -163,7 +163,7 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [
     				[
     					{ startIndex: 0, standardTokenType: StandardTokenType.Other }
    @@ -199,7 +199,7 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [
     				[{ startIndex: 0, standardTokenType: StandardTokenType.Other }],
     				[{ startIndex: 0, standardTokenType: StandardTokenType.String }],
    @@ -227,7 +227,7 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => {
     		].join('\n'), languageId, {});
     		disposables.add(model);
     
    -		withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => {
    +		withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => {
     			const tokens: StandardTokenTypeData[][] = [
     				[
     					{ startIndex: 0, standardTokenType: StandardTokenType.Other }
    diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts
    index 71d2278d792..9dae4e1e03a 100644
    --- a/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts
    +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts
    @@ -86,8 +86,8 @@ suite('computeGhostText', () => {
     	});
     
     	test('Multi Part Diffing 2', () => {
    -		assert.deepStrictEqual(getOutput('[)]', '())'), ({ prefix: undefined, subword: "[(])[)]" }));
    -		assert.deepStrictEqual(getOutput('[))]', '(())'), ({ prefix: undefined, subword: "[((]))" }));
    +		assert.deepStrictEqual(getOutput('[)]', '())'), ({ prefix: undefined, subword: '[(])[)]' }));
    +		assert.deepStrictEqual(getOutput('[))]', '(())'), ({ prefix: undefined, subword: '[((]))' }));
     	});
     
     	test('Parenthesis Matching', () => {
    diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/getSecondaryEdits.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/getSecondaryEdits.test.ts
    index 291c17bf0ae..3065243e875 100644
    --- a/src/vs/editor/contrib/inlineCompletions/test/browser/getSecondaryEdits.test.ts
    +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/getSecondaryEdits.test.ts
    @@ -52,7 +52,7 @@ suite('getSecondaryEdits', () => {
     			'}'
     		].join('\n'));
     		const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit);
    -		assert.deepStrictEqual(TextEdit.fromParallelReplacementsUnsorted(secondaryEdits.filter(isDefined)).toString(textModel.getValue()), "...ction fib(❰\n↦) {\n\t... 0;\n}❱");
    +		assert.deepStrictEqual(TextEdit.fromParallelReplacementsUnsorted(secondaryEdits.filter(isDefined)).toString(textModel.getValue()), '...ction fib(❰\n↦) {\n\t... 0;\n}❱');
     		textModel.dispose();
     	});
     
    diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/graph.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/graph.test.ts
    index bb0a79bd733..97dde9254ac 100644
    --- a/src/vs/editor/contrib/inlineCompletions/test/browser/graph.test.ts
    +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/graph.test.ts
    @@ -3,32 +3,32 @@
      *  Licensed under the MIT License. See License.txt in the project root for license information.
      *--------------------------------------------------------------------------------------------*/
     
    -import assert from "assert";
    -import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../base/test/common/utils.js";
    -import { DirectedGraph } from "../../browser/model/graph.js";
    +import assert from 'assert';
    +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
    +import { DirectedGraph } from '../../browser/model/graph.js';
     
    -suite("DirectedGraph", () => {
    +suite('DirectedGraph', () => {
     	ensureNoDisposablesAreLeakedInTestSuite();
     
    -	test("from - creates empty graph", () => {
    +	test('from - creates empty graph', () => {
     		const graph = DirectedGraph.from([], () => []);
    -		assert.deepStrictEqual(graph.getOutgoing("a"), []);
    +		assert.deepStrictEqual(graph.getOutgoing('a'), []);
     	});
     
    -	test("from - creates graph with single node", () => {
    -		const graph = DirectedGraph.from(["a"], () => []);
    -		assert.deepStrictEqual(graph.getOutgoing("a"), []);
    +	test('from - creates graph with single node', () => {
    +		const graph = DirectedGraph.from(['a'], () => []);
    +		assert.deepStrictEqual(graph.getOutgoing('a'), []);
     	});
     
    -	test("from - creates graph with nodes and edges", () => {
    -		const nodes = ["a", "b", "c"];
    +	test('from - creates graph with nodes and edges', () => {
    +		const nodes = ['a', 'b', 'c'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b", "c"];
    -				case "b":
    -					return ["c"];
    -				case "c":
    +				case 'a':
    +					return ['b', 'c'];
    +				case 'b':
    +					return ['c'];
    +				case 'c':
     					return [];
     				default:
     					return [];
    @@ -37,18 +37,18 @@ suite("DirectedGraph", () => {
     
     		const graph = DirectedGraph.from(nodes, getOutgoing);
     
    -		assert.deepStrictEqual([...graph.getOutgoing("a")].sort(), ["b", "c"]);
    -		assert.deepStrictEqual(graph.getOutgoing("b"), ["c"]);
    -		assert.deepStrictEqual(graph.getOutgoing("c"), []);
    +		assert.deepStrictEqual([...graph.getOutgoing('a')].sort(), ['b', 'c']);
    +		assert.deepStrictEqual(graph.getOutgoing('b'), ['c']);
    +		assert.deepStrictEqual(graph.getOutgoing('c'), []);
     	});
     
    -	test("from - handles duplicate edges", () => {
    -		const nodes = ["a", "b"];
    +	test('from - handles duplicate edges', () => {
    +		const nodes = ['a', 'b'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b", "b"]; // Duplicate edge
    -				case "b":
    +				case 'a':
    +					return ['b', 'b']; // Duplicate edge
    +				case 'b':
     					return [];
     				default:
     					return [];
    @@ -57,19 +57,19 @@ suite("DirectedGraph", () => {
     
     		const graph = DirectedGraph.from(nodes, getOutgoing);
     
    -		assert.deepStrictEqual(graph.getOutgoing("a"), ["b"]);
    -		assert.deepStrictEqual(graph.getOutgoing("b"), []);
    +		assert.deepStrictEqual(graph.getOutgoing('a'), ['b']);
    +		assert.deepStrictEqual(graph.getOutgoing('b'), []);
     	});
     
    -	test("removeCycles - no cycles", () => {
    -		const nodes = ["a", "b", "c"];
    +	test('removeCycles - no cycles', () => {
    +		const nodes = ['a', 'b', 'c'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b"];
    -				case "b":
    -					return ["c"];
    -				case "c":
    +				case 'a':
    +					return ['b'];
    +				case 'b':
    +					return ['c'];
    +				case 'c':
     					return [];
     				default:
     					return [];
    @@ -80,19 +80,19 @@ suite("DirectedGraph", () => {
     		const result = graph.removeCycles();
     
     		assert.deepStrictEqual(result.foundCycles, []);
    -		assert.deepStrictEqual(graph.getOutgoing("a"), ["b"]);
    -		assert.deepStrictEqual(graph.getOutgoing("b"), ["c"]);
    -		assert.deepStrictEqual(graph.getOutgoing("c"), []);
    +		assert.deepStrictEqual(graph.getOutgoing('a'), ['b']);
    +		assert.deepStrictEqual(graph.getOutgoing('b'), ['c']);
    +		assert.deepStrictEqual(graph.getOutgoing('c'), []);
     	});
     
    -	test("removeCycles - simple cycle", () => {
    -		const nodes = ["a", "b"];
    +	test('removeCycles - simple cycle', () => {
    +		const nodes = ['a', 'b'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b"];
    -				case "b":
    -					return ["a"]; // Creates cycle
    +				case 'a':
    +					return ['b'];
    +				case 'b':
    +					return ['a']; // Creates cycle
     				default:
     					return [];
     			}
    @@ -103,24 +103,24 @@ suite("DirectedGraph", () => {
     
     		assert.strictEqual(result.foundCycles.length, 1);
     		assert.ok(
    -			result.foundCycles.includes("a") || result.foundCycles.includes("b")
    +			result.foundCycles.includes('a') || result.foundCycles.includes('b')
     		);
     
     		// After removing cycles, one of the edges should be removed
    -		const aOutgoing = graph.getOutgoing("a");
    -		const bOutgoing = graph.getOutgoing("b");
    +		const aOutgoing = graph.getOutgoing('a');
    +		const bOutgoing = graph.getOutgoing('b');
     		assert.ok(
     			(aOutgoing.length === 0 && bOutgoing.length === 1) ||
     			(aOutgoing.length === 1 && bOutgoing.length === 0)
     		);
     	});
     
    -	test("removeCycles - self loop", () => {
    -		const nodes = ["a"];
    +	test('removeCycles - self loop', () => {
    +		const nodes = ['a'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["a"]; // Self loop
    +				case 'a':
    +					return ['a']; // Self loop
     				default:
     					return [];
     			}
    @@ -129,21 +129,21 @@ suite("DirectedGraph", () => {
     		const graph = DirectedGraph.from(nodes, getOutgoing);
     		const result = graph.removeCycles();
     
    -		assert.deepStrictEqual(result.foundCycles, ["a"]);
    -		assert.deepStrictEqual(graph.getOutgoing("a"), []);
    +		assert.deepStrictEqual(result.foundCycles, ['a']);
    +		assert.deepStrictEqual(graph.getOutgoing('a'), []);
     	});
     
    -	test("removeCycles - complex cycle", () => {
    -		const nodes = ["a", "b", "c", "d"];
    +	test('removeCycles - complex cycle', () => {
    +		const nodes = ['a', 'b', 'c', 'd'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b"];
    -				case "b":
    -					return ["c"];
    -				case "c":
    -					return ["d", "a"]; // Creates cycle back to 'a'
    -				case "d":
    +				case 'a':
    +					return ['b'];
    +				case 'b':
    +					return ['c'];
    +				case 'c':
    +					return ['d', 'a']; // Creates cycle back to 'a'
    +				case 'd':
     					return [];
     				default:
     					return [];
    @@ -156,22 +156,22 @@ suite("DirectedGraph", () => {
     		assert.ok(result.foundCycles.length >= 1);
     
     		// After removing cycles, there should be no path back to 'a' from 'c'
    -		const cOutgoing = graph.getOutgoing("c");
    -		assert.ok(!cOutgoing.includes("a"));
    +		const cOutgoing = graph.getOutgoing('c');
    +		assert.ok(!cOutgoing.includes('a'));
     	});
     
    -	test("removeCycles - multiple disconnected cycles", () => {
    -		const nodes = ["a", "b", "c", "d"];
    +	test('removeCycles - multiple disconnected cycles', () => {
    +		const nodes = ['a', 'b', 'c', 'd'];
     		const getOutgoing = (node: string) => {
     			switch (node) {
    -				case "a":
    -					return ["b"];
    -				case "b":
    -					return ["a"]; // Cycle 1: a <-> b
    -				case "c":
    -					return ["d"];
    -				case "d":
    -					return ["c"]; // Cycle 2: c <-> d
    +				case 'a':
    +					return ['b'];
    +				case 'b':
    +					return ['a']; // Cycle 1: a <-> b
    +				case 'c':
    +					return ['d'];
    +				case 'd':
    +					return ['c']; // Cycle 2: c <-> d
     				default:
     					return [];
     			}
    @@ -183,10 +183,10 @@ suite("DirectedGraph", () => {
     		assert.ok(result.foundCycles.length >= 2);
     
     		// After removing cycles, each pair should have only one direction
    -		const aOutgoing = graph.getOutgoing("a");
    -		const bOutgoing = graph.getOutgoing("b");
    -		const cOutgoing = graph.getOutgoing("c");
    -		const dOutgoing = graph.getOutgoing("d");
    +		const aOutgoing = graph.getOutgoing('a');
    +		const bOutgoing = graph.getOutgoing('b');
    +		const cOutgoing = graph.getOutgoing('c');
    +		const dOutgoing = graph.getOutgoing('d');
     
     		assert.ok(
     			(aOutgoing.length === 0 && bOutgoing.length === 1) ||
    @@ -198,12 +198,12 @@ suite("DirectedGraph", () => {
     		);
     	});
     
    -	test("getOutgoing - non-existent node", () => {
    -		const graph = DirectedGraph.from(["a"], () => []);
    -		assert.deepStrictEqual(graph.getOutgoing("b"), []);
    +	test('getOutgoing - non-existent node', () => {
    +		const graph = DirectedGraph.from(['a'], () => []);
    +		assert.deepStrictEqual(graph.getOutgoing('b'), []);
     	});
     
    -	test("with number nodes", () => {
    +	test('with number nodes', () => {
     		const nodes = [1, 2, 3];
     		const getOutgoing = (node: number) => {
     			switch (node) {
    diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletions.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletions.test.ts
    index 2bd599875cb..0f26af1bbb8 100644
    --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletions.test.ts
    +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletions.test.ts
    @@ -286,7 +286,7 @@ suite('Inline Completions', () => {
     					assert.deepStrictEqual(context.getAndClearViewStates(), ['', 'foo[bar]']);
     
     					context.keyboardType('b');
    -					assert.deepStrictEqual(context.getAndClearViewStates(), (["foob[ar]"]));
    +					assert.deepStrictEqual(context.getAndClearViewStates(), (['foob[ar]']));
     					await timeout(1000);
     					assert.deepStrictEqual(provider.getAndClearCallHistory(), [
     						{ position: '(1,5)', text: 'foob', triggerKind: 0, }
    @@ -294,7 +294,7 @@ suite('Inline Completions', () => {
     					assert.deepStrictEqual(context.getAndClearViewStates(), []);
     
     					context.keyboardType('a');
    -					assert.deepStrictEqual(context.getAndClearViewStates(), (["fooba[r]"]));
    +					assert.deepStrictEqual(context.getAndClearViewStates(), (['fooba[r]']));
     					await timeout(1000);
     					assert.deepStrictEqual(provider.getAndClearCallHistory(), [
     						{ position: '(1,6)', text: 'fooba', triggerKind: 0, }
    @@ -310,8 +310,8 @@ suite('Inline Completions', () => {
     			context.keyboardType('f');
     			model.triggerExplicitly();
     			await timeout(10000);
    -			assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(1,2)", triggerKind: 1, text: "f" }]));
    -			assert.deepStrictEqual(context.getAndClearViewStates(), (["f[oo bar]"]));
    +			assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(1,2)', triggerKind: 1, text: 'f' }]));
    +			assert.deepStrictEqual(context.getAndClearViewStates(), (['f[oo bar]']));
     
     			provider.setReturnValue({ insertText: 'foo baz' });
     			await timeout(10000);
    @@ -349,10 +349,10 @@ suite('Inline Completions', () => {
     					await setupScenario(ctx, provider);
     
     					await ctx.model.acceptNextWord();
    -					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (["foo[ bar]"]));
    +					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (['foo[ bar]']));
     
     					await timeout(10000);
    -					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(1,4)", triggerKind: 0, text: "foo" }]));
    +					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(1,4)', triggerKind: 0, text: 'foo' }]));
     					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), ([]));
     
     					await ctx.model.triggerExplicitly(); // reset to provider truth
    @@ -372,21 +372,21 @@ suite('Inline Completions', () => {
     					await setupScenario(ctx, provider);
     
     					await ctx.model.acceptNextWord();
    -					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (["foo[ bar]"]));
    +					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (['foo[ bar]']));
     
     					await timeout(10000);
     					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), ([]));
    -					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(1,4)", triggerKind: 0, text: "foo" }]));
    +					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(1,4)', triggerKind: 0, text: 'foo' }]));
     
     					await ctx.editor.getModel().undo();
     					await timeout(10000);
    -					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (["f[oo bar]"]));
    -					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(1,2)", triggerKind: 0, text: "f" }]));
    +					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (['f[oo bar]']));
    +					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(1,2)', triggerKind: 0, text: 'f' }]));
     
     					await ctx.editor.getModel().redo();
     					await timeout(10000);
    -					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (["foo[ bar]"]));
    -					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(1,4)", triggerKind: 0, text: "foo" }]));
    +					assert.deepStrictEqual(ctx.context.getAndClearViewStates(), (['foo[ bar]']));
    +					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(1,4)', triggerKind: 0, text: 'foo' }]));
     				}
     			);
     		});
    @@ -433,15 +433,15 @@ suite('Inline Completions', () => {
     
     					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([
     						{
    -							position: "(1,4)",
    +							position: '(1,4)',
     							triggerKind: 0,
    -							text: "foo"
    +							text: 'foo'
     						}
     					]));
     					assert.deepStrictEqual(context.getAndClearViewStates(),
     						([
    -							"",
    -							"foo[bar]"
    +							'',
    +							'foo[bar]'
     						])
     					);
     
    @@ -452,9 +452,9 @@ suite('Inline Completions', () => {
     
     					assert.deepStrictEqual(provider.getAndClearCallHistory(), ([
     						{
    -							position: "(1,4)",
    +							position: '(1,4)',
     							triggerKind: 1,
    -							text: "foo"
    +							text: 'foo'
     						}
     					]));
     					assert.deepStrictEqual(context.getAndClearViewStates(),
    @@ -556,7 +556,7 @@ suite('Inline Completions', () => {
     
     				model.accept(editor);
     
    -				assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: "(2,4)", triggerKind: 1, text: "buzz\nbaz" }]));
    +				assert.deepStrictEqual(provider.getAndClearCallHistory(), ([{ position: '(2,4)', triggerKind: 1, text: 'buzz\nbaz' }]));
     
     				assert.deepStrictEqual(context.getAndClearViewStates(), [
     					'',
    diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineEdits.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineEdits.test.ts
    index ea8896ee649..ba30b1c11db 100644
    --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineEdits.test.ts
    +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineEdits.test.ts
    @@ -45,7 +45,7 @@ class Point {
     			await timeout(10000);
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
     				undefined,
    -				"\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n"
    +				'\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n'
     			]));
     
     			model.accept();
    @@ -73,19 +73,19 @@ class Point {
     			await timeout(10000);
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
     				undefined,
    -				"\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n"
    +				'\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n'
     			]));
     
     			editor.setPosition(val.getMarkerPosition(1));
     			editorViewModel.type(' + t');
     
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
    -				"\n\tget❰Length2↦Length3❱D(): numbe...\n...this.y + t❰his.z...his.z❱);\n"
    +				'\n\tget❰Length2↦Length3❱D(): numbe...\n...this.y + t❰his.z...his.z❱);\n'
     			]));
     
     			editorViewModel.type('his.z * this.z');
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
    -				"\n\tget❰Length2↦Length3❱D(): numbe..."
    +				'\n\tget❰Length2↦Length3❱D(): numbe...'
     			]));
     		});
     	});
    @@ -101,14 +101,14 @@ class Point {
     			await timeout(10000);
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
     				undefined,
    -				"\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n"
    +				'\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n'
     			]));
     
     			editor.setPosition(val.getMarkerPosition(0));
     			editorViewModel.type('/* */');
     
     			assert.deepStrictEqual(view.getAndClearViewStates(), ([
    -				"\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n"
    +				'\n\tget❰Length2↦Length3❱D(): numbe...\n...y * this.y❰ + th...his.z❱);\n'
     			]));
     
     			await timeout(10000);
    diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
    index 9a34510127b..a5bc68f3b2c 100644
    --- a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
    +++ b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
    @@ -565,7 +565,7 @@ suite('SnippetController2', function () {
     				{ range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar";\n' }
     			]);
     
    -			assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
    +			assert.strictEqual(model.getValue(), 'const new_const = "bar";\nfoo(new_const)');
     			assertContextKeys(contextKeys, true, false, true);
     			assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
     
    @@ -584,7 +584,7 @@ suite('SnippetController2', function () {
     				{ range: new Range(1, 1, 1, 1), template: 'const ${1:new_const}$0 = "bar";\n' }
     			]);
     
    -			assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
    +			assert.strictEqual(model.getValue(), 'const new_const = "bar";\nfoo(new_const)');
     			assertContextKeys(contextKeys, true, false, true);
     			assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
     
    @@ -604,7 +604,7 @@ suite('SnippetController2', function () {
     				{ range: new Range(1, 1, 1, 1), template: '### ${2:Header}\n' }
     			]);
     
    -			assert.strictEqual(model.getValue(), "### Header\nfoo\nbar");
    +			assert.strictEqual(model.getValue(), '### Header\nfoo\nbar');
     			assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
     			assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 11)]);
     
    @@ -695,7 +695,7 @@ suite('SnippetController2', function () {
     			}];
     			ctrl.apply(edits);
     
    -			assert.strictEqual(model.getValue(), "fooAbazzBone\nfooCbazzDtwo");
    +			assert.strictEqual(model.getValue(), 'fooAbazzBone\nfooCbazzDtwo');
     			assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
     			assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5), new Selection(1, 10, 1, 10), new Selection(2, 5, 2, 5), new Selection(2, 10, 2, 10)]);
     		});
    diff --git a/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts
    index bf9d0a504a4..6afb05eaf63 100644
    --- a/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts
    +++ b/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts
    @@ -228,7 +228,7 @@ suite('WordPartOperations', () => {
     			ed => cursorWordPartLeft(ed),
     			ed => ed.getPosition()!,
     			ed => ed.getPosition()!.equals(new Position(1, 1)),
    -			{ wordSeparators: "!\"#&'()*+,./:;<=>?@[\\]^`{|}·" } // default characters sans '$-%~' plus '·'
    +			{ wordSeparators: '!"#&\'()*+,./:;<=>?@[\\]^`{|}·' } // default characters sans '$-%~' plus '·'
     		);
     		const actual = serializePipePositions(text, actualStops);
     		assert.deepStrictEqual(actual, EXPECTED);
    @@ -245,7 +245,7 @@ suite('WordPartOperations', () => {
     			ed => cursorWordPartRight(ed),
     			ed => ed.getPosition()!,
     			ed => ed.getPosition()!.equals(new Position(1, 60)),
    -			{ wordSeparators: "!\"#&'()*+,./:;<=>?@[\\]^`{|}·" } // default characters sans '$-%~' plus '·'
    +			{ wordSeparators: '!"#&\'()*+,./:;<=>?@[\\]^`{|}·' } // default characters sans '$-%~' plus '·'
     		);
     		const actual = serializePipePositions(text, actualStops);
     		assert.deepStrictEqual(actual, EXPECTED);
    @@ -262,7 +262,7 @@ suite('WordPartOperations', () => {
     			ed => deleteWordPartLeft(ed),
     			ed => ed.getPosition()!,
     			ed => ed.getValue().length === 0,
    -			{ wordSeparators: "!\"#&'()*+,./:;<=>?@[\\]^`{|}·" } // default characters sans '$-%~' plus '·'
    +			{ wordSeparators: '!"#&\'()*+,./:;<=>?@[\\]^`{|}·' } // default characters sans '$-%~' plus '·'
     		);
     		const actual = serializePipePositions(text, actualStops);
     		assert.deepStrictEqual(actual, EXPECTED);
    @@ -279,7 +279,7 @@ suite('WordPartOperations', () => {
     			ed => deleteWordPartRight(ed),
     			ed => new Position(1, text.length - ed.getValue().length + 1),
     			ed => ed.getValue().length === 0,
    -			{ wordSeparators: "!\"#&'()*+,./:;<=>?@[\\]^`{|}·" } // default characters sans '$-%~' plus '·'
    +			{ wordSeparators: '!"#&\'()*+,./:;<=>?@[\\]^`{|}·' } // default characters sans '$-%~' plus '·'
     		);
     		const actual = serializePipePositions(text, actualStops);
     		assert.deepStrictEqual(actual, EXPECTED);
    diff --git a/src/vs/editor/test/browser/config/editorConfiguration.test.ts b/src/vs/editor/test/browser/config/editorConfiguration.test.ts
    index d787e980ef7..ad945e54f39 100644
    --- a/src/vs/editor/test/browser/config/editorConfiguration.test.ts
    +++ b/src/vs/editor/test/browser/config/editorConfiguration.test.ts
    @@ -257,13 +257,13 @@ suite('Common Editor Config', () => {
     		const actual = config.options.get(EditorOption.unicodeHighlighting);
     		assert.deepStrictEqual(actual,
     			{
    -				nonBasicASCII: "inUntrustedWorkspace",
    +				nonBasicASCII: 'inUntrustedWorkspace',
     				invisibleCharacters: true,
     				ambiguousCharacters: true,
    -				includeComments: "inUntrustedWorkspace",
    -				includeStrings: "inUntrustedWorkspace",
    -				allowedCharacters: { "x": true },
    -				allowedLocales: { "_os": true, "_vscode": true }
    +				includeComments: 'inUntrustedWorkspace',
    +				includeStrings: 'inUntrustedWorkspace',
    +				allowedCharacters: { 'x': true },
    +				allowedLocales: { '_os': true, '_vscode': true }
     			}
     		);
     		config.dispose();
    diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts
    index bae8989b263..210dbed3713 100644
    --- a/src/vs/editor/test/browser/controller/cursor.test.ts
    +++ b/src/vs/editor/test/browser/controller/cursor.test.ts
    @@ -4514,8 +4514,8 @@ suite('Editor Controller', () => {
     				['(', ')']
     			],
     			indentationRules: {
    -				increaseIndentPattern: new RegExp("(^.*\\{[^}]*$)"),
    -				decreaseIndentPattern: new RegExp("^\\s*\\}")
    +				increaseIndentPattern: new RegExp('(^.*\\{[^}]*$)'),
    +				decreaseIndentPattern: new RegExp('^\\s*\\}')
     			}
     		}));
     
    @@ -4599,8 +4599,8 @@ suite('Editor Controller', () => {
     				['(', ')']
     			],
     			indentationRules: {
    -				increaseIndentPattern: new RegExp("({+(?=([^\"]*\"[^\"]*\")*[^\"}]*$))|(\\[+(?=([^\"]*\"[^\"]*\")*[^\"\\]]*$))"),
    -				decreaseIndentPattern: new RegExp("^\\s*[}\\]],?\\s*$")
    +				increaseIndentPattern: new RegExp('({+(?=([^"]*"[^"]*")*[^"}]*$))|(\\[+(?=([^"]*"[^"]*")*[^"\\]]*$))'),
    +				decreaseIndentPattern: new RegExp('^\\s*[}\\]],?\\s*$')
     			}
     		}));
     
    diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts
    index a1cfa19429d..2ef8dcb1ce6 100644
    --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts
    +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts
    @@ -589,20 +589,20 @@ suite('TextAreaInput', () => {
     
     		const actualOutgoingEvents = await simulateInteraction(recorded);
     		assert.deepStrictEqual(actualOutgoingEvents, ([
    -			{ type: "compositionStart", data: "" },
    -			{ type: "type", text: "'", replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionUpdate", data: "'" },
    -			{ type: "type", text: "'", replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionUpdate", data: "'" },
    -			{ type: "type", text: "'", replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionEnd" },
    -			{ type: "compositionStart", data: "" },
    -			{ type: "type", text: "'", replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionUpdate", data: "'" },
    -			{ type: "type", text: "';", replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionUpdate", data: "';" },
    -			{ type: "type", text: "';", replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 },
    -			{ type: "compositionEnd" }
    +			{ type: 'compositionStart', data: '' },
    +			{ type: 'type', text: `'`, replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionUpdate', data: `'` },
    +			{ type: 'type', text: `'`, replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionUpdate', data: `'` },
    +			{ type: 'type', text: `'`, replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionEnd' },
    +			{ type: 'compositionStart', data: '' },
    +			{ type: 'type', text: `'`, replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionUpdate', data: `'` },
    +			{ type: 'type', text: `';`, replacePrevCharCnt: 1, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionUpdate', data: `';` },
    +			{ type: 'type', text: `';`, replacePrevCharCnt: 2, replaceNextCharCnt: 0, positionDelta: 0 },
    +			{ type: 'compositionEnd' }
     		]));
     
     		const actualResultingState = interpretTypeEvents(recorded.env.OS, recorded.env.browser, recorded.initial, actualOutgoingEvents);
    diff --git a/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts b/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts
    index 1e293a9e878..19fdb45b585 100644
    --- a/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts
    +++ b/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts
    @@ -36,7 +36,7 @@ suite('DiffEditorWidget2', () => {
     				3,
     				3,
     			)), [
    -				"[1,11) - [1,11)"
    +				'[1,11) - [1,11)'
     			]);
     		});
     
    @@ -60,7 +60,7 @@ suite('DiffEditorWidget2', () => {
     				100,
     				3,
     				3,
    -			)), (["[1,96) - [1,96)"]));
    +			)), (['[1,96) - [1,96)']));
     		});
     	});
     });
    diff --git a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts
    index 7063bb1dd4e..f7845b1109a 100644
    --- a/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts
    +++ b/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts
    @@ -3,18 +3,18 @@
      *  Licensed under the MIT License. See License.txt in the project root for license information.
      *--------------------------------------------------------------------------------------------*/
     
    -import * as assert from "assert";
    -import { DisposableStore } from "../../../../base/common/lifecycle.js";
    -import { IObservable, derivedHandleChanges } from "../../../../base/common/observable.js";
    -import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../base/test/common/utils.js";
    -import { ICodeEditor } from "../../../browser/editorBrowser.js";
    -import { ObservableCodeEditor, observableCodeEditor } from "../../../browser/observableCodeEditor.js";
    -import { Position } from "../../../common/core/position.js";
    -import { Range } from "../../../common/core/range.js";
    -import { ViewModel } from "../../../common/viewModel/viewModelImpl.js";
    -import { withTestCodeEditor } from "../testCodeEditor.js";
    -
    -suite("CodeEditorWidget", () => {
    +import * as assert from 'assert';
    +import { DisposableStore } from '../../../../base/common/lifecycle.js';
    +import { IObservable, derivedHandleChanges } from '../../../../base/common/observable.js';
    +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
    +import { ICodeEditor } from '../../../browser/editorBrowser.js';
    +import { ObservableCodeEditor, observableCodeEditor } from '../../../browser/observableCodeEditor.js';
    +import { Position } from '../../../common/core/position.js';
    +import { Range } from '../../../common/core/range.js';
    +import { ViewModel } from '../../../common/viewModel/viewModelImpl.js';
    +import { withTestCodeEditor } from '../testCodeEditor.js';
    +
    +suite('CodeEditorWidget', () => {
     	ensureNoDisposablesAreLeakedInTestSuite();
     
     	function withTestFixture(
    @@ -29,7 +29,7 @@ suite("CodeEditorWidget", () => {
     			| undefined,
     		cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable }) => void
     	) {
    -		withTestCodeEditor("hello world", {}, (editor, viewModel) => {
    +		withTestCodeEditor('hello world', {}, (editor, viewModel) => {
     			const disposables = new DisposableStore();
     			preSetupCallback?.(editor, disposables);
     			const obsEditor = observableCodeEditor(editor);
    @@ -49,7 +49,7 @@ suite("CodeEditorWidget", () => {
     				},
     				(reader) => {
     					const versionId = obsEditor.versionId.read(reader);
    -					const selection = obsEditor.selections.read(reader)?.map((s) => s.toString()).join(", ");
    +					const selection = obsEditor.selections.read(reader)?.map((s) => s.toString()).join(', ');
     					obsEditor.onDidType.read(reader);
     
     					const str = `running derived: selection: ${selection}, value: ${versionId}`;
    @@ -60,7 +60,7 @@ suite("CodeEditorWidget", () => {
     
     			derived.recomputeInitiallyAndOnChange(disposables);
     			assert.deepStrictEqual(log.getAndClearEntries(), [
    -				"running derived: selection: [1,1 -> 1,1], value: 1",
    +				'running derived: selection: [1,1 -> 1,1], value: 1',
     			]);
     
     			cb({ editor, viewModel, log, derived });
    @@ -69,61 +69,61 @@ suite("CodeEditorWidget", () => {
     		});
     	}
     
    -	test("setPosition", () =>
    +	test('setPosition', () =>
     		withTestFixture(({ editor, log }) => {
     			editor.setPosition(new Position(1, 2));
     
     			assert.deepStrictEqual(log.getAndClearEntries(), ([
    -				"handle change: editor.selections {\"selection\":\"[1,2 -> 1,2]\",\"modelVersionId\":1,\"oldSelections\":[\"[1,1 -> 1,1]\"],\"oldModelVersionId\":1,\"source\":\"api\",\"reason\":0}",
    -				"running derived: selection: [1,2 -> 1,2], value: 1"
    +				'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":1,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"api","reason":0}',
    +				'running derived: selection: [1,2 -> 1,2], value: 1'
     			]));
     		}));
     
    -	test("keyboard.type", () =>
    +	test('keyboard.type', () =>
     		withTestFixture(({ editor, log }) => {
    -			editor.trigger("keyboard", "type", { text: "abc" });
    +			editor.trigger('keyboard', 'type', { text: 'abc' });
     
     			assert.deepStrictEqual(log.getAndClearEntries(), ([
    -				"handle change: editor.onDidType \"abc\"",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,1 -> 1,1]\",\"rangeLength\":0,\"text\":\"a\",\"rangeOffset\":0}],\"eol\":\"\\n\",\"versionId\":2,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,2 -> 1,2]\",\"rangeLength\":0,\"text\":\"b\",\"rangeOffset\":1}],\"eol\":\"\\n\",\"versionId\":3,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,3 -> 1,3]\",\"rangeLength\":0,\"text\":\"c\",\"rangeOffset\":2}],\"eol\":\"\\n\",\"versionId\":4,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.selections {\"selection\":\"[1,4 -> 1,4]\",\"modelVersionId\":4,\"oldSelections\":[\"[1,1 -> 1,1]\"],\"oldModelVersionId\":1,\"source\":\"keyboard\",\"reason\":0}",
    -				"running derived: selection: [1,4 -> 1,4], value: 4"
    +				'handle change: editor.onDidType "abc"',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',
    +				'running derived: selection: [1,4 -> 1,4], value: 4'
     			]));
     		}));
     
    -	test("keyboard.type and set position", () =>
    +	test('keyboard.type and set position', () =>
     		withTestFixture(({ editor, log }) => {
    -			editor.trigger("keyboard", "type", { text: "abc" });
    +			editor.trigger('keyboard', 'type', { text: 'abc' });
     
     			assert.deepStrictEqual(log.getAndClearEntries(), ([
    -				"handle change: editor.onDidType \"abc\"",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,1 -> 1,1]\",\"rangeLength\":0,\"text\":\"a\",\"rangeOffset\":0}],\"eol\":\"\\n\",\"versionId\":2,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,2 -> 1,2]\",\"rangeLength\":0,\"text\":\"b\",\"rangeOffset\":1}],\"eol\":\"\\n\",\"versionId\":3,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,3 -> 1,3]\",\"rangeLength\":0,\"text\":\"c\",\"rangeOffset\":2}],\"eol\":\"\\n\",\"versionId\":4,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -				"handle change: editor.selections {\"selection\":\"[1,4 -> 1,4]\",\"modelVersionId\":4,\"oldSelections\":[\"[1,1 -> 1,1]\"],\"oldModelVersionId\":1,\"source\":\"keyboard\",\"reason\":0}",
    -				"running derived: selection: [1,4 -> 1,4], value: 4"
    +				'handle change: editor.onDidType "abc"',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +				'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',
    +				'running derived: selection: [1,4 -> 1,4], value: 4'
     			]));
     
    -			editor.setPosition(new Position(1, 5), "test");
    +			editor.setPosition(new Position(1, 5), 'test');
     
     			assert.deepStrictEqual(log.getAndClearEntries(), ([
    -				"handle change: editor.selections {\"selection\":\"[1,5 -> 1,5]\",\"modelVersionId\":4,\"oldSelections\":[\"[1,4 -> 1,4]\"],\"oldModelVersionId\":4,\"source\":\"test\",\"reason\":0}",
    -				"running derived: selection: [1,5 -> 1,5], value: 4"
    +				'handle change: editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":4,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":4,"source":"test","reason":0}',
    +				'running derived: selection: [1,5 -> 1,5], value: 4'
     			]));
     		}));
     
    -	test("listener interaction (unforced)", () => {
    +	test('listener interaction (unforced)', () => {
     		let derived: IObservable;
     		let log: Log;
     		withEditorSetupTestFixture(
     			(editor, disposables) => {
     				disposables.add(
     					editor.onDidChangeModelContent(() => {
    -						log.log(">>> before get");
    +						log.log('>>> before get');
     						derived.get();
    -						log.log("<<< after get");
    +						log.log('<<< after get');
     					})
     				);
     			},
    @@ -132,32 +132,32 @@ suite("CodeEditorWidget", () => {
     				derived = args.derived;
     				log = args.log;
     
    -				editor.trigger("keyboard", "type", { text: "a" });
    +				editor.trigger('keyboard', 'type', { text: 'a' });
     				assert.deepStrictEqual(log.getAndClearEntries(), ([
    -					">>> before get",
    -					"<<< after get",
    -					"handle change: editor.onDidType \"a\"",
    -					"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,1 -> 1,1]\",\"rangeLength\":0,\"text\":\"a\",\"rangeOffset\":0}],\"eol\":\"\\n\",\"versionId\":2,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -					"handle change: editor.selections {\"selection\":\"[1,2 -> 1,2]\",\"modelVersionId\":2,\"oldSelections\":[\"[1,1 -> 1,1]\"],\"oldModelVersionId\":1,\"source\":\"keyboard\",\"reason\":0}",
    -					"running derived: selection: [1,2 -> 1,2], value: 2"
    +					'>>> before get',
    +					'<<< after get',
    +					'handle change: editor.onDidType "a"',
    +					'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +					'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',
    +					'running derived: selection: [1,2 -> 1,2], value: 2'
     				]));
     			}
     		);
     	});
     
    -	test("listener interaction ()", () => {
    +	test('listener interaction ()', () => {
     		let derived: IObservable;
     		let log: Log;
     		withEditorSetupTestFixture(
     			(editor, disposables) => {
     				disposables.add(
     					editor.onDidChangeModelContent(() => {
    -						log.log(">>> before forceUpdate");
    +						log.log('>>> before forceUpdate');
     						observableCodeEditor(editor).forceUpdate();
     
    -						log.log(">>> before get");
    +						log.log('>>> before get');
     						derived.get();
    -						log.log("<<< after get");
    +						log.log('<<< after get');
     					})
     				);
     			},
    @@ -166,18 +166,18 @@ suite("CodeEditorWidget", () => {
     				derived = args.derived;
     				log = args.log;
     
    -				editor.trigger("keyboard", "type", { text: "a" });
    +				editor.trigger('keyboard', 'type', { text: 'a' });
     
     				assert.deepStrictEqual(log.getAndClearEntries(), ([
    -					">>> before forceUpdate",
    -					">>> before get",
    -					"handle change: editor.versionId undefined",
    -					"running derived: selection: [1,2 -> 1,2], value: 2",
    -					"<<< after get",
    -					"handle change: editor.onDidType \"a\"",
    -					"handle change: editor.versionId {\"changes\":[{\"range\":\"[1,1 -> 1,1]\",\"rangeLength\":0,\"text\":\"a\",\"rangeOffset\":0}],\"eol\":\"\\n\",\"versionId\":2,\"detailedReasons\":[{\"metadata\":{\"source\":\"cursor\",\"kind\":\"type\",\"detailedSource\":\"keyboard\"}}],\"detailedReasonsChangeLengths\":[1]}",
    -					"handle change: editor.selections {\"selection\":\"[1,2 -> 1,2]\",\"modelVersionId\":2,\"oldSelections\":[\"[1,1 -> 1,1]\"],\"oldModelVersionId\":1,\"source\":\"keyboard\",\"reason\":0}",
    -					"running derived: selection: [1,2 -> 1,2], value: 2"
    +					'>>> before forceUpdate',
    +					'>>> before get',
    +					'handle change: editor.versionId undefined',
    +					'running derived: selection: [1,2 -> 1,2], value: 2',
    +					'<<< after get',
    +					'handle change: editor.onDidType "a"',
    +					'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',
    +					'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',
    +					'running derived: selection: [1,2 -> 1,2], value: 2'
     				]));
     			}
     		);
    @@ -218,12 +218,12 @@ function formatChange(change: unknown) {
     function observableName(obs: IObservable, obsEditor: ObservableCodeEditor): string {
     	switch (obs) {
     		case obsEditor.selections:
    -			return "editor.selections";
    +			return 'editor.selections';
     		case obsEditor.versionId:
    -			return "editor.versionId";
    +			return 'editor.versionId';
     		case obsEditor.onDidType:
    -			return "editor.onDidType";
    +			return 'editor.onDidType';
     		default:
    -			return "unknown";
    +			return 'unknown';
     	}
     }
    diff --git a/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts b/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts
    index f313b7b012e..d1f2837d2a5 100644
    --- a/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts
    +++ b/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts
    @@ -18,35 +18,35 @@ suite('PositionOffsetTransformer', () => {
     		assert.deepStrictEqual(
     			new OffsetRange(0, str.length + 2).map(i => t.getPosition(i).toString()),
     			[
    -				"(1,1)",
    -				"(1,2)",
    -				"(1,3)",
    -				"(1,4)",
    -				"(1,5)",
    -				"(1,6)",
    -				"(1,7)",
    -				"(2,1)",
    -				"(2,2)",
    -				"(2,3)",
    -				"(2,4)",
    -				"(2,5)",
    -				"(2,6)",
    -				"(2,7)",
    -				"(3,1)",
    -				"(3,2)",
    -				"(3,3)",
    -				"(3,4)",
    -				"(3,5)",
    -				"(3,6)",
    -				"(3,7)",
    -				"(4,1)",
    -				"(4,2)",
    -				"(4,3)",
    -				"(4,4)",
    -				"(4,5)",
    -				"(4,6)",
    -				"(4,7)",
    -				"(4,8)"
    +				'(1,1)',
    +				'(1,2)',
    +				'(1,3)',
    +				'(1,4)',
    +				'(1,5)',
    +				'(1,6)',
    +				'(1,7)',
    +				'(2,1)',
    +				'(2,2)',
    +				'(2,3)',
    +				'(2,4)',
    +				'(2,5)',
    +				'(2,6)',
    +				'(2,7)',
    +				'(3,1)',
    +				'(3,2)',
    +				'(3,3)',
    +				'(3,4)',
    +				'(3,5)',
    +				'(3,6)',
    +				'(3,7)',
    +				'(4,1)',
    +				'(4,2)',
    +				'(4,3)',
    +				'(4,4)',
    +				'(4,5)',
    +				'(4,6)',
    +				'(4,7)',
    +				'(4,8)'
     			]
     		);
     	});
    diff --git a/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts b/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts
    index 980d26f80e4..95b42693c25 100644
    --- a/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts
    +++ b/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts
    @@ -32,7 +32,7 @@ suite('Default Document Colors Computer', () => {
     
     	test('Hex colors in strings should be detected', () => {
     		// Test case from issue: hex color inside string is not detected
    -		const model = new TestDocumentModel("const color = '#ff0000';");
    +		const model = new TestDocumentModel(`const color = '#ff0000';`);
     		const colors = computeDefaultDocumentColors(model);
     
     		assert.strictEqual(colors.length, 1, 'Should detect one hex color');
    @@ -53,7 +53,7 @@ suite('Default Document Colors Computer', () => {
     	});
     
     	test('Multiple hex colors in array should be detected', () => {
    -		const model = new TestDocumentModel("const colors = ['#ff0000', '#00ff00', '#0000ff'];");
    +		const model = new TestDocumentModel(`const colors = ['#ff0000', '#00ff00', '#0000ff'];`);
     		const colors = computeDefaultDocumentColors(model);
     
     		assert.strictEqual(colors.length, 3, 'Should detect three hex colors');
    @@ -77,7 +77,7 @@ suite('Default Document Colors Computer', () => {
     	test('Existing functionality should still work', () => {
     		// Test cases that were already working
     		const testCases = [
    -			{ content: "const color = ' #ff0000';", name: 'hex with space before' },
    +			{ content: `const color = ' #ff0000';`, name: 'hex with space before' },
     			{ content: '#ff0000', name: 'hex at start of line' },
     			{ content: '  #ff0000', name: 'hex with whitespace before' }
     		];
    @@ -90,7 +90,7 @@ suite('Default Document Colors Computer', () => {
     	});
     
     	test('8-digit hex colors should also work', () => {
    -		const model = new TestDocumentModel("const color = '#ff0000ff';");
    +		const model = new TestDocumentModel(`const color = '#ff0000ff';`);
     		const colors = computeDefaultDocumentColors(model);
     
     		assert.strictEqual(colors.length, 1, 'Should detect one 8-digit hex color');
    diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
    index 47d8cd1f2d5..affee68537a 100644
    --- a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
    +++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts
    @@ -79,7 +79,7 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => {
     				'0  0  0  0  0  1  1  1  1  1  1  1  1  ',
     				'0  1  2  3  4  3  4  5  6  7  8  9  10 ',
     
    -				"0  0  0  0  0  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ",
    +				'0  0  0  0  0  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ',
     				'3  2  1  0  0  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ∞  ',
     				// ------------------
     				'⁰  ¹  ²  ³  ⁴  ⁵  ⁶  ⁷  ⁸  ⁹  ',
    diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts
    index c3293730e82..42deb289c6c 100644
    --- a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts
    +++ b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts
    @@ -195,72 +195,72 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => {
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,2 -> 1,3]"
    +						range: '[1,2 -> 1,3]'
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,4 -> 1,5]"
    +						range: '[1,4 -> 1,5]'
     					},
     					{
     						level: 2,
     						levelEqualBracketType: 0,
    -						range: "[1,6 -> 1,7]"
    +						range: '[1,6 -> 1,7]'
     					},
     					{
     						level: 3,
     						levelEqualBracketType: 1,
    -						range: "[1,8 -> 1,9]"
    +						range: '[1,8 -> 1,9]'
     					},
     					{
     						level: 4,
     						levelEqualBracketType: 2,
    -						range: "[1,10 -> 1,11]"
    +						range: '[1,10 -> 1,11]'
     					},
     					{
     						level: 5,
     						levelEqualBracketType: 1,
    -						range: "[1,12 -> 1,13]"
    +						range: '[1,12 -> 1,13]'
     					},
     					{
     						level: 5,
     						levelEqualBracketType: 1,
    -						range: "[1,15 -> 1,16]"
    +						range: '[1,15 -> 1,16]'
     					},
     					{
     						level: 4,
     						levelEqualBracketType: 2,
    -						range: "[1,17 -> 1,18]"
    +						range: '[1,17 -> 1,18]'
     					},
     					{
     						level: 3,
     						levelEqualBracketType: 1,
    -						range: "[1,19 -> 1,20]"
    +						range: '[1,19 -> 1,20]'
     					},
     					{
     						level: 2,
     						levelEqualBracketType: 0,
    -						range: "[1,21 -> 1,22]"
    +						range: '[1,21 -> 1,22]'
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,23 -> 1,24]"
    +						range: '[1,23 -> 1,24]'
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,25 -> 1,26]"
    +						range: '[1,25 -> 1,26]'
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,27 -> 1,28]"
    +						range: '[1,27 -> 1,28]'
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,29 -> 1,30]"
    +						range: '[1,29 -> 1,30]'
     					},
     				]
     			);
    @@ -280,22 +280,22 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => {
     					{
     						level: 0,
     						isInvalid: true,
    -						range: "[1,2 -> 1,3]",
    +						range: '[1,2 -> 1,3]',
     					},
     					{
     						level: 1,
     						isInvalid: false,
    -						range: "[1,4 -> 1,5]",
    +						range: '[1,4 -> 1,5]',
     					},
     					{
     						level: 1,
     						isInvalid: false,
    -						range: "[1,5 -> 1,6]",
    +						range: '[1,5 -> 1,6]',
     					},
     					{
     						level: 0,
     						isInvalid: true,
    -						range: "[1,7 -> 1,8]"
    +						range: '[1,7 -> 1,8]'
     					}
     				]
     			);
    @@ -316,42 +316,42 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => {
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,2 -> 1,3]",
    +						range: '[1,2 -> 1,3]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,3 -> 1,4]",
    +						range: '[1,3 -> 1,4]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,5 -> 1,6]",
    +						range: '[1,5 -> 1,6]',
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,7 -> 1,8]",
    +						range: '[1,7 -> 1,8]',
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,8 -> 1,9]",
    +						range: '[1,8 -> 1,9]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,10 -> 1,11]",
    +						range: '[1,10 -> 1,11]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,13 -> 1,14]",
    +						range: '[1,13 -> 1,14]',
     					},
     					{
     						level: -1,
     						levelEqualBracketType: 0,
    -						range: "[1,15 -> 1,16]",
    +						range: '[1,15 -> 1,16]',
     					},
     				]
     			);
    @@ -365,62 +365,62 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => {
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,2 -> 1,3]",
    +						range: '[1,2 -> 1,3]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,3 -> 1,4]",
    +						range: '[1,3 -> 1,4]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,5 -> 1,6]",
    +						range: '[1,5 -> 1,6]',
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,6 -> 1,7]",
    +						range: '[1,6 -> 1,7]',
     					},
     					{
     						level: 2,
     						levelEqualBracketType: 0,
    -						range: "[1,7 -> 1,8]",
    +						range: '[1,7 -> 1,8]',
     					},
     					{
     						level: 2,
     						levelEqualBracketType: 0,
    -						range: "[1,8 -> 1,9]",
    +						range: '[1,8 -> 1,9]',
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,9 -> 1,10]",
    +						range: '[1,9 -> 1,10]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,10 -> 1,11]",
    +						range: '[1,10 -> 1,11]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,12 -> 1,13]",
    +						range: '[1,12 -> 1,13]',
     					},
     					{
     						level: 1,
     						levelEqualBracketType: 0,
    -						range: "[1,13 -> 1,14]",
    +						range: '[1,13 -> 1,14]',
     					},
     					{
     						level: 0,
     						levelEqualBracketType: 0,
    -						range: "[1,14 -> 1,15]",
    +						range: '[1,14 -> 1,15]',
     					},
     					{
     						level: -1,
     						levelEqualBracketType: 0,
    -						range: "[1,15 -> 1,16]",
    +						range: '[1,15 -> 1,16]',
     					},
     				]
     			);
    diff --git a/src/vs/editor/test/common/services/editorWebWorker.test.ts b/src/vs/editor/test/common/services/editorWebWorker.test.ts
    index f2f33476c16..f1838e8444b 100644
    --- a/src/vs/editor/test/common/services/editorWebWorker.test.ts
    +++ b/src/vs/editor/test/common/services/editorWebWorker.test.ts
    @@ -205,10 +205,10 @@ suite('EditorWebWorker', () => {
     					'function test() {}'
     				],
     				[{
    -					text: "\n/** Some Comment */\n",
    +					text: '\n/** Some Comment */\n',
     					range: new Range(1, 1, 1, 1)
     				}]),
    -			([{ range: "[1,1 -> 1,1]", text: "\n/** Some Comment */\n" }])
    +			([{ range: '[1,1 -> 1,1]', text: '\n/** Some Comment */\n' }])
     		);
     	});
     
    @@ -253,12 +253,12 @@ suite('EditorWebWorker', () => {
     					text: 'function alm() {}',
     					range: new Range(1, 1, 1, Number.MAX_SAFE_INTEGER)
     				}]),
    -			([{ range: "[1,10 -> 1,19]", text: "alm" }])
    +			([{ range: '[1,10 -> 1,19]', text: 'alm' }])
     		);
     	});
     
     	test('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () {
    -		await testEdits(("const API = require('../src/api');\n\ndescribe('API', () => {\n  let api;\n  let database;\n\n  beforeAll(() => {\n    database = {\n      getAllBooks: jest.fn(),\n      getBooksByAuthor: jest.fn(),\n      getBooksByTitle: jest.fn(),\n    };\n    api = new API(database);\n  });\n\n  describe('GET /books', () => {\n    it('should return all books', async () => {\n      const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n      database.getAllBooks.mockResolvedValue(mockBooks);\n\n      const req = {};\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === '/books') {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getAllBooks).toHaveBeenCalled();\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n\n  describe('GET /books/author/:author', () => {\n    it('should return books by author', async () => {\n      const mockAuthor = 'John Doe';\n      const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n      database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n      const req = {\n        params: {\n          author: mockAuthor,\n        },\n      };\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === `/books/author/${mockAuthor}`) {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n\n  describe('GET /books/title/:title', () => {\n    it('should return books by title', async () => {\n      const mockTitle = 'Book 1';\n      const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n      database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n      const req = {\n        params: {\n          title: mockTitle,\n        },\n      };\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === `/books/title/${mockTitle}`) {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n});\n").split('\n'),
    +		await testEdits(('const API = require(\'../src/api\');\n\ndescribe(\'API\', () => {\n  let api;\n  let database;\n\n  beforeAll(() => {\n    database = {\n      getAllBooks: jest.fn(),\n      getBooksByAuthor: jest.fn(),\n      getBooksByTitle: jest.fn(),\n    };\n    api = new API(database);\n  });\n\n  describe(\'GET /books\', () => {\n    it(\'should return all books\', async () => {\n      const mockBooks = [{ title: \'Book 1\' }, { title: \'Book 2\' }];\n      database.getAllBooks.mockResolvedValue(mockBooks);\n\n      const req = {};\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === \'/books\') {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getAllBooks).toHaveBeenCalled();\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n\n  describe(\'GET /books/author/:author\', () => {\n    it(\'should return books by author\', async () => {\n      const mockAuthor = \'John Doe\';\n      const mockBooks = [{ title: \'Book 1\', author: mockAuthor }, { title: \'Book 2\', author: mockAuthor }];\n      database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n      const req = {\n        params: {\n          author: mockAuthor,\n        },\n      };\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === `/books/author/${mockAuthor}`) {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n\n  describe(\'GET /books/title/:title\', () => {\n    it(\'should return books by title\', async () => {\n      const mockTitle = \'Book 1\';\n      const mockBooks = [{ title: mockTitle, author: \'John Doe\' }];\n      database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n      const req = {\n        params: {\n          title: mockTitle,\n        },\n      };\n      const res = {\n        json: jest.fn(),\n      };\n\n      await api.register({\n        get: (path, handler) => {\n          if (path === `/books/title/${mockTitle}`) {\n            handler(req, res);\n          }\n        },\n      });\n\n      expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n      expect(res.json).toHaveBeenCalledWith(mockBooks);\n    });\n  });\n});\n').split('\n'),
     			[{
     				range: { startLineNumber: 1, startColumn: 1, endLineNumber: 96, endColumn: 1 },
     				text: `const request = require('supertest');\nconst API = require('../src/api');\n\ndescribe('API', () => {\n  let api;\n  let database;\n\n  beforeAll(() => {\n    database = {\n      getAllBooks: jest.fn(),\n      getBooksByAuthor: jest.fn(),\n      getBooksByTitle: jest.fn(),\n    };\n    api = new API(database);\n  });\n\n  describe('GET /books', () => {\n    it('should return all books', async () => {\n      const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n      database.getAllBooks.mockResolvedValue(mockBooks);\n\n      const response = await request(api.app).get('/books');\n\n      expect(database.getAllBooks).toHaveBeenCalled();\n      expect(response.status).toBe(200);\n      expect(response.body).toEqual(mockBooks);\n    });\n  });\n\n  describe('GET /books/author/:author', () => {\n    it('should return books by author', async () => {\n      const mockAuthor = 'John Doe';\n      const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n      database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n      const response = await request(api.app).get(\`/books/author/\${mockAuthor}\`);\n\n      expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n      expect(response.status).toBe(200);\n      expect(response.body).toEqual(mockBooks);\n    });\n  });\n\n  describe('GET /books/title/:title', () => {\n    it('should return books by title', async () => {\n      const mockTitle = 'Book 1';\n      const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n      database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n      const response = await request(api.app).get(\`/books/title/\${mockTitle}\`);\n\n      expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n      expect(response.status).toBe(200);\n      expect(response.body).toEqual(mockBooks);\n    });\n  });\n});\n`,
    diff --git a/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts b/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts
    index 9537819b77e..6bddad6b17f 100644
    --- a/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts
    +++ b/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts
    @@ -45,7 +45,7 @@ suite('lineRangeMapping', () => {
     					''
     				])
     			).toString(),
    -			"{[2,3)->[2,2)}"
    +			'{[2,3)->[2,2)}'
     		);
     	});
     
    @@ -67,7 +67,7 @@ suite('lineRangeMapping', () => {
     					'',
     				])
     			).toString(),
    -			"{[2,2)->[2,4)}"
    +			'{[2,2)->[2,4)}'
     		);
     	});
     });
    @@ -91,14 +91,14 @@ suite('LinesSliceCharSequence', () => {
     			{ result: OffsetRange.ofLength(sequence.length).map(offset => sequence.translateOffset(offset).toString()) },
     			({
     				result: [
    -					"(2,1)", "(2,2)", "(2,3)", "(2,4)", "(2,5)", "(2,6)", "(2,7)", "(2,8)", "(2,9)", "(2,10)", "(2,11)",
    -					"(2,12)", "(2,13)", "(2,14)", "(2,15)", "(2,16)",
    +					'(2,1)', '(2,2)', '(2,3)', '(2,4)', '(2,5)', '(2,6)', '(2,7)', '(2,8)', '(2,9)', '(2,10)', '(2,11)',
    +					'(2,12)', '(2,13)', '(2,14)', '(2,15)', '(2,16)',
     
    -					"(3,1)", "(3,2)", "(3,3)", "(3,4)", "(3,5)", "(3,6)", "(3,7)", "(3,8)", "(3,9)", "(3,10)", "(3,11)", "(3,12)",
    +					'(3,1)', '(3,2)', '(3,3)', '(3,4)', '(3,5)', '(3,6)', '(3,7)', '(3,8)', '(3,9)', '(3,10)', '(3,11)', '(3,12)',
     
    -					"(4,1)", "(4,2)", "(4,3)", "(4,4)", "(4,5)", "(4,6)", "(4,7)", "(4,8)", "(4,9)",
    -					"(4,10)", "(4,11)", "(4,12)", "(4,13)", "(4,14)", "(4,15)", "(4,16)", "(4,17)",
    -					"(4,18)", "(4,19)"
    +					'(4,1)', '(4,2)', '(4,3)', '(4,4)', '(4,5)', '(4,6)', '(4,7)', '(4,8)', '(4,9)',
    +					'(4,10)', '(4,11)', '(4,12)', '(4,13)', '(4,14)', '(4,15)', '(4,16)', '(4,17)',
    +					'(4,18)', '(4,19)'
     				]
     			})
     		);
    @@ -107,12 +107,12 @@ suite('LinesSliceCharSequence', () => {
     	test('extendToFullLines', () => {
     		assert.deepStrictEqual(
     			{ result: sequence.getText(sequence.extendToFullLines(new OffsetRange(20, 25))) },
    -			({ result: "line3: barr\n" })
    +			({ result: 'line3: barr\n' })
     		);
     
     		assert.deepStrictEqual(
     			{ result: sequence.getText(sequence.extendToFullLines(new OffsetRange(20, 45))) },
    -			({ result: "line3: barr\nline4: hello world\n" })
    +			({ result: 'line3: barr\nline4: hello world\n' })
     		);
     	});
     });
    diff --git a/src/vs/platform/contextkey/test/common/parser.test.ts b/src/vs/platform/contextkey/test/common/parser.test.ts
    index ad06b1f45f7..660161e5f5d 100644
    --- a/src/vs/platform/contextkey/test/common/parser.test.ts
    +++ b/src/vs/platform/contextkey/test/common/parser.test.ts
    @@ -39,42 +39,42 @@ suite('Context Key Parser', () => {
     
     	test(' foo', () => {
     		const input = ' foo';
    -		assert.deepStrictEqual(parseToStr(input), "foo");
    +		assert.deepStrictEqual(parseToStr(input), 'foo');
     	});
     
     	test('!foo', () => {
     		const input = '!foo';
    -		assert.deepStrictEqual(parseToStr(input), "!foo");
    +		assert.deepStrictEqual(parseToStr(input), '!foo');
     	});
     
     	test('foo =~ /bar/', () => {
     		const input = 'foo =~ /bar/';
    -		assert.deepStrictEqual(parseToStr(input), "foo =~ /bar/");
    +		assert.deepStrictEqual(parseToStr(input), 'foo =~ /bar/');
     	});
     
     	test(`foo || (foo =~ /bar/ && baz)`, () => {
     		const input = `foo || (foo =~ /bar/ && baz)`;
    -		assert.deepStrictEqual(parseToStr(input), "foo || baz && foo =~ /bar/");
    +		assert.deepStrictEqual(parseToStr(input), 'foo || baz && foo =~ /bar/');
     	});
     
     	test('foo || (foo =~ /bar/ || baz)', () => {
     		const input = 'foo || (foo =~ /bar/ || baz)';
    -		assert.deepStrictEqual(parseToStr(input), "baz || foo || foo =~ /bar/");
    +		assert.deepStrictEqual(parseToStr(input), 'baz || foo || foo =~ /bar/');
     	});
     
     	test(`(foo || bar) && (jee || jar)`, () => {
     		const input = `(foo || bar) && (jee || jar)`;
    -		assert.deepStrictEqual(parseToStr(input), "bar && jar || bar && jee || foo && jar || foo && jee");
    +		assert.deepStrictEqual(parseToStr(input), 'bar && jar || bar && jee || foo && jar || foo && jee');
     	});
     
     	test('foo && foo =~ /zee/i', () => {
     		const input = 'foo && foo =~ /zee/i';
    -		assert.deepStrictEqual(parseToStr(input), "foo && foo =~ /zee/i");
    +		assert.deepStrictEqual(parseToStr(input), 'foo && foo =~ /zee/i');
     	});
     
     	test('foo.bar==enabled', () => {
     		const input = 'foo.bar==enabled';
    -		assert.deepStrictEqual(parseToStr(input), "foo.bar == 'enabled'");
    +		assert.deepStrictEqual(parseToStr(input), `foo.bar == 'enabled'`);
     	});
     
     	test(`foo.bar == 'enabled'`, () => {
    @@ -84,17 +84,17 @@ suite('Context Key Parser', () => {
     
     	test('foo.bar:zed==completed - equality with no space', () => {
     		const input = 'foo.bar:zed==completed';
    -		assert.deepStrictEqual(parseToStr(input), "foo.bar:zed == 'completed'");
    +		assert.deepStrictEqual(parseToStr(input), `foo.bar:zed == 'completed'`);
     	});
     
     	test('a && b || c', () => {
     		const input = 'a && b || c';
    -		assert.deepStrictEqual(parseToStr(input), "c || a && b");
    +		assert.deepStrictEqual(parseToStr(input), 'c || a && b');
     	});
     
     	test('fooBar && baz.jar && fee.bee', () => {
     		const input = 'fooBar && baz.jar && fee.bee';
    -		assert.deepStrictEqual(parseToStr(input), "baz.jar && fee.bee && fooBar");
    +		assert.deepStrictEqual(parseToStr(input), 'baz.jar && fee.bee && fooBar');
     	});
     
     	test('foo.barBaz < 2', () => {
    @@ -104,12 +104,12 @@ suite('Context Key Parser', () => {
     
     	test('foo.bar >= -1', () => {
     		const input = 'foo.bar >= -1';
    -		assert.deepStrictEqual(parseToStr(input), "foo.bar >= -1");
    +		assert.deepStrictEqual(parseToStr(input), 'foo.bar >= -1');
     	});
     
     	test(`key contains  : view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded`, () => {
     		const input = `view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded`;
    -		assert.deepStrictEqual(parseToStr(input), "vsc-packages-folders-loaded && view == 'vsc-packages-activitybar-folders'");
    +		assert.deepStrictEqual(parseToStr(input), `vsc-packages-folders-loaded && view == 'vsc-packages-activitybar-folders'`);
     	});
     
     	test('foo.bar <= -1', () => {
    @@ -119,22 +119,22 @@ suite('Context Key Parser', () => {
     
     	test('!cmake:hideBuildCommand \u0026\u0026 cmake:enableFullFeatureSet', () => {
     		const input = '!cmake:hideBuildCommand \u0026\u0026 cmake:enableFullFeatureSet';
    -		assert.deepStrictEqual(parseToStr(input), "cmake:enableFullFeatureSet && !cmake:hideBuildCommand");
    +		assert.deepStrictEqual(parseToStr(input), 'cmake:enableFullFeatureSet && !cmake:hideBuildCommand');
     	});
     
     	test('!(foo && bar)', () => {
     		const input = '!(foo && bar)';
    -		assert.deepStrictEqual(parseToStr(input), "!bar || !foo");
    +		assert.deepStrictEqual(parseToStr(input), '!bar || !foo');
     	});
     
     	test('!(foo && bar || boar) || deer', () => {
     		const input = '!(foo && bar || boar) || deer';
    -		assert.deepStrictEqual(parseToStr(input), "deer || !bar && !boar || !boar && !foo");
    +		assert.deepStrictEqual(parseToStr(input), 'deer || !bar && !boar || !boar && !foo');
     	});
     
     	test(`!(!foo)`, () => {
     		const input = `!(!foo)`;
    -		assert.deepStrictEqual(parseToStr(input), "foo");
    +		assert.deepStrictEqual(parseToStr(input), 'foo');
     	});
     
     	suite('controversial', () => {
    @@ -148,7 +148,7 @@ suite('Context Key Parser', () => {
     		*/
     		test(`debugState == "stopped"`, () => {
     			const input = `debugState == "stopped"`;
    -			assert.deepStrictEqual(parseToStr(input), "debugState == '\"stopped\"'");
    +			assert.deepStrictEqual(parseToStr(input), `debugState == '"stopped"'`);
     		});
     
     		/*
    @@ -161,7 +161,7 @@ suite('Context Key Parser', () => {
     		*/
     		test(` viewItem == VSCode WorkSpace`, () => {
     			const input = ` viewItem == VSCode WorkSpace`;
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected 'WorkSpace' at offset 20.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected 'WorkSpace' at offset 20.\n`);
     		});
     
     
    @@ -171,17 +171,17 @@ suite('Context Key Parser', () => {
     
     		test(`resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`, () => {
     			const input = `resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`;
    -			assert.deepStrictEqual(parseToStr(input), "resource =~ /\\/foo\\/(barr|door\\/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(\\/.*)*$/");
    +			assert.deepStrictEqual(parseToStr(input), 'resource =~ /\\/foo\\/(barr|door\\/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(\\/.*)*$/');
     		});
     
     		test(`resource =~ /((/scratch/(?!update)(.*)/)|((/src/).*/)).*$/`, () => {
     			const input = `resource =~ /((/scratch/(?!update)(.*)/)|((/src/).*/)).*$/`;
    -			assert.deepStrictEqual(parseToStr(input), "resource =~ /((\\/scratch\\/(?!update)(.*)\\/)|((\\/src\\/).*\\/)).*$/");
    +			assert.deepStrictEqual(parseToStr(input), 'resource =~ /((\\/scratch\\/(?!update)(.*)\\/)|((\\/src\\/).*\\/)).*$/');
     		});
     
     		test(`resourcePath =~ /\.md(\.yml|\.txt)*$/giym`, () => {
     			const input = `resourcePath =~ /\.md(\.yml|\.txt)*$/giym`;
    -			assert.deepStrictEqual(parseToStr(input), "resourcePath =~ /.md(.yml|.txt)*$/im");
    +			assert.deepStrictEqual(parseToStr(input), 'resourcePath =~ /.md(.yml|.txt)*$/im');
     		});
     
     	});
    @@ -190,42 +190,42 @@ suite('Context Key Parser', () => {
     
     		test(`/foo`, () => {
     			const input = `/foo`;
    -			assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token '/foo' at offset 0. Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/'.\n\n --- \nParsing errors:\n\nUnexpected '/foo' at offset 0.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Lexing errors:\n\nUnexpected token '/foo' at offset 0. Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/'.\n\n --- \nParsing errors:\n\nUnexpected '/foo' at offset 0.\n`);
     		});
     
     		test(`!b == 'true'`, () => {
     			const input = `!b == 'true'`;
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '==' at offset 3.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected '==' at offset 3.\n`);
     		});
     
     		test('!foo &&  in bar', () => {
     			const input = '!foo &&  in bar';
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected 'in' at offset 9.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected 'in' at offset 9.\n`);
     		});
     
     		test('vim == 1 && vim<2<=3', () => {
     			const input = 'vim == 1 && vim<2<=3';
    -			assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token '=' at offset 23. Did you mean == or =~?\n\n --- \nParsing errors:\n\nUnexpected '=' at offset 23.\n"); // FIXME
    +			assert.deepStrictEqual(parseToStr(input), `Lexing errors:\n\nUnexpected token '=' at offset 23. Did you mean == or =~?\n\n --- \nParsing errors:\n\nUnexpected '=' at offset 23.\n`); // FIXME
     		});
     
     		test(`foo && 'bar`, () => {
     			const input = `foo && 'bar`;
    -			assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token ''bar' at offset 7. Did you forget to open or close the quote?\n\n --- \nParsing errors:\n\nUnexpected ''bar' at offset 7.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Lexing errors:\n\nUnexpected token ''bar' at offset 7. Did you forget to open or close the quote?\n\n --- \nParsing errors:\n\nUnexpected ''bar' at offset 7.\n`);
     		});
     
     		test(`config.foo &&  &&bar =~ /^foo$|^bar-foo$|^joo$|^jar$/ && !foo`, () => {
     			const input = `config.foo &&  &&bar =~ /^foo$|^bar-foo$|^joo$|^jar$/ && !foo`;
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '&&' at offset 15.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected '&&' at offset 15.\n`);
     		});
     
     		test(`!foo == 'test'`, () => {
     			const input = `!foo == 'test'`;
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '==' at offset 5.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected '==' at offset 5.\n`);
     		});
     
     		test(`!!foo`, function () {
     			const input = `!!foo`;
    -			assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '!' at offset 1.\n");
    +			assert.deepStrictEqual(parseToStr(input), `Parsing errors:\n\nUnexpected '!' at offset 1.\n`);
     		});
     
     	});
    diff --git a/src/vs/platform/contextkey/test/common/scanner.test.ts b/src/vs/platform/contextkey/test/common/scanner.test.ts
    index ba2a42fb630..bde5a5df3a5 100644
    --- a/src/vs/platform/contextkey/test/common/scanner.test.ts
    +++ b/src/vs/platform/contextkey/test/common/scanner.test.ts
    @@ -76,176 +76,176 @@ suite('Context Key Scanner', () => {
     
     		test('foo.bar', () => {
     			const input = 'foo.bar';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo.bar", offset: 0 }, { type: "EOF", offset: 18 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo.bar', offset: 0 }, { type: 'EOF', offset: 18 }]));
     		});
     
     		test('!foo', () => {
     			const input = '!foo';
    -			assert.deepStrictEqual(scan(input), ([{ type: "!", offset: 0 }, { type: "Str", lexeme: "foo", offset: 1 }, { type: "EOF", offset: 4 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: '!', offset: 0 }, { type: 'Str', lexeme: 'foo', offset: 1 }, { type: 'EOF', offset: 4 }]));
     		});
     
     		test('foo === bar', () => {
     			const input = 'foo === bar';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "Str", offset: 8, lexeme: "bar" }, { type: "EOF", offset: 11 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: '===', offset: 4 }, { type: 'Str', offset: 8, lexeme: 'bar' }, { type: 'EOF', offset: 11 }]));
     		});
     
     		test('foo  !== bar', () => {
     			const input = 'foo  !== bar';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "!==", offset: 5 }, { type: "Str", offset: 9, lexeme: "bar" }, { type: "EOF", offset: 12 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: '!==', offset: 5 }, { type: 'Str', offset: 9, lexeme: 'bar' }, { type: 'EOF', offset: 12 }]));
     		});
     
     		test('!(foo && bar)', () => {
     			const input = '!(foo && bar)';
    -			assert.deepStrictEqual(scan(input), ([{ type: "!", offset: 0 }, { type: "(", offset: 1 }, { type: "Str", lexeme: "foo", offset: 2 }, { type: "&&", offset: 6 }, { type: "Str", lexeme: "bar", offset: 9 }, { type: ")", offset: 12 }, { type: "EOF", offset: 13 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: '!', offset: 0 }, { type: '(', offset: 1 }, { type: 'Str', lexeme: 'foo', offset: 2 }, { type: '&&', offset: 6 }, { type: 'Str', lexeme: 'bar', offset: 9 }, { type: ')', offset: 12 }, { type: 'EOF', offset: 13 }]));
     		});
     
     		test('=~ ', () => {
     			const input = '=~ ';
    -			assert.deepStrictEqual(scan(input), ([{ type: "=~", offset: 0 }, { type: "EOF", offset: 3 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: '=~', offset: 0 }, { type: 'EOF', offset: 3 }]));
     		});
     
     		test('foo =~ /bar/', () => {
     			const input = 'foo =~ /bar/';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "=~", offset: 4 }, { type: "RegexStr", lexeme: "/bar/", offset: 7 }, { type: "EOF", offset: 12 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo', offset: 0 }, { type: '=~', offset: 4 }, { type: 'RegexStr', lexeme: '/bar/', offset: 7 }, { type: 'EOF', offset: 12 }]));
     		});
     
     		test('foo =~ /zee/i', () => {
     			const input = 'foo =~ /zee/i';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "=~", offset: 4 }, { type: "RegexStr", lexeme: "/zee/i", offset: 7 }, { type: "EOF", offset: 13 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo', offset: 0 }, { type: '=~', offset: 4 }, { type: 'RegexStr', lexeme: '/zee/i', offset: 7 }, { type: 'EOF', offset: 13 }]));
     		});
     
     
     		test('foo =~ /zee/gm', () => {
     			const input = 'foo =~ /zee/gm';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "=~", offset: 4 }, { type: "RegexStr", lexeme: "/zee/gm", offset: 7 }, { type: "EOF", offset: 14 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo', offset: 0 }, { type: '=~', offset: 4 }, { type: 'RegexStr', lexeme: '/zee/gm', offset: 7 }, { type: 'EOF', offset: 14 }]));
     		});
     
     		test('foo in barrr  ', () => {
     			const input = 'foo in barrr  ';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "in", offset: 4 }, { type: "Str", lexeme: "barrr", offset: 7 }, { type: "EOF", offset: 14 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo', offset: 0 }, { type: 'in', offset: 4 }, { type: 'Str', lexeme: 'barrr', offset: 7 }, { type: 'EOF', offset: 14 }]));
     		});
     
     		test(`resource =~ //FileCabinet/(SuiteScripts|Templates/(E-mail%20Templates|Marketing%20Templates)|Web%20Site%20Hosting%20Files)(/.*)*$/`, () => {
     			const input = `resource =~ //FileCabinet/(SuiteScripts|Templates/(E-mail%20Templates|Marketing%20Templates)|Web%20Site%20Hosting%20Files)(/.*)*$/`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "resource" }, { type: "=~", offset: 9 }, { type: "RegexStr", offset: 12, lexeme: "//" }, { type: "Str", offset: 14, lexeme: "FileCabinet/" }, { type: "(", offset: 26 }, { type: "Str", offset: 27, lexeme: "SuiteScripts" }, { type: "ErrorToken", offset: 39, lexeme: "|" }, { type: "Str", offset: 40, lexeme: "Templates/" }, { type: "(", offset: 50 }, { type: "Str", offset: 51, lexeme: "E-mail%20Templates" }, { type: "ErrorToken", offset: 69, lexeme: "|" }, { type: "Str", offset: 70, lexeme: "Marketing%20Templates" }, { type: ")", offset: 91 }, { type: "ErrorToken", offset: 92, lexeme: "|" }, { type: "Str", offset: 93, lexeme: "Web%20Site%20Hosting%20Files" }, { type: ")", offset: 121 }, { type: "(", offset: 122 }, { type: "RegexStr", offset: 123, lexeme: "/.*)*$/" }, { type: "EOF", offset: 130 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'resource' }, { type: '=~', offset: 9 }, { type: 'RegexStr', offset: 12, lexeme: '//' }, { type: 'Str', offset: 14, lexeme: 'FileCabinet/' }, { type: '(', offset: 26 }, { type: 'Str', offset: 27, lexeme: 'SuiteScripts' }, { type: 'ErrorToken', offset: 39, lexeme: '|' }, { type: 'Str', offset: 40, lexeme: 'Templates/' }, { type: '(', offset: 50 }, { type: 'Str', offset: 51, lexeme: 'E-mail%20Templates' }, { type: 'ErrorToken', offset: 69, lexeme: '|' }, { type: 'Str', offset: 70, lexeme: 'Marketing%20Templates' }, { type: ')', offset: 91 }, { type: 'ErrorToken', offset: 92, lexeme: '|' }, { type: 'Str', offset: 93, lexeme: 'Web%20Site%20Hosting%20Files' }, { type: ')', offset: 121 }, { type: '(', offset: 122 }, { type: 'RegexStr', offset: 123, lexeme: '/.*)*$/' }, { type: 'EOF', offset: 130 }]));
     		});
     
     		test('editorLangId in testely.supportedLangIds && resourceFilename =~ /^.+(.test.(\w+))$/gm', () => {
     			const input = 'editorLangId in testely.supportedLangIds && resourceFilename =~ /^.+(.test.(\w+))$/gm';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "editorLangId", offset: 0 }, { type: "in", offset: 13 }, { type: "Str", lexeme: "testely.supportedLangIds", offset: 16 }, { type: "&&", offset: 41 }, { type: "Str", lexeme: "resourceFilename", offset: 44 }, { type: "=~", offset: 61 }, { type: "RegexStr", lexeme: "/^.+(.test.(w+))$/gm", offset: 64 }, { type: "EOF", offset: 84 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'editorLangId', offset: 0 }, { type: 'in', offset: 13 }, { type: 'Str', lexeme: 'testely.supportedLangIds', offset: 16 }, { type: '&&', offset: 41 }, { type: 'Str', lexeme: 'resourceFilename', offset: 44 }, { type: '=~', offset: 61 }, { type: 'RegexStr', lexeme: '/^.+(.test.(w+))$/gm', offset: 64 }, { type: 'EOF', offset: 84 }]));
     		});
     
     		test('!(foo && bar) && baz', () => {
     			const input = '!(foo && bar) && baz';
    -			assert.deepStrictEqual(scan(input), ([{ type: "!", offset: 0 }, { type: "(", offset: 1 }, { type: "Str", lexeme: "foo", offset: 2 }, { type: "&&", offset: 6 }, { type: "Str", lexeme: "bar", offset: 9 }, { type: ")", offset: 12 }, { type: "&&", offset: 14 }, { type: "Str", lexeme: "baz", offset: 17 }, { type: "EOF", offset: 20 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: '!', offset: 0 }, { type: '(', offset: 1 }, { type: 'Str', lexeme: 'foo', offset: 2 }, { type: '&&', offset: 6 }, { type: 'Str', lexeme: 'bar', offset: 9 }, { type: ')', offset: 12 }, { type: '&&', offset: 14 }, { type: 'Str', lexeme: 'baz', offset: 17 }, { type: 'EOF', offset: 20 }]));
     		});
     
     		test('foo.bar:zed==completed - equality with no space', () => {
     			const input = 'foo.bar:zed==completed';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo.bar:zed", offset: 0 }, { type: "==", offset: 11 }, { type: "Str", lexeme: "completed", offset: 13 }, { type: "EOF", offset: 22 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo.bar:zed', offset: 0 }, { type: '==', offset: 11 }, { type: 'Str', lexeme: 'completed', offset: 13 }, { type: 'EOF', offset: 22 }]));
     		});
     
     		test('a && b || c', () => {
     			const input = 'a && b || c';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "a", offset: 0 }, { type: "&&", offset: 2 }, { type: "Str", lexeme: "b", offset: 5 }, { type: "||", offset: 7 }, { type: "Str", lexeme: "c", offset: 10 }, { type: "EOF", offset: 11 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'a', offset: 0 }, { type: '&&', offset: 2 }, { type: 'Str', lexeme: 'b', offset: 5 }, { type: '||', offset: 7 }, { type: 'Str', lexeme: 'c', offset: 10 }, { type: 'EOF', offset: 11 }]));
     		});
     
     		test('fooBar && baz.jar && fee.bee', () => {
     			const input = 'fooBar && baz.jar && fee.bee';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "fooBar", offset: 0 }, { type: "&&", offset: 7 }, { type: "Str", lexeme: "baz.jar", offset: 10 }, { type: "&&", offset: 18 }, { type: "Str", lexeme: "fee.bee", offset: 21 }, { type: "EOF", offset: 37 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'fooBar', offset: 0 }, { type: '&&', offset: 7 }, { type: 'Str', lexeme: 'baz.jar', offset: 10 }, { type: '&&', offset: 18 }, { type: 'Str', lexeme: 'fee.bee', offset: 21 }, { type: 'EOF', offset: 37 }]));
     		});
     
     		test('foo.barBaz < 2', () => {
     			const input = 'foo.barBaz < 2';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo.barBaz", offset: 0 }, { type: "<", offset: 16 }, { type: "Str", lexeme: "2", offset: 18 }, { type: "EOF", offset: 19 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo.barBaz', offset: 0 }, { type: '<', offset: 16 }, { type: 'Str', lexeme: '2', offset: 18 }, { type: 'EOF', offset: 19 }]));
     		});
     
     		test('foo.bar >= -1', () => {
     			const input = 'foo.bar >= -1';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo.bar", offset: 0 }, { type: ">=", offset: 8 }, { type: "Str", lexeme: "-1", offset: 11 }, { type: "EOF", offset: 13 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo.bar', offset: 0 }, { type: '>=', offset: 8 }, { type: 'Str', lexeme: '-1', offset: 11 }, { type: 'EOF', offset: 13 }]));
     		});
     
     		test('foo.bar <= -1', () => {
     			const input = 'foo.bar <= -1';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo.bar", offset: 0 }, { type: "<=", offset: 8 }, { type: "Str", lexeme: "-1", offset: 11 }, { type: "EOF", offset: 13 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo.bar', offset: 0 }, { type: '<=', offset: 8 }, { type: 'Str', lexeme: '-1', offset: 11 }, { type: 'EOF', offset: 13 }]));
     		});
     
     		test(`resource =~ /\\/Objects\\/.+\\.xml$/`, () => {
     			const input = `resource =~ /\\/Objects\\/.+\\.xml$/`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "resource", offset: 0 }, { type: "=~", offset: 9 }, { type: "RegexStr", lexeme: "/\\/Objects\\/.+\\.xml$/", offset: 12 }, { type: "EOF", offset: 33 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'resource', offset: 0 }, { type: '=~', offset: 9 }, { type: 'RegexStr', lexeme: '/\\/Objects\\/.+\\.xml$/', offset: 12 }, { type: 'EOF', offset: 33 }]));
     		});
     
     		test('view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded', () => {
     			const input = `view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "view", offset: 0 }, { type: "==", offset: 5 }, { type: "Str", lexeme: "vsc-packages-activitybar-folders", offset: 8 }, { type: "&&", offset: 41 }, { type: "Str", lexeme: "vsc-packages-folders-loaded", offset: 44 }, { type: "EOF", offset: 71 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'view', offset: 0 }, { type: '==', offset: 5 }, { type: 'Str', lexeme: 'vsc-packages-activitybar-folders', offset: 8 }, { type: '&&', offset: 41 }, { type: 'Str', lexeme: 'vsc-packages-folders-loaded', offset: 44 }, { type: 'EOF', offset: 71 }]));
     		});
     
     		test(`sfdx:project_opened && resource =~ /.*\\/functions\\/.*\\/[^\\/]+(\\/[^\\/]+\.(ts|js|java|json|toml))?$/ && resourceFilename != package.json && resourceFilename != package-lock.json && resourceFilename != tsconfig.json`, () => {
     			const input = `sfdx:project_opened && resource =~ /.*\\/functions\\/.*\\/[^\\/]+(\\/[^\\/]+\.(ts|js|java|json|toml))?$/ && resourceFilename != package.json && resourceFilename != package-lock.json && resourceFilename != tsconfig.json`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "sfdx:project_opened", offset: 0 }, { type: "&&", offset: 20 }, { type: "Str", lexeme: "resource", offset: 23 }, { type: "=~", offset: 32 }, { type: "RegexStr", lexeme: "/.*\\/functions\\/.*\\/[^\\/]+(\\/[^\\/]+.(ts|js|java|json|toml))?$/", offset: 35 }, { type: "&&", offset: 98 }, { type: "Str", lexeme: "resourceFilename", offset: 101 }, { type: "!=", offset: 118 }, { type: "Str", lexeme: "package.json", offset: 121 }, { type: "&&", offset: 134 }, { type: "Str", lexeme: "resourceFilename", offset: 137 }, { type: "!=", offset: 154 }, { type: "Str", lexeme: "package-lock.json", offset: 157 }, { type: "&&", offset: 175 }, { type: "Str", lexeme: "resourceFilename", offset: 178 }, { type: "!=", offset: 195 }, { type: "Str", lexeme: "tsconfig.json", offset: 198 }, { type: "EOF", offset: 211 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'sfdx:project_opened', offset: 0 }, { type: '&&', offset: 20 }, { type: 'Str', lexeme: 'resource', offset: 23 }, { type: '=~', offset: 32 }, { type: 'RegexStr', lexeme: '/.*\\/functions\\/.*\\/[^\\/]+(\\/[^\\/]+.(ts|js|java|json|toml))?$/', offset: 35 }, { type: '&&', offset: 98 }, { type: 'Str', lexeme: 'resourceFilename', offset: 101 }, { type: '!=', offset: 118 }, { type: 'Str', lexeme: 'package.json', offset: 121 }, { type: '&&', offset: 134 }, { type: 'Str', lexeme: 'resourceFilename', offset: 137 }, { type: '!=', offset: 154 }, { type: 'Str', lexeme: 'package-lock.json', offset: 157 }, { type: '&&', offset: 175 }, { type: 'Str', lexeme: 'resourceFilename', offset: 178 }, { type: '!=', offset: 195 }, { type: 'Str', lexeme: 'tsconfig.json', offset: 198 }, { type: 'EOF', offset: 211 }]));
     		});
     
     		test(`view =~ '/(servers)/' && viewItem =~ '/^(Starting|Started|Debugging|Stopping|Stopped)/'`, () => {
     			const input = `view =~ '/(servers)/' && viewItem =~ '/^(Starting|Started|Debugging|Stopping|Stopped)/'`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "view", offset: 0 }, { type: "=~", offset: 5 }, { type: "QuotedStr", lexeme: "/(servers)/", offset: 9 }, { type: "&&", offset: 22 }, { type: "Str", lexeme: "viewItem", offset: 25 }, { type: "=~", offset: 34 }, { type: "QuotedStr", lexeme: "/^(Starting|Started|Debugging|Stopping|Stopped)/", offset: 38 }, { type: "EOF", offset: 87 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'view', offset: 0 }, { type: '=~', offset: 5 }, { type: 'QuotedStr', lexeme: '/(servers)/', offset: 9 }, { type: '&&', offset: 22 }, { type: 'Str', lexeme: 'viewItem', offset: 25 }, { type: '=~', offset: 34 }, { type: 'QuotedStr', lexeme: '/^(Starting|Started|Debugging|Stopping|Stopped)/', offset: 38 }, { type: 'EOF', offset: 87 }]));
     		});
     
     		test(`resourcePath =~ /\.md(\.yml|\.txt)*$/gim`, () => {
     			const input = `resourcePath =~ /\.md(\.yml|\.txt)*$/gim`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "resourcePath" }, { type: "=~", offset: 13 }, { type: "RegexStr", offset: 16, lexeme: "/.md(.yml|.txt)*$/gim" }, { type: "EOF", offset: 37 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'resourcePath' }, { type: '=~', offset: 13 }, { type: 'RegexStr', offset: 16, lexeme: '/.md(.yml|.txt)*$/gim' }, { type: 'EOF', offset: 37 }]));
     		});
     	});
     
     	test(`foo === bar'`, () => {
     		const input = `foo === bar'`;
    -		assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "Str", offset: 8, lexeme: "bar" }, { type: "ErrorToken", offset: 11, lexeme: "'" }, { type: "EOF", offset: 12 }]));
    +		assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: '===', offset: 4 }, { type: 'Str', offset: 8, lexeme: 'bar' }, { type: 'ErrorToken', offset: 11, lexeme: `'` }, { type: 'EOF', offset: 12 }]));
     	});
     
     	suite('handling lexical errors', () => {
     
     		test(`foo === '`, () => {
     			const input = `foo === '`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "ErrorToken", offset: 8, lexeme: "'" }, { type: "EOF", offset: 9 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: '===', offset: 4 }, { type: 'ErrorToken', offset: 8, lexeme: `'` }, { type: 'EOF', offset: 9 }]));
     		});
     
     		test(`foo && 'bar - unterminated single quote`, () => {
     			const input = `foo && 'bar`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "&&", offset: 4 }, { type: "ErrorToken", offset: 7, lexeme: "'bar" }, { type: "EOF", offset: 11 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'foo', offset: 0 }, { type: '&&', offset: 4 }, { type: 'ErrorToken', offset: 7, lexeme: `'bar` }, { type: 'EOF', offset: 11 }]));
     		});
     
     		test('vim == 1 && vim<2 <= 3', () => {
     			const input = 'vim == 1 && vim<2 <= 3';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "vim", offset: 0 }, { type: "==", offset: 9 }, { type: "Str", lexeme: "1", offset: 12 }, { type: "&&", offset: 14 }, { type: "Str", lexeme: "vim<2", offset: 17 }, { type: "<=", offset: 23 }, { type: "Str", lexeme: "3", offset: 26 }, { type: "EOF", offset: 27 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', lexeme: 'vim', offset: 0 }, { type: '==', offset: 9 }, { type: 'Str', lexeme: '1', offset: 12 }, { type: '&&', offset: 14 }, { type: 'Str', lexeme: 'vim<2', offset: 17 }, { type: '<=', offset: 23 }, { type: 'Str', lexeme: '3', offset: 26 }, { type: 'EOF', offset: 27 }]));
     		});
     
     		test('vim==1 && vim<2<=3', () => {
     			const input = 'vim==1 && vim<2<=3';
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "vim" }, { type: "==", offset: 8 }, { type: "Str", offset: 10, lexeme: "1" }, { type: "&&", offset: 12 }, { type: "Str", offset: 15, lexeme: "vim<2<" }, { type: "ErrorToken", offset: 21, lexeme: "=" }, { type: "Str", offset: 22, lexeme: "3" }, { type: "EOF", offset: 23 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'vim' }, { type: '==', offset: 8 }, { type: 'Str', offset: 10, lexeme: '1' }, { type: '&&', offset: 12 }, { type: 'Str', offset: 15, lexeme: 'vim<2<' }, { type: 'ErrorToken', offset: 21, lexeme: '=' }, { type: 'Str', offset: 22, lexeme: '3' }, { type: 'EOF', offset: 23 }]));
     		});
     
     		test(`foo|bar`, () => {
     			const input = `foo|bar`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "ErrorToken", offset: 3, lexeme: "|" }, { type: "Str", offset: 4, lexeme: "bar" }, { type: "EOF", offset: 7 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: 'ErrorToken', offset: 3, lexeme: '|' }, { type: 'Str', offset: 4, lexeme: 'bar' }, { type: 'EOF', offset: 7 }]));
     		});
     
     		test(`resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`, () => {
     			const input = `resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "resource" }, { type: "=~", offset: 9 }, { type: "RegexStr", offset: 12, lexeme: "//" }, { type: "Str", offset: 14, lexeme: "foo/" }, { type: "(", offset: 18 }, { type: "Str", offset: 19, lexeme: "barr" }, { type: "ErrorToken", offset: 23, lexeme: "|" }, { type: "Str", offset: 24, lexeme: "door/" }, { type: "(", offset: 29 }, { type: "Str", offset: 30, lexeme: "Foo-Bar%20Templates" }, { type: "ErrorToken", offset: 49, lexeme: "|" }, { type: "Str", offset: 50, lexeme: "Soo%20Looo" }, { type: ")", offset: 60 }, { type: "ErrorToken", offset: 61, lexeme: "|" }, { type: "Str", offset: 62, lexeme: "Web%20Site%Jjj%20Llll" }, { type: ")", offset: 83 }, { type: "(", offset: 84 }, { type: "RegexStr", offset: 85, lexeme: "/.*)*$/" }, { type: "EOF", offset: 92 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'resource' }, { type: '=~', offset: 9 }, { type: 'RegexStr', offset: 12, lexeme: '//' }, { type: 'Str', offset: 14, lexeme: 'foo/' }, { type: '(', offset: 18 }, { type: 'Str', offset: 19, lexeme: 'barr' }, { type: 'ErrorToken', offset: 23, lexeme: '|' }, { type: 'Str', offset: 24, lexeme: 'door/' }, { type: '(', offset: 29 }, { type: 'Str', offset: 30, lexeme: 'Foo-Bar%20Templates' }, { type: 'ErrorToken', offset: 49, lexeme: '|' }, { type: 'Str', offset: 50, lexeme: 'Soo%20Looo' }, { type: ')', offset: 60 }, { type: 'ErrorToken', offset: 61, lexeme: '|' }, { type: 'Str', offset: 62, lexeme: 'Web%20Site%Jjj%20Llll' }, { type: ')', offset: 83 }, { type: '(', offset: 84 }, { type: 'RegexStr', offset: 85, lexeme: '/.*)*$/' }, { type: 'EOF', offset: 92 }]));
     		});
     
     		test(`/((/foo/(?!bar)(.*)/)|((/src/).*/)).*$/`, () => {
     			const input = `/((/foo/(?!bar)(.*)/)|((/src/).*/)).*$/`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "RegexStr", offset: 0, lexeme: "/((/" }, { type: "Str", offset: 4, lexeme: "foo/" }, { type: "(", offset: 8 }, { type: "Str", offset: 9, lexeme: "?" }, { type: "!", offset: 10 }, { type: "Str", offset: 11, lexeme: "bar" }, { type: ")", offset: 14 }, { type: "(", offset: 15 }, { type: "Str", offset: 16, lexeme: ".*" }, { type: ")", offset: 18 }, { type: "RegexStr", offset: 19, lexeme: "/)|((/s" }, { type: "Str", offset: 26, lexeme: "rc/" }, { type: ")", offset: 29 }, { type: "Str", offset: 30, lexeme: ".*/" }, { type: ")", offset: 33 }, { type: ")", offset: 34 }, { type: "Str", offset: 35, lexeme: ".*$/" }, { type: "EOF", offset: 39 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'RegexStr', offset: 0, lexeme: '/((/' }, { type: 'Str', offset: 4, lexeme: 'foo/' }, { type: '(', offset: 8 }, { type: 'Str', offset: 9, lexeme: '?' }, { type: '!', offset: 10 }, { type: 'Str', offset: 11, lexeme: 'bar' }, { type: ')', offset: 14 }, { type: '(', offset: 15 }, { type: 'Str', offset: 16, lexeme: '.*' }, { type: ')', offset: 18 }, { type: 'RegexStr', offset: 19, lexeme: '/)|((/s' }, { type: 'Str', offset: 26, lexeme: 'rc/' }, { type: ')', offset: 29 }, { type: 'Str', offset: 30, lexeme: '.*/' }, { type: ')', offset: 33 }, { type: ')', offset: 34 }, { type: 'Str', offset: 35, lexeme: '.*$/' }, { type: 'EOF', offset: 39 }]));
     		});
     
     		test(`resourcePath =~ //foo/barr// || resourcePath =~ //view/(jarrr|doooor|bees)/(web|templates)// && resourceExtname in foo.Bar`, () => {
     			const input = `resourcePath =~ //foo/barr// || resourcePath =~ //view/(jarrr|doooor|bees)/(web|templates)// && resourceExtname in foo.Bar`;
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "resourcePath" }, { type: "=~", offset: 13 }, { type: "RegexStr", offset: 16, lexeme: "//" }, { type: "Str", offset: 18, lexeme: "foo/barr//" }, { type: "||", offset: 29 }, { type: "Str", offset: 32, lexeme: "resourcePath" }, { type: "=~", offset: 45 }, { type: "RegexStr", offset: 48, lexeme: "//" }, { type: "Str", offset: 50, lexeme: "view/" }, { type: "(", offset: 55 }, { type: "Str", offset: 56, lexeme: "jarrr" }, { type: "ErrorToken", offset: 61, lexeme: "|" }, { type: "Str", offset: 62, lexeme: "doooor" }, { type: "ErrorToken", offset: 68, lexeme: "|" }, { type: "Str", offset: 69, lexeme: "bees" }, { type: ")", offset: 73 }, { type: "RegexStr", offset: 74, lexeme: "/(web|templates)/" }, { type: "ErrorToken", offset: 91, lexeme: "/ && resourceExtname in foo.Bar" }, { type: "EOF", offset: 122 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'resourcePath' }, { type: '=~', offset: 13 }, { type: 'RegexStr', offset: 16, lexeme: '//' }, { type: 'Str', offset: 18, lexeme: 'foo/barr//' }, { type: '||', offset: 29 }, { type: 'Str', offset: 32, lexeme: 'resourcePath' }, { type: '=~', offset: 45 }, { type: 'RegexStr', offset: 48, lexeme: '//' }, { type: 'Str', offset: 50, lexeme: 'view/' }, { type: '(', offset: 55 }, { type: 'Str', offset: 56, lexeme: 'jarrr' }, { type: 'ErrorToken', offset: 61, lexeme: '|' }, { type: 'Str', offset: 62, lexeme: 'doooor' }, { type: 'ErrorToken', offset: 68, lexeme: '|' }, { type: 'Str', offset: 69, lexeme: 'bees' }, { type: ')', offset: 73 }, { type: 'RegexStr', offset: 74, lexeme: '/(web|templates)/' }, { type: 'ErrorToken', offset: 91, lexeme: '/ && resourceExtname in foo.Bar' }, { type: 'EOF', offset: 122 }]));
     		});
     
     		test(`foo =~ /file:\// || bar`, () => {
     			const input = JSON.parse('"foo =~ /file:\// || bar"');
    -			assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "=~", offset: 4 }, { type: "RegexStr", offset: 7, lexeme: "/file:/" }, { type: "ErrorToken", offset: 14, lexeme: "/ || bar" }, { type: "EOF", offset: 22 }]));
    +			assert.deepStrictEqual(scan(input), ([{ type: 'Str', offset: 0, lexeme: 'foo' }, { type: '=~', offset: 4 }, { type: 'RegexStr', offset: 7, lexeme: '/file:/' }, { type: 'ErrorToken', offset: 14, lexeme: '/ || bar' }, { type: 'EOF', offset: 22 }]));
     		});
     	});
     });
    diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts
    index 0887aad785a..b5d53d754f8 100644
    --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts
    +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts
    @@ -235,7 +235,7 @@ suite('AbstractKeybindingService', () => {
     			currentContextValue = createContext({});
     			const shouldPreventDefault = kbService.testDispatch(key);
     			assert.deepStrictEqual(shouldPreventDefault, true);
    -			assert.deepStrictEqual(executeCommandCalls, ([{ commandId: "myCommand", args: [null] }]));
    +			assert.deepStrictEqual(executeCommandCalls, ([{ commandId: 'myCommand', args: [null] }]));
     			assert.deepStrictEqual(showMessageCalls, []);
     			assert.deepStrictEqual(statusMessageCalls, []);
     			assert.deepStrictEqual(statusMessageCallsDisposed, []);
    @@ -263,7 +263,7 @@ suite('AbstractKeybindingService', () => {
     
     			shouldPreventDefault = kbService.testDispatch(chord1);
     			assert.deepStrictEqual(shouldPreventDefault, true);
    -			assert.deepStrictEqual(executeCommandCalls, ([{ commandId: "myCommand", args: [null] }]));
    +			assert.deepStrictEqual(executeCommandCalls, ([{ commandId: 'myCommand', args: [null] }]));
     			assert.deepStrictEqual(showMessageCalls, []);
     			assert.deepStrictEqual(statusMessageCalls, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
     			assert.deepStrictEqual(statusMessageCallsDisposed, ([`(${toUsLabel(chord0)}) was pressed. Waiting for second key of chord...`]));
    diff --git a/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/src/vs/workbench/api/test/browser/extHostTypes.test.ts
    index 389e49c3c32..105b13734bc 100644
    --- a/src/vs/workbench/api/test/browser/extHostTypes.test.ts
    +++ b/src/vs/workbench/api/test/browser/extHostTypes.test.ts
    @@ -587,26 +587,26 @@ suite('ExtHostTypes', function () {
     	test('Snippet choices are incorrectly escaped/applied #180132', function () {
     		{
     			const s = new types.SnippetString();
    -			s.appendChoice(["aaa$aaa"]);
    -			s.appendText("bbb$bbb");
    +			s.appendChoice(['aaa$aaa']);
    +			s.appendText('bbb$bbb');
     			assert.strictEqual(s.value, '${1|aaa$aaa|}bbb\\$bbb');
     		}
     		{
     			const s = new types.SnippetString();
    -			s.appendChoice(["aaa,aaa"]);
    -			s.appendText("bbb$bbb");
    +			s.appendChoice(['aaa,aaa']);
    +			s.appendText('bbb$bbb');
     			assert.strictEqual(s.value, '${1|aaa\\,aaa|}bbb\\$bbb');
     		}
     		{
     			const s = new types.SnippetString();
    -			s.appendChoice(["aaa|aaa"]);
    -			s.appendText("bbb$bbb");
    +			s.appendChoice(['aaa|aaa']);
    +			s.appendText('bbb$bbb');
     			assert.strictEqual(s.value, '${1|aaa\\|aaa|}bbb\\$bbb');
     		}
     		{
     			const s = new types.SnippetString();
    -			s.appendChoice(["aaa\\aaa"]);
    -			s.appendText("bbb$bbb");
    +			s.appendChoice(['aaa\\aaa']);
    +			s.appendText('bbb$bbb');
     			assert.strictEqual(s.value, '${1|aaa\\\\aaa|}bbb\\$bbb');
     		}
     	});
    diff --git a/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts b/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts
    index ddb4b947a5d..9f95ae803d1 100644
    --- a/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts
    +++ b/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts
    @@ -101,7 +101,7 @@ suite('MainThreadManagedSockets', () => {
     
     			socket.write(VSBuffer.fromString('ping'));
     			await extHost.expectEvent(evt => evt.data === 'ping', 'expected ping');
    -			half.onData.fire(VSBuffer.fromString("pong"));
    +			half.onData.fire(VSBuffer.fromString('pong'));
     			assert.deepStrictEqual(data, ['pong']);
     		});
     	});
    diff --git a/src/vs/workbench/api/test/common/extensionHostMain.test.ts b/src/vs/workbench/api/test/common/extensionHostMain.test.ts
    index 8113e226fb0..4b334c24121 100644
    --- a/src/vs/workbench/api/test/common/extensionHostMain.test.ts
    +++ b/src/vs/workbench/api/test/common/extensionHostMain.test.ts
    @@ -191,7 +191,7 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo
     
     	suite('https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce', function () {
     
    -		test("Restored, separate operations", () => {
    +		test('Restored, separate operations', () => {
     			// Actual Test
     			let original;
     
    @@ -229,7 +229,7 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo
     			assert.strictEqual(findSubstrCount, 4);
     		});
     
    -		test("Never restored, separate operations", () => {
    +		test('Never restored, separate operations', () => {
     			// Operation 1
     			for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
     			assert.ok(new Error().stack);
    @@ -247,7 +247,7 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo
     			assert.ok(new Error().stack);
     		});
     
    -		test("Restored, too many uses before restoration", async () => {
    +		test('Restored, too many uses before restoration', async () => {
     			const original = Error.prepareStackTrace;
     			Error.prepareStackTrace = (_, stack) => stack;
     
    diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
    index 29606524fc6..eec21b24f83 100644
    --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
    +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts
    @@ -172,7 +172,7 @@ suite('LanguageModelToolsService', () => {
     		/** User Tool Set with tool1 */
     
     		const userToolSet = store.add(service.createToolSet(
    -			{ type: 'user', label: "User", file: URI.file('/test/userToolSet.json') },
    +			{ type: 'user', label: 'User', file: URI.file('/test/userToolSet.json') },
     			'userToolSet',
     			'userToolSetRefName',
     			{ description: 'Test Set' }
    @@ -181,7 +181,7 @@ suite('LanguageModelToolsService', () => {
     
     		/** MCP tool in a MCP tool set */
     
    -		const mcpDataSource: ToolDataSource = { type: 'mcp', label: 'My MCP Server', serverLabel: "MCP Server", instructions: undefined, collectionId: 'testMCPCollection', definitionId: 'testMCPDefId' };
    +		const mcpDataSource: ToolDataSource = { type: 'mcp', label: 'My MCP Server', serverLabel: 'MCP Server', instructions: undefined, collectionId: 'testMCPCollection', definitionId: 'testMCPDefId' };
     		const mcpTool1: IToolData = {
     			id: 'mcpTool1',
     			toolReferenceName: 'mcpTool1RefName',
    @@ -640,7 +640,7 @@ suite('LanguageModelToolsService', () => {
     			toolReferenceName: 'refTool1',
     			modelDescription: 'Test Tool 1',
     			displayName: 'Test Tool 1',
    -			source: { type: 'extension', label: "My Extension", extensionId: new ExtensionIdentifier('My.extension') },
    +			source: { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('My.extension') },
     			canBeReferencedInPrompt: true,
     		};
     
    diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts
    index 0791ec0a449..65b54be3b98 100644
    --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts
    +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts
    @@ -55,7 +55,7 @@ suite('PromptValidator', () => {
     
     		const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData;
     		const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData;
    -		const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: { type: 'extension', label: "My Extension", extensionId: new ExtensionIdentifier('My.extension') }, inputSchema: {} } satisfies IToolData;
    +		const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('My.extension') }, inputSchema: {} } satisfies IToolData;
     
     		disposables.add(toolService.registerToolData(testTool1));
     		disposables.add(toolService.registerToolData(testTool2));
    @@ -104,13 +104,13 @@ suite('PromptValidator', () => {
     
     		test('correct mode', async () => {
     			const content = [
    -			/* 01 */"---",
    +			/* 01 */'---',
     			/* 02 */`description: "Agent mode test"`,
    -			/* 03 */"model: MAE 4.1",
    -			/* 04 */"tools: ['tool1', 'tool2']",
    -			/* 05 */"---",
    -			/* 06 */"This is a chat mode test.",
    -			/* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).",
    +			/* 03 */'model: MAE 4.1',
    +			/* 04 */`tools: ['tool1', 'tool2']`,
    +			/* 05 */'---',
    +			/* 06 */'This is a chat mode test.',
    +			/* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.deepStrictEqual(markers, []);
    @@ -118,79 +118,79 @@ suite('PromptValidator', () => {
     
     		test('mode with errors (empty description, unknown tool & model)', async () => {
     			const content = [
    -			/* 01 */"---",
    +			/* 01 */'---',
     			/* 02 */`description: ""`, // empty description -> error
    -			/* 03 */"model: MAE 4.2", // unknown model -> warning
    -			/* 04 */"tools: ['tool1', 'tool2', 'tool4', 'my.extension/tool3']", // tool4 unknown -> error
    -			/* 05 */"---",
    -			/* 06 */"Body",
    +			/* 03 */'model: MAE 4.2', // unknown model -> warning
    +			/* 04 */`tools: ['tool1', 'tool2', 'tool4', 'my.extension/tool3']`, // tool4 unknown -> error
    +			/* 05 */'---',
    +			/* 06 */'Body',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.deepStrictEqual(
     				markers.map(m => ({ severity: m.severity, message: m.message })),
     				[
    -					{ severity: MarkerSeverity.Error, message: "The 'description' attribute should not be empty." },
    -					{ severity: MarkerSeverity.Warning, message: "Unknown tool 'tool4'." },
    -					{ severity: MarkerSeverity.Warning, message: "Unknown model 'MAE 4.2'." },
    +					{ severity: MarkerSeverity.Error, message: `The 'description' attribute should not be empty.` },
    +					{ severity: MarkerSeverity.Warning, message: `Unknown tool 'tool4'.` },
    +					{ severity: MarkerSeverity.Warning, message: `Unknown model 'MAE 4.2'.` },
     				]
     			);
     		});
     
     		test('tools must be array', async () => {
     			const content = [
    -				"---",
    -				"description: \"Test\"",
    -				"tools: 'tool1'",
    -				"---",
    +				'---',
    +				'description: "Test"',
    +				`tools: 'tool1'`,
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.strictEqual(markers.length, 1);
    -			assert.deepStrictEqual(markers.map(m => m.message), ["The 'tools' attribute must be an array."]);
    +			assert.deepStrictEqual(markers.map(m => m.message), [`The 'tools' attribute must be an array.`]);
     		});
     
     		test('each tool must be string', async () => {
     			const content = [
    -				"---",
    -				"description: \"Test\"",
    -				"tools: ['tool1', 2]",
    -				"---",
    +				'---',
    +				'description: "Test"',
    +				`tools: ['tool1', 2]`,
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.deepStrictEqual(
     				markers.map(m => ({ severity: m.severity, message: m.message })),
     				[
    -					{ severity: MarkerSeverity.Error, message: "Each tool name in the 'tools' attribute must be a string." },
    +					{ severity: MarkerSeverity.Error, message: `Each tool name in the 'tools' attribute must be a string.` },
     				]
     			);
     		});
     
     		test('old tool reference', async () => {
     			const content = [
    -				"---",
    -				"description: \"Test\"",
    -				"tools: ['tool1', 'tool3']",
    -				"---",
    +				'---',
    +				'description: "Test"',
    +				`tools: ['tool1', 'tool3']`,
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.deepStrictEqual(
     				markers.map(m => ({ severity: m.severity, message: m.message })),
     				[
    -					{ severity: MarkerSeverity.Info, message: "Tool or toolset 'tool3' has been renamed, use 'my.extension/tool3' instead." },
    +					{ severity: MarkerSeverity.Info, message: `Tool or toolset 'tool3' has been renamed, use 'my.extension/tool3' instead.` },
     				]
     			);
     		});
     
     		test('unknown attribute in mode file', async () => {
     			const content = [
    -				"---",
    -				"description: \"Test\"",
    -				"applyTo: '*.ts'", // not allowed in mode file
    -				"---",
    +				'---',
    +				'description: "Test"',
    +				`applyTo: '*.ts'`, // not allowed in mode file
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.mode);
     			assert.strictEqual(markers.length, 1);
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.ok(markers[0].message.startsWith("Attribute 'applyTo' is not supported in mode files."));
    +			assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in mode files.`));
     		});
     	});
     
    @@ -198,10 +198,10 @@ suite('PromptValidator', () => {
     
     		test('instructions valid', async () => {
     			const content = [
    -				"---",
    -				"description: \"Instr\"",
    -				"applyTo: *.ts,*.js",
    -				"---",
    +				'---',
    +				'description: "Instr"',
    +				'applyTo: *.ts,*.js',
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.instructions);
     			assert.deepEqual(markers, []);
    @@ -209,38 +209,38 @@ suite('PromptValidator', () => {
     
     		test('instructions invalid applyTo type', async () => {
     			const content = [
    -				"---",
    -				"description: \"Instr\"",
    -				"applyTo: 5",
    -				"---",
    +				'---',
    +				'description: "Instr"',
    +				'applyTo: 5',
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.instructions);
     			assert.strictEqual(markers.length, 1);
    -			assert.strictEqual(markers[0].message, "The 'applyTo' attribute must be a string.");
    +			assert.strictEqual(markers[0].message, `The 'applyTo' attribute must be a string.`);
     		});
     
     		test('instructions invalid applyTo glob & unknown attribute', async () => {
     			const content = [
    -				"---",
    -				"description: \"Instr\"",
    -				"applyTo: ''", // empty -> invalid glob
    -				"model: mae-4", // model not allowed in instructions
    -				"---",
    +				'---',
    +				'description: "Instr"',
    +				`applyTo: ''`, // empty -> invalid glob
    +				'model: mae-4', // model not allowed in instructions
    +				'---',
     			].join('\n');
     			const markers = await validate(content, PromptsType.instructions);
     			assert.strictEqual(markers.length, 2);
     			// Order: unknown attribute warnings first (attribute iteration) then applyTo validation
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.ok(markers[0].message.startsWith("Attribute 'model' is not supported in instructions files."));
    -			assert.strictEqual(markers[1].message, "The 'applyTo' attribute must be a valid glob pattern.");
    +			assert.ok(markers[0].message.startsWith(`Attribute 'model' is not supported in instructions files.`));
    +			assert.strictEqual(markers[1].message, `The 'applyTo' attribute must be a valid glob pattern.`);
     		});
     
     		test('invalid header structure (YAML array)', async () => {
     			const content = [
    -				"---",
    -				"- item1",
    -				"---",
    -				"Body",
    +				'---',
    +				'- item1',
    +				'---',
    +				'Body',
     			].join('\n');
     			const markers = await validate(content, PromptsType.instructions);
     			assert.strictEqual(markers.length, 1);
    @@ -255,8 +255,8 @@ suite('PromptValidator', () => {
     			const content = [
     				'---',
     				'description: "Prompt with tools"',
    -				"model: MAE 4.1",
    -				"tools: ['tool1','tool2']",
    +				'model: MAE 4.1',
    +				`tools: ['tool1','tool2']`,
     				'---',
     				'Body'
     			].join('\n');
    @@ -269,14 +269,14 @@ suite('PromptValidator', () => {
     			const content = [
     				'---',
     				'description: "Prompt with unsuitable model"',
    -				"model: MAE 3.5 Turbo",
    +				'model: MAE 3.5 Turbo',
     				'---',
     				'Body'
     			].join('\n');
     			const markers = await validate(content, PromptsType.prompt);
     			assert.strictEqual(markers.length, 1, 'Expected one warning about unsuitable model');
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.strictEqual(markers[0].message, "Model 'MAE 3.5 Turbo' is not suited for agent mode.");
    +			assert.strictEqual(markers[0].message, `Model 'MAE 3.5 Turbo' is not suited for agent mode.`);
     		});
     
     		test('prompt with custom mode BeastMode and tools', async () => {
    @@ -285,7 +285,7 @@ suite('PromptValidator', () => {
     				'---',
     				'description: "Prompt custom mode"',
     				'mode: BeastMode',
    -				"tools: ['tool1']",
    +				`tools: ['tool1']`,
     				'---',
     				'Body'
     			].join('\n');
    @@ -298,14 +298,14 @@ suite('PromptValidator', () => {
     				'---',
     				'description: "Prompt unknown mode Ask"',
     				'mode: Ask',
    -				"tools: ['tool1','tool2']",
    +				`tools: ['tool1','tool2']`,
     				'---',
     				'Body'
     			].join('\n');
     			const markers = await validate(content, PromptsType.prompt);
     			assert.strictEqual(markers.length, 1, 'Expected one warning about tools in non-agent mode');
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.strictEqual(markers[0].message, "Unknown mode 'Ask'. Available modes: agent, ask, edit, BeastMode.");
    +			assert.strictEqual(markers[0].message, `Unknown mode 'Ask'. Available modes: agent, ask, edit, BeastMode.`);
     		});
     
     		test('prompt with mode edit', async () => {
    @@ -313,14 +313,14 @@ suite('PromptValidator', () => {
     				'---',
     				'description: "Prompt edit mode with tool"',
     				'mode: edit',
    -				"tools: ['tool1']",
    +				`tools: ['tool1']`,
     				'---',
     				'Body'
     			].join('\n');
     			const markers = await validate(content, PromptsType.prompt);
     			assert.strictEqual(markers.length, 1);
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.strictEqual(markers[0].message, "The 'tools' attribute is only supported in agent mode. Attribute will be ignored.");
    +			assert.strictEqual(markers[0].message, `The 'tools' attribute is only supported in agent mode. Attribute will be ignored.`);
     		});
     	});
     
    @@ -346,8 +346,8 @@ suite('PromptValidator', () => {
     			const markers = await validate(content, PromptsType.prompt);
     			const messages = markers.map(m => m.message).sort();
     			assert.deepStrictEqual(messages, [
    -				"File './missing1.md' not found at '/missing1.md'.",
    -				"File './missing2.md' not found at '/missing2.md'."
    +				`File './missing1.md' not found at '/missing1.md'.`,
    +				`File './missing2.md' not found at '/missing2.md'.`
     			]);
     		});
     
    @@ -374,7 +374,7 @@ suite('PromptValidator', () => {
     			const markers = await validate(content, PromptsType.prompt);
     			const messages = markers.map(m => m.message).sort();
     			assert.deepStrictEqual(messages, [
    -				"File 'myFs://test/nonexisting' not found at '/nonexisting'.",
    +				`File 'myFs://test/nonexisting' not found at '/nonexisting'.`,
     			]);
     		});
     
    @@ -388,7 +388,7 @@ suite('PromptValidator', () => {
     			const markers = await validate(content, PromptsType.prompt);
     			assert.strictEqual(markers.length, 1, 'Expected one warning for unknown tool variable');
     			assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
    -			assert.strictEqual(markers[0].message, "Unknown tool or toolset 'toolX'.");
    +			assert.strictEqual(markers[0].message, `Unknown tool or toolset 'toolX'.`);
     		});
     
     	});
    diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts
    index 89dac4ac4db..8a91264ae25 100644
    --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts
    +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts
    @@ -16,13 +16,13 @@ suite('NewPromptsParser', () => {
     	test('mode', async () => {
     		const uri = URI.parse('file:///test/chatmode.md');
     		const content = [
    -			/* 01 */"---",
    +			/* 01 */'---',
     			/* 02 */`description: "Agent mode test"`,
    -			/* 03 */"model: GPT 4.1",
    -			/* 04 */"tools: ['tool1', 'tool2']",
    -			/* 05 */"---",
    -			/* 06 */"This is a chat mode test.",
    -			/* 07 */"Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).",
    +			/* 03 */'model: GPT 4.1',
    +			/* 04 */`tools: ['tool1', 'tool2']`,
    +			/* 05 */'---',
    +			/* 06 */'This is a chat mode test.',
    +			/* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).',
     		].join('\n');
     		const result = new NewPromptsParser().parse(uri, content);
     		assert.deepEqual(result.uri, uri);
    @@ -60,11 +60,11 @@ suite('NewPromptsParser', () => {
     	test('instructions', async () => {
     		const uri = URI.parse('file:///test/prompt1.md');
     		const content = [
    -			/* 01 */"---",
    +			/* 01 */'---',
     			/* 02 */`description: "Code style instructions for TypeScript"`,
    -			/* 03 */"applyTo: *.ts",
    -			/* 04 */"---",
    -			/* 05 */"Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)",
    +			/* 03 */'applyTo: *.ts',
    +			/* 04 */'---',
    +			/* 05 */'Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)',
     		].join('\n');
     		const result = new NewPromptsParser().parse(uri, content);
     		assert.deepEqual(result.uri, uri);
    @@ -90,13 +90,13 @@ suite('NewPromptsParser', () => {
     	test('prompt file', async () => {
     		const uri = URI.parse('file:///test/prompt2.md');
     		const content = [
    -			/* 01 */"---",
    +			/* 01 */'---',
     			/* 02 */`description: "General purpose coding assistant"`,
    -			/* 03 */"mode: agent",
    -			/* 04 */"model: GPT 4.1",
    -			/* 05 */"tools: ['search', 'terminal']",
    -			/* 06 */"---",
    -			/* 07 */"This is a prompt file body referencing #search and [docs](https://example.com/docs).",
    +			/* 03 */'mode: agent',
    +			/* 04 */'model: GPT 4.1',
    +			/* 05 */`tools: ['search', 'terminal']`,
    +			/* 06 */'---',
    +			/* 07 */'This is a prompt file body referencing #search and [docs](https://example.com/docs).',
     		].join('\n');
     		const result = new NewPromptsParser().parse(uri, content);
     		assert.deepEqual(result.uri, uri);
    @@ -134,17 +134,17 @@ suite('NewPromptsParser', () => {
     	test('prompt file tools as map', async () => {
     		const uri = URI.parse('file:///test/prompt2.md');
     		const content = [
    -			/* 01 */"---",
    -			/* 02 */"tools:",
    -			/* 03 */"  built-in: true",
    -			/* 04 */"  mcp:",
    -			/* 05 */"    vscode-playright-mcp:",
    -			/* 06 */"      browser-click: true",
    -			/* 07 */"  extensions:",
    -			/* 08 */"    github.vscode-pull-request-github:",
    -			/* 09 */"      openPullRequest: true",
    -			/* 10 */"      copilotCodingAgent: false",
    -			/* 11 */"---",
    +			/* 01 */'---',
    +			/* 02 */'tools:',
    +			/* 03 */'  built-in: true',
    +			/* 04 */'  mcp:',
    +			/* 05 */'    vscode-playright-mcp:',
    +			/* 06 */'      browser-click: true',
    +			/* 07 */'  extensions:',
    +			/* 08 */'    github.vscode-pull-request-github:',
    +			/* 09 */'      openPullRequest: true',
    +			/* 10 */'      copilotCodingAgent: false',
    +			/* 11 */'---',
     		].join('\n');
     		const result = new NewPromptsParser().parse(uri, content);
     		assert.deepEqual(result.uri, uri);
    @@ -157,17 +157,17 @@ suite('NewPromptsParser', () => {
     					type: 'object',
     					properties: [
     						{
    -							"key": { type: 'string', value: 'built-in', range: new Range(3, 3, 3, 11) },
    -							"value": { type: 'boolean', value: true, range: new Range(3, 13, 3, 17) }
    +							'key': { type: 'string', value: 'built-in', range: new Range(3, 3, 3, 11) },
    +							'value': { type: 'boolean', value: true, range: new Range(3, 13, 3, 17) }
     						},
     						{
    -							"key": { type: 'string', value: 'mcp', range: new Range(4, 3, 4, 6) },
    -							"value": {
    +							'key': { type: 'string', value: 'mcp', range: new Range(4, 3, 4, 6) },
    +							'value': {
     								type: 'object', range: new Range(5, 5, 6, 26), properties: [
     									{
    -										"key": { type: 'string', value: 'vscode-playright-mcp', range: new Range(5, 5, 5, 25) }, "value": {
    +										'key': { type: 'string', value: 'vscode-playright-mcp', range: new Range(5, 5, 5, 25) }, 'value': {
     											type: 'object', range: new Range(6, 7, 6, 26), properties: [
    -												{ "key": { type: 'string', value: 'browser-click', range: new Range(6, 7, 6, 20) }, "value": { type: 'boolean', value: true, range: new Range(6, 22, 6, 26) } }
    +												{ 'key': { type: 'string', value: 'browser-click', range: new Range(6, 7, 6, 20) }, 'value': { type: 'boolean', value: true, range: new Range(6, 22, 6, 26) } }
     											]
     										}
     									}
    @@ -175,14 +175,14 @@ suite('NewPromptsParser', () => {
     							}
     						},
     						{
    -							"key": { type: 'string', value: 'extensions', range: new Range(7, 3, 7, 13) },
    -							"value": {
    +							'key': { type: 'string', value: 'extensions', range: new Range(7, 3, 7, 13) },
    +							'value': {
     								type: 'object', range: new Range(8, 5, 10, 32), properties: [
     									{
    -										"key": { type: 'string', value: 'github.vscode-pull-request-github', range: new Range(8, 5, 8, 38) }, "value": {
    +										'key': { type: 'string', value: 'github.vscode-pull-request-github', range: new Range(8, 5, 8, 38) }, 'value': {
     											type: 'object', range: new Range(9, 7, 10, 32), properties: [
    -												{ "key": { type: 'string', value: 'openPullRequest', range: new Range(9, 7, 9, 22) }, "value": { type: 'boolean', value: true, range: new Range(9, 24, 9, 28) } },
    -												{ "key": { type: 'string', value: 'copilotCodingAgent', range: new Range(10, 7, 10, 25) }, "value": { type: 'boolean', value: false, range: new Range(10, 27, 10, 32) } }
    +												{ 'key': { type: 'string', value: 'openPullRequest', range: new Range(9, 7, 9, 22) }, 'value': { type: 'boolean', value: true, range: new Range(9, 24, 9, 28) } },
    +												{ 'key': { type: 'string', value: 'copilotCodingAgent', range: new Range(10, 7, 10, 25) }, 'value': { type: 'boolean', value: false, range: new Range(10, 27, 10, 32) } }
     											]
     										}
     									}
    diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts
    index ac66856fcbc..f1b6d0504dd 100644
    --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts
    +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts
    @@ -230,8 +230,8 @@ suite('PromptsService', () => {
     			assert.deepEqual(
     				result1.body.variableReferences,
     				[
    -					{ name: "my-tool", range: new Range(10, 5, 10, 12), offset: 239 },
    -					{ name: "my-other-tool", range: new Range(11, 5, 11, 18), offset: 251 },
    +					{ name: 'my-tool', range: new Range(10, 5, 10, 12), offset: 239 },
    +					{ name: 'my-other-tool', range: new Range(11, 5, 11, 18), offset: 251 },
     				]
     			);
     
    @@ -821,8 +821,8 @@ suite('PromptsService', () => {
     			const uri = URI.parse('file://extensions/my-extension/textMate.instructions.md');
     			const extension = {} as IExtensionDescription;
     			const registered = service.registerContributedFile(PromptsType.instructions,
    -				"TextMate Instructions",
    -				"Instructions to follow when authoring TextMate grammars",
    +				'TextMate Instructions',
    +				'Instructions to follow when authoring TextMate grammars',
     				uri,
     				extension
     			);
    @@ -830,7 +830,7 @@ suite('PromptsService', () => {
     			const actual = await service.listPromptFiles(PromptsType.instructions, CancellationToken.None);
     			assert.strictEqual(actual.length, 1);
     			assert.strictEqual(actual[0].uri.toString(), uri.toString());
    -			assert.strictEqual(actual[0].name, "TextMate Instructions");
    +			assert.strictEqual(actual[0].name, 'TextMate Instructions');
     			assert.strictEqual(actual[0].storage, PromptsStorage.extension);
     			assert.strictEqual(actual[0].type, PromptsType.instructions);
     			registered.dispose();
    diff --git a/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts b/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts
    index 4888d9f5db6..cc144be8284 100644
    --- a/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts
    +++ b/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts
    @@ -235,10 +235,10 @@ suite('Auto-Reindentation - TypeScript/JavaScript', () => {
     		assert.deepStrictEqual(editOperations.length, 1);
     		const operation = editOperations[0];
     		assert.deepStrictEqual(getIRange(operation.range), {
    -			"startLineNumber": 2,
    -			"startColumn": 1,
    -			"endLineNumber": 2,
    -			"endColumn": 5,
    +			'startLineNumber': 2,
    +			'startColumn': 1,
    +			'endLineNumber': 2,
    +			'endColumn': 5,
     		});
     		assert.deepStrictEqual(operation.text, '');
     	});
    @@ -263,10 +263,10 @@ suite('Auto-Reindentation - TypeScript/JavaScript', () => {
     		assert.deepStrictEqual(editOperations.length, 1);
     		let operation = editOperations[0];
     		assert.deepStrictEqual(getIRange(operation.range), {
    -			"startLineNumber": 3,
    -			"startColumn": 1,
    -			"endLineNumber": 3,
    -			"endColumn": 5,
    +			'startLineNumber': 3,
    +			'startColumn': 1,
    +			'endLineNumber': 3,
    +			'endColumn': 5,
     		});
     		assert.deepStrictEqual(operation.text, '');
     
    @@ -280,10 +280,10 @@ suite('Auto-Reindentation - TypeScript/JavaScript', () => {
     		assert.deepStrictEqual(editOperations.length, 1);
     		operation = editOperations[0];
     		assert.deepStrictEqual(getIRange(operation.range), {
    -			"startLineNumber": 2,
    -			"startColumn": 1,
    -			"endLineNumber": 2,
    -			"endColumn": 1,
    +			'startLineNumber': 2,
    +			'startColumn': 1,
    +			'endLineNumber': 2,
    +			'endColumn': 1,
     		});
     		assert.deepStrictEqual(operation.text, '    ');
     	});
    @@ -307,10 +307,10 @@ suite('Auto-Reindentation - TypeScript/JavaScript', () => {
     		assert.deepStrictEqual(editOperations.length, 1);
     		const operation = editOperations[0];
     		assert.deepStrictEqual(getIRange(operation.range), {
    -			"startLineNumber": 2,
    -			"startColumn": 1,
    -			"endLineNumber": 2,
    -			"endColumn": 1,
    +			'startLineNumber': 2,
    +			'startColumn': 1,
    +			'endLineNumber': 2,
    +			'endColumn': 1,
     		});
     		assert.deepStrictEqual(operation.text, '    ');
     	});
    @@ -354,10 +354,10 @@ suite('Auto-Reindentation - TypeScript/JavaScript', () => {
     		assert.deepStrictEqual(editOperations.length, 1);
     		const operation = editOperations[0];
     		assert.deepStrictEqual(getIRange(operation.range), {
    -			"startLineNumber": 2,
    -			"startColumn": 1,
    -			"endLineNumber": 2,
    -			"endColumn": 4,
    +			'startLineNumber': 2,
    +			'startColumn': 1,
    +			'endLineNumber': 2,
    +			'endColumn': 4,
     		});
     		assert.deepStrictEqual(operation.text, '');
     	});
    diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts
    index 9122412e421..d5eab305fdc 100644
    --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts
    +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts
    @@ -31,7 +31,7 @@ suite('Debug - Base Debug View', () => {
     	let renderer: DebugExpressionRenderer;
     	let configurationService: TestConfigurationService;
     
    -	function assertVariable(session: MockSession, scope: Scope, disposables: Pick, displayType: boolean) {
    +	function assertVariable(session: MockSession, scope: Scope, disposables: Pick, displayType: boolean) {
     		let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string');
     		let expression = $('.');
     		let name = $('.');
    diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts
    index 0ada6c2ae35..c53b662bf17 100644
    --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts
    +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts
    @@ -216,16 +216,16 @@ suite('Debug - Breakpoints', () => {
     	test('exception breakpoints', () => {
     		let eventCount = 0;
     		disposables.add(model.onDidChangeBreakpoints(() => eventCount++));
    -		model.setExceptionBreakpointsForSession("session-id-1", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]);
    +		model.setExceptionBreakpointsForSession('session-id-1', [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]);
     		assert.strictEqual(eventCount, 1);
    -		let exceptionBreakpoints = model.getExceptionBreakpointsForSession("session-id-1");
    +		let exceptionBreakpoints = model.getExceptionBreakpointsForSession('session-id-1');
     		assert.strictEqual(exceptionBreakpoints.length, 1);
     		assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpoints[0].enabled, true);
     
    -		model.setExceptionBreakpointsForSession("session-id-2", [{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]);
    +		model.setExceptionBreakpointsForSession('session-id-2', [{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]);
     		assert.strictEqual(eventCount, 2);
    -		exceptionBreakpoints = model.getExceptionBreakpointsForSession("session-id-2");
    +		exceptionBreakpoints = model.getExceptionBreakpointsForSession('session-id-2');
     		assert.strictEqual(exceptionBreakpoints.length, 2);
     		assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpoints[0].enabled, true);
    @@ -233,9 +233,9 @@ suite('Debug - Breakpoints', () => {
     		assert.strictEqual(exceptionBreakpoints[1].label, 'CAUGHT');
     		assert.strictEqual(exceptionBreakpoints[1].enabled, false);
     
    -		model.setExceptionBreakpointsForSession("session-id-3", [{ filter: 'all', label: 'ALL' }]);
    +		model.setExceptionBreakpointsForSession('session-id-3', [{ filter: 'all', label: 'ALL' }]);
     		assert.strictEqual(eventCount, 3);
    -		assert.strictEqual(model.getExceptionBreakpointsForSession("session-id-3").length, 1);
    +		assert.strictEqual(model.getExceptionBreakpointsForSession('session-id-3').length, 1);
     		exceptionBreakpoints = model.getExceptionBreakpoints();
     		assert.strictEqual(exceptionBreakpoints[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpoints[0].enabled, true);
    @@ -250,17 +250,17 @@ suite('Debug - Breakpoints', () => {
     		let eventCount = 0;
     		disposables.add(model.onDidChangeBreakpoints(() => eventCount++));
     
    -		model.setExceptionBreakpointsForSession("session-id-4", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }, { filter: 'caught', label: 'CAUGHT' }]);
    -		model.setExceptionBreakpointFallbackSession("session-id-4");
    +		model.setExceptionBreakpointsForSession('session-id-4', [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }, { filter: 'caught', label: 'CAUGHT' }]);
    +		model.setExceptionBreakpointFallbackSession('session-id-4');
     		assert.strictEqual(eventCount, 1);
    -		let exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession("session-id-4");
    +		let exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession('session-id-4');
     		assert.strictEqual(exceptionBreakpointsForSession.length, 2);
     		assert.strictEqual(exceptionBreakpointsForSession[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpointsForSession[1].filter, 'caught');
     
    -		model.setExceptionBreakpointsForSession("session-id-5", [{ filter: 'all', label: 'ALL' }, { filter: 'caught', label: 'CAUGHT' }]);
    +		model.setExceptionBreakpointsForSession('session-id-5', [{ filter: 'all', label: 'ALL' }, { filter: 'caught', label: 'CAUGHT' }]);
     		assert.strictEqual(eventCount, 2);
    -		exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession("session-id-5");
    +		exceptionBreakpointsForSession = model.getExceptionBreakpointsForSession('session-id-5');
     		let exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined);
     		assert.strictEqual(exceptionBreakpointsForSession.length, 2);
     		assert.strictEqual(exceptionBreakpointsForSession[0].filter, 'caught');
    @@ -269,14 +269,14 @@ suite('Debug - Breakpoints', () => {
     		assert.strictEqual(exceptionBreakpointsForUndefined[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpointsForUndefined[1].filter, 'caught');
     
    -		model.removeExceptionBreakpointsForSession("session-id-4");
    +		model.removeExceptionBreakpointsForSession('session-id-4');
     		assert.strictEqual(eventCount, 2);
     		exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined);
     		assert.strictEqual(exceptionBreakpointsForUndefined.length, 2);
     		assert.strictEqual(exceptionBreakpointsForUndefined[0].filter, 'uncaught');
     		assert.strictEqual(exceptionBreakpointsForUndefined[1].filter, 'caught');
     
    -		model.setExceptionBreakpointFallbackSession("session-id-5");
    +		model.setExceptionBreakpointFallbackSession('session-id-5');
     		assert.strictEqual(eventCount, 2);
     		exceptionBreakpointsForUndefined = model.getExceptionBreakpointsForSession(undefined);
     		assert.strictEqual(exceptionBreakpointsForUndefined.length, 2);
    diff --git a/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts b/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts
    index 0ca1a84601a..bb25500da7e 100644
    --- a/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts
    +++ b/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts
    @@ -22,7 +22,7 @@ import { MockDebugService, MockSession } from '../common/mockDebug.js';
     
     const $ = dom.$;
     
    -function assertVariable(disposables: Pick, variablesRenderer: VariablesRenderer, displayType: boolean) {
    +function assertVariable(disposables: Pick, variablesRenderer: VariablesRenderer, displayType: boolean) {
     	const session = new MockSession();
     	const thread = new Thread(session, 'mockthread', 1);
     	const range = {
    diff --git a/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts b/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts
    index f3e818393eb..577138b03f8 100644
    --- a/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts
    +++ b/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts
    @@ -21,7 +21,7 @@ import { TestConfigurationService } from '../../../../../platform/configuration/
     import { DebugExpressionRenderer } from '../../browser/debugExpressionRenderer.js';
     const $ = dom.$;
     
    -function assertWatchVariable(disposables: Pick, watchExpressionsRenderer: WatchExpressionsRenderer, displayType: boolean) {
    +function assertWatchVariable(disposables: Pick, watchExpressionsRenderer: WatchExpressionsRenderer, displayType: boolean) {
     	const session = new MockSession();
     	const thread = new Thread(session, 'mockthread', 1);
     	const range = {
    diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts
    index 8fc8e47f925..59248dc5d5a 100644
    --- a/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts
    +++ b/src/vs/workbench/contrib/mcp/test/common/mcpSamplingLog.test.ts
    @@ -3,20 +3,20 @@
      *  Licensed under the MIT License. See License.txt in the project root for license information.
      *--------------------------------------------------------------------------------------------*/
     
    -import * as assert from "assert";
    -import * as sinon from "sinon";
    -import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../base/test/common/utils.js";
    +import * as assert from 'assert';
    +import * as sinon from 'sinon';
    +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
     import {
     	StorageScope
    -} from "../../../../../platform/storage/common/storage.js";
    -import { TestStorageService } from "../../../../test/common/workbenchTestServices.js";
    -import { ISamplingStoredData, McpSamplingLog } from "../../common/mcpSamplingLog.js";
    -import { IMcpServer } from "../../common/mcpTypes.js";
    +} from '../../../../../platform/storage/common/storage.js';
    +import { TestStorageService } from '../../../../test/common/workbenchTestServices.js';
    +import { ISamplingStoredData, McpSamplingLog } from '../../common/mcpSamplingLog.js';
    +import { IMcpServer } from '../../common/mcpTypes.js';
     
    -suite("MCP - Sampling Log", () => {
    +suite('MCP - Sampling Log', () => {
     	const ds = ensureNoDisposablesAreLeakedInTestSuite();
     	const fakeServer: IMcpServer = {
    -		definition: { id: "testServer" },
    +		definition: { id: 'testServer' },
     		readDefinitions: () => ({
     			get: () => ({ collection: { scope: StorageScope.APPLICATION } }),
     		}),
    @@ -30,37 +30,37 @@ suite("MCP - Sampling Log", () => {
     		storage = ds.add(new TestStorageService());
     		log = ds.add(new McpSamplingLog(storage));
     		clock = sinon.useFakeTimers();
    -		clock.setSystemTime(new Date("2023-10-01T00:00:00Z").getTime());
    +		clock.setSystemTime(new Date('2023-10-01T00:00:00Z').getTime());
     	});
     
     	teardown(() => {
     		clock.restore();
     	});
     
    -	test("logs a single request", async () => {
    +	test('logs a single request', async () => {
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "test request" } }],
    -			"test response here",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'test request' } }],
    +			'test response here',
    +			'foobar9000',
     		);
     
     		// storage.testEmitWillSaveState(WillSaveStateReason.NONE);
     		await storage.flush();
     		assert.deepStrictEqual(
    -			(storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as unknown),
    +			(storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as unknown),
     			[
     				[
    -					"testServer",
    +					'testServer',
     					{
     						head: 19631,
     						bins: [1, 0, 0, 0, 0, 0, 0],
     						lastReqs: [
     							{
    -								request: [{ role: "user", content: { type: "text", text: "test request" } }],
    -								response: "test response here",
    +								request: [{ role: 'user', content: { type: 'text', text: 'test request' } }],
    +								response: 'test response here',
     								at: 1696118400000,
    -								model: "foobar9000",
    +								model: 'foobar9000',
     							},
     						],
     					},
    @@ -69,13 +69,13 @@ suite("MCP - Sampling Log", () => {
     		);
     	});
     
    -	test("logs multiple requests on the same day", async () => {
    +	test('logs multiple requests on the same day', async () => {
     		// First request
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "first request" } }],
    -			"first response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'first request' } }],
    +			'first response',
    +			'foobar9000',
     		);
     
     		// Advance time by a few hours but stay on the same day
    @@ -84,30 +84,30 @@ suite("MCP - Sampling Log", () => {
     		// Second request
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "second request" } }],
    -			"second response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'second request' } }],
    +			'second response',
    +			'foobar9000',
     		);
     
     		await storage.flush();
    -		const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, any][])[0][1];
    +		const data = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, any][])[0][1];
     
     		// Verify the bin for the current day has 2 requests
     		assert.strictEqual(data.bins[0], 2);
     
     		// Verify both requests are in the lastReqs array, with the most recent first
     		assert.strictEqual(data.lastReqs.length, 2);
    -		assert.strictEqual(data.lastReqs[0].request[0].content.text, "second request");
    -		assert.strictEqual(data.lastReqs[1].request[0].content.text, "first request");
    +		assert.strictEqual(data.lastReqs[0].request[0].content.text, 'second request');
    +		assert.strictEqual(data.lastReqs[1].request[0].content.text, 'first request');
     	});
     
    -	test("shifts bins when adding requests on different days", async () => {
    +	test('shifts bins when adding requests on different days', async () => {
     		// First request on day 1
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "day 1 request" } }],
    -			"day 1 response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'day 1 request' } }],
    +			'day 1 response',
    +			'foobar9000',
     		);
     
     		// Advance time to the next day
    @@ -116,13 +116,13 @@ suite("MCP - Sampling Log", () => {
     		// Second request on day 2
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "day 2 request" } }],
    -			"day 2 response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'day 2 request' } }],
    +			'day 2 response',
    +			'foobar9000',
     		);
     
     		await storage.flush();
    -		const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
    +		const data = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
     
     		// Verify the bins: day 2 should have 1 request, day 1 should have 1 request
     		assert.strictEqual(data.bins[0], 1); // day 2
    @@ -134,13 +134,13 @@ suite("MCP - Sampling Log", () => {
     		// Request on day 7
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "day 7 request" } }],
    -			"day 7 response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'day 7 request' } }],
    +			'day 7 response',
    +			'foobar9000',
     		);
     
     		await storage.flush();
    -		const updatedData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
    +		const updatedData = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
     
     		// Verify the bins have shifted correctly
     		assert.strictEqual(updatedData.bins[0], 1); // day 7
    @@ -148,81 +148,81 @@ suite("MCP - Sampling Log", () => {
     		assert.strictEqual(updatedData.bins[6], 1); // day 1
     	});
     
    -	test("limits the number of stored requests", async () => {
    +	test('limits the number of stored requests', async () => {
     		// Add more than the maximum number of requests (Constants.SamplingLastNMessage = 30)
     		for (let i = 0; i < 35; i++) {
     			log.add(
     				fakeServer,
    -				[{ role: "user", content: { type: "text", text: `request ${i}` } }],
    +				[{ role: 'user', content: { type: 'text', text: `request ${i}` } }],
     				`response ${i}`,
    -				"foobar9000",
    +				'foobar9000',
     			);
     		}
     
     		await storage.flush();
    -		const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
    +		const data = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
     
     		// Verify only the last 30 requests are kept
     		assert.strictEqual(data.lastReqs.length, 30);
    -		assert.strictEqual((data.lastReqs[0].request[0].content as { type: "text"; text: string }).text, "request 34");
    -		assert.strictEqual((data.lastReqs[29].request[0].content as { type: "text"; text: string }).text, "request 5");
    +		assert.strictEqual((data.lastReqs[0].request[0].content as { type: 'text'; text: string }).text, 'request 34');
    +		assert.strictEqual((data.lastReqs[29].request[0].content as { type: 'text'; text: string }).text, 'request 5');
     	});
     
    -	test("handles different content types", async () => {
    +	test('handles different content types', async () => {
     		// Add a request with text content
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "text request" } }],
    -			"text response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'text request' } }],
    +			'text response',
    +			'foobar9000',
     		);
     
     		// Add a request with image content
     		log.add(
     			fakeServer,
     			[{
    -				role: "user",
    +				role: 'user',
     				content: {
    -					type: "image",
    -					data: "base64data",
    -					mimeType: "image/png"
    +					type: 'image',
    +					data: 'base64data',
    +					mimeType: 'image/png'
     				}
     			}],
    -			"image response",
    -			"foobar9000",
    +			'image response',
    +			'foobar9000',
     		);
     
     		// Add a request with mixed content
     		log.add(
     			fakeServer,
     			[
    -				{ role: "user", content: { type: "text", text: "text and image" } },
    +				{ role: 'user', content: { type: 'text', text: 'text and image' } },
     				{
    -					role: "assistant",
    +					role: 'assistant',
     					content: {
    -						type: "image",
    -						data: "base64data",
    -						mimeType: "image/jpeg"
    +						type: 'image',
    +						data: 'base64data',
    +						mimeType: 'image/jpeg'
     					}
     				}
     			],
    -			"mixed response",
    -			"foobar9000",
    +			'mixed response',
    +			'foobar9000',
     		);
     
     		await storage.flush();
    -		const data = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
    +		const data = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, ISamplingStoredData][])[0][1];
     
     		// Verify all requests are stored correctly
     		assert.strictEqual(data.lastReqs.length, 3);
     		assert.strictEqual(data.lastReqs[0].request.length, 2); // Mixed content request has 2 messages
    -		assert.strictEqual(data.lastReqs[1].request[0].content.type, "image");
    -		assert.strictEqual(data.lastReqs[2].request[0].content.type, "text");
    +		assert.strictEqual(data.lastReqs[1].request[0].content.type, 'image');
    +		assert.strictEqual(data.lastReqs[2].request[0].content.type, 'text');
     	});
     
    -	test("handles multiple servers", async () => {
    +	test('handles multiple servers', async () => {
     		const fakeServer2: IMcpServer = {
    -			definition: { id: "testServer2" },
    +			definition: { id: 'testServer2' },
     			readDefinitions: () => ({
     				get: () => ({ collection: { scope: StorageScope.APPLICATION } }),
     			}),
    @@ -230,24 +230,24 @@ suite("MCP - Sampling Log", () => {
     
     		log.add(
     			fakeServer,
    -			[{ role: "user", content: { type: "text", text: "server1 request" } }],
    -			"server1 response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'server1 request' } }],
    +			'server1 response',
    +			'foobar9000',
     		);
     
     		log.add(
     			fakeServer2,
    -			[{ role: "user", content: { type: "text", text: "server2 request" } }],
    -			"server2 response",
    -			"foobar9000",
    +			[{ role: 'user', content: { type: 'text', text: 'server2 request' } }],
    +			'server2 response',
    +			'foobar9000',
     		);
     
     		await storage.flush();
    -		const storageData = (storage.getObject("mcp.sampling.logs", StorageScope.APPLICATION) as [string, ISamplingStoredData][]);
    +		const storageData = (storage.getObject('mcp.sampling.logs', StorageScope.APPLICATION) as [string, ISamplingStoredData][]);
     
     		// Verify both servers have their data stored
     		assert.strictEqual(storageData.length, 2);
    -		assert.strictEqual(storageData[0][0], "testServer");
    -		assert.strictEqual(storageData[1][0], "testServer2");
    +		assert.strictEqual(storageData[0][0], 'testServer');
    +		assert.strictEqual(storageData[1][0], 'testServer2');
     	});
     });
    diff --git a/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts b/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts
    index 6746710e0ff..8d6d5976f94 100644
    --- a/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts
    +++ b/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts
    @@ -31,9 +31,9 @@ suite('UriTemplate', () => {
     	test('simple replacement', () => {
     		const templ = UriTemplate.parse('http://example.com/{var}');
     		assert.deepStrictEqual(templ.components, ['http://example.com/', {
    -			expression: "{var}",
    +			expression: '{var}',
     			operator: '',
    -			variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]
    +			variables: [{ explodable: false, name: 'var', optional: false, prefixLength: undefined, repeatable: false }]
     		}, '']);
     		const result = templ.resolve({ var: 'value' });
     		assert.strictEqual(result, 'http://example.com/value');
    @@ -42,52 +42,52 @@ suite('UriTemplate', () => {
     	test('parsing components correctly', () => {
     		// Simple component
     		testParsing('http://example.com/{var}', [{
    -			expression: "{var}",
    +			expression: '{var}',
     			operator: '',
    -			variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]
    +			variables: [{ explodable: false, name: 'var', optional: false, prefixLength: undefined, repeatable: false }]
     		}]);
     
     		// Component with operator
     		testParsing('http://example.com/{+path}', [{
    -			expression: "{+path}",
    +			expression: '{+path}',
     			operator: '+',
    -			variables: [{ explodable: false, name: "path", optional: false, prefixLength: undefined, repeatable: false }]
    +			variables: [{ explodable: false, name: 'path', optional: false, prefixLength: undefined, repeatable: false }]
     		}]);
     
     		// Component with multiple variables
     		testParsing('http://example.com/{x,y}', [{
    -			expression: "{x,y}",
    +			expression: '{x,y}',
     			operator: '',
     			variables: [
    -				{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false },
    -				{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }
    +				{ explodable: false, name: 'x', optional: false, prefixLength: undefined, repeatable: false },
    +				{ explodable: false, name: 'y', optional: false, prefixLength: undefined, repeatable: false }
     			]
     		}]);
     
     		// Component with value modifiers
     		testParsing('http://example.com/{var:3}', [{
    -			expression: "{var:3}",
    +			expression: '{var:3}',
     			operator: '',
    -			variables: [{ explodable: false, name: "var", optional: false, prefixLength: 3, repeatable: false }]
    +			variables: [{ explodable: false, name: 'var', optional: false, prefixLength: 3, repeatable: false }]
     		}]);
     
     		testParsing('http://example.com/{list*}', [{
    -			expression: "{list*}",
    +			expression: '{list*}',
     			operator: '',
    -			variables: [{ explodable: true, name: "list", optional: false, prefixLength: undefined, repeatable: true }]
    +			variables: [{ explodable: true, name: 'list', optional: false, prefixLength: undefined, repeatable: true }]
     		}]);
     
     		// Multiple components
     		testParsing('http://example.com/{x}/path/{y}', [
     			{
    -				expression: "{x}",
    +				expression: '{x}',
     				operator: '',
    -				variables: [{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false }]
    +				variables: [{ explodable: false, name: 'x', optional: false, prefixLength: undefined, repeatable: false }]
     			},
     			{
    -				expression: "{y}",
    +				expression: '{y}',
     				operator: '',
    -				variables: [{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }]
    +				variables: [{ explodable: false, name: 'y', optional: false, prefixLength: undefined, repeatable: false }]
     			}
     		]);
     	});
    diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
    index da8ef8bb060..25b223f1cad 100644
    --- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
    +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
    @@ -25,11 +25,11 @@ suite('merge editor model', () => {
     	test('prepend line', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "plaintext",
    -				"base": "line1\nline2",
    -				"input1": "0\nline1\nline2",
    -				"input2": "0\nline1\nline2",
    -				"result": ""
    +				'languageId': 'plaintext',
    +				'base': 'line1\nline2',
    +				'input1': '0\nline1\nline2',
    +				'input2': '0\nline1\nline2',
    +				'result': ''
     			},
     			model => {
     				assert.deepStrictEqual(model.getProjections(), {
    @@ -48,7 +48,7 @@ suite('merge editor model', () => {
     				model.toggleConflict(0, 2);
     				assert.deepStrictEqual(
     					{ result: model.getResult() },
    -					({ result: "0\n0\nline1\nline2" })
    +					({ result: '0\n0\nline1\nline2' })
     				);
     			}
     		);
    @@ -57,11 +57,11 @@ suite('merge editor model', () => {
     	test('empty base', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "plaintext",
    -				"base": "",
    -				"input1": "input1",
    -				"input2": "input2",
    -				"result": ""
    +				'languageId': 'plaintext',
    +				'base': '',
    +				'input1': 'input1',
    +				'input2': 'input2',
    +				'result': ''
     			},
     			model => {
     				assert.deepStrictEqual(model.getProjections(), {
    @@ -74,13 +74,13 @@ suite('merge editor model', () => {
     				model.toggleConflict(0, 1);
     				assert.deepStrictEqual(
     					{ result: model.getResult() },
    -					({ result: "input1" })
    +					({ result: 'input1' })
     				);
     
     				model.toggleConflict(0, 2);
     				assert.deepStrictEqual(
     					{ result: model.getResult() },
    -					({ result: "input2" })
    +					({ result: 'input2' })
     				);
     			}
     		);
    @@ -89,11 +89,11 @@ suite('merge editor model', () => {
     	test('can merge word changes', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "plaintext",
    -				"base": "hello",
    -				"input1": "hallo",
    -				"input2": "helloworld",
    -				"result": ""
    +				'languageId': 'plaintext',
    +				'base': 'hello',
    +				'input1': 'hallo',
    +				'input2': 'helloworld',
    +				'result': ''
     			},
     			model => {
     				assert.deepStrictEqual(model.getProjections(), {
    @@ -118,11 +118,11 @@ suite('merge editor model', () => {
     	test('can combine insertions at end of document', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "plaintext",
    -				"base": "Zürich\nBern\nBasel\nChur\nGenf\nThun",
    -				"input1": "Zürich\nBern\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}",
    -				"input2": "Zürich\nBern\nBasel (FCB)\nChur\nGenf\nThun\nfunction f(a:number) {}",
    -				"result": "Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun"
    +				'languageId': 'plaintext',
    +				'base': 'Zürich\nBern\nBasel\nChur\nGenf\nThun',
    +				'input1': 'Zürich\nBern\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}',
    +				'input2': 'Zürich\nBern\nBasel (FCB)\nChur\nGenf\nThun\nfunction f(a:number) {}',
    +				'result': 'Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun'
     			},
     			model => {
     				assert.deepStrictEqual(model.getProjections(), {
    @@ -173,51 +173,51 @@ suite('merge editor model', () => {
     	test('conflicts are reset', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "typescript",
    -				"base": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
    -				"input1": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
    -				"input2": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
    -				"result": "import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n"
    +				'languageId': 'typescript',
    +				'base': `import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n`,
    +				'input1': `import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n`,
    +				'input2': `import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n`,
    +				'result': `import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n`
     			},
     			model => {
     				assert.deepStrictEqual(model.getProjections(), {
     					base: [
    -						"import { h } from 'vs/base/browser/dom';",
    -						"import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
    -						"⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
    -						"⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';",
    -						"import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
    -						"⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
    +						`import { h } from 'vs/base/browser/dom';`,
    +						`import { Disposable, IDisposable } from 'vs/base/common/lifecycle';`,
    +						`⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';`,
    +						`⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';`,
    +						`import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';`,
    +						`⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';`,
     						'',
     					],
     					input1: [
    -						"import { h } from 'vs/base/browser/dom';",
    -						"import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
    -						"⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
    -						"⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
    -						"⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
    -						"⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
    +						`import { h } from 'vs/base/browser/dom';`,
    +						`import { Disposable, IDisposable } from 'vs/base/common/lifecycle';`,
    +						`⟦import { observableSignalFromEvent } from 'vs/base/common/observable';`,
    +						`⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';`,
    +						`⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';`,
    +						`⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';`,
     						'',
     					],
     					input2: [
    -						"import { h } from 'vs/base/browser/dom';",
    -						"import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
    -						"⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
    -						"⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
    -						"⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
    +						`import { h } from 'vs/base/browser/dom';`,
    +						`import { Disposable, IDisposable } from 'vs/base/common/lifecycle';`,
    +						`⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';`,
    +						`⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';`,
    +						`⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';`,
     						'',
     					],
     					result: [
    -						"import { h } from 'vs/base/browser/dom';",
    -						"import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
    -						"⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
    -						"⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
    +						`import { h } from 'vs/base/browser/dom';`,
    +						`import { Disposable, IDisposable } from 'vs/base/common/lifecycle';`,
    +						`⟦import { observableSignalFromEvent } from 'vs/base/common/observable';`,
    +						`⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';`,
     						'⟦<<<<<<< Updated upstream',
    -						"import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
    +						`import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';`,
     						'=======',
    -						"import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
    +						`import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';`,
     						'>>>>>>> Stashed changes',
    -						"⟧{unrecognized}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
    +						`⟧{unrecognized}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';`,
     						'',
     					],
     				});
    @@ -228,11 +228,11 @@ suite('merge editor model', () => {
     	test('auto-solve equal edits', async () => {
     		await testMergeModel(
     			{
    -				"languageId": "javascript",
    -				"base": "const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nmain(paths);\n\nfunction main(paths) {\n    // print the welcome message\n    printMessage();\n\n    let data = getLineCountInfo(paths);\n    console.log(\"Lines: \" + data.totalLineCount);\n}\n\n/**\n * Prints the welcome message\n*/\nfunction printMessage() {\n    console.log(\"Welcome To Line Counter\");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n",
    -				"input1": "const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nmain(paths);\n\nfunction main(paths) {\n    // print the welcome message\n    printMessage();\n\n    const data = getLineCountInfo(paths);\n    console.log(\"Lines: \" + data.totalLineCount);\n}\n\nfunction printMessage() {\n    console.log(\"Welcome To Line Counter\");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n",
    -				"input2": "const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nrun(paths);\n\nfunction run(paths) {\n    // print the welcome message\n    printMessage();\n\n    const data = getLineCountInfo(paths);\n    console.log(\"Lines: \" + data.totalLineCount);\n}\n\nfunction printMessage() {\n    console.log(\"Welcome To Line Counter\");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n",
    -				"result": "<<<<<<< uiae\n>>>>>>> Stashed changes",
    +				'languageId': 'javascript',
    +				'base': `const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nmain(paths);\n\nfunction main(paths) {\n    // print the welcome message\n    printMessage();\n\n    let data = getLineCountInfo(paths);\n    console.log("Lines: " + data.totalLineCount);\n}\n\n/**\n * Prints the welcome message\n*/\nfunction printMessage() {\n    console.log("Welcome To Line Counter");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n`,
    +				'input1': `const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nmain(paths);\n\nfunction main(paths) {\n    // print the welcome message\n    printMessage();\n\n    const data = getLineCountInfo(paths);\n    console.log("Lines: " + data.totalLineCount);\n}\n\nfunction printMessage() {\n    console.log("Welcome To Line Counter");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n`,
    +				'input2': `const { readFileSync } = require('fs');\n\nlet paths = process.argv.slice(2);\nrun(paths);\n\nfunction run(paths) {\n    // print the welcome message\n    printMessage();\n\n    const data = getLineCountInfo(paths);\n    console.log("Lines: " + data.totalLineCount);\n}\n\nfunction printMessage() {\n    console.log("Welcome To Line Counter");\n}\n\n/**\n * @param {string[]} paths\n*/\nfunction getLineCountInfo(paths) {\n    let lineCounts = paths.map(path => ({ path, count: getLinesLength(readFileSync(path, 'utf8')) }));\n    return {\n        totalLineCount: lineCounts.reduce((acc, { count }) => acc + count, 0),\n        lineCounts,\n    };\n}\n\n/**\n * @param {string} str\n */\nfunction getLinesLength(str) {\n    return str.split('\\n').length;\n}\n`,
    +				'result': '<<<<<<< uiae\n>>>>>>> Stashed changes',
     				resetResult: true,
     			},
     			async model => {
    diff --git a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts
    index e73534da61c..3ed3d21b187 100644
    --- a/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts
    +++ b/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts
    @@ -87,7 +87,7 @@ suite('NotebookEditorWidgetService', () => {
     			override groups = [editorGroup1, editorGroup2];
     			override getPart(group: IEditorGroup | GroupIdentifier): IEditorPart;
     			override getPart(container: unknown): IEditorPart;
    -			override getPart(container: unknown): import("../../../../services/editor/common/editorGroupsService.js").IEditorPart {
    +			override getPart(container: unknown): import('../../../../services/editor/common/editorGroupsService.js').IEditorPart {
     				// eslint-disable-next-line local/code-no-any-casts
     				return { windowId: 0 } as any;
     			}
    diff --git a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts
    index 29a718058b8..4939fafd498 100644
    --- a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts
    +++ b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts
    @@ -1072,37 +1072,37 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells([
     			[
     				[
    -					"# This is a simple notebook\n",
    -					"\n",
    -					"There's nothing special here, I am just writing some text and plotting a function"
    +					'# This is a simple notebook\n',
    +					'\n',
    +					`There's nothing special here, I am just writing some text and plotting a function`
     				], 'markdown', CellKind.Markup, [], undefined],
     			[
     				[
    -					"import numpy as np\n",
    -					"import matplotlib.pyplot as plt\n",
    -					"import pandas as pd\n",
    -					"\n",
    -					"%matplotlib inline"
    +					'import numpy as np\n',
    +					'import matplotlib.pyplot as plt\n',
    +					'import pandas as pd\n',
    +					'\n',
    +					'%matplotlib inline'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"x = np.linspace(0, 4*np.pi,50)\n",
    -					"y = np.sin(x)\n",
    -					"\n",
    -					"plt.plot(x, y)"
    +					'x = np.linspace(0, 4*np.pi,50)\n',
    +					'y = np.sin(x)\n',
    +					'\n',
    +					'plt.plot(x, y)'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"df = pd.DataFrame({f\"column_{c}\": np.random.normal(size=100) for c in range(100)})\n",
    -					"df"
    +					'df = pd.DataFrame({f"column_{c}": np.random.normal(size=100) for c in range(100)})\n',
    +					'df'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"You could actually do some neat stuff with it, pandas like changing the colors (values above 1 should be green)"
    +					'You could actually do some neat stuff with it, pandas like changing the colors (values above 1 should be green)'
     				], 'python', CellKind.Markup, [], undefined],
     			[
     				[
    -					"df.style.applymap(lambda x: \"background-color: green\" if x>1 else \"background-color: white\")"
    +					'df.style.applymap(lambda x: "background-color: green" if x>1 else "background-color: white")'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[], 'python', CellKind.Code, [], undefined],
    @@ -1110,37 +1110,37 @@ suite('NotebookDiff Diff Service', () => {
     			, [
     				[
     					[
    -						"# This is a simple notebook\n",
    -						"\n",
    -						"There's nothing special here, I am just writing some text and plotting a function"
    +						'# This is a simple notebook\n',
    +						'\n',
    +						`There's nothing special here, I am just writing some text and plotting a function`
     					], 'markdown', CellKind.Markup, [], undefined],
     				[
     					[
    -						"import numpy as np\n",
    -						"import matplotlib.pyplot as plt\n",
    -						"import pandas as pd\n",
    -						"\n",
    -						"%matplotlib inline"
    +						'import numpy as np\n',
    +						'import matplotlib.pyplot as plt\n',
    +						'import pandas as pd\n',
    +						'\n',
    +						'%matplotlib inline'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"x = np.linspace(0, 4*np.pi,50)\n",
    -						"y = 2 * np.sin(x)\n",
    -						"\n",
    -						"plt.plot(x, y)"
    +						'x = np.linspace(0, 4*np.pi,50)\n',
    +						'y = 2 * np.sin(x)\n',
    +						'\n',
    +						'plt.plot(x, y)'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"df = pd.DataFrame({f\"column_{c}\": np.random.normal(size=100) for c in range(100)})\n",
    -						"df"
    +						'df = pd.DataFrame({f"column_{c}": np.random.normal(size=100) for c in range(100)})\n',
    +						'df'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"You could actually do some neat stuff with it, pandas like changing the colors (values above 1 should be green)"
    +						'You could actually do some neat stuff with it, pandas like changing the colors (values above 1 should be green)'
     					], 'python', CellKind.Markup, [], undefined],
     				[
     					[
    -						"df.style.applymap(lambda x: \"background-color: green\" if x>1 else \"background-color: white\")"
    +						'df.style.applymap(lambda x: "background-color: green" if x>1 else "background-color: white")'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[], 'python', CellKind.Code, [], undefined],
    @@ -1165,91 +1165,91 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells([
     			[
     				[
    -					"# Hello World"
    +					'# Hello World'
     				], 'markdown', CellKind.Markup, [], undefined],
     			[
     				[
    -					"import os\n",
    -					"from pprint import pprint \n",
    -					"from sklearn.preprocessing import LabelEncoder\n",
    -					"import ast\n",
    -					"import pandas as pd\n",
    -					"from clipper import clipper\n",
    -					"import ffmpeg"
    +					'import os\n',
    +					'from pprint import pprint \n',
    +					'from sklearn.preprocessing import LabelEncoder\n',
    +					'import ast\n',
    +					'import pandas as pd\n',
    +					'from clipper import clipper\n',
    +					'import ffmpeg'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"data_dir = '../../../app/data/'\n",
    -					"output_dir = 'output/'\n",
    -					"video_dir = 'videos/'\n",
    -					"\n",
    -					"file_path = data_dir+output_dir+data_file\n",
    -					"video_path = data_dir+video_dir+video_file\n",
    -					"\n",
    -					"df = pd.read_csv(file_path)\n",
    -					"df.head()"
    +					`data_dir = '../../../app/data/'\n`,
    +					`output_dir = 'output/'\n`,
    +					`video_dir = 'videos/'\n`,
    +					'\n',
    +					'file_path = data_dir+output_dir+data_file\n',
    +					'video_path = data_dir+video_dir+video_file\n',
    +					'\n',
    +					'df = pd.read_csv(file_path)\n',
    +					'df.head()'
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"# convert the string representation of results to a list\n",
    -					"df['list_categories'] = df['categories'].apply(ast.literal_eval)\n",
    -					"\n",
    -					"# get most likely label from each list\n",
    -					"df['label'] = df['list_categories'].apply(lambda x: x[0] if x else None)"
    +					'# convert the string representation of results to a list\n',
    +					`df['list_categories'] = df['categories'].apply(ast.literal_eval)\n`,
    +					'\n',
    +					'# get most likely label from each list\n',
    +					`df['label'] = df['list_categories'].apply(lambda x: x[0] if x else None)`
     				], 'python', CellKind.Code, [], undefined],
     			[
     				[
    -					"# initialize the LabelEncoder\n",
    -					"label_encoder = LabelEncoder()\n",
    -					"\n",
    -					"# fit and transform the label to a numerical value\n",
    -					"numerical_data = label_encoder.fit_transform(df['label'])\n",
    -					"\n",
    -					"df['label_value'] = numerical_data"
    +					'# initialize the LabelEncoder\n',
    +					'label_encoder = LabelEncoder()\n',
    +					'\n',
    +					'# fit and transform the label to a numerical value\n',
    +					`numerical_data = label_encoder.fit_transform(df['label'])\n`,
    +					'\n',
    +					`df['label_value'] = numerical_data`
     				], 'python', CellKind.Markup, [], undefined],
     		]
     			, [
     				[
     					[
    -						"# Updated markdown cell"
    +						'# Updated markdown cell'
     					], 'markdown', CellKind.Markup, [], undefined],
     				[
     					[
    -						"from sklearn.preprocessing import LabelEncoder\n",
    -						"import ast\n",
    -						"import pandas as pd\n",
    -						"from clipper import clipper\n",
    -						"import ffmpeg"
    +						'from sklearn.preprocessing import LabelEncoder\n',
    +						'import ast\n',
    +						'import pandas as pd\n',
    +						'from clipper import clipper\n',
    +						'import ffmpeg'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"data_dir = '../../../app/data/'\n",
    -						"output_dir = 'output/'\n",
    -						"video_dir = 'videos/'\n",
    -						"\n",
    -						"file_path = data_dir+output_dir+data_file\n",
    -						"video_path = data_dir+video_dir+video_file\n",
    -						"\n",
    -						"df = pd.read_csv(file_path)\n",
    -						"df.head()"
    +						`data_dir = '../../../app/data/'\n`,
    +						`output_dir = 'output/'\n`,
    +						`video_dir = 'videos/'\n`,
    +						'\n',
    +						'file_path = data_dir+output_dir+data_file\n',
    +						'video_path = data_dir+video_dir+video_file\n',
    +						'\n',
    +						'df = pd.read_csv(file_path)\n',
    +						'df.head()'
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"# convert the string representation of results to a list\n",
    -						"df['list_categories'] = df['categories'].apply(ast.literal_eval)\n",
    -						"\n",
    -						"# get most likely label from each list\n",
    -						"df['label'] = df['list_categories'].apply(lambda x: x[0] if x else None)"
    +						'# convert the string representation of results to a list\n',
    +						`df['list_categories'] = df['categories'].apply(ast.literal_eval)\n`,
    +						'\n',
    +						'# get most likely label from each list\n',
    +						`df['label'] = df['list_categories'].apply(lambda x: x[0] if x else None)`
     					], 'python', CellKind.Code, [], undefined],
     				[
     					[
    -						"# initialize the LabelEncoder\n",
    -						"label_encoder = LabelEncoder()\n",
    -						"\n",
    -						"# fit and transform the label to a numerical value\n",
    -						"numerical_data = label_encoder.fit_transform(df['label'])\n",
    -						"\n",
    -						"df['label_value'] = numerical_data"
    +						'# initialize the LabelEncoder\n',
    +						'label_encoder = LabelEncoder()\n',
    +						'\n',
    +						'# fit and transform the label to a numerical value\n',
    +						`numerical_data = label_encoder.fit_transform(df['label'])\n`,
    +						'\n',
    +						`df['label_value'] = numerical_data`
     					], 'python', CellKind.Markup, [], undefined],
     			]
     		);
    @@ -1270,1137 +1270,1137 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells(
     			[
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n",
    -						"\n",
    -						"### Installation Instructions\n",
    -						"\n",
    -						"This is a self-contained example that relies solely on its own code.\n",
    -						"\n",
    -						"We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n",
    -						"For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n",
    -						"\n",
    -						"Make sure your [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) is working properly.\n",
    -						"To avoid errors that may arise from the version of the dependency package, it is recommended to use the **JupyterLab** instead of the Jupyter notebook to display image results.\n",
    -						"```\n",
    -						"- pip install --upgrade pip && pip install -r requirements.txt\n",
    -						"- jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager\n",
    -						"- jupyter labextension install --no-build jupyter-datawidgets/extension\n",
    -						"- jupyter labextension install jupyter-threejs\n",
    -						"- jupyter labextension list\n",
    -						"```\n",
    -						"\n",
    -						"You should see:\n",
    -						"```\n",
    -						"JupyterLab v...\n",
    -						"  ...\n",
    -						"    jupyterlab-datawidgets v... enabled OK\n",
    -						"    @jupyter-widgets/jupyterlab-manager v... enabled OK\n",
    -						"    jupyter-threejs v... enabled OK\n",
    -						"```\n",
    -						"\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino-dev>=2024.0.0\" \"opencv-python\" \"torch\" \"onnx<1.16.2\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    -						")\n",
    -						"with open(\"notebook_utils.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py\",\n",
    -						")\n",
    -						"with open(\"engine3js.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"import notebook_utils as utils\n",
    -						"import engine3js as engine"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = \"model\"\n",
    -						"\n",
    -						"# model name as named in Open Model Zoo\n",
    -						"model_name = \"human-pose-estimation-3d-0001\"\n",
    -						"# selected precision (FP32, FP16)\n",
    -						"precision = \"FP32\"\n",
    -						"\n",
    -						"BASE_MODEL_NAME = f\"{base_model_dir}/public/{model_name}/{model_name}\"\n",
    -						"model_path = Path(BASE_MODEL_NAME).with_suffix(\".pth\")\n",
    -						"onnx_path = Path(BASE_MODEL_NAME).with_suffix(\".onnx\")\n",
    -						"\n",
    -						"ir_model_path = Path(f\"model/public/{model_name}/{precision}/{model_name}.xml\")\n",
    -						"model_weights_path = Path(f\"model/public/{model_name}/{precision}/{model_name}.bin\")\n",
    -						"\n",
    -						"if not model_path.exists():\n",
    -						"    download_command = f\"omz_downloader \" f\"--name {model_name} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    ! $download_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"if not onnx_path.exists():\n",
    -						"    convert_command = (\n",
    -						"        f\"omz_converter \" f\"--name {model_name} \" f\"--precisions {precision} \" f\"--download_dir {base_model_dir} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $convert_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"device = utils.device_widget()\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(model=ir_model_path, weights=model_weights_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)\n",
    -						"infer_request = compiled_model.create_infer_request()\n",
    -						"input_tensor_name = model.inputs[0].get_any_name()\n",
    -						"\n",
    -						"# get input and output names of nodes\n",
    -						"input_layer = compiled_model.input(0)\n",
    -						"output_layers = list(compiled_model.outputs)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"input_layer.any_name, [o.any_name for o in output_layers]"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[None,]\n",
    -						"    infer_request.infer({input_tensor_name: img})\n",
    -						"    # A set of three inference results is obtained\n",
    -						"    results = {name: infer_request.get_tensor(name).data[:] for name in {\"features\", \"heatmaps\", \"pafs\"}}\n",
    -						"    # Get the results\n",
    -						"    results = (results[\"features\"][0], results[\"heatmaps\"][0], results[\"pafs\"][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15],\n",
    -						"        [15, 16],  # nose - l_eye - l_ear\n",
    -						"        [1, 17],\n",
    -						"        [17, 18],  # nose - r_eye - r_ear\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],  # neck - nose\n",
    -						"        [1, 16],\n",
    -						"        [16, 18],  # nose - l_eye - l_ear\n",
    -						"        [1, 15],\n",
    -						"        [15, 17],  # nose - r_eye - r_ear\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(format=\"jpg\", height=windows_height, width=windows_width)\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://storage.openvinotoolkit.org/data/test_data/videos/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n',
    +						'\n',
    +						'### Installation Instructions\n',
    +						'\n',
    +						'This is a self-contained example that relies solely on its own code.\n',
    +						'\n',
    +						'We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n',
    +						'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n',
    +						'\n',
    +						'Make sure your [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) is working properly.\n',
    +						'To avoid errors that may arise from the version of the dependency package, it is recommended to use the **JupyterLab** instead of the Jupyter notebook to display image results.\n',
    +						'```\n',
    +						'- pip install --upgrade pip && pip install -r requirements.txt\n',
    +						'- jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager\n',
    +						'- jupyter labextension install --no-build jupyter-datawidgets/extension\n',
    +						'- jupyter labextension install jupyter-threejs\n',
    +						'- jupyter labextension list\n',
    +						'```\n',
    +						'\n',
    +						'You should see:\n',
    +						'```\n',
    +						'JupyterLab v...\n',
    +						'  ...\n',
    +						'    jupyterlab-datawidgets v... enabled OK\n',
    +						'    @jupyter-widgets/jupyterlab-manager v... enabled OK\n',
    +						'    jupyter-threejs v... enabled OK\n',
    +						'```\n',
    +						'\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino-dev>=2024.0.0" "opencv-python" "torch" "onnx<1.16.2" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n',
    +						')\n',
    +						'with open("notebook_utils.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py",\n',
    +						')\n',
    +						'with open("engine3js.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'import notebook_utils as utils\n',
    +						'import engine3js as engine'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = "model"\n',
    +						'\n',
    +						'# model name as named in Open Model Zoo\n',
    +						'model_name = "human-pose-estimation-3d-0001"\n',
    +						'# selected precision (FP32, FP16)\n',
    +						'precision = "FP32"\n',
    +						'\n',
    +						'BASE_MODEL_NAME = f"{base_model_dir}/public/{model_name}/{model_name}"\n',
    +						'model_path = Path(BASE_MODEL_NAME).with_suffix(".pth")\n',
    +						'onnx_path = Path(BASE_MODEL_NAME).with_suffix(".onnx")\n',
    +						'\n',
    +						'ir_model_path = Path(f"model/public/{model_name}/{precision}/{model_name}.xml")\n',
    +						'model_weights_path = Path(f"model/public/{model_name}/{precision}/{model_name}.bin")\n',
    +						'\n',
    +						'if not model_path.exists():\n',
    +						'    download_command = f"omz_downloader " f"--name {model_name} " f"--output_dir {base_model_dir}"\n',
    +						'    ! $download_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'if not onnx_path.exists():\n',
    +						'    convert_command = (\n',
    +						'        f"omz_converter " f"--name {model_name} " f"--precisions {precision} " f"--download_dir {base_model_dir} " f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $convert_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'device = utils.device_widget()\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(model=ir_model_path, weights=model_weights_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)\n',
    +						'infer_request = compiled_model.create_infer_request()\n',
    +						'input_tensor_name = model.inputs[0].get_any_name()\n',
    +						'\n',
    +						'# get input and output names of nodes\n',
    +						'input_layer = compiled_model.input(0)\n',
    +						'output_layers = list(compiled_model.outputs)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'input_layer.any_name, [o.any_name for o in output_layers]'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[None,]\n',
    +						'    infer_request.infer({input_tensor_name: img})\n',
    +						'    # A set of three inference results is obtained\n',
    +						'    results = {name: infer_request.get_tensor(name).data[:] for name in {"features", "heatmaps", "pafs"}}\n',
    +						'    # Get the results\n',
    +						'    results = (results["features"][0], results["heatmaps"][0], results["pafs"][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15],\n',
    +						'        [15, 16],  # nose - l_eye - l_ear\n',
    +						'        [1, 17],\n',
    +						'        [17, 18],  # nose - r_eye - r_ear\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],  # neck - nose\n',
    +						'        [1, 16],\n',
    +						'        [16, 18],  # nose - l_eye - l_ear\n',
    +						'        [1, 15],\n',
    +						'        [15, 17],  # nose - r_eye - r_ear\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(format="jpg", height=windows_height, width=windows_width)\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://storage.openvinotoolkit.org/data/test_data/videos/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
     			, [
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n",
    -						"\n",
    -						"### Installation Instructions\n",
    -						"\n",
    -						"This is a self-contained example that relies solely on its own code.\n",
    -						"\n",
    -						"We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n",
    -						"For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n",
    -						"\n",
    -						"Make sure your [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) is working properly.\n",
    -						"To avoid errors that may arise from the version of the dependency package, it is recommended to use the **JupyterLab** instead of the Jupyter notebook to display image results.\n",
    -						"```\n",
    -						"- pip install --upgrade pip && pip install -r requirements.txt\n",
    -						"- jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager\n",
    -						"- jupyter labextension install --no-build jupyter-datawidgets/extension\n",
    -						"- jupyter labextension install jupyter-threejs\n",
    -						"- jupyter labextension list\n",
    -						"```\n",
    -						"\n",
    -						"You should see:\n",
    -						"```\n",
    -						"JupyterLab v...\n",
    -						"  ...\n",
    -						"    jupyterlab-datawidgets v... enabled OK\n",
    -						"    @jupyter-widgets/jupyterlab-manager v... enabled OK\n",
    -						"    jupyter-threejs v... enabled OK\n",
    -						"```\n",
    -						"\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino>=2024.4.0\" \"opencv-python\" \"torch\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    -						")\n",
    -						"with open(\"notebook_utils.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py\",\n",
    -						")\n",
    -						"with open(\"engine3js.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"import notebook_utils as utils\n",
    -						"import engine3js as engine"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"from notebook_utils import download_file\n",
    -						"import tarfile\n",
    -						"\n",
    -						"\n",
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = Path(\"model\")\n",
    -						"\n",
    -						"download_file(\"https://storage.openvinotoolkit.org/repositories/open_model_zoo/public/2022.1/human-pose-estimation-3d-0001/human-pose-estimation-3d.tar.gz\", directory=base_model_dir)\n",
    -						"\n",
    -						"ckpt_file =  base_model_dir / \"human-pose-estimation-3d-0001.pth\"\n",
    -						"\n",
    -						"if not ckpt_file.exists():\n",
    -						"    with tarfile.open(base_model_dir / \"human-pose-estimation-3d.tar.gz\") as f:\n",
    -						"        f.extractall(base_model_dir)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import torch\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"ov_model_path = Path(base_model_dir) / \"human-pose-estimation-3d-0001.xml\"\n",
    -						"\n",
    -						"if not ov_model_path.exists():\n",
    -						"    from model.model import PoseEstimationWithMobileNet\n",
    -						"\n",
    -						"    pose_estimation_model = PoseEstimationWithMobileNet(is_convertible_by_mo=True)\n",
    -						"    pose_estimation_model.loas_state_dict(torch.load(ckpt_file, map_location=\"cpu\"))\n",
    -						"    pose_estimation_model.eval()\n",
    -						"\n",
    -						"    with torch.no_grad():\n",
    -						"        ov_model = ov.convert_model(pose_estimation_model, example_input=torch.zeros([1, 3, 256, 448]), input=[1, 3, 256, 448])\n",
    -						"        ov.save_model(ov_model, ov_model_path)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"device = utils.device_widget()\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(ov_model_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    mean_value = 128.0\n",
    -						"    scale_value = 255.0\n",
    -						"\n",
    -						"    img = (img - mean_value) / scale_value\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[None,]\n",
    -						"    result = compiled_model(img)\n",
    -						"    # Get the results\n",
    -						"    results = (result[0][0], results[1][0], results[2][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15],\n",
    -						"        [15, 16],  # nose - l_eye - l_ear\n",
    -						"        [1, 17],\n",
    -						"        [17, 18],  # nose - r_eye - r_ear\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],  # neck - nose\n",
    -						"        [1, 16],\n",
    -						"        [16, 18],  # nose - l_eye - l_ear\n",
    -						"        [1, 15],\n",
    -						"        [15, 17],  # nose - r_eye - r_ear\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(format=\"jpg\", height=windows_height, width=windows_width)\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://storage.openvinotoolkit.org/data/test_data/videos/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n',
    +						'\n',
    +						'### Installation Instructions\n',
    +						'\n',
    +						'This is a self-contained example that relies solely on its own code.\n',
    +						'\n',
    +						'We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n',
    +						'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n',
    +						'\n',
    +						'Make sure your [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) is working properly.\n',
    +						'To avoid errors that may arise from the version of the dependency package, it is recommended to use the **JupyterLab** instead of the Jupyter notebook to display image results.\n',
    +						'```\n',
    +						'- pip install --upgrade pip && pip install -r requirements.txt\n',
    +						'- jupyter labextension install --no-build @jupyter-widgets/jupyterlab-manager\n',
    +						'- jupyter labextension install --no-build jupyter-datawidgets/extension\n',
    +						'- jupyter labextension install jupyter-threejs\n',
    +						'- jupyter labextension list\n',
    +						'```\n',
    +						'\n',
    +						'You should see:\n',
    +						'```\n',
    +						'JupyterLab v...\n',
    +						'  ...\n',
    +						'    jupyterlab-datawidgets v... enabled OK\n',
    +						'    @jupyter-widgets/jupyterlab-manager v... enabled OK\n',
    +						'    jupyter-threejs v... enabled OK\n',
    +						'```\n',
    +						'\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino>=2024.4.0" "opencv-python" "torch" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n',
    +						')\n',
    +						'with open("notebook_utils.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py",\n',
    +						')\n',
    +						'with open("engine3js.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'import notebook_utils as utils\n',
    +						'import engine3js as engine'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'from notebook_utils import download_file\n',
    +						'import tarfile\n',
    +						'\n',
    +						'\n',
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = Path("model")\n',
    +						'\n',
    +						'download_file("https://storage.openvinotoolkit.org/repositories/open_model_zoo/public/2022.1/human-pose-estimation-3d-0001/human-pose-estimation-3d.tar.gz", directory=base_model_dir)\n',
    +						'\n',
    +						'ckpt_file =  base_model_dir / "human-pose-estimation-3d-0001.pth"\n',
    +						'\n',
    +						'if not ckpt_file.exists():\n',
    +						'    with tarfile.open(base_model_dir / "human-pose-estimation-3d.tar.gz") as f:\n',
    +						'        f.extractall(base_model_dir)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import torch\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'ov_model_path = Path(base_model_dir) / "human-pose-estimation-3d-0001.xml"\n',
    +						'\n',
    +						'if not ov_model_path.exists():\n',
    +						'    from model.model import PoseEstimationWithMobileNet\n',
    +						'\n',
    +						'    pose_estimation_model = PoseEstimationWithMobileNet(is_convertible_by_mo=True)\n',
    +						'    pose_estimation_model.loas_state_dict(torch.load(ckpt_file, map_location="cpu"))\n',
    +						'    pose_estimation_model.eval()\n',
    +						'\n',
    +						'    with torch.no_grad():\n',
    +						'        ov_model = ov.convert_model(pose_estimation_model, example_input=torch.zeros([1, 3, 256, 448]), input=[1, 3, 256, 448])\n',
    +						'        ov.save_model(ov_model, ov_model_path)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'device = utils.device_widget()\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(ov_model_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    mean_value = 128.0\n',
    +						'    scale_value = 255.0\n',
    +						'\n',
    +						'    img = (img - mean_value) / scale_value\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[None,]\n',
    +						'    result = compiled_model(img)\n',
    +						'    # Get the results\n',
    +						'    results = (result[0][0], results[1][0], results[2][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15],\n',
    +						'        [15, 16],  # nose - l_eye - l_ear\n',
    +						'        [1, 17],\n',
    +						'        [17, 18],  # nose - r_eye - r_ear\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],  # neck - nose\n',
    +						'        [1, 16],\n',
    +						'        [16, 18],  # nose - l_eye - l_ear\n',
    +						'        [1, 15],\n',
    +						'        [15, 17],  # nose - r_eye - r_ear\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(format="jpg", height=windows_height, width=windows_width)\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://storage.openvinotoolkit.org/data/test_data/videos/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
    @@ -2475,1110 +2475,1110 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells(
     			[
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino-dev>=2024.0.0\" \"opencv-python\" \"torch\" \"onnx\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import sys\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    -						")\n",
    -						"open(\"notebook_utils.py\", \"w\").write(r.text)\n",
    -						"import notebook_utils as utils\n",
    -						"\n",
    -						"sys.path.append(\"./engine\")\n",
    -						"import engine.engine3js as engine\n",
    -						"from engine.parse_poses import parse_poses"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = \"model\"\n",
    -						"\n",
    -						"# model name as named in Open Model Zoo\n",
    -						"model_name = \"human-pose-estimation-3d-0001\"\n",
    -						"# selected precision (FP32, FP16)\n",
    -						"precision = \"FP32\"\n",
    -						"\n",
    -						"BASE_MODEL_NAME = f\"{base_model_dir}/public/{model_name}/{model_name}\"\n",
    -						"model_path = Path(BASE_MODEL_NAME).with_suffix(\".pth\")\n",
    -						"onnx_path = Path(BASE_MODEL_NAME).with_suffix(\".onnx\")\n",
    -						"\n",
    -						"ir_model_path = f\"model/public/{model_name}/{precision}/{model_name}.xml\"\n",
    -						"model_weights_path = f\"model/public/{model_name}/{precision}/{model_name}.bin\"\n",
    -						"\n",
    -						"if not model_path.exists():\n",
    -						"    download_command = f\"omz_downloader \" f\"--name {model_name} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    ! $download_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"if not onnx_path.exists():\n",
    -						"    convert_command = (\n",
    -						"        f\"omz_converter \" f\"--name {model_name} \" f\"--precisions {precision} \" f\"--download_dir {base_model_dir} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $convert_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"core = ov.Core()\n",
    -						"\n",
    -						"device = widgets.Dropdown(\n",
    -						"    options=core.available_devices + [\"AUTO\"],\n",
    -						"    value=\"AUTO\",\n",
    -						"    description=\"Device:\",\n",
    -						"    disabled=False,\n",
    -						")\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(model=ir_model_path, weights=model_weights_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)\n",
    -						"infer_request = compiled_model.create_infer_request()\n",
    -						"input_tensor_name = model.inputs[0].get_any_name()\n",
    -						"\n",
    -						"# get input and output names of nodes\n",
    -						"input_layer = compiled_model.input(0)\n",
    -						"output_layers = list(compiled_model.outputs)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"input_layer.any_name, [o.any_name for o in output_layers]"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[None,]\n",
    -						"    infer_request.infer({input_tensor_name: img})\n",
    -						"    # A set of three inference results is obtained\n",
    -						"    results = {name: infer_request.get_tensor(name).data[:] for name in {\"features\", \"heatmaps\", \"pafs\"}}\n",
    -						"    # Get the results\n",
    -						"    results = (results[\"features\"][0], results[\"heatmaps\"][0], results[\"pafs\"][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15],\n",
    -						"        [15, 16],  # nose - l_eye - l_ear\n",
    -						"        [1, 17],\n",
    -						"        [17, 18],  # nose - r_eye - r_ear\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],  # neck - nose\n",
    -						"        [1, 16],\n",
    -						"        [16, 18],  # nose - l_eye - l_ear\n",
    -						"        [1, 15],\n",
    -						"        [15, 17],  # nose - r_eye - r_ear\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(format=\"jpg\", height=windows_height, width=windows_width)\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = parse_poses(inference_result, 1, stride, focal_length, True)\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino-dev>=2024.0.0" "opencv-python" "torch" "onnx" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import sys\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n',
    +						')\n',
    +						'open("notebook_utils.py", "w").write(r.text)\n',
    +						'import notebook_utils as utils\n',
    +						'\n',
    +						'sys.path.append("./engine")\n',
    +						'import engine.engine3js as engine\n',
    +						'from engine.parse_poses import parse_poses'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = "model"\n',
    +						'\n',
    +						'# model name as named in Open Model Zoo\n',
    +						'model_name = "human-pose-estimation-3d-0001"\n',
    +						'# selected precision (FP32, FP16)\n',
    +						'precision = "FP32"\n',
    +						'\n',
    +						'BASE_MODEL_NAME = f"{base_model_dir}/public/{model_name}/{model_name}"\n',
    +						'model_path = Path(BASE_MODEL_NAME).with_suffix(".pth")\n',
    +						'onnx_path = Path(BASE_MODEL_NAME).with_suffix(".onnx")\n',
    +						'\n',
    +						'ir_model_path = f"model/public/{model_name}/{precision}/{model_name}.xml"\n',
    +						'model_weights_path = f"model/public/{model_name}/{precision}/{model_name}.bin"\n',
    +						'\n',
    +						'if not model_path.exists():\n',
    +						'    download_command = f"omz_downloader " f"--name {model_name} " f"--output_dir {base_model_dir}"\n',
    +						'    ! $download_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'if not onnx_path.exists():\n',
    +						'    convert_command = (\n',
    +						'        f"omz_converter " f"--name {model_name} " f"--precisions {precision} " f"--download_dir {base_model_dir} " f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $convert_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'core = ov.Core()\n',
    +						'\n',
    +						'device = widgets.Dropdown(\n',
    +						'    options=core.available_devices + ["AUTO"],\n',
    +						'    value="AUTO",\n',
    +						'    description="Device:",\n',
    +						'    disabled=False,\n',
    +						')\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(model=ir_model_path, weights=model_weights_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)\n',
    +						'infer_request = compiled_model.create_infer_request()\n',
    +						'input_tensor_name = model.inputs[0].get_any_name()\n',
    +						'\n',
    +						'# get input and output names of nodes\n',
    +						'input_layer = compiled_model.input(0)\n',
    +						'output_layers = list(compiled_model.outputs)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'input_layer.any_name, [o.any_name for o in output_layers]'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[None,]\n',
    +						'    infer_request.infer({input_tensor_name: img})\n',
    +						'    # A set of three inference results is obtained\n',
    +						'    results = {name: infer_request.get_tensor(name).data[:] for name in {"features", "heatmaps", "pafs"}}\n',
    +						'    # Get the results\n',
    +						'    results = (results["features"][0], results["heatmaps"][0], results["pafs"][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15],\n',
    +						'        [15, 16],  # nose - l_eye - l_ear\n',
    +						'        [1, 17],\n',
    +						'        [17, 18],  # nose - r_eye - r_ear\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],  # neck - nose\n',
    +						'        [1, 16],\n',
    +						'        [16, 18],  # nose - l_eye - l_ear\n',
    +						'        [1, 15],\n',
    +						'        [15, 17],  # nose - r_eye - r_ear\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(format="jpg", height=windows_height, width=windows_width)\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = parse_poses(inference_result, 1, stride, focal_length, True)\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
     			, [
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino-dev>=2024.0.0\" \"opencv-python\" \"torch\" \"onnx\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    -						")\n",
    -						"with open(\"notebook_utils.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py\",\n",
    -						")\n",
    -						"with open(\"engine3js.py\", \"w\") as f:\n",
    -						"    f.write(r.text)\n",
    -						"\n",
    -						"import notebook_utils as utils\n",
    -						"import engine3js as engine"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = \"model\"\n",
    -						"\n",
    -						"# model name as named in Open Model Zoo\n",
    -						"model_name = \"human-pose-estimation-3d-0001\"\n",
    -						"# selected precision (FP32, FP16)\n",
    -						"precision = \"FP32\"\n",
    -						"\n",
    -						"BASE_MODEL_NAME = f\"{base_model_dir}/public/{model_name}/{model_name}\"\n",
    -						"model_path = Path(BASE_MODEL_NAME).with_suffix(\".pth\")\n",
    -						"onnx_path = Path(BASE_MODEL_NAME).with_suffix(\".onnx\")\n",
    -						"\n",
    -						"ir_model_path = f\"model/public/{model_name}/{precision}/{model_name}.xml\"\n",
    -						"model_weights_path = f\"model/public/{model_name}/{precision}/{model_name}.bin\"\n",
    -						"\n",
    -						"if not model_path.exists():\n",
    -						"    download_command = f\"omz_downloader \" f\"--name {model_name} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    ! $download_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"if not onnx_path.exists():\n",
    -						"    convert_command = (\n",
    -						"        f\"omz_converter \" f\"--name {model_name} \" f\"--precisions {precision} \" f\"--download_dir {base_model_dir} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $convert_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"core = ov.Core()\n",
    -						"\n",
    -						"device = widgets.Dropdown(\n",
    -						"    options=core.available_devices + [\"AUTO\"],\n",
    -						"    value=\"AUTO\",\n",
    -						"    description=\"Device:\",\n",
    -						"    disabled=False,\n",
    -						")\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(model=ir_model_path, weights=model_weights_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)\n",
    -						"infer_request = compiled_model.create_infer_request()\n",
    -						"input_tensor_name = model.inputs[0].get_any_name()\n",
    -						"\n",
    -						"# get input and output names of nodes\n",
    -						"input_layer = compiled_model.input(0)\n",
    -						"output_layers = list(compiled_model.outputs)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"input_layer.any_name, [o.any_name for o in output_layers]"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[None,]\n",
    -						"    infer_request.infer({input_tensor_name: img})\n",
    -						"    # A set of three inference results is obtained\n",
    -						"    results = {name: infer_request.get_tensor(name).data[:] for name in {\"features\", \"heatmaps\", \"pafs\"}}\n",
    -						"    # Get the results\n",
    -						"    results = (results[\"features\"][0], results[\"heatmaps\"][0], results[\"pafs\"][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15],\n",
    -						"        [15, 16],  # nose - l_eye - l_ear\n",
    -						"        [1, 17],\n",
    -						"        [17, 18],  # nose - r_eye - r_ear\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],  # neck - nose\n",
    -						"        [1, 16],\n",
    -						"        [16, 18],  # nose - l_eye - l_ear\n",
    -						"        [1, 15],\n",
    -						"        [15, 17],  # nose - r_eye - r_ear\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(format=\"jpg\", height=windows_height, width=windows_width)\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using a Jupyter Notebook release. Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino-dev>=2024.0.0" "opencv-python" "torch" "onnx" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n',
    +						')\n',
    +						'with open("notebook_utils.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/engine3js.py",\n',
    +						')\n',
    +						'with open("engine3js.py", "w") as f:\n',
    +						'    f.write(r.text)\n',
    +						'\n',
    +						'import notebook_utils as utils\n',
    +						'import engine3js as engine'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = "model"\n',
    +						'\n',
    +						'# model name as named in Open Model Zoo\n',
    +						'model_name = "human-pose-estimation-3d-0001"\n',
    +						'# selected precision (FP32, FP16)\n',
    +						'precision = "FP32"\n',
    +						'\n',
    +						'BASE_MODEL_NAME = f"{base_model_dir}/public/{model_name}/{model_name}"\n',
    +						'model_path = Path(BASE_MODEL_NAME).with_suffix(".pth")\n',
    +						'onnx_path = Path(BASE_MODEL_NAME).with_suffix(".onnx")\n',
    +						'\n',
    +						'ir_model_path = f"model/public/{model_name}/{precision}/{model_name}.xml"\n',
    +						'model_weights_path = f"model/public/{model_name}/{precision}/{model_name}.bin"\n',
    +						'\n',
    +						'if not model_path.exists():\n',
    +						'    download_command = f"omz_downloader " f"--name {model_name} " f"--output_dir {base_model_dir}"\n',
    +						'    ! $download_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'if not onnx_path.exists():\n',
    +						'    convert_command = (\n',
    +						'        f"omz_converter " f"--name {model_name} " f"--precisions {precision} " f"--download_dir {base_model_dir} " f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $convert_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'core = ov.Core()\n',
    +						'\n',
    +						'device = widgets.Dropdown(\n',
    +						'    options=core.available_devices + ["AUTO"],\n',
    +						'    value="AUTO",\n',
    +						'    description="Device:",\n',
    +						'    disabled=False,\n',
    +						')\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(model=ir_model_path, weights=model_weights_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)\n',
    +						'infer_request = compiled_model.create_infer_request()\n',
    +						'input_tensor_name = model.inputs[0].get_any_name()\n',
    +						'\n',
    +						'# get input and output names of nodes\n',
    +						'input_layer = compiled_model.input(0)\n',
    +						'output_layers = list(compiled_model.outputs)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'input_layer.any_name, [o.any_name for o in output_layers]'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[None,]\n',
    +						'    infer_request.infer({input_tensor_name: img})\n',
    +						'    # A set of three inference results is obtained\n',
    +						'    results = {name: infer_request.get_tensor(name).data[:] for name in {"features", "heatmaps", "pafs"}}\n',
    +						'    # Get the results\n',
    +						'    results = (results["features"][0], results["heatmaps"][0], results["pafs"][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15],\n',
    +						'        [15, 16],  # nose - l_eye - l_ear\n',
    +						'        [1, 17],\n',
    +						'        [17, 18],  # nose - r_eye - r_ear\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],  # neck - nose\n',
    +						'        [1, 16],\n',
    +						'        [16, 18],  # nose - l_eye - l_ear\n',
    +						'        [1, 15],\n',
    +						'        [15, 17],  # nose - r_eye - r_ear\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(format="jpg", height=windows_height, width=windows_width)\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = engine.parse_poses(inference_result, 1, stride, focal_length, True)\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
    @@ -3621,1117 +3621,1117 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells(
     			[
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino-dev>=2024.0.0\" \"opencv-python\" \"torch\" \"onnx\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import sys\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"r = requests.get(\n",
    -						"    url='https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py',\n",
    -						")\n",
    -						"open('notebook_utils.py', 'w').write(r.text)\n",
    -						"import notebook_utils as utils\n",
    -						"\n",
    -						"sys.path.append(\"./engine\")\n",
    -						"import engine.engine3js as engine\n",
    -						"from engine.parse_poses import parse_poses"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = \"model\"\n",
    -						"\n",
    -						"# model name as named in Open Model Zoo\n",
    -						"model_name = \"human-pose-estimation-3d-0001\"\n",
    -						"# selected precision (FP32, FP16)\n",
    -						"precision = \"FP32\"\n",
    -						"\n",
    -						"BASE_MODEL_NAME = f\"{base_model_dir}/public/{model_name}/{model_name}\"\n",
    -						"model_path = Path(BASE_MODEL_NAME).with_suffix(\".pth\")\n",
    -						"onnx_path = Path(BASE_MODEL_NAME).with_suffix(\".onnx\")\n",
    -						"\n",
    -						"ir_model_path = f\"model/public/{model_name}/{precision}/{model_name}.xml\"\n",
    -						"model_weights_path = f\"model/public/{model_name}/{precision}/{model_name}.bin\"\n",
    -						"\n",
    -						"if not model_path.exists():\n",
    -						"    download_command = (\n",
    -						"        f\"omz_downloader \" f\"--name {model_name} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $download_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"if not onnx_path.exists():\n",
    -						"    convert_command = (\n",
    -						"        f\"omz_converter \"\n",
    -						"        f\"--name {model_name} \"\n",
    -						"        f\"--precisions {precision} \"\n",
    -						"        f\"--download_dir {base_model_dir} \"\n",
    -						"        f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $convert_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"core = ov.Core()\n",
    -						"\n",
    -						"device = widgets.Dropdown(\n",
    -						"    options=core.available_devices + [\"AUTO\"],\n",
    -						"    value='AUTO',\n",
    -						"    description='Device:',\n",
    -						"    disabled=False,\n",
    -						")\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(model=ir_model_path, weights=model_weights_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)\n",
    -						"infer_request = compiled_model.create_infer_request()\n",
    -						"input_tensor_name = model.inputs[0].get_any_name()\n",
    -						"\n",
    -						"# get input and output names of nodes\n",
    -						"input_layer = compiled_model.input(0)\n",
    -						"output_layers = list(compiled_model.outputs)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"input_layer.any_name, [o.any_name for o in output_layers]"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[\n",
    -						"        None,\n",
    -						"    ]\n",
    -						"    infer_request.infer({input_tensor_name: img})\n",
    -						"    # A set of three inference results is obtained\n",
    -						"    results = {\n",
    -						"        name: infer_request.get_tensor(name).data[:]\n",
    -						"        for name in {\"features\", \"heatmaps\", \"pafs\"}\n",
    -						"    }\n",
    -						"    # Get the results\n",
    -						"    results = (results[\"features\"][0], results[\"heatmaps\"][0], results[\"pafs\"][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1], \n",
    -						"        [0, 9], [9, 10], [10, 11],    # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3], [3, 4], [4, 5],       # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15], [15, 16],            # nose - l_eye - l_ear\n",
    -						"        [1, 17], [17, 18],            # nose - r_eye - r_ear\n",
    -						"        [0, 6], [6, 7], [7, 8],       # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12], [12, 13], [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],                       # neck - nose\n",
    -						"        [1, 16], [16, 18],            # nose - l_eye - l_ear\n",
    -						"        [1, 15], [15, 17],            # nose - r_eye - r_ear\n",
    -						"        [0, 3], [3, 4], [4, 5],       # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9], [9, 10], [10, 11],    # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6], [6, 7], [7, 8],       # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12], [12, 13], [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")  \n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(\n",
    -						"                format=\"jpg\", height=windows_height, width=windows_width\n",
    -						"            )\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = parse_poses(inference_result, 1, stride, focal_length, True)\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino-dev>=2024.0.0" "opencv-python" "torch" "onnx" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import sys\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'r = requests.get(\n',
    +						`    url='https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py',\n`,
    +						')\n',
    +						`open('notebook_utils.py', 'w').write(r.text)\n`,
    +						'import notebook_utils as utils\n',
    +						'\n',
    +						'sys.path.append("./engine")\n',
    +						'import engine.engine3js as engine\n',
    +						'from engine.parse_poses import parse_poses'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = "model"\n',
    +						'\n',
    +						'# model name as named in Open Model Zoo\n',
    +						'model_name = "human-pose-estimation-3d-0001"\n',
    +						'# selected precision (FP32, FP16)\n',
    +						'precision = "FP32"\n',
    +						'\n',
    +						'BASE_MODEL_NAME = f"{base_model_dir}/public/{model_name}/{model_name}"\n',
    +						'model_path = Path(BASE_MODEL_NAME).with_suffix(".pth")\n',
    +						'onnx_path = Path(BASE_MODEL_NAME).with_suffix(".onnx")\n',
    +						'\n',
    +						'ir_model_path = f"model/public/{model_name}/{precision}/{model_name}.xml"\n',
    +						'model_weights_path = f"model/public/{model_name}/{precision}/{model_name}.bin"\n',
    +						'\n',
    +						'if not model_path.exists():\n',
    +						'    download_command = (\n',
    +						'        f"omz_downloader " f"--name {model_name} " f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $download_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'if not onnx_path.exists():\n',
    +						'    convert_command = (\n',
    +						'        f"omz_converter "\n',
    +						'        f"--name {model_name} "\n',
    +						'        f"--precisions {precision} "\n',
    +						'        f"--download_dir {base_model_dir} "\n',
    +						'        f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $convert_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'core = ov.Core()\n',
    +						'\n',
    +						'device = widgets.Dropdown(\n',
    +						'    options=core.available_devices + ["AUTO"],\n',
    +						`    value='AUTO',\n`,
    +						`    description='Device:',\n`,
    +						'    disabled=False,\n',
    +						')\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(model=ir_model_path, weights=model_weights_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)\n',
    +						'infer_request = compiled_model.create_infer_request()\n',
    +						'input_tensor_name = model.inputs[0].get_any_name()\n',
    +						'\n',
    +						'# get input and output names of nodes\n',
    +						'input_layer = compiled_model.input(0)\n',
    +						'output_layers = list(compiled_model.outputs)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'input_layer.any_name, [o.any_name for o in output_layers]'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[\n',
    +						'        None,\n',
    +						'    ]\n',
    +						'    infer_request.infer({input_tensor_name: img})\n',
    +						'    # A set of three inference results is obtained\n',
    +						'    results = {\n',
    +						'        name: infer_request.get_tensor(name).data[:]\n',
    +						'        for name in {"features", "heatmaps", "pafs"}\n',
    +						'    }\n',
    +						'    # Get the results\n',
    +						'    results = (results["features"][0], results["heatmaps"][0], results["pafs"][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1], \n',
    +						'        [0, 9], [9, 10], [10, 11],    # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3], [3, 4], [4, 5],       # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15], [15, 16],            # nose - l_eye - l_ear\n',
    +						'        [1, 17], [17, 18],            # nose - r_eye - r_ear\n',
    +						'        [0, 6], [6, 7], [7, 8],       # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12], [12, 13], [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],                       # neck - nose\n',
    +						'        [1, 16], [16, 18],            # nose - l_eye - l_ear\n',
    +						'        [1, 15], [15, 17],            # nose - r_eye - r_ear\n',
    +						'        [0, 3], [3, 4], [4, 5],       # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9], [9, 10], [10, 11],    # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6], [6, 7], [7, 8],       # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12], [12, 13], [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')  \n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(source, flip=flip, fps=30, skip_first_frames=skip_frames)\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(grid=True, axis=True, view_width=windows_width, view_height=windows_height)\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(\n',
    +						'                format="jpg", height=windows_height, width=windows_width\n',
    +						'            )\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2]))\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = parse_poses(inference_result, 1, stride, focal_length, True)\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
     			, [
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Live 3D Human Pose Estimation with OpenVINO\n",
    -						"\n",
    -						"This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n",
    -						"**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n",
    -						"\n",
    -						"> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n",
    -						"\n",
    -						"_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n",
    -						"_Ubuntu, Windows: Chrome_\n",
    -						"_macOS: Safari_\n",
    -						"\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Prerequisites](#Prerequisites)\n",
    -						"- [Imports](#Imports)\n",
    -						"- [The model](#The-model)\n",
    -						"    - [Download the model](#Download-the-model)\n",
    -						"    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Load the model](#Load-the-model)\n",
    -						"- [Processing](#Processing)\n",
    -						"    - [Model Inference](#Model-Inference)\n",
    -						"    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n",
    -						"    - [Main Processing Function](#Main-Processing-Function)\n",
    -						"- [Run](#Run)\n",
    -						"\n"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Prerequisites\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"%pip install pythreejs \"openvino-dev>=2024.0.0\" \"opencv-python\" \"torch\" \"onnx\" --extra-index-url https://download.pytorch.org/whl/cpu"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Imports\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"import collections\n",
    -						"import sys\n",
    -						"import time\n",
    -						"from pathlib import Path\n",
    -						"\n",
    -						"import cv2\n",
    -						"import ipywidgets as widgets\n",
    -						"import numpy as np\n",
    -						"from IPython.display import clear_output, display\n",
    -						"import openvino as ov\n",
    -						"\n",
    -						"# Fetch `notebook_utils` module\n",
    -						"import requests\n",
    -						"\n",
    -						"r = requests.get(\n",
    -						"    url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    -						")\n",
    -						"open(\"notebook_utils.py\", \"w\").write(r.text)\n",
    -						"import notebook_utils as utils\n",
    -						"\n",
    -						"sys.path.append(\"./engine\")\n",
    -						"import engine.engine3js as engine\n",
    -						"from engine.parse_poses import parse_poses"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## The model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Download the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# directory where model will be downloaded\n",
    -						"base_model_dir = \"model\"\n",
    -						"\n",
    -						"# model name as named in Open Model Zoo\n",
    -						"model_name = \"human-pose-estimation-3d-0001\"\n",
    -						"# selected precision (FP32, FP16)\n",
    -						"precision = \"FP32\"\n",
    -						"\n",
    -						"BASE_MODEL_NAME = f\"{base_model_dir}/public/{model_name}/{model_name}\"\n",
    -						"model_path = Path(BASE_MODEL_NAME).with_suffix(\".pth\")\n",
    -						"onnx_path = Path(BASE_MODEL_NAME).with_suffix(\".onnx\")\n",
    -						"\n",
    -						"ir_model_path = f\"model/public/{model_name}/{precision}/{model_name}.xml\"\n",
    -						"model_weights_path = f\"model/public/{model_name}/{precision}/{model_name}.bin\"\n",
    -						"\n",
    -						"if not model_path.exists():\n",
    -						"    download_command = (\n",
    -						"        f\"omz_downloader \" f\"--name {model_name} \" f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $download_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Convert Model to OpenVINO IR format\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"if not onnx_path.exists():\n",
    -						"    convert_command = (\n",
    -						"        f\"omz_converter \"\n",
    -						"        f\"--name {model_name} \"\n",
    -						"        f\"--precisions {precision} \"\n",
    -						"        f\"--download_dir {base_model_dir} \"\n",
    -						"        f\"--output_dir {base_model_dir}\"\n",
    -						"    )\n",
    -						"    ! $convert_command"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Select inference device\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"select device from dropdown list for running inference using OpenVINO"
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"core = ov.Core()\n",
    -						"\n",
    -						"device = widgets.Dropdown(\n",
    -						"    options=core.available_devices + [\"AUTO\"],\n",
    -						"    value=\"AUTO\",\n",
    -						"    description=\"Device:\",\n",
    -						"    disabled=False,\n",
    -						")\n",
    -						"\n",
    -						"device"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Load the model\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n",
    -						"\n",
    -						"First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# initialize inference engine\n",
    -						"core = ov.Core()\n",
    -						"# read the network and corresponding weights from file\n",
    -						"model = core.read_model(model=ir_model_path, weights=model_weights_path)\n",
    -						"# load the model on the specified device\n",
    -						"compiled_model = core.compile_model(model=model, device_name=device.value)\n",
    -						"infer_request = compiled_model.create_infer_request()\n",
    -						"input_tensor_name = model.inputs[0].get_any_name()\n",
    -						"\n",
    -						"# get input and output names of nodes\n",
    -						"input_layer = compiled_model.input(0)\n",
    -						"output_layers = list(compiled_model.outputs)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"input_layer.any_name, [o.any_name for o in output_layers]"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Processing\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"### Model Inference\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"def model_infer(scaled_img, stride):\n",
    -						"    \"\"\"\n",
    -						"    Run model inference on the input image\n",
    -						"\n",
    -						"    Parameters:\n",
    -						"        scaled_img: resized image according to the input size of the model\n",
    -						"        stride: int, the stride of the window\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    # Remove excess space from the picture\n",
    -						"    img = scaled_img[\n",
    -						"        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n",
    -						"        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n",
    -						"    ]\n",
    -						"\n",
    -						"    img = np.transpose(img, (2, 0, 1))[None,]\n",
    -						"    infer_request.infer({input_tensor_name: img})\n",
    -						"    # A set of three inference results is obtained\n",
    -						"    results = {\n",
    -						"        name: infer_request.get_tensor(name).data[:]\n",
    -						"        for name in {\"features\", \"heatmaps\", \"pafs\"}\n",
    -						"    }\n",
    -						"    # Get the results\n",
    -						"    results = (results[\"features\"][0], results[\"heatmaps\"][0], results[\"pafs\"][0])\n",
    -						"\n",
    -						"    return results"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Draw 2D Pose Overlays\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n",
    -						"Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"source": [
    -						"# 3D edge index array\n",
    -						"body_edges = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [1, 15],\n",
    -						"        [15, 16],  # nose - l_eye - l_ear\n",
    -						"        [1, 17],\n",
    -						"        [17, 18],  # nose - r_eye - r_ear\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"body_edges_2d = np.array(\n",
    -						"    [\n",
    -						"        [0, 1],  # neck - nose\n",
    -						"        [1, 16],\n",
    -						"        [16, 18],  # nose - l_eye - l_ear\n",
    -						"        [1, 15],\n",
    -						"        [15, 17],  # nose - r_eye - r_ear\n",
    -						"        [0, 3],\n",
    -						"        [3, 4],\n",
    -						"        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n",
    -						"        [0, 9],\n",
    -						"        [9, 10],\n",
    -						"        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n",
    -						"        [0, 6],\n",
    -						"        [6, 7],\n",
    -						"        [7, 8],  # neck - l_hip - l_knee - l_ankle\n",
    -						"        [0, 12],\n",
    -						"        [12, 13],\n",
    -						"        [13, 14],  # neck - r_hip - r_knee - r_ankle\n",
    -						"    ]\n",
    -						")\n",
    -						"\n",
    -						"\n",
    -						"def draw_poses(frame, poses_2d, scaled_img, use_popup):\n",
    -						"    \"\"\"\n",
    -						"    Draw 2D pose overlays on the image to visualize estimated poses.\n",
    -						"    Joints are drawn as circles and limbs are drawn as lines.\n",
    -						"\n",
    -						"    :param frame: the input image\n",
    -						"    :param poses_2d: array of human joint pairs\n",
    -						"    \"\"\"\n",
    -						"    for pose in poses_2d:\n",
    -						"        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n",
    -						"        was_found = pose[2] > 0\n",
    -						"\n",
    -						"        pose[0], pose[1] = (\n",
    -						"            pose[0] * frame.shape[1] / scaled_img.shape[1],\n",
    -						"            pose[1] * frame.shape[0] / scaled_img.shape[0],\n",
    -						"        )\n",
    -						"\n",
    -						"        # Draw joints.\n",
    -						"        for edge in body_edges_2d:\n",
    -						"            if was_found[edge[0]] and was_found[edge[1]]:\n",
    -						"                cv2.line(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n",
    -						"                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n",
    -						"                    (255, 255, 0),\n",
    -						"                    4,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"        # Draw limbs.\n",
    -						"        for kpt_id in range(pose.shape[1]):\n",
    -						"            if pose[2, kpt_id] != -1:\n",
    -						"                cv2.circle(\n",
    -						"                    frame,\n",
    -						"                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n",
    -						"                    3,\n",
    -						"                    (0, 255, 255),\n",
    -						"                    -1,\n",
    -						"                    cv2.LINE_AA,\n",
    -						"                )\n",
    -						"\n",
    -						"    return frame"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"### Main Processing Function\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n",
    -						"    \"\"\"\n",
    -						"    2D image as input, using OpenVINO as inference backend,\n",
    -						"    get joints 3D coordinates, and draw 3D human skeleton in the scene\n",
    -						"\n",
    -						"    :param source:      The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    -						"    :param flip:        To be used by VideoPlayer function for flipping capture image.\n",
    -						"    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n",
    -						"    :param skip_frames: Number of frames to skip at the beginning of the video.\n",
    -						"    \"\"\"\n",
    -						"\n",
    -						"    focal_length = -1  # default\n",
    -						"    stride = 8\n",
    -						"    player = None\n",
    -						"    skeleton_set = None\n",
    -						"\n",
    -						"    try:\n",
    -						"        # create video player to play with target fps  video_path\n",
    -						"        # get the frame from camera\n",
    -						"        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n",
    -						"        player = utils.VideoPlayer(\n",
    -						"            source, flip=flip, fps=30, skip_first_frames=skip_frames\n",
    -						"        )\n",
    -						"        # start capturing\n",
    -						"        player.start()\n",
    -						"\n",
    -						"        input_image = player.next()\n",
    -						"        # set the window size\n",
    -						"        resize_scale = 450 / input_image.shape[1]\n",
    -						"        windows_width = int(input_image.shape[1] * resize_scale)\n",
    -						"        windows_height = int(input_image.shape[0] * resize_scale)\n",
    -						"\n",
    -						"        # use visualization library\n",
    -						"        engine3D = engine.Engine3js(\n",
    -						"            grid=True, axis=True, view_width=windows_width, view_height=windows_height\n",
    -						"        )\n",
    -						"\n",
    -						"        if use_popup:\n",
    -						"            # display the 3D human pose in this notebook, and origin frame in popup window\n",
    -						"            display(engine3D.renderer)\n",
    -						"            title = \"Press ESC to Exit\"\n",
    -						"            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n",
    -						"        else:\n",
    -						"            # set the 2D image box, show both human pose and image in the notebook\n",
    -						"            imgbox = widgets.Image(\n",
    -						"                format=\"jpg\", height=windows_height, width=windows_width\n",
    -						"            )\n",
    -						"            display(widgets.HBox([engine3D.renderer, imgbox]))\n",
    -						"\n",
    -						"        skeleton = engine.Skeleton(body_edges=body_edges)\n",
    -						"\n",
    -						"        processing_times = collections.deque()\n",
    -						"\n",
    -						"        while True:\n",
    -						"            # grab the frame\n",
    -						"            frame = player.next()\n",
    -						"            if frame is None:\n",
    -						"                print(\"Source ended\")\n",
    -						"                break\n",
    -						"\n",
    -						"            # resize image and change dims to fit neural network input\n",
    -						"            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n",
    -						"            scaled_img = cv2.resize(\n",
    -						"                frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2])\n",
    -						"            )\n",
    -						"\n",
    -						"            if focal_length < 0:  # Focal length is unknown\n",
    -						"                focal_length = np.float32(0.8 * scaled_img.shape[1])\n",
    -						"\n",
    -						"            # inference start\n",
    -						"            start_time = time.time()\n",
    -						"            # get results\n",
    -						"            inference_result = model_infer(scaled_img, stride)\n",
    -						"\n",
    -						"            # inference stop\n",
    -						"            stop_time = time.time()\n",
    -						"            processing_times.append(stop_time - start_time)\n",
    -						"            # Process the point to point coordinates of the data\n",
    -						"            poses_3d, poses_2d = parse_poses(\n",
    -						"                inference_result, 1, stride, focal_length, True\n",
    -						"            )\n",
    -						"\n",
    -						"            # use processing times from last 200 frames\n",
    -						"            if len(processing_times) > 200:\n",
    -						"                processing_times.popleft()\n",
    -						"\n",
    -						"            processing_time = np.mean(processing_times) * 1000\n",
    -						"            fps = 1000 / processing_time\n",
    -						"\n",
    -						"            if len(poses_3d) > 0:\n",
    -						"                # From here, you can rotate the 3D point positions using the function \"draw_poses\",\n",
    -						"                # or you can directly make the correct mapping below to properly display the object image on the screen\n",
    -						"                poses_3d_copy = poses_3d.copy()\n",
    -						"                x = poses_3d_copy[:, 0::4]\n",
    -						"                y = poses_3d_copy[:, 1::4]\n",
    -						"                z = poses_3d_copy[:, 2::4]\n",
    -						"                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n",
    -						"                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n",
    -						"                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n",
    -						"                    -x,\n",
    -						"                )\n",
    -						"\n",
    -						"                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n",
    -						"                people = skeleton(poses_3d=poses_3d)\n",
    -						"\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"                engine3D.scene_add(people)\n",
    -						"                skeleton_set = people\n",
    -						"\n",
    -						"                # draw 2D\n",
    -						"                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n",
    -						"\n",
    -						"            else:\n",
    -						"                try:\n",
    -						"                    engine3D.scene_remove(skeleton_set)\n",
    -						"                    skeleton_set = None\n",
    -						"                except Exception:\n",
    -						"                    pass\n",
    -						"\n",
    -						"            cv2.putText(\n",
    -						"                frame,\n",
    -						"                f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n",
    -						"                (10, 30),\n",
    -						"                cv2.FONT_HERSHEY_COMPLEX,\n",
    -						"                0.7,\n",
    -						"                (0, 0, 255),\n",
    -						"                1,\n",
    -						"                cv2.LINE_AA,\n",
    -						"            )\n",
    -						"\n",
    -						"            if use_popup:\n",
    -						"                cv2.imshow(title, frame)\n",
    -						"                key = cv2.waitKey(1)\n",
    -						"                # escape = 27, use ESC to exit\n",
    -						"                if key == 27:\n",
    -						"                    break\n",
    -						"            else:\n",
    -						"                # encode numpy array to jpg\n",
    -						"                imgbox.value = cv2.imencode(\n",
    -						"                    \".jpg\",\n",
    -						"                    frame,\n",
    -						"                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n",
    -						"                )[1].tobytes()\n",
    -						"\n",
    -						"            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n",
    -						"\n",
    -						"    except KeyboardInterrupt:\n",
    -						"        print(\"Interrupted\")\n",
    -						"    except RuntimeError as e:\n",
    -						"        print(e)\n",
    -						"    finally:\n",
    -						"        clear_output()\n",
    -						"        if player is not None:\n",
    -						"            # stop capturing\n",
    -						"            player.stop()\n",
    -						"        if use_popup:\n",
    -						"            cv2.destroyAllWindows()\n",
    -						"        if skeleton_set:\n",
    -						"            engine3D.scene_remove(skeleton_set)"
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"## Run\n",
    -						"[back to top ⬆️](#Table-of-contents:)\n",
    -						"\n",
    -						"Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n",
    -						"\n",
    -						"> **NOTE**:\n",
    -						">\n",
    -						"> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n",
    -						">\n",
    -						"> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n",
    -						"\n",
    -						"If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work."
    -					]
    -				},
    -				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"Using the following method, you can click and move your mouse over the picture on the left to interact."
    -					]
    -				},
    -				{
    -					"cell_type": "code",
    -					"metadata": {
    -						"tags": []
    -					},
    -					"source": [
    -						"USE_WEBCAM = False\n",
    -						"\n",
    -						"cam_id = 0\n",
    -						"video_path = \"https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4\"\n",
    -						"\n",
    -						"source = cam_id if USE_WEBCAM else video_path\n",
    -						"\n",
    -						"run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Live 3D Human Pose Estimation with OpenVINO\n',
    +						'\n',
    +						'This notebook demonstrates live 3D Human Pose Estimation with OpenVINO via a webcam. We utilize the model [human-pose-estimation-3d-0001](https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001) from [Open Model Zoo](https://github.com/openvinotoolkit/open_model_zoo/). At the end of this notebook, you will see live inference results from your webcam (if available). Alternatively, you can also upload a video file to test out the algorithms.\n',
    +						'**Make sure you have properly installed the [Jupyter extension](https://github.com/jupyter-widgets/pythreejs#jupyterlab) and been using JupyterLab to run the demo as suggested in the `README.md`**\n',
    +						'\n',
    +						'> **NOTE**: _To use a webcam, you must run this Jupyter notebook on a computer with a webcam. If you run on a remote server, the webcam will not work. However, you can still do inference on a video file in the final step. This demo utilizes the Python interface in `Three.js` integrated with WebGL to process data from the model inference. These results are processed and displayed in the notebook._\n',
    +						'\n',
    +						'_To ensure that the results are displayed correctly, run the code in a recommended browser on one of the following operating systems:_\n',
    +						'_Ubuntu, Windows: Chrome_\n',
    +						'_macOS: Safari_\n',
    +						'\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Prerequisites](#Prerequisites)\n',
    +						'- [Imports](#Imports)\n',
    +						'- [The model](#The-model)\n',
    +						'    - [Download the model](#Download-the-model)\n',
    +						'    - [Convert Model to OpenVINO IR format](#Convert-Model-to-OpenVINO-IR-format)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Load the model](#Load-the-model)\n',
    +						'- [Processing](#Processing)\n',
    +						'    - [Model Inference](#Model-Inference)\n',
    +						'    - [Draw 2D Pose Overlays](#Draw-2D-Pose-Overlays)\n',
    +						'    - [Main Processing Function](#Main-Processing-Function)\n',
    +						'- [Run](#Run)\n',
    +						'\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Prerequisites\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'**The `pythreejs` extension may not display properly when using the latest Jupyter Notebook release (2.4.1). Therefore, it is recommended to use Jupyter Lab instead.**'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'%pip install pythreejs "openvino-dev>=2024.0.0" "opencv-python" "torch" "onnx" --extra-index-url https://download.pytorch.org/whl/cpu'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Imports\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'import collections\n',
    +						'import sys\n',
    +						'import time\n',
    +						'from pathlib import Path\n',
    +						'\n',
    +						'import cv2\n',
    +						'import ipywidgets as widgets\n',
    +						'import numpy as np\n',
    +						'from IPython.display import clear_output, display\n',
    +						'import openvino as ov\n',
    +						'\n',
    +						'# Fetch `notebook_utils` module\n',
    +						'import requests\n',
    +						'\n',
    +						'r = requests.get(\n',
    +						'    url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n',
    +						')\n',
    +						'open("notebook_utils.py", "w").write(r.text)\n',
    +						'import notebook_utils as utils\n',
    +						'\n',
    +						'sys.path.append("./engine")\n',
    +						'import engine.engine3js as engine\n',
    +						'from engine.parse_poses import parse_poses'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## The model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Download the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We use `omz_downloader`, which is a command line tool from the `openvino-dev` package. `omz_downloader` automatically creates a directory structure and downloads the selected model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# directory where model will be downloaded\n',
    +						'base_model_dir = "model"\n',
    +						'\n',
    +						'# model name as named in Open Model Zoo\n',
    +						'model_name = "human-pose-estimation-3d-0001"\n',
    +						'# selected precision (FP32, FP16)\n',
    +						'precision = "FP32"\n',
    +						'\n',
    +						'BASE_MODEL_NAME = f"{base_model_dir}/public/{model_name}/{model_name}"\n',
    +						'model_path = Path(BASE_MODEL_NAME).with_suffix(".pth")\n',
    +						'onnx_path = Path(BASE_MODEL_NAME).with_suffix(".onnx")\n',
    +						'\n',
    +						'ir_model_path = f"model/public/{model_name}/{precision}/{model_name}.xml"\n',
    +						'model_weights_path = f"model/public/{model_name}/{precision}/{model_name}.bin"\n',
    +						'\n',
    +						'if not model_path.exists():\n',
    +						'    download_command = (\n',
    +						'        f"omz_downloader " f"--name {model_name} " f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $download_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Convert Model to OpenVINO IR format\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'The selected model comes from the public directory, which means it must be converted into OpenVINO Intermediate Representation (OpenVINO IR). We use `omz_converter` to convert the ONNX format model to the OpenVINO IR format.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'if not onnx_path.exists():\n',
    +						'    convert_command = (\n',
    +						'        f"omz_converter "\n',
    +						'        f"--name {model_name} "\n',
    +						'        f"--precisions {precision} "\n',
    +						'        f"--download_dir {base_model_dir} "\n',
    +						'        f"--output_dir {base_model_dir}"\n',
    +						'    )\n',
    +						'    ! $convert_command'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Select inference device\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'select device from dropdown list for running inference using OpenVINO'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'core = ov.Core()\n',
    +						'\n',
    +						'device = widgets.Dropdown(\n',
    +						'    options=core.available_devices + ["AUTO"],\n',
    +						'    value="AUTO",\n',
    +						'    description="Device:",\n',
    +						'    disabled=False,\n',
    +						')\n',
    +						'\n',
    +						'device'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Load the model\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Converted models are located in a fixed structure, which indicates vendor, model name and precision.\n',
    +						'\n',
    +						'First, initialize the inference engine, OpenVINO Runtime. Then, read the network architecture and model weights from the `.bin` and `.xml` files to compile for the desired device. An inference request is then created to infer the compiled model.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# initialize inference engine\n',
    +						'core = ov.Core()\n',
    +						'# read the network and corresponding weights from file\n',
    +						'model = core.read_model(model=ir_model_path, weights=model_weights_path)\n',
    +						'# load the model on the specified device\n',
    +						'compiled_model = core.compile_model(model=model, device_name=device.value)\n',
    +						'infer_request = compiled_model.create_infer_request()\n',
    +						'input_tensor_name = model.inputs[0].get_any_name()\n',
    +						'\n',
    +						'# get input and output names of nodes\n',
    +						'input_layer = compiled_model.input(0)\n',
    +						'output_layers = list(compiled_model.outputs)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The input for the model is data from the input image and the outputs are heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'input_layer.any_name, [o.any_name for o in output_layers]'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Processing\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'### Model Inference\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Frames captured from video files or the live webcam are used as the input for the 3D model. This is how you obtain the output heat maps, PAF (part affinity fields) and features.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'def model_infer(scaled_img, stride):\n',
    +						'    """\n',
    +						'    Run model inference on the input image\n',
    +						'\n',
    +						'    Parameters:\n',
    +						'        scaled_img: resized image according to the input size of the model\n',
    +						'        stride: int, the stride of the window\n',
    +						'    """\n',
    +						'\n',
    +						'    # Remove excess space from the picture\n',
    +						'    img = scaled_img[\n',
    +						'        0 : scaled_img.shape[0] - (scaled_img.shape[0] % stride),\n',
    +						'        0 : scaled_img.shape[1] - (scaled_img.shape[1] % stride),\n',
    +						'    ]\n',
    +						'\n',
    +						'    img = np.transpose(img, (2, 0, 1))[None,]\n',
    +						'    infer_request.infer({input_tensor_name: img})\n',
    +						'    # A set of three inference results is obtained\n',
    +						'    results = {\n',
    +						'        name: infer_request.get_tensor(name).data[:]\n',
    +						'        for name in {"features", "heatmaps", "pafs"}\n',
    +						'    }\n',
    +						'    # Get the results\n',
    +						'    results = (results["features"][0], results["heatmaps"][0], results["pafs"][0])\n',
    +						'\n',
    +						'    return results'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Draw 2D Pose Overlays\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'We need to define some connections between the joints in advance, so that we can draw the structure of the human body in the resulting image after obtaining the inference results.\n',
    +						'Joints are drawn as circles and limbs are drawn as lines. The code is based on the [3D Human Pose Estimation Demo](https://github.com/openvinotoolkit/open_model_zoo/tree/master/demos/human_pose_estimation_3d_demo/python) from Open Model Zoo.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'source': [
    +						'# 3D edge index array\n',
    +						'body_edges = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [1, 15],\n',
    +						'        [15, 16],  # nose - l_eye - l_ear\n',
    +						'        [1, 17],\n',
    +						'        [17, 18],  # nose - r_eye - r_ear\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'body_edges_2d = np.array(\n',
    +						'    [\n',
    +						'        [0, 1],  # neck - nose\n',
    +						'        [1, 16],\n',
    +						'        [16, 18],  # nose - l_eye - l_ear\n',
    +						'        [1, 15],\n',
    +						'        [15, 17],  # nose - r_eye - r_ear\n',
    +						'        [0, 3],\n',
    +						'        [3, 4],\n',
    +						'        [4, 5],  # neck - l_shoulder - l_elbow - l_wrist\n',
    +						'        [0, 9],\n',
    +						'        [9, 10],\n',
    +						'        [10, 11],  # neck - r_shoulder - r_elbow - r_wrist\n',
    +						'        [0, 6],\n',
    +						'        [6, 7],\n',
    +						'        [7, 8],  # neck - l_hip - l_knee - l_ankle\n',
    +						'        [0, 12],\n',
    +						'        [12, 13],\n',
    +						'        [13, 14],  # neck - r_hip - r_knee - r_ankle\n',
    +						'    ]\n',
    +						')\n',
    +						'\n',
    +						'\n',
    +						'def draw_poses(frame, poses_2d, scaled_img, use_popup):\n',
    +						'    """\n',
    +						'    Draw 2D pose overlays on the image to visualize estimated poses.\n',
    +						'    Joints are drawn as circles and limbs are drawn as lines.\n',
    +						'\n',
    +						'    :param frame: the input image\n',
    +						'    :param poses_2d: array of human joint pairs\n',
    +						'    """\n',
    +						'    for pose in poses_2d:\n',
    +						'        pose = np.array(pose[0:-1]).reshape((-1, 3)).transpose()\n',
    +						'        was_found = pose[2] > 0\n',
    +						'\n',
    +						'        pose[0], pose[1] = (\n',
    +						'            pose[0] * frame.shape[1] / scaled_img.shape[1],\n',
    +						'            pose[1] * frame.shape[0] / scaled_img.shape[0],\n',
    +						'        )\n',
    +						'\n',
    +						'        # Draw joints.\n',
    +						'        for edge in body_edges_2d:\n',
    +						'            if was_found[edge[0]] and was_found[edge[1]]:\n',
    +						'                cv2.line(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, edge[0]].astype(np.int32)),\n',
    +						'                    tuple(pose[0:2, edge[1]].astype(np.int32)),\n',
    +						'                    (255, 255, 0),\n',
    +						'                    4,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'        # Draw limbs.\n',
    +						'        for kpt_id in range(pose.shape[1]):\n',
    +						'            if pose[2, kpt_id] != -1:\n',
    +						'                cv2.circle(\n',
    +						'                    frame,\n',
    +						'                    tuple(pose[0:2, kpt_id].astype(np.int32)),\n',
    +						'                    3,\n',
    +						'                    (0, 255, 255),\n',
    +						'                    -1,\n',
    +						'                    cv2.LINE_AA,\n',
    +						'                )\n',
    +						'\n',
    +						'    return frame'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'### Main Processing Function\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run 3D pose estimation on the specified source. It could be either a webcam feed or a video file.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'def run_pose_estimation(source=0, flip=False, use_popup=False, skip_frames=0):\n',
    +						'    """\n',
    +						'    2D image as input, using OpenVINO as inference backend,\n',
    +						'    get joints 3D coordinates, and draw 3D human skeleton in the scene\n',
    +						'\n',
    +						'    :param source:      The webcam number to feed the video stream with primary webcam set to "0", or the video path.\n',
    +						'    :param flip:        To be used by VideoPlayer function for flipping capture image.\n',
    +						'    :param use_popup:   False for showing encoded frames over this notebook, True for creating a popup window.\n',
    +						'    :param skip_frames: Number of frames to skip at the beginning of the video.\n',
    +						'    """\n',
    +						'\n',
    +						'    focal_length = -1  # default\n',
    +						'    stride = 8\n',
    +						'    player = None\n',
    +						'    skeleton_set = None\n',
    +						'\n',
    +						'    try:\n',
    +						'        # create video player to play with target fps  video_path\n',
    +						'        # get the frame from camera\n',
    +						`        # You can skip first N frames to fast forward video. change 'skip_first_frames'\n`,
    +						'        player = utils.VideoPlayer(\n',
    +						'            source, flip=flip, fps=30, skip_first_frames=skip_frames\n',
    +						'        )\n',
    +						'        # start capturing\n',
    +						'        player.start()\n',
    +						'\n',
    +						'        input_image = player.next()\n',
    +						'        # set the window size\n',
    +						'        resize_scale = 450 / input_image.shape[1]\n',
    +						'        windows_width = int(input_image.shape[1] * resize_scale)\n',
    +						'        windows_height = int(input_image.shape[0] * resize_scale)\n',
    +						'\n',
    +						'        # use visualization library\n',
    +						'        engine3D = engine.Engine3js(\n',
    +						'            grid=True, axis=True, view_width=windows_width, view_height=windows_height\n',
    +						'        )\n',
    +						'\n',
    +						'        if use_popup:\n',
    +						'            # display the 3D human pose in this notebook, and origin frame in popup window\n',
    +						'            display(engine3D.renderer)\n',
    +						'            title = "Press ESC to Exit"\n',
    +						'            cv2.namedWindow(title, cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)\n',
    +						'        else:\n',
    +						'            # set the 2D image box, show both human pose and image in the notebook\n',
    +						'            imgbox = widgets.Image(\n',
    +						'                format="jpg", height=windows_height, width=windows_width\n',
    +						'            )\n',
    +						'            display(widgets.HBox([engine3D.renderer, imgbox]))\n',
    +						'\n',
    +						'        skeleton = engine.Skeleton(body_edges=body_edges)\n',
    +						'\n',
    +						'        processing_times = collections.deque()\n',
    +						'\n',
    +						'        while True:\n',
    +						'            # grab the frame\n',
    +						'            frame = player.next()\n',
    +						'            if frame is None:\n',
    +						'                print("Source ended")\n',
    +						'                break\n',
    +						'\n',
    +						'            # resize image and change dims to fit neural network input\n',
    +						'            # (see https://github.com/openvinotoolkit/open_model_zoo/tree/master/models/public/human-pose-estimation-3d-0001)\n',
    +						'            scaled_img = cv2.resize(\n',
    +						'                frame, dsize=(model.inputs[0].shape[3], model.inputs[0].shape[2])\n',
    +						'            )\n',
    +						'\n',
    +						'            if focal_length < 0:  # Focal length is unknown\n',
    +						'                focal_length = np.float32(0.8 * scaled_img.shape[1])\n',
    +						'\n',
    +						'            # inference start\n',
    +						'            start_time = time.time()\n',
    +						'            # get results\n',
    +						'            inference_result = model_infer(scaled_img, stride)\n',
    +						'\n',
    +						'            # inference stop\n',
    +						'            stop_time = time.time()\n',
    +						'            processing_times.append(stop_time - start_time)\n',
    +						'            # Process the point to point coordinates of the data\n',
    +						'            poses_3d, poses_2d = parse_poses(\n',
    +						'                inference_result, 1, stride, focal_length, True\n',
    +						'            )\n',
    +						'\n',
    +						'            # use processing times from last 200 frames\n',
    +						'            if len(processing_times) > 200:\n',
    +						'                processing_times.popleft()\n',
    +						'\n',
    +						'            processing_time = np.mean(processing_times) * 1000\n',
    +						'            fps = 1000 / processing_time\n',
    +						'\n',
    +						'            if len(poses_3d) > 0:\n',
    +						'                # From here, you can rotate the 3D point positions using the function "draw_poses",\n',
    +						'                # or you can directly make the correct mapping below to properly display the object image on the screen\n',
    +						'                poses_3d_copy = poses_3d.copy()\n',
    +						'                x = poses_3d_copy[:, 0::4]\n',
    +						'                y = poses_3d_copy[:, 1::4]\n',
    +						'                z = poses_3d_copy[:, 2::4]\n',
    +						'                poses_3d[:, 0::4], poses_3d[:, 1::4], poses_3d[:, 2::4] = (\n',
    +						'                    -z + np.ones(poses_3d[:, 2::4].shape) * 200,\n',
    +						'                    -y + np.ones(poses_3d[:, 2::4].shape) * 100,\n',
    +						'                    -x,\n',
    +						'                )\n',
    +						'\n',
    +						'                poses_3d = poses_3d.reshape(poses_3d.shape[0], 19, -1)[:, :, 0:3]\n',
    +						'                people = skeleton(poses_3d=poses_3d)\n',
    +						'\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'                engine3D.scene_add(people)\n',
    +						'                skeleton_set = people\n',
    +						'\n',
    +						'                # draw 2D\n',
    +						'                frame = draw_poses(frame, poses_2d, scaled_img, use_popup)\n',
    +						'\n',
    +						'            else:\n',
    +						'                try:\n',
    +						'                    engine3D.scene_remove(skeleton_set)\n',
    +						'                    skeleton_set = None\n',
    +						'                except Exception:\n',
    +						'                    pass\n',
    +						'\n',
    +						'            cv2.putText(\n',
    +						'                frame,\n',
    +						'                f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n',
    +						'                (10, 30),\n',
    +						'                cv2.FONT_HERSHEY_COMPLEX,\n',
    +						'                0.7,\n',
    +						'                (0, 0, 255),\n',
    +						'                1,\n',
    +						'                cv2.LINE_AA,\n',
    +						'            )\n',
    +						'\n',
    +						'            if use_popup:\n',
    +						'                cv2.imshow(title, frame)\n',
    +						'                key = cv2.waitKey(1)\n',
    +						'                # escape = 27, use ESC to exit\n',
    +						'                if key == 27:\n',
    +						'                    break\n',
    +						'            else:\n',
    +						'                # encode numpy array to jpg\n',
    +						'                imgbox.value = cv2.imencode(\n',
    +						'                    ".jpg",\n',
    +						'                    frame,\n',
    +						'                    params=[cv2.IMWRITE_JPEG_QUALITY, 90],\n',
    +						'                )[1].tobytes()\n',
    +						'\n',
    +						'            engine3D.renderer.render(engine3D.scene, engine3D.cam)\n',
    +						'\n',
    +						'    except KeyboardInterrupt:\n',
    +						'        print("Interrupted")\n',
    +						'    except RuntimeError as e:\n',
    +						'        print(e)\n',
    +						'    finally:\n',
    +						'        clear_output()\n',
    +						'        if player is not None:\n',
    +						'            # stop capturing\n',
    +						'            player.stop()\n',
    +						'        if use_popup:\n',
    +						'            cv2.destroyAllWindows()\n',
    +						'        if skeleton_set:\n',
    +						'            engine3D.scene_remove(skeleton_set)'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'## Run\n',
    +						'[back to top ⬆️](#Table-of-contents:)\n',
    +						'\n',
    +						'Run, using a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n',
    +						'\n',
    +						'> **NOTE**:\n',
    +						'>\n',
    +						'> *1. To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server (e.g. Binder), the webcam will not work.*\n',
    +						'>\n',
    +						'> *2. Popup mode may not work if you run this notebook on a remote computer (e.g. Binder).*\n',
    +						'\n',
    +						'If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.'
    +					]
    +				},
    +				{
    +					'cell_type': 'markdown',
    +					'source': [
    +						'Using the following method, you can click and move your mouse over the picture on the left to interact.'
    +					]
    +				},
    +				{
    +					'cell_type': 'code',
    +					'metadata': {
    +						'tags': []
    +					},
    +					'source': [
    +						'USE_WEBCAM = False\n',
    +						'\n',
    +						'cam_id = 0\n',
    +						'video_path = "https://github.com/intel-iot-devkit/sample-videos/raw/master/face-demographics-walking.mp4"\n',
    +						'\n',
    +						'source = cam_id if USE_WEBCAM else video_path\n',
    +						'\n',
    +						'run_pose_estimation(source=source, flip=isinstance(source, int), use_popup=False)'
     					]
     				}
     			].map(fromJupyterCell)
    @@ -4776,1806 +4776,1806 @@ suite('NotebookDiff Diff Service', () => {
     		const { mapping, diff } = await mapCells(
     			[
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"# Video generation with ZeroScope and OpenVINO\n",
    -						"\n",
    -						"#### Table of contents:\n",
    -						"\n",
    -						"- [Install and import required packages](#Install-and-import-required-packages)\n",
    -						"- [Load the model](#Load-the-model)\n",
    -						"- [Convert the model](#Convert-the-model)\n",
    -						"    - [Define the conversion function](#Define-the-conversion-function)\n",
    -						"    - [UNet](#UNet)\n",
    -						"    - [VAE](#VAE)\n",
    -						"    - [Text encoder](#Text-encoder)\n",
    -						"- [Build a pipeline](#Build-a-pipeline)\n",
    -						"- [Inference with OpenVINO](#Inference-with-OpenVINO)\n",
    -						"    - [Select inference device](#Select-inference-device)\n",
    -						"    - [Define a prompt](#Define-a-prompt)\n",
    -						"    - [Video generation](#Video-generation)\n",
    -						"- [Interactive demo](#Interactive-demo)\n",
    -						"\n",
    -						"\n",
    -						"### Installation Instructions\n",
    -						"\n",
    -						"This is a self-contained example that relies solely on its own code.\n",
    -						"\n",
    -						"We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n",
    -						"For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n",
    -						"\n",
    -						"\n"
    +					'cell_type': 'markdown',
    +					'source': [
    +						'# Video generation with ZeroScope and OpenVINO\n',
    +						'\n',
    +						'#### Table of contents:\n',
    +						'\n',
    +						'- [Install and import required packages](#Install-and-import-required-packages)\n',
    +						'- [Load the model](#Load-the-model)\n',
    +						'- [Convert the model](#Convert-the-model)\n',
    +						'    - [Define the conversion function](#Define-the-conversion-function)\n',
    +						'    - [UNet](#UNet)\n',
    +						'    - [VAE](#VAE)\n',
    +						'    - [Text encoder](#Text-encoder)\n',
    +						'- [Build a pipeline](#Build-a-pipeline)\n',
    +						'- [Inference with OpenVINO](#Inference-with-OpenVINO)\n',
    +						'    - [Select inference device](#Select-inference-device)\n',
    +						'    - [Define a prompt](#Define-a-prompt)\n',
    +						'    - [Video generation](#Video-generation)\n',
    +						'- [Interactive demo](#Interactive-demo)\n',
    +						'\n',
    +						'\n',
    +						'### Installation Instructions\n',
    +						'\n',
    +						'This is a self-contained example that relies solely on its own code.\n',
    +						'\n',
    +						'We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n',
    +						'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n',
    +						'\n',
    +						'\n'
     					]
     				},
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n",
    -						"\n",
    -						"The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n",
    -						"\n",
    -						"The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n",
    -						"\n",
    -						"Both versions of the ZeroScope model are available on Hugging Face:\n",
    -						" - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n",
    -						" - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n",
    -						"\n",
    -						"We will use the first one."
    +					'cell_type': 'markdown',
    +					'source': [
    +						'The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n',
    +						'\n',
    +						'The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n',
    +						'\n',
    +						'The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n',
    +						'\n',
    +						'Both versions of the ZeroScope model are available on Hugging Face:\n',
    +						' - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n',
    +						' - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n',
    +						'\n',
    +						'We will use the first one.'
     					]
     				},
     				{
    -					"cell_type": "markdown",
    -					"source": [
    -						"
    \n", - " This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n", - "
    " + 'cell_type': 'markdown', + 'source': [ + '
    \n', + ' This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n', + '
    ' ] }, { - "cell_type": "markdown", - "source": [ - "## Install and import required packages\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Install and import required packages\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "To work with text-to-video synthesis model, we will use Hugging Face's [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`." + 'cell_type': 'markdown', + 'source': [ + 'To work with text-to-video synthesis model, we will use Hugging Face\'s [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`.' ] }, { - "cell_type": "code", - "source": [ - "%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu \"diffusers>=0.18.0\" \"torch>=2.1\" transformers \"openvino>=2023.1.0\" numpy \"gradio>=4.19\"" + 'cell_type': 'code', + 'source': [ + '%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu "diffusers>=0.18.0" "torch>=2.1" transformers "openvino>=2023.1.0" numpy "gradio>=4.19"' ] }, { - "cell_type": "code", - "source": [ - "import gc\n", - "from typing import Optional, Union, List, Callable\n", - "import base64\n", - "import tempfile\n", - "import warnings\n", - "\n", - "import diffusers\n", - "import transformers\n", - "import numpy as np\n", - "import IPython\n", - "import torch\n", - "import PIL\n", - "import gradio as gr\n", - "\n", - "import openvino as ov" + 'cell_type': 'code', + 'source': [ + 'import gc\n', + 'from typing import Optional, Union, List, Callable\n', + 'import base64\n', + 'import tempfile\n', + 'import warnings\n', + '\n', + 'import diffusers\n', + 'import transformers\n', + 'import numpy as np\n', + 'import IPython\n', + 'import torch\n', + 'import PIL\n', + 'import gradio as gr\n', + '\n', + 'import openvino as ov' ] }, { - "cell_type": "markdown", - "source": [ - "Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption." + 'cell_type': 'markdown', + 'source': [ + `Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption.` ] }, { - "cell_type": "code", - "source": [ - "WIDTH = 432 # must be divisible by 8\n", - "HEIGHT = 240 # must be divisible by 8\n", - "NUM_FRAMES = 16" + 'cell_type': 'code', + 'source': [ + 'WIDTH = 432 # must be divisible by 8\n', + 'HEIGHT = 240 # must be divisible by 8\n', + 'NUM_FRAMES = 16' ] }, { - "cell_type": "markdown", - "source": [ - "## Load the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Load the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`." + 'cell_type': 'markdown', + 'source': [ + 'The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`.' ] }, { - "cell_type": "code", - "source": [ - "pipe = diffusers.DiffusionPipeline.from_pretrained(\"cerspense/zeroscope_v2_576w\")" + 'cell_type': 'code', + 'source': [ + 'pipe = diffusers.DiffusionPipeline.from_pretrained("cerspense/zeroscope_v2_576w")' ] }, { - "cell_type": "code", - "source": [ - "unet = pipe.unet\n", - "unet.eval()\n", - "vae = pipe.vae\n", - "vae.eval()\n", - "text_encoder = pipe.text_encoder\n", - "text_encoder.eval()\n", - "tokenizer = pipe.tokenizer\n", - "scheduler = pipe.scheduler\n", - "vae_scale_factor = pipe.vae_scale_factor\n", - "unet_in_channels = pipe.unet.config.in_channels\n", - "sample_width = WIDTH // vae_scale_factor\n", - "sample_height = HEIGHT // vae_scale_factor\n", - "del pipe\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet = pipe.unet\n', + 'unet.eval()\n', + 'vae = pipe.vae\n', + 'vae.eval()\n', + 'text_encoder = pipe.text_encoder\n', + 'text_encoder.eval()\n', + 'tokenizer = pipe.tokenizer\n', + 'scheduler = pipe.scheduler\n', + 'vae_scale_factor = pipe.vae_scale_factor\n', + 'unet_in_channels = pipe.unet.config.in_channels\n', + 'sample_width = WIDTH // vae_scale_factor\n', + 'sample_height = HEIGHT // vae_scale_factor\n', + 'del pipe\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "## Convert the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Convert the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video." + 'cell_type': 'markdown', + 'source': [ + `The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video.` ] }, { - "cell_type": "markdown", - "source": [] + 'cell_type': 'markdown', + 'source': [] }, { - "cell_type": "markdown", - "source": [ - "### Define the conversion function\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define the conversion function\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion." + 'cell_type': 'markdown', + 'source': [ + 'Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion.' ] }, { - "cell_type": "code", - "source": [ - "warnings.filterwarnings(\"ignore\", category=torch.jit.TracerWarning)" + 'cell_type': 'code', + 'source': [ + 'warnings.filterwarnings("ignore", category=torch.jit.TracerWarning)' ] }, { - "cell_type": "code", - "source": [ - "from pathlib import Path\n", - "\n", - "\n", - "def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n", - " xml_path = Path(xml_path)\n", - " if not xml_path.exists():\n", - " xml_path.parent.mkdir(parents=True, exist_ok=True)\n", - " with torch.no_grad():\n", - " converted_model = ov.convert_model(model, **convert_kwargs)\n", - " ov.save_model(converted_model, xml_path)\n", - " del converted_model\n", - " gc.collect()\n", - " torch._C._jit_clear_class_registry()\n", - " torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n", - " torch.jit._state._clear_class_state()\n", - " return xml_path" + 'cell_type': 'code', + 'source': [ + 'from pathlib import Path\n', + '\n', + '\n', + 'def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n', + ' xml_path = Path(xml_path)\n', + ' if not xml_path.exists():\n', + ' xml_path.parent.mkdir(parents=True, exist_ok=True)\n', + ' with torch.no_grad():\n', + ' converted_model = ov.convert_model(model, **convert_kwargs)\n', + ' ov.save_model(converted_model, xml_path)\n', + ' del converted_model\n', + ' gc.collect()\n', + ' torch._C._jit_clear_class_registry()\n', + ' torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n', + ' torch.jit._state._clear_class_state()\n', + ' return xml_path' ] }, { - "cell_type": "markdown", - "source": [ - "### UNet\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### UNet\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output." + 'cell_type': 'markdown', + 'source': [ + 'Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output.' ] }, { - "cell_type": "code", - "source": [ - "unet_xml_path = convert(\n", - " unet,\n", - " \"models/unet.xml\",\n", - " example_input={\n", - " \"sample\": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n", - " \"timestep\": torch.tensor(1),\n", - " \"encoder_hidden_states\": torch.randn(2, 77, 1024),\n", - " },\n", - " input=[\n", - " (\"sample\", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n", - " (\"timestep\", ()),\n", - " (\"encoder_hidden_states\", (2, 77, 1024)),\n", - " ],\n", - ")\n", - "del unet\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet_xml_path = convert(\n', + ' unet,\n', + ' "models/unet.xml",\n', + ' example_input={\n', + ' "sample": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n', + ' "timestep": torch.tensor(1),\n', + ' "encoder_hidden_states": torch.randn(2, 77, 1024),\n', + ' },\n', + ' input=[\n', + ' ("sample", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n', + ' ("timestep", ()),\n', + ' ("encoder_hidden_states", (2, 77, 1024)),\n', + ' ],\n', + ')\n', + 'del unet\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "### VAE\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### VAE\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part." + 'cell_type': 'markdown', + 'source': [ + 'Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part.' ] }, { - "cell_type": "code", - "source": [ - "class VaeDecoderWrapper(torch.nn.Module):\n", - " def __init__(self, vae):\n", - " super().__init__()\n", - " self.vae = vae\n", - "\n", - " def forward(self, z: torch.FloatTensor):\n", - " return self.vae.decode(z)" + 'cell_type': 'code', + 'source': [ + 'class VaeDecoderWrapper(torch.nn.Module):\n', + ' def __init__(self, vae):\n', + ' super().__init__()\n', + ' self.vae = vae\n', + '\n', + ' def forward(self, z: torch.FloatTensor):\n', + ' return self.vae.decode(z)' ] }, { - "cell_type": "code", - "source": [ - "vae_decoder_xml_path = convert(\n", - " VaeDecoderWrapper(vae),\n", - " \"models/vae.xml\",\n", - " example_input=torch.randn(2, 4, 32, 32),\n", - " input=((NUM_FRAMES, 4, sample_height, sample_width)),\n", - ")\n", - "del vae\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Text encoder\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Text encoder is used to encode the input prompt to tensor. Default tensor length is 77." - ] - }, - { - "cell_type": "code", - "source": [ - "text_encoder_xml = convert(\n", - " text_encoder,\n", - " \"models/text_encoder.xml\",\n", - " example_input=torch.ones(1, 77, dtype=torch.int64),\n", - " input=((1, 77), ov.Type.i64),\n", - ")\n", - "del text_encoder\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Build a pipeline\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n", - " # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n", - " # reshape to ncfhw\n", - " mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " # unnormalize back to [0,1]\n", - " video = video.mul_(std).add_(mean)\n", - " video.clamp_(0, 1)\n", - " # prepare the final outputs\n", - " i, c, f, h, w = video.shape\n", - " images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n", - " images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n", - " images = [(image.cpu().numpy() * 255).astype(\"uint8\") for image in images] # f h w c\n", - " return images" - ] - }, - { - "cell_type": "code", - "source": [ - "try:\n", - " from diffusers.utils import randn_tensor\n", - "except ImportError:\n", - " from diffusers.utils.torch_utils import randn_tensor\n", - "\n", - "\n", - "class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n", - " def __init__(\n", - " self,\n", - " vae_decoder: ov.CompiledModel,\n", - " text_encoder: ov.CompiledModel,\n", - " tokenizer: transformers.CLIPTokenizer,\n", - " unet: ov.CompiledModel,\n", - " scheduler: diffusers.schedulers.DDIMScheduler,\n", - " ):\n", - " super().__init__()\n", - "\n", - " self.vae_decoder = vae_decoder\n", - " self.text_encoder = text_encoder\n", - " self.tokenizer = tokenizer\n", - " self.unet = unet\n", - " self.scheduler = scheduler\n", - " self.vae_scale_factor = vae_scale_factor\n", - " self.unet_in_channels = unet_in_channels\n", - " self.width = WIDTH\n", - " self.height = HEIGHT\n", - " self.num_frames = NUM_FRAMES\n", - "\n", - " def __call__(\n", - " self,\n", - " prompt: Union[str, List[str]] = None,\n", - " num_inference_steps: int = 50,\n", - " guidance_scale: float = 9.0,\n", - " negative_prompt: Optional[Union[str, List[str]]] = None,\n", - " eta: float = 0.0,\n", - " generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n", - " latents: Optional[torch.FloatTensor] = None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " output_type: Optional[str] = \"np\",\n", - " return_dict: bool = True,\n", - " callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n", - " callback_steps: int = 1,\n", - " ):\n", - " r\"\"\"\n", - " Function invoked when calling the pipeline for generation.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n", - " instead.\n", - " num_inference_steps (`int`, *optional*, defaults to 50):\n", - " The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n", - " expense of slower inference.\n", - " guidance_scale (`float`, *optional*, defaults to 7.5):\n", - " Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n", - " `guidance_scale` is defined as `w` of equation 2. of [Imagen\n", - " Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n", - " 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n", - " usually at the expense of lower video quality.\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the video generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " eta (`float`, *optional*, defaults to 0.0):\n", - " Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n", - " [`schedulers.DDIMScheduler`], will be ignored for others.\n", - " generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n", - " One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n", - " to make generation deterministic.\n", - " latents (`torch.FloatTensor`, *optional*):\n", - " Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n", - " generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n", - " tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n", - " `(batch_size, num_channel, num_frames, height, width)`.\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " output_type (`str`, *optional*, defaults to `\"np\"`):\n", - " The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n", - " return_dict (`bool`, *optional*, defaults to `True`):\n", - " Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n", - " plain tuple.\n", - " callback (`Callable`, *optional*):\n", - " A function that will be called every `callback_steps` steps during inference. The function will be\n", - " called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n", - " callback_steps (`int`, *optional*, defaults to 1):\n", - " The frequency at which the `callback` function will be called. If not specified, the callback will be\n", - " called at every step.\n", - "\n", - " Returns:\n", - " `List[np.ndarray]`: generated video frames\n", - " \"\"\"\n", - "\n", - " num_images_per_prompt = 1\n", - "\n", - " # 1. Check inputs. Raise error if not correct\n", - " self.check_inputs(\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt,\n", - " prompt_embeds,\n", - " negative_prompt_embeds,\n", - " )\n", - "\n", - " # 2. Define call parameters\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n", - " # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n", - " # corresponds to doing no classifier free guidance.\n", - " do_classifier_free_guidance = guidance_scale > 1.0\n", - "\n", - " # 3. Encode input prompt\n", - " prompt_embeds = self._encode_prompt(\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt,\n", - " prompt_embeds=prompt_embeds,\n", - " negative_prompt_embeds=negative_prompt_embeds,\n", - " )\n", - "\n", - " # 4. Prepare timesteps\n", - " self.scheduler.set_timesteps(num_inference_steps)\n", - " timesteps = self.scheduler.timesteps\n", - "\n", - " # 5. Prepare latent variables\n", - " num_channels_latents = self.unet_in_channels\n", - " latents = self.prepare_latents(\n", - " batch_size * num_images_per_prompt,\n", - " num_channels_latents,\n", - " prompt_embeds.dtype,\n", - " generator,\n", - " latents,\n", - " )\n", - "\n", - " # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n", - " extra_step_kwargs = {\"generator\": generator, \"eta\": eta}\n", - "\n", - " # 7. Denoising loop\n", - " num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n", - " with self.progress_bar(total=num_inference_steps) as progress_bar:\n", - " for i, t in enumerate(timesteps):\n", - " # expand the latents if we are doing classifier free guidance\n", - " latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n", - " latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n", - "\n", - " # predict the noise residual\n", - " noise_pred = self.unet(\n", - " {\n", - " \"sample\": latent_model_input,\n", - " \"timestep\": t,\n", - " \"encoder_hidden_states\": prompt_embeds,\n", - " }\n", - " )[0]\n", - " noise_pred = torch.tensor(noise_pred)\n", - "\n", - " # perform guidance\n", - " if do_classifier_free_guidance:\n", - " noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n", - " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", - "\n", - " # reshape latents\n", - " bsz, channel, frames, width, height = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - " noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - "\n", - " # compute the previous noisy sample x_t -> x_t-1\n", - " latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n", - "\n", - " # reshape latents back\n", - " latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n", - "\n", - " # call the callback, if provided\n", - " if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n", - " progress_bar.update()\n", - " if callback is not None and i % callback_steps == 0:\n", - " callback(i, t, latents)\n", - "\n", - " video_tensor = self.decode_latents(latents)\n", - "\n", - " if output_type == \"pt\":\n", - " video = video_tensor\n", - " else:\n", - " video = tensor2vid(video_tensor)\n", - "\n", - " if not return_dict:\n", - " return (video,)\n", - "\n", - " return {\"frames\": video}\n", - "\n", - " # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n", - " def _encode_prompt(\n", - " self,\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt=None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " ):\n", - " r\"\"\"\n", - " Encodes the prompt into text encoder hidden states.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " prompt to be encoded\n", - " num_images_per_prompt (`int`):\n", - " number of images that should be generated per prompt\n", - " do_classifier_free_guidance (`bool`):\n", - " whether to use classifier free guidance or not\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the image generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " \"\"\"\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " if prompt_embeds is None:\n", - " text_inputs = self.tokenizer(\n", - " prompt,\n", - " padding=\"max_length\",\n", - " max_length=self.tokenizer.model_max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - " text_input_ids = text_inputs.input_ids\n", - " untruncated_ids = self.tokenizer(prompt, padding=\"longest\", return_tensors=\"pt\").input_ids\n", - "\n", - " if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n", - " removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n", - " print(\n", - " \"The following part of your input was truncated because CLIP can only handle sequences up to\"\n", - " f\" {self.tokenizer.model_max_length} tokens: {removed_text}\"\n", - " )\n", - "\n", - " prompt_embeds = self.text_encoder(text_input_ids)\n", - " prompt_embeds = prompt_embeds[0]\n", - " prompt_embeds = torch.tensor(prompt_embeds)\n", - "\n", - " bs_embed, seq_len, _ = prompt_embeds.shape\n", - " # duplicate text embeddings for each generation per prompt, using mps friendly method\n", - " prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # get unconditional embeddings for classifier free guidance\n", - " if do_classifier_free_guidance and negative_prompt_embeds is None:\n", - " uncond_tokens: List[str]\n", - " if negative_prompt is None:\n", - " uncond_tokens = [\"\"] * batch_size\n", - " elif type(prompt) is not type(negative_prompt):\n", - " raise TypeError(f\"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=\" f\" {type(prompt)}.\")\n", - " elif isinstance(negative_prompt, str):\n", - " uncond_tokens = [negative_prompt]\n", - " elif batch_size != len(negative_prompt):\n", - " raise ValueError(\n", - " f\"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:\"\n", - " f\" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches\"\n", - " \" the batch size of `prompt`.\"\n", - " )\n", - " else:\n", - " uncond_tokens = negative_prompt\n", - "\n", - " max_length = prompt_embeds.shape[1]\n", - " uncond_input = self.tokenizer(\n", - " uncond_tokens,\n", - " padding=\"max_length\",\n", - " max_length=max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - "\n", - " negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n", - " negative_prompt_embeds = negative_prompt_embeds[0]\n", - " negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n", - "\n", - " if do_classifier_free_guidance:\n", - " # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n", - " seq_len = negative_prompt_embeds.shape[1]\n", - "\n", - " negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # For classifier free guidance, we need to do two forward passes.\n", - " # Here we concatenate the unconditional and text embeddings into a single batch\n", - " # to avoid doing two forward passes\n", - " prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n", - "\n", - " return prompt_embeds\n", - "\n", - " def prepare_latents(\n", - " self,\n", - " batch_size,\n", - " num_channels_latents,\n", - " dtype,\n", - " generator,\n", - " latents=None,\n", - " ):\n", - " shape = (\n", - " batch_size,\n", - " num_channels_latents,\n", - " self.num_frames,\n", - " self.height // self.vae_scale_factor,\n", - " self.width // self.vae_scale_factor,\n", - " )\n", - " if isinstance(generator, list) and len(generator) != batch_size:\n", - " raise ValueError(\n", - " f\"You have passed a list of generators of length {len(generator)}, but requested an effective batch\"\n", - " f\" size of {batch_size}. Make sure the batch size matches the length of the generators.\"\n", - " )\n", - "\n", - " if latents is None:\n", - " latents = randn_tensor(shape, generator=generator, dtype=dtype)\n", - "\n", - " # scale the initial noise by the standard deviation required by the scheduler\n", - " latents = latents * self.scheduler.init_noise_sigma\n", - " return latents\n", - "\n", - " def check_inputs(\n", - " self,\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt=None,\n", - " prompt_embeds=None,\n", - " negative_prompt_embeds=None,\n", - " ):\n", - " if self.height % 8 != 0 or self.width % 8 != 0:\n", - " raise ValueError(f\"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.\")\n", - "\n", - " if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n", - " raise ValueError(f\"`callback_steps` has to be a positive integer but is {callback_steps} of type\" f\" {type(callback_steps)}.\")\n", - "\n", - " if prompt is not None and prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to\" \" only forward one of the two.\"\n", - " )\n", - " elif prompt is None and prompt_embeds is None:\n", - " raise ValueError(\"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.\")\n", - " elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n", - " raise ValueError(f\"`prompt` has to be of type `str` or `list` but is {type(prompt)}\")\n", - "\n", - " if negative_prompt is not None and negative_prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:\"\n", - " f\" {negative_prompt_embeds}. Please make sure to only forward one of the two.\"\n", - " )\n", - "\n", - " if prompt_embeds is not None and negative_prompt_embeds is not None:\n", - " if prompt_embeds.shape != negative_prompt_embeds.shape:\n", - " raise ValueError(\n", - " \"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but\"\n", - " f\" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`\"\n", - " f\" {negative_prompt_embeds.shape}.\"\n", - " )\n", - "\n", - " def decode_latents(self, latents):\n", - " scale_factor = 0.18215\n", - " latents = 1 / scale_factor * latents\n", - "\n", - " batch_size, channels, num_frames, height, width = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n", - " image = self.vae_decoder(latents)[0]\n", - " image = torch.tensor(image)\n", - " video = (\n", - " image[None, :]\n", - " .reshape(\n", - " (\n", - " batch_size,\n", - " num_frames,\n", - " -1,\n", - " )\n", - " + image.shape[2:]\n", - " )\n", - " .permute(0, 2, 1, 3, 4)\n", - " )\n", - " # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n", - " video = video.float()\n", - " return video" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Inference with OpenVINO\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "core = ov.Core()" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "select device from dropdown list for running inference using OpenVINO" + 'cell_type': 'code', + 'source': [ + 'vae_decoder_xml_path = convert(\n', + ' VaeDecoderWrapper(vae),\n', + ' "models/vae.xml",\n', + ' example_input=torch.randn(2, 4, 32, 32),\n', + ' input=((NUM_FRAMES, 4, sample_height, sample_width)),\n', + ')\n', + 'del vae\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Text encoder\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + 'Text encoder is used to encode the input prompt to tensor. Default tensor length is 77.' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'text_encoder_xml = convert(\n', + ' text_encoder,\n', + ' "models/text_encoder.xml",\n', + ' example_input=torch.ones(1, 77, dtype=torch.int64),\n', + ' input=((1, 77), ov.Type.i64),\n', + ')\n', + 'del text_encoder\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Build a pipeline\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n', + ' # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n', + ' # reshape to ncfhw\n', + ' mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' # unnormalize back to [0,1]\n', + ' video = video.mul_(std).add_(mean)\n', + ' video.clamp_(0, 1)\n', + ' # prepare the final outputs\n', + ' i, c, f, h, w = video.shape\n', + ' images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n', + ' images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n', + ' images = [(image.cpu().numpy() * 255).astype("uint8") for image in images] # f h w c\n', + ' return images' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'try:\n', + ' from diffusers.utils import randn_tensor\n', + 'except ImportError:\n', + ' from diffusers.utils.torch_utils import randn_tensor\n', + '\n', + '\n', + 'class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n', + ' def __init__(\n', + ' self,\n', + ' vae_decoder: ov.CompiledModel,\n', + ' text_encoder: ov.CompiledModel,\n', + ' tokenizer: transformers.CLIPTokenizer,\n', + ' unet: ov.CompiledModel,\n', + ' scheduler: diffusers.schedulers.DDIMScheduler,\n', + ' ):\n', + ' super().__init__()\n', + '\n', + ' self.vae_decoder = vae_decoder\n', + ' self.text_encoder = text_encoder\n', + ' self.tokenizer = tokenizer\n', + ' self.unet = unet\n', + ' self.scheduler = scheduler\n', + ' self.vae_scale_factor = vae_scale_factor\n', + ' self.unet_in_channels = unet_in_channels\n', + ' self.width = WIDTH\n', + ' self.height = HEIGHT\n', + ' self.num_frames = NUM_FRAMES\n', + '\n', + ' def __call__(\n', + ' self,\n', + ' prompt: Union[str, List[str]] = None,\n', + ' num_inference_steps: int = 50,\n', + ' guidance_scale: float = 9.0,\n', + ' negative_prompt: Optional[Union[str, List[str]]] = None,\n', + ' eta: float = 0.0,\n', + ' generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n', + ' latents: Optional[torch.FloatTensor] = None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' output_type: Optional[str] = "np",\n', + ' return_dict: bool = True,\n', + ' callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n', + ' callback_steps: int = 1,\n', + ' ):\n', + ' r"""\n', + ' Function invoked when calling the pipeline for generation.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n', + ' instead.\n', + ' num_inference_steps (`int`, *optional*, defaults to 50):\n', + ' The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n', + ' expense of slower inference.\n', + ' guidance_scale (`float`, *optional*, defaults to 7.5):\n', + ' Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n', + ' `guidance_scale` is defined as `w` of equation 2. of [Imagen\n', + ' Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n', + ' 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n', + ' usually at the expense of lower video quality.\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the video generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' eta (`float`, *optional*, defaults to 0.0):\n', + ' Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n', + ' [`schedulers.DDIMScheduler`], will be ignored for others.\n', + ' generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n', + ' One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n', + ' to make generation deterministic.\n', + ' latents (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n', + ' generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n', + ' tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n', + ' `(batch_size, num_channel, num_frames, height, width)`.\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' output_type (`str`, *optional*, defaults to `"np"`):\n', + ' The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n', + ' return_dict (`bool`, *optional*, defaults to `True`):\n', + ' Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n', + ' plain tuple.\n', + ' callback (`Callable`, *optional*):\n', + ' A function that will be called every `callback_steps` steps during inference. The function will be\n', + ' called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n', + ' callback_steps (`int`, *optional*, defaults to 1):\n', + ' The frequency at which the `callback` function will be called. If not specified, the callback will be\n', + ' called at every step.\n', + '\n', + ' Returns:\n', + ' `List[np.ndarray]`: generated video frames\n', + ' """\n', + '\n', + ' num_images_per_prompt = 1\n', + '\n', + ' # 1. Check inputs. Raise error if not correct\n', + ' self.check_inputs(\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt,\n', + ' prompt_embeds,\n', + ' negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 2. Define call parameters\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n', + ' # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n', + ' # corresponds to doing no classifier free guidance.\n', + ' do_classifier_free_guidance = guidance_scale > 1.0\n', + '\n', + ' # 3. Encode input prompt\n', + ' prompt_embeds = self._encode_prompt(\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt,\n', + ' prompt_embeds=prompt_embeds,\n', + ' negative_prompt_embeds=negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 4. Prepare timesteps\n', + ' self.scheduler.set_timesteps(num_inference_steps)\n', + ' timesteps = self.scheduler.timesteps\n', + '\n', + ' # 5. Prepare latent variables\n', + ' num_channels_latents = self.unet_in_channels\n', + ' latents = self.prepare_latents(\n', + ' batch_size * num_images_per_prompt,\n', + ' num_channels_latents,\n', + ' prompt_embeds.dtype,\n', + ' generator,\n', + ' latents,\n', + ' )\n', + '\n', + ' # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n', + ' extra_step_kwargs = {"generator": generator, "eta": eta}\n', + '\n', + ' # 7. Denoising loop\n', + ' num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n', + ' with self.progress_bar(total=num_inference_steps) as progress_bar:\n', + ' for i, t in enumerate(timesteps):\n', + ' # expand the latents if we are doing classifier free guidance\n', + ' latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n', + ' latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n', + '\n', + ' # predict the noise residual\n', + ' noise_pred = self.unet(\n', + ' {\n', + ' "sample": latent_model_input,\n', + ' "timestep": t,\n', + ' "encoder_hidden_states": prompt_embeds,\n', + ' }\n', + ' )[0]\n', + ' noise_pred = torch.tensor(noise_pred)\n', + '\n', + ' # perform guidance\n', + ' if do_classifier_free_guidance:\n', + ' noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n', + ' noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n', + '\n', + ' # reshape latents\n', + ' bsz, channel, frames, width, height = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + ' noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + '\n', + ' # compute the previous noisy sample x_t -> x_t-1\n', + ' latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n', + '\n', + ' # reshape latents back\n', + ' latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n', + '\n', + ' # call the callback, if provided\n', + ' if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n', + ' progress_bar.update()\n', + ' if callback is not None and i % callback_steps == 0:\n', + ' callback(i, t, latents)\n', + '\n', + ' video_tensor = self.decode_latents(latents)\n', + '\n', + ' if output_type == "pt":\n', + ' video = video_tensor\n', + ' else:\n', + ' video = tensor2vid(video_tensor)\n', + '\n', + ' if not return_dict:\n', + ' return (video,)\n', + '\n', + ' return {"frames": video}\n', + '\n', + ' # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n', + ' def _encode_prompt(\n', + ' self,\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt=None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' ):\n', + ' r"""\n', + ' Encodes the prompt into text encoder hidden states.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' prompt to be encoded\n', + ' num_images_per_prompt (`int`):\n', + ' number of images that should be generated per prompt\n', + ' do_classifier_free_guidance (`bool`):\n', + ' whether to use classifier free guidance or not\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the image generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' """\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' if prompt_embeds is None:\n', + ' text_inputs = self.tokenizer(\n', + ' prompt,\n', + ' padding="max_length",\n', + ' max_length=self.tokenizer.model_max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + ' text_input_ids = text_inputs.input_ids\n', + ' untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids\n', + '\n', + ' if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n', + ' removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n', + ' print(\n', + ' "The following part of your input was truncated because CLIP can only handle sequences up to"\n', + ' f" {self.tokenizer.model_max_length} tokens: {removed_text}"\n', + ' )\n', + '\n', + ' prompt_embeds = self.text_encoder(text_input_ids)\n', + ' prompt_embeds = prompt_embeds[0]\n', + ' prompt_embeds = torch.tensor(prompt_embeds)\n', + '\n', + ' bs_embed, seq_len, _ = prompt_embeds.shape\n', + ' # duplicate text embeddings for each generation per prompt, using mps friendly method\n', + ' prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # get unconditional embeddings for classifier free guidance\n', + ' if do_classifier_free_guidance and negative_prompt_embeds is None:\n', + ' uncond_tokens: List[str]\n', + ' if negative_prompt is None:\n', + ' uncond_tokens = [""] * batch_size\n', + ' elif type(prompt) is not type(negative_prompt):\n', + ' raise TypeError(f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" f" {type(prompt)}.")\n', + ' elif isinstance(negative_prompt, str):\n', + ' uncond_tokens = [negative_prompt]\n', + ' elif batch_size != len(negative_prompt):\n', + ' raise ValueError(\n', + ' f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"\n', + ' f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"\n', + ' " the batch size of `prompt`."\n', + ' )\n', + ' else:\n', + ' uncond_tokens = negative_prompt\n', + '\n', + ' max_length = prompt_embeds.shape[1]\n', + ' uncond_input = self.tokenizer(\n', + ' uncond_tokens,\n', + ' padding="max_length",\n', + ' max_length=max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + '\n', + ' negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n', + ' negative_prompt_embeds = negative_prompt_embeds[0]\n', + ' negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n', + '\n', + ' if do_classifier_free_guidance:\n', + ' # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n', + ' seq_len = negative_prompt_embeds.shape[1]\n', + '\n', + ' negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # For classifier free guidance, we need to do two forward passes.\n', + ' # Here we concatenate the unconditional and text embeddings into a single batch\n', + ' # to avoid doing two forward passes\n', + ' prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n', + '\n', + ' return prompt_embeds\n', + '\n', + ' def prepare_latents(\n', + ' self,\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' dtype,\n', + ' generator,\n', + ' latents=None,\n', + ' ):\n', + ' shape = (\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' self.num_frames,\n', + ' self.height // self.vae_scale_factor,\n', + ' self.width // self.vae_scale_factor,\n', + ' )\n', + ' if isinstance(generator, list) and len(generator) != batch_size:\n', + ' raise ValueError(\n', + ' f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"\n', + ' f" size of {batch_size}. Make sure the batch size matches the length of the generators."\n', + ' )\n', + '\n', + ' if latents is None:\n', + ' latents = randn_tensor(shape, generator=generator, dtype=dtype)\n', + '\n', + ' # scale the initial noise by the standard deviation required by the scheduler\n', + ' latents = latents * self.scheduler.init_noise_sigma\n', + ' return latents\n', + '\n', + ' def check_inputs(\n', + ' self,\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt=None,\n', + ' prompt_embeds=None,\n', + ' negative_prompt_embeds=None,\n', + ' ):\n', + ' if self.height % 8 != 0 or self.width % 8 != 0:\n', + ' raise ValueError(f"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.")\n', + '\n', + ' if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n', + ' raise ValueError(f"`callback_steps` has to be a positive integer but is {callback_steps} of type" f" {type(callback_steps)}.")\n', + '\n', + ' if prompt is not None and prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" " only forward one of the two."\n', + ' )\n', + ' elif prompt is None and prompt_embeds is None:\n', + ' raise ValueError("Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.")\n', + ' elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n', + ' raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")\n', + '\n', + ' if negative_prompt is not None and negative_prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"\n', + ' f" {negative_prompt_embeds}. Please make sure to only forward one of the two."\n', + ' )\n', + '\n', + ' if prompt_embeds is not None and negative_prompt_embeds is not None:\n', + ' if prompt_embeds.shape != negative_prompt_embeds.shape:\n', + ' raise ValueError(\n', + ' "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"\n', + ' f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"\n', + ' f" {negative_prompt_embeds.shape}."\n', + ' )\n', + '\n', + ' def decode_latents(self, latents):\n', + ' scale_factor = 0.18215\n', + ' latents = 1 / scale_factor * latents\n', + '\n', + ' batch_size, channels, num_frames, height, width = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n', + ' image = self.vae_decoder(latents)[0]\n', + ' image = torch.tensor(image)\n', + ' video = (\n', + ' image[None, :]\n', + ' .reshape(\n', + ' (\n', + ' batch_size,\n', + ' num_frames,\n', + ' -1,\n', + ' )\n', + ' + image.shape[2:]\n', + ' )\n', + ' .permute(0, 2, 1, 3, 4)\n', + ' )\n', + ' # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n', + ' video = video.float()\n', + ' return video' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Inference with OpenVINO\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'core = ov.Core()' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'select device from dropdown list for running inference using OpenVINO' ] }, { - "cell_type": "code", - "source": [ - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "from notebook_utils import device_widget\n", - "\n", - "device = device_widget()\n", - "\n", - "device" + 'cell_type': 'code', + 'source': [ + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + '\n', + 'from notebook_utils import device_widget\n', + '\n', + 'device = device_widget()\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_unet = core.compile_model(unet_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_unet = core.compile_model(unet_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)' ] }, { - "cell_type": "markdown", - "source": [ - "Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler." + 'cell_type': 'markdown', + 'source': [ + 'Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler.' ] }, { - "cell_type": "code", - "source": [ - "ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)" + 'cell_type': 'code', + 'source': [ + 'ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)' ] }, { - "cell_type": "markdown", - "source": [ - "### Define a prompt\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define a prompt\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "prompt = \"A panda eating bamboo on a rock.\"" + 'cell_type': 'code', + 'source': [ + 'prompt = "A panda eating bamboo on a rock."' ] }, { - "cell_type": "markdown", - "source": [ - "Let's generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section." + 'cell_type': 'markdown', + 'source': [ + 'Let\'s generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section.' ] }, { - "cell_type": "markdown", - "source": [ - "### Video generation\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Video generation\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "frames = ov_pipe(prompt, num_inference_steps=25)[\"frames\"]" + 'cell_type': 'code', + 'source': [ + 'frames = ov_pipe(prompt, num_inference_steps=25)["frames"]' ] }, { - "cell_type": "code", - "source": [ - "images = [PIL.Image.fromarray(frame) for frame in frames]\n", - "images[0].save(\"output.gif\", save_all=True, append_images=images[1:], duration=125, loop=0)\n", - "with open(\"output.gif\", \"rb\") as gif_file:\n", - " b64 = f\"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}\"\n", - "IPython.display.HTML(f'')" + 'cell_type': 'code', + 'source': [ + 'images = [PIL.Image.fromarray(frame) for frame in frames]\n', + 'images[0].save("output.gif", save_all=True, append_images=images[1:], duration=125, loop=0)\n', + 'with open("output.gif", "rb") as gif_file:\n', + ' b64 = f"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}"\n', + `IPython.display.HTML(f'')` ] }, { - "cell_type": "markdown", - "source": [ - "## Interactive demo\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Interactive demo\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n", - " generator = torch.Generator().manual_seed(seed)\n", - " frames = ov_pipe(\n", - " prompt,\n", - " num_inference_steps=num_inference_steps,\n", - " generator=generator,\n", - " )[\"frames\"]\n", - " out_file = tempfile.NamedTemporaryFile(suffix=\".gif\", delete=False)\n", - " images = [PIL.Image.fromarray(frame) for frame in frames]\n", - " images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n", - " return out_file.name\n", - "\n", - "\n", - "demo = gr.Interface(\n", - " generate,\n", - " [\n", - " gr.Textbox(label=\"Prompt\"),\n", - " gr.Slider(0, 1000000, value=42, label=\"Seed\", step=1),\n", - " gr.Slider(10, 50, value=25, label=\"Number of inference steps\", step=1),\n", - " ],\n", - " gr.Image(label=\"Result\"),\n", - " examples=[\n", - " [\"An astronaut riding a horse.\", 0, 25],\n", - " [\"A panda eating bamboo on a rock.\", 0, 25],\n", - " [\"Spiderman is surfing.\", 0, 25],\n", - " ],\n", - " allow_flagging=\"never\",\n", - ")\n", - "\n", - "try:\n", - " demo.queue().launch(debug=True)\n", - "except Exception:\n", - " demo.queue().launch(share=True, debug=True)\n", - "# if you are launching remotely, specify server_name and server_port\n", - "# demo.launch(server_name='your server name', server_port='server port in int')\n", - "# Read more in the docs: https://gradio.app/docs/" + 'cell_type': 'code', + 'source': [ + 'def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n', + ' generator = torch.Generator().manual_seed(seed)\n', + ' frames = ov_pipe(\n', + ' prompt,\n', + ' num_inference_steps=num_inference_steps,\n', + ' generator=generator,\n', + ' )["frames"]\n', + ' out_file = tempfile.NamedTemporaryFile(suffix=".gif", delete=False)\n', + ' images = [PIL.Image.fromarray(frame) for frame in frames]\n', + ' images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n', + ' return out_file.name\n', + '\n', + '\n', + 'demo = gr.Interface(\n', + ' generate,\n', + ' [\n', + ' gr.Textbox(label="Prompt"),\n', + ' gr.Slider(0, 1000000, value=42, label="Seed", step=1),\n', + ' gr.Slider(10, 50, value=25, label="Number of inference steps", step=1),\n', + ' ],\n', + ' gr.Image(label="Result"),\n', + ' examples=[\n', + ' ["An astronaut riding a horse.", 0, 25],\n', + ' ["A panda eating bamboo on a rock.", 0, 25],\n', + ' ["Spiderman is surfing.", 0, 25],\n', + ' ],\n', + ' allow_flagging="never",\n', + ')\n', + '\n', + 'try:\n', + ' demo.queue().launch(debug=True)\n', + 'except Exception:\n', + ' demo.queue().launch(share=True, debug=True)\n', + '# if you are launching remotely, specify server_name and server_port\n', + `# demo.launch(server_name='your server name', server_port='server port in int')\n`, + '# Read more in the docs: https://gradio.app/docs/' ] } ].map(fromJupyterCell) , [ { - "cell_type": "markdown", - "source": [ - "# Video generation with ZeroScope and OpenVINO\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Install and import required packages](#Install-and-import-required-packages)\n", - "- [Load the model](#Load-the-model)\n", - "- [Convert the model](#Convert-the-model)\n", - " - [Define the conversion function](#Define-the-conversion-function)\n", - " - [UNet](#UNet)\n", - " - [VAE](#VAE)\n", - " - [Text encoder](#Text-encoder)\n", - "- [Build a pipeline](#Build-a-pipeline)\n", - "- [Inference with OpenVINO](#Inference-with-OpenVINO)\n", - " - [Select inference device](#Select-inference-device)\n", - " - [Define a prompt](#Define-a-prompt)\n", - " - [Video generation](#Video-generation)\n", - "- [Interactive demo](#Interactive-demo)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" + 'cell_type': 'markdown', + 'source': [ + '# Video generation with ZeroScope and OpenVINO\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Install and import required packages](#Install-and-import-required-packages)\n', + '- [Load the model](#Load-the-model)\n', + '- [Convert the model](#Convert-the-model)\n', + ' - [Define the conversion function](#Define-the-conversion-function)\n', + ' - [UNet](#UNet)\n', + ' - [VAE](#VAE)\n', + ' - [Text encoder](#Text-encoder)\n', + '- [Build a pipeline](#Build-a-pipeline)\n', + '- [Inference with OpenVINO](#Inference-with-OpenVINO)\n', + ' - [Select inference device](#Select-inference-device)\n', + ' - [Define a prompt](#Define-a-prompt)\n', + ' - [Video generation](#Video-generation)\n', + '- [Interactive demo](#Interactive-demo)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' ] }, { - "cell_type": "markdown", - "source": [ - "The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n", - "\n", - "The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n", - "\n", - "The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n", - "\n", - "Both versions of the ZeroScope model are available on Hugging Face:\n", - " - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n", - " - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n", - "\n", - "We will use the first one." + 'cell_type': 'markdown', + 'source': [ + 'The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n', + '\n', + 'The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n', + '\n', + 'The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n', + '\n', + 'Both versions of the ZeroScope model are available on Hugging Face:\n', + ' - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n', + ' - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n', + '\n', + 'We will use the first one.' ] }, { - "cell_type": "markdown", - "source": [ - "
    \n", - " This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n", - "
    " + 'cell_type': 'markdown', + 'source': [ + '
    \n', + ' This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n', + '
    ' ] }, { - "cell_type": "markdown", - "source": [ - "## Install and import required packages\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Install and import required packages\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "To work with text-to-video synthesis model, we will use Hugging Face's [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`." + 'cell_type': 'markdown', + 'source': [ + 'To work with text-to-video synthesis model, we will use Hugging Face\'s [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`.' ] }, { - "cell_type": "code", - "source": [ - "%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu \"diffusers>=0.18.0\" \"torch>=2.1\" transformers \"openvino>=2023.1.0\" numpy \"gradio>=4.19\"" + 'cell_type': 'code', + 'source': [ + '%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu "diffusers>=0.18.0" "torch>=2.1" transformers "openvino>=2023.1.0" numpy "gradio>=4.19"' ] }, { - "cell_type": "code", - "source": [ - "import gc\n", - "from typing import Optional, Union, List, Callable\n", - "import base64\n", - "import tempfile\n", - "import warnings\n", - "\n", - "import diffusers\n", - "import transformers\n", - "import numpy as np\n", - "import IPython\n", - "import torch\n", - "import PIL\n", - "import gradio as gr\n", - "\n", - "import openvino as ov" + 'cell_type': 'code', + 'source': [ + 'import gc\n', + 'from typing import Optional, Union, List, Callable\n', + 'import base64\n', + 'import tempfile\n', + 'import warnings\n', + '\n', + 'import diffusers\n', + 'import transformers\n', + 'import numpy as np\n', + 'import IPython\n', + 'import torch\n', + 'import PIL\n', + 'import gradio as gr\n', + '\n', + 'import openvino as ov' ] }, { - "cell_type": "markdown", - "source": [ - "Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption." + 'cell_type': 'markdown', + 'source': [ + `Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption.` ] }, { - "cell_type": "code", - "source": [ - "WIDTH = 432 # must be divisible by 8\n", - "HEIGHT = 240 # must be divisible by 8\n", - "NUM_FRAMES = 16" + 'cell_type': 'code', + 'source': [ + 'WIDTH = 432 # must be divisible by 8\n', + 'HEIGHT = 240 # must be divisible by 8\n', + 'NUM_FRAMES = 16' ] }, { - "cell_type": "markdown", - "source": [ - "## Load the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Load the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`." + 'cell_type': 'markdown', + 'source': [ + 'The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`.' ] }, { - "cell_type": "code", - "source": [ - "pipe = diffusers.DiffusionPipeline.from_pretrained(\"cerspense/zeroscope_v2_576w\")" + 'cell_type': 'code', + 'source': [ + 'pipe = diffusers.DiffusionPipeline.from_pretrained("cerspense/zeroscope_v2_576w")' ] }, { - "cell_type": "code", - "source": [ - "unet = pipe.unet\n", - "unet.eval()\n", - "vae = pipe.vae\n", - "vae.eval()\n", - "text_encoder = pipe.text_encoder\n", - "text_encoder.eval()\n", - "tokenizer = pipe.tokenizer\n", - "scheduler = pipe.scheduler\n", - "vae_scale_factor = pipe.vae_scale_factor\n", - "unet_in_channels = pipe.unet.config.in_channels\n", - "sample_width = WIDTH // vae_scale_factor\n", - "sample_height = HEIGHT // vae_scale_factor\n", - "del pipe\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet = pipe.unet\n', + 'unet.eval()\n', + 'vae = pipe.vae\n', + 'vae.eval()\n', + 'text_encoder = pipe.text_encoder\n', + 'text_encoder.eval()\n', + 'tokenizer = pipe.tokenizer\n', + 'scheduler = pipe.scheduler\n', + 'vae_scale_factor = pipe.vae_scale_factor\n', + 'unet_in_channels = pipe.unet.config.in_channels\n', + 'sample_width = WIDTH // vae_scale_factor\n', + 'sample_height = HEIGHT // vae_scale_factor\n', + 'del pipe\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "## Convert the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Convert the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video." + 'cell_type': 'markdown', + 'source': [ + `The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video.` ] }, { - "cell_type": "markdown", - "source": [] + 'cell_type': 'markdown', + 'source': [] }, { - "cell_type": "markdown", - "source": [ - "### Define the conversion function\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define the conversion function\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion." + 'cell_type': 'markdown', + 'source': [ + 'Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion.' ] }, { - "cell_type": "code", - "source": [ - "warnings.filterwarnings(\"ignore\", category=torch.jit.TracerWarning)" + 'cell_type': 'code', + 'source': [ + 'warnings.filterwarnings("ignore", category=torch.jit.TracerWarning)' ] }, { - "cell_type": "code", - "source": [ - "from pathlib import Path\n", - "\n", - "\n", - "def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n", - " xml_path = Path(xml_path)\n", - " if not xml_path.exists():\n", - " xml_path.parent.mkdir(parents=True, exist_ok=True)\n", - " with torch.no_grad():\n", - " converted_model = ov.convert_model(model, **convert_kwargs)\n", - " ov.save_model(converted_model, xml_path)\n", - " del converted_model\n", - " gc.collect()\n", - " torch._C._jit_clear_class_registry()\n", - " torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n", - " torch.jit._state._clear_class_state()\n", - " return xml_path" + 'cell_type': 'code', + 'source': [ + 'from pathlib import Path\n', + '\n', + '\n', + 'def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n', + ' xml_path = Path(xml_path)\n', + ' if not xml_path.exists():\n', + ' xml_path.parent.mkdir(parents=True, exist_ok=True)\n', + ' with torch.no_grad():\n', + ' converted_model = ov.convert_model(model, **convert_kwargs)\n', + ' ov.save_model(converted_model, xml_path)\n', + ' del converted_model\n', + ' gc.collect()\n', + ' torch._C._jit_clear_class_registry()\n', + ' torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n', + ' torch.jit._state._clear_class_state()\n', + ' return xml_path' ] }, { - "cell_type": "markdown", - "source": [ - "### UNet\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### UNet\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output." + 'cell_type': 'markdown', + 'source': [ + 'Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output.' ] }, { - "cell_type": "code", - "source": [ - "unet_xml_path = convert(\n", - " unet,\n", - " \"models/unet.xml\",\n", - " example_input={\n", - " \"sample\": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n", - " \"timestep\": torch.tensor(1),\n", - " \"encoder_hidden_states\": torch.randn(2, 77, 1024),\n", - " },\n", - " input=[\n", - " (\"sample\", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n", - " (\"timestep\", ()),\n", - " (\"encoder_hidden_states\", (2, 77, 1024)),\n", - " ],\n", - ")\n", - "del unet\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet_xml_path = convert(\n', + ' unet,\n', + ' "models/unet.xml",\n', + ' example_input={\n', + ' "sample": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n', + ' "timestep": torch.tensor(1),\n', + ' "encoder_hidden_states": torch.randn(2, 77, 1024),\n', + ' },\n', + ' input=[\n', + ' ("sample", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n', + ' ("timestep", ()),\n', + ' ("encoder_hidden_states", (2, 77, 1024)),\n', + ' ],\n', + ')\n', + 'del unet\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "### VAE\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### VAE\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part." + 'cell_type': 'markdown', + 'source': [ + 'Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part.' ] }, { - "cell_type": "code", - "source": [ - "class VaeDecoderWrapper(torch.nn.Module):\n", - " def __init__(self, vae):\n", - " super().__init__()\n", - " self.vae = vae\n", - "\n", - " def forward(self, z: torch.FloatTensor):\n", - " return self.vae.decode(z)" + 'cell_type': 'code', + 'source': [ + 'class VaeDecoderWrapper(torch.nn.Module):\n', + ' def __init__(self, vae):\n', + ' super().__init__()\n', + ' self.vae = vae\n', + '\n', + ' def forward(self, z: torch.FloatTensor):\n', + ' return self.vae.decode(z)' ] }, { - "cell_type": "code", - "source": [ - "vae_decoder_xml_path = convert(\n", - " VaeDecoderWrapper(vae),\n", - " \"models/vae.xml\",\n", - " example_input=torch.randn(2, 4, 32, 32),\n", - " input=((NUM_FRAMES, 4, sample_height, sample_width)),\n", - ")\n", - "del vae\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Text encoder\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Text encoder is used to encode the input prompt to tensor. Default tensor length is 77." - ] - }, - { - "cell_type": "code", - "source": [ - "text_encoder_xml = convert(\n", - " text_encoder,\n", - " \"models/text_encoder.xml\",\n", - " example_input=torch.ones(1, 77, dtype=torch.int64),\n", - " input=((1, 77), ov.Type.i64),\n", - ")\n", - "del text_encoder\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Build a pipeline\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n", - " # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n", - " # reshape to ncfhw\n", - " mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " # unnormalize back to [0,1]\n", - " video = video.mul_(std).add_(mean)\n", - " video.clamp_(0, 1)\n", - " # prepare the final outputs\n", - " i, c, f, h, w = video.shape\n", - " images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n", - " images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n", - " images = [(image.cpu().numpy() * 255).astype(\"uint8\") for image in images] # f h w c\n", - " return images" - ] - }, - { - "cell_type": "code", - "source": [ - "try:\n", - " from diffusers.utils import randn_tensor\n", - "except ImportError:\n", - " from diffusers.utils.torch_utils import randn_tensor\n", - "\n", - "\n", - "class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n", - " def __init__(\n", - " self,\n", - " vae_decoder: ov.CompiledModel,\n", - " text_encoder: ov.CompiledModel,\n", - " tokenizer: transformers.CLIPTokenizer,\n", - " unet: ov.CompiledModel,\n", - " scheduler: diffusers.schedulers.DDIMScheduler,\n", - " ):\n", - " super().__init__()\n", - "\n", - " self.vae_decoder = vae_decoder\n", - " self.text_encoder = text_encoder\n", - " self.tokenizer = tokenizer\n", - " self.unet = unet\n", - " self.scheduler = scheduler\n", - " self.vae_scale_factor = vae_scale_factor\n", - " self.unet_in_channels = unet_in_channels\n", - " self.width = WIDTH\n", - " self.height = HEIGHT\n", - " self.num_frames = NUM_FRAMES\n", - "\n", - " def __call__(\n", - " self,\n", - " prompt: Union[str, List[str]] = None,\n", - " num_inference_steps: int = 50,\n", - " guidance_scale: float = 9.0,\n", - " negative_prompt: Optional[Union[str, List[str]]] = None,\n", - " eta: float = 0.0,\n", - " generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n", - " latents: Optional[torch.FloatTensor] = None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " output_type: Optional[str] = \"np\",\n", - " return_dict: bool = True,\n", - " callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n", - " callback_steps: int = 1,\n", - " ):\n", - " r\"\"\"\n", - " Function invoked when calling the pipeline for generation.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n", - " instead.\n", - " num_inference_steps (`int`, *optional*, defaults to 50):\n", - " The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n", - " expense of slower inference.\n", - " guidance_scale (`float`, *optional*, defaults to 7.5):\n", - " Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n", - " `guidance_scale` is defined as `w` of equation 2. of [Imagen\n", - " Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n", - " 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n", - " usually at the expense of lower video quality.\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the video generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " eta (`float`, *optional*, defaults to 0.0):\n", - " Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n", - " [`schedulers.DDIMScheduler`], will be ignored for others.\n", - " generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n", - " One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n", - " to make generation deterministic.\n", - " latents (`torch.FloatTensor`, *optional*):\n", - " Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n", - " generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n", - " tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n", - " `(batch_size, num_channel, num_frames, height, width)`.\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " output_type (`str`, *optional*, defaults to `\"np\"`):\n", - " The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n", - " return_dict (`bool`, *optional*, defaults to `True`):\n", - " Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n", - " plain tuple.\n", - " callback (`Callable`, *optional*):\n", - " A function that will be called every `callback_steps` steps during inference. The function will be\n", - " called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n", - " callback_steps (`int`, *optional*, defaults to 1):\n", - " The frequency at which the `callback` function will be called. If not specified, the callback will be\n", - " called at every step.\n", - "\n", - " Returns:\n", - " `List[np.ndarray]`: generated video frames\n", - " \"\"\"\n", - "\n", - " num_images_per_prompt = 1\n", - "\n", - " # 1. Check inputs. Raise error if not correct\n", - " self.check_inputs(\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt,\n", - " prompt_embeds,\n", - " negative_prompt_embeds,\n", - " )\n", - "\n", - " # 2. Define call parameters\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n", - " # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n", - " # corresponds to doing no classifier free guidance.\n", - " do_classifier_free_guidance = guidance_scale > 1.0\n", - "\n", - " # 3. Encode input prompt\n", - " prompt_embeds = self._encode_prompt(\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt,\n", - " prompt_embeds=prompt_embeds,\n", - " negative_prompt_embeds=negative_prompt_embeds,\n", - " )\n", - "\n", - " # 4. Prepare timesteps\n", - " self.scheduler.set_timesteps(num_inference_steps)\n", - " timesteps = self.scheduler.timesteps\n", - "\n", - " # 5. Prepare latent variables\n", - " num_channels_latents = self.unet_in_channels\n", - " latents = self.prepare_latents(\n", - " batch_size * num_images_per_prompt,\n", - " num_channels_latents,\n", - " prompt_embeds.dtype,\n", - " generator,\n", - " latents,\n", - " )\n", - "\n", - " # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n", - " extra_step_kwargs = {\"generator\": generator, \"eta\": eta}\n", - "\n", - " # 7. Denoising loop\n", - " num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n", - " with self.progress_bar(total=num_inference_steps) as progress_bar:\n", - " for i, t in enumerate(timesteps):\n", - " # expand the latents if we are doing classifier free guidance\n", - " latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n", - " latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n", - "\n", - " # predict the noise residual\n", - " noise_pred = self.unet(\n", - " {\n", - " \"sample\": latent_model_input,\n", - " \"timestep\": t,\n", - " \"encoder_hidden_states\": prompt_embeds,\n", - " }\n", - " )[0]\n", - " noise_pred = torch.tensor(noise_pred)\n", - "\n", - " # perform guidance\n", - " if do_classifier_free_guidance:\n", - " noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n", - " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", - "\n", - " # reshape latents\n", - " bsz, channel, frames, width, height = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - " noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - "\n", - " # compute the previous noisy sample x_t -> x_t-1\n", - " latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n", - "\n", - " # reshape latents back\n", - " latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n", - "\n", - " # call the callback, if provided\n", - " if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n", - " progress_bar.update()\n", - " if callback is not None and i % callback_steps == 0:\n", - " callback(i, t, latents)\n", - "\n", - " video_tensor = self.decode_latents(latents)\n", - "\n", - " if output_type == \"pt\":\n", - " video = video_tensor\n", - " else:\n", - " video = tensor2vid(video_tensor)\n", - "\n", - " if not return_dict:\n", - " return (video,)\n", - "\n", - " return {\"frames\": video}\n", - "\n", - " # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n", - " def _encode_prompt(\n", - " self,\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt=None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " ):\n", - " r\"\"\"\n", - " Encodes the prompt into text encoder hidden states.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " prompt to be encoded\n", - " num_images_per_prompt (`int`):\n", - " number of images that should be generated per prompt\n", - " do_classifier_free_guidance (`bool`):\n", - " whether to use classifier free guidance or not\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the image generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " \"\"\"\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " if prompt_embeds is None:\n", - " text_inputs = self.tokenizer(\n", - " prompt,\n", - " padding=\"max_length\",\n", - " max_length=self.tokenizer.model_max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - " text_input_ids = text_inputs.input_ids\n", - " untruncated_ids = self.tokenizer(prompt, padding=\"longest\", return_tensors=\"pt\").input_ids\n", - "\n", - " if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n", - " removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n", - " print(\n", - " \"The following part of your input was truncated because CLIP can only handle sequences up to\"\n", - " f\" {self.tokenizer.model_max_length} tokens: {removed_text}\"\n", - " )\n", - "\n", - " prompt_embeds = self.text_encoder(text_input_ids)\n", - " prompt_embeds = prompt_embeds[0]\n", - " prompt_embeds = torch.tensor(prompt_embeds)\n", - "\n", - " bs_embed, seq_len, _ = prompt_embeds.shape\n", - " # duplicate text embeddings for each generation per prompt, using mps friendly method\n", - " prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # get unconditional embeddings for classifier free guidance\n", - " if do_classifier_free_guidance and negative_prompt_embeds is None:\n", - " uncond_tokens: List[str]\n", - " if negative_prompt is None:\n", - " uncond_tokens = [\"\"] * batch_size\n", - " elif type(prompt) is not type(negative_prompt):\n", - " raise TypeError(f\"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=\" f\" {type(prompt)}.\")\n", - " elif isinstance(negative_prompt, str):\n", - " uncond_tokens = [negative_prompt]\n", - " elif batch_size != len(negative_prompt):\n", - " raise ValueError(\n", - " f\"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:\"\n", - " f\" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches\"\n", - " \" the batch size of `prompt`.\"\n", - " )\n", - " else:\n", - " uncond_tokens = negative_prompt\n", - "\n", - " max_length = prompt_embeds.shape[1]\n", - " uncond_input = self.tokenizer(\n", - " uncond_tokens,\n", - " padding=\"max_length\",\n", - " max_length=max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - "\n", - " negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n", - " negative_prompt_embeds = negative_prompt_embeds[0]\n", - " negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n", - "\n", - " if do_classifier_free_guidance:\n", - " # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n", - " seq_len = negative_prompt_embeds.shape[1]\n", - "\n", - " negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # For classifier free guidance, we need to do two forward passes.\n", - " # Here we concatenate the unconditional and text embeddings into a single batch\n", - " # to avoid doing two forward passes\n", - " prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n", - "\n", - " return prompt_embeds\n", - "\n", - " def prepare_latents(\n", - " self,\n", - " batch_size,\n", - " num_channels_latents,\n", - " dtype,\n", - " generator,\n", - " latents=None,\n", - " ):\n", - " shape = (\n", - " batch_size,\n", - " num_channels_latents,\n", - " self.num_frames,\n", - " self.height // self.vae_scale_factor,\n", - " self.width // self.vae_scale_factor,\n", - " )\n", - " if isinstance(generator, list) and len(generator) != batch_size:\n", - " raise ValueError(\n", - " f\"You have passed a list of generators of length {len(generator)}, but requested an effective batch\"\n", - " f\" size of {batch_size}. Make sure the batch size matches the length of the generators.\"\n", - " )\n", - "\n", - " if latents is None:\n", - " latents = randn_tensor(shape, generator=generator, dtype=dtype)\n", - "\n", - " # scale the initial noise by the standard deviation required by the scheduler\n", - " latents = latents * self.scheduler.init_noise_sigma\n", - " return latents\n", - "\n", - " def check_inputs(\n", - " self,\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt=None,\n", - " prompt_embeds=None,\n", - " negative_prompt_embeds=None,\n", - " ):\n", - " if self.height % 8 != 0 or self.width % 8 != 0:\n", - " raise ValueError(f\"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.\")\n", - "\n", - " if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n", - " raise ValueError(f\"`callback_steps` has to be a positive integer but is {callback_steps} of type\" f\" {type(callback_steps)}.\")\n", - "\n", - " if prompt is not None and prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to\" \" only forward one of the two.\"\n", - " )\n", - " elif prompt is None and prompt_embeds is None:\n", - " raise ValueError(\"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.\")\n", - " elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n", - " raise ValueError(f\"`prompt` has to be of type `str` or `list` but is {type(prompt)}\")\n", - "\n", - " if negative_prompt is not None and negative_prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:\"\n", - " f\" {negative_prompt_embeds}. Please make sure to only forward one of the two.\"\n", - " )\n", - "\n", - " if prompt_embeds is not None and negative_prompt_embeds is not None:\n", - " if prompt_embeds.shape != negative_prompt_embeds.shape:\n", - " raise ValueError(\n", - " \"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but\"\n", - " f\" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`\"\n", - " f\" {negative_prompt_embeds.shape}.\"\n", - " )\n", - "\n", - " def decode_latents(self, latents):\n", - " scale_factor = 0.18215\n", - " latents = 1 / scale_factor * latents\n", - "\n", - " batch_size, channels, num_frames, height, width = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n", - " image = self.vae_decoder(latents)[0]\n", - " image = torch.tensor(image)\n", - " video = (\n", - " image[None, :]\n", - " .reshape(\n", - " (\n", - " batch_size,\n", - " num_frames,\n", - " -1,\n", - " )\n", - " + image.shape[2:]\n", - " )\n", - " .permute(0, 2, 1, 3, 4)\n", - " )\n", - " # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n", - " video = video.float()\n", - " return video" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Inference with OpenVINO\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "core = ov.Core()" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "select device from dropdown list for running inference using OpenVINO" + 'cell_type': 'code', + 'source': [ + 'vae_decoder_xml_path = convert(\n', + ' VaeDecoderWrapper(vae),\n', + ' "models/vae.xml",\n', + ' example_input=torch.randn(2, 4, 32, 32),\n', + ' input=((NUM_FRAMES, 4, sample_height, sample_width)),\n', + ')\n', + 'del vae\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Text encoder\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + 'Text encoder is used to encode the input prompt to tensor. Default tensor length is 77.' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'text_encoder_xml = convert(\n', + ' text_encoder,\n', + ' "models/text_encoder.xml",\n', + ' example_input=torch.ones(1, 77, dtype=torch.int64),\n', + ' input=((1, 77), ov.Type.i64),\n', + ')\n', + 'del text_encoder\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Build a pipeline\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n', + ' # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n', + ' # reshape to ncfhw\n', + ' mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' # unnormalize back to [0,1]\n', + ' video = video.mul_(std).add_(mean)\n', + ' video.clamp_(0, 1)\n', + ' # prepare the final outputs\n', + ' i, c, f, h, w = video.shape\n', + ' images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n', + ' images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n', + ' images = [(image.cpu().numpy() * 255).astype("uint8") for image in images] # f h w c\n', + ' return images' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'try:\n', + ' from diffusers.utils import randn_tensor\n', + 'except ImportError:\n', + ' from diffusers.utils.torch_utils import randn_tensor\n', + '\n', + '\n', + 'class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n', + ' def __init__(\n', + ' self,\n', + ' vae_decoder: ov.CompiledModel,\n', + ' text_encoder: ov.CompiledModel,\n', + ' tokenizer: transformers.CLIPTokenizer,\n', + ' unet: ov.CompiledModel,\n', + ' scheduler: diffusers.schedulers.DDIMScheduler,\n', + ' ):\n', + ' super().__init__()\n', + '\n', + ' self.vae_decoder = vae_decoder\n', + ' self.text_encoder = text_encoder\n', + ' self.tokenizer = tokenizer\n', + ' self.unet = unet\n', + ' self.scheduler = scheduler\n', + ' self.vae_scale_factor = vae_scale_factor\n', + ' self.unet_in_channels = unet_in_channels\n', + ' self.width = WIDTH\n', + ' self.height = HEIGHT\n', + ' self.num_frames = NUM_FRAMES\n', + '\n', + ' def __call__(\n', + ' self,\n', + ' prompt: Union[str, List[str]] = None,\n', + ' num_inference_steps: int = 50,\n', + ' guidance_scale: float = 9.0,\n', + ' negative_prompt: Optional[Union[str, List[str]]] = None,\n', + ' eta: float = 0.0,\n', + ' generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n', + ' latents: Optional[torch.FloatTensor] = None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' output_type: Optional[str] = "np",\n', + ' return_dict: bool = True,\n', + ' callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n', + ' callback_steps: int = 1,\n', + ' ):\n', + ' r"""\n', + ' Function invoked when calling the pipeline for generation.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n', + ' instead.\n', + ' num_inference_steps (`int`, *optional*, defaults to 50):\n', + ' The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n', + ' expense of slower inference.\n', + ' guidance_scale (`float`, *optional*, defaults to 7.5):\n', + ' Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n', + ' `guidance_scale` is defined as `w` of equation 2. of [Imagen\n', + ' Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n', + ' 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n', + ' usually at the expense of lower video quality.\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the video generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' eta (`float`, *optional*, defaults to 0.0):\n', + ' Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n', + ' [`schedulers.DDIMScheduler`], will be ignored for others.\n', + ' generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n', + ' One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n', + ' to make generation deterministic.\n', + ' latents (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n', + ' generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n', + ' tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n', + ' `(batch_size, num_channel, num_frames, height, width)`.\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' output_type (`str`, *optional*, defaults to `"np"`):\n', + ' The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n', + ' return_dict (`bool`, *optional*, defaults to `True`):\n', + ' Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n', + ' plain tuple.\n', + ' callback (`Callable`, *optional*):\n', + ' A function that will be called every `callback_steps` steps during inference. The function will be\n', + ' called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n', + ' callback_steps (`int`, *optional*, defaults to 1):\n', + ' The frequency at which the `callback` function will be called. If not specified, the callback will be\n', + ' called at every step.\n', + '\n', + ' Returns:\n', + ' `List[np.ndarray]`: generated video frames\n', + ' """\n', + '\n', + ' num_images_per_prompt = 1\n', + '\n', + ' # 1. Check inputs. Raise error if not correct\n', + ' self.check_inputs(\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt,\n', + ' prompt_embeds,\n', + ' negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 2. Define call parameters\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n', + ' # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n', + ' # corresponds to doing no classifier free guidance.\n', + ' do_classifier_free_guidance = guidance_scale > 1.0\n', + '\n', + ' # 3. Encode input prompt\n', + ' prompt_embeds = self._encode_prompt(\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt,\n', + ' prompt_embeds=prompt_embeds,\n', + ' negative_prompt_embeds=negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 4. Prepare timesteps\n', + ' self.scheduler.set_timesteps(num_inference_steps)\n', + ' timesteps = self.scheduler.timesteps\n', + '\n', + ' # 5. Prepare latent variables\n', + ' num_channels_latents = self.unet_in_channels\n', + ' latents = self.prepare_latents(\n', + ' batch_size * num_images_per_prompt,\n', + ' num_channels_latents,\n', + ' prompt_embeds.dtype,\n', + ' generator,\n', + ' latents,\n', + ' )\n', + '\n', + ' # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n', + ' extra_step_kwargs = {"generator": generator, "eta": eta}\n', + '\n', + ' # 7. Denoising loop\n', + ' num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n', + ' with self.progress_bar(total=num_inference_steps) as progress_bar:\n', + ' for i, t in enumerate(timesteps):\n', + ' # expand the latents if we are doing classifier free guidance\n', + ' latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n', + ' latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n', + '\n', + ' # predict the noise residual\n', + ' noise_pred = self.unet(\n', + ' {\n', + ' "sample": latent_model_input,\n', + ' "timestep": t,\n', + ' "encoder_hidden_states": prompt_embeds,\n', + ' }\n', + ' )[0]\n', + ' noise_pred = torch.tensor(noise_pred)\n', + '\n', + ' # perform guidance\n', + ' if do_classifier_free_guidance:\n', + ' noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n', + ' noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n', + '\n', + ' # reshape latents\n', + ' bsz, channel, frames, width, height = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + ' noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + '\n', + ' # compute the previous noisy sample x_t -> x_t-1\n', + ' latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n', + '\n', + ' # reshape latents back\n', + ' latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n', + '\n', + ' # call the callback, if provided\n', + ' if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n', + ' progress_bar.update()\n', + ' if callback is not None and i % callback_steps == 0:\n', + ' callback(i, t, latents)\n', + '\n', + ' video_tensor = self.decode_latents(latents)\n', + '\n', + ' if output_type == "pt":\n', + ' video = video_tensor\n', + ' else:\n', + ' video = tensor2vid(video_tensor)\n', + '\n', + ' if not return_dict:\n', + ' return (video,)\n', + '\n', + ' return {"frames": video}\n', + '\n', + ' # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n', + ' def _encode_prompt(\n', + ' self,\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt=None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' ):\n', + ' r"""\n', + ' Encodes the prompt into text encoder hidden states.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' prompt to be encoded\n', + ' num_images_per_prompt (`int`):\n', + ' number of images that should be generated per prompt\n', + ' do_classifier_free_guidance (`bool`):\n', + ' whether to use classifier free guidance or not\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the image generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' """\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' if prompt_embeds is None:\n', + ' text_inputs = self.tokenizer(\n', + ' prompt,\n', + ' padding="max_length",\n', + ' max_length=self.tokenizer.model_max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + ' text_input_ids = text_inputs.input_ids\n', + ' untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids\n', + '\n', + ' if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n', + ' removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n', + ' print(\n', + ' "The following part of your input was truncated because CLIP can only handle sequences up to"\n', + ' f" {self.tokenizer.model_max_length} tokens: {removed_text}"\n', + ' )\n', + '\n', + ' prompt_embeds = self.text_encoder(text_input_ids)\n', + ' prompt_embeds = prompt_embeds[0]\n', + ' prompt_embeds = torch.tensor(prompt_embeds)\n', + '\n', + ' bs_embed, seq_len, _ = prompt_embeds.shape\n', + ' # duplicate text embeddings for each generation per prompt, using mps friendly method\n', + ' prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # get unconditional embeddings for classifier free guidance\n', + ' if do_classifier_free_guidance and negative_prompt_embeds is None:\n', + ' uncond_tokens: List[str]\n', + ' if negative_prompt is None:\n', + ' uncond_tokens = [""] * batch_size\n', + ' elif type(prompt) is not type(negative_prompt):\n', + ' raise TypeError(f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" f" {type(prompt)}.")\n', + ' elif isinstance(negative_prompt, str):\n', + ' uncond_tokens = [negative_prompt]\n', + ' elif batch_size != len(negative_prompt):\n', + ' raise ValueError(\n', + ' f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"\n', + ' f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"\n', + ' " the batch size of `prompt`."\n', + ' )\n', + ' else:\n', + ' uncond_tokens = negative_prompt\n', + '\n', + ' max_length = prompt_embeds.shape[1]\n', + ' uncond_input = self.tokenizer(\n', + ' uncond_tokens,\n', + ' padding="max_length",\n', + ' max_length=max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + '\n', + ' negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n', + ' negative_prompt_embeds = negative_prompt_embeds[0]\n', + ' negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n', + '\n', + ' if do_classifier_free_guidance:\n', + ' # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n', + ' seq_len = negative_prompt_embeds.shape[1]\n', + '\n', + ' negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # For classifier free guidance, we need to do two forward passes.\n', + ' # Here we concatenate the unconditional and text embeddings into a single batch\n', + ' # to avoid doing two forward passes\n', + ' prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n', + '\n', + ' return prompt_embeds\n', + '\n', + ' def prepare_latents(\n', + ' self,\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' dtype,\n', + ' generator,\n', + ' latents=None,\n', + ' ):\n', + ' shape = (\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' self.num_frames,\n', + ' self.height // self.vae_scale_factor,\n', + ' self.width // self.vae_scale_factor,\n', + ' )\n', + ' if isinstance(generator, list) and len(generator) != batch_size:\n', + ' raise ValueError(\n', + ' f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"\n', + ' f" size of {batch_size}. Make sure the batch size matches the length of the generators."\n', + ' )\n', + '\n', + ' if latents is None:\n', + ' latents = randn_tensor(shape, generator=generator, dtype=dtype)\n', + '\n', + ' # scale the initial noise by the standard deviation required by the scheduler\n', + ' latents = latents * self.scheduler.init_noise_sigma\n', + ' return latents\n', + '\n', + ' def check_inputs(\n', + ' self,\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt=None,\n', + ' prompt_embeds=None,\n', + ' negative_prompt_embeds=None,\n', + ' ):\n', + ' if self.height % 8 != 0 or self.width % 8 != 0:\n', + ' raise ValueError(f"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.")\n', + '\n', + ' if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n', + ' raise ValueError(f"`callback_steps` has to be a positive integer but is {callback_steps} of type" f" {type(callback_steps)}.")\n', + '\n', + ' if prompt is not None and prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" " only forward one of the two."\n', + ' )\n', + ' elif prompt is None and prompt_embeds is None:\n', + ' raise ValueError("Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.")\n', + ' elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n', + ' raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")\n', + '\n', + ' if negative_prompt is not None and negative_prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"\n', + ' f" {negative_prompt_embeds}. Please make sure to only forward one of the two."\n', + ' )\n', + '\n', + ' if prompt_embeds is not None and negative_prompt_embeds is not None:\n', + ' if prompt_embeds.shape != negative_prompt_embeds.shape:\n', + ' raise ValueError(\n', + ' "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"\n', + ' f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"\n', + ' f" {negative_prompt_embeds.shape}."\n', + ' )\n', + '\n', + ' def decode_latents(self, latents):\n', + ' scale_factor = 0.18215\n', + ' latents = 1 / scale_factor * latents\n', + '\n', + ' batch_size, channels, num_frames, height, width = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n', + ' image = self.vae_decoder(latents)[0]\n', + ' image = torch.tensor(image)\n', + ' video = (\n', + ' image[None, :]\n', + ' .reshape(\n', + ' (\n', + ' batch_size,\n', + ' num_frames,\n', + ' -1,\n', + ' )\n', + ' + image.shape[2:]\n', + ' )\n', + ' .permute(0, 2, 1, 3, 4)\n', + ' )\n', + ' # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n', + ' video = video.float()\n', + ' return video' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Inference with OpenVINO\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'core = ov.Core()' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'select device from dropdown list for running inference using OpenVINO' ] }, { - "cell_type": "code", - "source": [ - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "from notebook_utils import device_widget\n", - "\n", - "device = device_widget()\n", - "\n", - "device" + 'cell_type': 'code', + 'source': [ + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + '\n', + 'from notebook_utils import device_widget\n', + '\n', + 'device = device_widget()\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_unet = core.compile_model(unet_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_unet = core.compile_model(unet_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)' ] }, { - "cell_type": "markdown", - "source": [ - "Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler." + 'cell_type': 'markdown', + 'source': [ + 'Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler.' ] }, { - "cell_type": "code", - "source": [ - "ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)" + 'cell_type': 'code', + 'source': [ + 'ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)' ] }, { - "cell_type": "markdown", - "source": [ - "### Define a prompt\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define a prompt\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "prompt = \"A panda eating bamboo on a rock.\"" + 'cell_type': 'code', + 'source': [ + 'prompt = "A panda eating bamboo on a rock."' ] }, { - "cell_type": "markdown", - "source": [ - "Let's generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section." + 'cell_type': 'markdown', + 'source': [ + 'Let\'s generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section.' ] }, { - "cell_type": "markdown", - "source": [ - "### Video generation\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Video generation\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "frames = ov_pipe(prompt, num_inference_steps=25)[\"frames\"]" + 'cell_type': 'code', + 'source': [ + 'frames = ov_pipe(prompt, num_inference_steps=25)["frames"]' ] }, { - "cell_type": "code", - "source": [ - "images = [PIL.Image.fromarray(frame) for frame in frames]\n", - "images[0].save(\"output.gif\", save_all=True, append_images=images[1:], duration=125, loop=0)\n", - "with open(\"output.gif\", \"rb\") as gif_file:\n", - " b64 = f\"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}\"\n", - "IPython.display.HTML(f'')" + 'cell_type': 'code', + 'source': [ + 'images = [PIL.Image.fromarray(frame) for frame in frames]\n', + 'images[0].save("output.gif", save_all=True, append_images=images[1:], duration=125, loop=0)\n', + 'with open("output.gif", "rb") as gif_file:\n', + ' b64 = f"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}"\n', + `IPython.display.HTML(f'')` ] }, { - "cell_type": "markdown", - "source": [ - "## Interactive demo\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Interactive demo\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "metadata": { - "test_replace": { - " demo.queue().launch(debug=True)": " demo.queue.launch()", - " demo.queue().launch(share=True, debug=True)": " demo.queue().launch(share=True)" + 'cell_type': 'code', + 'metadata': { + 'test_replace': { + ' demo.queue().launch(debug=True)': ' demo.queue.launch()', + ' demo.queue().launch(share=True, debug=True)': ' demo.queue().launch(share=True)' } }, - "source": [ - "def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n", - " generator = torch.Generator().manual_seed(seed)\n", - " frames = ov_pipe(\n", - " prompt,\n", - " num_inference_steps=num_inference_steps,\n", - " generator=generator,\n", - " )[\"frames\"]\n", - " out_file = tempfile.NamedTemporaryFile(suffix=\".gif\", delete=False)\n", - " images = [PIL.Image.fromarray(frame) for frame in frames]\n", - " images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n", - " return out_file.name" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "if not Path(\"gradio_helper.py\").exists():\n", - " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/zeroscope-text2video/gradio_helper.py\")\n", - " open(\"gradio_helper.py\", \"w\").write(r.text)\n", - "\n", - "from gradio_helper import make_demo\n", - "\n", - "demo = make_demo(fn=generate)\n", - "\n", - "try:\n", - " demo.queue().launch(debug=True)\n", - "except Exception:\n", - " demo.queue().launch(share=True, debug=True)\n", - "# If you are launching remotely, specify server_name and server_port\n", - "# EXAMPLE: `demo.launch(server_name='your server name', server_port='server port in int')`\n", - "# To learn more please refer to the Gradio docs: https://gradio.app/docs/" - ] - }, - { - "cell_type": "code", - "source": [ - "# please uncomment and run this cell for stopping gradio interface\n", - "# demo.close()" + 'source': [ + 'def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n', + ' generator = torch.Generator().manual_seed(seed)\n', + ' frames = ov_pipe(\n', + ' prompt,\n', + ' num_inference_steps=num_inference_steps,\n', + ' generator=generator,\n', + ' )["frames"]\n', + ' out_file = tempfile.NamedTemporaryFile(suffix=".gif", delete=False)\n', + ' images = [PIL.Image.fromarray(frame) for frame in frames]\n', + ' images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n', + ' return out_file.name' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'outputs': [], + 'source': [ + 'if not Path("gradio_helper.py").exists():\n', + ' r = requests.get(url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/zeroscope-text2video/gradio_helper.py")\n', + ' open("gradio_helper.py", "w").write(r.text)\n', + '\n', + 'from gradio_helper import make_demo\n', + '\n', + 'demo = make_demo(fn=generate)\n', + '\n', + 'try:\n', + ' demo.queue().launch(debug=True)\n', + 'except Exception:\n', + ' demo.queue().launch(share=True, debug=True)\n', + '# If you are launching remotely, specify server_name and server_port\n', + '# EXAMPLE: `demo.launch(server_name=\'your server name\', server_port=\'server port in int\')`\n', + '# To learn more please refer to the Gradio docs: https://gradio.app/docs/' + ] + }, + { + 'cell_type': 'code', + 'source': [ + '# please uncomment and run this cell for stopping gradio interface\n', + '# demo.close()' ] } ].map(fromJupyterCell) @@ -6644,895 +6644,895 @@ suite('NotebookDiff Diff Service', () => { const { mapping, diff } = await mapCells( [ { - "cell_type": "markdown", - "source": [ - "# Video generation with ZeroScope and OpenVINO\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Install and import required packages](#Install-and-import-required-packages)\n", - "- [Load the model](#Load-the-model)\n", - "- [Convert the model](#Convert-the-model)\n", - " - [Define the conversion function](#Define-the-conversion-function)\n", - " - [UNet](#UNet)\n", - " - [VAE](#VAE)\n", - " - [Text encoder](#Text-encoder)\n", - "- [Build a pipeline](#Build-a-pipeline)\n", - "- [Inference with OpenVINO](#Inference-with-OpenVINO)\n", - " - [Select inference device](#Select-inference-device)\n", - " - [Define a prompt](#Define-a-prompt)\n", - " - [Video generation](#Video-generation)\n", - "- [Interactive demo](#Interactive-demo)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" + 'cell_type': 'markdown', + 'source': [ + '# Video generation with ZeroScope and OpenVINO\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Install and import required packages](#Install-and-import-required-packages)\n', + '- [Load the model](#Load-the-model)\n', + '- [Convert the model](#Convert-the-model)\n', + ' - [Define the conversion function](#Define-the-conversion-function)\n', + ' - [UNet](#UNet)\n', + ' - [VAE](#VAE)\n', + ' - [Text encoder](#Text-encoder)\n', + '- [Build a pipeline](#Build-a-pipeline)\n', + '- [Inference with OpenVINO](#Inference-with-OpenVINO)\n', + ' - [Select inference device](#Select-inference-device)\n', + ' - [Define a prompt](#Define-a-prompt)\n', + ' - [Video generation](#Video-generation)\n', + '- [Interactive demo](#Interactive-demo)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' ] }, { - "cell_type": "markdown", - "source": [ - "The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n", - "\n", - "The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n", - "\n", - "The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n", - "\n", - "Both versions of the ZeroScope model are available on Hugging Face:\n", - " - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n", - " - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n", - "\n", - "We will use the first one." + 'cell_type': 'markdown', + 'source': [ + 'The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n', + '\n', + 'The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n', + '\n', + 'The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n', + '\n', + 'Both versions of the ZeroScope model are available on Hugging Face:\n', + ' - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n', + ' - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n', + '\n', + 'We will use the first one.' ] }, { - "cell_type": "markdown", - "source": [ - "
    \n", - " This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n", - "
    " + 'cell_type': 'markdown', + 'source': [ + '
    \n', + ' This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n', + '
    ' ] }, { - "cell_type": "markdown", - "source": [ - "## Install and import required packages\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Install and import required packages\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "To work with text-to-video synthesis model, we will use Hugging Face's [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`." + 'cell_type': 'markdown', + 'source': [ + 'To work with text-to-video synthesis model, we will use Hugging Face\'s [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`.' ] }, { - "cell_type": "code", - "source": [ - "%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu \"diffusers>=0.18.0\" \"torch>=2.1\" transformers \"openvino>=2023.1.0\" numpy \"gradio>=4.19\"" + 'cell_type': 'code', + 'source': [ + '%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu "diffusers>=0.18.0" "torch>=2.1" transformers "openvino>=2023.1.0" numpy "gradio>=4.19"' ] }, { - "cell_type": "code", - "source": [ - "import gc\n", - "from typing import Optional, Union, List, Callable\n", - "import base64\n", - "import tempfile\n", - "import warnings\n", - "\n", - "import diffusers\n", - "import transformers\n", - "import numpy as np\n", - "import IPython\n", - "import ipywidgets as widgets\n", - "import torch\n", - "import PIL\n", - "import gradio as gr\n", - "\n", - "import openvino as ov" + 'cell_type': 'code', + 'source': [ + 'import gc\n', + 'from typing import Optional, Union, List, Callable\n', + 'import base64\n', + 'import tempfile\n', + 'import warnings\n', + '\n', + 'import diffusers\n', + 'import transformers\n', + 'import numpy as np\n', + 'import IPython\n', + 'import ipywidgets as widgets\n', + 'import torch\n', + 'import PIL\n', + 'import gradio as gr\n', + '\n', + 'import openvino as ov' ] }, { - "cell_type": "markdown", - "source": [ - "Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption." + 'cell_type': 'markdown', + 'source': [ + `Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption.` ] }, { - "cell_type": "code", - "source": [ - "WIDTH = 432 # must be divisible by 8\n", - "HEIGHT = 240 # must be divisible by 8\n", - "NUM_FRAMES = 16" + 'cell_type': 'code', + 'source': [ + 'WIDTH = 432 # must be divisible by 8\n', + 'HEIGHT = 240 # must be divisible by 8\n', + 'NUM_FRAMES = 16' ] }, { - "cell_type": "markdown", - "source": [ - "## Load the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Load the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`." + 'cell_type': 'markdown', + 'source': [ + 'The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`.' ] }, { - "cell_type": "code", - "source": [ - "pipe = diffusers.DiffusionPipeline.from_pretrained(\"cerspense/zeroscope_v2_576w\")" + 'cell_type': 'code', + 'source': [ + 'pipe = diffusers.DiffusionPipeline.from_pretrained("cerspense/zeroscope_v2_576w")' ] }, { - "cell_type": "code", - "source": [ - "unet = pipe.unet\n", - "unet.eval()\n", - "vae = pipe.vae\n", - "vae.eval()\n", - "text_encoder = pipe.text_encoder\n", - "text_encoder.eval()\n", - "tokenizer = pipe.tokenizer\n", - "scheduler = pipe.scheduler\n", - "vae_scale_factor = pipe.vae_scale_factor\n", - "unet_in_channels = pipe.unet.config.in_channels\n", - "sample_width = WIDTH // vae_scale_factor\n", - "sample_height = HEIGHT // vae_scale_factor\n", - "del pipe\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet = pipe.unet\n', + 'unet.eval()\n', + 'vae = pipe.vae\n', + 'vae.eval()\n', + 'text_encoder = pipe.text_encoder\n', + 'text_encoder.eval()\n', + 'tokenizer = pipe.tokenizer\n', + 'scheduler = pipe.scheduler\n', + 'vae_scale_factor = pipe.vae_scale_factor\n', + 'unet_in_channels = pipe.unet.config.in_channels\n', + 'sample_width = WIDTH // vae_scale_factor\n', + 'sample_height = HEIGHT // vae_scale_factor\n', + 'del pipe\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "## Convert the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Convert the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video." + 'cell_type': 'markdown', + 'source': [ + `The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video.` ] }, { - "cell_type": "markdown", - "source": [] + 'cell_type': 'markdown', + 'source': [] }, { - "cell_type": "markdown", - "source": [ - "### Define the conversion function\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define the conversion function\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion." + 'cell_type': 'markdown', + 'source': [ + 'Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion.' ] }, { - "cell_type": "code", - "source": [ - "warnings.filterwarnings(\"ignore\", category=torch.jit.TracerWarning)" + 'cell_type': 'code', + 'source': [ + 'warnings.filterwarnings("ignore", category=torch.jit.TracerWarning)' ] }, { - "cell_type": "code", - "source": [ - "from pathlib import Path\n", - "\n", - "\n", - "def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n", - " xml_path = Path(xml_path)\n", - " if not xml_path.exists():\n", - " xml_path.parent.mkdir(parents=True, exist_ok=True)\n", - " with torch.no_grad():\n", - " converted_model = ov.convert_model(model, **convert_kwargs)\n", - " ov.save_model(converted_model, xml_path)\n", - " del converted_model\n", - " gc.collect()\n", - " torch._C._jit_clear_class_registry()\n", - " torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n", - " torch.jit._state._clear_class_state()\n", - " return xml_path" + 'cell_type': 'code', + 'source': [ + 'from pathlib import Path\n', + '\n', + '\n', + 'def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n', + ' xml_path = Path(xml_path)\n', + ' if not xml_path.exists():\n', + ' xml_path.parent.mkdir(parents=True, exist_ok=True)\n', + ' with torch.no_grad():\n', + ' converted_model = ov.convert_model(model, **convert_kwargs)\n', + ' ov.save_model(converted_model, xml_path)\n', + ' del converted_model\n', + ' gc.collect()\n', + ' torch._C._jit_clear_class_registry()\n', + ' torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n', + ' torch.jit._state._clear_class_state()\n', + ' return xml_path' ] }, { - "cell_type": "markdown", - "source": [ - "### UNet\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### UNet\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output." + 'cell_type': 'markdown', + 'source': [ + 'Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output.' ] }, { - "cell_type": "code", - "source": [ - "unet_xml_path = convert(\n", - " unet,\n", - " \"models/unet.xml\",\n", - " example_input={\n", - " \"sample\": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n", - " \"timestep\": torch.tensor(1),\n", - " \"encoder_hidden_states\": torch.randn(2, 77, 1024),\n", - " },\n", - " input=[\n", - " (\"sample\", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n", - " (\"timestep\", ()),\n", - " (\"encoder_hidden_states\", (2, 77, 1024)),\n", - " ],\n", - ")\n", - "del unet\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet_xml_path = convert(\n', + ' unet,\n', + ' "models/unet.xml",\n', + ' example_input={\n', + ' "sample": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n', + ' "timestep": torch.tensor(1),\n', + ' "encoder_hidden_states": torch.randn(2, 77, 1024),\n', + ' },\n', + ' input=[\n', + ' ("sample", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n', + ' ("timestep", ()),\n', + ' ("encoder_hidden_states", (2, 77, 1024)),\n', + ' ],\n', + ')\n', + 'del unet\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "### VAE\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### VAE\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part." + 'cell_type': 'markdown', + 'source': [ + 'Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part.' ] }, { - "cell_type": "code", - "source": [ - "class VaeDecoderWrapper(torch.nn.Module):\n", - " def __init__(self, vae):\n", - " super().__init__()\n", - " self.vae = vae\n", - "\n", - " def forward(self, z: torch.FloatTensor):\n", - " return self.vae.decode(z)" + 'cell_type': 'code', + 'source': [ + 'class VaeDecoderWrapper(torch.nn.Module):\n', + ' def __init__(self, vae):\n', + ' super().__init__()\n', + ' self.vae = vae\n', + '\n', + ' def forward(self, z: torch.FloatTensor):\n', + ' return self.vae.decode(z)' ] }, { - "cell_type": "code", - "source": [ - "vae_decoder_xml_path = convert(\n", - " VaeDecoderWrapper(vae),\n", - " \"models/vae.xml\",\n", - " example_input=torch.randn(2, 4, 32, 32),\n", - " input=((NUM_FRAMES, 4, sample_height, sample_width)),\n", - ")\n", - "del vae\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Text encoder\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Text encoder is used to encode the input prompt to tensor. Default tensor length is 77." - ] - }, - { - "cell_type": "code", - "source": [ - "text_encoder_xml = convert(\n", - " text_encoder,\n", - " \"models/text_encoder.xml\",\n", - " example_input=torch.ones(1, 77, dtype=torch.int64),\n", - " input=((1, 77), ov.Type.i64),\n", - ")\n", - "del text_encoder\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Build a pipeline\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n", - " # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n", - " # reshape to ncfhw\n", - " mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " # unnormalize back to [0,1]\n", - " video = video.mul_(std).add_(mean)\n", - " video.clamp_(0, 1)\n", - " # prepare the final outputs\n", - " i, c, f, h, w = video.shape\n", - " images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n", - " images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n", - " images = [(image.cpu().numpy() * 255).astype(\"uint8\") for image in images] # f h w c\n", - " return images" - ] - }, - { - "cell_type": "code", - "source": [ - "try:\n", - " from diffusers.utils import randn_tensor\n", - "except ImportError:\n", - " from diffusers.utils.torch_utils import randn_tensor\n", - "\n", - "\n", - "class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n", - " def __init__(\n", - " self,\n", - " vae_decoder: ov.CompiledModel,\n", - " text_encoder: ov.CompiledModel,\n", - " tokenizer: transformers.CLIPTokenizer,\n", - " unet: ov.CompiledModel,\n", - " scheduler: diffusers.schedulers.DDIMScheduler,\n", - " ):\n", - " super().__init__()\n", - "\n", - " self.vae_decoder = vae_decoder\n", - " self.text_encoder = text_encoder\n", - " self.tokenizer = tokenizer\n", - " self.unet = unet\n", - " self.scheduler = scheduler\n", - " self.vae_scale_factor = vae_scale_factor\n", - " self.unet_in_channels = unet_in_channels\n", - " self.width = WIDTH\n", - " self.height = HEIGHT\n", - " self.num_frames = NUM_FRAMES\n", - "\n", - " def __call__(\n", - " self,\n", - " prompt: Union[str, List[str]] = None,\n", - " num_inference_steps: int = 50,\n", - " guidance_scale: float = 9.0,\n", - " negative_prompt: Optional[Union[str, List[str]]] = None,\n", - " eta: float = 0.0,\n", - " generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n", - " latents: Optional[torch.FloatTensor] = None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " output_type: Optional[str] = \"np\",\n", - " return_dict: bool = True,\n", - " callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n", - " callback_steps: int = 1,\n", - " ):\n", - " r\"\"\"\n", - " Function invoked when calling the pipeline for generation.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n", - " instead.\n", - " num_inference_steps (`int`, *optional*, defaults to 50):\n", - " The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n", - " expense of slower inference.\n", - " guidance_scale (`float`, *optional*, defaults to 7.5):\n", - " Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n", - " `guidance_scale` is defined as `w` of equation 2. of [Imagen\n", - " Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n", - " 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n", - " usually at the expense of lower video quality.\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the video generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " eta (`float`, *optional*, defaults to 0.0):\n", - " Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n", - " [`schedulers.DDIMScheduler`], will be ignored for others.\n", - " generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n", - " One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n", - " to make generation deterministic.\n", - " latents (`torch.FloatTensor`, *optional*):\n", - " Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n", - " generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n", - " tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n", - " `(batch_size, num_channel, num_frames, height, width)`.\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " output_type (`str`, *optional*, defaults to `\"np\"`):\n", - " The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n", - " return_dict (`bool`, *optional*, defaults to `True`):\n", - " Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n", - " plain tuple.\n", - " callback (`Callable`, *optional*):\n", - " A function that will be called every `callback_steps` steps during inference. The function will be\n", - " called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n", - " callback_steps (`int`, *optional*, defaults to 1):\n", - " The frequency at which the `callback` function will be called. If not specified, the callback will be\n", - " called at every step.\n", - "\n", - " Returns:\n", - " `List[np.ndarray]`: generated video frames\n", - " \"\"\"\n", - "\n", - " num_images_per_prompt = 1\n", - "\n", - " # 1. Check inputs. Raise error if not correct\n", - " self.check_inputs(\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt,\n", - " prompt_embeds,\n", - " negative_prompt_embeds,\n", - " )\n", - "\n", - " # 2. Define call parameters\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n", - " # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n", - " # corresponds to doing no classifier free guidance.\n", - " do_classifier_free_guidance = guidance_scale > 1.0\n", - "\n", - " # 3. Encode input prompt\n", - " prompt_embeds = self._encode_prompt(\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt,\n", - " prompt_embeds=prompt_embeds,\n", - " negative_prompt_embeds=negative_prompt_embeds,\n", - " )\n", - "\n", - " # 4. Prepare timesteps\n", - " self.scheduler.set_timesteps(num_inference_steps)\n", - " timesteps = self.scheduler.timesteps\n", - "\n", - " # 5. Prepare latent variables\n", - " num_channels_latents = self.unet_in_channels\n", - " latents = self.prepare_latents(\n", - " batch_size * num_images_per_prompt,\n", - " num_channels_latents,\n", - " prompt_embeds.dtype,\n", - " generator,\n", - " latents,\n", - " )\n", - "\n", - " # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n", - " extra_step_kwargs = {\"generator\": generator, \"eta\": eta}\n", - "\n", - " # 7. Denoising loop\n", - " num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n", - " with self.progress_bar(total=num_inference_steps) as progress_bar:\n", - " for i, t in enumerate(timesteps):\n", - " # expand the latents if we are doing classifier free guidance\n", - " latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n", - " latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n", - "\n", - " # predict the noise residual\n", - " noise_pred = self.unet(\n", - " {\n", - " \"sample\": latent_model_input,\n", - " \"timestep\": t,\n", - " \"encoder_hidden_states\": prompt_embeds,\n", - " }\n", - " )[0]\n", - " noise_pred = torch.tensor(noise_pred)\n", - "\n", - " # perform guidance\n", - " if do_classifier_free_guidance:\n", - " noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n", - " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", - "\n", - " # reshape latents\n", - " bsz, channel, frames, width, height = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - " noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - "\n", - " # compute the previous noisy sample x_t -> x_t-1\n", - " latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n", - "\n", - " # reshape latents back\n", - " latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n", - "\n", - " # call the callback, if provided\n", - " if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n", - " progress_bar.update()\n", - " if callback is not None and i % callback_steps == 0:\n", - " callback(i, t, latents)\n", - "\n", - " video_tensor = self.decode_latents(latents)\n", - "\n", - " if output_type == \"pt\":\n", - " video = video_tensor\n", - " else:\n", - " video = tensor2vid(video_tensor)\n", - "\n", - " if not return_dict:\n", - " return (video,)\n", - "\n", - " return {\"frames\": video}\n", - "\n", - " # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n", - " def _encode_prompt(\n", - " self,\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt=None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " ):\n", - " r\"\"\"\n", - " Encodes the prompt into text encoder hidden states.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " prompt to be encoded\n", - " num_images_per_prompt (`int`):\n", - " number of images that should be generated per prompt\n", - " do_classifier_free_guidance (`bool`):\n", - " whether to use classifier free guidance or not\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the image generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " \"\"\"\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " if prompt_embeds is None:\n", - " text_inputs = self.tokenizer(\n", - " prompt,\n", - " padding=\"max_length\",\n", - " max_length=self.tokenizer.model_max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - " text_input_ids = text_inputs.input_ids\n", - " untruncated_ids = self.tokenizer(prompt, padding=\"longest\", return_tensors=\"pt\").input_ids\n", - "\n", - " if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n", - " removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n", - " print(\n", - " \"The following part of your input was truncated because CLIP can only handle sequences up to\"\n", - " f\" {self.tokenizer.model_max_length} tokens: {removed_text}\"\n", - " )\n", - "\n", - " prompt_embeds = self.text_encoder(text_input_ids)\n", - " prompt_embeds = prompt_embeds[0]\n", - " prompt_embeds = torch.tensor(prompt_embeds)\n", - "\n", - " bs_embed, seq_len, _ = prompt_embeds.shape\n", - " # duplicate text embeddings for each generation per prompt, using mps friendly method\n", - " prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # get unconditional embeddings for classifier free guidance\n", - " if do_classifier_free_guidance and negative_prompt_embeds is None:\n", - " uncond_tokens: List[str]\n", - " if negative_prompt is None:\n", - " uncond_tokens = [\"\"] * batch_size\n", - " elif type(prompt) is not type(negative_prompt):\n", - " raise TypeError(f\"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=\" f\" {type(prompt)}.\")\n", - " elif isinstance(negative_prompt, str):\n", - " uncond_tokens = [negative_prompt]\n", - " elif batch_size != len(negative_prompt):\n", - " raise ValueError(\n", - " f\"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:\"\n", - " f\" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches\"\n", - " \" the batch size of `prompt`.\"\n", - " )\n", - " else:\n", - " uncond_tokens = negative_prompt\n", - "\n", - " max_length = prompt_embeds.shape[1]\n", - " uncond_input = self.tokenizer(\n", - " uncond_tokens,\n", - " padding=\"max_length\",\n", - " max_length=max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - "\n", - " negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n", - " negative_prompt_embeds = negative_prompt_embeds[0]\n", - " negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n", - "\n", - " if do_classifier_free_guidance:\n", - " # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n", - " seq_len = negative_prompt_embeds.shape[1]\n", - "\n", - " negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # For classifier free guidance, we need to do two forward passes.\n", - " # Here we concatenate the unconditional and text embeddings into a single batch\n", - " # to avoid doing two forward passes\n", - " prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n", - "\n", - " return prompt_embeds\n", - "\n", - " def prepare_latents(\n", - " self,\n", - " batch_size,\n", - " num_channels_latents,\n", - " dtype,\n", - " generator,\n", - " latents=None,\n", - " ):\n", - " shape = (\n", - " batch_size,\n", - " num_channels_latents,\n", - " self.num_frames,\n", - " self.height // self.vae_scale_factor,\n", - " self.width // self.vae_scale_factor,\n", - " )\n", - " if isinstance(generator, list) and len(generator) != batch_size:\n", - " raise ValueError(\n", - " f\"You have passed a list of generators of length {len(generator)}, but requested an effective batch\"\n", - " f\" size of {batch_size}. Make sure the batch size matches the length of the generators.\"\n", - " )\n", - "\n", - " if latents is None:\n", - " latents = randn_tensor(shape, generator=generator, dtype=dtype)\n", - "\n", - " # scale the initial noise by the standard deviation required by the scheduler\n", - " latents = latents * self.scheduler.init_noise_sigma\n", - " return latents\n", - "\n", - " def check_inputs(\n", - " self,\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt=None,\n", - " prompt_embeds=None,\n", - " negative_prompt_embeds=None,\n", - " ):\n", - " if self.height % 8 != 0 or self.width % 8 != 0:\n", - " raise ValueError(f\"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.\")\n", - "\n", - " if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n", - " raise ValueError(f\"`callback_steps` has to be a positive integer but is {callback_steps} of type\" f\" {type(callback_steps)}.\")\n", - "\n", - " if prompt is not None and prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to\" \" only forward one of the two.\"\n", - " )\n", - " elif prompt is None and prompt_embeds is None:\n", - " raise ValueError(\"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.\")\n", - " elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n", - " raise ValueError(f\"`prompt` has to be of type `str` or `list` but is {type(prompt)}\")\n", - "\n", - " if negative_prompt is not None and negative_prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:\"\n", - " f\" {negative_prompt_embeds}. Please make sure to only forward one of the two.\"\n", - " )\n", - "\n", - " if prompt_embeds is not None and negative_prompt_embeds is not None:\n", - " if prompt_embeds.shape != negative_prompt_embeds.shape:\n", - " raise ValueError(\n", - " \"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but\"\n", - " f\" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`\"\n", - " f\" {negative_prompt_embeds.shape}.\"\n", - " )\n", - "\n", - " def decode_latents(self, latents):\n", - " scale_factor = 0.18215\n", - " latents = 1 / scale_factor * latents\n", - "\n", - " batch_size, channels, num_frames, height, width = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n", - " image = self.vae_decoder(latents)[0]\n", - " image = torch.tensor(image)\n", - " video = (\n", - " image[None, :]\n", - " .reshape(\n", - " (\n", - " batch_size,\n", - " num_frames,\n", - " -1,\n", - " )\n", - " + image.shape[2:]\n", - " )\n", - " .permute(0, 2, 1, 3, 4)\n", - " )\n", - " # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n", - " video = video.float()\n", - " return video" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Inference with OpenVINO\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "core = ov.Core()" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "select device from dropdown list for running inference using OpenVINO" + 'cell_type': 'code', + 'source': [ + 'vae_decoder_xml_path = convert(\n', + ' VaeDecoderWrapper(vae),\n', + ' "models/vae.xml",\n', + ' example_input=torch.randn(2, 4, 32, 32),\n', + ' input=((NUM_FRAMES, 4, sample_height, sample_width)),\n', + ')\n', + 'del vae\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Text encoder\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + 'Text encoder is used to encode the input prompt to tensor. Default tensor length is 77.' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'text_encoder_xml = convert(\n', + ' text_encoder,\n', + ' "models/text_encoder.xml",\n', + ' example_input=torch.ones(1, 77, dtype=torch.int64),\n', + ' input=((1, 77), ov.Type.i64),\n', + ')\n', + 'del text_encoder\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Build a pipeline\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n', + ' # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n', + ' # reshape to ncfhw\n', + ' mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' # unnormalize back to [0,1]\n', + ' video = video.mul_(std).add_(mean)\n', + ' video.clamp_(0, 1)\n', + ' # prepare the final outputs\n', + ' i, c, f, h, w = video.shape\n', + ' images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n', + ' images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n', + ' images = [(image.cpu().numpy() * 255).astype("uint8") for image in images] # f h w c\n', + ' return images' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'try:\n', + ' from diffusers.utils import randn_tensor\n', + 'except ImportError:\n', + ' from diffusers.utils.torch_utils import randn_tensor\n', + '\n', + '\n', + 'class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n', + ' def __init__(\n', + ' self,\n', + ' vae_decoder: ov.CompiledModel,\n', + ' text_encoder: ov.CompiledModel,\n', + ' tokenizer: transformers.CLIPTokenizer,\n', + ' unet: ov.CompiledModel,\n', + ' scheduler: diffusers.schedulers.DDIMScheduler,\n', + ' ):\n', + ' super().__init__()\n', + '\n', + ' self.vae_decoder = vae_decoder\n', + ' self.text_encoder = text_encoder\n', + ' self.tokenizer = tokenizer\n', + ' self.unet = unet\n', + ' self.scheduler = scheduler\n', + ' self.vae_scale_factor = vae_scale_factor\n', + ' self.unet_in_channels = unet_in_channels\n', + ' self.width = WIDTH\n', + ' self.height = HEIGHT\n', + ' self.num_frames = NUM_FRAMES\n', + '\n', + ' def __call__(\n', + ' self,\n', + ' prompt: Union[str, List[str]] = None,\n', + ' num_inference_steps: int = 50,\n', + ' guidance_scale: float = 9.0,\n', + ' negative_prompt: Optional[Union[str, List[str]]] = None,\n', + ' eta: float = 0.0,\n', + ' generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n', + ' latents: Optional[torch.FloatTensor] = None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' output_type: Optional[str] = "np",\n', + ' return_dict: bool = True,\n', + ' callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n', + ' callback_steps: int = 1,\n', + ' ):\n', + ' r"""\n', + ' Function invoked when calling the pipeline for generation.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n', + ' instead.\n', + ' num_inference_steps (`int`, *optional*, defaults to 50):\n', + ' The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n', + ' expense of slower inference.\n', + ' guidance_scale (`float`, *optional*, defaults to 7.5):\n', + ' Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n', + ' `guidance_scale` is defined as `w` of equation 2. of [Imagen\n', + ' Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n', + ' 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n', + ' usually at the expense of lower video quality.\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the video generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' eta (`float`, *optional*, defaults to 0.0):\n', + ' Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n', + ' [`schedulers.DDIMScheduler`], will be ignored for others.\n', + ' generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n', + ' One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n', + ' to make generation deterministic.\n', + ' latents (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n', + ' generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n', + ' tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n', + ' `(batch_size, num_channel, num_frames, height, width)`.\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' output_type (`str`, *optional*, defaults to `"np"`):\n', + ' The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n', + ' return_dict (`bool`, *optional*, defaults to `True`):\n', + ' Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n', + ' plain tuple.\n', + ' callback (`Callable`, *optional*):\n', + ' A function that will be called every `callback_steps` steps during inference. The function will be\n', + ' called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n', + ' callback_steps (`int`, *optional*, defaults to 1):\n', + ' The frequency at which the `callback` function will be called. If not specified, the callback will be\n', + ' called at every step.\n', + '\n', + ' Returns:\n', + ' `List[np.ndarray]`: generated video frames\n', + ' """\n', + '\n', + ' num_images_per_prompt = 1\n', + '\n', + ' # 1. Check inputs. Raise error if not correct\n', + ' self.check_inputs(\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt,\n', + ' prompt_embeds,\n', + ' negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 2. Define call parameters\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n', + ' # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n', + ' # corresponds to doing no classifier free guidance.\n', + ' do_classifier_free_guidance = guidance_scale > 1.0\n', + '\n', + ' # 3. Encode input prompt\n', + ' prompt_embeds = self._encode_prompt(\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt,\n', + ' prompt_embeds=prompt_embeds,\n', + ' negative_prompt_embeds=negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 4. Prepare timesteps\n', + ' self.scheduler.set_timesteps(num_inference_steps)\n', + ' timesteps = self.scheduler.timesteps\n', + '\n', + ' # 5. Prepare latent variables\n', + ' num_channels_latents = self.unet_in_channels\n', + ' latents = self.prepare_latents(\n', + ' batch_size * num_images_per_prompt,\n', + ' num_channels_latents,\n', + ' prompt_embeds.dtype,\n', + ' generator,\n', + ' latents,\n', + ' )\n', + '\n', + ' # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n', + ' extra_step_kwargs = {"generator": generator, "eta": eta}\n', + '\n', + ' # 7. Denoising loop\n', + ' num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n', + ' with self.progress_bar(total=num_inference_steps) as progress_bar:\n', + ' for i, t in enumerate(timesteps):\n', + ' # expand the latents if we are doing classifier free guidance\n', + ' latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n', + ' latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n', + '\n', + ' # predict the noise residual\n', + ' noise_pred = self.unet(\n', + ' {\n', + ' "sample": latent_model_input,\n', + ' "timestep": t,\n', + ' "encoder_hidden_states": prompt_embeds,\n', + ' }\n', + ' )[0]\n', + ' noise_pred = torch.tensor(noise_pred)\n', + '\n', + ' # perform guidance\n', + ' if do_classifier_free_guidance:\n', + ' noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n', + ' noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n', + '\n', + ' # reshape latents\n', + ' bsz, channel, frames, width, height = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + ' noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + '\n', + ' # compute the previous noisy sample x_t -> x_t-1\n', + ' latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n', + '\n', + ' # reshape latents back\n', + ' latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n', + '\n', + ' # call the callback, if provided\n', + ' if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n', + ' progress_bar.update()\n', + ' if callback is not None and i % callback_steps == 0:\n', + ' callback(i, t, latents)\n', + '\n', + ' video_tensor = self.decode_latents(latents)\n', + '\n', + ' if output_type == "pt":\n', + ' video = video_tensor\n', + ' else:\n', + ' video = tensor2vid(video_tensor)\n', + '\n', + ' if not return_dict:\n', + ' return (video,)\n', + '\n', + ' return {"frames": video}\n', + '\n', + ' # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n', + ' def _encode_prompt(\n', + ' self,\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt=None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' ):\n', + ' r"""\n', + ' Encodes the prompt into text encoder hidden states.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' prompt to be encoded\n', + ' num_images_per_prompt (`int`):\n', + ' number of images that should be generated per prompt\n', + ' do_classifier_free_guidance (`bool`):\n', + ' whether to use classifier free guidance or not\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the image generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' """\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' if prompt_embeds is None:\n', + ' text_inputs = self.tokenizer(\n', + ' prompt,\n', + ' padding="max_length",\n', + ' max_length=self.tokenizer.model_max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + ' text_input_ids = text_inputs.input_ids\n', + ' untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids\n', + '\n', + ' if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n', + ' removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n', + ' print(\n', + ' "The following part of your input was truncated because CLIP can only handle sequences up to"\n', + ' f" {self.tokenizer.model_max_length} tokens: {removed_text}"\n', + ' )\n', + '\n', + ' prompt_embeds = self.text_encoder(text_input_ids)\n', + ' prompt_embeds = prompt_embeds[0]\n', + ' prompt_embeds = torch.tensor(prompt_embeds)\n', + '\n', + ' bs_embed, seq_len, _ = prompt_embeds.shape\n', + ' # duplicate text embeddings for each generation per prompt, using mps friendly method\n', + ' prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # get unconditional embeddings for classifier free guidance\n', + ' if do_classifier_free_guidance and negative_prompt_embeds is None:\n', + ' uncond_tokens: List[str]\n', + ' if negative_prompt is None:\n', + ' uncond_tokens = [""] * batch_size\n', + ' elif type(prompt) is not type(negative_prompt):\n', + ' raise TypeError(f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" f" {type(prompt)}.")\n', + ' elif isinstance(negative_prompt, str):\n', + ' uncond_tokens = [negative_prompt]\n', + ' elif batch_size != len(negative_prompt):\n', + ' raise ValueError(\n', + ' f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"\n', + ' f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"\n', + ' " the batch size of `prompt`."\n', + ' )\n', + ' else:\n', + ' uncond_tokens = negative_prompt\n', + '\n', + ' max_length = prompt_embeds.shape[1]\n', + ' uncond_input = self.tokenizer(\n', + ' uncond_tokens,\n', + ' padding="max_length",\n', + ' max_length=max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + '\n', + ' negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n', + ' negative_prompt_embeds = negative_prompt_embeds[0]\n', + ' negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n', + '\n', + ' if do_classifier_free_guidance:\n', + ' # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n', + ' seq_len = negative_prompt_embeds.shape[1]\n', + '\n', + ' negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # For classifier free guidance, we need to do two forward passes.\n', + ' # Here we concatenate the unconditional and text embeddings into a single batch\n', + ' # to avoid doing two forward passes\n', + ' prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n', + '\n', + ' return prompt_embeds\n', + '\n', + ' def prepare_latents(\n', + ' self,\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' dtype,\n', + ' generator,\n', + ' latents=None,\n', + ' ):\n', + ' shape = (\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' self.num_frames,\n', + ' self.height // self.vae_scale_factor,\n', + ' self.width // self.vae_scale_factor,\n', + ' )\n', + ' if isinstance(generator, list) and len(generator) != batch_size:\n', + ' raise ValueError(\n', + ' f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"\n', + ' f" size of {batch_size}. Make sure the batch size matches the length of the generators."\n', + ' )\n', + '\n', + ' if latents is None:\n', + ' latents = randn_tensor(shape, generator=generator, dtype=dtype)\n', + '\n', + ' # scale the initial noise by the standard deviation required by the scheduler\n', + ' latents = latents * self.scheduler.init_noise_sigma\n', + ' return latents\n', + '\n', + ' def check_inputs(\n', + ' self,\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt=None,\n', + ' prompt_embeds=None,\n', + ' negative_prompt_embeds=None,\n', + ' ):\n', + ' if self.height % 8 != 0 or self.width % 8 != 0:\n', + ' raise ValueError(f"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.")\n', + '\n', + ' if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n', + ' raise ValueError(f"`callback_steps` has to be a positive integer but is {callback_steps} of type" f" {type(callback_steps)}.")\n', + '\n', + ' if prompt is not None and prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" " only forward one of the two."\n', + ' )\n', + ' elif prompt is None and prompt_embeds is None:\n', + ' raise ValueError("Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.")\n', + ' elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n', + ' raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")\n', + '\n', + ' if negative_prompt is not None and negative_prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"\n', + ' f" {negative_prompt_embeds}. Please make sure to only forward one of the two."\n', + ' )\n', + '\n', + ' if prompt_embeds is not None and negative_prompt_embeds is not None:\n', + ' if prompt_embeds.shape != negative_prompt_embeds.shape:\n', + ' raise ValueError(\n', + ' "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"\n', + ' f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"\n', + ' f" {negative_prompt_embeds.shape}."\n', + ' )\n', + '\n', + ' def decode_latents(self, latents):\n', + ' scale_factor = 0.18215\n', + ' latents = 1 / scale_factor * latents\n', + '\n', + ' batch_size, channels, num_frames, height, width = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n', + ' image = self.vae_decoder(latents)[0]\n', + ' image = torch.tensor(image)\n', + ' video = (\n', + ' image[None, :]\n', + ' .reshape(\n', + ' (\n', + ' batch_size,\n', + ' num_frames,\n', + ' -1,\n', + ' )\n', + ' + image.shape[2:]\n', + ' )\n', + ' .permute(0, 2, 1, 3, 4)\n', + ' )\n', + ' # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n', + ' video = video.float()\n', + ' return video' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Inference with OpenVINO\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'core = ov.Core()' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'select device from dropdown list for running inference using OpenVINO' ] }, { - "cell_type": "code", - "source": [ - "device = widgets.Dropdown(\n", - " options=core.available_devices + [\"AUTO\"],\n", - " value=\"AUTO\",\n", - " description=\"Device:\",\n", - " disabled=False,\n", - ")\n", - "\n", - "device" + 'cell_type': 'code', + 'source': [ + 'device = widgets.Dropdown(\n', + ' options=core.available_devices + ["AUTO"],\n', + ' value="AUTO",\n', + ' description="Device:",\n', + ' disabled=False,\n', + ')\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_unet = core.compile_model(unet_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_unet = core.compile_model(unet_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)' ] }, { - "cell_type": "markdown", - "source": [ - "Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler." + 'cell_type': 'markdown', + 'source': [ + 'Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler.' ] }, { - "cell_type": "code", - "source": [ - "ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)" + 'cell_type': 'code', + 'source': [ + 'ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)' ] }, { - "cell_type": "markdown", - "source": [ - "### Define a prompt\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define a prompt\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "prompt = \"A panda eating bamboo on a rock.\"" + 'cell_type': 'code', + 'source': [ + 'prompt = "A panda eating bamboo on a rock."' ] }, { - "cell_type": "markdown", - "source": [ - "Let's generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section." + 'cell_type': 'markdown', + 'source': [ + 'Let\'s generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section.' ] }, { - "cell_type": "markdown", - "source": [ - "### Video generation\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Video generation\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "frames = ov_pipe(prompt, num_inference_steps=25)[\"frames\"]" + 'cell_type': 'code', + 'source': [ + 'frames = ov_pipe(prompt, num_inference_steps=25)["frames"]' ] }, { - "cell_type": "code", - "source": [ - "images = [PIL.Image.fromarray(frame) for frame in frames]\n", - "images[0].save(\"output.gif\", save_all=True, append_images=images[1:], duration=125, loop=0)\n", - "with open(\"output.gif\", \"rb\") as gif_file:\n", - " b64 = f\"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}\"\n", - "IPython.display.HTML(f'')" + 'cell_type': 'code', + 'source': [ + 'images = [PIL.Image.fromarray(frame) for frame in frames]\n', + 'images[0].save("output.gif", save_all=True, append_images=images[1:], duration=125, loop=0)\n', + 'with open("output.gif", "rb") as gif_file:\n', + ' b64 = f"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}"\n', + `IPython.display.HTML(f'')` ] }, { - "cell_type": "markdown", - "source": [ - "## Interactive demo\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Interactive demo\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n", - " generator = torch.Generator().manual_seed(seed)\n", - " frames = ov_pipe(\n", - " prompt,\n", - " num_inference_steps=num_inference_steps,\n", - " generator=generator,\n", - " )[\"frames\"]\n", - " out_file = tempfile.NamedTemporaryFile(suffix=\".gif\", delete=False)\n", - " images = [PIL.Image.fromarray(frame) for frame in frames]\n", - " images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n", - " return out_file.name\n", - "\n", - "\n", - "demo = gr.Interface(\n", - " generate,\n", - " [\n", - " gr.Textbox(label=\"Prompt\"),\n", - " gr.Slider(0, 1000000, value=42, label=\"Seed\", step=1),\n", - " gr.Slider(10, 50, value=25, label=\"Number of inference steps\", step=1),\n", - " ],\n", - " gr.Image(label=\"Result\"),\n", - " examples=[\n", - " [\"An astronaut riding a horse.\", 0, 25],\n", - " [\"A panda eating bamboo on a rock.\", 0, 25],\n", - " [\"Spiderman is surfing.\", 0, 25],\n", - " ],\n", - " allow_flagging=\"never\",\n", - ")\n", - "\n", - "try:\n", - " demo.queue().launch(debug=True)\n", - "except Exception:\n", - " demo.queue().launch(share=True, debug=True)\n", - "# if you are launching remotely, specify server_name and server_port\n", - "# demo.launch(server_name='your server name', server_port='server port in int')\n", - "# Read more in the docs: https://gradio.app/docs/" + 'cell_type': 'code', + 'source': [ + 'def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n', + ' generator = torch.Generator().manual_seed(seed)\n', + ' frames = ov_pipe(\n', + ' prompt,\n', + ' num_inference_steps=num_inference_steps,\n', + ' generator=generator,\n', + ' )["frames"]\n', + ' out_file = tempfile.NamedTemporaryFile(suffix=".gif", delete=False)\n', + ' images = [PIL.Image.fromarray(frame) for frame in frames]\n', + ' images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n', + ' return out_file.name\n', + '\n', + '\n', + 'demo = gr.Interface(\n', + ' generate,\n', + ' [\n', + ' gr.Textbox(label="Prompt"),\n', + ' gr.Slider(0, 1000000, value=42, label="Seed", step=1),\n', + ' gr.Slider(10, 50, value=25, label="Number of inference steps", step=1),\n', + ' ],\n', + ' gr.Image(label="Result"),\n', + ' examples=[\n', + ' ["An astronaut riding a horse.", 0, 25],\n', + ' ["A panda eating bamboo on a rock.", 0, 25],\n', + ' ["Spiderman is surfing.", 0, 25],\n', + ' ],\n', + ' allow_flagging="never",\n', + ')\n', + '\n', + 'try:\n', + ' demo.queue().launch(debug=True)\n', + 'except Exception:\n', + ' demo.queue().launch(share=True, debug=True)\n', + '# if you are launching remotely, specify server_name and server_port\n', + `# demo.launch(server_name='your server name', server_port='server port in int')\n`, + '# Read more in the docs: https://gradio.app/docs/' ] } ] @@ -7540,900 +7540,900 @@ suite('NotebookDiff Diff Service', () => { , [ { - "cell_type": "markdown", - "source": [ - "# Video generation with ZeroScope and OpenVINO\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Install and import required packages](#Install-and-import-required-packages)\n", - "- [Load the model](#Load-the-model)\n", - "- [Convert the model](#Convert-the-model)\n", - " - [Define the conversion function](#Define-the-conversion-function)\n", - " - [UNet](#UNet)\n", - " - [VAE](#VAE)\n", - " - [Text encoder](#Text-encoder)\n", - "- [Build a pipeline](#Build-a-pipeline)\n", - "- [Inference with OpenVINO](#Inference-with-OpenVINO)\n", - " - [Select inference device](#Select-inference-device)\n", - " - [Define a prompt](#Define-a-prompt)\n", - " - [Video generation](#Video-generation)\n", - "- [Interactive demo](#Interactive-demo)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" + 'cell_type': 'markdown', + 'source': [ + '# Video generation with ZeroScope and OpenVINO\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Install and import required packages](#Install-and-import-required-packages)\n', + '- [Load the model](#Load-the-model)\n', + '- [Convert the model](#Convert-the-model)\n', + ' - [Define the conversion function](#Define-the-conversion-function)\n', + ' - [UNet](#UNet)\n', + ' - [VAE](#VAE)\n', + ' - [Text encoder](#Text-encoder)\n', + '- [Build a pipeline](#Build-a-pipeline)\n', + '- [Inference with OpenVINO](#Inference-with-OpenVINO)\n', + ' - [Select inference device](#Select-inference-device)\n', + ' - [Define a prompt](#Define-a-prompt)\n', + ' - [Video generation](#Video-generation)\n', + '- [Interactive demo](#Interactive-demo)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' ] }, { - "cell_type": "markdown", - "source": [ - "The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n", - "\n", - "The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n", - "\n", - "The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n", - "\n", - "Both versions of the ZeroScope model are available on Hugging Face:\n", - " - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n", - " - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n", - "\n", - "We will use the first one." + 'cell_type': 'markdown', + 'source': [ + 'The ZeroScope model is a free and open-source text-to-video model that can generate realistic and engaging videos from text descriptions. It is based on the [Modelscope](https://modelscope.cn/models/damo/text-to-video-synthesis/summary) model, but it has been improved to produce higher-quality videos with a 16:9 aspect ratio and no Shutterstock watermark. The ZeroScope model is available in two versions: ZeroScope_v2 576w, which is optimized for rapid content creation at a resolution of 576x320 pixels, and ZeroScope_v2 XL, which upscales videos to a high-definition resolution of 1024x576.\n', + '\n', + 'The ZeroScope model is trained on a dataset of over 9,000 videos and 29,000 tagged frames. It uses a diffusion model to generate videos, which means that it starts with a random noise image and gradually adds detail to it until it matches the text description. The ZeroScope model is still under development, but it has already been used to create some impressive videos. For example, it has been used to create videos of people dancing, playing sports, and even driving cars.\n', + '\n', + 'The ZeroScope model is a powerful tool that can be used to create various videos, from simple animations to complex scenes. It is still under development, but it has the potential to revolutionize the way we create and consume video content.\n', + '\n', + 'Both versions of the ZeroScope model are available on Hugging Face:\n', + ' - [ZeroScope_v2 576w](https://huggingface.co/cerspense/zeroscope_v2_576w)\n', + ' - [ZeroScope_v2 XL](https://huggingface.co/cerspense/zeroscope_v2_XL)\n', + '\n', + 'We will use the first one.' ] }, { - "cell_type": "markdown", - "source": [ - "
    \n", - " This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n", - "
    " + 'cell_type': 'markdown', + 'source': [ + '
    \n', + ' This tutorial requires at least 24GB of free memory to generate a video with a frame size of 432x240 and 16 frames. Increasing either of these values will require more memory and take more time.\n', + '
    ' ] }, { - "cell_type": "markdown", - "source": [ - "## Install and import required packages\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Install and import required packages\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "To work with text-to-video synthesis model, we will use Hugging Face's [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`." + 'cell_type': 'markdown', + 'source': [ + 'To work with text-to-video synthesis model, we will use Hugging Face\'s [Diffusers](https://github.com/huggingface/diffusers) library. It provides already pretrained model from `cerspense`.' ] }, { - "cell_type": "code", - "source": [ - "%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu \"diffusers>=0.18.0\" \"torch>=2.1\" transformers \"openvino>=2023.1.0\" numpy \"gradio>=4.19\"" + 'cell_type': 'code', + 'source': [ + '%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu "diffusers>=0.18.0" "torch>=2.1" transformers "openvino>=2023.1.0" numpy "gradio>=4.19"' ] }, { - "cell_type": "code", - "source": [ - "import gc\n", - "from typing import Optional, Union, List, Callable\n", - "import base64\n", - "import tempfile\n", - "import warnings\n", - "\n", - "import diffusers\n", - "import transformers\n", - "import numpy as np\n", - "import IPython\n", - "import torch\n", - "import PIL\n", - "import gradio as gr\n", - "\n", - "import openvino as ov" + 'cell_type': 'code', + 'source': [ + 'import gc\n', + 'from typing import Optional, Union, List, Callable\n', + 'import base64\n', + 'import tempfile\n', + 'import warnings\n', + '\n', + 'import diffusers\n', + 'import transformers\n', + 'import numpy as np\n', + 'import IPython\n', + 'import torch\n', + 'import PIL\n', + 'import gradio as gr\n', + '\n', + 'import openvino as ov' ] }, { - "cell_type": "markdown", - "source": [ - "Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption." + 'cell_type': 'markdown', + 'source': [ + `Original 576x320 inference requires a lot of RAM (>100GB), so let's run our example on a smaller frame size, keeping the same aspect ratio. Try reducing values below to reduce the memory consumption.` ] }, { - "cell_type": "code", - "source": [ - "WIDTH = 432 # must be divisible by 8\n", - "HEIGHT = 240 # must be divisible by 8\n", - "NUM_FRAMES = 16" + 'cell_type': 'code', + 'source': [ + 'WIDTH = 432 # must be divisible by 8\n', + 'HEIGHT = 240 # must be divisible by 8\n', + 'NUM_FRAMES = 16' ] }, { - "cell_type": "markdown", - "source": [ - "## Load the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Load the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`." + 'cell_type': 'markdown', + 'source': [ + 'The model is loaded from HuggingFace using `.from_pretrained` method of `diffusers.DiffusionPipeline`.' ] }, { - "cell_type": "code", - "source": [ - "pipe = diffusers.DiffusionPipeline.from_pretrained(\"cerspense/zeroscope_v2_576w\")" + 'cell_type': 'code', + 'source': [ + 'pipe = diffusers.DiffusionPipeline.from_pretrained("cerspense/zeroscope_v2_576w")' ] }, { - "cell_type": "code", - "source": [ - "unet = pipe.unet\n", - "unet.eval()\n", - "vae = pipe.vae\n", - "vae.eval()\n", - "text_encoder = pipe.text_encoder\n", - "text_encoder.eval()\n", - "tokenizer = pipe.tokenizer\n", - "scheduler = pipe.scheduler\n", - "vae_scale_factor = pipe.vae_scale_factor\n", - "unet_in_channels = pipe.unet.config.in_channels\n", - "sample_width = WIDTH // vae_scale_factor\n", - "sample_height = HEIGHT // vae_scale_factor\n", - "del pipe\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet = pipe.unet\n', + 'unet.eval()\n', + 'vae = pipe.vae\n', + 'vae.eval()\n', + 'text_encoder = pipe.text_encoder\n', + 'text_encoder.eval()\n', + 'tokenizer = pipe.tokenizer\n', + 'scheduler = pipe.scheduler\n', + 'vae_scale_factor = pipe.vae_scale_factor\n', + 'unet_in_channels = pipe.unet.config.in_channels\n', + 'sample_width = WIDTH // vae_scale_factor\n', + 'sample_height = HEIGHT // vae_scale_factor\n', + 'del pipe\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "## Convert the model\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Convert the model\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video." + 'cell_type': 'markdown', + 'source': [ + `The architecture for generating videos from text comprises three distinct sub-networks: one for extracting text features, another for translating text features into the video latent space using a diffusion model, and a final one for mapping the video latent space to the visual space. The collective parameters of the entire model amount to approximately 1.7 billion. It's capable of processing English input. The diffusion model is built upon the Unet3D model and achieves video generation by iteratively denoising a starting point of pure Gaussian noise video.` ] }, { - "cell_type": "markdown", - "source": [ - "![](data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCADoA/0DASIAAhEBAxEB/8QAHQABAQACAwEBAQAAAAAAAAAAAAcFBgMECAIJAf/EAFoQAAEDAwICAwsJBAcFBgMHBQIBAwQABQYHERIhCBMYFBUiMUFWWJaX09UWMjhRVXeVtdQXI2FxCTdCUoGRtjM0NnWhJCVidrG0NZTwQ2V0goOS4ZPBw9Hx/8QAGgEBAAMBAQEAAAAAAAAAAAAAAAMEBQECBv/EADwRAQABAgIHBQYDBwQDAAAAAAABAgMEERITITFSkdEUQVFhsQUicYHB0jIz8EJTVJKh4fEjNHKyFTWC/9oADAMBAAIRAxEAPwD1n0ZOjJ0bb/0bdKL7fej5prcblccHsUuZMl4nAefkvuQGScdccJpSMyJVJSVVVVVVWulfcG6OFk1Hj6aB/R3Wy4TZrb8mHNi4tiCRJMVk2wdkCrswHBAVeb8E2xcXfkC7LVY6J30WNG/u/wAe/LmK5M5wXJsh1Zs1+tjKs21nEb7aHZ6Oinc8qS5EVnweJDXk04u4psnDzVFVNw1OzaQ9AvInJzNg0u0DuTlrnhap4Q7JZniiTiPgGM6gAvA8pooo2WxKqbbb1j7vpv0DrLn2P6aTNFdFu/2SOzGIUUccs3H10YANxogUUPj2NNhQVXx77VqOmOiuqdpgP2zIsby+bJiYwxhjD+RXLHkgACvBvIit2thqQ5Ga6vrgKQ4EjdRFGhIjcGl4/hec41kWnEV7FJtwi4tIu9snXVqbHJZDUhoCbuTqOvI6SuEKo6iITnWkSoJAvHQde+aK9BXGLvEsGS6S6EWm6XB5qPEhTrDZ2JEh13i6oG2zbQjI+A+FERVLgLbfZa45uj3QNt0u8wLhpboHFlY7GKbeGHrJZgct0cURSekCobsgiKm5HsibpzrXNVrFkuSav6j4vi+n3fuXk2D2iyJdEfjNhaOtfnKj8hHnANWUVEP9wLjnG2Pgf2kzN90lzJjG8zkwsWC7znc9tmUswFlMAd6jQ24O4iThdWDhLHNAR0gTjAeIgFeKg/svTH+j7t+NRMzn6e9HqNj89kpMW7PWmyBDkNCQiTjbyhwGKEYipIqoikieVK7MPR/oGXHvolv0u0Dk943WWbn1Nksx9wuPbdUD+wfuiPiThQtlLdNt96xeH6TZrMz4dQb7gyWeNc5eR3Ru1Pyozr1qOXGhMNC71TptK871Ehw+pIwRXF3JVXddKzPSXIMO0itUKXhMBqNBwjGMcfg9YwjBzW7syRxSQFVOFeJdyRFDwl2VaDap+J/0esNcUci6V6FXOLmd4csVqlwLHY3o7ssAMjDjRNi2Jvq1QOIkMwFU51Ruyd0WPRp0q9Tbd7mtJhYbqPcsvg6nPaZXCztTM4iXN2wHMt6zocRu1OwTlPq1IKMRKZiai06Z9UI8lP8Adp6LoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKCL9IrGMazTJdFsTzHHrZfrHcs+kBNtlziNyokkQxq9ugjjLiKBoLjbZpxIuxAJJzRFrJ9k7osejTpV6m273NNZP6xdCfvAmf6Vv1VWglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCWdFR12R0XtHn33TcdcwHHzMzJVIiW3MKqqq+NVWqnUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEq6J30WNG/u/x78uYqq1Kuid9FjRv7v8AHvy5iqrQYIcmORf7vYbfbikHZ4TMh13rOESfd41BhOS8+EEJV8iGHJd60Sw6r6mXPBcgyi9aODYblZ7FFu0aBPvDoNSn3IqvPRie7k4m+qJOrUuqJVXxgPircLLYJtozrI7qMcSgX5qHK67jTiGS0CsmCjvvt1YMqionj49/JXczmxXLKMMveNWi5RLfMusB+E1Klwylssq4ChxGyLjauIiKvgo4O/10Gq2bU/IbZartK1YxCLZZlqjxppM4xKm5GLzD5m22gC3CZkG7xtkitiwXJRVCLdUHsXLXHTu1WK05FLk34o17N1mExHxm5vzDebRVcYKI3HJ9t9EE1Vk2xc8A/B8EttWj9HKz27R57TSw2XTWyyZr4SrkFswYY1huboqibSrWElCeAgEEISkbqoDuvCnBXPgmimVYDbsastnyrEItssF5m3MoVuw8oMdWpDLg9zx2m5nBHEDecJF4T3HgFUUkJwg2XJtZsJx2w229pLmzVvbBP25qLaZ8oiFB362QEaO67FYFVEXHnW0BtSRC2XYV60TW/FGrFid1yFm4xHMrt7U9s7fa51zgRRMQVSenMR1ZYaRTTZ19WhJN15Ii7Ye0aV6qY9ZLYzZNTsZavTPdMa4zHsSfcjSYbkhx4Baj93oTLwK4SI4TrgLuqq0vJB1PLeitdsrs2N2e45VhVzLH7S3bGpl9wZLlIjOMkStSoClLEYTyooI6aCamrYKPVbJsHoilcccXxYbGU4248gIjhtgoCRbc1QVVVFFXxIqrt9a+OuSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoJVrJ/WLoT94Ez/St+qq1KtZP6xdCfvAmf6Vv1VWgVq94zN+PcHbVj9oG5SIqoMlx2T3OwyaoioCmgmSnsqKqIKoiKm6pvtW0VNcf5s3FxeZFebpxL5V2mvCn+SIif4Vje1cTetVUWbNWjNWczOyZyjLZGcTG2ZjfE7InvnONDAWLd2aqrkZxGWz4/D4Mp8r80807J+OO/pafK/NPNOyfjjv6WlKytbjf4irlb+xp9mw37uOdX3HyvzTzTsn447+lp8r80807J+OO/paUprcb/ABFXK39h2bDfu451fcfK/NPNOyfjjv6WnyvzTzTsn447+lpSmtxv8RVyt/Ydmw37uOdX3HyvzTzTsn447+lp8r80807J+OO/paUprcb/ABFXK39h2bDfu451fc/qZvk0ZetuOIRSjjzPuC5k+8ieVUA2W0L+SFv9SLW2wJ0S5wmLjAfF+NJbF1pwfEQqm6LWo13tNuWKoCeILlcwFPqEZz6In8kRESrvs3F4jtXZ7tc1xNMztiImMppj9mI36XfHcp47DWqLWst05bYjv74nxmfBtFY+/X2345bHbrcjNGm1EBBseJx1wlQQbAf7RESoiJ9a1kK0fU/m9iTa8xO+rxJ5F2gSyT/IhRf5olfT2LcXbkUzuYl2uaKJqh11z7NnV442D2oG1+aMq+mDqJ/4hbjGKL/I1/nX8+XWe+Zdg9YXv0dfNK1ez2OCOc9WfrrvF6dH18us98y7B6wvfo6fLrPfMuwesL36OvmlOz2OCOc9TXXeL06Pr5dZ75l2D1he/R0+XWe+Zdg9YXv0dfNKdnscEc56muu8Xp0fXy6z3zLsHrC9+jp8us98y7B6wvfo6+aU7PY4I5z1Ndd4vTo+vl1nvmXYPWF79HT5dZ75l2D1he/R180p2exwRznqa67xenR9fLrPfMuwesL36Ony6z3zLsHrC9+jr5pTs9jgjnPU113i9Oj6+XWe+Zdg9YXv0dPl1nvmXYPWF79HXzSnZ7HBHOeprrvF6dGbxnNu/M4rLeLUVquiNq8211yPNSG0VEImnEQeLhVU3QhEk3Rdtl3raKlx+DmWHGPIiukhtVTyitvlqqfy3EV/wSqjWfjLVNquNDdMZ/1mPouYe5VcpnS7pKUpVRYKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5iqrUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEq6J30WNG/u/x78uYqq1Kuid9FjRv7v8e/LmKqtBJNbOk5ppoNcrXac1ltJKubZSeArzaoCsxhLhJ7/vCXHV5EXfwGOtc5fM5pv95X0ltOcQy7EMUuDquBmzDEi13AbnbWWzB4uFrhjvym5j/Eqj/u8d3h4kUuFN1TZ830+n5VLj3TH9R8mwq4tN9zvS7E3b3DlMoqkLbgToslvYSUlRRASTiJOLZVSsPlWix5RcSnjqnmdpGQzFGexb+9whOfjKhMyHCciGYkhCiqDZAyXNCbVCJFDsBqhkrmp8zTxvRfLXbfDGKZZI3NtPcCA8jn7wmymJKQEVtR5MkaqheAg8JFmbLltyueaZBjMvHLjbmbRHjOxnZIRlCeLiuorzJtSXC4N2+DgdaZNFBV8ISRU69607cu+XQ8qYzrJbW002y1OtdvcjNxbojJGbXXmrCyB4ScJdmXmkJPBNDFVFehG0uvsfUV/UBdZs0dZkCjR2E41n739QPWK2yhJASVwiTpEi9fxquyERJyoOrjGt8C/SXhvGB5Vi8IIsuUzPvAQ+pk9ylwyQbGPIdd4m1RV3IBE05tkac67GI6uP5Tlw4dL0uzPHpBwHbmMm7NwUjlGFwQbJCZlOFxOKSqgcPGCAvWi0pAh9WFogyx3kbuWpGWXWNaG7qy7HlhbRC4NzyVXAfVmIBIgIuwK0Ta7fOU151jNIdOdTcey+95NqVkr103ZW22dHLyzPIYXW8Y8Qs22CDKpsnJUfNd14niQR2CvUpSglXRO+ixo393+PflzFVWvL/Rk6MnRtv/AEbdKL7fej5prcblccHsUuZMl4nAefkvuQGScdccJpSMyJVJSVVVVVVWqX2Tuix6NOlXqbbvc0FVpUq7J3RY9GnSr1Nt3uadk7osejTpV6m273NA1k/rF0J+8CZ/pW/VVa8v6sdGTo227PNGIdv6PmmsWPdM4lRJzTOJwACUwmN3p5GnRRrYwR1lpxBLdONsC8YoqUvsndFj0adKvU23e5oNRs/Th0NLWDItB8+uz2BZlYbkcFuNkKgxHuTSqhMSI8hCVvhebNoxBxQNesREEtt63nHVQos8hVFRbzdVRU//ABz9ecbN/RYaF3LWDItWdTYNtuUSdcjes2JWG2hZ7Lb4YKgxwcaYVFecQAAjVFbAjJziA9969D4dAg2q0PWu1w2YkOHc7lHjx2G0BtloJrwiACnIRREREROSIlfP+1/9za/41+tDW9mfhr+X1Zl1HVaNGTEHFFeAiHiFC8iqiKm6fw3T+dQSBqPrHjWV5aOomZ4VPsGFdwLJZsuFzWJ1zKW0qtNMEd0dFs+tVsE4gNC358HjS+1Nck0XZyORm8lzIjjuZaVrkRiCIJLb5MBEJlzwiVHU6wQJRVB5Iqb890q0TEZxUv1xM7YYWV0iYDUm0JcbJMxZW72/a8jgX9ptZdvEYD0pshWK86yfWIDSirZuIqEo7IaKg5S+6+2fHnhauGC5YvcsNq5XtW2Iq94oTrhg0/LHujiVC6sy4GEdcERVTAaxLfR4dvsxq96mZTAyS6SLwd0uaM2ZYsN4O4HYTTDDBvukwgA5x8ZOOEp8SpwoqIOEyzonQsuvFuv94umI3m5s2+NaZ1yyPCIt4mlGjuGTLkVx9zhjyOBxRMzB5syRD6oV3RZIi1ntlH/q5NwLpAWD5aP4g3h+UnHi3sMckXxI0fvezPcjg+02u76PkJi6CIYtECEvCRCtY9rpAS4lqxOfI0xy29Rcp7kZj3a3Ba2GDffJU4EivXFZW4ChGaADiCAkXEqCW2ad0dEzlKGQIAycti5SgpDTwEZaab7n5Gic+q349k24tuHlWsY5ohqhh95s0yxao4rJgWW2x7XFYu+HyJL0Zof94WO61cWhbJ5UTcibMk4RTdRThrkavL/Lv+p+smyQdeManZAdoHHcgbtzj06JAvhssLAuMqGhrJjs8LqvCY9U7srrTYH1ZcBFy32TTjOmdScRgZpDxy72eFdGgkwm7ojCPPRzFCB3hZdcQRJC5ISoaeUUqXY50VMexnPJ+WWwMNYjyHrjLaeZwqIN8J+ZxqaP3RSI3WxJ01FAbac2QRJw0RUKv4ZjvyRxCyYp3Z3X3mt8eB1/V9X1vVNiHHw7rw78O+267b+Na816ER7r1RpzPvMxXe02/wCF1/5pdf8A379dGtNx/o9aBZzEl5Tmuh+n+QXqbdLj3TcbpjMKXKf4JjwBxuuNkZcIAIpuvIRRE5Ild9n/APsaP+Ff/a2hx/8Atf8A6j0qZvpC68WLo44E1qZluOXm548xco8K7P2tttw7Yw9xCMpwDIeJtHeqbVBXi3dRURdtq16NrXpTrlZsOyrSfOrXklv7+r1qxHf3scits1UF5ktnGSVOfC4Irtz2rS+kL0BNGtW8CawbT3T7TjTx2Zco7l0vdtwuF3eMBviM2orjYtk06bgsop8WyB1iKhb7L9af9ETQ/otQ8Wh6W404Fym3hWbhep73Xz5ojb5i7GeyCA7oi8DYgG6IvDvzr7HCfnR8/SXzWJ/Ln5erfNQcvbwPDLtlpwHJxW9jiZiNlwlIeIkBtpC2Xh4jIR325b7+StRcvetuI2i7XXMkxS9MNWabcWn7TAehDbZDLXGDDwOynSlAfhJ1rfVKnBzBOPcd2zLFLXnOLXPEr11qQ7pHJhw2S4XG9+YmBbLsYkiEK7clRFrS2tN9SrxFuELPdVotzYdtMu1w2bXYjt7e77fAsiWJSXu6XRT5vArLaKRrwbqKhqVZ57FCMu927vqFeoGm2K5izFhLNvkmxMyGyA+qEZrzAO8CcW6KiOlw7quyom+/llFw6R2SRswye1NapaXJMsWQu2mFgpW90shubQE2go2aXBF6xxDVRJIhCm26oqIqpv1p0o1Ldxe14fmuo2M3G32R+0vQSteKyID3/YX2nNnScuD4nxi0g+CIcKrvz+bW64RhnyN7/wD/AHl3Z38vkq9f7Hq+p67h/dfOXi24fnct9/Elecqp8nrOmGpa8ZJqrhtii5Lp9f8AFIcdJkG3yYt5x+TPMzlS2mEcBxmawgICO8SioFxKnzh3pb9Yzst4axDLIcy8yYk9mzXXJrRawh2ePcn0EmYysuy3ZIkQuMpxCjjaE4KEYqqom2aj4V+0DGkx3vn3BtcIE/rup63/AHaU1I4OHiH53VcO+/Li32XbZZ1dujJYJ+rTupsZjDUKZcmLvLdnYZFnXhJDTYAIR7i6W7DKo0CqPVGaLx8Dje6cPaoqic6XI0ZjKWQznXoLTg+U3nDsYuFzv2NWuRMm25xI6Lbn2y4RZlIT4IilsRogGqE2CkJKhBx1K0zH7ha4k+VAegvSGAdcjPKCuMkQoqgStkQKqKuy8JEnLkq+OptdNFJ9yseQW5c2cGZl1tlRL7IOGRtyJLg7MyGmld/co0i8CAhLxNoAkW4odUm1sTo1tixrnKYky2mQB95hhWW3HEREIhbIzUBVd1QVIlTxbr467TpZ7XJyy2IBgGu95zHN3bFP150dtklvIZ1qHEDtxrfHGWJLjYihrdEXrTAENF7mVPC+aqVu2OdIbGMgvCQXcWya0210ro3HvVxjxwhSHLc4YSgHgeJ4eHqzJCNsQIUVRJfFXHhmm2rmDTX4Fp1KxB7Gn7zMuhQ5OHyinI3Jkm+40kobkLfEiuKiH1G3i3Fa5w0NhO2u0Wa5X4pES3O3wnxGKgFIbuXXIQIqkvAoI+vPYt+HxJvXmmK4h6nRfY66xBsbt6maa5tEN5+KxZ4bsSKr177pVeoKMQyFaDiQSJRkOMm2KcTggioq9ST0g7S9Z4rlhwrKJ9+krObfsYRo/ddrKGojJKVxPi1wtkbaKjTjhOIYq0jm6VxFo/qLMx+Nbrvqvb359gkwpONPM4yLUaGUZCESlNLIJySbgGQOKDzAqmygDa7qvXj6D5NbkiX20ajRGstecuK3q6yLF10eY1ONsnwYjdeKx+DqWkaUnHUFA8NHd13e+e6+bL0joY4zjtyv2K3ia/JsVuvGRzbRHaWDYhlgnATwuvI8oKXGuzIvEIgpHwpsq71h+okfNL5kFpt2NXmPFx6a5bnbnJ7nGLJkguxtsoLxOrwookpE2IqhJsqqhIkmufRBsU6dj81ZGHznbdaLbZ58u+4TFu00ghpsDkF14+GGZIpISGD4fNVBFUVSsmF4imIM3dkZ/dSXS8S7qn7nq+q64uLq/Gu/Dttvy3+pK7Tp5+85Vo9zKO/8YYZ/zd/8tmVUai2Y4biGe3XEsZzrFLPkdnkXlw3rfdoLUyM4QW+YQqTTokKqhIioqpyVEWsp2Tuix6NOlXqbbvc1S9ofjp+H1lawf4avj9IVWlSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1QW1VpUq7J3RY9GnSr1Nt3uadk7osejTpV6m273NBVaVBcF0z030y6Tc236b6fY1ikWZgjb0lix2liA284lwJEMxZAUIkTluvPar1QKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5iqrUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEo6KKGvRV0dRshE10+x7hUk3RF72sbbpum/+dNLNZ7nlrMCDk+IXiK4+/KtyZAFvCLaZ06O86240w0chyU3/ALA1QnQ6ottgdc3Hd0URU+iro6AmQKWn2PIhDtuP/drHNN90/wA0ruYbogziMiMMjUvM7/bIZPPx7VdXoSx25TqmrklDZjNvcSq65sCuKyHF4DYcIcIYHHelXgWUBmrdos9ykz8ERxy426HcrRPlmy26TZuI3Emu9SqKJL1UhWXl4VRG1JFSmS6+3JvHbtcbTh16x+VZEYlvFfLa1LCSy3Laamx2WocpXVfAXBRC2UOJwOBHuEwTtNdHZxq3zrYutWoDjEm0OWKMJDZ9oEIiBRbZFICCqggcKG4hmqEvGRqgKPcyPQOFkseRFk6k5lECW884+sM4DRmDr0d5xtD7lUgRXIyLxAouD1rnCY7BwBkGtYUS0zH7hpxl1vvkdxtuNjj7cIrhP61T6lWSbklG2PqnV3N8OrQCV3q9q/j2sRDZGpEPTLMJmRnIKM5ijSQBuTBgAOGpuOShhIAg42XGklQXjERUjXhr+s6QPpaZjM7VHL59+kONuRskkBbUuEFG+PqwZAIYxeEUdeTw2DUkcLjUuW3y5o/MWyNRourGYxciGQch3KmmbUtyf4wACA2zhFD4FBpodhjj/sxVNi3JQ3HGsgiZPZmLxEjyYyOKbbseU2gPR3mzUHGnBRVRCExIV2VRXbcVVFRV17MtTCxS6BbIGBZRkysthIub1mbiqFrjkqojzySH2ic5Aa9XHF53YPmeEKFsGM2BnGLKxZ2p8qcTam49Ml9X18p4zU3HnOrEA4iMiJUARFN9hFERETXcy00m5Vd27tadSspxRHGRj3GPZgtxNXJoVVRF5ZcV8w2QjFCZJotjXdVVAUQx9y1qiW7JlsYYDlUu3NLCKRf2Bhd7mGZfJh4lKSLxCp7gqA0RiqcRCgKJrR6nGQ6Lt30b6xG1Gyqzxb03bGW40Bu29XbwhHxAkfrojheH4j61XOXzODx1RQFQAQIyNRREUi23L+K7bJ/klBLOid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtAqa49/u9wT6rzdf/AHz9UqtOuuJ3qLcZNxxdyC43NcV5+HMcNoRdVNiMHAElHi23UVFee6oqbrWJ7XsXKqrd+3TNUU5xMRvynKc8u/LR3Rt2tL2feotzVRXOWeX9P8uKlcPenUH7Ex78Zf8A0tO9OoP2Jj34y/8ApaydOrgr/kr+1p6y3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5q72m3/Cyr9d0uip/8+/WLSwZ9LXqHWrFbgLkUhqY7KME+sWyZbRV+rctv4L4q2+z2mHY7ZHtMASRiMHAKmW5EvjUiXykqqqqvlVVq77MsXa8XGImmYpimqNsTGczNM7Inbs0ds5d8ZZ7cqWPv25s6umc5mYnZt3RPV3a0fU//AHjEF8iX4/y6ZW8ViMoxyPk9qW3uyDjPNuBIiyW0RSYeBdxNEXkqeNFRfGKqnlr6rD1xbuRVVuYd6ia6JphqdK4Vx7UxlerS24xLQeXXd9JEfj/j1fcznD/LjL+dO8Wpv2DjH47I/R1r623xRzhnauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4c1dfhPJzUrh7xam/YOMfjsj9HTvFqb9g4x+OyP0dNZb4o5wauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4NXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5OalcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLfFHODV1+E8nNSuHvFqb9g4x+OyP0dO8Wpv2DjH47I/R01lvijnBq6/CeTruf8Y4an/wB7vr/h3umVUa03GMNujF2byLKZEQ5cZs24cWGpEzH49kM1MkQnDVE4d+EURN0ROarXX1b1RkaTY8eUFpvlGU2+M249OcsbluFYbYInhODMlx1JF35I3xryXdE5b52NuU3K40ZzyjL+sz9V7DUVUUzpd8t6pWjWDWDE7hLg4/lL8fDMsuDDstrFb7eLb327mBS3f6qLJeEm1EFLiEyRE+dwqionXzXXvSbA7fa7jes3s5t3mTbY8IWLjHInQnPI1HfFFcTdlV4i403RRA1TfbaqawoNKwGWZ/gmBBAcznNbDjo3SSMKCV2uTMRJUgvE011pDxmvkEd1/hXRvOr2k+OXGTZ8h1PxK1z4bD8mRFmXuMw8yyyKG84YGaEIgJCREqbCioq7ItBq4/Snc+78PzEqqtR8tQ9E2NZ4l8l5/Ai3e741Ag2l6RdYgW66x5Ul5xkYhKXE++pMmuwKqKKjsi89qDF1BwKdk9wwmFm9gkZFaWEkz7Q1cmTmxGV2VHHWELrGxXiTwiRE5p9dBn6VOLd0h9Gbm1fbnH1Fx1LDjzMV6ZkJXeJ3pTr3HWxBJSOKHEJsmJIu2xKic13ROaza56e3a3t3mVeItntjrclwZt0uEOO3szLKKvJXuPYnBVRNB4FRURSQl4aCg0rUn9XdJ4tgtGVSdT8SZsl/dRi03Jy9Rhi3B1UJUCO6p8DpbAS7Aqr4K/UtbPElxZ8VmdBktSY0lsXWXmjQwcAk3EhJOSoqKioqclRaDmpSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlBKuid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5islrVqdetL8di3PHMWayO5S5SMhb1W4qZNoKqbghboE6QfCvCi7MKKcSKRCnjxvRO+ixo393+PflzFbhmunmMagMxGciG6gUFxXGHrZeZlsfHi24gV2I604TZbDxNkSgXCnEK7JQd/EshYy7F7TlMaK/Gau0JmaDL4qLjaOAhcJIvNFTfZUVEWtUzDONSbNmcfFsU00tt+YmwFnMTHci7j6vqnQGQDzaxyUfAcDqlBXOM1UTRgU62tsxbGLLhmPwsXx2O7Htlub6mKy5JdfVptF5AhukRcKeJEVdhRERNkRErA3zSTEMhzWFqBcJWUt3iA2LTHcWW3aHE4BJC4ShsSQjOISiKkhNqh8I8XFslBjblqPmEW+PyIeBwXsRt0oINyuTt7Vu4NvEoIRMw0YJt1oFcRCIpDZ+A5wAewcfLCzfUa65MCWrTa3ScPOa7A76/KHguDZtOk268UIo/V9QhNnsoyCcXwf3SIqqPeuOkuF3XMGs4mJfUuDbjbyx2cjuLNuedbREBx23tvpEeNNh8Nxoi3AF33Adv4mkOC/LBc3KFdHJ/Xd0jGcvc47aEjffugbcTywxe33LrUZQ+JVLi3VVoNYwTUbIbxl1w0/tEqNlJY/dZzOSXSbMSK/amiMihsC3HidS+8oKP7tSaIWkEzUlMePI4Jius9tfuK57qczdmpNoajRO54UdtY09HpKuSU2YDfdo4ooJcQ7tL4Kbqp5ez6SYTYckj5da2Lw3dI7clrrTv8APcB4H3SdNHmjeVt/YzNQ6wS6vfYOFOVbXOalPQpDMGSEeS40YsvG31gtmqLwko7pxIi7Ltum+3jTx0GF0+yCXlOE2XILgyjUubDbOSA7cKPImzm2yry4kXb+G1bDWLxbHoWJ45bMZtyqse2RW4rZEiIRoAonEu3lVd1X+KrWUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKCVayf1i6E/eBM/0rfqqtSrWT+sXQn7wJn+lb9VVoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFanqxidxzvTfIsPtD0Zmbd4DkVhySRC0Jl4lJRQlRP5ItbZSg873jo75S/rTLz2M3AuNsm3aFfwcl5jfIfckmNHbaFlLXGJIUhSVkVSS4vECGqK06goi/T+gmocayyoECVjch6dklmywxdkusBGkR5oPPwGVBglSKLYJ1KqnEhke4ohbj6GrikjJOO4MN1tp9RVGzdbVwBLbkqihCpJ/BFT+aUEL6Q+g2SapZNachsIRZ7LdomWKdb5eXXnH2xZkG2av8drJClInVqJRneETRU2cbVOf8sXR2udov3fJzvA6ymexsp4v3pOHGZs4QhResEiV5DBFTiMthRFVxS5VvsfIswOwXHIZF8sYQmHdocgbM8SyWx3FVRrurfc3NhDwue2+3hJtk4GVXCAlvs2UQlO+zGwcFqCygtObkvGgcbi/wCzFEU1VfKm2+6JQQFzo06pw4T1ltqYVIh3rEYuI3CTImyW3reAzJT7kiMAxiR5UF5rhbImkIx3Ux4E4/pvok5I/fMmizbmyFvuSX5yBe/lbe5EgDuQuIoJaCcGBGUOuJCeBXFcEfmNkSklxn51doFzvrD9hBuNaY7Kx0J9CdlPvOKDSLw7i2JKniXctlRV4fFXGWSZex3ygTJ2PsuWtxoplzOO6MaO0bKuLu0ru5kiog79YnI0XbltQSQNE9Znr+1qNItGBR79a5VnkQ7HGvUvvfL7jjy4xdfKWEhtqoSQcAkYPgUEDYkFHF67fRjzs4+LNypuMCtmlsyZTTLjyNbDkA3JRaFWvEjQqKIu3h7J4vCqwys/u4WyzMR7S0l6nnEGW06hC1FF1xB3VFVCQiTiIQVd0QSUvm7LvdB5I1WwnLdMTG9WuwDk0y+HltuG1xLPdZzIRrrIB8HuOHCfBt4VAAIH1ZaNDL9+KAqr6YwC1TbFgmN2S5No3Lt1ohxJAISEguNsgJJunJeaLzrP0oFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBUAtPT06J98UEtWqqyCPkIpYrkhL/gsdFq/1+KesOmTFhuS5tYIb7LLRo7KbibgYIvjcHby7eNPFy3+uvFVyKaopnvS2qIrzzfqyPSf0MJpH0zlOrJN+JbbMRNtt+f7rl/jXXl9K3QOCglKzsgEh4kLvTOVNvr3RmvzJxjJX7vDALfd5T4x1QJA9UO7gKiEi7bfxTyb8lTmlby9Ig3Fhph9t3q1bIUJxODfZF2Th2TZE2qOq5VS96ql7yd6YnRyZRCc1DLYvFtZp6/+jFcLXTQ6NLy7BqWKLvw7HaJ4899tubCV+e9utse42xqSsVxVFCBD2Qd9uSePy10XcOOQSvuQkUvGgntsi+LxpXnXVGqpfpH2tuj2qbpqEKptvytc1eX/APRr4Tpd9HgjUB1C3UfH/wB0ztk/x6mvzU+S9yaf4Y7LzCN/N4D4h2/j9X+VdOfFvcM90kSAEt+IwDkqfz8tNdUaql+ojXSj0JfTiaztCTbf/wCGzPdV8F0qtAwRVPPwFEXZeK3S0/8A8VfmTAlZIor3FJMt/GpIm3+PKvgmsk7sKNIktjxoi8PB5f5qnlrutk1VL9Nu1f0f1321CaXblyt0v3VczHSi0JkpuxnYl5P/AIdL91X5oRMGkvL3ZcpchOXgg0fV7/zJOf8AlWSkQQtzCjHcICTmiC8fF/nvuvLbx7010uaul+ko9JPRUy4QzTiXx8rdLXb+f7rlWzYZqVhWoRzgw+8rPW3dV3T/ANleaQOs4uDZXAFC34C+bvttz25V+UjmcyokYo6qSvt7Kil4XLf/ANa9h/0fGQO32LnHXSBdNpbYRbBw8JF3Vun1L82vVNdUzGe55qppiM4WrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaVMiSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaUEq7J3RY9GnSr1Nt3uadk7osejTpV6m273NVWlBKuyd0WPRp0q9Tbd7mnZO6LHo06Veptu9zVVpQSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaUHUtNptVgtUKxWK2RLdbbdHbiQ4cRkWWIzDYoLbTbYoggAiiCgoiIiIiJXbpSgUpSglWsn9YuhP3gTP9K36qrUq1k/rF0J+8CZ/pW/VVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBXBPiJPgyIJPOspIaJpXGlRDBCRU3FVRURU35cq56UGJexq3PQbZbd3QjWp1l1loFREJWk2BC5c0Rdl5bcxSuBzEYT1/HJXZ845zZj1JKYcLLSCqEyKcPzC33LfclVBXfwR2ztKDTZrOIz8pu2FneJbd+usNi9ONNiqGyw0aNNOtmoKCbOB81VJd91VNlr7k6cRZAxjTJby3IYlHOckf9mcKTIJERHHBcZJvcEREDhEUHbkiLzrWR+lO5934fmJVVaDXZ+AYrdbhBu9ztESVcITgPLLcis9bIMQURVwkBN9t0LZNkRRTZNk2rYqUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFcE2bEtsR6fPktx40cFcddcLhEBTmqqq0mzYltiPT58luPGjgrjrrhcIgKc1VVWpZdrtLziWEuW05Hssc0chQnE4SkEnzX3h/wCoAvi+cXhbIMF69FrZG2ZQ3bsW483ze7nNz17rpSyoVlaXeFGEyZeeLySHFTZRVPGAf2eRL4WyDseG5lJSS3i+UPoU0kVIU1UQRnCib8JbcheRPGniJE4h8qDha68+BGuUYoksFICVCRRJRICRdxISTmJIuyoqc0VKpRNdFWnE7e/z/Xd4c86VNyqmrTz2q3StGw3MpKSmsWyl9CmkipBmqiCM4UTfhLyC8ieNPESJxD5UHea0LV2m7TpU/wCGhRXFyM4KUpUj2UpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFflga3W5icQYjqtOookrgcPJeW3Ov1Pr84QefZbRt+ISOKiqipsibfXvVHGzlo5eazh4zzef2JcmwXSRjc4I0V+E4Iw5UdVQlaVeSKm3PbxKm/k3qmq4l1iEYNCb7OyGyvJd9vHt5UXmqc+dYzLdOrHkTTt0ZmdVMZEndwXZxPq5bfwSufErQ53EiXO+Ic2MAgygRl6x0eHfYiQttk3Xmqf+teaLsVxlO9NXR3viJFOA2IE+/wBSZeC2Qc2yXn5FX+W1ZmI3LbhgicXAm6o2Xi3T6/8ApXbm2ia8rbTLgoKKJoaeF4XPbbmm/wDnXK/EvdsBmPOWOCGm6iCbcv4/Uq/Xzrszmif2KEY4pPSm0aRUXYQTbnWsy91ccZjkHCi7LvzXby/47fzrISbi6GzEx4AH+ym/+WyVE3+kKruo0bB8dxCVcm35SQx7k3KQ66pbbA2KLxc/J41r3bt1XM9FyZyVcmZFtQ223TAC3JCRF5/41in7XBfLrnQ64n0RXFd8Zfwr1BjHRB1Fya0sTb7Pg4+TgoaR3xV10d/7wguyL/Deunm3QgzyDZplwsGWxLjIjMOOjFjxFB99RFVRsEM+HiJUREVSREVaRRVPc5px4oBAHuxeCHG4mGgRslEd9/4In8K/t7ZYgti660fGaoOwJxKP1b/VXjWLrTrzp1k861XkZsF6NKUZ9qnQ1bQDRdlAhVEUa9T47mLWd2aBcnmShFKji/1Zbqgrvso7+PbfxfwqS5YqtRFU7nYnPa+bvY48w1cYFsfBTiUvGqrXrb+jmhJC/aG2hKqKtq238n+9/wCfj8deUbvb2/BZJ0/Gu5NmqL//AMr1t/R4susrqALpCX/wrYkXdV/3vx1233PFe6XselKVOhKUpQKUpQKUpQKUpQKUpQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlBKh+lO5934fmJVVah+bZZF096RDOU3/AB3MJVpmYWlvbl2PErpem0kDOI1bNYEd7qy4VRfD23TxVm+0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1eTtZ/wCk+k6AdIRzFcmwW7X3Ty9W+LcoCu45cbBe7YioTTwqzcWmu7AVxkzEkFsf3ij1iqCige3smzbvNOGy2e1FdboraPONdcjLUdtVVBJ1xULh4lRdkESJdlXbZN6w3y6z3zLsHrC9+jrUcCzK1ailes5ssa4sQbxKivxm7jCciSQaW3RCQTacRCHmRL4tl33RVRUVcbedQspx7OrXZbvhUNnG71cEtEG5jeUKc5LVk3UJYSNcKMbNmnH16uIqbq0g+EmvRh7NNFOlTnMxHj3x5Szq71yqqdGcss/DuUD5dZ75l2D1he/R0+XWe+Zdg9YXv0dRTF9cs8uNptebZPppZbbht2uCW9ufb8lemzo5HJWOy4/FOEyAtq5woSg8ajxIuxJuqUO/Zn3kzTFsQ729d8pe7v8AtHXcPc/c7SOfN4V4+Lfbxpt4+deos4eYz0P+3V5m5ejZpenRtHy6z3zLsHrC9+jp8us98y7B6wvfo6j2adIVrCn5SysPkS4kDLG8clusStzajrBGY7N4ODckbBS3bRd1QFVF/s1s0zVvHbRPycb6vcltxtm3upMa45JzFliStttMNArhmqiIiAIZGpIgpvsitVh+GOc9XdZe4vTo3v5dZ75l2D1he/R0+XWe+Zdg9YXv0dTCNr9h90yrEcbsMW7zwysrg0kjvROaWA9FQVNqSBsIsc9y2UXlbIeSqnhJWyZVqdheF3SJZ8huchmTMQS/cW+TJbjNkXCLslxlsgitKW4o48QAqoqIvJdmpw/DHOermsvcU8o6Nr+XWe+Zdg9YXv0dPl1nvmXYPWF79HWkRNZdOZ2TzMQjX50rjC7oQyK3yRiumwm77TMom0YfdbT57bRkYbLxCmy7Y6w9IfSDJLbPvFryt1INutrV4ckS7XMiNvQneTciOTzQJJbJdhQmeNFJUH5yolNTh+GOc9TWXuKeUdFI+XWe+Zdg9YXv0dPl1nvmXYPWF79HUds/SPx6+Tru1DtUhqNa7ottFJUeazOkKlrSeqJCWL1zbqD4PVOICqicSKqqIF2cT6ROIX3Dxze9oVkgpZLbd3Y7seaUwClk4AMjHKMBuqRt8DSNcZuqvIE3DjarDT+zHOerusv+M8o6Kz8us98y7B6wvfo6fLrPfMuwesL36OptM6QmlMGzwr07ebq4FwKS2xEj49cX56OR1FH2zhtsFIbNvjFSA2xIR3JU4UVU5brr9pTZwtT8jIpUmPeIke4MSbfaJs1hmK+SCy/JdYaMIjZqvI31bFeE+fgFs1WH4Y5z1c1l7xnlHRRUz7Nml45OD2o20+cMW+mbqp/4RcjAKr/M0/nW4WG+2/I7Y1dbaZq04pAQODwuNOCqibZj/ZISRUVPrSpXieXJlE3I4aQO5ksF3K18fW8fX7MNO9ZtsnD/ALXbbn83ffnsm2aYcnstbTkIX1OFPIm8CIS/5kSr/NVqHE2LUW5rojLL4/VNYu1zXo1Tnm3ilKVmLpXBNmxLbEenz5LceNHBXHXXC4RAU5qqqtJs2JbYj0+fJbjxo4K4664XCICnNVVall2u0vOJgS5bTkeyxzRyFCcThKQSfNfeT/qAL4vnL4WyDBevRa2RtmUN27FuPMu12l5xLCXLacj2WOaOQoTicJSCT5r7w/8AUAXxfOLwtkHnpSqUROec7ZlQmZmc53lKUro68+BGuUYoksFICVCRRJRICRdxISTmJIuyoqc0VK2LDcykpJaxbKX0KaSKkKaqIIzhRN+EtuQvInjHxEicQ+VBwtdefAjXKMUSWCkBKhIokokBIu4kJJzEkXZUVOaKlciaqKtOjf6/r+jtFc250qVbpWjYbmUlJLWLZS+hTSRUhTVRBGcKJvwrtyF5E8Y+IkTiHyoO81oWrtN2nSp/w0aK4uRnBSlKke2lZXqZDwfLbdaMsiMW+w3aK+UW+OS0RsZjIE65GeBRTq92ANwD4lQurcRUFUHj12z9IrDustUDNWJWN3K+ONlEjHElyW4zEhxRhLPkgx3PAefREUWXnBVSJBFTKs/rNp4OqOCuYkUS2SeO5W2bwXFvjZ2jTGXy5cJeFwtkg8vGqc08dS3Ufo33/KNVJuU2tuJMs+RSbbIuSy8zvttGEsTgRUS2QHAjXHiFsVFX3GlAvH1gogIFZsmrmCZFmEzBrTcZ7t0hG82RuWiY1CecaVEebYmG0kaQbarsYNOGQKhISIoltuNSDCdNs+sOq10yWQ3arTYH3ZTpBa8iuL7V060lIFO0vh3Lb3RJVM3o7hE8e5FwoailfoFKUoFda43GFaIEi6XKSEeLFbJ550/EACm6rXZrT9V/+DSD+y5dLS2SfWJXCOhIv8FRVT/GpLNEXLlNE98xDxcq0KJq8IY8tQsslL11qweIMYubffK7FGfVPIqttsOoP8lLdPKiLyr+fLrPfMuwesL36Ovmla/Z7HB/WerO113i9Oj6+XWe+Zdg9YXv0dPl1nvmXYPWF79HXzSnZ7HBHOeprrvF6dH18us98y7B6wvfo6fLrPfMuwesL36OvmlOz2OCOc9TXXeL06Pr5dZ75l2D1he/R0+XWe+Zdg9YXv0dfNKdnscEc56muu8Xp0fXy6z3zLsHrC9+jp8us98y7B6wvfo6+aU7PY4I5z1Ndd4vTo+vl1nvmXYPWF79HT5dZ75l2D1he/R180p2exwRznqa67xenR9fLrPfMuwesL36Ony6z3zLsHrC9+jr5pTs9jgjnPU113i9OjJ2LPZEq5s2bJbINqky1UYjrMrumM+aIpK2hqAEJ8KKqIQIioi7KqptW4VJck8Fm1uDyIL7aOFfKm89gV/zElT+SrVaqhjLNFuYmiMs1vDXKq4mKu5Kuid9FjRv7v8AHvy5iqrUq6J30WNG/u/x78uYqq1TWSlKUCvzalCc0HSbeURRPmKvDxJ9e3j5/V9VfpLX5qXBIxl3WDxNOCKqIkXLdP4VRxkTOj81nDzlm6+Rx+rZCQCohMx1420Hxpty3/j461mCTrrgvvojYgibEhbeTZE/9ay825q62UaUaIsgNhe8a8X1LWs2uNNauT8YpEVGhPdONtURPrTdEqvRRknmrY3RrJVeALeDrZup/syAuSJ/NPF/jX9ekKLqbg7JdNOSCm6Kn/iVa/lujRS4BigAEabO9WRIK/Wv+O1dS63KbZ5bkyK0j0Ydh3PchTbyKmy1NlmhmYfU6NAjtnJkIAPEm3ETfNf8fHtW26Taf6MdE3Bsg6XeWQg763Fs4mPMSVV7jkHxbdSKDxAThCqKXkASXdEVd5qWbddxhIFl1xV38BPB4V8SpvzretQbu1r1oJB07fyW52dzFyQ32oaqrEthF8DrB+aSpy2RV8abpXucRThaZrrzy73mqM4c2A/0vmHxbFbIGpOBX2Xd0bXvhOtjbQMqakqp1bRFvsg7JzXmqKvLetThf0r17sGp9xkPlGy7A58lTiRnLeltuNvaVd0BFRSBxRTkvERcW2+4+KvBWra5Bgl5Wzx7eg2/ZFamE2hE+KpvzVOQr/BK0tu62+5W9tpxpW5QLzJPESfXWrY1WIo1lG6XimKaozh+9OXz9BdaLBZ8gBqwz7hchYlg24DJyDbVEVQdRN99kX+OypXlfXrQeRpvlEe74kKfJ+7ATkcN/wDYHvuTX8UTxp/Bf4V+cOD6g5rp/cRueI5LMgPInCqsuqO47+JfIqV6QhdPvWqfjCYjnTtoyW1HwqKTYTYSWVTxG261wrxeP5yLui7LVK57Pu62a6ZjLwS6WjTot37qmSXCCbHJpxteAwLku6eXf6lr2B/R6vEbuoDKsmIgNpITL+1uszl/NNv+qV5bsFwsuf49GyKwGrgvD4S+JQLygW3iVF5V656BsV+OzmqyWuqdLvchBui8OyytuaeXZf8A651FbmYr0ZeK9tOb1hSlKtIClKUClKUClKUClKUClKUClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQK6ki7WuHOiWuXcorE24dZ3JGceEXZHVjxH1YKu58KLuuyLsnNa7dRvWm8piepmmWZT7Fks+021y8NTHbHjs+8OR1diiLam1CZdcFCJFTiUdt/LQWSurbrpbbvHWXabjGmsC64yrsd4XARxs1BwNxVU4hMSFU8aKiovNK8w6g3/OMr1txS74ZbNQ7Zb40qzSlJYGSNRbhbJBoMjrWOJiBENriVHG5TL8lULi4WRbRxJdLw/WTHsetWOY3ddRcYtke65G7HKNYMhvEly7ndnTjqXc0+KqMmwTZA5MJyASqaubLxKoe+CIRFSJURETdVXxIlde3XK3XiBHutonxp0KW2L0eTGdF1p0FTdCAxVUJFTxKi7V5oZxTVCblj14us/P3HZeX3CzyGUuE5mCtmctBEhDGBxWGw7qQeB8dyE14Qc2XZdC03sOpWKXDRWFbYupZMxbVa7dLskoL/AA2IXBxpLfckbuwHBHwxKPcGW3CFAKM+OzYkHt2lKUClKUClKUClKUClKUClKUCpqPRz0cc1cuGulzwyNds1nJHBu53IilLABloW2xiNuKoRuQqSk2KEpGaqq77VSq1+2ag4NestvGBWrLbVKyTH0aK6WhuUCzIgutg42Ztb8aAQOAqFtwrvtvuipQac1/xhmf8Azdj8th1N8twDOMs1Fs12fs+IRYNjnty4eTxpDwXxqKhCTtvFlWFFG3VHhccSSgkK82d0RapDX/GGZ/8AN2Py2HXereiM6KfhHpDJmcqqvjPqgeL6W6xnidm0qyu34hbsZtd0Ce/drbfpUudMaZmrKbaSKcJkGeMkBCLrnOFEJEEt0VKBqViuW3G641meCsWaZesYkSCCBd5bsSNLZkNdU4KyGmniaIfBNF6lxF4eHYeLiHfKUiiIjI0pzzR+1aS5dInW3I8nfsqXGTlzuS3eFGecejMNFbjhjGYcNoSf2Tq1UzBvi3NeEeQrqU7oy5P3my6xxshiyoLlztE/FY6XCXbXWY0HiIIT8qLs8yiKRNg+zuQggKokoqh+jawWdZjatPcOvGcXxqW7b7HDcmyQiNda8TYJuSAG6cRbeJPLXJopy2uxXV3JZgOjmY4hdcYvvcFladYulzlXmOeTXS5uNtSmG2wMJk4XHpjo9Q2i8SRwVC5InD4e0X7FdR7bqNMzDARxuQxkFvhW64FeJUhsrekZx4keZaabJJPEL5IrauMbKCLxrvsmPjdIa2nekstx02zW2E1dItrmyJTEJWYRS+FIjrhNyTUgeUxREaQzDf8Aeg0nOuzF6QWIOXiVDuVlv9qtDZT24V/mx2kgXJyEJFKBhAdJ9FBG3dutabQ0aNW1NERV8xoxGUO+81uFo1qB31iWOfKx5MYsN3u99ts1qS+dwlvzRkILDzJNcDINrLd3cF1xTQQ8AOdf2foPksvHrLbGLlamJFkxCBaGVQ3eqK4RJUeQ3uiCi9QRR+El5FsXza5YHSNfLIrwF+wS+2a1RbNa5tthzIjSXG4SZst9hkG1B82dnFBrhEyAgU163q08Wft2sTt+vWM2uBYJdoen3qZZr1bru0HdkB1mGchB4mHTZVVRGiQgNwFA+S7+JEUSZ1Netmk+pV0y1czy5zGYT8m/99nIdulyJAR2e8xQerR1xltXj60uPiUG04V8W6eFiLZoTqTCxxhSk403e7RbMbZgMpOkORZEm0yHj4XXOpE223QME4kA1AlJeE+BOL0NSvWrhzTlG8O0jzGHmreoWUO2Nq4XB+7TbhDgvuPMxXZLMRhltl02gJ5BbieG4Qtqqmuw7ck0XJOjlqzeMatOIjkEJ6BBxeHam0DLLrbo8OazxK6Sw4oI1cG3v3Y7yFTq0Hk2aKoL6epSbcTsNOWqYLilyxmdlUq4PRjG+XsrlHRkiJQaWMw1se4psXE0Xi3TZU5+RN20x/3nL/8Anwfl0KupWq2bRDRfUq/ZVfdRtIcKyq5MXZqI1MvdgiTn22BgRSFoXHmyJAQjMkFF23Ml8q1HiYys1frvh7sTndj9dzetY9SW9HtMr/qfJxm6X+JjcdJ02Da0bWSsQTHr3QRwhFeqaVx1UUk3RtU33rW9G+lNoLrzjj2S6a6jWye3DYWRPhvOdzzYIJ41eYPYxFF3Tj2UFVF4SKtO1j6EuiGcaZX/AA/TrR7SnEL/AHmOMKPffkNBdO3tmYi860IABdajKudWSGKiagW6bVjuj1/R69HXo725xyx2KTfMjlRijScgurvHKQS+cjAjsEdN/EoJx7bIRltWLVnlOjvaM55bHV1+1ov1tivXSBZ3pUSJZLpebXZziiqzXYgtEL8gjfaQBFXEJGtlVURV3E0Ea/pa8WuJOhQLhhuSCHFAjXme03Fci2SXLEFYYk8MhXCIlcbRVYB4A6wVMhTdU7eqWktwvl2bsuQZAUZt2yXazQJ3cqGkoZjbY7l4QoLzaNbqHicRdxUdlRNNufRegXjN4Ob3J7CJc4zt8i6y5mDxpk8n4oAKdwSZDrncTZo2O4ED6j4SgYEvEmdRlOc1fi7/AI/rczs4mff397bImtiXa1zrpj2lubXgYt2es8ZuIxCQpz7LrjbxATkkQabBWiVTkEyhbigcREg1qt66Rs8X4Ey0YddI1ml4tdr3KlS4sZ123yobzbRMuNDMBTUCUwJG1USJQ4XOHiJMjlfR8TI8MtuJnerJNbt+Qzb4ca/WDvlbJiSXXz6mRD69tHOr7o3AlPkbYlw/2a6AdGd2FilvxC0ZjDiQ4dlvViNEsiIKx5zwuh1QNvADStKAJsiKJCioghy29xokaLbJGt9gh5YuNP49f1hNTY9pkZAkdlLazcXgE24hbu9fxkhtpxi0TSE4Iq4hbonxp/rnZtQb3EtEXDsntDV0t7tytU65MRhj3Blkwbe6vqn3DAgJwUVHQDi8YcQ866ErRG7SMpffDN2m8Tm3mLkcuzpav+1HcWBbQeGWjqILCky0ZNqyRqQl+9QS4UzGI6UfJaRhz/f7ur5J2WXZ9u5eDunrzYLrPnrwbdR83wt+Lxptz57uTnu5Mrc9SsfsF+7w5LFutn6w0GPcJUBxba8i8KCqzAQmGVUy6sQfNtwiTYRVFFS6uoOqDGBTrZaGcOyHJbneGpL0SFZgjdYQsICuqpSX2Wx2E+JNyTfhVE3JREsFdtBIlz1NTVNrUbKItzbdE47HcloltxG+AQcYjuyoLsmO04g+GDTwIqkRJsq71t95w/vtmFmyvvj1XeiFPh9z9Txdb3T1XhcXEnDw9V4tl34vGm3PmxzYnOb6h5fd8Si6hYm3ZGMOW2Q7m0xdoby3G8Pvlu3Fik2+Cw3x8ARIm3lJ1wUEU4dy9AYRmss3WMayo1Gc4G8KWaIKTBRN1AtuQvCnjFORbKQ+VBltl08gYrjOF225XOJKZwZlF7qkMOBxkMY2etFBdQWy2Nfno4myqibKqEm+2fT6JnsVyVntkYlWGS0bbNknxxcblNmKiRymjRUISFVRGiRU2Xck3VEHkTMXI1e/6ef08/mktzVpxofqFRpXnzVDQLoz4PjLU+z9FnSSZdLjcYdot7cjD4AsDIlPC0BuqLCqjY8XEqJsq8PCioqotYBzo/6NaZ49cLlqz0a9E8nMXYzFqcxTTqFAeuMp9zqwhpElOPCBqatojpSkbXjVS6pAUl0mg9RUrx9dI/QytcdEPoS2CRcY0O4T7ta2cGsHdNpaguNBL69ScRo1bR5s9mHHeMV3b415VzZRinRWYyaFjmHdELTydFG+2yz3G+u4NaVt7LkoBdKMKIov9ejJtnxK0rScaCpqe4oHruleIMQtPR0yHKo9mmdE3S2NaJEHGJMe6lp3aUSW5cyfQx6lJBEwmzSIKr1nAoub9YihvsbkToTx4Dl0mdDnF48WQEeRZXDwSyL39ivS2ooyIqCSqII7IZ3GQjLnC4hICpQevKV5Iv1l6HmN6bztSLv0KsQiM2e4PWy8W6ZjWKw37Y80qIXXPyJLcNUXjb4erkGpcacKKu+2x6XaNdFvUifkb0boyaSjaoLlvO2GmEQAeNiTAYk7uorapxbvKnJERERE5+NQsOsepLej2mV/1Pk4zdL/ABMbjpOmwbWjayViCY9e6COEIr1TSuOqikm6Nqm+9TKy9JnRHpF6bpdtJs9gXd1u6WZyVbiLqZ8RO+UZF62Oexim/LjRFBVReEl8ddfWPoS6IZxplf8AD9OtHtKcQv8AeY4wo99+Q0F07e2ZiLzrQgAF1qMq51ZIYqJqBbptWl6cdA3QTos4i1e8QtEm8ZYtxtLL2RXZxHJPCVxj8YsgmzbAruqeCPGorsRlU+F/Po+MeqK/+VV8J9F5pSlbLMYPOrhkVpw29XXEokWVeIUF6RCjyRImnnQFSQCQVRfC225L41StRLVwZt0x9+zhFWxSMaeyq8ynhVTjQ1Ae5xHYk4SMlcXmi+CyabIvNKUqIqbKm6LUTsnRpbtGH5Lh7+bvz2ckubKuPPwA42bG0/xhaB4STdtGyda41XfZ0lUV8virSz2PVOXe4Lxrbm9k0ay/Lr7BsVlyfHEYkIEtpw4LMWSrZsOuj1okSA24QGvWAiuMObcKck2PRrPn84euZ/t00v1CZiC2nDhkFWCiESrzeLvjLRUJE8FNg8S818mDybowYz3Hd7dpQ3jmn8K92tIU2FbccaGM9JbkNvR5RtsOMoRBwuAqeMhc24h4edBw226qQZMk9QszxS9RybRI7dmxmTazbPfmpk7PkoabeRBHbx7r4q5EVZ7XZmnLYnMnP9a3ZGe5VaLjhj2P4RdZMZLG7ZJIz5seOw265tP7t6ptwkMuFVjKO6Ii+NSTZZ2veNQ7xGgM47kE23K5BYuN5issFCtT8wQKM1IQnUfUj61rdWmnBDrBU1BN1TDztFdQpUzL7RG1NssXEM1uL0y4wgxl1bo208022601NWb1QqQt7IaxiVEJdk32VOlkfRZxi8ajMZxBiYaDRPwH5S3TDYt0ujSxBAGwhTnyVIoELYISK06qLxE2rZLxJz343O+7O9t5612Fh+8SpWN5E1j1mamG5kiRW3be65EUkkNAjbhPiQkBiiuNABkKoBGqpvjmukFZkt01bjgmXW6/xnobDGNyGIi3Gasvi7mJpW5BR+E+BzmbwcHVn1nBtXB+xC9PWy/4PJz5scGvKXIwtcazAE1t2aZuOdZLNwwcAHHTJsQYbJNhQzNEVC6h6F5bcEk5Jf8AUeBLzVt63OWu6M2AmYMMYSuK0Jw1kkTvH17/AFqo+Cl1ngdXwpXc6z3XzYukFPfs77tx0/vlyv7t7u0GDj1qZjtz+5YRohuOd0yQYRQQgQlR3YlMerQt0rZ8X1nsGaZNCxzF7DfJzUuzxb4dy6llmLGjSetRpHEddF7jUmTFRFslFdt9k3VNDybost5RaIx3y8YnkF+j3S43NH8lw5u6WxVmqCuikEnwUVAgHqzR7iFE2Lj3LegadaUxNO5xyYNyZdYWx22yhHZtrEJsO5FfVXBbYQWgQ1fXwAbER4eW+/LlOnntJ0ctjfKUpUqNiMl/3a2/8+s35jHqtVGNQbHZcmx9jH8js8K62u4Xi0R5cGdHB+PIaK4R0IHGzRRMVTkqKiotZLsndFj0adKvU23e5qhj91Pz+i5hP2vkdE76LGjf3f49+XMVVa8v9GToydG2/wDRt0ovt96PmmtxuVxwexS5kyXicB5+S+5AZJx1xwmlIzIlUlJVVVVVVapfZO6LHo06Veptu9zWcuKrSpV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaDUdFenHoZrFkUrTyRd3cN1Atsx22zsUyNQjTAltmrbjTRoStPqhiSIIEp7DuoDXg8byisbszDcRP7SDxIi/Xuvjr0/ob/AEW+iWA5S9qZqnHg5plcqedzCHGgDbrBbXicVxAjQG1VFAFJREXCINhHZsVSvE4ZBd7KwEb90/EJUQCaVOHn4t033Tx1BejPJNa728uSpMuAIOuqSC4iqZfOROfP+f8AlX0xc2gRwXZDiltvuYom/LxbVqMe/wA0lJyOTfEo7IAlvt/Fa7trcF8XnZ3huEPEJEu23hIi/wAPLVfLJPk3W15H3BAQyMkVV4fGni8tUrSzS3LtaGnZNojpbbQyatP3GaK9WReUQFOZl4uScufjqVYXjMzL8qt+KWfZ+dcnxYDnugiq81+pERN1X+CLW59L/XDpJ9FHUnFNOdK7PHf0872RSb4bd1hS3kNUko68PMDJefLbkSeOvMxVMe68VRt2b1OsHRPtULISsM2aF4MOJWnldUBVPKitDzQUXlzPfddtk8dda+aBagYzNukCX3W9anWm22Vi29AjCJL80EaMl3Q0aVTVEXYS5LutehdNHWBillT7ZDIubTTnBvv1QcO4tp9SJuq/xVVXy1PdQP6QTSTTjU+LpJcrbeJd6elsRHlZYQWmCd4eBSIlTdNiRfBRaxbNF32jNVNdU/CIzyRV4e5G2qdrxxq1j9hgPyLQzKiSoTa9yNnOEJAyOrHg57j4K8vKqbIvlWvKeT4jgVqvhhndluFjWRJPaTa0AW90RFUUHYgVOaF4gXZU8lfuvqDhukeX2hwdQMQtk0CHr1UoqHIEtvnCYJxoW3lTnX5NdM3G9GXrdNl6T59NmQTnMxe4rzAcZegzUQ16tuQaCpt8KEhCabt8QqpKhbJqYLBXcLMRTXnE78tkx8P7orVNducpnOGq4d0ctG8sIJEHUW6OsOohgIttIaIv1ptuv+Xkrdneg/i1xt6uY5qarksd0QZUdARV35Iuy7pXjTHtQMixkxjJLkpGRV8FtzgIF+sV8W2/jTxL/Dx1VMG6QmpEi5s2q2yu6esJE6wkUVQf4pvslXLlrHWpmabuceeTToqtVbKo2vS2hWjuo+k8/IrbeXrfKtIA2+LjUjiET32UttkVOXjT+CV756IEYmouTvkqkr3cK8W2yFt1/iTdeXOvLWkV/hLZkau8Ga+7cgVuQRtbgakmy78/F9VeruilKGS5lYiv+z7gFE22RB/foiInkTktVcLiK8Tembm/+yPE2otU5Q9A0pStVQKUpQKUpQKUpQKUpQKUpQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtApSlApSlApSlApSlApSlApSlApSlApSlArhlzIkCM5MnSmY0dkeJx140AAT61JeSJXNWu5BhzN5uUS+x5iMXCCqFHWRHCUwiou6L1Z8wX/xNE2S8tyXZEQOqOqGFuZGGLMXmO9ON8YyI2+2Q9YokW3zt+XBwry+cQp499s9Lvtkt86PbJ95gxpktdo8d6QAOvc9vAFV3Lny5JWnWnT7IY+auZRcsodcZWRIkC02LKqvF1YAK7s7onVBsWxbpy2XmSr2Lvh18uN2ubQ97itt4kRX3pRumkpkGeFUZAOBRVFIN0LjTh6wl4VXxhk7Zn+PXWPd7gw+g26yuE0/OJ9lWTIfncPCakiJ9ZIO+6bbpXzZtRMUu1sbujt2h24XWlkIzMmx0cFrl4ZIDhIKbEPjXdN03RF5Vwji14DT5/GBlx++Upl0Hn+MurU3jInFRduL+2W3L6vFWNuGCXc4s4oaQXXp13GY82sk46uxWwQWW0fFsjaIeES3BN0XiRCTfeg2d3LsTZhM3J7J7S3EkoqsyCmtI24iKgrwkpbLsqonLyqlfS5ZiqRJM9cltSRobvUyHlmN9Wy54uAy32Ev4LzrRIul18W3SGZ8mC5KOJIjMGUh57qikyCJ8+NwVNV6ngFCVVVVRUXku689100uTt0W7W8Iio3LRWYbdxkQASOEZGmtnWB4gMF4/BRFFUNU3Sg3cslxwHYTJ3+2i5ckQoQLKbQpKL4lbTfw0XdPm7+OucLtanLet2bucQoKCpLJF4VaREXZV499tkVFTx+StHYwC9w51uZiN2sILAshJcSS+SvNi4rrjZsPC6Lu7hEQudYJjxeNdtlyDuDTY53G8Q5UOTermCtOKbAxYiCXgqRNtipvKg+JHjPmnJQRVoMxdsst1tdbhxWnbpPddVkYUEmye4kDjXi4zEQ2DYvCJOSptvum/RHUK1yShjarZdLl3TGblu9yMCaxGTLhEnUUkXffi8EEItgJdtk3rBy9Pr43jcGwQQtshyC5JRua9Nkx5CIe6C+rjKIRHsSobfzT5eEicq6iaSO22cx3ojWx1tgYKtT5DzgS46xhTcGkQCEUcUU4jQkVEMtxPZEoN4uOWWC2wDuB3JmQKNK621HcFxx5ONARGxRfC3NRH6t1RFVKxxZ/CFkW+8t0W5lLWElqEWVk9agI4vNHOq4UBUJS49tlRN9+VYiPg1/Ztt5bltWefIvLrUt1tx15psD6xSNlswHjbAd+IHETi41IlHdaxJ6RSwhsvnCtN1nOnNdks3GU+4w24+gIJiZCZvK2jYonGiKS+FuK7UHI/pnneQyHb9C6SeqVjj3Fw5TVrC2Y2gwQNVJGBR60uO7Ai8KcZmWwpuRLuS/H7G9RfSx1V/DcV+DVSrTBK12qHbTlOSSisNsq8585xRFE4l8fNdt67dBKv2N6i+ljqr+G4r8Gp+xvUX0sdVfw3Ffg1VWlB510RxDVrUnRrBdQ770qdSmLlk+OW68S2olrxcWAekRgdMW0K0ESAhGqIikq7bbqvjrdf2N6i+ljqr+G4r8Gp0UPovaRf+R7H/7FqqrQSr9jeovpY6q/huK/Bqfsb1F9LHVX8NxX4NVVpQSr9jeovpY6q/huK/Bq8S68dA7pJa49LNrLMb1QyKx2jF7fChpn97O3sXCS9wk6SwWbUxFU+rR8W+J1AXiBxEdVBEU/S+lBCtNsWvWFJkGM5Fnl4zG4QrjHB68XZuOEmSve6HzJGGwHZPEiqilsnhEZbku5Vkcnw26P3ZzIsWkRAlyWwbmRZikLMjg3QDQxRSbNEXh34SRU2RU5ItYjvFqb9g4x+OyP0dbVu9bqop2xuiNvlDMuWq4qnZ3ualcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jr3rLfFHOHjV1+E8nNWv5/iny5wu8Yh3f3F32iHF7o6rrOq4v7XDuPF/LdKzXeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLc/tRzh2KK47p5NBvekffi5Xi4/KDqe+t4sl24O5OLqu95NF1e/GnF1nVfO5cPF4i256410dnpclyy5LmYXHDIrt1ftVnZtfc8lhy4A6DyPykdIXwAZD6NiLLSihJxq4o7rYe8Wpv2DjH47I/R07xam/YOMfjsj9HXnStT+1HN3K54TyQ+7dGm65jbbpF1Hzq1ZE5Kttst8RpzGGxgt9wSjkMHIjOPOJI41NBdFSASQfARrflm9P8AQKLg44+4xLxuG5Z7vLu70bHcWYs0Bwnopx0baYaMiBBQkJScceMlRU4kThQar3i1N+wcY/HZH6OneLU37Bxj8dkfo65nazz0o5/3dyuTsynk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Ovest8Uc4eNXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5Oau3ph/vGXr5Fvwfl0OscmPamPL1a23GIiFy67vpIkcH8er7mb4v5cY/zrcsXxyPjFqS3tSDkvOOHIlSXERCfeNdyNUTkieJERPEKInkqvir1GrmmJzmU+Ht1aelMZRDL0pSspfdG9WW25DbXrVdY6PR3kTdN1QhJF3QhVOYki7KipzRUqYyY1yxi5BYb66rwvKqQJ6oiDKFE34D25C8ieNPESJxD5UGuV0b1ZbbkNtetN2jo9HeRN03VCEkXcSEk5iSLsqKnNFSq96xrPep/F6+U/rYgvWYubY3p1SutJjXLGLkFhvzqvC8qpAnqiIMoUTfgPbkLyJ408RInEPlQezVOJz+KjticpKUpXQrjkyY8OO5LlvA0y0KmZmuwiKeNVWkmTHhx3Jct4GmWhUzM12ERTxqq1kMSxORkMhjI8jim1AaJHbfb3R2IyTmL7wr5fKAL83kS+Fsg821To073aaZrnRpMTxORkMhjI8jim1AaJHbfb3R2IyTmL7wr5fKAL835y+Fsg0elKv2rUWoyjf3y0LduLcZQweaYfaM7x2Rjd5KS0y8bbzT8V1Wn4z7Ro4080afNMHBEkVUVNx5oqboukOaAWy42u4N5PqFl9/wAgm9zdTk0w4LVwgdzO9dG7nbjxWog9W6qnsUckNeTqOCiClTpUqRJovRvw9oJTs3IsjuFwuVnutnuNxkvx+vm98DaKRJcQGRbF39y2go2ANiibI34tvt/o7Y05kwX+PluTxIfd8G7vWZh6KkGRcIrQtBKPdhXuMmgACEXUbXgQkBD3JfPOoWr2UacYO/bsLzOXjt7dv2Y3eJxybdHiXHuW5uqcVe6Ykp+Q+onxBHjNtkQg4pPtIiLXUyLUrP8ABpGfOY7qjAsw3vUNvvper3doVrj2SOdljOsIkpy2zGmBeMUaEpDBoaAgiYmXEoei7R0aMJsk+2TYl9yEgtcSzRAjuPR1bd72G6Ucz/c8XEvXOCfCQiqKmyIqb1wNdF7DOoSHOyrKZ0OEsVuzRpEiNwWaKxMZlpFjqDAkTZOR2UIn1dd4QQUMalcTVXWye4F3n6htxSsVsw+ZIg223NFAuh3Cc7HkqZy4jcpGzbEDDgRhULZU3FeFdJv2pOa6W2q/vWfWx6J1eoORrOhOvWdm5yneuaJiLGGXFSPKNRLj7j66LIdAuJp7weFQ9L5N0dsZyKc1dGcqyO0TW73NvSyIJQyMllsCxJjor8dzq2zbBE42+F4efC6O61sunGl1h0wiSolinXGSMtuE24Uxxsi2ixGoze3AApuoMipcvnKqpsmyJtUF85MKPJcbNs3WgMgMOAhVURVRR3XhX+G67fXXPQK0/Vf/AIORfqu9nVf5d8Y9bhXQvtlg5DaJVluImseW2oEoFwmK+NCFfISKiKi+RUSpbNcW7lNc7omJeLlM10TTHfDS6VxFjGpENe52WseujYchkvTnobhp9ZNiw6KL9exbb+JE8VfzvFqb9g4x+OyP0dbGttzuqjmzdXX4S5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4c1dfhPJzUrh7xam/YOMfjsj9HTvFqb9g4x+OyP0dNZb4o5wauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4NXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5OalcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLfFHODV1+E8nNSuHvFqb9g4x+OyP0dO8Wpv2DjH47I/R01lvijnBq6/CeTHZL/u9sTyrfrP+Yx6rVaJZcJv0u6Rbpl7tvaat7qSI0GC4bwk8iKguOOmIKSDuqoKAnhbKqrslb3WfjbtNcxTTOeS5hqKqYmau9Kuid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrVJaKUpQK/DqFBkxG0gq0htkqiIqH/ov+FfuLX4kzJzbbqosh0DAtvEuyL5dtk2/hUV2d0JrPe6cWDc0dUWQ4FRdk5f5VttltN1WOL0g3WgQfn9SpIhcXl8m1dSJFclCEjrlFUDi3Xdd03/h5f41szuSvWbHzjJIKK0bRKalvsQfV5fKm9V5qz2QsbkJ1h1MvuE3mAxh17k225QT69ZcRzq3BPybKP+PKvT/Rs6ZVj1pxhzE9dwgTMqtytx4MwgJClMqqKTrm6cCGioKeCvPmuyeX8/M5vZ3a/TZrp8auulweXlv/AJVgoF0WyTQuEJwRlNLxAfCi8K/XzTb/AKVo3cBRfw8W6t/i8UVzTVpP2syHWHGoFoWPByyDBQA2EyeFETl//FeC28TxW5dINvVXJdShu8WNchuBsvGLjzrra7gHEnLq04R22Txcv415aZmTMokG/dL/ACXUFfCJ54i4i/xWu6wrdsdE4F7ECFeQoW/lROaf41Uw3sicLEzRc2zCau/FydsP0u1D6RuSahXJsrODKQnAQVfZmqw4A+JR2FOf1818taTkmmFs1J7hjTIDLgQ0LfrD6xAcPmRJz357Jz/glaH0YAHUPF3p04oDD8B9YxGTnV9Zsm+6Iv8A/avSFrwCwQYvdL8mObyIiogu8aFt4v4f5rWHir12xXNuJymO9ctW7OjFczCJSuh/ibjPCdoYccXkg8Ioip9ac96/sHocYxZrg1cbJEeiSw2VVUCQNvLzXktXRh10VFiOywyaEnVE2W6qvi4U3Ln/AJeWsTd9SZ+N3dm33THn4hcW7qSJZN8ac0RURRTwd/q//ms7/wAtiI2TXsV72MwtmNKqNjJaf4/cLPw2y5w4zrDfgr/ZJdl5KK7qm6V6E6MMFu23rNozQGgmsB9CJd90NZOyJ/LbavNialWGVLauUO+OREf4+7Y4iLvc5j4iHiFUIT28i8lr1D0ZMhh5FEvkmKw62QjD4utDhMkXrtlVE5J4lXZPrqx7JxE1YqKfHP0V72OsYqiaaN8O/aelZpLf7VCvtit+pVxttxjty4cyJpblDzElhwUJt1twbeomBCqEhIqoqKipXb7S2nXm5qr7J8q+HU6J30WNG/u/x78uYqq19YpJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oMTieU2LOcVs2a4tO7tsuQW+PdLdJ6o2+vivti405wGgmPEBiuxIhJvsqIvKstUq6J30WNG/u/x78uYqq0ClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKwl/zLHMZdajXe4EMh4VNuMxHdkPkKLtxI00JHw78t9tqzdSaxksuVe7vIXjlSrzPZcNfH1ceS4w0P8AJAaTl4t1JfKtWsLYpvTM1boQX7s2ojR3y2n9q+H/AN2/+rdy9xT9q+H/AN2/+rdy9xWMrrXS5QrNbZd4uT3UxILDkmQ5wqXA2AqRFsKKq7IirsiKtXex2PPnHRV7Td8uU9Wc/avh/wDdv/q3cvcU/avh/wDdv/q3cvcVLsP1y09zm5Q7TZHchjyLkysiCt3xa6WlqYCChL1DsyO0Dy8K8XCBKvDuW2yKtb9XIwmHndnzjo7OJvRvy5T1ZP8Aavh/92/+rdy9xT9q+H/3b/6t3L3FYysXj2TWTKosibYZvdTMSbIt7xdWYcMhhwm3Q2JEVeExJN05LtuiqnOu9jsefOOjnabvlynq2f8Aavh/92/+rdy9xT9q+H/3b/6t3L3FYylOxWPPnHQ7Vd8uU9WT/avh/wDdv/q3cvcU/avh/wDdv/q3cvcVjK68uexCcjNvBIJZTyMN9VGcdRC4VLc1AVRsdhXwi2HfZN91RFdjsefOOh2m75cp6s3+1fD/AO7f/Vu5e4p+1fD/AO7f/Vu5e4rGUp2Kx5846Harvlynqyf7V8P/ALt/9W7l7ivtjVPCnngZdnzofWEgC5PtUuG1xKuyJ1jzQgir/FaxNfLrTT7RsPti424KgYGiKJCqbKiovjSnYrHnzjodqu+XL+6i0rUtLH3nsKitPOm53HKnQGyNVUuqjy3mW0VV8aoDYpvW21l3KNXXNHhOS/RVp0xV4lSrpY/RY1k+7/Ify5+qrUq6WP0WNZPu/wAh/Ln68PSq0pSgUpSgUpSgUpSgUpSgUpSgUpSglXRQ+i9pF/5Hsf8A7FqqrUq6KH0XtIv/ACPY/wD2LVVWgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSg6N6sttyG2vWm7R0ejvIm6bqhCSLuJCScxJF2VFTmipUxkxrljFyCw351XheVUgT1REGUKJvwHtyF5E8aeIkTiHyoNcro3qy23Iba9abtHR6O8ibpuqEJIu4kJJzEkXZUVOaKlV79jWe9T+L18p/WxBesxc2xvTquOTJjw47kqU8DTLQqZma7CIp41Va6ty7rwyUtsyeUKx1Eih3M9hCQApuon5BdFE3VPESJxD5UHNYnicjIZDOR5HFNqA0SO2+3ujsRknMX3hXy+UAXxfOXwtkGlEzXOhTHvenx/W3uUqaaqqtCI2mJ4lIyGQxkeRxTagMkjtvt7o7EZJzF94V8vlAF8Xzl8LZBo9K6N6tEa+2x+1THHgafHhJWj4S/wD9En1iSKKpyJFRVStC1ai1GUb++WhbtxbjKGKv+oWH41wDdb/BBwpKRSbSS3xgfJS4kUk2QUJFX6kVPrTfu2/KLJcMdYypLgwxbX2Uf6990BBsV/vFvwoqLyXnyXlWmZFppkk60wbJZ8ljxolvSSrAtw2Y3M21AOMQbIFXw3d1AW05psO/hJsF2xi5paLNDtclq4O2aU2+jdwJGQkiAkgoRNN7Co8QkKo2vME5eWpUjmlZ9jrVwtFsgTGrm/e1JYqQ5LBIrYrsTm5GKKKKip4PEq8JbIuy18Pah4xGyCXj8i4MMlAaFyXJdlMNsskSKqAXE4h8WwqvIVRE8apXVxXD7pZ727erlJikr7Lxk0wRcDch97rHUFFRPBRBbFC8aqiqqJvtXx8i7jIyMLvcHojjA3R64oCkRKipHFmPyVNtx2Il58l223oM+1lOMSH5MVjI7W49DbV6Q2ExtSZbTxkaIu4om6c15c6+I2X4nMkNRImUWl999wmWmm5rRGbg8yAUQt1JN03ROab1Pw0vyqURhcJMEAdjOxpBBcpDgvK8+2Tzgsq2LTHE2LicADtuSbqvjTLy9NpUs3kbdhQxkXIpSuR0VDaaajq1EEfBT5i8JcO6Ii77LQbY3lOMPBNcZyO1mFt/30hmNqkbmqfvF38Dmi+PbxLXNBvlluitjbLxBlq8z3Q2jEgHONri4eNOFV3HiRU38W/Kp9B01vMS1K0tvtiyG2GIYNlfLi4itASGRNvFv3OvGDZCINltwbKqovLMBp/MutsgQ8muMZVjOHKe7mhsK8cgjVePrybTZeFURTbbbcUk4kIfFQbTIvlliCZSbtEb6twmjQnh3RxA41DbffiQPC28e3OsC3qRZ+5JEuXbbnEVthmTHZdaBXZjbpKDStCBku5EmyCXCSbpuiJXVYxC+jf5t+fW1uLKiOwW47puPA02gojRkqohOGeyo5uqLw8KIq8O5YBvSOU5HdmzIVudld0RFatrtylPxe52EJEaV5wVNEVTJeHgUU4RHhVN1UN9hZRb34rr9yByzusIRvR7ibbbjYCXD1i7EoqCrtsYkorv49+VdMc7tK3QoBxpbcZHnYw3ExBIxvNApuNovFx+Cgl4Sgg7iSIS1rsDT3II7tpJxy1BGs75SmYjRn1ZK67xm1zDwWmk4erREXcwElQdkROKfpbOvd1ny7h3DCaJqckfuSVIMXXpAqHWkya8DKoCrxI2qqZKqqqckoNxxzJ28kEnWbNdIbCtg9HelsiASWi34TBRJdvFvwlwlsqLtzrNVqeCYlIxopz70G3W4ZaMgMK3Om4yPVjsrpGYgpOGq+EqjvsI7qS862ygUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFfhmkeTFaV+OSo1z3Ew3UV8v/ANb1+5lfkJExx2zk8MkWgUU8Eic3b2TyoiePx1Bfq0YiU1mY2tGtOQAywkcWWusXdDJU+d9Sc/8AGqtpNpxD1bzSBhc4lKNNBzugwNVJsEbVfr+vb/OtWvd0s+Pxu7rs+ANIu+3IUVfqRPH/AJVrNn6VuUaezp0/Tqx22NIktdQEyUyTrjQKvPhTdE3VdvHvVaYqqjOiFqMmTv3RKiaUZnIt2Q29Lm6w+rkF8206sgReSbL4Kr/OuV/Q64XiQqxdLMdfjFzRZdrjxQTdd9+MRAtv477VN770o9bsxkORsmzeScR4TQ+52Ua4eS7oJAiEP80VKmzF1yKZLSN3wdleH+7J2Osp8915cKKikS//AFyqhew2NvTNVdyIn5z0ecRcyoiLcZfF6XtPRR01cBxcxj4lZ3VXcAtWSqR7/UrRAqIv8iWuBno3dHazvq/OZudz4C4usN6T1Xj8W4tCn/Va1vG+j30l8rhpJt9muGIWifw8T1wfSE7ITyEY7oa7+NBRET/1qnj0XG9PbA1Ly27yr7JkKpOPJNJG1X+7wq0fJP4Eu/8A0qjGBxtv39fVOfdGf1mYZtVvEVZzFTqNwNGsLjjHxK4x7RHVxTNlHJSt7r499y8a/wAq2PF5c02Fk2+a5OgyjQYwtPyEFUQvCUV3XbdE8RbKnjritFgy6zW5Dxuzuv21tV4e4QkbIvl8IjEd/wCVavk2rOQ2CQ81c7wUcVBQWLPeeJSReXITI0T+C7fyqCvA36ttUzM+e1BXh7lM5zVP6+Ld7jOyNiCpAzM7rBwvB4kAlBU2Q+NERR4V3+tV28fi2m+R354Lpx3W8MSJqOASvS3TRVLbfbkqqqboibl/0rpQ9brmNvba7tgMxkRUQHkMRc5eLiIU4v8ABK7TOcXlIjM2biUF2Gq8QOQn3OE1VV8FEEuFS23XbbflUVWEqtz70fRXrw8XMoqn9fKSDkUezxyDr5pzRcICisbPdUe+4r1ilsgrtuiInNPH9dexP6Oe9ZTendR3sldA0E7V1CNruI791qSIv/7a8b92YvOmpIuWO32C25HVZLbZNm05vz34eAOJUXbwl4v5+NV9m/0dbONMFqIzYrost5py1NSQMFFxjhSVwtn5N0XjTZF5bKmyVc9mWaacXTVlt2/LZL1h7WjciYXbonfRY0b+7/Hvy5it4yPMLZjFxx+2XBmSbmR3FbZFJoRUQdRh1/icVSTYeFg03Tdd1TltuqaP0TvosaN/d/j35cxWa1Jxm93+/wCAzbRC69my5Cc6cXWAPUsLAltIexKil4brabDuvhb7bIqp9a0XctesOkl7kXWJZdUsQuD9ijDMujUW9xnTgRyRFF19BNVaBUVFQi2RUVOddyzakad5FDeuGP57jlzix7glpefh3Vh5tucpIPcxEBKiPcSoPVr4W6om1eYs90iyPF9DYLkzFosVbFphktsuiq8yqNSpKxnEbJQJVJDJt0lUOJN0Xdd1TfZLhg+p16bn59a9KX7SUBzFgh4ws+AEq4N2uWbrzrRtvlGFCbcQGUddbJUbRD6rklBZLvrTprZc+x/TSZllr7/ZI7MYhRRnx+ProwAbjRApofHsabCgqvj32rMXjULAceyO14df84x+2X++b967VMubLMydsuy9QyZIbuy/3UWo3heIamQ9QbPmd80+mRWJmU3+XIZbnwnDt8SZGjpHee2e2LmwoGLSuEhryQg8OsnqNiWdydXIN0xHFr2sS4LbhuUwX7RJscpqO8R7XCPLRJzTjSEZMlBVeIyBXFRB2QKnCz3BbnlU7BLbmlil5LbGhkTbMxcWXJ0VottjdYQusAV4h2UhROafXXBZtTdNsjbvb2PahY1dG8adcZvRwrtHfS2OBvxhJUDXqSHhLdD2VOFd/Etec8S0G1Qh5+Vvu8vNDt9uumRXWFdHp9hCytLce6OBWBZjLdXXdpAobb7gAKgpI45wgi4PE+j3qu5i8223y15e/NsGOwLJFayC446kK6DEmsyCixAt0UHCjOJHURdmmDgo9srSKpkgenNPdWcK1TevfyGurV1hWOUzFK4xX2X4ctXY7b4nHdaMkcDhdFFXl4SEnNE3XcalWitjyaNkWoOX5Fp6eIDll4iXCLCekxXpJgFvjsGcjuU3Gxd42iHZHDThEV4l32Sq0Eq6J30WNG/u/wAe/LmKqteX+jJpPnlx6NulFwh9JvUq1x5WD2J5qDEgY2TEUCgMqLLavWk3VAUVBRXDM9kTiIl3VaX+xvUX0sdVfw3Ffg1BVaVKv2N6i+ljqr+G4r8Gp+xvUX0sdVfw3Ffg1A1k/rF0J+8CZ/pW/VVa8v6saT55FzzRhh/pN6lTDmZxKZZeegY2hwzTG70avNIFpEVNRAm1RxDDgdNUFDQDCl/sb1F9LHVX8NxX4NQbri2oODZxIu8PEMttV4k4/Petd2YhygcdgS2jIHGXgReJs0IS5Eib7bpunOtFxr/drl/z68/mMivD8foB9JjUHpcZZrVF1YyLTqys3VY0bI5DkMb9eW2BBo3wj25tiMLTyskqK4AKoECmDqqSl7YwmM9Cs0mHJnvznWLvdmnJT6Ajr5DPfRXDQBEEIlTdeERHdeSInKtHAbqvl9VLF76fn9GfrXtRYsmdp9k8KFHdkSJFmmtNNNApm4ZMGgiIpzVVVURETx1sNKvztVI2POeHYHn+HXPTq45VkOYZtaRsiMRIM2JEjnYLssTYSPuKIyXVE0rrHG8S9USpuqqe4ybH9P8AJbhZM0ODpudm+UuDzYs9iz4Rc7LKO5de0StSZMl5x25yRRxzhloI8e7hAp7lwe5axGS5hiWFw2bjmOUWixRZEgIjL9znNRW3Hz+Y2JOEiKZbLsKc18lRTbjvlJFcvO+TaV2nFrhkNrt2m7/7PFvdkuV7slssrshi5MdzPhIIYjIr3UvXdzG8AAanwbkJLyXXI+EWeLZ7f8ttHMnuODpcMrODYix+TPdjTZE1DgSFigjhtbtdb1TpAiMcaIpNeJPUEzUfTy3PWWPcM8x2M7kqoNlB66MAVzVdtkjIpfvvnJ8zfxp9dC1G09DKGsILO8dTI3lMW7Qt0Y7tNRFCJBY4usVUFUVdk5IqLSaKSK5ebbno3f73abndM8xS43fKoGOYixFmGDj7zE1p93upyK8Cbi+gmqOOtKhcKpuu21WDS7DWMLi6i47Y8Z7y2bv485aYUeIrEbqnIMdTKO2iIPCTyuqvAmymp+Xetwj6l6cy5d8gRM/xt6TjA8V8ZburBOWsefOSKFuwngl8/bxL9VdORrHpDD7z916qYex8onCZs/WXyKPfFwXEbII+5/viQ1QFQN1QlRPGtdimmJzzJqqnY89WPRy8YphUf9n+Dz7Rf75pa5HvD8WOUSVNuQEx1bb7u4L3Ugm+IKZIYoqoioicu1jGIspf4U7S7TbIsZwQb3bnItrGxvWg2pQW+cMuS3EfbbKOiq5GBXTEQNwd91+ctuha6aUySy5ZWcWS2sYPcBtl6kzrnGZZjOkAEKkaubAKqfB4fCvGBjtyrPPagYHGds7EjNrA05kIiVoA7kyJXFC24VjopfvkXdNuDffdK5FFPdJNVXfDytbsEyANMMusuA4e3AZOVaDuEtdP7jan7iw3J4pbM23OPit2e6lCV6RHMEko4TY8WyCtp6MlhnWDCbkw4yca3SLu8/bIg4o/jcaMwoAijGt8iQ89HZVxDJBc6teIjVAQVFVr9K7Tbimc3JrmYyKUpUjwyelH/B6/83vH5lJrcKh+I6aZpkFsl3e09IXUDGYki73Xq7Xa4Vgcix+Ge+K8BS7Y88vEqKa8bpeES7bDsKZv9jeovpY6q/huK/BqxsV+fX8Z9WnY/Kp+Eeiq1Kulj9FjWT7v8h/Ln6fsb1F9LHVX8NxX4NWMyfo65LmmNXbDss6TuqVysd+gv2y5wjh400MmI+2TbzSm1aBcBCAiHiAhJN90VF2WoEq0UpSgUpSgUpSgUpSgUpSgUpSgUpSglXRQ+i9pF/5Hsf8A7FqqrUq6KH0XtIv/ACPY/wD2LVVWgUpSgUpSgUpSgUpWmZpq9gmAXBm0ZBOuTk55pJBRrVZZ10djMKqoj8gIbLqx2dxJOtdQA3EvC8Fdg3OlcECfCukKPc7ZMZlxJbQvsPsOIbbrZJuJiSciFUVFRU5Ki1z0Cla47n+Mt3C5Wpt64Spdnd6ic1DtUqSrJ9zpIQV6pskXdpUUdt+IlQE3NUGvu2Z5jF5usqyWuXKkzIMxIEsAgSOGM+scZCA6fBwt/ujBdyVE3JA34vBoNgpSlApWNsuR2bISuIWeZ3QtqnOW6WnVmHVyAQVIPCRN9kMeabpz8dZKgUpSgUpSgUpSgUpSgUpSgl3SPkyImntqeiyHGXCzrCmSJs1FVbdyW2tugqp/ZNszAk8SiRIu6KqVUagGQa141mVszqxZZp+8XyDyu39xxnLkTYXRYdxiOMTW3GxQg6mWjSk0SLzAULiBxN6dE1d0/nZs5p7GvTxXltw2E4rfJGI5IAONyO3MVtIzj4BuRMg4rgiiqooiLsy7xuNKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBX5ExXL6w4L8OX4KLsSSHCUxX+0iKXLbb608lfrtURmdC7o3T2uok4HOVv+4OSXQE/yGSlQ3rWtjLPJ2Mn4s9IHIbo/lr0Xu1QYjIiCDbm47qm6rulSQMjuzKqjU93gVfmkSrtX7rSv6NvoXTXCelaOG6ZruSlkt35r/wDNV8Rv6NPoTRJLUtjRQesYMXA48iuxjxIu6biUpUVOXiVFSrtuu3RRFGSaLsQ/Ovo+dFy95PZLZqJnE2SzbJSJJC3IPBIJj604/BBS8aEqF4K7oiKqKntbA8bxHG8dKbhlht2NsL4Sk00jsh9UXxk54Rnz35kq16Ye0B0lfa6lzFSQNttguEoNk/hs4m1c1t0N0vtMYIdvxx5pkN0Ee+Ustt/H43VqjNnSqmqre7N6mdsvJ2QXVO+TU+Xc1fEEXj6xNtv4oq18zpRXeGiDfElRuJFSM4qKibf3dh32/wA69Vv9H3SCQhi/h4GhookizZOyov8A+pXXh9G7Re3goQsOJoV8g3OZ73lXZtzOyXNZT3PKky6I9HbtkyJHFhjbq21c/d8HlXYUTn9flrUct0+sWQ7Ns2q2k05vtIYFS2/huRKqf5V7XkdGvRSVxK/hilxeP/vKWn/o7XxH6MuiUXnHw1xvflyus331c1U95N2me5+YeYaAtR5yzSkyphgClHYEN12TyIm3/wBb1qV3VuxWqKy2s6NPcfI3nQJwERBXwQJCRN9lRfFy3Wv1zLo76PmvEWJmq8uffOX72sNfeiN0eclVsr3p6klWt0Fe+s0PH49+F5N/8aq3sBrNsShmmic8t78lorvWE26NzluukyTjLZOcBtF9ab8k225cuWyL/Bfb/wDRhwo8B7U2NHHZBGyIqqK8Xim8iVV3VdtvqTn4k3q7ROhD0X4QvDG0yUUfNHC3vdxJUL6xVZC8P/5dq37TfRjTXSJbkWnmNJaVvCsrN2lvv9crXHwKvWmWyp1h7qmyrvz32SmHwddm5FU5IqaNGc2A6J30WNG/u/x78uYqq15q6PWtuL4NoFpphWU4bqrCvWP4fZrXcY37K8nc6iUxCabdb4wgEBcJgSbiqiu26Kqc6oHaW0683NVfZPlXw6tFIqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gdE76LGjf3f49+XMVVamnRktN1sHRt0osV9tku3XK3YPYokyHLZJl+M+3AZFxpxskQgMSRRUVRFRUVFql0ClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKkuNf7tcv+fXn8xkVWql1xt1yw65TwO0XCdap0x6fHkQIpySaJ41ccbcbbRXN+sI1QkFUVCRF2VOd/A1RE1UzvnJUxdMzEVQ71KxHyljfYeT+rVx9xT5SxvsPJ/Vq4+4rSylRzhl6iHSDyyw6dZPiWc5NMs70FI1zsyW64XmBAI3JItbPt92Oti6go2QGLak5wOrwga7itY+Usb7Dyf1auPuKfKWN9h5P6tXH3FcqomYyeoqiJzeTWdE9TLli2PdyW/MUgZFg9kscu3WiVZYjEM46uGQzVuUZyQy2nXCQlEEnEUS3bQkFaq56TX4J91nMY+0kibqNAyDunrWVcOI1FjslIVd0XdEbMduRKich2Wq38pY32Hk/q1cfcU+Usb7Dyf1auPuK8RZyepuTLyratANUksF2xm7W/LLi9acRu9ggvXK42ELbLclkC8EMYzAzDA1BDIprraiu3+1VVNNw1z081ayW7XG0YbZrq3ap2PRIjTloSyNMynWTcI49xdmgUvgRFFGUjcIopnxGG/Gl6+Usb7Dyf1auPuKfKWN9h5P6tXH3FNTsy2mt25otecLz+35nOyuLp9Ovca1ZmzkbERiZBE7lHctCQ16jrnhEXmXU4lR4mhVOYmS8qyGkuluUY1lOP5BfbCxCBi033dlqQDg2051yaktw02JeLhbRRUgRQ3BURdlTes/KWN9h5P6tXH3FPlLG+w8n9Wrj7iu6rbm5rM4yZelYj5SxvsPJ/Vq4+4p8pY32Hk/q1cfcV7yl4zhl6ViPlLG+w8n9Wrj7iv6N8lS17ntGLZDKlHyBt60SYbe/1k6+AAKfWu6rt4kXxUymN7u/c2nSj/g9f+b3j8yk1uFYTDbA7jOORbRJfB6QJOvyXARUEn3nSddUd+fDxmW2/k2rN1h4iqK7tVVO6Zn1alqmabdMT4QUpSokhSlKBSlKBSlKBSlKBSlKBSlKBSlKCVdFD6L2kX/kex/8AsWqqtebdAtaMawDQzT3Bcsw/VKHe8dxe12q4xw0uyV8WZLEVtt0EcagE24iGJJxARCu26KqKi1vvaW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtK1/Cs4suf2p282KFkEWOzIKMQXvHrhZn1NBElUWZzLLpBsabGgqCqhIiqokibBQKkl9bzzAtTL/lmO6Z3TNYGWwYDCLbbjBZct0iMjo7PjMfZRI5C6JIrKuGhI5+75opVulB5jznSbUW/wCcW683fTi3XnIHo1lS05VbpDDUXD3o7ynPRpH3UlNA8K8Kdzg4ryeA9wCiVr7PRtzy3Y467jOJxbPkd3x/JYN7mMymWnrgrtxbehR33myUjQmOuACXiRlHFTwPFXpXU/J5+FacZRmFqZjuzLJaJdwjhIEiaJxpojFDQVRVHdE32VF28qVoWVdJOz4ll8HT244ldTvd3tT0+1GEy2C1PcajG8Tbcc5iTEH92QdaTAs8WyK4m6KoSBdCc2kwr8uIaK/Iq1T5c523WHu23B3K05jZQwHgjvmw1vJXh4WzUU341XZVVMlA0A1Bt2Y5BNxvEo2PyLpd37g1fGHoraKTuOLDB0laNXlIJfFuqhvufGPEiqqZyy9OPTmBB0+teowhZ8ky+z2u4z4/fK2sBbim7CyvUPTEkPgZ7qncwSFAdlc4Kz936U8BbBkk3HNPMoOVbbPfJ9oemtQwi3R+1ukzJaDhlcY8DiCv71GkIF3AiVFRAnVp6PV4vE61RS0OiYxh63e0d+salv291icbLE0J1wcZYdcZdB/r2AVTXrnkFVdbTasZqh0fdSb5pbjmmts0thTYtoj30bW4zHs78iySUlEVsFt2eZBFirH2BSisnIHZpBJlBVateAdJC1ZnmUHTlzDb+zfFtcOdc3gWEceA6/GR8RdZblHKBtUVRF9WVjqfgI8pcqsdBP8AR/GshxqJk6ZFBKM7c8ikXBhCeBxXGTZZFDVQJdlUgJNl58v4pVApSgUpU/ynXDC8PvsrHLtZdQJEuJwdY5a9Pb/c4pcYCacEmJCcZc5EiLwGvCW4rsQqiBQKVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1Bgc60EssjFrrHumo4WB66Zq3fWLmbINi2sqZGTvcSG5wuo+YNtJzRVcNtRHjEUXO23RO6Qc6j3l3NGXcXt98mZPAs42vhlt3KS26DvWTOuUXGE690hbRgTQlTdwkThXTNWdV7Dqdjtnw7DsT1Jfub+YYpMFJmnGQwGAYi36DJfcckSYTbLYgyy6aqZong7Juqoi+iaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCVdE76LGjf3f49+XMVValXRO+ixo393+PflzFVWgUpSgUpSgVFpmoer93t991Hw1MTHEsckz2Es02BJduV4CEZtvmEwHwbhkptuoALHf3QBVSHj2C01Jblo5mondsbxTU2LZ8JyGTJk3G2nYlfuTCySUpIQZqSAbjgZEZfvY75CTh8JInAgBlGtdcPftM69MQrq5GgTbRBJRZbRXDuQxyYIEU08FElN8W+ypsWyLsm+lYz0m7vKgjHvukGWyb/Pvd/t9rtdpbt5OSmLdI4FJTOb1QFwKm6uOAhGBoKeE2hZDIejxeLhfZSY5n8azYtPnWS5ybR3l659JFtNjqwako+IgwbUcAIFaIkJEJDRNwXDXHRLVuz5lZ5uA5xZ4jcabklxauM2xFJZhjcXmXUjPxxmNHJXiV9RcbNpBUG+IV2VDDd2ukHgsmwXfJYca7PwrLYIeRPqMcBM48lXhFsRI0VHRVhxCEuFEXZN157c+nGpeQ5jkGodru+GzLdGxC9pbre8qx17ua7kZe3TgkGvHu4q+ELacJtptxIe2i3XotXYLS9jWI6oLbLPdMbi49eAnWdJkmT3M6662+08LzSMqRPuo6JA4hCqIHVqnEtRwzB5+JZLl93O+R5cHJ7gzc2oqQibdiujGaYcQnesJHRJGAIURsFHckVS3TYNUuXSExh+xWm6Y+E5Vu1ugXZHDgg+MViTNZig28CPtqLpE44KbEqD1TheFwoJ464dIhx7UDH8fsmJ3JnGpd7uNqn5HcY7Yw5HccSS4+kVQe60VbejoBE8yIGiH1anspD923o2RbZbsut7WYPOpkl+h3SGrsEFS1wo8sZYQAQSRTDrikkhku6dftsqAKV/GOjzdByGKEjO4x4bbbvc7vBsjVm6uSJ3BmSElt2Z16oYIcpwm+FoFFFUSVzwVEO1bek1iMq2T7tdcQy2yMtW4LvahnQ2CO+wnHRZZehiw84u5uOMijb/Uup1oKQCiqqbfp1qTD1Dj3NssbvWN3exy0hXSzXkGElxHCAXG1Io7rzBibZiYk26abLsqoQkKRvDuhdYcTst4s8SXhNsJ+HGh2mbjmAQbVLbWNIB9iROeQnDmvIbLPFwqw0fCS9UhEhDW9MsAv+IOX2+5nlkbIckyWY3Kny4VtK3wwFpoWmWmI5PPE2IgCKqk6akZEu6IqCIbzSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCVayf1i6E/eBM/wBK36qrUq1k/rF0J+8CZ/pW/VVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBStfy6blUOO0WOQgdBVXuh0WkfeaHbxgyTjaF/PjVUVERGz35S3IHr3d8/iQ4kvK5TQFCZFVagNEXCKyT8BwQUV/dtFsQoqLxIqbcIqFypWgZ5k863XR22M5IVlJq3LJgiDLTjtylESiLII4JcSIojuIIhL1ic0RK+cPi8cnJc7u7p93E45F4lBlVjNMAiGLZ8HFwoaHyVVReFFXdd1UNpzDGIGa4peMPur0hqHe4L9vkHHIRdFt0FAlBSRUQtlXbdFTfyLU7Ho04eGZScwbyTImxlXIbw5bGyiBEKakBYJPEQx0fcUo6oOxukIqKKCBz3x+Pv3jG7JcbjGvjsYGIcWZLNYkXifny9y4n3FAf3YI4Cqqqiomy77Jwr27TmeVXJo4KZY31bEuS8/co6sSl7jYjATnAaR22y3cdHhLq+X/AI08Yfdh6N9nxc7ImP6lZ1b49rt0G1zo0adFaC8x4ar3Kkohjo4BAJKClGJgjHwXFNK77fR6wlu1BZluF5OOMa/xfCfa4lC7vdbJ3VG05iS7N/UnzuLx1i3ssz+1QxKTkLLxPQLest+W2zGZt7sgy8PjFouFOANlI0MUM0XhQfBr7l5tkMO0QO7cyYF11x9xtyKrJPy2d0FpWlfjtNSdj4uIWkbIhUFFV38IOzC6O2Os5fiuX3fMMkvh4Y0PemHchgONMSUjdzlJBwYoyGiNvkTTToMKvhdVvzqr1pnfrJIN1lnIeI7FH4ylzp8UIYxlRPC6o1LicEf7KEyqLuv75dtq1aZfp16xZ5/Jr3EjgzOjMnDuQ9zR5MdPDEpBiBK11489lRQTZA4d+JKCuUrz7IG4FbLc1e4tlYtxRpk+2WeYy6bUlx19UaYYbRQXjRtR4OW4daioG/zd0ayedPcvEFjInoNqiQZDkKYjSOk4QtoDgiSCpOBHNd1MfCJVTmqCqkFOpUBZCQdukhCPHbRjcqdBgyZTLxu259G23CedNzZvrEMurAl3FCVOBTXmtVrTsW0xCCbNtZgtuK44DLAmDKirhKhtgaqrYGmxoG+yIWyUGyUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJVrJ/WLoT94Ez/St+qq1KtZP6xdCfvAmf6Vv1VWgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgVrGp2XSMB07yTNolvbnP2O2SJzUZx1WwdNsFJBIkRVFFVE3VEWtnrE5XjFozXGbpiN/Zcdtt4iOwpQNuk0atOColwmKoQrsvJUXdKDz7qRqJ0pNLUx12VkGm2ULee6JD8WDh8+E62xFYWTIBpTujqOOE0DggqoKIaCqoSKqJsma6o6sZTlUGwdHp3FX4DFthXO9XS82x6a3HamGisK2DcuNxKjIOukCkpbE1snhLWzYxog9aMjtWS5bq3mucPWFp5u1MX0LW0zEJ1vqzcRIMKMThq3uP70jREJVRN+dZXTvRzD9K8XuuKYR3bBj3aZKmuPk6LzzTj3IRbUxUUBoEBtoFFRAGwHZUTmEgxvpOZJp/pZF1M6SNyxNuJkTduLGlszTVnSc/IYN12Kq3C4G0JN8Cr1rrzIKn8dt89G6ZemE7T236pW6z3ydjL1zds91uEJ+3SY9jlBtskt5qWTSgSqIi6wbzaqY7km6VtS6AWFNO8TwNrL8lal4QTTthyNs4iXSI6AE2jn+79zGqtGbZCbBAQku4qvOsHk/Rdt2c2GDjOeawag5LbGri7c7lEujtsej3l0tkbCUz3EjSMtIicDLAtN8ScZCZ+FQdbJukPnEG26cXqw9HzOXQza4uMSLZMK0M3CMykZ51sFE7iDbbx9WJohESCAuCfA5wivV1TzjpMW3LrREwRnDbJaL88xAt8e/2J24zHJZQZUl0ScjXNlsOEo4NbcKjuakjhImy7aGhJDp/juEHq3nT8/FJQS7Tk8g7c9dmSFs2kElOGsZ1OqcNvdxgiVF3UlNEKszctKYtzTCetzTJhLCJ6XFlwpDD7lyd6lxle6zeZMiRRdc/wBkraopclREREDVpGvzttZS33bDL4LtvGNb8iyOHFjyLLYrs80Cqy8HdQynQA3W0ImQcAENON0OEyHEBrNqGeCtY8i42mqx5QuIm13vf72hIQutKX3OkjrVZ7h/7Rw9fvzQVJF322u+6CWO+5FLuZZllUOyXac3c7xjEWTHS2XSUCBs46pMlJBF6tpSbZfbbc4PDAuI0POBpPhwapO6wBFfTIXrWlqIut/cICFv1qN7bdco7Ap+PgFB8VBjcM1PmXa8Zxb8ts02wBh5sk53fGjNocYmiLukXI8yQhtn1ZmKGjTgDsJAq86m7nSvftuf9bmWH3bDNPAw+bk/fTIIEcHJLbDzIi8y5HmuqgEDyKrD0dp4VUN+ZKI7tZdBHYGU37I77rDm2Sxcnj9yXazXSPZhhSmEbNsG17ngNPCgC4SJwuoq8uJSrEXDoq45fupj5ZqXnd+t0a3TLKxAlyoTTTVukI1/2YTYitup1ZMNGD/Wd0IQJxOknKgxmDdNrRzUCJfO8Sy3bpZViIFpi3G1XKTcFlOK1GFg4Ex9jiNxODgddbIPnOIALxVlcx6QFxiabZBkdqxG9Y1fsclx414g32zFc3bLHdVCWc7GtjzndjKM7ubRn132VCIFA0HKM6ALJt0qJlusmomUSSGKtum3CZCYctTsdzrGn47cOKwwrqHsqm826pInAXECkC9iJoasK1SgZ1azxMinzguEzJ0kwhuEk22jaZbNoYqQ+pbE/BZSOjakKEQkSkpBPLNrFq9qKzhEnSvUzSK6QcrG7OBc49jn3CM+EUWiaTYZzJxXS4yRxokcVouW5KK79XM+kbmkKx4FkEjOtNNLYl/lXW0Xx7MIjk+LFuMJXBIWJKT4Qk2TjLgjxChEiiWwruFbnK6MrByLZcrXrRqDaLrCenyplzhd6O6LnImC2L7z/WQDACUGgAUYFkRQdxRF51nI2gmPWefgz+K5TkVhgYH3QsS2RXIr7M8nxVHjluymHZDpnxGpGLokRGRKqku9Bi7DrNlcxNMmr/hc+3rmiuNTJzcNooaPiw+YtCjkpuUx1iM9cJLHeHgVAJRJeJEjpK2aDe73bbjprm0W3YvdktN9vZsQSg20jFsmXj4JSvONOC82u7TbhNou7otJWW1J0ZueouR2jIWdZ84xdLE8kqDCszFmKO3JRt1tX1WXAfcIlbeMVFTUPEqCi860Kw9H/P7vqBmk7UPIHIeIZFdGbgVus2Qi+N7FphhkAnsuW1s2N0Y4ySLJAT41bMTBOYd6N01tC5mq7WksPIGH5r91KxBOau1sNvviKkJR1ipK7vTYxUOtWKjO/wD9psqLW8YBk2czM5znEsxn2O4N2J2G/bHbXa3YRJGkg4YtPI7JeRxweBEVwVbEt1XgHxVj7doHDseRJccc1Oziz4+t3cvjmKwZURq2OS3DVxxVc7m7sRs3SVwmRko0pKqKHAqgv3jGil5xnNrvnBa557dHr2Aty4U2NZEjEgAYs7dTbm3E6pDVR8PmqJx8flDrW3XB9nCs9zbJMLyCP8h577UuzpGhBObYbYae3QgnOsPL1bnWcSOtqqLw9WhJsvRvHSowDEscvuRahWe9YaNm7jNqNfTgsOXBmWZBEdZMZJNNi6bZjtIcZJvgJXRaFOKuKP0Z5g2vMrPcekBqTco2csPN3UZDFgFetcabZJ9tWrYHCfVNCCJzDZVXg4tiTtZD0abHlFykXe9ai5m7LO3WuHFdacgMlAkW9wnY05km4or16OG4RCamyaOEJNKC8FBltDOkDgHSBstzu+DyFRyyze4LjEKdBmFHdUEMP38F+RGcEgJFRW3j25iWxCQpS61XAsHnYWxPW7ah5VmE24vC67Nvz0bibQRQRbaZissR2hRE3XgaFSVVUlJdttqoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCRa+y3rJfdJcxcst9uNsxvNn5t0WzWWXdZEeO5j94ig4seI248Q9fJYBVEF26xFXZN1TsdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSg7vRqsl3xro5aV45kFtkW+6WrCbHCnQ5Dag7HkNQGQcbMV5iQkKoqL4lRapFKUClKUClKUClKUClKUClKUClKUClKUClKUH//2Q==)" + 'cell_type': 'markdown', + 'source': [ + '![](data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCADoA/0DASIAAhEBAxEB/8QAHQABAQACAwEBAQAAAAAAAAAAAAcFBgMECAIJAf/EAFoQAAEDAwICAwsJBAcFBgMHBQIBAwQABQYHERIhCBMYFBUiMUFWWJaX09UWMjhRVXeVtdQXI2FxCTdCUoGRtjM0NnWhJCVidrG0NZTwQ2V0goOS4ZPBw9Hx/8QAGgEBAAMBAQEAAAAAAAAAAAAAAAMEBQECBv/EADwRAQABAgIHBQYDBwQDAAAAAAABAgMEERITITFSkdEUQVFhsQUicYHB0jIz8EJTVJKh4fEjNHKyFTWC/9oADAMBAAIRAxEAPwD1n0ZOjJ0bb/0bdKL7fej5prcblccHsUuZMl4nAefkvuQGScdccJpSMyJVJSVVVVVVWulfcG6OFk1Hj6aB/R3Wy4TZrb8mHNi4tiCRJMVk2wdkCrswHBAVeb8E2xcXfkC7LVY6J30WNG/u/wAe/LmK5M5wXJsh1Zs1+tjKs21nEb7aHZ6Oinc8qS5EVnweJDXk04u4psnDzVFVNw1OzaQ9AvInJzNg0u0DuTlrnhap4Q7JZniiTiPgGM6gAvA8pooo2WxKqbbb1j7vpv0DrLn2P6aTNFdFu/2SOzGIUUccs3H10YANxogUUPj2NNhQVXx77VqOmOiuqdpgP2zIsby+bJiYwxhjD+RXLHkgACvBvIit2thqQ5Ga6vrgKQ4EjdRFGhIjcGl4/hec41kWnEV7FJtwi4tIu9snXVqbHJZDUhoCbuTqOvI6SuEKo6iITnWkSoJAvHQde+aK9BXGLvEsGS6S6EWm6XB5qPEhTrDZ2JEh13i6oG2zbQjI+A+FERVLgLbfZa45uj3QNt0u8wLhpboHFlY7GKbeGHrJZgct0cURSekCobsgiKm5HsibpzrXNVrFkuSav6j4vi+n3fuXk2D2iyJdEfjNhaOtfnKj8hHnANWUVEP9wLjnG2Pgf2kzN90lzJjG8zkwsWC7znc9tmUswFlMAd6jQ24O4iThdWDhLHNAR0gTjAeIgFeKg/svTH+j7t+NRMzn6e9HqNj89kpMW7PWmyBDkNCQiTjbyhwGKEYipIqoikieVK7MPR/oGXHvolv0u0Dk943WWbn1Nksx9wuPbdUD+wfuiPiThQtlLdNt96xeH6TZrMz4dQb7gyWeNc5eR3Ru1Pyozr1qOXGhMNC71TptK871Ehw+pIwRXF3JVXddKzPSXIMO0itUKXhMBqNBwjGMcfg9YwjBzW7syRxSQFVOFeJdyRFDwl2VaDap+J/0esNcUci6V6FXOLmd4csVqlwLHY3o7ssAMjDjRNi2Jvq1QOIkMwFU51Ruyd0WPRp0q9Tbd7mtJhYbqPcsvg6nPaZXCztTM4iXN2wHMt6zocRu1OwTlPq1IKMRKZiai06Z9UI8lP8Adp6LoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKCL9IrGMazTJdFsTzHHrZfrHcs+kBNtlziNyokkQxq9ugjjLiKBoLjbZpxIuxAJJzRFrJ9k7osejTpV6m273NNZP6xdCfvAmf6Vv1VWglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCVdk7osejTpV6m273NOyd0WPRp0q9Tbd7mqrSglXZO6LHo06Veptu9zTsndFj0adKvU23e5qq0oJV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaqtKCWdFR12R0XtHn33TcdcwHHzMzJVIiW3MKqqq+NVWqnUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEq6J30WNG/u/x78uYqq1Kuid9FjRv7v8AHvy5iqrQYIcmORf7vYbfbikHZ4TMh13rOESfd41BhOS8+EEJV8iGHJd60Sw6r6mXPBcgyi9aODYblZ7FFu0aBPvDoNSn3IqvPRie7k4m+qJOrUuqJVXxgPircLLYJtozrI7qMcSgX5qHK67jTiGS0CsmCjvvt1YMqionj49/JXczmxXLKMMveNWi5RLfMusB+E1Klwylssq4ChxGyLjauIiKvgo4O/10Gq2bU/IbZartK1YxCLZZlqjxppM4xKm5GLzD5m22gC3CZkG7xtkitiwXJRVCLdUHsXLXHTu1WK05FLk34o17N1mExHxm5vzDebRVcYKI3HJ9t9EE1Vk2xc8A/B8EttWj9HKz27R57TSw2XTWyyZr4SrkFswYY1huboqibSrWElCeAgEEISkbqoDuvCnBXPgmimVYDbsastnyrEItssF5m3MoVuw8oMdWpDLg9zx2m5nBHEDecJF4T3HgFUUkJwg2XJtZsJx2w229pLmzVvbBP25qLaZ8oiFB362QEaO67FYFVEXHnW0BtSRC2XYV60TW/FGrFid1yFm4xHMrt7U9s7fa51zgRRMQVSenMR1ZYaRTTZ19WhJN15Ii7Ye0aV6qY9ZLYzZNTsZavTPdMa4zHsSfcjSYbkhx4Baj93oTLwK4SI4TrgLuqq0vJB1PLeitdsrs2N2e45VhVzLH7S3bGpl9wZLlIjOMkStSoClLEYTyooI6aCamrYKPVbJsHoilcccXxYbGU4248gIjhtgoCRbc1QVVVFFXxIqrt9a+OuSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoJVrJ/WLoT94Ez/St+qq1KtZP6xdCfvAmf6Vv1VWgVq94zN+PcHbVj9oG5SIqoMlx2T3OwyaoioCmgmSnsqKqIKoiKm6pvtW0VNcf5s3FxeZFebpxL5V2mvCn+SIif4Vje1cTetVUWbNWjNWczOyZyjLZGcTG2ZjfE7InvnONDAWLd2aqrkZxGWz4/D4Mp8r80807J+OO/pafK/NPNOyfjjv6WlKytbjf4irlb+xp9mw37uOdX3HyvzTzTsn447+lp8r80807J+OO/paUprcb/ABFXK39h2bDfu451fcfK/NPNOyfjjv6WnyvzTzTsn447+lpSmtxv8RVyt/Ydmw37uOdX3HyvzTzTsn447+lp8r80807J+OO/paUprcb/ABFXK39h2bDfu451fc/qZvk0ZetuOIRSjjzPuC5k+8ieVUA2W0L+SFv9SLW2wJ0S5wmLjAfF+NJbF1pwfEQqm6LWo13tNuWKoCeILlcwFPqEZz6In8kRESrvs3F4jtXZ7tc1xNMztiImMppj9mI36XfHcp47DWqLWst05bYjv74nxmfBtFY+/X2345bHbrcjNGm1EBBseJx1wlQQbAf7RESoiJ9a1kK0fU/m9iTa8xO+rxJ5F2gSyT/IhRf5olfT2LcXbkUzuYl2uaKJqh11z7NnV442D2oG1+aMq+mDqJ/4hbjGKL/I1/nX8+XWe+Zdg9YXv0dfNK1ez2OCOc9WfrrvF6dH18us98y7B6wvfo6fLrPfMuwesL36OvmlOz2OCOc9TXXeL06Pr5dZ75l2D1he/R0+XWe+Zdg9YXv0dfNKdnscEc56muu8Xp0fXy6z3zLsHrC9+jp8us98y7B6wvfo6+aU7PY4I5z1Ndd4vTo+vl1nvmXYPWF79HT5dZ75l2D1he/R180p2exwRznqa67xenR9fLrPfMuwesL36Ony6z3zLsHrC9+jr5pTs9jgjnPU113i9Oj6+XWe+Zdg9YXv0dPl1nvmXYPWF79HXzSnZ7HBHOeprrvF6dGbxnNu/M4rLeLUVquiNq8211yPNSG0VEImnEQeLhVU3QhEk3Rdtl3raKlx+DmWHGPIiukhtVTyitvlqqfy3EV/wSqjWfjLVNquNDdMZ/1mPouYe5VcpnS7pKUpVRYKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5iqrUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEq6J30WNG/u/x78uYqq1Kuid9FjRv7v8e/LmKqtBJNbOk5ppoNcrXac1ltJKubZSeArzaoCsxhLhJ7/vCXHV5EXfwGOtc5fM5pv95X0ltOcQy7EMUuDquBmzDEi13AbnbWWzB4uFrhjvym5j/Eqj/u8d3h4kUuFN1TZ830+n5VLj3TH9R8mwq4tN9zvS7E3b3DlMoqkLbgToslvYSUlRRASTiJOLZVSsPlWix5RcSnjqnmdpGQzFGexb+9whOfjKhMyHCciGYkhCiqDZAyXNCbVCJFDsBqhkrmp8zTxvRfLXbfDGKZZI3NtPcCA8jn7wmymJKQEVtR5MkaqheAg8JFmbLltyueaZBjMvHLjbmbRHjOxnZIRlCeLiuorzJtSXC4N2+DgdaZNFBV8ISRU69607cu+XQ8qYzrJbW002y1OtdvcjNxbojJGbXXmrCyB4ScJdmXmkJPBNDFVFehG0uvsfUV/UBdZs0dZkCjR2E41n739QPWK2yhJASVwiTpEi9fxquyERJyoOrjGt8C/SXhvGB5Vi8IIsuUzPvAQ+pk9ylwyQbGPIdd4m1RV3IBE05tkac67GI6uP5Tlw4dL0uzPHpBwHbmMm7NwUjlGFwQbJCZlOFxOKSqgcPGCAvWi0pAh9WFogyx3kbuWpGWXWNaG7qy7HlhbRC4NzyVXAfVmIBIgIuwK0Ta7fOU151jNIdOdTcey+95NqVkr103ZW22dHLyzPIYXW8Y8Qs22CDKpsnJUfNd14niQR2CvUpSglXRO+ixo393+PflzFVWvL/Rk6MnRtv/AEbdKL7fej5prcblccHsUuZMl4nAefkvuQGScdccJpSMyJVJSVVVVVVWqX2Tuix6NOlXqbbvc0FVpUq7J3RY9GnSr1Nt3uadk7osejTpV6m273NA1k/rF0J+8CZ/pW/VVa8v6sdGTo227PNGIdv6PmmsWPdM4lRJzTOJwACUwmN3p5GnRRrYwR1lpxBLdONsC8YoqUvsndFj0adKvU23e5oNRs/Th0NLWDItB8+uz2BZlYbkcFuNkKgxHuTSqhMSI8hCVvhebNoxBxQNesREEtt63nHVQos8hVFRbzdVRU//ABz9ecbN/RYaF3LWDItWdTYNtuUSdcjes2JWG2hZ7Lb4YKgxwcaYVFecQAAjVFbAjJziA9969D4dAg2q0PWu1w2YkOHc7lHjx2G0BtloJrwiACnIRREREROSIlfP+1/9za/41+tDW9mfhr+X1Zl1HVaNGTEHFFeAiHiFC8iqiKm6fw3T+dQSBqPrHjWV5aOomZ4VPsGFdwLJZsuFzWJ1zKW0qtNMEd0dFs+tVsE4gNC358HjS+1Nck0XZyORm8lzIjjuZaVrkRiCIJLb5MBEJlzwiVHU6wQJRVB5Iqb890q0TEZxUv1xM7YYWV0iYDUm0JcbJMxZW72/a8jgX9ptZdvEYD0pshWK86yfWIDSirZuIqEo7IaKg5S+6+2fHnhauGC5YvcsNq5XtW2Iq94oTrhg0/LHujiVC6sy4GEdcERVTAaxLfR4dvsxq96mZTAyS6SLwd0uaM2ZYsN4O4HYTTDDBvukwgA5x8ZOOEp8SpwoqIOEyzonQsuvFuv94umI3m5s2+NaZ1yyPCIt4mlGjuGTLkVx9zhjyOBxRMzB5syRD6oV3RZIi1ntlH/q5NwLpAWD5aP4g3h+UnHi3sMckXxI0fvezPcjg+02u76PkJi6CIYtECEvCRCtY9rpAS4lqxOfI0xy29Rcp7kZj3a3Ba2GDffJU4EivXFZW4ChGaADiCAkXEqCW2ad0dEzlKGQIAycti5SgpDTwEZaab7n5Gic+q349k24tuHlWsY5ohqhh95s0yxao4rJgWW2x7XFYu+HyJL0Zof94WO61cWhbJ5UTcibMk4RTdRThrkavL/Lv+p+smyQdeManZAdoHHcgbtzj06JAvhssLAuMqGhrJjs8LqvCY9U7srrTYH1ZcBFy32TTjOmdScRgZpDxy72eFdGgkwm7ojCPPRzFCB3hZdcQRJC5ISoaeUUqXY50VMexnPJ+WWwMNYjyHrjLaeZwqIN8J+ZxqaP3RSI3WxJ01FAbac2QRJw0RUKv4ZjvyRxCyYp3Z3X3mt8eB1/V9X1vVNiHHw7rw78O+267b+Na816ER7r1RpzPvMxXe02/wCF1/5pdf8A379dGtNx/o9aBZzEl5Tmuh+n+QXqbdLj3TcbpjMKXKf4JjwBxuuNkZcIAIpuvIRRE5Ild9n/APsaP+Ff/a2hx/8Atf8A6j0qZvpC68WLo44E1qZluOXm548xco8K7P2tttw7Yw9xCMpwDIeJtHeqbVBXi3dRURdtq16NrXpTrlZsOyrSfOrXklv7+r1qxHf3scits1UF5ktnGSVOfC4Irtz2rS+kL0BNGtW8CawbT3T7TjTx2Zco7l0vdtwuF3eMBviM2orjYtk06bgsop8WyB1iKhb7L9af9ETQ/otQ8Wh6W404Fym3hWbhep73Xz5ojb5i7GeyCA7oi8DYgG6IvDvzr7HCfnR8/SXzWJ/Ln5erfNQcvbwPDLtlpwHJxW9jiZiNlwlIeIkBtpC2Xh4jIR325b7+StRcvetuI2i7XXMkxS9MNWabcWn7TAehDbZDLXGDDwOynSlAfhJ1rfVKnBzBOPcd2zLFLXnOLXPEr11qQ7pHJhw2S4XG9+YmBbLsYkiEK7clRFrS2tN9SrxFuELPdVotzYdtMu1w2bXYjt7e77fAsiWJSXu6XRT5vArLaKRrwbqKhqVZ57FCMu927vqFeoGm2K5izFhLNvkmxMyGyA+qEZrzAO8CcW6KiOlw7quyom+/llFw6R2SRswye1NapaXJMsWQu2mFgpW90shubQE2go2aXBF6xxDVRJIhCm26oqIqpv1p0o1Ldxe14fmuo2M3G32R+0vQSteKyID3/YX2nNnScuD4nxi0g+CIcKrvz+bW64RhnyN7/wD/AHl3Z38vkq9f7Hq+p67h/dfOXi24fnct9/Elecqp8nrOmGpa8ZJqrhtii5Lp9f8AFIcdJkG3yYt5x+TPMzlS2mEcBxmawgICO8SioFxKnzh3pb9Yzst4axDLIcy8yYk9mzXXJrRawh2ePcn0EmYysuy3ZIkQuMpxCjjaE4KEYqqom2aj4V+0DGkx3vn3BtcIE/rup63/AHaU1I4OHiH53VcO+/Li32XbZZ1dujJYJ+rTupsZjDUKZcmLvLdnYZFnXhJDTYAIR7i6W7DKo0CqPVGaLx8Dje6cPaoqic6XI0ZjKWQznXoLTg+U3nDsYuFzv2NWuRMm25xI6Lbn2y4RZlIT4IilsRogGqE2CkJKhBx1K0zH7ha4k+VAegvSGAdcjPKCuMkQoqgStkQKqKuy8JEnLkq+OptdNFJ9yseQW5c2cGZl1tlRL7IOGRtyJLg7MyGmld/co0i8CAhLxNoAkW4odUm1sTo1tixrnKYky2mQB95hhWW3HEREIhbIzUBVd1QVIlTxbr467TpZ7XJyy2IBgGu95zHN3bFP150dtklvIZ1qHEDtxrfHGWJLjYihrdEXrTAENF7mVPC+aqVu2OdIbGMgvCQXcWya0210ro3HvVxjxwhSHLc4YSgHgeJ4eHqzJCNsQIUVRJfFXHhmm2rmDTX4Fp1KxB7Gn7zMuhQ5OHyinI3Jkm+40kobkLfEiuKiH1G3i3Fa5w0NhO2u0Wa5X4pES3O3wnxGKgFIbuXXIQIqkvAoI+vPYt+HxJvXmmK4h6nRfY66xBsbt6maa5tEN5+KxZ4bsSKr177pVeoKMQyFaDiQSJRkOMm2KcTggioq9ST0g7S9Z4rlhwrKJ9+krObfsYRo/ddrKGojJKVxPi1wtkbaKjTjhOIYq0jm6VxFo/qLMx+Nbrvqvb359gkwpONPM4yLUaGUZCESlNLIJySbgGQOKDzAqmygDa7qvXj6D5NbkiX20ajRGstecuK3q6yLF10eY1ONsnwYjdeKx+DqWkaUnHUFA8NHd13e+e6+bL0joY4zjtyv2K3ia/JsVuvGRzbRHaWDYhlgnATwuvI8oKXGuzIvEIgpHwpsq71h+okfNL5kFpt2NXmPFx6a5bnbnJ7nGLJkguxtsoLxOrwookpE2IqhJsqqhIkmufRBsU6dj81ZGHznbdaLbZ58u+4TFu00ghpsDkF14+GGZIpISGD4fNVBFUVSsmF4imIM3dkZ/dSXS8S7qn7nq+q64uLq/Gu/Dttvy3+pK7Tp5+85Vo9zKO/8YYZ/zd/8tmVUai2Y4biGe3XEsZzrFLPkdnkXlw3rfdoLUyM4QW+YQqTTokKqhIioqpyVEWsp2Tuix6NOlXqbbvc1S9ofjp+H1lawf4avj9IVWlSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1QW1VpUq7J3RY9GnSr1Nt3uadk7osejTpV6m273NBVaVBcF0z030y6Tc236b6fY1ikWZgjb0lix2liA284lwJEMxZAUIkTluvPar1QKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5iqrUq6J30WNG/u/x78uYqq0ClKUClKUClKUClKUClKUClKUClKUClKUEo6KKGvRV0dRshE10+x7hUk3RF72sbbpum/+dNLNZ7nlrMCDk+IXiK4+/KtyZAFvCLaZ06O86240w0chyU3/ALA1QnQ6ottgdc3Hd0URU+iro6AmQKWn2PIhDtuP/drHNN90/wA0ruYbogziMiMMjUvM7/bIZPPx7VdXoSx25TqmrklDZjNvcSq65sCuKyHF4DYcIcIYHHelXgWUBmrdos9ykz8ERxy426HcrRPlmy26TZuI3Emu9SqKJL1UhWXl4VRG1JFSmS6+3JvHbtcbTh16x+VZEYlvFfLa1LCSy3Laamx2WocpXVfAXBRC2UOJwOBHuEwTtNdHZxq3zrYutWoDjEm0OWKMJDZ9oEIiBRbZFICCqggcKG4hmqEvGRqgKPcyPQOFkseRFk6k5lECW884+sM4DRmDr0d5xtD7lUgRXIyLxAouD1rnCY7BwBkGtYUS0zH7hpxl1vvkdxtuNjj7cIrhP61T6lWSbklG2PqnV3N8OrQCV3q9q/j2sRDZGpEPTLMJmRnIKM5ijSQBuTBgAOGpuOShhIAg42XGklQXjERUjXhr+s6QPpaZjM7VHL59+kONuRskkBbUuEFG+PqwZAIYxeEUdeTw2DUkcLjUuW3y5o/MWyNRourGYxciGQch3KmmbUtyf4wACA2zhFD4FBpodhjj/sxVNi3JQ3HGsgiZPZmLxEjyYyOKbbseU2gPR3mzUHGnBRVRCExIV2VRXbcVVFRV17MtTCxS6BbIGBZRkysthIub1mbiqFrjkqojzySH2ic5Aa9XHF53YPmeEKFsGM2BnGLKxZ2p8qcTam49Ml9X18p4zU3HnOrEA4iMiJUARFN9hFERETXcy00m5Vd27tadSspxRHGRj3GPZgtxNXJoVVRF5ZcV8w2QjFCZJotjXdVVAUQx9y1qiW7JlsYYDlUu3NLCKRf2Bhd7mGZfJh4lKSLxCp7gqA0RiqcRCgKJrR6nGQ6Lt30b6xG1Gyqzxb03bGW40Bu29XbwhHxAkfrojheH4j61XOXzODx1RQFQAQIyNRREUi23L+K7bJ/klBLOid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtAqa49/u9wT6rzdf/AHz9UqtOuuJ3qLcZNxxdyC43NcV5+HMcNoRdVNiMHAElHi23UVFee6oqbrWJ7XsXKqrd+3TNUU5xMRvynKc8u/LR3Rt2tL2feotzVRXOWeX9P8uKlcPenUH7Ex78Zf8A0tO9OoP2Jj34y/8ApaydOrgr/kr+1p6y3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5qVw96dQfsTHvxl/8AS0706g/YmPfjL/6WmnVwV/yV/aay3xRzjq5q72m3/Cyr9d0uip/8+/WLSwZ9LXqHWrFbgLkUhqY7KME+sWyZbRV+rctv4L4q2+z2mHY7ZHtMASRiMHAKmW5EvjUiXykqqqqvlVVq77MsXa8XGImmYpimqNsTGczNM7Inbs0ds5d8ZZ7cqWPv25s6umc5mYnZt3RPV3a0fU//AHjEF8iX4/y6ZW8ViMoxyPk9qW3uyDjPNuBIiyW0RSYeBdxNEXkqeNFRfGKqnlr6rD1xbuRVVuYd6ia6JphqdK4Vx7UxlerS24xLQeXXd9JEfj/j1fcznD/LjL+dO8Wpv2DjH47I/R1r623xRzhnauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4c1dfhPJzUrh7xam/YOMfjsj9HTvFqb9g4x+OyP0dNZb4o5wauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4NXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5OalcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLfFHODV1+E8nNSuHvFqb9g4x+OyP0dO8Wpv2DjH47I/R01lvijnBq6/CeTruf8Y4an/wB7vr/h3umVUa03GMNujF2byLKZEQ5cZs24cWGpEzH49kM1MkQnDVE4d+EURN0ROarXX1b1RkaTY8eUFpvlGU2+M249OcsbluFYbYInhODMlx1JF35I3xryXdE5b52NuU3K40ZzyjL+sz9V7DUVUUzpd8t6pWjWDWDE7hLg4/lL8fDMsuDDstrFb7eLb327mBS3f6qLJeEm1EFLiEyRE+dwqionXzXXvSbA7fa7jes3s5t3mTbY8IWLjHInQnPI1HfFFcTdlV4i403RRA1TfbaqawoNKwGWZ/gmBBAcznNbDjo3SSMKCV2uTMRJUgvE011pDxmvkEd1/hXRvOr2k+OXGTZ8h1PxK1z4bD8mRFmXuMw8yyyKG84YGaEIgJCREqbCioq7ItBq4/Snc+78PzEqqtR8tQ9E2NZ4l8l5/Ai3e741Ag2l6RdYgW66x5Ul5xkYhKXE++pMmuwKqKKjsi89qDF1BwKdk9wwmFm9gkZFaWEkz7Q1cmTmxGV2VHHWELrGxXiTwiRE5p9dBn6VOLd0h9Gbm1fbnH1Fx1LDjzMV6ZkJXeJ3pTr3HWxBJSOKHEJsmJIu2xKic13ROaza56e3a3t3mVeItntjrclwZt0uEOO3szLKKvJXuPYnBVRNB4FRURSQl4aCg0rUn9XdJ4tgtGVSdT8SZsl/dRi03Jy9Rhi3B1UJUCO6p8DpbAS7Aqr4K/UtbPElxZ8VmdBktSY0lsXWXmjQwcAk3EhJOSoqKioqclRaDmpSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlBKuid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQSronfRY0b+7/Hvy5islrVqdetL8di3PHMWayO5S5SMhb1W4qZNoKqbghboE6QfCvCi7MKKcSKRCnjxvRO+ixo393+PflzFbhmunmMagMxGciG6gUFxXGHrZeZlsfHi24gV2I604TZbDxNkSgXCnEK7JQd/EshYy7F7TlMaK/Gau0JmaDL4qLjaOAhcJIvNFTfZUVEWtUzDONSbNmcfFsU00tt+YmwFnMTHci7j6vqnQGQDzaxyUfAcDqlBXOM1UTRgU62tsxbGLLhmPwsXx2O7Htlub6mKy5JdfVptF5AhukRcKeJEVdhRERNkRErA3zSTEMhzWFqBcJWUt3iA2LTHcWW3aHE4BJC4ShsSQjOISiKkhNqh8I8XFslBjblqPmEW+PyIeBwXsRt0oINyuTt7Vu4NvEoIRMw0YJt1oFcRCIpDZ+A5wAewcfLCzfUa65MCWrTa3ScPOa7A76/KHguDZtOk268UIo/V9QhNnsoyCcXwf3SIqqPeuOkuF3XMGs4mJfUuDbjbyx2cjuLNuedbREBx23tvpEeNNh8Nxoi3AF33Adv4mkOC/LBc3KFdHJ/Xd0jGcvc47aEjffugbcTywxe33LrUZQ+JVLi3VVoNYwTUbIbxl1w0/tEqNlJY/dZzOSXSbMSK/amiMihsC3HidS+8oKP7tSaIWkEzUlMePI4Jius9tfuK57qczdmpNoajRO54UdtY09HpKuSU2YDfdo4ooJcQ7tL4Kbqp5ez6SYTYckj5da2Lw3dI7clrrTv8APcB4H3SdNHmjeVt/YzNQ6wS6vfYOFOVbXOalPQpDMGSEeS40YsvG31gtmqLwko7pxIi7Ltum+3jTx0GF0+yCXlOE2XILgyjUubDbOSA7cKPImzm2yry4kXb+G1bDWLxbHoWJ45bMZtyqse2RW4rZEiIRoAonEu3lVd1X+KrWUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKCVayf1i6E/eBM/0rfqqtSrWT+sXQn7wJn+lb9VVoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFanqxidxzvTfIsPtD0Zmbd4DkVhySRC0Jl4lJRQlRP5ItbZSg873jo75S/rTLz2M3AuNsm3aFfwcl5jfIfckmNHbaFlLXGJIUhSVkVSS4vECGqK06goi/T+gmocayyoECVjch6dklmywxdkusBGkR5oPPwGVBglSKLYJ1KqnEhke4ohbj6GrikjJOO4MN1tp9RVGzdbVwBLbkqihCpJ/BFT+aUEL6Q+g2SapZNachsIRZ7LdomWKdb5eXXnH2xZkG2av8drJClInVqJRneETRU2cbVOf8sXR2udov3fJzvA6ymexsp4v3pOHGZs4QhResEiV5DBFTiMthRFVxS5VvsfIswOwXHIZF8sYQmHdocgbM8SyWx3FVRrurfc3NhDwue2+3hJtk4GVXCAlvs2UQlO+zGwcFqCygtObkvGgcbi/wCzFEU1VfKm2+6JQQFzo06pw4T1ltqYVIh3rEYuI3CTImyW3reAzJT7kiMAxiR5UF5rhbImkIx3Ux4E4/pvok5I/fMmizbmyFvuSX5yBe/lbe5EgDuQuIoJaCcGBGUOuJCeBXFcEfmNkSklxn51doFzvrD9hBuNaY7Kx0J9CdlPvOKDSLw7i2JKniXctlRV4fFXGWSZex3ygTJ2PsuWtxoplzOO6MaO0bKuLu0ru5kiog79YnI0XbltQSQNE9Znr+1qNItGBR79a5VnkQ7HGvUvvfL7jjy4xdfKWEhtqoSQcAkYPgUEDYkFHF67fRjzs4+LNypuMCtmlsyZTTLjyNbDkA3JRaFWvEjQqKIu3h7J4vCqwys/u4WyzMR7S0l6nnEGW06hC1FF1xB3VFVCQiTiIQVd0QSUvm7LvdB5I1WwnLdMTG9WuwDk0y+HltuG1xLPdZzIRrrIB8HuOHCfBt4VAAIH1ZaNDL9+KAqr6YwC1TbFgmN2S5No3Lt1ohxJAISEguNsgJJunJeaLzrP0oFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBUAtPT06J98UEtWqqyCPkIpYrkhL/gsdFq/1+KesOmTFhuS5tYIb7LLRo7KbibgYIvjcHby7eNPFy3+uvFVyKaopnvS2qIrzzfqyPSf0MJpH0zlOrJN+JbbMRNtt+f7rl/jXXl9K3QOCglKzsgEh4kLvTOVNvr3RmvzJxjJX7vDALfd5T4x1QJA9UO7gKiEi7bfxTyb8lTmlby9Ig3Fhph9t3q1bIUJxODfZF2Th2TZE2qOq5VS96ql7yd6YnRyZRCc1DLYvFtZp6/+jFcLXTQ6NLy7BqWKLvw7HaJ4899tubCV+e9utse42xqSsVxVFCBD2Qd9uSePy10XcOOQSvuQkUvGgntsi+LxpXnXVGqpfpH2tuj2qbpqEKptvytc1eX/APRr4Tpd9HgjUB1C3UfH/wB0ztk/x6mvzU+S9yaf4Y7LzCN/N4D4h2/j9X+VdOfFvcM90kSAEt+IwDkqfz8tNdUaql+ojXSj0JfTiaztCTbf/wCGzPdV8F0qtAwRVPPwFEXZeK3S0/8A8VfmTAlZIor3FJMt/GpIm3+PKvgmsk7sKNIktjxoi8PB5f5qnlrutk1VL9Nu1f0f1321CaXblyt0v3VczHSi0JkpuxnYl5P/AIdL91X5oRMGkvL3ZcpchOXgg0fV7/zJOf8AlWSkQQtzCjHcICTmiC8fF/nvuvLbx7010uaul+ko9JPRUy4QzTiXx8rdLXb+f7rlWzYZqVhWoRzgw+8rPW3dV3T/ANleaQOs4uDZXAFC34C+bvttz25V+UjmcyokYo6qSvt7Kil4XLf/ANa9h/0fGQO32LnHXSBdNpbYRbBw8JF3Vun1L82vVNdUzGe55qppiM4WrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaVMiSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaUEq7J3RY9GnSr1Nt3uadk7osejTpV6m273NVWlBKuyd0WPRp0q9Tbd7mnZO6LHo06Veptu9zVVpQSrsndFj0adKvU23e5p2Tuix6NOlXqbbvc1VaUHUtNptVgtUKxWK2RLdbbdHbiQ4cRkWWIzDYoLbTbYoggAiiCgoiIiIiJXbpSgUpSglWsn9YuhP3gTP9K36qrUq1k/rF0J+8CZ/pW/VVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBXBPiJPgyIJPOspIaJpXGlRDBCRU3FVRURU35cq56UGJexq3PQbZbd3QjWp1l1loFREJWk2BC5c0Rdl5bcxSuBzEYT1/HJXZ845zZj1JKYcLLSCqEyKcPzC33LfclVBXfwR2ztKDTZrOIz8pu2FneJbd+usNi9ONNiqGyw0aNNOtmoKCbOB81VJd91VNlr7k6cRZAxjTJby3IYlHOckf9mcKTIJERHHBcZJvcEREDhEUHbkiLzrWR+lO5934fmJVVaDXZ+AYrdbhBu9ztESVcITgPLLcis9bIMQURVwkBN9t0LZNkRRTZNk2rYqUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFcE2bEtsR6fPktx40cFcddcLhEBTmqqq0mzYltiPT58luPGjgrjrrhcIgKc1VVWpZdrtLziWEuW05Hssc0chQnE4SkEnzX3h/wCoAvi+cXhbIMF69FrZG2ZQ3bsW483ze7nNz17rpSyoVlaXeFGEyZeeLySHFTZRVPGAf2eRL4WyDseG5lJSS3i+UPoU0kVIU1UQRnCib8JbcheRPGniJE4h8qDha68+BGuUYoksFICVCRRJRICRdxISTmJIuyoqc0VKpRNdFWnE7e/z/Xd4c86VNyqmrTz2q3StGw3MpKSmsWyl9CmkipBmqiCM4UTfhLyC8ieNPESJxD5UHea0LV2m7TpU/wCGhRXFyM4KUpUj2UpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFflga3W5icQYjqtOookrgcPJeW3Ov1Pr84QefZbRt+ISOKiqipsibfXvVHGzlo5eazh4zzef2JcmwXSRjc4I0V+E4Iw5UdVQlaVeSKm3PbxKm/k3qmq4l1iEYNCb7OyGyvJd9vHt5UXmqc+dYzLdOrHkTTt0ZmdVMZEndwXZxPq5bfwSufErQ53EiXO+Ic2MAgygRl6x0eHfYiQttk3Xmqf+teaLsVxlO9NXR3viJFOA2IE+/wBSZeC2Qc2yXn5FX+W1ZmI3LbhgicXAm6o2Xi3T6/8ApXbm2ia8rbTLgoKKJoaeF4XPbbmm/wDnXK/EvdsBmPOWOCGm6iCbcv4/Uq/Xzrszmif2KEY4pPSm0aRUXYQTbnWsy91ccZjkHCi7LvzXby/47fzrISbi6GzEx4AH+ym/+WyVE3+kKruo0bB8dxCVcm35SQx7k3KQ66pbbA2KLxc/J41r3bt1XM9FyZyVcmZFtQ223TAC3JCRF5/41in7XBfLrnQ64n0RXFd8Zfwr1BjHRB1Fya0sTb7Pg4+TgoaR3xV10d/7wguyL/Deunm3QgzyDZplwsGWxLjIjMOOjFjxFB99RFVRsEM+HiJUREVSREVaRRVPc5px4oBAHuxeCHG4mGgRslEd9/4In8K/t7ZYgti660fGaoOwJxKP1b/VXjWLrTrzp1k861XkZsF6NKUZ9qnQ1bQDRdlAhVEUa9T47mLWd2aBcnmShFKji/1Zbqgrvso7+PbfxfwqS5YqtRFU7nYnPa+bvY48w1cYFsfBTiUvGqrXrb+jmhJC/aG2hKqKtq238n+9/wCfj8deUbvb2/BZJ0/Gu5NmqL//AMr1t/R4susrqALpCX/wrYkXdV/3vx1233PFe6XselKVOhKUpQKUpQKUpQKUpQKUpQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlApSlBKh+lO5934fmJVVah+bZZF096RDOU3/AB3MJVpmYWlvbl2PErpem0kDOI1bNYEd7qy4VRfD23TxVm+0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1eTtZ/wCk+k6AdIRzFcmwW7X3Ty9W+LcoCu45cbBe7YioTTwqzcWmu7AVxkzEkFsf3ij1iqCige3smzbvNOGy2e1FdboraPONdcjLUdtVVBJ1xULh4lRdkESJdlXbZN6w3y6z3zLsHrC9+jrUcCzK1ailes5ssa4sQbxKivxm7jCciSQaW3RCQTacRCHmRL4tl33RVRUVcbedQspx7OrXZbvhUNnG71cEtEG5jeUKc5LVk3UJYSNcKMbNmnH16uIqbq0g+EmvRh7NNFOlTnMxHj3x5Szq71yqqdGcss/DuUD5dZ75l2D1he/R0+XWe+Zdg9YXv0dRTF9cs8uNptebZPppZbbht2uCW9ufb8lemzo5HJWOy4/FOEyAtq5woSg8ajxIuxJuqUO/Zn3kzTFsQ729d8pe7v8AtHXcPc/c7SOfN4V4+Lfbxpt4+deos4eYz0P+3V5m5ejZpenRtHy6z3zLsHrC9+jp8us98y7B6wvfo6j2adIVrCn5SysPkS4kDLG8clusStzajrBGY7N4ODckbBS3bRd1QFVF/s1s0zVvHbRPycb6vcltxtm3upMa45JzFliStttMNArhmqiIiAIZGpIgpvsitVh+GOc9XdZe4vTo3v5dZ75l2D1he/R0+XWe+Zdg9YXv0dTCNr9h90yrEcbsMW7zwysrg0kjvROaWA9FQVNqSBsIsc9y2UXlbIeSqnhJWyZVqdheF3SJZ8huchmTMQS/cW+TJbjNkXCLslxlsgitKW4o48QAqoqIvJdmpw/DHOermsvcU8o6Nr+XWe+Zdg9YXv0dPl1nvmXYPWF79HWkRNZdOZ2TzMQjX50rjC7oQyK3yRiumwm77TMom0YfdbT57bRkYbLxCmy7Y6w9IfSDJLbPvFryt1INutrV4ckS7XMiNvQneTciOTzQJJbJdhQmeNFJUH5yolNTh+GOc9TWXuKeUdFI+XWe+Zdg9YXv0dPl1nvmXYPWF79HUds/SPx6+Tru1DtUhqNa7ottFJUeazOkKlrSeqJCWL1zbqD4PVOICqicSKqqIF2cT6ROIX3Dxze9oVkgpZLbd3Y7seaUwClk4AMjHKMBuqRt8DSNcZuqvIE3DjarDT+zHOerusv+M8o6Kz8us98y7B6wvfo6fLrPfMuwesL36OptM6QmlMGzwr07ebq4FwKS2xEj49cX56OR1FH2zhtsFIbNvjFSA2xIR3JU4UVU5brr9pTZwtT8jIpUmPeIke4MSbfaJs1hmK+SCy/JdYaMIjZqvI31bFeE+fgFs1WH4Y5z1c1l7xnlHRRUz7Nml45OD2o20+cMW+mbqp/4RcjAKr/M0/nW4WG+2/I7Y1dbaZq04pAQODwuNOCqibZj/ZISRUVPrSpXieXJlE3I4aQO5ksF3K18fW8fX7MNO9ZtsnD/ALXbbn83ffnsm2aYcnstbTkIX1OFPIm8CIS/5kSr/NVqHE2LUW5rojLL4/VNYu1zXo1Tnm3ilKVmLpXBNmxLbEenz5LceNHBXHXXC4RAU5qqqtJs2JbYj0+fJbjxo4K4664XCICnNVVall2u0vOJgS5bTkeyxzRyFCcThKQSfNfeT/qAL4vnL4WyDBevRa2RtmUN27FuPMu12l5xLCXLacj2WOaOQoTicJSCT5r7w/8AUAXxfOLwtkHnpSqUROec7ZlQmZmc53lKUro68+BGuUYoksFICVCRRJRICRdxISTmJIuyoqc0VK2LDcykpJaxbKX0KaSKkKaqIIzhRN+EtuQvInjHxEicQ+VBwtdefAjXKMUSWCkBKhIokokBIu4kJJzEkXZUVOaKlciaqKtOjf6/r+jtFc250qVbpWjYbmUlJLWLZS+hTSRUhTVRBGcKJvwrtyF5E8Y+IkTiHyoO81oWrtN2nSp/w0aK4uRnBSlKke2lZXqZDwfLbdaMsiMW+w3aK+UW+OS0RsZjIE65GeBRTq92ANwD4lQurcRUFUHj12z9IrDustUDNWJWN3K+ONlEjHElyW4zEhxRhLPkgx3PAefREUWXnBVSJBFTKs/rNp4OqOCuYkUS2SeO5W2bwXFvjZ2jTGXy5cJeFwtkg8vGqc08dS3Ufo33/KNVJuU2tuJMs+RSbbIuSy8zvttGEsTgRUS2QHAjXHiFsVFX3GlAvH1gogIFZsmrmCZFmEzBrTcZ7t0hG82RuWiY1CecaVEebYmG0kaQbarsYNOGQKhISIoltuNSDCdNs+sOq10yWQ3arTYH3ZTpBa8iuL7V060lIFO0vh3Lb3RJVM3o7hE8e5FwoailfoFKUoFda43GFaIEi6XKSEeLFbJ550/EACm6rXZrT9V/+DSD+y5dLS2SfWJXCOhIv8FRVT/GpLNEXLlNE98xDxcq0KJq8IY8tQsslL11qweIMYubffK7FGfVPIqttsOoP8lLdPKiLyr+fLrPfMuwesL36Ovmla/Z7HB/WerO113i9Oj6+XWe+Zdg9YXv0dPl1nvmXYPWF79HXzSnZ7HBHOeprrvF6dH18us98y7B6wvfo6fLrPfMuwesL36OvmlOz2OCOc9TXXeL06Pr5dZ75l2D1he/R0+XWe+Zdg9YXv0dfNKdnscEc56muu8Xp0fXy6z3zLsHrC9+jp8us98y7B6wvfo6+aU7PY4I5z1Ndd4vTo+vl1nvmXYPWF79HT5dZ75l2D1he/R180p2exwRznqa67xenR9fLrPfMuwesL36Ony6z3zLsHrC9+jr5pTs9jgjnPU113i9OjJ2LPZEq5s2bJbINqky1UYjrMrumM+aIpK2hqAEJ8KKqIQIioi7KqptW4VJck8Fm1uDyIL7aOFfKm89gV/zElT+SrVaqhjLNFuYmiMs1vDXKq4mKu5Kuid9FjRv7v8AHvy5iqrUq6J30WNG/u/x78uYqq1TWSlKUCvzalCc0HSbeURRPmKvDxJ9e3j5/V9VfpLX5qXBIxl3WDxNOCKqIkXLdP4VRxkTOj81nDzlm6+Rx+rZCQCohMx1420Hxpty3/j461mCTrrgvvojYgibEhbeTZE/9ay825q62UaUaIsgNhe8a8X1LWs2uNNauT8YpEVGhPdONtURPrTdEqvRRknmrY3RrJVeALeDrZup/syAuSJ/NPF/jX9ekKLqbg7JdNOSCm6Kn/iVa/lujRS4BigAEabO9WRIK/Wv+O1dS63KbZ5bkyK0j0Ydh3PchTbyKmy1NlmhmYfU6NAjtnJkIAPEm3ETfNf8fHtW26Taf6MdE3Bsg6XeWQg763Fs4mPMSVV7jkHxbdSKDxAThCqKXkASXdEVd5qWbddxhIFl1xV38BPB4V8SpvzretQbu1r1oJB07fyW52dzFyQ32oaqrEthF8DrB+aSpy2RV8abpXucRThaZrrzy73mqM4c2A/0vmHxbFbIGpOBX2Xd0bXvhOtjbQMqakqp1bRFvsg7JzXmqKvLetThf0r17sGp9xkPlGy7A58lTiRnLeltuNvaVd0BFRSBxRTkvERcW2+4+KvBWra5Bgl5Wzx7eg2/ZFamE2hE+KpvzVOQr/BK0tu62+5W9tpxpW5QLzJPESfXWrY1WIo1lG6XimKaozh+9OXz9BdaLBZ8gBqwz7hchYlg24DJyDbVEVQdRN99kX+OypXlfXrQeRpvlEe74kKfJ+7ATkcN/wDYHvuTX8UTxp/Bf4V+cOD6g5rp/cRueI5LMgPInCqsuqO47+JfIqV6QhdPvWqfjCYjnTtoyW1HwqKTYTYSWVTxG261wrxeP5yLui7LVK57Pu62a6ZjLwS6WjTot37qmSXCCbHJpxteAwLku6eXf6lr2B/R6vEbuoDKsmIgNpITL+1uszl/NNv+qV5bsFwsuf49GyKwGrgvD4S+JQLygW3iVF5V656BsV+OzmqyWuqdLvchBui8OyytuaeXZf8A651FbmYr0ZeK9tOb1hSlKtIClKUClKUClKUClKUClKUClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQK6ki7WuHOiWuXcorE24dZ3JGceEXZHVjxH1YKu58KLuuyLsnNa7dRvWm8piepmmWZT7Fks+021y8NTHbHjs+8OR1diiLam1CZdcFCJFTiUdt/LQWSurbrpbbvHWXabjGmsC64yrsd4XARxs1BwNxVU4hMSFU8aKiovNK8w6g3/OMr1txS74ZbNQ7Zb40qzSlJYGSNRbhbJBoMjrWOJiBENriVHG5TL8lULi4WRbRxJdLw/WTHsetWOY3ddRcYtke65G7HKNYMhvEly7ndnTjqXc0+KqMmwTZA5MJyASqaubLxKoe+CIRFSJURETdVXxIlde3XK3XiBHutonxp0KW2L0eTGdF1p0FTdCAxVUJFTxKi7V5oZxTVCblj14us/P3HZeX3CzyGUuE5mCtmctBEhDGBxWGw7qQeB8dyE14Qc2XZdC03sOpWKXDRWFbYupZMxbVa7dLskoL/AA2IXBxpLfckbuwHBHwxKPcGW3CFAKM+OzYkHt2lKUClKUClKUClKUClKUClKUCpqPRz0cc1cuGulzwyNds1nJHBu53IilLABloW2xiNuKoRuQqSk2KEpGaqq77VSq1+2ag4NestvGBWrLbVKyTH0aK6WhuUCzIgutg42Ztb8aAQOAqFtwrvtvuipQac1/xhmf8Azdj8th1N8twDOMs1Fs12fs+IRYNjnty4eTxpDwXxqKhCTtvFlWFFG3VHhccSSgkK82d0RapDX/GGZ/8AN2Py2HXereiM6KfhHpDJmcqqvjPqgeL6W6xnidm0qyu34hbsZtd0Ce/drbfpUudMaZmrKbaSKcJkGeMkBCLrnOFEJEEt0VKBqViuW3G641meCsWaZesYkSCCBd5bsSNLZkNdU4KyGmniaIfBNF6lxF4eHYeLiHfKUiiIjI0pzzR+1aS5dInW3I8nfsqXGTlzuS3eFGecejMNFbjhjGYcNoSf2Tq1UzBvi3NeEeQrqU7oy5P3my6xxshiyoLlztE/FY6XCXbXWY0HiIIT8qLs8yiKRNg+zuQggKokoqh+jawWdZjatPcOvGcXxqW7b7HDcmyQiNda8TYJuSAG6cRbeJPLXJopy2uxXV3JZgOjmY4hdcYvvcFladYulzlXmOeTXS5uNtSmG2wMJk4XHpjo9Q2i8SRwVC5InD4e0X7FdR7bqNMzDARxuQxkFvhW64FeJUhsrekZx4keZaabJJPEL5IrauMbKCLxrvsmPjdIa2nekstx02zW2E1dItrmyJTEJWYRS+FIjrhNyTUgeUxREaQzDf8Aeg0nOuzF6QWIOXiVDuVlv9qtDZT24V/mx2kgXJyEJFKBhAdJ9FBG3dutabQ0aNW1NERV8xoxGUO+81uFo1qB31iWOfKx5MYsN3u99ts1qS+dwlvzRkILDzJNcDINrLd3cF1xTQQ8AOdf2foPksvHrLbGLlamJFkxCBaGVQ3eqK4RJUeQ3uiCi9QRR+El5FsXza5YHSNfLIrwF+wS+2a1RbNa5tthzIjSXG4SZst9hkG1B82dnFBrhEyAgU163q08Wft2sTt+vWM2uBYJdoen3qZZr1bru0HdkB1mGchB4mHTZVVRGiQgNwFA+S7+JEUSZ1Netmk+pV0y1czy5zGYT8m/99nIdulyJAR2e8xQerR1xltXj60uPiUG04V8W6eFiLZoTqTCxxhSk403e7RbMbZgMpOkORZEm0yHj4XXOpE223QME4kA1AlJeE+BOL0NSvWrhzTlG8O0jzGHmreoWUO2Nq4XB+7TbhDgvuPMxXZLMRhltl02gJ5BbieG4Qtqqmuw7ck0XJOjlqzeMatOIjkEJ6BBxeHam0DLLrbo8OazxK6Sw4oI1cG3v3Y7yFTq0Hk2aKoL6epSbcTsNOWqYLilyxmdlUq4PRjG+XsrlHRkiJQaWMw1se4psXE0Xi3TZU5+RN20x/3nL/8Anwfl0KupWq2bRDRfUq/ZVfdRtIcKyq5MXZqI1MvdgiTn22BgRSFoXHmyJAQjMkFF23Ml8q1HiYys1frvh7sTndj9dzetY9SW9HtMr/qfJxm6X+JjcdJ02Da0bWSsQTHr3QRwhFeqaVx1UUk3RtU33rW9G+lNoLrzjj2S6a6jWye3DYWRPhvOdzzYIJ41eYPYxFF3Tj2UFVF4SKtO1j6EuiGcaZX/AA/TrR7SnEL/AHmOMKPffkNBdO3tmYi860IABdajKudWSGKiagW6bVjuj1/R69HXo725xyx2KTfMjlRijScgurvHKQS+cjAjsEdN/EoJx7bIRltWLVnlOjvaM55bHV1+1ov1tivXSBZ3pUSJZLpebXZziiqzXYgtEL8gjfaQBFXEJGtlVURV3E0Ea/pa8WuJOhQLhhuSCHFAjXme03Fci2SXLEFYYk8MhXCIlcbRVYB4A6wVMhTdU7eqWktwvl2bsuQZAUZt2yXazQJ3cqGkoZjbY7l4QoLzaNbqHicRdxUdlRNNufRegXjN4Ob3J7CJc4zt8i6y5mDxpk8n4oAKdwSZDrncTZo2O4ED6j4SgYEvEmdRlOc1fi7/AI/rczs4mff397bImtiXa1zrpj2lubXgYt2es8ZuIxCQpz7LrjbxATkkQabBWiVTkEyhbigcREg1qt66Rs8X4Ey0YddI1ml4tdr3KlS4sZ123yobzbRMuNDMBTUCUwJG1USJQ4XOHiJMjlfR8TI8MtuJnerJNbt+Qzb4ca/WDvlbJiSXXz6mRD69tHOr7o3AlPkbYlw/2a6AdGd2FilvxC0ZjDiQ4dlvViNEsiIKx5zwuh1QNvADStKAJsiKJCioghy29xokaLbJGt9gh5YuNP49f1hNTY9pkZAkdlLazcXgE24hbu9fxkhtpxi0TSE4Iq4hbonxp/rnZtQb3EtEXDsntDV0t7tytU65MRhj3Blkwbe6vqn3DAgJwUVHQDi8YcQ866ErRG7SMpffDN2m8Tm3mLkcuzpav+1HcWBbQeGWjqILCky0ZNqyRqQl+9QS4UzGI6UfJaRhz/f7ur5J2WXZ9u5eDunrzYLrPnrwbdR83wt+Lxptz57uTnu5Mrc9SsfsF+7w5LFutn6w0GPcJUBxba8i8KCqzAQmGVUy6sQfNtwiTYRVFFS6uoOqDGBTrZaGcOyHJbneGpL0SFZgjdYQsICuqpSX2Wx2E+JNyTfhVE3JREsFdtBIlz1NTVNrUbKItzbdE47HcloltxG+AQcYjuyoLsmO04g+GDTwIqkRJsq71t95w/vtmFmyvvj1XeiFPh9z9Txdb3T1XhcXEnDw9V4tl34vGm3PmxzYnOb6h5fd8Si6hYm3ZGMOW2Q7m0xdoby3G8Pvlu3Fik2+Cw3x8ARIm3lJ1wUEU4dy9AYRmss3WMayo1Gc4G8KWaIKTBRN1AtuQvCnjFORbKQ+VBltl08gYrjOF225XOJKZwZlF7qkMOBxkMY2etFBdQWy2Nfno4myqibKqEm+2fT6JnsVyVntkYlWGS0bbNknxxcblNmKiRymjRUISFVRGiRU2Xck3VEHkTMXI1e/6ef08/mktzVpxofqFRpXnzVDQLoz4PjLU+z9FnSSZdLjcYdot7cjD4AsDIlPC0BuqLCqjY8XEqJsq8PCioqotYBzo/6NaZ49cLlqz0a9E8nMXYzFqcxTTqFAeuMp9zqwhpElOPCBqatojpSkbXjVS6pAUl0mg9RUrx9dI/QytcdEPoS2CRcY0O4T7ta2cGsHdNpaguNBL69ScRo1bR5s9mHHeMV3b415VzZRinRWYyaFjmHdELTydFG+2yz3G+u4NaVt7LkoBdKMKIov9ejJtnxK0rScaCpqe4oHruleIMQtPR0yHKo9mmdE3S2NaJEHGJMe6lp3aUSW5cyfQx6lJBEwmzSIKr1nAoub9YihvsbkToTx4Dl0mdDnF48WQEeRZXDwSyL39ivS2ooyIqCSqII7IZ3GQjLnC4hICpQevKV5Iv1l6HmN6bztSLv0KsQiM2e4PWy8W6ZjWKw37Y80qIXXPyJLcNUXjb4erkGpcacKKu+2x6XaNdFvUifkb0boyaSjaoLlvO2GmEQAeNiTAYk7uorapxbvKnJERERE5+NQsOsepLej2mV/1Pk4zdL/ABMbjpOmwbWjayViCY9e6COEIr1TSuOqikm6Nqm+9TKy9JnRHpF6bpdtJs9gXd1u6WZyVbiLqZ8RO+UZF62Oexim/LjRFBVReEl8ddfWPoS6IZxplf8AD9OtHtKcQv8AeY4wo99+Q0F07e2ZiLzrQgAF1qMq51ZIYqJqBbptWl6cdA3QTos4i1e8QtEm8ZYtxtLL2RXZxHJPCVxj8YsgmzbAruqeCPGorsRlU+F/Po+MeqK/+VV8J9F5pSlbLMYPOrhkVpw29XXEokWVeIUF6RCjyRImnnQFSQCQVRfC225L41StRLVwZt0x9+zhFWxSMaeyq8ynhVTjQ1Ae5xHYk4SMlcXmi+CyabIvNKUqIqbKm6LUTsnRpbtGH5Lh7+bvz2ckubKuPPwA42bG0/xhaB4STdtGyda41XfZ0lUV8virSz2PVOXe4Lxrbm9k0ay/Lr7BsVlyfHEYkIEtpw4LMWSrZsOuj1okSA24QGvWAiuMObcKck2PRrPn84euZ/t00v1CZiC2nDhkFWCiESrzeLvjLRUJE8FNg8S818mDybowYz3Hd7dpQ3jmn8K92tIU2FbccaGM9JbkNvR5RtsOMoRBwuAqeMhc24h4edBw226qQZMk9QszxS9RybRI7dmxmTazbPfmpk7PkoabeRBHbx7r4q5EVZ7XZmnLYnMnP9a3ZGe5VaLjhj2P4RdZMZLG7ZJIz5seOw265tP7t6ptwkMuFVjKO6Ii+NSTZZ2veNQ7xGgM47kE23K5BYuN5issFCtT8wQKM1IQnUfUj61rdWmnBDrBU1BN1TDztFdQpUzL7RG1NssXEM1uL0y4wgxl1bo208022601NWb1QqQt7IaxiVEJdk32VOlkfRZxi8ajMZxBiYaDRPwH5S3TDYt0ujSxBAGwhTnyVIoELYISK06qLxE2rZLxJz343O+7O9t5612Fh+8SpWN5E1j1mamG5kiRW3be65EUkkNAjbhPiQkBiiuNABkKoBGqpvjmukFZkt01bjgmXW6/xnobDGNyGIi3Gasvi7mJpW5BR+E+BzmbwcHVn1nBtXB+xC9PWy/4PJz5scGvKXIwtcazAE1t2aZuOdZLNwwcAHHTJsQYbJNhQzNEVC6h6F5bcEk5Jf8AUeBLzVt63OWu6M2AmYMMYSuK0Jw1kkTvH17/AFqo+Cl1ngdXwpXc6z3XzYukFPfs77tx0/vlyv7t7u0GDj1qZjtz+5YRohuOd0yQYRQQgQlR3YlMerQt0rZ8X1nsGaZNCxzF7DfJzUuzxb4dy6llmLGjSetRpHEddF7jUmTFRFslFdt9k3VNDybost5RaIx3y8YnkF+j3S43NH8lw5u6WxVmqCuikEnwUVAgHqzR7iFE2Lj3LegadaUxNO5xyYNyZdYWx22yhHZtrEJsO5FfVXBbYQWgQ1fXwAbER4eW+/LlOnntJ0ctjfKUpUqNiMl/3a2/8+s35jHqtVGNQbHZcmx9jH8js8K62u4Xi0R5cGdHB+PIaK4R0IHGzRRMVTkqKiotZLsndFj0adKvU23e5qhj91Pz+i5hP2vkdE76LGjf3f49+XMVVa8v9GToydG2/wDRt0ovt96PmmtxuVxwexS5kyXicB5+S+5AZJx1xwmlIzIlUlJVVVVVVapfZO6LHo06Veptu9zWcuKrSpV2Tuix6NOlXqbbvc07J3RY9GnSr1Nt3uaDUdFenHoZrFkUrTyRd3cN1Atsx22zsUyNQjTAltmrbjTRoStPqhiSIIEp7DuoDXg8byisbszDcRP7SDxIi/Xuvjr0/ob/AEW+iWA5S9qZqnHg5plcqedzCHGgDbrBbXicVxAjQG1VFAFJREXCINhHZsVSvE4ZBd7KwEb90/EJUQCaVOHn4t033Tx1BejPJNa728uSpMuAIOuqSC4iqZfOROfP+f8AlX0xc2gRwXZDiltvuYom/LxbVqMe/wA0lJyOTfEo7IAlvt/Fa7trcF8XnZ3huEPEJEu23hIi/wAPLVfLJPk3W15H3BAQyMkVV4fGni8tUrSzS3LtaGnZNojpbbQyatP3GaK9WReUQFOZl4uScufjqVYXjMzL8qt+KWfZ+dcnxYDnugiq81+pERN1X+CLW59L/XDpJ9FHUnFNOdK7PHf0872RSb4bd1hS3kNUko68PMDJefLbkSeOvMxVMe68VRt2b1OsHRPtULISsM2aF4MOJWnldUBVPKitDzQUXlzPfddtk8dda+aBagYzNukCX3W9anWm22Vi29AjCJL80EaMl3Q0aVTVEXYS5LutehdNHWBillT7ZDIubTTnBvv1QcO4tp9SJuq/xVVXy1PdQP6QTSTTjU+LpJcrbeJd6elsRHlZYQWmCd4eBSIlTdNiRfBRaxbNF32jNVNdU/CIzyRV4e5G2qdrxxq1j9hgPyLQzKiSoTa9yNnOEJAyOrHg57j4K8vKqbIvlWvKeT4jgVqvhhndluFjWRJPaTa0AW90RFUUHYgVOaF4gXZU8lfuvqDhukeX2hwdQMQtk0CHr1UoqHIEtvnCYJxoW3lTnX5NdM3G9GXrdNl6T59NmQTnMxe4rzAcZegzUQ16tuQaCpt8KEhCabt8QqpKhbJqYLBXcLMRTXnE78tkx8P7orVNducpnOGq4d0ctG8sIJEHUW6OsOohgIttIaIv1ptuv+Xkrdneg/i1xt6uY5qarksd0QZUdARV35Iuy7pXjTHtQMixkxjJLkpGRV8FtzgIF+sV8W2/jTxL/Dx1VMG6QmpEi5s2q2yu6esJE6wkUVQf4pvslXLlrHWpmabuceeTToqtVbKo2vS2hWjuo+k8/IrbeXrfKtIA2+LjUjiET32UttkVOXjT+CV756IEYmouTvkqkr3cK8W2yFt1/iTdeXOvLWkV/hLZkau8Ga+7cgVuQRtbgakmy78/F9VeruilKGS5lYiv+z7gFE22RB/foiInkTktVcLiK8Tembm/+yPE2otU5Q9A0pStVQKUpQKUpQKUpQKUpQKUpQKUpQSrWT+sXQn7wJn+lb9VVqVayf1i6E/eBM/0rfqqtApSlApSlApSlApSlApSlApSlApSlApSlArhlzIkCM5MnSmY0dkeJx140AAT61JeSJXNWu5BhzN5uUS+x5iMXCCqFHWRHCUwiou6L1Z8wX/xNE2S8tyXZEQOqOqGFuZGGLMXmO9ON8YyI2+2Q9YokW3zt+XBwry+cQp499s9Lvtkt86PbJ95gxpktdo8d6QAOvc9vAFV3Lny5JWnWnT7IY+auZRcsodcZWRIkC02LKqvF1YAK7s7onVBsWxbpy2XmSr2Lvh18uN2ubQ97itt4kRX3pRumkpkGeFUZAOBRVFIN0LjTh6wl4VXxhk7Zn+PXWPd7gw+g26yuE0/OJ9lWTIfncPCakiJ9ZIO+6bbpXzZtRMUu1sbujt2h24XWlkIzMmx0cFrl4ZIDhIKbEPjXdN03RF5Vwji14DT5/GBlx++Upl0Hn+MurU3jInFRduL+2W3L6vFWNuGCXc4s4oaQXXp13GY82sk46uxWwQWW0fFsjaIeES3BN0XiRCTfeg2d3LsTZhM3J7J7S3EkoqsyCmtI24iKgrwkpbLsqonLyqlfS5ZiqRJM9cltSRobvUyHlmN9Wy54uAy32Ev4LzrRIul18W3SGZ8mC5KOJIjMGUh57qikyCJ8+NwVNV6ngFCVVVVRUXku689100uTt0W7W8Iio3LRWYbdxkQASOEZGmtnWB4gMF4/BRFFUNU3Sg3cslxwHYTJ3+2i5ckQoQLKbQpKL4lbTfw0XdPm7+OucLtanLet2bucQoKCpLJF4VaREXZV499tkVFTx+StHYwC9w51uZiN2sILAshJcSS+SvNi4rrjZsPC6Lu7hEQudYJjxeNdtlyDuDTY53G8Q5UOTermCtOKbAxYiCXgqRNtipvKg+JHjPmnJQRVoMxdsst1tdbhxWnbpPddVkYUEmye4kDjXi4zEQ2DYvCJOSptvum/RHUK1yShjarZdLl3TGblu9yMCaxGTLhEnUUkXffi8EEItgJdtk3rBy9Pr43jcGwQQtshyC5JRua9Nkx5CIe6C+rjKIRHsSobfzT5eEicq6iaSO22cx3ojWx1tgYKtT5DzgS46xhTcGkQCEUcUU4jQkVEMtxPZEoN4uOWWC2wDuB3JmQKNK621HcFxx5ONARGxRfC3NRH6t1RFVKxxZ/CFkW+8t0W5lLWElqEWVk9agI4vNHOq4UBUJS49tlRN9+VYiPg1/Ztt5bltWefIvLrUt1tx15psD6xSNlswHjbAd+IHETi41IlHdaxJ6RSwhsvnCtN1nOnNdks3GU+4w24+gIJiZCZvK2jYonGiKS+FuK7UHI/pnneQyHb9C6SeqVjj3Fw5TVrC2Y2gwQNVJGBR60uO7Ai8KcZmWwpuRLuS/H7G9RfSx1V/DcV+DVSrTBK12qHbTlOSSisNsq8585xRFE4l8fNdt67dBKv2N6i+ljqr+G4r8Gp+xvUX0sdVfw3Ffg1VWlB510RxDVrUnRrBdQ770qdSmLlk+OW68S2olrxcWAekRgdMW0K0ESAhGqIikq7bbqvjrdf2N6i+ljqr+G4r8Gp0UPovaRf+R7H/7FqqrQSr9jeovpY6q/huK/Bqfsb1F9LHVX8NxX4NVVpQSr9jeovpY6q/huK/Bq8S68dA7pJa49LNrLMb1QyKx2jF7fChpn97O3sXCS9wk6SwWbUxFU+rR8W+J1AXiBxEdVBEU/S+lBCtNsWvWFJkGM5Fnl4zG4QrjHB68XZuOEmSve6HzJGGwHZPEiqilsnhEZbku5Vkcnw26P3ZzIsWkRAlyWwbmRZikLMjg3QDQxRSbNEXh34SRU2RU5ItYjvFqb9g4x+OyP0dbVu9bqop2xuiNvlDMuWq4qnZ3ualcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jr3rLfFHOHjV1+E8nNWv5/iny5wu8Yh3f3F32iHF7o6rrOq4v7XDuPF/LdKzXeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLc/tRzh2KK47p5NBvekffi5Xi4/KDqe+t4sl24O5OLqu95NF1e/GnF1nVfO5cPF4i256410dnpclyy5LmYXHDIrt1ftVnZtfc8lhy4A6DyPykdIXwAZD6NiLLSihJxq4o7rYe8Wpv2DjH47I/R07xam/YOMfjsj9HXnStT+1HN3K54TyQ+7dGm65jbbpF1Hzq1ZE5Kttst8RpzGGxgt9wSjkMHIjOPOJI41NBdFSASQfARrflm9P8AQKLg44+4xLxuG5Z7vLu70bHcWYs0Bwnopx0baYaMiBBQkJScceMlRU4kThQar3i1N+wcY/HZH6OneLU37Bxj8dkfo65nazz0o5/3dyuTsynk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Ovest8Uc4eNXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5Oau3ph/vGXr5Fvwfl0OscmPamPL1a23GIiFy67vpIkcH8er7mb4v5cY/zrcsXxyPjFqS3tSDkvOOHIlSXERCfeNdyNUTkieJERPEKInkqvir1GrmmJzmU+Ht1aelMZRDL0pSspfdG9WW25DbXrVdY6PR3kTdN1QhJF3QhVOYki7KipzRUqYyY1yxi5BYb66rwvKqQJ6oiDKFE34D25C8ieNPESJxD5UGuV0b1ZbbkNtetN2jo9HeRN03VCEkXcSEk5iSLsqKnNFSq96xrPep/F6+U/rYgvWYubY3p1SutJjXLGLkFhvzqvC8qpAnqiIMoUTfgPbkLyJ408RInEPlQezVOJz+KjticpKUpXQrjkyY8OO5LlvA0y0KmZmuwiKeNVWkmTHhx3Jct4GmWhUzM12ERTxqq1kMSxORkMhjI8jim1AaJHbfb3R2IyTmL7wr5fKAL83kS+Fsg821To073aaZrnRpMTxORkMhjI8jim1AaJHbfb3R2IyTmL7wr5fKAL835y+Fsg0elKv2rUWoyjf3y0LduLcZQweaYfaM7x2Rjd5KS0y8bbzT8V1Wn4z7Ro4080afNMHBEkVUVNx5oqboukOaAWy42u4N5PqFl9/wAgm9zdTk0w4LVwgdzO9dG7nbjxWog9W6qnsUckNeTqOCiClTpUqRJovRvw9oJTs3IsjuFwuVnutnuNxkvx+vm98DaKRJcQGRbF39y2go2ANiibI34tvt/o7Y05kwX+PluTxIfd8G7vWZh6KkGRcIrQtBKPdhXuMmgACEXUbXgQkBD3JfPOoWr2UacYO/bsLzOXjt7dv2Y3eJxybdHiXHuW5uqcVe6Ykp+Q+onxBHjNtkQg4pPtIiLXUyLUrP8ABpGfOY7qjAsw3vUNvvper3doVrj2SOdljOsIkpy2zGmBeMUaEpDBoaAgiYmXEoei7R0aMJsk+2TYl9yEgtcSzRAjuPR1bd72G6Ucz/c8XEvXOCfCQiqKmyIqb1wNdF7DOoSHOyrKZ0OEsVuzRpEiNwWaKxMZlpFjqDAkTZOR2UIn1dd4QQUMalcTVXWye4F3n6htxSsVsw+ZIg223NFAuh3Cc7HkqZy4jcpGzbEDDgRhULZU3FeFdJv2pOa6W2q/vWfWx6J1eoORrOhOvWdm5yneuaJiLGGXFSPKNRLj7j66LIdAuJp7weFQ9L5N0dsZyKc1dGcqyO0TW73NvSyIJQyMllsCxJjor8dzq2zbBE42+F4efC6O61sunGl1h0wiSolinXGSMtuE24Uxxsi2ixGoze3AApuoMipcvnKqpsmyJtUF85MKPJcbNs3WgMgMOAhVURVRR3XhX+G67fXXPQK0/Vf/AIORfqu9nVf5d8Y9bhXQvtlg5DaJVluImseW2oEoFwmK+NCFfISKiKi+RUSpbNcW7lNc7omJeLlM10TTHfDS6VxFjGpENe52WseujYchkvTnobhp9ZNiw6KL9exbb+JE8VfzvFqb9g4x+OyP0dbGttzuqjmzdXX4S5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4c1dfhPJzUrh7xam/YOMfjsj9HTvFqb9g4x+OyP0dNZb4o5wauvwnk5qVw94tTfsHGPx2R+jp3i1N+wcY/HZH6Omst8Uc4NXX4Tyc1K4e8Wpv2DjH47I/R07xam/YOMfjsj9HTWW+KOcGrr8J5OalcPeLU37Bxj8dkfo6d4tTfsHGPx2R+jprLfFHODV1+E8nNSuHvFqb9g4x+OyP0dO8Wpv2DjH47I/R01lvijnBq6/CeTHZL/u9sTyrfrP+Yx6rVaJZcJv0u6Rbpl7tvaat7qSI0GC4bwk8iKguOOmIKSDuqoKAnhbKqrslb3WfjbtNcxTTOeS5hqKqYmau9Kuid9FjRv7v8e/LmKqtSronfRY0b+7/Hvy5iqrVJaKUpQK/DqFBkxG0gq0htkqiIqH/ov+FfuLX4kzJzbbqosh0DAtvEuyL5dtk2/hUV2d0JrPe6cWDc0dUWQ4FRdk5f5VttltN1WOL0g3WgQfn9SpIhcXl8m1dSJFclCEjrlFUDi3Xdd03/h5f41szuSvWbHzjJIKK0bRKalvsQfV5fKm9V5qz2QsbkJ1h1MvuE3mAxh17k225QT69ZcRzq3BPybKP+PKvT/Rs6ZVj1pxhzE9dwgTMqtytx4MwgJClMqqKTrm6cCGioKeCvPmuyeX8/M5vZ3a/TZrp8auulweXlv/AJVgoF0WyTQuEJwRlNLxAfCi8K/XzTb/AKVo3cBRfw8W6t/i8UVzTVpP2syHWHGoFoWPByyDBQA2EyeFETl//FeC28TxW5dINvVXJdShu8WNchuBsvGLjzrra7gHEnLq04R22Txcv415aZmTMokG/dL/ACXUFfCJ54i4i/xWu6wrdsdE4F7ECFeQoW/lROaf41Uw3sicLEzRc2zCau/FydsP0u1D6RuSahXJsrODKQnAQVfZmqw4A+JR2FOf1818taTkmmFs1J7hjTIDLgQ0LfrD6xAcPmRJz357Jz/glaH0YAHUPF3p04oDD8B9YxGTnV9Zsm+6Iv8A/avSFrwCwQYvdL8mObyIiogu8aFt4v4f5rWHir12xXNuJymO9ctW7OjFczCJSuh/ibjPCdoYccXkg8Ioip9ac96/sHocYxZrg1cbJEeiSw2VVUCQNvLzXktXRh10VFiOywyaEnVE2W6qvi4U3Ln/AJeWsTd9SZ+N3dm33THn4hcW7qSJZN8ac0RURRTwd/q//ms7/wAtiI2TXsV72MwtmNKqNjJaf4/cLPw2y5w4zrDfgr/ZJdl5KK7qm6V6E6MMFu23rNozQGgmsB9CJd90NZOyJ/LbavNialWGVLauUO+OREf4+7Y4iLvc5j4iHiFUIT28i8lr1D0ZMhh5FEvkmKw62QjD4utDhMkXrtlVE5J4lXZPrqx7JxE1YqKfHP0V72OsYqiaaN8O/aelZpLf7VCvtit+pVxttxjty4cyJpblDzElhwUJt1twbeomBCqEhIqoqKipXb7S2nXm5qr7J8q+HU6J30WNG/u/x78uYqq19YpJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oJV2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8Oqq0oMTieU2LOcVs2a4tO7tsuQW+PdLdJ6o2+vivti405wGgmPEBiuxIhJvsqIvKstUq6J30WNG/u/x78uYqq0ClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKwl/zLHMZdajXe4EMh4VNuMxHdkPkKLtxI00JHw78t9tqzdSaxksuVe7vIXjlSrzPZcNfH1ceS4w0P8AJAaTl4t1JfKtWsLYpvTM1boQX7s2ojR3y2n9q+H/AN2/+rdy9xT9q+H/AN2/+rdy9xWMrrXS5QrNbZd4uT3UxILDkmQ5wqXA2AqRFsKKq7IirsiKtXex2PPnHRV7Td8uU9Wc/avh/wDdv/q3cvcU/avh/wDdv/q3cvcVLsP1y09zm5Q7TZHchjyLkysiCt3xa6WlqYCChL1DsyO0Dy8K8XCBKvDuW2yKtb9XIwmHndnzjo7OJvRvy5T1ZP8Aavh/92/+rdy9xT9q+H/3b/6t3L3FYysXj2TWTKosibYZvdTMSbIt7xdWYcMhhwm3Q2JEVeExJN05LtuiqnOu9jsefOOjnabvlynq2f8Aavh/92/+rdy9xT9q+H/3b/6t3L3FYylOxWPPnHQ7Vd8uU9WT/avh/wDdv/q3cvcU/avh/wDdv/q3cvcVjK68uexCcjNvBIJZTyMN9VGcdRC4VLc1AVRsdhXwi2HfZN91RFdjsefOOh2m75cp6s3+1fD/AO7f/Vu5e4p+1fD/AO7f/Vu5e4rGUp2Kx5846Harvlynqyf7V8P/ALt/9W7l7ivtjVPCnngZdnzofWEgC5PtUuG1xKuyJ1jzQgir/FaxNfLrTT7RsPti424KgYGiKJCqbKiovjSnYrHnzjodqu+XL+6i0rUtLH3nsKitPOm53HKnQGyNVUuqjy3mW0VV8aoDYpvW21l3KNXXNHhOS/RVp0xV4lSrpY/RY1k+7/Ify5+qrUq6WP0WNZPu/wAh/Ln68PSq0pSgUpSgUpSgUpSgUpSgUpSgUpSglXRQ+i9pF/5Hsf8A7FqqrUq6KH0XtIv/ACPY/wD2LVVWgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSg6N6sttyG2vWm7R0ejvIm6bqhCSLuJCScxJF2VFTmipUxkxrljFyCw351XheVUgT1REGUKJvwHtyF5E8aeIkTiHyoNcro3qy23Iba9abtHR6O8ibpuqEJIu4kJJzEkXZUVOaKlV79jWe9T+L18p/WxBesxc2xvTquOTJjw47kqU8DTLQqZma7CIp41Va6ty7rwyUtsyeUKx1Eih3M9hCQApuon5BdFE3VPESJxD5UHNYnicjIZDOR5HFNqA0SO2+3ujsRknMX3hXy+UAXxfOXwtkGlEzXOhTHvenx/W3uUqaaqqtCI2mJ4lIyGQxkeRxTagMkjtvt7o7EZJzF94V8vlAF8Xzl8LZBo9K6N6tEa+2x+1THHgafHhJWj4S/wD9En1iSKKpyJFRVStC1ai1GUb++WhbtxbjKGKv+oWH41wDdb/BBwpKRSbSS3xgfJS4kUk2QUJFX6kVPrTfu2/KLJcMdYypLgwxbX2Uf6990BBsV/vFvwoqLyXnyXlWmZFppkk60wbJZ8ljxolvSSrAtw2Y3M21AOMQbIFXw3d1AW05psO/hJsF2xi5paLNDtclq4O2aU2+jdwJGQkiAkgoRNN7Co8QkKo2vME5eWpUjmlZ9jrVwtFsgTGrm/e1JYqQ5LBIrYrsTm5GKKKKip4PEq8JbIuy18Pah4xGyCXj8i4MMlAaFyXJdlMNsskSKqAXE4h8WwqvIVRE8apXVxXD7pZ727erlJikr7Lxk0wRcDch97rHUFFRPBRBbFC8aqiqqJvtXx8i7jIyMLvcHojjA3R64oCkRKipHFmPyVNtx2Il58l223oM+1lOMSH5MVjI7W49DbV6Q2ExtSZbTxkaIu4om6c15c6+I2X4nMkNRImUWl999wmWmm5rRGbg8yAUQt1JN03ROab1Pw0vyqURhcJMEAdjOxpBBcpDgvK8+2Tzgsq2LTHE2LicADtuSbqvjTLy9NpUs3kbdhQxkXIpSuR0VDaaajq1EEfBT5i8JcO6Ii77LQbY3lOMPBNcZyO1mFt/30hmNqkbmqfvF38Dmi+PbxLXNBvlluitjbLxBlq8z3Q2jEgHONri4eNOFV3HiRU38W/Kp9B01vMS1K0tvtiyG2GIYNlfLi4itASGRNvFv3OvGDZCINltwbKqovLMBp/MutsgQ8muMZVjOHKe7mhsK8cgjVePrybTZeFURTbbbcUk4kIfFQbTIvlliCZSbtEb6twmjQnh3RxA41DbffiQPC28e3OsC3qRZ+5JEuXbbnEVthmTHZdaBXZjbpKDStCBku5EmyCXCSbpuiJXVYxC+jf5t+fW1uLKiOwW47puPA02gojRkqohOGeyo5uqLw8KIq8O5YBvSOU5HdmzIVudld0RFatrtylPxe52EJEaV5wVNEVTJeHgUU4RHhVN1UN9hZRb34rr9yByzusIRvR7ibbbjYCXD1i7EoqCrtsYkorv49+VdMc7tK3QoBxpbcZHnYw3ExBIxvNApuNovFx+Cgl4Sgg7iSIS1rsDT3II7tpJxy1BGs75SmYjRn1ZK67xm1zDwWmk4erREXcwElQdkROKfpbOvd1ny7h3DCaJqckfuSVIMXXpAqHWkya8DKoCrxI2qqZKqqqckoNxxzJ28kEnWbNdIbCtg9HelsiASWi34TBRJdvFvwlwlsqLtzrNVqeCYlIxopz70G3W4ZaMgMK3Om4yPVjsrpGYgpOGq+EqjvsI7qS862ygUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFfhmkeTFaV+OSo1z3Ew3UV8v/ANb1+5lfkJExx2zk8MkWgUU8Eic3b2TyoiePx1Bfq0YiU1mY2tGtOQAywkcWWusXdDJU+d9Sc/8AGqtpNpxD1bzSBhc4lKNNBzugwNVJsEbVfr+vb/OtWvd0s+Pxu7rs+ANIu+3IUVfqRPH/AJVrNn6VuUaezp0/Tqx22NIktdQEyUyTrjQKvPhTdE3VdvHvVaYqqjOiFqMmTv3RKiaUZnIt2Q29Lm6w+rkF8206sgReSbL4Kr/OuV/Q64XiQqxdLMdfjFzRZdrjxQTdd9+MRAtv477VN770o9bsxkORsmzeScR4TQ+52Ua4eS7oJAiEP80VKmzF1yKZLSN3wdleH+7J2Osp8915cKKikS//AFyqhew2NvTNVdyIn5z0ecRcyoiLcZfF6XtPRR01cBxcxj4lZ3VXcAtWSqR7/UrRAqIv8iWuBno3dHazvq/OZudz4C4usN6T1Xj8W4tCn/Va1vG+j30l8rhpJt9muGIWifw8T1wfSE7ITyEY7oa7+NBRET/1qnj0XG9PbA1Ly27yr7JkKpOPJNJG1X+7wq0fJP4Eu/8A0qjGBxtv39fVOfdGf1mYZtVvEVZzFTqNwNGsLjjHxK4x7RHVxTNlHJSt7r499y8a/wAq2PF5c02Fk2+a5OgyjQYwtPyEFUQvCUV3XbdE8RbKnjritFgy6zW5Dxuzuv21tV4e4QkbIvl8IjEd/wCVavk2rOQ2CQ81c7wUcVBQWLPeeJSReXITI0T+C7fyqCvA36ttUzM+e1BXh7lM5zVP6+Ld7jOyNiCpAzM7rBwvB4kAlBU2Q+NERR4V3+tV28fi2m+R354Lpx3W8MSJqOASvS3TRVLbfbkqqqboibl/0rpQ9brmNvba7tgMxkRUQHkMRc5eLiIU4v8ABK7TOcXlIjM2biUF2Gq8QOQn3OE1VV8FEEuFS23XbbflUVWEqtz70fRXrw8XMoqn9fKSDkUezxyDr5pzRcICisbPdUe+4r1ilsgrtuiInNPH9dexP6Oe9ZTendR3sldA0E7V1CNruI791qSIv/7a8b92YvOmpIuWO32C25HVZLbZNm05vz34eAOJUXbwl4v5+NV9m/0dbONMFqIzYrost5py1NSQMFFxjhSVwtn5N0XjTZF5bKmyVc9mWaacXTVlt2/LZL1h7WjciYXbonfRY0b+7/Hvy5it4yPMLZjFxx+2XBmSbmR3FbZFJoRUQdRh1/icVSTYeFg03Tdd1TltuqaP0TvosaN/d/j35cxWa1Jxm93+/wCAzbRC69my5Cc6cXWAPUsLAltIexKil4brabDuvhb7bIqp9a0XctesOkl7kXWJZdUsQuD9ijDMujUW9xnTgRyRFF19BNVaBUVFQi2RUVOddyzakad5FDeuGP57jlzix7glpefh3Vh5tucpIPcxEBKiPcSoPVr4W6om1eYs90iyPF9DYLkzFosVbFphktsuiq8yqNSpKxnEbJQJVJDJt0lUOJN0Xdd1TfZLhg+p16bn59a9KX7SUBzFgh4ws+AEq4N2uWbrzrRtvlGFCbcQGUddbJUbRD6rklBZLvrTprZc+x/TSZllr7/ZI7MYhRRnx+ProwAbjRApofHsabCgqvj32rMXjULAceyO14df84x+2X++b967VMubLMydsuy9QyZIbuy/3UWo3heIamQ9QbPmd80+mRWJmU3+XIZbnwnDt8SZGjpHee2e2LmwoGLSuEhryQg8OsnqNiWdydXIN0xHFr2sS4LbhuUwX7RJscpqO8R7XCPLRJzTjSEZMlBVeIyBXFRB2QKnCz3BbnlU7BLbmlil5LbGhkTbMxcWXJ0VottjdYQusAV4h2UhROafXXBZtTdNsjbvb2PahY1dG8adcZvRwrtHfS2OBvxhJUDXqSHhLdD2VOFd/Etec8S0G1Qh5+Vvu8vNDt9uumRXWFdHp9hCytLce6OBWBZjLdXXdpAobb7gAKgpI45wgi4PE+j3qu5i8223y15e/NsGOwLJFayC446kK6DEmsyCixAt0UHCjOJHURdmmDgo9srSKpkgenNPdWcK1TevfyGurV1hWOUzFK4xX2X4ctXY7b4nHdaMkcDhdFFXl4SEnNE3XcalWitjyaNkWoOX5Fp6eIDll4iXCLCekxXpJgFvjsGcjuU3Gxd42iHZHDThEV4l32Sq0Eq6J30WNG/u/wAe/LmKqteX+jJpPnlx6NulFwh9JvUq1x5WD2J5qDEgY2TEUCgMqLLavWk3VAUVBRXDM9kTiIl3VaX+xvUX0sdVfw3Ffg1BVaVKv2N6i+ljqr+G4r8Gp+xvUX0sdVfw3Ffg1A1k/rF0J+8CZ/pW/VVa8v6saT55FzzRhh/pN6lTDmZxKZZeegY2hwzTG70avNIFpEVNRAm1RxDDgdNUFDQDCl/sb1F9LHVX8NxX4NQbri2oODZxIu8PEMttV4k4/Petd2YhygcdgS2jIHGXgReJs0IS5Eib7bpunOtFxr/drl/z68/mMivD8foB9JjUHpcZZrVF1YyLTqys3VY0bI5DkMb9eW2BBo3wj25tiMLTyskqK4AKoECmDqqSl7YwmM9Cs0mHJnvznWLvdmnJT6Ajr5DPfRXDQBEEIlTdeERHdeSInKtHAbqvl9VLF76fn9GfrXtRYsmdp9k8KFHdkSJFmmtNNNApm4ZMGgiIpzVVVURETx1sNKvztVI2POeHYHn+HXPTq45VkOYZtaRsiMRIM2JEjnYLssTYSPuKIyXVE0rrHG8S9USpuqqe4ybH9P8AJbhZM0ODpudm+UuDzYs9iz4Rc7LKO5de0StSZMl5x25yRRxzhloI8e7hAp7lwe5axGS5hiWFw2bjmOUWixRZEgIjL9znNRW3Hz+Y2JOEiKZbLsKc18lRTbjvlJFcvO+TaV2nFrhkNrt2m7/7PFvdkuV7slssrshi5MdzPhIIYjIr3UvXdzG8AAanwbkJLyXXI+EWeLZ7f8ttHMnuODpcMrODYix+TPdjTZE1DgSFigjhtbtdb1TpAiMcaIpNeJPUEzUfTy3PWWPcM8x2M7kqoNlB66MAVzVdtkjIpfvvnJ8zfxp9dC1G09DKGsILO8dTI3lMW7Qt0Y7tNRFCJBY4usVUFUVdk5IqLSaKSK5ebbno3f73abndM8xS43fKoGOYixFmGDj7zE1p93upyK8Cbi+gmqOOtKhcKpuu21WDS7DWMLi6i47Y8Z7y2bv485aYUeIrEbqnIMdTKO2iIPCTyuqvAmymp+Xetwj6l6cy5d8gRM/xt6TjA8V8ZburBOWsefOSKFuwngl8/bxL9VdORrHpDD7z916qYex8onCZs/WXyKPfFwXEbII+5/viQ1QFQN1QlRPGtdimmJzzJqqnY89WPRy8YphUf9n+Dz7Rf75pa5HvD8WOUSVNuQEx1bb7u4L3Ugm+IKZIYoqoioicu1jGIspf4U7S7TbIsZwQb3bnItrGxvWg2pQW+cMuS3EfbbKOiq5GBXTEQNwd91+ctuha6aUySy5ZWcWS2sYPcBtl6kzrnGZZjOkAEKkaubAKqfB4fCvGBjtyrPPagYHGds7EjNrA05kIiVoA7kyJXFC24VjopfvkXdNuDffdK5FFPdJNVXfDytbsEyANMMusuA4e3AZOVaDuEtdP7jan7iw3J4pbM23OPit2e6lCV6RHMEko4TY8WyCtp6MlhnWDCbkw4yca3SLu8/bIg4o/jcaMwoAijGt8iQ89HZVxDJBc6teIjVAQVFVr9K7Tbimc3JrmYyKUpUjwyelH/B6/83vH5lJrcKh+I6aZpkFsl3e09IXUDGYki73Xq7Xa4Vgcix+Ge+K8BS7Y88vEqKa8bpeES7bDsKZv9jeovpY6q/huK/BqxsV+fX8Z9WnY/Kp+Eeiq1Kulj9FjWT7v8h/Ln6fsb1F9LHVX8NxX4NWMyfo65LmmNXbDss6TuqVysd+gv2y5wjh400MmI+2TbzSm1aBcBCAiHiAhJN90VF2WoEq0UpSgUpSgUpSgUpSgUpSgUpSgUpSglXRQ+i9pF/5Hsf8A7FqqrUq6KH0XtIv/ACPY/wD2LVVWgUpSgUpSgUpSgUpWmZpq9gmAXBm0ZBOuTk55pJBRrVZZ10djMKqoj8gIbLqx2dxJOtdQA3EvC8Fdg3OlcECfCukKPc7ZMZlxJbQvsPsOIbbrZJuJiSciFUVFRU5Ki1z0Cla47n+Mt3C5Wpt64Spdnd6ic1DtUqSrJ9zpIQV6pskXdpUUdt+IlQE3NUGvu2Z5jF5usqyWuXKkzIMxIEsAgSOGM+scZCA6fBwt/ujBdyVE3JA34vBoNgpSlApWNsuR2bISuIWeZ3QtqnOW6WnVmHVyAQVIPCRN9kMeabpz8dZKgUpSgUpSgUpSgUpSgUpSgl3SPkyImntqeiyHGXCzrCmSJs1FVbdyW2tugqp/ZNszAk8SiRIu6KqVUagGQa141mVszqxZZp+8XyDyu39xxnLkTYXRYdxiOMTW3GxQg6mWjSk0SLzAULiBxN6dE1d0/nZs5p7GvTxXltw2E4rfJGI5IAONyO3MVtIzj4BuRMg4rgiiqooiLsy7xuNKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBX5ExXL6w4L8OX4KLsSSHCUxX+0iKXLbb608lfrtURmdC7o3T2uok4HOVv+4OSXQE/yGSlQ3rWtjLPJ2Mn4s9IHIbo/lr0Xu1QYjIiCDbm47qm6rulSQMjuzKqjU93gVfmkSrtX7rSv6NvoXTXCelaOG6ZruSlkt35r/wDNV8Rv6NPoTRJLUtjRQesYMXA48iuxjxIu6biUpUVOXiVFSrtuu3RRFGSaLsQ/Ovo+dFy95PZLZqJnE2SzbJSJJC3IPBIJj604/BBS8aEqF4K7oiKqKntbA8bxHG8dKbhlht2NsL4Sk00jsh9UXxk54Rnz35kq16Ye0B0lfa6lzFSQNttguEoNk/hs4m1c1t0N0vtMYIdvxx5pkN0Ee+Ustt/H43VqjNnSqmqre7N6mdsvJ2QXVO+TU+Xc1fEEXj6xNtv4oq18zpRXeGiDfElRuJFSM4qKibf3dh32/wA69Vv9H3SCQhi/h4GhookizZOyov8A+pXXh9G7Re3goQsOJoV8g3OZ73lXZtzOyXNZT3PKky6I9HbtkyJHFhjbq21c/d8HlXYUTn9flrUct0+sWQ7Ns2q2k05vtIYFS2/huRKqf5V7XkdGvRSVxK/hilxeP/vKWn/o7XxH6MuiUXnHw1xvflyus331c1U95N2me5+YeYaAtR5yzSkyphgClHYEN12TyIm3/wBb1qV3VuxWqKy2s6NPcfI3nQJwERBXwQJCRN9lRfFy3Wv1zLo76PmvEWJmq8uffOX72sNfeiN0eclVsr3p6klWt0Fe+s0PH49+F5N/8aq3sBrNsShmmic8t78lorvWE26NzluukyTjLZOcBtF9ab8k225cuWyL/Bfb/wDRhwo8B7U2NHHZBGyIqqK8Xim8iVV3VdtvqTn4k3q7ROhD0X4QvDG0yUUfNHC3vdxJUL6xVZC8P/5dq37TfRjTXSJbkWnmNJaVvCsrN2lvv9crXHwKvWmWyp1h7qmyrvz32SmHwddm5FU5IqaNGc2A6J30WNG/u/x78uYqq15q6PWtuL4NoFpphWU4bqrCvWP4fZrXcY37K8nc6iUxCabdb4wgEBcJgSbiqiu26Kqc6oHaW0683NVfZPlXw6tFIqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gdE76LGjf3f49+XMVVamnRktN1sHRt0osV9tku3XK3YPYokyHLZJl+M+3AZFxpxskQgMSRRUVRFRUVFql0ClKUEq1k/rF0J+8CZ/pW/VValWsn9YuhP3gTP9K36qrQKkuNf7tcv+fXn8xkVWql1xt1yw65TwO0XCdap0x6fHkQIpySaJ41ccbcbbRXN+sI1QkFUVCRF2VOd/A1RE1UzvnJUxdMzEVQ71KxHyljfYeT+rVx9xT5SxvsPJ/Vq4+4rSylRzhl6iHSDyyw6dZPiWc5NMs70FI1zsyW64XmBAI3JItbPt92Oti6go2QGLak5wOrwga7itY+Usb7Dyf1auPuKfKWN9h5P6tXH3FcqomYyeoqiJzeTWdE9TLli2PdyW/MUgZFg9kscu3WiVZYjEM46uGQzVuUZyQy2nXCQlEEnEUS3bQkFaq56TX4J91nMY+0kibqNAyDunrWVcOI1FjslIVd0XdEbMduRKich2Wq38pY32Hk/q1cfcU+Usb7Dyf1auPuK8RZyepuTLyratANUksF2xm7W/LLi9acRu9ggvXK42ELbLclkC8EMYzAzDA1BDIprraiu3+1VVNNw1z081ayW7XG0YbZrq3ap2PRIjTloSyNMynWTcI49xdmgUvgRFFGUjcIopnxGG/Gl6+Usb7Dyf1auPuKfKWN9h5P6tXH3FNTsy2mt25otecLz+35nOyuLp9Ovca1ZmzkbERiZBE7lHctCQ16jrnhEXmXU4lR4mhVOYmS8qyGkuluUY1lOP5BfbCxCBi033dlqQDg2051yaktw02JeLhbRRUgRQ3BURdlTes/KWN9h5P6tXH3FPlLG+w8n9Wrj7iu6rbm5rM4yZelYj5SxvsPJ/Vq4+4p8pY32Hk/q1cfcV7yl4zhl6ViPlLG+w8n9Wrj7iv6N8lS17ntGLZDKlHyBt60SYbe/1k6+AAKfWu6rt4kXxUymN7u/c2nSj/g9f+b3j8yk1uFYTDbA7jOORbRJfB6QJOvyXARUEn3nSddUd+fDxmW2/k2rN1h4iqK7tVVO6Zn1alqmabdMT4QUpSokhSlKBSlKBSlKBSlKBSlKBSlKBSlKCVdFD6L2kX/kex/8AsWqqtebdAtaMawDQzT3Bcsw/VKHe8dxe12q4xw0uyV8WZLEVtt0EcagE24iGJJxARCu26KqKi1vvaW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtKlXaW0683NVfZPlXw6naW0683NVfZPlXw6gqtK1/Cs4suf2p282KFkEWOzIKMQXvHrhZn1NBElUWZzLLpBsabGgqCqhIiqokibBQKkl9bzzAtTL/lmO6Z3TNYGWwYDCLbbjBZct0iMjo7PjMfZRI5C6JIrKuGhI5+75opVulB5jznSbUW/wCcW683fTi3XnIHo1lS05VbpDDUXD3o7ynPRpH3UlNA8K8Kdzg4ryeA9wCiVr7PRtzy3Y467jOJxbPkd3x/JYN7mMymWnrgrtxbehR33myUjQmOuACXiRlHFTwPFXpXU/J5+FacZRmFqZjuzLJaJdwjhIEiaJxpojFDQVRVHdE32VF28qVoWVdJOz4ll8HT244ldTvd3tT0+1GEy2C1PcajG8Tbcc5iTEH92QdaTAs8WyK4m6KoSBdCc2kwr8uIaK/Iq1T5c523WHu23B3K05jZQwHgjvmw1vJXh4WzUU341XZVVMlA0A1Bt2Y5BNxvEo2PyLpd37g1fGHoraKTuOLDB0laNXlIJfFuqhvufGPEiqqZyy9OPTmBB0+teowhZ8ky+z2u4z4/fK2sBbim7CyvUPTEkPgZ7qncwSFAdlc4Kz936U8BbBkk3HNPMoOVbbPfJ9oemtQwi3R+1ukzJaDhlcY8DiCv71GkIF3AiVFRAnVp6PV4vE61RS0OiYxh63e0d+salv291icbLE0J1wcZYdcZdB/r2AVTXrnkFVdbTasZqh0fdSb5pbjmmts0thTYtoj30bW4zHs78iySUlEVsFt2eZBFirH2BSisnIHZpBJlBVateAdJC1ZnmUHTlzDb+zfFtcOdc3gWEceA6/GR8RdZblHKBtUVRF9WVjqfgI8pcqsdBP8AR/GshxqJk6ZFBKM7c8ikXBhCeBxXGTZZFDVQJdlUgJNl58v4pVApSgUpU/ynXDC8PvsrHLtZdQJEuJwdY5a9Pb/c4pcYCacEmJCcZc5EiLwGvCW4rsQqiBQKVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1BVaVKu0tp15uaq+yfKvh1O0tp15uaq+yfKvh1Bgc60EssjFrrHumo4WB66Zq3fWLmbINi2sqZGTvcSG5wuo+YNtJzRVcNtRHjEUXO23RO6Qc6j3l3NGXcXt98mZPAs42vhlt3KS26DvWTOuUXGE690hbRgTQlTdwkThXTNWdV7Dqdjtnw7DsT1Jfub+YYpMFJmnGQwGAYi36DJfcckSYTbLYgyy6aqZong7Juqoi+iaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCVdE76LGjf3f49+XMVValXRO+ixo393+PflzFVWgUpSgUpSgVFpmoer93t991Hw1MTHEsckz2Es02BJduV4CEZtvmEwHwbhkptuoALHf3QBVSHj2C01Jblo5mondsbxTU2LZ8JyGTJk3G2nYlfuTCySUpIQZqSAbjgZEZfvY75CTh8JInAgBlGtdcPftM69MQrq5GgTbRBJRZbRXDuQxyYIEU08FElN8W+ypsWyLsm+lYz0m7vKgjHvukGWyb/Pvd/t9rtdpbt5OSmLdI4FJTOb1QFwKm6uOAhGBoKeE2hZDIejxeLhfZSY5n8azYtPnWS5ybR3l659JFtNjqwako+IgwbUcAIFaIkJEJDRNwXDXHRLVuz5lZ5uA5xZ4jcabklxauM2xFJZhjcXmXUjPxxmNHJXiV9RcbNpBUG+IV2VDDd2ukHgsmwXfJYca7PwrLYIeRPqMcBM48lXhFsRI0VHRVhxCEuFEXZN157c+nGpeQ5jkGodru+GzLdGxC9pbre8qx17ua7kZe3TgkGvHu4q+ELacJtptxIe2i3XotXYLS9jWI6oLbLPdMbi49eAnWdJkmT3M6662+08LzSMqRPuo6JA4hCqIHVqnEtRwzB5+JZLl93O+R5cHJ7gzc2oqQibdiujGaYcQnesJHRJGAIURsFHckVS3TYNUuXSExh+xWm6Y+E5Vu1ugXZHDgg+MViTNZig28CPtqLpE44KbEqD1TheFwoJ464dIhx7UDH8fsmJ3JnGpd7uNqn5HcY7Yw5HccSS4+kVQe60VbejoBE8yIGiH1anspD923o2RbZbsut7WYPOpkl+h3SGrsEFS1wo8sZYQAQSRTDrikkhku6dftsqAKV/GOjzdByGKEjO4x4bbbvc7vBsjVm6uSJ3BmSElt2Z16oYIcpwm+FoFFFUSVzwVEO1bek1iMq2T7tdcQy2yMtW4LvahnQ2CO+wnHRZZehiw84u5uOMijb/Uup1oKQCiqqbfp1qTD1Dj3NssbvWN3exy0hXSzXkGElxHCAXG1Io7rzBibZiYk26abLsqoQkKRvDuhdYcTst4s8SXhNsJ+HGh2mbjmAQbVLbWNIB9iROeQnDmvIbLPFwqw0fCS9UhEhDW9MsAv+IOX2+5nlkbIckyWY3Kny4VtK3wwFpoWmWmI5PPE2IgCKqk6akZEu6IqCIbzSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCVayf1i6E/eBM/wBK36qrUq1k/rF0J+8CZ/pW/VVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBStfy6blUOO0WOQgdBVXuh0WkfeaHbxgyTjaF/PjVUVERGz35S3IHr3d8/iQ4kvK5TQFCZFVagNEXCKyT8BwQUV/dtFsQoqLxIqbcIqFypWgZ5k863XR22M5IVlJq3LJgiDLTjtylESiLII4JcSIojuIIhL1ic0RK+cPi8cnJc7u7p93E45F4lBlVjNMAiGLZ8HFwoaHyVVReFFXdd1UNpzDGIGa4peMPur0hqHe4L9vkHHIRdFt0FAlBSRUQtlXbdFTfyLU7Ho04eGZScwbyTImxlXIbw5bGyiBEKakBYJPEQx0fcUo6oOxukIqKKCBz3x+Pv3jG7JcbjGvjsYGIcWZLNYkXifny9y4n3FAf3YI4Cqqqiomy77Jwr27TmeVXJo4KZY31bEuS8/co6sSl7jYjATnAaR22y3cdHhLq+X/AI08Yfdh6N9nxc7ImP6lZ1b49rt0G1zo0adFaC8x4ar3Kkohjo4BAJKClGJgjHwXFNK77fR6wlu1BZluF5OOMa/xfCfa4lC7vdbJ3VG05iS7N/UnzuLx1i3ssz+1QxKTkLLxPQLest+W2zGZt7sgy8PjFouFOANlI0MUM0XhQfBr7l5tkMO0QO7cyYF11x9xtyKrJPy2d0FpWlfjtNSdj4uIWkbIhUFFV38IOzC6O2Os5fiuX3fMMkvh4Y0PemHchgONMSUjdzlJBwYoyGiNvkTTToMKvhdVvzqr1pnfrJIN1lnIeI7FH4ylzp8UIYxlRPC6o1LicEf7KEyqLuv75dtq1aZfp16xZ5/Jr3EjgzOjMnDuQ9zR5MdPDEpBiBK11489lRQTZA4d+JKCuUrz7IG4FbLc1e4tlYtxRpk+2WeYy6bUlx19UaYYbRQXjRtR4OW4daioG/zd0ayedPcvEFjInoNqiQZDkKYjSOk4QtoDgiSCpOBHNd1MfCJVTmqCqkFOpUBZCQdukhCPHbRjcqdBgyZTLxu259G23CedNzZvrEMurAl3FCVOBTXmtVrTsW0xCCbNtZgtuK44DLAmDKirhKhtgaqrYGmxoG+yIWyUGyUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSglXRO+ixo393+PflzFVWpV0TvosaN/d/j35cxVVoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoJVrJ/WLoT94Ez/St+qq1KtZP6xdCfvAmf6Vv1VWgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgVrGp2XSMB07yTNolvbnP2O2SJzUZx1WwdNsFJBIkRVFFVE3VEWtnrE5XjFozXGbpiN/Zcdtt4iOwpQNuk0atOColwmKoQrsvJUXdKDz7qRqJ0pNLUx12VkGm2ULee6JD8WDh8+E62xFYWTIBpTujqOOE0DggqoKIaCqoSKqJsma6o6sZTlUGwdHp3FX4DFthXO9XS82x6a3HamGisK2DcuNxKjIOukCkpbE1snhLWzYxog9aMjtWS5bq3mucPWFp5u1MX0LW0zEJ1vqzcRIMKMThq3uP70jREJVRN+dZXTvRzD9K8XuuKYR3bBj3aZKmuPk6LzzTj3IRbUxUUBoEBtoFFRAGwHZUTmEgxvpOZJp/pZF1M6SNyxNuJkTduLGlszTVnSc/IYN12Kq3C4G0JN8Cr1rrzIKn8dt89G6ZemE7T236pW6z3ydjL1zds91uEJ+3SY9jlBtskt5qWTSgSqIi6wbzaqY7km6VtS6AWFNO8TwNrL8lal4QTTthyNs4iXSI6AE2jn+79zGqtGbZCbBAQku4qvOsHk/Rdt2c2GDjOeawag5LbGri7c7lEujtsej3l0tkbCUz3EjSMtIicDLAtN8ScZCZ+FQdbJukPnEG26cXqw9HzOXQza4uMSLZMK0M3CMykZ51sFE7iDbbx9WJohESCAuCfA5wivV1TzjpMW3LrREwRnDbJaL88xAt8e/2J24zHJZQZUl0ScjXNlsOEo4NbcKjuakjhImy7aGhJDp/juEHq3nT8/FJQS7Tk8g7c9dmSFs2kElOGsZ1OqcNvdxgiVF3UlNEKszctKYtzTCetzTJhLCJ6XFlwpDD7lyd6lxle6zeZMiRRdc/wBkraopclREREDVpGvzttZS33bDL4LtvGNb8iyOHFjyLLYrs80Cqy8HdQynQA3W0ImQcAENON0OEyHEBrNqGeCtY8i42mqx5QuIm13vf72hIQutKX3OkjrVZ7h/7Rw9fvzQVJF322u+6CWO+5FLuZZllUOyXac3c7xjEWTHS2XSUCBs46pMlJBF6tpSbZfbbc4PDAuI0POBpPhwapO6wBFfTIXrWlqIut/cICFv1qN7bdco7Ap+PgFB8VBjcM1PmXa8Zxb8ts02wBh5sk53fGjNocYmiLukXI8yQhtn1ZmKGjTgDsJAq86m7nSvftuf9bmWH3bDNPAw+bk/fTIIEcHJLbDzIi8y5HmuqgEDyKrD0dp4VUN+ZKI7tZdBHYGU37I77rDm2Sxcnj9yXazXSPZhhSmEbNsG17ngNPCgC4SJwuoq8uJSrEXDoq45fupj5ZqXnd+t0a3TLKxAlyoTTTVukI1/2YTYitup1ZMNGD/Wd0IQJxOknKgxmDdNrRzUCJfO8Sy3bpZViIFpi3G1XKTcFlOK1GFg4Ex9jiNxODgddbIPnOIALxVlcx6QFxiabZBkdqxG9Y1fsclx414g32zFc3bLHdVCWc7GtjzndjKM7ubRn132VCIFA0HKM6ALJt0qJlusmomUSSGKtum3CZCYctTsdzrGn47cOKwwrqHsqm826pInAXECkC9iJoasK1SgZ1azxMinzguEzJ0kwhuEk22jaZbNoYqQ+pbE/BZSOjakKEQkSkpBPLNrFq9qKzhEnSvUzSK6QcrG7OBc49jn3CM+EUWiaTYZzJxXS4yRxokcVouW5KK79XM+kbmkKx4FkEjOtNNLYl/lXW0Xx7MIjk+LFuMJXBIWJKT4Qk2TjLgjxChEiiWwruFbnK6MrByLZcrXrRqDaLrCenyplzhd6O6LnImC2L7z/WQDACUGgAUYFkRQdxRF51nI2gmPWefgz+K5TkVhgYH3QsS2RXIr7M8nxVHjluymHZDpnxGpGLokRGRKqku9Bi7DrNlcxNMmr/hc+3rmiuNTJzcNooaPiw+YtCjkpuUx1iM9cJLHeHgVAJRJeJEjpK2aDe73bbjprm0W3YvdktN9vZsQSg20jFsmXj4JSvONOC82u7TbhNou7otJWW1J0ZueouR2jIWdZ84xdLE8kqDCszFmKO3JRt1tX1WXAfcIlbeMVFTUPEqCi860Kw9H/P7vqBmk7UPIHIeIZFdGbgVus2Qi+N7FphhkAnsuW1s2N0Y4ySLJAT41bMTBOYd6N01tC5mq7WksPIGH5r91KxBOau1sNvviKkJR1ipK7vTYxUOtWKjO/wD9psqLW8YBk2czM5znEsxn2O4N2J2G/bHbXa3YRJGkg4YtPI7JeRxweBEVwVbEt1XgHxVj7doHDseRJccc1Oziz4+t3cvjmKwZURq2OS3DVxxVc7m7sRs3SVwmRko0pKqKHAqgv3jGil5xnNrvnBa557dHr2Aty4U2NZEjEgAYs7dTbm3E6pDVR8PmqJx8flDrW3XB9nCs9zbJMLyCP8h577UuzpGhBObYbYae3QgnOsPL1bnWcSOtqqLw9WhJsvRvHSowDEscvuRahWe9YaNm7jNqNfTgsOXBmWZBEdZMZJNNi6bZjtIcZJvgJXRaFOKuKP0Z5g2vMrPcekBqTco2csPN3UZDFgFetcabZJ9tWrYHCfVNCCJzDZVXg4tiTtZD0abHlFykXe9ai5m7LO3WuHFdacgMlAkW9wnY05km4or16OG4RCamyaOEJNKC8FBltDOkDgHSBstzu+DyFRyyze4LjEKdBmFHdUEMP38F+RGcEgJFRW3j25iWxCQpS61XAsHnYWxPW7ah5VmE24vC67Nvz0bibQRQRbaZissR2hRE3XgaFSVVUlJdttqoJV0TvosaN/d/j35cxVVqVdE76LGjf3f49+XMVVaBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKBSlKCRa+y3rJfdJcxcst9uNsxvNn5t0WzWWXdZEeO5j94ig4seI248Q9fJYBVEF26xFXZN1TsdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSgdpbTrzc1V9k+VfDqdpbTrzc1V9k+VfDqUoHaW0683NVfZPlXw6naW0683NVfZPlXw6lKB2ltOvNzVX2T5V8Op2ltOvNzVX2T5V8OpSg7vRqsl3xro5aV45kFtkW+6WrCbHCnQ5Dag7HkNQGQcbMV5iQkKoqL4lRapFKUClKUClKUClKUClKUClKUClKUClKUClKUH//2Q==)' ] }, { - "cell_type": "markdown", - "source": [ - "### Define the conversion function\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define the conversion function\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion." + 'cell_type': 'markdown', + 'source': [ + 'Model components are PyTorch modules, that can be converted with `ov.convert_model` function directly. We also use `ov.save_model` function to serialize the result of conversion.' ] }, { - "cell_type": "code", - "source": [ - "warnings.filterwarnings(\"ignore\", category=torch.jit.TracerWarning)" + 'cell_type': 'code', + 'source': [ + 'warnings.filterwarnings("ignore", category=torch.jit.TracerWarning)' ] }, { - "cell_type": "code", - "source": [ - "from pathlib import Path\n", - "\n", - "\n", - "def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n", - " xml_path = Path(xml_path)\n", - " if not xml_path.exists():\n", - " xml_path.parent.mkdir(parents=True, exist_ok=True)\n", - " with torch.no_grad():\n", - " converted_model = ov.convert_model(model, **convert_kwargs)\n", - " ov.save_model(converted_model, xml_path)\n", - " del converted_model\n", - " gc.collect()\n", - " torch._C._jit_clear_class_registry()\n", - " torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n", - " torch.jit._state._clear_class_state()\n", - " return xml_path" + 'cell_type': 'code', + 'source': [ + 'from pathlib import Path\n', + '\n', + '\n', + 'def convert(model: torch.nn.Module, xml_path: str, **convert_kwargs) -> Path:\n', + ' xml_path = Path(xml_path)\n', + ' if not xml_path.exists():\n', + ' xml_path.parent.mkdir(parents=True, exist_ok=True)\n', + ' with torch.no_grad():\n', + ' converted_model = ov.convert_model(model, **convert_kwargs)\n', + ' ov.save_model(converted_model, xml_path)\n', + ' del converted_model\n', + ' gc.collect()\n', + ' torch._C._jit_clear_class_registry()\n', + ' torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n', + ' torch.jit._state._clear_class_state()\n', + ' return xml_path' ] }, { - "cell_type": "markdown", - "source": [ - "### UNet\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### UNet\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output." + 'cell_type': 'markdown', + 'source': [ + 'Text-to-video generation pipeline main component is a conditional 3D UNet model that takes a noisy sample, conditional state, and a timestep and returns a sample shaped output.' ] }, { - "cell_type": "code", - "source": [ - "unet_xml_path = convert(\n", - " unet,\n", - " \"models/unet.xml\",\n", - " example_input={\n", - " \"sample\": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n", - " \"timestep\": torch.tensor(1),\n", - " \"encoder_hidden_states\": torch.randn(2, 77, 1024),\n", - " },\n", - " input=[\n", - " (\"sample\", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n", - " (\"timestep\", ()),\n", - " (\"encoder_hidden_states\", (2, 77, 1024)),\n", - " ],\n", - ")\n", - "del unet\n", - "gc.collect();" + 'cell_type': 'code', + 'source': [ + 'unet_xml_path = convert(\n', + ' unet,\n', + ' "models/unet.xml",\n', + ' example_input={\n', + ' "sample": torch.randn(2, 4, 2, int(sample_height // 2), int(sample_width // 2)),\n', + ' "timestep": torch.tensor(1),\n', + ' "encoder_hidden_states": torch.randn(2, 77, 1024),\n', + ' },\n', + ' input=[\n', + ' ("sample", (2, 4, NUM_FRAMES, sample_height, sample_width)),\n', + ' ("timestep", ()),\n', + ' ("encoder_hidden_states", (2, 77, 1024)),\n', + ' ],\n', + ')\n', + 'del unet\n', + 'gc.collect();' ] }, { - "cell_type": "markdown", - "source": [ - "### VAE\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### VAE\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "markdown", - "source": [ - "Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part." + 'cell_type': 'markdown', + 'source': [ + 'Variational autoencoder (VAE) uses UNet output to decode latents to visual representations. Our VAE model has KL loss for encoding images into latents and decoding latent representations into images. For inference, we need only decoder part.' ] }, { - "cell_type": "code", - "source": [ - "class VaeDecoderWrapper(torch.nn.Module):\n", - " def __init__(self, vae):\n", - " super().__init__()\n", - " self.vae = vae\n", - "\n", - " def forward(self, z: torch.FloatTensor):\n", - " return self.vae.decode(z)" + 'cell_type': 'code', + 'source': [ + 'class VaeDecoderWrapper(torch.nn.Module):\n', + ' def __init__(self, vae):\n', + ' super().__init__()\n', + ' self.vae = vae\n', + '\n', + ' def forward(self, z: torch.FloatTensor):\n', + ' return self.vae.decode(z)' ] }, { - "cell_type": "code", - "source": [ - "vae_decoder_xml_path = convert(\n", - " VaeDecoderWrapper(vae),\n", - " \"models/vae.xml\",\n", - " example_input=torch.randn(2, 4, 32, 32),\n", - " input=((NUM_FRAMES, 4, sample_height, sample_width)),\n", - ")\n", - "del vae\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Text encoder\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "markdown", - "source": [ - "Text encoder is used to encode the input prompt to tensor. Default tensor length is 77." - ] - }, - { - "cell_type": "code", - "source": [ - "text_encoder_xml = convert(\n", - " text_encoder,\n", - " \"models/text_encoder.xml\",\n", - " example_input=torch.ones(1, 77, dtype=torch.int64),\n", - " input=((1, 77), ov.Type.i64),\n", - ")\n", - "del text_encoder\n", - "gc.collect();" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Build a pipeline\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n", - " # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n", - " # reshape to ncfhw\n", - " mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n", - " # unnormalize back to [0,1]\n", - " video = video.mul_(std).add_(mean)\n", - " video.clamp_(0, 1)\n", - " # prepare the final outputs\n", - " i, c, f, h, w = video.shape\n", - " images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n", - " images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n", - " images = [(image.cpu().numpy() * 255).astype(\"uint8\") for image in images] # f h w c\n", - " return images" - ] - }, - { - "cell_type": "code", - "source": [ - "try:\n", - " from diffusers.utils import randn_tensor\n", - "except ImportError:\n", - " from diffusers.utils.torch_utils import randn_tensor\n", - "\n", - "\n", - "class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n", - " def __init__(\n", - " self,\n", - " vae_decoder: ov.CompiledModel,\n", - " text_encoder: ov.CompiledModel,\n", - " tokenizer: transformers.CLIPTokenizer,\n", - " unet: ov.CompiledModel,\n", - " scheduler: diffusers.schedulers.DDIMScheduler,\n", - " ):\n", - " super().__init__()\n", - "\n", - " self.vae_decoder = vae_decoder\n", - " self.text_encoder = text_encoder\n", - " self.tokenizer = tokenizer\n", - " self.unet = unet\n", - " self.scheduler = scheduler\n", - " self.vae_scale_factor = vae_scale_factor\n", - " self.unet_in_channels = unet_in_channels\n", - " self.width = WIDTH\n", - " self.height = HEIGHT\n", - " self.num_frames = NUM_FRAMES\n", - "\n", - " def __call__(\n", - " self,\n", - " prompt: Union[str, List[str]] = None,\n", - " num_inference_steps: int = 50,\n", - " guidance_scale: float = 9.0,\n", - " negative_prompt: Optional[Union[str, List[str]]] = None,\n", - " eta: float = 0.0,\n", - " generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n", - " latents: Optional[torch.FloatTensor] = None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " output_type: Optional[str] = \"np\",\n", - " return_dict: bool = True,\n", - " callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n", - " callback_steps: int = 1,\n", - " ):\n", - " r\"\"\"\n", - " Function invoked when calling the pipeline for generation.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n", - " instead.\n", - " num_inference_steps (`int`, *optional*, defaults to 50):\n", - " The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n", - " expense of slower inference.\n", - " guidance_scale (`float`, *optional*, defaults to 7.5):\n", - " Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n", - " `guidance_scale` is defined as `w` of equation 2. of [Imagen\n", - " Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n", - " 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n", - " usually at the expense of lower video quality.\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the video generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " eta (`float`, *optional*, defaults to 0.0):\n", - " Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n", - " [`schedulers.DDIMScheduler`], will be ignored for others.\n", - " generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n", - " One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n", - " to make generation deterministic.\n", - " latents (`torch.FloatTensor`, *optional*):\n", - " Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n", - " generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n", - " tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n", - " `(batch_size, num_channel, num_frames, height, width)`.\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " output_type (`str`, *optional*, defaults to `\"np\"`):\n", - " The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n", - " return_dict (`bool`, *optional*, defaults to `True`):\n", - " Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n", - " plain tuple.\n", - " callback (`Callable`, *optional*):\n", - " A function that will be called every `callback_steps` steps during inference. The function will be\n", - " called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n", - " callback_steps (`int`, *optional*, defaults to 1):\n", - " The frequency at which the `callback` function will be called. If not specified, the callback will be\n", - " called at every step.\n", - "\n", - " Returns:\n", - " `List[np.ndarray]`: generated video frames\n", - " \"\"\"\n", - "\n", - " num_images_per_prompt = 1\n", - "\n", - " # 1. Check inputs. Raise error if not correct\n", - " self.check_inputs(\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt,\n", - " prompt_embeds,\n", - " negative_prompt_embeds,\n", - " )\n", - "\n", - " # 2. Define call parameters\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n", - " # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n", - " # corresponds to doing no classifier free guidance.\n", - " do_classifier_free_guidance = guidance_scale > 1.0\n", - "\n", - " # 3. Encode input prompt\n", - " prompt_embeds = self._encode_prompt(\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt,\n", - " prompt_embeds=prompt_embeds,\n", - " negative_prompt_embeds=negative_prompt_embeds,\n", - " )\n", - "\n", - " # 4. Prepare timesteps\n", - " self.scheduler.set_timesteps(num_inference_steps)\n", - " timesteps = self.scheduler.timesteps\n", - "\n", - " # 5. Prepare latent variables\n", - " num_channels_latents = self.unet_in_channels\n", - " latents = self.prepare_latents(\n", - " batch_size * num_images_per_prompt,\n", - " num_channels_latents,\n", - " prompt_embeds.dtype,\n", - " generator,\n", - " latents,\n", - " )\n", - "\n", - " # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n", - " extra_step_kwargs = {\"generator\": generator, \"eta\": eta}\n", - "\n", - " # 7. Denoising loop\n", - " num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n", - " with self.progress_bar(total=num_inference_steps) as progress_bar:\n", - " for i, t in enumerate(timesteps):\n", - " # expand the latents if we are doing classifier free guidance\n", - " latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n", - " latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n", - "\n", - " # predict the noise residual\n", - " noise_pred = self.unet(\n", - " {\n", - " \"sample\": latent_model_input,\n", - " \"timestep\": t,\n", - " \"encoder_hidden_states\": prompt_embeds,\n", - " }\n", - " )[0]\n", - " noise_pred = torch.tensor(noise_pred)\n", - "\n", - " # perform guidance\n", - " if do_classifier_free_guidance:\n", - " noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n", - " noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n", - "\n", - " # reshape latents\n", - " bsz, channel, frames, width, height = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - " noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n", - "\n", - " # compute the previous noisy sample x_t -> x_t-1\n", - " latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n", - "\n", - " # reshape latents back\n", - " latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n", - "\n", - " # call the callback, if provided\n", - " if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n", - " progress_bar.update()\n", - " if callback is not None and i % callback_steps == 0:\n", - " callback(i, t, latents)\n", - "\n", - " video_tensor = self.decode_latents(latents)\n", - "\n", - " if output_type == \"pt\":\n", - " video = video_tensor\n", - " else:\n", - " video = tensor2vid(video_tensor)\n", - "\n", - " if not return_dict:\n", - " return (video,)\n", - "\n", - " return {\"frames\": video}\n", - "\n", - " # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n", - " def _encode_prompt(\n", - " self,\n", - " prompt,\n", - " num_images_per_prompt,\n", - " do_classifier_free_guidance,\n", - " negative_prompt=None,\n", - " prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n", - " ):\n", - " r\"\"\"\n", - " Encodes the prompt into text encoder hidden states.\n", - "\n", - " Args:\n", - " prompt (`str` or `List[str]`, *optional*):\n", - " prompt to be encoded\n", - " num_images_per_prompt (`int`):\n", - " number of images that should be generated per prompt\n", - " do_classifier_free_guidance (`bool`):\n", - " whether to use classifier free guidance or not\n", - " negative_prompt (`str` or `List[str]`, *optional*):\n", - " The prompt or prompts not to guide the image generation. If not defined, one has to pass\n", - " `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n", - " less than `1`).\n", - " prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n", - " provided, text embeddings will be generated from `prompt` input argument.\n", - " negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n", - " Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n", - " weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n", - " argument.\n", - " \"\"\"\n", - " if prompt is not None and isinstance(prompt, str):\n", - " batch_size = 1\n", - " elif prompt is not None and isinstance(prompt, list):\n", - " batch_size = len(prompt)\n", - " else:\n", - " batch_size = prompt_embeds.shape[0]\n", - "\n", - " if prompt_embeds is None:\n", - " text_inputs = self.tokenizer(\n", - " prompt,\n", - " padding=\"max_length\",\n", - " max_length=self.tokenizer.model_max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - " text_input_ids = text_inputs.input_ids\n", - " untruncated_ids = self.tokenizer(prompt, padding=\"longest\", return_tensors=\"pt\").input_ids\n", - "\n", - " if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n", - " removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n", - " print(\n", - " \"The following part of your input was truncated because CLIP can only handle sequences up to\"\n", - " f\" {self.tokenizer.model_max_length} tokens: {removed_text}\"\n", - " )\n", - "\n", - " prompt_embeds = self.text_encoder(text_input_ids)\n", - " prompt_embeds = prompt_embeds[0]\n", - " prompt_embeds = torch.tensor(prompt_embeds)\n", - "\n", - " bs_embed, seq_len, _ = prompt_embeds.shape\n", - " # duplicate text embeddings for each generation per prompt, using mps friendly method\n", - " prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # get unconditional embeddings for classifier free guidance\n", - " if do_classifier_free_guidance and negative_prompt_embeds is None:\n", - " uncond_tokens: List[str]\n", - " if negative_prompt is None:\n", - " uncond_tokens = [\"\"] * batch_size\n", - " elif type(prompt) is not type(negative_prompt):\n", - " raise TypeError(f\"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=\" f\" {type(prompt)}.\")\n", - " elif isinstance(negative_prompt, str):\n", - " uncond_tokens = [negative_prompt]\n", - " elif batch_size != len(negative_prompt):\n", - " raise ValueError(\n", - " f\"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:\"\n", - " f\" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches\"\n", - " \" the batch size of `prompt`.\"\n", - " )\n", - " else:\n", - " uncond_tokens = negative_prompt\n", - "\n", - " max_length = prompt_embeds.shape[1]\n", - " uncond_input = self.tokenizer(\n", - " uncond_tokens,\n", - " padding=\"max_length\",\n", - " max_length=max_length,\n", - " truncation=True,\n", - " return_tensors=\"pt\",\n", - " )\n", - "\n", - " negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n", - " negative_prompt_embeds = negative_prompt_embeds[0]\n", - " negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n", - "\n", - " if do_classifier_free_guidance:\n", - " # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n", - " seq_len = negative_prompt_embeds.shape[1]\n", - "\n", - " negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n", - " negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n", - "\n", - " # For classifier free guidance, we need to do two forward passes.\n", - " # Here we concatenate the unconditional and text embeddings into a single batch\n", - " # to avoid doing two forward passes\n", - " prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n", - "\n", - " return prompt_embeds\n", - "\n", - " def prepare_latents(\n", - " self,\n", - " batch_size,\n", - " num_channels_latents,\n", - " dtype,\n", - " generator,\n", - " latents=None,\n", - " ):\n", - " shape = (\n", - " batch_size,\n", - " num_channels_latents,\n", - " self.num_frames,\n", - " self.height // self.vae_scale_factor,\n", - " self.width // self.vae_scale_factor,\n", - " )\n", - " if isinstance(generator, list) and len(generator) != batch_size:\n", - " raise ValueError(\n", - " f\"You have passed a list of generators of length {len(generator)}, but requested an effective batch\"\n", - " f\" size of {batch_size}. Make sure the batch size matches the length of the generators.\"\n", - " )\n", - "\n", - " if latents is None:\n", - " latents = randn_tensor(shape, generator=generator, dtype=dtype)\n", - "\n", - " # scale the initial noise by the standard deviation required by the scheduler\n", - " latents = latents * self.scheduler.init_noise_sigma\n", - " return latents\n", - "\n", - " def check_inputs(\n", - " self,\n", - " prompt,\n", - " callback_steps,\n", - " negative_prompt=None,\n", - " prompt_embeds=None,\n", - " negative_prompt_embeds=None,\n", - " ):\n", - " if self.height % 8 != 0 or self.width % 8 != 0:\n", - " raise ValueError(f\"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.\")\n", - "\n", - " if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n", - " raise ValueError(f\"`callback_steps` has to be a positive integer but is {callback_steps} of type\" f\" {type(callback_steps)}.\")\n", - "\n", - " if prompt is not None and prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to\" \" only forward one of the two.\"\n", - " )\n", - " elif prompt is None and prompt_embeds is None:\n", - " raise ValueError(\"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.\")\n", - " elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n", - " raise ValueError(f\"`prompt` has to be of type `str` or `list` but is {type(prompt)}\")\n", - "\n", - " if negative_prompt is not None and negative_prompt_embeds is not None:\n", - " raise ValueError(\n", - " f\"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:\"\n", - " f\" {negative_prompt_embeds}. Please make sure to only forward one of the two.\"\n", - " )\n", - "\n", - " if prompt_embeds is not None and negative_prompt_embeds is not None:\n", - " if prompt_embeds.shape != negative_prompt_embeds.shape:\n", - " raise ValueError(\n", - " \"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but\"\n", - " f\" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`\"\n", - " f\" {negative_prompt_embeds.shape}.\"\n", - " )\n", - "\n", - " def decode_latents(self, latents):\n", - " scale_factor = 0.18215\n", - " latents = 1 / scale_factor * latents\n", - "\n", - " batch_size, channels, num_frames, height, width = latents.shape\n", - " latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n", - " image = self.vae_decoder(latents)[0]\n", - " image = torch.tensor(image)\n", - " video = (\n", - " image[None, :]\n", - " .reshape(\n", - " (\n", - " batch_size,\n", - " num_frames,\n", - " -1,\n", - " )\n", - " + image.shape[2:]\n", - " )\n", - " .permute(0, 2, 1, 3, 4)\n", - " )\n", - " # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n", - " video = video.float()\n", - " return video" - ] - }, - { - "cell_type": "markdown", - "source": [ - "## Inference with OpenVINO\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "code", - "source": [ - "core = ov.Core()" - ] - }, - { - "cell_type": "markdown", - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "select device from dropdown list for running inference using OpenVINO" + 'cell_type': 'code', + 'source': [ + 'vae_decoder_xml_path = convert(\n', + ' VaeDecoderWrapper(vae),\n', + ' "models/vae.xml",\n', + ' example_input=torch.randn(2, 4, 32, 32),\n', + ' input=((NUM_FRAMES, 4, sample_height, sample_width)),\n', + ')\n', + 'del vae\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Text encoder\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + 'Text encoder is used to encode the input prompt to tensor. Default tensor length is 77.' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'text_encoder_xml = convert(\n', + ' text_encoder,\n', + ' "models/text_encoder.xml",\n', + ' example_input=torch.ones(1, 77, dtype=torch.int64),\n', + ' input=((1, 77), ov.Type.i64),\n', + ')\n', + 'del text_encoder\n', + 'gc.collect();' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Build a pipeline\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'def tensor2vid(video: torch.Tensor, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) -> List[np.ndarray]:\n', + ' # This code is copied from https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78\n', + ' # reshape to ncfhw\n', + ' mean = torch.tensor(mean, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' std = torch.tensor(std, device=video.device).reshape(1, -1, 1, 1, 1)\n', + ' # unnormalize back to [0,1]\n', + ' video = video.mul_(std).add_(mean)\n', + ' video.clamp_(0, 1)\n', + ' # prepare the final outputs\n', + ' i, c, f, h, w = video.shape\n', + ' images = video.permute(2, 3, 0, 4, 1).reshape(f, h, i * w, c) # 1st (frames, h, batch_size, w, c) 2nd (frames, h, batch_size * w, c)\n', + ' images = images.unbind(dim=0) # prepare a list of indvidual (consecutive frames)\n', + ' images = [(image.cpu().numpy() * 255).astype("uint8") for image in images] # f h w c\n', + ' return images' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'try:\n', + ' from diffusers.utils import randn_tensor\n', + 'except ImportError:\n', + ' from diffusers.utils.torch_utils import randn_tensor\n', + '\n', + '\n', + 'class OVTextToVideoSDPipeline(diffusers.DiffusionPipeline):\n', + ' def __init__(\n', + ' self,\n', + ' vae_decoder: ov.CompiledModel,\n', + ' text_encoder: ov.CompiledModel,\n', + ' tokenizer: transformers.CLIPTokenizer,\n', + ' unet: ov.CompiledModel,\n', + ' scheduler: diffusers.schedulers.DDIMScheduler,\n', + ' ):\n', + ' super().__init__()\n', + '\n', + ' self.vae_decoder = vae_decoder\n', + ' self.text_encoder = text_encoder\n', + ' self.tokenizer = tokenizer\n', + ' self.unet = unet\n', + ' self.scheduler = scheduler\n', + ' self.vae_scale_factor = vae_scale_factor\n', + ' self.unet_in_channels = unet_in_channels\n', + ' self.width = WIDTH\n', + ' self.height = HEIGHT\n', + ' self.num_frames = NUM_FRAMES\n', + '\n', + ' def __call__(\n', + ' self,\n', + ' prompt: Union[str, List[str]] = None,\n', + ' num_inference_steps: int = 50,\n', + ' guidance_scale: float = 9.0,\n', + ' negative_prompt: Optional[Union[str, List[str]]] = None,\n', + ' eta: float = 0.0,\n', + ' generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n', + ' latents: Optional[torch.FloatTensor] = None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' output_type: Optional[str] = "np",\n', + ' return_dict: bool = True,\n', + ' callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,\n', + ' callback_steps: int = 1,\n', + ' ):\n', + ' r"""\n', + ' Function invoked when calling the pipeline for generation.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts to guide the video generation. If not defined, one has to pass `prompt_embeds`.\n', + ' instead.\n', + ' num_inference_steps (`int`, *optional*, defaults to 50):\n', + ' The number of denoising steps. More denoising steps usually lead to a higher quality videos at the\n', + ' expense of slower inference.\n', + ' guidance_scale (`float`, *optional*, defaults to 7.5):\n', + ' Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).\n', + ' `guidance_scale` is defined as `w` of equation 2. of [Imagen\n', + ' Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >\n', + ' 1`. Higher guidance scale encourages to generate videos that are closely linked to the text `prompt`,\n', + ' usually at the expense of lower video quality.\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the video generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' eta (`float`, *optional*, defaults to 0.0):\n', + ' Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to\n', + ' [`schedulers.DDIMScheduler`], will be ignored for others.\n', + ' generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n', + ' One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)\n', + ' to make generation deterministic.\n', + ' latents (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for video\n', + ' generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n', + ' tensor will ge generated by sampling using the supplied random `generator`. Latents should be of shape\n', + ' `(batch_size, num_channel, num_frames, height, width)`.\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' output_type (`str`, *optional*, defaults to `"np"`):\n', + ' The output format of the generate video. Choose between `torch.FloatTensor` or `np.array`.\n', + ' return_dict (`bool`, *optional*, defaults to `True`):\n', + ' Whether or not to return a [`~pipelines.stable_diffusion.TextToVideoSDPipelineOutput`] instead of a\n', + ' plain tuple.\n', + ' callback (`Callable`, *optional*):\n', + ' A function that will be called every `callback_steps` steps during inference. The function will be\n', + ' called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.\n', + ' callback_steps (`int`, *optional*, defaults to 1):\n', + ' The frequency at which the `callback` function will be called. If not specified, the callback will be\n', + ' called at every step.\n', + '\n', + ' Returns:\n', + ' `List[np.ndarray]`: generated video frames\n', + ' """\n', + '\n', + ' num_images_per_prompt = 1\n', + '\n', + ' # 1. Check inputs. Raise error if not correct\n', + ' self.check_inputs(\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt,\n', + ' prompt_embeds,\n', + ' negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 2. Define call parameters\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)\n', + ' # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`\n', + ' # corresponds to doing no classifier free guidance.\n', + ' do_classifier_free_guidance = guidance_scale > 1.0\n', + '\n', + ' # 3. Encode input prompt\n', + ' prompt_embeds = self._encode_prompt(\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt,\n', + ' prompt_embeds=prompt_embeds,\n', + ' negative_prompt_embeds=negative_prompt_embeds,\n', + ' )\n', + '\n', + ' # 4. Prepare timesteps\n', + ' self.scheduler.set_timesteps(num_inference_steps)\n', + ' timesteps = self.scheduler.timesteps\n', + '\n', + ' # 5. Prepare latent variables\n', + ' num_channels_latents = self.unet_in_channels\n', + ' latents = self.prepare_latents(\n', + ' batch_size * num_images_per_prompt,\n', + ' num_channels_latents,\n', + ' prompt_embeds.dtype,\n', + ' generator,\n', + ' latents,\n', + ' )\n', + '\n', + ' # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n', + ' extra_step_kwargs = {"generator": generator, "eta": eta}\n', + '\n', + ' # 7. Denoising loop\n', + ' num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n', + ' with self.progress_bar(total=num_inference_steps) as progress_bar:\n', + ' for i, t in enumerate(timesteps):\n', + ' # expand the latents if we are doing classifier free guidance\n', + ' latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents\n', + ' latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n', + '\n', + ' # predict the noise residual\n', + ' noise_pred = self.unet(\n', + ' {\n', + ' "sample": latent_model_input,\n', + ' "timestep": t,\n', + ' "encoder_hidden_states": prompt_embeds,\n', + ' }\n', + ' )[0]\n', + ' noise_pred = torch.tensor(noise_pred)\n', + '\n', + ' # perform guidance\n', + ' if do_classifier_free_guidance:\n', + ' noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n', + ' noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)\n', + '\n', + ' # reshape latents\n', + ' bsz, channel, frames, width, height = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + ' noise_pred = noise_pred.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channel, width, height)\n', + '\n', + ' # compute the previous noisy sample x_t -> x_t-1\n', + ' latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample\n', + '\n', + ' # reshape latents back\n', + ' latents = latents[None, :].reshape(bsz, frames, channel, width, height).permute(0, 2, 1, 3, 4)\n', + '\n', + ' # call the callback, if provided\n', + ' if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n', + ' progress_bar.update()\n', + ' if callback is not None and i % callback_steps == 0:\n', + ' callback(i, t, latents)\n', + '\n', + ' video_tensor = self.decode_latents(latents)\n', + '\n', + ' if output_type == "pt":\n', + ' video = video_tensor\n', + ' else:\n', + ' video = tensor2vid(video_tensor)\n', + '\n', + ' if not return_dict:\n', + ' return (video,)\n', + '\n', + ' return {"frames": video}\n', + '\n', + ' # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt\n', + ' def _encode_prompt(\n', + ' self,\n', + ' prompt,\n', + ' num_images_per_prompt,\n', + ' do_classifier_free_guidance,\n', + ' negative_prompt=None,\n', + ' prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' negative_prompt_embeds: Optional[torch.FloatTensor] = None,\n', + ' ):\n', + ' r"""\n', + ' Encodes the prompt into text encoder hidden states.\n', + '\n', + ' Args:\n', + ' prompt (`str` or `List[str]`, *optional*):\n', + ' prompt to be encoded\n', + ' num_images_per_prompt (`int`):\n', + ' number of images that should be generated per prompt\n', + ' do_classifier_free_guidance (`bool`):\n', + ' whether to use classifier free guidance or not\n', + ' negative_prompt (`str` or `List[str]`, *optional*):\n', + ' The prompt or prompts not to guide the image generation. If not defined, one has to pass\n', + ' `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is\n', + ' less than `1`).\n', + ' prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not\n', + ' provided, text embeddings will be generated from `prompt` input argument.\n', + ' negative_prompt_embeds (`torch.FloatTensor`, *optional*):\n', + ' Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt\n', + ' weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input\n', + ' argument.\n', + ' """\n', + ' if prompt is not None and isinstance(prompt, str):\n', + ' batch_size = 1\n', + ' elif prompt is not None and isinstance(prompt, list):\n', + ' batch_size = len(prompt)\n', + ' else:\n', + ' batch_size = prompt_embeds.shape[0]\n', + '\n', + ' if prompt_embeds is None:\n', + ' text_inputs = self.tokenizer(\n', + ' prompt,\n', + ' padding="max_length",\n', + ' max_length=self.tokenizer.model_max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + ' text_input_ids = text_inputs.input_ids\n', + ' untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids\n', + '\n', + ' if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):\n', + ' removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])\n', + ' print(\n', + ' "The following part of your input was truncated because CLIP can only handle sequences up to"\n', + ' f" {self.tokenizer.model_max_length} tokens: {removed_text}"\n', + ' )\n', + '\n', + ' prompt_embeds = self.text_encoder(text_input_ids)\n', + ' prompt_embeds = prompt_embeds[0]\n', + ' prompt_embeds = torch.tensor(prompt_embeds)\n', + '\n', + ' bs_embed, seq_len, _ = prompt_embeds.shape\n', + ' # duplicate text embeddings for each generation per prompt, using mps friendly method\n', + ' prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # get unconditional embeddings for classifier free guidance\n', + ' if do_classifier_free_guidance and negative_prompt_embeds is None:\n', + ' uncond_tokens: List[str]\n', + ' if negative_prompt is None:\n', + ' uncond_tokens = [""] * batch_size\n', + ' elif type(prompt) is not type(negative_prompt):\n', + ' raise TypeError(f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" f" {type(prompt)}.")\n', + ' elif isinstance(negative_prompt, str):\n', + ' uncond_tokens = [negative_prompt]\n', + ' elif batch_size != len(negative_prompt):\n', + ' raise ValueError(\n', + ' f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"\n', + ' f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"\n', + ' " the batch size of `prompt`."\n', + ' )\n', + ' else:\n', + ' uncond_tokens = negative_prompt\n', + '\n', + ' max_length = prompt_embeds.shape[1]\n', + ' uncond_input = self.tokenizer(\n', + ' uncond_tokens,\n', + ' padding="max_length",\n', + ' max_length=max_length,\n', + ' truncation=True,\n', + ' return_tensors="pt",\n', + ' )\n', + '\n', + ' negative_prompt_embeds = self.text_encoder(uncond_input.input_ids)\n', + ' negative_prompt_embeds = negative_prompt_embeds[0]\n', + ' negative_prompt_embeds = torch.tensor(negative_prompt_embeds)\n', + '\n', + ' if do_classifier_free_guidance:\n', + ' # duplicate unconditional embeddings for each generation per prompt, using mps friendly method\n', + ' seq_len = negative_prompt_embeds.shape[1]\n', + '\n', + ' negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)\n', + ' negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)\n', + '\n', + ' # For classifier free guidance, we need to do two forward passes.\n', + ' # Here we concatenate the unconditional and text embeddings into a single batch\n', + ' # to avoid doing two forward passes\n', + ' prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n', + '\n', + ' return prompt_embeds\n', + '\n', + ' def prepare_latents(\n', + ' self,\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' dtype,\n', + ' generator,\n', + ' latents=None,\n', + ' ):\n', + ' shape = (\n', + ' batch_size,\n', + ' num_channels_latents,\n', + ' self.num_frames,\n', + ' self.height // self.vae_scale_factor,\n', + ' self.width // self.vae_scale_factor,\n', + ' )\n', + ' if isinstance(generator, list) and len(generator) != batch_size:\n', + ' raise ValueError(\n', + ' f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"\n', + ' f" size of {batch_size}. Make sure the batch size matches the length of the generators."\n', + ' )\n', + '\n', + ' if latents is None:\n', + ' latents = randn_tensor(shape, generator=generator, dtype=dtype)\n', + '\n', + ' # scale the initial noise by the standard deviation required by the scheduler\n', + ' latents = latents * self.scheduler.init_noise_sigma\n', + ' return latents\n', + '\n', + ' def check_inputs(\n', + ' self,\n', + ' prompt,\n', + ' callback_steps,\n', + ' negative_prompt=None,\n', + ' prompt_embeds=None,\n', + ' negative_prompt_embeds=None,\n', + ' ):\n', + ' if self.height % 8 != 0 or self.width % 8 != 0:\n', + ' raise ValueError(f"`height` and `width` have to be divisible by 8 but are {self.height} and {self.width}.")\n', + '\n', + ' if (callback_steps is None) or (callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)):\n', + ' raise ValueError(f"`callback_steps` has to be a positive integer but is {callback_steps} of type" f" {type(callback_steps)}.")\n', + '\n', + ' if prompt is not None and prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" " only forward one of the two."\n', + ' )\n', + ' elif prompt is None and prompt_embeds is None:\n', + ' raise ValueError("Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined.")\n', + ' elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):\n', + ' raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")\n', + '\n', + ' if negative_prompt is not None and negative_prompt_embeds is not None:\n', + ' raise ValueError(\n', + ' f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"\n', + ' f" {negative_prompt_embeds}. Please make sure to only forward one of the two."\n', + ' )\n', + '\n', + ' if prompt_embeds is not None and negative_prompt_embeds is not None:\n', + ' if prompt_embeds.shape != negative_prompt_embeds.shape:\n', + ' raise ValueError(\n', + ' "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"\n', + ' f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"\n', + ' f" {negative_prompt_embeds.shape}."\n', + ' )\n', + '\n', + ' def decode_latents(self, latents):\n', + ' scale_factor = 0.18215\n', + ' latents = 1 / scale_factor * latents\n', + '\n', + ' batch_size, channels, num_frames, height, width = latents.shape\n', + ' latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)\n', + ' image = self.vae_decoder(latents)[0]\n', + ' image = torch.tensor(image)\n', + ' video = (\n', + ' image[None, :]\n', + ' .reshape(\n', + ' (\n', + ' batch_size,\n', + ' num_frames,\n', + ' -1,\n', + ' )\n', + ' + image.shape[2:]\n', + ' )\n', + ' .permute(0, 2, 1, 3, 4)\n', + ' )\n', + ' # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16\n', + ' video = video.float()\n', + ' return video' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '## Inference with OpenVINO\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'code', + 'source': [ + 'core = ov.Core()' + ] + }, + { + 'cell_type': 'markdown', + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'select device from dropdown list for running inference using OpenVINO' ] }, { - "cell_type": "code", - "source": [ - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "from notebook_utils import device_widget\n", - "\n", - "device = device_widget()\n", - "\n", - "device" + 'cell_type': 'code', + 'source': [ + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + '\n', + 'from notebook_utils import device_widget\n', + '\n', + 'device = device_widget()\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_unet = core.compile_model(unet_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_unet = core.compile_model(unet_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_vae_decoder = core.compile_model(vae_decoder_xml_path, device_name=device.value)' ] }, { - "cell_type": "code", - "source": [ - "%%time\n", - "ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)" + 'cell_type': 'code', + 'source': [ + '%%time\n', + 'ov_text_encoder = core.compile_model(text_encoder_xml, device_name=device.value)' ] }, { - "cell_type": "markdown", - "source": [ - "Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler." + 'cell_type': 'markdown', + 'source': [ + 'Here we replace the pipeline parts with versions converted to OpenVINO IR and compiled to specific device. Note that we use original pipeline tokenizer and scheduler.' ] }, { - "cell_type": "code", - "source": [ - "ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)" + 'cell_type': 'code', + 'source': [ + 'ov_pipe = OVTextToVideoSDPipeline(ov_vae_decoder, ov_text_encoder, tokenizer, ov_unet, scheduler)' ] }, { - "cell_type": "markdown", - "source": [ - "### Define a prompt\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Define a prompt\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "prompt = \"A panda eating bamboo on a rock.\"" + 'cell_type': 'code', + 'source': [ + 'prompt = "A panda eating bamboo on a rock."' ] }, { - "cell_type": "markdown", - "source": [ - "Let's generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section." + 'cell_type': 'markdown', + 'source': [ + 'Let\'s generate a video for our prompt. For full list of arguments, see `__call__` function definition of `OVTextToVideoSDPipeline` class in [Build a pipeline](#Build-a-pipeline) section.' ] }, { - "cell_type": "markdown", - "source": [ - "### Video generation\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '### Video generation\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "frames = ov_pipe(prompt, num_inference_steps=25)[\"frames\"]" + 'cell_type': 'code', + 'source': [ + 'frames = ov_pipe(prompt, num_inference_steps=25)["frames"]' ] }, { - "cell_type": "code", - "source": [ - "images = [PIL.Image.fromarray(frame) for frame in frames]\n", - "images[0].save(\"output.gif\", save_all=True, append_images=images[1:], duration=125, loop=0)\n", - "with open(\"output.gif\", \"rb\") as gif_file:\n", - " b64 = f\"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}\"\n", - "IPython.display.HTML(f'')" + 'cell_type': 'code', + 'source': [ + 'images = [PIL.Image.fromarray(frame) for frame in frames]\n', + 'images[0].save("output.gif", save_all=True, append_images=images[1:], duration=125, loop=0)\n', + 'with open("output.gif", "rb") as gif_file:\n', + ' b64 = f"data:image/gif;base64,{base64.b64encode(gif_file.read()).decode()}"\n', + `IPython.display.HTML(f'')` ] }, { - "cell_type": "markdown", - "source": [ - "## Interactive demo\n", - "[back to top ⬆️](#Table-of-contents:)\n" + 'cell_type': 'markdown', + 'source': [ + '## Interactive demo\n', + '[back to top ⬆️](#Table-of-contents:)\n' ] }, { - "cell_type": "code", - "source": [ - "def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n", - " generator = torch.Generator().manual_seed(seed)\n", - " frames = ov_pipe(\n", - " prompt,\n", - " num_inference_steps=num_inference_steps,\n", - " generator=generator,\n", - " )[\"frames\"]\n", - " out_file = tempfile.NamedTemporaryFile(suffix=\".gif\", delete=False)\n", - " images = [PIL.Image.fromarray(frame) for frame in frames]\n", - " images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n", - " return out_file.name\n", - "\n", - "\n", - "demo = gr.Interface(\n", - " generate,\n", - " [\n", - " gr.Textbox(label=\"Prompt\"),\n", - " gr.Slider(0, 1000000, value=42, label=\"Seed\", step=1),\n", - " gr.Slider(10, 50, value=25, label=\"Number of inference steps\", step=1),\n", - " ],\n", - " gr.Image(label=\"Result\"),\n", - " examples=[\n", - " [\"An astronaut riding a horse.\", 0, 25],\n", - " [\"A panda eating bamboo on a rock.\", 0, 25],\n", - " [\"Spiderman is surfing.\", 0, 25],\n", - " ],\n", - " allow_flagging=\"never\",\n", - ")\n", - "\n", - "try:\n", - " demo.queue().launch(debug=True)\n", - "except Exception:\n", - " demo.queue().launch(share=True, debug=True)\n", - "# if you are launching remotely, specify server_name and server_port\n", - "# demo.launch(server_name='your server name', server_port='server port in int')\n", - "# Read more in the docs: https://gradio.app/docs/" + 'cell_type': 'code', + 'source': [ + 'def generate(prompt, seed, num_inference_steps, _=gr.Progress(track_tqdm=True)):\n', + ' generator = torch.Generator().manual_seed(seed)\n', + ' frames = ov_pipe(\n', + ' prompt,\n', + ' num_inference_steps=num_inference_steps,\n', + ' generator=generator,\n', + ' )["frames"]\n', + ' out_file = tempfile.NamedTemporaryFile(suffix=".gif", delete=False)\n', + ' images = [PIL.Image.fromarray(frame) for frame in frames]\n', + ' images[0].save(out_file, save_all=True, append_images=images[1:], duration=125, loop=0)\n', + ' return out_file.name\n', + '\n', + '\n', + 'demo = gr.Interface(\n', + ' generate,\n', + ' [\n', + ' gr.Textbox(label="Prompt"),\n', + ' gr.Slider(0, 1000000, value=42, label="Seed", step=1),\n', + ' gr.Slider(10, 50, value=25, label="Number of inference steps", step=1),\n', + ' ],\n', + ' gr.Image(label="Result"),\n', + ' examples=[\n', + ' ["An astronaut riding a horse.", 0, 25],\n', + ' ["A panda eating bamboo on a rock.", 0, 25],\n', + ' ["Spiderman is surfing.", 0, 25],\n', + ' ],\n', + ' allow_flagging="never",\n', + ')\n', + '\n', + 'try:\n', + ' demo.queue().launch(debug=True)\n', + 'except Exception:\n', + ' demo.queue().launch(share=True, debug=True)\n', + '# if you are launching remotely, specify server_name and server_port\n', + `# demo.launch(server_name='your server name', server_port='server port in int')\n`, + '# Read more in the docs: https://gradio.app/docs/' ] } ].map(fromJupyterCell) @@ -8503,768 +8503,768 @@ suite('NotebookDiff Diff Service', () => { const { mapping, diff } = await mapCells( [ { - "cell_type": "markdown", - "id": "a2e7d62b-5779-4211-822c-457c77321f8b", - "source": [ - "# Convert and Optimize YOLOv11 instance segmentation model with OpenVINO™\n", - "\n", - "Instance segmentation goes a step further than object detection and involves identifying individual objects in an image and segmenting them from the rest of the image. Instance segmentation as an object detection are often used as key components in computer vision systems. \n", - "Applications that use real-time instance segmentation models include video analytics, robotics, autonomous vehicles, multi-object tracking and object counting, medical image analysis, and many others.\n", - "\n", - "\n", - "This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLOv11 with OpenVINO. We consider the steps required for instance segmentation scenario. You can find more details about model on [model page](https://docs.ultralytics.com/models/yolo11/) in Ultralytics documentation.\n", - "\n", - "The tutorial consists of the following steps:\n", - "- Prepare the PyTorch model.\n", - "- Download and prepare a dataset.\n", - "- Validate the original model.\n", - "- Convert the PyTorch model to OpenVINO IR.\n", - "- Validate the converted model.\n", - "- Prepare and run optimization pipeline.\n", - "- Compare performance of the FP32 and quantized models.\n", - "- Compare accuracy of the FP32 and quantized models.\n", - "- Live demo\n", - "\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Get PyTorch model](#Get-PyTorch-model)\n", - " - [Prerequisites](#Prerequisites)\n", - "- [Instantiate model](#Instantiate-model)\n", - " - [Convert model to OpenVINO IR](#Convert-model-to-OpenVINO-IR)\n", - " - [Verify model inference](#Verify-model-inference)\n", - " - [Select inference device](#Select-inference-device)\n", - " - [Test on single image](#Test-on-single-image)\n", - "- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n", - " - [Validate Quantized model inference](#Validate-Quantized-model-inference)\n", - "- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n", - " - [Compare performance of the Original and Quantized Models](#Compare-performance-of-the-Original-and-Quantized-Models)\n", - "- [Other ways to optimize model](#Other-ways-to-optimize-model)\n", - "- [Live demo](#Live-demo)\n", - " - [Run Live Object Detection and Segmentation](#Run-Live-Object-Detection-and-Segmentation)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "d7a12678-b12f-48d1-9735-398855733e46", - "source": [ - "## Get PyTorch model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Generally, PyTorch models represent an instance of the [`torch.nn.Module`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html) class, initialized by a state dictionary with model weights.\n", - "We will use the YOLOv11 nano model (also known as `yolo11n-seg`) pre-trained on a COCO dataset, which is available in this [repo](https://github.com/ultralytics/ultralytics). Similar steps are also applicable to other YOLOv11 models.\n", - "Typical steps to obtain a pre-trained model:\n", - "1. Create an instance of a model class.\n", - "2. Load a checkpoint state dict, which contains the pre-trained model weights.\n", - "3. Turn the model to evaluation for switching some operations to inference mode.\n", - "\n", - "In this case, the creators of the model provide an API that enables converting the YOLOv11 model to OpenVINO IR. Therefore, we do not need to do these steps manually." - ] - }, - { - "cell_type": "markdown", - "id": "e2267760-cbfe-41c6-958d-cad9f845d5bb", - "source": [ - "#### Prerequisites\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Install necessary packages." - ] - }, - { - "cell_type": "code", - "id": "30d04872-6916-454c-9211-6c644b50dc04", - "source": [ - "%pip install -q \"openvino>=2024.0.0\" \"nncf>=2.9.0\"\n", - "%pip install -q \"torch>=2.1\" \"torchvision>=0.16\" \"ultralytics==8.3.0\" opencv-python tqdm --extra-index-url https://download.pytorch.org/whl/cpu" - ] - }, - { - "cell_type": "markdown", - "id": "1bbe319c", - "source": [ - "Import required utility functions.\n", - "The lower cell will download the `notebook_utils` Python module from GitHub." - ] - }, - { - "cell_type": "code", - "id": "a2f6cd89", - "source": [ - "from pathlib import Path\n", - "\n", - "# Fetch `notebook_utils` module\n", - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "from notebook_utils import download_file, VideoPlayer, device_widget" - ] - }, - { - "cell_type": "code", - "id": "373658bd-7e64-4479-914e-f2742d330afd", - "source": [ - "# Download a test sample\n", - "IMAGE_PATH = Path(\"./data/coco_bike.jpg\")\n", - "download_file(\n", - " url=\"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg\",\n", - " filename=IMAGE_PATH.name,\n", - " directory=IMAGE_PATH.parent,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "ee32fd08-650c-4751-bb41-d8afccb2495e", - "source": [ - "## Instantiate model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "For loading the model, required to specify a path to the model checkpoint. It can be some local path or name available on models hub (in this case model checkpoint will be downloaded automatically). \n", - "You can select model using widget bellow:" - ] - }, - { - "cell_type": "code", - "id": "7c3963a5-b50b-4ffe-b710-ccd1f4a65380", - "source": [ - "import ipywidgets as widgets\n", - "\n", - "model_id = [\n", - " \"yolo11n-seg\",\n", - " \"yolo11s-seg\",\n", - " \"yolo11m-seg\",\n", - " \"yolo11l-seg\",\n", - " \"yolo11x-seg\",\n", - " \"yolov8n-seg\",\n", - " \"yolov8s-seg\",\n", - " \"yolov8m-seg\",\n", - " \"yolov8l-seg\",\n", - " \"yolov8x-seg\",\n", - "]\n", - "\n", - "model_name = widgets.Dropdown(options=model_id, value=model_id[0], description=\"Model\")\n", - "\n", - "model_name" + 'cell_type': 'markdown', + 'id': 'a2e7d62b-5779-4211-822c-457c77321f8b', + 'source': [ + '# Convert and Optimize YOLOv11 instance segmentation model with OpenVINO™\n', + '\n', + 'Instance segmentation goes a step further than object detection and involves identifying individual objects in an image and segmenting them from the rest of the image. Instance segmentation as an object detection are often used as key components in computer vision systems. \n', + 'Applications that use real-time instance segmentation models include video analytics, robotics, autonomous vehicles, multi-object tracking and object counting, medical image analysis, and many others.\n', + '\n', + '\n', + 'This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLOv11 with OpenVINO. We consider the steps required for instance segmentation scenario. You can find more details about model on [model page](https://docs.ultralytics.com/models/yolo11/) in Ultralytics documentation.\n', + '\n', + 'The tutorial consists of the following steps:\n', + '- Prepare the PyTorch model.\n', + '- Download and prepare a dataset.\n', + '- Validate the original model.\n', + '- Convert the PyTorch model to OpenVINO IR.\n', + '- Validate the converted model.\n', + '- Prepare and run optimization pipeline.\n', + '- Compare performance of the FP32 and quantized models.\n', + '- Compare accuracy of the FP32 and quantized models.\n', + '- Live demo\n', + '\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Get PyTorch model](#Get-PyTorch-model)\n', + ' - [Prerequisites](#Prerequisites)\n', + '- [Instantiate model](#Instantiate-model)\n', + ' - [Convert model to OpenVINO IR](#Convert-model-to-OpenVINO-IR)\n', + ' - [Verify model inference](#Verify-model-inference)\n', + ' - [Select inference device](#Select-inference-device)\n', + ' - [Test on single image](#Test-on-single-image)\n', + '- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n', + ' - [Validate Quantized model inference](#Validate-Quantized-model-inference)\n', + '- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n', + ' - [Compare performance of the Original and Quantized Models](#Compare-performance-of-the-Original-and-Quantized-Models)\n', + '- [Other ways to optimize model](#Other-ways-to-optimize-model)\n', + '- [Live demo](#Live-demo)\n', + ' - [Run Live Object Detection and Segmentation](#Run-Live-Object-Detection-and-Segmentation)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'd7a12678-b12f-48d1-9735-398855733e46', + 'source': [ + '## Get PyTorch model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Generally, PyTorch models represent an instance of the [`torch.nn.Module`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html) class, initialized by a state dictionary with model weights.\n', + 'We will use the YOLOv11 nano model (also known as `yolo11n-seg`) pre-trained on a COCO dataset, which is available in this [repo](https://github.com/ultralytics/ultralytics). Similar steps are also applicable to other YOLOv11 models.\n', + 'Typical steps to obtain a pre-trained model:\n', + '1. Create an instance of a model class.\n', + '2. Load a checkpoint state dict, which contains the pre-trained model weights.\n', + '3. Turn the model to evaluation for switching some operations to inference mode.\n', + '\n', + 'In this case, the creators of the model provide an API that enables converting the YOLOv11 model to OpenVINO IR. Therefore, we do not need to do these steps manually.' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'e2267760-cbfe-41c6-958d-cad9f845d5bb', + 'source': [ + '#### Prerequisites\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Install necessary packages.' + ] + }, + { + 'cell_type': 'code', + 'id': '30d04872-6916-454c-9211-6c644b50dc04', + 'source': [ + '%pip install -q "openvino>=2024.0.0" "nncf>=2.9.0"\n', + '%pip install -q "torch>=2.1" "torchvision>=0.16" "ultralytics==8.3.0" opencv-python tqdm --extra-index-url https://download.pytorch.org/whl/cpu' + ] + }, + { + 'cell_type': 'markdown', + 'id': '1bbe319c', + 'source': [ + 'Import required utility functions.\n', + 'The lower cell will download the `notebook_utils` Python module from GitHub.' + ] + }, + { + 'cell_type': 'code', + 'id': 'a2f6cd89', + 'source': [ + 'from pathlib import Path\n', + '\n', + '# Fetch `notebook_utils` module\n', + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + '\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + 'from notebook_utils import download_file, VideoPlayer, device_widget' + ] + }, + { + 'cell_type': 'code', + 'id': '373658bd-7e64-4479-914e-f2742d330afd', + 'source': [ + '# Download a test sample\n', + 'IMAGE_PATH = Path("./data/coco_bike.jpg")\n', + 'download_file(\n', + ' url="https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg",\n', + ' filename=IMAGE_PATH.name,\n', + ' directory=IMAGE_PATH.parent,\n', + ')' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'ee32fd08-650c-4751-bb41-d8afccb2495e', + 'source': [ + '## Instantiate model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'For loading the model, required to specify a path to the model checkpoint. It can be some local path or name available on models hub (in this case model checkpoint will be downloaded automatically). \n', + 'You can select model using widget bellow:' + ] + }, + { + 'cell_type': 'code', + 'id': '7c3963a5-b50b-4ffe-b710-ccd1f4a65380', + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'model_id = [\n', + ' "yolo11n-seg",\n', + ' "yolo11s-seg",\n', + ' "yolo11m-seg",\n', + ' "yolo11l-seg",\n', + ' "yolo11x-seg",\n', + ' "yolov8n-seg",\n', + ' "yolov8s-seg",\n', + ' "yolov8m-seg",\n', + ' "yolov8l-seg",\n', + ' "yolov8x-seg",\n', + ']\n', + '\n', + 'model_name = widgets.Dropdown(options=model_id, value=model_id[0], description="Model")\n', + '\n', + 'model_name' ] }, { - "cell_type": "markdown", - "id": "60105d86-d7e0-47a9-ad88-4fff014ba3d8", - "source": [ - "Making prediction, the model accepts a path to input image and returns list with Results class object. Results contains boxes for object detection model and boxes and masks for segmentation model. Also it contains utilities for processing results, for example, `plot()` method for drawing.\n", - "\n", - "Let us consider the examples:" + 'cell_type': 'markdown', + 'id': '60105d86-d7e0-47a9-ad88-4fff014ba3d8', + 'source': [ + 'Making prediction, the model accepts a path to input image and returns list with Results class object. Results contains boxes for object detection model and boxes and masks for segmentation model. Also it contains utilities for processing results, for example, `plot()` method for drawing.\n', + '\n', + 'Let us consider the examples:' ] }, - { - "cell_type": "code", - "id": "d4994e1e-a3ee-4620-bc8b-237a55c47742", - "source": [ - "from PIL import Image\n", - "from ultralytics import YOLO\n", - "\n", - "SEG_MODEL_NAME = model_name.value\n", - "\n", - "seg_model = YOLO(f\"{SEG_MODEL_NAME}.pt\")\n", - "label_map = seg_model.model.names\n", - "\n", - "res = seg_model(IMAGE_PATH)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" + { + 'cell_type': 'code', + 'id': 'd4994e1e-a3ee-4620-bc8b-237a55c47742', + 'source': [ + 'from PIL import Image\n', + 'from ultralytics import YOLO\n', + '\n', + 'SEG_MODEL_NAME = model_name.value\n', + '\n', + 'seg_model = YOLO(f"{SEG_MODEL_NAME}.pt")\n', + 'label_map = seg_model.model.names\n', + '\n', + 'res = seg_model(IMAGE_PATH)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "e345ffcc-c4b8-44ba-8b03-f37e63a060da", - "source": [ - "### Convert model to OpenVINO IR\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Ultralytics provides API for convenient model exporting to different formats including OpenVINO IR. `model.export` is responsible for model conversion. We need to specify the format, and additionally, we can preserve dynamic shapes in the model." - ] - }, - { - "cell_type": "code", - "id": "5c3ef363-f6a1-4fe5-b7ff-eb5a9c0789f1", - "source": [ - "# instance segmentation model\n", - "seg_model_path = Path(f\"{SEG_MODEL_NAME}_openvino_model/{SEG_MODEL_NAME}.xml\")\n", - "if not seg_model_path.exists():\n", - " seg_model.export(format=\"openvino\", dynamic=True, half=True)" + 'cell_type': 'markdown', + 'id': 'e345ffcc-c4b8-44ba-8b03-f37e63a060da', + 'source': [ + '### Convert model to OpenVINO IR\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Ultralytics provides API for convenient model exporting to different formats including OpenVINO IR. `model.export` is responsible for model conversion. We need to specify the format, and additionally, we can preserve dynamic shapes in the model.' + ] + }, + { + 'cell_type': 'code', + 'id': '5c3ef363-f6a1-4fe5-b7ff-eb5a9c0789f1', + 'source': [ + '# instance segmentation model\n', + 'seg_model_path = Path(f"{SEG_MODEL_NAME}_openvino_model/{SEG_MODEL_NAME}.xml")\n', + 'if not seg_model_path.exists():\n', + ' seg_model.export(format="openvino", dynamic=True, half=True)' ] }, { - "cell_type": "markdown", - "id": "713cd45f-2b19-4a1e-bc9d-5e69bd95d896", - "source": [ - "### Verify model inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "We can reuse the base model pipeline for pre- and postprocessing just replacing the inference method where we will use the IR model for inference." + 'cell_type': 'markdown', + 'id': '713cd45f-2b19-4a1e-bc9d-5e69bd95d896', + 'source': [ + '### Verify model inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'We can reuse the base model pipeline for pre- and postprocessing just replacing the inference method where we will use the IR model for inference.' ] }, { - "cell_type": "markdown", - "id": "f9b9c472", - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Select device from dropdown list for running inference using OpenVINO" + 'cell_type': 'markdown', + 'id': 'f9b9c472', + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Select device from dropdown list for running inference using OpenVINO' ] }, - { - "cell_type": "code", - "id": "4e0652b2", - "source": [ - "device = device_widget()\n", - "\n", - "device" + { + 'cell_type': 'code', + 'id': '4e0652b2', + 'source': [ + 'device = device_widget()\n', + '\n', + 'device' ] }, { - "cell_type": "markdown", - "id": "cd163eab-e803-4ae8-96da-22c3bf303630", - "source": [ - "### Test on single image\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'cd163eab-e803-4ae8-96da-22c3bf303630', + 'source': [ + '### Test on single image\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "34a65afa-4201-428b-af11-b35e0d206e93", - "source": [ - "import openvino as ov\n", - "\n", - "core = ov.Core()\n", - "seg_ov_model = core.read_model(seg_model_path)\n", - "\n", - "ov_config = {}\n", - "if device.value != \"CPU\":\n", - " seg_ov_model.reshape({0: [1, 3, 640, 640]})\n", - "if \"GPU\" in device.value or (\"AUTO\" in device.value and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "seg_compiled_model = core.compile_model(seg_ov_model, device.value, ov_config)" + 'cell_type': 'code', + 'id': '34a65afa-4201-428b-af11-b35e0d206e93', + 'source': [ + 'import openvino as ov\n', + '\n', + 'core = ov.Core()\n', + 'seg_ov_model = core.read_model(seg_model_path)\n', + '\n', + 'ov_config = {}\n', + 'if device.value != "CPU":\n', + ' seg_ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'if "GPU" in device.value or ("AUTO" in device.value and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + 'seg_compiled_model = core.compile_model(seg_ov_model, device.value, ov_config)' ] }, { - "cell_type": "code", - "id": "389a4698-b7fc-4aa2-9833-9681f575d88b", - "source": [ - "seg_model = YOLO(seg_model_path.parent, task=\"segment\")\n", - "\n", - "if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - "seg_model.predictor.model.ov_compiled_model = seg_compiled_model" + 'cell_type': 'code', + 'id': '389a4698-b7fc-4aa2-9833-9681f575d88b', + 'source': [ + 'seg_model = YOLO(seg_model_path.parent, task="segment")\n', + '\n', + 'if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + 'seg_model.predictor.model.ov_compiled_model = seg_compiled_model' ] }, { - "cell_type": "code", - "id": "12d2522f-e939-4512-b74a-3e20baf1edc8", - "source": [ - "res = seg_model(IMAGE_PATH)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" + 'cell_type': 'code', + 'id': '12d2522f-e939-4512-b74a-3e20baf1edc8', + 'source': [ + 'res = seg_model(IMAGE_PATH)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "7e918cf6-cdce-4e90-a6d7-c435a3c08d93", - "source": [ - "Great! The result is the same, as produced by original models." - ] - }, - { - "cell_type": "markdown", - "id": "13b69e12-2e22-44c1-bfed-fdc88f6c424d", - "source": [ - "## Optimize model using NNCF Post-training Quantization API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n", - "We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv11.\n", - "\n", - "The optimization process contains the following steps:\n", - "\n", - "1. Create a Dataset for quantization.\n", - "2. Run `nncf.quantize` for getting an optimized model.\n", - "3. Serialize OpenVINO IR model, using the `openvino.runtime.serialize` function." - ] - }, - { - "cell_type": "markdown", - "id": "7d7eeae8-f3f4-4e95-b1a6-a23085f03ebc", - "source": [ - "Please select below whether you would like to run quantization to improve model inference speed." - ] - }, - { - "cell_type": "code", - "id": "4690b9fc-c49f-4882-b8a5-c82f842a9126", - "source": [ - "import ipywidgets as widgets\n", - "\n", - "int8_model_seg_path = Path(f\"{SEG_MODEL_NAME}_openvino_int8_model/{SEG_MODEL_NAME}.xml\")\n", - "quantized_seg_model = None\n", - "\n", - "to_quantize = widgets.Checkbox(\n", - " value=True,\n", - " description=\"Quantization\",\n", - " disabled=False,\n", - ")\n", - "\n", - "to_quantize" - ] - }, - { - "cell_type": "markdown", - "id": "73cfdbd4-3b65-49e0-b8ab-ceb1d0362481", - "source": [ - "Let's load `skip magic` extension to skip quantization if `to_quantize` is not selected" - ] - }, - { - "cell_type": "code", - "id": "bc6121aa-3a07-4066-a123-fe0ed0e72478", - "source": [ - "# Fetch skip_kernel_extension module\n", - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py\",\n", - ")\n", - "open(\"skip_kernel_extension.py\", \"w\").write(r.text)\n", - "\n", - "%load_ext skip_kernel_extension" - ] - }, - { - "cell_type": "markdown", - "id": "94fb2515-3645-4798-bc7c-082129ac86c0", - "source": [ - "Reuse validation dataloader in accuracy testing for quantization. \n", - "For that, it should be wrapped into the `nncf.Dataset` object and define a transformation function for getting only input tensors." - ] - }, - { - "cell_type": "code", - "id": "fd994958-6988-4a1d-ac7f-3efbd97135cc", - "source": [ - "# %%skip not $to_quantize.value\n", - "\n", - "\n", - "import nncf\n", - "from typing import Dict\n", - "\n", - "from zipfile import ZipFile\n", - "\n", - "from ultralytics.data.utils import DATASETS_DIR\n", - "from ultralytics.utils import DEFAULT_CFG\n", - "from ultralytics.cfg import get_cfg\n", - "from ultralytics.data.converter import coco80_to_coco91_class\n", - "from ultralytics.data.utils import check_det_dataset\n", - "from ultralytics.utils import ops\n", - "\n", - "\n", - "if not int8_model_seg_path.exists():\n", - " DATA_URL = \"http://images.cocodataset.org/zips/val2017.zip\"\n", - " LABELS_URL = \"https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip\"\n", - " CFG_URL = \"https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml\"\n", - "\n", - " OUT_DIR = DATASETS_DIR\n", - "\n", - " DATA_PATH = OUT_DIR / \"val2017.zip\"\n", - " LABELS_PATH = OUT_DIR / \"coco2017labels-segments.zip\"\n", - " CFG_PATH = OUT_DIR / \"coco.yaml\"\n", - "\n", - " download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n", - " download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n", - " download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n", - "\n", - " if not (OUT_DIR / \"coco/labels\").exists():\n", - " with ZipFile(LABELS_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR)\n", - " with ZipFile(DATA_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR / \"coco/images\")\n", - "\n", - " args = get_cfg(cfg=DEFAULT_CFG)\n", - " args.data = str(CFG_PATH)\n", - " seg_validator = seg_model.task_map[seg_model.task][\"validator\"](args=args)\n", - " seg_validator.data = check_det_dataset(args.data)\n", - " seg_validator.stride = 32\n", - " seg_data_loader = seg_validator.get_dataloader(OUT_DIR / \"coco/\", 1)\n", - "\n", - " seg_validator.is_coco = True\n", - " seg_validator.class_map = coco80_to_coco91_class()\n", - " seg_validator.names = label_map\n", - " seg_validator.metrics.names = seg_validator.names\n", - " seg_validator.nc = 80\n", - " seg_validator.nm = 32\n", - " seg_validator.process = ops.process_mask\n", - " seg_validator.plot_masks = []\n", - "\n", - " def transform_fn(data_item: Dict):\n", - " \"\"\"\n", - " Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n", - " Parameters:\n", - " data_item: Dict with data item produced by DataLoader during iteration\n", - " Returns:\n", - " input_tensor: Input data for quantization\n", - " \"\"\"\n", - " input_tensor = seg_validator.preprocess(data_item)[\"img\"].numpy()\n", - " return input_tensor\n", - "\n", - " quantization_dataset = nncf.Dataset(seg_data_loader, transform_fn)" - ] - }, - { - "cell_type": "markdown", - "id": "91629284-e261-494d-9464-146ed7190084", - "source": [ - "The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n", - "Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. Ultralytics models contain non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations. For more accurate results, we should keep the operation in the postprocessing subgraph in floating point precision, using the `ignored_scope` parameter.\n", - "\n", - ">**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware." - ] - }, - { - "cell_type": "code", - "id": "b2e8cec4-d0b3-4da0-b54c-7964e7bcbfe2", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "if not int8_model_seg_path.exists():\n", - " ignored_scope = nncf.IgnoredScope( # post-processing\n", - " subgraphs=[\n", - " nncf.Subgraph(inputs=[f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_1\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_2\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_7\"],\n", - " outputs=[f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_8\"])\n", - " ]\n", - " )\n", - "\n", - " # Segmentation model\n", - " quantized_seg_model = nncf.quantize(\n", - " seg_ov_model,\n", - " quantization_dataset,\n", - " preset=nncf.QuantizationPreset.MIXED,\n", - " ignored_scope=ignored_scope\n", - " )\n", - "\n", - " print(f\"Quantized segmentation model will be saved to {int8_model_seg_path}\")\n", - " ov.save_model(quantized_seg_model, str(int8_model_seg_path))" - ] - }, - { - "cell_type": "markdown", - "id": "c8c92a68-3eb8-4ea5-9e35-d496f45b3cc3", - "source": [ - "### Validate Quantized model inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "`nncf.quantize` returns the OpenVINO Model class instance, which is suitable for loading on a device for making predictions. `INT8` model input data and output result formats have no difference from the floating point model representation. Therefore, we can reuse the same `detect` function defined above for getting the `INT8` model result on the image." - ] - }, - { - "cell_type": "code", - "id": "4003a3ae", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "id": "711200ec-2f26-47cc-b62d-3802961d5711", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "if quantized_seg_model is None:\n", - " quantized_seg_model = core.read_model(int8_model_seg_path)\n", - "\n", - "ov_config = {}\n", - "if device.value != \"CPU\":\n", - " quantized_seg_model.reshape({0: [1, 3, 640, 640]})\n", - "if \"GPU\" in device.value or (\"AUTO\" in device.value and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "\n", - "quantized_seg_compiled_model = core.compile_model(quantized_seg_model, device.value, ov_config)" - ] - }, - { - "cell_type": "code", - "id": "b2e3ea3b-5935-4474-9f08-f51fc688b9c0", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "\n", - "if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - "seg_model.predictor.model.ov_compiled_model = quantized_seg_compiled_model" - ] - }, - { - "cell_type": "code", - "id": "bef2ed5d-5762-41a6-af2e-ee4f9fd56557", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "res = seg_model(IMAGE_PATH)\n", - "display(Image.fromarray(res[0].plot()[:, :, ::-1]))" - ] - }, - { - "cell_type": "markdown", - "id": "b289f057", - "source": [ - "## Compare the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "cell_type": "markdown", - "id": "59081cae-9c92-46f0-8117-ed8c6fa6ff19", - "source": [ - "### Compare performance of the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "Finally, use the OpenVINO [Benchmark Tool](https://docs.openvino.ai/2024/learn-openvino/openvino-samples/benchmark-tool.html) to measure the inference performance of the `FP32` and `INT8` models.\n", - "\n", - "> **Note**: For more accurate performance, it is recommended to run `benchmark_app` in a terminal/command prompt after closing other applications. Run `benchmark_app -m -d CPU -shape \"\"` to benchmark async inference on CPU on specific input data shape for one minute. Change `CPU` to `GPU` to benchmark on GPU. Run `benchmark_app --help` to see an overview of all command-line options." - ] - }, - { - "cell_type": "code", - "id": "b8677df4", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "id": "546828d2", - "source": [ - "if int8_model_seg_path.exists():\n", - " !benchmark_app -m $seg_model_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "code", - "id": "1d43554b", - "source": [ - "if int8_model_seg_path.exists():\n", - " !benchmark_app -m $int8_model_seg_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "markdown", - "id": "184bcebd-588d-4b4d-a60a-0fa753a07ef9", - "source": [ - "## Other ways to optimize model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "The performance could be also improved by another OpenVINO method such as async inference pipeline or preprocessing API.\n", - "\n", - "Async Inference pipeline help to utilize the device more optimal. The key advantage of the Async API is that when a device is busy with inference, the application can perform other tasks in parallel (for example, populating inputs or scheduling other requests) rather than wait for the current inference to complete first. To understand how to perform async inference using openvino, refer to [Async API tutorial](../async-api/async-api.ipynb)\n", - "\n", - "Preprocessing API enables making preprocessing a part of the model reducing application code and dependency on additional image processing libraries. \n", - "The main advantage of Preprocessing API is that preprocessing steps will be integrated into the execution graph and will be performed on a selected device (CPU/GPU etc.) rather than always being executed on CPU as part of an application. This will also improve selected device utilization. For more information, refer to the overview of [Preprocessing API tutorial](../optimize-preprocessing/optimize-preprocessing.ipynb). To see, how it could be used with YOLOV8 object detection model, please, see [Convert and Optimize YOLOv8 real-time object detection with OpenVINO tutorial](./yolov8-object-detection.ipynb)" - ] - }, - { - "cell_type": "markdown", - "id": "2e3b4862-c182-4ce4-a473-9f38d98deab8", - "source": [ - "## Live demo\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "The following code runs model inference on a video:" - ] - }, - { - "cell_type": "code", - "id": "4c7bb92e-e301-45a9-b5ff-f7953fad298f", - "source": [ - "import collections\n", - "import time\n", - "import cv2\n", - "from IPython import display\n", - "import numpy as np\n", - "\n", - "\n", - "def run_instance_segmentation(\n", - " source=0,\n", - " flip=False,\n", - " use_popup=False,\n", - " skip_first_frames=0,\n", - " model=seg_model,\n", - " device=device.value,\n", - "):\n", - " player = None\n", - "\n", - " ov_config = {}\n", - " if device != \"CPU\":\n", - " model.reshape({0: [1, 3, 640, 640]})\n", - " if \"GPU\" in device or (\"AUTO\" in device and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - " compiled_model = core.compile_model(model, device, ov_config)\n", - "\n", - " if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - " seg_model.predictor.model.ov_compiled_model = compiled_model\n", - "\n", - " try:\n", - " # Create a video player to play with target fps.\n", - " player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n", - " # Start capturing.\n", - " player.start()\n", - " if use_popup:\n", - " title = \"Press ESC to Exit\"\n", - " cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n", - "\n", - " processing_times = collections.deque()\n", - " while True:\n", - " # Grab the frame.\n", - " frame = player.next()\n", - " if frame is None:\n", - " print(\"Source ended\")\n", - " break\n", - " # If the frame is larger than full HD, reduce size to improve the performance.\n", - " scale = 1280 / max(frame.shape)\n", - " if scale < 1:\n", - " frame = cv2.resize(\n", - " src=frame,\n", - " dsize=None,\n", - " fx=scale,\n", - " fy=scale,\n", - " interpolation=cv2.INTER_AREA,\n", - " )\n", - " # Get the results.\n", - " input_image = np.array(frame)\n", - "\n", - " start_time = time.time()\n", - " detections = seg_model(input_image)\n", - " stop_time = time.time()\n", - " frame = detections[0].plot()\n", - "\n", - " processing_times.append(stop_time - start_time)\n", - " # Use processing times from last 200 frames.\n", - " if len(processing_times) > 200:\n", - " processing_times.popleft()\n", - "\n", - " _, f_width = frame.shape[:2]\n", - " # Mean processing time [ms].\n", - " processing_time = np.mean(processing_times) * 1000\n", - " fps = 1000 / processing_time\n", - " cv2.putText(\n", - " img=frame,\n", - " text=f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n", - " org=(20, 40),\n", - " fontFace=cv2.FONT_HERSHEY_COMPLEX,\n", - " fontScale=f_width / 1000,\n", - " color=(0, 0, 255),\n", - " thickness=1,\n", - " lineType=cv2.LINE_AA,\n", - " )\n", - " # Use this workaround if there is flickering.\n", - " if use_popup:\n", - " cv2.imshow(winname=title, mat=frame)\n", - " key = cv2.waitKey(1)\n", - " # escape = 27\n", - " if key == 27:\n", - " break\n", - " else:\n", - " # Encode numpy array to jpg.\n", - " _, encoded_img = cv2.imencode(ext=\".jpg\", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n", - " # Create an IPython image.\n", - " i = display.Image(data=encoded_img)\n", - " # Display the image in this notebook.\n", - " display.clear_output(wait=True)\n", - " display.display(i)\n", - " # ctrl-c\n", - " except KeyboardInterrupt:\n", - " print(\"Interrupted\")\n", - " # any different error\n", - " except RuntimeError as e:\n", - " print(e)\n", - " finally:\n", - " if player is not None:\n", - " # Stop capturing.\n", - " player.stop()\n", - " if use_popup:\n", - " cv2.destroyAllWindows()" - ] - }, - { - "cell_type": "markdown", - "id": "cc5b4ba6-f478-4417-b09d-93fee5adca41", - "source": [ - "### Run Live Object Detection and Segmentation\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Use a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n", - "\n", - ">**NOTE**: To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a remote server (for example, in Binder or Google Colab service), the webcam will not work. By default, the lower cell will run model inference on a video file. If you want to try live inference on your webcam set `WEBCAM_INFERENCE = True`" - ] - }, - { - "cell_type": "code", - "id": "90708017", - "source": [ - "WEBCAM_INFERENCE = False\n", - "\n", - "if WEBCAM_INFERENCE:\n", - " VIDEO_SOURCE = 0 # Webcam\n", - "else:\n", - " VIDEO_SOURCE = \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4\"" - ] - }, - { - "cell_type": "code", - "id": "e8f1239c", - "source": [ - "device" - ] - }, - { - "cell_type": "code", - "id": "440f2d0e", - "source": [ - "run_instance_segmentation(\n", - " source=VIDEO_SOURCE,\n", - " flip=True,\n", - " use_popup=False,\n", - " model=seg_ov_model,\n", - " device=device.value,\n", - ")" + 'cell_type': 'markdown', + 'id': '7e918cf6-cdce-4e90-a6d7-c435a3c08d93', + 'source': [ + 'Great! The result is the same, as produced by original models.' + ] + }, + { + 'cell_type': 'markdown', + 'id': '13b69e12-2e22-44c1-bfed-fdc88f6c424d', + 'source': [ + '## Optimize model using NNCF Post-training Quantization API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n', + 'We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv11.\n', + '\n', + 'The optimization process contains the following steps:\n', + '\n', + '1. Create a Dataset for quantization.\n', + '2. Run `nncf.quantize` for getting an optimized model.\n', + '3. Serialize OpenVINO IR model, using the `openvino.runtime.serialize` function.' + ] + }, + { + 'cell_type': 'markdown', + 'id': '7d7eeae8-f3f4-4e95-b1a6-a23085f03ebc', + 'source': [ + 'Please select below whether you would like to run quantization to improve model inference speed.' + ] + }, + { + 'cell_type': 'code', + 'id': '4690b9fc-c49f-4882-b8a5-c82f842a9126', + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'int8_model_seg_path = Path(f"{SEG_MODEL_NAME}_openvino_int8_model/{SEG_MODEL_NAME}.xml")\n', + 'quantized_seg_model = None\n', + '\n', + 'to_quantize = widgets.Checkbox(\n', + ' value=True,\n', + ' description="Quantization",\n', + ' disabled=False,\n', + ')\n', + '\n', + 'to_quantize' + ] + }, + { + 'cell_type': 'markdown', + 'id': '73cfdbd4-3b65-49e0-b8ab-ceb1d0362481', + 'source': [ + 'Let\'s load `skip magic` extension to skip quantization if `to_quantize` is not selected' + ] + }, + { + 'cell_type': 'code', + 'id': 'bc6121aa-3a07-4066-a123-fe0ed0e72478', + 'source': [ + '# Fetch skip_kernel_extension module\n', + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py",\n', + ')\n', + 'open("skip_kernel_extension.py", "w").write(r.text)\n', + '\n', + '%load_ext skip_kernel_extension' + ] + }, + { + 'cell_type': 'markdown', + 'id': '94fb2515-3645-4798-bc7c-082129ac86c0', + 'source': [ + 'Reuse validation dataloader in accuracy testing for quantization. \n', + 'For that, it should be wrapped into the `nncf.Dataset` object and define a transformation function for getting only input tensors.' + ] + }, + { + 'cell_type': 'code', + 'id': 'fd994958-6988-4a1d-ac7f-3efbd97135cc', + 'source': [ + '# %%skip not $to_quantize.value\n', + '\n', + '\n', + 'import nncf\n', + 'from typing import Dict\n', + '\n', + 'from zipfile import ZipFile\n', + '\n', + 'from ultralytics.data.utils import DATASETS_DIR\n', + 'from ultralytics.utils import DEFAULT_CFG\n', + 'from ultralytics.cfg import get_cfg\n', + 'from ultralytics.data.converter import coco80_to_coco91_class\n', + 'from ultralytics.data.utils import check_det_dataset\n', + 'from ultralytics.utils import ops\n', + '\n', + '\n', + 'if not int8_model_seg_path.exists():\n', + ' DATA_URL = "http://images.cocodataset.org/zips/val2017.zip"\n', + ' LABELS_URL = "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip"\n', + ' CFG_URL = "https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml"\n', + '\n', + ' OUT_DIR = DATASETS_DIR\n', + '\n', + ' DATA_PATH = OUT_DIR / "val2017.zip"\n', + ' LABELS_PATH = OUT_DIR / "coco2017labels-segments.zip"\n', + ' CFG_PATH = OUT_DIR / "coco.yaml"\n', + '\n', + ' download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n', + ' download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n', + ' download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n', + '\n', + ' if not (OUT_DIR / "coco/labels").exists():\n', + ' with ZipFile(LABELS_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR)\n', + ' with ZipFile(DATA_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR / "coco/images")\n', + '\n', + ' args = get_cfg(cfg=DEFAULT_CFG)\n', + ' args.data = str(CFG_PATH)\n', + ' seg_validator = seg_model.task_map[seg_model.task]["validator"](args=args)\n', + ' seg_validator.data = check_det_dataset(args.data)\n', + ' seg_validator.stride = 32\n', + ' seg_data_loader = seg_validator.get_dataloader(OUT_DIR / "coco/", 1)\n', + '\n', + ' seg_validator.is_coco = True\n', + ' seg_validator.class_map = coco80_to_coco91_class()\n', + ' seg_validator.names = label_map\n', + ' seg_validator.metrics.names = seg_validator.names\n', + ' seg_validator.nc = 80\n', + ' seg_validator.nm = 32\n', + ' seg_validator.process = ops.process_mask\n', + ' seg_validator.plot_masks = []\n', + '\n', + ' def transform_fn(data_item: Dict):\n', + ' """\n', + ' Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n', + ' Parameters:\n', + ' data_item: Dict with data item produced by DataLoader during iteration\n', + ' Returns:\n', + ' input_tensor: Input data for quantization\n', + ' """\n', + ' input_tensor = seg_validator.preprocess(data_item)["img"].numpy()\n', + ' return input_tensor\n', + '\n', + ' quantization_dataset = nncf.Dataset(seg_data_loader, transform_fn)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '91629284-e261-494d-9464-146ed7190084', + 'source': [ + 'The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n', + 'Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. Ultralytics models contain non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations. For more accurate results, we should keep the operation in the postprocessing subgraph in floating point precision, using the `ignored_scope` parameter.\n', + '\n', + '>**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware.' + ] + }, + { + 'cell_type': 'code', + 'id': 'b2e8cec4-d0b3-4da0-b54c-7964e7bcbfe2', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'if not int8_model_seg_path.exists():\n', + ' ignored_scope = nncf.IgnoredScope( # post-processing\n', + ' subgraphs=[\n', + ` nncf.Subgraph(inputs=[f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_1",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_2",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_7"],\n`, + ` outputs=[f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_8"])\n`, + ' ]\n', + ' )\n', + '\n', + ' # Segmentation model\n', + ' quantized_seg_model = nncf.quantize(\n', + ' seg_ov_model,\n', + ' quantization_dataset,\n', + ' preset=nncf.QuantizationPreset.MIXED,\n', + ' ignored_scope=ignored_scope\n', + ' )\n', + '\n', + ' print(f"Quantized segmentation model will be saved to {int8_model_seg_path}")\n', + ' ov.save_model(quantized_seg_model, str(int8_model_seg_path))' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'c8c92a68-3eb8-4ea5-9e35-d496f45b3cc3', + 'source': [ + '### Validate Quantized model inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '`nncf.quantize` returns the OpenVINO Model class instance, which is suitable for loading on a device for making predictions. `INT8` model input data and output result formats have no difference from the floating point model representation. Therefore, we can reuse the same `detect` function defined above for getting the `INT8` model result on the image.' + ] + }, + { + 'cell_type': 'code', + 'id': '4003a3ae', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'id': '711200ec-2f26-47cc-b62d-3802961d5711', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'if quantized_seg_model is None:\n', + ' quantized_seg_model = core.read_model(int8_model_seg_path)\n', + '\n', + 'ov_config = {}\n', + 'if device.value != "CPU":\n', + ' quantized_seg_model.reshape({0: [1, 3, 640, 640]})\n', + 'if "GPU" in device.value or ("AUTO" in device.value and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + '\n', + 'quantized_seg_compiled_model = core.compile_model(quantized_seg_model, device.value, ov_config)' + ] + }, + { + 'cell_type': 'code', + 'id': 'b2e3ea3b-5935-4474-9f08-f51fc688b9c0', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + '\n', + 'if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + 'seg_model.predictor.model.ov_compiled_model = quantized_seg_compiled_model' + ] + }, + { + 'cell_type': 'code', + 'id': 'bef2ed5d-5762-41a6-af2e-ee4f9fd56557', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'res = seg_model(IMAGE_PATH)\n', + 'display(Image.fromarray(res[0].plot()[:, :, ::-1]))' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'b289f057', + 'source': [ + '## Compare the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'cell_type': 'markdown', + 'id': '59081cae-9c92-46f0-8117-ed8c6fa6ff19', + 'source': [ + '### Compare performance of the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)\n', + 'Finally, use the OpenVINO [Benchmark Tool](https://docs.openvino.ai/2024/learn-openvino/openvino-samples/benchmark-tool.html) to measure the inference performance of the `FP32` and `INT8` models.\n', + '\n', + '> **Note**: For more accurate performance, it is recommended to run `benchmark_app` in a terminal/command prompt after closing other applications. Run `benchmark_app -m -d CPU -shape ""` to benchmark async inference on CPU on specific input data shape for one minute. Change `CPU` to `GPU` to benchmark on GPU. Run `benchmark_app --help` to see an overview of all command-line options.' + ] + }, + { + 'cell_type': 'code', + 'id': 'b8677df4', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'id': '546828d2', + 'source': [ + 'if int8_model_seg_path.exists():\n', + ' !benchmark_app -m $seg_model_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'code', + 'id': '1d43554b', + 'source': [ + 'if int8_model_seg_path.exists():\n', + ' !benchmark_app -m $int8_model_seg_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'markdown', + 'id': '184bcebd-588d-4b4d-a60a-0fa753a07ef9', + 'source': [ + '## Other ways to optimize model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'The performance could be also improved by another OpenVINO method such as async inference pipeline or preprocessing API.\n', + '\n', + 'Async Inference pipeline help to utilize the device more optimal. The key advantage of the Async API is that when a device is busy with inference, the application can perform other tasks in parallel (for example, populating inputs or scheduling other requests) rather than wait for the current inference to complete first. To understand how to perform async inference using openvino, refer to [Async API tutorial](../async-api/async-api.ipynb)\n', + '\n', + 'Preprocessing API enables making preprocessing a part of the model reducing application code and dependency on additional image processing libraries. \n', + 'The main advantage of Preprocessing API is that preprocessing steps will be integrated into the execution graph and will be performed on a selected device (CPU/GPU etc.) rather than always being executed on CPU as part of an application. This will also improve selected device utilization. For more information, refer to the overview of [Preprocessing API tutorial](../optimize-preprocessing/optimize-preprocessing.ipynb). To see, how it could be used with YOLOV8 object detection model, please, see [Convert and Optimize YOLOv8 real-time object detection with OpenVINO tutorial](./yolov8-object-detection.ipynb)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '2e3b4862-c182-4ce4-a473-9f38d98deab8', + 'source': [ + '## Live demo\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'The following code runs model inference on a video:' + ] + }, + { + 'cell_type': 'code', + 'id': '4c7bb92e-e301-45a9-b5ff-f7953fad298f', + 'source': [ + 'import collections\n', + 'import time\n', + 'import cv2\n', + 'from IPython import display\n', + 'import numpy as np\n', + '\n', + '\n', + 'def run_instance_segmentation(\n', + ' source=0,\n', + ' flip=False,\n', + ' use_popup=False,\n', + ' skip_first_frames=0,\n', + ' model=seg_model,\n', + ' device=device.value,\n', + '):\n', + ' player = None\n', + '\n', + ' ov_config = {}\n', + ' if device != "CPU":\n', + ' model.reshape({0: [1, 3, 640, 640]})\n', + ' if "GPU" in device or ("AUTO" in device and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + ' compiled_model = core.compile_model(model, device, ov_config)\n', + '\n', + ' if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + ' seg_model.predictor.model.ov_compiled_model = compiled_model\n', + '\n', + ' try:\n', + ' # Create a video player to play with target fps.\n', + ' player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n', + ' # Start capturing.\n', + ' player.start()\n', + ' if use_popup:\n', + ' title = "Press ESC to Exit"\n', + ' cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n', + '\n', + ' processing_times = collections.deque()\n', + ' while True:\n', + ' # Grab the frame.\n', + ' frame = player.next()\n', + ' if frame is None:\n', + ' print("Source ended")\n', + ' break\n', + ' # If the frame is larger than full HD, reduce size to improve the performance.\n', + ' scale = 1280 / max(frame.shape)\n', + ' if scale < 1:\n', + ' frame = cv2.resize(\n', + ' src=frame,\n', + ' dsize=None,\n', + ' fx=scale,\n', + ' fy=scale,\n', + ' interpolation=cv2.INTER_AREA,\n', + ' )\n', + ' # Get the results.\n', + ' input_image = np.array(frame)\n', + '\n', + ' start_time = time.time()\n', + ' detections = seg_model(input_image)\n', + ' stop_time = time.time()\n', + ' frame = detections[0].plot()\n', + '\n', + ' processing_times.append(stop_time - start_time)\n', + ' # Use processing times from last 200 frames.\n', + ' if len(processing_times) > 200:\n', + ' processing_times.popleft()\n', + '\n', + ' _, f_width = frame.shape[:2]\n', + ' # Mean processing time [ms].\n', + ' processing_time = np.mean(processing_times) * 1000\n', + ' fps = 1000 / processing_time\n', + ' cv2.putText(\n', + ' img=frame,\n', + ' text=f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n', + ' org=(20, 40),\n', + ' fontFace=cv2.FONT_HERSHEY_COMPLEX,\n', + ' fontScale=f_width / 1000,\n', + ' color=(0, 0, 255),\n', + ' thickness=1,\n', + ' lineType=cv2.LINE_AA,\n', + ' )\n', + ' # Use this workaround if there is flickering.\n', + ' if use_popup:\n', + ' cv2.imshow(winname=title, mat=frame)\n', + ' key = cv2.waitKey(1)\n', + ' # escape = 27\n', + ' if key == 27:\n', + ' break\n', + ' else:\n', + ' # Encode numpy array to jpg.\n', + ' _, encoded_img = cv2.imencode(ext=".jpg", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n', + ' # Create an IPython image.\n', + ' i = display.Image(data=encoded_img)\n', + ' # Display the image in this notebook.\n', + ' display.clear_output(wait=True)\n', + ' display.display(i)\n', + ' # ctrl-c\n', + ' except KeyboardInterrupt:\n', + ' print("Interrupted")\n', + ' # any different error\n', + ' except RuntimeError as e:\n', + ' print(e)\n', + ' finally:\n', + ' if player is not None:\n', + ' # Stop capturing.\n', + ' player.stop()\n', + ' if use_popup:\n', + ' cv2.destroyAllWindows()' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'cc5b4ba6-f478-4417-b09d-93fee5adca41', + 'source': [ + '### Run Live Object Detection and Segmentation\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Use a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n', + '\n', + '>**NOTE**: To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a remote server (for example, in Binder or Google Colab service), the webcam will not work. By default, the lower cell will run model inference on a video file. If you want to try live inference on your webcam set `WEBCAM_INFERENCE = True`' + ] + }, + { + 'cell_type': 'code', + 'id': '90708017', + 'source': [ + 'WEBCAM_INFERENCE = False\n', + '\n', + 'if WEBCAM_INFERENCE:\n', + ' VIDEO_SOURCE = 0 # Webcam\n', + 'else:\n', + ' VIDEO_SOURCE = "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4"' + ] + }, + { + 'cell_type': 'code', + 'id': 'e8f1239c', + 'source': [ + 'device' + ] + }, + { + 'cell_type': 'code', + 'id': '440f2d0e', + 'source': [ + 'run_instance_segmentation(\n', + ' source=VIDEO_SOURCE,\n', + ' flip=True,\n', + ' use_popup=False,\n', + ' model=seg_ov_model,\n', + ' device=device.value,\n', + ')' ] } ] @@ -9272,907 +9272,907 @@ suite('NotebookDiff Diff Service', () => { , [ { - "attachments": {}, - "cell_type": "markdown", - "id": "a2e7d62b-5779-4211-822c-457c77321f8b", - "metadata": {}, - "source": [ - "# Convert and Optimize YOLOv11 instance segmentation model with OpenVINO™\n", - "\n", - "Instance segmentation goes a step further than object detection and involves identifying individual objects in an image and segmenting them from the rest of the image. Instance segmentation as an object detection are often used as key components in computer vision systems. \n", - "Applications that use real-time instance segmentation models include video analytics, robotics, autonomous vehicles, multi-object tracking and object counting, medical image analysis, and many others.\n", - "\n", - "\n", - "This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLOv11 with OpenVINO. We consider the steps required for instance segmentation scenario. You can find more details about model on [model page](https://docs.ultralytics.com/models/yolo11/) in Ultralytics documentation.\n", - "\n", - "The tutorial consists of the following steps:\n", - "- Prepare the PyTorch model.\n", - "- Download and prepare a dataset.\n", - "- Validate the original model.\n", - "- Convert the PyTorch model to OpenVINO IR.\n", - "- Validate the converted model.\n", - "- Prepare and run optimization pipeline.\n", - "- Compare performance of the FP32 and quantized models.\n", - "- Compare accuracy of the FP32 and quantized models.\n", - "- Live demo\n", - "\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Get PyTorch model](#Get-PyTorch-model)\n", - " - [Prerequisites](#Prerequisites)\n", - "- [Instantiate model](#Instantiate-model)\n", - " - [Convert model to OpenVINO IR](#Convert-model-to-OpenVINO-IR)\n", - " - [Verify model inference](#Verify-model-inference)\n", - " - [Select inference device](#Select-inference-device)\n", - " - [Test on single image](#Test-on-single-image)\n", - "- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n", - " - [Validate Quantized model inference](#Validate-Quantized-model-inference)\n", - "- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n", - " - [Compare performance of the Original and Quantized Models](#Compare-performance-of-the-Original-and-Quantized-Models)\n", - "- [Other ways to optimize model](#Other-ways-to-optimize-model)\n", - "- [Live demo](#Live-demo)\n", - " - [Run Live Object Detection and Segmentation](#Run-Live-Object-Detection-and-Segmentation)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d7a12678-b12f-48d1-9735-398855733e46", - "metadata": {}, - "source": [ - "## Get PyTorch model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Generally, PyTorch models represent an instance of the [`torch.nn.Module`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html) class, initialized by a state dictionary with model weights.\n", - "We will use the YOLOv11 nano model (also known as `yolo11n-seg`) pre-trained on a COCO dataset, which is available in this [repo](https://github.com/ultralytics/ultralytics). Similar steps are also applicable to other YOLOv11 models.\n", - "Typical steps to obtain a pre-trained model:\n", - "1. Create an instance of a model class.\n", - "2. Load a checkpoint state dict, which contains the pre-trained model weights.\n", - "3. Turn the model to evaluation for switching some operations to inference mode.\n", - "\n", - "In this case, the creators of the model provide an API that enables converting the YOLOv11 model to OpenVINO IR. Therefore, we do not need to do these steps manually." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "e2267760-cbfe-41c6-958d-cad9f845d5bb", - "metadata": {}, - "source": [ - "#### Prerequisites\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Install necessary packages." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30d04872-6916-454c-9211-6c644b50dc04", - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -q \"openvino>=2024.0.0\" \"nncf>=2.9.0\"\n", - "%pip install -q \"torch>=2.1\" \"torchvision>=0.16\" \"ultralytics==8.3.0\" opencv-python tqdm --extra-index-url https://download.pytorch.org/whl/cpu" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1bbe319c", - "metadata": {}, - "source": [ - "Import required utility functions.\n", - "The lower cell will download the `notebook_utils` Python module from GitHub." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2f6cd89", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "import requests\n", - "\n", - "if not Path(\"notebook_utils.py\").exists():\n", - " r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - " )\n", - "\n", - " open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "\n", - "from notebook_utils import download_file, VideoPlayer, device_widget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "373658bd-7e64-4479-914e-f2742d330afd", - "metadata": {}, - "outputs": [], - "source": [ - "# Download a test sample\n", - "IMAGE_PATH = Path(\"./data/coco_bike.jpg\")\n", - "\n", - "if not IMAGE_PATH.exists():\n", - " download_file(\n", - " url=\"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg\",\n", - " filename=IMAGE_PATH.name,\n", - " directory=IMAGE_PATH.parent,\n", - " )" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "ee32fd08-650c-4751-bb41-d8afccb2495e", - "metadata": {}, - "source": [ - "## Instantiate model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "For loading the model, required to specify a path to the model checkpoint. It can be some local path or name available on models hub (in this case model checkpoint will be downloaded automatically). \n", - "You can select model using widget bellow:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c3963a5-b50b-4ffe-b710-ccd1f4a65380", - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets\n", - "\n", - "model_id = [\n", - " \"yolo11n-seg\",\n", - " \"yolo11s-seg\",\n", - " \"yolo11m-seg\",\n", - " \"yolo11l-seg\",\n", - " \"yolo11x-seg\",\n", - " \"yolov8n-seg\",\n", - " \"yolov8s-seg\",\n", - " \"yolov8m-seg\",\n", - " \"yolov8l-seg\",\n", - " \"yolov8x-seg\",\n", - "]\n", - "\n", - "model_name = widgets.Dropdown(options=model_id, value=model_id[0], description=\"Model\")\n", - "\n", - "model_name" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "60105d86-d7e0-47a9-ad88-4fff014ba3d8", - "metadata": {}, - "source": [ - "Making prediction, the model accepts a path to input image and returns list with Results class object. Results contains boxes for object detection model and boxes and masks for segmentation model. Also it contains utilities for processing results, for example, `plot()` method for drawing.\n", - "\n", - "Let us consider the examples:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d4994e1e-a3ee-4620-bc8b-237a55c47742", - "metadata": {}, - "outputs": [], - "source": [ - "from PIL import Image\n", - "from ultralytics import YOLO\n", - "\n", - "SEG_MODEL_NAME = model_name.value\n", - "\n", - "seg_model = YOLO(f\"{SEG_MODEL_NAME}.pt\")\n", - "label_map = seg_model.model.names\n", - "\n", - "res = seg_model(IMAGE_PATH)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "e345ffcc-c4b8-44ba-8b03-f37e63a060da", - "metadata": {}, - "source": [ - "### Convert model to OpenVINO IR\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Ultralytics provides API for convenient model exporting to different formats including OpenVINO IR. `model.export` is responsible for model conversion. We need to specify the format, and additionally, we can preserve dynamic shapes in the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c3ef363-f6a1-4fe5-b7ff-eb5a9c0789f1", - "metadata": {}, - "outputs": [], - "source": [ - "# instance segmentation model\n", - "seg_model_path = Path(f\"{SEG_MODEL_NAME}_openvino_model/{SEG_MODEL_NAME}.xml\")\n", - "if not seg_model_path.exists():\n", - " seg_model.export(format=\"openvino\", dynamic=True, half=True)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "713cd45f-2b19-4a1e-bc9d-5e69bd95d896", - "metadata": {}, - "source": [ - "### Verify model inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "We can reuse the base model pipeline for pre- and postprocessing just replacing the inference method where we will use the IR model for inference." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f9b9c472", - "metadata": {}, - "source": [ - "### Select inference device\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Select device from dropdown list for running inference using OpenVINO" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e0652b2", - "metadata": {}, - "outputs": [], - "source": [ - "device = device_widget()\n", - "\n", - "device" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "cd163eab-e803-4ae8-96da-22c3bf303630", - "metadata": {}, - "source": [ - "### Test on single image\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34a65afa-4201-428b-af11-b35e0d206e93", - "metadata": {}, - "outputs": [], - "source": [ - "import openvino as ov\n", - "\n", - "core = ov.Core()\n", - "seg_ov_model = core.read_model(seg_model_path)\n", - "\n", - "ov_config = {}\n", - "if device.value != \"CPU\":\n", - " seg_ov_model.reshape({0: [1, 3, 640, 640]})\n", - "if \"GPU\" in device.value or (\"AUTO\" in device.value and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "seg_compiled_model = core.compile_model(seg_ov_model, device.value, ov_config)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "389a4698-b7fc-4aa2-9833-9681f575d88b", - "metadata": {}, - "outputs": [], - "source": [ - "seg_model = YOLO(seg_model_path.parent, task=\"segment\")\n", - "\n", - "if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - "seg_model.predictor.model.ov_compiled_model = seg_compiled_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12d2522f-e939-4512-b74a-3e20baf1edc8", - "metadata": {}, - "outputs": [], - "source": [ - "res = seg_model(IMAGE_PATH)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7e918cf6-cdce-4e90-a6d7-c435a3c08d93", - "metadata": {}, - "source": [ - "Great! The result is the same, as produced by original models." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "13b69e12-2e22-44c1-bfed-fdc88f6c424d", - "metadata": {}, - "source": [ - "## Optimize model using NNCF Post-training Quantization API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n", - "We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv11.\n", - "\n", - "The optimization process contains the following steps:\n", - "\n", - "1. Create a Dataset for quantization.\n", - "2. Run `nncf.quantize` for getting an optimized model.\n", - "3. Serialize OpenVINO IR model, using the `openvino.runtime.serialize` function." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7d7eeae8-f3f4-4e95-b1a6-a23085f03ebc", - "metadata": {}, - "source": [ - "Please select below whether you would like to run quantization to improve model inference speed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4690b9fc-c49f-4882-b8a5-c82f842a9126", - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets\n", - "\n", - "int8_model_seg_path = Path(f\"{SEG_MODEL_NAME}_openvino_int8_model/{SEG_MODEL_NAME}.xml\")\n", - "quantized_seg_model = None\n", - "\n", - "to_quantize = widgets.Checkbox(\n", - " value=True,\n", - " description=\"Quantization\",\n", - " disabled=False,\n", - ")\n", - "\n", - "to_quantize" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "73cfdbd4-3b65-49e0-b8ab-ceb1d0362481", - "metadata": {}, - "source": [ - "Let's load `skip magic` extension to skip quantization if `to_quantize` is not selected" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bc6121aa-3a07-4066-a123-fe0ed0e72478", - "metadata": {}, - "outputs": [], - "source": [ - "# Fetch skip_kernel_extension module\n", - "import requests\n", - "\n", - "\n", - "if not Path(\"skip_kernel_extension.py\").exists():\n", - " r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py\",\n", - " )\n", - " open(\"skip_kernel_extension.py\", \"w\").write(r.text)\n", - "\n", - "%load_ext skip_kernel_extension" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "94fb2515-3645-4798-bc7c-082129ac86c0", - "metadata": {}, - "source": [ - "Reuse validation dataloader in accuracy testing for quantization. \n", - "For that, it should be wrapped into the `nncf.Dataset` object and define a transformation function for getting only input tensors." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd994958-6988-4a1d-ac7f-3efbd97135cc", - "metadata": {}, - "outputs": [], - "source": [ - "# %%skip not $to_quantize.value\n", - "\n", - "\n", - "import nncf\n", - "from typing import Dict\n", - "\n", - "from zipfile import ZipFile\n", - "\n", - "from ultralytics.data.utils import DATASETS_DIR\n", - "from ultralytics.utils import DEFAULT_CFG\n", - "from ultralytics.cfg import get_cfg\n", - "from ultralytics.data.converter import coco80_to_coco91_class\n", - "from ultralytics.data.utils import check_det_dataset\n", - "from ultralytics.utils import ops\n", - "\n", - "\n", - "if not int8_model_seg_path.exists():\n", - " DATA_URL = \"http://images.cocodataset.org/zips/val2017.zip\"\n", - " LABELS_URL = \"https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip\"\n", - " CFG_URL = \"https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml\"\n", - "\n", - " OUT_DIR = DATASETS_DIR\n", - "\n", - " DATA_PATH = OUT_DIR / \"val2017.zip\"\n", - " LABELS_PATH = OUT_DIR / \"coco2017labels-segments.zip\"\n", - " CFG_PATH = OUT_DIR / \"coco.yaml\"\n", - "\n", - " download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n", - " download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n", - " download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n", - "\n", - " if not (OUT_DIR / \"coco/labels\").exists():\n", - " with ZipFile(LABELS_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR)\n", - " with ZipFile(DATA_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR / \"coco/images\")\n", - "\n", - " args = get_cfg(cfg=DEFAULT_CFG)\n", - " args.data = str(CFG_PATH)\n", - " seg_validator = seg_model.task_map[seg_model.task][\"validator\"](args=args)\n", - " seg_validator.data = check_det_dataset(args.data)\n", - " seg_validator.stride = 32\n", - " seg_data_loader = seg_validator.get_dataloader(OUT_DIR / \"coco/\", 1)\n", - "\n", - " seg_validator.is_coco = True\n", - " seg_validator.class_map = coco80_to_coco91_class()\n", - " seg_validator.names = label_map\n", - " seg_validator.metrics.names = seg_validator.names\n", - " seg_validator.nc = 80\n", - " seg_validator.nm = 32\n", - " seg_validator.process = ops.process_mask\n", - " seg_validator.plot_masks = []\n", - "\n", - " def transform_fn(data_item: Dict):\n", - " \"\"\"\n", - " Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n", - " Parameters:\n", - " data_item: Dict with data item produced by DataLoader during iteration\n", - " Returns:\n", - " input_tensor: Input data for quantization\n", - " \"\"\"\n", - " input_tensor = seg_validator.preprocess(data_item)[\"img\"].numpy()\n", - " return input_tensor\n", - "\n", - " quantization_dataset = nncf.Dataset(seg_data_loader, transform_fn)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "91629284-e261-494d-9464-146ed7190084", - "metadata": {}, - "source": [ - "The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n", - "Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. Ultralytics models contain non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations. For more accurate results, we should keep the operation in the postprocessing subgraph in floating point precision, using the `ignored_scope` parameter.\n", - "\n", - ">**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2e8cec4-d0b3-4da0-b54c-7964e7bcbfe2", - "metadata": { - "test_replace": { - "ignored_scope=ignored_scope\n": "ignored_scope=ignored_scope, subset_size=10\n" + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'a2e7d62b-5779-4211-822c-457c77321f8b', + 'metadata': {}, + 'source': [ + '# Convert and Optimize YOLOv11 instance segmentation model with OpenVINO™\n', + '\n', + 'Instance segmentation goes a step further than object detection and involves identifying individual objects in an image and segmenting them from the rest of the image. Instance segmentation as an object detection are often used as key components in computer vision systems. \n', + 'Applications that use real-time instance segmentation models include video analytics, robotics, autonomous vehicles, multi-object tracking and object counting, medical image analysis, and many others.\n', + '\n', + '\n', + 'This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLOv11 with OpenVINO. We consider the steps required for instance segmentation scenario. You can find more details about model on [model page](https://docs.ultralytics.com/models/yolo11/) in Ultralytics documentation.\n', + '\n', + 'The tutorial consists of the following steps:\n', + '- Prepare the PyTorch model.\n', + '- Download and prepare a dataset.\n', + '- Validate the original model.\n', + '- Convert the PyTorch model to OpenVINO IR.\n', + '- Validate the converted model.\n', + '- Prepare and run optimization pipeline.\n', + '- Compare performance of the FP32 and quantized models.\n', + '- Compare accuracy of the FP32 and quantized models.\n', + '- Live demo\n', + '\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Get PyTorch model](#Get-PyTorch-model)\n', + ' - [Prerequisites](#Prerequisites)\n', + '- [Instantiate model](#Instantiate-model)\n', + ' - [Convert model to OpenVINO IR](#Convert-model-to-OpenVINO-IR)\n', + ' - [Verify model inference](#Verify-model-inference)\n', + ' - [Select inference device](#Select-inference-device)\n', + ' - [Test on single image](#Test-on-single-image)\n', + '- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n', + ' - [Validate Quantized model inference](#Validate-Quantized-model-inference)\n', + '- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n', + ' - [Compare performance of the Original and Quantized Models](#Compare-performance-of-the-Original-and-Quantized-Models)\n', + '- [Other ways to optimize model](#Other-ways-to-optimize-model)\n', + '- [Live demo](#Live-demo)\n', + ' - [Run Live Object Detection and Segmentation](#Run-Live-Object-Detection-and-Segmentation)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'd7a12678-b12f-48d1-9735-398855733e46', + 'metadata': {}, + 'source': [ + '## Get PyTorch model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Generally, PyTorch models represent an instance of the [`torch.nn.Module`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html) class, initialized by a state dictionary with model weights.\n', + 'We will use the YOLOv11 nano model (also known as `yolo11n-seg`) pre-trained on a COCO dataset, which is available in this [repo](https://github.com/ultralytics/ultralytics). Similar steps are also applicable to other YOLOv11 models.\n', + 'Typical steps to obtain a pre-trained model:\n', + '1. Create an instance of a model class.\n', + '2. Load a checkpoint state dict, which contains the pre-trained model weights.\n', + '3. Turn the model to evaluation for switching some operations to inference mode.\n', + '\n', + 'In this case, the creators of the model provide an API that enables converting the YOLOv11 model to OpenVINO IR. Therefore, we do not need to do these steps manually.' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'e2267760-cbfe-41c6-958d-cad9f845d5bb', + 'metadata': {}, + 'source': [ + '#### Prerequisites\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Install necessary packages.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '30d04872-6916-454c-9211-6c644b50dc04', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%pip install -q "openvino>=2024.0.0" "nncf>=2.9.0"\n', + '%pip install -q "torch>=2.1" "torchvision>=0.16" "ultralytics==8.3.0" opencv-python tqdm --extra-index-url https://download.pytorch.org/whl/cpu' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '1bbe319c', + 'metadata': {}, + 'source': [ + 'Import required utility functions.\n', + 'The lower cell will download the `notebook_utils` Python module from GitHub.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'a2f6cd89', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'from pathlib import Path\n', + 'import requests\n', + '\n', + 'if not Path("notebook_utils.py").exists():\n', + ' r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ' )\n', + '\n', + ' open("notebook_utils.py", "w").write(r.text)\n', + '\n', + '\n', + 'from notebook_utils import download_file, VideoPlayer, device_widget' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '373658bd-7e64-4479-914e-f2742d330afd', + 'metadata': {}, + 'outputs': [], + 'source': [ + '# Download a test sample\n', + 'IMAGE_PATH = Path("./data/coco_bike.jpg")\n', + '\n', + 'if not IMAGE_PATH.exists():\n', + ' download_file(\n', + ' url="https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg",\n', + ' filename=IMAGE_PATH.name,\n', + ' directory=IMAGE_PATH.parent,\n', + ' )' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'ee32fd08-650c-4751-bb41-d8afccb2495e', + 'metadata': {}, + 'source': [ + '## Instantiate model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'For loading the model, required to specify a path to the model checkpoint. It can be some local path or name available on models hub (in this case model checkpoint will be downloaded automatically). \n', + 'You can select model using widget bellow:' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '7c3963a5-b50b-4ffe-b710-ccd1f4a65380', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'model_id = [\n', + ' "yolo11n-seg",\n', + ' "yolo11s-seg",\n', + ' "yolo11m-seg",\n', + ' "yolo11l-seg",\n', + ' "yolo11x-seg",\n', + ' "yolov8n-seg",\n', + ' "yolov8s-seg",\n', + ' "yolov8m-seg",\n', + ' "yolov8l-seg",\n', + ' "yolov8x-seg",\n', + ']\n', + '\n', + 'model_name = widgets.Dropdown(options=model_id, value=model_id[0], description="Model")\n', + '\n', + 'model_name' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '60105d86-d7e0-47a9-ad88-4fff014ba3d8', + 'metadata': {}, + 'source': [ + 'Making prediction, the model accepts a path to input image and returns list with Results class object. Results contains boxes for object detection model and boxes and masks for segmentation model. Also it contains utilities for processing results, for example, `plot()` method for drawing.\n', + '\n', + 'Let us consider the examples:' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'd4994e1e-a3ee-4620-bc8b-237a55c47742', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'from PIL import Image\n', + 'from ultralytics import YOLO\n', + '\n', + 'SEG_MODEL_NAME = model_name.value\n', + '\n', + 'seg_model = YOLO(f"{SEG_MODEL_NAME}.pt")\n', + 'label_map = seg_model.model.names\n', + '\n', + 'res = seg_model(IMAGE_PATH)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'e345ffcc-c4b8-44ba-8b03-f37e63a060da', + 'metadata': {}, + 'source': [ + '### Convert model to OpenVINO IR\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Ultralytics provides API for convenient model exporting to different formats including OpenVINO IR. `model.export` is responsible for model conversion. We need to specify the format, and additionally, we can preserve dynamic shapes in the model.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '5c3ef363-f6a1-4fe5-b7ff-eb5a9c0789f1', + 'metadata': {}, + 'outputs': [], + 'source': [ + '# instance segmentation model\n', + 'seg_model_path = Path(f"{SEG_MODEL_NAME}_openvino_model/{SEG_MODEL_NAME}.xml")\n', + 'if not seg_model_path.exists():\n', + ' seg_model.export(format="openvino", dynamic=True, half=True)' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '713cd45f-2b19-4a1e-bc9d-5e69bd95d896', + 'metadata': {}, + 'source': [ + '### Verify model inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'We can reuse the base model pipeline for pre- and postprocessing just replacing the inference method where we will use the IR model for inference.' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'f9b9c472', + 'metadata': {}, + 'source': [ + '### Select inference device\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Select device from dropdown list for running inference using OpenVINO' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '4e0652b2', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'device = device_widget()\n', + '\n', + 'device' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'cd163eab-e803-4ae8-96da-22c3bf303630', + 'metadata': {}, + 'source': [ + '### Test on single image\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '34a65afa-4201-428b-af11-b35e0d206e93', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import openvino as ov\n', + '\n', + 'core = ov.Core()\n', + 'seg_ov_model = core.read_model(seg_model_path)\n', + '\n', + 'ov_config = {}\n', + 'if device.value != "CPU":\n', + ' seg_ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'if "GPU" in device.value or ("AUTO" in device.value and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + 'seg_compiled_model = core.compile_model(seg_ov_model, device.value, ov_config)' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '389a4698-b7fc-4aa2-9833-9681f575d88b', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'seg_model = YOLO(seg_model_path.parent, task="segment")\n', + '\n', + 'if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + 'seg_model.predictor.model.ov_compiled_model = seg_compiled_model' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '12d2522f-e939-4512-b74a-3e20baf1edc8', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'res = seg_model(IMAGE_PATH)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '7e918cf6-cdce-4e90-a6d7-c435a3c08d93', + 'metadata': {}, + 'source': [ + 'Great! The result is the same, as produced by original models.' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '13b69e12-2e22-44c1-bfed-fdc88f6c424d', + 'metadata': {}, + 'source': [ + '## Optimize model using NNCF Post-training Quantization API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n', + 'We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv11.\n', + '\n', + 'The optimization process contains the following steps:\n', + '\n', + '1. Create a Dataset for quantization.\n', + '2. Run `nncf.quantize` for getting an optimized model.\n', + '3. Serialize OpenVINO IR model, using the `openvino.runtime.serialize` function.' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '7d7eeae8-f3f4-4e95-b1a6-a23085f03ebc', + 'metadata': {}, + 'source': [ + 'Please select below whether you would like to run quantization to improve model inference speed.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '4690b9fc-c49f-4882-b8a5-c82f842a9126', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'int8_model_seg_path = Path(f"{SEG_MODEL_NAME}_openvino_int8_model/{SEG_MODEL_NAME}.xml")\n', + 'quantized_seg_model = None\n', + '\n', + 'to_quantize = widgets.Checkbox(\n', + ' value=True,\n', + ' description="Quantization",\n', + ' disabled=False,\n', + ')\n', + '\n', + 'to_quantize' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '73cfdbd4-3b65-49e0-b8ab-ceb1d0362481', + 'metadata': {}, + 'source': [ + 'Let\'s load `skip magic` extension to skip quantization if `to_quantize` is not selected' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'bc6121aa-3a07-4066-a123-fe0ed0e72478', + 'metadata': {}, + 'outputs': [], + 'source': [ + '# Fetch skip_kernel_extension module\n', + 'import requests\n', + '\n', + '\n', + 'if not Path("skip_kernel_extension.py").exists():\n', + ' r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py",\n', + ' )\n', + ' open("skip_kernel_extension.py", "w").write(r.text)\n', + '\n', + '%load_ext skip_kernel_extension' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '94fb2515-3645-4798-bc7c-082129ac86c0', + 'metadata': {}, + 'source': [ + 'Reuse validation dataloader in accuracy testing for quantization. \n', + 'For that, it should be wrapped into the `nncf.Dataset` object and define a transformation function for getting only input tensors.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'fd994958-6988-4a1d-ac7f-3efbd97135cc', + 'metadata': {}, + 'outputs': [], + 'source': [ + '# %%skip not $to_quantize.value\n', + '\n', + '\n', + 'import nncf\n', + 'from typing import Dict\n', + '\n', + 'from zipfile import ZipFile\n', + '\n', + 'from ultralytics.data.utils import DATASETS_DIR\n', + 'from ultralytics.utils import DEFAULT_CFG\n', + 'from ultralytics.cfg import get_cfg\n', + 'from ultralytics.data.converter import coco80_to_coco91_class\n', + 'from ultralytics.data.utils import check_det_dataset\n', + 'from ultralytics.utils import ops\n', + '\n', + '\n', + 'if not int8_model_seg_path.exists():\n', + ' DATA_URL = "http://images.cocodataset.org/zips/val2017.zip"\n', + ' LABELS_URL = "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip"\n', + ' CFG_URL = "https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml"\n', + '\n', + ' OUT_DIR = DATASETS_DIR\n', + '\n', + ' DATA_PATH = OUT_DIR / "val2017.zip"\n', + ' LABELS_PATH = OUT_DIR / "coco2017labels-segments.zip"\n', + ' CFG_PATH = OUT_DIR / "coco.yaml"\n', + '\n', + ' download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n', + ' download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n', + ' download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n', + '\n', + ' if not (OUT_DIR / "coco/labels").exists():\n', + ' with ZipFile(LABELS_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR)\n', + ' with ZipFile(DATA_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR / "coco/images")\n', + '\n', + ' args = get_cfg(cfg=DEFAULT_CFG)\n', + ' args.data = str(CFG_PATH)\n', + ' seg_validator = seg_model.task_map[seg_model.task]["validator"](args=args)\n', + ' seg_validator.data = check_det_dataset(args.data)\n', + ' seg_validator.stride = 32\n', + ' seg_data_loader = seg_validator.get_dataloader(OUT_DIR / "coco/", 1)\n', + '\n', + ' seg_validator.is_coco = True\n', + ' seg_validator.class_map = coco80_to_coco91_class()\n', + ' seg_validator.names = label_map\n', + ' seg_validator.metrics.names = seg_validator.names\n', + ' seg_validator.nc = 80\n', + ' seg_validator.nm = 32\n', + ' seg_validator.process = ops.process_mask\n', + ' seg_validator.plot_masks = []\n', + '\n', + ' def transform_fn(data_item: Dict):\n', + ' """\n', + ' Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n', + ' Parameters:\n', + ' data_item: Dict with data item produced by DataLoader during iteration\n', + ' Returns:\n', + ' input_tensor: Input data for quantization\n', + ' """\n', + ' input_tensor = seg_validator.preprocess(data_item)["img"].numpy()\n', + ' return input_tensor\n', + '\n', + ' quantization_dataset = nncf.Dataset(seg_data_loader, transform_fn)' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '91629284-e261-494d-9464-146ed7190084', + 'metadata': {}, + 'source': [ + 'The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n', + 'Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. Ultralytics models contain non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations. For more accurate results, we should keep the operation in the postprocessing subgraph in floating point precision, using the `ignored_scope` parameter.\n', + '\n', + '>**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'b2e8cec4-d0b3-4da0-b54c-7964e7bcbfe2', + 'metadata': { + 'test_replace': { + 'ignored_scope=ignored_scope\n': 'ignored_scope=ignored_scope, subset_size=10\n' } }, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "if not int8_model_seg_path.exists():\n", - " ignored_scope = nncf.IgnoredScope( # post-processing\n", - " subgraphs=[\n", - " nncf.Subgraph(inputs=[f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_1\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_2\",\n", - " f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_7\"],\n", - " outputs=[f\"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_8\"])\n", - " ]\n", - " )\n", - "\n", - " # Segmentation model\n", - " quantized_seg_model = nncf.quantize(\n", - " seg_ov_model,\n", - " quantization_dataset,\n", - " preset=nncf.QuantizationPreset.MIXED,\n", - " ignored_scope=ignored_scope\n", - " )\n", - "\n", - " print(f\"Quantized segmentation model will be saved to {int8_model_seg_path}\")\n", - " ov.save_model(quantized_seg_model, str(int8_model_seg_path))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "c8c92a68-3eb8-4ea5-9e35-d496f45b3cc3", - "metadata": {}, - "source": [ - "### Validate Quantized model inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "`nncf.quantize` returns the OpenVINO Model class instance, which is suitable for loading on a device for making predictions. `INT8` model input data and output result formats have no difference from the floating point model representation. Therefore, we can reuse the same `detect` function defined above for getting the `INT8` model result on the image." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4003a3ae", - "metadata": {}, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "711200ec-2f26-47cc-b62d-3802961d5711", - "metadata": {}, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "if quantized_seg_model is None:\n", - " quantized_seg_model = core.read_model(int8_model_seg_path)\n", - "\n", - "ov_config = {}\n", - "if device.value != \"CPU\":\n", - " quantized_seg_model.reshape({0: [1, 3, 640, 640]})\n", - "if \"GPU\" in device.value or (\"AUTO\" in device.value and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "\n", - "quantized_seg_compiled_model = core.compile_model(quantized_seg_model, device.value, ov_config)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2e3ea3b-5935-4474-9f08-f51fc688b9c0", - "metadata": {}, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "\n", - "if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - "seg_model.predictor.model.ov_compiled_model = quantized_seg_compiled_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bef2ed5d-5762-41a6-af2e-ee4f9fd56557", - "metadata": {}, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "res = seg_model(IMAGE_PATH)\n", - "display(Image.fromarray(res[0].plot()[:, :, ::-1]))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "b289f057", - "metadata": {}, - "source": [ - "## Compare the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "59081cae-9c92-46f0-8117-ed8c6fa6ff19", - "metadata": {}, - "source": [ - "### Compare performance of the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "Finally, use the OpenVINO [Benchmark Tool](https://docs.openvino.ai/2024/learn-openvino/openvino-samples/benchmark-tool.html) to measure the inference performance of the `FP32` and `INT8` models.\n", - "\n", - "> **Note**: For more accurate performance, it is recommended to run `benchmark_app` in a terminal/command prompt after closing other applications. Run `benchmark_app -m -d CPU -shape \"\"` to benchmark async inference on CPU on specific input data shape for one minute. Change `CPU` to `GPU` to benchmark on GPU. Run `benchmark_app --help` to see an overview of all command-line options." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8677df4", - "metadata": {}, - "outputs": [], - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "546828d2", - "metadata": {}, - "outputs": [], - "source": [ - "if int8_model_seg_path.exists():\n", - " !benchmark_app -m $seg_model_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d43554b", - "metadata": {}, - "outputs": [], - "source": [ - "if int8_model_seg_path.exists():\n", - " !benchmark_app -m $int8_model_seg_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "184bcebd-588d-4b4d-a60a-0fa753a07ef9", - "metadata": {}, - "source": [ - "## Other ways to optimize model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "The performance could be also improved by another OpenVINO method such as async inference pipeline or preprocessing API.\n", - "\n", - "Async Inference pipeline help to utilize the device more optimal. The key advantage of the Async API is that when a device is busy with inference, the application can perform other tasks in parallel (for example, populating inputs or scheduling other requests) rather than wait for the current inference to complete first. To understand how to perform async inference using openvino, refer to [Async API tutorial](../async-api/async-api.ipynb)\n", - "\n", - "Preprocessing API enables making preprocessing a part of the model reducing application code and dependency on additional image processing libraries. \n", - "The main advantage of Preprocessing API is that preprocessing steps will be integrated into the execution graph and will be performed on a selected device (CPU/GPU etc.) rather than always being executed on CPU as part of an application. This will also improve selected device utilization. For more information, refer to the overview of [Preprocessing API tutorial](../optimize-preprocessing/optimize-preprocessing.ipynb). To see, how it could be used with YOLOV8 object detection model, please, see [Convert and Optimize YOLOv8 real-time object detection with OpenVINO tutorial](./yolov8-object-detection.ipynb)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "2e3b4862-c182-4ce4-a473-9f38d98deab8", - "metadata": { - "tags": [] - }, - "source": [ - "## Live demo\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "The following code runs model inference on a video:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c7bb92e-e301-45a9-b5ff-f7953fad298f", - "metadata": {}, - "outputs": [], - "source": [ - "import collections\n", - "import time\n", - "import cv2\n", - "from IPython import display\n", - "import numpy as np\n", - "\n", - "\n", - "def run_instance_segmentation(\n", - " source=0,\n", - " flip=False,\n", - " use_popup=False,\n", - " skip_first_frames=0,\n", - " model=seg_model,\n", - " device=device.value,\n", - " video_width=1280,\n", - "):\n", - " player = None\n", - "\n", - " ov_config = {}\n", - " if device != \"CPU\":\n", - " model.reshape({0: [1, 3, 640, 640]})\n", - " if \"GPU\" in device or (\"AUTO\" in device and \"GPU\" in core.available_devices):\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - " compiled_model = core.compile_model(model, device, ov_config)\n", - "\n", - " if seg_model.predictor is None:\n", - " custom = {\"conf\": 0.25, \"batch\": 1, \"save\": False, \"mode\": \"predict\"} # method defaults\n", - " args = {**seg_model.overrides, **custom}\n", - " seg_model.predictor = seg_model._smart_load(\"predictor\")(overrides=args, _callbacks=seg_model.callbacks)\n", - " seg_model.predictor.setup_model(model=seg_model.model)\n", - "\n", - " seg_model.predictor.model.ov_compiled_model = compiled_model\n", - "\n", - " try:\n", - " # Create a video player to play with target fps.\n", - " player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames, width=video_width)\n", - " # Start capturing.\n", - " player.start()\n", - " if use_popup:\n", - " title = \"Press ESC to Exit\"\n", - " cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n", - "\n", - " processing_times = collections.deque()\n", - " while True:\n", - " # Grab the frame.\n", - " frame = player.next()\n", - " if frame is None:\n", - " print(\"Source ended\")\n", - " break\n", - " # If the frame is larger than video_width, reduce size to improve the performance.\n", - " # If more, increase size for better demo expirience.\n", - " scale = video_width / max(frame.shape)\n", - " frame = cv2.resize(\n", - " src=frame,\n", - " dsize=None,\n", - " fx=scale,\n", - " fy=scale,\n", - " interpolation=cv2.INTER_AREA,\n", - " )\n", - " # Get the results.\n", - " input_image = np.array(frame)\n", - "\n", - " start_time = time.time()\n", - " detections = seg_model(input_image)\n", - " stop_time = time.time()\n", - " frame = detections[0].plot()\n", - "\n", - " processing_times.append(stop_time - start_time)\n", - " # Use processing times from last 200 frames.\n", - " if len(processing_times) > 200:\n", - " processing_times.popleft()\n", - "\n", - " _, f_width = frame.shape[:2]\n", - " # Mean processing time [ms].\n", - " processing_time = np.mean(processing_times) * 1000\n", - " fps = 1000 / processing_time\n", - " cv2.putText(\n", - " img=frame,\n", - " text=f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n", - " org=(20, 40),\n", - " fontFace=cv2.FONT_HERSHEY_COMPLEX,\n", - " fontScale=f_width / 1000,\n", - " color=(0, 0, 255),\n", - " thickness=1,\n", - " lineType=cv2.LINE_AA,\n", - " )\n", - " # Use this workaround if there is flickering.\n", - " if use_popup:\n", - " cv2.imshow(winname=title, mat=frame)\n", - " key = cv2.waitKey(1)\n", - " # escape = 27\n", - " if key == 27:\n", - " break\n", - " else:\n", - " # Encode numpy array to jpg.\n", - " _, encoded_img = cv2.imencode(ext=\".jpg\", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n", - " # Create an IPython image.\n", - " i = display.Image(data=encoded_img)\n", - " # Display the image in this notebook.\n", - " display.clear_output(wait=True)\n", - " display.display(i)\n", - " # ctrl-c\n", - " except KeyboardInterrupt:\n", - " print(\"Interrupted\")\n", - " # any different error\n", - " except RuntimeError as e:\n", - " print(e)\n", - " finally:\n", - " if player is not None:\n", - " # Stop capturing.\n", - " player.stop()\n", - " if use_popup:\n", - " cv2.destroyAllWindows()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "cc5b4ba6-f478-4417-b09d-93fee5adca41", - "metadata": {}, - "source": [ - "### Run Live Object Detection and Segmentation\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "Use a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n", - "\n", - ">**NOTE**: To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a remote server (for example, in Binder or Google Colab service), the webcam will not work. By default, the lower cell will run model inference on a video file. If you want to try live inference on your webcam set `WEBCAM_INFERENCE = True`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "90708017", - "metadata": {}, - "outputs": [], - "source": [ - "WEBCAM_INFERENCE = False\n", - "\n", - "if WEBCAM_INFERENCE:\n", - " VIDEO_SOURCE = 0 # Webcam\n", - "else:\n", - " if not Path(\"people.mp4\").exists():\n", - " download_file(\n", - " \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4\",\n", - " \"people.mp4\",\n", - " )\n", - " VIDEO_SOURCE = \"people.mp4\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e8f1239c", - "metadata": {}, - "outputs": [], - "source": [ - "device" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "440f2d0e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "run_instance_segmentation(\n", - " source=VIDEO_SOURCE,\n", - " flip=True,\n", - " use_popup=False,\n", - " model=seg_ov_model,\n", - " device=device.value,\n", - " video_width=1280,\n", - ")" + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'if not int8_model_seg_path.exists():\n', + ' ignored_scope = nncf.IgnoredScope( # post-processing\n', + ' subgraphs=[\n', + ` nncf.Subgraph(inputs=[f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_1",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_2",\n`, + ` f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_7"],\n`, + ` outputs=[f"__module.model.{22 if 'v8' in SEG_MODEL_NAME else 23}/aten::cat/Concat_8"])\n`, + ' ]\n', + ' )\n', + '\n', + ' # Segmentation model\n', + ' quantized_seg_model = nncf.quantize(\n', + ' seg_ov_model,\n', + ' quantization_dataset,\n', + ' preset=nncf.QuantizationPreset.MIXED,\n', + ' ignored_scope=ignored_scope\n', + ' )\n', + '\n', + ' print(f"Quantized segmentation model will be saved to {int8_model_seg_path}")\n', + ' ov.save_model(quantized_seg_model, str(int8_model_seg_path))' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'c8c92a68-3eb8-4ea5-9e35-d496f45b3cc3', + 'metadata': {}, + 'source': [ + '### Validate Quantized model inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '`nncf.quantize` returns the OpenVINO Model class instance, which is suitable for loading on a device for making predictions. `INT8` model input data and output result formats have no difference from the floating point model representation. Therefore, we can reuse the same `detect` function defined above for getting the `INT8` model result on the image.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '4003a3ae', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '711200ec-2f26-47cc-b62d-3802961d5711', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'if quantized_seg_model is None:\n', + ' quantized_seg_model = core.read_model(int8_model_seg_path)\n', + '\n', + 'ov_config = {}\n', + 'if device.value != "CPU":\n', + ' quantized_seg_model.reshape({0: [1, 3, 640, 640]})\n', + 'if "GPU" in device.value or ("AUTO" in device.value and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + '\n', + 'quantized_seg_compiled_model = core.compile_model(quantized_seg_model, device.value, ov_config)' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'b2e3ea3b-5935-4474-9f08-f51fc688b9c0', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + '\n', + 'if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + 'seg_model.predictor.model.ov_compiled_model = quantized_seg_compiled_model' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'bef2ed5d-5762-41a6-af2e-ee4f9fd56557', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'res = seg_model(IMAGE_PATH)\n', + 'display(Image.fromarray(res[0].plot()[:, :, ::-1]))' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'b289f057', + 'metadata': {}, + 'source': [ + '## Compare the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)\n' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '59081cae-9c92-46f0-8117-ed8c6fa6ff19', + 'metadata': {}, + 'source': [ + '### Compare performance of the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)\n', + 'Finally, use the OpenVINO [Benchmark Tool](https://docs.openvino.ai/2024/learn-openvino/openvino-samples/benchmark-tool.html) to measure the inference performance of the `FP32` and `INT8` models.\n', + '\n', + '> **Note**: For more accurate performance, it is recommended to run `benchmark_app` in a terminal/command prompt after closing other applications. Run `benchmark_app -m -d CPU -shape ""` to benchmark async inference on CPU on specific input data shape for one minute. Change `CPU` to `GPU` to benchmark on GPU. Run `benchmark_app --help` to see an overview of all command-line options.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'b8677df4', + 'metadata': {}, + 'outputs': [], + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '546828d2', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'if int8_model_seg_path.exists():\n', + ' !benchmark_app -m $seg_model_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '1d43554b', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'if int8_model_seg_path.exists():\n', + ' !benchmark_app -m $int8_model_seg_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '184bcebd-588d-4b4d-a60a-0fa753a07ef9', + 'metadata': {}, + 'source': [ + '## Other ways to optimize model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'The performance could be also improved by another OpenVINO method such as async inference pipeline or preprocessing API.\n', + '\n', + 'Async Inference pipeline help to utilize the device more optimal. The key advantage of the Async API is that when a device is busy with inference, the application can perform other tasks in parallel (for example, populating inputs or scheduling other requests) rather than wait for the current inference to complete first. To understand how to perform async inference using openvino, refer to [Async API tutorial](../async-api/async-api.ipynb)\n', + '\n', + 'Preprocessing API enables making preprocessing a part of the model reducing application code and dependency on additional image processing libraries. \n', + 'The main advantage of Preprocessing API is that preprocessing steps will be integrated into the execution graph and will be performed on a selected device (CPU/GPU etc.) rather than always being executed on CPU as part of an application. This will also improve selected device utilization. For more information, refer to the overview of [Preprocessing API tutorial](../optimize-preprocessing/optimize-preprocessing.ipynb). To see, how it could be used with YOLOV8 object detection model, please, see [Convert and Optimize YOLOv8 real-time object detection with OpenVINO tutorial](./yolov8-object-detection.ipynb)' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': '2e3b4862-c182-4ce4-a473-9f38d98deab8', + 'metadata': { + 'tags': [] + }, + 'source': [ + '## Live demo\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'The following code runs model inference on a video:' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '4c7bb92e-e301-45a9-b5ff-f7953fad298f', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import collections\n', + 'import time\n', + 'import cv2\n', + 'from IPython import display\n', + 'import numpy as np\n', + '\n', + '\n', + 'def run_instance_segmentation(\n', + ' source=0,\n', + ' flip=False,\n', + ' use_popup=False,\n', + ' skip_first_frames=0,\n', + ' model=seg_model,\n', + ' device=device.value,\n', + ' video_width=1280,\n', + '):\n', + ' player = None\n', + '\n', + ' ov_config = {}\n', + ' if device != "CPU":\n', + ' model.reshape({0: [1, 3, 640, 640]})\n', + ' if "GPU" in device or ("AUTO" in device and "GPU" in core.available_devices):\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + ' compiled_model = core.compile_model(model, device, ov_config)\n', + '\n', + ' if seg_model.predictor is None:\n', + ' custom = {"conf": 0.25, "batch": 1, "save": False, "mode": "predict"} # method defaults\n', + ' args = {**seg_model.overrides, **custom}\n', + ' seg_model.predictor = seg_model._smart_load("predictor")(overrides=args, _callbacks=seg_model.callbacks)\n', + ' seg_model.predictor.setup_model(model=seg_model.model)\n', + '\n', + ' seg_model.predictor.model.ov_compiled_model = compiled_model\n', + '\n', + ' try:\n', + ' # Create a video player to play with target fps.\n', + ' player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames, width=video_width)\n', + ' # Start capturing.\n', + ' player.start()\n', + ' if use_popup:\n', + ' title = "Press ESC to Exit"\n', + ' cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n', + '\n', + ' processing_times = collections.deque()\n', + ' while True:\n', + ' # Grab the frame.\n', + ' frame = player.next()\n', + ' if frame is None:\n', + ' print("Source ended")\n', + ' break\n', + ' # If the frame is larger than video_width, reduce size to improve the performance.\n', + ' # If more, increase size for better demo expirience.\n', + ' scale = video_width / max(frame.shape)\n', + ' frame = cv2.resize(\n', + ' src=frame,\n', + ' dsize=None,\n', + ' fx=scale,\n', + ' fy=scale,\n', + ' interpolation=cv2.INTER_AREA,\n', + ' )\n', + ' # Get the results.\n', + ' input_image = np.array(frame)\n', + '\n', + ' start_time = time.time()\n', + ' detections = seg_model(input_image)\n', + ' stop_time = time.time()\n', + ' frame = detections[0].plot()\n', + '\n', + ' processing_times.append(stop_time - start_time)\n', + ' # Use processing times from last 200 frames.\n', + ' if len(processing_times) > 200:\n', + ' processing_times.popleft()\n', + '\n', + ' _, f_width = frame.shape[:2]\n', + ' # Mean processing time [ms].\n', + ' processing_time = np.mean(processing_times) * 1000\n', + ' fps = 1000 / processing_time\n', + ' cv2.putText(\n', + ' img=frame,\n', + ' text=f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n', + ' org=(20, 40),\n', + ' fontFace=cv2.FONT_HERSHEY_COMPLEX,\n', + ' fontScale=f_width / 1000,\n', + ' color=(0, 0, 255),\n', + ' thickness=1,\n', + ' lineType=cv2.LINE_AA,\n', + ' )\n', + ' # Use this workaround if there is flickering.\n', + ' if use_popup:\n', + ' cv2.imshow(winname=title, mat=frame)\n', + ' key = cv2.waitKey(1)\n', + ' # escape = 27\n', + ' if key == 27:\n', + ' break\n', + ' else:\n', + ' # Encode numpy array to jpg.\n', + ' _, encoded_img = cv2.imencode(ext=".jpg", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n', + ' # Create an IPython image.\n', + ' i = display.Image(data=encoded_img)\n', + ' # Display the image in this notebook.\n', + ' display.clear_output(wait=True)\n', + ' display.display(i)\n', + ' # ctrl-c\n', + ' except KeyboardInterrupt:\n', + ' print("Interrupted")\n', + ' # any different error\n', + ' except RuntimeError as e:\n', + ' print(e)\n', + ' finally:\n', + ' if player is not None:\n', + ' # Stop capturing.\n', + ' player.stop()\n', + ' if use_popup:\n', + ' cv2.destroyAllWindows()' + ] + }, + { + 'attachments': {}, + 'cell_type': 'markdown', + 'id': 'cc5b4ba6-f478-4417-b09d-93fee5adca41', + 'metadata': {}, + 'source': [ + '### Run Live Object Detection and Segmentation\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + 'Use a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`.\n', + '\n', + '>**NOTE**: To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a remote server (for example, in Binder or Google Colab service), the webcam will not work. By default, the lower cell will run model inference on a video file. If you want to try live inference on your webcam set `WEBCAM_INFERENCE = True`' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '90708017', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'WEBCAM_INFERENCE = False\n', + '\n', + 'if WEBCAM_INFERENCE:\n', + ' VIDEO_SOURCE = 0 # Webcam\n', + 'else:\n', + ' if not Path("people.mp4").exists():\n', + ' download_file(\n', + ' "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4",\n', + ' "people.mp4",\n', + ' )\n', + ' VIDEO_SOURCE = "people.mp4"' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': 'e8f1239c', + 'metadata': {}, + 'outputs': [], + 'source': [ + 'device' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'id': '440f2d0e', + 'metadata': { + 'tags': [] + }, + 'outputs': [], + 'source': [ + 'run_instance_segmentation(\n', + ' source=VIDEO_SOURCE,\n', + ' flip=True,\n', + ' use_popup=False,\n', + ' model=seg_ov_model,\n', + ' device=device.value,\n', + ' video_width=1280,\n', + ')' ] } ].map(fromJupyterCell) @@ -10241,939 +10241,939 @@ suite('NotebookDiff Diff Service', () => { const { mapping, diff } = await mapCells( [ { - "cell_type": "markdown", - "id": "e9d010ff-85ba-4e69-b439-ba89e4a3a715", - "source": [ - "# Convert and Optimize YOLOv10 with OpenVINO\n", - "\n", - "Real-time object detection aims to accurately predict object categories and positions in images with low latency. The YOLO series has been at the forefront of this research due to its balance between performance and efficiency. However, reliance on NMS and architectural inefficiencies have hindered optimal performance. YOLOv10 addresses these issues by introducing consistent dual assignments for NMS-free training and a holistic efficiency-accuracy driven model design strategy.\n", - "\n", - "YOLOv10, built on the [Ultralytics Python package](https://pypi.org/project/ultralytics/) by researchers at [Tsinghua University](https://www.tsinghua.edu.cn/en/), introduces a new approach to real-time object detection, addressing both the post-processing and model architecture deficiencies found in previous YOLO versions. By eliminating non-maximum suppression (NMS) and optimizing various model components, YOLOv10 achieves state-of-the-art performance with significantly reduced computational overhead. Extensive experiments demonstrate its superior accuracy-latency trade-offs across multiple model scales.\n", - "\n", - "![yolov10-approach.png](https://github.com/ultralytics/ultralytics/assets/26833433/f9b1bec0-928e-41ce-a205-e12db3c4929a)\n", - "\n", - "More details about model architecture you can find in original [repo](https://github.com/THU-MIG/yolov10), [paper](https://arxiv.org/abs/2405.14458) and [Ultralytics documentation](https://docs.ultralytics.com/models/yolov10/).\n", - "\n", - "This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLO V10 with OpenVINO.\n", - "\n", - "The tutorial consists of the following steps:\n", - "\n", - "- Prepare PyTorch model\n", - "- Convert PyTorch model to OpenVINO IR\n", - "- Run model inference with OpenVINO\n", - "- Prepare and run optimization pipeline using NNCF\n", - "- Compare performance of the FP16 and quantized models.\n", - "- Run optimized model inference on video\n", - "- Launch interactive Gradio demo\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Prerequisites](#Prerequisites)\n", - "- [Download PyTorch model](#Download-PyTorch-model)\n", - "- [Export PyTorch model to OpenVINO IR Format](#Export-PyTorch-model-to-OpenVINO-IR-Format)\n", - "- [Run OpenVINO Inference on AUTO device using Ultralytics API](#Run-OpenVINO-Inference-on-AUTO-device-using-Ultralytics-API)\n", - "- [Run OpenVINO Inference on selected device using Ultralytics API](#Run-OpenVINO-Inference-on-selected-device-using-Ultralytics-API)\n", - "- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n", - " - [Prepare Quantization Dataset](#Prepare-Quantization-Dataset)\n", - " - [Quantize and Save INT8 model](#Quantize-and-Save-INT8-model)\n", - "- [Run Optimized Model Inference](#Run-Optimized-Model-Inference)\n", - " - [Run Optimized Model on AUTO device](#Run-Optimized-Model-on-AUTO-device)\n", - " - [Run Optimized Model Inference on selected device](#Run-Optimized-Model-Inference-on-selected-device)\n", - "- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n", - " - [Model size](#Model-size)\n", - " - [Performance](#Performance)\n", - " - [FP16 model performance](#FP16-model-performance)\n", - " - [Int8 model performance](#Int8-model-performance)\n", - "- [Live demo](#Live-demo)\n", - " - [Gradio Interactive Demo](#Gradio-Interactive-Demo)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "144e86a1-c220-4e3b-a8b2-8eda443faf2d", - "source": [ - "## Prerequisites\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "bf76580b-860a-4dcd-8b01-f494cdffe37e", - "source": [ - "import os\n", - "\n", - "os.environ[\"GIT_CLONE_PROTECTION_ACTIVE\"] = \"false\"\n", - "\n", - "%pip install -q \"nncf>=2.11.0\"\n", - "%pip install -Uq \"openvino>=2024.3.0\"\n", - "%pip install -q \"git+https://github.com/THU-MIG/yolov10.git\" --extra-index-url https://download.pytorch.org/whl/cpu\n", - "%pip install -q \"torch>=2.1\" \"torchvision>=0.16\" tqdm opencv-python \"gradio>=4.19\" --extra-index-url https://download.pytorch.org/whl/cpu" - ] - }, - { - "cell_type": "code", - "id": "75772eac-565b-4234-82bf-f305e0c1ceda", - "source": [ - "from pathlib import Path\n", - "\n", - "# Fetch `notebook_utils` module\n", - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "from notebook_utils import download_file, VideoPlayer, device_widget" - ] - }, - { - "cell_type": "markdown", - "id": "2888d8b4-44f6-4418-bae4-f433ff28010b", - "source": [ - "## Download PyTorch model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "There are several version of [YOLO V10](https://github.com/THU-MIG/yolov10/tree/main?tab=readme-ov-file#performance) models provided by model authors. Each of them has different characteristics depends on number of training parameters, performance and accuracy. For demonstration purposes we will use `yolov10n`, but the same steps are also applicable to other models in YOLO V10 series." - ] - }, - { - "cell_type": "code", - "id": "aea6315b-6a29-4f2a-96a9-eb3c1646a3a4", - "source": [ - "models_dir = Path(\"./models\")\n", - "models_dir.mkdir(exist_ok=True)" - ] - }, - { - "cell_type": "code", - "id": "d828401e-b6bd-4796-a7b8-681957a159d4", - "source": [ - "model_weights_url = \"https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10n.pt\"\n", - "file_name = model_weights_url.split(\"/\")[-1]\n", - "model_name = file_name.replace(\".pt\", \"\")\n", - "\n", - "download_file(model_weights_url, directory=models_dir)" - ] - }, - { - "cell_type": "markdown", - "id": "dc21487d-dd79-4b56-b8e2-5b28c8d92ac8", - "source": [ - "## Export PyTorch model to OpenVINO IR Format\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "markdown", - "id": "8cfec0d6-fb55-4611-861b-326e892fcf09", - "source": [ - "As it was discussed before, YOLO V10 code is designed on top of [Ultralytics](https://docs.ultralytics.com/) library and has similar interface with YOLO V8 (You can check [YOLO V8 notebooks](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/yolov8-optimization) for more detailed instruction how to work with Ultralytics API). Ultralytics support OpenVINO model export using [export](https://docs.ultralytics.com/modes/export/) method of model class. Additionally, we can specify parameters responsible for target input size, static or dynamic input shapes and model precision (FP32/FP16/INT8). INT8 quantization can be additionally performed on export stage, but for making approach more flexible, we consider how to perform quantization using [NNCF](https://github.com/openvinotoolkit/nncf)." - ] - }, - { - "cell_type": "code", - "id": "d488f564-c1d5-4c6a-9b4a-353a3ab749e6", - "source": [ - "import types\n", - "from ultralytics.utils import ops, yaml_load, yaml_save\n", - "from ultralytics import YOLOv10\n", - "import torch\n", - "\n", - "detection_labels = {\n", - " 0: \"person\",\n", - " 1: \"bicycle\",\n", - " 2: \"car\",\n", - " 3: \"motorcycle\",\n", - " 4: \"airplane\",\n", - " 5: \"bus\",\n", - " 6: \"train\",\n", - " 7: \"truck\",\n", - " 8: \"boat\",\n", - " 9: \"traffic light\",\n", - " 10: \"fire hydrant\",\n", - " 11: \"stop sign\",\n", - " 12: \"parking meter\",\n", - " 13: \"bench\",\n", - " 14: \"bird\",\n", - " 15: \"cat\",\n", - " 16: \"dog\",\n", - " 17: \"horse\",\n", - " 18: \"sheep\",\n", - " 19: \"cow\",\n", - " 20: \"elephant\",\n", - " 21: \"bear\",\n", - " 22: \"zebra\",\n", - " 23: \"giraffe\",\n", - " 24: \"backpack\",\n", - " 25: \"umbrella\",\n", - " 26: \"handbag\",\n", - " 27: \"tie\",\n", - " 28: \"suitcase\",\n", - " 29: \"frisbee\",\n", - " 30: \"skis\",\n", - " 31: \"snowboard\",\n", - " 32: \"sports ball\",\n", - " 33: \"kite\",\n", - " 34: \"baseball bat\",\n", - " 35: \"baseball glove\",\n", - " 36: \"skateboard\",\n", - " 37: \"surfboard\",\n", - " 38: \"tennis racket\",\n", - " 39: \"bottle\",\n", - " 40: \"wine glass\",\n", - " 41: \"cup\",\n", - " 42: \"fork\",\n", - " 43: \"knife\",\n", - " 44: \"spoon\",\n", - " 45: \"bowl\",\n", - " 46: \"banana\",\n", - " 47: \"apple\",\n", - " 48: \"sandwich\",\n", - " 49: \"orange\",\n", - " 50: \"broccoli\",\n", - " 51: \"carrot\",\n", - " 52: \"hot dog\",\n", - " 53: \"pizza\",\n", - " 54: \"donut\",\n", - " 55: \"cake\",\n", - " 56: \"chair\",\n", - " 57: \"couch\",\n", - " 58: \"potted plant\",\n", - " 59: \"bed\",\n", - " 60: \"dining table\",\n", - " 61: \"toilet\",\n", - " 62: \"tv\",\n", - " 63: \"laptop\",\n", - " 64: \"mouse\",\n", - " 65: \"remote\",\n", - " 66: \"keyboard\",\n", - " 67: \"cell phone\",\n", - " 68: \"microwave\",\n", - " 69: \"oven\",\n", - " 70: \"toaster\",\n", - " 71: \"sink\",\n", - " 72: \"refrigerator\",\n", - " 73: \"book\",\n", - " 74: \"clock\",\n", - " 75: \"vase\",\n", - " 76: \"scissors\",\n", - " 77: \"teddy bear\",\n", - " 78: \"hair drier\",\n", - " 79: \"toothbrush\",\n", - "}\n", - "\n", - "\n", - "def v10_det_head_forward(self, x):\n", - " one2one = self.forward_feat([xi.detach() for xi in x], self.one2one_cv2, self.one2one_cv3)\n", - " if not self.export:\n", - " one2many = super().forward(x)\n", - "\n", - " if not self.training:\n", - " one2one = self.inference(one2one)\n", - " if not self.export:\n", - " return {\"one2many\": one2many, \"one2one\": one2one}\n", - " else:\n", - " assert self.max_det != -1\n", - " boxes, scores, labels = ops.v10postprocess(one2one.permute(0, 2, 1), self.max_det, self.nc)\n", - " return torch.cat(\n", - " [boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)],\n", - " dim=-1,\n", - " )\n", - " else:\n", - " return {\"one2many\": one2many, \"one2one\": one2one}\n", - "\n", - "\n", - "ov_model_path = models_dir / f\"{model_name}_openvino_model/{model_name}.xml\"\n", - "if not ov_model_path.exists():\n", - " model = YOLOv10(models_dir / file_name)\n", - " model.model.model[-1].forward = types.MethodType(v10_det_head_forward, model.model.model[-1])\n", - " model.export(format=\"openvino\", dynamic=True, half=True)\n", - " config = yaml_load(ov_model_path.parent / \"metadata.yaml\")\n", - " config[\"names\"] = detection_labels\n", - " yaml_save(ov_model_path.parent / \"metadata.yaml\", config)" - ] - }, - { - "cell_type": "markdown", - "id": "8a23f621-f6cd-4bfd-8b98-24b8c0392761", - "source": [ - "## Run OpenVINO Inference on AUTO device using Ultralytics API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "Now, when we exported model to OpenVINO, we can load it directly into YOLOv10 class, where automatic inference backend will provide easy-to-use user experience to run OpenVINO YOLOv10 model on the similar level like for original PyTorch model. The code bellow demonstrates how to run inference OpenVINO exported model with Ultralytics API on single image. [AUTO device](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/auto-device) will be used for launching model." - ] - }, - { - "cell_type": "code", - "id": "7e8ed361-bf09-4398-ba15-e6c5dda73cd3", - "source": [ - "ov_yolo_model = YOLOv10(ov_model_path.parent, task=\"detect\")" - ] - }, - { - "cell_type": "code", - "id": "8a473286-59b8-498f-8541-df84f041e119", - "source": [ - "from PIL import Image\n", - "\n", - "IMAGE_PATH = Path(\"./data/coco_bike.jpg\")\n", - "download_file(\n", - " url=\"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg\",\n", - " filename=IMAGE_PATH.name,\n", - " directory=IMAGE_PATH.parent,\n", - ")" - ] - }, - { - "cell_type": "code", - "id": "7e116e81-0ad4-4b9e-9f5c-58680fd8f0c6", - "source": [ - "res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "cell_type": "markdown", - "id": "943b9b04-4b97-4395-99d1-695465de1140", - "source": [ - "## Run OpenVINO Inference on selected device using Ultralytics API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "In this part of notebook you can select inference device for running model inference to compare results with AUTO." - ] - }, - { - "cell_type": "code", - "id": "41655bec-b006-4376-abbb-d6c8c09e89fb", - "source": [ - "device = device_widget(\"CPU\")\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "id": "f284d5fd-12ba-4c08-afce-d8131ef7ad4d", - "source": [ - "import openvino as ov\n", - "\n", - "core = ov.Core()\n", - "\n", - "ov_model = core.read_model(ov_model_path)\n", - "\n", - "# load model on selected device\n", - "if \"GPU\" in device.value or \"NPU\" in device.value:\n", - " ov_model.reshape({0: [1, 3, 640, 640]})\n", - "ov_config = {}\n", - "if \"GPU\" in device.value:\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "det_compiled_model = core.compile_model(ov_model, device.value, ov_config)" - ] - }, - { - "cell_type": "code", - "id": "12164d21-948a-4ef7-9e1c-b0d41c53cf91", - "source": [ - "ov_yolo_model.predictor.model.ov_compiled_model = det_compiled_model" - ] - }, - { - "cell_type": "code", - "id": "ecb9d396-ab1e-4398-a110-18c08b00ab14", - "source": [ - "res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)" - ] - }, - { - "cell_type": "code", - "id": "a4abf758-3a72-4ee0-afde-38076602f1c7", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "cell_type": "markdown", - "id": "992b6ac3-4248-4848-8fd6-f9c1b9fc2051", - "source": [ - "## Optimize model using NNCF Post-training Quantization API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n", - "We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv10.\n", - "\n", - "The optimization process contains the following steps:\n", - "\n", - "1. Create a Dataset for quantization.\n", - "2. Run `nncf.quantize` for getting an optimized model.\n", - "3. Serialize OpenVINO IR model, using the `openvino.save_model` function.\n", - "\n", - "Quantization is time and memory consuming process, you can skip this step using checkbox bellow:" - ] - }, - { - "cell_type": "code", - "id": "ed9d9ce5-8df9-4803-ae85-bf0744de914c", - "source": [ - "import ipywidgets as widgets\n", - "\n", - "int8_model_det_path = models_dir / \"int8\" / f\"{model_name}_openvino_model/{model_name}.xml\"\n", - "ov_yolo_int8_model = None\n", - "\n", - "to_quantize = widgets.Checkbox(\n", - " value=True,\n", - " description=\"Quantization\",\n", - " disabled=False,\n", - ")\n", - "\n", - "to_quantize" - ] - }, - { - "cell_type": "code", - "id": "52287a23-b5ef-4c34-873a-32b398d26d1a", - "source": [ - "# Fetch skip_kernel_extension module\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py\",\n", - ")\n", - "open(\"skip_kernel_extension.py\", \"w\").write(r.text)\n", - "\n", - "%load_ext skip_kernel_extension" - ] - }, - { - "cell_type": "markdown", - "id": "89ba64af-aa73-4ed9-a2f9-527bd9ae8221", - "source": [ - "### Prepare Quantization Dataset\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "For starting quantization, we need to prepare dataset. We will use validation subset from [MS COCO dataset](https://cocodataset.org/) for model quantization and Ultralytics validation data loader for preparing input data." - ] - }, - { - "cell_type": "code", - "id": "5e10435e-9cd6-4f29-a865-721d6209314d", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "from zipfile import ZipFile\n", - "\n", - "from ultralytics.data.utils import DATASETS_DIR\n", - "\n", - "if not int8_model_det_path.exists():\n", - "\n", - " DATA_URL = \"http://images.cocodataset.org/zips/val2017.zip\"\n", - " LABELS_URL = \"https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip\"\n", - " CFG_URL = \"https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml\"\n", - "\n", - " OUT_DIR = DATASETS_DIR\n", - "\n", - " DATA_PATH = OUT_DIR / \"val2017.zip\"\n", - " LABELS_PATH = OUT_DIR / \"coco2017labels-segments.zip\"\n", - " CFG_PATH = OUT_DIR / \"coco.yaml\"\n", - "\n", - " download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n", - " download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n", - " download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n", - "\n", - " if not (OUT_DIR / \"coco/labels\").exists():\n", - " with ZipFile(LABELS_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR)\n", - " with ZipFile(DATA_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR / \"coco/images\")" - ] - }, - { - "cell_type": "code", - "id": "b0a14818-21fd-4c71-8e58-2297dfafaadd", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "from ultralytics.utils import DEFAULT_CFG\n", - "from ultralytics.cfg import get_cfg\n", - "from ultralytics.data.converter import coco80_to_coco91_class\n", - "from ultralytics.data.utils import check_det_dataset\n", - "\n", - "if not int8_model_det_path.exists():\n", - " args = get_cfg(cfg=DEFAULT_CFG)\n", - " args.data = str(CFG_PATH)\n", - " det_validator = ov_yolo_model.task_map[ov_yolo_model.task][\"validator\"](args=args)\n", - "\n", - " det_validator.data = check_det_dataset(args.data)\n", - " det_validator.stride = 32\n", - " det_data_loader = det_validator.get_dataloader(OUT_DIR / \"coco\", 1)" - ] - }, - { - "cell_type": "markdown", - "id": "83fa49f4-e40c-48c7-82a6-8ef56e20b343", - "source": [ - "NNCF provides `nncf.Dataset` wrapper for using native framework dataloaders in quantization pipeline. Additionally, we specify transform function that will be responsible for preparing input data in model expected format." - ] - }, - { - "cell_type": "code", - "id": "253f0e0f-131e-43b9-b1be-639d4b3d1fe5", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "import nncf\n", - "from typing import Dict\n", - "\n", - "\n", - "def transform_fn(data_item:Dict):\n", - " \"\"\"\n", - " Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n", - " Parameters:\n", - " data_item: Dict with data item produced by DataLoader during iteration\n", - " Returns:\n", - " input_tensor: Input data for quantization\n", - " \"\"\"\n", - " input_tensor = det_validator.preprocess(data_item)['img'].numpy()\n", - " return input_tensor\n", - "\n", - "if not int8_model_det_path.exists():\n", - " quantization_dataset = nncf.Dataset(det_data_loader, transform_fn)" - ] - }, - { - "cell_type": "markdown", - "id": "ff8a6372-2097-4308-a4e5-e4651242a8df", - "source": [ - "### Quantize and Save INT8 model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n", - "Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. YOLOv10 model contains non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations.\n", - "\n", - ">**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware." - ] - }, - { - "cell_type": "code", - "id": "4c12ae38-239e-4fe1-8296-47567f98538c", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "import shutil\n", - "\n", - "if not int8_model_det_path.exists():\n", - " quantized_det_model = nncf.quantize(\n", - " ov_model,\n", - " quantization_dataset,\n", - " preset=nncf.QuantizationPreset.MIXED,\n", - " )\n", - "\n", - " ov.save_model(quantized_det_model, int8_model_det_path)\n", - " shutil.copy(ov_model_path.parent / \"metadata.yaml\", int8_model_det_path.parent / \"metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "41b8a3eb-adb8-40cf-8602-311ff8d75a76", - "source": [ - "## Run Optimized Model Inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The way of usage INT8 quantized model is the same like for model before quantization. Let's check inference result of quantized model on single image" + 'cell_type': 'markdown', + 'id': 'e9d010ff-85ba-4e69-b439-ba89e4a3a715', + 'source': [ + '# Convert and Optimize YOLOv10 with OpenVINO\n', + '\n', + 'Real-time object detection aims to accurately predict object categories and positions in images with low latency. The YOLO series has been at the forefront of this research due to its balance between performance and efficiency. However, reliance on NMS and architectural inefficiencies have hindered optimal performance. YOLOv10 addresses these issues by introducing consistent dual assignments for NMS-free training and a holistic efficiency-accuracy driven model design strategy.\n', + '\n', + 'YOLOv10, built on the [Ultralytics Python package](https://pypi.org/project/ultralytics/) by researchers at [Tsinghua University](https://www.tsinghua.edu.cn/en/), introduces a new approach to real-time object detection, addressing both the post-processing and model architecture deficiencies found in previous YOLO versions. By eliminating non-maximum suppression (NMS) and optimizing various model components, YOLOv10 achieves state-of-the-art performance with significantly reduced computational overhead. Extensive experiments demonstrate its superior accuracy-latency trade-offs across multiple model scales.\n', + '\n', + '![yolov10-approach.png](https://github.com/ultralytics/ultralytics/assets/26833433/f9b1bec0-928e-41ce-a205-e12db3c4929a)\n', + '\n', + 'More details about model architecture you can find in original [repo](https://github.com/THU-MIG/yolov10), [paper](https://arxiv.org/abs/2405.14458) and [Ultralytics documentation](https://docs.ultralytics.com/models/yolov10/).\n', + '\n', + 'This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLO V10 with OpenVINO.\n', + '\n', + 'The tutorial consists of the following steps:\n', + '\n', + '- Prepare PyTorch model\n', + '- Convert PyTorch model to OpenVINO IR\n', + '- Run model inference with OpenVINO\n', + '- Prepare and run optimization pipeline using NNCF\n', + '- Compare performance of the FP16 and quantized models.\n', + '- Run optimized model inference on video\n', + '- Launch interactive Gradio demo\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Prerequisites](#Prerequisites)\n', + '- [Download PyTorch model](#Download-PyTorch-model)\n', + '- [Export PyTorch model to OpenVINO IR Format](#Export-PyTorch-model-to-OpenVINO-IR-Format)\n', + '- [Run OpenVINO Inference on AUTO device using Ultralytics API](#Run-OpenVINO-Inference-on-AUTO-device-using-Ultralytics-API)\n', + '- [Run OpenVINO Inference on selected device using Ultralytics API](#Run-OpenVINO-Inference-on-selected-device-using-Ultralytics-API)\n', + '- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n', + ' - [Prepare Quantization Dataset](#Prepare-Quantization-Dataset)\n', + ' - [Quantize and Save INT8 model](#Quantize-and-Save-INT8-model)\n', + '- [Run Optimized Model Inference](#Run-Optimized-Model-Inference)\n', + ' - [Run Optimized Model on AUTO device](#Run-Optimized-Model-on-AUTO-device)\n', + ' - [Run Optimized Model Inference on selected device](#Run-Optimized-Model-Inference-on-selected-device)\n', + '- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n', + ' - [Model size](#Model-size)\n', + ' - [Performance](#Performance)\n', + ' - [FP16 model performance](#FP16-model-performance)\n', + ' - [Int8 model performance](#Int8-model-performance)\n', + '- [Live demo](#Live-demo)\n', + ' - [Gradio Interactive Demo](#Gradio-Interactive-Demo)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' + ] + }, + { + 'cell_type': 'markdown', + 'id': '144e86a1-c220-4e3b-a8b2-8eda443faf2d', + 'source': [ + '## Prerequisites\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': 'bf76580b-860a-4dcd-8b01-f494cdffe37e', + 'source': [ + 'import os\n', + '\n', + 'os.environ["GIT_CLONE_PROTECTION_ACTIVE"] = "false"\n', + '\n', + '%pip install -q "nncf>=2.11.0"\n', + '%pip install -Uq "openvino>=2024.3.0"\n', + '%pip install -q "git+https://github.com/THU-MIG/yolov10.git" --extra-index-url https://download.pytorch.org/whl/cpu\n', + '%pip install -q "torch>=2.1" "torchvision>=0.16" tqdm opencv-python "gradio>=4.19" --extra-index-url https://download.pytorch.org/whl/cpu' + ] + }, + { + 'cell_type': 'code', + 'id': '75772eac-565b-4234-82bf-f305e0c1ceda', + 'source': [ + 'from pathlib import Path\n', + '\n', + '# Fetch `notebook_utils` module\n', + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + '\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + '\n', + 'from notebook_utils import download_file, VideoPlayer, device_widget' + ] + }, + { + 'cell_type': 'markdown', + 'id': '2888d8b4-44f6-4418-bae4-f433ff28010b', + 'source': [ + '## Download PyTorch model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'There are several version of [YOLO V10](https://github.com/THU-MIG/yolov10/tree/main?tab=readme-ov-file#performance) models provided by model authors. Each of them has different characteristics depends on number of training parameters, performance and accuracy. For demonstration purposes we will use `yolov10n`, but the same steps are also applicable to other models in YOLO V10 series.' + ] + }, + { + 'cell_type': 'code', + 'id': 'aea6315b-6a29-4f2a-96a9-eb3c1646a3a4', + 'source': [ + 'models_dir = Path("./models")\n', + 'models_dir.mkdir(exist_ok=True)' + ] + }, + { + 'cell_type': 'code', + 'id': 'd828401e-b6bd-4796-a7b8-681957a159d4', + 'source': [ + 'model_weights_url = "https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10n.pt"\n', + 'file_name = model_weights_url.split("/")[-1]\n', + 'model_name = file_name.replace(".pt", "")\n', + '\n', + 'download_file(model_weights_url, directory=models_dir)' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'dc21487d-dd79-4b56-b8e2-5b28c8d92ac8', + 'source': [ + '## Export PyTorch model to OpenVINO IR Format\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '8cfec0d6-fb55-4611-861b-326e892fcf09', + 'source': [ + 'As it was discussed before, YOLO V10 code is designed on top of [Ultralytics](https://docs.ultralytics.com/) library and has similar interface with YOLO V8 (You can check [YOLO V8 notebooks](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/yolov8-optimization) for more detailed instruction how to work with Ultralytics API). Ultralytics support OpenVINO model export using [export](https://docs.ultralytics.com/modes/export/) method of model class. Additionally, we can specify parameters responsible for target input size, static or dynamic input shapes and model precision (FP32/FP16/INT8). INT8 quantization can be additionally performed on export stage, but for making approach more flexible, we consider how to perform quantization using [NNCF](https://github.com/openvinotoolkit/nncf).' + ] + }, + { + 'cell_type': 'code', + 'id': 'd488f564-c1d5-4c6a-9b4a-353a3ab749e6', + 'source': [ + 'import types\n', + 'from ultralytics.utils import ops, yaml_load, yaml_save\n', + 'from ultralytics import YOLOv10\n', + 'import torch\n', + '\n', + 'detection_labels = {\n', + ' 0: "person",\n', + ' 1: "bicycle",\n', + ' 2: "car",\n', + ' 3: "motorcycle",\n', + ' 4: "airplane",\n', + ' 5: "bus",\n', + ' 6: "train",\n', + ' 7: "truck",\n', + ' 8: "boat",\n', + ' 9: "traffic light",\n', + ' 10: "fire hydrant",\n', + ' 11: "stop sign",\n', + ' 12: "parking meter",\n', + ' 13: "bench",\n', + ' 14: "bird",\n', + ' 15: "cat",\n', + ' 16: "dog",\n', + ' 17: "horse",\n', + ' 18: "sheep",\n', + ' 19: "cow",\n', + ' 20: "elephant",\n', + ' 21: "bear",\n', + ' 22: "zebra",\n', + ' 23: "giraffe",\n', + ' 24: "backpack",\n', + ' 25: "umbrella",\n', + ' 26: "handbag",\n', + ' 27: "tie",\n', + ' 28: "suitcase",\n', + ' 29: "frisbee",\n', + ' 30: "skis",\n', + ' 31: "snowboard",\n', + ' 32: "sports ball",\n', + ' 33: "kite",\n', + ' 34: "baseball bat",\n', + ' 35: "baseball glove",\n', + ' 36: "skateboard",\n', + ' 37: "surfboard",\n', + ' 38: "tennis racket",\n', + ' 39: "bottle",\n', + ' 40: "wine glass",\n', + ' 41: "cup",\n', + ' 42: "fork",\n', + ' 43: "knife",\n', + ' 44: "spoon",\n', + ' 45: "bowl",\n', + ' 46: "banana",\n', + ' 47: "apple",\n', + ' 48: "sandwich",\n', + ' 49: "orange",\n', + ' 50: "broccoli",\n', + ' 51: "carrot",\n', + ' 52: "hot dog",\n', + ' 53: "pizza",\n', + ' 54: "donut",\n', + ' 55: "cake",\n', + ' 56: "chair",\n', + ' 57: "couch",\n', + ' 58: "potted plant",\n', + ' 59: "bed",\n', + ' 60: "dining table",\n', + ' 61: "toilet",\n', + ' 62: "tv",\n', + ' 63: "laptop",\n', + ' 64: "mouse",\n', + ' 65: "remote",\n', + ' 66: "keyboard",\n', + ' 67: "cell phone",\n', + ' 68: "microwave",\n', + ' 69: "oven",\n', + ' 70: "toaster",\n', + ' 71: "sink",\n', + ' 72: "refrigerator",\n', + ' 73: "book",\n', + ' 74: "clock",\n', + ' 75: "vase",\n', + ' 76: "scissors",\n', + ' 77: "teddy bear",\n', + ' 78: "hair drier",\n', + ' 79: "toothbrush",\n', + '}\n', + '\n', + '\n', + 'def v10_det_head_forward(self, x):\n', + ' one2one = self.forward_feat([xi.detach() for xi in x], self.one2one_cv2, self.one2one_cv3)\n', + ' if not self.export:\n', + ' one2many = super().forward(x)\n', + '\n', + ' if not self.training:\n', + ' one2one = self.inference(one2one)\n', + ' if not self.export:\n', + ' return {"one2many": one2many, "one2one": one2one}\n', + ' else:\n', + ' assert self.max_det != -1\n', + ' boxes, scores, labels = ops.v10postprocess(one2one.permute(0, 2, 1), self.max_det, self.nc)\n', + ' return torch.cat(\n', + ' [boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)],\n', + ' dim=-1,\n', + ' )\n', + ' else:\n', + ' return {"one2many": one2many, "one2one": one2one}\n', + '\n', + '\n', + 'ov_model_path = models_dir / f"{model_name}_openvino_model/{model_name}.xml"\n', + 'if not ov_model_path.exists():\n', + ' model = YOLOv10(models_dir / file_name)\n', + ' model.model.model[-1].forward = types.MethodType(v10_det_head_forward, model.model.model[-1])\n', + ' model.export(format="openvino", dynamic=True, half=True)\n', + ' config = yaml_load(ov_model_path.parent / "metadata.yaml")\n', + ' config["names"] = detection_labels\n', + ' yaml_save(ov_model_path.parent / "metadata.yaml", config)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '8a23f621-f6cd-4bfd-8b98-24b8c0392761', + 'source': [ + '## Run OpenVINO Inference on AUTO device using Ultralytics API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'Now, when we exported model to OpenVINO, we can load it directly into YOLOv10 class, where automatic inference backend will provide easy-to-use user experience to run OpenVINO YOLOv10 model on the similar level like for original PyTorch model. The code bellow demonstrates how to run inference OpenVINO exported model with Ultralytics API on single image. [AUTO device](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/auto-device) will be used for launching model.' + ] + }, + { + 'cell_type': 'code', + 'id': '7e8ed361-bf09-4398-ba15-e6c5dda73cd3', + 'source': [ + 'ov_yolo_model = YOLOv10(ov_model_path.parent, task="detect")' + ] + }, + { + 'cell_type': 'code', + 'id': '8a473286-59b8-498f-8541-df84f041e119', + 'source': [ + 'from PIL import Image\n', + '\n', + 'IMAGE_PATH = Path("./data/coco_bike.jpg")\n', + 'download_file(\n', + ' url="https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg",\n', + ' filename=IMAGE_PATH.name,\n', + ' directory=IMAGE_PATH.parent,\n', + ')' + ] + }, + { + 'cell_type': 'code', + 'id': '7e116e81-0ad4-4b9e-9f5c-58680fd8f0c6', + 'source': [ + 'res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'cell_type': 'markdown', + 'id': '943b9b04-4b97-4395-99d1-695465de1140', + 'source': [ + '## Run OpenVINO Inference on selected device using Ultralytics API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'In this part of notebook you can select inference device for running model inference to compare results with AUTO.' + ] + }, + { + 'cell_type': 'code', + 'id': '41655bec-b006-4376-abbb-d6c8c09e89fb', + 'source': [ + 'device = device_widget("CPU")\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'id': 'f284d5fd-12ba-4c08-afce-d8131ef7ad4d', + 'source': [ + 'import openvino as ov\n', + '\n', + 'core = ov.Core()\n', + '\n', + 'ov_model = core.read_model(ov_model_path)\n', + '\n', + '# load model on selected device\n', + 'if "GPU" in device.value or "NPU" in device.value:\n', + ' ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'ov_config = {}\n', + 'if "GPU" in device.value:\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + 'det_compiled_model = core.compile_model(ov_model, device.value, ov_config)' + ] + }, + { + 'cell_type': 'code', + 'id': '12164d21-948a-4ef7-9e1c-b0d41c53cf91', + 'source': [ + 'ov_yolo_model.predictor.model.ov_compiled_model = det_compiled_model' + ] + }, + { + 'cell_type': 'code', + 'id': 'ecb9d396-ab1e-4398-a110-18c08b00ab14', + 'source': [ + 'res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)' + ] + }, + { + 'cell_type': 'code', + 'id': 'a4abf758-3a72-4ee0-afde-38076602f1c7', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'cell_type': 'markdown', + 'id': '992b6ac3-4248-4848-8fd6-f9c1b9fc2051', + 'source': [ + '## Optimize model using NNCF Post-training Quantization API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + '[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n', + 'We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv10.\n', + '\n', + 'The optimization process contains the following steps:\n', + '\n', + '1. Create a Dataset for quantization.\n', + '2. Run `nncf.quantize` for getting an optimized model.\n', + '3. Serialize OpenVINO IR model, using the `openvino.save_model` function.\n', + '\n', + 'Quantization is time and memory consuming process, you can skip this step using checkbox bellow:' + ] + }, + { + 'cell_type': 'code', + 'id': 'ed9d9ce5-8df9-4803-ae85-bf0744de914c', + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'int8_model_det_path = models_dir / "int8" / f"{model_name}_openvino_model/{model_name}.xml"\n', + 'ov_yolo_int8_model = None\n', + '\n', + 'to_quantize = widgets.Checkbox(\n', + ' value=True,\n', + ' description="Quantization",\n', + ' disabled=False,\n', + ')\n', + '\n', + 'to_quantize' + ] + }, + { + 'cell_type': 'code', + 'id': '52287a23-b5ef-4c34-873a-32b398d26d1a', + 'source': [ + '# Fetch skip_kernel_extension module\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py",\n', + ')\n', + 'open("skip_kernel_extension.py", "w").write(r.text)\n', + '\n', + '%load_ext skip_kernel_extension' + ] + }, + { + 'cell_type': 'markdown', + 'id': '89ba64af-aa73-4ed9-a2f9-527bd9ae8221', + 'source': [ + '### Prepare Quantization Dataset\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'For starting quantization, we need to prepare dataset. We will use validation subset from [MS COCO dataset](https://cocodataset.org/) for model quantization and Ultralytics validation data loader for preparing input data.' + ] + }, + { + 'cell_type': 'code', + 'id': '5e10435e-9cd6-4f29-a865-721d6209314d', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'from zipfile import ZipFile\n', + '\n', + 'from ultralytics.data.utils import DATASETS_DIR\n', + '\n', + 'if not int8_model_det_path.exists():\n', + '\n', + ' DATA_URL = "http://images.cocodataset.org/zips/val2017.zip"\n', + ' LABELS_URL = "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip"\n', + ' CFG_URL = "https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml"\n', + '\n', + ' OUT_DIR = DATASETS_DIR\n', + '\n', + ' DATA_PATH = OUT_DIR / "val2017.zip"\n', + ' LABELS_PATH = OUT_DIR / "coco2017labels-segments.zip"\n', + ' CFG_PATH = OUT_DIR / "coco.yaml"\n', + '\n', + ' download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n', + ' download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n', + ' download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n', + '\n', + ' if not (OUT_DIR / "coco/labels").exists():\n', + ' with ZipFile(LABELS_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR)\n', + ' with ZipFile(DATA_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR / "coco/images")' + ] + }, + { + 'cell_type': 'code', + 'id': 'b0a14818-21fd-4c71-8e58-2297dfafaadd', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'from ultralytics.utils import DEFAULT_CFG\n', + 'from ultralytics.cfg import get_cfg\n', + 'from ultralytics.data.converter import coco80_to_coco91_class\n', + 'from ultralytics.data.utils import check_det_dataset\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' args = get_cfg(cfg=DEFAULT_CFG)\n', + ' args.data = str(CFG_PATH)\n', + ' det_validator = ov_yolo_model.task_map[ov_yolo_model.task]["validator"](args=args)\n', + '\n', + ' det_validator.data = check_det_dataset(args.data)\n', + ' det_validator.stride = 32\n', + ' det_data_loader = det_validator.get_dataloader(OUT_DIR / "coco", 1)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '83fa49f4-e40c-48c7-82a6-8ef56e20b343', + 'source': [ + 'NNCF provides `nncf.Dataset` wrapper for using native framework dataloaders in quantization pipeline. Additionally, we specify transform function that will be responsible for preparing input data in model expected format.' + ] + }, + { + 'cell_type': 'code', + 'id': '253f0e0f-131e-43b9-b1be-639d4b3d1fe5', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'import nncf\n', + 'from typing import Dict\n', + '\n', + '\n', + 'def transform_fn(data_item:Dict):\n', + ' """\n', + ' Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n', + ' Parameters:\n', + ' data_item: Dict with data item produced by DataLoader during iteration\n', + ' Returns:\n', + ' input_tensor: Input data for quantization\n', + ' """\n', + ` input_tensor = det_validator.preprocess(data_item)['img'].numpy()\n`, + ' return input_tensor\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' quantization_dataset = nncf.Dataset(det_data_loader, transform_fn)' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'ff8a6372-2097-4308-a4e5-e4651242a8df', + 'source': [ + '### Quantize and Save INT8 model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n', + 'Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. YOLOv10 model contains non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations.\n', + '\n', + '>**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware.' + ] + }, + { + 'cell_type': 'code', + 'id': '4c12ae38-239e-4fe1-8296-47567f98538c', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'import shutil\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' quantized_det_model = nncf.quantize(\n', + ' ov_model,\n', + ' quantization_dataset,\n', + ' preset=nncf.QuantizationPreset.MIXED,\n', + ' )\n', + '\n', + ' ov.save_model(quantized_det_model, int8_model_det_path)\n', + ' shutil.copy(ov_model_path.parent / "metadata.yaml", int8_model_det_path.parent / "metadata.yaml")' + ] + }, + { + 'cell_type': 'markdown', + 'id': '41b8a3eb-adb8-40cf-8602-311ff8d75a76', + 'source': [ + '## Run Optimized Model Inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + `The way of usage INT8 quantized model is the same like for model before quantization. Let's check inference result of quantized model on single image` ] }, { - "cell_type": "markdown", - "id": "f438df41-d614-4cca-a746-a173d92dddf5", - "source": [ - "### Run Optimized Model on AUTO device\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'f438df41-d614-4cca-a746-a173d92dddf5', + 'source': [ + '### Run Optimized Model on AUTO device\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "af61303b-16bd-4206-b926-2c28b3a859a4", - "source": [ - "%%skip not $to_quantize.value\n", - "ov_yolo_int8_model = YOLOv10(int8_model_det_path.parent, task=\"detect\")" + 'cell_type': 'code', + 'id': 'af61303b-16bd-4206-b926-2c28b3a859a4', + 'source': [ + '%%skip not $to_quantize.value\n', + 'ov_yolo_int8_model = YOLOv10(int8_model_det_path.parent, task="detect")' ] }, { - "cell_type": "code", - "id": "db13b8dd-49a1-4046-8a1a-491eee095a1b", - "source": [ - "%%skip not $to_quantize.value\n", - "res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)" + 'cell_type': 'code', + 'id': 'db13b8dd-49a1-4046-8a1a-491eee095a1b', + 'source': [ + '%%skip not $to_quantize.value\n', + 'res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)' ] }, { - "cell_type": "code", - "id": "0423ba31-f0ee-4bfc-a18a-f7b870a9683d", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" + 'cell_type': 'code', + 'id': '0423ba31-f0ee-4bfc-a18a-f7b870a9683d', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "4b4f7118-e046-4f87-b403-87053f2b0ac3", - "source": [ - "### Run Optimized Model Inference on selected device\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': '4b4f7118-e046-4f87-b403-87053f2b0ac3', + 'source': [ + '### Run Optimized Model Inference on selected device\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "142f22ae-6a8d-4578-b97f-4d9c77123418", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" + 'cell_type': 'code', + 'id': '142f22ae-6a8d-4578-b97f-4d9c77123418', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "id": "2f43767a-1994-4202-a411-4738c92223da", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "ov_config = {}\n", - "if \"GPU\" in device.value or \"NPU\" in device.value:\n", - " ov_model.reshape({0: [1, 3, 640, 640]})\n", - "ov_config = {}\n", - "if \"GPU\" in device.value:\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "\n", - "quantized_det_model = core.read_model(int8_model_det_path)\n", - "quantized_det_compiled_model = core.compile_model(quantized_det_model, device.value, ov_config)\n", - "\n", - "ov_yolo_int8_model.predictor.model.ov_compiled_model = quantized_det_compiled_model\n", - "\n", - "res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)" + 'cell_type': 'code', + 'id': '2f43767a-1994-4202-a411-4738c92223da', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'ov_config = {}\n', + 'if "GPU" in device.value or "NPU" in device.value:\n', + ' ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'ov_config = {}\n', + 'if "GPU" in device.value:\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + '\n', + 'quantized_det_model = core.read_model(int8_model_det_path)\n', + 'quantized_det_compiled_model = core.compile_model(quantized_det_model, device.value, ov_config)\n', + '\n', + 'ov_yolo_int8_model.predictor.model.ov_compiled_model = quantized_det_compiled_model\n', + '\n', + 'res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)' ] }, { - "cell_type": "code", - "id": "51d64c7b-7595-41ca-87dd-4c85308f84d6", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" + 'cell_type': 'code', + 'id': '51d64c7b-7595-41ca-87dd-4c85308f84d6', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "c7b70f75-a706-4f24-a487-88a3ad645dd5", - "source": [ - "## Compare the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'c7b70f75-a706-4f24-a487-88a3ad645dd5', + 'source': [ + '## Compare the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "markdown", - "id": "d6561246-a9e0-4cdf-8207-7e2f919d8582", - "source": [ - "### Model size\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'd6561246-a9e0-4cdf-8207-7e2f919d8582', + 'source': [ + '### Model size\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "43d84d15-75be-4430-a58b-61cfd8e08dd0", - "source": [ - "ov_model_weights = ov_model_path.with_suffix(\".bin\")\n", - "print(f\"Size of FP16 model is {ov_model_weights.stat().st_size / 1024 / 1024:.2f} MB\")\n", - "if int8_model_det_path.exists():\n", - " ov_int8_weights = int8_model_det_path.with_suffix(\".bin\")\n", - " print(f\"Size of model with INT8 compressed weights is {ov_int8_weights.stat().st_size / 1024 / 1024:.2f} MB\")\n", - " print(f\"Compression rate for INT8 model: {ov_model_weights.stat().st_size / ov_int8_weights.stat().st_size:.3f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "f72b3d84-91f3-493c-8f4e-46598ffbd6d1", - "source": [ - "### Performance\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "### FP16 model performance\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "909ea508-e007-4313-ab0f-bfa0e3906176", - "source": [ - "!benchmark_app -m $ov_model_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "markdown", - "id": "9852a8ef-4c45-43dd-a9ef-53ca6f13c99e", - "source": [ - "### Int8 model performance\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "8254626d-6c4f-4ca3-936f-a7c47e402e03", - "source": [ - "if int8_model_det_path.exists():\n", - " !benchmark_app -m $int8_model_det_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "markdown", - "id": "12d35bc1-8101-45ea-8bd7-53720b98f5e5", - "source": [ - "## Live demo\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The following code runs model inference on a video:" - ] - }, - { - "cell_type": "code", - "id": "246bd379-b1d5-4eb2-928f-97c7c2455a92", - "source": [ - "import collections\n", - "import time\n", - "from IPython import display\n", - "import cv2\n", - "import numpy as np\n", - "\n", - "\n", - "# Main processing function to run object detection.\n", - "def run_object_detection(\n", - " source=0,\n", - " flip=False,\n", - " use_popup=False,\n", - " skip_first_frames=0,\n", - " det_model=ov_yolo_int8_model,\n", - " device=device.value,\n", - "):\n", - " player = None\n", - " try:\n", - " # Create a video player to play with target fps.\n", - " player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n", - " # Start capturing.\n", - " player.start()\n", - " if use_popup:\n", - " title = \"Press ESC to Exit\"\n", - " cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n", - "\n", - " processing_times = collections.deque()\n", - " while True:\n", - " # Grab the frame.\n", - " frame = player.next()\n", - " if frame is None:\n", - " print(\"Source ended\")\n", - " break\n", - " # If the frame is larger than full HD, reduce size to improve the performance.\n", - " scale = 1280 / max(frame.shape)\n", - " if scale < 1:\n", - " frame = cv2.resize(\n", - " src=frame,\n", - " dsize=None,\n", - " fx=scale,\n", - " fy=scale,\n", - " interpolation=cv2.INTER_AREA,\n", - " )\n", - " # Get the results.\n", - " input_image = np.array(frame)\n", - "\n", - " start_time = time.time()\n", - " detections = det_model(input_image, iou=0.45, conf=0.2, verbose=False)\n", - " stop_time = time.time()\n", - " frame = detections[0].plot()\n", - "\n", - " processing_times.append(stop_time - start_time)\n", - " # Use processing times from last 200 frames.\n", - " if len(processing_times) > 200:\n", - " processing_times.popleft()\n", - "\n", - " _, f_width = frame.shape[:2]\n", - " # Mean processing time [ms].\n", - " processing_time = np.mean(processing_times) * 1000\n", - " fps = 1000 / processing_time\n", - " cv2.putText(\n", - " img=frame,\n", - " text=f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n", - " org=(20, 40),\n", - " fontFace=cv2.FONT_HERSHEY_COMPLEX,\n", - " fontScale=f_width / 1000,\n", - " color=(0, 0, 255),\n", - " thickness=1,\n", - " lineType=cv2.LINE_AA,\n", - " )\n", - " # Use this workaround if there is flickering.\n", - " if use_popup:\n", - " cv2.imshow(winname=title, mat=frame)\n", - " key = cv2.waitKey(1)\n", - " # escape = 27\n", - " if key == 27:\n", - " break\n", - " else:\n", - " # Encode numpy array to jpg.\n", - " _, encoded_img = cv2.imencode(ext=\".jpg\", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n", - " # Create an IPython image.\n", - " i = display.Image(data=encoded_img)\n", - " # Display the image in this notebook.\n", - " display.clear_output(wait=True)\n", - " display.display(i)\n", - " # ctrl-c\n", - " except KeyboardInterrupt:\n", - " print(\"Interrupted\")\n", - " # any different error\n", - " except RuntimeError as e:\n", - " print(e)\n", - " finally:\n", - " if player is not None:\n", - " # Stop capturing.\n", - " player.stop()\n", - " if use_popup:\n", - " cv2.destroyAllWindows()" - ] - }, - { - "cell_type": "code", - "id": "8db930f4-fc41-4635-9248-deab2b6cfcb8", - "source": [ - "use_int8 = widgets.Checkbox(\n", - " value=ov_yolo_int8_model is not None,\n", - " description=\"Use int8 model\",\n", - " disabled=ov_yolo_int8_model is None,\n", - ")\n", - "\n", - "use_int8" - ] - }, - { - "cell_type": "code", - "id": "59e37135-1bdc-40ff-8789-b5b4491cab84", - "source": [ - "WEBCAM_INFERENCE = False\n", - "\n", - "if WEBCAM_INFERENCE:\n", - " VIDEO_SOURCE = 0 # Webcam\n", - "else:\n", - " download_file(\n", - " \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4\",\n", - " directory=\"data\",\n", - " )\n", - " VIDEO_SOURCE = \"data/people.mp4\"" - ] - }, - { - "cell_type": "code", - "id": "88c6f47c-1c47-451d-89ba-0a29760f5ec4", - "source": [ - "run_object_detection(\n", - " det_model=ov_yolo_model if not use_int8.value else ov_yolo_int8_model,\n", - " source=VIDEO_SOURCE,\n", - " flip=True,\n", - " use_popup=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "9f612d80-68e2-45f8-aa62-9f3a05ca9de8", - "source": [ - "### Gradio Interactive Demo\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "ffdbe248-7086-4804-b4b9-c3dd454bf80c", - "source": [ - "import gradio as gr\n", - "\n", - "\n", - "def yolov10_inference(image, int8, conf_threshold, iou_threshold):\n", - " model = ov_yolo_model if not int8 else ov_yolo_int8_model\n", - " results = model(source=image, iou=iou_threshold, conf=conf_threshold, verbose=False)[0]\n", - " annotated_image = Image.fromarray(results.plot())\n", - "\n", - " return annotated_image\n", - "\n", - "\n", - "with gr.Blocks() as demo:\n", - " gr.HTML(\n", - " \"\"\"\n", - "

    \n", - " YOLOv10: Real-Time End-to-End Object Detection using OpenVINO\n", - "

    \n", - " \"\"\"\n", - " )\n", - " with gr.Row():\n", - " with gr.Column():\n", - " image = gr.Image(type=\"numpy\", label=\"Image\")\n", - " conf_threshold = gr.Slider(\n", - " label=\"Confidence Threshold\",\n", - " minimum=0.1,\n", - " maximum=1.0,\n", - " step=0.1,\n", - " value=0.2,\n", - " )\n", - " iou_threshold = gr.Slider(\n", - " label=\"IoU Threshold\",\n", - " minimum=0.1,\n", - " maximum=1.0,\n", - " step=0.1,\n", - " value=0.45,\n", - " )\n", - " use_int8 = gr.Checkbox(\n", - " value=ov_yolo_int8_model is not None,\n", - " visible=ov_yolo_int8_model is not None,\n", - " label=\"Use INT8 model\",\n", - " )\n", - " yolov10_infer = gr.Button(value=\"Detect Objects\")\n", - "\n", - " with gr.Column():\n", - " output_image = gr.Image(type=\"pil\", label=\"Annotated Image\")\n", - "\n", - " yolov10_infer.click(\n", - " fn=yolov10_inference,\n", - " inputs=[\n", - " image,\n", - " use_int8,\n", - " conf_threshold,\n", - " iou_threshold,\n", - " ],\n", - " outputs=[output_image],\n", - " )\n", - " examples = gr.Examples(\n", - " [\n", - " \"data/coco_bike.jpg\",\n", - " ],\n", - " inputs=[\n", - " image,\n", - " ],\n", - " )\n", - "\n", - "\n", - "try:\n", - " demo.launch(debug=True)\n", - "except Exception:\n", - " demo.launch(debug=True, share=True)" + 'cell_type': 'code', + 'id': '43d84d15-75be-4430-a58b-61cfd8e08dd0', + 'source': [ + 'ov_model_weights = ov_model_path.with_suffix(".bin")\n', + 'print(f"Size of FP16 model is {ov_model_weights.stat().st_size / 1024 / 1024:.2f} MB")\n', + 'if int8_model_det_path.exists():\n', + ' ov_int8_weights = int8_model_det_path.with_suffix(".bin")\n', + ' print(f"Size of model with INT8 compressed weights is {ov_int8_weights.stat().st_size / 1024 / 1024:.2f} MB")\n', + ' print(f"Compression rate for INT8 model: {ov_model_weights.stat().st_size / ov_int8_weights.stat().st_size:.3f}")' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'f72b3d84-91f3-493c-8f4e-46598ffbd6d1', + 'source': [ + '### Performance\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + '### FP16 model performance\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': '909ea508-e007-4313-ab0f-bfa0e3906176', + 'source': [ + '!benchmark_app -m $ov_model_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'markdown', + 'id': '9852a8ef-4c45-43dd-a9ef-53ca6f13c99e', + 'source': [ + '### Int8 model performance\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': '8254626d-6c4f-4ca3-936f-a7c47e402e03', + 'source': [ + 'if int8_model_det_path.exists():\n', + ' !benchmark_app -m $int8_model_det_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'markdown', + 'id': '12d35bc1-8101-45ea-8bd7-53720b98f5e5', + 'source': [ + '## Live demo\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'The following code runs model inference on a video:' + ] + }, + { + 'cell_type': 'code', + 'id': '246bd379-b1d5-4eb2-928f-97c7c2455a92', + 'source': [ + 'import collections\n', + 'import time\n', + 'from IPython import display\n', + 'import cv2\n', + 'import numpy as np\n', + '\n', + '\n', + '# Main processing function to run object detection.\n', + 'def run_object_detection(\n', + ' source=0,\n', + ' flip=False,\n', + ' use_popup=False,\n', + ' skip_first_frames=0,\n', + ' det_model=ov_yolo_int8_model,\n', + ' device=device.value,\n', + '):\n', + ' player = None\n', + ' try:\n', + ' # Create a video player to play with target fps.\n', + ' player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n', + ' # Start capturing.\n', + ' player.start()\n', + ' if use_popup:\n', + ' title = "Press ESC to Exit"\n', + ' cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n', + '\n', + ' processing_times = collections.deque()\n', + ' while True:\n', + ' # Grab the frame.\n', + ' frame = player.next()\n', + ' if frame is None:\n', + ' print("Source ended")\n', + ' break\n', + ' # If the frame is larger than full HD, reduce size to improve the performance.\n', + ' scale = 1280 / max(frame.shape)\n', + ' if scale < 1:\n', + ' frame = cv2.resize(\n', + ' src=frame,\n', + ' dsize=None,\n', + ' fx=scale,\n', + ' fy=scale,\n', + ' interpolation=cv2.INTER_AREA,\n', + ' )\n', + ' # Get the results.\n', + ' input_image = np.array(frame)\n', + '\n', + ' start_time = time.time()\n', + ' detections = det_model(input_image, iou=0.45, conf=0.2, verbose=False)\n', + ' stop_time = time.time()\n', + ' frame = detections[0].plot()\n', + '\n', + ' processing_times.append(stop_time - start_time)\n', + ' # Use processing times from last 200 frames.\n', + ' if len(processing_times) > 200:\n', + ' processing_times.popleft()\n', + '\n', + ' _, f_width = frame.shape[:2]\n', + ' # Mean processing time [ms].\n', + ' processing_time = np.mean(processing_times) * 1000\n', + ' fps = 1000 / processing_time\n', + ' cv2.putText(\n', + ' img=frame,\n', + ' text=f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n', + ' org=(20, 40),\n', + ' fontFace=cv2.FONT_HERSHEY_COMPLEX,\n', + ' fontScale=f_width / 1000,\n', + ' color=(0, 0, 255),\n', + ' thickness=1,\n', + ' lineType=cv2.LINE_AA,\n', + ' )\n', + ' # Use this workaround if there is flickering.\n', + ' if use_popup:\n', + ' cv2.imshow(winname=title, mat=frame)\n', + ' key = cv2.waitKey(1)\n', + ' # escape = 27\n', + ' if key == 27:\n', + ' break\n', + ' else:\n', + ' # Encode numpy array to jpg.\n', + ' _, encoded_img = cv2.imencode(ext=".jpg", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n', + ' # Create an IPython image.\n', + ' i = display.Image(data=encoded_img)\n', + ' # Display the image in this notebook.\n', + ' display.clear_output(wait=True)\n', + ' display.display(i)\n', + ' # ctrl-c\n', + ' except KeyboardInterrupt:\n', + ' print("Interrupted")\n', + ' # any different error\n', + ' except RuntimeError as e:\n', + ' print(e)\n', + ' finally:\n', + ' if player is not None:\n', + ' # Stop capturing.\n', + ' player.stop()\n', + ' if use_popup:\n', + ' cv2.destroyAllWindows()' + ] + }, + { + 'cell_type': 'code', + 'id': '8db930f4-fc41-4635-9248-deab2b6cfcb8', + 'source': [ + 'use_int8 = widgets.Checkbox(\n', + ' value=ov_yolo_int8_model is not None,\n', + ' description="Use int8 model",\n', + ' disabled=ov_yolo_int8_model is None,\n', + ')\n', + '\n', + 'use_int8' + ] + }, + { + 'cell_type': 'code', + 'id': '59e37135-1bdc-40ff-8789-b5b4491cab84', + 'source': [ + 'WEBCAM_INFERENCE = False\n', + '\n', + 'if WEBCAM_INFERENCE:\n', + ' VIDEO_SOURCE = 0 # Webcam\n', + 'else:\n', + ' download_file(\n', + ' "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4",\n', + ' directory="data",\n', + ' )\n', + ' VIDEO_SOURCE = "data/people.mp4"' + ] + }, + { + 'cell_type': 'code', + 'id': '88c6f47c-1c47-451d-89ba-0a29760f5ec4', + 'source': [ + 'run_object_detection(\n', + ' det_model=ov_yolo_model if not use_int8.value else ov_yolo_int8_model,\n', + ' source=VIDEO_SOURCE,\n', + ' flip=True,\n', + ' use_popup=False,\n', + ')' + ] + }, + { + 'cell_type': 'markdown', + 'id': '9f612d80-68e2-45f8-aa62-9f3a05ca9de8', + 'source': [ + '### Gradio Interactive Demo\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': 'ffdbe248-7086-4804-b4b9-c3dd454bf80c', + 'source': [ + 'import gradio as gr\n', + '\n', + '\n', + 'def yolov10_inference(image, int8, conf_threshold, iou_threshold):\n', + ' model = ov_yolo_model if not int8 else ov_yolo_int8_model\n', + ' results = model(source=image, iou=iou_threshold, conf=conf_threshold, verbose=False)[0]\n', + ' annotated_image = Image.fromarray(results.plot())\n', + '\n', + ' return annotated_image\n', + '\n', + '\n', + 'with gr.Blocks() as demo:\n', + ' gr.HTML(\n', + ' """\n', + `

    \n`, + ' YOLOv10: Real-Time End-to-End Object Detection using OpenVINO\n', + '

    \n', + ' """\n', + ' )\n', + ' with gr.Row():\n', + ' with gr.Column():\n', + ' image = gr.Image(type="numpy", label="Image")\n', + ' conf_threshold = gr.Slider(\n', + ' label="Confidence Threshold",\n', + ' minimum=0.1,\n', + ' maximum=1.0,\n', + ' step=0.1,\n', + ' value=0.2,\n', + ' )\n', + ' iou_threshold = gr.Slider(\n', + ' label="IoU Threshold",\n', + ' minimum=0.1,\n', + ' maximum=1.0,\n', + ' step=0.1,\n', + ' value=0.45,\n', + ' )\n', + ' use_int8 = gr.Checkbox(\n', + ' value=ov_yolo_int8_model is not None,\n', + ' visible=ov_yolo_int8_model is not None,\n', + ' label="Use INT8 model",\n', + ' )\n', + ' yolov10_infer = gr.Button(value="Detect Objects")\n', + '\n', + ' with gr.Column():\n', + ' output_image = gr.Image(type="pil", label="Annotated Image")\n', + '\n', + ' yolov10_infer.click(\n', + ' fn=yolov10_inference,\n', + ' inputs=[\n', + ' image,\n', + ' use_int8,\n', + ' conf_threshold,\n', + ' iou_threshold,\n', + ' ],\n', + ' outputs=[output_image],\n', + ' )\n', + ' examples = gr.Examples(\n', + ' [\n', + ' "data/coco_bike.jpg",\n', + ' ],\n', + ' inputs=[\n', + ' image,\n', + ' ],\n', + ' )\n', + '\n', + '\n', + 'try:\n', + ' demo.launch(debug=True)\n', + 'except Exception:\n', + ' demo.launch(debug=True, share=True)' ] } ] @@ -11181,904 +11181,904 @@ suite('NotebookDiff Diff Service', () => { , [ { - "cell_type": "markdown", - "id": "e9d010ff-85ba-4e69-b439-ba89e4a3a715", - "source": [ - "# Convert and Optimize YOLOv10 with OpenVINO\n", - "\n", - "Real-time object detection aims to accurately predict object categories and positions in images with low latency. The YOLO series has been at the forefront of this research due to its balance between performance and efficiency. However, reliance on NMS and architectural inefficiencies have hindered optimal performance. YOLOv10 addresses these issues by introducing consistent dual assignments for NMS-free training and a holistic efficiency-accuracy driven model design strategy.\n", - "\n", - "YOLOv10, built on the [Ultralytics Python package](https://pypi.org/project/ultralytics/) by researchers at [Tsinghua University](https://www.tsinghua.edu.cn/en/), introduces a new approach to real-time object detection, addressing both the post-processing and model architecture deficiencies found in previous YOLO versions. By eliminating non-maximum suppression (NMS) and optimizing various model components, YOLOv10 achieves state-of-the-art performance with significantly reduced computational overhead. Extensive experiments demonstrate its superior accuracy-latency trade-offs across multiple model scales.\n", - "\n", - "![yolov10-approach.png](https://github.com/ultralytics/ultralytics/assets/26833433/f9b1bec0-928e-41ce-a205-e12db3c4929a)\n", - "\n", - "More details about model architecture you can find in original [repo](https://github.com/THU-MIG/yolov10), [paper](https://arxiv.org/abs/2405.14458) and [Ultralytics documentation](https://docs.ultralytics.com/models/yolov10/).\n", - "\n", - "This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLO V10 with OpenVINO.\n", - "\n", - "The tutorial consists of the following steps:\n", - "\n", - "- Prepare PyTorch model\n", - "- Convert PyTorch model to OpenVINO IR\n", - "- Run model inference with OpenVINO\n", - "- Prepare and run optimization pipeline using NNCF\n", - "- Compare performance of the FP16 and quantized models.\n", - "- Run optimized model inference on video\n", - "- Launch interactive Gradio demo\n", - "\n", - "#### Table of contents:\n", - "\n", - "- [Prerequisites](#Prerequisites)\n", - "- [Download PyTorch model](#Download-PyTorch-model)\n", - "- [Export PyTorch model to OpenVINO IR Format](#Export-PyTorch-model-to-OpenVINO-IR-Format)\n", - "- [Run OpenVINO Inference on AUTO device using Ultralytics API](#Run-OpenVINO-Inference-on-AUTO-device-using-Ultralytics-API)\n", - "- [Run OpenVINO Inference on selected device using Ultralytics API](#Run-OpenVINO-Inference-on-selected-device-using-Ultralytics-API)\n", - "- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n", - " - [Prepare Quantization Dataset](#Prepare-Quantization-Dataset)\n", - " - [Quantize and Save INT8 model](#Quantize-and-Save-INT8-model)\n", - "- [Run Optimized Model Inference](#Run-Optimized-Model-Inference)\n", - " - [Run Optimized Model on AUTO device](#Run-Optimized-Model-on-AUTO-device)\n", - " - [Run Optimized Model Inference on selected device](#Run-Optimized-Model-Inference-on-selected-device)\n", - "- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n", - " - [Model size](#Model-size)\n", - " - [Performance](#Performance)\n", - " - [FP16 model performance](#FP16-model-performance)\n", - " - [Int8 model performance](#Int8-model-performance)\n", - "- [Live demo](#Live-demo)\n", - " - [Gradio Interactive Demo](#Gradio-Interactive-Demo)\n", - "\n", - "\n", - "### Installation Instructions\n", - "\n", - "This is a self-contained example that relies solely on its own code.\n", - "\n", - "We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n", - "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "144e86a1-c220-4e3b-a8b2-8eda443faf2d", - "source": [ - "## Prerequisites\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "bf76580b-860a-4dcd-8b01-f494cdffe37e", - "source": [ - "import os\n", - "\n", - "os.environ[\"GIT_CLONE_PROTECTION_ACTIVE\"] = \"false\"\n", - "\n", - "%pip install -q \"nncf>=2.11.0\"\n", - "%pip install -Uq \"openvino>=2024.3.0\"\n", - "%pip install -q \"git+https://github.com/THU-MIG/yolov10.git\" --extra-index-url https://download.pytorch.org/whl/cpu\n", - "%pip install -q \"torch>=2.1\" \"torchvision>=0.16\" tqdm opencv-python \"gradio>=4.19\" --extra-index-url https://download.pytorch.org/whl/cpu" - ] - }, - { - "cell_type": "code", - "id": "75772eac-565b-4234-82bf-f305e0c1ceda", - "source": [ - "from pathlib import Path\n", - "\n", - "# Fetch `notebook_utils` module\n", - "import requests\n", - "\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n", - ")\n", - "\n", - "open(\"notebook_utils.py\", \"w\").write(r.text)\n", - "\n", - "from notebook_utils import download_file, VideoPlayer, device_widget" - ] - }, - { - "cell_type": "markdown", - "id": "2888d8b4-44f6-4418-bae4-f433ff28010b", - "source": [ - "## Download PyTorch model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "There are several version of [YOLO V10](https://github.com/THU-MIG/yolov10/tree/main?tab=readme-ov-file#performance) models provided by model authors. Each of them has different characteristics depends on number of training parameters, performance and accuracy. For demonstration purposes we will use `yolov10n`, but the same steps are also applicable to other models in YOLO V10 series." - ] - }, - { - "cell_type": "code", - "id": "aea6315b-6a29-4f2a-96a9-eb3c1646a3a4", - "source": [ - "models_dir = Path(\"./models\")\n", - "models_dir.mkdir(exist_ok=True)" - ] - }, - { - "cell_type": "code", - "id": "d828401e-b6bd-4796-a7b8-681957a159d4", - "source": [ - "model_weights_url = \"https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10n.pt\"\n", - "file_name = model_weights_url.split(\"/\")[-1]\n", - "model_name = file_name.replace(\".pt\", \"\")\n", - "\n", - "download_file(model_weights_url, directory=models_dir)" - ] - }, - { - "cell_type": "markdown", - "id": "dc21487d-dd79-4b56-b8e2-5b28c8d92ac8", - "source": [ - "## Export PyTorch model to OpenVINO IR Format\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "markdown", - "id": "8cfec0d6-fb55-4611-861b-326e892fcf09", - "source": [ - "As it was discussed before, YOLO V10 code is designed on top of [Ultralytics](https://docs.ultralytics.com/) library and has similar interface with YOLO V8 (You can check [YOLO V8 notebooks](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/yolov8-optimization) for more detailed instruction how to work with Ultralytics API). Ultralytics support OpenVINO model export using [export](https://docs.ultralytics.com/modes/export/) method of model class. Additionally, we can specify parameters responsible for target input size, static or dynamic input shapes and model precision (FP32/FP16/INT8). INT8 quantization can be additionally performed on export stage, but for making approach more flexible, we consider how to perform quantization using [NNCF](https://github.com/openvinotoolkit/nncf)." - ] - }, - { - "cell_type": "code", - "id": "d488f564-c1d5-4c6a-9b4a-353a3ab749e6", - "source": [ - "import types\n", - "from ultralytics.utils import ops, yaml_load, yaml_save\n", - "from ultralytics import YOLOv10\n", - "import torch\n", - "\n", - "detection_labels = {\n", - " 0: \"person\",\n", - " 1: \"bicycle\",\n", - " 2: \"car\",\n", - " 3: \"motorcycle\",\n", - " 4: \"airplane\",\n", - " 5: \"bus\",\n", - " 6: \"train\",\n", - " 7: \"truck\",\n", - " 8: \"boat\",\n", - " 9: \"traffic light\",\n", - " 10: \"fire hydrant\",\n", - " 11: \"stop sign\",\n", - " 12: \"parking meter\",\n", - " 13: \"bench\",\n", - " 14: \"bird\",\n", - " 15: \"cat\",\n", - " 16: \"dog\",\n", - " 17: \"horse\",\n", - " 18: \"sheep\",\n", - " 19: \"cow\",\n", - " 20: \"elephant\",\n", - " 21: \"bear\",\n", - " 22: \"zebra\",\n", - " 23: \"giraffe\",\n", - " 24: \"backpack\",\n", - " 25: \"umbrella\",\n", - " 26: \"handbag\",\n", - " 27: \"tie\",\n", - " 28: \"suitcase\",\n", - " 29: \"frisbee\",\n", - " 30: \"skis\",\n", - " 31: \"snowboard\",\n", - " 32: \"sports ball\",\n", - " 33: \"kite\",\n", - " 34: \"baseball bat\",\n", - " 35: \"baseball glove\",\n", - " 36: \"skateboard\",\n", - " 37: \"surfboard\",\n", - " 38: \"tennis racket\",\n", - " 39: \"bottle\",\n", - " 40: \"wine glass\",\n", - " 41: \"cup\",\n", - " 42: \"fork\",\n", - " 43: \"knife\",\n", - " 44: \"spoon\",\n", - " 45: \"bowl\",\n", - " 46: \"banana\",\n", - " 47: \"apple\",\n", - " 48: \"sandwich\",\n", - " 49: \"orange\",\n", - " 50: \"broccoli\",\n", - " 51: \"carrot\",\n", - " 52: \"hot dog\",\n", - " 53: \"pizza\",\n", - " 54: \"donut\",\n", - " 55: \"cake\",\n", - " 56: \"chair\",\n", - " 57: \"couch\",\n", - " 58: \"potted plant\",\n", - " 59: \"bed\",\n", - " 60: \"dining table\",\n", - " 61: \"toilet\",\n", - " 62: \"tv\",\n", - " 63: \"laptop\",\n", - " 64: \"mouse\",\n", - " 65: \"remote\",\n", - " 66: \"keyboard\",\n", - " 67: \"cell phone\",\n", - " 68: \"microwave\",\n", - " 69: \"oven\",\n", - " 70: \"toaster\",\n", - " 71: \"sink\",\n", - " 72: \"refrigerator\",\n", - " 73: \"book\",\n", - " 74: \"clock\",\n", - " 75: \"vase\",\n", - " 76: \"scissors\",\n", - " 77: \"teddy bear\",\n", - " 78: \"hair drier\",\n", - " 79: \"toothbrush\",\n", - "}\n", - "\n", - "\n", - "def v10_det_head_forward(self, x):\n", - " one2one = self.forward_feat([xi.detach() for xi in x], self.one2one_cv2, self.one2one_cv3)\n", - " if not self.export:\n", - " one2many = super().forward(x)\n", - "\n", - " if not self.training:\n", - " one2one = self.inference(one2one)\n", - " if not self.export:\n", - " return {\"one2many\": one2many, \"one2one\": one2one}\n", - " else:\n", - " assert self.max_det != -1\n", - " boxes, scores, labels = ops.v10postprocess(one2one.permute(0, 2, 1), self.max_det, self.nc)\n", - " return torch.cat(\n", - " [boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)],\n", - " dim=-1,\n", - " )\n", - " else:\n", - " return {\"one2many\": one2many, \"one2one\": one2one}\n", - "\n", - "\n", - "ov_model_path = models_dir / f\"{model_name}_openvino_model/{model_name}.xml\"\n", - "if not ov_model_path.exists():\n", - " model = YOLOv10(models_dir / file_name)\n", - " model.model.model[-1].forward = types.MethodType(v10_det_head_forward, model.model.model[-1])\n", - " model.export(format=\"openvino\", dynamic=True, half=True)\n", - " config = yaml_load(ov_model_path.parent / \"metadata.yaml\")\n", - " config[\"names\"] = detection_labels\n", - " yaml_save(ov_model_path.parent / \"metadata.yaml\", config)" - ] - }, - { - "cell_type": "markdown", - "id": "8a23f621-f6cd-4bfd-8b98-24b8c0392761", - "source": [ - "## Run OpenVINO Inference on AUTO device using Ultralytics API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "Now, when we exported model to OpenVINO, we can load it directly into YOLOv10 class, where automatic inference backend will provide easy-to-use user experience to run OpenVINO YOLOv10 model on the similar level like for original PyTorch model. The code bellow demonstrates how to run inference OpenVINO exported model with Ultralytics API on single image. [AUTO device](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/auto-device) will be used for launching model." - ] - }, - { - "cell_type": "code", - "id": "7e8ed361-bf09-4398-ba15-e6c5dda73cd3", - "source": [ - "ov_yolo_model = YOLOv10(ov_model_path.parent, task=\"detect\")" - ] - }, - { - "cell_type": "code", - "id": "8a473286-59b8-498f-8541-df84f041e119", - "source": [ - "from PIL import Image\n", - "\n", - "IMAGE_PATH = Path(\"./data/coco_bike.jpg\")\n", - "download_file(\n", - " url=\"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg\",\n", - " filename=IMAGE_PATH.name,\n", - " directory=IMAGE_PATH.parent,\n", - ")" - ] - }, - { - "cell_type": "code", - "id": "7e116e81-0ad4-4b9e-9f5c-58680fd8f0c6", - "source": [ - "res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)\n", - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "cell_type": "markdown", - "id": "943b9b04-4b97-4395-99d1-695465de1140", - "source": [ - "## Run OpenVINO Inference on selected device using Ultralytics API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "In this part of notebook you can select inference device for running model inference to compare results with AUTO." - ] - }, - { - "cell_type": "code", - "id": "41655bec-b006-4376-abbb-d6c8c09e89fb", - "source": [ - "device = device_widget(\"CPU\")\n", - "\n", - "device" - ] - }, - { - "cell_type": "code", - "id": "f284d5fd-12ba-4c08-afce-d8131ef7ad4d", - "source": [ - "import openvino as ov\n", - "\n", - "core = ov.Core()\n", - "\n", - "ov_model = core.read_model(ov_model_path)\n", - "\n", - "# load model on selected device\n", - "if \"GPU\" in device.value or \"NPU\" in device.value:\n", - " ov_model.reshape({0: [1, 3, 640, 640]})\n", - "ov_config = {}\n", - "if \"GPU\" in device.value:\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "det_compiled_model = core.compile_model(ov_model, device.value, ov_config)" - ] - }, - { - "cell_type": "code", - "id": "12164d21-948a-4ef7-9e1c-b0d41c53cf91", - "source": [ - "ov_yolo_model.predictor.model.ov_compiled_model = det_compiled_model" - ] - }, - { - "cell_type": "code", - "id": "ecb9d396-ab1e-4398-a110-18c08b00ab14", - "source": [ - "res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)" - ] - }, - { - "cell_type": "code", - "id": "a4abf758-3a72-4ee0-afde-38076602f1c7", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" - ] - }, - { - "cell_type": "markdown", - "id": "992b6ac3-4248-4848-8fd6-f9c1b9fc2051", - "source": [ - "## Optimize model using NNCF Post-training Quantization API\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n", - "We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv10.\n", - "\n", - "The optimization process contains the following steps:\n", - "\n", - "1. Create a Dataset for quantization.\n", - "2. Run `nncf.quantize` for getting an optimized model.\n", - "3. Serialize OpenVINO IR model, using the `openvino.save_model` function.\n", - "\n", - "Quantization is time and memory consuming process, you can skip this step using checkbox bellow:" - ] - }, - { - "cell_type": "code", - "id": "ed9d9ce5-8df9-4803-ae85-bf0744de914c", - "source": [ - "import ipywidgets as widgets\n", - "\n", - "int8_model_det_path = models_dir / \"int8\" / f\"{model_name}_openvino_model/{model_name}.xml\"\n", - "ov_yolo_int8_model = None\n", - "\n", - "to_quantize = widgets.Checkbox(\n", - " value=True,\n", - " description=\"Quantization\",\n", - " disabled=False,\n", - ")\n", - "\n", - "to_quantize" - ] - }, - { - "cell_type": "code", - "id": "52287a23-b5ef-4c34-873a-32b398d26d1a", - "source": [ - "# Fetch skip_kernel_extension module\n", - "r = requests.get(\n", - " url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py\",\n", - ")\n", - "open(\"skip_kernel_extension.py\", \"w\").write(r.text)\n", - "\n", - "%load_ext skip_kernel_extension" - ] - }, - { - "cell_type": "markdown", - "id": "89ba64af-aa73-4ed9-a2f9-527bd9ae8221", - "source": [ - "### Prepare Quantization Dataset\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "For starting quantization, we need to prepare dataset. We will use validation subset from [MS COCO dataset](https://cocodataset.org/) for model quantization and Ultralytics validation data loader for preparing input data." - ] - }, - { - "cell_type": "code", - "id": "5e10435e-9cd6-4f29-a865-721d6209314d", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "from zipfile import ZipFile\n", - "\n", - "from ultralytics.data.utils import DATASETS_DIR\n", - "\n", - "if not int8_model_det_path.exists():\n", - "\n", - " DATA_URL = \"http://images.cocodataset.org/zips/val2017.zip\"\n", - " LABELS_URL = \"https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip\"\n", - " CFG_URL = \"https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml\"\n", - "\n", - " OUT_DIR = DATASETS_DIR\n", - "\n", - " DATA_PATH = OUT_DIR / \"val2017.zip\"\n", - " LABELS_PATH = OUT_DIR / \"coco2017labels-segments.zip\"\n", - " CFG_PATH = OUT_DIR / \"coco.yaml\"\n", - "\n", - " download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n", - " download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n", - " download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n", - "\n", - " if not (OUT_DIR / \"coco/labels\").exists():\n", - " with ZipFile(LABELS_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR)\n", - " with ZipFile(DATA_PATH, \"r\") as zip_ref:\n", - " zip_ref.extractall(OUT_DIR / \"coco/images\")" - ] - }, - { - "cell_type": "code", - "id": "b0a14818-21fd-4c71-8e58-2297dfafaadd", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "from ultralytics.utils import DEFAULT_CFG\n", - "from ultralytics.cfg import get_cfg\n", - "from ultralytics.data.converter import coco80_to_coco91_class\n", - "from ultralytics.data.utils import check_det_dataset\n", - "\n", - "if not int8_model_det_path.exists():\n", - " args = get_cfg(cfg=DEFAULT_CFG)\n", - " args.data = str(CFG_PATH)\n", - " det_validator = ov_yolo_model.task_map[ov_yolo_model.task][\"validator\"](args=args)\n", - "\n", - " det_validator.data = check_det_dataset(args.data)\n", - " det_validator.stride = 32\n", - " det_data_loader = det_validator.get_dataloader(OUT_DIR / \"coco\", 1)" - ] - }, - { - "cell_type": "markdown", - "id": "83fa49f4-e40c-48c7-82a6-8ef56e20b343", - "source": [ - "NNCF provides `nncf.Dataset` wrapper for using native framework dataloaders in quantization pipeline. Additionally, we specify transform function that will be responsible for preparing input data in model expected format." - ] - }, - { - "cell_type": "code", - "id": "253f0e0f-131e-43b9-b1be-639d4b3d1fe5", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "import nncf\n", - "from typing import Dict\n", - "\n", - "\n", - "def transform_fn(data_item:Dict):\n", - " \"\"\"\n", - " Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n", - " Parameters:\n", - " data_item: Dict with data item produced by DataLoader during iteration\n", - " Returns:\n", - " input_tensor: Input data for quantization\n", - " \"\"\"\n", - " input_tensor = det_validator.preprocess(data_item)['img'].numpy()\n", - " return input_tensor\n", - "\n", - "if not int8_model_det_path.exists():\n", - " quantization_dataset = nncf.Dataset(det_data_loader, transform_fn)" - ] - }, - { - "cell_type": "markdown", - "id": "ff8a6372-2097-4308-a4e5-e4651242a8df", - "source": [ - "### Quantize and Save INT8 model\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n", - "Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. YOLOv10 model contains non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations.\n", - "\n", - ">**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware." - ] - }, - { - "cell_type": "code", - "id": "4c12ae38-239e-4fe1-8296-47567f98538c", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "import shutil\n", - "\n", - "if not int8_model_det_path.exists():\n", - " quantized_det_model = nncf.quantize(\n", - " ov_model,\n", - " quantization_dataset,\n", - " preset=nncf.QuantizationPreset.MIXED,\n", - " )\n", - "\n", - " ov.save_model(quantized_det_model, int8_model_det_path)\n", - " shutil.copy(ov_model_path.parent / \"metadata.yaml\", int8_model_det_path.parent / \"metadata.yaml\")" - ] - }, - { - "cell_type": "markdown", - "id": "41b8a3eb-adb8-40cf-8602-311ff8d75a76", - "source": [ - "## Run Optimized Model Inference\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The way of usage INT8 quantized model is the same like for model before quantization. Let's check inference result of quantized model on single image" + 'cell_type': 'markdown', + 'id': 'e9d010ff-85ba-4e69-b439-ba89e4a3a715', + 'source': [ + '# Convert and Optimize YOLOv10 with OpenVINO\n', + '\n', + 'Real-time object detection aims to accurately predict object categories and positions in images with low latency. The YOLO series has been at the forefront of this research due to its balance between performance and efficiency. However, reliance on NMS and architectural inefficiencies have hindered optimal performance. YOLOv10 addresses these issues by introducing consistent dual assignments for NMS-free training and a holistic efficiency-accuracy driven model design strategy.\n', + '\n', + 'YOLOv10, built on the [Ultralytics Python package](https://pypi.org/project/ultralytics/) by researchers at [Tsinghua University](https://www.tsinghua.edu.cn/en/), introduces a new approach to real-time object detection, addressing both the post-processing and model architecture deficiencies found in previous YOLO versions. By eliminating non-maximum suppression (NMS) and optimizing various model components, YOLOv10 achieves state-of-the-art performance with significantly reduced computational overhead. Extensive experiments demonstrate its superior accuracy-latency trade-offs across multiple model scales.\n', + '\n', + '![yolov10-approach.png](https://github.com/ultralytics/ultralytics/assets/26833433/f9b1bec0-928e-41ce-a205-e12db3c4929a)\n', + '\n', + 'More details about model architecture you can find in original [repo](https://github.com/THU-MIG/yolov10), [paper](https://arxiv.org/abs/2405.14458) and [Ultralytics documentation](https://docs.ultralytics.com/models/yolov10/).\n', + '\n', + 'This tutorial demonstrates step-by-step instructions on how to run and optimize PyTorch YOLO V10 with OpenVINO.\n', + '\n', + 'The tutorial consists of the following steps:\n', + '\n', + '- Prepare PyTorch model\n', + '- Convert PyTorch model to OpenVINO IR\n', + '- Run model inference with OpenVINO\n', + '- Prepare and run optimization pipeline using NNCF\n', + '- Compare performance of the FP16 and quantized models.\n', + '- Run optimized model inference on video\n', + '- Launch interactive Gradio demo\n', + '\n', + '#### Table of contents:\n', + '\n', + '- [Prerequisites](#Prerequisites)\n', + '- [Download PyTorch model](#Download-PyTorch-model)\n', + '- [Export PyTorch model to OpenVINO IR Format](#Export-PyTorch-model-to-OpenVINO-IR-Format)\n', + '- [Run OpenVINO Inference on AUTO device using Ultralytics API](#Run-OpenVINO-Inference-on-AUTO-device-using-Ultralytics-API)\n', + '- [Run OpenVINO Inference on selected device using Ultralytics API](#Run-OpenVINO-Inference-on-selected-device-using-Ultralytics-API)\n', + '- [Optimize model using NNCF Post-training Quantization API](#Optimize-model-using-NNCF-Post-training-Quantization-API)\n', + ' - [Prepare Quantization Dataset](#Prepare-Quantization-Dataset)\n', + ' - [Quantize and Save INT8 model](#Quantize-and-Save-INT8-model)\n', + '- [Run Optimized Model Inference](#Run-Optimized-Model-Inference)\n', + ' - [Run Optimized Model on AUTO device](#Run-Optimized-Model-on-AUTO-device)\n', + ' - [Run Optimized Model Inference on selected device](#Run-Optimized-Model-Inference-on-selected-device)\n', + '- [Compare the Original and Quantized Models](#Compare-the-Original-and-Quantized-Models)\n', + ' - [Model size](#Model-size)\n', + ' - [Performance](#Performance)\n', + ' - [FP16 model performance](#FP16-model-performance)\n', + ' - [Int8 model performance](#Int8-model-performance)\n', + '- [Live demo](#Live-demo)\n', + ' - [Gradio Interactive Demo](#Gradio-Interactive-Demo)\n', + '\n', + '\n', + '### Installation Instructions\n', + '\n', + 'This is a self-contained example that relies solely on its own code.\n', + '\n', + 'We recommend running the notebook in a virtual environment. You only need a Jupyter server to start.\n', + 'For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n', + '\n', + '\n' + ] + }, + { + 'cell_type': 'markdown', + 'id': '144e86a1-c220-4e3b-a8b2-8eda443faf2d', + 'source': [ + '## Prerequisites\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': 'bf76580b-860a-4dcd-8b01-f494cdffe37e', + 'source': [ + 'import os\n', + '\n', + 'os.environ["GIT_CLONE_PROTECTION_ACTIVE"] = "false"\n', + '\n', + '%pip install -q "nncf>=2.11.0"\n', + '%pip install -Uq "openvino>=2024.3.0"\n', + '%pip install -q "git+https://github.com/THU-MIG/yolov10.git" --extra-index-url https://download.pytorch.org/whl/cpu\n', + '%pip install -q "torch>=2.1" "torchvision>=0.16" tqdm opencv-python "gradio>=4.19" --extra-index-url https://download.pytorch.org/whl/cpu' + ] + }, + { + 'cell_type': 'code', + 'id': '75772eac-565b-4234-82bf-f305e0c1ceda', + 'source': [ + 'from pathlib import Path\n', + '\n', + '# Fetch `notebook_utils` module\n', + 'import requests\n', + '\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",\n', + ')\n', + '\n', + 'open("notebook_utils.py", "w").write(r.text)\n', + '\n', + 'from notebook_utils import download_file, VideoPlayer, device_widget' + ] + }, + { + 'cell_type': 'markdown', + 'id': '2888d8b4-44f6-4418-bae4-f433ff28010b', + 'source': [ + '## Download PyTorch model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'There are several version of [YOLO V10](https://github.com/THU-MIG/yolov10/tree/main?tab=readme-ov-file#performance) models provided by model authors. Each of them has different characteristics depends on number of training parameters, performance and accuracy. For demonstration purposes we will use `yolov10n`, but the same steps are also applicable to other models in YOLO V10 series.' + ] + }, + { + 'cell_type': 'code', + 'id': 'aea6315b-6a29-4f2a-96a9-eb3c1646a3a4', + 'source': [ + 'models_dir = Path("./models")\n', + 'models_dir.mkdir(exist_ok=True)' + ] + }, + { + 'cell_type': 'code', + 'id': 'd828401e-b6bd-4796-a7b8-681957a159d4', + 'source': [ + 'model_weights_url = "https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10n.pt"\n', + 'file_name = model_weights_url.split("/")[-1]\n', + 'model_name = file_name.replace(".pt", "")\n', + '\n', + 'download_file(model_weights_url, directory=models_dir)' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'dc21487d-dd79-4b56-b8e2-5b28c8d92ac8', + 'source': [ + '## Export PyTorch model to OpenVINO IR Format\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '8cfec0d6-fb55-4611-861b-326e892fcf09', + 'source': [ + 'As it was discussed before, YOLO V10 code is designed on top of [Ultralytics](https://docs.ultralytics.com/) library and has similar interface with YOLO V8 (You can check [YOLO V8 notebooks](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/yolov8-optimization) for more detailed instruction how to work with Ultralytics API). Ultralytics support OpenVINO model export using [export](https://docs.ultralytics.com/modes/export/) method of model class. Additionally, we can specify parameters responsible for target input size, static or dynamic input shapes and model precision (FP32/FP16/INT8). INT8 quantization can be additionally performed on export stage, but for making approach more flexible, we consider how to perform quantization using [NNCF](https://github.com/openvinotoolkit/nncf).' + ] + }, + { + 'cell_type': 'code', + 'id': 'd488f564-c1d5-4c6a-9b4a-353a3ab749e6', + 'source': [ + 'import types\n', + 'from ultralytics.utils import ops, yaml_load, yaml_save\n', + 'from ultralytics import YOLOv10\n', + 'import torch\n', + '\n', + 'detection_labels = {\n', + ' 0: "person",\n', + ' 1: "bicycle",\n', + ' 2: "car",\n', + ' 3: "motorcycle",\n', + ' 4: "airplane",\n', + ' 5: "bus",\n', + ' 6: "train",\n', + ' 7: "truck",\n', + ' 8: "boat",\n', + ' 9: "traffic light",\n', + ' 10: "fire hydrant",\n', + ' 11: "stop sign",\n', + ' 12: "parking meter",\n', + ' 13: "bench",\n', + ' 14: "bird",\n', + ' 15: "cat",\n', + ' 16: "dog",\n', + ' 17: "horse",\n', + ' 18: "sheep",\n', + ' 19: "cow",\n', + ' 20: "elephant",\n', + ' 21: "bear",\n', + ' 22: "zebra",\n', + ' 23: "giraffe",\n', + ' 24: "backpack",\n', + ' 25: "umbrella",\n', + ' 26: "handbag",\n', + ' 27: "tie",\n', + ' 28: "suitcase",\n', + ' 29: "frisbee",\n', + ' 30: "skis",\n', + ' 31: "snowboard",\n', + ' 32: "sports ball",\n', + ' 33: "kite",\n', + ' 34: "baseball bat",\n', + ' 35: "baseball glove",\n', + ' 36: "skateboard",\n', + ' 37: "surfboard",\n', + ' 38: "tennis racket",\n', + ' 39: "bottle",\n', + ' 40: "wine glass",\n', + ' 41: "cup",\n', + ' 42: "fork",\n', + ' 43: "knife",\n', + ' 44: "spoon",\n', + ' 45: "bowl",\n', + ' 46: "banana",\n', + ' 47: "apple",\n', + ' 48: "sandwich",\n', + ' 49: "orange",\n', + ' 50: "broccoli",\n', + ' 51: "carrot",\n', + ' 52: "hot dog",\n', + ' 53: "pizza",\n', + ' 54: "donut",\n', + ' 55: "cake",\n', + ' 56: "chair",\n', + ' 57: "couch",\n', + ' 58: "potted plant",\n', + ' 59: "bed",\n', + ' 60: "dining table",\n', + ' 61: "toilet",\n', + ' 62: "tv",\n', + ' 63: "laptop",\n', + ' 64: "mouse",\n', + ' 65: "remote",\n', + ' 66: "keyboard",\n', + ' 67: "cell phone",\n', + ' 68: "microwave",\n', + ' 69: "oven",\n', + ' 70: "toaster",\n', + ' 71: "sink",\n', + ' 72: "refrigerator",\n', + ' 73: "book",\n', + ' 74: "clock",\n', + ' 75: "vase",\n', + ' 76: "scissors",\n', + ' 77: "teddy bear",\n', + ' 78: "hair drier",\n', + ' 79: "toothbrush",\n', + '}\n', + '\n', + '\n', + 'def v10_det_head_forward(self, x):\n', + ' one2one = self.forward_feat([xi.detach() for xi in x], self.one2one_cv2, self.one2one_cv3)\n', + ' if not self.export:\n', + ' one2many = super().forward(x)\n', + '\n', + ' if not self.training:\n', + ' one2one = self.inference(one2one)\n', + ' if not self.export:\n', + ' return {"one2many": one2many, "one2one": one2one}\n', + ' else:\n', + ' assert self.max_det != -1\n', + ' boxes, scores, labels = ops.v10postprocess(one2one.permute(0, 2, 1), self.max_det, self.nc)\n', + ' return torch.cat(\n', + ' [boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)],\n', + ' dim=-1,\n', + ' )\n', + ' else:\n', + ' return {"one2many": one2many, "one2one": one2one}\n', + '\n', + '\n', + 'ov_model_path = models_dir / f"{model_name}_openvino_model/{model_name}.xml"\n', + 'if not ov_model_path.exists():\n', + ' model = YOLOv10(models_dir / file_name)\n', + ' model.model.model[-1].forward = types.MethodType(v10_det_head_forward, model.model.model[-1])\n', + ' model.export(format="openvino", dynamic=True, half=True)\n', + ' config = yaml_load(ov_model_path.parent / "metadata.yaml")\n', + ' config["names"] = detection_labels\n', + ' yaml_save(ov_model_path.parent / "metadata.yaml", config)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '8a23f621-f6cd-4bfd-8b98-24b8c0392761', + 'source': [ + '## Run OpenVINO Inference on AUTO device using Ultralytics API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'Now, when we exported model to OpenVINO, we can load it directly into YOLOv10 class, where automatic inference backend will provide easy-to-use user experience to run OpenVINO YOLOv10 model on the similar level like for original PyTorch model. The code bellow demonstrates how to run inference OpenVINO exported model with Ultralytics API on single image. [AUTO device](https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/auto-device) will be used for launching model.' + ] + }, + { + 'cell_type': 'code', + 'id': '7e8ed361-bf09-4398-ba15-e6c5dda73cd3', + 'source': [ + 'ov_yolo_model = YOLOv10(ov_model_path.parent, task="detect")' + ] + }, + { + 'cell_type': 'code', + 'id': '8a473286-59b8-498f-8541-df84f041e119', + 'source': [ + 'from PIL import Image\n', + '\n', + 'IMAGE_PATH = Path("./data/coco_bike.jpg")\n', + 'download_file(\n', + ' url="https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bike.jpg",\n', + ' filename=IMAGE_PATH.name,\n', + ' directory=IMAGE_PATH.parent,\n', + ')' + ] + }, + { + 'cell_type': 'code', + 'id': '7e116e81-0ad4-4b9e-9f5c-58680fd8f0c6', + 'source': [ + 'res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)\n', + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'cell_type': 'markdown', + 'id': '943b9b04-4b97-4395-99d1-695465de1140', + 'source': [ + '## Run OpenVINO Inference on selected device using Ultralytics API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'In this part of notebook you can select inference device for running model inference to compare results with AUTO.' + ] + }, + { + 'cell_type': 'code', + 'id': '41655bec-b006-4376-abbb-d6c8c09e89fb', + 'source': [ + 'device = device_widget("CPU")\n', + '\n', + 'device' + ] + }, + { + 'cell_type': 'code', + 'id': 'f284d5fd-12ba-4c08-afce-d8131ef7ad4d', + 'source': [ + 'import openvino as ov\n', + '\n', + 'core = ov.Core()\n', + '\n', + 'ov_model = core.read_model(ov_model_path)\n', + '\n', + '# load model on selected device\n', + 'if "GPU" in device.value or "NPU" in device.value:\n', + ' ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'ov_config = {}\n', + 'if "GPU" in device.value:\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + 'det_compiled_model = core.compile_model(ov_model, device.value, ov_config)' + ] + }, + { + 'cell_type': 'code', + 'id': '12164d21-948a-4ef7-9e1c-b0d41c53cf91', + 'source': [ + 'ov_yolo_model.predictor.model.ov_compiled_model = det_compiled_model' + ] + }, + { + 'cell_type': 'code', + 'id': 'ecb9d396-ab1e-4398-a110-18c08b00ab14', + 'source': [ + 'res = ov_yolo_model(IMAGE_PATH, iou=0.45, conf=0.2)' + ] + }, + { + 'cell_type': 'code', + 'id': 'a4abf758-3a72-4ee0-afde-38076602f1c7', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' + ] + }, + { + 'cell_type': 'markdown', + 'id': '992b6ac3-4248-4848-8fd6-f9c1b9fc2051', + 'source': [ + '## Optimize model using NNCF Post-training Quantization API\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + '[NNCF](https://github.com/openvinotoolkit/nncf) provides a suite of advanced algorithms for Neural Networks inference optimization in OpenVINO with minimal accuracy drop.\n', + 'We will use 8-bit quantization in post-training mode (without the fine-tuning pipeline) to optimize YOLOv10.\n', + '\n', + 'The optimization process contains the following steps:\n', + '\n', + '1. Create a Dataset for quantization.\n', + '2. Run `nncf.quantize` for getting an optimized model.\n', + '3. Serialize OpenVINO IR model, using the `openvino.save_model` function.\n', + '\n', + 'Quantization is time and memory consuming process, you can skip this step using checkbox bellow:' + ] + }, + { + 'cell_type': 'code', + 'id': 'ed9d9ce5-8df9-4803-ae85-bf0744de914c', + 'source': [ + 'import ipywidgets as widgets\n', + '\n', + 'int8_model_det_path = models_dir / "int8" / f"{model_name}_openvino_model/{model_name}.xml"\n', + 'ov_yolo_int8_model = None\n', + '\n', + 'to_quantize = widgets.Checkbox(\n', + ' value=True,\n', + ' description="Quantization",\n', + ' disabled=False,\n', + ')\n', + '\n', + 'to_quantize' + ] + }, + { + 'cell_type': 'code', + 'id': '52287a23-b5ef-4c34-873a-32b398d26d1a', + 'source': [ + '# Fetch skip_kernel_extension module\n', + 'r = requests.get(\n', + ' url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/skip_kernel_extension.py",\n', + ')\n', + 'open("skip_kernel_extension.py", "w").write(r.text)\n', + '\n', + '%load_ext skip_kernel_extension' + ] + }, + { + 'cell_type': 'markdown', + 'id': '89ba64af-aa73-4ed9-a2f9-527bd9ae8221', + 'source': [ + '### Prepare Quantization Dataset\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'For starting quantization, we need to prepare dataset. We will use validation subset from [MS COCO dataset](https://cocodataset.org/) for model quantization and Ultralytics validation data loader for preparing input data.' + ] + }, + { + 'cell_type': 'code', + 'id': '5e10435e-9cd6-4f29-a865-721d6209314d', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'from zipfile import ZipFile\n', + '\n', + 'from ultralytics.data.utils import DATASETS_DIR\n', + '\n', + 'if not int8_model_det_path.exists():\n', + '\n', + ' DATA_URL = "http://images.cocodataset.org/zips/val2017.zip"\n', + ' LABELS_URL = "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip"\n', + ' CFG_URL = "https://raw.githubusercontent.com/ultralytics/ultralytics/v8.1.0/ultralytics/cfg/datasets/coco.yaml"\n', + '\n', + ' OUT_DIR = DATASETS_DIR\n', + '\n', + ' DATA_PATH = OUT_DIR / "val2017.zip"\n', + ' LABELS_PATH = OUT_DIR / "coco2017labels-segments.zip"\n', + ' CFG_PATH = OUT_DIR / "coco.yaml"\n', + '\n', + ' download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)\n', + ' download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)\n', + ' download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)\n', + '\n', + ' if not (OUT_DIR / "coco/labels").exists():\n', + ' with ZipFile(LABELS_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR)\n', + ' with ZipFile(DATA_PATH, "r") as zip_ref:\n', + ' zip_ref.extractall(OUT_DIR / "coco/images")' + ] + }, + { + 'cell_type': 'code', + 'id': 'b0a14818-21fd-4c71-8e58-2297dfafaadd', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'from ultralytics.utils import DEFAULT_CFG\n', + 'from ultralytics.cfg import get_cfg\n', + 'from ultralytics.data.converter import coco80_to_coco91_class\n', + 'from ultralytics.data.utils import check_det_dataset\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' args = get_cfg(cfg=DEFAULT_CFG)\n', + ' args.data = str(CFG_PATH)\n', + ' det_validator = ov_yolo_model.task_map[ov_yolo_model.task]["validator"](args=args)\n', + '\n', + ' det_validator.data = check_det_dataset(args.data)\n', + ' det_validator.stride = 32\n', + ' det_data_loader = det_validator.get_dataloader(OUT_DIR / "coco", 1)' + ] + }, + { + 'cell_type': 'markdown', + 'id': '83fa49f4-e40c-48c7-82a6-8ef56e20b343', + 'source': [ + 'NNCF provides `nncf.Dataset` wrapper for using native framework dataloaders in quantization pipeline. Additionally, we specify transform function that will be responsible for preparing input data in model expected format.' + ] + }, + { + 'cell_type': 'code', + 'id': '253f0e0f-131e-43b9-b1be-639d4b3d1fe5', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'import nncf\n', + 'from typing import Dict\n', + '\n', + '\n', + 'def transform_fn(data_item:Dict):\n', + ' """\n', + ' Quantization transform function. Extracts and preprocess input data from dataloader item for quantization.\n', + ' Parameters:\n', + ' data_item: Dict with data item produced by DataLoader during iteration\n', + ' Returns:\n', + ' input_tensor: Input data for quantization\n', + ' """\n', + ` input_tensor = det_validator.preprocess(data_item)['img'].numpy()\n`, + ' return input_tensor\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' quantization_dataset = nncf.Dataset(det_data_loader, transform_fn)' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'ff8a6372-2097-4308-a4e5-e4651242a8df', + 'source': [ + '### Quantize and Save INT8 model\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'The `nncf.quantize` function provides an interface for model quantization. It requires an instance of the OpenVINO Model and quantization dataset. \n', + 'Optionally, some additional parameters for the configuration quantization process (number of samples for quantization, preset, ignored scope, etc.) can be provided. YOLOv10 model contains non-ReLU activation functions, which require asymmetric quantization of activations. To achieve a better result, we will use a `mixed` quantization preset. It provides symmetric quantization of weights and asymmetric quantization of activations.\n', + '\n', + '>**Note**: Model post-training quantization is time-consuming process. Be patient, it can take several minutes depending on your hardware.' + ] + }, + { + 'cell_type': 'code', + 'id': '4c12ae38-239e-4fe1-8296-47567f98538c', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'import shutil\n', + '\n', + 'if not int8_model_det_path.exists():\n', + ' quantized_det_model = nncf.quantize(\n', + ' ov_model,\n', + ' quantization_dataset,\n', + ' preset=nncf.QuantizationPreset.MIXED,\n', + ' )\n', + '\n', + ' ov.save_model(quantized_det_model, int8_model_det_path)\n', + ' shutil.copy(ov_model_path.parent / "metadata.yaml", int8_model_det_path.parent / "metadata.yaml")' + ] + }, + { + 'cell_type': 'markdown', + 'id': '41b8a3eb-adb8-40cf-8602-311ff8d75a76', + 'source': [ + '## Run Optimized Model Inference\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + `The way of usage INT8 quantized model is the same like for model before quantization. Let's check inference result of quantized model on single image` ] }, { - "cell_type": "markdown", - "id": "f438df41-d614-4cca-a746-a173d92dddf5", - "source": [ - "### Run Optimized Model on AUTO device\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'f438df41-d614-4cca-a746-a173d92dddf5', + 'source': [ + '### Run Optimized Model on AUTO device\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "af61303b-16bd-4206-b926-2c28b3a859a4", - "source": [ - "%%skip not $to_quantize.value\n", - "ov_yolo_int8_model = YOLOv10(int8_model_det_path.parent, task=\"detect\")" + 'cell_type': 'code', + 'id': 'af61303b-16bd-4206-b926-2c28b3a859a4', + 'source': [ + '%%skip not $to_quantize.value\n', + 'ov_yolo_int8_model = YOLOv10(int8_model_det_path.parent, task="detect")' ] }, { - "cell_type": "code", - "id": "db13b8dd-49a1-4046-8a1a-491eee095a1b", - "source": [ - "%%skip not $to_quantize.value\n", - "res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)" + 'cell_type': 'code', + 'id': 'db13b8dd-49a1-4046-8a1a-491eee095a1b', + 'source': [ + '%%skip not $to_quantize.value\n', + 'res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)' ] }, { - "cell_type": "code", - "id": "0423ba31-f0ee-4bfc-a18a-f7b870a9683d", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" + 'cell_type': 'code', + 'id': '0423ba31-f0ee-4bfc-a18a-f7b870a9683d', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "4b4f7118-e046-4f87-b403-87053f2b0ac3", - "source": [ - "### Run Optimized Model Inference on selected device\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': '4b4f7118-e046-4f87-b403-87053f2b0ac3', + 'source': [ + '### Run Optimized Model Inference on selected device\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "142f22ae-6a8d-4578-b97f-4d9c77123418", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "device" + 'cell_type': 'code', + 'id': '142f22ae-6a8d-4578-b97f-4d9c77123418', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'device' ] }, { - "cell_type": "code", - "id": "2f43767a-1994-4202-a411-4738c92223da", - "source": [ - "%%skip not $to_quantize.value\n", - "\n", - "ov_config = {}\n", - "if \"GPU\" in device.value or \"NPU\" in device.value:\n", - " ov_model.reshape({0: [1, 3, 640, 640]})\n", - "ov_config = {}\n", - "if \"GPU\" in device.value:\n", - " ov_config = {\"GPU_DISABLE_WINOGRAD_CONVOLUTION\": \"YES\"}\n", - "\n", - "quantized_det_model = core.read_model(int8_model_det_path)\n", - "quantized_det_compiled_model = core.compile_model(quantized_det_model, device.value, ov_config)\n", - "\n", - "ov_yolo_int8_model.predictor.model.ov_compiled_model = quantized_det_compiled_model\n", - "\n", - "res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)" + 'cell_type': 'code', + 'id': '2f43767a-1994-4202-a411-4738c92223da', + 'source': [ + '%%skip not $to_quantize.value\n', + '\n', + 'ov_config = {}\n', + 'if "GPU" in device.value or "NPU" in device.value:\n', + ' ov_model.reshape({0: [1, 3, 640, 640]})\n', + 'ov_config = {}\n', + 'if "GPU" in device.value:\n', + ' ov_config = {"GPU_DISABLE_WINOGRAD_CONVOLUTION": "YES"}\n', + '\n', + 'quantized_det_model = core.read_model(int8_model_det_path)\n', + 'quantized_det_compiled_model = core.compile_model(quantized_det_model, device.value, ov_config)\n', + '\n', + 'ov_yolo_int8_model.predictor.model.ov_compiled_model = quantized_det_compiled_model\n', + '\n', + 'res = ov_yolo_int8_model(IMAGE_PATH, iou=0.45, conf=0.2)' ] }, { - "cell_type": "code", - "id": "51d64c7b-7595-41ca-87dd-4c85308f84d6", - "source": [ - "Image.fromarray(res[0].plot()[:, :, ::-1])" + 'cell_type': 'code', + 'id': '51d64c7b-7595-41ca-87dd-4c85308f84d6', + 'source': [ + 'Image.fromarray(res[0].plot()[:, :, ::-1])' ] }, { - "cell_type": "markdown", - "id": "c7b70f75-a706-4f24-a487-88a3ad645dd5", - "source": [ - "## Compare the Original and Quantized Models\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'c7b70f75-a706-4f24-a487-88a3ad645dd5', + 'source': [ + '## Compare the Original and Quantized Models\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "markdown", - "id": "d6561246-a9e0-4cdf-8207-7e2f919d8582", - "source": [ - "### Model size\n", - "[back to top ⬆️](#Table-of-contents:)" + 'cell_type': 'markdown', + 'id': 'd6561246-a9e0-4cdf-8207-7e2f919d8582', + 'source': [ + '### Model size\n', + '[back to top ⬆️](#Table-of-contents:)' ] }, { - "cell_type": "code", - "id": "43d84d15-75be-4430-a58b-61cfd8e08dd0", - "source": [ - "ov_model_weights = ov_model_path.with_suffix(\".bin\")\n", - "print(f\"Size of FP16 model is {ov_model_weights.stat().st_size / 1024 / 1024:.2f} MB\")\n", - "if int8_model_det_path.exists():\n", - " ov_int8_weights = int8_model_det_path.with_suffix(\".bin\")\n", - " print(f\"Size of model with INT8 compressed weights is {ov_int8_weights.stat().st_size / 1024 / 1024:.2f} MB\")\n", - " print(f\"Compression rate for INT8 model: {ov_model_weights.stat().st_size / ov_int8_weights.stat().st_size:.3f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "f72b3d84-91f3-493c-8f4e-46598ffbd6d1", - "source": [ - "### Performance\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "### FP16 model performance\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "909ea508-e007-4313-ab0f-bfa0e3906176", - "source": [ - "!benchmark_app -m $ov_model_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "markdown", - "id": "9852a8ef-4c45-43dd-a9ef-53ca6f13c99e", - "source": [ - "### Int8 model performance\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "8254626d-6c4f-4ca3-936f-a7c47e402e03", - "source": [ - "if int8_model_det_path.exists():\n", - " !benchmark_app -m $int8_model_det_path -d $device.value -api async -shape \"[1,3,640,640]\" -t 15" - ] - }, - { - "cell_type": "markdown", - "id": "12d35bc1-8101-45ea-8bd7-53720b98f5e5", - "source": [ - "## Live demo\n", - "[back to top ⬆️](#Table-of-contents:)\n", - "\n", - "\n", - "The following code runs model inference on a video:" - ] - }, - { - "cell_type": "code", - "id": "246bd379-b1d5-4eb2-928f-97c7c2455a92", - "source": [ - "import collections\n", - "import time\n", - "from IPython import display\n", - "import cv2\n", - "import numpy as np\n", - "\n", - "\n", - "# Main processing function to run object detection.\n", - "def run_object_detection(\n", - " source=0,\n", - " flip=False,\n", - " use_popup=False,\n", - " skip_first_frames=0,\n", - " det_model=ov_yolo_int8_model,\n", - " device=device.value,\n", - "):\n", - " player = None\n", - " try:\n", - " # Create a video player to play with target fps.\n", - " player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n", - " # Start capturing.\n", - " player.start()\n", - " if use_popup:\n", - " title = \"Press ESC to Exit\"\n", - " cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n", - "\n", - " processing_times = collections.deque()\n", - " while True:\n", - " # Grab the frame.\n", - " frame = player.next()\n", - " if frame is None:\n", - " print(\"Source ended\")\n", - " break\n", - " # If the frame is larger than full HD, reduce size to improve the performance.\n", - " scale = 1280 / max(frame.shape)\n", - " if scale < 1:\n", - " frame = cv2.resize(\n", - " src=frame,\n", - " dsize=None,\n", - " fx=scale,\n", - " fy=scale,\n", - " interpolation=cv2.INTER_AREA,\n", - " )\n", - " # Get the results.\n", - " input_image = np.array(frame)\n", - "\n", - " start_time = time.time()\n", - " detections = det_model(input_image, iou=0.45, conf=0.2, verbose=False)\n", - " stop_time = time.time()\n", - " frame = detections[0].plot()\n", - "\n", - " processing_times.append(stop_time - start_time)\n", - " # Use processing times from last 200 frames.\n", - " if len(processing_times) > 200:\n", - " processing_times.popleft()\n", - "\n", - " _, f_width = frame.shape[:2]\n", - " # Mean processing time [ms].\n", - " processing_time = np.mean(processing_times) * 1000\n", - " fps = 1000 / processing_time\n", - " cv2.putText(\n", - " img=frame,\n", - " text=f\"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)\",\n", - " org=(20, 40),\n", - " fontFace=cv2.FONT_HERSHEY_COMPLEX,\n", - " fontScale=f_width / 1000,\n", - " color=(0, 0, 255),\n", - " thickness=1,\n", - " lineType=cv2.LINE_AA,\n", - " )\n", - " # Use this workaround if there is flickering.\n", - " if use_popup:\n", - " cv2.imshow(winname=title, mat=frame)\n", - " key = cv2.waitKey(1)\n", - " # escape = 27\n", - " if key == 27:\n", - " break\n", - " else:\n", - " # Encode numpy array to jpg.\n", - " _, encoded_img = cv2.imencode(ext=\".jpg\", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n", - " # Create an IPython image.\n", - " i = display.Image(data=encoded_img)\n", - " # Display the image in this notebook.\n", - " display.clear_output(wait=True)\n", - " display.display(i)\n", - " # ctrl-c\n", - " except KeyboardInterrupt:\n", - " print(\"Interrupted\")\n", - " # any different error\n", - " except RuntimeError as e:\n", - " print(e)\n", - " finally:\n", - " if player is not None:\n", - " # Stop capturing.\n", - " player.stop()\n", - " if use_popup:\n", - " cv2.destroyAllWindows()" - ] - }, - { - "cell_type": "code", - "id": "8db930f4-fc41-4635-9248-deab2b6cfcb8", - "source": [ - "use_int8 = widgets.Checkbox(\n", - " value=ov_yolo_int8_model is not None,\n", - " description=\"Use int8 model\",\n", - " disabled=ov_yolo_int8_model is None,\n", - ")\n", - "\n", - "use_int8" - ] - }, - { - "cell_type": "code", - "id": "59e37135-1bdc-40ff-8789-b5b4491cab84", - "source": [ - "WEBCAM_INFERENCE = False\n", - "\n", - "if WEBCAM_INFERENCE:\n", - " VIDEO_SOURCE = 0 # Webcam\n", - "else:\n", - " download_file(\n", - " \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4\",\n", - " directory=\"data\",\n", - " )\n", - " VIDEO_SOURCE = \"data/people.mp4\"" - ] - }, - { - "cell_type": "code", - "id": "88c6f47c-1c47-451d-89ba-0a29760f5ec4", - "source": [ - "run_object_detection(\n", - " det_model=ov_yolo_model if not use_int8.value else ov_yolo_int8_model,\n", - " source=VIDEO_SOURCE,\n", - " flip=True,\n", - " use_popup=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "9f612d80-68e2-45f8-aa62-9f3a05ca9de8", - "source": [ - "### Gradio Interactive Demo\n", - "[back to top ⬆️](#Table-of-contents:)" - ] - }, - { - "cell_type": "code", - "id": "2e5a6249", - "source": [ - "def yolov10_inference(image, int8, conf_threshold, iou_threshold):\n", - " model = ov_yolo_model if not int8 else ov_yolo_int8_model\n", - " results = model(source=image, iou=iou_threshold, conf=conf_threshold, verbose=False)[0]\n", - " annotated_image = Image.fromarray(results.plot())\n", - "\n", - " return annotated_image" - ] - }, - { - "cell_type": "code", - "id": "ffdbe248-7086-4804-b4b9-c3dd454bf80c", - "source": [ - "if not Path(\"gradio_helper.py\").exists():\n", - " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/yolov10-optimization/gradio_helper.py\")\n", - " open(\"gradio_helper.py\", \"w\").write(r.text)\n", - "\n", - "from gradio_helper import make_demo\n", - "\n", - "demo = make_demo(fn=yolov10_inference, quantized=ov_yolo_int8_model is not None)\n", - "\n", - "try:\n", - " demo.launch(debug=True)\n", - "except Exception:\n", - " demo.launch(debug=True, share=True)\n", - "# If you are launching remotely, specify server_name and server_port\n", - "# EXAMPLE: `demo.launch(server_name='your server name', server_port='server port in int')`\n", - "# To learn more please refer to the Gradio docs: https://gradio.app/docs/" - ] - }, - { - "cell_type": "code", - "id": "4b123c5e", - "source": [ - "# please uncomment and run this cell for stopping gradio interface\n", - "# demo.close()" + 'cell_type': 'code', + 'id': '43d84d15-75be-4430-a58b-61cfd8e08dd0', + 'source': [ + 'ov_model_weights = ov_model_path.with_suffix(".bin")\n', + 'print(f"Size of FP16 model is {ov_model_weights.stat().st_size / 1024 / 1024:.2f} MB")\n', + 'if int8_model_det_path.exists():\n', + ' ov_int8_weights = int8_model_det_path.with_suffix(".bin")\n', + ' print(f"Size of model with INT8 compressed weights is {ov_int8_weights.stat().st_size / 1024 / 1024:.2f} MB")\n', + ' print(f"Compression rate for INT8 model: {ov_model_weights.stat().st_size / ov_int8_weights.stat().st_size:.3f}")' + ] + }, + { + 'cell_type': 'markdown', + 'id': 'f72b3d84-91f3-493c-8f4e-46598ffbd6d1', + 'source': [ + '### Performance\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + '### FP16 model performance\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': '909ea508-e007-4313-ab0f-bfa0e3906176', + 'source': [ + '!benchmark_app -m $ov_model_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'markdown', + 'id': '9852a8ef-4c45-43dd-a9ef-53ca6f13c99e', + 'source': [ + '### Int8 model performance\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': '8254626d-6c4f-4ca3-936f-a7c47e402e03', + 'source': [ + 'if int8_model_det_path.exists():\n', + ' !benchmark_app -m $int8_model_det_path -d $device.value -api async -shape "[1,3,640,640]" -t 15' + ] + }, + { + 'cell_type': 'markdown', + 'id': '12d35bc1-8101-45ea-8bd7-53720b98f5e5', + 'source': [ + '## Live demo\n', + '[back to top ⬆️](#Table-of-contents:)\n', + '\n', + '\n', + 'The following code runs model inference on a video:' + ] + }, + { + 'cell_type': 'code', + 'id': '246bd379-b1d5-4eb2-928f-97c7c2455a92', + 'source': [ + 'import collections\n', + 'import time\n', + 'from IPython import display\n', + 'import cv2\n', + 'import numpy as np\n', + '\n', + '\n', + '# Main processing function to run object detection.\n', + 'def run_object_detection(\n', + ' source=0,\n', + ' flip=False,\n', + ' use_popup=False,\n', + ' skip_first_frames=0,\n', + ' det_model=ov_yolo_int8_model,\n', + ' device=device.value,\n', + '):\n', + ' player = None\n', + ' try:\n', + ' # Create a video player to play with target fps.\n', + ' player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n', + ' # Start capturing.\n', + ' player.start()\n', + ' if use_popup:\n', + ' title = "Press ESC to Exit"\n', + ' cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n', + '\n', + ' processing_times = collections.deque()\n', + ' while True:\n', + ' # Grab the frame.\n', + ' frame = player.next()\n', + ' if frame is None:\n', + ' print("Source ended")\n', + ' break\n', + ' # If the frame is larger than full HD, reduce size to improve the performance.\n', + ' scale = 1280 / max(frame.shape)\n', + ' if scale < 1:\n', + ' frame = cv2.resize(\n', + ' src=frame,\n', + ' dsize=None,\n', + ' fx=scale,\n', + ' fy=scale,\n', + ' interpolation=cv2.INTER_AREA,\n', + ' )\n', + ' # Get the results.\n', + ' input_image = np.array(frame)\n', + '\n', + ' start_time = time.time()\n', + ' detections = det_model(input_image, iou=0.45, conf=0.2, verbose=False)\n', + ' stop_time = time.time()\n', + ' frame = detections[0].plot()\n', + '\n', + ' processing_times.append(stop_time - start_time)\n', + ' # Use processing times from last 200 frames.\n', + ' if len(processing_times) > 200:\n', + ' processing_times.popleft()\n', + '\n', + ' _, f_width = frame.shape[:2]\n', + ' # Mean processing time [ms].\n', + ' processing_time = np.mean(processing_times) * 1000\n', + ' fps = 1000 / processing_time\n', + ' cv2.putText(\n', + ' img=frame,\n', + ' text=f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",\n', + ' org=(20, 40),\n', + ' fontFace=cv2.FONT_HERSHEY_COMPLEX,\n', + ' fontScale=f_width / 1000,\n', + ' color=(0, 0, 255),\n', + ' thickness=1,\n', + ' lineType=cv2.LINE_AA,\n', + ' )\n', + ' # Use this workaround if there is flickering.\n', + ' if use_popup:\n', + ' cv2.imshow(winname=title, mat=frame)\n', + ' key = cv2.waitKey(1)\n', + ' # escape = 27\n', + ' if key == 27:\n', + ' break\n', + ' else:\n', + ' # Encode numpy array to jpg.\n', + ' _, encoded_img = cv2.imencode(ext=".jpg", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n', + ' # Create an IPython image.\n', + ' i = display.Image(data=encoded_img)\n', + ' # Display the image in this notebook.\n', + ' display.clear_output(wait=True)\n', + ' display.display(i)\n', + ' # ctrl-c\n', + ' except KeyboardInterrupt:\n', + ' print("Interrupted")\n', + ' # any different error\n', + ' except RuntimeError as e:\n', + ' print(e)\n', + ' finally:\n', + ' if player is not None:\n', + ' # Stop capturing.\n', + ' player.stop()\n', + ' if use_popup:\n', + ' cv2.destroyAllWindows()' + ] + }, + { + 'cell_type': 'code', + 'id': '8db930f4-fc41-4635-9248-deab2b6cfcb8', + 'source': [ + 'use_int8 = widgets.Checkbox(\n', + ' value=ov_yolo_int8_model is not None,\n', + ' description="Use int8 model",\n', + ' disabled=ov_yolo_int8_model is None,\n', + ')\n', + '\n', + 'use_int8' + ] + }, + { + 'cell_type': 'code', + 'id': '59e37135-1bdc-40ff-8789-b5b4491cab84', + 'source': [ + 'WEBCAM_INFERENCE = False\n', + '\n', + 'if WEBCAM_INFERENCE:\n', + ' VIDEO_SOURCE = 0 # Webcam\n', + 'else:\n', + ' download_file(\n', + ' "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/people.mp4",\n', + ' directory="data",\n', + ' )\n', + ' VIDEO_SOURCE = "data/people.mp4"' + ] + }, + { + 'cell_type': 'code', + 'id': '88c6f47c-1c47-451d-89ba-0a29760f5ec4', + 'source': [ + 'run_object_detection(\n', + ' det_model=ov_yolo_model if not use_int8.value else ov_yolo_int8_model,\n', + ' source=VIDEO_SOURCE,\n', + ' flip=True,\n', + ' use_popup=False,\n', + ')' + ] + }, + { + 'cell_type': 'markdown', + 'id': '9f612d80-68e2-45f8-aa62-9f3a05ca9de8', + 'source': [ + '### Gradio Interactive Demo\n', + '[back to top ⬆️](#Table-of-contents:)' + ] + }, + { + 'cell_type': 'code', + 'id': '2e5a6249', + 'source': [ + 'def yolov10_inference(image, int8, conf_threshold, iou_threshold):\n', + ' model = ov_yolo_model if not int8 else ov_yolo_int8_model\n', + ' results = model(source=image, iou=iou_threshold, conf=conf_threshold, verbose=False)[0]\n', + ' annotated_image = Image.fromarray(results.plot())\n', + '\n', + ' return annotated_image' + ] + }, + { + 'cell_type': 'code', + 'id': 'ffdbe248-7086-4804-b4b9-c3dd454bf80c', + 'source': [ + 'if not Path("gradio_helper.py").exists():\n', + ' r = requests.get(url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/yolov10-optimization/gradio_helper.py")\n', + ' open("gradio_helper.py", "w").write(r.text)\n', + '\n', + 'from gradio_helper import make_demo\n', + '\n', + 'demo = make_demo(fn=yolov10_inference, quantized=ov_yolo_int8_model is not None)\n', + '\n', + 'try:\n', + ' demo.launch(debug=True)\n', + 'except Exception:\n', + ' demo.launch(debug=True, share=True)\n', + '# If you are launching remotely, specify server_name and server_port\n', + '# EXAMPLE: `demo.launch(server_name=\'your server name\', server_port=\'server port in int\')`\n', + '# To learn more please refer to the Gradio docs: https://gradio.app/docs/' + ] + }, + { + 'cell_type': 'code', + 'id': '4b123c5e', + 'source': [ + '# please uncomment and run this cell for stopping gradio interface\n', + '# demo.close()' ] } ].map(fromJupyterCell) @@ -12151,1085 +12151,1085 @@ suite('NotebookDiff Diff Service', () => { const { mapping, diff } = await mapCells( [ { - "cell_type": "markdown", - "metadata": { - "id": "F0Wut2G5kz2Z" - }, - "source": [ - "[![Roboflow Notebooks](https://ik.imagekit.io/roboflow/notebooks/template/bannertest2-2.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672932710194)](https://github.com/roboflow/notebooks)\n", - "\n", - "# Fast Segment Anything Model (FastSAM)\n", - "\n", - "---\n", - "\n", - "[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/CASIA-IVA-Lab/FastSAM)\n", - "[![arXiv](https://img.shields.io/badge/arXiv-2306.12156-b31b1b.svg)](https://arxiv.org/pdf/2306.12156.pdf)\n", - "[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/yHNPyqazYYU)\n", - "[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-fastsam)\n", - "\n", - "The Fast Segment Anything Model (FastSAM) is a CNN Segment Anything Model trained by only 2% of the SA-1B dataset published by SAM authors.\n", - "\n", - "![fast-segment-anything-model-figure-1](https://media.roboflow.com/notebooks/examples/fast-sam-figure-1.png)\n", - "\n", - "The FastSAM authors claim it achieves comparable performance to the SAM method at 50 times the speed.\n", - "\n", - "![fast-segment-anything-model-figure-2](https://media.roboflow.com/notebooks/examples/fast-sam-figure-2.png)\n", - "\n", - "## Complementary Materials Covering SAM\n", - "\n", - "---\n", - "\n", - "[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/facebookresearch/segment-anything)\n", - "[![arXiv](https://img.shields.io/badge/arXiv-2304.02643-b31b1b.svg)](https://arxiv.org/abs/2304.02643)\n", - "[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-segment-anything-with-sam.ipynb)\n", - "[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/D-D6ZmadzPE)\n", - "[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-segment-anything-model-sam)\n", - "\n", - "## Steps in this Tutorial\n", - "\n", - "In this tutorial, we are going to cover:\n", - "\n", - "- Before you start\n", - "- Install FastSAM, SAM, and other dependencies\n", - "- Download FastSAM and SAM weights\n", - "- Download example data\n", - "- Imports\n", - "- Load FastSAM\n", - "- FastSAM inference\n", - "- FastSAM box prompt inference\n", - "- FastSAM point prompt inference\n", - "- FastSAM text prompt inference\n", - "- SAM vs FastSAM\n", - "- Roboflow benchmark dataset\n", - "\n", - "## 🔥 Let's begin!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "W4u6KjiVdNZM" - }, - "source": [ - "## Before you start\n", - "\n", - "Let's make sure that we have access to GPU. We can use `nvidia-smi` command to do that. In case of any problems navigate to `Edit` -> `Notebook settings` -> `Hardware accelerator`, set it to `GPU`, and then click `Save`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'F0Wut2G5kz2Z' + }, + 'source': [ + '[![Roboflow Notebooks](https://ik.imagekit.io/roboflow/notebooks/template/bannertest2-2.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672932710194)](https://github.com/roboflow/notebooks)\n', + '\n', + '# Fast Segment Anything Model (FastSAM)\n', + '\n', + '---\n', + '\n', + '[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/CASIA-IVA-Lab/FastSAM)\n', + '[![arXiv](https://img.shields.io/badge/arXiv-2306.12156-b31b1b.svg)](https://arxiv.org/pdf/2306.12156.pdf)\n', + '[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/yHNPyqazYYU)\n', + '[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-fastsam)\n', + '\n', + 'The Fast Segment Anything Model (FastSAM) is a CNN Segment Anything Model trained by only 2% of the SA-1B dataset published by SAM authors.\n', + '\n', + '![fast-segment-anything-model-figure-1](https://media.roboflow.com/notebooks/examples/fast-sam-figure-1.png)\n', + '\n', + 'The FastSAM authors claim it achieves comparable performance to the SAM method at 50 times the speed.\n', + '\n', + '![fast-segment-anything-model-figure-2](https://media.roboflow.com/notebooks/examples/fast-sam-figure-2.png)\n', + '\n', + '## Complementary Materials Covering SAM\n', + '\n', + '---\n', + '\n', + '[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/facebookresearch/segment-anything)\n', + '[![arXiv](https://img.shields.io/badge/arXiv-2304.02643-b31b1b.svg)](https://arxiv.org/abs/2304.02643)\n', + '[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-segment-anything-with-sam.ipynb)\n', + '[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/D-D6ZmadzPE)\n', + '[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-segment-anything-model-sam)\n', + '\n', + '## Steps in this Tutorial\n', + '\n', + 'In this tutorial, we are going to cover:\n', + '\n', + '- Before you start\n', + '- Install FastSAM, SAM, and other dependencies\n', + '- Download FastSAM and SAM weights\n', + '- Download example data\n', + '- Imports\n', + '- Load FastSAM\n', + '- FastSAM inference\n', + '- FastSAM box prompt inference\n', + '- FastSAM point prompt inference\n', + '- FastSAM text prompt inference\n', + '- SAM vs FastSAM\n', + '- Roboflow benchmark dataset\n', + '\n', + `## 🔥 Let's begin!` + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': 'W4u6KjiVdNZM' + }, + 'source': [ + '## Before you start\n', + '\n', + 'Let\'s make sure that we have access to GPU. We can use `nvidia-smi` command to do that. In case of any problems navigate to `Edit` -> `Notebook settings` -> `Hardware accelerator`, set it to `GPU`, and then click `Save`.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "Frcrk09FhJeV", - "outputId": "683a378e-4a8f-4239-901b-037279e92e2b" + 'id': 'Frcrk09FhJeV', + 'outputId': '683a378e-4a8f-4239-901b-037279e92e2b' }, - "outputs": [], - "source": [ - "!nvidia-smi" + 'outputs': [], + 'source': [ + '!nvidia-smi' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "PUACTM2zdmJI" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'PUACTM2zdmJI' }, - "source": [ - "**NOTE:** To make it easier for us to manage datasets, images and models we create a `HOME` constant." + 'source': [ + '**NOTE:** To make it easier for us to manage datasets, images and models we create a `HOME` constant.' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "PRtHQpTNdnm7", - "outputId": "2824c70d-dcf9-4006-b32d-adc6797fd708" + 'id': 'PRtHQpTNdnm7', + 'outputId': '2824c70d-dcf9-4006-b32d-adc6797fd708' }, - "outputs": [], - "source": [ - "import os\n", - "HOME = os.getcwd()\n", - "print(\"HOME:\", HOME)" + 'outputs': [], + 'source': [ + 'import os\n', + 'HOME = os.getcwd()\n', + 'print("HOME:", HOME)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "YN3DPGZSn57p" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'YN3DPGZSn57p' }, - "source": [ - "## Install FastSAM, SAM, and other dependencies" + 'source': [ + '## Install FastSAM, SAM, and other dependencies' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "Vr3kcq1KfFir", - "outputId": "234e5336-ba28-4114-d9e7-efd7f2752ca8" + 'id': 'Vr3kcq1KfFir', + 'outputId': '234e5336-ba28-4114-d9e7-efd7f2752ca8' }, - "outputs": [], - "source": [ - "%cd {HOME}\n", - "\n", - "# install FastSAM\n", - "!git clone https://github.com/CASIA-IVA-Lab/FastSAM.git\n", - "!pip -q install -r FastSAM/requirements.txt\n", - "# install CLIP\n", - "!pip -q install git+https://github.com/openai/CLIP.git\n", - "# install SAM\n", - "!pip -q install git+https://github.com/facebookresearch/segment-anything.git\n", - "# install other dependencies\n", - "!pip -q install roboflow supervision jupyter_bbox_widget" + 'outputs': [], + 'source': [ + '%cd {HOME}\n', + '\n', + '# install FastSAM\n', + '!git clone https://github.com/CASIA-IVA-Lab/FastSAM.git\n', + '!pip -q install -r FastSAM/requirements.txt\n', + '# install CLIP\n', + '!pip -q install git+https://github.com/openai/CLIP.git\n', + '# install SAM\n', + '!pip -q install git+https://github.com/facebookresearch/segment-anything.git\n', + '# install other dependencies\n', + '!pip -q install roboflow supervision jupyter_bbox_widget' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "BMmgSfDWfFis" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'BMmgSfDWfFis' }, - "source": [ - "## Download FastSAM and SAM weights" + 'source': [ + '## Download FastSAM and SAM weights' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "ADpOBElSfFis", - "outputId": "b3d89d4c-e2ad-4f5e-a910-b52695263151" + 'id': 'ADpOBElSfFis', + 'outputId': 'b3d89d4c-e2ad-4f5e-a910-b52695263151' }, - "outputs": [], - "source": [ - "!mkdir -p {HOME}/weights\n", - "!wget -P {HOME}/weights -q https://huggingface.co/spaces/An-619/FastSAM/resolve/main/weights/FastSAM.pt\n", - "!wget -P {HOME}/weights -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\n", - "!ls -lh {HOME}/weights" + 'outputs': [], + 'source': [ + '!mkdir -p {HOME}/weights\n', + '!wget -P {HOME}/weights -q https://huggingface.co/spaces/An-619/FastSAM/resolve/main/weights/FastSAM.pt\n', + '!wget -P {HOME}/weights -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\n', + '!ls -lh {HOME}/weights' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "W6w1ck4z72sn" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'W6w1ck4z72sn' }, - "outputs": [], - "source": [ - "FAST_SAM_CHECKPOINT_PATH = f\"{HOME}/weights/FastSAM.pt\"\n", - "SAM_SAM_CHECKPOINT_PATH = f\"{HOME}/weights/sam_vit_h_4b8939.pth\"" + 'outputs': [], + 'source': [ + 'FAST_SAM_CHECKPOINT_PATH = f"{HOME}/weights/FastSAM.pt"\n', + 'SAM_SAM_CHECKPOINT_PATH = f"{HOME}/weights/sam_vit_h_4b8939.pth"' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "t1ef2h1R9WMb" + 'cell_type': 'markdown', + 'metadata': { + 'id': 't1ef2h1R9WMb' }, - "source": [ - "## Download example data\n", - "\n", - "**NONE:** Let's download few example images. Feel free to use your images or videos." + 'source': [ + '## Download example data\n', + '\n', + `**NONE:** Let's download few example images. Feel free to use your images or videos.` ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "CL4gVrnl9dmR", - "outputId": "db8f78d2-eaf4-41de-9d1f-e5fd6b588a87" + 'id': 'CL4gVrnl9dmR', + 'outputId': 'db8f78d2-eaf4-41de-9d1f-e5fd6b588a87' }, - "outputs": [], - "source": [ - "!mkdir -p {HOME}/data\n", - "!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/dog.jpeg\n", - "!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/robot.jpeg\n", - "!ls -lh {HOME}/data" + 'outputs': [], + 'source': [ + '!mkdir -p {HOME}/data\n', + '!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/dog.jpeg\n', + '!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/robot.jpeg\n', + '!ls -lh {HOME}/data' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "J_a_icikYiVN" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'J_a_icikYiVN' }, - "source": [ - "## Imports\n", - "\n", - "**NOTE:** `FastSAM` code is not distributed via `pip` not it is packaged. Make sure to run code below from `{HOME}/FastSAM` directory. ⚠️" + 'source': [ + '## Imports\n', + '\n', + '**NOTE:** `FastSAM` code is not distributed via `pip` not it is packaged. Make sure to run code below from `{HOME}/FastSAM` directory. ⚠️' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "u_NVTXOwYppS", - "outputId": "b86184de-5827-4b9c-eb5c-959539abe699" + 'id': 'u_NVTXOwYppS', + 'outputId': 'b86184de-5827-4b9c-eb5c-959539abe699' }, - "outputs": [], - "source": [ - "%cd {HOME}/FastSAM\n", - "\n", - "import os\n", - "import cv2\n", - "import torch\n", - "import roboflow\n", - "import base64\n", - "\n", - "import supervision as sv\n", - "import numpy as np\n", - "\n", - "from roboflow import Roboflow\n", - "from fastsam import FastSAM, FastSAMPrompt\n", - "from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor" + 'outputs': [], + 'source': [ + '%cd {HOME}/FastSAM\n', + '\n', + 'import os\n', + 'import cv2\n', + 'import torch\n', + 'import roboflow\n', + 'import base64\n', + '\n', + 'import supervision as sv\n', + 'import numpy as np\n', + '\n', + 'from roboflow import Roboflow\n', + 'from fastsam import FastSAM, FastSAMPrompt\n', + 'from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "a_Iizsy07pb7" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'a_Iizsy07pb7' }, - "source": [ - "## Load FastSAM" + 'source': [ + '## Load FastSAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ao8tVDVq7ebG" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'ao8tVDVq7ebG' }, - "outputs": [], - "source": [ - "DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n", - "print(f\"DEVICE = {DEVICE}\")\n", - "fast_sam = FastSAM(FAST_SAM_CHECKPOINT_PATH)" + 'outputs': [], + 'source': [ + `DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n`, + 'print(f"DEVICE = {DEVICE}")\n', + 'fast_sam = FastSAM(FAST_SAM_CHECKPOINT_PATH)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "SvsRFtw_--Nn" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'SvsRFtw_--Nn' }, - "source": [ - "## FastSAM inference\n", - "\n", - "* `retina_masks=True` determines whether the model uses retina masks for generating segmentation masks.\n", - "* `imgsz=1024` sets the input image size to 1024x1024 pixels for processing by the model.\n", - "* `conf=0.4` sets the minimum confidence threshold for object detection.\n", - "* `iou=0.9` sets the minimum intersection over union threshold for non-maximum suppression to filter out duplicate detections." + 'source': [ + '## FastSAM inference\n', + '\n', + '* `retina_masks=True` determines whether the model uses retina masks for generating segmentation masks.\n', + '* `imgsz=1024` sets the input image size to 1024x1024 pixels for processing by the model.\n', + '* `conf=0.4` sets the minimum confidence threshold for object detection.\n', + '* `iou=0.9` sets the minimum intersection over union threshold for non-maximum suppression to filter out duplicate detections.' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "L49P5e-0Iaea" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'L49P5e-0Iaea' }, - "outputs": [], - "source": [ - "IMAGE_PATH = f\"{HOME}/data/dog.jpeg\"" + 'outputs': [], + 'source': [ + 'IMAGE_PATH = f"{HOME}/data/dog.jpeg"' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "os.makedirs(f\"{HOME}/output\", exist_ok=True)\n", - "output_image_path = f\"{HOME}/output/output-{os.path.basename(IMAGE_PATH)}\"" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'os.makedirs(f"{HOME}/output", exist_ok=True)\n', + 'output_image_path = f"{HOME}/output/output-{os.path.basename(IMAGE_PATH)}"' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "KV4XzCg1_fAS", - "outputId": "1df75d90-cf73-4d78-feed-01911a81f6e4" - }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.4,\n", - " iou=0.9)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()\n", - "prompt_process.plot(annotations=masks, output=f\"{HOME}/output\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uPrxqbaBOBMw" - }, - "source": [ - "**NOTE:** `prompt_process.everything_prompt` returns `torch.Tensor` when DEVICE = 'cuda:0'. `prompt_process.everything_prompt` returns `numpy.ndarray` when DEVICE = 'cpu'." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Convert masks to boolean (True/False)\n", - "def masks_to_bool(masks):\n", - " if type(masks) == np.ndarray:\n", - " masks = masks.astype(bool)\n", - " else:\n", - " masks = masks.cpu().numpy().astype(bool)\n", - " return masks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PUJkojNiKBQa" - }, - "outputs": [], - "source": [ - "def annotate_image(image_path: str, masks: np.ndarray) -> np.ndarray:\n", - " image = cv2.imread(image_path)\n", - "\n", - " xyxy = sv.mask_to_xyxy(masks=masks)\n", - " detections = sv.Detections(xyxy=xyxy, mask=masks)\n", - "\n", - " mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n", - " return mask_annotator.annotate(scene=image.copy(), detections=detections)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'id': 'KV4XzCg1_fAS', + 'outputId': '1df75d90-cf73-4d78-feed-01911a81f6e4' + }, + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.4,\n', + ' iou=0.9)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()\n', + 'prompt_process.plot(annotations=masks, output=f"{HOME}/output")' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': 'uPrxqbaBOBMw' + }, + 'source': [ + '**NOTE:** `prompt_process.everything_prompt` returns `torch.Tensor` when DEVICE = \'cuda:0\'. `prompt_process.everything_prompt` returns `numpy.ndarray` when DEVICE = \'cpu\'.' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + '# Convert masks to boolean (True/False)\n', + 'def masks_to_bool(masks):\n', + ' if type(masks) == np.ndarray:\n', + ' masks = masks.astype(bool)\n', + ' else:\n', + ' masks = masks.cpu().numpy().astype(bool)\n', + ' return masks' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'PUJkojNiKBQa' + }, + 'outputs': [], + 'source': [ + 'def annotate_image(image_path: str, masks: np.ndarray) -> np.ndarray:\n', + ' image = cv2.imread(image_path)\n', + '\n', + ' xyxy = sv.mask_to_xyxy(masks=masks)\n', + ' detections = sv.Detections(xyxy=xyxy, mask=masks)\n', + '\n', + ' mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n', + ' return mask_annotator.annotate(scene=image.copy(), detections=detections)' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "wwtmiH6pLA8N", - "outputId": "17948850-f051-4a1f-b2b8-d01c2a1f4f20" + 'id': 'wwtmiH6pLA8N', + 'outputId': '17948850-f051-4a1f-b2b8-d01c2a1f4f20' }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "jYkdczKoRUVB" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'jYkdczKoRUVB' }, - "source": [ - "**NOTE:** The quality of the generated masks is quite poor. Let's see if we can improve it by manipulating the parameters." + 'source': [ + `**NOTE:** The quality of the generated masks is quite poor. Let's see if we can improve it by manipulating the parameters.` ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "dDFGi6EhO2ul", - "outputId": "f5a03357-eb37-446e-dce7-0659e4c2a368" - }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'id': 'dDFGi6EhO2ul', + 'outputId': 'f5a03357-eb37-446e-dce7-0659e4c2a368' + }, + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "OiBPjQW-O59c", - "outputId": "afb011fc-933e-4d69-b4dd-e5e2713f407b" + 'id': 'OiBPjQW-O59c', + 'outputId': 'afb011fc-933e-4d69-b4dd-e5e2713f407b' }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "GoymBQ16KJbv" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'GoymBQ16KJbv' }, - "source": [ - "## FastSAM box prompt inference" + 'source': [ + '## FastSAM box prompt inference' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "w1_T5SXpMNku" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'w1_T5SXpMNku' }, - "source": [ - "### Draw Box" + 'source': [ + '### Draw Box' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "04-1G1WrMJUm" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': '04-1G1WrMJUm' }, - "outputs": [], - "source": [ - "# helper function that loads an image before adding it to the widget\n", - "\n", - "def encode_image(filepath):\n", - " with open(filepath, 'rb') as f:\n", - " image_bytes = f.read()\n", - " encoded = str(base64.b64encode(image_bytes), 'utf-8')\n", - " return \"data:image/jpg;base64,\"+encoded" + 'outputs': [], + 'source': [ + '# helper function that loads an image before adding it to the widget\n', + '\n', + 'def encode_image(filepath):\n', + ` with open(filepath, 'rb') as f:\n`, + ' image_bytes = f.read()\n', + ` encoded = str(base64.b64encode(image_bytes), 'utf-8')\n`, + ' return "data:image/jpg;base64,"+encoded' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "xrxd-Ug9MSWG" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'xrxd-Ug9MSWG' }, - "source": [ - "**NOTE:** Execute cell below and use your mouse to draw bounding box on the image 👇" + 'source': [ + '**NOTE:** Execute cell below and use your mouse to draw bounding box on the image 👇' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000, - "referenced_widgets": [ - "a747e095e21448f29d8697fb6b1a7a74", - "6401ca8c2f574ba5ab62f0f06b9e0d06" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 1000, + 'referenced_widgets': [ + 'a747e095e21448f29d8697fb6b1a7a74', + '6401ca8c2f574ba5ab62f0f06b9e0d06' ] }, - "id": "nLA7xmDIMTfx", - "outputId": "06b51195-d54f-44ed-9236-258e94d624c5" - }, - "outputs": [], - "source": [ - "IS_COLAB = True\n", - "\n", - "if IS_COLAB:\n", - " from google.colab import output\n", - " output.enable_custom_widget_manager()\n", - "\n", - "from jupyter_bbox_widget import BBoxWidget\n", - "\n", - "widget = BBoxWidget()\n", - "widget.image = encode_image(IMAGE_PATH)\n", - "widget" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'id': 'nLA7xmDIMTfx', + 'outputId': '06b51195-d54f-44ed-9236-258e94d624c5' + }, + 'outputs': [], + 'source': [ + 'IS_COLAB = True\n', + '\n', + 'if IS_COLAB:\n', + ' from google.colab import output\n', + ' output.enable_custom_widget_manager()\n', + '\n', + 'from jupyter_bbox_widget import BBoxWidget\n', + '\n', + 'widget = BBoxWidget()\n', + 'widget.image = encode_image(IMAGE_PATH)\n', + 'widget' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "td6tYT4INOyD", - "outputId": "73a4e886-dcb8-447c-d3f7-1b3a6496e44d" + 'id': 'td6tYT4INOyD', + 'outputId': '73a4e886-dcb8-447c-d3f7-1b3a6496e44d' }, - "outputs": [], - "source": [ - "widget.bboxes" + 'outputs': [], + 'source': [ + 'widget.bboxes' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "XfUvm9bwNUAe" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'XfUvm9bwNUAe' }, - "source": [ - "### Generate mask with FastSAM" + 'source': [ + '### Generate mask with FastSAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bI6kPDSROBI-" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'bI6kPDSROBI-' }, - "outputs": [], - "source": [ - "# default_box is going to be used if you will not draw any box on image above\n", - "default_box = {'x': 68, 'y': 247, 'width': 555, 'height': 678, 'label': ''}\n", - "\n", - "box = widget.bboxes[0] if widget.bboxes else default_box\n", - "box = [\n", - " box['x'],\n", - " box['y'],\n", - " box['x'] + box['width'],\n", - " box['y'] + box['height']\n", - "]" + 'outputs': [], + 'source': [ + '# default_box is going to be used if you will not draw any box on image above\n', + `default_box = {'x': 68, 'y': 247, 'width': 555, 'height': 678, 'label': ''}\n`, + '\n', + 'box = widget.bboxes[0] if widget.bboxes else default_box\n', + 'box = [\n', + ` box['x'],\n`, + ` box['y'],\n`, + ` box['x'] + box['width'],\n`, + ` box['y'] + box['height']\n`, + ']' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "LF879hDqO5iB", - "outputId": "4e7498c4-4007-4614-d46c-0e35608a04a2" + 'id': 'LF879hDqO5iB', + 'outputId': '4e7498c4-4007-4614-d46c-0e35608a04a2' }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.box_prompt(bbox=box)" + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.box_prompt(bbox=box)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "yt6i2sNJPEdk" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'yt6i2sNJPEdk' }, - "source": [ - "**NOTE:** `prompt_process.box_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.box_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "EJThTct_PA68", - "outputId": "f6eddd24-f603-4702-8325-6cff7925fe0f" + 'id': 'EJThTct_PA68', + 'outputId': 'f6eddd24-f603-4702-8325-6cff7925fe0f' }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "vR-THJthhWH5" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'vR-THJthhWH5' }, - "source": [ - "## FastSAM point prompt inference" + 'source': [ + '## FastSAM point prompt inference' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "xyfpOPPwhpMy" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'xyfpOPPwhpMy' }, - "source": [ - "### Select point" + 'source': [ + '### Select point' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "OC7d3pZ3hop9" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'OC7d3pZ3hop9' }, - "outputs": [], - "source": [ - "point = [405, 505]" + 'outputs': [], + 'source': [ + 'point = [405, 505]' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "rE5tV4e3h6nG" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'rE5tV4e3h6nG' }, - "source": [ - "### Generate mask with FastSAM" + 'source': [ + '### Generate mask with FastSAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "31FIby6Zh-Wa", - "outputId": "a12c4d1c-837b-4b1b-9240-664797442413" + 'id': '31FIby6Zh-Wa', + 'outputId': 'a12c4d1c-837b-4b1b-9240-664797442413' }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.point_prompt(points=[point], pointlabel=[1])" + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.point_prompt(points=[point], pointlabel=[1])' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "d7tpVo8CiM3s" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'd7tpVo8CiM3s' }, - "source": [ - "**NOTE:** `prompt_process.point_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.point_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "lMVpGxShiPky", - "outputId": "224d84ee-e664-400b-c0df-49507d229f62" + 'id': 'lMVpGxShiPky', + 'outputId': '224d84ee-e664-400b-c0df-49507d229f62' }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "HOC7v6ATHEr6" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'HOC7v6ATHEr6' }, - "source": [ - "## FastSAM text prompt inference" + 'source': [ + '## FastSAM text prompt inference' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "4CzLLtXOHeGk", - "outputId": "cecc50d8-e2be-4b33-850a-a2442e3e30b6" + 'id': '4CzLLtXOHeGk', + 'outputId': 'cecc50d8-e2be-4b33-850a-a2442e3e30b6' }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.text_prompt(text='cap')" + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + `masks = prompt_process.text_prompt(text='cap')` ] }, { - "cell_type": "markdown", - "metadata": { - "id": "KYW79zD0OKsv" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'KYW79zD0OKsv' }, - "source": [ - "**NOTE:** `prompt_process.text_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.text_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "zODY5xU1LMCb", - "outputId": "a01540a7-deb3-4555-916c-2ac59928e0f3" + 'id': 'zODY5xU1LMCb', + 'outputId': 'a01540a7-deb3-4555-916c-2ac59928e0f3' }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "XFOUWUMZEk7m" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'XFOUWUMZEk7m' }, - "source": [ - "## SAM vs FastSAM" + 'source': [ + '## SAM vs FastSAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "7PKBZ7Hhahhw" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': '7PKBZ7Hhahhw' }, - "outputs": [], - "source": [ - "IMAGE_PATH = f\"{HOME}/data/robot.jpeg\"" + 'outputs': [], + 'source': [ + 'IMAGE_PATH = f"{HOME}/data/robot.jpeg"' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "c48hRY7dVJeL" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'c48hRY7dVJeL' }, - "source": [ - "### Load SAM" + 'source': [ + '### Load SAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PdKpFCCmaETG" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'PdKpFCCmaETG' }, - "outputs": [], - "source": [ - "MODEL_TYPE = \"vit_h\"\n", - "\n", - "sam = sam_model_registry[MODEL_TYPE](checkpoint=SAM_SAM_CHECKPOINT_PATH).to(device=DEVICE)\n", - "mask_generator = SamAutomaticMaskGenerator(sam)" + 'outputs': [], + 'source': [ + 'MODEL_TYPE = "vit_h"\n', + '\n', + 'sam = sam_model_registry[MODEL_TYPE](checkpoint=SAM_SAM_CHECKPOINT_PATH).to(device=DEVICE)\n', + 'mask_generator = SamAutomaticMaskGenerator(sam)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "V5bPGvtBaewO" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'V5bPGvtBaewO' }, - "source": [ - "### SAM inference" + 'source': [ + '### SAM inference' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0HgWsk1yalow" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': '0HgWsk1yalow' }, - "outputs": [], - "source": [ - "image_bgr = cv2.imread(IMAGE_PATH)\n", - "image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)\n", - "\n", - "sam_result = mask_generator.generate(image_rgb)\n", - "sam_detections = sv.Detections.from_sam(sam_result=sam_result)" + 'outputs': [], + 'source': [ + 'image_bgr = cv2.imread(IMAGE_PATH)\n', + 'image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)\n', + '\n', + 'sam_result = mask_generator.generate(image_rgb)\n', + 'sam_detections = sv.Detections.from_sam(sam_result=sam_result)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "t03RrSzMasN6" + 'cell_type': 'markdown', + 'metadata': { + 'id': 't03RrSzMasN6' }, - "source": [ - "### FastSAM inference" + 'source': [ + '### FastSAM inference' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "xuFGLw8MavQI", - "outputId": "b0118973-1e29-419d-b7c0-2933b97d7ce5" + 'id': 'xuFGLw8MavQI', + 'outputId': 'b0118973-1e29-419d-b7c0-2933b97d7ce5' }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()\n", - "masks = masks_to_bool(masks)\n", - "xyxy = sv.mask_to_xyxy(masks=masks)\n", - "fast_sam_detections = sv.Detections(xyxy=xyxy, mask=masks)" + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()\n', + 'masks = masks_to_bool(masks)\n', + 'xyxy = sv.mask_to_xyxy(masks=masks)\n', + 'fast_sam_detections = sv.Detections(xyxy=xyxy, mask=masks)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "IYn8xKySbBnc" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'IYn8xKySbBnc' }, - "source": [ - "### FastSAM vs. SAM" + 'source': [ + '### FastSAM vs. SAM' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 321 }, - "id": "c2Qj7sLjbDvo", - "outputId": "e85e3ed3-8b90-409d-cf95-877a6a9b2034" + 'id': 'c2Qj7sLjbDvo', + 'outputId': 'e85e3ed3-8b90-409d-cf95-877a6a9b2034' }, - "outputs": [], - "source": [ - "mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n", - "\n", - "sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=sam_detections)\n", - "fast_sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=fast_sam_detections)\n", - "\n", - "sv.plot_images_grid(\n", - " images=[sam_result, fast_sam_result],\n", - " grid_size=(1, 2),\n", - " titles=['SAM', 'FastSAM']\n", - ")" + 'outputs': [], + 'source': [ + 'mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n', + '\n', + 'sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=sam_detections)\n', + 'fast_sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=fast_sam_detections)\n', + '\n', + 'sv.plot_images_grid(\n', + ' images=[sam_result, fast_sam_result],\n', + ' grid_size=(1, 2),\n', + ` titles=['SAM', 'FastSAM']\n`, + ')' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "MfU4NQiGWQMC" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'MfU4NQiGWQMC' }, - "source": [ - "**NOTE:** There is a lot going on in our test image. Let's plot our masks against a blank background." + 'source': [ + `**NOTE:** There is a lot going on in our test image. Let's plot our masks against a blank background.` ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 321 }, - "id": "PIrywlF5QRPz", - "outputId": "8e7c84ae-7869-4523-847a-0678fd1aea53" - }, - "outputs": [], - "source": [ - "mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n", - "\n", - "sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=sam_detections)\n", - "fast_sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=fast_sam_detections)\n", - "\n", - "sv.plot_images_grid(\n", - " images=[sam_result, fast_sam_result],\n", - " grid_size=(1, 2),\n", - " titles=['SAM', 'FastSAM']\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "51Fs3sDGQmIt" - }, - "source": [ - "### Mask diff\n", - "\n", - "**NOTE:** Let's try to understand which masks generated by SAM were not generated by FastSAM." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "O5bIUAhYQrqZ" - }, - "outputs": [], - "source": [ - "def compute_iou(mask1, mask2):\n", - " intersection = np.logical_and(mask1, mask2)\n", - " union = np.logical_or(mask1, mask2)\n", - " return np.sum(intersection) / np.sum(union)\n", - "\n", - "def filter_masks(mask_array1, mask_array2, threshold):\n", - " return np.array([mask1 for mask1 in mask_array1 if max(compute_iou(mask1, mask2) for mask2 in mask_array2) < threshold])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 420 + 'id': 'PIrywlF5QRPz', + 'outputId': '8e7c84ae-7869-4523-847a-0678fd1aea53' + }, + 'outputs': [], + 'source': [ + 'mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n', + '\n', + 'sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=sam_detections)\n', + 'fast_sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=fast_sam_detections)\n', + '\n', + 'sv.plot_images_grid(\n', + ' images=[sam_result, fast_sam_result],\n', + ' grid_size=(1, 2),\n', + ` titles=['SAM', 'FastSAM']\n`, + ')' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': '51Fs3sDGQmIt' + }, + 'source': [ + '### Mask diff\n', + '\n', + `**NOTE:** Let's try to understand which masks generated by SAM were not generated by FastSAM.` + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'O5bIUAhYQrqZ' + }, + 'outputs': [], + 'source': [ + 'def compute_iou(mask1, mask2):\n', + ' intersection = np.logical_and(mask1, mask2)\n', + ' union = np.logical_or(mask1, mask2)\n', + ' return np.sum(intersection) / np.sum(union)\n', + '\n', + 'def filter_masks(mask_array1, mask_array2, threshold):\n', + ' return np.array([mask1 for mask1 in mask_array1 if max(compute_iou(mask1, mask2) for mask2 in mask_array2) < threshold])' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 420 }, - "id": "1GM4bL9sRbOm", - "outputId": "421fb4f5-7a42-461b-ba1e-7cd46d2d7bd7" + 'id': '1GM4bL9sRbOm', + 'outputId': '421fb4f5-7a42-461b-ba1e-7cd46d2d7bd7' }, - "outputs": [], - "source": [ - "diff_masks = filter_masks(sam_detections.mask, fast_sam_detections.mask, 0.5)\n", - "diff_xyxy = sv.mask_to_xyxy(masks=diff_masks)\n", - "diff_detections = sv.Detections(xyxy=diff_xyxy, mask=diff_masks)\n", - "\n", - "mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n", - "diff_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=diff_detections)\n", - "sv.plot_image(image=diff_result, size=(8, 8))" + 'outputs': [], + 'source': [ + 'diff_masks = filter_masks(sam_detections.mask, fast_sam_detections.mask, 0.5)\n', + 'diff_xyxy = sv.mask_to_xyxy(masks=diff_masks)\n', + 'diff_detections = sv.Detections(xyxy=diff_xyxy, mask=diff_masks)\n', + '\n', + 'mask_annotator = sv.MaskAnnotator(color_lookup = sv.ColorLookup.INDEX)\n', + 'diff_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=diff_detections)\n', + 'sv.plot_image(image=diff_result, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "0lPZAZZulhF8" + 'cell_type': 'markdown', + 'metadata': { + 'id': '0lPZAZZulhF8' }, - "source": [ - "## Roboflow Benchmark Dataset" + 'source': [ + '## Roboflow Benchmark Dataset' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "LWN6XwPuSZBY", - "outputId": "86b18c0e-f6d6-486b-eee6-d004f13f531e" + 'id': 'LWN6XwPuSZBY', + 'outputId': '86b18c0e-f6d6-486b-eee6-d004f13f531e' }, - "outputs": [], - "source": [ - "%cd {HOME}\n", - "\n", - "roboflow.login()\n", - "rf = Roboflow()\n", - "\n", - "project = rf.workspace(\"roboticfish\").project(\"underwater_object_detection\")\n", - "dataset = project.version(10).download(\"yolov8\")" + 'outputs': [], + 'source': [ + '%cd {HOME}\n', + '\n', + 'roboflow.login()\n', + 'rf = Roboflow()\n', + '\n', + 'project = rf.workspace("roboticfish").project("underwater_object_detection")\n', + 'dataset = project.version(10).download("yolov8")' ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "T6-PaxWXTZ69" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'id': 'T6-PaxWXTZ69' }, - "outputs": [], - "source": [ - "IMAGE_PATH = os.path.join(dataset.location, 'train', 'images', 'IMG_2311_jpeg_jpg.rf.09ae6820eaff21dc838b1f9b6b20342b.jpg')" + 'outputs': [], + 'source': [ + `IMAGE_PATH = os.path.join(dataset.location, 'train', 'images', 'IMG_2311_jpeg_jpg.rf.09ae6820eaff21dc838b1f9b6b20342b.jpg')` ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "p-gY5jf1Tife", - "outputId": "630d35d8-26f4-4342-8d82-c7b61be26fe8" - }, - "outputs": [], - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.text_prompt(text='Penguin')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'id': 'p-gY5jf1Tife', + 'outputId': '630d35d8-26f4-4342-8d82-c7b61be26fe8' + }, + 'outputs': [], + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + `masks = prompt_process.text_prompt(text='Penguin')` + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "3tBIH0yubHsW", - "outputId": "fc4d0a6a-53c1-4a2b-91f0-75c8f78e10fd" - }, - "outputs": [], - "source": [ - "masks = masks_to_bool(masks)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gP9gEw9Tp8GJ" - }, - "source": [ - "## 🏆 Congratulations\n", - "\n", - "### Learning Resources\n", - "\n", - "Roboflow has produced many resources that you may find interesting as you advance your knowledge of computer vision:\n", - "\n", - "- [Roboflow Notebooks](https://github.com/roboflow/notebooks): A repository of over 20 notebooks that walk through how to train custom models with a range of model types, from YOLOv7 to SegFormer.\n", - "- [Roboflow YouTube](https://www.youtube.com/c/Roboflow): Our library of videos featuring deep dives into the latest in computer vision, detailed tutorials that accompany our notebooks, and more.\n", - "- [Roboflow Discuss](https://discuss.roboflow.com/): Have a question about how to do something on Roboflow? Ask your question on our discussion forum.\n", - "- [Roboflow Models](https://roboflow.com): Learn about state-of-the-art models and their performance. Find links and tutorials to guide your learning.\n", - "\n", - "### Convert data formats\n", - "\n", - "Roboflow provides free utilities to convert data between dozens of popular computer vision formats. Check out [Roboflow Formats](https://roboflow.com/formats) to find tutorials on how to convert data between formats in a few clicks.\n", - "\n", - "### Connect computer vision to your project logic\n", - "\n", - "[Roboflow Templates](https://roboflow.com/templates) is a public gallery of code snippets that you can use to connect computer vision to your project logic. Code snippets range from sending emails after inference to measuring object distance between detections." + 'id': '3tBIH0yubHsW', + 'outputId': 'fc4d0a6a-53c1-4a2b-91f0-75c8f78e10fd' + }, + 'outputs': [], + 'source': [ + 'masks = masks_to_bool(masks)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': 'gP9gEw9Tp8GJ' + }, + 'source': [ + '## 🏆 Congratulations\n', + '\n', + '### Learning Resources\n', + '\n', + 'Roboflow has produced many resources that you may find interesting as you advance your knowledge of computer vision:\n', + '\n', + '- [Roboflow Notebooks](https://github.com/roboflow/notebooks): A repository of over 20 notebooks that walk through how to train custom models with a range of model types, from YOLOv7 to SegFormer.\n', + '- [Roboflow YouTube](https://www.youtube.com/c/Roboflow): Our library of videos featuring deep dives into the latest in computer vision, detailed tutorials that accompany our notebooks, and more.\n', + '- [Roboflow Discuss](https://discuss.roboflow.com/): Have a question about how to do something on Roboflow? Ask your question on our discussion forum.\n', + '- [Roboflow Models](https://roboflow.com): Learn about state-of-the-art models and their performance. Find links and tutorials to guide your learning.\n', + '\n', + '### Convert data formats\n', + '\n', + 'Roboflow provides free utilities to convert data between dozens of popular computer vision formats. Check out [Roboflow Formats](https://roboflow.com/formats) to find tutorials on how to convert data between formats in a few clicks.\n', + '\n', + '### Connect computer vision to your project logic\n', + '\n', + '[Roboflow Templates](https://roboflow.com/templates) is a public gallery of code snippets that you can use to connect computer vision to your project logic. Code snippets range from sending emails after inference to measuring object distance between detections.' ] } ] @@ -13237,982 +13237,982 @@ suite('NotebookDiff Diff Service', () => { , [ { - "cell_type": "markdown", - "metadata": { - "id": "F0Wut2G5kz2Z" - }, - "source": [ - "[![Roboflow Notebooks](https://ik.imagekit.io/roboflow/notebooks/template/bannertest2-2.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672932710194)](https://github.com/roboflow/notebooks)\n", - "\n", - "# Fast Segment Anything Model (FastSAM)\n", - "\n", - "---\n", - "\n", - "[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/CASIA-IVA-Lab/FastSAM)\n", - "[![arXiv](https://img.shields.io/badge/arXiv-2306.12156-b31b1b.svg)](https://arxiv.org/pdf/2306.12156.pdf)\n", - "[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/yHNPyqazYYU)\n", - "[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-fastsam)\n", - "\n", - "The Fast Segment Anything Model (FastSAM) is a CNN Segment Anything Model trained by only 2% of the SA-1B dataset published by SAM authors.\n", - "\n", - "![fast-segment-anything-model-figure-1](https://media.roboflow.com/notebooks/examples/fast-sam-figure-1.png)\n", - "\n", - "The FastSAM authors claim it achieves comparable performance to the SAM method at 50 times the speed.\n", - "\n", - "![fast-segment-anything-model-figure-2](https://media.roboflow.com/notebooks/examples/fast-sam-figure-2.png)\n", - "\n", - "## Complementary Materials Covering SAM\n", - "\n", - "---\n", - "\n", - "[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/facebookresearch/segment-anything)\n", - "[![arXiv](https://img.shields.io/badge/arXiv-2304.02643-b31b1b.svg)](https://arxiv.org/abs/2304.02643)\n", - "[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-segment-anything-with-sam.ipynb)\n", - "[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/D-D6ZmadzPE)\n", - "[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-segment-anything-model-sam)\n", - "\n", - "## Steps in this Tutorial\n", - "\n", - "In this tutorial, we are going to cover:\n", - "\n", - "- Before you start\n", - "- Install FastSAM, SAM, and other dependencies\n", - "- Download FastSAM and SAM weights\n", - "- Download example data\n", - "- Imports\n", - "- Load FastSAM\n", - "- FastSAM inference\n", - "- FastSAM box prompt inference\n", - "- FastSAM point prompt inference\n", - "- FastSAM text prompt inference\n", - "- SAM vs FastSAM\n", - "- Roboflow benchmark dataset\n", - "\n", - "## 🔥 Let's begin!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "W4u6KjiVdNZM" - }, - "source": [ - "## Before you start\n", - "\n", - "Let's make sure that we have access to GPU. We can use `nvidia-smi` command to do that. In case of any problems navigate to `Edit` -> `Notebook settings` -> `Hardware accelerator`, set it to `GPU`, and then click `Save`." - ] - }, - { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'F0Wut2G5kz2Z' + }, + 'source': [ + '[![Roboflow Notebooks](https://ik.imagekit.io/roboflow/notebooks/template/bannertest2-2.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672932710194)](https://github.com/roboflow/notebooks)\n', + '\n', + '# Fast Segment Anything Model (FastSAM)\n', + '\n', + '---\n', + '\n', + '[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/CASIA-IVA-Lab/FastSAM)\n', + '[![arXiv](https://img.shields.io/badge/arXiv-2306.12156-b31b1b.svg)](https://arxiv.org/pdf/2306.12156.pdf)\n', + '[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/yHNPyqazYYU)\n', + '[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-fastsam)\n', + '\n', + 'The Fast Segment Anything Model (FastSAM) is a CNN Segment Anything Model trained by only 2% of the SA-1B dataset published by SAM authors.\n', + '\n', + '![fast-segment-anything-model-figure-1](https://media.roboflow.com/notebooks/examples/fast-sam-figure-1.png)\n', + '\n', + 'The FastSAM authors claim it achieves comparable performance to the SAM method at 50 times the speed.\n', + '\n', + '![fast-segment-anything-model-figure-2](https://media.roboflow.com/notebooks/examples/fast-sam-figure-2.png)\n', + '\n', + '## Complementary Materials Covering SAM\n', + '\n', + '---\n', + '\n', + '[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/facebookresearch/segment-anything)\n', + '[![arXiv](https://img.shields.io/badge/arXiv-2304.02643-b31b1b.svg)](https://arxiv.org/abs/2304.02643)\n', + '[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-segment-anything-with-sam.ipynb)\n', + '[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/D-D6ZmadzPE)\n', + '[![Roboflow](https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/roboflow-blogpost.svg)](https://blog.roboflow.com/how-to-use-segment-anything-model-sam)\n', + '\n', + '## Steps in this Tutorial\n', + '\n', + 'In this tutorial, we are going to cover:\n', + '\n', + '- Before you start\n', + '- Install FastSAM, SAM, and other dependencies\n', + '- Download FastSAM and SAM weights\n', + '- Download example data\n', + '- Imports\n', + '- Load FastSAM\n', + '- FastSAM inference\n', + '- FastSAM box prompt inference\n', + '- FastSAM point prompt inference\n', + '- FastSAM text prompt inference\n', + '- SAM vs FastSAM\n', + '- Roboflow benchmark dataset\n', + '\n', + `## 🔥 Let's begin!` + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': 'W4u6KjiVdNZM' + }, + 'source': [ + '## Before you start\n', + '\n', + 'Let\'s make sure that we have access to GPU. We can use `nvidia-smi` command to do that. In case of any problems navigate to `Edit` -> `Notebook settings` -> `Hardware accelerator`, set it to `GPU`, and then click `Save`.' + ] + }, + { + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "Frcrk09FhJeV", - "outputId": "683a378e-4a8f-4239-901b-037279e92e2b" + 'id': 'Frcrk09FhJeV', + 'outputId': '683a378e-4a8f-4239-901b-037279e92e2b' }, - "source": [ - "!nvidia-smi" + 'source': [ + '!nvidia-smi' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "PUACTM2zdmJI" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'PUACTM2zdmJI' }, - "source": [ - "**NOTE:** To make it easier for us to manage datasets, images and models we create a `HOME` constant." + 'source': [ + '**NOTE:** To make it easier for us to manage datasets, images and models we create a `HOME` constant.' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "PRtHQpTNdnm7", - "outputId": "2824c70d-dcf9-4006-b32d-adc6797fd708" + 'id': 'PRtHQpTNdnm7', + 'outputId': '2824c70d-dcf9-4006-b32d-adc6797fd708' }, - "source": [ - "import os\n", - "HOME = os.getcwd()\n", - "print(\"HOME:\", HOME)" + 'source': [ + 'import os\n', + 'HOME = os.getcwd()\n', + 'print("HOME:", HOME)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "YN3DPGZSn57p" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'YN3DPGZSn57p' }, - "source": [ - "## Install FastSAM, SAM, and other dependencies" + 'source': [ + '## Install FastSAM, SAM, and other dependencies' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "Vr3kcq1KfFir", - "outputId": "234e5336-ba28-4114-d9e7-efd7f2752ca8" + 'id': 'Vr3kcq1KfFir', + 'outputId': '234e5336-ba28-4114-d9e7-efd7f2752ca8' }, - "source": [ - "%cd {HOME}\n", - "\n", - "# install FastSAM\n", - "!git clone https://github.com/CASIA-IVA-Lab/FastSAM.git\n", - "!pip -q install -r FastSAM/requirements.txt\n", - "# install CLIP\n", - "!pip -q install git+https://github.com/openai/CLIP.git\n", - "# install SAM\n", - "!pip -q install git+https://github.com/facebookresearch/segment-anything.git\n", - "# install other dependencies\n", - "!pip -q install roboflow supervision jupyter_bbox_widget" + 'source': [ + '%cd {HOME}\n', + '\n', + '# install FastSAM\n', + '!git clone https://github.com/CASIA-IVA-Lab/FastSAM.git\n', + '!pip -q install -r FastSAM/requirements.txt\n', + '# install CLIP\n', + '!pip -q install git+https://github.com/openai/CLIP.git\n', + '# install SAM\n', + '!pip -q install git+https://github.com/facebookresearch/segment-anything.git\n', + '# install other dependencies\n', + '!pip -q install roboflow supervision jupyter_bbox_widget' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "BMmgSfDWfFis" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'BMmgSfDWfFis' }, - "source": [ - "## Download FastSAM and SAM weights" + 'source': [ + '## Download FastSAM and SAM weights' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "ADpOBElSfFis", - "outputId": "b3d89d4c-e2ad-4f5e-a910-b52695263151" + 'id': 'ADpOBElSfFis', + 'outputId': 'b3d89d4c-e2ad-4f5e-a910-b52695263151' }, - "source": [ - "!mkdir -p {HOME}/weights\n", - "!wget -P {HOME}/weights -q https://huggingface.co/spaces/An-619/FastSAM/resolve/main/weights/FastSAM.pt\n", - "!wget -P {HOME}/weights -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\n", - "!ls -lh {HOME}/weights" + 'source': [ + '!mkdir -p {HOME}/weights\n', + '!wget -P {HOME}/weights -q https://huggingface.co/spaces/An-619/FastSAM/resolve/main/weights/FastSAM.pt\n', + '!wget -P {HOME}/weights -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\n', + '!ls -lh {HOME}/weights' ] }, { - "cell_type": "code", - "metadata": { - "id": "W6w1ck4z72sn" + 'cell_type': 'code', + 'metadata': { + 'id': 'W6w1ck4z72sn' }, - "source": [ - "FAST_SAM_CHECKPOINT_PATH = f\"{HOME}/weights/FastSAM.pt\"\n", - "SAM_SAM_CHECKPOINT_PATH = f\"{HOME}/weights/sam_vit_h_4b8939.pth\"" + 'source': [ + 'FAST_SAM_CHECKPOINT_PATH = f"{HOME}/weights/FastSAM.pt"\n', + 'SAM_SAM_CHECKPOINT_PATH = f"{HOME}/weights/sam_vit_h_4b8939.pth"' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "t1ef2h1R9WMb" + 'cell_type': 'markdown', + 'metadata': { + 'id': 't1ef2h1R9WMb' }, - "source": [ - "## Download example data\n", - "\n", - "**NONE:** Let's download few example images. Feel free to use your images or videos." + 'source': [ + '## Download example data\n', + '\n', + `**NONE:** Let's download few example images. Feel free to use your images or videos.` ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "CL4gVrnl9dmR", - "outputId": "db8f78d2-eaf4-41de-9d1f-e5fd6b588a87" + 'id': 'CL4gVrnl9dmR', + 'outputId': 'db8f78d2-eaf4-41de-9d1f-e5fd6b588a87' }, - "source": [ - "!mkdir -p {HOME}/data\n", - "!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/dog.jpeg\n", - "!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/robot.jpeg\n", - "!ls -lh {HOME}/data" + 'source': [ + '!mkdir -p {HOME}/data\n', + '!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/dog.jpeg\n', + '!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/robot.jpeg\n', + '!ls -lh {HOME}/data' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "J_a_icikYiVN" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'J_a_icikYiVN' }, - "source": [ - "## Imports\n", - "\n", - "**NOTE:** `FastSAM` code is not distributed via `pip` not it is packaged. Make sure to run code balow from `{HOME}/FastSAM` directory. ⚠️" + 'source': [ + '## Imports\n', + '\n', + '**NOTE:** `FastSAM` code is not distributed via `pip` not it is packaged. Make sure to run code balow from `{HOME}/FastSAM` directory. ⚠️' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "u_NVTXOwYppS", - "outputId": "b86184de-5827-4b9c-eb5c-959539abe699" + 'id': 'u_NVTXOwYppS', + 'outputId': 'b86184de-5827-4b9c-eb5c-959539abe699' }, - "source": [ - "%cd {HOME}/FastSAM\n", - "\n", - "import os\n", - "import cv2\n", - "import torch\n", - "import roboflow\n", - "import base64\n", - "\n", - "import supervision as sv\n", - "import numpy as np\n", - "\n", - "from roboflow import Roboflow\n", - "from fastsam import FastSAM, FastSAMPrompt\n", - "from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor" + 'source': [ + '%cd {HOME}/FastSAM\n', + '\n', + 'import os\n', + 'import cv2\n', + 'import torch\n', + 'import roboflow\n', + 'import base64\n', + '\n', + 'import supervision as sv\n', + 'import numpy as np\n', + '\n', + 'from roboflow import Roboflow\n', + 'from fastsam import FastSAM, FastSAMPrompt\n', + 'from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "a_Iizsy07pb7" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'a_Iizsy07pb7' }, - "source": [ - "## Load FastSAM" + 'source': [ + '## Load FastSAM' ] }, { - "cell_type": "code", - "metadata": { - "id": "ao8tVDVq7ebG" + 'cell_type': 'code', + 'metadata': { + 'id': 'ao8tVDVq7ebG' }, - "source": [ - "DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n", - "\n", - "fast_sam = FastSAM(FAST_SAM_CHECKPOINT_PATH)" + 'source': [ + `DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n`, + '\n', + 'fast_sam = FastSAM(FAST_SAM_CHECKPOINT_PATH)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "SvsRFtw_--Nn" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'SvsRFtw_--Nn' }, - "source": [ - "## FastSAM inference\n", - "\n", - "* `retina_masks=True` determines whether the model uses retina masks for generating segmentation masks.\n", - "* `imgsz=1024` sets the input image size to 1024x1024 pixels for processing by the model.\n", - "* `conf=0.4` sets the minimum confidence threshold for object detection.\n", - "* `iou=0.9` sets the minimum intersection over union threshold for non-maximum suppression to filter out duplicate detections." + 'source': [ + '## FastSAM inference\n', + '\n', + '* `retina_masks=True` determines whether the model uses retina masks for generating segmentation masks.\n', + '* `imgsz=1024` sets the input image size to 1024x1024 pixels for processing by the model.\n', + '* `conf=0.4` sets the minimum confidence threshold for object detection.\n', + '* `iou=0.9` sets the minimum intersection over union threshold for non-maximum suppression to filter out duplicate detections.' ] }, { - "cell_type": "code", - "metadata": { - "id": "L49P5e-0Iaea" + 'cell_type': 'code', + 'metadata': { + 'id': 'L49P5e-0Iaea' }, - "source": [ - "IMAGE_PATH = f\"{HOME}/data/dog.jpeg\"" + 'source': [ + 'IMAGE_PATH = f"{HOME}/data/dog.jpeg"' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "KV4XzCg1_fAS", - "outputId": "1df75d90-cf73-4d78-feed-01911a81f6e4" + 'id': 'KV4XzCg1_fAS', + 'outputId': '1df75d90-cf73-4d78-feed-01911a81f6e4' }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.4,\n", - " iou=0.9)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()\n", - "prompt_process.plot(annotations=masks, output=f\"{HOME}/output\")" + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.4,\n', + ' iou=0.9)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()\n', + 'prompt_process.plot(annotations=masks, output=f"{HOME}/output")' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "uPrxqbaBOBMw" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'uPrxqbaBOBMw' }, - "source": [ - "**NOTE:** `prompt_process.everything_prompt` returns `torch.Tensor`" + 'source': [ + '**NOTE:** `prompt_process.everything_prompt` returns `torch.Tensor`' ] }, { - "cell_type": "code", - "metadata": { - "id": "PUJkojNiKBQa" + 'cell_type': 'code', + 'metadata': { + 'id': 'PUJkojNiKBQa' }, - "source": [ - "def annotate_image(image_path: str, masks: np.ndarray) -> np.ndarray:\n", - " image = cv2.imread(image_path)\n", - "\n", - " xyxy = sv.mask_to_xyxy(masks=masks)\n", - " detections = sv.Detections(xyxy=xyxy, mask=masks)\n", - "\n", - " mask_annotator = sv.MaskAnnotator()\n", - " return mask_annotator.annotate(scene=image.copy(), detections=detections)" + 'source': [ + 'def annotate_image(image_path: str, masks: np.ndarray) -> np.ndarray:\n', + ' image = cv2.imread(image_path)\n', + '\n', + ' xyxy = sv.mask_to_xyxy(masks=masks)\n', + ' detections = sv.Detections(xyxy=xyxy, mask=masks)\n', + '\n', + ' mask_annotator = sv.MaskAnnotator()\n', + ' return mask_annotator.annotate(scene=image.copy(), detections=detections)' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "wwtmiH6pLA8N", - "outputId": "17948850-f051-4a1f-b2b8-d01c2a1f4f20" + 'id': 'wwtmiH6pLA8N', + 'outputId': '17948850-f051-4a1f-b2b8-d01c2a1f4f20' }, - "source": [ - "masks = masks.cpu().numpy().astype(bool)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'source': [ + 'masks = masks.cpu().numpy().astype(bool)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "jYkdczKoRUVB" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'jYkdczKoRUVB' }, - "source": [ - "**NOTE:** The quality of the generated masks is quite poor. Let's see if we can improve it by manipulating the parameters." + 'source': [ + `**NOTE:** The quality of the generated masks is quite poor. Let's see if we can improve it by manipulating the parameters.` ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "dDFGi6EhO2ul", - "outputId": "f5a03357-eb37-446e-dce7-0659e4c2a368" - }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()" - ] - }, - { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'id': 'dDFGi6EhO2ul', + 'outputId': 'f5a03357-eb37-446e-dce7-0659e4c2a368' + }, + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()' + ] + }, + { + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "OiBPjQW-O59c", - "outputId": "afb011fc-933e-4d69-b4dd-e5e2713f407b" + 'id': 'OiBPjQW-O59c', + 'outputId': 'afb011fc-933e-4d69-b4dd-e5e2713f407b' }, - "source": [ - "masks = masks.cpu().numpy().astype(bool)\n", - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'source': [ + 'masks = masks.cpu().numpy().astype(bool)\n', + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "GoymBQ16KJbv" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'GoymBQ16KJbv' }, - "source": [ - "## FastSAM box prompt inference" + 'source': [ + '## FastSAM box prompt inference' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "w1_T5SXpMNku" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'w1_T5SXpMNku' }, - "source": [ - "### Draw Box" + 'source': [ + '### Draw Box' ] }, { - "cell_type": "code", - "metadata": { - "id": "04-1G1WrMJUm" + 'cell_type': 'code', + 'metadata': { + 'id': '04-1G1WrMJUm' }, - "source": [ - "# helper function that loads an image before adding it to the widget\n", - "\n", - "def encode_image(filepath):\n", - " with open(filepath, 'rb') as f:\n", - " image_bytes = f.read()\n", - " encoded = str(base64.b64encode(image_bytes), 'utf-8')\n", - " return \"data:image/jpg;base64,\"+encoded" + 'source': [ + '# helper function that loads an image before adding it to the widget\n', + '\n', + 'def encode_image(filepath):\n', + ` with open(filepath, 'rb') as f:\n`, + ' image_bytes = f.read()\n', + ` encoded = str(base64.b64encode(image_bytes), 'utf-8')\n`, + ' return "data:image/jpg;base64,"+encoded' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "xrxd-Ug9MSWG" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'xrxd-Ug9MSWG' }, - "source": [ - "**NOTE:** Execute cell below and use your mouse to draw bounding box on the image 👇" + 'source': [ + '**NOTE:** Execute cell below and use your mouse to draw bounding box on the image 👇' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000, - "referenced_widgets": [ - "a747e095e21448f29d8697fb6b1a7a74", - "6401ca8c2f574ba5ab62f0f06b9e0d06" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 1000, + 'referenced_widgets': [ + 'a747e095e21448f29d8697fb6b1a7a74', + '6401ca8c2f574ba5ab62f0f06b9e0d06' ] }, - "id": "nLA7xmDIMTfx", - "outputId": "06b51195-d54f-44ed-9236-258e94d624c5" - }, - "source": [ - "IS_COLAB = True\n", - "\n", - "if IS_COLAB:\n", - " from google.colab import output\n", - " output.enable_custom_widget_manager()\n", - "\n", - "from jupyter_bbox_widget import BBoxWidget\n", - "\n", - "widget = BBoxWidget()\n", - "widget.image = encode_image(IMAGE_PATH)\n", - "widget" - ] - }, - { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'id': 'nLA7xmDIMTfx', + 'outputId': '06b51195-d54f-44ed-9236-258e94d624c5' + }, + 'source': [ + 'IS_COLAB = True\n', + '\n', + 'if IS_COLAB:\n', + ' from google.colab import output\n', + ' output.enable_custom_widget_manager()\n', + '\n', + 'from jupyter_bbox_widget import BBoxWidget\n', + '\n', + 'widget = BBoxWidget()\n', + 'widget.image = encode_image(IMAGE_PATH)\n', + 'widget' + ] + }, + { + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "td6tYT4INOyD", - "outputId": "73a4e886-dcb8-447c-d3f7-1b3a6496e44d" + 'id': 'td6tYT4INOyD', + 'outputId': '73a4e886-dcb8-447c-d3f7-1b3a6496e44d' }, - "source": [ - "widget.bboxes" + 'source': [ + 'widget.bboxes' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "XfUvm9bwNUAe" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'XfUvm9bwNUAe' }, - "source": [ - "### Generate mask with FastSAM" + 'source': [ + '### Generate mask with FastSAM' ] }, { - "cell_type": "code", - "metadata": { - "id": "bI6kPDSROBI-" + 'cell_type': 'code', + 'metadata': { + 'id': 'bI6kPDSROBI-' }, - "source": [ - "# default_box is going to be used if you will not draw any box on image above\n", - "default_box = {'x': 68, 'y': 247, 'width': 555, 'height': 678, 'label': ''}\n", - "\n", - "box = widget.bboxes[0] if widget.bboxes else default_box\n", - "box = [\n", - " box['x'],\n", - " box['y'],\n", - " box['x'] + box['width'],\n", - " box['y'] + box['height']\n", - "]" + 'source': [ + '# default_box is going to be used if you will not draw any box on image above\n', + `default_box = {'x': 68, 'y': 247, 'width': 555, 'height': 678, 'label': ''}\n`, + '\n', + 'box = widget.bboxes[0] if widget.bboxes else default_box\n', + 'box = [\n', + ` box['x'],\n`, + ` box['y'],\n`, + ` box['x'] + box['width'],\n`, + ` box['y'] + box['height']\n`, + ']' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "LF879hDqO5iB", - "outputId": "4e7498c4-4007-4614-d46c-0e35608a04a2" + 'id': 'LF879hDqO5iB', + 'outputId': '4e7498c4-4007-4614-d46c-0e35608a04a2' }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.box_prompt(bbox=box)" + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.box_prompt(bbox=box)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "yt6i2sNJPEdk" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'yt6i2sNJPEdk' }, - "source": [ - "**NOTE:** `prompt_process.box_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.box_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "EJThTct_PA68", - "outputId": "f6eddd24-f603-4702-8325-6cff7925fe0f" + 'id': 'EJThTct_PA68', + 'outputId': 'f6eddd24-f603-4702-8325-6cff7925fe0f' }, - "source": [ - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'source': [ + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "vR-THJthhWH5" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'vR-THJthhWH5' }, - "source": [ - "## FastSAM point prompt inference" + 'source': [ + '## FastSAM point prompt inference' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "xyfpOPPwhpMy" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'xyfpOPPwhpMy' }, - "source": [ - "### Select point" + 'source': [ + '### Select point' ] }, { - "cell_type": "code", - "metadata": { - "id": "OC7d3pZ3hop9" + 'cell_type': 'code', + 'metadata': { + 'id': 'OC7d3pZ3hop9' }, - "source": [ - "point = [405, 505]" + 'source': [ + 'point = [405, 505]' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "rE5tV4e3h6nG" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'rE5tV4e3h6nG' }, - "source": [ - "### Generate mask with FastSAM" + 'source': [ + '### Generate mask with FastSAM' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "31FIby6Zh-Wa", - "outputId": "a12c4d1c-837b-4b1b-9240-664797442413" + 'id': '31FIby6Zh-Wa', + 'outputId': 'a12c4d1c-837b-4b1b-9240-664797442413' }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.point_prompt(points=[point], pointlabel=[1])" + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.point_prompt(points=[point], pointlabel=[1])' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "d7tpVo8CiM3s" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'd7tpVo8CiM3s' }, - "source": [ - "**NOTE:** `prompt_process.point_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.point_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "lMVpGxShiPky", - "outputId": "224d84ee-e664-400b-c0df-49507d229f62" + 'id': 'lMVpGxShiPky', + 'outputId': '224d84ee-e664-400b-c0df-49507d229f62' }, - "source": [ - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'source': [ + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "HOC7v6ATHEr6" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'HOC7v6ATHEr6' }, - "source": [ - "## FastSAM text prompt inference" + 'source': [ + '## FastSAM text prompt inference' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "4CzLLtXOHeGk", - "outputId": "cecc50d8-e2be-4b33-850a-a2442e3e30b6" + 'id': '4CzLLtXOHeGk', + 'outputId': 'cecc50d8-e2be-4b33-850a-a2442e3e30b6' }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.text_prompt(text='cap')" + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + `masks = prompt_process.text_prompt(text='cap')` ] }, { - "cell_type": "markdown", - "metadata": { - "id": "KYW79zD0OKsv" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'KYW79zD0OKsv' }, - "source": [ - "**NOTE:** `prompt_process.text_prompt` returns `numy.ndarray`" + 'source': [ + '**NOTE:** `prompt_process.text_prompt` returns `numy.ndarray`' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "zODY5xU1LMCb", - "outputId": "a01540a7-deb3-4555-916c-2ac59928e0f3" + 'id': 'zODY5xU1LMCb', + 'outputId': 'a01540a7-deb3-4555-916c-2ac59928e0f3' }, - "source": [ - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" + 'source': [ + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "XFOUWUMZEk7m" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'XFOUWUMZEk7m' }, - "source": [ - "## SAM vs FastSAM" + 'source': [ + '## SAM vs FastSAM' ] }, { - "cell_type": "code", - "metadata": { - "id": "7PKBZ7Hhahhw" + 'cell_type': 'code', + 'metadata': { + 'id': '7PKBZ7Hhahhw' }, - "source": [ - "IMAGE_PATH = f\"{HOME}/data/robot.jpeg\"" + 'source': [ + 'IMAGE_PATH = f"{HOME}/data/robot.jpeg"' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "c48hRY7dVJeL" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'c48hRY7dVJeL' }, - "source": [ - "### Load SAM" + 'source': [ + '### Load SAM' ] }, { - "cell_type": "code", - "metadata": { - "id": "PdKpFCCmaETG" + 'cell_type': 'code', + 'metadata': { + 'id': 'PdKpFCCmaETG' }, - "source": [ - "MODEL_TYPE = \"vit_h\"\n", - "\n", - "sam = sam_model_registry[MODEL_TYPE](checkpoint=SAM_SAM_CHECKPOINT_PATH).to(device=DEVICE)\n", - "mask_generator = SamAutomaticMaskGenerator(sam)" + 'source': [ + 'MODEL_TYPE = "vit_h"\n', + '\n', + 'sam = sam_model_registry[MODEL_TYPE](checkpoint=SAM_SAM_CHECKPOINT_PATH).to(device=DEVICE)\n', + 'mask_generator = SamAutomaticMaskGenerator(sam)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "V5bPGvtBaewO" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'V5bPGvtBaewO' }, - "source": [ - "### SAM inference" + 'source': [ + '### SAM inference' ] }, { - "cell_type": "code", - "metadata": { - "id": "0HgWsk1yalow" + 'cell_type': 'code', + 'metadata': { + 'id': '0HgWsk1yalow' }, - "source": [ - "image_bgr = cv2.imread(IMAGE_PATH)\n", - "image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)\n", - "\n", - "sam_result = mask_generator.generate(image_rgb)\n", - "sam_detections = sv.Detections.from_sam(sam_result=sam_result)" + 'source': [ + 'image_bgr = cv2.imread(IMAGE_PATH)\n', + 'image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)\n', + '\n', + 'sam_result = mask_generator.generate(image_rgb)\n', + 'sam_detections = sv.Detections.from_sam(sam_result=sam_result)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "t03RrSzMasN6" + 'cell_type': 'markdown', + 'metadata': { + 'id': 't03RrSzMasN6' }, - "source": [ - "### FastSAM inference" + 'source': [ + '### FastSAM inference' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "xuFGLw8MavQI", - "outputId": "b0118973-1e29-419d-b7c0-2933b97d7ce5" + 'id': 'xuFGLw8MavQI', + 'outputId': 'b0118973-1e29-419d-b7c0-2933b97d7ce5' }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.everything_prompt()\n", - "masks = masks.cpu().numpy().astype(bool)\n", - "xyxy = sv.mask_to_xyxy(masks=masks)\n", - "fast_sam_detections = sv.Detections(xyxy=xyxy, mask=masks)" + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + 'masks = prompt_process.everything_prompt()\n', + 'masks = masks.cpu().numpy().astype(bool)\n', + 'xyxy = sv.mask_to_xyxy(masks=masks)\n', + 'fast_sam_detections = sv.Detections(xyxy=xyxy, mask=masks)' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "IYn8xKySbBnc" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'IYn8xKySbBnc' }, - "source": [ - "### FastSAM vs. SAM" + 'source': [ + '### FastSAM vs. SAM' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 321 }, - "id": "c2Qj7sLjbDvo", - "outputId": "e85e3ed3-8b90-409d-cf95-877a6a9b2034" + 'id': 'c2Qj7sLjbDvo', + 'outputId': 'e85e3ed3-8b90-409d-cf95-877a6a9b2034' }, - "source": [ - "mask_annotator = sv.MaskAnnotator()\n", - "\n", - "sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=sam_detections)\n", - "fast_sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=fast_sam_detections)\n", - "\n", - "sv.plot_images_grid(\n", - " images=[sam_result, fast_sam_result],\n", - " grid_size=(1, 2),\n", - " titles=['SAM', 'FastSAM']\n", - ")" + 'source': [ + 'mask_annotator = sv.MaskAnnotator()\n', + '\n', + 'sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=sam_detections)\n', + 'fast_sam_result = mask_annotator.annotate(scene=image_bgr.copy(), detections=fast_sam_detections)\n', + '\n', + 'sv.plot_images_grid(\n', + ' images=[sam_result, fast_sam_result],\n', + ' grid_size=(1, 2),\n', + ` titles=['SAM', 'FastSAM']\n`, + ')' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "MfU4NQiGWQMC" + 'cell_type': 'markdown', + 'metadata': { + 'id': 'MfU4NQiGWQMC' }, - "source": [ - "**NOTE:** There is a lot going on in our test image. Let's plot our masks against a blank background." + 'source': [ + `**NOTE:** There is a lot going on in our test image. Let's plot our masks against a blank background.` ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 321 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 321 }, - "id": "PIrywlF5QRPz", - "outputId": "8e7c84ae-7869-4523-847a-0678fd1aea53" + 'id': 'PIrywlF5QRPz', + 'outputId': '8e7c84ae-7869-4523-847a-0678fd1aea53' }, - "source": [ - "mask_annotator = sv.MaskAnnotator()\n", - "\n", - "sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=sam_detections)\n", - "fast_sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=fast_sam_detections)\n", - "\n", - "sv.plot_images_grid(\n", - " images=[sam_result, fast_sam_result],\n", - " grid_size=(1, 2),\n", - " titles=['SAM', 'FastSAM']\n", - ")" + 'source': [ + 'mask_annotator = sv.MaskAnnotator()\n', + '\n', + 'sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=sam_detections)\n', + 'fast_sam_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=fast_sam_detections)\n', + '\n', + 'sv.plot_images_grid(\n', + ' images=[sam_result, fast_sam_result],\n', + ' grid_size=(1, 2),\n', + ` titles=['SAM', 'FastSAM']\n`, + ')' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "51Fs3sDGQmIt" + 'cell_type': 'markdown', + 'metadata': { + 'id': '51Fs3sDGQmIt' }, - "source": [ - "### Mask diff\n", - "\n", - "**NOTE:** Let's try to understand which masks generated by SAM were not generated by FastSAM." + 'source': [ + '### Mask diff\n', + '\n', + `**NOTE:** Let's try to understand which masks generated by SAM were not generated by FastSAM.` ] }, { - "cell_type": "code", - "metadata": { - "id": "O5bIUAhYQrqZ" + 'cell_type': 'code', + 'metadata': { + 'id': 'O5bIUAhYQrqZ' }, - "source": [ - "def compute_iou(mask1, mask2):\n", - " intersection = np.logical_and(mask1, mask2)\n", - " union = np.logical_or(mask1, mask2)\n", - " return np.sum(intersection) / np.sum(union)\n", - "\n", - "def filter_masks(mask_array1, mask_array2, threshold):\n", - " return np.array([mask1 for mask1 in mask_array1 if max(compute_iou(mask1, mask2) for mask2 in mask_array2) < threshold])" + 'source': [ + 'def compute_iou(mask1, mask2):\n', + ' intersection = np.logical_and(mask1, mask2)\n', + ' union = np.logical_or(mask1, mask2)\n', + ' return np.sum(intersection) / np.sum(union)\n', + '\n', + 'def filter_masks(mask_array1, mask_array2, threshold):\n', + ' return np.array([mask1 for mask1 in mask_array1 if max(compute_iou(mask1, mask2) for mask2 in mask_array2) < threshold])' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 420 + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 420 }, - "id": "1GM4bL9sRbOm", - "outputId": "421fb4f5-7a42-461b-ba1e-7cd46d2d7bd7" + 'id': '1GM4bL9sRbOm', + 'outputId': '421fb4f5-7a42-461b-ba1e-7cd46d2d7bd7' }, - "source": [ - "diff_masks = filter_masks(sam_detections.mask, fast_sam_detections.mask, 0.5)\n", - "diff_xyxy = sv.mask_to_xyxy(masks=diff_masks)\n", - "diff_detections = sv.Detections(xyxy=diff_xyxy, mask=diff_masks)\n", - "\n", - "mask_annotator = sv.MaskAnnotator()\n", - "diff_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=diff_detections)\n", - "sv.plot_image(image=diff_result, size=(8, 8))" + 'source': [ + 'diff_masks = filter_masks(sam_detections.mask, fast_sam_detections.mask, 0.5)\n', + 'diff_xyxy = sv.mask_to_xyxy(masks=diff_masks)\n', + 'diff_detections = sv.Detections(xyxy=diff_xyxy, mask=diff_masks)\n', + '\n', + 'mask_annotator = sv.MaskAnnotator()\n', + 'diff_result = mask_annotator.annotate(scene=np.zeros_like(image_bgr), detections=diff_detections)\n', + 'sv.plot_image(image=diff_result, size=(8, 8))' ] }, { - "cell_type": "markdown", - "metadata": { - "id": "0lPZAZZulhF8" + 'cell_type': 'markdown', + 'metadata': { + 'id': '0lPZAZZulhF8' }, - "source": [ - "## Roboflow Benchmark Dataset" + 'source': [ + '## Roboflow Benchmark Dataset' ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "LWN6XwPuSZBY", - "outputId": "86b18c0e-f6d6-486b-eee6-d004f13f531e" + 'id': 'LWN6XwPuSZBY', + 'outputId': '86b18c0e-f6d6-486b-eee6-d004f13f531e' }, - "source": [ - "%cd {HOME}\n", - "\n", - "roboflow.login()\n", - "rf = Roboflow()\n", - "\n", - "project = rf.workspace(\"roboticfish\").project(\"underwater_object_detection\")\n", - "dataset = project.version(10).download(\"yolov8\")" + 'source': [ + '%cd {HOME}\n', + '\n', + 'roboflow.login()\n', + 'rf = Roboflow()\n', + '\n', + 'project = rf.workspace("roboticfish").project("underwater_object_detection")\n', + 'dataset = project.version(10).download("yolov8")' ] }, { - "cell_type": "code", - "metadata": { - "id": "T6-PaxWXTZ69" + 'cell_type': 'code', + 'metadata': { + 'id': 'T6-PaxWXTZ69' }, - "source": [ - "IMAGE_PATH = os.path.join(dataset.location, 'train', 'images', 'IMG_2311_jpeg_jpg.rf.09ae6820eaff21dc838b1f9b6b20342b.jpg')" + 'source': [ + `IMAGE_PATH = os.path.join(dataset.location, 'train', 'images', 'IMG_2311_jpeg_jpg.rf.09ae6820eaff21dc838b1f9b6b20342b.jpg')` ] }, { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/' }, - "id": "p-gY5jf1Tife", - "outputId": "630d35d8-26f4-4342-8d82-c7b61be26fe8" - }, - "source": [ - "results = fast_sam(\n", - " source=IMAGE_PATH,\n", - " device=DEVICE,\n", - " retina_masks=True,\n", - " imgsz=1024,\n", - " conf=0.5,\n", - " iou=0.6)\n", - "prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n", - "masks = prompt_process.text_prompt(text='Penguin')" - ] - }, - { - "cell_type": "code", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 653 + 'id': 'p-gY5jf1Tife', + 'outputId': '630d35d8-26f4-4342-8d82-c7b61be26fe8' + }, + 'source': [ + 'results = fast_sam(\n', + ' source=IMAGE_PATH,\n', + ' device=DEVICE,\n', + ' retina_masks=True,\n', + ' imgsz=1024,\n', + ' conf=0.5,\n', + ' iou=0.6)\n', + 'prompt_process = FastSAMPrompt(IMAGE_PATH, results, device=DEVICE)\n', + `masks = prompt_process.text_prompt(text='Penguin')` + ] + }, + { + 'cell_type': 'code', + 'metadata': { + 'colab': { + 'base_uri': 'https://localhost:8080/', + 'height': 653 }, - "id": "3tBIH0yubHsW", - "outputId": "fc4d0a6a-53c1-4a2b-91f0-75c8f78e10fd" - }, - "source": [ - "annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n", - "sv.plot_image(image=annotated_image, size=(8, 8))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gP9gEw9Tp8GJ" - }, - "source": [ - "## 🏆 Congratulations\n", - "\n", - "### Learning Resources\n", - "\n", - "Roboflow has produced many resources that you may find interesting as you advance your knowledge of computer vision:\n", - "\n", - "- [Roboflow Notebooks](https://github.com/roboflow/notebooks): A repository of over 20 notebooks that walk through how to train custom models with a range of model types, from YOLOv7 to SegFormer.\n", - "- [Roboflow YouTube](https://www.youtube.com/c/Roboflow): Our library of videos featuring deep dives into the latest in computer vision, detailed tutorials that accompany our notebooks, and more.\n", - "- [Roboflow Discuss](https://discuss.roboflow.com/): Have a question about how to do something on Roboflow? Ask your question on our discussion forum.\n", - "- [Roboflow Models](https://roboflow.com): Learn about state-of-the-art models and their performance. Find links and tutorials to guide your learning.\n", - "\n", - "### Convert data formats\n", - "\n", - "Roboflow provides free utilities to convert data between dozens of popular computer vision formats. Check out [Roboflow Formats](https://roboflow.com/formats) to find tutorials on how to convert data between formats in a few clicks.\n", - "\n", - "### Connect computer vision to your project logic\n", - "\n", - "[Roboflow Templates](https://roboflow.com/templates) is a public gallery of code snippets that you can use to connect computer vision to your project logic. Code snippets range from sending emails after inference to measuring object distance between detections." + 'id': '3tBIH0yubHsW', + 'outputId': 'fc4d0a6a-53c1-4a2b-91f0-75c8f78e10fd' + }, + 'source': [ + 'annotated_image=annotate_image(image_path=IMAGE_PATH, masks=masks)\n', + 'sv.plot_image(image=annotated_image, size=(8, 8))' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': { + 'id': 'gP9gEw9Tp8GJ' + }, + 'source': [ + '## 🏆 Congratulations\n', + '\n', + '### Learning Resources\n', + '\n', + 'Roboflow has produced many resources that you may find interesting as you advance your knowledge of computer vision:\n', + '\n', + '- [Roboflow Notebooks](https://github.com/roboflow/notebooks): A repository of over 20 notebooks that walk through how to train custom models with a range of model types, from YOLOv7 to SegFormer.\n', + '- [Roboflow YouTube](https://www.youtube.com/c/Roboflow): Our library of videos featuring deep dives into the latest in computer vision, detailed tutorials that accompany our notebooks, and more.\n', + '- [Roboflow Discuss](https://discuss.roboflow.com/): Have a question about how to do something on Roboflow? Ask your question on our discussion forum.\n', + '- [Roboflow Models](https://roboflow.com): Learn about state-of-the-art models and their performance. Find links and tutorials to guide your learning.\n', + '\n', + '### Convert data formats\n', + '\n', + 'Roboflow provides free utilities to convert data between dozens of popular computer vision formats. Check out [Roboflow Formats](https://roboflow.com/formats) to find tutorials on how to convert data between formats in a few clicks.\n', + '\n', + '### Connect computer vision to your project logic\n', + '\n', + '[Roboflow Templates](https://roboflow.com/templates) is a public gallery of code snippets that you can use to connect computer vision to your project logic. Code snippets range from sending emails after inference to measuring object distance between detections.' ] } ].map(fromJupyterCell) @@ -14313,163 +14313,163 @@ suite('NotebookDiff Diff Service', () => { const { mapping, diff } = await mapCells( [ { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "from cachetools import cached, TTLCache\n", - "from dotenv import load_dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "GITHUB_API_URL = \"https://api.github.com\"\n", - "REPO_OWNER = \"microsoft\"\n", - "ISSUE = 123 # Replace with your milestone number\n", - "load_dotenv()\n", - "TOKEN = os.getenv(\"GH\")\n", - "\n", - "headers = {\n", - " \"Authorization\": f\"Bearer {TOKEN}\",\n", - " \"Accept\": \"application/vnd.github+json\"\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import csv\n", - "from datetime import datetime\n", - "import os\n", - "\n", - "def append_information(repo, open_issues_count, ):\n", - " file_exists = os.path.isfile(filename)\n", - " timestamp = datetime.utcnow().isoformat()\n", - "\n", - " # Open the file in append mode\n", - " with open(filename, 'a', newline='', encoding='utf-8') as csvfile:\n", - " writer = csv.writer(csvfile)\n", - "\n", - " # If the file didn't exist before, write the header row\n", - " if not file_exists:\n", - " writer.writerow([\n", - " \"timestamp\",\n", - " \"repo\",\n", - " \"open_issues_count\",\n", - " \"open_prs_count\",\n", - " \"bug_issues_count\",\n", - " \"feature_request_issues_count\",\n", - " \"invalid_issues_count\"\n", - " ])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_repo_issue_stats(owner, repo, token):\n", - " # Common GitHub GraphQL endpoint\n", - " url = \"https://api.github.com/graphql\"\n", - " headers = {\n", - " \"Authorization\": f\"Bearer {token}\",\n", - " \"Accept\": \"application/vnd.github.v4+json\"\n", - " }\n", - "\n", - " # First query: Get various issue/PR counts from the repository object\n", - " query_repo = \"\"\"\n", - " query ($owner: String!, $name: String!) {\n", - " repository(owner: $owner, name: $name) {\n", - " issues(states: OPEN) {\n", - " totalCount\n", - " }\n", - " pullRequests(states: OPEN) {\n", - " totalCount\n", - " }\n", - " bugIssues: issues(states: OPEN, labels: [\"bug\"]) {\n", - " totalCount\n", - " }\n", - " featureRequestIssues: issues(states: OPEN, labels: [\"feature-request\"]) {\n", - " totalCount\n", - " }\n", - " }\n", - " }\n", - " \"\"\"\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for REPO in REPOS:\n", - " stats = get_repo_issue_stats(OWNER, REPO, TOKEN)\n", - " print(stats)\n", - "\n", - " append_metrics_to_csv(\n", - " filename=\"repo_metrics.csv\",\n", - " repo=f\"{OWNER}/{REPO}\",\n", - " open_issues_count=stats['open_issues_count'],\n", - " open_prs_count=stats['open_prs_count'],\n", - " bug_issues_count=stats['bug_issues_count'],\n", - " feature_request_issues_count=stats['feature_request_issues_count'],\n", - " invalid_issues_count=stats['invalid_issues_count']\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import plotly.graph_objs as go\n", - "from plotly.subplots import make_subplots\n", - "\n", - "def plot_repo_metrics(repo, csv_filename=\"repo_metrics.csv\"):\n", - " # Load the CSV data\n", - " df = pd.read_csv(csv_filename)\n", - "\n", - " fig.add_trace(go.Scatter(\n", - " x=df_repo['timestamp'],\n", - " y=df_repo['open_issues_count'],\n", - " mode='lines+markers',\n", - " name='Open Issues',\n", - " hovertemplate='Open Issues: %{y}
    Time: %{x}'\n", - " ))\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for REPO in REPOS:\n", - " plot_repo_metrics(f\"{OWNER}/{REPO}\")" + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import requests\n', + 'import pandas as pd\n', + 'import matplotlib.pyplot as plt\n', + 'from cachetools import cached, TTLCache\n', + 'from dotenv import load_dotenv' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import os\n', + '\n', + 'GITHUB_API_URL = "https://api.github.com"\n', + 'REPO_OWNER = "microsoft"\n', + 'ISSUE = 123 # Replace with your milestone number\n', + 'load_dotenv()\n', + 'TOKEN = os.getenv("GH")\n', + '\n', + 'headers = {\n', + ' "Authorization": f"Bearer {TOKEN}",\n', + ' "Accept": "application/vnd.github+json"\n', + '}' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import csv\n', + 'from datetime import datetime\n', + 'import os\n', + '\n', + 'def append_information(repo, open_issues_count, ):\n', + ' file_exists = os.path.isfile(filename)\n', + ' timestamp = datetime.utcnow().isoformat()\n', + '\n', + ' # Open the file in append mode\n', + ` with open(filename, 'a', newline='', encoding='utf-8') as csvfile:\n`, + ' writer = csv.writer(csvfile)\n', + '\n', + ` # If the file didn't exist before, write the header row\n`, + ' if not file_exists:\n', + ' writer.writerow([\n', + ' "timestamp",\n', + ' "repo",\n', + ' "open_issues_count",\n', + ' "open_prs_count",\n', + ' "bug_issues_count",\n', + ' "feature_request_issues_count",\n', + ' "invalid_issues_count"\n', + ' ])\n' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'def get_repo_issue_stats(owner, repo, token):\n', + ' # Common GitHub GraphQL endpoint\n', + ' url = "https://api.github.com/graphql"\n', + ' headers = {\n', + ' "Authorization": f"Bearer {token}",\n', + ' "Accept": "application/vnd.github.v4+json"\n', + ' }\n', + '\n', + ' # First query: Get various issue/PR counts from the repository object\n', + ' query_repo = """\n', + ' query ($owner: String!, $name: String!) {\n', + ' repository(owner: $owner, name: $name) {\n', + ' issues(states: OPEN) {\n', + ' totalCount\n', + ' }\n', + ' pullRequests(states: OPEN) {\n', + ' totalCount\n', + ' }\n', + ' bugIssues: issues(states: OPEN, labels: ["bug"]) {\n', + ' totalCount\n', + ' }\n', + ' featureRequestIssues: issues(states: OPEN, labels: ["feature-request"]) {\n', + ' totalCount\n', + ' }\n', + ' }\n', + ' }\n', + ' """\n' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'for REPO in REPOS:\n', + ' stats = get_repo_issue_stats(OWNER, REPO, TOKEN)\n', + ' print(stats)\n', + '\n', + ' append_metrics_to_csv(\n', + ' filename="repo_metrics.csv",\n', + ' repo=f"{OWNER}/{REPO}",\n', + ` open_issues_count=stats['open_issues_count'],\n`, + ` open_prs_count=stats['open_prs_count'],\n`, + ` bug_issues_count=stats['bug_issues_count'],\n`, + ` feature_request_issues_count=stats['feature_request_issues_count'],\n`, + ` invalid_issues_count=stats['invalid_issues_count']\n`, + ' )' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': {}, + 'source': [ + '# Plot' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import pandas as pd\n', + 'import plotly.graph_objs as go\n', + 'from plotly.subplots import make_subplots\n', + '\n', + 'def plot_repo_metrics(repo, csv_filename="repo_metrics.csv"):\n', + ' # Load the CSV data\n', + ' df = pd.read_csv(csv_filename)\n', + '\n', + ' fig.add_trace(go.Scatter(\n', + ` x=df_repo['timestamp'],\n`, + ` y=df_repo['open_issues_count'],\n`, + ` mode='lines+markers',\n`, + ` name='Open Issues',\n`, + ` hovertemplate='Open Issues: %{y}
    Time: %{x}'\n`, + ' ))\n' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'for REPO in REPOS:\n', + ' plot_repo_metrics(f"{OWNER}/{REPO}")' ] } ] @@ -14477,187 +14477,187 @@ suite('NotebookDiff Diff Service', () => { , [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Fetch Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import sys\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "from cachetools import cached, TTLCache\n", - "from dotenv import load_dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "GITHUB_API_URL = \"https://api.github.com\"\n", - "REPO_OWNER = \"microsoft\"\n", - "ISSUE = 123\n", - "load_dotenv()\n", - "TOKEN = os.getenv(\"GH\")\n", - "\n", - "headers = {\n", - " \"Authorization\": f\"Bearer {TOKEN}\",\n", - " \"Accept\": \"application/vnd.github+json\"\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import csv\n", - "from datetime import datetime\n", - "import os\n", - "\n", - "def append_information(repo, open_issues_count, ):\n", - " file_exists = os.path.isfile(filename)\n", - " timestamp = datetime.utcnow().isoformat()\n", - "\n", - " # Open the file in append mode\n", - " with open(filename, 'a', newline='', encoding='utf-8') as csvfile:\n", - " writer = csv.writer(csvfile)\n", - "\n", - " # If the file didn't exist before, write the header row\n", - " if not file_exists:\n", - " writer.writerow([\n", - " \"timestamp\",\n", - " \"repo\",\n", - " \"open_issues_count\",\n", - " \"open_prs_count\",\n", - " \"bug_issues_count\",\n", - " \"feature_request_issues_count\",\n", - " \"invalid_issues_count\"\n", - " ])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_repo_issue_stats(owner, repo, token):\n", - " # Common GitHub GraphQL endpoint\n", - " url = \"https://api.github.com/graphql\"\n", - " headers = {\n", - " \"Authorization\": f\"Bearer {token}\",\n", - " \"Accept\": \"application/vnd.github.v4+json\"\n", - " }\n", - "\n", - " # First query: Get various issue/PR counts from the repository object\n", - " query_repo = \"\"\"\n", - " query ($owner: String!, $name: String!) {\n", - " repository(owner: $owner, name: $name) {\n", - " issues(states: OPEN) {\n", - " totalCount\n", - " }\n", - " pullRequests(states: OPEN) {\n", - " totalCount\n", - " }\n", - " bugIssues: issues(states: OPEN, labels: [\"bug\"]) {\n", - " totalCount\n", - " }\n", - " featureRequestIssues: issues(states: OPEN, labels: [\"feature-request\"]) {\n", - " totalCount\n", - " }\n", - " }\n", - " }\n", - " \"\"\"\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for REPO in REPOS:\n", - " stats = get_repo_issue_stats(OWNER, REPO, TOKEN)\n", - " print(stats)\n", - "\n", - " append_metrics_to_csv(\n", - " filename=\"repo_metrics.csv\",\n", - " repo=f\"{OWNER}/{REPO}\",\n", - " open_issues_count=stats['open_issues_count'],\n", - " open_prs_count=stats['open_prs_count'],\n", - " bug_issues_count=stats['bug_issues_count'],\n", - " feature_request_issues_count=stats['feature_request_issues_count'],\n", - " invalid_issues_count=stats['invalid_issues_count']\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import plotly.graph_objs as go\n", - "from plotly.subplots import make_subplots\n", - "\n", - "def plot_repo_metrics(repo, csv_filename=\"repo_metrics.csv\"):\n", - " # Load the CSV data\n", - " df = pd.read_csv(csv_filename)\n", - "\n", - " fig.add_trace(go.Scatter(\n", - " x=df_repo['timestamp'],\n", - " y=df_repo['open_issues_count'],\n", - " mode='lines+markers',\n", - " name='Open Issues',\n", - " hovertemplate='Open Issues: %{y}
    Time: %{x}'\n", - " ))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Header" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for REPO in REPOS:\n", - " plot_repo_metrics(f\"{OWNER}/{REPO}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_repo_metrics(f\"{OWNER}/vscode\")" + 'cell_type': 'markdown', + 'metadata': {}, + 'source': [ + '# Fetch Data' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import requests\n', + 'import sys\n', + 'import pandas as pd\n', + 'import matplotlib.pyplot as plt\n', + 'from cachetools import cached, TTLCache\n', + 'from dotenv import load_dotenv' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import os\n', + '\n', + 'GITHUB_API_URL = "https://api.github.com"\n', + 'REPO_OWNER = "microsoft"\n', + 'ISSUE = 123\n', + 'load_dotenv()\n', + 'TOKEN = os.getenv("GH")\n', + '\n', + 'headers = {\n', + ' "Authorization": f"Bearer {TOKEN}",\n', + ' "Accept": "application/vnd.github+json"\n', + '}' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import csv\n', + 'from datetime import datetime\n', + 'import os\n', + '\n', + 'def append_information(repo, open_issues_count, ):\n', + ' file_exists = os.path.isfile(filename)\n', + ' timestamp = datetime.utcnow().isoformat()\n', + '\n', + ' # Open the file in append mode\n', + ` with open(filename, 'a', newline='', encoding='utf-8') as csvfile:\n`, + ' writer = csv.writer(csvfile)\n', + '\n', + ` # If the file didn't exist before, write the header row\n`, + ' if not file_exists:\n', + ' writer.writerow([\n', + ' "timestamp",\n', + ' "repo",\n', + ' "open_issues_count",\n', + ' "open_prs_count",\n', + ' "bug_issues_count",\n', + ' "feature_request_issues_count",\n', + ' "invalid_issues_count"\n', + ' ])\n' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'def get_repo_issue_stats(owner, repo, token):\n', + ' # Common GitHub GraphQL endpoint\n', + ' url = "https://api.github.com/graphql"\n', + ' headers = {\n', + ' "Authorization": f"Bearer {token}",\n', + ' "Accept": "application/vnd.github.v4+json"\n', + ' }\n', + '\n', + ' # First query: Get various issue/PR counts from the repository object\n', + ' query_repo = """\n', + ' query ($owner: String!, $name: String!) {\n', + ' repository(owner: $owner, name: $name) {\n', + ' issues(states: OPEN) {\n', + ' totalCount\n', + ' }\n', + ' pullRequests(states: OPEN) {\n', + ' totalCount\n', + ' }\n', + ' bugIssues: issues(states: OPEN, labels: ["bug"]) {\n', + ' totalCount\n', + ' }\n', + ' featureRequestIssues: issues(states: OPEN, labels: ["feature-request"]) {\n', + ' totalCount\n', + ' }\n', + ' }\n', + ' }\n', + ' """\n' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'for REPO in REPOS:\n', + ' stats = get_repo_issue_stats(OWNER, REPO, TOKEN)\n', + ' print(stats)\n', + '\n', + ' append_metrics_to_csv(\n', + ' filename="repo_metrics.csv",\n', + ' repo=f"{OWNER}/{REPO}",\n', + ` open_issues_count=stats['open_issues_count'],\n`, + ` open_prs_count=stats['open_prs_count'],\n`, + ` bug_issues_count=stats['bug_issues_count'],\n`, + ` feature_request_issues_count=stats['feature_request_issues_count'],\n`, + ` invalid_issues_count=stats['invalid_issues_count']\n`, + ' )' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': {}, + 'source': [ + '# Plot' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'import pandas as pd\n', + 'import plotly.graph_objs as go\n', + 'from plotly.subplots import make_subplots\n', + '\n', + 'def plot_repo_metrics(repo, csv_filename="repo_metrics.csv"):\n', + ' # Load the CSV data\n', + ' df = pd.read_csv(csv_filename)\n', + '\n', + ' fig.add_trace(go.Scatter(\n', + ` x=df_repo['timestamp'],\n`, + ` y=df_repo['open_issues_count'],\n`, + ` mode='lines+markers',\n`, + ` name='Open Issues',\n`, + ` hovertemplate='Open Issues: %{y}
    Time: %{x}'\n`, + ' ))\n' + ] + }, + { + 'cell_type': 'markdown', + 'metadata': {}, + 'source': [ + '## Header' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'for REPO in REPOS:\n', + ' plot_repo_metrics(f"{OWNER}/{REPO}")' + ] + }, + { + 'cell_type': 'code', + 'execution_count': null, + 'metadata': {}, + 'outputs': [], + 'source': [ + 'plot_repo_metrics(f"{OWNER}/vscode")' ] } ].map(fromJupyterCell) diff --git a/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts index a9d87888531..cc7beec45b4 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import assert from "assert"; -import { ensureNoDisposablesAreLeakedInTestSuite } from "../../../../../../base/test/common/utils.js"; -import { CodeCellRenderTemplate } from "../../../browser/view/notebookRenderingCommon.js"; -import { CodeCellViewModel } from "../../../browser/viewModel/codeCellViewModel.js"; -import { CodeCellLayout } from "../../../browser/view/cellParts/codeCell.js"; +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { CodeCellRenderTemplate } from '../../../browser/view/notebookRenderingCommon.js'; +import { CodeCellViewModel } from '../../../browser/viewModel/codeCellViewModel.js'; +import { CodeCellLayout } from '../../../browser/view/cellParts/codeCell.js'; import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { CodeCellLayoutInfo, IActiveNotebookEditorDelegate } from '../../../browser/notebookBrowser.js'; -suite("CellPart", () => { +suite('CellPart', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test("CodeCellLayout editor visibility states", () => { + test('CodeCellLayout editor visibility states', () => { /** * We construct a very small mock around the parts that `CodeCellLayout` touches. The goal * is to validate the branching logic that sets `_editorVisibility` without mutating any @@ -45,65 +45,65 @@ suite("CellPart", () => { const scenarios: TestScenario[] = [ { - name: "Full", + name: 'Full', scrollTop: 0, viewportHeight: 400, editorContentHeight: 300, editorHeight: 300, outputContainerOffset: 300, // editorBottom = 100 + 300 = 400, fully inside viewport (scrollBottom=400) - expected: "Full", + expected: 'Full', elementTop: DEFAULT_ELEMENT_TOP, elementHeight: DEFAULT_ELEMENT_HEIGHT, expectedTop: 0, expectedEditorScrollTop: 0, }, { - name: "Bottom Clipped", + name: 'Bottom Clipped', scrollTop: 0, viewportHeight: 350, // scrollBottom=350 < editorBottom(400) editorContentHeight: 300, editorHeight: 300, outputContainerOffset: 300, - expected: "Bottom Clipped", + expected: 'Bottom Clipped', elementTop: DEFAULT_ELEMENT_TOP, elementHeight: DEFAULT_ELEMENT_HEIGHT, expectedTop: 0, expectedEditorScrollTop: 0, }, { - name: "Full (Small Viewport)", + name: 'Full (Small Viewport)', scrollTop: DEFAULT_ELEMENT_TOP + TOP_MARGIN + 20, // scrolled into the cell body viewportHeight: 220, // small vs content editorContentHeight: 500, // larger than viewport so we clamp editorHeight: 500, outputContainerOffset: 600, // editorBottom=700 > scrollBottom - expected: "Full (Small Viewport)", + expected: 'Full (Small Viewport)', elementTop: DEFAULT_ELEMENT_TOP, elementHeight: DEFAULT_ELEMENT_HEIGHT, expectedTop: 19, // (scrollTop - elementTop - topMargin - outlineWidth) = (100+6+20 -100 -6 -1) expectedEditorScrollTop: 19, }, { - name: "Top Clipped", + name: 'Top Clipped', scrollTop: DEFAULT_ELEMENT_TOP + TOP_MARGIN + 40, // scrolled further down but not past bottom viewportHeight: 600, // larger than content height below (forces branch for Top Clipped) editorContentHeight: 200, editorHeight: 200, outputContainerOffset: 450, // editorBottom=550; scrollBottom= scrollTop+viewportHeight = > 550? (540+600=1140) but we only need scrollTop < editorBottom - expected: "Top Clipped", + expected: 'Top Clipped', elementTop: DEFAULT_ELEMENT_TOP, elementHeight: DEFAULT_ELEMENT_HEIGHT, expectedTop: 39, // (100+6+40 -100 -6 -1) expectedEditorScrollTop: 40, // contentHeight(200) - computed height(160) }, { - name: "Invisible", + name: 'Invisible', scrollTop: DEFAULT_ELEMENT_TOP + 1000, // well below editor bottom viewportHeight: 400, editorContentHeight: 300, editorHeight: 300, outputContainerOffset: 300, // editorBottom=400 < scrollTop - expected: "Invisible", + expected: 'Invisible', elementTop: DEFAULT_ELEMENT_TOP, elementHeight: DEFAULT_ELEMENT_HEIGHT, expectedTop: 278, // adjusted after ensuring minimum line height when possibleEditorHeight < LINE_HEIGHT @@ -129,7 +129,7 @@ suite("CellPart", () => { hasModel: () => true, }; - const editorPart = { style: { top: "" } }; + const editorPart = { style: { top: '' } }; const template: Partial = { editor: stubEditor as unknown as ICodeEditor, editorPart: editorPart as unknown as HTMLElement, @@ -193,7 +193,7 @@ suite("CellPart", () => { `Scenario '${s.name}' (scrollTop=${s.scrollTop}) expected visibility ${s.expected} but got ${layout.editorVisibility}` ); const actualTop = parseInt( - (editorPart.style.top || "0").replace(/px$/, "") + (editorPart.style.top || '0').replace(/px$/, '') ); // style.top always like 'NNNpx' assert.strictEqual( actualTop, @@ -207,23 +207,23 @@ suite("CellPart", () => { ); // Basic sanity: style.top should always be set when visible states other than Full (handled) or Invisible. - if (s.expected !== "Invisible") { + if (s.expected !== 'Invisible') { assert.notStrictEqual( editorPart.style.top, - "", + '', `Scenario '${s.name}' should set a top style value` ); } else { // Invisible still sets a top; just ensure layout ran assert.ok( editorPart.style.top !== undefined, - "Invisible scenario still performs a layout" + 'Invisible scenario still performs a layout' ); } } }); - test("Scrolling", () => { + test('Scrolling', () => { /** * Pixel-by-pixel scroll test to validate `CodeCellLayout` calculations for: * - editorPart.style.top @@ -261,7 +261,7 @@ suite("CellPart", () => { top = top - (LINE_HEIGHT - possibleEditorHeight) - CELL_OUTLINE_WIDTH; } let height = EDITOR_CONTENT_HEIGHT; - let visibility: string = "Full"; + let visibility: string = 'Full'; let editorScrollTop = 0; if (scrollTop <= ELEMENT_TOP + CELL_TOP_MARGIN) { const minimumEditorHeight = LINE_HEIGHT + 6; // editorTopPadding from configuration stub (6) @@ -271,7 +271,7 @@ suite("CellPart", () => { minimumEditorHeight, EDITOR_CONTENT_HEIGHT ); - visibility = "Full"; + visibility = 'Full'; } else { height = clamp( @@ -280,7 +280,7 @@ suite("CellPart", () => { EDITOR_CONTENT_HEIGHT ) + 2 * CELL_OUTLINE_WIDTH; - visibility = "Bottom Clipped"; + visibility = 'Bottom Clipped'; editorScrollTop = 0; } } else { @@ -296,7 +296,7 @@ suite("CellPart", () => { EDITOR_CONTENT_HEIGHT - STATUSBAR_HEIGHT ) + 2 * CELL_OUTLINE_WIDTH; - visibility = "Full (Small Viewport)"; + visibility = 'Full (Small Viewport)'; editorScrollTop = top; } else { const minimumEditorHeight = LINE_HEIGHT; @@ -307,9 +307,9 @@ suite("CellPart", () => { EDITOR_CONTENT_HEIGHT ); if (scrollTop > editorBottom) { - visibility = "Invisible"; + visibility = 'Invisible'; } else { - visibility = "Top Clipped"; + visibility = 'Top Clipped'; } editorScrollTop = EDITOR_CONTENT_HEIGHT - height; } @@ -337,7 +337,7 @@ suite("CellPart", () => { }, hasModel: () => true, }; - const editorPart = { style: { top: "" } }; + const editorPart = { style: { top: '' } }; const template: Partial = { editor: stubEditor as unknown as ICodeEditor, editorPart: editorPart as unknown as HTMLElement, @@ -380,9 +380,9 @@ suite("CellPart", () => { { debug: () => { } }, { width: 600, height: EDITOR_HEIGHT } ); - layout.layoutEditor("scroll"); + layout.layoutEditor('scroll'); const actualTop = parseInt( - (editorPart.style.top || "0").replace(/px$/, "") + (editorPart.style.top || '0').replace(/px$/, '') ); assert.strictEqual( actualTop, diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index f8a0dea5e53..056213b688f 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -396,7 +396,7 @@ suite('SearchModel', () => { }; const notebookSearchService = instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([aRawMatchWithCells('/1', cellMatchMd, cellMatchCode)], undefined)); - const notebookSearch = sinon.spy(notebookSearchService, "notebookSearch"); + const notebookSearch = sinon.spy(notebookSearchService, 'notebookSearch'); const model: SearchModelImpl = instantiationService.createInstance(SearchModelImpl); store.add(model); await model.search({ contentPattern: { pattern: 'test' }, type: QueryType.Text, folderQueries }).asyncResults; diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 751c6acd986..6bf4e8c993b 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -251,7 +251,7 @@ suite('SearchResult', () => { sinon.stub(CellMatch.prototype, 'addContext'); - const addFileMatch = sinon.spy(FolderMatchImpl.prototype, "addFileMatch"); + const addFileMatch = sinon.spy(FolderMatchImpl.prototype, 'addFileMatch'); const fileMatch1 = aRawFileMatchWithCells('/1', { cell: cell1, diff --git a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 5bd1a4da935..1ccb592a855 100644 --- a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -1823,13 +1823,13 @@ suite('Task configuration conversions', () => { test('returns config for a known problem matcher', () => { const result = (ProblemMatcherConverter.from('$real', parseContext)); assert.strictEqual(result.errors?.length, 0); - assert.deepEqual(result.value, [{ "label": "real label" }]); + assert.deepEqual(result.value, [{ 'label': 'real label' }]); }); test('returns config for a known problem matcher including applyTo', () => { namedProblemMatcher.applyTo = ApplyToKind.closedDocuments; const result = (ProblemMatcherConverter.from('$real', parseContext)); assert.strictEqual(result.errors?.length, 0); - assert.deepEqual(result.value, [{ "label": "real label", "applyTo": ApplyToKind.closedDocuments }]); + assert.deepEqual(result.value, [{ 'label': 'real label', 'applyTo': ApplyToKind.closedDocuments }]); }); }); suite('TaskParser.from', () => { @@ -1841,7 +1841,7 @@ suite('Task configuration conversions', () => { }); test('command', () => { const result = TaskParser.from([{ taskName: 'task' } as ICustomTask], globals, parseContext, taskConfigSource); - assertTaskParseResult(result, undefined, problemReporter, "Error: the task 'task' doesn't define a command"); + assertTaskParseResult(result, undefined, problemReporter, `Error: the task 'task' doesn't define a command`); }); }); test('returns expected result', () => { diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 985965e9108..44454ca1d0d 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -36,18 +36,18 @@ const ROOT_2 = fixPath(root2); class MockTerminalProfileResolverService extends TestTerminalProfileResolverService { override async getDefaultProfile(): Promise { return { - profileName: "my-sh", - path: "/usr/bin/zsh", + profileName: 'my-sh', + path: '/usr/bin/zsh', env: { - TEST: "TEST", + TEST: 'TEST', }, isDefault: true, isUnsafePath: false, isFromPath: true, icon: { - id: "terminal-linux", + id: 'terminal-linux', }, - color: "terminal.ansiYellow", + color: 'terminal.ansiYellow', }; } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts index 0111843f473..c33a16cbf88 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts @@ -248,13 +248,13 @@ suite('ShellIntegrationAddon', () => { deepEqual(parseMarkSequence(['', '']), { id: undefined, hidden: false }); }); test('ID', async () => { - deepEqual(parseMarkSequence(['Id=3', '']), { id: "3", hidden: false }); + deepEqual(parseMarkSequence(['Id=3', '']), { id: '3', hidden: false }); }); test('hidden', async () => { deepEqual(parseMarkSequence(['', 'Hidden']), { id: undefined, hidden: true }); }); test('ID + hidden', async () => { - deepEqual(parseMarkSequence(['Id=4555', 'Hidden']), { id: "4555", hidden: true }); + deepEqual(parseMarkSequence(['Id=4555', 'Hidden']), { id: '4555', hidden: true }); }); }); }); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts index ff8e3dbddb7..224f6af84d3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts @@ -63,37 +63,37 @@ suite('CommandLineAutoApprover', () => { suite('autoApprove with allow patterns only', () => { test('should auto-approve exact command match', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(isAutoApproved('echo')); }); test('should auto-approve command with arguments', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(isAutoApproved('echo hello world')); }); test('should not auto-approve when there is no match', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(!isAutoApproved('ls')); }); test('should not auto-approve partial command matches', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(!isAutoApproved('echotest')); }); test('should handle multiple commands in autoApprove', () => { setAutoApprove({ - "echo": true, - "ls": true, - "pwd": true + 'echo': true, + 'ls': true, + 'pwd': true }); ok(isAutoApproved('echo')); ok(isAutoApproved('ls -la')); @@ -105,8 +105,8 @@ suite('CommandLineAutoApprover', () => { suite('autoApprove with deny patterns only', () => { test('should deny commands in autoApprove', () => { setAutoApprove({ - "rm": false, - "del": false + 'rm': false, + 'del': false }); ok(!isAutoApproved('rm file.txt')); ok(!isAutoApproved('del file.txt')); @@ -114,7 +114,7 @@ suite('CommandLineAutoApprover', () => { test('should not auto-approve safe commands when no allow patterns are present', () => { setAutoApprove({ - "rm": false + 'rm': false }); ok(!isAutoApproved('echo hello')); ok(!isAutoApproved('ls')); @@ -124,8 +124,8 @@ suite('CommandLineAutoApprover', () => { suite('autoApprove with mixed allow and deny patterns', () => { test('should deny commands set to false even if other commands are set to true', () => { setAutoApprove({ - "echo": true, - "rm": false + 'echo': true, + 'rm': false }); ok(isAutoApproved('echo hello')); ok(!isAutoApproved('rm file.txt')); @@ -133,11 +133,11 @@ suite('CommandLineAutoApprover', () => { test('should auto-approve allow patterns not set to false', () => { setAutoApprove({ - "echo": true, - "ls": true, - "pwd": true, - "rm": false, - "del": false + 'echo': true, + 'ls': true, + 'pwd': true, + 'rm': false, + 'del': false }); ok(isAutoApproved('echo')); ok(isAutoApproved('ls')); @@ -150,7 +150,7 @@ suite('CommandLineAutoApprover', () => { suite('regex patterns', () => { test('should handle /.*/', () => { setAutoApprove({ - "/.*/": true, + '/.*/': true, }); ok(isAutoApproved('echo hello')); @@ -158,9 +158,9 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns in autoApprove', () => { setAutoApprove({ - "/^echo/": true, - "/^ls/": true, - "pwd": true + '/^echo/': true, + '/^ls/': true, + 'pwd': true }); ok(isAutoApproved('echo hello')); @@ -171,10 +171,10 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns for deny', () => { setAutoApprove({ - "echo": true, - "rm": true, - "/^rm\\s+/": false, - "/^del\\s+/": false + 'echo': true, + 'rm': true, + '/^rm\\s+/': false, + '/^del\\s+/': false }); ok(isAutoApproved('echo hello')); @@ -185,9 +185,9 @@ suite('CommandLineAutoApprover', () => { test('should handle complex regex patterns', () => { setAutoApprove({ - "/^(echo|ls|pwd)\\b/": true, - "/^git (status|show\\b.*)$/": true, - "/rm|del|kill/": false + '/^(echo|ls|pwd)\\b/': true, + '/^git (status|show\\b.*)$/': true, + '/rm|del|kill/': false }); ok(isAutoApproved('echo test')); @@ -204,9 +204,9 @@ suite('CommandLineAutoApprover', () => { suite('flags', () => { test('should handle case-insensitive regex patterns with i flag', () => { setAutoApprove({ - "/^echo/i": true, - "/^ls/i": true, - "/rm|del/i": false + '/^echo/i': true, + '/^ls/i': true, + '/rm|del/i': false }); ok(isAutoApproved('echo hello')); @@ -223,8 +223,8 @@ suite('CommandLineAutoApprover', () => { test('should handle multiple regex flags', () => { setAutoApprove({ - "/^git\\s+/gim": true, - "/dangerous/gim": false + '/^git\\s+/gim': true, + '/dangerous/gim': false }); ok(isAutoApproved('git status')); @@ -236,9 +236,9 @@ suite('CommandLineAutoApprover', () => { test('should handle various regex flags', () => { setAutoApprove({ - "/^echo.*/s": true, // dotall flag - "/^git\\s+/i": true, // case-insensitive flag - "/rm|del/g": false // global flag + '/^echo.*/s': true, // dotall flag + '/^git\\s+/i': true, // case-insensitive flag + '/rm|del/g': false // global flag }); ok(isAutoApproved('echo hello\nworld')); @@ -250,8 +250,8 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns without flags', () => { setAutoApprove({ - "/^echo/": true, - "/rm|del/": false + '/^echo/': true, + '/rm|del/': false }); ok(isAutoApproved('echo hello')); @@ -273,7 +273,7 @@ suite('CommandLineAutoApprover', () => { test('should handle empty command strings', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(!isAutoApproved('')); @@ -282,7 +282,7 @@ suite('CommandLineAutoApprover', () => { test('should handle whitespace in commands', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(isAutoApproved('echo hello world')); @@ -290,7 +290,7 @@ suite('CommandLineAutoApprover', () => { test('should be case-sensitive by default', () => { setAutoApprove({ - "echo": true + 'echo': true }); ok(isAutoApproved('echo hello')); @@ -301,7 +301,7 @@ suite('CommandLineAutoApprover', () => { // https://github.com/microsoft/vscode/issues/252411 test('should handle string-based values with special regex characters', () => { setAutoApprove({ - "pwsh.exe -File D:\\foo.bar\\a-script.ps1": true + 'pwsh.exe -File D:\\foo.bar\\a-script.ps1': true }); ok(isAutoApproved('pwsh.exe -File D:\\foo.bar\\a-script.ps1')); @@ -310,7 +310,7 @@ suite('CommandLineAutoApprover', () => { test('should ignore the empty string key', () => { setAutoApprove({ - "": true + '': true }); ok(!isAutoApproved('echo hello')); @@ -318,10 +318,10 @@ suite('CommandLineAutoApprover', () => { test('should handle empty regex patterns that could cause endless loops', () => { setAutoApprove({ - "//": true, - "/(?:)/": true, - "/*/": true, // Invalid regex pattern - "/.**/": true // Invalid regex pattern + '//': true, + '/(?:)/': true, + '/*/': true, // Invalid regex pattern + '/.**/': true // Invalid regex pattern }); // These patterns should not cause endless loops and should not match any commands @@ -333,10 +333,10 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns that would cause endless loops', () => { setAutoApprove({ - "/a*/": true, - "/b?/": true, - "/(x|)*/": true, - "/(?:)*/": true + '/a*/': true, + '/b?/': true, + '/(x|)*/': true, + '/(?:)*/': true }); // Commands should still work normally, endless loop patterns should be safely handled @@ -348,11 +348,11 @@ suite('CommandLineAutoApprover', () => { test('should handle mixed valid and problematic regex patterns', () => { setAutoApprove({ - "/^echo/": true, // Valid pattern - "//": true, // Empty pattern - "/^ls/": true, // Valid pattern - "/a*/": true, // Potential endless loop - "pwd": true // Valid string pattern + '/^echo/': true, // Valid pattern + '//': true, // Empty pattern + '/^ls/': true, // Valid pattern + '/a*/': true, // Potential endless loop + 'pwd': true // Valid string pattern }); ok(isAutoApproved('echo hello')); @@ -363,11 +363,11 @@ suite('CommandLineAutoApprover', () => { test('should handle invalid regex patterns gracefully', () => { setAutoApprove({ - "/*/": true, // Invalid regex - nothing to repeat - "/(?:+/": true, // Invalid regex - incomplete quantifier - "/[/": true, // Invalid regex - unclosed character class - "/^echo/": true, // Valid pattern - "ls": true // Valid string pattern + '/*/': true, // Invalid regex - nothing to repeat + '/(?:+/': true, // Invalid regex - incomplete quantifier + '/[/': true, // Invalid regex - unclosed character class + '/^echo/': true, // Valid pattern + 'ls': true // Valid string pattern }); // Valid patterns should still work @@ -381,7 +381,7 @@ suite('CommandLineAutoApprover', () => { suite('path-aware auto approval', () => { test('should handle path variations with forward slashes', () => { setAutoApprove({ - "bin/foo": true + 'bin/foo': true }); // Should approve the exact match @@ -405,7 +405,7 @@ suite('CommandLineAutoApprover', () => { test('should handle path variations with backslashes', () => { setAutoApprove({ - "bin\\script.bat": true + 'bin\\script.bat': true }); // Should approve the exact match @@ -425,7 +425,7 @@ suite('CommandLineAutoApprover', () => { test('should handle deep paths', () => { setAutoApprove({ - "src/utils/helper.js": true + 'src/utils/helper.js': true }); ok(isAutoApproved('src/utils/helper.js')); @@ -438,9 +438,9 @@ suite('CommandLineAutoApprover', () => { test('should not treat non-paths as paths', () => { setAutoApprove({ - "echo": true, // Not a path - "ls": true, // Not a path - "git": true // Not a path + 'echo': true, // Not a path + 'ls': true, // Not a path + 'git': true // Not a path }); // These should work as normal command matching, not path matching @@ -455,7 +455,7 @@ suite('CommandLineAutoApprover', () => { test('should handle paths with mixed separators in config', () => { setAutoApprove({ - "bin/foo\\bar": true // Mixed separators in config + 'bin/foo\\bar': true // Mixed separators in config }); ok(isAutoApproved('bin/foo\\bar')); @@ -468,7 +468,7 @@ suite('CommandLineAutoApprover', () => { test('should work with command line auto approval for paths', () => { setAutoApproveWithCommandLine({ - "bin/deploy": { approve: true, matchCommandLine: true } + 'bin/deploy': { approve: true, matchCommandLine: true } }); ok(isCommandLineAutoApproved('bin/deploy --prod')); @@ -479,9 +479,9 @@ suite('CommandLineAutoApprover', () => { test('should handle special characters in paths', () => { setAutoApprove({ - "bin/my-script.sh": true, - "scripts/build_all.py": true, - "tools/run (debug).exe": true + 'bin/my-script.sh': true, + 'scripts/build_all.py': true, + 'tools/run (debug).exe': true }); ok(isAutoApproved('bin/my-script.sh')); @@ -503,11 +503,11 @@ suite('CommandLineAutoApprover', () => { test('should handle Windows PowerShell commands', () => { setAutoApprove({ - "Get-ChildItem": true, - "Get-Content": true, - "Get-Location": true, - "Remove-Item": false, - "del": false + 'Get-ChildItem': true, + 'Get-Content': true, + 'Get-Location': true, + 'Remove-Item': false, + 'del': false }); ok(isAutoApproved('Get-ChildItem')); @@ -518,7 +518,7 @@ suite('CommandLineAutoApprover', () => { test('should handle ( prefixes', () => { setAutoApprove({ - "Get-Content": true + 'Get-Content': true }); ok(isAutoApproved('Get-Content file.txt')); @@ -529,9 +529,9 @@ suite('CommandLineAutoApprover', () => { test('should be case-insensitive for PowerShell commands', () => { setAutoApprove({ - "Get-ChildItem": true, - "Get-Content": true, - "Remove-Item": false + 'Get-ChildItem': true, + 'Get-Content': true, + 'Remove-Item': false }); ok(isAutoApproved('Get-ChildItem')); @@ -553,10 +553,10 @@ suite('CommandLineAutoApprover', () => { test('should be case-insensitive for PowerShell aliases', () => { setAutoApprove({ - "ls": true, - "dir": true, - "rm": false, - "del": false + 'ls': true, + 'dir': true, + 'rm': false, + 'del': false }); // Test case-insensitive matching for aliases @@ -579,8 +579,8 @@ suite('CommandLineAutoApprover', () => { test('should be case-insensitive with regex patterns', () => { setAutoApprove({ - "/^Get-/": true, - "/Remove-Item|rm/": false + '/^Get-/': true, + '/Remove-Item|rm/': false }); ok(isAutoApproved('Get-ChildItem')); @@ -596,8 +596,8 @@ suite('CommandLineAutoApprover', () => { test('should handle case-insensitive PowerShell commands on different OS', () => { setAutoApprove({ - "Get-Process": true, - "Stop-Process": false + 'Get-Process': true, + 'Stop-Process': false }); for (const currnetOS of [OperatingSystem.Windows, OperatingSystem.Linux, OperatingSystem.Macintosh]) { @@ -614,7 +614,7 @@ suite('CommandLineAutoApprover', () => { suite('isCommandLineAutoApproved - matchCommandLine functionality', () => { test('should auto-approve command line patterns with matchCommandLine: true', () => { setAutoApproveWithCommandLine({ - "echo": { approve: true, matchCommandLine: true } + 'echo': { approve: true, matchCommandLine: true } }); ok(isCommandLineAutoApproved('echo hello')); @@ -623,7 +623,7 @@ suite('CommandLineAutoApprover', () => { test('should not auto-approve regular patterns with isCommandLineAutoApproved', () => { setAutoApprove({ - "echo": true + 'echo': true }); // Regular patterns should not be matched by isCommandLineAutoApproved @@ -632,7 +632,7 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns with matchCommandLine: true', () => { setAutoApproveWithCommandLine({ - "/echo.*world/": { approve: true, matchCommandLine: true } + '/echo.*world/': { approve: true, matchCommandLine: true } }); ok(isCommandLineAutoApproved('echo hello world')); @@ -641,7 +641,7 @@ suite('CommandLineAutoApprover', () => { test('should handle case-insensitive regex with matchCommandLine: true', () => { setAutoApproveWithCommandLine({ - "/echo/i": { approve: true, matchCommandLine: true } + '/echo/i': { approve: true, matchCommandLine: true } }); ok(isCommandLineAutoApproved('echo hello')); @@ -651,8 +651,8 @@ suite('CommandLineAutoApprover', () => { test('should handle complex command line patterns', () => { setAutoApproveWithCommandLine({ - "/^npm run build/": { approve: true, matchCommandLine: true }, - "/\.ps1/i": { approve: true, matchCommandLine: true } + '/^npm run build/': { approve: true, matchCommandLine: true }, + '/\.ps1/i': { approve: true, matchCommandLine: true } }); ok(isCommandLineAutoApproved('npm run build --production')); @@ -663,7 +663,7 @@ suite('CommandLineAutoApprover', () => { test('should return false for empty command line', () => { setAutoApproveWithCommandLine({ - "echo": { approve: true, matchCommandLine: true } + 'echo': { approve: true, matchCommandLine: true } }); ok(!isCommandLineAutoApproved('')); @@ -672,9 +672,9 @@ suite('CommandLineAutoApprover', () => { test('should handle mixed configuration with matchCommandLine entries', () => { setAutoApproveWithCommandLine({ - "echo": true, // Regular pattern - "ls": { approve: true, matchCommandLine: true }, // Command line pattern - "rm": { approve: true, matchCommandLine: false } // Explicit regular pattern + 'echo': true, // Regular pattern + 'ls': { approve: true, matchCommandLine: true }, // Command line pattern + 'rm': { approve: true, matchCommandLine: false } // Explicit regular pattern }); // Only the matchCommandLine: true entry should work with isCommandLineAutoApproved @@ -685,8 +685,8 @@ suite('CommandLineAutoApprover', () => { test('should handle deny patterns with matchCommandLine: true', () => { setAutoApproveWithCommandLine({ - "echo": { approve: true, matchCommandLine: true }, - "/dangerous/": { approve: false, matchCommandLine: true } + 'echo': { approve: true, matchCommandLine: true }, + '/dangerous/': { approve: false, matchCommandLine: true } }); ok(isCommandLineAutoApproved('echo hello')); @@ -696,8 +696,8 @@ suite('CommandLineAutoApprover', () => { test('should prioritize deny list over allow list for command line patterns', () => { setAutoApproveWithCommandLine({ - "/echo/": { approve: true, matchCommandLine: true }, - "/echo.*dangerous/": { approve: false, matchCommandLine: true } + '/echo/': { approve: true, matchCommandLine: true }, + '/echo.*dangerous/': { approve: false, matchCommandLine: true } }); ok(isCommandLineAutoApproved('echo hello')); @@ -706,9 +706,9 @@ suite('CommandLineAutoApprover', () => { test('should handle complex deny patterns with matchCommandLine', () => { setAutoApproveWithCommandLine({ - "npm": { approve: true, matchCommandLine: true }, - "/npm.*--force/": { approve: false, matchCommandLine: true }, - "/\.ps1.*-ExecutionPolicy/i": { approve: false, matchCommandLine: true } + 'npm': { approve: true, matchCommandLine: true }, + '/npm.*--force/': { approve: false, matchCommandLine: true }, + '/\.ps1.*-ExecutionPolicy/i': { approve: false, matchCommandLine: true } }); ok(isCommandLineAutoApproved('npm install')); @@ -719,10 +719,10 @@ suite('CommandLineAutoApprover', () => { test('should handle empty regex patterns with matchCommandLine that could cause endless loops', () => { setAutoApproveWithCommandLine({ - "//": { approve: true, matchCommandLine: true }, - "/(?:)/": { approve: true, matchCommandLine: true }, - "/*/": { approve: true, matchCommandLine: true }, // Invalid regex pattern - "/.**/": { approve: true, matchCommandLine: true } // Invalid regex pattern + '//': { approve: true, matchCommandLine: true }, + '/(?:)/': { approve: true, matchCommandLine: true }, + '/*/': { approve: true, matchCommandLine: true }, // Invalid regex pattern + '/.**/': { approve: true, matchCommandLine: true } // Invalid regex pattern }); // These patterns should not cause endless loops and should not match any commands @@ -734,10 +734,10 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns with matchCommandLine that would cause endless loops', () => { setAutoApproveWithCommandLine({ - "/a*/": { approve: true, matchCommandLine: true }, - "/b?/": { approve: true, matchCommandLine: true }, - "/(x|)*/": { approve: true, matchCommandLine: true }, - "/(?:)*/": { approve: true, matchCommandLine: true } + '/a*/': { approve: true, matchCommandLine: true }, + '/b?/': { approve: true, matchCommandLine: true }, + '/(x|)*/': { approve: true, matchCommandLine: true }, + '/(?:)*/': { approve: true, matchCommandLine: true } }); // Commands should still work normally, endless loop patterns should be safely handled @@ -749,11 +749,11 @@ suite('CommandLineAutoApprover', () => { test('should handle mixed valid and problematic regex patterns with matchCommandLine', () => { setAutoApproveWithCommandLine({ - "/^echo/": { approve: true, matchCommandLine: true }, // Valid pattern - "//": { approve: true, matchCommandLine: true }, // Empty pattern - "/^ls/": { approve: true, matchCommandLine: true }, // Valid pattern - "/a*/": { approve: true, matchCommandLine: true }, // Potential endless loop - "pwd": { approve: true, matchCommandLine: true } // Valid string pattern + '/^echo/': { approve: true, matchCommandLine: true }, // Valid pattern + '//': { approve: true, matchCommandLine: true }, // Empty pattern + '/^ls/': { approve: true, matchCommandLine: true }, // Valid pattern + '/a*/': { approve: true, matchCommandLine: true }, // Potential endless loop + 'pwd': { approve: true, matchCommandLine: true } // Valid string pattern }); ok(isCommandLineAutoApproved('echo hello')); @@ -764,11 +764,11 @@ suite('CommandLineAutoApprover', () => { test('should handle invalid regex patterns with matchCommandLine gracefully', () => { setAutoApproveWithCommandLine({ - "/*/": { approve: true, matchCommandLine: true }, // Invalid regex - nothing to repeat - "/(?:+/": { approve: true, matchCommandLine: true }, // Invalid regex - incomplete quantifier - "/[/": { approve: true, matchCommandLine: true }, // Invalid regex - unclosed character class - "/^echo/": { approve: true, matchCommandLine: true }, // Valid pattern - "ls": { approve: true, matchCommandLine: true } // Valid string pattern + '/*/': { approve: true, matchCommandLine: true }, // Invalid regex - nothing to repeat + '/(?:+/': { approve: true, matchCommandLine: true }, // Invalid regex - incomplete quantifier + '/[/': { approve: true, matchCommandLine: true }, // Invalid regex - unclosed character class + '/^echo/': { approve: true, matchCommandLine: true }, // Valid pattern + 'ls': { approve: true, matchCommandLine: true } // Valid string pattern }); // Valid patterns should still work @@ -913,8 +913,8 @@ suite('CommandLineAutoApprover', () => { test('should correctly identify default rules vs user-defined rules', () => { setAutoApproveWithDefaults( - { "echo": true, "ls": true, "pwd": false }, - { "echo": true, "cat": true } + { 'echo': true, 'ls': true, 'pwd': false }, + { 'echo': true, 'cat': true } ); strictEqual(getIsDefaultRule('echo hello'), true, 'echo is in both default and user config with same value - should be marked as default'); @@ -925,8 +925,8 @@ suite('CommandLineAutoApprover', () => { test('should mark as default when command is only in default config but not in user config', () => { setAutoApproveWithDefaults( - { "echo": true, "ls": true }, // User config (cat is NOT here) - { "echo": true, "cat": true } // Default config (cat IS here) + { 'echo': true, 'ls': true }, // User config (cat is NOT here) + { 'echo': true, 'cat': true } // Default config (cat IS here) ); // Test that merged config includes all commands @@ -943,8 +943,8 @@ suite('CommandLineAutoApprover', () => { test('should handle default rules with different values', () => { setAutoApproveWithDefaults( - { "echo": true, "rm": true }, - { "echo": false, "rm": true } + { 'echo': true, 'rm': true }, + { 'echo': false, 'rm': true } ); strictEqual(getIsDefaultRule('echo hello'), false, 'echo has different values in default vs user - should be marked as user-defined'); @@ -953,8 +953,8 @@ suite('CommandLineAutoApprover', () => { test('should handle regex patterns as default rules', () => { setAutoApproveWithDefaults( - { "/^git/": true, "/^npm/": false }, - { "/^git/": true, "/^docker/": true } + { '/^git/': true, '/^npm/': false }, + { '/^git/': true, '/^docker/': true } ); strictEqual(getIsDefaultRule('git status'), true, 'git pattern matches default - should be marked as default'); @@ -963,8 +963,8 @@ suite('CommandLineAutoApprover', () => { test('should handle mixed string and regex patterns', () => { setAutoApproveWithDefaults( - { "echo": true, "/^ls/": false }, - { "echo": true, "cat": true } + { 'echo': true, '/^ls/': false }, + { 'echo': true, 'cat': true } ); strictEqual(getIsDefaultRule('echo hello'), true, 'String pattern matching default'); @@ -974,12 +974,12 @@ suite('CommandLineAutoApprover', () => { test('should handle command line rules with isDefaultRule', () => { setAutoApproveWithDefaultsCommandLine( { - "echo": { approve: true, matchCommandLine: true }, - "ls": { approve: false, matchCommandLine: true } + 'echo': { approve: true, matchCommandLine: true }, + 'ls': { approve: false, matchCommandLine: true } }, { - "echo": { approve: true, matchCommandLine: true }, - "cat": { approve: true, matchCommandLine: true } + 'echo': { approve: true, matchCommandLine: true }, + 'cat': { approve: true, matchCommandLine: true } } ); @@ -990,12 +990,12 @@ suite('CommandLineAutoApprover', () => { test('should handle command line rules with different matchCommandLine values', () => { setAutoApproveWithDefaultsCommandLine( { - "echo": { approve: true, matchCommandLine: true }, - "ls": { approve: true, matchCommandLine: false } + 'echo': { approve: true, matchCommandLine: true }, + 'ls': { approve: true, matchCommandLine: false } }, { - "echo": { approve: true, matchCommandLine: false }, - "ls": { approve: true, matchCommandLine: false } + 'echo': { approve: true, matchCommandLine: false }, + 'ls': { approve: true, matchCommandLine: false } } ); @@ -1006,12 +1006,12 @@ suite('CommandLineAutoApprover', () => { test('should handle boolean vs object format consistency', () => { setAutoApproveWithDefaultsCommandLine( { - "echo": true, - "ls": { approve: true, matchCommandLine: true } + 'echo': true, + 'ls': { approve: true, matchCommandLine: true } }, { - "echo": true, - "ls": { approve: true, matchCommandLine: true } + 'echo': true, + 'ls': { approve: true, matchCommandLine: true } } ); @@ -1021,8 +1021,8 @@ suite('CommandLineAutoApprover', () => { test('should return undefined for noMatch cases', () => { setAutoApproveWithDefaults( - { "echo": true }, - { "cat": true } + { 'echo': true }, + { 'cat': true } ); strictEqual(getIsDefaultRule('unknown-command'), undefined, 'Command that matches neither user nor default config'); @@ -1042,7 +1042,7 @@ suite('CommandLineAutoApprover', () => { test('should handle only default config with no user overrides', () => { setAutoApproveWithDefaults( {}, - { "echo": true, "ls": false } + { 'echo': true, 'ls': false } ); strictEqual(getIsDefaultRule('echo hello'), true, 'Commands in default config should be marked as default rules even with empty user config'); @@ -1052,12 +1052,12 @@ suite('CommandLineAutoApprover', () => { test('should handle complex nested object rules', () => { setAutoApproveWithDefaultsCommandLine( { - "npm": { approve: true, matchCommandLine: true }, - "git": { approve: false, matchCommandLine: false } + 'npm': { approve: true, matchCommandLine: true }, + 'git': { approve: false, matchCommandLine: false } }, { - "npm": { approve: true, matchCommandLine: true }, - "docker": { approve: true, matchCommandLine: true } + 'npm': { approve: true, matchCommandLine: true }, + 'docker': { approve: true, matchCommandLine: true } } ); @@ -1070,8 +1070,8 @@ suite('CommandLineAutoApprover', () => { os = OperatingSystem.Windows; setAutoApproveWithDefaults( - { "Get-Process": true }, - { "Get-Process": true } + { 'Get-Process': true }, + { 'Get-Process': true } ); strictEqual(getIsDefaultRule('Get-Process'), true, 'Case-insensitive PowerShell command matching default'); @@ -1081,8 +1081,8 @@ suite('CommandLineAutoApprover', () => { test('should use structural equality for object comparison', () => { // Test that objects with same content but different instances are treated as equal - const userConfig = { "test": { approve: true, matchCommandLine: true } }; - const defaultConfig = { "test": { approve: true, matchCommandLine: true } }; + const userConfig = { 'test': { approve: true, matchCommandLine: true } }; + const defaultConfig = { 'test': { approve: true, matchCommandLine: true } }; setAutoApproveWithDefaultsCommandLine(userConfig, defaultConfig); @@ -1090,8 +1090,8 @@ suite('CommandLineAutoApprover', () => { }); test('should detect structural differences in objects', () => { - const userConfig = { "test": { approve: true, matchCommandLine: true } }; - const defaultConfig = { "test": { approve: true, matchCommandLine: false } }; + const userConfig = { 'test': { approve: true, matchCommandLine: true } }; + const defaultConfig = { 'test': { approve: true, matchCommandLine: false } }; setAutoApproveWithDefaultsCommandLine(userConfig, defaultConfig); @@ -1100,12 +1100,12 @@ suite('CommandLineAutoApprover', () => { test('should handle mixed types correctly', () => { const userConfig = { - "cmd1": true, - "cmd2": { approve: false, matchCommandLine: true } + 'cmd1': true, + 'cmd2': { approve: false, matchCommandLine: true } }; const defaultConfig = { - "cmd1": true, - "cmd2": { approve: false, matchCommandLine: true } + 'cmd1': true, + 'cmd2': { approve: false, matchCommandLine: true } }; setAutoApproveWithDefaultsCommandLine(userConfig, defaultConfig); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 7672927a8a9..671a513798b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -438,8 +438,8 @@ suite('RunInTerminalTool', () => { test('should handle matchCommandLine: true patterns', async () => { setAutoApprove({ - "/dangerous/": { approve: false, matchCommandLine: true }, - "echo": { approve: true, matchCommandLine: true } + '/dangerous/': { approve: false, matchCommandLine: true }, + 'echo': { approve: true, matchCommandLine: true } }); const result1 = await executeToolTest({ command: 'echo hello world' }); @@ -451,8 +451,8 @@ suite('RunInTerminalTool', () => { test('should only approve when neither sub-commands or command lines are denied', async () => { setAutoApprove({ - "foo": true, - "/^foo$/": { approve: false, matchCommandLine: true }, + 'foo': true, + '/^foo$/': { approve: false, matchCommandLine: true }, }); const result1 = await executeToolTest({ command: 'foo' }); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts index 2914c4cb446..8630afb6430 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts @@ -335,9 +335,9 @@ suite('TerminalCompletionModel', function () { ]; const model = new TerminalCompletionModel(items, new LineContext('git checkout ', 0)); assertItems(model, [ - { label: "main", description: "Main branch" }, - { label: "master", description: "Master branch" }, - { label: "feature-branch", description: "Feature branch" }, + { label: 'main', description: 'Main branch' }, + { label: 'master', description: 'Master branch' }, + { label: 'feature-branch', description: 'Feature branch' }, ]); }); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.escaping.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.escaping.test.ts index 4899bea4a11..2eb637e7d6f 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.escaping.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.escaping.test.ts @@ -17,7 +17,7 @@ suite('escapeTerminalCompletionLabel', () => { { char: ']', label: 'abc]', expected: 'abc\\]' }, { char: '(', label: '(abc', expected: '\\(abc' }, { char: ')', label: 'abc)', expected: 'abc\\)' }, - { char: '\'', label: "'abc", expected: "\\'abc" }, + { char: '\'', label: `'abc`, expected: `\\'abc` }, { char: '"', label: '"abc', expected: '\\"abc' }, { char: '\\', label: 'abc\\', expected: 'abc\\\\' }, { char: '`', label: '`abc', expected: '\\`abc' }, diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts index 1646cc01bb3..5da4586da86 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/treeProjection.test.ts @@ -173,10 +173,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { // sortText causes order to change harness.pushDiff({ op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { sortText: "z" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { sortText: 'z' } } }, { op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { sortText: "a" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { sortText: 'a' } } }); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'ab' }, { e: 'aa' }] }, { e: 'b' } @@ -184,20 +184,20 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { // label causes order to change harness.pushDiff({ op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { sortText: undefined, label: "z" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { sortText: undefined, label: 'z' } } }, { op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { sortText: undefined, label: "a" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { sortText: undefined, label: 'a' } } }); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'a' }, { e: 'z' }] }, { e: 'b' } ]); harness.pushDiff({ op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: "a2" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: 'a2' } } }, { op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: "z2" } } + item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: 'z2' } } }); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'a2' }, { e: 'z2' }] }, { e: 'b' } @@ -212,7 +212,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { // sortText causes order to change harness.pushDiff({ op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { error: "bad" } } + item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { error: 'bad' } } }); assert.deepStrictEqual(harness.flush(), [ { e: 'a' }, { e: 'b' } @@ -223,7 +223,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { ]); harness.pushDiff({ op: TestDiffOpType.Update, - item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { error: "badder" } } + item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { error: 'badder' } } }); assert.deepStrictEqual(harness.flush(), [ { e: 'a', children: [{ e: 'badder' }, { e: 'aa' }, { e: 'ab' }] }, { e: 'b' } diff --git a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts index c97e4ec22cf..58a7bd2fb56 100644 --- a/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testCoverage.test.ts @@ -111,20 +111,20 @@ suite('TestCoverage', () => { assert.deepStrictEqual(changes, [ [ - "file:///", - "file:///", - "file:///", - "file:///path", - "file:///path/to", - "file:///path/to/file", + 'file:///', + 'file:///', + 'file:///', + 'file:///path', + 'file:///path/to', + 'file:///path/to/file', ], [ - "file:///", - "file:///", - "file:///", - "file:///path", - "file:///path/to", - "file:///path/to/file2", + 'file:///', + 'file:///', + 'file:///', + 'file:///path', + 'file:///path/to', + 'file:///path/to/file2', ], ]); }); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index cd3b770a0d1..ebbda208696 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -1229,17 +1229,17 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(actual.application, undefined); assert.deepStrictEqual(actual.userValue, {}); assert.deepStrictEqual(actual.workspaceValue, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); assert.strictEqual(actual.workspaceFolderValue, undefined); assert.deepStrictEqual(actual.value, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); @@ -1285,17 +1285,17 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(actual.application, undefined); assert.deepStrictEqual(actual.userValue, {}); assert.deepStrictEqual(actual.workspaceValue, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); assert.strictEqual(actual.workspaceFolderValue, undefined); assert.deepStrictEqual(actual.value, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); @@ -1317,17 +1317,17 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(actual.application, undefined); assert.deepStrictEqual(actual.userValue, {}); assert.deepStrictEqual(actual.workspaceValue, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); assert.strictEqual(actual.workspaceFolderValue, undefined); assert.deepStrictEqual(actual.value, { - "configurationService": { - "tasks": { - "testSetting": "tasksValue" + 'configurationService': { + 'tasks': { + 'testSetting': 'tasksValue' } } }); diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index f1958c90006..9d33b6e6d5e 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -1014,9 +1014,9 @@ suite('ConfigurationResolverExpression', () => { test('resolves nested values 2 (#245798)', () => { const expr = ConfigurationResolverExpression.parse({ env: { - SITE: "${input:site}", - TLD: "${input:tld}", - HOST: "${input:host}", + SITE: '${input:site}', + TLD: '${input:tld}', + HOST: '${input:host}', }, }); @@ -1041,7 +1041,7 @@ suite('ConfigurationResolverExpression', () => { test('out-of-order key resolution (#248550)', () => { const expr = ConfigurationResolverExpression.parse({ - '${input:key}': "${input:value}", + '${input:key}': '${input:value}', }); for (const r of expr.unresolved()) { diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index 825a3576717..c76cae22c4f 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -51,7 +51,7 @@ suite('HistoryService', function () { configurationService.setUserConfiguration('workbench.editor.navigationScope', 'editor'); } if (configureSearchExclude) { - configurationService.setUserConfiguration('search', { exclude: { "**/node_modules/**": true } }); + configurationService.setUserConfiguration('search', { exclude: { '**/node_modules/**': true } }); } instantiationService.stub(IConfigurationService, configurationService); diff --git a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts index 5adf99af9d0..72b1043fd52 100644 --- a/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/browser/queryBuilder.test.ts @@ -1094,10 +1094,10 @@ suite('QueryBuilder', () => { } ); assert.deepEqual(query.includePattern, { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, }); assert.strictEqual(query.folderQueries.length, 0); }); @@ -1111,10 +1111,10 @@ suite('QueryBuilder', () => { } ); assert.deepEqual(query.includePattern, { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, }); assert.strictEqual(query.folderQueries.length, 1); }); @@ -1128,10 +1128,10 @@ suite('QueryBuilder', () => { } ); assert.deepEqual(query.excludePattern, { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, }); assert.strictEqual(query.folderQueries.length, 0); }); @@ -1145,10 +1145,10 @@ suite('QueryBuilder', () => { } ); assert.deepEqual(query.excludePattern, { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, }); assert.strictEqual(query.folderQueries.length, 1); }); @@ -1165,16 +1165,16 @@ suite('QueryBuilder', () => { assert.deepEqual(query.excludePattern, [ { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, }, { - "**/foo/*/**": true, - "**/foo/*": true, - "**/bar/*/**": true, - "**/bar/*": true, + '**/foo/*/**': true, + '**/foo/*': true, + '**/bar/*/**': true, + '**/bar/*': true, } ]); assert.strictEqual(query.folderQueries.length, 1); @@ -1193,10 +1193,10 @@ suite('QueryBuilder', () => { assert.deepEqual(query.excludePattern, { uri: ROOT_1_URI, pattern: { - "**/*.js/**": true, - "**/*.js": true, - "**/*.ts/**": true, - "**/*.ts": true, + '**/*.js/**': true, + '**/*.js': true, + '**/*.ts/**': true, + '**/*.ts': true, } }); assert.strictEqual(query.folderQueries.length, 1); diff --git a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts index 0508e090ca2..9b2be6eac9e 100644 --- a/src/vs/workbench/services/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/services/search/test/common/queryBuilder.test.ts @@ -22,13 +22,13 @@ suite('QueryBuilderCommon', () => { }); test('resolveResourcesForSearchIncludes passes through paths without special glob characters', () => { - const actual = resolveResourcesForSearchIncludes([URI.file(isWindows ? "C:\\testWorkspace\\pages\\blog" : "/testWorkspace/pages/blog")], context); - assert.deepStrictEqual(actual, ["./pages/blog"]); + const actual = resolveResourcesForSearchIncludes([URI.file(isWindows ? 'C:\\testWorkspace\\pages\\blog' : '/testWorkspace/pages/blog')], context); + assert.deepStrictEqual(actual, ['./pages/blog']); }); test('resolveResourcesForSearchIncludes escapes paths with special characters', () => { - const actual = resolveResourcesForSearchIncludes([URI.file(isWindows ? "C:\\testWorkspace\\pages\\blog\\[postId]" : "/testWorkspace/pages/blog/[postId]")], context); - assert.deepStrictEqual(actual, ["./pages/blog/[[]postId[]]"]); + const actual = resolveResourcesForSearchIncludes([URI.file(isWindows ? 'C:\\testWorkspace\\pages\\blog\\[postId]' : '/testWorkspace/pages/blog/[postId]')], context); + assert.deepStrictEqual(actual, ['./pages/blog/[[]postId[]]']); }); test('escapeGlobPattern properly escapes square brackets for literal matching', () => { From 40fd1d8ebc3be8bdb1595128960e855d1856ef55 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:16:00 -0700 Subject: [PATCH 1195/4355] Fix string name --- extensions/typescript-language-features/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 2a04d409f29..81d1611e8da 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -221,7 +221,7 @@ "typescript.implementationsCodeLens.showOnAllClassMethods": { "type": "boolean", "default": false, - "description": "%typescript.implementationsCodeLens.showOnAllClassMethods.desc%", + "description": "%typescript.implementationsCodeLens.showOnAllClassMethods%", "scope": "window" }, "typescript.reportStyleChecksAsWarnings": { From 29eb45f7fe6f5c9ae275aa830048290d3f35f055 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:58:57 -0700 Subject: [PATCH 1196/4355] Make sure we clear diagnostics when switching to tsgo --- .../typescript-language-features/src/typescriptServiceClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index dc66ab49fed..207698de92f 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -235,7 +235,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.apiVersion.fullVersionString; }); - this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem); + this.diagnosticsManager = this._register(new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem)); this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory); this._register(this.pluginManager.onDidUpdateConfig(update => { From 98997404e6e98636d82a47771a1cd1ca3c71708e Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:24:26 -0700 Subject: [PATCH 1197/4355] Show progress indicator when loading chat session items (#271212) * add loading indicator * set individual props * localize and simplification * update text * Move style to css and use themeicon * remove stale entry --------- Co-authored-by: vijay upadya --- .../contrib/chat/browser/chatEditor.ts | 101 +++++++++++++++--- .../chat/browser/media/chatSessions.css | 22 ++++ 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 9bfb0f0aeac..11bb8513b28 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -4,8 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import * as nls from '../../../../nls.js'; import { raceCancellationError } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -51,6 +55,8 @@ export class ChatEditor extends EditorPane { private _memento: Memento | undefined; private _viewState: IChatViewState | undefined; private dimension = new dom.Dimension(0, 0); + private _loadingContainer: HTMLElement | undefined; + private _editorContainer: HTMLElement | undefined; constructor( group: IEditorGroup, @@ -71,6 +77,7 @@ export class ChatEditor extends EditorPane { } protected override createEditor(parent: HTMLElement): void { + this._editorContainer = parent; this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true); @@ -128,6 +135,48 @@ export class ChatEditor extends EditorPane { super.clearInput(); } + private showLoadingInChatWidget(message: string): void { + if (!this._editorContainer) { + return; + } + + // If already showing, just update text + if (this._loadingContainer) { + const existingText = this._loadingContainer.querySelector('.chat-loading-content span'); + if (existingText) { + existingText.textContent = message; + return; // aria-live will announce the text change + } + this.hideLoadingInChatWidget(); // unexpected structure + } + + // Mark container busy for assistive technologies + this._editorContainer.setAttribute('aria-busy', 'true'); + + this._loadingContainer = dom.append(this._editorContainer, dom.$('.chat-loading-overlay')); + // Accessibility: announce loading state politely without stealing focus + this._loadingContainer.setAttribute('role', 'status'); + this._loadingContainer.setAttribute('aria-live', 'polite'); + // Rely on live region text content instead of aria-label to avoid duplicate announcements + this._loadingContainer.tabIndex = -1; // ensure it isn't focusable + const loadingContent = dom.append(this._loadingContainer, dom.$('.chat-loading-content')); + const spinner = renderIcon(ThemeIcon.modify(Codicon.loading, 'spin')); + spinner.setAttribute('aria-hidden', 'true'); + loadingContent.appendChild(spinner); + const text = dom.append(loadingContent, dom.$('span')); + text.textContent = message; + } + + private hideLoadingInChatWidget(): void { + if (this._loadingContainer) { + this._loadingContainer.remove(); + this._loadingContainer = undefined; + } + if (this._editorContainer) { + this._editorContainer.removeAttribute('aria-busy'); + } + } + override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); if (token.isCancellationRequested) { @@ -141,29 +190,49 @@ export class ChatEditor extends EditorPane { let isContributedChatSession = false; const chatSessionType = getChatSessionType(input); if (chatSessionType !== 'local') { - await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); - const contributions = this.chatSessionsService.getAllChatSessionContributions(); - const contribution = contributions.find(c => c.type === chatSessionType); - if (contribution) { - this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); - isContributedChatSession = true; - } else { - this.widget.unlockFromCodingAgent(); + // Show single loading state for contributed sessions + const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); + this.showLoadingInChatWidget(loadingMessage); + + try { + await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); + const contributions = this.chatSessionsService.getAllChatSessionContributions(); + const contribution = contributions.find(c => c.type === chatSessionType); + if (contribution) { + this.widget.lockToCodingAgent(contribution.name, contribution.displayName, contribution.type); + isContributedChatSession = true; + } else { + this.widget.unlockFromCodingAgent(); + } + } catch (error) { + this.hideLoadingInChatWidget(); + throw error; } } else { this.widget.unlockFromCodingAgent(); } - const editorModel = await raceCancellationError(input.resolve(), token); + try { + const editorModel = await raceCancellationError(input.resolve(), token); - if (!editorModel) { - throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); - } - const viewState = options?.viewState ?? input.options.viewState; - this.updateModel(editorModel.model, viewState); + if (!editorModel) { + throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); + } + + // Hide loading state before updating model + if (chatSessionType !== 'local') { + this.hideLoadingInChatWidget(); + } - if (isContributedChatSession && options?.title?.preferred) { - editorModel.model.setCustomTitle(options.title.preferred); + const viewState = options?.viewState ?? input.options.viewState; + this.updateModel(editorModel.model, viewState); + + if (isContributedChatSession && options?.title?.preferred) { + editorModel.model.setCustomTitle(options.title.preferred); + } + } catch (error) { + this.hideLoadingInChatWidget(); + throw error; } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css index 1bd6b83280a..3ba12e266f7 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css @@ -260,3 +260,25 @@ .chat-sessions-tree-container .chat-session-item[data-history-item="true"]:hover { background-color: var(--vscode-list-hoverBackground); } + +/* Chat editor loading overlay styles */ +.chat-loading-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--vscode-editor-background); + z-index: 1000; +} + +.chat-loading-overlay .chat-loading-content { + display: flex; + align-items: center; + gap: 8px; + color: var(--vscode-editor-foreground); +} + +.chat-loading-overlay .codicon { + font-size: 16px; +} From 22060b6e9efb8a9770c9c7bc58231824046edd32 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 15 Oct 2025 11:29:09 -0700 Subject: [PATCH 1198/4355] Fix issues with monitoring dependency tasks (#270839) --- .../tasks/browser/terminalTaskSystem.ts | 2 +- .../executeStrategy/executeStrategy.ts | 5 +- .../chatAgentTools/browser/taskHelpers.ts | 46 +++++++++++++++++-- .../browser/tools/monitoring/outputMonitor.ts | 16 +++---- .../browser/tools/monitoring/types.ts | 1 + .../tools/task/createAndRunTaskTool.ts | 4 +- .../browser/tools/task/getTaskOutputTool.ts | 4 +- .../browser/tools/task/runTaskTool.ts | 4 +- 8 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index b3bde5ba6fe..66f7156ac2a 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -71,7 +71,7 @@ interface IActiveTerminalData { count: IInstanceCount; } -interface IReconnectionTaskData { +export interface IReconnectionTaskData { label: string; id: string; lastTask: string; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts index 0b317bbbe5c..bcc764d753e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/executeStrategy.ts @@ -106,10 +106,13 @@ const YN_PAIRED_RE = /(?:\(|\[)\s*(?:y(?:es)?\s*\/\s*n(?:o)?|n(?:o)?\s*\/\s*y(?: // Same as YN_PAIRED_RE but allows a preceding '?' or ':' and optional wrappers e.g. "Continue? (y/n)" or "Overwrite: [yes/no]" const YN_AFTER_PUNCT_RE = /[?:]\s*(?:\(|\[)?\s*y(?:es)?\s*\/\s*n(?:o)?\s*(?:\]|\))?\s+$/i; +// Confirmation prompts ending with (y) e.g. "Ok to proceed? (y)" +const CONFIRM_Y_RE = /\(y\)\s*$/i; + const LINE_ENDS_WITH_COLON_RE = /:\s*$/; export function detectsInputRequiredPattern(cursorLine: string): boolean { - return PS_CONFIRM_RE.test(cursorLine) || YN_PAIRED_RE.test(cursorLine) || YN_AFTER_PUNCT_RE.test(cursorLine) || LINE_ENDS_WITH_COLON_RE.test(cursorLine.trim()); + return PS_CONFIRM_RE.test(cursorLine) || YN_PAIRED_RE.test(cursorLine) || YN_AFTER_PUNCT_RE.test(cursorLine) || CONFIRM_Y_RE.test(cursorLine) || LINE_ENDS_WITH_COLON_RE.test(cursorLine.trim()); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index ab46476d4e2..879c9bda3c8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -20,6 +20,7 @@ import { getOutput } from './outputHelpers.js'; import { OutputMonitor } from './tools/monitoring/outputMonitor.js'; import { IExecution, IPollingResult, OutputMonitorState } from './tools/monitoring/types.js'; import { Event } from '../../../../../base/common/event.js'; +import { IReconnectionTaskData } from '../../../tasks/browser/terminalTaskSystem.js'; export function getTaskDefinition(id: string) { @@ -153,7 +154,7 @@ export async function collectTerminalResults( progress: ToolProgress, token: CancellationToken, disposableStore: DisposableStore, - isActive?: () => Promise, + isActive?: (task: Task) => Promise, dependencyTasks?: Task[] ): Promise getOutput(instance) ?? '', - isActive, - task, + task: terminalTask, + isActive: isActive ? () => isActive(terminalTask) : undefined, instance, dependencyTasks, sessionId: invocationContext.sessionId }; + const outputMonitor = disposableStore.add(instantiationService.createInstance(OutputMonitor, execution, taskProblemPollFn, invocationContext, token, task._label)); await Event.toPromise(outputMonitor.onDidFinishCommand); const pollingResult = outputMonitor.pollingResult; results.push({ - name: instance.shellLaunchConfig.name ?? 'unknown', + name: instance.shellLaunchConfig.name ?? instance.title ?? 'unknown', output: pollingResult?.output ?? '', pollDurationMs: pollingResult?.pollDurationMs ?? 0, resources: pollingResult?.resources, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index a22daf83ec4..e257d239e05 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -286,8 +286,9 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { let waited = 0; let consecutiveIdleEvents = 0; let hasReceivedData = false; - let currentOutput: string | undefined; - let onDataDisposable = Disposable.None; + const onDataDisposable = execution.instance.onData((_data) => { + hasReceivedData = true; + }); try { while (!token.isCancellationRequested && waited < maxWaitMs) { @@ -295,13 +296,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { await timeout(waitTime, token); waited += waitTime; currentInterval = Math.min(currentInterval * 2, maxInterval); - if (currentOutput === undefined) { - currentOutput = execution.getOutput(); - onDataDisposable = execution.instance.onData((data) => { - hasReceivedData = true; - currentOutput += data; - }); - } + const currentOutput = execution.getOutput(); const promptResult = detectsInputRequiredPattern(currentOutput); if (promptResult) { this._state = OutputMonitorState.Idle; @@ -325,7 +320,8 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { } if (recentlyIdle || isActive === false) { - return OutputMonitorState.Idle; + this._state = OutputMonitorState.Idle; + return this._state; } } } finally { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts index 9c82240dc01..cd6510b14e7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/types.ts @@ -19,6 +19,7 @@ export interface IExecution { getOutput: (marker?: XtermMarker) => string; isActive?: () => Promise; task?: Task | Pick; + dependencyTasks?: Task[]; instance: Pick; sessionId: string | undefined; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts index 51b81177c23..b045488aa24 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts @@ -115,7 +115,7 @@ export class CreateAndRunTaskTool implements IToolImpl { _progress, token, store, - () => this._isTaskActive(task), + (terminalTask) => this._isTaskActive(terminalTask), dependencyTasks ); store.dispose(); @@ -146,7 +146,7 @@ export class CreateAndRunTaskTool implements IToolImpl { private async _isTaskActive(task: Task): Promise { const activeTasks = await this._tasksService.getActiveTasks(); - return activeTasks?.includes(task) ?? false; + return activeTasks?.some((t) => t._id === task._id); } async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts index 22a752a23df..dd04861251d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/getTaskOutputTool.ts @@ -102,7 +102,7 @@ export class GetTaskOutputTool extends Disposable implements IToolImpl { _progress, token, store, - () => this._isTaskActive(task), + (terminalTask) => this._isTaskActive(terminalTask), dependencyTasks ); store.dispose(); @@ -132,6 +132,6 @@ export class GetTaskOutputTool extends Disposable implements IToolImpl { } private async _isTaskActive(task: Task): Promise { const activeTasks = await this._tasksService.getActiveTasks(); - return activeTasks?.includes(task) ?? false; + return activeTasks?.some((t) => t._id === task._id); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts index beb83addb78..6d6bbcfd7b7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts @@ -75,7 +75,7 @@ export class RunTaskTool implements IToolImpl { _progress, token, store, - () => this._isTaskActive(task), + (terminalTask) => this._isTaskActive(terminalTask), dependencyTasks ); store.dispose(); @@ -107,7 +107,7 @@ export class RunTaskTool implements IToolImpl { private async _isTaskActive(task: Task): Promise { const activeTasks = await this._tasksService.getActiveTasks(); - return Promise.resolve(activeTasks?.includes(task)); + return Promise.resolve(activeTasks?.some((t) => t._id === task._id)); } async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { From 2f5b1d0cc40622a33feb807fb5357ee4bbf84015 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:33:45 -0700 Subject: [PATCH 1199/4355] Enforce single quote strings in tests too Fixes #271473 Final pass of fixes and removes the exception for test files --- eslint.config.js | 1 - .../src/areas/multiroot/multiroot.test.ts | 2 +- .../src/areas/task/task-quick-pick.test.ts | 22 +++++++++---------- .../terminal/terminal-stickyScroll.test.ts | 6 ++--- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 6c65c69287b..a8cb68fee70 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -326,7 +326,6 @@ export default tseslint.config( 'local/code-must-use-super-dispose': 'off', 'local/code-no-test-only': 'error', 'local/code-no-test-async-suite': 'warn', - 'local/code-no-unexternalized-strings': 'off', 'local/code-must-use-result': [ 'warn', [ diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index c4e6e46f9a4..cedbac51e7a 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -32,7 +32,7 @@ function createWorkspaceFile(workspacePath: string): string { 'npm.fetchOnlinePackageInfo': false, 'npm.autoDetect': 'off', 'workbench.editor.languageDetection': false, - "workbench.localHistory.enabled": false + 'workbench.localHistory.enabled': false } }; diff --git a/test/smoke/src/areas/task/task-quick-pick.test.ts b/test/smoke/src/areas/task/task-quick-pick.test.ts index fd1610be941..7f7aeabe55d 100644 --- a/test/smoke/src/areas/task/task-quick-pick.test.ts +++ b/test/smoke/src/areas/task/task-quick-pick.test.ts @@ -24,9 +24,9 @@ export function setup(options?: { skipSuite: boolean }) { }); describe('Tasks: Run Task', () => { - const label = "name"; - const type = "shell"; - const command = "echo 'test'"; + const label = 'name'; + const type = 'shell'; + const command = `echo 'test'`; it('hide property - true', async () => { await task.configureTask({ type, command, label, hide: true }); await task.assertTasks(label, [], 'run'); @@ -40,26 +40,26 @@ export function setup(options?: { skipSuite: boolean }) { await task.assertTasks(label, [{ label }], 'run'); }); (options?.skipSuite ? it.skip : it.skip)('icon - icon only', async () => { - const config = { label, type, command, icon: { id: "lightbulb" } }; + const config = { label, type, command, icon: { id: 'lightbulb' } }; await task.configureTask(config); await task.assertTasks(label, [config], 'run'); }); (options?.skipSuite ? it.skip : it.skip)('icon - color only', async () => { - const config = { label, type, command, icon: { color: "terminal.ansiRed" } }; + const config = { label, type, command, icon: { color: 'terminal.ansiRed' } }; await task.configureTask(config); - await task.assertTasks(label, [{ label, type, command, icon: { color: "Red" } }], 'run'); + await task.assertTasks(label, [{ label, type, command, icon: { color: 'Red' } }], 'run'); }); (options?.skipSuite ? it.skip : it.skip)('icon - icon & color', async () => { - const config = { label, type, command, icon: { id: "lightbulb", color: "terminal.ansiRed" } }; + const config = { label, type, command, icon: { id: 'lightbulb', color: 'terminal.ansiRed' } }; await task.configureTask(config); - await task.assertTasks(label, [{ label, type, command, icon: { id: "lightbulb", color: "Red" } }], 'run'); + await task.assertTasks(label, [{ label, type, command, icon: { id: 'lightbulb', color: 'Red' } }], 'run'); }); }); //TODO: why won't this command run describe.skip('Tasks: Configure Task', () => { - const label = "name"; - const type = "shell"; - const command = "echo 'test'"; + const label = 'name'; + const type = 'shell'; + const command = `echo 'test'`; describe('hide', () => { it('true should still show the task', async () => { await task.configureTask({ type, command, label, hide: true }); diff --git a/test/smoke/src/areas/terminal/terminal-stickyScroll.test.ts b/test/smoke/src/areas/terminal/terminal-stickyScroll.test.ts index 67871470bee..1859494bbeb 100644 --- a/test/smoke/src/areas/terminal/terminal-stickyScroll.test.ts +++ b/test/smoke/src/areas/terminal/terminal-stickyScroll.test.ts @@ -71,13 +71,13 @@ export function setup(options?: { skipSuite: boolean }) { await beforeEachSetup(); // Standard multi-line prompt - await checkCommandAndOutput('sticky scroll 1', 0, "Multi-line\\r\\nPrompt> ", 2); + await checkCommandAndOutput('sticky scroll 1', 0, 'Multi-line\\r\\nPrompt> ', 2); // New line before prompt - await checkCommandAndOutput('sticky scroll 2', 0, "\\r\\nMulti-line Prompt> ", 1); + await checkCommandAndOutput('sticky scroll 2', 0, '\\r\\nMulti-line Prompt> ', 1); // New line before multi-line prompt - await checkCommandAndOutput('sticky scroll 3', 0, "\\r\\nMulti-line\\r\\nPrompt> ", 2); + await checkCommandAndOutput('sticky scroll 3', 0, '\\r\\nMulti-line\\r\\nPrompt> ', 2); }); }); } From b08c2247ae702dd155a6d7874c3a2fd8518b3b24 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Oct 2025 21:08:40 +0200 Subject: [PATCH 1200/4355] chat - enable completions/NES for no-auth (#271563) --- .../contrib/chat/browser/chatStatus.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index d67ef327324..dfa6c119766 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -286,13 +286,19 @@ function isNewUser(chatEntitlementService: IChatEntitlementService): boolean { } function canUseCopilot(chatEntitlementService: IChatEntitlementService): boolean { - const newUser = isNewUser(chatEntitlementService); - const disabled = chatEntitlementService.sentiment.disabled || chatEntitlementService.sentiment.untrusted; - const signedOut = chatEntitlementService.entitlement === ChatEntitlement.Unknown; - const free = chatEntitlementService.entitlement === ChatEntitlement.Free; - const allFreeQuotaReached = free && chatEntitlementService.quotas.chat?.percentRemaining === 0 && chatEntitlementService.quotas.completions?.percentRemaining === 0; + if (!chatEntitlementService.sentiment.installed || chatEntitlementService.sentiment.disabled || chatEntitlementService.sentiment.untrusted) { + return false; // copilot not installed or not enabled + } + + if (chatEntitlementService.entitlement === ChatEntitlement.Unknown || chatEntitlementService.entitlement === ChatEntitlement.Available) { + return chatEntitlementService.anonymous; // signed out or not-yet-signed-up users can only use Copilot if anonymous access is allowed + } + + if (chatEntitlementService.entitlement === ChatEntitlement.Free && chatEntitlementService.quotas.chat?.percentRemaining === 0 && chatEntitlementService.quotas.completions?.percentRemaining === 0) { + return false; // free user with no quota left + } - return !newUser && !signedOut && !allFreeQuotaReached && !disabled; + return true; } function isCompletionsEnabled(configurationService: IConfigurationService, modeId: string = '*'): boolean { @@ -423,7 +429,7 @@ class ChatStatusDashboard extends Disposable { else if (this.chatEntitlementService.anonymous && this.chatEntitlementService.sentiment.installed) { addSeparator(localize('anonymousTitle', "Copilot Usage")); - this.createQuotaIndicator(this.element, disposables, localize('quotaDisabled', "Disabled"), localize('completionsLabel', "Code completions"), false); // TODO@bpasero revisit this in the future when Completions are supported + this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('completionsLabel', "Code completions"), false); this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('chatsLabel', "Chat messages"), false); } From 11190d75283c2d3937de5de17b76aea1c6114e48 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 15 Oct 2025 16:15:27 -0400 Subject: [PATCH 1201/4355] rm `isActive` check (#271582) rm isActive check --- .../chatAgentTools/browser/tools/monitoring/outputMonitor.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index e257d239e05..9fc673c68ef 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -313,11 +313,6 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { const recentlyIdle = consecutiveIdleEvents >= PollingConsts.MinIdleEvents; const isActive = execution.isActive ? await execution.isActive() : undefined; this._logService.trace(`OutputMonitor: waitForIdle check: waited=${waited}ms, recentlyIdle=${recentlyIdle}, isActive=${isActive}`); - // Keep polling if still active with no recent data - if (recentlyIdle && isActive === true) { - consecutiveIdleEvents = 0; - continue; - } if (recentlyIdle || isActive === false) { this._state = OutputMonitorState.Idle; From 411c8b890c7d19cc12f8270ad439fea0cbc9c862 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Oct 2025 13:37:35 -0700 Subject: [PATCH 1202/4355] Tweak to LMProxy API (#271581) --- .../vscode.proposed.chatParticipantPrivate.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index 9f90021eeba..5f269f878e3 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -288,6 +288,14 @@ declare module 'vscode' { // #region LanguageModelProxyProvider + /** + * Duplicated so that this proposal and languageModelProxy can be independent. + */ + export interface LanguageModelProxyInfo { + readonly uri: Uri; + readonly key: string; + } + export interface LanguageModelProxyProvider { provideModelProxy(forExtensionId: string, token: CancellationToken): ProviderResult; } From 7a6864552ef8ad80ae8cbdd3524e3df9cb7586e2 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:06:32 -0700 Subject: [PATCH 1203/4355] Add example fix for unexternalized strings Adds a fix (off by default) for converting double quoted to single quoted string --- .../code-no-unexternalized-strings.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.eslint-plugin-local/code-no-unexternalized-strings.ts b/.eslint-plugin-local/code-no-unexternalized-strings.ts index 74c712e56fd..7cf7b2f38ee 100644 --- a/.eslint-plugin-local/code-no-unexternalized-strings.ts +++ b/.eslint-plugin-local/code-no-unexternalized-strings.ts @@ -15,6 +15,15 @@ function isDoubleQuoted(node: TSESTree.StringLiteral): boolean { return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; } +/** + * Enable bulk fixing double-quoted strings to single-quoted strings with the --fix eslint flag + * + * Disabled by default as this is often not the desired fix. Instead the string should be localized. However it is + * useful for bulk conversations of existing code. + */ +const enableDoubleToSingleQuoteFixes = false; + + export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; @@ -27,6 +36,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { badMessage: 'Message argument to \'{{message}}\' must be a string literal.' }, schema: false, + fixable: enableDoubleToSingleQuoteFixes ? 'code' : undefined, }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { @@ -112,7 +122,30 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { // (1) // report all strings that are in double quotes for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); + context.report({ + loc: node.loc, + messageId: 'doubleQuoted', + fix: enableDoubleToSingleQuoteFixes ? (fixer) => { + // Get the raw string content, unescaping any escaped quotes + const content = (node as ESTree.SimpleLiteral).raw! + .slice(1, -1) + .replace(/(? Date: Wed, 15 Oct 2025 17:26:18 -0400 Subject: [PATCH 1204/4355] bring back copilot terminal icon (#271594) --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 aefa4413896..72d099d21ea 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -640,7 +640,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { }; } - return defaultProfile; + // Setting icon: undefined allows the system to use the default Copilot terminal icon (not overridden or removed) + return { ...defaultProfile, icon: undefined }; } private async _getCopilotShell(): Promise { From d814050cfc5cf0bad0f874dcaa60233e0b724ea8 Mon Sep 17 00:00:00 2001 From: Sam Tran Date: Wed, 15 Oct 2025 16:57:43 -0500 Subject: [PATCH 1205/4355] Ask for requestIds when labeling capi issues (#271592) Update capi slack message in commands.json Provide instructions for users to supply a requestId to help with investigations. --- .github/commands.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index 374010f98ee..daaeebf243c 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -632,8 +632,9 @@ "removeLabel": "~capi", "assign": [ "samvantran", - "thispaul" - ] + "sharonlo" + ], + "comment": "Thank you for creating this issue! Please provide one or more `requestIds` to help the platform team investigate. You can follow instructions [found here](https://github.com/microsoft/vscode/wiki/Copilot-Issues#language-model-requests-and-responses) to locate the `requestId` value.\n\nHappy Coding!" }, { "type": "label", From f8b5d7523f0cc29e0b9556b17cd5c42185b8a3b4 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 15 Oct 2025 16:59:41 -0700 Subject: [PATCH 1206/4355] Refactor Build task instructions with tool references --- .github/copilot-instructions.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 29bac72ad52..ef2d6e8aa67 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -48,12 +48,10 @@ Each extension follows the standard VS Code extension structure with `package.js ## Validating TypeScript changes -You MUST check compilation output before running ANY script or declaring work complete! +MANDATORY: Always check the `VS Code - Build` watch task output (via #get_task_output) for compilation errors before running ANY script or declaring work complete, then fix all compilation errors before moving forward. -1. **ALWAYS** check the `VS Code - Build` watch task output for compilation errors -2. **NEVER** run tests if there are compilation errors -3. **NEVER** use `npm run compile` to compile TypeScript files, always check task output -4. **FIX** all compilation errors before moving forward +- NEVER run tests if there are compilation errors +- NEVER use `npm run compile` to compile TypeScript files but call #get_task_output instead ### TypeScript compilation steps - Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes From 7afe5486a52262fa65fc4e82dc355bcc8de26450 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 15 Oct 2025 17:00:29 -0700 Subject: [PATCH 1207/4355] Declare all tools in data prompt --- .github/prompts/data.prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/prompts/data.prompt.md b/.github/prompts/data.prompt.md index a0956fb7d1d..f3fe3292aaf 100644 --- a/.github/prompts/data.prompt.md +++ b/.github/prompts/data.prompt.md @@ -1,7 +1,7 @@ --- mode: agent description: 'Answer telemetry questions with data queries' -tools: ['runInTerminal', 'search', 'extensions', 'githubRepo', 'todos', 'kusto_query'] +tools: ['search', 'runCommands/runInTerminal', 'Azure MCP/kusto_query', 'githubRepo', 'extensions', 'todos'] --- From 9722262df14632ac3c1f167a7b162a12a5bee509 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 15 Oct 2025 20:18:50 -0700 Subject: [PATCH 1208/4355] mcp: fix overeager output focusing in mcp devmode (#271626) Closes https://github.com/microsoft/vscode/issues/271543 --- src/vs/workbench/contrib/mcp/common/mcpDevMode.ts | 2 -- src/vs/workbench/contrib/mcp/common/mcpServer.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpDevMode.ts b/src/vs/workbench/contrib/mcp/common/mcpDevMode.ts index 85c445d7747..14b5510691a 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpDevMode.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpDevMode.ts @@ -20,8 +20,6 @@ import { IMcpRegistry } from './mcpRegistryTypes.js'; import { IMcpServer, McpServerDefinition, McpServerLaunch, McpServerTransportType } from './mcpTypes.js'; export class McpDevModeServerAttache extends Disposable { - public active: boolean = false; - constructor( server: IMcpServer, fwdRef: { lastModeDebugged: boolean }, diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index c86cbc1c5c7..0bd6db9155b 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -555,10 +555,10 @@ export class McpServer extends Disposable implements IMcpServer { } this._connection.set(connection, undefined); - } - if (connection.definition.devMode) { - this.showOutput(); + if (connection.definition.devMode) { + this.showOutput(); + } } const start = Date.now(); From 3a8f6b3483fdd8eb7e1d6483350a0ae00f7bc8c2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Oct 2025 07:36:02 +0200 Subject: [PATCH 1209/4355] style - update menu title `Configure Chat` (#271632) --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 8a29c68125a..efaacb62d49 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1637,7 +1637,7 @@ Update \`.github/copilot-instructions.md\` for the user, then ask for feedback o MenuRegistry.appendMenuItem(MenuId.ViewTitle, { submenu: CHAT_CONFIG_MENU_ID, - title: localize2('config.label', "Configure Chat..."), + title: localize2('config.label', "Configure Chat"), group: 'navigation', when: ContextKeyExpr.equals('view', ChatViewId), icon: Codicon.gear, From cee904f80cc6d15f22c850482789e06b1f536e72 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 16 Oct 2025 11:48:38 +0200 Subject: [PATCH 1210/4355] remove some anys (#271651) --- .../css-language-features/client/src/cssClient.ts | 5 +++-- .../css-language-features/server/src/cssServer.ts | 14 ++++++-------- .../client/src/htmlClient.ts | 6 +++--- .../server/src/htmlServer.ts | 9 ++++----- .../client/src/jsonClient.ts | 5 ++--- .../server/src/jsonServer.ts | 3 +-- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 5f2bbd8dbd1..49bacd90a5c 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -83,8 +83,9 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } return r; } - // eslint-disable-next-line local/code-no-any-casts - const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; + } const r = next(document, position, context, token); if (isThenable(r)) { diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index c3ab75519c3..b46e20bb7c1 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -68,16 +68,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - // eslint-disable-next-line local/code-no-any-casts - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; - // eslint-disable-next-line local/code-no-any-casts - workspaceFolders = (params).workspaceFolders; - if (!Array.isArray(workspaceFolders)) { + if (!Array.isArray(params.workspaceFolders)) { workspaceFolders = []; if (params.rootPath) { workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString(true) }); } + } else { + workspaceFolders = params.workspaceFolders; } requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); @@ -168,11 +167,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { - // eslint-disable-next-line local/code-no-any-casts - updateConfiguration(change.settings as any); + updateConfiguration(change.settings as { [languageId: string]: LanguageSettings }); }); - function updateConfiguration(settings: any) { + function updateConfiguration(settings: { [languageId: string]: LanguageSettings }) { for (const languageId in languageServices) { languageServices[languageId].configure(settings[languageId]); } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 9cbbca14292..54fc91469da 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -179,9 +179,9 @@ async function startClientWithParticipants(languageParticipants: LanguagePartici } return r; } - // eslint-disable-next-line local/code-no-any-casts - const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; - + function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; + } const r = next(document, position, context, token); if (isThenable(r)) { return r.then(updateProposals); diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 66e3d3474fc..877c42f5c7c 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -134,16 +134,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities connection.onInitialize((params: InitializeParams): InitializeResult => { - // eslint-disable-next-line local/code-no-any-casts - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; - // eslint-disable-next-line local/code-no-any-casts - workspaceFolders = (params).workspaceFolders; - if (!Array.isArray(workspaceFolders)) { + if (!Array.isArray(params.workspaceFolders)) { workspaceFolders = []; if (params.rootPath) { workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() }); } + } else { + workspaceFolders = params.workspaceFolders; } const handledSchemas = initializationOptions?.handledSchemas as string[] ?? ['file']; diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index afa1c201dca..6d832e6c159 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -771,9 +771,8 @@ function getSchemaId(schema: JSONSchemaSettings, settingsLocation?: Uri): string return url; } -function isThenable(obj: ProviderResult): obj is Thenable { - // eslint-disable-next-line local/code-no-any-casts - return obj && (obj)['then']; +function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; } function updateMarkdownString(h: MarkdownString): MarkdownString { diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 8dc1c588a6d..cbe1e7d02b4 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -141,8 +141,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - // eslint-disable-next-line local/code-no-any-casts - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; const handledProtocols = initializationOptions?.handledSchemaProtocols; From 3add072f87d142a8de947b8376de2c805bd2a6fe Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:50:49 +0200 Subject: [PATCH 1211/4355] Add a placeholder for simple file picker (#271655) Part of #271507 --- src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 478f44398b8..5539de36b3e 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -308,6 +308,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog { this.filePickBox.matchOnLabel = false; this.filePickBox.sortByLabel = false; this.filePickBox.ignoreFocusOut = true; + this.filePickBox.placeholder = nls.localize('remoteFileDialog.placeholder', "Folder path"); this.filePickBox.ok = true; this.filePickBox.okLabel = typeof this.options.openLabel === 'string' ? this.options.openLabel : this.options.openLabel?.withoutMnemonic; if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) { From 00f67f5aef7c0e9d7c35d8c51e85940df5aa44d0 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 16 Oct 2025 13:42:41 +0100 Subject: [PATCH 1212/4355] update close icon and add strikethrough icon --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118592 -> 118808 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index a7d78f82517a27e034068d155819630aae7e6372..7c0904e96dbcf2fe54816dd91423cefba135840b 100644 GIT binary patch delta 2155 zcmZY9X;f5Y9LMqhZ|*FB%z_L{3aCIpSh#>_T2^Liwj z7{}8o)Db~26_}9`*#@P86L#W6qN4%a-ZAL<1)hRvfoyPRp3Oe)DTYbeW~wc|c=R;&{K7 z#oM!T7f-zX&_{%JBE#i5bKjfxN=HW;QQ}l0R6cw8y7nRHcEwM`wzI@+d-w}VHe^PsfumnBn=;5;p!Z8oo zbOvvt9xr1fQc*}YbmDVtfS^(uiPz~{>Od^LfLi*BB5;G;RESx49wRXd&2){t)QTh6 zK~C&L5#2)tw!wvJ?7=Q-Lm51zAuZ|nd-bAVGLnhRWN|eG&4!~GCD@Kqd{1ud#BNl= zjVkPg7mYZI<2Z@aID@lj!8x4A1zf^U_!+<8H~fw(xQgqziCef$@wkI7+$RqnL0?4C ze9EOyX*7LCi}*|A(MnoI*_1?&A%%*ljIN-KZ&zdtEkSRL#fPZDWV(Q_kdF!2f>-f2 zW#JETI=JtUI$Xw&xQJFdj-~hr&(J6M6Zdcz2XGKS;1CX@3HvF8GVu~sp+iI&Q}L^q zY>Y;O$Tkk^bpoeoA_}MxKC#OfVcd#!)I!a47VYqhX5$kmUvCw1`H`C^1mUWg!`t1?cBE{lycve@V)5i6XDp&NAF75&3#Wo zB{xHYo0}=2iaTAxUhWJDUann2BX_2Rquf~%_(#D%Tf#~193qFh!D&9ol5mEbE#WM8 zu7nnDj)ZgE_a&U?&XaI~J72;jZmxu%xC zdJ1YpPxK;QMGIp}hSc#@>P=_3%;m>Mg&(>6D5`LgTO*-WOzJn@@h2bcmvE1JK*C+_ zK?&;E4-(X|LlV@n!xGf7CJE{oFAx>fF?E_5uVC^YlXxwYpQl_{`#**b*XaJk!3%of z02dlu$+6GFc!H?iD&dq^8aIenc3IpSY!xHpM;Y7sOtJ*OYk7PqQ0^*tv>$khS0yYB zYR}-43{H`f7=df935nws$B4pj3a@M$vuW4naho@8(QetWrNuecxniqj>*}KLqTHgE zZ3)}Ti><{Ai|b1wOY9{*+m~;@RXVP8b?MbIds*9#oE=>|yLLOv_2tRs1?8O;=@spj zNtN$ZdMmqJHdm*6vOC-Tg}c~&(0z5!m_3D6ma6rhv7Umx3DuVB@_k7u`?_n!)D+hy z)vl<$UN^a}pgyMl-TI=2ryBAbdc5ggZ{y;|=KYiQHy!AIU^{n_rRiH2>qY z^wgo@5iuBz#1zEE8cYbYMx>@7fSnnT1?w*DjLmS do~a(34h_zoH**Ulgs2TfiPfltG_^|QDgs)i6)CzX7Oi3x zhFF^r&9J2TG^Ci}i~ILopj)AFE~ zD$IHP4x%B=M2_3Tqqc^H2Q7F(G-VDU*&!k{I3y@7Yb6ogV_!w^1&ctLD<2z}dqi|> z+>+BDjYQ}pvh0f5ur4^&@j0JQ@F$YJhz^e1C{tn&&mH)Fmzdz_Q0bqlrG&C&eC_?l z4Vx9QIs0?H2pax_vF}q2Zi;sVzL$A*WGzhXn}&B9C!||S16E3D0CTwAvywmEL-ItD zeLC{sS`!UQCb``Fv_nbBS1gQsj48&m#un2M)6%|m(s1jqRUddN!$@pG3|+!%)M5z^ zV+JxQ5Kr+3(jieX&BZFppr>%9Z%{+~X%u=%OPPqoLd?ZHT%yO+KsV5g63Rgpa_KG9 z$U`|Qp~Z3PLNRn?L2??z&-EGEkb-Q-A9OqX2~{!co%V7*3!J8dN}!dNiN~ z=WziS(T)yWMklV|DsG|+w{ZvExQ_>Th{t$_=XgQxc!{^@Cp|`EEXL7hiltpNpMIw; z{18dBn|4qPO{7VfN_kX5J-Ed`mqR3N#ZW9l0;;i$u3-=8-oog8_GO1J`h!S`m+J@S#M!!W+Cs6Hek3PU8&DqLJ(=6pKlRKFLKf z!}%V(VNV}H7z`N(!3slQ13M^T0iaIWq?qN|iZ%+sA!@`EnuP$&h8LnJ1S1fOzv(YZ zM-Eb{lRD@!OfX7U6bVgD8n{0RXyHZ*IL}=#-~u;Fz(sDffOc+-fDY~k0hhTO1$2^%Oh5Ap!xio(0av-3 z1>EGu3h3hgBH%XnR{?jpTLg4-;{@F2ZWZu=8!zA?_cya3{>P8`AVI)0?lu9>x!VPP zEy<)3$eo)g;3ap50NyO7odWu$qH#&Q>K=|sxFx9_SD;#wJ8eOal;Bi2&A@l@Rx!g} zE^j|G+~8IVxW=s!a9vvI?5BFgXH5d$a8C+&%{?W+JU=bKJU=7AJU=VIJZ~0Yo|{*e z!8|`F@Y_x%^C~lb@5$6kB#m*Igg(t8m)XEQDdBSs>NH-i3!&gy-*5qKQlgtDo=9r9 z1IUql-RCJxJee%OsM+H_3@FuPeBlh7lMLhIr3Dj4$Q}`Cf+qOMRKA&eGkcG$Jkpmn zKdU5rZgzaOF~=iEmpe1pkT)hTKCd-DFu%HBTtP@dU7>AZUeUy&Lq&bXGmBG7Y)f_? z9eFh4ct)vz>9NvwwYNH5-Bp%W)>b~aJghvU{DUT4)2|(__0k4tBef~o-ik#PI^7(d zQSYs9t<+ToR<%`UPOk2)38-l^co_aP^w)aVo~pB|TU>X)etLageOE(Rqf=vC Date: Thu, 16 Oct 2025 15:39:52 +0200 Subject: [PATCH 1213/4355] yaml parser: fix property values on next line (#271673) --- src/vs/base/common/yaml.ts | 15 +++++++++++-- src/vs/base/test/common/yaml.test.ts | 33 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/yaml.ts b/src/vs/base/common/yaml.ts index cbbb5247b6c..6f2e801d696 100644 --- a/src/vs/base/common/yaml.ts +++ b/src/vs/base/common/yaml.ts @@ -746,13 +746,24 @@ class YamlParser { const nextIndent = this.lexer.getIndentation(); if (nextIndent > currentIndent) { - // Nested content - determine if it's an object or array + // Nested content - determine if it's an object, array, or just a scalar value this.lexer.skipWhitespace(); if (this.lexer.getCurrentChar() === '-') { value = this.parseBlockArray(nextIndent); } else { - value = this.parseBlockObject(nextIndent); + // Check if this looks like an object property (has a colon) + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) { + // It's a nested object + value = this.parseBlockObject(nextIndent); + } else { + // It's just a scalar value on the next line + value = this.parseValue(); + } } } else if (!fromCurrentPosition && nextIndent === currentIndent) { // Same indentation level - check if it's an array item diff --git a/src/vs/base/test/common/yaml.test.ts b/src/vs/base/test/common/yaml.test.ts index e91c611c03e..c6e3a53e7cc 100644 --- a/src/vs/base/test/common/yaml.test.ts +++ b/src/vs/base/test/common/yaml.test.ts @@ -6,6 +6,7 @@ import { deepStrictEqual, strictEqual, ok } from 'assert'; import { parse, ParseOptions, YamlParseError, Position, YamlNode } from '../../common/yaml.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; + function assertValidParse(input: string[], expected: YamlNode, expectedErrors: YamlParseError[], options?: ParseOptions): void { const errors: YamlParseError[] = []; const text = input.join('\n'); @@ -85,6 +86,36 @@ suite('YAML Parser', () => { }, []); }); + test('value on next line', () => { + assertValidParse( + [ + 'name:', + ' John Doe', + 'colors:', + ' [ Red, Green, Blue ]', + ], + { + type: 'object', start: pos(0, 0), end: pos(3, 22), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(1, 2), end: pos(1, 10), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 6), value: 'colors' }, + value: { + type: 'array', start: pos(3, 2), end: pos(3, 22), items: [ + { type: 'string', start: pos(3, 4), end: pos(3, 7), value: 'Red' }, + { type: 'string', start: pos(3, 9), end: pos(3, 14), value: 'Green' }, + { type: 'string', start: pos(3, 16), end: pos(3, 20), value: 'Blue' } + ] + } + } + ] + }, + [] + ); + }); + test('multiple properties', () => { assertValidParse( [ @@ -759,7 +790,7 @@ suite('YAML Parser', () => { }, [ { - message: `Duplicate key 'key'`, + message: 'Duplicate key \'key\'', code: 'duplicateKey', start: pos(1, 0), end: pos(1, 3) From d1e5492917548c8298ca20ecc84b556dd806f5ff Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 16 Oct 2025 14:47:32 +0100 Subject: [PATCH 1214/4355] feat(codicons): add 'open-in-product' icon (0xec65) and update codicon.ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 118808 -> 119012 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 7c0904e96dbcf2fe54816dd91423cefba135840b..d3ab8d9b630af0ac48281b4ac3da817d1008d978 100644 GIT binary patch delta 3745 zcmXBX2~?Ez8prYHH*BemqL)Cuz(ohJ!YxxXm-Lz@E|Ghdrh=eof~1Im=%6T|;`*B6 zhKP!|+(JM>#SL53%+##(W-eu9GgDb`Z;kTx< zGo(IgqDZ4lA|acn&CHluSUcctk*LXnyp+U**!XelibspcZR(!L9k#Jv;k;c-;fYD9 z=`kVybBVws;&*rEtSPYr|1PZ;X*W~E>p@a%dWu&Iv;~3DnoJ0r}B|+y{+5w#|OoGiFiG} z)t+rE;y>j3>bgXxzi;&7GBM~&FTTYUJ$n#hx4m!{H8DnDub1e`D96%4{_l8aFojWH6A*YO74 z#00#BI7~r25-=6hFdZ{66Ulf7vymzrk%oClM+P#n7&%yirO3rHti~Fwg@p|$#8zxW z5sI+`dr*eG*oXf}72ZQRDsc$aID*rtbK(cEw|u6Kg3E$3`ex|lHM{LCvXQ}<2wE; z7cd_S(O0tZ0Ds^iKEQ|g2p{7t&f$zSmjn!z!}!?>vxmHxEhi*X0y&{~5F=sI25lq& z-=a5~%l{zog%^DA1RCKOK_1Dch>}uxqcQ93x~~$Z{`?0N5nNnal*wFujDeDpuj8LHC5px zJ56C2o2anKy3{PF&vtI{eZY{hEU#w+zpzOP%h_awcUkkOg!NXqzXM~fX#ZX~ZY|+$ zmsRHPu$$kvP+@>|jr%{h{`T+WJkNc(idU7m@|2XYeD5%Dj5Uu(yynEULdiq6K%s(N ztx(FYQK(|qDja3kDI8`k5nuBI?_1@~gY5gcYlA{f<<;hUd?Q{KaX+K*3j3_WAT~Zri?Om`=RG1jG)LC)8?)kWbA_IZUj*{%u`*lr4MvE3Eo*cTMI#_k>p zTx0i(3S47%PX(^AyO#pj*xg&iX?|cjH~J{dU|&+;(z^R9a7o?$6y9O~tuUMIulS}U zZu7zrFAQ-HP)K73D$HZe3r0v6r?|~4M#x~J6*Aeu3X9nx3OVdhg(d7Tg{ADP3b|~I z!ZLQa0%yQILV+{j9%(rFz#Ia%StNqRnnfaT#oc2R3fZv=TUoPggl#N8VGIrXhsn)N3qPV+{hLLEC@;S<)(0^w8kZH3QRGZ%!*EH8TlS6DM6gsbc< zg)i6?g)dn%JA|*;*=GGYK)A+@ISMz}RE3-DT!mY#86?7OcAmm_Y`Vf-*31#%d)CYm z;RklU!jEj0!hJJv3lx537baDEMQHZrJg$CKp1z^U45N$mO?KI{Dw`vsbux20#U$aLQuCvD!KYGP&o;S$__PBz1 zNzLyfEVR=9<#0aWR-M8h>=}iJ><0>_A1auBq+t57g6UZW({l=@=M_Kl#cigP_~9>Z zJ~OLzSo2mxdHbT0!>pND;unJfVU@_X8o~x4#_HFm7rwQ!+En6{)wyjDKlr2DeixA^ z;%Ta6G}}y}fc00{VhwjV?0f`IfWjp!+Y!`iBe#N-@T&5(RB&76jv%~hopm^nWO;aB zjk^+v?of*V79#iC)CqHAK!qu)j=NVuOyazSs}%$ir9jDYj~ssE3DMWLAYSmMvm$;p2y93ghVz@@f(JCrYPZs z=80F>$tEcDxB9gU^13HdIj&uv&6zs)N?Kal-}7e9yPEz)`mprEjKGZ985c5xGbd)2 zWxD1+JwJJVT~`0Bl&p#c@e3|43|?5|jB(z~PRlOKu3r?o=*Hsk#o0ODIX!bm<{Vg3 zyEJBLcJ7n81$mN}lIO~g&fk@Pe_8ahi_15x2wbsvg{z=_L3)8}W%SB@tGrjuT-D%Q z9l!e9H3QaEtPNeecwNtRh3l?bU9HJh{kt$BaXWACO5F9) z?#SKgrMA+krDyl}@0qdZXxZ?xt9$$JE!=y1Uypsxqx(DVuR9R^UiAA9%Ey&wmESwq z?_hq#OBI{AP>)0F58Xc;cld7ghU$AY=||#@+Ky%( z8**&@v4+~r+H1#q9xpk5??lRpsuO>lTy^s1so|&cPq#RIzAn73_)P0Fv(MDA-Ti!k7QdN0{2!SqTIB!$ delta 3603 zcmXBXd0bTW8prYHE4btpMXw5Ym5U9aSIec+O1mv+xI|_yXl5y{XsA>qi^w1s|zu)|@c{8o11EU0` z+{c5+1@C)AJ^6dlB|({|NY_>wRSu5_0^XO#y)-#5qrN{LxO{Q7x%GIfD65-$8*65Z zm9EyUzD2i`zwRjRN#gd`t;g8bBF$68-90q3wHIbs-mRZ;x;^B>xuG-O!+bf5w@{5& zkb@_YFQK@DmB@l6J7o~Y%O<&vN9AQy$!hV!&r&A&n2RA8guyr`KS_;zi<8(bg{VZ4 z+{a#Qg9`_+4}0V?cA-L=iMurCv-q2|5f5oA?ZowY>sD|E;bjD4lq6#e#$ufKBNP+y z2HwPEgkc({V+Lko7Q!(H?_eGx@h;w%_4oh_5setcVhNJ46w9z2DM-f}tVJf)VLfuO z33=Fz0u*CAc3>y|BW3syAE6XwD93)(;K)dvLM_hV3!KH5IFGOJHNL?me1|KzitG3R zH*gcT@CzDnmrw2<8u7bSzz6?8S9xEeWSKlK%Ox6ntb#V3oR|5AdrK@jU?}2o5ToTA ztVSA!V+&r#6q$z~WjOlEHn|E1Uc^-CgV!)j2Fr6Y8i#NV7jXgKN-g5B7%xa7e#L$K zhGRI6kMRjU#R(jh_A(PAr5v{6NA;^{^sFMK*!PDr62$_LS zh{6V0FIm`%3^_07R1vp6k*^16geft1QgOu}p= zN(>6+1b*T*(83C9+hj$yeWYC*&aim9n=Oz3&zjrL5%!))-E_stmbw{A&ayKV-ePAd zRI{@cUSY!(a@aWvPqK3r@~u1VyaMiUZ=S+R*8EF^EOvo{W%crOV7xWTb1Io zi_K6tz-B7!V=cw&Q|in!Cb`V6Q`p63i?lEsRJbm--`>I*Bx3(d;bpd$0zW40-ilYU z*pDgUE3qF}7{m5a7|ZrmyyC^~r^KJ_uMo;Up}>z!oA1BVZ14tmo>F*|9iT9o9jFk- zKCLi~eMW&pVLz+Dp|A%ja477-3LFaiIRy@a{cnXi;uQON?hxK#Ur^u-*e@zXvM(vT z%bItL_=zR<5XGxV?4b%Du;!g4EM(0)Nr+~LDa5eD4JS{;^2P{-CG1FrB$lIVU@04- zu#A0GVL5A-hs-yRJz621eO-ZLWSgZTaExrTRD?{{Y1WFs8L`JJm{()gijd1rP}szp zbtB}lX4wdv*+~iotXV=rF>98Pu$|@gX<%6uw~1 zToBH(^A)~i7bu)(&Abr4V$Hk|zGmN3_=YvJL%78Cx1$ukW6kgouCNOguCiu$2-jIN zJcJ)uGdqMEY@EVPc9Fs@HeTTucCkVOn?TLE!Cfn%tC!C`?wau~oncAJ9vJ2N{3 zGsI?gh+nm0?-1!=He!~-%nsp4E6I0kz*O$-Rnmtw(?NKRbtw$9ntc0?Kg2yV7ldo< z0fmcfrNRZ)yddItx7bxm%y^i;CzyADcTV2*vPV2*vF zV2*vNV2+(oFvm_Rm}6#GiN6`dKCSrcL2N!V%g?X9&q&@rqr_YZpDWz9M*6wnv>y5m z!>d+s_rd(xkksAfbC~D@)$s+Y_m5gKCDXd~W6*gL-4oARe-0P_L zlSAq|DG6aaD`Z>w4lkcN?)fMgz;;mxvCcRgh_LQC93H$X>bom!upaeycyMg$0~9i> zG5&o$_-yNYDcDxJzn2Hcx&Cp5MOLZ*FpRL8xSe1H1x)cN;h7*MVeAlv1y*H1E>>8f zfexfvae>2d+BzCI)@KsWoBI)Fv(pq3tv)@xd)PfoVBujjJcFE=})}Uc9D*bIsZ{_ts8bTbt1@<5p&HW>V&LtF4u_&U;-| zc4~IR`dRDG7+R6NKk$I(fs)Fel@AUE9W1WuS(RLM>(J;!+10+))2fROKX*9o@WYyc zH8n?Kj+{F>>gb7Me#d6<1HqjHUC@R98}X4IuE^d4>MBNbMnOIvRMeHB!L8!*ez<#9 KG@QV_mj4HI^Hekd diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index cfbb7ed2c1a..707bb663ba5 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -626,4 +626,5 @@ export const codiconsLibrary = { runWithDeps: register('run-with-deps', 0xec62), debugConnected: register('debug-connected', 0xec63), strikethrough: register('strikethrough', 0xec64), + openInProduct: register('open-in-product', 0xec65), } as const; From 71b8165adb249038e7233f3677c28ec524209d9a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 16 Oct 2025 16:03:36 +0200 Subject: [PATCH 1215/4355] files - clarify watch behaviour better around how delete operations are handled (#271674) --- src/vscode-dts/vscode.d.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index e39bac4a6e6..ff8d57f61ec 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -13874,6 +13874,14 @@ declare module 'vscode' { * * To stop listening to events the watcher must be disposed. * + * *Note* that file events from deleting a folder may not include events for the contained files. + * For example, when a folder is moved to the trash, only one event is reported because technically + * this is a rename/move operation and not a delete operation for each files within. + * On top of that, performance optimisations are in place to fold multiple events that all belong + * to the same parent operation (e.g. delete folder) into one event for that parent. As such, if + * you need to know about all deleted files, you have to watch with `**` and deal with all file + * events yourself. + * * *Note* that file events from recursive file watchers may be excluded based on user configuration. * The setting `files.watcherExclude` helps to reduce the overhead of file events from folders * that are known to produce many file changes at once (such as `.git` folders). As such, @@ -13894,11 +13902,6 @@ declare module 'vscode' { * In the same way, symbolic links are preserved, i.e. the file event will report the path of the * symbolic link as it was provided for watching and not the target. * - * *Note* that file events from deleting a folder may not include events for contained files but - * only the most top level folder that was deleted. This is a performance optimisation to reduce - * the overhead of file events being sent. If you need to know about all deleted files, you have - * to watch with `**` and deal with all file events yourself. - * * ### Examples * * The basic anatomy of a file watcher is as follows: @@ -20898,7 +20901,7 @@ declare module 'vscode' { /** * Create a new {@linkcode LanguageModelDataPart} for a json. - * + * * *Note* that this function is not expecting "stringified JSON" but * an object that can be stringified. This function will throw an error * when the passed value cannot be JSON-stringified. @@ -20909,7 +20912,7 @@ declare module 'vscode' { /** * Create a new {@linkcode LanguageModelDataPart} for text. - * + * * *Note* that an UTF-8 encoder is used to create bytes for the string. * @param value Text data * @param mimeType The MIME type if any. Common values are `text/plain` and `text/markdown`. From 6d0d29a429998a5e3f66d103b5c93e9df9ddfcbf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:00:20 +0200 Subject: [PATCH 1216/4355] Fix High Contrast Light theme icon selection in tree views (#271653) * Initial plan * Fix High Contrast Light theme icon selection in tree view Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Remove unused ColorScheme import Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/vs/workbench/browser/parts/views/treeView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index ce6cc95dfbb..897d2dc41cb 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -50,7 +50,7 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IProgressService } from '../../../../platform/progress/common/progress.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; import { FileThemeIcon, FolderThemeIcon, IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { fillEditorsDragData } from '../../dnd.js'; @@ -1345,7 +1345,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer Date: Thu, 16 Oct 2025 09:33:28 -0700 Subject: [PATCH 1217/4355] debug: fix mislocalized string (#271707) * debug: fix mislocalized string Closes https://github.com/microsoft/vscode/issues/271691 * Update src/vs/workbench/contrib/debug/browser/welcomeView.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/debug/browser/welcomeView.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index 831bf466494..481f775794f 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -140,7 +140,7 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { }); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: localize('customizeRunAndDebug', + content: localize({ key: 'customizeRunAndDebug2', comment: ['{Locked="launch.json"}', '{Locked="["}', '{Locked="]({0})"}'] }, "To customize Run and Debug [create a launch.json file]({0}).", `${createCommandUri(DEBUG_CONFIGURE_COMMAND_ID, { addNew: true }).toString()}`), when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, WorkbenchStateContext.notEqualsTo('empty')), group: ViewContentGroups.Debug @@ -149,9 +149,11 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize( { - key: 'customizeRunAndDebugOpenFolder', + key: 'customizeRunAndDebugOpenFolder2', comment: [ - 'Please do not translate "launch.json", it is the specific configuration file name', + '{Locked="launch.json"}', + '{Locked="["}', + '{Locked="]({0})"}', ] }, "To customize Run and Debug, [open a folder]({0}) and create a launch.json file.", createCommandUri(OpenFolderAction.ID).toString()), From a1a256617fcdca6fd9593d54da0569ba2b6e493f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:45:00 -0700 Subject: [PATCH 1218/4355] Add `resource` to chat session, replacing id --- .../api/browser/mainThreadChatSessions.ts | 22 ++++++---- .../workbench/api/common/extHost.protocol.ts | 9 ++-- .../api/common/extHostChatAgents2.ts | 5 ++- .../api/common/extHostChatSessions.ts | 19 ++++++--- .../browser/mainThreadChatSessions.test.ts | 41 ++++++++++++++----- .../chat/browser/chatSessions.contribution.ts | 5 ++- .../chatSessions/chatSessionTracker.ts | 1 + .../chatSessions/localChatSessionsProvider.ts | 6 +++ .../chatSessions/view/sessionsTreeRenderer.ts | 4 +- .../contrib/chat/common/chatService.ts | 11 ++++- .../contrib/chat/common/chatServiceImpl.ts | 10 ++--- .../chat/common/chatSessionsService.ts | 9 ++-- .../chat/test/common/mockChatService.ts | 4 +- .../vscode.proposed.chatSessionsProvider.d.ts | 14 +++++++ 14 files changed, 115 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index b3f0e49a1ad..c37362d8234 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -10,6 +10,7 @@ import { IMarkdownString, MarkdownString } from '../../../base/common/htmlConten import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; import { revive } from '../../../base/common/marshalling.js'; import { autorun, IObservable, observableValue } from '../../../base/common/observable.js'; +import { URI, UriComponents } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; import { ILogService } from '../../../platform/log/common/log.js'; @@ -33,6 +34,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { } readonly sessionId: string; + readonly sessionResource: URI; readonly providerHandle: number; readonly history: Array; @@ -76,6 +78,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { constructor( id: string, + resource: URI, providerHandle: number, proxy: ExtHostChatSessionsShape, logService: ILogService, @@ -84,6 +87,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { super(); this.sessionId = id; + this.sessionResource = resource; this.providerHandle = providerHandle; this.history = []; this._proxy = proxy; @@ -102,7 +106,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { private async _doInitializeContent(token: CancellationToken): Promise { try { const sessionContent = await raceCancellationError( - this._proxy.$provideChatSessionContent(this._providerHandle, this.sessionId, token), + this._proxy.$provideChatSessionContent(this._providerHandle, this.sessionId, this.sessionResource, token), token ); @@ -351,15 +355,15 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire(); } - $onDidCommitChatSessionItem(handle: number, original: string, modified: string): void { + $onDidCommitChatSessionItem(handle: number, original: { id: string; resource: UriComponents }, modified: { id: string; resource: UriComponents }): void { this._logService.trace(`$onDidCommitChatSessionItem: handle(${handle}), original(${original}), modified(${modified})`); const chatSessionType = this._itemProvidersRegistrations.get(handle)?.provider.chatSessionType; if (!chatSessionType) { this._logService.error(`No chat session type found for provider handle ${handle}`); return; } - const originalResource = ChatSessionUri.forSession(chatSessionType, original); - const modifiedResource = ChatSessionUri.forSession(chatSessionType, modified); + const originalResource = ChatSessionUri.forSession(chatSessionType, original.id); + const modifiedResource = ChatSessionUri.forSession(chatSessionType, modified.id); const originalEditor = this._editorService.editors.find(editor => editor.resource?.toString() === originalResource.toString()); // Find the group containing the original editor @@ -378,7 +382,8 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat // Prefetch the chat session content to make the subsequent editor swap quick this._chatSessionsService.provideChatSessionContent( chatSessionType, - modified, + modified.id, + URI.revive(modified.resource), CancellationToken.None, ).then(() => { this._editorService.replaceEditors([{ @@ -402,6 +407,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat return sessions.map(session => ({ ...session, id: session.id, + resource: URI.revive(session.resource), iconPath: session.iconPath, tooltip: session.tooltip ? this._reviveTooltip(session.tooltip) : undefined })); @@ -420,6 +426,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat return { ...chatSessionItem, id: chatSessionItem.id, + resource: URI.revive(chatSessionItem.resource), iconPath: chatSessionItem.iconPath, tooltip: chatSessionItem.tooltip ? this._reviveTooltip(chatSessionItem.tooltip) : undefined, }; @@ -429,13 +436,14 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } } - private async _provideChatSessionContent(providerHandle: number, id: string, token: CancellationToken): Promise { + private async _provideChatSessionContent(providerHandle: number, id: string, resource: URI, token: CancellationToken): Promise { const sessionKey = ObservableChatSession.generateSessionKey(providerHandle, id); let session = this._activeSessions.get(sessionKey); if (!session) { session = new ObservableChatSession( id, + resource, providerHandle, this._proxy, this._logService, @@ -466,7 +474,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat $registerChatSessionContentProvider(handle: number, chatSessionType: string): void { const provider: IChatSessionContentProvider = { - provideChatSessionContent: (id, token) => this._provideChatSessionContent(handle, id, token) + provideChatSessionContent: (id, resource, token) => this._provideChatSessionContent(handle, id, resource, token) }; this._contentProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(chatSessionType, provider)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 195117012b7..eebd6db4546 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -58,7 +58,7 @@ import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, UserSelectedTo import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js'; import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js'; -import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; +import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatSessionContext, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; @@ -1372,7 +1372,7 @@ export type IChatAgentHistoryEntryDto = { }; export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean }; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; + $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContext; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; $provideFollowups(request: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; @@ -3158,6 +3158,7 @@ export type IChatSessionHistoryItemDto = { type: 'request'; prompt: string; part export interface ChatSessionDto { id: string; + resource: UriComponents; history: Array; hasActiveResponseCallback: boolean; hasRequestHandler: boolean; @@ -3169,7 +3170,7 @@ export interface MainThreadChatSessionsShape extends IDisposable { $registerChatSessionItemProvider(handle: number, chatSessionType: string): void; $unregisterChatSessionItemProvider(handle: number): void; $onDidChangeChatSessionItems(handle: number): void; - $onDidCommitChatSessionItem(handle: number, original: string, modified: string): void; + $onDidCommitChatSessionItem(handle: number, original: { id: string; resource: UriComponents }, modified: { id: string; resource: UriComponents }): void; $registerChatSessionContentProvider(handle: number, chatSessionType: string): void; $unregisterChatSessionContentProvider(handle: number): void; @@ -3184,7 +3185,7 @@ export interface ExtHostChatSessionsShape { $provideChatSessionItems(providerHandle: number, token: CancellationToken): Promise[]>; $provideNewChatSessionItem(providerHandle: number, options: { request: IChatAgentRequest; metadata?: any }, token: CancellationToken): Promise>; - $provideChatSessionContent(providerHandle: number, sessionId: string, token: CancellationToken): Promise; + $provideChatSessionContent(providerHandle: number, sessionId: string, sessionResource: UriComponents, token: CancellationToken): Promise; $interruptChatSessionActiveResponse(providerHandle: number, sessionId: string, requestId: string): Promise; $disposeChatSessionContent(providerHandle: number, sessionId: string): Promise; $invokeChatSessionRequestHandler(providerHandle: number, id: string, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 561325dd202..9c6ee6d866c 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -22,7 +22,7 @@ import { ILogService } from '../../../platform/log/common/log.js'; import { isChatViewTitleActionContext } from '../../contrib/chat/common/chatActions.js'; import { IChatAgentRequest, IChatAgentResult, IChatAgentResultTimings, UserSelectedTools } from '../../contrib/chat/common/chatAgents.js'; import { IChatRelatedFile, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; -import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; +import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatSessionContext, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; @@ -541,7 +541,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS this._onDidChangeChatRequestTools.fire(request.extRequest); } - async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: { chatSessionType: string; chatSessionId: string; isUntitled: boolean }; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { + async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContext; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -582,6 +582,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS chatSessionContext = { chatSessionItem: { id: context.chatSessionContext.chatSessionId, + resource: URI.revive(context.chatSessionContext.chatSessionResource), label: context.chatSessionContext.isUntitled ? 'Untitled Session' : 'Session', }, isUntitled: context.chatSessionContext.isUntitled, diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 670fec02512..97ec8895f12 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -22,6 +22,8 @@ import { ExtHostLanguageModels } from './extHostLanguageModels.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import * as typeConvert from './extHostTypeConverters.js'; import * as extHostTypes from './extHostTypes.js'; +import { URI, UriComponents } from '../../../base/common/uri.js'; +import { ChatSessionUri } from '../../contrib/chat/common/chatUri.js'; class ExtHostChatSession { private _stream: ChatAgentResponseStream; @@ -51,6 +53,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio private readonly _proxy: Proxied; private readonly _chatSessionItemProviders = new Map { @@ -106,7 +109,9 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio if (provider.onDidCommitChatSessionItem) { disposables.add(provider.onDidCommitChatSessionItem((e) => { const { original, modified } = e; - this._proxy.$onDidCommitChatSessionItem(handle, original.id, modified.id); + this._proxy.$onDidCommitChatSessionItem(handle, + { id: original.id, resource: original.resource ?? ChatSessionUri.forSession(chatSessionType, original.id) }, + { id: modified.id, resource: modified.resource ?? ChatSessionUri.forSession(chatSessionType, modified.id) }); })); } return { @@ -152,9 +157,10 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } } - private convertChatSessionItem(sessionContent: vscode.ChatSessionItem): IChatSessionItem { + private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem { return { id: sessionContent.id, + resource: sessionContent.resource ?? ChatSessionUri.forSession(sessionType, sessionContent.id), label: sessionContent.label, description: sessionContent.description ? typeConvert.MarkdownString.from(sessionContent.description) : undefined, status: this.convertChatSessionStatus(sessionContent.status), @@ -200,7 +206,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio chatSessionItem.id, chatSessionItem ); - return this.convertChatSessionItem(chatSessionItem); + return this.convertChatSessionItem(entry.sessionType, chatSessionItem); } catch (error) { this._logService.error(`Error creating chat session: ${error}`); throw error; @@ -226,7 +232,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio sessionContent.id, sessionContent ); - response.push(this.convertChatSessionItem(sessionContent)); + response.push(this.convertChatSessionItem(entry.sessionType, sessionContent)); } } return response; @@ -234,7 +240,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio private readonly _extHostChatSessions = new Map(); - async $provideChatSessionContent(handle: number, id: string, token: CancellationToken): Promise { + async $provideChatSessionContent(handle: number, id: string, resource: UriComponents, token: CancellationToken): Promise { const provider = this._chatSessionContentProviders.get(handle); if (!provider) { throw new Error(`No provider for handle ${handle}`); @@ -273,6 +279,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio const { capabilities } = provider; return { id: sessionId + '', + resource: URI.revive(resource), hasActiveResponseCallback: !!session.activeResponseCallback, hasRequestHandler: !!session.requestHandler, supportsInterruption: !!capabilities?.supportsInterruptions, diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 1fc04f7656a..4af39a81a08 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -7,6 +7,8 @@ import assert from 'assert'; import * as sinon from 'sinon'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js'; @@ -77,14 +79,17 @@ suite('ObservableChatSession', function () { } async function createInitializedSession(sessionContent: any, sessionId = 'test-id'): Promise { - const session = new ObservableChatSession(sessionId, 1, proxy, logService, dialogService); + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); await session.initialize(CancellationToken.None); return session; } test('constructor creates session with proper initial state', function () { - const session = disposables.add(new ObservableChatSession('test-id', 1, proxy, logService, dialogService)); + const sessionId = 'test-id'; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); assert.strictEqual(session.sessionId, 'test-id'); assert.strictEqual(session.providerHandle, 1); @@ -98,7 +103,9 @@ suite('ObservableChatSession', function () { }); test('session queues progress before initialization and processes it after', async function () { - const session = disposables.add(new ObservableChatSession('test-id', 1, proxy, logService, dialogService)); + const sessionId = 'test-id'; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); const progress1: IChatProgress = { kind: 'progressMessage', content: { value: 'Hello', isTrusted: false } }; const progress2: IChatProgress = { kind: 'progressMessage', content: { value: 'World', isTrusted: false } }; @@ -147,7 +154,10 @@ suite('ObservableChatSession', function () { }); test('initialization is idempotent and returns same promise', async function () { - const session = disposables.add(new ObservableChatSession('test-id', 1, proxy, logService, dialogService)); + const sessionId = 'test-id'; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); + const sessionContent = createSessionContent(); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); @@ -272,7 +282,9 @@ suite('ObservableChatSession', function () { }); test('dispose properly cleans up resources and notifies listeners', function () { - const session = new ObservableChatSession('test-id', 1, proxy, logService, dialogService); + const sessionId = 'test-id'; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); let disposeEventFired = false; const disposable = session.onWillDispose(() => { @@ -288,7 +300,9 @@ suite('ObservableChatSession', function () { }); test('session key generation is consistent', function () { - const session = new ObservableChatSession('test-id', 42, proxy, logService, dialogService); + const sessionId = 'test-id'; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const session = disposables.add(new ObservableChatSession(sessionId, resource, 42, proxy, logService, dialogService)); assert.strictEqual(session.sessionKey, '42_test-id'); assert.strictEqual(ObservableChatSession.generateSessionKey(42, 'test-id'), '42_test-id'); @@ -427,13 +441,15 @@ suite('MainThreadChatSessions', function () { hasRequestHandler: false }; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); + (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session1 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', CancellationToken.None); + const session1 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None); assert.ok(session1); assert.strictEqual(session1.sessionId, 'test-session'); - const session2 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', CancellationToken.None); + const session2 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None); assert.strictEqual(session1, session2); assert.ok((proxy.$provideChatSessionContent as sinon.SinonStub).calledOnce); @@ -452,7 +468,8 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); + const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, 'test-session', 'req1', [progressDto]); @@ -475,7 +492,8 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); + const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, 'test-session', 'req1', [progressDto]); @@ -503,7 +521,8 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'multi-turn-session', CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/multi-turn-session`); + const session = await chatSessionsService.provideChatSessionContent('test-type', 'multi-turn-session', resource, CancellationToken.None) as ObservableChatSession; // Verify the session loaded correctly assert.ok(session); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 6d6403154ca..0621ecd5cf6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -7,6 +7,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; @@ -538,7 +539,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return chatSessionItem; } - public async provideChatSessionContent(chatSessionType: string, id: string, token: CancellationToken): Promise { + public async provideChatSessionContent(chatSessionType: string, id: string, resource: URI, token: CancellationToken): Promise { if (!(await this.canResolveContentProvider(chatSessionType))) { throw Error(`Can not find provider for ${chatSessionType}`); } @@ -554,7 +555,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return existingSessionData.session; } - const session = await provider.provideChatSessionContent(id, token); + const session = await provider.provideChatSessionContent(id, resource, token); const sessionData = new ContributedChatSessionData(session, chatSessionType, id, this._onWillDisposeSession.bind(this)); this._sessions.set(sessionKey, sessionData); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index 23f298d790e..c0d1fecf8b8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -102,6 +102,7 @@ export class ChatSessionTracker extends Disposable { const parsed = ChatSessionUri.parse(editor.resource); const hybridSession: ChatSessionItemWithProvider = { id: parsed?.sessionId || editor.sessionId || `${provider.chatSessionType}-local-${index}`, + resource: editor.resource, label: editor.getName(), iconPath: Codicon.chatSparkle, status, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 673e495ef9e..9760fb42fc2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -6,7 +6,9 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../../base/common/network.js'; import { IObservable } from '../../../../../base/common/observable.js'; +import { URI } from '../../../../../base/common/uri.js'; import * as nls from '../../../../../nls.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroupsService, IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js'; @@ -198,6 +200,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; const widgetSession: ChatSessionItemWithProvider = { id: LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID, + resource: URI.parse(`${Schemas.vscodeChatSession}://widget`), label: chatWidget?.viewModel?.model.title || nls.localize2('chat.sessions.chatView', "Chat").value, description: nls.localize('chat.sessions.chatView.description', "Chat View"), iconPath: Codicon.chatSparkle, @@ -229,6 +232,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio } const editorSession: ChatSessionItemWithProvider = { id: editorInfo.editor.sessionId, + resource: editorInfo.editor.resource, label: editorInfo.editor.getName(), iconPath: Codicon.chatSparkle, status, @@ -242,8 +246,10 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio } }); + // TODO: This should not be a session items const historyNode: IChatSessionItem = { id: LocalChatSessionsProvider.HISTORY_NODE_ID, + resource: URI.parse(`${Schemas.vscodeChatSession}://history`), label: nls.localize('chat.sessions.showHistory', "History"), }; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 0a1cd0890ef..afa70133eed 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -47,6 +47,7 @@ import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/brow import { ChatSessionTracker } from '../chatSessionTracker.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; +import { ChatSessionUri } from '../../../common/chatUri.js'; interface ISessionTemplateData { readonly container: HTMLElement; @@ -575,8 +576,9 @@ export class SessionsDataSource implements IAsyncDataSource ({ + const historyItems = allHistory.map((historyDetail): ChatSessionItemWithProvider => ({ id: historyDetail.sessionId, + resource: ChatSessionUri.forSession(this.provider.chatSessionType, historyDetail.sessionId), label: historyDetail.title, iconPath: Codicon.chatSparkle, provider: this.provider, diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index e60fe7a8ad5..bd6d737b99f 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -10,7 +10,7 @@ import { Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { autorunSelfDisposable, IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { URI } from '../../../../base/common/uri.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { ISelection } from '../../../../editor/common/core/selection.js'; import { Command, Location, TextEdit } from '../../../../editor/common/languages.js'; @@ -737,7 +737,7 @@ export interface IChatService { loadSessionFromContent(data: IExportableChatData | ISerializableChatData | URI): IChatModel | undefined; loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise; readonly editingSessions: IChatEditingSession[]; - getChatSessionFromInternalId(sessionId: string): { chatSessionType: string; chatSessionId: string; isUntitled: boolean } | undefined; + getChatSessionFromInternalId(sessionId: string): IChatSessionContext | undefined; /** * Returns whether the request was accepted.` @@ -770,4 +770,11 @@ export interface IChatService { readonly requestInProgressObs: IObservable; } +export interface IChatSessionContext { + chatSessionType: string; + chatSessionId: string; + chatSessionResource: UriComponents; + isUntitled: boolean; +} + export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1c5d664f520..3e788aff39e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -30,7 +30,7 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRe import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, chatSubcommandLeader, getPromptText, IParsedChatRequest } from './chatParserTypes.js'; import { ChatRequestParser } from './chatRequestParser.js'; -import { ChatMcpServersStarting, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; +import { ChatMcpServersStarting, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatSessionContext, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js'; import { ChatRequestTelemetry, ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { IChatSessionsService } from './chatSessionsService.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; @@ -73,7 +73,7 @@ export class ChatService extends Disposable implements IChatService { private readonly _sessionModels = new ObservableMap(); private readonly _contentProviderSessionModels = new Map>(); - private readonly _modelToExternalSession = new Map(); + private readonly _modelToExternalSession = new Map(); private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; @@ -468,12 +468,12 @@ export class ChatService extends Disposable implements IChatService { } const chatSessionType = parsed.chatSessionType; - const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, CancellationToken.None); + const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, resource, CancellationToken.None); // Contributed sessions do not use UI tools const model = this._startSession(undefined, location, true, CancellationToken.None, { canUseTools: false, inputType: chatSessionType }); // Record mapping from internal model session id to external contributed chat session identity - this._modelToExternalSession.set(model.sessionId, { chatSessionType, chatSessionId: parsed.sessionId }); + this._modelToExternalSession.set(model.sessionId, { chatSessionType, chatSessionId: parsed.sessionId, chatSessionResource: resource }); if (!this._contentProviderSessionModels.has(chatSessionType)) { this._contentProviderSessionModels.set(chatSessionType, new Map()); } @@ -578,7 +578,7 @@ export class ChatService extends Disposable implements IChatService { return model; } - getChatSessionFromInternalId(modelSessionId: string): { chatSessionType: string; chatSessionId: string; isUntitled: boolean } | undefined { + getChatSessionFromInternalId(modelSessionId: string): IChatSessionContext | undefined { const data = this._modelToExternalSession.get(modelSessionId); if (!data) { return; diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 4957f58c0bc..d425fd017f2 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -9,6 +9,7 @@ import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; import { IRelaxedExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditableData } from '../../../common/views.js'; @@ -41,7 +42,8 @@ export interface IChatSessionsExtensionPoint { readonly commands?: IChatSessionCommandContribution[]; } export interface IChatSessionItem { - id: string; + id: string; // TODO: remove + resource: URI; label: string; iconPath?: ThemeIcon; description?: string | IMarkdownString; @@ -62,6 +64,7 @@ export type IChatSessionHistoryItem = { type: 'request'; prompt: string; partici export interface ChatSession extends IDisposable { readonly sessionId: string; + readonly sessionResource: URI; readonly onWillDispose: Event; history: Array; readonly progressObs?: IObservable; @@ -87,7 +90,7 @@ export interface IChatSessionItemProvider { } export interface IChatSessionContentProvider { - provideChatSessionContent(sessionId: string, token: CancellationToken): Promise; + provideChatSessionContent(sessionId: string, sessionResource: URI, token: CancellationToken): Promise; } export interface IChatSessionsService { @@ -112,7 +115,7 @@ export interface IChatSessionsService { registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable; canResolveContentProvider(chatSessionType: string): Promise; - provideChatSessionContent(chatSessionType: string, id: string, token: CancellationToken): Promise; + provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise; // Editable session support setEditableSession(sessionId: string, data: IEditableData | null): Promise; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index defe6776a67..d0e350af990 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -9,11 +9,11 @@ import { observableValue } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, ISerializableChatData } from '../../common/chatModel.js'; import { IParsedChatRequest } from '../../common/chatParserTypes.js'; -import { IChatCompleteResponse, IChatDetail, IChatProviderInfo, IChatSendRequestData, IChatSendRequestOptions, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from '../../common/chatService.js'; +import { IChatCompleteResponse, IChatDetail, IChatProviderInfo, IChatSendRequestData, IChatSendRequestOptions, IChatService, IChatSessionContext, IChatTransferredSessionData, IChatUserActionEvent } from '../../common/chatService.js'; import { ChatAgentLocation } from '../../common/constants.js'; export class MockChatService implements IChatService { - getChatSessionFromInternalId(modelSessionId: string): { chatSessionType: string; chatSessionId: string; isUntitled: boolean } | undefined { + getChatSessionFromInternalId(modelSessionId: string): IChatSessionContext | undefined { throw new Error('Method not implemented.'); } requestInProgressObs = observableValue('name', false); diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 4ef1ed94055..6075c09fc14 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -71,9 +71,18 @@ declare module 'vscode' { export interface ChatSessionItem { /** * Unique identifier for the chat session. + * + * @deprecated Will be replaced by `resource` */ id: string; + /** + * The resource associated with the chat session. + * + * This is uniquely identifies the chat session and is used to open the chat session. + */ + resource: Uri | undefined; + /** * Human readable name of the session shown in the UI */ @@ -212,6 +221,9 @@ declare module 'vscode' { supportsInterruptions?: boolean; } + /** + * @deprecated + */ export interface ChatSessionShowOptions { /** * The editor view column to show the chat session in. @@ -224,6 +236,8 @@ declare module 'vscode' { export namespace window { /** * Shows a chat session in the panel or editor. + * + * @deprecated */ export function showChatSession(chatSessionType: string, sessionId: string, options: ChatSessionShowOptions): Thenable; } From 701d67038dc61dfb06ad56bef6215e430be50350 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 16 Oct 2025 11:28:26 -0700 Subject: [PATCH 1219/4355] LanguageModelProxy disposable (#271607) * LanguageModelProxy disposable * Fix --- src/vs/workbench/api/common/extHostLanguageModels.ts | 2 +- src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts | 4 ++-- src/vscode-dts/vscode.proposed.languageModelProxy.d.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 8e701dcf0b5..5011da45902 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -606,7 +606,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return this._proxy.$fileIsIgnored(uri, token); } - async getModelProxy(extension: IExtensionDescription): Promise { + async getModelProxy(extension: IExtensionDescription): Promise { checkProposedApiEnabled(extension, 'languageModelProxy'); if (!this._languageModelProxyProvider) { diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index 5f269f878e3..dca0f364d75 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -291,13 +291,13 @@ declare module 'vscode' { /** * Duplicated so that this proposal and languageModelProxy can be independent. */ - export interface LanguageModelProxyInfo { + export interface LanguageModelProxy extends Disposable { readonly uri: Uri; readonly key: string; } export interface LanguageModelProxyProvider { - provideModelProxy(forExtensionId: string, token: CancellationToken): ProviderResult; + provideModelProxy(forExtensionId: string, token: CancellationToken): ProviderResult; } export namespace lm { diff --git a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts index 32fcc0b0181..849e08d4529 100644 --- a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { - export interface LanguageModelProxyInfo { + export interface LanguageModelProxy extends Disposable { readonly uri: Uri; readonly key: string; } @@ -15,6 +15,6 @@ declare module 'vscode' { * - The user is not logged in, or isn't the right SKU, with expected model access * - The server fails to start for some reason */ - export function getModelProxy(): Thenable; + export function getModelProxy(): Thenable; } } From df0eb86a8a791e4cfdf6cb234f03762e94760d51 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 16 Oct 2025 22:00:42 +0200 Subject: [PATCH 1220/4355] use isDark to check whether to use a dark or light icon (#271752) --- src/vs/editor/common/model/textModel.ts | 5 +++-- .../browser/chatContentParts/chatReferencesContentPart.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 4 ++-- .../chat/electron-browser/actions/voiceChatActions.ts | 4 ++-- src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts | 4 ++-- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalIcon.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalView.ts | 4 ++-- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 4 ++-- 10 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 5a2293dfcc4..342ddc740e0 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -50,6 +50,7 @@ import { TokenArray } from '../tokens/lineTokens.js'; import { SetWithKey } from '../../../base/common/collections.js'; import { EditSources, TextModelEditSource } from '../textModelEditSource.js'; import { TextEdit } from '../core/edits/textEdit.js'; +import { isDark } from '../../../platform/theme/common/theme.js'; export function createTextBufferFactory(text: string): model.ITextBufferFactory { const builder = new PieceTreeTextBufferBuilder(); @@ -2286,7 +2287,7 @@ export class ModelDecorationOverviewRulerOptions extends DecorationOptions { public getColor(theme: IColorTheme): string { if (!this._resolvedColor) { - if (theme.type !== 'light' && this.darkColor) { + if (isDark(theme.type) && this.darkColor) { this._resolvedColor = this._resolveColor(this.darkColor, theme); } else { this._resolvedColor = this._resolveColor(this.color, theme); @@ -2336,7 +2337,7 @@ export class ModelDecorationMinimapOptions extends DecorationOptions { public getColor(theme: IColorTheme): Color | undefined { if (!this._resolvedColor) { - if (theme.type !== 'light' && this.darkColor) { + if (isDark(theme.type) && this.darkColor) { this._resolvedColor = this._resolveColor(this.darkColor, theme); } else { this._resolvedColor = this._resolveColor(this.color, theme); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index f1d855a183e..2ced9feab06 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -31,10 +31,10 @@ import { ILabelService } from '../../../../../platform/label/common/label.js'; import { WorkbenchList } from '../../../../../platform/list/browser/listService.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; +import { isDark } from '../../../../../platform/theme/common/theme.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { fillEditorsDragData } from '../../../../browser/dnd.js'; import { IResourceLabel, IResourceLabelProps, ResourceLabels } from '../../../../browser/labels.js'; -import { ColorScheme } from '../../../../browser/web.api.js'; import { ResourceContextKey } from '../../../../common/contextkeys.js'; import { SETTINGS_AUTHORITY } from '../../../../services/preferences/common/preferences.js'; import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js'; @@ -336,7 +336,7 @@ class CollapsibleListRenderer implements IListRenderer { let activeRecordingColor: Color | undefined; let activeRecordingDimmedColor: Color | undefined; - if (theme.type === ColorScheme.LIGHT || theme.type === ColorScheme.DARK) { + if (!isHighContrast(theme.type)) { activeRecordingColor = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND) ?? theme.getColor(focusBorder); activeRecordingDimmedColor = activeRecordingColor?.transparent(0.38); } else { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts index 47dbe902e79..ee7deb1c3e3 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts @@ -18,7 +18,7 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { verifiedPublisherIcon } from '../../../services/extensionManagement/common/extensionsIcons.js'; import { IMcpServerContainer, IWorkbenchMcpServer, McpServerInstallState } from '../common/mcpTypes.js'; import { IThemeService, registerThemingParticipant } from '../../../../platform/theme/common/themeService.js'; -import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { McpServerStatusAction } from './mcpServerActions.js'; import { reset } from '../../../../base/browser/dom.js'; @@ -100,7 +100,7 @@ export class McpServerIconWidget extends McpServerWidget { this.iconElement.style.display = 'inherit'; this.codiconIconElement.style.display = 'none'; const type = this.themeService.getColorTheme().type; - const iconUrl = type === ColorScheme.DARK || ColorScheme.HIGH_CONTRAST_DARK ? this.mcpServer.icon.dark : this.mcpServer.icon.light; + const iconUrl = isDark(type) ? this.mcpServer.icon.dark : this.mcpServer.icon.light; if (this.iconUrl !== iconUrl) { this.iconUrl = iconUrl; this.disposables.add(dom.addDisposableListener(this.iconElement, 'error', () => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 359bbf4c5d0..339857fc6a8 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -67,7 +67,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js'; import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRenderer.js'; -import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.js'; import { Selection } from '../../../../editor/common/core/selection.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; @@ -678,7 +678,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer 1) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 9e6bf5a1c64..7c0f6bad08b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -36,7 +36,7 @@ import { getFlatContextMenuActions, MenuEntryActionViewItem } from '../../../../ import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { DisposableMap, DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; -import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; import { getColorClass, getUriClasses } from './terminalIcon.js'; import { getTerminalActionBarArgs } from './terminalMenus.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; @@ -628,7 +628,7 @@ class TerminalThemeIconStyle extends Themable { if (icon instanceof URI) { uri = icon; } else if (icon instanceof Object && 'light' in icon && 'dark' in icon) { - uri = colorTheme.type === ColorScheme.LIGHT ? icon.light : icon.dark; + uri = isDark(colorTheme.type) ? icon.dark : icon.light; } const iconClasses = getUriClasses(instance, colorTheme.type); if (uri instanceof URI && iconClasses && iconClasses.length > 1) { diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index f16522ceb4e..031de4066c6 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -42,7 +42,7 @@ import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/ import { getContextMenuActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; @@ -1202,7 +1202,7 @@ class TimelineTreeRenderer implements ITreeRenderer Date: Thu, 16 Oct 2025 17:03:01 -0400 Subject: [PATCH 1221/4355] hide copilot terminals by default in insider's, make configurable (#271400) --- .../media/chatTerminalToolProgressPart.css | 13 +- .../chatTerminalToolProgressPart.ts | 86 +++++++++- .../contrib/terminal/browser/terminal.ts | 42 ++++- .../terminal/browser/terminalService.ts | 21 ++- .../contrib/terminal/common/terminal.ts | 1 + .../terminal/common/terminalStrings.ts | 1 + .../browser/terminal.chat.contribution.ts | 9 + .../chat/browser/terminalChatService.ts | 159 ++++++++++++++++++ .../browser/tools/runInTerminalTool.ts | 26 ++- .../terminalChatAgentToolsConfiguration.ts | 13 ++ 10 files changed, 350 insertions(+), 21 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css index 61b9ed79f3b..1eafedcbe42 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css @@ -12,9 +12,9 @@ border-radius: 4px; padding: 5px 9px; display: flex; - justify-content: space-between; - flex-direction: column; - column-gap: 10px; + flex-direction: row; + align-items: flex-start; + gap: 10px; .rendered-markdown { display: flex; @@ -30,6 +30,13 @@ } } +.chat-terminal-content-part .chat-terminal-action-bar { + display: flex; + gap: 4px; + margin-left: auto; + align-self: flex-start; +} + .chat-terminal-content-part .chat-terminal-content-message { .rendered-markdown { white-space: normal; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index f5f91c9aaa5..f63cc2819a9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { h } from '../../../../../../base/browser/dom.js'; +import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { isMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; @@ -24,10 +25,17 @@ import { ConfigurationTarget } from '../../../../../../platform/configuration/co import type { ICodeBlockRenderOptions } from '../../codeBlockPart.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { CommandsRegistry } from '../../../../../../platform/commands/common/commands.js'; +import { ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../../terminal/browser/terminal.js'; +import { Action, IAction } from '../../../../../../base/common/actions.js'; +import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import { localize } from '../../../../../../nls.js'; +import { TerminalLocation } from '../../../../../../platform/terminal/common/terminal.js'; export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart { public readonly domNode: HTMLElement; + private _actionBar: ActionBar | undefined; + private markdownPart: ChatMarkdownContentPart | undefined; public get codeblocks(): IChatCodeBlockInfo[] { return this.markdownPart?.codeblocks ?? []; @@ -42,7 +50,9 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart currentWidthDelegate: () => number, codeBlockStartIndex: number, codeBlockModelCollection: CodeBlockModelCollection, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, + @ITerminalService private readonly _terminalService: ITerminalService ) { super(toolInvocation); @@ -55,7 +65,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart const command = terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original; - const titlePart = this._register(instantiationService.createInstance( + const titlePart = this._register(_instantiationService.createInstance( ChatQueryTitlePart, elements.title, new MarkdownString(`$(${Codicon.terminal.id})\n\n\`\`\`${terminalData.language}\n${command}\n\`\`\``, { supportThemeIcons: true }), @@ -63,6 +73,13 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart )); this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); + // Wait for terminal reconnection to ensure the terminal instance is available + this._terminalService.whenConnected.then(() => { + // Append the action bar element after the title has been populated so flex order hacks aren't required. + const actionBarEl = h('.chat-terminal-action-bar@actionBar'); + elements.title.append(actionBarEl.root); + this._createActionBar({ actionBar: actionBarEl.actionBar }, terminalData); + }); let pastTenseMessage: string | undefined; if (toolInvocation.pastTenseMessage) { pastTenseMessage = `${typeof toolInvocation.pastTenseMessage === 'string' ? toolInvocation.pastTenseMessage : toolInvocation.pastTenseMessage.value}`; @@ -84,13 +101,46 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart wordWrap: 'on' } }; - this.markdownPart = this._register(instantiationService.createInstance(ChatMarkdownContentPart, chatMarkdownContent, context, editorPool, false, codeBlockStartIndex, renderer, {}, currentWidthDelegate(), codeBlockModelCollection, { codeBlockRenderOptions })); + this.markdownPart = this._register(_instantiationService.createInstance(ChatMarkdownContentPart, chatMarkdownContent, context, editorPool, false, codeBlockStartIndex, renderer, {}, currentWidthDelegate(), codeBlockModelCollection, { codeBlockRenderOptions })); this._register(this.markdownPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); elements.message.append(this.markdownPart.domNode); - const progressPart = instantiationService.createInstance(ChatCustomProgressPart, elements.container, this.getIcon()); + const progressPart = _instantiationService.createInstance(ChatCustomProgressPart, elements.container, this.getIcon()); this.domNode = progressPart.domNode; } + + private _createActionBar(elements: { actionBar: HTMLElement }, terminalData: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData): void { + this._actionBar = this._register(new ActionBar(elements.actionBar, {})); + + const terminalToolSessionId = 'terminalToolSessionId' in terminalData ? terminalData.terminalToolSessionId : undefined; + if (!terminalToolSessionId || !elements.actionBar) { + return; + } + const terminalInstance = this._terminalChatService.getTerminalInstanceByToolSessionId(terminalToolSessionId); + if (terminalInstance) { + this._registerInstanceListener(terminalInstance); + this._addFocusAction(terminalInstance, terminalToolSessionId); + } else { + const listener = this._register(this._terminalChatService.onDidRegisterTerminalInstanceWithToolSession(terminalInstance => { + this._registerInstanceListener(terminalInstance); + this._addFocusAction(terminalInstance, terminalToolSessionId); + this._store.delete(listener); + })); + } + } + + private _addFocusAction(terminalInstance: ITerminalInstance, terminalToolSessionId: string) { + const isTerminalHidden = this._terminalChatService.terminalIsHidden(terminalToolSessionId); + const focusAction = this._register(this._instantiationService.createInstance(FocusChatInstanceAction, terminalInstance, isTerminalHidden)); + this._actionBar!.push([focusAction], { icon: true, label: false }); + } + + private _registerInstanceListener(terminalInstance: ITerminalInstance) { + const instanceListener = this._register(terminalInstance.onDisposed(() => { + this._actionBar?.dispose(); + instanceListener?.dispose(); + })); + } } export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink'; @@ -129,3 +179,31 @@ CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (acces } } }); + +export class FocusChatInstanceAction extends Action implements IAction { + constructor( + private readonly _instance: ITerminalInstance, + isTerminalHidden: boolean, + @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, + @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService + ) { + super( + 'chat.focusTerminalInstance', + isTerminalHidden ? localize('showTerminal', 'Show Terminal') : localize('focusTerminal', 'Focus Terminal'), + ThemeIcon.asClassName(Codicon.linkExternal), + true, + ); + } + + public override async run() { + this.label = localize('focusTerminal', 'Focus Terminal'); + this._terminalService.setActiveInstance(this._instance); + if (this._instance.target === TerminalLocation.Editor) { + this._terminalEditorService.openEditor(this._instance); + } else { + this._terminalGroupService.showPanel(true); + } + await this._instance?.focusWhenReady(true); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 26fc1a6cae9..95c50dcbf65 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -41,6 +41,7 @@ export const ITerminalEditorService = createDecorator('t export const ITerminalEditingService = createDecorator('terminalEditingService'); export const ITerminalGroupService = createDecorator('terminalGroupService'); export const ITerminalInstanceService = createDecorator('terminalInstanceService'); +export const ITerminalChatService = createDecorator('terminalChatService'); /** * A terminal contribution that gets created whenever a terminal is created. A contribution has @@ -100,6 +101,35 @@ export interface ITerminalInstanceService { didRegisterBackend(backend: ITerminalBackend): void; } +/** + * Service enabling communication between the chat tool implementation in terminal contrib and workbench contribs. + * Acts as a communication mechanism for chat-related terminal features. + */ +export interface ITerminalChatService { + readonly _serviceBrand: undefined; + + /** + * Fired when a terminal instance is registered for a tool session id. This can happen after + * the chat UI first renders, enabling late binding of the focus action. + */ + readonly onDidRegisterTerminalInstanceWithToolSession: Event; + + /** + * Associate a tool session id with a terminal instance. The association is automatically + * cleared when the instance is disposed. + */ + registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void; + + /** + * Resolve a terminal instance by its tool session id. + * @param terminalToolSessionId The tool session id provided in toolSpecificData. + * If no tool session ID is provided, we do nothing. + */ + getTerminalInstanceByToolSessionId(terminalToolSessionId: string): ITerminalInstance | undefined; + + terminalIsHidden(terminalToolSessionId: string): boolean; +} + /** * A service responsible for managing terminal editing state and functionality. This includes * tracking which terminal is currently being edited and managing editable data associated with @@ -292,8 +322,12 @@ export const isDetachedTerminalInstance = (t: ITerminalInstance | IDetachedTermi export interface ITerminalService extends ITerminalInstanceHost { readonly _serviceBrand: undefined; - /** Gets all terminal instances, including editor and terminal view (group) instances. */ + /** Gets all terminal instances, including editor, terminal view (group), and background instances. */ readonly instances: readonly ITerminalInstance[]; + + /** Gets all foreground terminal instances */ + readonly foregroundInstances: readonly ITerminalInstance[]; + /** Gets detached terminal instances created via {@link createDetachedXterm}. */ readonly detachedInstances: Iterable; @@ -359,6 +393,12 @@ export interface ITerminalService extends ITerminalInstanceHost { getActiveOrCreateInstance(options?: { acceptsInput?: boolean }): Promise; revealTerminal(source: ITerminalInstance, preserveFocus?: boolean): Promise; + /** + * @param instance + * @param suppressSetActive Do not set the active instance when there is only one terminal + * @param forceSaveState Used when the window is shutting down and we need to reveal and save hideFromUser terminals + */ + showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean, forceSaveState?: boolean): Promise; revealActiveTerminal(preserveFocus?: boolean): Promise; moveToEditor(source: ITerminalInstance, group?: GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | AUX_WINDOW_GROUP_TYPE): void; moveIntoNewEditor(source: ITerminalInstance): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 1aa1fc46c4d..33032d51b23 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -374,7 +374,7 @@ export class TerminalService extends Disposable implements ITerminalService { // If this was a hideFromUser terminal created by the API this was triggered by show, // in which case we need to create the terminal group if (value.shellLaunchConfig.hideFromUser) { - this._showBackgroundTerminal(value); + this.showBackgroundTerminal(value); } if (value.target === TerminalLocation.Editor) { this._terminalEditorService.setActiveInstance(value); @@ -692,6 +692,12 @@ export class TerminalService extends Disposable implements ITerminalService { } } + private async _saveStateNow(): Promise { + const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); + const state: ITerminalsLayoutInfoById = { tabs }; + await this._primaryBackend?.setTerminalLayoutInfo(state); + } + @debounce(500) private _saveState(): void { // Avoid saving state when shutting down as that would override process state to be revived @@ -701,9 +707,7 @@ export class TerminalService extends Disposable implements ITerminalService { if (!this._terminalConfigurationService.config.enablePersistentSessions) { return; } - const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs }; - this._primaryBackend?.setTerminalLayoutInfo(state); + this._saveStateNow(); } @debounce(500) @@ -1160,7 +1164,7 @@ export class TerminalService extends Disposable implements ITerminalService { } } - protected _showBackgroundTerminal(instance: ITerminalInstance): void { + public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean, forceSaveState?: boolean): Promise { const index = this._backgroundedTerminalInstances.indexOf(instance); if (index === -1) { return; @@ -1174,10 +1178,15 @@ export class TerminalService extends Disposable implements ITerminalService { this._terminalGroupService.createGroup(instance); // Make active automatically if it's the first instance - if (this.instances.length === 1) { + if (this.instances.length === 1 && !suppressSetActive) { this._terminalGroupService.setActiveInstanceByIndex(0); } + if (forceSaveState) { + // Skips the debounce of _saveState as we are shutting down + // and need to save the revealed hideFromUser terminals + await this._saveStateNow(); + } this._onDidChangeInstances.fire(); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 31221d50263..866dbe84131 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -443,6 +443,7 @@ export const enum TerminalCommandId { SizeToContentWidthActiveTab = 'workbench.action.terminal.sizeToContentWidthActiveTab', ResizePaneDown = 'workbench.action.terminal.resizePaneDown', Focus = 'workbench.action.terminal.focus', + FocusInstance = 'workbench.action.terminal.focusInstance', FocusNext = 'workbench.action.terminal.focusNext', FocusPrevious = 'workbench.action.terminal.focusPrevious', Paste = 'workbench.action.terminal.paste', diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index 64e370ddbfc..08ce9eacc9c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -18,6 +18,7 @@ export const terminalStrings = { typeLocal: localize('local', "Local"), actionCategory: localize2('terminalCategory', "Terminal"), focus: localize2('workbench.action.terminal.focus', "Focus Terminal"), + focusInstance: localize2('workbench.action.terminal.focusInstance', "Focus Terminal"), focusAndHideAccessibleBuffer: localize2('workbench.action.terminal.focusAndHideAccessibleBuffer', "Focus Terminal and Hide Accessible Buffer"), kill: { ...localize2('killTerminal', "Kill Terminal"), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index af00d422d48..06e092ab794 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -29,5 +29,14 @@ import { AccessibleViewRegistry } from '../../../../../platform/accessibility/br import { TerminalChatAccessibilityHelp } from './terminalChatAccessibilityHelp.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js'; import { TerminalChatEnabler } from './terminalChatEnabler.js'; +import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; +import { ITerminalChatService } from '../../../terminal/browser/terminal.js'; +import { TerminalChatService } from './terminalChatService.js'; + +// #region Services + +registerSingleton(ITerminalChatService, TerminalChatService, InstantiationType.Delayed); + +// #endregion // #endregion diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts new file mode 100644 index 00000000000..b4470655143 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { Disposable, DisposableMap, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { ITerminalChatService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; +import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { PromptInputState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; + +/** + * Used to manage chat tool invocations and the underlying terminal instances they create/use. + */ +export class TerminalChatService extends Disposable implements ITerminalChatService { + declare _serviceBrand: undefined; + + private static readonly _storageKey = 'terminalChat.toolSessionMappings'; + + private readonly _terminalInstancesByToolSessionId = new Map(); + private readonly _terminalInstanceListenersByToolSessionId = this._register(new DisposableMap()); + private readonly _onDidRegisterTerminalInstanceForToolSession = new Emitter(); + readonly onDidRegisterTerminalInstanceWithToolSession: Event = this._onDidRegisterTerminalInstanceForToolSession.event; + + /** + * Pending mappings restored from storage that have not yet been matched to a live terminal + * instance (we match by persistentProcessId when it becomes available after reconnection). + * toolSessionId -> persistentProcessId + */ + private readonly _pendingRestoredMappings = new Map(); + + constructor( + @ILogService private readonly _logService: ILogService, + @ITerminalService private readonly _terminalService: ITerminalService, + @IStorageService private readonly _storageService: IStorageService, + @ILifecycleService private readonly _lifecycleService: ILifecycleService + ) { + super(); + + this._restoreFromStorage(); + this._register(this._lifecycleService.onBeforeShutdown(async e => { + let veto = false; + // Show all hidden terminals before shutdown so they are restored + for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId) { + if (this.terminalIsHidden(toolSessionId) && (instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.state === PromptInputState.Execute || instance.hasChildProcesses)) { + await this._terminalService.showBackgroundTerminal(instance, true, true); + veto = true; + } + } + if (veto) { + e.veto(Promise.resolve(true), 'terminalChatService'); + } + })); + } + + registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void { + if (!terminalToolSessionId) { + this._logService.warn('Attempted to register a terminal instance with an undefined tool session ID'); + return; + } + this._terminalInstancesByToolSessionId.set(terminalToolSessionId, instance); + this._onDidRegisterTerminalInstanceForToolSession.fire(instance); + this._terminalInstanceListenersByToolSessionId.set(terminalToolSessionId, instance.onDisposed(() => { + this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); + this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId); + this._persistToStorage(); + })); + + if (typeof instance.persistentProcessId === 'number') { + this._persistToStorage(); + } + } + + getTerminalInstanceByToolSessionId(terminalToolSessionId: string | undefined): ITerminalInstance | undefined { + if (!terminalToolSessionId) { + return undefined; + } + if (this._pendingRestoredMappings.has(terminalToolSessionId)) { + const instance = this._terminalService.instances.find(i => i.persistentProcessId === this._pendingRestoredMappings.get(terminalToolSessionId)); + if (instance) { + this._tryAdoptRestoredMapping(instance); + return instance; + } + } + return this._terminalInstancesByToolSessionId.get(terminalToolSessionId); + } + + terminalIsHidden(terminalToolSessionId: string | undefined): boolean { + if (!terminalToolSessionId) { + return false; + } + const instance = this._terminalInstancesByToolSessionId.get(terminalToolSessionId); + if (!instance) { + return false; + } + return this._terminalService.instances.includes(instance) && !this._terminalService.foregroundInstances.includes(instance); + } + + private _restoreFromStorage(): void { + try { + const raw = this._storageService.get(TerminalChatService._storageKey, StorageScope.WORKSPACE); + if (!raw) { + return; + } + const parsed: [string, number][] = JSON.parse(raw); + for (const [toolSessionId, persistentProcessId] of parsed) { + if (typeof toolSessionId === 'string' && typeof persistentProcessId === 'number') { + this._pendingRestoredMappings.set(toolSessionId, persistentProcessId); + } + } + } catch (err) { + this._logService.warn('Failed to restore terminal chat tool session mappings', err); + } + } + + private _tryAdoptRestoredMapping(instance: ITerminalInstance): void { + if (this._pendingRestoredMappings.size === 0) { + return; + } + if (typeof instance.persistentProcessId !== 'number') { + return; + } + for (const [toolSessionId, persistentProcessId] of this._pendingRestoredMappings) { + if (persistentProcessId === instance.persistentProcessId) { + this._terminalInstancesByToolSessionId.set(toolSessionId, instance); + this._onDidRegisterTerminalInstanceForToolSession.fire(instance); + this._terminalInstanceListenersByToolSessionId.set(toolSessionId, instance.onDisposed(() => { + this._terminalInstancesByToolSessionId.delete(toolSessionId); + this._terminalInstanceListenersByToolSessionId.deleteAndDispose(toolSessionId); + this._persistToStorage(); + })); + this._pendingRestoredMappings.delete(toolSessionId); + this._persistToStorage(); + break; + } + } + } + + private _persistToStorage(): void { + try { + const entries: [string, number][] = []; + for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId.entries()) { + if (typeof instance.persistentProcessId === 'number' && instance.shouldPersist) { + entries.push([toolSessionId, instance.persistentProcessId]); + } + } + if (entries.length > 0) { + this._storageService.store(TerminalChatService._storageKey, JSON.stringify(entries), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } else { + this._storageService.remove(TerminalChatService._storageKey, StorageScope.WORKSPACE); + } + } catch (err) { + this._logService.warn('Failed to persist terminal chat tool session mappings', err); + } + } +} 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 72d099d21ea..ab6bec13e81 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -29,7 +29,7 @@ import { openTerminalSettingsLinkCommandId } from '../../../../chat/browser/chat import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; import { ChatConfiguration } from '../../../../chat/common/constants.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress, type IToolConfirmationMessages, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; -import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; +import { ITerminalService, type ITerminalInstance, ITerminalChatService } from '../../../../terminal/browser/terminal.js'; import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; @@ -172,6 +172,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { @ITerminalLogService private readonly _logService: ITerminalLogService, @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IChatService private readonly _chatService: IChatService ) { @@ -396,16 +397,17 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const timingStart = Date.now(); const termId = generateUuid(); + const terminalToolSessionId = (toolSpecificData as IChatTerminalToolInvocationData).terminalToolSessionId; const store = new DisposableStore(); this._logService.debug(`RunInTerminalTool: Creating ${args.isBackground ? 'background' : 'foreground'} terminal. termId=${termId}, chatSessionId=${chatSessionId}`); const toolTerminal = await (args.isBackground - ? this._initBackgroundTerminal(chatSessionId, termId, token) - : this._initForegroundTerminal(chatSessionId, termId, token)); + ? this._initBackgroundTerminal(chatSessionId, termId, terminalToolSessionId, token) + : this._initForegroundTerminal(chatSessionId, termId, terminalToolSessionId, token)); + + this._handleTerminalVisibility(toolTerminal); - this._terminalService.setActiveInstance(toolTerminal.instance); - this._terminalService.revealTerminal(toolTerminal.instance, true); const timingConnectMs = Date.now() - timingStart; const xterm = await toolTerminal.instance.xtermReadyPromise; @@ -614,6 +616,13 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } } + private _handleTerminalVisibility(toolTerminal: IToolTerminal) { + if (this._configurationService.getValue(TerminalChatAgentToolsSettingId.OutputLocation) === 'terminal') { + this._terminalService.setActiveInstance(toolTerminal.instance); + this._terminalService.revealTerminal(toolTerminal.instance, true); + } + } + // #region Terminal init protected async _getCopilotProfile(): Promise { @@ -681,10 +690,11 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { return false; } - private async _initBackgroundTerminal(chatSessionId: string, termId: string, token: CancellationToken): Promise { + private async _initBackgroundTerminal(chatSessionId: string, termId: string, terminalToolSessionId: string | undefined, token: CancellationToken): Promise { this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`); const profile = await this._getCopilotProfile(); const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); + this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, toolTerminal.instance); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { @@ -695,15 +705,17 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { return toolTerminal; } - private async _initForegroundTerminal(chatSessionId: string, termId: string, token: CancellationToken): Promise { + private async _initForegroundTerminal(chatSessionId: string, termId: string, terminalToolSessionId: string | undefined, token: CancellationToken): Promise { const cachedTerminal = this._sessionTerminalAssociations.get(chatSessionId); if (cachedTerminal) { this._logService.debug(`RunInTerminalTool: Using cached foreground terminal with session ID \`${chatSessionId}\``); this._terminalToolCreator.refreshShellIntegrationQuality(cachedTerminal); + this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, cachedTerminal.instance); return cachedTerminal; } const profile = await this._getCopilotProfile(); const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); + this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, toolTerminal.instance); this._registerInputListener(toolTerminal); this._sessionTerminalAssociations.set(chatSessionId, toolTerminal); if (token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index a3f55354e65..0b9f56098c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -8,6 +8,7 @@ import type { IJSONSchema } from '../../../../../base/common/jsonSchema.js'; import { localize } from '../../../../../nls.js'; import { type IConfigurationPropertySchema } from '../../../../../platform/configuration/common/configurationRegistry.js'; import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; +import product from '../../../../../platform/product/common/product.js'; import { terminalProfileBaseProperties } from '../../../../../platform/terminal/common/terminalPlatformConfiguration.js'; export const enum TerminalChatAgentToolsSettingId { @@ -15,6 +16,7 @@ export const enum TerminalChatAgentToolsSettingId { AutoApprove = 'chat.tools.terminal.autoApprove', ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout', AutoReplyToPrompts = 'chat.tools.terminal.autoReplyToPrompts', + OutputLocation = 'chat.tools.terminal.outputLocation', TerminalProfileLinux = 'chat.tools.terminal.terminalProfile.linux', TerminalProfileMacOs = 'chat.tools.terminal.terminalProfile.osx', @@ -386,6 +388,17 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Thu, 16 Oct 2025 14:13:29 -0700 Subject: [PATCH 1222/4355] Fix for chat uri handler (#271795) --- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index b3542d7dcbc..d224fe979c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1275,12 +1275,12 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr if (foundMode) { modeToUse = foundMode.id; } + // execute the command to change the mode in panel, note that the command only supports mode IDs, not names + await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); + return true; } - // execute the command to change the mode in panel, note that the command only supports mode IDs, not names - await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); - - return true; + return false; } })); } From 0df032fb9f886f574ae3309c5dfb81edade9fd7d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 16 Oct 2025 23:45:26 +0200 Subject: [PATCH 1223/4355] support handoff property in modes (#271710) * support handoff property in modes * allow empty prompt * Update src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix nls key --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../contrib/chat/common/chatModes.ts | 14 +++- .../promptHeaderAutocompletion.ts | 19 ++++- .../languageProviders/promptValidator.ts | 51 +++++++++++- .../promptSyntax/service/newPromptsParser.ts | 36 +++++++++ .../promptSyntax/service/promptsService.ts | 7 +- .../service/promptsServiceImpl.ts | 4 +- .../promptSytntax/promptValidator.test.ts | 41 ++++++++++ .../chat/test/common/chatModeService.test.ts | 1 + .../service/newPromptsParser.test.ts | 60 ++++++++++++++ .../service/promptsService.test.ts | 80 ++++++++++++++----- 10 files changed, 287 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index aa345b2874b..58470cb29d5 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -17,6 +17,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; import { ChatModeKind } from './constants.js'; +import { IHandOff } from './promptSyntax/service/newPromptsParser.js'; import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); @@ -99,6 +100,7 @@ export class ChatModeService extends Disposable implements IChatModeService { tools: cachedMode.customTools, model: cachedMode.model, modeInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, + handOffs: cachedMode.handOffs }; const instance = new CustomChatMode(customChatMode); this._customModeInstances.set(uri.toString(), instance); @@ -202,6 +204,7 @@ export interface IChatModeData { readonly model?: string; readonly modeInstructions?: IChatModeInstructions; readonly body?: string; /* deprecated */ + readonly handOffs?: readonly IHandOff[]; readonly uri?: URI; } @@ -213,6 +216,7 @@ export interface IChatMode { readonly isBuiltin: boolean; readonly kind: ChatModeKind; readonly customTools?: IObservable; + readonly handOffs?: IObservable; readonly model?: IObservable; readonly modeInstructions?: IObservable; readonly uri?: IObservable; @@ -242,6 +246,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { (mode.customTools === undefined || Array.isArray(mode.customTools)) && (mode.modeInstructions === undefined || (typeof mode.modeInstructions === 'object' && mode.modeInstructions !== null)) && (mode.model === undefined || typeof mode.model === 'string') && + (mode.handOffs === undefined || Array.isArray(mode.handOffs)) && (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)); } @@ -251,6 +256,7 @@ export class CustomChatMode implements IChatMode { private readonly _modeInstructions: ISettableObservable; private readonly _uriObservable: ISettableObservable; private readonly _modelObservable: ISettableObservable; + private readonly _handoffsObservable: ISettableObservable; public readonly id: string; public readonly name: string; @@ -283,6 +289,10 @@ export class CustomChatMode implements IChatMode { return this.name; } + get handOffs(): IObservable { + return this._handoffsObservable; + } + public readonly kind = ChatModeKind.Agent; constructor( @@ -295,6 +305,7 @@ export class CustomChatMode implements IChatMode { this._modelObservable = observableValue('model', customChatMode.model); this._modeInstructions = observableValue('_modeInstructions', customChatMode.modeInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); + this._handoffsObservable = observableValue('handoffs', customChatMode.handOffs); } /** @@ -320,7 +331,8 @@ export class CustomChatMode implements IChatMode { customTools: this.customTools.get(), model: this.model.get(), modeInstructions: this.modeInstructions.get(), - uri: this.uri.get() + uri: this.uri.get(), + handOffs: this.handOffs.get() }; } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index ca13e60ddf1..d84a8ac728d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -17,6 +17,7 @@ import { IPromptsService } from '../service/promptsService.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; import { PromptHeader } from '../service/newPromptsParser.js'; import { getValidAttributeNames } from './promptValidator.js'; +import { localize } from '../../../../../../nls.js'; export class PromptHeaderAutocompletion implements CompletionItemProvider { /** @@ -140,7 +141,6 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { } } - const bracketIndex = lineContent.indexOf('['); if (bracketIndex !== -1 && bracketIndex <= position.column - 1) { // if the property is already inside a bracket, we don't provide value completions @@ -158,6 +158,22 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { }; suggestions.push(item); } + if (property === 'handoffs' && (promptType === PromptsType.mode)) { + const value = [ + '', + ' - label: Start Implementation', + ' agent: agent', + ' prompt: Implement the plan', + ' send: true' + ].join('\n'); + const item: CompletionItem = { + label: localize('promptHeaderAutocompletion.handoffsExample', "Handoff Example"), + kind: CompletionItemKind.Value, + insertText: whilespaceAfterColon === 0 ? ` ${value}` : value, + range: new Range(position.lineNumber, colonPosition.column + whilespaceAfterColon + 1, position.lineNumber, model.getLineMaxColumn(position.lineNumber)), + }; + suggestions.push(item); + } return { suggestions }; } @@ -193,6 +209,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) { return this.getModelNames(promptType === PromptsType.mode); } + return []; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index b4772e2e4a6..5e388a31aff 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -140,6 +140,7 @@ export class PromptValidator { case PromptsType.mode: this.validateTools(attributes, ChatModeKind.Agent, report); this.validateModel(attributes, ChatModeKind.Agent, report); + this.validateHandoffs(attributes, report); break; } @@ -306,12 +307,60 @@ export class PromptValidator { return; } } + + private validateHandoffs(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { + const attribute = attributes.find(attr => attr.key === 'handoffs'); + if (!attribute) { + return; + } + if (attribute.value.type !== 'array') { + report(toMarker(localize('promptValidator.handoffsMustBeArray', "The 'handoffs' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); + return; + } + for (const item of attribute.value.items) { + if (item.type !== 'object') { + report(toMarker(localize('promptValidator.eachHandoffMustBeObject', "Each handoff in the 'handoffs' attribute must be an object with 'label', 'agent', 'prompt' and optional 'send'."), item.range, MarkerSeverity.Error)); + continue; + } + const required = new Set(['label', 'agent', 'prompt']); + for (const prop of item.properties) { + switch (prop.key.value) { + case 'label': + if (prop.value.type !== 'string' || prop.value.value.trim().length === 0) { + report(toMarker(localize('promptValidator.handoffLabelMustBeNonEmptyString', "The 'label' property in a handoff must be a non-empty string."), prop.value.range, MarkerSeverity.Error)); + } + break; + case 'agent': + if (prop.value.type !== 'string' || prop.value.value.trim().length === 0) { + report(toMarker(localize('promptValidator.handoffAgentMustBeNonEmptyString', "The 'agent' property in a handoff must be a non-empty string."), prop.value.range, MarkerSeverity.Error)); + } + break; + case 'prompt': + if (prop.value.type !== 'string') { + report(toMarker(localize('promptValidator.handoffPromptMustBeString', "The 'prompt' property in a handoff must be a string."), prop.value.range, MarkerSeverity.Error)); + } + break; + case 'send': + if (prop.value.type !== 'boolean') { + report(toMarker(localize('promptValidator.handoffSendMustBeBoolean', "The 'send' property in a handoff must be a boolean."), prop.value.range, MarkerSeverity.Error)); + } + break; + default: + report(toMarker(localize('promptValidator.unknownHandoffProperty', "Unknown property '{0}' in handoff object. Supported properties are 'label', 'agent', 'prompt' and optional 'send'.", prop.key.value), prop.value.range, MarkerSeverity.Warning)); + } + required.delete(prop.key.value); + } + if (required.size > 0) { + report(toMarker(localize('promptValidator.missingHandoffProperties', "Missing required properties {0} in handoff object.", Array.from(required).map(s => `'${s}'`).join(', ')), item.range, MarkerSeverity.Error)); + } + } + } } const validAttributeNames = { [PromptsType.prompt]: ['description', 'model', 'tools', 'mode'], [PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'], - [PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions'] + [PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs'] }; const validAttributeNamesNoExperimental = { [PromptsType.prompt]: validAttributeNames[PromptsType.prompt].filter(name => !isExperimentalAttribute(name)), diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 09fa9079953..d0cb0143a72 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -180,8 +180,44 @@ export class PromptHeader { return undefined; } + public get handOffs(): IHandOff[] | undefined { + const handoffsAttribute = this._parsedHeader.attributes.find(attr => attr.key === 'handoffs'); + if (!handoffsAttribute) { + return undefined; + } + if (handoffsAttribute.value.type === 'array') { + // Array format: list of objects: { agent, label, prompt, send? } + const handoffs: IHandOff[] = []; + for (const item of handoffsAttribute.value.items) { + if (item.type === 'object') { + let agent: string | undefined; + let label: string | undefined; + let prompt: string | undefined; + let send: boolean | undefined; + for (const prop of item.properties) { + if (prop.key.value === 'agent' && prop.value.type === 'string') { + agent = prop.value.value; + } else if (prop.key.value === 'label' && prop.value.type === 'string') { + label = prop.value.value; + } else if (prop.key.value === 'prompt' && prop.value.type === 'string') { + prompt = prop.value.value; + } else if (prop.key.value === 'send' && prop.value.type === 'boolean') { + send = prop.value.value; + } + } + if (agent && label && prompt !== undefined) { + handoffs.push({ agent, label, prompt, send }); + } + } + } + return handoffs; + } + return undefined; + } } +export interface IHandOff { readonly agent: string; readonly label: string; readonly prompt: string; readonly send?: boolean } + export interface IHeaderAttribute { readonly range: Range; readonly key: string; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 01466a2a9e4..43d12c89214 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -12,7 +12,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js import { PromptsType } from '../promptTypes.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatModeInstructions } from '../../chatModes.js'; -import { ParsedPromptFile } from './newPromptsParser.js'; +import { IHandOff, ParsedPromptFile } from './newPromptsParser.js'; import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; /** @@ -106,6 +106,11 @@ export interface ICustomChatMode { * Contents of the custom chat mode file body and other mode instructions. */ readonly modeInstructions: IChatModeInstructions; + + /** + * Hand-offs defined in the custom chat mode file. + */ + readonly handOffs?: readonly IHandOff[]; } /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 45ae2157870..3f9b9337d46 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -282,8 +282,8 @@ export class PromptsService extends Disposable implements IPromptsService { if (!ast.header) { return { uri, name, modeInstructions }; } - const { description, model, tools } = ast.header; - return { uri, name, description, model, tools, modeInstructions }; + const { description, model, tools, handOffs } = ast.header; + return { uri, name, description, model, tools, handOffs, modeInstructions }; }) ); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index 65b54be3b98..86c89d3ba6f 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -192,6 +192,47 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in mode files.`)); }); + + test('tools with invalid handoffs', async () => { + { + const content = [ + '---', + 'description: "Test"', + `handoffs: next`, + '---', + ].join('\n'); + const markers = await validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), [`The 'handoffs' attribute must be an array.`]); + } + { + const content = [ + '---', + 'description: "Test"', + `handoffs:`, + ` - label: '123'`, + '---', + ].join('\n'); + const markers = await validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), [`Missing required properties 'agent', 'prompt' in handoff object.`]); + } + { + const content = [ + '---', + 'description: "Test"', + `handoffs:`, + ` - label: '123'`, + ` agent: ''`, + ` prompt: ''`, + ` send: true`, + '---', + ].join('\n'); + const markers = await validate(content, PromptsType.mode); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), [`The 'agent' property in a handoff must be a non-empty string.`]); + } + }); }); suite('instructions', () => { diff --git a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts index 9b17fbdd988..a22e793ba3d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts @@ -130,6 +130,7 @@ suite('ChatModeService', () => { assert.strictEqual(testMode.kind, ChatModeKind.Agent); assert.deepStrictEqual(testMode.customTools?.get(), customMode.tools); assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.modeInstructions); + assert.deepStrictEqual(testMode.handOffs?.get(), customMode.handOffs); assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString()); }); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 8a91264ae25..e500e40d69e 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -57,6 +57,66 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.tools, ['tool1', 'tool2']); }); + test('mode with handoff', async () => { + const uri = URI.parse('file:///test/chatmode.md'); + const content = [ + /* 01 */'---', + /* 02 */`description: "Agent test"`, + /* 03 */'model: GPT 4.1', + /* 04 */'handoffs:', + /* 05 */' - label: "Implement"', + /* 06 */' agent: Default', + /* 07 */' prompt: "Implement the plan"', + /* 08 */' send: false', + /* 09 */' - label: "Save"', + /* 10 */' agent: Default', + /* 11 */' prompt: "Save the plan to a file"', + /* 12 */' send: true', + /* 13 */'---', + ].join('\n'); + const result = new NewPromptsParser().parse(uri, content); + assert.deepEqual(result.uri, uri); + assert.ok(result.header); + assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 13, endColumn: 1 }); + assert.deepEqual(result.header.attributes, [ + { key: 'description', range: new Range(2, 1, 2, 26), value: { type: 'string', value: 'Agent test', range: new Range(2, 14, 2, 26) } }, + { key: 'model', range: new Range(3, 1, 3, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(3, 8, 3, 15) } }, + { + key: 'handoffs', range: new Range(4, 1, 12, 15), value: { + type: 'array', + range: new Range(5, 3, 12, 15), + items: [ + { + type: 'object', range: new Range(5, 5, 8, 16), + properties: [ + { key: { type: 'string', value: 'label', range: new Range(5, 5, 5, 10) }, value: { type: 'string', value: 'Implement', range: new Range(5, 12, 5, 23) } }, + { key: { type: 'string', value: 'agent', range: new Range(6, 5, 6, 10) }, value: { type: 'string', value: 'Default', range: new Range(6, 12, 6, 19) } }, + { key: { type: 'string', value: 'prompt', range: new Range(7, 5, 7, 11) }, value: { type: 'string', value: 'Implement the plan', range: new Range(7, 13, 7, 33) } }, + { key: { type: 'string', value: 'send', range: new Range(8, 5, 8, 9) }, value: { type: 'boolean', value: false, range: new Range(8, 11, 8, 16) } }, + ] + }, + { + type: 'object', range: new Range(9, 5, 12, 15), + properties: [ + { key: { type: 'string', value: 'label', range: new Range(9, 5, 9, 10) }, value: { type: 'string', value: 'Save', range: new Range(9, 12, 9, 18) } }, + { key: { type: 'string', value: 'agent', range: new Range(10, 5, 10, 10) }, value: { type: 'string', value: 'Default', range: new Range(10, 12, 10, 19) } }, + { key: { type: 'string', value: 'prompt', range: new Range(11, 5, 11, 11) }, value: { type: 'string', value: 'Save the plan to a file', range: new Range(11, 13, 11, 38) } }, + { key: { type: 'string', value: 'send', range: new Range(12, 5, 12, 9) }, value: { type: 'boolean', value: true, range: new Range(12, 11, 12, 15) } }, + ] + }, + ] + } + }, + ]); + assert.deepEqual(result.header.description, 'Agent test'); + assert.deepEqual(result.header.model, 'GPT 4.1'); + assert.ok(result.header.handOffs); + assert.deepEqual(result.header.handOffs, [ + { label: 'Implement', agent: 'Default', prompt: 'Implement the plan', send: false }, + { label: 'Save', agent: 'Default', prompt: 'Save the plan to a file', send: true } + ]); + }); + test('instructions', async () => { const uri = URI.parse('file:///test/prompt1.md'); const content = [ diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index f1b6d0504dd..999ea40e068 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -731,26 +731,65 @@ suite('PromptsService', () => { }); - test('body with tool references', async () => { - const rootFolderName = 'custom-modes'; + test('header with handOffs', async () => { + const rootFolderName = 'custom-modes-with-handoffs'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); - sinon.stub(service, 'listPromptFiles') - .returns(Promise.resolve([ - // local instructions - { - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'), - storage: PromptsStorage.local, - type: PromptsType.mode, - }, - { - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), - storage: PromptsStorage.local, - type: PromptsType.instructions, + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + await (instaService.createInstance(MockFilesystem, + [{ + name: rootFolderName, + children: [ + { + name: '.github/chatmodes', + children: [ + { + name: 'mode1.chatmode.md', + contents: [ + '---', + 'description: \'Mode file 1.\'', + 'handoffs: [ { agent: "Edit", label: "Do it", prompt: "Do it now" } ]', + '---', + ], + } + ], + + }, + ], + }])).mock(); + + const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) })); + const expected: ICustomChatMode[] = [ + { + name: 'mode1', + description: 'Mode file 1.', + handOffs: [{ agent: 'Edit', label: 'Do it', prompt: 'Do it now', send: undefined }], + modeInstructions: { + content: '', + toolReferences: [], + metadata: undefined }, + model: undefined, + tools: undefined, + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), + }, + ]; - ])); + assert.deepEqual( + result, + expected, + 'Must get custom chat modes.', + ); + }); + + test('body with tool references', async () => { + const rootFolderName = 'custom-modes'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); // mock current workspace file structure await (instaService.createInstance(MockFilesystem, @@ -761,7 +800,7 @@ suite('PromptsService', () => { name: '.github/chatmodes', children: [ { - name: 'mode1.instructions.md', + name: 'mode1.chatmode.md', contents: [ '---', 'description: \'Mode file 1.\'', @@ -771,7 +810,7 @@ suite('PromptsService', () => { ], }, { - name: 'mode2.instructions.md', + name: 'mode2.chatmode.md', contents: [ 'First use #tool2\nThen use #tool1', ], @@ -782,7 +821,7 @@ suite('PromptsService', () => { ], }])).mock(); - const result = await service.getCustomChatModes(CancellationToken.None); + const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) })); const expected: ICustomChatMode[] = [ { name: 'mode1', @@ -793,8 +832,9 @@ suite('PromptsService', () => { toolReferences: [{ name: 'tool1', range: { start: 11, endExclusive: 17 } }], metadata: undefined }, + handOffs: undefined, model: undefined, - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'), + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), }, { name: 'mode2', @@ -806,7 +846,7 @@ suite('PromptsService', () => { ], metadata: undefined }, - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'), + uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.chatmode.md'), } ]; From ef4342262138748bac6403648f259acba3f73c24 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Oct 2025 15:52:52 -0700 Subject: [PATCH 1224/4355] Enable sessions view for the team --- .vscode/settings.json | 1 + .../chat/browser/chatSessions/view/chatSessionsView.ts | 6 +++--- src/vs/workbench/contrib/chat/browser/chatStatus.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9176c8db754..2a11ccbf3b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -216,6 +216,7 @@ "editor.aiStats.enabled": true, // Team selfhosting on ai stats "chat.emptyState.history.enabled": true, + "chat.agentSessionsViewLocation": "view", "github.copilot.chat.advanced.taskTools.enabled": true, "chat.promptFilesRecommendations": { "plan-fast": true, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index ae25b928388..e5f3ce688b4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -93,10 +93,10 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, - title: nls.localize2('chat.sessions', "Chat Sessions"), + title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), hideIfEmpty: false, - icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Chat Sessions View'), + icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); this.isViewContainerRegistered = true; @@ -173,7 +173,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { } override getTitle(): string { - const title = nls.localize('chat.sessions.title', "Chat Sessions"); + const title = nls.localize('chat.agent.sessions.title', "Agent Sessions"); return title; } diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index dfa6c119766..a6225c451e0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -441,10 +441,10 @@ class ChatStatusDashboard extends Disposable { const inProgress = this.chatSessionsService.getInProgress(); if (inProgress.some(item => item.count > 0)) { - addSeparator(localize('chatSessionsTitle', "Chat Sessions"), toAction({ + addSeparator(localize('chatAgentSessionsTitle', "Agent Sessions"), toAction({ id: 'workbench.view.chat.status.sessions', - label: localize('viewChatSessionsLabel', "View Chat Sessions"), - tooltip: localize('viewChatSessionsTooltip', "View Chat Sessions"), + label: localize('viewChatSessionsLabel', "View Agent Sessions"), + tooltip: localize('viewChatSessionsTooltip', "View Agent Sessions"), class: ThemeIcon.asClassName(Codicon.eye), run: () => this.runCommandAndClose('workbench.view.chat.sessions'), })); From af1cbea727abf8c68e9bb12c3655695112c89950 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:18:23 -0700 Subject: [PATCH 1225/4355] Add `--video` and `--autostart` (#271849) This enables: * Playwright video recording * Starting the Code OSS on server start (for clients like Copilot CLI that don't react to tool list changes) --- test/automation/src/code.ts | 1 + test/automation/src/playwrightBrowser.ts | 8 ++- test/automation/src/playwrightElectron.ts | 1 + test/mcp/scripts/start-stdio.sh | 7 +++ test/mcp/src/application.ts | 74 +++-------------------- test/mcp/src/automation.ts | 9 ++- test/mcp/src/automationTools/core.ts | 28 ++++----- test/mcp/src/multiplex.ts | 4 ++ test/mcp/src/options.ts | 41 +++++++++++++ 9 files changed, 89 insertions(+), 84 deletions(-) create mode 100644 test/mcp/scripts/start-stdio.sh create mode 100644 test/mcp/src/options.ts diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 85b2566cdcc..34c3a0717b4 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -24,6 +24,7 @@ export interface LaunchOptions { readonly logger: Logger; logsPath: string; crashesPath: string; + readonly videosPath?: string; verbose?: boolean; useInMemorySecretStorage?: boolean; readonly extraArgs?: string[]; diff --git a/test/automation/src/playwrightBrowser.ts b/test/automation/src/playwrightBrowser.ts index 1e2bf3058f6..463ebb842d1 100644 --- a/test/automation/src/playwrightBrowser.ts +++ b/test/automation/src/playwrightBrowser.ts @@ -105,7 +105,13 @@ async function launchBrowser(options: LaunchOptions, endpoint: string) { browser.on('disconnected', () => logger.log(`Playwright: browser disconnected`)); - const context = await measureAndLog(() => browser.newContext(), 'browser.newContext', logger); + const context = await measureAndLog( + () => browser.newContext({ + recordVideo: options.videosPath ? { dir: options.videosPath } : undefined + }), + 'browser.newContext', + logger + ); if (tracing) { try { diff --git a/test/automation/src/playwrightElectron.ts b/test/automation/src/playwrightElectron.ts index 9ad74d2f166..29555eb8b02 100644 --- a/test/automation/src/playwrightElectron.ts +++ b/test/automation/src/playwrightElectron.ts @@ -33,6 +33,7 @@ async function launchElectron(configuration: IElectronConfiguration, options: La const electron = await measureAndLog(() => playwrightImpl._electron.launch({ executablePath: configuration.electronPath, args: configuration.args, + recordVideo: options.videosPath ? { dir: options.videosPath } : undefined, env: configuration.env as { [key: string]: string }, timeout: 0 }), 'playwright-electron#launch', logger); diff --git a/test/mcp/scripts/start-stdio.sh b/test/mcp/scripts/start-stdio.sh new file mode 100644 index 00000000000..9f8cd85dd61 --- /dev/null +++ b/test/mcp/scripts/start-stdio.sh @@ -0,0 +1,7 @@ + +SCRIPT_DIR="$(dirname -- "$( readlink -f -- "$0"; )")" +# Go to mcp server project root +cd "$SCRIPT_DIR/.." + +# Start mcp +npm run start-stdio -- --video --autostart diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index 9cfafeb60cc..7754eab111c 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -10,71 +10,12 @@ import * as fs from 'fs'; import * as os from 'os'; import * as vscodetest from '@vscode/test-electron'; import { createApp, retry } from './utils'; -import * as minimist from 'minimist'; +import { opts } from './options'; const rootPath = path.join(__dirname, '..', '..', '..'); - -const [, , ...args] = process.argv; -const opts = minimist(args, { - string: [ - 'browser', - 'build', - 'stable-build', - 'wait-time', - 'test-repo', - 'electronArgs' - ], - boolean: [ - 'verbose', - 'remote', - 'web', - 'headless', - 'tracing' - ], - default: { - verbose: false - } -}) as { - verbose?: boolean; - remote?: boolean; - headless?: boolean; - web?: boolean; - tracing?: boolean; - build?: string; - 'stable-build'?: string; - browser?: 'chromium' | 'webkit' | 'firefox' | 'chromium-msedge' | 'chromium-chrome' | undefined; - electronArgs?: string; -}; - -const logsRootPath = (() => { - const logsParentPath = path.join(rootPath, '.build', 'logs'); - - let logsName: string; - if (opts.web) { - logsName = 'smoke-tests-browser'; - } else if (opts.remote) { - logsName = 'smoke-tests-remote'; - } else { - logsName = 'smoke-tests-electron'; - } - - return path.join(logsParentPath, logsName); -})(); - -const crashesRootPath = (() => { - const crashesParentPath = path.join(rootPath, '.build', 'crashes'); - - let crashesName: string; - if (opts.web) { - crashesName = 'smoke-tests-browser'; - } else if (opts.remote) { - crashesName = 'smoke-tests-remote'; - } else { - crashesName = 'smoke-tests-electron'; - } - - return path.join(crashesParentPath, crashesName); -})(); +const logsRootPath = path.join(rootPath, '.build', 'vscode-playwright-mcp', 'logs'); +const crashesRootPath = path.join(rootPath, '.build', 'vscode-playwright-mcp', 'crashes'); +const videoRootPath = path.join(rootPath, '.build', 'vscode-playwright-mcp', 'videos'); const logger = createLogger(); @@ -291,7 +232,7 @@ async function setup(): Promise { logger.log('Smoketest setup done!\n'); } -export async function getApplication() { +export async function getApplication({ recordVideo }: { recordVideo?: boolean } = {}) { const testCodePath = getDevElectronPath(); const electronPath = testCodePath; if (!fs.existsSync(electronPath || '')) { @@ -315,6 +256,7 @@ export async function getApplication() { logger, logsPath: path.join(logsRootPath, 'suite_unknown'), crashesPath: path.join(crashesRootPath, 'suite_unknown'), + videosPath: (recordVideo || opts.video) ? videoRootPath : undefined, verbose: opts.verbose, remote: opts.remote, web: opts.web, @@ -350,12 +292,12 @@ export class ApplicationService { return this._application; } - async getOrCreateApplication(): Promise { + async getOrCreateApplication({ recordVideo }: { recordVideo?: boolean } = {}): Promise { if (this._closing) { await this._closing; } if (!this._application) { - this._application = await getApplication(); + this._application = await getApplication({ recordVideo }); this._application.code.driver.currentPage.on('close', () => { this._closing = (async () => { if (this._application) { diff --git a/test/mcp/src/automation.ts b/test/mcp/src/automation.ts index 5b52e14a0a8..8e4f84a25fe 100644 --- a/test/mcp/src/automation.ts +++ b/test/mcp/src/automation.ts @@ -7,6 +7,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ApplicationService } from './application'; import { applyAllTools } from './automationTools/index.js'; import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { z } from 'zod'; export async function getServer(appService: ApplicationService): Promise { const server = new McpServer({ @@ -18,9 +19,11 @@ export async function getServer(appService: ApplicationService): Promise server.tool( 'vscode_automation_start', 'Start VS Code Build', - {}, - async () => { - const app = await appService.getOrCreateApplication(); + { + recordVideo: z.boolean().optional() + }, + async ({ recordVideo }) => { + const app = await appService.getOrCreateApplication({ recordVideo }); return { content: [{ type: 'text' as const, diff --git a/test/mcp/src/automationTools/core.ts b/test/mcp/src/automationTools/core.ts index 879f58ef7c0..3f9e34cf1e6 100644 --- a/test/mcp/src/automationTools/core.ts +++ b/test/mcp/src/automationTools/core.ts @@ -32,20 +32,20 @@ export function applyCoreTools(server: McpServer, appService: ApplicationService // } // ); - // I don't think Playwright needs this - // server.tool( - // 'vscode_automation_stop', - // 'Stop the VS Code application', - // async () => { - // await app.stop(); - // return { - // content: [{ - // type: 'text' as const, - // text: 'VS Code stopped successfully' - // }] - // }; - // } - // ); + tools.push(server.tool( + 'vscode_automation_stop', + 'Stop the VS Code application', + async () => { + const app = await appService.getOrCreateApplication(); + await app.stop(); + return { + content: [{ + type: 'text' as const, + text: 'VS Code stopped successfully' + }] + }; + } + )); // This doesn't seem particularly useful // server.tool( diff --git a/test/mcp/src/multiplex.ts b/test/mcp/src/multiplex.ts index 5d3c26da43f..b5f96f0d709 100644 --- a/test/mcp/src/multiplex.ts +++ b/test/mcp/src/multiplex.ts @@ -11,6 +11,7 @@ import { ApplicationService } from './application'; import { createInMemoryTransportPair } from './inMemoryTransport'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Application } from '../../automation'; +import { opts } from './options'; interface SubServerConfig { subServer: Client; @@ -81,6 +82,9 @@ export async function getServer(): Promise { } }); + if (opts.autostart) { + await appService.getOrCreateApplication(); + } return multiplexServer.server; } diff --git a/test/mcp/src/options.ts b/test/mcp/src/options.ts new file mode 100644 index 00000000000..ac4ded14760 --- /dev/null +++ b/test/mcp/src/options.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as minimist from 'minimist'; + +const [, , ...args] = process.argv; +export const opts = minimist(args, { + string: [ + 'browser', + 'build', + 'stable-build', + 'wait-time', + 'test-repo', + 'electronArgs' + ], + boolean: [ + 'verbose', + 'remote', + 'web', + 'headless', + 'tracing', + 'video', + 'autostart' + ], + default: { + verbose: false + } +}) as { + verbose?: boolean; + remote?: boolean; + headless?: boolean; + web?: boolean; + tracing?: boolean; + build?: string; + 'stable-build'?: string; + browser?: 'chromium' | 'webkit' | 'firefox' | 'chromium-msedge' | 'chromium-chrome' | undefined; + electronArgs?: string; + video?: boolean; + autostart?: boolean; +}; From af6adc61cebb854d26c9b0e065df66f6f560080f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:20:07 +0200 Subject: [PATCH 1226/4355] SCM - introduce single selection mode for repositories view (#271831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial implementation * Pinning/unpinning selection is working * 💄 fix hygiene --- .../contrib/scm/browser/scm.contribution.ts | 12 ++- .../contrib/scm/browser/scmHistoryViewPane.ts | 5 +- .../scm/browser/scmRepositoriesViewPane.ts | 63 ++++++++++++- .../contrib/scm/browser/scmViewService.ts | 88 +++++++++++++++---- 4 files changed, 147 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index fa0903c081c..5c2cd7d202b 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -348,6 +348,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('providersVisible', "Controls how many repositories are visible in the Source Control Repositories section. Set to 0, to be able to manually resize the view."), default: 10 }, + 'scm.repositories.selectionMode': { + type: 'string', + enum: ['multiple', 'single'], + enumDescriptions: [ + localize('scm.repositories.selectionMode.multiple', "Multiple repositories can be selected at the same time."), + localize('scm.repositories.selectionMode.single', "Only one repository can be selected at a time.") + ], + description: localize('scm.repositories.selectionMode', "Controls the selection mode of the repositories in the Source Control Repositories view."), + default: 'multiple' + }, 'scm.showActionButton': { type: 'boolean', markdownDescription: localize('showActionButton', "Controls whether an action button can be shown in the Source Control view."), @@ -549,7 +559,7 @@ CommandsRegistry.registerCommand('scm.setActiveProvider', async (accessor) => { const scmViewService = accessor.get(ISCMViewService); const placeHolder = localize('scmActiveRepositoryPlaceHolder', "Select the active repository, type to filter all repositories"); - const autoQuickItemDescription = localize('scmActiveRepositoryAutoDescription', "The active repository is updated based on focused repository/active editor"); + const autoQuickItemDescription = localize('scmActiveRepositoryAutoDescription', "The active repository is updated based on active editor"); const repositoryPicker = instantiationService.createInstance(RepositoryPicker, placeHolder, autoQuickItemDescription); const result = await repositoryPicker.pickRepository(); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 8a36faae018..30d78427915 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -169,7 +169,10 @@ registerAction2(class extends ViewAction { f1: false, menu: { id: MenuId.SCMHistoryTitle, - when: ContextKeyExpr.and(ContextKeyExpr.has('scm.providerCount'), ContextKeyExpr.greater('scm.providerCount', 1)), + when: ContextKeyExpr.and( + ContextKeyExpr.has('scm.providerCount'), + ContextKeyExpr.greater('scm.providerCount', 1), + ContextKeyExpr.equals('config.scm.repositories.selectionMode', 'multiple')), group: 'navigation', order: 0 } diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 7a76f7e5854..a8d0406c5ab 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browse import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent } from '../../../../base/browser/ui/tree/tree.js'; import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; @@ -24,11 +24,13 @@ import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRende import { collectContextMenuActions, getActionViewItemProvider, isSCMRepository } from './util.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { MenuId, registerAction2, Action2 } from '../../../../platform/actions/common/actions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { autorun, IObservable, observableFromEvent, observableSignalFromEvent } from '../../../../base/common/observable.js'; import { Sequencer } from '../../../../base/common/async.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { RepositoryContextKeys } from './scmViewService.js'; class ListDelegate implements IListVirtualDelegate { @@ -190,6 +192,7 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.treeDataSource); const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); + const selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'multiple', this.configurationService); this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, @@ -214,6 +217,7 @@ export class SCMRepositoriesViewPane extends ViewPane { }, compressionEnabled: compressionEnabled.get(), overrideStyles: this.getLocationBasedColors().listOverrideStyles, + multipleSelectionSupport: selectionModeConfig.get() === 'multiple', expandOnDoubleClick: false, expandOnlyOnTwistieClick: true, accessibilityProvider: { @@ -228,6 +232,11 @@ export class SCMRepositoriesViewPane extends ViewPane { ) as WorkbenchCompressibleAsyncDataTree; this._register(this.tree); + this._register(autorun(reader => { + const selectionMode = selectionModeConfig.read(reader); + this.tree.updateOptions({ multipleSelectionSupport: selectionMode === 'multiple' }); + })); + this._register(this.tree.onDidChangeSelection(this.onTreeSelectionChange, this)); this._register(this.tree.onDidChangeFocus(this.onTreeDidChangeFocus, this)); this._register(this.tree.onDidFocus(this.onDidTreeFocus, this)); @@ -372,3 +381,53 @@ export class SCMRepositoriesViewPane extends ViewPane { super.dispose(); } } + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'scm.repositories.pinSelection', + title: localize('scmPinSelection', "Pin the Current Selection"), + f1: false, + icon: Codicon.pin, + menu: { + id: MenuId.SCMSourceControlTitle, + when: RepositoryContextKeys.RepositoryPinned.isEqualTo(false), + group: 'navigation', + order: 1 + }, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const scmViewService = accessor.get(ISCMViewService); + const activeRepository = scmViewService.activeRepository.get(); + if (!activeRepository) { + return; + } + + scmViewService.pinActiveRepository(activeRepository); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'scm.repositories.unpinSelection', + title: localize('scmUnpinSelection', "Unpin the Current Selection"), + f1: false, + icon: Codicon.pinned, + menu: { + id: MenuId.SCMSourceControlTitle, + when: RepositoryContextKeys.RepositoryPinned.isEqualTo(true), + group: 'navigation', + order: 2 + }, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const scmViewService = accessor.get(ISCMViewService); + scmViewService.pinActiveRepository(undefined); + } +}); + diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 9460688190f..c52de42d8b0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -18,7 +18,7 @@ import { binarySearch } from '../../../../base/common/arrays.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue } from '../../../../base/common/observable.js'; +import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -26,6 +26,7 @@ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../. import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { localize } from '../../../../nls.js'; +import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; function getProviderStorageKey(provider: ISCMProvider): string { return `${provider.providerId}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`; @@ -42,6 +43,7 @@ function getRepositoryName(workspaceContextService: IWorkspaceContextService, re export const RepositoryContextKeys = { RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime), + RepositoryPinned: new RawContextKey('scmRepositoryPinned', false) }; export type RepositoryQuickPickItem = IQuickPickItem & { repository: 'auto' | ISCMRepository }; @@ -204,16 +206,20 @@ export class SCMViewService implements ISCMViewService { private readonly _activeEditorRepositoryObs: IObservable; /** - * The focused repository takes precedence over the active editor repository when the observable - * values are updated in the same transaction (or during the initial read of the observable value). + * The focused repository takes precedence over the active editor repository when the observable + * values are updated in the same transaction (or during the initial read of the observable value). */ private readonly _activeRepositoryObs: IObservable; private readonly _activeRepositoryPinnedObs: ISettableObservable; private readonly _focusedRepositoryObs: IObservable; + private readonly _selectionModeConfig: IObservable<'multiple' | 'single'>; + private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; + private _repositoryPinnedContextKey: IContextKey; + constructor( @ISCMService private readonly scmService: ISCMService, @IContextKeyService contextKeyService: IContextKeyService, @@ -226,6 +232,8 @@ export class SCMViewService implements ISCMViewService { ) { this.menus = instantiationService.createInstance(SCMMenus); + this._selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'multiple', this.configurationService); + this._focusedRepositoryObs = observableFromEventOpts( { owner: this, @@ -253,7 +261,7 @@ export class SCMViewService implements ISCMViewService { return lastValue; } - return Object.create(repository); + return repository; }); this._activeRepositoryPinnedObs = observableValue(this, undefined); @@ -269,8 +277,33 @@ export class SCMViewService implements ISCMViewService { return activeRepositoryPinned ?? activeRepository; }); + this.disposables.add(autorun(reader => { + const selectionMode = this._selectionModeConfig.read(undefined); + const activeRepository = this.activeRepository.read(reader); + + if (selectionMode === 'single' && activeRepository) { + this.visibleRepositories = [activeRepository]; + } + })); + + this.disposables.add(runOnChange(this._selectionModeConfig, selectionMode => { + if (selectionMode === 'single' && this.visibleRepositories.length > 1) { + const repository = this.visibleRepositories[0]; + this.visibleRepositories = [repository]; + } + })); + try { this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); + + // If previously there were multiple visible repositories but the + // view mode is `single`, only restore the first visible repository. + if (this.previousState && this.previousState.visible.length > 1 && this._selectionModeConfig.get() === 'single') { + this.previousState = { + ...this.previousState, + visible: [this.previousState.visible[0]] + }; + } } catch { // noop } @@ -279,6 +312,9 @@ export class SCMViewService implements ISCMViewService { this._sortKeyContextKey = RepositoryContextKeys.RepositorySortKey.bindTo(contextKeyService); this._sortKeyContextKey.set(this._repositoriesSortKey); + this._repositoryPinnedContextKey = RepositoryContextKeys.RepositoryPinned.bindTo(contextKeyService); + this._repositoryPinnedContextKey.set(!!this._activeRepositoryPinnedObs.get()); + scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -314,19 +350,24 @@ export class SCMViewService implements ISCMViewService { if (index === -1) { // This repository is not part of the previous state which means that it // was either manually closed in the previous session, or the repository - // was added after the previous session.In this case, we should select all - // of the repositories. + // was added after the previous session. In this case, we should select + // all of the repositories. const added: ISCMRepository[] = []; this.insertRepositoryView(this._repositories, repositoryView); - this._repositories.forEach((repositoryView, index) => { - if (repositoryView.selectionIndex === -1) { - added.push(repositoryView.repository); - } - repositoryView.selectionIndex = index; - }); - this._onDidChangeRepositories.fire({ added, removed: Iterable.empty() }); + if (this._selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + // Multiple selection mode or single selection mode (select first repository) + this._repositories.forEach((repositoryView, index) => { + if (repositoryView.selectionIndex === -1) { + added.push(repositoryView.repository); + } + repositoryView.selectionIndex = index; + }); + + this._onDidChangeRepositories.fire({ added, removed: Iterable.empty() }); + } + this.didSelectRepository = false; return; } @@ -352,10 +393,18 @@ export class SCMViewService implements ISCMViewService { } } - const maxSelectionIndex = this.getMaxSelectionIndex(); - this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); - this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed }); + if (this._selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + // Multiple selection mode or single selection mode (select first repository) + const maxSelectionIndex = this.getMaxSelectionIndex(); + this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); + this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed }); + } else { + // Single selection mode (add subsequent repository) + this.insertRepositoryView(this._repositories, repositoryView); + this._onDidChangeRepositories.fire({ added: Iterable.empty(), removed }); + } + // Focus repository if nothing is focused if (!this._repositories.find(r => r.focused)) { this.focus(repository); } @@ -410,7 +459,11 @@ export class SCMViewService implements ISCMViewService { } if (visible) { - this.visibleRepositories = [...this.visibleRepositories, repository]; + if (this._selectionModeConfig.get() === 'single') { + this.visibleRepositories = [repository]; + } else if (this._selectionModeConfig.get() === 'multiple') { + this.visibleRepositories = [...this.visibleRepositories, repository]; + } } else { const index = this.visibleRepositories.indexOf(repository); @@ -445,6 +498,7 @@ export class SCMViewService implements ISCMViewService { pinActiveRepository(repository: ISCMRepository | undefined): void { this._activeRepositoryPinnedObs.set(repository, undefined); + this._repositoryPinnedContextKey.set(!!repository); } private compareRepositories(op1: ISCMRepositoryView, op2: ISCMRepositoryView): number { From ba9828d1dc52768006183268dbd314c4b3b8f28c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:45:11 -0700 Subject: [PATCH 1227/4355] Add custom eslint rules info as readme Copying the wiki section into the repo too so tools and contributors can find it more easily --- .eslint-plugin-local/README.md | 125 +++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .eslint-plugin-local/README.md diff --git a/.eslint-plugin-local/README.md b/.eslint-plugin-local/README.md new file mode 100644 index 00000000000..2f6da02e3b8 --- /dev/null +++ b/.eslint-plugin-local/README.md @@ -0,0 +1,125 @@ +# Custom ESLint rules + +We use a set of custom [ESLint](http://eslint.org) to enforce repo specific coding rules and styles. These custom rules are run in addition to many standard ESLine rules we enable in the project. Some example custom rules includes: + +- Enforcing proper code layering +- Preventing checking in of `test.only(...)` +- Enforcing conventions in `vscode.d.ts` + +Custom rules are mostly used for enforcing or banning certain coding patterns. We tend to leave stylistic choices up to area owners unless there's a good reason to enforce something project wide. + +This doc provides a brief overview of how these rules are setup and how you can add a new one. + +# Resources +- [ESLint rules](https://eslint.org/docs/latest/extend/custom-rules) — General documentation about writing eslint rules +- [TypeScript ASTs and eslint](https://typescript-eslint.io/blog/asts-and-typescript-eslint/) — Look at how ESLint works with TS programs +- [ESTree selectors](https://eslint.org/docs/latest/extend/selectors) — Info about the selector syntax rules use to target specific nodes in an AST. Works similarly to css selectors. +- [TypeScript ESLint playground](https://typescript-eslint.io/play/#showAST=es) — Useful tool for figuring out the structure of TS programs and debugging custom rule selectors + + +# Custom Rule Configuration + +Custom rules are defined in the `.eslint-plugin-local` folder. Each rule is defined in its own TypeScript file. These follow the naming convention: + +- `code-RULE-NAME.ts` — General rules that apply to the entire repo. +- `vscode-dts-RULE-NAME.ts` — Rules that apply just to `vscode.d.ts`. + +These rules are then enabled in the `eslint.config.js` file. This is the main eslint configuration for our repo. It defines a set of file scopes which rules should apple to files in those scopes. + +For example, here's a configuration that enables the no `test.only` rule in all `*.test.ts` files in the VS Code repo: + +```ts +{ + // Define which files these rules apply to + files: [ + '**/*.test.ts' + ], + languageOptions: { parser: tseslint.parser, }, + plugins: { + 'local': pluginLocal, + }, + rules: { + // Enable the rule from .eslint-plugin-local/code-no-test-only.ts + 'local/code-no-test-only': 'error', + } +} +``` + +# Creating a new custom rule +This walks through the steps to create a new eslint rule: + +1. Create a new rule file under `.eslint-plugin-local`. Generally you should call it `code-YOUR-RULE-NAME.ts`, for example, `.eslint-plugin-local/code-no-not-null-assertions-on-undefined-values.ts` + +2. In this file, add the rule. Here's a template: + + ```ts + /*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + import * as eslint from 'eslint'; + + export = new class YourRuleName implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + customMessageName: 'message text shown in errors/warnings', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + [SELECTOR]: (node: any) => { + // Report errors if needed + return context.report({ + node, + messageId: 'customMessageName' + }); + } + }; + } + }; + ``` + + - Update the name of the class to match the name of your rule + - Add message entries for any errors you want to report + - Update `SELECTOR` with the [ESTree selector](https://eslint.org/docs/latest/extend/selectors) needed to target the nodes you are interested in. Use the [TypeScript ESLint playground](https://typescript-eslint.io/play/#showAST=es) to figure out which nodes you need and debug selectors + +3. Register the rule in `eslint.config.js` + + Generally this is just turning on the rule in the rule list like so: + + ```js + rules: { + // Name should match file name + 'local/code-no-not-null-assertions-on-undefined-values': 'warn', + ... + } + ``` + +Rules can also take custom arguments. For example, here's how we can pass arguments to a custom rule in the `eslint.config.js`: + +``` +rules: { + 'local/code-no-not-null-assertions-on-undefined-values': ['warn', { testsOk: true }], + ... +} +``` + +In these cases make sure to update the `meta.schema` property on your rule with the JSON schema for the arguments. You can access these arguments using `context.options` in the rule `create` function + + +## Adding fixes to custom rules +Fixes are a useful way to mechanically fix basic linting issues, such as auto inserting semicolons. These fix typically work at the AST level, say they are more reliable way to perform bulk fixes compared to find/replaces + +To add a fix for a custom rule: + +1. On the `meta` for your rule, add `fixable: 'code'` + +2. When reporting an error in the rule, also include a `fix`. This is a function that takes a `fixer` argument and returns one or more fixes. + +See the [Double quoted to single quoted string covert fix](https://github.com/microsoft/vscode/blob/b074375e1884ae01033967bf0bbceeaa4795354a/.eslint-plugin-local/code-no-unexternalized-strings.ts#L128) for an example. The ESLint docs also have [details on adding fixes and the fixer api](https://eslint.org/docs/latest/extend/custom-rules#applying-fixes) + +The fixes can be run using `npx eslint --fix` in the VS Code repo From 98503adeebdbfdaaa7e3b83bc3a2a8523305103a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:47:42 -0700 Subject: [PATCH 1228/4355] Update .eslint-plugin-local/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .eslint-plugin-local/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslint-plugin-local/README.md b/.eslint-plugin-local/README.md index 2f6da02e3b8..bb70707bed0 100644 --- a/.eslint-plugin-local/README.md +++ b/.eslint-plugin-local/README.md @@ -1,6 +1,6 @@ # Custom ESLint rules -We use a set of custom [ESLint](http://eslint.org) to enforce repo specific coding rules and styles. These custom rules are run in addition to many standard ESLine rules we enable in the project. Some example custom rules includes: +We use a set of custom [ESLint](http://eslint.org) to enforce repo specific coding rules and styles. These custom rules are run in addition to many standard ESLint rules we enable in the project. Some example custom rules includes: - Enforcing proper code layering - Preventing checking in of `test.only(...)` From 0a4864aa7f18261f05e4b0e2ce5b0c5677fd626b Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:47:53 -0700 Subject: [PATCH 1229/4355] Update .eslint-plugin-local/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .eslint-plugin-local/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslint-plugin-local/README.md b/.eslint-plugin-local/README.md index bb70707bed0..adb5a59ebcb 100644 --- a/.eslint-plugin-local/README.md +++ b/.eslint-plugin-local/README.md @@ -24,7 +24,7 @@ Custom rules are defined in the `.eslint-plugin-local` folder. Each rule is defi - `code-RULE-NAME.ts` — General rules that apply to the entire repo. - `vscode-dts-RULE-NAME.ts` — Rules that apply just to `vscode.d.ts`. -These rules are then enabled in the `eslint.config.js` file. This is the main eslint configuration for our repo. It defines a set of file scopes which rules should apple to files in those scopes. +These rules are then enabled in the `eslint.config.js` file. This is the main eslint configuration for our repo. It defines a set of file scopes which rules should apply to files in those scopes. For example, here's a configuration that enables the no `test.only` rule in all `*.test.ts` files in the VS Code repo: From f6e388e29e5811f42d407725a4246386fd04a5da Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:48:02 -0700 Subject: [PATCH 1230/4355] Update .eslint-plugin-local/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .eslint-plugin-local/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslint-plugin-local/README.md b/.eslint-plugin-local/README.md index adb5a59ebcb..1d100cfba07 100644 --- a/.eslint-plugin-local/README.md +++ b/.eslint-plugin-local/README.md @@ -112,7 +112,7 @@ In these cases make sure to update the `meta.schema` property on your rule with ## Adding fixes to custom rules -Fixes are a useful way to mechanically fix basic linting issues, such as auto inserting semicolons. These fix typically work at the AST level, say they are more reliable way to perform bulk fixes compared to find/replaces +Fixes are a useful way to mechanically fix basic linting issues, such as auto inserting semicolons. These fixes typically work at the AST level, so they are a more reliable way to perform bulk fixes compared to find/replaces. To add a fix for a custom rule: From 55cdeb577125ddd62e0a6a5d1a292f862f34c2be Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 16 Oct 2025 17:06:01 -0700 Subject: [PATCH 1231/4355] Add prefer device flow config for GitHub auth (#271838) * Initial plan * Add forceDeviceCodeFlow setting for GitHub authentication Co-authored-by: pwang347 <8560030+pwang347@users.noreply.github.com> * modifications * nit --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pwang347 <8560030+pwang347@users.noreply.github.com> --- extensions/github-authentication/package.json | 6 ++ .../github-authentication/package.nls.json | 3 +- extensions/github-authentication/src/flows.ts | 14 +++- .../src/test/flows.test.ts | 65 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 6d22dfe1a6a..91f2d1b57db 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -62,6 +62,12 @@ "default": true, "scope": "application", "markdownDescription": "%config.github-authentication.useElectronFetch.description%" + }, + "github-authentication.preferDeviceCodeFlow": { + "type": "boolean", + "default": false, + "scope": "application", + "markdownDescription": "%config.github-authentication.preferDeviceCodeFlow.description%" } } } diff --git a/extensions/github-authentication/package.nls.json b/extensions/github-authentication/package.nls.json index 4f45cd5d4f6..dfbd033224d 100644 --- a/extensions/github-authentication/package.nls.json +++ b/extensions/github-authentication/package.nls.json @@ -3,5 +3,6 @@ "description": "GitHub Authentication Provider", "config.github-enterprise.title": "GHE.com & GitHub Enterprise Server Authentication", "config.github-enterprise.uri.description": "The URI for your GHE.com or GitHub Enterprise Server instance.\n\nExamples:\n* GHE.com: `https://octocat.ghe.com`\n* GitHub Enterprise Server: `https://github.octocat.com`\n\n> **Note:** This should _not_ be set to a GitHub.com URI. If your account exists on GitHub.com or is a GitHub Enterprise Managed User, you do not need any additional configuration and can simply log in to GitHub.", - "config.github-authentication.useElectronFetch.description": "When true, uses Electron's built-in fetch function for HTTP requests. When false, uses the Node.js global fetch function. This setting only applies when running in the Electron environment. **Note:** A restart is required for this setting to take effect." + "config.github-authentication.useElectronFetch.description": "When true, uses Electron's built-in fetch function for HTTP requests. When false, uses the Node.js global fetch function. This setting only applies when running in the Electron environment. **Note:** A restart is required for this setting to take effect.", + "config.github-authentication.preferDeviceCodeFlow.description": "When true, prioritize the device code flow for authentication instead of other available flows. This is useful for environments like WSL where the local server or URL handler flows may not work as expected." } diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index 5a917fbabca..76da600118a 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import { ProgressLocation, Uri, commands, env, l10n, window } from 'vscode'; +import { ProgressLocation, Uri, commands, env, l10n, window, workspace } from 'vscode'; import { Log } from './common/logger'; import { Config } from './config'; import { UriEventHandler } from './github'; @@ -612,7 +612,7 @@ const allFlows: IFlow[] = [ ]; export function getFlows(query: IFlowQuery) { - return allFlows.filter(flow => { + const validFlows = allFlows.filter(flow => { let useFlow: boolean = true; switch (query.target) { case GitHubTarget.DotCom: @@ -648,6 +648,16 @@ export function getFlows(query: IFlowQuery) { } return useFlow; }); + + const preferDeviceCodeFlow = workspace.getConfiguration('github-authentication').get('preferDeviceCodeFlow', false); + if (preferDeviceCodeFlow) { + return [ + ...validFlows.filter(flow => flow instanceof DeviceCodeFlow), + ...validFlows.filter(flow => !(flow instanceof DeviceCodeFlow)) + ]; + } + + return validFlows; } /** diff --git a/extensions/github-authentication/src/test/flows.test.ts b/extensions/github-authentication/src/test/flows.test.ts index 77c023e4819..fdcdd0f3f45 100644 --- a/extensions/github-authentication/src/test/flows.test.ts +++ b/extensions/github-authentication/src/test/flows.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { ExtensionHost, GitHubTarget, IFlowQuery, getFlows } from '../flows'; import { Config } from '../config'; +import * as vscode from 'vscode'; const enum Flows { UrlHandlerFlow = 'url handler', @@ -193,4 +194,68 @@ suite('getFlows', () => { } }); } + + suite('preferDeviceCodeFlow configuration', () => { + let originalConfig: boolean | undefined; + + suiteSetup(async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + originalConfig = config.get('preferDeviceCodeFlow'); + }); + + suiteTeardown(async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', originalConfig, vscode.ConfigurationTarget.Global); + }); + + test('returns device code flow first when preferDeviceCodeFlow is true - VS Code Desktop', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.Local, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // Should return device code flow first, then other flows + assert.strictEqual(flows.length, 3, `Expected 3 flows, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.DeviceCodeFlow); + // Other flows should still be available + assert.strictEqual(flows[1].label, Flows.LocalServerFlow); + assert.strictEqual(flows[2].label, Flows.UrlHandlerFlow); + }); + + test('returns device code flow first when preferDeviceCodeFlow is true - Remote', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.Remote, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // Should return device code flow first, then other flows + assert.strictEqual(flows.length, 2, `Expected 2 flows, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.DeviceCodeFlow); + assert.strictEqual(flows[1].label, Flows.UrlHandlerFlow); + }); + + test('returns normal flows when preferDeviceCodeFlow is true but device code flow is not supported - WebWorker', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.WebWorker, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // WebWorker doesn't support DeviceCodeFlow, so should return normal flows + // Based on the original logic, WebWorker + DotCom should return UrlHandlerFlow + assert.strictEqual(flows.length, 1, `Expected 1 flow for WebWorker configuration, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.UrlHandlerFlow); + }); + }); }); From d02b204cb021856004c130ae59d8cef80812d312 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:14:38 -0700 Subject: [PATCH 1232/4355] Make sure more TS commands are disabled when tsgo is active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to clear the contexts when switching to tsgo --- extensions/typescript-language-features/package.json | 4 ++-- .../src/languageFeatures/fileReferences.ts | 9 ++++++--- .../src/languageFeatures/sourceDefinition.ts | 11 +++++++---- .../src/ui/managedFileContext.ts | 9 ++++++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b48ba9ea67a..9dcb59d3e50 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1632,7 +1632,7 @@ }, { "command": "typescript.goToProjectConfig", - "when": "editorLangId == typescriptreact" + "when": "editorLangId == typescriptreact && typescript.isManagedFile" }, { "command": "javascript.goToProjectConfig", @@ -1682,7 +1682,7 @@ "editor/context": [ { "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", + "when": "!config.typescript.experimental.useTsgo && tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", "group": "navigation@1.41" } ], diff --git a/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts b/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts index 3a475ac257b..c9d47d6ebff 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts @@ -79,11 +79,14 @@ export function register( client: ITypeScriptServiceClient, commandManager: CommandManager ) { - function updateContext() { - vscode.commands.executeCommand('setContext', FileReferencesCommand.context, client.apiVersion.gte(FileReferencesCommand.minVersion)); + function updateContext(overrideValue?: boolean) { + vscode.commands.executeCommand('setContext', FileReferencesCommand.context, overrideValue ?? client.apiVersion.gte(FileReferencesCommand.minVersion)); } updateContext(); commandManager.register(new FileReferencesCommand(client)); - return client.onTsServerStarted(() => updateContext()); + return vscode.Disposable.from( + client.onTsServerStarted(() => updateContext()), + new vscode.Disposable(() => updateContext(false)), + ); } diff --git a/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts index 301f8607a1e..f3798a0a53f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts +++ b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts @@ -80,12 +80,15 @@ class SourceDefinitionCommand implements Command { export function register( client: ITypeScriptServiceClient, commandManager: CommandManager -) { - function updateContext() { - vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, client.apiVersion.gte(SourceDefinitionCommand.minVersion)); +): vscode.Disposable { + function updateContext(overrideValue?: boolean) { + vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, overrideValue ?? client.apiVersion.gte(SourceDefinitionCommand.minVersion)); } updateContext(); commandManager.register(new SourceDefinitionCommand(client)); - return client.onTsServerStarted(() => updateContext()); + return vscode.Disposable.from( + client.onTsServerStarted(() => updateContext()), + new vscode.Disposable(() => updateContext(false)), + ); } diff --git a/extensions/typescript-language-features/src/ui/managedFileContext.ts b/extensions/typescript-language-features/src/ui/managedFileContext.ts index 1da4588a334..c66b90c9e82 100644 --- a/extensions/typescript-language-features/src/ui/managedFileContext.ts +++ b/extensions/typescript-language-features/src/ui/managedFileContext.ts @@ -18,13 +18,20 @@ export default class ManagedFileContextManager extends Disposable { private isInManagedFileContext: boolean = false; - public constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) { + constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) { super(); activeJsTsEditorTracker.onDidChangeActiveJsTsEditor(this.onDidChangeActiveTextEditor, this, this._disposables); this.onDidChangeActiveTextEditor(activeJsTsEditorTracker.activeJsTsEditor); } + override dispose() { + // Clear the context + this.updateContext(false); + + super.dispose(); + } + private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): void { if (editor) { this.updateContext(this.isManagedFile(editor)); From b4d7420912bb840112b4874293393f1b6400ec1c Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Thu, 16 Oct 2025 17:44:41 -0700 Subject: [PATCH 1233/4355] Enables passing selection or cursor position as context to remote agent --- .../browser/actions/chatExecuteActions.ts | 59 +++++++++++++++++-- .../contrib/chat/browser/chat.contribution.ts | 1 - .../browser/inlineChatZoneWidget.ts | 1 - 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 684524f887f..d2e0e5d688d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -11,6 +11,7 @@ import { basename, relativePath } from '../../../../../base/common/resources.js' import { ThemeIcon } from '../../../../../base/common/themables.js'; import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; +import { isLocation } from '../../../../../editor/common/languages.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; @@ -25,6 +26,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js'; import { IChatAgent, IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys, ChatContextKeyExprs } from '../../common/chatContextKeys.js'; @@ -737,10 +739,6 @@ export class CreateRemoteAgentJobAction extends Action2 { * Converts full URIs from the user's systems into workspace-relative paths for coding agent. */ private extractRelativeFromAttachedContext(attachedContext: ChatRequestVariableSet, workspaceContextService: IWorkspaceContextService): string[] { - const workspaceFolder = workspaceContextService.getWorkspace().folders[0]; - if (!workspaceFolder) { - return []; - } if (!attachedContext) { return []; } @@ -750,8 +748,9 @@ export class CreateRemoteAgentJobAction extends Action2 { if (!(contextEntry.value instanceof URI)) { continue; } + const workspaceFolder = workspaceContextService.getWorkspaceFolder(contextEntry.value); const fileUri = contextEntry.value; - const relativePathResult = relativePath(workspaceFolder.uri, fileUri); + const relativePathResult = workspaceFolder ? relativePath(workspaceFolder.uri, fileUri) : undefined; if (relativePathResult) { relativePaths.push(relativePathResult); } @@ -776,6 +775,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const remoteCodingAgentService = accessor.get(IRemoteCodingAgentsService); const chatSessionsService = accessor.get(IChatSessionsService); const workspaceContextService = accessor.get(IWorkspaceContextService); + const editorService = accessor.get(IEditorService); const widget = widgetService.lastFocusedWidget; if (!widget) { @@ -803,6 +803,30 @@ export class CreateRemoteAgentJobAction extends Action2 { const attachedContext = widget.input.getAttachedAndImplicitContext(sessionId); widget.input.acceptInput(true); + // For inline editor mode, add selection or cursor information + if (widget.location === ChatAgentLocation.EditorInline) { + const activeEditor = editorService.activeTextEditorControl; + if (activeEditor) { + const model = activeEditor.getModel(); + let activeEditorUri: URI | undefined = undefined; + if (model && 'uri' in model) { + activeEditorUri = model.uri as URI; + } + const selection = activeEditor.getSelection(); + if (activeEditorUri && selection) { + attachedContext.add({ + kind: 'file', + id: 'vscode.implicit.selection', + name: basename(activeEditorUri), + value: { + uri: activeEditorUri, + range: selection + }, + }); + } + } + } + const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat); const instantiationService = accessor.get(IInstantiationService); const requestParser = instantiationService.createInstance(ChatRequestParser); @@ -839,6 +863,31 @@ export class CreateRemoteAgentJobAction extends Action2 { summary += `\n\n${localize('attachedFiles', "The user has attached the following files from their workspace:")}\n${relativeAttachedContext.map(file => `- ${file}`).join('\n')}\n\n`; } + // Add selection or cursor information to the summary + attachedContext.asArray().forEach(ctx => { + if (isChatRequestFileEntry(ctx) && ctx.value && isLocation(ctx.value)) { + const range = ctx.value.range; + const isSelection = range.startLineNumber !== range.endLineNumber || range.startColumn !== range.endColumn; + + // Get relative path for the file + let filePath = ctx.name; + const workspaceFolder = workspaceContextService.getWorkspaceFolder(ctx.value.uri); + + if (workspaceFolder && ctx.value.uri) { + const relativePathResult = relativePath(workspaceFolder.uri, ctx.value.uri); + if (relativePathResult) { + filePath = relativePathResult; + } + } + + if (isSelection) { + summary += `User has selected text in file ${filePath} from ${range.startLineNumber}:${range.startColumn} to ${range.endLineNumber}:${range.endColumn}\n`; + } else { + summary += `User is on file ${filePath} at position ${range.startLineNumber}:${range.startColumn}\n`; + } + } + }); + // -- summarize context if necessary if (defaultAgent && chatRequests.length > 1) { chatModel.acceptResponseProgress(addedRequest, { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 1980b07c4d8..236262563ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -190,7 +190,6 @@ configurationRegistry.registerConfiguration({ }, default: { 'panel': 'always', - 'editor': 'always', } }, 'chat.implicitContext.suggestedContext': { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 64ff3aeb7df..85a7c8b047b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -86,7 +86,6 @@ export class InlineChatZoneWidget extends ZoneWidget { telemetrySource: 'interactiveEditorWidget-toolbar', inputSideToolbar: MENU_INLINE_CHAT_SIDE }, - enableImplicitContext: true, ...options, rendererOptions: { renderTextEditsAsSummary: (uri) => { From e71973a6ba16175650141f12ba5f049b1fe30624 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 16 Oct 2025 19:10:51 -0700 Subject: [PATCH 1234/4355] Bump distro (#271873) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 929140ae9fb..299c470a67a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "cf7a6d44d8299723d955936df569ea424e6b68fd", + "distro": "d5a36b1bb1ce3814ad7e416d17ce669df183eeb6", "author": { "name": "Microsoft Corporation" }, From 820f25892015277555a91be5c88c92d9aea396e1 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:20:13 -0700 Subject: [PATCH 1235/4355] ChatSessions: Select Model (#271728) * fix merge conflicts * Modify LanguageModelsService to manage Chat Session "contributed" model identifiers * refactor to sessionModelPickerActionItem * add missed file * rename to IChatSessionProviderOptions * change to provideChatSessionProviderOptions --- .../api/browser/mainThreadChatSessions.ts | 56 ++++++++++- .../workbench/api/common/extHost.protocol.ts | 14 ++- .../api/common/extHostChatSessions.ts | 72 +++++++++++++- .../browser/mainThreadChatSessions.test.ts | 6 +- .../browser/actions/chatExecuteActions.ts | 33 ++++++- .../browser/actions/chatSessionActions.ts | 9 -- .../contrib/chat/browser/chatInputPart.ts | 93 ++++++++++++++++-- .../chat/browser/chatSessions.contribution.ts | 98 ++++++++++++++++++- .../sessionModelPickerActionItem.ts | 79 +++++++++++++++ .../modelPicker/modelPickerActionItem.ts | 7 +- .../contrib/chat/common/chatContextKeys.ts | 1 + .../contrib/chat/common/chatModel.ts | 12 ++- .../contrib/chat/common/chatServiceImpl.ts | 26 ++--- .../chat/common/chatSessionsService.ts | 31 ++++++ .../chat/test/common/languageModels.ts | 14 ++- .../vscode.proposed.chatSessionsProvider.d.ts | 38 +++++++ 16 files changed, 546 insertions(+), 43 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index c37362d8234..b031593220b 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -20,6 +20,7 @@ import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; import { ChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; import { ChatSessionUri } from '../../contrib/chat/common/chatUri.js'; +import { ILanguageModelChatMetadata } from '../../contrib/chat/common/languageModels.js'; import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IEditorGroup, IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; @@ -37,7 +38,10 @@ export class ObservableChatSession extends Disposable implements ChatSession { readonly sessionResource: URI; readonly providerHandle: number; readonly history: Array; - + private _options?: { model?: ILanguageModelChatMetadata } | undefined; + public get options(): { model?: ILanguageModelChatMetadata } | undefined { + return this._options; + } private readonly _progressObservable = observableValue(this, []); private readonly _isCompleteObservable = observableValue(this, false); @@ -110,6 +114,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { token ); + this._options = sessionContent.options; this.history.length = 0; this.history.push(...sessionContent.history.map((turn: IChatSessionHistoryItemDto) => { if (turn.type === 'request') { @@ -311,6 +316,8 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat readonly onDidChangeItems: Emitter; }>()); private readonly _contentProvidersRegistrations = this._register(new DisposableMap()); + private readonly _contentProviderModels = new Map(); + private readonly _sessionTypeToHandle = new Map(); private readonly _activeSessions = new Map(); private readonly _sessionDisposables = new Map(); @@ -329,6 +336,17 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat super(); this._proxy = this._extHostContext.getProxy(ExtHostContext.ExtHostChatSessions); + + this._chatSessionsService.setOptionsChangeCallback(async (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => { + const handle = this._getHandleForSessionType(chatSessionType); + if (handle !== undefined) { + await this.notifyOptionsChange(handle, sessionId, updates); + } + }); + } + + private _getHandleForSessionType(chatSessionType: string): number | undefined { + return this._sessionTypeToHandle.get(chatSessionType); } $registerChatSessionItemProvider(handle: number, chatSessionType: string): void { @@ -477,11 +495,29 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat provideChatSessionContent: (id, resource, token) => this._provideChatSessionContent(handle, id, resource, token) }; + this._sessionTypeToHandle.set(chatSessionType, handle); this._contentProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(chatSessionType, provider)); + this._proxy.$provideChatSessionProviderOptions(handle, CancellationToken.None).then(options => { + if (options?.models && options.models.length) { + this._contentProviderModels.set(handle, options.models); + const serviceModels: ILanguageModelChatMetadata[] = options.models.map(m => ({ ...m })); + this._chatSessionsService.setModelsForSessionType(chatSessionType, handle, serviceModels); + } + }).catch(err => this._logService.error('Error fetching chat session options', err)); } $unregisterChatSessionContentProvider(handle: number): void { this._contentProvidersRegistrations.deleteAndDispose(handle); + this._contentProviderModels.delete(handle); + + // Remove session type mapping + for (const [sessionType, h] of this._sessionTypeToHandle) { + if (h === handle) { + this._sessionTypeToHandle.delete(sessionType); + break; + } + } + // dispose all sessions from this provider and clean up its disposables for (const [key, session] of this._activeSessions) { if (session.providerHandle === handle) { @@ -567,4 +603,22 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat }, position); } } + + /** + * Get the available models for a session provider + */ + getModelsForProvider(handle: number): ILanguageModelChatMetadata[] | undefined { + return this._contentProviderModels.get(handle); + } + + /** + * Notify the extension about option changes for a session + */ + async notifyOptionsChange(handle: number, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>): Promise { + try { + await this._proxy.$provideHandleOptionsChange(handle, sessionId, updates, CancellationToken.None); + } catch (error) { + this._logService.error(`Error notifying extension about options change for handle ${handle}, sessionId ${sessionId}:`, error); + } + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index eebd6db4546..497ed675745 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -62,7 +62,7 @@ import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineRefe import { IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; -import { IChatMessage, IChatResponsePart, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatSelector } from '../../contrib/chat/common/languageModels.js'; +import { IChatMessage, IChatResponsePart, ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatSelector } from '../../contrib/chat/common/languageModels.js'; import { IPreparedToolInvocation, IToolInvocation, IToolInvocationPreparationContext, IToolProgressStep, IToolResult, ToolDataSource } from '../../contrib/chat/common/languageModelToolsService.js'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from '../../contrib/debug/common/debug.js'; import { McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from '../../contrib/mcp/common/mcpTypes.js'; @@ -3156,6 +3156,10 @@ export interface MainThreadChatStatusShape { export type IChatSessionHistoryItemDto = { type: 'request'; prompt: string; participant: string } | { type: 'response'; parts: IChatProgressDto[]; participant: string }; +export interface ChatSessionOptionUpdateDto { + readonly optionId: string; + readonly value: string | undefined; +} export interface ChatSessionDto { id: string; resource: UriComponents; @@ -3163,8 +3167,11 @@ export interface ChatSessionDto { hasActiveResponseCallback: boolean; hasRequestHandler: boolean; supportsInterruption: boolean; + options?: { model?: ILanguageModelChatMetadata }; +} +export interface IChatSessionProviderOptions { + models?: ILanguageModelChatMetadata[]; } - export interface MainThreadChatSessionsShape extends IDisposable { $registerChatSessionItemProvider(handle: number, chatSessionType: string): void; @@ -3184,8 +3191,9 @@ export interface MainThreadChatSessionsShape extends IDisposable { export interface ExtHostChatSessionsShape { $provideChatSessionItems(providerHandle: number, token: CancellationToken): Promise[]>; $provideNewChatSessionItem(providerHandle: number, options: { request: IChatAgentRequest; metadata?: any }, token: CancellationToken): Promise>; - $provideChatSessionContent(providerHandle: number, sessionId: string, sessionResource: UriComponents, token: CancellationToken): Promise; + $provideChatSessionProviderOptions(providerHandle: number, token: CancellationToken): Promise; + $provideHandleOptionsChange(providerHandle: number, sessionId: string, updates: ReadonlyArray, token: CancellationToken): Promise; $interruptChatSessionActiveResponse(providerHandle: number, sessionId: string, requestId: string): Promise; $disposeChatSessionContent(providerHandle: number, sessionId: string): Promise; $invokeChatSessionRequestHandler(providerHandle: number, id: string, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 97ec8895f12..52562834ae9 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -15,7 +15,7 @@ import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/c import { ChatSessionStatus, IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { Proxied } from '../../services/extensions/common/proxyIdentifier.js'; -import { ChatSessionDto, ExtHostChatSessionsShape, IChatAgentProgressShape, MainContext, MainThreadChatSessionsShape } from './extHost.protocol.js'; +import { ChatSessionDto, ExtHostChatSessionsShape, IChatAgentProgressShape, IChatSessionProviderOptions, MainContext, MainThreadChatSessionsShape } from './extHost.protocol.js'; import { ChatAgentResponseStream } from './extHostChatAgents2.js'; import { CommandsConverter, ExtHostCommands } from './extHostCommands.js'; import { ExtHostLanguageModels } from './extHostLanguageModels.js'; @@ -276,13 +276,24 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio this._proxy.$handleProgressComplete(handle, id, 'ongoing'); }); } - const { capabilities } = provider; + const { capabilities, extension } = provider; return { id: sessionId + '', resource: URI.revive(resource), hasActiveResponseCallback: !!session.activeResponseCallback, hasRequestHandler: !!session.requestHandler, supportsInterruption: !!capabilities?.supportsInterruptions, + options: { + ...(session.options?.model && { + model: { + ...session.options.model, + extension: extension.identifier, + vendor: 'contributed', + modelPickerCategory: undefined, + capabilities: {}, + } + }) + }, history: session.history.map(turn => { if (turn instanceof extHostTypes.ChatRequestTurn) { return { type: 'request' as const, prompt: turn.prompt, participant: turn.participant }; @@ -300,6 +311,63 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }; } + async $provideHandleOptionsChange(handle: number, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>, token: CancellationToken): Promise { + const provider = this._chatSessionContentProviders.get(handle); + if (!provider) { + this._logService.warn(`No provider for handle ${handle}`); + return; + } + + if (!provider.provider.provideHandleOptionsChange) { + this._logService.debug(`Provider for handle ${handle} does not implement provideHandleOptionsChange`); + return; + } + + try { + await provider.provider.provideHandleOptionsChange(sessionId, updates, token); + } catch (error) { + this._logService.error(`Error calling provideHandleOptionsChange for handle ${handle}, sessionId ${sessionId}:`, error); + } + } + + async $provideChatSessionProviderOptions(handle: number, token: CancellationToken): Promise { + const entry = this._chatSessionContentProviders.get(handle); + if (!entry) { + this._logService.warn(`No provider for handle ${handle} when requesting chat session options`); + return; + } + + const provider = entry.provider; + if (!provider.provideChatSessionProviderOptions) { + return; + } + + try { + const extOptions = await provider.provideChatSessionProviderOptions(token); + if (!extOptions) { + return; + } + + const { models } = extOptions; + if (!models || models.length === 0) { + return {}; + } + return { + models: models.map(m => ({ + ...m, + extension: entry.extension.identifier, + vendor: 'contributed', + modelPickerCategory: undefined, + capabilities: {}, + + })) + }; + } catch (error) { + this._logService.error(`Error calling provideChatSessionProviderOptions for handle ${handle}:`, error); + return; + } + } + async $interruptChatSessionActiveResponse(providerHandle: number, sessionId: string, requestId: string): Promise { const key = `${providerHandle}_${sessionId}`; const entry = this._extHostChatSessions.get(key); diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 4af39a81a08..9145bb7d931 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -29,7 +29,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IViewsService } from '../../../services/views/common/viewsService.js'; import { mock, TestExtensionService } from '../../../test/common/workbenchTestServices.js'; import { MainThreadChatSessions, ObservableChatSession } from '../../browser/mainThreadChatSessions.js'; -import { ExtHostChatSessionsShape, IChatProgressDto } from '../../common/extHost.protocol.js'; +import { ExtHostChatSessionsShape, IChatProgressDto, IChatSessionProviderOptions } from '../../common/extHost.protocol.js'; suite('ObservableChatSession', function () { let disposables: DisposableStore; @@ -49,6 +49,8 @@ suite('ObservableChatSession', function () { proxy = { $provideChatSessionContent: sinon.stub(), + $provideChatSessionProviderOptions: sinon.stub<[providerHandle: number, token: CancellationToken], Promise>().resolves(undefined), + $provideHandleOptionsChange: sinon.stub(), $interruptChatSessionActiveResponse: sinon.stub(), $invokeChatSessionRequestHandler: sinon.stub(), $disposeChatSessionContent: sinon.stub(), @@ -355,6 +357,8 @@ suite('MainThreadChatSessions', function () { proxy = { $provideChatSessionContent: sinon.stub(), + $provideChatSessionProviderOptions: sinon.stub<[providerHandle: number, token: CancellationToken], Promise>().resolves(undefined), + $provideHandleOptionsChange: sinon.stub(), $interruptChatSessionActiveResponse: sinon.stub(), $invokeChatSessionRequestHandler: sinon.stub(), $disposeChatSessionContent: sinon.stub(), diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index d00d61eb7cd..29dcdf95ee3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -436,7 +436,6 @@ class OpenModelPickerAction extends Action2 { } } } - export class OpenModePickerAction extends Action2 { static readonly ID = 'workbench.action.chat.openModePicker'; @@ -480,6 +479,37 @@ export class OpenModePickerAction extends Action2 { } } +export class ChatSessionOpenModelPickerAction extends Action2 { + static readonly ID = 'workbench.action.chat.chatSessionOpenModelPicker'; + constructor() { + super({ + id: ChatSessionOpenModelPickerAction.ID, + title: localize2('interactive.openModelPicker.label', "Open Model Picker"), + category: CHAT_CATEGORY, + f1: false, + precondition: ChatContextKeys.enabled, + menu: { + id: MenuId.ChatInput, + order: 4, + group: 'navigation', + when: + ContextKeyExpr.and( + ChatContextKeys.lockedToCodingAgent, + ChatContextKeys.chatSessionHasModels + ) + } + }); + } + + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const widgetService = accessor.get(IChatWidgetService); + const widget = widgetService.lastFocusedWidget; + if (widget) { + widget.input.openChatSessionModelPicker(); + } + } +} + export const ChangeChatModelActionId = 'workbench.action.chat.changeModel'; class ChangeChatModelAction extends Action2 { static readonly ID = ChangeChatModelActionId; @@ -1161,6 +1191,7 @@ export function registerChatExecuteActions() { registerAction2(SwitchToNextModelAction); registerAction2(OpenModelPickerAction); registerAction2(OpenModePickerAction); + registerAction2(ChatSessionOpenModelPickerAction); registerAction2(ChangeChatModelAction); registerAction2(CancelEdit); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 54ed47797b4..4b92867a8c3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -37,15 +37,6 @@ import { ChatViewPane } from '../chatViewPane.js'; import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; -export interface IChatSessionContext { - sessionId: string; - sessionType: 'editor' | 'widget'; - currentTitle: string; - editorInput?: any; - editorGroup?: any; - widget?: any; -} - interface IMarshalledChatSessionContext { $mid: MarshalledId.ChatSessionContext; session: ChatSessionItemWithProvider; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 43aad3857e5..c75adfaaf7f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -80,7 +80,8 @@ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; -import { IChatFollowup } from '../common/chatService.js'; +import { IChatFollowup, IChatService } from '../common/chatService.js'; +import { IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; @@ -89,7 +90,7 @@ import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, IL import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; -import { CancelAction, ChatDelegateToEditSessionAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; +import { CancelAction, ChatDelegateToEditSessionAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSessionOpenModelPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; @@ -101,6 +102,7 @@ import { ChatDragAndDrop } from './chatDragAndDrop.js'; import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js'; import { ChatFollowups } from './chatFollowups.js'; import { ChatSelectedTools } from './chatSelectedTools.js'; +import { ChatSessionModelPickerActionItem } from './chatSessions/sessionModelPickerActionItem.js'; import { IChatViewState } from './chatWidget.js'; import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js'; @@ -171,6 +173,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge readonly onDidClickOverlay: Event; private readonly _attachmentModel: ChatAttachmentModel; + private _widget?: IChatWidget; public get attachmentModel(): ChatAttachmentModel { return this._attachmentModel; } @@ -292,11 +295,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private chatModeKindKey: IContextKey; private withinEditSessionKey: IContextKey; private filePartOfEditSessionKey: IContextKey; - + private chatSessionHasModels: IContextKey; private modelWidget: ModelPickerActionItem | undefined; private modeWidget: ModePickerActionItem | undefined; + private chatSessionModelWidget: ChatSessionModelPickerActionItem | undefined; private readonly _waitForPersistedLanguageModel: MutableDisposable; private _onDidChangeCurrentLanguageModel: Emitter; + private _onDidChangeChatSessionLanguageModel: Emitter; private _currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined; @@ -411,6 +416,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IChatModeService private readonly chatModeService: IChatModeService, @IPromptsService private readonly promptsService: IPromptsService, @ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService, + @IChatService private readonly chatService: IChatService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super(); this._onDidLoadInputState = this._register(new Emitter()); @@ -442,6 +449,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeCurrentLanguageModel = this._register(new Emitter()); this._onDidChangeCurrentChatMode = this._register(new Emitter()); this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; + this._onDidChangeChatSessionLanguageModel = this._register(new Emitter()); this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); this._chatEditsActionsDisposables = this._register(new DisposableStore()); this._chatEditsDisposables = this._register(new DisposableStore()); @@ -450,6 +458,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this.editorService.onDidActiveEditorChange(() => { this._indexOfLastOpenedContext = -1; + this.refreshChatSessionPicker(); })); this._attachmentModel = this._register(this.instantiationService.createInstance(ChatAttachmentModel)); @@ -472,6 +481,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatModeKindKey = ChatContextKeys.chatModeKind.bindTo(contextKeyService); this.withinEditSessionKey = ChatContextKeys.withinEditSessionDiff.bindTo(contextKeyService); this.filePartOfEditSessionKey = ChatContextKeys.filePartOfEditSession.bindTo(contextKeyService); + this.chatSessionHasModels = ChatContextKeys.chatSessionHasModels.bindTo(contextKeyService); const chatToolCount = ChatContextKeys.chatToolCount.bindTo(contextKeyService); @@ -644,6 +654,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.modeWidget?.show(); } + public openChatSessionModelPicker(): void { + this.chatSessionModelWidget?.show(); + } + public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { this._currentLanguageModel = model; @@ -1076,7 +1090,37 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderAttachedContext(); } + private refreshChatSessionPicker(): ILanguageModelChatMetadataAndIdentifier | undefined { + this.chatSessionHasModels.set(false); + const sessionId = this._widget?.viewModel?.model.sessionId; + if (!sessionId) { + return; + } + const ctx = this.chatService.getChatSessionFromInternalId(sessionId); + if (!ctx) { + return; + } + const models = this.chatSessionsService.getModelsForSessionType(ctx.chatSessionType); + if (!models) { + return; + } + + this.chatSessionHasModels.set(true); + + const currentModelId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, 'model'); + if (!currentModelId) { + return; + } + const model = models.find(m => m.identifier === currentModelId); + if (model) { + this._onDidChangeChatSessionLanguageModel.fire(model); + } + return model; + } + render(container: HTMLElement, initialValue: string, widget: IChatWidget) { + this._widget = widget; + let elements; if (this.options.renderStyle === 'compact') { elements = dom.h('.interactive-input-part', [ @@ -1273,21 +1317,58 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge getCurrentModel: () => this._currentLanguageModel, onDidChangeModel: this._onDidChangeCurrentLanguageModel.event, setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { - // The user changed the language model, so we don't wait for the persisted option to be registered this._waitForPersistedLanguageModel.clear(); this.setCurrentLanguageModel(model); this.renderAttachedContext(); }, getModels: () => this.getModels() }; - return this.modelWidget = this.instantiationService.createInstance(ModelPickerActionItem, action, this._currentLanguageModel, itemDelegate); + return this.modelWidget = this.instantiationService.createInstance(ModelPickerActionItem, action, this._currentLanguageModel, undefined, itemDelegate); } else if (action.id === OpenModePickerAction.ID && action instanceof MenuItemAction) { const delegate: IModePickerDelegate = { currentMode: this._currentModeObservable }; return this.modeWidget = this.instantiationService.createInstance(ModePickerActionItem, action, delegate); - } + } else if (action.id === ChatSessionOpenModelPickerAction.ID && action instanceof MenuItemAction) { + const resolveChatSessionContext = () => { + const sessionId = this._widget?.viewModel?.model.sessionId; + if (!sessionId) { + return; + } + const chatSessionContext = this.chatService.getChatSessionFromInternalId(sessionId); + if (!chatSessionContext) { + return; + } + return chatSessionContext; + }; + const itemDelegate: IModelPickerDelegate = { + getCurrentModel: () => { + return this.refreshChatSessionPicker(); + }, + onDidChangeModel: this._onDidChangeChatSessionLanguageModel.event, + setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { + const ctx = resolveChatSessionContext(); + if (!ctx) { + return; + } + this._onDidChangeChatSessionLanguageModel.fire(model); + this.chatSessionsService.notifySessionOptionsChange( + ctx.chatSessionType, + ctx.chatSessionId, + [{ optionId: 'model', value: model.identifier }] + ).catch(err => this.logService.error('Failed to notify extension of model change:', err)); + }, + getModels: () => { + const ctx = resolveChatSessionContext(); + if (!ctx) { + return []; + } + return this.chatSessionsService.getModelsForSessionType(ctx.chatSessionType) || []; + } + }; + return this.chatSessionModelWidget = this.instantiationService.createInstance(ChatSessionModelPickerActionItem, action, this.refreshChatSessionPicker(), itemDelegate); + } return undefined; } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 0621ecd5cf6..e15549a35ae 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -22,9 +22,10 @@ import { ExtensionsRegistry } from '../../../services/extensions/common/extensio import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; +import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionModelInfo, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatModeKind, VIEWLET_ID } from '../common/constants.js'; +import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier } from '../common/languageModels.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; @@ -109,12 +110,25 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint; + public getOption(optionId: string): string | undefined { + return this._optionsCache.get(optionId); + } + public setOption(optionId: string, value: string): void { + this._optionsCache.set(optionId, value); + } + constructor( readonly session: ChatSession, readonly chatSessionType: string, readonly id: string, + readonly options: { model?: ILanguageModelChatMetadata } | undefined, private readonly onWillDispose: (session: ChatSession, chatSessionType: string, id: string) => void ) { + this._optionsCache = new Map(); + if (options?.model) { + this._optionsCache.set('model', options.model.id); + } this._disposableStore = new DisposableStore(); this._disposableStore.add(this.session.onWillDispose(() => { this.onWillDispose(this.session, this.chatSessionType, this.id); @@ -144,6 +158,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private readonly _onDidChangeInProgress = this._register(new Emitter()); public get onDidChangeInProgress() { return this._onDidChangeInProgress.event; } private readonly inProgressMap: Map = new Map(); + private readonly _sessionTypeModels: Map = new Map(); constructor( @ILogService private readonly _logService: ILogService, @@ -556,7 +571,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } const session = await provider.provideChatSessionContent(id, resource, token); - const sessionData = new ContributedChatSessionData(session, chatSessionType, id, this._onWillDisposeSession.bind(this)); + const sessionData = new ContributedChatSessionData(session, chatSessionType, id, session.options, this._onWillDisposeSession.bind(this)); this._sessions.set(sessionKey, sessionData); @@ -568,6 +583,18 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessions.delete(sessionKey); } + public getSessionOption(chatSessionType: string, id: string, optionId: string): string | undefined { + const sessionKey = `${chatSessionType}_${id}`; + const session = this._sessions.get(sessionKey); + return session?.getOption(optionId); + } + + public setSessionOption(chatSessionType: string, id: string, optionId: string, value: string): boolean { + const sessionKey = `${chatSessionType}_${id}`; + const session = this._sessions.get(sessionKey); + return !!session?.setOption(optionId, value); + } + // Implementation of editable session methods public async setEditableSession(sessionId: string, data: IEditableData | null): Promise { if (!data) { @@ -590,6 +617,73 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public notifySessionItemsChanged(chatSessionType: string): void { this._onDidChangeSessionItems.fire(chatSessionType); } + + /** + * Store models for a session type + */ + public setModelsForSessionType(chatSessionType: string, handle: number, models: IChatSessionModelInfo[]): void { + if (models) { + const mappedModels: ILanguageModelChatMetadataAndIdentifier[] = []; + const contribution = this._contributions.get(chatSessionType); + const extensionIdentifier = contribution?.extensionDescription.identifier; + if (!extensionIdentifier) { + this._logService.error('Extension identifier not found for chat session type:', chatSessionType); + return; + } + for (const m of models) { + mappedModels.push({ + identifier: m.id, + metadata: { + id: m.id, + name: m.name, + family: m.family, + vendor: 'contributed', + version: m.version, + maxInputTokens: m.maxInputTokens, + maxOutputTokens: m.maxOutputTokens, + modelPickerCategory: undefined, + extension: extensionIdentifier, + isDefault: false, + isUserSelectable: true + } + }); + } + this._sessionTypeModels.set(chatSessionType, mappedModels); + } else { + this._sessionTypeModels.delete(chatSessionType); + } + } + + /** + * Get available models for a session type + */ + public getModelsForSessionType(chatSessionType: string): ILanguageModelChatMetadataAndIdentifier[] | undefined { + return this._sessionTypeModels.get(chatSessionType); + } + + private _optionsChangeCallback?: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + + /** + * Set the callback for notifying extensions about option changes + */ + public setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + this._optionsChangeCallback = callback; + } + + /** + * Notify extension about option changes for a session + */ + public async notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + if (!updates.length) { + return; + } + if (this._optionsChangeCallback) { + await this._optionsChangeCallback(chatSessionType, sessionId, updates); + } + for (const u of updates) { + this.setSessionOption(chatSessionType, sessionId, u.optionId, u.value); + } + } } registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts new file mode 100644 index 00000000000..276a9ba6fe2 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from '../../../../../base/common/actions.js'; +import { ILanguageModelChatMetadataAndIdentifier } from '../../common/languageModels.js'; +import { localize } from '../../../../../nls.js'; +import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js'; +import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider, IActionWidgetDropdownOptions } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { IModelPickerDelegate, ModelPickerActionItem } from '../modelPicker/modelPickerActionItem.js'; +import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; + + +function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate): IActionWidgetDropdownActionProvider { + return { + getActions: () => { + return delegate.getModels().map(model => { + return { + id: model.metadata.id, + enabled: true, + icon: model.metadata.statusIcon, + checked: model.identifier === delegate.getCurrentModel()?.identifier, + class: undefined, + description: model.metadata.detail, + tooltip: model.metadata.tooltip ?? model.metadata.name, + label: model.metadata.name, + run: () => { + delegate.setModel(model); + + } + } satisfies IActionWidgetDropdownAction; + }); + } + }; +} + +/** + * Action view item for selecting the model for a contributed chat session + * These models are also contributed by the chat session provider and cannot be used outside that context + * This may also be generalized into eg 'Chat Session Picker 1' to be used however the provider wants + */ +export class ChatSessionModelPickerActionItem extends ModelPickerActionItem { + constructor( + action: IAction, + currentModel: ILanguageModelChatMetadataAndIdentifier | undefined, + delegate: IModelPickerDelegate, + @IActionWidgetService actionWidgetService: IActionWidgetService, + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService, + @IChatEntitlementService chatEntitlementService: IChatEntitlementService, + @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + const actionWithLabel: IAction = { + ...action, + label: currentModel?.metadata.name ?? localize('chat.modelPicker.label', "Pick Model"), + tooltip: localize('chat.modelPicker.label', "Pick Model"), + run: () => { } + }; + + const modelPickerActionWidgetOptions: Omit = { + actionProvider: modelDelegateToWidgetActionsProvider(delegate), + actionBarActionProvider: undefined, + }; + + super(actionWithLabel, currentModel, modelPickerActionWidgetOptions, delegate, actionWidgetService, contextKeyService, commandService, chatEntitlementService, keybindingService, telemetryService); + this._register(delegate.onDidChangeModel(model => { + this.currentModel = model; + if (this.element) { + this.renderLabel(this.element); + } + })); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts index a094206450b..2d9bc62efb0 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts @@ -120,14 +120,15 @@ function getModelPickerActionBarActionProvider(commandService: ICommandService, export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { constructor( action: IAction, - private currentModel: ILanguageModelChatMetadataAndIdentifier | undefined, + protected currentModel: ILanguageModelChatMetadataAndIdentifier | undefined, + widgetOptions: Omit | undefined, delegate: IModelPickerDelegate, @IActionWidgetService actionWidgetService: IActionWidgetService, @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, @IKeybindingService keybindingService: IKeybindingService, - @ITelemetryService telemetryService: ITelemetryService + @ITelemetryService telemetryService: ITelemetryService, ) { // Modify the original action with a different label and make it show the current model const actionWithLabel: IAction = { @@ -142,7 +143,7 @@ export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { actionBarActionProvider: getModelPickerActionBarActionProvider(commandService, chatEntitlementService) }; - super(actionWithLabel, modelPickerActionWidgetOptions, actionWidgetService, keybindingService, contextKeyService); + super(actionWithLabel, widgetOptions ?? modelPickerActionWidgetOptions, actionWidgetService, keybindingService, contextKeyService); // Listen for model changes from the delegate this._register(delegate.onDidChangeModel(model => { diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 487740709d9..b6a505478ae 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -53,6 +53,7 @@ export namespace ChatContextKeys { export const chatEditingCanUndo = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); export const chatEditingCanRedo = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); export const languageModelsAreUserSelectable = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); + export const chatSessionHasModels = new RawContextKey('chatSessionHasModels', false, { type: 'boolean', description: localize('chatSessionHasModels', "True when the chat is in a contributed chat session that has available 'models' to display.") }); export const extensionInvalid = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); export const inputCursorAtTop = new RawContextKey('chatCursorAtTop', false); export const inputHasAgent = new RawContextKey('chatInputHasAgent', false); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 5c7970f9685..a0c4c1e12bc 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -28,7 +28,7 @@ import { migrateLegacyTerminalToolSpecificData } from './chat.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from './chatAgents.js'; import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatSessionContext, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; @@ -1078,6 +1078,8 @@ export interface IChatModel extends IDisposable { setCustomTitle(title: string): void; toExport(): IExportableChatData; toJSON(): ISerializableChatData; + readonly contributedChatSession: IChatSessionContext | undefined; + setContributedChatSession(session: IChatSessionContext | undefined): void; } export interface ISerializableChatsData { @@ -1344,6 +1346,14 @@ export class ChatModel extends Disposable implements IChatModel { private _requests: ChatRequestModel[]; + private _contributedChatSession: IChatSessionContext | undefined; + public get contributedChatSession(): IChatSessionContext | undefined { + return this._contributedChatSession; + } + public setContributedChatSession(session: IChatSessionContext | undefined) { + this._contributedChatSession = session; + } + // TODO to be clear, this is not the same as the id from the session object, which belongs to the provider. // It's easier to be able to identify this model before its async initialization is complete private _sessionId: string; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 3e788aff39e..00d634bfa84 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -73,7 +73,6 @@ export class ChatService extends Disposable implements IChatService { private readonly _sessionModels = new ObservableMap(); private readonly _contentProviderSessionModels = new Map>(); - private readonly _modelToExternalSession = new Map(); private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; @@ -451,9 +450,9 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data, data.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); } - async loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { + async loadSessionForResource(chatSessionResource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { // TODO: Move this into a new ChatModelService - const parsed = ChatSessionUri.parse(resource); + const parsed = ChatSessionUri.parse(chatSessionResource); if (!parsed) { throw new Error('Invalid chat session URI'); } @@ -468,12 +467,16 @@ export class ChatService extends Disposable implements IChatService { } const chatSessionType = parsed.chatSessionType; - const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, resource, CancellationToken.None); + const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, chatSessionResource, CancellationToken.None); // Contributed sessions do not use UI tools const model = this._startSession(undefined, location, true, CancellationToken.None, { canUseTools: false, inputType: chatSessionType }); - // Record mapping from internal model session id to external contributed chat session identity - this._modelToExternalSession.set(model.sessionId, { chatSessionType, chatSessionId: parsed.sessionId, chatSessionResource: resource }); + model.setContributedChatSession({ + chatSessionType, + chatSessionId: parsed.sessionId, + chatSessionResource, + isUntitled: parsed.sessionId.startsWith('untitled-') //TODO(jospicer) + }); if (!this._contentProviderSessionModels.has(chatSessionType)) { this._contentProviderSessionModels.set(chatSessionType, new Map()); } @@ -482,7 +485,6 @@ export class ChatService extends Disposable implements IChatService { disposables.add(model.onDidDispose(() => { this._contentProviderSessionModels?.get(chatSessionType)?.delete(parsed.sessionId); - this._modelToExternalSession.delete(model.sessionId); content.dispose(); })); @@ -579,14 +581,12 @@ export class ChatService extends Disposable implements IChatService { } getChatSessionFromInternalId(modelSessionId: string): IChatSessionContext | undefined { - const data = this._modelToExternalSession.get(modelSessionId); - if (!data) { + const model = this._sessionModels.get(modelSessionId); + if (!model) { return; } - return { - ...data, - isUntitled: data.chatSessionId.startsWith('untitled-'), // TODO(jospicer) - }; + const { contributedChatSession } = model; + return contributedChatSession; } async resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions): Promise { diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index d425fd017f2..0b22578f247 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -15,6 +15,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IEditableData } from '../../../common/views.js'; import { IChatAgentRequest } from './chatAgents.js'; import { IChatProgress } from './chatService.js'; +import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier } from './languageModels.js'; export const enum ChatSessionStatus { Failed = 0, @@ -67,6 +68,8 @@ export interface ChatSession extends IDisposable { readonly sessionResource: URI; readonly onWillDispose: Event; history: Array; + options: { model?: ILanguageModelChatMetadata } | undefined; + readonly progressObs?: IObservable; readonly isCompleteObs?: IObservable; readonly interruptActiveResponseCallback?: () => Promise; @@ -93,6 +96,19 @@ export interface IChatSessionContentProvider { provideChatSessionContent(sessionId: string, sessionResource: URI, token: CancellationToken): Promise; } +// TODO: Copy of LanguageModelChatInformation with various omissions +export interface IChatSessionModelInfo { + id: string; + name: string; + family: string; + tooltip?: string; + detail?: string; + version: string; + maxInputTokens: number; + maxOutputTokens: number; + capabilities?: { imageInput?: boolean; toolCalling?: boolean | number }; +} + export interface IChatSessionsService { readonly _serviceBrand: undefined; @@ -117,6 +133,18 @@ export interface IChatSessionsService { canResolveContentProvider(chatSessionType: string): Promise; provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise; + // Get available models for a session type + getModelsForSessionType(chatSessionType: string): ILanguageModelChatMetadataAndIdentifier[] | undefined; + + // Set available models for a session type (called by MainThreadChatSessions) + setModelsForSessionType(chatSessionType: string, handle: number, models?: IChatSessionModelInfo[]): void; + + // Set callback for notifying extensions about option changes + setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void; + + // Notify extension about option changes + notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; + // Editable session support setEditableSession(sessionId: string, data: IEditableData | null): Promise; getEditableData(sessionId: string): IEditableData | undefined; @@ -124,6 +152,9 @@ export interface IChatSessionsService { // Notify providers about session items changes notifySessionItemsChanged(chatSessionType: string): void; + + getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined; + setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean; } export const IChatSessionsService = createDecorator('chatSessionsService'); diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.ts index 41b281ae8b0..c7a54d6c2e6 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.ts @@ -7,7 +7,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; -import { IChatMessage, ILanguageModelChatMetadata, ILanguageModelChatProvider, ILanguageModelChatResponse, ILanguageModelChatSelector, ILanguageModelsService, IUserFriendlyLanguageModel } from '../../common/languageModels.js'; +import { IChatMessage, ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatProvider, ILanguageModelChatResponse, ILanguageModelChatSelector, ILanguageModelsService, IUserFriendlyLanguageModel } from '../../common/languageModels.js'; export class NullLanguageModelsService implements ILanguageModelsService { _serviceBrand: undefined; @@ -34,6 +34,18 @@ export class NullLanguageModelsService implements ILanguageModelsService { return undefined; } + getLanguageModels(): ILanguageModelChatMetadataAndIdentifier[] { + return []; + } + + setContributedSessionModels(): void { + return; + } + + clearContributedSessionModels(): void { + return; + } + async selectLanguageModels(selector: ILanguageModelChatSelector): Promise { return []; } diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 6075c09fc14..534c45a1920 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -148,6 +148,11 @@ declare module 'vscode' { // TODO: link request + response to encourage correct usage? readonly history: ReadonlyArray; + /** + * Options configured for this session. + */ + readonly options?: { model?: LanguageModelChatInformation }; + /** * Callback invoked by the editor for a currently running response. This allows the session to push items for the * current response and stream these in as them come in. The current response will be considered complete once the @@ -175,6 +180,32 @@ declare module 'vscode' { * @param token A cancellation token that can be used to cancel the operation. */ provideChatSessionContent(sessionId: string, token: CancellationToken): Thenable | ChatSession; + + /** + * + * @param sessionId Identifier of the chat session being updated. + * @param updates Collection of option identifiers and their new values. Only the options that changed are included. + * @param token A cancellation token that can be used to cancel the notification if the session is disposed. + */ + provideHandleOptionsChange?(sessionId: string, updates: ReadonlyArray, token: CancellationToken): void; + + /** + * Called as soon as you register (call me once) + * @param token + */ + provideChatSessionProviderOptions?(token: CancellationToken): Thenable | ChatSessionProviderOptions; + } + + export interface ChatSessionOptionUpdate { + /** + * Identifier of the option that changed (for example `model`). + */ + readonly optionId: string; + + /** + * The new value assigned to the option. When `undefined`, the option is cleared. + */ + readonly value: string | undefined; } export namespace chat { @@ -221,6 +252,13 @@ declare module 'vscode' { supportsInterruptions?: boolean; } + export interface ChatSessionProviderOptions { + /** + * Set of available models. + */ + models?: LanguageModelChatInformation[]; + } + /** * @deprecated */ From ec8586ef251f560f4deaaa205d67bc32213b5055 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 16 Oct 2025 21:11:11 -0700 Subject: [PATCH 1236/4355] Add isModelProxyAvailable (#271872) * Add isModelProxyAvailable * tests --- extensions/vscode-api-tests/package.json | 3 ++- .../workbench/api/common/extHost.api.impl.ts | 16 +++++++++--- .../api/common/extHostLanguageModels.ts | 26 +++++++++++++------ .../vscode.proposed.languageModelProxy.d.ts | 17 +++++++++--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 74c7eacd7e1..aaad54485df 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -51,7 +51,8 @@ "tunnels", "workspaceTrust", "inlineCompletionsAdditions", - "devDeviceId" + "devDeviceId", + "languageModelProxy" ], "private": true, "activationEvents": [], diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 463fe9c7201..a542aed9209 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -27,14 +27,17 @@ import { ExtensionDescriptionRegistry } from '../../services/extensions/common/e import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2, AISearchKeyword } from '../../services/search/common/searchExtTypes.js'; +import { AISearchKeyword, ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from './extHost.protocol.js'; import { ExtHostRelatedInformation } from './extHostAiRelatedInformation.js'; +import { ExtHostAiSettingsSearch } from './extHostAiSettingsSearch.js'; import { ExtHostApiCommands } from './extHostApiCommands.js'; import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js'; import { IExtHostAuthentication } from './extHostAuthentication.js'; import { ExtHostBulkEdits } from './extHostBulkEdits.js'; import { ExtHostChatAgents2 } from './extHostChatAgents2.js'; +import { ExtHostChatOutputRenderer } from './extHostChatOutputRenderer.js'; +import { ExtHostChatSessions } from './extHostChatSessions.js'; import { ExtHostChatStatus } from './extHostChatStatus.js'; import { ExtHostClipboard } from './extHostClipboard.js'; import { ExtHostEditorInsets } from './extHostCodeInsets.js'; @@ -111,9 +114,6 @@ import { ExtHostWebviewPanels } from './extHostWebviewPanels.js'; import { ExtHostWebviewViews } from './extHostWebviewView.js'; import { IExtHostWindow } from './extHostWindow.js'; import { IExtHostWorkspace } from './extHostWorkspace.js'; -import { ExtHostAiSettingsSearch } from './extHostAiSettingsSearch.js'; -import { ExtHostChatSessions } from './extHostChatSessions.js'; -import { ExtHostChatOutputRenderer } from './extHostChatOutputRenderer.js'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -1552,6 +1552,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerLanguageModelChatProvider: (vendor, provider) => { return extHostLanguageModels.registerLanguageModelChatProvider(extension, vendor, provider); }, + get isModelProxyAvailable() { + checkProposedApiEnabled(extension, 'languageModelProxy'); + return extHostLanguageModels.isModelProxyAvailable; + }, + onDidChangeModelProxyAvailability: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'languageModelProxy'); + return extHostLanguageModels.onDidChangeModelProxyAvailability(listener, thisArgs, disposables); + }, getModelProxy: () => { checkProposedApiEnabled(extension, 'languageModelProxy'); return extHostLanguageModels.getModelProxy(extension); diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 5011da45902..05a8e6c10e7 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -114,6 +114,8 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; + private readonly _onDidChangeModelProxyAvailability = new Emitter(); + readonly onDidChangeModelProxyAvailability = this._onDidChangeModelProxyAvailability.event; private readonly _languageModelProviders = new Map(); // TODO @lramos15 - Remove the need for both info and metadata as it's a lot of redundancy. Should just need one @@ -134,6 +136,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { dispose(): void { this._onDidChangeModelAccess.dispose(); this._onDidChangeProviders.dispose(); + this._onDidChangeModelProxyAvailability.dispose(); } registerLanguageModelChatProvider(extension: IExtensionDescription, vendor: string, provider: vscode.LanguageModelChatProvider): IDisposable { @@ -606,25 +609,30 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return this._proxy.$fileIsIgnored(uri, token); } - async getModelProxy(extension: IExtensionDescription): Promise { + get isModelProxyAvailable(): boolean { + return !!this._languageModelProxyProvider; + } + + async getModelProxy(extension: IExtensionDescription): Promise { checkProposedApiEnabled(extension, 'languageModelProxy'); if (!this._languageModelProxyProvider) { - this._logService.warn('[LanguageModelProxy] No LanguageModelProxyProvider registered'); - return undefined; + this._logService.trace('[LanguageModelProxy] No LanguageModelProxyProvider registered'); + throw new Error('No language model proxy provider is registered.'); } const requestingExtensionId = ExtensionIdentifier.toKey(extension.identifier); try { const result = await Promise.resolve(this._languageModelProxyProvider.provideModelProxy(requestingExtensionId, CancellationToken.None)); - if (result) { - return result; + if (!result) { + this._logService.warn(`[LanguageModelProxy] Provider returned no proxy for ${requestingExtensionId}`); + throw new Error('Language model proxy is not available.'); } + return result; } catch (err) { - this._logService.error(`[LanguageModelProxy] Provider ${ExtensionIdentifier.toKey(extension.identifier)} failed`, err); + this._logService.error(`[LanguageModelProxy] Provider failed to return proxy for ${requestingExtensionId}`, err); + throw err; } - - return undefined; } async $isFileIgnored(handle: number, uri: UriComponents, token: CancellationToken): Promise { @@ -652,9 +660,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { checkProposedApiEnabled(extension, 'chatParticipantPrivate'); this._languageModelProxyProvider = provider; + this._onDidChangeModelProxyAvailability.fire(); return toDisposable(() => { if (this._languageModelProxyProvider === provider) { this._languageModelProxyProvider = undefined; + this._onDidChangeModelProxyAvailability.fire(); } }); } diff --git a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts index 849e08d4529..6d32e6de3f8 100644 --- a/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModelProxy.d.ts @@ -11,10 +11,21 @@ declare module 'vscode' { export namespace lm { /** - * Returns undefined if + * Returns false if + * - Copilot Chat extension is not installed + * - Copilot Chat has not finished activating or finished auth * - The user is not logged in, or isn't the right SKU, with expected model access - * - The server fails to start for some reason */ - export function getModelProxy(): Thenable; + export const isModelProxyAvailable: boolean; + + /** + * Fired when isModelProxyAvailable changes. + */ + export const onDidChangeModelProxyAvailability: Event; + + /** + * Throws if the server fails to start for some reason, or something else goes wrong. + */ + export function getModelProxy(): Thenable; } } From e57cd5ad4d770ab5f23b73cf7dd9e629409b8e63 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 16 Oct 2025 21:53:55 -0700 Subject: [PATCH 1237/4355] align send button --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 29dcdf95ee3..8abe50692eb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -176,7 +176,7 @@ export class ChatSubmitAction extends SubmitAction { precondition, toggled: { condition: ChatContextKeys.lockedToCodingAgent, - icon: Codicon.sendToRemoteAgent, + icon: Codicon.send, tooltip: localize('sendToRemoteAgent', "Send to coding agent"), }, keybinding: { From 6bc4c3fbc81e0b502e2983235675ae11ac8f564c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Oct 2025 07:39:16 +0200 Subject: [PATCH 1238/4355] voice - disable auto-submit after delay (#271880) --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 73726461da2..7f1ebe7ddb1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -860,7 +860,7 @@ export function registerAccessibilityConfiguration() { export { AccessibilityVoiceSettingId }; -export const SpeechTimeoutDefault = 2000; +export const SpeechTimeoutDefault = 0; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { From 62be39dd53ad983e5a8baa051a5df8ab5ddaf087 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Oct 2025 07:39:51 +0200 Subject: [PATCH 1239/4355] remote - fix status entry kind (#271881) --- .../contrib/remote/browser/remoteIndicator.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 732e59e4ae2..66108502b91 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -79,6 +79,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private static readonly SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command private static readonly INSTALL_REMOTE_EXTENSIONS_ID = 'workbench.action.remote.extensions'; + private static readonly DEFAULT_REMOTE_STATUS_LABEL = '$(remote)'; + private static readonly REMOTE_STATUS_LABEL_MAX_LENGTH = 40; private static readonly REMOTE_CONNECTION_LATENCY_SCHEDULER_DELAY = 60 * 1000; @@ -553,7 +555,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } } - this.renderRemoteStatusIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a Remote Window")); + this.renderRemoteStatusIndicator(RemoteStatusIndicator.DEFAULT_REMOTE_STATUS_LABEL, nls.localize('noHost.tooltip', "Open a Remote Window")); return; } @@ -562,7 +564,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr const properties: IStatusbarEntry = { name: nls.localize('remoteHost', "Remote Host"), - kind: this.networkState === 'offline' ? 'offline' : this.remoteAuthority ? 'remote' : undefined, + kind: this.networkState === 'offline' ? 'offline' : text !== RemoteStatusIndicator.DEFAULT_REMOTE_STATUS_LABEL ? 'remote' : undefined, // only emphasize when applicable ariaLabel, text, showProgress, @@ -590,8 +592,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr // to replace with an alert icon for when a normal remote indicator // is shown. - if (!showProgress && initialText.startsWith('$(remote)')) { - return initialText.replace('$(remote)', '$(alert)'); + if (!showProgress && initialText.startsWith(RemoteStatusIndicator.DEFAULT_REMOTE_STATUS_LABEL)) { + return initialText.replace(RemoteStatusIndicator.DEFAULT_REMOTE_STATUS_LABEL, '$(alert)'); } return initialText; From 3177a3ac1d93643e30af24041a0ff5b287d7c909 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Oct 2025 07:46:22 +0200 Subject: [PATCH 1240/4355] chat - rename actions that move a chat (#271884) --- .../contrib/chat/browser/actions/chatMoveActions.ts | 12 ++++++------ .../chat/browser/actions/chatSessionActions.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 4d25bee34f1..2ee4914c152 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -33,7 +33,7 @@ export function registerMoveActions() { constructor() { super({ id: 'workbench.action.chat.openInEditor', - title: localize2('chat.openInEditor.label', "Open Chat in Editor"), + title: localize2('chat.openInEditor.label', "Move Chat into Editor Area"), category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, f1: true, @@ -56,7 +56,7 @@ export function registerMoveActions() { constructor() { super({ id: 'workbench.action.chat.openInNewWindow', - title: localize2('chat.openInNewWindow.label', "Open Chat in New Window"), + title: localize2('chat.openInNewWindow.label', "Move Chat into New Window"), category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, f1: true, @@ -79,7 +79,7 @@ export function registerMoveActions() { constructor() { super({ id: 'workbench.action.chat.openInSidebar', - title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), + title: localize2('interactiveSession.openInSidebar.label', "Move Chat into Side Bar"), category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, f1: true @@ -104,9 +104,9 @@ export function registerMoveActions() { } [MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].forEach(id => { - appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInSecondarySidebar.label', "Open Chat in Secondary Side Bar"), Codicon.layoutSidebarRightDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.AuxiliaryBar)); - appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInPrimarySidebar.label', "Open Chat in Primary Side Bar"), Codicon.layoutSidebarLeftDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.Sidebar)); - appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInPanel.label', "Open Chat in Panel"), Codicon.layoutPanelDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.Panel)); + appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInSecondarySidebar.label', "Move Chat into Secondary Side Bar"), Codicon.layoutSidebarRightDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.AuxiliaryBar)); + appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInPrimarySidebar.label', "Move Chat into Primary Side Bar"), Codicon.layoutSidebarLeftDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.Sidebar)); + appendOpenChatInViewMenuItem(id, localize('interactiveSession.openInPanel.label', "Move Chat into Panel"), Codicon.layoutPanelDock, ChatContextKeys.panelLocation.isEqualTo(ViewContainerLocation.Panel)); }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 4b92867a8c3..3e4b881944b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -167,7 +167,7 @@ export class OpenChatSessionInNewWindowAction extends Action2 { constructor() { super({ id: OpenChatSessionInNewWindowAction.id, - title: localize('chat.openSessionInNewWindow.label', "Open Chat in New Window"), + title: localize('chat.openSessionInNewWindow.label', "Move Chat into New Window"), category: CHAT_CATEGORY, f1: false, }); @@ -224,7 +224,7 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { constructor() { super({ id: OpenChatSessionInNewEditorGroupAction.id, - title: localize('chat.openSessionInNewEditorGroup.label', "Open Chat to the Side"), + title: localize('chat.openSessionInNewEditorGroup.label', "Move Chat to the Side"), category: CHAT_CATEGORY, f1: false, }); @@ -281,7 +281,7 @@ export class OpenChatSessionInSidebarAction extends Action2 { constructor() { super({ id: OpenChatSessionInSidebarAction.id, - title: localize('chat.openSessionInSidebar.label', "Open Chat in Sidebar"), + title: localize('chat.openSessionInSidebar.label', "Move Chat into Side Bar"), category: CHAT_CATEGORY, f1: false, }); From 39e5d9930c8324846c348e577c42ad02f9218a72 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 07:50:02 +0200 Subject: [PATCH 1241/4355] SCM - only show pin/unpin actions when there are multiple repositories (#271885) --- .../contrib/scm/browser/scmRepositoriesViewPane.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index a8d0406c5ab..13f5d062dfc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -13,7 +13,7 @@ import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/br import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; @@ -391,7 +391,10 @@ registerAction2(class extends Action2 { icon: Codicon.pin, menu: { id: MenuId.SCMSourceControlTitle, - when: RepositoryContextKeys.RepositoryPinned.isEqualTo(false), + when: ContextKeyExpr.and( + ContextKeyExpr.has('scm.providerCount'), + ContextKeyExpr.greater('scm.providerCount', 1), + RepositoryContextKeys.RepositoryPinned.isEqualTo(false)), group: 'navigation', order: 1 }, @@ -418,7 +421,10 @@ registerAction2(class extends Action2 { icon: Codicon.pinned, menu: { id: MenuId.SCMSourceControlTitle, - when: RepositoryContextKeys.RepositoryPinned.isEqualTo(true), + when: ContextKeyExpr.and( + ContextKeyExpr.has('scm.providerCount'), + ContextKeyExpr.greater('scm.providerCount', 1), + RepositoryContextKeys.RepositoryPinned.isEqualTo(true)), group: 'navigation', order: 2 }, From a40557656ff70ea10392060c7497b37ca79de7e8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 17 Oct 2025 11:13:39 +0200 Subject: [PATCH 1242/4355] Edits toolbar and button hover have different corner radius (fix #271194) (#271490) --- .../chat/browser/media/chatEditingEditorOverlay.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chatEditingEditorOverlay.css b/src/vs/workbench/contrib/chat/browser/media/chatEditingEditorOverlay.css index 5995a3e59f3..0177040611d 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatEditingEditorOverlay.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatEditingEditorOverlay.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .chat-editor-overlay-widget { - padding: 0px; + padding: 2px; color: var(--vscode-button-foreground); background-color: var(--vscode-button-background); border-radius: 2px; @@ -56,6 +56,7 @@ .chat-editor-overlay-widget .action-item > .action-label { padding: 5px; font-size: 12px; + border-radius: 2px; /* same as overlay widget */ } @@ -84,6 +85,10 @@ } } +.chat-diff-change-content-widget .action-item > .action-label { + border-radius: 2px; /* same as overlay widget */ +} + .chat-editor-overlay-widget .action-item.label-item { font-variant-numeric: tabular-nums; From 4bc4fefe0068b65c29799aa2b7d32015414d6ba9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 17 Oct 2025 12:10:06 +0200 Subject: [PATCH 1243/4355] Implement #255220 (#271911) --- .../contrib/mcp/browser/mcpServerActions.ts | 210 +++++++++++++++--- .../contrib/mcp/browser/mcpServerEditor.ts | 26 ++- .../mcp/browser/mcpServerEditorInput.ts | 2 +- .../contrib/mcp/browser/mcpServersView.ts | 55 +++-- .../mcp/browser/mcpWorkbenchService.ts | 125 ++++++++--- .../discovery/installedMcpServersDiscovery.ts | 51 ++++- .../workbench/contrib/mcp/common/mcpTypes.ts | 6 +- 7 files changed, 370 insertions(+), 105 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts index e268560603c..3c759b6dc9c 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts @@ -6,9 +6,9 @@ import { getDomNodePagePosition } from '../../../../base/browser/dom.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { alert } from '../../../../base/browser/ui/aria/aria.js'; -import { Action, IAction, Separator } from '../../../../base/common/actions.js'; -import { Emitter } from '../../../../base/common/event.js'; -import { createCommandUri, IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { Action, IAction, IActionChangeEvent, Separator } from '../../../../base/common/actions.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { disposeIfDisposable } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; @@ -17,7 +17,6 @@ import { Location } from '../../../../editor/common/languages.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { mcpAccessConfig, McpAccessValue } from '../../../../platform/mcp/common/mcpManagement.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IAuthenticationService } from '../../../services/authentication/common/authentication.js'; import { IAccountQuery, IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js'; @@ -25,23 +24,53 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { errorIcon, infoIcon, manageExtensionIcon, trustIcon, warningIcon } from '../../extensions/browser/extensionsIcons.js'; import { McpCommandIds } from '../common/mcpCommandIds.js'; import { IMcpRegistry } from '../common/mcpRegistryTypes.js'; -import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerEnablementState, McpServerInstallState } from '../common/mcpTypes.js'; +import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerInstallState } from '../common/mcpTypes.js'; import { startServerByFilter } from '../common/mcpTypesUtils.js'; -import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { IQuickInputService, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../base/common/network.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; +import { LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchManagementService.js'; +import { ExtensionAction } from '../../extensions/browser/extensionsActions.js'; +import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js'; + +export interface IMcpServerActionChangeEvent extends IActionChangeEvent { + readonly hidden?: boolean; + readonly menuActions?: IAction[]; +} export abstract class McpServerAction extends Action implements IMcpServerContainer { + protected override _onDidChange = this._register(new Emitter()); + override get onDidChange() { return this._onDidChange.event; } + static readonly EXTENSION_ACTION_CLASS = 'extension-action'; static readonly TEXT_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} text`; static readonly LABEL_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} label`; static readonly PROMINENT_LABEL_ACTION_CLASS = `${McpServerAction.LABEL_ACTION_CLASS} prominent`; static readonly ICON_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} icon`; + private _hidden: boolean = false; + get hidden(): boolean { return this._hidden; } + set hidden(hidden: boolean) { + if (this._hidden !== hidden) { + this._hidden = hidden; + this._onDidChange.fire({ hidden }); + } + } + + protected override _setEnabled(value: boolean): void { + super._setEnabled(value); + if (this.hideOnDisabled) { + this.hidden = !value; + } + } + + protected hideOnDisabled: boolean = true; + private _mcpServer: IWorkbenchMcpServer | null = null; get mcpServer(): IWorkbenchMcpServer | null { return this._mcpServer; } set mcpServer(mcpServer: IWorkbenchMcpServer | null) { this._mcpServer = mcpServer; this.update(); } @@ -49,6 +78,111 @@ export abstract class McpServerAction extends Action implements IMcpServerContai abstract update(): void; } +export class ButtonWithDropDownExtensionAction extends McpServerAction { + + private primaryAction: IAction | undefined; + + readonly menuActionClassNames: string[] = []; + private _menuActions: IAction[] = []; + get menuActions(): IAction[] { return [...this._menuActions]; } + + override get mcpServer(): IWorkbenchMcpServer | null { + return super.mcpServer; + } + + override set mcpServer(mcpServer: IWorkbenchMcpServer | null) { + this.actions.forEach(a => a.mcpServer = mcpServer); + super.mcpServer = mcpServer; + } + + protected readonly actions: McpServerAction[]; + + constructor( + id: string, + clazz: string, + private readonly actionsGroups: McpServerAction[][], + ) { + clazz = `${clazz} action-dropdown`; + super(id, undefined, clazz); + this.menuActionClassNames = clazz.split(' '); + this.hideOnDisabled = false; + this.actions = actionsGroups.flat(); + this.update(); + this._register(Event.any(...this.actions.map(a => a.onDidChange))(() => this.update(true))); + this.actions.forEach(a => this._register(a)); + } + + update(donotUpdateActions?: boolean): void { + if (!donotUpdateActions) { + this.actions.forEach(a => a.update()); + } + + const actionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => !a.hidden)); + + let actions: IAction[] = []; + for (const visibleActions of actionsGroups) { + if (visibleActions.length) { + actions = [...actions, ...visibleActions, new Separator()]; + } + } + actions = actions.length ? actions.slice(0, actions.length - 1) : actions; + + this.primaryAction = actions[0]; + this._menuActions = actions.length > 1 ? actions : []; + this._onDidChange.fire({ menuActions: this._menuActions }); + + if (this.primaryAction) { + this.enabled = this.primaryAction.enabled; + this.label = this.getLabel(this.primaryAction as ExtensionAction); + this.tooltip = this.primaryAction.tooltip; + } else { + this.enabled = false; + } + } + + override async run(): Promise { + if (this.enabled) { + await this.primaryAction?.run(); + } + } + + protected getLabel(action: ExtensionAction): string { + return action.label; + } +} + +export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdownActionViewItem { + + constructor( + action: ButtonWithDropDownExtensionAction, + options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, + contextMenuProvider: IContextMenuProvider + ) { + super(null, action, options, contextMenuProvider); + this._register(action.onDidChange(e => { + if (e.hidden !== undefined || e.menuActions !== undefined) { + this.updateClass(); + } + })); + } + + override render(container: HTMLElement): void { + super.render(container); + this.updateClass(); + } + + protected override updateClass(): void { + super.updateClass(); + if (this.element && this.dropdownMenuActionViewItem?.element) { + this.element.classList.toggle('hide', (this._action).hidden); + const isMenuEmpty = (this._action).menuActions.length === 0; + this.element.classList.toggle('empty', isMenuEmpty); + this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty); + } + } + +} + export abstract class DropDownAction extends McpServerAction { constructor( @@ -112,7 +246,7 @@ export class InstallAction extends McpServerAction { private static readonly HIDE = `${this.CLASS} hide`; constructor( - private readonly editor: boolean, + private readonly open: boolean, @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IMcpService private readonly mcpService: IMcpService, @@ -139,7 +273,7 @@ export class InstallAction extends McpServerAction { return; } - if (!this.editor) { + if (this.open) { this.mcpWorkbenchService.open(this.mcpServer); alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label)); } @@ -168,27 +302,27 @@ export class InstallInWorkspaceAction extends McpServerAction { private static readonly HIDE = `${this.CLASS} hide`; constructor( - private readonly editor: boolean, + private readonly open: boolean, @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IMcpService private readonly mcpService: IMcpService, ) { - super('extensions.installWorkspace', localize('installInWorkspace', "Install (Workspace)"), InstallAction.CLASS, false); + super('extensions.installWorkspace', localize('installInWorkspace', "Install in Workspace"), InstallAction.CLASS, false); this.update(); } update(): void { this.enabled = false; this.class = InstallInWorkspaceAction.HIDE; - if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { + if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { return; } - if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { return; } - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.mcpServer.installState !== McpServerInstallState.Uninstalled && this.mcpServer.local?.scope === LocalMcpServerScope.Workspace) { return; } this.class = InstallAction.CLASS; @@ -200,7 +334,7 @@ export class InstallInWorkspaceAction extends McpServerAction { return; } - if (!this.editor) { + if (this.open) { this.mcpWorkbenchService.open(this.mcpServer, { preserveFocus: true }); alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label)); } @@ -259,7 +393,7 @@ export class InstallInRemoteAction extends McpServerAction { private static readonly HIDE = `${this.CLASS} hide`; constructor( - private readonly editor: boolean, + private readonly open: boolean, @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -267,25 +401,30 @@ export class InstallInRemoteAction extends McpServerAction { @IMcpService private readonly mcpService: IMcpService, ) { super('extensions.installRemote', localize('installInRemote', "Install (Remote)"), InstallAction.CLASS, false); + const remoteLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority); + this.label = localize('installInRemoteLabel', "Install in {0}", remoteLabel); this.update(); } update(): void { this.enabled = false; this.class = InstallInRemoteAction.HIDE; - if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { + if (!this.environmentService.remoteAuthority) { return; } - if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + if (!this.mcpServer?.gallery && !this.mcpServer?.installable) { return; } - if (!this.environmentService.remoteAuthority) { - return; + if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) { + if (this.mcpServer.local?.scope === LocalMcpServerScope.RemoteUser) { + return; + } + if (this.mcpWorkbenchService.local.find(mcpServer => mcpServer.name === this.mcpServer?.name && mcpServer.local?.scope === LocalMcpServerScope.RemoteUser)) { + return; + } } this.class = InstallAction.CLASS; this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true; - const remoteLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority); - this.label = localize('installInRemoteLabel', "Install ({0})", remoteLabel); } override async run(): Promise { @@ -293,7 +432,7 @@ export class InstallInRemoteAction extends McpServerAction { return; } - if (!this.editor) { + if (this.open) { this.mcpWorkbenchService.open(this.mcpServer); alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label)); } @@ -400,17 +539,22 @@ export function getContextMenuActions(mcpServer: IWorkbenchMcpServer, isEditorAc instantiationService.createInstance(BrowseResourcesAction), ]); if (!isEditorAction) { - groups.push([ - instantiationService.createInstance(UninstallAction), - ]); + const installGroup: McpServerAction[] = [instantiationService.createInstance(UninstallAction)]; + if (workspaceService.getWorkbenchState() !== WorkbenchState.EMPTY) { + installGroup.push(instantiationService.createInstance(InstallInWorkspaceAction, false)); + } + if (environmentService.remoteAuthority && mcpServer.local?.scope !== LocalMcpServerScope.RemoteUser) { + installGroup.push(instantiationService.createInstance(InstallInRemoteAction, false)); + } + groups.push(installGroup); } } else { const installGroup = []; if (workspaceService.getWorkbenchState() !== WorkbenchState.EMPTY) { - installGroup.push(instantiationService.createInstance(InstallInWorkspaceAction, isEditorAction)); + installGroup.push(instantiationService.createInstance(InstallInWorkspaceAction, !isEditorAction)); } if (environmentService.remoteAuthority) { - installGroup.push(instantiationService.createInstance(InstallInRemoteAction, isEditorAction)); + installGroup.push(instantiationService.createInstance(InstallInRemoteAction, !isEditorAction)); } groups.push(installGroup); } @@ -989,7 +1133,6 @@ export class McpServerStatusAction extends McpServerAction { constructor( @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @ICommandService private readonly commandService: ICommandService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { super('extensions.status', '', `${McpServerStatusAction.CLASS} hide`, false); this.update(); @@ -1015,14 +1158,9 @@ export class McpServerStatusAction extends McpServerAction { } } - if (this.mcpServer.local && this.mcpServer.installState === McpServerInstallState.Installed && this.mcpServer.enablementState === McpServerEnablementState.DisabledByAccess) { - const settingsCommandLink = createCommandUri('workbench.action.openSettings', { query: `@id:${mcpAccessConfig}` }).toString(); - if (this.configurationService.getValue(mcpAccessConfig) === McpAccessValue.None) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - all not allowed', "This MCP Server is disabled because MCP servers are configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }, true); - } else { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }, true); - } - return; + const runtimeState = this.mcpServer.runtimeState; + if (runtimeState?.disabled && runtimeState.reason) { + this.updateStatus({ icon: warningIcon, message: runtimeState.reason }, true); } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts index 6b3c30a545d..54a382ebf98 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts @@ -38,7 +38,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IMcpServerContainer, IMcpServerEditorOptions, IMcpWorkbenchService, IWorkbenchMcpServer, McpServerContainers, McpServerInstallState } from '../common/mcpTypes.js'; import { StarredWidget, McpServerIconWidget, McpServerStatusWidget, McpServerWidget, onClick, PublisherWidget, McpServerScopeBadgeWidget, LicenseWidget } from './mcpServerWidgets.js'; -import { DropDownAction, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction, UninstallAction } from './mcpServerActions.js'; +import { ButtonWithDropDownExtensionAction, ButtonWithDropdownExtensionActionViewItem, DropDownAction, InstallAction, InstallingLabelAction, InstallInRemoteAction, InstallInWorkspaceAction, ManageMcpServerAction, McpServerStatusAction, UninstallAction } from './mcpServerActions.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { ILocalMcpServer, IGalleryMcpServerConfiguration, IMcpServerPackage, IMcpServerKeyValueInput, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -47,6 +47,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { getMcpGalleryManifestResourceUri, IMcpGalleryManifestService, McpGalleryResourceType } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { fromNow } from '../../../../base/common/date.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; const enum McpServerEditorTab { Readme = 'readme', @@ -185,6 +186,7 @@ export class McpServerEditor extends EditorPane { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @IHoverService private readonly hoverService: IHoverService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { super(McpServerEditor.ID, group, telemetryService, themeService, storageService); this.mcpServerReadme = null; @@ -240,9 +242,15 @@ export class McpServerEditor extends EditorPane { const description = append(details, $('.description')); const actions = [ - this.instantiationService.createInstance(InstallAction, true), + this.instantiationService.createInstance(InstallAction, false), this.instantiationService.createInstance(InstallingLabelAction), - this.instantiationService.createInstance(UninstallAction), + this.instantiationService.createInstance(ButtonWithDropDownExtensionAction, 'extensions.uninstall', UninstallAction.CLASS, [ + [ + this.instantiationService.createInstance(UninstallAction), + this.instantiationService.createInstance(InstallInWorkspaceAction, false), + this.instantiationService.createInstance(InstallInRemoteAction, false) + ] + ]), this.instantiationService.createInstance(ManageMcpServerAction, true), ]; @@ -252,6 +260,18 @@ export class McpServerEditor extends EditorPane { if (action instanceof DropDownAction) { return action.createActionViewItem(options); } + if (action instanceof ButtonWithDropDownExtensionAction) { + return new ButtonWithDropdownExtensionActionViewItem( + action, + { + ...options, + icon: true, + label: true, + menuActionsOrProvider: { getActions: () => action.menuActions }, + menuActionClassNames: action.menuActionClassNames + }, + this.contextMenuService); + } return undefined; }, focusOnlyEnabledItems: true diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerEditorInput.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerEditorInput.ts index f82a38ff41e..fe87eabc083 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerEditorInput.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerEditorInput.ts @@ -54,6 +54,6 @@ export class McpServerEditorInput extends EditorInput { return true; } - return other instanceof McpServerEditorInput && this._mcpServer.name === other._mcpServer.name; + return other instanceof McpServerEditorInput && this._mcpServer.id === other._mcpServer.id; } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index af63377febb..0a5e67fd61f 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -26,12 +26,12 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js'; import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; import { IViewDescriptorService, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; -import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServerEnablementState, McpServerInstallState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; +import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; import { DropDownAction, getContextMenuActions, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js'; import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js'; import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { IAllowedMcpServersService, mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; +import { mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; @@ -328,8 +328,8 @@ export class McpServersListView extends AbstractExtensionsListView>()); let servers = await this.mcpWorkbenchService.queryLocal(); - disposables.add(Event.debounce(Event.filter(this.mcpWorkbenchService.onChange, e => e?.installState === McpServerInstallState.Installed), () => undefined)(() => { - const mergedMcpServers = this.mergeAddedMcpServers(servers, [...this.mcpWorkbenchService.local]); + disposables.add(Event.debounce(this.mcpWorkbenchService.onChange, () => undefined)(() => { + const mergedMcpServers = this.mergeChangedMcpServers(servers, [...this.mcpWorkbenchService.local]); if (mergedMcpServers) { servers = mergedMcpServers; onDidChangeModel.fire(new PagedModel(servers)); @@ -339,7 +339,7 @@ export class McpServersListView extends AbstractExtensionsListView { let index = -1; @@ -355,16 +355,35 @@ export class McpServersListView extends AbstractExtensionsListView r.id !== mcpServer.id)) { + const newMcpServer = newMcpServers[index]; + if (mcpServers.every(r => r.id !== newMcpServer.id)) { hasChanged = true; - mcpServers.splice(findPreviousMcpServerIndex(index - 1) + 1, 0, mcpServer); + mcpServers.splice(findPreviousMcpServerIndex(index - 1) + 1, 0, newMcpServer); + } + } + + for (let index = mcpServers.length - 1; index >= 0; index--) { + const oldMcpServer = mcpServers[index]; + if (newMcpServers.every(r => r.id !== oldMcpServer.id) && newMcpServers.some(r => r.name === oldMcpServer.name)) { + hasChanged = true; + mcpServers.splice(index, 1); + } + } + + if (!hasChanged) { + if (mcpServers.length === newMcpServers.length) { + for (let index = 0; index < newMcpServers.length; index++) { + if (mcpServers[index]?.id !== newMcpServers[index]?.id) { + hasChanged = true; + mcpServers = newMcpServers; + break; + } + } } } return hasChanged ? mcpServers : undefined; } - } interface IMcpServerTemplateData { @@ -386,8 +405,8 @@ class McpServerRenderer implements IPagedRenderer { - const disabled = !!mcpServer.local && - (mcpServer.installState === McpServerInstallState.Installed - ? mcpServer.enablementState === McpServerEnablementState.DisabledByAccess - : mcpServer.installState === McpServerInstallState.Uninstalled); - data.root.classList.toggle('disabled', disabled); - }; + const updateEnablement = () => data.root.classList.toggle('disabled', !!mcpServer.runtimeState?.disabled); updateEnablement(); - this.allowedMcpServersService.onDidChangeAllowedMcpServers(() => updateEnablement(), this, data.mcpServerDisposables); + data.mcpServerDisposables.push(this.mcpWorkbenchService.onChange(e => { + if (!e || e.id === mcpServer.id) { + updateEnablement(); + } + })); } disposeElement(mcpServer: IWorkbenchMcpServer, index: number, data: IMcpServerTemplateData): void { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index 5dd6a3f44ec..22d68e6d88f 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -5,7 +5,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { createCommandUri, IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { basename } from '../../../../base/common/resources.js'; @@ -19,7 +19,7 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { IGalleryMcpServer, IMcpGalleryService, IQueryOptions, IInstallableMcpServer, IGalleryMcpServerConfiguration, mcpAccessConfig, McpAccessValue } from '../../../../platform/mcp/common/mcpManagement.js'; +import { IGalleryMcpServer, IMcpGalleryService, IQueryOptions, IInstallableMcpServer, IGalleryMcpServerConfiguration, mcpAccessConfig, McpAccessValue, IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IMcpServerConfiguration, IMcpServerVariable, IMcpStdioServerConfiguration, McpServerType } from '../../../../platform/mcp/common/mcpPlatformTypes.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; @@ -36,11 +36,12 @@ import { DidUninstallWorkbenchMcpServerEvent, IWorkbenchLocalMcpServer, IWorkben import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; import { mcpConfigurationSection } from '../common/mcpConfiguration.js'; import { McpServerInstallData, McpServerInstallClassification } from '../common/mcpServer.js'; -import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerEnablementState, McpServerInstallState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; +import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerEnablementState, McpServerInstallState, McpServerRuntimeState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { IMcpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { IPager, singlePagePager } from '../../../../base/common/paging.js'; import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { runOnChange } from '../../../../base/common/observable.js'; interface IMcpServerStateProvider { (mcpWorkbenchServer: McpWorkbenchServer): T; @@ -50,12 +51,12 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { constructor( private installStateProvider: IMcpServerStateProvider, + private runtimeStateProvider: IMcpServerStateProvider, public local: IWorkbenchLocalMcpServer | undefined, public gallery: IGalleryMcpServer | undefined, public readonly installable: IInstallableMcpServer | undefined, @IMcpGalleryService private readonly mcpGalleryService: IMcpGalleryService, @IFileService private readonly fileService: IFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { this.local = local; } @@ -115,15 +116,8 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { return this.local?.config ?? this.installable?.config; } - get enablementState(): McpServerEnablementState { - const accessValue = this.configurationService.getValue(mcpAccessConfig); - if (accessValue === McpAccessValue.None) { - return McpServerEnablementState.DisabledByAccess; - } - if (accessValue === McpAccessValue.Registry && !this.gallery) { - return McpServerEnablementState.DisabledByAccess; - } - return McpServerEnablementState.Enabled; + get runtimeState(): McpServerRuntimeState | undefined { + return this.runtimeStateProvider(this); } get readmeUrl(): URI | undefined { @@ -194,6 +188,8 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IAllowedMcpServersService private readonly allowedMcpServersService: IAllowedMcpServersService, + @IMcpService private readonly mcpService: IMcpService, @IURLService urlService: IURLService, ) { super(); @@ -209,6 +205,14 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } })); this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifest(e => this.syncInstalledMcpServers(true))); + this._register(this.allowedMcpServersService.onDidChangeAllowedMcpServers(() => { + this._local = this.sort(this._local); + this._onChange.fire(undefined); + })); + this._register(runOnChange(mcpService.servers, () => { + this._local = this.sort(this._local); + this._onChange.fire(undefined); + })); } private async onDidChangeProfile() { @@ -247,13 +251,13 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (server) { server.local = local; } else { - server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), local, source, undefined); + server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), local, source, undefined); } if (!local.galleryUrl) { server.gallery = undefined; } this._local = this._local.filter(server => !this.areSameMcpServers(server.local, local)); - this._local.push(server); + this.addServer(server); } this._onChange.fire(server); } @@ -273,8 +277,8 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this._local[serverIndex].local = result.local; server = this._local[serverIndex]; } else { - server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), result.local, result.source, undefined); - this._local.push(server); + server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), result.local, result.source, undefined); + this.addServer(server); } this._onChange.fire(server); } @@ -339,40 +343,55 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } const pager = await this.mcpGalleryService.query(options, token); return { - firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined)), + firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined)), total: pager.total, pageSize: pager.pageSize, getPage: async (pageIndex, token) => { const page = await pager.getPage(pageIndex, token); - return page.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined)); + return page.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined)); } }; } async queryLocal(): Promise { const installed = await this.mcpManagementService.getInstalled(); - this._local = installed.map(i => { - const existing = this._local.find(local => { - if (i.galleryUrl) { - return local.local?.name === i.name; - } - return local.id === i.id; - }); - const local = existing ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, undefined, undefined); + this._local = this.sort(installed.map(i => { + const existing = this._local.find(local => local.id === i.id); + const local = existing ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, undefined, undefined); local.local = i; return local; - }); + })); this._onChange.fire(undefined); return [...this.local]; } + private addServer(server: McpWorkbenchServer): void { + this._local.push(server); + this._local = this.sort(this._local); + } + + private sort(local: McpWorkbenchServer[]): McpWorkbenchServer[] { + return local.sort((a, b) => { + if (a.name === b.name) { + if (!a.runtimeState?.disabled) { + return -1; + } + if (!b.runtimeState?.disabled) { + return 1; + } + return 0; + } + return a.name.localeCompare(b.name); + }); + } + getEnabledLocalMcpServers(): IWorkbenchLocalMcpServer[] { const result = new Map(); const userRemote: IWorkbenchLocalMcpServer[] = []; const workspace: IWorkbenchLocalMcpServer[] = []; for (const server of this.local) { - if (server.enablementState !== McpServerEnablementState.Enabled) { + if (this.getEnablementState(server) !== McpServerEnablementState.Enabled) { continue; } @@ -491,6 +510,11 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ }); throw error; + } finally { + if (this.installing.includes(server)) { + this.installing.splice(this.installing.indexOf(server), 1); + this._onChange.fire(server); + } } } @@ -637,7 +661,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (config.type === undefined) { (>config).type = (parsed).command ? McpServerType.LOCAL : McpServerType.REMOTE; } - this.open(this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, undefined, { name, config, inputs })); + this.open(this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, undefined, { name, config, inputs })); } catch (e) { // ignore } @@ -651,7 +675,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this.logService.info(`MCP server '${url}' not found`); return true; } - const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore @@ -667,7 +691,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this.logService.info(`MCP server '${name}' not found`); return true; } - const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore @@ -695,6 +719,43 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return local ? McpServerInstallState.Installed : McpServerInstallState.Uninstalled; } + private getRuntimeState(mcpServer: McpWorkbenchServer): McpServerRuntimeState | undefined { + if (!mcpServer.local) { + return undefined; + } + + const accessValue = this.configurationService.getValue(mcpAccessConfig); + const settingsCommandLink = createCommandUri('workbench.action.openSettings', { query: `@id:${mcpAccessConfig}` }).toString(); + if (accessValue === McpAccessValue.None) { + return { disabled: true, reason: new MarkdownString(localize('disabled - all not allowed', "This MCP Server is disabled because MCP servers are configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }; + } + if (accessValue === McpAccessValue.Registry && !mcpServer.gallery) { + return { disabled: true, reason: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }; + } + + if (!this.mcpService.servers.get().find(s => s.definition.id === mcpServer.id)) { + return { disabled: true }; + } + + return undefined; + } + + private getEnablementState(mcpServer: McpWorkbenchServer): McpServerEnablementState { + if (!mcpServer.local) { + return McpServerEnablementState.Enabled; + } + + const accessValue = this.configurationService.getValue(mcpAccessConfig); + if (accessValue === McpAccessValue.None) { + return McpServerEnablementState.DisabledByAccess; + } + if (accessValue === McpAccessValue.Registry && !mcpServer.gallery) { + return McpServerEnablementState.DisabledByAccess; + } + + return McpServerEnablementState.Enabled; + } + } export class MCPContextsInitialisation extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/mcp/common/discovery/installedMcpServersDiscovery.ts b/src/vs/workbench/contrib/mcp/common/discovery/installedMcpServersDiscovery.ts index 99769a4ac58..990d135833d 100644 --- a/src/vs/workbench/contrib/mcp/common/discovery/installedMcpServersDiscovery.ts +++ b/src/vs/workbench/contrib/mcp/common/discovery/installedMcpServersDiscovery.ts @@ -3,31 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals } from '../../../../../base/common/arrays.js'; import { Throttler } from '../../../../../base/common/async.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; -import { observableValue } from '../../../../../base/common/observable.js'; +import { ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { Location } from '../../../../../editor/common/languages.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { ConfigurationTarget } from '../../../../../platform/configuration/common/configuration.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; import { StorageScope } from '../../../../../platform/storage/common/storage.js'; import { IWorkbenchLocalMcpServer } from '../../../../services/mcp/common/mcpWorkbenchManagementService.js'; import { getMcpServerMapping } from '../mcpConfigFileUtils.js'; import { mcpConfigurationSection } from '../mcpConfiguration.js'; import { IMcpRegistry } from '../mcpRegistryTypes.js'; -import { IMcpConfigPath, IMcpWorkbenchService, McpServerDefinition, McpServerLaunch, McpServerTransportType, McpServerTrust } from '../mcpTypes.js'; +import { IMcpConfigPath, IMcpWorkbenchService, McpCollectionDefinition, McpServerDefinition, McpServerLaunch, McpServerTransportType, McpServerTrust } from '../mcpTypes.js'; import { IMcpDiscovery } from './mcpDiscovery.js'; +interface CollectionState extends IDisposable { + definition: McpCollectionDefinition; + serverDefinitions: ISettableObservable; +} + export class InstalledMcpServersDiscovery extends Disposable implements IMcpDiscovery { readonly fromGallery = true; - private readonly collectionDisposables = this._register(new DisposableMap()); + private readonly collections = this._register(new DisposableMap()); constructor( @IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService, @IMcpRegistry private readonly mcpRegistry: IMcpRegistry, @ITextModelService private readonly textModelService: ITextModelService, + @ILogService private readonly logService: ILogService, ) { super(); } @@ -109,9 +117,15 @@ export class InstalledMcpServersDiscovery extends Disposable implements IMcpDisc }); } + for (const [id] of this.collections) { + if (!collections.has(id)) { + this.collections.deleteAndDispose(id); + } + } + for (const [id, [mcpConfigPath, serverDefinitions]] of collections) { - this.collectionDisposables.deleteAndDispose(id); - this.collectionDisposables.set(id, this.mcpRegistry.registerCollection({ + const newServerDefinitions = observableValue(this, serverDefinitions); + const newCollection: McpCollectionDefinition = { id, label: mcpConfigPath?.label ?? '', presentation: { @@ -119,20 +133,33 @@ export class InstalledMcpServersDiscovery extends Disposable implements IMcpDisc origin: mcpConfigPath?.uri, }, remoteAuthority: mcpConfigPath?.remoteAuthority ?? null, - serverDefinitions: observableValue(this, serverDefinitions), + serverDefinitions: newServerDefinitions, trustBehavior: McpServerTrust.Kind.Trusted, configTarget: mcpConfigPath?.target ?? ConfigurationTarget.USER, scope: mcpConfigPath?.scope ?? StorageScope.PROFILE, - })); - } - for (const [id] of this.collectionDisposables) { - if (!collections.has(id)) { - this.collectionDisposables.deleteAndDispose(id); + }; + const existingCollection = this.collections.get(id); + + const collectionDefinitionsChanged = existingCollection ? !McpCollectionDefinition.equals(existingCollection.definition, newCollection) : true; + if (!collectionDefinitionsChanged) { + const serverDefinitionsChanged = existingCollection ? !equals(existingCollection.definition.serverDefinitions.get(), newCollection.serverDefinitions.get(), McpServerDefinition.equals) : true; + if (serverDefinitionsChanged) { + existingCollection?.serverDefinitions.set(serverDefinitions, undefined); + } + continue; } + + this.collections.deleteAndDispose(id); + const disposable = this.mcpRegistry.registerCollection(newCollection); + this.collections.set(id, { + definition: newCollection, + serverDefinitions: newServerDefinitions, + dispose: () => disposable.dispose() + }); } } catch (error) { - this.collectionDisposables.clearAndDisposeAll(); + this.logService.error(error); } } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 7afabf87bc1..794a2485ae4 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -8,7 +8,7 @@ import { assertNever } from '../../../../base/common/assert.js'; import { decodeHex, encodeHex, VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; -import { IMarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { equals as objectsEqual } from '../../../../base/common/objects.js'; import { IObservable, ObservableMap } from '../../../../base/common/observable.js'; @@ -677,12 +677,14 @@ export const enum McpServerEditorTab { Configuration = 'configuration', } +export type McpServerRuntimeState = { readonly disabled: boolean; readonly reason?: MarkdownString }; + export interface IWorkbenchMcpServer { readonly gallery: IGalleryMcpServer | undefined; readonly local: IWorkbenchLocalMcpServer | undefined; readonly installable: IInstallableMcpServer | undefined; readonly installState: McpServerInstallState; - readonly enablementState: McpServerEnablementState; + readonly runtimeState: McpServerRuntimeState | undefined; readonly id: string; readonly name: string; readonly label: string; From b2d986a041f24a5533c08f4f4c3daed91846fda6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:21:53 +0200 Subject: [PATCH 1244/4355] SCM - save/restore pinned repository state and update icons (#271914) --- .../workbench/contrib/scm/browser/activity.ts | 37 ++++---- .../scm/browser/scmHistoryChatContext.ts | 6 +- .../contrib/scm/browser/scmHistoryViewPane.ts | 2 +- .../scm/browser/scmRepositoriesViewPane.ts | 2 +- .../scm/browser/scmRepositoryRenderer.ts | 48 +++++++--- .../contrib/scm/browser/scmViewService.ts | 92 ++++++++++++------- src/vs/workbench/contrib/scm/common/scm.ts | 2 +- 7 files changed, 119 insertions(+), 70 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index b527318ed40..9103ec311cc 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -24,6 +24,7 @@ import { autorun, autorunWithStore, derived, IObservable, observableFromEvent } import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { Command } from '../../../../editor/common/languages.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; const ActiveRepositoryContextKeys = { ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), @@ -66,8 +67,8 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe () => this.scmService.repositories); this._activeRepositoryHistoryItemRefName = derived(reader => { - const repository = this.scmViewService.activeRepository.read(reader); - const historyProvider = repository?.provider.historyProvider.read(reader); + const activeRepository = this.scmViewService.activeRepository.read(reader); + const historyProvider = activeRepository?.repository.provider.historyProvider.read(reader); const historyItemRef = historyProvider?.historyItemRef.read(reader); return historyItemRef?.name; @@ -80,8 +81,8 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe return [...Iterable.map(repositories, r => ({ provider: r.provider, resourceCount: this._getRepositoryResourceCount(r) }))]; } case 'focused': { - const repository = this.scmViewService.activeRepository.read(reader); - return repository ? [{ provider: repository.provider, resourceCount: this._getRepositoryResourceCount(repository) }] : []; + const activeRepository = this.scmViewService.activeRepository.read(reader); + return activeRepository ? [{ provider: activeRepository.repository.provider, resourceCount: this._getRepositoryResourceCount(activeRepository.repository) }] : []; } case 'off': return []; @@ -109,17 +110,17 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe })); this._register(autorunWithStore((reader, store) => { - const repository = this.scmViewService.activeRepository.read(reader); - const commands = repository?.provider.statusBarCommands.read(reader); + const activeRepository = this.scmViewService.activeRepository.read(reader); + const commands = activeRepository?.repository.provider.statusBarCommands.read(reader); - this._updateStatusBar(repository, commands ?? [], store); + this._updateStatusBar(activeRepository, commands ?? [], store); })); this._register(autorun(reader => { - const repository = this.scmViewService.activeRepository.read(reader); + const activeRepository = this.scmViewService.activeRepository.read(reader); const historyItemRefName = this._activeRepositoryHistoryItemRefName.read(reader); - this._updateActiveRepositoryContextKeys(repository?.provider.name, historyItemRefName); + this._updateActiveRepositoryContextKeys(activeRepository?.repository.provider.name, historyItemRefName); })); } @@ -136,14 +137,14 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe store.add(this.activityService.showViewActivity(VIEW_PANE_ID, { badge })); } - private _updateStatusBar(repository: ISCMRepository | undefined, commands: readonly Command[], store: DisposableStore): void { - if (!repository) { + private _updateStatusBar(activeRepository: { repository: ISCMRepository; pinned: boolean } | undefined, commands: readonly Command[], store: DisposableStore): void { + if (!activeRepository) { return; } - const label = repository.provider.rootUri - ? `${basename(repository.provider.rootUri)} (${repository.provider.label})` - : repository.provider.label; + const label = activeRepository.repository.provider.rootUri + ? `${basename(activeRepository.repository.provider.rootUri)} (${activeRepository.repository.provider.label})` + : activeRepository.repository.provider.label; for (let index = 0; index < commands.length; index++) { const command = commands[index]; @@ -177,13 +178,15 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe // Source control provider status bar entry if (this.scmService.repositoryCount > 1) { - const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) - ? `$(${repository.provider.iconPath.id})` + const icon = ThemeIcon.isThemeIcon(activeRepository.repository.provider.iconPath) + ? activeRepository.repository.provider.iconPath.id === Codicon.repo.id && activeRepository.pinned === true + ? `$(${Codicon.repoPinned.id})` + : `$(${activeRepository.repository.provider.iconPath.id})` : '$(repo)'; const repositoryStatusbarEntry: IStatusbarEntry = { name: localize('status.scm.provider', "Source Control Provider"), - text: `${icon} ${repository.provider.name}`, + text: `${icon} ${activeRepository.repository.provider.name}`, ariaLabel: label, tooltip: label, command: 'scm.setActiveProvider' diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 3983103dcc9..ffb72f15522 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -100,7 +100,7 @@ class SCMHistoryItemContext implements IChatContextPickerItem { isEnabled(_widget: IChatWidget): Promise | boolean { const activeRepository = this._scmViewService.activeRepository.get(); - return activeRepository?.provider.historyProvider.get() !== undefined; + return activeRepository?.repository.provider.historyProvider.get() !== undefined; } asPicker(_widget: IChatWidget) { @@ -109,7 +109,7 @@ class SCMHistoryItemContext implements IChatContextPickerItem { picks: picksWithPromiseFn((query: string, token: CancellationToken) => { const filterText = query.trim() !== '' ? query.trim() : undefined; const activeRepository = this._scmViewService.activeRepository.get(); - const historyProvider = activeRepository?.provider.historyProvider.get(); + const historyProvider = activeRepository?.repository.provider.historyProvider.get(); if (!activeRepository || !historyProvider) { return Promise.resolve([]); } @@ -143,7 +143,7 @@ class SCMHistoryItemContext implements IChatContextPickerItem { iconClass: ThemeIcon.asClassName(Codicon.gitCommit), label: historyItem.subject, detail: details.join(`$(${Codicon.circleSmallFilled.id})`), - asAttachment: () => SCMHistoryItemContext.asAttachment(activeRepository.provider, historyItem) + asAttachment: () => SCMHistoryItemContext.asAttachment(activeRepository.repository.provider, historyItem) } satisfies IChatContextPickerPickItem; }); }); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 30d78427915..2f848351969 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -1069,7 +1069,7 @@ class SCMHistoryViewModel extends Disposable { return selectedRepository; } - return this._scmViewService.activeRepository.read(reader); + return this._scmViewService.activeRepository.read(reader)?.repository; }); this.repository = latestChangedValue(this, [firstRepository, graphRepository]); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 13f5d062dfc..96f036a847c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -408,7 +408,7 @@ registerAction2(class extends Action2 { return; } - scmViewService.pinActiveRepository(activeRepository); + scmViewService.pinActiveRepository(activeRepository.repository); } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index c0fd32831ac..604c5813d2f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -5,7 +5,7 @@ import './media/scm.css'; import { IDisposable, DisposableStore, combinedDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore } from '../../../../base/common/observable.js'; +import { autorun, autorunWithStore, IObservable } from '../../../../base/common/observable.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { ISCMProvider, ISCMRepository, ISCMViewService } from '../common/scm.js'; import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; @@ -26,6 +26,9 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -64,17 +67,21 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer; constructor( private readonly toolbarMenuId: MenuId, private readonly actionViewItemProvider: IActionViewItemProvider, @ICommandService private commandService: ICommandService, + @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private contextKeyService: IContextKeyService, @IContextMenuService private contextMenuService: IContextMenuService, @IKeybindingService private keybindingService: IKeybindingService, @IMenuService private menuService: IMenuService, @ISCMViewService private scmViewService: ISCMViewService, @ITelemetryService private telemetryService: ITelemetryService - ) { } + ) { + this._selectionModeConfig = observableConfigValue('scm.repositories.selectionMode', 'multiple', this.configurationService); + } renderTemplate(container: HTMLElement): RepositoryTemplate { // hack @@ -99,19 +106,16 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate): void { const repository = isSCMRepository(arg) ? arg : arg.element; - const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) - ? repository.provider.iconPath.id - : undefined; + templateData.elementDisposables.add(autorun(reader => { + const selectionMode = this._selectionModeConfig.read(undefined); + const activeRepository = this.scmViewService.activeRepository.read(reader); - const label = icon - ? `$(${icon}) ${repository.provider.name}` - : repository.provider.name; + const pinned = selectionMode === 'single' + ? activeRepository?.pinned === true && repository === activeRepository?.repository + : false; - if (repository.provider.rootUri) { - templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); - } else { - templateData.label.setLabel(label, undefined, { title: repository.provider.label }); - } + this._renderLabel(repository, pinned, templateData); + })); let statusPrimaryActions: IAction[] = []; let menuPrimaryActions: IAction[] = []; @@ -154,6 +158,24 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, template: RepositoryTemplate): void { template.elementDisposables.clear(); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index c52de42d8b0..d3ad96a387e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -70,16 +70,30 @@ export class RepositoryPicker { { type: 'separator' } ]; - picks.push(...this._scmViewService.repositories.map(r => ({ - label: r.provider.name, - description: r.provider.rootUri?.fsPath, - iconClass: ThemeIcon.isThemeIcon(r.provider.iconPath) + const activeRepository = this._scmViewService.activeRepository.get(); + const repository = activeRepository?.repository; + const pinned = activeRepository?.pinned === true; + + picks.push(...this._scmViewService.repositories.map(r => { + let iconClass = ThemeIcon.isThemeIcon(r.provider.iconPath) ? ThemeIcon.asClassName(r.provider.iconPath) - : ThemeIcon.asClassName(Codicon.repo), - repository: r - }))); + : ThemeIcon.asClassName(Codicon.repo); + + // Pinned repository icon + if (r === repository && pinned && ThemeIcon.isThemeIcon(r.provider.iconPath) && r.provider.iconPath.id === Codicon.repo.id) { + iconClass = ThemeIcon.asClassName(Codicon.repoPinned); + } + + return { + label: r.provider.name, description: r.provider.rootUri?.fsPath, iconClass, repository: r + }; + })); + + const activeItem = pinned + ? picks.find(p => p.type !== 'separator' && p.repository === repository) as RepositoryQuickPickItem | undefined + : this._autoQuickPickItem; - return this._quickInputService.pick(picks, { placeHolder: this._placeHolder }); + return this._quickInputService.pick(picks, { placeHolder: this._placeHolder, activeItem }); } } @@ -92,8 +106,9 @@ interface ISCMRepositoryView { export interface ISCMViewServiceState { readonly all: string[]; - readonly sortKey: ISCMRepositorySortKey; readonly visible: number[]; + readonly pinned?: boolean; + readonly sortKey: ISCMRepositorySortKey; } export class SCMViewService implements ISCMViewService { @@ -201,7 +216,7 @@ export class SCMViewService implements ISCMViewService { private _onDidFocusRepository = new Emitter(); readonly onDidFocusRepository = this._onDidFocusRepository.event; - readonly activeRepository: IObservable; + readonly activeRepository: IObservable<{ repository: ISCMRepository; pinned: boolean } | undefined>; private readonly _activeEditorObs: IObservable; private readonly _activeEditorRepositoryObs: IObservable; @@ -234,6 +249,21 @@ export class SCMViewService implements ISCMViewService { this._selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'multiple', this.configurationService); + try { + this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); + + // If previously there were multiple visible repositories but the + // view mode is `single`, only restore the first visible repository. + if (this.previousState && this.previousState.visible.length > 1 && this._selectionModeConfig.get() === 'single') { + this.previousState = { + ...this.previousState, + visible: [this.previousState.visible[0]] + }; + } + } catch { + // noop + } + this._focusedRepositoryObs = observableFromEventOpts( { owner: this, @@ -267,14 +297,17 @@ export class SCMViewService implements ISCMViewService { this._activeRepositoryPinnedObs = observableValue(this, undefined); this._activeRepositoryObs = latestChangedValue(this, [this._activeEditorRepositoryObs, this._focusedRepositoryObs]); - this.activeRepository = derivedOpts({ + this.activeRepository = derivedOpts<{ repository: ISCMRepository; pinned: boolean } | undefined>({ owner: this, - equalsFn: (r1, r2) => r1?.id === r2?.id + equalsFn: (r1, r2) => r1?.repository.id === r2?.repository.id && r1?.pinned === r2?.pinned }, reader => { const activeRepository = this._activeRepositoryObs.read(reader); const activeRepositoryPinned = this._activeRepositoryPinnedObs.read(reader); - return activeRepositoryPinned ?? activeRepository; + const repository = activeRepositoryPinned ?? activeRepository; + const pinned = !!activeRepositoryPinned; + + return repository ? { repository, pinned } : undefined; }); this.disposables.add(autorun(reader => { @@ -282,7 +315,7 @@ export class SCMViewService implements ISCMViewService { const activeRepository = this.activeRepository.read(reader); if (selectionMode === 'single' && activeRepository) { - this.visibleRepositories = [activeRepository]; + this.visibleRepositories = [activeRepository.repository]; } })); @@ -293,28 +326,13 @@ export class SCMViewService implements ISCMViewService { } })); - try { - this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); - - // If previously there were multiple visible repositories but the - // view mode is `single`, only restore the first visible repository. - if (this.previousState && this.previousState.visible.length > 1 && this._selectionModeConfig.get() === 'single') { - this.previousState = { - ...this.previousState, - visible: [this.previousState.visible[0]] - }; - } - } catch { - // noop - } + this._repositoryPinnedContextKey = RepositoryContextKeys.RepositoryPinned.bindTo(contextKeyService); + this._repositoryPinnedContextKey.set(!!this._activeRepositoryPinnedObs.get()); this._repositoriesSortKey = this.previousState?.sortKey ?? this.getViewSortOrder(); this._sortKeyContextKey = RepositoryContextKeys.RepositorySortKey.bindTo(contextKeyService); this._sortKeyContextKey.set(this._repositoriesSortKey); - this._repositoryPinnedContextKey = RepositoryContextKeys.RepositoryPinned.bindTo(contextKeyService); - this._repositoryPinnedContextKey.set(!!this._activeRepositoryPinnedObs.get()); - scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -338,9 +356,9 @@ export class SCMViewService implements ISCMViewService { this.eventuallyFinishLoading(); } - const repositoryView: ISCMRepositoryView = { + const repositoryView = { repository, discoveryTime: Date.now(), focused: false, selectionIndex: -1 - }; + } satisfies ISCMRepositoryView; let removed: Iterable = Iterable.empty(); @@ -398,6 +416,11 @@ export class SCMViewService implements ISCMViewService { const maxSelectionIndex = this.getMaxSelectionIndex(); this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed }); + + // Pin repository if needed + if (this._selectionModeConfig.get() === 'single' && this.previousState?.pinned && this.didSelectRepository) { + this.pinActiveRepository(repository); + } } else { // Single selection mode (add subsequent repository) this.insertRepositoryView(this._repositories, repositoryView); @@ -555,8 +578,9 @@ export class SCMViewService implements ISCMViewService { const all = this.repositories.map(r => getProviderStorageKey(r.provider)); const visible = this.visibleRepositories.map(r => all.indexOf(getProviderStorageKey(r.provider))); - this.previousState = { all, sortKey: this._repositoriesSortKey, visible }; + const pinned = this.activeRepository.get()?.pinned === true; + this.previousState = { all, visible, pinned, sortKey: this._repositoriesSortKey } satisfies ISCMViewServiceState; this.storageService.store('scm:view:visibleRepositories', JSON.stringify(this.previousState), StorageScope.WORKSPACE, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 9f10ac505ef..5bcc5e638f4 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -240,6 +240,6 @@ export interface ISCMViewService { /** * Focused repository or the repository for the active editor */ - readonly activeRepository: IObservable; + readonly activeRepository: IObservable<{ repository: ISCMRepository; pinned: boolean } | undefined>; pinActiveRepository(repository: ISCMRepository | undefined): void; } From 8ca283b8b4037f3fe524d024350fe5e05b8b03db Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:40:47 +0200 Subject: [PATCH 1245/4355] SCM - fix issue with updating selected repository (#271924) --- .../workbench/contrib/scm/browser/scmViewService.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index d3ad96a387e..7268923e02b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -291,7 +291,7 @@ export class SCMViewService implements ISCMViewService { return lastValue; } - return repository; + return Object.create(repository); }); this._activeRepositoryPinnedObs = observableValue(this, undefined); @@ -315,7 +315,14 @@ export class SCMViewService implements ISCMViewService { const activeRepository = this.activeRepository.read(reader); if (selectionMode === 'single' && activeRepository) { - this.visibleRepositories = [activeRepository.repository]; + // `this._activeEditorRepositoryObs` uses `Object.create` to + // ensure that the observable updates even if the repository + // instance is the same. + const repository = this.repositories + .find(r => r.id === activeRepository.repository.id); + if (repository) { + this.visibleRepositories = [repository]; + } } })); From e1ab600307004e0a62037f4d06dd8fbac12bfcf0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 17 Oct 2025 09:48:07 -0400 Subject: [PATCH 1246/4355] rm veto (#271806) --- .../terminalContrib/chat/browser/terminalChatService.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index b4470655143..a695f349a1f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -42,17 +42,12 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._restoreFromStorage(); this._register(this._lifecycleService.onBeforeShutdown(async e => { - let veto = false; // Show all hidden terminals before shutdown so they are restored for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId) { if (this.terminalIsHidden(toolSessionId) && (instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.state === PromptInputState.Execute || instance.hasChildProcesses)) { await this._terminalService.showBackgroundTerminal(instance, true, true); - veto = true; } } - if (veto) { - e.veto(Promise.resolve(true), 'terminalChatService'); - } })); } From 0113ed285bf077b099f3d1e73344702033d7c44c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 17 Oct 2025 09:51:16 -0400 Subject: [PATCH 1247/4355] update icon (#271936) --- .../toolInvocationParts/chatTerminalToolProgressPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index f63cc2819a9..21b721a3f0b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -191,7 +191,7 @@ export class FocusChatInstanceAction extends Action implements IAction { super( 'chat.focusTerminalInstance', isTerminalHidden ? localize('showTerminal', 'Show Terminal') : localize('focusTerminal', 'Focus Terminal'), - ThemeIcon.asClassName(Codicon.linkExternal), + ThemeIcon.asClassName(Codicon.openInProduct), true, ); } From a72a078093ce2bb346042e6dcd888fd760319170 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:12:20 +0200 Subject: [PATCH 1248/4355] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20consolidate?= =?UTF-8?q?=20code=20related=20to=20pinned=20repository=20icon=20(#271939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workbench/contrib/scm/browser/activity.ts | 13 ++----- .../scm/browser/scmRepositoryRenderer.ts | 38 +++++++------------ .../contrib/scm/browser/scmViewService.ts | 16 +++----- src/vs/workbench/contrib/scm/browser/util.ts | 21 ++++++++++ 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 9103ec311cc..ea73105b4cc 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -19,12 +19,10 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { ITitleService } from '../../../services/title/browser/titleService.js'; import { IEditorGroupContextKeyProvider, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { getRepositoryResourceCount } from './util.js'; +import { getRepositoryResourceCount, getSCMRepositoryIcon } from './util.js'; import { autorun, autorunWithStore, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { Command } from '../../../../editor/common/languages.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { Codicon } from '../../../../base/common/codicons.js'; const ActiveRepositoryContextKeys = { ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), @@ -178,15 +176,10 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe // Source control provider status bar entry if (this.scmService.repositoryCount > 1) { - const icon = ThemeIcon.isThemeIcon(activeRepository.repository.provider.iconPath) - ? activeRepository.repository.provider.iconPath.id === Codicon.repo.id && activeRepository.pinned === true - ? `$(${Codicon.repoPinned.id})` - : `$(${activeRepository.repository.provider.iconPath.id})` - : '$(repo)'; - + const icon = getSCMRepositoryIcon(activeRepository, activeRepository.repository); const repositoryStatusbarEntry: IStatusbarEntry = { name: localize('status.scm.provider', "Source Control Provider"), - text: `${icon} ${activeRepository.repository.provider.name}`, + text: `$(${icon.id}) ${activeRepository.repository.provider.name}`, ariaLabel: label, tooltip: label, command: 'scm.setActiveProvider' diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 604c5813d2f..c4a7120ee15 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -12,7 +12,7 @@ import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ActionRunner, IAction } from '../../../../base/common/actions.js'; -import { connectPrimaryMenu, getRepositoryResourceCount, isSCMRepository, StatusBarAction } from './util.js'; +import { connectPrimaryMenu, getRepositoryResourceCount, getSCMRepositoryIcon, isSCMRepository, StatusBarAction } from './util.js'; import { ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; @@ -25,8 +25,6 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; @@ -110,11 +108,19 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, template: RepositoryTemplate): void { template.elementDisposables.clear(); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 7268923e02b..b1390a1d345 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -24,9 +24,9 @@ import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { localize } from '../../../../nls.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; +import { getSCMRepositoryIcon } from './util.js'; function getProviderStorageKey(provider: ISCMProvider): string { return `${provider.providerId}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`; @@ -75,17 +75,13 @@ export class RepositoryPicker { const pinned = activeRepository?.pinned === true; picks.push(...this._scmViewService.repositories.map(r => { - let iconClass = ThemeIcon.isThemeIcon(r.provider.iconPath) - ? ThemeIcon.asClassName(r.provider.iconPath) - : ThemeIcon.asClassName(Codicon.repo); - - // Pinned repository icon - if (r === repository && pinned && ThemeIcon.isThemeIcon(r.provider.iconPath) && r.provider.iconPath.id === Codicon.repo.id) { - iconClass = ThemeIcon.asClassName(Codicon.repoPinned); - } + const icon = getSCMRepositoryIcon(activeRepository, r); return { - label: r.provider.name, description: r.provider.rootUri?.fsPath, iconClass, repository: r + label: r.provider.name, + description: r.provider.rootUri?.fsPath, + iconClass: ThemeIcon.asClassName(icon), + repository: r }; })); diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index e982ea2ac36..f915cda154e 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -18,6 +18,8 @@ import { Command } from '../../../../editor/common/languages.js'; import { reset } from '../../../../base/browser/dom.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IResourceNode, ResourceTree } from '../../../../base/common/resourceTree.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; export function isSCMViewService(element: any): element is ISCMViewService { return Array.isArray((element as ISCMViewService).repositories) && Array.isArray((element as ISCMViewService).visibleRepositories); @@ -146,3 +148,22 @@ export function getRepositoryResourceCount(provider: ISCMProvider): number { export function getHistoryItemEditorTitle(historyItem: ISCMHistoryItem): string { return `${historyItem.displayId ?? historyItem.id} - ${historyItem.subject}`; } + +export function getSCMRepositoryIcon( + activeRepository: { repository: ISCMRepository; pinned: boolean } | undefined, + repository: ISCMRepository +): ThemeIcon { + if (!ThemeIcon.isThemeIcon(repository.provider.iconPath)) { + return Codicon.repo; + } + + if ( + activeRepository?.pinned === true && + activeRepository?.repository.id === repository.id && + repository.provider.iconPath.id === Codicon.repo.id + ) { + return Codicon.repoPinned; + } + + return repository.provider.iconPath; +} From 54cadfe42406ca0bfb3ce4440ac631966f71ad95 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 17 Oct 2025 10:12:42 -0400 Subject: [PATCH 1249/4355] follow-ups to hidden chat terminal work (#271940) follow-ups to #271400 --- .../chatTerminalToolProgressPart.ts | 11 ++++++----- src/vs/workbench/contrib/terminal/browser/terminal.ts | 3 +-- .../contrib/terminal/browser/terminalService.ts | 3 +-- .../chat/browser/terminalChatService.ts | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 21b721a3f0b..7ea405ef1fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -27,6 +27,7 @@ import { ChatConfiguration } from '../../../common/constants.js'; import { CommandsRegistry } from '../../../../../../platform/commands/common/commands.js'; import { ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../../terminal/browser/terminal.js'; import { Action, IAction } from '../../../../../../base/common/actions.js'; +import { MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { localize } from '../../../../../../nls.js'; import { TerminalLocation } from '../../../../../../platform/terminal/common/terminal.js'; @@ -34,7 +35,7 @@ import { TerminalLocation } from '../../../../../../platform/terminal/common/ter export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart { public readonly domNode: HTMLElement; - private _actionBar: ActionBar | undefined; + private readonly _actionBar = this._register(new MutableDisposable()); private markdownPart: ChatMarkdownContentPart | undefined; public get codeblocks(): IChatCodeBlockInfo[] { @@ -110,7 +111,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart } private _createActionBar(elements: { actionBar: HTMLElement }, terminalData: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData): void { - this._actionBar = this._register(new ActionBar(elements.actionBar, {})); + this._actionBar.value = new ActionBar(elements.actionBar, {}); const terminalToolSessionId = 'terminalToolSessionId' in terminalData ? terminalData.terminalToolSessionId : undefined; if (!terminalToolSessionId || !elements.actionBar) { @@ -130,14 +131,14 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart } private _addFocusAction(terminalInstance: ITerminalInstance, terminalToolSessionId: string) { - const isTerminalHidden = this._terminalChatService.terminalIsHidden(terminalToolSessionId); + const isTerminalHidden = this._terminalChatService.isBackgroundTerminal(terminalToolSessionId); const focusAction = this._register(this._instantiationService.createInstance(FocusChatInstanceAction, terminalInstance, isTerminalHidden)); - this._actionBar!.push([focusAction], { icon: true, label: false }); + this._actionBar.value?.push([focusAction], { icon: true, label: false }); } private _registerInstanceListener(terminalInstance: ITerminalInstance) { const instanceListener = this._register(terminalInstance.onDisposed(() => { - this._actionBar?.dispose(); + this._actionBar.clear(); instanceListener?.dispose(); })); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 95c50dcbf65..b84e545546e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -127,7 +127,7 @@ export interface ITerminalChatService { */ getTerminalInstanceByToolSessionId(terminalToolSessionId: string): ITerminalInstance | undefined; - terminalIsHidden(terminalToolSessionId: string): boolean; + isBackgroundTerminal(terminalToolSessionId: string): boolean; } /** @@ -325,7 +325,6 @@ export interface ITerminalService extends ITerminalInstanceHost { /** Gets all terminal instances, including editor, terminal view (group), and background instances. */ readonly instances: readonly ITerminalInstance[]; - /** Gets all foreground terminal instances */ readonly foregroundInstances: readonly ITerminalInstance[]; /** Gets detached terminal instances created via {@link createDetachedXterm}. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 33032d51b23..1c8d951c75a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -1183,8 +1183,7 @@ export class TerminalService extends Disposable implements ITerminalService { } if (forceSaveState) { - // Skips the debounce of _saveState as we are shutting down - // and need to save the revealed hideFromUser terminals + // Skips the debounce of _saveState in case it's shutting down await this._saveStateNow(); } this._onDidChangeInstances.fire(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index a695f349a1f..e80aa6a64c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -44,7 +44,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._register(this._lifecycleService.onBeforeShutdown(async e => { // Show all hidden terminals before shutdown so they are restored for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId) { - if (this.terminalIsHidden(toolSessionId) && (instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.state === PromptInputState.Execute || instance.hasChildProcesses)) { + if (this.isBackgroundTerminal(toolSessionId) && (instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.state === PromptInputState.Execute || instance.hasChildProcesses)) { await this._terminalService.showBackgroundTerminal(instance, true, true); } } @@ -83,7 +83,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ return this._terminalInstancesByToolSessionId.get(terminalToolSessionId); } - terminalIsHidden(terminalToolSessionId: string | undefined): boolean { + isBackgroundTerminal(terminalToolSessionId: string | undefined): boolean { if (!terminalToolSessionId) { return false; } From 3b923d988161f647f9a2369f0598e894d45b0bfc Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 17 Oct 2025 11:12:32 -0400 Subject: [PATCH 1250/4355] Fix model picker not swapping on login (#271949) * Fix https://github.com/microsoft/vscode/issues/271481 * Update src/vs/workbench/contrib/chat/browser/chatInputPart.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index c75adfaaf7f..48d0e86c560 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -530,7 +530,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this.languageModelsService.onDidChangeLanguageModels(() => { // We've changed models and the current one is no longer available. Select a new one const selectedModel = this._currentLanguageModel ? this.getModels().find(m => m.identifier === this._currentLanguageModel?.identifier) : undefined; - if (this._currentLanguageModel && (!selectedModel || !selectedModel.metadata.isUserSelectable)) { + const selectedModelNotAvailable = this._currentLanguageModel && (!selectedModel || !selectedModel.metadata.isUserSelectable); + if (!this.currentLanguageModel || selectedModelNotAvailable) { this.setCurrentLanguageModelToDefault(); } })); From 05ae4774b363a5f34256fde271ca536534639007 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:41:16 +0200 Subject: [PATCH 1251/4355] SCM - fix icon sizes in the Repository renderer (#271969) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 4e6b6520d01..b19eee0339e 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -59,10 +59,6 @@ font-weight: bold; } -.scm-view .scm-provider .monaco-highlighted-label .codicon { - font-size: 14px; -} - .scm-view .scm-provider > .actions { overflow: hidden; justify-content: flex-end; @@ -91,7 +87,6 @@ } .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label > .codicon { - font-size: 12px; justify-content: center; } From ea8930620941bc580426b6d81e58341e7bde7704 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:55:20 -0700 Subject: [PATCH 1252/4355] Force shell integration on for old p10k Fixes #271977 --- .../contrib/terminal/common/scripts/shellIntegration-rc.zsh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh index dc52dd8c7f9..266f84eed6f 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh @@ -84,6 +84,8 @@ fi # Report prompt type if [ -n "${P9K_SSH:-}" ] || [ -n "${P9K_TTY:-}" ]; then builtin printf '\e]633;P;PromptType=p10k\a' + # Force shell integration on for p10k + typeset -g POWERLEVEL9K_TERM_SHELL_INTEGRATION=true elif [ -n "${ZSH:-}" ] && [ -n "$ZSH_VERSION" ] && (( ${+functions[omz]} )); then builtin printf '\e]633;P;PromptType=oh-my-zsh\a' elif [ -n "${STARSHIP_SESSION_KEY:-}" ]; then From 1c2fd65c90cbfbd125ac51eccd446de019afaca3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:23:48 -0700 Subject: [PATCH 1253/4355] Add prompt type capability Fixes #271980 --- .../common/capabilities/capabilities.ts | 20 ++++--- .../commandDetectionCapability.ts | 9 ---- .../promptTypeDetectionCapability.ts | 23 ++++++++ .../common/xterm/shellIntegrationAddon.ts | 14 ++++- .../terminal/browser/terminalInstance.ts | 8 ++- .../terminal/browser/terminalTelemetry.ts | 2 +- .../terminal/browser/terminalTooltip.ts | 2 +- .../common/scripts/shellIntegration-rc.zsh | 2 +- .../promptTypeDetectionCapability.test.ts | 53 +++++++++++++++++++ 9 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 src/vs/platform/terminal/common/capabilities/promptTypeDetectionCapability.ts create mode 100644 src/vs/workbench/contrib/terminal/test/browser/xterm/promptTypeDetectionCapability.test.ts diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 1c42d5efe6e..687420dad81 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -47,6 +47,11 @@ export const enum TerminalCapability { */ ShellEnvDetection, + /** + * The terminal can detect the prompt type being used (e.g., p10k, posh-git). + */ + PromptTypeDetection, + } /** @@ -128,9 +133,8 @@ export interface ITerminalCapabilityImplMap { [TerminalCapability.PartialCommandDetection]: IPartialCommandDetectionCapability; [TerminalCapability.BufferMarkDetection]: IBufferMarkCapability; [TerminalCapability.ShellEnvDetection]: IShellEnvDetectionCapability; -} - -export interface ICwdDetectionCapability { + [TerminalCapability.PromptTypeDetection]: IPromptTypeDetectionCapability; +}export interface ICwdDetectionCapability { readonly type: TerminalCapability.CwdDetection; readonly onDidChangeCwd: Event; readonly cwds: string[]; @@ -149,6 +153,13 @@ export interface IShellEnvDetectionCapability { endEnvironmentSingleVar(isTrusted: boolean): void; } +export interface IPromptTypeDetectionCapability { + readonly type: TerminalCapability.PromptTypeDetection; + readonly promptType: string | undefined; + readonly onPromptTypeChanged: Event; + setPromptType(value: string): void; +} + export interface TerminalShellIntegrationEnvironment { /** * The dictionary of environment variables. @@ -203,14 +214,12 @@ export interface ICommandDetectionCapability { /** The current cwd at the cursor's position. */ readonly cwd: string | undefined; readonly hasRichCommandDetection: boolean; - readonly promptType: string | undefined; readonly currentCommand: ICurrentPartialCommand | undefined; readonly onCommandStarted: Event; readonly onCommandFinished: Event; readonly onCommandExecuted: Event; readonly onCommandInvalidated: Event; readonly onCurrentCommandInvalidated: Event; - readonly onPromptTypeChanged: Event; readonly onSetRichCommandDetection: Event; setContinuationPrompt(value: string): void; setPromptTerminator(value: string, lastPromptLine: string): void; @@ -232,7 +241,6 @@ export interface ICommandDetectionCapability { handleCommandExecuted(options?: IHandleCommandOptions): void; handleCommandFinished(exitCode?: number, options?: IHandleCommandOptions): void; setHasRichCommandDetection(value: boolean): void; - setPromptType(value: string): void; /** * Set the command line explicitly. * @param commandLine The command line being set. diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 83ffcf3e409..d0184883d20 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -35,8 +35,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private _handleCommandStartOptions?: IHandleCommandOptions; private _hasRichCommandDetection: boolean = false; get hasRichCommandDetection() { return this._hasRichCommandDetection; } - private _promptType: string | undefined; - get promptType(): string | undefined { return this._promptType; } private _ptyHeuristicsHooks: ICommandDetectionHeuristicsHooks; private readonly _ptyHeuristics: MandatoryMutableDisposable; @@ -75,8 +73,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe readonly onCommandInvalidated = this._onCommandInvalidated.event; private readonly _onCurrentCommandInvalidated = this._register(new Emitter()); readonly onCurrentCommandInvalidated = this._onCurrentCommandInvalidated.event; - private readonly _onPromptTypeChanged = this._register(new Emitter()); - readonly onPromptTypeChanged = this._onPromptTypeChanged.event; private readonly _onSetRichCommandDetection = this._register(new Emitter()); readonly onSetRichCommandDetection = this._onSetRichCommandDetection.event; @@ -236,11 +232,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._onSetRichCommandDetection.fire(value); } - setPromptType(value: string): void { - this._promptType = value; - this._onPromptTypeChanged.fire(value); - } - setIsCommandStorageDisabled(): void { this.__isCommandStorageDisabled = true; } diff --git a/src/vs/platform/terminal/common/capabilities/promptTypeDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/promptTypeDetectionCapability.ts new file mode 100644 index 00000000000..b64f00b4c78 --- /dev/null +++ b/src/vs/platform/terminal/common/capabilities/promptTypeDetectionCapability.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from '../../../../base/common/event.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IPromptTypeDetectionCapability, TerminalCapability } from './capabilities.js'; + +export class PromptTypeDetectionCapability extends Disposable implements IPromptTypeDetectionCapability { + readonly type = TerminalCapability.PromptTypeDetection; + + private _promptType: string | undefined; + get promptType(): string | undefined { return this._promptType; } + + private readonly _onPromptTypeChanged = this._register(new Emitter()); + readonly onPromptTypeChanged = this._onPromptTypeChanged.event; + + setPromptType(value: string): void { + this._promptType = value; + this._onPromptTypeChanged.fire(value); + } +} diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 4ea8b27cc69..4113a6bc1bc 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -8,7 +8,7 @@ import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base import { TerminalCapabilityStore } from '../capabilities/terminalCapabilityStore.js'; import { CommandDetectionCapability } from '../capabilities/commandDetectionCapability.js'; import { CwdDetectionCapability } from '../capabilities/cwdDetectionCapability.js'; -import { IBufferMarkCapability, ICommandDetectionCapability, ICwdDetectionCapability, ISerializedCommandDetectionCapability, IShellEnvDetectionCapability, TerminalCapability } from '../capabilities/capabilities.js'; +import { IBufferMarkCapability, ICommandDetectionCapability, ICwdDetectionCapability, IPromptTypeDetectionCapability, ISerializedCommandDetectionCapability, IShellEnvDetectionCapability, TerminalCapability } from '../capabilities/capabilities.js'; import { PartialCommandDetectionCapability } from '../capabilities/partialCommandDetectionCapability.js'; import { ILogService } from '../../../log/common/log.js'; import { ITelemetryService } from '../../../telemetry/common/telemetry.js'; @@ -19,6 +19,7 @@ import { URI } from '../../../../base/common/uri.js'; import { sanitizeCwd } from '../terminalEnvironment.js'; import { removeAnsiEscapeCodesFromPrompt } from '../../../../base/common/strings.js'; import { ShellEnvDetectionCapability } from '../capabilities/shellEnvDetectionCapability.js'; +import { PromptTypeDetectionCapability } from '../capabilities/promptTypeDetectionCapability.js'; /** @@ -585,7 +586,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return true; } case 'PromptType': { - this._createOrGetCommandDetection(this._terminal).setPromptType(value); + this._createOrGetPromptTypeDetection().setPromptType(value); return true; } case 'Task': { @@ -768,6 +769,15 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } return shellEnvDetection; } + + protected _createOrGetPromptTypeDetection(): IPromptTypeDetectionCapability { + let promptTypeDetection = this.capabilities.get(TerminalCapability.PromptTypeDetection); + if (!promptTypeDetection) { + promptTypeDetection = this._register(new PromptTypeDetectionCapability()); + this.capabilities.add(TerminalCapability.PromptTypeDetection, promptTypeDetection); + } + return promptTypeDetection; + } } export function deserializeMessage(message: string): string { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e9c79b32bf1..0c98331dfac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -476,7 +476,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { case TerminalCapability.CommandDetection: { e.capability.promptInputModel.setShellType(this.shellType); capabilityListeners.set(e.id, Event.any( - e.capability.onPromptTypeChanged, e.capability.promptInputModel.onDidStartInput, e.capability.promptInputModel.onDidChangeInput, e.capability.promptInputModel.onDidFinishInput @@ -486,6 +485,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { })); break; } + case TerminalCapability.PromptTypeDetection: { + capabilityListeners.set(e.id, e.capability.onPromptTypeChanged(() => { + this._labelComputer?.refreshLabel(this); + refreshShellIntegrationInfoStatus(this); + })); + break; + } } })); this._register(this.onDidChangeShellType(() => refreshShellIntegrationInfoStatus(this))); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts b/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts index 145896b533f..18d4347808b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts @@ -109,7 +109,7 @@ export class TerminalTelemetryContribution extends Disposable implements IWorkbe : 'unknown'), shellType: new TelemetryTrustedValue(getSanitizedShellType(slc)), - promptType: new TelemetryTrustedValue(commandDetection?.promptType), + promptType: new TelemetryTrustedValue(instance.capabilities.get(TerminalCapability.PromptTypeDetection)?.promptType), isCustomPtyImplementation: !!slc.customPtyImplementation, isExtensionOwnedTerminal: !!slc.isExtensionOwnedTerminal, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index ebd16411d7a..ed400202bb9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -103,7 +103,7 @@ export function refreshShellIntegrationInfoStatus(instance: ITerminalInstance) { if (seenSequences.length > 0) { detailedAdditions.push(`Seen sequences: ${seenSequences.map(e => `\`${e}\``).join(', ')}`); } - const promptType = instance.capabilities.get(TerminalCapability.CommandDetection)?.promptType; + const promptType = instance.capabilities.get(TerminalCapability.PromptTypeDetection)?.promptType; if (promptType) { detailedAdditions.push(`Prompt type: \`${promptType}\``); } diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh index 266f84eed6f..f32b74f1927 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh @@ -85,7 +85,7 @@ fi if [ -n "${P9K_SSH:-}" ] || [ -n "${P9K_TTY:-}" ]; then builtin printf '\e]633;P;PromptType=p10k\a' # Force shell integration on for p10k - typeset -g POWERLEVEL9K_TERM_SHELL_INTEGRATION=true + # typeset -g POWERLEVEL9K_TERM_SHELL_INTEGRATION=true elif [ -n "${ZSH:-}" ] && [ -n "$ZSH_VERSION" ] && (( ${+functions[omz]} )); then builtin printf '\e]633;P;PromptType=oh-my-zsh\a' elif [ -n "${STARSHIP_SESSION_KEY:-}" ]; then diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/promptTypeDetectionCapability.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/promptTypeDetectionCapability.test.ts new file mode 100644 index 00000000000..7fa53a7d82e --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/promptTypeDetectionCapability.test.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { PromptTypeDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/promptTypeDetectionCapability.js'; +import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; + +suite('PromptTypeDetectionCapability', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('should have correct capability type', () => { + const capability = store.add(new PromptTypeDetectionCapability()); + strictEqual(capability.type, TerminalCapability.PromptTypeDetection); + }); + + test('should initialize with undefined prompt type', () => { + const capability = store.add(new PromptTypeDetectionCapability()); + strictEqual(capability.promptType, undefined); + }); + + test('should set and get prompt type', () => { + const capability = store.add(new PromptTypeDetectionCapability()); + + capability.setPromptType('p10k'); + strictEqual(capability.promptType, 'p10k'); + + capability.setPromptType('posh-git'); + strictEqual(capability.promptType, 'posh-git'); + }); + + test('should fire event when prompt type changes', () => { + const capability = store.add(new PromptTypeDetectionCapability()); + let eventFiredCount = 0; + let lastEventValue: string | undefined; + + const disposable = capability.onPromptTypeChanged(value => { + eventFiredCount++; + lastEventValue = value; + }); + store.add(disposable); + + capability.setPromptType('starship'); + strictEqual(eventFiredCount, 1); + strictEqual(lastEventValue, 'starship'); + + capability.setPromptType('oh-my-zsh'); + strictEqual(eventFiredCount, 2); + strictEqual(lastEventValue, 'oh-my-zsh'); + }); +}); From c230805dcc2a2d1f1a8b83a9c5f91b04b92feae3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:29:07 -0700 Subject: [PATCH 1254/4355] Clean up for copilot --- .../terminal/common/capabilities/capabilities.ts | 5 +++-- .../contrib/terminal/browser/terminalInstance.ts | 14 ++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 687420dad81..5190dad97bd 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -51,7 +51,6 @@ export const enum TerminalCapability { * The terminal can detect the prompt type being used (e.g., p10k, posh-git). */ PromptTypeDetection, - } /** @@ -134,7 +133,9 @@ export interface ITerminalCapabilityImplMap { [TerminalCapability.BufferMarkDetection]: IBufferMarkCapability; [TerminalCapability.ShellEnvDetection]: IShellEnvDetectionCapability; [TerminalCapability.PromptTypeDetection]: IPromptTypeDetectionCapability; -}export interface ICwdDetectionCapability { +} + +export interface ICwdDetectionCapability { readonly type: TerminalCapability.CwdDetection; readonly onDidChangeCwd: Event; readonly cwds: string[]; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 0c98331dfac..18bf9a46092 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -465,6 +465,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const capabilityListeners = this._register(new DisposableMap()); this._register(this.capabilities.onDidAddCapability(e => { capabilityListeners.get(e.id)?.dispose(); + const refreshInfo = () => { + this._labelComputer?.refreshLabel(this); + refreshShellIntegrationInfoStatus(this); + }; switch (e.id) { case TerminalCapability.CwdDetection: { capabilityListeners.set(e.id, e.capability.onDidChangeCwd(e => { @@ -479,17 +483,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { e.capability.promptInputModel.onDidStartInput, e.capability.promptInputModel.onDidChangeInput, e.capability.promptInputModel.onDidFinishInput - )(() => { - this._labelComputer?.refreshLabel(this); - refreshShellIntegrationInfoStatus(this); - })); + )(refreshInfo)); break; } case TerminalCapability.PromptTypeDetection: { - capabilityListeners.set(e.id, e.capability.onPromptTypeChanged(() => { - this._labelComputer?.refreshLabel(this); - refreshShellIntegrationInfoStatus(this); - })); + capabilityListeners.set(e.id, e.capability.onPromptTypeChanged(refreshInfo)); break; } } From c648e057d19326ddd71c5688556e0a2e31dc753e Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 17 Oct 2025 10:31:31 -0700 Subject: [PATCH 1255/4355] Include output in task results even if no problems are matched (#271851) --- .../terminalContrib/chatAgentTools/browser/taskHelpers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index 879c9bda3c8..29971d1984d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -265,9 +265,10 @@ export async function taskProblemPollFn(execution: IExecution, token: Cancellati } } if (problemList.length === 0) { + const lastTenLines = execution.getOutput().split('\n').filter(line => line !== '').slice(-10).join('\n'); return { state: OutputMonitorState.Idle, - output: 'The task succeeded with no problems.', + output: `Task completed with output:\n${lastTenLines}`, }; } return { From eff5d8392a6f583d019b0b09a29876c58d88ca2c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:37:07 -0700 Subject: [PATCH 1256/4355] Initial support for opening custom resource from sessions list --- .../chatSessions/view/sessionsViewPane.ts | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 25922c11aa2..4a109d36af6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -204,9 +204,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Updates the empty state message based on current tree data. - * Uses the tree's existing data to avoid redundant provider calls. - */ + * Updates the empty state message based on current tree data. + * Uses the tree's existing data to avoid redundant provider calls. + */ private updateEmptyState(): void { try { const newEmptyState = this.isEmpty(); @@ -220,9 +220,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Refreshes the tree data with progress indication. - * Shows a progress indicator while the tree updates its children from the provider. - */ + * Refreshes the tree data with progress indication. + * Shows a progress indicator while the tree updates its children from the provider. + */ private async refreshTreeWithProgress(): Promise { if (!this.tree) { return; @@ -248,9 +248,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Loads initial tree data with progress indication. - * Shows a progress indicator while the tree loads data from the provider. - */ + * Loads initial tree data with progress indication. + * Shows a progress indicator while the tree loads data from the provider. + */ private async loadDataWithProgress(): Promise { if (!this.tree) { return; @@ -305,7 +305,7 @@ export class SessionsViewPane extends ViewPane { return null; } - return ChatSessionUri.forSession(element.provider.chatSessionType, element.id); + return element.resource; }; this.tree = this.instantiationService.createInstance( @@ -456,6 +456,11 @@ export class SessionsViewPane extends ViewPane { return; } + if (session.resource.scheme !== ChatSessionUri.scheme) { + await this.openerService.open(session.resource); + return; + } + try { // Check first if we already have an open editor for this session const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); From d2418eceaafaa0c5600b18271410b31ad43c7ea2 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:38:58 -0700 Subject: [PATCH 1257/4355] Revert weird formats --- .../chatSessions/view/sessionsViewPane.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 4a109d36af6..790c3c959c9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -204,9 +204,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Updates the empty state message based on current tree data. - * Uses the tree's existing data to avoid redundant provider calls. - */ + * Updates the empty state message based on current tree data. + * Uses the tree's existing data to avoid redundant provider calls. + */ private updateEmptyState(): void { try { const newEmptyState = this.isEmpty(); @@ -220,9 +220,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Refreshes the tree data with progress indication. - * Shows a progress indicator while the tree updates its children from the provider. - */ + * Refreshes the tree data with progress indication. + * Shows a progress indicator while the tree updates its children from the provider. + */ private async refreshTreeWithProgress(): Promise { if (!this.tree) { return; @@ -248,9 +248,9 @@ export class SessionsViewPane extends ViewPane { } /** - * Loads initial tree data with progress indication. - * Shows a progress indicator while the tree loads data from the provider. - */ + * Loads initial tree data with progress indication. + * Shows a progress indicator while the tree loads data from the provider. + */ private async loadDataWithProgress(): Promise { if (!this.tree) { return; From 89cbc044e0a930830b4276b2f1d2d2c9027124aa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:53:13 -0700 Subject: [PATCH 1258/4355] Add missing monaco-hover --- src/vs/editor/browser/services/hoverService/hover.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 70299bf9975..597738d3069 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -30,7 +30,7 @@ border-bottom: none; } -.workbench-hover.compact { +.monaco-hover.workbench-hover.compact { font-size: 12px; } From 5593ecd8e2863e2bd6a85a9e318e7acb2055f642 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 17 Oct 2025 11:12:37 -0700 Subject: [PATCH 1259/4355] Add support for copying math source from katex blocks (#271590) --- .../chat/browser/actions/chatCopyActions.ts | 59 +++++++++++++++++++ .../contrib/chat/browser/chatWidget.ts | 8 ++- .../contrib/chat/common/chatContextKeys.ts | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index f690000ec9f..70d7f9f0537 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -87,4 +87,63 @@ export function registerChatCopyActions() { await clipboardService.writeText(text); } }); + + registerAction2(class CopyKatexMathSourceAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.copyKatexMathSource', + title: localize2('chat.copyKatexMathSource.label', "Copy Math Source"), + f1: false, + category: CHAT_CATEGORY, + menu: { + id: MenuId.ChatContext, + group: 'copy', + when: ChatContextKeys.isKatexMathElement, + } + }); + } + + async run(accessor: ServicesAccessor, ...args: unknown[]) { + const chatWidgetService = accessor.get(IChatWidgetService); + const clipboardService = accessor.get(IClipboardService); + + const widget = chatWidgetService.lastFocusedWidget; + let item = args[0] as ChatTreeItem | undefined; + if (!isChatTreeItem(item)) { + item = widget?.getFocus(); + if (!item) { + return; + } + } + + // Try to find a KaTeX element from the selection or active element + let selectedElement: Node | null = null; + + // If there is a selection, and focus is inside the widget, extract the inner KaTeX element. + const activeElement = dom.getActiveElement(); + const nativeSelection = dom.getActiveWindow().getSelection(); + if (widget && nativeSelection && nativeSelection.rangeCount > 0 && dom.isAncestor(activeElement, widget.domNode)) { + const range = nativeSelection.getRangeAt(0); + selectedElement = range.commonAncestorContainer; + + // If it's a text node, get its parent element + if (selectedElement.nodeType === Node.TEXT_NODE) { + selectedElement = selectedElement.parentElement; + } + } + + // Otherwise, fallback to querying from the active element + if (!selectedElement) { + selectedElement = activeElement?.querySelector('.katex') ?? null; + } + + // Extract the LaTeX source from the annotation element + const katexElement = dom.isHTMLElement(selectedElement) ? selectedElement.closest('.katex') : null; + const annotation = katexElement?.querySelector('annotation[encoding="application/x-tex"]'); + if (annotation) { + const latexSource = annotation.textContent || ''; + await clipboardService.writeText(latexSource); + } + } + }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index c570ca99cce..be87a6d7135 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1963,8 +1963,14 @@ export class ChatWidget extends Disposable implements IChatWidget { e.browserEvent.stopPropagation(); const selected = e.element; + + // Check if the context menu was opened on a KaTeX element + const target = e.browserEvent.target as HTMLElement; + const isKatexElement = target.closest('.katex') !== null; + const scopedContextKeyService = this.contextKeyService.createOverlay([ - [ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] + [ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered], + [ChatContextKeys.isKatexMathElement.key, isKatexElement] ]); this.contextMenuService.showContextMenu({ menuId: MenuId.ChatContext, diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index b6a505478ae..b90624bb858 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -92,6 +92,7 @@ export namespace ChatContextKeys { export const sessionType = new RawContextKey('chatSessionType', '', { type: 'string', description: localize('chatSessionType', "The type of the current chat session item.") }); export const isHistoryItem = new RawContextKey('chatIsHistoryItem', false, { type: 'boolean', description: localize('chatIsHistoryItem', "True when the chat session item is from history.") }); export const isActiveSession = new RawContextKey('chatIsActiveSession', false, { type: 'boolean', description: localize('chatIsActiveSession', "True when the chat session is currently active (not deletable).") }); + export const isKatexMathElement = new RawContextKey('chatIsKatexMathElement', false, { type: 'boolean', description: localize('chatIsKatexMathElement', "True when focusing a KaTeX math element.") }); } export namespace ChatContextKeyExprs { From d0806c44a8c030894e8f6568ac5f851568c22b82 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 17 Oct 2025 15:39:42 -0400 Subject: [PATCH 1260/4355] persist `forcePersist` background terminals (#271983) --- src/vs/platform/terminal/common/terminal.ts | 7 ++++ .../terminal/common/terminalProcess.ts | 1 + src/vs/platform/terminal/node/ptyService.ts | 17 ++++++---- .../contrib/terminal/browser/terminal.ts | 2 +- .../terminal/browser/terminalService.ts | 32 +++++++++++-------- .../common/remote/remoteTerminalChannel.ts | 3 +- .../electron-browser/localTerminalBackend.ts | 3 +- .../chat/browser/terminalChatService.ts | 12 ------- .../browser/toolTerminalCreator.ts | 1 + 9 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index a2132c2e911..5d78feb15ec 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -169,6 +169,7 @@ export type ITerminalTabLayoutInfoById = IRawTerminalTabLayoutInfo; export interface IRawTerminalsLayoutInfo { tabs: IRawTerminalTabLayoutInfo[]; + background: T[] | null; } export interface IPtyHostAttachTarget { @@ -592,6 +593,12 @@ export interface IShellLaunchConfig { */ hideFromUser?: boolean; + /** + * Whether to force the terminal to persist across sessions regardless of the other + * launch config, like `hideFromUser`. + */ + forcePersist?: boolean; + /** * Whether this terminal is not a terminal that the user directly created and uses, but rather * a terminal used to drive some VS Code feature. diff --git a/src/vs/platform/terminal/common/terminalProcess.ts b/src/vs/platform/terminal/common/terminalProcess.ts index 17c30159562..917db9710d8 100644 --- a/src/vs/platform/terminal/common/terminalProcess.ts +++ b/src/vs/platform/terminal/common/terminalProcess.ts @@ -32,6 +32,7 @@ export interface IWorkspaceFolderData { export interface ISetTerminalLayoutInfoArgs { workspaceId: string; tabs: ITerminalTabLayoutInfoById[]; + background: number[] | null; } export interface IGetTerminalLayoutInfoArgs { diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 1ddab07849a..ba8a09b809f 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -550,8 +550,9 @@ export class PtyService extends Disposable implements IPtyService { const doneSet: Set = new Set(); const expandedTabs = await Promise.all(layout.tabs.map(async tab => this._expandTerminalTab(args.workspaceId, tab, doneSet))); const tabs = expandedTabs.filter(t => t.terminals.length > 0); + const expandedBackground = (await Promise.all(layout.background?.map(b => this._expandTerminalInstance(args.workspaceId, b, doneSet)) ?? [])).filter(b => b.terminal !== null).map(b => b.terminal); performance.mark('code/didGetTerminalLayoutInfo'); - return { tabs }; + return { tabs, background: expandedBackground }; } performance.mark('code/didGetTerminalLayoutInfo'); return undefined; @@ -567,22 +568,24 @@ export class PtyService extends Disposable implements IPtyService { }; } - private async _expandTerminalInstance(workspaceId: string, t: ITerminalInstanceLayoutInfoById, doneSet: Set): Promise> { + private async _expandTerminalInstance(workspaceId: string, t: ITerminalInstanceLayoutInfoById | number, doneSet: Set): Promise> { + const hasLayout = typeof t !== 'number'; + const ptyId = hasLayout ? t.terminal : t; try { - const oldId = this._getRevivingProcessId(workspaceId, t.terminal); + const oldId = this._getRevivingProcessId(workspaceId, ptyId); const revivedPtyId = this._revivedPtyIdMap.get(oldId)?.newId; this._logService.info(`Expanding terminal instance, old id ${oldId} -> new id ${revivedPtyId}`); this._revivedPtyIdMap.delete(oldId); - const persistentProcessId = revivedPtyId ?? t.terminal; + const persistentProcessId = revivedPtyId ?? ptyId; if (doneSet.has(persistentProcessId)) { throw new Error(`Terminal ${persistentProcessId} has already been expanded`); } doneSet.add(persistentProcessId); const persistentProcess = this._throwIfNoPty(persistentProcessId); - const processDetails = persistentProcess && await this._buildProcessDetails(t.terminal, persistentProcess, revivedPtyId !== undefined); + const processDetails = persistentProcess && await this._buildProcessDetails(ptyId, persistentProcess, revivedPtyId !== undefined); return { terminal: { ...processDetails, id: persistentProcessId }, - relativeSize: t.relativeSize + relativeSize: hasLayout ? t.relativeSize : 0 }; } catch (e) { this._logService.warn(`Couldn't get layout info, a terminal was probably disconnected`, e.message); @@ -592,7 +595,7 @@ export class PtyService extends Disposable implements IPtyService { // this will be filtered out and not reconnected return { terminal: null, - relativeSize: t.relativeSize + relativeSize: hasLayout ? t.relativeSize : 0 }; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b84e545546e..8517883a550 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -397,7 +397,7 @@ export interface ITerminalService extends ITerminalInstanceHost { * @param suppressSetActive Do not set the active instance when there is only one terminal * @param forceSaveState Used when the window is shutting down and we need to reveal and save hideFromUser terminals */ - showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean, forceSaveState?: boolean): Promise; + showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean): Promise; revealActiveTerminal(preserveFocus?: boolean): Promise; moveToEditor(source: ITerminalInstance, group?: GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | AUX_WINDOW_GROUP_TYPE): void; moveIntoNewEditor(source: ITerminalInstance): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 1c8d951c75a..38ba7d2addf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -467,9 +467,10 @@ export class TerminalService extends Disposable implements ITerminalService { mark('code/terminal/willGetTerminalLayoutInfo'); const layoutInfo = await localBackend.getTerminalLayoutInfo(); mark('code/terminal/didGetTerminalLayoutInfo'); - if (layoutInfo && layoutInfo.tabs.length > 0) { + if (layoutInfo && (layoutInfo.tabs.length > 0 || layoutInfo?.background?.length)) { mark('code/terminal/willRecreateTerminalGroups'); this._reconnectedTerminalGroups = this._recreateTerminalGroups(layoutInfo); + this._backgroundedTerminalInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); mark('code/terminal/didRecreateTerminalGroups'); } // now that terminals have been restored, @@ -505,6 +506,19 @@ export class TerminalService extends Disposable implements ITerminalService { return Promise.all(groupPromises).then(result => result.filter(e => !!e) as ITerminalGroup[]); } + private async _reviveBackgroundTerminalInstances(bgTerminals: (IPtyHostAttachTarget | null)[]): Promise { + const instances: ITerminalInstance[] = []; + for (const bg of bgTerminals) { + const attachPersistentProcess = bg; + if (!attachPersistentProcess) { + continue; + } + const instance = await this.createTerminal({ config: { attachPersistentProcess, hideFromUser: true, forcePersist: true }, location: TerminalLocation.Panel }); + instances.push(instance); + } + return instances; + } + private async _recreateTerminalGroup(tabLayout: IRawTerminalTabLayoutInfo, terminalLayouts: IRawTerminalInstanceLayoutInfo[]): Promise { let lastInstance: Promise | undefined; for (const terminalLayout of terminalLayouts) { @@ -692,12 +706,6 @@ export class TerminalService extends Disposable implements ITerminalService { } } - private async _saveStateNow(): Promise { - const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs }; - await this._primaryBackend?.setTerminalLayoutInfo(state); - } - @debounce(500) private _saveState(): void { // Avoid saving state when shutting down as that would override process state to be revived @@ -707,7 +715,9 @@ export class TerminalService extends Disposable implements ITerminalService { if (!this._terminalConfigurationService.config.enablePersistentSessions) { return; } - this._saveStateNow(); + const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); + const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; + this._primaryBackend?.setTerminalLayoutInfo(state); } @debounce(500) @@ -1164,7 +1174,7 @@ export class TerminalService extends Disposable implements ITerminalService { } } - public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean, forceSaveState?: boolean): Promise { + public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean): Promise { const index = this._backgroundedTerminalInstances.indexOf(instance); if (index === -1) { return; @@ -1182,10 +1192,6 @@ export class TerminalService extends Disposable implements ITerminalService { this._terminalGroupService.setActiveInstanceByIndex(0); } - if (forceSaveState) { - // Skips the debounce of _saveState in case it's shutting down - await this._saveStateNow(); - } this._onDidChangeInstances.fire(); } diff --git a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts index e54883f9aee..1c3ff56809f 100644 --- a/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts @@ -271,7 +271,8 @@ export class RemoteTerminalChannelClient implements IPtyHostController { const workspace = this._workspaceContextService.getWorkspace(); const args: ISetTerminalLayoutInfoArgs = { workspaceId: workspace.id, - tabs: layout ? layout.tabs : [] + tabs: layout ? layout.tabs : [], + background: layout ? layout.background : null }; return this._channel.call(RemoteTerminalChannelRequest.SetTerminalLayoutInfo, args); } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts index bac58b90108..e5e629a5612 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts @@ -300,7 +300,8 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke async setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise { const args: ISetTerminalLayoutInfoArgs = { workspaceId: this._getWorkspaceId(), - tabs: layoutInfo ? layoutInfo.tabs : [] + tabs: layoutInfo ? layoutInfo.tabs : [], + background: layoutInfo ? layoutInfo.background : null }; await this._proxy.setTerminalLayoutInfo(args); // Store in the storage service as well to be used when reviving processes as normally this diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index e80aa6a64c0..fef463dffdc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -8,9 +8,6 @@ import { Disposable, DisposableMap, IDisposable } from '../../../../../base/comm import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITerminalChatService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; -import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; -import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; -import { PromptInputState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; /** * Used to manage chat tool invocations and the underlying terminal instances they create/use. @@ -36,19 +33,10 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ @ILogService private readonly _logService: ILogService, @ITerminalService private readonly _terminalService: ITerminalService, @IStorageService private readonly _storageService: IStorageService, - @ILifecycleService private readonly _lifecycleService: ILifecycleService ) { super(); this._restoreFromStorage(); - this._register(this._lifecycleService.onBeforeShutdown(async e => { - // Show all hidden terminals before shutdown so they are restored - for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId) { - if (this.isBackgroundTerminal(toolSessionId) && (instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.state === PromptInputState.Execute || instance.hasChildProcesses)) { - await this._terminalService.showBackgroundTerminal(instance, true, true); - } - } - })); } registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index a2dc81323d1..0cfb2df160b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -140,6 +140,7 @@ export class ToolTerminalCreator { const config: IShellLaunchConfig = { icon: ThemeIcon.fromId(Codicon.chatSparkle.id), hideFromUser: true, + forcePersist: true, env: { // Avoid making `git diff` interactive when called from copilot GIT_PAGER: 'cat', From dd797c727d2a057c235d9b750b9f3d9e101b6238 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Fri, 17 Oct 2025 12:43:28 -0700 Subject: [PATCH 1261/4355] Use built-in plan mode (#271853) --- .github/chatmodes/Plan.chatmode.md | 35 ----------------------------- .github/prompts/plan-deep.prompt.md | 6 +++-- .github/prompts/plan.prompt.md | 18 ++------------- .vscode/settings.json | 2 +- 4 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 .github/chatmodes/Plan.chatmode.md diff --git a/.github/chatmodes/Plan.chatmode.md b/.github/chatmodes/Plan.chatmode.md deleted file mode 100644 index 552daed6373..00000000000 --- a/.github/chatmodes/Plan.chatmode.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -description: Research and draft an implementation plan -tools: ['executePrompt', 'usages', 'problems', 'githubRepo', 'github.vscode-pull-request-github/activePullRequest', 'search', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'fetch'] ---- -You are pairing with the user to create a clear, detailed, and actionable plan for the given task, iterating through a of gathering context and drafting the plan for review. - - -Comprehensive context gathering for planning following : -1. Context gathering and research: - - MUST run `execute_prompt` tool: Instruct the agent to work autonomously without pausing for user feedback, following to gather context and writing a complete to return to you. - - If `execute_prompt` tool is NOT available: Run via tools yourself. -2. Present the plan to the user for feedback and refinement: - - Highlights key areas of ambiguity with specific questions and suggestions. - - MANDATORY: Pause for user feedback! - - Handle feedback: Refine the plan after doing further context gathering and research. - - - -Comprehensive information gathering using read-only tools: -- Examine existing codebase structure, architecture, documentation, and dependencies -- Start with high-level code searches before reading specific files -- Prioritize parallel tool calls for efficiency -- Analyze gaps between current state and desired outcome -- Assess potential integration points and conflicts - - - -- Style: - - Clear, concise, non-repetitive, and high-signal; optimized for quick human review - - Rich in references to specific/related files, symbols, and documentation; while avoiding excessive code snippets - - Tailored to the task and context: Higher complexity requires more detail, higher ambiguity more alternative approaches, etc. -- Briefly summarize problem understanding and proposed technical approach -- Implementation plan broken down into clear, iterative steps as ordered markdown list -- Call out any steps that are too vague or ambiguous to act on - diff --git a/.github/prompts/plan-deep.prompt.md b/.github/prompts/plan-deep.prompt.md index 351a0ebb252..3c3a8402d50 100644 --- a/.github/prompts/plan-deep.prompt.md +++ b/.github/prompts/plan-deep.prompt.md @@ -2,6 +2,8 @@ mode: Plan description: Clarify before planning in more detail --- -Before doing your research look up related code (max 5 tool calls!) to get a high-level overview, then ask 3 clarifying questions. Once the user answered, go to *Context gathering and research*. +Before doing your research workflow, gather preliminary context using #executePrompt (instructed to use max 5 tool calls) to get a high-level overview. -Be extra detailed in your planning draft. +Then ask 3 clarifying questions and PAUSE for the user to answer them. + +AFTER the user has answered, start the . Add extra details to your planning draft. diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index bff5fa18743..53a5fa9b0fb 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,19 +1,5 @@ --- -mode: agent +mode: Plan description: 'Start planning' -tools: ['runNotebooks/getNotebookSummary', 'runNotebooks/readNotebookCellOutput', 'search', 'runCommands/getTerminalOutput', 'runCommands/terminalSelection', 'runCommands/terminalLastCommand', 'github/github-mcp-server/get_issue', 'github/github-mcp-server/get_issue_comments', 'github/github-mcp-server/get_me', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos'] --- -Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: -* Understand the context of the bug or feature by reading the issue description and comments. -* Understand the codebase by reading the relevant instruction files. -* If its a bug, then identify the root cause of the bug, and explain this to the user. - -Based on your above understanding generate a plan to fix the bug or add the new feature. -Ensure the plan consists of a Markdown document that has the following sections: - -* Overview: A brief description of the bug/feature. -* Root Cause: A detailed explanation of the root cause of the bug, including any relevant code snippets or references to the codebase. (only if it's a bug) -* Requirements: A list of requirements to resolve the bug or add the new feature. -* Implementation Steps: A detailed list of steps to implement the bug fix or new feature. - -Remember, do not make any code edits, just generate a plan. Use thinking and reasoning skills to outline the steps needed to achieve the desired outcome. +Start planning. diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a11ccbf3b6..89cb313781d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -216,8 +216,8 @@ "editor.aiStats.enabled": true, // Team selfhosting on ai stats "chat.emptyState.history.enabled": true, + "github.copilot.chat.executePrompt.enabled": true, "chat.agentSessionsViewLocation": "view", - "github.copilot.chat.advanced.taskTools.enabled": true, "chat.promptFilesRecommendations": { "plan-fast": true, "plan-deep": true From 65d6e350b119f77c3fc42e83c0fae996a15fcd97 Mon Sep 17 00:00:00 2001 From: Michael Lively <12552271+Yoyokrazy@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:07:42 -0700 Subject: [PATCH 1262/4355] add agent behavior label (#271986) * agent behavior label * Update .github/commands.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/commands.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/commands.json b/.github/commands.json index daaeebf243c..e3118602d31 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -654,5 +654,14 @@ ], "action": "updateLabels", "addLabel": "*edu" + }, + { + "type": "label", + "name": "~agent-behavior", + "action": "close", + "reason": "not_planned", + "addLabel": "agent-behavior", + "removeLabel": "~agent-behavior", + "comment": "Unfortunately I think you are hitting a AI quality issue that is not actionable enough for us to track a bug. We would recommend that you try other available models and look at the [Tips and tricks for Copilot in VS Code](https://code.visualstudio.com/docs/copilot/copilot-tips-and-tricks) doc page.\n\nWe are constantly improving AI quality in every release, thank you for the feedback! If you believe this is a technical bug, we recommend you report a new issue including logs described on the [Copilot Issues](https://github.com/microsoft/vscode/wiki/Copilot-Issues) wiki page." } ] From aceb52a963b04e54f47fc7bbd15928c99d162e06 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 17 Oct 2025 13:11:52 -0700 Subject: [PATCH 1263/4355] Update xterm --- package-lock.json | 96 ++++++++++++++++++------------------ package.json | 20 ++++---- remote/package-lock.json | 96 ++++++++++++++++++------------------ remote/package.json | 20 ++++---- remote/web/package-lock.json | 88 ++++++++++++++++----------------- remote/web/package.json | 18 +++---- 6 files changed, 169 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75c113f18e8..1b186c8bbeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,16 +27,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/headless": "^5.6.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/headless": "^5.6.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -3619,30 +3619,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.102", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", - "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", + "version": "0.2.0-beta.114", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", + "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", - "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", + "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", - "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", + "version": "0.10.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", + "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -3652,64 +3652,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.25", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", - "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", + "version": "0.2.0-beta.37", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", + "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", - "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", + "version": "0.16.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", + "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", - "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", + "version": "0.14.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", + "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", - "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", + "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", - "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", + "version": "0.19.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", + "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.119.tgz", - "integrity": "sha512-kZW0Nuu0Rhm/lov6rp+LTbf0rWur/cM2zAUiU/IxpgR+tCJlPT5stSFXBvc+AzaNNHuvpHG8ko2YUJTmqdWp2A==", + "version": "5.6.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.131.tgz", + "integrity": "sha512-LUaXCWsBj/RJfNTO89z1jzZZ8pnANWTL8M2TBmimsFpz3+KbkOmrVt+XfpWSCY0Y1HmQqI1/xjBCCSwN2Ij3hA==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", - "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", + "version": "5.6.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", + "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", "license": "MIT" }, "node_modules/@xtuc/ieee754": { diff --git a/package.json b/package.json index 299c470a67a..e240259c573 100644 --- a/package.json +++ b/package.json @@ -88,16 +88,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/headless": "^5.6.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/headless": "^5.6.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "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 749a327de23..238a0284ce0 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -20,16 +20,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/headless": "^5.6.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/headless": "^5.6.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -238,30 +238,30 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.102", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", - "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", + "version": "0.2.0-beta.114", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", + "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", - "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", + "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", - "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", + "version": "0.10.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", + "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -271,64 +271,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.25", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", - "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", + "version": "0.2.0-beta.37", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", + "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", - "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", + "version": "0.16.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", + "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", - "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", + "version": "0.14.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", + "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", - "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", + "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", - "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", + "version": "0.19.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", + "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.119.tgz", - "integrity": "sha512-kZW0Nuu0Rhm/lov6rp+LTbf0rWur/cM2zAUiU/IxpgR+tCJlPT5stSFXBvc+AzaNNHuvpHG8ko2YUJTmqdWp2A==", + "version": "5.6.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.131.tgz", + "integrity": "sha512-LUaXCWsBj/RJfNTO89z1jzZZ8pnANWTL8M2TBmimsFpz3+KbkOmrVt+XfpWSCY0Y1HmQqI1/xjBCCSwN2Ij3hA==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", - "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", + "version": "5.6.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", + "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", "license": "MIT" }, "node_modules/agent-base": { diff --git a/remote/package.json b/remote/package.json index 4572aeabb03..268bdf55729 100644 --- a/remote/package.json +++ b/remote/package.json @@ -15,16 +15,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/headless": "^5.6.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/headless": "^5.6.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "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 0165458a666..fab2167c05e 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.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", @@ -92,30 +92,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.102", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.102.tgz", - "integrity": "sha512-HDI5T7k7UXEIWKMOmKZAArhZcpmz/J2tpF8DNpTVNkhOpIKWwGbhaFuhPQBuNtnfxtoR5sqVkr/tMIji4jfC+A==", + "version": "0.2.0-beta.114", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", + "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.119.tgz", - "integrity": "sha512-mFBAU8SysOqkBt4KYetwY5OEzIziRG4ECImtYgbx8Z6AIZ8NSqSE8/f12TOZ+vWkGwHrx12odsM94Wllpjz2dQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", + "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.119.tgz", - "integrity": "sha512-CjKsvXzRwDSDaTGz1jCX2tMS3X6BtBiN/nWx4xOYVi7QAQ516tUOup/oUhK4x7lKIYv5CrPKWfX9JzQ/Bzou5w==", + "version": "0.10.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", + "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -125,58 +125,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.25", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.25.tgz", - "integrity": "sha512-snRn6MFbX2GhzVf0+yFpVJ0qe/A94VQ7swBmFEa6JwV8kEuzkmjeU5dPhztKSXp5KFnJV9kgj0kTs3J6Y5GlnQ==", + "version": "0.2.0-beta.37", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", + "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.119.tgz", - "integrity": "sha512-RiWmnW1b1jvPebN/FtmXyKaXwcNPp34WJT52jE2QP101Mx7wIsTwketoqHkRAqX7fqV9YZ/4ZoJKjtW9IdroEg==", + "version": "0.16.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", + "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.119.tgz", - "integrity": "sha512-dLFW+wKImCoLIAGLMgm6ReiaY1oxSEsIaT0WoOIiA30I3vTwi2gM7SbqIv7W8WsD4GIDHETUTPEtrEP2t4i7ZQ==", + "version": "0.14.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", + "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.119.tgz", - "integrity": "sha512-R7UNkxnjPIcYIQK6EHyG+SBP/I0tbqobwAYLbeahvf7E6okcQzirFYgTUi4vY3f9wRJTDx/cS7Vh4XJG55e4nQ==", + "version": "0.9.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", + "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.119.tgz", - "integrity": "sha512-j8usRuPfHIbjPoPTGbeUehuEslYIVDwfJb+2clM79j0hal8rvXVvX1/GOpqVK7LJtWbg+EaFZZo8EvUsJMiQpg==", + "version": "0.19.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", + "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.119" + "@xterm/xterm": "^5.6.0-beta.131" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.119", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.119.tgz", - "integrity": "sha512-DGuLxADtzHBSuD1YK7URgkUglPsayWlE722QMVknvfm72PdbzSqu6TPzsN7KeFsFHN7ajYjuEz9B/RaIQoLC8Q==", + "version": "5.6.0-beta.131", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", + "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", "license": "MIT" }, "node_modules/commander": { diff --git a/remote/web/package.json b/remote/web/package.json index aa642d61472..8494a8b5090 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.1.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.102", - "@xterm/addon-image": "^0.9.0-beta.119", - "@xterm/addon-ligatures": "^0.10.0-beta.119", - "@xterm/addon-progress": "^0.2.0-beta.25", - "@xterm/addon-search": "^0.16.0-beta.119", - "@xterm/addon-serialize": "^0.14.0-beta.119", - "@xterm/addon-unicode11": "^0.9.0-beta.119", - "@xterm/addon-webgl": "^0.19.0-beta.119", - "@xterm/xterm": "^5.6.0-beta.119", + "@xterm/addon-clipboard": "^0.2.0-beta.114", + "@xterm/addon-image": "^0.9.0-beta.131", + "@xterm/addon-ligatures": "^0.10.0-beta.131", + "@xterm/addon-progress": "^0.2.0-beta.37", + "@xterm/addon-search": "^0.16.0-beta.131", + "@xterm/addon-serialize": "^0.14.0-beta.131", + "@xterm/addon-unicode11": "^0.9.0-beta.131", + "@xterm/addon-webgl": "^0.19.0-beta.131", + "@xterm/xterm": "^5.6.0-beta.131", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", From 76c33ab5a417b3f630520b1c643a4f0006d36d33 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 17 Oct 2025 16:32:40 -0400 Subject: [PATCH 1264/4355] add View chat terminals action (#271758) --- .../contrib/terminal/browser/terminal.ts | 8 +- .../terminal/browser/terminalService.ts | 8 +- .../terminal/common/terminalContextKey.ts | 3 + .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 116 +++++++++++++++++- .../chat/browser/terminalChatService.ts | 22 +++- 6 files changed, 152 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 8517883a550..3049edd9ee5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -127,7 +127,13 @@ export interface ITerminalChatService { */ getTerminalInstanceByToolSessionId(terminalToolSessionId: string): ITerminalInstance | undefined; - isBackgroundTerminal(terminalToolSessionId: string): boolean; + /** + * Returns the list of terminal instances that have been registered with a tool session id. + * This is used for surfacing tool-driven/background terminals in UI (eg. quick picks). + */ + getToolSessionTerminalInstances(): readonly ITerminalInstance[]; + + isBackgroundTerminal(terminalToolSessionId?: string): boolean; } /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 38ba7d2addf..ee1546ab0c4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -1005,7 +1005,13 @@ export class TerminalService extends Disposable implements ITerminalService { const instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Panel); this._backgroundedTerminalInstances.push(instance); this._backgroundedTerminalDisposables.set(instance.instanceId, [ - instance.onDisposed(this._onDidDisposeInstance.fire, this._onDidDisposeInstance) + instance.onDisposed(instance => { + const idx = this._backgroundedTerminalInstances.indexOf(instance); + if (idx !== -1) { + this._backgroundedTerminalInstances.splice(idx, 1); + } + this._onDidDisposeInstance.fire(instance); + }) ]); this._terminalHasBeenCreated.set(true); return instance; diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 23512aa1aa2..9db5585cc44 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -140,6 +140,9 @@ export namespace TerminalContextKeys { /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */ export const terminalShellIntegrationEnabled = new RawContextKey(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal")); + /** Whether there is at least one tool terminal (created via run_in_terminal). */ + export const hasToolTerminal = new RawContextKey('hasToolTerminal', false, localize('hasToolTerminalContextKey', "Whether there is at least one tool terminal (created via run_in_terminal).")); + /** Whether a speech to text (dictation) session is in progress. */ export const terminalDictationInProgress = new RawContextKey(TerminalContextKeyStrings.DictationInProgress, false); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 5f16923d086..f5333183b09 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -18,6 +18,7 @@ export const enum TerminalChatCommandId { InsertFirstCommand = 'workbench.action.terminal.chat.insertFirstCommand', ViewInChat = 'workbench.action.terminal.chat.viewInChat', RerunRequest = 'workbench.action.terminal.chat.rerunRequest', + ViewChatTerminals = 'workbench.action.terminal.chat.viewChatTerminals', } export const MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR = MenuId.for('terminalChatWidget'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 8271c7cdb5d..bffdf386498 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -6,20 +6,24 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { localize2 } from '../../../../../nls.js'; -import { MenuId } from '../../../../../platform/actions/common/actions.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { IChatWidgetService } from '../../../chat/browser/chat.js'; +import { ChatViewId, IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatContextKeys } from '../../../chat/common/chatContextKeys.js'; import { IChatService } from '../../../chat/common/chatService.js'; import { ChatAgentLocation } from '../../../chat/common/constants.js'; import { AbstractInline1ChatAction } from '../../../inlineChat/browser/inlineChatActions.js'; -import { isDetachedTerminalInstance } from '../../../terminal/browser/terminal.js'; +import { isDetachedTerminalInstance, ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { registerActiveXtermAction } from '../../../terminal/browser/terminalActions.js'; import { TerminalContextMenuGroup } from '../../../terminal/browser/terminalMenus.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; +import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { getIconId } from '../../../terminal/browser/terminalIcon.js'; import { TerminalChatController } from './terminalChatController.js'; +import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; registerActiveXtermAction({ id: TerminalChatCommandId.Start, @@ -297,3 +301,109 @@ registerActiveXtermAction({ contr?.viewInChat(); } }); + +registerAction2(class ShowChatTerminalsAction extends Action2 { + constructor() { + super({ + id: TerminalChatCommandId.ViewChatTerminals, + title: localize2('viewChatTerminals', 'View Chat Terminals'), + category: localize2('terminalCategory2', 'Terminal'), + f1: true, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(TerminalContextKeys.hasToolTerminal, ContextKeyExpr.equals('view', ChatViewId)), + group: 'terminal', + order: 0, + isHiddenByDefault: true + }] + }); + } + + run(accessor: ServicesAccessor): Promise | void { + const terminalService = accessor.get(ITerminalService); + const groupService = accessor.get(ITerminalGroupService); + const editorService = accessor.get(ITerminalEditorService); + const terminalChatService = accessor.get(ITerminalChatService); + const quickInputService = accessor.get(IQuickInputService); + const instantiationService = accessor.get(IInstantiationService); + + const visible = new Set([...groupService.instances, ...editorService.instances]); + const toolInstances = new Set(terminalChatService.getToolSessionTerminalInstances()); + + if (toolInstances.size === 0) { + return; + } + + const all = new Map(); + + for (const i of toolInstances) { + all.set(i.instanceId, { instance: i, isBackground: !visible.has(i) }); + } + + const items: IQuickPickItem[] = []; + interface IItemMeta { + label: string; + description: string | undefined; + detail: string | undefined; + id: string; + isBackground: boolean; + } + const hiddenLocalized = localize2('chatTerminal.hidden', 'Hidden').value; + const lastCommandLocalized = (command: string) => localize2('chatTerminal.lastCommand', 'Last: {0}', command).value; + + const metas: IItemMeta[] = []; + for (const { instance, isBackground } of all.values()) { + const iconId = instantiationService.invokeFunction(getIconId, instance); + const label = `$(${iconId}) ${instance.title}`; + const lastCommand = instance.capabilities.get(TerminalCapability.CommandDetection)?.commands.at(-1)?.command; + metas.push({ + label, + description: isBackground ? hiddenLocalized : undefined, + detail: lastCommand ? lastCommandLocalized(lastCommand) : undefined, + id: String(instance.instanceId), + isBackground + }); + } + + // Sort: hidden first (stable by label inside each group) + metas.sort((a, b) => { + if (a.isBackground !== b.isBackground) { + return a.isBackground ? -1 : 1; + } + return a.label.localeCompare(b.label); + }); + + for (const m of metas) { + items.push({ + label: m.label, + description: m.description, + detail: m.detail, + id: m.id + }); + } + + const qp = quickInputService.createQuickPick(); + qp.placeholder = localize2('selectChatTerminal', 'Select a chat terminal to focus').value; + qp.items = items; + qp.canSelectMany = false; + qp.title = localize2('showChatTerminals.title', 'Chat Terminals').value; + qp.matchOnDescription = true; + qp.matchOnDetail = true; + qp.onDidAccept(async () => { + const sel = qp.selectedItems[0]; + if (sel) { + const target = all.get(Number(sel.id)); + if (target) { + // If hidden, reveal first then focus. Both are async; await to ensure focus happens after reveal. + if (target.isBackground) { + await terminalService.showBackgroundTerminal(target.instance); + } + await terminalService.focusInstance(target.instance); + } + } + qp.hide(); + }); + qp.onDidHide(() => qp.dispose()); + qp.show(); + } +}); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index fef463dffdc..af8f9a02a40 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -7,6 +7,8 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableMap, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITerminalChatService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; +import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; /** @@ -29,13 +31,18 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ */ private readonly _pendingRestoredMappings = new Map(); + private readonly _hasToolTerminalContext: IContextKey; + constructor( @ILogService private readonly _logService: ILogService, @ITerminalService private readonly _terminalService: ITerminalService, @IStorageService private readonly _storageService: IStorageService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); + this._hasToolTerminalContext = TerminalContextKeys.hasToolTerminal.bindTo(this._contextKeyService); + this._restoreFromStorage(); } @@ -50,11 +57,14 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId); this._persistToStorage(); + this._updateHasToolTerminalContextKey(); })); if (typeof instance.persistentProcessId === 'number') { this._persistToStorage(); } + + this._updateHasToolTerminalContextKey(); } getTerminalInstanceByToolSessionId(terminalToolSessionId: string | undefined): ITerminalInstance | undefined { @@ -71,7 +81,11 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ return this._terminalInstancesByToolSessionId.get(terminalToolSessionId); } - isBackgroundTerminal(terminalToolSessionId: string | undefined): boolean { + getToolSessionTerminalInstances(): readonly ITerminalInstance[] { + return Array.from(this._terminalInstancesByToolSessionId.values()); + } + + isBackgroundTerminal(terminalToolSessionId?: string): boolean { if (!terminalToolSessionId) { return false; } @@ -123,6 +137,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ } private _persistToStorage(): void { + this._updateHasToolTerminalContextKey(); try { const entries: [string, number][] = []; for (const [toolSessionId, instance] of this._terminalInstancesByToolSessionId.entries()) { @@ -139,4 +154,9 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._logService.warn('Failed to persist terminal chat tool session mappings', err); } } + + private _updateHasToolTerminalContextKey(): void { + const toolCount = this._terminalInstancesByToolSessionId.size; + this._hasToolTerminalContext.set(toolCount > 0); + } } From c4cc397f7aaacb77240fb1e0aca4d4699d4ebce2 Mon Sep 17 00:00:00 2001 From: vijay upadya Date: Fri, 17 Oct 2025 13:52:11 -0700 Subject: [PATCH 1265/4355] Fix tab disappearence when loading chat item --- .../contrib/chat/browser/chatEditor.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 11bb8513b28..988d9fe3621 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -78,6 +78,8 @@ export class ChatEditor extends EditorPane { protected override createEditor(parent: HTMLElement): void { this._editorContainer = parent; + // Ensure the container has position relative for the loading overlay + parent.style.position = 'relative'; this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true); @@ -178,8 +180,17 @@ export class ChatEditor extends EditorPane { } override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + // Show loading indicator early for non-local sessions to prevent layout shifts + let isContributedChatSession = false; + const chatSessionType = getChatSessionType(input); + if (chatSessionType !== 'local') { + const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); + this.showLoadingInChatWidget(loadingMessage); + } + await super.setInput(input, options, context, token); if (token.isCancellationRequested) { + this.hideLoadingInChatWidget(); return; } @@ -187,13 +198,7 @@ export class ChatEditor extends EditorPane { throw new Error('ChatEditor lifecycle issue: no editor widget'); } - let isContributedChatSession = false; - const chatSessionType = getChatSessionType(input); if (chatSessionType !== 'local') { - // Show single loading state for contributed sessions - const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); - this.showLoadingInChatWidget(loadingMessage); - try { await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); const contributions = this.chatSessionsService.getAllChatSessionContributions(); From fe388224098ae290020bdea099c5880e15fe3ce3 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:57:22 -0700 Subject: [PATCH 1266/4355] Always show cloud button with config.chat.useCloudButtonV2 (#272006) always show cloud button with config.chat.useCloudButtonV2 --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 10 ++++++++-- .../workbench/contrib/chat/common/chatContextKeys.ts | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 8abe50692eb..0e87bd27288 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -648,7 +648,10 @@ export class CreateRemoteAgentJobAction extends Action2 { group: 'navigation', order: 3.4, when: ContextKeyExpr.and( - ChatContextKeys.hasRemoteCodingAgent, + ContextKeyExpr.or( + ChatContextKeys.hasRemoteCodingAgent, + ChatContextKeys.hasCloudButtonV2 + ), ChatContextKeys.lockedToCodingAgent.negate(), ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, false) ), @@ -658,7 +661,10 @@ export class CreateRemoteAgentJobAction extends Action2 { group: 'group_3', order: 1, when: ContextKeyExpr.and( - ChatContextKeys.hasRemoteCodingAgent, + ContextKeyExpr.or( + ChatContextKeys.hasRemoteCodingAgent, + ChatContextKeys.hasCloudButtonV2 + ), ChatContextKeys.lockedToCodingAgent.negate(), ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, true) ), diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index b90624bb858..43431e50a2e 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -63,6 +63,7 @@ export namespace ChatContextKeys { export const remoteJobCreating = new RawContextKey('chatRemoteJobCreating', false, { type: 'boolean', description: localize('chatRemoteJobCreating', "True when a remote coding agent job is being created.") }); export const hasRemoteCodingAgent = new RawContextKey('hasRemoteCodingAgent', false, localize('hasRemoteCodingAgent', "Whether any remote coding agent is available")); + export const hasCloudButtonV2 = ContextKeyExpr.has('config.chat.useCloudButtonV2'); export const enableRemoteCodingAgentPromptFileOverlay = new RawContextKey('enableRemoteCodingAgentPromptFileOverlay', false, localize('enableRemoteCodingAgentPromptFileOverlay', "Whether the remote coding agent prompt file overlay feature is enabled")); /** Used by the extension to skip the quit confirmation when #new wants to open a new folder */ export const skipChatRequestInProgressMessage = new RawContextKey('chatSkipRequestInProgressMessage', false, { type: 'boolean', description: localize('chatSkipRequestInProgressMessage', "True when the chat request in progress message should be skipped.") }); From 4862ab8a5521401ae550627fa50e800c441a85ca Mon Sep 17 00:00:00 2001 From: vijay upadya Date: Fri, 17 Oct 2025 14:08:21 -0700 Subject: [PATCH 1267/4355] move to css --- src/vs/workbench/contrib/chat/browser/chatEditor.ts | 2 +- src/vs/workbench/contrib/chat/browser/media/chatSessions.css | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 988d9fe3621..e353947423a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -79,7 +79,7 @@ export class ChatEditor extends EditorPane { protected override createEditor(parent: HTMLElement): void { this._editorContainer = parent; // Ensure the container has position relative for the loading overlay - parent.style.position = 'relative'; + parent.classList.add('chat-editor-relative'); this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); ChatContextKeys.inChatEditor.bindTo(this._scopedContextKeyService).set(true); diff --git a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css index 3ba12e266f7..bcd378530be 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatSessions.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatSessions.css @@ -261,6 +261,11 @@ background-color: var(--vscode-list-hoverBackground); } +/* Chat editor relative positioning for loading overlay */ +.chat-editor-relative { + position: relative; +} + /* Chat editor loading overlay styles */ .chat-loading-overlay { position: absolute; From 0a1565315de17c48f8af5102050aaed6cb3cab22 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 17 Oct 2025 15:04:24 -0700 Subject: [PATCH 1268/4355] mcp: fix incorrect 'activating extensions' method (#272011) Closes #271822 Closes #270982 --- src/vs/workbench/contrib/mcp/common/mcpService.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index 96014024bc9..7b261f00f10 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -118,6 +118,8 @@ export class McpService extends Disposable implements IMcpService { serversRequiringInteraction: requiringInteraction, }, undefined); + update(); + await Promise.all([...todo].map(async (server, i) => { try { await startServerAndWaitForLiveTools(server, { interaction, errorOnUserInteraction: true }, token); @@ -125,11 +127,11 @@ export class McpService extends Disposable implements IMcpService { if (error instanceof UserInteractionRequiredError) { requiringInteraction.push({ id: server.definition.id, label: server.definition.label, errorMessage: error.message }); } - } - - if (!token.isCancellationRequested) { + } finally { todo.delete(server); - update(); + if (!token.isCancellationRequested) { + update(); + } } })); } From c8f6138825f8a39da31a0f3f7627ba677f77acb9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 17 Oct 2025 15:04:50 -0700 Subject: [PATCH 1269/4355] tools: initial proposal for static mcp tool definitions (#271716) * tools: initial proposal for static mcp tool definitions * finish implementation * up distro --- package.json | 2 +- .../observableInternal/utils/promise.ts | 4 + .../common/extensionsApiProposals.ts | 3 + .../workbench/api/common/extHost.api.impl.ts | 3 + src/vs/workbench/api/common/extHostMcp.ts | 19 ++- src/vs/workbench/api/common/extHostTypes.ts | 7 + .../contrib/mcp/browser/mcpCommands.ts | 2 + .../mcpLanguageModelToolContribution.ts | 24 +-- .../workbench/contrib/mcp/common/mcpServer.ts | 158 ++++++++++++------ .../contrib/mcp/common/mcpService.ts | 15 +- .../workbench/contrib/mcp/common/mcpTypes.ts | 21 ++- .../vscode.proposed.mcpToolDefinitions.d.ts | 89 ++++++++++ 12 files changed, 254 insertions(+), 93 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts diff --git a/package.json b/package.json index 299c470a67a..5a5610d6a72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "d5a36b1bb1ce3814ad7e416d17ce669df183eeb6", + "distro": "b4c6e5cbae656f37b06e74d2f613f44f05730ace", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/common/observableInternal/utils/promise.ts b/src/vs/base/common/observableInternal/utils/promise.ts index e36c6be95e1..a6493858f6c 100644 --- a/src/vs/base/common/observableInternal/utils/promise.ts +++ b/src/vs/base/common/observableInternal/utils/promise.ts @@ -41,6 +41,10 @@ export class ObservablePromise { return new ObservablePromise(fn()); } + public static resolved(value: T): ObservablePromise { + return new ObservablePromise(Promise.resolve(value)); + } + private readonly _value = observableValue | undefined>(this, undefined); /** diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 52179154197..3eec8979844 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -274,6 +274,9 @@ const _allApiProposals = { mappedEditsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', }, + mcpToolDefinitions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts', + }, multiDocumentHighlightProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a542aed9209..67a604b3816 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1935,7 +1935,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, ChatErrorLevel: extHostTypes.ChatErrorLevel, McpHttpServerDefinition: extHostTypes.McpHttpServerDefinition, + McpHttpServerDefinition2: extHostTypes.McpHttpServerDefinition, McpStdioServerDefinition: extHostTypes.McpStdioServerDefinition, + McpStdioServerDefinition2: extHostTypes.McpStdioServerDefinition, + McpToolAvailability: extHostTypes.McpToolAvailability, SettingsSearchResultKind: extHostTypes.SettingsSearchResultKind }; }; diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index f5cd5fc926d..8c83a873af5 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -16,7 +16,7 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/ex import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { canLog, ILogService, LogLevel } from '../../../platform/log/common/log.js'; import { StorageScope } from '../../../platform/storage/common/storage.js'; -import { extensionPrefixedIdentifier, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch, McpServerTransportHTTP, McpServerTransportType, UserInteractionRequiredError } from '../../contrib/mcp/common/mcpTypes.js'; +import { extensionPrefixedIdentifier, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch, McpServerStaticMetadata, McpServerStaticToolAvailability, McpServerTransportHTTP, McpServerTransportType, UserInteractionRequiredError } from '../../contrib/mcp/common/mcpTypes.js'; import { MCP } from '../../contrib/mcp/common/modelContextProtocol.js'; import { ExtHostMcpShape, IMcpAuthenticationDetails, IStartMcpOptions, MainContext, MainThreadMcpShape } from './extHost.protocol.js'; import { IExtHostInitDataService } from './extHostInitDataService.js'; @@ -24,6 +24,8 @@ import { IExtHostRpcService } from './extHostRpcService.js'; import * as Convert from './extHostTypeConverters.js'; import { IExtHostVariableResolverProvider } from './extHostVariableResolverService.js'; import { IExtHostWorkspace } from './extHostWorkspace.js'; +import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { McpHttpServerDefinition, McpStdioServerDefinition, McpToolAvailability } from './extHostTypes.js'; export const IExtHostMpcService = createDecorator('IExtHostMpcService'); @@ -140,10 +142,25 @@ export class ExtHostMcpService extends Disposable implements IExtHostMpcService id = id + i; } + let staticMetadata: McpServerStaticMetadata | undefined; + const castAs2 = item as McpStdioServerDefinition | McpHttpServerDefinition; + if (isProposedApiEnabled(extension, 'mcpToolDefinitions') && castAs2.metadata) { + staticMetadata = { + capabilities: castAs2.metadata.capabilities as MCP.ServerCapabilities, + instructions: castAs2.metadata.instructions, + serverInfo: castAs2.metadata.serverInfo as MCP.Implementation, + tools: castAs2.metadata.tools?.map(t => ({ + availability: t.availability === McpToolAvailability.Dynamic ? McpServerStaticToolAvailability.Dynamic : McpServerStaticToolAvailability.Initial, + definition: t.definition as MCP.Tool, + })), + }; + } + servers.push({ id, label: item.label, cacheNonce: item.version || '$$NONE', + staticMetadata, launch: Convert.McpServerDefinition.from(item), }); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index fb0e0bcb5ad..dec735ba8e0 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3841,6 +3841,11 @@ export enum KeywordRecognitionStatus { //#endregion //#region MCP +export enum McpToolAvailability { + Initial = 0, + Dynamic = 1, +} + export class McpStdioServerDefinition implements vscode.McpStdioServerDefinition { cwd?: URI; @@ -3850,6 +3855,7 @@ export class McpStdioServerDefinition implements vscode.McpStdioServerDefinition public args: string[], public env: Record = {}, public version?: string, + public metadata?: vscode.McpServerMetadata, ) { } } @@ -3859,6 +3865,7 @@ export class McpHttpServerDefinition implements vscode.McpHttpServerDefinition { public uri: URI, public headers: Record = {}, public version?: string, + public metadata?: vscode.McpServerMetadata, ) { } } //#endregion diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index f238ef01c4e..66b370cc0b1 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -109,6 +109,8 @@ export class ListMcpServerCommand extends Action2 { const pick = quickInput.createQuickPick({ useSeparators: true }); pick.placeholder = localize('mcp.selectServer', 'Select an MCP Server'); + mcpService.activateCollections(); + store.add(pick); store.add(autorun(reader => { diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index 7ecad35937a..b15ca909029 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -10,12 +10,11 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable, DisposableMap, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { equals } from '../../../../base/common/objects.js'; -import { autorun, autorunSelfDisposable } from '../../../../base/common/observable.js'; +import { autorun } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; -import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { IImageResizeService } from '../../../../platform/imageResize/common/imageResizeService.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -26,7 +25,7 @@ import { ChatResponseResource, getAttachableImageExtension } from '../../chat/co import { LanguageModelPartAudience } from '../../chat/common/languageModels.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultInputOutputDetails, ToolDataSource, ToolProgress, ToolSet } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; -import { IMcpServer, IMcpService, IMcpTool, IMcpToolResourceLinkContents, McpResourceURI, McpServerCacheState, McpToolResourceLinkMimeType } from './mcpTypes.js'; +import { IMcpServer, IMcpService, IMcpTool, IMcpToolResourceLinkContents, McpResourceURI, McpToolResourceLinkMimeType } from './mcpTypes.js'; import { mcpServerToSourceData } from './mcpTypesUtils.js'; interface ISyncedToolData { @@ -89,25 +88,6 @@ export class McpLanguageModelToolContribution extends Disposable implements IWor const collectionObservable = this._mcpRegistry.collections.map(collections => collections.find(c => c.id === server.collection.id)); - // If the server is extension-provided and was marked outdated automatically start it - store.add(autorunSelfDisposable(reader => { - const collection = collectionObservable.read(reader); - if (!collection) { - return; - } - - if (!(collection.source instanceof ExtensionIdentifier)) { - reader.dispose(); - return; - } - - const cacheState = server.cacheState.read(reader); - if (cacheState === McpServerCacheState.Unknown || cacheState === McpServerCacheState.Outdated) { - reader.dispose(); - server.start(); - } - })); - store.add(autorun(reader => { const toDelete = new Set(tools.keys()); diff --git a/src/vs/workbench/contrib/mcp/common/mcpServer.ts b/src/vs/workbench/contrib/mcp/common/mcpServer.ts index 0bd6db9155b..07cc7064901 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpServer.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpServer.ts @@ -10,7 +10,7 @@ import * as json from '../../../../base/common/json.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { LRUCache } from '../../../../base/common/map.js'; import { mapValues } from '../../../../base/common/objects.js'; -import { autorun, autorunSelfDisposable, derived, disposableObservableValue, IDerivedReader, IObservable, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js'; +import { autorun, autorunSelfDisposable, derived, disposableObservableValue, IDerivedReader, IObservable, IReader, ITransaction, observableFromEvent, ObservablePromise, observableValue, transaction } from '../../../../base/common/observable.js'; import { basename } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { createURITransformer } from '../../../../base/common/uriTransformer.js'; @@ -34,7 +34,7 @@ import { McpDevModeServerAttache } from './mcpDevMode.js'; import { McpIcons, parseAndValidateMcpIcon, StoredMcpIcons } from './mcpIcons.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { McpServerRequestHandler } from './mcpServerRequestHandler.js'; -import { extensionMcpCollectionPrefix, IMcpElicitationService, IMcpIcons, IMcpPrompt, IMcpPromptMessage, IMcpResource, IMcpResourceTemplate, IMcpSamplingService, IMcpServer, IMcpServerConnection, IMcpServerStartOpts, IMcpTool, IMcpToolCallContext, McpCapability, McpCollectionDefinition, McpCollectionReference, McpConnectionFailedError, McpConnectionState, McpDefinitionReference, mcpPromptReplaceSpecialChars, McpResourceURI, McpServerCacheState, McpServerDefinition, McpServerTransportType, McpToolName, UserInteractionRequiredError } from './mcpTypes.js'; +import { extensionMcpCollectionPrefix, IMcpElicitationService, IMcpIcons, IMcpPrompt, IMcpPromptMessage, IMcpResource, IMcpResourceTemplate, IMcpSamplingService, IMcpServer, IMcpServerConnection, IMcpServerStartOpts, IMcpTool, IMcpToolCallContext, McpCapability, McpCollectionDefinition, McpCollectionReference, McpConnectionFailedError, McpConnectionState, McpDefinitionReference, mcpPromptReplaceSpecialChars, McpResourceURI, McpServerCacheState, McpServerDefinition, McpServerStaticToolAvailability, McpServerTransportType, McpToolName, UserInteractionRequiredError } from './mcpTypes.js'; import { MCP } from './modelContextProtocol.js'; import { UriTemplate } from './uriTemplate.js'; @@ -230,9 +230,20 @@ interface ServerMetadata { } class CachedPrimitive { + /** + * @param _definitionId Server definition ID + * @param _cache Metadata cache instance + * @param _fromStaticDefinition Static definition that came with the server. + * This should ONLY have a value if it should be used instead of whatever + * is currently in the cache. + * @param _fromCache Pull the value from the cache entry. + * @param _toT Transform the value to the observable type. + * @param defaultValue Default value if no cache entry. + */ constructor( private readonly _definitionId: string, private readonly _cache: McpServerMetadataCache, + private readonly _fromStaticDefinition: IObservable | undefined, private readonly _fromCache: (entry: IToolCacheEntry) => C, private readonly _toT: (values: C, reader: IDerivedReader) => T, private readonly defaultValue: C, @@ -243,6 +254,10 @@ class CachedPrimitive { return c ? { data: this._fromCache(c), nonce: c.nonce } : undefined; } + public hasStaticDefinition(reader: IReader | undefined) { + return !!this._fromStaticDefinition?.read(reader); + } + public readonly fromServerPromise = observableValue { public readonly value: IObservable = derived(reader => { const serverTools = this.fromServer.read(reader); - const definitions = serverTools?.data ?? this.fromCache?.data ?? this.defaultValue; + const definitions = serverTools?.data ?? this._fromStaticDefinition?.read(reader) ?? this.fromCache?.data ?? this.defaultValue; return this._toT(definitions, reader); }); } @@ -307,9 +322,9 @@ export class McpServer extends Disposable implements IMcpServer { public readonly connectionState: IObservable = derived(reader => this._connection.read(reader)?.state.read(reader) ?? { state: McpConnectionState.Kind.Stopped }); - private readonly _capabilities = observableValue('mcpserver.capabilities', undefined); + private readonly _capabilities: CachedPrimitive; public get capabilities() { - return this._capabilities; + return this._capabilities.value; } private readonly _tools: CachedPrimitive; @@ -343,6 +358,10 @@ export class McpServer extends Disposable implements IMcpServer { public readonly cacheState = derived(reader => { const currentNonce = () => this._fullDefinitions.read(reader)?.server?.cacheNonce; const stateWhenServingFromCache = () => { + if (this._tools.hasStaticDefinition(reader)) { + return McpServerCacheState.Cached; + } + if (!this._tools.fromCache) { return McpServerCacheState.Unknown; } @@ -440,16 +459,27 @@ export class McpServer extends Disposable implements IMcpServer { const cnx = this._connection.read(reader); const handler = cnx?.handler.read(reader); if (handler) { - this.populateLiveData(handler, cnx?.definition.cacheNonce, reader.store); + this._populateLiveData(handler, cnx?.definition.cacheNonce, reader.store); } else if (this._tools) { this.resetLiveData(); } })); + const staticMetadata = derived(reader => { + const def = this._fullDefinitions.read(reader).server; + return def && def.cacheNonce !== this._tools.fromCache?.nonce ? def.staticMetadata : undefined; + }); + // 3. Publish tools this._tools = new CachedPrimitive( this.definition.id, this._primitiveCache, + staticMetadata + .map(m => { + const tools = m?.tools?.filter(t => t.availability === McpServerStaticToolAvailability.Initial).map(t => t.definition); + return tools?.length ? new ObservablePromise(this._getValidatedTools(tools)) : undefined; + }) + .map((o, reader) => o?.promiseResult.read(reader)?.data), (entry) => entry.tools, (entry) => entry.map(def => new McpTool(this, toolPrefix, def)).sort((a, b) => a.compare(b)), [], @@ -459,6 +489,7 @@ export class McpServer extends Disposable implements IMcpServer { this._prompts = new CachedPrimitive( this.definition.id, this._primitiveCache, + undefined, (entry) => entry.prompts || [], (entry) => entry.map(e => new McpPrompt(this, e)), [], @@ -467,12 +498,20 @@ export class McpServer extends Disposable implements IMcpServer { this._serverMetadata = new CachedPrimitive( this.definition.id, this._primitiveCache, + staticMetadata.map(m => m ? this._toStoredMetadata(m?.serverInfo, m?.instructions) : undefined), (entry) => ({ serverName: entry.serverName, serverInstructions: entry.serverInstructions, serverIcons: entry.serverIcons }), (entry) => ({ serverName: entry?.serverName, serverInstructions: entry?.serverInstructions, icons: McpIcons.fromStored(entry?.serverIcons) }), undefined, ); - this._capabilities.set(this._primitiveCache.get(this.definition.id)?.capabilities, undefined); + this._capabilities = new CachedPrimitive( + this.definition.id, + this._primitiveCache, + staticMetadata.map(m => m?.capabilities !== undefined ? encodeCapabilities(m.capabilities) : undefined), + (entry) => entry.capabilities, + (entry) => entry, + undefined, + ); } public readDefinitions(): IObservable<{ server: McpServerDefinition | undefined; collection: McpCollectionDefinition | undefined }> { @@ -731,7 +770,7 @@ export class McpServer extends Disposable implements IMcpServer { return { error: messages }; } - private async _getValidatedTools(handler: McpServerRequestHandler, tools: MCP.Tool[]): Promise { + private async _getValidatedTools(tools: MCP.Tool[]): Promise { let error = ''; const validations = await Promise.all(tools.map(t => this._normalizeTool(t))); @@ -749,7 +788,7 @@ export class McpServer extends Disposable implements IMcpServer { } if (error) { - handler.logger.warn(`${tools.length - validated.length} tools have invalid JSON schemas and will be omitted`); + this._logger.warn(`${tools.length - validated.length} tools have invalid JSON schemas and will be omitted`); warnInvalidTools(this._instantiationService, this.definition.label, error); } @@ -771,76 +810,87 @@ export class McpServer extends Disposable implements IMcpServer { return parseAndValidateMcpIcon(icons, cnx.launchDefinition, this._logger); } - private populateLiveData(handler: McpServerRequestHandler, cacheNonce: string | undefined, store: DisposableStore) { + private _setServerTools(nonce: string | undefined, toolsPromise: Promise, tx: ITransaction | undefined) { + const toolPromiseSafe = toolsPromise.then(async tools => { + this._logger.info(`Discovered ${tools.length} tools`); + const data = await this._getValidatedTools(tools); + this._primitiveCache.store(this.definition.id, { tools: data, nonce }); + return { data, nonce }; + }); + this._tools.fromServerPromise.set(new ObservablePromise(toolPromiseSafe), tx); + return toolPromiseSafe; + } + + private _setServerPrompts(nonce: string | undefined, promptsPromise: Promise, tx: ITransaction | undefined) { + const promptsPromiseSafe = promptsPromise.then((result): { data: StoredMcpPrompt[]; nonce: string | undefined } => { + const data: StoredMcpPrompt[] = result.map(prompt => ({ + ...prompt, + _icons: this._parseIcons(prompt) + })); + this._primitiveCache.store(this.definition.id, { prompts: data, nonce }); + return { data, nonce }; + }); + + this._prompts.fromServerPromise.set(new ObservablePromise(promptsPromiseSafe), tx); + return promptsPromiseSafe; + } + + private _toStoredMetadata(serverInfo?: MCP.Implementation, instructions?: string): StoredServerMetadata { + return { + serverName: serverInfo ? serverInfo.title || serverInfo.name : undefined, + serverInstructions: instructions, + serverIcons: serverInfo ? this._parseIcons(serverInfo) : undefined, + }; + } + + private _setServerMetadata( + nonce: string | undefined, + { serverInfo, instructions, capabilities }: { serverInfo: MCP.Implementation; instructions: string | undefined; capabilities: MCP.ServerCapabilities }, + tx: ITransaction | undefined, + ) { + const serverMetadata: StoredServerMetadata = this._toStoredMetadata(serverInfo, instructions); + this._serverMetadata.fromServerPromise.set(ObservablePromise.resolved({ nonce, data: serverMetadata }), tx); + + const capabilitiesEncoded = encodeCapabilities(capabilities); + this._capabilities.fromServerPromise.set(ObservablePromise.resolved({ data: capabilitiesEncoded, nonce }), tx); + this._primitiveCache.store(this.definition.id, { ...serverMetadata, nonce, capabilities: capabilitiesEncoded }); + } + + private _populateLiveData(handler: McpServerRequestHandler, cacheNonce: string | undefined, store: DisposableStore) { const cts = new CancellationTokenSource(); store.add(toDisposable(() => cts.dispose(true))); - // todo: add more than just tools here - const updateTools = (tx: ITransaction | undefined) => { const toolPromise = handler.capabilities.tools ? handler.listTools({}, cts.token) : Promise.resolve([]); - const toolPromiseSafe = toolPromise.then(async tools => { - handler.logger.info(`Discovered ${tools.length} tools`); - return { data: await this._getValidatedTools(handler, tools), nonce: cacheNonce }; - }); - this._tools.fromServerPromise.set(new ObservablePromise(toolPromiseSafe), tx); - return toolPromiseSafe; + return this._setServerTools(cacheNonce, toolPromise, tx); }; const updatePrompts = (tx: ITransaction | undefined) => { const promptsPromise = handler.capabilities.prompts ? handler.listPrompts({}, cts.token) : Promise.resolve([]); - const promptsPromiseSafe = promptsPromise.then(data => ({ - data: data.map(prompt => ({ - ...prompt, - _icons: this._parseIcons(prompt) - })), - nonce: cacheNonce - })); - this._prompts.fromServerPromise.set(new ObservablePromise(promptsPromiseSafe), tx); - return promptsPromiseSafe; + return this._setServerPrompts(cacheNonce, promptsPromise, tx); }; store.add(handler.onDidChangeToolList(() => { - handler.logger.info('Tool list changed, refreshing tools...'); + this._logger.info('Tool list changed, refreshing tools...'); updateTools(undefined); })); store.add(handler.onDidChangePromptList(() => { - handler.logger.info('Prompts list changed, refreshing prompts...'); + this._logger.info('Prompts list changed, refreshing prompts...'); updatePrompts(undefined); })); - const serverMetadata = { - serverName: handler.serverInfo.title || handler.serverInfo.name, - serverInstructions: handler.serverInstructions, - serverIcons: this._parseIcons(handler.serverInfo), - }; - - const metadataPromise = new ObservablePromise(Promise.resolve({ - nonce: cacheNonce, - data: serverMetadata, - })); - transaction(tx => { - // note: all update* methods must use tx synchronously - const capabilities = encodeCapabilities(handler.capabilities); - this._primitiveCache.store(this.definition.id, { ...serverMetadata, capabilities }); - this._capabilities.set(capabilities, tx); - this._serverMetadata.fromServerPromise.set(metadataPromise, tx); - - Promise.all([updateTools(tx), updatePrompts(tx)]).then(([{ data: tools }, { data: prompts }]) => { - this._primitiveCache.store(this.definition.id, { - nonce: cacheNonce, - tools, - prompts, - capabilities, - }); + this._setServerMetadata(cacheNonce, { serverInfo: handler.serverInfo, instructions: handler.serverInstructions, capabilities: handler.capabilities }, tx); + updatePrompts(tx); + const toolUpdate = updateTools(tx); + toolUpdate.then(tools => { this._telemetryService.publicLog2('mcp/serverBoot', { supportsLogging: !!handler.capabilities.logging, supportsPrompts: !!handler.capabilities.prompts, supportsResources: !!handler.capabilities.resources, - toolCount: tools.length, + toolCount: tools.data.length, serverName: handler.serverInfo.name, serverVersion: handler.serverInfo.version, }); diff --git a/src/vs/workbench/contrib/mcp/common/mcpService.ts b/src/vs/workbench/contrib/mcp/common/mcpService.ts index 7b261f00f10..4b3d6cb4bd7 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpService.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpService.ts @@ -146,20 +146,7 @@ export class McpService extends Disposable implements IMcpService { } public async activateCollections(): Promise { - const collectionIds = await this._activateCollections(); - - // Discover any newly-collected servers with unknown tools - const todo: Promise[] = []; - for (const { object: server } of this._servers.get()) { - if (collectionIds.has(server.collection.id)) { - const state = server.cacheState.get(); - if (state === McpServerCacheState.Unknown) { - todo.push(server.start()); - } - } - } - - await Promise.all(todo); + await this._activateCollections(); } private async _activateCollections() { diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 794a2485ae4..ca79e79d5ec 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -129,6 +129,9 @@ export interface McpServerDefinition { readonly cacheNonce: string; /** Dev mode configuration for the server */ readonly devMode?: IMcpDevModeConfig; + /** Static description of server tools/data, used to hydrate the cache. */ + readonly staticMetadata?: McpServerStaticMetadata; + readonly presentation?: { /** Sort order of the definition. */ @@ -138,6 +141,20 @@ export interface McpServerDefinition { }; } +export const enum McpServerStaticToolAvailability { + /** Tool is expected to be present as soon as the server is started. */ + Initial, + /** Tool may be present later. */ + Dynamic, +} + +export interface McpServerStaticMetadata { + tools?: { availability: McpServerStaticToolAvailability; definition: MCP.Tool }[]; + instructions?: string; + capabilities?: MCP.ServerCapabilities; + serverInfo?: MCP.Implementation; +} + export namespace McpServerDefinition { export interface Serialized { readonly id: string; @@ -145,6 +162,7 @@ export namespace McpServerDefinition { readonly cacheNonce: string; readonly launch: McpServerLaunch.Serialized; readonly variableReplacement?: McpServerDefinitionVariableReplacement.Serialized; + readonly staticMetadata?: McpServerStaticMetadata; } export function toSerialized(def: McpServerDefinition): McpServerDefinition.Serialized { @@ -156,6 +174,7 @@ export namespace McpServerDefinition { id: def.id, label: def.label, cacheNonce: def.cacheNonce, + staticMetadata: def.staticMetadata, launch: McpServerLaunch.fromSerialized(def.launch), variableReplacement: def.variableReplacement ? McpServerDefinitionVariableReplacement.fromSerialized(def.variableReplacement) : undefined, }; @@ -229,7 +248,7 @@ export interface IMcpService { /** Cancels any current autostart @internal */ cancelAutostart(): void; - /** Activatese extensions and runs their MCP servers. */ + /** Activates extension-providing MCP servers that have not yet been discovered. */ activateCollections(): Promise; } diff --git a/src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts b/src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts new file mode 100644 index 00000000000..6c806264304 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/272000 @connor4312 + + /** + * Defines when a {@link McpServerLanguageModelToolDefinition} is available + * for calling. + */ + export enum McpToolAvailability { + /** + * The MCP tool is available when the server starts up. + */ + Initial = 0, + + /** + * The MCP tool is conditionally available when certain preconditions are met. + */ + Dynamic = 1, + } + + /** + * The definition for a tool an MCP server provides. Extensions may provide + * this as part of their server metadata to allow the editor to defer + * starting the server until it's called by a language model. + */ + export interface McpServerLanguageModelToolDefinition { + /** + * The definition of the tool as it appears on the MCP protocol. This should + * be an object that includes the `inputSchema` and `name`, + * among other optional properties. + */ + definition?: unknown; + + /** + * An indicator for when the tool is available for calling. + */ + availability: McpToolAvailability; + } + + /** + * Metadata which the editor can use to hydrate information about the server + * prior to starting it. The extension can provide tools and basic server + * instructions as they would be expected to appear on MCP itself. + * + * Once a server is started, the observed values will be cached and take + * precedence over those statically declared here unless and until the + * server's {@link McpStdioServerDefinition.version version} is updated. If + * you can ensure the metadata is always accurate and do not otherwise have + * a server `version` to use, it is reasonable to set the server `version` + * to a hash of this object to ensure the cache tracks the {@link McpServerMetadata}. + */ + export interface McpServerMetadata { + /** + * Tools the MCP server exposes. + */ + tools?: McpServerLanguageModelToolDefinition[]; + + /** + * MCP server instructions as it would appear on the `initialize` result in the protocol. + */ + instructions?: string; + + /** + * MCP server capabilities as they would appear on the `initialize` result in the protocol. + */ + capabilities?: unknown; + + /** + * MCP server info as it would appear on the `initialize` result in the protocol. + */ + serverInfo?: unknown; + } + + + export class McpStdioServerDefinition2 extends McpStdioServerDefinition { + metadata?: McpServerMetadata; + constructor(label: string, command: string, args?: string[], env?: Record, version?: string, metadata?: McpServerMetadata); + } + + export class McpHttpServerDefinition2 extends McpHttpServerDefinition { + metadata?: McpServerMetadata; + constructor(label: string, uri: Uri, headers?: Record, version?: string, metadata?: McpServerMetadata); + } +} From cdbfba6dbf8be50184553ed8e6c8fd4e25c74051 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Fri, 17 Oct 2025 17:32:23 -0700 Subject: [PATCH 1270/4355] Mode handoff flow (#269604) * Mode handoff flow * Show/hide widget on mode switching * Fix tests * Address PR feedback: remove subtitle, exclude quick chat, add debouncing for suggest next widget * Show next steps widget only after request completion, not on message send * Reset promptSyntax changes that landed in main The handOffs property logic has landed in main branch via another PR. Resetting these files to avoid conflicts and use the upstream implementation. * Fixed handoff props * Improve chat suggest next widget layout and styling * Fix mode handoff widget visibility on clear and mode changes * Prevent suggest widget re-show on clear * reset styling and clean up chatWidget * Schedule welcome view render instead of invoking it directly Use the existing _welcomeRenderScheduler.schedule() when the view model has no items so welcome view rendering is debounced and avoids potential reentrant/cyclic calls. * update css * Schedule suggest-next widget render instead of hiding it synchronously * Use single quotes for YAML lines in "mode with handoffs attribute" test * Update tests * Account for suggest-next widget height * Nit: remove height update for welcome content --------- Co-authored-by: bhavyaus --- .../chatContentParts/chatSuggestNextWidget.ts | 124 ++++++++++++++++++ .../contrib/chat/browser/chatWidget.ts | 109 +++++++++++++-- .../contrib/chat/browser/media/chat.css | 26 ++++ .../contrib/chat/common/chatModes.ts | 3 +- .../promptSytntax/promptValidator.test.ts | 18 +++ 5 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts new file mode 100644 index 00000000000..5b8a6ebcb28 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../../base/browser/dom.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { localize } from '../../../../../nls.js'; +import { IChatMode } from '../../common/chatModes.js'; +import { IHandOff } from '../../common/promptSyntax/service/newPromptsParser.js'; + +export interface INextPromptSelection { + readonly handoff: IHandOff; +} + +export class ChatSuggestNextWidget extends Disposable { + public readonly domNode: HTMLElement; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight: Event = this._onDidChangeHeight.event; + + private readonly _onDidSelectPrompt = this._register(new Emitter()); + public readonly onDidSelectPrompt: Event = this._onDidSelectPrompt.event; + + private promptsContainer!: HTMLElement; + private titleElement!: HTMLElement; + private _currentMode: IChatMode | undefined; + + constructor() { + super(); + this.domNode = this.createSuggestNextWidget(); + } + + public get height(): number { + return this.domNode.style.display === 'none' ? 0 : this.domNode.offsetHeight; + } + + public getCurrentMode(): IChatMode | undefined { + return this._currentMode; + } + + private createSuggestNextWidget(): HTMLElement { + // Reuse welcome view classes for consistent styling + const container = dom.$('.chat-suggest-next-widget.chat-welcome-view-suggested-prompts'); + container.style.display = 'none'; + + // Title element using welcome view class + this.titleElement = dom.append(container, dom.$('.chat-welcome-view-suggested-prompts-title')); + + // Container for prompt buttons + this.promptsContainer = container; + + return container; + } + + public render(mode: IChatMode): void { + const handoffs = mode.handOffs?.get(); + + if (!handoffs || handoffs.length === 0) { + this.hide(); + return; + } + + this._currentMode = mode; + + // Update title with mode name: "Proceed from {Mode}" + const modeName = mode.name || mode.label || localize('chat.currentMode', 'current mode'); + this.titleElement.textContent = localize('chat.proceedFrom', 'Proceed from {0}', modeName); + + // Clear existing prompt buttons (keep title which is first child) + const childrenToRemove: HTMLElement[] = []; + for (let i = 1; i < this.promptsContainer.children.length; i++) { + childrenToRemove.push(this.promptsContainer.children[i] as HTMLElement); + } + for (const child of childrenToRemove) { + this.promptsContainer.removeChild(child); + } + + // Create prompt buttons using welcome view classes + for (const handoff of handoffs) { + const promptButton = this.createPromptButton(handoff); + this.promptsContainer.appendChild(promptButton); + } + + this.domNode.style.display = 'flex'; + this._onDidChangeHeight.fire(); + } + + private createPromptButton(handoff: IHandOff): HTMLElement { + // Reuse welcome view prompt button class + const button = dom.$('.chat-welcome-view-suggested-prompt'); + button.setAttribute('tabindex', '0'); + button.setAttribute('role', 'button'); + button.setAttribute('aria-label', localize('chat.suggestNext.item', '{0}', handoff.label)); + + // Title element using welcome view class + const titleElement = dom.append(button, dom.$('.chat-welcome-view-suggested-prompt-title')); + titleElement.textContent = handoff.label; + + // Click handler + this._register(dom.addDisposableListener(button, 'click', () => { + this._onDidSelectPrompt.fire({ handoff }); + })); + + // Keyboard handler + this._register(dom.addDisposableListener(button, 'keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + this._onDidSelectPrompt.fire({ handoff }); + } + })); + + return button; + } + + public hide(): void { + if (this.domNode.style.display !== 'none') { + this._currentMode = undefined; + this.domNode.style.display = 'none'; + this._onDidChangeHeight.fire(); + } + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index be87a6d7135..39691612fb1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -76,12 +76,13 @@ import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/langua import { ComputeAutomaticInstructions } from '../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; -import { ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; +import { IHandOff, ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { handleModeSwitch } from './actions/chatActions.js'; import { ChatTreeItem, ChatViewId, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from './chat.js'; import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; +import { ChatSuggestNextWidget } from './chatContentParts/chatSuggestNextWidget.js'; import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; import { ChatInputPart, IChatInputPartOptions, IChatInputStyles } from './chatInputPart.js'; import { ChatListDelegate, ChatListItemRenderer, IChatListItemTemplate, IChatRendererDelegate } from './chatListRenderer.js'; @@ -317,6 +318,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private readonly historyViewStore = this._register(new DisposableStore()); private readonly chatTodoListWidget: ChatTodoListWidget; + private readonly chatSuggestNextWidget: ChatSuggestNextWidget; private historyList: WorkbenchList | undefined; private bodyDimension: dom.Dimension | undefined; @@ -350,6 +352,8 @@ export class ChatWidget extends Disposable implements IChatWidget { // Welcome view rendering scheduler to prevent reentrant calls private _welcomeRenderScheduler: RunOnceScheduler; + // Suggest next widget rendering scheduler to prevent excessive renders during mode changes + private _chatSuggestNextScheduler: RunOnceScheduler; // Coding agent locking state private _lockedToCodingAgent: string | undefined; @@ -507,6 +511,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this._welcomeRenderScheduler.schedule(); } })); + this._chatSuggestNextScheduler = this._register( + new RunOnceScheduler(() => this.renderChatSuggestNextWidget(), 20), + ); this.updateEmptyStateWithHistoryContext(); // Update welcome view content when `anonymous` condition changes @@ -556,6 +563,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection, undefined)); this.chatTodoListWidget = this._register(this.instantiationService.createInstance(ChatTodoListWidget)); + this.chatSuggestNextWidget = this._register(this.instantiationService.createInstance(ChatSuggestNextWidget)); this._register(this.configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration('chat.renderRelatedFiles')) { @@ -723,7 +731,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } get contentHeight(): number { - return this.input.contentHeight + this.tree.contentHeight + this.chatTodoListWidget.height; + return this.input.contentHeight + this.tree.contentHeight + this.chatTodoListWidget.height + this.chatSuggestNextWidget.height; } get attachmentModel(): ChatAttachmentModel { @@ -763,12 +771,21 @@ export class ChatWidget extends Disposable implements IChatWidget { this.layout(this.bodyDimension.height, this.bodyDimension.width); } })); + this._register(this.chatSuggestNextWidget.onDidChangeHeight(() => { + if (this.bodyDimension) { + this.layout(this.bodyDimension.height, this.bodyDimension.width); + } + })); + this._register(this.chatSuggestNextWidget.onDidSelectPrompt(({ handoff }) => { + this.handleNextPromptSelection(handoff); + })); if (renderInputOnTop) { this.createInput(this.container, { renderFollowups, renderStyle }); this.listContainer = dom.append(this.container, $(`.interactive-list`)); } else { this.listContainer = dom.append(this.container, $(`.interactive-list`)); + dom.append(this.container, this.chatSuggestNextWidget.domNode); this.createInput(this.container, { renderFollowups, renderStyle }); } @@ -911,8 +928,12 @@ export class ChatWidget extends Disposable implements IChatWidget { } // Unlock coding agent when clearing this.unlockFromCodingAgent(); - this._onDidClear.fire(); this.clearTodoListWidget(this.viewModel?.sessionId); + // Cancel any pending widget render and hide the widget BEFORE firing onDidClear + // This prevents the widget from being re-shown by any handlers triggered by the clear event + this._chatSuggestNextScheduler.cancel(); + this.chatSuggestNextWidget.hide(); + this._onDidClear.fire(); } public toggleHistoryVisibility(): void { @@ -1293,7 +1314,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Only re-render if the current view still doesn't have items and we're showing the welcome message const hasViewModelItems = this.viewModel?.getItems().length ?? 0; if (hasViewModelItems === 0) { - this.renderWelcomeViewContentIfNeeded(); + this._welcomeRenderScheduler.schedule(); } })); } @@ -1610,6 +1631,58 @@ export class ChatWidget extends Disposable implements IChatWidget { } } + private renderChatSuggestNextWidget(): void { + const items = this.viewModel?.getItems() ?? []; + if (!items.length) { + return; + } + + const lastItem = items[items.length - 1]; + const lastResponseComplete = lastItem && isResponseVM(lastItem) && lastItem.isComplete; + if (!lastResponseComplete) { + return; + } + // Get the currently selected mode directly from the observable + // Note: We use currentModeObs instead of currentModeKind because currentModeKind returns + // the ChatModeKind enum (e.g., 'agent'), which doesn't distinguish between custom modes. + // Custom modes all have kind='agent' but different IDs. + const currentMode = this.input.currentModeObs.get(); + const handoffs = currentMode?.handOffs?.get(); + + // Only show if: mode has handoffs AND chat has content AND not quick chat + const shouldShow = currentMode && handoffs && handoffs.length > 0; + + if (shouldShow) { + this.chatSuggestNextWidget.render(currentMode); + } else { + this.chatSuggestNextWidget.hide(); + } + + // Trigger layout update + if (this.bodyDimension) { + this.layout(this.bodyDimension.height, this.bodyDimension.width); + } + } + + private handleNextPromptSelection(handoff: IHandOff): void { + // Hide the widget after selection + this.chatSuggestNextWidget.hide(); + this._chatSuggestNextScheduler.cancel(); + + // Switch to the specified agent/mode if provided + if (handoff.agent) { + this.input.setChatMode(handoff.agent); + } + // Insert the handoff prompt into the input + this.input.setValue(handoff.prompt, false); + this.input.focus(); + + // Auto-submit if send flag is true + if (handoff.send) { + this.acceptInput(); + } + } + setVisible(visible: boolean): void { const wasVisible = this._visible; this._visible = visible; @@ -2142,6 +2215,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._welcomeRenderScheduler.schedule(); this.refreshParsedInput(); this.renderFollowups(); + this._chatSuggestNextScheduler.schedule(); })); this._register(autorun(r => { @@ -2247,8 +2321,18 @@ export class ChatWidget extends Disposable implements IChatWidget { if (e.kind === 'setAgent') { this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); } - if (e.kind === 'addRequest' || e.kind === 'removeRequest') { - this.clearTodoListWidget(model.sessionId, e.kind === 'removeRequest' /*force*/); + if (e.kind === 'addRequest') { + this.clearTodoListWidget(model.sessionId, false); + } + // Hide widget on request removal + if (e.kind === 'removeRequest') { + this.clearTodoListWidget(model.sessionId, true); + this.chatSuggestNextWidget.hide(); + } + // Show next steps widget when response completes (not when request starts) + if (e.kind === 'completedRequest') { + // Only show if response wasn't canceled + this._chatSuggestNextScheduler.schedule(); } })); @@ -2620,10 +2704,11 @@ export class ChatWidget extends Disposable implements IChatWidget { const inputHeight = this.inputPart.inputPartHeight; const chatTodoListWidgetHeight = this.chatTodoListWidget.height; + const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight - 2; const lastItem = this.viewModel?.getItems().at(-1); - const contentHeight = Math.max(0, height - inputHeight - chatTodoListWidgetHeight); + const contentHeight = Math.max(0, height - inputHeight - chatTodoListWidgetHeight - chatSuggestNextWidgetHeight); if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal') { this.listContainer.style.removeProperty('--chat-current-response-min-height'); } else { @@ -2635,7 +2720,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.layout(contentHeight, width); // Push the welcome message down so it doesn't change position - // when followups, attachments or working set appear + // when followups, attachments, working set, or suggest next widget appear let welcomeOffset = 100; if (this.viewOptions.renderFollowups) { welcomeOffset = Math.max(welcomeOffset - this.input.followupsHeight, 0); @@ -2690,8 +2775,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this.input.layout(possibleMaxHeight, width); const inputPartHeight = this.input.inputPartHeight; const chatTodoListWidgetHeight = this.chatTodoListWidget.height; - const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight - chatTodoListWidgetHeight); - this.layout(newHeight + inputPartHeight + chatTodoListWidgetHeight, width); + const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; + const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight - chatTodoListWidgetHeight - chatSuggestNextWidgetHeight); + this.layout(newHeight + inputPartHeight + chatTodoListWidgetHeight + chatSuggestNextWidgetHeight, width); }); })); } @@ -2735,6 +2821,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.input.layout(this._dynamicMessageLayoutData.maxHeight, width); const inputHeight = this.input.inputPartHeight; const chatTodoListWidgetHeight = this.chatTodoListWidget.height; + const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const totalMessages = this.viewModel.getItems(); // grab the last N messages @@ -2748,7 +2835,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.layout( Math.min( // we add an additional 18px in order to show that there is scrollable content - inputHeight + chatTodoListWidgetHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), + inputHeight + chatTodoListWidgetHeight + chatSuggestNextWidgetHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), this._dynamicMessageLayoutData.maxHeight ), width diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index fc45901f765..61464738d65 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -756,6 +756,12 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } +/* When suggest-next widget is present above input, remove top border-radius for connected appearance */ +.interactive-session .chat-suggest-next-widget ~ .interactive-input-part .chat-input-container { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + .interactive-session .chat-editing-session { margin-bottom: -4px; /* reset the 4px gap of the container for editing sessions */ width: 100%; @@ -2719,6 +2725,26 @@ have to be updated for changes to the rules above, or to support more deeply nes min-width: 0; } +/* Chat Suggest Next Widget */ +/* Suggested Actions widget - reuses chat-welcome-view-suggested-prompts styling */ + +.interactive-session > .chat-suggest-next-widget { + /* Override positioning from welcome view styles (relative instead of absolute) */ + position: relative; + bottom: auto; + left: auto; + right: auto; + margin: 0 0 0 0; + /* Keep standard padding with space for title */ + padding: 32px 16px 8px 16px; + /* Left align buttons to match standard welcome view */ + justify-content: flex-start; + /* Ensure flex layout is properly applied */ + display: flex; + flex-wrap: wrap; + gap: 8px; +} + .interactive-session .interactive-response .chat-used-context-list.chat-thinking-items { color: var(--vscode-descriptionForeground); padding-top: 0; diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 58470cb29d5..0df94999787 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -303,9 +303,9 @@ export class CustomChatMode implements IChatMode { this._descriptionObservable = observableValue('description', customChatMode.description); this._customToolsObservable = observableValue('customTools', customChatMode.tools); this._modelObservable = observableValue('model', customChatMode.model); + this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs); this._modeInstructions = observableValue('_modeInstructions', customChatMode.modeInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); - this._handoffsObservable = observableValue('handoffs', customChatMode.handOffs); } /** @@ -317,6 +317,7 @@ export class CustomChatMode implements IChatMode { this._descriptionObservable.set(newData.description, tx); this._customToolsObservable.set(newData.tools, tx); this._modelObservable.set(newData.model, tx); + this._handoffsObservable.set(newData.handOffs, tx); this._modeInstructions.set(newData.modeInstructions, tx); this._uriObservable.set(newData.uri, tx); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index 86c89d3ba6f..912962573b5 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -233,6 +233,24 @@ suite('PromptValidator', () => { assert.deepStrictEqual(markers.map(m => m.message), [`The 'agent' property in a handoff must be a non-empty string.`]); } }); + + test('mode with handoffs attribute', async () => { + const content = [ + '---', + 'description: \"Test mode with handoffs\"', + `handoffs:`, + ' - label: Test Prompt', + ' agent: Default', + ' prompt: Add tests for this code', + ' - label: Optimize Performance', + ' agent: Default', + ' prompt: Optimize for performance', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.mode); + assert.deepStrictEqual(markers, [], 'Expected no validation issues for handoffs attribute'); + }); }); suite('instructions', () => { From c0618ef272175e7e1a0e826c8cc3ea519f2388c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:05:21 -0700 Subject: [PATCH 1271/4355] Move auto approve message into progress hover Part of #271965 Part of #264370 --- .../chatMcpServersInteractionContentPart.ts | 3 +- .../chatProgressContentPart.ts | 64 ++++++++++++++++--- .../chatContentParts/chatTaskContentPart.ts | 3 +- .../chatTerminalToolProgressPart.ts | 4 +- .../chatToolInvocationPart.ts | 56 +--------------- .../toolInvocationParts/chatToolOutputPart.ts | 4 +- .../chatToolProgressPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 2 +- .../browser/tools/runInTerminalTool.ts | 15 +---- 9 files changed, 68 insertions(+), 85 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index 96359dbf9cc..caf2b3e8d2a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -141,7 +141,8 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements this.context, true, // forceShowSpinner true, // forceShowMessage - undefined // icon + undefined, // icon + undefined, // confirmedReason )); this.domNode.appendChild(this.workingProgressPart.domNode); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index df7d3c31fb7..39494860cf1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -6,14 +6,14 @@ import { $, append } from '../../../../../base/browser/dom.js'; import { alert } from '../../../../../base/browser/ui/aria/aria.js'; import { Codicon } from '../../../../../base/common/codicons.js'; -import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { markdownCommandLink, MarkdownString, type IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; -import { IChatProgressMessage, IChatTask, IChatTaskSerialized } from '../../common/chatService.js'; +import { IChatProgressMessage, IChatTask, IChatTaskSerialized, ToolConfirmKind, type ConfirmedReason } from '../../common/chatService.js'; import { IChatRendererContent, isResponseVM } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { renderFileWidgets } from '../chatInlineAnchorWidget.js'; @@ -21,6 +21,7 @@ import { IChatContentPart, IChatContentPartRenderContext } from './chatContentPa import { IChatMarkdownAnchorService } from './chatMarkdownAnchorService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; export class ChatProgressContentPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -36,6 +37,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP forceShowSpinner: boolean | undefined, forceShowMessage: boolean | undefined, icon: ThemeIcon | undefined, + confirmedReason: boolean | ConfirmedReason | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -61,11 +63,9 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP result.element.classList.add('progress-step'); renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store); - this.domNode = $('.progress-container'); - const iconElement = $('div'); - iconElement.classList.add(...ThemeIcon.asClassNameArray(codicon)); - append(this.domNode, iconElement); - append(this.domNode, result.element); + const tooltip: IMarkdownString | undefined = this.createApprovalMessage(confirmedReason); + const progressPart = this._register(instantiationService.createInstance(ChatProgressSubPart, result.element, codicon, tooltip)); + this.domNode = progressPart.domNode; this.renderedMessage.value = result; } @@ -100,6 +100,39 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP const showSpinner = shouldShowSpinner(followingContent, element); return other.kind === 'progressMessage' && this.showSpinner === showSpinner; } + + private createApprovalMessage(reason: boolean | ConfirmedReason | undefined): IMarkdownString | undefined { + if (!reason || typeof reason === 'boolean') { + return undefined; + } + + let md: string; + switch (reason.type) { + case ToolConfirmKind.Setting: + md = localize('chat.autoapprove.setting', 'Auto approved by {0}', markdownCommandLink({ title: '`' + reason.id + '`', id: 'workbench.action.openSettings', arguments: [reason.id] }, false)); + break; + case ToolConfirmKind.LmServicePerTool: + md = reason.scope === 'session' + ? localize('chat.autoapprove.lmServicePerTool.session', 'Auto approved for this session') + : reason.scope === 'workspace' + ? localize('chat.autoapprove.lmServicePerTool.workspace', 'Auto approved for this workspace') + : localize('chat.autoapprove.lmServicePerTool.profile', 'Auto approved for this profile'); + // TODO: Pass in toolId + // md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; + break; + case ToolConfirmKind.UserAction: + case ToolConfirmKind.Denied: + case ToolConfirmKind.ConfirmationNotNeeded: + default: + return; + } + + if (!md) { + return undefined; + } + + return new MarkdownString(md, { isTrusted: true }); + } } function shouldShowSpinner(followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { @@ -107,16 +140,29 @@ function shouldShowSpinner(followingContent: IChatRendererContent[], element: Ch } -export class ChatCustomProgressPart { +export class ChatProgressSubPart extends Disposable { public readonly domNode: HTMLElement; constructor( messageElement: HTMLElement, icon: ThemeIcon, + tooltip: IMarkdownString | string | undefined, + @IHoverService hoverService: IHoverService, ) { + super(); + this.domNode = $('.progress-container'); const iconElement = $('div'); iconElement.classList.add(...ThemeIcon.asClassNameArray(icon)); + if (tooltip) { + this._register(hoverService.setupDelayedHover(iconElement, { + content: tooltip, + appearance: { + compact: true, + showPointer: true, + }, + })); + } append(this.domNode, iconElement); messageElement.classList.add('progress-step'); @@ -137,7 +183,7 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; - super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); + super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 4a8f126270c..6cd94f46a5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -41,7 +41,8 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart true; this.isSettled = isSettled; const showSpinner = !isSettled && !context.element.isComplete; - const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined)); + // TODO: Pass in confirmed reason + const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined)); this.domNode = progressPart.domNode; this.onDidChangeHeight = Event.None; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 7ea405ef1fa..1871b84f9bf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -17,7 +17,7 @@ import { IChatCodeBlockInfo } from '../../chat.js'; import { ChatQueryTitlePart } from '../chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; -import { ChatCustomProgressPart } from '../chatProgressContentPart.js'; +import { ChatProgressSubPart } from '../chatProgressContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import '../media/chatTerminalToolProgressPart.css'; import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; @@ -106,7 +106,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart this._register(this.markdownPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); elements.message.append(this.markdownPart.domNode); - const progressPart = _instantiationService.createInstance(ChatCustomProgressPart, elements.container, this.getIcon()); + const progressPart = this._register(_instantiationService.createInstance(ChatProgressSubPart, elements.container, this.getIcon(), terminalData.autoApproveInfo)); this.domNode = progressPart.domNode; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 75f87b4e3df..1277a1679c3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -5,12 +5,10 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../base/common/event.js'; -import { markdownCommandLink, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; -import { localize } from '../../../../../../nls.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; +import { IChatToolInvocation, IChatToolInvocationSerialized } from '../../../common/chatService.js'; import { IChatRendererContent } from '../../../common/chatViewModel.js'; import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js'; import { isToolResultInputOutputDetails, isToolResultOutputDetails, ToolInvocationPresentation } from '../../../common/languageModelToolsService.js'; @@ -85,62 +83,10 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa render(); this._onDidChangeHeight.fire(); })); - - // todo@connor4312/tyriar: standardize how these are displayed - if (!(this.subPart instanceof ChatTerminalToolProgressPart)) { - const approval = this.createApprovalMessage(); - if (approval) { - this.domNode.appendChild(approval); - } - } }; render(); } - private get autoApproveMessageContent() { - const reason = this.toolInvocation.isConfirmed; - if (!reason || typeof reason === 'boolean') { - return; - } - - let md: string; - switch (reason.type) { - case ToolConfirmKind.Setting: - md = localize('chat.autoapprove.setting', 'Auto approved by {0}', markdownCommandLink({ title: '`' + reason.id + '`', id: 'workbench.action.openSettings', arguments: [reason.id] }, false)); - break; - case ToolConfirmKind.LmServicePerTool: - md = reason.scope === 'session' - ? localize('chat.autoapprove.lmServicePerTool.session', 'Auto approved for this session') - : reason.scope === 'workspace' - ? localize('chat.autoapprove.lmServicePerTool.workspace', 'Auto approved for this workspace') - : localize('chat.autoapprove.lmServicePerTool.profile', 'Auto approved for this profile'); - md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; - break; - case ToolConfirmKind.UserAction: - case ToolConfirmKind.Denied: - case ToolConfirmKind.ConfirmationNotNeeded: - default: - return; - } - - - return md; - } - - private createApprovalMessage(): HTMLElement | undefined { - const md = this.autoApproveMessageContent; - if (!md) { - return undefined; - } - - const markdownString = new MarkdownString('_' + md + '_', { isTrusted: true }); - const result = this.renderer.render(markdownString); - this._register(result); - result.element.classList.add('chat-tool-approval-message'); - - return result.element; - } - private createToolInvocationSubPart(): BaseChatToolInvocationSubPart { if (this.toolInvocation.kind === 'toolInvocation') { if (this.toolInvocation.toolSpecificData?.kind === 'extensions') { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts index 111b8fb0c22..41729203275 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts @@ -18,7 +18,7 @@ import { IToolResultOutputDetails } from '../../../common/languageModelToolsServ import { IChatCodeBlockInfo, IChatWidgetService } from '../../chat.js'; import { IChatOutputRendererService } from '../../chatOutputItemRenderer.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; -import { ChatCustomProgressPart } from '../chatProgressContentPart.js'; +import { ChatProgressSubPart } from '../chatProgressContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; interface OutputState { @@ -104,7 +104,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { const progressMessage = dom.$('span'); progressMessage.textContent = localize('loading', 'Rendering tool output...'); - const progressPart = this.instantiationService.createInstance(ChatCustomProgressPart, progressMessage, ThemeIcon.modify(Codicon.loading, 'spin')); + const progressPart = this._register(this.instantiationService.createInstance(ChatProgressSubPart, progressMessage, ThemeIcon.modify(Codicon.loading, 'spin'), 'hello world')); parent.appendChild(progressPart.domNode); // TODO: we also need to show the tool output in the UI diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index 5a5f1e42dee..808d46b9220 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -82,7 +82,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { this.provideScreenReaderStatus(content); } - return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon()); + return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon(), this.toolInvocation.isConfirmed); } private getAnnouncementKey(kind: 'progress' | 'complete'): string { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 958a5203d48..0f085599718 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1232,7 +1232,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Sat, 18 Oct 2025 04:18:07 -0700 Subject: [PATCH 1272/4355] Remove italics from term auto approve info --- .../chatTerminalToolConfirmationSubPart.ts | 4 ++-- .../browser/tools/runInTerminalTool.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 9baa67e2329..0e98ef1d6ef 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -280,9 +280,9 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS } }; if (newRules.length === 1) { - terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule', 'Auto approve rule {0} added', formatRuleLinks(newRules))}_`, mdTrustSettings); + terminalData.autoApproveInfo = new MarkdownString(localize('newRule', 'Auto approve rule {0} added', formatRuleLinks(newRules)), mdTrustSettings); } else if (newRules.length > 1) { - terminalData.autoApproveInfo = new MarkdownString(`_${localize('newRule.plural', 'Auto approve rules {0} added', formatRuleLinks(newRules))}_`, mdTrustSettings); + terminalData.autoApproveInfo = new MarkdownString(localize('newRule.plural', 'Auto approve rules {0} added', formatRuleLinks(newRules)), mdTrustSettings); } toolConfirmKind = ToolConfirmKind.UserAction; break; 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 a0d12abb1a4..b9d3428b109 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -861,23 +861,23 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const isGlobalAutoApproved = config?.value ?? config.defaultValue; if (isGlobalAutoApproved) { const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); - return new MarkdownString(`*${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}*`, mdTrustSettings); + return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); } if (isAutoApproved) { switch (autoApproveReason) { case 'commandLine': { if (commandLineResult.rule) { - return new MarkdownString(`*${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult))}*`, mdTrustSettings); + return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); } break; } case 'subCommand': { const uniqueRules = dedupeRules(subCommandResults); if (uniqueRules.length === 1) { - return new MarkdownString(`*${localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules))}*`, mdTrustSettings); + return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); } else if (uniqueRules.length > 1) { - return new MarkdownString(`*${localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules))}*`, mdTrustSettings); + return new MarkdownString(localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); } break; } @@ -886,16 +886,16 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { switch (autoApproveReason) { case 'commandLine': { if (commandLineResult.rule) { - return new MarkdownString(`*${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult))}*`, mdTrustSettings); + return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); } break; } case 'subCommand': { const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); if (uniqueRules.length === 1) { - return new MarkdownString(`*${localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))}*`); + return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))); } else if (uniqueRules.length > 1) { - return new MarkdownString(`*${localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))}*`); + return new MarkdownString(localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))); } break; } From da133d7e4484759c62776be252477c60ace8ba7d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:39:29 -0700 Subject: [PATCH 1273/4355] Pass in tool invocation --- .../chatMcpServersInteractionContentPart.ts | 1 + .../chatContentParts/chatProgressContentPart.ts | 10 ++++++---- .../browser/chatContentParts/chatTaskContentPart.ts | 2 +- .../toolInvocationParts/chatToolProgressPart.ts | 2 +- .../workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index caf2b3e8d2a..265fa6d2093 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -143,6 +143,7 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements true, // forceShowMessage undefined, // icon undefined, // confirmedReason + undefined, // toolInvocation )); this.domNode.appendChild(this.workingProgressPart.domNode); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 39494860cf1..6b0c89e8014 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -13,7 +13,7 @@ import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/mark import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; -import { IChatProgressMessage, IChatTask, IChatTaskSerialized, ToolConfirmKind, type ConfirmedReason } from '../../common/chatService.js'; +import { IChatProgressMessage, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type ConfirmedReason } from '../../common/chatService.js'; import { IChatRendererContent, isResponseVM } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { renderFileWidgets } from '../chatInlineAnchorWidget.js'; @@ -38,6 +38,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP forceShowMessage: boolean | undefined, icon: ThemeIcon | undefined, confirmedReason: boolean | ConfirmedReason | undefined, + private readonly toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -117,8 +118,9 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP : reason.scope === 'workspace' ? localize('chat.autoapprove.lmServicePerTool.workspace', 'Auto approved for this workspace') : localize('chat.autoapprove.lmServicePerTool.profile', 'Auto approved for this profile'); - // TODO: Pass in toolId - // md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; + if (this.toolInvocation?.toolId) { + md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; + } break; case ToolConfirmKind.UserAction: case ToolConfirmKind.Denied: @@ -183,7 +185,7 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; - super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); + super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 6cd94f46a5d..0dd6a5045c4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -42,7 +42,7 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart this.isSettled = isSettled; const showSpinner = !isSettled && !context.element.isComplete; // TODO: Pass in confirmed reason - const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined)); + const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined, undefined)); this.domNode = progressPart.domNode; this.onDidChangeHeight = Event.None; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index 808d46b9220..0fb9c2e5407 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -82,7 +82,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { this.provideScreenReaderStatus(content); } - return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon(), this.toolInvocation.isConfirmed); + return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon(), this.toolInvocation.isConfirmed, this.toolInvocation); } private getAnnouncementKey(kind: 'progress' | 'complete'): string { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 0f085599718..2ad32a914cd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1232,7 +1232,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Sat, 18 Oct 2025 04:50:09 -0700 Subject: [PATCH 1274/4355] Reveal and focus terminal when selected Fixes #272061 --- .../chat/browser/terminalChatActions.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index bffdf386498..746b791fa7d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -393,12 +393,11 @@ registerAction2(class ShowChatTerminalsAction extends Action2 { const sel = qp.selectedItems[0]; if (sel) { const target = all.get(Number(sel.id)); - if (target) { - // If hidden, reveal first then focus. Both are async; await to ensure focus happens after reveal. - if (target.isBackground) { - await terminalService.showBackgroundTerminal(target.instance); - } - await terminalService.focusInstance(target.instance); + const instance = target?.instance; + if (instance) { + terminalService.setActiveInstance(instance); + await terminalService.revealTerminal(instance); + terminalService.focusInstance(instance); } } qp.hide(); From a33c1a10301300a0f6abbc7b63069be9bf05fe49 Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:27:05 -0700 Subject: [PATCH 1275/4355] change terminilogy --- .../contrib/chat/browser/chatSessions.contribution.ts | 8 ++++---- .../chat/browser/chatSessions/view/chatSessionsView.ts | 4 ++-- .../chat/browser/chatSessions/view/sessionsViewPane.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatStatus.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index e15549a35ae..7ea23ab4316 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -207,7 +207,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ let displayName: string | undefined; if (chatSessionType === 'local') { - displayName = 'Local Chat Sessions'; + displayName = 'Local Chat Agent'; } else { displayName = this._contributions.get(chatSessionType)?.displayName; } @@ -275,7 +275,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return MenuRegistry.appendMenuItem(MenuId.ViewTitle, { command: { id: `${NEW_CHAT_SESSION_ACTION_ID}.${contribution.type}`, - title: localize('interactiveSession.openNewSessionEditor', "New {0} Chat Editor", contribution.displayName), + title: localize('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), icon: Codicon.plus, source: { id: contribution.extensionDescription.identifier.value, @@ -295,7 +295,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ constructor() { super({ id: `workbench.action.chat.openNewSessionEditor.${contribution.type}`, - title: localize2('interactiveSession.openNewSessionEditor', "New {0} Chat Editor", contribution.displayName), + title: localize2('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), category: CHAT_CATEGORY, icon: Codicon.plus, f1: true, // Show in command palette @@ -314,7 +314,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ override: ChatEditorInput.EditorID, pinned: true, title: { - fallback: localize('chatEditorContributionName', "{0} chat", contribution.displayName), + fallback: localize('chatEditorContributionName', "{0}", contribution.displayName), } }; const untitledId = `untitled-${generateUuid()}`; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index e5f3ce688b4..925bc2270cd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -254,7 +254,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { // Register views in priority order: local, history, then alphabetically sorted others const orderedProviders = [ - ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Sessions', baseOrder: 0 }] : []), + ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Agent', baseOrder: 0 }] : []), ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: undefined }] : []), ...providersWithDisplayNames.map((item, index) => ({ ...item, @@ -286,7 +286,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { if (provider.chatSessionType === 'local') { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); this._register(viewsRegistry.registerViewWelcomeContent(viewDescriptor.id, { - content: nls.localize('chatSessions.noResults', "No local chat sessions\n[Start a Chat](command:{0})", ACTION_ID_OPEN_CHAT), + content: nls.localize('chatSessions.noResults', "No local chat agent sessions\n[Start an Agent Session](command:{0})", ACTION_ID_OPEN_CHAT), })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 790c3c959c9..c7e0ac14a02 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -336,7 +336,7 @@ export class SessionsViewPane extends ViewPane { if (elements.length === 1) { return elements[0].label; } - return nls.localize('chatSessions.dragLabel', "{0} chat sessions", elements.length); + return nls.localize('chatSessions.dragLabel', "{0} agent sessions", elements.length); }, drop: () => { }, onDragOver: () => false, diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index a6225c451e0..75a0e100fb7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -215,9 +215,9 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu else if (chatSessionsInProgressCount > 0) { text = '$(copilot-in-progress)'; if (chatSessionsInProgressCount > 1) { - ariaLabel = localize('chatSessionsInProgressStatus', "{0} chat sessions in progress", chatSessionsInProgressCount); + ariaLabel = localize('chatSessionsInProgressStatus', "{0} agent sessions in progress", chatSessionsInProgressCount); } else { - ariaLabel = localize('chatSessionInProgressStatus', "1 chat session in progress"); + ariaLabel = localize('chatSessionInProgressStatus', "1 agent session in progress"); } } From e693523b9752231f08442260d114fcd32fa3a5b1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 18 Oct 2025 21:28:25 +0200 Subject: [PATCH 1276/4355] eslint - enable `no-explicit-any` for `src/**/*.ts` by using `ignores` (#272101) --- eslint.config.js | 838 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 723 insertions(+), 115 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index a8cb68fee70..9baf392eda7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -180,122 +180,730 @@ export default tseslint.config( // vscode TS: strict no explicit `any` { files: [ - 'src/vs/base/browser/fastDomNode.ts', - 'src/vs/base/browser/globalPointerMoveMonitor.ts', - 'src/vs/base/browser/keyboardEvent.ts', - 'src/vs/base/browser/ui/mouseCursor/**', - 'src/vs/base/browser/ui/scrollbar/**', - 'src/vs/base/browser/ui/widget.ts', - 'src/vs/base/common/extpath.ts', - 'src/vs/base/common/fuzzyScorer.ts', - 'src/vs/base/common/glob.ts', - 'src/vs/base/common/path.ts', - 'src/vs/base/common/stream.ts', - 'src/vs/base/common/buffer.ts', - 'src/vs/base/common/charCode.ts', - 'src/vs/base/common/hash.ts', - 'src/vs/base/common/keybindingLabels.ts', - 'src/vs/base/common/keybindings.ts', - 'src/vs/base/common/keyCodes.ts', - 'src/vs/base/common/scrollable.ts', - 'src/vs/base/common/uint.ts', - 'src/vs/base/common/uriTransformer.ts', - 'src/vs/base/common/worker/webWorker.ts', - 'src/vs/base/node/pfs.ts', - 'src/vs/base/node/unc.ts', - 'src/vs/base/parts/contextmenu/**', - 'src/vs/editor/browser/**', - 'src/vs/editor/common/**', - 'src/vs/base/parts/sandbox/**', - 'src/vs/base/parts/storage/**', - 'src/vs/platform/auxiliaryWindow/**', - 'src/vs/platform/backup/**', - // 'src/vs/platform/configuration/**', - 'src/vs/platform/editor/**', - 'src/vs/platform/environment/**', - // 'src/vs/platform/extensionManagement/**', - // 'src/vs/platform/extensionRecommendations/**', - // 'src/vs/platform/extensionResourceLoader/**', - 'src/vs/platform/dialogs/**', - 'src/vs/platform/files/**', - 'src/vs/platform/ipc/**', - 'src/vs/platform/launch/**', - 'src/vs/platform/lifecycle/**', - // 'src/vs/platform/log/**', - 'src/vs/platform/mcp/**', - 'src/vs/platform/menubar/**', - 'src/vs/platform/native/**', - // 'src/vs/platform/policy/**', - 'src/vs/platform/sharedProcess/**', - 'src/vs/platform/state/**', - 'src/vs/platform/storage/**', - // 'src/vs/platform/userData/**', - // 'src/vs/platform/userDataProfile/**', - // 'src/vs/platform/userDataSync/**', - 'src/vs/platform/utilityProcess/**', - 'src/vs/platform/window/**', - 'src/vs/platform/windows/**', - 'src/vs/platform/workspace/**', - 'src/vs/platform/workspaces/**', - 'src/bootstrap-cli.ts', - 'src/bootstrap-esm.ts', - 'src/bootstrap-fork.ts', - 'src/bootstrap-import.ts', - 'src/bootstrap-meta.ts', - 'src/bootstrap-node.ts', - 'src/bootstrap-server.ts', - 'src/cli.ts', - 'src/main.ts', - 'src/server-cli.ts', - 'src/server-main.ts', - 'src/vs/code/**', - // 'src/vs/workbench/services/accounts/**', - 'src/vs/workbench/services/activity/**', - 'src/vs/workbench/services/auxiliaryWindow/**', - 'src/vs/workbench/services/chat/**', - // 'src/vs/workbench/services/configuration/**', - 'src/vs/workbench/services/contextmenu/**', - 'src/vs/workbench/services/dialogs/**', - 'src/vs/workbench/services/editor/**', - 'src/vs/workbench/services/environment/**', - // 'src/vs/workbench/services/extensionManagement/**', - // 'src/vs/workbench/services/extensionRecommendations/**', - // 'src/vs/workbench/services/extensions/**', - 'src/vs/workbench/services/files/**', - 'src/vs/workbench/services/filesConfiguration/**', - 'src/vs/workbench/services/history/**', - 'src/vs/workbench/services/host/**', - 'src/vs/workbench/services/label/**', - 'src/vs/workbench/services/layout/**', - 'src/vs/workbench/services/lifecycle/**', - // 'src/vs/workbench/services/log/**', - 'src/vs/workbench/services/mcp/**', - 'src/vs/workbench/services/notification/**', - // 'src/vs/workbench/services/output/**', - 'src/vs/workbench/services/path/**', - // 'src/vs/workbench/services/policies/**', - // 'src/vs/workbench/services/preferences/**', - 'src/vs/workbench/services/progress/**', - 'src/vs/workbench/services/storage/**', - 'src/vs/workbench/services/textfile/**', - 'src/vs/workbench/services/textmodelResolver/**', - 'src/vs/workbench/services/untitled/**', - // 'src/vs/workbench/services/userData/**', - // 'src/vs/workbench/services/userDataProfile/**', - // 'src/vs/workbench/services/userDataSync/**', - 'src/vs/workbench/services/utilityProcess/**', - 'src/vs/workbench/services/views/**', - 'src/vs/workbench/services/workingCopy/**', - 'src/vs/workbench/services/workspaces/**', - 'src/vs/workbench/common/**', - 'src/vs/workbench/browser/**', - 'src/vs/workbench/electron-browser/**', - 'src/vs/workbench/contrib/files/**', - 'src/vs/workbench/contrib/chat/browser/chatSetup.ts', - 'src/vs/workbench/contrib/chat/browser/chatStatus.ts', - 'src/vs/workbench/contrib/mcp/**', + 'src/**/*.ts', + ], + ignores: [ + 'src/vs/amdX.ts', + 'src/vs/monaco.d.ts', + 'src/vscode-dts/**', + // Base + 'src/vs/base/browser/dom.ts', + 'src/vs/base/browser/mouseEvent.ts', + 'src/vs/base/browser/pixelRatio.ts', + 'src/vs/base/browser/trustedTypes.ts', + 'src/vs/base/browser/webWorkerFactory.ts', + 'src/vs/base/node/id.ts', + 'src/vs/base/node/osDisplayProtocolInfo.ts', + 'src/vs/base/node/osReleaseInfo.ts', + 'src/vs/base/node/processes.ts', + 'src/vs/base/common/arrays.ts', + 'src/vs/base/common/async.ts', + 'src/vs/base/common/cancellation.ts', + 'src/vs/base/common/collections.ts', + 'src/vs/base/common/console.ts', + 'src/vs/base/common/controlFlow.ts', + 'src/vs/base/common/decorators.ts', + 'src/vs/base/common/equals.ts', + 'src/vs/base/common/errorMessage.ts', + 'src/vs/base/common/errors.ts', + 'src/vs/base/common/event.ts', + 'src/vs/base/common/history.ts', + 'src/vs/base/common/hotReload.ts', + 'src/vs/base/common/hotReloadHelpers.ts', + 'src/vs/base/common/iterator.ts', + 'src/vs/base/common/json.ts', + 'src/vs/base/common/jsonSchema.ts', + 'src/vs/base/common/lifecycle.ts', + 'src/vs/base/common/linkedList.ts', + 'src/vs/base/common/map.ts', + 'src/vs/base/common/marshalling.ts', + 'src/vs/base/common/network.ts', + 'src/vs/base/common/oauth.ts', + 'src/vs/base/common/objects.ts', + 'src/vs/base/common/performance.ts', + 'src/vs/base/common/platform.ts', + 'src/vs/base/common/processes.ts', + 'src/vs/base/common/resourceTree.ts', + 'src/vs/base/common/skipList.ts', + 'src/vs/base/common/strings.ts', + 'src/vs/base/common/ternarySearchTree.ts', + 'src/vs/base/common/types.ts', + 'src/vs/base/common/uriIpc.ts', + 'src/vs/base/common/verifier.ts', + 'src/vs/base/common/observableInternal/base.ts', + 'src/vs/base/common/observableInternal/changeTracker.ts', + 'src/vs/base/common/observableInternal/debugLocation.ts', + 'src/vs/base/common/observableInternal/debugName.ts', + 'src/vs/base/common/observableInternal/map.ts', + 'src/vs/base/common/observableInternal/set.ts', + 'src/vs/base/common/observableInternal/transaction.ts', + 'src/vs/base/common/worker/webWorkerBootstrap.ts', + 'src/vs/base/test/common/mock.ts', + 'src/vs/base/test/common/snapshot.ts', + 'src/vs/base/test/common/timeTravelScheduler.ts', + 'src/vs/base/test/common/troubleshooting.ts', + 'src/vs/base/test/common/utils.ts', + 'src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts', + 'src/vs/base/browser/ui/grid/grid.ts', + 'src/vs/base/browser/ui/grid/gridview.ts', + 'src/vs/base/browser/ui/list/listPaging.ts', + 'src/vs/base/browser/ui/list/listView.ts', + 'src/vs/base/browser/ui/list/listWidget.ts', + 'src/vs/base/browser/ui/list/rowCache.ts', + 'src/vs/base/browser/ui/sash/sash.ts', + 'src/vs/base/browser/ui/table/tableWidget.ts', + 'src/vs/base/browser/ui/tree/abstractTree.ts', + 'src/vs/base/browser/ui/tree/asyncDataTree.ts', + 'src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts', + 'src/vs/base/browser/ui/tree/dataTree.ts', + 'src/vs/base/browser/ui/tree/indexTree.ts', + 'src/vs/base/browser/ui/tree/indexTreeModel.ts', + 'src/vs/base/browser/ui/tree/objectTree.ts', + 'src/vs/base/browser/ui/tree/objectTreeModel.ts', + 'src/vs/base/browser/ui/tree/tree.ts', + 'src/vs/base/parts/ipc/common/ipc.net.ts', + 'src/vs/base/parts/ipc/common/ipc.ts', + 'src/vs/base/parts/ipc/electron-main/ipcMain.ts', + 'src/vs/base/parts/ipc/node/ipc.cp.ts', + 'src/vs/base/common/observableInternal/experimental/reducer.ts', + 'src/vs/base/common/observableInternal/experimental/utils.ts', + 'src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts', + 'src/vs/base/common/observableInternal/logging/logging.ts', + 'src/vs/base/common/observableInternal/observables/baseObservable.ts', + 'src/vs/base/common/observableInternal/observables/derived.ts', + 'src/vs/base/common/observableInternal/observables/derivedImpl.ts', + 'src/vs/base/common/observableInternal/observables/observableFromEvent.ts', + 'src/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts', + 'src/vs/base/common/observableInternal/reactions/autorunImpl.ts', + 'src/vs/base/common/observableInternal/utils/utils.ts', + 'src/vs/base/common/observableInternal/utils/utilsCancellation.ts', + 'src/vs/base/parts/ipc/test/node/testService.ts', + 'src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts', + 'src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugger/rpc.ts', + 'src/vs/base/test/browser/ui/grid/util.ts', + // Platform + 'src/vs/platform/accessibility/browser/accessibleView.ts', + 'src/vs/platform/accessibility/common/accessibility.ts', + 'src/vs/platform/action/common/action.ts', + 'src/vs/platform/actionWidget/browser/actionList.ts', + 'src/vs/platform/actions/common/actions.ts', + 'src/vs/platform/assignment/common/assignment.ts', + 'src/vs/platform/browserElements/electron-main/nativeBrowserElementsMainService.ts', + 'src/vs/platform/commands/common/commands.ts', + 'src/vs/platform/configuration/common/configuration.ts', + 'src/vs/platform/configuration/common/configurationModels.ts', + 'src/vs/platform/configuration/common/configurationRegistry.ts', + 'src/vs/platform/configuration/common/configurationService.ts', + 'src/vs/platform/configuration/common/configurations.ts', + 'src/vs/platform/contextkey/browser/contextKeyService.ts', + 'src/vs/platform/contextkey/common/contextkey.ts', + 'src/vs/platform/contextview/browser/contextView.ts', + 'src/vs/platform/contextview/browser/contextViewService.ts', + 'src/vs/platform/debug/common/extensionHostDebugIpc.ts', + 'src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts', + 'src/vs/platform/diagnostics/common/diagnostics.ts', + 'src/vs/platform/diagnostics/node/diagnosticsService.ts', + 'src/vs/platform/download/common/downloadIpc.ts', + 'src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts', + 'src/vs/platform/extensionManagement/common/allowedExtensionsService.ts', + 'src/vs/platform/extensionManagement/common/extensionGalleryManifestServiceIpc.ts', + 'src/vs/platform/extensionManagement/common/extensionGalleryService.ts', + 'src/vs/platform/extensionManagement/common/extensionManagement.ts', + 'src/vs/platform/extensionManagement/common/extensionManagementIpc.ts', + 'src/vs/platform/extensionManagement/common/extensionManagementUtil.ts', + 'src/vs/platform/extensionManagement/common/extensionNls.ts', + 'src/vs/platform/extensionManagement/common/extensionStorage.ts', + 'src/vs/platform/extensionManagement/common/extensionTipsService.ts', + 'src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts', + 'src/vs/platform/extensionManagement/common/implicitActivationEvents.ts', + 'src/vs/platform/extensionManagement/node/extensionManagementService.ts', + 'src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts', + 'src/vs/platform/extensions/common/extensionHostStarter.ts', + 'src/vs/platform/extensions/common/extensionValidator.ts', + 'src/vs/platform/extensions/common/extensions.ts', + 'src/vs/platform/extensions/electron-main/extensionHostStarter.ts', + 'src/vs/platform/externalTerminal/node/externalTerminalService.ts', + 'src/vs/platform/instantiation/common/descriptors.ts', + 'src/vs/platform/instantiation/common/extensions.ts', + 'src/vs/platform/instantiation/common/instantiation.ts', + 'src/vs/platform/instantiation/common/instantiationService.ts', + 'src/vs/platform/instantiation/common/serviceCollection.ts', + 'src/vs/platform/keybinding/common/keybinding.ts', + 'src/vs/platform/keybinding/common/keybindingResolver.ts', + 'src/vs/platform/keybinding/common/keybindingsRegistry.ts', + 'src/vs/platform/keybinding/common/resolvedKeybindingItem.ts', + 'src/vs/platform/keyboardLayout/common/keyboardConfig.ts', + 'src/vs/platform/languagePacks/node/languagePacks.ts', + 'src/vs/platform/list/browser/listService.ts', + 'src/vs/platform/log/browser/log.ts', + 'src/vs/platform/log/common/log.ts', + 'src/vs/platform/log/common/logIpc.ts', + 'src/vs/platform/log/electron-main/logIpc.ts', + 'src/vs/platform/observable/common/wrapInHotClass.ts', + 'src/vs/platform/observable/common/wrapInReloadableClass.ts', + 'src/vs/platform/policy/common/policyIpc.ts', + 'src/vs/platform/policy/node/nativePolicyService.ts', + 'src/vs/platform/profiling/common/profilingTelemetrySpec.ts', + 'src/vs/platform/quickinput/browser/commandsQuickAccess.ts', + 'src/vs/platform/quickinput/browser/quickInputActions.ts', + 'src/vs/platform/quickinput/common/quickAccess.ts', + 'src/vs/platform/quickinput/common/quickInput.ts', + 'src/vs/platform/registry/common/platform.ts', + 'src/vs/platform/remote/browser/browserSocketFactory.ts', + 'src/vs/platform/remote/browser/remoteAuthorityResolverService.ts', + 'src/vs/platform/remote/common/electronRemoteResources.ts', + 'src/vs/platform/remote/common/managedSocket.ts', + 'src/vs/platform/remote/common/remoteAgentConnection.ts', + 'src/vs/platform/remote/common/remoteAuthorityResolver.ts', + 'src/vs/platform/remote/electron-browser/electronRemoteResourceLoader.ts', + 'src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts', + 'src/vs/platform/remoteTunnel/node/remoteTunnelService.ts', + 'src/vs/platform/request/common/request.ts', + 'src/vs/platform/request/common/requestIpc.ts', + 'src/vs/platform/request/electron-utility/requestService.ts', + 'src/vs/platform/request/node/proxy.ts', + 'src/vs/platform/telemetry/browser/1dsAppender.ts', + 'src/vs/platform/telemetry/browser/errorTelemetry.ts', + 'src/vs/platform/telemetry/common/1dsAppender.ts', + 'src/vs/platform/telemetry/common/errorTelemetry.ts', + 'src/vs/platform/telemetry/common/gdprTypings.ts', + 'src/vs/platform/telemetry/common/remoteTelemetryChannel.ts', + 'src/vs/platform/telemetry/common/telemetry.ts', + 'src/vs/platform/telemetry/common/telemetryIpc.ts', + 'src/vs/platform/telemetry/common/telemetryLogAppender.ts', + 'src/vs/platform/telemetry/common/telemetryService.ts', + 'src/vs/platform/telemetry/common/telemetryUtils.ts', + 'src/vs/platform/telemetry/node/1dsAppender.ts', + 'src/vs/platform/telemetry/node/errorTelemetry.ts', + 'src/vs/platform/terminal/common/terminal.ts', + 'src/vs/platform/terminal/node/ptyHostService.ts', + 'src/vs/platform/terminal/node/ptyService.ts', + 'src/vs/platform/terminal/node/terminalProcess.ts', + 'src/vs/platform/terminal/node/windowsShellHelper.ts', + 'src/vs/platform/theme/common/iconRegistry.ts', + 'src/vs/platform/theme/common/tokenClassificationRegistry.ts', + 'src/vs/platform/update/common/updateIpc.ts', + 'src/vs/platform/update/electron-main/updateService.snap.ts', + 'src/vs/platform/url/common/urlIpc.ts', + 'src/vs/platform/userDataProfile/common/userDataProfileIpc.ts', + 'src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts', + 'src/vs/platform/userDataSync/common/abstractSynchronizer.ts', + 'src/vs/platform/userDataSync/common/extensionsMerge.ts', + 'src/vs/platform/userDataSync/common/extensionsSync.ts', + 'src/vs/platform/userDataSync/common/globalStateMerge.ts', + 'src/vs/platform/userDataSync/common/globalStateSync.ts', + 'src/vs/platform/userDataSync/common/ignoredExtensions.ts', + 'src/vs/platform/userDataSync/common/settingsMerge.ts', + 'src/vs/platform/userDataSync/common/settingsSync.ts', + 'src/vs/platform/userDataSync/common/userDataAutoSyncService.ts', + 'src/vs/platform/userDataSync/common/userDataSync.ts', + 'src/vs/platform/userDataSync/common/userDataSyncAccount.ts', + 'src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts', + 'src/vs/platform/userDataSync/common/userDataSyncIpc.ts', + 'src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts', + 'src/vs/platform/userDataSync/common/userDataSyncMachines.ts', + 'src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts', + 'src/vs/platform/userDataSync/common/userDataSyncService.ts', + 'src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts', + 'src/vs/platform/userDataSync/common/userDataSyncStoreService.ts', + 'src/vs/platform/webContentExtractor/electron-main/cdpAccessibilityDomain.ts', + 'src/vs/platform/webview/common/webviewManagerService.ts', + 'src/vs/platform/configuration/test/common/testConfigurationService.ts', + 'src/vs/platform/instantiation/test/common/instantiationServiceMock.ts', + 'src/vs/platform/keybinding/test/common/mockKeybindingService.ts', + 'src/vs/platform/quickinput/browser/tree/quickTree.ts', + 'src/vs/platform/userDataSync/test/common/userDataSyncClient.ts', + // Editor + 'src/vs/editor/editor.api.ts', + 'src/vs/editor/standalone/browser/standaloneCodeEditor.ts', + 'src/vs/editor/standalone/browser/standaloneEditor.ts', + 'src/vs/editor/standalone/browser/standaloneLanguages.ts', + 'src/vs/editor/standalone/browser/standaloneServices.ts', + 'src/vs/editor/standalone/browser/standaloneWebWorker.ts', + 'src/vs/editor/test/browser/testCodeEditor.ts', + 'src/vs/editor/test/common/testTextModel.ts', + 'src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts', + 'src/vs/editor/contrib/clipboard/browser/clipboard.ts', + 'src/vs/editor/contrib/codeAction/browser/codeAction.ts', + 'src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts', + 'src/vs/editor/contrib/codeAction/common/types.ts', + 'src/vs/editor/contrib/codelens/browser/codelens.ts', + 'src/vs/editor/contrib/codelens/browser/codelensController.ts', + 'src/vs/editor/contrib/colorPicker/browser/colorDetector.ts', + 'src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts', + 'src/vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts', + 'src/vs/editor/contrib/editorState/browser/editorState.ts', + 'src/vs/editor/contrib/find/browser/findController.ts', + 'src/vs/editor/contrib/find/browser/findModel.ts', + 'src/vs/editor/contrib/find/browser/findWidgetSearchHistory.ts', + 'src/vs/editor/contrib/find/browser/replaceWidgetHistory.ts', + 'src/vs/editor/contrib/folding/browser/folding.ts', + 'src/vs/editor/contrib/format/browser/format.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/symbolNavigation.ts', + 'src/vs/editor/contrib/hover/browser/hoverActions.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/utils.ts', + 'src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts', + 'src/vs/editor/contrib/lineSelection/browser/lineSelection.ts', + 'src/vs/editor/contrib/linesOperations/browser/linesOperations.ts', + 'src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts', + 'src/vs/editor/contrib/multicursor/browser/multicursor.ts', + 'src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts', + 'src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts', + 'src/vs/editor/contrib/smartSelect/browser/smartSelect.ts', + 'src/vs/editor/contrib/snippet/browser/snippetParser.ts', + 'src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts', + 'src/vs/editor/contrib/suggest/browser/suggest.ts', + 'src/vs/editor/contrib/suggest/browser/suggestAlternatives.ts', + 'src/vs/editor/contrib/suggest/browser/suggestCommitCharacters.ts', + 'src/vs/editor/contrib/suggest/browser/suggestController.ts', + 'src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts', + 'src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts', + 'src/vs/editor/contrib/wordOperations/browser/wordOperations.ts', + 'src/vs/editor/standalone/common/monarch/monarchCommon.ts', + 'src/vs/editor/standalone/common/monarch/monarchCompile.ts', + 'src/vs/editor/standalone/common/monarch/monarchLexer.ts', + 'src/vs/editor/standalone/common/monarch/monarchTypes.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/typingSpeed.ts', + 'src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts', + 'src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts', + // Workbench + 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', + 'src/vs/workbench/api/browser/mainThreadClipboard.ts', + 'src/vs/workbench/api/browser/mainThreadCodeInsets.ts', + 'src/vs/workbench/api/browser/mainThreadComments.ts', + 'src/vs/workbench/api/browser/mainThreadConfiguration.ts', + 'src/vs/workbench/api/browser/mainThreadDebugService.ts', + 'src/vs/workbench/api/browser/mainThreadDocuments.ts', + 'src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts', + 'src/vs/workbench/api/browser/mainThreadErrors.ts', + 'src/vs/workbench/api/browser/mainThreadExtensionService.ts', + 'src/vs/workbench/api/browser/mainThreadFileSystem.ts', + 'src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts', + 'src/vs/workbench/api/browser/mainThreadLanguageModels.ts', + 'src/vs/workbench/api/browser/mainThreadNotebook.ts', + 'src/vs/workbench/api/browser/mainThreadNotebookKernels.ts', + 'src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts', + 'src/vs/workbench/api/browser/mainThreadOutputService.ts', + 'src/vs/workbench/api/browser/mainThreadQuickOpen.ts', + 'src/vs/workbench/api/browser/mainThreadSCM.ts', + 'src/vs/workbench/api/browser/mainThreadSaveParticipant.ts', + 'src/vs/workbench/api/browser/mainThreadSearch.ts', + 'src/vs/workbench/api/browser/mainThreadTask.ts', + 'src/vs/workbench/api/browser/mainThreadTelemetry.ts', + 'src/vs/workbench/api/browser/mainThreadTerminalService.ts', + 'src/vs/workbench/api/browser/mainThreadTreeViews.ts', + 'src/vs/workbench/api/browser/statusBarExtensionPoint.ts', + 'src/vs/workbench/api/browser/viewsExtensionPoint.ts', + 'src/vs/workbench/api/common/configurationExtensionPoint.ts', + 'src/vs/workbench/api/common/extHost.api.impl.ts', + 'src/vs/workbench/api/common/extHost.protocol.ts', + 'src/vs/workbench/api/common/extHostChatSessions.ts', + 'src/vs/workbench/api/common/extHostCodeInsets.ts', + 'src/vs/workbench/api/common/extHostCommands.ts', + 'src/vs/workbench/api/common/extHostConfiguration.ts', + 'src/vs/workbench/api/common/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/common/extHostDataChannels.ts', + 'src/vs/workbench/api/common/extHostDebugService.ts', + 'src/vs/workbench/api/common/extHostDiagnostics.ts', + 'src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts', + 'src/vs/workbench/api/common/extHostExtensionActivator.ts', + 'src/vs/workbench/api/common/extHostExtensionService.ts', + 'src/vs/workbench/api/common/extHostFileSystemConsumer.ts', + 'src/vs/workbench/api/common/extHostFileSystemEventService.ts', + 'src/vs/workbench/api/common/extHostLanguageFeatures.ts', + 'src/vs/workbench/api/common/extHostLanguageModelTools.ts', + 'src/vs/workbench/api/common/extHostMcp.ts', + 'src/vs/workbench/api/common/extHostMemento.ts', + 'src/vs/workbench/api/common/extHostMessageService.ts', + 'src/vs/workbench/api/common/extHostNotebookDocument.ts', + 'src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts', + 'src/vs/workbench/api/common/extHostNotebookKernels.ts', + 'src/vs/workbench/api/common/extHostQuickOpen.ts', + 'src/vs/workbench/api/common/extHostRequireInterceptor.ts', + 'src/vs/workbench/api/common/extHostRpcService.ts', + 'src/vs/workbench/api/common/extHostSCM.ts', + 'src/vs/workbench/api/common/extHostSearch.ts', + 'src/vs/workbench/api/common/extHostStatusBar.ts', + 'src/vs/workbench/api/common/extHostStoragePaths.ts', + 'src/vs/workbench/api/common/extHostTelemetry.ts', + 'src/vs/workbench/api/common/extHostTerminalService.ts', + 'src/vs/workbench/api/common/extHostTesting.ts', + 'src/vs/workbench/api/common/extHostTextEditor.ts', + 'src/vs/workbench/api/common/extHostTimeline.ts', + 'src/vs/workbench/api/common/extHostTreeViews.ts', + 'src/vs/workbench/api/common/extHostTypeConverters.ts', + 'src/vs/workbench/api/common/extHostTypes.ts', + 'src/vs/workbench/api/common/extHostTypes/diagnostic.ts', + 'src/vs/workbench/api/common/extHostTypes/es5ClassCompat.ts', + 'src/vs/workbench/api/common/extHostTypes/location.ts', + 'src/vs/workbench/api/common/extHostTypes/markdownString.ts', + 'src/vs/workbench/api/common/extHostTypes/notebooks.ts', + 'src/vs/workbench/api/common/extHostTypes/position.ts', + 'src/vs/workbench/api/common/extHostTypes/range.ts', + 'src/vs/workbench/api/common/extHostTypes/selection.ts', + 'src/vs/workbench/api/common/extHostTypes/snippetString.ts', + 'src/vs/workbench/api/common/extHostTypes/snippetTextEdit.ts', + 'src/vs/workbench/api/common/extHostTypes/symbolInformation.ts', + 'src/vs/workbench/api/common/extHostTypes/textEdit.ts', + 'src/vs/workbench/api/common/extHostTypes/workspaceEdit.ts', + 'src/vs/workbench/api/common/extHostWebview.ts', + 'src/vs/workbench/api/common/extHostWebviewMessaging.ts', + 'src/vs/workbench/api/common/extHostWebviewPanels.ts', + 'src/vs/workbench/api/common/extHostWebviewView.ts', + 'src/vs/workbench/api/common/extHostWorkspace.ts', + 'src/vs/workbench/api/common/extensionHostMain.ts', + 'src/vs/workbench/api/common/shared/tasks.ts', + 'src/vs/workbench/api/node/extHostAuthentication.ts', + 'src/vs/workbench/api/node/extHostCLIServer.ts', + 'src/vs/workbench/api/node/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/node/extHostDownloadService.ts', + 'src/vs/workbench/api/node/extHostExtensionService.ts', + 'src/vs/workbench/api/node/extHostMcpNode.ts', + 'src/vs/workbench/api/node/extensionHostProcess.ts', + 'src/vs/workbench/api/node/proxyResolver.ts', + 'src/vs/workbench/api/test/common/testRPCProtocol.ts', + 'src/vs/workbench/api/worker/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/worker/extHostExtensionService.ts', + 'src/vs/workbench/api/worker/extensionHostWorker.ts', + 'src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts', + 'src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts', + 'src/vs/workbench/contrib/authentication/browser/actions/manageTrustedMcpServersForAccountAction.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/opaqueEdits.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts', + 'src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts', + 'src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts', + 'src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts', + 'src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts', + 'src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts', + 'src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts', + 'src/vs/workbench/contrib/chat/browser/chatInputPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions/common.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts', + 'src/vs/workbench/contrib/chat/browser/chatWidget.ts', + 'src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts', + 'src/vs/workbench/contrib/chat/common/chatAgents.ts', + 'src/vs/workbench/contrib/chat/common/chatModel.ts', + 'src/vs/workbench/contrib/chat/common/chatModes.ts', + 'src/vs/workbench/contrib/chat/common/chatService.ts', + 'src/vs/workbench/contrib/chat/common/chatServiceImpl.ts', + 'src/vs/workbench/contrib/chat/common/chatSessionsService.ts', + 'src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts', + 'src/vs/workbench/contrib/chat/common/languageModelToolsService.ts', + 'src/vs/workbench/contrib/chat/common/languageModels.ts', + 'src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts', + 'src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts', + 'src/vs/workbench/contrib/chat/test/common/languageModels.ts', + 'src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts', + 'src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts', + 'src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts', + 'src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts', + 'src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts', + 'src/vs/workbench/contrib/commands/common/commands.contribution.ts', + 'src/vs/workbench/contrib/comments/browser/commentNode.ts', + 'src/vs/workbench/contrib/comments/browser/commentThreadBody.ts', + 'src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts', + 'src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts', + 'src/vs/workbench/contrib/comments/browser/commentsView.ts', + 'src/vs/workbench/contrib/comments/browser/reactionsAction.ts', + 'src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts', + 'src/vs/workbench/contrib/customEditor/browser/customEditors.ts', + 'src/vs/workbench/contrib/customEditor/common/customEditor.ts', + 'src/vs/workbench/contrib/debug/browser/breakpointWidget.ts', + 'src/vs/workbench/contrib/debug/browser/breakpointsView.ts', + 'src/vs/workbench/contrib/debug/browser/callStackView.ts', + 'src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts', + 'src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts', + 'src/vs/workbench/contrib/debug/browser/debugCommands.ts', + 'src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts', + 'src/vs/workbench/contrib/debug/browser/debugEditorActions.ts', + 'src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts', + 'src/vs/workbench/contrib/debug/browser/debugHover.ts', + 'src/vs/workbench/contrib/debug/browser/debugService.ts', + 'src/vs/workbench/contrib/debug/browser/debugSession.ts', + 'src/vs/workbench/contrib/debug/browser/rawDebugSession.ts', + 'src/vs/workbench/contrib/debug/browser/repl.ts', + 'src/vs/workbench/contrib/debug/browser/replViewer.ts', + 'src/vs/workbench/contrib/debug/browser/variablesView.ts', + 'src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts', + 'src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts', + 'src/vs/workbench/contrib/debug/common/debug.ts', + 'src/vs/workbench/contrib/debug/common/debugModel.ts', + 'src/vs/workbench/contrib/debug/common/debugger.ts', + 'src/vs/workbench/contrib/debug/common/replModel.ts', + 'src/vs/workbench/contrib/debug/test/common/mockDebug.ts', + 'src/vs/workbench/contrib/editSessions/common/editSessionsStorageClient.ts', + 'src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionEditor.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts', + 'src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsActions.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsViews.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts', + 'src/vs/workbench/contrib/extensions/common/extensions.ts', + 'src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts', + 'src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts', + 'src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts', + 'src/vs/workbench/contrib/issue/browser/issueReporterModel.ts', + 'src/vs/workbench/contrib/list/browser/tableColumnResizeQuickPick.ts', + 'src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts', + 'src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts', + 'src/vs/workbench/contrib/markers/browser/markers.contribution.ts', + 'src/vs/workbench/contrib/markers/browser/markersTable.ts', + 'src/vs/workbench/contrib/markers/browser/markersView.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/utils.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/editActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/sectionActions.ts', + 'src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts', + 'src/vs/workbench/contrib/notebook/browser/diff/inlineDiff/notebookDeletedCellDecorator.ts', + 'src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts', + 'src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts', + 'src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts', + 'src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts', + 'src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts', + 'src/vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions.ts', + 'src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookMetadataTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/notebookCommon.ts', + 'src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts', + 'src/vs/workbench/contrib/notebook/common/notebookRange.ts', + 'src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts', + 'src/vs/workbench/contrib/outline/browser/outlinePane.ts', + 'src/vs/workbench/contrib/outline/browser/outlineViewState.ts', + 'src/vs/workbench/contrib/output/browser/outputView.ts', + 'src/vs/workbench/contrib/output/common/outputChannelModel.ts', + 'src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts', + 'src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts', + 'src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts', + 'src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts', + 'src/vs/workbench/contrib/remote/browser/tunnelView.ts', + 'src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts', + 'src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts', + 'src/vs/workbench/contrib/scm/browser/scmViewPane.ts', + 'src/vs/workbench/contrib/scm/browser/util.ts', + 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModel.ts', + 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModelBase.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchModel.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchModelBase.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts', + 'src/vs/workbench/contrib/search/browser/replace.ts', + 'src/vs/workbench/contrib/search/browser/replaceService.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsCopy.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsFind.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsNav.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts', + 'src/vs/workbench/contrib/search/browser/searchMessage.ts', + 'src/vs/workbench/contrib/search/browser/searchResultsView.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/folderMatch.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/textSearchHeading.ts', + 'src/vs/workbench/contrib/search/browser/searchView.ts', + 'src/vs/workbench/contrib/search/browser/searchWidget.ts', + 'src/vs/workbench/contrib/search/common/cacheState.ts', + 'src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts', + 'src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts', + 'src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts', + 'src/vs/workbench/contrib/snippets/browser/snippetsFile.ts', + 'src/vs/workbench/contrib/snippets/browser/snippetsService.ts', + 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', + 'src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts', + 'src/vs/workbench/contrib/tasks/browser/task.contribution.ts', + 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', + 'src/vs/workbench/contrib/tasks/common/jsonSchema_v1.ts', + 'src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts', + 'src/vs/workbench/contrib/tasks/common/problemMatcher.ts', + 'src/vs/workbench/contrib/tasks/common/taskConfiguration.ts', + 'src/vs/workbench/contrib/tasks/common/taskSystem.ts', + 'src/vs/workbench/contrib/tasks/common/tasks.ts', + 'src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalExtensions.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts', + 'src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts', + 'src/vs/workbench/contrib/terminal/common/basePty.ts', + 'src/vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel.ts', + 'src/vs/workbench/contrib/terminal/common/terminal.ts', + 'src/vs/workbench/contrib/terminalContrib/links/browser/links.ts', + 'src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts', + 'src/vs/workbench/contrib/testing/browser/testExplorerActions.ts', + 'src/vs/workbench/contrib/testing/browser/testingExplorerView.ts', + 'src/vs/workbench/contrib/testing/common/storedValue.ts', + 'src/vs/workbench/contrib/testing/common/testItemCollection.ts', + 'src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts', + 'src/vs/workbench/contrib/themes/browser/themes.contribution.ts', + 'src/vs/workbench/contrib/timeline/browser/timelinePane.ts', + 'src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts', + 'src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts', + 'src/vs/workbench/contrib/update/browser/update.ts', + 'src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts', + 'src/vs/workbench/contrib/webview/browser/overlayWebview.ts', + 'src/vs/workbench/contrib/webview/browser/webview.ts', + 'src/vs/workbench/contrib/webview/browser/webviewElement.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts', + 'src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedAccessibleView.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts', + 'src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts', + 'src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts', + 'src/vs/workbench/services/accounts/common/defaultAccount.ts', + 'src/vs/workbench/services/actions/common/menusExtensionPoint.ts', + 'src/vs/workbench/services/assignment/common/assignmentFilters.ts', + 'src/vs/workbench/services/authentication/common/authentication.ts', + 'src/vs/workbench/services/authentication/test/browser/authenticationQueryServiceMocks.ts', + 'src/vs/workbench/services/commands/common/commandService.ts', + 'src/vs/workbench/services/configuration/browser/configuration.ts', + 'src/vs/workbench/services/configuration/browser/configurationService.ts', + 'src/vs/workbench/services/configuration/common/configurationModels.ts', + 'src/vs/workbench/services/configuration/common/jsonEditingService.ts', + 'src/vs/workbench/services/configuration/test/common/testServices.ts', + 'src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts', + 'src/vs/workbench/services/configurationResolver/common/configurationResolver.ts', + 'src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts', + 'src/vs/workbench/services/configurationResolver/common/variableResolver.ts', + 'src/vs/workbench/services/driver/browser/driver.ts', + 'src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts', + 'src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts', + 'src/vs/workbench/services/extensions/common/extHostCustomers.ts', + 'src/vs/workbench/services/extensions/common/extensionHostManager.ts', + 'src/vs/workbench/services/extensions/common/extensionHostProtocol.ts', + 'src/vs/workbench/services/extensions/common/extensionHostProxy.ts', + 'src/vs/workbench/services/extensions/common/extensionsRegistry.ts', + 'src/vs/workbench/services/extensions/common/lazyPromise.ts', + 'src/vs/workbench/services/extensions/common/polyfillNestedWorker.protocol.ts', + 'src/vs/workbench/services/extensions/common/proxyIdentifier.ts', + 'src/vs/workbench/services/extensions/common/rpcProtocol.ts', + 'src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts', + 'src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts', + 'src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts', + 'src/vs/workbench/services/keybinding/browser/keybindingService.ts', + 'src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts', + 'src/vs/workbench/services/keybinding/common/keybindingEditing.ts', + 'src/vs/workbench/services/keybinding/common/keybindingIO.ts', + 'src/vs/workbench/services/keybinding/common/keymapInfo.ts', + 'src/vs/workbench/services/language/common/languageService.ts', + 'src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts', + 'src/vs/workbench/services/languageStatus/common/languageStatusService.ts', + 'src/vs/workbench/services/outline/browser/outline.ts', + 'src/vs/workbench/services/outline/browser/outlineService.ts', + 'src/vs/workbench/services/preferences/common/preferences.ts', + 'src/vs/workbench/services/preferences/common/preferencesModels.ts', + 'src/vs/workbench/services/preferences/common/preferencesValidation.ts', + 'src/vs/workbench/services/remote/browser/remoteAgentService.ts', + 'src/vs/workbench/services/remote/common/tunnelModel.ts', + 'src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts', + 'src/vs/workbench/services/search/common/replace.ts', + 'src/vs/workbench/services/search/common/search.ts', + 'src/vs/workbench/services/search/common/searchExtConversionTypes.ts', + 'src/vs/workbench/services/search/common/searchExtTypes.ts', + 'src/vs/workbench/services/search/common/searchService.ts', + 'src/vs/workbench/services/search/node/fileSearch.ts', + 'src/vs/workbench/services/search/node/rawSearchService.ts', + 'src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts', + 'src/vs/workbench/services/search/worker/localFileSearch.ts', + 'src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts', + 'src/vs/workbench/services/terminal/common/embedderTerminalService.ts', + 'src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts', + 'src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts', + 'src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts', + 'src/vs/workbench/services/textMate/common/TMGrammarFactory.ts', + 'src/vs/workbench/services/themes/browser/fileIconThemeData.ts', + 'src/vs/workbench/services/themes/browser/productIconThemeData.ts', + 'src/vs/workbench/services/themes/browser/workbenchThemeService.ts', + 'src/vs/workbench/services/themes/common/colorThemeData.ts', + 'src/vs/workbench/services/themes/common/plistParser.ts', + 'src/vs/workbench/services/themes/common/themeExtensionPoints.ts', + 'src/vs/workbench/services/themes/common/workbenchThemeService.ts', + 'src/vs/workbench/services/userActivity/common/userActivityRegistry.ts', + 'src/vs/workbench/services/userData/browser/userDataInit.ts', + 'src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts', + 'src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts', + 'src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts', + 'src/vs/workbench/services/userDataSync/common/userDataSync.ts', + 'src/vs/workbench/test/browser/workbenchTestServices.ts', + 'src/vs/workbench/test/common/workbenchTestServices.ts', + 'src/vs/workbench/test/electron-browser/workbenchTestServices.ts', + 'src/vs/workbench/workbench.web.main.internal.ts', + 'src/vs/workbench/workbench.web.main.ts', + // Server + 'src/vs/server/node/extensionHostConnection.ts', + 'src/vs/server/node/remoteAgentEnvironmentImpl.ts', + 'src/vs/server/node/remoteExtensionHostAgentServer.ts', + 'src/vs/server/node/remoteExtensionsScanner.ts', + 'src/vs/server/node/remoteTerminalChannel.ts', + 'src/vs/server/node/server.cli.ts', + 'src/vs/server/node/serverConnectionToken.ts', + // Tests + '**/*.test.ts', + '**/*.integrationTest.ts' ], - ignores: ['**/*.test.ts', '**/*.integrationTest.ts'], languageOptions: { parser: tseslint.parser, }, From dfa4b5b3c27b6dbdad5b0dc4fc333a90a3c104d9 Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:59:34 -0700 Subject: [PATCH 1277/4355] Add icon support for chat session editor tabs --- .../contrib/chat/browser/chatEditorInput.ts | 90 ++++++++++++++++++- .../chat/browser/chatSessions.contribution.ts | 25 ++++++ .../chat/common/chatSessionsService.ts | 2 + 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index bfeb8de21b7..b725de037dc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -19,7 +19,9 @@ import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js' import { EditorInputCapabilities, IEditorIdentifier, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js'; import { EditorInput, IEditorCloseHandler } from '../../../common/editor/editorInput.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; +import { IChatSessionsService } from '../common/chatSessionsService.js'; import { IChatModel } from '../common/chatModel.js'; +import { ChatSessionUri } from '../common/chatUri.js'; import { IChatService } from '../common/chatService.js'; import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; @@ -39,6 +41,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler public sessionId: string | undefined; private hasCustomTitle: boolean = false; + private cachedIcon: ThemeIcon | URI | undefined; private model: IChatModel | undefined; @@ -61,6 +64,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler readonly options: IChatEditorOptions, @IChatService private readonly chatService: IChatService, @IDialogService private readonly dialogService: IDialogService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super(); @@ -182,10 +186,75 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return defaultName + inputCountSuffix; } - override getIcon(): ThemeIcon { + override getIcon(): ThemeIcon | undefined { + // Return cached icon if available + if (this.cachedIcon) { + return ThemeIcon.isThemeIcon(this.cachedIcon) ? this.cachedIcon : undefined; + } + + // Try to resolve icon and cache it + const resolvedIcon = this.resolveIcon(); + if (resolvedIcon) { + this.cachedIcon = resolvedIcon; + return ThemeIcon.isThemeIcon(resolvedIcon) ? resolvedIcon : undefined; + } + + // Fall back to default icon return ChatEditorIcon; } + private resolveIcon(): ThemeIcon | URI | undefined { + // 1. Try to get session-specific icon from IChatSessionItem.iconPath (supports both ThemeIcon and URI) + if (this.sessionId) { + const sessionType = this.getSessionType(); + if (sessionType !== 'local') { + // For non-local sessions, try to find the session item + // Note: This is a best-effort synchronous lookup. Session items may not be loaded yet. + const providers = this.chatSessionsService.getAllChatSessionItemProviders(); + const provider = providers.find(p => p.chatSessionType === sessionType); + if (provider) { + // We can't await here, so we'll check for icon updates during resolve() + // For now, fall through to session type icon + } + } + } + + // 2. Fall back to session type icon from extension point + const sessionType = this.getSessionType(); + if (sessionType !== 'local') { + const typeIcon = this.chatSessionsService.getIconForSessionType(sessionType); + if (typeIcon) { + return typeIcon; + } + } + + // 3. No custom icon found + return undefined; + } + + private getSessionType(): string { + if (!this.resource) { + return 'local'; + } + + const { scheme, query } = this.resource; + + if (scheme === Schemas.vscodeChatSession) { + const parsed = ChatSessionUri.parse(this.resource); + if (parsed) { + return parsed.chatSessionType; + } + } + + const sessionTypeFromQuery = new URLSearchParams(query).get('chatSessionType'); + if (sessionTypeFromQuery) { + return sessionTypeFromQuery; + } + + // Default to 'local' for vscode-chat-editor scheme or when type cannot be determined + return 'local'; + } + override async resolve(): Promise { const searchParams = new URLSearchParams(this.resource.query); const chatSessionType = searchParams.get('chatSessionType'); @@ -215,12 +284,31 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler ChatEditorInput.countsInUseMap.delete(this.inputName); } } + // Invalidate icon cache when label changes + this.cachedIcon = undefined; this._onDidChangeLabel.fire(); })); + // Check if icon has changed after model resolution + const newIcon = this.resolveIcon(); + if (newIcon && (!this.cachedIcon || !this.iconsEqual(this.cachedIcon, newIcon))) { + this.cachedIcon = newIcon; + this._onDidChangeLabel.fire(); + } + return this._register(new ChatEditorModel(this.model)); } + private iconsEqual(a: ThemeIcon | URI, b: ThemeIcon | URI): boolean { + if (ThemeIcon.isThemeIcon(a) && ThemeIcon.isThemeIcon(b)) { + return a.id === b.id; + } + if (a instanceof URI && b instanceof URI) { + return a.toString() === b.toString(); + } + return false; + } + override dispose(): void { super.dispose(); if (this.sessionId) { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 7ea23ab4316..3b93af99507 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -7,6 +7,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize, localize2 } from '../../../../nls.js'; @@ -58,6 +59,10 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = new Map(); private readonly _sessionTypeModels: Map = new Map(); + private readonly _sessionTypeIcons: Map = new Map(); constructor( @ILogService private readonly _logService: ILogService, @@ -182,6 +188,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ displayName: contribution.displayName, description: contribution.description, when: contribution.when, + icon: contribution.icon, capabilities: contribution.capabilities, extensionDescription: ext.description, commands: contribution.commands @@ -249,11 +256,22 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } this._contributions.set(contribution.type, contribution); + + // Store icon mapping if provided + if (contribution.icon) { + // Parse icon string - support both "$(iconId)" and "iconId" formats + const iconId = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')') + ? contribution.icon.slice(2, -1) + : contribution.icon; + this._sessionTypeIcons.set(contribution.type, ThemeIcon.fromId(iconId)); + } + this._evaluateAvailability(); return { dispose: () => { this._contributions.delete(contribution.type); + this._sessionTypeIcons.delete(contribution.type); const store = this._disposableStores.get(contribution.type); if (store) { store.dispose(); @@ -684,6 +702,13 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this.setSessionOption(chatSessionType, sessionId, u.optionId, u.value); } } + + /** + * Get the icon for a specific session type + */ + public getIconForSessionType(chatSessionType: string): ThemeIcon | undefined { + return this._sessionTypeIcons.get(chatSessionType); + } } registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 0b22578f247..0055a4afc4d 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -36,6 +36,7 @@ export interface IChatSessionsExtensionPoint { readonly description: string; readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; + readonly icon?: string; readonly capabilities?: { supportsFileAttachments?: boolean; supportsToolAttachments?: boolean; @@ -121,6 +122,7 @@ export interface IChatSessionsService { getAllChatSessionContributions(): IChatSessionsExtensionPoint[]; canResolveItemProvider(chatSessionType: string): Promise; getAllChatSessionItemProviders(): IChatSessionItemProvider[]; + getIconForSessionType(chatSessionType: string): ThemeIcon | undefined; provideNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: any; From f7fc5ff92e7df95c110abd67be643817b5e93295 Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:59:58 -0700 Subject: [PATCH 1278/4355] Add icon support for chat session editor tabs --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index b725de037dc..85917e73b9a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -19,10 +19,10 @@ import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js' import { EditorInputCapabilities, IEditorIdentifier, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js'; import { EditorInput, IEditorCloseHandler } from '../../../common/editor/editorInput.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; -import { IChatSessionsService } from '../common/chatSessionsService.js'; import { IChatModel } from '../common/chatModel.js'; -import { ChatSessionUri } from '../common/chatUri.js'; import { IChatService } from '../common/chatService.js'; +import { IChatSessionsService } from '../common/chatSessionsService.js'; +import { ChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; import type { IChatEditorOptions } from './chatEditor.js'; From 65b2dcd24af55ca7a9e3672582390e7d20bd7c4b Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:21:04 -0700 Subject: [PATCH 1279/4355] Add support for provider specific messaging in the editor --- .../chat/browser/chatSessions.contribution.ts | 53 +++++++++++++++++++ .../contrib/chat/browser/chatWidget.ts | 20 +++++-- .../chat/common/chatSessionsService.ts | 6 +++ 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 3b93af99507..97dbc6edb9d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -63,6 +63,18 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = new Map(); private readonly _sessionTypeModels: Map = new Map(); private readonly _sessionTypeIcons: Map = new Map(); + private readonly _sessionTypeWelcomeTitles: Map = new Map(); + private readonly _sessionTypeWelcomeMessages: Map = new Map(); + private readonly _sessionTypeInputPlaceholders: Map = new Map(); constructor( @ILogService private readonly _logService: ILogService, @@ -189,6 +204,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ description: contribution.description, when: contribution.when, icon: contribution.icon, + welcomeTitle: contribution.welcomeTitle, + welcomeMessage: contribution.welcomeMessage, + inputPlaceholder: contribution.inputPlaceholder, capabilities: contribution.capabilities, extensionDescription: ext.description, commands: contribution.commands @@ -266,12 +284,26 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessionTypeIcons.set(contribution.type, ThemeIcon.fromId(iconId)); } + // Store welcome title, message, and input placeholder if provided + if (contribution.welcomeTitle) { + this._sessionTypeWelcomeTitles.set(contribution.type, contribution.welcomeTitle); + } + if (contribution.welcomeMessage) { + this._sessionTypeWelcomeMessages.set(contribution.type, contribution.welcomeMessage); + } + if (contribution.inputPlaceholder) { + this._sessionTypeInputPlaceholders.set(contribution.type, contribution.inputPlaceholder); + } + this._evaluateAvailability(); return { dispose: () => { this._contributions.delete(contribution.type); this._sessionTypeIcons.delete(contribution.type); + this._sessionTypeWelcomeTitles.delete(contribution.type); + this._sessionTypeWelcomeMessages.delete(contribution.type); + this._sessionTypeInputPlaceholders.delete(contribution.type); const store = this._disposableStores.get(contribution.type); if (store) { store.dispose(); @@ -709,6 +741,27 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public getIconForSessionType(chatSessionType: string): ThemeIcon | undefined { return this._sessionTypeIcons.get(chatSessionType); } + + /** + * Get the welcome title for a specific session type + */ + public getWelcomeTitleForSessionType(chatSessionType: string): string | undefined { + return this._sessionTypeWelcomeTitles.get(chatSessionType); + } + + /** + * Get the welcome message for a specific session type + */ + public getWelcomeMessageForSessionType(chatSessionType: string): string | undefined { + return this._sessionTypeWelcomeMessages.get(chatSessionType); + } + + /** + * Get the input placeholder for a specific session type + */ + public getInputPlaceholderForSessionType(chatSessionType: string): string | undefined { + return this._sessionTypeInputPlaceholders.get(chatSessionType); + } } registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 39691612fb1..31a80f60918 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -66,6 +66,7 @@ import { IChatModeService } from '../common/chatModes.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../common/chatParserTypes.js'; import { ChatRequestParser } from '../common/chatRequestParser.js'; import { IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; +import { IChatSessionsService } from '../common/chatSessionsService.js'; import { IChatSlashCommandService } from '../common/chatSlashCommands.js'; import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, PromptFileVariableKind, toPromptFileVariableEntry } from '../common/chatVariableEntries.js'; import { ChatViewModel, IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; @@ -482,6 +483,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, @ICommandService private readonly commandService: ICommandService, @IHoverService private readonly hoverService: IHoverService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super(); this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService); @@ -1412,10 +1414,15 @@ export class ChatWidget extends Disposable implements IChatWidget { additionalMessage = localize('expChatAdditionalMessage', "AI responses may be inaccurate."); } + // Check for provider-specific customizations + const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; + const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; + const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; + const welcomeContent: IChatViewWelcomeContent = { - title: localize('expChatTitle', 'Build with agent mode'), - message: new MarkdownString(localize('expchatMessage', "Let's get started")), - icon: Codicon.chatSparkle, + title: providerTitle ?? localize('expChatTitle', 'Build with agent mode'), + message: providerMessage ? new MarkdownString(providerMessage) : new MarkdownString(localize('expchatMessage', "Let's get started")), + icon: providerIcon ?? Codicon.chatSparkle, inputPart: this.inputPart.element, additionalMessage, isNew: true, @@ -2268,8 +2275,11 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container.setAttribute('data-session-id', model.sessionId); this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); - if (this._lockedToCodingAgent) { - const placeholder = localize('chat.input.placeholder.lockedToAgent', "Chat with {0}", this._lockedToCodingAgent); + if (this._lockedAgentId) { + let placeholder = this._lockedAgentId ? this.chatSessionsService.getInputPlaceholderForSessionType(this._lockedAgentId) : undefined; + if (!placeholder) { + placeholder = localize('chat.input.placeholder.lockedToAgent', "Chat with {0}", this._lockedAgentId); + } this.viewModel.setInputPlaceholder(placeholder); this.inputEditor.updateOptions({ placeholder }); } else if (this.viewModel.inputPlaceholder) { diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 0055a4afc4d..4be7387d2b6 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -37,6 +37,9 @@ export interface IChatSessionsExtensionPoint { readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; readonly icon?: string; + readonly welcomeTitle?: string; + readonly welcomeMessage?: string; + readonly inputPlaceholder?: string; readonly capabilities?: { supportsFileAttachments?: boolean; supportsToolAttachments?: boolean; @@ -123,6 +126,9 @@ export interface IChatSessionsService { canResolveItemProvider(chatSessionType: string): Promise; getAllChatSessionItemProviders(): IChatSessionItemProvider[]; getIconForSessionType(chatSessionType: string): ThemeIcon | undefined; + getWelcomeTitleForSessionType(chatSessionType: string): string | undefined; + getWelcomeMessageForSessionType(chatSessionType: string): string | undefined; + getInputPlaceholderForSessionType(chatSessionType: string): string | undefined; provideNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: any; From 16069a3656a2001583a1a76311ee48625e25a0f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:32:54 -0700 Subject: [PATCH 1280/4355] Prevent hover creation if content is empty string Fixes #271507 --- src/vs/editor/browser/services/hoverService/hoverService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 673d8852da9..b93fb6e88ca 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -192,6 +192,10 @@ export class HoverService extends Disposable implements IHoverService { private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { this._currentDelayedHover = undefined; + if (options.content === '') { + return undefined; + } + if (this._currentHover?.isLocked) { return undefined; } From 8a68ac06c549721fef21bd56c37f65e926340d18 Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:37:34 -0700 Subject: [PATCH 1281/4355] don't show suggested prompts for agents --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 31a80f60918..fd0169b2051 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1418,6 +1418,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; + const suggestedPrompts = this._lockedAgentId ? undefined : this.getNewSuggestedPrompts(); const welcomeContent: IChatViewWelcomeContent = { title: providerTitle ?? localize('expChatTitle', 'Build with agent mode'), @@ -1426,7 +1427,7 @@ export class ChatWidget extends Disposable implements IChatWidget { inputPart: this.inputPart.element, additionalMessage, isNew: true, - suggestedPrompts: this.getNewSuggestedPrompts(), + suggestedPrompts }; return welcomeContent; } From bfba6b040c556dc7baef5e325eadfe7ebbbdd62b Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Sat, 18 Oct 2025 15:57:07 -0700 Subject: [PATCH 1282/4355] Add toggle support for QuickInput/QuickPick, resourceUri support for QuickPickItem (#271598) * Add quickPickItemResource API proposal * Transfer resourceUri from extension host to main thread. * Make proposed API checks consistent. * Process resourceUri * Fix up resourceUri mapping logic * API proposal * Transfer toggles from extension host to main thread * Support Folder icon, refactor label/description derivation. * Update * Update API proposal per API review * Update transfer logic per API changes * Move toggles to the base input interface * Handle toggle button type * Fix up * Updates * Propagate checked state, dispose removed toggles. * Nit * Expand icons * Feedback/updates * Added comments, PR feedback * Updates * Revert some change, add typings and unit-tests to converters. * Add a quick pick test for resourceUri * Test updates --- extensions/vscode-api-tests/package.json | 2 + .../src/singlefolder-tests/quickInput.test.ts | 67 ++++- src/vs/base/common/themables.ts | 13 + .../common/extensionsApiProposals.ts | 3 + .../platform/quickinput/common/quickInput.ts | 18 +- .../api/browser/mainThreadQuickOpen.ts | 250 ++++++++++++++---- .../workbench/api/common/extHost.protocol.ts | 31 ++- .../workbench/api/common/extHostQuickOpen.ts | 102 +++---- .../api/common/extHostTypeConverters.ts | 50 ++++ src/vs/workbench/api/common/extHostTypes.ts | 3 +- .../test/common/extHostTypeConverters.test.ts | 123 +++++++++ .../workbench/browser/parts/views/treeView.ts | 10 +- ...ode.proposed.quickInputButtonLocation.d.ts | 14 +- ...vscode.proposed.quickPickItemResource.d.ts | 21 ++ 14 files changed, 557 insertions(+), 150 deletions(-) create mode 100644 src/vs/workbench/api/test/common/extHostTypeConverters.test.ts create mode 100644 src/vscode-dts/vscode.proposed.quickPickItemResource.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index aaad54485df..10eb7e68fe9 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -32,7 +32,9 @@ "notebookMessaging", "notebookMime", "portsAttributes", + "quickInputButtonLocation", "quickPickSortByLabel", + "quickPickItemResource", "resolvers", "scmActionButton", "scmSelectedProvider", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 4f8331c286f..485e3d85f34 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { commands, Disposable, QuickPick, QuickPickItem, window } from 'vscode'; +import { commands, Disposable, QuickPick, QuickPickItem, window, workspace } from 'vscode'; import { assertNoRpc, closeAllEditors } from '../utils'; interface QuickPickExpected { @@ -248,6 +248,71 @@ suite('vscode API - quick input', function () { quickPick.hide(); await waitForHide(quickPick); }); + + test('createQuickPick, match item by label derived from resourceUri', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['']], + selectionItems: [['']], + acceptedItems: { + active: [['']], + selection: [['']], + dispose: [true] + }, + }, (err?: any) => done(err)); + + const baseUri = workspace!.workspaceFolders![0].uri; + quickPick.items = [ + { label: 'a1', resourceUri: baseUri.with({ path: baseUri.path + '/test1.txt' }) }, + { label: '', resourceUri: baseUri.with({ path: baseUri.path + '/test2.txt' }) }, + { label: 'a3', resourceUri: baseUri.with({ path: baseUri.path + '/test3.txt' }) } + ]; + quickPick.value = 'test2.txt'; + quickPick.show(); + + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); + + test('createQuickPick, match item by description derived from resourceUri', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['a2']], + selectionItems: [['a2']], + acceptedItems: { + active: [['a2']], + selection: [['a2']], + dispose: [true] + }, + }, (err?: any) => done(err)); + + const baseUri = workspace!.workspaceFolders![0].uri; + quickPick.items = [ + { label: 'a1', resourceUri: baseUri.with({ path: baseUri.path + '/test1.txt' }) }, + { label: 'a2', resourceUri: baseUri.with({ path: baseUri.path + '/test2.txt' }) }, + { label: 'a3', resourceUri: baseUri.with({ path: baseUri.path + '/test3.txt' }) } + ]; + quickPick.matchOnDescription = true; + quickPick.value = 'test2.txt'; + quickPick.show(); + + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); }); function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) { diff --git a/src/vs/base/common/themables.ts b/src/vs/base/common/themables.ts index 6b2551bebb2..dcc1369e593 100644 --- a/src/vs/base/common/themables.ts +++ b/src/vs/base/common/themables.ts @@ -101,4 +101,17 @@ export namespace ThemeIcon { return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id; } + /** + * Returns whether specified icon is defined and has 'file' ID. + */ + export function isFile(icon: ThemeIcon | undefined): boolean { + return icon?.id === Codicon.file.id; + } + + /** + * Returns whether specified icon is defined and has 'folder' ID. + */ + export function isFolder(icon: ThemeIcon | undefined): boolean { + return icon?.id === Codicon.folder.id; + } } diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 3eec8979844..cae07b0fd1b 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -328,6 +328,9 @@ const _allApiProposals = { quickInputButtonLocation: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts', }, + quickPickItemResource: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemResource.d.ts', + }, quickPickItemTooltip: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', }, diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 3716a235669..bafa97203bf 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -359,6 +359,11 @@ export interface IQuickInput extends IDisposable { */ ignoreFocusOut: boolean; + /** + * The toggle buttons to be added to the input box. + */ + toggles: IQuickInputToggle[] | undefined; + /** * Shows the quick input. */ @@ -694,11 +699,6 @@ export interface IQuickPick; + handlesToToggles: Map; store: DisposableStore; } -function reviveIconPathUris(iconPath: { dark: URI; light?: URI | undefined }) { - iconPath.dark = URI.revive(iconPath.dark); - if (iconPath.light) { - iconPath.light = URI.revive(iconPath.light); - } -} - @extHostNamedCustomer(MainContext.MainThreadQuickOpen) export class MainThreadQuickOpen implements MainThreadQuickOpenShape { @@ -35,7 +39,11 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { constructor( extHostContext: IExtHostContext, - @IQuickInputService quickInputService: IQuickInputService + @IQuickInputService quickInputService: IQuickInputService, + @ILabelService private readonly labelService: ILabelService, + @ICustomEditorLabelService private readonly customEditorLabelService: ICustomEditorLabelService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostQuickOpen); this._quickInputService = quickInputService; @@ -80,6 +88,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { $setItems(instance: number, items: TransferQuickPickItemOrSeparator[]): Promise { if (this._items[instance]) { + items.forEach(item => this.expandItemProps(item)); this._items[instance].resolve(items); delete this._items[instance]; } @@ -159,62 +168,79 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { session = { input, handlesToItems: new Map(), + handlesToToggles: new Map(), store }; this.sessions.set(sessionId, session); } + const { input, handlesToItems } = session; + const quickPick = input as IQuickPick; for (const param in params) { - if (param === 'id' || param === 'type') { - continue; - } - if (param === 'visible') { - if (params.visible) { - input.show(); - } else { - input.hide(); - } - } else if (param === 'items') { - handlesToItems.clear(); - params[param].forEach((item: TransferQuickPickItemOrSeparator) => { - if (item.type === 'separator') { - return; + switch (param) { + case 'id': + case 'type': + continue; + + case 'visible': + if (params.visible) { + input.show(); + } else { + input.hide(); } + break; - if (item.buttons) { - item.buttons = item.buttons.map((button: TransferQuickInputButton) => { - if (button.iconPath) { - reviveIconPathUris(button.iconPath); - } + case 'items': { + handlesToItems.clear(); + params.items?.forEach((item: TransferQuickPickItemOrSeparator) => { + this.expandItemProps(item); + if (item.type !== 'separator') { + item.buttons?.forEach(button => this.expandIconPath(button)); + handlesToItems.set(item.handle, item); + } + }); + quickPick.items = params.items; + break; + } - return button; - }); - } - handlesToItems.set(item.handle, item); - }); - // eslint-disable-next-line local/code-no-any-casts - (input as any)[param] = params[param]; - } else if (param === 'activeItems' || param === 'selectedItems') { - // eslint-disable-next-line local/code-no-any-casts - (input as any)[param] = params[param] - .filter((handle: number) => handlesToItems.has(handle)) - .map((handle: number) => handlesToItems.get(handle)); - } else if (param === 'buttons') { - // eslint-disable-next-line local/code-no-any-casts - (input as any)[param] = params.buttons!.map(button => { - if (button.handle === -1) { - return this._quickInputService.backButton; - } + case 'activeItems': + quickPick.activeItems = params.activeItems + ?.map((handle: number) => handlesToItems.get(handle)) + .filter(Boolean); + break; - if (button.iconPath) { - reviveIconPathUris(button.iconPath); + case 'selectedItems': + quickPick.selectedItems = params.selectedItems + ?.map((handle: number) => handlesToItems.get(handle)) + .filter(Boolean); + break; + + case 'buttons': { + const buttons = [], toggles = []; + for (const button of params.buttons!) { + if (button.handle === -1) { + buttons.push(this._quickInputService.backButton); + } else { + this.expandIconPath(button); + + // Currently buttons are only supported outside of the input box + // and toggles only inside. When/if that changes, this will need to be updated. + if (button.location === QuickInputButtonLocation.Input) { + toggles.push(button); + } else { + buttons.push(button); + } + } } + input.buttons = buttons; + this.updateToggles(sessionId, session, toggles); + break; + } - return button; - }); - } else { - // eslint-disable-next-line local/code-no-any-casts - (input as any)[param] = params[param]; + default: + // eslint-disable-next-line local/code-no-any-casts + (input as any)[param] = params[param]; + break; } } return Promise.resolve(undefined); @@ -228,4 +254,114 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } return Promise.resolve(undefined); } + + /** + * Derives icon, label and description for Quick Pick items that represent a resource URI. + */ + private expandItemProps(item: TransferQuickPickItemOrSeparator) { + if (item.type === 'separator') { + return; + } + + if (!item.resourceUri) { + this.expandIconPath(item); + return; + } + + // Derive missing label and description from resourceUri. + const resourceUri = URI.from(item.resourceUri); + item.label ??= this.customEditorLabelService.getName(resourceUri) || ''; + if (item.label) { + item.description ??= this.labelService.getUriLabel(resourceUri, { relative: true }); + } else { + item.label = basenameOrAuthority(resourceUri); + item.description ??= this.labelService.getUriLabel(dirname(resourceUri), { relative: true }); + } + + // Derive icon props from resourceUri if icon is set to ThemeIcon.File or ThemeIcon.Folder. + const icon = item.iconPathDto; + if (ThemeIcon.isThemeIcon(icon) && (ThemeIcon.isFile(icon) || ThemeIcon.isFolder(icon))) { + const iconClasses = new Lazy(() => getIconClasses(this.modelService, this.languageService, resourceUri)); + Object.defineProperty(item, 'iconClasses', { get: () => iconClasses.value }); + } else { + this.expandIconPath(item); + } + } + + /** + * Converts IconPath DTO into iconPath/iconClass properties. + */ + private expandIconPath(target: Pick) { + const icon = target.iconPathDto; + if (!icon) { + return; + } else if (ThemeIcon.isThemeIcon(icon)) { + // TODO: Since IQuickPickItem and IQuickInputButton do not support ThemeIcon directly, the color ID is lost here. + // We should consider changing changing iconPath/iconClass to IconPath in both interfaces. + // Request for color support: https://github.com/microsoft/vscode/issues/185356.. + target.iconClass = ThemeIcon.asClassName(icon); + } else if (isUriComponents(icon)) { + const uri = URI.from(icon); + target.iconPath = { dark: uri, light: uri }; + } else { + const { dark, light } = icon; + target.iconPath = { dark: URI.from(dark), light: URI.from(light) }; + } + } + + /** + * Updates the toggles for a given quick input session by creating new {@link Toggle}-s + * from buttons, updating existing toggles props and removing old ones. + */ + private updateToggles(sessionId: number, session: QuickInputSession, buttons: TransferQuickInputButton[]) { + const { input, handlesToToggles, store } = session; + + // Add new or update existing toggles. + const toggles = []; + for (const button of buttons) { + const title = button.tooltip || ''; + const isChecked = !!button.checked; + + // TODO: Toggle class only supports ThemeIcon at the moment, but not other formats of IconPath. + // We should consider adding support for the full IconPath to Toggle, in this code should be updated. + const icon = ThemeIcon.isThemeIcon(button.iconPathDto) ? button.iconPathDto : undefined; + + let { toggle } = handlesToToggles.get(button.handle) || {}; + if (toggle) { + // Toggle already exists, update its props. + toggle.setTitle(title); + toggle.setIcon(icon); + toggle.checked = isChecked; + } else { + // Create a new toggle from the button. + toggle = store.add(new Toggle({ + title, + icon, + isChecked, + inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), + inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), + inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) + })); + + const listener = store.add(toggle.onChange(() => { + this._proxy.$onDidTriggerButton(sessionId, button.handle, toggle!.checked); + })); + + handlesToToggles.set(button.handle, { toggle, listener }); + } + toggles.push(toggle); + } + + // Remove toggles that are no longer present from the session map. + for (const [handle, { toggle, listener }] of handlesToToggles) { + if (!buttons.some(button => button.handle === handle)) { + handlesToToggles.delete(handle); + store.delete(toggle); + store.delete(listener); + } + } + + // Update toggle interfaces on the input widget. + input.toggles = toggles; + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 497ed675745..8c1fc131190 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -98,6 +98,11 @@ import { IExtHostDocumentSaveDelegate } from './extHostDocumentData.js'; import { TerminalShellExecutionCommandLineConfidence } from './extHostTypes.js'; import * as tasks from './shared/tasks.js'; +export type IconPathDto = + | UriComponents + | { light: UriComponents; dark: UriComponents } + | ThemeIcon; + export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; } @@ -614,17 +619,29 @@ export interface TransferQuickPickItem { // shared properties from IQuickPickItem type?: 'item'; label: string; - iconPath?: { light?: URI; dark: URI }; - iconClass?: string; + iconPathDto?: IconPathDto; description?: string; detail?: string; picked?: boolean; alwaysShow?: boolean; buttons?: TransferQuickInputButton[]; + resourceUri?: UriComponents; + + // TODO: These properties are not used for transfer (iconPathDto is used instead) but they cannot be removed + // because this type is used as IQuickPickItem on the main thread. Ideally IQuickPickItem should also use IconPath. + iconPath?: { light?: URI; dark: URI }; + iconClass?: string; } export interface TransferQuickInputButton extends quickInput.IQuickInputButton { handle: number; + iconPathDto: IconPathDto; + checked?: boolean; + + // TODO: These properties are not used for transfer (iconPathDto is used instead) but they cannot be removed + // because this type is used as IQuickInputButton on the main thread. Ideally IQuickInputButton should also use IconPath. + iconPath?: { light?: URI; dark: URI }; + iconClass?: string; } export type TransferQuickInput = TransferQuickPick | TransferInputBox; @@ -1635,7 +1652,7 @@ export interface SCMHistoryItemRefDto { readonly revision?: string; readonly category?: string; readonly description?: string; - readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; + readonly icon?: IconPathDto; } export interface SCMHistoryItemRefsChangeEventDto { @@ -1652,7 +1669,7 @@ export interface SCMHistoryItemDto { readonly message: string; readonly displayId?: string; readonly author?: string; - readonly authorIcon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; + readonly authorIcon?: IconPathDto; readonly authorEmail?: string; readonly timestamp?: number; readonly statistics?: { @@ -1671,7 +1688,7 @@ export interface SCMHistoryItemChangeDto { } export interface MainThreadSCMShape extends IDisposable { - $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, inputBoxDocumentUri: UriComponents): Promise; + $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: IconPathDto | undefined, inputBoxDocumentUri: UriComponents): Promise; $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise; $unregisterSourceControl(handle: number): Promise; @@ -2198,7 +2215,7 @@ export interface IWorkspaceEditEntryMetadataDto { needsConfirmation: boolean; label: string; description?: string; - iconPath?: { id: string } | UriComponents | { light: UriComponents; dark: UriComponents }; + iconPath?: IconPathDto; } export interface IChatNotebookEditDto { @@ -2423,7 +2440,7 @@ export interface ExtHostQuickOpenShape { $onDidChangeSelection(sessionId: number, handles: number[]): void; $onDidAccept(sessionId: number): void; $onDidChangeValue(sessionId: number, value: string): void; - $onDidTriggerButton(sessionId: number, handle: number): void; + $onDidTriggerButton(sessionId: number, handle: number, checked?: boolean): void; $onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number): void; $onDidHide(sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 05598c71d6a..8824dba941e 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -10,15 +10,13 @@ import { ExtHostCommands } from './extHostCommands.js'; import { IExtHostWorkspaceProvider } from './extHostWorkspace.js'; import { InputBox, InputBoxOptions, InputBoxValidationMessage, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickInput, TransferQuickInputButton, TransferQuickPickItemOrSeparator } from './extHost.protocol.js'; -import { URI } from '../../../base/common/uri.js'; -import { ThemeIcon, QuickInputButtons, QuickPickItemKind, InputBoxValidationSeverity } from './extHostTypes.js'; +import { QuickInputButtons, QuickPickItemKind, InputBoxValidationSeverity } from './extHostTypes.js'; import { isCancellationError } from '../../../base/common/errors.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { coalesce } from '../../../base/common/arrays.js'; import Severity from '../../../base/common/severity.js'; -import { ThemeIcon as ThemeIconUtils } from '../../../base/common/themables.js'; -import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; -import { MarkdownString } from './extHostTypeConverters.js'; +import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { IconPath, MarkdownString } from './extHostTypeConverters.js'; export type Item = string | QuickPickItem; @@ -90,8 +88,6 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx return undefined; } - const allowedTooltips = isProposedApiEnabled(extension, 'quickPickItemTooltip'); - return itemsPromise.then(items => { const pickItems: TransferQuickPickItemOrSeparator[] = []; @@ -102,20 +98,23 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } else if (item.kind === QuickPickItemKind.Separator) { pickItems.push({ type: 'separator', label: item.label }); } else { - if (item.tooltip && !allowedTooltips) { - console.warn(`Extension '${extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.identifier.value}`); + if (item.tooltip) { + checkProposedApiEnabled(extension, 'quickPickItemTooltip'); + } + + if (item.resourceUri) { + checkProposedApiEnabled(extension, 'quickPickItemResource'); } - const icon = (item.iconPath) ? getIconPathOrClass(item.iconPath) : undefined; pickItems.push({ label: item.label, - iconPath: icon?.iconPath, - iconClass: icon?.iconClass, + iconPathDto: IconPath.from(item.iconPath), description: item.description, detail: item.detail, picked: item.picked, alwaysShow: item.alwaysShow, - tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined, + tooltip: MarkdownString.fromStrict(item.tooltip), + resourceUri: item.resourceUri, handle }); } @@ -256,9 +255,9 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } } - $onDidTriggerButton(sessionId: number, handle: number): void { + $onDidTriggerButton(sessionId: number, handle: number, checked?: boolean): void { const session = this._sessions.get(sessionId); - session?._fireDidTriggerButton(handle); + session?._fireDidTriggerButton(handle, checked); } $onDidTriggerItemButton(sessionId: number, itemHandle: number, buttonHandle: number): void { @@ -400,9 +399,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } set buttons(buttons: QuickInputButton[]) { - const allowedButtonLocation = isProposedApiEnabled(this._extension, 'quickInputButtonLocation'); - if (!allowedButtonLocation && buttons.some(button => button.location)) { - console.warn(`Extension '${this._extension.identifier.value}' uses a button location which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); + if (buttons.some(button => button.location || button.checked !== undefined)) { + checkProposedApiEnabled(this._extension, 'quickInputButtonLocation'); } this._buttons = buttons.slice(); this._handlesToButtons.clear(); @@ -413,10 +411,11 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this.update({ buttons: buttons.map((button, i) => { return { - ...getIconPathOrClass(button.iconPath), + iconPathDto: IconPath.from(button.iconPath), tooltip: button.tooltip, handle: button === QuickInputButtons.Back ? -1 : i, - location: allowedButtonLocation ? button.location : undefined + location: button.location, + checked: button.checked }; }) }); @@ -446,9 +445,12 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._onDidChangeValueEmitter.fire(value); } - _fireDidTriggerButton(handle: number) { + _fireDidTriggerButton(handle: number, checked?: boolean) { const button = this._handlesToButtons.get(handle); if (button) { + if (checked !== undefined) { + button.checked = checked; + } this._onDidTriggerButtonEmitter.fire(button); } } @@ -499,7 +501,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } this.dispatchUpdate(); } else if (this._visible && !this._updateTimeout) { - // Defer the update so that multiple changes to setters dont cause a redraw each + // Defer the update so that multiple changes to setters don't cause a redraw each this._updateTimeout = setTimeout(() => { this._updateTimeout = undefined; this.dispatchUpdate(); @@ -513,43 +515,6 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } } - function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI; light?: URI } | { id: string } { - if (iconPath instanceof ThemeIcon) { - return { id: iconPath.id }; - } - const dark = getDarkIconUri(iconPath as URI | { light: URI; dark: URI }); - const light = getLightIconUri(iconPath as URI | { light: URI; dark: URI }); - // Tolerate strings: https://github.com/microsoft/vscode/issues/110432#issuecomment-726144556 - return { - dark: typeof dark === 'string' ? URI.file(dark) : dark, - light: typeof light === 'string' ? URI.file(light) : light - }; - } - - function getLightIconUri(iconPath: URI | { light: URI; dark: URI }) { - return typeof iconPath === 'object' && 'light' in iconPath ? iconPath.light : iconPath; - } - - function getDarkIconUri(iconPath: URI | { light: URI; dark: URI }) { - return typeof iconPath === 'object' && 'dark' in iconPath ? iconPath.dark : iconPath; - } - - function getIconPathOrClass(icon: QuickInputButton['iconPath']) { - const iconPathOrIconClass = getIconUris(icon); - let iconPath: { dark: URI; light?: URI | undefined } | undefined; - let iconClass: string | undefined; - if ('id' in iconPathOrIconClass) { - iconClass = ThemeIconUtils.asClassName(iconPathOrIconClass); - } else { - iconPath = iconPathOrIconClass; - } - - return { - iconPath, - iconClass - }; - } - class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { private _items: T[] = []; @@ -590,32 +555,33 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._itemsToHandles.set(item, i); }); - const allowedTooltips = isProposedApiEnabled(this._extension, 'quickPickItemTooltip'); - const pickItems: TransferQuickPickItemOrSeparator[] = []; for (let handle = 0; handle < items.length; handle++) { const item = items[handle]; if (item.kind === QuickPickItemKind.Separator) { pickItems.push({ type: 'separator', label: item.label }); } else { - if (item.tooltip && !allowedTooltips) { - console.warn(`Extension '${this._extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); + if (item.tooltip) { + checkProposedApiEnabled(this._extension, 'quickPickItemTooltip'); + } + + if (item.resourceUri) { + checkProposedApiEnabled(this._extension, 'quickPickItemResource'); } - const icon = (item.iconPath) ? getIconPathOrClass(item.iconPath) : undefined; pickItems.push({ handle, label: item.label, - iconPath: icon?.iconPath, - iconClass: icon?.iconClass, + iconPathDto: IconPath.from(item.iconPath), description: item.description, detail: item.detail, picked: item.picked, alwaysShow: item.alwaysShow, - tooltip: allowedTooltips ? MarkdownString.fromStrict(item.tooltip) : undefined, + tooltip: MarkdownString.fromStrict(item.tooltip), + resourceUri: item.resourceUri, buttons: item.buttons?.map((button, i) => { return { - ...getIconPathOrClass(button.iconPath), + iconPathDto: IconPath.from(button.iconPath), tooltip: button.tooltip, handle: i }; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6571d8f86d8..3edcbf7cb7d 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3732,6 +3732,56 @@ export namespace IconPath { export function fromThemeIcon(iconPath: vscode.ThemeIcon): languages.IconPath { return iconPath; } + + /** + * Converts a {@link vscode.IconPath} to an {@link extHostProtocol.IconPathDto}. + * @note This function will tolerate strings specified instead of URIs in IconPath for historical reasons. + * Such strings are treated as file paths and converted using {@link URI.file} function, not {@link URI.from}. + * See https://github.com/microsoft/vscode/issues/110432#issuecomment-726144556 for context. + */ + export function from(value: undefined): undefined; + export function from(value: vscode.IconPath): extHostProtocol.IconPathDto; + export function from(value: vscode.IconPath | undefined): extHostProtocol.IconPathDto | undefined; + export function from(value: vscode.IconPath | undefined): extHostProtocol.IconPathDto | undefined { + if (!value) { + return undefined; + } else if (ThemeIcon.isThemeIcon(value)) { + return value; + } else if (URI.isUri(value)) { + return value; + } else if (typeof value === 'string') { + return URI.file(value); + } else if (typeof value === 'object' && value !== null && 'dark' in value) { + const dark = typeof value.dark === 'string' ? URI.file(value.dark) : value.dark; + const light = typeof value.light === 'string' ? URI.file(value.light) : value.light; + return !dark ? undefined : { dark, light: light ?? dark }; + } else { + return undefined; + } + } + + /** + * Converts a {@link extHostProtocol.IconPathDto} to a {@link vscode.IconPath}. + * @note This is a strict conversion and we assume types are correct in this case. + */ + export function to(value: undefined): undefined; + export function to(value: extHostProtocol.IconPathDto): vscode.IconPath; + export function to(value: extHostProtocol.IconPathDto | undefined): vscode.IconPath | undefined; + export function to(value: extHostProtocol.IconPathDto | undefined): vscode.IconPath | undefined { + if (!value) { + return undefined; + } else if (ThemeIcon.isThemeIcon(value)) { + return value; + } else if (isUriComponents(value)) { + return URI.revive(value); + } else { + const icon = value as { light: UriComponents; dark: UriComponents }; + return { + light: URI.revive(icon.light), + dark: URI.revive(icon.dark) + }; + } + } } export namespace AiSettingsSearch { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index dec735ba8e0..a61088ba6ce 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2544,7 +2544,8 @@ export class DebugVisualization { export enum QuickInputButtonLocation { Title = 1, - Inline = 2 + Inline = 2, + Input = 3 } @es5ClassCompat diff --git a/src/vs/workbench/api/test/common/extHostTypeConverters.test.ts b/src/vs/workbench/api/test/common/extHostTypeConverters.test.ts new file mode 100644 index 00000000000..ed0e1ceae16 --- /dev/null +++ b/src/vs/workbench/api/test/common/extHostTypeConverters.test.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { IconPathDto } from '../../common/extHost.protocol.js'; +import { IconPath } from '../../common/extHostTypeConverters.js'; +import { ThemeColor, ThemeIcon } from '../../common/extHostTypes.js'; + +suite('extHostTypeConverters', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('IconPath', function () { + suite('from', function () { + test('undefined', function () { + assert.strictEqual(IconPath.from(undefined), undefined); + }); + + test('ThemeIcon', function () { + const themeIcon = new ThemeIcon('account', new ThemeColor('testing.iconForeground')); + assert.strictEqual(IconPath.from(themeIcon), themeIcon); + }); + + test('URI', function () { + const uri = URI.parse('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='); + assert.strictEqual(IconPath.from(uri), uri); + }); + + test('string', function () { + const str = '/path/to/icon.png'; + // eslint-disable-next-line local/code-no-any-casts + const r1 = IconPath.from(str as any) as any as URI; + assert.ok(URI.isUri(r1)); + assert.strictEqual(r1.scheme, 'file'); + assert.strictEqual(r1.path, str); + }); + + test('dark only', function () { + const input = { dark: URI.file('/path/to/dark.png') }; + // eslint-disable-next-line local/code-no-any-casts + const result = IconPath.from(input as any) as unknown as { dark: URI; light: URI }; + assert.strictEqual(typeof result, 'object'); + assert.ok('light' in result && 'dark' in result); + assert.ok(URI.isUri(result.light)); + assert.ok(URI.isUri(result.dark)); + assert.strictEqual(result.dark.toString(), input.dark.toString()); + assert.strictEqual(result.light.toString(), input.dark.toString()); + }); + + test('dark/light', function () { + const input = { light: URI.file('/path/to/light.png'), dark: URI.file('/path/to/dark.png') }; + const result = IconPath.from(input); + assert.strictEqual(typeof result, 'object'); + assert.ok('light' in result && 'dark' in result); + assert.ok(URI.isUri(result.light)); + assert.ok(URI.isUri(result.dark)); + assert.strictEqual(result.dark.toString(), input.dark.toString()); + assert.strictEqual(result.light.toString(), input.light.toString()); + }); + + test('dark/light strings', function () { + const input = { light: '/path/to/light.png', dark: '/path/to/dark.png' }; + // eslint-disable-next-line local/code-no-any-casts + const result = IconPath.from(input as any) as unknown as IconPathDto; + assert.strictEqual(typeof result, 'object'); + assert.ok('light' in result && 'dark' in result); + assert.ok(URI.isUri(result.light)); + assert.ok(URI.isUri(result.dark)); + assert.strictEqual(result.dark.path, input.dark); + assert.strictEqual(result.light.path, input.light); + }); + + test('invalid object', function () { + const invalidObject = { foo: 'bar' }; + // eslint-disable-next-line local/code-no-any-casts + const result = IconPath.from(invalidObject as any); + assert.strictEqual(result, undefined); + }); + + test('light only', function () { + const input = { light: URI.file('/path/to/light.png') }; + // eslint-disable-next-line local/code-no-any-casts + const result = IconPath.from(input as any); + assert.strictEqual(result, undefined); + }); + }); + + suite('to', function () { + test('undefined', function () { + assert.strictEqual(IconPath.to(undefined), undefined); + }); + + test('ThemeIcon', function () { + const themeIcon = new ThemeIcon('account'); + assert.strictEqual(IconPath.to(themeIcon), themeIcon); + }); + + test('URI', function () { + const uri: UriComponents = { scheme: 'data', path: 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' }; + const result = IconPath.to(uri); + assert.ok(URI.isUri(result)); + assert.strictEqual(result.toString(), URI.revive(uri).toString()); + }); + + test('dark/light', function () { + const input: { light: UriComponents; dark: UriComponents } = { + light: { scheme: 'file', path: '/path/to/light.png' }, + dark: { scheme: 'file', path: '/path/to/dark.png' } + }; + const result = IconPath.to(input); + assert.strictEqual(typeof result, 'object'); + assert.ok('light' in result && 'dark' in result); + assert.ok(URI.isUri(result.light)); + assert.ok(URI.isUri(result.dark)); + assert.strictEqual(result.dark.toString(), URI.revive(input.dark).toString()); + assert.strictEqual(result.light.toString(), URI.revive(input.light).toString()); + }); + }); + }); +}); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 897d2dc41cb..b88f240aa31 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1475,16 +1475,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer Date: Sun, 19 Oct 2025 02:30:39 +0200 Subject: [PATCH 1283/4355] chat - simplify send action by removing dropdown options (#271950) --- src/vs/platform/actions/common/actions.ts | 1 - .../browser/actions/chatExecuteActions.ts | 79 +++---------------- .../contrib/chat/browser/chat.contribution.ts | 5 -- .../contrib/chat/browser/chatInputPart.ts | 58 +------------- .../contrib/chat/common/constants.ts | 1 - 5 files changed, 15 insertions(+), 129 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 77343d49aea..d9377a3cb52 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -249,7 +249,6 @@ export class MenuId { static readonly ChatWelcomeHistoryContext = new MenuId('ChatWelcomeHistoryContext'); static readonly ChatMessageFooter = new MenuId('ChatMessageFooter'); static readonly ChatExecute = new MenuId('ChatExecute'); - static readonly ChatExecuteSecondary = new MenuId('ChatExecuteSecondary'); static readonly ChatInput = new MenuId('ChatInput'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly ChatModePicker = new MenuId('ChatModePicker'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 8de85386d9c..e3911048304 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -171,7 +171,7 @@ export class ChatSubmitAction extends SubmitAction { super({ id: ChatSubmitAction.ID, - title: localize2('interactive.submit.label', "Send and Dispatch"), + title: localize2('interactive.submit.label', "Send"), f1: false, category: CHAT_CATEGORY, icon: Codicon.send, @@ -179,7 +179,7 @@ export class ChatSubmitAction extends SubmitAction { toggled: { condition: ChatContextKeys.lockedToCodingAgent, icon: Codicon.send, - tooltip: localize('sendToRemoteAgent', "Send to coding agent"), + tooltip: localize('sendToRemoteAgent', "Send to Coding Agent"), }, keybinding: { when: ContextKeyExpr.and( @@ -190,15 +190,6 @@ export class ChatSubmitAction extends SubmitAction { weight: KeybindingWeight.EditorContrib }, menu: [ - { - id: MenuId.ChatExecuteSecondary, - group: 'group_1', - order: 1, - when: ContextKeyExpr.or( - ChatContextKeys.withinEditSessionDiff, - ContextKeyExpr.and(menuCondition, ChatContextKeys.lockedToCodingAgent.negate()) - ), - }, { id: MenuId.ChatExecute, order: 4, @@ -208,6 +199,11 @@ export class ChatSubmitAction extends SubmitAction { ChatContextKeys.withinEditSessionDiff.negate(), ), group: 'navigation', + alt: { + id: 'workbench.action.chat.sendToNewChat', + title: localize2('chat.newChat.label', "Send to New Chat"), + icon: Codicon.plus + } }] }); } @@ -240,15 +236,6 @@ export class ChatDelegateToEditSessionAction extends Action2 { ChatContextKeys.withinEditSessionDiff, ), group: 'navigation', - }, - { - id: MenuId.ChatExecuteSecondary, - group: 'group_1', - order: 1, - when: ContextKeyExpr.and( - whenNotInProgress, - ChatContextKeys.filePartOfEditSession, - ), } ] }); @@ -556,12 +543,6 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { icon: Codicon.send, precondition, menu: [ - { - id: MenuId.ChatExecuteSecondary, - group: 'group_1', - when: ContextKeyExpr.and(whenNotInProgress, menuCondition), - order: 1 - }, { id: MenuId.ChatExecute, order: 4, @@ -569,6 +550,11 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { ChatContextKeys.requestInProgress.negate(), menuCondition), group: 'navigation', + alt: { + id: 'workbench.action.chat.sendToNewChat', + title: localize2('chat.newChat.label', "Send to New Chat"), + icon: Codicon.plus + } }] }); } @@ -596,15 +582,7 @@ class SubmitWithoutDispatchingAction extends Action2 { when: ChatContextKeys.inChatInput, primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, weight: KeybindingWeight.EditorContrib - }, - menu: [ - { - id: MenuId.ChatExecuteSecondary, - group: 'group_1', - order: 2, - when: ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask), - } - ] + } }); } @@ -655,20 +633,6 @@ export class CreateRemoteAgentJobAction extends Action2 { ChatContextKeys.hasCloudButtonV2 ), ChatContextKeys.lockedToCodingAgent.negate(), - ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, false) - ), - }, - { - id: MenuId.ChatExecuteSecondary, - group: 'group_3', - order: 1, - when: ContextKeyExpr.and( - ContextKeyExpr.or( - ChatContextKeys.hasRemoteCodingAgent, - ChatContextKeys.hasCloudButtonV2 - ), - ChatContextKeys.lockedToCodingAgent.negate(), - ContextKeyExpr.equals(`config.${ChatConfiguration.DelegateToCodingAgentInSecondaryMenu}`, true) ), } ] @@ -1049,15 +1013,6 @@ export class ChatSubmitWithCodebaseAction extends Action2 { id: ChatSubmitWithCodebaseAction.ID, title: localize2('actions.chat.submitWithCodebase', "Send with {0}", `${chatVariableLeader}codebase`), precondition, - menu: { - id: MenuId.ChatExecuteSecondary, - group: 'group_1', - order: 3, - when: ContextKeyExpr.and( - ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat), - ChatContextKeys.lockedToCodingAgent.negate() - ), - }, keybinding: { when: ChatContextKeys.inChatInput, primary: KeyMod.CtrlCmd | KeyCode.Enter, @@ -1108,14 +1063,6 @@ class SendToNewChatAction extends Action2 { precondition, category: CHAT_CATEGORY, f1: false, - menu: { - id: MenuId.ChatExecuteSecondary, - group: 'group_2', - when: ContextKeyExpr.and( - ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat), - ChatContextKeys.lockedToCodingAgent.negate() - ) - }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 055e6790796..61016419e09 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -704,11 +704,6 @@ configurationRegistry.registerConfiguration({ tags: ['experimental'], }, - [ChatConfiguration.DelegateToCodingAgentInSecondaryMenu]: { - type: 'boolean', - description: nls.localize('chat.delegateToCodingAgentInSecondaryMenu', "Controls whether the 'Delegate to Coding Agent' action appears in the secondary send menu instead of the primary toolbar."), - default: false, - }, [ChatConfiguration.ShowAgentSessionsViewDescription]: { type: 'boolean', description: nls.localize('chat.showAgentSessionsViewDescription', "Controls whether session descriptions are displayed on a second row in the Chat Sessions view."), diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 48d0e86c560..6501791edd1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -49,13 +49,10 @@ import { SuggestController } from '../../../../editor/contrib/suggest/browser/su import { localize } from '../../../../nls.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js'; -import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { registerAndCreateHistoryNavigationContext } from '../../../../platform/history/browser/contextScopedHistoryWidget.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -64,7 +61,6 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { ILabelService } from '../../../../platform/label/common/label.js'; import { WorkbenchList } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; @@ -90,7 +86,7 @@ import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, IL import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; -import { CancelAction, ChatDelegateToEditSessionAction, ChatEditingSessionSubmitAction, ChatOpenModelPickerActionId, ChatSessionOpenModelPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; +import { ChatOpenModelPickerActionId, ChatSessionOpenModelPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; @@ -1387,16 +1383,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }, hoverDelegate, hiddenItemStrategy: HiddenItemStrategy.NoHide, - actionViewItemProvider: (action, options) => { - if (this.location === ChatAgentLocation.Chat || this.location === ChatAgentLocation.EditorInline) { - if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID || action.id === ChatEditingSessionSubmitAction.ID || action.id === ChatDelegateToEditSessionAction.ID) && action instanceof MenuItemAction) { - const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); - return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, { ...options, menuAsChild: false }); - } - } - - return undefined; - } })); this.executeToolbar.getElement().classList.add('chat-execute-toolbar'); this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext; @@ -2060,46 +2046,6 @@ function getLastPosition(model: ITextModel): IPosition { return { lineNumber: model.getLineCount(), column: model.getLineLength(model.getLineCount()) + 1 }; } -// This does seems like a lot just to customize an item with dropdown. This whole class exists just because we need an -// onDidChange listener on the submenu, which is apparently not needed in other cases. -class ChatSubmitDropdownActionItem extends DropdownWithPrimaryActionViewItem { - constructor( - action: MenuItemAction, - dropdownAction: IAction, - options: IDropdownWithPrimaryActionViewItemOptions, - @IMenuService menuService: IMenuService, - @IContextMenuService contextMenuService: IContextMenuService, - @IContextKeyService contextKeyService: IContextKeyService, - @IKeybindingService keybindingService: IKeybindingService, - @INotificationService notificationService: INotificationService, - @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService - ) { - super( - action, - dropdownAction, - [], - '', - { - ...options, - getKeyBinding: (action: IAction) => keybindingService.lookupKeybinding(action.id, contextKeyService) - }, - contextMenuService, - keybindingService, - notificationService, - contextKeyService, - themeService, - accessibilityService); - const menu = menuService.createMenu(MenuId.ChatExecuteSecondary, contextKeyService); - const setActions = () => { - const secondary = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); - this.update(dropdownAction, secondary); - }; - setActions(); - this._register(menu.onDidChange(() => setActions())); - } -} - const chatInputEditorContainerSelector = '.interactive-input-editor'; setupSimpleEditorSelectionStyling(chatInputEditorContainerSelector); diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 5768517faf6..3501fbc5103 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -21,7 +21,6 @@ export enum ChatConfiguration { ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', EmptyStateHistoryEnabled = 'chat.emptyState.history.enabled', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', - DelegateToCodingAgentInSecondaryMenu = 'chat.delegateToCodingAgentInSecondaryMenu', } /** From adaf83dd225eed1a2eba3bdc719a23bb02c45d44 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:36:00 -0700 Subject: [PATCH 1284/4355] remove 'Delegate to Coding Agent' tooltip (#272121) remove tooltip Remove detailed tooltip for creating remote job action. --- .../workbench/contrib/chat/browser/actions/chatExecuteActions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index e3911048304..1147483e655 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -615,7 +615,6 @@ export class CreateRemoteAgentJobAction extends Action2 { // TODO(joshspicer): Generalize title/tooltip - pull from contribution title: localize2('actions.chat.createRemoteJob', "Delegate to Coding Agent"), icon: Codicon.sendToRemoteAgent, - tooltip: localize('delegateToCodingAgentToolTip', "Delegate this task to the GitHub Copilot coding agent. The agent will continue work asynchronously and create a pull request with the proposed changes. Iterate further via chat or from the associated pull request."), precondition, toggled: { condition: ChatContextKeys.remoteJobCreating, From 60686129f4af929b656e97f8fba3e7f4dda251ac Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Sat, 18 Oct 2025 22:01:58 -0700 Subject: [PATCH 1285/4355] Resolve issue where first input gets ignored on new conpty with a ding sound (#271556) * Fix first input getting ignored * More cleanups * Remove pauseInputEventbarrier from onData handler * Revert my changes, back to original ptyService * micro task `this._processManager.write('\x1b[?61;4c');` * Remove pauseInputEventBarrier * try to fix unused import error * use new _sendDataToProcess * Dont use async, dont await on \x1b[?61;4c * rename to _handleOnData --- .../contrib/terminal/browser/terminal.ts | 7 ------- .../terminal/browser/terminalInstance.ts | 17 ++++++++--------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 3049edd9ee5..af68229d537 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -30,7 +30,6 @@ import { ACTIVE_GROUP_TYPE, AUX_WINDOW_GROUP_TYPE, SIDE_GROUP_TYPE } from '../.. import type { ICurrentPartialCommand } from '../../../../platform/terminal/common/capabilities/commandDetection/terminalCommand.js'; import type { IXtermCore } from './xterm-private.js'; import type { IMenu } from '../../../../platform/actions/common/actions.js'; -import type { Barrier } from '../../../../base/common/async.js'; import type { IProgressState } from '@xterm/addon-progress'; import type { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import type { TerminalEditorInput } from './terminalEditorInput.js'; @@ -1156,12 +1155,6 @@ export interface ITerminalInstance extends IBaseTerminalInstance { * @returns Whether the context menu should be suppressed. */ handleMouseEvent(event: MouseEvent, contextMenu: IMenu): Promise<{ cancelContextMenu: boolean } | void>; - - /** - * Pause input events until the provided barrier is resolved. - * @param barrier The barrier to wait for until input events can continue. - */ - pauseInputEvents(barrier: Barrier): void; } export const enum XtermTerminalConstants { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 18bf9a46092..76121a56773 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -10,7 +10,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; -import { AutoOpenBarrier, Barrier, Promises, disposableTimeout, timeout } from '../../../../base/common/async.js'; +import { AutoOpenBarrier, Promises, disposableTimeout, timeout } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { debounce } from '../../../../base/common/decorators.js'; import { BugIndicatingError, onUnexpectedError } from '../../../../base/common/errors.js'; @@ -200,10 +200,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _lineDataEventAddon: LineDataEventAddon | undefined; private readonly _scopedContextKeyService: IContextKeyService; private _resizeDebouncer?: TerminalResizeDebouncer; - private _pauseInputEventBarrier: Barrier | undefined; - pauseInputEvents(barrier: Barrier): void { - this._pauseInputEventBarrier = barrier; - } readonly capabilities = this._register(new TerminalCapabilityStoreMultiplexer()); readonly statusList: ITerminalStatusList; @@ -654,6 +650,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this._contributions.get(id) as T | null; } + private async _handleOnData(data: string): Promise { + await this._processManager.write(data); + this._onDidInputData.fire(data); + } + private _getIcon(): TerminalIcon | undefined { if (!this._icon) { this._icon = this._processManager.processState >= ProcessState.Launching @@ -848,9 +849,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._register(this._processManager.onProcessData(e => this._onProcessData(e))); this._register(xterm.raw.onData(async data => { - await this._pauseInputEventBarrier?.wait(); - await this._processManager.write(data); - this._onDidInputData.fire(data); + await this._handleOnData(data); })); this._register(xterm.raw.onBinary(data => this._processManager.processBinary(data))); // Init winpty compat and link handler after process creation as they rely on the @@ -862,7 +861,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (processTraits?.windowsPty?.backend === 'conpty') { this._register(xterm.raw.parser.registerCsiHandler({ final: 'c' }, params => { if (params.length === 0 || params.length === 1 && params[0] === 0) { - this._processManager.write('\x1b[?61;4c'); + this._handleOnData('\x1b[?61;4c'); return true; } return false; From 2db6661bf7d7376fa88e9cd9f0871da9e783e169 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:27:45 -0700 Subject: [PATCH 1286/4355] Remove hoverDelegate from toggle options --- src/vs/base/browser/ui/toggle/toggle.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index f3a511a869d..8e6bddc561d 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -12,7 +12,6 @@ import { $, addDisposableListener, EventType, isActiveElement } from '../../dom. import { IKeyboardEvent } from '../../keyboardEvent.js'; import { BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js'; import { HoverStyle, IHoverLifecycleOptions } from '../hover/hover.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; import { Widget } from '../widget.js'; import './toggle.css'; @@ -23,9 +22,6 @@ export interface IToggleOpts extends IToggleStyles { readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; - // TODO: Remove this, the previous default was mouse, so anything not mouse needs to be explicit - /** @deprecated Prefer hoverStyle and hoverLifecycleOptions instead */ - readonly hoverDelegate?: IHoverDelegate; readonly hoverStyle?: HoverStyle; readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } @@ -69,7 +65,7 @@ export class ToggleActionViewItem extends BaseActionViewItem { inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, - hoverDelegate: options.hoverDelegate, + hoverStyle: HoverStyle.Pointer, })); this._register(this.toggle.onChange(() => { this._action.checked = !!this.toggle && this.toggle.checked; From 19b5f21ee09d37bbbd9bbe0004327328b1793581 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:34:10 -0700 Subject: [PATCH 1287/4355] Use pointer style in preserve case --- src/vs/base/browser/ui/findinput/replaceInput.ts | 5 +++++ src/vs/editor/contrib/find/browser/findWidget.ts | 1 + .../browser/contrib/find/notebookFindReplaceWidget.ts | 3 ++- src/vs/workbench/contrib/search/browser/searchWidget.ts | 5 +++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 3062cfecc57..4c770300aed 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -17,6 +17,7 @@ import { KeyCode } from '../../../common/keyCodes.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; import { IHistory } from '../../../common/history.js'; +import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IReplaceInputOptions { @@ -27,6 +28,7 @@ export interface IReplaceInputOptions { readonly flexibleHeight?: boolean; readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; readonly appendPreserveCaseLabel?: string; readonly history?: IHistory; @@ -45,6 +47,7 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -119,6 +122,8 @@ export class ReplaceInput extends Widget { this.preserveCase = this._register(new PreserveCaseToggle({ appendTitle: appendPreserveCaseLabel, isChecked: false, + hoverStyle: options.hoverStyle, + hoverLifecycleOptions: options.hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.preserveCase.onChange(viaKeyboard => { diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 465206e5121..29fbac939d5 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -1102,6 +1102,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService), inputBoxStyles: defaultInputBoxStyles, toggleStyles: defaultToggleStyles, + hoverLifecycleOptions, }, this._contextKeyService, true)); this._replaceInput.setPreserveCase(!!this._state.preserveCase); this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e))); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 3a2294650a6..1184ba6cdf9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -588,7 +588,8 @@ export abstract class SimpleFindReplaceWidget extends Widget { placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, history: replaceHistoryConfig === 'workspace' ? this._replaceWidgetHistory : new Set([]), inputBoxStyles: defaultInputBoxStyles, - toggleStyles: defaultToggleStyles + toggleStyles: defaultToggleStyles, + hoverLifecycleOptions, }, contextKeyService, false)); this._innerReplaceDomNode.appendChild(this._replaceInput.domNode); this._replaceInputFocusTracker = this._register(dom.trackFocus(this._replaceInput.domNode)); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 96eb749a5ff..839aca2eb43 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -93,6 +93,7 @@ class ReplaceAllAction extends Action { } } +const hoverLifecycleOptions = { groupId: 'search-widget' }; const ctrlKeyMod = (isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd); function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { @@ -396,7 +397,6 @@ export class SearchWidget extends Widget { private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void { const history = options.searchHistory || []; - const hoverLifecycleOptions = { groupId: 'search-widget' }; const inputOptions: IFindInputOptions = { label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search'), validation: (value: string) => this.validateSearchInput(value), @@ -521,7 +521,8 @@ export class SearchWidget extends Widget { flexibleHeight: true, flexibleMaxHeight: SearchWidget.INPUT_MAX_HEIGHT, inputBoxStyles: options.inputBoxStyles, - toggleStyles: options.toggleStyles + toggleStyles: options.toggleStyles, + hoverLifecycleOptions }, this.contextKeyService, true)); this._register(this.replaceInput.onDidOptionChange(viaKeyboard => { From e1e18b660248fc805202600da949c66d41f37d6c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:36:12 -0700 Subject: [PATCH 1288/4355] Set all toggles to use pointer style --- src/vs/base/browser/ui/findinput/findInputToggles.ts | 5 +---- src/vs/base/browser/ui/findinput/replaceInput.ts | 4 +--- src/vs/base/browser/ui/toggle/toggle.ts | 4 +--- src/vs/base/browser/ui/tree/abstractTree.ts | 3 +-- src/vs/editor/contrib/find/browser/findWidget.ts | 3 +-- .../workbench/contrib/search/browser/patternInputWidget.ts | 3 --- src/vs/workbench/contrib/search/browser/searchWidget.ts | 2 -- 7 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index fc8f79dfd85..b2eec09e331 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -6,7 +6,7 @@ import { Toggle } from '../toggle/toggle.js'; import { Codicon } from '../../../common/codicons.js'; import * as nls from '../../../../nls.js'; -import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputToggleOpts { readonly appendTitle: string; @@ -27,7 +27,6 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -42,7 +41,6 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -57,7 +55,6 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 4c770300aed..10c5b47b653 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -17,7 +17,7 @@ import { KeyCode } from '../../../common/keyCodes.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; import { IHistory } from '../../../common/history.js'; -import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IReplaceInputOptions { @@ -47,7 +47,6 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, @@ -122,7 +121,6 @@ export class ReplaceInput extends Widget { this.preserveCase = this._register(new PreserveCaseToggle({ appendTitle: appendPreserveCaseLabel, isChecked: false, - hoverStyle: options.hoverStyle, hoverLifecycleOptions: options.hoverLifecycleOptions, ...options.toggleStyles })); diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 8e6bddc561d..ecad99575c2 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -22,7 +22,6 @@ export interface IToggleOpts extends IToggleStyles { readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; - readonly hoverStyle?: HoverStyle; readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } @@ -65,7 +64,6 @@ export class ToggleActionViewItem extends BaseActionViewItem { inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, - hoverStyle: HoverStyle.Pointer, })); this._register(this.toggle.onChange(() => { this._action.checked = !!this.toggle && this.toggle.checked; @@ -155,7 +153,7 @@ export class Toggle extends Widget { this.domNode = document.createElement('div'); this._register(getBaseLayerHoverDelegate().setupDelayedHover(this.domNode, () => ({ content: this._title, - style: this._opts.hoverStyle ?? HoverStyle.Mouse, + style: HoverStyle.Pointer, }), this._opts.hoverLifecycleOptions)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a6d64e469b8..8e4449b76b7 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -36,7 +36,7 @@ import { localize } from '../../../../nls.js'; import { autorun, constObservable } from '../../../common/observable.js'; import { alert } from '../aria/aria.js'; import { IMouseWheelEvent } from '../../mouseEvent.js'; -import { HoverStyle, type IHoverLifecycleOptions } from '../hover/hover.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -715,7 +715,6 @@ class TreeFindToggle extends Toggle { inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions, }); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 29fbac939d5..7c177fa0c07 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -44,7 +44,7 @@ import { Selection } from '../../../common/core/selection.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IHistory } from '../../../../base/common/history.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import { HoverStyle, type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); @@ -1036,7 +1036,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL icon: findSelectionIcon, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), isChecked: false, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 9df598afc0b..350fa0ad053 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,7 +19,6 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { HoverStyle } from '../../../../base/browser/ui/hover/hover.js'; export interface IOptions { placeholder?: string; @@ -221,7 +220,6 @@ export class IncludePatternInputWidget extends PatternInputWidget { icon: Codicon.book, title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, - hoverStyle: HoverStyle.Pointer, ...defaultToggleStyles })); this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { @@ -274,7 +272,6 @@ export class ExcludePatternInputWidget extends PatternInputWidget { actionClassName: 'useExcludesAndIgnoreFiles', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, - hoverStyle: HoverStyle.Pointer, ...defaultToggleStyles })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 839aca2eb43..c06dbd4b3ea 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -45,7 +45,6 @@ import { SearchFindInput } from './searchFindInput.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { NotebookFindScopeType } from '../../notebook/common/notebookCommon.js'; -import { HoverStyle } from '../../../../base/browser/ui/hover/hover.js'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; @@ -468,7 +467,6 @@ export class SearchWidget extends Widget { isChecked: false, title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId)), icon: searchShowContextIcon, - hoverStyle: HoverStyle.Pointer, hoverLifecycleOptions, ...defaultToggleStyles }); From ba86b94ef8555e4fed75551a99999a74d9ea0a83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:39:47 -0700 Subject: [PATCH 1289/4355] Simplify findWidget hover options --- src/vs/editor/contrib/find/browser/findWidget.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 7c177fa0c07..b7e7f878a91 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,8 +43,7 @@ import { defaultInputBoxStyles, defaultToggleStyles } from '../../../../platform import { Selection } from '../../../common/core/selection.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IHistory } from '../../../../base/common/history.js'; -import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import { type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { HoverStyle, type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js'; const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); @@ -1329,15 +1328,8 @@ export class SimpleButton extends Widget { this._domNode.setAttribute('aria-label', this._opts.label); this._register(hoverService.setupDelayedHover(this._domNode, { content: this._opts.label, - appearance: { - compact: true, - showPointer: true, - }, - position: { - hoverPosition: HoverPosition.BELOW - }, + style: HoverStyle.Pointer, }, opts.hoverLifecycleOptions)); - // this._register(hoverService.setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); this.onclick(this._domNode, (e) => { this._opts.onTrigger(); From 1f4b2d51949af04734ae25766e7c8824116d70fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 01:51:33 -0700 Subject: [PATCH 1290/4355] Remove string that can't be injecting into localized string Fixes #271491 --- .../common/terminalChatAgentToolsConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 0b9f56098c0..b0d475dd1c7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -65,7 +65,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Sun, 19 Oct 2025 03:04:16 -0700 Subject: [PATCH 1291/4355] Add shell specific instructions for pwsh and bash Fixes #262386 --- .../browser/runInTerminalHelpers.ts | 4 + .../terminal.chatAgentTools.contribution.ts | 12 +- .../browser/tools/runInTerminalTool.ts | 340 +++++++++++------- 3 files changed, 228 insertions(+), 128 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 153a231f5c1..898ffb43d77 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -21,6 +21,10 @@ export function isPowerShell(envShell: string, os: OperatingSystem): boolean { return /^(?:powershell|pwsh)(?:-preview)?$/.test(pathPosix.basename(envShell)); } +export function isWindowsPowerShell(envShell: string): boolean { + return envShell.endsWith('System32\\WindowsPowerShell\\v1.0\\powershell.exe'); +} + // Maximum output length to prevent context overflow const MAX_OUTPUT_LENGTH = 60000; // ~60KB limit to keep context manageable const TRUNCATION_MESSAGE = '\n\n[... MIDDLE OF OUTPUT TRUNCATED ...]\n\n'; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts index ce15dccdca8..deada5e6bc1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts @@ -22,7 +22,7 @@ import { TerminalChatAgentToolsCommandId } from '../common/terminal.chatAgentToo import { GetTerminalLastCommandTool, GetTerminalLastCommandToolData } from './tools/getTerminalLastCommandTool.js'; import { GetTerminalOutputTool, GetTerminalOutputToolData } from './tools/getTerminalOutputTool.js'; import { GetTerminalSelectionTool, GetTerminalSelectionToolData } from './tools/getTerminalSelectionTool.js'; -import { RunInTerminalTool, RunInTerminalToolData } from './tools/runInTerminalTool.js'; +import { RunInTerminalTool, createRunInTerminalToolData } from './tools/runInTerminalTool.js'; import { CreateAndRunTaskTool, CreateAndRunTaskToolData } from './tools/task/createAndRunTaskTool.js'; import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOutputTool.js'; import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js'; @@ -41,9 +41,6 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib // #region Terminal - const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool); - this._register(toolsService.registerTool(RunInTerminalToolData, runInTerminalTool)); - const getTerminalOutputTool = instantiationService.createInstance(GetTerminalOutputTool); this._register(toolsService.registerTool(GetTerminalOutputToolData, getTerminalOutputTool)); @@ -51,9 +48,14 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib icon: ThemeIcon.fromId(Codicon.terminal.id), description: localize('toolset.runCommands', 'Runs commands in the terminal') })); - runCommandsToolSet.addTool(RunInTerminalToolData); runCommandsToolSet.addTool(GetTerminalOutputToolData); + instantiationService.invokeFunction(createRunInTerminalToolData).then(runInTerminalToolData => { + const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool); + this._register(toolsService.registerTool(runInTerminalToolData, runInTerminalTool)); + runCommandsToolSet.addTool(runInTerminalToolData); + }); + const getTerminalSelectionTool = instantiationService.createInstance(GetTerminalSelectionTool); this._register(toolsService.registerTool(GetTerminalSelectionToolData, getTerminalSelectionTool)); 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 ab6bec13e81..bfd14e567be 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -19,7 +19,7 @@ import type { SingleOrMany } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, type ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.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'; @@ -41,83 +41,161 @@ import type { ITerminalExecuteStrategy } from '../executeStrategy/executeStrateg import { NoneExecuteStrategy } from '../executeStrategy/noneExecuteStrategy.js'; import { RichExecuteStrategy } from '../executeStrategy/richExecuteStrategy.js'; import { getOutput } from '../outputHelpers.js'; -import { dedupeRules, generateAutoApproveActions, isPowerShell } from '../runInTerminalHelpers.js'; +import { dedupeRules, generateAutoApproveActions, isPowerShell, isWindowsPowerShell } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { splitCommandLineIntoSubCommands } from '../subCommands.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; import { OutputMonitor } from './monitoring/outputMonitor.js'; import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; -const enum TerminalToolStorageKeysInternal { - TerminalSession = 'chat.terminalSessions' -} +// #region Tool data -interface IStoredTerminalAssociation { - sessionId: string; - id: string; - shellIntegrationQuality: ShellIntegrationQuality; - isBackground?: boolean; +function createBashModelDescription(): string { + return [ + 'This tool allows you to execute shell commands in a persistent bash terminal session, preserving environment variables, working directory, and other context across multiple commands.', + '', + 'Command Execution:', + '- Does NOT support multi-line commands', + '- Use && to chain simple commands on one line', + '- Prefer pipelines (|) over temporary files for data flow', + '', + 'Directory Management:', + '- Must use absolute paths to avoid navigation issues', + '- Use $PWD for current directory references', + '- Consider using pushd/popd for directory stack management', + '', + 'Program Execution:', + '- Supports Python, Node.js, and other executables', + '- Install dependencies via pip, npm, apt, etc.', + '- Use which or command -v to verify command availability', + '', + 'Background Processes:', + '- For long-running tasks (e.g., servers), set isBackground=true', + '- Returns a terminal ID for checking status and runtime later', + '- Use nohup for processes that should survive terminal closure', + '', + 'Output Management:', + '- Output is automatically truncated if longer than 60KB to prevent context overflow', + '- Use head, tail, grep, awk to filter and limit output size', + '- For pager commands, disable paging: git --no-pager or add | cat', + '- Use wc -l to count lines before displaying large outputs', + '', + 'Best Practices:', + '- Quote variables: "$var" instead of $var to handle spaces', + '- Use [[ ]] for conditional tests instead of [ ]', + '- Prefer $() over backticks for command substitution', + '- Use set -e at start of complex commands to exit on errors', + '- Use find with -exec or xargs for file operations', + '- Be specific with commands to avoid excessive output' + ].join('\n'); } -export const RunInTerminalToolData: IToolData = { - id: 'run_in_terminal', - toolReferenceName: 'runInTerminal', - displayName: localize('runInTerminalTool.displayName', 'Run in Terminal'), - modelDescription: [ - 'This tool allows you to execute shell commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.', +function createPowerShellModelDescription(shell: string): string { + return [ + 'This tool allows you to execute PowerShell commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.', '', 'Command Execution:', - // TODO: Multi-line command execution does work, but it requires AST parsing to pull - // sub-commands out reliably https://github.com/microsoft/vscode/issues/261794 '- Does NOT support multi-line commands', + `- ${isWindowsPowerShell(shell) + ? 'Use semicolons `;` to chain commands on one line, NEVER use `&&` even when asked explicitly' + : 'Use && to chain simple commands on one line'}`, + '- Prefer pipelines `|` for object-based data flow', '', 'Directory Management:', - '- Must use absolute paths to avoid navigation issues.', + '- Must use absolute paths to avoid navigation issues', + '- Use $PWD or Get-Location for current directory', + '- Use Push-Location/Pop-Location for directory stack', '', 'Program Execution:', - '- Supports Python, Node.js, and other executables.', - '- Install dependencies via pip, npm, etc.', + '- Supports .NET, Python, Node.js, and other executables', + '- Install modules via Install-Module, Install-Package', + '- Use Get-Command to verify cmdlet/function availability', '', 'Background Processes:', - '- For long-running tasks (e.g., servers), set isBackground=true.', - '- Returns a terminal ID for checking status and runtime later.', + '- For long-running tasks (e.g., servers), set isBackground=true', + '- Returns a terminal ID for checking status and runtime later', + '- Use Start-Job for background PowerShell jobs', '', 'Output Management:', '- Output is automatically truncated if longer than 60KB to prevent context overflow', - '- Use filters like \'head\', \'tail\', \'grep\' to limit output size', - '- For pager commands, disable paging: use \'git --no-pager\' or add \'| cat\'', + '- Use Select-Object, Where-Object, Format-Table to filter output', + '- Use -First/-Last parameters to limit results', + '- For pager commands, add | Out-String or | Format-List', '', 'Best Practices:', - '- Be specific with commands to avoid excessive output', - '- Use targeted queries instead of broad scans', - '- Consider using \'wc -l\' to count before listing many items' - ].join('\n'), - userDescription: localize('runInTerminalTool.userDescription', 'Tool for running commands in the terminal'), - source: ToolDataSource.Internal, - icon: Codicon.terminal, - inputSchema: { - type: 'object', - properties: { - command: { - type: 'string', - description: 'The command to run in the terminal.' - }, - explanation: { - type: 'string', - description: 'A one-sentence description of what the command does. This will be shown to the user before the command is run.' - }, - isBackground: { - type: 'boolean', - description: 'Whether the command starts a background process. If true, the command will run in the background and you will not see the output. If false, the tool call will block on the command finishing, and then you will get the output. Examples of background processes: building in watch mode, starting a server. You can check the output of a background process later on by using get_terminal_output.' - }, - }, - required: [ - 'command', - 'explanation', - 'isBackground', - ] + '- Use proper cmdlet names instead of aliases in scripts', + '- Quote paths with spaces: "C:\\Path With Spaces"', + '- Use $ErrorActionPreference = "Stop" for strict error handling', + '- Prefer PowerShell cmdlets over external commands when available', + '- Use Get-ChildItem instead of dir/ls for file listings', + '- Use Test-Path to check file/directory existence', + '- Be specific with Select-Object properties to avoid excessive output' + ].join('\n'); +} + +export async function createRunInTerminalToolData( + accessor: ServicesAccessor +): Promise { + const instantiationService = accessor.get(IInstantiationService); + + const profileFetcher = instantiationService.createInstance(TerminalProfileFetcher); + const shell = await profileFetcher.getCopilotShell(); + const os = await profileFetcher.osBackend; + + let modelDescription: string; + if (shell && os && isPowerShell(shell, os)) { + modelDescription = createPowerShellModelDescription(shell); + } else { + modelDescription = createBashModelDescription(); } -}; + + return { + id: 'run_in_terminal', + toolReferenceName: 'runInTerminal', + displayName: localize('runInTerminalTool.displayName', 'Run in Terminal'), + modelDescription, + userDescription: localize('runInTerminalTool.userDescription', 'Tool for running commands in the terminal'), + source: ToolDataSource.Internal, + icon: Codicon.terminal, + inputSchema: { + type: 'object', + properties: { + command: { + type: 'string', + description: 'The command to run in the terminal.' + }, + explanation: { + type: 'string', + description: 'A one-sentence description of what the command does. This will be shown to the user before the command is run.' + }, + isBackground: { + type: 'boolean', + description: 'Whether the command starts a background process. If true, the command will run in the background and you will not see the output. If false, the tool call will block on the command finishing, and then you will get the output. Examples of background processes: building in watch mode, starting a server. You can check the output of a background process later on by using get_terminal_output.' + }, + }, + required: [ + 'command', + 'explanation', + 'isBackground', + ] + } + }; +} + +// #endregion + +// #region Tool implementation + +const enum TerminalToolStorageKeysInternal { + TerminalSession = 'chat.terminalSessions' +} + +interface IStoredTerminalAssociation { + sessionId: string; + id: string; + shellIntegrationQuality: ShellIntegrationQuality; + isBackground?: boolean; +} export interface IRunInTerminalInputParams { command: string; @@ -148,6 +226,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _terminalToolCreator: ToolTerminalCreator; private readonly _commandSimplifier: CommandSimplifier; + private readonly _profileFetcher: TerminalProfileFetcher; private readonly _telemetry: RunInTerminalToolTelemetry; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; protected readonly _sessionTerminalAssociations: Map = new Map(); @@ -170,7 +249,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IStorageService private readonly _storageService: IStorageService, @ITerminalLogService private readonly _logService: ITerminalLogService, - @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @@ -182,6 +260,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._terminalToolCreator = _instantiationService.createInstance(ToolTerminalCreator); this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); + this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); @@ -217,7 +296,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; const os = await this._osBackend; - const shell = await this._getCopilotShell(); + const shell = await this._profileFetcher.getCopilotShell(); const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; const instance = context.chatSessionId ? this._sessionTerminalAssociations.get(context.chatSessionId)?.instance : undefined; @@ -625,74 +704,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Terminal init - protected async _getCopilotProfile(): Promise { - const os = await this._osBackend; - - // Check for chat agent terminal profile first - const customChatAgentProfile = this._getChatTerminalProfile(os); - if (customChatAgentProfile) { - return customChatAgentProfile; - } - - // When setting is null, use the previous behavior - const defaultProfile = await this._terminalProfileResolverService.getDefaultProfile({ - os, - remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority - }); - - // Force pwsh over cmd as cmd doesn't have shell integration - if (basename(defaultProfile.path) === 'cmd.exe') { - return { - ...defaultProfile, - path: 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', - profileName: 'PowerShell' - }; - } - - // Setting icon: undefined allows the system to use the default Copilot terminal icon (not overridden or removed) - return { ...defaultProfile, icon: undefined }; - } - - private async _getCopilotShell(): Promise { - return (await this._getCopilotProfile()).path; - } - - private _getChatTerminalProfile(os: OperatingSystem): ITerminalProfile | undefined { - let profileSetting: string; - switch (os) { - case OperatingSystem.Windows: - profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileWindows; - break; - case OperatingSystem.Macintosh: - profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileMacOs; - break; - case OperatingSystem.Linux: - default: - profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileLinux; - break; - } - - const profile = this._configurationService.getValue(profileSetting); - if (this._isValidChatAgentTerminalProfile(profile)) { - return profile; - } - - return undefined; - } - - private _isValidChatAgentTerminalProfile(profile: unknown): profile is ITerminalProfile { - if (profile === null || profile === undefined || typeof profile !== 'object') { - return false; - } - if ('path' in profile && typeof (profile as { path: unknown }).path === 'string') { - return true; - } - return false; - } - private async _initBackgroundTerminal(chatSessionId: string, termId: string, terminalToolSessionId: string | undefined, token: CancellationToken): Promise { this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`); - const profile = await this._getCopilotProfile(); + const profile = await this._profileFetcher.getCopilotProfile(); const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, toolTerminal.instance); this._registerInputListener(toolTerminal); @@ -713,7 +727,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, cachedTerminal.instance); return cachedTerminal; } - const profile = await this._getCopilotProfile(); + const profile = await this._profileFetcher.getCopilotProfile(); const toolTerminal = await this._terminalToolCreator.createTerminal(profile, token); this._terminalChatService.registerTerminalInstanceWithToolSession(terminalToolSessionId, toolTerminal.instance); this._registerInputListener(toolTerminal); @@ -937,3 +951,83 @@ class BackgroundTerminalExecution extends Disposable { return getOutput(this.instance, marker ?? this._startMarker); } } + +class TerminalProfileFetcher { + + readonly osBackend: Promise; + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + ) { + this.osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); + } + + async getCopilotProfile(): Promise { + const os = await this.osBackend; + + // Check for chat agent terminal profile first + const customChatAgentProfile = this._getChatTerminalProfile(os); + if (customChatAgentProfile) { + return customChatAgentProfile; + } + + // When setting is null, use the previous behavior + const defaultProfile = await this._terminalProfileResolverService.getDefaultProfile({ + os, + remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority + }); + + // Force pwsh over cmd as cmd doesn't have shell integration + if (basename(defaultProfile.path) === 'cmd.exe') { + return { + ...defaultProfile, + path: 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', + profileName: 'PowerShell' + }; + } + + // Setting icon: undefined allows the system to use the default Copilot terminal icon (not overridden or removed) + return { ...defaultProfile, icon: undefined }; + } + + async getCopilotShell(): Promise { + return (await this.getCopilotProfile()).path; + } + + private _getChatTerminalProfile(os: OperatingSystem): ITerminalProfile | undefined { + let profileSetting: string; + switch (os) { + case OperatingSystem.Windows: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileWindows; + break; + case OperatingSystem.Macintosh: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileMacOs; + break; + case OperatingSystem.Linux: + default: + profileSetting = TerminalChatAgentToolsSettingId.TerminalProfileLinux; + break; + } + + const profile = this._configurationService.getValue(profileSetting); + if (this._isValidChatAgentTerminalProfile(profile)) { + return profile; + } + + return undefined; + } + + private _isValidChatAgentTerminalProfile(profile: unknown): profile is ITerminalProfile { + if (profile === null || profile === undefined || typeof profile !== 'object') { + return false; + } + if ('path' in profile && typeof (profile as { path: unknown }).path === 'string') { + return true; + } + return false; + } +} + +// #endregion From 7ba1f76545bc06a416dda9233ba1ebfa51e4266a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 19 Oct 2025 03:36:56 -0700 Subject: [PATCH 1292/4355] Add zsh and fish instructions --- .../browser/runInTerminalHelpers.ts | 14 ++ .../browser/tools/runInTerminalTool.ts | 124 +++++++++++------- .../test/browser/runInTerminalTool.test.ts | 57 ++++++-- 3 files changed, 141 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 898ffb43d77..0ee62614c54 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -25,6 +25,20 @@ export function isWindowsPowerShell(envShell: string): boolean { return envShell.endsWith('System32\\WindowsPowerShell\\v1.0\\powershell.exe'); } +export function isZsh(envShell: string, os: OperatingSystem): boolean { + if (os === OperatingSystem.Windows) { + return /^zsh(?:\.exe)?$/i.test(pathWin32.basename(envShell)); + } + return /^zsh$/.test(pathPosix.basename(envShell)); +} + +export function isFish(envShell: string, os: OperatingSystem): boolean { + if (os === OperatingSystem.Windows) { + return /^fish(?:\.exe)?$/i.test(pathWin32.basename(envShell)); + } + return /^fish$/.test(pathPosix.basename(envShell)); +} + // Maximum output length to prevent context overflow const MAX_OUTPUT_LENGTH = 60000; // ~60KB limit to keep context manageable const TRUNCATION_MESSAGE = '\n\n[... MIDDLE OF OUTPUT TRUNCATED ...]\n\n'; 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 bfd14e567be..94cdc2fa128 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -41,7 +41,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 { dedupeRules, generateAutoApproveActions, isPowerShell, isWindowsPowerShell } from '../runInTerminalHelpers.js'; +import { dedupeRules, generateAutoApproveActions, isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { splitCommandLineIntoSubCommands } from '../subCommands.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; @@ -50,46 +50,6 @@ import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; // #region Tool data -function createBashModelDescription(): string { - return [ - 'This tool allows you to execute shell commands in a persistent bash terminal session, preserving environment variables, working directory, and other context across multiple commands.', - '', - 'Command Execution:', - '- Does NOT support multi-line commands', - '- Use && to chain simple commands on one line', - '- Prefer pipelines (|) over temporary files for data flow', - '', - 'Directory Management:', - '- Must use absolute paths to avoid navigation issues', - '- Use $PWD for current directory references', - '- Consider using pushd/popd for directory stack management', - '', - 'Program Execution:', - '- Supports Python, Node.js, and other executables', - '- Install dependencies via pip, npm, apt, etc.', - '- Use which or command -v to verify command availability', - '', - 'Background Processes:', - '- For long-running tasks (e.g., servers), set isBackground=true', - '- Returns a terminal ID for checking status and runtime later', - '- Use nohup for processes that should survive terminal closure', - '', - 'Output Management:', - '- Output is automatically truncated if longer than 60KB to prevent context overflow', - '- Use head, tail, grep, awk to filter and limit output size', - '- For pager commands, disable paging: git --no-pager or add | cat', - '- Use wc -l to count lines before displaying large outputs', - '', - 'Best Practices:', - '- Quote variables: "$var" instead of $var to handle spaces', - '- Use [[ ]] for conditional tests instead of [ ]', - '- Prefer $() over backticks for command substitution', - '- Use set -e at start of complex commands to exit on errors', - '- Use find with -exec or xargs for file operations', - '- Be specific with commands to avoid excessive output' - ].join('\n'); -} - function createPowerShellModelDescription(shell: string): string { return [ 'This tool allows you to execute PowerShell commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.', @@ -97,9 +57,9 @@ function createPowerShellModelDescription(shell: string): string { 'Command Execution:', '- Does NOT support multi-line commands', `- ${isWindowsPowerShell(shell) - ? 'Use semicolons `;` to chain commands on one line, NEVER use `&&` even when asked explicitly' + ? 'Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly' : 'Use && to chain simple commands on one line'}`, - '- Prefer pipelines `|` for object-based data flow', + '- Prefer pipelines | for object-based data flow', '', 'Directory Management:', '- Must use absolute paths to avoid navigation issues', @@ -125,14 +85,82 @@ function createPowerShellModelDescription(shell: string): string { 'Best Practices:', '- Use proper cmdlet names instead of aliases in scripts', '- Quote paths with spaces: "C:\\Path With Spaces"', - '- Use $ErrorActionPreference = "Stop" for strict error handling', '- Prefer PowerShell cmdlets over external commands when available', - '- Use Get-ChildItem instead of dir/ls for file listings', + '- Prefer idiomatic PowerShell like Get-ChildItem instead of dir or ls for file listings', '- Use Test-Path to check file/directory existence', '- Be specific with Select-Object properties to avoid excessive output' ].join('\n'); } +const genericDescription = ` +Command Execution: +- Does NOT support multi-line commands +- Use && to chain simple commands on one line +- Prefer pipelines | over temporary files for data flow + +Directory Management: +- Must use absolute paths to avoid navigation issues +- Use $PWD for current directory references +- Consider using pushd/popd for directory stack management +- Supports directory shortcuts like ~ and - + +Program Execution: +- Supports Python, Node.js, and other executables +- Install packages via package managers (brew, apt, etc.) +- Use which or command -v to verify command availability + +Background Processes: +- For long-running tasks (e.g., servers), set isBackground=true +- Returns a terminal ID for checking status and runtime later + +Output Management: +- Output is automatically truncated if longer than 60KB to prevent context overflow +- Use head, tail, grep, awk to filter and limit output size +- For pager commands, disable paging: git --no-pager or add | cat +- Use wc -l to count lines before displaying large outputs + +Best Practices: +- Quote variables: "$var" instead of $var to handle spaces +- Use find with -exec or xargs for file operations +- Be specific with commands to avoid excessive output`; + +function createBashModelDescription(): string { + return [ + 'This tool allows you to execute shell commands in a persistent bash terminal session, preserving environment variables, working directory, and other context across multiple commands.', + genericDescription, + '- Use [[ ]] for conditional tests instead of [ ]', + '- Prefer $() over backticks for command substitution', + '- Use set -e at start of complex commands to exit on errors' + ].join('\n'); +} + +function createZshModelDescription(): string { + return [ + 'This tool allows you to execute shell commands in a persistent zsh terminal session, preserving environment variables, working directory, and other context across multiple commands.', + genericDescription, + '- Use type to check command type (builtin, function, alias)', + '- Use jobs, fg, bg for job control', + '- Use [[ ]] for conditional tests instead of [ ]', + '- Prefer $() over backticks for command substitution', + '- Use setopt errexit for strict error handling', + '- Take advantage of zsh globbing features (**, extended globs)' + ].join('\n'); +} + +function createFishModelDescription(): string { + return [ + 'This tool allows you to execute shell commands in a persistent fish terminal session, preserving environment variables, working directory, and other context across multiple commands.', + genericDescription, + '- Use type to check command type (builtin, function, alias)', + '- Use jobs, fg, bg for job control', + '- Use test expressions for conditionals (no [[ ]] syntax)', + '- Prefer command substitution with () syntax', + '- Variables are arrays by default, use $var[1] for first element', + '- Use set -e for strict error handling', + '- Take advantage of fish\'s autosuggestions and completions' + ].join('\n'); +} + export async function createRunInTerminalToolData( accessor: ServicesAccessor ): Promise { @@ -145,6 +173,10 @@ export async function createRunInTerminalToolData( let modelDescription: string; if (shell && os && isPowerShell(shell, os)) { modelDescription = createPowerShellModelDescription(shell); + } else if (shell && os && isZsh(shell, os)) { + modelDescription = createZshModelDescription(); + } else if (shell && os && isFish(shell, os)) { + modelDescription = createFishModelDescription(); } else { modelDescription = createBashModelDescription(); } @@ -226,7 +258,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _terminalToolCreator: ToolTerminalCreator; private readonly _commandSimplifier: CommandSimplifier; - private readonly _profileFetcher: TerminalProfileFetcher; + protected readonly _profileFetcher: TerminalProfileFetcher; private readonly _telemetry: RunInTerminalToolTelemetry; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; protected readonly _sessionTerminalAssociations: Map = new Map(); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 671a513798b..209ab524a10 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -30,10 +30,8 @@ class TestRunInTerminalTool extends RunInTerminalTool { get commandLineAutoApprover() { return this._commandLineAutoApprover; } get sessionTerminalAssociations() { return this._sessionTerminalAssociations; } + get profileFetcher() { return this._profileFetcher; } - getCopilotProfile() { - return this._getCopilotProfile(); - } setBackendOs(os: OperatingSystem) { this._osBackend = Promise.resolve(os); } @@ -946,21 +944,64 @@ suite('RunInTerminalTool', () => { }); }); - suite('getCopilotShellOrProfile', () => { +}); + +suite('TerminalProfileFetcher', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + let configurationService: TestConfigurationService; + let testTool: TestRunInTerminalTool; + + setup(() => { + configurationService = new TestConfigurationService(); + + instantiationService = workbenchInstantiationService({ + configurationService: () => configurationService, + }, store); + instantiationService.stub(ILanguageModelToolsService, { + getTools() { + return []; + }, + }); + instantiationService.stub(ITerminalService, { + onDidDisposeInstance: new Emitter().event + }); + instantiationService.stub(IChatService, { + onDidDisposeSession: new Emitter<{ sessionId: string; reason: 'cleared' }>().event + }); + instantiationService.stub(ITerminalProfileResolverService, { + getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) + }); + + testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); + }); + + function setConfig(key: string, value: unknown) { + configurationService.setUserConfiguration(key, value); + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: new Set([key]), + source: ConfigurationTarget.USER, + change: null!, + }); + } + + suite('getCopilotProfile', () => { test('should return custom profile when configured', async () => { - runInTerminalTool.setBackendOs(OperatingSystem.Windows); + testTool.setBackendOs(OperatingSystem.Windows); const customProfile = Object.freeze({ path: 'C:\\Windows\\System32\\powershell.exe', args: ['-NoProfile'] }); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, customProfile); - const result = await runInTerminalTool.getCopilotProfile(); + const result = await testTool.profileFetcher.getCopilotProfile(); strictEqual(result, customProfile); }); test('should fall back to default shell when no custom profile is configured', async () => { - runInTerminalTool.setBackendOs(OperatingSystem.Linux); + testTool.setBackendOs(OperatingSystem.Linux); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); - const result = await runInTerminalTool.getCopilotProfile(); + const result = await testTool.profileFetcher.getCopilotProfile(); strictEqual(typeof result, 'object'); strictEqual((result as ITerminalProfile).path, 'pwsh'); // From the mock ITerminalProfileResolverService }); From c6277d5d8df123d15b98002153435a63371af50c Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sun, 19 Oct 2025 10:03:45 -0700 Subject: [PATCH 1293/4355] Chat sessions generalized option pickers (#272167) * generalize chat session options prototype * dynamically generate a menu per option group * fix bugs * polish * polish --- .../api/browser/mainThreadChatSessions.ts | 33 ++- .../workbench/api/common/extHost.protocol.ts | 9 +- .../api/common/extHostChatSessions.ts | 32 +-- .../browser/actions/chatExecuteActions.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 223 +++++++++++++----- .../chat/browser/chatSessions.contribution.ts | 56 ++--- .../chatSessionPickerActionItem.ts | 105 +++++++++ .../sessionModelPickerActionItem.ts | 79 ------- .../chat/common/chatSessionsService.ts | 41 ++-- .../vscode.proposed.chatSessionsProvider.d.ts | 52 +++- 10 files changed, 380 insertions(+), 252 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index b031593220b..40f3559265f 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -20,7 +20,6 @@ import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; import { ChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; import { ChatSessionUri } from '../../contrib/chat/common/chatUri.js'; -import { ILanguageModelChatMetadata } from '../../contrib/chat/common/languageModels.js'; import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IEditorGroup, IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; @@ -38,8 +37,8 @@ export class ObservableChatSession extends Disposable implements ChatSession { readonly sessionResource: URI; readonly providerHandle: number; readonly history: Array; - private _options?: { model?: ILanguageModelChatMetadata } | undefined; - public get options(): { model?: ILanguageModelChatMetadata } | undefined { + private _options?: Record; + public get options(): Record | undefined { return this._options; } private readonly _progressObservable = observableValue(this, []); @@ -316,7 +315,6 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat readonly onDidChangeItems: Emitter; }>()); private readonly _contentProvidersRegistrations = this._register(new DisposableMap()); - private readonly _contentProviderModels = new Map(); private readonly _sessionTypeToHandle = new Map(); private readonly _activeSessions = new Map(); @@ -478,6 +476,17 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat try { await session.initialize(token); + if (session.options) { + for (const [chatSessionType, handle] of this._sessionTypeToHandle) { + if (handle === providerHandle) { + for (const [optionId, value] of Object.entries(session.options)) { + this._chatSessionsService.setSessionOption(chatSessionType, id, optionId, value); + } + break; + } + } + } + return session; } catch (error) { session.dispose(); @@ -498,19 +507,14 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._sessionTypeToHandle.set(chatSessionType, handle); this._contentProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(chatSessionType, provider)); this._proxy.$provideChatSessionProviderOptions(handle, CancellationToken.None).then(options => { - if (options?.models && options.models.length) { - this._contentProviderModels.set(handle, options.models); - const serviceModels: ILanguageModelChatMetadata[] = options.models.map(m => ({ ...m })); - this._chatSessionsService.setModelsForSessionType(chatSessionType, handle, serviceModels); + if (options?.optionGroups && options.optionGroups.length) { + this._chatSessionsService.setOptionGroupsForSessionType(chatSessionType, handle, options.optionGroups); } }).catch(err => this._logService.error('Error fetching chat session options', err)); } $unregisterChatSessionContentProvider(handle: number): void { this._contentProvidersRegistrations.deleteAndDispose(handle); - this._contentProviderModels.delete(handle); - - // Remove session type mapping for (const [sessionType, h] of this._sessionTypeToHandle) { if (h === handle) { this._sessionTypeToHandle.delete(sessionType); @@ -604,13 +608,6 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } } - /** - * Get the available models for a session provider - */ - getModelsForProvider(handle: number): ILanguageModelChatMetadata[] | undefined { - return this._contentProviderModels.get(handle); - } - /** * Notify the extension about option changes for a session */ diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8c1fc131190..3555ad1a0cf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -59,10 +59,10 @@ import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js'; import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatSessionContext, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; -import { IChatSessionItem } from '../../contrib/chat/common/chatSessionsService.js'; +import { IChatSessionItem, IChatSessionProviderOptionGroup } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; -import { IChatMessage, IChatResponsePart, ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatSelector } from '../../contrib/chat/common/languageModels.js'; +import { IChatMessage, IChatResponsePart, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatSelector } from '../../contrib/chat/common/languageModels.js'; import { IPreparedToolInvocation, IToolInvocation, IToolInvocationPreparationContext, IToolProgressStep, IToolResult, ToolDataSource } from '../../contrib/chat/common/languageModelToolsService.js'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from '../../contrib/debug/common/debug.js'; import { McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from '../../contrib/mcp/common/mcpTypes.js'; @@ -3184,10 +3184,11 @@ export interface ChatSessionDto { hasActiveResponseCallback: boolean; hasRequestHandler: boolean; supportsInterruption: boolean; - options?: { model?: ILanguageModelChatMetadata }; + options?: Record; } + export interface IChatSessionProviderOptions { - models?: ILanguageModelChatMetadata[]; + optionGroups?: IChatSessionProviderOptionGroup[]; } export interface MainThreadChatSessionsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 52562834ae9..9c8f312ceef 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -276,24 +276,14 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio this._proxy.$handleProgressComplete(handle, id, 'ongoing'); }); } - const { capabilities, extension } = provider; + const { capabilities } = provider; return { id: sessionId + '', resource: URI.revive(resource), hasActiveResponseCallback: !!session.activeResponseCallback, hasRequestHandler: !!session.requestHandler, supportsInterruption: !!capabilities?.supportsInterruptions, - options: { - ...(session.options?.model && { - model: { - ...session.options.model, - extension: extension.identifier, - vendor: 'contributed', - modelPickerCategory: undefined, - capabilities: {}, - } - }) - }, + options: session.options, history: session.history.map(turn => { if (turn instanceof extHostTypes.ChatRequestTurn) { return { type: 'request' as const, prompt: turn.prompt, participant: turn.participant }; @@ -343,24 +333,12 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } try { - const extOptions = await provider.provideChatSessionProviderOptions(token); - if (!extOptions) { + const { optionGroups } = await provider.provideChatSessionProviderOptions(token); + if (!optionGroups) { return; } - - const { models } = extOptions; - if (!models || models.length === 0) { - return {}; - } return { - models: models.map(m => ({ - ...m, - extension: entry.extension.identifier, - vendor: 'contributed', - modelPickerCategory: undefined, - capabilities: {}, - - })) + optionGroups, }; } catch (error) { this._logService.error(`Error calling provideChatSessionProviderOptions for handle ${handle}:`, error); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 1147483e655..4776ade978d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -494,7 +494,7 @@ export class ChatSessionOpenModelPickerAction extends Action2 { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; if (widget) { - widget.input.openChatSessionModelPicker(); + widget.input.openChatSessionPicker(); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 6501791edd1..5f1d370ba8d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -77,7 +77,7 @@ import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditi import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup, IChatService } from '../common/chatService.js'; -import { IChatSessionsService } from '../common/chatSessionsService.js'; +import { IChatSessionProviderOptionItem, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; @@ -98,7 +98,7 @@ import { ChatDragAndDrop } from './chatDragAndDrop.js'; import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from './chatEditing/chatEditingActions.js'; import { ChatFollowups } from './chatFollowups.js'; import { ChatSelectedTools } from './chatSelectedTools.js'; -import { ChatSessionModelPickerActionItem } from './chatSessions/sessionModelPickerActionItem.js'; +import { ChatSessionPickerActionItem, IChatSessionPickerDelegate } from './chatSessions/chatSessionPickerActionItem.js'; import { IChatViewState } from './chatWidget.js'; import { ChatImplicitContext } from './contrib/chatImplicitContext.js'; import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js'; @@ -291,13 +291,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private chatModeKindKey: IContextKey; private withinEditSessionKey: IContextKey; private filePartOfEditSessionKey: IContextKey; - private chatSessionHasModels: IContextKey; + private chatSessionHasOptions: IContextKey; private modelWidget: ModelPickerActionItem | undefined; private modeWidget: ModePickerActionItem | undefined; - private chatSessionModelWidget: ChatSessionModelPickerActionItem | undefined; + private chatSessionPickerWidgets: Map; + private additionalSessionPickerContainers: HTMLElement[]; private readonly _waitForPersistedLanguageModel: MutableDisposable; private _onDidChangeCurrentLanguageModel: Emitter; - private _onDidChangeChatSessionLanguageModel: Emitter; + private readonly _chatSessionOptionEmitters: Map>; private _currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined; @@ -445,16 +446,18 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeCurrentLanguageModel = this._register(new Emitter()); this._onDidChangeCurrentChatMode = this._register(new Emitter()); this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; - this._onDidChangeChatSessionLanguageModel = this._register(new Emitter()); + this._chatSessionOptionEmitters = new Map(); this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); this._chatEditsActionsDisposables = this._register(new DisposableStore()); this._chatEditsDisposables = this._register(new DisposableStore()); this._attemptedWorkingSetEntriesCount = 0; this._currentModeObservable = observableValue('currentMode', this.options.defaultMode ?? ChatMode.Agent); + this.chatSessionPickerWidgets = new Map(); + this.additionalSessionPickerContainers = []; this._register(this.editorService.onDidActiveEditorChange(() => { this._indexOfLastOpenedContext = -1; - this.refreshChatSessionPicker(); + this.refreshChatSessionPickers(); })); this._attachmentModel = this._register(this.instantiationService.createInstance(ChatAttachmentModel)); @@ -477,7 +480,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatModeKindKey = ChatContextKeys.chatModeKind.bindTo(contextKeyService); this.withinEditSessionKey = ChatContextKeys.withinEditSessionDiff.bindTo(contextKeyService); this.filePartOfEditSessionKey = ChatContextKeys.filePartOfEditSession.bindTo(contextKeyService); - this.chatSessionHasModels = ChatContextKeys.chatSessionHasModels.bindTo(contextKeyService); + this.chatSessionHasOptions = ChatContextKeys.chatSessionHasModels.bindTo(contextKeyService); const chatToolCount = ChatContextKeys.chatToolCount.bindTo(contextKeyService); @@ -651,11 +654,75 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.modeWidget?.show(); } - public openChatSessionModelPicker(): void { - this.chatSessionModelWidget?.show(); + public openChatSessionPicker(): void { + const firstWidget = this.chatSessionPickerWidgets?.values()?.next().value; + firstWidget?.show(); } - public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { + /** + * Create picker widgets for all registered option groups for the current session type. + * Returns an array of all created widgets. + */ + private createChatSessionPickerWidgets(action: MenuItemAction): ChatSessionPickerActionItem[] { + // Helper to resolve chat session context + const resolveChatSessionContext = () => { + const sessionId = this._widget?.viewModel?.model.sessionId; + if (!sessionId) { + return undefined; + } + return this.chatService.getChatSessionFromInternalId(sessionId); + }; + + // Get all option groups for the current session type + const ctx = resolveChatSessionContext(); + const optionGroups = ctx ? this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType) : undefined; + if (!optionGroups || optionGroups.length === 0) { + return []; + } + + // Clear existing widgets + this.chatSessionPickerWidgets.clear(); + + // Create a widget for each option group + const widgets: ChatSessionPickerActionItem[] = []; + for (const optionGroup of optionGroups) { + const initialItem = this.getCurrentOptionForGroup(optionGroup.id); + const initialState = { group: optionGroup, item: initialItem }; + + // Create delegate for this option group + const itemDelegate: IChatSessionPickerDelegate = { + getCurrentOption: () => this.getCurrentOptionForGroup(optionGroup.id), + onDidChangeOption: this.getOrCreateOptionEmitter(optionGroup.id).event, + setOption: (option: IChatSessionProviderOptionItem) => { + const ctx = resolveChatSessionContext(); + if (!ctx) { + return; + } + this.getOrCreateOptionEmitter(optionGroup.id).fire(option); + this.chatSessionsService.notifySessionOptionsChange( + ctx.chatSessionType, + ctx.chatSessionId, + [{ optionId: optionGroup.id, value: option.id }] + ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); + }, + getAllOptions: () => { + const ctx = resolveChatSessionContext(); + if (!ctx) { + return []; + } + const groups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); + const group = groups?.find(g => g.id === optionGroup.id); + return group?.items ?? []; + } + }; + + const widget = this.instantiationService.createInstance(ChatSessionPickerActionItem, action, initialState, itemDelegate); + this.chatSessionPickerWidgets.set(optionGroup.id, widget); + widgets.push(widget); + } + + return widgets; + } public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { this._currentLanguageModel = model; if (this.cachedDimensions) { @@ -1087,32 +1154,71 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderAttachedContext(); } - private refreshChatSessionPicker(): ILanguageModelChatMetadataAndIdentifier | undefined { - this.chatSessionHasModels.set(false); + private getOrCreateOptionEmitter(optionGroupId: string): Emitter { + let emitter = this._chatSessionOptionEmitters.get(optionGroupId); + if (!emitter) { + emitter = this._register(new Emitter()); + this._chatSessionOptionEmitters.set(optionGroupId, emitter); + } + return emitter; + } + + /** + * Refresh all registered option groups for the current chat session. + * Fires events for each option group with their current selection. + */ + private refreshChatSessionPickers(): void { const sessionId = this._widget?.viewModel?.model.sessionId; if (!sessionId) { + this.chatSessionHasOptions.set(false); return; } const ctx = this.chatService.getChatSessionFromInternalId(sessionId); if (!ctx) { + this.chatSessionHasOptions.set(false); return; } - const models = this.chatSessionsService.getModelsForSessionType(ctx.chatSessionType); - if (!models) { + const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); + if (!optionGroups || optionGroups.length === 0) { + this.chatSessionHasOptions.set(false); return; } - this.chatSessionHasModels.set(true); + this.chatSessionHasOptions.set(true); - const currentModelId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, 'model'); - if (!currentModelId) { + // Refresh each registered option group + for (const optionGroup of optionGroups) { + const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroup.id); + if (currentOptionId) { + const item = optionGroup.items.find(m => m.id === currentOptionId); + if (item) { + this.getOrCreateOptionEmitter(optionGroup.id).fire(item); + } + } + } + } + + /** + * Get the current option for a specific option group. + * If no option is currently set, initializes with the first item as default. + */ + private getCurrentOptionForGroup(optionGroupId: string): IChatSessionProviderOptionItem | undefined { + const sessionId = this._widget?.viewModel?.model.sessionId; + if (!sessionId) { return; } - const model = models.find(m => m.identifier === currentModelId); - if (model) { - this._onDidChangeChatSessionLanguageModel.fire(model); + const ctx = this.chatService.getChatSessionFromInternalId(sessionId); + if (!ctx) { + return; + } + const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); + const optionGroup = optionGroups?.find(g => g.id === optionGroupId); + if (!optionGroup || optionGroup.items.length === 0) { + return; } - return model; + + const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); + return optionGroup.items.find(m => m.id === currentOptionId); } render(container: HTMLElement, initialValue: string, widget: IChatWidget) { @@ -1327,44 +1433,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; return this.modeWidget = this.instantiationService.createInstance(ModePickerActionItem, action, delegate); } else if (action.id === ChatSessionOpenModelPickerAction.ID && action instanceof MenuItemAction) { - const resolveChatSessionContext = () => { - const sessionId = this._widget?.viewModel?.model.sessionId; - if (!sessionId) { - return; - } - const chatSessionContext = this.chatService.getChatSessionFromInternalId(sessionId); - if (!chatSessionContext) { - return; - } - return chatSessionContext; - }; - - const itemDelegate: IModelPickerDelegate = { - getCurrentModel: () => { - return this.refreshChatSessionPicker(); - }, - onDidChangeModel: this._onDidChangeChatSessionLanguageModel.event, - setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { - const ctx = resolveChatSessionContext(); - if (!ctx) { - return; - } - this._onDidChangeChatSessionLanguageModel.fire(model); - this.chatSessionsService.notifySessionOptionsChange( - ctx.chatSessionType, - ctx.chatSessionId, - [{ optionId: 'model', value: model.identifier }] - ).catch(err => this.logService.error('Failed to notify extension of model change:', err)); - }, - getModels: () => { - const ctx = resolveChatSessionContext(); - if (!ctx) { - return []; - } - return this.chatSessionsService.getModelsForSessionType(ctx.chatSessionType) || []; - } - }; - return this.chatSessionModelWidget = this.instantiationService.createInstance(ChatSessionModelPickerActionItem, action, this.refreshChatSessionPicker(), itemDelegate); + // Dynamically create pickers for all registered option groups + const widgets = this.createChatSessionPickerWidgets(action); + // Return the first widget, but note: we need a different approach to show all + // The toolbar's actionViewItemProvider can only return one item per action + // We'll need to render additional widgets separately + return widgets[0]; } return undefined; } @@ -1372,6 +1446,35 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.inputActionsToolbar.getElement().classList.add('chat-input-toolbar'); this.inputActionsToolbar.context = { widget } satisfies IChatExecuteActionContext; this._register(this.inputActionsToolbar.onDidChangeMenuItems(() => { + // Clean up any previously rendered additional pickers + for (const container of this.additionalSessionPickerContainers) { + container.remove(); + } + this.additionalSessionPickerContainers = []; + if (this.chatSessionPickerWidgets.size > 1) { + const toolbarElement = this.inputActionsToolbar.getElement(); + // Find where the first session picker is rendered + const firstPickerElement = toolbarElement.querySelector('.chat-sessionPicker-item'); + if (firstPickerElement && firstPickerElement.parentElement) { + // Get all widgets except the first one (which is already rendered by the toolbar) + const additionalWidgets = Array.from(this.chatSessionPickerWidgets.values()).slice(1); + let insertAfter = firstPickerElement; + for (const widget of additionalWidgets) { + // Create a container for this widget + const container = dom.$('.action-item'); + widget.render(container); + // Insert after the previous picker + if (insertAfter.nextSibling) { + insertAfter.parentElement!.insertBefore(container, insertAfter.nextSibling); + } else { + insertAfter.parentElement!.appendChild(container); + } + this.additionalSessionPickerContainers.push(container); + insertAfter = container; + } + } + } + if (this.cachedDimensions && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) { this.layout(this.cachedDimensions.height, this.cachedDimensions.width); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index e15549a35ae..fea9127b3ec 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -22,10 +22,9 @@ import { ExtensionsRegistry } from '../../../services/extensions/common/extensio import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionModelInfo, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; +import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatModeKind, VIEWLET_ID } from '../common/constants.js'; -import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier } from '../common/languageModels.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; @@ -110,7 +109,7 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint; + private readonly _optionsCache: Map; public getOption(optionId: string): string | undefined { return this._optionsCache.get(optionId); } @@ -122,12 +121,14 @@ class ContributedChatSessionData implements IDisposable { readonly session: ChatSession, readonly chatSessionType: string, readonly id: string, - readonly options: { model?: ILanguageModelChatMetadata } | undefined, + readonly options: Record | undefined, private readonly onWillDispose: (session: ChatSession, chatSessionType: string, id: string) => void ) { this._optionsCache = new Map(); - if (options?.model) { - this._optionsCache.set('model', options.model.id); + if (options) { + for (const [key, value] of Object.entries(options)) { + this._optionsCache.set(key, value); + } } this._disposableStore = new DisposableStore(); this._disposableStore.add(this.session.onWillDispose(() => { @@ -158,7 +159,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private readonly _onDidChangeInProgress = this._register(new Emitter()); public get onDidChangeInProgress() { return this._onDidChangeInProgress.event; } private readonly inProgressMap: Map = new Map(); - private readonly _sessionTypeModels: Map = new Map(); + private readonly _sessionTypeOptions: Map = new Map(); constructor( @ILogService private readonly _logService: ILogService, @@ -619,46 +620,21 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } /** - * Store models for a session type + * Store option groups for a session type */ - public setModelsForSessionType(chatSessionType: string, handle: number, models: IChatSessionModelInfo[]): void { - if (models) { - const mappedModels: ILanguageModelChatMetadataAndIdentifier[] = []; - const contribution = this._contributions.get(chatSessionType); - const extensionIdentifier = contribution?.extensionDescription.identifier; - if (!extensionIdentifier) { - this._logService.error('Extension identifier not found for chat session type:', chatSessionType); - return; - } - for (const m of models) { - mappedModels.push({ - identifier: m.id, - metadata: { - id: m.id, - name: m.name, - family: m.family, - vendor: 'contributed', - version: m.version, - maxInputTokens: m.maxInputTokens, - maxOutputTokens: m.maxOutputTokens, - modelPickerCategory: undefined, - extension: extensionIdentifier, - isDefault: false, - isUserSelectable: true - } - }); - } - this._sessionTypeModels.set(chatSessionType, mappedModels); + public setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void { + if (optionGroups) { + this._sessionTypeOptions.set(chatSessionType, optionGroups); } else { - this._sessionTypeModels.delete(chatSessionType); + this._sessionTypeOptions.delete(chatSessionType); } } /** - * Get available models for a session type + * Get available option groups for a session type */ - public getModelsForSessionType(chatSessionType: string): ILanguageModelChatMetadataAndIdentifier[] | undefined { - return this._sessionTypeModels.get(chatSessionType); + public getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined { + return this._sessionTypeOptions.get(chatSessionType); } private _optionsChangeCallback?: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts new file mode 100644 index 00000000000..3415f5e77eb --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from '../../../../../base/common/actions.js'; +import { Event } from '../../../../../base/common/event.js'; +import * as dom from '../../../../../base/browser/dom.js'; +import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js'; +import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider, IActionWidgetDropdownOptions } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; +import { ActionWidgetDropdownActionViewItem } from '../../../../../platform/actions/browser/actionWidgetDropdownActionViewItem.js'; +import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem } from '../../common/chatSessionsService.js'; +import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { renderLabelWithIcons } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { localize } from '../../../../../nls.js'; + + +export interface IChatSessionPickerDelegate { + readonly onDidChangeOption: Event; + getCurrentOption(): IChatSessionProviderOptionItem | undefined; + setOption(option: IChatSessionProviderOptionItem): void; + getAllOptions(): IChatSessionProviderOptionItem[]; +} + +function delegateToWidgetActionsProvider(delegate: IChatSessionPickerDelegate): IActionWidgetDropdownActionProvider { + return { + getActions: () => { + return delegate.getAllOptions().map(item => { + return { + id: item.id, + enabled: true, + icon: undefined, + checked: item.id === delegate.getCurrentOption()?.id, + class: undefined, + description: undefined, + tooltip: item.name, + label: item.name, + run: () => { + delegate.setOption(item); + } + } satisfies IActionWidgetDropdownAction; + }); + } + }; +} + +/** + * Action view item for making an option selection for a contributed chat session + * These options are provided by the relevant ChatSession Provider + */ +export class ChatSessionPickerActionItem extends ActionWidgetDropdownActionViewItem { + currentOption: IChatSessionProviderOptionItem | undefined; + constructor( + action: IAction, + initialState: { group: IChatSessionProviderOptionGroup; item: IChatSessionProviderOptionItem | undefined }, + delegate: IChatSessionPickerDelegate, + @IActionWidgetService actionWidgetService: IActionWidgetService, + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService, + @IChatEntitlementService chatEntitlementService: IChatEntitlementService, + @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + const { group, item } = initialState; + const actionWithLabel: IAction = { + ...action, + label: item?.name || group.name, + tooltip: group.description || group.name, + run: () => { } + }; + + const sessionPickerActionWidgetOptions: Omit = { + actionProvider: delegateToWidgetActionsProvider(delegate), + actionBarActionProvider: undefined, + }; + + super(actionWithLabel, sessionPickerActionWidgetOptions, actionWidgetService, keybindingService, contextKeyService); + this.currentOption = item; + this._register(delegate.onDidChangeOption(newOption => { + this.currentOption = newOption; + if (this.element) { + this.renderLabel(this.element); + } + })); + } + protected override renderLabel(element: HTMLElement): IDisposable | null { + const domChildren = []; + domChildren.push(dom.$('span.chat-session-option-label', undefined, this.currentOption?.name ?? localize('chat.sessionPicker.label', "Pick Option"))); + domChildren.push(...renderLabelWithIcons(`$(chevron-down)`)); + dom.reset(element, ...domChildren); + this.setAriaLabelAttributes(element); + return null; + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('chat-sessionPicker-item'); + } + +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts deleted file mode 100644 index 276a9ba6fe2..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/sessionModelPickerActionItem.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IAction } from '../../../../../base/common/actions.js'; -import { ILanguageModelChatMetadataAndIdentifier } from '../../common/languageModels.js'; -import { localize } from '../../../../../nls.js'; -import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js'; -import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider, IActionWidgetDropdownOptions } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; -import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; -import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; -import { IModelPickerDelegate, ModelPickerActionItem } from '../modelPicker/modelPickerActionItem.js'; -import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; - - -function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate): IActionWidgetDropdownActionProvider { - return { - getActions: () => { - return delegate.getModels().map(model => { - return { - id: model.metadata.id, - enabled: true, - icon: model.metadata.statusIcon, - checked: model.identifier === delegate.getCurrentModel()?.identifier, - class: undefined, - description: model.metadata.detail, - tooltip: model.metadata.tooltip ?? model.metadata.name, - label: model.metadata.name, - run: () => { - delegate.setModel(model); - - } - } satisfies IActionWidgetDropdownAction; - }); - } - }; -} - -/** - * Action view item for selecting the model for a contributed chat session - * These models are also contributed by the chat session provider and cannot be used outside that context - * This may also be generalized into eg 'Chat Session Picker 1' to be used however the provider wants - */ -export class ChatSessionModelPickerActionItem extends ModelPickerActionItem { - constructor( - action: IAction, - currentModel: ILanguageModelChatMetadataAndIdentifier | undefined, - delegate: IModelPickerDelegate, - @IActionWidgetService actionWidgetService: IActionWidgetService, - @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService commandService: ICommandService, - @IChatEntitlementService chatEntitlementService: IChatEntitlementService, - @IKeybindingService keybindingService: IKeybindingService, - @ITelemetryService telemetryService: ITelemetryService, - ) { - const actionWithLabel: IAction = { - ...action, - label: currentModel?.metadata.name ?? localize('chat.modelPicker.label', "Pick Model"), - tooltip: localize('chat.modelPicker.label', "Pick Model"), - run: () => { } - }; - - const modelPickerActionWidgetOptions: Omit = { - actionProvider: modelDelegateToWidgetActionsProvider(delegate), - actionBarActionProvider: undefined, - }; - - super(actionWithLabel, currentModel, modelPickerActionWidgetOptions, delegate, actionWidgetService, contextKeyService, commandService, chatEntitlementService, keybindingService, telemetryService); - this._register(delegate.onDidChangeModel(model => { - this.currentModel = model; - if (this.element) { - this.renderLabel(this.element); - } - })); - } -} diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 0b22578f247..3b06d7e7650 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -15,7 +15,6 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IEditableData } from '../../../common/views.js'; import { IChatAgentRequest } from './chatAgents.js'; import { IChatProgress } from './chatService.js'; -import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier } from './languageModels.js'; export const enum ChatSessionStatus { Failed = 0, @@ -29,6 +28,19 @@ export interface IChatSessionCommandContribution { when?: string; } +export interface IChatSessionProviderOptionItem { + id: string; + name: string; + // [key: string]: any; +} + +export interface IChatSessionProviderOptionGroup { + id: string; + name: string; + description?: string; + items: IChatSessionProviderOptionItem[]; +} + export interface IChatSessionsExtensionPoint { readonly type: string; readonly name: string; @@ -68,7 +80,11 @@ export interface ChatSession extends IDisposable { readonly sessionResource: URI; readonly onWillDispose: Event; history: Array; - options: { model?: ILanguageModelChatMetadata } | undefined; + /** + * Session options as key-value pairs. Keys correspond to option group IDs (e.g., 'models', 'subagents') + * and values are the selected option item IDs. + */ + options?: Record; readonly progressObs?: IObservable; readonly isCompleteObs?: IObservable; @@ -96,19 +112,6 @@ export interface IChatSessionContentProvider { provideChatSessionContent(sessionId: string, sessionResource: URI, token: CancellationToken): Promise; } -// TODO: Copy of LanguageModelChatInformation with various omissions -export interface IChatSessionModelInfo { - id: string; - name: string; - family: string; - tooltip?: string; - detail?: string; - version: string; - maxInputTokens: number; - maxOutputTokens: number; - capabilities?: { imageInput?: boolean; toolCalling?: boolean | number }; -} - export interface IChatSessionsService { readonly _serviceBrand: undefined; @@ -133,11 +136,11 @@ export interface IChatSessionsService { canResolveContentProvider(chatSessionType: string): Promise; provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise; - // Get available models for a session type - getModelsForSessionType(chatSessionType: string): ILanguageModelChatMetadataAndIdentifier[] | undefined; + // Get available option groups for a session type + getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined; - // Set available models for a session type (called by MainThreadChatSessions) - setModelsForSessionType(chatSessionType: string, handle: number, models?: IChatSessionModelInfo[]): void; + // Set available option groups for a session type (called by MainThreadChatSessions) + setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void; // Set callback for notifying extensions about option changes setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void; diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 534c45a1920..e40a444b9c4 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -149,9 +149,12 @@ declare module 'vscode' { readonly history: ReadonlyArray; /** - * Options configured for this session. + * Options configured for this session as key-value pairs. + * Keys correspond to option group IDs (e.g., 'models', 'subagents') + * and values are the selected option item IDs. + * TODO: Strongly type the keys */ - readonly options?: { model?: LanguageModelChatInformation }; + readonly options?: Record; /** * Callback invoked by the editor for a currently running response. This allows the session to push items for the @@ -252,11 +255,52 @@ declare module 'vscode' { supportsInterruptions?: boolean; } + /** + * Represents a single selectable item within a provider option group. + */ + export interface ChatSessionProviderOptionItem { + /** + * Unique identifier for the option item. + */ + readonly id: string; + + /** + * Human-readable name displayed in the UI. + */ + readonly name: string; + } + + /** + * Represents a group of related provider options (e.g., models, sub-agents). + */ + export interface ChatSessionProviderOptionGroup { + /** + * Unique identifier for the option group (e.g., "models", "subagents"). + */ + readonly id: string; + + /** + * Human-readable name for the option group. + */ + readonly name: string; + + /** + * Optional description providing context about this option group. + */ + readonly description?: string; + + /** + * The selectable items within this option group. + */ + readonly items: ChatSessionProviderOptionItem[]; + } + export interface ChatSessionProviderOptions { /** - * Set of available models. + * Provider-defined option groups (0-2 groups supported). + * Examples: models picker, sub-agents picker, etc. */ - models?: LanguageModelChatInformation[]; + optionGroups?: ChatSessionProviderOptionGroup[]; } /** From 78fb84bc707fb77b38530266d8d2dbc0306c3e47 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:21:37 +0200 Subject: [PATCH 1294/4355] SCM - update the default value for the `scm.repositories.selectionMode` setting (#272173) --- src/vs/workbench/contrib/scm/browser/scm.contribution.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmViewService.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 5c2cd7d202b..89474f78eef 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -356,7 +356,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('scm.repositories.selectionMode.single', "Only one repository can be selected at a time.") ], description: localize('scm.repositories.selectionMode', "Controls the selection mode of the repositories in the Source Control Repositories view."), - default: 'multiple' + default: 'single' }, 'scm.showActionButton': { type: 'boolean', diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 96f036a847c..7e3c11a21da 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -192,7 +192,7 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.treeDataSource); const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); - const selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'multiple', this.configurationService); + const selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index c4a7120ee15..26c8a37769a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -78,7 +78,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer('scm.repositories.selectionMode', 'multiple', this.configurationService); + this._selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); try { this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); From c5b94232980982af3766f5042b7c96f4c854c65c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:21:05 +0200 Subject: [PATCH 1295/4355] Engineering - add debug messages to verify that the SUID flag is removed from a file (#272175) --- .../azure-pipelines/linux/steps/product-build-linux-compile.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index 950faecccef..4a73211e216 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -400,5 +400,7 @@ steps: # SBOM generation uses hard links which are not supported by the Linux kernel # for files that have the SUID bit set, so we need to remove the SUID bit from # the chrome-sandbox file. + sudo ls -l $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox sudo chmod u-s $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox + sudo ls -l $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox displayName: Move artifacts to out directory From e0f4b8926e7af69e54f8af8870c64342efcdc120 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 20 Oct 2025 04:19:05 +0900 Subject: [PATCH 1296/4355] chore: bump electron@37.7.0 (#272157) * chore: bump electron@37.7.0 * chore: update node.js build * chore: bump distro --- .npmrc | 4 +- .nvmrc | 2 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ build/checksums/nodejs.txt | 14 ++-- cgmanifest.json | 12 +-- package-lock.json | 8 +- package.json | 4 +- remote/.npmrc | 4 +- 8 files changed, 99 insertions(+), 99 deletions(-) diff --git a/.npmrc b/.npmrc index 30da46b2d4c..5c19939fde2 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" -target="37.6.0" -ms_build_id="12506819" +target="37.7.0" +ms_build_id="12597478" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/.nvmrc b/.nvmrc index e2228113dd0..442c7587a99 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.19.0 +22.20.0 diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index a65112f108e..ab1540038a3 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -ea1b52ec0af04037b419897585ebb2e81f65ccd79dd8f331e26578d042c01a2f *chromedriver-v37.6.0-darwin-arm64.zip -667a9067a774a5ada82a44bc310a23657dc2942067732b6c2879ae0042e2faf2 *chromedriver-v37.6.0-darwin-x64.zip -5c588569bbbca2bcef0881ea877d70dafcf7955e0d19765797f60e2c4d228c8a *chromedriver-v37.6.0-linux-arm64.zip -87a781e82f4fc530820311edc059eafe30e9cec673704c8e73ebf452c5382bc6 *chromedriver-v37.6.0-linux-armv7l.zip -e1c04c3826e506765e90d95738d1cf0ecbb3a798f2f3a7bf17d0cc03fe0be1fe *chromedriver-v37.6.0-linux-x64.zip -53e9d63c01eb018f5be1ebf6d2cba92c25b3097f1500f3d7fe1703447f7f3452 *chromedriver-v37.6.0-mas-arm64.zip -d556851da7ba16ae4b0a39e0c74a1295dfc42875a485d01483babd8e180e2f82 *chromedriver-v37.6.0-mas-x64.zip -0687f18325df7ce6845491fda61b8b4c40d0cc268c8fa54448591461cfd7f7b8 *chromedriver-v37.6.0-win32-arm64.zip -e7a5e4ce6bd6f6d34bcf773ac0796924582ec0c3b37c9442d874b5617b38be69 *chromedriver-v37.6.0-win32-ia32.zip -a1491a67dd7b2a1300283ac862e9ff97adc9db279c3dad0e843e441758fd7e2a *chromedriver-v37.6.0-win32-x64.zip -56e01ec4745a98b0cec8fb9aad00396072127883e76781d912bf3410c1da4cfc *electron-api.json -2f71423b81668b01a1e823593696800f22c262170e89372a3a00388f288cc914 *electron-v37.6.0-darwin-arm64-dsym-snapshot.zip -d632b133b742b7a9c94baa6577cbf528e6cd3a92afbf21245b56d37c580b062b *electron-v37.6.0-darwin-arm64-dsym.zip -a4f47ecea85e134d1b305b7286ef0ada0de714e2874c267169d9d3eafb2c51d2 *electron-v37.6.0-darwin-arm64-symbols.zip -82869e916e74cb763c03fefb5fcee1fc99536e597716d4f3a9c716f9c37effab *electron-v37.6.0-darwin-arm64.zip -5314570f28ca70a6aead23b1902da9a0cb120abb364e3bed12605f36571a4ce2 *electron-v37.6.0-darwin-x64-dsym-snapshot.zip -5689342a29ab4b9fa1b3f5b153d3eb9e7dbe12a10c07a7877b368b3ffbe82004 *electron-v37.6.0-darwin-x64-dsym.zip -978cf0fd18f2b2518e44c35cfd63d6268d28aadeff759212fe4d9b2bf3efa3b9 *electron-v37.6.0-darwin-x64-symbols.zip -258a104f781c50939ec6b45177a6f810da646ade15567a09cf9d14ec81b82cb2 *electron-v37.6.0-darwin-x64.zip -f2053132ca60ec546fccfbf0265d54b1405ad28156fda17c621a461b0c1be2e0 *electron-v37.6.0-linux-arm64-debug.zip -11c2461e956e525ab36784db0e2817fe35ff53a1154285f7ea2827df6f2954d8 *electron-v37.6.0-linux-arm64-symbols.zip -7c76a5147a9870b8bcbd859278148e1e326f98ea6a5dcb3ac2707d39bd22b2d5 *electron-v37.6.0-linux-arm64.zip -92f444a60f677a41d30afaadc0c8e754888ca4829a859b14459c9109b68e1fb0 *electron-v37.6.0-linux-armv7l-debug.zip -2ef9ee6afd566654950f06f8c8f32186133eadd015d74f0074d4c5a7d93b8be2 *electron-v37.6.0-linux-armv7l-symbols.zip -0d4a49b5f462972173daf6e919664cc04cad7a7d2a96b074cb181ea07bb0cd74 *electron-v37.6.0-linux-armv7l.zip -4b9dee173eddf90b2969b74ed86c6a81cd97c208e35b3db115da1f6534e31308 *electron-v37.6.0-linux-x64-debug.zip -cefdb0e96f980fed3ae22c62a204ccb6754311959ba2867a759887c27cbf7f09 *electron-v37.6.0-linux-x64-symbols.zip -02e644d75392a1ea8991106bc77e1db243ee1fc0c23107dc3b253ed545dd4c66 *electron-v37.6.0-linux-x64.zip -3fbfb12b83eb678a84a102808a338dc7d828362a012be0f315ccbfee098aeb1c *electron-v37.6.0-mas-arm64-dsym-snapshot.zip -ce6f18733a966fc9f2ab8e3ca0c9ee15f8ca1abd7f219990fe4795ff9e4946d7 *electron-v37.6.0-mas-arm64-dsym.zip -293ac1f466ec8d6888df1702e2ba2d2f1414068259af5ece7a0170044b2d2de3 *electron-v37.6.0-mas-arm64-symbols.zip -ecc1efd59bd23ae508cf0595594c8dda225d0e6eeb2df7f90b56cdb763aeb2c1 *electron-v37.6.0-mas-arm64.zip -c378220d65f5cf4b8ecd8823a4d8499111c77d2377b6bcb7d0727c567254ec06 *electron-v37.6.0-mas-x64-dsym-snapshot.zip -22c718ab643ff134262a3308104814f9789307f43ec045e6a132dfb57d7bc364 *electron-v37.6.0-mas-x64-dsym.zip -022d38652a15f44b13c3ee6a0691650f3dc7d0fd6827f79877de3f8bbd3bd68c *electron-v37.6.0-mas-x64-symbols.zip -258c44b8bae66e714cb26b11451f97e2678e9d0b6536cac19d74d49544f5805f *electron-v37.6.0-mas-x64.zip -c4927ece20680a6d2940ce5acee0c051abb96c8ca09c071bced548fbbfa9b3b1 *electron-v37.6.0-win32-arm64-pdb.zip -469335cb89bca0fcaefd33922f9db39f283b8a6009e84dadb8bb98de94e4c67c *electron-v37.6.0-win32-arm64-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-arm64-toolchain-profile.zip -1d09e49e246fc36052e4eb55013a907af6d1754521924c8113ad621eca2e8bd3 *electron-v37.6.0-win32-arm64.zip -bce61cf19e53390654bd2a57706462d40ba7da4c83750cfe2242279961bffbf7 *electron-v37.6.0-win32-ia32-pdb.zip -9c85d0eff9eb1bfbb3ef7479fbe709204f594cbdefa745df68e7491013f20c6f *electron-v37.6.0-win32-ia32-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-ia32-toolchain-profile.zip -62ba4c8ba5734fbd0371ec5d04481f498d5b202024a3083fe428d0df55f88ff4 *electron-v37.6.0-win32-ia32.zip -6fe8d1e8f40a46b84ecac8e53e0607e9b43e2afe24cac37a7912a3413b210847 *electron-v37.6.0-win32-x64-pdb.zip -f3fe1e92a4f6977d24848be13d15eafc1558f21230387c66c0b5f41051ba2049 *electron-v37.6.0-win32-x64-symbols.zip -74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.6.0-win32-x64-toolchain-profile.zip -4412a99d07f32a79de16e5a44739062f5fe27efd68cd9ad95122c560f33704b8 *electron-v37.6.0-win32-x64.zip -0bcd0d5f201de6d8903d2860ede68911c834be99d24fb6482aeee492d2b5265a *electron.d.ts -39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.6.0-darwin-arm64.zip -210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.6.0-darwin-x64.zip -f0792bdd28ac2231e2d10bdb89da0221e9b15149a4761448e6bfd50ba8e76895 *ffmpeg-v37.6.0-linux-arm64.zip -5bd4adf23596c09bbb671952b73427f6701a7e9aee647979925e9cc4ff973045 *ffmpeg-v37.6.0-linux-armv7l.zip -561a7685536c133d2072e2e2b5a64ca3897bb8c71624158a6fe8e07cae9116c9 *ffmpeg-v37.6.0-linux-x64.zip -39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.6.0-mas-arm64.zip -210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.6.0-mas-x64.zip -e6e0b42b1afd3bed31fdeda0d5e9e3ae71c7a5dd950ef7f137ec22b8eb53f372 *ffmpeg-v37.6.0-win32-arm64.zip -dc5509c12b30983c200a1a7059673ce093a9bdf38197e3ddcd9a2c33591e1811 *ffmpeg-v37.6.0-win32-ia32.zip -b60372e7431ad40292a77ffc75f011e06c528589f20e6596de9c9090356d1699 *ffmpeg-v37.6.0-win32-x64.zip -4b80ff3d9e48d7b00cdf8265a6a8d7b1a14ed403ca2c7266c1e755fbbbbf4671 *hunspell_dictionaries.zip -00c3f8aff441d6a4b12d076055760895760dbc4f42708358d71f0a2e6937a509 *libcxx-objects-v37.6.0-linux-arm64.zip -71cd9bc71ef32d25aa50ed2aec8859ae6213b4e10c2e7d9cd6174b4221bd023b *libcxx-objects-v37.6.0-linux-armv7l.zip -95bf4671c353ea80e6c6d579e2513200329ba449761ba02dfe3f93ee213cced2 *libcxx-objects-v37.6.0-linux-x64.zip -421b28d575bc966cb17cd3747d9c6a62050648792ec15e5bf8f1eb46f0931e19 *libcxx_headers.zip -006ccb83761a3bf5791d165bc9795e4de3308c65df646b4cbcaa61025fe7a6c5 *libcxxabi_headers.zip -9a50ca41df5b99d2c0557ee5b1db6657baca398f557109955f54433ed64aa7e1 *mksnapshot-v37.6.0-darwin-arm64.zip -6544c9c5007fc9dd5ee3480aa1b5b71a8a5797e492c4f6ef69c7f59b0ee3fc00 *mksnapshot-v37.6.0-darwin-x64.zip -ff7c115df6caf20383765ec4b236c5220e7a32d842ddaabd209cbe063b018b0f *mksnapshot-v37.6.0-linux-arm64-x64.zip -3a4b458aaed19854ff69151bdadd542a398ac670fed4be0e59402e24a461faf4 *mksnapshot-v37.6.0-linux-armv7l-x64.zip -c46f5fe87bc799a5661c0926d4572ad488805210be85a7e1cb4c6e2068697628 *mksnapshot-v37.6.0-linux-x64.zip -5d5e202f46f9654429b8e175bcba4ef4174dd2c3ab8a27a63ea9b0bcaec0c486 *mksnapshot-v37.6.0-mas-arm64.zip -31a5dd8f72d8910335e266eea2cd478e4d45fe16de2118368466401db8eca6f1 *mksnapshot-v37.6.0-mas-x64.zip -a4ea3aa43b1c0efbf27c3a4a2ea9269f12d855fd290d3834c0f7de34e18c4ab1 *mksnapshot-v37.6.0-win32-arm64-x64.zip -ab0f8bcd2af4fbe461c23fa06cbcd9f62187ba080a9f3c56f24c40f3f5ba1f59 *mksnapshot-v37.6.0-win32-ia32.zip -4dc1c6dbf2c2568f0221ec06ca4029919e406b7a466ec4b722f704b579b3e3fa *mksnapshot-v37.6.0-win32-x64.zip +766c6904e9825e3342a28ddd0210204d42e40377d2ab7ee17f5f954fbebab8ae *chromedriver-v37.7.0-darwin-arm64.zip +4b24cf1e4c00ef73411c4b715a7c1198d0186d5dcac7a64137759fd987836931 *chromedriver-v37.7.0-darwin-x64.zip +d3635fdbd2a09e23fa9be99e954bd910e8cd2a06265d011b0ed42c9f947a4572 *chromedriver-v37.7.0-linux-arm64.zip +aca613941e5412ea016307a9e40c4b78e8caacc0451fb83166b67ed9da617a91 *chromedriver-v37.7.0-linux-armv7l.zip +22570d8f0f89f22b6175bba8ead9421ffe7316829a08d4b987d7c3adb4c2f153 *chromedriver-v37.7.0-linux-x64.zip +2a4a7f0a43cd611db609b8217687083cf7d88f0e10ab24116a205169b05d75c4 *chromedriver-v37.7.0-mas-arm64.zip +1cd0ec52b319b43ca08f5db0ecb4848b35b2babf1187757aea2904e3bc217dd1 *chromedriver-v37.7.0-mas-x64.zip +d7ee42b443c9b83efaaf0a75b3e5b50cdb3cb04540a57a8398babcfffeaadc2f *chromedriver-v37.7.0-win32-arm64.zip +55199f114621ecbb2365fe0e22b9b188bc013c2c0d7ff66f43f814ebebe38739 *chromedriver-v37.7.0-win32-ia32.zip +18c5e32bfd075a9b497c7e514a83dc18257cbc5119530c0372d61451138bdc78 *chromedriver-v37.7.0-win32-x64.zip +a6d46dfbbd5a7e0c31272eabe1068e8570f18df1559209e8ec239d0eeb0ee38f *electron-api.json +d05484feef95627bc407f1ef6c86dc7076c568f763afd162c718c65e736645e2 *electron-v37.7.0-darwin-arm64-dsym-snapshot.zip +59cc95edb7599fb24b2cc65b9313583465faeeb95a8822cb74b170ffe43d9aee *electron-v37.7.0-darwin-arm64-dsym.zip +e09febc22a7635fc2ca7de0bbeefb5aaba9fe91e6e15da3cb90ec12a2a0cd822 *electron-v37.7.0-darwin-arm64-symbols.zip +c962552e6de47f5eebf1c2932f21d66d556c351d90d70ccbae68a9d22ac17ba9 *electron-v37.7.0-darwin-arm64.zip +c083a859932a2a070cfc0c110f03c7573c1f83f6aef624aacac34fe16c0fb0c9 *electron-v37.7.0-darwin-x64-dsym-snapshot.zip +43758f15ef737505edc544aca5ae4a06636930ca5d95a32030d1c742acc07141 *electron-v37.7.0-darwin-x64-dsym.zip +c65b761a0481ee97c75f62add721b3427d0bde61c5545ebfd3a059b11cb8055a *electron-v37.7.0-darwin-x64-symbols.zip +4aebc43ef4de09086bf4487d1bc491de27f6aa1a2e8dd32622e8bf1deaf9a1ae *electron-v37.7.0-darwin-x64.zip +690855692f644997420b08b79cfa4a2b852f705982f754afb32877e55642b58a *electron-v37.7.0-linux-arm64-debug.zip +6cceaeabd2e7517d8562da6fc9a17a73fc0be5b0bb05b3732ff5b5ec2a08745e *electron-v37.7.0-linux-arm64-symbols.zip +c72c2e963dcdea65d500b23fff3f22806a2518a86b52236dceb825a1d194cd7e *electron-v37.7.0-linux-arm64.zip +eb3303f9d335e5bd518f91ceee7832348ed2943fecf965ab4312ac40e91644f1 *electron-v37.7.0-linux-armv7l-debug.zip +839b0c8c4c637aeb968fdd85cdfedf7e83398aa756724b83ea482b65f8f80e83 *electron-v37.7.0-linux-armv7l-symbols.zip +eecc89703c24b019fad14e3e8341a6e2bcd995c473c9c5e56bf1a4664d8630f7 *electron-v37.7.0-linux-armv7l.zip +42cb0ba3e380f6a10ab6ec56e9e9a2f55c7f71621daf605e94a0eba21c3c9f6c *electron-v37.7.0-linux-x64-debug.zip +03c3761ea84447022f5acb171f34b0129b44c3b54d8882addb67cac3572e1368 *electron-v37.7.0-linux-x64-symbols.zip +4ae04d20d0ea25bf3451e3d0eaa757190b5813fa58d17bbe3be4332836da6b25 *electron-v37.7.0-linux-x64.zip +23bf51bc222cf1384594ac2dba294899e0170f5695125b156b0d5c213eb81af6 *electron-v37.7.0-mas-arm64-dsym-snapshot.zip +a0799acdf988e48c45778c5a797bcece64531c2c0070ab14b066b477df52e837 *electron-v37.7.0-mas-arm64-dsym.zip +dcd1be8cf58bc07813e34d93903e8bf1f268ff02d1214b01d02298e6d83f01b2 *electron-v37.7.0-mas-arm64-symbols.zip +da5d2aaac129d90f36e65d091b630528ac0aff81ca0b9895089878c02a59d8fb *electron-v37.7.0-mas-arm64.zip +a4f489fe0aec2ab13605189ba80ca49042d11249a00c79a6d5456689288e3479 *electron-v37.7.0-mas-x64-dsym-snapshot.zip +1051763877e03d0d80fe5af3f6fdd50bf158f25c7d058a4755ca83fc036f9f69 *electron-v37.7.0-mas-x64-dsym.zip +95fe16c43a57ae8be98d4bb4dfce4b2c3e2f6c6ed1415ca757a1ee15727e476f *electron-v37.7.0-mas-x64-symbols.zip +8e59bf3fa4704a01cdeb3b38ec3beb4de118743af6974b41689b567f80c4939e *electron-v37.7.0-mas-x64.zip +52b7825a8fc4529f622665d46f9cceeacf50a7d75659c38b99c84a35d5d08f41 *electron-v37.7.0-win32-arm64-pdb.zip +9c9c6ce62bd1974f2bd703106fac45127270fcfc629b233572692b3870fcd733 *electron-v37.7.0-win32-arm64-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.7.0-win32-arm64-toolchain-profile.zip +875ff30aa3148665fc028abb762cf265e5f0a79ed98d1cceec2441afa17b76ea *electron-v37.7.0-win32-arm64.zip +744ead04becabbceaef15a80f6e45539472f20ffb1651c9238a68daed3d4587d *electron-v37.7.0-win32-ia32-pdb.zip +dc8ab512983cecf68d7662fc05c20d46c73646996f2a8b1b53642a27b3b7ebb7 *electron-v37.7.0-win32-ia32-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.7.0-win32-ia32-toolchain-profile.zip +d79cf6cc733691bce26b4c8830bc927ce028fbf6174aa17041559f5f71d69452 *electron-v37.7.0-win32-ia32.zip +4313294bf3de78ef12b3948a32990d6b2c5ce270f3ba7b6d81c582c02d4127e1 *electron-v37.7.0-win32-x64-pdb.zip +8f3ea7630b0945d2d26aec9c2236560050ea46d61f225ffeed25c5381a131aef *electron-v37.7.0-win32-x64-symbols.zip +74e88ea46bb62f4d8698b1058963568b8ccd9debbdd5d755dfdf4303874446d5 *electron-v37.7.0-win32-x64-toolchain-profile.zip +875cea08076dfa433189aa7e82263cff7f0aa3795a69172baeec4a85fb57bc05 *electron-v37.7.0-win32-x64.zip +a136e010d8757d8a61f06ba9d8fbd3ad057ab04d7d386ff3b0e1ba56ec4a5b64 *electron.d.ts +39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.7.0-darwin-arm64.zip +210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.7.0-darwin-x64.zip +f0792bdd28ac2231e2d10bdb89da0221e9b15149a4761448e6bfd50ba8e76895 *ffmpeg-v37.7.0-linux-arm64.zip +5bd4adf23596c09bbb671952b73427f6701a7e9aee647979925e9cc4ff973045 *ffmpeg-v37.7.0-linux-armv7l.zip +561a7685536c133d2072e2e2b5a64ca3897bb8c71624158a6fe8e07cae9116c9 *ffmpeg-v37.7.0-linux-x64.zip +39d5a5663e491f4e5e4a60ded8d6361a8f4d905a220aa681adfabac1fa90d06f *ffmpeg-v37.7.0-mas-arm64.zip +210e095fc7c629b411caf90f00958aa004ac33f2f6dd1291780a670c46f028cf *ffmpeg-v37.7.0-mas-x64.zip +cb73c4eb1c68b7c1bc8d6962165a4f9f26a92560770d33b27945df2e778a5c37 *ffmpeg-v37.7.0-win32-arm64.zip +0663b6c70171b7abe2cf47a5d0f102da6f10fda69744ec6bc96bc32fde253cbd *ffmpeg-v37.7.0-win32-ia32.zip +9fa33053350c6c9d4420739f82083895dbb4a1d2904a1965ee94a83ab9507a3c *ffmpeg-v37.7.0-win32-x64.zip +bd6cbcc9cb6d9fc4b219b42104cfeaa69260cc7830234150056f0a929358681c *hunspell_dictionaries.zip +7032d6827a0bfe2f382742591241feb238c4fba5ee5db325987f55f56d1ac1e2 *libcxx-objects-v37.7.0-linux-arm64.zip +9a1beec72b821269bdab123025eb7ba54f31e2875b1cd97198952544bfb40ddd *libcxx-objects-v37.7.0-linux-armv7l.zip +f756deeb30afecfa2753ea7cd24e10bb922e3d64d40da5b64189a7d196815330 *libcxx-objects-v37.7.0-linux-x64.zip +1a664a8739a67b1b445ad11da1770d18ecb24a3038a70c2356ed653605175a19 *libcxx_headers.zip +c46a23338c31d465ddb4005e870b955da8886146e5ee92b2bab1c8bccf876a85 *libcxxabi_headers.zip +94e9f968e8de57aebfe0933db9a57dd8645690d8adf731fe2c4eb6b67a2b6534 *mksnapshot-v37.7.0-darwin-arm64.zip +868bd0900566cc32b3414ffe8dd5c229fb0e4953ccc8832c23eb3645c70e5a72 *mksnapshot-v37.7.0-darwin-x64.zip +69e896e2600368244f8f14f2b35857b2c571aabbffbb2277479a709852bba4a2 *mksnapshot-v37.7.0-linux-arm64-x64.zip +dc447c6a9b13ca8b7cf63ad9da9007b19398b6e234bc4b9538bb9e541dd4b57f *mksnapshot-v37.7.0-linux-armv7l-x64.zip +4cb3fe3e176173002b6085b8a87eb51bb6bdefd04ff4ff67a5aba26fce94655a *mksnapshot-v37.7.0-linux-x64.zip +5eef6889b798793eff86b9a5496c9b0ade49266ef885941817bf4520d6e48783 *mksnapshot-v37.7.0-mas-arm64.zip +22d7f4ce4eb6399fc5dc6e6468ed6c27ce6b9f2d434cab45d4646ec7e710589c *mksnapshot-v37.7.0-mas-x64.zip +8185f50df97b2f894560b739889a9d6c219cfa53ef64315fca23481e810edfc5 *mksnapshot-v37.7.0-win32-arm64-x64.zip +51679e701842079ac04fce0c52d3531c13fd4a42fa0cc481ae2a0f8310605ad9 *mksnapshot-v37.7.0-win32-ia32.zip +8415c984500f66c69b195fb31606e29c07e92544e2ed3a9093993f47cb5cb15d *mksnapshot-v37.7.0-win32-x64.zip diff --git a/build/checksums/nodejs.txt b/build/checksums/nodejs.txt index 982e92d5e5f..7c35de5be61 100644 --- a/build/checksums/nodejs.txt +++ b/build/checksums/nodejs.txt @@ -1,7 +1,7 @@ -c59006db713c770d6ec63ae16cb3edc11f49ee093b5c415d667bb4f436c6526d node-v22.19.0-darwin-arm64.tar.gz -3cfed4795cd97277559763c5f56e711852d2cc2420bda1cea30c8aa9ac77ce0c node-v22.19.0-darwin-x64.tar.gz -d32817b937219b8f131a28546035183d79e7fd17a86e38ccb8772901a7cd9009 node-v22.19.0-linux-arm64.tar.gz -969037e6da2a710904d121dcb998510bc0d5d4d61d70ce5eb578096cf36c60e8 node-v22.19.0-linux-armv7l.tar.gz -d36e56998220085782c0ca965f9d51b7726335aed2f5fc7321c6c0ad233aa96d node-v22.19.0-linux-x64.tar.gz -e22d8ae9e47ff62ae4a9a21ecd52feb229c32c1a1d3024ec60c100abb940df30 win-arm64/node.exe -995a3fb3cefad590cd3f4b321532a4b9582fb9c6575320ed2e3e894caac3e362 win-x64/node.exe +cc04a76a09f79290194c0646f48fec40354d88969bec467789a5d55dd097f949 node-v22.20.0-darwin-arm64.tar.gz +00df9c5df3e4ec6848c26b70fb47bf96492f342f4bed6b17f12d99b3a45eeecc node-v22.20.0-darwin-x64.tar.gz +4181609e03dcb9880e7e5bf956061ecc0503c77a480c6631d868cb1f65a2c7dd node-v22.20.0-linux-arm64.tar.gz +607380e96e1543c5ca6dc8a9f5575181b2855b8769fb31d646ef9cf27224f300 node-v22.20.0-linux-armv7l.tar.gz +eeaccb0378b79406f2208e8b37a62479c70595e20be6b659125eb77dd1ab2a29 node-v22.20.0-linux-x64.tar.gz +f7dd5b44ef1bcd751107f89cc2e27f17b012be5e21b5f11f923eff84bb52a3e0 win-arm64/node.exe +fdddbf4581e046b8102815d56208d6a248950bb554570b81519a8a5dacfee95d win-x64/node.exe diff --git a/cgmanifest.json b/cgmanifest.json index 199532ab318..b8d91764fff 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -516,12 +516,12 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "f8fe6858549f75a4b4e9633abf39dd2038dbf496", - "tag": "22.19.0" + "commitHash": "caa20e28dc1f21a97f7b2a7134973fd6435b65f0", + "tag": "22.20.0" } }, "isOnlyProductionDependency": true, - "version": "22.19.0" + "version": "22.20.0" }, { "component": { @@ -529,13 +529,13 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "9a2b4f84be2f4bcc468a63ef93520e60790b8f3c", - "tag": "37.6.0" + "commitHash": "dfc60f0f4246a542a45601f93572eca77bdff2f9", + "tag": "37.7.0" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "37.6.0" + "version": "37.7.0" }, { "component": { diff --git a/package-lock.json b/package-lock.json index 1b186c8bbeb..e322ffc91d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.6.0", + "electron": "37.7.0", "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", @@ -6332,9 +6332,9 @@ "dev": true }, "node_modules/electron": { - "version": "37.6.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-37.6.0.tgz", - "integrity": "sha512-8AANcn6irYQ7cTAJRY7r0CovWckcGCHUniQecyGhw/jJ25vWwitVhF97skF+EyDztiEI6YBoF0G6tx1s37bO3g==", + "version": "37.7.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-37.7.0.tgz", + "integrity": "sha512-LBzvfrS0aalynOsnC11AD7zeoU8eOois090mzLpQM3K8yZ2N04i2ZW9qmHOTFLrXlKvrwRc7EbyQf1u8XHMl6Q==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 195c6ef2163..aa84a3b0265 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "b4c6e5cbae656f37b06e74d2f613f44f05730ace", + "distro": "e8adcc1b4408a680b2e06374d8e65a2f7f90409d", "author": { "name": "Microsoft Corporation" }, @@ -159,7 +159,7 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.6.0", + "electron": "37.7.0", "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", diff --git a/remote/.npmrc b/remote/.npmrc index c1ef6fcca6e..40367a0138b 100644 --- a/remote/.npmrc +++ b/remote/.npmrc @@ -1,6 +1,6 @@ disturl="https://nodejs.org/dist" -target="22.19.0" -ms_build_id="360633" +target="22.20.0" +ms_build_id="365661" runtime="node" build_from_source="true" legacy-peer-deps="true" From f7d918f6030862374146af00a703ffdbea6aa81a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 19 Oct 2025 12:25:11 -0700 Subject: [PATCH 1297/4355] Update todos --- .../contrib/chat/browser/chatEditorInput.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 85917e73b9a..c62c7736dba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -204,22 +204,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } private resolveIcon(): ThemeIcon | URI | undefined { - // 1. Try to get session-specific icon from IChatSessionItem.iconPath (supports both ThemeIcon and URI) - if (this.sessionId) { - const sessionType = this.getSessionType(); - if (sessionType !== 'local') { - // For non-local sessions, try to find the session item - // Note: This is a best-effort synchronous lookup. Session items may not be loaded yet. - const providers = this.chatSessionsService.getAllChatSessionItemProviders(); - const provider = providers.find(p => p.chatSessionType === sessionType); - if (provider) { - // We can't await here, so we'll check for icon updates during resolve() - // For now, fall through to session type icon - } - } - } - - // 2. Fall back to session type icon from extension point + // TODO@osortega,@rebornix double check: Chat Session Item icon is reserved for chat session list and deprecated for chat session status. thus here we use session type icon. We may want to show status for the Editor Title. const sessionType = this.getSessionType(); if (sessionType !== 'local') { const typeIcon = this.chatSessionsService.getIconForSessionType(sessionType); @@ -228,7 +213,6 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } } - // 3. No custom icon found return undefined; } From f1d3d7e517aa21427dd7d5db07d8f1f1318bd271 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sun, 19 Oct 2025 12:48:01 -0700 Subject: [PATCH 1298/4355] rename delegate to coding agent (#272180) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 4776ade978d..ab151f1a4d0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -613,13 +613,13 @@ export class CreateRemoteAgentJobAction extends Action2 { super({ id: CreateRemoteAgentJobAction.ID, // TODO(joshspicer): Generalize title/tooltip - pull from contribution - title: localize2('actions.chat.createRemoteJob', "Delegate to Coding Agent"), + title: localize2('actions.chat.createRemoteJob', "Delegate to Agent"), icon: Codicon.sendToRemoteAgent, precondition, toggled: { condition: ChatContextKeys.remoteJobCreating, icon: Codicon.sync, - tooltip: localize('remoteJobCreating', "Delegating to Coding Agent"), + tooltip: localize('remoteJobCreating', "Delegating to Agent"), }, menu: [ { From 48c93224ca007619fa9698e8180ac68cdaa2a189 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 19 Oct 2025 12:56:19 -0700 Subject: [PATCH 1299/4355] :lipstick: load theme icon --- .../chat/browser/chatSessions.contribution.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index a944aab5a26..983494d16da 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -277,12 +277,17 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._contributions.set(contribution.type, contribution); // Store icon mapping if provided + let icon: ThemeIcon | undefined; + if (contribution.icon) { // Parse icon string - support both "$(iconId)" and "iconId" formats - const iconId = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')') - ? contribution.icon.slice(2, -1) - : contribution.icon; - this._sessionTypeIcons.set(contribution.type, ThemeIcon.fromId(iconId)); + icon = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')') + ? ThemeIcon.fromString(contribution.icon) + : ThemeIcon.fromId(contribution.icon); + } + + if (icon) { + this._sessionTypeIcons.set(contribution.type, icon); } // Store welcome title, message, and input placeholder if provided From a676d9651ceecb9a70df6ad25801de48fa0953f1 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 19 Oct 2025 12:56:48 -0700 Subject: [PATCH 1300/4355] support welcome title/description in empty editor (when users login) --- .../contrib/chat/browser/chatWidget.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fd0169b2051..6487ffae34d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1358,15 +1358,22 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this.isLockedToCodingAgent) { - // TODO(jospicer): Let extensions contribute this welcome message/docs - const message = this._codingAgentPrefix === '@copilot ' - ? new MarkdownString(localize('copilotCodingAgentMessage', "This chat session will be forwarded to the {0} [coding agent]({1}) where work is completed in the background. ", this._codingAgentPrefix, 'https://aka.ms/coding-agent-docs') + this.chatDisclaimer, { isTrusted: true }) - : new MarkdownString(localize('genericCodingAgentMessage', "This chat session will be forwarded to the {0} coding agent where work is completed in the background. ", this._codingAgentPrefix) + this.chatDisclaimer); + // Check for provider-specific customizations from chat sessions service + const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; + const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; + const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; + + // Fallback to default messages if provider doesn't specify + const message = providerMessage + ? new MarkdownString(providerMessage) + : (this._codingAgentPrefix === '@copilot ' + ? new MarkdownString(localize('copilotCodingAgentMessage', "This chat session will be forwarded to the {0} [coding agent]({1}) where work is completed in the background. ", this._codingAgentPrefix, 'https://aka.ms/coding-agent-docs') + this.chatDisclaimer, { isTrusted: true }) + : new MarkdownString(localize('genericCodingAgentMessage', "This chat session will be forwarded to the {0} coding agent where work is completed in the background. ", this._codingAgentPrefix) + this.chatDisclaimer)); return { - title: localize('codingAgentTitle', "Delegate to {0}", this._codingAgentPrefix), + title: providerTitle ?? localize('codingAgentTitle', "Delegate to {0}", this._codingAgentPrefix), message, - icon: Codicon.sendToRemoteAgent, + icon: providerIcon ?? Codicon.sendToRemoteAgent, additionalMessage, }; } From 5b43928adc90efb6483bfca6115fb5f58d379a05 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 19 Oct 2025 14:50:23 -0700 Subject: [PATCH 1301/4355] Adopt renaming executePrompt tool to runSubagent (#272187) --- .github/prompts/plan-deep.prompt.md | 2 +- .vscode/settings.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/prompts/plan-deep.prompt.md b/.github/prompts/plan-deep.prompt.md index 3c3a8402d50..412bae87cbc 100644 --- a/.github/prompts/plan-deep.prompt.md +++ b/.github/prompts/plan-deep.prompt.md @@ -2,7 +2,7 @@ mode: Plan description: Clarify before planning in more detail --- -Before doing your research workflow, gather preliminary context using #executePrompt (instructed to use max 5 tool calls) to get a high-level overview. +Before doing your research workflow, gather preliminary context using #runSubagent (instructed to use max 5 tool calls) to get a high-level overview. Then ask 3 clarifying questions and PAUSE for the user to answer them. diff --git a/.vscode/settings.json b/.vscode/settings.json index 89cb313781d..f4e168eca5c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -216,7 +216,6 @@ "editor.aiStats.enabled": true, // Team selfhosting on ai stats "chat.emptyState.history.enabled": true, - "github.copilot.chat.executePrompt.enabled": true, "chat.agentSessionsViewLocation": "view", "chat.promptFilesRecommendations": { "plan-fast": true, From a95d62323ce316ec9a027297f741bf599075e76f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 19 Oct 2025 17:33:44 -0700 Subject: [PATCH 1302/4355] Include chat mode name (#272193) --- src/vs/workbench/api/common/extHostTypeConverters.ts | 1 + src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 1 + src/vs/workbench/contrib/chat/common/chatModel.ts | 1 + src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 3edcbf7cb7d..b6668e645d3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3327,6 +3327,7 @@ export namespace ChatRequestModeInstructions { export function to(mode: IChatRequestModeInstructions | undefined): vscode.ChatRequestModeInstructions | undefined { if (mode) { return { + name: mode.name, content: mode.content, toolReferences: ChatLanguageModelToolReferences.to(mode.toolReferences), metadata: mode.metadata diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 5f1d370ba8d..247ce7a1d37 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -334,6 +334,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge kind: this.currentModeKind, isBuiltin: mode.isBuiltin, modeInstructions: modeInstructions ? { + name: mode.name, content: modeInstructions.content, toolReferences: this.toolService.toToolReferences(modeInstructions.toolReferences), metadata: modeInstructions.metadata, diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index a0c4c1e12bc..8ac5999ece6 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -221,6 +221,7 @@ export interface IChatRequestModeInfo { } export interface IChatRequestModeInstructions { + readonly name: string; readonly content: string; readonly toolReferences: readonly ChatRequestToolReferenceEntry[]; readonly metadata?: Record; diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 778d6d7a407..c0b38dffc4e 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -652,6 +652,7 @@ declare module 'vscode' { } export interface ChatRequestModeInstructions { + readonly name: string; readonly content: string; readonly toolReferences?: readonly ChatLanguageModelToolReference[]; readonly metadata?: Record; From 40f44bddba2e66d23d7957e49d27c3e8094de041 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Sun, 19 Oct 2025 17:47:34 -0700 Subject: [PATCH 1303/4355] Enable settings UI for terminal suggestion providers --- .../browser/terminal.suggest.contribution.ts | 34 +++++------ .../common/terminalSuggestConfiguration.ts | 60 +++++++++---------- .../terminalSuggestConfiguration.test.ts | 11 +--- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 3a4ff1bbe08..dd185e718b0 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -21,7 +21,7 @@ import { registerActiveInstanceAction, registerTerminalAction } from '../../../t import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalSuggestCommandId } from '../common/terminal.suggest.js'; -import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration, type ITerminalSuggestProviderInfo } from '../common/terminalSuggestConfiguration.js'; +import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration } from '../common/terminalSuggestConfiguration.js'; import { ITerminalCompletionService, TerminalCompletionService } from './terminalCompletionService.js'; import { ITerminalContributionService } from '../../../terminal/common/terminalExtensionPoints.js'; import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; @@ -39,6 +39,8 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { getTerminalLspSupportedLanguageObj } from './lspTerminalUtil.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IStringDictionary } from '../../../../../base/common/collections.js'; +import { IConfigurationPropertySchema } from '../../../../../platform/configuration/common/configurationRegistry.js'; registerSingleton(ITerminalCompletionService, TerminalCompletionService, InstantiationType.Delayed); @@ -470,7 +472,8 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { constructor( @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, - @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService + @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this._register(this._terminalCompletionService.onDidChangeProviders(() => { @@ -484,29 +487,22 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { } private _updateConfiguration(): void { - const providerInfos: ITerminalSuggestProviderInfo[] = []; - // Add statically declared providers from package.json contributions - for (const contributedProvider of this._terminalContributionService.terminalCompletionProviders) { - providerInfos.push({ - id: contributedProvider.id, - description: contributedProvider.description - }); - } + const providers = new Set(); + this._terminalContributionService.terminalCompletionProviders.forEach(o => providers.add(o.id)); // Add dynamically registered providers (that aren't already declared statically) - const staticProviderIds = new Set(providerInfos.map(p => p.id)); - const dynamicProviders = Array.from(this._terminalCompletionService.providers); - for (const provider of dynamicProviders) { - if (provider.id && !staticProviderIds.has(provider.id)) { - providerInfos.push({ - id: provider.id, - description: undefined - }); + for (const { id } of this._terminalCompletionService.providers) { + if (id) { + providers.add(id); } } - registerTerminalSuggestProvidersConfiguration(providerInfos); + // Add providers present in the user's config. + const configuredProviders = this._configurationService.getValue>(TerminalSuggestSettingId.Providers)?.properties ?? {}; + Object.keys(configuredProviders).forEach(o => providers.add(o)); + + registerTerminalSuggestProvidersConfiguration(Array.from(providers)); } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index d23f9691546..e4d3eba0b73 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -188,42 +188,30 @@ export const terminalSuggestConfiguration: IStringDictionary(ConfigurationExtensions.Configuration); - +export function registerTerminalSuggestProvidersConfiguration(providerIds?: string[]) { const oldProvidersConfiguration = terminalSuggestProvidersConfiguration; + const enableByDefault = product.quality !== 'stable'; - const providersProperties: IStringDictionary = {}; + providerIds ??= []; + if (!providerIds.includes('lsp')) { + providerIds.push('lsp'); + } + providerIds.sort(); - const lspProviderId = 'lsp'; - const defaultValue: IStringDictionary = { - [lspProviderId]: product.quality !== 'stable', - }; - providersProperties[lspProviderId] ??= { - type: 'boolean', - description: localize('suggest.provider.lsp.description', "Provides completions for language server protocol-specific arguments."), - default: product.quality !== 'stable', - }; + const providersProperties: IStringDictionary = {}; + for (const id of providerIds) { + providersProperties[id] = { + type: 'boolean', + default: enableByDefault, + description: localize('suggest.provider.title', 'Show suggestions from {0}', id) + }; + } - if (availableProviders) { - for (const provider of availableProviders) { - if (provider.id in defaultValue) { - continue; - } - providersProperties[provider.id] = { - type: 'boolean', - description: provider.description || localize('suggest.provider.description', "Whether to enable this provider."), - default: true - }; - defaultValue[provider.id] = true; - } + const defaultValue: IStringDictionary = {}; + for (const key in providersProperties) { + defaultValue[key] = providersProperties[key].default as boolean; } terminalSuggestProvidersConfiguration = { @@ -234,19 +222,27 @@ export function registerTerminalSuggestProvidersConfiguration(availableProviders properties: { [TerminalSuggestSettingId.Providers]: { restricted: true, - markdownDescription: localize('suggest.providers', "Providers are enabled by default. Omit them by setting the id of the provider to `false`."), + markdownDescription: enableByDefault ? + localize( + 'suggest.providersEnabledByDefault', + 'Controls which suggestions automatically show up while typing. Suggestion providers are enabled by default.') : + localize( + 'suggest.providersDisabledByDefault', + 'Controls which suggestions automatically show up while typing. Suggestion providers are disabled by default.'), type: 'object', properties: providersProperties, default: defaultValue, tags: ['preview'], + additionalProperties: false } } }; + const registry = Registry.as(ConfigurationExtensions.Configuration); registry.updateConfigurations({ add: [terminalSuggestProvidersConfiguration], remove: oldProvidersConfiguration ? [oldProvidersConfiguration] : [] }); } -registerTerminalSuggestProvidersConfiguration([]); +registerTerminalSuggestProvidersConfiguration(); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts index a413045b90d..cf4c2d9841c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts @@ -12,19 +12,14 @@ suite('Terminal Suggest Dynamic Configuration', () => { test('should update configuration when providers change', () => { // Test initial state - registerTerminalSuggestProvidersConfiguration([]); + registerTerminalSuggestProvidersConfiguration(); // Test with some providers - const providers = [ - { id: 'terminal-suggest', description: 'Provides intelligent completions for terminal commands' }, - { id: 'builtinPwsh', description: 'PowerShell completion provider' }, - { id: 'lsp' }, - { id: 'custom-provider' } - ]; + const providers = ['terminal-suggest', 'builtinPwsh', 'lsp', 'custom-provider']; registerTerminalSuggestProvidersConfiguration(providers); // Test with empty providers - registerTerminalSuggestProvidersConfiguration([]); + registerTerminalSuggestProvidersConfiguration(); // The fact that this doesn't throw means the basic logic works assert.ok(true); From 7e2dbf92b2b461a587a7a744205899e01da2cd76 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Sun, 19 Oct 2025 17:56:25 -0700 Subject: [PATCH 1304/4355] PR feedback --- .../suggest/browser/terminal.suggest.contribution.ts | 3 +-- .../suggest/common/terminalSuggestConfiguration.ts | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index dd185e718b0..873d96ab9c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -40,7 +40,6 @@ import { ILanguageFeaturesService } from '../../../../../editor/common/services/ import { getTerminalLspSupportedLanguageObj } from './lspTerminalUtil.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IStringDictionary } from '../../../../../base/common/collections.js'; -import { IConfigurationPropertySchema } from '../../../../../platform/configuration/common/configurationRegistry.js'; registerSingleton(ITerminalCompletionService, TerminalCompletionService, InstantiationType.Delayed); @@ -499,7 +498,7 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { } // Add providers present in the user's config. - const configuredProviders = this._configurationService.getValue>(TerminalSuggestSettingId.Providers)?.properties ?? {}; + const configuredProviders = this._configurationService.getValue>(TerminalSuggestSettingId.Providers)?.properties ?? {}; Object.keys(configuredProviders).forEach(o => providers.add(o)); registerTerminalSuggestProvidersConfiguration(Array.from(providers)); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index e4d3eba0b73..42b451f822c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -205,7 +205,7 @@ export function registerTerminalSuggestProvidersConfiguration(providerIds?: stri providersProperties[id] = { type: 'boolean', default: enableByDefault, - description: localize('suggest.provider.title', 'Show suggestions from {0}', id) + description: localize('suggest.provider.title', "Show suggestions from {0}", id) }; } @@ -225,10 +225,10 @@ export function registerTerminalSuggestProvidersConfiguration(providerIds?: stri markdownDescription: enableByDefault ? localize( 'suggest.providersEnabledByDefault', - 'Controls which suggestions automatically show up while typing. Suggestion providers are enabled by default.') : + "Controls which suggestions automatically show up while typing. Suggestion providers are enabled by default.") : localize( 'suggest.providersDisabledByDefault', - 'Controls which suggestions automatically show up while typing. Suggestion providers are disabled by default.'), + "Controls which suggestions automatically show up while typing. Suggestion providers are disabled by default."), type: 'object', properties: providersProperties, default: defaultValue, From 7a86bdb7c01e42820804ffaf5d76ed90c149ca68 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 19 Oct 2025 18:29:47 -0700 Subject: [PATCH 1305/4355] Support welcome tips from agents --- .../chat/browser/chatSessions.contribution.ts | 19 ++++++++++++++++++- .../contrib/chat/browser/chatWidget.ts | 12 +++++++++++- .../chat/common/chatSessionsService.ts | 9 +++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 983494d16da..eba2c4b614d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -70,6 +70,10 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = new Map(); private readonly _sessionTypeWelcomeTitles: Map = new Map(); private readonly _sessionTypeWelcomeMessages: Map = new Map(); + private readonly _sessionTypeWelcomeTips: Map = new Map(); private readonly _sessionTypeInputPlaceholders: Map = new Map(); constructor( @@ -207,6 +212,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ icon: contribution.icon, welcomeTitle: contribution.welcomeTitle, welcomeMessage: contribution.welcomeMessage, + welcomeTips: contribution.welcomeTips, inputPlaceholder: contribution.inputPlaceholder, capabilities: contribution.capabilities, extensionDescription: ext.description, @@ -290,13 +296,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessionTypeIcons.set(contribution.type, icon); } - // Store welcome title, message, and input placeholder if provided + // Store welcome title, message, tips, and input placeholder if provided if (contribution.welcomeTitle) { this._sessionTypeWelcomeTitles.set(contribution.type, contribution.welcomeTitle); } if (contribution.welcomeMessage) { this._sessionTypeWelcomeMessages.set(contribution.type, contribution.welcomeMessage); } + if (contribution.welcomeTips) { + this._sessionTypeWelcomeTips.set(contribution.type, contribution.welcomeTips); + } if (contribution.inputPlaceholder) { this._sessionTypeInputPlaceholders.set(contribution.type, contribution.inputPlaceholder); } @@ -309,6 +318,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessionTypeIcons.delete(contribution.type); this._sessionTypeWelcomeTitles.delete(contribution.type); this._sessionTypeWelcomeMessages.delete(contribution.type); + this._sessionTypeWelcomeTips.delete(contribution.type); this._sessionTypeInputPlaceholders.delete(contribution.type); const store = this._disposableStores.get(contribution.type); if (store) { @@ -743,6 +753,13 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public getInputPlaceholderForSessionType(chatSessionType: string): string | undefined { return this._sessionTypeInputPlaceholders.get(chatSessionType); } + + /** + * Get the welcome tips for a specific session type + */ + public getWelcomeTipsForSessionType(chatSessionType: string): string | undefined { + return this._sessionTypeWelcomeTips.get(chatSessionType); + } } registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 6487ffae34d..98146ca0654 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1060,9 +1060,13 @@ export class ChatWidget extends Disposable implements IChatWidget { } else if (expEmptyState) { welcomeContent = this.getWelcomeViewContent(additionalMessage, expEmptyState); } else { - const tips = this.input.currentModeKind === ChatModeKind.Ask + const defaultTips = this.input.currentModeKind === ChatModeKind.Ask ? new MarkdownString(localize('chatWidget.tips', "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", '$(attach)', '#', '$(mention)', '/'), { supportThemeIcons: true }) : new MarkdownString(localize('chatWidget.tips.withoutParticipants', "{0} or type {1} to attach context", '$(attach)', '#'), { supportThemeIcons: true }); + const contributedTips = this._lockedAgentId ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgentId) : undefined; + const tips = contributedTips + ? new MarkdownString(contributedTips, { supportThemeIcons: true }) + : (!this._lockedAgentId ? defaultTips : undefined); welcomeContent = this.getWelcomeViewContent(additionalMessage); welcomeContent.tips = tips; } @@ -1425,6 +1429,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; + const providerTips = this._lockedAgentId ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgentId) : undefined; const suggestedPrompts = this._lockedAgentId ? undefined : this.getNewSuggestedPrompts(); const welcomeContent: IChatViewWelcomeContent = { @@ -1436,6 +1441,11 @@ export class ChatWidget extends Disposable implements IChatWidget { isNew: true, suggestedPrompts }; + + // Add contributed tips if available + if (providerTips) { + welcomeContent.tips = new MarkdownString(providerTips, { supportThemeIcons: true }); + } return welcomeContent; } diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index a272cef6f1b..95cd253a667 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -51,6 +51,7 @@ export interface IChatSessionsExtensionPoint { readonly icon?: string; readonly welcomeTitle?: string; readonly welcomeMessage?: string; + readonly welcomeTips?: string; readonly inputPlaceholder?: string; readonly capabilities?: { supportsFileAttachments?: boolean; @@ -131,7 +132,15 @@ export interface IChatSessionsService { getIconForSessionType(chatSessionType: string): ThemeIcon | undefined; getWelcomeTitleForSessionType(chatSessionType: string): string | undefined; getWelcomeMessageForSessionType(chatSessionType: string): string | undefined; + /** + * Get the input placeholder for a specific session type + */ getInputPlaceholderForSessionType(chatSessionType: string): string | undefined; + + /** + * Get the welcome tips for a specific session type + */ + getWelcomeTipsForSessionType(chatSessionType: string): string | undefined; provideNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: any; From 56951bdc52d72be2f64a2288f1c205ec8326e880 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 19 Oct 2025 18:42:20 -0700 Subject: [PATCH 1306/4355] Add chat mode 'source' and sort built-in custom modes into the builtin section (#272197) --- src/vs/base/common/collections.ts | 2 +- .../modelPicker/modePickerActionItem.ts | 52 +++++++++------- .../contrib/chat/common/chatModes.ts | 60 +++++++++++++++++-- .../promptSyntax/service/promptsService.ts | 36 +++++++++-- .../service/promptsServiceImpl.ts | 53 ++++++++-------- .../promptSytntax/promptValidator.test.ts | 36 +++++------ .../chat/test/common/chatModeService.test.ts | 15 ++++- .../service/promptsService.test.ts | 37 ++++++------ 8 files changed, 196 insertions(+), 95 deletions(-) diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 81a8bfae1cb..24c0b9767d7 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -19,7 +19,7 @@ export type INumberDictionary = Record; * Groups the collection into a dictionary based on the provided * group function. */ -export function groupBy(data: V[], groupFn: (element: V) => K): Record { +export function groupBy(data: readonly V[], groupFn: (element: V) => K): Record { const result: Record = Object.create(null); for (const element of data) { const key = groupFn(element); diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts index c6d1973f689..65fae65e6bf 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts @@ -6,6 +6,8 @@ import * as dom from '../../../../../base/browser/dom.js'; import { renderLabelWithIcons } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../../base/common/actions.js'; +import { coalesce } from '../../../../../base/common/arrays.js'; +import { groupBy } from '../../../../../base/common/collections.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { autorun, IObservable } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; @@ -17,9 +19,11 @@ import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider, IActi import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { IProductService } from '../../../../../platform/product/common/productService.js'; import { IChatAgentService } from '../../common/chatAgents.js'; -import { IChatMode, IChatModeService } from '../../common/chatModes.js'; +import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; import { ChatAgentLocation } from '../../common/constants.js'; +import { PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { getOpenChatActionIdForMode } from '../actions/chatActions.js'; import { IToggleChatModeArgs, ToggleAgentModeActionId } from '../actions/chatExecuteActions.js'; @@ -37,8 +41,11 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatModeService chatModeService: IChatModeService, @IMenuService private readonly menuService: IMenuService, - @ICommandService commandService: ICommandService + @ICommandService commandService: ICommandService, + @IProductService productService: IProductService ) { + const builtInCategory = { label: localize('built-in', "Built-In"), order: 0 }; + const customCategory = { label: localize('custom', "Custom"), order: 1 }; const makeAction = (mode: IChatMode, currentMode: IChatMode): IActionWidgetDropdownAction => ({ ...action, id: getOpenChatActionIdForMode(mode), @@ -52,35 +59,40 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { this.renderLabel(this.element!); return result; }, - category: { label: localize('built-in', "Built-In"), order: 0 } + category: builtInCategory }); const makeActionFromCustomMode = (mode: IChatMode, currentMode: IChatMode): IActionWidgetDropdownAction => ({ - ...action, - id: getOpenChatActionIdForMode(mode), - label: mode.label, - class: undefined, - enabled: true, - checked: currentMode.id === mode.id, + ...makeAction(mode, currentMode), tooltip: mode.description.get() ?? chatAgentService.getDefaultAgent(ChatAgentLocation.Chat, mode.kind)?.description ?? action.tooltip, - run: async () => { - const result = await commandService.executeCommand(ToggleAgentModeActionId, { modeId: mode.id } satisfies IToggleChatModeArgs); - this.renderLabel(this.element!); - return result; - }, - category: { label: localize('custom', "Custom"), order: 1 } + category: customCategory }); const actionProvider: IActionWidgetDropdownActionProvider = { getActions: () => { const modes = chatModeService.getModes(); const currentMode = delegate.currentMode.get(); - const agentStateActions: IActionWidgetDropdownAction[] = modes.builtin.map(mode => makeAction(mode, currentMode)); - if (modes.custom) { - agentStateActions.push(...modes.custom.map(mode => makeActionFromCustomMode(mode, currentMode))); - } + const agentMode = modes.builtin.find(mode => mode.id === ChatMode.Agent.id); + 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 ? + 'builtin' : 'custom'); + + const customBuiltinModeActions = customModes.builtin.map(mode => { + const action = makeActionFromCustomMode(mode, currentMode); + action.category = builtInCategory; + return action; + }); + + const orderedModes = coalesce([ + agentMode && makeAction(agentMode, currentMode), + ...customBuiltinModeActions, + ...otherBuiltinModes.map(mode => mode && makeAction(mode, currentMode)), + ...customModes.custom.map(mode => makeActionFromCustomMode(mode, currentMode)) + ]); - return agentStateActions; + return orderedModes; } }; diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 0df94999787..0b8f2db623c 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -11,6 +11,7 @@ import { URI } from '../../../../base/common/uri.js'; import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; import { localize } from '../../../../nls.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -18,7 +19,7 @@ import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; import { ChatModeKind } from './constants.js'; import { IHandOff } from './promptSyntax/service/newPromptsParser.js'; -import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js'; +import { IChatModeSource, ICustomChatMode, IPromptsService, PromptsStorage } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); export interface IChatModeService { @@ -100,12 +101,13 @@ export class ChatModeService extends Disposable implements IChatModeService { tools: cachedMode.customTools, model: cachedMode.model, modeInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, - handOffs: cachedMode.handOffs + handOffs: cachedMode.handOffs, + source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } }; const instance = new CustomChatMode(customChatMode); this._customModeInstances.set(uri.toString(), instance); } catch (error) { - this.logService.error(error, 'Failed to create custom chat mode instance from cached data'); + this.logService.error(error, 'Failed to revive cached custom chat mode'); } } } @@ -170,7 +172,7 @@ export class ChatModeService extends Disposable implements IChatModeService { } findModeById(id: string | ChatModeKind): IChatMode | undefined { - return this.getBuiltinModes().find(mode => mode.id === id) ?? this.getCustomModes().find(mode => mode.id === id); + return this.getBuiltinModes().find(mode => mode.id === id) ?? this._customModeInstances.get(id); } findModeByName(name: string): IChatMode | undefined { @@ -206,6 +208,7 @@ export interface IChatModeData { readonly body?: string; /* deprecated */ readonly handOffs?: readonly IHandOff[]; readonly uri?: URI; + readonly source?: IChatModeSourceData; } export interface IChatMode { @@ -220,6 +223,7 @@ export interface IChatMode { readonly model?: IObservable; readonly modeInstructions?: IObservable; readonly uri?: IObservable; + readonly source?: IChatModeSource; } export interface IVariableReference { @@ -247,7 +251,8 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { (mode.modeInstructions === undefined || (typeof mode.modeInstructions === 'object' && mode.modeInstructions !== null)) && (mode.model === undefined || typeof mode.model === 'string') && (mode.handOffs === undefined || Array.isArray(mode.handOffs)) && - (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)); + (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)) && + (mode.source === undefined || isChatModeSourceData(mode.source)); } export class CustomChatMode implements IChatMode { @@ -257,6 +262,7 @@ export class CustomChatMode implements IChatMode { private readonly _uriObservable: ISettableObservable; private readonly _modelObservable: ISettableObservable; private readonly _handoffsObservable: ISettableObservable; + private _source: IChatModeSource; public readonly id: string; public readonly name: string; @@ -293,6 +299,10 @@ export class CustomChatMode implements IChatMode { return this._handoffsObservable; } + get source(): IChatModeSource { + return this._source; + } + public readonly kind = ChatModeKind.Agent; constructor( @@ -306,6 +316,7 @@ export class CustomChatMode implements IChatMode { this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs); this._modeInstructions = observableValue('_modeInstructions', customChatMode.modeInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); + this._source = customChatMode.source; } /** @@ -320,6 +331,7 @@ export class CustomChatMode implements IChatMode { this._handoffsObservable.set(newData.handOffs, tx); this._modeInstructions.set(newData.modeInstructions, tx); this._uriObservable.set(newData.uri, tx); + this._source = newData.source; }); } @@ -333,11 +345,47 @@ export class CustomChatMode implements IChatMode { model: this.model.get(), modeInstructions: this.modeInstructions.get(), uri: this.uri.get(), - handOffs: this.handOffs.get() + handOffs: this.handOffs.get(), + source: serializeChatModeSource(this._source) }; } } +type IChatModeSourceData = + | { readonly storage: PromptsStorage.extension; readonly extensionId: string } + | { readonly storage: PromptsStorage.local | PromptsStorage.user }; + +function isChatModeSourceData(value: unknown): value is IChatModeSourceData { + if (typeof value !== 'object' || value === null) { + return false; + } + const data = value as { storage?: unknown; extensionId?: unknown }; + if (data.storage === PromptsStorage.extension) { + return typeof data.extensionId === 'string'; + } + return data.storage === PromptsStorage.local || data.storage === PromptsStorage.user; +} + +function serializeChatModeSource(source: IChatModeSource | undefined): IChatModeSourceData | undefined { + if (!source) { + return undefined; + } + if (source.storage === PromptsStorage.extension) { + return { storage: PromptsStorage.extension, extensionId: source.extensionId.value }; + } + return { storage: source.storage }; +} + +function reviveChatModeSource(data: IChatModeSourceData | undefined): IChatModeSource | undefined { + if (!data) { + return undefined; + } + if (data.storage === PromptsStorage.extension) { + return { storage: PromptsStorage.extension, extensionId: new ExtensionIdentifier(data.extensionId) }; + } + return { storage: data.storage }; +} + export class BuiltinChatMode implements IChatMode { public readonly description: IObservable; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 43d12c89214..6fb1db99014 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChatModeKind } from '../../constants.js'; -import { URI } from '../../../../../../base/common/uri.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Event } from '../../../../../../base/common/event.js'; -import { ITextModel } from '../../../../../../editor/common/model.js'; import { IDisposable } from '../../../../../../base/common/lifecycle.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { PromptsType } from '../promptTypes.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { ExtensionIdentifier, IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatModeInstructions } from '../../chatModes.js'; +import { ChatModeKind } from '../../constants.js'; +import { PromptsType } from '../promptTypes.js'; import { IHandOff, ParsedPromptFile } from './newPromptsParser.js'; -import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; /** * Provides prompt services. @@ -75,6 +75,25 @@ export interface IUserPromptPath extends IPromptPathBase { readonly storage: PromptsStorage.user; } +export type IChatModeSource = { + readonly storage: PromptsStorage.extension; + readonly extensionId: ExtensionIdentifier; +} | { + readonly storage: PromptsStorage.local | PromptsStorage.user; +}; + +export function promptPathToChatModeSource(promptPath: IPromptPath): IChatModeSource { + if (promptPath.storage === PromptsStorage.extension) { + return { + storage: PromptsStorage.extension, + extensionId: promptPath.extension.identifier + }; + } else { + return { + storage: promptPath.storage + }; + } +} export interface ICustomChatMode { /** @@ -111,6 +130,11 @@ export interface ICustomChatMode { * Hand-offs defined in the custom chat mode file. */ readonly handOffs?: readonly IHandOff[]; + + /** + * Where the mode was loaded from. + */ + readonly source: IChatModeSource; } /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 3f9b9337d46..4b7c087ec7a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -3,35 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../../nls.js'; -import { getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; -import { type URI } from '../../../../../../base/common/uri.js'; -import { basename } from '../../../../../../base/common/path.js'; -import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; -import { Disposable, IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { Delayer } from '../../../../../../base/common/async.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; +import { Disposable, IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../../../base/common/map.js'; +import { basename } from '../../../../../../base/common/path.js'; +import { dirname, isEqual } from '../../../../../../base/common/resources.js'; +import { type URI } from '../../../../../../base/common/uri.js'; +import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js'; +import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { type ITextModel } from '../../../../../../editor/common/model.js'; -import { ILogService } from '../../../../../../platform/log/common/log.js'; -import { ILabelService } from '../../../../../../platform/label/common/label.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { localize } from '../../../../../../nls.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { ILogService } from '../../../../../../platform/log/common/log.js'; +import { IFilesConfigurationService } from '../../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; -import { IChatPromptSlashCommand, ICustomChatMode, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; -import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; -import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; +import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; import { PromptsConfig } from '../config/config.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; +import { getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; -import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { ResourceMap } from '../../../../../../base/common/map.js'; -import { CancellationError } from '../../../../../../base/common/errors.js'; -import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetRange.js'; -import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; -import { dirname, isEqual } from '../../../../../../base/common/resources.js'; -import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; -import { Delayer } from '../../../../../../base/common/async.js'; -import { IFilesConfigurationService } from '../../../../../services/filesConfiguration/common/filesConfigurationService.js'; +import { IChatModeSource, IChatPromptSlashCommand, ICustomChatMode, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, promptPathToChatModeSource, PromptsStorage } from './promptsService.js'; /** * Provides prompt services. @@ -246,7 +246,8 @@ export class PromptsService extends Disposable implements IPromptsService { const modeFiles = await this.listPromptFiles(PromptsType.mode, token); const customChatModes = await Promise.all( - modeFiles.map(async ({ uri, name: modeName }): Promise => { + modeFiles.map(async (promptPath): Promise => { + const { uri, name: modeName } = promptPath; const ast = await this.parseNew(uri, token); let metadata: any | undefined; @@ -279,11 +280,13 @@ export class PromptsService extends Disposable implements IPromptsService { } satisfies IChatModeInstructions; const name = modeName ?? getCleanPromptName(uri); + + const source: IChatModeSource = promptPathToChatModeSource(promptPath); if (!ast.header) { - return { uri, name, modeInstructions }; + return { uri, name, modeInstructions, source }; } const { description, model, tools, handOffs } = ast.header; - return { uri, name, description, model, tools, handOffs, modeInstructions }; + return { uri, name, description, model, tools, handOffs, modeInstructions, source }; }) ); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index 912962573b5..df6e3cf777d 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -5,30 +5,31 @@ import assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { ResourceSet } from '../../../../../../base/common/map.js'; import { URI } from '../../../../../../base/common/uri.js'; -import { NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js'; -import { PromptValidator } from '../../../common/promptSyntax/languageProviders/promptValidator.js'; -import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; -import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js'; -import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../common/languageModels.js'; +import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; -import { ChatMode, CustomChatMode, IChatModeService } from '../../../common/chatModes.js'; -import { MockChatModeService } from '../../common/mockChatModeService.js'; -import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; -import { IMarkerData, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; -import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; -import { ResourceSet } from '../../../../../../base/common/map.js'; -import { ChatConfiguration } from '../../../common/constants.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { IMarkerData, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { LanguageModelToolsService } from '../../../browser/languageModelToolsService.js'; +import { ChatMode, CustomChatMode, IChatModeService } from '../../../common/chatModes.js'; import { IChatService } from '../../../common/chatService.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../common/languageModels.js'; +import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; +import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; +import { PromptValidator } from '../../../common/promptSyntax/languageProviders/promptValidator.js'; +import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptsStorage } from '../../../common/promptSyntax/service/promptsService.js'; +import { MockChatModeService } from '../../common/mockChatModeService.js'; import { MockChatService } from '../../common/mockChatService.js'; -import { LanguageModelToolsService } from '../../../browser/languageModelToolsService.js'; -import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; -import { ILabelService } from '../../../../../../platform/label/common/label.js'; suite('PromptValidator', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -80,6 +81,7 @@ suite('PromptValidator', () => { uri: URI.parse('myFs://test/test/chatmode.md'), name: 'BeastMode', modeInstructions: { content: 'Beast mode instructions', toolReferences: [] }, + source: { storage: PromptsStorage.local } }); instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); diff --git a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts index a22e793ba3d..8ffbae92a44 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts @@ -17,7 +17,7 @@ import { TestStorageService } from '../../../../test/common/workbenchTestService import { IChatAgentService } from '../../common/chatAgents.js'; import { ChatMode, ChatModeService } from '../../common/chatModes.js'; import { ChatModeKind } from '../../common/constants.js'; -import { ICustomChatMode, IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; +import { IChatModeSource, ICustomChatMode, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { MockPromptsService } from './mockPromptsService.js'; class TestChatAgentService implements Partial { @@ -41,6 +41,8 @@ class TestChatAgentService implements Partial { suite('ChatModeService', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); + const workspaceSource: IChatModeSource = { storage: PromptsStorage.local }; + let instantiationService: TestInstantiationService; let promptsService: MockPromptsService; let chatAgentService: TestChatAgentService; @@ -110,8 +112,8 @@ suite('ChatModeService', () => { name: 'Test Mode', description: 'A test custom mode', tools: ['tool1', 'tool2'], - modeInstructions: { content: 'Custom mode body', toolReferences: [] } - + modeInstructions: { content: 'Custom mode body', toolReferences: [] }, + source: workspaceSource }; promptsService.setCustomModes([customMode]); @@ -132,6 +134,7 @@ suite('ChatModeService', () => { assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.modeInstructions); assert.deepStrictEqual(testMode.handOffs?.get(), customMode.handOffs); assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString()); + assert.deepStrictEqual(testMode.source, workspaceSource); }); test('should fire change event when custom modes are updated', async () => { @@ -146,6 +149,7 @@ suite('ChatModeService', () => { description: 'A test custom mode', tools: [], modeInstructions: { content: 'Custom mode body', toolReferences: [] }, + source: workspaceSource, }; promptsService.setCustomModes([customMode]); @@ -163,6 +167,7 @@ suite('ChatModeService', () => { description: 'A findable custom mode', tools: [], modeInstructions: { content: 'Findable mode body', toolReferences: [] }, + source: workspaceSource, }; promptsService.setCustomModes([customMode]); @@ -186,6 +191,7 @@ suite('ChatModeService', () => { tools: ['tool1'], modeInstructions: { content: 'Initial body', toolReferences: [] }, model: 'gpt-4', + source: workspaceSource, }; promptsService.setCustomModes([initialMode]); @@ -218,6 +224,7 @@ suite('ChatModeService', () => { assert.deepStrictEqual(updatedCustomMode.customTools?.get(), ['tool1', 'tool2']); assert.deepStrictEqual(updatedCustomMode.modeInstructions?.get(), { content: 'Updated body', toolReferences: [] }); assert.strictEqual(updatedCustomMode.model?.get(), 'Updated model'); + assert.deepStrictEqual(updatedCustomMode.source, workspaceSource); }); test('should remove custom modes that no longer exist', async () => { @@ -227,6 +234,7 @@ suite('ChatModeService', () => { description: 'First mode', tools: [], modeInstructions: { content: 'Mode 1 body', toolReferences: [] }, + source: workspaceSource, }; const mode2: ICustomChatMode = { @@ -235,6 +243,7 @@ suite('ChatModeService', () => { description: 'Second mode', tools: [], modeInstructions: { content: 'Mode 2 body', toolReferences: [] }, + source: workspaceSource, }; // Add both modes diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 999ea40e068..8f73cd9facd 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -5,6 +5,9 @@ import assert from 'assert'; import * as sinon from 'sinon'; +import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; +import { Event } from '../../../../../../../base/common/event.js'; +import { ResourceSet } from '../../../../../../../base/common/map.js'; import { Schemas } from '../../../../../../../base/common/network.js'; import { URI } from '../../../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; @@ -13,32 +16,29 @@ import { ILanguageService } from '../../../../../../../editor/common/languages/l import { IModelService } from '../../../../../../../editor/common/services/model.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; import { IFileService } from '../../../../../../../platform/files/common/files.js'; import { FileService } from '../../../../../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ILabelService } from '../../../../../../../platform/label/common/label.js'; import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; +import { ITelemetryService } from '../../../../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryService } from '../../../../../../../platform/telemetry/common/telemetryUtils.js'; +import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'; +import { testWorkspace } from '../../../../../../../platform/workspace/test/common/testWorkspace.js'; +import { IWorkbenchEnvironmentService } from '../../../../../../services/environment/common/environmentService.js'; +import { IFilesConfigurationService } from '../../../../../../services/filesConfiguration/common/filesConfigurationService.js'; +import { IUserDataProfileService } from '../../../../../../services/userDataProfile/common/userDataProfile.js'; +import { TestContextService, TestUserDataProfileService } from '../../../../../../test/common/workbenchTestServices.js'; +import { ChatRequestVariableSet, isPromptFileVariableEntry, toFileVariableEntry } from '../../../../common/chatVariableEntries.js'; +import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; +import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; import { INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; -import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; import { ICustomChatMode, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; +import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; import { MockFilesystem } from '../testUtils/mockFilesystem.js'; -import { ILabelService } from '../../../../../../../platform/label/common/label.js'; -import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; -import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; -import { ResourceSet } from '../../../../../../../base/common/map.js'; -import { IWorkbenchEnvironmentService } from '../../../../../../services/environment/common/environmentService.js'; -import { ChatRequestVariableSet, isPromptFileVariableEntry, toFileVariableEntry } from '../../../../common/chatVariableEntries.js'; -import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; -import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'; -import { TestContextService, TestUserDataProfileService } from '../../../../../../test/common/workbenchTestServices.js'; -import { testWorkspace } from '../../../../../../../platform/workspace/test/common/testWorkspace.js'; -import { IUserDataProfileService } from '../../../../../../services/userDataProfile/common/userDataProfile.js'; -import { ITelemetryService } from '../../../../../../../platform/telemetry/common/telemetry.js'; -import { NullTelemetryService } from '../../../../../../../platform/telemetry/common/telemetryUtils.js'; -import { Event } from '../../../../../../../base/common/event.js'; -import { IFilesConfigurationService } from '../../../../../../services/filesConfiguration/common/filesConfigurationService.js'; -import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; suite('PromptsService', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -774,6 +774,7 @@ suite('PromptsService', () => { model: undefined, tools: undefined, uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), + source: { storage: PromptsStorage.local } }, ]; @@ -835,6 +836,7 @@ suite('PromptsService', () => { handOffs: undefined, model: undefined, uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), + source: { storage: PromptsStorage.local }, }, { name: 'mode2', @@ -847,6 +849,7 @@ suite('PromptsService', () => { metadata: undefined }, uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.chatmode.md'), + source: { storage: PromptsStorage.local }, } ]; From c397e8689ef5f494f90b49ce9d46f6932fe76963 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 19 Oct 2025 19:54:24 -0700 Subject: [PATCH 1307/4355] Move property inits out of the constructor, back to the declaration (#272206) This was for #243049 but I think we just did it too aggressively? --- .../contrib/chat/browser/chatInputPart.ts | 109 ++++++------------ 1 file changed, 38 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 247ce7a1d37..1a6bf0c81a6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -147,26 +147,26 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private readonly _chatEditingTodosDisposables = this._register(new DisposableStore()); private _lastEditingSessionId: string | undefined; - private _onDidLoadInputState: Emitter; - readonly onDidLoadInputState: Event; + private _onDidLoadInputState: Emitter = this._register(new Emitter()); + readonly onDidLoadInputState: Event = this._onDidLoadInputState.event; - private _onDidChangeHeight: Emitter; - readonly onDidChangeHeight: Event; + private _onDidChangeHeight = this._register(new Emitter()); + readonly onDidChangeHeight: Event = this._onDidChangeHeight.event; - private _onDidFocus: Emitter; - readonly onDidFocus: Event; + private _onDidFocus = this._register(new Emitter()); + readonly onDidFocus: Event = this._onDidFocus.event; - private _onDidBlur: Emitter; - readonly onDidBlur: Event; + private _onDidBlur = this._register(new Emitter()); + readonly onDidBlur: Event = this._onDidBlur.event; - private _onDidChangeContext: Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>; - readonly onDidChangeContext: Event<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>; + private _onDidChangeContext = this._register(new Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>()); + readonly onDidChangeContext: Event<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }> = this._onDidChangeContext.event; - private _onDidAcceptFollowup: Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>; - readonly onDidAcceptFollowup: Event<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>; + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); + readonly onDidAcceptFollowup: Event<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }> = this._onDidAcceptFollowup.event; - private _onDidClickOverlay: Emitter; - readonly onDidClickOverlay: Event; + private _onDidClickOverlay = this._register(new Emitter()); + readonly onDidClickOverlay: Event = this._onDidClickOverlay.event; private readonly _attachmentModel: ChatAttachmentModel; private _widget?: IChatWidget; @@ -202,8 +202,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }); } - private _indexOfLastAttachedContextDeletedWithKeyboard: number; - private _indexOfLastOpenedContext: number; + private _indexOfLastAttachedContextDeletedWithKeyboard: number = -1; + private _indexOfLastOpenedContext: number = -1; private _implicitContext: ChatImplicitContext | undefined; public get implicitContext(): ChatImplicitContext | undefined { @@ -217,42 +217,42 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _hasFileAttachmentContextKey: IContextKey; - private readonly _onDidChangeVisibility: Emitter; + private readonly _onDidChangeVisibility = this._register(new Emitter()); private readonly _contextResourceLabels: ResourceLabels; private readonly inputEditorMaxHeight: number; - private inputEditorHeight: number; + private inputEditorHeight: number = 0; private container!: HTMLElement; private inputSideToolbarContainer?: HTMLElement; private followupsContainer!: HTMLElement; - private readonly followupsDisposables: DisposableStore; + private readonly followupsDisposables: DisposableStore = this._register(new DisposableStore()); private attachmentsContainer!: HTMLElement; private chatInputOverlay!: HTMLElement; - private readonly overlayClickListener: MutableDisposable; + private readonly overlayClickListener: MutableDisposable = this._register(new MutableDisposable()); private attachedContextContainer!: HTMLElement; - private readonly attachedContextDisposables: MutableDisposable; + private readonly attachedContextDisposables: MutableDisposable = this._register(new MutableDisposable()); private relatedFilesContainer!: HTMLElement; private chatEditingSessionWidgetContainer!: HTMLElement; private chatInputTodoListWidgetContainer!: HTMLElement; - private _inputPartHeight: number; + private _inputPartHeight: number = 0; get inputPartHeight() { return this._inputPartHeight; } - private _followupsHeight: number; + private _followupsHeight: number = 0; get followupsHeight() { return this._followupsHeight; } - private _editSessionWidgetHeight: number; + private _editSessionWidgetHeight: number = 0; get editSessionWidgetHeight() { return this._editSessionWidgetHeight; } @@ -294,11 +294,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private chatSessionHasOptions: IContextKey; private modelWidget: ModelPickerActionItem | undefined; private modeWidget: ModePickerActionItem | undefined; - private chatSessionPickerWidgets: Map; - private additionalSessionPickerContainers: HTMLElement[]; - private readonly _waitForPersistedLanguageModel: MutableDisposable; - private _onDidChangeCurrentLanguageModel: Emitter; - private readonly _chatSessionOptionEmitters: Map>; + private chatSessionPickerWidgets: Map = new Map(); + private additionalSessionPickerContainers: HTMLElement[] = []; + private readonly _waitForPersistedLanguageModel: MutableDisposable = this._register(new MutableDisposable()); + private _onDidChangeCurrentLanguageModel: Emitter = this._register(new Emitter()); + private readonly _chatSessionOptionEmitters: Map> = new Map(); private _currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined; @@ -310,8 +310,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._currentLanguageModel; } - private _onDidChangeCurrentChatMode: Emitter; - readonly onDidChangeCurrentChatMode: Event; + private _onDidChangeCurrentChatMode: Emitter = this._register(new Emitter()); + readonly onDidChangeCurrentChatMode: Event = this._onDidChangeCurrentChatMode.event; private readonly _currentModeObservable: ISettableObservable; public get currentModeKind(): ChatModeKind { @@ -348,13 +348,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private cachedExecuteToolbarWidth: number | undefined; private cachedInputToolbarWidth: number | undefined; - readonly inputUri: URI; + readonly inputUri: URI = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); private _workingSetLinesAddedSpan?: HTMLElement; private _workingSetLinesRemovedSpan?: HTMLElement; - private readonly _chatEditsActionsDisposables: DisposableStore; - private readonly _chatEditsDisposables: DisposableStore; + private readonly _chatEditsActionsDisposables: DisposableStore = this._register(new DisposableStore()); + private readonly _chatEditsDisposables: DisposableStore = this._register(new DisposableStore()); private _chatEditsListPool: CollapsibleListPool; private _chatEditList: IDisposableReference> | undefined; get selectedElements(): URI[] { @@ -369,7 +369,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return edits; } - private _attemptedWorkingSetEntriesCount: number; + private _attemptedWorkingSetEntriesCount: number = 0; /** * The number of working set entries that the user actually wanted to attach. * This is less than or equal to {@link ChatInputPart.chatEditWorkingSetFiles}. @@ -418,43 +418,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super(); - this._onDidLoadInputState = this._register(new Emitter()); - this.onDidLoadInputState = this._onDidLoadInputState.event; - this._onDidChangeHeight = this._register(new Emitter()); - this.onDidChangeHeight = this._onDidChangeHeight.event; - this._onDidFocus = this._register(new Emitter()); - this.onDidFocus = this._onDidFocus.event; - this._onDidBlur = this._register(new Emitter()); - this.onDidBlur = this._onDidBlur.event; - this._onDidClickOverlay = this._register(new Emitter()); - this.onDidClickOverlay = this._onDidClickOverlay.event; - this._onDidChangeContext = this._register(new Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>()); - this.onDidChangeContext = this._onDidChangeContext.event; - this._onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); - this.onDidAcceptFollowup = this._onDidAcceptFollowup.event; - this._indexOfLastAttachedContextDeletedWithKeyboard = -1; - this._indexOfLastOpenedContext = -1; - this._onDidChangeVisibility = this._register(new Emitter()); this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event })); - this.inputEditorHeight = 0; - this.followupsDisposables = this._register(new DisposableStore()); - this.attachedContextDisposables = this._register(new MutableDisposable()); - this.overlayClickListener = this._register(new MutableDisposable()); - this._inputPartHeight = 0; - this._followupsHeight = 0; - this._editSessionWidgetHeight = 0; - this._waitForPersistedLanguageModel = this._register(new MutableDisposable()); - this._onDidChangeCurrentLanguageModel = this._register(new Emitter()); - this._onDidChangeCurrentChatMode = this._register(new Emitter()); - this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event; - this._chatSessionOptionEmitters = new Map(); - this.inputUri = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); - this._chatEditsActionsDisposables = this._register(new DisposableStore()); - this._chatEditsDisposables = this._register(new DisposableStore()); - this._attemptedWorkingSetEntriesCount = 0; this._currentModeObservable = observableValue('currentMode', this.options.defaultMode ?? ChatMode.Agent); - this.chatSessionPickerWidgets = new Map(); - this.additionalSessionPickerContainers = []; this._register(this.editorService.onDidActiveEditorChange(() => { this._indexOfLastOpenedContext = -1; @@ -723,7 +688,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } return widgets; - } public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { + } + + public setCurrentLanguageModel(model: ILanguageModelChatMetadataAndIdentifier) { this._currentLanguageModel = model; if (this.cachedDimensions) { From 42943e3121fe00fcedf95b64ada12f82b54ff082 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 19 Oct 2025 20:07:44 -0700 Subject: [PATCH 1308/4355] Ensure that we have the correct model when forwarding request (#272207) Fix #267523 --- .../workbench/contrib/chat/browser/chatSetup.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index d224fe979c1..4c2b979e196 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -321,7 +321,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let toolsModelReady = false; const whenAgentReady = this.whenAgentReady(chatAgentService, modeInfo?.kind)?.then(() => agentReady = true); - const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService)?.then(() => languageModelReady = true); + const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService, requestModel.modelId)?.then(() => languageModelReady = true); const whenToolsModelReady = this.whenToolsModelReady(languageModelToolsService, requestModel)?.then(() => toolsModelReady = true); if (whenLanguageModelReady instanceof Promise || whenAgentReady instanceof Promise || whenToolsModelReady instanceof Promise) { @@ -383,7 +383,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { }); } - private whenLanguageModelReady(languageModelsService: ILanguageModelsService): Promise | void { + private whenLanguageModelReady(languageModelsService: ILanguageModelsService, modelId: string | undefined): Promise | void { const hasDefaultModel = () => { for (const id of languageModelsService.getLanguageModelIds()) { const model = languageModelsService.lookupLanguageModel(id); @@ -393,11 +393,20 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } return false; }; - if (hasDefaultModel()) { + const hasModelForRequest = () => { + if (modelId) { + return !!languageModelsService.lookupLanguageModel(modelId); + } else { + return hasDefaultModel(); + } + }; + + if (hasModelForRequest()) { return; // we have language models! } - return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasDefaultModel() ?? false)); + + return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasModelForRequest())); } private whenToolsModelReady(languageModelToolsService: ILanguageModelToolsService, requestModel: IChatRequestModel): Promise | void { From 4659a80b74ca7f55600e1f5b54830d4240ab174a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 20 Oct 2025 07:35:31 +0200 Subject: [PATCH 1309/4355] debt - code and lint :lipstick: (#272215) --- eslint.config.js | 2 +- .../contrib/chat/browser/chatSetup.ts | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 9baf392eda7..a64868e144c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -914,7 +914,7 @@ export default tseslint.config( '@typescript-eslint/no-explicit-any': [ 'warn', { - 'ignoreRestArgs': false + 'fixToUnknown': true } ] } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 4c2b979e196..a200b626aaa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -384,28 +384,25 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } private whenLanguageModelReady(languageModelsService: ILanguageModelsService, modelId: string | undefined): Promise | void { - const hasDefaultModel = () => { + const hasModelForRequest = () => { + if (modelId) { + return !!languageModelsService.lookupLanguageModel(modelId); + } + for (const id of languageModelsService.getLanguageModelIds()) { const model = languageModelsService.lookupLanguageModel(id); if (model && model.isDefault) { - return true; // we have language models! + return true; } } + return false; }; - const hasModelForRequest = () => { - if (modelId) { - return !!languageModelsService.lookupLanguageModel(modelId); - } else { - return hasDefaultModel(); - } - }; if (hasModelForRequest()) { - return; // we have language models! + return; } - return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasModelForRequest())); } From 2c8dc1604ebd8b6f8af14abab6632ab5dc66e4cd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 20 Oct 2025 05:50:38 +0000 Subject: [PATCH 1310/4355] Support context widget in chat input box for chat sessions (#272190) * Support context widget in chat input box for chat sessions * Disable tools from context if tools aren't supported * Support for MCP references as well * Exclude images as well * Remove empty lint * Udpates * Simplify --- .../chat/browser/actions/chatContext.ts | 9 +++- .../browser/actions/chatContextActions.ts | 5 ++- src/vs/workbench/contrib/chat/browser/chat.ts | 3 ++ .../chat/browser/chatSessions.contribution.ts | 16 +++++++ .../contrib/chat/browser/chatWidget.ts | 45 ++++++++++++++++++- .../contrib/chat/common/chatAgents.ts | 2 + .../contrib/chat/common/chatContextKeys.ts | 1 + .../chat/common/chatSessionsService.ts | 12 +++++ .../mcp/browser/mcpAddContextContribution.ts | 3 ++ 9 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 00d29bf8e00..4ea83b5610d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -65,6 +65,10 @@ class ToolsContextPickerPick implements IChatContextPickerItem { readonly icon: ThemeIcon = Codicon.tools; readonly ordinal = -500; + isEnabled(widget: IChatWidget): boolean { + return widget.supportsToolAttachments; + } + asPicker(widget: IChatWidget): IChatContextPicker { type Pick = IChatContextPickerPickItem & { toolInfo: { ordinal: number; label: string } }; @@ -232,6 +236,9 @@ class ClipboardImageContextValuePick implements IChatContextValueItem { ) { } async isEnabled(widget: IChatWidget) { + if (!widget.supportsImageAttachments) { + return false; + } if (!widget.input.selectedLanguageModel?.metadata.capabilities?.vision) { return false; } @@ -264,7 +271,7 @@ class ScreenshotContextValuePick implements IChatContextValueItem { ) { } async isEnabled(widget: IChatWidget) { - return !!widget.input.selectedLanguageModel?.metadata.capabilities?.vision; + return widget.supportsImageAttachments && !!widget.input.selectedLanguageModel?.metadata.capabilities?.vision; } async asAttachment(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 40d8d46b6e9..a9bcf5423ae 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -408,7 +408,10 @@ export class AttachContextAction extends Action2 { menu: { when: ContextKeyExpr.and( ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), - ChatContextKeys.lockedToCodingAgent.negate() + ContextKeyExpr.or( + ChatContextKeys.lockedToCodingAgent.negate(), + ChatContextKeys.agentSupportsAttachments + ) ), id: MenuId.ChatInputAttachmentToolbar, group: 'navigation', diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 6f5294575ef..c40efd5f108 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -214,6 +214,9 @@ export interface IChatWidget { readonly viewModel: IChatViewModel | undefined; readonly inputEditor: ICodeEditor; readonly supportsFileReferences: boolean; + readonly supportsToolAttachments: boolean; + readonly supportsMCPAttachments: boolean; + readonly supportsImageAttachments: boolean; readonly parsedInput: IParsedChatRequest; readonly lockedAgentId: string | undefined; lastSelectedAgent: IChatAgentData | undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index eba2c4b614d..12810e6d4fd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -89,6 +89,14 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint; + private _agentSupportsAttachmentsContextKey!: IContextKey; + private _supportsToolAttachments: boolean = true; + private _supportsMCPAttachments: boolean = true; + private _supportsFileAttachments: boolean = true; + private _supportsImageAttachments: boolean = true; private _codingAgentPrefix: string | undefined; private _lockedAgentId: string | undefined; @@ -487,6 +492,7 @@ export class ChatWidget extends Disposable implements IChatWidget { ) { super(); this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService); + this._agentSupportsAttachmentsContextKey = ChatContextKeys.agentSupportsAttachments.bindTo(this.contextKeyService); this.viewContext = _viewContext ?? {}; @@ -701,6 +707,7 @@ export class ChatWidget extends Disposable implements IChatWidget { set lastSelectedAgent(agent: IChatAgentData | undefined) { this.parsedChatRequest = undefined; this._lastSelectedAgent = agent; + this._updateAgentCapabilitiesContextKeys(agent); this._onDidChangeParsedInput.fire(); } @@ -708,10 +715,40 @@ export class ChatWidget extends Disposable implements IChatWidget { return this._lastSelectedAgent; } + private _updateAgentCapabilitiesContextKeys(agent: IChatAgentData | undefined): void { + this._supportsFileAttachments = true; + this._supportsToolAttachments = true; + this._supportsMCPAttachments = true; + this._supportsImageAttachments = true; + + // Check if the agent has capabilities defined directly + const capabilities = agent?.capabilities ?? (this._lockedAgentId ? this.chatSessionsService.getCapabilitiesForSessionType(this._lockedAgentId) : undefined); + if (capabilities) { + this._supportsFileAttachments = capabilities.supportsFileAttachments ?? false; + this._supportsToolAttachments = capabilities.supportsToolAttachments ?? false; + this._supportsMCPAttachments = capabilities.supportsMCPAttachments ?? false; + this._supportsImageAttachments = capabilities.supportsImageAttachments ?? false; + } + + this._agentSupportsAttachmentsContextKey.set(this._supportsFileAttachments || this._supportsImageAttachments || this._supportsToolAttachments || this._supportsMCPAttachments); + } + get supportsFileReferences(): boolean { return !!this.viewOptions.supportsFileReferences; } + get supportsToolAttachments(): boolean { + return this._supportsToolAttachments; + } + + get supportsMCPAttachments(): boolean { + return this._supportsMCPAttachments; + } + + get supportsImageAttachments(): boolean { + return this._supportsImageAttachments; + } + get input(): ChatInputPart { return this.viewModel?.editing && this.configurationService.getValue('chat.editRequests') !== 'input' ? this.inlineInputPart : this.inputPart; } @@ -2348,6 +2385,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModelDisposables.add(model.onDidChange((e) => { if (e.kind === 'setAgent') { this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); + // Update capabilities context keys when agent changes + this._updateAgentCapabilitiesContextKeys(e.agent); } if (e.kind === 'addRequest') { this.clearTodoListWidget(model.sessionId, false); @@ -2421,6 +2460,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this._lockedAgentId = agentId; this._lockedToCodingAgentContextKey.set(true); this._welcomeRenderScheduler.schedule(); + // Update capabilities for the locked agent + const agent = this.chatAgentService.getAgent(agentId); + this._updateAgentCapabilitiesContextKeys(agent); this.renderer.updateOptions({ restorable: false, editable: false, noFooter: true, progressMessageAtBottomOfResponse: true }); this.tree.rerender(); } @@ -2431,6 +2473,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._codingAgentPrefix = undefined; this._lockedAgentId = undefined; this._lockedToCodingAgentContextKey.set(false); + this._updateAgentCapabilitiesContextKeys(undefined); // Explicitly update the DOM to reflect unlocked state this._welcomeRenderScheduler.schedule(); diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 4e3190d4992..658c7b633a2 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -66,6 +66,8 @@ export interface IChatAgentData { capabilities?: { supportsToolAttachments?: boolean; supportsFileAttachments?: boolean; + supportsMCPAttachments?: boolean; + supportsImageAttachments?: boolean; }; } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 43431e50a2e..3ae052a3fde 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -45,6 +45,7 @@ export namespace ChatContextKeys { * True when the chat widget is locked to the coding agent session. */ export const lockedToCodingAgent = new RawContextKey('lockedToCodingAgent', false, { type: 'boolean', description: localize('lockedToCodingAgent', "True when the chat widget is locked to the coding agent session.") }); + export const agentSupportsAttachments = new RawContextKey('agentSupportsAttachments', false, { type: 'boolean', description: localize('agentSupportsAttachments', "True when the chat agent supports attachments.") }); export const withinEditSessionDiff = new RawContextKey('withinEditSessionDiff', false, { type: 'boolean', description: localize('withinEditSessionDiff', "True when the chat widget dispatches to the edit session chat.") }); export const filePartOfEditSession = new RawContextKey('filePartOfEditSession', false, { type: 'boolean', description: localize('filePartOfEditSession', "True when the chat widget is within a file with an edit session.") }); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 95cd253a667..c3b9afcb346 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -56,6 +56,8 @@ export interface IChatSessionsExtensionPoint { readonly capabilities?: { supportsFileAttachments?: boolean; supportsToolAttachments?: boolean; + supportsMCPAttachments?: boolean; + supportsImageAttachments?: boolean; }; readonly commands?: IChatSessionCommandContribution[]; } @@ -175,6 +177,16 @@ export interface IChatSessionsService { getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined; setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean; + + /** + * Get the capabilities for a specific session type + */ + getCapabilitiesForSessionType(chatSessionType: string): { + supportsFileAttachments?: boolean; + supportsToolAttachments?: boolean; + supportsMCPAttachments?: boolean; + supportsImageAttachments?: boolean; + } | undefined; } export const IChatSessionsService = createDecorator('chatSessionsService'); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts b/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts index a55ccfe7de8..bcabf36168a 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts @@ -39,6 +39,9 @@ export class McpAddContextContribution extends Disposable implements IWorkbenchC type: 'pickerPick', label: localize('mcp.addContext', "MCP Resources..."), icon: Codicon.mcp, + isEnabled(widget) { + return widget.supportsMCPAttachments; + }, asPicker: () => ({ placeholder: localize('mcp.addContext.placeholder', "Select MCP Resource..."), picks: (_query, token) => this._getResourcePicks(token), From c3d5818fc946dc6ac87cb6befcdf8f5bb9bf8cbb Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:30:34 +0200 Subject: [PATCH 1311/4355] SCM - remove the usage of `autorunWithStore` (#272238) --- src/vs/workbench/contrib/scm/browser/activity.ts | 14 +++++++------- .../contrib/scm/browser/quickDiffDecorator.ts | 12 ++++++------ .../contrib/scm/browser/quickDiffModel.ts | 8 ++++---- .../contrib/scm/browser/scmHistoryViewPane.ts | 14 +++++++------- .../contrib/scm/browser/scmRepositoryRenderer.ts | 6 +++--- src/vs/workbench/contrib/scm/browser/workingSet.ts | 8 ++++---- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index ea73105b4cc..3a3b77f7be8 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -20,7 +20,7 @@ import { ITitleService } from '../../../services/title/browser/titleService.js'; import { IEditorGroupContextKeyProvider, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { getRepositoryResourceCount, getSCMRepositoryIcon } from './util.js'; -import { autorun, autorunWithStore, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; +import { autorun, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { Command } from '../../../../editor/common/languages.js'; @@ -102,16 +102,16 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe return total; }); - this._register(autorunWithStore((reader, store) => { + this._register(autorun(reader => { const countBadge = this._countBadge.read(reader); - this._updateActivityCountBadge(countBadge, store); + this._updateActivityCountBadge(countBadge, reader.store); })); - this._register(autorunWithStore((reader, store) => { + this._register(autorun(reader => { const activeRepository = this.scmViewService.activeRepository.read(reader); const commands = activeRepository?.repository.provider.statusBarCommands.read(reader); - this._updateStatusBar(activeRepository, commands ?? [], store); + this._updateStatusBar(activeRepository, commands ?? [], reader.store); })); this._register(autorun(reader => { @@ -214,9 +214,9 @@ export class SCMActiveResourceContextKeyController extends Disposable implements Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository), () => this.scmService.repositories); - this._store.add(autorunWithStore((reader, store) => { + this._register(autorun((reader) => { for (const repository of this._repositories.read(reader)) { - store.add(Event.runAndSubscribe(repository.provider.onDidChangeResources, () => { + reader.store.add(Event.runAndSubscribe(repository.provider.onDidChangeResources, () => { this._onDidRepositoryChange.fire(); })); } diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts index 5937359f548..12509dfbbc2 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts @@ -22,7 +22,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { ContextKeyTrueExpr, ContextKeyFalseExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { autorun, autorunWithStore, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; +import { autorun, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -338,7 +338,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben } private onDidActiveEditorChange(): void { - this.transientDisposables.add(autorunWithStore((reader, store) => { + this.transientDisposables.add(autorun(reader => { const activeEditor = this.activeEditor.read(reader); const activeTextEditorControl = this.editorService.activeTextEditorControl; @@ -353,7 +353,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben return; } - store.add(quickDiffModelRef); + reader.store.add(quickDiffModelRef); const visibleDecorationCount = observableFromEvent(this, quickDiffModelRef.object.onDidChange, () => { @@ -361,7 +361,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben return quickDiffModelRef.object.changes.filter(change => visibleQuickDiffs.some(quickDiff => quickDiff.id === change.providerId)).length; }); - store.add(autorun(reader => { + reader.store.add(autorun(reader => { const count = visibleDecorationCount.read(reader); this.quickDiffDecorationCount.set(count); })); @@ -369,7 +369,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben } private onDidChangeQuickDiffProviders(): void { - this.transientDisposables.add(autorunWithStore((reader, store) => { + this.transientDisposables.add(autorun(reader => { const providers = this.quickDiffProviders.read(reader); const labels: string[] = []; @@ -383,7 +383,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben const group = provider.kind !== 'contributed' ? '0_scm' : '1_contributed'; const order = index + 1; - store.add(registerAction2(class extends Action2 { + reader.store.add(registerAction2(class extends Action2 { constructor() { super({ id: `workbench.scm.action.toggleQuickDiffVisibility.${provider.id}`, diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts index 94ab85f3eb3..59c51ee0c20 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffModel.ts @@ -28,7 +28,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IChatEditingService, ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { autorun, autorunWithStore } from '../../../../base/common/observable.js'; +import { autorun } from '../../../../base/common/observable.js'; export const IQuickDiffModelService = createDecorator('IQuickDiffModelService'); @@ -159,9 +159,9 @@ export class QuickDiffModel extends Disposable { this._register(this.quickDiffService.onDidChangeQuickDiffProviders(() => this.triggerDiff())); - this._register(autorunWithStore((r, store) => { - for (const session of this._chatEditingService.editingSessionsObs.read(r)) { - store.add(autorun(r => { + this._register(autorun(reader => { + for (const session of this._chatEditingService.editingSessionsObs.read(reader)) { + reader.store.add(autorun(r => { for (const entry of session.entries.read(r)) { entry.state.read(r); // signal } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 2f848351969..cec76c3a1a0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -13,7 +13,7 @@ import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.j import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeElementRenderDetails, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; import { createMatches, FuzzyScore, IMatch } from '../../../../base/common/filters.js'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, derived, IObservable, observableValue, waitForState, constObservable, latestChangedValue, observableFromEvent, runOnChange, observableSignal, ISettableObservable } from '../../../../base/common/observable.js'; +import { autorun, derived, IObservable, observableValue, waitForState, constObservable, latestChangedValue, observableFromEvent, runOnChange, observableSignal, ISettableObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -1628,7 +1628,7 @@ export class SCMHistoryViewPane extends ViewPane { // Repository change let isFirstRun = true; - this._visibilityDisposables.add(autorunWithStore((reader, store) => { + this._visibilityDisposables.add(autorun(reader => { const repository = this._treeViewModel.repository.read(reader); const historyProvider = repository?.provider.historyProvider.read(reader); if (!repository || !historyProvider) { @@ -1639,7 +1639,7 @@ export class SCMHistoryViewPane extends ViewPane { const historyItemRefId = derived(reader => { return historyProvider.historyItemRef.read(reader)?.id; }); - store.add(runOnChange(historyItemRefId, async historyItemRefIdValue => { + reader.store.add(runOnChange(historyItemRefId, async historyItemRefIdValue => { await this.refresh(); // Update context key (needs to be done after the refresh call) @@ -1647,7 +1647,7 @@ export class SCMHistoryViewPane extends ViewPane { })); // HistoryItemRefs changed - store.add(runOnChange(historyProvider.historyItemRefChanges, changes => { + reader.store.add(runOnChange(historyProvider.historyItemRefChanges, changes => { if (changes.silent) { // The history item reference changes occurred in the background (ex: Auto Fetch) // If tree is scrolled to the top, we can safely refresh the tree, otherwise we @@ -1666,7 +1666,7 @@ export class SCMHistoryViewPane extends ViewPane { })); // HistoryItemRefs filter changed - store.add(runOnChange(this._treeViewModel.onDidChangeHistoryItemsFilter, async () => { + reader.store.add(runOnChange(this._treeViewModel.onDidChangeHistoryItemsFilter, async () => { await this.refresh(); // Update context key (needs to be done after the refresh call) @@ -1674,12 +1674,12 @@ export class SCMHistoryViewPane extends ViewPane { })); // HistoryItemRemoteRef changed - store.add(autorun(reader => { + reader.store.add(autorun(reader => { this._scmCurrentHistoryItemRefHasRemote.set(!!historyProvider.historyItemRemoteRef.read(reader)); })); // ViewMode changed - store.add(runOnChange(this._treeViewModel.viewMode, async () => { + reader.store.add(runOnChange(this._treeViewModel.viewMode, async () => { await this._updateChildren(); })); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 26c8a37769a..90bb220a5fd 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -5,7 +5,7 @@ import './media/scm.css'; import { IDisposable, DisposableStore, combinedDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, IObservable } from '../../../../base/common/observable.js'; +import { autorun, IObservable } from '../../../../base/common/observable.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { ISCMProvider, ISCMRepository, ISCMViewService } from '../common/scm.js'; import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; @@ -130,9 +130,9 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer { + templateData.elementDisposables.add(autorun(reader => { const commands = repository.provider.statusBarCommands.read(reader) ?? []; - statusPrimaryActions = commands.map(c => store.add(new StatusBarAction(c, this.commandService))); + statusPrimaryActions = commands.map(c => reader.store.add(new StatusBarAction(c, this.commandService))); updateToolbar(); })); diff --git a/src/vs/workbench/contrib/scm/browser/workingSet.ts b/src/vs/workbench/contrib/scm/browser/workingSet.ts index 1f53f8d298b..6152701f3f7 100644 --- a/src/vs/workbench/contrib/scm/browser/workingSet.ts +++ b/src/vs/workbench/contrib/scm/browser/workingSet.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, derived, IObservable } from '../../../../base/common/observable.js'; +import { autorun, derived, IObservable } from '../../../../base/common/observable.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -44,7 +44,7 @@ export class SCMWorkingSetController extends Disposable implements IWorkbenchCon this._enabledConfig = observableConfigValue('scm.workingSets.enabled', false, this.configurationService); - this._store.add(autorunWithStore((reader, store) => { + this._store.add(autorun(reader => { if (!this._enabledConfig.read(reader)) { this.storageService.remove('scm.workingSets', StorageScope.WORKSPACE); this._repositoryDisposables.clearAndDisposeAll(); @@ -53,8 +53,8 @@ export class SCMWorkingSetController extends Disposable implements IWorkbenchCon this._workingSets = this._loadWorkingSets(); - this.scmService.onDidAddRepository(this._onDidAddRepository, this, store); - this.scmService.onDidRemoveRepository(this._onDidRemoveRepository, this, store); + this.scmService.onDidAddRepository(this._onDidAddRepository, this, reader.store); + this.scmService.onDidRemoveRepository(this._onDidRemoveRepository, this, reader.store); for (const repository of this.scmService.repositories) { this._onDidAddRepository(repository); From ca2f93e16d0b821dc6fbe3eda74ae5923c545189 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 20 Oct 2025 11:42:04 +0200 Subject: [PATCH 1312/4355] Removes deprecated InlineCompletionEndOfLifeEvent.id (#271701) --- .../browser/model/inlineCompletionsSource.ts | 1 - src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts | 5 ----- src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts | 1 - 3 files changed, 7 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 50aa83335a6..ac8d6ff2516 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -399,7 +399,6 @@ export class InlineCompletionsSource extends Disposable { } const emptyEndOfLifeEvent: InlineCompletionEndOfLifeEvent = { - id: requestResponseInfo.requestUuid, opportunityId: requestResponseInfo.requestUuid, noSuggestionReason: requestResponseInfo.noSuggestionReason ?? 'unknown', extensionId: 'vscode-core', diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts index 947d9db0b5d..d56faa53354 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -11,10 +11,6 @@ export function sendInlineCompletionsEndOfLifeTelemetry(dataChannel: DataChannel export type InlineCompletionEndOfLifeEvent = { // request - /** - * @deprecated To be removed at one point in favor of opportunityId - */ - id: string; opportunityId: string; requestReason: string; editorType: string; @@ -61,7 +57,6 @@ export type InlineCompletionEndOfLifeEvent = { type InlineCompletionsEndOfLifeClassification = { owner: 'benibenj'; comment: 'Inline completions ended'; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the inline completion request' }; opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES' }; correlationId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The correlation identifier for the inline completion' }; extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the extension that contributed the inline completion' }; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 2923de5b93c..799de991883 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -719,7 +719,6 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } const endOfLifeSummary: InlineCompletionEndOfLifeEvent = { - id: lifetimeSummary.requestUuid, opportunityId: lifetimeSummary.requestUuid, correlationId: lifetimeSummary.correlationId, shown: lifetimeSummary.shown, From 02ebbd647de6ec4acebaf49d03e648127e45068e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:43:40 +0200 Subject: [PATCH 1313/4355] Allow undoing individual "Revert Block" and manual edits in diff editor (#271484) * Initial plan * Add undo stops before revert operations in diff editor Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hediet <2931520+hediet@users.noreply.github.com> --- src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 607a104123e..516652d36bd 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -578,12 +578,14 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { const model = this._diffModel.get(); if (!model || !model.isDiffUpToDate.get()) { return; } + this._editors.modified.pushUndoStop(); this._editors.modified.executeEdits('diffEditor', [ { range: diff.modified.toExclusiveRange(), text: model.model.original.getValueInRange(diff.original.toExclusiveRange()) } ]); + this._editors.modified.pushUndoStop(); } revertRangeMappings(diffs: RangeMapping[]): void { @@ -595,7 +597,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { text: model.model.original.getValueInRange(c.originalRange) })); + this._editors.modified.pushUndoStop(); this._editors.modified.executeEdits('diffEditor', changes); + this._editors.modified.pushUndoStop(); } revertFocusedRangeMappings() { @@ -615,12 +619,14 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { return d.lineRangeMapping.modified.intersect(selectedRange); }); + modifiedEditor.pushUndoStop(); modifiedEditor.executeEdits('diffEditor', diffsToRevert.map(d => ( { range: d.lineRangeMapping.modified.toExclusiveRange(), text: model.model.original.getValueInRange(d.lineRangeMapping.original.toExclusiveRange()) } ))); + modifiedEditor.pushUndoStop(); } From b63cdda669e29e195a4b5bf75b19c655da7c95b8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:47:06 +0200 Subject: [PATCH 1314/4355] SCM - remove the usage of `any` (#272231) --- eslint.config.js | 4 ---- .../contrib/scm/browser/quickDiffWidget.ts | 4 ++-- .../scm/browser/scmRepositoriesViewPane.ts | 4 ++-- .../contrib/scm/browser/scmViewPane.ts | 6 ++--- src/vs/workbench/contrib/scm/browser/util.ts | 22 +++++++++---------- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index a64868e144c..509648a12c1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -731,10 +731,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', 'src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts', 'src/vs/workbench/contrib/remote/browser/tunnelView.ts', - 'src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts', - 'src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts', - 'src/vs/workbench/contrib/scm/browser/scmViewPane.ts', - 'src/vs/workbench/contrib/scm/browser/util.ts', 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModel.ts', 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModelBase.ts', 'src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchModel.ts', diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts index 619519173b5..51bf45c1fad 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts @@ -108,7 +108,7 @@ export class QuickDiffPickerBaseAction extends Action { class QuickDiffWidgetActionRunner extends ActionRunner { - protected override runAction(action: IAction, context: any): Promise { + protected override runAction(action: IAction, context: unknown[]): Promise { if (action instanceof MenuItemAction) { return action.run(...context); } @@ -140,7 +140,7 @@ class QuickDiffWidgetEditorAction extends Action { this.editor = editor; } - override run(): Promise { + override run(): Promise { return Promise.resolve(this.instantiationService.invokeFunction(accessor => this.action.run(accessor, this.editor, null))); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 7e3c11a21da..b689e9d9fbe 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -79,7 +79,7 @@ class RepositoryTreeIdentityProvider implements IIdentityProvider; + private tree!: WorkbenchCompressibleAsyncDataTree; private treeDataSource!: RepositoryTreeDataSource; private treeIdentityProvider!: RepositoryTreeIdentityProvider; private readonly treeOperationSequencer = new Sequencer(); @@ -229,7 +229,7 @@ export class SCMRepositoriesViewPane extends ViewPane { } } } - ) as WorkbenchCompressibleAsyncDataTree; + ) as WorkbenchCompressibleAsyncDataTree; this._register(this.tree); this._register(autorun(reader => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 339857fc6a8..204e6632e8c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -515,7 +515,7 @@ class RepositoryPaneActionRunner extends ActionRunner { super(); } - protected override async runAction(action: IAction, context: ISCMResourceGroup | ISCMResource | IResourceNode): Promise { + protected override async runAction(action: IAction, context: ISCMResourceGroup | ISCMResource | IResourceNode): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } @@ -1694,7 +1694,7 @@ class SCMInputWidget { } // Validation - const validationDelayer = new ThrottledDelayer(200); + const validationDelayer = new ThrottledDelayer(200); const validate = async () => { const position = this.inputEditor.getSelection()?.getStartPosition(); const offset = position && textModel.getOffsetAt(position); @@ -2612,7 +2612,7 @@ export class SCMViewPane extends ViewPane { } const element = e.element; - let context: any = element; + let context: unknown = element; let actions: IAction[] = []; const disposables = new DisposableStore(); diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index f915cda154e..1d9b9215cec 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -21,47 +21,47 @@ import { IResourceNode, ResourceTree } from '../../../../base/common/resourceTre import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; -export function isSCMViewService(element: any): element is ISCMViewService { +export function isSCMViewService(element: unknown): element is ISCMViewService { return Array.isArray((element as ISCMViewService).repositories) && Array.isArray((element as ISCMViewService).visibleRepositories); } -export function isSCMRepository(element: any): element is ISCMRepository { +export function isSCMRepository(element: unknown): element is ISCMRepository { return !!(element as ISCMRepository).provider && !!(element as ISCMRepository).input; } -export function isSCMInput(element: any): element is ISCMInput { +export function isSCMInput(element: unknown): element is ISCMInput { return !!(element as ISCMInput).validateInput && typeof (element as ISCMInput).value === 'string'; } -export function isSCMActionButton(element: any): element is ISCMActionButton { +export function isSCMActionButton(element: unknown): element is ISCMActionButton { return (element as ISCMActionButton).type === 'actionButton'; } -export function isSCMResourceGroup(element: any): element is ISCMResourceGroup { +export function isSCMResourceGroup(element: unknown): element is ISCMResourceGroup { return !!(element as ISCMResourceGroup).provider && !!(element as ISCMResourceGroup).resources; } -export function isSCMResource(element: any): element is ISCMResource { +export function isSCMResource(element: unknown): element is ISCMResource { return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup); } -export function isSCMResourceNode(element: any): element is IResourceNode { +export function isSCMResourceNode(element: unknown): element is IResourceNode { return ResourceTree.isResourceNode(element) && isSCMResourceGroup(element.context); } -export function isSCMHistoryItemViewModelTreeElement(element: any): element is SCMHistoryItemViewModelTreeElement { +export function isSCMHistoryItemViewModelTreeElement(element: unknown): element is SCMHistoryItemViewModelTreeElement { return (element as SCMHistoryItemViewModelTreeElement).type === 'historyItemViewModel'; } -export function isSCMHistoryItemLoadMoreTreeElement(element: any): element is SCMHistoryItemLoadMoreTreeElement { +export function isSCMHistoryItemLoadMoreTreeElement(element: unknown): element is SCMHistoryItemLoadMoreTreeElement { return (element as SCMHistoryItemLoadMoreTreeElement).type === 'historyItemLoadMore'; } -export function isSCMHistoryItemChangeViewModelTreeElement(element: any): element is SCMHistoryItemChangeViewModelTreeElement { +export function isSCMHistoryItemChangeViewModelTreeElement(element: unknown): element is SCMHistoryItemChangeViewModelTreeElement { return (element as SCMHistoryItemChangeViewModelTreeElement).type === 'historyItemChangeViewModel'; } -export function isSCMHistoryItemChangeNode(element: any): element is IResourceNode { +export function isSCMHistoryItemChangeNode(element: unknown): element is IResourceNode { return ResourceTree.isResourceNode(element) && isSCMHistoryItemViewModelTreeElement(element.context); } From d4c395ab120790d3a0ef72cc8567620a0473bed4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 02:51:31 -0700 Subject: [PATCH 1315/4355] Cache container width for the task, clear in microtask Part of #272239 --- .../contrib/terminal/browser/terminalTabsList.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 598cdaf0a04..91334b6e4bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -317,11 +317,20 @@ class TerminalTabsRenderer extends Disposable implements IListRenderer this._cachedContainerWidth = -1); + } + return this._cachedContainerWidth; } renderElement(instance: ITerminalInstance, index: number, template: ITerminalTabEntryTemplate): void { From e4db2588522e6440fc13908c497ce09092afb0d5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Oct 2025 12:09:15 +0200 Subject: [PATCH 1316/4355] support multiple v0 versions - fix #271970 (#272237) --- .../platform/mcp/common/mcpGalleryService.ts | 101 ++++++++++++++---- 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index d223dc5c0b7..c82d7dc0041 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -17,11 +17,12 @@ import { GalleryMcpServerStatus, IGalleryMcpServer, IMcpGalleryService, IMcpServ import { IMcpGalleryManifestService, McpGalleryManifestStatus, getMcpGalleryManifestResourceUri, McpGalleryResourceType, IMcpGalleryManifest } from './mcpGalleryManifest.js'; import { IPageIterator, IPager, PageIteratorPager, singlePagePager } from '../../../base/common/paging.js'; import { CancellationError } from '../../../base/common/errors.js'; +import { isObject, isString } from '../../../base/common/types.js'; interface IMcpRegistryInfo { - readonly isLatest: boolean; - readonly publishedAt: string; - readonly updatedAt: string; + readonly isLatest?: boolean; + readonly publishedAt?: string; + readonly updatedAt?: string; } interface IGitHubInfo { @@ -73,7 +74,7 @@ interface IRawGalleryMcpServer { readonly updatedAt?: string; readonly packages?: readonly IMcpServerPackage[]; readonly remotes?: ReadonlyArray; - readonly registryInfo: IMcpRegistryInfo; + readonly registryInfo?: IMcpRegistryInfo; readonly githubInfo?: IGitHubInfo; } @@ -83,9 +84,9 @@ interface IGalleryMcpServerDataSerializer { } -namespace McpServerSchemaVersion_v0 { +namespace McpServerSchemaVersion_v2025_07_09 { - export const VERSION = 'v0'; + export const VERSION = 'v0-2025-07-09'; export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json`; interface RawGalleryMcpServerInput { @@ -226,11 +227,24 @@ namespace McpServerSchemaVersion_v0 { } public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { - if (!input || typeof input !== 'object' || (input).$schema !== McpServerSchemaVersion_v0.SCHEMA) { + if (!input || typeof input !== 'object') { return undefined; } const from = input; + + if ( + (!from.name || !isString(from.name)) + || (!from.description || !isString(from.description)) + || (!from.version || !isString(from.version)) + ) { + return undefined; + } + + if (from.$schema && from.$schema !== McpServerSchemaVersion_v2025_07_09.SCHEMA) { + return undefined; + } + const registryInfo = from._meta?.['io.modelcontextprotocol.registry/official']; function convertServerInput(input: RawGalleryMcpServerInput): IMcpServerInput { @@ -368,6 +382,7 @@ namespace McpServerSchemaVersion_v0 { namespace McpServerSchemaVersion_v0_1 { export const VERSION = 'v0.1'; + export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json`; interface RawGalleryMcpServerInput { readonly description?: string; @@ -437,10 +452,10 @@ namespace McpServerSchemaVersion_v0_1 { } interface RawGalleryMcpServer { - readonly $schema: string; readonly name: string; readonly description: string; readonly version: string; + readonly $schema?: string; readonly title?: string; readonly repository?: { readonly source: string; @@ -451,15 +466,15 @@ namespace McpServerSchemaVersion_v0_1 { readonly websiteUrl?: string; readonly packages?: readonly RawGalleryMcpServerPackage[]; readonly remotes?: RawGalleryMcpServerRemotes; - readonly _meta: { + readonly _meta?: { readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; }; } interface RawGalleryMcpServerInfo { readonly server: RawGalleryMcpServer; - readonly _meta: { - readonly 'io.modelcontextprotocol.registry/official': { + readonly _meta?: { + readonly 'io.modelcontextprotocol.registry/official'?: { readonly status: GalleryMcpServerStatus; readonly isLatest: boolean; readonly publishedAt: string; @@ -502,12 +517,25 @@ namespace McpServerSchemaVersion_v0_1 { } public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { - if (!input || typeof input !== 'object' || !(input).server) { + if (!input || typeof input !== 'object') { return undefined; } const from = input; + if ( + (!from.server || !isObject(from.server)) + || (!from.server.name || !isString(from.server.name)) + || (!from.server.description || !isString(from.server.description)) + || (!from.server.version || !isString(from.server.version)) + ) { + return undefined; + } + + if (from.server.$schema && from.server.$schema !== McpServerSchemaVersion_v0_1.SCHEMA) { + return undefined; + } + return { name: from.server.name, description: from.server.description, @@ -522,9 +550,9 @@ namespace McpServerSchemaVersion_v0_1 { websiteUrl: from.server.websiteUrl, packages: from.server.packages, remotes: from.server.remotes, - status: from._meta['io.modelcontextprotocol.registry/official'].status, - registryInfo: from._meta['io.modelcontextprotocol.registry/official'], - githubInfo: from.server._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, + status: from._meta?.['io.modelcontextprotocol.registry/official']?.status, + registryInfo: from._meta?.['io.modelcontextprotocol.registry/official'], + githubInfo: from.server._meta?.['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, }; } } @@ -532,6 +560,43 @@ namespace McpServerSchemaVersion_v0_1 { export const SERIALIZER = new Serializer(); } +namespace McpServerSchemaVersion_v0 { + + export const VERSION = 'v0'; + + class Serializer implements IGalleryMcpServerDataSerializer { + + private readonly galleryMcpServerDataSerializers: IGalleryMcpServerDataSerializer[] = []; + + constructor() { + this.galleryMcpServerDataSerializers.push(McpServerSchemaVersion_v0_1.SERIALIZER); + this.galleryMcpServerDataSerializers.push(McpServerSchemaVersion_v2025_07_09.SERIALIZER); + } + + public toRawGalleryMcpServerResult(input: unknown): IRawGalleryMcpServersResult | undefined { + for (const serializer of this.galleryMcpServerDataSerializers) { + const result = serializer.toRawGalleryMcpServerResult(input); + if (result) { + return result; + } + } + return undefined; + } + + public toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined { + for (const serializer of this.galleryMcpServerDataSerializers) { + const result = serializer.toRawGalleryMcpServer(input); + if (result) { + return result; + } + } + return undefined; + } + } + + export const SERIALIZER = new Serializer(); +} + const DefaultPageSize = 50; interface IQueryState { @@ -728,9 +793,9 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService description: server.description, status: server.status ?? GalleryMcpServerStatus.Active, version: server.version, - isLatest: server.registryInfo.isLatest, - publishDate: server.registryInfo.publishedAt ? Date.parse(server.registryInfo.publishedAt) : undefined, - lastUpdated: server.githubInfo?.pushed_at ? Date.parse(server.githubInfo.pushed_at) : server.registryInfo ? Date.parse(server.registryInfo.updatedAt) : undefined, + isLatest: server.registryInfo?.isLatest ?? true, + publishDate: server.registryInfo?.publishedAt ? Date.parse(server.registryInfo.publishedAt) : undefined, + lastUpdated: server.githubInfo?.pushed_at ? Date.parse(server.githubInfo.pushed_at) : server.registryInfo?.updatedAt ? Date.parse(server.registryInfo.updatedAt) : undefined, repositoryUrl: server.repository?.url, readme: server.repository?.readme, icon, From cc184d3e3279d7395b9da6aab5e4b43454f735d0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 20 Oct 2025 10:09:48 +0000 Subject: [PATCH 1317/4355] Preserve agent association when creating a new chat from within a chat (#272234) * Fix creatig new chat editor for a specific agent * Unify lockedAgent info into a single type * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../contrib/chat/browser/actions/chatClear.ts | 7 +- .../contrib/chat/browser/chatWidget.ts | 85 +++++++++++-------- 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts index 731ffe8e023..e3a802dbaf5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts @@ -7,6 +7,8 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { ChatSessionUri } from '../../common/chatUri.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; export async function clearChatEditor(accessor: ServicesAccessor, chatEditorInput?: ChatEditorInput): Promise { const editorService = accessor.get(IEditorService); @@ -17,11 +19,14 @@ export async function clearChatEditor(accessor: ServicesAccessor, chatEditorInpu } if (chatEditorInput instanceof ChatEditorInput) { + const parsedInfo = ChatSessionUri.parse(chatEditorInput.resource); + const resource = parsedInfo?.chatSessionType ? ChatSessionUri.forSession(parsedInfo.chatSessionType, `untitled-${generateUuid()}`) : ChatEditorInput.getNewEditorUri(); + // A chat editor can only be open in one group const identifier = editorService.findEditors(chatEditorInput.resource)[0]; await editorService.replaceEditors([{ editor: chatEditorInput, - replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions } + replacement: { resource, options: { pinned: true } satisfies IChatEditorOptions } }], identifier.groupId); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index cef0029a4ea..eb15198a9fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -357,15 +357,18 @@ export class ChatWidget extends Disposable implements IChatWidget { private _chatSuggestNextScheduler: RunOnceScheduler; // Coding agent locking state - private _lockedToCodingAgent: string | undefined; - private _lockedToCodingAgentContextKey!: IContextKey; - private _agentSupportsAttachmentsContextKey!: IContextKey; + private _lockedAgent?: { + id: string; + name: string; + prefix: string; + displayName: string; + }; + private readonly _lockedToCodingAgentContextKey: IContextKey; + private readonly _agentSupportsAttachmentsContextKey: IContextKey; private _supportsToolAttachments: boolean = true; private _supportsMCPAttachments: boolean = true; private _supportsFileAttachments: boolean = true; private _supportsImageAttachments: boolean = true; - private _codingAgentPrefix: string | undefined; - private _lockedAgentId: string | undefined; private lastWelcomeViewChatMode: ChatModeKind | undefined; @@ -423,7 +426,7 @@ export class ChatWidget extends Disposable implements IChatWidget { .parseChatRequest(this.viewModel!.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind, - forcedAgent: this._lockedAgentId ? this.chatAgentService.getAgent(this._lockedAgentId) : undefined + forcedAgent: this._lockedAgent?.id ? this.chatAgentService.getAgent(this._lockedAgent.id) : undefined }); this._onDidChangeParsedInput.fire(); } @@ -722,7 +725,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._supportsImageAttachments = true; // Check if the agent has capabilities defined directly - const capabilities = agent?.capabilities ?? (this._lockedAgentId ? this.chatSessionsService.getCapabilitiesForSessionType(this._lockedAgentId) : undefined); + const capabilities = agent?.capabilities ?? (this._lockedAgent ? this.chatSessionsService.getCapabilitiesForSessionType(this._lockedAgent.id) : undefined); if (capabilities) { this._supportsFileAttachments = capabilities.supportsFileAttachments ?? false; this._supportsToolAttachments = capabilities.supportsToolAttachments ?? false; @@ -965,8 +968,16 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this._dynamicMessageLayoutData) { this._dynamicMessageLayoutData.enabled = true; } - // Unlock coding agent when clearing - this.unlockFromCodingAgent(); + + if (this.viewModel) { + this.viewModel.resetInputPlaceholder(); + } + if (this._lockedAgent) { + this.lockToCodingAgent(this._lockedAgent.name, this._lockedAgent.displayName, this._lockedAgent.id); + } else { + this.unlockFromCodingAgent(); + } + this.clearTodoListWidget(this.viewModel?.sessionId); // Cancel any pending widget render and hide the widget BEFORE firing onDidClear // This prevents the widget from being re-shown by any handlers triggered by the clear event @@ -1100,10 +1111,10 @@ export class ChatWidget extends Disposable implements IChatWidget { const defaultTips = this.input.currentModeKind === ChatModeKind.Ask ? new MarkdownString(localize('chatWidget.tips', "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", '$(attach)', '#', '$(mention)', '/'), { supportThemeIcons: true }) : new MarkdownString(localize('chatWidget.tips.withoutParticipants', "{0} or type {1} to attach context", '$(attach)', '#'), { supportThemeIcons: true }); - const contributedTips = this._lockedAgentId ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgentId) : undefined; + const contributedTips = this._lockedAgent?.id ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; const tips = contributedTips ? new MarkdownString(contributedTips, { supportThemeIcons: true }) - : (!this._lockedAgentId ? defaultTips : undefined); + : (!this._lockedAgent ? defaultTips : undefined); welcomeContent = this.getWelcomeViewContent(additionalMessage); welcomeContent.tips = tips; } @@ -1116,7 +1127,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Optional: recent chat history above welcome content when enabled const showHistory = this.configurationService.getValue(ChatConfiguration.EmptyStateHistoryEnabled); - if (showHistory && !this._lockedToCodingAgent && this._historyVisible) { + if (showHistory && !this._lockedAgent && this._historyVisible) { this.renderWelcomeHistorySection(); } this.welcomePart.value = this.instantiationService.createInstance( @@ -1400,19 +1411,19 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this.isLockedToCodingAgent) { // Check for provider-specific customizations from chat sessions service - const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; - const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; - const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; + const providerIcon = this._lockedAgent ? this.chatSessionsService.getIconForSessionType(this._lockedAgent.id) : undefined; + const providerTitle = this._lockedAgent ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgent.id) : undefined; + const providerMessage = this._lockedAgent ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgent.id) : undefined; // Fallback to default messages if provider doesn't specify const message = providerMessage ? new MarkdownString(providerMessage) - : (this._codingAgentPrefix === '@copilot ' - ? new MarkdownString(localize('copilotCodingAgentMessage', "This chat session will be forwarded to the {0} [coding agent]({1}) where work is completed in the background. ", this._codingAgentPrefix, 'https://aka.ms/coding-agent-docs') + this.chatDisclaimer, { isTrusted: true }) - : new MarkdownString(localize('genericCodingAgentMessage', "This chat session will be forwarded to the {0} coding agent where work is completed in the background. ", this._codingAgentPrefix) + this.chatDisclaimer)); + : (this._lockedAgent?.prefix === '@copilot ' + ? new MarkdownString(localize('copilotCodingAgentMessage', "This chat session will be forwarded to the {0} [coding agent]({1}) where work is completed in the background. ", this._lockedAgent.prefix, 'https://aka.ms/coding-agent-docs') + this.chatDisclaimer, { isTrusted: true }) + : new MarkdownString(localize('genericCodingAgentMessage', "This chat session will be forwarded to the {0} coding agent where work is completed in the background. ", this._lockedAgent?.prefix) + this.chatDisclaimer)); return { - title: providerTitle ?? localize('codingAgentTitle', "Delegate to {0}", this._codingAgentPrefix), + title: providerTitle ?? localize('codingAgentTitle', "Delegate to {0}", this._lockedAgent?.prefix), message, icon: providerIcon ?? Codicon.sendToRemoteAgent, additionalMessage, @@ -1463,12 +1474,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } // Check for provider-specific customizations - const providerIcon = this._lockedAgentId ? this.chatSessionsService.getIconForSessionType(this._lockedAgentId) : undefined; - const providerTitle = this._lockedAgentId ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgentId) : undefined; - const providerMessage = this._lockedAgentId ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgentId) : undefined; - const providerTips = this._lockedAgentId ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgentId) : undefined; - const suggestedPrompts = this._lockedAgentId ? undefined : this.getNewSuggestedPrompts(); - + const providerIcon = this._lockedAgent?.id ? this.chatSessionsService.getIconForSessionType(this._lockedAgent.id) : undefined; + const providerTitle = this._lockedAgent ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgent.id) : undefined; + const providerMessage = this._lockedAgent ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgent.id) : undefined; + const providerTips = this._lockedAgent ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; + const suggestedPrompts = this._lockedAgent ? undefined : this.getNewSuggestedPrompts(); const welcomeContent: IChatViewWelcomeContent = { title: providerTitle ?? localize('expChatTitle', 'Build with agent mode'), message: providerMessage ? new MarkdownString(providerMessage) : new MarkdownString(localize('expchatMessage', "Let's get started")), @@ -2330,10 +2340,10 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container.setAttribute('data-session-id', model.sessionId); this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); - if (this._lockedAgentId) { - let placeholder = this._lockedAgentId ? this.chatSessionsService.getInputPlaceholderForSessionType(this._lockedAgentId) : undefined; + if (this._lockedAgent) { + let placeholder = this.chatSessionsService.getInputPlaceholderForSessionType(this._lockedAgent.id); if (!placeholder) { - placeholder = localize('chat.input.placeholder.lockedToAgent', "Chat with {0}", this._lockedAgentId); + placeholder = localize('chat.input.placeholder.lockedToAgent', "Chat with {0}", this._lockedAgent.id); } this.viewModel.setInputPlaceholder(placeholder); this.inputEditor.updateOptions({ placeholder }); @@ -2455,9 +2465,12 @@ export class ChatWidget extends Disposable implements IChatWidget { // Coding agent locking methods public lockToCodingAgent(name: string, displayName: string, agentId: string): void { - this._lockedToCodingAgent = displayName; - this._codingAgentPrefix = `@${name} `; - this._lockedAgentId = agentId; + this._lockedAgent = { + id: agentId, + name, + prefix: `@${name} `, + displayName + }; this._lockedToCodingAgentContextKey.set(true); this._welcomeRenderScheduler.schedule(); // Update capabilities for the locked agent @@ -2469,9 +2482,7 @@ export class ChatWidget extends Disposable implements IChatWidget { public unlockFromCodingAgent(): void { // Clear all state related to locking - this._lockedToCodingAgent = undefined; - this._codingAgentPrefix = undefined; - this._lockedAgentId = undefined; + this._lockedAgent = undefined; this._lockedToCodingAgentContextKey.set(false); this._updateAgentCapabilitiesContextKeys(undefined); @@ -2488,11 +2499,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } public get isLockedToCodingAgent(): boolean { - return !!this._lockedToCodingAgent; + return !!this._lockedAgent; } public get lockedAgentId(): string | undefined { - return this._lockedAgentId; + return this._lockedAgent?.id; } logInputHistory(): void { @@ -2684,7 +2695,7 @@ export class ChatWidget extends Disposable implements IChatWidget { noCommandDetection: options?.noCommandDetection, ...this.getModeRequestOptions(), modeInfo: this.input.currentModeInfo, - agentIdSilent: this._lockedAgentId + agentIdSilent: this._lockedAgent?.id, }); if (result) { From 806b0a4a8609b918e4c4634a65ab8f55737708bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 03:13:07 -0700 Subject: [PATCH 1318/4355] Don't imply appearance via style Position should be determined through the regular mechanism. Top is actually better generally as there's less change it will be obscured by the cursor. Part of #243348 Part of #270314 --- src/vs/editor/browser/services/hoverService/hoverWidget.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 3696dd6561f..6e393602895 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -112,8 +112,6 @@ export class HoverWidget extends Widget implements IHoverWidget { options.appearance ??= {}; options.appearance.compact ??= true; options.appearance.showPointer ??= true; - options.position ??= {}; - options.position.hoverPosition ??= HoverPosition.BELOW; break; } case HoverStyle.Mouse: { From 69d1cbe07c62a20a37558085a42c2108df3fc0b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 03:20:05 -0700 Subject: [PATCH 1319/4355] Adopt HoverStyle.Pointer in a few places Part of #243348 --- .../contrib/rename/browser/renameWidget.ts | 6 ++---- .../browser/parts/compositeBarActions.ts | 6 ++---- .../chat/browser/chatAttachmentWidgets.ts | 17 ++++++++++------- .../chatAgentCommandContentPart.ts | 11 +++++++++-- .../chatContentParts/chatMarkdownContentPart.ts | 15 +++++++-------- .../chatTerminalToolConfirmationSubPart.ts | 3 ++- .../chatSessions/view/sessionsTreeRenderer.ts | 7 ++++--- .../browser/settingsEditorSettingIndicators.ts | 12 +++--------- 8 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index f2bc9d613f2..8a9e22a6602 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -45,6 +45,7 @@ import { widgetShadow } from '../../../../platform/theme/common/colorRegistry.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { HoverStyle } from '../../../../base/browser/ui/hover/hover.js'; /** for debugging */ const _sticky = false @@ -935,10 +936,7 @@ class InputWithButton implements IDisposable { this._buttonHoverContent = this._buttonGenHoverText; this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ content: this._buttonHoverContent, - appearance: { - showPointer: true, - compact: true, - } + style: HoverStyle.Pointer, }))); this._domNode.appendChild(this._buttonNode); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index f0aa6f62f1d..337ec9a0d4e 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -29,6 +29,7 @@ import { Action2, IAction2Options } from '../../../platform/actions/common/actio import { ViewContainerLocation } from '../../common/views.js'; import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js'; import { createConfigureKeybindingAction } from '../../../platform/actions/common/menuService.js'; +import { HoverStyle } from '../../../base/browser/ui/hover/hover.js'; export interface ICompositeBar { @@ -260,16 +261,13 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this._register(this.hoverService.setupDelayedHover(this.container, () => ({ content: this.computeTitle(), + style: HoverStyle.Pointer, position: { hoverPosition: this.options.hoverOptions.position(), }, persistence: { hideOnKeyDown: true, }, - appearance: { - showPointer: true, - compact: true, - } }), { groupId: 'composite-bar-actions' })); // Label diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 2d63ad8b8a5..d93d47517ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -8,7 +8,7 @@ import { $ } from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; -import type { IHoverLifecycleOptions, IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import { HoverStyle, type IHoverLifecycleOptions, type IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Codicon } from '../../../../base/common/codicons.js'; @@ -59,10 +59,7 @@ import { ILanguageModelToolsService, ToolSet } from '../common/languageModelTool import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; const commonHoverOptions: Partial = { - appearance: { - compact: true, - showPointer: true, - }, + style: HoverStyle.Pointer, position: { hoverPosition: HoverPosition.BELOW }, @@ -336,9 +333,15 @@ function createImageElements(resource: URI | undefined, name: string, fullName: if ((!supportsVision && currentLanguageModel) || omittedState === OmittedState.Full) { element.classList.add('warning'); hoverElement.textContent = localize('chat.imageAttachmentHover', "{0} does not support images.", currentLanguageModelName ?? 'This model'); - disposable.add(hoverService.setupDelayedHover(element, { content: hoverElement, appearance: { showPointer: true } })); + disposable.add(hoverService.setupDelayedHover(element, { + content: hoverElement, + style: HoverStyle.Pointer, + })); } else { - disposable.add(hoverService.setupDelayedHover(element, { content: hoverElement, appearance: { showPointer: true } })); + disposable.add(hoverService.setupDelayedHover(element, { + content: hoverElement, + style: HoverStyle.Pointer, + })); const blob = new Blob([buffer as Uint8Array], { type: 'image/png' }); const url = URL.createObjectURL(blob); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAgentCommandContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAgentCommandContentPart.ts index 5f3e0a44fb7..cbc8518b7c4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAgentCommandContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAgentCommandContentPart.ts @@ -15,6 +15,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { localize } from '../../../../../nls.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; export class ChatAgentCommandContentPart extends Disposable implements IChatContentPart { @@ -36,14 +37,20 @@ export class ChatAgentCommandContentPart extends Disposable implements IChatCont const commandSpan = document.createElement('span'); this.domNode.appendChild(commandSpan); commandSpan.innerText = chatSubcommandLeader + cmd.name; - this._store.add(this._hoverService.setupDelayedHover(commandSpan, { content: cmd.description, appearance: { showPointer: true } }, { groupId })); + this._store.add(this._hoverService.setupDelayedHover(commandSpan, { + content: cmd.description, + style: HoverStyle.Pointer, + }, { groupId })); const rerun = localize('rerun', "Rerun without {0}{1}", chatSubcommandLeader, cmd.name); const btn = new Button(this.domNode, { ariaLabel: rerun }); btn.icon = Codicon.close; this._store.add(btn.onDidClick(() => onClick())); this._store.add(btn); - this._store.add(this._hoverService.setupDelayedHover(btn.element, { content: rerun, appearance: { showPointer: true } }, { groupId })); + this._store.add(this._hoverService.setupDelayedHover(btn.element, { + content: rerun, + style: HoverStyle.Pointer, + }, { groupId })); } hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 3a92acc4922..7e34977acaf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -59,6 +59,7 @@ import { IDisposableReference, ResourcePool } from './chatCollections.js'; import { IChatContentPart, IChatContentPartRenderContext } from './chatContentParts.js'; import { ChatExtensionsContentPart } from './chatExtensionsContentPart.js'; import { IOpenEditorOptions, registerOpenEditorListeners } from '../../../../../platform/editor/browser/editor.js'; +import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; const $ = dom.$; @@ -584,14 +585,12 @@ export class CollapsedCodeBlock extends Disposable { this.tooltip = tooltip; if (!this.hover.value) { - this.hover.value = this.hoverService.setupDelayedHover(this.element, () => ( - { - content: this.tooltip!, - appearance: { compact: true, showPointer: true }, - position: { hoverPosition: HoverPosition.BELOW }, - persistence: { hideOnKeyDown: true }, - } - )); + this.hover.value = this.hoverService.setupDelayedHover(this.element, () => ({ + content: this.tooltip!, + style: HoverStyle.Pointer, + position: { hoverPosition: HoverPosition.BELOW }, + persistence: { hideOnKeyDown: true }, + })); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 9baa67e2329..ef4e9b60a94 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -42,6 +42,7 @@ import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; +import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; export const enum TerminalToolConfirmationStorageKeys { TerminalAutoApproveWarningAccepted = 'chat.tools.terminal.autoApprove.warningAccepted' @@ -184,8 +185,8 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS append(elements.editor, editor.object.element); this._register(hoverService.setupDelayedHover(elements.editor, { content: message, + style: HoverStyle.Pointer, position: { hoverPosition: HoverPosition.LEFT }, - appearance: { showPointer: true }, })); const confirmWidget = this._register(this.instantiationService.createInstance( ChatCustomConfirmationWidget, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index afa70133eed..3ea4e046fd5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -48,6 +48,7 @@ import { ChatSessionTracker } from '../chatSessionTracker.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; import { ChatSessionUri } from '../../../common/chatUri.js'; +import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; interface ISessionTemplateData { readonly container: HTMLElement; @@ -282,7 +283,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer ({ content: tooltipContent, - appearance: { showPointer: true }, + style: HoverStyle.Pointer, position: { hoverPosition: this.getHoverPosition() } }), { groupId: 'chat.sessions' }) ); @@ -290,7 +291,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer ({ content: tooltipContent.markdown, - appearance: { showPointer: true }, + style: HoverStyle.Pointer, position: { hoverPosition: this.getHoverPosition() } }), { groupId: 'chat.sessions' }) ); @@ -311,7 +312,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer ({ content: nls.localize('chat.sessions.lastActivity', 'Last Activity: {0}', fullDateTime), - appearance: { showPointer: true }, + style: HoverStyle.Pointer, position: { hoverPosition: this.getHoverPosition() } }), { groupId: 'chat.sessions' }) ); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 639cc8f5d28..50ce47140b2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -5,7 +5,7 @@ import * as DOM from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import type { IHoverOptions, IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; +import { HoverStyle, type IHoverOptions, type IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { SimpleIconLabel } from '../../../../base/browser/ui/iconLabel/simpleIconLabel.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; @@ -100,13 +100,10 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private defaultHoverOptions: Partial = { trapFocus: true, + style: HoverStyle.Pointer, position: { hoverPosition: HoverPosition.BELOW, }, - appearance: { - showPointer: true, - compact: false, - } }; private addHoverDisposables(disposables: DisposableStore, element: HTMLElement, showHover: (focus: boolean) => IHoverWidget | undefined) { @@ -515,13 +512,10 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { return this.hoverService.showInstantHover({ content: new MarkdownString().appendMarkdown(defaultOverrideHoverContent), target: this.defaultOverrideIndicator.element, + style: HoverStyle.Pointer, position: { hoverPosition: HoverPosition.BELOW, }, - appearance: { - showPointer: true, - compact: false - } }, focus); }; this.addHoverDisposables(this.defaultOverrideIndicator.disposables, this.defaultOverrideIndicator.element, showHover); From d568dbbeff7e3c847a886ff61716dd667fff0347 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:55:16 +0200 Subject: [PATCH 1320/4355] SCM - cancel Graph view refresh when view is disposed (#272246) --- src/vs/base/common/async.ts | 23 +++++++++++-------- .../contrib/scm/browser/scmHistoryViewPane.ts | 15 ++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 5e4cd533c9a..7f44d84b8c4 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -191,6 +191,10 @@ export interface ITask { (): T; } +export interface ICancellableTask { + (token: CancellationToken): T; +} + /** * A helper to prevent accumulation of sequential async tasks. * @@ -221,18 +225,19 @@ export class Throttler implements IDisposable { private activePromise: Promise | null; private queuedPromise: Promise | null; - private queuedPromiseFactory: ITask> | null; - - private isDisposed = false; + private queuedPromiseFactory: ICancellableTask> | null; + private cancellationTokenSource: CancellationTokenSource; constructor() { this.activePromise = null; this.queuedPromise = null; this.queuedPromiseFactory = null; + + this.cancellationTokenSource = new CancellationTokenSource(); } - queue(promiseFactory: ITask>): Promise { - if (this.isDisposed) { + queue(promiseFactory: ICancellableTask>): Promise { + if (this.cancellationTokenSource.token.isCancellationRequested) { return Promise.reject(new Error('Throttler is disposed')); } @@ -243,7 +248,7 @@ export class Throttler implements IDisposable { const onComplete = () => { this.queuedPromise = null; - if (this.isDisposed) { + if (this.cancellationTokenSource.token.isCancellationRequested) { return; } @@ -263,7 +268,7 @@ export class Throttler implements IDisposable { }); } - this.activePromise = promiseFactory(); + this.activePromise = promiseFactory(this.cancellationTokenSource.token); return new Promise((resolve, reject) => { this.activePromise!.then((result: T) => { @@ -277,7 +282,7 @@ export class Throttler implements IDisposable { } dispose(): void { - this.isDisposed = true; + this.cancellationTokenSource.cancel(); } } @@ -458,7 +463,7 @@ export class ThrottledDelayer { this.throttler = new Throttler(); } - trigger(promiseFactory: ITask>, delay?: number): Promise { + trigger(promiseFactory: ICancellableTask>, delay?: number): Promise { return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index cec76c3a1a0..8ca4b2be266 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -75,6 +75,7 @@ import { IDragAndDropData } from '../../../../base/browser/dnd.js'; import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; import { CodeDataTransfers } from '../../../../platform/dnd/browser/dnd.js'; import { SCMHistoryItemTransferData } from './scmHistoryChatContext.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; const PICK_REPOSITORY_ACTION_ID = 'workbench.scm.action.graph.pickRepository'; const PICK_HISTORY_ITEM_REFS_ACTION_ID = 'workbench.scm.action.graph.pickHistoryItemRefs'; @@ -1521,6 +1522,7 @@ export class SCMHistoryViewPane extends ViewPane { private readonly _treeOperationSequencer = new Sequencer(); private readonly _treeLoadMoreSequencer = new Sequencer(); + private readonly _refreshThrottler = new Throttler(); private readonly _updateChildrenThrottler = new Throttler(); private readonly _scmProviderCtx: IContextKey; @@ -1558,6 +1560,7 @@ export class SCMHistoryViewPane extends ViewPane { this._actionRunner = this.instantiationService.createInstance(SCMHistoryViewPaneActionRunner); this._register(this._actionRunner); + this._register(this._refreshThrottler); this._register(this._updateChildrenThrottler); } @@ -1753,9 +1756,21 @@ export class SCMHistoryViewPane extends ViewPane { } async refresh(): Promise { + return this._refreshThrottler.queue(token => this._refresh(token)); + } + + private async _refresh(token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return; + } + this._treeViewModel.clearRepositoryState(); await this._updateChildren(); + if (token.isCancellationRequested) { + return; + } + this.updateActions(); this._repositoryOutdated.set(false, undefined); this._tree.scrollTop = 0; From 795c809554e5e21d1192652c7f9d513fb8f7f22b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Oct 2025 12:56:00 +0200 Subject: [PATCH 1321/4355] stricter validation for remote mcp servers - #272247 (#272249) --- .../contrib/mcp/browser/mcpServerActions.ts | 7 +- .../contrib/mcp/browser/mcpServersView.ts | 4 +- .../mcp/browser/mcpWorkbenchService.ts | 110 +++++++++++------- .../workbench/contrib/mcp/common/mcpTypes.ts | 14 ++- 4 files changed, 84 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts index 3c759b6dc9c..10f62eb9c83 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts @@ -36,6 +36,7 @@ import { LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchMa import { ExtensionAction } from '../../extensions/browser/extensionsActions.js'; import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js'; +import Severity from '../../../../base/common/severity.js'; export interface IMcpServerActionChangeEvent extends IActionChangeEvent { readonly hidden?: boolean; @@ -1158,9 +1159,9 @@ export class McpServerStatusAction extends McpServerAction { } } - const runtimeState = this.mcpServer.runtimeState; - if (runtimeState?.disabled && runtimeState.reason) { - this.updateStatus({ icon: warningIcon, message: runtimeState.reason }, true); + const runtimeState = this.mcpServer.runtimeStatus; + if (runtimeState?.message) { + this.updateStatus({ icon: runtimeState.message.severity === Severity.Warning ? warningIcon : runtimeState.message.severity === Severity.Error ? errorIcon : infoIcon, message: runtimeState.message.text }, true); } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 0a5e67fd61f..51842d678ce 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -26,7 +26,7 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js'; import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js'; import { IViewDescriptorService, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; -import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; +import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServerEnablementState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; import { DropDownAction, getContextMenuActions, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js'; import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js'; import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; @@ -484,7 +484,7 @@ class McpServerRenderer implements IPagedRenderer data.root.classList.toggle('disabled', !!mcpServer.runtimeState?.disabled); + const updateEnablement = () => data.root.classList.toggle('disabled', !!mcpServer.runtimeStatus?.state && mcpServer.runtimeStatus.state !== McpServerEnablementState.Enabled); updateEnablement(); data.mcpServerDisposables.push(this.mcpWorkbenchService.onChange(e => { if (!e || e.id === mcpServer.id) { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index 22d68e6d88f..e4f66303f51 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -36,12 +36,13 @@ import { DidUninstallWorkbenchMcpServerEvent, IWorkbenchLocalMcpServer, IWorkben import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; import { mcpConfigurationSection } from '../common/mcpConfiguration.js'; import { McpServerInstallData, McpServerInstallClassification } from '../common/mcpServer.js'; -import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerEnablementState, McpServerInstallState, McpServerRuntimeState, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; +import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerEnablementState, McpServerInstallState, McpServerEnablementStatus, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { IMcpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; import { IPager, singlePagePager } from '../../../../base/common/paging.js'; import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { runOnChange } from '../../../../base/common/observable.js'; +import Severity from '../../../../base/common/severity.js'; interface IMcpServerStateProvider { (mcpWorkbenchServer: McpWorkbenchServer): T; @@ -51,7 +52,7 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { constructor( private installStateProvider: IMcpServerStateProvider, - private runtimeStateProvider: IMcpServerStateProvider, + private runtimeStateProvider: IMcpServerStateProvider, public local: IWorkbenchLocalMcpServer | undefined, public gallery: IGalleryMcpServer | undefined, public readonly installable: IInstallableMcpServer | undefined, @@ -116,7 +117,7 @@ class McpWorkbenchServer implements IWorkbenchMcpServer { return this.local?.config ?? this.installable?.config; } - get runtimeState(): McpServerRuntimeState | undefined { + get runtimeStatus(): McpServerEnablementStatus | undefined { return this.runtimeStateProvider(this); } @@ -204,7 +205,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this._onChange.fire(undefined); } })); - this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifest(e => this.syncInstalledMcpServers(true))); + this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifest(e => this.syncInstalledMcpServers())); this._register(this.allowedMcpServersService.onDidChangeAllowedMcpServers(() => { this._local = this.sort(this._local); this._onChange.fire(undefined); @@ -251,7 +252,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (server) { server.local = local; } else { - server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), local, source, undefined); + server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), local, source, undefined); } if (!local.galleryUrl) { server.gallery = undefined; @@ -277,7 +278,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this._local[serverIndex].local = result.local; server = this._local[serverIndex]; } else { - server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), result.local, result.source, undefined); + server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), result.local, result.source, undefined); this.addServer(server); } this._onChange.fire(server); @@ -294,7 +295,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return undefined; } - private async syncInstalledMcpServers(resetGallery?: boolean): Promise { + private async syncInstalledMcpServers(): Promise { const names: string[] = []; for (const installed of this.local) { @@ -308,30 +309,30 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (names.length) { const galleryServers = await this.mcpGalleryService.getMcpServersFromGallery(names); - if (galleryServers.length) { - await this.syncInstalledMcpServersWithGallery(galleryServers, resetGallery); - } + await this.syncInstalledMcpServersWithGallery(galleryServers); } } - private async syncInstalledMcpServersWithGallery(gallery: IGalleryMcpServer[], resetGallery?: boolean): Promise { + private async syncInstalledMcpServersWithGallery(gallery: IGalleryMcpServer[]): Promise { const galleryMap = new Map(gallery.map(server => [server.name, server])); for (const mcpServer of this.local) { if (!mcpServer.local) { continue; } const key = mcpServer.local.name; - const galleryServer = key ? galleryMap.get(key) : undefined; - if (!galleryServer) { - if (mcpServer.gallery && resetGallery) { + const gallery = key ? galleryMap.get(key) : undefined; + + if (!gallery || gallery.galleryUrl !== mcpServer.local.galleryUrl) { + if (mcpServer.gallery) { mcpServer.gallery = undefined; this._onChange.fire(mcpServer); } continue; } - mcpServer.gallery = galleryServer; + + mcpServer.gallery = gallery; if (!mcpServer.local.manifest) { - mcpServer.local = await this.mcpManagementService.updateMetadata(mcpServer.local, galleryServer); + mcpServer.local = await this.mcpManagementService.updateMetadata(mcpServer.local, gallery); } this._onChange.fire(mcpServer); } @@ -343,12 +344,12 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } const pager = await this.mcpGalleryService.query(options, token); return { - firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined)), + firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined)), total: pager.total, pageSize: pager.pageSize, getPage: async (pageIndex, token) => { const page = await pager.getPage(pageIndex, token); - return page.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined)); + return page.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined)); } }; } @@ -357,7 +358,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ const installed = await this.mcpManagementService.getInstalled(); this._local = this.sort(installed.map(i => { const existing = this._local.find(local => local.id === i.id); - const local = existing ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, undefined, undefined); + const local = existing ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, undefined, undefined); local.local = i; return local; })); @@ -373,10 +374,10 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ private sort(local: McpWorkbenchServer[]): McpWorkbenchServer[] { return local.sort((a, b) => { if (a.name === b.name) { - if (!a.runtimeState?.disabled) { + if (!a.runtimeStatus || a.runtimeStatus.state === McpServerEnablementState.Enabled) { return -1; } - if (!b.runtimeState?.disabled) { + if (!b.runtimeStatus || b.runtimeStatus.state === McpServerEnablementState.Enabled) { return 1; } return 0; @@ -391,7 +392,8 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ const workspace: IWorkbenchLocalMcpServer[] = []; for (const server of this.local) { - if (this.getEnablementState(server) !== McpServerEnablementState.Enabled) { + const enablementStatus = this.getEnablementStatus(server); + if (enablementStatus && enablementStatus.state !== McpServerEnablementState.Enabled) { continue; } @@ -661,7 +663,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ if (config.type === undefined) { (>config).type = (parsed).command ? McpServerType.LOCAL : McpServerType.REMOTE; } - this.open(this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, undefined, { name, config, inputs })); + this.open(this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, undefined, { name, config, inputs })); } catch (e) { // ignore } @@ -675,7 +677,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this.logService.info(`MCP server '${url}' not found`); return true; } - const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore @@ -691,7 +693,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this.logService.info(`MCP server '${name}' not found`); return true; } - const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeState(e), undefined, gallery, undefined); + const local = this.local.find(e => e.name === gallery.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined); this.open(local); } catch (e) { // ignore @@ -719,41 +721,63 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ return local ? McpServerInstallState.Installed : McpServerInstallState.Uninstalled; } - private getRuntimeState(mcpServer: McpWorkbenchServer): McpServerRuntimeState | undefined { - if (!mcpServer.local) { - return undefined; - } + private getRuntimeStatus(mcpServer: McpWorkbenchServer): McpServerEnablementStatus | undefined { + const enablementStatus = this.getEnablementStatus(mcpServer); - const accessValue = this.configurationService.getValue(mcpAccessConfig); - const settingsCommandLink = createCommandUri('workbench.action.openSettings', { query: `@id:${mcpAccessConfig}` }).toString(); - if (accessValue === McpAccessValue.None) { - return { disabled: true, reason: new MarkdownString(localize('disabled - all not allowed', "This MCP Server is disabled because MCP servers are configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }; - } - if (accessValue === McpAccessValue.Registry && !mcpServer.gallery) { - return { disabled: true, reason: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }; + if (enablementStatus) { + return enablementStatus; } if (!this.mcpService.servers.get().find(s => s.definition.id === mcpServer.id)) { - return { disabled: true }; + return { state: McpServerEnablementState.Disabled }; } return undefined; } - private getEnablementState(mcpServer: McpWorkbenchServer): McpServerEnablementState { + private getEnablementStatus(mcpServer: McpWorkbenchServer): McpServerEnablementStatus | undefined { if (!mcpServer.local) { - return McpServerEnablementState.Enabled; + return undefined; } + const settingsCommandLink = createCommandUri('workbench.action.openSettings', { query: `@id:${mcpAccessConfig}` }).toString(); const accessValue = this.configurationService.getValue(mcpAccessConfig); + if (accessValue === McpAccessValue.None) { - return McpServerEnablementState.DisabledByAccess; + return { + state: McpServerEnablementState.DisabledByAccess, + message: { + severity: Severity.Warning, + text: new MarkdownString(localize('disabled - all not allowed', "This MCP Server is disabled because MCP servers are configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) + } + }; + } - if (accessValue === McpAccessValue.Registry && !mcpServer.gallery) { - return McpServerEnablementState.DisabledByAccess; + + if (accessValue === McpAccessValue.Registry) { + if (!mcpServer.gallery) { + return { + state: McpServerEnablementState.DisabledByAccess, + message: { + severity: Severity.Warning, + text: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) + } + }; + } + + const remoteUrl = mcpServer.local.config.type === McpServerType.REMOTE && mcpServer.local.config.url; + if (remoteUrl && !mcpServer.gallery.configuration.remotes?.some(remote => remote.url === remoteUrl)) { + return { + state: McpServerEnablementState.DisabledByAccess, + message: { + severity: Severity.Warning, + text: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) + } + }; + } } - return McpServerEnablementState.Enabled; + return undefined; } } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index ca79e79d5ec..7bc9dadc0f4 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -8,11 +8,12 @@ import { assertNever } from '../../../../base/common/assert.js'; import { decodeHex, encodeHex, VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; -import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { equals as objectsEqual } from '../../../../base/common/objects.js'; import { IObservable, ObservableMap } from '../../../../base/common/observable.js'; import { IPager } from '../../../../base/common/paging.js'; +import Severity from '../../../../base/common/severity.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { Location } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; @@ -679,6 +680,7 @@ export interface IMcpServerEditorOptions extends IEditorOptions { } export const enum McpServerEnablementState { + Disabled, DisabledByAccess, Enabled, } @@ -696,14 +698,20 @@ export const enum McpServerEditorTab { Configuration = 'configuration', } -export type McpServerRuntimeState = { readonly disabled: boolean; readonly reason?: MarkdownString }; +export type McpServerEnablementStatus = { + readonly state: McpServerEnablementState; + readonly message?: { + readonly severity: Severity; + readonly text: IMarkdownString; + }; +}; export interface IWorkbenchMcpServer { readonly gallery: IGalleryMcpServer | undefined; readonly local: IWorkbenchLocalMcpServer | undefined; readonly installable: IInstallableMcpServer | undefined; readonly installState: McpServerInstallState; - readonly runtimeState: McpServerRuntimeState | undefined; + readonly runtimeStatus: McpServerEnablementStatus | undefined; readonly id: string; readonly name: string; readonly label: string; From ec115fccf19c6dedb9568d8e54925ef1fe8b6e95 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 04:04:04 -0700 Subject: [PATCH 1322/4355] Don't present command line rule if it was denied Fixes #272251 --- .../browser/runInTerminalHelpers.ts | 4 +++- .../test/browser/runInTerminalTool.test.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 153a231f5c1..154d27f2af0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -134,7 +134,9 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str if ( firstSubcommandFirstWord !== commandLine && !commandsWithSubcommands.has(commandLine) && - !commandsWithSubSubCommands.has(commandLine) + !commandsWithSubSubCommands.has(commandLine) && + autoApproveResult.commandLineResult.result !== 'denied' && + autoApproveResult.subCommandResults.every(e => e.result !== 'denied') ) { actions.push({ label: localize('autoApprove.exactCommand', 'Always Allow Exact Command Line'), diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 671a513798b..4b0f8976ab4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -826,6 +826,22 @@ suite('RunInTerminalTool', () => { 'configure', ]); }); + + test('should not show command line option when it\'s rejected', async () => { + setAutoApprove({ + echo: true, + '/\\(.+\\)/s': { approve: false, matchCommandLine: true } + }); + + const result = await executeToolTest({ + command: 'echo (abc)' + }); + + assertConfirmationRequired(result); + assertDropdownActions(result, [ + 'configure', + ]); + }); }); suite('chat session disposal cleanup', () => { From 2eec8929ea4006220144b95a803a7868810e1643 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:28:59 +0200 Subject: [PATCH 1323/4355] SCM - cleanup `scm.repositories.selectionMode` setting management (#272221) * SCM - cleanup `scm.repositories.selectionMode` setting management * Remove unused import --- .../scm/browser/scmRepositoriesViewPane.ts | 5 ++--- .../scm/browser/scmRepositoryRenderer.ts | 12 +++-------- .../contrib/scm/browser/scmViewService.ts | 21 +++++++++---------- src/vs/workbench/contrib/scm/common/scm.ts | 1 + 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index b689e9d9fbe..4b54e88b639 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -192,7 +192,6 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.treeDataSource); const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); - const selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, @@ -217,7 +216,7 @@ export class SCMRepositoriesViewPane extends ViewPane { }, compressionEnabled: compressionEnabled.get(), overrideStyles: this.getLocationBasedColors().listOverrideStyles, - multipleSelectionSupport: selectionModeConfig.get() === 'multiple', + multipleSelectionSupport: this.scmViewService.selectionModeConfig.get() === 'multiple', expandOnDoubleClick: false, expandOnlyOnTwistieClick: true, accessibilityProvider: { @@ -233,7 +232,7 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.tree); this._register(autorun(reader => { - const selectionMode = selectionModeConfig.read(reader); + const selectionMode = this.scmViewService.selectionModeConfig.read(reader); this.tree.updateOptions({ multipleSelectionSupport: selectionMode === 'multiple' }); })); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 90bb220a5fd..5cc2ffe1e60 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -5,7 +5,7 @@ import './media/scm.css'; import { IDisposable, DisposableStore, combinedDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, IObservable } from '../../../../base/common/observable.js'; +import { autorun } from '../../../../base/common/observable.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { ISCMProvider, ISCMRepository, ISCMViewService } from '../common/scm.js'; import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; @@ -25,8 +25,6 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -65,21 +63,17 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer; constructor( private readonly toolbarMenuId: MenuId, private readonly actionViewItemProvider: IActionViewItemProvider, @ICommandService private commandService: ICommandService, - @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private contextKeyService: IContextKeyService, @IContextMenuService private contextMenuService: IContextMenuService, @IKeybindingService private keybindingService: IKeybindingService, @IMenuService private menuService: IMenuService, @ISCMViewService private scmViewService: ISCMViewService, @ITelemetryService private telemetryService: ITelemetryService - ) { - this._selectionModeConfig = observableConfigValue('scm.repositories.selectionMode', 'single', this.configurationService); - } + ) { } renderTemplate(container: HTMLElement): RepositoryTemplate { // hack @@ -105,7 +99,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer { - const selectionMode = this._selectionModeConfig.read(undefined); + const selectionMode = this.scmViewService.selectionModeConfig.read(undefined); const activeRepository = this.scmViewService.activeRepository.read(reader); const icon = selectionMode === 'single' diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 551ab1b493d..2e5b4c2cbd2 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -112,6 +112,7 @@ export class SCMViewService implements ISCMViewService { declare readonly _serviceBrand: undefined; readonly menus: ISCMMenus; + readonly selectionModeConfig: IObservable<'multiple' | 'single'>; private didFinishLoading: boolean = false; private didSelectRepository: boolean = false; @@ -224,8 +225,6 @@ export class SCMViewService implements ISCMViewService { private readonly _activeRepositoryPinnedObs: ISettableObservable; private readonly _focusedRepositoryObs: IObservable; - private readonly _selectionModeConfig: IObservable<'multiple' | 'single'>; - private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; @@ -243,14 +242,14 @@ export class SCMViewService implements ISCMViewService { ) { this.menus = instantiationService.createInstance(SCMMenus); - this._selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); + this.selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); try { this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); // If previously there were multiple visible repositories but the // view mode is `single`, only restore the first visible repository. - if (this.previousState && this.previousState.visible.length > 1 && this._selectionModeConfig.get() === 'single') { + if (this.previousState && this.previousState.visible.length > 1 && this.selectionModeConfig.get() === 'single') { this.previousState = { ...this.previousState, visible: [this.previousState.visible[0]] @@ -307,7 +306,7 @@ export class SCMViewService implements ISCMViewService { }); this.disposables.add(autorun(reader => { - const selectionMode = this._selectionModeConfig.read(undefined); + const selectionMode = this.selectionModeConfig.read(undefined); const activeRepository = this.activeRepository.read(reader); if (selectionMode === 'single' && activeRepository) { @@ -322,7 +321,7 @@ export class SCMViewService implements ISCMViewService { } })); - this.disposables.add(runOnChange(this._selectionModeConfig, selectionMode => { + this.disposables.add(runOnChange(this.selectionModeConfig, selectionMode => { if (selectionMode === 'single' && this.visibleRepositories.length > 1) { const repository = this.visibleRepositories[0]; this.visibleRepositories = [repository]; @@ -377,7 +376,7 @@ export class SCMViewService implements ISCMViewService { this.insertRepositoryView(this._repositories, repositoryView); - if (this._selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + if (this.selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { // Multiple selection mode or single selection mode (select first repository) this._repositories.forEach((repositoryView, index) => { if (repositoryView.selectionIndex === -1) { @@ -414,14 +413,14 @@ export class SCMViewService implements ISCMViewService { } } - if (this._selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + if (this.selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { // Multiple selection mode or single selection mode (select first repository) const maxSelectionIndex = this.getMaxSelectionIndex(); this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed }); // Pin repository if needed - if (this._selectionModeConfig.get() === 'single' && this.previousState?.pinned && this.didSelectRepository) { + if (this.selectionModeConfig.get() === 'single' && this.previousState?.pinned && this.didSelectRepository) { this.pinActiveRepository(repository); } } else { @@ -485,9 +484,9 @@ export class SCMViewService implements ISCMViewService { } if (visible) { - if (this._selectionModeConfig.get() === 'single') { + if (this.selectionModeConfig.get() === 'single') { this.visibleRepositories = [repository]; - } else if (this._selectionModeConfig.get() === 'multiple') { + } else if (this.selectionModeConfig.get() === 'multiple') { this.visibleRepositories = [...this.visibleRepositories, repository]; } } else { diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 5bcc5e638f4..cb50e484dd0 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -221,6 +221,7 @@ export interface ISCMViewService { readonly _serviceBrand: undefined; readonly menus: ISCMMenus; + readonly selectionModeConfig: IObservable<'multiple' | 'single'>; repositories: ISCMRepository[]; readonly onDidChangeRepositories: Event; From 3925c196a5db04e194670425511615aff02efeab Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 20 Oct 2025 13:29:16 +0200 Subject: [PATCH 1324/4355] Leaked Disposable in promptValidator (#272255) * Leaked Disposable in promptValidator * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../languageProviders/promptValidator.ts | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 5e388a31aff..b3462c6d400 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -414,33 +414,26 @@ export class PromptValidatorContribution extends Disposable { const trackers = new ResourceMap(); this.localDisposables.add(toDisposable(() => { trackers.forEach(tracker => tracker.dispose()); + trackers.clear(); })); + this.modelService.getModels().forEach(model => { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (promptType) { + trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); + } + }); - const validateAllDelayer = this._register(new Delayer(200)); - const validateAll = (): void => { - validateAllDelayer.trigger(async () => { - this.modelService.getModels().forEach(model => { - const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); - if (promptType) { - trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); - } - }); - }); - }; this.localDisposables.add(this.modelService.onModelAdded((model) => { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); - if (promptType) { + if (promptType && !trackers.has(model.uri)) { trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } })); this.localDisposables.add(this.modelService.onModelRemoved((model) => { - const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); - if (promptType) { - const tracker = trackers.get(model.uri); - if (tracker) { - tracker.dispose(); - trackers.delete(model.uri); - } + const tracker = trackers.get(model.uri); + if (tracker) { + tracker.dispose(); + trackers.delete(model.uri); } })); this.localDisposables.add(this.modelService.onModelLanguageChanged((event) => { @@ -455,10 +448,11 @@ export class PromptValidatorContribution extends Disposable { trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService)); } })); + + const validateAll = (): void => trackers.forEach(tracker => tracker.validate()); this.localDisposables.add(this.languageModelToolsService.onDidChangeTools(() => validateAll())); this.localDisposables.add(this.chatModeService.onDidChangeChatModes(() => validateAll())); this.localDisposables.add(this.languageModelsService.onDidChangeLanguageModels(() => validateAll())); - validateAll(); } } @@ -479,7 +473,7 @@ class ModelTracker extends Disposable { this.validate(); } - private validate(): void { + public validate(): void { this.delayer.trigger(async () => { const markers: IMarkerData[] = []; const ast = this.promptsService.getParsedPromptFile(this.textModel); From 67961abc03b1ecca2756ac02bd650ac0502d2d87 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 20 Oct 2025 14:00:36 +0200 Subject: [PATCH 1325/4355] Fixes https://github.com/microsoft/vscode/issues/272152 (#272261) --- .../editContext/native/nativeEditContext.ts | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 5d10e6e9f8a..b417161930f 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -14,7 +14,7 @@ import { EditorOption } from '../../../../common/config/editorOptions.js'; import { EndOfLinePreference, IModelDeltaDecoration } from '../../../../common/model.js'; import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewDecorationsChangedEvent, ViewFlushedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewLinesInsertedEvent, ViewScrollChangedEvent, ViewZonesChangedEvent } from '../../../../common/viewEvents.js'; import { ViewContext } from '../../../../common/viewModel/viewContext.js'; -import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js'; +import { RestrictedRenderingContext, RenderingContext, HorizontalPosition } from '../../../view/renderingContext.js'; import { ViewController } from '../../../view/viewController.js'; import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { AbstractEditContext } from '../editContext.js'; @@ -60,7 +60,7 @@ export class NativeEditContext extends AbstractEditContext { private _editContextPrimarySelection: Selection = new Selection(1, 1, 1, 1); // Overflow guard container - private _parent: HTMLElement | undefined; + private readonly _parent: HTMLElement; private _decorations: string[] = []; private _primarySelection: Selection = new Selection(1, 1, 1, 1); @@ -241,9 +241,13 @@ export class NativeEditContext extends AbstractEditContext { return this._primarySelection.getPosition(); } - public prepareRender(ctx: RenderingContext): void { + public override prepareRender(ctx: RenderingContext): void { this._screenReaderSupport.prepareRender(ctx); - this._updateSelectionAndControlBounds(ctx); + this._updateSelectionAndControlBoundsData(ctx); + } + + public override onDidRender(): void { + this._updateSelectionAndControlBoundsAfterRender(); } public render(ctx: RestrictedRenderingContext): void { @@ -483,26 +487,35 @@ export class NativeEditContext extends AbstractEditContext { this._decorations = this._context.viewModel.model.deltaDecorations(this._decorations, decorations); } - private _updateSelectionAndControlBounds(ctx: RenderingContext) { - if (!this._parent) { - return; + private _linesVisibleRanges: HorizontalPosition | null = null; + private _updateSelectionAndControlBoundsData(ctx: RenderingContext): void { + const viewSelection = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(this._primarySelection); + if (this._primarySelection.isEmpty()) { + const linesVisibleRanges = ctx.visibleRangeForPosition(viewSelection.getStartPosition()); + this._linesVisibleRanges = linesVisibleRanges; + } else { + this._linesVisibleRanges = null; } + } + + private _updateSelectionAndControlBoundsAfterRender() { const options = this._context.configuration.options; const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; - const parentBounds = this._parent.getBoundingClientRect(); + const viewSelection = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(this._primarySelection); const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(viewSelection.startLineNumber); + const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetAfterLineNumber(viewSelection.endLineNumber); + // Make sure this doesn't force an extra layout (i.e. don't call it before rendering finished) + const parentBounds = this._parent.getBoundingClientRect(); const top = parentBounds.top + verticalOffsetStart - this._scrollTop; - const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetAfterLineNumber(viewSelection.endLineNumber); const height = verticalOffsetEnd - verticalOffsetStart; let left = parentBounds.left + contentLeft - this._scrollLeft; let width: number; if (this._primarySelection.isEmpty()) { - const linesVisibleRanges = ctx.visibleRangeForPosition(viewSelection.getStartPosition()); - if (linesVisibleRanges) { - left += linesVisibleRanges.left; + if (this._linesVisibleRanges) { + left += this._linesVisibleRanges.left; } width = 0; } else { @@ -515,9 +528,6 @@ export class NativeEditContext extends AbstractEditContext { } private _updateCharacterBounds(e: CharacterBoundsUpdateEvent): void { - if (!this._parent) { - return; - } const options = this._context.configuration.options; const typicalHalfWidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; From 7a90c2cf94ab9f2b8b7dc87f0e3c892cec0dbb91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:14:12 +0200 Subject: [PATCH 1326/4355] Bump actions/setup-node from 5 to 6 (#272258) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/monaco-editor.yml | 2 +- .github/workflows/pr-darwin-test.yml | 2 +- .github/workflows/pr-linux-test.yml | 2 +- .github/workflows/pr-node-modules.yml | 8 ++++---- .github/workflows/pr-win32-test.yml | 2 +- .github/workflows/pr.yml | 2 +- .github/workflows/telemetry.yml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index b520069a389..1b0af580378 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index cedbd379a3d..b1d462546ac 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index afcb9cc5bb4..ff671ee251c 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index ed8ff3a40ba..7f892be30f2 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-node-modules.yml b/.github/workflows/pr-node-modules.yml index 65c57f078f9..fc7497aa3f6 100644 --- a/.github/workflows/pr-node-modules.yml +++ b/.github/workflows/pr-node-modules.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -95,7 +95,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -167,7 +167,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -228,7 +228,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index 0a1ec3aa45b..b2427d0fad3 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2361aaa31de..26ad219d114 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index 03c3f649d58..cb7d81e551f 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -11,7 +11,7 @@ jobs: with: persist-credentials: false - - uses: 'actions/setup-node@v5' + - uses: 'actions/setup-node@v6' with: node-version: 'lts/*' From f77f4c50cd205eb865def4be26b2ee5e2de63a36 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 20 Oct 2025 15:24:28 +0200 Subject: [PATCH 1327/4355] eng - disable `start debugging` test that is flaky (#242033) (#272273) --- .../vscode-api-tests/src/singlefolder-tests/debug.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index 28f66a0f104..18ae50d3af6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -63,7 +63,7 @@ suite('vscode API - debug', function () { assert.strictEqual(functionBreakpoint.functionName, 'func'); }); - test('start debugging', async function () { + test.skip('start debugging', async function () { // Flaky: https://github.com/microsoft/vscode/issues/242033 let stoppedEvents = 0; let variablesReceived: () => void; let initializedReceived: () => void; From afecaaa7e88b2be589fc99ff1566c058b7fae906 Mon Sep 17 00:00:00 2001 From: Takashi Tamura Date: Mon, 20 Oct 2025 22:32:41 +0900 Subject: [PATCH 1328/4355] fix(chat): guard against undefined customModes.custom Fix #272223 and #272236 (#272263) fix(chat): guard against undefined customModes.custom --- .../contrib/chat/browser/modelPicker/modePickerActionItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts index 65fae65e6bf..07d9543ec15 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts @@ -89,7 +89,7 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { agentMode && makeAction(agentMode, currentMode), ...customBuiltinModeActions, ...otherBuiltinModes.map(mode => mode && makeAction(mode, currentMode)), - ...customModes.custom.map(mode => makeActionFromCustomMode(mode, currentMode)) + ...customModes.custom?.map(mode => makeActionFromCustomMode(mode, currentMode)) ?? [] ]); return orderedModes; From 5f4c61a11532a0c92d1b35aa016dbb9402d887b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 07:25:25 -0700 Subject: [PATCH 1329/4355] Initial working tree-sitter for command parsing Part of #261794 --- .../treeSitter/treeSitterLibraryService.ts | 2 ++ .../browser/commandLineAutoApprover.ts | 25 +++++++++++++++++++ .../browser/treeSitterLibraryService.ts | 15 +++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index b24a348c2d4..e4588e6348d 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -28,4 +28,6 @@ export interface ITreeSitterLibraryService { * @param reader */ getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined; + + createQuery(languageId: string, reader: IReader | undefined, querySource: string): Promise; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 291d53028c9..f660ef9dd84 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -11,6 +11,8 @@ import { structuralEquals } from '../../../../../base/common/equals.js'; import { ConfigurationTarget, IConfigurationService, type IConfigurationValue } from '../../../../../platform/configuration/common/configuration.js'; import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; import { isPowerShell } from './runInTerminalHelpers.js'; +import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { timeout } from '../../../../../base/common/async.js'; export interface IAutoApproveRule { regex: RegExp; @@ -39,6 +41,7 @@ export class CommandLineAutoApprover extends Disposable { constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService ) { super(); this.updateConfiguration(); @@ -50,6 +53,28 @@ export class CommandLineAutoApprover extends Disposable { this.updateConfiguration(); } })); + + const parserClass = this._treeSitterLibraryService.getParserClass(); + parserClass.then(async parserCtor => { + // HACK: Trigger async load + _treeSitterLibraryService.getLanguage('bash', undefined); + await timeout(1000); + const lang = _treeSitterLibraryService.getLanguage('bash', undefined); + + const parser = new parserCtor(); + if (lang) { + parser.setLanguage(lang); + const tree = parser.parse('echo "$(evil) a|b|c" | ls'); + + const q = await _treeSitterLibraryService.createQuery('bash', undefined, '(command) @command'); + if (tree && q) { + const captures = q.captures(tree.rootNode); + const subCommands = captures.map(e => e.node.text); + console.log('done', subCommands); + } + } + }); + } updateConfiguration() { diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 6302e6c322c..c641fd48dce 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -144,6 +144,21 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL const query = this._injectionQueries.get({ languageId, kind: 'highlights' }).read(reader); return query; } + + async createQuery(languageId: string, reader: IReader | undefined, querySource: string): Promise { + if (!this.supportsLanguage(languageId, reader)) { + return undefined; + } + const [ + language, + treeSitter + ] = await Promise.all([ + this._languagesCache.get(languageId).promise, + this._treeSitterImport.value, + ]); + + return new treeSitter.Query(language, querySource); + } } async function tryReadFile(fileService: IFileService, uri: URI): Promise { From b46218e4493b5265c337044198016bc47b595e72 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:38:22 +0200 Subject: [PATCH 1330/4355] SCM - fix bug related to pinned repositories (#272275) --- .../contrib/scm/browser/scmViewService.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 2e5b4c2cbd2..9ffbf6cc760 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -18,7 +18,7 @@ import { binarySearch } from '../../../../base/common/arrays.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; +import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, observableValueOpts, runOnChange, transaction } from '../../../../base/common/observable.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -223,7 +223,7 @@ export class SCMViewService implements ISCMViewService { */ private readonly _activeRepositoryObs: IObservable; private readonly _activeRepositoryPinnedObs: ISettableObservable; - private readonly _focusedRepositoryObs: IObservable; + private readonly _focusedRepositoryObs: ISettableObservable; private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; @@ -259,19 +259,15 @@ export class SCMViewService implements ISCMViewService { // noop } - this._focusedRepositoryObs = observableFromEventOpts( - { - owner: this, - equalsFn: () => false - }, this.onDidFocusRepository, - () => this.focusedRepository); + this._focusedRepositoryObs = observableValueOpts({ + owner: this, + equalsFn: () => false + }, undefined); - this._activeEditorObs = observableFromEventOpts( - { - owner: this, - equalsFn: () => false - }, this.editorService.onDidActiveEditorChange, - () => this.editorService.activeEditor); + this._activeEditorObs = observableFromEventOpts({ + owner: this, + equalsFn: () => false + }, this.editorService.onDidActiveEditorChange, () => this.editorService.activeEditor); this._activeEditorRepositoryObs = derivedObservableWithCache(this, (reader, lastValue) => { @@ -464,6 +460,7 @@ export class SCMViewService implements ISCMViewService { // Check if the last repository was removed if (removed.length === 1 && this._repositories.length === 0) { this._onDidFocusRepository.fire(undefined); + this._focusedRepositoryObs.set(undefined, undefined); } // Check if the pinned repository was removed @@ -515,9 +512,19 @@ export class SCMViewService implements ISCMViewService { } this._repositories.forEach(r => r.focused = r.repository === repository); + const focusedRepository = this._repositories.find(r => r.focused); - if (this._repositories.find(r => r.focused)) { + if (focusedRepository) { this._onDidFocusRepository.fire(repository); + + transaction(tx => { + this._focusedRepositoryObs.set(focusedRepository.repository, tx); + + // Pin the focused repository if needed + if (this._activeRepositoryPinnedObs.get() !== undefined) { + this._activeRepositoryPinnedObs.set(focusedRepository.repository, tx); + } + }); } } From 5bb3b395939d7b2becd83b5256a0a2342f00379c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Oct 2025 07:46:36 -0700 Subject: [PATCH 1331/4355] Get powershell working --- .../browser/commandLineAutoApprover.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index f660ef9dd84..1a8af7f8d7d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -58,12 +58,14 @@ export class CommandLineAutoApprover extends Disposable { parserClass.then(async parserCtor => { // HACK: Trigger async load _treeSitterLibraryService.getLanguage('bash', undefined); + _treeSitterLibraryService.getLanguage('powershell', undefined); await timeout(1000); - const lang = _treeSitterLibraryService.getLanguage('bash', undefined); + const bashLang = _treeSitterLibraryService.getLanguage('bash', undefined); + const pwshLang = _treeSitterLibraryService.getLanguage('powershell', undefined); const parser = new parserCtor(); - if (lang) { - parser.setLanguage(lang); + if (bashLang) { + parser.setLanguage(bashLang); const tree = parser.parse('echo "$(evil) a|b|c" | ls'); const q = await _treeSitterLibraryService.createQuery('bash', undefined, '(command) @command'); @@ -73,6 +75,18 @@ export class CommandLineAutoApprover extends Disposable { console.log('done', subCommands); } } + + if (pwshLang) { + parser.setLanguage(pwshLang); + const tree = parser.parse('Get-ChildItem | Write-Host "$(evil)"'); + + const q = await _treeSitterLibraryService.createQuery('powershell', undefined, '(command\ncommand_name: (command_name) @function)'); + if (tree && q) { + const captures = q.captures(tree.rootNode); + const subCommands = captures.map(e => e.node.text); + console.log('done', subCommands); + } + } }); } From 901240c8d3e1095468694c1d3c840cdff5fb6c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20Ruales?= <1588988+jruales@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:42:34 +0200 Subject: [PATCH 1332/4355] Show workspace-level agent instruction files in 'configure instructions' picker (#271952) * Show workspace-level agent instruction files in dropdown * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * PR Feedback --- .../contrib/chat/browser/chatWidget.ts | 23 +++++- .../promptSyntax/pickers/promptFilePickers.ts | 33 +++++++- .../computeAutomaticInstructions.ts | 76 +++---------------- .../promptSyntax/service/promptsService.ts | 14 ++++ .../service/promptsServiceImpl.ts | 20 +++++ .../promptSyntax/utils/promptFilesLocator.ts | 34 ++++++++- .../chat/test/common/mockPromptsService.ts | 2 + .../service/promptsService.test.ts | 14 +++- 8 files changed, 145 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index eb15198a9fa..d3c59cf56ce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1361,7 +1361,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private _getGenerateInstructionsMessage(): IMarkdownString { // Start checking for instruction files immediately if not already done if (!this._instructionFilesCheckPromise) { - this._instructionFilesCheckPromise = this._checkForInstructionFiles(); + this._instructionFilesCheckPromise = this._checkForAgentInstructionFiles(); // Use VS Code's idiomatic pattern for disposal-safe promise callbacks this._register(thenIfNotDisposed(this._instructionFilesCheckPromise, hasFiles => { this._instructionFilesExist = hasFiles; @@ -1391,10 +1391,25 @@ export class ChatWidget extends Disposable implements IChatWidget { return new MarkdownString(''); } - private async _checkForInstructionFiles(): Promise { + /** + * Checks if any agent instruction files (.github/copilot-instructions.md or AGENTS.md) exist in the workspace. + * Used to determine whether to show the "Generate Agent Instructions" hint. + * + * @returns true if instruction files exist OR if instruction features are disabled (to hide the hint) + */ + private async _checkForAgentInstructionFiles(): Promise { try { - const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, undefined); - return await computer.hasAgentInstructions(CancellationToken.None); + const useCopilotInstructionsFiles = this.configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES); + const useAgentMd = this.configurationService.getValue(PromptsConfig.USE_AGENT_MD); + if (!useCopilotInstructionsFiles && !useAgentMd) { + // If both settings are disabled, return true to hide the hint (since the features aren't enabled) + return true; + } + return ( + (await this.promptsService.listCopilotInstructionsMDs(CancellationToken.None)).length > 0 || + // Note: only checking for AGENTS.md files at the root folder, not ones in subfolders. + (await this.promptsService.listAgentMDs(CancellationToken.None, false)).length > 0 + ); } catch (error) { // On error, assume no instruction files exist to be safe this.logService.warn('[ChatWidget] Error checking for instruction files:', error); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index 395d477a9c4..f453f91f2d2 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -26,6 +26,8 @@ import { UILabelProvider } from '../../../../../../base/common/keybindingLabels. import { OS } from '../../../../../../base/common/platform.js'; import { askForPromptSourceFolder } from './askForPromptSourceFolder.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; +import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; /** * Options for the {@link askToSelectInstructions} function. @@ -194,6 +196,7 @@ export class PromptFilePickers { @IInstantiationService private readonly _instaService: IInstantiationService, @IPromptsService private readonly _promptsService: IPromptsService, @ILabelService private readonly _labelService: ILabelService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { } @@ -297,9 +300,37 @@ export class PromptFilePickers { } const locals = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.local, CancellationToken.None); if (locals.length) { - result.push({ type: 'separator', label: localize('separator.workspace', "Workspace") }); + // Note: No need to localize the label ".github/instructions', since it's the same in all languages. + result.push({ type: 'separator', label: '.github/instructions' }); result.push(...locals.map(l => this._createPromptPickItem(l, buttons))); } + + // Agent instruction files (copilot-instructions.md and AGENTS.md) are added here and not included in the output of + // listPromptFilesForStorage() because that function only handles *.instructions.md files (under `.github/instructions/`, etc.) + let agentInstructionFiles: IPromptPath[] = []; + if (options.type === PromptsType.instructions) { + const useNestedAgentMD = this._configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD); + const agentInstructionUris = [ + ...await this._promptsService.listCopilotInstructionsMDs(CancellationToken.None), + ...await this._promptsService.listAgentMDs(CancellationToken.None, !!useNestedAgentMD) + ]; + agentInstructionFiles = agentInstructionUris.map(uri => { + const folderName = this._labelService.getUriLabel(dirname(uri), { relative: true }); + // Don't show the folder path for files under .github folder (namely, copilot-instructions.md) since that is only defined once per repo. + const shouldShowFolderPath = folderName?.toLowerCase() !== '.github'; + return { + uri, + description: shouldShowFolderPath ? folderName : undefined, + storage: PromptsStorage.local, + type: options.type + } satisfies IPromptPath; + }); + } + if (agentInstructionFiles.length) { + result.push({ type: 'separator', label: localize('separator.workspace-agent-instructions', "Agent Instructions") }); + result.push(...agentInstructionFiles.map(l => this._createPromptPickItem(l, buttons))); + } + const exts = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.extension, CancellationToken.None); if (exts.length) { result.push({ type: 'separator', label: localize('separator.extensions', "Extensions") }); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index 1efe6567c4b..ad05805f32e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -7,7 +7,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { match, splitGlobAware } from '../../../../../base/common/glob.js'; import { ResourceMap, ResourceSet } from '../../../../../base/common/map.js'; import { Schemas } from '../../../../../base/common/network.js'; -import { basename, dirname, joinPath } from '../../../../../base/common/resources.js'; +import { basename, dirname } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -19,7 +19,7 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariableEntry, toPromptFileVariableEntry, toPromptTextVariableEntry, PromptFileVariableKind } from '../chatVariableEntries.js'; import { IToolData } from '../languageModelToolsService.js'; import { PromptsConfig } from './config/config.js'; -import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, isPromptOrInstructionsFile } from './config/promptFileLocations.js'; +import { isPromptOrInstructionsFile } from './config/promptFileLocations.js'; import { PromptsType } from './promptTypes.js'; import { ParsedPromptFile } from './service/newPromptsParser.js'; import { IPromptPath, IPromptsService } from './service/promptsService.js'; @@ -110,48 +110,6 @@ export class ComputeAutomaticInstructions { this.sendTelemetry(telemetryEvent); } - /** - * Checks if any agent instruction files (.github/copilot-instructions.md or agents.md) exist in the workspace. - * Used to determine whether to show the "Generate Agent Instructions" hint. - * - * @returns true if instruction files exist OR if instruction features are disabled (to hide the hint) - */ - public async hasAgentInstructions(token: CancellationToken): Promise { - const useCopilotInstructionsFiles = this._configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES); - const useAgentMd = this._configurationService.getValue(PromptsConfig.USE_AGENT_MD); - - // If both settings are disabled, return true to hide the hint (since the features aren't enabled) - if (!useCopilotInstructionsFiles && !useAgentMd) { - return true; - } - const { folders } = this._workspaceService.getWorkspace(); - - // Check for copilot-instructions.md files - if (useCopilotInstructionsFiles) { - for (const folder of folders) { - const file = joinPath(folder.uri, `.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); - if (await this._fileService.exists(file)) { - return true; - } - } - } - - // Check for agents.md files - if (useAgentMd) { - const resolvedRoots = await this._fileService.resolveAll(folders.map(f => ({ resource: f.uri }))); - for (const root of resolvedRoots) { - if (root.success && root.stat?.children) { - const agentMd = root.stat.children.find(c => c.isFile && c.name.toLowerCase() === 'agents.md'); - if (agentMd) { - return true; - } - } - } - } - - return false; - } - private sendTelemetry(telemetryEvent: InstructionsCollectionEvent): void { // Emit telemetry telemetryEvent.totalInstructionsCount = telemetryEvent.agentInstructionsCount + telemetryEvent.referencedInstructionsCount + telemetryEvent.applyingInstructionsCount + telemetryEvent.listedInstructionsCount; @@ -221,33 +179,23 @@ export class ComputeAutomaticInstructions { this._logService.trace(`[InstructionsContextComputer] No agent instructions files added (settings disabled).`); return; } - const instructionFiles: string[] = []; - instructionFiles.push(`.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); - const { folders } = this._workspaceService.getWorkspace(); const entries: ChatRequestVariableSet = new ChatRequestVariableSet(); if (useCopilotInstructionsFiles) { - for (const folder of folders) { - const file = joinPath(folder.uri, `.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); - if (await this._fileService.exists(file)) { - entries.add(toPromptFileVariableEntry(file, PromptFileVariableKind.Instruction, localize('instruction.file.reason.copilot', 'Automatically attached as setting {0} is enabled', PromptsConfig.USE_COPILOT_INSTRUCTION_FILES), true)); - telemetryEvent.agentInstructionsCount++; - this._logService.trace(`[InstructionsContextComputer] copilot-instruction.md files added: ${file.toString()}`); - } + const files: URI[] = await this._promptsService.listCopilotInstructionsMDs(token); + for (const file of files) { + entries.add(toPromptFileVariableEntry(file, PromptFileVariableKind.Instruction, localize('instruction.file.reason.copilot', 'Automatically attached as setting {0} is enabled', PromptsConfig.USE_COPILOT_INSTRUCTION_FILES), true)); + telemetryEvent.agentInstructionsCount++; + this._logService.trace(`[InstructionsContextComputer] copilot-instruction.md files added: ${file.toString()}`); } await this._addReferencedInstructions(entries, telemetryEvent, token); } if (useAgentMd) { - const resolvedRoots = await this._fileService.resolveAll(folders.map(f => ({ resource: f.uri }))); - for (const root of resolvedRoots) { - if (root.success && root.stat?.children) { - const agentMd = root.stat.children.find(c => c.isFile && c.name.toLowerCase() === 'agents.md'); - if (agentMd) { - entries.add(toPromptFileVariableEntry(agentMd.resource, PromptFileVariableKind.Instruction, localize('instruction.file.reason.agentsmd', 'Automatically attached as setting {0} is enabled', PromptsConfig.USE_AGENT_MD), true)); - telemetryEvent.agentInstructionsCount++; - this._logService.trace(`[InstructionsContextComputer] AGENTS.md files added: ${agentMd.resource.toString()}`); - } - } + const files = await this._promptsService.listAgentMDs(token, false); + for (const file of files) { + entries.add(toPromptFileVariableEntry(file, PromptFileVariableKind.Instruction, localize('instruction.file.reason.agentsmd', 'Automatically attached as setting {0} is enabled', PromptsConfig.USE_AGENT_MD), true)); + telemetryEvent.agentInstructionsCount++; + this._logService.trace(`[InstructionsContextComputer] AGENTS.md files added: ${file.toString()}`); } } for (const entry of entries.asArray()) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 6fb1db99014..399f9941714 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -257,7 +257,21 @@ export interface IPromptsService extends IDisposable { getPromptLocationLabel(promptPath: IPromptPath): string; + /** + * Gets list of all AGENTS.md files in the workspace. + */ findAgentMDsInWorkspace(token: CancellationToken): Promise; + + /** + * Gets list of AGENTS.md files. + * @param includeNested Whether to include AGENTS.md files from subfolders, or only from the root. + */ + listAgentMDs(token: CancellationToken, includeNested: boolean): Promise; + + /** + * Gets list of .github/copilot-instructions.md files. + */ + listCopilotInstructionsMDs(token: CancellationToken): Promise; } export interface IChatPromptSlashCommand { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 4b7c087ec7a..cc2d468f76d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -351,6 +351,26 @@ export class PromptsService extends Disposable implements IPromptsService { findAgentMDsInWorkspace(token: CancellationToken): Promise { return this.fileLocator.findAgentMDsInWorkspace(token); } + + public async listAgentMDs(token: CancellationToken, includeNested: boolean): Promise { + const useAgentMD = this.configurationService.getValue(PromptsConfig.USE_AGENT_MD); + if (!useAgentMD) { + return []; + } + if (includeNested) { + return await this.fileLocator.findAgentMDsInWorkspace(token); + } else { + return await this.fileLocator.findAgentMDsInWorkspaceRoots(token); + } + } + + public async listCopilotInstructionsMDs(token: CancellationToken): Promise { + const useCopilotInstructionsFiles = this.configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES); + if (!useCopilotInstructionsFiles) { + return []; + } + return await this.fileLocator.findCopilotInstructionsMDsInWorkspace(token); + } } function getCommandNameFromPromptPath(promptPath: IPromptPath): string { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index e3afeaf8f4f..831abd801db 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -11,7 +11,7 @@ import { getPromptFileLocationsConfigKey, PromptsConfig } from '../config/config import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; +import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; import { PromptsType } from '../promptTypes.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../../../base/common/network.js'; @@ -275,7 +275,21 @@ export class PromptFilesLocator extends Disposable { return []; } + public async findCopilotInstructionsMDsInWorkspace(token: CancellationToken): Promise { + const result: URI[] = []; + const { folders } = this.workspaceService.getWorkspace(); + for (const folder of folders) { + const file = joinPath(folder.uri, `.github/` + COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); + if (await this.fileService.exists(file)) { + result.push(file); + } + } + return result; + } + /** + * Gets list of `AGENTS.md` files anywhere in the workspace. + */ public async findAgentMDsInWorkspace(token: CancellationToken): Promise { const result = await Promise.all(this.workspaceService.getWorkspace().folders.map(folder => this.findAgentMDsInFolder(folder.uri, token))); return result.flat(1); @@ -305,6 +319,24 @@ export class PromptFilesLocator extends Disposable { return []; } + + /** + * Gets list of `AGENTS.md` files only at the root workspace folder(s). + */ + public async findAgentMDsInWorkspaceRoots(token: CancellationToken): Promise { + const result: URI[] = []; + const { folders } = this.workspaceService.getWorkspace(); + const resolvedRoots = await this.fileService.resolveAll(folders.map(f => ({ resource: f.uri }))); + for (const root of resolvedRoots) { + if (root.success && root.stat?.children) { + const agentMd = root.stat.children.find(c => c.isFile && c.name.toLowerCase() === 'agents.md'); + if (agentMd) { + result.push(agentMd.resource); + } + } + } + return result; + } } /** diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index a1b522e7ccd..41b921b3e72 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -46,6 +46,8 @@ export class MockPromptsService implements IPromptsService { registerContributedFile(type: PromptsType, name: string, description: string, uri: URI, extension: IExtensionDescription): IDisposable { throw new Error('Not implemented'); } getPromptLocationLabel(promptPath: IPromptPath): string { throw new Error('Not implemented'); } findAgentMDsInWorkspace(token: CancellationToken): Promise { throw new Error('Not implemented'); } + listAgentMDs(token: CancellationToken): Promise { throw new Error('Not implemented'); } + listCopilotInstructionsMDs(token: CancellationToken): Promise { throw new Error('Not implemented'); } dispose(): void { } } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 8f73cd9facd..8aa3a89b7b1 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -58,6 +58,7 @@ suite('PromptsService', () => { testConfigService.setUserConfiguration(PromptsConfig.KEY, true); testConfigService.setUserConfiguration(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES, true); testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, true); + testConfigService.setUserConfiguration(PromptsConfig.USE_NESTED_AGENT_MD, false); testConfigService.setUserConfiguration(PromptsConfig.INSTRUCTIONS_LOCATION_KEY, { [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true }); testConfigService.setUserConfiguration(PromptsConfig.PROMPT_LOCATIONS_KEY, { [PROMPT_DEFAULT_SOURCE_FOLDER]: true }); testConfigService.setUserConfiguration(PromptsConfig.MODE_LOCATION_KEY, { [MODE_DEFAULT_SOURCE_FOLDER]: true }); @@ -701,7 +702,18 @@ suite('PromptsService', () => { }, ], }, - + { + name: 'folder1', + children: [ + // This will not be returned because we have PromptsConfig.USE_NESTED_AGENT_MD set to false. + { + name: 'AGENTS.md', + contents: [ + 'An AGENTS.md file in another repo' + ] + } + ] + } ], }])).mock(); From adcc3fa24b4993be73bf344ae5c02bf65e6a87cb Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Mon, 20 Oct 2025 09:30:11 -0700 Subject: [PATCH 1333/4355] Add context menus to suggested prompts (#271596) --- .../contrib/chat/browser/chatWidget.ts | 6 ++- .../viewsWelcome/chatViewWelcomeController.ts | 50 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d3c59cf56ce..f59b42d4fea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -374,6 +374,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Cache for prompt file descriptions to avoid async calls during rendering private readonly promptDescriptionsCache = new Map(); + private readonly promptUriCache = new Map(); private _isLoadingPromptDescriptions = false; // UI state for temporarily hiding chat history @@ -1629,12 +1630,14 @@ export class ChatWidget extends Disposable implements IChatWidget { for (const { promptName } of topPrompts) { const description = this.promptDescriptionsCache.get(promptName); const commandLabel = localize('chatWidget.promptFile.commandLabel', "{0}", promptName); + const uri = this.promptUriCache.get(promptName); const descriptionText = description?.trim() ? description : undefined; result.push({ icon: Codicon.run, label: commandLabel, description: descriptionText, - prompt: `/${promptName} ` + prompt: `/${promptName} `, + uri: uri }); } @@ -1659,6 +1662,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (promptNames.includes(promptCommand.command)) { try { if (promptCommand.promptPath) { + this.promptUriCache.set(promptCommand.command, promptCommand.promptPath.uri); const parseResult = await this.promptsService.parseNew( promptCommand.promptPath.uri, CancellationToken.None diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index a7c0b481b4a..14d3849169c 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -7,7 +7,10 @@ import * as dom from '../../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { Action, IAction } from '../../../../../base/common/actions.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; import { Event } from '../../../../../base/common/event.js'; +import { URI } from '../../../../../base/common/uri.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -17,6 +20,7 @@ import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer. import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; @@ -121,6 +125,7 @@ export interface IChatSuggestedPrompts { readonly label: string; readonly description?: string; readonly prompt: string; + readonly uri?: URI; } export interface IChatViewWelcomeRenderOptions { @@ -141,6 +146,7 @@ export class ChatViewWelcomePart extends Disposable { @ITelemetryService private telemetryService: ITelemetryService, @IConfigurationService private configurationService: IConfigurationService, @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { super(); @@ -235,9 +241,21 @@ export class ChatViewWelcomePart extends Disposable { this.chatWidgetService.lastFocusedWidget.setInput(prompt.prompt); } }; + // Add context menu handler + this._register(dom.addDisposableListener(promptElement, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => { + e.preventDefault(); + e.stopImmediatePropagation(); + + const actions = this.getPromptContextMenuActions(prompt); + + this.contextMenuService.showContextMenu({ + getAnchor: () => ({ x: e.clientX, y: e.clientY }), + getActions: () => actions, + }); + })); // Add click handler this._register(dom.addDisposableListener(promptElement, dom.EventType.CLICK, executePrompt)); - // Add keyboard handler for Enter and Space keys + // Add keyboard handler this._register(dom.addDisposableListener(promptElement, dom.EventType.KEY_DOWN, (e) => { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { @@ -245,6 +263,15 @@ export class ChatViewWelcomePart extends Disposable { e.stopPropagation(); executePrompt(); } + else if (event.equals(KeyCode.F10) && event.shiftKey) { + e.preventDefault(); + e.stopPropagation(); + const actions = this.getPromptContextMenuActions(prompt); + this.contextMenuService.showContextMenu({ + getAnchor: () => promptElement, + getActions: () => actions, + }); + } })); } } @@ -271,6 +298,27 @@ export class ChatViewWelcomePart extends Disposable { } } + private getPromptContextMenuActions(prompt: IChatSuggestedPrompts): IAction[] { + const actions: IAction[] = []; + if (prompt.uri) { + const uri = prompt.uri; + actions.push(new Action( + 'chat.editPromptFile', + localize('editPromptFile', "Edit Prompt File"), + ThemeIcon.asClassName(Codicon.goToFile), + true, + async () => { + try { + await this.openerService.open(uri); + } catch (error) { + this.logService.error('Failed to open prompt file:', error); + } + } + )); + } + return actions; + } + public needsRerender(content: IChatViewWelcomeContent): boolean { // Heuristic based on content that changes between states return !!(content.isNew || From 87d1fdea7ce39f02a81cd1cfd582e66d2890dc6c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 21 Oct 2025 04:33:17 +1100 Subject: [PATCH 1334/4355] Find grained control over chat attachment contributions --- .../chat/browser/actions/chatContext.ts | 6 +-- src/vs/workbench/contrib/chat/browser/chat.ts | 6 +-- .../chat/browser/chatSessions.contribution.ts | 24 ++++++++- .../contrib/chat/browser/chatWidget.ts | 49 +++++++++---------- .../promptSyntax/attachInstructionsAction.ts | 2 +- .../contrib/chat/common/chatAgents.ts | 19 ++++--- .../chat/common/chatSessionsService.ts | 16 ++---- .../markers/browser/markersChatContext.ts | 4 ++ .../mcp/browser/mcpAddContextContribution.ts | 2 +- .../scm/browser/scmHistoryChatContext.ts | 5 +- .../search/browser/searchChatContext.ts | 8 ++- 11 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 4ea83b5610d..9b428597510 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -66,7 +66,7 @@ class ToolsContextPickerPick implements IChatContextPickerItem { readonly ordinal = -500; isEnabled(widget: IChatWidget): boolean { - return widget.supportsToolAttachments; + return !!widget.attachmentCapabilities.supportsToolAttachments; } asPicker(widget: IChatWidget): IChatContextPicker { @@ -236,7 +236,7 @@ class ClipboardImageContextValuePick implements IChatContextValueItem { ) { } async isEnabled(widget: IChatWidget) { - if (!widget.supportsImageAttachments) { + if (!widget.attachmentCapabilities.supportsImageAttachments) { return false; } if (!widget.input.selectedLanguageModel?.metadata.capabilities?.vision) { @@ -271,7 +271,7 @@ class ScreenshotContextValuePick implements IChatContextValueItem { ) { } async isEnabled(widget: IChatWidget) { - return widget.supportsImageAttachments && !!widget.input.selectedLanguageModel?.metadata.capabilities?.vision; + return !!widget.attachmentCapabilities.supportsImageAttachments && !!widget.input.selectedLanguageModel?.metadata.capabilities?.vision; } async asAttachment(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index c40efd5f108..a49b3f71e0d 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -17,7 +17,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { IChatAgentCommand, IChatAgentData } from '../common/chatAgents.js'; +import { IChatAgentAttachmentCapabilities, IChatAgentCommand, IChatAgentData } from '../common/chatAgents.js'; import { IChatResponseModel } from '../common/chatModel.js'; import { IChatMode } from '../common/chatModes.js'; import { IParsedChatRequest } from '../common/chatParserTypes.js'; @@ -214,9 +214,7 @@ export interface IChatWidget { readonly viewModel: IChatViewModel | undefined; readonly inputEditor: ICodeEditor; readonly supportsFileReferences: boolean; - readonly supportsToolAttachments: boolean; - readonly supportsMCPAttachments: boolean; - readonly supportsImageAttachments: boolean; + readonly attachmentCapabilities: IChatAgentAttachmentCapabilities; readonly parsedInput: IParsedChatRequest; readonly lockedAgentId: string | undefined; lastSelectedAgent: IChatAgentData | undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 12810e6d4fd..9db400beaef 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -21,7 +21,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; import { ChatEditorInput } from '../browser/chatEditorInput.js'; -import { IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; +import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; @@ -97,6 +97,26 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = { + supportsFileAttachments: true, + supportsToolAttachments: true, + supportsMCPAttachments: true, + supportsImageAttachments: true, + supportsSearchResultAttachments: true, + supportsInstructionAttachments: true, + supportsSourceControlAttachments: true, + supportsProblemAttachments: true, + supportsSymbolAttachments: true, +}; + export class ChatWidget extends Disposable implements IChatWidget { public static readonly CONTRIBS: { new(...args: [IChatWidget, ...any]): IChatWidgetContrib }[] = []; @@ -365,11 +378,7 @@ export class ChatWidget extends Disposable implements IChatWidget { }; private readonly _lockedToCodingAgentContextKey: IContextKey; private readonly _agentSupportsAttachmentsContextKey: IContextKey; - private _supportsToolAttachments: boolean = true; - private _supportsMCPAttachments: boolean = true; - private _supportsFileAttachments: boolean = true; - private _supportsImageAttachments: boolean = true; - + private _attachmentCapabilities: IChatAgentAttachmentCapabilities = supportsAllAttachments; private lastWelcomeViewChatMode: ChatModeKind | undefined; // Cache for prompt file descriptions to avoid async calls during rendering @@ -720,37 +729,25 @@ export class ChatWidget extends Disposable implements IChatWidget { } private _updateAgentCapabilitiesContextKeys(agent: IChatAgentData | undefined): void { - this._supportsFileAttachments = true; - this._supportsToolAttachments = true; - this._supportsMCPAttachments = true; - this._supportsImageAttachments = true; - // Check if the agent has capabilities defined directly const capabilities = agent?.capabilities ?? (this._lockedAgent ? this.chatSessionsService.getCapabilitiesForSessionType(this._lockedAgent.id) : undefined); if (capabilities) { - this._supportsFileAttachments = capabilities.supportsFileAttachments ?? false; - this._supportsToolAttachments = capabilities.supportsToolAttachments ?? false; - this._supportsMCPAttachments = capabilities.supportsMCPAttachments ?? false; - this._supportsImageAttachments = capabilities.supportsImageAttachments ?? false; + const disabledCapabilities = mapValues(supportsAllAttachments, () => false); + this._attachmentCapabilities = Object.assign(disabledCapabilities, filter(capabilities, (key, value) => typeof value === 'boolean')); + } else { + this._attachmentCapabilities = supportsAllAttachments; } - this._agentSupportsAttachmentsContextKey.set(this._supportsFileAttachments || this._supportsImageAttachments || this._supportsToolAttachments || this._supportsMCPAttachments); + const supportsAttachments = Object.keys(filter(this._attachmentCapabilities, (key, value) => value === true)).length > 0; + this._agentSupportsAttachmentsContextKey.set(supportsAttachments); } get supportsFileReferences(): boolean { return !!this.viewOptions.supportsFileReferences; } - get supportsToolAttachments(): boolean { - return this._supportsToolAttachments; - } - - get supportsMCPAttachments(): boolean { - return this._supportsMCPAttachments; - } - - get supportsImageAttachments(): boolean { - return this._supportsImageAttachments; + get attachmentCapabilities(): IChatAgentAttachmentCapabilities { + return this._attachmentCapabilities; } get input(): ChatInputPart { diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index b1596dff6fa..c485b72a2e1 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -226,7 +226,7 @@ export class ChatInstructionsPickerPick implements IChatContextPickerItem { ) { } isEnabled(widget: IChatWidget): Promise | boolean { - return PromptsConfig.enabled(this.configurationService); + return PromptsConfig.enabled(this.configurationService) && !!widget.attachmentCapabilities.supportsInstructionAttachments; } asPicker(): IChatContextPicker { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 658c7b633a2..bf1d9a0da19 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -38,6 +38,18 @@ export interface IChatAgentHistoryEntry { result: IChatAgentResult; } +export interface IChatAgentAttachmentCapabilities { + supportsFileAttachments?: boolean; + supportsToolAttachments?: boolean; + supportsMCPAttachments?: boolean; + supportsImageAttachments?: boolean; + supportsSearchResultAttachments?: boolean; + supportsInstructionAttachments?: boolean; + supportsSourceControlAttachments?: boolean; + supportsProblemAttachments?: boolean; + supportsSymbolAttachments?: boolean; +} + export interface IChatAgentData { id: string; name: string; @@ -63,12 +75,7 @@ export interface IChatAgentData { /** This is only relevant for isDefault agents. Others should have all modes available. */ modes: ChatModeKind[]; disambiguation: { category: string; description: string; examples: string[] }[]; - capabilities?: { - supportsToolAttachments?: boolean; - supportsFileAttachments?: boolean; - supportsMCPAttachments?: boolean; - supportsImageAttachments?: boolean; - }; + capabilities?: IChatAgentAttachmentCapabilities; } export interface IChatWelcomeMessageContent { diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index c3b9afcb346..f751bc34cd8 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -13,7 +13,7 @@ import { URI } from '../../../../base/common/uri.js'; import { IRelaxedExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditableData } from '../../../common/views.js'; -import { IChatAgentRequest } from './chatAgents.js'; +import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './chatAgents.js'; import { IChatProgress } from './chatService.js'; export const enum ChatSessionStatus { @@ -53,12 +53,7 @@ export interface IChatSessionsExtensionPoint { readonly welcomeMessage?: string; readonly welcomeTips?: string; readonly inputPlaceholder?: string; - readonly capabilities?: { - supportsFileAttachments?: boolean; - supportsToolAttachments?: boolean; - supportsMCPAttachments?: boolean; - supportsImageAttachments?: boolean; - }; + readonly capabilities?: IChatAgentAttachmentCapabilities; readonly commands?: IChatSessionCommandContribution[]; } export interface IChatSessionItem { @@ -181,12 +176,7 @@ export interface IChatSessionsService { /** * Get the capabilities for a specific session type */ - getCapabilitiesForSessionType(chatSessionType: string): { - supportsFileAttachments?: boolean; - supportsToolAttachments?: boolean; - supportsMCPAttachments?: boolean; - supportsImageAttachments?: boolean; - } | undefined; + getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined; } export const IChatSessionsService = createDecorator('chatSessionsService'); diff --git a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts index 309d35faf2b..1d862978895 100644 --- a/src/vs/workbench/contrib/markers/browser/markersChatContext.ts +++ b/src/vs/workbench/contrib/markers/browser/markersChatContext.ts @@ -19,6 +19,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, IChatContextPicker, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; import { IDiagnosticVariableEntryFilterData } from '../../chat/common/chatVariableEntries.js'; +import { IChatWidget } from '../../chat/browser/chat.js'; class MarkerChatContextPick implements IChatContextPickerItem { @@ -33,6 +34,9 @@ class MarkerChatContextPick implements IChatContextPickerItem { @IEditorService private readonly _editorService: IEditorService, ) { } + isEnabled(widget: IChatWidget): Promise | boolean { + return !!widget.attachmentCapabilities.supportsProblemAttachments; + } asPicker(): IChatContextPicker { return { placeholder: localize('chatContext.diagnstic.placeholder', 'Select a problem to attach'), diff --git a/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts b/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts index bcabf36168a..ec78181f5b7 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts @@ -40,7 +40,7 @@ export class McpAddContextContribution extends Disposable implements IWorkbenchC label: localize('mcp.addContext', "MCP Resources..."), icon: Codicon.mcp, isEnabled(widget) { - return widget.supportsMCPAttachments; + return !!widget.attachmentCapabilities.supportsMCPAttachments; }, asPicker: () => ({ placeholder: localize('mcp.addContext.placeholder', "Select MCP Resource..."), diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index ffb72f15522..44c5a3ed214 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -98,9 +98,10 @@ class SCMHistoryItemContext implements IChatContextPickerItem { @ISCMViewService private readonly _scmViewService: ISCMViewService ) { } - isEnabled(_widget: IChatWidget): Promise | boolean { + isEnabled(widget: IChatWidget): Promise | boolean { const activeRepository = this._scmViewService.activeRepository.get(); - return activeRepository?.repository.provider.historyProvider.get() !== undefined; + const supported = !!widget.attachmentCapabilities.supportsSourceControlAttachments; + return activeRepository?.repository.provider.historyProvider.get() !== undefined && supported; } asPicker(_widget: IChatWidget) { diff --git a/src/vs/workbench/contrib/search/browser/searchChatContext.ts b/src/vs/workbench/contrib/search/browser/searchChatContext.ts index c1c7c7311cd..7b8c6245d85 100644 --- a/src/vs/workbench/contrib/search/browser/searchChatContext.ts +++ b/src/vs/workbench/contrib/search/browser/searchChatContext.ts @@ -34,6 +34,7 @@ import { ResourceSet } from '../../../../base/common/map.js'; import { SymbolsQuickAccessProvider } from './symbolsQuickAccess.js'; import { SymbolKinds } from '../../../../editor/common/languages.js'; import { ChatUnsupportedFileSchemes } from '../../chat/common/constants.js'; +import { IChatWidget } from '../../chat/browser/chat.js'; export class SearchChatContextContribution extends Disposable implements IWorkbenchContribution { @@ -63,8 +64,8 @@ class SearchViewResultChatContextPick implements IChatContextValueItem { @ILabelService private readonly _labelService: ILabelService, ) { } - isEnabled(): Promise | boolean { - return !!SearchContext.HasSearchResults.getValue(this._contextKeyService); + isEnabled(widget: IChatWidget): Promise | boolean { + return !!SearchContext.HasSearchResults.getValue(this._contextKeyService) && !!widget.attachmentCapabilities.supportsSearchResultAttachments; } async asAttachment(): Promise { @@ -100,6 +101,9 @@ class SymbolsContextPickerPick implements IChatContextPickerItem { this._provider?.dispose(); } + isEnabled(widget: IChatWidget): boolean { + return !!widget.attachmentCapabilities.supportsSymbolAttachments; + } asPicker() { return { From dd3d0f74864abdd2e19d9991acb0d3ebd89395b6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Oct 2025 19:42:46 +0200 Subject: [PATCH 1335/4355] support icons (#272314) --- .../platform/mcp/common/mcpGalleryService.ts | 70 ++++++++++++++++--- .../extensions/browser/extensionsWidgets.ts | 13 ++-- .../contrib/mcp/browser/mcpServerWidgets.ts | 13 ++-- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index c82d7dc0041..836f7713429 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -41,6 +41,10 @@ interface IGitHubInfo { readonly uses_custom_opengraph_image?: boolean; } +interface IAzureAPICenterInfo { + readonly 'x-ms-icon'?: string; +} + interface IRawGalleryMcpServersMetadata { readonly count: number; readonly total?: number; @@ -68,6 +72,7 @@ interface IRawGalleryMcpServer { readonly id?: string; readonly readme?: string; }; + readonly icons?: readonly IRawGalleryMcpServerIcon[]; readonly status?: GalleryMcpServerStatus; readonly websiteUrl?: string; readonly createdAt?: string; @@ -76,6 +81,7 @@ interface IRawGalleryMcpServer { readonly remotes?: ReadonlyArray; readonly registryInfo?: IMcpRegistryInfo; readonly githubInfo?: IGitHubInfo; + readonly apicInfo?: IAzureAPICenterInfo; } interface IGalleryMcpServerDataSerializer { @@ -83,6 +89,25 @@ interface IGalleryMcpServerDataSerializer { toRawGalleryMcpServer(input: unknown): IRawGalleryMcpServer | undefined; } +interface IRawGalleryMcpServerIcon { + readonly src: string; + readonly theme?: IconTheme; + readonly sizes?: string[]; + readonly mimeType?: IconMimeType; +} + +const enum IconMimeType { + PNG = 'image/png', + JPEG = 'image/jpeg', + JPG = 'image/jpg', + SVG = 'image/svg+xml', + WEBP = 'image/webp', +} + +const enum IconTheme { + LIGHT = 'light', + DARK = 'dark', +} namespace McpServerSchemaVersion_v2025_07_09 { @@ -457,6 +482,7 @@ namespace McpServerSchemaVersion_v0_1 { readonly version: string; readonly $schema?: string; readonly title?: string; + readonly icons?: IRawGalleryMcpServerIcon[]; readonly repository?: { readonly source: string; readonly url: string; @@ -468,7 +494,7 @@ namespace McpServerSchemaVersion_v0_1 { readonly remotes?: RawGalleryMcpServerRemotes; readonly _meta?: { readonly 'io.modelcontextprotocol.registry/publisher-provided'?: Record; - }; + } & IAzureAPICenterInfo; } interface RawGalleryMcpServerInfo { @@ -505,7 +531,11 @@ namespace McpServerSchemaVersion_v0_1 { for (const server of from.servers) { const rawServer = this.toRawGalleryMcpServer(server); if (!rawServer) { - return undefined; + if (servers.length === 0) { + return undefined; + } else { + continue; + } } servers.push(rawServer); } @@ -536,6 +566,8 @@ namespace McpServerSchemaVersion_v0_1 { return undefined; } + const { 'io.modelcontextprotocol.registry/official': registryInfo, ...apicInfo } = from._meta ?? {}; + return { name: from.server.name, description: from.server.description, @@ -547,12 +579,14 @@ namespace McpServerSchemaVersion_v0_1 { id: from.server.repository.id, readme: from.server.repository.readme } : undefined, + icons: from.server.icons, websiteUrl: from.server.websiteUrl, packages: from.server.packages, remotes: from.server.remotes, - status: from._meta?.['io.modelcontextprotocol.registry/official']?.status, - registryInfo: from._meta?.['io.modelcontextprotocol.registry/official'], + status: registryInfo?.status, + registryInfo, githubInfo: from.server._meta?.['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, + apicInfo }; } } @@ -777,10 +811,30 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService displayName = server.githubInfo.display_name; } - const icon: { light: string; dark: string } | undefined = server.githubInfo?.owner_avatar_url ? { - light: server.githubInfo.owner_avatar_url, - dark: server.githubInfo.owner_avatar_url - } : undefined; + let icon: { light: string; dark: string } | undefined; + + if (server.githubInfo?.owner_avatar_url) { + icon = { + light: server.githubInfo.owner_avatar_url, + dark: server.githubInfo.owner_avatar_url + }; + } + + else if (server.apicInfo?.['x-ms-icon']) { + icon = { + light: server.apicInfo['x-ms-icon'], + dark: server.apicInfo['x-ms-icon'] + }; + } + + else if (server.icons && server.icons.length > 0) { + const lightIcon = server.icons.find(icon => icon.theme === 'light') ?? server.icons[0]; + const darkIcon = server.icons.find(icon => icon.theme === 'dark') ?? lightIcon; + icon = { + light: lightIcon.src, + dark: darkIcon.src + }; + } const webUrl = manifest ? this.getWebUrl(server.name, manifest) : undefined; const publisherUrl = manifest ? this.getPublisherUrl(publisher, manifest) : undefined; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 012c6840c2c..2f1c096f1aa 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -75,7 +75,7 @@ export function onClick(element: HTMLElement, callback: () => void): IDisposable export class ExtensionIconWidget extends ExtensionWidget { - private readonly disposables = this._register(new DisposableStore()); + private readonly iconLoadingDisposable = this._register(new MutableDisposable()); private readonly element: HTMLElement; private readonly iconElement: HTMLImageElement; private readonly defaultIconElement: HTMLElement; @@ -103,7 +103,7 @@ export class ExtensionIconWidget extends ExtensionWidget { this.iconElement.src = ''; this.iconElement.style.display = 'none'; this.defaultIconElement.style.display = 'none'; - this.disposables.clear(); + this.iconLoadingDisposable.clear(); } render(): void { @@ -113,18 +113,18 @@ export class ExtensionIconWidget extends ExtensionWidget { } if (this.extension.iconUrl) { - this.iconElement.style.display = 'inherit'; - this.defaultIconElement.style.display = 'none'; if (this.iconUrl !== this.extension.iconUrl) { + this.iconElement.style.display = 'inherit'; + this.defaultIconElement.style.display = 'none'; this.iconUrl = this.extension.iconUrl; - this.disposables.add(addDisposableListener(this.iconElement, 'error', () => { + this.iconLoadingDisposable.value = addDisposableListener(this.iconElement, 'error', () => { if (this.extension?.iconUrlFallback) { this.iconElement.src = this.extension.iconUrlFallback; } else { this.iconElement.style.display = 'none'; this.defaultIconElement.style.display = 'inherit'; } - }, { once: true })); + }, { once: true }); this.iconElement.src = this.iconUrl; if (!this.iconElement.complete) { this.iconElement.style.visibility = 'hidden'; @@ -138,6 +138,7 @@ export class ExtensionIconWidget extends ExtensionWidget { this.iconElement.style.display = 'none'; this.iconElement.src = ''; this.defaultIconElement.style.display = 'inherit'; + this.iconLoadingDisposable.clear(); } } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts index ee7deb1c3e3..990b1523b78 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.ts @@ -56,7 +56,7 @@ export function onClick(element: HTMLElement, callback: () => void): IDisposable export class McpServerIconWidget extends McpServerWidget { - private readonly disposables = this._register(new DisposableStore()); + private readonly iconLoadingDisposable = this._register(new MutableDisposable()); private readonly element: HTMLElement; private readonly iconElement: HTMLImageElement; private readonly codiconIconElement: HTMLElement; @@ -87,7 +87,7 @@ export class McpServerIconWidget extends McpServerWidget { this.iconElement.style.display = 'none'; this.codiconIconElement.style.display = 'none'; this.codiconIconElement.className = ThemeIcon.asClassName(mcpServerIcon); - this.disposables.clear(); + this.iconLoadingDisposable.clear(); } render(): void { @@ -97,16 +97,16 @@ export class McpServerIconWidget extends McpServerWidget { } if (this.mcpServer.icon) { - this.iconElement.style.display = 'inherit'; - this.codiconIconElement.style.display = 'none'; const type = this.themeService.getColorTheme().type; const iconUrl = isDark(type) ? this.mcpServer.icon.dark : this.mcpServer.icon.light; if (this.iconUrl !== iconUrl) { + this.iconElement.style.display = 'inherit'; + this.codiconIconElement.style.display = 'none'; this.iconUrl = iconUrl; - this.disposables.add(dom.addDisposableListener(this.iconElement, 'error', () => { + this.iconLoadingDisposable.value = dom.addDisposableListener(this.iconElement, 'error', () => { this.iconElement.style.display = 'none'; this.codiconIconElement.style.display = 'inherit'; - }, { once: true })); + }, { once: true }); this.iconElement.src = this.iconUrl; if (!this.iconElement.complete) { this.iconElement.style.visibility = 'hidden'; @@ -121,6 +121,7 @@ export class McpServerIconWidget extends McpServerWidget { this.iconElement.src = ''; this.codiconIconElement.className = this.mcpServer.codicon ? `codicon ${this.mcpServer.codicon}` : ThemeIcon.asClassName(mcpServerIcon); this.codiconIconElement.style.display = 'inherit'; + this.iconLoadingDisposable.clear(); } } } From 79d548f26cefd7ef1321ec3bcb17a5213f6370df Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 19:43:05 +0200 Subject: [PATCH 1336/4355] SCM - remove the concept of pinning a repository selection (#272293) --- .../scm/browser/scmRepositoriesViewPane.ts | 64 +------------------ .../scm/browser/scmRepositoryRenderer.ts | 34 +++++----- .../contrib/scm/browser/scmViewService.ts | 60 +++-------------- src/vs/workbench/contrib/scm/common/scm.ts | 4 +- 4 files changed, 31 insertions(+), 131 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 4b54e88b639..1a45f6bebb5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -11,9 +11,9 @@ import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browse import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent } from '../../../../base/browser/ui/tree/tree.js'; import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; @@ -24,13 +24,11 @@ import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRende import { collectContextMenuActions, getActionViewItemProvider, isSCMRepository } from './util.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { MenuId, registerAction2, Action2 } from '../../../../platform/actions/common/actions.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { autorun, IObservable, observableFromEvent, observableSignalFromEvent } from '../../../../base/common/observable.js'; import { Sequencer } from '../../../../base/common/async.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { RepositoryContextKeys } from './scmViewService.js'; class ListDelegate implements IListVirtualDelegate { @@ -380,59 +378,3 @@ export class SCMRepositoriesViewPane extends ViewPane { super.dispose(); } } - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'scm.repositories.pinSelection', - title: localize('scmPinSelection', "Pin the Current Selection"), - f1: false, - icon: Codicon.pin, - menu: { - id: MenuId.SCMSourceControlTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.has('scm.providerCount'), - ContextKeyExpr.greater('scm.providerCount', 1), - RepositoryContextKeys.RepositoryPinned.isEqualTo(false)), - group: 'navigation', - order: 1 - }, - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const scmViewService = accessor.get(ISCMViewService); - const activeRepository = scmViewService.activeRepository.get(); - if (!activeRepository) { - return; - } - - scmViewService.pinActiveRepository(activeRepository.repository); - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'scm.repositories.unpinSelection', - title: localize('scmUnpinSelection', "Unpin the Current Selection"), - f1: false, - icon: Codicon.pinned, - menu: { - id: MenuId.SCMSourceControlTitle, - when: ContextKeyExpr.and( - ContextKeyExpr.has('scm.providerCount'), - ContextKeyExpr.greater('scm.providerCount', 1), - RepositoryContextKeys.RepositoryPinned.isEqualTo(true)), - group: 'navigation', - order: 2 - }, - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const scmViewService = accessor.get(ISCMViewService); - scmViewService.pinActiveRepository(undefined); - } -}); - diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 5cc2ffe1e60..92606458284 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -12,7 +12,7 @@ import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ActionRunner, IAction } from '../../../../base/common/actions.js'; -import { connectPrimaryMenu, getRepositoryResourceCount, getSCMRepositoryIcon, isSCMRepository, StatusBarAction } from './util.js'; +import { connectPrimaryMenu, getRepositoryResourceCount, isSCMRepository, StatusBarAction } from './util.js'; import { ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; @@ -25,6 +25,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -98,24 +99,19 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate): void { const repository = isSCMRepository(arg) ? arg : arg.element; - templateData.elementDisposables.add(autorun(reader => { - const selectionMode = this.scmViewService.selectionModeConfig.read(undefined); - const activeRepository = this.scmViewService.activeRepository.read(reader); - - const icon = selectionMode === 'single' - ? getSCMRepositoryIcon(activeRepository, repository) - : getSCMRepositoryIcon(activeRepository ? { repository: activeRepository.repository, pinned: false } : undefined, repository); - - const label = icon - ? `$(${icon.id}) ${repository.provider.name}` - : repository.provider.name; - - if (repository.provider.rootUri) { - templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); - } else { - templateData.label.setLabel(label, undefined, { title: repository.provider.label }); - } - })); + const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) + ? repository.provider.iconPath.id + : undefined; + + const label = icon + ? `$(${icon}) ${repository.provider.name}` + : repository.provider.name; + + if (repository.provider.rootUri) { + templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); + } else { + templateData.label.setLabel(label, undefined, { title: repository.provider.label }); + } let statusPrimaryActions: IAction[] = []; let menuPrimaryActions: IAction[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 9ffbf6cc760..b8af15c5bff 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -18,7 +18,7 @@ import { binarySearch } from '../../../../base/common/arrays.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, observableValueOpts, runOnChange, transaction } from '../../../../base/common/observable.js'; +import { derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -42,8 +42,7 @@ function getRepositoryName(workspaceContextService: IWorkspaceContextService, re } export const RepositoryContextKeys = { - RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime), - RepositoryPinned: new RawContextKey('scmRepositoryPinned', false) + RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime) }; export type RepositoryQuickPickItem = IQuickPickItem & { repository: 'auto' | ISCMRepository }; @@ -103,7 +102,6 @@ interface ISCMRepositoryView { export interface ISCMViewServiceState { readonly all: string[]; readonly visible: number[]; - readonly pinned?: boolean; readonly sortKey: ISCMRepositorySortKey; } @@ -223,13 +221,11 @@ export class SCMViewService implements ISCMViewService { */ private readonly _activeRepositoryObs: IObservable; private readonly _activeRepositoryPinnedObs: ISettableObservable; - private readonly _focusedRepositoryObs: ISettableObservable; + private readonly _focusedRepositoryObs: IObservable; private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; - private _repositoryPinnedContextKey: IContextKey; - constructor( @ISCMService private readonly scmService: ISCMService, @IContextKeyService contextKeyService: IContextKeyService, @@ -259,10 +255,11 @@ export class SCMViewService implements ISCMViewService { // noop } - this._focusedRepositoryObs = observableValueOpts({ - owner: this, - equalsFn: () => false - }, undefined); + this._focusedRepositoryObs = observableFromEventOpts( + { + owner: this, + equalsFn: () => false + }, this.onDidFocusRepository, () => this.focusedRepository); this._activeEditorObs = observableFromEventOpts({ owner: this, @@ -301,22 +298,6 @@ export class SCMViewService implements ISCMViewService { return repository ? { repository, pinned } : undefined; }); - this.disposables.add(autorun(reader => { - const selectionMode = this.selectionModeConfig.read(undefined); - const activeRepository = this.activeRepository.read(reader); - - if (selectionMode === 'single' && activeRepository) { - // `this._activeEditorRepositoryObs` uses `Object.create` to - // ensure that the observable updates even if the repository - // instance is the same. - const repository = this.repositories - .find(r => r.id === activeRepository.repository.id); - if (repository) { - this.visibleRepositories = [repository]; - } - } - })); - this.disposables.add(runOnChange(this.selectionModeConfig, selectionMode => { if (selectionMode === 'single' && this.visibleRepositories.length > 1) { const repository = this.visibleRepositories[0]; @@ -324,9 +305,6 @@ export class SCMViewService implements ISCMViewService { } })); - this._repositoryPinnedContextKey = RepositoryContextKeys.RepositoryPinned.bindTo(contextKeyService); - this._repositoryPinnedContextKey.set(!!this._activeRepositoryPinnedObs.get()); - this._repositoriesSortKey = this.previousState?.sortKey ?? this.getViewSortOrder(); this._sortKeyContextKey = RepositoryContextKeys.RepositorySortKey.bindTo(contextKeyService); this._sortKeyContextKey.set(this._repositoriesSortKey); @@ -414,11 +392,6 @@ export class SCMViewService implements ISCMViewService { const maxSelectionIndex = this.getMaxSelectionIndex(); this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed }); - - // Pin repository if needed - if (this.selectionModeConfig.get() === 'single' && this.previousState?.pinned && this.didSelectRepository) { - this.pinActiveRepository(repository); - } } else { // Single selection mode (add subsequent repository) this.insertRepositoryView(this._repositories, repositoryView); @@ -460,7 +433,6 @@ export class SCMViewService implements ISCMViewService { // Check if the last repository was removed if (removed.length === 1 && this._repositories.length === 0) { this._onDidFocusRepository.fire(undefined); - this._focusedRepositoryObs.set(undefined, undefined); } // Check if the pinned repository was removed @@ -512,25 +484,14 @@ export class SCMViewService implements ISCMViewService { } this._repositories.forEach(r => r.focused = r.repository === repository); - const focusedRepository = this._repositories.find(r => r.focused); - if (focusedRepository) { + if (this._repositories.find(r => r.focused)) { this._onDidFocusRepository.fire(repository); - - transaction(tx => { - this._focusedRepositoryObs.set(focusedRepository.repository, tx); - - // Pin the focused repository if needed - if (this._activeRepositoryPinnedObs.get() !== undefined) { - this._activeRepositoryPinnedObs.set(focusedRepository.repository, tx); - } - }); } } pinActiveRepository(repository: ISCMRepository | undefined): void { this._activeRepositoryPinnedObs.set(repository, undefined); - this._repositoryPinnedContextKey.set(!!repository); } private compareRepositories(op1: ISCMRepositoryView, op2: ISCMRepositoryView): number { @@ -587,9 +548,8 @@ export class SCMViewService implements ISCMViewService { const all = this.repositories.map(r => getProviderStorageKey(r.provider)); const visible = this.visibleRepositories.map(r => all.indexOf(getProviderStorageKey(r.provider))); - const pinned = this.activeRepository.get()?.pinned === true; + this.previousState = { all, visible, sortKey: this._repositoriesSortKey } satisfies ISCMViewServiceState; - this.previousState = { all, visible, pinned, sortKey: this._repositoriesSortKey } satisfies ISCMViewServiceState; this.storageService.store('scm:view:visibleRepositories', JSON.stringify(this.previousState), StorageScope.WORKSPACE, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index cb50e484dd0..9b7aea28f6b 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -239,7 +239,9 @@ export interface ISCMViewService { focus(repository: ISCMRepository): void; /** - * Focused repository or the repository for the active editor + * The active repository is the repository selected in the Source Control Repositories view + * or the repository associated with the active editor. The active repository is shown in the + * Source Control Repository status bar item. */ readonly activeRepository: IObservable<{ repository: ISCMRepository; pinned: boolean } | undefined>; pinActiveRepository(repository: ISCMRepository | undefined): void; From 5aac6e2245dbbe419298b5137927ea2ea44bf9dc Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 20 Oct 2025 10:43:31 -0700 Subject: [PATCH 1337/4355] mcp: use standard fetch unless pipe connection is needed (#272305) Closes https://github.com/microsoft/vscode/issues/270655 --- src/vs/workbench/api/node/extHostMcpNode.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/api/node/extHostMcpNode.ts b/src/vs/workbench/api/node/extHostMcpNode.ts index 5ae89c7a8a8..1b961f66ae1 100644 --- a/src/vs/workbench/api/node/extHostMcpNode.ts +++ b/src/vs/workbench/api/node/extHostMcpNode.ts @@ -164,6 +164,8 @@ class McpHTTPHandleNode extends McpHTTPHandle { authority: 'localhost', // HTTP always wants a host (not that we're using it), but if we're using a socket or pipe then localhost is sorta right anyway path: uri.fragment, }).toString(true); + } else { + return super._fetchInternal(url, init); } const undiciResponse = await fetch(httpUrl, undiciInit); From e15e11414401175b7e237f281c5275c735fcc82c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 21 Oct 2025 04:50:11 +1100 Subject: [PATCH 1338/4355] simplify --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 6dea34d9cfc..de779e66019 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -23,7 +23,7 @@ import { KeyCode } from '../../../../base/common/keyCodes.js'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable, thenIfNotDisposed, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; -import { filter, mapValues } from '../../../../base/common/objects.js'; +import { filter } from '../../../../base/common/objects.js'; import { autorun, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; import { basename, extUri, isEqual } from '../../../../base/common/resources.js'; import { MicrotaskDelay } from '../../../../base/common/symbols.js'; @@ -731,12 +731,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private _updateAgentCapabilitiesContextKeys(agent: IChatAgentData | undefined): void { // Check if the agent has capabilities defined directly const capabilities = agent?.capabilities ?? (this._lockedAgent ? this.chatSessionsService.getCapabilitiesForSessionType(this._lockedAgent.id) : undefined); - if (capabilities) { - const disabledCapabilities = mapValues(supportsAllAttachments, () => false); - this._attachmentCapabilities = Object.assign(disabledCapabilities, filter(capabilities, (key, value) => typeof value === 'boolean')); - } else { - this._attachmentCapabilities = supportsAllAttachments; - } + this._attachmentCapabilities = capabilities ?? supportsAllAttachments; const supportsAttachments = Object.keys(filter(this._attachmentCapabilities, (key, value) => value === true)).length > 0; this._agentSupportsAttachmentsContextKey.set(supportsAttachments); From b42b378761493e9555e6580895ce48ae1027ea67 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:15:14 +0200 Subject: [PATCH 1339/4355] SCM - remove View as Tree/List actions from the Changes view's title (#272319) --- .../contrib/scm/browser/scmViewPane.ts | 46 +++---------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 204e6632e8c..37eb11d07ca 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -20,7 +20,7 @@ import { IContextViewService, IContextMenuService, IOpenContextView } from '../. import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2, IMenu } from '../../../../platform/actions/common/actions.js'; +import { MenuItemAction, IMenuService, registerAction2, MenuId, MenuRegistry, Action2, IMenu } from '../../../../platform/actions/common/actions.js'; import { IAction, ActionRunner, Action, Separator, IActionRunner, toAction } from '../../../../base/common/actions.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IThemeService, IFileIconTheme } from '../../../../platform/theme/common/themeService.js'; @@ -1116,17 +1116,15 @@ class RepositoryVisibilityActionController { } class SetListViewModeAction extends ViewAction { - constructor( - id = 'workbench.scm.action.setListViewMode', - menu: Partial = {}) { + constructor() { super({ - id, + id: 'workbench.scm.action.setListViewMode', title: localize('setListViewMode', "View as List"), viewId: VIEW_PANE_ID, f1: false, icon: Codicon.listTree, toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.List), - menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } + menu: { id: Menus.ViewSort, group: '1_viewmode' } }); } @@ -1135,32 +1133,17 @@ class SetListViewModeAction extends ViewAction { } } -class SetListViewModeNavigationAction extends SetListViewModeAction { - constructor() { - super( - 'workbench.scm.action.setListViewModeNavigation', - { - id: MenuId.SCMTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree)), - group: 'navigation', - order: -1000 - }); - } -} - class SetTreeViewModeAction extends ViewAction { - constructor( - id = 'workbench.scm.action.setTreeViewMode', - menu: Partial = {}) { + constructor() { super( { - id, + id: 'workbench.scm.action.setTreeViewMode', title: localize('setTreeViewMode', "View as Tree"), viewId: VIEW_PANE_ID, f1: false, icon: Codicon.listFlat, toggled: ContextKeys.SCMViewMode.isEqualTo(ViewMode.Tree), - menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu } + menu: { id: Menus.ViewSort, group: '1_viewmode' } }); } @@ -1169,23 +1152,8 @@ class SetTreeViewModeAction extends ViewAction { } } -class SetTreeViewModeNavigationAction extends SetTreeViewModeAction { - constructor() { - super( - 'workbench.scm.action.setTreeViewModeNavigation', - { - id: MenuId.SCMTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.SCMViewMode.isEqualTo(ViewMode.List)), - group: 'navigation', - order: -1000 - }); - } -} - registerAction2(SetListViewModeAction); registerAction2(SetTreeViewModeAction); -registerAction2(SetListViewModeNavigationAction); -registerAction2(SetTreeViewModeNavigationAction); abstract class RepositorySortAction extends ViewAction { constructor(private sortKey: ISCMRepositorySortKey, title: string) { From f93044c01559a6b09d2469940c555ddd25bc3ec2 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:16:04 +0200 Subject: [PATCH 1340/4355] Maintain a cache of repo remotes that we know about (#271652) * Maintain a cache of repo remotes that we know about Part of #271101 * Save workspace file or folder instead of repo root path * PR feedback - Rename to repositoryCache - Remove timestamp - Move logic into repo cache - Fix tests * Name changes * Actually delete * Fix change that got lost in the rename --- extensions/git/src/model.ts | 12 +- extensions/git/src/repository.ts | 20 +- extensions/git/src/repositoryCache.ts | 173 ++++++++++++++++++ .../git/src/test/repositoryCache.test.ts | 135 ++++++++++++++ 4 files changed, 334 insertions(+), 6 deletions(-) create mode 100644 extensions/git/src/repositoryCache.ts create mode 100644 extensions/git/src/test/repositoryCache.test.ts diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 0fe0055e5a6..e96a9d31290 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -20,6 +20,7 @@ import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; import { IBranchProtectionProviderRegistry } from './branchProtection'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; +import { RepositoryCache } from './repositoryCache'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { @@ -275,6 +276,8 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi */ private _workspaceFolders = new Map(); + private repositoryCache: RepositoryCache; + private disposables: Disposable[] = []; constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { @@ -298,6 +301,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.setState('uninitialized'); this.doInitialScan().finally(() => this.setState('initialized')); + this.repositoryCache = new RepositoryCache(globalState, logger); } private async doInitialScan(): Promise { @@ -653,7 +657,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Open repository const [dotGit, repositoryRootRealPath] = await Promise.all([this.git.getRepositoryDotGit(repositoryRoot), this.getRepositoryRootRealPath(repositoryRoot)]); const gitRepository = this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger); - const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter); + const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter, this.repositoryCache); this.open(repository); this._closedRepositoriesManager.deleteRepository(repository.root); @@ -664,7 +668,9 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Do not await this, we want SCM // to know about the repo asap - repository.status(); + repository.status().then(() => { + this.repositoryCache.update(repository.remotes, [], repository.root); + }); } catch (err) { // noop this.logger.trace(`[Model][openRepository] Opening repository for path='${repoPath}' failed. Error:${err}`); @@ -858,7 +864,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.logger.info(`[Model][close] Repository: ${repository.root}`); this._closedRepositoriesManager.addRepository(openRepository.repository.root); - + this.repositoryCache.update(repository.remotes, [], repository.root); openRepository.dispose(); } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 619a2c70e9f..6db96628cc7 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -25,6 +25,7 @@ import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; +import { RepositoryCache } from './repositoryCache'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -895,7 +896,8 @@ export class Repository implements Disposable { historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailsProviderRegistry, globalState: Memento, private readonly logger: LogOutputChannel, - private telemetryReporter: TelemetryReporter + private telemetryReporter: TelemetryReporter, + private readonly repositoryCache: RepositoryCache ) { this._operations = new OperationManager(this.logger); @@ -1847,11 +1849,23 @@ export class Repository implements Disposable { } async addRemote(name: string, url: string): Promise { - await this.run(Operation.Remote, () => this.repository.addRemote(name, url)); + await this.run(Operation.Remote, async () => { + const result = await this.repository.addRemote(name, url); + this.repositoryCache.update(this.remotes, [], this.root); + return result; + }); } async removeRemote(name: string): Promise { - await this.run(Operation.Remote, () => this.repository.removeRemote(name)); + await this.run(Operation.Remote, async () => { + const result = this.repository.removeRemote(name); + const remote = this.remotes.find(remote => remote.name === name); + if (remote) { + this.repositoryCache.update([], [remote], this.root); + } + return result; + }); + } async renameRemote(name: string, newName: string): Promise { diff --git a/extensions/git/src/repositoryCache.ts b/extensions/git/src/repositoryCache.ts new file mode 100644 index 00000000000..6a00974e0b3 --- /dev/null +++ b/extensions/git/src/repositoryCache.ts @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel, Memento, workspace } from 'vscode'; +import * as path from 'path'; +import { LRUCache } from './cache'; +import { Remote } from './api/git'; + +export class RepositoryCache { + + private static readonly STORAGE_KEY = 'git.repositoryCache'; + private static readonly MAX_REPO_ENTRIES = 30; // Max repositories tracked + private static readonly MAX_FOLDER_ENTRIES = 10; // Max folders per repository + + // Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> true). Only keys matter. + private readonly lru = new LRUCache>(RepositoryCache.MAX_REPO_ENTRIES); + + constructor(public readonly _globalState: Memento, private readonly _logger: LogOutputChannel) { + this.load(); + } + + // Exposed for testing + protected get _workspaceFile() { + return workspace.workspaceFile; + } + + // Exposed for testing + protected get _workspaceFolders() { + return workspace.workspaceFolders; + } + + /** + * Associate a repository remote URL with a local workspace folder or workspace file. + * Re-associating bumps recency and persists the updated LRU state. + * @param repoUrl Remote repository URL (e.g. https://github.com/owner/repo.git) + * @param rootPath Root path of the local repo clone. + */ + set(repoUrl: string, rootPath: string): void { + let foldersLru = this.lru.get(repoUrl); + if (!foldersLru) { + foldersLru = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + } + const folderPathOrWorkspaceFile: string | undefined = this._findWorkspaceForRepo(rootPath); + if (!folderPathOrWorkspaceFile) { + return; + } + + foldersLru.set(folderPathOrWorkspaceFile, true); // touch entry + this.lru.set(repoUrl, foldersLru); + this.save(); + } + + private _findWorkspaceForRepo(rootPath: string): string | undefined { + // If the current workspace is a workspace file, use that. Otherwise, find the workspace folder that contains the rootUri + let folderPathOrWorkspaceFile: string | undefined; + try { + if (this._workspaceFile) { + folderPathOrWorkspaceFile = this._workspaceFile.fsPath; + } else if (this._workspaceFolders && this._workspaceFolders.length) { + const sorted = [...this._workspaceFolders].sort((a, b) => b.uri.fsPath.length - a.uri.fsPath.length); + for (const folder of sorted) { + const folderPath = folder.uri.fsPath; + const relToFolder = path.relative(folderPath, rootPath); + if (relToFolder === '' || (!relToFolder.startsWith('..') && !path.isAbsolute(relToFolder))) { + folderPathOrWorkspaceFile = folderPath; + break; + } + const relFromFolder = path.relative(rootPath, folderPath); + if (relFromFolder === '' || (!relFromFolder.startsWith('..') && !path.isAbsolute(relFromFolder))) { + folderPathOrWorkspaceFile = folderPath; + break; + } + } + } + return folderPathOrWorkspaceFile; + } catch { + return; + } + + } + + update(addedRemotes: Remote[], removedRemotes: Remote[], rootPath: string): void { + for (const remote of removedRemotes) { + const url = remote.fetchUrl; + if (!url) { + continue; + } + const relatedWorkspace = this._findWorkspaceForRepo(rootPath); + if (relatedWorkspace) { + this.delete(url, relatedWorkspace); + } + } + + for (const remote of addedRemotes) { + const url = remote.fetchUrl; + if (!url) { + continue; + } + this.set(url, rootPath); + } + } + + /** + * We should possibly support converting between ssh remotes and http remotes. + */ + get(repoUrl: string): string[] | undefined { + const inner = this.lru.get(repoUrl); + return inner ? Array.from(inner.keys()) : undefined; + } + + delete(repoUrl: string, folderPathOrWorkspaceFile: string) { + const inner = this.lru.get(repoUrl); + if (!inner) { + return; + } + if (!inner.remove(folderPathOrWorkspaceFile)) { + return; + } + if (inner.size === 0) { + this.lru.remove(repoUrl); + } else { + // Re-set to bump outer LRU recency after modification + this.lru.set(repoUrl, inner); + } + this.save(); + } + + private load(): void { + try { + const raw = this._globalState.get<[string, [string, true][]][]>(RepositoryCache.STORAGE_KEY); + if (Array.isArray(raw)) { + for (const [repo, storedFolders] of raw) { + if (typeof repo !== 'string' || !Array.isArray(storedFolders)) { + continue; + } + const inner = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + for (const entry of storedFolders) { + let folderPath: string | undefined; + if (Array.isArray(entry) && entry.length === 2) { + const [workspaceFolder, _] = entry; + if (typeof workspaceFolder === 'string') { + folderPath = workspaceFolder; + } + } + if (folderPath) { + inner.set(folderPath, true); + } + } + if (inner.size) { + this.lru.set(repo, inner); + } + } + } + } catch { + this._logger.warn('[CachedRepositories][load] Failed to load cached repositories from global state.'); + } + } + + private save(): void { + // Serialize as [repoUrl, [folderPathOrWorkspaceFile, true][]] preserving outer LRU order. + const serialized: [string, [string, true][]][] = []; + for (const [repo, inner] of this.lru) { + const folders: [string, true][] = []; + for (const [folder, _] of inner) { + folders.push([folder, true]); + } + serialized.push([repo, folders]); + } + void this._globalState.update(RepositoryCache.STORAGE_KEY, serialized); + } +} diff --git a/extensions/git/src/test/repositoryCache.test.ts b/extensions/git/src/test/repositoryCache.test.ts new file mode 100644 index 00000000000..f5a8e315817 --- /dev/null +++ b/extensions/git/src/test/repositoryCache.test.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import { RepositoryCache } from '../repositoryCache'; +import { Event, EventEmitter, LogLevel, LogOutputChannel, Memento, Uri, WorkspaceFolder } from 'vscode'; + +class InMemoryMemento implements Memento { + private store = new Map(); + + constructor(initial?: Record) { + if (initial) { + for (const k of Object.keys(initial)) { + this.store.set(k, initial[k]); + } + } + } + + get(key: string): T | undefined; + get(key: string, defaultValue: T): T; + get(key: string, defaultValue?: T): T | undefined { + if (this.store.has(key)) { + return this.store.get(key); + } + return defaultValue as (T | undefined); + } + + update(key: string, value: any): Thenable { + this.store.set(key, value); + return Promise.resolve(); + } + + keys(): readonly string[] { + return Array.from(this.store.keys()); + } +} + +class MockLogOutputChannel implements LogOutputChannel { + logLevel: LogLevel = LogLevel.Info; + onDidChangeLogLevel: Event = new EventEmitter().event; + trace(_message: string, ..._args: any[]): void { } + debug(_message: string, ..._args: any[]): void { } + info(_message: string, ..._args: any[]): void { } + warn(_message: string, ..._args: any[]): void { } + error(_error: string | Error, ..._args: any[]): void { } + name: string = 'MockLogOutputChannel'; + append(_value: string): void { } + appendLine(_value: string): void { } + replace(_value: string): void { } + clear(): void { } + show(_column?: unknown, _preserveFocus?: unknown): void { } + hide(): void { } + dispose(): void { } +} + +class TestRepositoryCache extends RepositoryCache { + constructor(memento: Memento, logger: LogOutputChannel, private readonly _workspaceFileProp: Uri | undefined, private readonly _workspaceFoldersProp: readonly WorkspaceFolder[] | undefined) { + super(memento, logger); + } + + protected override get _workspaceFile() { + return this._workspaceFileProp; + } + + protected override get _workspaceFolders() { + return this._workspaceFoldersProp; + } +} + +suite('RepositoryCache', () => { + + test('set & get basic', () => { + const memento = new InMemoryMemento(); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: Uri.file('/workspace/repo'), name: 'workspace', index: 0 }]); + cache.set('https://example.com/repo.git', '/workspace/repo'); + const folders = cache.get('https://example.com/repo.git')!.map(folder => folder.replace(/\\/g, '/')); + assert.ok(folders, 'folders should be defined'); + assert.deepStrictEqual(folders, ['/workspace/repo']); + }); + + test('inner LRU capped at 10 entries', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + for (let i = 1; i <= 12; i++) { + workspaceFolders.push({ uri: Uri.file(`/ws/folder-${i.toString().padStart(2, '0')}`), name: `folder-${i.toString().padStart(2, '0')}`, index: i - 1 }); + } + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + const repo = 'https://example.com/repo.git'; + for (let i = 1; i <= 12; i++) { + cache.set(repo, `/ws/folder-${i.toString().padStart(2, '0')}`); + } + const folders = cache.get(repo)!.map(folder => folder.replace(/\\/g, '/')); + assert.strictEqual(folders.length, 10, 'should only retain 10 most recent folders'); + assert.ok(!folders.includes('/ws/folder-01'), 'oldest folder-01 should be evicted'); + assert.ok(!folders.includes('/ws/folder-02'), 'second oldest folder-02 should be evicted'); + assert.ok(folders.includes('/ws/folder-12'), 'latest folder should be present'); + }); + + test('outer LRU capped at 30 repos', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + for (let i = 1; i <= 35; i++) { + workspaceFolders.push({ uri: Uri.file(`/ws/r${i}`), name: `r${i}`, index: i - 1 }); + } + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + for (let i = 1; i <= 35; i++) { + const repo = `https://example.com/r${i}.git`; + cache.set(repo, `/ws/r${i}`); + } + assert.strictEqual(cache.get('https://example.com/r1.git'), undefined, 'oldest repo should be trimmed'); + assert.ok(cache.get('https://example.com/r35.git'), 'newest repo should remain'); + }); + + test('delete removes folder and prunes empty repo', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + workspaceFolders.push({ uri: Uri.file(`/ws/a`), name: `a`, index: 0 }); + workspaceFolders.push({ uri: Uri.file(`/ws/b`), name: `b`, index: 1 }); + + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + const repo = 'https://example.com/repo.git'; + const a = Uri.file('/ws/a').fsPath; + const b = Uri.file('/ws/b').fsPath; + cache.set(repo, a); + cache.set(repo, b); + assert.deepStrictEqual(new Set(cache.get(repo)!), new Set([a, b])); + cache.delete(repo, a); + assert.deepStrictEqual(cache.get(repo)!, [b]); + cache.delete(repo, b); + assert.strictEqual(cache.get(repo), undefined, 'repo should be pruned when last folder removed'); + }); +}); From 900c09271d195ecb0b30a3eb09f06858e147fc91 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 20 Oct 2025 11:26:50 -0700 Subject: [PATCH 1341/4355] Add back descriptions, remove settings fetch since it had no effect. --- extensions/terminal-suggest/package.json | 2 +- .../browser/terminal.suggest.contribution.ts | 18 +++++---------- .../common/terminalSuggestConfiguration.ts | 23 +++++++++++++------ .../terminalSuggestConfiguration.test.ts | 7 +++++- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index 2151c48ae6f..cbb5f54de6b 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -29,7 +29,7 @@ "completionProviders": [ { "id": "terminal-suggest", - "description": "Provides completions for terminal commands, arguments, flags, and file paths based upon the Fig spec." + "description": "Show suggestions for commands, arguments, flags, and file paths based upon the Fig spec." } ] } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 873d96ab9c0..c65da9afa01 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -21,7 +21,7 @@ import { registerActiveInstanceAction, registerTerminalAction } from '../../../t import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalSuggestCommandId } from '../common/terminal.suggest.js'; -import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration } from '../common/terminalSuggestConfiguration.js'; +import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration, ITerminalSuggestProviderInfo } from '../common/terminalSuggestConfiguration.js'; import { ITerminalCompletionService, TerminalCompletionService } from './terminalCompletionService.js'; import { ITerminalContributionService } from '../../../terminal/common/terminalExtensionPoints.js'; import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; @@ -39,7 +39,6 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { getTerminalLspSupportedLanguageObj } from './lspTerminalUtil.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { IStringDictionary } from '../../../../../base/common/collections.js'; registerSingleton(ITerminalCompletionService, TerminalCompletionService, InstantiationType.Delayed); @@ -472,7 +471,6 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { constructor( @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService, - @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this._register(this._terminalCompletionService.onDidChangeProviders(() => { @@ -487,21 +485,17 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { private _updateConfiguration(): void { // Add statically declared providers from package.json contributions - const providers = new Set(); - this._terminalContributionService.terminalCompletionProviders.forEach(o => providers.add(o.id)); + const providers = new Map(); + this._terminalContributionService.terminalCompletionProviders.forEach(o => providers.set(o.id, o)); // Add dynamically registered providers (that aren't already declared statically) for (const { id } of this._terminalCompletionService.providers) { - if (id) { - providers.add(id); + if (id && !providers.has(id)) { + providers.set(id, { id }); } } - // Add providers present in the user's config. - const configuredProviders = this._configurationService.getValue>(TerminalSuggestSettingId.Providers)?.properties ?? {}; - Object.keys(configuredProviders).forEach(o => providers.add(o)); - - registerTerminalSuggestProvidersConfiguration(Array.from(providers)); + registerTerminalSuggestProvidersConfiguration(providers); } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 42b451f822c..9635b905e76 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -188,24 +188,33 @@ export const terminalSuggestConfiguration: IStringDictionary) { const oldProvidersConfiguration = terminalSuggestProvidersConfiguration; const enableByDefault = product.quality !== 'stable'; - providerIds ??= []; - if (!providerIds.includes('lsp')) { - providerIds.push('lsp'); + providers ??= new Map(); + if (!providers.has('lsp')) { + providers.set('lsp', { + id: 'lsp', + description: localize('suggest.provider.lsp.description', 'Show suggestions from language servers.') + }); } - providerIds.sort(); const providersProperties: IStringDictionary = {}; - for (const id of providerIds) { + for (const id of Array.from(providers.keys()).sort()) { providersProperties[id] = { type: 'boolean', default: enableByDefault, - description: localize('suggest.provider.title', "Show suggestions from {0}", id) + description: + providers.get(id)?.description ?? + localize('suggest.provider.title', "Show suggestions from {0}", id) }; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts index cf4c2d9841c..f973e3b5d48 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalSuggestConfiguration.test.ts @@ -15,7 +15,12 @@ suite('Terminal Suggest Dynamic Configuration', () => { registerTerminalSuggestProvidersConfiguration(); // Test with some providers - const providers = ['terminal-suggest', 'builtinPwsh', 'lsp', 'custom-provider']; + const providers = new Map([ + ['terminal-suggest', { id: 'terminal-suggest', description: 'Provides intelligent completions for terminal commands' }], + ['builtinPwsh', { id: 'builtinPwsh', description: 'PowerShell completion provider' }], + ['lsp', { id: 'lsp' }], + ['custom-provider', { id: 'custom-provider' }], + ]); registerTerminalSuggestProvidersConfiguration(providers); // Test with empty providers From ecb6b29d4fd94f3ba9a5ee5268f9c1c350c0ad47 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 20 Oct 2025 11:27:57 -0700 Subject: [PATCH 1342/4355] Text fix --- .../suggest/common/terminalSuggestConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 9635b905e76..497ae7155bb 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -214,7 +214,7 @@ export function registerTerminalSuggestProvidersConfiguration(providers?: Map Date: Mon, 20 Oct 2025 11:30:18 -0700 Subject: [PATCH 1343/4355] Undo unnecessary changes. --- .../suggest/browser/terminal.suggest.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index c65da9afa01..69ae3d9f94c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -21,7 +21,7 @@ import { registerActiveInstanceAction, registerTerminalAction } from '../../../t import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalSuggestCommandId } from '../common/terminal.suggest.js'; -import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration, ITerminalSuggestProviderInfo } from '../common/terminalSuggestConfiguration.js'; +import { terminalSuggestConfigSection, TerminalSuggestSettingId, type ITerminalSuggestConfiguration, registerTerminalSuggestProvidersConfiguration, type ITerminalSuggestProviderInfo } from '../common/terminalSuggestConfiguration.js'; import { ITerminalCompletionService, TerminalCompletionService } from './terminalCompletionService.js'; import { ITerminalContributionService } from '../../../terminal/common/terminalExtensionPoints.js'; import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js'; @@ -470,7 +470,7 @@ class TerminalSuggestProvidersConfigurationManager extends Disposable { constructor( @ITerminalCompletionService private readonly _terminalCompletionService: ITerminalCompletionService, - @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService, + @ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService ) { super(); this._register(this._terminalCompletionService.onDidChangeProviders(() => { From 9d45ef57fae55a58398c3eda940a50de4fd77d70 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Oct 2025 11:39:46 -0700 Subject: [PATCH 1344/4355] Fix hover widget out of bounds (#271721) * fix hover widget * glyph hover widget --- .../multiDiffEditor/diffEditorItemTemplate.ts | 1 + .../contrib/hover/browser/glyphHoverWidget.ts | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 8e82b694509..7461b28f2b4 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -113,6 +113,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< ]) as Record; this.editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { overflowWidgetsDomNode: this._overflowWidgetsDomNode, + fixedOverflowWidgets: true }, {})); this.isModifedFocused = observableCodeEditor(this.editor.getModifiedEditor()).isFocused; this.isOriginalFocused = observableCodeEditor(this.editor.getOriginalEditor()).isFocused; diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index 4302560f260..486573a133d 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -19,6 +19,7 @@ const $ = dom.$; export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHoverWidget { public static readonly ID = 'editor.contrib.modesGlyphHoverWidget'; + public readonly allowEditorOverflow = true; private readonly _editor: ICodeEditor; private readonly _hover: HoverWidget; @@ -173,8 +174,28 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov const nodeHeight = this._hover.containerDomNode.clientHeight; const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); const left = editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth + (laneOrLine === 'lineNo' ? editorLayout.lineNumbersWidth : 0); - this._hover.containerDomNode.style.left = `${left}px`; - this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; + + // Constrain the hover widget to stay within the editor bounds + const editorHeight = editorLayout.height; + const maxTop = editorHeight - nodeHeight; + const constrainedTop = Math.max(0, Math.min(Math.round(top), maxTop)); + + const fixedOverflowWidgets = this._editor.getOption(EditorOption.fixedOverflowWidgets); + if (fixedOverflowWidgets) { + // Use fixed positioning relative to the viewport + const editorDomNode = this._editor.getDomNode(); + if (editorDomNode) { + const editorRect = dom.getDomNodePagePosition(editorDomNode); + this._hover.containerDomNode.style.position = 'fixed'; + this._hover.containerDomNode.style.left = `${editorRect.left + left}px`; + this._hover.containerDomNode.style.top = `${editorRect.top + constrainedTop}px`; + } + } else { + // Use absolute positioning relative to the editor + this._hover.containerDomNode.style.position = 'absolute'; + this._hover.containerDomNode.style.left = `${left}px`; + this._hover.containerDomNode.style.top = `${constrainedTop}px`; + } this._hover.containerDomNode.style.zIndex = '11'; // 1 more than the zone widget at 10 (#233819) } From ab73e46a31c628b8c9dcf52ccad77a8e1c59b0e2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 20 Oct 2025 21:09:44 +0200 Subject: [PATCH 1345/4355] rename mode to agent (#272282) * rename mode to agent * update * update * fix tests * update * update * update * update * update * update --- extensions/prompt-basics/package.json | 8 +- .../common/promptsSync/promptsSync.ts | 2 +- .../chat/browser/actions/chatActions.ts | 6 +- .../browser/actions/chatExecuteActions.ts | 8 +- .../chat/browser/actions/chatToolActions.ts | 4 +- .../contrib/chat/browser/chat.contribution.ts | 15 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatSetup.ts | 2 +- .../contrib/chat/browser/chatWidget.ts | 4 +- .../browser/promptSyntax/chatModeActions.ts | 36 ++--- .../promptSyntax/newPromptFileActions.ts | 22 +-- .../promptSyntax/pickers/askForPromptName.ts | 8 +- .../pickers/askForPromptSourceFolder.ts | 20 +-- .../promptSyntax/pickers/promptFilePickers.ts | 18 +-- .../browser/promptSyntax/promptFileActions.ts | 4 +- .../browser/promptSyntax/promptUrlHandler.ts | 5 +- .../contrib/chat/common/chatContextKeys.ts | 6 +- .../contrib/chat/common/chatModes.ts | 36 ++--- .../contrib/chat/common/constants.ts | 2 +- .../chatPromptFilesContribution.ts | 10 +- .../chat/common/promptSyntax/config/config.ts | 2 +- .../config/promptFileLocations.ts | 29 ++-- .../PromptHeaderDefinitionProvider.ts | 10 +- .../promptBodyAutocompletion.ts | 2 +- .../promptHeaderAutocompletion.ts | 20 +-- .../languageProviders/promptHovers.ts | 54 ++++---- .../languageProviders/promptValidator.ts | 82 ++++++----- .../chat/common/promptSyntax/promptTypes.ts | 18 +-- .../promptSyntax/service/newPromptsParser.ts | 4 +- .../promptSyntax/service/promptsService.ts | 87 +++--------- .../service/promptsServiceImpl.ts | 131 +++++++++++------- .../promptSyntax/utils/promptFilesLocator.ts | 9 +- .../promptSytntax/promptValidator.test.ts | 88 ++++++++---- .../chat/test/common/chatModeService.test.ts | 46 +++--- .../chat/test/common/mockPromptsService.ts | 10 +- .../promptSyntax/promptFileReference.test.ts | 44 +++--- .../service/newPromptsParser.test.ts | 32 ++--- .../service/promptsService.test.ts | 77 +++++----- .../browser/inlineChatCurrentLine.ts | 5 +- 39 files changed, 508 insertions(+), 464 deletions(-) diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index 91ea0c081b0..00f1f9be7bb 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -38,12 +38,14 @@ "configuration": "./language-configuration.json" }, { - "id": "chatmode", + "id": "agent", "aliases": [ - "Chat Mode", - "chat mode" + "Agent", + "chat agent" ], "extensions": [ + ".agent.md", + ".vscode-agent.md", ".chatmode.md" ], "configuration": "./language-configuration.json" diff --git a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts index a1f1b54b5f1..5b5aa362bd8 100644 --- a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts +++ b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts @@ -517,7 +517,7 @@ export class PromptsSynchronizer extends AbstractSynchroniser implements IUserDa for (const entry of stat.children || []) { const resource = entry.resource; const path = resource.path; - if (['.prompt.md', '.instructions.md', '.chatmode.md'].some(ext => path.endsWith(ext))) { + if (['.prompt.md', '.instructions.md', '.chatmode.md', '.vscode-agent.md'].some(ext => path.endsWith(ext))) { const key = this.extUri.relativePath(this.promptsFolder, resource)!; const content = await this.fileService.readFile(resource); prompts[key] = content; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index efaacb62d49..1df45c2c169 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1788,7 +1788,7 @@ export async function handleCurrentEditingSession(currentEditingSession: IChatEd } /** - * Returns whether we can switch the chat mode, based on whether the user had to agree to clear the session, false to cancel. + * Returns whether we can switch the agent, based on whether the user had to agree to clear the session, false to cancel. */ export async function handleModeSwitch( accessor: ServicesAccessor, @@ -1806,7 +1806,7 @@ export async function handleModeSwitch( const needToClearEdits = (!configurationService.getValue(ChatConfiguration.Edits2Enabled) && (fromMode === ChatModeKind.Edit || toMode === ChatModeKind.Edit)) && requestCount > 0; if (needToClearEdits) { // If not using edits2 and switching into or out of edit mode, ask to discard the session - const phrase = localize('switchMode.confirmPhrase', "Switching chat modes will end your current edit session."); + const phrase = localize('switchMode.confirmPhrase', "Switching agents will end your current edit session."); const currentEdits = editingSession.entries.get(); const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === ModifiedFileEntryState.Modified); @@ -1819,7 +1819,7 @@ export async function handleModeSwitch( } else { const confirmation = await dialogService.confirm({ title: localize('agent.newSession', "Start new session?"), - message: localize('agent.newSessionMessage', "Changing the chat mode will end your current edit session. Would you like to change the chat mode?"), + message: localize('agent.newSessionMessage', "Changing the agent will end your current edit session. Would you like to change the agent?"), primaryButton: localize('agent.newSession.confirm', "Yes"), type: 'info' }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index ab151f1a4d0..56e97edc435 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -280,9 +280,9 @@ export interface IToggleChatModeArgs { type ChatModeChangeClassification = { owner: 'digitarald'; - comment: 'Reporting when Chat mode is switched between different modes'; - fromMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The previous chat mode' }; - toMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The new chat mode' }; + comment: 'Reporting when agent is switched between different modes'; + fromMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The previous agent' }; + toMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The new agent' }; requestCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of requests in the current chat session'; 'isMeasurement': true }; }; @@ -299,7 +299,7 @@ class ToggleChatModeAction extends Action2 { constructor() { super({ id: ToggleChatModeAction.ID, - title: localize2('interactive.toggleAgent.label', "Switch to Next Chat Mode"), + title: localize2('interactive.toggleAgent.label', "Switch to Next Agent"), f1: true, category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index c079d2365c6..6df887faa03 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -157,8 +157,8 @@ class ConfigureToolsAction extends Action2 { description = localize('chat.tools.description.session', "The selected tools were configured by a prompt command and only apply to this chat session."); break; case ToolsScope.Mode: - placeholder = localize('chat.tools.placeholder.mode', "Select tools for this chat mode"); - description = localize('chat.tools.description.mode', "The selected tools are configured by the '{0}' chat mode. Changes to the tools will be applied to the mode file as well.", widget.input.currentModeObs.get().label); + placeholder = localize('chat.tools.placeholder.mode', "Select tools for this agent"); + description = localize('chat.tools.description.mode', "The selected tools are configured by the '{0}' agent. Changes to the tools will be applied to the agent file as well.", widget.input.currentModeObs.get().label); break; case ToolsScope.Global: placeholder = localize('chat.tools.placeholder.global', "Select tools that are available to chat."); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 61016419e09..c79d3b3fb10 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -51,9 +51,9 @@ import { ILanguageModelsService, LanguageModelsService } from '../common/languag import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; -import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, MODE_DEFAULT_SOURCE_FOLDER, MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js'; -import { INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; +import { INSTRUCTIONS_DOCUMENTATION_URL, AGENT_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { PromptsService } from '../common/promptSyntax/service/promptsServiceImpl.js'; import { LanguageModelToolsExtensionPointHandler } from '../common/tools/languageModelToolsContribution.js'; @@ -565,22 +565,23 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize( 'chat.mode.config.locations.description', "Specify location(s) of custom chat mode files (`*{0}`). [Learn More]({1}).\n\nRelative paths are resolved from the root folder(s) of your workspace.", - MODE_FILE_EXTENSION, - MODE_DOCUMENTATION_URL, + LEGACY_MODE_FILE_EXTENSION, + AGENT_DOCUMENTATION_URL, ), default: { - [MODE_DEFAULT_SOURCE_FOLDER]: true, + [LEGACY_MODE_DEFAULT_SOURCE_FOLDER]: true, }, + deprecationMessage: nls.localize('chat.mode.config.locations.deprecated', "This setting is deprecated and will be removed in future releases. Chat modes are now called agents and are located in `.github/agents`"), additionalProperties: { type: 'boolean' }, unevaluatedProperties: { type: 'boolean' }, restricted: true, tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], examples: [ { - [MODE_DEFAULT_SOURCE_FOLDER]: true, + [LEGACY_MODE_DEFAULT_SOURCE_FOLDER]: true, }, { - [MODE_DEFAULT_SOURCE_FOLDER]: true, + [LEGACY_MODE_DEFAULT_SOURCE_FOLDER]: true, '/Users/vscode/repos/chatmodes': true, }, ], diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 1a6bf0c81a6..708a459829c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -785,14 +785,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge let modeLabel = ''; switch (this.currentModeKind) { case ChatModeKind.Agent: - modeLabel = localize('chatInput.mode.agent', "(Agent Mode), edit files in your workspace."); + modeLabel = localize('chatInput.mode.agent', "(Agent), edit files in your workspace."); break; case ChatModeKind.Edit: - modeLabel = localize('chatInput.mode.edit', "(Edit Mode), edit files in your workspace."); + modeLabel = localize('chatInput.mode.edit', "(Edit), edit files in your workspace."); break; case ChatModeKind.Ask: default: - modeLabel = localize('chatInput.mode.ask', "(Ask Mode), ask questions or type / for topics."); + modeLabel = localize('chatInput.mode.ask', "(Ask), ask questions or type / for topics."); break; } if (verbose) { diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index a200b626aaa..ccb5e3e6a4e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1268,7 +1268,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const params = new URLSearchParams(url.query); this.telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined }); - const modeParam = params.get('mode'); + const modeParam = params.get('agent') ?? params.get('mode'); let modeToUse: ChatModeKind | string | undefined; if (modeParam) { // check if the given param is a valid mode ID diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index de779e66019..8c5a850b691 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2957,7 +2957,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.agentInInput.set(!!currentAgent); } - private async _applyPromptMetadata({ mode, tools, model }: PromptHeader, requestInput: IChatRequestInputOptions): Promise { + private async _applyPromptMetadata({ agent: mode, tools, model }: PromptHeader, requestInput: IChatRequestInputOptions): Promise { const currentMode = this.input.currentModeObs.get(); @@ -2965,7 +2965,7 @@ export class ChatWidget extends Disposable implements IChatWidget { mode = ChatModeKind.Agent; } - // switch to appropriate chat mode if needed + // switch to appropriate agent if needed if (mode && mode !== currentMode.name) { // Find the mode object to get its kind const chatMode = this.chatModeService.findModeByName(mode); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts index 4126bfc1b5c..6944ab99f59 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts @@ -17,7 +17,7 @@ import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { ChatViewId } from '../chat.js'; -abstract class ConfigModeActionImpl extends Action2 { +abstract class ConfigAgentActionImpl extends Action2 { public override async run(accessor: ServicesAccessor): Promise { const openerService = accessor.get(IOpenerService); const instaService = accessor.get(IInstantiationService); @@ -25,26 +25,26 @@ abstract class ConfigModeActionImpl extends Action2 { const pickers = instaService.createInstance(PromptFilePickers); const placeholder = localize( - 'commands.mode.select-dialog.placeholder', - 'Select the chat mode file to open' + 'commands.agent.select-dialog.placeholder', + 'Select the agent file to open' ); - const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.mode, optionEdit: false }); + const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.agent, optionEdit: false }); if (result !== undefined) { await openerService.open(result.promptFile); } } } -// Separate action `Configure Mode` link in the mode picker. +// Separate action `Configure Agent` link in the agent picker. -const PICKER_CONFIGURE_MODES_ACTION_ID = 'workbench.action.chat.picker.configmode'; +const PICKER_CONFIGURE_AGENTS_ACTION_ID = 'workbench.action.chat.picker.configagents'; -class PickerConfigModeAction extends ConfigModeActionImpl { +class PickerConfigAgentAction extends ConfigAgentActionImpl { constructor() { super({ - id: PICKER_CONFIGURE_MODES_ACTION_ID, - title: localize2('select-mode', "Configure Modes..."), + id: PICKER_CONFIGURE_AGENTS_ACTION_ID, + title: localize2('select-agent', "Configure Agents..."), category: CHAT_CATEGORY, f1: false, menu: { @@ -55,16 +55,16 @@ class PickerConfigModeAction extends ConfigModeActionImpl { } /** - * Action ID for the `Configure Custom Chat Mode` action. + * Action ID for the `Configure Agent` action. */ -const CONFIGURE_MODES_ACTION_ID = 'workbench.action.chat.manage.mode'; +const CONFIGURE_AGENTS_ACTION_ID = 'workbench.action.chat.manage.agents'; -class ManageModeAction extends ConfigModeActionImpl { +class ManageAgentsAction extends ConfigAgentActionImpl { constructor() { super({ - id: CONFIGURE_MODES_ACTION_ID, - title: localize2('configure-modes', "Configure Chat Modes..."), - shortTitle: localize('configure-modes.short', "Chat Modes"), + id: CONFIGURE_AGENTS_ACTION_ID, + title: localize2('configure-agents', "Configure Agents..."), + shortTitle: localize('configure-agents.short', "Agents"), icon: Codicon.bookmark, f1: true, precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), @@ -85,7 +85,7 @@ class ManageModeAction extends ConfigModeActionImpl { /** * Helper to register all the `Run Current Prompt` actions. */ -export function registerChatModeActions(): void { - registerAction2(ManageModeAction); - registerAction2(PickerConfigModeAction); +export function registerAgentActions(): void { + registerAction2(ManageAgentsAction); + registerAction2(PickerConfigAgentAction); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index 3be2c2f3538..83ec11802bb 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -112,7 +112,7 @@ class AbstractNewPromptFileAction extends Action2 { Severity.Info, localize( 'workbench.command.prompts.create.user.enable-sync-notification', - "Do you want to backup and sync your user prompt, instruction and mode files with Setting Sync?'", + "Do you want to backup and sync your user prompt, instruction and agent files with Setting Sync?'", ), [ { @@ -141,13 +141,13 @@ class AbstractNewPromptFileAction extends Action2 { } private getDefaultContentSnippet(promptType: PromptsType, chatModeService: IChatModeService): string { - const modes = chatModeService.getModes(); - const modeNames = modes.builtin.map(mode => mode.name).join(',') + (modes.custom.length ? (',' + modes.custom.map(mode => mode.name).join(',')) : ''); + const agents = chatModeService.getModes(); + const agentNames = agents.builtin.map(agent => agent.name).join(',') + (agents.custom.length ? (',' + agents.custom.map(agent => agent.name).join(',')) : ''); switch (promptType) { case PromptsType.prompt: return [ `---`, - `mode: \${1|${modeNames}|}`, + `agent: \${1|${agentNames}|}`, `---`, `\${2:Define the task to achieve, including specific requirements, constraints, and success criteria.}`, ].join('\n'); @@ -158,13 +158,13 @@ class AbstractNewPromptFileAction extends Action2 { `---`, `\${2:Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.}`, ].join('\n'); - case PromptsType.mode: + case PromptsType.agent: return [ `---`, - `description: '\${1:Description of the custom chat mode.}'`, + `description: '\${1:Description of the agent.}'`, `tools: []`, `---`, - `\${2:Define the purpose of this chat mode and how AI should behave: response style, available tools, focus areas, and any mode-specific instructions or constraints.}`, + `\${2:Define the purpose of this agent and how AI should behave: response style, available tools, focus areas, and any agent-specific instructions or constraints.}`, ].join('\n'); default: throw new Error(`Unknown prompt type: ${promptType}`); @@ -176,7 +176,7 @@ class AbstractNewPromptFileAction extends Action2 { export const NEW_PROMPT_COMMAND_ID = 'workbench.command.new.prompt'; export const NEW_INSTRUCTIONS_COMMAND_ID = 'workbench.command.new.instructions'; -export const NEW_MODE_COMMAND_ID = 'workbench.command.new.mode'; +export const NEW_AGENT_COMMAND_ID = 'workbench.command.new.agent'; class NewPromptFileAction extends AbstractNewPromptFileAction { constructor() { @@ -190,9 +190,9 @@ class NewInstructionsFileAction extends AbstractNewPromptFileAction { } } -class NewModeFileAction extends AbstractNewPromptFileAction { +class NewAgentFileAction extends AbstractNewPromptFileAction { constructor() { - super(NEW_MODE_COMMAND_ID, localize('commands.new.mode.local.title', "New Mode File..."), PromptsType.mode); + super(NEW_AGENT_COMMAND_ID, localize('commands.new.agent.local.title', "New Agent File..."), PromptsType.agent); } } @@ -242,6 +242,6 @@ class NewUntitledPromptFileAction extends Action2 { export function registerNewPromptFileActions(): void { registerAction2(NewPromptFileAction); registerAction2(NewInstructionsFileAction); - registerAction2(NewModeFileAction); + registerAction2(NewAgentFileAction); registerAction2(NewUntitledPromptFileAction); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts index af69d44f8ee..cce0b716c08 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptName.ts @@ -78,8 +78,8 @@ function getPlaceholderStringForNew(type: PromptsType): string { return localize('askForInstructionsFileName.placeholder', "Enter the name of the instructions file"); case PromptsType.prompt: return localize('askForPromptFileName.placeholder', "Enter the name of the prompt file"); - case PromptsType.mode: - return localize('askForModeFileName.placeholder', "Enter the name of the custom chat mode file"); + case PromptsType.agent: + return localize('askForAgentFileName.placeholder', "Enter the name of the agent file"); default: throw new Error('Unknown prompt type'); } @@ -91,8 +91,8 @@ function getPlaceholderStringForRename(type: PromptsType): string { return localize('askForRenamedInstructionsFileName.placeholder', "Enter a new name of the instructions file"); case PromptsType.prompt: return localize('askForRenamedPromptFileName.placeholder', "Enter a new name of the prompt file"); - case PromptsType.mode: - return localize('askForRenamedModeFileName.placeholder', "Enter a new name of the custom chat mode file"); + case PromptsType.agent: + return localize('askForRenamedAgentFileName.placeholder', "Enter a new name of the agent file"); default: throw new Error('Unknown prompt type'); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts index c8816722082..f919ee7d77b 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/askForPromptSourceFolder.ts @@ -109,8 +109,8 @@ function getPlaceholderStringforNew(type: PromptsType): string { return localize('workbench.command.instructions.create.location.placeholder', "Select a location to create the instructions file in..."); case PromptsType.prompt: return localize('workbench.command.prompt.create.location.placeholder', "Select a location to create the prompt file in..."); - case PromptsType.mode: - return localize('workbench.command.mode.create.location.placeholder', "Select a location to create the mode file in..."); + case PromptsType.agent: + return localize('workbench.command.agent.create.location.placeholder', "Select a location to create the agent file in..."); default: throw new Error('Unknown prompt type'); } @@ -123,8 +123,8 @@ function getPlaceholderStringforMove(type: PromptsType, isMove: boolean): string return localize('instructions.move.location.placeholder', "Select a location to move the instructions file to..."); case PromptsType.prompt: return localize('prompt.move.location.placeholder', "Select a location to move the prompt file to..."); - case PromptsType.mode: - return localize('mode.move.location.placeholder', "Select a location to move the mode file to..."); + case PromptsType.agent: + return localize('agent.move.location.placeholder', "Select a location to move the agent file to..."); default: throw new Error('Unknown prompt type'); } @@ -134,8 +134,8 @@ function getPlaceholderStringforMove(type: PromptsType, isMove: boolean): string return localize('instructions.copy.location.placeholder', "Select a location to copy the instructions file to..."); case PromptsType.prompt: return localize('prompt.copy.location.placeholder', "Select a location to copy the prompt file to..."); - case PromptsType.mode: - return localize('mode.copy.location.placeholder', "Select a location to copy the mode file to..."); + case PromptsType.agent: + return localize('agent.copy.location.placeholder', "Select a location to copy the agent file to..."); default: throw new Error('Unknown prompt type'); } @@ -177,8 +177,8 @@ function getLearnLabel(type: PromptsType): string { return localize('commands.prompts.create.ask-folder.empty.docs-label', 'Learn how to configure reusable prompts'); case PromptsType.instructions: return localize('commands.instructions.create.ask-folder.empty.docs-label', 'Learn how to configure reusable instructions'); - case PromptsType.mode: - return localize('commands.mode.create.ask-folder.empty.docs-label', 'Learn how to configure custom chat modes'); + case PromptsType.agent: + return localize('commands.agent.create.ask-folder.empty.docs-label', 'Learn how to configure custom agents'); default: throw new Error('Unknown prompt type'); } @@ -190,8 +190,8 @@ function getMissingSourceFolderString(type: PromptsType): string { return localize('commands.instructions.create.ask-folder.empty.placeholder', 'No instruction source folders found.'); case PromptsType.prompt: return localize('commands.prompts.create.ask-folder.empty.placeholder', 'No prompt source folders found.'); - case PromptsType.mode: - return localize('commands.mode.create.ask-folder.empty.placeholder', 'No custom chat mode source folders found.'); + case PromptsType.agent: + return localize('commands.agent.create.ask-folder.empty.placeholder', 'No agent source folders found.'); default: throw new Error('Unknown prompt type'); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index f453f91f2d2..b6bce16fa7b 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -16,8 +16,8 @@ import { IOpenerService } from '../../../../../../platform/opener/common/opener. import { IDialogService } from '../../../../../../platform/dialogs/common/dialogs.js'; import { ICommandService } from '../../../../../../platform/commands/common/commands.js'; import { getCleanPromptName } from '../../../common/promptSyntax/config/promptFileLocations.js'; -import { PromptsType, INSTRUCTIONS_DOCUMENTATION_URL, MODE_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../../../common/promptSyntax/promptTypes.js'; -import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID, NEW_MODE_COMMAND_ID } from '../newPromptFileActions.js'; +import { PromptsType, INSTRUCTIONS_DOCUMENTATION_URL, AGENT_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../../../common/promptSyntax/promptTypes.js'; +import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID, NEW_AGENT_COMMAND_ID } from '../newPromptFileActions.js'; import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from '../../../../../../platform/quickinput/common/quickInput.js'; import { askForPromptFileName } from './askForPromptName.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -140,17 +140,17 @@ const UPDATE_INSTRUCTIONS_OPTION: IPromptPickerQuickPickItem = Object.freeze({ /** * A quick pick item that starts the 'New Instructions File' command. */ -const NEW_MODE_FILE_OPTION: IPromptPickerQuickPickItem = Object.freeze({ +const NEW_AGENT_FILE_OPTION: IPromptPickerQuickPickItem = Object.freeze({ type: 'item', label: `$(plus) ${localize( - 'commands.new-modefile.select-dialog.label', - 'Create new custom chat mode file...', + 'commands.new-agentfile.select-dialog.label', + 'Create new agent file...', )}`, - value: URI.parse(MODE_DOCUMENTATION_URL), + value: URI.parse(AGENT_DOCUMENTATION_URL), pickable: false, alwaysShow: true, buttons: [HELP_BUTTON], - commandId: NEW_MODE_COMMAND_ID, + commandId: NEW_AGENT_COMMAND_ID, }); @@ -350,8 +350,8 @@ export class PromptFilePickers { return [NEW_PROMPT_FILE_OPTION]; case PromptsType.instructions: return [NEW_INSTRUCTIONS_FILE_OPTION, UPDATE_INSTRUCTIONS_OPTION]; - case PromptsType.mode: - return [NEW_MODE_FILE_OPTION]; + case PromptsType.agent: + return [NEW_AGENT_FILE_OPTION]; default: throw new Error(`Unknown prompt type '${type}'.`); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts index 46b22c19fef..bac1d683495 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerAttachPromptActions } from './attachInstructionsAction.js'; -import { registerChatModeActions } from './chatModeActions.js'; +import { registerAgentActions } from './chatModeActions.js'; import { registerRunPromptActions } from './runPromptAction.js'; import { registerSaveToPromptActions } from './saveToPromptAction.js'; import { registerNewPromptFileActions } from './newPromptFileActions.js'; @@ -17,6 +17,6 @@ export function registerPromptActions(): void { registerRunPromptActions(); registerAttachPromptActions(); registerSaveToPromptActions(); - registerChatModeActions(); + registerAgentActions(); registerNewPromptFileActions(); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts index 2875e5bcdb7..de7151064f8 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts @@ -58,7 +58,8 @@ export class PromptUrlHandler extends Disposable implements IWorkbenchContributi promptType = PromptsType.instructions; break; case 'chat-mode/install': - promptType = PromptsType.mode; + case 'chat-agent/install': + promptType = PromptsType.agent; break; default: return false; @@ -135,7 +136,7 @@ export class PromptUrlHandler extends Disposable implements IWorkbenchContributi message = localize('confirmInstallInstructions', "An external application wants to create an instructions file with content from a URL. Do you want to continue by selecting a destination folder and name?"); break; default: - message = localize('confirmInstallMode', "An external application wants to create a chat mode with content from a URL. Do you want to continue by selecting a destination folder and name?"); + message = localize('confirmInstallAgent', "An external application wants to create an agent with content from a URL. Do you want to continue by selecting a destination folder and name?"); break; } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 3ae052a3fde..49c2317cad2 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -34,8 +34,8 @@ export namespace ChatContextKeys { export const inChatSession = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); export const inChatEditor = new RawContextKey('inChatEditor', false, { type: 'boolean', description: localize('inChatEditor', "Whether focus is in a chat editor.") }); export const hasPromptFile = new RawContextKey('chatPromptFileAttached', false, { type: 'boolean', description: localize('chatPromptFileAttachedContextDescription', "True when the chat has a prompt file attached.") }); - export const chatModeKind = new RawContextKey('chatMode', ChatModeKind.Ask, { type: 'string', description: localize('chatMode', "The 'kind' of the current chat mode- Agent for custom modes.") }); - export const chatToolCount = new RawContextKey('chatToolCount', 0, { type: 'number', description: localize('chatToolCount', "The number of tools available in the current chat mode.") }); + export const chatModeKind = new RawContextKey('chatAgentKind', ChatModeKind.Ask, { type: 'string', description: localize('agentKind', "The 'kind' of the current agent.") }); + export const chatToolCount = new RawContextKey('chatToolCount', 0, { type: 'number', description: localize('chatToolCount', "The number of tools available in the current agent.") }); export const chatToolGroupingThreshold = new RawContextKey('chat.toolGroupingThreshold', 0, { type: 'number', description: localize('chatToolGroupingThreshold', "The number of tools at which we start doing virtual grouping.") }); export const supported = ContextKeyExpr.or(IsWebContext.negate(), RemoteNameContext.notEqualsTo(''), ContextKeyExpr.has('config.chat.experimental.serverlessWebEnabled')); @@ -84,7 +84,7 @@ export namespace ChatContextKeys { }; export const Modes = { - hasCustomChatModes: new RawContextKey('chatHasCustomChatModes', false, { type: 'boolean', description: localize('chatHasCustomChatModes', "True when the chat has custom chat modes available.") }), + hasCustomChatModes: new RawContextKey('chatHasCustomAgents', false, { type: 'boolean', description: localize('chatHasAgents', "True when the chat has custom agents available.") }), }; export const panelLocation = new RawContextKey('chatPanelLocation', undefined, { type: 'number', description: localize('chatPanelLocation', "The location of the chat panel.") }); diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 0b8f2db623c..d6675063aaa 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -19,7 +19,7 @@ import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; import { ChatModeKind } from './constants.js'; import { IHandOff } from './promptSyntax/service/newPromptsParser.js'; -import { IChatModeSource, ICustomChatMode, IPromptsService, PromptsStorage } from './promptSyntax/service/promptsService.js'; +import { IAgentSource, ICustomAgent, IPromptsService, PromptsStorage } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); export interface IChatModeService { @@ -58,7 +58,7 @@ export class ChatModeService extends Disposable implements IChatModeService { this.loadCachedModes(); void this.refreshCustomPromptModes(true); - this._register(this.promptsService.onDidChangeCustomChatModes(() => { + this._register(this.promptsService.onDidChangeCustomAgents(() => { void this.refreshCustomPromptModes(true); })); this._register(this.storageService.onWillSaveState(() => this.saveCachedModes())); @@ -80,7 +80,7 @@ export class ChatModeService extends Disposable implements IChatModeService { this.deserializeCachedModes(cachedCustomModes); } } catch (error) { - this.logService.error(error, 'Failed to load cached custom chat modes'); + this.logService.error(error, 'Failed to load cached custom agents'); } } @@ -94,20 +94,20 @@ export class ChatModeService extends Disposable implements IChatModeService { if (isCachedChatModeData(cachedMode) && cachedMode.uri) { try { const uri = URI.revive(cachedMode.uri); - const customChatMode: ICustomChatMode = { + const customChatMode: ICustomAgent = { uri, name: cachedMode.name, description: cachedMode.description, tools: cachedMode.customTools, model: cachedMode.model, - modeInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, + agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, handOffs: cachedMode.handOffs, source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } }; const instance = new CustomChatMode(customChatMode); this._customModeInstances.set(uri.toString(), instance); } catch (error) { - this.logService.error(error, 'Failed to revive cached custom chat mode'); + this.logService.error(error, 'Failed to revive cached custom agent'); } } } @@ -120,13 +120,13 @@ export class ChatModeService extends Disposable implements IChatModeService { const modesToCache = Array.from(this._customModeInstances.values()); this.storageService.store(ChatModeService.CUSTOM_MODES_STORAGE_KEY, modesToCache, StorageScope.WORKSPACE, StorageTarget.MACHINE); } catch (error) { - this.logService.warn('Failed to save cached custom chat modes', error); + this.logService.warn('Failed to save cached custom agents', error); } } private async refreshCustomPromptModes(fireChangeEvent?: boolean): Promise { try { - const customModes = await this.promptsService.getCustomChatModes(CancellationToken.None); + const customModes = await this.promptsService.getCustomAgents(CancellationToken.None); // Create a new set of mode instances, reusing existing ones where possible const seenUris = new Set(); @@ -155,7 +155,7 @@ export class ChatModeService extends Disposable implements IChatModeService { this.hasCustomModes.set(this._customModeInstances.size > 0); } catch (error) { - this.logService.error(error, 'Failed to load custom chat modes'); + this.logService.error(error, 'Failed to load custom agents'); this._customModeInstances.clear(); this.hasCustomModes.set(false); } @@ -223,7 +223,7 @@ export interface IChatMode { readonly model?: IObservable; readonly modeInstructions?: IObservable; readonly uri?: IObservable; - readonly source?: IChatModeSource; + readonly source?: IAgentSource; } export interface IVariableReference { @@ -262,7 +262,7 @@ export class CustomChatMode implements IChatMode { private readonly _uriObservable: ISettableObservable; private readonly _modelObservable: ISettableObservable; private readonly _handoffsObservable: ISettableObservable; - private _source: IChatModeSource; + private _source: IAgentSource; public readonly id: string; public readonly name: string; @@ -299,14 +299,14 @@ export class CustomChatMode implements IChatMode { return this._handoffsObservable; } - get source(): IChatModeSource { + get source(): IAgentSource { return this._source; } public readonly kind = ChatModeKind.Agent; constructor( - customChatMode: ICustomChatMode + customChatMode: ICustomAgent ) { this.id = customChatMode.uri.toString(); this.name = customChatMode.name; @@ -314,7 +314,7 @@ export class CustomChatMode implements IChatMode { this._customToolsObservable = observableValue('customTools', customChatMode.tools); this._modelObservable = observableValue('model', customChatMode.model); this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs); - this._modeInstructions = observableValue('_modeInstructions', customChatMode.modeInstructions); + this._modeInstructions = observableValue('_modeInstructions', customChatMode.agentInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); this._source = customChatMode.source; } @@ -322,14 +322,14 @@ export class CustomChatMode implements IChatMode { /** * Updates the underlying data and triggers observable changes */ - updateData(newData: ICustomChatMode): void { + updateData(newData: ICustomAgent): void { transaction(tx => { // Note- name is derived from ID, it can't change this._descriptionObservable.set(newData.description, tx); this._customToolsObservable.set(newData.tools, tx); this._modelObservable.set(newData.model, tx); this._handoffsObservable.set(newData.handOffs, tx); - this._modeInstructions.set(newData.modeInstructions, tx); + this._modeInstructions.set(newData.agentInstructions, tx); this._uriObservable.set(newData.uri, tx); this._source = newData.source; }); @@ -366,7 +366,7 @@ function isChatModeSourceData(value: unknown): value is IChatModeSourceData { return data.storage === PromptsStorage.local || data.storage === PromptsStorage.user; } -function serializeChatModeSource(source: IChatModeSource | undefined): IChatModeSourceData | undefined { +function serializeChatModeSource(source: IAgentSource | undefined): IChatModeSourceData | undefined { if (!source) { return undefined; } @@ -376,7 +376,7 @@ function serializeChatModeSource(source: IChatModeSource | undefined): IChatMode return { storage: source.storage }; } -function reviveChatModeSource(data: IChatModeSourceData | undefined): IChatModeSource | undefined { +function reviveChatModeSource(data: IChatModeSourceData | undefined): IAgentSource | undefined { if (!data) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 3501fbc5103..81203c9c643 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -24,7 +24,7 @@ export enum ChatConfiguration { } /** - * The "kind" of the chat mode- "Agent" for custom modes. + * The "kind" of agents for custom agents. */ export enum ChatModeKind { Ask = 'ask', diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts index 634c0b6d196..b10c316f49e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/chatPromptFilesContribution.ts @@ -19,7 +19,7 @@ interface IRawChatFileContribution { readonly description?: string; // reserved for future use } -type ChatContributionPoint = 'chatPromptFiles' | 'chatInstructions' | 'chatModes'; +type ChatContributionPoint = 'chatPromptFiles' | 'chatInstructions' | 'chatAgents'; function registerChatFilesExtensionPoint(point: ChatContributionPoint) { return extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ @@ -60,13 +60,13 @@ function registerChatFilesExtensionPoint(point: ChatContributionPoint) { const epPrompt = registerChatFilesExtensionPoint('chatPromptFiles'); const epInstructions = registerChatFilesExtensionPoint('chatInstructions'); -const epModes = registerChatFilesExtensionPoint('chatModes'); +const epAgents = registerChatFilesExtensionPoint('chatAgents'); function pointToType(contributionPoint: ChatContributionPoint): PromptsType { switch (contributionPoint) { case 'chatPromptFiles': return PromptsType.prompt; case 'chatInstructions': return PromptsType.instructions; - case 'chatModes': return PromptsType.mode; + case 'chatAgents': return PromptsType.agent; } } @@ -84,13 +84,13 @@ export class ChatPromptFilesExtensionPointHandler implements IWorkbenchContribut ) { this.handle(epPrompt, 'chatPromptFiles'); this.handle(epInstructions, 'chatInstructions'); - this.handle(epModes, 'chatModes'); + this.handle(epAgents, 'chatAgents'); } private handle(extensionPoint: extensionsRegistry.IExtensionPoint, contributionPoint: ChatContributionPoint) { extensionPoint.setHandler((_extensions, delta) => { for (const ext of delta.added) { - if (contributionPoint === 'chatModes') { + if (contributionPoint === 'chatAgents') { checkProposedApiEnabled(ext.description, 'chatParticipantPrivate'); } const type = pointToType(contributionPoint); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts index 9e07c2521b3..9f83b30ae32 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts @@ -227,7 +227,7 @@ export function getPromptFileLocationsConfigKey(type: PromptsType): string { return PromptsConfig.INSTRUCTIONS_LOCATION_KEY; case PromptsType.prompt: return PromptsConfig.PROMPT_LOCATIONS_KEY; - case PromptsType.mode: + case PromptsType.agent: return PromptsConfig.MODE_LOCATION_KEY; default: throw new Error('Unknown prompt type'); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index 007495ef853..741fee4406f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -20,7 +20,12 @@ export const INSTRUCTION_FILE_EXTENSION = '.instructions.md'; /** * File extension for the modes files. */ -export const MODE_FILE_EXTENSION = '.chatmode.md'; +export const LEGACY_MODE_FILE_EXTENSION = '.chatmode.md'; + +/** + * File extension for the agent files. + */ +export const AGENT_FILE_EXTENSION = '.vscode-agent.md'; /** * Copilot custom instructions file name. @@ -41,7 +46,12 @@ export const INSTRUCTIONS_DEFAULT_SOURCE_FOLDER = '.github/instructions'; /** * Default modes source folder. */ -export const MODE_DEFAULT_SOURCE_FOLDER = '.github/chatmodes'; +export const LEGACY_MODE_DEFAULT_SOURCE_FOLDER = '.github/chatmodes'; + +/** + * Agents folder. + */ +export const AGENTS_SOURCE_FOLDER = '.github/agents'; /** * Gets the prompt file type from the provided path. @@ -57,8 +67,8 @@ export function getPromptFileType(fileUri: URI): PromptsType | undefined { return PromptsType.instructions; } - if (filename.endsWith(MODE_FILE_EXTENSION)) { - return PromptsType.mode; + if (filename.endsWith(LEGACY_MODE_FILE_EXTENSION) || filename.endsWith(AGENT_FILE_EXTENSION)) { + return PromptsType.agent; } return undefined; @@ -77,8 +87,8 @@ export function getPromptFileExtension(type: PromptsType): string { return INSTRUCTION_FILE_EXTENSION; case PromptsType.prompt: return PROMPT_FILE_EXTENSION; - case PromptsType.mode: - return MODE_FILE_EXTENSION; + case PromptsType.agent: + return AGENT_FILE_EXTENSION; default: throw new Error('Unknown prompt type'); } @@ -90,8 +100,8 @@ export function getPromptFileDefaultLocation(type: PromptsType): string { return INSTRUCTIONS_DEFAULT_SOURCE_FOLDER; case PromptsType.prompt: return PROMPT_DEFAULT_SOURCE_FOLDER; - case PromptsType.mode: - return MODE_DEFAULT_SOURCE_FOLDER; + case PromptsType.agent: + return AGENTS_SOURCE_FOLDER; default: throw new Error('Unknown prompt type'); } @@ -107,7 +117,8 @@ export function getCleanPromptName(fileUri: URI): string { const extensions = [ PROMPT_FILE_EXTENSION, INSTRUCTION_FILE_EXTENSION, - MODE_FILE_EXTENSION, + LEGACY_MODE_FILE_EXTENSION, + AGENT_FILE_EXTENSION, ]; for (const ext of extensions) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts index 0657805ac18..0ceb5945f69 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts @@ -37,12 +37,12 @@ export class PromptHeaderDefinitionProvider implements DefinitionProvider { return undefined; } - const modeAttr = header.getAttribute('mode'); - if (modeAttr && modeAttr.value.type === 'string' && modeAttr.range.containsPosition(position)) { - const mode = this.chatModeService.findModeByName(modeAttr.value.value); - if (mode && mode.uri) { + const agentAttr = header.getAttribute('agent') ?? header.getAttribute('mode'); + if (agentAttr && agentAttr.value.type === 'string' && agentAttr.range.containsPosition(position)) { + const agent = this.chatModeService.findModeByName(agentAttr.value.value); + if (agent && agent.uri) { return { - uri: mode.uri.get(), + uri: agent.uri.get(), range: new Range(1, 1, 1, 1) }; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts index beb0435919f..0796fb1be1a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts @@ -56,7 +56,7 @@ export class PromptBodyAutocompletion implements CompletionItemProvider { } } else if (reference.type === '') { const promptFileType = getPromptFileType(model.uri); - if (promptFileType === PromptsType.mode || promptFileType === PromptsType.prompt) { + if (promptFileType === PromptsType.agent || promptFileType === PromptsType.prompt) { await this.collectToolCompletions(model, position, reference.contentRange, suggestions); } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index d84a8ac728d..09dba840229 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -133,7 +133,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return undefined; } - if (promptType === PromptsType.prompt || promptType === PromptsType.mode) { + if (promptType === PromptsType.prompt || promptType === PromptsType.agent) { // if the position is inside the tools metadata, we provide tool name completions const result = this.provideToolCompletions(model, position, header); if (result) { @@ -158,7 +158,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { }; suggestions.push(item); } - if (property === 'handoffs' && (promptType === PromptsType.mode)) { + if (property === 'handoffs' && (promptType === PromptsType.agent)) { const value = [ '', ' - label: Start Implementation', @@ -194,20 +194,20 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { if (promptType === PromptsType.instructions && property === 'applyTo') { return [`'**'`, `'**/*.ts, **/*.js'`, `'**/*.php'`, `'**/*.py'`]; } - if (promptType === PromptsType.prompt && property === 'mode') { - // Get all available modes (builtin + custom) - const modes = this.chatModeService.getModes(); + if (promptType === PromptsType.prompt && (property === 'agent' || property === 'mode')) { + // Get all available agents (builtin + custom) + const agents = this.chatModeService.getModes(); const suggestions: string[] = []; - for (const mode of Iterable.concat(modes.builtin, modes.custom)) { - suggestions.push(mode.name); + for (const agent of Iterable.concat(agents.builtin, agents.custom)) { + suggestions.push(agent.name); } return suggestions; } - if (property === 'tools' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) { + if (property === 'tools' && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { return ['[]', `['search', 'edit', 'fetch']`]; } - if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) { - return this.getModelNames(promptType === PromptsType.mode); + if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { + return this.getModelNames(promptType === PromptsType.agent); } return []; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 50a4db0b376..3f3a32fe387 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -77,23 +77,23 @@ export class PromptHoverProvider implements HoverProvider { return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: `**/*.ts`, `**/*.js`, `client/**`'), applyToRange); } - } else if (promptType === PromptsType.mode) { + } else if (promptType === PromptsType.agent) { const descriptionRange = header.getAttribute('description')?.range; if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.mode.description', 'The description of the mode file. It can be used to provide additional context or information about the mode to the mode author.'), descriptionRange); + return this.createHover(localize('promptHeader.agent.description', 'The description of the agent file. It is a short description of what the agent does.'), descriptionRange); } const model = header.getAttribute('model'); if (model?.range.containsPosition(position)) { - return this.getModelHover(model, model.range, localize('promptHeader.mode.model', 'The model to use in this mode.')); + return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'The model to use in this agent.')); } const tools = header.getAttribute('tools'); if (tools?.range.containsPosition(position)) { - return this.getToolHover(tools, position, localize('promptHeader.mode.tools', 'The tools to use in this mode.')); + return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The tools to use in this agent.')); } } else { const descriptionRange = header.getAttribute('description')?.range; if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It can be used to provide additional context or information about the prompt to the prompt author.'), descriptionRange); + return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It is a short description of what the prompt does.'), descriptionRange); } const model = header.getAttribute('model'); if (model?.range.containsPosition(position)) { @@ -103,9 +103,9 @@ export class PromptHoverProvider implements HoverProvider { if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.')); } - const mode = header.getAttribute('mode'); - if (mode?.range.containsPosition(position)) { - return this.getModeHover(mode, position, localize('promptHeader.prompt.mode', 'The mode to use in this prompt.')); + const agent = header.getAttribute('agent') ?? header.getAttribute('mode'); + if (agent?.range.containsPosition(position)) { + return this.getAgentHover(agent, position, localize('promptHeader.prompt.agent', 'The agent to use in this prompt.')); } } return undefined; @@ -166,37 +166,37 @@ export class PromptHoverProvider implements HoverProvider { return this.createHover(baseMessage, range); } - private getModeHover(mode: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { + private getAgentHover(agentAttribute: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { const lines: string[] = []; - const value = mode.value; + const value = agentAttribute.value; if (value.type === 'string' && value.range.containsPosition(position)) { - const mode = this.chatModeService.findModeByName(value.value); - if (mode) { - const description = mode.description.get() || (isBuiltinChatMode(mode) ? localize('promptHeader.prompt.mode.builtInDesc', 'Built-in chat mode') : localize('promptHeader.prompt.mode.customDesc', 'Custom chat mode')); - lines.push(`\`${mode.name}\`: ${description}`); + const agent = this.chatModeService.findModeByName(value.value); + if (agent) { + const description = agent.description.get() || (isBuiltinChatMode(agent) ? localize('promptHeader.prompt.agent.builtInDesc', 'Built-in agent') : localize('promptHeader.prompt.agent.customDesc', 'Custom agent')); + lines.push(`\`${agent.name}\`: ${description}`); } } else { - const modes = this.chatModeService.getModes(); - lines.push(localize('promptHeader.prompt.mode.description', 'The chat mode to use when running this prompt.')); + const agents = this.chatModeService.getModes(); + lines.push(localize('promptHeader.prompt.agent.description', 'The agent to use when running this prompt.')); lines.push(''); - // Built-in modes - lines.push(localize('promptHeader.prompt.mode.builtin', '**Built-in modes:**')); - for (const mode of modes.builtin) { - lines.push(`- \`${mode.name}\`: ${mode.description.get() || mode.label}`); + // Built-in agents + lines.push(localize('promptHeader.prompt.agent.builtin', '**Built-in agents:**')); + for (const agent of agents.builtin) { + lines.push(`- \`${agent.name}\`: ${agent.description.get() || agent.label}`); } - // Custom modes - if (modes.custom.length > 0) { + // Custom agents + if (agents.custom.length > 0) { lines.push(''); - lines.push(localize('promptHeader.prompt.mode.custom', '**Custom modes:**')); - for (const mode of modes.custom) { - const description = mode.description.get(); - lines.push(`- \`${mode.name}\`: ${description || localize('promptHeader.prompt.mode.customDesc', 'Custom chat mode')}`); + lines.push(localize('promptHeader.prompt.agent.custom', '**Custom agents:**')); + for (const agent of agents.custom) { + const description = agent.description.get(); + lines.push(`- \`${agent.name}\`: ${description || localize('promptHeader.prompt.agent.customDesc', 'Custom agent')}`); } } } - return this.createHover(lines.join('\n'), mode.range); + return this.createHover(lines.join('\n'), agentAttribute.range); } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index b3462c6d400..065c28d92a2 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -115,8 +115,8 @@ export class PromptValidator { case PromptsType.prompt: report(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); break; - case PromptsType.mode: - report(toMarker(localize('promptValidator.unknownAttribute.mode', "Attribute '{0}' is not supported in mode files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); + case PromptsType.agent: + report(toMarker(localize('promptValidator.unknownAttribute.agent', "Attribute '{0}' is not supported in agent files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); break; case PromptsType.instructions: report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); @@ -127,9 +127,9 @@ export class PromptValidator { this.validateDescription(attributes, report); switch (promptType) { case PromptsType.prompt: { - const mode = this.validateMode(attributes, report); - this.validateTools(attributes, mode?.kind ?? ChatModeKind.Agent, report); - this.validateModel(attributes, mode?.kind ?? ChatModeKind.Agent, report); + const agent = this.validateAgent(attributes, report); + this.validateTools(attributes, agent?.kind ?? ChatModeKind.Agent, report); + this.validateModel(attributes, agent?.kind ?? ChatModeKind.Agent, report); break; } case PromptsType.instructions: @@ -137,7 +137,7 @@ export class PromptValidator { this.validateExcludeAgent(attributes, report); break; - case PromptsType.mode: + case PromptsType.agent: this.validateTools(attributes, ChatModeKind.Agent, report); this.validateModel(attributes, ChatModeKind.Agent, report); this.validateHandoffs(attributes, report); @@ -162,7 +162,7 @@ export class PromptValidator { } - private validateModel(attributes: IHeaderAttribute[], modeKind: ChatModeKind, report: (markers: IMarkerData) => void): void { + private validateModel(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): void { const attribute = attributes.find(attr => attr.key === 'model'); if (!attribute) { return; @@ -186,7 +186,7 @@ export class PromptValidator { if (!modelMetadata) { report(toMarker(localize('promptValidator.modelNotFound', "Unknown model '{0}'.", modelName), attribute.value.range, MarkerSeverity.Warning)); - } else if (modeKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { + } else if (agentKind === ChatModeKind.Agent && !ILanguageModelChatMetadata.suitableForAgentMode(modelMetadata)) { report(toMarker(localize('promptValidator.modelNotSuited', "Model '{0}' is not suited for agent mode.", modelName), attribute.value.range, MarkerSeverity.Warning)); } } @@ -201,43 +201,53 @@ export class PromptValidator { return undefined; } - private validateMode(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): IChatMode | undefined { - const attribute = attributes.find(attr => attr.key === 'mode'); + private validateAgent(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): IChatMode | undefined { + const agentAttribute = attributes.find(attr => attr.key === 'agent'); + const modeAttribute = attributes.find(attr => attr.key === 'mode'); + if (modeAttribute) { + if (agentAttribute) { + report(toMarker(localize('promptValidator.modeDeprecated', "The 'mode' attribute has been deprecated. The 'agent' attribute is used instead."), modeAttribute.range, MarkerSeverity.Warning)); + } else { + report(toMarker(localize('promptValidator.modeDeprecated.useAgent', "The 'mode' attribute has been deprecated. Please rename it to 'agent'."), modeAttribute.range, MarkerSeverity.Error)); + } + } + + const attribute = attributes.find(attr => attr.key === 'agent') ?? modeAttribute; if (!attribute) { - return undefined; // default mode for prompts is Agent + return undefined; // default agent for prompts is Agent } if (attribute.value.type !== 'string') { - report(toMarker(localize('promptValidator.modeMustBeString', "The 'mode' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + report(toMarker(localize('promptValidator.attributeMustBeString', "The '{0}' attribute must be a string.", attribute.key), attribute.value.range, MarkerSeverity.Error)); return undefined; } - const modeValue = attribute.value.value; - if (modeValue.trim().length === 0) { - report(toMarker(localize('promptValidator.modeMustBeNonEmpty', "The 'mode' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + const agentValue = attribute.value.value; + if (agentValue.trim().length === 0) { + report(toMarker(localize('promptValidator.attributeMustBeNonEmpty', "The '{0}' attribute must be a non-empty string.", attribute.key), attribute.value.range, MarkerSeverity.Error)); return undefined; } - const modes = this.chatModeService.getModes(); - const availableModes = []; + const agents = this.chatModeService.getModes(); + const availableAgents = []; - // Check if mode exists in builtin or custom modes - for (const mode of Iterable.concat(modes.builtin, modes.custom)) { - if (mode.name === modeValue) { - return mode; + // Check if agent exists in builtin or custom agents + for (const agent of Iterable.concat(agents.builtin, agents.custom)) { + if (agent.name === agentValue) { + return agent; } - availableModes.push(mode.name); // collect all available mode names + availableAgents.push(agent.name); // collect all available agent names } - const errorMessage = localize('promptValidator.modeNotFound', "Unknown mode '{0}'. Available modes: {1}.", modeValue, availableModes.join(', ')); + const errorMessage = localize('promptValidator.agentNotFound', "Unknown agent '{0}'. Available agents: {1}.", agentValue, availableAgents.join(', ')); report(toMarker(errorMessage, attribute.value.range, MarkerSeverity.Warning)); return undefined; } - private validateTools(attributes: IHeaderAttribute[], modeKind: ChatModeKind, report: (markers: IMarkerData) => void): undefined { + private validateTools(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): undefined { const attribute = attributes.find(attr => attr.key === 'tools'); if (!attribute) { return; } - if (modeKind !== ChatModeKind.Agent) { + if (agentKind !== ChatModeKind.Agent) { report(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); } @@ -357,23 +367,23 @@ export class PromptValidator { } } -const validAttributeNames = { - [PromptsType.prompt]: ['description', 'model', 'tools', 'mode'], +const allAttributeNames = { + [PromptsType.prompt]: ['description', 'model', 'tools', 'mode', 'agent'], [PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'], - [PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs'] + [PromptsType.agent]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs'] }; -const validAttributeNamesNoExperimental = { - [PromptsType.prompt]: validAttributeNames[PromptsType.prompt].filter(name => !isExperimentalAttribute(name)), - [PromptsType.instructions]: validAttributeNames[PromptsType.instructions].filter(name => !isExperimentalAttribute(name)), - [PromptsType.mode]: validAttributeNames[PromptsType.mode].filter(name => !isExperimentalAttribute(name)) +const recommendedAttributeNames = { + [PromptsType.prompt]: allAttributeNames[PromptsType.prompt].filter(name => !isNonRecommendedAttribute(name)), + [PromptsType.instructions]: allAttributeNames[PromptsType.instructions].filter(name => !isNonRecommendedAttribute(name)), + [PromptsType.agent]: allAttributeNames[PromptsType.agent].filter(name => !isNonRecommendedAttribute(name)) }; -export function getValidAttributeNames(promptType: PromptsType, includeExperimental: boolean): string[] { - return includeExperimental ? validAttributeNames[promptType] : validAttributeNamesNoExperimental[promptType]; +export function getValidAttributeNames(promptType: PromptsType, includeNonRecommended: boolean): string[] { + return includeNonRecommended ? allAttributeNames[promptType] : recommendedAttributeNames[promptType]; } -export function isExperimentalAttribute(attributeName: string): boolean { - return attributeName === 'advancedOptions' || attributeName === 'excludeAgent'; +export function isNonRecommendedAttribute(attributeName: string): boolean { + return attributeName === 'advancedOptions' || attributeName === 'excludeAgent' || attributeName === 'mode'; } function toMarker(message: string, range: Range, severity = MarkerSeverity.Error): IMarkerData { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts index a1f2f9c8569..bae4b5626c4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts @@ -10,7 +10,7 @@ import { LanguageSelector } from '../../../../../editor/common/languageSelector. */ export const PROMPT_DOCUMENTATION_URL = 'https://aka.ms/vscode-ghcp-prompt-snippets'; export const INSTRUCTIONS_DOCUMENTATION_URL = 'https://aka.ms/vscode-ghcp-custom-instructions'; -export const MODE_DOCUMENTATION_URL = 'https://aka.ms/vscode-ghcp-custom-chat-modes'; // todo +export const AGENT_DOCUMENTATION_URL = 'https://aka.ms/vscode-ghcp-custom-chat-modes'; // todo /** * Language ID for the reusable prompt syntax. @@ -23,14 +23,14 @@ export const PROMPT_LANGUAGE_ID = 'prompt'; export const INSTRUCTIONS_LANGUAGE_ID = 'instructions'; /** - * Language ID for modes syntax. + * Language ID for agent syntax. */ -export const MODE_LANGUAGE_ID = 'chatmode'; +export const AGENT_LANGUAGE_ID = 'agent'; /** * Prompt and instructions files language selector. */ -export const ALL_PROMPTS_LANGUAGE_SELECTOR: LanguageSelector = [PROMPT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID, MODE_LANGUAGE_ID]; +export const ALL_PROMPTS_LANGUAGE_SELECTOR: LanguageSelector = [PROMPT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID, AGENT_LANGUAGE_ID]; /** * The language id for for a prompts type. @@ -41,8 +41,8 @@ export function getLanguageIdForPromptsType(type: PromptsType): string { return PROMPT_LANGUAGE_ID; case PromptsType.instructions: return INSTRUCTIONS_LANGUAGE_ID; - case PromptsType.mode: - return MODE_LANGUAGE_ID; + case PromptsType.agent: + return AGENT_LANGUAGE_ID; default: throw new Error(`Unknown prompt type: ${type}`); } @@ -54,8 +54,8 @@ export function getPromptsTypeForLanguageId(languageId: string): PromptsType | u return PromptsType.prompt; case INSTRUCTIONS_LANGUAGE_ID: return PromptsType.instructions; - case MODE_LANGUAGE_ID: - return PromptsType.mode; + case AGENT_LANGUAGE_ID: + return PromptsType.agent; default: return undefined; } @@ -68,7 +68,7 @@ export function getPromptsTypeForLanguageId(languageId: string): PromptsType | u export enum PromptsType { instructions = 'instructions', prompt = 'prompt', - mode = 'mode' + agent = 'agent' } export function isValidPromptType(type: string): type is PromptsType { return Object.values(PromptsType).includes(type as PromptsType); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index d0cb0143a72..11aa2c2bd4c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -140,8 +140,8 @@ export class PromptHeader { return this.getStringAttribute('description'); } - public get mode(): string | undefined { - return this.getStringAttribute('mode'); + public get agent(): string | undefined { + return this.getStringAttribute('agent') ?? this.getStringAttribute('mode'); } public get model(): string | undefined { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 399f9941714..1e274cb67e1 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -10,8 +10,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { ExtensionIdentifier, IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IChatModeInstructions } from '../../chatModes.js'; -import { ChatModeKind } from '../../constants.js'; +import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; import { PromptsType } from '../promptTypes.js'; import { IHandOff, ParsedPromptFile } from './newPromptsParser.js'; @@ -75,39 +74,26 @@ export interface IUserPromptPath extends IPromptPathBase { readonly storage: PromptsStorage.user; } -export type IChatModeSource = { +export type IAgentSource = { readonly storage: PromptsStorage.extension; readonly extensionId: ExtensionIdentifier; } | { readonly storage: PromptsStorage.local | PromptsStorage.user; }; -export function promptPathToChatModeSource(promptPath: IPromptPath): IChatModeSource { - if (promptPath.storage === PromptsStorage.extension) { - return { - storage: PromptsStorage.extension, - extensionId: promptPath.extension.identifier - }; - } else { - return { - storage: promptPath.storage - }; - } -} - -export interface ICustomChatMode { +export interface ICustomAgent { /** - * URI of a custom chat mode file. + * URI of a custom agent file. */ readonly uri: URI; /** - * Name of the custom chat mode as used in prompt files or contexts + * Name of the custom agent as used in prompt files or contexts */ readonly name: string; /** - * Description of the mode + * Description of the agent */ readonly description?: string; @@ -122,62 +108,27 @@ export interface ICustomChatMode { readonly model?: string; /** - * Contents of the custom chat mode file body and other mode instructions. + * Contents of the custom agent file body and other agent instructions. */ - readonly modeInstructions: IChatModeInstructions; + readonly agentInstructions: IChatModeInstructions; /** - * Hand-offs defined in the custom chat mode file. + * Hand-offs defined in the custom agent file. */ readonly handOffs?: readonly IHandOff[]; /** - * Where the mode was loaded from. + * Where the agent was loaded from. */ - readonly source: IChatModeSource; + readonly source: IAgentSource; } -/** - * Type of combined tools metadata for the case - * when the prompt is in the agent mode. - */ -interface ICombinedAgentToolsMetadata { - /** - * List of combined tools metadata for - * the entire tree of prompt references. - */ - readonly tools: readonly string[] | undefined; - - /** - * Resulting chat mode of a prompt, based on modes - * used in the entire tree of prompt references. - */ - readonly mode: ChatModeKind.Agent; +export interface IAgentInstructions { + readonly content: string; + readonly toolReferences: readonly IVariableReference[]; + readonly metadata?: Record; } -/** - * Type of combined tools metadata for the case - * when the prompt is in non-agent mode. - */ -interface ICombinedNonAgentToolsMetadata { - /** - * List of combined tools metadata is empty - * when the prompt is in non-agent mode. - */ - readonly tools: undefined; - - /** - * Resulting chat mode of a prompt, based on modes - * used in the entire tree of prompt references. - */ - readonly mode?: ChatModeKind.Ask | ChatModeKind.Edit; -} - -/** - * General type of the combined tools metadata. - */ -export type TCombinedToolsMetadata = ICombinedAgentToolsMetadata | ICombinedNonAgentToolsMetadata; - /** * Provides prompt services. */ @@ -227,14 +178,14 @@ export interface IPromptsService extends IDisposable { getPromptCommandName(uri: URI): Promise; /** - * Event that is triggered when the list of custom chat modes changes. + * Event that is triggered when the list of custom agents changes. */ - readonly onDidChangeCustomChatModes: Event; + readonly onDidChangeCustomAgents: Event; /** - * Finds all available custom chat modes + * Finds all available custom agents */ - getCustomChatModes(token: CancellationToken): Promise; + getCustomAgents(token: CancellationToken): Promise; /** * Parses the provided URI diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index cc2d468f76d..f10cdc2a937 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -25,13 +25,13 @@ import { ILabelService } from '../../../../../../platform/label/common/label.js' import { ILogService } from '../../../../../../platform/log/common/log.js'; import { IFilesConfigurationService } from '../../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { IUserDataProfileService } from '../../../../../services/userDataProfile/common/userDataProfile.js'; -import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; +import { IVariableReference } from '../../chatModes.js'; import { PromptsConfig } from '../config/config.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; -import { getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; -import { IChatModeSource, IChatPromptSlashCommand, ICustomChatMode, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, promptPathToChatModeSource, PromptsStorage } from './promptsService.js'; +import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; /** * Provides prompt services. @@ -45,9 +45,9 @@ export class PromptsService extends Disposable implements IPromptsService { private readonly fileLocator: PromptFilesLocator; /** - * Cached custom modes. Caching only happens if the `onDidChangeCustomChatModes` event is used. + * Cached custom agents. Caching only happens if the `onDidChangeCustomAgents` event is used. */ - private cachedCustomChatModes: Promise | undefined; + private cachedCustomAgents: Promise | undefined; private parsedPromptFileCache = new ResourceMap<[number, ParsedPromptFile]>(); @@ -58,13 +58,13 @@ export class PromptsService extends Disposable implements IPromptsService { private readonly contributedFiles = { [PromptsType.prompt]: new ResourceMap>(), [PromptsType.instructions]: new ResourceMap>(), - [PromptsType.mode]: new ResourceMap>(), + [PromptsType.agent]: new ResourceMap>(), }; /** - * Lazily created event that is fired when the custom chat modes change. + * Lazily created event that is fired when the custom agents change. */ - private onDidChangeCustomChatModesEmitter: Emitter | undefined; + private onDidChangeCustomAgentsEmitter: Emitter | undefined; constructor( @ILogService public readonly logger: ILogService, @@ -87,18 +87,18 @@ export class PromptsService extends Disposable implements IPromptsService { } /** - * Emitter for the custom chat modes change event. + * Emitter for the custom agents change event. */ - public get onDidChangeCustomChatModes(): Event { - if (!this.onDidChangeCustomChatModesEmitter) { - const emitter = this.onDidChangeCustomChatModesEmitter = this._register(new Emitter()); - const chatModelTracker = this._register(new ChatModeUpdateTracker(this.fileLocator, this.modelService)); - this._register(chatModelTracker.onDidChangeContent(() => { - this.cachedCustomChatModes = undefined; // reset cached custom chat modes + public get onDidChangeCustomAgents(): Event { + if (!this.onDidChangeCustomAgentsEmitter) { + const emitter = this.onDidChangeCustomAgentsEmitter = this._register(new Emitter()); + const updateTracker = this._register(new UpdateTracker(this.fileLocator, PromptsType.agent, this.modelService)); + this._register(updateTracker.onDidChangeContent(() => { + this.cachedCustomAgents = undefined; // reset cached custom agents emitter.fire(); })); } - return this.onDidChangeCustomChatModesEmitter.event; + return this.onDidChangeCustomAgentsEmitter.event; } public getPromptFileType(uri: URI): PromptsType | undefined { @@ -162,9 +162,17 @@ export class PromptsService extends Disposable implements IPromptsService { const result: IPromptPath[] = []; - for (const uri of this.fileLocator.getConfigBasedSourceFolders(type)) { - result.push({ uri, storage: PromptsStorage.local, type }); + if (type === PromptsType.agent) { + const folders = this.fileLocator.getAgentSourceFolder(); + for (const uri of folders) { + result.push({ uri, storage: PromptsStorage.local, type }); + } + } else { + for (const uri of this.fileLocator.getConfigBasedSourceFolders(type)) { + result.push({ uri, storage: PromptsStorage.local, type }); + } } + const userHome = this.userDataService.currentProfile.promptsHome; result.push({ uri: userHome, storage: PromptsStorage.user, type }); @@ -231,23 +239,23 @@ export class PromptsService extends Disposable implements IPromptsService { }); } - public async getCustomChatModes(token: CancellationToken): Promise { - if (!this.cachedCustomChatModes) { - const customChatModes = this.computeCustomChatModes(token); - if (!this.onDidChangeCustomChatModesEmitter) { - return customChatModes; + public async getCustomAgents(token: CancellationToken): Promise { + if (!this.cachedCustomAgents) { + const customAgents = this.computeCustomAgents(token); + if (!this.onDidChangeCustomAgentsEmitter) { + return customAgents; } - this.cachedCustomChatModes = customChatModes; + this.cachedCustomAgents = customAgents; } - return this.cachedCustomChatModes; + return this.cachedCustomAgents; } - private async computeCustomChatModes(token: CancellationToken): Promise { - const modeFiles = await this.listPromptFiles(PromptsType.mode, token); + private async computeCustomAgents(token: CancellationToken): Promise { + const agentFiles = await this.listPromptFiles(PromptsType.agent, token); - const customChatModes = await Promise.all( - modeFiles.map(async (promptPath): Promise => { - const { uri, name: modeName } = promptPath; + const customAgents = await Promise.all( + agentFiles.map(async (promptPath): Promise => { + const { uri, name: agentName } = promptPath; const ast = await this.parseNew(uri, token); let metadata: any | undefined; @@ -273,24 +281,24 @@ export class PromptsService extends Disposable implements IPromptsService { } } - const modeInstructions = { + const agentInstructions = { content: ast.body?.getContent() ?? '', toolReferences, metadata, - } satisfies IChatModeInstructions; + } satisfies IAgentInstructions; - const name = modeName ?? getCleanPromptName(uri); + const name = agentName ?? getCleanPromptName(uri); - const source: IChatModeSource = promptPathToChatModeSource(promptPath); + const source: IAgentSource = IAgentSource.fromPromptPath(promptPath); if (!ast.header) { - return { uri, name, modeInstructions, source }; + return { uri, name, agentInstructions, source }; } const { description, model, tools, handOffs } = ast.header; - return { uri, name, description, model, tools, handOffs, modeInstructions, source }; + return { uri, name, description, model, tools, handOffs, agentInstructions, source }; }) ); - return customChatModes; + return customAgents; } public async parseNew(uri: URI, token: CancellationToken): Promise { @@ -322,17 +330,17 @@ export class PromptsService extends Disposable implements IPromptsService { })(); bucket.set(uri, entryPromise); - const updateModesIfRequired = () => { - if (type === PromptsType.mode) { - this.cachedCustomChatModes = undefined; - this.onDidChangeCustomChatModesEmitter?.fire(); + const updateAgentsIfRequired = () => { + if (type === PromptsType.agent) { + this.cachedCustomAgents = undefined; + this.onDidChangeCustomAgentsEmitter?.fire(); } }; - updateModesIfRequired(); + updateAgentsIfRequired(); return { dispose: () => { bucket.delete(uri); - updateModesIfRequired(); + updateAgentsIfRequired(); } }; } @@ -381,36 +389,37 @@ function getCommandNameFromURI(uri: URI): string { return basename(uri.fsPath, PROMPT_FILE_EXTENSION); } -export class ChatModeUpdateTracker extends Disposable { +export class UpdateTracker extends Disposable { - private static readonly CHAT_MODE_UPDATE_DELAY_MS = 200; + private static readonly CHAT_AGENT_UPDATE_DELAY_MS = 200; private readonly listeners = new ResourceMap(); - private readonly onDidChatModeModelChange: Emitter; + private readonly onDidChangeContentEmitter: Emitter; public get onDidChangeContent(): Event { - return this.onDidChatModeModelChange.event; + return this.onDidChangeContentEmitter.event; } constructor( fileLocator: PromptFilesLocator, + promptTypes: PromptsType, @IModelService modelService: IModelService, ) { super(); - this.onDidChatModeModelChange = this._register(new Emitter()); - const delayer = this._register(new Delayer(ChatModeUpdateTracker.CHAT_MODE_UPDATE_DELAY_MS)); - const trigger = () => delayer.trigger(() => this.onDidChatModeModelChange.fire()); + this.onDidChangeContentEmitter = this._register(new Emitter()); + const delayer = this._register(new Delayer(UpdateTracker.CHAT_AGENT_UPDATE_DELAY_MS)); + const trigger = () => delayer.trigger(() => this.onDidChangeContentEmitter.fire()); - const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.mode)); + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(promptTypes)); this._register(filesUpdatedEventRegistration.event(() => trigger())); const onAdd = (model: ITextModel) => { - if (model.getLanguageId() === MODE_LANGUAGE_ID) { + if (model.getLanguageId() === AGENT_LANGUAGE_ID) { this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); } }; const onRemove = (languageId: string, uri: URI) => { - if (languageId === MODE_LANGUAGE_ID) { + if (languageId === AGENT_LANGUAGE_ID) { this.listeners.get(uri)?.dispose(); this.listeners.delete(uri); trigger(); @@ -431,3 +440,19 @@ export class ChatModeUpdateTracker extends Disposable { } } + +namespace IAgentSource { + export function fromPromptPath(promptPath: IPromptPath): IAgentSource { + if (promptPath.storage === PromptsStorage.extension) { + return { + storage: PromptsStorage.extension, + extensionId: promptPath.extension.identifier + }; + } else { + return { + storage: promptPath.storage + }; + } + } +} + diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index 831abd801db..10018713ba2 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -11,7 +11,7 @@ import { getPromptFileLocationsConfigKey, PromptsConfig } from '../config/config import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; +import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; import { PromptsType } from '../promptTypes.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../../../base/common/network.js'; @@ -103,6 +103,10 @@ export class PromptFilesLocator extends Disposable { return { event: eventEmitter.event, dispose: () => disposables.dispose() }; } + public getAgentSourceFolder(): readonly URI[] { + return this.toAbsoluteLocations([AGENTS_SOURCE_FOLDER]); + } + /** * Get all possible unambiguous prompt file source folders based on * the current workspace folder structure. @@ -183,6 +187,9 @@ export class PromptFilesLocator extends Disposable { private getLocalParentFolders(type: PromptsType): readonly { parent: URI; filePattern?: string }[] { const configuredLocations = PromptsConfig.promptSourceFolders(this.configService, type); + if (type === PromptsType.agent) { + configuredLocations.push(AGENTS_SOURCE_FOLDER); + } const absoluteLocations = this.toAbsoluteLocations(configuredLocations); return absoluteLocations.map(firstNonGlobParentAndPattern); } diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index df6e3cf777d..e672e8d7892 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -80,7 +80,7 @@ suite('PromptValidator', () => { const customChatMode = new CustomChatMode({ uri: URI.parse('myFs://test/test/chatmode.md'), name: 'BeastMode', - modeInstructions: { content: 'Beast mode instructions', toolReferences: [] }, + agentInstructions: { content: 'Beast mode instructions', toolReferences: [] }, source: { storage: PromptsStorage.local } }); instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); @@ -102,23 +102,23 @@ suite('PromptValidator', () => { await validator.validate(result, promptType, m => markers.push(m)); return markers; } - suite('modes', () => { + suite('agents', () => { - test('correct mode', async () => { + test('correct agent', async () => { const content = [ /* 01 */'---', /* 02 */`description: "Agent mode test"`, /* 03 */'model: MAE 4.1', /* 04 */`tools: ['tool1', 'tool2']`, /* 05 */'---', - /* 06 */'This is a chat mode test.', + /* 06 */'This is a chat agent test.', /* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual(markers, []); }); - test('mode with errors (empty description, unknown tool & model)', async () => { + test('agent with errors (empty description, unknown tool & model)', async () => { const content = [ /* 01 */'---', /* 02 */`description: ""`, // empty description -> error @@ -127,7 +127,7 @@ suite('PromptValidator', () => { /* 05 */'---', /* 06 */'Body', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), [ @@ -145,7 +145,7 @@ suite('PromptValidator', () => { `tools: 'tool1'`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.deepStrictEqual(markers.map(m => m.message), [`The 'tools' attribute must be an array.`]); }); @@ -157,7 +157,7 @@ suite('PromptValidator', () => { `tools: ['tool1', 2]`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), [ @@ -173,7 +173,7 @@ suite('PromptValidator', () => { `tools: ['tool1', 'tool3']`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), [ @@ -182,17 +182,17 @@ suite('PromptValidator', () => { ); }); - test('unknown attribute in mode file', async () => { + test('unknown attribute in agent file', async () => { const content = [ '---', 'description: "Test"', - `applyTo: '*.ts'`, // not allowed in mode file + `applyTo: '*.ts'`, // not allowed in agent file '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); - assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in mode files.`)); + assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in agent files.`)); }); test('tools with invalid handoffs', async () => { @@ -203,7 +203,7 @@ suite('PromptValidator', () => { `handoffs: next`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.deepStrictEqual(markers.map(m => m.message), [`The 'handoffs' attribute must be an array.`]); } @@ -215,7 +215,7 @@ suite('PromptValidator', () => { ` - label: '123'`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.deepStrictEqual(markers.map(m => m.message), [`Missing required properties 'agent', 'prompt' in handoff object.`]); } @@ -230,16 +230,16 @@ suite('PromptValidator', () => { ` send: true`, '---', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.deepStrictEqual(markers.map(m => m.message), [`The 'agent' property in a handoff must be a non-empty string.`]); } }); - test('mode with handoffs attribute', async () => { + test('agent with handoffs attribute', async () => { const content = [ '---', - 'description: \"Test mode with handoffs\"', + 'description: \"Test agent with handoffs\"', `handoffs:`, ' - label: Test Prompt', ' agent: Default', @@ -250,7 +250,7 @@ suite('PromptValidator', () => { '---', 'Body', ].join('\n'); - const markers = await validate(content, PromptsType.mode); + const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual(markers, [], 'Expected no validation issues for handoffs attribute'); }); }); @@ -326,7 +326,7 @@ suite('PromptValidator', () => { }); test('prompt model not suited for agent mode', async () => { - // MAE 3.5 Turbo lacks agentMode capability -> warning when used in agent (default) mode + // MAE 3.5 Turbo lacks agentMode capability -> warning when used in agent (default) const content = [ '---', 'description: "Prompt with unsuitable model"', @@ -340,6 +340,20 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].message, `Model 'MAE 3.5 Turbo' is not suited for agent mode.`); }); + test('prompt with custom agent BeastMode and tools', async () => { + // Explicit custom agent should be recognized; BeastMode kind comes from setup; ensure tools accepted + const content = [ + '---', + 'description: "Prompt custom mode"', + 'agent: BeastMode', + `tools: ['tool1']`, + '---', + 'Body' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, []); + }); + test('prompt with custom mode BeastMode and tools', async () => { // Explicit custom mode should be recognized; BeastMode kind comes from setup; ensure tools accepted const content = [ @@ -351,14 +365,32 @@ suite('PromptValidator', () => { 'Body' ].join('\n'); const markers = await validate(content, PromptsType.prompt); - assert.deepStrictEqual(markers, []); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), [`The 'mode' attribute has been deprecated. Please rename it to 'agent'.`]); + + }); + + test('prompt with custom mode an agent', async () => { + // Explicit custom mode should be recognized; BeastMode kind comes from setup; ensure tools accepted + const content = [ + '---', + 'description: "Prompt custom mode"', + 'mode: BeastMode', + `agent: agent`, + '---', + 'Body' + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1); + assert.deepStrictEqual(markers.map(m => m.message), [`The 'mode' attribute has been deprecated. The 'agent' attribute is used instead.`]); + }); - test('prompt with unknown mode Ask', async () => { + test('prompt with unknown agent Ask', async () => { const content = [ '---', - 'description: "Prompt unknown mode Ask"', - 'mode: Ask', + 'description: "Prompt unknown agent Ask"', + 'agent: Ask', `tools: ['tool1','tool2']`, '---', 'Body' @@ -366,14 +398,14 @@ suite('PromptValidator', () => { const markers = await validate(content, PromptsType.prompt); assert.strictEqual(markers.length, 1, 'Expected one warning about tools in non-agent mode'); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); - assert.strictEqual(markers[0].message, `Unknown mode 'Ask'. Available modes: agent, ask, edit, BeastMode.`); + assert.strictEqual(markers[0].message, `Unknown agent 'Ask'. Available agents: agent, ask, edit, BeastMode.`); }); - test('prompt with mode edit', async () => { + test('prompt with agent edit', async () => { const content = [ '---', 'description: "Prompt edit mode with tool"', - 'mode: edit', + 'agent: edit', `tools: ['tool1']`, '---', 'Body' diff --git a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts index 8ffbae92a44..2151cc347ae 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModeService.test.ts @@ -17,7 +17,7 @@ import { TestStorageService } from '../../../../test/common/workbenchTestService import { IChatAgentService } from '../../common/chatAgents.js'; import { ChatMode, ChatModeService } from '../../common/chatModes.js'; import { ChatModeKind } from '../../common/constants.js'; -import { IChatModeSource, ICustomChatMode, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; +import { IAgentSource, ICustomAgent, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { MockPromptsService } from './mockPromptsService.js'; class TestChatAgentService implements Partial { @@ -41,7 +41,7 @@ class TestChatAgentService implements Partial { suite('ChatModeService', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); - const workspaceSource: IChatModeSource = { storage: PromptsStorage.local }; + const workspaceSource: IAgentSource = { storage: PromptsStorage.local }; let instantiationService: TestInstantiationService; let promptsService: MockPromptsService; @@ -81,17 +81,17 @@ suite('ChatModeService', () => { test('should adjust builtin modes based on tools agent availability', () => { // With tools agent chatAgentService.setHasToolsAgent(true); - let modes = chatModeService.getModes(); - assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Agent)); + let agents = chatModeService.getModes(); + assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Agent)); // Without tools agent - Agent mode should not be present chatAgentService.setHasToolsAgent(false); - modes = chatModeService.getModes(); - assert.strictEqual(modes.builtin.find(mode => mode.id === ChatModeKind.Agent), undefined); + agents = chatModeService.getModes(); + assert.strictEqual(agents.builtin.find(agent => agent.id === ChatModeKind.Agent), undefined); // But Ask and Edit modes should always be present - assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Ask)); - assert.ok(modes.builtin.find(mode => mode.id === ChatModeKind.Edit)); + assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Ask)); + assert.ok(agents.builtin.find(agent => agent.id === ChatModeKind.Edit)); }); test('should find builtin modes by id', () => { @@ -107,12 +107,12 @@ suite('ChatModeService', () => { }); test('should handle custom modes from prompts service', async () => { - const customMode: ICustomChatMode = { + const customMode: ICustomAgent = { uri: URI.parse('file:///test/custom-mode.md'), name: 'Test Mode', description: 'A test custom mode', tools: ['tool1', 'tool2'], - modeInstructions: { content: 'Custom mode body', toolReferences: [] }, + agentInstructions: { content: 'Custom mode body', toolReferences: [] }, source: workspaceSource }; @@ -131,7 +131,7 @@ suite('ChatModeService', () => { assert.strictEqual(testMode.description.get(), customMode.description); assert.strictEqual(testMode.kind, ChatModeKind.Agent); assert.deepStrictEqual(testMode.customTools?.get(), customMode.tools); - assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.modeInstructions); + assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.agentInstructions); assert.deepStrictEqual(testMode.handOffs?.get(), customMode.handOffs); assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString()); assert.deepStrictEqual(testMode.source, workspaceSource); @@ -143,12 +143,12 @@ suite('ChatModeService', () => { eventFired = true; })); - const customMode: ICustomChatMode = { + const customMode: ICustomAgent = { uri: URI.parse('file:///test/custom-mode.md'), name: 'Test Mode', description: 'A test custom mode', tools: [], - modeInstructions: { content: 'Custom mode body', toolReferences: [] }, + agentInstructions: { content: 'Custom mode body', toolReferences: [] }, source: workspaceSource, }; @@ -161,12 +161,12 @@ suite('ChatModeService', () => { }); test('should find custom modes by id', async () => { - const customMode: ICustomChatMode = { + const customMode: ICustomAgent = { uri: URI.parse('file:///test/findable-mode.md'), name: 'Findable Mode', description: 'A findable custom mode', tools: [], - modeInstructions: { content: 'Findable mode body', toolReferences: [] }, + agentInstructions: { content: 'Findable mode body', toolReferences: [] }, source: workspaceSource, }; @@ -184,12 +184,12 @@ suite('ChatModeService', () => { test('should update existing custom mode instances when data changes', async () => { const uri = URI.parse('file:///test/updateable-mode.md'); - const initialMode: ICustomChatMode = { + const initialMode: ICustomAgent = { uri, name: 'Initial Mode', description: 'Initial description', tools: ['tool1'], - modeInstructions: { content: 'Initial body', toolReferences: [] }, + agentInstructions: { content: 'Initial body', toolReferences: [] }, model: 'gpt-4', source: workspaceSource, }; @@ -202,11 +202,11 @@ suite('ChatModeService', () => { assert.strictEqual(initialCustomMode.description.get(), 'Initial description'); // Update the mode data - const updatedMode: ICustomChatMode = { + const updatedMode: ICustomAgent = { ...initialMode, description: 'Updated description', tools: ['tool1', 'tool2'], - modeInstructions: { content: 'Updated body', toolReferences: [] }, + agentInstructions: { content: 'Updated body', toolReferences: [] }, model: 'Updated model' }; @@ -228,21 +228,21 @@ suite('ChatModeService', () => { }); test('should remove custom modes that no longer exist', async () => { - const mode1: ICustomChatMode = { + const mode1: ICustomAgent = { uri: URI.parse('file:///test/mode1.md'), name: 'Mode 1', description: 'First mode', tools: [], - modeInstructions: { content: 'Mode 1 body', toolReferences: [] }, + agentInstructions: { content: 'Mode 1 body', toolReferences: [] }, source: workspaceSource, }; - const mode2: ICustomChatMode = { + const mode2: ICustomAgent = { uri: URI.parse('file:///test/mode2.md'), name: 'Mode 2', description: 'Second mode', tools: [], - modeInstructions: { content: 'Mode 2 body', toolReferences: [] }, + agentInstructions: { content: 'Mode 2 body', toolReferences: [] }, source: workspaceSource, }; diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index 41b921b3e72..a3a17bc9a17 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -11,22 +11,22 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { ParsedPromptFile } from '../../common/promptSyntax/service/newPromptsParser.js'; -import { ICustomChatMode, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; +import { ICustomAgent, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; export class MockPromptsService implements IPromptsService { _serviceBrand: undefined; private readonly _onDidChangeCustomChatModes = new Emitter(); - readonly onDidChangeCustomChatModes = this._onDidChangeCustomChatModes.event; + readonly onDidChangeCustomAgents = this._onDidChangeCustomChatModes.event; - private _customModes: ICustomChatMode[] = []; + private _customModes: ICustomAgent[] = []; - setCustomModes(modes: ICustomChatMode[]): void { + setCustomModes(modes: ICustomAgent[]): void { this._customModes = modes; this._onDidChangeCustomChatModes.fire(); } - async getCustomChatModes(token: CancellationToken): Promise { + async getCustomAgents(token: CancellationToken): Promise { return this._customModes; } diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts index 89385e764ec..373cf708408 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts @@ -119,7 +119,7 @@ class TestPromptFileReference extends Disposable { const result: any = {}; result.promptType = getPromptFileType(this.rootFileUri); if (ast.header) { - for (const key of ['tools', 'model', 'mode', 'applyTo', 'description'] as const) { + for (const key of ['tools', 'model', 'agent', 'applyTo', 'description'] as const) { if (ast.header[key]) { result[key] = ast.header[key]; } @@ -308,7 +308,7 @@ suite('PromptFileReference', function () { '---', 'description: \'Root prompt description.\'', 'tools: [\'my-tool1\']', - 'mode: "agent" ', + 'agent: "agent" ', '---', '## Files', '\t- this file #file:folder1/file3.prompt.md ', @@ -340,7 +340,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , ]', 'something: true', - 'mode: \'ask\'\t', + 'agent: \'ask\'\t', '---', 'this file has a non-existing #file:./some-non-existing/file.prompt.md\t\treference', '', @@ -406,7 +406,7 @@ suite('PromptFileReference', function () { metadata, { promptType: PromptsType.prompt, - mode: 'agent', + agent: 'agent', description: 'Root prompt description.', tools: ['my-tool1'], }, @@ -471,7 +471,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , \'my-tool3\' , ]', 'something: true', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', '', '', @@ -577,7 +577,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , \'my-tool3\' , ]', 'something: true', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', '', '', @@ -627,8 +627,8 @@ suite('PromptFileReference', function () { }); }); - suite('tools and mode compatibility', () => { - test('ask mode', async function () { + suite('tools and agent compatibility', () => { + test('ask agent', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = toUri(rootFolder); @@ -653,7 +653,7 @@ suite('PromptFileReference', function () { contents: [ '---', 'description: \'Description of my prompt.\'', - 'mode: "ask" ', + 'agent: "ask" ', '---', '## Files', '\t- this file #file:folder1/file3.prompt.md ', @@ -669,7 +669,7 @@ suite('PromptFileReference', function () { contents: [ '---', 'tools: [ false, \'my-tool1\' , ]', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', ' some more\t content', ], @@ -683,7 +683,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , ]', 'something: true', - 'mode: \'ask\'\t', + 'agent: \'ask\'\t', '---', '', '', @@ -724,14 +724,14 @@ suite('PromptFileReference', function () { metadata, { promptType: PromptsType.prompt, - mode: ChatModeKind.Ask, + agent: ChatModeKind.Ask, description: 'Description of my prompt.', }, 'Must have correct metadata.', ); }); - test('edit mode', async function () { + test('edit agent', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = toUri(rootFolder); @@ -756,7 +756,7 @@ suite('PromptFileReference', function () { contents: [ '---', 'description: \'Description of my prompt.\'', - 'mode:\t\t"edit"\t\t', + 'agent:\t\t"edit"\t\t', '---', '## Files', '\t- this file #file:folder1/file3.prompt.md ', @@ -785,7 +785,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , ]', 'something: true', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', '', '', @@ -826,7 +826,7 @@ suite('PromptFileReference', function () { metadata, { promptType: PromptsType.prompt, - mode: ChatModeKind.Edit, + agent: ChatModeKind.Edit, description: 'Description of my prompt.', }, 'Must have correct metadata.', @@ -834,7 +834,7 @@ suite('PromptFileReference', function () { }); - test('agent mode', async function () { + test('agent', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = toUri(rootFolder); @@ -859,7 +859,7 @@ suite('PromptFileReference', function () { contents: [ '---', 'description: \'Description of my prompt.\'', - 'mode: \t\t "agent" \t\t ', + 'agent: \t\t "agent" \t\t ', '---', '## Files', '\t- this file #file:folder1/file3.prompt.md ', @@ -888,7 +888,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , \'my-tool3\' , ]', 'something: true', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', '', '', @@ -929,7 +929,7 @@ suite('PromptFileReference', function () { metadata, { promptType: PromptsType.prompt, - mode: ChatModeKind.Agent, + agent: ChatModeKind.Agent, description: 'Description of my prompt.', }, 'Must have correct metadata.', @@ -937,7 +937,7 @@ suite('PromptFileReference', function () { }); - test('no mode', async function () { + test('no agent', async function () { const rootFolderName = 'resolves-nested-file-references'; const rootFolder = `/${rootFolderName}`; const rootUri = toUri(rootFolder); @@ -991,7 +991,7 @@ suite('PromptFileReference', function () { '---', 'tools: [\'my-tool1\', "my-tool2", true, , \'my-tool3\' , ]', 'something: true', - 'mode: \'agent\'\t', + 'agent: \'agent\'\t', '---', '', '', diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index e500e40d69e..2ca9e16cd5c 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -13,15 +13,15 @@ import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPro suite('NewPromptsParser', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('mode', async () => { - const uri = URI.parse('file:///test/chatmode.md'); + test('agent', async () => { + const uri = URI.parse('file:///test/test.vscode-agent.md'); const content = [ /* 01 */'---', - /* 02 */`description: "Agent mode test"`, + /* 02 */`description: "Agent test"`, /* 03 */'model: GPT 4.1', /* 04 */`tools: ['tool1', 'tool2']`, /* 05 */'---', - /* 06 */'This is a chat mode test.', + /* 06 */'This is an agent test.', /* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).', ].join('\n'); const result = new NewPromptsParser().parse(uri, content); @@ -30,7 +30,7 @@ suite('NewPromptsParser', () => { assert.ok(result.body); assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 5, endColumn: 1 }); assert.deepEqual(result.header.attributes, [ - { key: 'description', range: new Range(2, 1, 2, 31), value: { type: 'string', value: 'Agent mode test', range: new Range(2, 14, 2, 31) } }, + { key: 'description', range: new Range(2, 1, 2, 26), value: { type: 'string', value: 'Agent test', range: new Range(2, 14, 2, 26) } }, { key: 'model', range: new Range(3, 1, 3, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(3, 8, 3, 15) } }, { key: 'tools', range: new Range(4, 1, 4, 26), value: { @@ -41,24 +41,24 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); - assert.equal(result.body.offset, 80); - assert.equal(result.body.getContent(), 'This is a chat mode test.\nHere is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).'); + assert.equal(result.body.offset, 75); + assert.equal(result.body.getContent(), 'This is an agent test.\nHere is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).'); assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 39, 7, 54), content: './reference1.md', isMarkdownLink: false }, { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 12, 7, 17), name: 'tool1', offset: 116 } + { range: new Range(7, 12, 7, 17), name: 'tool1', offset: 108 } ]); - assert.deepEqual(result.header.description, 'Agent mode test'); + assert.deepEqual(result.header.description, 'Agent test'); assert.deepEqual(result.header.model, 'GPT 4.1'); assert.ok(result.header.tools); assert.deepEqual(result.header.tools, ['tool1', 'tool2']); }); test('mode with handoff', async () => { - const uri = URI.parse('file:///test/chatmode.md'); + const uri = URI.parse('file:///test/test.vscode-agent.md'); const content = [ /* 01 */'---', /* 02 */`description: "Agent test"`, @@ -152,7 +152,7 @@ suite('NewPromptsParser', () => { const content = [ /* 01 */'---', /* 02 */`description: "General purpose coding assistant"`, - /* 03 */'mode: agent', + /* 03 */'agent: agent', /* 04 */'model: GPT 4.1', /* 05 */`tools: ['search', 'terminal']`, /* 06 */'---', @@ -165,7 +165,7 @@ suite('NewPromptsParser', () => { assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 6, endColumn: 1 }); assert.deepEqual(result.header.attributes, [ { key: 'description', range: new Range(2, 1, 2, 48), value: { type: 'string', value: 'General purpose coding assistant', range: new Range(2, 14, 2, 48) } }, - { key: 'mode', range: new Range(3, 1, 3, 12), value: { type: 'string', value: 'agent', range: new Range(3, 7, 3, 12) } }, + { key: 'agent', range: new Range(3, 1, 3, 13), value: { type: 'string', value: 'agent', range: new Range(3, 8, 3, 13) } }, { key: 'model', range: new Range(4, 1, 4, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(4, 8, 4, 15) } }, { key: 'tools', range: new Range(5, 1, 5, 30), value: { @@ -176,16 +176,16 @@ suite('NewPromptsParser', () => { }, ]); assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); - assert.equal(result.body.offset, 113); + assert.equal(result.body.offset, 114); assert.equal(result.body.getContent(), 'This is a prompt file body referencing #search and [docs](https://example.com/docs).'); assert.deepEqual(result.body.fileReferences, [ { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 41, 7, 47), name: 'search', offset: 152 } + { range: new Range(7, 41, 7, 47), name: 'search', offset: 153 } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); - assert.deepEqual(result.header.mode, 'agent'); + assert.deepEqual(result.header.agent, 'agent'); assert.deepEqual(result.header.model, 'GPT 4.1'); assert.ok(result.header.tools); assert.deepEqual(result.header.tools, ['search', 'terminal']); @@ -255,7 +255,7 @@ suite('NewPromptsParser', () => { } ]); assert.deepEqual(result.header.description, undefined); - assert.deepEqual(result.header.mode, undefined); + assert.deepEqual(result.header.agent, undefined); assert.deepEqual(result.header.model, undefined); assert.ok(result.header.tools); assert.deepEqual(result.header.tools, ['built-in', 'browser-click', 'openPullRequest', 'copilotCodingAgent']); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 8aa3a89b7b1..5a083a0b891 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -34,9 +34,9 @@ import { TestContextService, TestUserDataProfileService } from '../../../../../. import { ChatRequestVariableSet, isPromptFileVariableEntry, toFileVariableEntry } from '../../../../common/chatVariableEntries.js'; import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '../../../../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; -import { INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; -import { ICustomChatMode, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; +import { ICustomAgent, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; import { MockFilesystem } from '../testUtils/mockFilesystem.js'; @@ -61,7 +61,7 @@ suite('PromptsService', () => { testConfigService.setUserConfiguration(PromptsConfig.USE_NESTED_AGENT_MD, false); testConfigService.setUserConfiguration(PromptsConfig.INSTRUCTIONS_LOCATION_KEY, { [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true }); testConfigService.setUserConfiguration(PromptsConfig.PROMPT_LOCATIONS_KEY, { [PROMPT_DEFAULT_SOURCE_FOLDER]: true }); - testConfigService.setUserConfiguration(PromptsConfig.MODE_LOCATION_KEY, { [MODE_DEFAULT_SOURCE_FOLDER]: true }); + testConfigService.setUserConfiguration(PromptsConfig.MODE_LOCATION_KEY, { [LEGACY_MODE_DEFAULT_SOURCE_FOLDER]: true }); instaService.stub(IConfigurationService, testConfigService); instaService.stub(IWorkbenchEnvironmentService, {}); @@ -127,7 +127,7 @@ suite('PromptsService', () => { '---', 'description: \'Root prompt description.\'', 'tools: [\'my-tool1\', , true]', - 'mode: "agent" ', + 'agent: "agent" ', '---', '## Files', '\t- this file #file:folder1/file3.prompt.md ', @@ -146,7 +146,7 @@ suite('PromptsService', () => { contents: [ '---', 'tools: [ false, \'my-tool1\' , ]', - 'mode: \'edit\'', + 'agent: \'edit\'', '---', '', '[](./some-other-folder/non-existing-folder)', @@ -163,7 +163,7 @@ suite('PromptsService', () => { '---', 'tools: [\'my-tool1\', "my-tool2", true, , ]', 'something: true', - 'mode: \'ask\'\t', + 'agent: \'ask\'\t', 'description: "File 4 splendid description."', '---', 'this file has a non-existing #file:./some-non-existing/file.prompt.md\t\treference', @@ -222,7 +222,7 @@ suite('PromptsService', () => { assert.deepEqual(result1.uri, rootFileUri); assert.deepEqual(result1.header?.description, 'Root prompt description.'); assert.deepEqual(result1.header?.tools, ['my-tool1']); - assert.deepEqual(result1.header?.mode, 'agent'); + assert.deepEqual(result1.header?.agent, 'agent'); assert.ok(result1.body); assert.deepEqual( result1.body.fileReferences.map(r => result1.body?.resolveFilePath(r.content)), @@ -231,14 +231,14 @@ suite('PromptsService', () => { assert.deepEqual( result1.body.variableReferences, [ - { name: 'my-tool', range: new Range(10, 5, 10, 12), offset: 239 }, - { name: 'my-other-tool', range: new Range(11, 5, 11, 18), offset: 251 }, + { name: 'my-tool', range: new Range(10, 5, 10, 12), offset: 240 }, + { name: 'my-other-tool', range: new Range(11, 5, 11, 18), offset: 252 }, ] ); const result2 = await service.parseNew(file3, CancellationToken.None); assert.deepEqual(result2.uri, file3); - assert.deepEqual(result2.header?.mode, 'edit'); + assert.deepEqual(result2.header?.agent, 'edit'); assert.ok(result2.body); assert.deepEqual( result2.body.fileReferences.map(r => result2.body?.resolveFilePath(r.content)), @@ -737,14 +737,14 @@ suite('PromptsService', () => { }); }); - suite('getCustomChatModes', () => { + suite('getCustomAgents', () => { teardown(() => { sinon.restore(); }); test('header with handOffs', async () => { - const rootFolderName = 'custom-modes-with-handoffs'; + const rootFolderName = 'custom-agents-with-handoffs'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); @@ -755,13 +755,13 @@ suite('PromptsService', () => { name: rootFolderName, children: [ { - name: '.github/chatmodes', + name: '.github/agents', children: [ { - name: 'mode1.chatmode.md', + name: 'agent1.vscode-agent.md', contents: [ '---', - 'description: \'Mode file 1.\'', + 'description: \'Agent file 1.\'', 'handoffs: [ { agent: "Edit", label: "Do it", prompt: "Do it now" } ]', '---', ], @@ -772,20 +772,20 @@ suite('PromptsService', () => { ], }])).mock(); - const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) })); - const expected: ICustomChatMode[] = [ + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + const expected: ICustomAgent[] = [ { - name: 'mode1', - description: 'Mode file 1.', + name: 'agent1', + description: 'Agent file 1.', handOffs: [{ agent: 'Edit', label: 'Do it', prompt: 'Do it now', send: undefined }], - modeInstructions: { + agentInstructions: { content: '', toolReferences: [], metadata: undefined }, model: undefined, tools: undefined, - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.vscode-agent.md'), source: { storage: PromptsStorage.local } }, ]; @@ -793,12 +793,12 @@ suite('PromptsService', () => { assert.deepEqual( result, expected, - 'Must get custom chat modes.', + 'Must get custom agents.', ); }); test('body with tool references', async () => { - const rootFolderName = 'custom-modes'; + const rootFolderName = 'custom-agents'; const rootFolder = `/${rootFolderName}`; const rootFolderUri = URI.file(rootFolder); @@ -810,20 +810,20 @@ suite('PromptsService', () => { name: rootFolderName, children: [ { - name: '.github/chatmodes', + name: '.github/agents', children: [ { - name: 'mode1.chatmode.md', + name: 'agent1.vscode-agent.md', contents: [ '---', - 'description: \'Mode file 1.\'', + 'description: \'Agent file 1.\'', 'tools: [ tool1, tool2 ]', '---', 'Do it with #tool1', ], }, { - name: 'mode2.chatmode.md', + name: 'agent2.vscode-agent.md', contents: [ 'First use #tool2\nThen use #tool1', ], @@ -834,25 +834,25 @@ suite('PromptsService', () => { ], }])).mock(); - const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) })); - const expected: ICustomChatMode[] = [ + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + const expected: ICustomAgent[] = [ { - name: 'mode1', - description: 'Mode file 1.', + name: 'agent1', + description: 'Agent file 1.', tools: ['tool1', 'tool2'], - modeInstructions: { + agentInstructions: { content: 'Do it with #tool1', toolReferences: [{ name: 'tool1', range: { start: 11, endExclusive: 17 } }], metadata: undefined }, handOffs: undefined, model: undefined, - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.vscode-agent.md'), source: { storage: PromptsStorage.local }, }, { - name: 'mode2', - modeInstructions: { + name: 'agent2', + agentInstructions: { content: 'First use #tool2\nThen use #tool1', toolReferences: [ { name: 'tool1', range: { start: 26, endExclusive: 32 } }, @@ -860,7 +860,7 @@ suite('PromptsService', () => { ], metadata: undefined }, - uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.chatmode.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.vscode-agent.md'), source: { storage: PromptsStorage.local }, } ]; @@ -868,9 +868,12 @@ suite('PromptsService', () => { assert.deepEqual( result, expected, - 'Must get custom chat modes.', + 'Must get custom agents.', ); }); + }); + + suite('listPromptFiles - extensions', () => { test('Contributed prompt file', async () => { const uri = URI.parse('file://extensions/my-extension/textMate.instructions.md'); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 563f6043a52..fce058db20d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -35,7 +35,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { IChatAgentService } from '../../chat/common/chatAgents.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; -import { MODE_FILE_EXTENSION } from '../../chat/common/promptSyntax/config/promptFileLocations.js'; +import { AGENT_FILE_EXTENSION, LEGACY_MODE_FILE_EXTENSION } from '../../chat/common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../chat/common/promptSyntax/promptTypes.js'; import { ACTION_START, CTX_INLINE_CHAT_V1_ENABLED, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { AbstractInline1ChatAction } from './inlineChatActions.js'; @@ -51,7 +51,8 @@ const IGNORED_LANGUAGE_IDS = new Set([ 'search-result', INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, - MODE_FILE_EXTENSION + LEGACY_MODE_FILE_EXTENSION, + AGENT_FILE_EXTENSION ]); export const CTX_INLINE_CHAT_SHOWING_HINT = new RawContextKey('inlineChatShowingHint', false, localize('inlineChatShowingHint', "Whether inline chat shows a contextual hint")); From 025f45e7aa4e8af199e1966481cc8db793984442 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 20 Oct 2025 21:54:15 +0200 Subject: [PATCH 1346/4355] eng - make it possible run OSS with chat development setup (#272268) * eng - make it possible run OSS with chat development setup * easy --- product.json | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/product.json b/product.json index a23473a4663..f088e15a772 100644 --- a/product.json +++ b/product.json @@ -82,5 +82,65 @@ "publisherDisplayName": "Microsoft" } } - ] + ], + "defaultChatAgent": { + "extensionId": "GitHub.copilot", + "chatExtensionId": "GitHub.copilot-chat", + "documentationUrl": "https://aka.ms/github-copilot-overview", + "termsStatementUrl": "https://aka.ms/github-copilot-terms-statement", + "privacyStatementUrl": "https://aka.ms/github-copilot-privacy-statement", + "skusDocumentationUrl": "https://aka.ms/github-copilot-plans", + "publicCodeMatchesUrl": "https://aka.ms/github-copilot-match-public-code", + "manageSettingsUrl": "https://aka.ms/github-copilot-settings", + "managePlanUrl": "https://aka.ms/github-copilot-manage-plan", + "manageOverageUrl": "https://aka.ms/github-copilot-manage-overage", + "upgradePlanUrl": "https://aka.ms/github-copilot-upgrade-plan", + "signUpUrl": "https://aka.ms/github-sign-up", + "provider": { + "default": { + "id": "github", + "name": "GitHub" + }, + "enterprise": { + "id": "github-enterprise", + "name": "GHE.com" + }, + "google": { + "id": "google", + "name": "Google" + }, + "apple": { + "id": "apple", + "name": "Apple" + } + }, + "providerUriSetting": "github-enterprise.uri", + "providerScopes": [ + [ + "user:email" + ], + [ + "read:user" + ], + [ + "read:user", + "user:email", + "repo", + "workflow" + ] + ], + "entitlementUrl": "https://api.github.com/copilot_internal/user", + "entitlementSignupLimitedUrl": "https://api.github.com/copilot_internal/subscribe_limited_user", + "chatQuotaExceededContext": "github.copilot.chat.quotaExceeded", + "completionsQuotaExceededContext": "github.copilot.completions.quotaExceeded", + "walkthroughCommand": "github.copilot.open.walkthrough", + "completionsMenuCommand": "github.copilot.toggleStatusMenu", + "completionsRefreshTokenCommand": "github.copilot.signIn", + "chatRefreshTokenCommand": "github.copilot.refreshToken", + "generateCommitMessageCommand": "github.copilot.git.generateCommitMessage", + "resolveMergeConflictsCommand": "github.copilot.git.resolveMergeConflicts", + "completionsAdvancedSetting": "github.copilot.advanced", + "completionsEnablementSetting": "github.copilot.enable", + "nextEditSuggestionsSetting": "github.copilot.nextEditSuggestions.enabled" + } } From 9db33b991f0a069feb7bb472e9fe781edd3852e3 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:36:14 -0700 Subject: [PATCH 1347/4355] chat session: let extension decide which options to render for a particular session (#272333) --- .../contrib/chat/browser/chatInputPart.ts | 80 +++++++++++++++---- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 708a459829c..67360e3355a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -647,11 +647,19 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } // Clear existing widgets - this.chatSessionPickerWidgets.clear(); + this.disposeSessionPickerWidgets(); - // Create a widget for each option group + // Create a widget for each option group in effect for this specific session const widgets: ChatSessionPickerActionItem[] = []; for (const optionGroup of optionGroups) { + if (!ctx) { + continue; + } + if (!this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroup.id)) { + // This session does not have a value to contribute for this option group + continue; + } + const initialItem = this.getCurrentOptionForGroup(optionGroup.id); const initialState = { group: optionGroup, item: initialItem }; @@ -1137,33 +1145,70 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge */ private refreshChatSessionPickers(): void { const sessionId = this._widget?.viewModel?.model.sessionId; - if (!sessionId) { + const hideAll = () => { this.chatSessionHasOptions.set(false); - return; + this.hideAllSessionPickerWidgets(); + }; + + if (!sessionId) { + return hideAll(); } const ctx = this.chatService.getChatSessionFromInternalId(sessionId); if (!ctx) { - this.chatSessionHasOptions.set(false); - return; + return hideAll(); } const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); if (!optionGroups || optionGroups.length === 0) { - this.chatSessionHasOptions.set(false); - return; + return hideAll(); } this.chatSessionHasOptions.set(true); - // Refresh each registered option group - for (const optionGroup of optionGroups) { - const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroup.id); - if (currentOptionId) { - const item = optionGroup.items.find(m => m.id === currentOptionId); - if (item) { - this.getOrCreateOptionEmitter(optionGroup.id).fire(item); + // Session dictates which option groups are shown + for (const [optionGroupId, widget] of this.chatSessionPickerWidgets.entries()) { + const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); + if (widget.element) { + widget.element.style.display = currentOption ? '' : 'none'; + } + if (currentOption) { + const optionGroup = optionGroups.find(g => g.id === optionGroupId); + if (optionGroup) { + const item = optionGroup.items.find(m => m.id === currentOption); + if (item) { + this.getOrCreateOptionEmitter(optionGroupId).fire(item); + } } } } + for (const container of this.additionalSessionPickerContainers) { + const optionGroupId = container.getAttribute('data-option-group-id'); + if (optionGroupId) { + const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); + container.style.display = currentOption ? '' : 'none'; + } + } + } + + private hideAllSessionPickerWidgets(): void { + for (const widget of this.chatSessionPickerWidgets.values()) { + if (widget.element) { + widget.element.style.display = 'none'; + } + } + for (const container of this.additionalSessionPickerContainers) { + container.style.display = 'none'; + } + } + + private disposeSessionPickerWidgets(): void { + for (const widget of this.chatSessionPickerWidgets.values()) { + widget.dispose(); + } + this.chatSessionPickerWidgets.clear(); + for (const container of this.additionalSessionPickerContainers) { + container.remove(); + } + this.additionalSessionPickerContainers = []; } /** @@ -1425,11 +1470,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const firstPickerElement = toolbarElement.querySelector('.chat-sessionPicker-item'); if (firstPickerElement && firstPickerElement.parentElement) { // Get all widgets except the first one (which is already rendered by the toolbar) - const additionalWidgets = Array.from(this.chatSessionPickerWidgets.values()).slice(1); + const widgetEntries = Array.from(this.chatSessionPickerWidgets.entries()).slice(1); let insertAfter = firstPickerElement; - for (const widget of additionalWidgets) { + for (const [optionGroupId, widget] of widgetEntries) { // Create a container for this widget const container = dom.$('.action-item'); + container.setAttribute('data-option-group-id', optionGroupId); widget.render(container); // Insert after the previous picker if (insertAfter.nextSibling) { From ba9ddce76a6b33e15bdf5a43116659c97d61488f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:45:29 -0700 Subject: [PATCH 1348/4355] Warn when using unknown properties on custom editors --- .../workbench/contrib/chat/browser/chatSessions.contribution.ts | 2 ++ src/vs/workbench/contrib/customEditor/common/extensionPoint.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 9db400beaef..3ad521839e8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -37,6 +37,7 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint Date: Mon, 20 Oct 2025 13:33:35 -0700 Subject: [PATCH 1349/4355] chat: refactor into `IChatToolInvocation.state` This iteration we'll be adding a post-execution confirmation step for tools that read data from the outside world. This is another state that tool invocation may be in. Previously the state of a tool invocation was a complex arithmetic of various properties that were valid or invalid in various different states. This PR sets the groundwork for post-confirmations by moving these into a `state` observable which is the point of truth for the invocation's state. Helpers in the `IChatToolInvocation` provide drop-in methods for existing callers. Validated the confirmation approve/skip/deny flows still work for the terminal tool, built-in tools, and MCP tools. --- .../chat/browser/actions/chatToolActions.ts | 9 +- .../chat/browser/chatAccessibilityProvider.ts | 2 +- .../chatExtensionsInstallToolSubPart.ts | 16 +- .../chatInputOutputMarkdownProgressPart.ts | 6 +- .../chatTerminalToolConfirmationSubPart.ts | 15 +- .../chatToolConfirmationSubPart.ts | 25 ++- .../chatToolInvocationPart.ts | 23 +-- .../chatToolInvocationSubPart.ts | 21 ++- .../toolInvocationParts/chatToolOutputPart.ts | 2 +- .../chatToolProgressPart.ts | 13 +- .../contrib/chat/browser/chatListRenderer.ts | 4 +- .../browser/chatResponseAccessibleView.ts | 12 +- .../chat/browser/languageModelToolsService.ts | 12 +- .../contrib/chat/common/chatModel.ts | 25 +-- .../chatProgressTypes/chatToolInvocation.ts | 92 +++++----- .../chatResponseResourceFileSystemProvider.ts | 5 +- .../contrib/chat/common/chatService.ts | 158 ++++++++++++++++-- 17 files changed, 289 insertions(+), 151 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index c079d2365c6..ee7356e6807 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -54,9 +54,12 @@ abstract class ToolConfirmationAction extends Action2 { return; } - const unconfirmedToolInvocation = lastItem.model.response.value.find((item): item is IChatToolInvocation => item.kind === 'toolInvocation' && item.isConfirmed === undefined); - if (unconfirmedToolInvocation) { - unconfirmedToolInvocation.confirmed.complete(this.getReason()); + for (const item of lastItem.model.response.value) { + const state = item.kind === 'toolInvocation' ? item.state.get() : undefined; + if (state?.type === IChatToolInvocation.StateKind.WaitingForConfirmation) { + state.confirm(this.getReason()); + break; + } } // Return focus to the chat input, in case it was in the tool confirmation editor diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts index ff09418421a..289cf7a5ea6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts @@ -85,7 +85,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider v.kind === 'toolInvocation'); let toolInvocationHint = ''; if (toolInvocation.length) { - const waitingForConfirmation = toolInvocation.filter(v => !v.isComplete); + const waitingForConfirmation = toolInvocation.filter(v => v.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation); if (waitingForConfirmation.length) { toolInvocationHint = this._instantiationService.invokeFunction(getToolConfirmationAlert, toolInvocation); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts index 6ae4c7a59e8..5b00de85d3b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts @@ -5,6 +5,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../base/common/event.js'; +import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; import { localize } from '../../../../../../nls.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IExtensionManagementService } from '../../../../../../platform/extensionManagement/common/extensionManagement.js'; @@ -46,7 +47,7 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo this._register(chatExtensionsContentPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); dom.append(this.domNode, chatExtensionsContentPart.domNode); - if (toolInvocation.isConfirmed === undefined) { + if (toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { const allowLabel = localize('allow', "Allow"); const allowKeybinding = keybindingService.lookupKeybinding(AcceptToolConfirmationActionId)?.getLabel(); const allowTooltip = allowKeybinding ? `${allowLabel} (${allowKeybinding})` : allowLabel; @@ -84,13 +85,16 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); dom.append(this.domNode, confirmWidget.domNode); this._register(confirmWidget.onDidClick(button => { - toolInvocation.confirmed.complete(button.data); + IChatToolInvocation.confirmWith(toolInvocation, button.data); chatWidgetService.getWidgetBySessionId(context.element.sessionId)?.focusInput(); })); - toolInvocation.confirmed.p.then(() => { - ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); - this._onNeedsRerender.fire(); - }); + this._register(autorunSelfDisposable(reader => { + if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { + reader.dispose(); + ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); + this._onNeedsRerender.fire(); + } + })); const disposable = this._register(extensionManagementService.onInstallExtension(e => { if (extensionsContent.extensions.some(id => areSameExtensions({ id }, e.identifier))) { disposable.dispose(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index 242739b4ac2..9d187b52e5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -136,15 +136,15 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS this._register(collapsibleListPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(toDisposable(() => ChatInputOutputMarkdownProgressPart._expandedByDefault.set(toolInvocation, collapsibleListPart.expanded))); - const progressObservable = toolInvocation.kind === 'toolInvocation' ? toolInvocation.progress : undefined; + const progressObservable = toolInvocation.kind === 'toolInvocation' ? toolInvocation.state.map((s, r) => s.type === IChatToolInvocation.StateKind.Executing ? s.progress.read(r) : undefined) : undefined; const progressBar = new Lazy(() => this._register(new ProgressBar(collapsibleListPart.domNode))); if (progressObservable) { this._register(autorun(reader => { const progress = progressObservable?.read(reader); - if (progress.message) { + if (progress?.message) { collapsibleListPart.title = progress.message; } - if (progress.progress && !toolInvocation.isComplete) { + if (progress?.progress && !IChatToolInvocation.isComplete(toolInvocation, reader)) { progressBar.value.setWorked(progress.progress * 100); } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index ef4e9b60a94..d45127c7579 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -43,6 +43,7 @@ import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart. import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; +import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; export const enum TerminalToolConfirmationStorageKeys { TerminalAutoApproveWarningAccepted = 'chat.tools.terminal.autoApprove.warningAccepted' @@ -298,16 +299,20 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS } } } + if (doComplete) { - toolInvocation.confirmed.complete({ type: toolConfirmKind }); + IChatToolInvocation.confirmWith(toolInvocation, { type: toolConfirmKind }); this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.focusInput(); } })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); - toolInvocation.confirmed.p.then(() => { - ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService).set(false); - this._onNeedsRerender.fire(); - }); + this._register(autorunSelfDisposable(reader => { + if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { + reader.dispose(); + ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); + this._onNeedsRerender.fire(); + } + })); this.domNode = confirmWidget.domNode; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index 0e0c4f7880c..abf101e7968 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from '../../../../../../platform/instantiation/ import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { IChatToolInvocation, ToolConfirmKind } from '../../../common/chatService.js'; +import { ConfirmedReason, IChatToolInvocation, ToolConfirmKind } from '../../../common/chatService.js'; import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js'; import { createToolInputUri, createToolSchemaUri, ILanguageModelToolsService } from '../../../common/languageModelToolsService.js'; import { AcceptToolConfirmationActionId, SkipToolConfirmationActionId } from '../../actions/chatToolActions.js'; @@ -34,6 +34,7 @@ import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { IChatMarkdownAnchorService } from '../chatMarkdownAnchorService.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; +import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; const SHOW_MORE_MESSAGE_HEIGHT_TRIGGER = 45; @@ -314,9 +315,14 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { this._register(confirmWidget.onDidClick(button => { const confirmAndSave = (kind: 'profile' | 'workspace' | 'session') => { this.languageModelToolsService.setToolAutoConfirmation(toolInvocation.toolId, kind); - toolInvocation.confirmed.complete({ type: ToolConfirmKind.LmServicePerTool, scope: kind }); + confirm({ type: ToolConfirmKind.LmServicePerTool, scope: kind }); }; + const confirm = (reason: ConfirmedReason) => { + IChatToolInvocation.confirmWith(toolInvocation, reason); + }; + + switch (button.data as ConfirmationOutcome) { case ConfirmationOutcome.AllowGlobally: confirmAndSave('profile'); @@ -328,10 +334,10 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { confirmAndSave('session'); break; case ConfirmationOutcome.Allow: - toolInvocation.confirmed.complete({ type: ToolConfirmKind.UserAction }); + confirm({ type: ToolConfirmKind.UserAction }); break; case ConfirmationOutcome.Skip: - toolInvocation.confirmed.complete({ type: ToolConfirmKind.Skipped }); + confirm({ type: ToolConfirmKind.Skipped }); break; } @@ -339,10 +345,13 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(toDisposable(() => hasToolConfirmation.reset())); - toolInvocation.confirmed.p.then(() => { - hasToolConfirmation.reset(); - this._onNeedsRerender.fire(); - }); + this._register(autorunSelfDisposable(reader => { + if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { + reader.dispose(); + hasToolConfirmation.reset(); + this._onNeedsRerender.fire(); + } + })); this.domNode = confirmWidget.domNode; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 75f87b4e3df..7ba55ee90fc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -74,7 +74,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa dom.clearNode(this.domNode); partStore.clear(); - if (toolInvocation.presentation === ToolInvocationPresentation.HiddenAfterComplete && toolInvocation.isComplete) { + if (toolInvocation.presentation === ToolInvocationPresentation.HiddenAfterComplete && IChatToolInvocation.isComplete(toolInvocation)) { return; } @@ -98,7 +98,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa } private get autoApproveMessageContent() { - const reason = this.toolInvocation.isConfirmed; + const reason = IChatToolInvocation.isConfirmed(this.toolInvocation); if (!reason || typeof reason === 'boolean') { return; } @@ -146,7 +146,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa if (this.toolInvocation.toolSpecificData?.kind === 'extensions') { return this.instantiationService.createInstance(ExtensionsInstallConfirmationWidgetSubPart, this.toolInvocation, this.context); } - if (this.toolInvocation.confirmationMessages) { + if (this.toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { if (this.toolInvocation.toolSpecificData?.kind === 'terminal') { return this.instantiationService.createInstance(ChatTerminalToolConfirmationSubPart, this.toolInvocation, this.toolInvocation.toolSpecificData, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockModelCollection, this.codeBlockStartIndex); } else { @@ -159,15 +159,16 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa return this.instantiationService.createInstance(ChatTerminalToolProgressPart, this.toolInvocation, this.toolInvocation.toolSpecificData, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockStartIndex, this.codeBlockModelCollection); } - if (Array.isArray(this.toolInvocation.resultDetails) && this.toolInvocation.resultDetails?.length) { - return this.instantiationService.createInstance(ChatResultListSubPart, this.toolInvocation, this.context, this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage, this.toolInvocation.resultDetails, this.listPool); + const resultDetails = IChatToolInvocation.resultDetails(this.toolInvocation); + if (Array.isArray(resultDetails) && resultDetails.length) { + return this.instantiationService.createInstance(ChatResultListSubPart, this.toolInvocation, this.context, this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage, resultDetails, this.listPool); } - if (isToolResultOutputDetails(this.toolInvocation.resultDetails)) { + if (isToolResultOutputDetails(resultDetails)) { return this.instantiationService.createInstance(ChatToolOutputSubPart, this.toolInvocation, this.context); } - if (isToolResultInputOutputDetails(this.toolInvocation.resultDetails)) { + if (isToolResultInputOutputDetails(resultDetails)) { return this.instantiationService.createInstance( ChatInputOutputMarkdownProgressPart, this.toolInvocation, @@ -176,14 +177,14 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa this.codeBlockStartIndex, this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage, this.toolInvocation.originMessage, - this.toolInvocation.resultDetails.input, - this.toolInvocation.resultDetails.output, - !!this.toolInvocation.resultDetails.isError, + resultDetails.input, + resultDetails.output, + !!resultDetails.isError, this.currentWidthDelegate ); } - if (this.toolInvocation.kind === 'toolInvocation' && this.toolInvocation.toolSpecificData?.kind === 'input' && !this.toolInvocation.isComplete) { + if (this.toolInvocation.kind === 'toolInvocation' && this.toolInvocation.toolSpecificData?.kind === 'input' && !IChatToolInvocation.isComplete(this.toolInvocation)) { return this.instantiationService.createInstance( ChatInputOutputMarkdownProgressPart, this.toolInvocation, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts index c700b12dbd9..3886e5475b6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts @@ -6,6 +6,7 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; import { IChatCodeBlockInfo } from '../../chat.js'; @@ -29,23 +30,27 @@ export abstract class BaseChatToolInvocationSubPart extends Disposable { ) { super(); - if (toolInvocation.kind === 'toolInvocation' && !toolInvocation.isComplete) { - toolInvocation.isCompletePromise.then(() => this._onNeedsRerender.fire()); + if (toolInvocation.kind === 'toolInvocation' && !IChatToolInvocation.isComplete(toolInvocation)) { + this._register(autorunSelfDisposable(reader => { + if (IChatToolInvocation.isComplete(toolInvocation, reader)) { + this._onNeedsRerender.fire(); + reader.dispose(); + } + })); } } protected getIcon() { const toolInvocation = this.toolInvocation; - const isSkipped = typeof toolInvocation.isConfirmed !== 'boolean' && toolInvocation.isConfirmed?.type === ToolConfirmKind.Skipped; + const confirmState = IChatToolInvocation.isConfirmed(toolInvocation); + const isSkipped = confirmState?.type === ToolConfirmKind.Skipped; if (isSkipped) { return Codicon.circleSlash; } - const isConfirmed = typeof toolInvocation.isConfirmed === 'boolean' - ? toolInvocation.isConfirmed - : toolInvocation.isConfirmed?.type !== ToolConfirmKind.Denied; - return !isConfirmed ? + + return confirmState?.type === ToolConfirmKind.Denied ? Codicon.error : - toolInvocation.isComplete ? + IChatToolInvocation.isComplete(toolInvocation) ? Codicon.check : ThemeIcon.modify(Codicon.loading, 'spin'); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts index 111b8fb0c22..9f93197494a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts @@ -48,7 +48,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { super(toolInvocation); const details: IToolResultOutputDetails = toolInvocation.kind === 'toolInvocation' - ? toolInvocation.resultDetails as IToolResultOutputDetails + ? IChatToolInvocation.resultDetails(toolInvocation) as IToolResultOutputDetails : { output: { type: 'data', diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index 5a5f1e42dee..19c532c5871 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -36,7 +36,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { } private createProgressPart(): HTMLElement { - if (this.toolInvocation.isComplete && this.toolIsConfirmed && this.toolInvocation.pastTenseMessage) { + if (IChatToolInvocation.isComplete(this.toolInvocation) && this.toolIsConfirmed && this.toolInvocation.pastTenseMessage) { const key = this.getAnnouncementKey('complete'); const completionContent = this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage; const shouldAnnounce = this.toolInvocation.kind === 'toolInvocation' && this.hasMeaningfulContent(this.toolInvocation.pastTenseMessage) ? this.computeShouldAnnounce(key) : false; @@ -45,7 +45,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { return part.domNode; } else { const container = document.createElement('div'); - const progressObservable = this.toolInvocation.kind === 'toolInvocation' ? this.toolInvocation.progress : undefined; + const progressObservable = this.toolInvocation.kind === 'toolInvocation' ? this.toolInvocation.state.map((s, r) => s.type === IChatToolInvocation.StateKind.Executing ? s.progress.read(r) : undefined) : undefined; this._register(autorun(reader => { const progress = progressObservable?.read(reader); const key = this.getAnnouncementKey('progress'); @@ -59,13 +59,8 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { } private get toolIsConfirmed() { - if (!this.toolInvocation.isConfirmed) { - return false; - } - if (this.toolInvocation.isConfirmed === true) { - return true; - } - return this.toolInvocation.isConfirmed.type !== ToolConfirmKind.Denied; + const c = IChatToolInvocation.isConfirmed(this.toolInvocation); + return !!c && c.type !== ToolConfirmKind.Denied; } private renderProgressContent(content: IMarkdownString | string, shouldAnnounce: boolean) { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 958a5203d48..1e3cd408e41 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -778,8 +778,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer part.kind === 'toolInvocation' && !part.isComplete)) || + ((lastPart.kind === 'toolInvocation' || lastPart.kind === 'toolInvocationSerialized') && (IChatToolInvocation.isComplete(lastPart) || lastPart.presentation === 'hidden')) || + ((lastPart.kind === 'textEditGroup' || lastPart.kind === 'notebookEditGroup') && lastPart.done && !partsToRender.some(part => part.kind === 'toolInvocation' && !IChatToolInvocation.isComplete(part))) || (lastPart.kind === 'progressTask' && lastPart.deferred.isSettled) || lastPart.kind === 'prepareToolInvocation' || lastPart.kind === 'mcpServersStarting' ) { diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index 3654c2e4449..544966f98fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -13,6 +13,7 @@ import { ServicesAccessor } from '../../../../platform/instantiation/common/inst import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { migrateLegacyTerminalToolSpecificData } from '../common/chat.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IChatToolInvocation } from '../common/chatService.js'; import { isResponseVM } from '../common/chatViewModel.js'; import { ChatTreeItem, IChatWidget, IChatWidgetService } from './chat.js'; @@ -83,7 +84,7 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi }); const toolInvocations = item.response.value.filter(item => item.kind === 'toolInvocation'); for (const toolInvocation of toolInvocations) { - if (toolInvocation.confirmationMessages) { + if (toolInvocation.confirmationMessages && toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { const title = typeof toolInvocation.confirmationMessages.title === 'string' ? toolInvocation.confirmationMessages.title : toolInvocation.confirmationMessages.title.value; const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderAsPlaintext(toolInvocation.confirmationMessages.message)); let input = ''; @@ -106,9 +107,12 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi responseContent += `: ${input}`; } responseContent += `\n${message}\n`; - } else if (toolInvocation.isComplete && toolInvocation.resultDetails && 'input' in toolInvocation.resultDetails) { - responseContent += '\n' + toolInvocation.resultDetails.isError ? 'Errored ' : 'Completed '; - responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderAsPlaintext(toolInvocation.invocationMessage))} with input: ${toolInvocation.resultDetails.input}`}\n`; + } else { + const resultDetails = IChatToolInvocation.resultDetails(toolInvocation); + if (resultDetails && 'input' in resultDetails) { + responseContent += '\n' + resultDetails.isError ? 'Errored ' : 'Completed '; + responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderAsPlaintext(toolInvocation.invocationMessage))} with input: ${resultDetails.input}`}\n`; + } } } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index c915956415e..6aa5a10ef76 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -38,7 +38,7 @@ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatModel } from '../common/chatModel.js'; import { IVariableReference } from '../common/chatModes.js'; import { ChatToolInvocation } from '../common/chatProgressTypes/chatToolInvocation.js'; -import { ConfirmedReason, IChatService, ToolConfirmKind } from '../common/chatService.js'; +import { ConfirmedReason, IChatService, IChatToolInvocation, ToolConfirmKind } from '../common/chatService.js'; import { ChatRequestToolReferenceEntry, toToolSetVariableEntry, toToolVariableEntry } from '../common/chatVariableEntries.js'; import { ChatConfiguration } from '../common/constants.js'; import { CountTokensCallback, createToolSchemaUri, ILanguageModelToolsService, IPreparedToolInvocation, IToolAndToolSetEnablementMap, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultInputOutputDetails, stringifyPromptTsxPart, ToolDataSource, ToolSet } from '../common/languageModelToolsService.js'; @@ -316,11 +316,11 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo source.dispose(true); })); store.add(token.onCancellationRequested(() => { - toolInvocation?.confirmed.complete({ type: ToolConfirmKind.Denied }); + IChatToolInvocation.confirmWith(toolInvocation, { type: ToolConfirmKind.Denied }); source.cancel(); })); store.add(source.token.onCancellationRequested(() => { - toolInvocation?.confirmed.complete({ type: ToolConfirmKind.Denied }); + IChatToolInvocation.confirmWith(toolInvocation, { type: ToolConfirmKind.Denied }); })); token = source.token; @@ -332,17 +332,17 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo trackedCall.invocation = toolInvocation; const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace); if (autoConfirmed) { - toolInvocation.confirmed.complete(autoConfirmed); + IChatToolInvocation.confirmWith(toolInvocation, autoConfirmed); } model.acceptResponseProgress(request, toolInvocation); dto.toolSpecificData = toolInvocation?.toolSpecificData; if (prepared?.confirmationMessages) { - if (!toolInvocation.isConfirmed?.type && !autoConfirmed) { + if (!IChatToolInvocation.isConfirmed(toolInvocation) && !autoConfirmed) { this.playAccessibilitySignal([toolInvocation]); } - const userConfirmed = await toolInvocation.confirmed.p; + const userConfirmed = await IChatToolInvocation.awaitConfirmation(toolInvocation, token); if (userConfirmed.type === ToolConfirmKind.Denied) { throw new CancellationError(); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 8ac5999ece6..74403fd6d09 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -12,7 +12,7 @@ import { ResourceMap } from '../../../../base/common/map.js'; import { revive } from '../../../../base/common/marshalling.js'; import { Schemas } from '../../../../base/common/network.js'; import { equals } from '../../../../base/common/objects.js'; -import { IObservable, ObservablePromise, observableFromEvent, observableSignalFromEvent } from '../../../../base/common/observable.js'; +import { IObservable, ObservablePromise, autorunSelfDisposable, observableFromEvent, observableSignalFromEvent } from '../../../../base/common/observable.js'; import { basename, isEqual } from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI, UriComponents, UriDto, isUriComponents } from '../../../../base/common/uri.js'; @@ -492,10 +492,11 @@ class AbstractResponse implements IResponse { } // For completed tool invocations, also include the result details if available - if (toolInvocation.kind === 'toolInvocationSerialized' || (toolInvocation.kind === 'toolInvocation' && toolInvocation.isComplete)) { - if (toolInvocation.resultDetails && 'input' in toolInvocation.resultDetails) { - const resultPrefix = toolInvocation.kind === 'toolInvocationSerialized' || toolInvocation.isComplete ? 'Completed' : 'Errored'; - text += `\n${resultPrefix} with input: ${toolInvocation.resultDetails.input}`; + if (toolInvocation.kind === 'toolInvocationSerialized' || (toolInvocation.kind === 'toolInvocation' && IChatToolInvocation.isComplete(toolInvocation))) { + const resultDetails = IChatToolInvocation.resultDetails(toolInvocation); + if (resultDetails && 'input' in resultDetails) { + const resultPrefix = toolInvocation.kind === 'toolInvocationSerialized' || IChatToolInvocation.isComplete(toolInvocation) ? 'Completed' : 'Errored'; + text += `\n${resultPrefix} with input: ${resultDetails.input}`; } } @@ -680,13 +681,13 @@ export class Response extends AbstractResponse implements IDisposable { }); } else if (progress.kind === 'toolInvocation') { - if (progress.confirmationMessages) { - progress.confirmed.p.then(() => { - this._updateRepr(false); - }); - } - progress.isCompletePromise.then(() => { + autorunSelfDisposable(reader => { + progress.state.read(reader); // update repr when state changes this._updateRepr(false); + + if (IChatToolInvocation.isComplete(progress, reader)) { + reader.dispose(); + } }); this._responseParts.push(progress); this._updateRepr(quiet); @@ -908,7 +909,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel signal.read(r); return this._response.value.some(part => - part.kind === 'toolInvocation' && part.isConfirmed === undefined + part.kind === 'toolInvocation' && part.state.read(r).type === IChatToolInvocation.StateKind.WaitingForConfirmation || part.kind === 'confirmation' && part.isUsed === false ); }); diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index bdba2e6820d..af526f1f699 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -3,45 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DeferredPromise } from '../../../../../base/common/async.js'; import { encodeBase64 } from '../../../../../base/common/buffer.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; -import { observableValue } from '../../../../../base/common/observable.js'; +import { IObservable, ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; -import { ConfirmedReason, IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type IChatTerminalToolInvocationData } from '../chatService.js'; +import { IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type IChatTerminalToolInvocationData } from '../chatService.js'; import { IPreparedToolInvocation, isToolResultOutputDetails, IToolConfirmationMessages, IToolData, IToolProgressStep, IToolResult, ToolDataSource } from '../languageModelToolsService.js'; export class ChatToolInvocation implements IChatToolInvocation { public readonly kind: 'toolInvocation' = 'toolInvocation'; - private _isComplete = false; - public get isComplete(): boolean { - return this._isComplete; - } - - private _isCompleteDeferred = new DeferredPromise(); - public get isCompletePromise(): Promise { - return this._isCompleteDeferred.p; - } - - private _confirmDeferred = new DeferredPromise(); - public get confirmed() { - return this._confirmDeferred; - } - - public get isConfirmed(): ConfirmedReason | undefined { - return this._confirmDeferred.value; - } - - private _resultDetails: IToolResult['toolResultDetails'] | undefined; - public get resultDetails(): IToolResult['toolResultDetails'] | undefined { - return this._resultDetails; - } - public readonly invocationMessage: string | IMarkdownString; public readonly originMessage: string | IMarkdownString | undefined; public pastTenseMessage: string | IMarkdownString | undefined; - private _confirmationMessages: IToolConfirmationMessages | undefined; + public confirmationMessages: IToolConfirmationMessages | undefined; public readonly presentation: IPreparedToolInvocation['presentation']; public readonly toolId: string; public readonly source: ToolDataSource; @@ -49,7 +24,13 @@ export class ChatToolInvocation implements IChatToolInvocation { public readonly toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent; - public readonly progress = observableValue<{ message?: string | IMarkdownString; progress: number }>(this, { progress: 0 }); + private readonly _progress = observableValue<{ message?: string | IMarkdownString; progress: number | undefined }>(this, { progress: 0 }); + private readonly _state: ISettableObservable; + + public get state(): IObservable { + return this._state; + } + constructor(preparedInvocation: IPreparedToolInvocation | undefined, toolData: IToolData, public readonly toolCallId: string, fromSubAgent: boolean | undefined) { const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${toolData.displayName}"`); @@ -57,61 +38,66 @@ export class ChatToolInvocation implements IChatToolInvocation { this.invocationMessage = invocationMessage; this.pastTenseMessage = preparedInvocation?.pastTenseMessage; this.originMessage = preparedInvocation?.originMessage; - this._confirmationMessages = preparedInvocation?.confirmationMessages; + this.confirmationMessages = preparedInvocation?.confirmationMessages; this.presentation = preparedInvocation?.presentation; this.toolSpecificData = preparedInvocation?.toolSpecificData; this.toolId = toolData.id; this.source = toolData.source; this.fromSubAgent = fromSubAgent; - if (!this._confirmationMessages) { - // No confirmation needed - this._confirmDeferred.complete({ type: ToolConfirmKind.ConfirmationNotNeeded }); + if (!this.confirmationMessages) { + this._state = observableValue(this, { type: IChatToolInvocation.StateKind.Executing, confirmed: { type: ToolConfirmKind.ConfirmationNotNeeded }, progress: this._progress }); + } else { + this._state = observableValue(this, { + type: IChatToolInvocation.StateKind.WaitingForConfirmation, + confirm: reason => { + if (reason.type === ToolConfirmKind.Denied || reason.type === ToolConfirmKind.Skipped) { + this._state.set({ type: IChatToolInvocation.StateKind.Cancelled, reason: reason.type }, undefined); + } else { + this._state.set({ type: IChatToolInvocation.StateKind.Executing, confirmed: reason, progress: this._progress }, undefined); + } + } + }); } - - this._confirmDeferred.p.then(() => { - this._confirmationMessages = undefined; - }); - - this._isCompleteDeferred.p.then(() => { - this._isComplete = true; - }); } public complete(result: IToolResult | undefined): void { if (result?.toolResultMessage) { this.pastTenseMessage = result.toolResultMessage; + } else if (this._progress.get().message) { + this.pastTenseMessage = this._progress.get().message; } - this._resultDetails = result?.toolResultDetails; - this._isCompleteDeferred.complete(); - } - - public get confirmationMessages(): IToolConfirmationMessages | undefined { - return this._confirmationMessages; + this._state.set({ + type: IChatToolInvocation.StateKind.Completed, + confirmed: IChatToolInvocation.isConfirmed(this) || { type: ToolConfirmKind.UserAction }, + resultDetails: result?.toolResultDetails, + postConfirmed: undefined, + }, undefined); } public acceptProgress(step: IToolProgressStep) { - const prev = this.progress.get(); - this.progress.set({ + const prev = this._progress.get(); + this._progress.set({ progress: step.progress || prev.progress || 0, message: step.message, }, undefined); } public toJSON(): IChatToolInvocationSerialized { + const details = IChatToolInvocation.resultDetails(this); return { kind: 'toolInvocationSerialized', presentation: this.presentation, invocationMessage: this.invocationMessage, pastTenseMessage: this.pastTenseMessage, originMessage: this.originMessage, - isConfirmed: this._confirmDeferred.value, + isConfirmed: IChatToolInvocation.isConfirmed(this), isComplete: true, source: this.source, - resultDetails: isToolResultOutputDetails(this._resultDetails) - ? { output: { type: 'data', mimeType: this._resultDetails.output.mimeType, base64Data: encodeBase64(this._resultDetails.output.value) } } - : this._resultDetails, + resultDetails: isToolResultOutputDetails(details) + ? { output: { type: 'data', mimeType: details.output.mimeType, base64Data: encodeBase64(details.output.value) } } + : details, toolSpecificData: this.toolSpecificData, toolCallId: this.toolCallId, toolId: this.toolId, diff --git a/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts b/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts index ee8e6774a1d..b61ced7561c 100644 --- a/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts @@ -109,11 +109,12 @@ export class ChatResponseResourceFileSystemProvider extends Disposable implement private lookupURI(uri: URI): Uint8Array | Promise { const { result, index } = this.findMatchingInvocation(uri); - if (!isToolResultInputOutputDetails(result.resultDetails)) { + const details = IChatToolInvocation.resultDetails(result); + if (!isToolResultInputOutputDetails(details)) { throw createFileSystemProviderError(`Tool does not have I/O`, FileSystemProviderErrorCode.FileNotFound); } - const part = result.resultDetails.output.at(index); + const part = details.output.at(index); if (!part) { throw createFileSystemProviderError(`Tool does not have part`, FileSystemProviderErrorCode.FileNotFound); } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bd6d737b99f..b0689e3899e 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -8,7 +8,8 @@ import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; -import { autorunSelfDisposable, IObservable } from '../../../../base/common/observable.js'; +import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { autorun, autorunSelfDisposable, IObservable, IReader } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; @@ -339,29 +340,152 @@ export type ConfirmedReason = | { type: ToolConfirmKind.Skipped }; export interface IChatToolInvocation { - presentation: IPreparedToolInvocation['presentation']; - toolSpecificData?: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatPullRequestContent | IChatTodoListContent; - /** Presence of this property says that confirmation is required */ - confirmationMessages?: IToolConfirmationMessages; - confirmed: DeferredPromise; - /** undefined=don't know yet. */ - isConfirmed: ConfirmedReason | undefined; - originMessage: string | IMarkdownString | undefined; - invocationMessage: string | IMarkdownString; - pastTenseMessage: string | IMarkdownString | undefined; - resultDetails: IToolResult['toolResultDetails']; - source: ToolDataSource; - progress: IObservable<{ message?: string | IMarkdownString; progress: number | undefined }>; + readonly presentation: IPreparedToolInvocation['presentation']; + readonly toolSpecificData?: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatPullRequestContent | IChatTodoListContent; + readonly confirmationMessages?: IToolConfirmationMessages; + readonly originMessage: string | IMarkdownString | undefined; + readonly invocationMessage: string | IMarkdownString; + readonly pastTenseMessage: string | IMarkdownString | undefined; + readonly source: ToolDataSource; readonly toolId: string; readonly toolCallId: string; readonly fromSubAgent?: boolean; + readonly state: IObservable; - isCompletePromise: Promise; - isComplete: boolean; - complete(result: IToolResult): void; kind: 'toolInvocation'; } +export namespace IChatToolInvocation { + export const enum StateKind { + WaitingForConfirmation, + Executing, + WaitingForPostApproval, + Completed, + Cancelled, + } + + interface IChatToolInvocationStateBase { + type: StateKind; + } + + interface IChatToolInvocationWaitingForConfirmationState extends IChatToolInvocationStateBase { + type: StateKind.WaitingForConfirmation; + confirm(reason: ConfirmedReason): void; + } + + interface IChatToolInvocationPostConfirmState { + confirmed: ConfirmedReason; + } + + interface IChatToolInvocationExecutingState extends IChatToolInvocationStateBase, IChatToolInvocationPostConfirmState { + type: StateKind.Executing; + progress: IObservable<{ message?: string | IMarkdownString; progress: number | undefined }>; + } + + interface IChatToolInvocationPostExecuteState extends IChatToolInvocationPostConfirmState { + resultDetails: IToolResult['toolResultDetails']; + } + + interface IChatToolWaitingForPostApprovalState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState { + type: StateKind.WaitingForPostApproval; + postConfirm(reason: ConfirmedReason): void; + } + + interface IChatToolInvocationCompleteState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState { + type: StateKind.Completed; + postConfirmed: ConfirmedReason | undefined; + } + + interface IChatToolInvocationCancelledState extends IChatToolInvocationStateBase { + type: StateKind.Cancelled; + reason: ToolConfirmKind.Denied | ToolConfirmKind.Skipped; + } + + export type State = + | IChatToolInvocationWaitingForConfirmationState + | IChatToolInvocationExecutingState + | IChatToolWaitingForPostApprovalState + | IChatToolInvocationCompleteState + | IChatToolInvocationCancelledState; + + export function isConfirmed(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): ConfirmedReason | undefined { + if (invocation.kind === 'toolInvocationSerialized') { + if (invocation.isConfirmed === undefined || typeof invocation.isConfirmed === 'boolean') { + return { type: invocation.isConfirmed ? ToolConfirmKind.UserAction : ToolConfirmKind.Denied }; + } + return invocation.isConfirmed; + } + + const state = invocation.state.read(reader); + if (state.type === StateKind.WaitingForConfirmation) { + return undefined; // don't know yet + } + if (state.type === StateKind.Cancelled) { + return { type: state.reason }; + } + + return state.confirmed; + } + + export function awaitConfirmation(invocation: IChatToolInvocation, token?: CancellationToken): Promise { + const reason = isConfirmed(invocation); + if (reason) { + return Promise.resolve(reason); + } + + const store = new DisposableStore(); + return new Promise(resolve => { + if (token) { + store.add(token.onCancellationRequested(() => { + resolve({ type: ToolConfirmKind.Denied }); + })); + } + + store.add(autorun(reader => { + const reason = isConfirmed(invocation, reader); + if (reason) { + store.dispose(); + resolve(reason); + } + })); + }).finally(() => { + store.dispose(); + }); + } + + export function confirmWith(invocation: IChatToolInvocation | undefined, reason: ConfirmedReason) { + const state = invocation?.state.get(); + if (state?.type === StateKind.WaitingForConfirmation) { + state.confirm(reason); + return true; + } + return false; + } + + export function resultDetails(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader) { + if (invocation.kind === 'toolInvocationSerialized') { + return invocation.resultDetails; + } + + const state = invocation.state.read(reader); + if (state.type === StateKind.Completed || state.type === StateKind.WaitingForPostApproval) { + return state.resultDetails; + } + + return undefined; + } + + export function isComplete(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): boolean { + if ('isComplete' in invocation) { // serialized + return true; // always cancelled or complete + } + + const state = invocation.state.read(reader); + return state.type === StateKind.Completed || state.type === StateKind.Cancelled; + } +} + + export interface IToolResultOutputDetailsSerialized { output: { type: 'data'; From 69490ecae716f89c551e20bbeb588fc66b92aea7 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Mon, 20 Oct 2025 13:26:30 -0700 Subject: [PATCH 1350/4355] Add azd fig spec --- .../terminal-suggest/src/completions/azd.ts | 1808 +++++++++++++++++ .../src/terminalSuggestMain.ts | 2 + 2 files changed, 1810 insertions(+) create mode 100644 extensions/terminal-suggest/src/completions/azd.ts diff --git a/extensions/terminal-suggest/src/completions/azd.ts b/extensions/terminal-suggest/src/completions/azd.ts new file mode 100644 index 00000000000..1b5609b0433 --- /dev/null +++ b/extensions/terminal-suggest/src/completions/azd.ts @@ -0,0 +1,1808 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface AzdEnvListItem { + Name: string; + DotEnvPath: string; + HasLocal: boolean; + HasRemote: boolean; + IsDefault: boolean; +} + +interface AzdTemplateListItem { + name: string; + description: string; + repositoryPath: string; + tags: string[]; +} + +interface AzdExtensionListItem { + id: string; + name: string; + namespace: string; + version: string; + installedVersion: string; + source: string; +} + +const azdGenerators: Record = { + listEnvironments: { + script: ['azd', 'env', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const envs: AzdEnvListItem[] = JSON.parse(out); + return envs.map((env) => ({ + name: env.Name, + displayName: env.IsDefault ? 'Default' : undefined, + })); + } catch { + return []; + } + }, + }, + listEnvironmentVariables: { + script: ['azd', 'env', 'get-values', '--output', 'json'], + postProcess: (out) => { + try { + const envVars: Record = JSON.parse(out); + return Object.keys(envVars).map((key) => ({ + name: key, + })); + } catch { + return []; + } + }, + }, + listTemplates: { + script: ['azd', 'template', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const templates: AzdTemplateListItem[] = JSON.parse(out); + return templates.map((template) => ({ + name: template.repositoryPath, + description: template.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listTemplateTags: { + script: ['azd', 'template', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const templates: AzdTemplateListItem[] = JSON.parse(out); + const tagsSet = new Set(); + + // Collect all unique tags from all templates + templates.forEach((template) => { + if (template.tags && Array.isArray(template.tags)) { + template.tags.forEach((tag) => tagsSet.add(tag)); + } + }); + + // Convert set to array and return as suggestions + return Array.from(tagsSet).sort().map((tag) => ({ + name: tag, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listTemplatesFiltered: { + custom: async (tokens, executeCommand, generatorContext) => { + // Find if there's a -f or --filter flag in the tokens + let filterValue: string | undefined; + for (let i = 0; i < tokens.length; i++) { + if ((tokens[i] === '-f' || tokens[i] === '--filter') && i + 1 < tokens.length) { + filterValue = tokens[i + 1]; + break; + } + } + + // Build the azd command with filter if present + const args = ['template', 'list', '--output', 'json']; + if (filterValue) { + args.push('--filter', filterValue); + } + + try { + const { stdout } = await executeCommand({ + command: 'azd', + args: args, + }); + + const templates: AzdTemplateListItem[] = JSON.parse(stdout); + return templates.map((template) => ({ + name: template.repositoryPath, + description: template.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listExtensions: { + script: ['azd', 'ext', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const extensions: AzdExtensionListItem[] = JSON.parse(out); + const uniqueExtensions = new Map(); + + extensions.forEach((ext) => { + if (!uniqueExtensions.has(ext.id)) { + uniqueExtensions.set(ext.id, ext); + } + }); + + return Array.from(uniqueExtensions.values()).map((ext) => ({ + name: ext.id, + description: ext.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listInstalledExtensions: { + script: ['azd', 'ext', 'list', '--installed', '--output', 'json'], + postProcess: (out) => { + try { + const extensions: AzdExtensionListItem[] = JSON.parse(out); + const uniqueExtensions = new Map(); + + extensions.forEach((ext) => { + if (!uniqueExtensions.has(ext.id)) { + uniqueExtensions.set(ext.id, ext); + } + }); + + return Array.from(uniqueExtensions.values()).map((ext) => ({ + name: ext.id, + description: ext.name, + })); + } catch { + return []; + } + }, + }, +}; + +const completionSpec: Fig.Spec = { + name: 'azd', + description: 'Azure Developer CLI', + subcommands: [ + { + name: ['add'], + description: 'Add a component to your project.', + }, + { + name: ['auth'], + description: 'Authenticate with Azure.', + subcommands: [ + { + name: ['login'], + description: 'Log in to Azure.', + options: [ + { + name: ['--check-status'], + description: 'Checks the log-in status instead of logging in.', + }, + { + name: ['--client-certificate'], + description: 'The path to the client certificate for the service principal to authenticate with.', + args: [ + { + name: 'client-certificate', + }, + ], + }, + { + name: ['--client-id'], + description: 'The client id for the service principal to authenticate with.', + args: [ + { + name: 'client-id', + }, + ], + }, + { + name: ['--client-secret'], + description: 'The client secret for the service principal to authenticate with. Set to the empty string to read the value from the console.', + args: [ + { + name: 'client-secret', + }, + ], + }, + { + name: ['--federated-credential-provider'], + description: 'The provider to use to acquire a federated token to authenticate with. Supported values: github, azure-pipelines, oidc', + args: [ + { + name: 'federated-credential-provider', + suggestions: ['github', 'azure-pipelines', 'oidc'], + }, + ], + }, + { + name: ['--managed-identity'], + description: 'Use a managed identity to authenticate.', + }, + { + name: ['--redirect-port'], + description: 'Choose the port to be used as part of the redirect URI during interactive login.', + args: [ + { + name: 'redirect-port', + }, + ], + }, + { + name: ['--tenant-id'], + description: 'The tenant id or domain name to authenticate with.', + args: [ + { + name: 'tenant-id', + }, + ], + }, + { + name: ['--use-device-code'], + description: 'When true, log in by using a device code instead of a browser.', + }, + ], + }, + { + name: ['logout'], + description: 'Log out of Azure.', + }, + ], + }, + { + name: ['completion'], + description: 'Generate shell completion scripts.', + subcommands: [ + { + name: ['bash'], + description: 'Generate bash completion script.', + }, + { + name: ['fig'], + description: 'Generate Fig autocomplete spec.', + }, + { + name: ['fish'], + description: 'Generate fish completion script.', + }, + { + name: ['powershell'], + description: 'Generate PowerShell completion script.', + }, + { + name: ['zsh'], + description: 'Generate zsh completion script.', + }, + ], + }, + { + name: ['config'], + description: 'Manage azd configurations (ex: default Azure subscription, location).', + subcommands: [ + { + name: ['get'], + description: 'Gets a configuration.', + args: { + name: 'path', + }, + }, + { + name: ['list-alpha'], + description: 'Display the list of available features in alpha stage.', + }, + { + name: ['reset'], + description: 'Resets configuration to default.', + options: [ + { + name: ['--force', '-f'], + description: 'Force reset without confirmation.', + isDangerous: true, + }, + ], + }, + { + name: ['set'], + description: 'Sets a configuration.', + args: [ + { + name: 'path', + }, + { + name: 'value', + }, + ], + }, + { + name: ['show'], + description: 'Show all the configuration values.', + }, + { + name: ['unset'], + description: 'Unsets a configuration.', + args: { + name: 'path', + }, + }, + ], + }, + { + name: ['deploy'], + description: 'Deploy your project code to Azure.', + options: [ + { + name: ['--all'], + description: 'Deploys all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--from-package'], + description: 'Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag).', + args: [ + { + name: 'file-path|image-tag', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['down'], + description: 'Delete your project\'s Azure resources.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--force'], + description: 'Does not require confirmation before it deletes resources.', + isDangerous: true, + }, + { + name: ['--purge'], + description: 'Does not require confirmation before it permanently deletes resources that are soft-deleted by default (for example, key vaults).', + isDangerous: true, + }, + ], + }, + { + name: ['env'], + description: 'Manage environments (ex: default environment, environment variables).', + subcommands: [ + { + name: ['get-value'], + description: 'Get specific environment value.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'keyName', + generators: azdGenerators.listEnvironmentVariables, + }, + }, + { + name: ['get-values'], + description: 'Get all environment values.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + }, + { + name: ['list', 'ls'], + description: 'List environments.', + }, + { + name: ['new'], + description: 'Create a new environment and set it as the default.', + options: [ + { + name: ['--location', '-l'], + description: 'Azure location for the new environment', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--subscription'], + description: 'Name or ID of an Azure subscription to use for the new environment', + args: [ + { + name: 'subscription', + }, + ], + }, + ], + args: { + name: 'environment', + }, + }, + { + name: ['refresh'], + description: 'Refresh environment values by using information from a previous infrastructure provision.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--hint'], + description: 'Hint to help identify the environment to refresh', + args: [ + { + name: 'hint', + }, + ], + }, + ], + args: { + name: 'environment', + }, + }, + { + name: ['select'], + description: 'Set the default environment.', + args: { + name: 'environment', + generators: azdGenerators.listEnvironments, + }, + }, + { + name: ['set'], + description: 'Set one or more environment values.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--file'], + description: 'Path to .env formatted file to load environment values from.', + args: [ + { + name: 'file', + }, + ], + }, + ], + args: [ + { + name: 'key', + isOptional: true, + }, + { + name: 'value', + isOptional: true, + }, + ], + }, + { + name: ['set-secret'], + description: 'Set a name as a reference to a Key Vault secret in the environment.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'name', + }, + }, + ], + }, + { + name: ['extension', 'ext'], + description: 'Manage azd extensions.', + subcommands: [ + { + name: ['install'], + description: 'Installs specified extensions.', + options: [ + { + name: ['--force', '-f'], + description: 'Force installation even if it would downgrade the current version', + isDangerous: true, + }, + { + name: ['--source', '-s'], + description: 'The extension source to use for installs', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--version', '-v'], + description: 'The version of the extension to install', + args: [ + { + name: 'version', + }, + ], + }, + ], + args: { + name: 'extension-id', + generators: azdGenerators.listExtensions, + }, + }, + { + name: ['list'], + description: 'List available extensions.', + options: [ + { + name: ['--installed'], + description: 'List installed extensions', + }, + { + name: ['--source'], + description: 'Filter extensions by source', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--tags'], + description: 'Filter extensions by tags', + isRepeatable: true, + args: [ + { + name: 'tags', + }, + ], + }, + ], + }, + { + name: ['show'], + description: 'Show details for a specific extension.', + options: [ + { + name: ['--source', '-s'], + description: 'The extension source to use.', + args: [ + { + name: 'source', + }, + ], + }, + ], + args: { + name: 'extension-name', + }, + }, + { + name: ['source'], + description: 'View and manage extension sources', + subcommands: [ + { + name: ['add'], + description: 'Add an extension source with the specified name', + options: [ + { + name: ['--location', '-l'], + description: 'The location of the extension source', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--name', '-n'], + description: 'The name of the extension source', + args: [ + { + name: 'name', + }, + ], + }, + { + name: ['--type', '-t'], + description: 'The type of the extension source. Supported types are \'file\' and \'url\'', + args: [ + { + name: 'type', + }, + ], + }, + ], + }, + { + name: ['list'], + description: 'List extension sources', + }, + { + name: ['remove'], + description: 'Remove an extension source with the specified name', + args: { + name: 'name', + }, + }, + ], + }, + { + name: ['uninstall'], + description: 'Uninstall specified extensions.', + options: [ + { + name: ['--all'], + description: 'Uninstall all installed extensions', + }, + ], + args: { + name: 'extension-id', + isOptional: true, + generators: azdGenerators.listInstalledExtensions, + }, + }, + { + name: ['upgrade'], + description: 'Upgrade specified extensions.', + options: [ + { + name: ['--all'], + description: 'Upgrade all installed extensions', + }, + { + name: ['--source', '-s'], + description: 'The extension source to use for upgrades', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--version', '-v'], + description: 'The version of the extension to upgrade to', + args: [ + { + name: 'version', + }, + ], + }, + ], + args: { + name: 'extension-id', + isOptional: true, + generators: azdGenerators.listInstalledExtensions, + }, + }, + ], + }, + { + name: ['hooks'], + description: 'Develop, test and run hooks for a project.', + subcommands: [ + { + name: ['run'], + description: 'Runs the specified hook for the project and services', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--platform'], + description: 'Forces hooks to run for the specified platform.', + args: [ + { + name: 'platform', + }, + ], + }, + { + name: ['--service'], + description: 'Only runs hooks for the specified service.', + args: [ + { + name: 'service', + }, + ], + }, + ], + args: { + name: 'name', + suggestions: [ + 'prebuild', + 'postbuild', + 'predeploy', + 'postdeploy', + 'predown', + 'postdown', + 'prepackage', + 'postpackage', + 'preprovision', + 'postprovision', + 'prepublish', + 'postpublish', + 'prerestore', + 'postrestore', + 'preup', + 'postup', + ], + }, + }, + ], + }, + { + name: ['infra'], + description: 'Manage your Infrastructure as Code (IaC).', + subcommands: [ + { + name: ['generate', 'gen', 'synth'], + description: 'Write IaC for your project to disk, allowing you to manually manage it.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--force'], + description: 'Overwrite any existing files without prompting', + isDangerous: true, + }, + ], + }, + ], + }, + { + name: ['init'], + description: 'Initialize a new application.', + options: [ + { + name: ['--branch', '-b'], + description: 'The template branch to initialize from. Must be used with a template argument (--template or -t).', + args: [ + { + name: 'branch', + }, + ], + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--filter', '-f'], + description: 'The tag(s) used to filter template results. Supports comma-separated values.', + isRepeatable: true, + args: [ + { + name: 'filter', + generators: azdGenerators.listTemplateTags, + }, + ], + }, + { + name: ['--from-code'], + description: 'Initializes a new application from your existing code.', + }, + { + name: ['--location', '-l'], + description: 'Azure location for the new environment', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--minimal', '-m'], + description: 'Initializes a minimal project.', + }, + { + name: ['--subscription', '-s'], + description: 'Name or ID of an Azure subscription to use for the new environment', + args: [ + { + name: 'subscription', + }, + ], + }, + { + name: ['--template', '-t'], + description: 'Initializes a new application from a template. You can use Full URI, /, or if it\'s part of the azure-samples organization.', + args: [ + { + name: 'template', + generators: azdGenerators.listTemplatesFiltered, + }, + ], + }, + { + name: ['--up'], + description: 'Provision and deploy to Azure after initializing the project from a template.', + }, + ], + }, + { + name: ['mcp'], + description: 'Manage Model Context Protocol (MCP) server. (Alpha)', + subcommands: [ + { + name: ['consent'], + description: 'Manage MCP tool consent.', + subcommands: [ + { + name: ['grant'], + description: 'Grant consent trust rules.', + options: [ + { + name: ['--action'], + description: 'Action type: \'all\' or \'readonly\'', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--global'], + description: 'Apply globally to all servers', + }, + { + name: ['--operation'], + description: 'Operation type: \'tool\' or \'sampling\'', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission: \'allow\', \'deny\', or \'prompt\'', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Rule scope: \'global\', or \'project\'', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--server'], + description: 'Server name', + args: [ + { + name: 'server', + }, + ], + }, + { + name: ['--tool'], + description: 'Specific tool name (requires --server)', + args: [ + { + name: 'tool', + }, + ], + }, + ], + }, + { + name: ['list'], + description: 'List consent rules.', + options: [ + { + name: ['--action'], + description: 'Action type to filter by (readonly, any)', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--operation'], + description: 'Operation to filter by (tool, sampling)', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission to filter by (allow, deny, prompt)', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Consent scope to filter by (global, project). If not specified, lists rules from all scopes.', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--target'], + description: 'Specific target to operate on (server/tool format)', + args: [ + { + name: 'target', + }, + ], + }, + ], + }, + { + name: ['revoke'], + description: 'Revoke consent rules.', + options: [ + { + name: ['--action'], + description: 'Action type to filter by (readonly, any)', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--operation'], + description: 'Operation to filter by (tool, sampling)', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission to filter by (allow, deny, prompt)', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Consent scope to filter by (global, project). If not specified, revokes rules from all scopes.', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--target'], + description: 'Specific target to operate on (server/tool format)', + args: [ + { + name: 'target', + }, + ], + }, + ], + }, + ], + }, + { + name: ['start'], + description: 'Starts the MCP server.', + }, + ], + }, + { + name: ['monitor'], + description: 'Monitor a deployed project.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--live'], + description: 'Open a browser to Application Insights Live Metrics. Live Metrics is currently not supported for Python apps.', + }, + { + name: ['--logs'], + description: 'Open a browser to Application Insights Logs.', + }, + { + name: ['--overview'], + description: 'Open a browser to Application Insights Overview Dashboard.', + }, + ], + }, + { + name: ['package'], + description: 'Packages the project\'s code to be deployed to Azure.', + options: [ + { + name: ['--all'], + description: 'Packages all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--output-path'], + description: 'File or folder path where the generated packages will be saved.', + args: [ + { + name: 'output-path', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['pipeline'], + description: 'Manage and configure your deployment pipelines.', + subcommands: [ + { + name: ['config'], + description: 'Configure your deployment pipeline to connect securely to Azure. (Beta)', + options: [ + { + name: ['--applicationServiceManagementReference', '-m'], + description: 'Service Management Reference. References application or service contact information from a Service or Asset Management database. This value must be a Universally Unique Identifier (UUID). You can set this value globally by running azd config set pipeline.config.applicationServiceManagementReference .', + args: [ + { + name: 'applicationServiceManagementReference', + }, + ], + }, + { + name: ['--auth-type'], + description: 'The authentication type used between the pipeline provider and Azure for deployment (Only valid for GitHub provider). Valid values: federated, client-credentials.', + args: [ + { + name: 'auth-type', + suggestions: ['federated', 'client-credentials'], + }, + ], + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--principal-id'], + description: 'The client id of the service principal to use to grant access to Azure resources as part of the pipeline.', + args: [ + { + name: 'principal-id', + }, + ], + }, + { + name: ['--principal-name'], + description: 'The name of the service principal to use to grant access to Azure resources as part of the pipeline.', + args: [ + { + name: 'principal-name', + }, + ], + }, + { + name: ['--principal-role'], + description: 'The roles to assign to the service principal. By default the service principal will be granted the Contributor and User Access Administrator roles.', + isRepeatable: true, + args: [ + { + name: 'principal-role', + }, + ], + }, + { + name: ['--provider'], + description: 'The pipeline provider to use (github for Github Actions and azdo for Azure Pipelines).', + args: [ + { + name: 'provider', + suggestions: ['github', 'azdo'], + }, + ], + }, + { + name: ['--remote-name'], + description: 'The name of the git remote to configure the pipeline to run on.', + args: [ + { + name: 'remote-name', + }, + ], + }, + ], + }, + ], + }, + { + name: ['provision'], + description: 'Provision Azure resources for your project.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--no-state'], + description: '(Bicep only) Forces a fresh deployment based on current Bicep template files, ignoring any stored deployment state.', + }, + { + name: ['--preview'], + description: 'Preview changes to Azure resources.', + }, + ], + }, + { + name: ['publish'], + description: 'Publish a service to a container registry.', + options: [ + { + name: ['--all'], + description: 'Publishes all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--from-package'], + description: 'Publishes the service from a container image (image tag).', + args: [ + { + name: 'image-tag', + }, + ], + }, + { + name: ['--to'], + description: 'The target container image in the form \'[registry/]repository[:tag]\' to publish to.', + args: [ + { + name: 'image-tag', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['restore'], + description: 'Restores the project\'s dependencies.', + options: [ + { + name: ['--all'], + description: 'Restores all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['show'], + description: 'Display information about your project and its resources.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--show-secrets'], + description: 'Unmask secrets in output.', + isDangerous: true, + }, + ], + args: { + name: 'resource-name|resource-id', + isOptional: true, + }, + }, + { + name: ['template'], + description: 'Find and view template details.', + subcommands: [ + { + name: ['list', 'ls'], + description: 'Show list of sample azd templates. (Beta)', + options: [ + { + name: ['--filter', '-f'], + description: 'The tag(s) used to filter template results. Supports comma-separated values.', + isRepeatable: true, + args: [ + { + name: 'filter', + generators: azdGenerators.listTemplateTags, + }, + ], + }, + { + name: ['--source', '-s'], + description: 'Filters templates by source.', + args: [ + { + name: 'source', + }, + ], + }, + ], + }, + { + name: ['show'], + description: 'Show details for a given template. (Beta)', + args: { + name: 'template', + generators: azdGenerators.listTemplates, + }, + }, + { + name: ['source'], + description: 'View and manage template sources. (Beta)', + subcommands: [ + { + name: ['add'], + description: 'Adds an azd template source with the specified key. (Beta)', + options: [ + { + name: ['--location', '-l'], + description: 'Location of the template source. Required when using type flag.', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--name', '-n'], + description: 'Display name of the template source.', + args: [ + { + name: 'name', + }, + ], + }, + { + name: ['--type', '-t'], + description: 'Kind of the template source. Supported types are \'file\', \'url\' and \'gh\'.', + args: [ + { + name: 'type', + }, + ], + }, + ], + args: { + name: 'key', + }, + }, + { + name: ['list', 'ls'], + description: 'Lists the configured azd template sources. (Beta)', + }, + { + name: ['remove'], + description: 'Removes the specified azd template source (Beta)', + args: { + name: 'key', + }, + }, + ], + }, + ], + }, + { + name: ['up'], + description: 'Provision and deploy your project to Azure with a single command.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + }, + { + name: ['version'], + description: 'Print the version number of Azure Developer CLI.', + }, + { + name: ['help'], + description: 'Help about any command', + subcommands: [ + { + name: ['add'], + description: 'Add a component to your project.', + }, + { + name: ['auth'], + description: 'Authenticate with Azure.', + subcommands: [ + { + name: ['login'], + description: 'Log in to Azure.', + }, + { + name: ['logout'], + description: 'Log out of Azure.', + }, + ], + }, + { + name: ['completion'], + description: 'Generate shell completion scripts.', + subcommands: [ + { + name: ['bash'], + description: 'Generate bash completion script.', + }, + { + name: ['fig'], + description: 'Generate Fig autocomplete spec.', + }, + { + name: ['fish'], + description: 'Generate fish completion script.', + }, + { + name: ['powershell'], + description: 'Generate PowerShell completion script.', + }, + { + name: ['zsh'], + description: 'Generate zsh completion script.', + }, + ], + }, + { + name: ['config'], + description: 'Manage azd configurations (ex: default Azure subscription, location).', + subcommands: [ + { + name: ['get'], + description: 'Gets a configuration.', + }, + { + name: ['list-alpha'], + description: 'Display the list of available features in alpha stage.', + }, + { + name: ['reset'], + description: 'Resets configuration to default.', + }, + { + name: ['set'], + description: 'Sets a configuration.', + }, + { + name: ['show'], + description: 'Show all the configuration values.', + }, + { + name: ['unset'], + description: 'Unsets a configuration.', + }, + ], + }, + { + name: ['deploy'], + description: 'Deploy your project code to Azure.', + }, + { + name: ['down'], + description: 'Delete your project\'s Azure resources.', + }, + { + name: ['env'], + description: 'Manage environments (ex: default environment, environment variables).', + subcommands: [ + { + name: ['get-value'], + description: 'Get specific environment value.', + }, + { + name: ['get-values'], + description: 'Get all environment values.', + }, + { + name: ['list', 'ls'], + description: 'List environments.', + }, + { + name: ['new'], + description: 'Create a new environment and set it as the default.', + }, + { + name: ['refresh'], + description: 'Refresh environment values by using information from a previous infrastructure provision.', + }, + { + name: ['select'], + description: 'Set the default environment.', + }, + { + name: ['set'], + description: 'Set one or more environment values.', + }, + { + name: ['set-secret'], + description: 'Set a name as a reference to a Key Vault secret in the environment.', + }, + ], + }, + { + name: ['extension', 'ext'], + description: 'Manage azd extensions.', + subcommands: [ + { + name: ['install'], + description: 'Installs specified extensions.', + }, + { + name: ['list'], + description: 'List available extensions.', + }, + { + name: ['show'], + description: 'Show details for a specific extension.', + }, + { + name: ['source'], + description: 'View and manage extension sources', + subcommands: [ + { + name: ['add'], + description: 'Add an extension source with the specified name', + }, + { + name: ['list'], + description: 'List extension sources', + }, + { + name: ['remove'], + description: 'Remove an extension source with the specified name', + }, + ], + }, + { + name: ['uninstall'], + description: 'Uninstall specified extensions.', + }, + { + name: ['upgrade'], + description: 'Upgrade specified extensions.', + }, + ], + }, + { + name: ['hooks'], + description: 'Develop, test and run hooks for a project.', + subcommands: [ + { + name: ['run'], + description: 'Runs the specified hook for the project and services', + }, + ], + }, + { + name: ['infra'], + description: 'Manage your Infrastructure as Code (IaC).', + subcommands: [ + { + name: ['generate', 'gen', 'synth'], + description: 'Write IaC for your project to disk, allowing you to manually manage it.', + }, + ], + }, + { + name: ['init'], + description: 'Initialize a new application.', + }, + { + name: ['mcp'], + description: 'Manage Model Context Protocol (MCP) server. (Alpha)', + subcommands: [ + { + name: ['consent'], + description: 'Manage MCP tool consent.', + subcommands: [ + { + name: ['grant'], + description: 'Grant consent trust rules.', + }, + { + name: ['list'], + description: 'List consent rules.', + }, + { + name: ['revoke'], + description: 'Revoke consent rules.', + }, + ], + }, + { + name: ['start'], + description: 'Starts the MCP server.', + }, + ], + }, + { + name: ['monitor'], + description: 'Monitor a deployed project.', + }, + { + name: ['package'], + description: 'Packages the project\'s code to be deployed to Azure.', + }, + { + name: ['pipeline'], + description: 'Manage and configure your deployment pipelines.', + subcommands: [ + { + name: ['config'], + description: 'Configure your deployment pipeline to connect securely to Azure. (Beta)', + }, + ], + }, + { + name: ['provision'], + description: 'Provision Azure resources for your project.', + }, + { + name: ['publish'], + description: 'Publish a service to a container registry.', + }, + { + name: ['restore'], + description: 'Restores the project\'s dependencies.', + }, + { + name: ['show'], + description: 'Display information about your project and its resources.', + }, + { + name: ['template'], + description: 'Find and view template details.', + subcommands: [ + { + name: ['list', 'ls'], + description: 'Show list of sample azd templates. (Beta)', + }, + { + name: ['show'], + description: 'Show details for a given template. (Beta)', + }, + { + name: ['source'], + description: 'View and manage template sources. (Beta)', + subcommands: [ + { + name: ['add'], + description: 'Adds an azd template source with the specified key. (Beta)', + }, + { + name: ['list', 'ls'], + description: 'Lists the configured azd template sources. (Beta)', + }, + { + name: ['remove'], + description: 'Removes the specified azd template source (Beta)', + }, + ], + }, + ], + }, + { + name: ['up'], + description: 'Provision and deploy your project to Azure with a single command.', + }, + { + name: ['version'], + description: 'Print the version number of Azure Developer CLI.', + }, + ], + }, + ], + options: [ + { + name: ['--cwd', '-C'], + description: 'Sets the current working directory.', + isPersistent: true, + args: [ + { + name: 'cwd', + }, + ], + }, + { + name: ['--debug'], + description: 'Enables debugging and diagnostics logging.', + isPersistent: true, + }, + { + name: ['--no-prompt'], + description: 'Accepts the default value instead of prompting, or it fails if there is no default.', + isPersistent: true, + }, + { + name: ['--docs'], + description: 'Opens the documentation for azd in your web browser.', + isPersistent: true, + }, + { + name: ['--help', '-h'], + description: 'Gets help for azd.', + isPersistent: true, + }, + ], +}; + +export default completionSpec; diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 45805d9def4..e075bba947d 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,6 +5,7 @@ import { ExecOptionsWithStringEncoding } from 'child_process'; import * as vscode from 'vscode'; +import azdSpec from './completions/azd'; import cdSpec from './completions/cd'; import codeCompletionSpec from './completions/code'; import codeInsidersCompletionSpec from './completions/code-insiders'; @@ -58,6 +59,7 @@ function getCacheKey(machineId: string, remoteAuthority: string | undefined, she } export const availableSpecs: Fig.Spec[] = [ + azdSpec, cdSpec, codeInsidersCompletionSpec, codeCompletionSpec, From 705c8a2977335ce726122be5512e5f24948d004d Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:33:24 -0700 Subject: [PATCH 1351/4355] Never show _getGenerateInstructionsMessage when locked to a coding agent (#272349) do not show _getGenerateInstructionsMessage when locked to a coding agent --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8c5a850b691..803fd905064 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1092,7 +1092,7 @@ export class ChatWidget extends Disposable implements IChatWidget { let welcomeContent: IChatViewWelcomeContent; const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind); let additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage; - if (!additionalMessage) { + if (!additionalMessage && !this._lockedAgent) { additionalMessage = this._getGenerateInstructionsMessage(); } if (this.shouldShowChatSetup()) { From 04da0048f79de7692e66390dae58c4aa392c61bd Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 20 Oct 2025 14:37:49 -0700 Subject: [PATCH 1352/4355] Add Manage Accounts command and UI (#272042) * Add Manage Accounts command and UI Fix for #233881 * Update src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../browser/actions/manageAccountsAction.ts | 140 ++++++++++++++++++ .../browser/authentication.contribution.ts | 2 + 2 files changed, 142 insertions(+) create mode 100644 src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts new file mode 100644 index 00000000000..27a84b0e281 --- /dev/null +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Lazy } from '../../../../../base/common/lazy.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { Action2 } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IProductService } from '../../../../../platform/product/common/productService.js'; +import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; +import { ISecretStorageService } from '../../../../../platform/secrets/common/secrets.js'; +import { getCurrentAuthenticationSessionInfo } from '../../../../services/authentication/browser/authenticationService.js'; +import { IAuthenticationProvider, IAuthenticationService } from '../../../../services/authentication/common/authentication.js'; + +export class ManageAccountsAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.manageAccounts', + title: localize2('manageAccounts', "Manage Accounts"), + category: localize2('accounts', "Accounts"), + f1: true + }); + } + + public override run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + return instantiationService.createInstance(ManageAccountsActionImpl).run(); + } +} + +interface AccountQuickPickItem extends IQuickPickItem { + providerId: string; + canUseMcp: boolean; + canSignOut: () => Promise; +} + +interface AccountActionQuickPickItem extends IQuickPickItem { + action: () => void; +} + +class ManageAccountsActionImpl { + private activeSession = new Lazy(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService)); + + constructor( + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @ICommandService private readonly commandService: ICommandService, + @ISecretStorageService private readonly secretStorageService: ISecretStorageService, + @IProductService private readonly productService: IProductService, + ) { } + + public async run() { + const placeHolder = localize('pickAccount', "Select an account to manage"); + + const accounts = await this.listAccounts(); + if (!accounts.length) { + await this.quickInputService.pick([{ label: localize('noActiveAccounts', "There are no active accounts.") }], { placeHolder }); + return; + } + + const account = await this.quickInputService.pick(accounts, { placeHolder, matchOnDescription: true }); + if (!account) { + return; + } + + await this.showAccountActions(account); + } + + private async listAccounts(): Promise { + const accounts: AccountQuickPickItem[] = []; + for (const providerId of this.authenticationService.getProviderIds()) { + const provider = this.authenticationService.getProvider(providerId); + for (const { label, id } of await this.authenticationService.getAccounts(providerId)) { + accounts.push({ + label, + description: provider.label, + providerId, + canUseMcp: !!provider.authorizationServers?.length, + canSignOut: () => this.canSignOut(provider, id) + }); + } + } + return accounts; + } + + private async canSignOut(provider: IAuthenticationProvider, accountId: string): Promise { + const session = await this.activeSession.value; + if (session && !session.canSignOut && session.providerId === provider.id) { + const sessions = await this.authenticationService.getSessions(provider.id); + return !sessions.some(o => o.id === session.id && o.account.id === accountId); + } + return true; + } + + private async showAccountActions(account: AccountQuickPickItem): Promise { + const { providerId, label: accountLabel, canUseMcp, canSignOut } = account; + + const store = new DisposableStore(); + const quickPick = store.add(this.quickInputService.createQuickPick()); + + quickPick.title = localize('manageAccount', "Manage '{0}'", accountLabel); + quickPick.placeholder = localize('selectAction', "Select an action"); + + const items: AccountActionQuickPickItem[] = [{ + label: localize('manageTrustedExtensions', "Manage Trusted Extensions"), + action: () => this.commandService.executeCommand('_manageTrustedExtensionsForAccount', { providerId, accountLabel }) + }]; + + if (canUseMcp) { + items.push({ + label: localize('manageTrustedMCPServers', "Manage Trusted MCP Servers"), + action: () => this.commandService.executeCommand('_manageTrustedMCPServersForAccount', { providerId, accountLabel }) + }); + } + + if (await canSignOut()) { + items.push({ + label: localize('signOut', "Sign Out"), + action: () => this.commandService.executeCommand('_signOutOfAccount', { providerId, accountLabel }) + }); + } + + quickPick.items = items; + + store.add(quickPick.onDidAccept(() => { + const selected = quickPick.selectedItems[0]; + if (selected) { + quickPick.hide(); + selected.action(); + } + })); + + store.add(quickPick.onDidHide(() => store.dispose())); + + quickPick.show(); + } +} diff --git a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts index 693975bc45a..e13ee49b07a 100644 --- a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts +++ b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts @@ -20,6 +20,7 @@ import { IAuthenticationUsageService } from '../../../services/authentication/br import { ManageAccountPreferencesForMcpServerAction } from './actions/manageAccountPreferencesForMcpServerAction.js'; import { ManageTrustedMcpServersForAccountAction } from './actions/manageTrustedMcpServersForAccountAction.js'; import { RemoveDynamicAuthenticationProvidersAction } from './actions/manageDynamicAuthenticationProvidersAction.js'; +import { ManageAccountsAction } from './actions/manageAccountsAction.js'; import { IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js'; import { IMcpRegistry } from '../../mcp/common/mcpRegistryTypes.js'; import { autorun } from '../../../../base/common/observable.js'; @@ -92,6 +93,7 @@ class AuthenticationContribution extends Disposable implements IWorkbenchContrib } private _registerActions(): void { + this._register(registerAction2(ManageAccountsAction)); this._register(registerAction2(SignOutOfAccountAction)); this._register(registerAction2(ManageTrustedExtensionsForAccountAction)); this._register(registerAction2(ManageAccountPreferencesForExtensionAction)); From 99c299e880a53c1b41afd52c073ed8a93a8aceae Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Oct 2025 14:38:48 -0700 Subject: [PATCH 1353/4355] Generate policy data as JSON (#272018) --- build/.gitignore | 1 + .../steps/product-build-darwin-compile.yml | 2 +- .../steps/product-build-win32-compile.yml | 2 +- build/lib/policies/copyPolicyDto.js | 58 +++ build/lib/policies/copyPolicyDto.ts | 25 + build/lib/policies/policyData.jsonc | 277 +++++++++++ .../policyGenerator.js} | 357 +++++---------- .../policyGenerator.ts} | 432 ++++-------------- build/package.json | 9 +- src/vs/base/common/policy.ts | 59 ++- .../test/common/configurationRegistry.test.ts | 9 +- .../test/common/configurationService.test.ts | 3 + .../test/common/policyConfiguration.test.ts | 15 + src/vs/platform/environment/common/argv.ts | 1 + .../environment/common/environment.ts | 1 + .../environment/common/environmentService.ts | 4 + src/vs/platform/environment/node/argv.ts | 1 + .../common/extensionManagement.ts | 9 +- .../telemetry/common/telemetryService.ts | 29 +- .../common/update.config.contribution.ts | 23 + .../contrib/chat/browser/chat.contribution.ts | 62 ++- .../browser/extensions.contribution.ts | 8 + .../contrib/policyExport/common/policyDto.ts | 33 ++ .../policyExport.contribution.ts | 117 +++++ .../test/node/policyExport.integrationTest.ts | 60 +++ .../terminal/common/terminalConfiguration.ts | 8 + .../test/browser/configurationEditing.test.ts | 3 + .../test/browser/configurationService.test.ts | 5 + .../test/common/accountPolicyService.test.ts | 9 + .../common/multiplexPolicyService.test.ts | 9 + src/vs/workbench/workbench.desktop.main.ts | 3 + 31 files changed, 1042 insertions(+), 592 deletions(-) create mode 100644 build/lib/policies/copyPolicyDto.js create mode 100644 build/lib/policies/copyPolicyDto.ts create mode 100644 build/lib/policies/policyData.jsonc rename build/lib/{policies.js => policies/policyGenerator.js} (70%) rename build/lib/{policies.ts => policies/policyGenerator.ts} (69%) create mode 100644 src/vs/workbench/contrib/policyExport/common/policyDto.ts create mode 100644 src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts create mode 100644 src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts diff --git a/build/.gitignore b/build/.gitignore index 61cf49cb9a1..679674617c7 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1 +1,2 @@ *.js.map +lib/policies/policyDto.* diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index bc8e962f91b..d1d431505f6 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -118,7 +118,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies darwin + - script: node build/lib/policies/policyGenerator build/lib/policies/policyData.jsonc darwin displayName: Generate policy definitions retryCountOnTaskFailure: 3 diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index 072d64ec2d9..bdc807fdae5 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -120,7 +120,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - powershell: node build\lib\policies win32 + - powershell: node build\lib\policies\policyGenerator build\lib\policies\policyData.jsonc win32 displayName: Generate Group Policy definitions retryCountOnTaskFailure: 3 diff --git a/build/lib/policies/copyPolicyDto.js b/build/lib/policies/copyPolicyDto.js new file mode 100644 index 00000000000..9a7d518ced8 --- /dev/null +++ b/build/lib/policies/copyPolicyDto.js @@ -0,0 +1,58 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); +const destFile = path.join(__dirname, 'policyDto.ts'); +try { + // Check if source file exists + if (!fs.existsSync(sourceFile)) { + console.error(`Error: Source file not found: ${sourceFile}`); + console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); + process.exit(1); + } + // Copy the file + fs.copyFileSync(sourceFile, destFile); +} +catch (error) { + console.error(`Error copying policyDto.ts: ${error.message}`); + process.exit(1); +} +//# sourceMappingURL=copyPolicyDto.js.map \ No newline at end of file diff --git a/build/lib/policies/copyPolicyDto.ts b/build/lib/policies/copyPolicyDto.ts new file mode 100644 index 00000000000..a0ec9fc4c1d --- /dev/null +++ b/build/lib/policies/copyPolicyDto.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; + +const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); +const destFile = path.join(__dirname, 'policyDto.ts'); + +try { + // Check if source file exists + if (!fs.existsSync(sourceFile)) { + console.error(`Error: Source file not found: ${sourceFile}`); + console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); + process.exit(1); + } + + // Copy the file + fs.copyFileSync(sourceFile, destFile); +} catch (error) { + console.error(`Error copying policyDto.ts: ${(error as Error).message}`); + process.exit(1); +} diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc new file mode 100644 index 00000000000..096b911e5d3 --- /dev/null +++ b/build/lib/policies/policyData.jsonc @@ -0,0 +1,277 @@ +/** THIS FILE IS AUTOMATICALLY GENERATED USING `code --export-policy-data`. DO NOT MODIFY IT MANUALLY. **/ +{ + "categories": [ + { + "key": "Extensions", + "name": { + "key": "extensionsConfigurationTitle", + "value": "Extensions" + } + }, + { + "key": "IntegratedTerminal", + "name": { + "key": "terminalIntegratedConfigurationTitle", + "value": "Integrated Terminal" + } + }, + { + "key": "InteractiveSession", + "name": { + "key": "interactiveSessionConfigurationTitle", + "value": "Chat" + } + }, + { + "key": "Telemetry", + "name": { + "key": "telemetryConfigurationTitle", + "value": "Telemetry" + } + }, + { + "key": "Update", + "name": { + "key": "updateConfigurationTitle", + "value": "Update" + } + } + ], + "policies": [ + { + "key": "chat.mcp.gallery.serviceUrl", + "name": "McpGalleryServiceUrl", + "category": "InteractiveSession", + "minimumVersion": "1.101", + "localization": { + "description": { + "key": "mcp.gallery.serviceUrl", + "value": "Configure the MCP Gallery service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.gallery.serviceUrl", + "name": "ExtensionGalleryServiceUrl", + "category": "Extensions", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "extensions.gallery.serviceUrl", + "value": "Configure the Marketplace service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.allowed", + "name": "AllowedExtensions", + "category": "Extensions", + "minimumVersion": "1.96", + "localization": { + "description": { + "key": "extensions.allowed.policy", + "value": "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions" + } + }, + "type": "object", + "default": "*" + }, + { + "key": "chat.tools.global.autoApprove", + "name": "ChatToolsAutoApprove", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "autoApprove2.description", + "value": "Global auto approve also known as \"YOLO mode\" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine." + } + }, + "type": "boolean", + "default": false + }, + { + "key": "chat.mcp.access", + "name": "ChatMCP", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.mcp.access", + "value": "Controls access to installed Model Context Protocol servers." + }, + "enumDescriptions": [ + { + "key": "chat.mcp.access.none", + "value": "No access to MCP servers." + }, + { + "key": "chat.mcp.access.registry", + "value": "Allows access to MCP servers installed from the registry that VS Code is connected to." + }, + { + "key": "chat.mcp.access.any", + "value": "Allow access to any installed MCP server." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "none", + "registry", + "all" + ] + }, + { + "key": "chat.extensionTools.enabled", + "name": "ChatAgentExtensionTools", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.extensionToolsEnabled", + "value": "Enable using tools contributed by third-party extensions." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.agent.enabled", + "name": "ChatAgentMode", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.agent.enabled.description", + "value": "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.promptFiles", + "name": "ChatPromptFiles", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.promptFiles.policy", + "value": "Enables reusable prompt and instruction files in Chat sessions." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.tools.terminal.enableAutoApprove", + "name": "ChatToolsTerminalEnableAutoApprove", + "category": "IntegratedTerminal", + "minimumVersion": "1.104", + "localization": { + "description": { + "key": "autoApproveMode.description", + "value": "Controls whether to allow auto approval in the run in terminal tool." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "update.mode", + "name": "UpdateMode", + "category": "Update", + "minimumVersion": "1.67", + "localization": { + "description": { + "key": "updateMode", + "value": "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service." + }, + "enumDescriptions": [ + { + "key": "none", + "value": "Disable updates." + }, + { + "key": "manual", + "value": "Disable automatic background update checks. Updates will be available if you manually check for updates." + }, + { + "key": "start", + "value": "Check for updates only on startup. Disable automatic background update checks." + }, + { + "key": "default", + "value": "Enable automatic update checks. Code will check for updates automatically and periodically." + } + ] + }, + "type": "string", + "default": "default", + "enum": [ + "none", + "manual", + "start", + "default" + ] + }, + { + "key": "telemetry.telemetryLevel", + "name": "TelemetryLevel", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.telemetryLevel.policyDescription", + "value": "Controls the level of telemetry." + }, + "enumDescriptions": [ + { + "key": "telemetry.telemetryLevel.default", + "value": "Sends usage data, errors, and crash reports." + }, + { + "key": "telemetry.telemetryLevel.error", + "value": "Sends general error telemetry and crash reports." + }, + { + "key": "telemetry.telemetryLevel.crash", + "value": "Sends OS level crash reports." + }, + { + "key": "telemetry.telemetryLevel.off", + "value": "Disables all product telemetry." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "all", + "error", + "crash", + "off" + ] + }, + { + "key": "telemetry.feedback.enabled", + "name": "EnableFeedback", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.feedback.enabled", + "value": "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options." + } + }, + "type": "boolean", + "default": true + } + ] +} diff --git a/build/lib/policies.js b/build/lib/policies/policyGenerator.js similarity index 70% rename from build/lib/policies.js rename to build/lib/policies/policyGenerator.js index d2ef760870d..7ddb6956c18 100644 --- a/build/lib/policies.js +++ b/build/lib/policies/policyGenerator.js @@ -1,4 +1,37 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -7,24 +40,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const child_process_1 = require("child_process"); +const minimist_1 = __importDefault(require("minimist")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); -const byline_1 = __importDefault(require("byline")); -const ripgrep_1 = require("@vscode/ripgrep"); -const tree_sitter_1 = __importDefault(require("tree-sitter")); -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); -function isNlsString(value) { - return value ? typeof value !== 'string' : false; -} -function isStringArray(value) { - return !value.some(s => isNlsString(s)); -} -function isNlsStringArray(value) { - return value.every(s => isNlsString(s)); -} +const JSONC = __importStar(require("jsonc-parser")); +const product = require('../../../product.json'); +const packageJson = require('../../../package.json'); var PolicyType; (function (PolicyType) { PolicyType["Boolean"] = "boolean"; @@ -107,12 +128,12 @@ ${this.renderProfileManifestValue(translations)} } } class BooleanPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { name, minimumVersion, localization, type } = policy; if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); @@ -143,23 +164,17 @@ class BooleanPolicy extends BasePolicy { boolean`; } } -class ParseError extends Error { - constructor(message, moduleName, node) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} class NumberPolicy extends BasePolicy { defaultValue; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; if (type !== 'number') { return undefined; } - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); } - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); } constructor(name, category, minimumVersion, description, moduleName, defaultValue) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -191,12 +206,12 @@ class NumberPolicy extends BasePolicy { } } class StringPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; if (type !== 'string') { return undefined; } - return new StringPolicy(name, category, minimumVersion, description, moduleName); + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.String, name, category, minimumVersion, description, moduleName); @@ -224,12 +239,12 @@ class StringPolicy extends BasePolicy { } } class ObjectPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Object, name, category, minimumVersion, description, moduleName); @@ -260,26 +275,20 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; if (type !== 'string') { return undefined; } - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); + const enum_ = enumValue; if (!enum_) { return undefined; } - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); - } - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); } - else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); } constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -322,178 +331,6 @@ class StringEnumPolicy extends BasePolicy { `; } } -const NumberQ = { - Q: `(number) @value`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - return parseInt(value); - } -}; -const StringQ = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - if (nlsKey) { - return { value, nlsKey }; - } - else { - return value; - } - } -}; -const StringArrayQ = { - Q: `(array ${StringQ.Q})`, - value(matches) { - if (matches.length === 0) { - return undefined; - } - return matches.map(match => { - return StringQ.value([match]); - }); - } -}; -function getProperty(qtype, moduleName, node, key) { - const query = new tree_sitter_1.default.Query(typescript, `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )`); - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } - catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} -function getNumberProperty(moduleName, node, key) { - return getProperty(NumberQ, moduleName, node, key); -} -function getStringProperty(moduleName, node, key) { - return getProperty(StringQ, moduleName, node, key); -} -function getStringArrayProperty(moduleName, node, key) { - return getProperty(StringArrayQ, moduleName, node, key); -} -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; -function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { - const name = getStringProperty(moduleName, policyNode, 'name'); - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } - else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } - else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } - else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } - if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - let result; - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - return result; -} -function getPolicies(moduleName, node) { - const query = new tree_sitter_1.default.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - const categories = new Map(); - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} -async function getFiles(root) { - return new Promise((c, e) => { - const result = []; - const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} function renderADMX(regKey, versions, categories, policies) { versions = versions.map(v => v.replace(/\./g, '_')); return ` @@ -757,7 +594,15 @@ async function getSpecificNLS(resourceUrlTemplate, languageId, version) { throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); } const { contents: result } = await res.json(); - return result; + // TODO: support module namespacing + // Flatten all moduleName keys to empty string + const flattened = { '': {} }; + for (const moduleName in result) { + for (const nlsKey in result[moduleName]) { + flattened[''][nlsKey] = result[moduleName][nlsKey]; + } + } + return flattened; } function parseVersion(version) { const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); @@ -801,18 +646,45 @@ async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageI } return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -async function parsePolicies() { - const parser = new tree_sitter_1.default(); - parser.setLanguage(typescript); - const files = await getFiles(process.cwd()); - const base = path_1.default.join(process.cwd(), 'src'); +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; +async function parsePolicies(policyDataFile) { + const contents = JSONC.parse(await fs_1.promises.readFile(policyDataFile, { encoding: 'utf8' })); + const categories = new Map(); + for (const category of contents.categories) { + categories.set(category.key, category); + } const policies = []; - for (const file of files) { - const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); + for (const policy of contents.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + let result; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + policies.push(result); } + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); return policies; } async function getTranslations() { @@ -860,8 +732,14 @@ async function darwinMain(policies, translations) { } } async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; + const args = (0, minimist_1.default)(process.argv.slice(2)); + if (args._.length !== 2) { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } + const policyDataFile = args._[0]; + const platform = args._[1]; + const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); if (platform === 'darwin') { await darwinMain(policies, translations); } @@ -869,19 +747,14 @@ async function main() { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } - else { - console.error(err); - } + console.error(err); process.exit(1); }); } -//# sourceMappingURL=policies.js.map \ No newline at end of file +//# sourceMappingURL=policyGenerator.js.map \ No newline at end of file diff --git a/build/lib/policies.ts b/build/lib/policies/policyGenerator.ts similarity index 69% rename from build/lib/policies.ts rename to build/lib/policies/policyGenerator.ts index 381d2f4c1ac..5fb06942f67 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies/policyGenerator.ts @@ -3,29 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { spawn } from 'child_process'; +import minimist from 'minimist'; import { promises as fs } from 'fs'; import path from 'path'; -import byline from 'byline'; -import { rgPath } from '@vscode/ripgrep'; -import Parser from 'tree-sitter'; -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); +import { CategoryDto, ExportedPolicyDataDto, PolicyDto } from './policyDto'; +import * as JSONC from 'jsonc-parser'; -type NlsString = { value: string; nlsKey: string }; - -function isNlsString(value: string | NlsString | undefined): value is NlsString { - return value ? typeof value !== 'string' : false; -} - -function isStringArray(value: (string | NlsString)[]): value is string[] { - return !value.some(s => isNlsString(s)); -} +const product = require('../../../product.json'); +const packageJson = require('../../../package.json'); -function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { - return value.every(s => isNlsString(s)); -} +type NlsString = { value: string; nlsKey: string }; interface Category { readonly moduleName: string; @@ -146,21 +133,14 @@ ${this.renderProfileManifestValue(translations)} class BooleanPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): BooleanPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { + const { name, minimumVersion, localization, type } = policy; if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -203,35 +183,20 @@ class BooleanPolicy extends BasePolicy { } } -class ParseError extends Error { - constructor(message: string, moduleName: string, node: Parser.SyntaxNode) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} - class NumberPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): NumberPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; if (type !== 'number') { return undefined; } - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); } - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); } private constructor( @@ -276,21 +241,14 @@ class NumberPolicy extends BasePolicy { class StringPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; if (type !== 'string') { return undefined; } - return new StringPolicy(name, category, minimumVersion, description, moduleName); + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -331,21 +289,14 @@ class StringPolicy extends BasePolicy { class ObjectPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): ObjectPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -387,39 +338,32 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringEnumPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; if (type !== 'string') { return undefined; } - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); + const enum_ = enumValue; if (!enum_) { return undefined; } - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); } - - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); - } else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy( + name, + { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, + minimumVersion, + { nlsKey: localization.description.key, value: localization.description.value }, + '', + enum_, + enumDescriptions + ); } private constructor( @@ -475,226 +419,6 @@ class StringEnumPolicy extends BasePolicy { } } -interface QType { - Q: string; - value(matches: Parser.QueryMatch[]): T | undefined; -} - -const NumberQ: QType = { - Q: `(number) @value`, - - value(matches: Parser.QueryMatch[]): number | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - return parseInt(value); - } -}; - -const StringQ: QType = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - - value(matches: Parser.QueryMatch[]): string | NlsString | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - - if (nlsKey) { - return { value, nlsKey }; - } else { - return value; - } - } -}; - -const StringArrayQ: QType<(string | NlsString)[]> = { - Q: `(array ${StringQ.Q})`, - - value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { - if (matches.length === 0) { - return undefined; - } - - return matches.map(match => { - return StringQ.value([match]) as string | NlsString; - }); - } -}; - -function getProperty(qtype: QType, moduleName: string, node: Parser.SyntaxNode, key: string): T | undefined { - const query = new Parser.Query( - typescript, - `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )` - ); - - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} - -function getNumberProperty(moduleName: string, node: Parser.SyntaxNode, key: string): number | undefined { - return getProperty(NumberQ, moduleName, node, key); -} - -function getStringProperty(moduleName: string, node: Parser.SyntaxNode, key: string): string | NlsString | undefined { - return getProperty(StringQ, moduleName, node, key); -} - -function getStringArrayProperty(moduleName: string, node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { - return getProperty(StringArrayQ, moduleName, node, key); -} - -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; - -function getPolicy( - moduleName: string, - configurationNode: Parser.SyntaxNode, - settingNode: Parser.SyntaxNode, - policyNode: Parser.SyntaxNode, - categories: Map -): Policy { - const name = getStringProperty(moduleName, policyNode, 'name'); - - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - - let result: Policy | undefined; - - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - - return result; -} - -function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { - const query = new Parser.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - - const categories = new Map(); - - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} - -async function getFiles(root: string): Promise { - return new Promise((c, e) => { - const result: string[] = []; - const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = byline(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} - function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { versions = versions.map(v => v.replace(/\./g, '_')); @@ -969,7 +693,7 @@ type Translations = { languageId: string; languageTranslations: LanguageTranslat type Version = [number, number, number]; -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -985,7 +709,17 @@ async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, v } const { contents: result } = await res.json() as { contents: LanguageTranslations }; - return result; + + // TODO: support module namespacing + // Flatten all moduleName keys to empty string + const flattened: LanguageTranslations = { '': {} }; + for (const moduleName in result) { + for (const nlsKey in result[moduleName]) { + flattened[''][nlsKey] = result[moduleName][nlsKey]; + } + } + + return flattened; } function parseVersion(version: string): Version { @@ -1034,21 +768,52 @@ async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: s return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -async function parsePolicies(): Promise { - const parser = new Parser(); - parser.setLanguage(typescript); +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; - const files = await getFiles(process.cwd()); - const base = path.join(process.cwd(), 'src'); - const policies = []; +async function parsePolicies(policyDataFile: string): Promise { + const contents = JSONC.parse(await fs.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto; + const categories = new Map(); + for (const category of contents.categories) { + categories.set(category.key, category); + } - for (const file of files) { - const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); + const policies: Policy[] = []; + for (const policy of contents.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + + policies.push(result); } + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + return policies; } @@ -1112,26 +877,29 @@ async function darwinMain(policies: Policy[], translations: Translations) { } async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; + const args = minimist(process.argv.slice(2)); + if (args._.length !== 2) { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } + + const policyDataFile = args._[0]; + const platform = args._[1]; + const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); if (platform === 'darwin') { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } else { - console.error(err); - } + console.error(err); process.exit(1); }); } diff --git a/build/package.json b/build/package.json index fbbf5264776..ef715fbfb6a 100644 --- a/build/package.json +++ b/build/package.json @@ -64,9 +64,12 @@ }, "type": "commonjs", "scripts": { - "compile": "cd .. && npx tsgo --project build/tsconfig.build.json", - "watch": "cd .. && npx tsgo --project build/tsconfig.build.json --watch", - "npmCheckJs": "cd .. && npx tsgo --project build/tsconfig.build.json --noEmit" + "copy-policy-dto": "node lib/policies/copyPolicyDto.js", + "prebuild-ts": "npm run copy-policy-dto", + "build-ts": "cd .. && npx tsgo --project build/tsconfig.build.json", + "compile": "npm run build-ts", + "watch": "npm run build-ts -- --watch", + "npmCheckJs": "npm run build-ts -- --noEmit" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 1e97392d5e2..7fa484a477e 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -3,9 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; export type PolicyName = string; +export type LocalizedValue = { + key: string; + value: string; +}; + +export enum PolicyCategory { + Extensions = 'Extensions', + IntegratedTerminal = 'IntegratedTerminal', + InteractiveSession = 'InteractiveSession', + Telemetry = 'Telemetry', + Update = 'Update', +} + +export const PolicyCategoryData: { + [key in PolicyCategory]: { name: LocalizedValue } +} = { + [PolicyCategory.Extensions]: { + name: { + key: 'extensionsConfigurationTitle', value: localize('extensionsConfigurationTitle', "Extensions"), + } + }, + [PolicyCategory.IntegratedTerminal]: { + name: { + key: 'terminalIntegratedConfigurationTitle', value: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + } + }, + [PolicyCategory.InteractiveSession]: { + name: { + key: 'interactiveSessionConfigurationTitle', value: localize('interactiveSessionConfigurationTitle', "Chat"), + } + }, + [PolicyCategory.Telemetry]: { + name: { + key: 'telemetryConfigurationTitle', value: localize('telemetryConfigurationTitle', "Telemetry"), + } + }, + [PolicyCategory.Update]: { + name: { + key: 'updateConfigurationTitle', value: localize('updateConfigurationTitle', "Update"), + } + } +}; export interface IPolicy { @@ -14,15 +57,27 @@ export interface IPolicy { */ readonly name: PolicyName; + /** + * The policy category. + */ + readonly category: PolicyCategory; + /** * The Code version in which this policy was introduced. */ readonly minimumVersion: `${number}.${number}`; /** - * The policy description (optional). + * Localization info for the policy. + * + * IMPORTANT: the key values for these must be unique to avoid collisions, as during the export time the module information is not available. */ - readonly description?: string; + readonly localization: { + /** The localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's description property. */ + description: LocalizedValue; + /** List of localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's enumDescriptions property. */ + enumDescriptions?: LocalizedValue[]; + }; /** * The value that an ACCOUNT-based feature will use when its corresponding policy is active. diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 4d089e71eb8..043f137b202 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -7,6 +7,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../common/configurationRegistry.js'; import { Registry } from '../../../registry/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationRegistry', () => { @@ -89,14 +90,18 @@ suite('ConfigurationRegistry', () => { 'type': 'object', policy: { name: 'policy', - minimumVersion: '1.0.0' + category: PolicyCategory.Extensions, + minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy2': { 'type': 'object', policy: { name: 'policy', - minimumVersion: '1.0.0' + category: PolicyCategory.Extensions, + minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 4e765d63efe..a258c16a082 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -20,6 +20,7 @@ import { NullLogService } from '../../../log/common/log.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { NullPolicyService } from '../../../policy/common/policy.js'; import { Registry } from '../../../registry/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationService.test.ts', () => { @@ -374,7 +375,9 @@ suite('ConfigurationService.test.ts', () => { 'default': 'isSet', policy: { name: 'configurationService.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index ac30e8b5050..5cc463e4ac0 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -19,6 +19,7 @@ import { IPolicyService } from '../../../policy/common/policy.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('PolicyConfiguration', () => { @@ -39,7 +40,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.settingB': { @@ -47,7 +50,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.objectSetting': { @@ -55,7 +60,9 @@ suite('PolicyConfiguration', () => { 'default': {}, policy: { name: 'PolicyObjectSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.arraySetting': { @@ -63,7 +70,9 @@ suite('PolicyConfiguration', () => { 'default': [], policy: { name: 'PolicyArraySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.booleanSetting': { @@ -71,7 +80,9 @@ suite('PolicyConfiguration', () => { 'default': true, policy: { name: 'PolicyBooleanSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.internalSetting': { @@ -80,7 +91,9 @@ suite('PolicyConfiguration', () => { included: false, policy: { name: 'PolicyInternalSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'nonPolicy.setting': { @@ -267,7 +280,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueC', policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, }, } }; Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index e7b08b8f887..a10f4c9b3bb 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -105,6 +105,7 @@ export interface NativeParsedArgs { 'skip-welcome'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; + 'export-policy-data'?: string; 'install-source'?: string; 'add-mcp'?: string[]; 'disable-updates'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index c5f10d53040..4a14c835bf9 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -145,6 +145,7 @@ export interface INativeEnvironmentService extends IEnvironmentService { useInMemorySecretStorage?: boolean; crossOriginIsolated?: boolean; + exportPolicyData?: string; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 6b6c82c6d5d..535132f43a4 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -254,6 +254,10 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get editSessionId(): string | undefined { return this.args['editSessionId']; } + get exportPolicyData(): string | undefined { + return this.args['export-policy-data']; + } + get continueOn(): string | undefined { return this.args['continueOn']; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 9b0cf59752b..d0ab1d6b4e5 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -162,6 +162,7 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-sharedprocess': { type: 'string', allowEmptyValue: true }, 'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true }, 'export-default-configuration': { type: 'string' }, + 'export-policy-data': { type: 'string', allowEmptyValue: true }, 'install-source': { type: 'string' }, 'enable-smoke-test-driver': { type: 'boolean' }, 'logExtensionHostCommunication': { type: 'boolean' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index cd5c1945c5a..35fe4290937 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,6 +9,7 @@ import { Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IPager } from '../../../base/common/paging.js'; import { Platform } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { URI } from '../../../base/common/uri.js'; import { localize, localize2 } from '../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; @@ -734,8 +735,14 @@ Registry.as(Extensions.Configuration) scope: ConfigurationScope.APPLICATION, policy: { name: 'AllowedExtensions', + category: PolicyCategory.Extensions, minimumVersion: '1.96', - description: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), + localization: { + description: { + key: 'extensions.allowed.policy', + value: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), + } + } }, additionalProperties: false, patternProperties: { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index ef676bdc69b..e26d733e98e 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -6,6 +6,7 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; @@ -223,8 +224,32 @@ configurationRegistry.registerConfiguration({ 'tags': ['usesOnlineServices', 'telemetry'], 'policy': { name: 'TelemetryLevel', + category: PolicyCategory.Telemetry, minimumVersion: '1.99', - description: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), + localization: { + description: { + key: 'telemetry.telemetryLevel.policyDescription', + value: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), + }, + enumDescriptions: [ + { + key: 'telemetry.telemetryLevel.default', + value: localize('telemetry.telemetryLevel.default', "Sends usage data, errors, and crash reports."), + }, + { + key: 'telemetry.telemetryLevel.error', + value: localize('telemetry.telemetryLevel.error', "Sends general error telemetry and crash reports."), + }, + { + key: 'telemetry.telemetryLevel.crash', + value: localize('telemetry.telemetryLevel.crash', "Sends OS level crash reports."), + }, + { + key: 'telemetry.telemetryLevel.off', + value: localize('telemetry.telemetryLevel.off', "Disables all product telemetry."), + } + ] + } } }, 'telemetry.feedback.enabled': { @@ -233,7 +258,9 @@ configurationRegistry.registerConfiguration({ description: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options."), policy: { name: 'EnableFeedback', + category: PolicyCategory.Telemetry, minimumVersion: '1.99', + localization: { description: { key: 'telemetry.feedback.enabled', value: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.") } }, } }, // Deprecated telemetry setting diff --git a/src/vs/platform/update/common/update.config.contribution.ts b/src/vs/platform/update/common/update.config.contribution.ts index d96926b5578..e5fb1abc0b6 100644 --- a/src/vs/platform/update/common/update.config.contribution.ts +++ b/src/vs/platform/update/common/update.config.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWeb, isWindows } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { localize } from '../../../nls.js'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; import { Registry } from '../../registry/common/platform.js'; @@ -30,7 +31,29 @@ configurationRegistry.registerConfiguration({ ], policy: { name: 'UpdateMode', + category: PolicyCategory.Update, minimumVersion: '1.67', + localization: { + description: { key: 'updateMode', value: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), }, + enumDescriptions: [ + { + key: 'none', + value: localize('none', "Disable updates."), + }, + { + key: 'manual', + value: localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), + }, + { + key: 'start', + value: localize('start', "Check for updates only on startup. Disable automatic background update checks."), + }, + { + key: 'default', + value: localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically."), + } + ] + }, } }, 'update.channel': { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c79d3b3fb10..fc1a01be613 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -123,6 +123,7 @@ import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './p import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -240,18 +241,21 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.GlobalAutoApprove]: { default: false, - // HACK: Description duplicated for policy parser. See https://github.com/microsoft/vscode/issues/254526 - description: nls.localize('autoApprove2.description', - 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' - ), markdownDescription: globalAutoApproveDescription.value, type: 'boolean', scope: ConfigurationScope.APPLICATION_MACHINE, tags: ['experimental'], policy: { name: 'ChatToolsAutoApprove', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_preview_features_enabled === false ? false : undefined, + localization: { + description: { + key: 'autoApprove2.description', + value: nls.localize('autoApprove2.description', 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.') + } + }, } }, [ChatConfiguration.AutoApproveEdits]: { @@ -344,6 +348,7 @@ configurationRegistry.registerConfiguration({ default: McpAccessValue.All, policy: { name: 'ChatMCP', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => { if (account.mcp === false) { @@ -354,6 +359,23 @@ configurationRegistry.registerConfiguration({ } return undefined; }, + localization: { + description: { + key: 'chat.mcp.access', + value: nls.localize('chat.mcp.access', "Controls access to installed Model Context Protocol servers.") + }, + enumDescriptions: [ + { + key: 'chat.mcp.access.none', value: nls.localize('chat.mcp.access.none', "No access to MCP servers."), + }, + { + key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), + }, + { + key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") + } + ] + }, } }, [mcpAutoStartConfig]: { @@ -419,8 +441,14 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentExtensionTools', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - description: nls.localize('chat.extensionToolsPolicy', "Enable using tools contributed by third-party extensions."), + localization: { + description: { + key: 'chat.extensionToolsEnabled', + value: nls.localize('chat.extensionToolsEnabled', "Enable using tools contributed by third-party extensions.") + } + }, } }, [ChatConfiguration.AgentEnabled]: { @@ -429,8 +457,15 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentMode', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_agent_enabled === false ? false : undefined, + localization: { + description: { + key: 'chat.agent.enabled.description', + value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), + } + } } }, [ChatConfiguration.EnableMath]: { @@ -472,8 +507,15 @@ configurationRegistry.registerConfiguration({ included: false, policy: { name: 'McpGalleryServiceUrl', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.101', - value: (account) => account.mcpRegistryUrl + value: (account) => account.mcpRegistryUrl, + localization: { + description: { + key: 'mcp.gallery.serviceUrl', + value: nls.localize('mcp.gallery.serviceUrl', "Configure the MCP Gallery service URL to connect to"), + } + } }, }, [PromptsConfig.KEY]: { @@ -495,8 +537,14 @@ configurationRegistry.registerConfiguration({ tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], policy: { name: 'ChatPromptFiles', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - description: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") + localization: { + description: { + key: 'chat.promptFiles.policy', + value: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") + } + } } }, [PromptsConfig.INSTRUCTIONS_LOCATION_KEY]: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b78c1385ac9..012c4d398e5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,6 +13,7 @@ import { mnemonicButtonLabel } from '../../../../base/common/labels.js'; import { Disposable, DisposableStore, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isNative, isWeb } from '../../../../base/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { MultiCommand } from '../../../../editor/browser/editorExtensions.js'; import { CopyAction, CutAction, PasteAction } from '../../../../editor/contrib/clipboard/browser/clipboard.js'; @@ -286,7 +287,14 @@ Registry.as(ConfigurationExtensions.Configuration) included: false, policy: { name: 'ExtensionGalleryServiceUrl', + category: PolicyCategory.Extensions, minimumVersion: '1.99', + localization: { + description: { + key: 'extensions.gallery.serviceUrl', + value: localize('extensions.gallery.serviceUrl', "Configure the Marketplace service URL to connect to"), + } + } }, }, 'extensions.supportNodeGlobalNavigator': { diff --git a/src/vs/workbench/contrib/policyExport/common/policyDto.ts b/src/vs/workbench/contrib/policyExport/common/policyDto.ts new file mode 100644 index 00000000000..0408c74fc6e --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/common/policyDto.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type LocalizedValueDto = { + key: string; + value: string; +}; + +export interface CategoryDto { + key: string; + name: LocalizedValueDto; +} + +export interface PolicyDto { + key: string; + name: string; + category: string; + minimumVersion: `${number}.${number}`; + localization: { + description: LocalizedValueDto; + enumDescriptions?: LocalizedValueDto[]; + }; + type?: string | string[]; + default?: unknown; + enum?: string[]; +} + +export interface ExportedPolicyDataDto { + categories: CategoryDto[]; + policies: PolicyDto[]; +} diff --git a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts new file mode 100644 index 00000000000..51a0f8b42e1 --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { INativeEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { INativeHostService } from '../../../../platform/native/common/native.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { URI } from '../../../../base/common/uri.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { PolicyCategory, PolicyCategoryData } from '../../../../base/common/policy.js'; +import { ExportedPolicyDataDto } from '../common/policyDto.js'; +import { join } from '../../../../base/common/path.js'; + +export class PolicyExportContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.policyExport'; + static readonly DEFAULT_POLICY_EXPORT_PATH = 'build/lib/policies/policyData.jsonc'; + + constructor( + @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, + @IExtensionService private readonly extensionService: IExtensionService, + @IFileService private readonly fileService: IFileService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService, + ) { + super(); + + // Skip for non-development flows + if (this.nativeEnvironmentService.isBuilt) { + return; + } + + const policyDataPath = this.nativeEnvironmentService.exportPolicyData; + if (policyDataPath !== undefined) { + const defaultPath = join(this.nativeEnvironmentService.appRoot, PolicyExportContribution.DEFAULT_POLICY_EXPORT_PATH); + void this.exportPolicyDataAndQuit(policyDataPath ? policyDataPath : defaultPath); + } + } + + private log(msg: string | undefined, ...args: unknown[]) { + this.logService.info(`[${PolicyExportContribution.ID}]`, msg, ...args); + } + + private async exportPolicyDataAndQuit(policyDataPath: string): Promise { + try { + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: `Exporting policy data to ${policyDataPath}` + }, async (_progress) => { + this.log('Export started. Waiting for configurations to load.'); + await this.extensionService.whenInstalledExtensionsRegistered(); + await this.configurationService.whenRemoteConfigurationLoaded(); + + this.log('Extensions and configuration loaded.'); + const configurationRegistry = Registry.as(Extensions.Configuration); + const configurationProperties = { + ...configurationRegistry.getExcludedConfigurationProperties(), + ...configurationRegistry.getConfigurationProperties(), + }; + + const policyData: ExportedPolicyDataDto = { + categories: Object.values(PolicyCategory).map(category => ({ + key: category, + name: PolicyCategoryData[category].name + })), + policies: [] + }; + + for (const [key, schema] of Object.entries(configurationProperties)) { + // Check for the localization property for now to remain backwards compatible. + if (schema.policy?.localization) { + policyData.policies.push({ + key, + name: schema.policy.name, + category: schema.policy.category, + minimumVersion: schema.policy.minimumVersion, + localization: { + description: schema.policy.localization.description, + enumDescriptions: schema.policy.localization.enumDescriptions, + }, + type: schema.type, + default: schema.default, + enum: schema.enum, + }); + } + } + this.log(`Discovered ${policyData.policies.length} policies to export.`); + + const disclaimerComment = `/** THIS FILE IS AUTOMATICALLY GENERATED USING \`code --export-policy-data\`. DO NOT MODIFY IT MANUALLY. **/`; + const policyDataFileContent = `${disclaimerComment}\n${JSON.stringify(policyData, null, 4)}\n`; + await this.fileService.writeFile(URI.file(policyDataPath), VSBuffer.fromString(policyDataFileContent)); + this.log(`Successfully exported ${policyData.policies.length} policies to ${policyDataPath}.`); + }); + + await this.nativeHostService.exit(0); + } catch (error) { + this.log('Failed to export policy', error); + await this.nativeHostService.exit(1); + } + } +} + +registerWorkbenchContribution2( + PolicyExportContribution.ID, + PolicyExportContribution, + WorkbenchPhase.Eventually, +); diff --git a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts new file mode 100644 index 00000000000..725455533c0 --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as cp from 'child_process'; +import { promises as fs } from 'fs'; +import * as os from 'os'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { isWindows } from '../../../../../base/common/platform.js'; +import { join } from '../../../../../base/common/path.js'; +import { FileAccess } from '../../../../../base/common/network.js'; +import * as util from 'util'; + +const exec = util.promisify(cp.exec); + +suite('PolicyExport Integration Tests', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('exported policy data matches checked-in file', async function () { + // This test launches VS Code with --export-policy-data flag, so it takes longer + this.timeout(60000); + + const rootPath = FileAccess.asFileUri('').fsPath.replace(/[\/\\]out[\/\\].*$/, ''); + const checkedInFile = join(rootPath, 'build/lib/policies/policyData.jsonc'); + const tempFile = join(os.tmpdir(), `policyData-test-${Date.now()}.jsonc`); + + try { + // Launch VS Code with --export-policy-data flag + const scriptPath = isWindows + ? join(rootPath, 'scripts', 'code.bat') + : join(rootPath, 'scripts', 'code.sh'); + + await exec(`"${scriptPath}" --export-policy-data="${tempFile}"`, { + cwd: rootPath + }); + + // Read both files + const [exportedContent, checkedInContent] = await Promise.all([ + fs.readFile(tempFile, 'utf-8'), + fs.readFile(checkedInFile, 'utf-8') + ]); + + // Compare contents + assert.strictEqual( + exportedContent, + checkedInContent, + 'Exported policy data should match the checked-in file. If this fails, run: ./scripts/code.sh --export-policy-data' + ); + } finally { + // Clean up temp file + try { + await fs.unlink(tempFile); + } catch { + // Ignore cleanup errors + } + } + }); +}); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ebd5bd55d8d..76f7b87f762 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -7,6 +7,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import type { IStringDictionary } from '../../../../base/common/collections.js'; import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; import { localize } from '../../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import product from '../../../../platform/product/common/product.js'; @@ -645,7 +646,14 @@ export async function registerTerminalConfiguration(getFontSnippets: () => Promi default: true, policy: { name: 'ChatToolsTerminalEnableAutoApprove', + category: PolicyCategory.IntegratedTerminal, minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), + } + } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index fd6e51c680e..6cb93a3632f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -47,6 +47,7 @@ import { UserDataProfileService } from '../../../userDataProfile/common/userData import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js'; import { IBrowserWorkbenchEnvironmentService } from '../../../environment/browser/environmentService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -89,7 +90,9 @@ suite('ConfigurationEditing', () => { 'default': 'isSet', policy: { name: 'configurationEditing.service.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index ebbda208696..ab268311fd1 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -52,6 +52,7 @@ import { IUserDataProfileService } from '../../../userDataProfile/common/userDat import { TasksSchemaProperties } from '../../../../contrib/tasks/common/tasks.js'; import { RemoteSocketFactoryService } from '../../../../../platform/remote/common/remoteSocketFactoryService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -781,7 +782,9 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', policy: { name: 'configurationService.folder.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'configurationService.folder.policyObjectSetting': { @@ -789,7 +792,9 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': {}, policy: { name: 'configurationService.folder.policyObjectSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, } diff --git a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts index 1e7621472f5..795a4c567c8 100644 --- a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts @@ -12,6 +12,7 @@ import { Registry } from '../../../../../platform/registry/common/platform.js'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js'; import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -38,7 +39,9 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -46,7 +49,9 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -55,7 +60,9 @@ suite('AccountPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -64,7 +71,9 @@ suite('AccountPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts index 4484b9d01b2..cee9adcd499 100644 --- a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts @@ -19,6 +19,7 @@ import { InMemoryFileSystemProvider } from '../../../../../platform/files/common import { FileService } from '../../../../../platform/files/common/fileService.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -47,7 +48,9 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -55,7 +58,9 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -64,7 +69,9 @@ suite('MultiplexPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -73,7 +80,9 @@ suite('MultiplexPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index c1d9dc494ce..7a17b276d15 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -182,6 +182,9 @@ import './contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.js // MCP import './contrib/mcp/electron-browser/mcp.contribution.js'; +// Policy Export +import './contrib/policyExport/electron-browser/policyExport.contribution.js'; + //#endregion From 0d1cb5e746510323f785ac40bc91033caacecf4a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:47:18 -0700 Subject: [PATCH 1354/4355] Use `toAction` instead of `new Action` to prevent some leaks --- src/vs/base/common/actions.ts | 5 +++ .../testing/browser/testingExplorerView.ts | 40 ++++++++----------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 0970509e98c..9660e763095 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -52,6 +52,11 @@ export interface IActionChangeEvent { readonly checked?: boolean; } +/** + * A concrete implementation of {@link IAction}. + * + * Note that in most cases you should use the lighter-weight {@linkcode toAction} function instead. + */ export class Action extends Disposable implements IAction { protected _onDidChange = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index e111e2f44c8..ac41fe2000c 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -14,7 +14,7 @@ import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/icon import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { ITreeContextMenuEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from '../../../../base/browser/ui/tree/tree.js'; -import { Action, ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; +import { Action, ActionRunner, IAction, Separator, toAction } from '../../../../base/common/actions.js'; import { mapFindFirst } from '../../../../base/common/arraysFind.js'; import { RunOnceScheduler, disposableTimeout } from '../../../../base/common/async.js'; import { groupBy } from '../../../../base/common/collections.js'; @@ -345,17 +345,15 @@ export class TestingExplorerView extends ViewPane { if (!hasAdded) { hasAdded = true; participatingGroups++; - profileActions.push(new Action(`${controller.id}.$root`, controller.label.get(), undefined, false)); + profileActions.push(toAction({ id: `${controller.id}.$root`, label: controller.label.get(), enabled: false, checked: false, run: () => { } })); } hasConfigurable = hasConfigurable || profile.hasConfigurationHandler; participatingProfiles++; - profileActions.push(new Action( - `${controller.id}.${profile.profileId}`, - defaults.includes(profile) ? localize('defaultTestProfile', '{0} (Default)', profile.label) : profile.label, - undefined, - undefined, - () => { + profileActions.push(toAction({ + id: `${controller.id}.${profile.profileId}`, + label: defaults.includes(profile) ? localize('defaultTestProfile', '{0} (Default)', profile.label) : profile.label, + run: () => { const { include, exclude } = this.getTreeIncludeExclude(profile); this.testService.runResolvedTests({ exclude: exclude.map(e => e.item.extId), @@ -367,7 +365,7 @@ export class TestingExplorerView extends ViewPane { }] }); }, - )); + })); } } @@ -390,23 +388,19 @@ export class TestingExplorerView extends ViewPane { const postActions: IAction[] = []; if (participatingProfiles > 1) { - postActions.push(new Action( - 'selectDefaultTestConfigurations', - localize('selectDefaultConfigs', 'Select Default Profile'), - undefined, - undefined, - () => this.commandService.executeCommand(TestCommandId.SelectDefaultTestProfiles, group), - )); + postActions.push(toAction({ + id: 'selectDefaultTestConfigurations', + label: localize('selectDefaultConfigs', 'Select Default Profile'), + run: () => this.commandService.executeCommand(TestCommandId.SelectDefaultTestProfiles, group), + })); } if (hasConfigurable) { - postActions.push(new Action( - 'configureTestProfiles', - localize('configureTestProfiles', 'Configure Test Profiles'), - undefined, - undefined, - () => this.commandService.executeCommand(TestCommandId.ConfigureTestProfilesAction, group), - )); + postActions.push(toAction({ + id: 'configureTestProfiles', + label: localize('configureTestProfiles', 'Configure Test Profiles'), + run: () => this.commandService.executeCommand(TestCommandId.ConfigureTestProfilesAction, group), + })); } // show menu actions if there are any otherwise don't From 012732b88ab9455dd13f2dd370b20e896420106a Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 13 Oct 2025 12:05:13 -0700 Subject: [PATCH 1355/4355] feat: add description of prompt as placeholder in chat for `/{prompt}` --- .../browser/contrib/chatInputEditorContrib.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index cb6e9f5bad8..50a86c32863 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -22,6 +22,8 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentService } from '../../comm import { chatSlashCommandBackground, chatSlashCommandForeground } from '../../common/chatColors.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader } from '../../common/chatParserTypes.js'; import { ChatRequestParser } from '../../common/chatRequestParser.js'; +import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { IChatWidget } from '../chat.js'; import { ChatWidget } from '../chatWidget.js'; import { dynamicVariableDecorationType } from './chatDynamicVariables.js'; @@ -40,6 +42,8 @@ class InputEditorDecorations extends Disposable { public readonly id = 'inputEditorDecorations'; private readonly previouslyUsedAgents = new Set(); + private readonly promptDescriptions = new Map(); + private readonly pendingPromptResolutions = new Set(); private readonly viewModelDisposables = this._register(new MutableDisposable()); @@ -50,6 +54,7 @@ class InputEditorDecorations extends Disposable { @IChatAgentService private readonly chatAgentService: IChatAgentService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService private readonly labelService: ILabelService, + @IPromptsService private readonly promptsService: IPromptsService, ) { super(); @@ -69,6 +74,11 @@ class InputEditorDecorations extends Disposable { this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent, e.slashCommand?.name)); })); this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); + this._register(this.promptsService.onDidChangeCustomChatModes(() => { + // Clear cached descriptions when prompts change + this.promptDescriptions.clear(); + this.updateInputEditorDecorations(); + })); this._register(autorun(reader => { // Watch for changes to the current mode and its properties const currentMode = this.widget.input.currentModeObs.read(reader); @@ -123,6 +133,42 @@ class InputEditorDecorations extends Disposable { return transparentForeground?.toString(); } + private async resolvePromptDescription(slashPromptPart: ChatRequestSlashPromptPart): Promise { + const command = slashPromptPart.slashPromptCommand.command; + + // Check cache first + if (this.promptDescriptions.has(command)) { + return this.promptDescriptions.get(command); + } + + // Avoid duplicate resolutions + if (this.pendingPromptResolutions.has(command)) { + return undefined; + } + + try { + this.pendingPromptResolutions.add(command); + const promptFile = await this.promptsService.resolvePromptSlashCommand(slashPromptPart.slashPromptCommand, CancellationToken.None); + const description = promptFile?.header?.description; + + // Cache the result (even if undefined) + this.promptDescriptions.set(command, description); + + // Update decorations if we got a description + if (description) { + this.updateInputEditorDecorations(); + } + + return description; + } catch (error) { + // Cache the failure + this.promptDescriptions.set(command, undefined); + return undefined; + } finally { + this.pendingPromptResolutions.delete(command); + } + } + private async updateInputEditorDecorations() { const inputValue = this.widget.inputEditor.getValue(); @@ -235,6 +281,26 @@ class InputEditorDecorations extends Disposable { } } + const onlyPromptCommandAndWhitespace = slashPromptPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashPromptPart); + if (onlyPromptCommandAndWhitespace && exactlyOneSpaceAfterPart(slashPromptPart)) { + // Prompt slash command with no other text - show the placeholder + const cachedDescription = this.promptDescriptions.get(slashPromptPart.slashPromptCommand.command); + if (cachedDescription) { + placeholderDecoration = [{ + range: getRangeForPlaceholder(slashPromptPart), + renderOptions: { + after: { + contentText: cachedDescription, + color: this.getPlaceholderColor(), + } + } + }]; + } else if (!this.pendingPromptResolutions.has(slashPromptPart.slashPromptCommand.command)) { + // Try to resolve the description asynchronously + this.resolvePromptDescription(slashPromptPart); + } + } + this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); const textDecorations: IDecorationOptions[] | undefined = []; From f356ae8b949ae48f9a2865943ac6f4dfee0586d6 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 14 Oct 2025 11:40:02 -0700 Subject: [PATCH 1356/4355] move cache to promptService --- .../browser/contrib/chatInputEditorContrib.ts | 77 +++++---------- .../service/promptsServiceImpl.ts | 99 ++++++++++++++++++- 2 files changed, 121 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 50a86c32863..5ff2aeda181 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -37,13 +37,15 @@ function agentAndCommandToKey(agent: IChatAgentData, subcommand: string | undefi return subcommand ? `${agent.id}__${subcommand}` : agent.id; } +function isWhitespaceOrPromptPart(p: IParsedChatRequestPart): boolean { + return (p instanceof ChatRequestTextPart && !p.text.trim().length) || (p instanceof ChatRequestSlashPromptPart); +} + class InputEditorDecorations extends Disposable { public readonly id = 'inputEditorDecorations'; private readonly previouslyUsedAgents = new Set(); - private readonly promptDescriptions = new Map(); - private readonly pendingPromptResolutions = new Set(); private readonly viewModelDisposables = this._register(new MutableDisposable()); @@ -75,8 +77,6 @@ class InputEditorDecorations extends Disposable { })); this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); this._register(this.promptsService.onDidChangeCustomChatModes(() => { - // Clear cached descriptions when prompts change - this.promptDescriptions.clear(); this.updateInputEditorDecorations(); })); this._register(autorun(reader => { @@ -133,41 +133,7 @@ class InputEditorDecorations extends Disposable { return transparentForeground?.toString(); } - private async resolvePromptDescription(slashPromptPart: ChatRequestSlashPromptPart): Promise { - const command = slashPromptPart.slashPromptCommand.command; - - // Check cache first - if (this.promptDescriptions.has(command)) { - return this.promptDescriptions.get(command); - } - - // Avoid duplicate resolutions - if (this.pendingPromptResolutions.has(command)) { - return undefined; - } - try { - this.pendingPromptResolutions.add(command); - const promptFile = await this.promptsService.resolvePromptSlashCommand(slashPromptPart.slashPromptCommand, CancellationToken.None); - const description = promptFile?.header?.description; - - // Cache the result (even if undefined) - this.promptDescriptions.set(command, description); - - // Update decorations if we got a description - if (description) { - this.updateInputEditorDecorations(); - } - - return description; - } catch (error) { - // Cache the failure - this.promptDescriptions.set(command, undefined); - return undefined; - } finally { - this.pendingPromptResolutions.delete(command); - } - } private async updateInputEditorDecorations() { const inputValue = this.widget.inputEditor.getValue(); @@ -281,24 +247,29 @@ class InputEditorDecorations extends Disposable { } } - const onlyPromptCommandAndWhitespace = slashPromptPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashPromptPart); + const onlyPromptCommandAndWhitespace = slashPromptPart && parsedRequest.every(isWhitespaceOrPromptPart); if (onlyPromptCommandAndWhitespace && exactlyOneSpaceAfterPart(slashPromptPart)) { // Prompt slash command with no other text - show the placeholder - const cachedDescription = this.promptDescriptions.get(slashPromptPart.slashPromptCommand.command); - if (cachedDescription) { - placeholderDecoration = [{ - range: getRangeForPlaceholder(slashPromptPart), - renderOptions: { - after: { - contentText: cachedDescription, - color: this.getPlaceholderColor(), + // Resolve the prompt file (this will use cache if available) + this.promptsService.resolvePromptSlashCommand(slashPromptPart.slashPromptCommand, CancellationToken.None).then(promptFile => { + const description = promptFile?.header?.description; + if (description) { + // Create placeholder decoration + const decoration: IDecorationOptions[] = [{ + range: getRangeForPlaceholder(slashPromptPart), + renderOptions: { + after: { + contentText: description, + color: this.getPlaceholderColor(), + } } - } - }]; - } else if (!this.pendingPromptResolutions.has(slashPromptPart.slashPromptCommand.command)) { - // Try to resolve the description asynchronously - this.resolvePromptDescription(slashPromptPart); - } + }]; + // Update the specific placeholder decoration + this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, decoration); + } + }).catch(() => { + // Ignore errors, just don't show description + }); } this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 3f9b9337d46..0c3c91e06d3 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -52,6 +52,16 @@ export class PromptsService extends Disposable implements IPromptsService { private parsedPromptFileCache = new ResourceMap<[number, ParsedPromptFile]>(); + /** + * Cache for parsed prompt files keyed by command name. + */ + private promptFileByCommandCache = new Map(); + + /** + * Set to track pending prompt file resolutions to avoid duplicates. + */ + private pendingPromptFileResolutions = new Set(); + /** * Contributed files from extensions keyed by prompt type then name. */ @@ -83,6 +93,16 @@ export class PromptsService extends Disposable implements IPromptsService { this._register(this.modelService.onModelRemoved((model) => { this.parsedPromptFileCache.delete(model.uri); + // Clear prompt file cache when prompt files are removed + if (this.getPromptFileType(model.uri) === PromptsType.prompt) { + this.promptFileByCommandCache.clear(); + } + })); + + // Track prompt file updates (adds / removes / content changes) to invalidate command-based cache + const promptFileTracker = this._register(new PromptFileUpdateTracker(this.fileLocator, this.modelService)); + this._register(promptFileTracker.onDidChangeContent(() => { + this.promptFileByCommandCache.clear(); })); } @@ -179,19 +199,43 @@ export class PromptsService extends Disposable implements IPromptsService { } public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { + const command = data.command; + + // Check cache first + if (this.promptFileByCommandCache.has(command)) { + return this.promptFileByCommandCache.get(command); + } + + // Avoid duplicate resolutions + if (this.pendingPromptFileResolutions.has(command)) { + return undefined; + } + const promptUri = await this.getPromptPath(data); if (!promptUri) { return undefined; } + try { - return await this.parseNew(promptUri, token); + this.pendingPromptFileResolutions.add(command); + const parsedFile = await this.parseNew(promptUri, token); + + // Cache the result (even if undefined) + this.promptFileByCommandCache.set(command, parsedFile); + + return parsedFile; } catch (error) { this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri}`, error); + // Cache the failure + this.promptFileByCommandCache.set(command, undefined); return undefined; + } finally { + this.pendingPromptFileResolutions.delete(command); } - } + + private async getPromptPath(data: IChatPromptSlashCommand): Promise { if (data.promptPath) { return data.promptPath.uri; @@ -408,3 +452,54 @@ export class ChatModeUpdateTracker extends Disposable { } } + +export class PromptFileUpdateTracker extends Disposable { + + private static readonly PROMPT_FILE_UPDATE_DELAY_MS = 200; + + private readonly listeners = new ResourceMap(); + private readonly onDidPromptFileChange: Emitter; + + public get onDidChangeContent(): Event { + return this.onDidPromptFileChange.event; + } + + constructor( + fileLocator: PromptFilesLocator, + @IModelService modelService: IModelService, + ) { + super(); + this.onDidPromptFileChange = this._register(new Emitter()); + const delayer = this._register(new Delayer(PromptFileUpdateTracker.PROMPT_FILE_UPDATE_DELAY_MS)); + const trigger = () => delayer.trigger(() => this.onDidPromptFileChange.fire()); + + // File add/remove/rename events for prompt files + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.prompt)); + this._register(filesUpdatedEventRegistration.event(() => trigger())); + + const onAdd = (model: ITextModel) => { + if (model.getLanguageId() === PROMPT_LANGUAGE_ID) { + this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); + } + }; + const onRemove = (languageId: string, uri: URI) => { + if (languageId === PROMPT_LANGUAGE_ID) { + this.listeners.get(uri)?.dispose(); + this.listeners.delete(uri); + trigger(); + } + }; + this._register(modelService.onModelAdded(model => onAdd(model))); + this._register(modelService.onModelLanguageChanged(e => { + onRemove(e.oldLanguageId, e.model.uri); + onAdd(e.model); + })); + this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); + } + + public override dispose(): void { + super.dispose(); + this.listeners.forEach(listener => listener.dispose()); + this.listeners.clear(); + } +} From bdeb3d8ac6ab9610b0c586887a03b759332bce87 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 17 Oct 2025 16:36:45 -0700 Subject: [PATCH 1357/4355] Add resolvePromptSlashCommandFromCache and onDidChangeParsedPromptFilesCache in IPromptsService --- .../browser/contrib/chatInputEditorContrib.ts | 36 ++--- .../promptSyntax/service/promptsService.ts | 11 ++ .../service/promptsServiceImpl.ts | 143 +++++++----------- .../chat/test/common/mockPromptsService.ts | 6 +- 4 files changed, 82 insertions(+), 114 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 5ff2aeda181..2ace12b48fb 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -23,7 +23,6 @@ import { chatSlashCommandBackground, chatSlashCommandForeground } from '../../co import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader } from '../../common/chatParserTypes.js'; import { ChatRequestParser } from '../../common/chatRequestParser.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; -import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { IChatWidget } from '../chat.js'; import { ChatWidget } from '../chatWidget.js'; import { dynamicVariableDecorationType } from './chatDynamicVariables.js'; @@ -76,9 +75,7 @@ class InputEditorDecorations extends Disposable { this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent, e.slashCommand?.name)); })); this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); - this._register(this.promptsService.onDidChangeCustomChatModes(() => { - this.updateInputEditorDecorations(); - })); + this._register(this.promptsService.onDidChangeParsedPromptFilesCache(() => this.updateInputEditorDecorations())); this._register(autorun(reader => { // Watch for changes to the current mode and its properties const currentMode = this.widget.input.currentModeObs.read(reader); @@ -251,25 +248,20 @@ class InputEditorDecorations extends Disposable { if (onlyPromptCommandAndWhitespace && exactlyOneSpaceAfterPart(slashPromptPart)) { // Prompt slash command with no other text - show the placeholder // Resolve the prompt file (this will use cache if available) - this.promptsService.resolvePromptSlashCommand(slashPromptPart.slashPromptCommand, CancellationToken.None).then(promptFile => { - const description = promptFile?.header?.description; - if (description) { - // Create placeholder decoration - const decoration: IDecorationOptions[] = [{ - range: getRangeForPlaceholder(slashPromptPart), - renderOptions: { - after: { - contentText: description, - color: this.getPlaceholderColor(), - } + const promptFile = this.promptsService.resolvePromptSlashCommandFromCache(slashPromptPart.slashPromptCommand); + + const description = promptFile?.header?.description; + if (description) { + placeholderDecoration = [{ + range: getRangeForPlaceholder(slashPromptPart), + renderOptions: { + after: { + contentText: description, + color: this.getPlaceholderColor(), } - }]; - // Update the specific placeholder decoration - this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, decoration); - } - }).catch(() => { - // Ignore errors, just don't show description - }); + } + }]; + } } this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 43d12c89214..34cdf2ab6a9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -192,6 +192,17 @@ export interface IPromptsService extends IDisposable { */ resolvePromptSlashCommand(data: IChatPromptSlashCommand, _token: CancellationToken): Promise; + /** + * Gets the prompt file for a slash command from cache if available. + */ + resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined; + + /** + * Event that is triggered when slash command -> ParsedPromptFile cache is updated. + * Event handler can call resolvePromptSlashCommandFromCache in case there is new value populated. + */ + readonly onDidChangeParsedPromptFilesCache: Event; + /** * Returns a prompt command if the command name is valid. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 0c3c91e06d3..edded523ac5 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -55,12 +55,9 @@ export class PromptsService extends Disposable implements IPromptsService { /** * Cache for parsed prompt files keyed by command name. */ - private promptFileByCommandCache = new Map(); + private promptFileByCommandCache = new Map | undefined }>(); - /** - * Set to track pending prompt file resolutions to avoid duplicates. - */ - private pendingPromptFileResolutions = new Set(); + private onDidChangeParsedPromptFilesCacheEmitter = new Emitter(); /** * Contributed files from extensions keyed by prompt type then name. @@ -89,19 +86,14 @@ export class PromptsService extends Disposable implements IPromptsService { ) { super(); + this.onDidChangeParsedPromptFilesCacheEmitter = this._register(new Emitter()); + this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); this._register(this.modelService.onModelRemoved((model) => { this.parsedPromptFileCache.delete(model.uri); - // Clear prompt file cache when prompt files are removed - if (this.getPromptFileType(model.uri) === PromptsType.prompt) { - this.promptFileByCommandCache.clear(); - } - })); - // Track prompt file updates (adds / removes / content changes) to invalidate command-based cache - const promptFileTracker = this._register(new PromptFileUpdateTracker(this.fileLocator, this.modelService)); - this._register(promptFileTracker.onDidChangeContent(() => { + // clear prompt file command cache this.promptFileByCommandCache.clear(); })); } @@ -118,16 +110,20 @@ export class PromptsService extends Disposable implements IPromptsService { emitter.fire(); })); } + return this.onDidChangeCustomChatModesEmitter.event; } + public get onDidChangeParsedPromptFilesCache(): Event { + return this.onDidChangeParsedPromptFilesCacheEmitter.event; + } + public getPromptFileType(uri: URI): PromptsType | undefined { const model = this.modelService.getModel(uri); const languageId = model ? model.getLanguageId() : this.languageService.guessLanguageIdByFilepathOrFirstLine(uri); return languageId ? getPromptsTypeForLanguageId(languageId) : undefined; } - public getParsedPromptFile(textModel: ITextModel): ParsedPromptFile { const cached = this.parsedPromptFileCache.get(textModel.uri); if (cached && cached[0] === textModel.getVersionId()) { @@ -198,43 +194,62 @@ export class PromptsService extends Disposable implements IPromptsService { return undefined; } - public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { + private async resolvePromptSlashCommandInternal(data: IChatPromptSlashCommand, token: CancellationToken) { const command = data.command; + let promptUri: URI | undefined; + try { + promptUri = await this.getPromptPath(data); + if (!promptUri) { + return undefined; + } - // Check cache first - if (this.promptFileByCommandCache.has(command)) { - return this.promptFileByCommandCache.get(command); - } + if (token.isCancellationRequested) { + throw new CancellationError(); + } - // Avoid duplicate resolutions - if (this.pendingPromptFileResolutions.has(command)) { + const parsedFile = await this.parseNew(promptUri, token); + return parsedFile; + } catch (error) { + if (error instanceof CancellationError) { + throw error; + } + this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri ?? command}`, error); return undefined; } + } - const promptUri = await this.getPromptPath(data); - if (!promptUri) { - return undefined; + public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { + const command = data.command; + + let cache = this.promptFileByCommandCache.get(command); + if (cache && cache.pendingPromise) { + return cache.pendingPromise; } - try { - this.pendingPromptFileResolutions.add(command); - const parsedFile = await this.parseNew(promptUri, token); + const newPromise = this.resolvePromptSlashCommandInternal(data, token); + if (cache) { + cache.pendingPromise = newPromise; + } + else { + cache = { value: undefined, pendingPromise: newPromise }; + this.promptFileByCommandCache.set(command, cache); + } - // Cache the result (even if undefined) - this.promptFileByCommandCache.set(command, parsedFile); + const newValue = await newPromise.finally(() => cache.pendingPromise = undefined); - return parsedFile; - } catch (error) { - this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri}`, error); - // Cache the failure - this.promptFileByCommandCache.set(command, undefined); - return undefined; - } finally { - this.pendingPromptFileResolutions.delete(command); - } + // TODO: consider comparing the newValue and the old and only emit change event when there are value changes + cache.value = newValue; + this.onDidChangeParsedPromptFilesCacheEmitter.fire(); + + return newValue; } + public resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined { + // kick off a async process to refresh the cache while we returns the current cached value + void this.resolvePromptSlashCommand(data, CancellationToken.None).catch((error) => { }); + return this.promptFileByCommandCache.get(data.command)?.value; + } private async getPromptPath(data: IChatPromptSlashCommand): Promise { if (data.promptPath) { @@ -445,58 +460,6 @@ export class ChatModeUpdateTracker extends Disposable { this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); } - public override dispose(): void { - super.dispose(); - this.listeners.forEach(listener => listener.dispose()); - this.listeners.clear(); - } - -} - -export class PromptFileUpdateTracker extends Disposable { - - private static readonly PROMPT_FILE_UPDATE_DELAY_MS = 200; - - private readonly listeners = new ResourceMap(); - private readonly onDidPromptFileChange: Emitter; - - public get onDidChangeContent(): Event { - return this.onDidPromptFileChange.event; - } - - constructor( - fileLocator: PromptFilesLocator, - @IModelService modelService: IModelService, - ) { - super(); - this.onDidPromptFileChange = this._register(new Emitter()); - const delayer = this._register(new Delayer(PromptFileUpdateTracker.PROMPT_FILE_UPDATE_DELAY_MS)); - const trigger = () => delayer.trigger(() => this.onDidPromptFileChange.fire()); - - // File add/remove/rename events for prompt files - const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.prompt)); - this._register(filesUpdatedEventRegistration.event(() => trigger())); - - const onAdd = (model: ITextModel) => { - if (model.getLanguageId() === PROMPT_LANGUAGE_ID) { - this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); - } - }; - const onRemove = (languageId: string, uri: URI) => { - if (languageId === PROMPT_LANGUAGE_ID) { - this.listeners.get(uri)?.dispose(); - this.listeners.delete(uri); - trigger(); - } - }; - this._register(modelService.onModelAdded(model => onAdd(model))); - this._register(modelService.onModelLanguageChanged(e => { - onRemove(e.oldLanguageId, e.model.uri); - onAdd(e.model); - })); - this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); - } - public override dispose(): void { super.dispose(); this.listeners.forEach(listener => listener.dispose()); diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index a1b522e7ccd..edeb79b0345 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../../base/common/cancellation.js'; -import { Emitter } from '../../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { URI } from '../../../../../base/common/uri.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { ParsedPromptFile } from '../../common/promptSyntax/service/newPromptsParser.js'; -import { ICustomChatMode, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; +import { IChatPromptSlashCommand, ICustomChatMode, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; export class MockPromptsService implements IPromptsService { _serviceBrand: undefined; @@ -37,6 +37,8 @@ export class MockPromptsService implements IPromptsService { getSourceFolders(_type: any): readonly any[] { throw new Error('Not implemented'); } asPromptSlashCommand(_command: string): any { return undefined; } resolvePromptSlashCommand(_data: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } + resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined { throw new Error('Not implemented'); } + get onDidChangeParsedPromptFilesCache(): Event { throw new Error('Not implemented'); } findPromptSlashCommands(): Promise { throw new Error('Not implemented'); } getPromptCommandName(uri: URI): Promise { throw new Error('Not implemented'); } parse(_uri: URI, _type: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } From 38301e29f10a6c03ee31b43c592a00c790a145de Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 17 Oct 2025 16:49:49 -0700 Subject: [PATCH 1358/4355] only kick off prompt command resolution when cache is empty. --- .../common/promptSyntax/service/promptsServiceImpl.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index edded523ac5..1e4791a5b87 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -245,10 +245,14 @@ export class PromptsService extends Disposable implements IPromptsService { } public resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined { - // kick off a async process to refresh the cache while we returns the current cached value - void this.resolvePromptSlashCommand(data, CancellationToken.None).catch((error) => { }); + const cache = this.promptFileByCommandCache.get(data.command); + const value = cache?.value; + if (value === undefined) { + // kick off a async process to refresh the cache while we returns the current cached value + void this.resolvePromptSlashCommand(data, CancellationToken.None).catch((error) => { }); + } - return this.promptFileByCommandCache.get(data.command)?.value; + return value; } private async getPromptPath(data: IChatPromptSlashCommand): Promise { From 60d6529c6427e4f530c42459f89c6d1b86c4d148 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 20 Oct 2025 12:04:49 -0700 Subject: [PATCH 1359/4355] prompts: use command string for cache lookup and add prompt update tracker --- .../browser/contrib/chatInputEditorContrib.ts | 2 +- .../promptSyntax/service/promptsService.ts | 2 +- .../service/promptsServiceImpl.ts | 126 +++++++++++++----- .../chat/test/common/mockPromptsService.ts | 4 +- 4 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 2ace12b48fb..f6d9ee5bb77 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -248,7 +248,7 @@ class InputEditorDecorations extends Disposable { if (onlyPromptCommandAndWhitespace && exactlyOneSpaceAfterPart(slashPromptPart)) { // Prompt slash command with no other text - show the placeholder // Resolve the prompt file (this will use cache if available) - const promptFile = this.promptsService.resolvePromptSlashCommandFromCache(slashPromptPart.slashPromptCommand); + const promptFile = this.promptsService.resolvePromptSlashCommandFromCache(slashPromptPart.slashPromptCommand.command); const description = promptFile?.header?.description; if (description) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 34cdf2ab6a9..fed1bfd5cc9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -195,7 +195,7 @@ export interface IPromptsService extends IDisposable { /** * Gets the prompt file for a slash command from cache if available. */ - resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined; + resolvePromptSlashCommandFromCache(command: string): ParsedPromptFile | undefined; /** * Event that is triggered when slash command -> ParsedPromptFile cache is updated. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 1e4791a5b87..53d843f25d8 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -90,11 +90,29 @@ export class PromptsService extends Disposable implements IPromptsService { this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); + const promptUpdateTracker = this._register(new PromptUpdateTracker(this.fileLocator, this.modelService)); + this._register(promptUpdateTracker.onDiDPromptChange((event) => { + if (event.kind === 'fileSystem') { + this.promptFileByCommandCache.clear(); + } + else { + const pendingClear = []; + for (const [key, value] of this.promptFileByCommandCache) { + if (value.value?.uri === event.uri) { + pendingClear.push(key); + } + } + + for (const key of pendingClear) { + this.promptFileByCommandCache.delete(key); + } + } + + this.onDidChangeParsedPromptFilesCacheEmitter.fire(); + })); + this._register(this.modelService.onModelRemoved((model) => { this.parsedPromptFileCache.delete(model.uri); - - // clear prompt file command cache - this.promptFileByCommandCache.clear(); })); } @@ -194,39 +212,27 @@ export class PromptsService extends Disposable implements IPromptsService { return undefined; } - private async resolvePromptSlashCommandInternal(data: IChatPromptSlashCommand, token: CancellationToken) { - const command = data.command; - let promptUri: URI | undefined; - try { - promptUri = await this.getPromptPath(data); - if (!promptUri) { - return undefined; - } - - if (token.isCancellationRequested) { - throw new CancellationError(); - } + public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { + const promptUri = data.promptPath?.uri ?? await this.getPromptPath(data.command); + if (!promptUri) { + return undefined; + } - const parsedFile = await this.parseNew(promptUri, token); - return parsedFile; + try { + return await this.parseNew(promptUri, token); } catch (error) { - if (error instanceof CancellationError) { - throw error; - } - this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri ?? command}`, error); + this.logger.error(`[resolvePromptSlashCommand] Failed to parse prompt file: ${promptUri}`, error); return undefined; } } - public async resolvePromptSlashCommand(data: IChatPromptSlashCommand, token: CancellationToken): Promise { - const command = data.command; - + private async populatePromptCommandCache(command: string): Promise { let cache = this.promptFileByCommandCache.get(command); if (cache && cache.pendingPromise) { return cache.pendingPromise; } - const newPromise = this.resolvePromptSlashCommandInternal(data, token); + const newPromise = this.resolvePromptSlashCommand({ command, detail: '' }, CancellationToken.None); if (cache) { cache.pendingPromise = newPromise; } @@ -244,24 +250,19 @@ export class PromptsService extends Disposable implements IPromptsService { return newValue; } - public resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined { - const cache = this.promptFileByCommandCache.get(data.command); + public resolvePromptSlashCommandFromCache(command: string): ParsedPromptFile | undefined { + const cache = this.promptFileByCommandCache.get(command); const value = cache?.value; if (value === undefined) { // kick off a async process to refresh the cache while we returns the current cached value - void this.resolvePromptSlashCommand(data, CancellationToken.None).catch((error) => { }); + void this.populatePromptCommandCache(command).catch((error) => { }); } return value; } - private async getPromptPath(data: IChatPromptSlashCommand): Promise { - if (data.promptPath) { - return data.promptPath.uri; - } - + private async getPromptPath(command: string): Promise { const promptPaths = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - const command = data.command; const result = promptPaths.find(promptPath => getCommandNameFromPromptPath(promptPath) === command); if (result) { return result.uri; @@ -470,3 +471,60 @@ export class ChatModeUpdateTracker extends Disposable { this.listeners.clear(); } } + +export type PromptUpdateKind = 'fileSystem' | 'textModel'; + +export interface IPromptUpdateEvent { + kind: PromptUpdateKind; + uri?: URI; +} + +export class PromptUpdateTracker extends Disposable { + + private static readonly PROMPT_UPDATE_DELAY_MS = 200; + + private readonly listeners = new ResourceMap(); + private readonly onDidPromptModelChange: Emitter; + + public get onDiDPromptChange(): Event { + return this.onDidPromptModelChange.event; + } + + constructor( + fileLocator: PromptFilesLocator, + @IModelService modelService: IModelService, + ) { + super(); + this.onDidPromptModelChange = this._register(new Emitter()); + const delayer = this._register(new Delayer(PromptUpdateTracker.PROMPT_UPDATE_DELAY_MS)); + const trigger = (event: IPromptUpdateEvent) => delayer.trigger(() => this.onDidPromptModelChange.fire(event)); + + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.prompt)); + this._register(filesUpdatedEventRegistration.event(() => trigger({ kind: 'fileSystem' }))); + + const onAdd = (model: ITextModel) => { + if (model.getLanguageId() === PROMPT_LANGUAGE_ID) { + this.listeners.set(model.uri, model.onDidChangeContent(() => trigger({ kind: 'textModel', uri: model.uri }))); + } + }; + const onRemove = (languageId: string, uri: URI) => { + if (languageId === PROMPT_LANGUAGE_ID) { + this.listeners.get(uri)?.dispose(); + this.listeners.delete(uri); + trigger({ kind: 'textModel', uri }); + } + }; + this._register(modelService.onModelAdded(model => onAdd(model))); + this._register(modelService.onModelLanguageChanged(e => { + onRemove(e.oldLanguageId, e.model.uri); + onAdd(e.model); + })); + this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); + } + + public override dispose(): void { + super.dispose(); + this.listeners.forEach(listener => listener.dispose()); + this.listeners.clear(); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index edeb79b0345..3c43c049731 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -11,7 +11,7 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { ParsedPromptFile } from '../../common/promptSyntax/service/newPromptsParser.js'; -import { IChatPromptSlashCommand, ICustomChatMode, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; +import { ICustomChatMode, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; export class MockPromptsService implements IPromptsService { _serviceBrand: undefined; @@ -37,7 +37,7 @@ export class MockPromptsService implements IPromptsService { getSourceFolders(_type: any): readonly any[] { throw new Error('Not implemented'); } asPromptSlashCommand(_command: string): any { return undefined; } resolvePromptSlashCommand(_data: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } - resolvePromptSlashCommandFromCache(data: IChatPromptSlashCommand): ParsedPromptFile | undefined { throw new Error('Not implemented'); } + resolvePromptSlashCommandFromCache(command: string): ParsedPromptFile | undefined { throw new Error('Not implemented'); } get onDidChangeParsedPromptFilesCache(): Event { throw new Error('Not implemented'); } findPromptSlashCommands(): Promise { throw new Error('Not implemented'); } getPromptCommandName(uri: URI): Promise { throw new Error('Not implemented'); } From 384684e49e31f2df9aed20684c12af84c24cd5bb Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 20 Oct 2025 15:01:18 -0700 Subject: [PATCH 1360/4355] explicitly mention the command is without slash --- .../contrib/chat/common/promptSyntax/service/promptsService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index fed1bfd5cc9..852d43bcad3 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -194,6 +194,7 @@ export interface IPromptsService extends IDisposable { /** * Gets the prompt file for a slash command from cache if available. + * @param command - name of the prompt command without slash */ resolvePromptSlashCommandFromCache(command: string): ParsedPromptFile | undefined; From b09414d102a42b952a44d2ebfab53d4aa8f6d39c Mon Sep 17 00:00:00 2001 From: Vijay Upadya <41652029+vijayupadya@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:38:10 -0700 Subject: [PATCH 1361/4355] Show chat item title while loading in editor (#272360) * Show chat item title while loading * update comment --------- Co-authored-by: vijay upadya --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index c62c7736dba..0e2d8e0c989 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -180,6 +180,11 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } } + // If a preferred title was provided in options, use it + if (this.options.title?.preferred) { + return this.options.title.preferred; + } + // Fall back to default naming pattern const inputCountSuffix = (this.inputCount > 0 ? ` ${this.inputCount + 1}` : ''); const defaultName = this.options.title?.fallback ?? nls.localize('chatEditorName', "Chat"); From 6025703c6a0cd48e626b2cf18e6c97ec77f961e6 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Oct 2025 16:12:10 -0700 Subject: [PATCH 1362/4355] Revert "Generate policy data as JSON" (#272362) --- build/.gitignore | 1 - .../steps/product-build-darwin-compile.yml | 2 +- .../steps/product-build-win32-compile.yml | 2 +- .../policyGenerator.js => policies.js} | 357 ++++++++++----- .../policyGenerator.ts => policies.ts} | 432 ++++++++++++++---- build/lib/policies/copyPolicyDto.js | 58 --- build/lib/policies/copyPolicyDto.ts | 25 - build/lib/policies/policyData.jsonc | 277 ----------- build/package.json | 9 +- src/vs/base/common/policy.ts | 59 +-- .../test/common/configurationRegistry.test.ts | 9 +- .../test/common/configurationService.test.ts | 3 - .../test/common/policyConfiguration.test.ts | 15 - src/vs/platform/environment/common/argv.ts | 1 - .../environment/common/environment.ts | 1 - .../environment/common/environmentService.ts | 4 - src/vs/platform/environment/node/argv.ts | 1 - .../common/extensionManagement.ts | 9 +- .../telemetry/common/telemetryService.ts | 29 +- .../common/update.config.contribution.ts | 23 - .../contrib/chat/browser/chat.contribution.ts | 62 +-- .../browser/extensions.contribution.ts | 8 - .../contrib/policyExport/common/policyDto.ts | 33 -- .../policyExport.contribution.ts | 117 ----- .../test/node/policyExport.integrationTest.ts | 60 --- .../terminal/common/terminalConfiguration.ts | 8 - .../test/browser/configurationEditing.test.ts | 3 - .../test/browser/configurationService.test.ts | 5 - .../test/common/accountPolicyService.test.ts | 9 - .../common/multiplexPolicyService.test.ts | 9 - src/vs/workbench/workbench.desktop.main.ts | 3 - 31 files changed, 592 insertions(+), 1042 deletions(-) rename build/lib/{policies/policyGenerator.js => policies.js} (70%) rename build/lib/{policies/policyGenerator.ts => policies.ts} (69%) delete mode 100644 build/lib/policies/copyPolicyDto.js delete mode 100644 build/lib/policies/copyPolicyDto.ts delete mode 100644 build/lib/policies/policyData.jsonc delete mode 100644 src/vs/workbench/contrib/policyExport/common/policyDto.ts delete mode 100644 src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts delete mode 100644 src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts diff --git a/build/.gitignore b/build/.gitignore index 679674617c7..61cf49cb9a1 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,2 +1 @@ *.js.map -lib/policies/policyDto.* diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index d1d431505f6..bc8e962f91b 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -118,7 +118,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies/policyGenerator build/lib/policies/policyData.jsonc darwin + - script: node build/lib/policies darwin displayName: Generate policy definitions retryCountOnTaskFailure: 3 diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index bdc807fdae5..072d64ec2d9 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -120,7 +120,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - powershell: node build\lib\policies\policyGenerator build\lib\policies\policyData.jsonc win32 + - powershell: node build\lib\policies win32 displayName: Generate Group Policy definitions retryCountOnTaskFailure: 3 diff --git a/build/lib/policies/policyGenerator.js b/build/lib/policies.js similarity index 70% rename from build/lib/policies/policyGenerator.js rename to build/lib/policies.js index 7ddb6956c18..d2ef760870d 100644 --- a/build/lib/policies/policyGenerator.js +++ b/build/lib/policies.js @@ -1,37 +1,4 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -40,12 +7,24 @@ Object.defineProperty(exports, "__esModule", { value: true }); * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const minimist_1 = __importDefault(require("minimist")); +const child_process_1 = require("child_process"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); -const JSONC = __importStar(require("jsonc-parser")); -const product = require('../../../product.json'); -const packageJson = require('../../../package.json'); +const byline_1 = __importDefault(require("byline")); +const ripgrep_1 = require("@vscode/ripgrep"); +const tree_sitter_1 = __importDefault(require("tree-sitter")); +const { typescript } = require('tree-sitter-typescript'); +const product = require('../../product.json'); +const packageJson = require('../../package.json'); +function isNlsString(value) { + return value ? typeof value !== 'string' : false; +} +function isStringArray(value) { + return !value.some(s => isNlsString(s)); +} +function isNlsStringArray(value) { + return value.every(s => isNlsString(s)); +} var PolicyType; (function (PolicyType) { PolicyType["Boolean"] = "boolean"; @@ -128,12 +107,12 @@ ${this.renderProfileManifestValue(translations)} } } class BooleanPolicy extends BasePolicy { - static from(category, policy) { - const { name, minimumVersion, localization, type } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new BooleanPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); @@ -164,17 +143,23 @@ class BooleanPolicy extends BasePolicy { boolean`; } } +class ParseError extends Error { + constructor(message, moduleName, node) { + super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); + } +} class NumberPolicy extends BasePolicy { defaultValue; - static from(category, policy) { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'number') { return undefined; } - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); + const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); + if (typeof defaultValue === 'undefined') { + throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); } - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); } constructor(name, category, minimumVersion, description, moduleName, defaultValue) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -206,12 +191,12 @@ class NumberPolicy extends BasePolicy { } } class StringPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new StringPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.String, name, category, minimumVersion, description, moduleName); @@ -239,12 +224,12 @@ class StringPolicy extends BasePolicy { } } class ObjectPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Object, name, category, minimumVersion, description, moduleName); @@ -275,20 +260,26 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; - static from(category, policy) { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - const enum_ = enumValue; + const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); if (!enum_) { return undefined; } - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + if (!isStringArray(enum_)) { + throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); + } + const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); + if (!enumDescriptions) { + throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); + else if (!isNlsStringArray(enumDescriptions)) { + throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); + } + return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); } constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -331,6 +322,178 @@ class StringEnumPolicy extends BasePolicy { `; } } +const NumberQ = { + Q: `(number) @value`, + value(matches) { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + return parseInt(value); + } +}; +const StringQ = { + Q: `[ + (string (string_fragment) @value) + (call_expression + function: [ + (identifier) @localizeFn (#eq? @localizeFn localize) + (member_expression + object: (identifier) @nlsObj (#eq? @nlsObj nls) + property: (property_identifier) @localizeFn (#eq? @localizeFn localize) + ) + ] + arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) + ) + ]`, + value(matches) { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; + if (nlsKey) { + return { value, nlsKey }; + } + else { + return value; + } + } +}; +const StringArrayQ = { + Q: `(array ${StringQ.Q})`, + value(matches) { + if (matches.length === 0) { + return undefined; + } + return matches.map(match => { + return StringQ.value([match]); + }); + } +}; +function getProperty(qtype, moduleName, node, key) { + const query = new tree_sitter_1.default.Query(typescript, `( + (pair + key: [(property_identifier)(string)] @key + value: ${qtype.Q} + ) + (#any-of? @key "${key}" "'${key}'") + )`); + try { + const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); + return qtype.value(matches); + } + catch (e) { + throw new ParseError(e.message, moduleName, node); + } +} +function getNumberProperty(moduleName, node, key) { + return getProperty(NumberQ, moduleName, node, key); +} +function getStringProperty(moduleName, node, key) { + return getProperty(StringQ, moduleName, node, key); +} +function getStringArrayProperty(moduleName, node, key) { + return getProperty(StringArrayQ, moduleName, node, key); +} +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; +function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { + const name = getStringProperty(moduleName, policyNode, 'name'); + if (!name) { + throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); + } + else if (isNlsString(name)) { + throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); + } + const categoryName = getStringProperty(moduleName, configurationNode, 'title'); + if (!categoryName) { + throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); + } + else if (!isNlsString(categoryName)) { + throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); + } + const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; + let category = categories.get(categoryKey); + if (!category) { + category = { moduleName, name: categoryName }; + categories.set(categoryKey, category); + } + const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); + if (!minimumVersion) { + throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); + } + else if (isNlsString(minimumVersion)) { + throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); + } + const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); + if (!description) { + throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); + } + if (!isNlsString(description)) { + throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); + } + let result; + for (const policyType of PolicyTypes) { + if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { + break; + } + } + if (!result) { + throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); + } + return result; +} +function getPolicies(moduleName, node) { + const query = new tree_sitter_1.default.Query(typescript, ` + ( + (call_expression + function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) + arguments: (arguments (object (pair + key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") + value: (object (pair + key: [(property_identifier)(string)(computed_property_name)] + value: (object (pair + key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") + value: (object) @policy + )) @setting + )) + )) @configuration) + ) + ) + `); + const categories = new Map(); + return query.matches(node).map(m => { + const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; + const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; + const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; + return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); + }); +} +async function getFiles(root) { + return new Promise((c, e) => { + const result = []; + const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); + const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); + stream.on('data', path => result.push(path)); + stream.on('error', err => e(err)); + stream.on('end', () => c(result)); + }); +} function renderADMX(regKey, versions, categories, policies) { versions = versions.map(v => v.replace(/\./g, '_')); return ` @@ -594,15 +757,7 @@ async function getSpecificNLS(resourceUrlTemplate, languageId, version) { throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); } const { contents: result } = await res.json(); - // TODO: support module namespacing - // Flatten all moduleName keys to empty string - const flattened = { '': {} }; - for (const moduleName in result) { - for (const nlsKey in result[moduleName]) { - flattened[''][nlsKey] = result[moduleName][nlsKey]; - } - } - return flattened; + return result; } function parseVersion(version) { const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); @@ -646,45 +801,18 @@ async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageI } return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; -async function parsePolicies(policyDataFile) { - const contents = JSONC.parse(await fs_1.promises.readFile(policyDataFile, { encoding: 'utf8' })); - const categories = new Map(); - for (const category of contents.categories) { - categories.set(category.key, category); - } +async function parsePolicies() { + const parser = new tree_sitter_1.default(); + parser.setLanguage(typescript); + const files = await getFiles(process.cwd()); + const base = path_1.default.join(process.cwd(), 'src'); const policies = []; - for (const policy of contents.policies) { - const category = categories.get(policy.category); - if (!category) { - throw new Error(`Unknown category: ${policy.category}`); - } - let result; - for (const policyType of PolicyTypes) { - if (result = policyType.from(category, policy)) { - break; - } - } - if (!result) { - throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); - } - policies.push(result); + for (const file of files) { + const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); + const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); + const tree = parser.parse(contents); + policies.push(...getPolicies(moduleName, tree.rootNode)); } - // Sort policies first by category name, then by policy name - policies.sort((a, b) => { - const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); - if (categoryCompare !== 0) { - return categoryCompare; - } - return a.name.localeCompare(b.name); - }); return policies; } async function getTranslations() { @@ -732,14 +860,8 @@ async function darwinMain(policies, translations) { } } async function main() { - const args = (0, minimist_1.default)(process.argv.slice(2)); - if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } - const policyDataFile = args._[0]; - const platform = args._[1]; - const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); + const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); + const platform = process.argv[2]; if (platform === 'darwin') { await darwinMain(policies, translations); } @@ -747,14 +869,19 @@ async function main() { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - console.error(err); + if (err instanceof ParseError) { + console.error(`Parse Error:`, err.message); + } + else { + console.error(err); + } process.exit(1); }); } -//# sourceMappingURL=policyGenerator.js.map \ No newline at end of file +//# sourceMappingURL=policies.js.map \ No newline at end of file diff --git a/build/lib/policies/policyGenerator.ts b/build/lib/policies.ts similarity index 69% rename from build/lib/policies/policyGenerator.ts rename to build/lib/policies.ts index 5fb06942f67..381d2f4c1ac 100644 --- a/build/lib/policies/policyGenerator.ts +++ b/build/lib/policies.ts @@ -3,17 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist from 'minimist'; +import { spawn } from 'child_process'; import { promises as fs } from 'fs'; import path from 'path'; -import { CategoryDto, ExportedPolicyDataDto, PolicyDto } from './policyDto'; -import * as JSONC from 'jsonc-parser'; - -const product = require('../../../product.json'); -const packageJson = require('../../../package.json'); +import byline from 'byline'; +import { rgPath } from '@vscode/ripgrep'; +import Parser from 'tree-sitter'; +const { typescript } = require('tree-sitter-typescript'); +const product = require('../../product.json'); +const packageJson = require('../../package.json'); type NlsString = { value: string; nlsKey: string }; +function isNlsString(value: string | NlsString | undefined): value is NlsString { + return value ? typeof value !== 'string' : false; +} + +function isStringArray(value: (string | NlsString)[]): value is string[] { + return !value.some(s => isNlsString(s)); +} + +function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { + return value.every(s => isNlsString(s)); +} + interface Category { readonly moduleName: string; readonly name: NlsString; @@ -133,14 +146,21 @@ ${this.renderProfileManifestValue(translations)} class BooleanPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { - const { name, minimumVersion, localization, type } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): BooleanPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new BooleanPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -183,20 +203,35 @@ class BooleanPolicy extends BasePolicy { } } +class ParseError extends Error { + constructor(message: string, moduleName: string, node: Parser.SyntaxNode) { + super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); + } +} + class NumberPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): NumberPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'number') { return undefined; } - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); + const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); + + if (typeof defaultValue === 'undefined') { + throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); } - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); } private constructor( @@ -241,14 +276,21 @@ class NumberPolicy extends BasePolicy { class StringPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): StringPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new StringPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -289,14 +331,21 @@ class StringPolicy extends BasePolicy { class ObjectPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): ObjectPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -338,32 +387,39 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): StringEnumPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - const enum_ = enumValue; + const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); if (!enum_) { return undefined; } - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + if (!isStringArray(enum_)) { + throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy( - name, - { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, - minimumVersion, - { nlsKey: localization.description.key, value: localization.description.value }, - '', - enum_, - enumDescriptions - ); + + const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); + + if (!enumDescriptions) { + throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); + } else if (!isNlsStringArray(enumDescriptions)) { + throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); + } + + return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); } private constructor( @@ -419,6 +475,226 @@ class StringEnumPolicy extends BasePolicy { } } +interface QType { + Q: string; + value(matches: Parser.QueryMatch[]): T | undefined; +} + +const NumberQ: QType = { + Q: `(number) @value`, + + value(matches: Parser.QueryMatch[]): number | undefined { + const match = matches[0]; + + if (!match) { + return undefined; + } + + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + + return parseInt(value); + } +}; + +const StringQ: QType = { + Q: `[ + (string (string_fragment) @value) + (call_expression + function: [ + (identifier) @localizeFn (#eq? @localizeFn localize) + (member_expression + object: (identifier) @nlsObj (#eq? @nlsObj nls) + property: (property_identifier) @localizeFn (#eq? @localizeFn localize) + ) + ] + arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) + ) + ]`, + + value(matches: Parser.QueryMatch[]): string | NlsString | undefined { + const match = matches[0]; + + if (!match) { + return undefined; + } + + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + + const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; + + if (nlsKey) { + return { value, nlsKey }; + } else { + return value; + } + } +}; + +const StringArrayQ: QType<(string | NlsString)[]> = { + Q: `(array ${StringQ.Q})`, + + value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { + if (matches.length === 0) { + return undefined; + } + + return matches.map(match => { + return StringQ.value([match]) as string | NlsString; + }); + } +}; + +function getProperty(qtype: QType, moduleName: string, node: Parser.SyntaxNode, key: string): T | undefined { + const query = new Parser.Query( + typescript, + `( + (pair + key: [(property_identifier)(string)] @key + value: ${qtype.Q} + ) + (#any-of? @key "${key}" "'${key}'") + )` + ); + + try { + const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); + return qtype.value(matches); + } catch (e) { + throw new ParseError(e.message, moduleName, node); + } +} + +function getNumberProperty(moduleName: string, node: Parser.SyntaxNode, key: string): number | undefined { + return getProperty(NumberQ, moduleName, node, key); +} + +function getStringProperty(moduleName: string, node: Parser.SyntaxNode, key: string): string | NlsString | undefined { + return getProperty(StringQ, moduleName, node, key); +} + +function getStringArrayProperty(moduleName: string, node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { + return getProperty(StringArrayQ, moduleName, node, key); +} + +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + +function getPolicy( + moduleName: string, + configurationNode: Parser.SyntaxNode, + settingNode: Parser.SyntaxNode, + policyNode: Parser.SyntaxNode, + categories: Map +): Policy { + const name = getStringProperty(moduleName, policyNode, 'name'); + + if (!name) { + throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); + } else if (isNlsString(name)) { + throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); + } + + const categoryName = getStringProperty(moduleName, configurationNode, 'title'); + + if (!categoryName) { + throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); + } else if (!isNlsString(categoryName)) { + throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); + } + + const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; + let category = categories.get(categoryKey); + + if (!category) { + category = { moduleName, name: categoryName }; + categories.set(categoryKey, category); + } + + const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); + + if (!minimumVersion) { + throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); + } else if (isNlsString(minimumVersion)) { + throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); + } + + const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); + + if (!description) { + throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); + } if (!isNlsString(description)) { + throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); + } + + let result: Policy | undefined; + + for (const policyType of PolicyTypes) { + if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { + break; + } + } + + if (!result) { + throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); + } + + return result; +} + +function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { + const query = new Parser.Query(typescript, ` + ( + (call_expression + function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) + arguments: (arguments (object (pair + key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") + value: (object (pair + key: [(property_identifier)(string)(computed_property_name)] + value: (object (pair + key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") + value: (object) @policy + )) @setting + )) + )) @configuration) + ) + ) + `); + + const categories = new Map(); + + return query.matches(node).map(m => { + const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; + const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; + const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; + return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); + }); +} + +async function getFiles(root: string): Promise { + return new Promise((c, e) => { + const result: string[] = []; + const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); + const stream = byline(rg.stdout.setEncoding('utf8')); + stream.on('data', path => result.push(path)); + stream.on('error', err => e(err)); + stream.on('end', () => c(result)); + }); +} + function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { versions = versions.map(v => v.replace(/\./g, '_')); @@ -693,7 +969,7 @@ type Translations = { languageId: string; languageTranslations: LanguageTranslat type Version = [number, number, number]; -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -709,17 +985,7 @@ async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, v } const { contents: result } = await res.json() as { contents: LanguageTranslations }; - - // TODO: support module namespacing - // Flatten all moduleName keys to empty string - const flattened: LanguageTranslations = { '': {} }; - for (const moduleName in result) { - for (const nlsKey in result[moduleName]) { - flattened[''][nlsKey] = result[moduleName][nlsKey]; - } - } - - return flattened; + return result; } function parseVersion(version: string): Version { @@ -768,52 +1034,21 @@ async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: s return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; +async function parsePolicies(): Promise { + const parser = new Parser(); + parser.setLanguage(typescript); -async function parsePolicies(policyDataFile: string): Promise { - const contents = JSONC.parse(await fs.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto; - const categories = new Map(); - for (const category of contents.categories) { - categories.set(category.key, category); - } + const files = await getFiles(process.cwd()); + const base = path.join(process.cwd(), 'src'); + const policies = []; - const policies: Policy[] = []; - for (const policy of contents.policies) { - const category = categories.get(policy.category); - if (!category) { - throw new Error(`Unknown category: ${policy.category}`); - } - - let result: Policy | undefined; - for (const policyType of PolicyTypes) { - if (result = policyType.from(category, policy)) { - break; - } - } - - if (!result) { - throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); - } - - policies.push(result); + for (const file of files) { + const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); + const contents = await fs.readFile(file, { encoding: 'utf8' }); + const tree = parser.parse(contents); + policies.push(...getPolicies(moduleName, tree.rootNode)); } - // Sort policies first by category name, then by policy name - policies.sort((a, b) => { - const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); - if (categoryCompare !== 0) { - return categoryCompare; - } - return a.name.localeCompare(b.name); - }); - return policies; } @@ -877,29 +1112,26 @@ async function darwinMain(policies: Policy[], translations: Translations) { } async function main() { - const args = minimist(process.argv.slice(2)); - if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } - - const policyDataFile = args._[0]; - const platform = args._[1]; - const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); + const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); + const platform = process.argv[2]; if (platform === 'darwin') { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - console.error(err); + if (err instanceof ParseError) { + console.error(`Parse Error:`, err.message); + } else { + console.error(err); + } process.exit(1); }); } diff --git a/build/lib/policies/copyPolicyDto.js b/build/lib/policies/copyPolicyDto.js deleted file mode 100644 index 9a7d518ced8..00000000000 --- a/build/lib/policies/copyPolicyDto.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const fs = __importStar(require("fs")); -const path = __importStar(require("path")); -const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); -const destFile = path.join(__dirname, 'policyDto.ts'); -try { - // Check if source file exists - if (!fs.existsSync(sourceFile)) { - console.error(`Error: Source file not found: ${sourceFile}`); - console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); - process.exit(1); - } - // Copy the file - fs.copyFileSync(sourceFile, destFile); -} -catch (error) { - console.error(`Error copying policyDto.ts: ${error.message}`); - process.exit(1); -} -//# sourceMappingURL=copyPolicyDto.js.map \ No newline at end of file diff --git a/build/lib/policies/copyPolicyDto.ts b/build/lib/policies/copyPolicyDto.ts deleted file mode 100644 index a0ec9fc4c1d..00000000000 --- a/build/lib/policies/copyPolicyDto.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as path from 'path'; - -const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); -const destFile = path.join(__dirname, 'policyDto.ts'); - -try { - // Check if source file exists - if (!fs.existsSync(sourceFile)) { - console.error(`Error: Source file not found: ${sourceFile}`); - console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); - process.exit(1); - } - - // Copy the file - fs.copyFileSync(sourceFile, destFile); -} catch (error) { - console.error(`Error copying policyDto.ts: ${(error as Error).message}`); - process.exit(1); -} diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc deleted file mode 100644 index 096b911e5d3..00000000000 --- a/build/lib/policies/policyData.jsonc +++ /dev/null @@ -1,277 +0,0 @@ -/** THIS FILE IS AUTOMATICALLY GENERATED USING `code --export-policy-data`. DO NOT MODIFY IT MANUALLY. **/ -{ - "categories": [ - { - "key": "Extensions", - "name": { - "key": "extensionsConfigurationTitle", - "value": "Extensions" - } - }, - { - "key": "IntegratedTerminal", - "name": { - "key": "terminalIntegratedConfigurationTitle", - "value": "Integrated Terminal" - } - }, - { - "key": "InteractiveSession", - "name": { - "key": "interactiveSessionConfigurationTitle", - "value": "Chat" - } - }, - { - "key": "Telemetry", - "name": { - "key": "telemetryConfigurationTitle", - "value": "Telemetry" - } - }, - { - "key": "Update", - "name": { - "key": "updateConfigurationTitle", - "value": "Update" - } - } - ], - "policies": [ - { - "key": "chat.mcp.gallery.serviceUrl", - "name": "McpGalleryServiceUrl", - "category": "InteractiveSession", - "minimumVersion": "1.101", - "localization": { - "description": { - "key": "mcp.gallery.serviceUrl", - "value": "Configure the MCP Gallery service URL to connect to" - } - }, - "type": "string", - "default": "" - }, - { - "key": "extensions.gallery.serviceUrl", - "name": "ExtensionGalleryServiceUrl", - "category": "Extensions", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "extensions.gallery.serviceUrl", - "value": "Configure the Marketplace service URL to connect to" - } - }, - "type": "string", - "default": "" - }, - { - "key": "extensions.allowed", - "name": "AllowedExtensions", - "category": "Extensions", - "minimumVersion": "1.96", - "localization": { - "description": { - "key": "extensions.allowed.policy", - "value": "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions" - } - }, - "type": "object", - "default": "*" - }, - { - "key": "chat.tools.global.autoApprove", - "name": "ChatToolsAutoApprove", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "autoApprove2.description", - "value": "Global auto approve also known as \"YOLO mode\" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine." - } - }, - "type": "boolean", - "default": false - }, - { - "key": "chat.mcp.access", - "name": "ChatMCP", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.mcp.access", - "value": "Controls access to installed Model Context Protocol servers." - }, - "enumDescriptions": [ - { - "key": "chat.mcp.access.none", - "value": "No access to MCP servers." - }, - { - "key": "chat.mcp.access.registry", - "value": "Allows access to MCP servers installed from the registry that VS Code is connected to." - }, - { - "key": "chat.mcp.access.any", - "value": "Allow access to any installed MCP server." - } - ] - }, - "type": "string", - "default": "all", - "enum": [ - "none", - "registry", - "all" - ] - }, - { - "key": "chat.extensionTools.enabled", - "name": "ChatAgentExtensionTools", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.extensionToolsEnabled", - "value": "Enable using tools contributed by third-party extensions." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.agent.enabled", - "name": "ChatAgentMode", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.agent.enabled.description", - "value": "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.promptFiles", - "name": "ChatPromptFiles", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.promptFiles.policy", - "value": "Enables reusable prompt and instruction files in Chat sessions." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.tools.terminal.enableAutoApprove", - "name": "ChatToolsTerminalEnableAutoApprove", - "category": "IntegratedTerminal", - "minimumVersion": "1.104", - "localization": { - "description": { - "key": "autoApproveMode.description", - "value": "Controls whether to allow auto approval in the run in terminal tool." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "update.mode", - "name": "UpdateMode", - "category": "Update", - "minimumVersion": "1.67", - "localization": { - "description": { - "key": "updateMode", - "value": "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service." - }, - "enumDescriptions": [ - { - "key": "none", - "value": "Disable updates." - }, - { - "key": "manual", - "value": "Disable automatic background update checks. Updates will be available if you manually check for updates." - }, - { - "key": "start", - "value": "Check for updates only on startup. Disable automatic background update checks." - }, - { - "key": "default", - "value": "Enable automatic update checks. Code will check for updates automatically and periodically." - } - ] - }, - "type": "string", - "default": "default", - "enum": [ - "none", - "manual", - "start", - "default" - ] - }, - { - "key": "telemetry.telemetryLevel", - "name": "TelemetryLevel", - "category": "Telemetry", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "telemetry.telemetryLevel.policyDescription", - "value": "Controls the level of telemetry." - }, - "enumDescriptions": [ - { - "key": "telemetry.telemetryLevel.default", - "value": "Sends usage data, errors, and crash reports." - }, - { - "key": "telemetry.telemetryLevel.error", - "value": "Sends general error telemetry and crash reports." - }, - { - "key": "telemetry.telemetryLevel.crash", - "value": "Sends OS level crash reports." - }, - { - "key": "telemetry.telemetryLevel.off", - "value": "Disables all product telemetry." - } - ] - }, - "type": "string", - "default": "all", - "enum": [ - "all", - "error", - "crash", - "off" - ] - }, - { - "key": "telemetry.feedback.enabled", - "name": "EnableFeedback", - "category": "Telemetry", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "telemetry.feedback.enabled", - "value": "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options." - } - }, - "type": "boolean", - "default": true - } - ] -} diff --git a/build/package.json b/build/package.json index ef715fbfb6a..fbbf5264776 100644 --- a/build/package.json +++ b/build/package.json @@ -64,12 +64,9 @@ }, "type": "commonjs", "scripts": { - "copy-policy-dto": "node lib/policies/copyPolicyDto.js", - "prebuild-ts": "npm run copy-policy-dto", - "build-ts": "cd .. && npx tsgo --project build/tsconfig.build.json", - "compile": "npm run build-ts", - "watch": "npm run build-ts -- --watch", - "npmCheckJs": "npm run build-ts -- --noEmit" + "compile": "cd .. && npx tsgo --project build/tsconfig.build.json", + "watch": "cd .. && npx tsgo --project build/tsconfig.build.json --watch", + "npmCheckJs": "cd .. && npx tsgo --project build/tsconfig.build.json --noEmit" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 7fa484a477e..1e97392d5e2 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -3,52 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; export type PolicyName = string; -export type LocalizedValue = { - key: string; - value: string; -}; - -export enum PolicyCategory { - Extensions = 'Extensions', - IntegratedTerminal = 'IntegratedTerminal', - InteractiveSession = 'InteractiveSession', - Telemetry = 'Telemetry', - Update = 'Update', -} - -export const PolicyCategoryData: { - [key in PolicyCategory]: { name: LocalizedValue } -} = { - [PolicyCategory.Extensions]: { - name: { - key: 'extensionsConfigurationTitle', value: localize('extensionsConfigurationTitle', "Extensions"), - } - }, - [PolicyCategory.IntegratedTerminal]: { - name: { - key: 'terminalIntegratedConfigurationTitle', value: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), - } - }, - [PolicyCategory.InteractiveSession]: { - name: { - key: 'interactiveSessionConfigurationTitle', value: localize('interactiveSessionConfigurationTitle', "Chat"), - } - }, - [PolicyCategory.Telemetry]: { - name: { - key: 'telemetryConfigurationTitle', value: localize('telemetryConfigurationTitle', "Telemetry"), - } - }, - [PolicyCategory.Update]: { - name: { - key: 'updateConfigurationTitle', value: localize('updateConfigurationTitle', "Update"), - } - } -}; export interface IPolicy { @@ -57,27 +14,15 @@ export interface IPolicy { */ readonly name: PolicyName; - /** - * The policy category. - */ - readonly category: PolicyCategory; - /** * The Code version in which this policy was introduced. */ readonly minimumVersion: `${number}.${number}`; /** - * Localization info for the policy. - * - * IMPORTANT: the key values for these must be unique to avoid collisions, as during the export time the module information is not available. + * The policy description (optional). */ - readonly localization: { - /** The localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's description property. */ - description: LocalizedValue; - /** List of localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's enumDescriptions property. */ - enumDescriptions?: LocalizedValue[]; - }; + readonly description?: string; /** * The value that an ACCOUNT-based feature will use when its corresponding policy is active. diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 043f137b202..4d089e71eb8 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -7,7 +7,6 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../common/configurationRegistry.js'; import { Registry } from '../../../registry/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationRegistry', () => { @@ -90,18 +89,14 @@ suite('ConfigurationRegistry', () => { 'type': 'object', policy: { name: 'policy', - category: PolicyCategory.Extensions, - minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } + minimumVersion: '1.0.0' } }, 'policy2': { 'type': 'object', policy: { name: 'policy', - category: PolicyCategory.Extensions, - minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } + minimumVersion: '1.0.0' } } } diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index a258c16a082..4e765d63efe 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -20,7 +20,6 @@ import { NullLogService } from '../../../log/common/log.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { NullPolicyService } from '../../../policy/common/policy.js'; import { Registry } from '../../../registry/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationService.test.ts', () => { @@ -375,9 +374,7 @@ suite('ConfigurationService.test.ts', () => { 'default': 'isSet', policy: { name: 'configurationService.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 5cc463e4ac0..ac30e8b5050 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -19,7 +19,6 @@ import { IPolicyService } from '../../../policy/common/policy.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('PolicyConfiguration', () => { @@ -40,9 +39,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.settingB': { @@ -50,9 +47,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.objectSetting': { @@ -60,9 +55,7 @@ suite('PolicyConfiguration', () => { 'default': {}, policy: { name: 'PolicyObjectSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.arraySetting': { @@ -70,9 +63,7 @@ suite('PolicyConfiguration', () => { 'default': [], policy: { name: 'PolicyArraySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.booleanSetting': { @@ -80,9 +71,7 @@ suite('PolicyConfiguration', () => { 'default': true, policy: { name: 'PolicyBooleanSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.internalSetting': { @@ -91,9 +80,7 @@ suite('PolicyConfiguration', () => { included: false, policy: { name: 'PolicyInternalSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'nonPolicy.setting': { @@ -280,9 +267,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueC', policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, }, } }; Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index a10f4c9b3bb..e7b08b8f887 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -105,7 +105,6 @@ export interface NativeParsedArgs { 'skip-welcome'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; - 'export-policy-data'?: string; 'install-source'?: string; 'add-mcp'?: string[]; 'disable-updates'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a14c835bf9..c5f10d53040 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -145,7 +145,6 @@ export interface INativeEnvironmentService extends IEnvironmentService { useInMemorySecretStorage?: boolean; crossOriginIsolated?: boolean; - exportPolicyData?: string; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 535132f43a4..6b6c82c6d5d 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -254,10 +254,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get editSessionId(): string | undefined { return this.args['editSessionId']; } - get exportPolicyData(): string | undefined { - return this.args['export-policy-data']; - } - get continueOn(): string | undefined { return this.args['continueOn']; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index d0ab1d6b4e5..9b0cf59752b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -162,7 +162,6 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-sharedprocess': { type: 'string', allowEmptyValue: true }, 'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true }, 'export-default-configuration': { type: 'string' }, - 'export-policy-data': { type: 'string', allowEmptyValue: true }, 'install-source': { type: 'string' }, 'enable-smoke-test-driver': { type: 'boolean' }, 'logExtensionHostCommunication': { type: 'boolean' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 35fe4290937..cd5c1945c5a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,7 +9,6 @@ import { Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IPager } from '../../../base/common/paging.js'; import { Platform } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { URI } from '../../../base/common/uri.js'; import { localize, localize2 } from '../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; @@ -735,14 +734,8 @@ Registry.as(Extensions.Configuration) scope: ConfigurationScope.APPLICATION, policy: { name: 'AllowedExtensions', - category: PolicyCategory.Extensions, minimumVersion: '1.96', - localization: { - description: { - key: 'extensions.allowed.policy', - value: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), - } - } + description: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), }, additionalProperties: false, patternProperties: { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index e26d733e98e..ef676bdc69b 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -6,7 +6,6 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; @@ -224,32 +223,8 @@ configurationRegistry.registerConfiguration({ 'tags': ['usesOnlineServices', 'telemetry'], 'policy': { name: 'TelemetryLevel', - category: PolicyCategory.Telemetry, minimumVersion: '1.99', - localization: { - description: { - key: 'telemetry.telemetryLevel.policyDescription', - value: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), - }, - enumDescriptions: [ - { - key: 'telemetry.telemetryLevel.default', - value: localize('telemetry.telemetryLevel.default', "Sends usage data, errors, and crash reports."), - }, - { - key: 'telemetry.telemetryLevel.error', - value: localize('telemetry.telemetryLevel.error', "Sends general error telemetry and crash reports."), - }, - { - key: 'telemetry.telemetryLevel.crash', - value: localize('telemetry.telemetryLevel.crash', "Sends OS level crash reports."), - }, - { - key: 'telemetry.telemetryLevel.off', - value: localize('telemetry.telemetryLevel.off', "Disables all product telemetry."), - } - ] - } + description: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), } }, 'telemetry.feedback.enabled': { @@ -258,9 +233,7 @@ configurationRegistry.registerConfiguration({ description: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options."), policy: { name: 'EnableFeedback', - category: PolicyCategory.Telemetry, minimumVersion: '1.99', - localization: { description: { key: 'telemetry.feedback.enabled', value: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.") } }, } }, // Deprecated telemetry setting diff --git a/src/vs/platform/update/common/update.config.contribution.ts b/src/vs/platform/update/common/update.config.contribution.ts index e5fb1abc0b6..d96926b5578 100644 --- a/src/vs/platform/update/common/update.config.contribution.ts +++ b/src/vs/platform/update/common/update.config.contribution.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { isWeb, isWindows } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { localize } from '../../../nls.js'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; import { Registry } from '../../registry/common/platform.js'; @@ -31,29 +30,7 @@ configurationRegistry.registerConfiguration({ ], policy: { name: 'UpdateMode', - category: PolicyCategory.Update, minimumVersion: '1.67', - localization: { - description: { key: 'updateMode', value: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), }, - enumDescriptions: [ - { - key: 'none', - value: localize('none', "Disable updates."), - }, - { - key: 'manual', - value: localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), - }, - { - key: 'start', - value: localize('start', "Check for updates only on startup. Disable automatic background update checks."), - }, - { - key: 'default', - value: localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically."), - } - ] - }, } }, 'update.channel': { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index fc1a01be613..c79d3b3fb10 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -123,7 +123,6 @@ import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './p import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -241,21 +240,18 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.GlobalAutoApprove]: { default: false, + // HACK: Description duplicated for policy parser. See https://github.com/microsoft/vscode/issues/254526 + description: nls.localize('autoApprove2.description', + 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' + ), markdownDescription: globalAutoApproveDescription.value, type: 'boolean', scope: ConfigurationScope.APPLICATION_MACHINE, tags: ['experimental'], policy: { name: 'ChatToolsAutoApprove', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_preview_features_enabled === false ? false : undefined, - localization: { - description: { - key: 'autoApprove2.description', - value: nls.localize('autoApprove2.description', 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.') - } - }, } }, [ChatConfiguration.AutoApproveEdits]: { @@ -348,7 +344,6 @@ configurationRegistry.registerConfiguration({ default: McpAccessValue.All, policy: { name: 'ChatMCP', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => { if (account.mcp === false) { @@ -359,23 +354,6 @@ configurationRegistry.registerConfiguration({ } return undefined; }, - localization: { - description: { - key: 'chat.mcp.access', - value: nls.localize('chat.mcp.access', "Controls access to installed Model Context Protocol servers.") - }, - enumDescriptions: [ - { - key: 'chat.mcp.access.none', value: nls.localize('chat.mcp.access.none', "No access to MCP servers."), - }, - { - key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), - }, - { - key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") - } - ] - }, } }, [mcpAutoStartConfig]: { @@ -441,14 +419,8 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentExtensionTools', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - localization: { - description: { - key: 'chat.extensionToolsEnabled', - value: nls.localize('chat.extensionToolsEnabled', "Enable using tools contributed by third-party extensions.") - } - }, + description: nls.localize('chat.extensionToolsPolicy', "Enable using tools contributed by third-party extensions."), } }, [ChatConfiguration.AgentEnabled]: { @@ -457,15 +429,8 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentMode', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_agent_enabled === false ? false : undefined, - localization: { - description: { - key: 'chat.agent.enabled.description', - value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), - } - } } }, [ChatConfiguration.EnableMath]: { @@ -507,15 +472,8 @@ configurationRegistry.registerConfiguration({ included: false, policy: { name: 'McpGalleryServiceUrl', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.101', - value: (account) => account.mcpRegistryUrl, - localization: { - description: { - key: 'mcp.gallery.serviceUrl', - value: nls.localize('mcp.gallery.serviceUrl', "Configure the MCP Gallery service URL to connect to"), - } - } + value: (account) => account.mcpRegistryUrl }, }, [PromptsConfig.KEY]: { @@ -537,14 +495,8 @@ configurationRegistry.registerConfiguration({ tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], policy: { name: 'ChatPromptFiles', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - localization: { - description: { - key: 'chat.promptFiles.policy', - value: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") - } - } + description: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") } }, [PromptsConfig.INSTRUCTIONS_LOCATION_KEY]: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 012c4d398e5..b78c1385ac9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,7 +13,6 @@ import { mnemonicButtonLabel } from '../../../../base/common/labels.js'; import { Disposable, DisposableStore, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isNative, isWeb } from '../../../../base/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { MultiCommand } from '../../../../editor/browser/editorExtensions.js'; import { CopyAction, CutAction, PasteAction } from '../../../../editor/contrib/clipboard/browser/clipboard.js'; @@ -287,14 +286,7 @@ Registry.as(ConfigurationExtensions.Configuration) included: false, policy: { name: 'ExtensionGalleryServiceUrl', - category: PolicyCategory.Extensions, minimumVersion: '1.99', - localization: { - description: { - key: 'extensions.gallery.serviceUrl', - value: localize('extensions.gallery.serviceUrl', "Configure the Marketplace service URL to connect to"), - } - } }, }, 'extensions.supportNodeGlobalNavigator': { diff --git a/src/vs/workbench/contrib/policyExport/common/policyDto.ts b/src/vs/workbench/contrib/policyExport/common/policyDto.ts deleted file mode 100644 index 0408c74fc6e..00000000000 --- a/src/vs/workbench/contrib/policyExport/common/policyDto.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type LocalizedValueDto = { - key: string; - value: string; -}; - -export interface CategoryDto { - key: string; - name: LocalizedValueDto; -} - -export interface PolicyDto { - key: string; - name: string; - category: string; - minimumVersion: `${number}.${number}`; - localization: { - description: LocalizedValueDto; - enumDescriptions?: LocalizedValueDto[]; - }; - type?: string | string[]; - default?: unknown; - enum?: string[]; -} - -export interface ExportedPolicyDataDto { - categories: CategoryDto[]; - policies: PolicyDto[]; -} diff --git a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts deleted file mode 100644 index 51a0f8b42e1..00000000000 --- a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { INativeEnvironmentService } from '../../../../platform/environment/common/environment.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; -import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; -import { URI } from '../../../../base/common/uri.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { PolicyCategory, PolicyCategoryData } from '../../../../base/common/policy.js'; -import { ExportedPolicyDataDto } from '../common/policyDto.js'; -import { join } from '../../../../base/common/path.js'; - -export class PolicyExportContribution extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.policyExport'; - static readonly DEFAULT_POLICY_EXPORT_PATH = 'build/lib/policies/policyData.jsonc'; - - constructor( - @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, - @IExtensionService private readonly extensionService: IExtensionService, - @IFileService private readonly fileService: IFileService, - @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, - @INativeHostService private readonly nativeHostService: INativeHostService, - @IProgressService private readonly progressService: IProgressService, - @ILogService private readonly logService: ILogService, - ) { - super(); - - // Skip for non-development flows - if (this.nativeEnvironmentService.isBuilt) { - return; - } - - const policyDataPath = this.nativeEnvironmentService.exportPolicyData; - if (policyDataPath !== undefined) { - const defaultPath = join(this.nativeEnvironmentService.appRoot, PolicyExportContribution.DEFAULT_POLICY_EXPORT_PATH); - void this.exportPolicyDataAndQuit(policyDataPath ? policyDataPath : defaultPath); - } - } - - private log(msg: string | undefined, ...args: unknown[]) { - this.logService.info(`[${PolicyExportContribution.ID}]`, msg, ...args); - } - - private async exportPolicyDataAndQuit(policyDataPath: string): Promise { - try { - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: `Exporting policy data to ${policyDataPath}` - }, async (_progress) => { - this.log('Export started. Waiting for configurations to load.'); - await this.extensionService.whenInstalledExtensionsRegistered(); - await this.configurationService.whenRemoteConfigurationLoaded(); - - this.log('Extensions and configuration loaded.'); - const configurationRegistry = Registry.as(Extensions.Configuration); - const configurationProperties = { - ...configurationRegistry.getExcludedConfigurationProperties(), - ...configurationRegistry.getConfigurationProperties(), - }; - - const policyData: ExportedPolicyDataDto = { - categories: Object.values(PolicyCategory).map(category => ({ - key: category, - name: PolicyCategoryData[category].name - })), - policies: [] - }; - - for (const [key, schema] of Object.entries(configurationProperties)) { - // Check for the localization property for now to remain backwards compatible. - if (schema.policy?.localization) { - policyData.policies.push({ - key, - name: schema.policy.name, - category: schema.policy.category, - minimumVersion: schema.policy.minimumVersion, - localization: { - description: schema.policy.localization.description, - enumDescriptions: schema.policy.localization.enumDescriptions, - }, - type: schema.type, - default: schema.default, - enum: schema.enum, - }); - } - } - this.log(`Discovered ${policyData.policies.length} policies to export.`); - - const disclaimerComment = `/** THIS FILE IS AUTOMATICALLY GENERATED USING \`code --export-policy-data\`. DO NOT MODIFY IT MANUALLY. **/`; - const policyDataFileContent = `${disclaimerComment}\n${JSON.stringify(policyData, null, 4)}\n`; - await this.fileService.writeFile(URI.file(policyDataPath), VSBuffer.fromString(policyDataFileContent)); - this.log(`Successfully exported ${policyData.policies.length} policies to ${policyDataPath}.`); - }); - - await this.nativeHostService.exit(0); - } catch (error) { - this.log('Failed to export policy', error); - await this.nativeHostService.exit(1); - } - } -} - -registerWorkbenchContribution2( - PolicyExportContribution.ID, - PolicyExportContribution, - WorkbenchPhase.Eventually, -); diff --git a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts deleted file mode 100644 index 725455533c0..00000000000 --- a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as cp from 'child_process'; -import { promises as fs } from 'fs'; -import * as os from 'os'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { isWindows } from '../../../../../base/common/platform.js'; -import { join } from '../../../../../base/common/path.js'; -import { FileAccess } from '../../../../../base/common/network.js'; -import * as util from 'util'; - -const exec = util.promisify(cp.exec); - -suite('PolicyExport Integration Tests', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('exported policy data matches checked-in file', async function () { - // This test launches VS Code with --export-policy-data flag, so it takes longer - this.timeout(60000); - - const rootPath = FileAccess.asFileUri('').fsPath.replace(/[\/\\]out[\/\\].*$/, ''); - const checkedInFile = join(rootPath, 'build/lib/policies/policyData.jsonc'); - const tempFile = join(os.tmpdir(), `policyData-test-${Date.now()}.jsonc`); - - try { - // Launch VS Code with --export-policy-data flag - const scriptPath = isWindows - ? join(rootPath, 'scripts', 'code.bat') - : join(rootPath, 'scripts', 'code.sh'); - - await exec(`"${scriptPath}" --export-policy-data="${tempFile}"`, { - cwd: rootPath - }); - - // Read both files - const [exportedContent, checkedInContent] = await Promise.all([ - fs.readFile(tempFile, 'utf-8'), - fs.readFile(checkedInFile, 'utf-8') - ]); - - // Compare contents - assert.strictEqual( - exportedContent, - checkedInContent, - 'Exported policy data should match the checked-in file. If this fails, run: ./scripts/code.sh --export-policy-data' - ); - } finally { - // Clean up temp file - try { - await fs.unlink(tempFile); - } catch { - // Ignore cleanup errors - } - } - }); -}); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 76f7b87f762..ebd5bd55d8d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -7,7 +7,6 @@ import { Codicon } from '../../../../base/common/codicons.js'; import type { IStringDictionary } from '../../../../base/common/collections.js'; import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; import { localize } from '../../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import product from '../../../../platform/product/common/product.js'; @@ -646,14 +645,7 @@ export async function registerTerminalConfiguration(getFontSnippets: () => Promi default: true, policy: { name: 'ChatToolsTerminalEnableAutoApprove', - category: PolicyCategory.IntegratedTerminal, minimumVersion: '1.104', - localization: { - description: { - key: 'autoApproveMode.description', - value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), - } - } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index 6cb93a3632f..fd6e51c680e 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -47,7 +47,6 @@ import { UserDataProfileService } from '../../../userDataProfile/common/userData import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js'; import { IBrowserWorkbenchEnvironmentService } from '../../../environment/browser/environmentService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -90,9 +89,7 @@ suite('ConfigurationEditing', () => { 'default': 'isSet', policy: { name: 'configurationEditing.service.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index ab268311fd1..ebbda208696 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -52,7 +52,6 @@ import { IUserDataProfileService } from '../../../userDataProfile/common/userDat import { TasksSchemaProperties } from '../../../../contrib/tasks/common/tasks.js'; import { RemoteSocketFactoryService } from '../../../../../platform/remote/common/remoteSocketFactoryService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -782,9 +781,7 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', policy: { name: 'configurationService.folder.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'configurationService.folder.policyObjectSetting': { @@ -792,9 +789,7 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': {}, policy: { name: 'configurationService.folder.policyObjectSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, } diff --git a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts index 795a4c567c8..1e7621472f5 100644 --- a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts @@ -12,7 +12,6 @@ import { Registry } from '../../../../../platform/registry/common/platform.js'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js'; import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -39,9 +38,7 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -49,9 +46,7 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -60,9 +55,7 @@ suite('AccountPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -71,9 +64,7 @@ suite('AccountPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts index cee9adcd499..4484b9d01b2 100644 --- a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts @@ -19,7 +19,6 @@ import { InMemoryFileSystemProvider } from '../../../../../platform/files/common import { FileService } from '../../../../../platform/files/common/fileService.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -48,9 +47,7 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -58,9 +55,7 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -69,9 +64,7 @@ suite('MultiplexPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -80,9 +73,7 @@ suite('MultiplexPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7a17b276d15..c1d9dc494ce 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -182,9 +182,6 @@ import './contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.js // MCP import './contrib/mcp/electron-browser/mcp.contribution.js'; -// Policy Export -import './contrib/policyExport/electron-browser/policyExport.contribution.js'; - //#endregion From c00104761413abfeef89f61c581e0edade663c6b Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 20 Oct 2025 16:54:31 -0700 Subject: [PATCH 1363/4355] Fix tests --- .../common/promptSyntax/service/promptsService.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 5a083a0b891..e07f736b25c 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -6,7 +6,6 @@ import assert from 'assert'; import * as sinon from 'sinon'; import { CancellationToken } from '../../../../../../../base/common/cancellation.js'; -import { Event } from '../../../../../../../base/common/event.js'; import { ResourceSet } from '../../../../../../../base/common/map.js'; import { Schemas } from '../../../../../../../base/common/network.js'; import { URI } from '../../../../../../../base/common/uri.js'; @@ -14,6 +13,9 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../ba import { Range } from '../../../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../../editor/common/services/model.js'; +import { ModelService } from '../../../../../../../editor/common/services/modelService.js'; +import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js'; +import { TestThemeService } from '../../../../../../../platform/theme/test/common/testThemeService.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; @@ -70,7 +72,11 @@ suite('PromptsService', () => { const fileService = disposables.add(instaService.createInstance(FileService)); instaService.stub(IFileService, fileService); - instaService.stub(IModelService, { getModel() { return null; }, onModelRemoved: Event.None }); + + // Set up model service with theme service requirement + instaService.stub(IThemeService, new TestThemeService()); + const modelService = disposables.add(instaService.createInstance(ModelService)); + instaService.stub(IModelService, modelService); instaService.stub(ILanguageService, { guessLanguageIdByFilepathOrFirstLine(uri: URI) { if (uri.path.endsWith(PROMPT_FILE_EXTENSION)) { From 76a9325ab6f45c4845fbe088c8663294d05cef9c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 20 Oct 2025 16:58:57 -0700 Subject: [PATCH 1364/4355] update --- .../browser/chatResponseAccessibleView.ts | 2 +- .../browser/languageModelToolsService.test.ts | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index 544966f98fb..41b1decc2f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -110,7 +110,7 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi } else { const resultDetails = IChatToolInvocation.resultDetails(toolInvocation); if (resultDetails && 'input' in resultDetails) { - responseContent += '\n' + resultDetails.isError ? 'Errored ' : 'Completed '; + responseContent += '\n' + (resultDetails.isError ? 'Errored ' : 'Completed '); responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderAsPlaintext(toolInvocation.invocationMessage))} with input: ${resultDetails.input}`}\n`; } } diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index eec21b24f83..da3683e38eb 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -22,10 +22,11 @@ import { ITelemetryService } from '../../../../../platform/telemetry/common/tele import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { LanguageModelToolsService } from '../../browser/languageModelToolsService.js'; import { IChatModel } from '../../common/chatModel.js'; -import { IChatService, IChatToolInputInvocationData } from '../../common/chatService.js'; +import { IChatService, IChatToolInputInvocationData, IChatToolInvocation, ToolConfirmKind } from '../../common/chatService.js'; import { ChatConfiguration } from '../../common/constants.js'; import { isToolResultInputOutputDetails, IToolData, IToolImpl, IToolInvocation, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { MockChatService } from '../common/mockChatService.js'; +import { ChatToolInvocation } from '../../common/chatProgressTypes/chatToolInvocation.js'; // --- Test helpers to reduce repetition and improve readability --- @@ -86,7 +87,7 @@ function stubGetSession(chatService: MockChatService, sessionId: string, options return fakeModel; } -async function waitForPublishedInvocation(capture: { invocation?: any }, tries = 5): Promise { +async function waitForPublishedInvocation(capture: { invocation?: any }, tries = 5): Promise { for (let i = 0; i < tries && !capture.invocation; i++) { await Promise.resolve(); } @@ -367,7 +368,7 @@ suite('LanguageModelToolsService', () => { const invokeP = service.invokeTool(dto, async () => 0, CancellationToken.None); const published = await waitForPublishedInvocation(capture); - published.confirmed.complete(true); + IChatToolInvocation.confirmWith(published, { type: ToolConfirmKind.UserAction }); const result = await invokeP; assert.strictEqual(result.content[0].value, 'ok'); }); @@ -403,7 +404,7 @@ suite('LanguageModelToolsService', () => { assert.deepStrictEqual(published.toolSpecificData?.rawInput, dto.parameters); // Confirm to let invoke proceed - published.confirmed.complete(true); + IChatToolInvocation.confirmWith(published, { type: ToolConfirmKind.UserAction }); const result = await invokeP; assert.strictEqual(result.content[0].value, 'done'); }); @@ -436,7 +437,7 @@ suite('LanguageModelToolsService', () => { assert.ok(published, 'expected ChatToolInvocation to be published'); assert.strictEqual(invoked, false, 'invoke should not run before confirmation'); - published.confirmed.complete(true); + IChatToolInvocation.confirmWith(published, { type: ToolConfirmKind.UserAction }); const result = await promise; assert.strictEqual(invoked, true, 'invoke should have run after confirmation'); assert.strictEqual(result.content[0].value, 'ran'); @@ -811,7 +812,7 @@ suite('LanguageModelToolsService', () => { assert.ok(signalCall.options?.customAlertMessage.includes('Chat confirmation required'), 'alert message should include confirmation text'); // Complete the invocation - published.confirmed.complete(true); + IChatToolInvocation.confirmWith(published, { type: ToolConfirmKind.UserAction }); const result = await promise; assert.strictEqual(result.content[0].value, 'executed'); }); @@ -944,7 +945,7 @@ suite('LanguageModelToolsService', () => { const published = await waitForPublishedInvocation(capture); assert.ok(published?.confirmationMessages, 'unspecified tool should require confirmation'); - published.confirmed.complete(true); + IChatToolInvocation.confirmWith(published, { type: ToolConfirmKind.UserAction }); const unspecifiedResult = await unspecifiedPromise; assert.strictEqual(unspecifiedResult.content[0].value, 'unspecified'); }); @@ -1118,7 +1119,7 @@ suite('LanguageModelToolsService', () => { const call1 = testAccessibilitySignalService.signalPlayedCalls[0]; assert.strictEqual(call1.options?.modality, undefined, 'should use default modality for sound'); - published1.confirmed.complete(true); + IChatToolInvocation.confirmWith(published1, { type: ToolConfirmKind.UserAction }); await promise1; testAccessibilitySignalService.reset(); @@ -1159,7 +1160,7 @@ suite('LanguageModelToolsService', () => { assert.ok(call2.options?.customAlertMessage, 'should have custom alert message'); assert.strictEqual(call2.options?.userGesture, true, 'should mark as user gesture'); - published2.confirmed.complete(true); + IChatToolInvocation.confirmWith(published2, { type: ToolConfirmKind.UserAction }); await promise2; testAccessibilitySignalService.reset(); @@ -1197,7 +1198,7 @@ suite('LanguageModelToolsService', () => { // No signal should be played assert.strictEqual(testAccessibilitySignalService.signalPlayedCalls.length, 0, 'no signal should be played when both sound and announcement are off'); - published3.confirmed.complete(true); + IChatToolInvocation.confirmWith(published3, { type: ToolConfirmKind.UserAction }); await promise3; }); From 9c6ebcb0eaef3016f0d99c857fb5d0a587cc82ab Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:59:48 -0700 Subject: [PATCH 1365/4355] Convert a few more `new Action` calls to `toAction` `toAction` is preferred as it doesn't require disposal --- src/vs/base/browser/ui/button/button.ts | 6 +- .../api/browser/mainThreadExtensionService.ts | 24 +-- .../browser/parts/titlebar/menubarControl.ts | 36 +++-- .../browser/breakpointEditorContribution.ts | 145 ++++++++---------- .../debug/browser/debugEditorActions.ts | 4 +- .../contrib/debug/browser/debugService.ts | 21 ++- .../contrib/debug/browser/debugTaskRunner.ts | 4 +- .../contrib/debug/browser/variablesView.ts | 10 +- .../contrib/remote/browser/remoteExplorer.ts | 28 ++-- .../remoteTunnel.contribution.ts | 22 ++- 10 files changed, 156 insertions(+), 144 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index c0a8eec0633..1f9ea52453f 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -11,7 +11,7 @@ import { Gesture, EventType as TouchEventType } from '../../touch.js'; import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { renderLabelWithIcons } from '../iconLabel/iconLabels.js'; -import { Action, IAction, IActionRunner } from '../../../common/actions.js'; +import { IAction, IActionRunner, toAction } from '../../../common/actions.js'; import { Codicon } from '../../../common/codicons.js'; import { Color } from '../../../common/color.js'; import { Event as BaseEvent, Emitter } from '../../../common/event.js'; @@ -371,7 +371,7 @@ export interface IButtonWithDropdownOptions extends IButtonOptions { export class ButtonWithDropdown extends Disposable implements IButton { readonly primaryButton: Button; - private readonly action: Action; + private readonly action: IAction; readonly dropdownButton: Button; private readonly separatorContainer: HTMLDivElement; private readonly separator: HTMLDivElement; @@ -393,7 +393,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.primaryButton = this._register(new Button(this.element, options)); this._register(this.primaryButton.onDidClick(e => this._onDidClick.fire(e))); - this.action = this._register(new Action('primaryAction', renderAsPlaintext(this.primaryButton.label), undefined, true, async () => this._onDidClick.fire(undefined))); + this.action = toAction({ id: 'primaryAction', label: renderAsPlaintext(this.primaryButton.label), run: async () => this._onDidClick.fire(undefined) }); this.separatorContainer = document.createElement('div'); this.separatorContainer.classList.add('monaco-button-dropdown-separator'); diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 67dc8c1bbfb..20daca562a4 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from '../../../base/common/actions.js'; +import { toAction } from '../../../base/common/actions.js'; import { VSBuffer } from '../../../base/common/buffer.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { SerializedError, transformErrorFromSerialization } from '../../../base/common/errors.js'; @@ -115,7 +115,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha severity: Severity.Error, message: localize('reload window', "Cannot activate the '{0}' extension because it depends on the '{1}' extension, which is not loaded. Would you like to reload the window to load the extension?", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name), actions: { - primary: [new Action('reload', localize('reload', "Reload Window"), '', true, () => this._hostService.reload())] + primary: [toAction({ id: 'reload', label: localize('reload', "Reload Window"), run: () => this._hostService.reload() })] } }); } else { @@ -130,8 +130,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha severity: Severity.Error, message: localize('restrictedMode', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is not supported in Restricted Mode", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name), actions: { - primary: [new Action('manageWorkspaceTrust', localize('manageWorkspaceTrust', "Manage Workspace Trust"), '', true, - () => this._commandService.executeCommand('workbench.trust.manage'))] + primary: [toAction({ id: 'manageWorkspaceTrust', label: localize('manageWorkspaceTrust', "Manage Workspace Trust"), run: () => this._commandService.executeCommand('workbench.trust.manage') })] } }); } else if (this._extensionEnablementService.canChangeEnablement(missingInstalledDependency)) { @@ -139,9 +138,11 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha severity: Severity.Error, message: localize('disabledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is disabled. Would you like to enable the extension and reload the window?", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name), actions: { - primary: [new Action('enable', localize('enable dep', "Enable and Reload"), '', true, - () => this._extensionEnablementService.setEnablement([missingInstalledDependency], enablementState === EnablementState.DisabledGlobally ? EnablementState.EnabledGlobally : EnablementState.EnabledWorkspace) - .then(() => this._hostService.reload(), e => this._notificationService.error(e)))] + primary: [toAction({ + id: 'enable', label: localize('enable dep', "Enable and Reload"), enabled: true, + run: () => this._extensionEnablementService.setEnablement([missingInstalledDependency], enablementState === EnablementState.DisabledGlobally ? EnablementState.EnabledGlobally : EnablementState.EnabledWorkspace) + .then(() => this._hostService.reload(), e => this._notificationService.error(e)) + })] } }); } else { @@ -165,9 +166,12 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha severity: Severity.Error, message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension from '{2}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName, dependencyExtension.publisherDisplayName), actions: { - primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true, - () => this._extensionsWorkbenchService.install(dependencyExtension) - .then(() => this._hostService.reload(), e => this._notificationService.error(e)))] + primary: [toAction({ + id: 'install', + label: localize('install missing dep', "Install and Reload"), + run: () => this._extensionsWorkbenchService.install(dependencyExtension) + .then(() => this._hostService.reload(), e => this._notificationService.error(e)) + })] } }); } else { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 07e61ab30e0..07137d30ebb 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -463,29 +463,37 @@ export class CustomMenubarControl extends MenubarControl { switch (state.type) { case StateType.Idle: - return new Action('update.check', localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => - this.updateService.checkForUpdates(true)); + return toAction({ + id: 'update.check', label: localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), enabled: true, run: () => + this.updateService.checkForUpdates(true) + }); case StateType.CheckingForUpdates: - return new Action('update.checking', localize('checkingForUpdates', "Checking for Updates..."), undefined, false); + return toAction({ id: 'update.checking', label: localize('checkingForUpdates', "Checking for Updates..."), enabled: false, run: () => { } }); case StateType.AvailableForDownload: - return new Action('update.downloadNow', localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), undefined, true, () => - this.updateService.downloadUpdate()); + return toAction({ + id: 'update.downloadNow', label: localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), enabled: true, run: () => + this.updateService.downloadUpdate() + }); case StateType.Downloading: - return new Action('update.downloading', localize('DownloadingUpdate', "Downloading Update..."), undefined, false); + return toAction({ id: 'update.downloading', label: localize('DownloadingUpdate', "Downloading Update..."), enabled: false, run: () => { } }); case StateType.Downloaded: - return isMacintosh ? null : new Action('update.install', localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () => - this.updateService.applyUpdate()); + return isMacintosh ? null : toAction({ + id: 'update.install', label: localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), enabled: true, run: () => + this.updateService.applyUpdate() + }); case StateType.Updating: - return new Action('update.updating', localize('installingUpdate', "Installing Update..."), undefined, false); + return toAction({ id: 'update.updating', label: localize('installingUpdate', "Installing Update..."), enabled: false, run: () => { } }); case StateType.Ready: - return new Action('update.restart', localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), undefined, true, () => - this.updateService.quitAndInstall()); + return toAction({ + id: 'update.restart', label: localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), enabled: true, run: () => + this.updateService.quitAndInstall() + }); default: return null; @@ -721,8 +729,10 @@ export class CustomMenubarControl extends MenubarControl { const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.mnemonicTitle ?? action.item.title.value; - webNavigationActions.push(new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, async (event?: unknown) => { - this.commandService.executeCommand(action.id, event); + webNavigationActions.push(toAction({ + id: action.id, label: mnemonicMenuLabel(title), class: action.class, enabled: action.enabled, run: async (event?: unknown) => { + this.commandService.executeCommand(action.id, event); + } })); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index a8f46c2e44b..aac4fe7083e 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -7,7 +7,7 @@ import { isSafari } from '../../../../base/browser/browser.js'; import { BrowserFeatures } from '../../../../base/browser/canIUse.js'; import * as dom from '../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; -import { Action, IAction, Separator, SubmenuAction } from '../../../../base/common/actions.js'; +import { IAction, Separator, SubmenuAction, toAction } from '../../../../base/common/actions.js'; import { distinct } from '../../../../base/common/arrays.js'; import { RunOnceScheduler, timeout } from '../../../../base/common/async.js'; import { memoize } from '../../../../base/common/decorators.js'; @@ -406,94 +406,79 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (breakpoints.length === 1) { const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); - actions.push(new Action('debug.removeBreakpoint', nls.localize('removeBreakpoint', "Remove {0}", breakpointType), undefined, true, async () => { - await this.debugService.removeBreakpoints(breakpoints[0].getId()); + actions.push(toAction({ + id: 'debug.removeBreakpoint', label: nls.localize('removeBreakpoint', "Remove {0}", breakpointType), enabled: true, run: async () => { + await this.debugService.removeBreakpoints(breakpoints[0].getId()); + } + })); + actions.push(toAction({ + id: 'workbench.debug.action.editBreakpointAction', + label: nls.localize('editBreakpoint', "Edit {0}...", breakpointType), + enabled: true, + run: () => Promise.resolve(this.showBreakpointWidget(breakpoints[0].lineNumber, breakpoints[0].column)) + })); actions.push(toAction({ + id: `workbench.debug.viewlet.action.toggleBreakpoint`, + label: breakpoints[0].enabled ? nls.localize('disableBreakpoint', "Disable {0}", breakpointType) : nls.localize('enableBreakpoint', "Enable {0}", breakpointType), + enabled: true, + run: () => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0]) })); - actions.push(new Action( - 'workbench.debug.action.editBreakpointAction', - nls.localize('editBreakpoint', "Edit {0}...", breakpointType), - undefined, - true, - () => Promise.resolve(this.showBreakpointWidget(breakpoints[0].lineNumber, breakpoints[0].column)) - )); - - actions.push(new Action( - `workbench.debug.viewlet.action.toggleBreakpoint`, - breakpoints[0].enabled ? nls.localize('disableBreakpoint', "Disable {0}", breakpointType) : nls.localize('enableBreakpoint', "Enable {0}", breakpointType), - undefined, - true, - () => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0]) - )); } else if (breakpoints.length > 1) { const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1); - actions.push(new SubmenuAction('debug.removeBreakpoints', nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( - 'removeInlineBreakpoint', - bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), - undefined, - true, - () => this.debugService.removeBreakpoints(bp.getId()) - )))); - - actions.push(new SubmenuAction('debug.editBreakpoints', nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => - new Action('editBreakpoint', - bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBreakpoint', "Edit Line Breakpoint"), - undefined, - true, - () => Promise.resolve(this.showBreakpointWidget(bp.lineNumber, bp.column)) - ) - ))); - - actions.push(new SubmenuAction('debug.enableDisableBreakpoints', nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action( - bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', - bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) + actions.push(new SubmenuAction('debug.removeBreakpoints', nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => toAction({ + id: 'removeInlineBreakpoint', + label: bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), + enabled: true, + run: () => this.debugService.removeBreakpoints(bp.getId()) + })))); actions.push(new SubmenuAction('debug.editBreakpoints', nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => + toAction({ + id: 'editBreakpoint', + label: bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBreakpoint', "Edit Line Breakpoint"), + enabled: true, + run: () => Promise.resolve(this.showBreakpointWidget(bp.lineNumber, bp.column)) + }) + ))); actions.push(new SubmenuAction('debug.enableDisableBreakpoints', nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => toAction({ + id: bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', + label: bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) : (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")), - undefined, - true, - () => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp) - )))); + enabled: true, + run: () => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp) + })))); } else { - actions.push(new Action( - 'addBreakpoint', - nls.localize('addBreakpoint', "Add Breakpoint"), - undefined, - true, - () => this.debugService.addBreakpoints(uri, [{ lineNumber, column }]) - )); - actions.push(new Action( - 'addConditionalBreakpoint', - nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), - undefined, - true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.CONDITION)) - )); - actions.push(new Action( - 'addLogPoint', - nls.localize('addLogPoint', "Add Logpoint..."), - undefined, - true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) - )); - actions.push(new Action( - 'addTriggeredBreakpoint', - nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint..."), - undefined, - true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) - )); + actions.push(toAction({ + id: 'addBreakpoint', + label: nls.localize('addBreakpoint', "Add Breakpoint"), + enabled: true, + run: () => this.debugService.addBreakpoints(uri, [{ lineNumber, column }]) + })); + actions.push(toAction({ + id: 'addConditionalBreakpoint', + label: nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), + enabled: true, + run: () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.CONDITION)) + })); + actions.push(toAction({ + id: 'addLogPoint', + label: nls.localize('addLogPoint', "Add Logpoint..."), + enabled: true, + run: () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) + })); + actions.push(toAction({ + id: 'addTriggeredBreakpoint', + label: nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint..."), + enabled: true, + run: () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT)) + })); } if (this.debugService.state === State.Stopped) { actions.push(new Separator()); - actions.push(new Action( - 'runToLine', - nls.localize('runToLine', "Run to Line"), - undefined, - true, - () => this.debugService.runTo(uri, lineNumber).catch(onUnexpectedError) - )); - } - - return actions; + actions.push(toAction({ + id: 'runToLine', + label: nls.localize('runToLine', "Run to Line"), + enabled: true, + run: () => this.debugService.runTo(uri, lineNumber).catch(onUnexpectedError) + })); + } return actions; } private marginFreeFromNonDebugDecorations(line: number): boolean { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 6edaa7cb866..e196fe59530 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { getDomNodePagePosition } from '../../../../base/browser/dom.js'; -import { Action } from '../../../../base/common/actions.js'; +import { toAction } from '../../../../base/common/actions.js'; import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { EditorAction, IActionOptions, registerEditorAction } from '../../../../editor/browser/editorExtensions.js'; @@ -542,7 +542,7 @@ class StepIntoTargetsAction extends EditorAction { contextMenuService.showContextMenu({ getAnchor: () => ({ x, y }), getActions: () => { - return targets.map(t => new Action(`stepIntoTarget:${t.id}`, t.label, undefined, true, () => session.stepIn(frame.thread.threadId, t.id))); + return targets.map(t => toAction({ id: `stepIntoTarget:${t.id}`, label: t.label, enabled: true, run: () => session.stepIn(frame.thread.threadId, t.id) })); } }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index de94395e9a6..e76162ba639 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as aria from '../../../../base/browser/ui/aria/aria.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { IAction, toAction } from '../../../../base/common/actions.js'; import { distinct } from '../../../../base/common/arrays.js'; import { RunOnceScheduler, raceTimeout } from '../../../../base/common/async.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; @@ -572,17 +572,14 @@ export class DebugService implements IDebugService { const actionList: IAction[] = []; - actionList.push(new Action( - 'installAdditionalDebuggers', - nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type), - undefined, - true, - async () => this.commandService.executeCommand('debug.installAdditionalDebuggers', resolvedConfig?.type) - )); + actionList.push(toAction({ + id: 'installAdditionalDebuggers', + label: nls.localize({ key: 'installAdditionalDebuggers', comment: ['Placeholder is the debug type, so for example "node", "python"'] }, "Install {0} Extension", resolvedConfig.type), + enabled: true, + run: async () => this.commandService.executeCommand('debug.installAdditionalDebuggers', resolvedConfig?.type) + })); - await this.showError(message, actionList); - - return false; + await this.showError(message, actionList); return false; } if (!dbg.enabled) { @@ -1001,7 +998,7 @@ export class DebugService implements IDebugService { } private async showError(message: string, errorActions: ReadonlyArray = [], promptLaunchJson = true): Promise { - const configureAction = new Action(DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, undefined, true, () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID)); + const configureAction = toAction({ id: DEBUG_CONFIGURE_COMMAND_ID, label: DEBUG_CONFIGURE_LABEL, enabled: true, run: () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID) }); // Don't append the standard command if id of any provided action indicates it is a command const actions = errorActions.filter((action) => action.id.endsWith('.command')).length > 0 ? errorActions : diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 67e1f1bdb8f..9374887badd 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from '../../../../base/common/actions.js'; +import { toAction } from '../../../../base/common/actions.js'; import { disposableTimeout } from '../../../../base/common/async.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { createErrorWithActions } from '../../../../base/common/errorMessage.js'; @@ -204,7 +204,7 @@ export class DebugTaskRunner implements IDisposable { const errorMessage = typeof taskId === 'string' ? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId) : nls.localize('DebugTaskNotFound', "Could not find the specified task."); - return Promise.reject(createErrorWithActions(errorMessage, [new Action(DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, undefined, true, () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID))])); + return Promise.reject(createErrorWithActions(errorMessage, [toAction({ id: DEBUG_CONFIGURE_COMMAND_ID, label: DEBUG_CONFIGURE_LABEL, enabled: true, run: () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID) })])); } // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 2130f6831f5..2d5079adedb 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -10,7 +10,7 @@ import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { AsyncDataTree, IAsyncDataTreeViewState } from '../../../../base/browser/ui/tree/asyncDataTree.js'; import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { IAction, toAction } from '../../../../base/common/actions.js'; import { coalesce } from '../../../../base/common/arrays.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; @@ -506,7 +506,9 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { const { primary } = getContextMenuActions(menu, 'inline'); if (viz.original) { - const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); + const action = toAction({ + id: 'debugViz', label: localize('removeVisualizer', 'Remove Visualizer'), class: ThemeIcon.asClassName(Codicon.eye), run: () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined) + }); action.checked = true; primary.push(action); actionBar.domNode.style.display = 'initial'; @@ -592,13 +594,13 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { data.elementDisposable.add(result); const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression; - const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, originalExpression, cts.token))); + const actions = result.object.map(v => toAction({ id: 'debugViz', label: v.name, class: v.iconClass || 'debug-viz-icon', run: this.useVisualizer(v, originalExpression, cts.token) })); if (actions.length === 0) { // no-op } else if (actions.length === 1) { actionBar.push(actions[0], { icon: true, label: false }); } else { - actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, originalExpression, data)), { icon: true, label: false }); + actionBar.push(toAction({ id: 'debugViz', label: localize('useVisualizer', 'Visualize Variable...'), class: ThemeIcon.asClassName(Codicon.eye), run: () => this.pickVisualizer(actions, originalExpression, data) }), { icon: true, label: false }); } }); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 480feea12ad..7adab723887 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -34,7 +34,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '. import { ILogService } from '../../../../platform/log/common/log.js'; import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; import { IRemoteAgentEnvironment } from '../../../../platform/remote/common/remoteAgentEnvironment.js'; -import { Action } from '../../../../base/common/actions.js'; +import { toAction } from '../../../../base/common/actions.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { IStorageService, StorageScope } from '../../../../platform/storage/common/storage.js'; @@ -273,16 +273,24 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon severity: Severity.Warning, actions: { primary: [ - new Action('switchBack', nls.localize('remote.autoForwardPortsSource.fallback.switchBack', "Undo"), undefined, true, async () => { - await this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_PROCESS); - await this.configurationService.updateValue(PORT_AUTO_FALLBACK_SETTING, 0, ConfigurationTarget.WORKSPACE); - this.portListener?.dispose(); - this.portListener = undefined; + toAction({ + id: 'switchBack', + label: nls.localize('remote.autoForwardPortsSource.fallback.switchBack', "Undo"), + run: async () => { + await this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_PROCESS); + await this.configurationService.updateValue(PORT_AUTO_FALLBACK_SETTING, 0, ConfigurationTarget.WORKSPACE); + this.portListener?.dispose(); + this.portListener = undefined; + } }), - new Action('showPortSourceSetting', nls.localize('remote.autoForwardPortsSource.fallback.showPortSourceSetting', "Show Setting"), undefined, true, async () => { - await this.preferencesService.openSettings({ - query: 'remote.autoForwardPortsSource' - }); + toAction({ + id: 'showPortSourceSetting', + label: nls.localize('remote.autoForwardPortsSource.fallback.showPortSourceSetting', "Show Setting"), + run: async () => { + await this.preferencesService.openSettings({ + query: 'remote.autoForwardPortsSource' + }); + } }) ] } diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts index 5de04851541..0eaa650b9c9 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from '../../../../base/common/actions.js'; +import { toAction } from '../../../../base/common/actions.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { ITunnelApplicationConfig } from '../../../../base/common/product.js'; @@ -200,11 +200,15 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo ), actions: { primary: [ - new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { - return this.commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', [remoteExtension.extensionId]); + toAction({ + id: 'showExtension', label: localize('action.showExtension', "Show Extension"), run: () => { + return this.commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', [remoteExtension.extensionId]); + } }), - new Action('doNotShowAgain', localize('action.doNotShowAgain', "Do not show again"), undefined, true, () => { - this.storageService.store(REMOTE_TUNNEL_EXTENSION_RECOMMENDED_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); + toAction({ + id: 'doNotShowAgain', label: localize('action.doNotShowAgain', "Do not show again"), run: () => { + this.storageService.store(REMOTE_TUNNEL_EXTENSION_RECOMMENDED_KEY, true, StorageScope.APPLICATION, StorageTarget.USER); + } }), ] } @@ -564,9 +568,11 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo ), actions: { primary: [ - new Action('copyToClipboard', localize('action.copyToClipboard', "Copy Browser Link to Clipboard"), undefined, true, () => clipboardService.writeText(linkToOpen.toString(true))), - new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { - return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', [remoteExtension.extensionId]); + toAction({ id: 'copyToClipboard', label: localize('action.copyToClipboard', "Copy Browser Link to Clipboard"), run: () => clipboardService.writeText(linkToOpen.toString(true)) }), + toAction({ + id: 'showExtension', label: localize('action.showExtension', "Show Extension"), run: () => { + return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', [remoteExtension.extensionId]); + } }) ] } From 75e1682c9ab0d8ece6a75882eabcee3cabdd3d3b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:11:10 -0700 Subject: [PATCH 1366/4355] Remove MCP Server and Extension prefixes from tool picker categories (#271572) * Initial plan * Remove MCP Server and Extension prefixes from tool picker categories Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> Co-authored-by: Harald Kirschner --- .../workbench/contrib/chat/browser/actions/chatToolPicker.ts | 4 ++-- .../contrib/chat/common/languageModelToolsService.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 25ec851b2bc..db65a8d09bc 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -311,7 +311,7 @@ export async function showToolsPicker( itemType: 'bucket', ordinal: BucketOrdinal.Mcp, id: key, - label: localize('mcplabel', "MCP Server: {0}", source.label), + label: source.label, checked: undefined, collapsed, children, @@ -329,7 +329,7 @@ export async function showToolsPicker( itemType: 'bucket', ordinal: BucketOrdinal.Extension, id: key, - label: localize('ext', 'Extension: {0}', source.label), + label: source.label, checked: undefined, children: [], buttons: [], diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 45a19c5b828..134a4228516 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -107,11 +107,11 @@ export namespace ToolDataSource { if (source.type === 'internal') { return { ordinal: 1, label: localize('builtin', 'Built-In') }; } else if (source.type === 'mcp') { - return { ordinal: 2, label: localize('mcp', 'MCP Server: {0}', source.label) }; + return { ordinal: 2, label: source.label }; } else if (source.type === 'user') { return { ordinal: 0, label: localize('user', 'User Defined') }; } else { - return { ordinal: 3, label: localize('ext', 'Extension: {0}', source.label) }; + return { ordinal: 3, label: source.label }; } } } From 1d337001f1d167169d635f2e607a18760b462b38 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:15:13 -0700 Subject: [PATCH 1367/4355] Allow custom editors to change title Fixes #105299 Allows custom editors to set a title using `WebviewPanel.title`. This matches how webviews work today --- .../api/browser/mainThreadCustomEditors.ts | 1 + .../customEditor/browser/customEditorInput.ts | 40 +++++++++++++++---- .../browser/customEditorInputFactory.ts | 20 ++++++++-- .../customEditor/browser/customEditors.ts | 10 ++--- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 5e9387f7a88..bad7e038dc7 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -670,6 +670,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom const backupMeta: CustomDocumentBackupData = { viewType: this.viewType, editorResource: this._editorResource, + customTitle: primaryEditor.getCustomTitle(), backupId: '', extension: primaryEditor.extension ? { id: primaryEditor.extension.id.value, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index ee708d0ddda..4428b5cc8aa 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -36,29 +36,29 @@ import { IUntitledTextEditorService } from '../../../services/untitled/common/un interface CustomEditorInputInitInfo { readonly resource: URI; readonly viewType: string; + readonly customTitle: string | undefined; } export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { static create( instantiationService: IInstantiationService, - resource: URI, - viewType: string, + init: CustomEditorInputInitInfo, group: GroupIdentifier | undefined, options?: { readonly customClasses?: string; readonly oldResource?: URI }, ): EditorInput { return instantiationService.invokeFunction(accessor => { // If it's an untitled file we must populate the untitledDocumentData - const untitledString = accessor.get(IUntitledTextEditorService).getValue(resource); + const untitledString = accessor.get(IUntitledTextEditorService).getValue(init.resource); const untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; const webview = accessor.get(IWebviewService).createWebviewOverlay({ - providedViewType: viewType, - title: undefined, + providedViewType: init.viewType, + title: init.customTitle, options: { customClasses: options?.customClasses }, contentOptions: {}, extension: undefined, }); - const input = instantiationService.createInstance(CustomEditorInput, { resource, viewType }, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); + const input = instantiationService.createInstance(CustomEditorInput, init, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); if (typeof group !== 'undefined') { input.updateGroup(group); } @@ -72,6 +72,9 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public readonly oldResource?: URI; private _defaultDirtyState: boolean | undefined; + private _editorName: string | undefined = undefined; + private _customTitle: string | undefined = undefined; + private readonly _backupId: string | undefined; private readonly _untitledDocumentData: VSBuffer | undefined; @@ -103,6 +106,8 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._backupId = options.backupId; this._untitledDocumentData = options.untitledDocumentData; + this._customTitle = init.customTitle; + this.registerListeners(); } @@ -171,8 +176,11 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return capabilities; } - private _editorName: string | undefined = undefined; override getName(): string { + if (this._customTitle) { + return this._customTitle; + } + if (typeof this._editorName !== 'string') { this._editorName = this.customEditorLabelService.getName(this.resource) ?? basename(this.labelService.getUriLabel(this.resource)); } @@ -180,6 +188,15 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return this._editorName; } + override setName(value: string): void { + this._customTitle = value; + super.setName(value); + } + + getCustomTitle(): string | undefined { + return this._customTitle; + } + override getDescription(verbosity = Verbosity.MEDIUM): string | undefined { switch (verbosity) { case Verbosity.SHORT: @@ -247,6 +264,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } override getTitle(verbosity?: Verbosity): string { + if (this._customTitle) { + return this._customTitle; + } + switch (verbosity) { case Verbosity.SHORT: return this.shortTitle; @@ -268,7 +289,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public override copy(): EditorInput { - return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this.webview.options); + return CustomEditorInput.create(this.instantiationService, + { resource: this.resource, viewType: this.viewType, customTitle: this._customTitle }, + this.group, + this.webview.options); } public override isReadonly(): boolean | IMarkdownString { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index c8be107e8a2..5f3778e641a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -23,6 +23,9 @@ import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../s export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta { readonly viewType: string; readonly editorResource: UriComponents; + + readonly customTitle: string | undefined; + backupId: string; readonly extension: undefined | { @@ -92,7 +95,11 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { const data = this.fromJson(JSON.parse(serializedEditorInput)); const webview = reviveWebview(this._webviewService, data); - const customInput = this._instantiationService.createInstance(CustomEditorInput, { resource: data.editorResource, viewType: data.viewType }, webview, { startsDirty: data.dirty, backupId: data.backupId }); + const customInput = this._instantiationService.createInstance(CustomEditorInput, { + resource: data.editorResource, + viewType: data.viewType, + customTitle: data.title, + }, webview, { startsDirty: data.dirty, backupId: data.backupId }); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } @@ -100,11 +107,11 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } } -function reviveWebview(webviewService: IWebviewService, data: { origin: string | undefined; viewType: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { +function reviveWebview(webviewService: IWebviewService, data: { origin: string | undefined; viewType: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription; title: string | undefined }) { const webview = webviewService.createWebviewOverlay({ providedViewType: data.viewType, origin: data.origin, - title: undefined, + title: data.title, options: { purpose: WebviewContentPurpose.CustomEditor, enableFindWidget: data.webviewOptions.enableFindWidget, @@ -185,9 +192,14 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements contentOptions: restoreWebviewContentOptions(backupData.webview.options), state: backupData.webview.state, extension, + title: backupData.customTitle, }); - const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType }, webview, { backupId: backupData.backupId }); + const editor = this._instantiationService.createInstance(CustomEditorInput, { + resource: URI.revive(backupData.editorResource), + viewType: backupData.viewType, + customTitle: backupData.customTitle, + }, webview, { backupId: backupData.backupId }); editor.updateGroup(0); return editor; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index e4113a87326..8e99799abe5 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -138,10 +138,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }, { createEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource, viewType: contributedEditor.id, customTitle: undefined }, group.id) }; }, createUntitledEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), contributedEditor.id, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource: resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), viewType: contributedEditor.id, customTitle: undefined }, group.id) }; }, createDiffEditorInput: (diffEditorInput, group) => { return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; @@ -157,8 +157,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ editorID: string, group: IEditorGroup ): DiffEditorInput { - const modifiedOverride = CustomEditorInput.create(this.instantiationService, assertReturnsDefined(editor.modified.resource), editorID, group.id, { customClasses: 'modified' }); - const originalOverride = CustomEditorInput.create(this.instantiationService, assertReturnsDefined(editor.original.resource), editorID, group.id, { customClasses: 'original' }); + const modifiedOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.modified.resource), viewType: editorID, customTitle: undefined }, group.id, { customClasses: 'modified' }); + const originalOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.original.resource), viewType: editorID, customTitle: undefined }, group.id, { customClasses: 'original' }); return this.instantiationService.createInstance(DiffEditorInput, editor.label, editor.description, originalOverride, modifiedOverride, true); } @@ -259,7 +259,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: EditorInput | IResourceEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType, group); + replacement = CustomEditorInput.create(this.instantiationService, { resource: newResource, viewType, customTitle: undefined }, group); } else { replacement = { resource: newResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; } From 9500b5a3191b0147ceeecf3036d00180a33db3a1 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:01:52 -0700 Subject: [PATCH 1368/4355] Allow custom editors to set custom icons Fixes #105028 Enables icons for custom editors if set by extensions. Also removes the `WebviewIconManager` we were using to support icons previously to move to a more standard approach Still needs some styling fixes but seems to work ok --- .../api/browser/mainThreadCustomEditors.ts | 1 + .../api/browser/mainThreadWebviewPanels.ts | 5 +- src/vs/workbench/common/editor/editorInput.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 8 +- .../browser/customEditorInputFactory.ts | 5 ++ .../customEditor/browser/customEditors.ts | 10 +-- .../search/browser/anythingQuickAccess.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 1 + .../browser/webviewEditorInput.ts | 28 ++++++- .../browser/webviewEditorInputSerializer.ts | 3 +- .../browser/webviewIconManager.ts | 80 ------------------- .../browser/webviewWorkbenchService.ts | 39 ++++----- 12 files changed, 63 insertions(+), 121 deletions(-) delete mode 100644 src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index bad7e038dc7..c31db977685 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -671,6 +671,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom viewType: this.viewType, editorResource: this._editorResource, customTitle: primaryEditor.getCustomTitle(), + iconPath: primaryEditor.iconPath, backupId: '', extension: primaryEditor.extension ? { id: primaryEditor.extension.id.value, diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 7c0c51e08c8..b085bb73a77 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -13,8 +13,7 @@ import { IStorageService } from '../../../platform/storage/common/storage.js'; import { DiffEditorInput } from '../../common/editor/diffEditorInput.js'; import { EditorInput } from '../../common/editor/editorInput.js'; import { ExtensionKeyedWebviewOriginStore, WebviewOptions } from '../../contrib/webview/browser/webview.js'; -import { WebviewInput } from '../../contrib/webviewPanel/browser/webviewEditorInput.js'; -import { WebviewIcons } from '../../contrib/webviewPanel/browser/webviewIconManager.js'; +import { WebviewIcons, WebviewInput } from '../../contrib/webviewPanel/browser/webviewEditorInput.js'; import { IWebViewShowOptions, IWebviewWorkbenchService } from '../../contrib/webviewPanel/browser/webviewWorkbenchService.js'; import { editorGroupToColumn } from '../../services/editor/common/editorGroupColumn.js'; import { GroupLocation, GroupsOrder, IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from '../../services/editor/common/editorGroupsService.js'; @@ -171,7 +170,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc options: reviveWebviewOptions(initData.panelOptions), contentOptions: reviveWebviewContentOptions(initData.webviewOptions), extension - }, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions); + }, this.webviewPanelViewType.fromExternal(viewType), initData.title, undefined, mainThreadShowOptions); this.addWebviewInput(handle, webview, { serializeBuffersForPostMessage: initData.serializeBuffersForPostMessage }); } diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index d8a61df5058..df428b66fc3 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -183,7 +183,7 @@ export abstract class EditorInput extends AbstractEditorInput { * Returns the icon which represents this editor input. * If undefined, the default icon will be used. */ - getIcon(): ThemeIcon | undefined { + getIcon(): ThemeIcon | URI | undefined { return undefined; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 4428b5cc8aa..5317ef95d91 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -32,11 +32,14 @@ import { IEditorGroupsService } from '../../../services/editor/common/editorGrou import { IFilesConfigurationService } from '../../../services/filesConfiguration/common/filesConfigurationService.js'; import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IUntitledTextEditorService } from '../../../services/untitled/common/untitledTextEditorService.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { WebviewIcons } from '../../webviewPanel/browser/webviewEditorInput.js'; interface CustomEditorInputInitInfo { readonly resource: URI; readonly viewType: string; readonly customTitle: string | undefined; + readonly iconPath: WebviewIcons | undefined; } export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @@ -87,6 +90,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { init: CustomEditorInputInitInfo, webview: IOverlayWebview, options: { startsDirty?: boolean; backupId?: string; untitledDocumentData?: VSBuffer; readonly oldResource?: URI }, + @IThemeService themeService: IThemeService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @@ -99,7 +103,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ICustomEditorLabelService private readonly customEditorLabelService: ICustomEditorLabelService, ) { - super({ providedId: init.viewType, viewType: init.viewType, name: '' }, webview, webviewWorkbenchService); + super({ providedId: init.viewType, viewType: init.viewType, name: '', iconPath: init.iconPath }, webview, themeService, webviewWorkbenchService); this._editorResource = init.resource; this.oldResource = options.oldResource; this._defaultDirtyState = options.startsDirty; @@ -290,7 +294,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public override copy(): EditorInput { return CustomEditorInput.create(this.instantiationService, - { resource: this.resource, viewType: this.viewType, customTitle: this._customTitle }, + { resource: this.resource, viewType: this.viewType, customTitle: this._customTitle, iconPath: this.iconPath, }, this.group, this.webview.options); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 5f3778e641a..a9d522182e0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -25,6 +25,7 @@ export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta { readonly editorResource: UriComponents; readonly customTitle: string | undefined; + readonly iconPath: { dark: UriComponents; light: UriComponents } | undefined; backupId: string; @@ -99,6 +100,7 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { resource: data.editorResource, viewType: data.viewType, customTitle: data.title, + iconPath: data.iconPath, }, webview, { startsDirty: data.dirty, backupId: data.backupId }); if (typeof data.group === 'number') { customInput.updateGroup(data.group); @@ -199,6 +201,9 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements resource: URI.revive(backupData.editorResource), viewType: backupData.viewType, customTitle: backupData.customTitle, + iconPath: backupData.iconPath + ? { dark: URI.revive(backupData.iconPath.dark), light: URI.revive(backupData.iconPath.light) } + : undefined }, webview, { backupId: backupData.backupId }); editor.updateGroup(0); return editor; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 8e99799abe5..93c582d4773 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -138,10 +138,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }, { createEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, { resource, viewType: contributedEditor.id, customTitle: undefined }, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource, viewType: contributedEditor.id, customTitle: undefined, iconPath: undefined }, group.id) }; }, createUntitledEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, { resource: resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), viewType: contributedEditor.id, customTitle: undefined }, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource: resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), viewType: contributedEditor.id, customTitle: undefined, iconPath: undefined }, group.id) }; }, createDiffEditorInput: (diffEditorInput, group) => { return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; @@ -157,8 +157,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ editorID: string, group: IEditorGroup ): DiffEditorInput { - const modifiedOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.modified.resource), viewType: editorID, customTitle: undefined }, group.id, { customClasses: 'modified' }); - const originalOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.original.resource), viewType: editorID, customTitle: undefined }, group.id, { customClasses: 'original' }); + const modifiedOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.modified.resource), viewType: editorID, customTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'modified' }); + const originalOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.original.resource), viewType: editorID, customTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'original' }); return this.instantiationService.createInstance(DiffEditorInput, editor.label, editor.description, originalOverride, modifiedOverride, true); } @@ -259,7 +259,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: EditorInput | IResourceEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = CustomEditorInput.create(this.instantiationService, { resource: newResource, viewType, customTitle: undefined }, group); + replacement = CustomEditorInput.create(this.instantiationService, { resource: newResource, viewType, customTitle: undefined, iconPath: undefined }, group); } else { replacement = { resource: newResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index acbecc9773b..3fb056bf360 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -992,7 +992,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { + // Potentially update icon + this._onDidChangeLabel.fire(); + })); } override dispose() { @@ -104,13 +111,23 @@ export class WebviewInput extends EditorInput { return this.webview.extension; } + override getIcon(): URI | undefined { + if (!this._iconPath) { + return; + } + + return isDark(this._themeService.getColorTheme().type) + ? this._iconPath.dark + : (this._iconPath.light ?? this._iconPath.dark); + } + public get iconPath() { return this._iconPath; } public set iconPath(value: WebviewIcons | undefined) { this._iconPath = value; - this._iconManager.setIcons(this._resourceId, value); + this._onDidChangeLabel.fire(); } public override matches(other: EditorInput | IUntypedEditorInput): boolean { @@ -138,3 +155,8 @@ export class WebviewInput extends EditorInput { return this._webview.claim(claimant, targetWindow, scopedContextKeyService); } } +export interface WebviewIcons { + readonly light: URI; + readonly dark: URI; +} + diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index 5e5a6d8912f..0ebe5b1c840 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -8,8 +8,7 @@ import { ExtensionIdentifier } from '../../../../platform/extensions/common/exte import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorSerializer } from '../../../common/editor.js'; import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from '../../webview/browser/webview.js'; -import { WebviewIcons } from './webviewIconManager.js'; -import { WebviewInput } from './webviewEditorInput.js'; +import { WebviewIcons, WebviewInput } from './webviewEditorInput.js'; import { IWebviewWorkbenchService } from './webviewWorkbenchService.js'; export type SerializedWebviewOptions = WebviewOptions & WebviewContentOptions; diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts deleted file mode 100644 index 1452b2f37d0..00000000000 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as cssValue from '../../../../base/browser/cssValue.js'; -import * as domStylesheets from '../../../../base/browser/domStylesheets.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { URI } from '../../../../base/common/uri.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ILifecycleService, LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; - -export interface WebviewIcons { - readonly light: URI; - readonly dark: URI; -} - -export class WebviewIconManager extends Disposable { - - private readonly _icons = new Map(); - - private _styleElement: HTMLStyleElement | undefined; - - constructor( - @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { - super(); - this._register(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.iconTheme')) { - this.updateStyleSheet(); - } - })); - } - override dispose() { - super.dispose(); - this._styleElement = undefined; - } - - private get styleElement(): HTMLStyleElement { - if (!this._styleElement) { - this._styleElement = domStylesheets.createStyleSheet(undefined, undefined, this._store); - this._styleElement.className = 'webview-icons'; - } - return this._styleElement; - } - - public setIcons( - webviewId: string, - iconPath: WebviewIcons | undefined, - ) { - if (iconPath) { - this._icons.set(webviewId, iconPath); - } else { - this._icons.delete(webviewId); - } - - this.updateStyleSheet(); - } - - private async updateStyleSheet() { - await this._lifecycleService.when(LifecyclePhase.Starting); - - const cssRules: string[] = []; - if (this._configService.getValue('workbench.iconTheme') !== null) { - for (const [key, value] of this._icons) { - const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; - try { - cssRules.push( - `.monaco-workbench.vs ${webviewSelector}, .monaco-workbench.hc-light ${webviewSelector} { content: ""; background-image: ${cssValue.asCSSUrl(value.light)}; }`, - `.monaco-workbench.vs-dark ${webviewSelector}, .monaco-workbench.hc-black ${webviewSelector} { content: ""; background-image: ${cssValue.asCSSUrl(value.dark)}; }` - ); - } catch { - // noop - } - } - } - this.styleElement.textContent = cssRules.join('\n'); - } -} diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 65baea83ae6..20b07fdddab 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -12,15 +12,15 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { combinedDisposable, Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { EditorActivation } from '../../../../platform/editor/common/editor.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { GroupIdentifier } from '../../../common/editor.js'; import { DiffEditorInput } from '../../../common/editor/diffEditorInput.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { IOverlayWebview, IWebviewService, WebviewInitInfo } from '../../webview/browser/webview.js'; -import { CONTEXT_ACTIVE_WEBVIEW_PANEL_ID } from './webviewEditor.js'; -import { WebviewIconManager, WebviewIcons } from './webviewIconManager.js'; import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from '../../../services/editor/common/editorService.js'; -import { WebviewInput, WebviewInputInitInfo } from './webviewEditorInput.js'; +import { IOverlayWebview, IWebviewService, WebviewInitInfo } from '../../webview/browser/webview.js'; +import { CONTEXT_ACTIVE_WEBVIEW_PANEL_ID } from './webviewEditor.js'; +import { WebviewIcons, WebviewInput, WebviewInputInitInfo } from './webviewEditorInput.js'; export interface IWebViewShowOptions { readonly group?: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE; @@ -35,11 +35,6 @@ export const IWebviewWorkbenchService = createDecorator(); private readonly _revivalPool = new RevivalPool(); - private readonly _iconManager: WebviewIconManager; - constructor( @IEditorGroupsService editorGroupsService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @@ -220,8 +215,6 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc ) { super(); - this._iconManager = this._register(this._instantiationService.createInstance(WebviewIconManager)); - this._register(editorGroupsService.registerContextKeyProvider({ contextKey: CONTEXT_ACTIVE_WEBVIEW_PANEL_ID, getGroupContextKeyValue: (group) => this.getWebviewId(group.activeEditor), @@ -239,10 +232,6 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc this.updateActiveWebview(); } - get iconManager() { - return this._iconManager; - } - private _activeWebview: WebviewInput | undefined; private readonly _onDidChangeActiveWebviewEditor = this._register(new Emitter()); @@ -286,10 +275,11 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc webviewInitInfo: WebviewInitInfo, viewType: string, title: string, + iconPath: WebviewIcons | undefined, showOptions: IWebViewShowOptions, ): WebviewInput { const webview = this._webviewService.createWebviewOverlay(webviewInitInfo); - const webviewInput = this._instantiationService.createInstance(WebviewInput, { viewType, name: title, providedId: webviewInitInfo.providedViewType }, webview, this.iconManager); + const webviewInput = this._instantiationService.createInstance(WebviewInput, { viewType, name: title, providedId: webviewInitInfo.providedViewType, iconPath }, webview); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -340,7 +330,12 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc const webview = this._webviewService.createWebviewOverlay(options.webviewInitInfo); webview.state = options.state; - const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, { viewType: options.viewType, providedId: options.webviewInitInfo.providedViewType, name: options.title }, webview); + const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, { + viewType: options.viewType, + providedId: options.webviewInitInfo.providedViewType, + name: options.title, + iconPath: options.iconPath + }, webview); webviewInput.iconPath = options.iconPath; if (typeof options.group === 'number') { @@ -388,8 +383,4 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc return this._revivalPool.enqueueForRestoration(webview, token); } } - - public setIcons(id: string, iconPath: WebviewIcons | undefined): void { - this._iconManager.setIcons(id, iconPath); - } } From 741846c56eca327073bbb8b13ec2ae2143a0a5a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Mon, 20 Oct 2025 18:44:31 -0700 Subject: [PATCH 1369/4355] Use stable comparison key when populating Quick Open items --- .../contrib/search/browser/anythingQuickAccess.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index acbecc9773b..a92ba5d6dc0 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -417,8 +417,8 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider> => { - // Exclude any result that is already present in editor history - const additionalPicksExcludes = new ResourceMap(); + // Exclude any result that is already present in editor history. + const additionalPicksExcludes = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); for (const historyEditorPick of historyEditorPicks) { if (historyEditorPick.resource) { additionalPicksExcludes.set(historyEditorPick.resource, true); @@ -638,7 +638,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider(); + const relativePathFileResultsMap = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); for (const relativePathFileResult of relativePathFileResults) { relativePathFileResultsMap.set(relativePathFileResult, true); } @@ -681,7 +681,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider(); + const existingFileSearchResultsMap = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); for (const fileSearchResult of fileSearchResults.results) { existingFileSearchResultsMap.set(fileSearchResult.resource, true); } From f846b8ee3b4cd5c7fc8907d77ced464e59656b2a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 20 Oct 2025 19:43:29 -0700 Subject: [PATCH 1370/4355] Use the contributed icon for newly created chat editor --- .../chat/browser/chatSessions/chatSessionTracker.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index c0d1fecf8b8..7c5e82d744d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -12,7 +12,6 @@ import { EditorInput } from '../../../../common/editor/editorInput.js'; import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; import { IChatService } from '../../common/chatService.js'; -import { Codicon } from '../../../../../base/common/codicons.js'; import { IChatModel } from '../../common/chatModel.js'; import { ChatSessionUri } from '../../common/chatUri.js'; @@ -85,13 +84,14 @@ export class ChatSessionTracker extends Disposable { return; } - let status: ChatSessionStatus | undefined; + let status: ChatSessionStatus = ChatSessionStatus.Completed; let timestamp: number | undefined; if (editor.sessionId) { const model = this.chatService.getSession(editor.sessionId); - if (model) { - status = this.modelToStatus(model); + const modelStatus = model ? this.modelToStatus(model) : undefined; + if (model && modelStatus) { + status = modelStatus; const requests = model.getRequests(); if (requests.length > 0) { timestamp = requests[requests.length - 1].timestamp; @@ -104,8 +104,7 @@ export class ChatSessionTracker extends Disposable { id: parsed?.sessionId || editor.sessionId || `${provider.chatSessionType}-local-${index}`, resource: editor.resource, label: editor.getName(), - iconPath: Codicon.chatSparkle, - status, + status: status, provider, timing: { startTime: timestamp ?? Date.now() From b9480e9d7d4575e3074b30fbb21e0c92beb1d187 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 20 Oct 2025 21:13:56 -0700 Subject: [PATCH 1371/4355] Make type signature of groupBy more accurate (#272395) See #272263, it currently assumes that all keys of K will be defined. This still isn't perfect, because it says that the returned object might contain a key with value `undefined`, but in reality, if a key is defined, it will have a value. So now usages of this with Object.entries and so on are slighly wrong, but that's preferable IMO. --- src/vs/base/common/collections.ts | 4 ++-- src/vs/base/common/lifecycle.ts | 4 +++- src/vs/base/test/common/collections.test.ts | 4 ++-- .../chat/browser/modelPicker/modePickerActionItem.ts | 4 ++-- .../contrib/issue/browser/baseIssueReporterService.ts | 4 ++-- .../contrib/markers/test/browser/markersModel.test.ts | 6 ++++-- src/vs/workbench/contrib/mcp/browser/mcpCommands.ts | 6 +++--- .../notebook/browser/viewModel/notebookViewModelImpl.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts | 6 +++++- 9 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 24c0b9767d7..845c5d9a2a9 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -19,8 +19,8 @@ export type INumberDictionary = Record; * Groups the collection into a dictionary based on the provided * group function. */ -export function groupBy(data: readonly V[], groupFn: (element: V) => K): Record { - const result: Record = Object.create(null); +export function groupBy(data: readonly V[], groupFn: (element: V) => K): Partial> { + const result: Partial> = Object.create(null); for (const element of data) { const key = groupFn(element); let target = result[key]; diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index ff7684f382b..0de9f6e37c8 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -206,7 +206,9 @@ export class DisposableTracker implements IDisposableTracker { const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v); delete continuations[stackTracePath[i]]; for (const [cont, set] of Object.entries(continuations)) { - stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + if (set) { + stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + } } stackTraceFormattedLines.unshift(line); diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index fbf8f6e77b3..5bd2206d538 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -24,12 +24,12 @@ suite('Collections', () => { const grouped = collections.groupBy(source, x => x.key); // Group 1 - assert.strictEqual(grouped[group1].length, 2); + assert.strictEqual(grouped[group1]?.length, 2); assert.strictEqual(grouped[group1][0].value, value1); assert.strictEqual(grouped[group1][1].value, value2); // Group 2 - assert.strictEqual(grouped[group2].length, 1); + assert.strictEqual(grouped[group2]?.length, 1); assert.strictEqual(grouped[group2][0].value, value3); }); diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts index 07d9543ec15..36fb1e79077 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modePickerActionItem.ts @@ -79,11 +79,11 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem { mode => mode.source?.storage === PromptsStorage.extension && mode.source.extensionId.value === productService.defaultChatAgent?.chatExtensionId ? 'builtin' : 'custom'); - const customBuiltinModeActions = customModes.builtin.map(mode => { + const customBuiltinModeActions = customModes.builtin?.map(mode => { const action = makeActionFromCustomMode(mode, currentMode); action.category = builtInCategory; return action; - }); + }) ?? []; const orderedModes = coalesce([ agentMode && makeAction(agentMode, currentMode), diff --git a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts index 9f1f8ade96f..ca6f65cbadb 100644 --- a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts +++ b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts @@ -410,9 +410,9 @@ export class BaseIssueReporterService extends Disposable { return ext.isTheme ? 'themes' : 'nonThemes'; }); - const numberOfThemeExtesions = themes && themes.length; + const numberOfThemeExtesions = (themes && themes.length) ?? 0; this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); - this.updateExtensionTable(nonThemes, numberOfThemeExtesions); + this.updateExtensionTable(nonThemes ?? [], numberOfThemeExtesions); if (this.disableExtensions || installedExtensions.length === 0) { (this.getElementById('disableExtensions')).disabled = true; } diff --git a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts index 34990da558e..48596e960b4 100644 --- a/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts +++ b/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts @@ -19,9 +19,11 @@ class TestMarkersModel extends MarkersModel { Object.keys(byResource).forEach(key => { const markers = byResource[key]; - const resource = markers[0].resource; + if (markers) { + const resource = markers[0].resource; - this.setResourceMarkers([[resource, markers]]); + this.setResourceMarkers([[resource, markers]]); + } }); } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index 3c60794dc2d..eae0316f8c0 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -117,9 +117,9 @@ export class ListMcpServerCommand extends Action2 { const firstRun = pick.items.length === 0; pick.items = [ { id: '$add', label: localize('mcp.addServer', 'Add Server'), description: localize('mcp.addServer.description', 'Add a new server configuration'), alwaysShow: true, iconClass: ThemeIcon.asClassName(Codicon.add) }, - ...Object.values(servers).filter(s => s.length).flatMap((servers): (ItemType | IQuickPickSeparator)[] => [ - { type: 'separator', label: servers[0].collection.label, id: servers[0].collection.id }, - ...servers.map(server => ({ + ...Object.values(servers).filter(s => s!.length).flatMap((servers): (ItemType | IQuickPickSeparator)[] => [ + { type: 'separator', label: servers![0].collection.label, id: servers![0].collection.id }, + ...servers!.map(server => ({ id: server.definition.id, label: server.definition.label, description: McpConnectionState.toString(server.connectionState.read(reader)), diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 44d5b94af14..ca7a0dabfe6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -778,7 +778,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD for (const _handle in deletesByHandle) { const handle = parseInt(_handle); - const ids = deletesByHandle[handle]; + const ids = deletesByHandle[handle]!; const cell = this.getCellByHandle(handle); cell?.deltaCellStatusBarItems(ids, []); ids.forEach(id => this._statusBarItemIdToCellMap.delete(id)); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 8ca4b2be266..21a3f1e9ae0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -504,11 +504,15 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer ThemeIcon.isThemeIcon(ref.icon) ? ref.icon.id : ''); for (const [key, historyItemRefs] of Object.entries(historyItemRefByIconId)) { // Skip badges without an icon - if (key === '') { + if (key === '' || !historyItemRefs) { continue; } From 7525da5e65a7ab2ca4542328ec2159d051320012 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 20 Oct 2025 21:35:27 -0700 Subject: [PATCH 1372/4355] mcp: implement close transport message for streamable http (#270433) * mcp: implement close transport message for streamable http Fixes #263068 * fix --- src/vs/workbench/api/common/extHostMcp.ts | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostMcp.ts b/src/vs/workbench/api/common/extHostMcp.ts index 8c83a873af5..6d639789c20 100644 --- a/src/vs/workbench/api/common/extHostMcp.ts +++ b/src/vs/workbench/api/common/extHostMcp.ts @@ -78,10 +78,13 @@ export class ExtHostMcpService extends Disposable implements IExtHostMpcService } $stopMcp(id: number): void { - if (this._sseEventSources.has(id)) { - this._sseEventSources.deleteAndDispose(id); - this._proxy.$onDidChangeState(id, { state: McpConnectionState.Kind.Stopped }); - } + this._sseEventSources.get(id) + ?.close() + .then(() => this._didClose(id)); + } + + private _didClose(id: number) { + this._sseEventSources.deleteAndDispose(id); } $sendMessage(id: number, message: string): void { @@ -234,6 +237,7 @@ export class McpHTTPHandle extends Disposable { resourceMetadata?: IAuthorizationProtectedResourceMetadata; scopes?: string[]; }; + private _didSendClose = false; constructor( private readonly _id: number, @@ -264,7 +268,38 @@ export class McpHTTPHandle extends Disposable { } } - _send(message: string) { + async close() { + if (this._mode.value === HttpMode.Http && this._mode.sessionId && !this._didSendClose) { + this._didSendClose = true; + try { + await this._closeSession(this._mode.sessionId); + } catch { + // ignored -- already logged + } + } + + this._proxy.$onDidChangeState(this._id, { state: McpConnectionState.Kind.Stopped }); + } + + private async _closeSession(sessionId: string) { + const headers: Record = { + ...Object.fromEntries(this._launch.headers), + 'Mcp-Session-Id': sessionId, + }; + + await this._addAuthHeader(headers); + + // no fetch with retry here -- don't try to auth if we get an auth failure + await this._fetch( + this._launch.uri.toString(true), + { + method: 'DELETE', + headers, + }, + ); + } + + private _send(message: string) { if (this._mode.value === HttpMode.SSE) { return this._sendLegacySSE(this._mode.endpoint, message); } else { From 85ae0153f9caf3dd8347db6f363a54ee5a1d5003 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:39:21 -0700 Subject: [PATCH 1373/4355] Fix css webview icons in tabs Aligns with how we support other icons --- src/vs/base/browser/ui/iconLabel/iconlabel.css | 5 ++--- .../browser/parts/editor/media/editortabscontrol.css | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index ed9278d7087..f520d6e8bf9 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -33,9 +33,8 @@ .monaco-icon-label-iconpath { width: 16px; - height: 16px; - padding-left: 2px; - margin-top: 2px; + height: 22px; + margin-right: 6px; display: flex; } diff --git a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css index 37b90640ac5..57ab8ca5e2b 100644 --- a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css @@ -34,8 +34,11 @@ cursor: pointer; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before { - height: var(--editor-group-tab-height); /* tweak the icon size of the editor labels when icons are enabled */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label { + &::before, + & > .monaco-icon-label-iconpath { + height: var(--editor-group-tab-height); /* tweak the icon size of the editor labels when icons are enabled */ + } } .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after, From 0af4f80b051adfb7a46f3bb9499bcc1aa4ba0665 Mon Sep 17 00:00:00 2001 From: Jerome Lelong Date: Tue, 21 Oct 2025 10:46:46 +0200 Subject: [PATCH 1374/4355] Make pair colorizer ignore \@ifnextchar (#272329) Make pair colorizer ignore \@ifnextchar --- extensions/latex/cgmanifest.json | 2 +- extensions/latex/package.json | 3 +++ extensions/latex/syntaxes/TeX.tmLanguage.json | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index c23c98bd760..3dc5c4baef7 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "84ce12aa6be384369ff218ac25efb27e6f34e78c" + "commitHash": "ca85e20304afcb5c6a28a6e0b9fc1ead8f124001" } }, "license": "MIT", diff --git a/extensions/latex/package.json b/extensions/latex/package.json index 73e1829f73f..18bbd6cec32 100644 --- a/extensions/latex/package.json +++ b/extensions/latex/package.json @@ -72,6 +72,9 @@ "language": "latex", "scopeName": "text.tex.latex", "path": "./syntaxes/LaTeX.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.control.ifnextchar.tex" + ], "embeddedLanguages": { "source.cpp": "cpp_embedded_latex", "source.css": "css", diff --git a/extensions/latex/syntaxes/TeX.tmLanguage.json b/extensions/latex/syntaxes/TeX.tmLanguage.json index b31ccccb631..f4e926c9cf0 100644 --- a/extensions/latex/syntaxes/TeX.tmLanguage.json +++ b/extensions/latex/syntaxes/TeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/6bd99800f7b2cbd0e36cecb56fe1936da5affadb", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/ca85e20304afcb5c6a28a6e0b9fc1ead8f124001", "name": "TeX", "scopeName": "text.tex", "patterns": [ @@ -31,6 +31,9 @@ "match": "\\\\\\\\", "name": "keyword.control.newline.tex" }, + { + "include": "#ifnextchar" + }, { "include": "#macro-general" } @@ -86,6 +89,10 @@ } ] }, + "ifnextchar": { + "match": "\\\\@ifnextchar[({\\[]", + "name": "keyword.control.ifnextchar.tex" + }, "macro-control": { "match": "(\\\\)(backmatter|csname|else|endcsname|fi|frontmatter|mainmatter|unless|if(case|cat|csname|defined|dim|eof|false|fontchar|hbox|hmode|inner|mmode|num|odd|true|vbox|vmode|void|x)?)(?![a-zA-Z@])", "captures": { From f94ca1a55d709a8aeb05d8ccf27002b446d6a3b6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Oct 2025 11:02:39 +0200 Subject: [PATCH 1375/4355] adopt to v0.1 of github gallery (#272429) --- .../platform/mcp/common/mcpGalleryService.ts | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 836f7713429..e2f5e0bb7cf 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -27,18 +27,19 @@ interface IMcpRegistryInfo { interface IGitHubInfo { readonly name: string; - readonly name_with_owner: string; - readonly display_name?: string; - readonly is_in_organization?: boolean; + readonly nameWithOwner: string; + readonly displayName?: string; + readonly isInOrganization?: boolean; readonly license?: string; - readonly opengraph_image_url?: string; - readonly owner_avatar_url?: string; - readonly primary_language?: string; - readonly primary_language_color?: string; - readonly pushed_at?: string; - readonly stargazer_count?: number; + readonly opengraphImageUrl?: string; + readonly ownerAvatarUrl?: string; + readonly primaryLanguage?: string; + readonly primaryLanguageColor?: string; + readonly pushedAt?: string; + readonly readMe?: string; + readonly stargazerCount?: number; readonly topics?: readonly string[]; - readonly uses_custom_opengraph_image?: boolean; + readonly usesCustomOpengraphImage?: boolean; } interface IAzureAPICenterInfo { @@ -47,17 +48,16 @@ interface IAzureAPICenterInfo { interface IRawGalleryMcpServersMetadata { readonly count: number; - readonly total?: number; - readonly next_cursor?: string; + readonly nextCursor?: string; } interface IRawGalleryMcpServersResult { - readonly metadata?: IRawGalleryMcpServersMetadata; + readonly metadata: IRawGalleryMcpServersMetadata; readonly servers: readonly IRawGalleryMcpServer[]; } interface IGalleryMcpServersResult { - readonly metadata?: IRawGalleryMcpServersMetadata; + readonly metadata: IRawGalleryMcpServersMetadata; readonly servers: IGalleryMcpServer[]; } @@ -219,7 +219,7 @@ namespace McpServerSchemaVersion_v2025_07_09 { } interface RawGalleryMcpServersResult { - readonly metadata?: { + readonly metadata: { readonly count: number; readonly total?: number; readonly next_cursor?: string; @@ -227,6 +227,22 @@ namespace McpServerSchemaVersion_v2025_07_09 { readonly servers: readonly RawGalleryMcpServer[]; } + interface RawGitHubInfo { + readonly name: string; + readonly name_with_owner: string; + readonly display_name?: string; + readonly is_in_organization?: boolean; + readonly license?: string; + readonly opengraph_image_url?: string; + readonly owner_avatar_url?: string; + readonly primary_language?: string; + readonly primary_language_color?: string; + readonly pushed_at?: string; + readonly stargazer_count?: number; + readonly topics?: readonly string[]; + readonly uses_custom_opengraph_image?: boolean; + } + class Serializer implements IGalleryMcpServerDataSerializer { public toRawGalleryMcpServerResult(input: unknown): IRawGalleryMcpServersResult | undefined { @@ -246,7 +262,10 @@ namespace McpServerSchemaVersion_v2025_07_09 { } return { - metadata: from.metadata, + metadata: { + count: from.metadata.count ?? 0, + nextCursor: from.metadata?.next_cursor + }, servers }; } @@ -359,6 +378,8 @@ namespace McpServerSchemaVersion_v2025_07_09 { } } + const gitHubInfo: RawGitHubInfo | undefined = from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as RawGitHubInfo | undefined; + return { name: from.name, description: from.description, @@ -396,7 +417,21 @@ namespace McpServerSchemaVersion_v2025_07_09 { publishedAt: registryInfo.published_at, updatedAt: registryInfo.updated_at, }, - githubInfo: from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, + githubInfo: gitHubInfo ? { + name: gitHubInfo.name, + nameWithOwner: gitHubInfo.name_with_owner, + displayName: gitHubInfo.display_name, + isInOrganization: gitHubInfo.is_in_organization, + license: gitHubInfo.license, + opengraphImageUrl: gitHubInfo.opengraph_image_url, + ownerAvatarUrl: gitHubInfo.owner_avatar_url, + primaryLanguage: gitHubInfo.primary_language, + primaryLanguageColor: gitHubInfo.primary_language_color, + pushedAt: gitHubInfo.pushed_at, + stargazerCount: gitHubInfo.stargazer_count, + topics: gitHubInfo.topics, + usesCustomOpengraphImage: gitHubInfo.uses_custom_opengraph_image + } : undefined }; } } @@ -510,10 +545,10 @@ namespace McpServerSchemaVersion_v0_1 { } interface RawGalleryMcpServersResult { - readonly metadata?: { + readonly metadata: { readonly count: number; readonly total?: number; - readonly next_cursor?: string; + readonly nextCursor?: string; }; readonly servers: readonly RawGalleryMcpServerInfo[]; } @@ -694,27 +729,27 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } const { servers, metadata } = await this.queryGalleryMcpServers(query, mcpGalleryManifest, token); - const total = metadata?.total ?? metadata?.count ?? servers.length; + let total = metadata.nextCursor ? metadata.count + query.pageSize : metadata.count; const getNextPage = async (cursor: string | undefined, ct: CancellationToken): Promise> => { if (ct.isCancellationRequested) { throw new CancellationError(); } - const { servers, metadata } = cursor ? await this.queryGalleryMcpServers(query.withPage(cursor).withSearchText(undefined), mcpGalleryManifest, token) : { servers: [], metadata: undefined }; + const { servers, metadata } = cursor ? await this.queryGalleryMcpServers(query.withPage(cursor).withSearchText(undefined), mcpGalleryManifest, token) : { servers: [], metadata: { count: 0 } }; + total = metadata.nextCursor ? total + query.pageSize : total; return { elements: servers, total, hasNextPage: !!cursor, - getNextPage: (token) => getNextPage(metadata?.next_cursor, token) + getNextPage: (token) => getNextPage(metadata.nextCursor, token) }; }; return new PageIteratorPager({ elements: servers, total, - hasNextPage: !!metadata?.next_cursor, - getNextPage: (token) => getNextPage(metadata?.next_cursor, token), - + hasNextPage: !!metadata.nextCursor, + getNextPage: (token) => getNextPage(metadata.nextCursor, token), }); } @@ -793,7 +828,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService if (!displayName) { displayName = server.githubInfo.name.split('-').map(s => s.toLowerCase() === 'mcp' ? 'MCP' : s.toLowerCase() === 'github' ? 'GitHub' : uppercaseFirstLetter(s)).join(' '); } - publisher = server.githubInfo.name_with_owner.split('/')[0]; + publisher = server.githubInfo.nameWithOwner.split('/')[0]; } else { const nameParts = server.name.split('/'); if (nameParts.length > 0) { @@ -807,16 +842,16 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } } - if (server.githubInfo?.display_name) { - displayName = server.githubInfo.display_name; + if (server.githubInfo?.displayName) { + displayName = server.githubInfo.displayName; } let icon: { light: string; dark: string } | undefined; - if (server.githubInfo?.owner_avatar_url) { + if (server.githubInfo?.ownerAvatarUrl) { icon = { - light: server.githubInfo.owner_avatar_url, - dark: server.githubInfo.owner_avatar_url + light: server.githubInfo.ownerAvatarUrl, + dark: server.githubInfo.ownerAvatarUrl }; } @@ -849,14 +884,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService version: server.version, isLatest: server.registryInfo?.isLatest ?? true, publishDate: server.registryInfo?.publishedAt ? Date.parse(server.registryInfo.publishedAt) : undefined, - lastUpdated: server.githubInfo?.pushed_at ? Date.parse(server.githubInfo.pushed_at) : server.registryInfo?.updatedAt ? Date.parse(server.registryInfo.updatedAt) : undefined, + lastUpdated: server.githubInfo?.pushedAt ? Date.parse(server.githubInfo.pushedAt) : server.registryInfo?.updatedAt ? Date.parse(server.registryInfo.updatedAt) : undefined, repositoryUrl: server.repository?.url, readme: server.repository?.readme, icon, publisher, publisherUrl, license: server.githubInfo?.license, - starsCount: server.githubInfo?.stargazer_count, + starsCount: server.githubInfo?.stargazerCount, topics: server.githubInfo?.topics, configuration: { packages: server.packages, @@ -874,9 +909,9 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } private async queryRawGalleryMcpServers(query: Query, mcpGalleryManifest: IMcpGalleryManifest, token: CancellationToken): Promise { - const mcpGalleryUrl = query.searchText ? this.getSearchUrl(mcpGalleryManifest) : this.getMcpGalleryUrl(mcpGalleryManifest); + const mcpGalleryUrl = query.searchText && mcpGalleryManifest.version === 'v0' ? this.getSearchUrl(mcpGalleryManifest) : this.getMcpGalleryUrl(mcpGalleryManifest); if (!mcpGalleryUrl) { - return { servers: [] }; + return { servers: [], metadata: { count: 0 } }; } const uri = URI.parse(mcpGalleryUrl); @@ -896,7 +931,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } if (query.searchText) { const text = encodeURIComponent(query.searchText); - url += `&q=${text}`; + url += mcpGalleryManifest.version === 'v0' ? `&q=${text}` : `&search=${text}`; } const context = await this.requestService.request({ @@ -907,7 +942,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService const data = await asJson(context); if (!data) { - return { servers: [] }; + return { servers: [], metadata: { count: 0 } }; } const result = this.serializeMcpServersResult(data, mcpGalleryManifest); From 355af8979ea8c0e1034e13f56143171e124b8c85 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 21 Oct 2025 12:02:26 +0200 Subject: [PATCH 1376/4355] updates telemetry docs (#272439) --- .../contrib/inlineCompletions/browser/telemetry.ts | 2 +- .../aiEditTelemetry/aiEditTelemetryServiceImpl.ts | 4 ++-- .../browser/telemetry/arcTelemetrySender.ts | 10 +++++----- .../browser/telemetry/editSourceTrackingImpl.ts | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts index d56faa53354..bda92ed5c84 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -56,7 +56,7 @@ export type InlineCompletionEndOfLifeEvent = { type InlineCompletionsEndOfLifeClassification = { owner: 'benibenj'; - comment: 'Inline completions ended'; + comment: 'Inline completions ended. @sentToGitHub'; opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES' }; correlationId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The correlation identifier for the inline completion' }; extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier for the extension that contributed the inline completion' }; diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts index 2d3784af1cb..55d879e5bc5 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts @@ -46,7 +46,7 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { applyCodeBlockSuggestionId: string | undefined; }, { owner: 'hediet'; - comment: 'Reports when code from AI is suggested to the user.'; + comment: 'Reports when code from AI is suggested to the user. @sentToGitHub'; eventId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this event.' }; suggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this suggestion. Not always set.' }; @@ -125,7 +125,7 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { | 'accept'; }, { owner: 'hediet'; - comment: 'Reports when code from AI is accepted by the user.'; + comment: 'Reports when code from AI is accepted by the user. @sentToGitHub'; eventId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this event.' }; suggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this suggestion. Not always set.' }; diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts index 7509a1e8273..a69e7943c2a 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts @@ -61,11 +61,11 @@ export class InlineEditArcTelemetrySender extends Disposable { currentDeletedLineCount: number; }, { owner: 'hediet'; - comment: 'Reports the accepted and retained character count for an inline completion/edit.'; + comment: 'Reports for each accepted inline suggestion (= inline completions + next edit suggestions) the accumulated retained character count after a certain time delay. This event is sent 0s, 30s, 120s, 300s, 600s and 900s after acceptance. @sentToGitHub'; - extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id (copilot or copilot-chat); which provided this inline completion.' }; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id which provided this inline suggestion.' }; extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension.' }; - opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline completion or NES.' }; + opportunityId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for an opportunity to show an inline suggestion.' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language id of the document.' }; didBranchChange: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates if the branch changed in the meantime. If the branch changed (value is 1); this event should probably be ignored.' }; @@ -74,7 +74,7 @@ export class InlineEditArcTelemetrySender extends Disposable { originalCharCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original character count before any edits.' }; originalLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original line count before any edits.' }; originalDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original deleted line count before any edits.' }; - arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' }; + arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and retained character count.' }; currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' }; currentDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current deleted line count after edits.' }; }>('editTelemetry.reportInlineEditArc', { @@ -201,7 +201,7 @@ export class ChatArcTelemetrySender extends Disposable { currentDeletedLineCount: number; }, { owner: 'hediet'; - comment: 'Reports the accepted and retained character count for an inline completion/edit.'; + comment: 'Reports the accepted and retained character count for an inline completion/edit. @sentToGitHub'; sourceKeyCleaned: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The key of the edit source.' }; extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id (copilot or copilot-chat); which provided this inline completion.' }; diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts index a49cf205c86..8bc64c351e7 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editSourceTrackingImpl.ts @@ -179,9 +179,9 @@ class TrackedDocumentInfo extends Disposable { totalModifiedCount: number; }, { owner: 'hediet'; - comment: 'Reports distribution of various edit sources per session.'; + comment: 'Provides detailed character count breakdown for individual edit sources (typing, paste, inline completions, NES, etc.) within a session. Reports the top 10-30 sources per session with granular metadata including extension IDs and model IDs for AI edits. Sessions are scoped to either 5-minute windows for visible documents or longer periods ending on branch changes, commits, or 10-hour intervals. This event complements editSources.stats by providing source-specific details. @sentToGitHub'; - mode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Describes the session mode. Is either longterm or 5minWindow.' }; + mode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Describes the session mode. Is either \'longterm\' or \'5minWindow\'.' }; sourceKey: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A description of the source of the edit.' }; sourceKeyCleaned: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source of the edit with some properties (such as extensionId, extensionVersion and modelId) removed.' }; @@ -234,7 +234,7 @@ class TrackedDocumentInfo extends Disposable { isTrackedByGit: number; }, { owner: 'hediet'; - comment: 'Reports distribution of AI vs user edited characters.'; + comment: 'Aggregates character counts by edit source category (user typing, AI completions, NES, IDE actions, external changes) for each editing session. Sessions represent units of work and end when documents close, branches change, commits occur, or time limits are reached (5 minutes for visible documents, 10 hours otherwise). Tracks both total characters inserted and characters remaining at session end to measure retention. This high-level summary complements editSources.details which provides granular per-source breakdowns. @sentToGitHub'; mode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'longterm or 5minWindow' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language id of the document.' }; From 095f984998dd5f9a607054275ae565a6b7794918 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 12:03:47 +0200 Subject: [PATCH 1377/4355] promptfiles: code action to rename 'mode' to 'agent' (#272438) promptfile: rename 'mode' to 'agent' quick fix --- .../promptSyntax/promptFileRewriter.ts | 3 +- .../promptToolsCodeLensProvider.ts | 3 +- .../PromptHeaderDefinitionProvider.ts | 3 +- .../languageProviders/promptCodeActions.ts | 57 ++++++++++++++----- .../languageProviders/promptHovers.ts | 20 +++---- .../promptSyntax/service/newPromptsParser.ts | 23 ++++++-- .../service/promptsServiceImpl.ts | 4 +- 7 files changed, 77 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts index c8b543ef4f1..0fdc227d629 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts @@ -10,6 +10,7 @@ import { EditOperation } from '../../../../../editor/common/core/editOperation.j import { Range } from '../../../../../editor/common/core/range.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { ILanguageModelToolsService, IToolAndToolSetEnablementMap } from '../../common/languageModelToolsService.js'; +import { PromptHeaderAttributes } from '../../common/promptSyntax/service/newPromptsParser.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; export class PromptFileRewriter { @@ -32,7 +33,7 @@ export class PromptFileRewriter { return undefined; } - const toolsAttr = parser.header.getAttribute('tools'); + const toolsAttr = parser.header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 03ede182c37..5f5d9d08443 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -20,6 +20,7 @@ import { registerEditorFeature } from '../../../../../editor/common/editorFeatur import { PromptFileRewriter } from './promptFileRewriter.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IEditorModel } from '../../../../../editor/common/editorCommon.js'; +import { PromptHeaderAttributes } from '../../common/promptSyntax/service/newPromptsParser.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { @@ -54,7 +55,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider } - const toolsAttr = parser.header.getAttribute('tools'); + const toolsAttr = parser.header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr || toolsAttr.value.type !== 'array') { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts index 0ceb5945f69..0425b933c88 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts @@ -10,6 +10,7 @@ import { Definition, DefinitionProvider } from '../../../../../../editor/common/ import { ITextModel } from '../../../../../../editor/common/model.js'; import { IChatModeService } from '../../chatModes.js'; import { getPromptsTypeForLanguageId } from '../promptTypes.js'; +import { PromptHeaderAttributes } from '../service/newPromptsParser.js'; import { IPromptsService } from '../service/promptsService.js'; export class PromptHeaderDefinitionProvider implements DefinitionProvider { @@ -37,7 +38,7 @@ export class PromptHeaderDefinitionProvider implements DefinitionProvider { return undefined; } - const agentAttr = header.getAttribute('agent') ?? header.getAttribute('mode'); + const agentAttr = header.getAttribute(PromptHeaderAttributes.agent) ?? header.getAttribute(PromptHeaderAttributes.mode); if (agentAttr && agentAttr.value.type === 'string' && agentAttr.range.containsPosition(position)) { const agent = this.chatModeService.findModeByName(agentAttr.value.value); if (agent && agent.uri) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts index 75427ed5277..ba2805433e0 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts @@ -9,9 +9,9 @@ import { CodeAction, CodeActionContext, CodeActionList, CodeActionProvider, IWor import { ITextModel } from '../../../../../../editor/common/model.js'; import { localize } from '../../../../../../nls.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; -import { getPromptsTypeForLanguageId } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { IValue } from '../service/newPromptsParser.js'; +import { ParsedPromptFile, PromptHeaderAttributes } from '../service/newPromptsParser.js'; import { Selection } from '../../../../../../editor/common/core/selection.js'; import { Lazy } from '../../../../../../base/common/lazy.js'; @@ -29,24 +29,55 @@ export class PromptCodeActionProvider implements CodeActionProvider { provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); - if (!promptType) { + if (!promptType || promptType === PromptsType.instructions) { // if the model is not a prompt, we don't provide any code actions return undefined; } + const result: CodeAction[] = []; + const parser = this.promptsService.getParsedPromptFile(model); - const toolsAttr = parser.header?.getAttribute('tools'); - if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.value.range.containsRange(range)) { + switch (promptType) { + case PromptsType.agent: + this.getUpdateToolsCodeActions(parser, model, range, result); + break; + case PromptsType.prompt: + this.getUpdateModeCodeActions(parser, model, range, result); + this.getUpdateToolsCodeActions(parser, model, range, result); + break; + } + + if (result.length === 0) { return undefined; } - return this.getUpdateToolsCodeActions(toolsAttr.value.items, model, range); + return { + actions: result, + dispose: () => { } + }; } - private getUpdateToolsCodeActions(values: readonly IValue[], model: ITextModel, range: Range): CodeActionList | undefined { + private getUpdateModeCodeActions(promptFile: ParsedPromptFile, model: ITextModel, range: Range, result: CodeAction[]): void { + const modeAttr = promptFile.header?.getAttribute(PromptHeaderAttributes.mode); + if (!modeAttr?.range.containsRange(range)) { + return; + } + const keyRange = new Range(modeAttr.range.startLineNumber, modeAttr.range.startColumn, modeAttr.range.startLineNumber, modeAttr.range.startColumn + modeAttr.key.length); + result.push({ + title: localize('renameToAgent', "Rename to 'agent'"), + edit: { + edits: [asWorkspaceTextEdit(model, { range: keyRange, text: 'agent' })] + } + }); + } + private getUpdateToolsCodeActions(promptFile: ParsedPromptFile, model: ITextModel, range: Range, result: CodeAction[]): void { + const toolsAttr = promptFile.header?.getAttribute(PromptHeaderAttributes.tools); + if (toolsAttr?.value.type !== 'array' || !toolsAttr.value.range.containsRange(range)) { + return; + } + const values = toolsAttr.value.items; const deprecatedNames = new Lazy(() => this.languageModelToolsService.getDeprecatedQualifiedToolNames()); - const actions: CodeAction[] = []; const edits: TextEdit[] = []; for (const item of values) { if (item.type !== 'string') { @@ -60,7 +91,7 @@ export class PromptCodeActionProvider implements CodeActionProvider { edits.push(edit); if (item.range.containsRange(range)) { - actions.push({ + result.push({ title: localize('updateToolName', "Update to '{0}'", newName), edit: { edits: [asWorkspaceTextEdit(model, edit)] @@ -70,18 +101,14 @@ export class PromptCodeActionProvider implements CodeActionProvider { } } - if (edits.length && actions.length === 0 || edits.length > 1) { - actions.push({ + if (edits.length && result.length === 0 || edits.length > 1) { + result.push({ title: localize('updateAllToolNames', "Update all tool names"), edit: { edits: edits.map(edit => asWorkspaceTextEdit(model, edit)) } }); } - if (actions.length) { - return { actions, dispose: () => { } }; - } - return undefined; } } function asWorkspaceTextEdit(model: ITextModel, textEdit: TextEdit): IWorkspaceTextEdit { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 3f3a32fe387..d74174df9cf 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -15,7 +15,7 @@ import { ILanguageModelToolsService, ToolSet } from '../../languageModelToolsSer import { IChatModeService, isBuiltinChatMode } from '../../chatModes.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { IHeaderAttribute, PromptBody, PromptHeader } from '../service/newPromptsParser.js'; +import { IHeaderAttribute, PromptBody, PromptHeader, PromptHeaderAttributes } from '../service/newPromptsParser.js'; export class PromptHoverProvider implements HoverProvider { /** @@ -68,42 +68,42 @@ export class PromptHoverProvider implements HoverProvider { private async provideHeaderHover(position: Position, promptType: PromptsType, header: PromptHeader): Promise { if (promptType === PromptsType.instructions) { - const descriptionRange = header.getAttribute('description')?.range; + const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), descriptionRange); } - const applyToRange = header.getAttribute('applyTo')?.range; + const applyToRange = header.getAttribute(PromptHeaderAttributes.applyTo)?.range; if (applyToRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: `**/*.ts`, `**/*.js`, `client/**`'), applyToRange); } } else if (promptType === PromptsType.agent) { - const descriptionRange = header.getAttribute('description')?.range; + const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.agent.description', 'The description of the agent file. It is a short description of what the agent does.'), descriptionRange); } - const model = header.getAttribute('model'); + const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'The model to use in this agent.')); } - const tools = header.getAttribute('tools'); + const tools = header.getAttribute(PromptHeaderAttributes.tools); if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The tools to use in this agent.')); } } else { - const descriptionRange = header.getAttribute('description')?.range; + const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It is a short description of what the prompt does.'), descriptionRange); } - const model = header.getAttribute('model'); + const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.')); } - const tools = header.getAttribute('tools'); + const tools = header.getAttribute(PromptHeaderAttributes.tools); if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.')); } - const agent = header.getAttribute('agent') ?? header.getAttribute('mode'); + const agent = header.getAttribute(PromptHeaderAttributes.agent) ?? header.getAttribute(PromptHeaderAttributes.mode); if (agent?.range.containsPosition(position)) { return this.getAgentHover(agent, position, localize('promptHeader.prompt.agent', 'The agent to use in this prompt.')); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts index 11aa2c2bd4c..49b90645dba 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts @@ -62,6 +62,17 @@ interface ParsedHeader { readonly attributes: IHeaderAttribute[]; } +export namespace PromptHeaderAttributes { + export const description = 'description'; + export const agent = 'agent'; + export const mode = 'mode'; + export const model = 'model'; + export const applyTo = 'applyTo'; + export const tools = 'tools'; + export const handOffs = 'handoffs'; + export const advancedOptions = 'advancedOptions'; +} + export class PromptHeader { private _parsed: ParsedHeader | undefined; @@ -137,23 +148,23 @@ export class PromptHeader { } public get description(): string | undefined { - return this.getStringAttribute('description'); + return this.getStringAttribute(PromptHeaderAttributes.description); } public get agent(): string | undefined { - return this.getStringAttribute('agent') ?? this.getStringAttribute('mode'); + return this.getStringAttribute(PromptHeaderAttributes.agent) ?? this.getStringAttribute(PromptHeaderAttributes.mode); } public get model(): string | undefined { - return this.getStringAttribute('model'); + return this.getStringAttribute(PromptHeaderAttributes.model); } public get applyTo(): string | undefined { - return this.getStringAttribute('applyTo'); + return this.getStringAttribute(PromptHeaderAttributes.applyTo); } public get tools(): string[] | undefined { - const toolsAttribute = this._parsedHeader.attributes.find(attr => attr.key === 'tools'); + const toolsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.tools); if (!toolsAttribute) { return undefined; } @@ -181,7 +192,7 @@ export class PromptHeader { } public get handOffs(): IHandOff[] | undefined { - const handoffsAttribute = this._parsedHeader.attributes.find(attr => attr.key === 'handoffs'); + const handoffsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.handOffs); if (!handoffsAttribute) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index f10cdc2a937..7fe420d9e86 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -30,7 +30,7 @@ import { PromptsConfig } from '../config/config.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; -import { NewPromptsParser, ParsedPromptFile } from './newPromptsParser.js'; +import { NewPromptsParser, ParsedPromptFile, PromptHeaderAttributes } from './newPromptsParser.js'; import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; /** @@ -260,7 +260,7 @@ export class PromptsService extends Disposable implements IPromptsService { let metadata: any | undefined; if (ast.header) { - const advanced = ast.header.getAttribute('advancedOptions'); + const advanced = ast.header.getAttribute(PromptHeaderAttributes.advancedOptions); if (advanced && advanced.value.type === 'object') { metadata = {}; for (const [key, value] of Object.entries(advanced.value)) { From 488a36023a4ee774e1df17c36c2be7c7d2660958 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 21 Oct 2025 11:18:56 +0100 Subject: [PATCH 1378/4355] feat(codicons): add 'index-zero' icon (0xec66) and update codicon.ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119012 -> 119404 bytes src/vs/base/common/codiconsLibrary.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index d3ab8d9b630af0ac48281b4ac3da817d1008d978..ff6315a057067b6b3cdb1ab5fcf726418ea6a1bd 100644 GIT binary patch delta 6965 zcmZvg33yaRx`zL+cha4tvvm?kLP#ea2q7UM8(Y{REQWm*L&7FY2nYxvEX`(E36pCqYMX3VV~*!SlK5d=l-H!EjOsPK(=@198dA`$c585PyD%u!g)<8eGcxvFBu zB%|@oQo-Q?{OsPWnR7ztzV~(Vw}PWRMH13Cp7e$`3V1FtvM%n}7|cOqeG>XujaH70 zMv`NNk!W!llWc41LmZzYvF^diVyPALgIXpXB4Vu*i)GBFkXTGM;zHW{P5eePVlfx9 zm0ma{>m(KdIV?L+iC*Z4PPin0lhg7! z{(xW0Zk)tkxr=(df+Khf$8b=-!~wi1Cb5W>K{&-Fp%NzH;x;aXeB{3^Kgb>VhuoEa z%00P{Xv85N&5(rVNJa~!!H3ppgDkW|J_=BX_9#XdbVpD0K?w#)Erww@Mo20~VGJI{ zV<^J}OvEHi#^acRX_$@~n2Fh#gD2%#%ttjApau)E0xSJ^3bj~;wb+1-*o4h^7SG`Y z?7)lIh27YPSFsPR9{unVhN41d;BQibw(_!kjZ}2Qc*(+G^p%d%Ne1B^T*sIA z0)LWoSOh=1%2NCT|HNJV7VqPC_yE7hhdAq(2$_Tdauok1OXNHGS$>j#%V-%TBPB+@ zmp{mPsgy-nCa0uEJWTjMW1u8U3#3Ri{({bkgaEL^0vnnj1fg&v0%n--t^`poyTm4T zaYz${!vU8h$X}6xbm<~ZC0_i)l}Dvq+F*@5FXK=rLnKpLODQ_wBk{>#nIb#oko+EF zF%`?D8hg+~zLgK~cg{$gQ4!W?%nEDbcX2vNT!Jb^Oq{9>aDX{gX3mL9LcTzZ0r zQ&1WvYUC4ok_O}G$r_xdAJ^a!dWr_y=&8}FU_g2FG!1qdx58u7zGG{K22ayf8f>9w zYVa!~!|laLqtqRnc8i}qso|`bhItz4MbFpZl(ClQ2aMO<-keH)wnPJUj$eb0^imBv z(aSWrWSAndL;t`Q*Hi_5Z4^hu`nh^GtkrN8kp>kj;o2Y#bsE&u&uH)py+MN`^hOQd zqBm)9j5ai!XVS1)BVW?L(%=BizObnRZ`OYi(cj8-Ndg>D1y?8uglf1}NgzzaRZ9Zl z8Wm%}t>H>0fd~!PGzmm%}q& zGTl^z7PR^sf;2io10S8JL2Ful7eO0ZeHTF%t-gz(o%ki7zKkHBPSK!%W=1GbNT+Jh zo>mtmD5li~3A)hf8g!>yYQUehKr0RU&>0$(&?>;ze*WXNq&y${FV}J%~2p-X36}4LV_1K{(+Gi?4Z>c61+%{)?gQ{ z#*<(-t;UmJA3ausS7|l21p8?12Zqh2L2pZ@`8hlSL*5Ee1M1voUB?+C+lnO24bYKj4r;(K0^e%DM&OnPc3Q=z?v>FNm6RoBe;htOqKWan;*{DIeQJT^xK3uqE*T6yZFBNbZucvrJGuUE)3ZxrX zQ-=CivlXV1VYK>tf^{?_Q*cu+K}Mz!-focHqd+blrNPHEyH>%?zXW46!eI`2G<+bC zV5~-X7r{6_3Lo@Z{k)4{yhifrrW%ye%`_;Y6Ew)76E%FSkYJKVCeqC{P}fY>U>@B< z!^aN^G947^w>Riz40)*$pb;KHDgwKmQqsG@U zv;z;1*5D)KjBjZCShiG91XJnp8Z0-m(qnV>ur*1;hbak8*6^`PzK2FWpiLV5-Kb0- z=|2#>mKBu<=zm{DPnfS?uZi+_ajpxOCt9~UXHvK<%#MPbuPL? z^!1nlF*PwaJf)uXvF_NVu@~cN;?Bhv#Fxe2ZaS!0&t``ch9qoBxR6+ycqqw|w7GfL zE{f7>fLEpw*j9LTw!o00on zyRvrs^TP55b|-AjUEX-M)g?P<95%MJU*|d?|w)7ckh37z^nn+ADR2ewbHSrmj`wj zxP9PHgSHQL4<0o5yCGAC1cwe8dSd7|!$u9;JY0q^9e#1dyb*gx+#Xpva*uyh+^FhN z7e<>#SB^eBCS}a*M~6Ln>e0JnlgG{*d*dI9?GPpD@1X_`30@$KRaLcEZF7brTLvv`kz($uVi{rG-Rr{*$ z&KxlF^sJ;=`)A#GqWcp&X1ATaV@~v(-gEZMxiRKHoBb?fk~-@zq4*B!^Hg!hzu043`jmS5aym9$Z(xFTB?)sAYef3ul7azWSr0B?qBeRdJ zKXTy6`J)*}YmeSLR&zY__>won-n@5W>s#)(uAR&{xxVGw?zgMnzHqAO)RK2>@AP}8 z?sV$uxu?H*H|gE?&n!7}<-K9=oj>b4TPads-nur<&!4$F(wuKndI(pYv^-zB*Jlso ztG&X4*j!JHFAUM1+`^b>Pe;DeD@?cByeYoCg2IC4NN<~yo&{StY)L5Sf_AYH92pv&E8=!#{-G zQrxd!{YyRjHA~~N`18-mXA!Q1MH+(LVvuQgI^Jcm7z+Z_4oSZ+aQvP*i`&)2X0n>S zDb9!p=OCC}cGzv9HcOesYPI+*<`}Cb%3?hq>2Nw7vrVSiZjZ+u5@u`D{s+Kib?~c9 zW)t7=Ijk<2Oon}Xw7-$pgoQn2wTGGbFU)Q=n|KXIVhXW`^ZF*Y&CX9|+8sqMccRS% zv)Aon)ogEK4O15`ve-JvKY{l}0e!0UmYbJ$EvVk0h2icVCQN8_3FF$PA3cV`f*s~8uBgOWh#eUB7 z*<+$@`utoEha(JXFmem>``gSRD%KFybGl}>t^1|IgQdS5P4^_n#3Xy7oNg!oH|(?6 ztPZQ&wa*n{b69QhV9UmY`kznBeBmBX_=CdXM2Leo?Q}?em#=m-^K(ncA5ArTTzp5G z#x!GkshoItU$NelFsM5z$n$yaUVS6QCZE0CFK6Tx_+YbaQNgkJ?7YRwCQGZO{|Z&I zfNj;Qs;Y_xi;YoQGgQH-i>wZt3RsyRNqxzWjV--s%79||+O~}p@YS^x{{Kn(Ye@}C6*Z6{2b(xb}obY4crC`;XPMPdLwfOYm)@T8v%|g02<(K6r32OOC z;^cd{{N(?y<;UbTzV71jt2vvVV$c6Svy<84X_s3JpFNKYQenF!#Cl?5Z2Vr8aOuAM z6^Myc>yydqFxw+tA(37WxIkGgT%e-Nkq_4=^UgaiH+$D=xx)#-F7N3MwQ@qSFIc$T zQO)2_$hKNsRAT2xh4R5#il^yRUqe~mIb8jm#` j#%<2+e;xbsIR5Q)hD@!RIH|f#&7|2gk9~d?A+G-hLWBeQ delta 6575 zcmZ{o3v^V~*@nM&?wQ;t$s~a!gj@zlF!u|Da1jCoh=>p%+(`&AK!6AVLPQ1v0%BC) zP>iS;A|hg|NFpF2BDNM4DOIXyk)oxP&O}9vid1di&2GC^|F!!v z6s{j5DYXk58pp=}(`I+}?`9}UO*X3LJ zPQI6aAOg{dK{v!B0X>k26r>^zeQ_IdkcWH}pdSWe5N^jXjKE0TDH|{W<(Mcbn2afy zis_hvD$GGO=As67V;<(C4vTOPmY_i%#d54bBUYjb%~4OZ}Bm3nt zKIO>8$V#{{9QWZE?vju2Fdo7vv|~JG%R*d{Qe?_A@+Tx?2r4B5<4`ICq*%t`I6lK4 z@i9J;)3_I_F<92%I=;iV_%(imxA0q>!rOS$E8#L1W8@INl-2T;{477ok1|CnWU@rb zHF;OgNS&<08aXaa;%3}`hOv?;J<(Gl@i~eSF8_qU1~aU1AOJxKMHox~PDmGK$!@WU zT^tgC5ZDnYaq<`Rfky^QH;EPRMERYZ#YQPd3m%s*k&QDrD`j|8-hf~7aR&;Kg@sa$ zC@jNHc~rJz54OoUd0#%j7fiHK@M4wJ$a(ZeFZmRsB|`ovJ7u)YL50kaD!D0BWu_cK zBwmtA)XNR|LEb~U?1g0roCucbGEHX7ZOqKaWfmTk@zPh)Wi0yRw0Pt$sg*W4C~spL zYOz)t@id0YDO_b*+Mccqfr~j4q!K44Of5xBV$_F5$GSy)RT?a%=V-9lXbX+@a+>kg zXz)6Hw+3Z&tp<N<=Z_%KW-m1Y- zdYcA^XhS4TEtq`bQ(Z#j0ihollTPtMdu^(&~JI+h}z@L5_IE-;)J`JeqN#Kt7$UK>@99 zLeP&^Hz62Edo&nC_tM~Yy0-?y=sp^ZpjD*^M$+jD1{J4!@v9mVVd(f(4GAXD85)$+ znHv5rh(Ajss$$t1Or~=*m_p}jFqKw~BbZLB#u3b*y{dHtRdk^S%xS-BDM2;eUxT@H zkp?w1qhA4LxxZKgX0~7T48eR_^$bBBJy-)~uz!fA8}8wX>N0{Q^iT~Nj1#fZ-sLO} z*I)&$I+37}R-H(&k}lPti5{gvGd)^^b@Uhw*3)Gg+)v-3f$ICQ8azOcQ{l-49%11w z4YttZHP}k4K_W0{HAnFR-MBmf!%bhL)g%RzpkhGOdP|;4rO*mf#gy z4K2YNbgc#_>3JHwORHffc#mG7!S84exmPXldlnXI@II}^pWqz5Sc5;%^%{IgtFR#W zh*n`iaGqYO!3DZOgNyVs4gN%{pdq-V(t8C91b?Pg)DT>zRn!ptg;r5R@K^d?4gN;2 z(%>3>p9Wvkt2KNEVg_k$W#SsH6a1ZCt6-Gld#M=uWFp zB#1LE$4$VCM!)z8$S_*sGx4c$EI!lg;(irD1jA_+K?JIjPib(BR?$TGoF)F<8u^HB z*Wh9LX$>Bt_h>MRentaz>|PDV(+otLT41(OpAa2;g_~5|5m8zAyat&@TSD@{WR_mk zfSuu2F-Z7eC;pc-l0hHTU>vPtlc024kId}jEF96`Gg<{D!5`_P8hlK@s=+75>>kmv zY7o_#1gq)SH5hDc=@IR{&eBN@zN6pN;9L6F8Yurp1Le0gQ2wn3%BM6?ep>_OcQjDf zR!KtmSStS0d`j^ohR2TK`WcNJqE*5Wd})kJj86TErS~;_d=>u(8a~L1Uu6xEAB~p8 z0k~$I;&P2)={XW(jk2D}=xNmVjP`!cqkI|^(|!#fZN=ZEk$=)(Xuu2lztq4+s~QlP zX%+qiR{CoV9P~FD1kftv34&<$?^v}!C=04n3BqXA*#st9r3C@+>{n?)aDx6pBdU@& zHJD|TCyk5^5k7otV5fN%1pfr!N+sy zVwWg{&F^w)kWEKuaE6Z5@G)MxqBK%QyES~Ym#%1yyg|os?o$gs?@L##M)K)y8r(s5 z*PxJ&(;$nE*YFKNx)L-}P5011RUlD=Wpqys-z20fNyB#vBR|C*%!KYr)nJ=Zk&>Cs z0q;uFs5knqbd8Ln`)c5&Z_{9vaU>-iYBFvb zb*Y&XRj1vq;aiGy4b?~`Jxs$l7wIa|@Eu0FhHLmXBV8jj@*X`>gLLCsYILlHrO_HV zX=alG!A7EI05%!ZJ;`{`SmPNG+@E`12H-%J(ri507t`D6zAb-K{;7hPg64wH3%eDb?YFDn zC;f~2*Y-bM6jn67=)iy_13oMcDQ+shGI0Dr@BTq4gW3ki3~nCMcgW@;H*c@L{p`>Y zLz{=bKg>C7#IPm9E|s`T%1W9`t_@#4{M?9`5gSHUk33siT-sTBb=3S(2S?|P-ZLg^ z%;~b3WoPfGy5r2)(PMvoXYV^V-`O>8^SGby8g$pi@sq}XGGWMseG|@=k0@_0|8nA@ zi6+#8#=9N+Ma1Qr`Jy3KEpDjbVkdJ z(=&@_9-EaiYxS(Xvrf;J*;8k4pM9Y+q_ViOzOt?IR8?42NmX4{XVuj?VRH)RY^n~a zZmGUB_fSnx&Gee%HP`Qct5#}@YIn?Y%`2L>W!{DPedlkQe`8b-!GH?EbO$uh|f^p=G0Ntl#L{RJv); zrfUzBK5%C9jtAWjHb3ZV>D$uS;(Ms{q5ThA9b*%fYP)Tbs8PZEM|j z&d4)n8&`fYe@DoU3y%(c^hj%B>-^Tv$J~$Y-5IxY`{QwsUwId%sOJ68`@%n*^1FH^PeQC%`n+^^-*mUql}QjVQ} zt?0G(*RCF~Ki+x#yVoCn{o;vnCpNv|dgGmwDJR?B?Dpo8H;;5Qq;H7v@&n4p{A3s< zg>t({p(iO6wgeOmMsBn_w{Q?LQ5apAWDnsdyTXFpoM?AEJjqG6Nbb!kBn1PJ=ShyX zB`4+Qsr?P6AX6WhZ8lRtmeXW2o6RV3xh97?%_g(WYqCR0RQK%mWLwG@TA#yG<50i1L? z-Yps3-OK-G&QEdt^p-963uo9u59?CMM({TXx!F1H9?-uWccd+utqC>RlCzUM`Molb zY4Wi8QIWQEB-!G@EnM-iSp@@-X<=LAk!;UT{?8^)Ug526k?!26$OH^Vl#yB$?zPzq zJ;}YYc{yb8I#KE#G>?i_A3GCNEpF9jFmej=_VFWHu+;*K$-<9oCbrPTHd=z!YOpQ9 zWHIpov(;obn?p=9t#+$9++v9^nM_G$tJPe}Qvxly&QME6nBD2LPc)gl6GI{+Lma`@ z-l5Kf9^kc10nSj9ISL50*iGslX0yp|3FNoC|7?M}hu-!T%w{WC9~-LxQ?SiqHo?ll z<7I3i>c9|=D8B;Ewe+!ASd+KtXlO)OSb0d``M?mh!E)ZBdivRaC!l#isLN_n z@xb^9FqzWrVU7TErqyh6#;3(QlU2toGg~LP5?ro?C|9+E*K;~}3CpdqQ)`ERdI7a| Z{BXGD5WX=Vo_Z8N1s<+?3(g?OzW`(B%_9H+ diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 707bb663ba5..a786a4331ac 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -627,4 +627,5 @@ export const codiconsLibrary = { debugConnected: register('debug-connected', 0xec63), strikethrough: register('strikethrough', 0xec64), openInProduct: register('open-in-product', 0xec65), + indexZero: register('index-zero', 0xec66), } as const; From fdbc73eb50eca0522c3f2275201271cd73fe0aa8 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 21 Oct 2025 11:27:09 +0100 Subject: [PATCH 1379/4355] chore(codicons): update codicon.ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119404 -> 119388 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index ff6315a057067b6b3cdb1ab5fcf726418ea6a1bd..60af8275d291243c9d7bbff24e97f4cd2f355548 100644 GIT binary patch delta 1689 zcmZvZeN0nV7>Dm^@4YSUrQCkqA`~cXy>+dYQc6LDf(5Yx;zY(6D)J>FAl6bV0#d|^ zh*&`!BC@L3Au>XcFBwzDkdVb0Ll&|aLfB#!w=BqvA;cJFc5xqZi+`Q>ob#TO_xU{w zZpNIO(HMi(8|YIIq_RSw?%Sp$9URbcKfcK!<3>Udcx66ZYJKpd$!SvF}IyN$X(`%c}AX#H_iK%kMs5XDS=Kf zCyW>`94@0oEO{~ysVXLe+x+uaqF16cwMXRhfrSV zrO+9@T<_E`hLK@&;oafSHkvkCH$I6FM-)Wd+f=pbX{1kNZRE?S%qaV?L1XBNmPb2d z!ed%Cqnpi}=Zq;vr*S@(h)s#Did~44#-+zO<5srRZ22i(9)EGGdF!0XWEwXuZL@6~ zONdG6ON>l>l2n*9x4mThY;s!i^bXyQmL0D?Y}v`$nXvP>l$?}jyOMSd?)oV;CDobw z`lIrX9;KPnuBR=0Z2H(`Pv@mu(jTU;e3Ji3Z-zLdcz5#dk=-x$`0O$7ncvIWTfTQJ zla^`B?BA!|*S2pdOPpoQvSm$Yv$9jN9ohZa&vJ+yLr!hZXwK7IWo~9}ZEjEQn>@#U z+Wxfs_4zgV-yhH&s4Pe>s4JK_=zp;7;6$N!VP4^25nl9RQA1IGk^MM zWqZmR%Vv&Cj=PS(E>A6Ymp`k}R9GtdDwZpgD<4;`oG_etQI%2EWmno~s|!!kPU=ot zPIlK6)byRgPBosIvY(oFgr7E@o~^}dThDl(X*jb|XRDhzt2tYFcBVe#ob+5r1E;~( zFmfI~Z#zHZ)H@rUuP!()yl5b$p|4-t?aGD1+8UK;=i)C)+-ARuFKfEq-D5QWGe_yGaRkV(lXfJ#Z70f=;@jAWo{ zpbygr0Fr7I5Fy%V5SmEi&^7{?PNyS)8^r|-CLMebyr!NngkeBO`GF##oX5tvOgfX= zV}#JlYcm_eSe|_Pa}@1@8Gw2m91O#B25d<12Li5%`U#=a*&-={AS^aj(@-YO18jn6 zfGuESG#_Z-ySScty8seFN=OSuP#^|CypJSIO9&YLKqx>}6k`<7=|e=q5Urk~QT)9o z(ilwe9s&p7*OS8w6a~Qu0(+3SP>7R$Y%Z7moPvd^!Pow0a)nso$q;izfhcu{W+oB= zL}EDZjo+2YLF-vu3`IgFCbQC?QK{10}(aa|8iFRlXHmZot!BWp-3O1 z^#`a-st^+I0C4~VrF>D=HUTZ=CoGgysDT2=Uh=^4Y0Z|bU5D*cOq96j+ z3PKQ(MbwIj#Bdi86_prcj3LGlVocW%;`#%PF~$%=%nrM1Om_Fr$#;@7bKZBJdE;Qt zIGDBJ_~ELsN(fRGK%i=}E=G$`36E#%DrB4(2m-UYIr#;T|M2_(fk9*4kQ%91y=?87 z`IEACxmCDld#F6JJnRHPsEJr2m#F20a`HG=IK!NIu9R!yI=BA!u}h3g`o(eLM)CMk^U_`kB57C_?z!0WxmS}k zOXezj;%)bl`n;eBs(>1#-cqwxxlKO4yyLed1+Iuw*cGFS_exCZsWd6~DqDQ{zHj{q zzi_`=m8Ysy^~yiOzu$je?W-=?-c^cK4dI6H zmeu&`!qrm|mWYOk=}2*;C9)#&vvIjG$=I-lv8HOxyC_9e)7rwdQzoZVhHPxv_${_&6R|0F)2F!fxS7pyn};{Qi?_r#Y=O3v zZF!kcn9!c^d8>J=!)g&(iY(*X7~8V8^>3GM&)X5d5ti8HLG*k;65I=ZaE` z8jD^Zp^i9;q2leuoy9Xpbw`Vi4j!E^v6M_6!;YDb&6TE=_FA>p>9T_3_;JJWlH>g+ z@=gqwljY6jnl~+zlPc>FyRb^Gq>X7QnYG+N_Y2VXbHj%B&_UMfJ znX)rZd$_&%EaR;G>|AY8?dv*IUH>`Jx$$~ceMbGW`mYTojZmZGeBSxVCUeu^1?WQb zg|6n%=H82ni@hx+Ew@^xet*`Aw;Eb2Tc2F=ymaEy^ERuZt?{zza@pmXDC8d7ziFn7|0v24|EN@7*q^a4$j^#9`YS3z2kmocDU&- zarf5{iN#A1x)9QA~Fr0eYQKLkcK&z$Z0&0iRq63_veuVlpYE)#-K1fLt9QR{_ie zU=pBP1pG3>XD$R}>|sLN-j#C@f;&Y(5(>5a61N(8oIv zCX65f0rZ1_i5Q}z`yLp(oK_*U81jR3kO6}9{~uJZ(^5(x#g@`7R|1VLi0;qFlprWj zLo#IxD<3QWp7LKIZSGIznew0l7^214058ASB=?M9?n~3|b@%XmuL$M~k03 zOfF^8m40m)2FO5-0Z>dWZKV&C03j(9VDv>wV3ksGDySR?xC?d+lm#=nxGPsi0vZoR zXgnUw{cZ=heZdKog`vn71jSe=VI Date: Tue, 21 Oct 2025 12:51:54 +0000 Subject: [PATCH 1380/4355] Initial plan From 166e825eea3c1a920f4d12e34c05a913a39e590a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 06:10:24 -0700 Subject: [PATCH 1381/4355] Make profile tests OS specific --- .../chatAgentTools/test/browser/runInTerminalTool.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 209ab524a10..9a55eabe18f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -7,7 +7,7 @@ import { ok, strictEqual } from 'assert'; import { Separator } from '../../../../../../base/common/actions.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Emitter } from '../../../../../../base/common/event.js'; -import { OperatingSystem } from '../../../../../../base/common/platform.js'; +import { isLinux, isWindows, OperatingSystem } from '../../../../../../base/common/platform.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { ConfigurationTarget } from '../../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; @@ -988,7 +988,7 @@ suite('TerminalProfileFetcher', () => { } suite('getCopilotProfile', () => { - test('should return custom profile when configured', async () => { + (isWindows ? test : test.skip)('should return custom profile when configured', async () => { testTool.setBackendOs(OperatingSystem.Windows); const customProfile = Object.freeze({ path: 'C:\\Windows\\System32\\powershell.exe', args: ['-NoProfile'] }); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, customProfile); @@ -997,7 +997,7 @@ suite('TerminalProfileFetcher', () => { strictEqual(result, customProfile); }); - test('should fall back to default shell when no custom profile is configured', async () => { + (isLinux ? test : test.skip)('should fall back to default shell when no custom profile is configured', async () => { testTool.setBackendOs(OperatingSystem.Linux); setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); From 6d8728dd8e4847daf8f554c6ea5c5c8fd4a02d85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:10:36 +0000 Subject: [PATCH 1382/4355] Add terminal.integrated.shellIntegration.quickFixEnabled setting Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/common/terminalConfiguration.ts | 6 ++++++ .../quickFix/browser/quickFixAddon.ts | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 5d78feb15ec..39cd340b442 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -109,6 +109,7 @@ export const enum TerminalSettingId { ShellIntegrationEnabled = 'terminal.integrated.shellIntegration.enabled', ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome', ShellIntegrationDecorationsEnabled = 'terminal.integrated.shellIntegration.decorationsEnabled', + ShellIntegrationQuickFixEnabled = 'terminal.integrated.shellIntegration.quickFixEnabled', ShellIntegrationEnvironmentReporting = 'terminal.integrated.shellIntegration.environmentReporting', EnableImages = 'terminal.integrated.enableImages', SmoothScrolling = 'terminal.integrated.smoothScrolling', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ebd5bd55d8d..f8980e03e06 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -593,6 +593,12 @@ const terminalConfiguration: IStringDictionary = { ], default: 'both' }, + [TerminalSettingId.ShellIntegrationQuickFixEnabled]: { + restricted: true, + markdownDescription: localize('terminal.integrated.shellIntegration.quickFixEnabled', "When shell integration is enabled, show quick fixes for failed terminal commands. This includes the lightbulb icon and AI sparkle icon."), + type: 'boolean', + default: true + }, [TerminalSettingId.ShellIntegrationEnvironmentReporting]: { markdownDescription: localize('terminal.integrated.shellIntegration.environmentReporting', "Controls whether to report the shell environment, enabling its use in features such as {0}. This may cause a slowdown when printing your shell's prompt.", `\`#${TerminalContribSettingId.SuggestEnabled}#\``), type: 'boolean', diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 681c5c4bbc1..e4d2a43e5a8 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -27,7 +27,7 @@ import { ILabelService } from '../../../../../platform/label/common/label.js'; import { Schemas } from '../../../../../base/common/network.js'; import { URI } from '../../../../../base/common/uri.js'; import { ITerminalQuickFixInternalOptions, ITerminalQuickFixResolvedExtensionOptions, ITerminalQuickFix, ITerminalQuickFixTerminalCommandAction, ITerminalQuickFixOpenerAction, ITerminalQuickFixOptions, ITerminalQuickFixProviderSelector, ITerminalQuickFixService, ITerminalQuickFixUnresolvedExtensionOptions, TerminalQuickFixType, ITerminalQuickFixCommandAction } from './quickFix.js'; -import { ITerminalCommandSelector } from '../../../../../platform/terminal/common/terminal.js'; +import { ITerminalCommandSelector, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; import { ActionListItemKind, IActionListItem } from '../../../../../platform/actionWidget/browser/actionList.js'; import { CodeActionKind } from '../../../../../editor/contrib/codeAction/common/types.js'; import { Codicon } from '../../../../../base/common/codicons.js'; @@ -111,6 +111,13 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, }); this._register(this._quickFixService.onDidRegisterCommandSelector(selector => this.registerCommandSelector(selector))); this._register(this._quickFixService.onDidUnregisterProvider(id => this._commandListeners.delete(id))); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationQuickFixEnabled)) { + // Clear existing decorations when setting changes + this._decoration.clear(); + this._decorationDisposables.clear(); + } + })); } activate(terminal: Terminal): void { @@ -253,6 +260,12 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, return; } + // Check if quick fix decorations are enabled + const quickFixEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationQuickFixEnabled); + if (!quickFixEnabled) { + return; + } + this._decoration.clear(); this._decorationDisposables.clear(); const quickFixes = this._quickFixes; From 63f20de2f139ef2843f25a4bad0a4ff256717274 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 06:20:17 -0700 Subject: [PATCH 1383/4355] Tweak wording --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index f8980e03e06..aea2d085ed1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -595,7 +595,7 @@ const terminalConfiguration: IStringDictionary = { }, [TerminalSettingId.ShellIntegrationQuickFixEnabled]: { restricted: true, - markdownDescription: localize('terminal.integrated.shellIntegration.quickFixEnabled', "When shell integration is enabled, show quick fixes for failed terminal commands. This includes the lightbulb icon and AI sparkle icon."), + markdownDescription: localize('terminal.integrated.shellIntegration.quickFixEnabled', "When shell integration is enabled, enables quick fixes for terminal commands that appear as a lightbulb or sparkle icon to the left of the prompt."), type: 'boolean', default: true }, From 5c2c735b17f6c50b3cf6c501c399addf5af72ad7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 06:34:13 -0700 Subject: [PATCH 1384/4355] Be more explicit about windows powershell in tool desc Part of #262386 --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 94cdc2fa128..72861ea8830 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -51,12 +51,13 @@ import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; // #region Tool data function createPowerShellModelDescription(shell: string): string { + const isWinPwsh = isWindowsPowerShell(shell); return [ - 'This tool allows you to execute PowerShell commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.', + `This tool allows you to execute ${isWinPwsh ? 'Windows PowerShell 5.1' : 'PowerShell'} commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.`, '', 'Command Execution:', '- Does NOT support multi-line commands', - `- ${isWindowsPowerShell(shell) + `- ${isWinPwsh ? 'Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly' : 'Use && to chain simple commands on one line'}`, '- Prefer pipelines | for object-based data flow', From 048b29e98a84a97602fb6c30703dab8309aebb28 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:02:11 -0700 Subject: [PATCH 1385/4355] Ignore default auto approve rules Fixes #272250 FIxes #270641 --- .../browser/commandLineAutoApprover.ts | 13 +++++++++++-- .../common/terminalChatAgentToolsConfiguration.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 291d53028c9..b48404a18d5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -45,6 +45,7 @@ export class CommandLineAutoApprover extends Disposable { this._register(this._configurationService.onDidChangeConfiguration(e => { if ( e.affectsConfiguration(TerminalChatAgentToolsSettingId.AutoApprove) || + e.affectsConfiguration(TerminalChatAgentToolsSettingId.IgnoreDefaultAutoApproveRules) || e.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedAutoApproveCompatible) ) { this.updateConfiguration(); @@ -180,7 +181,9 @@ export class CommandLineAutoApprover extends Disposable { const allowListCommandLineRules: IAutoApproveRule[] = []; const denyListCommandLineRules: IAutoApproveRule[] = []; - Object.entries(config).forEach(([key, value]) => { + const ignoreDefaults = this._configurationService.getValue(TerminalChatAgentToolsSettingId.IgnoreDefaultAutoApproveRules) === true; + + for (const [key, value] of Object.entries(config)) { const defaultValue = configInspectValue?.default?.value; const isDefaultRule = !!( isObject(defaultValue) && @@ -203,6 +206,12 @@ export class CommandLineAutoApprover extends Disposable { : checkTarget(configInspectValue.applicationValue) ? ConfigurationTarget.APPLICATION : ConfigurationTarget.DEFAULT ); + + // If default rules are disabled, ignore entries that come from the default config + if (ignoreDefaults && isDefaultRule && sourceTarget === ConfigurationTarget.DEFAULT) { + continue; + } + if (typeof value === 'boolean') { const { regex, regexCaseInsensitive } = this._convertAutoApproveEntryToRegex(key); // IMPORTANT: Only true and false are used, null entries need to be ignored @@ -231,7 +240,7 @@ export class CommandLineAutoApprover extends Disposable { } } } - }); + } return { denyListRules, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index b0d475dd1c7..2239d6523cc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -14,6 +14,7 @@ import { terminalProfileBaseProperties } from '../../../../../platform/terminal/ export const enum TerminalChatAgentToolsSettingId { EnableAutoApprove = 'chat.tools.terminal.enableAutoApprove', AutoApprove = 'chat.tools.terminal.autoApprove', + IgnoreDefaultAutoApproveRules = 'chat.tools.terminal.ignoreDefaultAutoApproveRules', ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout', AutoReplyToPrompts = 'chat.tools.terminal.autoReplyToPrompts', OutputLocation = 'chat.tools.terminal.outputLocation', @@ -67,6 +68,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary, }, + [TerminalChatAgentToolsSettingId.IgnoreDefaultAutoApproveRules]: { + type: 'boolean', + default: false, + tags: ['experimental'], + markdownDescription: localize('ignoreDefaultAutoApproveRules.description', "Whether to ignore the built-in default auto-approve rules used by the run in terminal tool as defined in {0}. Setting to false will disable any rule the comes from the default set, enabling only rules from user, remote and workspace settings. Use this at your own risk, the default denial rules are designed to protect you against running dangerous commands.", `\`#${TerminalChatAgentToolsSettingId.AutoApprove}#\``), + }, [TerminalChatAgentToolsSettingId.ShellIntegrationTimeout]: { markdownDescription: localize('shellIntegrationTimeout.description', "Configures the duration in milliseconds to wait for shell integration to be detected when the run in terminal tool launches a new terminal. Set to `0` to wait the minimum time, the default value `-1` means the wait time is variable based on the value of {0} and whether it's a remote window. A large value can be useful if your shell starts very slowly and a low value if you're intentionally not using shell integration.", `\`#${TerminalSettingId.ShellIntegrationEnabled}#\``), type: 'integer', @@ -387,8 +395,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Tue, 21 Oct 2025 07:06:23 -0700 Subject: [PATCH 1386/4355] Add simple tests for ignoreDefaultAutoApproveRules --- .../browser/commandLineAutoApprover.test.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts index 224f6af84d3..3ef59e07877 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts @@ -1114,4 +1114,74 @@ suite('CommandLineAutoApprover', () => { strictEqual(getCommandLineIsDefaultRule('cmd2 arg'), true, 'Object type should match default using structural equality (even though it\'s a deny rule)'); }); }); + + suite('ignoreDefaultAutoApproveRules', () => { + function setAutoApproveWithDefaults(userConfig: { [key: string]: boolean }, defaultConfig: { [key: string]: boolean }) { + // Set up mock configuration with default values + configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AutoApprove, userConfig); + + // Mock the inspect method to return default values + const originalInspect = configurationService.inspect; + const originalGetValue = configurationService.getValue; + + configurationService.inspect = (key: string): any => { + if (key === TerminalChatAgentToolsSettingId.AutoApprove) { + return { + default: { value: defaultConfig }, + user: { value: userConfig }, + workspace: undefined, + workspaceFolder: undefined, + application: undefined, + policy: undefined, + memory: undefined, + value: { ...defaultConfig, ...userConfig } + }; + } + return originalInspect.call(configurationService, key); + }; + + configurationService.getValue = (key: string): any => { + if (key === TerminalChatAgentToolsSettingId.AutoApprove) { + return { ...defaultConfig, ...userConfig }; + } + return originalGetValue.call(configurationService, key); + }; + + // Trigger configuration update + configurationService.onDidChangeConfigurationEmitter.fire({ + affectsConfiguration: () => true, + affectedKeys: new Set([TerminalChatAgentToolsSettingId.AutoApprove]), + source: ConfigurationTarget.USER, + change: null!, + }); + } + + function setIgnoreDefaultAutoApproveRules(value: boolean) { + setConfig(TerminalChatAgentToolsSettingId.IgnoreDefaultAutoApproveRules, value); + } + + test('should include default rules when ignoreDefaultAutoApproveRules is false (default behavior)', () => { + setAutoApproveWithDefaults( + { 'ls': true }, + { 'echo': true, 'cat': true } + ); + setIgnoreDefaultAutoApproveRules(false); + + ok(isAutoApproved('ls -la'), 'User-defined rule should work'); + ok(isAutoApproved('echo hello'), 'Default rule should work when not ignored'); + ok(isAutoApproved('cat file.txt'), 'Default rule should work when not ignored'); + }); + + test('should exclude default rules when ignoreDefaultAutoApproveRules is true', () => { + setAutoApproveWithDefaults( + { 'ls': true }, + { 'echo': true, 'cat': true } + ); + setIgnoreDefaultAutoApproveRules(true); + + ok(isAutoApproved('ls -la'), 'User-defined rule should still work'); + ok(!isAutoApproved('echo hello'), 'Default rule should be ignored'); + ok(!isAutoApproved('cat file.txt'), 'Default rule should be ignored'); + }); + }); }); From d875d4501fa54f555ae598df15e165aaf45071e9 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:06:35 +0200 Subject: [PATCH 1387/4355] Use Inline Suggestions naming (#272447) * Use Inline Suggestions naming * revert sound change --- README.md | 2 +- .../json-language-features/server/README.md | 4 +- .../services/inlineCompletionsService.ts | 2 +- src/vs/editor/common/standaloneStrings.ts | 2 +- .../browser/controller/commands.ts | 40 +------------------ .../browser/inlineCompletions.contribution.ts | 6 +-- .../chat/browser/actions/chatActions.ts | 10 ++--- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/chat/browser/chatStatus.ts | 26 ++++++------ .../common/gettingStartedContent.ts | 2 +- .../chat/common/chatEntitlementService.ts | 2 +- 11 files changed, 29 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index b7a101827f6..9ef3a879555 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Many of the core components and extensions to VS Code live in their own reposito ## Bundled Extensions -VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. +VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (inline suggestions, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. ## Development Container diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 1c382916072..8047006f4e3 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -17,7 +17,7 @@ The JSON language server supports requests on documents of language id `json` an The server implements the following capabilities of the language server protocol: -- [Code completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for JSON properties and values based on the document's [JSON schema](http://json-schema.org/) or based on existing properties and values used at other places in the document. JSON schemas are configured through the server configuration options. +- [Inline Suggestion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for JSON properties and values based on the document's [JSON schema](http://json-schema.org/) or based on existing properties and values used at other places in the document. JSON schemas are configured through the server configuration options. - [Hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover) for values based on descriptions in the document's [JSON schema](http://json-schema.org/). - [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to properties in the document. - [Document Colors](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor) for showing color decorators on values representing colors and [Color Presentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation) for color presentation information to support color pickers. The location of colors is defined by the document's [JSON schema](http://json-schema.org/). All values marked with `"format": "color-hex"` (VSCode specific, non-standard JSON Schema extension) are considered color values. The supported color formats are `#rgb[a]` and `#rrggbb[aa]`. @@ -37,7 +37,7 @@ The JSON language server expects the client to only send requests and notificati The JSON language server has the following dependencies on the client's capabilities: -- Code completion requires that the client capability has *snippetSupport*. If not supported by the client, the server will not offer the completion capability. +- Inline suggestion requires that the client capability has *snippetSupport*. If not supported by the client, the server will not offer the completion capability. - Formatting support requires the client to support *dynamicRegistration* for *rangeFormatting*. If not supported by the client, the server will not offer the format capability. ## Configuration diff --git a/src/vs/editor/browser/services/inlineCompletionsService.ts b/src/vs/editor/browser/services/inlineCompletionsService.ts index 260d617e7af..0f8fcc0cfdb 100644 --- a/src/vs/editor/browser/services/inlineCompletionsService.ts +++ b/src/vs/editor/browser/services/inlineCompletionsService.ts @@ -222,7 +222,7 @@ export class SnoozeInlineCompletion extends Action2 { ]; const picked = await quickInputService.pick(items, { - placeHolder: localize('snooze.placeholder', "Select snooze duration for Code completions and NES"), + placeHolder: localize('snooze.placeholder', "Select snooze duration for Inline Suggestions"), activeItem: items.find(item => item.value === lastSelectedDuration), }); diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 262c6383f9e..f15817953a8 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -24,7 +24,7 @@ export namespace AccessibilityHelpNLS { export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior{0}.", ''); export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior{0}.", ''); export const stickScroll = nls.localize("stickScrollKb", "Focus Sticky Scroll{0} to focus the currently nested scopes.", ''); - export const suggestActions = nls.localize("suggestActionsKb", "Trigger the suggest widget{0} to show possible code completions.", ''); + export const suggestActions = nls.localize("suggestActionsKb", "Trigger the suggest widget{0} to show possible inline suggestions.", ''); export const acceptSuggestAction = nls.localize("acceptSuggestAction", "Accept suggestion{0} to accept the currently selected suggestion.", ''); export const toggleSuggestionFocus = nls.localize("toggleSuggestionFocus", "Toggle focus between the suggest widget and the editor{0} and toggle details focus with{1} to learn more about the suggestion.", '', ''); export const codeFolding = nls.localize("codeFolding", "Use code folding to collapse blocks of code and focus on the code you're interested in via the Toggle Folding Command{0}.", ''); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index 68c78205585..58846c3a386 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -13,9 +13,8 @@ import { IClipboardService } from '../../../../../platform/clipboard/common/clip import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; -import { EditorAction, EditorCommand, ServicesAccessor } from '../../../../browser/editorExtensions.js'; +import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../common/editorContextKeys.js'; import { Context as SuggestContext } from '../../../suggest/browser/suggest.js'; import { hideInlineCompletionId, inlineSuggestCommitId, jumpToNextInlineEditId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, toggleShowCollapsedId } from './commandIds.js'; @@ -81,43 +80,6 @@ export class TriggerInlineSuggestionAction extends EditorAction { } } -export class ExplicitTriggerInlineEditAction extends EditorAction { - constructor() { - super({ - id: 'editor.action.inlineSuggest.triggerInlineEditExplicit', - label: nls.localize2('action.inlineSuggest.trigger.explicitInlineEdit', "Trigger Next Edit Suggestion"), - precondition: EditorContextKeys.writable, - }); - } - - public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const notificationService = accessor.get(INotificationService); - const controller = InlineCompletionsController.get(editor); - - await controller?.model.get()?.triggerExplicitly(undefined, true); - if (!controller?.model.get()?.inlineEditAvailable.get()) { - notificationService.notify({ - severity: Severity.Info, - message: nls.localize('noInlineEditAvailable', "No inline edit is available.") - }); - } - } -} - -export class TriggerInlineEditAction extends EditorCommand { - constructor() { - super({ - id: 'editor.action.inlineSuggest.triggerInlineEdit', - precondition: EditorContextKeys.writable, - }); - } - - public override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: { triggerKind?: 'automatic' | 'explicit' }): Promise { - const controller = InlineCompletionsController.get(editor); - await controller?.model.get()?.trigger(undefined, { onlyFetchInlineEdits: true }); - } -} - export class AcceptNextWordOfInlineCompletion extends EditorAction { constructor() { super({ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts index f0294b339b5..1885a412fb2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts @@ -6,9 +6,9 @@ import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { registerAction2 } from '../../../../platform/actions/common/actions.js'; import { wrapInHotClass1 } from '../../../../platform/observable/common/wrapInHotClass.js'; -import { EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js'; +import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from '../../../browser/editorExtensions.js'; import { HoverParticipantRegistry } from '../../hover/browser/hoverTypes.js'; -import { AcceptInlineCompletion, AcceptNextLineOfInlineCompletion, AcceptNextWordOfInlineCompletion, DevExtractReproSample, HideInlineCompletion, JumpToNextInlineEdit, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, ToggleAlwaysShowInlineSuggestionToolbar, ExplicitTriggerInlineEditAction, TriggerInlineSuggestionAction, TriggerInlineEditAction, ToggleInlineCompletionShowCollapsed } from './controller/commands.js'; +import { AcceptInlineCompletion, AcceptNextLineOfInlineCompletion, AcceptNextWordOfInlineCompletion, DevExtractReproSample, HideInlineCompletion, JumpToNextInlineEdit, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, ToggleAlwaysShowInlineSuggestionToolbar, TriggerInlineSuggestionAction, ToggleInlineCompletionShowCollapsed } from './controller/commands.js'; import { InlineCompletionsController } from './controller/inlineCompletionsController.js'; import { InlineCompletionsHoverParticipant } from './hintsWidget/hoverParticipant.js'; import { InlineCompletionsAccessibleView } from './inlineCompletionsAccessibleView.js'; @@ -17,8 +17,6 @@ import { CancelSnoozeInlineCompletion, SnoozeInlineCompletion } from '../../../b registerEditorContribution(InlineCompletionsController.ID, wrapInHotClass1(InlineCompletionsController.hot), EditorContributionInstantiation.Eventually); registerEditorAction(TriggerInlineSuggestionAction); -registerEditorAction(ExplicitTriggerInlineEditAction); -registerEditorCommand(new TriggerInlineEditAction()); registerEditorAction(ShowNextInlineSuggestionAction); registerEditorAction(ShowPreviousInlineSuggestionAction); registerEditorAction(AcceptNextWordOfInlineCompletion); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 1df45c2c169..51c8fec2a88 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1462,7 +1462,7 @@ export function registerChatActions() { constructor() { super({ id: 'workbench.action.chat.configureCodeCompletions', - title: localize2('configureCompletions', "Configure Code Completions..."), + title: localize2('configureCompletions', "Configure Inline Suggestions..."), precondition: ContextKeyExpr.and( ChatContextKeys.Setup.installed, ChatContextKeys.Setup.disabled.negate(), @@ -1501,11 +1501,11 @@ export function registerChatActions() { const chatQuotaExceeded = chatEntitlementService.quotas.chat?.percentRemaining === 0; const completionsQuotaExceeded = chatEntitlementService.quotas.completions?.percentRemaining === 0; if (chatQuotaExceeded && !completionsQuotaExceeded) { - message = localize('chatQuotaExceeded', "You've reached your monthly chat messages quota. You still have free code completions available."); + message = localize('chatQuotaExceeded', "You've reached your monthly chat messages quota. You still have free inline suggestions available."); } else if (completionsQuotaExceeded && !chatQuotaExceeded) { - message = localize('completionsQuotaExceeded', "You've reached your monthly code completions quota. You still have free chat messages available."); + message = localize('completionsQuotaExceeded', "You've reached your monthly inline suggestions quota. You still have free chat messages available."); } else { - message = localize('chatAndCompletionsQuotaExceeded', "You've reached your monthly chat messages and code completions quota."); + message = localize('chatAndCompletionsQuotaExceeded', "You've reached your monthly chat messages and inline suggestions quota."); } if (chatEntitlementService.quotas.resetDate) { @@ -1515,7 +1515,7 @@ export function registerChatActions() { } const free = chatEntitlementService.entitlement === ChatEntitlement.Free; - const upgradeToPro = free ? localize('upgradeToPro', "Upgrade to GitHub Copilot Pro (your first 30 days are free) for:\n- Unlimited code completions\n- Unlimited chat messages\n- Access to premium models") : undefined; + const upgradeToPro = free ? localize('upgradeToPro', "Upgrade to GitHub Copilot Pro (your first 30 days are free) for:\n- Unlimited inline suggestions\n- Unlimited chat messages\n- Access to premium models") : undefined; await dialogService.prompt({ type: 'none', diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c79d3b3fb10..469ddcce680 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -694,7 +694,7 @@ configurationRegistry.registerConfiguration({ }, 'chat.disableAIFeatures': { type: 'boolean', - description: nls.localize('chat.disableAIFeatures', "Disable and hide built-in AI features provided by GitHub Copilot, including chat, code completions and next edit suggestions."), + description: nls.localize('chat.disableAIFeatures', "Disable and hide built-in AI features provided by GitHub Copilot, including chat and inline suggestions."), default: false, scope: ConfigurationScope.WINDOW }, diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 63b9e0c04bc..1025c89b78f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -236,7 +236,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu if (chatQuotaExceeded && !completionsQuotaExceeded) { quotaWarning = localize('chatQuotaExceededStatus', "Chat quota reached"); } else if (completionsQuotaExceeded && !chatQuotaExceeded) { - quotaWarning = localize('completionsQuotaExceededStatus', "Completions quota reached"); + quotaWarning = localize('completionsQuotaExceededStatus', "Inline suggestions quota reached"); } else { quotaWarning = localize('chatAndCompletionsQuotaExceededStatus', "Quota reached"); } @@ -249,13 +249,13 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu // Completions Disabled else if (this.editorService.activeTextEditorLanguageId && !isCompletionsEnabled(this.configurationService, this.editorService.activeTextEditorLanguageId)) { text = '$(copilot-unavailable)'; - ariaLabel = localize('completionsDisabledStatus', "Code completions disabled"); + ariaLabel = localize('completionsDisabledStatus', "Inline suggestions disabled"); } // Completions Snoozed else if (this.completionsService.isSnoozing()) { text = '$(copilot-snooze)'; - ariaLabel = localize('completionsSnoozedStatus', "Code completions snoozed"); + ariaLabel = localize('completionsSnoozedStatus', "Inline suggestions snoozed"); } } @@ -392,7 +392,7 @@ class ChatStatusDashboard extends Disposable { run: () => this.runCommandAndClose(() => this.openerService.open(URI.parse(defaultChat.manageSettingsUrl))), })); - const completionsQuotaIndicator = completionsQuota && (completionsQuota.total > 0 || completionsQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, completionsQuota, localize('completionsLabel', "Code completions"), false) : undefined; + const completionsQuotaIndicator = completionsQuota && (completionsQuota.total > 0 || completionsQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, completionsQuota, localize('completionsLabel', "Inline Suggestions"), false) : undefined; const chatQuotaIndicator = chatQuota && (chatQuota.total > 0 || chatQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, chatQuota, localize('chatsLabel', "Chat messages"), false) : undefined; const premiumChatQuotaIndicator = premiumChatQuota && (premiumChatQuota.total > 0 || premiumChatQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, premiumChatQuota, localize('premiumChatsLabel', "Premium requests"), true) : undefined; @@ -429,7 +429,7 @@ class ChatStatusDashboard extends Disposable { else if (this.chatEntitlementService.anonymous && this.chatEntitlementService.sentiment.installed) { addSeparator(localize('anonymousTitle', "Copilot Usage")); - this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('completionsLabel', "Code completions"), false); + this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('completionsLabel', "Inline Suggestions"), false); this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('chatsLabel', "Chat messages"), false); } @@ -493,7 +493,7 @@ class ChatStatusDashboard extends Disposable { // Settings { const chatSentiment = this.chatEntitlementService.sentiment; - addSeparator(localize('codeCompletions', "Code Completions"), chatSentiment.installed && !chatSentiment.disabled && !chatSentiment.untrusted ? toAction({ + addSeparator(localize('inlineSuggestions', "Inline Suggestions"), chatSentiment.installed && !chatSentiment.disabled && !chatSentiment.untrusted ? toAction({ id: 'workbench.action.openChatSettings', label: localize('settingsLabel', "Settings"), tooltip: localize('settingsTooltip', "Open Settings"), @@ -699,14 +699,14 @@ class ChatStatusDashboard extends Disposable { const modeId = this.editorService.activeTextEditorLanguageId; const settings = container.appendChild($('div.settings')); - // --- Code completions + // --- Inline Suggestions { const globalSetting = append(settings, $('div.setting')); - this.createCodeCompletionsSetting(globalSetting, localize('settings.codeCompletions.allFiles', "All files"), '*', disposables); + this.createInlineSuggestionsSetting(globalSetting, localize('settings.codeCompletions.allFiles', "All files"), '*', disposables); if (modeId) { const languageSetting = append(settings, $('div.setting')); - this.createCodeCompletionsSetting(languageSetting, localize('settings.codeCompletions.language', "{0}", this.languageService.getLanguageName(modeId) ?? modeId), modeId, disposables); + this.createInlineSuggestionsSetting(languageSetting, localize('settings.codeCompletions.language', "{0}", this.languageService.getLanguageName(modeId) ?? modeId), modeId, disposables); } } @@ -756,7 +756,7 @@ class ChatStatusDashboard extends Disposable { return checkbox; } - private createCodeCompletionsSetting(container: HTMLElement, label: string, modeId: string | undefined, disposables: DisposableStore): void { + private createInlineSuggestionsSetting(container: HTMLElement, label: string, modeId: string | undefined, disposables: DisposableStore): void { this.createSetting(container, [defaultChat.completionsEnablementSetting], label, this.getCompletionsSettingAccessor(modeId), disposables); } @@ -846,10 +846,10 @@ class ChatStatusDashboard extends Disposable { const timeLeftMs = this.inlineCompletionsService.snoozeTimeLeft; if (!isEnabled || timeLeftMs <= 0) { - timerDisplay.textContent = localize('completions.snooze5minutesTitle', "Hide completions for 5 min"); + timerDisplay.textContent = localize('completions.snooze5minutesTitle', "Hide suggestions for 5 min"); timerDisplay.title = ''; button.label = label; - button.setTitle(localize('completions.snooze5minutes', "Hide completions and NES for 5 min")); + button.setTitle(localize('completions.snooze5minutes', "Hide inline suggestions for 5 min")); return true; } @@ -858,7 +858,7 @@ class ChatStatusDashboard extends Disposable { const seconds = timeLeftSeconds % 60; timerDisplay.textContent = `${minutes}:${seconds < 10 ? '0' : ''}${seconds} ${localize('completions.remainingTime', "remaining")}`; - timerDisplay.title = localize('completions.snoozeTimeDescription', "Completions are hidden for the remaining duration"); + timerDisplay.title = localize('completions.snoozeTimeDescription', "Inline suggestions are hidden for the remaining duration"); button.label = localize('completions.plus5min', "+5 min"); button.setTitle(localize('completions.snoozeAdditional5minutes', "Snooze additional 5 min")); toolbar.push([cancelAction], { icon: true, label: false }); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 64b06614e3e..8264534af8d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -328,7 +328,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ { id: 'findLanguageExtensionsWeb', title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"), - description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), + description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, inline suggestions, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), when: 'workspacePlatform != \'webworker\'', media: { type: 'svg', altText: 'Language extensions', path: 'languages.svg' diff --git a/src/vs/workbench/services/chat/common/chatEntitlementService.ts b/src/vs/workbench/services/chat/common/chatEntitlementService.ts index ac2e2ee5021..4dbad52014d 100644 --- a/src/vs/workbench/services/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/services/chat/common/chatEntitlementService.ts @@ -472,7 +472,7 @@ type EntitlementClassification = { sku: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The SKU of the chat entitlement' }; quotaChat: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of chat requests available to the user' }; quotaPremiumChat: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of premium chat requests available to the user' }; - quotaCompletions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of code completions available to the user' }; + quotaCompletions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of inline suggestions available to the user' }; quotaResetDate: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The date the quota will reset' }; owner: 'bpasero'; comment: 'Reporting chat entitlements'; From ca9fc427e2159c59f01171686e32112baeebf144 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:21:11 -0700 Subject: [PATCH 1388/4355] Add Sync suffix and jsdoc to tree sitter service --- .../treeSitterSyntaxTokenBackend.ts | 6 +-- .../treeSitter/treeSitterLibraryService.ts | 53 +++++++++++++++---- .../standaloneTreeSitterLibraryService.ts | 6 +-- .../services/testTreeSitterLibraryService.ts | 10 ++-- .../browser/commandLineAutoApprover.ts | 12 ++--- .../browser/treeSitterLibraryService.ts | 8 +-- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts index 5d61d664043..d322f530088 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts @@ -54,7 +54,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { } const currentLanguage = this._languageIdObs.read(reader); - const treeSitterLang = this._treeSitterLibraryService.getLanguage(currentLanguage, reader); + const treeSitterLang = this._treeSitterLibraryService.getLanguageSync(currentLanguage, reader); if (!treeSitterLang) { return undefined; } @@ -65,7 +65,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { })); parser.setLanguage(treeSitterLang); - const queries = this._treeSitterLibraryService.getInjectionQueries(currentLanguage, reader); + const queries = this._treeSitterLibraryService.getInjectionQueriesSync(currentLanguage, reader); if (queries === undefined) { return undefined; } @@ -80,7 +80,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { return undefined; } - const queries = this._treeSitterLibraryService.getHighlightingQueries(treeModel.languageId, reader); + const queries = this._treeSitterLibraryService.getHighlightingQueriesSync(treeModel.languageId, reader); if (!queries) { return undefined; } diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index e4588e6348d..003ee708683 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -12,22 +12,55 @@ export const ITreeSitterLibraryService = createDecorator; + /** + * Checks whether a language is supported and available based setting enablement. + * @param languageId The language identifier to check. + * @param reader Optional observable reader. + */ supportsLanguage(languageId: string, reader: IReader | undefined): boolean; - getLanguage(languageId: string, reader: IReader | undefined): Language | undefined; + /** - * Return value of null indicates that there are no injection queries for this language. - * @param languageId - * @param reader + * Gets the Tree-sitter Language object synchronously. + * + * Note that This method runs synchronously and may fail if the language is + * not yet cached, as synchronous methods are required by editor APIs. + * @param languageId The language identifier to retrieve. + * @param reader Optional observable reader. */ - getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined; + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined; + /** - * Return value of null indicates that there are no highlights queries for this language. - * @param languageId - * @param reader + * Gets the injection queries for a language. A return value of `null` + * indicates that there are no highlights queries for this language. + * + * Note that This method runs synchronously and may fail if the language is + * not yet cached, as synchronous methods are required by editor APIs. + * @param languageId The language identifier to retrieve queries for. + * @param reader Optional observable reader. */ - getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined; + getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined; - createQuery(languageId: string, reader: IReader | undefined, querySource: string): Promise; + /** + * Gets the highlighting queries for a language. A return value of `null` + * indicates that there are no highlights queries for this language. + * + * Note that This method runs synchronously and may fail if the language is + * not yet cached, as synchronous methods are required by editor APIs. + * @param languageId The language identifier to retrieve queries for. + * @param reader Optional observable reader. + */ + getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined; + + /** + * Creates a custom query for a language. Returns undefiend if + * @param languageId The language identifier to create the query for. + * @param reader Optional observable reader. + * @param querySource The query source string to compile. + */ + createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise; } diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index f5e4f3f95f4..121d877cbd6 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -18,7 +18,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer return false; } - getLanguage(languageId: string, reader: IReader | undefined): Language | undefined { + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { return undefined; } /** @@ -26,7 +26,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer * @param languageId * @param reader */ - getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } /** @@ -34,7 +34,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer * @param languageId * @param reader */ - getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index e07e75740a9..b5416d9cfad 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -18,15 +18,19 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { return false; } - getLanguage(languageId: string, reader: IReader | undefined): Language | undefined { + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { return undefined; } - getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } - getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } + + async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { + return undefined; + } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 1a8af7f8d7d..89c6aad84c7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -57,18 +57,18 @@ export class CommandLineAutoApprover extends Disposable { const parserClass = this._treeSitterLibraryService.getParserClass(); parserClass.then(async parserCtor => { // HACK: Trigger async load - _treeSitterLibraryService.getLanguage('bash', undefined); - _treeSitterLibraryService.getLanguage('powershell', undefined); + _treeSitterLibraryService.getLanguageSync('bash', undefined); + _treeSitterLibraryService.getLanguageSync('powershell', undefined); await timeout(1000); - const bashLang = _treeSitterLibraryService.getLanguage('bash', undefined); - const pwshLang = _treeSitterLibraryService.getLanguage('powershell', undefined); + const bashLang = _treeSitterLibraryService.getLanguageSync('bash', undefined); + const pwshLang = _treeSitterLibraryService.getLanguageSync('powershell', undefined); const parser = new parserCtor(); if (bashLang) { parser.setLanguage(bashLang); const tree = parser.parse('echo "$(evil) a|b|c" | ls'); - const q = await _treeSitterLibraryService.createQuery('bash', undefined, '(command) @command'); + const q = await _treeSitterLibraryService.createQuery('bash', '(command) @command', undefined); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); @@ -80,7 +80,7 @@ export class CommandLineAutoApprover extends Disposable { parser.setLanguage(pwshLang); const tree = parser.parse('Get-ChildItem | Write-Host "$(evil)"'); - const q = await _treeSitterLibraryService.createQuery('powershell', undefined, '(command\ncommand_name: (command_name) @function)'); + const q = await _treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)', undefined); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index c641fd48dce..1fafa751551 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -121,7 +121,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return treeSitter.Parser; } - getLanguage(languageId: string, reader: IReader | undefined): Language | undefined { + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; } @@ -129,7 +129,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return lang; } - getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; } @@ -137,7 +137,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return query; } - getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; } @@ -145,7 +145,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return query; } - async createQuery(languageId: string, reader: IReader | undefined, querySource: string): Promise { + async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { if (!this.supportsLanguage(languageId, reader)) { return undefined; } From db4d2b246a15d5c608ca722aa2afcf3526cf9c13 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:26:14 -0700 Subject: [PATCH 1389/4355] Update src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../common/terminalChatAgentToolsConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 2239d6523cc..cba511d00cf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -330,7 +330,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Tue, 21 Oct 2025 07:26:46 -0700 Subject: [PATCH 1390/4355] Add ITreeSitterLibraryService.getLanguage async api --- .../treeSitter/treeSitterLibraryService.ts | 7 ++++++ .../standaloneTreeSitterLibraryService.ts | 22 +++++++++---------- .../services/testTreeSitterLibraryService.ts | 6 ++++- .../browser/treeSitterLibraryService.ts | 4 ++++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index 003ee708683..78146ef0dca 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -24,6 +24,13 @@ export interface ITreeSitterLibraryService { */ supportsLanguage(languageId: string, reader: IReader | undefined): boolean; + /** + * Gets the Tree-sitter Language object. + * @param languageId The language identifier to retrieve. + * @param reader Optional observable reader. + */ + getLanguage(languageId: string): Promise; + /** * Gets the Tree-sitter Language object synchronously. * diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index 121d877cbd6..837ef46ce7a 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -11,30 +11,30 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer readonly _serviceBrand: undefined; getParserClass(): Promise { - throw new Error('getParserClass is not implemented in StandaloneTreeSitterLibraryService'); + throw new Error('not implemented in StandaloneTreeSitterLibraryService'); } supportsLanguage(languageId: string, reader: IReader | undefined): boolean { return false; } + async getLanguage(languageId: string): Promise { + throw new Error('not implemented in TestTreeSitterLibraryService'); + } + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { return undefined; } - /** - * Return value of null indicates that there are no injection queries for this language. - * @param languageId - * @param reader - */ + getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } - /** - * Return value of null indicates that there are no highlights queries for this language. - * @param languageId - * @param reader - */ + getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } + + async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { + return undefined; + } } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index b5416d9cfad..f90d98ee7f4 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -11,13 +11,17 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { readonly _serviceBrand: undefined; getParserClass(): Promise { - throw new Error('getParserClass is not implemented in TestTreeSitterLibraryService'); + throw new Error('not implemented in TestTreeSitterLibraryService'); } supportsLanguage(languageId: string, reader: IReader | undefined): boolean { return false; } + async getLanguage(languageId: string): Promise { + throw new Error('not implemented in TestTreeSitterLibraryService'); + } + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { return undefined; } diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 1fafa751551..07be55eaf06 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -121,6 +121,10 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return treeSitter.Parser; } + async getLanguage(languageId: string): Promise { + return this._languagesCache.get(languageId).promise; + } + getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; From d9fe576718e824fd1f2da4ccd0422a2b344edef6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:32:55 -0700 Subject: [PATCH 1391/4355] Use async API in test code --- .../browser/commandLineAutoApprover.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 89c6aad84c7..79c105eb84e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -12,7 +12,6 @@ import { ConfigurationTarget, IConfigurationService, type IConfigurationValue } import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; import { isPowerShell } from './runInTerminalHelpers.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import { timeout } from '../../../../../base/common/async.js'; export interface IAutoApproveRule { regex: RegExp; @@ -56,19 +55,15 @@ export class CommandLineAutoApprover extends Disposable { const parserClass = this._treeSitterLibraryService.getParserClass(); parserClass.then(async parserCtor => { - // HACK: Trigger async load - _treeSitterLibraryService.getLanguageSync('bash', undefined); - _treeSitterLibraryService.getLanguageSync('powershell', undefined); - await timeout(1000); - const bashLang = _treeSitterLibraryService.getLanguageSync('bash', undefined); - const pwshLang = _treeSitterLibraryService.getLanguageSync('powershell', undefined); + const bashLang = await this._treeSitterLibraryService.getLanguage('bash'); + const pwshLang = await this._treeSitterLibraryService.getLanguage('powershell'); const parser = new parserCtor(); if (bashLang) { parser.setLanguage(bashLang); const tree = parser.parse('echo "$(evil) a|b|c" | ls'); - const q = await _treeSitterLibraryService.createQuery('bash', '(command) @command', undefined); + const q = await this._treeSitterLibraryService.createQuery('bash', '(command) @command', undefined); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); @@ -80,7 +75,7 @@ export class CommandLineAutoApprover extends Disposable { parser.setLanguage(pwshLang); const tree = parser.parse('Get-ChildItem | Write-Host "$(evil)"'); - const q = await _treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)', undefined); + const q = await this._treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)', undefined); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); From 9013d2a9ecc09ee2d0e6885e14eca29d3c09c7d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 08:04:32 -0700 Subject: [PATCH 1392/4355] Update tree sitter, fix createQuery api --- package-lock.json | 8 ++++---- package.json | 2 +- .../services/treeSitter/treeSitterLibraryService.ts | 2 +- .../browser/standaloneTreeSitterLibraryService.ts | 6 +++--- .../test/common/services/testTreeSitterLibraryService.ts | 4 ++-- .../chatAgentTools/browser/commandLineAutoApprover.ts | 4 ++-- .../treeSitter/browser/treeSitterLibraryService.ts | 5 +---- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index e322ffc91d9..879bca4ff26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", @@ -3319,9 +3319,9 @@ } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.2.0.tgz", + "integrity": "sha512-abvLfKwmriqgdS4WrIzFK7mzdPUVqIIW1UWarp2lA8lpOZ1EDPL1snRBKe7g+5R5ri173mNJEuPLnG/NlpMp4w==", "license": "MIT" }, "node_modules/@vscode/v8-heap-parser": { diff --git a/package.json b/package.json index aa84a3b0265..f2adbeeea1a 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index 78146ef0dca..096cb8ded88 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -69,5 +69,5 @@ export interface ITreeSitterLibraryService { * @param reader Optional observable reader. * @param querySource The query source string to compile. */ - createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise; + createQuery(languageId: string, querySource: string): Promise; } diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index 837ef46ce7a..123cb671ee4 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -19,7 +19,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer } async getLanguage(languageId: string): Promise { - throw new Error('not implemented in TestTreeSitterLibraryService'); + throw new Error('not implemented in StandaloneTreeSitterLibraryService'); } getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { @@ -34,7 +34,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer return null; } - async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { - return undefined; + async createQuery(languageId: string, querySource: string): Promise { + throw new Error('not implemented in StandaloneTreeSitterLibraryService'); } } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index f90d98ee7f4..d90cfd0b67b 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -34,7 +34,7 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { return null; } - async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { - return undefined; + async createQuery(languageId: string, querySource: string): Promise { + throw new Error('not implemented in TestTreeSitterLibraryService'); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 79c105eb84e..73a83f5cec4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -63,7 +63,7 @@ export class CommandLineAutoApprover extends Disposable { parser.setLanguage(bashLang); const tree = parser.parse('echo "$(evil) a|b|c" | ls'); - const q = await this._treeSitterLibraryService.createQuery('bash', '(command) @command', undefined); + const q = await this._treeSitterLibraryService.createQuery('bash', '(command) @command'); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); @@ -75,7 +75,7 @@ export class CommandLineAutoApprover extends Disposable { parser.setLanguage(pwshLang); const tree = parser.parse('Get-ChildItem | Write-Host "$(evil)"'); - const q = await this._treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)', undefined); + const q = await this._treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)'); if (tree && q) { const captures = q.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 07be55eaf06..82ef2193d31 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -149,10 +149,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return query; } - async createQuery(languageId: string, querySource: string, reader: IReader | undefined): Promise { - if (!this.supportsLanguage(languageId, reader)) { - return undefined; - } + async createQuery(languageId: string, querySource: string): Promise { const [ language, treeSitter From ba0a3c7efedce8c326156fde873749b636364e6e Mon Sep 17 00:00:00 2001 From: Lee Murray Date: Tue, 21 Oct 2025 16:06:13 +0100 Subject: [PATCH 1393/4355] Adjust extension badge positioning in extension list items (#272254) * extensions: adjust extension badge positioning in extension list items * extensions: nudge extension badge down 1px in extension list items --------- Co-authored-by: mrleemurray --- .../workbench/contrib/extensions/browser/media/extension.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index db553e5f64c..0b4d2dd3aa0 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -28,7 +28,8 @@ .extension-list-item > .icon-container .extension-badge { position: absolute; - bottom: 5px; + bottom: 10px; + left: -8px; } .extension-list-item > .icon-container .extension-badge.extension-icon-badge { From 2b8dab13c88610b4e9bc39e790d41bd74df6ebc4 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 21 Oct 2025 16:16:33 +0100 Subject: [PATCH 1394/4355] chore(codicons): regenerate codicon.ttf with latest glyph updates --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119388 -> 119432 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 60af8275d291243c9d7bbff24e97f4cd2f355548..b9ffca587229bded535465d94fdf375cae70131b 100644 GIT binary patch delta 2157 zcma)53rtj38ouY;Co{v$Wq`Rn#$gILLqTSEi~}O&{dS6oh=_>1iU^1Z6md|7M@vUK zQkY>;kwvA5lp2D(q!v+;v<>MRV~8P!WeG86LtNt;(y+ws?itP2ns&2$a!+#3_n-TJ z|M~v!EcVgHD|B0gAVt^kCk*qc!hmtDUANhO&%ahN3_Qay`md!&^K%uiW?S1o@vtjG z;ClJd%Bp*Rk^cHST*O~W!MGrNV!o#b=xb-EXOFdnm>rp@xrKPrTZ3dNXWN%51i z#h|?J)&G8$H_to8+vGj%{nUr>k@>{=6!>&)5pP-Y75awxwyI>Rld6a62=$11!_V8V z!f(Luk-x&f$bZm(Bfu-*d0WusQuzkh*Ml$7@<@;0jPwijJl4Vt8tsD>@s5>x|O=ZVrPaM=9>^ZoeBhQJ;(dW$P z3UkwQjky!Kt9dSYQF$$S(|J$xRrz`OE&0O*u)z2c^-*S_Utx3MqeH=m>Wfl~Ohw-v zc0b&6_`70haZ&MPi9<Lc@IY*}Ghf7#m6q@&a2((-}w zm&YQGbyuh=x+}$%F_pcQD^)We`+YoAja6qKhsTY_*K2ZWI%*b9c%JAz@v}ZdKcHW& z)zntkj@53QOg;JV6m}}=)LLD3-8F;CuvA~xKs5w6R5uJYmNt$xu}vLKb4@FTPeMP{ zeY(`lHg|s}{jAN18TH16)0)%urx#kXKUaL-ZxWmIrl~W`8U2}s){xeY)>mH`zgTOl zXq#`3Y9BI-&2ttnOMzw9@}i^q4;!65XY0;BIhT2E=Dgs1`uWMOgs$le+6&X&#_pTl z&;R(S$Ehc&$K3Pq;+Bi87Z)yRFBy6-hg>#YUhg&b{_th+m-;@Uuki}+O3{@c`d#|_ zt}<7PuZ|1|2C@cTepUU|lfjU|#=*y5XMO$PTEVr|q1EBe;f?FE>n+z;M!ZLsN2Q}_ zqhq71V@YEx<1yoT<8|X*7PBZ(ME?H~YSoe*0`nJJoed>Um3l zYjHYey5+X`cKYpecl_?u-+6i0`|kXVY3Av@ynBnYnpyp5h0h*m+!y#-=3;!6A|M3T zxd}+PAZ<7Z0~&=I_^W^#sDQiD4ak6XT4b@D*ax^%BJk!D0tZPTXNiyXDm?}WH%i2l z3RqTf62g-NNfPRB03ZNE3IKGG-~`175CQ?oKpa3kLr4-dBms^vNJ1VZf@p{c0w-|- z-~gaWqBubbtpNk4P$@Z1sZwz9mb$y#!9+gKl>|J-lNB@x>_{F#5lsXcgeVaMqJ;oh zvsqU-{7)Gi2_a3fR!R}ggNO-72u{+kIfOK#gNU5K`_4ut0#6$XLjzc4(#NTJZ)LI! zU*H_xDn$QJiRk$E4Xn>^1PZJDMIj0)_F5Tq^VjvhD;L=N8@&LIga4}6DM;FoO(a9X zC}_jCfl-NDNt#57DH2C0MN`>sir>*MsPV0U5H-hgsoQb4wS&Ov|JCl#1dl;?;So(F zMUhR25?u{7n#i@9BAB2k0>egQ5^$(xu{Dgvu+7>4i32i~yBa7o+5k5IFq|Rri#UTv z;uMW<|2s}GuoFi+hJ>MK|4|RVZ%Wv4BVh+PWXqQT7mUH|uw1OmGB_zWc!+?9JMynW zsc@%}QH5yhp$2G8Q*JI?5#m3Cr;LET3QLguFyL5sN<0y)UVkE;FY*5yBgHsZ@wX$v2s#w0Q10-z;2b^?u!~vfs-B54PTYHZr`2crx wC2@&0Oc1{xCI|%s1w4uafD^Egu@$j4Qa-_oY~R3;g}=r{tnH2HOyFGeZ&l(bCjbBd delta 2144 zcmb7@e@v6t8pnIid*1hL3;h9Y-}0lSwUk!MuY$E8A|N1E#!wL%Lq&d#At2OJ7y{A> z43S|6vLRy(6;T-@2Kgmp$`}%|5Msze7DEVI%;H_VTw;vjnuY7Vr!w!|-M#mZdve~B z=RD`hbH3;EJa6N5dbyWwiFR&^BxYGGKPw9~e%G4lBii@(CoC3NWwGdAib`^_6g~Ii zTkE}DBCzv?xun#3|3~FtSYXW1I<7}meusu`ukNASL%ZSUmtIRdVwcB;U^%s#)WE9Pc4dP`P5VN6V;lq;>2 z&bwLNX5IDfY3{2Y37(Ojw`7U37TKKKBp;B!^s+0$m15=WmW!%9Z{$6tZuF7)e5c`R ziZz3pCCxK!gH1cPwZ}J2N9huEjk*clBR`g(hhL0ereC{1-+w898xS7Q6zCCH5%?e| zBIs7ox?ZC%*7xea4^{=|2Hyx?4^f9a*_O7g*T6Q!8#)X>Y?=bDK?syn33(pU~yR&BJqX>_PhKOgoGIotc+VqhFQR*mrbXauzZfduA_i{{f zj6G&0mWfS{t%+TYQ^lpl+2c0$)b9ByULAjNuX*pX$z+-`J>OTpZ!#e|VepNJHy$Py zBrd;M{N_?pYSO}f!~XXDFAlUHRYC_y3<^3X_mCPw2im(-oBMCOD{T-bY%R<)1w|o%|};`agS9Vo6LY2F&RV0LymVI zf1WAJjL9s|T*%^PC1=%V4P`yfX0ju*8?q;|ALZzBGIAPn26A5J*1rSaNzL=mtIhlV zUBkQ8`APYW`O_!7PIR7_E^sTzEf^^j79J>UE*vU+SY#6xB@}g^)SR@OTqt%f&MxjL zep(V=GGTTz_nOyEMV#ue=qw$j{L<*s?$Qfd`_akQePV)3-{ zboc3BD^n_aD<4l*ACV> z*R|Bm)~(p;!#*&5u+-q(&~eV~T=Th&#`4C+^ZN7E=NFsOKU96#)68!!ZyvvZE|gzb zv>WX$_7@-3fAqAaxMiU=vbFyr|Ke<$x-GM9s%`aS%O~sYotN@2J?Mz*80c94H2Tw? z&XCT&&y=6_by>Q4yB>Z%)eXB1-PPT9FEf`bE>B-kUa>V_RbMT?`l!d!GjvULE%(}5 zZ&B~_>&e%LZlD|X8_#a0-0Zr!-WT7u{6*#$5Bk;pMg22h#(z06kTkG(YjN=X!Jlul zZ(DEA4~d87hN51Cg)%r^G5>l|+IUag>N4X+k7|GzrCGNYYRwB52Y;9OQ7^NWh_$ z0&W>sfH;UG$_P^53jh}f5fDNifpA)EVo_KO6t|`RRT~UiB`Wc6`Ne-si!X9q$HIeh zEWiP)CO#-221g>a2@GIRo4S7o z5hVRT6wLs!Q5j;BFdT9?Y`AkbNgMqu%Q=Lk^OlyIjh0R-@EGT_UKWm#EgSPeK8 zPJ(M-)M~UEoKu6hS`CfU^KlbngQgVV)ooHn%HTL$J8Xn(c00C1jiE+btDyvtax}dF zYP6w|AP5>mrHnBO8XaYhf{YaZDGEr@S`}pzGa*KUhE_SmGhzxb9=L5u4-#_3i1MbL z0RWLx7j$PDwg&ulNTPbtu^Y From f34115fa1b91648ac3c2a5ce7528a1d2ee77463c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 17:59:38 +0200 Subject: [PATCH 1395/4355] rename newPromptsParser to promptFileParser, move out of service folder (#272499) --- .../chatContentParts/chatSuggestNextWidget.ts | 2 +- .../contrib/chat/browser/chatWidget.ts | 2 +- .../promptSyntax/promptFileRewriter.ts | 2 +- .../promptToolsCodeLensProvider.ts | 2 +- .../contrib/chat/common/chatModes.ts | 2 +- .../computeAutomaticInstructions.ts | 2 +- .../PromptHeaderDefinitionProvider.ts | 2 +- .../languageProviders/promptCodeActions.ts | 2 +- .../promptHeaderAutocompletion.ts | 2 +- .../languageProviders/promptHovers.ts | 2 +- .../languageProviders/promptValidator.ts | 2 +- ...ewPromptsParser.ts => promptFileParser.ts} | 19 +++++++++---------- .../promptSyntax/service/promptsService.ts | 2 +- .../service/promptsServiceImpl.ts | 6 +++--- .../promptSytntax/promptValidator.test.ts | 4 ++-- .../chat/test/common/mockPromptsService.ts | 2 +- .../promptSyntax/promptFileReference.test.ts | 4 ++-- .../service/newPromptsParser.test.ts | 12 ++++++------ 18 files changed, 35 insertions(+), 36 deletions(-) rename src/vs/workbench/contrib/chat/common/promptSyntax/{service/newPromptsParser.ts => promptFileParser.ts} (95%) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts index 5b8a6ebcb28..d8277329f6a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatSuggestNextWidget.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; import { IChatMode } from '../../common/chatModes.js'; -import { IHandOff } from '../../common/promptSyntax/service/newPromptsParser.js'; +import { IHandOff } from '../../common/promptSyntax/promptFileParser.js'; export interface INextPromptSelection { readonly handoff: IHandOff; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 803fd905064..deecb62e162 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -78,7 +78,7 @@ import { ILanguageModelToolsService, IToolData, ToolSet } from '../common/langua import { ComputeAutomaticInstructions } from '../common/promptSyntax/computeAutomaticInstructions.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; -import { IHandOff, ParsedPromptFile, PromptHeader } from '../common/promptSyntax/service/newPromptsParser.js'; +import { IHandOff, ParsedPromptFile, PromptHeader } from '../common/promptSyntax/promptFileParser.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { handleModeSwitch } from './actions/chatActions.js'; import { ChatTreeItem, ChatViewId, IChatAcceptInputOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from './chat.js'; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts index 0fdc227d629..67fb1f57082 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts @@ -10,7 +10,7 @@ import { EditOperation } from '../../../../../editor/common/core/editOperation.j import { Range } from '../../../../../editor/common/core/range.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { ILanguageModelToolsService, IToolAndToolSetEnablementMap } from '../../common/languageModelToolsService.js'; -import { PromptHeaderAttributes } from '../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; export class PromptFileRewriter { diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 5f5d9d08443..e256c983407 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -20,7 +20,7 @@ import { registerEditorFeature } from '../../../../../editor/common/editorFeatur import { PromptFileRewriter } from './promptFileRewriter.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IEditorModel } from '../../../../../editor/common/editorCommon.js'; -import { PromptHeaderAttributes } from '../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index d6675063aaa..7a439d54857 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -18,7 +18,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IChatAgentService } from './chatAgents.js'; import { ChatContextKeys } from './chatContextKeys.js'; import { ChatModeKind } from './constants.js'; -import { IHandOff } from './promptSyntax/service/newPromptsParser.js'; +import { IHandOff } from './promptSyntax/promptFileParser.js'; import { IAgentSource, ICustomAgent, IPromptsService, PromptsStorage } from './promptSyntax/service/promptsService.js'; export const IChatModeService = createDecorator('chatModeService'); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index ad05805f32e..a879a7c594e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -21,7 +21,7 @@ import { IToolData } from '../languageModelToolsService.js'; import { PromptsConfig } from './config/config.js'; import { isPromptOrInstructionsFile } from './config/promptFileLocations.js'; import { PromptsType } from './promptTypes.js'; -import { ParsedPromptFile } from './service/newPromptsParser.js'; +import { ParsedPromptFile } from './promptFileParser.js'; import { IPromptPath, IPromptsService } from './service/promptsService.js'; export type InstructionsCollectionEvent = { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts index 0425b933c88..b1baef7e24b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts @@ -10,7 +10,7 @@ import { Definition, DefinitionProvider } from '../../../../../../editor/common/ import { ITextModel } from '../../../../../../editor/common/model.js'; import { IChatModeService } from '../../chatModes.js'; import { getPromptsTypeForLanguageId } from '../promptTypes.js'; -import { PromptHeaderAttributes } from '../service/newPromptsParser.js'; +import { PromptHeaderAttributes } from '../promptFileParser.js'; import { IPromptsService } from '../service/promptsService.js'; export class PromptHeaderDefinitionProvider implements DefinitionProvider { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts index ba2805433e0..b6f2124c082 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts @@ -11,7 +11,7 @@ import { localize } from '../../../../../../nls.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { ParsedPromptFile, PromptHeaderAttributes } from '../service/newPromptsParser.js'; +import { ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { Selection } from '../../../../../../editor/common/core/selection.js'; import { Lazy } from '../../../../../../base/common/lazy.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 09dba840229..8605abd3eef 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -15,7 +15,7 @@ import { IChatModeService } from '../../chatModes.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; -import { PromptHeader } from '../service/newPromptsParser.js'; +import { PromptHeader } from '../promptFileParser.js'; import { getValidAttributeNames } from './promptValidator.js'; import { localize } from '../../../../../../nls.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index d74174df9cf..98aa4192a2c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -15,7 +15,7 @@ import { ILanguageModelToolsService, ToolSet } from '../../languageModelToolsSer import { IChatModeService, isBuiltinChatMode } from '../../chatModes.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; -import { IHeaderAttribute, PromptBody, PromptHeader, PromptHeaderAttributes } from '../service/newPromptsParser.js'; +import { IHeaderAttribute, PromptBody, PromptHeader, PromptHeaderAttributes } from '../promptFileParser.js'; export class PromptHoverProvider implements HoverProvider { /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 065c28d92a2..1b730397809 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -17,7 +17,7 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { IArrayValue, IHeaderAttribute, ParsedPromptFile } from '../service/newPromptsParser.js'; +import { IArrayValue, IHeaderAttribute, ParsedPromptFile } from '../promptFileParser.js'; import { PromptsConfig } from '../config/config.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts similarity index 95% rename from src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts rename to src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index 49b90645dba..3d61ea2d726 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -3,15 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Iterable } from '../../../../../../base/common/iterator.js'; -import { dirname, joinPath } from '../../../../../../base/common/resources.js'; -import { splitLinesIncludeSeparators } from '../../../../../../base/common/strings.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../../base/common/yaml.js'; -import { Range } from '../../../../../../editor/common/core/range.js'; -import { chatVariableLeader } from '../../chatParserTypes.js'; - -export class NewPromptsParser { +import { Iterable } from '../../../../../base/common/iterator.js'; +import { dirname, joinPath } from '../../../../../base/common/resources.js'; +import { splitLinesIncludeSeparators } from '../../../../../base/common/strings.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../base/common/yaml.js'; +import { Range } from '../../../../../editor/common/core/range.js'; + +export class PromptFileParser { constructor() { } @@ -295,7 +294,7 @@ export class PromptBody { fileReferences.push({ content: match[2], range, isMarkdownLink: true }); markdownLinkRanges.push(new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)); } - const reg = new RegExp(`${chatVariableLeader}([\\w]+:)?([^\\s#]+)`, 'g'); + const reg = new RegExp(`#([\\w]+:)?([^\\s#]+)`, 'g'); const matches = line.matchAll(reg); for (const match of matches) { const fullRange = new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 1e274cb67e1..fbafb992369 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -12,7 +12,7 @@ import { ExtensionIdentifier, IExtensionDescription } from '../../../../../../pl import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IChatModeInstructions, IVariableReference } from '../../chatModes.js'; import { PromptsType } from '../promptTypes.js'; -import { IHandOff, ParsedPromptFile } from './newPromptsParser.js'; +import { IHandOff, ParsedPromptFile } from '../promptFileParser.js'; /** * Provides prompt services. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 7fe420d9e86..7d4ecefb845 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -30,7 +30,7 @@ import { PromptsConfig } from '../config/config.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; -import { NewPromptsParser, ParsedPromptFile, PromptHeaderAttributes } from './newPromptsParser.js'; +import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; /** @@ -113,7 +113,7 @@ export class PromptsService extends Disposable implements IPromptsService { if (cached && cached[0] === textModel.getVersionId()) { return cached[1]; } - const ast = new NewPromptsParser().parse(textModel.uri, textModel.getValue()); + const ast = new PromptFileParser().parse(textModel.uri, textModel.getValue()); if (!cached || cached[0] < textModel.getVersionId()) { this.parsedPromptFileCache.set(textModel.uri, [textModel.getVersionId(), ast]); } @@ -310,7 +310,7 @@ export class PromptsService extends Disposable implements IPromptsService { if (token.isCancellationRequested) { throw new CancellationError(); } - return new NewPromptsParser().parse(uri, fileContent.value.toString()); + return new PromptFileParser().parse(uri, fileContent.value.toString()); } public registerContributedFile(type: PromptsType, name: string, description: string, uri: URI, extension: IExtensionDescription) { diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index e672e8d7892..ace1729a814 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -26,7 +26,7 @@ import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { PromptValidator } from '../../../common/promptSyntax/languageProviders/promptValidator.js'; import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; -import { NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptFileParser } from '../../../common/promptSyntax/promptFileParser.js'; import { PromptsStorage } from '../../../common/promptSyntax/service/promptsService.js'; import { MockChatModeService } from '../../common/mockChatModeService.js'; import { MockChatService } from '../../common/mockChatService.js'; @@ -96,7 +96,7 @@ suite('PromptValidator', () => { async function validate(code: string, promptType: PromptsType): Promise { const uri = URI.parse('myFs://test/testFile' + getPromptFileExtension(promptType)); - const result = new NewPromptsParser().parse(uri, code); + const result = new PromptFileParser().parse(uri, code); const validator = instaService.createInstance(PromptValidator); const markers: IMarkerData[] = []; await validator.validate(result, promptType, m => markers.push(m)); diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index a3a17bc9a17..948a8414563 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -10,7 +10,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; -import { ParsedPromptFile } from '../../common/promptSyntax/service/newPromptsParser.js'; +import { ParsedPromptFile } from '../../common/promptSyntax/promptFileParser.js'; import { ICustomAgent, IPromptPath, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; export class MockPromptsService implements IPromptsService { diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts index 373cf708408..0d09d3047d1 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/promptFileReference.test.ts @@ -23,7 +23,7 @@ import { ChatModeKind } from '../../../common/constants.js'; import { getPromptFileType } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; import { IMockFolder, MockFilesystem } from './testUtils/mockFilesystem.js'; -import { IBodyFileReference, NewPromptsParser } from '../../../common/promptSyntax/service/newPromptsParser.js'; +import { IBodyFileReference, PromptFileParser } from '../../../common/promptSyntax/promptFileParser.js'; /** * Represents a file reference with an expected @@ -91,7 +91,7 @@ class TestPromptFileReference extends Disposable { const content = await this.fileService.readFile(this.rootFileUri); - const ast = new NewPromptsParser().parse(this.rootFileUri, content.value.toString()); + const ast = new PromptFileParser().parse(this.rootFileUri, content.value.toString()); assert(ast.body, 'Prompt file must have a body'); // resolve the root file reference including all nested references diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 2ca9e16cd5c..783dd6c65a4 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -8,7 +8,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; import { URI } from '../../../../../../../base/common/uri.js'; -import { NewPromptsParser } from '../../../../common/promptSyntax/service/newPromptsParser.js'; +import { PromptFileParser } from '../../../../common/promptSyntax/promptFileParser.js'; suite('NewPromptsParser', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -24,7 +24,7 @@ suite('NewPromptsParser', () => { /* 06 */'This is an agent test.', /* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).', ].join('\n'); - const result = new NewPromptsParser().parse(uri, content); + const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); assert.ok(result.header); assert.ok(result.body); @@ -74,7 +74,7 @@ suite('NewPromptsParser', () => { /* 12 */' send: true', /* 13 */'---', ].join('\n'); - const result = new NewPromptsParser().parse(uri, content); + const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); assert.ok(result.header); assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 13, endColumn: 1 }); @@ -126,7 +126,7 @@ suite('NewPromptsParser', () => { /* 04 */'---', /* 05 */'Follow my companies coding guidlines at [mycomp-ts-guidelines](https://mycomp/guidelines#typescript.md)', ].join('\n'); - const result = new NewPromptsParser().parse(uri, content); + const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); assert.ok(result.header); assert.ok(result.body); @@ -158,7 +158,7 @@ suite('NewPromptsParser', () => { /* 06 */'---', /* 07 */'This is a prompt file body referencing #search and [docs](https://example.com/docs).', ].join('\n'); - const result = new NewPromptsParser().parse(uri, content); + const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); assert.ok(result.header); assert.ok(result.body); @@ -206,7 +206,7 @@ suite('NewPromptsParser', () => { /* 10 */' copilotCodingAgent: false', /* 11 */'---', ].join('\n'); - const result = new NewPromptsParser().parse(uri, content); + const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); assert.ok(result.header); assert.ok(!result.body); From f4edae643bb92d505f77d22c43b411514ba67677 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:01:44 -0700 Subject: [PATCH 1396/4355] Move tree sitter parsing into own class --- eslint.config.js | 1 + .../browser/commandLineAutoApprover.ts | 34 -- .../chatAgentTools/browser/subCommands.ts | 75 --- .../browser/tools/runInTerminalTool.ts | 25 +- .../browser/treeSitterCommandParser.ts | 52 ++ .../test/browser/subCommands.test.ts | 469 ------------------ 6 files changed, 70 insertions(+), 586 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/subCommands.ts create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts delete mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/subCommands.test.ts diff --git a/eslint.config.js b/eslint.config.js index 509648a12c1..9fb6db94c50 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1824,6 +1824,7 @@ export default tseslint.config( // terminalContrib is one extra folder deep 'vs/workbench/contrib/terminalContrib/*/~', 'vscode-notebook-renderer', // Type only import + '@vscode/tree-sitter-wasm', // type import { 'when': 'hasBrowser', 'pattern': '@xterm/xterm' diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 73a83f5cec4..291d53028c9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -11,7 +11,6 @@ import { structuralEquals } from '../../../../../base/common/equals.js'; import { ConfigurationTarget, IConfigurationService, type IConfigurationValue } from '../../../../../platform/configuration/common/configuration.js'; import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; import { isPowerShell } from './runInTerminalHelpers.js'; -import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; export interface IAutoApproveRule { regex: RegExp; @@ -40,7 +39,6 @@ export class CommandLineAutoApprover extends Disposable { constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService ) { super(); this.updateConfiguration(); @@ -52,38 +50,6 @@ export class CommandLineAutoApprover extends Disposable { this.updateConfiguration(); } })); - - const parserClass = this._treeSitterLibraryService.getParserClass(); - parserClass.then(async parserCtor => { - const bashLang = await this._treeSitterLibraryService.getLanguage('bash'); - const pwshLang = await this._treeSitterLibraryService.getLanguage('powershell'); - - const parser = new parserCtor(); - if (bashLang) { - parser.setLanguage(bashLang); - const tree = parser.parse('echo "$(evil) a|b|c" | ls'); - - const q = await this._treeSitterLibraryService.createQuery('bash', '(command) @command'); - if (tree && q) { - const captures = q.captures(tree.rootNode); - const subCommands = captures.map(e => e.node.text); - console.log('done', subCommands); - } - } - - if (pwshLang) { - parser.setLanguage(pwshLang); - const tree = parser.parse('Get-ChildItem | Write-Host "$(evil)"'); - - const q = await this._treeSitterLibraryService.createQuery('powershell', '(command\ncommand_name: (command_name) @function)'); - if (tree && q) { - const captures = q.captures(tree.rootNode); - const subCommands = captures.map(e => e.node.text); - console.log('done', subCommands); - } - } - }); - } updateConfiguration() { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/subCommands.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/subCommands.ts deleted file mode 100644 index df98f6c64f8..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/subCommands.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { OperatingSystem } from '../../../../../base/common/platform.js'; -import { isPowerShell } from './runInTerminalHelpers.js'; - -function createNumberRange(start: number, end: number): string[] { - return Array.from({ length: end - start + 1 }, (_, i) => (start + i).toString()); -} - -function sortByStringLengthDesc(arr: string[]): string[] { - return [...arr].sort((a, b) => b.length - a.length); -} - -// Derived from https://github.com/microsoft/vscode/blob/315b0949786b3807f05cb6acd13bf0029690a052/extensions/terminal-suggest/src/tokens.ts#L14-L18 -// Some of these can match the same string, so the order matters. -// -// This isn't perfect, at some point it would be better off moving over to tree sitter for this -// instead of simple string matching. -const shellTypeResetChars = new Map<'sh' | 'zsh' | 'pwsh', string[]>([ - ['sh', sortByStringLengthDesc([ - // Redirection docs (bash) https://www.gnu.org/software/bash/manual/html_node/Redirections.html - ...createNumberRange(1, 9).concat('').map(n => `${n}<<<`), // Here strings - ...createNumberRange(1, 9).concat('').flatMap(n => createNumberRange(1, 9).map(m => `${n}>&${m}`)), // Redirect stream to stream - ...createNumberRange(1, 9).concat('').map(n => `${n}<>`), // Open file descriptor for reading and writing - ...createNumberRange(1, 9).concat('&', '').map(n => `${n}>>`), - ...createNumberRange(1, 9).concat('&', '').map(n => `${n}>`), - '0<', '||', '&&', '|&', '<<', '&', ';', '{', '>', '<', '|' - ])], - ['zsh', sortByStringLengthDesc([ - // Redirection docs https://zsh.sourceforge.io/Doc/Release/Redirection.html - ...createNumberRange(1, 9).concat('').map(n => `${n}<<<`), // Here strings - ...createNumberRange(1, 9).concat('').flatMap(n => createNumberRange(1, 9).map(m => `${n}>&${m}`)), // Redirect stream to stream - ...createNumberRange(1, 9).concat('').map(n => `${n}<>`), // Open file descriptor for reading and writing - ...createNumberRange(1, 9).concat('&', '').map(n => `${n}>>`), - ...createNumberRange(1, 9).concat('&', '').map(n => `${n}>`), - '<(', '||', '>|', '>!', '&&', '|&', '&', ';', '{', '<(', '<', '|' - ])], - ['pwsh', sortByStringLengthDesc([ - // Redirection docs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.5 - ...createNumberRange(1, 6).concat('*', '').flatMap(n => createNumberRange(1, 6).map(m => `${n}>&${m}`)), // Stream to stream redirection - ...createNumberRange(1, 6).concat('*', '').map(n => `${n}>>`), - ...createNumberRange(1, 6).concat('*', '').map(n => `${n}>`), - '&&', '<', '|', ';', '!', '&' - ])], -]); - -export function splitCommandLineIntoSubCommands(commandLine: string, envShell: string, envOS: OperatingSystem): string[] { - let shellType: 'sh' | 'zsh' | 'pwsh'; - const envShellWithoutExe = envShell.replace(/\.exe$/, ''); - if (isPowerShell(envShell, envOS)) { - shellType = 'pwsh'; - } else { - switch (envShellWithoutExe) { - case 'zsh': shellType = 'zsh'; break; - default: shellType = 'sh'; break; - } - } - const subCommands = [commandLine]; - const resetChars = shellTypeResetChars.get(shellType); - if (resetChars) { - for (const chars of resetChars) { - for (let i = 0; i < subCommands.length; i++) { - const subCommand = subCommands[i]; - if (subCommand.includes(chars)) { - subCommands.splice(i, 1, ...subCommand.split(chars).map(e => e.trim())); - i--; - } - } - } - } - return subCommands.filter(e => e.length > 0); -} 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 ab6bec13e81..59d79407f44 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -43,10 +43,10 @@ import { RichExecuteStrategy } from '../executeStrategy/richExecuteStrategy.js'; import { getOutput } from '../outputHelpers.js'; import { dedupeRules, generateAutoApproveActions, isPowerShell } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; -import { splitCommandLineIntoSubCommands } from '../subCommands.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; import { OutputMonitor } from './monitoring/outputMonitor.js'; import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; +import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; const enum TerminalToolStorageKeysInternal { TerminalSession = 'chat.terminalSessions' @@ -148,6 +148,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _terminalToolCreator: ToolTerminalCreator; private readonly _commandSimplifier: CommandSimplifier; + private readonly _treeSitterCommandParser: TreeSitterCommandParser; private readonly _telemetry: RunInTerminalToolTelemetry; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; protected readonly _sessionTerminalAssociations: Map = new Map(); @@ -182,6 +183,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._terminalToolCreator = _instantiationService.createInstance(ToolTerminalCreator); this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); + this._treeSitterCommandParser = this._instantiationService.createInstance(TreeSitterCommandParser); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); @@ -237,7 +239,14 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // can be reviewed in the terminal channel. It also allows gauging the effective set of // commands that would be auto approved if it were enabled. const actualCommand = toolEditedCommand ?? args.command; - const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); + + const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; + // TODO: Handle throw + const subCommands = await this._treeSitterCommandParser.extractSubCommands(treeSitterLanguage, actualCommand); + this._logService.info('RunInTerminalTool: autoApprove: Parsed sub-commands', subCommands); + + + // const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); const autoApproveReasons: string[] = [ @@ -252,30 +261,30 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); if (deniedSubCommandResult) { - this._logService.info('autoApprove: Sub-command DENIED auto approval'); + this._logService.info('RunInTerminalTool: autoApprove: Sub-command DENIED auto approval'); isDenied = true; autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; autoApproveReason = 'subCommand'; } else if (commandLineResult.result === 'denied') { - this._logService.info('autoApprove: Command line DENIED auto approval'); + this._logService.info('RunInTerminalTool: autoApprove: Command line DENIED auto approval'); isDenied = true; autoApproveDefault = commandLineResult.rule?.isDefaultRule; autoApproveReason = 'commandLine'; } else { if (subCommandResults.every(e => e.result === 'approved')) { - this._logService.info('autoApprove: All sub-commands auto-approved'); + this._logService.info('RunInTerminalTool: autoApprove: All sub-commands auto-approved'); autoApproveReason = 'subCommand'; isAutoApproved = true; autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); } else { - this._logService.info('autoApprove: All sub-commands NOT auto-approved'); + this._logService.info('RunInTerminalTool: autoApprove: All sub-commands NOT auto-approved'); if (commandLineResult.result === 'approved') { - this._logService.info('autoApprove: Command line auto-approved'); + this._logService.info('RunInTerminalTool: autoApprove: Command line auto-approved'); autoApproveReason = 'commandLine'; isAutoApproved = true; autoApproveDefault = commandLineResult.rule?.isDefaultRule; } else { - this._logService.info('autoApprove: Command line NOT auto-approved'); + this._logService.info('RunInTerminalTool: autoApprove: Command line NOT auto-approved'); } } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts new file mode 100644 index 00000000000..b495aaa51e6 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BugIndicatingError } from '../../../../../base/common/errors.js'; +import { Lazy } from '../../../../../base/common/lazy.js'; +import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import type { Parser, Query } from '@vscode/tree-sitter-wasm'; + +export const enum TreeSitterCommandParserLanguage { + Bash = 'bash', + PowerShell = 'powershell', +} + +export class TreeSitterCommandParser { + private readonly _parser: Promise; + + private readonly _languageToQueryMap = { + [TreeSitterCommandParserLanguage.Bash]: new Lazy(() => { + return this._treeSitterLibraryService.createQuery(TreeSitterCommandParserLanguage.Bash, '(command) @command'); + }), + [TreeSitterCommandParserLanguage.PowerShell]: new Lazy(() => { + return this._treeSitterLibraryService.createQuery(TreeSitterCommandParserLanguage.PowerShell, '(command\ncommand_name: (command_name) @function)'); + }), + } satisfies { [K in TreeSitterCommandParserLanguage]: Lazy> }; + + constructor( + @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService + ) { + this._parser = this._treeSitterLibraryService.getParserClass().then(ParserCtor => new ParserCtor()); + } + + async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { + const parser = await this._parser; + parser.setLanguage(await this._treeSitterLibraryService.getLanguage(languageId)); + + const tree = parser.parse(commandLine); + if (!tree) { + throw new BugIndicatingError('Failed to parse tree'); + } + + const query = await this._languageToQueryMap[languageId].value; + if (!query) { + throw new BugIndicatingError('Failed to create tree sitter query'); + } + + const captures = query.captures(tree.rootNode); + const subCommands = captures.map(e => e.node.text); + return subCommands; + } +} diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/subCommands.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/subCommands.test.ts deleted file mode 100644 index cf1d81fd1ca..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/subCommands.test.ts +++ /dev/null @@ -1,469 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { deepStrictEqual } from 'assert'; -import { splitCommandLineIntoSubCommands } from '../../browser/subCommands.js'; -import { OperatingSystem } from '../../../../../../base/common/platform.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; - -suite('splitCommandLineIntoSubCommands', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('should split command line into subcommands', () => { - const commandLine = 'echo "Hello World" && ls -la || pwd'; - const expectedSubCommands = ['echo "Hello World"', 'ls -la', 'pwd']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - suite('bash/sh shell', () => { - test('should split on logical operators', () => { - const commandLine = 'echo test && ls -la || pwd'; - const expectedSubCommands = ['echo test', 'ls -la', 'pwd']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on pipes', () => { - const commandLine = 'ls -la | grep test | wc -l'; - const expectedSubCommands = ['ls -la', 'grep test', 'wc -l']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on semicolons', () => { - const commandLine = 'cd /tmp; ls -la; pwd'; - const expectedSubCommands = ['cd /tmp', 'ls -la', 'pwd']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on background operator', () => { - const commandLine = 'sleep 5 & echo done'; - const expectedSubCommands = ['sleep 5', 'echo done']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on redirection operators', () => { - const commandLine = 'echo test > output.txt && cat output.txt'; - const expectedSubCommands = ['echo test', 'output.txt', 'cat output.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on stderr redirection', () => { - const commandLine = 'command 2> error.log && echo success'; - const expectedSubCommands = ['command', 'error.log', 'echo success']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on append redirection', () => { - const commandLine = 'echo line1 >> file.txt && echo line2 >> file.txt'; - const expectedSubCommands = ['echo line1', 'file.txt', 'echo line2', 'file.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('zsh shell', () => { - test('should split on zsh-specific operators', () => { - const commandLine = 'echo test <<< "input" && ls'; - const expectedSubCommands = ['echo test', '"input"', 'ls']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on process substitution', () => { - const commandLine = 'diff <(ls dir1) <(ls dir2)'; - const expectedSubCommands = ['diff', 'ls dir1)', 'ls dir2)']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on bidirectional redirection', () => { - const commandLine = 'command <> file.txt && echo done'; - const expectedSubCommands = ['command', 'file.txt', 'echo done']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should handle complex zsh command chains', () => { - const commandLine = 'ls | grep test && echo found || echo not found'; - const expectedSubCommands = ['ls', 'grep test', 'echo found', 'echo not found']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('PowerShell', () => { - test('should not split on PowerShell logical operators', () => { - const commandLine = 'Get-ChildItem -and Get-Location -or Write-Host "test"'; - const expectedSubCommands = ['Get-ChildItem -and Get-Location -or Write-Host "test"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell pipes', () => { - const commandLine = 'Get-Process | Where-Object Name -eq "notepad" | Stop-Process'; - const expectedSubCommands = ['Get-Process', 'Where-Object Name -eq "notepad"', 'Stop-Process']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell redirection', () => { - const commandLine = 'Get-Process > processes.txt && Get-Content processes.txt'; - const expectedSubCommands = ['Get-Process', 'processes.txt', 'Get-Content processes.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('edge cases', () => { - test('should return single command when no operators present', () => { - const commandLine = 'echo "hello world"'; - const expectedSubCommands = ['echo "hello world"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should handle empty command', () => { - const commandLine = ''; - const expectedSubCommands: string[] = []; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should trim whitespace from subcommands', () => { - const commandLine = 'echo test && ls -la || pwd'; - const expectedSubCommands = ['echo test', 'ls -la', 'pwd']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'sh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should handle multiple consecutive operators', () => { - const commandLine = 'echo test && && ls'; - const expectedSubCommands = ['echo test', 'ls']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should handle unknown shell as sh', () => { - const commandLine = 'echo test && ls -la'; - const expectedSubCommands = ['echo test', 'ls -la']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'unknown-shell', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('shell type detection', () => { - test('should detect PowerShell variants', () => { - const commandLine = 'Get-Process ; Get-Location'; - const expectedSubCommands = ['Get-Process', 'Get-Location']; - - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'powershell', OperatingSystem.Linux), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'pwsh', OperatingSystem.Linux), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'powershell-preview', OperatingSystem.Linux), expectedSubCommands); - }); - - test('should detect zsh specifically', () => { - const commandLine = 'echo test <<< input'; - const expectedSubCommands = ['echo test', 'input']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should default to sh for other shells', () => { - const commandLine = 'echo test && ls'; - const expectedSubCommands = ['echo test', 'ls']; - - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'dash', OperatingSystem.Linux), expectedSubCommands); - deepStrictEqual(splitCommandLineIntoSubCommands(commandLine, 'fish', OperatingSystem.Linux), expectedSubCommands); - }); - }); - - suite('redirection tests', () => { - suite('output redirection', () => { - test('should split on basic output redirection', () => { - const commandLine = 'echo hello > output.txt'; - const expectedSubCommands = ['echo hello', 'output.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on append redirection', () => { - const commandLine = 'echo hello >> output.txt'; - const expectedSubCommands = ['echo hello', 'output.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on multiple output redirections', () => { - const commandLine = 'ls > files.txt && cat files.txt > backup.txt'; - const expectedSubCommands = ['ls', 'files.txt', 'cat files.txt', 'backup.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on numbered file descriptor redirection', () => { - const commandLine = 'command 1> stdout.txt 2> stderr.txt'; - const expectedSubCommands = ['command', 'stdout.txt', 'stderr.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on stderr-only redirection', () => { - const commandLine = 'make 2> errors.log'; - const expectedSubCommands = ['make', 'errors.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on all output redirection (&>)', () => { - const commandLine = 'command &> all_output.txt'; - const expectedSubCommands = ['command', 'all_output.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('input redirection', () => { - test('should split on input redirection', () => { - const commandLine = 'sort < input.txt'; - const expectedSubCommands = ['sort', 'input.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on numbered input redirection', () => { - const commandLine = 'program 0< input.txt'; - const expectedSubCommands = ['program', 'input.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on input/output combined', () => { - const commandLine = 'sort < input.txt > output.txt'; - const expectedSubCommands = ['sort', 'input.txt', 'output.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('stream redirection', () => { - test('should split on stdout to stderr redirection', () => { - const commandLine = 'echo error 1>&2'; - const expectedSubCommands = ['echo error']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on stderr to stdout redirection', () => { - const commandLine = 'command 2>&1'; - const expectedSubCommands = ['command']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on stream redirection with numbered descriptors', () => { - const commandLine = 'exec 3>&1 && exec 4>&2'; - const expectedSubCommands = ['exec', 'exec']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on multiple stream redirections', () => { - const commandLine = 'command 2>&1 1>&3 3>&2'; - const expectedSubCommands = ['command']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('here documents and here strings', () => { - test('should split on here document', () => { - const commandLine = 'cat << EOF && echo done'; - const expectedSubCommands = ['cat', 'EOF', 'echo done']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on here string (bash/zsh)', () => { - const commandLine = 'grep pattern <<< "search this text"'; - const expectedSubCommands = ['grep pattern', '"search this text"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on numbered here string', () => { - const commandLine = 'command 3<<< "input data"'; - const expectedSubCommands = ['command', '"input data"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('bidirectional redirection', () => { - test('should split on read/write redirection', () => { - const commandLine = 'dialog <> /dev/tty1'; - const expectedSubCommands = ['dialog', '/dev/tty1']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on numbered bidirectional redirection', () => { - const commandLine = 'program 3<> data.file'; - const expectedSubCommands = ['program', 'data.file']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('PowerShell redirection', () => { - test('should split on PowerShell output redirection', () => { - const commandLine = 'Get-Process > processes.txt'; - const expectedSubCommands = ['Get-Process', 'processes.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell append redirection', () => { - const commandLine = 'Write-Output "log entry" >> log.txt'; - const expectedSubCommands = ['Write-Output "log entry"', 'log.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell error stream redirection', () => { - const commandLine = 'Get-Content nonexistent.txt 2> errors.log'; - const expectedSubCommands = ['Get-Content nonexistent.txt', 'errors.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell warning stream redirection', () => { - const commandLine = 'Get-Process 3> warnings.log'; - const expectedSubCommands = ['Get-Process', 'warnings.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell verbose stream redirection', () => { - const commandLine = 'Get-ChildItem 4> verbose.log'; - const expectedSubCommands = ['Get-ChildItem', 'verbose.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell debug stream redirection', () => { - const commandLine = 'Invoke-Command 5> debug.log'; - const expectedSubCommands = ['Invoke-Command', 'debug.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell information stream redirection', () => { - const commandLine = 'Write-Information "info" 6> info.log'; - const expectedSubCommands = ['Write-Information "info"', 'info.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell all streams redirection', () => { - const commandLine = 'Get-Process *> all_streams.log'; - const expectedSubCommands = ['Get-Process', 'all_streams.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'pwsh.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on PowerShell stream to stream redirection', () => { - const commandLine = 'Write-Error "error" 2>&1'; - const expectedSubCommands = ['Write-Error "error"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'powershell.exe', OperatingSystem.Windows); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('complex redirection scenarios', () => { - test('should split on command with multiple redirections', () => { - const commandLine = 'command < input.txt > output.txt 2> errors.log'; - const expectedSubCommands = ['command', 'input.txt', 'output.txt', 'errors.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on redirection with pipes and logical operators', () => { - const commandLine = 'cat file.txt | grep pattern > results.txt && echo "Found" || echo "Not found" 2> errors.log'; - const expectedSubCommands = ['cat file.txt', 'grep pattern', 'results.txt', 'echo "Found"', 'echo "Not found"', 'errors.log']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on chained redirections', () => { - const commandLine = 'echo "step1" > temp.txt && cat temp.txt >> final.txt && rm temp.txt'; - const expectedSubCommands = ['echo "step1"', 'temp.txt', 'cat temp.txt', 'final.txt', 'rm temp.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should handle redirection with background processes', () => { - const commandLine = 'long_running_command > output.log 2>&1 & echo "started"'; - const expectedSubCommands = ['long_running_command', 'output.log', 'echo "started"']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - - suite('zsh-specific redirection', () => { - test('should split on zsh noclobber override', () => { - const commandLine = 'echo "force" >! existing_file.txt'; - const expectedSubCommands = ['echo "force"', 'existing_file.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on zsh clobber override', () => { - const commandLine = 'echo "overwrite" >| protected_file.txt'; - const expectedSubCommands = ['echo "overwrite"', 'protected_file.txt']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on zsh process substitution for input', () => { - const commandLine = 'diff <(sort file1.txt) <(sort file2.txt)'; - const expectedSubCommands = ['diff', 'sort file1.txt)', 'sort file2.txt)']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test('should split on zsh multios', () => { - const commandLine = 'echo "test" | tee >(gzip > file1.gz) >(bzip2 > file1.bz2)'; - const expectedSubCommands = ['echo "test"', 'tee', '(gzip', 'file1.gz)', '(bzip2', 'file1.bz2)']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); - }); - - suite('complex command combinations', () => { - test('should handle mixed operators in order', () => { - const commandLine = 'ls | grep test && echo found > result.txt || echo failed'; - const expectedSubCommands = ['ls', 'grep test', 'echo found', 'result.txt', 'echo failed']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'bash', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - - test.skip('should handle subshells and braces', () => { - const commandLine = '(cd /tmp && ls) && { echo done; }'; - const expectedSubCommands = ['(cd /tmp', 'ls)', '{ echo done', '}']; - const actualSubCommands = splitCommandLineIntoSubCommands(commandLine, 'zsh', OperatingSystem.Linux); - deepStrictEqual(actualSubCommands, expectedSubCommands); - }); - }); -}); From cfc2ee927188d44dfabade8bd2c4d6c862d6442e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:05:02 -0700 Subject: [PATCH 1397/4355] Try force ; chaining usage in all pwshs --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 ea5fac72309..0d92a893626 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -41,7 +41,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 { dedupeRules, generateAutoApproveActions, isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; +import { dedupeRules, generateAutoApproveActions, isFish, isPowerShell, isZsh } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; import { OutputMonitor } from './monitoring/outputMonitor.js'; @@ -56,9 +56,9 @@ function createPowerShellModelDescription(shell: string): string { '', 'Command Execution:', '- Does NOT support multi-line commands', - `- ${isWindowsPowerShell(shell) - ? 'Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly' - : 'Use && to chain simple commands on one line'}`, + // Even for pwsh 7+ we want to use `;` to chain commands since the tree sitter grammar + // doesn't parse `&&` + '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', '- Prefer pipelines | for object-based data flow', '', 'Directory Management:', From b5903ae54c35d78b8ed2fd82a7dcf31764191de7 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Tue, 21 Oct 2025 09:26:47 -0700 Subject: [PATCH 1398/4355] Support folding in git COMMIT_MSG files (#272356) --- .vscode-test.js | 4 + extensions/git-base/src/extension.ts | 8 +- extensions/git-base/src/foldingProvider.ts | 92 +++++++ .../git-base/src/test/foldingProvider.test.ts | 258 ++++++++++++++++++ scripts/test-integration.bat | 5 + scripts/test-integration.sh | 6 + 6 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 extensions/git-base/src/foldingProvider.ts create mode 100644 extensions/git-base/src/test/foldingProvider.test.ts diff --git a/.vscode-test.js b/.vscode-test.js index 2e49c90126b..4c093d0e2b3 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -79,6 +79,10 @@ const extensions = [ workspaceFolder: `extensions/vscode-api-tests/testworkspace.code-workspace`, mocha: { timeout: 60_000 }, files: 'extensions/vscode-api-tests/out/workspace-tests/**/*.test.js', + }, + { + label: 'git-base', + mocha: { timeout: 60_000 } } ]; diff --git a/extensions/git-base/src/extension.ts b/extensions/git-base/src/extension.ts index 17ffb89f82d..453d8f7850f 100644 --- a/extensions/git-base/src/extension.ts +++ b/extensions/git-base/src/extension.ts @@ -3,14 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext } from 'vscode'; +import { ExtensionContext, languages } from 'vscode'; import { registerAPICommands } from './api/api1'; import { GitBaseExtensionImpl } from './api/extension'; import { Model } from './model'; +import { GitCommitFoldingProvider } from './foldingProvider'; export function activate(context: ExtensionContext): GitBaseExtensionImpl { const apiImpl = new GitBaseExtensionImpl(new Model()); context.subscriptions.push(registerAPICommands(apiImpl)); + // Register folding provider for git-commit language + context.subscriptions.push( + languages.registerFoldingRangeProvider('git-commit', new GitCommitFoldingProvider()) + ); + return apiImpl; } diff --git a/extensions/git-base/src/foldingProvider.ts b/extensions/git-base/src/foldingProvider.ts new file mode 100644 index 00000000000..b1c1cc45171 --- /dev/null +++ b/extensions/git-base/src/foldingProvider.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export class GitCommitFoldingProvider implements vscode.FoldingRangeProvider { + + provideFoldingRanges( + document: vscode.TextDocument, + _context: vscode.FoldingContext, + _token: vscode.CancellationToken + ): vscode.ProviderResult { + const ranges: vscode.FoldingRange[] = []; + + let commentBlockStart: number | undefined; + let currentDiffStart: number | undefined; + + for (let i = 0; i < document.lineCount; i++) { + const line = document.lineAt(i); + const lineText = line.text; + + // Check for comment lines (lines starting with #) + if (lineText.startsWith('#')) { + // Close any active diff block when we encounter a comment + if (currentDiffStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange(currentDiffStart, i - 1)); + } + currentDiffStart = undefined; + } + + if (commentBlockStart === undefined) { + commentBlockStart = i; + } + } else { + // End of comment block + if (commentBlockStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - commentBlockStart > 1) { + ranges.push(new vscode.FoldingRange( + commentBlockStart, + i - 1, + vscode.FoldingRangeKind.Comment + )); + } + commentBlockStart = undefined; + } + } + + // Check for diff sections (lines starting with "diff --git") + if (lineText.startsWith('diff --git ')) { + // If there's a previous diff block, close it + if (currentDiffStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange(currentDiffStart, i - 1)); + } + } + // Start new diff block + currentDiffStart = i; + } + } + + // Handle end-of-document cases + + // If comment block extends to end of document + if (commentBlockStart !== undefined) { + if (document.lineCount - commentBlockStart > 1) { + ranges.push(new vscode.FoldingRange( + commentBlockStart, + document.lineCount - 1, + vscode.FoldingRangeKind.Comment + )); + } + } + + // If diff block extends to end of document + if (currentDiffStart !== undefined) { + if (document.lineCount - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange( + currentDiffStart, + document.lineCount - 1 + )); + } + } + + return ranges; + } +} diff --git a/extensions/git-base/src/test/foldingProvider.test.ts b/extensions/git-base/src/test/foldingProvider.test.ts new file mode 100644 index 00000000000..69f7d35bf18 --- /dev/null +++ b/extensions/git-base/src/test/foldingProvider.test.ts @@ -0,0 +1,258 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { GitCommitFoldingProvider } from '../foldingProvider'; + +suite('GitCommitFoldingProvider', () => { + + function createMockDocument(content: string): vscode.TextDocument { + const lines = content.split('\n'); + return { + lineCount: lines.length, + lineAt: (index: number) => ({ + text: lines[index] || '', + lineNumber: index + }), + } as vscode.TextDocument; + } + + const mockContext: vscode.FoldingContext = {} as vscode.FoldingContext; + const mockToken: vscode.CancellationToken = { isCancellationRequested: false } as vscode.CancellationToken; + + test('empty document returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument(''); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('single line document returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('commit message'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('single comment line returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('# Comment'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('two comment lines create one folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('multiple comment lines create one folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\n# Comment 3\n# Comment 4'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('comment block followed by content', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\nCommit message'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('comment block at end of document', () => { + const provider = new GitCommitFoldingProvider(); + const content = 'Commit message\n\n# Comment 1\n# Comment 2'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('multiple separated comment blocks', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\n\nCommit message\n\n# Comment 3\n# Comment 4'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 2); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + assert.strictEqual(ranges[1].start, 5); + assert.strictEqual(ranges[1].end, 6); + assert.strictEqual(ranges[1].kind, vscode.FoldingRangeKind.Comment); + }); + + test('single diff line returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('diff --git a/file.txt b/file.txt'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('diff block with content creates folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = 'diff --git a/file.txt b/file.txt\nindex 1234..5678\n--- a/file.txt\n+++ b/file.txt'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, undefined); // Diff blocks don't have a specific kind + }); + + test('multiple diff blocks', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'diff --git a/file1.txt b/file1.txt', + '--- a/file1.txt', + '+++ b/file1.txt', + 'diff --git a/file2.txt b/file2.txt', + '--- a/file2.txt', + '+++ b/file2.txt' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 2); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 2); + assert.strictEqual(ranges[1].start, 3); + assert.strictEqual(ranges[1].end, 5); + }); + + test('diff block at end of document', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Commit message', + '', + 'diff --git a/file.txt b/file.txt', + '--- a/file.txt', + '+++ b/file.txt' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 4); + }); + + test('realistic git commit message with comments and verbose diff', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Add folding support for git commit messages', + '', + '# Please enter the commit message for your changes. Lines starting', + '# with \'#\' will be ignored, and an empty message aborts the commit.', + '#', + '# On branch main', + '# Changes to be committed:', + '#\tmodified: extension.ts', + '#\tnew file: foldingProvider.ts', + '#', + '# ------------------------ >8 ------------------------', + '# Do not modify or remove the line above.', + '# Everything below it will be ignored.', + 'diff --git a/extensions/git-base/src/extension.ts b/extensions/git-base/src/extension.ts', + 'index 17ffb89..453d8f7 100644', + '--- a/extensions/git-base/src/extension.ts', + '+++ b/extensions/git-base/src/extension.ts', + '@@ -3,14 +3,20 @@', + ' * Licensed under the MIT License.', + '-import { ExtensionContext } from \'vscode\';', + '+import { ExtensionContext, languages } from \'vscode\';', + 'diff --git a/extensions/git-base/src/foldingProvider.ts b/extensions/git-base/src/foldingProvider.ts', + 'new file mode 100644', + 'index 0000000..2c4a9c3', + '--- /dev/null', + '+++ b/extensions/git-base/src/foldingProvider.ts' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + // Should have one comment block and two diff blocks + assert.strictEqual(ranges.length, 3); + + // Comment block (lines 2-12) + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 12); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + + // First diff block (lines 13-20) + assert.strictEqual(ranges[1].start, 13); + assert.strictEqual(ranges[1].end, 20); + assert.strictEqual(ranges[1].kind, undefined); + + // Second diff block (lines 21-25) + assert.strictEqual(ranges[2].start, 21); + assert.strictEqual(ranges[2].end, 25); + assert.strictEqual(ranges[2].kind, undefined); + }); + + test('mixed comment and diff content', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Fix bug in parser', + '', + '# Comment 1', + '# Comment 2', + '', + 'diff --git a/file.txt b/file.txt', + '--- a/file.txt', + '+++ b/file.txt', + '', + '# Comment 3', + '# Comment 4' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 3); + + // First comment block + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + + // Diff block + assert.strictEqual(ranges[1].start, 5); + assert.strictEqual(ranges[1].end, 8); + assert.strictEqual(ranges[1].kind, undefined); + + // Second comment block + assert.strictEqual(ranges[2].start, 9); + assert.strictEqual(ranges[2].end, 10); + assert.strictEqual(ranges[2].kind, vscode.FoldingRangeKind.Comment); + }); +}); diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 50c14ff8d3f..2be5bfef0a0 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -80,6 +80,11 @@ mkdir %GITWORKSPACE% call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% +echo. +echo ### Git Base tests +call npm run test-extension -- -l git-base +if %errorlevel% neq 0 exit /b %errorlevel% + echo. echo ### Ipynb tests call npm run test-extension -- -l ipynb diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 3e26bb17a17..e3c391004f8 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -97,6 +97,12 @@ echo "$INTEGRATION_TEST_ELECTRON_PATH" $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS kill_app +echo +echo "### Git Base tests" +echo +npm run test-extension -- -l git-base +kill_app + echo echo "### Ipynb tests" echo From 6026fabe181e5fccf2e3d2447ef437633798b14b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Oct 2025 18:39:48 +0200 Subject: [PATCH 1399/4355] update distro (#272504) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index aa84a3b0265..ebddfe47e02 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "e8adcc1b4408a680b2e06374d8e65a2f7f90409d", + "distro": "6ccc3d7617839f653f9dd899e316200befab12d8", "author": { "name": "Microsoft Corporation" }, @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file From 9e9c78adc57ab64b4f9a8d2a3441c4a9e55fd212 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 21 Oct 2025 09:40:09 -0700 Subject: [PATCH 1400/4355] Skip rendering the "Suggest Next" widget when locked to a coding agent (#272377) * Skip rendering the "Suggest Next" widget when locked to a coding agent * Prevent Suggest Next widget from rendering when chat is locked to a coding agent --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index deecb62e162..8e8291e8519 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1715,6 +1715,12 @@ export class ChatWidget extends Disposable implements IChatWidget { } private renderChatSuggestNextWidget(): void { + // Skip rendering in coding agent sessions + if (this.isLockedToCodingAgent) { + this.chatSuggestNextWidget.hide(); + return; + } + const items = this.viewModel?.getItems() ?? []; if (!items.length) { return; From 45bd380e2251b7a89a664bed5b20e26c2ec82a1f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 21 Oct 2025 18:54:43 +0200 Subject: [PATCH 1401/4355] update docs (#272503) update docs --- .../browser/chatEditing/chatEditingSession.ts | 4 +- .../chatEditing/chatEditingSessionStorage.ts | 2 +- .../contrib/chat/common/chatEditingService.ts | 2 +- .../aiEditTelemetry/aiEditTelemetryService.ts | 41 +++++++++++++++++-- .../aiEditTelemetryServiceImpl.ts | 14 +++---- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index ed46048b561..20c70db80db 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -514,13 +514,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio get result() { return responseModel.result; } get applyCodeBlockSuggestionId() { return responseModel.request?.modeInfo?.applyCodeBlockSuggestionId; } - get feature(): string { + get feature(): 'sideBarChat' | 'inlineChat' | undefined { if (responseModel.session.initialLocation === ChatAgentLocation.Chat) { return 'sideBarChat'; } else if (responseModel.session.initialLocation === ChatAgentLocation.EditorInline) { return 'inlineChat'; } - return responseModel.session.initialLocation; + return undefined; } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts index 0fe47f5ca10..846fe53a9fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts @@ -296,7 +296,7 @@ interface IModifiedEntryTelemetryInfoDTO { readonly modelId?: string; readonly modeId?: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined; readonly applyCodeBlockSuggestionId?: EditSuggestionId | undefined; - readonly feature?: string; + readonly feature?: 'sideBarChat' | 'inlineChat' | undefined; } type ResourceMapDTO = [string, T][]; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 0366481090c..88ee4c1036a 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -89,7 +89,7 @@ export interface IModifiedEntryTelemetryInfo { readonly modelId: string | undefined; readonly modeId: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined; readonly applyCodeBlockSuggestionId: EditSuggestionId | undefined; - readonly feature: 'sideBarChat' | 'inlineChat' | string | undefined; + readonly feature: 'sideBarChat' | 'inlineChat' | undefined; } export interface ISnapshotEntry { diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.ts index 7393ed8cf6c..ffef6e96b80 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.ts @@ -20,15 +20,43 @@ export interface IAiEditTelemetryService { export interface IEditTelemetryBaseData { suggestionId: EditSuggestionId | undefined; - presentation: 'codeBlock' | 'highlightedEdit' | 'inlineCompletion' | 'nextEditSuggestion'; - feature: 'sideBarChat' | 'inlineChat' | 'inlineSuggestion' | string | undefined; + feature: + /** Code suggestions generated in the sidebar chat panel */ + | 'sideBarChat' + /** Code suggestions generated through inline chat within the editor */ + | 'inlineChat' + /** Inline code completion suggestions */ + | 'inlineSuggestion' + | undefined; + + presentation: + /** Code displayed in a code block within chat responses. Only possible when feature is `sideBarChat` or `inlineChat`. */ + | 'codeBlock' + /** Code already applied to the editor and highlighted with diff-style formatting. Only possible when feature is `sideBarChat` or `inlineChat`. */ + | 'highlightedEdit' + /** Code suggested inline as completion text. Only possible when feature is `inlineSuggestion`. */ + | 'inlineCompletion' + /** Code shown as next edit suggestion. Only possible when feature is `nextEditSuggestion`. */ + | 'nextEditSuggestion'; + source: ProviderId | undefined; languageId: string | undefined; editDeltaInfo: EditDeltaInfo | undefined; - modeId: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined; + modeId: + /** User asking questions without requesting code changes */ + | 'ask' + /** User requesting direct code edits or modifications */ + | 'edit' + /** AI agent mode for autonomous task completion and multi-file edits */ + | 'agent' + /** Custom mode defined by extensions or user settings */ + | 'custom' + /** Applying a previously suggested code block */ + | 'applyCodeBlock' + | undefined; applyCodeBlockSuggestionId: EditSuggestionId | undefined; // Is set if modeId is applyCodeBlock modelId: string | undefined; // e.g. 'gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo' @@ -39,9 +67,14 @@ export interface IEditTelemetryCodeSuggestedData extends IEditTelemetryBaseData export interface IEditTelemetryCodeAcceptedData extends IEditTelemetryBaseData { acceptanceMethod: + /** Insert code at the current cursor position in the active editor. Only possible when presentation is `codeBlock`. */ | 'insertAtCursor' + /** Create a new file and insert the code there. Only possible when presentation is `codeBlock`. */ | 'insertInNewFile' + /** User manually copied the code. Only possible when presentation is `codeBlock`. */ | 'copyManual' + /** User clicked a copy button to copy the code. Only possible when presentation is `codeBlock`. */ | 'copyButton' - | 'accept'; // clicking on 'keep' or tab for inline completions + /** User accepted the suggestion by clicking 'keep' (when presentation is `highlightedEdit`) or pressing Tab (when feature is `inlineSuggestion`) */ + | 'accept'; } diff --git a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts index 55d879e5bc5..e8ab0d9c852 100644 --- a/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts +++ b/src/vs/workbench/contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryServiceImpl.ts @@ -51,8 +51,8 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { eventId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this event.' }; suggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this suggestion. Not always set.' }; - presentation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the code suggestion is presented to the user.' }; - feature: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The feature the code suggestion came from.' }; + presentation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the code suggestion is presented to the user. See #IEditTelemetryBaseData.presentation for possible values.' }; + feature: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The feature the code suggestion came from. See #IEditTelemetryBaseData.feature for possible values.' }; sourceExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that provided the code suggestion, if any.' }; sourceExtensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension that provided the code suggestion, if any.' }; @@ -64,7 +64,7 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { editLinesInserted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of lines inserted in the edit.' }; editLinesDeleted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of lines deleted in the edit.' }; - modeId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The mode (ask/edit/...).' }; + modeId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The mode. See #IEditTelemetryBaseData.modeId for possible values.' }; modelId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The AI model used to generate the suggestion.' }; applyCodeBlockSuggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If this suggestion is for applying a suggested code block, this is the id of the suggested code block.' }; }>('editTelemetry.codeSuggested', { @@ -130,8 +130,8 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { eventId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this event.' }; suggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Unique identifier for this suggestion. Not always set.' }; - presentation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the code suggestion is presented to the user.' }; - feature: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The feature the code suggestion came from.' }; + presentation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the code suggestion is presented to the user. See #IEditTelemetryBaseData.presentation for possible values.' }; + feature: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The feature the code suggestion came from. See #IEditTelemetryBaseData.feature for possible values.' }; sourceExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that provided the code suggestion, if any.' }; sourceExtensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version of the extension that provided the code suggestion, if any.' }; @@ -143,11 +143,11 @@ export class AiEditTelemetryServiceImpl implements IAiEditTelemetryService { editLinesInserted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of lines inserted in the edit.' }; editLinesDeleted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of lines deleted in the edit.' }; - modeId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The mode (ask/edit/...).' }; + modeId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The mode. See #IEditTelemetryBaseData.modeId for possible values.' }; modelId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The AI model used to generate the suggestion.' }; applyCodeBlockSuggestionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If this suggestion is for applying a suggested code block, this is the id of the suggested code block.' }; - acceptanceMethod: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the user accepted the code suggestion.' }; + acceptanceMethod: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the user accepted the code suggestion. See #IEditTelemetryCodeAcceptedData.acceptanceMethod for possible values.' }; }>('editTelemetry.codeAccepted', { eventId: prefixedUuid('evt'), suggestionId: data.suggestionId as unknown as string, From 0e57cafca71da0511b3d966d0e45bf30f0f91ba4 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 09:56:15 -0700 Subject: [PATCH 1402/4355] Reduce lifetime of active session instance in Managed Accounts action --- .../browser/actions/manageAccountsAction.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts b/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts index 27a84b0e281..bfb31bd6152 100644 --- a/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts +++ b/src/vs/workbench/contrib/authentication/browser/actions/manageAccountsAction.ts @@ -12,7 +12,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../../platform import { IProductService } from '../../../../../platform/product/common/productService.js'; import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; import { ISecretStorageService } from '../../../../../platform/secrets/common/secrets.js'; -import { getCurrentAuthenticationSessionInfo } from '../../../../services/authentication/browser/authenticationService.js'; +import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from '../../../../services/authentication/browser/authenticationService.js'; import { IAuthenticationProvider, IAuthenticationService } from '../../../../services/authentication/common/authentication.js'; export class ManageAccountsAction extends Action2 { @@ -42,8 +42,6 @@ interface AccountActionQuickPickItem extends IQuickPickItem { } class ManageAccountsActionImpl { - private activeSession = new Lazy(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService)); - constructor( @IQuickInputService private readonly quickInputService: IQuickInputService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @@ -70,6 +68,7 @@ class ManageAccountsActionImpl { } private async listAccounts(): Promise { + const activeSession = new Lazy(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService)); const accounts: AccountQuickPickItem[] = []; for (const providerId of this.authenticationService.getProviderIds()) { const provider = this.authenticationService.getProvider(providerId); @@ -79,15 +78,14 @@ class ManageAccountsActionImpl { description: provider.label, providerId, canUseMcp: !!provider.authorizationServers?.length, - canSignOut: () => this.canSignOut(provider, id) + canSignOut: async () => this.canSignOut(provider, id, await activeSession.value) }); } } return accounts; } - private async canSignOut(provider: IAuthenticationProvider, accountId: string): Promise { - const session = await this.activeSession.value; + private async canSignOut(provider: IAuthenticationProvider, accountId: string, session?: AuthenticationSessionInfo): Promise { if (session && !session.canSignOut && session.providerId === provider.id) { const sessions = await this.authenticationService.getSessions(provider.id); return !sessions.some(o => o.id === session.id && o.account.id === accountId); From 66ddf3ecf50b3b4f29b76a590cf01274929d0a42 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:01:30 -0700 Subject: [PATCH 1403/4355] fix chat session options pickers from interfering with each other (#272487) * rename ChatSessionPrimaryPickerAction for clarity * fix chat session options pickers from interfereing with each other --- .../browser/actions/chatExecuteActions.ts | 10 +- .../contrib/chat/browser/chatInputPart.ts | 130 +++++++++--------- 2 files changed, 73 insertions(+), 67 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 56e97edc435..8691714c094 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -468,12 +468,12 @@ export class OpenModePickerAction extends Action2 { } } -export class ChatSessionOpenModelPickerAction extends Action2 { - static readonly ID = 'workbench.action.chat.chatSessionOpenModelPicker'; +export class ChatSessionPrimaryPickerAction extends Action2 { + static readonly ID = 'workbench.action.chat.chatSessionPrimaryPicker'; constructor() { super({ - id: ChatSessionOpenModelPickerAction.ID, - title: localize2('interactive.openModelPicker.label', "Open Model Picker"), + id: ChatSessionPrimaryPickerAction.ID, + title: localize2('interactive.openChatSessionPrimaryPicker.label', "Open Picker"), category: CHAT_CATEGORY, f1: false, precondition: ChatContextKeys.enabled, @@ -1192,7 +1192,7 @@ export function registerChatExecuteActions() { registerAction2(SwitchToNextModelAction); registerAction2(OpenModelPickerAction); registerAction2(OpenModePickerAction); - registerAction2(ChatSessionOpenModelPickerAction); + registerAction2(ChatSessionPrimaryPickerAction); registerAction2(ChangeChatModelAction); registerAction2(CancelEdit); } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 67360e3355a..471850bc014 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -86,7 +86,7 @@ import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, IL import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; -import { ChatOpenModelPickerActionId, ChatSessionOpenModelPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; +import { ChatOpenModelPickerActionId, ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js'; import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; @@ -295,7 +295,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private modelWidget: ModelPickerActionItem | undefined; private modeWidget: ModePickerActionItem | undefined; private chatSessionPickerWidgets: Map = new Map(); - private additionalSessionPickerContainers: HTMLElement[] = []; + private chatSessionPickerContainer: HTMLElement | undefined; + private _lastSessionPickerAction: MenuItemAction | undefined; private readonly _waitForPersistedLanguageModel: MutableDisposable = this._register(new MutableDisposable()); private _onDidChangeCurrentLanguageModel: Emitter = this._register(new Emitter()); private readonly _chatSessionOptionEmitters: Map> = new Map(); @@ -621,15 +622,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } public openChatSessionPicker(): void { + // Open the first available picker widget const firstWidget = this.chatSessionPickerWidgets?.values()?.next().value; firstWidget?.show(); } /** - * Create picker widgets for all registered option groups for the current session type. - * Returns an array of all created widgets. + * Create picker widgets for all option groups available for the current session type. */ private createChatSessionPickerWidgets(action: MenuItemAction): ChatSessionPickerActionItem[] { + this._lastSessionPickerAction = action; + // Helper to resolve chat session context const resolveChatSessionContext = () => { const sessionId = this._widget?.viewModel?.model.sessionId; @@ -1164,12 +1167,29 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatSessionHasOptions.set(true); - // Session dictates which option groups are shown - for (const [optionGroupId, widget] of this.chatSessionPickerWidgets.entries()) { - const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); - if (widget.element) { - widget.element.style.display = currentOption ? '' : 'none'; + const currentWidgetGroupIds = new Set(this.chatSessionPickerWidgets.keys()); + const requiredGroupIds = new Set(optionGroups.map(g => g.id)); + + const needsRecreation = + currentWidgetGroupIds.size !== requiredGroupIds.size || + !Array.from(requiredGroupIds).every(id => currentWidgetGroupIds.has(id)); + + if (needsRecreation && this._lastSessionPickerAction && this.chatSessionPickerContainer) { + const widgets = this.createChatSessionPickerWidgets(this._lastSessionPickerAction); + dom.clearNode(this.chatSessionPickerContainer); + for (const widget of widgets) { + const container = dom.$('.action-item.chat-sessionPicker-item'); + widget.render(container); + this.chatSessionPickerContainer.appendChild(container); } + } + + if (this.chatSessionPickerContainer) { + this.chatSessionPickerContainer.style.display = ''; + } + + for (const [optionGroupId] of this.chatSessionPickerWidgets.entries()) { + const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); if (currentOption) { const optionGroup = optionGroups.find(g => g.id === optionGroupId); if (optionGroup) { @@ -1180,23 +1200,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } } - for (const container of this.additionalSessionPickerContainers) { - const optionGroupId = container.getAttribute('data-option-group-id'); - if (optionGroupId) { - const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); - container.style.display = currentOption ? '' : 'none'; - } - } } private hideAllSessionPickerWidgets(): void { - for (const widget of this.chatSessionPickerWidgets.values()) { - if (widget.element) { - widget.element.style.display = 'none'; - } - } - for (const container of this.additionalSessionPickerContainers) { - container.style.display = 'none'; + if (this.chatSessionPickerContainer) { + this.chatSessionPickerContainer.style.display = 'none'; } } @@ -1205,10 +1213,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge widget.dispose(); } this.chatSessionPickerWidgets.clear(); - for (const container of this.additionalSessionPickerContainers) { - container.remove(); - } - this.additionalSessionPickerContainers = []; } /** @@ -1445,13 +1449,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge currentMode: this._currentModeObservable }; return this.modeWidget = this.instantiationService.createInstance(ModePickerActionItem, action, delegate); - } else if (action.id === ChatSessionOpenModelPickerAction.ID && action instanceof MenuItemAction) { - // Dynamically create pickers for all registered option groups + } else if (action.id === ChatSessionPrimaryPickerAction.ID && action instanceof MenuItemAction) { + // Create all pickers and return a container action view item const widgets = this.createChatSessionPickerWidgets(action); - // Return the first widget, but note: we need a different approach to show all - // The toolbar's actionViewItemProvider can only return one item per action - // We'll need to render additional widgets separately - return widgets[0]; + if (widgets.length === 0) { + return undefined; + } + // Create a container to hold all picker widgets + return this.instantiationService.createInstance(ChatSessionPickersContainerActionItem, action, widgets); } return undefined; } @@ -1459,35 +1464,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.inputActionsToolbar.getElement().classList.add('chat-input-toolbar'); this.inputActionsToolbar.context = { widget } satisfies IChatExecuteActionContext; this._register(this.inputActionsToolbar.onDidChangeMenuItems(() => { - // Clean up any previously rendered additional pickers - for (const container of this.additionalSessionPickerContainers) { - container.remove(); - } - this.additionalSessionPickerContainers = []; - if (this.chatSessionPickerWidgets.size > 1) { - const toolbarElement = this.inputActionsToolbar.getElement(); - // Find where the first session picker is rendered - const firstPickerElement = toolbarElement.querySelector('.chat-sessionPicker-item'); - if (firstPickerElement && firstPickerElement.parentElement) { - // Get all widgets except the first one (which is already rendered by the toolbar) - const widgetEntries = Array.from(this.chatSessionPickerWidgets.entries()).slice(1); - let insertAfter = firstPickerElement; - for (const [optionGroupId, widget] of widgetEntries) { - // Create a container for this widget - const container = dom.$('.action-item'); - container.setAttribute('data-option-group-id', optionGroupId); - widget.render(container); - // Insert after the previous picker - if (insertAfter.nextSibling) { - insertAfter.parentElement!.insertBefore(container, insertAfter.nextSibling); - } else { - insertAfter.parentElement!.appendChild(container); - } - this.additionalSessionPickerContainers.push(container); - insertAfter = container; - } - } - } + // Update container reference for the pickers + const toolbarElement = this.inputActionsToolbar.getElement(); + const container = toolbarElement.querySelector('.chat-sessionPicker-container'); + this.chatSessionPickerContainer = container as HTMLElement | undefined; if (this.cachedDimensions && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) { this.layout(this.cachedDimensions.height, this.cachedDimensions.width); @@ -2166,6 +2146,32 @@ function getLastPosition(model: ITextModel): IPosition { const chatInputEditorContainerSelector = '.interactive-input-editor'; setupSimpleEditorSelectionStyling(chatInputEditorContainerSelector); +class ChatSessionPickersContainerActionItem extends ActionViewItem { + constructor( + action: IAction, + private readonly widgets: ChatSessionPickerActionItem[], + options?: IActionViewItemOptions + ) { + super(null, action, options ?? {}); + } + + override render(container: HTMLElement): void { + container.classList.add('chat-sessionPicker-container'); + for (const widget of this.widgets) { + const itemContainer = dom.$('.action-item.chat-sessionPicker-item'); + widget.render(itemContainer); + container.appendChild(itemContainer); + } + } + + override dispose(): void { + for (const widget of this.widgets) { + widget.dispose(); + } + super.dispose(); + } +} + class AddFilesButton extends ActionViewItem { private showLabel: boolean | undefined; From cf2929e687d07755797efa0dd8dbeaedc3a9f565 Mon Sep 17 00:00:00 2001 From: Takashi Tamura Date: Wed, 22 Oct 2025 02:10:11 +0900 Subject: [PATCH 1404/4355] chat: always apply prompt file and auto-attach instructions. Fix #271624 (#272020) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8e8291e8519..42db37ebcf9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2646,11 +2646,9 @@ export class ChatWidget extends Disposable implements IChatWidget { const isUserQuery = !query; - if (!this.viewModel.editing) { - // process the prompt command - await this._applyPromptFileIfSet(requestInputs); - await this._autoAttachInstructions(requestInputs); - } + // process the prompt command + await this._applyPromptFileIfSet(requestInputs); + await this._autoAttachInstructions(requestInputs); if (this.viewOptions.enableWorkingSet !== undefined && this.input.currentModeKind === ChatModeKind.Edit && !this.chatService.edits2Enabled) { const uniqueWorkingSetEntries = new ResourceSet(); // NOTE: this is used for bookkeeping so the UI can avoid rendering references in the UI that are already shown in the working set From b42e7ba8b417522d99d47ae43770ac6d7e2de9f6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:11:07 +0200 Subject: [PATCH 1405/4355] SCM - hide inline actions in the Repositories view by default (#272471) --- src/vs/workbench/contrib/scm/browser/menus.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 59a953b6dfa..6e58d44ba54 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -10,7 +10,7 @@ import { DisposableStore, IDisposable, dispose } from '../../../../base/common/l import './media/scm.css'; import { localize } from '../../../../nls.js'; import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IMenu, IMenuService, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { IMenu, IMenuService, isIMenuItem, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; @@ -345,6 +345,12 @@ export class SCMMenus implements ISCMMenus, IDisposable { this.repositoryMenuDisposables.clear(); for (const menuItem of MenuRegistry.getMenuItems(MenuId.SCMTitle)) { + // In order to keep the Repositories view clean, we hide the + // primary actions by default. Users can always promote actions + // from the `...` menu to inline actions. + if (isIMenuItem(menuItem) && menuItem.group === 'navigation') { + menuItem.isHiddenByDefault = true; + } this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); } })); From 00673479b7ee1e550f6222bf400b4da75d11b20b Mon Sep 17 00:00:00 2001 From: Giovanni Magliocchetti <62136803+obrobrio2000@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:24:16 +0200 Subject: [PATCH 1406/4355] html: add setting to disable end tag suggestions (#269605) Add html.suggest.hideEndTagSuggestions setting to allow users to disable automatic end tag completion suggestions. When enabled, prevents the completion provider from suggesting closing tags for open elements (e.g., when typing after ). Fixes microsoft/vscode-html-languageservice#216 Signed-off-by: Giovanni Magliocchetti --- extensions/html-language-features/package.json | 6 ++++++ extensions/html-language-features/package.nls.json | 1 + 2 files changed, 7 insertions(+) diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 731a2e396f2..2aae9f4a113 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -182,6 +182,12 @@ "default": true, "description": "%html.suggest.html5.desc%" }, + "html.suggest.hideEndTagSuggestions": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%html.suggest.hideEndTagSuggestions.desc%" + }, "html.validate.scripts": { "type": "boolean", "scope": "resource", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index f36ecf34f02..d8390703757 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -23,6 +23,7 @@ "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to `aligned`.", "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", + "html.suggest.hideEndTagSuggestions.desc": "Controls whether the built-in HTML language support suggests closing tags. When disabled, end tag completions like `` will not be shown.", "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", From f6ec3541a3093153c4a0edf9cc475e12621991dc Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:27:52 -0700 Subject: [PATCH 1407/4355] session bar container flex (#272515) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 61464738d65..d637dd2bc87 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1198,6 +1198,10 @@ have to be updated for changes to the rules above, or to support more deeply nes gap: 4px; } +.interactive-session .chat-input-toolbars .chat-sessionPicker-container { + display: flex; +} + .interactive-session .chat-input-toolbars .codicon-debug-stop { color: var(--vscode-icon-foreground) !important; } From 7b14a618d057b57180d2ad62e50bf48472d4eaf4 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 10:30:30 -0700 Subject: [PATCH 1408/4355] Handoff telemetry --- .../contrib/chat/browser/chatWidget.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8e8291e8519..bb037532379 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -152,6 +152,34 @@ interface IChatHistoryListItem { readonly isActive: boolean; } +type ChatHandoffClickEvent = { + fromAgent: string; + toAgent: string; + hasPrompt: boolean; + autoSend: boolean; +}; + +type ChatHandoffClickClassification = { + owner: 'digitarald'; + comment: 'Event fired when a user clicks on a handoff prompt in the chat suggest-next widget'; + fromAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The agent/mode the user was in before clicking the handoff' }; + toAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The agent/mode specified in the handoff' }; + hasPrompt: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the handoff includes a prompt' }; + autoSend: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the handoff automatically submits the request' }; +}; + +type ChatHandoffWidgetShownEvent = { + agent: string; + handoffCount: number; +}; + +type ChatHandoffWidgetShownClassification = { + owner: 'digitarald'; + comment: 'Event fired when the suggest-next widget is shown with handoff prompts'; + agent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current agent/mode that has handoffs defined' }; + handoffCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of handoff options shown to the user' }; +}; + class ChatHistoryListDelegate implements IListVirtualDelegate { getHeight(element: IChatHistoryListItem): number { return 22; @@ -1742,7 +1770,16 @@ export class ChatWidget extends Disposable implements IChatWidget { const shouldShow = currentMode && handoffs && handoffs.length > 0; if (shouldShow) { + // Log telemetry only when widget transitions from hidden to visible + const wasHidden = this.chatSuggestNextWidget.domNode.style.display === 'none'; this.chatSuggestNextWidget.render(currentMode); + + if (wasHidden) { + this.telemetryService.publicLog2('chat.handoffWidgetShown', { + agent: currentMode.id, + handoffCount: handoffs.length + }); + } } else { this.chatSuggestNextWidget.hide(); } @@ -1758,6 +1795,16 @@ export class ChatWidget extends Disposable implements IChatWidget { this.chatSuggestNextWidget.hide(); this._chatSuggestNextScheduler.cancel(); + // Log telemetry + const currentMode = this.input.currentModeObs.get(); + const fromAgent = currentMode?.id ?? ''; + this.telemetryService.publicLog2('chat.handoffClicked', { + fromAgent: fromAgent, + toAgent: handoff.agent || '', + hasPrompt: Boolean(handoff.prompt), + autoSend: Boolean(handoff.send) + }); + // Switch to the specified agent/mode if provided if (handoff.agent) { this.input.setChatMode(handoff.agent); From d14c77dd6847b58a5c25bb0e51c3c59888477411 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 10:31:35 -0700 Subject: [PATCH 1409/4355] Update quick pick toggle button API --- src/vs/workbench/api/common/extHostQuickOpen.ts | 15 ++++++++++----- .../vscode.proposed.quickInputButtonLocation.d.ts | 7 ++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 8824dba941e..35bb5eb63fa 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -10,7 +10,7 @@ import { ExtHostCommands } from './extHostCommands.js'; import { IExtHostWorkspaceProvider } from './extHostWorkspace.js'; import { InputBox, InputBoxOptions, InputBoxValidationMessage, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickInput, TransferQuickInputButton, TransferQuickPickItemOrSeparator } from './extHost.protocol.js'; -import { QuickInputButtons, QuickPickItemKind, InputBoxValidationSeverity } from './extHostTypes.js'; +import { QuickInputButtons, QuickPickItemKind, InputBoxValidationSeverity, QuickInputButtonLocation } from './extHostTypes.js'; import { isCancellationError } from '../../../base/common/errors.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { coalesce } from '../../../base/common/arrays.js'; @@ -399,9 +399,14 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } set buttons(buttons: QuickInputButton[]) { - if (buttons.some(button => button.location || button.checked !== undefined)) { + if (buttons.some(button => button.location || button.toggle)) { checkProposedApiEnabled(this._extension, 'quickInputButtonLocation'); } + + if (buttons.some(button => button.toggle && button.location !== QuickInputButtonLocation.Input)) { + throw new Error('QuickInputButtons with toggle set are only supported in the Input location.'); + } + this._buttons = buttons.slice(); this._handlesToButtons.clear(); buttons.forEach((button, i) => { @@ -415,7 +420,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx tooltip: button.tooltip, handle: button === QuickInputButtons.Back ? -1 : i, location: button.location, - checked: button.checked + checked: button.toggle?.checked }; }) }); @@ -448,8 +453,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx _fireDidTriggerButton(handle: number, checked?: boolean) { const button = this._handlesToButtons.get(handle); if (button) { - if (checked !== undefined) { - button.checked = checked; + if (checked !== undefined && button.toggle) { + button.toggle.checked = checked; } this._onDidTriggerButtonEmitter.fire(button); } diff --git a/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts b/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts index bfc50d74370..d0ca1fdf03b 100644 --- a/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts +++ b/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts @@ -32,10 +32,11 @@ declare module 'vscode' { location?: QuickInputButtonLocation; /** - * Indicates whether the button is checked. + * When present, indicates that the button is a toggle that can be checked. * @note This property is currently only applicable to buttons with location {@link QuickInputButtonLocation.Input}. - * It must be set for such buttons and will be updated when the button is toggled. + * It must be set for such buttons and the state will be updated when the button is toggled. + * It cannot be set for buttons with other location value currently. */ - checked?: boolean; + toggle?: { checked: boolean }; } } From fa358d14a19f7437c200f690dafd630b98e37751 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 19:38:03 +0200 Subject: [PATCH 1410/4355] mode files: show validation error and code action to migrate (#272485) --- .../languageProviders/promptCodeActions.ts | 26 ++++++++++++++++--- .../languageProviders/promptValidator.ts | 15 ++++++++++- .../promptSyntax/service/promptsService.ts | 6 +++++ .../service/promptsServiceImpl.ts | 7 ++++- .../promptSyntax/utils/promptFilesLocator.ts | 19 ++++++++++++-- .../chat/test/common/mockPromptsService.ts | 2 +- 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts index b6f2124c082..329518e0f21 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts @@ -5,7 +5,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Range } from '../../../../../../editor/common/core/range.js'; -import { CodeAction, CodeActionContext, CodeActionList, CodeActionProvider, IWorkspaceTextEdit, ProviderResult, TextEdit } from '../../../../../../editor/common/languages.js'; +import { CodeAction, CodeActionContext, CodeActionList, CodeActionProvider, IWorkspaceFileEdit, IWorkspaceTextEdit, TextEdit } from '../../../../../../editor/common/languages.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { localize } from '../../../../../../nls.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; @@ -14,6 +14,9 @@ import { IPromptsService } from '../service/promptsService.js'; import { ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { Selection } from '../../../../../../editor/common/core/selection.js'; import { Lazy } from '../../../../../../base/common/lazy.js'; +import { LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { URI } from '../../../../../../base/common/uri.js'; export class PromptCodeActionProvider implements CodeActionProvider { /** @@ -23,11 +26,12 @@ export class PromptCodeActionProvider implements CodeActionProvider { constructor( @IPromptsService private readonly promptsService: IPromptsService, - @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService + @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, + @IFileService private readonly fileService: IFileService, ) { } - provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult { + async provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): Promise { const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); if (!promptType || promptType === PromptsType.instructions) { // if the model is not a prompt, we don't provide any code actions @@ -40,6 +44,7 @@ export class PromptCodeActionProvider implements CodeActionProvider { switch (promptType) { case PromptsType.agent: this.getUpdateToolsCodeActions(parser, model, range, result); + await this.getMigrateModeFileCodeActions(model.uri, result); break; case PromptsType.prompt: this.getUpdateModeCodeActions(parser, model, range, result); @@ -71,6 +76,21 @@ export class PromptCodeActionProvider implements CodeActionProvider { }); } + private async getMigrateModeFileCodeActions(uri: URI, result: CodeAction[]): Promise { + if (uri.path.endsWith(LEGACY_MODE_FILE_EXTENSION)) { + const location = this.promptsService.getAgentFileURIFromModeFile(uri); + if (location && await this.fileService.canMove(uri, location)) { + const edit: IWorkspaceFileEdit = { oldResource: uri, newResource: location, options: { overwrite: false, copy: false } }; + result.push({ + title: localize('migrateToAgent', "Migrate to agent file"), + edit: { + edits: [edit] + } + }); + } + } + } + private getUpdateToolsCodeActions(promptFile: ParsedPromptFile, model: ITextModel, range: Range, result: CodeAction[]): void { const toolsAttr = promptFile.header?.getAttribute(PromptHeaderAttributes.tools); if (toolsAttr?.value.type !== 'array' || !toolsAttr.value.range.containsRange(range)) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 1b730397809..599f15dd4cb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -25,7 +25,7 @@ import { ResourceMap } from '../../../../../../base/common/map.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IPromptsService } from '../service/promptsService.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; - +import { AGENTS_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; @@ -36,12 +36,25 @@ export class PromptValidator { @IChatModeService private readonly chatModeService: IChatModeService, @IFileService private readonly fileService: IFileService, @ILabelService private readonly labelService: ILabelService, + @IPromptsService private readonly promptsService: IPromptsService ) { } public async validate(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { promptAST.header?.errors.forEach(error => report(toMarker(error.message, error.range, MarkerSeverity.Error))); this.validateHeader(promptAST, promptType, report); await this.validateBody(promptAST, report); + await this.validateFileName(promptAST, promptType, report); + } + + private async validateFileName(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { + if (promptType === PromptsType.agent && promptAST.uri.path.endsWith(LEGACY_MODE_FILE_EXTENSION)) { + const location = this.promptsService.getAgentFileURIFromModeFile(promptAST.uri); + if (location && await this.fileService.canCreateFile(location)) { + report(toMarker(localize('promptValidator.chatModesRenamedToAgents', "Chat modes have been renamed to agents. Please move this file to {0}", location.toString()), new Range(1, 1, 1, 4), MarkerSeverity.Warning)); + } else { + report(toMarker(localize('promptValidator.chatModesRenamedToAgentsNoMove', "Chat modes have been renamed to agents. Please move the file to {0}", AGENTS_SOURCE_FOLDER), new Range(1, 1, 1, 4), MarkerSeverity.Warning)); + } + } } private async validateBody(promptAST: ParsedPromptFile, report: (markers: IMarkerData) => void): Promise { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index fbafb992369..c2692b867c1 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -223,6 +223,12 @@ export interface IPromptsService extends IDisposable { * Gets list of .github/copilot-instructions.md files. */ listCopilotInstructionsMDs(token: CancellationToken): Promise; + + /** + * For a chat mode file URI, return the name of the agent file that it should use. + * @param oldURI + */ + getAgentFileURIFromModeFile(oldURI: URI): URI | undefined; } export interface IChatPromptSlashCommand { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 7d4ecefb845..62e9bf3d31c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -75,7 +75,7 @@ export class PromptsService extends Disposable implements IPromptsService { @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, - @IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService, ) { super(); @@ -379,6 +379,11 @@ export class PromptsService extends Disposable implements IPromptsService { } return await this.fileLocator.findCopilotInstructionsMDsInWorkspace(token); } + + public getAgentFileURIFromModeFile(oldURI: URI): URI | undefined { + return this.fileLocator.getAgentFileURIFromModeFile(oldURI); + } + } function getCommandNameFromPromptPath(promptPath: IPromptPath): string { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index 10018713ba2..b346f9f1d45 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -8,10 +8,10 @@ import { isAbsolute } from '../../../../../../base/common/path.js'; import { ResourceSet } from '../../../../../../base/common/map.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { getPromptFileLocationsConfigKey, PromptsConfig } from '../config/config.js'; -import { basename, dirname, joinPath } from '../../../../../../base/common/resources.js'; +import { basename, dirname, isEqualOrParent, joinPath } from '../../../../../../base/common/resources.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType } from '../config/promptFileLocations.js'; +import { COPILOT_CUSTOM_INSTRUCTIONS_FILENAME, AGENTS_SOURCE_FOLDER, getPromptFileExtension, getPromptFileType, LEGACY_MODE_FILE_EXTENSION, getCleanPromptName, AGENT_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { PromptsType } from '../promptTypes.js'; import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js'; import { Schemas } from '../../../../../../base/common/network.js'; @@ -301,6 +301,7 @@ export class PromptFilesLocator extends Disposable { const result = await Promise.all(this.workspaceService.getWorkspace().folders.map(folder => this.findAgentMDsInFolder(folder.uri, token))); return result.flat(1); } + private async findAgentMDsInFolder(folder: URI, token: CancellationToken): Promise { const disregardIgnoreFiles = this.configService.getValue('explorer.excludeGitIgnore'); const getExcludePattern = (folder: URI) => getExcludes(this.configService.getValue({ resource: folder })) || {}; @@ -344,6 +345,20 @@ export class PromptFilesLocator extends Disposable { } return result; } + + public getAgentFileURIFromModeFile(oldURI: URI): URI | undefined { + if (oldURI.path.endsWith(LEGACY_MODE_FILE_EXTENSION)) { + let newLocation; + const workspaceFolder = this.workspaceService.getWorkspaceFolder(oldURI); + if (workspaceFolder) { + newLocation = joinPath(workspaceFolder.uri, AGENTS_SOURCE_FOLDER, getCleanPromptName(oldURI) + AGENT_FILE_EXTENSION); + } else if (isEqualOrParent(oldURI, this.userDataService.currentProfile.promptsHome)) { + newLocation = joinPath(this.userDataService.currentProfile.promptsHome, getCleanPromptName(oldURI) + AGENT_FILE_EXTENSION); + } + return newLocation; + } + return undefined; + } } /** diff --git a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts index 948a8414563..76eb5ed0f7c 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts @@ -48,6 +48,6 @@ export class MockPromptsService implements IPromptsService { findAgentMDsInWorkspace(token: CancellationToken): Promise { throw new Error('Not implemented'); } listAgentMDs(token: CancellationToken): Promise { throw new Error('Not implemented'); } listCopilotInstructionsMDs(token: CancellationToken): Promise { throw new Error('Not implemented'); } - + getAgentFileURIFromModeFile(oldURI: URI): URI | undefined { throw new Error('Not implemented'); } dispose(): void { } } From 3daa4445b53208310b9773b95c1c02ed017992d4 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 21 Oct 2025 18:42:39 +0100 Subject: [PATCH 1411/4355] Use fixed 16px font-size for chat todo status icon --- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 61464738d65..f87af19d13e 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2713,7 +2713,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .chat-todo-list-widget .todo-item > .todo-status-icon.codicon { flex-shrink: 0; - font-size: var(--vscode-chat-font-size-body-l); + font-size: 16px; } .chat-todo-list-widget .todo-content { From bca57f0e24dc1e903e6aa2832ad851baaa4b44a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 10:59:55 -0700 Subject: [PATCH 1412/4355] PR feedback --- src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts b/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts index d0ca1fdf03b..bb41ac8a9ef 100644 --- a/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts +++ b/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts @@ -37,6 +37,6 @@ declare module 'vscode' { * It must be set for such buttons and the state will be updated when the button is toggled. * It cannot be set for buttons with other location value currently. */ - toggle?: { checked: boolean }; + readonly toggle?: { checked: boolean }; } } From 3bb287f6fbb93d3628f81bb45f19b0a79c48dc02 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 11:05:23 -0700 Subject: [PATCH 1413/4355] Update Go to Offset toggle to a new one --- .../editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 972420ad578..22d4e9e76e1 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -94,7 +94,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // Add a toggle to switch between 1- and 0-based offsets. const toggle = new Toggle({ title: localize('gotoLineToggle', "Use zero-based offset"), - icon: Codicon.symbolArray, + icon: Codicon.indexZero, isChecked: this.useZeroBasedOffset.value, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), From d304c575facf9ab73add26186eb856ce37a22bc8 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 11:34:56 -0700 Subject: [PATCH 1414/4355] Consistent custom agent copy --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 4 ++-- .../contrib/chat/browser/actions/chatToolActions.ts | 4 ++-- .../workbench/contrib/chat/browser/chat.contribution.ts | 8 ++++---- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 +++--- .../chat/browser/promptSyntax/newPromptFileActions.ts | 8 ++++---- .../browser/promptSyntax/pickers/promptFilePickers.ts | 2 +- .../contrib/chat/browser/promptSyntax/promptUrlHandler.ts | 2 +- .../browser/viewsWelcome/chatViewWelcomeController.ts | 3 +-- .../promptSyntax/languageProviders/promptCodeActions.ts | 2 +- .../common/promptSyntax/languageProviders/promptHovers.ts | 8 ++++---- .../promptSyntax/languageProviders/promptValidator.ts | 4 ++-- 11 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 8691714c094..4fbf5de92fb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -431,8 +431,8 @@ export class OpenModePickerAction extends Action2 { constructor() { super({ id: OpenModePickerAction.ID, - title: localize2('interactive.openModePicker.label', "Open Mode Picker"), - tooltip: localize('setChatMode', "Set Mode"), + title: localize2('interactive.openModePicker.label', "Open Agent Picker"), + tooltip: localize('setChatMode', "Set Agent"), category: CHAT_CATEGORY, f1: false, precondition: ChatContextKeys.enabled, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index a52b0992758..581abeab7aa 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -160,8 +160,8 @@ class ConfigureToolsAction extends Action2 { description = localize('chat.tools.description.session', "The selected tools were configured by a prompt command and only apply to this chat session."); break; case ToolsScope.Mode: - placeholder = localize('chat.tools.placeholder.mode', "Select tools for this agent"); - description = localize('chat.tools.description.mode', "The selected tools are configured by the '{0}' agent. Changes to the tools will be applied to the agent file as well.", widget.input.currentModeObs.get().label); + placeholder = localize('chat.tools.placeholder.mode', "Select tools for this custom agent"); + description = localize('chat.tools.description.mode', "The selected tools are configured by the '{0}' custom agent. Changes to the tools will be applied to the custom agent file as well.", widget.input.currentModeObs.get().label); break; case ToolsScope.Global: placeholder = localize('chat.tools.placeholder.global', "Select tools that are available to chat."); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 469ddcce680..e4b95037eac 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -195,7 +195,7 @@ configurationRegistry.registerConfiguration({ 'chat.implicitContext.suggestedContext': { type: 'boolean', tags: ['experimental'], - markdownDescription: nls.localize('chat.implicitContext.suggestedContext', "Controls whether the new implicit context flow is shown. In Ask and Edit modes, the context will automatically be included. In Agent mode context will be suggested as an attachment. Selections are always included as context."), + markdownDescription: nls.localize('chat.implicitContext.suggestedContext', "Controls whether the new implicit context flow is shown. In Ask and Edit modes, the context will automatically be included. When using an agent, context will be suggested as an attachment. Selections are always included as context."), default: true, }, 'chat.editing.autoAcceptDelay': { @@ -425,7 +425,7 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.AgentEnabled]: { type: 'boolean', - description: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), + description: nls.localize('chat.agent.enabled.description', "Enable agents for chat. When this is enabled, agents can be activated via the dropdown in the view."), default: true, policy: { name: 'ChatAgentMode', @@ -571,7 +571,7 @@ configurationRegistry.registerConfiguration({ default: { [LEGACY_MODE_DEFAULT_SOURCE_FOLDER]: true, }, - deprecationMessage: nls.localize('chat.mode.config.locations.deprecated', "This setting is deprecated and will be removed in future releases. Chat modes are now called agents and are located in `.github/agents`"), + deprecationMessage: nls.localize('chat.mode.config.locations.deprecated', "This setting is deprecated and will be removed in future releases. Chat modes are now called custom agents and are located in `.github/agents`"), additionalProperties: { type: 'boolean' }, unevaluatedProperties: { type: 'boolean' }, restricted: true, @@ -809,7 +809,7 @@ class ChatAgentSettingContribution extends Disposable implements IWorkbenchContr properties: { 'chat.agent.maxRequests': { type: 'number', - markdownDescription: nls.localize('chat.agent.maxRequests', "The maximum number of requests to allow per-turn in agent mode. When the limit is reached, will ask to confirm to continue."), + markdownDescription: nls.localize('chat.agent.maxRequests', "The maximum number of requests to allow per-turn when using an agent. When the limit is reached, will ask to confirm to continue."), default: defaultValue, }, } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 42db37ebcf9..e76e8746b08 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1460,11 +1460,11 @@ export class ChatWidget extends Disposable implements IChatWidget { suggestedPrompts }; } else { - const agentHelpMessage = localize('agentMessage', "Ask to edit your files in [agent mode]({0}). Agent mode will automatically use multiple requests to pick files to edit, run terminal commands, and iterate on errors.", 'https://aka.ms/vscode-copilot-agent'); + const agentHelpMessage = localize('agentMessage', "Ask to edit your files using [Agent]({0}). Agent will automatically use multiple requests to pick files to edit, run terminal commands, and iterate on errors.", 'https://aka.ms/vscode-copilot-agent'); const message = expEmptyState ? disclaimerMessage : `${agentHelpMessage}\n\n${disclaimerMessage}`; return { - title: localize('agentTitle', "Build with agent mode"), + title: localize('agentTitle', "Build with Agent"), message: new MarkdownString(message), icon, additionalMessage, @@ -1488,7 +1488,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const providerTips = this._lockedAgent ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; const suggestedPrompts = this._lockedAgent ? undefined : this.getNewSuggestedPrompts(); const welcomeContent: IChatViewWelcomeContent = { - title: providerTitle ?? localize('expChatTitle', 'Build with agent mode'), + title: providerTitle ?? localize('expChatTitle', 'Build with Agent'), message: providerMessage ? new MarkdownString(providerMessage) : new MarkdownString(localize('expchatMessage', "Let's get started")), icon: providerIcon ?? Codicon.chatSparkle, inputPart: this.inputPart.element, diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index 83ec11802bb..d96288a817e 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -112,7 +112,7 @@ class AbstractNewPromptFileAction extends Action2 { Severity.Info, localize( 'workbench.command.prompts.create.user.enable-sync-notification', - "Do you want to backup and sync your user prompt, instruction and agent files with Setting Sync?'", + "Do you want to backup and sync your user prompt, instruction and custom agent files with Setting Sync?'", ), [ { @@ -161,10 +161,10 @@ class AbstractNewPromptFileAction extends Action2 { case PromptsType.agent: return [ `---`, - `description: '\${1:Description of the agent.}'`, + `description: '\${1:Describe what this custom agent does and when to use it.}'`, `tools: []`, `---`, - `\${2:Define the purpose of this agent and how AI should behave: response style, available tools, focus areas, and any agent-specific instructions or constraints.}`, + `\${2:Define what this custom agent accomplishes for the user, when to use it, and the edges it won't cross. Specify its ideal inputs/outputs, the tools it may call, and how it reports progress or asks for help.}`, ].join('\n'); default: throw new Error(`Unknown prompt type: ${promptType}`); @@ -192,7 +192,7 @@ class NewInstructionsFileAction extends AbstractNewPromptFileAction { class NewAgentFileAction extends AbstractNewPromptFileAction { constructor() { - super(NEW_AGENT_COMMAND_ID, localize('commands.new.agent.local.title', "New Agent File..."), PromptsType.agent); + super(NEW_AGENT_COMMAND_ID, localize('commands.new.agent.local.title', "New Custom Agent..."), PromptsType.agent); } } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index b6bce16fa7b..8096052598e 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -144,7 +144,7 @@ const NEW_AGENT_FILE_OPTION: IPromptPickerQuickPickItem = Object.freeze({ type: 'item', label: `$(plus) ${localize( 'commands.new-agentfile.select-dialog.label', - 'Create new agent file...', + 'Create new custom agent...', )}`, value: URI.parse(AGENT_DOCUMENTATION_URL), pickable: false, diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts index de7151064f8..b77665f2351 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptUrlHandler.ts @@ -136,7 +136,7 @@ export class PromptUrlHandler extends Disposable implements IWorkbenchContributi message = localize('confirmInstallInstructions', "An external application wants to create an instructions file with content from a URL. Do you want to continue by selecting a destination folder and name?"); break; default: - message = localize('confirmInstallAgent', "An external application wants to create an agent with content from a URL. Do you want to continue by selecting a destination folder and name?"); + message = localize('confirmInstallAgent', "An external application wants to create a custom agent with content from a URL. Do you want to continue by selecting a destination folder and name?"); break; } diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index 14d3849169c..6d058c13372 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -168,10 +168,9 @@ export class ChatViewWelcomePart extends Disposable { const expEmptyState = this.configurationService.getValue('chat.emptyChatState.enabled'); if (typeof content.message !== 'function' && options?.isWidgetAgentWelcomeViewContent && !expEmptyState) { const container = dom.append(this.element, $('.chat-welcome-view-indicator-container')); - dom.append(container, $('.chat-welcome-view-subtitle', undefined, localize('agentModeSubtitle', "Agent Mode"))); + dom.append(container, $('.chat-welcome-view-subtitle', undefined, localize('agentModeSubtitle', "Agent"))); } - // Message const message = dom.append(this.element, content.isNew ? $('.chat-welcome-new-view-message') : $('.chat-welcome-view-message')); message.classList.toggle('empty-state', expEmptyState); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts index 329518e0f21..56d8bd2d7c0 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts @@ -82,7 +82,7 @@ export class PromptCodeActionProvider implements CodeActionProvider { if (location && await this.fileService.canMove(uri, location)) { const edit: IWorkspaceFileEdit = { oldResource: uri, newResource: location, options: { overwrite: false, copy: false } }; result.push({ - title: localize('migrateToAgent', "Migrate to agent file"), + title: localize('migrateToAgent', "Migrate to custom agent file"), edit: { edits: [edit] } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 98aa4192a2c..1d5a18c044c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -80,20 +80,20 @@ export class PromptHoverProvider implements HoverProvider { } else if (promptType === PromptsType.agent) { const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.agent.description', 'The description of the agent file. It is a short description of what the agent does.'), descriptionRange); + return this.createHover(localize('promptHeader.agent.description', 'The description of the custom agent, what it does and when to use it.'), descriptionRange); } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { - return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'The model to use in this agent.')); + return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'Specify the model that runs this custom agent.')); } const tools = header.getAttribute(PromptHeaderAttributes.tools); if (tools?.range.containsPosition(position)) { - return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The tools to use in this agent.')); + return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The set of tools that the custom agent has access to.')); } } else { const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It is a short description of what the prompt does.'), descriptionRange); + return this.createHover(localize('promptHeader.prompt.description', 'The description of the reusable prompt, what it does and when to use it.'), descriptionRange); } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 599f15dd4cb..fecabd585c0 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -129,7 +129,7 @@ export class PromptValidator { report(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); break; case PromptsType.agent: - report(toMarker(localize('promptValidator.unknownAttribute.agent', "Attribute '{0}' is not supported in agent files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.unknownAttribute.agent', "Attribute '{0}' is not supported in custom agent files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); break; case PromptsType.instructions: report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); @@ -261,7 +261,7 @@ export class PromptValidator { return; } if (agentKind !== ChatModeKind.Agent) { - report(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported in agent mode. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); + report(toMarker(localize('promptValidator.toolsOnlyInAgent', "The 'tools' attribute is only supported when using agents. Attribute will be ignored."), attribute.range, MarkerSeverity.Warning)); } switch (attribute.value.type) { From 44663093112905e8a4bda34d825569245240c940 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 21 Oct 2025 11:53:36 -0700 Subject: [PATCH 1415/4355] Update per comments --- .../common/promptSyntax/service/promptsServiceImpl.ts | 10 +++------- .../common/promptSyntax/service/promptsService.test.ts | 4 ---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index c48fe8d6a37..915549de950 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -96,16 +96,12 @@ export class PromptsService extends Disposable implements IPromptsService { this.promptFileByCommandCache.clear(); } else { - const pendingClear = []; + // Clear cache for prompt files that match the changed URI for (const [key, value] of this.promptFileByCommandCache) { - if (value.value?.uri === event.uri) { - pendingClear.push(key); + if (isEqual(value.value?.uri, event.uri)) { + this.promptFileByCommandCache.delete(key); } } - - for (const key of pendingClear) { - this.promptFileByCommandCache.delete(key); - } } this.onDidChangeParsedPromptFilesCacheEmitter.fire(); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index e07f736b25c..21dd5807f3c 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -14,8 +14,6 @@ import { Range } from '../../../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../../editor/common/services/model.js'; import { ModelService } from '../../../../../../../editor/common/services/modelService.js'; -import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js'; -import { TestThemeService } from '../../../../../../../platform/theme/test/common/testThemeService.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; @@ -73,8 +71,6 @@ suite('PromptsService', () => { const fileService = disposables.add(instaService.createInstance(FileService)); instaService.stub(IFileService, fileService); - // Set up model service with theme service requirement - instaService.stub(IThemeService, new TestThemeService()); const modelService = disposables.add(instaService.createInstance(ModelService)); instaService.stub(IModelService, modelService); instaService.stub(ILanguageService, { From b71efe876aaf03346ddb35cedbf3f77a8e068e9c Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 12:41:21 -0700 Subject: [PATCH 1416/4355] Update src/vs/workbench/contrib/chat/browser/chatWidget.ts --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e76e8746b08..40ad27fa9e2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1488,7 +1488,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const providerTips = this._lockedAgent ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; const suggestedPrompts = this._lockedAgent ? undefined : this.getNewSuggestedPrompts(); const welcomeContent: IChatViewWelcomeContent = { - title: providerTitle ?? localize('expChatTitle', 'Build with Agent'), + title: providerTitle ?? localize('expChatTitle', 'Build with agents'), message: providerMessage ? new MarkdownString(providerMessage) : new MarkdownString(localize('expchatMessage', "Let's get started")), icon: providerIcon ?? Codicon.chatSparkle, inputPart: this.inputPart.element, From 2c95f8d28435429eb231ca0f7c252f3615be0ce7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 21:44:48 +0200 Subject: [PATCH 1417/4355] `.vscode-agent.md` -> `.agent.md` (#272535) --- extensions/prompt-basics/package.json | 1 - .../userDataSync/common/promptsSync/promptsSync.ts | 2 +- .../promptSyntax/config/promptFileLocations.ts | 2 +- .../promptSyntax/service/newPromptsParser.test.ts | 4 ++-- .../promptSyntax/service/promptsService.test.ts | 12 ++++++------ 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index 00f1f9be7bb..caeeb0b73ff 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -45,7 +45,6 @@ ], "extensions": [ ".agent.md", - ".vscode-agent.md", ".chatmode.md" ], "configuration": "./language-configuration.json" diff --git a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts index 5b5aa362bd8..e2d57a6e72e 100644 --- a/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts +++ b/src/vs/platform/userDataSync/common/promptsSync/promptsSync.ts @@ -517,7 +517,7 @@ export class PromptsSynchronizer extends AbstractSynchroniser implements IUserDa for (const entry of stat.children || []) { const resource = entry.resource; const path = resource.path; - if (['.prompt.md', '.instructions.md', '.chatmode.md', '.vscode-agent.md'].some(ext => path.endsWith(ext))) { + if (['.prompt.md', '.instructions.md', '.chatmode.md', '.agent.md'].some(ext => path.endsWith(ext))) { const key = this.extUri.relativePath(this.promptsFolder, resource)!; const content = await this.fileService.readFile(resource); prompts[key] = content; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index 741fee4406f..ed5874143d3 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -25,7 +25,7 @@ export const LEGACY_MODE_FILE_EXTENSION = '.chatmode.md'; /** * File extension for the agent files. */ -export const AGENT_FILE_EXTENSION = '.vscode-agent.md'; +export const AGENT_FILE_EXTENSION = '.agent.md'; /** * Copilot custom instructions file name. diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 783dd6c65a4..89af0fd6f4a 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -14,7 +14,7 @@ suite('NewPromptsParser', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('agent', async () => { - const uri = URI.parse('file:///test/test.vscode-agent.md'); + const uri = URI.parse('file:///test/test.agent.md'); const content = [ /* 01 */'---', /* 02 */`description: "Agent test"`, @@ -58,7 +58,7 @@ suite('NewPromptsParser', () => { }); test('mode with handoff', async () => { - const uri = URI.parse('file:///test/test.vscode-agent.md'); + const uri = URI.parse('file:///test/test.agent.md'); const content = [ /* 01 */'---', /* 02 */`description: "Agent test"`, diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 5a083a0b891..e99cf9865bd 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -758,7 +758,7 @@ suite('PromptsService', () => { name: '.github/agents', children: [ { - name: 'agent1.vscode-agent.md', + name: 'agent1.agent.md', contents: [ '---', 'description: \'Agent file 1.\'', @@ -785,7 +785,7 @@ suite('PromptsService', () => { }, model: undefined, tools: undefined, - uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.vscode-agent.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } }, ]; @@ -813,7 +813,7 @@ suite('PromptsService', () => { name: '.github/agents', children: [ { - name: 'agent1.vscode-agent.md', + name: 'agent1.agent.md', contents: [ '---', 'description: \'Agent file 1.\'', @@ -823,7 +823,7 @@ suite('PromptsService', () => { ], }, { - name: 'agent2.vscode-agent.md', + name: 'agent2.agent.md', contents: [ 'First use #tool2\nThen use #tool1', ], @@ -847,7 +847,7 @@ suite('PromptsService', () => { }, handOffs: undefined, model: undefined, - uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.vscode-agent.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local }, }, { @@ -860,7 +860,7 @@ suite('PromptsService', () => { ], metadata: undefined }, - uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.vscode-agent.md'), + uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.agent.md'), source: { storage: PromptsStorage.local }, } ]; From d72d6a24245db6cc9d80988f7cda1305a385037c Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 21 Oct 2025 12:48:33 -0700 Subject: [PATCH 1418/4355] Generate policy data as JSON (#272368) --- build/.gitignore | 1 + .../steps/product-build-darwin-compile.yml | 2 +- .../steps/product-build-win32-compile.yml | 2 +- build/lib/policies/copyPolicyDto.js | 58 +++ build/lib/policies/copyPolicyDto.ts | 25 + build/lib/policies/policyData.jsonc | 277 +++++++++++ .../policyGenerator.js} | 357 +++++---------- .../policyGenerator.ts} | 432 ++++-------------- build/package.json | 9 +- src/vs/base/common/policy.ts | 59 ++- .../test/common/configurationRegistry.test.ts | 9 +- .../test/common/configurationService.test.ts | 3 + .../test/common/policyConfiguration.test.ts | 15 + src/vs/platform/environment/common/argv.ts | 1 + .../environment/common/environment.ts | 1 + .../environment/common/environmentService.ts | 4 + src/vs/platform/environment/node/argv.ts | 1 + .../common/extensionManagement.ts | 9 +- .../telemetry/common/telemetryService.ts | 29 +- .../common/update.config.contribution.ts | 23 + .../contrib/chat/browser/chat.contribution.ts | 62 ++- .../browser/extensions.contribution.ts | 8 + .../contrib/policyExport/common/policyDto.ts | 33 ++ .../policyExport.contribution.ts | 117 +++++ .../test/node/policyExport.integrationTest.ts | 66 +++ .../terminal/common/terminalConfiguration.ts | 8 + .../test/browser/configurationEditing.test.ts | 3 + .../test/browser/configurationService.test.ts | 5 + .../test/common/accountPolicyService.test.ts | 9 + .../common/multiplexPolicyService.test.ts | 9 + src/vs/workbench/workbench.desktop.main.ts | 3 + 31 files changed, 1048 insertions(+), 592 deletions(-) create mode 100644 build/lib/policies/copyPolicyDto.js create mode 100644 build/lib/policies/copyPolicyDto.ts create mode 100644 build/lib/policies/policyData.jsonc rename build/lib/{policies.js => policies/policyGenerator.js} (70%) rename build/lib/{policies.ts => policies/policyGenerator.ts} (69%) create mode 100644 src/vs/workbench/contrib/policyExport/common/policyDto.ts create mode 100644 src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts create mode 100644 src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts diff --git a/build/.gitignore b/build/.gitignore index 61cf49cb9a1..679674617c7 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1 +1,2 @@ *.js.map +lib/policies/policyDto.* diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index bc8e962f91b..d1d431505f6 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -118,7 +118,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies darwin + - script: node build/lib/policies/policyGenerator build/lib/policies/policyData.jsonc darwin displayName: Generate policy definitions retryCountOnTaskFailure: 3 diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index 072d64ec2d9..bdc807fdae5 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -120,7 +120,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - powershell: node build\lib\policies win32 + - powershell: node build\lib\policies\policyGenerator build\lib\policies\policyData.jsonc win32 displayName: Generate Group Policy definitions retryCountOnTaskFailure: 3 diff --git a/build/lib/policies/copyPolicyDto.js b/build/lib/policies/copyPolicyDto.js new file mode 100644 index 00000000000..8ce193770c7 --- /dev/null +++ b/build/lib/policies/copyPolicyDto.js @@ -0,0 +1,58 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); +const destFile = path.join(__dirname, 'policyDto.ts'); +try { + // Check if source file exists + if (!fs.existsSync(sourceFile)) { + console.error(`Error: Source file not found: ${sourceFile}`); + console.error('Please ensure policyDto.ts exists in src/vs/workbench/contrib/policyExport/common/'); + process.exit(1); + } + // Copy the file + fs.copyFileSync(sourceFile, destFile); +} +catch (error) { + console.error(`Error copying policyDto.ts: ${error.message}`); + process.exit(1); +} +//# sourceMappingURL=copyPolicyDto.js.map \ No newline at end of file diff --git a/build/lib/policies/copyPolicyDto.ts b/build/lib/policies/copyPolicyDto.ts new file mode 100644 index 00000000000..4fb74456837 --- /dev/null +++ b/build/lib/policies/copyPolicyDto.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; + +const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); +const destFile = path.join(__dirname, 'policyDto.ts'); + +try { + // Check if source file exists + if (!fs.existsSync(sourceFile)) { + console.error(`Error: Source file not found: ${sourceFile}`); + console.error('Please ensure policyDto.ts exists in src/vs/workbench/contrib/policyExport/common/'); + process.exit(1); + } + + // Copy the file + fs.copyFileSync(sourceFile, destFile); +} catch (error) { + console.error(`Error copying policyDto.ts: ${(error as Error).message}`); + process.exit(1); +} diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc new file mode 100644 index 00000000000..096b911e5d3 --- /dev/null +++ b/build/lib/policies/policyData.jsonc @@ -0,0 +1,277 @@ +/** THIS FILE IS AUTOMATICALLY GENERATED USING `code --export-policy-data`. DO NOT MODIFY IT MANUALLY. **/ +{ + "categories": [ + { + "key": "Extensions", + "name": { + "key": "extensionsConfigurationTitle", + "value": "Extensions" + } + }, + { + "key": "IntegratedTerminal", + "name": { + "key": "terminalIntegratedConfigurationTitle", + "value": "Integrated Terminal" + } + }, + { + "key": "InteractiveSession", + "name": { + "key": "interactiveSessionConfigurationTitle", + "value": "Chat" + } + }, + { + "key": "Telemetry", + "name": { + "key": "telemetryConfigurationTitle", + "value": "Telemetry" + } + }, + { + "key": "Update", + "name": { + "key": "updateConfigurationTitle", + "value": "Update" + } + } + ], + "policies": [ + { + "key": "chat.mcp.gallery.serviceUrl", + "name": "McpGalleryServiceUrl", + "category": "InteractiveSession", + "minimumVersion": "1.101", + "localization": { + "description": { + "key": "mcp.gallery.serviceUrl", + "value": "Configure the MCP Gallery service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.gallery.serviceUrl", + "name": "ExtensionGalleryServiceUrl", + "category": "Extensions", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "extensions.gallery.serviceUrl", + "value": "Configure the Marketplace service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.allowed", + "name": "AllowedExtensions", + "category": "Extensions", + "minimumVersion": "1.96", + "localization": { + "description": { + "key": "extensions.allowed.policy", + "value": "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions" + } + }, + "type": "object", + "default": "*" + }, + { + "key": "chat.tools.global.autoApprove", + "name": "ChatToolsAutoApprove", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "autoApprove2.description", + "value": "Global auto approve also known as \"YOLO mode\" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine." + } + }, + "type": "boolean", + "default": false + }, + { + "key": "chat.mcp.access", + "name": "ChatMCP", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.mcp.access", + "value": "Controls access to installed Model Context Protocol servers." + }, + "enumDescriptions": [ + { + "key": "chat.mcp.access.none", + "value": "No access to MCP servers." + }, + { + "key": "chat.mcp.access.registry", + "value": "Allows access to MCP servers installed from the registry that VS Code is connected to." + }, + { + "key": "chat.mcp.access.any", + "value": "Allow access to any installed MCP server." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "none", + "registry", + "all" + ] + }, + { + "key": "chat.extensionTools.enabled", + "name": "ChatAgentExtensionTools", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.extensionToolsEnabled", + "value": "Enable using tools contributed by third-party extensions." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.agent.enabled", + "name": "ChatAgentMode", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.agent.enabled.description", + "value": "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.promptFiles", + "name": "ChatPromptFiles", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.promptFiles.policy", + "value": "Enables reusable prompt and instruction files in Chat sessions." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.tools.terminal.enableAutoApprove", + "name": "ChatToolsTerminalEnableAutoApprove", + "category": "IntegratedTerminal", + "minimumVersion": "1.104", + "localization": { + "description": { + "key": "autoApproveMode.description", + "value": "Controls whether to allow auto approval in the run in terminal tool." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "update.mode", + "name": "UpdateMode", + "category": "Update", + "minimumVersion": "1.67", + "localization": { + "description": { + "key": "updateMode", + "value": "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service." + }, + "enumDescriptions": [ + { + "key": "none", + "value": "Disable updates." + }, + { + "key": "manual", + "value": "Disable automatic background update checks. Updates will be available if you manually check for updates." + }, + { + "key": "start", + "value": "Check for updates only on startup. Disable automatic background update checks." + }, + { + "key": "default", + "value": "Enable automatic update checks. Code will check for updates automatically and periodically." + } + ] + }, + "type": "string", + "default": "default", + "enum": [ + "none", + "manual", + "start", + "default" + ] + }, + { + "key": "telemetry.telemetryLevel", + "name": "TelemetryLevel", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.telemetryLevel.policyDescription", + "value": "Controls the level of telemetry." + }, + "enumDescriptions": [ + { + "key": "telemetry.telemetryLevel.default", + "value": "Sends usage data, errors, and crash reports." + }, + { + "key": "telemetry.telemetryLevel.error", + "value": "Sends general error telemetry and crash reports." + }, + { + "key": "telemetry.telemetryLevel.crash", + "value": "Sends OS level crash reports." + }, + { + "key": "telemetry.telemetryLevel.off", + "value": "Disables all product telemetry." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "all", + "error", + "crash", + "off" + ] + }, + { + "key": "telemetry.feedback.enabled", + "name": "EnableFeedback", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.feedback.enabled", + "value": "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options." + } + }, + "type": "boolean", + "default": true + } + ] +} diff --git a/build/lib/policies.js b/build/lib/policies/policyGenerator.js similarity index 70% rename from build/lib/policies.js rename to build/lib/policies/policyGenerator.js index d2ef760870d..7ddb6956c18 100644 --- a/build/lib/policies.js +++ b/build/lib/policies/policyGenerator.js @@ -1,4 +1,37 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -7,24 +40,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const child_process_1 = require("child_process"); +const minimist_1 = __importDefault(require("minimist")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); -const byline_1 = __importDefault(require("byline")); -const ripgrep_1 = require("@vscode/ripgrep"); -const tree_sitter_1 = __importDefault(require("tree-sitter")); -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); -function isNlsString(value) { - return value ? typeof value !== 'string' : false; -} -function isStringArray(value) { - return !value.some(s => isNlsString(s)); -} -function isNlsStringArray(value) { - return value.every(s => isNlsString(s)); -} +const JSONC = __importStar(require("jsonc-parser")); +const product = require('../../../product.json'); +const packageJson = require('../../../package.json'); var PolicyType; (function (PolicyType) { PolicyType["Boolean"] = "boolean"; @@ -107,12 +128,12 @@ ${this.renderProfileManifestValue(translations)} } } class BooleanPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { name, minimumVersion, localization, type } = policy; if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); @@ -143,23 +164,17 @@ class BooleanPolicy extends BasePolicy { boolean`; } } -class ParseError extends Error { - constructor(message, moduleName, node) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} class NumberPolicy extends BasePolicy { defaultValue; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; if (type !== 'number') { return undefined; } - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); } - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); } constructor(name, category, minimumVersion, description, moduleName, defaultValue) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -191,12 +206,12 @@ class NumberPolicy extends BasePolicy { } } class StringPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; if (type !== 'string') { return undefined; } - return new StringPolicy(name, category, minimumVersion, description, moduleName); + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.String, name, category, minimumVersion, description, moduleName); @@ -224,12 +239,12 @@ class StringPolicy extends BasePolicy { } } class ObjectPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Object, name, category, minimumVersion, description, moduleName); @@ -260,26 +275,20 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category, policy) { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; if (type !== 'string') { return undefined; } - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); + const enum_ = enumValue; if (!enum_) { return undefined; } - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); - } - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); } - else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); } constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -322,178 +331,6 @@ class StringEnumPolicy extends BasePolicy { `; } } -const NumberQ = { - Q: `(number) @value`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - return parseInt(value); - } -}; -const StringQ = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - if (nlsKey) { - return { value, nlsKey }; - } - else { - return value; - } - } -}; -const StringArrayQ = { - Q: `(array ${StringQ.Q})`, - value(matches) { - if (matches.length === 0) { - return undefined; - } - return matches.map(match => { - return StringQ.value([match]); - }); - } -}; -function getProperty(qtype, moduleName, node, key) { - const query = new tree_sitter_1.default.Query(typescript, `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )`); - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } - catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} -function getNumberProperty(moduleName, node, key) { - return getProperty(NumberQ, moduleName, node, key); -} -function getStringProperty(moduleName, node, key) { - return getProperty(StringQ, moduleName, node, key); -} -function getStringArrayProperty(moduleName, node, key) { - return getProperty(StringArrayQ, moduleName, node, key); -} -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; -function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { - const name = getStringProperty(moduleName, policyNode, 'name'); - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } - else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } - else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } - else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } - if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - let result; - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - return result; -} -function getPolicies(moduleName, node) { - const query = new tree_sitter_1.default.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - const categories = new Map(); - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} -async function getFiles(root) { - return new Promise((c, e) => { - const result = []; - const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} function renderADMX(regKey, versions, categories, policies) { versions = versions.map(v => v.replace(/\./g, '_')); return ` @@ -757,7 +594,15 @@ async function getSpecificNLS(resourceUrlTemplate, languageId, version) { throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); } const { contents: result } = await res.json(); - return result; + // TODO: support module namespacing + // Flatten all moduleName keys to empty string + const flattened = { '': {} }; + for (const moduleName in result) { + for (const nlsKey in result[moduleName]) { + flattened[''][nlsKey] = result[moduleName][nlsKey]; + } + } + return flattened; } function parseVersion(version) { const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); @@ -801,18 +646,45 @@ async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageI } return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -async function parsePolicies() { - const parser = new tree_sitter_1.default(); - parser.setLanguage(typescript); - const files = await getFiles(process.cwd()); - const base = path_1.default.join(process.cwd(), 'src'); +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; +async function parsePolicies(policyDataFile) { + const contents = JSONC.parse(await fs_1.promises.readFile(policyDataFile, { encoding: 'utf8' })); + const categories = new Map(); + for (const category of contents.categories) { + categories.set(category.key, category); + } const policies = []; - for (const file of files) { - const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); + for (const policy of contents.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + let result; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + policies.push(result); } + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); return policies; } async function getTranslations() { @@ -860,8 +732,14 @@ async function darwinMain(policies, translations) { } } async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; + const args = (0, minimist_1.default)(process.argv.slice(2)); + if (args._.length !== 2) { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } + const policyDataFile = args._[0]; + const platform = args._[1]; + const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); if (platform === 'darwin') { await darwinMain(policies, translations); } @@ -869,19 +747,14 @@ async function main() { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } - else { - console.error(err); - } + console.error(err); process.exit(1); }); } -//# sourceMappingURL=policies.js.map \ No newline at end of file +//# sourceMappingURL=policyGenerator.js.map \ No newline at end of file diff --git a/build/lib/policies.ts b/build/lib/policies/policyGenerator.ts similarity index 69% rename from build/lib/policies.ts rename to build/lib/policies/policyGenerator.ts index 381d2f4c1ac..5fb06942f67 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies/policyGenerator.ts @@ -3,29 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { spawn } from 'child_process'; +import minimist from 'minimist'; import { promises as fs } from 'fs'; import path from 'path'; -import byline from 'byline'; -import { rgPath } from '@vscode/ripgrep'; -import Parser from 'tree-sitter'; -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); +import { CategoryDto, ExportedPolicyDataDto, PolicyDto } from './policyDto'; +import * as JSONC from 'jsonc-parser'; -type NlsString = { value: string; nlsKey: string }; - -function isNlsString(value: string | NlsString | undefined): value is NlsString { - return value ? typeof value !== 'string' : false; -} - -function isStringArray(value: (string | NlsString)[]): value is string[] { - return !value.some(s => isNlsString(s)); -} +const product = require('../../../product.json'); +const packageJson = require('../../../package.json'); -function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { - return value.every(s => isNlsString(s)); -} +type NlsString = { value: string; nlsKey: string }; interface Category { readonly moduleName: string; @@ -146,21 +133,14 @@ ${this.renderProfileManifestValue(translations)} class BooleanPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): BooleanPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { + const { name, minimumVersion, localization, type } = policy; if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -203,35 +183,20 @@ class BooleanPolicy extends BasePolicy { } } -class ParseError extends Error { - constructor(message: string, moduleName: string, node: Parser.SyntaxNode) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} - class NumberPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): NumberPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; if (type !== 'number') { return undefined; } - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); } - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); } private constructor( @@ -276,21 +241,14 @@ class NumberPolicy extends BasePolicy { class StringPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; if (type !== 'string') { return undefined; } - return new StringPolicy(name, category, minimumVersion, description, moduleName); + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -331,21 +289,14 @@ class StringPolicy extends BasePolicy { class ObjectPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): ObjectPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); } private constructor( @@ -387,39 +338,32 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringEnumPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); + static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; if (type !== 'string') { return undefined; } - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); + const enum_ = enumValue; if (!enum_) { return undefined; } - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); } - - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); - } else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy( + name, + { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, + minimumVersion, + { nlsKey: localization.description.key, value: localization.description.value }, + '', + enum_, + enumDescriptions + ); } private constructor( @@ -475,226 +419,6 @@ class StringEnumPolicy extends BasePolicy { } } -interface QType { - Q: string; - value(matches: Parser.QueryMatch[]): T | undefined; -} - -const NumberQ: QType = { - Q: `(number) @value`, - - value(matches: Parser.QueryMatch[]): number | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - return parseInt(value); - } -}; - -const StringQ: QType = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - - value(matches: Parser.QueryMatch[]): string | NlsString | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - - if (nlsKey) { - return { value, nlsKey }; - } else { - return value; - } - } -}; - -const StringArrayQ: QType<(string | NlsString)[]> = { - Q: `(array ${StringQ.Q})`, - - value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { - if (matches.length === 0) { - return undefined; - } - - return matches.map(match => { - return StringQ.value([match]) as string | NlsString; - }); - } -}; - -function getProperty(qtype: QType, moduleName: string, node: Parser.SyntaxNode, key: string): T | undefined { - const query = new Parser.Query( - typescript, - `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )` - ); - - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} - -function getNumberProperty(moduleName: string, node: Parser.SyntaxNode, key: string): number | undefined { - return getProperty(NumberQ, moduleName, node, key); -} - -function getStringProperty(moduleName: string, node: Parser.SyntaxNode, key: string): string | NlsString | undefined { - return getProperty(StringQ, moduleName, node, key); -} - -function getStringArrayProperty(moduleName: string, node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { - return getProperty(StringArrayQ, moduleName, node, key); -} - -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; - -function getPolicy( - moduleName: string, - configurationNode: Parser.SyntaxNode, - settingNode: Parser.SyntaxNode, - policyNode: Parser.SyntaxNode, - categories: Map -): Policy { - const name = getStringProperty(moduleName, policyNode, 'name'); - - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - - let result: Policy | undefined; - - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - - return result; -} - -function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { - const query = new Parser.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - - const categories = new Map(); - - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} - -async function getFiles(root: string): Promise { - return new Promise((c, e) => { - const result: string[] = []; - const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = byline(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} - function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { versions = versions.map(v => v.replace(/\./g, '_')); @@ -969,7 +693,7 @@ type Translations = { languageId: string; languageTranslations: LanguageTranslat type Version = [number, number, number]; -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -985,7 +709,17 @@ async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, v } const { contents: result } = await res.json() as { contents: LanguageTranslations }; - return result; + + // TODO: support module namespacing + // Flatten all moduleName keys to empty string + const flattened: LanguageTranslations = { '': {} }; + for (const moduleName in result) { + for (const nlsKey in result[moduleName]) { + flattened[''][nlsKey] = result[moduleName][nlsKey]; + } + } + + return flattened; } function parseVersion(version: string): Version { @@ -1034,21 +768,52 @@ async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: s return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -async function parsePolicies(): Promise { - const parser = new Parser(); - parser.setLanguage(typescript); +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; - const files = await getFiles(process.cwd()); - const base = path.join(process.cwd(), 'src'); - const policies = []; +async function parsePolicies(policyDataFile: string): Promise { + const contents = JSONC.parse(await fs.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto; + const categories = new Map(); + for (const category of contents.categories) { + categories.set(category.key, category); + } - for (const file of files) { - const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); + const policies: Policy[] = []; + for (const policy of contents.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + + policies.push(result); } + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + return policies; } @@ -1112,26 +877,29 @@ async function darwinMain(policies: Policy[], translations: Translations) { } async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; + const args = minimist(process.argv.slice(2)); + if (args._.length !== 2) { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } + + const policyDataFile = args._[0]; + const platform = args._[1]; + const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); if (platform === 'darwin') { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } else { - console.error(err); - } + console.error(err); process.exit(1); }); } diff --git a/build/package.json b/build/package.json index fbbf5264776..ef715fbfb6a 100644 --- a/build/package.json +++ b/build/package.json @@ -64,9 +64,12 @@ }, "type": "commonjs", "scripts": { - "compile": "cd .. && npx tsgo --project build/tsconfig.build.json", - "watch": "cd .. && npx tsgo --project build/tsconfig.build.json --watch", - "npmCheckJs": "cd .. && npx tsgo --project build/tsconfig.build.json --noEmit" + "copy-policy-dto": "node lib/policies/copyPolicyDto.js", + "prebuild-ts": "npm run copy-policy-dto", + "build-ts": "cd .. && npx tsgo --project build/tsconfig.build.json", + "compile": "npm run build-ts", + "watch": "npm run build-ts -- --watch", + "npmCheckJs": "npm run build-ts -- --noEmit" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 1e97392d5e2..7fa484a477e 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -3,9 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; export type PolicyName = string; +export type LocalizedValue = { + key: string; + value: string; +}; + +export enum PolicyCategory { + Extensions = 'Extensions', + IntegratedTerminal = 'IntegratedTerminal', + InteractiveSession = 'InteractiveSession', + Telemetry = 'Telemetry', + Update = 'Update', +} + +export const PolicyCategoryData: { + [key in PolicyCategory]: { name: LocalizedValue } +} = { + [PolicyCategory.Extensions]: { + name: { + key: 'extensionsConfigurationTitle', value: localize('extensionsConfigurationTitle', "Extensions"), + } + }, + [PolicyCategory.IntegratedTerminal]: { + name: { + key: 'terminalIntegratedConfigurationTitle', value: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + } + }, + [PolicyCategory.InteractiveSession]: { + name: { + key: 'interactiveSessionConfigurationTitle', value: localize('interactiveSessionConfigurationTitle', "Chat"), + } + }, + [PolicyCategory.Telemetry]: { + name: { + key: 'telemetryConfigurationTitle', value: localize('telemetryConfigurationTitle', "Telemetry"), + } + }, + [PolicyCategory.Update]: { + name: { + key: 'updateConfigurationTitle', value: localize('updateConfigurationTitle', "Update"), + } + } +}; export interface IPolicy { @@ -14,15 +57,27 @@ export interface IPolicy { */ readonly name: PolicyName; + /** + * The policy category. + */ + readonly category: PolicyCategory; + /** * The Code version in which this policy was introduced. */ readonly minimumVersion: `${number}.${number}`; /** - * The policy description (optional). + * Localization info for the policy. + * + * IMPORTANT: the key values for these must be unique to avoid collisions, as during the export time the module information is not available. */ - readonly description?: string; + readonly localization: { + /** The localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's description property. */ + description: LocalizedValue; + /** List of localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's enumDescriptions property. */ + enumDescriptions?: LocalizedValue[]; + }; /** * The value that an ACCOUNT-based feature will use when its corresponding policy is active. diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 4d089e71eb8..043f137b202 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -7,6 +7,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../common/configurationRegistry.js'; import { Registry } from '../../../registry/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationRegistry', () => { @@ -89,14 +90,18 @@ suite('ConfigurationRegistry', () => { 'type': 'object', policy: { name: 'policy', - minimumVersion: '1.0.0' + category: PolicyCategory.Extensions, + minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy2': { 'type': 'object', policy: { name: 'policy', - minimumVersion: '1.0.0' + category: PolicyCategory.Extensions, + minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 4e765d63efe..a258c16a082 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -20,6 +20,7 @@ import { NullLogService } from '../../../log/common/log.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { NullPolicyService } from '../../../policy/common/policy.js'; import { Registry } from '../../../registry/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationService.test.ts', () => { @@ -374,7 +375,9 @@ suite('ConfigurationService.test.ts', () => { 'default': 'isSet', policy: { name: 'configurationService.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index ac30e8b5050..5cc463e4ac0 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -19,6 +19,7 @@ import { IPolicyService } from '../../../policy/common/policy.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; suite('PolicyConfiguration', () => { @@ -39,7 +40,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.settingB': { @@ -47,7 +50,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.objectSetting': { @@ -55,7 +60,9 @@ suite('PolicyConfiguration', () => { 'default': {}, policy: { name: 'PolicyObjectSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.arraySetting': { @@ -63,7 +70,9 @@ suite('PolicyConfiguration', () => { 'default': [], policy: { name: 'PolicyArraySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.booleanSetting': { @@ -71,7 +80,9 @@ suite('PolicyConfiguration', () => { 'default': true, policy: { name: 'PolicyBooleanSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'policy.internalSetting': { @@ -80,7 +91,9 @@ suite('PolicyConfiguration', () => { included: false, policy: { name: 'PolicyInternalSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, } } }, 'nonPolicy.setting': { @@ -267,7 +280,9 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueC', policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' }, }, } }; Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index e7b08b8f887..a10f4c9b3bb 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -105,6 +105,7 @@ export interface NativeParsedArgs { 'skip-welcome'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; + 'export-policy-data'?: string; 'install-source'?: string; 'add-mcp'?: string[]; 'disable-updates'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index c5f10d53040..4a14c835bf9 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -145,6 +145,7 @@ export interface INativeEnvironmentService extends IEnvironmentService { useInMemorySecretStorage?: boolean; crossOriginIsolated?: boolean; + exportPolicyData?: string; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 6b6c82c6d5d..535132f43a4 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -254,6 +254,10 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get editSessionId(): string | undefined { return this.args['editSessionId']; } + get exportPolicyData(): string | undefined { + return this.args['export-policy-data']; + } + get continueOn(): string | undefined { return this.args['continueOn']; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 9b0cf59752b..d0ab1d6b4e5 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -162,6 +162,7 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-sharedprocess': { type: 'string', allowEmptyValue: true }, 'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true }, 'export-default-configuration': { type: 'string' }, + 'export-policy-data': { type: 'string', allowEmptyValue: true }, 'install-source': { type: 'string' }, 'enable-smoke-test-driver': { type: 'boolean' }, 'logExtensionHostCommunication': { type: 'boolean' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index cd5c1945c5a..35fe4290937 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,6 +9,7 @@ import { Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IPager } from '../../../base/common/paging.js'; import { Platform } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { URI } from '../../../base/common/uri.js'; import { localize, localize2 } from '../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; @@ -734,8 +735,14 @@ Registry.as(Extensions.Configuration) scope: ConfigurationScope.APPLICATION, policy: { name: 'AllowedExtensions', + category: PolicyCategory.Extensions, minimumVersion: '1.96', - description: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), + localization: { + description: { + key: 'extensions.allowed.policy', + value: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), + } + } }, additionalProperties: false, patternProperties: { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index ef676bdc69b..e26d733e98e 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -6,6 +6,7 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; @@ -223,8 +224,32 @@ configurationRegistry.registerConfiguration({ 'tags': ['usesOnlineServices', 'telemetry'], 'policy': { name: 'TelemetryLevel', + category: PolicyCategory.Telemetry, minimumVersion: '1.99', - description: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), + localization: { + description: { + key: 'telemetry.telemetryLevel.policyDescription', + value: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), + }, + enumDescriptions: [ + { + key: 'telemetry.telemetryLevel.default', + value: localize('telemetry.telemetryLevel.default', "Sends usage data, errors, and crash reports."), + }, + { + key: 'telemetry.telemetryLevel.error', + value: localize('telemetry.telemetryLevel.error', "Sends general error telemetry and crash reports."), + }, + { + key: 'telemetry.telemetryLevel.crash', + value: localize('telemetry.telemetryLevel.crash', "Sends OS level crash reports."), + }, + { + key: 'telemetry.telemetryLevel.off', + value: localize('telemetry.telemetryLevel.off', "Disables all product telemetry."), + } + ] + } } }, 'telemetry.feedback.enabled': { @@ -233,7 +258,9 @@ configurationRegistry.registerConfiguration({ description: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options."), policy: { name: 'EnableFeedback', + category: PolicyCategory.Telemetry, minimumVersion: '1.99', + localization: { description: { key: 'telemetry.feedback.enabled', value: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.") } }, } }, // Deprecated telemetry setting diff --git a/src/vs/platform/update/common/update.config.contribution.ts b/src/vs/platform/update/common/update.config.contribution.ts index d96926b5578..e5fb1abc0b6 100644 --- a/src/vs/platform/update/common/update.config.contribution.ts +++ b/src/vs/platform/update/common/update.config.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWeb, isWindows } from '../../../base/common/platform.js'; +import { PolicyCategory } from '../../../base/common/policy.js'; import { localize } from '../../../nls.js'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; import { Registry } from '../../registry/common/platform.js'; @@ -30,7 +31,29 @@ configurationRegistry.registerConfiguration({ ], policy: { name: 'UpdateMode', + category: PolicyCategory.Update, minimumVersion: '1.67', + localization: { + description: { key: 'updateMode', value: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), }, + enumDescriptions: [ + { + key: 'none', + value: localize('none', "Disable updates."), + }, + { + key: 'manual', + value: localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), + }, + { + key: 'start', + value: localize('start', "Check for updates only on startup. Disable automatic background update checks."), + }, + { + key: 'default', + value: localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically."), + } + ] + }, } }, 'update.channel': { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 469ddcce680..151be32ce10 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -123,6 +123,7 @@ import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './p import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -240,18 +241,21 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.GlobalAutoApprove]: { default: false, - // HACK: Description duplicated for policy parser. See https://github.com/microsoft/vscode/issues/254526 - description: nls.localize('autoApprove2.description', - 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' - ), markdownDescription: globalAutoApproveDescription.value, type: 'boolean', scope: ConfigurationScope.APPLICATION_MACHINE, tags: ['experimental'], policy: { name: 'ChatToolsAutoApprove', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_preview_features_enabled === false ? false : undefined, + localization: { + description: { + key: 'autoApprove2.description', + value: nls.localize('autoApprove2.description', 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.') + } + }, } }, [ChatConfiguration.AutoApproveEdits]: { @@ -344,6 +348,7 @@ configurationRegistry.registerConfiguration({ default: McpAccessValue.All, policy: { name: 'ChatMCP', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => { if (account.mcp === false) { @@ -354,6 +359,23 @@ configurationRegistry.registerConfiguration({ } return undefined; }, + localization: { + description: { + key: 'chat.mcp.access', + value: nls.localize('chat.mcp.access', "Controls access to installed Model Context Protocol servers.") + }, + enumDescriptions: [ + { + key: 'chat.mcp.access.none', value: nls.localize('chat.mcp.access.none', "No access to MCP servers."), + }, + { + key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), + }, + { + key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") + } + ] + }, } }, [mcpAutoStartConfig]: { @@ -419,8 +441,14 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentExtensionTools', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - description: nls.localize('chat.extensionToolsPolicy', "Enable using tools contributed by third-party extensions."), + localization: { + description: { + key: 'chat.extensionToolsEnabled', + value: nls.localize('chat.extensionToolsEnabled', "Enable using tools contributed by third-party extensions.") + } + }, } }, [ChatConfiguration.AgentEnabled]: { @@ -429,8 +457,15 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentMode', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_agent_enabled === false ? false : undefined, + localization: { + description: { + key: 'chat.agent.enabled.description', + value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), + } + } } }, [ChatConfiguration.EnableMath]: { @@ -472,8 +507,15 @@ configurationRegistry.registerConfiguration({ included: false, policy: { name: 'McpGalleryServiceUrl', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.101', - value: (account) => account.mcpRegistryUrl + value: (account) => account.mcpRegistryUrl, + localization: { + description: { + key: 'mcp.gallery.serviceUrl', + value: nls.localize('mcp.gallery.serviceUrl', "Configure the MCP Gallery service URL to connect to"), + } + } }, }, [PromptsConfig.KEY]: { @@ -495,8 +537,14 @@ configurationRegistry.registerConfiguration({ tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], policy: { name: 'ChatPromptFiles', + category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - description: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") + localization: { + description: { + key: 'chat.promptFiles.policy', + value: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") + } + } } }, [PromptsConfig.INSTRUCTIONS_LOCATION_KEY]: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b78c1385ac9..012c4d398e5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,6 +13,7 @@ import { mnemonicButtonLabel } from '../../../../base/common/labels.js'; import { Disposable, DisposableStore, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isNative, isWeb } from '../../../../base/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { MultiCommand } from '../../../../editor/browser/editorExtensions.js'; import { CopyAction, CutAction, PasteAction } from '../../../../editor/contrib/clipboard/browser/clipboard.js'; @@ -286,7 +287,14 @@ Registry.as(ConfigurationExtensions.Configuration) included: false, policy: { name: 'ExtensionGalleryServiceUrl', + category: PolicyCategory.Extensions, minimumVersion: '1.99', + localization: { + description: { + key: 'extensions.gallery.serviceUrl', + value: localize('extensions.gallery.serviceUrl', "Configure the Marketplace service URL to connect to"), + } + } }, }, 'extensions.supportNodeGlobalNavigator': { diff --git a/src/vs/workbench/contrib/policyExport/common/policyDto.ts b/src/vs/workbench/contrib/policyExport/common/policyDto.ts new file mode 100644 index 00000000000..0408c74fc6e --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/common/policyDto.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type LocalizedValueDto = { + key: string; + value: string; +}; + +export interface CategoryDto { + key: string; + name: LocalizedValueDto; +} + +export interface PolicyDto { + key: string; + name: string; + category: string; + minimumVersion: `${number}.${number}`; + localization: { + description: LocalizedValueDto; + enumDescriptions?: LocalizedValueDto[]; + }; + type?: string | string[]; + default?: unknown; + enum?: string[]; +} + +export interface ExportedPolicyDataDto { + categories: CategoryDto[]; + policies: PolicyDto[]; +} diff --git a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts new file mode 100644 index 00000000000..51a0f8b42e1 --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { INativeEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { INativeHostService } from '../../../../platform/native/common/native.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { URI } from '../../../../base/common/uri.js'; +import { VSBuffer } from '../../../../base/common/buffer.js'; +import { PolicyCategory, PolicyCategoryData } from '../../../../base/common/policy.js'; +import { ExportedPolicyDataDto } from '../common/policyDto.js'; +import { join } from '../../../../base/common/path.js'; + +export class PolicyExportContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.policyExport'; + static readonly DEFAULT_POLICY_EXPORT_PATH = 'build/lib/policies/policyData.jsonc'; + + constructor( + @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, + @IExtensionService private readonly extensionService: IExtensionService, + @IFileService private readonly fileService: IFileService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService, + ) { + super(); + + // Skip for non-development flows + if (this.nativeEnvironmentService.isBuilt) { + return; + } + + const policyDataPath = this.nativeEnvironmentService.exportPolicyData; + if (policyDataPath !== undefined) { + const defaultPath = join(this.nativeEnvironmentService.appRoot, PolicyExportContribution.DEFAULT_POLICY_EXPORT_PATH); + void this.exportPolicyDataAndQuit(policyDataPath ? policyDataPath : defaultPath); + } + } + + private log(msg: string | undefined, ...args: unknown[]) { + this.logService.info(`[${PolicyExportContribution.ID}]`, msg, ...args); + } + + private async exportPolicyDataAndQuit(policyDataPath: string): Promise { + try { + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: `Exporting policy data to ${policyDataPath}` + }, async (_progress) => { + this.log('Export started. Waiting for configurations to load.'); + await this.extensionService.whenInstalledExtensionsRegistered(); + await this.configurationService.whenRemoteConfigurationLoaded(); + + this.log('Extensions and configuration loaded.'); + const configurationRegistry = Registry.as(Extensions.Configuration); + const configurationProperties = { + ...configurationRegistry.getExcludedConfigurationProperties(), + ...configurationRegistry.getConfigurationProperties(), + }; + + const policyData: ExportedPolicyDataDto = { + categories: Object.values(PolicyCategory).map(category => ({ + key: category, + name: PolicyCategoryData[category].name + })), + policies: [] + }; + + for (const [key, schema] of Object.entries(configurationProperties)) { + // Check for the localization property for now to remain backwards compatible. + if (schema.policy?.localization) { + policyData.policies.push({ + key, + name: schema.policy.name, + category: schema.policy.category, + minimumVersion: schema.policy.minimumVersion, + localization: { + description: schema.policy.localization.description, + enumDescriptions: schema.policy.localization.enumDescriptions, + }, + type: schema.type, + default: schema.default, + enum: schema.enum, + }); + } + } + this.log(`Discovered ${policyData.policies.length} policies to export.`); + + const disclaimerComment = `/** THIS FILE IS AUTOMATICALLY GENERATED USING \`code --export-policy-data\`. DO NOT MODIFY IT MANUALLY. **/`; + const policyDataFileContent = `${disclaimerComment}\n${JSON.stringify(policyData, null, 4)}\n`; + await this.fileService.writeFile(URI.file(policyDataPath), VSBuffer.fromString(policyDataFileContent)); + this.log(`Successfully exported ${policyData.policies.length} policies to ${policyDataPath}.`); + }); + + await this.nativeHostService.exit(0); + } catch (error) { + this.log('Failed to export policy', error); + await this.nativeHostService.exit(1); + } + } +} + +registerWorkbenchContribution2( + PolicyExportContribution.ID, + PolicyExportContribution, + WorkbenchPhase.Eventually, +); diff --git a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts new file mode 100644 index 00000000000..ddfa6885e01 --- /dev/null +++ b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as cp from 'child_process'; +import { promises as fs } from 'fs'; +import * as os from 'os'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { isWindows } from '../../../../../base/common/platform.js'; +import { dirname, join } from '../../../../../base/common/path.js'; +import { FileAccess } from '../../../../../base/common/network.js'; +import * as util from 'util'; + +const exec = util.promisify(cp.exec); + +suite('PolicyExport Integration Tests', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('exported policy data matches checked-in file', async function () { + // Skip this test in ADO pipelines + if (process.env['TF_BUILD']) { + this.skip(); + } + + // This test launches VS Code with --export-policy-data flag, so it takes longer + this.timeout(60000); + + // Get the repository root (FileAccess.asFileUri('') points to the 'out' directory) + const rootPath = dirname(FileAccess.asFileUri('').fsPath); + const checkedInFile = join(rootPath, 'build/lib/policies/policyData.jsonc'); + const tempFile = join(os.tmpdir(), `policyData-test-${Date.now()}.jsonc`); + + try { + // Launch VS Code with --export-policy-data flag + const scriptPath = isWindows + ? join(rootPath, 'scripts', 'code.bat') + : join(rootPath, 'scripts', 'code.sh'); + + await exec(`"${scriptPath}" --export-policy-data="${tempFile}"`, { + cwd: rootPath + }); + + // Read both files + const [exportedContent, checkedInContent] = await Promise.all([ + fs.readFile(tempFile, 'utf-8'), + fs.readFile(checkedInFile, 'utf-8') + ]); + + // Compare contents + assert.strictEqual( + exportedContent, + checkedInContent, + 'Exported policy data should match the checked-in file. If this fails, run: ./scripts/code.sh --export-policy-data' + ); + } finally { + // Clean up temp file + try { + await fs.unlink(tempFile); + } catch { + // Ignore cleanup errors + } + } + }); +}); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ebd5bd55d8d..76f7b87f762 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -7,6 +7,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import type { IStringDictionary } from '../../../../base/common/collections.js'; import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; import { localize } from '../../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import product from '../../../../platform/product/common/product.js'; @@ -645,7 +646,14 @@ export async function registerTerminalConfiguration(getFontSnippets: () => Promi default: true, policy: { name: 'ChatToolsTerminalEnableAutoApprove', + category: PolicyCategory.IntegratedTerminal, minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), + } + } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index fd6e51c680e..6cb93a3632f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -47,6 +47,7 @@ import { UserDataProfileService } from '../../../userDataProfile/common/userData import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js'; import { IBrowserWorkbenchEnvironmentService } from '../../../environment/browser/environmentService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -89,7 +90,9 @@ suite('ConfigurationEditing', () => { 'default': 'isSet', policy: { name: 'configurationEditing.service.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index ebbda208696..ab268311fd1 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -52,6 +52,7 @@ import { IUserDataProfileService } from '../../../userDataProfile/common/userDat import { TasksSchemaProperties } from '../../../../contrib/tasks/common/tasks.js'; import { RemoteSocketFactoryService } from '../../../../../platform/remote/common/remoteSocketFactoryService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -781,7 +782,9 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', policy: { name: 'configurationService.folder.policySetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'configurationService.folder.policyObjectSetting': { @@ -789,7 +792,9 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': {}, policy: { name: 'configurationService.folder.policyObjectSetting', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, } diff --git a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts index 1e7621472f5..795a4c567c8 100644 --- a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts @@ -12,6 +12,7 @@ import { Registry } from '../../../../../platform/registry/common/platform.js'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js'; import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -38,7 +39,9 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -46,7 +49,9 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -55,7 +60,9 @@ suite('AccountPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -64,7 +71,9 @@ suite('AccountPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts index 4484b9d01b2..cee9adcd499 100644 --- a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts @@ -19,6 +19,7 @@ import { InMemoryFileSystemProvider } from '../../../../../platform/files/common import { FileService } from '../../../../../platform/files/common/fileService.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -47,7 +48,9 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -55,7 +58,9 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -64,7 +69,9 @@ suite('MultiplexPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -73,7 +80,9 @@ suite('MultiplexPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', + category: PolicyCategory.Extensions, minimumVersion: '1.0.0', + localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index c1d9dc494ce..7a17b276d15 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -182,6 +182,9 @@ import './contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.js // MCP import './contrib/mcp/electron-browser/mcp.contribution.js'; +// Policy Export +import './contrib/policyExport/electron-browser/policyExport.contribution.js'; + //#endregion From 127f757368979ceeb975dba773e53131b9a0d907 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 21 Oct 2025 19:52:09 +0000 Subject: [PATCH 1419/4355] Support auto approval of tools in custom confirmation tool (#272532) --- src/vs/workbench/contrib/chat/common/tools/confirmationTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/tools/confirmationTool.ts b/src/vs/workbench/contrib/chat/common/tools/confirmationTool.ts index 2347b34df91..be0ea9ce410 100644 --- a/src/vs/workbench/contrib/chat/common/tools/confirmationTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/confirmationTool.ts @@ -78,7 +78,7 @@ export class ConfirmationTool implements IToolImpl { confirmationMessages: { title: parameters.title, message: new MarkdownString(parameters.message), - allowAutoConfirm: false + allowAutoConfirm: true }, toolSpecificData, presentation: ToolInvocationPresentation.HiddenAfterComplete From b8c5e29f9c1cf121e1c527937d3cf4ae1e1c4f22 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 21 Oct 2025 12:55:54 -0700 Subject: [PATCH 1420/4355] added pendingDeletes --- .../common/promptSyntax/service/promptsServiceImpl.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 915549de950..32d2b194a52 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -96,12 +96,17 @@ export class PromptsService extends Disposable implements IPromptsService { this.promptFileByCommandCache.clear(); } else { - // Clear cache for prompt files that match the changed URI + // Clear cache for prompt files that match the changed URI\ + const pendingDeletes: string[] = []; for (const [key, value] of this.promptFileByCommandCache) { if (isEqual(value.value?.uri, event.uri)) { - this.promptFileByCommandCache.delete(key); + pendingDeletes.push(key); } } + + for (const key of pendingDeletes) { + this.promptFileByCommandCache.delete(key); + } } this.onDidChangeParsedPromptFilesCacheEmitter.fire(); From 8c65bb17ebf7f8d359b3178ed6746d8ae7586407 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 21 Oct 2025 20:11:39 +0000 Subject: [PATCH 1421/4355] Custom tool for confirming terminal executions in other agents (#272523) * Custom tool for confirming terminal executions in other agents * dispose --- .../terminal.chatAgentTools.contribution.ts | 8 +- .../tools/runInTerminalConfirmationTool.ts | 79 +++++ .../browser/tools/runInTerminalTool.ts | 329 ++++++++++-------- .../test/browser/runInTerminalTool.test.ts | 9 +- 4 files changed, 272 insertions(+), 153 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts index deada5e6bc1..1baa65005ec 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts @@ -22,7 +22,8 @@ import { TerminalChatAgentToolsCommandId } from '../common/terminal.chatAgentToo import { GetTerminalLastCommandTool, GetTerminalLastCommandToolData } from './tools/getTerminalLastCommandTool.js'; import { GetTerminalOutputTool, GetTerminalOutputToolData } from './tools/getTerminalOutputTool.js'; import { GetTerminalSelectionTool, GetTerminalSelectionToolData } from './tools/getTerminalSelectionTool.js'; -import { RunInTerminalTool, createRunInTerminalToolData } from './tools/runInTerminalTool.js'; +import { ConfirmTerminalCommandTool, ConfirmTerminalCommandToolData } from './tools/runInTerminalConfirmationTool.js'; +import { RunInTerminalTool, RunInTerminalToolConfirmationHelper, createRunInTerminalToolData } from './tools/runInTerminalTool.js'; import { CreateAndRunTaskTool, CreateAndRunTaskToolData } from './tools/task/createAndRunTaskTool.js'; import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOutputTool.js'; import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js'; @@ -41,6 +42,9 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib // #region Terminal + const terminalConfirmationHelper = this._register(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); + const confirmTerminalCommandTool = instantiationService.createInstance(ConfirmTerminalCommandTool, terminalConfirmationHelper); + this._register(toolsService.registerTool(ConfirmTerminalCommandToolData, confirmTerminalCommandTool)); const getTerminalOutputTool = instantiationService.createInstance(GetTerminalOutputTool); this._register(toolsService.registerTool(GetTerminalOutputToolData, getTerminalOutputTool)); @@ -51,7 +55,7 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib runCommandsToolSet.addTool(GetTerminalOutputToolData); instantiationService.invokeFunction(createRunInTerminalToolData).then(runInTerminalToolData => { - const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool); + const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool, terminalConfirmationHelper); this._register(toolsService.registerTool(runInTerminalToolData, runInTerminalTool)); runCommandsToolSet.addTool(runInTerminalToolData); }); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts new file mode 100644 index 00000000000..172b5b30bdd --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { localize } from '../../../../../../nls.js'; +import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/languageModelToolsService.js'; +import { RunInTerminalToolConfirmationHelper } from './runInTerminalTool.js'; + +export const ConfirmTerminalCommandToolData: IToolData = { + id: 'vscode_get_terminal_confirmation', + displayName: localize('confirmTerminalCommandTool.displayName', 'Confirm Terminal Command'), + modelDescription: [ + 'This tool allows you to get explicit user confirmation for a terminal command without executing it.', + '', + 'When to use:', + '- When you need to verify user approval before executing a command', + '- When you want to show command details, auto-approval status, and simplified versions to the user', + '- When you need the user to review a potentially risky command', + '', + 'The tool will:', + '- Show the command with syntax highlighting', + '- Display auto-approval status if enabled', + '- Show simplified version of the command if applicable', + '- Provide custom actions for creating auto-approval rules', + '- Return approval/rejection status', + '', + 'After confirmation, use a tool to actually execute the command.' + ].join('\n'), + userDescription: localize('confirmTerminalCommandTool.userDescription', 'Tool for confirming terminal commands'), + source: ToolDataSource.Internal, + icon: Codicon.shield, + inputSchema: { + type: 'object', + properties: { + command: { + type: 'string', + description: 'The command to confirm with the user.' + }, + explanation: { + type: 'string', + description: 'A one-sentence description of what the command does. This will be shown to the user in the confirmation dialog.' + }, + isBackground: { + type: 'boolean', + description: 'Whether the command would start a background process. This provides context for the confirmation.' + }, + }, + required: [ + 'command', + 'explanation', + 'isBackground', + ] + } +}; + +export class ConfirmTerminalCommandTool implements IToolImpl { + constructor(private readonly _terminalCommandConfirmationHelper: RunInTerminalToolConfirmationHelper, + ) { } + async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { + const preparedInvocation = await this._terminalCommandConfirmationHelper.prepareToolInvocation(context, undefined, token); + if (preparedInvocation) { + preparedInvocation.presentation = ToolInvocationPresentation.HiddenAfterComplete; + } + return preparedInvocation; + } + + async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise { + // This is a confirmation-only tool - just return success + return { + content: [{ + kind: 'text', + value: 'yes' + }] + }; + } +} 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 72861ea8830..cbcca9e1355 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -258,7 +258,6 @@ const promptInjectionWarningCommandsLowerPwshOnly = [ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _terminalToolCreator: ToolTerminalCreator; - private readonly _commandSimplifier: CommandSimplifier; protected readonly _profileFetcher: TerminalProfileFetcher; private readonly _telemetry: RunInTerminalToolTelemetry; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; @@ -277,9 +276,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } constructor( + private readonly _confirmationHelper: RunInTerminalToolConfirmationHelper, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IStorageService private readonly _storageService: IStorageService, @ITerminalLogService private readonly _logService: ITerminalLogService, @ITerminalService private readonly _terminalService: ITerminalService, @@ -292,7 +291,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); this._terminalToolCreator = _instantiationService.createInstance(ToolTerminalCreator); - this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); @@ -323,151 +321,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { - const args = context.parameters as IRunInTerminalInputParams; - - const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); - const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; - - const os = await this._osBackend; - const shell = await this._profileFetcher.getCopilotShell(); - const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; - - const instance = context.chatSessionId ? this._sessionTerminalAssociations.get(context.chatSessionId)?.instance : undefined; - const terminalToolSessionId = generateUuid(); - - let toolEditedCommand: string | undefined = await this._commandSimplifier.rewriteIfNeeded(args, instance, shell); - if (toolEditedCommand === args.command) { - toolEditedCommand = undefined; - } - - let autoApproveInfo: IMarkdownString | undefined; - let confirmationMessages: IToolConfirmationMessages | undefined; - if (alternativeRecommendation) { - confirmationMessages = undefined; - } else { - // Determine auto approval, this happens even when auto approve is off to that reasoning - // can be reviewed in the terminal channel. It also allows gauging the effective set of - // commands that would be auto approved if it were enabled. - const actualCommand = toolEditedCommand ?? args.command; - const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); - const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); - const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); - const autoApproveReasons: string[] = [ - ...subCommandResults.map(e => e.reason), - commandLineResult.reason, - ]; - - let isAutoApproved = false; - let isDenied = false; - let autoApproveReason: 'subCommand' | 'commandLine' | undefined; - let autoApproveDefault: boolean | undefined; - - const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); - if (deniedSubCommandResult) { - this._logService.info('autoApprove: Sub-command DENIED auto approval'); - isDenied = true; - autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; - autoApproveReason = 'subCommand'; - } else if (commandLineResult.result === 'denied') { - this._logService.info('autoApprove: Command line DENIED auto approval'); - isDenied = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - autoApproveReason = 'commandLine'; - } else { - if (subCommandResults.every(e => e.result === 'approved')) { - this._logService.info('autoApprove: All sub-commands auto-approved'); - autoApproveReason = 'subCommand'; - isAutoApproved = true; - autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); - } else { - this._logService.info('autoApprove: All sub-commands NOT auto-approved'); - if (commandLineResult.result === 'approved') { - this._logService.info('autoApprove: Command line auto-approved'); - autoApproveReason = 'commandLine'; - isAutoApproved = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - } else { - this._logService.info('autoApprove: Command line NOT auto-approved'); - } - } - } - - // Log detailed auto approval reasoning - for (const reason of autoApproveReasons) { - this._logService.info(`- ${reason}`); - } - - // Apply auto approval or force it off depending on enablement/opt-in state - const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; - const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); - const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - if (isAutoApproveEnabled) { - autoApproveInfo = this._createAutoApproveInfo( - isAutoApproved, - isDenied, - autoApproveReason, - subCommandResults, - commandLineResult, - ); - } else { - isAutoApproved = false; - } - - // Send telemetry about auto approval process - this._telemetry.logPrepare({ - terminalToolSessionId, - subCommands, - autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', - autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', - autoApproveReason, - autoApproveDefault - }); - - // Add a disclaimer warning about prompt injection for common commands that return - // content from the web - let disclaimer: IMarkdownString | undefined; - const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); - if (!isAutoApproved && ( - subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || - (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) - )) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); - } - - let customActions: ToolConfirmationAction[] | undefined; - if (!isAutoApproved && isAutoApproveEnabled) { - customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); - } - - let shellType = basename(shell, '.exe'); - if (shellType === 'powershell') { - shellType = 'pwsh'; - } - confirmationMessages = (isAutoApproved && isAutoApproveAllowed) ? undefined : { - title: args.isBackground - ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) - : localize('runInTerminal', "Run `{0}` command?", shellType), - message: new MarkdownString(args.explanation), - disclaimer, - terminalCustomActions: customActions, - }; - } - - return { - confirmationMessages, - presentation, - toolSpecificData: { - kind: 'terminal', - terminalToolSessionId, - commandLine: { - original: args.command, - toolEdited: toolEditedCommand - }, - language, - alternativeRecommendation, - autoApproveInfo, - } - }; + const terminalInstance = context.chatSessionId ? this._sessionTerminalAssociations.get(context.chatSessionId)?.instance : undefined; + return this._confirmationHelper.prepareToolInvocation(context, terminalInstance, token); } async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { @@ -892,6 +747,184 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // #endregion +} + +export class RunInTerminalToolConfirmationHelper extends Disposable { + + private readonly _commandSimplifier: CommandSimplifier; + protected readonly _profileFetcher: TerminalProfileFetcher; + private readonly _telemetry: RunInTerminalToolTelemetry; + private readonly _commandLineAutoApprover: CommandLineAutoApprover; + + // Immutable window state + private readonly _osBackend: Promise; + + constructor( + @IInstantiationService _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, + @IStorageService private readonly _storageService: IStorageService, + @ITerminalLogService private readonly _logService: ITerminalLogService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + ) { + super(); + + this._osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); + + this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); + this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); + this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); + this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); + } + + async prepareToolInvocation(context: IToolInvocationPreparationContext, terminalInstance: Pick | undefined, token: CancellationToken): Promise { + const args = context.parameters as IRunInTerminalInputParams; + + const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); + const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; + + const os = await this._osBackend; + const shell = await this._profileFetcher.getCopilotShell(); + const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; + + const instance = terminalInstance; + const terminalToolSessionId = generateUuid(); + + let toolEditedCommand: string | undefined = await this._commandSimplifier.rewriteIfNeeded(args, instance, shell); + if (toolEditedCommand === args.command) { + toolEditedCommand = undefined; + } + + let autoApproveInfo: IMarkdownString | undefined; + let confirmationMessages: IToolConfirmationMessages | undefined; + if (alternativeRecommendation) { + confirmationMessages = undefined; + } else { + // Determine auto approval, this happens even when auto approve is off to that reasoning + // can be reviewed in the terminal channel. It also allows gauging the effective set of + // commands that would be auto approved if it were enabled. + const actualCommand = toolEditedCommand ?? args.command; + const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); + const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); + const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); + const autoApproveReasons: string[] = [ + ...subCommandResults.map(e => e.reason), + commandLineResult.reason, + ]; + + let isAutoApproved = false; + let isDenied = false; + let autoApproveReason: 'subCommand' | 'commandLine' | undefined; + let autoApproveDefault: boolean | undefined; + + const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); + if (deniedSubCommandResult) { + this._logService.info('autoApprove: Sub-command DENIED auto approval'); + isDenied = true; + autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; + autoApproveReason = 'subCommand'; + } else if (commandLineResult.result === 'denied') { + this._logService.info('autoApprove: Command line DENIED auto approval'); + isDenied = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + autoApproveReason = 'commandLine'; + } else { + if (subCommandResults.every(e => e.result === 'approved')) { + this._logService.info('autoApprove: All sub-commands auto-approved'); + autoApproveReason = 'subCommand'; + isAutoApproved = true; + autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); + } else { + this._logService.info('autoApprove: All sub-commands NOT auto-approved'); + if (commandLineResult.result === 'approved') { + this._logService.info('autoApprove: Command line auto-approved'); + autoApproveReason = 'commandLine'; + isAutoApproved = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + } else { + this._logService.info('autoApprove: Command line NOT auto-approved'); + } + } + } + + // Log detailed auto approval reasoning + for (const reason of autoApproveReasons) { + this._logService.info(`- ${reason}`); + } + + // Apply auto approval or force it off depending on enablement/opt-in state + const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; + const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); + const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; + if (isAutoApproveEnabled) { + autoApproveInfo = this._createAutoApproveInfo( + isAutoApproved, + isDenied, + autoApproveReason, + subCommandResults, + commandLineResult, + ); + } else { + isAutoApproved = false; + } + + // Send telemetry about auto approval process + this._telemetry.logPrepare({ + terminalToolSessionId, + subCommands, + autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', + autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', + autoApproveReason, + autoApproveDefault + }); + + // Add a disclaimer warning about prompt injection for common commands that return + // content from the web + let disclaimer: IMarkdownString | undefined; + const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); + if (!isAutoApproved && ( + subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || + (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) + )) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); + } + + let customActions: ToolConfirmationAction[] | undefined; + if (!isAutoApproved && isAutoApproveEnabled) { + customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); + } + + let shellType = basename(shell, '.exe'); + if (shellType === 'powershell') { + shellType = 'pwsh'; + } + confirmationMessages = (isAutoApproved && isAutoApproveAllowed) ? undefined : { + title: args.isBackground + ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) + : localize('runInTerminal', "Run `{0}` command?", shellType), + message: new MarkdownString(args.explanation), + disclaimer, + terminalCustomActions: customActions, + }; + } + + return { + confirmationMessages, + presentation, + toolSpecificData: { + kind: 'terminal', + terminalToolSessionId, + commandLine: { + original: args.command, + toolEdited: toolEditedCommand + }, + language, + alternativeRecommendation, + autoApproveInfo, + } + }; + } + // #region Auto approve diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index be45523bbe1..dd2832536e4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -17,7 +17,7 @@ import { IChatService, type IChatTerminalToolInvocationData } from '../../../../ import { ILanguageModelToolsService, IPreparedToolInvocation, IToolInvocationPreparationContext, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; -import { RunInTerminalTool, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; +import { RunInTerminalTool, RunInTerminalToolConfirmationHelper, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js'; import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; @@ -75,7 +75,9 @@ suite('RunInTerminalTool', () => { storageService = instantiationService.get(IStorageService); storageService.store(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, true, StorageScope.APPLICATION, StorageTarget.USER); - runInTerminalTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); + const confirmTerminalTool = store.add(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); + + runInTerminalTool = store.add(instantiationService.createInstance(TestRunInTerminalTool, confirmTerminalTool)); }); function setAutoApprove(value: { [key: string]: { approve: boolean; matchCommandLine?: boolean } | boolean }) { @@ -990,7 +992,8 @@ suite('TerminalProfileFetcher', () => { getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) }); - testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); + const confirmTerminalTool = store.add(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); + testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool, confirmTerminalTool)); }); function setConfig(key: string, value: unknown) { From 04c143a191cd3d07aa1cb7363c75ce7d9a4eaa84 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 22:16:19 +0200 Subject: [PATCH 1422/4355] extract _switchToAgentByName, use for handoffs (#272544) * extract _switchToAgentByName, use for handoffs * fix typo --- .../contrib/chat/browser/chatWidget.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 42db37ebcf9..7a26a2e16ae 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -63,7 +63,7 @@ import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; import { IChatModel, IChatResponseModel } from '../common/chatModel.js'; -import { IChatModeService } from '../common/chatModes.js'; +import { ChatMode, IChatModeService } from '../common/chatModes.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../common/chatParserTypes.js'; import { ChatRequestParser } from '../common/chatRequestParser.js'; import { IChatLocationData, IChatSendRequestOptions, IChatService } from '../common/chatService.js'; @@ -1760,7 +1760,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Switch to the specified agent/mode if provided if (handoff.agent) { - this.input.setChatMode(handoff.agent); + this._switchToAgentByName(handoff.agent); } // Insert the handoff prompt into the input this.input.setValue(handoff.prompt, false); @@ -2961,21 +2961,16 @@ export class ChatWidget extends Disposable implements IChatWidget { this.agentInInput.set(!!currentAgent); } - private async _applyPromptMetadata({ agent: mode, tools, model }: PromptHeader, requestInput: IChatRequestInputOptions): Promise { - - const currentMode = this.input.currentModeObs.get(); - - if (tools !== undefined && !mode && currentMode.kind !== ChatModeKind.Agent) { - mode = ChatModeKind.Agent; - } + private async _switchToAgentByName(agentName: string): Promise { + const currentAgent = this.input.currentModeObs.get(); // switch to appropriate agent if needed - if (mode && mode !== currentMode.name) { + if (agentName !== currentAgent.name) { // Find the mode object to get its kind - const chatMode = this.chatModeService.findModeByName(mode); - if (chatMode) { - if (currentMode.kind !== chatMode.kind) { - const chatModeCheck = await this.instantiationService.invokeFunction(handleModeSwitch, currentMode.kind, chatMode.kind, this.viewModel?.model.getRequests().length ?? 0, this.viewModel?.model.editingSession); + const agent = this.chatModeService.findModeByName(agentName); + if (agent) { + if (currentAgent.kind !== agent.kind) { + const chatModeCheck = await this.instantiationService.invokeFunction(handleModeSwitch, currentAgent.kind, agent.kind, this.viewModel?.model.getRequests().length ?? 0, this.viewModel?.model.editingSession); if (!chatModeCheck) { return undefined; } else if (chatModeCheck.needToClearSession) { @@ -2983,9 +2978,20 @@ export class ChatWidget extends Disposable implements IChatWidget { await this.waitForReady(); } } - this.input.setChatMode(chatMode.id); + this.input.setChatMode(agent.id); } } + } + + private async _applyPromptMetadata({ agent, tools, model }: PromptHeader, requestInput: IChatRequestInputOptions): Promise { + + if (tools !== undefined && !agent && this.input.currentModeKind !== ChatModeKind.Agent) { + agent = ChatMode.Agent.name; + } + // switch to appropriate agent if needed + if (agent) { + this._switchToAgentByName(agent); + } // if not tools to enable are present, we are done if (tools !== undefined && this.input.currentModeKind === ChatModeKind.Agent) { From 45f0c0e4be22cc513b786fe2f7b4b0dce396a6b5 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 13:29:33 -0700 Subject: [PATCH 1423/4355] Fix CI/CD: Expected validation messages wording --- .../chat/test/browser/promptSytntax/promptValidator.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index ace1729a814..c6b0d403231 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -192,7 +192,7 @@ suite('PromptValidator', () => { const markers = await validate(content, PromptsType.agent); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); - assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in agent files.`)); + assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in custom agent files.`)); }); test('tools with invalid handoffs', async () => { @@ -413,7 +413,7 @@ suite('PromptValidator', () => { const markers = await validate(content, PromptsType.prompt); assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); - assert.strictEqual(markers[0].message, `The 'tools' attribute is only supported in agent mode. Attribute will be ignored.`); + assert.strictEqual(markers[0].message, `The 'tools' attribute is only supported when using agents. Attribute will be ignored.`); }); }); From 0fbe5c26f1afa1863b0ab46013501ea08818ee4d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 21 Oct 2025 22:42:51 +0200 Subject: [PATCH 1424/4355] chatMode.name is case sensitive, expect when used in the URL handler (#272549) --- .../contrib/chat/browser/chatSetup.ts | 23 +++++++++---------- .../contrib/chat/common/chatModes.ts | 3 +-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index ccb5e3e6a4e..971c9b920c4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -1268,21 +1268,20 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const params = new URLSearchParams(url.query); this.telemetryService.publicLog2('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined }); - const modeParam = params.get('agent') ?? params.get('mode'); - let modeToUse: ChatModeKind | string | undefined; - if (modeParam) { - // check if the given param is a valid mode ID - let foundMode = this.chatModeService.findModeById(modeParam); - if (!foundMode) { - // if not, check if the given param is a valid mode name, note the name is case insensitive - foundMode = this.chatModeService.findModeByName(modeParam); - } + const agentParam = params.get('agent') ?? params.get('mode'); + if (agentParam) { + const agents = this.chatModeService.getModes(); + const allAgents = [...agents.builtin, ...agents.custom]; - if (foundMode) { - modeToUse = foundMode.id; + // check if the given param is a valid mode ID + let foundAgent = allAgents.find(agent => agent.id === agentParam); + if (!foundAgent) { + // if not, check if the given param is a valid mode name, note the parameter as name is case insensitive + const nameLower = agentParam.toLowerCase(); + foundAgent = allAgents.find(agent => agent.name.toLowerCase() === nameLower); } // execute the command to change the mode in panel, note that the command only supports mode IDs, not names - await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, modeToUse); + await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, foundAgent?.id); return true; } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 7a439d54857..ba23fc77fec 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -176,8 +176,7 @@ export class ChatModeService extends Disposable implements IChatModeService { } findModeByName(name: string): IChatMode | undefined { - const lowerCasedName = name.toLowerCase(); - return this.getBuiltinModes().find(mode => mode.name.toLowerCase() === lowerCasedName) ?? this.getCustomModes().find(mode => mode.name.toLowerCase() === lowerCasedName); + return this.getBuiltinModes().find(mode => mode.name === name) ?? this.getCustomModes().find(mode => mode.name === name); } private getBuiltinModes(): IChatMode[] { From 67a46682309034c8a420105d9aff491f32e9bdd4 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 21 Oct 2025 13:59:40 -0700 Subject: [PATCH 1425/4355] Show exception widget in all editors for same file; only scroll in active editor --- .../debug/browser/debugEditorContribution.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index d2d5051ca28..80643109672 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -587,18 +587,19 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (this.exceptionWidget && !sameUri) { this.closeExceptionWidget(); } else if (sameUri) { - // Only show exception widget in the active editor to prevent disrupting workflow in multiple editor groups with the same file + // Show exception widget in all editors with the same file, but only scroll in the active editor const activeControl = this.editorService.activeTextEditorControl; const isActiveEditor = activeControl === this.editor; + const exceptionInfo = await focusedSf.thread.exceptionInfo; - if (isActiveEditor) { - const exceptionInfo = await focusedSf.thread.exceptionInfo; - if (exceptionInfo) { + if (exceptionInfo) { + if (isActiveEditor) { + // Active editor: show widget and scroll to it this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); + } else { + // Inactive editor: show widget without scrolling + this.showExceptionWidgetWithoutScroll(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); } - } else { - // For non-active editors, close any existing exception widget - this.closeExceptionWidget(); } } } @@ -620,6 +621,16 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.exceptionWidgetVisible.set(true); } + private showExceptionWidgetWithoutScroll(exceptionInfo: IExceptionInfo, debugSession: IDebugSession | undefined, lineNumber: number, column: number): void { + if (this.exceptionWidget) { + this.exceptionWidget.dispose(); + } + + this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession); + this.exceptionWidget.show({ lineNumber, column }, 0); + this.exceptionWidgetVisible.set(true); + } + closeExceptionWidget(): void { if (this.exceptionWidget) { const shouldFocusEditor = this.exceptionWidget.hasFocus(); From fe76bb3c956ad13be251044e5eeb7b692da2827b Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 14:08:04 -0700 Subject: [PATCH 1426/4355] Fix CI/CD --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 60b3527e8b9..7b6b92c6d2e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -463,7 +463,7 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.enabled.description', - value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), + value: nls.localize('chat.agent.enabled.description', "Enable agents for chat. When this is enabled, agents can be activated via the dropdown in the view."), } } } From 3a76457e5656a2c675caab6c1df7986b849ef9a4 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 14:22:46 -0700 Subject: [PATCH 1427/4355] Fix CI/CD --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7b6b92c6d2e..dddf9c72e88 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -453,7 +453,7 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.AgentEnabled]: { type: 'boolean', - description: nls.localize('chat.agent.enabled.description', "Enable agents for chat. When this is enabled, agents can be activated via the dropdown in the view."), + description: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), default: true, policy: { name: 'ChatAgentMode', @@ -463,7 +463,7 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.enabled.description', - value: nls.localize('chat.agent.enabled.description', "Enable agents for chat. When this is enabled, agents can be activated via the dropdown in the view."), + value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), } } } From c53994bfc4b0a898aa5117adb34cf0a2a69dd5fe Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 21 Oct 2025 23:25:52 +0200 Subject: [PATCH 1428/4355] SCM - fix repository name text overflow (#272547) --- .../contrib/scm/browser/media/scm.css | 8 ++------ .../scm/browser/scmRepositoryRenderer.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index b19eee0339e..ec19d55ee61 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -45,8 +45,8 @@ display: none; } -.scm-view .scm-provider .monaco-icon-label-container { - display: flex; +.scm-view .scm-provider > .icon { + padding-right: 2px; } .scm-view .scm-provider > .monaco-icon-label { @@ -493,10 +493,6 @@ font-weight: normal; } -.scm-view.scm-repositories-view .scm-provider .monaco-icon-label-container { - display: flex; -} - /* History item hover */ .monaco-hover.history-item-hover p:first-child { diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 92606458284..400a449eaa1 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -51,6 +51,7 @@ export class RepositoryActionRunner extends ActionRunner { } interface RepositoryTemplate { + readonly icon: HTMLElement; readonly label: IconLabel; readonly countContainer: HTMLElement; readonly count: CountBadge; @@ -83,7 +84,8 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate): void { const repository = isSCMRepository(arg) ? arg : arg.element; - const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) - ? repository.provider.iconPath.id - : undefined; - - const label = icon - ? `$(${icon}) ${repository.provider.name}` - : repository.provider.name; + if (ThemeIcon.isThemeIcon(repository.provider.iconPath)) { + templateData.icon.classList.add(...ThemeIcon.asClassNameArray(repository.provider.iconPath)); + } if (repository.provider.rootUri) { - templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); + templateData.label.setLabel(repository.provider.name, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); } else { - templateData.label.setLabel(label, undefined, { title: repository.provider.label }); + templateData.label.setLabel(repository.provider.name, undefined, { title: repository.provider.label }); } let statusPrimaryActions: IAction[] = []; From 0638ad0a8bccd0e2fefa2362c530327c595b855e Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 14:51:46 -0700 Subject: [PATCH 1429/4355] Clip line and column numbers to valid ranges in Go To quick access --- .../browser/gotoLineQuickAccess.ts | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 972420ad578..a5fb23f6370 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -137,35 +137,41 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor } private parsePosition(editor: IEditor, value: string): IPosition { + const model = this.getModel(editor); // Support :: notation to navigate to a specific offset in the model. if (value.startsWith(':')) { let offset = parseInt(value.substring(1), 10); - if (!isNaN(offset)) { - const model = this.getModel(editor); - if (model) { - const reverse = offset < 0; - if (!this.useZeroBasedOffset.value) { - // Convert 1-based offset to model's 0-based. - offset -= Math.sign(offset); - } - if (reverse) { - // Offset from the end of the buffer - offset += model.getValueLength(); - } - return model.getPositionAt(offset); + if (!isNaN(offset) && model) { + const reverse = offset < 0; + if (!this.useZeroBasedOffset.value) { + // Convert 1-based offset to model's 0-based. + offset -= Math.sign(offset); + } + if (reverse) { + // Offset from the end of the buffer + offset += model.getValueLength(); } + return model.getPositionAt(offset); } } // Support line-col formats of `line,col`, `line:col`, `line#col` - const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - const endLine = this.lineCount(editor) + 1; + let [lineNumber, column] = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); + + // Handle negative line numbers and clip to valid range. + const maxLine = model?.getLineCount() ?? 0 + 1; + lineNumber = lineNumber >= 0 ? lineNumber : maxLine + lineNumber; + lineNumber = Math.min(Math.max(1, lineNumber), maxLine); + + // Handle negative column numbers and clip to valid range. + if (column !== undefined && model) { + const maxColumn = model.getLineMaxColumn(lineNumber); + column = column >= 0 ? column : maxColumn + column; + column = Math.min(Math.max(1, column), maxColumn); + } - return { - lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0], - column: numbers[1] - }; + return { lineNumber, column }; } private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined, inOffsetMode: boolean): string { From 059c13074c795d9a190a02b3408fd49019825bba Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 14:55:25 -0700 Subject: [PATCH 1430/4355] Operator fix --- .../editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index a5fb23f6370..df06ae8381f 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -160,7 +160,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor let [lineNumber, column] = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); // Handle negative line numbers and clip to valid range. - const maxLine = model?.getLineCount() ?? 0 + 1; + const maxLine = (model?.getLineCount() ?? 0) + 1; lineNumber = lineNumber >= 0 ? lineNumber : maxLine + lineNumber; lineNumber = Math.min(Math.max(1, lineNumber), maxLine); From cddaca1d679a81698ffb84d092e1ade2e97bb6d6 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 21 Oct 2025 15:24:42 -0700 Subject: [PATCH 1431/4355] Add exceptionWidget for inactive editors --- .../debug/browser/debugEditorContribution.ts | 33 +++++++++++++++++-- .../contrib/debug/browser/exceptionWidget.ts | 10 ++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 80643109672..005700db9ec 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -267,6 +267,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { private readonly displayedStore = new DisposableStore(); private editorHoverOptions: IEditorHoverOptions | undefined; private readonly debounceInfo: IFeatureDebounceInformation; + private allowScrollToExceptionWidget = true; + private shouldScrollToExceptionWidget = () => this.allowScrollToExceptionWidget; // Holds a Disposable that prevents the default editor hover behavior while it exists. private readonly defaultHoverLockout = new MutableDisposable(); @@ -609,7 +611,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.exceptionWidget.dispose(); } - this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession); + this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession, this.shouldScrollToExceptionWidget); this.exceptionWidget.show({ lineNumber, column }, 0); this.exceptionWidget.focus(); this.editor.revealRangeInCenter({ @@ -626,9 +628,36 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.exceptionWidget.dispose(); } - this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession); + // Disable scrolling to exception widget + this.allowScrollToExceptionWidget = false; + + const currentScrollTop = this.editor.getScrollTop(); + const visibleRanges = this.editor.getVisibleRanges(); + const firstVisibleLine = visibleRanges.length > 0 ? visibleRanges[0].startLineNumber : 1; + + // Create widget - this may add a zone that pushes content down + this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession, this.shouldScrollToExceptionWidget); this.exceptionWidget.show({ lineNumber, column }, 0); this.exceptionWidgetVisible.set(true); + + // only adjust scroll if the exception widget is above the first visible line + if (lineNumber < firstVisibleLine) { + // Get the actual height of the widget that was just added + // by checking the view zones - find the zone we just added + const whitespaces = this.editor.getWhitespaces(); + let scrollAdjustment = 0; + if (whitespaces.length > 0) { + // The most recently added whitespace is our widget + const lastWhitespace = whitespaces[whitespaces.length - 1]; + scrollAdjustment = lastWhitespace.height; + } + + // Scroll down by the actual widget height to keep the first visible line the same + this.editor.setScrollTop(currentScrollTop + scrollAdjustment, ScrollType.Immediate); + } + + // Re-enable scrolling to exception widget + this.allowScrollToExceptionWidget = true; } closeExceptionWidget(): void { diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 35479f2cef7..c408d6dbb10 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -20,6 +20,7 @@ import { EditorOption } from '../../../../editor/common/config/editorOptions.js' import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { Action } from '../../../../base/common/actions.js'; import { widgetClose } from '../../../../platform/theme/common/iconRegistry.js'; +import { Range } from '../../../../editor/common/core/range.js'; const $ = dom.$; // theming @@ -35,6 +36,7 @@ export class ExceptionWidget extends ZoneWidget { editor: ICodeEditor, private exceptionInfo: IExceptionInfo, private debugSession: IDebugSession | undefined, + private readonly shouldScroll: () => boolean, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -117,6 +119,14 @@ export class ExceptionWidget extends ZoneWidget { this._relayout(computedLinesNumber); } + protected override revealRange(range: Range, isLastLine: boolean): void { + // Only reveal/scroll if this widget should scroll + // For inactive editors, skip the reveal to prevent scrolling + if (this.shouldScroll()) { + super.revealRange(range, isLastLine); + } + } + focus(): void { // Focus into the container for accessibility purposes so the exception and stack trace gets read this.container?.focus(); From e156dad048af5e4f455d922310317942be3f22f3 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 15:34:04 -0700 Subject: [PATCH 1432/4355] Add some unit-tests. --- .../test/browser/gotoLineQuickAccess.test.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts diff --git a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts new file mode 100644 index 00000000000..2b9019a48a2 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { Event } from '../../../../../base/common/event.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IEditor } from '../../../../common/editorCommon.js'; +import { withTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; +import { AbstractGotoLineQuickAccessProvider } from '../../browser/gotoLineQuickAccess.js'; + +class TestGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { + protected override onDidActiveTextEditorControlChange = Event.None; + protected override activeTextEditorControl: IEditor | undefined; + constructor(useZeroBasedOffset?: { value: boolean }) { + super(useZeroBasedOffset); + } +} + +suite('AbstractGotoLineQuickAccessProvider', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function runTest(input: string, expectedLine: number, expectedColumn?: number, zeroBased = false) { + const provider = new TestGotoLineQuickAccessProvider({ value: zeroBased }); + withTestCodeEditor([ + 'line 1', + 'line 2', + 'line 3', + 'line 4', + 'line 5' + ], {}, (editor, _) => { + // eslint-disable-next-line local/code-no-any-casts + const { lineNumber, column } = (provider as any).parsePosition(editor, input); + assert.strictEqual(lineNumber, expectedLine); + assert.strictEqual(column, expectedColumn); + }); + } + + test('parsePosition works as expected', () => { + // :line + runTest('-100', 1); + runTest('-5', 1); + runTest('-1', 5); + runTest('0', 1); + runTest('1', 1); + runTest('2', 2); + runTest('5', 5); + runTest('6', 6); + runTest('7', 6); + runTest('100', 6); + + // :line,column + runTest('2:-100', 2, 1); + runTest('2:-5', 2, 2); + runTest('2:-1', 2, 6); + runTest('2:0', 2, 1); + runTest('2:1', 2, 1); + runTest('2:2', 2, 2); + runTest('2:6', 2, 6); + runTest('2:7', 2, 7); + runTest('2:8', 2, 7); + runTest('2:100', 2, 7); + + // ::offset (1-based) + runTest(':-1000', 1, 1); + runTest(':-10', 4, 5); + runTest(':-1', 5, 7); + runTest(':0', 1, 1); + runTest(':1', 1, 1); + runTest(':10', 2, 3); + runTest(':1000', 5, 7); + + // offset (0-based) + runTest(':-1000', 1, 1, true); + runTest(':-10', 4, 4, true); + runTest(':-1', 5, 6, true); + runTest(':0', 1, 1, true); + runTest(':1', 1, 2, true); + runTest(':10', 2, 4, true); + runTest(':1000', 5, 7, true); + + // :line#column + // :line,column + // spaces + runTest('-1#6', 5, 6); + runTest('2,4', 2, 4); + runTest(' 2 : 3 ', 2, 3); + }); +}); From 73095df2b88b5b0a522bf2656e83959a52215493 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 16:08:34 -0700 Subject: [PATCH 1433/4355] Add hint text --- .../contrib/quickAccess/browser/gotoLineQuickAccess.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 22d4e9e76e1..a728cfb09f2 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -71,7 +71,10 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor picker.items = [{ lineNumber: position.lineNumber, column: position.column, - label + label, + detail: inputText.length ? + undefined : // Don't show hint once the user has started typing. + localize('gotoLineQuickAccessDescription', "Use :line[:column] or ::offset to go to a position. Negative values are counted from the end.") }]; // ARIA Label From a0f95f954864922d14842c342ac255f1ae0ba7e5 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:00:32 -0700 Subject: [PATCH 1434/4355] Add contribution point for chat session create menu This allows extension to contribute to a special split button for creating chat sessions. Currently this is shown in the title of each chat session view, but if we unify the sessions view we would likely move it to the panel title --- src/vs/platform/actions/common/actions.ts | 1 + .../chat/browser/chatSessions.contribution.ts | 54 +++++++++++++------ .../chatSessions/view/sessionsViewPane.ts | 4 ++ .../actions/common/menusExtensionPoint.ts | 8 +++ 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index d9377a3cb52..5ceff4add17 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -273,6 +273,7 @@ export class MenuId { static readonly ChatToolOutputResourceContext = new MenuId('ChatToolOutputResourceContext'); static readonly ChatMultiDiffContext = new MenuId('ChatMultiDiffContext'); static readonly ChatSessionsMenu = new MenuId('ChatSessionsMenu'); + static readonly ChatSessionsCreateSubMenu = new MenuId('ChatSessionsCreateSubMenu'); static readonly ChatConfirmationMenu = new MenuId('ChatConfirmationMenu'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 3ad521839e8..717a3dc9b54 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -11,7 +11,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize, localize2 } from '../../../../nls.js'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -222,6 +222,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IExtensionService private readonly _extensionService: IExtensionService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IMenuService private readonly _menuService: IMenuService, ) { super(); this._register(extensionPoint.setHandler(extensions => { @@ -368,22 +369,43 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } private _registerMenuItems(contribution: IChatSessionsExtensionPoint): IDisposable { - return MenuRegistry.appendMenuItem(MenuId.ViewTitle, { - command: { - id: `${NEW_CHAT_SESSION_ACTION_ID}.${contribution.type}`, - title: localize('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), + // If provider registers anything for the create submenu, let it fully control the creation + const contextKeyService = this._contextKeyService.createOverlay([ + ['chatSessionType', contribution.type] + ]); + + const menuActions = this._menuService.getMenuActions(MenuId.ChatSessionsCreateSubMenu, contextKeyService); + if (menuActions?.length) { + return MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + group: 'navigation', + title: localize('interactiveSession.chatSessionSubMenuTitle', "Create chat session"), icon: Codicon.plus, - source: { - id: contribution.extensionDescription.identifier.value, - title: contribution.extensionDescription.displayName || contribution.extensionDescription.name, - } - }, - group: 'navigation', - order: 1, - when: ContextKeyExpr.and( - ContextKeyExpr.equals('view', `${VIEWLET_ID}.${contribution.type}`) - ), - }); + order: 1, + when: ContextKeyExpr.and( + ContextKeyExpr.equals('view', `${VIEWLET_ID}.${contribution.type}`) + ), + submenu: MenuId.ChatSessionsCreateSubMenu, + isSplitButton: true + }); + } else { + // We control creation instead + return MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + command: { + id: `${NEW_CHAT_SESSION_ACTION_ID}.${contribution.type}`, + title: localize('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), + icon: Codicon.plus, + source: { + id: contribution.extensionDescription.identifier.value, + title: contribution.extensionDescription.displayName || contribution.extensionDescription.name, + } + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and( + ContextKeyExpr.equals('view', `${VIEWLET_ID}.${contribution.type}`) + ), + }); + } } private _registerCommands(contribution: IChatSessionsExtensionPoint): IDisposable { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index c7e0ac14a02..519b9574af9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -121,6 +121,10 @@ export class SessionsViewPane extends ViewPane { } } })); + + if (provider) { // TODO: Why can this be undefined? + this.scopedContextKeyService.createKey('chatSessionType', provider.chatSessionType); + } } override shouldShowWelcome(): boolean { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index d8ae0d506ff..f74a85a7d0d 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -451,12 +451,20 @@ const apiMenus: IAPIMenu[] = [ proposed: 'chatParticipantPrivate' }, { + // TODO: rename this to something like: `chatSessions/item/inline` key: 'chat/chatSessions', id: MenuId.ChatSessionsMenu, description: localize('menus.chatSessions', "The Chat Sessions menu."), supportsSubmenus: false, proposed: 'chatSessionsProvider' }, + { + key: 'chatSessions/newSession', + id: MenuId.ChatSessionsCreateSubMenu, + description: localize('menus.chatSessionsNewSession', "Menu for new chat sessions."), + supportsSubmenus: false, + proposed: 'chatSessionsProvider' + }, { key: 'chat/multiDiff/context', id: MenuId.ChatMultiDiffContext, From 0ea6a23376dd7d0d55fd97e77b1bb7b2b4a019db Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 21 Oct 2025 17:08:32 -0700 Subject: [PATCH 1435/4355] Modularize the policy generation script (#272332) --- build/lib/policies/basePolicy.js | 57 ++ build/lib/policies/basePolicy.ts | 62 ++ build/lib/policies/booleanPolicy.js | 49 ++ build/lib/policies/booleanPolicy.ts | 61 ++ build/lib/policies/numberPolicy.js | 53 ++ build/lib/policies/numberPolicy.ts | 65 ++ build/lib/policies/objectPolicy.js | 46 + build/lib/policies/objectPolicy.ts | 58 ++ build/lib/policies/policyGenerator.js | 562 +----------- build/lib/policies/policyGenerator.ts | 698 +-------------- build/lib/policies/render.js | 271 ++++++ build/lib/policies/render.ts | 295 +++++++ build/lib/policies/stringEnumPolicy.js | 71 ++ build/lib/policies/stringEnumPolicy.ts | 92 ++ build/lib/policies/stringPolicy.js | 45 + build/lib/policies/stringPolicy.ts | 57 ++ build/lib/policies/types.js | 27 + build/lib/policies/types.ts | 65 ++ build/lib/test/booleanPolicy.test.js | 120 +++ build/lib/test/booleanPolicy.test.ts | 158 ++++ .../com.visualstudio.code.oss.mobileconfig | 61 ++ .../en-us/com.visualstudio.code.oss.plist | 274 ++++++ .../fr-fr/com.visualstudio.code.oss.plist | 274 ++++++ .../test/fixtures/policies/win32/CodeOSS.admx | 136 +++ .../policies/win32/en-us/CodeOSS.adml | 71 ++ .../policies/win32/fr-fr/CodeOSS.adml | 71 ++ build/lib/test/numberPolicy.test.js | 119 +++ build/lib/test/numberPolicy.test.ts | 157 ++++ build/lib/test/objectPolicy.test.js | 118 +++ build/lib/test/objectPolicy.test.ts | 156 ++++ build/lib/test/policyConversion.test.js | 455 ++++++++++ build/lib/test/policyConversion.test.ts | 495 +++++++++++ build/lib/test/render.test.js | 683 +++++++++++++++ build/lib/test/render.test.ts | 827 ++++++++++++++++++ build/lib/test/stringEnumPolicy.test.js | 136 +++ build/lib/test/stringEnumPolicy.test.ts | 174 ++++ build/lib/test/stringPolicy.test.js | 119 +++ build/lib/test/stringPolicy.test.ts | 157 ++++ build/package.json | 3 +- 39 files changed, 6166 insertions(+), 1232 deletions(-) create mode 100644 build/lib/policies/basePolicy.js create mode 100644 build/lib/policies/basePolicy.ts create mode 100644 build/lib/policies/booleanPolicy.js create mode 100644 build/lib/policies/booleanPolicy.ts create mode 100644 build/lib/policies/numberPolicy.js create mode 100644 build/lib/policies/numberPolicy.ts create mode 100644 build/lib/policies/objectPolicy.js create mode 100644 build/lib/policies/objectPolicy.ts create mode 100644 build/lib/policies/render.js create mode 100644 build/lib/policies/render.ts create mode 100644 build/lib/policies/stringEnumPolicy.js create mode 100644 build/lib/policies/stringEnumPolicy.ts create mode 100644 build/lib/policies/stringPolicy.js create mode 100644 build/lib/policies/stringPolicy.ts create mode 100644 build/lib/policies/types.js create mode 100644 build/lib/policies/types.ts create mode 100644 build/lib/test/booleanPolicy.test.js create mode 100644 build/lib/test/booleanPolicy.test.ts create mode 100644 build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig create mode 100644 build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist create mode 100644 build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist create mode 100644 build/lib/test/fixtures/policies/win32/CodeOSS.admx create mode 100644 build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml create mode 100644 build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml create mode 100644 build/lib/test/numberPolicy.test.js create mode 100644 build/lib/test/numberPolicy.test.ts create mode 100644 build/lib/test/objectPolicy.test.js create mode 100644 build/lib/test/objectPolicy.test.ts create mode 100644 build/lib/test/policyConversion.test.js create mode 100644 build/lib/test/policyConversion.test.ts create mode 100644 build/lib/test/render.test.js create mode 100644 build/lib/test/render.test.ts create mode 100644 build/lib/test/stringEnumPolicy.test.js create mode 100644 build/lib/test/stringEnumPolicy.test.ts create mode 100644 build/lib/test/stringPolicy.test.js create mode 100644 build/lib/test/stringPolicy.test.ts diff --git a/build/lib/policies/basePolicy.js b/build/lib/policies/basePolicy.js new file mode 100644 index 00000000000..844b395a088 --- /dev/null +++ b/build/lib/policies/basePolicy.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BasePolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const render_1 = require("./render"); +class BasePolicy { + type; + name; + category; + minimumVersion; + description; + moduleName; + constructor(type, name, category, minimumVersion, description, moduleName) { + this.type = type; + this.name = name; + this.category = category; + this.minimumVersion = minimumVersion; + this.description = description; + this.moduleName = moduleName; + } + renderADMLString(nlsString, translations) { + return (0, render_1.renderADMLString)(this.name, this.moduleName, nlsString, translations); + } + renderADMX(regKey) { + return [ + ``, + ` `, + ` `, + ` `, + ...this.renderADMXElements(), + ` `, + `` + ]; + } + renderADMLStrings(translations) { + return [ + `${this.name}`, + this.renderADMLString(this.description, translations) + ]; + } + renderADMLPresentation() { + return `${this.renderADMLPresentationContents()}`; + } + renderProfile() { + return [`${this.name}`, this.renderProfileValue()]; + } + renderProfileManifest(translations) { + return ` +${this.renderProfileManifestValue(translations)} +`; + } +} +exports.BasePolicy = BasePolicy; +//# sourceMappingURL=basePolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/basePolicy.ts b/build/lib/policies/basePolicy.ts new file mode 100644 index 00000000000..064467f7485 --- /dev/null +++ b/build/lib/policies/basePolicy.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { renderADMLString } from './render'; +import { Category, LanguageTranslations, NlsString, Policy, PolicyType } from './types'; + +export abstract class BasePolicy implements Policy { + constructor( + readonly type: PolicyType, + readonly name: string, + readonly category: Category, + readonly minimumVersion: string, + protected description: NlsString, + protected moduleName: string, + ) { } + + protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { + return renderADMLString(this.name, this.moduleName, nlsString, translations); + } + + renderADMX(regKey: string) { + return [ + ``, + ` `, + ` `, + ` `, + ...this.renderADMXElements(), + ` `, + `` + ]; + } + + protected abstract renderADMXElements(): string[]; + + renderADMLStrings(translations?: LanguageTranslations) { + return [ + `${this.name}`, + this.renderADMLString(this.description, translations) + ]; + } + + renderADMLPresentation(): string { + return `${this.renderADMLPresentationContents()}`; + } + + protected abstract renderADMLPresentationContents(): string; + + renderProfile() { + return [`${this.name}`, this.renderProfileValue()]; + } + + renderProfileManifest(translations?: LanguageTranslations): string { + return ` +${this.renderProfileManifestValue(translations)} +`; + } + + abstract renderProfileValue(): string; + abstract renderProfileManifestValue(translations?: LanguageTranslations): string; +} diff --git a/build/lib/policies/booleanPolicy.js b/build/lib/policies/booleanPolicy.js new file mode 100644 index 00000000000..1ec18d003ec --- /dev/null +++ b/build/lib/policies/booleanPolicy.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BooleanPolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const basePolicy_1 = require("./basePolicy"); +const render_1 = require("./render"); +const types_1 = require("./types"); +class BooleanPolicy extends basePolicy_1.BasePolicy { + static from(category, policy) { + const { name, minimumVersion, localization, type } = policy; + if (type !== 'boolean') { + return undefined; + } + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + constructor(name, category, minimumVersion, description, moduleName) { + super(types_1.PolicyType.Boolean, name, category, minimumVersion, description, moduleName); + } + renderADMXElements() { + return [ + ``, + ` `, + `` + ]; + } + renderADMLPresentationContents() { + return `${this.name}`; + } + renderProfileValue() { + return ``; + } + renderProfileManifestValue(translations) { + return `pfm_default + +pfm_description +${(0, render_1.renderProfileString)(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +boolean`; + } +} +exports.BooleanPolicy = BooleanPolicy; +//# sourceMappingURL=booleanPolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/booleanPolicy.ts b/build/lib/policies/booleanPolicy.ts new file mode 100644 index 00000000000..b37bb1e1816 --- /dev/null +++ b/build/lib/policies/booleanPolicy.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy'; +import { CategoryDto, PolicyDto } from './policyDto'; +import { renderProfileString } from './render'; +import { Category, NlsString, PolicyType, LanguageTranslations } from './types'; + +export class BooleanPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { + const { name, minimumVersion, localization, type } = policy; + + if (type !== 'boolean') { + return undefined; + } + + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [ + ``, + ` `, + `` + ]; + } + + renderADMLPresentationContents() { + return `${this.name}`; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +boolean`; + } +} diff --git a/build/lib/policies/numberPolicy.js b/build/lib/policies/numberPolicy.js new file mode 100644 index 00000000000..4cdc03ef738 --- /dev/null +++ b/build/lib/policies/numberPolicy.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NumberPolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const basePolicy_1 = require("./basePolicy"); +const render_1 = require("./render"); +const types_1 = require("./types"); +class NumberPolicy extends basePolicy_1.BasePolicy { + defaultValue; + static from(category, policy) { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; + if (type !== 'number') { + return undefined; + } + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); + } + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + } + constructor(name, category, minimumVersion, description, moduleName, defaultValue) { + super(types_1.PolicyType.Number, name, category, minimumVersion, description, moduleName); + this.defaultValue = defaultValue; + } + renderADMXElements() { + return [ + `` + // `` + ]; + } + renderADMLPresentationContents() { + return `${this.name}`; + } + renderProfileValue() { + return `${this.defaultValue}`; + } + renderProfileManifestValue(translations) { + return `pfm_default +${this.defaultValue} +pfm_description +${(0, render_1.renderProfileString)(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +integer`; + } +} +exports.NumberPolicy = NumberPolicy; +//# sourceMappingURL=numberPolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/numberPolicy.ts b/build/lib/policies/numberPolicy.ts new file mode 100644 index 00000000000..9bbbbddc07e --- /dev/null +++ b/build/lib/policies/numberPolicy.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy'; +import { CategoryDto, PolicyDto } from './policyDto'; +import { renderProfileString } from './render'; +import { Category, NlsString, PolicyType, LanguageTranslations } from './types'; + +export class NumberPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; + + if (type !== 'number') { + return undefined; + } + + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); + } + + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + protected readonly defaultValue: number, + ) { + super(PolicyType.Number, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [ + `` + // `` + ]; + } + + renderADMLPresentationContents() { + return `${this.name}`; + } + + renderProfileValue() { + return `${this.defaultValue}`; + } + + renderProfileManifestValue(translations?: LanguageTranslations) { + return `pfm_default +${this.defaultValue} +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +integer`; + } +} diff --git a/build/lib/policies/objectPolicy.js b/build/lib/policies/objectPolicy.js new file mode 100644 index 00000000000..ab15ce9ec3e --- /dev/null +++ b/build/lib/policies/objectPolicy.js @@ -0,0 +1,46 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ObjectPolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const basePolicy_1 = require("./basePolicy"); +const render_1 = require("./render"); +const types_1 = require("./types"); +class ObjectPolicy extends basePolicy_1.BasePolicy { + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; + if (type !== 'object' && type !== 'array') { + return undefined; + } + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + constructor(name, category, minimumVersion, description, moduleName) { + super(types_1.PolicyType.Object, name, category, minimumVersion, description, moduleName); + } + renderADMXElements() { + return [``]; + } + renderADMLPresentationContents() { + return ``; + } + renderProfileValue() { + return ``; + } + renderProfileManifestValue(translations) { + return `pfm_default + +pfm_description +${(0, render_1.renderProfileString)(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +`; + } +} +exports.ObjectPolicy = ObjectPolicy; +//# sourceMappingURL=objectPolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/objectPolicy.ts b/build/lib/policies/objectPolicy.ts new file mode 100644 index 00000000000..8e555133f38 --- /dev/null +++ b/build/lib/policies/objectPolicy.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy'; +import { CategoryDto, PolicyDto } from './policyDto'; +import { renderProfileString } from './render'; +import { Category, NlsString, PolicyType, LanguageTranslations } from './types'; + +export class ObjectPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; + + if (type !== 'object' && type !== 'array') { + return undefined; + } + + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.Object, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [``]; + } + + renderADMLPresentationContents() { + return ``; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +`; + } +} diff --git a/build/lib/policies/policyGenerator.js b/build/lib/policies/policyGenerator.js index 7ddb6956c18..e9f97b49092 100644 --- a/build/lib/policies/policyGenerator.js +++ b/build/lib/policies/policyGenerator.js @@ -44,543 +44,15 @@ const minimist_1 = __importDefault(require("minimist")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const JSONC = __importStar(require("jsonc-parser")); +const booleanPolicy_1 = require("./booleanPolicy"); +const numberPolicy_1 = require("./numberPolicy"); +const objectPolicy_1 = require("./objectPolicy"); +const stringEnumPolicy_1 = require("./stringEnumPolicy"); +const stringPolicy_1 = require("./stringPolicy"); +const types_1 = require("./types"); +const render_1 = require("./render"); const product = require('../../../product.json'); const packageJson = require('../../../package.json'); -var PolicyType; -(function (PolicyType) { - PolicyType["Boolean"] = "boolean"; - PolicyType["Number"] = "number"; - PolicyType["Object"] = "object"; - PolicyType["String"] = "string"; - PolicyType["StringEnum"] = "stringEnum"; -})(PolicyType || (PolicyType = {})); -function renderADMLString(prefix, moduleName, nlsString, translations) { - let value; - if (translations) { - const moduleTranslations = translations[moduleName]; - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - if (!value) { - value = nlsString.value; - } - return `${value}`; -} -function renderProfileString(_prefix, moduleName, nlsString, translations) { - let value; - if (translations) { - const moduleTranslations = translations[moduleName]; - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - if (!value) { - value = nlsString.value; - } - return value; -} -class BasePolicy { - type; - name; - category; - minimumVersion; - description; - moduleName; - constructor(type, name, category, minimumVersion, description, moduleName) { - this.type = type; - this.name = name; - this.category = category; - this.minimumVersion = minimumVersion; - this.description = description; - this.moduleName = moduleName; - } - renderADMLString(nlsString, translations) { - return renderADMLString(this.name, this.moduleName, nlsString, translations); - } - renderADMX(regKey) { - return [ - ``, - ` `, - ` `, - ` `, - ...this.renderADMXElements(), - ` `, - `` - ]; - } - renderADMLStrings(translations) { - return [ - `${this.name}`, - this.renderADMLString(this.description, translations) - ]; - } - renderADMLPresentation() { - return `${this.renderADMLPresentationContents()}`; - } - renderProfile() { - return [`${this.name}`, this.renderProfileValue()]; - } - renderProfileManifest(translations) { - return ` -${this.renderProfileManifestValue(translations)} -`; - } -} -class BooleanPolicy extends BasePolicy { - static from(category, policy) { - const { name, minimumVersion, localization, type } = policy; - if (type !== 'boolean') { - return undefined; - } - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [ - ``, - ` `, - `` - ]; - } - renderADMLPresentationContents() { - return `${this.name}`; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -boolean`; - } -} -class NumberPolicy extends BasePolicy { - defaultValue; - static from(category, policy) { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; - if (type !== 'number') { - return undefined; - } - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); - } - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); - } - constructor(name, category, minimumVersion, description, moduleName, defaultValue) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - this.defaultValue = defaultValue; - } - renderADMXElements() { - return [ - `` - // `` - ]; - } - renderADMLPresentationContents() { - return `${this.name}`; - } - renderProfileValue() { - return `${this.defaultValue}`; - } - renderProfileManifestValue(translations) { - return `pfm_default -${this.defaultValue} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -integer`; - } -} -class StringPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; - if (type !== 'string') { - return undefined; - } - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.String, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [``]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string`; - } -} -class ObjectPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; - if (type !== 'object' && type !== 'array') { - return undefined; - } - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.Object, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [``]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -`; - } -} -class StringEnumPolicy extends BasePolicy { - enum_; - enumDescriptions; - static from(category, policy) { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; - if (type !== 'string') { - return undefined; - } - const enum_ = enumValue; - if (!enum_) { - return undefined; - } - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); - } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); - } - constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - this.enum_ = enum_; - this.enumDescriptions = enumDescriptions; - } - renderADMXElements() { - return [ - ``, - ...this.enum_.map((value, index) => ` ${value}`), - `` - ]; - } - renderADMLStrings(translations) { - return [ - ...super.renderADMLStrings(translations), - ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) - ]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return `${this.enum_[0]}`; - } - renderProfileManifestValue(translations) { - return `pfm_default -${this.enum_[0]} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -pfm_range_list - - ${this.enum_.map(e => `${e}`).join('\n ')} -`; - } -} -function renderADMX(regKey, versions, categories, policies) { - versions = versions.map(v => v.replace(/\./g, '_')); - return ` - - - - - - - - ${versions.map(v => ``).join(`\n `)} - - - - - ${categories.map(c => ``).join(`\n `)} - - - ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} - - -`; -} -function renderADML(appName, versions, categories, policies, translations) { - return ` - - - - - - ${appName} - ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} - ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} - ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} - - - ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} - - - -`; -} -function renderProfileManifest(appName, bundleIdentifier, _versions, _categories, policies, translations) { - const requiredPayloadFields = ` - - pfm_default - Configure ${appName} - pfm_name - PayloadDescription - pfm_title - Payload Description - pfm_type - string - - - pfm_default - ${appName} - pfm_name - PayloadDisplayName - pfm_require - always - pfm_title - Payload Display Name - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadIdentifier - pfm_require - always - pfm_title - Payload Identifier - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadType - pfm_require - always - pfm_title - Payload Type - pfm_type - string - - - pfm_default - - pfm_name - PayloadUUID - pfm_require - always - pfm_title - Payload UUID - pfm_type - string - - - pfm_default - 1 - pfm_name - PayloadVersion - pfm_range_list - - 1 - - pfm_require - always - pfm_title - Payload Version - pfm_type - integer - - - pfm_default - Microsoft - pfm_name - PayloadOrganization - pfm_title - Payload Organization - pfm_type - string - `; - const profileManifestSubkeys = policies.map(policy => { - return policy.renderProfileManifest(translations); - }).join(''); - return ` - - - - pfm_app_url - https://code.visualstudio.com/ - pfm_description - ${appName} Managed Settings - pfm_documentation_url - https://code.visualstudio.com/docs/setup/enterprise - pfm_domain - ${bundleIdentifier} - pfm_format_version - 1 - pfm_interaction - combined - pfm_last_modified - ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} - pfm_platforms - - macOS - - pfm_subkeys - - ${requiredPayloadFields} - ${profileManifestSubkeys} - - pfm_title - ${appName} - pfm_unique - - pfm_version - 1 - -`; -} -function renderMacOSPolicy(policies, translations) { - const appName = product.nameLong; - const bundleIdentifier = product.darwinBundleIdentifier; - const payloadUUID = product.darwinProfilePayloadUUID; - const UUID = product.darwinProfileUUID; - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...new Set(policies.map(p => p.category))]; - const policyEntries = policies.map(policy => policy.renderProfile()) - .flat() - .map(entry => `\t\t\t\t${entry}`) - .join('\n'); - return { - profile: ` - - - - PayloadContent - - - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier}.${UUID} - PayloadType - ${bundleIdentifier} - PayloadUUID - ${UUID} - PayloadVersion - 1 -${policyEntries} - - - PayloadDescription - This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier} - PayloadOrganization - Microsoft - PayloadType - Configuration - PayloadUUID - ${payloadUUID} - PayloadVersion - 1 - TargetDeviceType - 5 - -`, - manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) - ] - }; -} -function renderGP(policies, translations) { - const appName = product.nameLong; - const regKey = product.win32RegValueName; - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))]; - return { - admx: renderADMX(regKey, versions, categories, policies), - adml: [ - { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) - ] - }; -} -const Languages = { - 'fr': 'fr-fr', - 'it': 'it-it', - 'de': 'de-de', - 'es': 'es-es', - 'ru': 'ru-ru', - 'zh-hans': 'zh-cn', - 'zh-hant': 'zh-tw', - 'ja': 'ja-jp', - 'ko': 'ko-kr', - 'cs': 'cs-cz', - 'pt-br': 'pt-br', - 'tr': 'tr-tr', - 'pl': 'pl-pl', -}; async function getSpecificNLS(resourceUrlTemplate, languageId, version) { const resource = { publisher: 'ms-ceintl', @@ -648,11 +120,11 @@ async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageI } // TODO: add more policy types const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy + booleanPolicy_1.BooleanPolicy, + numberPolicy_1.NumberPolicy, + stringEnumPolicy_1.StringEnumPolicy, + stringPolicy_1.StringPolicy, + objectPolicy_1.ObjectPolicy ]; async function parsePolicies(policyDataFile) { const contents = JSONC.parse(await fs_1.promises.readFile(policyDataFile, { encoding: 'utf8' })); @@ -699,18 +171,18 @@ async function getTranslations() { return []; } const version = parseVersion(packageJson.version); - const languageIds = Object.keys(Languages); + const languageIds = Object.keys(types_1.Languages); return await Promise.all(languageIds.map(languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) .then(languageTranslations => ({ languageId, languageTranslations })))); } async function windowsMain(policies, translations) { const root = '.build/policies/win32'; - const { admx, adml } = await renderGP(policies, translations); + const { admx, adml } = (0, render_1.renderGP)(product, policies, translations); await fs_1.promises.rm(root, { recursive: true, force: true }); await fs_1.promises.mkdir(root, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); for (const { languageId, contents } of adml) { - const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); + const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : types_1.Languages[languageId]); await fs_1.promises.mkdir(languagePath, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); } @@ -721,12 +193,12 @@ async function darwinMain(policies, translations) { throw new Error(`Missing required product information.`); } const root = '.build/policies/darwin'; - const { profile, manifests } = await renderMacOSPolicy(policies, translations); + const { profile, manifests } = (0, render_1.renderMacOSPolicy)(product, policies, translations); await fs_1.promises.rm(root, { recursive: true, force: true }); await fs_1.promises.mkdir(root, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n')); for (const { languageId, contents } of manifests) { - const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); + const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : types_1.Languages[languageId]); await fs_1.promises.mkdir(languagePath, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); } diff --git a/build/lib/policies/policyGenerator.ts b/build/lib/policies/policyGenerator.ts index 5fb06942f67..4c5e1d616f7 100644 --- a/build/lib/policies/policyGenerator.ts +++ b/build/lib/policies/policyGenerator.ts @@ -6,693 +6,19 @@ import minimist from 'minimist'; import { promises as fs } from 'fs'; import path from 'path'; -import { CategoryDto, ExportedPolicyDataDto, PolicyDto } from './policyDto'; +import { CategoryDto, ExportedPolicyDataDto } from './policyDto'; import * as JSONC from 'jsonc-parser'; - -const product = require('../../../product.json'); +import { BooleanPolicy } from './booleanPolicy'; +import { NumberPolicy } from './numberPolicy'; +import { ObjectPolicy } from './objectPolicy'; +import { StringEnumPolicy } from './stringEnumPolicy'; +import { StringPolicy } from './stringPolicy'; +import { Version, LanguageTranslations, Policy, Translations, Languages, ProductJson } from './types'; +import { renderGP, renderMacOSPolicy } from './render'; + +const product = require('../../../product.json') as ProductJson; const packageJson = require('../../../package.json'); -type NlsString = { value: string; nlsKey: string }; - -interface Category { - readonly moduleName: string; - readonly name: NlsString; -} - -enum PolicyType { - Boolean = 'boolean', - Number = 'number', - Object = 'object', - String = 'string', - StringEnum = 'stringEnum', -} - -interface Policy { - readonly name: string; - readonly type: PolicyType; - readonly category: Category; - readonly minimumVersion: string; - renderADMX(regKey: string): string[]; - renderADMLStrings(translations?: LanguageTranslations): string[]; - renderADMLPresentation(): string; - renderProfile(): string[]; - // https://github.com/ProfileManifests/ProfileManifests/wiki/Manifest-Format - renderProfileManifest(translations?: LanguageTranslations): string; -} - -function renderADMLString(prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { - let value: string | undefined; - - if (translations) { - const moduleTranslations = translations[moduleName]; - - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - - if (!value) { - value = nlsString.value; - } - - return `${value}`; -} - -function renderProfileString(_prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { - let value: string | undefined; - - if (translations) { - const moduleTranslations = translations[moduleName]; - - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - - if (!value) { - value = nlsString.value; - } - - return value; -} - -abstract class BasePolicy implements Policy { - constructor( - readonly type: PolicyType, - readonly name: string, - readonly category: Category, - readonly minimumVersion: string, - protected description: NlsString, - protected moduleName: string, - ) { } - - protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { - return renderADMLString(this.name, this.moduleName, nlsString, translations); - } - - renderADMX(regKey: string) { - return [ - ``, - ` `, - ` `, - ` `, - ...this.renderADMXElements(), - ` `, - `` - ]; - } - - protected abstract renderADMXElements(): string[]; - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - `${this.name}`, - this.renderADMLString(this.description, translations) - ]; - } - - renderADMLPresentation(): string { - return `${this.renderADMLPresentationContents()}`; - } - - protected abstract renderADMLPresentationContents(): string; - - renderProfile() { - return [`${this.name}`, this.renderProfileValue()]; - } - - renderProfileManifest(translations?: LanguageTranslations): string { - return ` -${this.renderProfileManifestValue(translations)} -`; - } - - abstract renderProfileValue(): string; - abstract renderProfileManifestValue(translations?: LanguageTranslations): string; -} - -class BooleanPolicy extends BasePolicy { - - static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { - const { name, minimumVersion, localization, type } = policy; - - if (type !== 'boolean') { - return undefined; - } - - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ` `, - `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -boolean`; - } -} - -class NumberPolicy extends BasePolicy { - - static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; - - if (type !== 'number') { - return undefined; - } - - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); - } - - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected readonly defaultValue: number, - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - `` - // `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } - - renderProfileValue() { - return `${this.defaultValue}`; - } - - renderProfileManifestValue(translations?: LanguageTranslations) { - return `pfm_default -${this.defaultValue} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -integer`; - } -} - -class StringPolicy extends BasePolicy { - - static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; - - if (type !== 'string') { - return undefined; - } - - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.String, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [``]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string`; - } -} - -class ObjectPolicy extends BasePolicy { - - static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; - - if (type !== 'object' && type !== 'array') { - return undefined; - } - - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.Object, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [``]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -`; - } -} - -class StringEnumPolicy extends BasePolicy { - - static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; - - if (type !== 'string') { - return undefined; - } - - const enum_ = enumValue; - - if (!enum_) { - return undefined; - } - - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); - } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy( - name, - { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, - minimumVersion, - { nlsKey: localization.description.key, value: localization.description.value }, - '', - enum_, - enumDescriptions - ); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected enum_: string[], - protected enumDescriptions: NlsString[], - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ...this.enum_.map((value, index) => ` ${value}`), - `` - ]; - } - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - ...super.renderADMLStrings(translations), - ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) - ]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue() { - return `${this.enum_[0]}`; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default -${this.enum_[0]} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -pfm_range_list - - ${this.enum_.map(e => `${e}`).join('\n ')} -`; - } -} - -function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { - versions = versions.map(v => v.replace(/\./g, '_')); - - return ` - - - - - - - - ${versions.map(v => ``).join(`\n `)} - - - - - ${categories.map(c => ``).join(`\n `)} - - - ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} - - -`; -} - -function renderADML(appName: string, versions: string[], categories: Category[], policies: Policy[], translations?: LanguageTranslations) { - return ` - - - - - - ${appName} - ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} - ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} - ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} - - - ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} - - - -`; -} - -function renderProfileManifest(appName: string, bundleIdentifier: string, _versions: string[], _categories: Category[], policies: Policy[], translations?: LanguageTranslations) { - - const requiredPayloadFields = ` - - pfm_default - Configure ${appName} - pfm_name - PayloadDescription - pfm_title - Payload Description - pfm_type - string - - - pfm_default - ${appName} - pfm_name - PayloadDisplayName - pfm_require - always - pfm_title - Payload Display Name - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadIdentifier - pfm_require - always - pfm_title - Payload Identifier - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadType - pfm_require - always - pfm_title - Payload Type - pfm_type - string - - - pfm_default - - pfm_name - PayloadUUID - pfm_require - always - pfm_title - Payload UUID - pfm_type - string - - - pfm_default - 1 - pfm_name - PayloadVersion - pfm_range_list - - 1 - - pfm_require - always - pfm_title - Payload Version - pfm_type - integer - - - pfm_default - Microsoft - pfm_name - PayloadOrganization - pfm_title - Payload Organization - pfm_type - string - `; - - const profileManifestSubkeys = policies.map(policy => { - return policy.renderProfileManifest(translations); - }).join(''); - - return ` - - - - pfm_app_url - https://code.visualstudio.com/ - pfm_description - ${appName} Managed Settings - pfm_documentation_url - https://code.visualstudio.com/docs/setup/enterprise - pfm_domain - ${bundleIdentifier} - pfm_format_version - 1 - pfm_interaction - combined - pfm_last_modified - ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} - pfm_platforms - - macOS - - pfm_subkeys - - ${requiredPayloadFields} - ${profileManifestSubkeys} - - pfm_title - ${appName} - pfm_unique - - pfm_version - 1 - -`; -} - -function renderMacOSPolicy(policies: Policy[], translations: Translations) { - const appName = product.nameLong; - const bundleIdentifier = product.darwinBundleIdentifier; - const payloadUUID = product.darwinProfilePayloadUUID; - const UUID = product.darwinProfileUUID; - - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...new Set(policies.map(p => p.category))]; - - const policyEntries = - policies.map(policy => policy.renderProfile()) - .flat() - .map(entry => `\t\t\t\t${entry}`) - .join('\n'); - - - return { - profile: ` - - - - PayloadContent - - - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier}.${UUID} - PayloadType - ${bundleIdentifier} - PayloadUUID - ${UUID} - PayloadVersion - 1 -${policyEntries} - - - PayloadDescription - This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier} - PayloadOrganization - Microsoft - PayloadType - Configuration - PayloadUUID - ${payloadUUID} - PayloadVersion - 1 - TargetDeviceType - 5 - -`, - manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => - ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) - ] - }; -} - -function renderGP(policies: Policy[], translations: Translations) { - const appName = product.nameLong; - const regKey = product.win32RegValueName; - - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))] as Category[]; - - return { - admx: renderADMX(regKey, versions, categories, policies), - adml: [ - { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => - ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) - ] - }; -} - -const Languages = { - 'fr': 'fr-fr', - 'it': 'it-it', - 'de': 'de-de', - 'es': 'es-es', - 'ru': 'ru-ru', - 'zh-hans': 'zh-cn', - 'zh-hant': 'zh-tw', - 'ja': 'ja-jp', - 'ko': 'ko-kr', - 'cs': 'cs-cz', - 'pt-br': 'pt-br', - 'tr': 'tr-tr', - 'pl': 'pl-pl', -}; - -type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; -type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; - -type Version = [number, number, number]; - async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { const resource = { publisher: 'ms-ceintl', @@ -843,7 +169,7 @@ async function getTranslations(): Promise { async function windowsMain(policies: Policy[], translations: Translations) { const root = '.build/policies/win32'; - const { admx, adml } = await renderGP(policies, translations); + const { admx, adml } = renderGP(product, policies, translations); await fs.rm(root, { recursive: true, force: true }); await fs.mkdir(root, { recursive: true }); @@ -863,7 +189,7 @@ async function darwinMain(policies: Policy[], translations: Translations) { throw new Error(`Missing required product information.`); } const root = '.build/policies/darwin'; - const { profile, manifests } = await renderMacOSPolicy(policies, translations); + const { profile, manifests } = renderMacOSPolicy(product, policies, translations); await fs.rm(root, { recursive: true, force: true }); await fs.mkdir(root, { recursive: true }); diff --git a/build/lib/policies/render.js b/build/lib/policies/render.js new file mode 100644 index 00000000000..7f312314e35 --- /dev/null +++ b/build/lib/policies/render.js @@ -0,0 +1,271 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.renderADMLString = renderADMLString; +exports.renderProfileString = renderProfileString; +exports.renderADMX = renderADMX; +exports.renderADML = renderADML; +exports.renderProfileManifest = renderProfileManifest; +exports.renderMacOSPolicy = renderMacOSPolicy; +exports.renderGP = renderGP; +function renderADMLString(prefix, moduleName, nlsString, translations) { + let value; + if (translations) { + const moduleTranslations = translations[moduleName]; + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + if (!value) { + value = nlsString.value; + } + return `${value}`; +} +function renderProfileString(_prefix, moduleName, nlsString, translations) { + let value; + if (translations) { + const moduleTranslations = translations[moduleName]; + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + if (!value) { + value = nlsString.value; + } + return value; +} +function renderADMX(regKey, versions, categories, policies) { + versions = versions.map(v => v.replace(/\./g, '_')); + return ` + + + + + + + + ${versions.map(v => ``).join(`\n `)} + + + + + ${categories.map(c => ``).join(`\n `)} + + + ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} + + +`; +} +function renderADML(appName, versions, categories, policies, translations) { + return ` + + + + + + ${appName} + ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} + ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} + ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} + + + ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} + + + +`; +} +function renderProfileManifest(appName, bundleIdentifier, _versions, _categories, policies, translations) { + const requiredPayloadFields = ` + + pfm_default + Configure ${appName} + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + ${appName} + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + `; + const profileManifestSubkeys = policies.map(policy => { + return policy.renderProfileManifest(translations); + }).join(''); + return ` + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + ${appName} Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + ${bundleIdentifier} + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} + pfm_platforms + + macOS + + pfm_subkeys + + ${requiredPayloadFields} + ${profileManifestSubkeys} + + pfm_title + ${appName} + pfm_unique + + pfm_version + 1 + +`; +} +function renderMacOSPolicy(product, policies, translations) { + const appName = product.nameLong; + const bundleIdentifier = product.darwinBundleIdentifier; + const payloadUUID = product.darwinProfilePayloadUUID; + const UUID = product.darwinProfileUUID; + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...new Set(policies.map(p => p.category))]; + const policyEntries = policies.map(policy => policy.renderProfile()) + .flat() + .map(entry => `\t\t\t\t${entry}`) + .join('\n'); + return { + profile: ` + + + + PayloadContent + + + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier}.${UUID} + PayloadType + ${bundleIdentifier} + PayloadUUID + ${UUID} + PayloadVersion + 1 +${policyEntries} + + + PayloadDescription + This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier} + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + ${payloadUUID} + PayloadVersion + 1 + TargetDeviceType + 5 + +`, + manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) + ] + }; +} +function renderGP(product, policies, translations) { + const appName = product.nameLong; + const regKey = product.win32RegValueName; + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))]; + return { + admx: renderADMX(regKey, versions, categories, policies), + adml: [ + { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) + ] + }; +} +//# sourceMappingURL=render.js.map \ No newline at end of file diff --git a/build/lib/policies/render.ts b/build/lib/policies/render.ts new file mode 100644 index 00000000000..8953a6b7fde --- /dev/null +++ b/build/lib/policies/render.ts @@ -0,0 +1,295 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NlsString, LanguageTranslations, Category, Policy, Translations, ProductJson } from './types'; + +export function renderADMLString(prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { + let value: string | undefined; + + if (translations) { + const moduleTranslations = translations[moduleName]; + + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + + if (!value) { + value = nlsString.value; + } + + return `${value}`; +} + +export function renderProfileString(_prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { + let value: string | undefined; + + if (translations) { + const moduleTranslations = translations[moduleName]; + + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + + if (!value) { + value = nlsString.value; + } + + return value; +} + +export function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { + versions = versions.map(v => v.replace(/\./g, '_')); + + return ` + + + + + + + + ${versions.map(v => ``).join(`\n `)} + + + + + ${categories.map(c => ``).join(`\n `)} + + + ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} + + +`; +} + +export function renderADML(appName: string, versions: string[], categories: Category[], policies: Policy[], translations?: LanguageTranslations) { + return ` + + + + + + ${appName} + ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} + ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} + ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} + + + ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} + + + +`; +} + +export function renderProfileManifest(appName: string, bundleIdentifier: string, _versions: string[], _categories: Category[], policies: Policy[], translations?: LanguageTranslations) { + + const requiredPayloadFields = ` + + pfm_default + Configure ${appName} + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + ${appName} + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + `; + + const profileManifestSubkeys = policies.map(policy => { + return policy.renderProfileManifest(translations); + }).join(''); + + return ` + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + ${appName} Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + ${bundleIdentifier} + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} + pfm_platforms + + macOS + + pfm_subkeys + + ${requiredPayloadFields} + ${profileManifestSubkeys} + + pfm_title + ${appName} + pfm_unique + + pfm_version + 1 + +`; +} + +export function renderMacOSPolicy(product: ProductJson, policies: Policy[], translations: Translations) { + const appName = product.nameLong; + const bundleIdentifier = product.darwinBundleIdentifier; + const payloadUUID = product.darwinProfilePayloadUUID; + const UUID = product.darwinProfileUUID; + + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...new Set(policies.map(p => p.category))]; + + const policyEntries = + policies.map(policy => policy.renderProfile()) + .flat() + .map(entry => `\t\t\t\t${entry}`) + .join('\n'); + + + return { + profile: ` + + + + PayloadContent + + + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier}.${UUID} + PayloadType + ${bundleIdentifier} + PayloadUUID + ${UUID} + PayloadVersion + 1 +${policyEntries} + + + PayloadDescription + This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier} + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + ${payloadUUID} + PayloadVersion + 1 + TargetDeviceType + 5 + +`, + manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => + ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) + ] + }; +} + +export function renderGP(product: ProductJson, policies: Policy[], translations: Translations) { + const appName = product.nameLong; + const regKey = product.win32RegValueName; + + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))] as Category[]; + + return { + admx: renderADMX(regKey, versions, categories, policies), + adml: [ + { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => + ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) + ] + }; +} diff --git a/build/lib/policies/stringEnumPolicy.js b/build/lib/policies/stringEnumPolicy.js new file mode 100644 index 00000000000..03508238bab --- /dev/null +++ b/build/lib/policies/stringEnumPolicy.js @@ -0,0 +1,71 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StringEnumPolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const basePolicy_1 = require("./basePolicy"); +const render_1 = require("./render"); +const types_1 = require("./types"); +class StringEnumPolicy extends basePolicy_1.BasePolicy { + enum_; + enumDescriptions; + static from(category, policy) { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; + if (type !== 'string') { + return undefined; + } + const enum_ = enumValue; + if (!enum_) { + return undefined; + } + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + } + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); + } + constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { + super(types_1.PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + this.enum_ = enum_; + this.enumDescriptions = enumDescriptions; + } + renderADMXElements() { + return [ + ``, + ...this.enum_.map((value, index) => ` ${value}`), + `` + ]; + } + renderADMLStrings(translations) { + return [ + ...super.renderADMLStrings(translations), + ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) + ]; + } + renderADMLPresentationContents() { + return ``; + } + renderProfileValue() { + return `${this.enum_[0]}`; + } + renderProfileManifestValue(translations) { + return `pfm_default +${this.enum_[0]} +pfm_description +${(0, render_1.renderProfileString)(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +pfm_range_list + + ${this.enum_.map(e => `${e}`).join('\n ')} +`; + } +} +exports.StringEnumPolicy = StringEnumPolicy; +//# sourceMappingURL=stringEnumPolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/stringEnumPolicy.ts b/build/lib/policies/stringEnumPolicy.ts new file mode 100644 index 00000000000..b8f8263fb68 --- /dev/null +++ b/build/lib/policies/stringEnumPolicy.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy'; +import { CategoryDto, PolicyDto } from './policyDto'; +import { renderProfileString } from './render'; +import { Category, NlsString, PolicyType, LanguageTranslations } from './types'; + +export class StringEnumPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; + + if (type !== 'string') { + return undefined; + } + + const enum_ = enumValue; + + if (!enum_) { + return undefined; + } + + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + } + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy( + name, + { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, + minimumVersion, + { nlsKey: localization.description.key, value: localization.description.value }, + '', + enum_, + enumDescriptions + ); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + protected enum_: string[], + protected enumDescriptions: NlsString[], + ) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [ + ``, + ...this.enum_.map((value, index) => ` ${value}`), + `` + ]; + } + + renderADMLStrings(translations?: LanguageTranslations) { + return [ + ...super.renderADMLStrings(translations), + ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) + ]; + } + + renderADMLPresentationContents() { + return ``; + } + + renderProfileValue() { + return `${this.enum_[0]}`; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default +${this.enum_[0]} +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +pfm_range_list + + ${this.enum_.map(e => `${e}`).join('\n ')} +`; + } +} diff --git a/build/lib/policies/stringPolicy.js b/build/lib/policies/stringPolicy.js new file mode 100644 index 00000000000..493bc33af40 --- /dev/null +++ b/build/lib/policies/stringPolicy.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StringPolicy = void 0; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const basePolicy_1 = require("./basePolicy"); +const render_1 = require("./render"); +const types_1 = require("./types"); +class StringPolicy extends basePolicy_1.BasePolicy { + static from(category, policy) { + const { type, name, minimumVersion, localization } = policy; + if (type !== 'string') { + return undefined; + } + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + constructor(name, category, minimumVersion, description, moduleName) { + super(types_1.PolicyType.String, name, category, minimumVersion, description, moduleName); + } + renderADMXElements() { + return [``]; + } + renderADMLPresentationContents() { + return ``; + } + renderProfileValue() { + return ``; + } + renderProfileManifestValue(translations) { + return `pfm_default + +pfm_description +${(0, render_1.renderProfileString)(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string`; + } +} +exports.StringPolicy = StringPolicy; +//# sourceMappingURL=stringPolicy.js.map \ No newline at end of file diff --git a/build/lib/policies/stringPolicy.ts b/build/lib/policies/stringPolicy.ts new file mode 100644 index 00000000000..b18160640a8 --- /dev/null +++ b/build/lib/policies/stringPolicy.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy'; +import { CategoryDto, PolicyDto } from './policyDto'; +import { renderProfileString } from './render'; +import { Category, NlsString, PolicyType, LanguageTranslations } from './types'; + +export class StringPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; + + if (type !== 'string') { + return undefined; + } + + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.String, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [``]; + } + + renderADMLPresentationContents() { + return ``; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string`; + } +} diff --git a/build/lib/policies/types.js b/build/lib/policies/types.js new file mode 100644 index 00000000000..f0456389d79 --- /dev/null +++ b/build/lib/policies/types.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Languages = exports.PolicyType = void 0; +var PolicyType; +(function (PolicyType) { + PolicyType["Boolean"] = "boolean"; + PolicyType["Number"] = "number"; + PolicyType["Object"] = "object"; + PolicyType["String"] = "string"; + PolicyType["StringEnum"] = "stringEnum"; +})(PolicyType || (exports.PolicyType = PolicyType = {})); +exports.Languages = { + 'fr': 'fr-fr', + 'it': 'it-it', + 'de': 'de-de', + 'es': 'es-es', + 'ru': 'ru-ru', + 'zh-hans': 'zh-cn', + 'zh-hant': 'zh-tw', + 'ja': 'ja-jp', + 'ko': 'ko-kr', + 'cs': 'cs-cz', + 'pt-br': 'pt-br', + 'tr': 'tr-tr', + 'pl': 'pl-pl', +}; +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/build/lib/policies/types.ts b/build/lib/policies/types.ts new file mode 100644 index 00000000000..35e93c435ef --- /dev/null +++ b/build/lib/policies/types.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ProductJson { + readonly nameLong: string; + readonly darwinBundleIdentifier: string; + readonly darwinProfilePayloadUUID: string; + readonly darwinProfileUUID: string; + readonly win32RegValueName: string; + readonly extensionsGallery?: { + readonly serviceUrl: string; + readonly resourceUrlTemplate: string; + }; +} + +export interface Policy { + readonly name: string; + readonly type: PolicyType; + readonly category: Category; + readonly minimumVersion: string; + renderADMX(regKey: string): string[]; + renderADMLStrings(translations?: LanguageTranslations): string[]; + renderADMLPresentation(): string; + renderProfile(): string[]; + // https://github.com/ProfileManifests/ProfileManifests/wiki/Manifest-Format + renderProfileManifest(translations?: LanguageTranslations): string; +} + +export type NlsString = { value: string; nlsKey: string }; + +export interface Category { + readonly moduleName: string; + readonly name: NlsString; +} + +export enum PolicyType { + Boolean = 'boolean', + Number = 'number', + Object = 'object', + String = 'string', + StringEnum = 'stringEnum', +} + +export const Languages = { + 'fr': 'fr-fr', + 'it': 'it-it', + 'de': 'de-de', + 'es': 'es-es', + 'ru': 'ru-ru', + 'zh-hans': 'zh-cn', + 'zh-hant': 'zh-tw', + 'ja': 'ja-jp', + 'ko': 'ko-kr', + 'cs': 'cs-cz', + 'pt-br': 'pt-br', + 'tr': 'tr-tr', + 'pl': 'pl-pl', +}; + +export type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; +export type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; + +export type Version = [number, number, number]; diff --git a/build/lib/test/booleanPolicy.test.js b/build/lib/test/booleanPolicy.test.js new file mode 100644 index 00000000000..65bb0f5b88e --- /dev/null +++ b/build/lib/test/booleanPolicy.test.js @@ -0,0 +1,120 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const booleanPolicy_js_1 = require("../policies/booleanPolicy.js"); +const types_js_1 = require("../policies/types.js"); +suite('BooleanPolicy', () => { + const mockCategory = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + const mockPolicy = { + key: 'test.boolean.policy', + name: 'TestBooleanPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'boolean', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + test('should create BooleanPolicy from factory method', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + assert_1.default.strictEqual(policy.name, 'TestBooleanPolicy'); + assert_1.default.strictEqual(policy.minimumVersion, '1.0'); + assert_1.default.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert_1.default.strictEqual(policy.category.name.value, mockCategory.name.value); + assert_1.default.strictEqual(policy.type, types_js_1.PolicyType.Boolean); + }); + test('should render ADMX elements correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admx = policy.renderADMX('TestKey'); + assert_1.default.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '', + '\t', + '' + ]); + }); + test('should render ADML strings correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admlStrings = policy.renderADMLStrings(); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Test policy description' + ]); + }); + test('should render ADML strings with translations', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + const admlStrings = policy.renderADMLStrings(translations); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Translated description' + ]); + }); + test('should render ADML presentation correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const presentation = policy.renderADMLPresentation(); + assert_1.default.strictEqual(presentation, 'TestBooleanPolicy'); + }); + test('should render profile value correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profileValue = policy.renderProfileValue(); + assert_1.default.strictEqual(profileValue, ''); + }); + test('should render profile correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profile = policy.renderProfile(); + assert_1.default.strictEqual(profile.length, 2); + assert_1.default.strictEqual(profile[0], 'TestBooleanPolicy'); + assert_1.default.strictEqual(profile[1], ''); + }); + test('should render profile manifest value correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifestValue = policy.renderProfileManifestValue(); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + test('should render profile manifest value with translations', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + const manifestValue = policy.renderProfileManifestValue(translations); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + test('should render profile manifest correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifest = policy.renderProfileManifest(); + assert_1.default.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean\n'); + }); +}); +//# sourceMappingURL=booleanPolicy.test.js.map \ No newline at end of file diff --git a/build/lib/test/booleanPolicy.test.ts b/build/lib/test/booleanPolicy.test.ts new file mode 100644 index 00000000000..eed38d99d00 --- /dev/null +++ b/build/lib/test/booleanPolicy.test.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { BooleanPolicy } from '../policies/booleanPolicy.js'; +import { LanguageTranslations, PolicyType } from '../policies/types.js'; +import { CategoryDto, PolicyDto } from '../policies/policyDto.js'; + +suite('BooleanPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.boolean.policy', + name: 'TestBooleanPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'boolean', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + + test('should create BooleanPolicy from factory method', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestBooleanPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Boolean); + }); + + test('should render ADMX elements correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Test policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, 'TestBooleanPolicy'); + }); + + test('should render profile value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestBooleanPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + + test('should render profile manifest value with translations', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + + test('should render profile manifest correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean\n'); + }); +}); diff --git a/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig b/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig new file mode 100644 index 00000000000..0f0ae365d7c --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig @@ -0,0 +1,61 @@ + + + + + PayloadContent + + + PayloadDisplayName + Code - OSS + PayloadIdentifier + com.visualstudio.code.oss.47827DD9-4734-49A0-AF80-7E19B11495CC + PayloadType + com.visualstudio.code.oss + PayloadUUID + 47827DD9-4734-49A0-AF80-7E19B11495CC + PayloadVersion + 1 + ChatAgentExtensionTools + + ChatAgentMode + + ChatMCP + none + ChatPromptFiles + + ChatToolsAutoApprove + + McpGalleryServiceUrl + + AllowedExtensions + + ExtensionGalleryServiceUrl + + ChatToolsTerminalEnableAutoApprove + + EnableFeedback + + TelemetryLevel + all + UpdateMode + none + + + PayloadDescription + This profile manages Code - OSS. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + Code - OSS + PayloadIdentifier + com.visualstudio.code.oss + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + CF808BE7-53F3-46C6-A7E2-7EDB98A5E959 + PayloadVersion + 1 + TargetDeviceType + 5 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist b/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist new file mode 100644 index 00000000000..f90533cfeaa --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist @@ -0,0 +1,274 @@ + + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + Code - OSS Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + com.visualstudio.code.oss + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + 2025-10-21T23:04:34Z + pfm_platforms + + macOS + + pfm_subkeys + + + + pfm_default + Configure Code - OSS + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + Code - OSS + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + + +pfm_default + +pfm_description +Enable using tools contributed by third-party extensions. +pfm_name +ChatAgentExtensionTools +pfm_title +ChatAgentExtensionTools +pfm_type +boolean + +pfm_default + +pfm_description +Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view. +pfm_name +ChatAgentMode +pfm_title +ChatAgentMode +pfm_type +boolean + +pfm_default +none +pfm_description +Controls access to installed Model Context Protocol servers. +pfm_name +ChatMCP +pfm_title +ChatMCP +pfm_type +string +pfm_range_list + + none + registry + all + + +pfm_default + +pfm_description +Enables reusable prompt and instruction files in Chat sessions. +pfm_name +ChatPromptFiles +pfm_title +ChatPromptFiles +pfm_type +boolean + +pfm_default + +pfm_description +Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised. + +This feature disables critical security protections and makes it much easier for an attacker to compromise the machine. +pfm_name +ChatToolsAutoApprove +pfm_title +ChatToolsAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Configure the MCP Gallery service URL to connect to +pfm_name +McpGalleryServiceUrl +pfm_title +McpGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions +pfm_name +AllowedExtensions +pfm_title +AllowedExtensions +pfm_type +string + + +pfm_default + +pfm_description +Configure the Marketplace service URL to connect to +pfm_name +ExtensionGalleryServiceUrl +pfm_title +ExtensionGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Controls whether to allow auto approval in the run in terminal tool. +pfm_name +ChatToolsTerminalEnableAutoApprove +pfm_title +ChatToolsTerminalEnableAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options. +pfm_name +EnableFeedback +pfm_title +EnableFeedback +pfm_type +boolean + +pfm_default +all +pfm_description +Controls the level of telemetry. +pfm_name +TelemetryLevel +pfm_title +TelemetryLevel +pfm_type +string +pfm_range_list + + all + error + crash + off + + +pfm_default +none +pfm_description +Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service. +pfm_name +UpdateMode +pfm_title +UpdateMode +pfm_type +string +pfm_range_list + + none + manual + start + default + + + + pfm_title + Code - OSS + pfm_unique + + pfm_version + 1 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist b/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist new file mode 100644 index 00000000000..60f244be3b5 --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist @@ -0,0 +1,274 @@ + + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + Code - OSS Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + com.visualstudio.code.oss + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + 2025-10-21T23:04:34Z + pfm_platforms + + macOS + + pfm_subkeys + + + + pfm_default + Configure Code - OSS + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + Code - OSS + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + + +pfm_default + +pfm_description +Autorisez l’utilisation d’outils fournis par des extensions tierces. +pfm_name +ChatAgentExtensionTools +pfm_title +ChatAgentExtensionTools +pfm_type +boolean + +pfm_default + +pfm_description +Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue. +pfm_name +ChatAgentMode +pfm_title +ChatAgentMode +pfm_type +boolean + +pfm_default +none +pfm_description +Contrôle l’accès aux serveurs de protocole de contexte du modèle. +pfm_name +ChatMCP +pfm_title +ChatMCP +pfm_type +string +pfm_range_list + + none + registry + all + + +pfm_default + +pfm_description +Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation. +pfm_name +ChatPromptFiles +pfm_title +ChatPromptFiles +pfm_type +boolean + +pfm_default + +pfm_description +L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant. +pfm_name +ChatToolsAutoApprove +pfm_title +ChatToolsAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Configurer l’URL du service de la galerie MCP à laquelle se connecter +pfm_name +McpGalleryServiceUrl +pfm_title +McpGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions +pfm_name +AllowedExtensions +pfm_title +AllowedExtensions +pfm_type +string + + +pfm_default + +pfm_description +Configurer l’URL du service Place de marché à laquelle se connecter +pfm_name +ExtensionGalleryServiceUrl +pfm_title +ExtensionGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal. +pfm_name +ChatToolsTerminalEnableAutoApprove +pfm_title +ChatToolsTerminalEnableAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires. +pfm_name +EnableFeedback +pfm_title +EnableFeedback +pfm_type +boolean + +pfm_default +all +pfm_description +Contrôle le niveau de télémétrie. +pfm_name +TelemetryLevel +pfm_title +TelemetryLevel +pfm_type +string +pfm_range_list + + all + error + crash + off + + +pfm_default +none +pfm_description +Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft. +pfm_name +UpdateMode +pfm_title +UpdateMode +pfm_type +string +pfm_range_list + + none + manual + start + default + + + + pfm_title + Code - OSS + pfm_unique + + pfm_version + 1 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/win32/CodeOSS.admx b/build/lib/test/fixtures/policies/win32/CodeOSS.admx new file mode 100644 index 00000000000..fee094c56c5 --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/CodeOSS.admx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + registry + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + error + crash + off + + + + + + + + + none + manual + start + default + + + + + diff --git a/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml b/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml new file mode 100644 index 00000000000..39b05ddc72d --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml @@ -0,0 +1,71 @@ + + + + + + + Code - OSS + Code - OSS >= 1.101 + Code - OSS >= 1.104 + Code - OSS >= 1.67 + Code - OSS >= 1.96 + Code - OSS >= 1.99 + Chat + Extensions + Integrated Terminal + Telemetry + Update + ChatAgentExtensionTools + Enable using tools contributed by third-party extensions. + ChatAgentMode + Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view. + ChatMCP + Controls access to installed Model Context Protocol servers. + No access to MCP servers. + Allows access to MCP servers installed from the registry that VS Code is connected to. + Allow access to any installed MCP server. + ChatPromptFiles + Enables reusable prompt and instruction files in Chat sessions. + ChatToolsAutoApprove + Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised. + +This feature disables critical security protections and makes it much easier for an attacker to compromise the machine. + McpGalleryServiceUrl + Configure the MCP Gallery service URL to connect to + AllowedExtensions + Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions + ExtensionGalleryServiceUrl + Configure the Marketplace service URL to connect to + ChatToolsTerminalEnableAutoApprove + Controls whether to allow auto approval in the run in terminal tool. + EnableFeedback + Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options. + TelemetryLevel + Controls the level of telemetry. + Sends usage data, errors, and crash reports. + Sends general error telemetry and crash reports. + Sends OS level crash reports. + Disables all product telemetry. + UpdateMode + Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service. + Disable updates. + Disable automatic background update checks. Updates will be available if you manually check for updates. + Check for updates only on startup. Disable automatic background update checks. + Enable automatic update checks. Code will check for updates automatically and periodically. + + + ChatAgentExtensionTools + ChatAgentMode + + ChatPromptFiles + ChatToolsAutoApprove + + + + ChatToolsTerminalEnableAutoApprove + EnableFeedback + + + + + diff --git a/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml b/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml new file mode 100644 index 00000000000..eab50282b2d --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml @@ -0,0 +1,71 @@ + + + + + + + Code - OSS + Code - OSS >= 1.101 + Code - OSS >= 1.104 + Code - OSS >= 1.67 + Code - OSS >= 1.96 + Code - OSS >= 1.99 + Session interactive + Extensions + Terminal intégré + Télémétrie + Mettre à jour + ChatAgentExtensionTools + Autorisez l’utilisation d’outils fournis par des extensions tierces. + ChatAgentMode + Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue. + ChatMCP + Contrôle l’accès aux serveurs de protocole de contexte du modèle. + Aucun accès aux serveurs MCP. + Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté. + Autorisez l’accès à tout serveur MCP installé. + ChatPromptFiles + Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation. + ChatToolsAutoApprove + L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant. + McpGalleryServiceUrl + Configurer l’URL du service de la galerie MCP à laquelle se connecter + AllowedExtensions + Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions + ExtensionGalleryServiceUrl + Configurer l’URL du service Place de marché à laquelle se connecter + ChatToolsTerminalEnableAutoApprove + Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal. + EnableFeedback + Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires. + TelemetryLevel + Contrôle le niveau de télémétrie. + Envoie les données d'utilisation, les erreurs et les rapports d'erreur. + Envoie la télémétrie d'erreur générale et les rapports de plantage. + Envoie des rapports de plantage au niveau du système d'exploitation. + Désactive toutes les données de télémétrie du produit. + UpdateMode + Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft. + Aucun + Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement. + Démarrer + Système + + + ChatAgentExtensionTools + ChatAgentMode + + ChatPromptFiles + ChatToolsAutoApprove + + + + ChatToolsTerminalEnableAutoApprove + EnableFeedback + + + + + diff --git a/build/lib/test/numberPolicy.test.js b/build/lib/test/numberPolicy.test.js new file mode 100644 index 00000000000..de3a73565be --- /dev/null +++ b/build/lib/test/numberPolicy.test.js @@ -0,0 +1,119 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const numberPolicy_js_1 = require("../policies/numberPolicy.js"); +const types_js_1 = require("../policies/types.js"); +suite('NumberPolicy', () => { + const mockCategory = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + const mockPolicy = { + key: 'test.number.policy', + name: 'TestNumberPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'number', + default: 42, + localization: { + description: { key: 'test.policy.description', value: 'Test number policy description' } + } + }; + test('should create NumberPolicy from factory method', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + assert_1.default.strictEqual(policy.name, 'TestNumberPolicy'); + assert_1.default.strictEqual(policy.minimumVersion, '1.0'); + assert_1.default.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert_1.default.strictEqual(policy.category.name.value, mockCategory.name.value); + assert_1.default.strictEqual(policy.type, types_js_1.PolicyType.Number); + }); + test('should render ADMX elements correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admx = policy.renderADMX('TestKey'); + assert_1.default.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + test('should render ADML strings correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admlStrings = policy.renderADMLStrings(); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Test number policy description' + ]); + }); + test('should render ADML strings with translations', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + const admlStrings = policy.renderADMLStrings(translations); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Translated description' + ]); + }); + test('should render ADML presentation correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const presentation = policy.renderADMLPresentation(); + assert_1.default.strictEqual(presentation, 'TestNumberPolicy'); + }); + test('should render profile value correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profileValue = policy.renderProfileValue(); + assert_1.default.strictEqual(profileValue, '42'); + }); + test('should render profile correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profile = policy.renderProfile(); + assert_1.default.strictEqual(profile.length, 2); + assert_1.default.strictEqual(profile[0], 'TestNumberPolicy'); + assert_1.default.strictEqual(profile[1], '42'); + }); + test('should render profile manifest value correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifestValue = policy.renderProfileManifestValue(); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + test('should render profile manifest value with translations', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + const manifestValue = policy.renderProfileManifestValue(translations); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTranslated manifest description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + test('should render profile manifest correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifest = policy.renderProfileManifest(); + assert_1.default.strictEqual(manifest, '\npfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger\n'); + }); +}); +//# sourceMappingURL=numberPolicy.test.js.map \ No newline at end of file diff --git a/build/lib/test/numberPolicy.test.ts b/build/lib/test/numberPolicy.test.ts new file mode 100644 index 00000000000..4f27891ec61 --- /dev/null +++ b/build/lib/test/numberPolicy.test.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { NumberPolicy } from '../policies/numberPolicy.js'; +import { LanguageTranslations, PolicyType } from '../policies/types.js'; +import { CategoryDto, PolicyDto } from '../policies/policyDto.js'; + +suite('NumberPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.number.policy', + name: 'TestNumberPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'number', + default: 42, + localization: { + description: { key: 'test.policy.description', value: 'Test number policy description' } + } + }; + + test('should create NumberPolicy from factory method', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestNumberPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Number); + }); + + test('should render ADMX elements correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Test number policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, 'TestNumberPolicy'); + }); + + test('should render profile value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, '42'); + }); + + test('should render profile correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestNumberPolicy'); + assert.strictEqual(profile[1], '42'); + }); + + test('should render profile manifest value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + + test('should render profile manifest value with translations', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTranslated manifest description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + + test('should render profile manifest correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger\n'); + }); +}); diff --git a/build/lib/test/objectPolicy.test.js b/build/lib/test/objectPolicy.test.js new file mode 100644 index 00000000000..1c25d52cf61 --- /dev/null +++ b/build/lib/test/objectPolicy.test.js @@ -0,0 +1,118 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const objectPolicy_js_1 = require("../policies/objectPolicy.js"); +const types_js_1 = require("../policies/types.js"); +suite('ObjectPolicy', () => { + const mockCategory = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + const mockPolicy = { + key: 'test.object.policy', + name: 'TestObjectPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'object', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + test('should create ObjectPolicy from factory method', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + assert_1.default.strictEqual(policy.name, 'TestObjectPolicy'); + assert_1.default.strictEqual(policy.minimumVersion, '1.0'); + assert_1.default.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert_1.default.strictEqual(policy.category.name.value, mockCategory.name.value); + assert_1.default.strictEqual(policy.type, types_js_1.PolicyType.Object); + }); + test('should render ADMX elements correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admx = policy.renderADMX('TestKey'); + assert_1.default.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + test('should render ADML strings correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admlStrings = policy.renderADMLStrings(); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Test policy description' + ]); + }); + test('should render ADML strings with translations', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + const admlStrings = policy.renderADMLStrings(translations); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Translated description' + ]); + }); + test('should render ADML presentation correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const presentation = policy.renderADMLPresentation(); + assert_1.default.strictEqual(presentation, ''); + }); + test('should render profile value correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profileValue = policy.renderProfileValue(); + assert_1.default.strictEqual(profileValue, ''); + }); + test('should render profile correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profile = policy.renderProfile(); + assert_1.default.strictEqual(profile.length, 2); + assert_1.default.strictEqual(profile[0], 'TestObjectPolicy'); + assert_1.default.strictEqual(profile[1], ''); + }); + test('should render profile manifest value correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifestValue = policy.renderProfileManifestValue(); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + test('should render profile manifest value with translations', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + const manifestValue = policy.renderProfileManifestValue(translations); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + test('should render profile manifest correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifest = policy.renderProfileManifest(); + assert_1.default.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n\n'); + }); +}); +//# sourceMappingURL=objectPolicy.test.js.map \ No newline at end of file diff --git a/build/lib/test/objectPolicy.test.ts b/build/lib/test/objectPolicy.test.ts new file mode 100644 index 00000000000..a4c2638a52f --- /dev/null +++ b/build/lib/test/objectPolicy.test.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ObjectPolicy } from '../policies/objectPolicy.js'; +import { LanguageTranslations, PolicyType } from '../policies/types.js'; +import { CategoryDto, PolicyDto } from '../policies/policyDto.js'; + +suite('ObjectPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.object.policy', + name: 'TestObjectPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'object', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + + test('should create ObjectPolicy from factory method', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestObjectPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Object); + }); + + test('should render ADMX elements correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Test policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render profile value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestObjectPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + + test('should render profile manifest value with translations', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + + test('should render profile manifest correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n\n'); + }); +}); diff --git a/build/lib/test/policyConversion.test.js b/build/lib/test/policyConversion.test.js new file mode 100644 index 00000000000..0f61c856f04 --- /dev/null +++ b/build/lib/test/policyConversion.test.js @@ -0,0 +1,455 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const fs_1 = require("fs"); +const path_1 = __importDefault(require("path")); +const booleanPolicy_1 = require("../policies/booleanPolicy"); +const numberPolicy_1 = require("../policies/numberPolicy"); +const objectPolicy_1 = require("../policies/objectPolicy"); +const stringEnumPolicy_1 = require("../policies/stringEnumPolicy"); +const stringPolicy_1 = require("../policies/stringPolicy"); +const render_1 = require("../policies/render"); +const PolicyTypes = [ + booleanPolicy_1.BooleanPolicy, + numberPolicy_1.NumberPolicy, + stringEnumPolicy_1.StringEnumPolicy, + stringPolicy_1.StringPolicy, + objectPolicy_1.ObjectPolicy +]; +function parsePolicies(policyData) { + const categories = new Map(); + for (const category of policyData.categories) { + categories.set(category.key, category); + } + const policies = []; + for (const policy of policyData.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + let result; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + policies.push(result); + } + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + return policies; +} +/** + * This is a snapshot of the data taken on Oct. 20 2025 as part of the + * policy refactor effort. Let's make sure that nothing has regressed. + */ +const policies = { + categories: [ + { + key: 'Extensions', + name: { + key: 'extensionsConfigurationTitle', + value: 'Extensions' + } + }, + { + key: 'IntegratedTerminal', + name: { + key: 'terminalIntegratedConfigurationTitle', + value: 'Integrated Terminal' + } + }, + { + key: 'InteractiveSession', + name: { + key: 'interactiveSessionConfigurationTitle', + value: 'Chat' + } + }, + { + key: 'Telemetry', + name: { + key: 'telemetryConfigurationTitle', + value: 'Telemetry' + } + }, + { + key: 'Update', + name: { + key: 'updateConfigurationTitle', + value: 'Update' + } + } + ], + policies: [ + { + key: 'chat.mcp.gallery.serviceUrl', + name: 'McpGalleryServiceUrl', + category: 'InteractiveSession', + minimumVersion: '1.101', + localization: { + description: { + key: 'mcp.gallery.serviceUrl', + value: 'Configure the MCP Gallery service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.gallery.serviceUrl', + name: 'ExtensionGalleryServiceUrl', + category: 'Extensions', + minimumVersion: '1.99', + localization: { + description: { + key: 'extensions.gallery.serviceUrl', + value: 'Configure the Marketplace service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.allowed', + name: 'AllowedExtensions', + category: 'Extensions', + minimumVersion: '1.96', + localization: { + description: { + key: 'extensions.allowed.policy', + value: 'Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions' + } + }, + type: 'object', + default: '*' + }, + { + key: 'chat.tools.global.autoApprove', + name: 'ChatToolsAutoApprove', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'autoApprove2.description', + value: 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' + } + }, + type: 'boolean', + default: false + }, + { + key: 'chat.mcp.access', + name: 'ChatMCP', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.mcp.access', + value: 'Controls access to installed Model Context Protocol servers.' + }, + enumDescriptions: [ + { + key: 'chat.mcp.access.none', + value: 'No access to MCP servers.' + }, + { + key: 'chat.mcp.access.registry', + value: 'Allows access to MCP servers installed from the registry that VS Code is connected to.' + }, + { + key: 'chat.mcp.access.any', + value: 'Allow access to any installed MCP server.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'none', + 'registry', + 'all' + ] + }, + { + key: 'chat.extensionTools.enabled', + name: 'ChatAgentExtensionTools', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.extensionToolsEnabled', + value: 'Enable using tools contributed by third-party extensions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.agent.enabled', + name: 'ChatAgentMode', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.agent.enabled.description', + value: 'Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.promptFiles', + name: 'ChatPromptFiles', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.promptFiles.policy', + value: 'Enables reusable prompt and instruction files in Chat sessions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.tools.terminal.enableAutoApprove', + name: 'ChatToolsTerminalEnableAutoApprove', + category: 'IntegratedTerminal', + minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: 'Controls whether to allow auto approval in the run in terminal tool.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'update.mode', + name: 'UpdateMode', + category: 'Update', + minimumVersion: '1.67', + localization: { + description: { + key: 'updateMode', + value: 'Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service.' + }, + enumDescriptions: [ + { + key: 'none', + value: 'Disable updates.' + }, + { + key: 'manual', + value: 'Disable automatic background update checks. Updates will be available if you manually check for updates.' + }, + { + key: 'start', + value: 'Check for updates only on startup. Disable automatic background update checks.' + }, + { + key: 'default', + value: 'Enable automatic update checks. Code will check for updates automatically and periodically.' + } + ] + }, + type: 'string', + default: 'default', + enum: [ + 'none', + 'manual', + 'start', + 'default' + ] + }, + { + key: 'telemetry.telemetryLevel', + name: 'TelemetryLevel', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.telemetryLevel.policyDescription', + value: 'Controls the level of telemetry.' + }, + enumDescriptions: [ + { + key: 'telemetry.telemetryLevel.default', + value: 'Sends usage data, errors, and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.error', + value: 'Sends general error telemetry and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.crash', + value: 'Sends OS level crash reports.' + }, + { + key: 'telemetry.telemetryLevel.off', + value: 'Disables all product telemetry.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'all', + 'error', + 'crash', + 'off' + ] + }, + { + key: 'telemetry.feedback.enabled', + name: 'EnableFeedback', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.feedback.enabled', + value: 'Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.' + } + }, + type: 'boolean', + default: true + } + ] +}; +const mockProduct = { + nameLong: 'Code - OSS', + darwinBundleIdentifier: 'com.visualstudio.code.oss', + darwinProfilePayloadUUID: 'CF808BE7-53F3-46C6-A7E2-7EDB98A5E959', + darwinProfileUUID: '47827DD9-4734-49A0-AF80-7E19B11495CC', + win32RegValueName: 'CodeOSS' +}; +const frenchTranslations = [ + { + languageId: 'fr-fr', + languageTranslations: { + '': { + 'interactiveSessionConfigurationTitle': 'Session interactive', + 'extensionsConfigurationTitle': 'Extensions', + 'terminalIntegratedConfigurationTitle': 'Terminal intégré', + 'telemetryConfigurationTitle': 'Télémétrie', + 'updateConfigurationTitle': 'Mettre à jour', + 'chat.extensionToolsEnabled': 'Autorisez l’utilisation d’outils fournis par des extensions tierces.', + 'chat.agent.enabled.description': 'Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue.', + 'chat.mcp.access': 'Contrôle l’accès aux serveurs de protocole de contexte du modèle.', + 'chat.mcp.access.none': 'Aucun accès aux serveurs MCP.', + 'chat.mcp.access.registry': `Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté.`, + 'chat.mcp.access.any': 'Autorisez l’accès à tout serveur MCP installé.', + 'chat.promptFiles.policy': 'Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation.', + 'autoApprove2.description': `L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant.`, + 'mcp.gallery.serviceUrl': 'Configurer l’URL du service de la galerie MCP à laquelle se connecter', + 'extensions.allowed.policy': 'Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions', + 'extensions.gallery.serviceUrl': 'Configurer l’URL du service Place de marché à laquelle se connecter', + 'autoApproveMode.description': 'Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal.', + 'telemetry.feedback.enabled': 'Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires.', + 'telemetry.telemetryLevel.policyDescription': 'Contrôle le niveau de télémétrie.', + 'telemetry.telemetryLevel.default': `Envoie les données d'utilisation, les erreurs et les rapports d'erreur.`, + 'telemetry.telemetryLevel.error': `Envoie la télémétrie d'erreur générale et les rapports de plantage.`, + 'telemetry.telemetryLevel.crash': `Envoie des rapports de plantage au niveau du système d'exploitation.`, + 'telemetry.telemetryLevel.off': 'Désactive toutes les données de télémétrie du produit.', + 'updateMode': `Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft.`, + 'none': 'Aucun', + 'manual': 'Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement.', + 'start': 'Démarrer', + 'default': 'Système' + } + } + } +]; +suite('Policy E2E conversion', () => { + test('should render macOS policy profile from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderMacOSPolicy)(mockProduct, parsedPolicies, []); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'darwin', 'com.visualstudio.code.oss.mobileconfig'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Compare the rendered profile with the fixture + assert_1.default.strictEqual(result.profile, expectedContent, 'macOS policy profile should match the fixture'); + }); + test('should render macOS manifest from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderMacOSPolicy)(mockProduct, parsedPolicies, []); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'darwin', 'en-us', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Find the en-us manifest + const enUsManifest = result.manifests.find(m => m.languageId === 'en-us'); + assert_1.default.ok(enUsManifest, 'en-us manifest should exist'); + // Compare the rendered manifest with the fixture, ignoring the timestamp + // The pfm_last_modified field contains a timestamp that will differ each time + const normalizeTimestamp = (content) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert_1.default.strictEqual(normalizeTimestamp(enUsManifest.contents), normalizeTimestamp(expectedContent), 'macOS manifest should match the fixture (ignoring timestamp)'); + }); + test('should render Windows ADMX from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderGP)(mockProduct, parsedPolicies, []); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'win32', 'CodeOSS.admx'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Compare the rendered ADMX with the fixture + assert_1.default.strictEqual(result.admx, expectedContent, 'Windows ADMX should match the fixture'); + }); + test('should render Windows ADML from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderGP)(mockProduct, parsedPolicies, []); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'win32', 'en-us', 'CodeOSS.adml'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Find the en-us ADML + const enUsAdml = result.adml.find(a => a.languageId === 'en-us'); + assert_1.default.ok(enUsAdml, 'en-us ADML should exist'); + // Compare the rendered ADML with the fixture + assert_1.default.strictEqual(enUsAdml.contents, expectedContent, 'Windows ADML should match the fixture'); + }); + test('should render macOS manifest with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderMacOSPolicy)(mockProduct, parsedPolicies, frenchTranslations); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'darwin', 'fr-fr', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Find the fr-fr manifest + const frFrManifest = result.manifests.find(m => m.languageId === 'fr-fr'); + assert_1.default.ok(frFrManifest, 'fr-fr manifest should exist'); + // Compare the rendered manifest with the fixture, ignoring the timestamp + const normalizeTimestamp = (content) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert_1.default.strictEqual(normalizeTimestamp(frFrManifest.contents), normalizeTimestamp(expectedContent), 'macOS fr-fr manifest should match the fixture (ignoring timestamp)'); + }); + test('should render Windows ADML with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderGP)(mockProduct, parsedPolicies, frenchTranslations); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'win32', 'fr-fr', 'CodeOSS.adml'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + // Find the fr-fr ADML + const frFrAdml = result.adml.find(a => a.languageId === 'fr-fr'); + assert_1.default.ok(frFrAdml, 'fr-fr ADML should exist'); + // Compare the rendered ADML with the fixture + assert_1.default.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture'); + }); +}); +//# sourceMappingURL=policyConversion.test.js.map \ No newline at end of file diff --git a/build/lib/test/policyConversion.test.ts b/build/lib/test/policyConversion.test.ts new file mode 100644 index 00000000000..3be9fd8c10d --- /dev/null +++ b/build/lib/test/policyConversion.test.ts @@ -0,0 +1,495 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { promises as fs } from 'fs'; +import path from 'path'; +import { ExportedPolicyDataDto, CategoryDto } from '../policies/policyDto'; +import { BooleanPolicy } from '../policies/booleanPolicy'; +import { NumberPolicy } from '../policies/numberPolicy'; +import { ObjectPolicy } from '../policies/objectPolicy'; +import { StringEnumPolicy } from '../policies/stringEnumPolicy'; +import { StringPolicy } from '../policies/stringPolicy'; +import { Policy, ProductJson } from '../policies/types'; +import { renderGP, renderMacOSPolicy } from '../policies/render'; + +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + +function parsePolicies(policyData: ExportedPolicyDataDto): Policy[] { + const categories = new Map(); + for (const category of policyData.categories) { + categories.set(category.key, category); + } + + const policies: Policy[] = []; + for (const policy of policyData.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + + policies.push(result); + } + + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + + return policies; +} + +/** + * This is a snapshot of the data taken on Oct. 20 2025 as part of the + * policy refactor effort. Let's make sure that nothing has regressed. + */ +const policies: ExportedPolicyDataDto = { + categories: [ + { + key: 'Extensions', + name: { + key: 'extensionsConfigurationTitle', + value: 'Extensions' + } + }, + { + key: 'IntegratedTerminal', + name: { + key: 'terminalIntegratedConfigurationTitle', + value: 'Integrated Terminal' + } + }, + { + key: 'InteractiveSession', + name: { + key: 'interactiveSessionConfigurationTitle', + value: 'Chat' + } + }, + { + key: 'Telemetry', + name: { + key: 'telemetryConfigurationTitle', + value: 'Telemetry' + } + }, + { + key: 'Update', + name: { + key: 'updateConfigurationTitle', + value: 'Update' + } + } + ], + policies: [ + { + key: 'chat.mcp.gallery.serviceUrl', + name: 'McpGalleryServiceUrl', + category: 'InteractiveSession', + minimumVersion: '1.101', + localization: { + description: { + key: 'mcp.gallery.serviceUrl', + value: 'Configure the MCP Gallery service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.gallery.serviceUrl', + name: 'ExtensionGalleryServiceUrl', + category: 'Extensions', + minimumVersion: '1.99', + localization: { + description: { + key: 'extensions.gallery.serviceUrl', + value: 'Configure the Marketplace service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.allowed', + name: 'AllowedExtensions', + category: 'Extensions', + minimumVersion: '1.96', + localization: { + description: { + key: 'extensions.allowed.policy', + value: 'Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions' + } + }, + type: 'object', + default: '*' + }, + { + key: 'chat.tools.global.autoApprove', + name: 'ChatToolsAutoApprove', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'autoApprove2.description', + value: 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' + } + }, + type: 'boolean', + default: false + }, + { + key: 'chat.mcp.access', + name: 'ChatMCP', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.mcp.access', + value: 'Controls access to installed Model Context Protocol servers.' + }, + enumDescriptions: [ + { + key: 'chat.mcp.access.none', + value: 'No access to MCP servers.' + }, + { + key: 'chat.mcp.access.registry', + value: 'Allows access to MCP servers installed from the registry that VS Code is connected to.' + }, + { + key: 'chat.mcp.access.any', + value: 'Allow access to any installed MCP server.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'none', + 'registry', + 'all' + ] + }, + { + key: 'chat.extensionTools.enabled', + name: 'ChatAgentExtensionTools', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.extensionToolsEnabled', + value: 'Enable using tools contributed by third-party extensions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.agent.enabled', + name: 'ChatAgentMode', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.agent.enabled.description', + value: 'Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.promptFiles', + name: 'ChatPromptFiles', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.promptFiles.policy', + value: 'Enables reusable prompt and instruction files in Chat sessions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.tools.terminal.enableAutoApprove', + name: 'ChatToolsTerminalEnableAutoApprove', + category: 'IntegratedTerminal', + minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: 'Controls whether to allow auto approval in the run in terminal tool.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'update.mode', + name: 'UpdateMode', + category: 'Update', + minimumVersion: '1.67', + localization: { + description: { + key: 'updateMode', + value: 'Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service.' + }, + enumDescriptions: [ + { + key: 'none', + value: 'Disable updates.' + }, + { + key: 'manual', + value: 'Disable automatic background update checks. Updates will be available if you manually check for updates.' + }, + { + key: 'start', + value: 'Check for updates only on startup. Disable automatic background update checks.' + }, + { + key: 'default', + value: 'Enable automatic update checks. Code will check for updates automatically and periodically.' + } + ] + }, + type: 'string', + default: 'default', + enum: [ + 'none', + 'manual', + 'start', + 'default' + ] + }, + { + key: 'telemetry.telemetryLevel', + name: 'TelemetryLevel', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.telemetryLevel.policyDescription', + value: 'Controls the level of telemetry.' + }, + enumDescriptions: [ + { + key: 'telemetry.telemetryLevel.default', + value: 'Sends usage data, errors, and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.error', + value: 'Sends general error telemetry and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.crash', + value: 'Sends OS level crash reports.' + }, + { + key: 'telemetry.telemetryLevel.off', + value: 'Disables all product telemetry.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'all', + 'error', + 'crash', + 'off' + ] + }, + { + key: 'telemetry.feedback.enabled', + name: 'EnableFeedback', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.feedback.enabled', + value: 'Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.' + } + }, + type: 'boolean', + default: true + } + ] +}; + +const mockProduct: ProductJson = { + nameLong: 'Code - OSS', + darwinBundleIdentifier: 'com.visualstudio.code.oss', + darwinProfilePayloadUUID: 'CF808BE7-53F3-46C6-A7E2-7EDB98A5E959', + darwinProfileUUID: '47827DD9-4734-49A0-AF80-7E19B11495CC', + win32RegValueName: 'CodeOSS' +}; + +const frenchTranslations = [ + { + languageId: 'fr-fr', + languageTranslations: { + '': { + 'interactiveSessionConfigurationTitle': 'Session interactive', + 'extensionsConfigurationTitle': 'Extensions', + 'terminalIntegratedConfigurationTitle': 'Terminal intégré', + 'telemetryConfigurationTitle': 'Télémétrie', + 'updateConfigurationTitle': 'Mettre à jour', + 'chat.extensionToolsEnabled': 'Autorisez l’utilisation d’outils fournis par des extensions tierces.', + 'chat.agent.enabled.description': 'Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue.', + 'chat.mcp.access': 'Contrôle l’accès aux serveurs de protocole de contexte du modèle.', + 'chat.mcp.access.none': 'Aucun accès aux serveurs MCP.', + 'chat.mcp.access.registry': `Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté.`, + 'chat.mcp.access.any': 'Autorisez l’accès à tout serveur MCP installé.', + 'chat.promptFiles.policy': 'Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation.', + 'autoApprove2.description': `L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant.`, + 'mcp.gallery.serviceUrl': 'Configurer l’URL du service de la galerie MCP à laquelle se connecter', + 'extensions.allowed.policy': 'Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions', + 'extensions.gallery.serviceUrl': 'Configurer l’URL du service Place de marché à laquelle se connecter', + 'autoApproveMode.description': 'Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal.', + 'telemetry.feedback.enabled': 'Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires.', + 'telemetry.telemetryLevel.policyDescription': 'Contrôle le niveau de télémétrie.', + 'telemetry.telemetryLevel.default': `Envoie les données d'utilisation, les erreurs et les rapports d'erreur.`, + 'telemetry.telemetryLevel.error': `Envoie la télémétrie d'erreur générale et les rapports de plantage.`, + 'telemetry.telemetryLevel.crash': `Envoie des rapports de plantage au niveau du système d'exploitation.`, + 'telemetry.telemetryLevel.off': 'Désactive toutes les données de télémétrie du produit.', + 'updateMode': `Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft.`, + 'none': 'Aucun', + 'manual': 'Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement.', + 'start': 'Démarrer', + 'default': 'Système' + } + } + } +]; + +suite('Policy E2E conversion', () => { + + test('should render macOS policy profile from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'darwin', 'com.visualstudio.code.oss.mobileconfig'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Compare the rendered profile with the fixture + assert.strictEqual(result.profile, expectedContent, 'macOS policy profile should match the fixture'); + }); + + test('should render macOS manifest from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'darwin', 'en-us', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the en-us manifest + const enUsManifest = result.manifests.find(m => m.languageId === 'en-us'); + assert.ok(enUsManifest, 'en-us manifest should exist'); + + // Compare the rendered manifest with the fixture, ignoring the timestamp + // The pfm_last_modified field contains a timestamp that will differ each time + const normalizeTimestamp = (content: string) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert.strictEqual( + normalizeTimestamp(enUsManifest.contents), + normalizeTimestamp(expectedContent), + 'macOS manifest should match the fixture (ignoring timestamp)' + ); + }); + + test('should render Windows ADMX from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'win32', 'CodeOSS.admx'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Compare the rendered ADMX with the fixture + assert.strictEqual(result.admx, expectedContent, 'Windows ADMX should match the fixture'); + }); + + test('should render Windows ADML from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'win32', 'en-us', 'CodeOSS.adml'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the en-us ADML + const enUsAdml = result.adml.find(a => a.languageId === 'en-us'); + assert.ok(enUsAdml, 'en-us ADML should exist'); + + // Compare the rendered ADML with the fixture + assert.strictEqual(enUsAdml.contents, expectedContent, 'Windows ADML should match the fixture'); + }); + + test('should render macOS manifest with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, frenchTranslations); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'darwin', 'fr-fr', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the fr-fr manifest + const frFrManifest = result.manifests.find(m => m.languageId === 'fr-fr'); + assert.ok(frFrManifest, 'fr-fr manifest should exist'); + + // Compare the rendered manifest with the fixture, ignoring the timestamp + const normalizeTimestamp = (content: string) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert.strictEqual( + normalizeTimestamp(frFrManifest.contents), + normalizeTimestamp(expectedContent), + 'macOS fr-fr manifest should match the fixture (ignoring timestamp)' + ); + }); + + test('should render Windows ADML with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, frenchTranslations); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'win32', 'fr-fr', 'CodeOSS.adml'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the fr-fr ADML + const frFrAdml = result.adml.find(a => a.languageId === 'fr-fr'); + assert.ok(frFrAdml, 'fr-fr ADML should exist'); + + // Compare the rendered ADML with the fixture + assert.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture'); + }); + +}); diff --git a/build/lib/test/render.test.js b/build/lib/test/render.test.js new file mode 100644 index 00000000000..005460c6204 --- /dev/null +++ b/build/lib/test/render.test.js @@ -0,0 +1,683 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const render_js_1 = require("../policies/render.js"); +const types_js_1 = require("../policies/types.js"); +suite('Render Functions', () => { + suite('renderADMLString', () => { + test('should render ADML string without translations', () => { + const nlsString = { + value: 'Test description', + nlsKey: 'test.description' + }; + const result = (0, render_js_1.renderADMLString)('TestPrefix', 'testModule', nlsString); + assert_1.default.strictEqual(result, 'Test description'); + }); + test('should replace dots with underscores in nls key', () => { + const nlsString = { + value: 'Test value', + nlsKey: 'my.test.nls.key' + }; + const result = (0, render_js_1.renderADMLString)('Prefix', 'testModule', nlsString); + assert_1.default.ok(result.includes('id="Prefix_my_test_nls_key"')); + }); + test('should use translation when available', () => { + const nlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + const translations = { + 'testModule': { + 'test.key': 'Translated value' + } + }; + const result = (0, render_js_1.renderADMLString)('TestPrefix', 'testModule', nlsString, translations); + assert_1.default.ok(result.includes('>Translated value')); + }); + test('should fallback to original value when translation not found', () => { + const nlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + const translations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + const result = (0, render_js_1.renderADMLString)('TestPrefix', 'testModule', nlsString, translations); + assert_1.default.ok(result.includes('>Original value')); + }); + }); + suite('renderProfileString', () => { + test('should render profile string without translations', () => { + const nlsString = { + value: 'Profile description', + nlsKey: 'profile.description' + }; + const result = (0, render_js_1.renderProfileString)('ProfilePrefix', 'testModule', nlsString); + assert_1.default.strictEqual(result, 'Profile description'); + }); + test('should use translation when available', () => { + const nlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + const translations = { + 'testModule': { + 'profile.key': 'Translated profile value' + } + }; + const result = (0, render_js_1.renderProfileString)('ProfilePrefix', 'testModule', nlsString, translations); + assert_1.default.strictEqual(result, 'Translated profile value'); + }); + test('should fallback to original value when translation not found', () => { + const nlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + const translations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + const result = (0, render_js_1.renderProfileString)('ProfilePrefix', 'testModule', nlsString, translations); + assert_1.default.strictEqual(result, 'Original profile value'); + }); + }); + suite('renderADMX', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + const mockPolicy = { + name: 'TestPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy' + }; + test('should render ADMX with correct XML structure', () => { + const result = (0, render_js_1.renderADMX)('VSCode', ['1.85'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + }); + test('should include policy namespaces with regKey', () => { + const result = (0, render_js_1.renderADMX)('TestApp', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes(' { + const result = (0, render_js_1.renderADMX)('VSCode', ['1.85.0', '1.90.1'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('Supported_1_85_0')); + assert_1.default.ok(result.includes('Supported_1_90_1')); + assert_1.default.ok(!result.includes('Supported_1.85.0')); + }); + test('should include categories in correct structure', () => { + const result = (0, render_js_1.renderADMX)('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + }); + test('should include policies section', () => { + const result = (0, render_js_1.renderADMX)('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('TestPolicy')); + assert_1.default.ok(result.includes('')); + }); + test('should handle multiple versions', () => { + const result = (0, render_js_1.renderADMX)('VSCode', ['1.0', '1.5', '2.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('Supported_1_0')); + assert_1.default.ok(result.includes('Supported_1_5')); + assert_1.default.ok(result.includes('Supported_2_0')); + }); + test('should handle multiple categories', () => { + const category1 = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2 = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + const result = (0, render_js_1.renderADMX)('VSCode', ['1.0'], [category1, category2], [mockPolicy]); + assert_1.default.ok(result.includes('Category_cat1')); + assert_1.default.ok(result.includes('Category_cat2')); + }); + test('should handle multiple policies', () => { + const policy2 = { + name: 'TestPolicy2', + type: types_js_1.PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy2', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy2' + }; + const result = (0, render_js_1.renderADMX)('VSCode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + assert_1.default.ok(result.includes('TestPolicy')); + assert_1.default.ok(result.includes('TestPolicy2')); + }); + }); + suite('renderADML', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + const mockPolicy = { + name: 'TestPolicy', + type: types_js_1.PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: () => [], + renderADMLStrings: (translations) => [ + `Test Policy ${translations?.['testModule']?.['test.policy'] || 'Default'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '' + }; + test('should render ADML with correct XML structure', () => { + const result = (0, render_js_1.renderADML)('VS Code', ['1.85'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + }); + test('should include application name', () => { + const result = (0, render_js_1.renderADML)('My Application', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('My Application')); + }); + test('should include supported versions with escaped greater-than', () => { + const result = (0, render_js_1.renderADML)('VS Code', ['1.85', '1.90'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('VS Code >= 1.85')); + assert_1.default.ok(result.includes('VS Code >= 1.90')); + }); + test('should include category strings', () => { + const result = (0, render_js_1.renderADML)('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('Category_test_category')); + }); + test('should include policy strings', () => { + const result = (0, render_js_1.renderADML)('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('TestPolicy')); + assert_1.default.ok(result.includes('Test Policy Default')); + }); + test('should include policy presentations', () => { + const result = (0, render_js_1.renderADML)('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + }); + test('should pass translations to policy strings', () => { + const translations = { + 'testModule': { + 'test.policy': 'Translated' + } + }; + const result = (0, render_js_1.renderADML)('VS Code', ['1.0'], [mockCategory], [mockPolicy], translations); + assert_1.default.ok(result.includes('Test Policy Translated')); + }); + test('should handle multiple categories', () => { + const category1 = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2 = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + const result = (0, render_js_1.renderADML)('VS Code', ['1.0'], [category1, category2], [mockPolicy]); + assert_1.default.ok(result.includes('Category_cat1')); + assert_1.default.ok(result.includes('Category_cat2')); + }); + }); + suite('renderProfileManifest', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + const mockPolicy = { + name: 'TestPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: (translations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +` + }; + test('should render profile manifest with correct XML structure', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + assert_1.default.ok(result.includes('')); + }); + test('should include app name', () => { + const result = (0, render_js_1.renderProfileManifest)('My App', 'com.example.myapp', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('My App Managed Settings')); + assert_1.default.ok(result.includes('My App')); + }); + test('should include bundle identifier', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('com.microsoft.vscode')); + }); + test('should include required payload fields', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('PayloadDescription')); + assert_1.default.ok(result.includes('PayloadDisplayName')); + assert_1.default.ok(result.includes('PayloadIdentifier')); + assert_1.default.ok(result.includes('PayloadType')); + assert_1.default.ok(result.includes('PayloadUUID')); + assert_1.default.ok(result.includes('PayloadVersion')); + assert_1.default.ok(result.includes('PayloadOrganization')); + }); + test('should include policy manifests in subkeys', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_subkeys')); + assert_1.default.ok(result.includes('TestPolicy')); + assert_1.default.ok(result.includes('Default Desc')); + }); + test('should pass translations to policy manifests', () => { + const translations = { + 'testModule': { + 'test.desc': 'Translated Description' + } + }; + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy], translations); + assert_1.default.ok(result.includes('Translated Description')); + }); + test('should include VS Code specific URLs', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('https://code.visualstudio.com/')); + assert_1.default.ok(result.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + test('should include last modified date', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_last_modified')); + assert_1.default.ok(result.includes('')); + }); + test('should mark manifest as unique', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_unique')); + assert_1.default.ok(result.includes('')); + }); + test('should handle multiple policies', () => { + const policy2 = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfileManifest: () => ` +pfm_name +TestPolicy2 +` + }; + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + assert_1.default.ok(result.includes('TestPolicy')); + assert_1.default.ok(result.includes('TestPolicy2')); + }); + test('should set format version to 1', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_format_version')); + assert_1.default.ok(result.includes('1')); + }); + test('should set interaction to combined', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_interaction')); + assert_1.default.ok(result.includes('combined')); + }); + test('should set platform to macOS', () => { + const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + assert_1.default.ok(result.includes('pfm_platforms')); + assert_1.default.ok(result.includes('macOS')); + }); + }); + suite('renderMacOSPolicy', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + const mockPolicy = { + name: 'TestPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: (translations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +` + }; + test('should render complete macOS policy profile', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], []); + const expected = ` + + + + PayloadContent + + + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode.uuid + PayloadType + com.microsoft.vscode + PayloadUUID + uuid + PayloadVersion + 1 + TestPolicy + + + + PayloadDescription + This profile manages VS Code. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + payload-uuid + PayloadVersion + 1 + TargetDeviceType + 5 + +`; + assert_1.default.strictEqual(result.profile, expected); + }); + test('should include en-us manifest by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], []); + assert_1.default.strictEqual(result.manifests.length, 1); + assert_1.default.strictEqual(result.manifests[0].languageId, 'en-us'); + assert_1.default.ok(result.manifests[0].contents.includes('VS Code Managed Settings')); + }); + test('should include translations', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.desc': 'Description Française' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.desc': 'Deutsche Beschreibung' } } } + ]; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], translations); + assert_1.default.strictEqual(result.manifests.length, 3); // en-us + 2 translations + assert_1.default.strictEqual(result.manifests[0].languageId, 'en-us'); + assert_1.default.strictEqual(result.manifests[1].languageId, 'fr-fr'); + assert_1.default.strictEqual(result.manifests[2].languageId, 'de-de'); + assert_1.default.ok(result.manifests[1].contents.includes('Description Française')); + assert_1.default.ok(result.manifests[2].contents.includes('Deutsche Beschreibung')); + }); + test('should handle multiple policies with correct indentation', () => { + const policy2 = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfile: () => ['TestPolicy2', 'test value'] + }; + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy, policy2], []); + assert_1.default.ok(result.profile.includes('TestPolicy')); + assert_1.default.ok(result.profile.includes('')); + assert_1.default.ok(result.profile.includes('TestPolicy2')); + assert_1.default.ok(result.profile.includes('test value')); + }); + test('should use provided UUIDs in profile', () => { + const product = { + nameLong: 'My App', + darwinBundleIdentifier: 'com.example.app', + darwinProfilePayloadUUID: 'custom-payload-uuid', + darwinProfileUUID: 'custom-uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], []); + assert_1.default.ok(result.profile.includes('custom-payload-uuid')); + assert_1.default.ok(result.profile.includes('custom-uuid')); + assert_1.default.ok(result.profile.includes('com.example.app.custom-uuid')); + }); + test('should include enterprise documentation link', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], []); + assert_1.default.ok(result.profile.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + test('should set TargetDeviceType to 5', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderMacOSPolicy)(product, [mockPolicy], []); + assert_1.default.ok(result.profile.includes('TargetDeviceType')); + assert_1.default.ok(result.profile.includes('5')); + }); + }); + suite('renderGP', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + const mockPolicy = { + name: 'TestPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey) => [ + ``, + ` `, + `` + ], + renderADMLStrings: (translations) => [ + `${translations?.['testModule']?.['test.policy'] || 'Test Policy'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '' + }; + test('should render complete GP with ADMX and ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.admx); + assert_1.default.ok(result.adml); + assert_1.default.ok(Array.isArray(result.adml)); + }); + test('should include regKey in ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'CustomRegKey' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.admx.includes('CustomRegKey')); + assert_1.default.ok(result.admx.includes('Software\\Policies\\Microsoft\\CustomRegKey')); + }); + test('should include en-us ADML by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.strictEqual(result.adml.length, 1); + assert_1.default.strictEqual(result.adml[0].languageId, 'en-us'); + assert_1.default.ok(result.adml[0].contents.includes('VS Code')); + }); + test('should include translations in ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.policy': 'Politique de test' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.policy': 'Testrichtlinie' } } } + ]; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], translations); + assert_1.default.strictEqual(result.adml.length, 3); // en-us + 2 translations + assert_1.default.strictEqual(result.adml[0].languageId, 'en-us'); + assert_1.default.strictEqual(result.adml[1].languageId, 'fr-fr'); + assert_1.default.strictEqual(result.adml[2].languageId, 'de-de'); + assert_1.default.ok(result.adml[1].contents.includes('Politique de test')); + assert_1.default.ok(result.adml[2].contents.includes('Testrichtlinie')); + }); + test('should pass versions to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.admx.includes('Supported_1_85')); + }); + test('should pass versions to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.adml[0].contents.includes('VS Code >= 1.85')); + }); + test('should pass categories to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.admx.includes('test.category')); + }); + test('should pass categories to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.adml[0].contents.includes('Category_test_category')); + }); + test('should handle multiple policies', () => { + const policy2 = { + ...mockPolicy, + name: 'TestPolicy2', + renderADMX: (regKey) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'] + }; + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy, policy2], []); + assert_1.default.ok(result.admx.includes('TestPolicy')); + assert_1.default.ok(result.admx.includes('TestPolicy2')); + assert_1.default.ok(result.adml[0].contents.includes('TestPolicy')); + assert_1.default.ok(result.adml[0].contents.includes('TestPolicy2')); + }); + test('should include app name in ADML', () => { + const product = { + nameLong: 'My Custom App', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok(result.adml[0].contents.includes('My Custom App')); + }); + test('should return structured result with admx and adml properties', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = (0, render_js_1.renderGP)(product, [mockPolicy], []); + assert_1.default.ok('admx' in result); + assert_1.default.ok('adml' in result); + assert_1.default.strictEqual(typeof result.admx, 'string'); + assert_1.default.ok(Array.isArray(result.adml)); + }); + }); +}); +//# sourceMappingURL=render.test.js.map \ No newline at end of file diff --git a/build/lib/test/render.test.ts b/build/lib/test/render.test.ts new file mode 100644 index 00000000000..cbe24ba0725 --- /dev/null +++ b/build/lib/test/render.test.ts @@ -0,0 +1,827 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { renderADMLString, renderProfileString, renderADMX, renderADML, renderProfileManifest, renderMacOSPolicy, renderGP } from '../policies/render.js'; +import { NlsString, LanguageTranslations, Category, Policy, PolicyType } from '../policies/types.js'; + +suite('Render Functions', () => { + + suite('renderADMLString', () => { + + test('should render ADML string without translations', () => { + const nlsString: NlsString = { + value: 'Test description', + nlsKey: 'test.description' + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString); + + assert.strictEqual(result, 'Test description'); + }); + + test('should replace dots with underscores in nls key', () => { + const nlsString: NlsString = { + value: 'Test value', + nlsKey: 'my.test.nls.key' + }; + + const result = renderADMLString('Prefix', 'testModule', nlsString); + + assert.ok(result.includes('id="Prefix_my_test_nls_key"')); + }); + + test('should use translation when available', () => { + const nlsString: NlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'test.key': 'Translated value' + } + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString, translations); + + assert.ok(result.includes('>Translated value')); + }); + + test('should fallback to original value when translation not found', () => { + const nlsString: NlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString, translations); + + assert.ok(result.includes('>Original value')); + }); + }); + + suite('renderProfileString', () => { + + test('should render profile string without translations', () => { + const nlsString: NlsString = { + value: 'Profile description', + nlsKey: 'profile.description' + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString); + + assert.strictEqual(result, 'Profile description'); + }); + + test('should use translation when available', () => { + const nlsString: NlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'profile.key': 'Translated profile value' + } + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString, translations); + + assert.strictEqual(result, 'Translated profile value'); + }); + + test('should fallback to original value when translation not found', () => { + const nlsString: NlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString, translations); + + assert.strictEqual(result, 'Original profile value'); + }); + }); + + suite('renderADMX', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy' + }; + + test('should render ADMX with correct XML structure', () => { + const result = renderADMX('VSCode', ['1.85'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include policy namespaces with regKey', () => { + const result = renderADMX('TestApp', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes(' { + const result = renderADMX('VSCode', ['1.85.0', '1.90.1'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Supported_1_85_0')); + assert.ok(result.includes('Supported_1_90_1')); + assert.ok(!result.includes('Supported_1.85.0')); + }); + + test('should include categories in correct structure', () => { + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include policies section', () => { + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('')); + }); + + test('should handle multiple versions', () => { + const result = renderADMX('VSCode', ['1.0', '1.5', '2.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Supported_1_0')); + assert.ok(result.includes('Supported_1_5')); + assert.ok(result.includes('Supported_2_0')); + }); + + test('should handle multiple categories', () => { + const category1: Category = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2: Category = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + + const result = renderADMX('VSCode', ['1.0'], [category1, category2], [mockPolicy]); + + assert.ok(result.includes('Category_cat1')); + assert.ok(result.includes('Category_cat2')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + name: 'TestPolicy2', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy2', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy2' + }; + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('TestPolicy2')); + }); + }); + + suite('renderADML', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: () => [], + renderADMLStrings: (translations?: LanguageTranslations) => [ + `Test Policy ${translations?.['testModule']?.['test.policy'] || 'Default'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '' + }; + + test('should render ADML with correct XML structure', () => { + const result = renderADML('VS Code', ['1.85'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include application name', () => { + const result = renderADML('My Application', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('My Application')); + }); + + test('should include supported versions with escaped greater-than', () => { + const result = renderADML('VS Code', ['1.85', '1.90'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('VS Code >= 1.85')); + assert.ok(result.includes('VS Code >= 1.90')); + }); + + test('should include category strings', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Category_test_category')); + }); + + test('should include policy strings', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('Test Policy Default')); + }); + + test('should include policy presentations', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should pass translations to policy strings', () => { + const translations: LanguageTranslations = { + 'testModule': { + 'test.policy': 'Translated' + } + }; + + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy], translations); + + assert.ok(result.includes('Test Policy Translated')); + }); + + test('should handle multiple categories', () => { + const category1: Category = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2: Category = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + + const result = renderADML('VS Code', ['1.0'], [category1, category2], [mockPolicy]); + + assert.ok(result.includes('Category_cat1')); + assert.ok(result.includes('Category_cat2')); + }); + }); + + suite('renderProfileManifest', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: (translations?: LanguageTranslations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +` + }; + + test('should render profile manifest with correct XML structure', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include app name', () => { + const result = renderProfileManifest('My App', 'com.example.myapp', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('My App Managed Settings')); + assert.ok(result.includes('My App')); + }); + + test('should include bundle identifier', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('com.microsoft.vscode')); + }); + + test('should include required payload fields', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('PayloadDescription')); + assert.ok(result.includes('PayloadDisplayName')); + assert.ok(result.includes('PayloadIdentifier')); + assert.ok(result.includes('PayloadType')); + assert.ok(result.includes('PayloadUUID')); + assert.ok(result.includes('PayloadVersion')); + assert.ok(result.includes('PayloadOrganization')); + }); + + test('should include policy manifests in subkeys', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_subkeys')); + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('Default Desc')); + }); + + test('should pass translations to policy manifests', () => { + const translations: LanguageTranslations = { + 'testModule': { + 'test.desc': 'Translated Description' + } + }; + + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy], translations); + + assert.ok(result.includes('Translated Description')); + }); + + test('should include VS Code specific URLs', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('https://code.visualstudio.com/')); + assert.ok(result.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + + test('should include last modified date', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_last_modified')); + assert.ok(result.includes('')); + }); + + test('should mark manifest as unique', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_unique')); + assert.ok(result.includes('')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfileManifest: () => ` +pfm_name +TestPolicy2 +` + }; + + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('TestPolicy2')); + }); + + test('should set format version to 1', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_format_version')); + assert.ok(result.includes('1')); + }); + + test('should set interaction to combined', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_interaction')); + assert.ok(result.includes('combined')); + }); + + test('should set platform to macOS', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_platforms')); + assert.ok(result.includes('macOS')); + }); + }); + + suite('renderMacOSPolicy', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: (translations?: LanguageTranslations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +` + }; + + test('should render complete macOS policy profile', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + const expected = ` + + + + PayloadContent + + + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode.uuid + PayloadType + com.microsoft.vscode + PayloadUUID + uuid + PayloadVersion + 1 + TestPolicy + + + + PayloadDescription + This profile manages VS Code. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + payload-uuid + PayloadVersion + 1 + TargetDeviceType + 5 + +`; + + assert.strictEqual(result.profile, expected); + }); + + test('should include en-us manifest by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.strictEqual(result.manifests.length, 1); + assert.strictEqual(result.manifests[0].languageId, 'en-us'); + assert.ok(result.manifests[0].contents.includes('VS Code Managed Settings')); + }); + + test('should include translations', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.desc': 'Description Française' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.desc': 'Deutsche Beschreibung' } } } + ]; + + const result = renderMacOSPolicy(product, [mockPolicy], translations); + + assert.strictEqual(result.manifests.length, 3); // en-us + 2 translations + assert.strictEqual(result.manifests[0].languageId, 'en-us'); + assert.strictEqual(result.manifests[1].languageId, 'fr-fr'); + assert.strictEqual(result.manifests[2].languageId, 'de-de'); + + assert.ok(result.manifests[1].contents.includes('Description Française')); + assert.ok(result.manifests[2].contents.includes('Deutsche Beschreibung')); + }); + + test('should handle multiple policies with correct indentation', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfile: () => ['TestPolicy2', 'test value'] + }; + + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy, policy2], []); + + assert.ok(result.profile.includes('TestPolicy')); + assert.ok(result.profile.includes('')); + assert.ok(result.profile.includes('TestPolicy2')); + assert.ok(result.profile.includes('test value')); + }); + + test('should use provided UUIDs in profile', () => { + const product = { + nameLong: 'My App', + darwinBundleIdentifier: 'com.example.app', + darwinProfilePayloadUUID: 'custom-payload-uuid', + darwinProfileUUID: 'custom-uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('custom-payload-uuid')); + assert.ok(result.profile.includes('custom-uuid')); + assert.ok(result.profile.includes('com.example.app.custom-uuid')); + }); + + test('should include enterprise documentation link', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + + test('should set TargetDeviceType to 5', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('TargetDeviceType')); + assert.ok(result.profile.includes('5')); + }); + }); + + suite('renderGP', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: (translations?: LanguageTranslations) => [ + `${translations?.['testModule']?.['test.policy'] || 'Test Policy'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '' + }; + + test('should render complete GP with ADMX and ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx); + assert.ok(result.adml); + assert.ok(Array.isArray(result.adml)); + }); + + test('should include regKey in ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'CustomRegKey' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('CustomRegKey')); + assert.ok(result.admx.includes('Software\\Policies\\Microsoft\\CustomRegKey')); + }); + + test('should include en-us ADML by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.strictEqual(result.adml.length, 1); + assert.strictEqual(result.adml[0].languageId, 'en-us'); + assert.ok(result.adml[0].contents.includes('VS Code')); + }); + + test('should include translations in ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.policy': 'Politique de test' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.policy': 'Testrichtlinie' } } } + ]; + + const result = renderGP(product, [mockPolicy], translations); + + assert.strictEqual(result.adml.length, 3); // en-us + 2 translations + assert.strictEqual(result.adml[0].languageId, 'en-us'); + assert.strictEqual(result.adml[1].languageId, 'fr-fr'); + assert.strictEqual(result.adml[2].languageId, 'de-de'); + + assert.ok(result.adml[1].contents.includes('Politique de test')); + assert.ok(result.adml[2].contents.includes('Testrichtlinie')); + }); + + test('should pass versions to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('Supported_1_85')); + }); + + test('should pass versions to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('VS Code >= 1.85')); + }); + + test('should pass categories to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('test.category')); + }); + + test('should pass categories to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('Category_test_category')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'] + }; + + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy, policy2], []); + + assert.ok(result.admx.includes('TestPolicy')); + assert.ok(result.admx.includes('TestPolicy2')); + assert.ok(result.adml[0].contents.includes('TestPolicy')); + assert.ok(result.adml[0].contents.includes('TestPolicy2')); + }); + + test('should include app name in ADML', () => { + const product = { + nameLong: 'My Custom App', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('My Custom App')); + }); + + test('should return structured result with admx and adml properties', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok('admx' in result); + assert.ok('adml' in result); + assert.strictEqual(typeof result.admx, 'string'); + assert.ok(Array.isArray(result.adml)); + }); + }); +}); diff --git a/build/lib/test/stringEnumPolicy.test.js b/build/lib/test/stringEnumPolicy.test.js new file mode 100644 index 00000000000..8b45b754c0c --- /dev/null +++ b/build/lib/test/stringEnumPolicy.test.js @@ -0,0 +1,136 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const stringEnumPolicy_js_1 = require("../policies/stringEnumPolicy.js"); +const types_js_1 = require("../policies/types.js"); +suite('StringEnumPolicy', () => { + const mockCategory = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + const mockPolicy = { + key: 'test.stringenum.policy', + name: 'TestStringEnumPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' }, + enumDescriptions: [ + { key: 'test.option.one', value: 'Option One' }, + { key: 'test.option.two', value: 'Option Two' }, + { key: 'test.option.three', value: 'Option Three' } + ] + }, + enum: ['auto', 'manual', 'disabled'] + }; + test('should create StringEnumPolicy from factory method', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + assert_1.default.strictEqual(policy.name, 'TestStringEnumPolicy'); + assert_1.default.strictEqual(policy.minimumVersion, '1.0'); + assert_1.default.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert_1.default.strictEqual(policy.category.name.value, mockCategory.name.value); + assert_1.default.strictEqual(policy.type, types_js_1.PolicyType.StringEnum); + }); + test('should render ADMX elements correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admx = policy.renderADMX('TestKey'); + assert_1.default.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\tauto', + '\tmanual', + '\tdisabled', + '', + '\t', + '' + ]); + }); + test('should render ADML strings correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admlStrings = policy.renderADMLStrings(); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Test policy description', + 'Option One', + 'Option Two', + 'Option Three' + ]); + }); + test('should render ADML strings with translations', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated description', + 'test.option.one': 'Translated Option One', + 'test.option.two': 'Translated Option Two' + } + }; + const admlStrings = policy.renderADMLStrings(translations); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Translated description', + 'Translated Option One', + 'Translated Option Two', + 'Option Three' + ]); + }); + test('should render ADML presentation correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const presentation = policy.renderADMLPresentation(); + assert_1.default.strictEqual(presentation, ''); + }); + test('should render profile value correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profileValue = policy.renderProfileValue(); + assert_1.default.strictEqual(profileValue, 'auto'); + }); + test('should render profile correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profile = policy.renderProfile(); + assert_1.default.strictEqual(profile.length, 2); + assert_1.default.strictEqual(profile[0], 'TestStringEnumPolicy'); + assert_1.default.strictEqual(profile[1], 'auto'); + }); + test('should render profile manifest value correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifestValue = policy.renderProfileManifestValue(); + assert_1.default.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + test('should render profile manifest value with translations', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + const manifestValue = policy.renderProfileManifestValue(translations); + assert_1.default.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTranslated manifest description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + test('should render profile manifest correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifest = policy.renderProfileManifest(); + assert_1.default.strictEqual(manifest, '\npfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n\n'); + }); +}); +//# sourceMappingURL=stringEnumPolicy.test.js.map \ No newline at end of file diff --git a/build/lib/test/stringEnumPolicy.test.ts b/build/lib/test/stringEnumPolicy.test.ts new file mode 100644 index 00000000000..feac73ed44f --- /dev/null +++ b/build/lib/test/stringEnumPolicy.test.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { StringEnumPolicy } from '../policies/stringEnumPolicy.js'; +import { LanguageTranslations, PolicyType } from '../policies/types.js'; +import { CategoryDto, PolicyDto } from '../policies/policyDto.js'; + +suite('StringEnumPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.stringenum.policy', + name: 'TestStringEnumPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' }, + enumDescriptions: [ + { key: 'test.option.one', value: 'Option One' }, + { key: 'test.option.two', value: 'Option Two' }, + { key: 'test.option.three', value: 'Option Three' } + ] + }, + enum: ['auto', 'manual', 'disabled'] + }; + + test('should create StringEnumPolicy from factory method', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestStringEnumPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.StringEnum); + }); + + test('should render ADMX elements correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\tauto', + '\tmanual', + '\tdisabled', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Test policy description', + 'Option One', + 'Option Two', + 'Option Three' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description', + 'test.option.one': 'Translated Option One', + 'test.option.two': 'Translated Option Two' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Translated description', + 'Translated Option One', + 'Translated Option Two', + 'Option Three' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render profile value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, 'auto'); + }); + + test('should render profile correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestStringEnumPolicy'); + assert.strictEqual(profile[1], 'auto'); + }); + + test('should render profile manifest value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + + test('should render profile manifest value with translations', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTranslated manifest description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + + test('should render profile manifest correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n\n'); + }); +}); diff --git a/build/lib/test/stringPolicy.test.js b/build/lib/test/stringPolicy.test.js new file mode 100644 index 00000000000..75d6365878c --- /dev/null +++ b/build/lib/test/stringPolicy.test.js @@ -0,0 +1,119 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const assert_1 = __importDefault(require("assert")); +const stringPolicy_js_1 = require("../policies/stringPolicy.js"); +const types_js_1 = require("../policies/types.js"); +suite('StringPolicy', () => { + const mockCategory = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + const mockPolicy = { + key: 'test.string.policy', + name: 'TestStringPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + default: '', + localization: { + description: { key: 'test.policy.description', value: 'Test string policy description' } + } + }; + test('should create StringPolicy from factory method', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + assert_1.default.strictEqual(policy.name, 'TestStringPolicy'); + assert_1.default.strictEqual(policy.minimumVersion, '1.0'); + assert_1.default.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert_1.default.strictEqual(policy.category.name.value, mockCategory.name.value); + assert_1.default.strictEqual(policy.type, types_js_1.PolicyType.String); + }); + test('should render ADMX elements correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admx = policy.renderADMX('TestKey'); + assert_1.default.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + test('should render ADML strings correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const admlStrings = policy.renderADMLStrings(); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Test string policy description' + ]); + }); + test('should render ADML strings with translations', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + const admlStrings = policy.renderADMLStrings(translations); + assert_1.default.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Translated description' + ]); + }); + test('should render ADML presentation correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const presentation = policy.renderADMLPresentation(); + assert_1.default.strictEqual(presentation, ''); + }); + test('should render profile value correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profileValue = policy.renderProfileValue(); + assert_1.default.strictEqual(profileValue, ''); + }); + test('should render profile correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const profile = policy.renderProfile(); + assert_1.default.strictEqual(profile.length, 2); + assert_1.default.strictEqual(profile[0], 'TestStringPolicy'); + assert_1.default.strictEqual(profile[1], ''); + }); + test('should render profile manifest value correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifestValue = policy.renderProfileManifestValue(); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + test('should render profile manifest value with translations', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const translations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + const manifestValue = policy.renderProfileManifestValue(translations); + assert_1.default.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + test('should render profile manifest correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const manifest = policy.renderProfileManifest(); + assert_1.default.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring\n'); + }); +}); +//# sourceMappingURL=stringPolicy.test.js.map \ No newline at end of file diff --git a/build/lib/test/stringPolicy.test.ts b/build/lib/test/stringPolicy.test.ts new file mode 100644 index 00000000000..ae692d82bbe --- /dev/null +++ b/build/lib/test/stringPolicy.test.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { StringPolicy } from '../policies/stringPolicy.js'; +import { LanguageTranslations, PolicyType } from '../policies/types.js'; +import { CategoryDto, PolicyDto } from '../policies/policyDto.js'; + +suite('StringPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.string.policy', + name: 'TestStringPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + default: '', + localization: { + description: { key: 'test.policy.description', value: 'Test string policy description' } + } + }; + + test('should create StringPolicy from factory method', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestStringPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.String); + }); + + test('should render ADMX elements correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Test string policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render profile value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestStringPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + + test('should render profile manifest value with translations', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + + test('should render profile manifest correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring\n'); + }); +}); diff --git a/build/package.json b/build/package.json index ef715fbfb6a..fedaea5e271 100644 --- a/build/package.json +++ b/build/package.json @@ -69,7 +69,8 @@ "build-ts": "cd .. && npx tsgo --project build/tsconfig.build.json", "compile": "npm run build-ts", "watch": "npm run build-ts -- --watch", - "npmCheckJs": "npm run build-ts -- --noEmit" + "npmCheckJs": "npm run build-ts -- --noEmit", + "test": "npx mocha --ui tdd 'lib/**/*.test.js'" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", From a8907697a59f540617f8f215dd38b35ae9a5838a Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Tue, 21 Oct 2025 18:15:34 -0700 Subject: [PATCH 1436/4355] Enabled navigating to previous and next change in multi-file diff editor. --- .../multiDiffEditor/diffEditorItemTemplate.ts | 1 + .../multiDiffEditor/multiDiffEditorWidget.ts | 8 + .../multiDiffEditorWidgetImpl.ts | 158 ++++++++++++++++++ src/vs/editor/common/editorContextKeys.ts | 1 + .../multiDiffEditor/browser/actions.ts | 70 ++++++++ .../browser/multiDiffEditor.contribution.ts | 4 +- .../browser/multiDiffEditor.ts | 20 +++ 7 files changed, 261 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 7461b28f2b4..2e599aff775 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -223,6 +223,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< renderOverviewRuler: false, fixedOverflowWidgets: true, overviewRulerBorder: false, + renderGutterMenu: true, }; } diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index 9c49808167e..e2fd4edea20 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -85,6 +85,14 @@ export class MultiDiffEditorWidget extends Disposable { public findDocumentDiffItem(resource: URI): IDocumentDiffItem | undefined { return this._widgetImpl.get().findDocumentDiffItem(resource); } + + public goToNextChange(): void { + this._widgetImpl.get().goToNextChange(); + } + + public goToPreviousChange(): void { + this._widgetImpl.get().goToPreviousChange(); + } } export interface RevealOptions { diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 288d0bd8678..fc5b2e2970d 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -218,6 +218,29 @@ export class MultiDiffEditorWidgetImpl extends Disposable { _element.replaceChildren(); })); + // Automatically select the first change in the first file when items are loaded + this._register(autorun(reader => { + /** @description Initialize first change */ + const viewModel = this._viewModel.read(reader); + if (!viewModel) { + return; + } + + const items = viewModel.items.read(reader); + if (items.length === 0) { + return; + } + + // Only initialize if there's no active item yet + const activeDiffItem = viewModel.activeDiffItem.read(reader); + if (activeDiffItem) { + return; + } + + // Navigate to the first change using the existing navigation logic + this.goToNextChange(); + })); + this._register(this._register(autorun(reader => { /** @description Render all */ globalTransaction(tx => { @@ -312,6 +335,141 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } + public goToNextChange(): void { + this._navigateToChange('next'); + } + + public goToPreviousChange(): void { + this._navigateToChange('previous'); + } + + private _navigateToChange(direction: 'next' | 'previous'): void { + const viewItems = this._viewItems.get(); + if (viewItems.length === 0) { + return; + } + + // Get the currently active diff item + const activeViewModel = this._viewModel.get()?.activeDiffItem.get(); + if (!activeViewModel) { + // No active item, go to first change in first file + const firstItem = viewItems[0]; + if (firstItem.viewModel.collapsed.get()) { + firstItem.viewModel.collapsed.set(false, undefined); + } + this._viewModel.get()?.activeDiffItem.setCache(firstItem.viewModel, undefined); + const template = firstItem.template.get(); + if (template) { + template.editor.revealFirstDiff(); + } + return; + } + + // Find the active view item and its index + const activeViewItemIndex = viewItems.findIndex(v => v.viewModel === activeViewModel); + if (activeViewItemIndex === -1) { + return; + } + + const activeViewItem = viewItems[activeViewItemIndex]; + const template = activeViewItem.template.get(); + if (!template) { + return; + } + + const diffEditor = template.editor; + const diffModel = diffEditor.getDiffComputationResult(); + + if (!diffModel || !diffModel.changes2 || diffModel.changes2.length === 0) { + // No changes in current file, move to next/previous file + this.moveToAdjacentFile(direction, activeViewItemIndex, viewItems); + return; + } + + // Check if we're at a boundary (first/last change in the file) + const modifiedEditor = diffEditor.getModifiedEditor(); + const currentLineNumber = modifiedEditor.getPosition()?.lineNumber ?? 1; + const changes = diffModel.changes2; + + // Find which change we're currently at or near + let isAtBoundary = false; + + if (direction === 'next') { + // Check if we're at or past the last change + const lastChange = changes[changes.length - 1]; + const lastChangeStartLine = lastChange.modified.startLineNumber; + // If we're at or past the start of the last change, we're at the boundary + isAtBoundary = currentLineNumber >= lastChangeStartLine; + } else { + // Check if we're at or before the first change + const firstChange = changes[0]; + const firstChangeStartLine = firstChange.modified.startLineNumber; + isAtBoundary = currentLineNumber <= firstChangeStartLine; + } + + if (isAtBoundary) { + // We're at the boundary, move to next/previous file + this.moveToAdjacentFile(direction, activeViewItemIndex, viewItems); + } else { + // Navigate within the current file + diffEditor.goToDiff(direction); + } + } + + private moveToAdjacentFile(direction: 'next' | 'previous', currentIndex: number, viewItems: readonly VirtualizedViewItem[]): void { + const nextIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1; + + // Wrap around if needed + let targetIndex: number; + if (nextIndex >= viewItems.length) { + targetIndex = 0; // Wrap to first file + } else if (nextIndex < 0) { + targetIndex = viewItems.length - 1; // Wrap to last file + } else { + targetIndex = nextIndex; + } + + const targetViewItem = viewItems[targetIndex]; + + // Expand if collapsed + if (targetViewItem.viewModel.collapsed.get()) { + targetViewItem.viewModel.collapsed.set(false, undefined); + } + + // Set as active + this._viewModel.get()?.activeDiffItem.setCache(targetViewItem.viewModel, undefined); + + // Scroll the multi-diff viewport to bring the target file into view + let scrollTop = 0; + for (let i = 0; i < targetIndex; i++) { + scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + } + this._scrollableElement.setScrollPosition({ scrollTop }); + + // Reveal and go to first/last change in the new file + const template = targetViewItem.template.get(); + if (template) { + const diffEditor = template.editor; + const diffModel = diffEditor.getDiffComputationResult(); + + if (diffModel && diffModel.changes2 && diffModel.changes2.length > 0) { + if (direction === 'next') { + diffEditor.revealFirstDiff(); + diffEditor.focus(); + } else { + // For 'previous', position at the last change directly + const changes = diffModel.changes2; + const lastChange = changes[changes.length - 1]; + const modifiedEditor = diffEditor.getModifiedEditor(); + const startLine = lastChange.modified.startLineNumber; + modifiedEditor.setPosition({ lineNumber: startLine, column: 1 }); + modifiedEditor.revealLineInCenter(startLine); + modifiedEditor.focus(); + } + } + } + } + private render(reader: IReader | undefined) { const scrollTop = this.scrollTop.read(reader); let contentScrollOffsetToScrollOffset = 0; diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 8c6952a2e75..4b8e4eafa89 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -27,6 +27,7 @@ export namespace EditorContextKeys { export const readOnly = new RawContextKey('editorReadonly', false, nls.localize('editorReadonly', "Whether the editor is read-only")); export const inDiffEditor = new RawContextKey('inDiffEditor', false, nls.localize('inDiffEditor', "Whether the context is a diff editor")); export const isEmbeddedDiffEditor = new RawContextKey('isEmbeddedDiffEditor', false, nls.localize('isEmbeddedDiffEditor', "Whether the context is an embedded diff editor")); + export const inMultiDiffEditor = new RawContextKey('inMultiDiffEditor', false, nls.localize('inMultiDiffEditor', "Whether the context is a multi diff editor")); export const multiDiffEditorAllCollapsed = new RawContextKey('multiDiffEditorAllCollapsed', undefined, nls.localize('multiDiffEditorAllCollapsed', "Whether all files in multi diff editor are collapsed")); export const hasChanges = new RawContextKey('diffEditorHasChanges', false, nls.localize('diffEditorHasChanges', "Whether the diff editor has changes")); export const comparingMovedCode = new RawContextKey('comparingMovedCode', false, nls.localize('comparingMovedCode', "Whether a moved code block is selected for comparison")); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index 86aeac17ca8..ff0395aa3c4 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from '../../../../base/common/codicons.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { URI } from '../../../../base/common/uri.js'; import { Selection } from '../../../../editor/common/core/selection.js'; import { localize2 } from '../../../../nls.js'; @@ -11,6 +12,7 @@ import { Action2, MenuId } from '../../../../platform/actions/common/actions.js' import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { ITextEditorOptions, TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IListService } from '../../../../platform/list/browser/listService.js'; import { resolveCommandsContext } from '../../../browser/parts/editor/editorCommandsContext.js'; import { MultiDiffEditor } from './multiDiffEditor.js'; @@ -66,6 +68,74 @@ export class GoToFileAction extends Action2 { } } +export class GoToNextChangeAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.goToNextChange', + title: localize2('goToNextChange', 'Go to Next Change'), + icon: Codicon.arrowDown, + precondition: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + menu: [MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].map(id => ({ + id, + when: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + group: 'navigation', + order: 2 + })), + keybinding: { + primary: KeyMod.Alt | KeyCode.F5, + weight: KeybindingWeight.EditorContrib, + when: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + + if (!(activeEditorPane instanceof MultiDiffEditor)) { + return; + } + + (activeEditorPane as MultiDiffEditor).goToNextChange(); + } +} + +export class GoToPreviousChangeAction extends Action2 { + constructor() { + super({ + id: 'multiDiffEditor.goToPreviousChange', + title: localize2('goToPreviousChange', 'Go to Previous Change'), + icon: Codicon.arrowUp, + precondition: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + menu: [MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].map(id => ({ + id, + when: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + group: 'navigation', + order: 1 + })), + keybinding: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5, + weight: KeybindingWeight.EditorContrib, + when: ContextKeyExpr.equals('activeEditor', MultiDiffEditor.ID), + }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + + if (!(activeEditorPane instanceof MultiDiffEditor)) { + return; + } + + (activeEditorPane as MultiDiffEditor).goToPreviousChange(); + } +} + export class CollapseAllAction extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts index 81572303918..c779a89e7f9 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -13,12 +13,14 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/ import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { MultiDiffEditor } from './multiDiffEditor.js'; import { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer } from './multiDiffEditorInput.js'; -import { CollapseAllAction, ExpandAllAction, GoToFileAction } from './actions.js'; +import { CollapseAllAction, ExpandAllAction, GoToFileAction, GoToNextChangeAction, GoToPreviousChangeAction } from './actions.js'; import { IMultiDiffSourceResolverService, MultiDiffSourceResolverService } from './multiDiffSourceResolverService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { OpenScmGroupAction, ScmMultiDiffSourceResolverContribution } from './scmMultiDiffSourceResolver.js'; registerAction2(GoToFileAction); +registerAction2(GoToNextChangeAction); +registerAction2(GoToPreviousChangeAction); registerAction2(CollapseAllAction); registerAction2(ExpandAllAction); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 3a45ea35556..35592b49f7b 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -26,15 +26,18 @@ import { MultiDiffEditorViewModel } from '../../../../editor/browser/widget/mult import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from '../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IDiffEditor } from '../../../../editor/common/editorCommon.js'; +import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { Range } from '../../../../editor/common/core/range.js'; import { MultiDiffEditorItem } from './multiDiffSourceResolverService.js'; import { IEditorProgressService } from '../../../../platform/progress/common/progress.js'; +import { IContextKeyService, IContextKey } from '../../../../platform/contextkey/common/contextkey.js'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; private _multiDiffEditorWidget: MultiDiffEditorWidget | undefined = undefined; private _viewModel: MultiDiffEditorViewModel | undefined; + private readonly _inMultiDiffEditorContextKey: IContextKey; public get viewModel(): MultiDiffEditorViewModel | undefined { return this._viewModel; @@ -50,6 +53,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { this._onDidChangeControl.fire(); })); + + this._inMultiDiffEditorContextKey.set(true); } override async setInput(input: MultiDiffEditorInput, options: IMultiDiffEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -150,9 +157,22 @@ export class MultiDiffEditor extends AbstractEditorWithViewState): Promise { return this.editorProgressService.showWhile(promise); } + + override dispose(): void { + this._inMultiDiffEditorContextKey.reset(); + super.dispose(); + } } From 5e0f19521abda40b1698e3afbbd702164bf90814 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 Oct 2025 18:19:55 -0700 Subject: [PATCH 1437/4355] Populate chat request references for contributed chat sessions --- .../api/browser/mainThreadChatSessions.ts | 17 ++++++- .../workbench/api/common/extHost.protocol.ts | 14 +++++- .../api/common/extHostChatSessions.ts | 47 +++++++++++++++---- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../chat/common/chatSessionsService.ts | 13 ++++- 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index 40f3559265f..3ac0731adb7 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -26,6 +26,7 @@ import { IEditorService } from '../../services/editor/common/editorService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; import { IViewsService } from '../../services/views/common/viewsService.js'; +import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { ExtHostChatSessionsShape, ExtHostContext, IChatProgressDto, IChatSessionHistoryItemDto, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; export class ObservableChatSession extends Disposable implements ChatSession { @@ -117,7 +118,21 @@ export class ObservableChatSession extends Disposable implements ChatSession { this.history.length = 0; this.history.push(...sessionContent.history.map((turn: IChatSessionHistoryItemDto) => { if (turn.type === 'request') { - return { type: 'request' as const, prompt: turn.prompt, participant: turn.participant }; + const variables = turn.variableData?.variables.map(v => { + const entry = { + ...v, + value: revive(v.value) + }; + return entry as IChatRequestVariableEntry; + }); + + return { + type: 'request' as const, + prompt: turn.prompt, + participant: turn.participant, + command: turn.command, + variableData: variables ? { variables } : undefined + }; } return { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3555ad1a0cf..c764273e963 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -57,7 +57,7 @@ import { CallHierarchyItem } from '../../contrib/callHierarchy/common/callHierar import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, UserSelectedTools } from '../../contrib/chat/common/chatAgents.js'; import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js'; import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; -import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js'; +import { IChatProgressHistoryResponseContent, IChatRequestVariableData } from '../../contrib/chat/common/chatModel.js'; import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatSessionContext, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { IChatSessionItem, IChatSessionProviderOptionGroup } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; @@ -3171,7 +3171,17 @@ export interface MainThreadChatStatusShape { $disposeEntry(id: string): void; } -export type IChatSessionHistoryItemDto = { type: 'request'; prompt: string; participant: string } | { type: 'response'; parts: IChatProgressDto[]; participant: string }; +export type IChatSessionHistoryItemDto = { + type: 'request'; + prompt: string; + participant: string; + command?: string; + variableData?: Dto; +} | { + type: 'response'; + parts: IChatProgressDto[]; + participant: string; +}; export interface ChatSessionOptionUpdateDto { readonly optionId: string; diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 9c8f312ceef..192f9dc7918 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -286,16 +286,9 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio options: session.options, history: session.history.map(turn => { if (turn instanceof extHostTypes.ChatRequestTurn) { - return { type: 'request' as const, prompt: turn.prompt, participant: turn.participant }; + return this.convertRequestTurn(turn); } else { - const responseTurn = turn as extHostTypes.ChatResponseTurn2; - const parts = coalesce(responseTurn.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter, sessionDisposables))); - - return { - type: 'response' as const, - parts, - participant: responseTurn.participant - }; + return this.convertResponseTurn(turn as extHostTypes.ChatResponseTurn2, sessionDisposables); } }) }; @@ -394,4 +387,40 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio return model; } + + private convertRequestTurn(turn: extHostTypes.ChatRequestTurn) { + const variables = turn.references.map(ref => this.convertReferenceToVariable(ref)); + return { + type: 'request' as const, + prompt: turn.prompt, + participant: turn.participant, + command: turn.command, + variableData: variables.length > 0 ? { variables } : undefined + }; + } + + private convertReferenceToVariable(ref: vscode.ChatPromptReference) { + const value = ref.value && typeof ref.value === 'object' && 'uri' in ref.value && 'range' in ref.value + ? typeConvert.Location.from(ref.value as vscode.Location) + : ref.value; + const range = ref.range ? { start: ref.range[0], endExclusive: ref.range[1] } : undefined; + const isFile = URI.isUri(value) || (value && typeof value === 'object' && 'uri' in value); + return { + id: ref.id, + name: ref.id, + value, + modelDescription: ref.modelDescription, + range, + kind: isFile ? 'file' as const : 'generic' as const + }; + } + + private convertResponseTurn(turn: extHostTypes.ChatResponseTurn2, sessionDisposables: DisposableStore) { + const parts = coalesce(turn.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter, sessionDisposables))); + return { + type: 'response' as const, + parts, + participant: turn.participant + }; + } } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 00d634bfa84..a17248e0a27 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -510,7 +510,7 @@ export class ChatService extends Disposable implements IChatService { ? this.chatAgentService.getAgent(message.participant) // TODO(jospicer): Remove and always hardcode? : this.chatAgentService.getAgent(chatSessionType); lastRequest = model.addRequest(parsedRequest, - { variables: [] }, // variableData + message.variableData ?? { variables: [] }, 0, // attempt undefined, agent, diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index f751bc34cd8..f17e54f45c9 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -14,6 +14,7 @@ import { IRelaxedExtensionDescription } from '../../../../platform/extensions/co import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditableData } from '../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './chatAgents.js'; +import { IChatRequestVariableData } from './chatModel.js'; import { IChatProgress } from './chatService.js'; export const enum ChatSessionStatus { @@ -75,7 +76,17 @@ export interface IChatSessionItem { } -export type IChatSessionHistoryItem = { type: 'request'; prompt: string; participant: string } | { type: 'response'; parts: IChatProgress[]; participant: string }; +export type IChatSessionHistoryItem = { + type: 'request'; + prompt: string; + participant: string; + command?: string; + variableData?: IChatRequestVariableData; +} | { + type: 'response'; + parts: IChatProgress[]; + participant: string; +}; export interface ChatSession extends IDisposable { readonly sessionId: string; From e87d853313369edf16d137f0601f3c2822bff9c2 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 Oct 2025 18:29:06 -0700 Subject: [PATCH 1438/4355] use plain text renderer for pull request card description --- .../browser/chatContentParts/chatPullRequestContentPart.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatPullRequestContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatPullRequestContentPart.ts index d3df1823d78..9ea411c9ef1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatPullRequestContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatPullRequestContentPart.ts @@ -16,7 +16,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; import { addDisposableListener } from '../../../../../base/browser/dom.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { renderMarkdown } from '../../../../../base/browser/markdownRenderer.js'; +import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js'; export class ChatPullRequestContentPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -42,8 +42,8 @@ export class ChatPullRequestContentPart extends Disposable implements IChatConte const descriptionElement = dom.append(contentContainer, dom.$('.description')); const descriptionWrapper = dom.append(descriptionElement, dom.$('.description-wrapper')); - const markdown = this._register(renderMarkdown({ value: this.pullRequestContent.description })); - dom.append(descriptionWrapper, markdown.element); + const plainText = renderAsPlaintext({ value: this.pullRequestContent.description }); + descriptionWrapper.textContent = plainText; const seeMoreContainer = dom.append(descriptionElement, dom.$('.see-more')); const seeMore: HTMLAnchorElement = dom.append(seeMoreContainer, dom.$('a')); From 91734de2128aefeb0cec1659bb5f933c0917b472 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 Oct 2025 19:00:04 -0700 Subject: [PATCH 1439/4355] Support agent session grouping/sorting --- .../chat/browser/chatSessions.contribution.ts | 5 +++ .../chatSessions/view/chatSessionsView.ts | 36 +++++++++++++++---- .../chat/common/chatSessionsService.ts | 1 + 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 3ad521839e8..da96f1217f7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -63,6 +63,10 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint 0) { const viewDescriptorsToRegister: IViewDescriptor[] = []; - // Separate providers by type and prepare display names + // Separate providers by type and prepare display names with groups const localProvider = providers.find(p => p.chatSessionType === 'local'); const historyProvider = providers.find(p => p.chatSessionType === 'history'); const otherProviders = providers.filter(p => p.chatSessionType !== 'local' && p.chatSessionType !== 'history'); - // Sort other providers alphabetically by display name + // Sort other providers by group, then alphabetically by display name const providersWithDisplayNames = otherProviders.map(provider => { const extContribution = extensionPointContributions.find(c => c.type === provider.chatSessionType); if (!extContribution) { @@ -245,12 +245,36 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { } return { provider, - displayName: extContribution.displayName + displayName: extContribution.displayName, + group: extContribution.group }; - }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string }>; + }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string; group: string | undefined }>; - // Sort alphabetically by display name - providersWithDisplayNames.sort((a, b) => a.displayName.localeCompare(b.displayName)); + providersWithDisplayNames.sort((a, b) => { + // Both have no group - sort by display name + if (!a.group && !b.group) { + return a.displayName.localeCompare(b.displayName); + } + + // Only a has no group - push it to the end + if (!a.group) { + return 1; + } + + // Only b has no group - push it to the end + if (!b.group) { + return -1; + } + + // Both have groups - use numeric-aware comparison for groups like "builtin@1", "builtin@2" + const groupCompare = a.group.localeCompare(b.group, undefined, { numeric: true, sensitivity: 'base' }); + if (groupCompare !== 0) { + return groupCompare; + } + + // Same group - sort by display name + return a.displayName.localeCompare(b.displayName); + }); // Register views in priority order: local, history, then alphabetically sorted others const orderedProviders = [ diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index f17e54f45c9..c3e0dc0eb96 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -50,6 +50,7 @@ export interface IChatSessionsExtensionPoint { readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; readonly icon?: string; + readonly group?: string; readonly welcomeTitle?: string; readonly welcomeMessage?: string; readonly welcomeTips?: string; From 9d79e43569c48ed669af0ccf822ff40e0a400543 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:12:22 -0700 Subject: [PATCH 1440/4355] clarify `chatSessions.name` cannot contain whitespace (#272599) clarify chatSessions.name --- .../contrib/chat/browser/chatSessions.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 3ad521839e8..0f75bedbb8b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -44,8 +44,9 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint Date: Tue, 21 Oct 2025 19:12:31 -0700 Subject: [PATCH 1441/4355] fixup todo styling (#271454) * Update todo styling * Hide todo list container initially and manage its visibility during rendering * Remove standalone ChatTodoListWidget and integrate todo UI into ChatInputPart - Remove ChatTodoListWidget import, field, instantiation and DOM append - Delegate render/clear of todo list to ChatInputPart (use inputPart.renderChatTodoListWidget / inputPart.clearTodoListWidget) - Update content height and layout math to exclude removed standalone widget height - Simplify and relocate todo-list CSS: reduce padding/margins, move widget container styling under .interactive-input-part, and remove the large global .chat-todo-list-widget rules * Account for integrated todo list in chat input layout - Include todoListWidgetContainerHeight in ChatInputPart contentHeight and layout calculations so the embedded todo UI is reserved in layout - Rename ChatWidget.renderChatTodoListWidget to renderChatInputTodoListWidget and update the call site - Increase bottom padding of .chat-editing-session-container to make space for the todo list * update config * Update icon size * Remove standalone Chat Todo List Widget styles from chat.css * Add onDidUpdateTodos to mock IChatTodoListService in inline chat tests --- .../contrib/chat/browser/chat.contribution.ts | 23 +- .../chatContentParts/chatTodoListWidget.ts | 25 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatWidget.ts | 68 +--- .../contrib/chat/browser/media/chat.css | 351 +++++++++--------- .../chat/common/chatTodoListService.ts | 6 + .../contrib/chat/common/constants.ts | 2 +- .../test/browser/chatTodoListWidget.test.ts | 3 + .../test/browser/inlineChatController.test.ts | 1 + 9 files changed, 208 insertions(+), 277 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index dddf9c72e88..c62eea2f7bd 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -689,25 +689,10 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - [ChatConfiguration.TodoList]: { - type: 'object', - description: nls.localize('chat.agent.todoList', "Configures the todo list widget in chat."), - properties: { - position: { - type: 'string', - default: 'default', - enum: ['default', 'off', 'chat-input'], - enumDescriptions: [ - nls.localize('chat.agent.todoList.position.default', "Show todo list in the top of the chat panel."), - nls.localize('chat.agent.todoList.position.off', "Hide the todo list (still shows the tool calls for tracking todo progress)."), - nls.localize('chat.agent.todoList.position.chatInput', "Show todo list above the chat input.") - ], - description: nls.localize('chat.agent.todoList.position', "Controls the position of the todo list in the chat view, which opens when the agent has created todo items and updates as it makes progress.") - } - }, - default: { - position: 'default' - }, + [ChatConfiguration.TodosShowWidget]: { + type: 'boolean', + default: true, + description: nls.localize('chat.tools.todos.showWidget', "Controls whether to show the todo list widget above the chat input. When enabled, the widget displays todo items created by the agent and updates as progress is made."), tags: ['experimental'], experiment: { mode: 'auto' diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 768077f35e3..efb113d88af 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -118,24 +118,16 @@ export class ChatTodoListWidget extends Disposable { public render(sessionId: string | undefined): void { if (!sessionId) { this.domNode.style.display = 'none'; + this._onDidChangeHeight.fire(); return; } if (this._currentSessionId !== sessionId) { this._userHasScrolledManually = false; this._userManuallyExpanded = false; + this._currentSessionId = sessionId; } - const todoList = this.chatTodoListService.getTodos(sessionId); - if (todoList.length > 2) { - this.renderTodoList(todoList); - this.domNode.style.display = 'block'; - } else { - this.domNode.style.display = 'none'; - return; - } - - this._currentSessionId = sessionId; this.updateTodoDisplay(); } @@ -144,7 +136,6 @@ export class ChatTodoListWidget extends Disposable { return; } - this.domNode.style.display = 'none'; const currentTodos = this.chatTodoListService.getTodos(sessionId); const shouldClear = force || !currentTodos.some(todo => todo.status !== 'completed'); if (shouldClear) { @@ -154,20 +145,18 @@ export class ChatTodoListWidget extends Disposable { private updateTodoDisplay(): void { if (!this._currentSessionId) { - this.domNode.style.display = 'none'; - this._onDidChangeHeight.fire(); return; } const todoList = this.chatTodoListService.getTodos(this._currentSessionId); + const shouldShow = todoList.length > 2; - if (todoList.length > 0) { - this.renderTodoList(todoList); - this.domNode.style.display = 'block'; - } else { - this.domNode.style.display = 'none'; + if (!shouldShow) { + return; } + this.renderTodoList(todoList); + this.domNode.style.display = 'block'; this._onDidChangeHeight.fire(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 471850bc014..bf2616bf153 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1295,6 +1295,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const attachmentToolbarContainer = elements.attachmentToolbar; this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer; this.chatInputTodoListWidgetContainer = elements.chatInputTodoListWidgetContainer; + if (this.options.enableImplicitContext) { this._implicitContext = this._register( this.instantiationService.createInstance(ChatImplicitContext), @@ -1797,7 +1798,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); } - // Render the widget with the current session this._chatInputTodoListWidget.value.render(chatSessionId); } @@ -2064,7 +2064,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge get contentHeight(): number { const data = this.getLayoutData(); - return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight; + return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight + data.chatEditingStateHeight + data.todoListWidgetContainerHeight; } layout(height: number, width: number) { @@ -2076,7 +2076,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private previousInputEditorDimension: IDimension | undefined; private _layout(height: number, width: number, allowRecurse = true): void { const data = this.getLayoutData(); - const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight); + const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight - data.chatEditingStateHeight - data.todoListWidgetContainerHeight); const followupsWidth = width - data.inputPartHorizontalPadding; this.followupsContainer.style.width = `${followupsWidth}px`; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 858b0ffaba2..e11ee96b923 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -62,6 +62,7 @@ import { IChatAgentAttachmentCapabilities, IChatAgentCommand, IChatAgentData, IC import { ChatContextKeys } from '../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; +import { IChatTodoListService } from '../common/chatTodoListService.js'; import { IChatModel, IChatResponseModel } from '../common/chatModel.js'; import { ChatMode, IChatModeService } from '../common/chatModes.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../common/chatParserTypes.js'; @@ -85,7 +86,6 @@ import { ChatTreeItem, ChatViewId, IChatAcceptInputOptions, IChatAccessibilitySe import { ChatAccessibilityProvider } from './chatAccessibilityProvider.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; import { ChatSuggestNextWidget } from './chatContentParts/chatSuggestNextWidget.js'; -import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; import { ChatInputPart, IChatInputPartOptions, IChatInputStyles } from './chatInputPart.js'; import { ChatListDelegate, ChatListItemRenderer, IChatListItemTemplate, IChatRendererDelegate } from './chatListRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; @@ -331,7 +331,6 @@ export class ChatWidget extends Disposable implements IChatWidget { private readonly welcomeContextMenuDisposable: MutableDisposable = this._register(new MutableDisposable()); private readonly historyViewStore = this._register(new DisposableStore()); - private readonly chatTodoListWidget: ChatTodoListWidget; private readonly chatSuggestNextWidget: ChatSuggestNextWidget; private historyList: WorkbenchList | undefined; @@ -502,6 +501,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @ICommandService private readonly commandService: ICommandService, @IHoverService private readonly hoverService: IHoverService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IChatTodoListService private readonly chatTodoListService: IChatTodoListService ) { super(); this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService); @@ -583,7 +583,6 @@ export class ChatWidget extends Disposable implements IChatWidget { })); this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection, undefined)); - this.chatTodoListWidget = this._register(this.instantiationService.createInstance(ChatTodoListWidget)); this.chatSuggestNextWidget = this._register(this.instantiationService.createInstance(ChatSuggestNextWidget)); this._register(this.configurationService.onDidChangeConfiguration((e) => { @@ -703,6 +702,11 @@ export class ChatWidget extends Disposable implements IChatWidget { this.resetWelcomeViewInput(); } })); + this._register(this.chatTodoListService.onDidUpdateTodos((sessionId) => { + if (this.viewModel?.sessionId === sessionId) { + this.inputPart.renderChatTodoListWidget(sessionId); + } + })); } private resetWelcomeViewInput(): void { @@ -766,7 +770,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } get contentHeight(): number { - return this.input.contentHeight + this.tree.contentHeight + this.chatTodoListWidget.height + this.chatSuggestNextWidget.height; + return this.input.contentHeight + this.tree.contentHeight + this.chatSuggestNextWidget.height; } get attachmentModel(): ChatAttachmentModel { @@ -800,12 +804,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.welcomeMessageContainer = dom.append(this.container, $('.chat-welcome-view-container', { style: 'display: none' })); this._register(dom.addStandardDisposableListener(this.welcomeMessageContainer, dom.EventType.CLICK, () => this.focusInput())); - dom.append(this.container, this.chatTodoListWidget.domNode); - this._register(this.chatTodoListWidget.onDidChangeHeight(() => { - if (this.bodyDimension) { - this.layout(this.bodyDimension.height, this.bodyDimension.width); - } - })); this._register(this.chatSuggestNextWidget.onDidChangeHeight(() => { if (this.bodyDimension) { this.layout(this.bodyDimension.height, this.bodyDimension.width); @@ -971,7 +969,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.unlockFromCodingAgent(); } - this.clearTodoListWidget(this.viewModel?.sessionId); + this.inputPart.clearTodoListWidget(this.viewModel?.sessionId, true); // Cancel any pending widget render and hide the widget BEFORE firing onDidClear // This prevents the widget from being re-shown by any handlers triggered by the clear event this._chatSuggestNextScheduler.cancel(); @@ -1014,7 +1012,6 @@ export class ChatWidget extends Disposable implements IChatWidget { if (treeItems.length > 0) { this.updateChatViewVisibility(); - this.renderChatTodoListWidget(); } else { this._welcomeRenderScheduler.schedule(); } @@ -1319,38 +1316,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.renderHistoryItems(historyItems); } - private renderChatTodoListWidget(): void { - const sessionId = this.viewModel?.sessionId; - if (!sessionId || !this._isReady) { - return; - } - - const todoListConfig = this.configurationService.getValue<{ position?: string }>(ChatConfiguration.TodoList); - const todoListWidgetPosition = todoListConfig?.position || 'default'; - - // Handle 'off' - hide the widget and return - if (todoListWidgetPosition === 'off') { - this.chatTodoListWidget.domNode.style.display = 'none'; - this._onDidChangeContentHeight.fire(); - return; - } - - // Handle 'chat-input' - hide the standalone widget to avoid duplication - if (todoListWidgetPosition === 'chat-input') { - this.chatTodoListWidget.domNode.style.display = 'none'; - this.inputPart.renderChatTodoListWidget(sessionId); - this._onDidChangeContentHeight.fire(); - return; - } - - this.chatTodoListWidget.render(sessionId); - } - - private clearTodoListWidget(sessionId: string | undefined, force: boolean = false): void { - this.chatTodoListWidget.clear(sessionId, force); - this.inputPart.clearTodoListWidget(sessionId, force); - } - private _getGenerateInstructionsMessage(): IMarkdownString { // Start checking for instruction files immediately if not already done if (!this._instructionFilesCheckPromise) { @@ -2416,11 +2381,11 @@ export class ChatWidget extends Disposable implements IChatWidget { this._updateAgentCapabilitiesContextKeys(e.agent); } if (e.kind === 'addRequest') { - this.clearTodoListWidget(model.sessionId, false); + this.inputPart.clearTodoListWidget(this.viewModel?.sessionId, false); } // Hide widget on request removal if (e.kind === 'removeRequest') { - this.clearTodoListWidget(model.sessionId, true); + this.inputPart.clearTodoListWidget(this.viewModel?.sessionId, true); this.chatSuggestNextWidget.hide(); } // Show next steps widget when response completes (not when request starts) @@ -2800,12 +2765,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } const inputHeight = this.inputPart.inputPartHeight; - const chatTodoListWidgetHeight = this.chatTodoListWidget.height; const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight - 2; const lastItem = this.viewModel?.getItems().at(-1); - const contentHeight = Math.max(0, height - inputHeight - chatTodoListWidgetHeight - chatSuggestNextWidgetHeight); + const contentHeight = Math.max(0, height - inputHeight - chatSuggestNextWidgetHeight); if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal') { this.listContainer.style.removeProperty('--chat-current-response-min-height'); } else { @@ -2871,10 +2835,9 @@ export class ChatWidget extends Disposable implements IChatWidget { const width = this.bodyDimension?.width ?? this.container.offsetWidth; this.input.layout(possibleMaxHeight, width); const inputPartHeight = this.input.inputPartHeight; - const chatTodoListWidgetHeight = this.chatTodoListWidget.height; const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; - const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight - chatTodoListWidgetHeight - chatSuggestNextWidgetHeight); - this.layout(newHeight + inputPartHeight + chatTodoListWidgetHeight + chatSuggestNextWidgetHeight, width); + const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight - chatSuggestNextWidgetHeight); + this.layout(newHeight + inputPartHeight + chatSuggestNextWidgetHeight, width); }); })); } @@ -2917,7 +2880,6 @@ export class ChatWidget extends Disposable implements IChatWidget { const width = this.bodyDimension?.width ?? this.container.offsetWidth; this.input.layout(this._dynamicMessageLayoutData.maxHeight, width); const inputHeight = this.input.inputPartHeight; - const chatTodoListWidgetHeight = this.chatTodoListWidget.height; const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; const totalMessages = this.viewModel.getItems(); @@ -2932,7 +2894,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.layout( Math.min( // we add an additional 18px in order to show that there is scrollable content - inputHeight + chatTodoListWidgetHeight + chatSuggestNextWidgetHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), + inputHeight + chatSuggestNextWidgetHeight + listHeight + (totalMessages.length > 2 ? 18 : 0), this._dynamicMessageLayoutData.maxHeight ), width diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index cf64f06491b..2e8d7c98df7 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1080,6 +1080,174 @@ have to be updated for changes to the rules above, or to support more deeply nes background-color: var(--vscode-toolbar-hoverBackground); } +/* Chat Todo List Widget Container - mirrors chat-editing-session styling */ +.interactive-session .interactive-input-part > .chat-todo-list-widget-container { + width: 100%; + position: relative; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { + padding: 6px 8px 6px 12px; + box-sizing: border-box; + border: 1px solid var(--vscode-input-border, transparent); + background-color: var(--vscode-editor-background); + border-bottom: none; + border-radius: 4px; + flex-direction: column; + gap: 2px; + overflow: hidden; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + padding: 2px 0; + justify-content: space-between; + width: 100%; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand:focus:not(:focus-visible) { + outline: none; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section { + display: flex; + align-items: center; + gap: 4px; + flex: 1; + color: var(--vscode-foreground); + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + align-content: center; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section .codicon { + font-size: 16px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container { + display: flex; + align-items: center; + opacity: 1; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button { + padding: 2px; + margin-right: 2px; + min-width: unset; + background-color: transparent; + color: var(--vscode-foreground); + border: none; + border-radius: 3px; + height: 14px; + width: 14px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button:focus { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 1px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button .codicon { + font-size: 10px; + color: var(--vscode-foreground); +} + + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .expand-icon { + flex-shrink: 0; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title { + font-weight: normal; + font-size: 11px; + color: var(--vscode-foreground); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container { + margin-top: 2px; + max-height: calc(6.5 * 21px); + /* 6.5 items to show half-line affordance */ + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: var(--vscode-scrollbarSlider-background) transparent; + scroll-behavior: smooth; + scroll-padding-top: 24px; + /* Half item height to show partial next item */ + scroll-padding-bottom: 24px; + /* Half item height to show partial previous item */ +} + +/* Modern scrollbar styling for WebKit browsers */ +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar { + width: 8px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-track { + background: transparent; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb { + background-color: var(--vscode-scrollbarSlider-background); + border-radius: 4px; + border: 2px solid transparent; + background-clip: content-box; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb:hover { + background-color: var(--vscode-scrollbarSlider-hoverBackground); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list { + display: flex; + flex-direction: column; + gap: 4px; + scroll-snap-type: y proximity; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item { + display: flex; + align-items: center; + gap: 8px; + scroll-snap-align: start; + min-height: 22px; + font-size: var(--vscode-chat-font-size-body-m); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:focus { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 1px; + background-color: var(--vscode-list-focusBackground); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:hover { + background-color: var(--vscode-list-hoverBackground); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item > .todo-status-icon.codicon { + flex-shrink: 0; + font-size: 16px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-content { + color: var(--vscode-foreground); + flex-grow: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0; +} + .interactive-session .interactive-input-part.compact .chat-input-container { display: flex; justify-content: space-between; @@ -2550,185 +2718,6 @@ have to be updated for changes to the rules above, or to support more deeply nes background-color: var(--vscode-toolbar-hoverBackground); } -/* Chat Todo List Widget */ -.chat-todo-list-widget { - position: sticky; - top: 0; - z-index: 2; - background-color: var(--vscode-sideBar-background); - padding-left: 12px; - padding-right: 12px; -} - -/* Special styling when todo widget is used inline in chat responses (subparts) */ -.chat-todo-list-widget.chat-todo-subpart { - padding: 4px 3px 6px 3px; - border: 1px solid var(--vscode-chat-requestBorder); - border-radius: 4px; - margin-bottom: 8px; - position: static; - z-index: auto; -} - -.chat-todo-list-widget.scrolled { - box-shadow: 0 2px 4px var(--vscode-scrollbar-shadow); -} - -.chat-todo-list-widget .todo-list-expand { - display: flex; - align-items: center; - gap: 4px; - cursor: pointer; - padding: 2px 0; - justify-content: space-between; - width: 100%; -} - -.chat-todo-list-widget .todo-list-expand:focus:not(:focus-visible) { - outline: none; -} - -.chat-todo-list-widget .todo-list-expand .todo-list-title-section { - display: flex; - align-items: center; - gap: 6px; - flex: 1; -} - -.chat-todo-list-widget .todo-clear-button-container { - display: flex; - align-items: center; - opacity: 0; - transition: opacity 0.2s ease; -} - -.chat-todo-list-widget .todo-list-expand:hover .todo-clear-button-container, -.chat-todo-list-widget .todo-list-expand:focus-within .todo-clear-button-container, -.chat-todo-list-widget .todo-clear-button-container:focus-within { - opacity: 1; -} - -.chat-todo-list-widget .todo-clear-button-container .monaco-button { - padding: 2px; - margin-right: 2px; - min-width: unset; - background-color: transparent; - color: var(--vscode-foreground); - border: none; - border-radius: 3px; - height: 14px; - width: 14px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; -} - -.chat-todo-list-widget .todo-clear-button-container .monaco-button:hover { - background-color: var(--vscode-toolbar-hoverBackground) !important; -} - -.chat-todo-list-widget .todo-clear-button-container .monaco-button:focus { - outline: 1px solid var(--vscode-focusBorder); - outline-offset: 1px; -} - -.chat-todo-list-widget .todo-clear-button-container .monaco-button .codicon { - font-size: 10px; - color: var(--vscode-foreground); -} - -.chat-todo-list-widget .todo-list-expand:hover { - background-color: var(--vscode-toolbar-hoverBackground); -} - - -.chat-todo-list-widget .expand-icon { - flex-shrink: 0; - font-size: 12px; -} - -.chat-todo-list-widget .todo-list-title { - font-weight: normal; - font-size: var(--vscode-chat-font-size-body-s); - color: var(--vscode-descriptionForeground); -} - -.chat-todo-list-widget .todo-list-container { - margin-top: 2px; - max-height: calc(6.5 * 21px); - /* 6.5 items to show half-line affordance */ - overflow-y: auto; - overscroll-behavior: contain; - scrollbar-width: thin; - scrollbar-color: var(--vscode-scrollbarSlider-background) transparent; - scroll-behavior: smooth; - scroll-padding-top: 24px; - /* Half item height to show partial next item */ - scroll-padding-bottom: 24px; - /* Half item height to show partial previous item */ -} - -/* Modern scrollbar styling for WebKit browsers */ -.chat-todo-list-widget .todo-list-container::-webkit-scrollbar { - width: 8px; -} - -.chat-todo-list-widget .todo-list-container::-webkit-scrollbar-track { - background: transparent; -} - -.chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb { - background-color: var(--vscode-scrollbarSlider-background); - border-radius: 4px; - border: 2px solid transparent; - background-clip: content-box; -} - -.chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb:hover { - background-color: var(--vscode-scrollbarSlider-hoverBackground); -} - -.chat-todo-list-widget .todo-list { - display: flex; - flex-direction: column; - gap: 4px; - scroll-snap-type: y proximity; -} - -.chat-todo-list-widget .todo-item { - display: flex; - align-items: center; - gap: 8px; - scroll-snap-align: start; - min-height: 22px; - font-size: var(--vscode-chat-font-size-body-m); -} - -.chat-todo-list-widget .todo-item:focus { - outline: 1px solid var(--vscode-focusBorder); - outline-offset: 1px; - background-color: var(--vscode-list-focusBackground); -} - -.chat-todo-list-widget .todo-item:hover { - background-color: var(--vscode-list-hoverBackground); -} - -.chat-todo-list-widget .todo-item > .todo-status-icon.codicon { - flex-shrink: 0; - font-size: 16px; -} - -.chat-todo-list-widget .todo-content { - color: var(--vscode-foreground); - flex-grow: 1; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - min-width: 0; -} - /* Chat Suggest Next Widget */ /* Suggested Actions widget - reuses chat-welcome-view-suggested-prompts styling */ @@ -3085,10 +3074,6 @@ have to be updated for changes to the rules above, or to support more deeply nes background: var(--vscode-editor-background); } -.editor-instance .chat-todo-list-widget { - background-color: var(--vscode-editor-background); -} - /* Show more attachments button styling */ .chat-attachments-show-more-button { opacity: 0.8; diff --git a/src/vs/workbench/contrib/chat/common/chatTodoListService.ts b/src/vs/workbench/contrib/chat/common/chatTodoListService.ts index 835af80fd20..425e66c6463 100644 --- a/src/vs/workbench/contrib/chat/common/chatTodoListService.ts +++ b/src/vs/workbench/contrib/chat/common/chatTodoListService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -24,6 +25,7 @@ export const IChatTodoListService = createDecorator('chatT export interface IChatTodoListService { readonly _serviceBrand: undefined; + readonly onDidUpdateTodos: Event; getTodos(sessionId: string): IChatTodo[]; setTodos(sessionId: string, todos: IChatTodo[]): void; } @@ -58,6 +60,9 @@ export class ChatTodoListStorage implements IChatTodoListStorage { export class ChatTodoListService extends Disposable implements IChatTodoListService { declare readonly _serviceBrand: undefined; + private readonly _onDidUpdateTodos = this._register(new Emitter()); + readonly onDidUpdateTodos: Event = this._onDidUpdateTodos.event; + private todoListStorage: IChatTodoListStorage; constructor(@IStorageService storageService: IStorageService) { @@ -71,5 +76,6 @@ export class ChatTodoListService extends Disposable implements IChatTodoListServ setTodos(sessionId: string, todos: IChatTodo[]): void { this.todoListStorage.setTodoList(sessionId, todos); + this._onDidUpdateTodos.fire(sessionId); } } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 81203c9c643..b6341fc18aa 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -16,7 +16,7 @@ export enum ChatConfiguration { CheckpointsEnabled = 'chat.checkpoints.enabled', AgentSessionsViewLocation = 'chat.agentSessionsViewLocation', ThinkingStyle = 'chat.agent.thinkingStyle', - TodoList = 'chat.agent.todoList', + TodosShowWidget = 'chat.tools.todos.showWidget', UseCloudButtonV2 = 'chat.useCloudButtonV2', ShowAgentSessionsViewDescription = 'chat.showAgentSessionsViewDescription', EmptyStateHistoryEnabled = 'chat.emptyState.history.enabled', diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index 4d0fc5580aa..b5cc3e286d1 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; +import { Event } from '../../../../../base/common/event.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { ChatTodoListWidget } from '../../browser/chatContentParts/chatTodoListWidget.js'; import { IChatTodo, IChatTodoListService } from '../../common/chatTodoListService.js'; @@ -27,6 +28,7 @@ suite('ChatTodoListWidget Accessibility', () => { // Mock the todo list service mockTodoListService = { _serviceBrand: undefined, + onDidUpdateTodos: Event.None, getTodos: (sessionId: string) => sampleTodos, setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; @@ -137,6 +139,7 @@ suite('ChatTodoListWidget Accessibility', () => { // Create a new mock service with empty todos const emptyTodoListService: IChatTodoListService = { _serviceBrand: undefined, + onDidUpdateTodos: Event.None, getTodos: (sessionId: string) => [], setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 3f91aecb961..9c2e82b77c2 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -224,6 +224,7 @@ suite('InlineChatController', function () { [IChatModeService, new SyncDescriptor(MockChatModeService)], [IChatLayoutService, new SyncDescriptor(ChatLayoutService)], [IChatTodoListService, new class extends mock() { + override onDidUpdateTodos = Event.None; override getTodos(sessionId: string): IChatTodo[] { return []; } override setTodos(sessionId: string, todos: IChatTodo[]): void { } }], From b1dc4b520382e5135d62a02c9fcdd83703e2b442 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 Oct 2025 19:20:48 -0700 Subject: [PATCH 1442/4355] rename to order. --- .../chat/browser/chatSessions.contribution.ts | 8 ++--- .../chatSessions/view/chatSessionsView.ts | 30 +++++++++---------- .../chat/common/chatSessionsService.ts | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index da96f1217f7..99bcdce9eb0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -63,9 +63,9 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint 0) { const viewDescriptorsToRegister: IViewDescriptor[] = []; - // Separate providers by type and prepare display names with groups + // Separate providers by type and prepare display names with order const localProvider = providers.find(p => p.chatSessionType === 'local'); const historyProvider = providers.find(p => p.chatSessionType === 'history'); const otherProviders = providers.filter(p => p.chatSessionType !== 'local' && p.chatSessionType !== 'history'); - // Sort other providers by group, then alphabetically by display name + // Sort other providers by order, then alphabetically by display name const providersWithDisplayNames = otherProviders.map(provider => { const extContribution = extensionPointContributions.find(c => c.type === provider.chatSessionType); if (!extContribution) { @@ -246,33 +246,33 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { return { provider, displayName: extContribution.displayName, - group: extContribution.group + order: extContribution.order }; - }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string; group: string | undefined }>; + }).filter(item => item !== null) as Array<{ provider: IChatSessionItemProvider; displayName: string; order: number | undefined }>; providersWithDisplayNames.sort((a, b) => { - // Both have no group - sort by display name - if (!a.group && !b.group) { + // Both have no order - sort by display name + if (a.order === undefined && b.order === undefined) { return a.displayName.localeCompare(b.displayName); } - // Only a has no group - push it to the end - if (!a.group) { + // Only a has no order - push it to the end + if (a.order === undefined) { return 1; } - // Only b has no group - push it to the end - if (!b.group) { + // Only b has no order - push it to the end + if (b.order === undefined) { return -1; } - // Both have groups - use numeric-aware comparison for groups like "builtin@1", "builtin@2" - const groupCompare = a.group.localeCompare(b.group, undefined, { numeric: true, sensitivity: 'base' }); - if (groupCompare !== 0) { - return groupCompare; + // Both have orders - compare numerically + const orderCompare = a.order - b.order; + if (orderCompare !== 0) { + return orderCompare; } - // Same group - sort by display name + // Same order - sort by display name return a.displayName.localeCompare(b.displayName); }); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index c3e0dc0eb96..da1470d4d57 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -50,7 +50,7 @@ export interface IChatSessionsExtensionPoint { readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; readonly icon?: string; - readonly group?: string; + readonly order?: number; readonly welcomeTitle?: string; readonly welcomeMessage?: string; readonly welcomeTips?: string; From 4e17acbdd22ed14514d9f6d3ee8d2210e8e97238 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 19:24:48 -0700 Subject: [PATCH 1443/4355] Enable accessible mixed state for tree item check boxes --- src/vs/base/browser/ui/list/listView.ts | 13 ++++++++----- .../tree/quickInputTreeAccessibilityProvider.ts | 5 +++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 7fc6ea246b6..6cc8d89af33 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -58,11 +58,13 @@ export const enum ListViewTargetSector { BOTTOM = 3 // [75%-100%) } +export type CheckBoxAccessibleState = boolean | 'mixed'; + export interface IListViewAccessibilityProvider { getSetSize?(element: T, index: number, listLength: number): number; getPosInSet?(element: T, index: number): number; getRole?(element: T): AriaRole | undefined; - isChecked?(element: T): boolean | IValueWithChangeEvent | undefined; + isChecked?(element: T): CheckBoxAccessibleState | IValueWithChangeEvent | undefined; } export interface IListViewOptionsUpdate { @@ -197,7 +199,7 @@ class ListViewAccessibilityProvider implements Required number; readonly getPosInSet: (element: T, index: number) => number; readonly getRole: (element: T) => AriaRole | undefined; - readonly isChecked: (element: T) => boolean | IValueWithChangeEvent | undefined; + readonly isChecked: (element: T) => CheckBoxAccessibleState | IValueWithChangeEvent | undefined; constructor(accessibilityProvider?: IListViewAccessibilityProvider) { if (accessibilityProvider?.getSetSize) { @@ -959,11 +961,12 @@ export class ListView implements IListView { item.row.domNode.setAttribute('role', role); const checked = this.accessibilityProvider.isChecked(item.element); + const toAriaState = (value: CheckBoxAccessibleState) => value === 'mixed' ? 'mixed' : String(!!value); - if (typeof checked === 'boolean') { - item.row.domNode.setAttribute('aria-checked', String(!!checked)); + if (typeof checked === 'boolean' || checked === 'mixed') { + item.row.domNode.setAttribute('aria-checked', toAriaState(checked)); } else if (checked) { - const update = (checked: boolean) => item.row!.domNode.setAttribute('aria-checked', String(!!checked)); + const update = (value: CheckBoxAccessibleState) => item.row!.domNode.setAttribute('aria-checked', toAriaState(value)); update(checked.value); item.checkedDisposable = checked.onDidChange(() => update(checked.value)); } diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts index 51dfbc018e2..33fc1a61a04 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; +import { CheckBoxAccessibleState } from '../../../../base/browser/ui/list/listView.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { Event, IValueWithChangeEvent } from '../../../../base/common/event.js'; import { getCodiconAriaLabel } from '../../../../base/common/iconLabels.js'; @@ -34,9 +35,9 @@ export class QuickTreeAccessibilityProvider implements return 'checkbox'; } - isChecked(element: T): IValueWithChangeEvent | undefined { + isChecked(element: T): IValueWithChangeEvent | undefined { return { - get value() { return element.checked === true; }, + get value() { return element.checked === 'partial' ? 'mixed' : element.checked === true; }, onDidChange: e => Event.filter(this.onCheckedEvent, e => e.item === element)(_ => e()), }; } From e7a4d0ef87a8a1c077358aa63b490aae1f672270 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 19:28:51 -0700 Subject: [PATCH 1444/4355] PR feedback --- .../browser/tree/quickInputTreeAccessibilityProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts index 33fc1a61a04..6be2373d53a 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeAccessibilityProvider.ts @@ -37,7 +37,7 @@ export class QuickTreeAccessibilityProvider implements isChecked(element: T): IValueWithChangeEvent | undefined { return { - get value() { return element.checked === 'partial' ? 'mixed' : element.checked === true; }, + get value() { return element.checked === 'partial' ? 'mixed' : !!element.checked; }, onDidChange: e => Event.filter(this.onCheckedEvent, e => e.item === element)(_ => e()), }; } From c3ebc7922704eea363a3399d8ff906bc0134774c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 21 Oct 2025 20:32:16 -0700 Subject: [PATCH 1445/4355] Bump distro (#272574) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebddfe47e02..7cc9ebfd35e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "6ccc3d7617839f653f9dd899e316200befab12d8", + "distro": "dd592c236d97d1f7ffa7cb95b154020782fa3659", "author": { "name": "Microsoft Corporation" }, @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} From 8741efcdee1e93d11b6696034e4b6f0bdf9d4348 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Tue, 21 Oct 2025 21:10:43 -0700 Subject: [PATCH 1446/4355] Include input state when adding history --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index bf2616bf153..90e99dd5386 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1075,7 +1075,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputState = this.getInputState(); const entry = this.getFilteredEntry(userQuery, inputState); this.history.replaceLast(entry); - this.history.add({ text: '' }); + this.history.add({ text: '', state: this.getInputState() }); } // Clear attached context, fire event to clear input state, and clear the input editor From 25be0314fa88fbaaf01fd8da8efb2636a83e1d7e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 04:20:59 +0000 Subject: [PATCH 1447/4355] Add read-only flag to ChatMultiDiffContentPart to disable file interactions (#272605) * clarify chatSessions.name * Initial plan * Add read-only flag to ChatMultiDiffContentPart Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * extension API --------- Co-authored-by: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .../api/common/extHostTypeConverters.ts | 5 ++- src/vs/workbench/api/common/extHostTypes.ts | 4 +- .../chatMultiDiffContentPart.ts | 45 +++++++++++-------- .../contrib/chat/common/chatService.ts | 1 + ...ode.proposed.chatParticipantAdditions.d.ts | 9 +++- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index b6668e645d3..72978c57bc6 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2691,7 +2691,8 @@ export namespace ChatResponseMultiDiffPart { added: entry.added, removed: entry.removed, })) - } + }, + readOnly: part.readOnly }; } export function to(part: Dto): vscode.ChatResponseMultiDiffPart { @@ -2702,7 +2703,7 @@ export namespace ChatResponseMultiDiffPart { added: resource.added, removed: resource.removed, })); - return new types.ChatResponseMultiDiffPart(resources, part.multiDiffData.title); + return new types.ChatResponseMultiDiffPart(resources, part.multiDiffData.title, part.readOnly); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a61088ba6ce..0d99b23840c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3159,9 +3159,11 @@ export class ChatResponseFileTreePart { export class ChatResponseMultiDiffPart { value: vscode.ChatResponseDiffEntry[]; title: string; - constructor(value: vscode.ChatResponseDiffEntry[], title: string) { + readOnly?: boolean; + constructor(value: vscode.ChatResponseDiffEntry[], title: string, readOnly?: boolean) { this.value = value; this.title = title; + this.readOnly = readOnly; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index 5eef3ba2147..b8ec66f0193 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -50,6 +50,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent private list!: WorkbenchList; private isCollapsed: boolean = false; + private readonly readOnly: boolean; constructor( private readonly content: IChatMultiDiffData, @@ -62,6 +63,8 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent ) { super(); + this.readOnly = content.readOnly ?? false; + const headerDomNode = $('.checkpoint-file-changes-summary-header'); this.domNode = $('.checkpoint-file-changes-summary', undefined, headerDomNode); this.domNode.tabIndex = 0; @@ -92,7 +95,9 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent this.isCollapsed = !this.isCollapsed; setExpansionState(); })); - disposables.add(this.renderViewAllFileChangesButton(viewListButton.element)); + if (!this.readOnly) { + disposables.add(this.renderViewAllFileChangesButton(viewListButton.element)); + } disposables.add(this.renderContributedButtons(viewListButton.element)); return toDisposable(() => disposables.dispose()); } @@ -180,7 +185,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent setRowLineHeight: true, horizontalScrolling: false, supportDynamicHeights: false, - mouseSupport: true, + mouseSupport: !this.readOnly, alwaysConsumeMouseWheel: false, accessibilityProvider: { getAriaLabel: (element: IChatMultiDiffItem) => element.uri.path, @@ -217,24 +222,26 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent this.list.layout(height); listContainer.style.height = `${height}px`; - store.add(this.list.onDidOpen((e) => { - if (!e.element) { - return; - } + if (!this.readOnly) { + store.add(this.list.onDidOpen((e) => { + if (!e.element) { + return; + } - if (e.element.diff) { - this.editorService.openEditor({ - original: { resource: e.element.diff.originalURI }, - modified: { resource: e.element.diff.modifiedURI }, - options: { preserveFocus: true } - }); - } else { - this.editorService.openEditor({ - resource: e.element.uri, - options: { preserveFocus: true } - }); - } - })); + if (e.element.diff) { + this.editorService.openEditor({ + original: { resource: e.element.diff.originalURI }, + modified: { resource: e.element.diff.modifiedURI }, + options: { preserveFocus: true } + }); + } else { + this.editorService.openEditor({ + resource: e.element.uri, + options: { preserveFocus: true } + }); + } + })); + } return store; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index b0689e3899e..f4d14ba7a8c 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -167,6 +167,7 @@ export interface IChatMultiDiffData { }>; }; kind: 'multiDiffData'; + readOnly?: boolean; } export interface IChatProgressMessage { diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index c0b38dffc4e..f05e4fe6915 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -152,12 +152,19 @@ declare module 'vscode' { */ title: string; + /** + * Whether the multi diff editor should be read-only. + * When true, users cannot open individual files or interact with file navigation. + */ + readOnly?: boolean; + /** * Create a new ChatResponseMultiDiffPart. * @param value Array of file diff entries. * @param title The title for the multi diff editor. + * @param readOnly Optional flag to make the multi diff editor read-only. */ - constructor(value: ChatResponseDiffEntry[], title: string); + constructor(value: ChatResponseDiffEntry[], title: string, readOnly?: boolean); } export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart; From 024a13795f3dc1b48bae00f1d21fa5eacb5e0561 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 22 Oct 2025 04:40:58 +0000 Subject: [PATCH 1448/4355] Refactor RunInTerminalTool confirmation handling (#272603) * Updates to RunInTerminalConfirmationTool * Updates --- .../terminal.chatAgentTools.contribution.ts | 7 +- .../tools/runInTerminalConfirmationTool.ts | 15 +- .../browser/tools/runInTerminalTool.ts | 329 ++++++++---------- .../test/browser/runInTerminalTool.test.ts | 9 +- 4 files changed, 160 insertions(+), 200 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts index 1baa65005ec..fa9e71a2976 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts @@ -23,7 +23,7 @@ import { GetTerminalLastCommandTool, GetTerminalLastCommandToolData } from './to import { GetTerminalOutputTool, GetTerminalOutputToolData } from './tools/getTerminalOutputTool.js'; import { GetTerminalSelectionTool, GetTerminalSelectionToolData } from './tools/getTerminalSelectionTool.js'; import { ConfirmTerminalCommandTool, ConfirmTerminalCommandToolData } from './tools/runInTerminalConfirmationTool.js'; -import { RunInTerminalTool, RunInTerminalToolConfirmationHelper, createRunInTerminalToolData } from './tools/runInTerminalTool.js'; +import { RunInTerminalTool, createRunInTerminalToolData } from './tools/runInTerminalTool.js'; import { CreateAndRunTaskTool, CreateAndRunTaskToolData } from './tools/task/createAndRunTaskTool.js'; import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOutputTool.js'; import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js'; @@ -42,8 +42,7 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib // #region Terminal - const terminalConfirmationHelper = this._register(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); - const confirmTerminalCommandTool = instantiationService.createInstance(ConfirmTerminalCommandTool, terminalConfirmationHelper); + const confirmTerminalCommandTool = instantiationService.createInstance(ConfirmTerminalCommandTool); this._register(toolsService.registerTool(ConfirmTerminalCommandToolData, confirmTerminalCommandTool)); const getTerminalOutputTool = instantiationService.createInstance(GetTerminalOutputTool); this._register(toolsService.registerTool(GetTerminalOutputToolData, getTerminalOutputTool)); @@ -55,7 +54,7 @@ class ChatAgentToolsContribution extends Disposable implements IWorkbenchContrib runCommandsToolSet.addTool(GetTerminalOutputToolData); instantiationService.invokeFunction(createRunInTerminalToolData).then(runInTerminalToolData => { - const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool, terminalConfirmationHelper); + const runInTerminalTool = instantiationService.createInstance(RunInTerminalTool); this._register(toolsService.registerTool(runInTerminalToolData, runInTerminalTool)); runCommandsToolSet.addTool(runInTerminalToolData); }); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts index 172b5b30bdd..db6b36a9b0e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalConfirmationTool.ts @@ -6,8 +6,8 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { localize } from '../../../../../../nls.js'; -import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/languageModelToolsService.js'; -import { RunInTerminalToolConfirmationHelper } from './runInTerminalTool.js'; +import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/languageModelToolsService.js'; +import { RunInTerminalTool } from './runInTerminalTool.js'; export const ConfirmTerminalCommandToolData: IToolData = { id: 'vscode_get_terminal_confirmation', @@ -56,18 +56,15 @@ export const ConfirmTerminalCommandToolData: IToolData = { } }; -export class ConfirmTerminalCommandTool implements IToolImpl { - constructor(private readonly _terminalCommandConfirmationHelper: RunInTerminalToolConfirmationHelper, - ) { } - async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { - const preparedInvocation = await this._terminalCommandConfirmationHelper.prepareToolInvocation(context, undefined, token); +export class ConfirmTerminalCommandTool extends RunInTerminalTool { + override async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { + const preparedInvocation = await super.prepareToolInvocation(context, token); if (preparedInvocation) { preparedInvocation.presentation = ToolInvocationPresentation.HiddenAfterComplete; } return preparedInvocation; } - - async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise { + override async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise { // This is a confirmation-only tool - just return success return { content: [{ 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 cbcca9e1355..72861ea8830 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -258,6 +258,7 @@ const promptInjectionWarningCommandsLowerPwshOnly = [ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _terminalToolCreator: ToolTerminalCreator; + private readonly _commandSimplifier: CommandSimplifier; protected readonly _profileFetcher: TerminalProfileFetcher; private readonly _telemetry: RunInTerminalToolTelemetry; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; @@ -276,9 +277,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } constructor( - private readonly _confirmationHelper: RunInTerminalToolConfirmationHelper, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IStorageService private readonly _storageService: IStorageService, @ITerminalLogService private readonly _logService: ITerminalLogService, @ITerminalService private readonly _terminalService: ITerminalService, @@ -291,6 +292,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); this._terminalToolCreator = _instantiationService.createInstance(ToolTerminalCreator); + this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); @@ -321,8 +323,151 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { - const terminalInstance = context.chatSessionId ? this._sessionTerminalAssociations.get(context.chatSessionId)?.instance : undefined; - return this._confirmationHelper.prepareToolInvocation(context, terminalInstance, token); + const args = context.parameters as IRunInTerminalInputParams; + + const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); + const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; + + const os = await this._osBackend; + const shell = await this._profileFetcher.getCopilotShell(); + const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; + + const instance = context.chatSessionId ? this._sessionTerminalAssociations.get(context.chatSessionId)?.instance : undefined; + const terminalToolSessionId = generateUuid(); + + let toolEditedCommand: string | undefined = await this._commandSimplifier.rewriteIfNeeded(args, instance, shell); + if (toolEditedCommand === args.command) { + toolEditedCommand = undefined; + } + + let autoApproveInfo: IMarkdownString | undefined; + let confirmationMessages: IToolConfirmationMessages | undefined; + if (alternativeRecommendation) { + confirmationMessages = undefined; + } else { + // Determine auto approval, this happens even when auto approve is off to that reasoning + // can be reviewed in the terminal channel. It also allows gauging the effective set of + // commands that would be auto approved if it were enabled. + const actualCommand = toolEditedCommand ?? args.command; + const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); + const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); + const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); + const autoApproveReasons: string[] = [ + ...subCommandResults.map(e => e.reason), + commandLineResult.reason, + ]; + + let isAutoApproved = false; + let isDenied = false; + let autoApproveReason: 'subCommand' | 'commandLine' | undefined; + let autoApproveDefault: boolean | undefined; + + const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); + if (deniedSubCommandResult) { + this._logService.info('autoApprove: Sub-command DENIED auto approval'); + isDenied = true; + autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; + autoApproveReason = 'subCommand'; + } else if (commandLineResult.result === 'denied') { + this._logService.info('autoApprove: Command line DENIED auto approval'); + isDenied = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + autoApproveReason = 'commandLine'; + } else { + if (subCommandResults.every(e => e.result === 'approved')) { + this._logService.info('autoApprove: All sub-commands auto-approved'); + autoApproveReason = 'subCommand'; + isAutoApproved = true; + autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); + } else { + this._logService.info('autoApprove: All sub-commands NOT auto-approved'); + if (commandLineResult.result === 'approved') { + this._logService.info('autoApprove: Command line auto-approved'); + autoApproveReason = 'commandLine'; + isAutoApproved = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + } else { + this._logService.info('autoApprove: Command line NOT auto-approved'); + } + } + } + + // Log detailed auto approval reasoning + for (const reason of autoApproveReasons) { + this._logService.info(`- ${reason}`); + } + + // Apply auto approval or force it off depending on enablement/opt-in state + const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; + const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); + const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; + if (isAutoApproveEnabled) { + autoApproveInfo = this._createAutoApproveInfo( + isAutoApproved, + isDenied, + autoApproveReason, + subCommandResults, + commandLineResult, + ); + } else { + isAutoApproved = false; + } + + // Send telemetry about auto approval process + this._telemetry.logPrepare({ + terminalToolSessionId, + subCommands, + autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', + autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', + autoApproveReason, + autoApproveDefault + }); + + // Add a disclaimer warning about prompt injection for common commands that return + // content from the web + let disclaimer: IMarkdownString | undefined; + const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); + if (!isAutoApproved && ( + subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || + (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) + )) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); + } + + let customActions: ToolConfirmationAction[] | undefined; + if (!isAutoApproved && isAutoApproveEnabled) { + customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); + } + + let shellType = basename(shell, '.exe'); + if (shellType === 'powershell') { + shellType = 'pwsh'; + } + confirmationMessages = (isAutoApproved && isAutoApproveAllowed) ? undefined : { + title: args.isBackground + ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) + : localize('runInTerminal', "Run `{0}` command?", shellType), + message: new MarkdownString(args.explanation), + disclaimer, + terminalCustomActions: customActions, + }; + } + + return { + confirmationMessages, + presentation, + toolSpecificData: { + kind: 'terminal', + terminalToolSessionId, + commandLine: { + original: args.command, + toolEdited: toolEditedCommand + }, + language, + alternativeRecommendation, + autoApproveInfo, + } + }; } async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { @@ -747,184 +892,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // #endregion -} - -export class RunInTerminalToolConfirmationHelper extends Disposable { - - private readonly _commandSimplifier: CommandSimplifier; - protected readonly _profileFetcher: TerminalProfileFetcher; - private readonly _telemetry: RunInTerminalToolTelemetry; - private readonly _commandLineAutoApprover: CommandLineAutoApprover; - - // Immutable window state - private readonly _osBackend: Promise; - - constructor( - @IInstantiationService _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, - @IStorageService private readonly _storageService: IStorageService, - @ITerminalLogService private readonly _logService: ITerminalLogService, - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, - ) { - super(); - - this._osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); - - this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); - this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); - this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); - this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); - } - - async prepareToolInvocation(context: IToolInvocationPreparationContext, terminalInstance: Pick | undefined, token: CancellationToken): Promise { - const args = context.parameters as IRunInTerminalInputParams; - - const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); - const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; - - const os = await this._osBackend; - const shell = await this._profileFetcher.getCopilotShell(); - const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; - - const instance = terminalInstance; - const terminalToolSessionId = generateUuid(); - - let toolEditedCommand: string | undefined = await this._commandSimplifier.rewriteIfNeeded(args, instance, shell); - if (toolEditedCommand === args.command) { - toolEditedCommand = undefined; - } - - let autoApproveInfo: IMarkdownString | undefined; - let confirmationMessages: IToolConfirmationMessages | undefined; - if (alternativeRecommendation) { - confirmationMessages = undefined; - } else { - // Determine auto approval, this happens even when auto approve is off to that reasoning - // can be reviewed in the terminal channel. It also allows gauging the effective set of - // commands that would be auto approved if it were enabled. - const actualCommand = toolEditedCommand ?? args.command; - const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); - const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); - const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); - const autoApproveReasons: string[] = [ - ...subCommandResults.map(e => e.reason), - commandLineResult.reason, - ]; - - let isAutoApproved = false; - let isDenied = false; - let autoApproveReason: 'subCommand' | 'commandLine' | undefined; - let autoApproveDefault: boolean | undefined; - - const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); - if (deniedSubCommandResult) { - this._logService.info('autoApprove: Sub-command DENIED auto approval'); - isDenied = true; - autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; - autoApproveReason = 'subCommand'; - } else if (commandLineResult.result === 'denied') { - this._logService.info('autoApprove: Command line DENIED auto approval'); - isDenied = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - autoApproveReason = 'commandLine'; - } else { - if (subCommandResults.every(e => e.result === 'approved')) { - this._logService.info('autoApprove: All sub-commands auto-approved'); - autoApproveReason = 'subCommand'; - isAutoApproved = true; - autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); - } else { - this._logService.info('autoApprove: All sub-commands NOT auto-approved'); - if (commandLineResult.result === 'approved') { - this._logService.info('autoApprove: Command line auto-approved'); - autoApproveReason = 'commandLine'; - isAutoApproved = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - } else { - this._logService.info('autoApprove: Command line NOT auto-approved'); - } - } - } - - // Log detailed auto approval reasoning - for (const reason of autoApproveReasons) { - this._logService.info(`- ${reason}`); - } - - // Apply auto approval or force it off depending on enablement/opt-in state - const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; - const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); - const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - if (isAutoApproveEnabled) { - autoApproveInfo = this._createAutoApproveInfo( - isAutoApproved, - isDenied, - autoApproveReason, - subCommandResults, - commandLineResult, - ); - } else { - isAutoApproved = false; - } - - // Send telemetry about auto approval process - this._telemetry.logPrepare({ - terminalToolSessionId, - subCommands, - autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', - autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', - autoApproveReason, - autoApproveDefault - }); - - // Add a disclaimer warning about prompt injection for common commands that return - // content from the web - let disclaimer: IMarkdownString | undefined; - const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); - if (!isAutoApproved && ( - subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || - (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) - )) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); - } - - let customActions: ToolConfirmationAction[] | undefined; - if (!isAutoApproved && isAutoApproveEnabled) { - customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); - } - - let shellType = basename(shell, '.exe'); - if (shellType === 'powershell') { - shellType = 'pwsh'; - } - confirmationMessages = (isAutoApproved && isAutoApproveAllowed) ? undefined : { - title: args.isBackground - ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) - : localize('runInTerminal', "Run `{0}` command?", shellType), - message: new MarkdownString(args.explanation), - disclaimer, - terminalCustomActions: customActions, - }; - } - - return { - confirmationMessages, - presentation, - toolSpecificData: { - kind: 'terminal', - terminalToolSessionId, - commandLine: { - original: args.command, - toolEdited: toolEditedCommand - }, - language, - alternativeRecommendation, - autoApproveInfo, - } - }; - } - // #region Auto approve diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index dd2832536e4..be45523bbe1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -17,7 +17,7 @@ import { IChatService, type IChatTerminalToolInvocationData } from '../../../../ import { ILanguageModelToolsService, IPreparedToolInvocation, IToolInvocationPreparationContext, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; -import { RunInTerminalTool, RunInTerminalToolConfirmationHelper, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; +import { RunInTerminalTool, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js'; import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; @@ -75,9 +75,7 @@ suite('RunInTerminalTool', () => { storageService = instantiationService.get(IStorageService); storageService.store(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, true, StorageScope.APPLICATION, StorageTarget.USER); - const confirmTerminalTool = store.add(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); - - runInTerminalTool = store.add(instantiationService.createInstance(TestRunInTerminalTool, confirmTerminalTool)); + runInTerminalTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); }); function setAutoApprove(value: { [key: string]: { approve: boolean; matchCommandLine?: boolean } | boolean }) { @@ -992,8 +990,7 @@ suite('TerminalProfileFetcher', () => { getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) }); - const confirmTerminalTool = store.add(instantiationService.createInstance(RunInTerminalToolConfirmationHelper)); - testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool, confirmTerminalTool)); + testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); }); function setConfig(key: string, value: unknown) { From 962113e37ab1bb521e3e931dc62a7c0a9e46146d Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 22:17:55 -0700 Subject: [PATCH 1449/4355] Change the way target function is accessed in unit-tests to fix build break (#272627) --- .../contrib/quickAccess/browser/gotoLineQuickAccess.ts | 2 +- .../quickAccess/test/browser/gotoLineQuickAccess.test.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index f2bb6269937..36f333b4d33 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -139,7 +139,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor }; } - private parsePosition(editor: IEditor, value: string): IPosition { + protected parsePosition(editor: IEditor, value: string): IPosition { const model = this.getModel(editor); // Support :: notation to navigate to a specific offset in the model. diff --git a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts index 2b9019a48a2..566af872fcf 100644 --- a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts +++ b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts @@ -16,6 +16,9 @@ class TestGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvide constructor(useZeroBasedOffset?: { value: boolean }) { super(useZeroBasedOffset); } + public override parsePosition(editor: IEditor, value: string) { + return super.parsePosition(editor, value); + } } suite('AbstractGotoLineQuickAccessProvider', () => { @@ -30,8 +33,7 @@ suite('AbstractGotoLineQuickAccessProvider', () => { 'line 4', 'line 5' ], {}, (editor, _) => { - // eslint-disable-next-line local/code-no-any-casts - const { lineNumber, column } = (provider as any).parsePosition(editor, input); + const { lineNumber, column } = provider.parsePosition(editor, input); assert.strictEqual(lineNumber, expectedLine); assert.strictEqual(column, expectedColumn); }); From 55505bf0b5b7abbe820137bf8e01bed9b72675cf Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Tue, 21 Oct 2025 22:48:19 -0700 Subject: [PATCH 1450/4355] Another build break fix: don't override, just call (#272639) --- .../quickAccess/test/browser/gotoLineQuickAccess.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts index 566af872fcf..27175748811 100644 --- a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts +++ b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts @@ -16,7 +16,7 @@ class TestGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvide constructor(useZeroBasedOffset?: { value: boolean }) { super(useZeroBasedOffset); } - public override parsePosition(editor: IEditor, value: string) { + public parsePositionTest(editor: IEditor, value: string) { return super.parsePosition(editor, value); } } @@ -33,7 +33,7 @@ suite('AbstractGotoLineQuickAccessProvider', () => { 'line 4', 'line 5' ], {}, (editor, _) => { - const { lineNumber, column } = provider.parsePosition(editor, input); + const { lineNumber, column } = provider.parsePositionTest(editor, input); assert.strictEqual(lineNumber, expectedLine); assert.strictEqual(column, expectedColumn); }); From 1fd26136285b60269c2376b539fd9cbb67d7edfb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 09:43:53 +0200 Subject: [PATCH 1451/4355] Implement infinite scrolling (#272451) * Implement infinite scrolling * hdnle ct * feedback * feedback --- src/vs/base/browser/ui/list/listPaging.ts | 8 +- src/vs/base/common/paging.ts | 119 ++++++- .../base/test/common/iterativePaging.test.ts | 300 ++++++++++++++++++ .../platform/mcp/common/mcpGalleryService.ts | 41 ++- src/vs/platform/mcp/common/mcpManagement.ts | 4 +- .../extensions/browser/extensionsViews.ts | 4 + .../contrib/mcp/browser/mcpServersView.ts | 5 +- .../mcp/browser/mcpWorkbenchService.ts | 25 +- .../workbench/contrib/mcp/common/mcpTypes.ts | 4 +- 9 files changed, 469 insertions(+), 41 deletions(-) create mode 100644 src/vs/base/test/common/iterativePaging.test.ts diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index cb486cfaaba..04c2b8de2ef 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import { range } from '../../../common/arrays.js'; import { CancellationTokenSource } from '../../../common/cancellation.js'; import { Event } from '../../../common/event.js'; -import { Disposable, IDisposable } from '../../../common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js'; import { IPagedModel } from '../../../common/paging.js'; import { ScrollbarVisibility } from '../../../common/scrollable.js'; import './list.css'; @@ -122,8 +122,9 @@ function fromPagedListOptions(modelProvider: () => IPagedModel, options: I export class PagedList implements IDisposable { - private list: List; + private readonly list: List; private _model!: IPagedModel; + private readonly modelDisposables = new DisposableStore(); constructor( user: string, @@ -202,8 +203,10 @@ export class PagedList implements IDisposable { } set model(model: IPagedModel) { + this.modelDisposables.clear(); this._model = model; this.list.splice(0, this.list.length, range(model.length)); + this.modelDisposables.add(model.onDidIncrementLength(newLength => this.list.splice(this.list.length, 0, range(this.list.length, newLength)))); } get length(): number { @@ -296,5 +299,6 @@ export class PagedList implements IDisposable { dispose(): void { this.list.dispose(); + this.modelDisposables.dispose(); } } diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts index 463852a8cd7..295600dac63 100644 --- a/src/vs/base/common/paging.ts +++ b/src/vs/base/common/paging.ts @@ -6,6 +6,7 @@ import { range } from './arrays.js'; import { CancellationToken, CancellationTokenSource } from './cancellation.js'; import { CancellationError } from './errors.js'; +import { Event, Emitter } from './event.js'; /** * A Pager is a stateless abstraction over a paged collection. @@ -17,6 +18,16 @@ export interface IPager { getPage(pageIndex: number, cancellationToken: CancellationToken): Promise; } +export interface IIterativePage { + readonly items: T[]; + readonly hasMore: boolean; +} + +export interface IIterativePager { + readonly firstPage: IIterativePage; + getNextPage(cancellationToken: CancellationToken): Promise>; +} + export interface IPageIterator { elements: T[]; total: number; @@ -46,7 +57,8 @@ function createPage(elements?: T[]): IPage { * A PagedModel is a stateful model over an abstracted paged collection. */ export interface IPagedModel { - length: number; + readonly length: number; + readonly onDidIncrementLength: Event; isResolved(index: number): boolean; get(index: number): T; resolve(index: number, cancellationToken: CancellationToken): Promise; @@ -69,6 +81,7 @@ export class PagedModel implements IPagedModel { private pages: IPage[] = []; get length(): number { return this.pager.total; } + readonly onDidIncrementLength = Event.None; constructor(arg: IPager | T[]) { this.pager = Array.isArray(arg) ? singlePagePager(arg) : arg; @@ -147,8 +160,9 @@ export class PagedModel implements IPagedModel { export class DelayedPagedModel implements IPagedModel { get length(): number { return this.model.length; } + get onDidIncrementLength() { return this.model.onDidIncrementLength; } - constructor(private model: IPagedModel, private timeout: number = 500) { } + constructor(private readonly model: IPagedModel, private timeout: number = 500) { } isResolved(index: number): boolean { return this.model.isResolved(index); @@ -265,6 +279,107 @@ export class PageIteratorPager implements IPager { } } +export class IterativePagedModel implements IPagedModel { + + private items: T[] = []; + private _hasNextPage = true; + private readonly _onDidIncrementLength = new Emitter(); + private loadingPromise: Promise | null = null; + + private readonly pager: IIterativePager; + + constructor(pager: IIterativePager) { + this.pager = pager; + this.items = [...pager.firstPage.items]; + this._hasNextPage = pager.firstPage.hasMore; + } + + get onDidIncrementLength(): Event { + return this._onDidIncrementLength.event; + } + + /** + * Returns actual length + 1 if there are more pages (sentinel approach) + */ + get length(): number { + return this.items.length + (this._hasNextPage ? 1 : 0); + } + + /** + * Sentinel item is never resolved - it triggers loading + */ + isResolved(index: number): boolean { + if (index === this.items.length && this._hasNextPage) { + return false; // This will trigger resolve() call + } + return index < this.items.length; + } + + get(index: number): T { + if (index < this.items.length) { + return this.items[index]; + } + throw new Error('Item not resolved yet'); + } + + /** + * When sentinel item is accessed, load next page + */ + async resolve(index: number, cancellationToken: CancellationToken): Promise { + if (cancellationToken.isCancellationRequested) { + return Promise.reject(new CancellationError()); + } + + // If trying to resolve the sentinel item, load next page + if (index === this.items.length && this._hasNextPage) { + await this.loadNextPage(cancellationToken); + } + + // After loading, the requested index should now be valid + if (index < this.items.length) { + return this.items[index]; + } + + throw new Error('Index out of bounds'); + } + + private async loadNextPage(cancellationToken: CancellationToken): Promise { + if (!this._hasNextPage) { + return; + } + + // If already loading, return the cached promise + if (this.loadingPromise) { + await this.loadingPromise; + return; + } + + const pagePromise = this.pager.getNextPage(cancellationToken); + + this.loadingPromise = pagePromise + .then(page => { + this.items.push(...page.items); + this._hasNextPage = page.hasMore; + + // Clear the loading promise before firing the event + // so that event handlers can trigger loading the next page if needed + this.loadingPromise = null; + + // Fire length update event + this._onDidIncrementLength.fire(this.length); + }, err => { + this.loadingPromise = null; + throw err; + }); + + await this.loadingPromise; + } + + dispose(): void { + this._onDidIncrementLength.dispose(); + } +} + /** * Similar to array.map, `mapPager` lets you map the elements of an * abstract paged collection to another type. diff --git a/src/vs/base/test/common/iterativePaging.test.ts b/src/vs/base/test/common/iterativePaging.test.ts new file mode 100644 index 00000000000..395b928a99a --- /dev/null +++ b/src/vs/base/test/common/iterativePaging.test.ts @@ -0,0 +1,300 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { IterativePagedModel, IIterativePager, IIterativePage } from '../../common/paging.js'; + +function createTestPager(pageSize: number, maxPages: number): IIterativePager { + let currentPage = 0; + + const createPage = (page: number): IIterativePage => { + const start = page * pageSize; + const items: number[] = []; + for (let i = 0; i < pageSize; i++) { + items.push(start + i); + } + const hasMore = page + 1 < maxPages; + return { items, hasMore }; + }; + + return { + firstPage: createPage(currentPage++), + getNextPage: async (cancellationToken: CancellationToken): Promise> => { + if (currentPage >= maxPages) { + return { items: [], hasMore: false }; + } + return createPage(currentPage++); + } + }; +} + +suite('IterativePagedModel', () => { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('initial state', () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Initially first page is loaded, so length should be 10 + 1 sentinel + assert.strictEqual(model.length, 11); + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(9), true); + assert.strictEqual(model.isResolved(10), false); // sentinel + }); + + test('load first page via sentinel access', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Access an item in the first page (already loaded) + const item = await model.resolve(0, CancellationToken.None); + + assert.strictEqual(item, 0); + assert.strictEqual(model.length, 11); // 10 items + 1 sentinel + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(9), true); + assert.strictEqual(model.isResolved(10), false); // sentinel + }); + + test('load multiple pages', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // First page already loaded + assert.strictEqual(model.length, 11); + + // Load second page by accessing its sentinel + await model.resolve(10, CancellationToken.None); + assert.strictEqual(model.length, 21); // 20 items + 1 sentinel + assert.strictEqual(model.get(10), 10); // First item of second page + + // Load third (final) page + await model.resolve(20, CancellationToken.None); + assert.strictEqual(model.length, 30); // 30 items, no sentinel (no more pages) + }); + + test('onDidIncrementLength event fires correctly', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + const lengths: number[] = []; + + store.add(model.onDidIncrementLength((length: number) => lengths.push(length))); + + // Load second page + await model.resolve(10, CancellationToken.None); + + assert.strictEqual(lengths.length, 1); + assert.strictEqual(lengths[0], 21); // 20 items + 1 sentinel + + // Load third page + await model.resolve(20, CancellationToken.None); + + assert.strictEqual(lengths.length, 2); + assert.strictEqual(lengths[1], 30); // 30 items, no sentinel + }); + + test('accessing regular items does not trigger loading', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + const initialLength = model.length; + + // Access items within the loaded range + assert.strictEqual(model.get(5), 5); + assert.strictEqual(model.isResolved(5), true); + + // Length should not change + assert.strictEqual(model.length, initialLength); + }); + + test('reaching end of data removes sentinel', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load all pages + await model.resolve(10, CancellationToken.None); // Page 2 + await model.resolve(20, CancellationToken.None); // Page 3 (final) + + // After loading all data, there should be no more pages + assert.strictEqual(model.length, 30); // Exactly 30 items, no sentinel + + // Accessing resolved items should work + assert.strictEqual(model.isResolved(29), true); + assert.strictEqual(model.isResolved(30), false); + }); + + test('concurrent access to sentinel only loads once', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Access sentinel concurrently + const [item1, item2, item3] = await Promise.all([ + model.resolve(10, CancellationToken.None), + model.resolve(10, CancellationToken.None), + model.resolve(10, CancellationToken.None) + ]); + + // All should get the same item + assert.strictEqual(item1, 10); + assert.strictEqual(item2, 10); + assert.strictEqual(item3, 10); + assert.strictEqual(model.length, 21); // 20 items + 1 sentinel + }); + + test('empty pager with no items', () => { + const emptyPager: IIterativePager = { + firstPage: { items: [], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; + const model = store.add(new IterativePagedModel(emptyPager)); + + assert.strictEqual(model.length, 0); + assert.strictEqual(model.isResolved(0), false); + }); + + test('single page pager with no more pages', () => { + const singlePagePager: IIterativePager = { + firstPage: { items: [1, 2, 3], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; + const model = store.add(new IterativePagedModel(singlePagePager)); + + assert.strictEqual(model.length, 3); // No sentinel + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(2), true); + assert.strictEqual(model.isResolved(3), false); + assert.strictEqual(model.get(0), 1); + assert.strictEqual(model.get(2), 3); + }); + + test('accessing item beyond loaded range throws', () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Try to access item beyond current length + assert.throws(() => model.get(15), /Item not resolved yet/); + }); + + test('resolving item beyond all pages throws', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load all pages + await model.resolve(10, CancellationToken.None); + await model.resolve(20, CancellationToken.None); + + // Try to resolve beyond the last item + await assert.rejects( + async () => model.resolve(30, CancellationToken.None), + /Index out of bounds/ + ); + }); + + test('cancelled token during initial resolve', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + const cts = new CancellationTokenSource(); + cts.cancel(); + + await assert.rejects( + async () => model.resolve(0, cts.token), + /Canceled/ + ); + }); + + test('event fires for each page load', async () => { + const pager = createTestPager(5, 4); + const model = store.add(new IterativePagedModel(pager)); + const lengths: number[] = []; + + store.add(model.onDidIncrementLength((length: number) => lengths.push(length))); + + // Initially has first page (5 items + 1 sentinel = 6) + assert.strictEqual(model.length, 6); + + // Load page 2 + await model.resolve(5, CancellationToken.None); + assert.deepStrictEqual(lengths, [11]); // 10 items + 1 sentinel + + // Load page 3 + await model.resolve(10, CancellationToken.None); + assert.deepStrictEqual(lengths, [11, 16]); // 15 items + 1 sentinel + + // Load page 4 (final) + await model.resolve(15, CancellationToken.None); + assert.deepStrictEqual(lengths, [11, 16, 20]); // 20 items, no sentinel + }); + + test('sequential page loads work correctly', async () => { + const pager = createTestPager(5, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load pages sequentially + for (let page = 1; page < 3; page++) { + const sentinelIndex = page * 5; + await model.resolve(sentinelIndex, CancellationToken.None); + } + + // Verify all items are accessible + assert.strictEqual(model.length, 15); // 3 pages * 5 items, no sentinel + for (let i = 0; i < 15; i++) { + assert.strictEqual(model.get(i), i); + assert.strictEqual(model.isResolved(i), true); + } + }); + + test('accessing items after loading all pages', async () => { + const pager = createTestPager(10, 2); + const model = store.add(new IterativePagedModel(pager)); + + // Load second page + await model.resolve(10, CancellationToken.None); + + // No sentinel after loading all pages + assert.strictEqual(model.length, 20); + assert.strictEqual(model.isResolved(19), true); + assert.strictEqual(model.isResolved(20), false); + + // All items should be accessible + for (let i = 0; i < 20; i++) { + assert.strictEqual(model.get(i), i); + } + }); + + test('pager with varying page sizes', async () => { + let pageNum = 0; + const varyingPager: IIterativePager = { + firstPage: { items: ['a', 'b', 'c'], hasMore: true }, + getNextPage: async (): Promise> => { + pageNum++; + if (pageNum === 1) { + return { items: ['d', 'e'], hasMore: true }; + } else if (pageNum === 2) { + return { items: ['f', 'g', 'h', 'i'], hasMore: false }; + } + return { items: [], hasMore: false }; + } + }; + + const model = store.add(new IterativePagedModel(varyingPager)); + + assert.strictEqual(model.length, 4); // 3 items + 1 sentinel + + // Load second page (2 items) + await model.resolve(3, CancellationToken.None); + assert.strictEqual(model.length, 6); // 5 items + 1 sentinel + assert.strictEqual(model.get(3), 'd'); + + // Load third page (4 items) + await model.resolve(5, CancellationToken.None); + assert.strictEqual(model.length, 9); // 9 items, no sentinel + assert.strictEqual(model.get(5), 'f'); + assert.strictEqual(model.get(8), 'i'); + }); +}); diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index e2f5e0bb7cf..68cd37db738 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -15,7 +15,7 @@ import { ILogService } from '../../log/common/log.js'; import { asJson, asText, IRequestService } from '../../request/common/request.js'; import { GalleryMcpServerStatus, IGalleryMcpServer, IMcpGalleryService, IMcpServerArgument, IMcpServerInput, IMcpServerKeyValueInput, IMcpServerPackage, IQueryOptions, RegistryType, SseTransport, StreamableHttpTransport, Transport, TransportType } from './mcpManagement.js'; import { IMcpGalleryManifestService, McpGalleryManifestStatus, getMcpGalleryManifestResourceUri, McpGalleryResourceType, IMcpGalleryManifest } from './mcpGalleryManifest.js'; -import { IPageIterator, IPager, PageIteratorPager, singlePagePager } from '../../../base/common/paging.js'; +import { IIterativePager, IIterativePage } from '../../../base/common/paging.js'; import { CancellationError } from '../../../base/common/errors.js'; import { isObject, isString } from '../../../base/common/types.js'; @@ -717,10 +717,13 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return this.mcpGalleryManifestService.mcpGalleryManifestStatus === McpGalleryManifestStatus.Available; } - async query(options?: IQueryOptions, token: CancellationToken = CancellationToken.None): Promise> { + async query(options?: IQueryOptions, token: CancellationToken = CancellationToken.None): Promise> { const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); if (!mcpGalleryManifest) { - return singlePagePager([]); + return { + firstPage: { items: [], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; } let query = new Query(); @@ -729,28 +732,22 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } const { servers, metadata } = await this.queryGalleryMcpServers(query, mcpGalleryManifest, token); - let total = metadata.nextCursor ? metadata.count + query.pageSize : metadata.count; - const getNextPage = async (cursor: string | undefined, ct: CancellationToken): Promise> => { - if (ct.isCancellationRequested) { - throw new CancellationError(); + let currentCursor = metadata.nextCursor; + return { + firstPage: { items: servers, hasMore: !!metadata.nextCursor }, + getNextPage: async (ct: CancellationToken): Promise> => { + if (ct.isCancellationRequested) { + throw new CancellationError(); + } + if (!currentCursor) { + return { items: [], hasMore: false }; + } + const { servers, metadata: nextMetadata } = await this.queryGalleryMcpServers(query.withPage(currentCursor).withSearchText(undefined), mcpGalleryManifest, ct); + currentCursor = nextMetadata.nextCursor; + return { items: servers, hasMore: !!nextMetadata.nextCursor }; } - const { servers, metadata } = cursor ? await this.queryGalleryMcpServers(query.withPage(cursor).withSearchText(undefined), mcpGalleryManifest, token) : { servers: [], metadata: { count: 0 } }; - total = metadata.nextCursor ? total + query.pageSize : total; - return { - elements: servers, - total, - hasNextPage: !!cursor, - getNextPage: (token) => getNextPage(metadata.nextCursor, token) - }; }; - - return new PageIteratorPager({ - elements: servers, - total, - hasNextPage: !!metadata.nextCursor, - getNextPage: (token) => getNextPage(metadata.nextCursor, token), - }); } async getMcpServersFromGallery(names: string[]): Promise { diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 8cf71bb530c..ec5e9325e99 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -6,7 +6,7 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; -import { IPager } from '../../../base/common/paging.js'; +import { IIterativePager } from '../../../base/common/paging.js'; import { URI } from '../../../base/common/uri.js'; import { SortBy, SortOrder } from '../../extensionManagement/common/extensionManagement.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; @@ -165,7 +165,7 @@ export const IMcpGalleryService = createDecorator('IMcpGalle export interface IMcpGalleryService { readonly _serviceBrand: undefined; isEnabled(): boolean; - query(options?: IQueryOptions, token?: CancellationToken): Promise>; + query(options?: IQueryOptions, token?: CancellationToken): Promise>; getMcpServersFromGallery(urls: string[]): Promise; getMcpServer(url: string): Promise; getReadme(extension: IGalleryMcpServer, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 50c0f87a277..3e1d8735e2e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -1566,6 +1566,10 @@ export class PreferredExtensionsPagedModel implements IPagedModel { public readonly length: number; + get onDidIncrementLength(): Event { + return Event.None; + } + constructor( private readonly preferredExtensions: IExtension[], private readonly pager: IPager, diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts index 51842d678ce..7eb368978f8 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -10,7 +10,7 @@ import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js' import { Emitter, Event } from '../../../../base/common/event.js'; import { markdownCommandLink, MarkdownString } from '../../../../base/common/htmlContent.js'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; -import { DelayedPagedModel, IPagedModel, PagedModel } from '../../../../base/common/paging.js'; +import { DelayedPagedModel, IPagedModel, PagedModel, IterativePagedModel } from '../../../../base/common/paging.js'; import { localize, localize2 } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyDefinedExpr, ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -323,7 +323,8 @@ export class McpServersListView extends AbstractExtensionsListView>()); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index e4f66303f51..407b84e5756 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -39,7 +39,7 @@ import { McpServerInstallData, McpServerInstallClassification } from '../common/ import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerEnablementState, McpServerInstallState, McpServerEnablementStatus, McpServersGalleryStatusContext } from '../common/mcpTypes.js'; import { McpServerEditorInput } from './mcpServerEditorInput.js'; import { IMcpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; -import { IPager, singlePagePager } from '../../../../base/common/paging.js'; +import { IIterativePager, IIterativePage } from '../../../../base/common/paging.js'; import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { runOnChange } from '../../../../base/common/observable.js'; import Severity from '../../../../base/common/severity.js'; @@ -338,18 +338,25 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } } - async queryGallery(options?: IQueryOptions, token?: CancellationToken): Promise> { + async queryGallery(options?: IQueryOptions, token?: CancellationToken): Promise> { if (!this.mcpGalleryService.isEnabled()) { - return singlePagePager([]); + return { + firstPage: { items: [], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; } const pager = await this.mcpGalleryService.query(options, token); + + const mapPage = (page: IIterativePage): IIterativePage => ({ + items: page.items.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined)), + hasMore: page.hasMore + }); + return { - firstPage: pager.firstPage.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined)), - total: pager.total, - pageSize: pager.pageSize, - getPage: async (pageIndex, token) => { - const page = await pager.getPage(pageIndex, token); - return page.map(gallery => this.fromGallery(gallery) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), e => this.getRuntimeStatus(e), undefined, gallery, undefined)); + firstPage: mapPage(pager.firstPage), + getNextPage: async (ct) => { + const nextPage = await pager.getNextPage(ct); + return mapPage(nextPage); } }; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts index 7bc9dadc0f4..2b1c0fc8f0d 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpTypes.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpTypes.ts @@ -12,7 +12,7 @@ import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { equals as objectsEqual } from '../../../../base/common/objects.js'; import { IObservable, ObservableMap } from '../../../../base/common/observable.js'; -import { IPager } from '../../../../base/common/paging.js'; +import { IIterativePager } from '../../../../base/common/paging.js'; import Severity from '../../../../base/common/severity.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { Location } from '../../../../editor/common/languages.js'; @@ -740,7 +740,7 @@ export interface IMcpWorkbenchService { readonly local: readonly IWorkbenchMcpServer[]; getEnabledLocalMcpServers(): IWorkbenchLocalMcpServer[]; queryLocal(): Promise; - queryGallery(options?: IQueryOptions, token?: CancellationToken): Promise>; + queryGallery(options?: IQueryOptions, token?: CancellationToken): Promise>; canInstall(mcpServer: IWorkbenchMcpServer): true | IMarkdownString; install(server: IWorkbenchMcpServer, installOptions?: IWorkbencMcpServerInstallOptions): Promise; uninstall(mcpServer: IWorkbenchMcpServer): Promise; From 194c4c8526ae79afd443185df11dea1ab9745f88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 09:45:22 +0200 Subject: [PATCH 1452/4355] adopt to v0.1 (#272478) --- .../platform/mcp/common/mcpGalleryManifest.ts | 1 - .../mcp/common/mcpGalleryManifestService.ts | 6 +- .../platform/mcp/common/mcpGalleryService.ts | 70 ++++++++++--------- src/vs/platform/mcp/common/mcpManagement.ts | 4 +- .../test/common/mcpManagementService.test.ts | 29 +++++++- 5 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index d75a920a351..d1b21381d8c 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -8,7 +8,6 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; export const enum McpGalleryResourceType { McpServersQueryService = 'McpServersQueryService', - McpServersSearchService = 'McpServersSearchService', McpServerWebUri = 'McpServerWebUriTemplate', McpServerVersionUri = 'McpServerVersionUriTemplate', McpServerLatestVersionUri = 'McpServerLatestVersionUriTemplate', diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index c0424d3f3eb..294e09ef3be 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -34,7 +34,7 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery protected createMcpGalleryManifest(url: string): IMcpGalleryManifest { url = url.endsWith('/') ? url.slice(0, -1) : url; const isProductGalleryUrl = this.productService.mcpGallery?.serviceUrl === url; - const version = 'v0'; + const version = 'v0.1'; const serversUrl = `${url}/${version}/servers`; const resources = [ { @@ -52,10 +52,6 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery ]; if (isProductGalleryUrl) { - resources.push({ - id: `${serversUrl}/search`, - type: McpGalleryResourceType.McpServersSearchService - }); resources.push({ id: `${serversUrl}/by-name/{name}`, type: McpGalleryResourceType.McpServerNamedResourceUri diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index 68cd37db738..e405e9a383b 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -33,10 +33,11 @@ interface IGitHubInfo { readonly license?: string; readonly opengraphImageUrl?: string; readonly ownerAvatarUrl?: string; + readonly preferredImage?: string; readonly primaryLanguage?: string; readonly primaryLanguageColor?: string; readonly pushedAt?: string; - readonly readMe?: string; + readonly readme?: string; readonly stargazerCount?: number; readonly topics?: readonly string[]; readonly usesCustomOpengraphImage?: boolean; @@ -70,8 +71,8 @@ interface IRawGalleryMcpServer { readonly source: string; readonly url: string; readonly id?: string; - readonly readme?: string; }; + readonly readme?: string; readonly icons?: readonly IRawGalleryMcpServerIcon[]; readonly status?: GalleryMcpServerStatus; readonly websiteUrl?: string; @@ -221,7 +222,6 @@ namespace McpServerSchemaVersion_v2025_07_09 { interface RawGalleryMcpServersResult { readonly metadata: { readonly count: number; - readonly total?: number; readonly next_cursor?: string; }; readonly servers: readonly RawGalleryMcpServer[]; @@ -336,7 +336,7 @@ namespace McpServerSchemaVersion_v2025_07_09 { }; } - function convertTransport(input: RawGalleryTransport): Transport | undefined { + function convertTransport(input: RawGalleryTransport): Transport { switch (input.type) { case 'stdio': return { @@ -355,7 +355,9 @@ namespace McpServerSchemaVersion_v2025_07_09 { headers: input.headers?.map(convertKeyValueInput), }; default: - return undefined; + return { + type: TransportType.STDIO, + }; } } @@ -387,8 +389,8 @@ namespace McpServerSchemaVersion_v2025_07_09 { url: from.repository.url, source: from.repository.source, id: from.repository.id, - readme: from.repository.readme } : undefined, + readme: from.repository?.readme, version: from.version, createdAt: from.created_at, updatedAt: from.updated_at, @@ -398,7 +400,7 @@ namespace McpServerSchemaVersion_v2025_07_09 { version: p.version, fileSha256: p.file_sha256, registryBaseUrl: p.registry_base_url, - transport: p.transport ? convertTransport(p.transport) : undefined, + transport: p.transport ? convertTransport(p.transport) : { type: TransportType.STDIO }, packageArguments: p.package_arguments?.map(convertServerArgument), runtimeHint: p.runtime_hint, runtimeArguments: p.runtime_arguments?.map(convertServerArgument), @@ -445,14 +447,14 @@ namespace McpServerSchemaVersion_v0_1 { export const SCHEMA = `https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json`; interface RawGalleryMcpServerInput { + readonly choices?: readonly string[]; + readonly default?: string; readonly description?: string; - readonly isRequired?: boolean; readonly format?: 'string' | 'number' | 'boolean' | 'filepath'; - readonly value?: string; + readonly isRequired?: boolean; readonly isSecret?: boolean; - readonly default?: string; readonly placeholder?: string; - readonly choices?: readonly string[]; + readonly value?: string; } interface RawGalleryMcpServerVariableInput extends RawGalleryMcpServerInput { @@ -473,7 +475,6 @@ namespace McpServerSchemaVersion_v0_1 { interface RawGalleryMcpServerKeyValueInput extends RawGalleryMcpServerVariableInput { readonly name: string; - readonly value?: string; } type RawGalleryMcpServerArgument = RawGalleryMcpServerPositionalArgument | RawGalleryMcpServerNamedArgument; @@ -499,30 +500,30 @@ namespace McpServerSchemaVersion_v0_1 { } interface RawGalleryMcpServerPackage { - readonly registryType: RegistryType; readonly identifier: string; - readonly version: string; + readonly registryType: RegistryType; readonly transport: RawGalleryTransport; - readonly registryBaseUrl?: string; readonly fileSha256?: string; + readonly environmentVariables?: ReadonlyArray; readonly packageArguments?: readonly RawGalleryMcpServerArgument[]; - readonly runtimeHint?: string; + readonly registryBaseUrl?: string; readonly runtimeArguments?: readonly RawGalleryMcpServerArgument[]; - readonly environmentVariables?: ReadonlyArray; + readonly runtimeHint?: string; + readonly version?: string; } interface RawGalleryMcpServer { readonly name: string; readonly description: string; readonly version: string; - readonly $schema?: string; + readonly $schema: string; readonly title?: string; readonly icons?: IRawGalleryMcpServerIcon[]; readonly repository?: { readonly source: string; readonly url: string; + readonly subfolder?: string; readonly id?: string; - readonly readme?: string; }; readonly websiteUrl?: string; readonly packages?: readonly RawGalleryMcpServerPackage[]; @@ -534,12 +535,12 @@ namespace McpServerSchemaVersion_v0_1 { interface RawGalleryMcpServerInfo { readonly server: RawGalleryMcpServer; - readonly _meta?: { + readonly _meta: { readonly 'io.modelcontextprotocol.registry/official'?: { readonly status: GalleryMcpServerStatus; readonly isLatest: boolean; readonly publishedAt: string; - readonly updatedAt: string; + readonly updatedAt?: string; }; }; } @@ -547,7 +548,6 @@ namespace McpServerSchemaVersion_v0_1 { interface RawGalleryMcpServersResult { readonly metadata: { readonly count: number; - readonly total?: number; readonly nextCursor?: string; }; readonly servers: readonly RawGalleryMcpServerInfo[]; @@ -601,7 +601,8 @@ namespace McpServerSchemaVersion_v0_1 { return undefined; } - const { 'io.modelcontextprotocol.registry/official': registryInfo, ...apicInfo } = from._meta ?? {}; + const { 'io.modelcontextprotocol.registry/official': registryInfo, ...apicInfo } = from._meta; + const githubInfo = from.server._meta?.['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined; return { name: from.server.name, @@ -612,15 +613,15 @@ namespace McpServerSchemaVersion_v0_1 { url: from.server.repository.url, source: from.server.repository.source, id: from.server.repository.id, - readme: from.server.repository.readme } : undefined, + readme: githubInfo?.readme, icons: from.server.icons, websiteUrl: from.server.websiteUrl, packages: from.server.packages, remotes: from.server.remotes, status: registryInfo?.status, registryInfo, - githubInfo: from.server._meta?.['io.modelcontextprotocol.registry/publisher-provided']?.github as IGitHubInfo | undefined, + githubInfo, apicInfo }; } @@ -845,7 +846,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService let icon: { light: string; dark: string } | undefined; - if (server.githubInfo?.ownerAvatarUrl) { + if (server.githubInfo?.preferredImage) { + icon = { + light: server.githubInfo.preferredImage, + dark: server.githubInfo.preferredImage + }; + } + + else if (server.githubInfo?.ownerAvatarUrl) { icon = { light: server.githubInfo.ownerAvatarUrl, dark: server.githubInfo.ownerAvatarUrl @@ -883,7 +891,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService publishDate: server.registryInfo?.publishedAt ? Date.parse(server.registryInfo.publishedAt) : undefined, lastUpdated: server.githubInfo?.pushedAt ? Date.parse(server.githubInfo.pushedAt) : server.registryInfo?.updatedAt ? Date.parse(server.registryInfo.updatedAt) : undefined, repositoryUrl: server.repository?.url, - readme: server.repository?.readme, + readme: server.readme, icon, publisher, publisherUrl, @@ -906,7 +914,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } private async queryRawGalleryMcpServers(query: Query, mcpGalleryManifest: IMcpGalleryManifest, token: CancellationToken): Promise { - const mcpGalleryUrl = query.searchText && mcpGalleryManifest.version === 'v0' ? this.getSearchUrl(mcpGalleryManifest) : this.getMcpGalleryUrl(mcpGalleryManifest); + const mcpGalleryUrl = this.getMcpGalleryUrl(mcpGalleryManifest); if (!mcpGalleryUrl) { return { servers: [], metadata: { count: 0 } }; } @@ -928,7 +936,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } if (query.searchText) { const text = encodeURIComponent(query.searchText); - url += mcpGalleryManifest.version === 'v0' ? `&q=${text}` : `&search=${text}`; + url += `&search=${text}`; } const context = await this.requestService.request({ @@ -1008,10 +1016,6 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return format2(latestVersionResourceUriTemplate, { name }); } - private getSearchUrl(mcpGalleryManifest: IMcpGalleryManifest): string | undefined { - return getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServersSearchService); - } - private getWebUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerWebUri); if (!resourceUriTemplate) { diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index ec5e9325e99..e80ff40683e 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -105,8 +105,8 @@ export type Transport = StdioTransport | StreamableHttpTransport | SseTransport; export interface IMcpServerPackage { readonly registryType: RegistryType; readonly identifier: string; - readonly version: string; - readonly transport?: Transport; + readonly transport: Transport; + readonly version?: string; readonly registryBaseUrl?: string; readonly fileSha256?: string; readonly packageArguments?: readonly IMcpServerArgument[]; diff --git a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts index daf8d844e70..1756c100a36 100644 --- a/src/vs/platform/mcp/test/common/mcpManagementService.test.ts +++ b/src/vs/platform/mcp/test/common/mcpManagementService.test.ts @@ -62,6 +62,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { registryType: RegistryType.NODE, registryBaseUrl: 'https://registry.npmjs.org', identifier: '@modelcontextprotocol/server-brave-search', + transport: { type: TransportType.STDIO }, version: '1.0.2', environmentVariables: [{ name: 'BRAVE_API_KEY', @@ -87,7 +88,8 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { registryType: RegistryType.NODE, registryBaseUrl: 'https://registry.npmjs.org', identifier: '@modelcontextprotocol/everything', - version: '' + version: '', + transport: { type: TransportType.STDIO } }] }; @@ -104,6 +106,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'test-server', version: '1.0.0', environmentVariables: [{ @@ -137,6 +140,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: '@modelcontextprotocol/server-brave-search', version: '1.0.2', environmentVariables: [{ @@ -169,6 +173,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'test-server', version: '1.0.0', environmentVariables: [{ @@ -202,6 +207,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'snyk', version: '1.1298.0', packageArguments: [ @@ -230,6 +236,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.PYTHON, + transport: { type: TransportType.STDIO }, registryBaseUrl: 'https://pypi.org', identifier: 'weather-mcp-server', version: '0.5.0', @@ -260,6 +267,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.PYTHON, + transport: { type: TransportType.STDIO }, identifier: 'weather-mcp-server', version: '' }] @@ -278,6 +286,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.DOCKER, + transport: { type: TransportType.STDIO }, registryBaseUrl: 'https://docker.io', identifier: 'mcp/filesystem', version: '1.0.2', @@ -320,6 +329,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.DOCKER, + transport: { type: TransportType.STDIO }, identifier: 'example/database-manager-mcp', version: '3.1.0', runtimeArguments: [{ @@ -358,6 +368,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.DOCKER, + transport: { type: TransportType.STDIO }, identifier: 'example/database-manager-mcp', version: '3.1.0', packageArguments: [{ @@ -410,6 +421,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.DOCKER, identifier: 'example/test-image', + transport: { type: TransportType.STDIO }, version: '1.0.0' }] }; @@ -432,6 +444,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NUGET, + transport: { type: TransportType.STDIO }, registryBaseUrl: 'https://api.nuget.org', identifier: 'Knapcode.SampleMcpServer', version: '0.5.0', @@ -456,6 +469,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NUGET, + transport: { type: TransportType.STDIO }, identifier: 'Knapcode.SampleMcpServer', version: '0.4.0-beta', packageArguments: [{ @@ -610,6 +624,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', environmentVariables: [{ name: 'CONNECTION_STRING', @@ -658,6 +673,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', runtimeArguments: [{ type: 'named', @@ -688,6 +704,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.DOCKER, identifier: 'test-image', + transport: { type: TransportType.STDIO }, version: '1.0.0', packageArguments: [{ type: 'named', @@ -733,6 +750,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: '@example/math-tool', + transport: { type: TransportType.STDIO }, version: '2.0.1', packageArguments: [{ type: 'positional', @@ -777,6 +795,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.PYTHON, + transport: { type: TransportType.STDIO }, identifier: 'python-server', version: '1.0.0' }] @@ -795,10 +814,12 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.PYTHON, + transport: { type: TransportType.STDIO }, identifier: 'python-server', version: '1.0.0' }, { registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'node-server', version: '2.0.0' }] @@ -816,6 +837,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'test-server', version: '1.0.0' }] @@ -832,6 +854,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { const manifest: IGalleryMcpServerConfiguration = { packages: [{ registryType: RegistryType.NODE, + transport: { type: TransportType.STDIO }, identifier: 'test-server', version: '1.0.0', runtimeArguments: [{ @@ -854,6 +877,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', packageArguments: [{ type: 'positional', @@ -875,6 +899,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', runtimeArguments: [{ type: 'named', @@ -901,6 +926,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', runtimeArguments: [{ type: 'named', @@ -930,6 +956,7 @@ suite('McpManagementService - getMcpServerConfigurationFromManifest', () => { packages: [{ registryType: RegistryType.NODE, identifier: 'test-server', + transport: { type: TransportType.STDIO }, version: '1.0.0', environmentVariables: [{ name: 'API_KEY', From 8ac9f453a03338cb15869281a4d286befb64c410 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 09:47:02 +0200 Subject: [PATCH 1453/4355] Support advanced settings. (#271397) * Support advanced settings. Fixes #270142 * add preview tag --- .../api/common/configurationExtensionPoint.ts | 20 ++++- .../preferences/browser/settingsEditor2.ts | 24 ++++-- .../preferences/browser/settingsLayout.ts | 11 +++ .../preferences/browser/settingsSearchMenu.ts | 39 +++++++-- .../preferences/browser/settingsTree.ts | 83 +++++++++++++------ .../contrib/preferences/common/preferences.ts | 1 + 6 files changed, 138 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 8e3547f362b..78841c4e21b 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -119,9 +119,25 @@ const configurationEntrySchema: IJSONSchema = { tags: { type: 'array', items: { - type: 'string' + type: 'string', + enum: [ + 'accessibility', + 'advanced', + 'experimental', + 'telemetry', + 'usesOnlineServices', + ], + enumDescriptions: [ + nls.localize('accessibility', 'Accessibility settings'), + nls.localize('advanced', 'Advanced settings are hidden by default in the Settings editor unless the user chooses to show advanced settings.'), + nls.localize('experimental', 'Experimental settings are subject to change and may be removed in future releases.'), + nls.localize('preview', 'Preview settings can be used to try out new features before they are finalized.'), + nls.localize('telemetry', 'Telemetry settings'), + nls.localize('usesOnlineServices', 'Settings that use online services') + ], }, - markdownDescription: nls.localize('scope.tags', 'A list of categories under which to place the setting. The category can then be searched up in the Settings editor. For example, specifying the `experimental` tag allows one to find the setting by searching `@tag:experimental`.'), + additionalItems: true, + markdownDescription: nls.localize('scope.tags', 'A list of tags under which to place the setting. The tag can then be searched up in the Settings editor. For example, specifying the `experimental` tag allows one to find the setting by searching `@tag:experimental`.'), } } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 8090311b6d6..c42375d865e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -61,7 +61,7 @@ import { nullRange, Settings2EditorModel } from '../../../services/preferences/c import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js'; import { IUserDataSyncWorkbenchService } from '../../../services/userDataSync/common/userDataSync.js'; import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js'; -import { CONTEXT_AI_SETTING_RESULTS_AVAILABLE, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EMBEDDINGS_SEARCH_PROVIDER_NAME, ENABLE_LANGUAGE_FILTER, EXTENSION_FETCH_TIMEOUT_MS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, FILTER_MODEL_SEARCH_PROVIDER_NAME, getExperimentalExtensionToggleData, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, LLM_RANKED_SEARCH_PROVIDER_NAME, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, SETTINGS_EDITOR_COMMAND_TOGGLE_AI_SEARCH, STRING_MATCH_SEARCH_PROVIDER_NAME, TF_IDF_SEARCH_PROVIDER_NAME, WorkbenchSettingsEditorSettings, WORKSPACE_TRUST_SETTING_TAG } from '../common/preferences.js'; +import { ADVANCED_SETTING_TAG, CONTEXT_AI_SETTING_RESULTS_AVAILABLE, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EMBEDDINGS_SEARCH_PROVIDER_NAME, ENABLE_LANGUAGE_FILTER, EXTENSION_FETCH_TIMEOUT_MS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, FILTER_MODEL_SEARCH_PROVIDER_NAME, getExperimentalExtensionToggleData, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, LLM_RANKED_SEARCH_PROVIDER_NAME, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, SETTINGS_EDITOR_COMMAND_TOGGLE_AI_SEARCH, STRING_MATCH_SEARCH_PROVIDER_NAME, TF_IDF_SEARCH_PROVIDER_NAME, WorkbenchSettingsEditorSettings, WORKSPACE_TRUST_SETTING_TAG } from '../common/preferences.js'; import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from '../common/settingsEditorColorRegistry.js'; import './media/settingsEditor2.css'; import { preferencesAiResultsIcon, preferencesClearInputIcon, preferencesFilterIcon } from './preferencesIcons.js'; @@ -131,6 +131,7 @@ export class SettingsEditor2 extends EditorPane { '@tag:accessibility', '@tag:preview', '@tag:experimental', + `@tag:${ADVANCED_SETTING_TAG}`, `@${ID_SETTING_TAG}`, `@${EXTENSION_SETTING_TAG}`, `@${FEATURE_SETTING_TAG}scm`, @@ -1397,9 +1398,17 @@ export class SettingsEditor2 extends EditorPane { } const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed + const coreSettingsGroups = [], extensionSettingsGroups = []; + for (const group of groups) { + if (group.extensionInfo) { + extensionSettingsGroups.push(group); + } else { + coreSettingsGroups.push(group); + } + } + const filter = this.viewState.tagFilters?.has(ADVANCED_SETTING_TAG) ? undefined : { exclude: { tags: [ADVANCED_SETTING_TAG] } }; - const coreSettings = groups.filter(g => !g.extensionInfo); - const settingsResult = resolveSettingsTree(tocData, coreSettings, this.logService); + const settingsResult = resolveSettingsTree(tocData, coreSettingsGroups, filter, this.logService); const resolvedSettingsRoot = settingsResult.tree; // Warn for settings not included in layout @@ -1510,10 +1519,10 @@ export class SettingsEditor2 extends EditorPane { } } - resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, groups.filter(g => g.extensionInfo))); + resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, extensionSettingsGroups, filter)); const commonlyUsedDataToUse = getCommonlyUsedData(toggleData); - const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, this.logService); + const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, undefined, this.logService); resolvedSettingsRoot.children!.unshift(commonlyUsed.tree); if (toggleData && setAdditionalGroups) { @@ -1712,6 +1721,7 @@ export class SettingsEditor2 extends EditorPane { private async triggerSearch(query: string, expandResults: boolean): Promise { const progressRunner = this.editorProgressService.show(true, 800); + const showAdvanced = this.viewState.tagFilters?.has(ADVANCED_SETTING_TAG); this.viewState.tagFilters = new Set(); this.viewState.extensionFilters = new Set(); this.viewState.featureFilters = new Set(); @@ -1727,6 +1737,10 @@ export class SettingsEditor2 extends EditorPane { this.viewState.languageFilter = parsedQuery.languageFilter; } + if (showAdvanced !== this.viewState.tagFilters?.has(ADVANCED_SETTING_TAG)) { + await this.onConfigUpdate(); + } + this.settingsTargetsWidget.updateLanguageFilterIndicators(this.viewState.languageFilter); if (query && query !== '@') { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 7ce956e18ce..5be14514660 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -7,6 +7,17 @@ import { isWeb, isWindows } from '../../../../base/common/platform.js'; import { localize } from '../../../../nls.js'; import { ExtensionToggleData } from '../common/preferences.js'; +export interface ITOCFilter { + include?: { + keyPatterns?: string[]; + tags?: string[]; + }; + exclude?: { + keyPatterns?: string[]; + tags?: string[]; + }; +} + export interface ITOCEntry { id: string; label: string; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 3d8640ac0af..620532e60c4 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -6,12 +6,12 @@ import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js'; import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; -import { IAction, IActionRunner } from '../../../../base/common/actions.js'; +import { IAction, IActionRunner, Separator } from '../../../../base/common/actions.js'; import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize } from '../../../../nls.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js'; -import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, ID_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG } from '../common/preferences.js'; +import { ADVANCED_SETTING_TAG, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, ID_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG } from '../common/preferences.js'; export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionViewItem { private readonly suggestController: SuggestController | null; @@ -102,6 +102,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu localize('modifiedSettingsSearchTooltip', "Add or remove modified settings filter"), `@${MODIFIED_SETTING_TAG}` ), + new Separator(), this.createAction( 'extSettingsSearch', localize('extSettingsSearch', "Extension ID..."), @@ -130,6 +131,14 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu `@${LANGUAGE_SETTING_TAG}`, true ), + this.createAction( + 'idSettingsSearch', + localize('idSettingsSearch', "Setting ID..."), + localize('idSettingsSearchTooltip', "Add Setting ID filter"), + `@${ID_SETTING_TAG}`, + false + ), + new Separator(), this.createToggleAction( 'onlineSettingsSearch', localize('onlineSettingsSearch', "Online services"), @@ -142,13 +151,25 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu localize('policySettingsSearchTooltip', "Show settings for policy services"), `@${POLICY_SETTING_TAG}` ), - this.createAction( - 'idSettingsSearch', - localize('idSettingsSearch', "Setting ID"), - localize('idSettingsSearchTooltip', "Add Setting ID filter"), - `@${ID_SETTING_TAG}`, - false - ) + new Separator(), + this.createToggleAction( + 'previewSettingsSearch', + localize('previewSettings', "Preview"), + localize('previewSettingsSearchTooltip', "Show preview settings"), + `@tag:preview`, + ), + this.createToggleAction( + 'experimentalSettingsSearch', + localize('experimental', "Experimental"), + localize('experimentalSettingsSearchTooltip', "Show experimental settings"), + `@tag:experimental`, + ), + this.createToggleAction( + 'advancedSettingsSearch', + localize('advancedSettingsSearch', "Advanced"), + localize('advancedSettingsSearchTooltip', "Show advanced settings"), + `@tag:${ADVANCED_SETTING_TAG}`, + ), ]; } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index e9f0ed424fa..8b0615038dd 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -69,7 +69,7 @@ import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumbe import { settingsMoreActionIcon } from './preferencesIcons.js'; import { SettingsTarget } from './preferencesWidgets.js'; import { ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel, getIndicatorsLabelAriaLabel } from './settingsEditorSettingIndicators.js'; -import { ITOCEntry } from './settingsLayout.js'; +import { ITOCEntry, ITOCFilter } from './settingsLayout.js'; import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, objectSettingSupportsRemoveDefaultValue, settingKeyToDisplayFormat } from './settingsTreeModels.js'; import { ExcludeSettingWidget, IBoolObjectDataItem, IIncludeExcludeDataItem, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue, SettingListEvent } from './settingsWidgets.js'; @@ -459,10 +459,10 @@ function getShowAddButtonList(dataElement: SettingsTreeSettingElement, listDispl } } -export function resolveSettingsTree(tocData: ITOCEntry, coreSettingsGroups: ISettingsGroup[], logService: ILogService): { tree: ITOCEntry; leftoverSettings: Set } { +export function resolveSettingsTree(tocData: ITOCEntry, coreSettingsGroups: ISettingsGroup[], filter: ITOCFilter | undefined, logService: ILogService): { tree: ITOCEntry; leftoverSettings: Set } { const allSettings = getFlatSettings(coreSettingsGroups); return { - tree: _resolveSettingsTree(tocData, allSettings, logService), + tree: _resolveSettingsTree(tocData, allSettings, filter, logService), leftoverSettings: allSettings }; } @@ -472,7 +472,7 @@ export function resolveConfiguredUntrustedSettings(groups: ISettingsGroup[], tar return [...allSettings].filter(setting => setting.restricted && inspectSetting(setting.key, target, languageFilter, configurationService).isConfigured); } -export async function createTocTreeForExtensionSettings(extensionService: IExtensionService, groups: ISettingsGroup[]): Promise> { +export async function createTocTreeForExtensionSettings(extensionService: IExtensionService, groups: ISettingsGroup[], filter: ITOCFilter | undefined): Promise> { const extGroupTree = new Map>(); const addEntryToTree = (extensionId: string, extensionName: string, childEntry: ITOCEntry) => { if (!extGroupTree.has(extensionId)) { @@ -487,6 +487,7 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten }; const processGroupEntry = async (group: ISettingsGroup) => { const flatSettings = group.sections.map(section => section.settings).flat(); + const settings = filter ? getMatchingSettings(new Set(flatSettings), filter) : flatSettings; const extensionId = group.extensionInfo!.id; const extension = await extensionService.getExtension(extensionId); @@ -502,7 +503,7 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten id: settingGroupId, label: group.title, order: group.order, - settings: flatSettings + settings }; addEntryToTree(extensionId, extensionName, childEntry); }; @@ -564,18 +565,24 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten }); } -function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set, logService: ILogService): ITOCEntry { +function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set, filter: ITOCFilter | undefined, logService: ILogService): ITOCEntry { let children: ITOCEntry[] | undefined; if (tocData.children) { children = tocData.children .filter(child => child.hide !== true) - .map(child => _resolveSettingsTree(child, allSettings, logService)) + .map(child => _resolveSettingsTree(child, allSettings, filter, logService)) .filter(child => child.children?.length || child.settings?.length); } let settings: ISetting[] | undefined; - if (tocData.settings) { - settings = tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern, logService)).flat(); + if (filter || tocData.settings) { + settings = getMatchingSettings(allSettings, { + include: { + keyPatterns: [...filter?.include?.keyPatterns ?? [], ...tocData.settings ?? []], + tags: filter?.include?.tags ? [...filter.include.tags] : [] + }, + exclude: filter?.exclude ?? {} + }); } if (!children && !settings) { @@ -590,25 +597,53 @@ function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set, pattern: string, logService: ILogService): ISetting[] { +function getMatchingSettings(allSettings: Set, filter: ITOCFilter): ISetting[] { const result: ISetting[] = []; - allSettings.forEach(s => { - if (settingMatches(s, pattern)) { - result.push(s); - allSettings.delete(s); + allSettings.forEach(setting => { + let shouldInclude = false; + let shouldExclude = false; + + // Check include filters + if (filter.include?.keyPatterns) { + shouldInclude = filter.include.keyPatterns.some(pattern => { + if (pattern.startsWith('@tag:')) { + const tagName = pattern.substring(5); + return setting.tags?.includes(tagName); + } else { + return settingMatches(setting, pattern); + } + }); + } else { + shouldInclude = true; } - }); - if (!result.length && !knownDynamicSettingGroups.some(r => r.test(pattern))) { - logService.warn(`Settings pattern "${pattern}" doesn't match any settings`); - } + if (shouldInclude && filter.include?.tags?.length) { + shouldInclude = filter.include.tags.some(tag => setting.tags?.includes(tag)); + } + + // Check exclude filters (takes precedence) + if (filter.exclude?.keyPatterns) { + shouldExclude = filter.exclude.keyPatterns.some(pattern => { + if (pattern.startsWith('@tag:')) { + const tagName = pattern.substring(5); + return setting.tags?.includes(tagName); + } else { + return settingMatches(setting, pattern); + } + }); + } + + if (!shouldExclude && filter.exclude?.tags?.length) { + shouldExclude = filter.exclude.tags.some(tag => setting.tags?.includes(tag)); + } + + // Include if matches include filter and doesn't match exclude filter + if (shouldInclude && !shouldExclude) { + result.push(setting); + allSettings.delete(setting); + } + }); return result.sort((a, b) => a.key.localeCompare(b.key)); } diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index f93bd3b939f..812be33d82d 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -106,6 +106,7 @@ export const GENERAL_TAG_SETTING_TAG = 'tag:'; export const POLICY_SETTING_TAG = 'hasPolicy'; export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust'; export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace'; +export const ADVANCED_SETTING_TAG = 'advanced'; export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; export const ENABLE_LANGUAGE_FILTER = true; From b0615f2843b4b5dbb18f47ec2f05696bf581da05 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 10:15:33 +0200 Subject: [PATCH 1454/4355] commands - developer commands are sorted to the bottom of the picker (#272658) --- .../quickinput/browser/commandsQuickAccess.ts | 23 +++++++++++++++---- .../electron-browser/extensionsActions.ts | 4 ++-- .../contrib/files/browser/fileActions.ts | 14 +++++------ .../browser/commandsQuickAccess.ts | 1 + 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index fec1e3088ac..7e878e2df9c 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -26,14 +26,18 @@ import { IQuickPickSeparator } from '../common/quickInput.js'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { removeAccents } from '../../../base/common/normalization.js'; +import { Categories } from '../../action/common/actionCommonCategories.js'; export interface ICommandQuickPick extends IPickerQuickAccessItem { readonly commandId: string; readonly commandWhen?: string; readonly commandAlias?: string; readonly commandDescription?: ILocalizedString; + readonly commandCategory?: string; + + readonly args?: unknown[]; + tfIdfScore?: number; - readonly args?: any[]; // These fields are lazy initialized during filtering process. labelNoAccents?: string; @@ -154,11 +158,13 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc // Sort by MRU order and fallback to name otherwise filteredCommandPicks.sort((commandPickA, commandPickB) => { + // If a result came from tf-idf, we want to put that towards the bottom if (commandPickA.tfIdfScore && commandPickB.tfIdfScore) { if (commandPickA.tfIdfScore === commandPickB.tfIdfScore) { return commandPickA.label.localeCompare(commandPickB.label); // prefer lexicographically smaller command } + return commandPickB.tfIdfScore - commandPickA.tfIdfScore; // prefer higher tf-idf score } else if (commandPickA.tfIdfScore) { return 1; // first command has a score but other doesn't so other wins @@ -197,6 +203,16 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc } } + // if one is Developer and the other isn't, put non-Developer first + const isDeveloperA = commandPickA.commandCategory === Categories.Developer.value; + const isDeveloperB = commandPickB.commandCategory === Categories.Developer.value; + if (isDeveloperA && !isDeveloperB) { + return 1; + } + if (!isDeveloperA && isDeveloperB) { + return -1; + } + // both commands were never used, so we sort by name return commandPickA.label.localeCompare(commandPickB.label); }); @@ -313,10 +329,9 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return chunk; } - /** - * Normalizes a string for filtering by removing accents, but only if the result has the same length, otherwise - * returns the original string. + * Normalizes a string for filtering by removing accents, but only if + * the result has the same length, otherwise returns the original string. */ private normalizeForFiltering(value: string): string { const withoutAccents = removeAccents(value); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 41978486698..49d06bcce71 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -11,7 +11,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js import { Schemas } from '../../../../base/common/network.js'; import { Action2 } from '../../../../platform/actions/common/actions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { ExtensionsLocalizedLabel, IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; export class OpenExtensionsFolderAction extends Action2 { @@ -20,7 +20,7 @@ export class OpenExtensionsFolderAction extends Action2 { super({ id: 'workbench.extensions.action.openExtensionsFolder', title: localize2('openExtensionsFolder', 'Open Extensions Folder'), - category: ExtensionsLocalizedLabel, + category: Categories.Developer, f1: true }); } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index bcb80404e4f..281fe92dfce 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -678,7 +678,7 @@ export class ShowActiveFileInExplorer extends Action2 { export class OpenActiveFileInEmptyWorkspace extends Action2 { static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; - static readonly LABEL = nls.localize2('openFileInEmptyWorkspace', "Open Active File in New Empty Workspace"); + static readonly LABEL = nls.localize2('openFileInEmptyWorkspace', "Open Active Editor in New Empty Workspace"); constructor( ) { @@ -689,7 +689,7 @@ export class OpenActiveFileInEmptyWorkspace extends Action2 { category: Categories.File, precondition: EmptyWorkspaceSupportContext, metadata: { - description: nls.localize2('openFileInEmptyWorkspaceMetadata', "Opens the active file in a new window with no folders open.") + description: nls.localize2('openFileInEmptyWorkspaceMetadata', "Opens the active editor in a new window with no folders open.") } }); } @@ -701,12 +701,10 @@ export class OpenActiveFileInEmptyWorkspace extends Action2 { const fileService = accessor.get(IFileService); const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); - if (fileResource) { - if (fileService.hasProvider(fileResource)) { - hostService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true }); - } else { - dialogService.error(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); - } + if (fileResource && fileService.hasProvider(fileResource)) { + hostService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true }); + } else { + dialogService.error(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); } } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 7ff6b778cca..71cabee0115 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -260,6 +260,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce commandAlias, label: stripIcons(label), commandDescription, + commandCategory: category, }); } From 168a80297b9d3ccf46ea0b08038f8fd042518de9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 10:20:56 +0200 Subject: [PATCH 1455/4355] `Ctrl+R` shows folders, workspaces, and files mixed up together (fix #272616) (#272651) --- src/vs/workbench/browser/actions/windowActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 7ba99ce6b2c..808d5e717ca 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -138,6 +138,7 @@ abstract class BaseOpenRecentAction extends Action2 { activeItem: [...workspacePicks, ...filePicks][autoFocusSecondEntry ? 1 : 0], placeHolder: isMacintosh ? localize('openRecentPlaceholderMac', "Select to open (hold Cmd-key to force new window or Option-key for same window)") : localize('openRecentPlaceholder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"), matchOnDescription: true, + sortByLabel: false, onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: keybindingService.lookupKeybindings(this.desc.id) } : undefined, hideInput: this.isQuickNavigate(), From 0e9d0e4c8ca71d01b168794b26a401dc10841355 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:24:37 +0200 Subject: [PATCH 1456/4355] SCM - select all repositories when switching the `scm.repositories.selectionMode` to `multiple` (#272677) --- src/vs/workbench/contrib/scm/browser/scmViewService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index b8af15c5bff..859431e6d4f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -302,6 +302,8 @@ export class SCMViewService implements ISCMViewService { if (selectionMode === 'single' && this.visibleRepositories.length > 1) { const repository = this.visibleRepositories[0]; this.visibleRepositories = [repository]; + } else if (selectionMode === 'multiple' && this.repositories.length > 1) { + this.visibleRepositories = this.repositories; } })); From b36eec2dd5f1c40e76bdde90d8895db5fe9e1c42 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 22 Oct 2025 11:40:52 +0100 Subject: [PATCH 1457/4355] chore(codicons): regenerate codicon.ttf with updated glyphs --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119432 -> 119300 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index b9ffca587229bded535465d94fdf375cae70131b..fcd31780d4a5c564db9e60789dfd7b3e70bd69e7 100644 GIT binary patch delta 7594 zcmZwM33L=y+6Lh7?M`nY35!`kSptECJt6F?5ds8=h%xLT5C{+;EV2d&bl4*x0bK|}`;6%iE?t6Bs^1!e~4y?#3X|DW@7&Qot!(%q@L_tyQYF2&nD zi??}JC;7)+zyFDd@2p6zva$KI$81@-sqg%L2FAhMdUKI$>R?k1Dy5w z`ViI6n>f9oZ=>HV5d=j%SMn#1%2`tM-F1{;-q-(Ra*bxp7Z?v~fa2}k)==M4Ag zXiURoIg4x@K{_^~C0>#N_z}zT9G;aj>3{+9g8YDb(j9NhN{N9buYcubby31p(b)W<*!#t>v-7)D|gaxofXFc#x5 z9{HGrCovV%WgTW>778#MbFdJLuoz3Q6i>nb3|3Lb8%P7Mu*onVL zId)?Y%CQ&w@g|PpIR1|J@c}->M>vO%aUP%G0xsebzQkpGgDdzJ-{E`Qz)jrZ!Tp3^ zaYy!{1|C99nI_X^k#v#8GK;;iP?q|oNG3@`X@usoRbG*A@CC<2b(xK7=#F_fgvaGP zR^e&%!WLv;giOR$NkgJ+lS_z03UZ`5`XNm^N+;=u!}toH<5PSlf5%+R$HU^sZTy0t zaRPtEyLb;L@i)99Rb@2#$X@ub%RKo}?#sXBUouz*$v}yc8*)nCmwcIvB6(ZpNVI$@ zm(f>Zr7r49B)&!`RF&T#aKi}~yzn6ml~Dx_07u2fa48kHc*H9{M8JbeQd6#=5#r@x zc~ELde>o!`V3}n4u@W!Hb+o`4oRxH}m!q(yH6BGIUhtz(#>gjVhy=Na-tvHaCL5)a}vY}My%;deqhc} zoOHyRsieF3#hS$(;!GqKCq#pDl325q#4zV5&QoH|Rh+TJDpc|!bDn}Z!}$uSOuvFT z&?1G7%moI2$>~n4g^ClOSc?>=K(Q7pxxieaaEiH9ae@?UnUc*+&f*5%Vw#pDPM>0} zQ1Ua=oE+f|=CEhDA?#qTR;XaEQ8>t4tFVt5P`v)dGF?LQIn#6%;Z^23g#+c6t1feJ zk`~JhA>wo`)`yA{w^$!3PT^vib|6mXVx3c**2Vf*ae^1?yy8?Z7AvB`Nnb3}KE&x? ztWOmSfmolJA;t}Bf>`D_iPb@@i;8tZtS=NRhFF&rYlm20DpnD(E-Tg(vA$BQEMk4F zSYyQcMzPw6bw#o6aEN`&9byR*%k(g@7>V_rVp$UFnqq+x>wCpgC6=XFw8XL%%a>R| z#X=^Q8Q;W`Cf0Su;wILQ28S5yoLDy$E1p<46>FbZw-l?OSU)M&L$PivRz|UYR;-a? z{i4jxh4rgq-4yGNVg(iJu3|0a_`k;uv8;;qPsIW&*1r@>tyuq7EV^RdS1i9`{iaxm zg(v?zmSoP+ip5!Mhhmu)+o@Qv#ST$^)^4#~id9@}w_-gP+oM?7#r7)Jc(Hwo)n4pM zigjP?FvU9nvBMSb1;ma}yekm9vN9K&U4{M6jqpxE?5c|Q3}RPP!WOX~P%s&(5W|d8 z=Dy61R(OzUen+UqG`}O%X4X)s!!++B#IpbGhZO2FYbrEgnlB(UW|}V`G-aAEAT(pf zDkL-ODzswOQ)tbMQ)t69e}mADY5sHU~&}g4tXllbNV^hb4YHi900b$XX~2WSTP~3}&`e7{WAXN62EDvm*>+ znzJK}WVTfp#WZJ1$Yr)y7|rbPJHKEIUvyL$%j~2uj%iwkFrH~zhLF#ESYZ+~Md3-N zX)(f7W>>{KM6pfl5$`p`PF0x6>~8wMJ3lasX_}Q#z%&aEzI!aGW_#;qOc{76|V%Cn$WtG~>lGdRf74ReFS7lA{y68+VvnD_2b$YAn1aGMQA1g6xE z@n7YS%&;dhlfbKrPiSKAQhZhu+guJLW?<}Ah+~?jBBU_)C_dAP{kjs)P7#IzM3vBeH5`HlI50{^sa&Whk>nlm9dndVptF6K=IFY}gyk7;HCLKu@h zIK*sFnLDP}2vwM-Hwg}=nJx(Y%eI*=2uGQBmAR+1?Y5U_`x+y&^<{{GBD+nb92TX2Vqq3C1Wn%B-%qav{MQ zN?J1?RCtv6kV0FgIbA{$vzFpohXiXY$z|42Fb5E;FoRcLur7Cqiy{)Nr_3!&FiuH; zSzlorvw`B`i3H;nmr*2`ptztS!G=nBK?F^k6MkSeR%Y2V*u>zGNiV)=s^DifQ(TRa zU~|QF83`sT`GjfClwkglX>Q`GjRcdGTx7OX=)JZhYH1N9rkVNf8A+k1ih z266uV{7s62>8(c;7kVVvRY?xBo8sb+1XC54fh5>naY0CeJ(Qeg_EcOXl3<#W?M!pZ zgb-$Lk!ogxQ10|m2oJ=@cWhA1y~h=olq8s;gcn8d356AbqWF&C?YTEt;dJ0+e4_s) z?qw@3I!VxchU6r3gu+m!IW0mSbEM+plmtg9DPXdf3~XhNR$RK0py?IjB9`2QlJ}Sn zmNc`$wLoFQ=l;@00~&`k?%jB4;{%N^H@@GbdXtnU*-f5p8rJkeGjFp&&8{`?(R@>4 zXkuaFiNxDUosy;|l_uS2k=$Z>ixVwwB_C)R-*Rfp^Q|IVjcK*5)#cVBTW@K7t4&s$ zLv3!i-Px|7T}Auo_FdcWXm53B(_wkX^p1NvUh91gMsofmh$-(^mhTmFZ$Qrs!I zDd!&P^2pAvv0bye?&^A_TWq)VZU?*FNR3a;N}+^IDIMz=+dHpj-hjN-dBJgc<5rKmIzD;)!ts|T)SfV8 zLeYdB6E5Xf&(HVg-=3HC&xT_XX==#7pFx` z%b&Ju`pW6|W)#dgHM8N&)3Y*W9WQ8HkX^8);QH+R*(c}Jn=^Gz#hm+dQ|8W}d!n#r z;ike<^TOsGp5Ji(%=vfxWkv0Z&Mt5)NLf&@u+75x3qMj4Ow=oIJJ07@%^WIKec6f!{s+tOj&VpW$MbME6+Wh{&d-@kX6~M-hC$HnZv6) ztu9#;wx(ds#kJ3_y%lI4SQR+) zyI#Nj#?Cjcme($yUtUoWS+Q$x#NMWR)Ar`>UATAKKG~PG@67&w2OI}74_tZk%)vLq~5PD>**k_?~x~zO&<<`zKP% zr#IR9SXU2=S*TQ#mR#s66=@q873r>wgf3{E&^m(ukI^Q%ZG0l)BOK;sOzT!{lI!sQ zHq3h>J<&03yP#FzW?_E9S6+9r(^qMwFVyAxI>fWi6A|up`CKlKW1Yk4bo6jKo#`IW z*uboLU6Y48oSyKInl49WU+)gi2w$ut#uJhm0#CRn)!|4DaiKp#TrMPdJq~A+knqY` zf$@d?%a0Wnd&^&0()WnJI*(Qwxa%c?2U{<`Gd$e3dYA`T2houp9walWWvky0oaYc( z8xNzk#~qJGPLDIn?R7;vyl!W_!|QC|{L1HA=X${FS?7xk50CT>8RhczbvmPl@CZVN zL^)kvzRwZw^hT7qU5@@vmnULSNTw^Ka;36Ll|x*~(d`^APDKXq3)q-$-qj^ZXL*c` z={CpGHYTZs$L(QLb;kd+W!wMVl-iI9>toVTCqBNCH`KSSio+G__SXBODMNjKwq#oe zI(t0MA?y35;lciUTpo`v!a2(q9#$76W=@` zCMF@w>1+|^;K@~UI@>ut=IEV&p2GU6J&@@CeH^&s>LtV{H0P1?Jfhf+$!$}7mAvkC z9{&5ilcODfbn!Ze%jNLLc-_M*SM~a;hK2eKnMsiKq!Y!qzO3~iin7G z5s@w+AfQ*hS4BibL_|cc&KwaCk&9ft@AhM@f32Ujo_S}7Q)c$wv-iwC^EP=)H+z?- z`p4`>Es>COA~m;-DwsO5XSdlOizF2ZO7G;4$Q|CVY(ut)T%nA7{=t#stk3Z8xcLH(Nh*68-0-@_0SIk@C*iFFot0`MqngHAs=Hf7UM7;6EO+T z$tq076ih`4reQATVLldMAr`^E94kX*2X_be@E5Np_EZ?FY+9FSq(F4B`5-P(92i}z+hRUnr5wG|p1Qp>!n8eBtNJN4>AvL7B^p#KL z3@?~$KbB&h{DLO<6rV{qtd@6qjisYInxZiZWjLZyjF)7Uti)ynhgG@E97%H+WL2%Ga`1y2>#0mqC&zf5-qCEQb(DsunFztXZZj>@w)W{aw8rD zCPa7xgDS?4ixIK%6pEO`6t7{ihAa7uIYMCwbELuv<|u`ym>e<;tYwZ?NMnvs*bul` zF~)zBrEv<2nS~1F%<+mNg;?eYh+~FW&njuooTxaCh&4%ZL=npzJ;@E`bBd#kSj9>@ zFei)n&4xpdSW^@SA+e?^G1XC`I53GdO>u}4izB8X>zOkY3Yaq$%oq3-S}5Oy+6vk~6jd#Un52S;VG&M1!0 zVwt`}JZ-VgDvsG=eXclqi*-(M92e^g#SvYs^NM4;SYIlR@?u?39Ph<4bwwQc#ky!( zmm5w3VtuVR9f)O)nK&_s^^M{bA=YKZ$wI7e6{iict|(3*VtuDLm56m!aZ(ZMd&TKR ztZRxB4O{mI7Kn3>Sl1P2AF)hp6XzkZepH;1#Nst+aBdQd6ScuvN-SG(z7i{_ICF{h zv*H{kmg(!n*-Wfo3|2EIHnDChPH|%0Qk?9>x~(|viS?`E1Sr-W#i>xN-;}w7upzNZiw@!Sbr$asA4@(oLj~Eui`8#)}M;=tyq64&b*vx z73W~Fd8=V?HWu5VI4_IsRGgv3b}2t6Yq8ym)3(?i#R*(&ui{iLwoh?V7du3mZ?!8Z zPV{1jDNgxfhbvD0Vn-<628dlznTyS?#P;V#cxxbbWyPBWv8yOyE!b5RBAE}Fn=d;` zp*k~K;SpwxLLAe)j_@ecypB+dSwo>V+ut^CBGhNbD#SBuDkL$@hY=bv&4&?^ndZX? zk27m4q%-R%WH9S0WHRe1G-I0Y^K*mN+%aEBc!HUrki~4E(4P62LMLXTLT9EqK;o^E z*bS8#Cv0=DglwicSVCWBisCJm`0Z2{NX(HnR_MoUqA-AI&W`X5GfiO-)0`<`Fw>kV zVHne#DPcIXslo`RIcvg5X7j)K1*5prLLr~oQeh0UmBLu2sU^ZVrl}>ucxD@giOjYN zlbELVh_{4dw^O{i5ZlxwVKTFW-)vZz!tAIpm1(M&P{K4-Oqj;(qA-`)Rbd|UNrm~$ zZVILeo>ExI?5?ngX_|_7SIV7U3M-hs70Q@>6jm}#8xqVJm^LJ=VX_Skyv%%BVLj6{ zA>kEfe}z|>ra1{4nQTtK*)&(+!eOTA3xt!*e1%iY(F*@!ntnm}glYN(;S94t;Vjei z5yI!pLWM7wroRx*Gbb2qe|*WEXB93mO-CYp#WWp>@C~y_;ajHZRD>(cVukORrc)8F zF-@N${J=DQig2B2`V`?uW+T(72o`g?!Y$?uh1<-T3cm(s#m02G!_sVpd(1hC&mLl% zZb@e9vQ4)n)L@!!Nif6P0)?7^E3tL^npZ7Sd|DBEvEp-!*h`d{lUl0Gt&P1*@u^1a z7nGO|Zu&CG>_ESoDUDNDdQtI#N9=ONM<20QDY?d6t#CPTsAh`4DoZabbY!kqFm+)1 zGT|NO2E~Uau}!}w`I2e+HNhO$YYOIg%@Gi~GB+!j|KFm}hq={U58UwCOKfvZkeIGy zt_kAvnAqmqv6MLuP)0)_CC^0={mqI<}n+k22rXq6La!kDuOg}rO;Afi8CA49l zP%?}8uEKB3lOiL{2EVg#O5rZ^w8DGLhwwi4AHqMl{}4Xl{zLeX`w!tG?mvXnfl9UN z_U*>Me4^w4^HYUim}e9pXT|D!qWFjzGGff=n?2#yRN?uOFt@! zWd5XJ-eD=UV%iE-m_Y>|hW)bw3-%2KFVoZl!NoLZPVg|zxe-E`w@qi}2B9qcsu0dJ z;|HM<)3hw1GSf6B!O1j@OmHyG7(;y87u$?6B<3LRD-31+p|}o_cMz5` zd0Gb78YIZmGUOK~>()RMW>p3A`D_=1YZ4NSQqqkX&H6JNE>uV`MhWX6SWTG)!eDhJ z>C74m-I9NU)AFH!;DwN>(!K zDQsreS6n}lV7%f=iUboB*Hk3fKnbsr;A0BxT|ra-#1$6_CK-X%^(y*jaj%i$DvSh^ z71v`Vn4;tYGgZNSqB&LKYK;Un(8C2>PRp{aeYUEnIeJs z`Zdu%(7t|M{1M2nU)O(#e{Z8;+N-VN%8vxIl;kqoDXs=du)X5CkOVs@t`JGEqmqxA zofKD%B-mNW7G@U((@VRGR52S=V8NUOAtDeP-=hBWEcH@cW0GKRB}15f6qW?0$F~S? z&QgDcj{~RUQ(A3cX|UodlmyLtNIql^RT#)Lr$rde%u|>XNKJ@oyosd|iYr$VG>t=C z!;)K2@&S_1xzHxlx{Kh*P-*3{O$&JScKE5q2EUjzWy0nYw(dnzx zFJv^xD9gB=nV5N{>8_?$vu@3%G`rNiR`Y`9CtA#DalK_+%SA2kw#sXDxOH0V-To&U zJW_)-S=dhCogp?>~`g;iBDbbKA`)#9!-0!?eU=J+FlX8y7#)#dt`5` zPtQJw`drJ-$u7(Ov+vBlXL5>iHs#!Wy4%wm`$hLF?&trczw{r|f6stg113Dv`>?1XI-Za>@R*>w|> zC$5`RWm5Y|8z)^YI{94cbGwSAc<^M$|9N^`>r|`e#YMl5Hi{w6tl( z)AmifKfU+#vomBy){Hwd2hZH;PxfD)RXE!-yXoxW*}LWx%-J`$(%hAEKbm`WUd#FZ zZu2iMsI*|+!q|n&OQp1=^uqIL&(C`P!lJ>8;}>sPQe{cWlA}v~OG}nsSk`>m%4H8; zD1G71@`=mOt;kxjtE@`dM}Y=`;epeEn=6Z7ykEY4)y!4bRu5Qxa!uHpoHd8mc3ykz zrHq%3tt(x3c-@Vc&#$kxzU%t3^=Dsc_{y?ZE^Nryu<_OA{#Td0dT(Rt#t&X=`dY~* z*QPm}LpSGd{$NXuEt|IbwvO4lXIt2|f!l6xFW!D_NAn$Xc3gaY;Ol304&Q0*vUZp6 zzW-L-TZ`Vhx+ihZmA$chv-j@XYwhd0@9O^c`}6kC+`o4Jq5T&RWE?0v=sLLIP}4(; z-mY=jb$HW}*dxKC8AsQ?6Z=ldJ9RG|YkzFf@hZo2j;}tEdScdz+wUg6d+y|tlh;q> zow{^7?Q{v(h6<8i>(QwOZ=!vYD4AU0;-!|Eo|cgr6@jSC_!Pv~i9k(6$7GnlHhGl4 zYeae~A}Z3G*&1mH-a3uF(OO7P2=tm(@K}7P&sX85(_8MX80PahT^_gBQSRWs{>~9z zud6A44Dq(`_>djAHZ3cCX=+G>Gu0UplIV5zb^5}5IS!}Ok>m0>vf*~S(8TBUHFbqo z^5j%^IzxT6T;V=Up5FV$^ip3^WOS2^^vtx@{H-O9%k!ROq-Q2XHFf(u9-lit zB*HV#3*S6XL`eC(K<@n5y|MF09rxD|j@M5h%FCCx!oxR5)lK8MdTJsvIx``@4nK>Z z%@>((NJx+2Hm#s%r;y0-@W_yIueYkJ+#M2fvcA*jhA)Tl2cf(}I#K*+MCnv5;FB=!g{m-v{TZTPGntAsO0- zBI6vHeSBeF^YLcGE)nH?*dNLrZnwi94WD;NMCA})Y(84dHGcRJzZ>1FRM{ng=;IEGj(R+#(7`|G)m?&ut+RCv8}d z+NA%#wY~8?$c*@e7;oU#^2i}EQLND>agIn&0bAAmT*1S34)wSlPPd0ul2G5_bUFD` z9xKh|bb;;d=Fc(QbppR?13R*!TK!+sdtySJPM5>!LGh~Ao$!d;Wvc67Q75b^#QlR2 d2XNczaQt!LFcy0}g+-$apBr&-&I!z}{6F^GdA0xm From 6667b832a39b06b5c7c29b08f41f0b49ac17ae64 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 12:46:15 +0200 Subject: [PATCH 1458/4355] support multiple versions (#272676) --- .../mcp/common/mcpGalleryManifestService.ts | 51 +++++++++++++++++-- src/vs/platform/mcp/common/mcpManagement.ts | 6 +++ .../mcp/browser/mcpGalleryManifestService.ts | 6 ++- .../mcpGalleryManifestService.ts | 18 ++++--- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index 294e09ef3be..e7c355ff5a9 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -3,23 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from '../../../base/common/cancellation.js'; import { Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; +import { ILogService } from '../../log/common/log.js'; import { IProductService } from '../../product/common/productService.js'; +import { IRequestService, isSuccess } from '../../request/common/request.js'; import { McpGalleryResourceType, IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; +const SUPPORTED_VERSIONS = [ + 'v0.1', // First version is preferred + 'v0' +]; + export class McpGalleryManifestService extends Disposable implements IMcpGalleryManifestService { readonly _serviceBrand: undefined; readonly onDidChangeMcpGalleryManifest = Event.None; readonly onDidChangeMcpGalleryManifestStatus = Event.None; + private readonly versionByUrl = new Map>(); + get mcpGalleryManifestStatus(): McpGalleryManifestStatus { return !!this.productService.mcpGallery?.serviceUrl ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable; } constructor( @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + @ILogService protected readonly logService: ILogService, ) { super(); } @@ -28,13 +40,21 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery if (!this.productService.mcpGallery) { return null; } - return this.createMcpGalleryManifest(this.productService.mcpGallery.serviceUrl); + return this.createMcpGalleryManifest(this.productService.mcpGallery.serviceUrl, SUPPORTED_VERSIONS[0]); } - protected createMcpGalleryManifest(url: string): IMcpGalleryManifest { + protected async createMcpGalleryManifest(url: string, version?: string): Promise { url = url.endsWith('/') ? url.slice(0, -1) : url; + + if (!version) { + let versionPromise = this.versionByUrl.get(url); + if (!versionPromise) { + this.versionByUrl.set(url, versionPromise = this.getVersion(url)); + } + version = await versionPromise; + } + const isProductGalleryUrl = this.productService.mcpGallery?.serviceUrl === url; - const version = 'v0.1'; const serversUrl = `${url}/${version}/servers`; const resources = [ { @@ -92,4 +112,29 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery resources }; } + + private async getVersion(url: string): Promise { + for (const version of SUPPORTED_VERSIONS) { + if (await this.checkVersion(url, version)) { + return version; + } + } + return SUPPORTED_VERSIONS[0]; + } + + private async checkVersion(url: string, version: string): Promise { + try { + const context = await this.requestService.request({ + type: 'GET', + url: `${url}/${version}/servers?limit=1`, + }, CancellationToken.None); + if (isSuccess(context)) { + return true; + } + this.logService.info(`The service at ${url} does not support version ${version}. Service returned status ${context.res.statusCode}.`); + } catch (error) { + this.logService.error(error); + } + return false; + } } diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index e80ff40683e..2653373113c 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -248,6 +248,12 @@ export const mcpGalleryServiceUrlConfig = 'chat.mcp.gallery.serviceUrl'; export const mcpGalleryServiceEnablementConfig = 'chat.mcp.gallery.enabled'; export const mcpAutoStartConfig = 'chat.mcp.autostart'; +export interface IMcpGalleryConfig { + readonly serviceUrl?: string; + readonly enabled?: boolean; + readonly version?: string; +} + export const enum McpAutoStartValue { Never = 'never', OnlyNew = 'onlyNew', diff --git a/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts b/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts index 47e8489ca2b..a15a7aa501f 100644 --- a/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts +++ b/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts @@ -8,14 +8,18 @@ import { McpGalleryManifestService as McpGalleryManifestService } from '../../.. import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; +import { IRequestService } from '../../../../platform/request/common/request.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; class WebMcpGalleryManifestService extends McpGalleryManifestService implements IMcpGalleryManifestService { constructor( @IProductService productService: IProductService, + @IRequestService requestService: IRequestService, + @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, ) { - super(productService); + super(productService, requestService, logService); const remoteConnection = remoteAgentService.getConnection(); if (remoteConnection) { const channel = remoteConnection.getChannel('mcpGalleryManifest'); diff --git a/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts index 59024b55ed3..2e91c7ce135 100644 --- a/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts +++ b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts @@ -11,8 +11,9 @@ import { InstantiationType, registerSingleton } from '../../../../platform/insta import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; -import { mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; +import { IMcpGalleryConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { IRequestService } from '../../../../platform/request/common/request.js'; export class WorkbenchMcpGalleryManifestService extends McpGalleryManifestService implements IMcpGalleryManifestService { @@ -29,11 +30,12 @@ export class WorkbenchMcpGalleryManifestService extends McpGalleryManifestServic constructor( @IProductService productService: IProductService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRequestService requestService: IRequestService, + @ILogService logService: ILogService, @ISharedProcessService sharedProcessService: ISharedProcessService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ILogService private readonly logService: ILogService, ) { - super(productService); + super(productService, requestService, logService); const channels = [sharedProcessService.getChannel('mcpGalleryManifest')]; const remoteConnection = remoteAgentService.getConnection(); @@ -58,23 +60,23 @@ export class WorkbenchMcpGalleryManifestService extends McpGalleryManifestServic await this.getAndUpdateMcpGalleryManifest(); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(mcpGalleryServiceUrlConfig)) { + if (e.affectsConfiguration(mcpGalleryServiceUrlConfig) || e.affectsConfiguration('chat.mcp.gallery.version')) { this.getAndUpdateMcpGalleryManifest(); } })); } private async getAndUpdateMcpGalleryManifest(): Promise { - const value = this.configurationService.getValue(mcpGalleryServiceUrlConfig); - if (value) { - this.update(this.createMcpGalleryManifest(value)); + const mcpGalleryConfig = this.configurationService.getValue('chat.mcp.gallery'); + if (mcpGalleryConfig?.serviceUrl) { + this.update(await this.createMcpGalleryManifest(mcpGalleryConfig.serviceUrl, mcpGalleryConfig.version)); } else { this.update(await super.getMcpGalleryManifest()); } } private update(manifest: IMcpGalleryManifest | null): void { - if (this.mcpGalleryManifest?.url === manifest?.url) { + if (this.mcpGalleryManifest?.url === manifest?.url && this.mcpGalleryManifest?.version === manifest?.version) { return; } From 106d7b64ff1b463a85b3ced4d7e9beeff3dd270b Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 22 Oct 2025 12:47:46 +0200 Subject: [PATCH 1459/4355] inlineEdit: allow empty edits --- .../browser/model/inlineSuggestionItem.ts | 18 +++++++++--------- .../view/inlineEdits/inlineEditWithChanges.ts | 7 +++++-- .../view/inlineEdits/inlineEditsView.ts | 10 +++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index 24c559e7518..bfd2af15d05 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -5,26 +5,26 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { matchesSubString } from '../../../../../base/common/filters.js'; -import { observableSignal, IObservable } from '../../../../../base/common/observable.js'; +import { IObservable, observableSignal } from '../../../../../base/common/observable.js'; import { commonPrefixLength, commonSuffixLength, splitLines } from '../../../../../base/common/strings.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ISingleEditOperation } from '../../../../common/core/editOperation.js'; import { applyEditsToRanges, StringEdit, StringReplacement } from '../../../../common/core/edits/stringEdit.js'; -import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; +import { TextEdit, TextReplacement } from '../../../../common/core/edits/textEdit.js'; import { Position } from '../../../../common/core/position.js'; -import { PositionOffsetTransformerBase } from '../../../../common/core/text/positionToOffset.js'; import { Range } from '../../../../common/core/range.js'; -import { TextReplacement, TextEdit } from '../../../../common/core/edits/textEdit.js'; +import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; import { StringText } from '../../../../common/core/text/abstractText.js'; +import { getPositionOffsetTransformerFromTextModel } from '../../../../common/core/text/getPositionOffsetTransformerFromTextModel.js'; +import { PositionOffsetTransformerBase } from '../../../../common/core/text/positionToOffset.js'; import { TextLength } from '../../../../common/core/text/textLength.js'; import { linesDiffComputers } from '../../../../common/diff/linesDiffComputers.js'; -import { InlineCompletion, InlineCompletionTriggerKind, Command, InlineCompletionWarning, PartialAcceptInfo, InlineCompletionEndOfLifeReason, InlineCompletionDisplayLocationKind } from '../../../../common/languages.js'; -import { ITextModel, EndOfLinePreference } from '../../../../common/model.js'; +import { Command, InlineCompletion, InlineCompletionDisplayLocationKind, InlineCompletionEndOfLifeReason, InlineCompletionTriggerKind, InlineCompletionWarning, PartialAcceptInfo } from '../../../../common/languages.js'; +import { EndOfLinePreference, ITextModel } from '../../../../common/model.js'; import { TextModelText } from '../../../../common/model/textModelText.js'; +import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js'; import { IDisplayLocation, InlineSuggestData, InlineSuggestionList, PartialAcceptance, SnippetInfo } from './provideInlineCompletions.js'; import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js'; -import { getPositionOffsetTransformerFromTextModel } from '../../../../common/core/text/getPositionOffsetTransformerFromTextModel.js'; -import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js'; export type InlineSuggestionItem = InlineEditItem | InlineCompletionItem; @@ -343,7 +343,7 @@ export class InlineEditItem extends InlineSuggestionItemBase { const offsetEdit = getStringEdit(textModel, data.range, data.insertText); const text = new TextModelText(textModel); const textEdit = TextEdit.fromStringEdit(offsetEdit, text); - const singleTextEdit = textEdit.toReplacement(text); + const singleTextEdit = offsetEdit.isEmpty() ? new TextReplacement(new Range(1, 1, 1, 1), '') : textEdit.toReplacement(text); // FIXME: .toReplacement() can throw because offsetEdit is empty because we get an empty diff in getStringEdit after diffing const identity = new InlineSuggestionIdentity(); const edits = offsetEdit.replacements.map(edit => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts index 675e9911fb0..96bc17bdbab 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts @@ -4,15 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { LineReplacement } from '../../../../../common/core/edits/lineEdit.js'; -import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; -import { Position } from '../../../../../common/core/position.js'; import { TextEdit } from '../../../../../common/core/edits/textEdit.js'; +import { Position } from '../../../../../common/core/position.js'; +import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; import { AbstractText } from '../../../../../common/core/text/abstractText.js'; import { InlineCompletionCommand } from '../../../../../common/languages.js'; import { InlineSuggestionItem } from '../../model/inlineSuggestionItem.js'; export class InlineEditWithChanges { public get lineEdit() { + if (this.edit.replacements.length === 0) { + return new LineReplacement(new LineRange(1, 1), []); + } return LineReplacement.fromSingleTextEdit(this.edit.toReplacement(this.originalText), this.originalText); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 64e4fbdb7be..2d3babdead7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { $ } from '../../../../../../base/browser/dom.js'; import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equals.js'; import { BugIndicatingError } from '../../../../../../base/common/errors.js'; import { Event } from '../../../../../../base/common/event.js'; @@ -12,10 +13,10 @@ import { IInstantiationService } from '../../../../../../platform/instantiation/ import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor, observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { EditorOption } from '../../../../../common/config/editorOptions.js'; -import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; +import { TextReplacement } from '../../../../../common/core/edits/textEdit.js'; import { Position } from '../../../../../common/core/position.js'; import { Range } from '../../../../../common/core/range.js'; -import { TextReplacement } from '../../../../../common/core/edits/textEdit.js'; +import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; import { AbstractText, StringText } from '../../../../../common/core/text/abstractText.js'; import { TextLength } from '../../../../../common/core/text/textLength.js'; import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; @@ -35,7 +36,6 @@ import { InlineEditsWordReplacementView } from './inlineEditsViews/inlineEditsWo import { IOriginalEditorInlineDiffViewState, OriginalEditorInlineDiffView } from './inlineEditsViews/originalEditorInlineDiffView.js'; import { applyEditToModifiedRangeMappings, createReindentEdit } from './utils/utils.js'; import './view.css'; -import { $ } from '../../../../../../base/browser/dom.js'; export class InlineEditsView extends Disposable { @@ -497,9 +497,9 @@ export class InlineEditsView extends Disposable { })); const cursorPosition = inlineEdit.cursorPosition; - const startsWithEOL = stringChanges[0].modified.startsWith(textModel.getEOL()); + const startsWithEOL = stringChanges.length === 0 ? false : stringChanges[0].modified.startsWith(textModel.getEOL()); const viewData: InlineCompletionViewData = { - cursorColumnDistance: inlineEdit.edit.replacements[0].range.getStartPosition().column - cursorPosition.column, + cursorColumnDistance: inlineEdit.edit.replacements.length === 0 ? 0 : inlineEdit.edit.replacements[0].range.getStartPosition().column - cursorPosition.column, cursorLineDistance: inlineEdit.lineEdit.lineRange.startLineNumber - cursorPosition.lineNumber + (startsWithEOL && inlineEdit.lineEdit.lineRange.startLineNumber >= cursorPosition.lineNumber ? 1 : 0), lineCountOriginal: inlineEdit.lineEdit.lineRange.length, lineCountModified: inlineEdit.lineEdit.newLines.length, From 146c49bf203f08d131b2f2c697378c4a7193c4ff Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 22 Oct 2025 12:20:50 +0100 Subject: [PATCH 1460/4355] extension-editor: add 2px horizontal padding to dropdown action label --- .../contrib/extensions/browser/media/extensionEditor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a45036b6727..a5cd25004a2 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -281,6 +281,7 @@ .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item:not(.empty) > .monaco-dropdown .extension-action.label { border-left-width: 0; border-radius: 0 2px 2px 0; + padding: 0 2px; } .extension-editor > .header > .details > .actions-status-container > .monaco-action-bar > .actions-container > .action-item > .action-label.extension-status { From 7b0c38220089d21c5e0d80060157ec80bdf2cbf2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 13:31:16 +0200 Subject: [PATCH 1461/4355] chat - first cut of a agent sessions view in one list (#271927) --- .github/CODENOTIFY | 1 + .../browser/parts/views/viewFilter.ts | 19 +- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../chat/browser/actions/chatActions.ts | 8 +- .../browser/actions/chatSessionActions.ts | 6 +- .../agentSessions/agentSessionViewModel.ts | 146 +++++++ .../agentSessions/agentSessionsView.ts | 382 ++++++++++++++++++ .../agentSessions/agentSessionsViewer.ts | 251 ++++++++++++ .../agentSessions/media/agentsessionsview.css | 32 ++ .../media/agentsessionsviewer.css | 95 +++++ .../contrib/chat/browser/chat.contribution.ts | 3 +- .../browser/chatParticipant.contribution.ts | 6 +- .../chat/browser/chatSessions.contribution.ts | 4 +- .../chatSessions/localChatSessionsProvider.ts | 2 + .../chatSessions/view/chatSessionsView.ts | 18 +- .../contrib/chat/browser/chatStatus.ts | 10 +- .../contrib/chat/browser/chatViewPane.ts | 1 - .../chat/common/chatSessionsService.ts | 2 +- .../contrib/chat/common/constants.ts | 2 +- 19 files changed, 956 insertions(+), 34 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 21889363f06..4a6d179c43d 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -102,6 +102,7 @@ src/vs/workbench/contrib/files/** @bpasero src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero +src/vs/workbench/contrib/chat/browser/agentSessions/** @bpasero src/vs/workbench/contrib/localization/** @TylerLeonhardt src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @TylerLeonhardt src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index bf01e9fae4c..d6d5197adc2 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -56,7 +56,6 @@ class MoreFiltersActionViewItem extends SubmenuEntryActionViewItem { super.render(container); this.updateChecked(); } - } export interface IFilterWidgetOptions { @@ -79,13 +78,16 @@ export class FilterWidget extends Widget { private readonly _onDidChangeFilterText = this._register(new Emitter()); readonly onDidChangeFilterText = this._onDidChangeFilterText.event; + private readonly _onDidAcceptFilterText = this._register(new Emitter()); + readonly onDidAcceptFilterText = this._onDidAcceptFilterText.event; + private moreFiltersActionViewItem: MoreFiltersActionViewItem | undefined; private isMoreFiltersChecked: boolean = false; private lastWidth?: number; - private focusTracker: DOM.IFocusTracker; - public get onDidFocus() { return this.focusTracker.onDidFocus; } - public get onDidBlur() { return this.focusTracker.onDidBlur; } + private readonly focusTracker: DOM.IFocusTracker; + get onDidFocus() { return this.focusTracker.onDidFocus; } + get onDidBlur() { return this.focusTracker.onDidBlur; } constructor( private readonly options: IFilterWidgetOptions, @@ -178,8 +180,8 @@ export class FilterWidget extends Widget { } this._register(inputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(inputBox)))); this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => this.onInputKeyDown(e))); - this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent)); - this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent)); + this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => this.handleKeyboardEvent(e))); + this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, (e: StandardKeyboardEvent) => this.handleKeyboardEvent(e))); this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.CLICK, (e) => { e.stopPropagation(); e.preventDefault(); @@ -244,10 +246,13 @@ export class FilterWidget extends Widget { this.toolbar.focus(); handled = true; } + if (event.equals(KeyCode.Enter)) { + this._onDidAcceptFilterText.fire(); + handled = true; + } if (handled) { event.stopPropagation(); event.preventDefault(); } } - } diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 0eba1e6e0b9..0f0b283069b 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -744,7 +744,7 @@ export abstract class FilterViewPane extends ViewPane { readonly filterWidget: FilterWidget; private dimension: Dimension | undefined; - private filterContainer: HTMLElement | undefined; + protected filterContainer: HTMLElement | undefined; constructor( options: IFilterViewPaneOptions, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 51c8fec2a88..666308c49ae 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -66,7 +66,7 @@ import { IChatSessionItem, IChatSessionsService } from '../../common/chatSession import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind, VIEWLET_ID } from '../../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; @@ -1122,7 +1122,7 @@ export function registerChatActions() { id: MenuId.ViewTitle, group: 'submenu', order: 1, - when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), + when: ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.local`), } }); } @@ -1151,7 +1151,7 @@ export function registerChatActions() { id: MenuId.ViewTitle, group: 'submenu', order: 1, - when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), + when: ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.local`), } }); } @@ -1187,7 +1187,7 @@ export function registerChatActions() { id: MenuId.ViewTitle, group: 'submenu', order: 1, - when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), + when: ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.local`), } }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 3e4b881944b..796beb79aee 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -28,7 +28,7 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; -import { ChatConfiguration, VIEWLET_ID } from '../../common/constants.js'; +import { ChatConfiguration, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -477,7 +477,7 @@ MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { }, group: '1_config', order: 1, - when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), + when: ContextKeyExpr.equals('viewContainer', AGENT_SESSIONS_VIEWLET_ID), }); MenuRegistry.appendMenuItem(MenuId.ViewTitle, { @@ -488,5 +488,5 @@ MenuRegistry.appendMenuItem(MenuId.ViewTitle, { }, group: 'navigation', order: 1, - when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`), + when: ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.local`), }); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts new file mode 100644 index 00000000000..ab7b7d4dd91 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from '../../../../../base/common/async.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { localize } from '../../../../../nls.js'; +import { IChatService } from '../../common/chatService.js'; +import { IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { ChatSessionUri } from '../../common/chatUri.js'; + +//#region Interfaces, Types + +export interface IAgentSessionsViewModel { + + readonly sessions: IAgentSessionViewModel[]; + + resolve(): Promise; +} + +export const enum AgentSessionStatus { + Failed = 0, + Completed = 1, + InProgress = 2 +} + +export interface IAgentSessionViewModel { + + readonly provider: IChatSessionItemProvider; + + readonly id: string; + readonly resource: URI; + + readonly status?: AgentSessionStatus; + + readonly label: string; + readonly description: string | IMarkdownString; + readonly icon?: ThemeIcon; // TODO@bpasero support + + readonly timing: { + readonly startTime: number; + readonly endTime?: number; + }; + + readonly statistics?: { + readonly insertions: number; + readonly deletions: number; + }; +} + +export const LOCAL_AGENT_SESSION_TYPE = 'local'; + +export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean { + return session.provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE; +} + +export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel { + const session = obj as IAgentSessionViewModel | undefined; + + return typeof session?.id === 'string'; +} + +export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionsViewModel { + const sessionsViewModel = obj as IAgentSessionsViewModel | undefined; + + return Array.isArray(sessionsViewModel?.sessions); +} + +//#endregion + +const INCLUDE_HISTORY = false; // TODO@bpasero figure out how to best support history +export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel { + + readonly sessions: IAgentSessionViewModel[] = []; + + private readonly resolver = this._register(new ThrottledDelayer(100)); + + constructor( + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IChatService private readonly chatService: IChatService, + ) { + super(); + } + + async resolve(): Promise { + return this.resolver.trigger(token => this.doResolve(token)); + } + + private async doResolve(token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return; + } + + const newSessions: IAgentSessionViewModel[] = []; + for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) { + const sessions = await provider.provideChatSessionItems(token); + if (token.isCancellationRequested) { + return; + } + + for (const session of sessions) { + if (session.id === 'show-history' || session.id === 'workbench.panel.chat.view.copilot') { + continue; // TODO@bpasero this needs to be fixed at the provider level + } + + newSessions.push({ + provider, + id: session.id, + resource: session.resource, + label: session.label, + description: session.description || new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), + icon: session.iconPath, + status: session.status as unknown as AgentSessionStatus, + timing: { + startTime: session.timing.startTime, + endTime: session.timing.endTime + }, + statistics: session.statistics + }); + } + + if (INCLUDE_HISTORY && provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE) { + for (const history of await this.chatService.getHistory()) { // TODO@bpasero this needs to come from the local provider + newSessions.push({ + id: history.sessionId, + resource: ChatSessionUri.forSession(LOCAL_AGENT_SESSION_TYPE, history.sessionId), + label: history.title, + provider: provider, + timing: { + startTime: history.lastMessageDate ?? Date.now() /* TODO@bpasero BAD */ + }, + description: new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), + }); + } + } + } + + this.sessions.length = 0; + this.sessions.push(...newSessions); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts new file mode 100644 index 00000000000..d378c0f133d --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -0,0 +1,382 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/agentsessionsview.css'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js'; +import { FilterViewPane, IViewPaneOptions, ViewAction } from '../../../../browser/parts/views/viewPane.js'; +import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js'; +import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewDescriptor, IViewDescriptorService } from '../../../../common/views.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { ChatConfiguration } from '../../common/constants.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; +import { $, append } from '../../../../../base/browser/dom.js'; +import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem } from './agentSessionViewModel.js'; +import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsFilter, AgentSessionsIdentityProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; +import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; +import { IAction, toAction } from '../../../../../base/common/actions.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; +import { MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { IChatSessionsService } from '../../common/chatSessionsService.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { findExistingChatEditorByUri, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; +import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; +import { Event } from '../../../../../base/common/event.js'; +import { IProgressService } from '../../../../../platform/progress/common/progress.js'; +import { ChatSessionUri } from '../../common/chatUri.js'; +import { IChatEditorOptions } from '../chatEditor.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { assertReturnsDefined } from '../../../../../base/common/types.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { URI } from '../../../../../base/common/uri.js'; + +export class AgentSessionsView extends FilterViewPane { + + private static FILTER_FOCUS_CONTEXT_KEY = new RawContextKey('agentSessionsViewFilterFocus', false); + + private list: WorkbenchCompressibleAsyncDataTree | undefined; + private filter: AgentSessionsFilter | undefined; + + private sessionsViewModel: IAgentSessionsViewModel | undefined; + + constructor( + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @ICommandService private readonly commandService: ICommandService, + @IProgressService private readonly progressService: IProgressService, + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + ) { + super({ + ...options, + filterOptions: { + placeholder: localize('agentSessions.filterPlaceholder', "Type to filter agent sessions"), + ariaLabel: localize('agentSessions.filterAriaLabel', "Filter Agent Sessions"), + focusContextKey: AgentSessionsView.FILTER_FOCUS_CONTEXT_KEY.key + } + }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); + + this.registerActions(); + } + + override shouldShowFilterInHeader(): boolean { + return false; + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + container.classList.add('agent-sessions-view'); + + // New Session + this.createNewSessionButton(container); + + // Sessions List + this.createList(container); + + this.registerListeners(); + } + + private registerListeners(): void { + const list = assertReturnsDefined(this.list); + + // Sessions Filter + this._register(this.filterWidget.onDidChangeFilterText(() => { + if (this.filter) { + this.filter.pattern = this.filterWidget.getFilterText() || ''; + list.refilter(); + } + })); + + this._register(this.filterWidget.onDidAcceptFilterText(() => { + list.domFocus(); + if (list.getFocus().length === 0) { + list.focusFirst(); + } + })); + + // Sessions List + this._register(this.onDidChangeBodyVisibility(visible => { + if (!visible || this.sessionsViewModel) { + return; + } + + this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel)); + list.setInput(this.sessionsViewModel); + })); + + this._register(Event.debounce(Event.any( + this.chatSessionsService.onDidChangeItemsProviders, + this.chatSessionsService.onDidChangeAvailability, + this.chatSessionsService.onDidChangeSessionItems, + this.chatSessionsService.onDidChangeInProgress + ), () => undefined, 500)(() => this.refreshList({ fromEvent: true }))); + + this._register(list.onDidOpen(e => { + this.openAgentSession(e); + })); + + this._register(list.onMouseDblClick(({ element }) => { + if (element === null) { + this.commandService.executeCommand(ACTION_ID_OPEN_CHAT); + } + })); + } + + private async openAgentSession(e: IOpenEvent) { + const session = e.element; + if (!session) { + return; + } + + if (session.resource.scheme !== ChatSessionUri.scheme) { + await this.openerService.open(session.resource); + return; + } + + const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + const existingSessionEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); + if (existingSessionEditor) { + await this.editorGroupsService.getGroup(existingSessionEditor.groupId)?.openEditor(existingSessionEditor.editor, e.editorOptions); + return; + } + + let sessionResource: URI; + let sessionOptions: IChatEditorOptions; + if (isLocalAgentSessionItem(session)) { + sessionResource = ChatEditorInput.getNewEditorUri(); + sessionOptions = { target: { sessionId: session.id } }; + } else { + sessionResource = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + sessionOptions = { title: { preferred: session.label } }; + } + + sessionOptions.ignoreInView = true; + + await this.editorService.openEditor({ resource: sessionResource, options: sessionOptions }); + } + + private registerActions(): void { + this._register(registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'agentSessionsView.clearFilterText', + title: localize('clearFiltersText', "Clear Filter"), + keybinding: { + when: AgentSessionsView.FILTER_FOCUS_CONTEXT_KEY, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Escape + }, + viewId: AGENT_SESSIONS_VIEW_ID + }); + } + runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { + view.filterWidget?.setFilterText(''); + } + })); + + this._register(registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'agentSessionsView.refresh', + title: localize2('refresh', "Refresh Agent Sessions"), + icon: Codicon.refresh, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', AGENT_SESSIONS_VIEW_ID), + group: 'navigation' + }, + viewId: AGENT_SESSIONS_VIEW_ID + }); + } + runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { + view.refreshList({ fromEvent: false }); + } + })); + } + + //#region New Session Controls + + private newSessionContainer: HTMLElement | undefined; + + private createNewSessionButton(container: HTMLElement): void { + this.newSessionContainer = append(container, $('.agent-sessions-new-session-container')); + + const primaryAction = toAction({ + id: 'agentSessions.newSession.primary', + label: localize('agentSessions.newSession', "New Session"), + run: () => this.commandService.executeCommand(ACTION_ID_OPEN_CHAT) + }); + + const newSessionButton = this._register(new ButtonWithDropdown(this.newSessionContainer, { + title: localize('agentSessions.newSession', "New Session"), + ariaLabel: localize('agentSessions.newSessionAriaLabel', "New Session"), + contextMenuProvider: this.contextMenuService, + actions: { + getActions: () => { + const actions: IAction[] = []; + for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) { + if (provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE) { + continue; // local is the primary action + } + + actions.push(toAction({ + id: `newChatSessionFromProvider.${provider.chatSessionType}`, + label: localize('newChatSessionFromProvider', "New Session ({0})", provider.chatSessionType), + run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.chatSessionType}`) + })); + } + return actions; + } + }, + addPrimaryActionToDropdown: false, + ...defaultButtonStyles, + })); + + newSessionButton.label = localize('agentSessions.newSession', "New Session"); + + this._register(newSessionButton.onDidClick(() => primaryAction.run())); + } + + //#endregion + + //#region Sessions List + + private createList(container: HTMLElement): void { + const listContainer = append(container, $('.agent-sessions-viewer')); + + this.filter = this._register(new AgentSessionsFilter()); + + this.list = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, + 'AgentSessionsView', + listContainer, + new AgentSessionsListDelegate(), + new AgentSessionsCompressionDelegate(), + [ + this.instantiationService.createInstance(AgentSessionRenderer) + ], + new AgentSessionsDataSource(), + { + accessibilityProvider: new AgentSessionsAccessibilityProvider(), + identityProvider: new AgentSessionsIdentityProvider(), + horizontalScrolling: false, + multipleSelectionSupport: false, + filter: this.filter, + sorter: new AgentSessionsSorter(), + paddingBottom: AgentSessionsListDelegate.ITEM_HEIGHT + } + )) as WorkbenchCompressibleAsyncDataTree; + } + + private async refreshList({ fromEvent }: { fromEvent: boolean }): Promise { + if (this.sessionsViewModel?.sessions.length === 0 || !fromEvent) { + await this.progressService.withProgress( + { + location: this.id, + title: localize('agentSessions.refreshing', 'Refreshing agent sessions...'), + delay: fromEvent ? 800 : undefined + }, + async () => { + await this.list?.updateChildren(); + } + ); + } else { + await this.list?.updateChildren(); + } + } + + //#endregion + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + + let treeHeight = height; + treeHeight -= this.filterContainer?.offsetHeight ?? 0; + if (this.newSessionContainer) { + treeHeight -= this.newSessionContainer.offsetHeight; + } + + this.list?.layout(treeHeight, width); + } + + protected override layoutBodyContent(height: number, width: number): void { + // TODO@bpasero we deal with layout in layoutBody because we heavily customize it, reconsider using view filter inheritance + } + + override focus(): void { + super.focus(); + + if (this.list?.getFocus().length) { + this.list?.domFocus(); + } else { + this.filterWidget.focus(); + } + } +} + +//#region View Registration + +const chatAgentsIcon = registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'); + +const AGENT_SESSIONS_VIEW_CONTAINER_ID = 'workbench.viewContainer.agentSessions'; +const AGENT_SESSIONS_VIEW_ID = 'workbench.view.agentSessions'; +const AGENT_SESSIONS_VIEW_TITLE = localize2('agentSessions.view.label', "Agent Sessions"); + +const agentSessionsViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: AGENT_SESSIONS_VIEW_CONTAINER_ID, + title: AGENT_SESSIONS_VIEW_TITLE, + icon: chatAgentsIcon, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [AGENT_SESSIONS_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]), + storageId: AGENT_SESSIONS_VIEW_CONTAINER_ID, + hideIfEmpty: true, + order: 6, +}, ViewContainerLocation.Sidebar); + +const agentSessionsViewDescriptor: IViewDescriptor = { + id: AGENT_SESSIONS_VIEW_ID, + containerIcon: chatAgentsIcon, + containerTitle: AGENT_SESSIONS_VIEW_TITLE.value, + singleViewPaneContainerTitle: AGENT_SESSIONS_VIEW_TITLE.value, + name: AGENT_SESSIONS_VIEW_TITLE, + canToggleVisibility: false, + canMoveView: true, + openCommandActionDescriptor: { + id: AGENT_SESSIONS_VIEW_ID, + title: AGENT_SESSIONS_VIEW_TITLE + }, + ctorDescriptor: new SyncDescriptor(AgentSessionsView), + when: ContextKeyExpr.and( + ChatContextKeys.Setup.hidden.negate(), + ChatContextKeys.Setup.disabled.negate(), + ContextKeyExpr.equals(`config.${ChatConfiguration.AgentSessionsViewLocation}`, 'single-view'), + ) +}; +Registry.as(ViewExtensions.ViewsRegistry).registerViews([agentSessionsViewDescriptor], agentSessionsViewContainer); + +//#endregion diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts new file mode 100644 index 00000000000..43678fef7d0 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -0,0 +1,251 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/agentsessionsviewer.css'; +import { h } from '../../../../../base/browser/dom.js'; +import { localize } from '../../../../../nls.js'; +import { IIdentityProvider, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; +import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; +import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeFilter, ITreeSorter, TreeFilterResult, TreeVisibility } from '../../../../../base/browser/ui/tree/tree.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { AgentSessionStatus, IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js'; +import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { fromNow } from '../../../../../base/common/date.js'; +import { FuzzyScore, createMatches, matchesFuzzy } from '../../../../../base/common/filters.js'; +import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; +import { allowedChatMarkdownHtmlTags } from '../chatContentMarkdownRenderer.js'; +import { IProductService } from '../../../../../platform/product/common/productService.js'; + +interface IAgentSessionItemTemplate { + readonly element: HTMLElement; + + readonly title: IconLabel; + readonly icon: HTMLElement; + + readonly description: HTMLElement; + readonly timestamp: HTMLElement; + readonly diffAdded: HTMLElement; + readonly diffRemoved: HTMLElement; + + readonly elementDisposable: DisposableStore; + readonly disposables: IDisposable; +} + +export class AgentSessionRenderer implements ICompressibleTreeRenderer { + + static readonly TEMPLATE_ID = 'agent-session'; + + readonly templateId = AgentSessionRenderer.TEMPLATE_ID; + + constructor( + @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, + @IProductService private readonly productService: IProductService + ) { } + + renderTemplate(container: HTMLElement): IAgentSessionItemTemplate { + container.parentElement?.parentElement?.querySelector('.monaco-tl-twistie')?.classList.add('force-no-twistie'); // hack, but no API for hiding twistie on tree + + const disposables = new DisposableStore(); + const elementDisposable = disposables.add(new DisposableStore()); + + const elements = h( + 'div.agent-session-item@item', + [ + h('div.agent-session-icon-col', [ + h('div.agent-session-icon@icon') + ]), + h('div.agent-session-main-col', [ + h('div.agent-session-title-row', [ + h('div.agent-session-title@titleContainer'), + h('div.agent-session-diff', [ + h('span.agent-session-diff-added@diffAdded'), + h('span.agent-session-diff-removed@diffRemoved') + ]) + ]), + h('div.agent-session-details-row', [ + h('div.agent-session-description@description'), + h('div.agent-session-timestamp@timestamp') + ]) + ]) + ] + ); + + container.appendChild(elements.item); + + return { + element: elements.item, + icon: elements.icon, + title: disposables.add(new IconLabel(elements.titleContainer, { supportHighlights: true, supportIcons: true })), + description: elements.description, + timestamp: elements.timestamp, + diffAdded: elements.diffAdded, + diffRemoved: elements.diffRemoved, + elementDisposable, + disposables + }; + } + + renderElement(session: ITreeNode, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void { + template.elementDisposable.clear(); + + template.icon.className = `agent-session-icon ${ThemeIcon.asClassName(this.statusToIcon(session.element.status))}`; + + template.title.setLabel(session.element.label, undefined, { matches: createMatches(session.filterData) }); + + const { statistics: diff } = session.element; + template.diffAdded.textContent = diff ? `+${diff.insertions}` : ''; + template.diffRemoved.textContent = diff ? `-${diff.deletions}` : ''; + + if (typeof session.element.description === 'string') { + template.description.textContent = session.element.description; + } else { + template.elementDisposable.add(this.markdownRendererService.render(session.element.description, { + sanitizerConfig: { + replaceWithPlaintext: true, + allowedTags: { + override: allowedChatMarkdownHtmlTags, + }, + allowedLinkSchemes: { augment: [this.productService.urlProtocol] } + }, + }, template.description)); + } + + template.timestamp.textContent = fromNow(session.element.timing.startTime); + } + + private statusToIcon(status?: AgentSessionStatus): ThemeIcon { + switch (status) { + case AgentSessionStatus.InProgress: + return ThemeIcon.modify(Codicon.loading, 'spin'); + case AgentSessionStatus.Completed: + return Codicon.pass; + case AgentSessionStatus.Failed: + return Codicon.error; + } + + return Codicon.circleOutline; + } + + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void { + throw new Error('Should never happen since session is incompressible'); + } + + disposeElement(element: ITreeNode, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void { + template.elementDisposable.clear(); + } + + disposeTemplate(templateData: IAgentSessionItemTemplate): void { + templateData.disposables.dispose(); + } +} + +export class AgentSessionsListDelegate implements IListVirtualDelegate { + + static readonly ITEM_HEIGHT = 44; + + getHeight(element: IAgentSessionViewModel): number { + return AgentSessionsListDelegate.ITEM_HEIGHT; + } + + getTemplateId(element: IAgentSessionViewModel): string { + return AgentSessionRenderer.TEMPLATE_ID; + } +} + +export class AgentSessionsAccessibilityProvider implements IListAccessibilityProvider { + + getWidgetAriaLabel(): string { + return localize('agentSessions', "Agent Sessions"); + } + + getAriaLabel(element: IAgentSessionViewModel): string | null { + return element.label; + } +} + +export class AgentSessionsDataSource implements IAsyncDataSource { + + hasChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): boolean { + return isAgentSessionsViewModel(element); + } + + async getChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): Promise> { + if (!isAgentSessionsViewModel(element)) { + return []; + } + + await element.resolve(); + + return element.sessions; + } +} + +export class AgentSessionsIdentityProvider implements IIdentityProvider { + + getId(element: IAgentSessionsViewModel | IAgentSessionViewModel): string { + if (isAgentSession(element)) { + return element.id; + } + + return 'agent-sessions-id'; + } +} + +export class AgentSessionsFilter extends Disposable implements ITreeFilter { + + private _pattern: string = ''; + set pattern(pattern: string) { this._pattern = pattern; } + + filter(element: IAgentSessionViewModel, parentVisibility: TreeVisibility): TreeFilterResult { + if (!this._pattern) { + return TreeVisibility.Visible; + } + + const score = matchesFuzzy(this._pattern, element.label, true); + if (score) { + const fuzzyScore: FuzzyScore = [0, 0]; + for (let matchIndex = score.length - 1; matchIndex >= 0; matchIndex--) { + const match = score[matchIndex]; + for (let i = match.end - 1; i >= match.start; i--) { + fuzzyScore.push(i); + } + } + + return { data: fuzzyScore, visibility: TreeVisibility.Visible }; + } + + return TreeVisibility.Hidden; + } +} + +export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate { + + isIncompressible(element: IAgentSessionViewModel): boolean { + return true; + } +} + +export class AgentSessionsSorter implements ITreeSorter { + + compare(sessionA: IAgentSessionViewModel, sessionB: IAgentSessionViewModel): number { + const aHasEndTime = !!sessionA.timing.endTime; + const bHasEndTime = !!sessionB.timing.endTime; + + if (!aHasEndTime && bHasEndTime) { + return -1; // a (in-progress) comes before b (finished) + } + if (aHasEndTime && !bHasEndTime) { + return 1; // a (finished) comes after b (in-progress) + } + + // Both in-progress or finished: sort by start time (most recent first) + return sessionB.timing.startTime - sessionA.timing.startTime; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css new file mode 100644 index 00000000000..bb8b4742503 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.agent-sessions-view { + + display: flex; + flex-direction: column; + + .agent-sessions-viewer { + flex: 1 1 auto !important; + } + + .viewpane-filter-container, + .agent-sessions-new-session-container { + padding: 5px 12px 6px 20px; + flex: 0 0 auto !important; + } + + .viewpane-filter-container { + margin: 0 !important; + + .monaco-inputbox .input { + font-size: 13px; + } + } + + .agent-sessions-new-session-container .monaco-dropdown-button { + padding: 0 4px; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css new file mode 100644 index 00000000000..51a1a5cc087 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.agent-sessions-viewer { + + .monaco-list-row > .monaco-tl-row > .monaco-tl-twistie.force-no-twistie { + display: none !important; + } + + .monaco-list-row.selected .agent-session-details-row, + .monaco-list-row.selected span.agent-session-diff-added, + .monaco-list-row.selected span.agent-session-diff-removed { + color: unset; + } + + .agent-session-item { + display: flex; + flex-direction: row; + padding: 0 6px; + + /* #region Icon Column */ + + .agent-session-icon-col { + display: flex; + align-items: flex-start; + padding-top: 3px; + } + + .agent-session-icon { + flex-shrink: 0; + width: 16px; + height: 16px; + font-size: 16px; + } + + /* #endregion */ + + .agent-session-main-col, + .agent-session-title-row, + .agent-session-details-row { + flex: 1; + min-width: 0; + } + + .agent-session-title-row, + .agent-session-details-row { + display: flex; + line-height: 22px; + gap: 6px; + padding: 0 6px; + } + + .agent-session-details-row { + color: var(--vscode-descriptionForeground); + + .rendered-markdown { + p { + margin: 0; + } + + a { + color: var(--vscode-descriptionForeground); + } + } + } + + .agent-session-title, + .agent-session-description { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + } + + /* #region Diff Styling */ + + .agent-session-diff { + font-size: 11px; + font-weight: 700; + display: flex; + gap: 4px; + } + + span.agent-session-diff-added { + color: var(--vscode-chat-linesAddedForeground); + } + + span.agent-session-diff-removed { + color: var(--vscode-chat-linesRemovedForeground); + } + + /* #endregion */ + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c62eea2f7bd..b379cafa08f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -118,6 +118,7 @@ import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesCon import { LanguageModelToolsService, globalAutoApproveDescription } from './languageModelToolsService.js'; import './promptSyntax/promptCodingAgentActionContribution.js'; import './promptSyntax/promptToolsCodeLensProvider.js'; +import './agentSessions/agentSessionsView.js'; import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; @@ -476,7 +477,7 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.AgentSessionsViewLocation]: { type: 'string', - enum: ['disabled', 'view'], + enum: ['disabled', 'view', 'single-view'], description: nls.localize('chat.sessionsViewLocation.description', "Controls where to show the agent sessions menu."), default: 'disabled', tags: ['experimental'], diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 391a4decb20..f9404af1a5a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -44,7 +44,7 @@ const chatViewContainer: ViewContainer = Registry.as(Vi order: 1, }, ViewContainerLocation.AuxiliaryBar, { isDefault: true, doNotRegisterOpenCommand: true }); -const chatViewDescriptor: IViewDescriptor[] = [{ +const chatViewDescriptor: IViewDescriptor = { id: ChatViewId, containerIcon: chatViewContainer.icon, containerTitle: chatViewContainer.title.value, @@ -73,8 +73,8 @@ const chatViewDescriptor: IViewDescriptor[] = [{ ChatContextKeys.panelParticipantRegistered, ChatContextKeys.extensionInvalid ) -}]; -Registry.as(ViewExtensions.ViewsRegistry).registerViews(chatViewDescriptor, chatViewContainer); +}; +Registry.as(ViewExtensions.ViewsRegistry).registerViews([chatViewDescriptor], chatViewContainer); const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatParticipants', diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 570b43caba6..2ba8db27fce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -25,7 +25,7 @@ import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IC import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; -import { ChatAgentLocation, ChatModeKind, VIEWLET_ID } from '../common/constants.js'; +import { ChatAgentLocation, ChatModeKind, AGENT_SESSIONS_VIEWLET_ID } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; @@ -387,7 +387,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ group: 'navigation', order: 1, when: ContextKeyExpr.and( - ContextKeyExpr.equals('view', `${VIEWLET_ID}.${contribution.type}`) + ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.${contribution.type}`) ), }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 9760fb42fc2..f8a842fa38f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -205,6 +205,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio description: nls.localize('chat.sessions.chatView.description', "Chat View"), iconPath: Codicon.chatSparkle, status, + timing: { startTime: chatWidget?.viewModel?.model.getRequests().at(0)?.timestamp || 0 }, provider: this }; sessions.push(widgetSession); @@ -251,6 +252,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio id: LocalChatSessionsProvider.HISTORY_NODE_ID, resource: URI.parse(`${Schemas.vscodeChatSession}://history`), label: nls.localize('chat.sessions.showHistory', "History"), + timing: { startTime: 0 } }; // Add "Show history..." node at the end diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index dcc1ebf5b4b..5fbd70753b3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -25,7 +25,7 @@ import { IChatEntitlementService } from '../../../../../services/chat/common/cha import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; import { IChatSessionsService, IChatSessionItemProvider, IChatSessionsExtensionPoint } from '../../../common/chatSessionsService.js'; -import { ChatConfiguration, VIEWLET_ID } from '../../../common/constants.js'; +import { ChatConfiguration, AGENT_SESSIONS_VIEWLET_ID } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; @@ -92,7 +92,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { - id: VIEWLET_ID, + id: AGENT_SESSIONS_VIEWLET_ID, title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), hideIfEmpty: false, @@ -137,7 +137,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super( - VIEWLET_ID, + AGENT_SESSIONS_VIEWLET_ID, { mergeViewWithContainerWhenSingleView: false, }, @@ -188,7 +188,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { if (targetProvider) { // Find the corresponding view and refresh its tree - const viewId = `${VIEWLET_ID}.${chatSessionType}`; + const viewId = `${AGENT_SESSIONS_VIEWLET_ID}.${chatSessionType}`; const view = this.getView(viewId) as SessionsViewPane | undefined; if (view) { view.refreshTree(); @@ -214,7 +214,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { // Unregister removed views if (viewsToUnregister.length > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + const container = Registry.as(Extensions.ViewContainersRegistry).get(AGENT_SESSIONS_VIEWLET_ID); if (container) { Registry.as(Extensions.ViewsRegistry).deregisterViews(viewsToUnregister, container); } @@ -225,7 +225,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { } private async registerViews(extensionPointContributions: IChatSessionsExtensionPoint[]) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + const container = Registry.as(Extensions.ViewContainersRegistry).get(AGENT_SESSIONS_VIEWLET_ID); const providers = this.getAllChatSessionItemProviders(); if (container && providers.length > 0) { @@ -290,7 +290,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { orderedProviders.forEach(({ provider, displayName, baseOrder, when }) => { // Only register if not already registered if (!this.registeredViewDescriptors.has(provider.chatSessionType)) { - const viewId = `${VIEWLET_ID}.${provider.chatSessionType}`; + const viewId = `${AGENT_SESSIONS_VIEWLET_ID}.${provider.chatSessionType}`; const viewDescriptor: IViewDescriptor = { id: viewId, name: { @@ -316,7 +316,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { } }); - const gettingStartedViewId = `${VIEWLET_ID}.gettingStarted`; + const gettingStartedViewId = `${AGENT_SESSIONS_VIEWLET_ID}.gettingStarted`; if (!this.registeredViewDescriptors.has('gettingStarted') && this.productService.chatSessionRecommendations && this.productService.chatSessionRecommendations.length) { @@ -345,7 +345,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { override dispose(): void { // Unregister all views before disposal if (this.registeredViewDescriptors.size > 0) { - const container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID); + const container = Registry.as(Extensions.ViewContainersRegistry).get(AGENT_SESSIONS_VIEWLET_ID); if (container) { const allRegisteredViews = Array.from(this.registeredViewDescriptors.values()); Registry.as(Extensions.ViewsRegistry).deregisterViews(allRegisteredViews, container); diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 1025c89b78f..f166fe1d0a5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -46,6 +46,7 @@ import { IInlineCompletionsService } from '../../../../editor/browser/services/i import { IChatSessionsService } from '../common/chatSessionsService.js'; import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { AGENT_SESSIONS_VIEWLET_ID } from '../common/constants.js'; const gaugeForeground = registerColor('gauge.foreground', { dark: inputValidationInfoBorder, @@ -446,7 +447,14 @@ class ChatStatusDashboard extends Disposable { label: localize('viewChatSessionsLabel', "View Agent Sessions"), tooltip: localize('viewChatSessionsTooltip', "View Agent Sessions"), class: ThemeIcon.asClassName(Codicon.eye), - run: () => this.runCommandAndClose('workbench.view.chat.sessions'), + run: () => { + // TODO@bpasero remove this check once settled + if (this.configurationService.getValue('chat.agentSessionsViewLocation') === 'single-view') { + this.runCommandAndClose('workbench.view.agentSessions'); + } else { + this.runCommandAndClose(AGENT_SESSIONS_VIEWLET_ID); + } + } })); for (const { displayName, count } of inProgress) { diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index ca264af96e7..983599641e0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -43,7 +43,6 @@ interface IViewPaneState extends IChatViewState { hasMigratedCurrentSession?: boolean; } -export const CHAT_SIDEBAR_OLD_VIEW_PANEL_ID = 'workbench.panel.chatSidebar'; export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chat'; export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { private _widget!: ChatWidget; diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index da1470d4d57..c7240288d02 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -66,7 +66,7 @@ export interface IChatSessionItem { description?: string | IMarkdownString; status?: ChatSessionStatus; tooltip?: string | IMarkdownString; - timing?: { + timing: { startTime: number; endTime?: number; }; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index b6341fc18aa..3c826b02447 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -86,6 +86,6 @@ export namespace ChatAgentLocation { export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); -export const VIEWLET_ID = 'workbench.view.chat.sessions'; +export const AGENT_SESSIONS_VIEWLET_ID = 'workbench.view.chat.sessions'; // TODO@bpasero clear once settled export const ChatEditorTitleMaxLength = 30; From 9b5c62446f327b88f2303de91d270438df1e2ae0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 13:36:56 +0200 Subject: [PATCH 1462/4355] fix #272598 (#272685) --- .../platform/mcp/common/mcpGalleryManifest.ts | 1 + .../mcp/common/mcpGalleryManifestService.ts | 11 +++++-- .../platform/mcp/common/mcpGalleryService.ts | 32 ++++++++++++++++--- src/vs/platform/mcp/common/mcpManagement.ts | 4 ++- .../mcp/common/mcpManagementService.ts | 3 ++ .../mcp/browser/mcpWorkbenchService.ts | 21 ++++++++---- .../common/mcpWorkbenchManagementService.ts | 2 +- 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts index d1b21381d8c..86dd5d30691 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifest.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -10,6 +10,7 @@ export const enum McpGalleryResourceType { McpServersQueryService = 'McpServersQueryService', McpServerWebUri = 'McpServerWebUriTemplate', McpServerVersionUri = 'McpServerVersionUriTemplate', + McpServerIdUri = 'McpServerIdUriTemplate', McpServerLatestVersionUri = 'McpServerLatestVersionUriTemplate', McpServerNamedResourceUri = 'McpServerNamedResourceUriTemplate', PublisherUriTemplate = 'PublisherUriTemplate', diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index e7c355ff5a9..97e3406a091 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -12,8 +12,8 @@ import { IRequestService, isSuccess } from '../../request/common/request.js'; import { McpGalleryResourceType, IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; const SUPPORTED_VERSIONS = [ - 'v0.1', // First version is preferred - 'v0' + 'v0', + 'v0.1', ]; export class McpGalleryManifestService extends Disposable implements IMcpGalleryManifestService { @@ -106,6 +106,13 @@ export class McpGalleryManifestService extends Disposable implements IMcpGallery }); } + if (version === 'v0') { + resources.push({ + id: `${serversUrl}/{id}`, + type: McpGalleryResourceType.McpServerIdUri + }); + } + return { version, url, diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index e405e9a383b..a626b1fbd38 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -66,6 +66,7 @@ interface IRawGalleryMcpServer { readonly name: string; readonly description: string; readonly version: string; + readonly id?: string; readonly title?: string; readonly repository?: { readonly source: string; @@ -383,6 +384,7 @@ namespace McpServerSchemaVersion_v2025_07_09 { const gitHubInfo: RawGitHubInfo | undefined = from._meta['io.modelcontextprotocol.registry/publisher-provided']?.github as RawGitHubInfo | undefined; return { + id: registryInfo.id, name: from.name, description: from.description, repository: from.repository ? { @@ -751,15 +753,15 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService }; } - async getMcpServersFromGallery(names: string[]): Promise { + async getMcpServersFromGallery(infos: { name: string; id?: string }[]): Promise { const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); if (!mcpGalleryManifest) { return []; } const mcpServers: IGalleryMcpServer[] = []; - await Promise.allSettled(names.map(async name => { - const mcpServer = await this.getMcpServerByName(name, mcpGalleryManifest); + await Promise.allSettled(infos.map(async info => { + const mcpServer = await this.getMcpServerByName(info, mcpGalleryManifest); if (mcpServer) { mcpServers.push(mcpServer); } @@ -768,7 +770,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return mcpServers; } - private async getMcpServerByName(name: string, mcpGalleryManifest: IMcpGalleryManifest): Promise { + private async getMcpServerByName({ name, id }: { name: string; id?: string }, mcpGalleryManifest: IMcpGalleryManifest): Promise { const mcpServerUrl = this.getLatestServerVersionUrl(name, mcpGalleryManifest); if (mcpServerUrl) { const mcpServer = await this.getMcpServer(mcpServerUrl); @@ -779,7 +781,18 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService const byNameUrl = this.getNamedServerUrl(name, mcpGalleryManifest); if (byNameUrl) { - return this.getMcpServer(byNameUrl); + const mcpServer = await this.getMcpServer(byNameUrl); + if (mcpServer) { + return mcpServer; + } + } + + const byIdUrl = id ? this.getServerIdUrl(id, mcpGalleryManifest) : undefined; + if (byIdUrl) { + const mcpServer = await this.getMcpServer(byIdUrl); + if (mcpServer) { + return mcpServer; + } } return undefined; @@ -880,6 +893,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService const publisherUrl = manifest ? this.getPublisherUrl(publisher, manifest) : undefined; return { + id: server.id, name: server.name, displayName, galleryUrl: manifest?.url, @@ -1008,6 +1022,14 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return format2(namedResourceUriTemplate, { name }); } + private getServerIdUrl(id: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerIdUri); + if (!resourceUriTemplate) { + return undefined; + } + return format2(resourceUriTemplate, { id }); + } + private getLatestServerVersionUrl(name: string, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { const latestVersionResourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerLatestVersionUri); if (!latestVersionResourceUriTemplate) { diff --git a/src/vs/platform/mcp/common/mcpManagement.ts b/src/vs/platform/mcp/common/mcpManagement.ts index 2653373113c..d67ff47fb31 100644 --- a/src/vs/platform/mcp/common/mcpManagement.ts +++ b/src/vs/platform/mcp/common/mcpManagement.ts @@ -23,6 +23,7 @@ export interface ILocalMcpServer { readonly displayName?: string; readonly description?: string; readonly galleryUrl?: string; + readonly galleryId?: string; readonly repositoryUrl?: string; readonly readmeUrl?: URI; readonly publisher?: string; @@ -132,6 +133,7 @@ export interface IGalleryMcpServer { readonly version: string; readonly isLatest: boolean; readonly status: GalleryMcpServerStatus; + readonly id?: string; readonly galleryUrl?: string; readonly webUrl?: string; readonly codicon?: string; @@ -166,7 +168,7 @@ export interface IMcpGalleryService { readonly _serviceBrand: undefined; isEnabled(): boolean; query(options?: IQueryOptions, token?: CancellationToken): Promise>; - getMcpServersFromGallery(urls: string[]): Promise; + getMcpServersFromGallery(infos: { name: string; id?: string }[]): Promise; getMcpServer(url: string): Promise; getReadme(extension: IGalleryMcpServer, token: CancellationToken): Promise; } diff --git a/src/vs/platform/mcp/common/mcpManagementService.ts b/src/vs/platform/mcp/common/mcpManagementService.ts index 8c9cff671fe..570bcfa89e5 100644 --- a/src/vs/platform/mcp/common/mcpManagementService.ts +++ b/src/vs/platform/mcp/common/mcpManagementService.ts @@ -29,6 +29,7 @@ export interface ILocalMcpServerInfo { name: string; version?: string; displayName?: string; + galleryId?: string; galleryUrl?: string; description?: string; repositoryUrl?: string; @@ -426,6 +427,7 @@ export abstract class AbstractMcpResourceManagementService extends AbstractCommo publisher: mcpServerInfo.publisher, publisherDisplayName: mcpServerInfo.publisherDisplayName, galleryUrl: mcpServerInfo.galleryUrl, + galleryId: mcpServerInfo.galleryId, repositoryUrl: mcpServerInfo.repositoryUrl, readmeUrl: mcpServerInfo.readmeUrl, icon: mcpServerInfo.icon, @@ -514,6 +516,7 @@ export class McpUserResourceManagementService extends AbstractMcpResourceManagem const manifestPath = this.uriIdentityService.extUri.joinPath(location, 'manifest.json'); const local: ILocalMcpServerInfo = { galleryUrl: gallery.galleryUrl, + galleryId: gallery.id, name: gallery.name, displayName: gallery.displayName, description: gallery.description, diff --git a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts index 407b84e5756..fcfc538cf80 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts @@ -43,6 +43,7 @@ import { IIterativePager, IIterativePage } from '../../../../base/common/paging. import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { runOnChange } from '../../../../base/common/observable.js'; import Severity from '../../../../base/common/severity.js'; +import { Queue } from '../../../../base/common/async.js'; interface IMcpServerStateProvider { (mcpWorkbenchServer: McpWorkbenchServer): T; @@ -198,14 +199,20 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ this._register(this.mcpManagementService.onDidUpdateMcpServersInCurrentProfile(e => this.onDidUpdateMcpServers(e))); this._register(this.mcpManagementService.onDidUninstallMcpServerInCurrentProfile(e => this.onDidUninstallMcpServer(e))); this._register(this.mcpManagementService.onDidChangeProfile(e => this.onDidChangeProfile())); - this.queryLocal().then(() => this.syncInstalledMcpServers()); + this.queryLocal().then(() => { + if (this._store.isDisposed) { + return; + } + const queue = this._register(new Queue()); + this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifest(e => queue.queue(() => this.syncInstalledMcpServers()))); + queue.queue(() => this.syncInstalledMcpServers()); + }); urlService.registerHandler(this); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(mcpAccessConfig)) { this._onChange.fire(undefined); } })); - this._register(mcpGalleryManifestService.onDidChangeMcpGalleryManifest(e => this.syncInstalledMcpServers())); this._register(this.allowedMcpServersService.onDidChangeAllowedMcpServers(() => { this._local = this.sort(this._local); this._onChange.fire(undefined); @@ -296,19 +303,19 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ } private async syncInstalledMcpServers(): Promise { - const names: string[] = []; + const infos: { name: string; id?: string }[] = []; for (const installed of this.local) { if (installed.local?.source !== 'gallery') { continue; } if (installed.local.galleryUrl) { - names.push(installed.local.name); + infos.push({ name: installed.local.name, id: installed.local.galleryId }); } } - if (names.length) { - const galleryServers = await this.mcpGalleryService.getMcpServersFromGallery(names); + if (infos.length) { + const galleryServers = await this.mcpGalleryService.getMcpServersFromGallery(infos); await this.syncInstalledMcpServersWithGallery(galleryServers); } } @@ -695,7 +702,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ private async handleMcpServerByName(name: string): Promise { try { - const [gallery] = await this.mcpGalleryService.getMcpServersFromGallery([name]); + const [gallery] = await this.mcpGalleryService.getMcpServersFromGallery([{ name }]); if (!gallery) { this.logService.info(`MCP server '${name}' not found`); return true; diff --git a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts index b16a5566091..b2c25db699c 100644 --- a/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts +++ b/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts @@ -503,7 +503,7 @@ class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagemen return undefined; } - const [mcpServer] = await this.mcpGalleryService.getMcpServersFromGallery([name]); + const [mcpServer] = await this.mcpGalleryService.getMcpServersFromGallery([{ name }]); if (!mcpServer) { return undefined; } From e4ceaaf4ec8aca26675229b1e7431a2953a84814 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:02:48 -0700 Subject: [PATCH 1463/4355] Handle hideFromUser properly for terminal editors Fixes #272656 --- .../terminal/browser/terminalService.ts | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index ee1546ab0c4..d6fc8a493a8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -29,7 +29,6 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ThemeIcon } from '../../../../base/common/themables.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { VirtualWorkspaceContext } from '../../../common/contextkeys.js'; - import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from './terminal.js'; import { getCwdForSplit } from './terminalActions.js'; import { TerminalEditorInput } from './terminalEditorInput.js'; @@ -58,6 +57,11 @@ import { isAuxiliaryWindow, mainWindow } from '../../../../base/browser/window.j import { GroupIdentifier } from '../../../common/editor.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; +interface IBackgroundTerminal { + instance: ITerminalInstance; + terminalLocationOptions?: ITerminalLocationOptions; +} + export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; @@ -68,7 +72,7 @@ export class TerminalService extends Disposable implements ITerminalService { private readonly _terminalShellTypeContextKey: IContextKey; private _isShuttingDown: boolean = false; - private _backgroundedTerminalInstances: ITerminalInstance[] = []; + private _backgroundedTerminalInstances: IBackgroundTerminal[] = []; private _backgroundedTerminalDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -90,7 +94,7 @@ export class TerminalService extends Disposable implements ITerminalService { get restoredGroupCount(): number { return this._restoredGroupCount; } get instances(): ITerminalInstance[] { - return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances); + return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances.map(bg => bg.instance)); } /** Gets all non-background terminals. */ get foregroundInstances(): ITerminalInstance[] { @@ -470,7 +474,8 @@ export class TerminalService extends Disposable implements ITerminalService { if (layoutInfo && (layoutInfo.tabs.length > 0 || layoutInfo?.background?.length)) { mark('code/terminal/willRecreateTerminalGroups'); this._reconnectedTerminalGroups = this._recreateTerminalGroups(layoutInfo); - this._backgroundedTerminalInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); + const revivedInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); + this._backgroundedTerminalInstances = revivedInstances.map(instance => ({ instance })); mark('code/terminal/didRecreateTerminalGroups'); } // now that terminals have been restored, @@ -692,7 +697,7 @@ export class TerminalService extends Disposable implements ITerminalService { // Don't touch processes if the shutdown was a result of reload as they will be reattached const shouldPersistTerminals = this._terminalConfigurationService.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD; - for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances]) { + for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances.map(bg => bg.instance)]) { if (shouldPersistTerminals && instance.shouldPersist) { instance.detachProcessAndDispose(TerminalExitReason.Shutdown); } else { @@ -716,7 +721,7 @@ export class TerminalService extends Disposable implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; + const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.map(bg => bg.instance).filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; this._primaryBackend?.setTerminalLayoutInfo(state); } @@ -746,13 +751,13 @@ export class TerminalService extends Disposable implements ITerminalService { getInstanceFromId(terminalId: number): ITerminalInstance | undefined { let bgIndex = -1; - this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { - if (terminalInstance.instanceId === terminalId) { + this._backgroundedTerminalInstances.forEach((bg, i) => { + if (bg.instance.instanceId === terminalId) { bgIndex = i; } }); if (bgIndex !== -1) { - return this._backgroundedTerminalInstances[bgIndex]; + return this._backgroundedTerminalInstances[bgIndex].instance; } try { return this.instances[this._getIndexFromId(terminalId)]; @@ -1001,21 +1006,6 @@ export class TerminalService extends Disposable implements ITerminalService { if (!shellLaunchConfig.customPtyImplementation && !this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } - if (shellLaunchConfig.hideFromUser) { - const instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Panel); - this._backgroundedTerminalInstances.push(instance); - this._backgroundedTerminalDisposables.set(instance.instanceId, [ - instance.onDisposed(instance => { - const idx = this._backgroundedTerminalInstances.indexOf(instance); - if (idx !== -1) { - this._backgroundedTerminalInstances.splice(idx, 1); - } - this._onDidDisposeInstance.fire(instance); - }) - ]); - this._terminalHasBeenCreated.set(true); - return instance; - } this._evaluateLocalCwd(shellLaunchConfig); const location = await this.resolveLocation(options?.location) || this._terminalConfigurationService.defaultLocation; @@ -1031,6 +1021,20 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance.shellType) { this._extensionService.activateByEvent(`onTerminal:${instance.shellType}`); } + + if (shellLaunchConfig.hideFromUser) { + this._backgroundedTerminalInstances.push({ instance, terminalLocationOptions: options?.location }); + this._backgroundedTerminalDisposables.set(instance.instanceId, [ + instance.onDisposed(instance => { + const idx = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); + if (idx !== -1) { + this._backgroundedTerminalInstances.splice(idx, 1); + } + this._onDidDisposeInstance.fire(instance); + }) + ]); + } + return instance; } @@ -1115,10 +1119,12 @@ export class TerminalService extends Disposable implements ITerminalService { private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance { let instance; - const editorOptions = this._getEditorOptions(options?.location); if (location === TerminalLocation.Editor) { instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Editor); - this._terminalEditorService.openEditor(instance, editorOptions); + if (!shellLaunchConfig.hideFromUser) { + const editorOptions = this._getEditorOptions(options?.location); + this._terminalEditorService.openEditor(instance, editorOptions); + } } else { // TODO: pass resource? const group = this._terminalGroupService.createGroup(shellLaunchConfig); @@ -1181,21 +1187,27 @@ export class TerminalService extends Disposable implements ITerminalService { } public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean): Promise { - const index = this._backgroundedTerminalInstances.indexOf(instance); + const index = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); if (index === -1) { return; } - this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); + const backgroundTerminal = this._backgroundedTerminalInstances[index]; + this._backgroundedTerminalInstances.splice(index, 1); const disposables = this._backgroundedTerminalDisposables.get(instance.instanceId); if (disposables) { dispose(disposables); } this._backgroundedTerminalDisposables.delete(instance.instanceId); - this._terminalGroupService.createGroup(instance); + if (instance.target === TerminalLocation.Panel) { + this._terminalGroupService.createGroup(instance); - // Make active automatically if it's the first instance - if (this.instances.length === 1 && !suppressSetActive) { - this._terminalGroupService.setActiveInstanceByIndex(0); + // Make active automatically if it's the first instance + if (this.instances.length === 1 && !suppressSetActive) { + this._terminalGroupService.setActiveInstanceByIndex(0); + } + } else { + const editorOptions = backgroundTerminal.terminalLocationOptions ? this._getEditorOptions(backgroundTerminal.terminalLocationOptions) : this._getEditorOptions(instance.target); + this._terminalEditorService.openEditor(instance, editorOptions); } this._onDidChangeInstances.fire(); From f35c9b56a41024f1702a8b7b8f484b905be05a0e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:57:56 -0700 Subject: [PATCH 1464/4355] Don't auto approve when parsing fails --- .../browser/tools/runInTerminalTool.ts | 166 +++++++++--------- 1 file changed, 87 insertions(+), 79 deletions(-) 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 edf82c8f656..ce6765a60da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -353,99 +353,107 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const actualCommand = toolEditedCommand ?? args.command; const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; - // TODO: Handle throw - const subCommands = await this._treeSitterCommandParser.extractSubCommands(treeSitterLanguage, actualCommand); - this._logService.info('RunInTerminalTool: autoApprove: Parsed sub-commands', subCommands); + let disclaimer: IMarkdownString | undefined; + let customActions: ToolConfirmationAction[] | undefined; - // const subCommands = splitCommandLineIntoSubCommands(actualCommand, shell, os); - const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); - const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); - const autoApproveReasons: string[] = [ - ...subCommandResults.map(e => e.reason), - commandLineResult.reason, - ]; - + const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; + const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); + const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; let isAutoApproved = false; - let isDenied = false; - let autoApproveReason: 'subCommand' | 'commandLine' | undefined; - let autoApproveDefault: boolean | undefined; - - const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); - if (deniedSubCommandResult) { - this._logService.info('RunInTerminalTool: autoApprove: Sub-command DENIED auto approval'); - isDenied = true; - autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; - autoApproveReason = 'subCommand'; - } else if (commandLineResult.result === 'denied') { - this._logService.info('RunInTerminalTool: autoApprove: Command line DENIED auto approval'); - isDenied = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - autoApproveReason = 'commandLine'; - } else { - if (subCommandResults.every(e => e.result === 'approved')) { - this._logService.info('RunInTerminalTool: autoApprove: All sub-commands auto-approved'); + + let subCommands: string[] | undefined; + try { + subCommands = await this._treeSitterCommandParser.extractSubCommands(treeSitterLanguage, actualCommand); + this._logService.info(`RunInTerminalTool: autoApprove: Parsed sub-commands via ${treeSitterLanguage} grammar`, subCommands); + } catch (e) { + console.error(e); + this._logService.info(`RunInTerminalTool: autoApprove: Failed to parse sub-commands via ${treeSitterLanguage} grammar`); + } + + if (subCommands) { + const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); + const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); + const autoApproveReasons: string[] = [ + ...subCommandResults.map(e => e.reason), + commandLineResult.reason, + ]; + + let isDenied = false; + let autoApproveReason: 'subCommand' | 'commandLine' | undefined; + let autoApproveDefault: boolean | undefined; + + const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); + if (deniedSubCommandResult) { + this._logService.info('RunInTerminalTool: autoApprove: Sub-command DENIED auto approval'); + isDenied = true; + autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; autoApproveReason = 'subCommand'; - isAutoApproved = true; - autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); + } else if (commandLineResult.result === 'denied') { + this._logService.info('RunInTerminalTool: autoApprove: Command line DENIED auto approval'); + isDenied = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + autoApproveReason = 'commandLine'; } else { - this._logService.info('RunInTerminalTool: autoApprove: All sub-commands NOT auto-approved'); - if (commandLineResult.result === 'approved') { - this._logService.info('RunInTerminalTool: autoApprove: Command line auto-approved'); - autoApproveReason = 'commandLine'; + if (subCommandResults.every(e => e.result === 'approved')) { + this._logService.info('RunInTerminalTool: autoApprove: All sub-commands auto-approved'); + autoApproveReason = 'subCommand'; isAutoApproved = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; + autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); } else { - this._logService.info('RunInTerminalTool: autoApprove: Command line NOT auto-approved'); + this._logService.info('RunInTerminalTool: autoApprove: All sub-commands NOT auto-approved'); + if (commandLineResult.result === 'approved') { + this._logService.info('RunInTerminalTool: autoApprove: Command line auto-approved'); + autoApproveReason = 'commandLine'; + isAutoApproved = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + } else { + this._logService.info('RunInTerminalTool: autoApprove: Command line NOT auto-approved'); + } } } - } - // Log detailed auto approval reasoning - for (const reason of autoApproveReasons) { - this._logService.info(`- ${reason}`); - } + // Log detailed auto approval reasoning + for (const reason of autoApproveReasons) { + this._logService.info(`- ${reason}`); + } - // Apply auto approval or force it off depending on enablement/opt-in state - const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; - const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); - const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - if (isAutoApproveEnabled) { - autoApproveInfo = this._createAutoApproveInfo( - isAutoApproved, - isDenied, + // Apply auto approval or force it off depending on enablement/opt-in state + if (isAutoApproveEnabled) { + autoApproveInfo = this._createAutoApproveInfo( + isAutoApproved, + isDenied, + autoApproveReason, + subCommandResults, + commandLineResult, + ); + } else { + isAutoApproved = false; + } + + // Send telemetry about auto approval process + this._telemetry.logPrepare({ + terminalToolSessionId, + subCommands, + autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', + autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', autoApproveReason, - subCommandResults, - commandLineResult, - ); - } else { - isAutoApproved = false; - } + autoApproveDefault + }); - // Send telemetry about auto approval process - this._telemetry.logPrepare({ - terminalToolSessionId, - subCommands, - autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', - autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', - autoApproveReason, - autoApproveDefault - }); - - // Add a disclaimer warning about prompt injection for common commands that return - // content from the web - let disclaimer: IMarkdownString | undefined; - const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); - if (!isAutoApproved && ( - subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || - (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) - )) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); - } + // Add a disclaimer warning about prompt injection for common commands that return + // content from the web + const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); + if (!isAutoApproved && ( + subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || + (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) + )) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); + } - let customActions: ToolConfirmationAction[] | undefined; - if (!isAutoApproved && isAutoApproveEnabled) { - customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); + if (!isAutoApproved && isAutoApproveEnabled) { + customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); + } } let shellType = basename(shell, '.exe'); From 2fe79c9b7e7fee5cc4896097ca5122c7004d41dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:00:23 -0700 Subject: [PATCH 1465/4355] Remove broad deny rules as tree sitter can parse now --- .../terminalChatAgentToolsConfiguration.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index b0d475dd1c7..1b8384b92f2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -255,24 +255,6 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Wed, 22 Oct 2025 06:49:11 -0700 Subject: [PATCH 1466/4355] Revert some changes to TreeSitterLibraryService --- .../treeSitterSyntaxTokenBackend.ts | 6 ++-- .../treeSitter/treeSitterLibraryService.ts | 28 ++++--------------- .../standaloneTreeSitterLibraryService.ts | 10 ++----- .../services/testTreeSitterLibraryService.ts | 10 ++----- .../browser/tools/runInTerminalTool.ts | 3 +- .../browser/treeSitterCommandParser.ts | 6 +++- .../browser/treeSitterLibraryService.ts | 12 +++----- 7 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts index d322f530088..de7890799df 100644 --- a/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts +++ b/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts @@ -54,7 +54,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { } const currentLanguage = this._languageIdObs.read(reader); - const treeSitterLang = this._treeSitterLibraryService.getLanguageSync(currentLanguage, reader); + const treeSitterLang = this._treeSitterLibraryService.getLanguage(currentLanguage, false, reader); if (!treeSitterLang) { return undefined; } @@ -65,7 +65,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { })); parser.setLanguage(treeSitterLang); - const queries = this._treeSitterLibraryService.getInjectionQueriesSync(currentLanguage, reader); + const queries = this._treeSitterLibraryService.getInjectionQueries(currentLanguage, reader); if (queries === undefined) { return undefined; } @@ -80,7 +80,7 @@ export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend { return undefined; } - const queries = this._treeSitterLibraryService.getHighlightingQueriesSync(treeModel.languageId, reader); + const queries = this._treeSitterLibraryService.getHighlightingQueries(treeModel.languageId, reader); if (!queries) { return undefined; } diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index 096cb8ded88..349045958c6 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -25,48 +25,32 @@ export interface ITreeSitterLibraryService { supportsLanguage(languageId: string, reader: IReader | undefined): boolean; /** - * Gets the Tree-sitter Language object. + * Gets the tree sitter Language object synchronously. * @param languageId The language identifier to retrieve. + * @param ignoreSupportsCheck Whether to ignore the supportsLanguage check. * @param reader Optional observable reader. */ - getLanguage(languageId: string): Promise; - - /** - * Gets the Tree-sitter Language object synchronously. - * - * Note that This method runs synchronously and may fail if the language is - * not yet cached, as synchronous methods are required by editor APIs. - * @param languageId The language identifier to retrieve. - * @param reader Optional observable reader. - */ - getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined; + getLanguage(languageId: string, ignoreSupportsCheck: boolean, reader: IReader | undefined): Language | undefined; /** * Gets the injection queries for a language. A return value of `null` * indicates that there are no highlights queries for this language. - * - * Note that This method runs synchronously and may fail if the language is - * not yet cached, as synchronous methods are required by editor APIs. * @param languageId The language identifier to retrieve queries for. * @param reader Optional observable reader. */ - getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined; + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined; /** * Gets the highlighting queries for a language. A return value of `null` * indicates that there are no highlights queries for this language. - * - * Note that This method runs synchronously and may fail if the language is - * not yet cached, as synchronous methods are required by editor APIs. * @param languageId The language identifier to retrieve queries for. * @param reader Optional observable reader. */ - getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined; + getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined; /** - * Creates a custom query for a language. Returns undefiend if + * Creates a one-off custom query for a language. * @param languageId The language identifier to create the query for. - * @param reader Optional observable reader. * @param querySource The query source string to compile. */ createQuery(languageId: string, querySource: string): Promise; diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index 123cb671ee4..913a0b0fab1 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -18,19 +18,15 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer return false; } - async getLanguage(languageId: string): Promise { - throw new Error('not implemented in StandaloneTreeSitterLibraryService'); - } - - getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { + getLanguage(languageId: string, ignoreSupportsCheck: boolean, reader: IReader | undefined): Language | undefined { return undefined; } - getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } - getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index d90cfd0b67b..a28e7f3dd93 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -18,19 +18,15 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { return false; } - async getLanguage(languageId: string): Promise { - throw new Error('not implemented in TestTreeSitterLibraryService'); - } - - getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { + getLanguage(languageId: string, ignoreSupportsCheck: boolean, reader: IReader | undefined): Language | undefined { return undefined; } - getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } - getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } 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 ce6765a60da..3626204020e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -352,8 +352,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // commands that would be auto approved if it were enabled. const actualCommand = toolEditedCommand ?? args.command; - const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; - let disclaimer: IMarkdownString | undefined; let customActions: ToolConfirmationAction[] | undefined; @@ -363,6 +361,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { let isAutoApproved = false; let subCommands: string[] | undefined; + const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; try { subCommands = await this._treeSitterCommandParser.extractSubCommands(treeSitterLanguage, actualCommand); this._logService.info(`RunInTerminalTool: autoApprove: Parsed sub-commands via ${treeSitterLanguage} grammar`, subCommands); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index b495aaa51e6..7a3f3dee46a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -5,6 +5,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Lazy } from '../../../../../base/common/lazy.js'; +import { derived, waitForState } from '../../../../../base/common/observable.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; import type { Parser, Query } from '@vscode/tree-sitter-wasm'; @@ -33,7 +34,10 @@ export class TreeSitterCommandParser { async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { const parser = await this._parser; - parser.setLanguage(await this._treeSitterLibraryService.getLanguage(languageId)); + const language = await waitForState(derived(reader => { + return this._treeSitterLibraryService.getLanguage(languageId, true, reader); + })); + parser.setLanguage(language); const tree = parser.parse(commandLine); if (!tree) { diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 82ef2193d31..0fb3a46221f 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -121,19 +121,15 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return treeSitter.Parser; } - async getLanguage(languageId: string): Promise { - return this._languagesCache.get(languageId).promise; - } - - getLanguageSync(languageId: string, reader: IReader | undefined): Language | undefined { - if (!this.supportsLanguage(languageId, reader)) { + getLanguage(languageId: string, ignoreSupportsCheck: boolean, reader: IReader | undefined): Language | undefined { + if (!ignoreSupportsCheck && !this.supportsLanguage(languageId, reader)) { return undefined; } const lang = this._languagesCache.get(languageId).resolvedValue.read(reader); return lang; } - getInjectionQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; } @@ -141,7 +137,7 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return query; } - getHighlightingQueriesSync(languageId: string, reader: IReader | undefined): Query | null | undefined { + getHighlightingQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; } From 5a686608f6e368d717468635bed5ef2dbf3ef172 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 15:54:06 +0200 Subject: [PATCH 1467/4355] agent sessions - rewrite refresh logic to be provider and model based (#272708) --- .../agentSessions/agentSessionViewModel.ts | 61 +- .../agentSessions/agentSessionsView.ts | 41 +- .../agentSessions/agentSessionsViewer.ts | 4 +- .../browser/agentSessionViewModel.test.ts | 888 ++++++++++++++++++ .../test/common/mockChatSessionsService.ts | 208 ++++ 5 files changed, 1172 insertions(+), 30 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index ab7b7d4dd91..7ea9e3ff9e0 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -5,6 +5,7 @@ import { ThrottledDelayer } from '../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; @@ -18,9 +19,14 @@ import { ChatSessionUri } from '../../common/chatUri.js'; export interface IAgentSessionsViewModel { + readonly onWillResolve: Event; + readonly onDidResolve: Event; + + readonly onDidChangeSessions: Event; + readonly sessions: IAgentSessionViewModel[]; - resolve(): Promise; + resolve(provider: string | string[] | undefined): Promise; } export const enum AgentSessionStatus { @@ -78,26 +84,67 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions readonly sessions: IAgentSessionViewModel[] = []; + private readonly _onWillResolve = this._register(new Emitter()); + readonly onWillResolve = this._onWillResolve.event; + + private readonly _onDidResolve = this._register(new Emitter()); + readonly onDidResolve = this._onDidResolve.event; + + private readonly _onDidChangeSessions = this._register(new Emitter()); + readonly onDidChangeSessions = this._onDidChangeSessions.event; + private readonly resolver = this._register(new ThrottledDelayer(100)); + private readonly providersToResolve = new Set(); constructor( @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, @IChatService private readonly chatService: IChatService, ) { super(); + + this.registerListeners(); } - async resolve(): Promise { - return this.resolver.trigger(token => this.doResolve(token)); + private registerListeners(): void { + this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType }) => this.resolve(chatSessionType))); + this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined))); + this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider))); } - private async doResolve(token: CancellationToken): Promise { - if (token.isCancellationRequested) { - return; + async resolve(provider: string | string[] | undefined): Promise { + if (Array.isArray(provider)) { + for (const p of provider) { + this.providersToResolve.add(p); + } + } else { + this.providersToResolve.add(provider); } + return this.resolver.trigger(async token => { + if (token.isCancellationRequested) { + return; + } + + try { + this._onWillResolve.fire(); + return await this.doResolve(token); + } finally { + this._onDidResolve.fire(); + } + }); + } + + private async doResolve(token: CancellationToken): Promise { + const providersToResolve = Array.from(this.providersToResolve); + this.providersToResolve.clear(); + const newSessions: IAgentSessionViewModel[] = []; for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) { + if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) { + newSessions.push(...this.sessions.filter(session => session.provider.chatSessionType === provider.chatSessionType)); + continue; // skipped for resolving, preserve existing ones + } + const sessions = await provider.provideChatSessionItems(token); if (token.isCancellationRequested) { return; @@ -142,5 +189,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions this.sessions.length = 0; this.sessions.push(...newSessions); + + this._onDidChangeSessions.fire(); } } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index d378c0f133d..a7786598a05 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -37,7 +37,6 @@ import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { findExistingChatEditorByUri, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; -import { Event } from '../../../../../base/common/event.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatEditorOptions } from '../chatEditor.js'; @@ -46,6 +45,9 @@ import { ChatEditorInput } from '../chatEditorInput.js'; import { assertReturnsDefined } from '../../../../../base/common/types.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { URI } from '../../../../../base/common/uri.js'; +import { DeferredPromise } from '../../../../../base/common/async.js'; +import { Event } from '../../../../../base/common/event.js'; +import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; export class AgentSessionsView extends FilterViewPane { @@ -127,17 +129,9 @@ export class AgentSessionsView extends FilterViewPane { return; } - this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel)); - list.setInput(this.sessionsViewModel); + this.createViewModel(); })); - this._register(Event.debounce(Event.any( - this.chatSessionsService.onDidChangeItemsProviders, - this.chatSessionsService.onDidChangeAvailability, - this.chatSessionsService.onDidChangeSessionItems, - this.chatSessionsService.onDidChangeInProgress - ), () => undefined, 500)(() => this.refreshList({ fromEvent: true }))); - this._register(list.onDidOpen(e => { this.openAgentSession(e); })); @@ -216,7 +210,7 @@ export class AgentSessionsView extends FilterViewPane { }); } runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { - view.refreshList({ fromEvent: false }); + view.sessionsViewModel?.resolve(undefined); } })); } @@ -294,21 +288,26 @@ export class AgentSessionsView extends FilterViewPane { )) as WorkbenchCompressibleAsyncDataTree; } - private async refreshList({ fromEvent }: { fromEvent: boolean }): Promise { - if (this.sessionsViewModel?.sessions.length === 0 || !fromEvent) { - await this.progressService.withProgress( + private createViewModel(): void { + const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel)); + this.list?.setInput(sessionsViewModel); + + this._register(sessionsViewModel.onDidChangeSessions(() => this.list?.updateChildren())); + + const didResolveDisposable = this._register(new MutableDisposable()); + this._register(sessionsViewModel.onWillResolve(() => { + const didResolve = new DeferredPromise(); + didResolveDisposable.value = Event.once(sessionsViewModel.onDidResolve)(() => didResolve.complete()); + + this.progressService.withProgress( { location: this.id, title: localize('agentSessions.refreshing', 'Refreshing agent sessions...'), - delay: fromEvent ? 800 : undefined + delay: 500 }, - async () => { - await this.list?.updateChildren(); - } + () => didResolve.p ); - } else { - await this.list?.updateChildren(); - } + })); } //#endregion diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 43678fef7d0..0a96b2f2729 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -176,13 +176,11 @@ export class AgentSessionsDataSource implements IAsyncDataSource> { + getChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): Iterable { if (!isAgentSessionsViewModel(element)) { return []; } - await element.resolve(); - return element.sessions; } } diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts new file mode 100644 index 00000000000..80ef436bdda --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts @@ -0,0 +1,888 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { Event } from '../../../../../base/common/event.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IChatSessionItem, IChatSessionItemProvider, ChatSessionStatus } from '../../common/chatSessionsService.js'; +import { ChatSessionUri } from '../../common/chatUri.js'; +import { AgentSessionsViewModel, IAgentSessionViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem, isAgentSession, isAgentSessionsViewModel } from '../../browser/agentSessions/agentSessionViewModel.js'; +import { MockChatService } from '../common/mockChatService.js'; +import { MockChatSessionsService } from '../common/mockChatSessionsService.js'; + +suite('AgentSessionsViewModel', () => { + + const disposables = new DisposableStore(); + let mockChatSessionsService: MockChatSessionsService; + let mockChatService: MockChatService; + let viewModel: AgentSessionsViewModel; + + setup(() => { + mockChatSessionsService = new MockChatSessionsService(); + mockChatService = new MockChatService(); + }); + + teardown(() => { + disposables.clear(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('should initialize with empty sessions', () => { + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + assert.strictEqual(viewModel.sessions.length, 0); + }); + + test('should resolve sessions from providers', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session 1', + description: 'Description 1', + timing: { startTime: Date.now() } + }, + { + id: 'session-2', + resource: URI.parse('test://session-2'), + label: 'Test Session 2', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 2); + assert.strictEqual(viewModel.sessions[0].id, 'session-1'); + assert.strictEqual(viewModel.sessions[0].label, 'Test Session 1'); + assert.strictEqual(viewModel.sessions[1].id, 'session-2'); + assert.strictEqual(viewModel.sessions[1].label, 'Test Session 2'); + }); + + test('should resolve sessions from multiple providers', async () => { + const provider1: IChatSessionItemProvider = { + chatSessionType: 'type-1', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Session 1', + timing: { startTime: Date.now() } + } + ] + }; + + const provider2: IChatSessionItemProvider = { + chatSessionType: 'type-2', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-2', + resource: URI.parse('test://session-2'), + label: 'Session 2', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider1); + mockChatSessionsService.registerChatSessionItemProvider(provider2); + + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 2); + assert.strictEqual(viewModel.sessions[0].id, 'session-1'); + assert.strictEqual(viewModel.sessions[1].id, 'session-2'); + }); + + test('should fire onWillResolve and onDidResolve events', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + let willResolveFired = false; + let didResolveFired = false; + + disposables.add(viewModel.onWillResolve(() => { + willResolveFired = true; + assert.strictEqual(didResolveFired, false, 'onDidResolve should not fire before onWillResolve completes'); + })); + + disposables.add(viewModel.onDidResolve(() => { + didResolveFired = true; + assert.strictEqual(willResolveFired, true, 'onWillResolve should fire before onDidResolve'); + })); + + await viewModel.resolve(undefined); + + assert.strictEqual(willResolveFired, true, 'onWillResolve should have fired'); + assert.strictEqual(didResolveFired, true, 'onDidResolve should have fired'); + }); + + test('should fire onDidChangeSessions event after resolving', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + let sessionsChangedFired = false; + disposables.add(viewModel.onDidChangeSessions(() => { + sessionsChangedFired = true; + })); + + await viewModel.resolve(undefined); + + assert.strictEqual(sessionsChangedFired, true, 'onDidChangeSessions should have fired'); + }); + + test('should handle session with all properties', async () => { + const startTime = Date.now(); + const endTime = startTime + 1000; + + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + description: new MarkdownString('**Bold** description'), + status: ChatSessionStatus.Completed, + tooltip: 'Session tooltip', + iconPath: ThemeIcon.fromId('check'), + timing: { startTime, endTime }, + statistics: { insertions: 10, deletions: 5 } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + const session = viewModel.sessions[0]; + assert.strictEqual(session.id, 'session-1'); + assert.strictEqual(session.label, 'Test Session'); + assert.ok(session.description instanceof MarkdownString); + if (session.description instanceof MarkdownString) { + assert.strictEqual(session.description.value, '**Bold** description'); + } + assert.strictEqual(session.status, 1); // ChatSessionStatus.Completed + assert.strictEqual(session.timing.startTime, startTime); + assert.strictEqual(session.timing.endTime, endTime); + assert.deepStrictEqual(session.statistics, { insertions: 10, deletions: 5 }); + }); + + test('should add default description for sessions without description', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + assert.ok(viewModel.sessions[0].description instanceof MarkdownString); + }); + + test('should filter out special session IDs', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'show-history', + resource: URI.parse('test://show-history'), + label: 'Show History', + timing: { startTime: Date.now() } + }, + { + id: 'workbench.panel.chat.view.copilot', + resource: URI.parse('test://copilot'), + label: 'Copilot', + timing: { startTime: Date.now() } + }, + { + id: 'valid-session', + resource: URI.parse('test://valid'), + label: 'Valid Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + assert.strictEqual(viewModel.sessions[0].id, 'valid-session'); + }); + + test('should handle resolve with specific provider', async () => { + const provider1: IChatSessionItemProvider = { + chatSessionType: 'type-1', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Session 1', + timing: { startTime: Date.now() } + } + ] + }; + + const provider2: IChatSessionItemProvider = { + chatSessionType: 'type-2', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-2', + resource: URI.parse('test://session-2'), + label: 'Session 2', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider1); + mockChatSessionsService.registerChatSessionItemProvider(provider2); + + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + // First resolve all + await viewModel.resolve(undefined); + assert.strictEqual(viewModel.sessions.length, 2); + + // Now resolve only type-1 + await viewModel.resolve('type-1'); + // Should still have both sessions, but only type-1 was re-resolved + assert.strictEqual(viewModel.sessions.length, 2); + }); + + test('should handle resolve with multiple specific providers', async () => { + const provider1: IChatSessionItemProvider = { + chatSessionType: 'type-1', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Session 1', + timing: { startTime: Date.now() } + } + ] + }; + + const provider2: IChatSessionItemProvider = { + chatSessionType: 'type-2', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-2', + resource: URI.parse('test://session-2'), + label: 'Session 2', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider1); + mockChatSessionsService.registerChatSessionItemProvider(provider2); + + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(['type-1', 'type-2']); + + assert.strictEqual(viewModel.sessions.length, 2); + }); + + test('should respond to onDidChangeItemsProviders event', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions); + + // Trigger event - this should automatically call resolve + mockChatSessionsService.fireDidChangeItemsProviders(provider); + + // Wait for the sessions to be resolved + await sessionsChangedPromise; + + assert.strictEqual(viewModel.sessions.length, 1); + }); + + test('should respond to onDidChangeAvailability event', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions); + + // Trigger event - this should automatically call resolve + mockChatSessionsService.fireDidChangeAvailability(); + + // Wait for the sessions to be resolved + await sessionsChangedPromise; + + assert.strictEqual(viewModel.sessions.length, 1); + }); + + test('should respond to onDidChangeSessionItems event', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions); + + // Trigger event - this should automatically call resolve + mockChatSessionsService.fireDidChangeSessionItems('test-type'); + + // Wait for the sessions to be resolved + await sessionsChangedPromise; + + assert.strictEqual(viewModel.sessions.length, 1); + }); + + test('should maintain provider reference in session view model', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + assert.strictEqual(viewModel.sessions[0].provider, provider); + assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, 'test-type'); + }); + + test('should handle empty provider results', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 0); + }); + + test('should handle sessions with different statuses', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-failed', + resource: URI.parse('test://session-failed'), + label: 'Failed Session', + status: ChatSessionStatus.Failed, + timing: { startTime: Date.now() } + }, + { + id: 'session-completed', + resource: URI.parse('test://session-completed'), + label: 'Completed Session', + status: ChatSessionStatus.Completed, + timing: { startTime: Date.now() } + }, + { + id: 'session-inprogress', + resource: URI.parse('test://session-inprogress'), + label: 'In Progress Session', + status: ChatSessionStatus.InProgress, + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 3); + assert.strictEqual(viewModel.sessions[0].status, 0); // Failed + assert.strictEqual(viewModel.sessions[1].status, 1); // Completed + assert.strictEqual(viewModel.sessions[2].status, 2); // InProgress + }); + + test('should replace sessions on re-resolve', async () => { + let sessionCount = 1; + + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + const sessions: IChatSessionItem[] = []; + for (let i = 0; i < sessionCount; i++) { + sessions.push({ + id: `session-${i}`, + resource: URI.parse(`test://session-${i}`), + label: `Session ${i}`, + timing: { startTime: Date.now() } + }); + } + return sessions; + } + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + assert.strictEqual(viewModel.sessions.length, 1); + + sessionCount = 3; + await viewModel.resolve(undefined); + assert.strictEqual(viewModel.sessions.length, 3); + }); + + test('should handle local agent session type specially', async () => { + const provider: IChatSessionItemProvider = { + chatSessionType: LOCAL_AGENT_SESSION_TYPE, + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'local-session', + resource: ChatSessionUri.forSession(LOCAL_AGENT_SESSION_TYPE, 'local-session'), + label: 'Local Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, LOCAL_AGENT_SESSION_TYPE); + }); + + test('should correctly construct resource URIs for sessions', async () => { + const resource = URI.parse('custom://my-session/path'); + + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [ + { + id: 'session-1', + resource: resource, + label: 'Test Session', + timing: { startTime: Date.now() } + } + ] + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + await viewModel.resolve(undefined); + + assert.strictEqual(viewModel.sessions.length, 1); + assert.strictEqual(viewModel.sessions[0].resource.toString(), resource.toString()); + }); + + test('should throttle multiple rapid resolve calls', async () => { + let providerCallCount = 0; + + const provider: IChatSessionItemProvider = { + chatSessionType: 'test-type', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + providerCallCount++; + return [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Test Session', + timing: { startTime: Date.now() } + } + ]; + } + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider); + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + // Make multiple rapid resolve calls + const resolvePromises = [ + viewModel.resolve(undefined), + viewModel.resolve(undefined), + viewModel.resolve(undefined) + ]; + + await Promise.all(resolvePromises); + + // Should only call provider once due to throttling + assert.strictEqual(providerCallCount, 1); + assert.strictEqual(viewModel.sessions.length, 1); + }); + + test('should preserve sessions from non-resolved providers', async () => { + let provider1CallCount = 0; + let provider2CallCount = 0; + + const provider1: IChatSessionItemProvider = { + chatSessionType: 'type-1', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + provider1CallCount++; + return [ + { + id: 'session-1', + resource: URI.parse('test://session-1'), + label: `Session 1 (call ${provider1CallCount})`, + timing: { startTime: Date.now() } + } + ]; + } + }; + + const provider2: IChatSessionItemProvider = { + chatSessionType: 'type-2', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + provider2CallCount++; + return [ + { + id: 'session-2', + resource: URI.parse('test://session-2'), + label: `Session 2 (call ${provider2CallCount})`, + timing: { startTime: Date.now() } + } + ]; + } + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider1); + mockChatSessionsService.registerChatSessionItemProvider(provider2); + + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + // First resolve all + await viewModel.resolve(undefined); + assert.strictEqual(viewModel.sessions.length, 2); + assert.strictEqual(provider1CallCount, 1); + assert.strictEqual(provider2CallCount, 1); + const originalSession1Label = viewModel.sessions[0].label; + + // Now resolve only type-2 + await viewModel.resolve('type-2'); + + // Should still have both sessions + assert.strictEqual(viewModel.sessions.length, 2); + // Provider 1 should not be called again + assert.strictEqual(provider1CallCount, 1); + // Provider 2 should be called again + assert.strictEqual(provider2CallCount, 2); + // Session 1 should be preserved with original label + assert.strictEqual(viewModel.sessions.find(s => s.id === 'session-1')?.label, originalSession1Label); + }); + + test('should accumulate providers when resolve is called with different provider types', async () => { + let resolveCount = 0; + const resolvedProviders: (string | undefined)[] = []; + + const provider1: IChatSessionItemProvider = { + chatSessionType: 'type-1', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + resolveCount++; + resolvedProviders.push('type-1'); + return [{ + id: 'session-1', + resource: URI.parse('test://session-1'), + label: 'Session 1', + timing: { startTime: Date.now() } + }]; + } + }; + + const provider2: IChatSessionItemProvider = { + chatSessionType: 'type-2', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => { + resolveCount++; + resolvedProviders.push('type-2'); + return [{ + id: 'session-2', + resource: URI.parse('test://session-2'), + label: 'Session 2', + timing: { startTime: Date.now() } + }]; + } + }; + + mockChatSessionsService.registerChatSessionItemProvider(provider1); + mockChatSessionsService.registerChatSessionItemProvider(provider2); + + viewModel = disposables.add(new AgentSessionsViewModel( + mockChatSessionsService, + mockChatService + )); + + // Call resolve with different types rapidly - they should accumulate + const promise1 = viewModel.resolve('type-1'); + const promise2 = viewModel.resolve(['type-2']); + + await Promise.all([promise1, promise2]); + + // Both providers should be resolved + assert.strictEqual(viewModel.sessions.length, 2); + }); +}); + +suite('AgentSessionsViewModel - Helper Functions', () => { + const disposables = new DisposableStore(); + + teardown(() => { + disposables.clear(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('isLocalAgentSessionItem should identify local sessions', () => { + const localSession: IAgentSessionViewModel = { + provider: { + chatSessionType: LOCAL_AGENT_SESSION_TYPE, + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }, + id: 'local-1', + resource: URI.parse('test://local-1'), + label: 'Local', + description: 'test', + timing: { startTime: Date.now() } + }; + + const remoteSession: IAgentSessionViewModel = { + provider: { + chatSessionType: 'remote', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }, + id: 'remote-1', + resource: URI.parse('test://remote-1'), + label: 'Remote', + description: 'test', + timing: { startTime: Date.now() } + }; + + assert.strictEqual(isLocalAgentSessionItem(localSession), true); + assert.strictEqual(isLocalAgentSessionItem(remoteSession), false); + }); + + test('isAgentSession should identify session view models', () => { + const session: IAgentSessionViewModel = { + provider: { + chatSessionType: 'test', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }, + id: 'test-1', + resource: URI.parse('test://test-1'), + label: 'Test', + description: 'test', + timing: { startTime: Date.now() } + }; + + // Test with a session object + assert.strictEqual(isAgentSession(session), true); + + // Test with a sessions container - pass as session to see it returns false + const sessionOrContainer: IAgentSessionViewModel = session; + assert.strictEqual(isAgentSession(sessionOrContainer), true); + }); + + test('isAgentSessionsViewModel should identify sessions view models', () => { + const session: IAgentSessionViewModel = { + provider: { + chatSessionType: 'test', + onDidChangeChatSessionItems: Event.None, + provideChatSessionItems: async () => [] + }, + id: 'test-1', + resource: URI.parse('test://test-1'), + label: 'Test', + description: 'test', + timing: { startTime: Date.now() } + }; + + // Test with actual view model + const actualViewModel = new AgentSessionsViewModel(new MockChatSessionsService(), new MockChatService()); + disposables.add(actualViewModel); + assert.strictEqual(isAgentSessionsViewModel(actualViewModel), true); + + // Test with session object + assert.strictEqual(isAgentSessionsViewModel(session), false); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts new file mode 100644 index 00000000000..c626224c118 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/chatAgents.js'; +import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { IEditableData } from '../../../../common/views.js'; + +export class MockChatSessionsService implements IChatSessionsService { + _serviceBrand: undefined; + + private readonly _onDidChangeItemsProviders = new Emitter(); + readonly onDidChangeItemsProviders = this._onDidChangeItemsProviders.event; + + private readonly _onDidChangeSessionItems = new Emitter(); + readonly onDidChangeSessionItems = this._onDidChangeSessionItems.event; + + private readonly _onDidChangeAvailability = new Emitter(); + readonly onDidChangeAvailability = this._onDidChangeAvailability.event; + + private readonly _onDidChangeInProgress = new Emitter(); + readonly onDidChangeInProgress = this._onDidChangeInProgress.event; + + private providers = new Map(); + private contentProviders = new Map(); + private contributions: IChatSessionsExtensionPoint[] = []; + private optionGroups = new Map(); + private sessionOptions = new Map>(); + private editableData = new Map(); + private inProgress = new Map(); + + // For testing: allow triggering events + fireDidChangeItemsProviders(provider: IChatSessionItemProvider): void { + this._onDidChangeItemsProviders.fire(provider); + } + + fireDidChangeSessionItems(chatSessionType: string): void { + this._onDidChangeSessionItems.fire(chatSessionType); + } + + fireDidChangeAvailability(): void { + this._onDidChangeAvailability.fire(); + } + + fireDidChangeInProgress(): void { + this._onDidChangeInProgress.fire(); + } + + registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable { + this.providers.set(provider.chatSessionType, provider); + return { + dispose: () => { + this.providers.delete(provider.chatSessionType); + } + }; + } + + getAllChatSessionContributions(): IChatSessionsExtensionPoint[] { + return this.contributions; + } + + setContributions(contributions: IChatSessionsExtensionPoint[]): void { + this.contributions = contributions; + } + + async canResolveItemProvider(chatSessionType: string): Promise { + return this.providers.has(chatSessionType); + } + + getAllChatSessionItemProviders(): IChatSessionItemProvider[] { + return Array.from(this.providers.values()); + } + + getIconForSessionType(chatSessionType: string): ThemeIcon | undefined { + const contribution = this.contributions.find(c => c.type === chatSessionType); + return contribution?.icon ? ThemeIcon.fromId(contribution.icon) : undefined; + } + + getWelcomeTitleForSessionType(chatSessionType: string): string | undefined { + return this.contributions.find(c => c.type === chatSessionType)?.welcomeTitle; + } + + getWelcomeMessageForSessionType(chatSessionType: string): string | undefined { + return this.contributions.find(c => c.type === chatSessionType)?.welcomeMessage; + } + + getInputPlaceholderForSessionType(chatSessionType: string): string | undefined { + return this.contributions.find(c => c.type === chatSessionType)?.inputPlaceholder; + } + + getWelcomeTipsForSessionType(chatSessionType: string): string | undefined { + return this.contributions.find(c => c.type === chatSessionType)?.welcomeTips; + } + + async provideNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: unknown }, token: CancellationToken): Promise { + const provider = this.providers.get(chatSessionType); + if (!provider?.provideNewChatSessionItem) { + throw new Error(`No provider for ${chatSessionType}`); + } + return provider.provideNewChatSessionItem(options, token); + } + + async provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { + const provider = this.providers.get(chatSessionType); + if (!provider) { + return []; + } + return provider.provideChatSessionItems(token); + } + + reportInProgress(chatSessionType: string, count: number): void { + this.inProgress.set(chatSessionType, count); + this._onDidChangeInProgress.fire(); + } + + getInProgress(): { displayName: string; count: number }[] { + return Array.from(this.inProgress.entries()).map(([displayName, count]) => ({ displayName, count })); + } + + registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable { + this.contentProviders.set(chatSessionType, provider); + return { + dispose: () => { + this.contentProviders.delete(chatSessionType); + } + }; + } + + async canResolveContentProvider(chatSessionType: string): Promise { + return this.contentProviders.has(chatSessionType); + } + + async provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise { + const provider = this.contentProviders.get(chatSessionType); + if (!provider) { + throw new Error(`No content provider for ${chatSessionType}`); + } + return provider.provideChatSessionContent(id, sessionResource, token); + } + + getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined { + return this.optionGroups.get(chatSessionType); + } + + setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void { + if (optionGroups) { + this.optionGroups.set(chatSessionType, optionGroups); + } else { + this.optionGroups.delete(chatSessionType); + } + } + + private optionsChangeCallback?: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + + setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + this.optionsChangeCallback = callback; + } + + async notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + if (this.optionsChangeCallback) { + await this.optionsChangeCallback(chatSessionType, sessionId, updates); + } + } + + async setEditableSession(sessionId: string, data: IEditableData | null): Promise { + if (data) { + this.editableData.set(sessionId, data); + } else { + this.editableData.delete(sessionId); + } + } + + getEditableData(sessionId: string): IEditableData | undefined { + return this.editableData.get(sessionId); + } + + isEditable(sessionId: string): boolean { + return this.editableData.has(sessionId); + } + + notifySessionItemsChanged(chatSessionType: string): void { + this._onDidChangeSessionItems.fire(chatSessionType); + } + + getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined { + const sessionKey = `${chatSessionType}:${sessionId}`; + return this.sessionOptions.get(sessionKey)?.get(optionId); + } + + setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean { + const sessionKey = `${chatSessionType}:${sessionId}`; + if (!this.sessionOptions.has(sessionKey)) { + this.sessionOptions.set(sessionKey, new Map()); + } + this.sessionOptions.get(sessionKey)!.set(optionId, value); + return true; + } + + getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined { + return this.contributions.find(c => c.type === chatSessionType)?.capabilities; + } +} From 258d0621411aa03e74feaa9f9763633e675f23f5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:08:25 -0700 Subject: [PATCH 1468/4355] Use Language in createQuery --- .../treeSitter/treeSitterLibraryService.ts | 4 ++-- .../standaloneTreeSitterLibraryService.ts | 2 +- .../services/testTreeSitterLibraryService.ts | 2 +- .../browser/treeSitterCommandParser.ts | 24 +++++++++---------- .../browser/treeSitterLibraryService.ts | 11 ++------- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index 349045958c6..3c907113854 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -50,8 +50,8 @@ export interface ITreeSitterLibraryService { /** * Creates a one-off custom query for a language. - * @param languageId The language identifier to create the query for. + * @param language The Language to create the query for. * @param querySource The query source string to compile. */ - createQuery(languageId: string, querySource: string): Promise; + createQuery(language: Language, querySource: string): Promise; } diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index 913a0b0fab1..f724e9019ee 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -30,7 +30,7 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer return null; } - async createQuery(languageId: string, querySource: string): Promise { + async createQuery(language: Language, querySource: string): Promise { throw new Error('not implemented in StandaloneTreeSitterLibraryService'); } } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index a28e7f3dd93..b724892f883 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -30,7 +30,7 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { return null; } - async createQuery(languageId: string, querySource: string): Promise { + async createQuery(language: Language, querySource: string): Promise { throw new Error('not implemented in TestTreeSitterLibraryService'); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 7a3f3dee46a..44d4a4446a0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { BugIndicatingError } from '../../../../../base/common/errors.js'; -import { Lazy } from '../../../../../base/common/lazy.js'; import { derived, waitForState } from '../../../../../base/common/observable.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import type { Parser, Query } from '@vscode/tree-sitter-wasm'; +import type { Language, Parser, Query } from '@vscode/tree-sitter-wasm'; export const enum TreeSitterCommandParserLanguage { Bash = 'bash', @@ -16,15 +15,7 @@ export const enum TreeSitterCommandParserLanguage { export class TreeSitterCommandParser { private readonly _parser: Promise; - - private readonly _languageToQueryMap = { - [TreeSitterCommandParserLanguage.Bash]: new Lazy(() => { - return this._treeSitterLibraryService.createQuery(TreeSitterCommandParserLanguage.Bash, '(command) @command'); - }), - [TreeSitterCommandParserLanguage.PowerShell]: new Lazy(() => { - return this._treeSitterLibraryService.createQuery(TreeSitterCommandParserLanguage.PowerShell, '(command\ncommand_name: (command_name) @function)'); - }), - } satisfies { [K in TreeSitterCommandParserLanguage]: Lazy> }; + private readonly _queries: Map = new Map(); constructor( @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService @@ -44,7 +35,7 @@ export class TreeSitterCommandParser { throw new BugIndicatingError('Failed to parse tree'); } - const query = await this._languageToQueryMap[languageId].value; + const query = await this._getQuery(language); if (!query) { throw new BugIndicatingError('Failed to create tree sitter query'); } @@ -53,4 +44,13 @@ export class TreeSitterCommandParser { const subCommands = captures.map(e => e.node.text); return subCommands; } + + private async _getQuery(language: Language): Promise { + let query = this._queries.get(language); + if (!query) { + query = await this._treeSitterLibraryService.createQuery(language, '(command) @command'); + this._queries.set(language, query); + } + return query; + } } diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 0fb3a46221f..b6e82609b2d 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -145,15 +145,8 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return query; } - async createQuery(languageId: string, querySource: string): Promise { - const [ - language, - treeSitter - ] = await Promise.all([ - this._languagesCache.get(languageId).promise, - this._treeSitterImport.value, - ]); - + async createQuery(language: Language, querySource: string): Promise { + const treeSitter = await this._treeSitterImport.value; return new treeSitter.Query(language, querySource); } } From e7bc1917139599733c87796448ec33591045852e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 Oct 2025 16:22:58 +0200 Subject: [PATCH 1469/4355] update distro (#272690) --- package.json | 4 ++-- src/vs/platform/mcp/common/mcpGalleryManifestService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7cc9ebfd35e..827154f201b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "dd592c236d97d1f7ffa7cb95b154020782fa3659", + "distro": "e3691f9d32e65359c32b6b83a24dd5a927e3765a", "author": { "name": "Microsoft Corporation" }, @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} +} \ No newline at end of file diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts index 97e3406a091..377d3071260 100644 --- a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -12,8 +12,8 @@ import { IRequestService, isSuccess } from '../../request/common/request.js'; import { McpGalleryResourceType, IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; const SUPPORTED_VERSIONS = [ - 'v0', 'v0.1', + 'v0', ]; export class McpGalleryManifestService extends Disposable implements IMcpGalleryManifestService { From c8a2746c5cf0e608d1ac32fe155c3107eca06371 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 22 Oct 2025 15:23:31 +0100 Subject: [PATCH 1470/4355] Adjust extension editor layout spacing and increase max-width to 90% --- .../extensions/browser/media/extensionEditor.css | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a45036b6727..3ca6ab5a863 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -9,7 +9,7 @@ display: flex; flex-direction: column; margin: 0px auto; - max-width: 80%; + max-width: 90%; } .extension-editor .clickable { @@ -19,9 +19,8 @@ .extension-editor > .header { display: flex; padding-top: 20px; - padding-bottom: 14px; + padding-bottom: 12px; padding-left: 20px; - padding-right: 20px; overflow: hidden; font-size: 14px; } @@ -59,7 +58,7 @@ } .extension-editor > .header > .details { - padding-left: 20px; + padding-left: 12px; overflow: hidden; user-select: text; -webkit-user-select: text; @@ -460,16 +459,16 @@ .extension-editor > .body > .content > .details > .additional-details-container > .monaco-scrollable-element > .additional-details-content { height: 100%; overflow-y: scroll; - padding: 20px; + padding: 12px; box-sizing: border-box; } .extension-editor > .body > .content > .details > .additional-details-container .additional-details-element:not(:first-child) { - padding-top: 15px; + padding-top: 16px; } .extension-editor > .body > .content > .details > .additional-details-container .additional-details-element { - padding-bottom: 15px; + padding-bottom: 16px; } .extension-editor > .body > .content > .details > .additional-details-container .additional-details-element:not(:last-child) { @@ -478,7 +477,7 @@ .extension-editor > .body > .content > .details > .additional-details-container .additional-details-element > .additional-details-title { font-size: 120%; - padding-bottom: 15px; + padding-bottom: 12px; } .extension-editor > .body > .content > .details > .additional-details-container .categories-container > .categories > .category { From 6ae9c550e306668c0a2cf1545eb827c6080d235d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:32:57 -0700 Subject: [PATCH 1471/4355] Get tree sitter import working in tests --- .../test/browser/runInTerminalTool.test.ts | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index be45523bbe1..dd3bce6f4d1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -24,6 +24,15 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../../../ import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import { count } from '../../../../../../base/common/strings.js'; import { ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; +import { ITreeSitterLibraryService } from '../../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; +import { FileService } from '../../../../../../platform/files/common/fileService.js'; +import { NullLogService } from '../../../../../../platform/log/common/log.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { Schemas } from '../../../../../../base/common/network.js'; + +// eslint-disable-next-line local/code-layering, local/code-import-patterns +import { DiskFileSystemProvider } from '../../../../../../platform/files/node/diskFileSystemProvider.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -42,6 +51,7 @@ suite('RunInTerminalTool', () => { let instantiationService: TestInstantiationService; let configurationService: TestConfigurationService; + let fileService: IFileService; let storageService: IStorageService; let terminalServiceDisposeEmitter: Emitter; let chatServiceDisposeEmitter: Emitter<{ sessionId: string; reason: 'cleared' }>; @@ -50,13 +60,25 @@ suite('RunInTerminalTool', () => { setup(() => { configurationService = new TestConfigurationService(); + + const logService = new NullLogService(); + fileService = store.add(new FileService(logService)); + const diskFileSystemProvider = store.add(new DiskFileSystemProvider(logService)); + store.add(fileService.registerProvider(Schemas.file, diskFileSystemProvider)); + setConfig(TerminalChatAgentToolsSettingId.EnableAutoApprove, true); terminalServiceDisposeEmitter = new Emitter(); chatServiceDisposeEmitter = new Emitter<{ sessionId: string; reason: 'cleared' }>(); instantiationService = workbenchInstantiationService({ configurationService: () => configurationService, + fileService: () => fileService, }, store); + + const treeSitterLibraryService = store.add(instantiationService.createInstance(TreeSitterLibraryService)); + treeSitterLibraryService.isTest = true; + instantiationService.stub(ITreeSitterLibraryService, treeSitterLibraryService); + instantiationService.stub(ILanguageModelToolsService, { getTools() { return []; @@ -286,14 +308,14 @@ suite('RunInTerminalTool', () => { suite('auto approved', () => { for (const command of autoApprovedTestCases) { test(command.replaceAll('\n', '\\n'), async () => { - assertAutoApproved(await executeToolTest({ command: command })); + assertAutoApproved(await executeToolTest({ command })); }); } }); suite('confirmation required', () => { for (const command of confirmationRequiredTestCases) { test(command.replaceAll('\n', '\\n'), async () => { - assertConfirmationRequired(await executeToolTest({ command: command })); + assertConfirmationRequired(await executeToolTest({ command })); }); } }); @@ -422,18 +444,6 @@ suite('RunInTerminalTool', () => { assertAutoApproved(result); }); - test('should handle commands with only whitespace', async () => { - setAutoApprove({ - echo: true - }); - - const result = await executeToolTest({ - command: ' \t\n ', - explanation: 'Whitespace only command' - }); - assertConfirmationRequired(result); - }); - test('should handle matchCommandLine: true patterns', async () => { setAutoApprove({ '/dangerous/': { approve: false, matchCommandLine: true }, From b8f5add6c85e257193fc82b559779f378eded397 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 17:25:02 +0200 Subject: [PATCH 1472/4355] view filter - call into `focus()` when accepting filter (#272721) --- src/vs/workbench/browser/parts/views/viewPane.ts | 1 + .../browser/agentSessions/agentSessionsView.ts | 15 ++------------- .../test/browser/agentSessionViewModel.test.ts | 8 ++++---- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 0f0b283069b..4eb9f243c31 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -762,6 +762,7 @@ export abstract class FilterViewPane extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService, accessibleViewService); const childInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); this.filterWidget = this._register(childInstantiationService.createInstance(FilterWidget, options.filterOptions)); + this._register(this.filterWidget.onDidAcceptFilterText(() => this.focus())); } override getFilterWidget(): FilterWidget { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index a7786598a05..0d0f77a7ead 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -116,13 +116,6 @@ export class AgentSessionsView extends FilterViewPane { } })); - this._register(this.filterWidget.onDidAcceptFilterText(() => { - list.domFocus(); - if (list.getFocus().length === 0) { - list.focusFirst(); - } - })); - // Sessions List this._register(this.onDidChangeBodyVisibility(visible => { if (!visible || this.sessionsViewModel) { @@ -331,11 +324,7 @@ export class AgentSessionsView extends FilterViewPane { override focus(): void { super.focus(); - if (this.list?.getFocus().length) { - this.list?.domFocus(); - } else { - this.filterWidget.focus(); - } + this.list?.domFocus(); } } @@ -355,7 +344,7 @@ const agentSessionsViewContainer = Registry.as(ViewExte storageId: AGENT_SESSIONS_VIEW_CONTAINER_ID, hideIfEmpty: true, order: 6, -}, ViewContainerLocation.Sidebar); +}, ViewContainerLocation.AuxiliaryBar); const agentSessionsViewDescriptor: IViewDescriptor = { id: AGENT_SESSIONS_VIEW_ID, diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts index 80ef436bdda..5737e1e148a 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts @@ -221,7 +221,7 @@ suite('AgentSessionsViewModel', () => { if (session.description instanceof MarkdownString) { assert.strictEqual(session.description.value, '**Bold** description'); } - assert.strictEqual(session.status, 1); // ChatSessionStatus.Completed + assert.strictEqual(session.status, ChatSessionStatus.Completed); assert.strictEqual(session.timing.startTime, startTime); assert.strictEqual(session.timing.endTime, endTime); assert.deepStrictEqual(session.statistics, { insertions: 10, deletions: 5 }); @@ -552,9 +552,9 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 3); - assert.strictEqual(viewModel.sessions[0].status, 0); // Failed - assert.strictEqual(viewModel.sessions[1].status, 1); // Completed - assert.strictEqual(viewModel.sessions[2].status, 2); // InProgress + assert.strictEqual(viewModel.sessions[0].status, ChatSessionStatus.Failed); + assert.strictEqual(viewModel.sessions[1].status, ChatSessionStatus.Completed); + assert.strictEqual(viewModel.sessions[2].status, ChatSessionStatus.InProgress); }); test('should replace sessions on re-resolve', async () => { From ad758a48495f5be94365a573f888819323406d96 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 22 Oct 2025 16:26:50 +0100 Subject: [PATCH 1473/4355] Fix unintended hiding of inline actions on SCM Changes view (fix #272716) (#272717) --- src/vs/workbench/contrib/scm/browser/menus.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 6e58d44ba54..e8534f7bc99 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -348,10 +348,11 @@ export class SCMMenus implements ISCMMenus, IDisposable { // In order to keep the Repositories view clean, we hide the // primary actions by default. Users can always promote actions // from the `...` menu to inline actions. + let itemToAppend = menuItem; if (isIMenuItem(menuItem) && menuItem.group === 'navigation') { - menuItem.isHiddenByDefault = true; + itemToAppend = { ...menuItem, isHiddenByDefault: true }; } - this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); + this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, itemToAppend)); } })); } From e70113b5bdb1679ccea20104103d2edd7a35e99f Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 22 Oct 2025 08:29:53 -0700 Subject: [PATCH 1474/4355] Fix todo margins (#272613) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 2e8d7c98df7..bea4e48024b 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1082,6 +1082,7 @@ have to be updated for changes to the rules above, or to support more deeply nes /* Chat Todo List Widget Container - mirrors chat-editing-session styling */ .interactive-session .interactive-input-part > .chat-todo-list-widget-container { + margin-bottom: -4px; /* reset the 4px gap of the container for editing sessions */ width: 100%; position: relative; } From e1f2a0414f4dfd1b4efd85a542c37216e881d646 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:34:52 -0700 Subject: [PATCH 1475/4355] Sync tree sitter in remote package.json --- remote/package-lock.json | 8 ++++---- remote/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/remote/package-lock.json b/remote/package-lock.json index 238a0284ce0..09d6c70275c 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -16,7 +16,7 @@ "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", @@ -185,9 +185,9 @@ } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.2.0.tgz", + "integrity": "sha512-abvLfKwmriqgdS4WrIzFK7mzdPUVqIIW1UWarp2lA8lpOZ1EDPL1snRBKe7g+5R5ri173mNJEuPLnG/NlpMp4w==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { diff --git a/remote/package.json b/remote/package.json index 268bdf55729..41979903e57 100644 --- a/remote/package.json +++ b/remote/package.json @@ -11,7 +11,7 @@ "@vscode/proxy-agent": "^0.35.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", From 462356a14d686ecf253b254a385cc41d68f01d56 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:40:30 -0700 Subject: [PATCH 1476/4355] Fix denial auto approve check We shouldn't show any rule suggestions if anything was denied --- .../browser/runInTerminalHelpers.ts | 9 ++++---- .../browser/tools/runInTerminalTool.ts | 6 ++--- .../test/browser/runInTerminalTool.test.ts | 22 +++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 36cd3b1ab10..19330fc97ca 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -69,7 +69,10 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str // We shouldn't offer configuring rules for commands that are explicitly denied since it // wouldn't get auto approved with a new rule - const canCreateAutoApproval = autoApproveResult.subCommandResults.some(e => e.result !== 'denied') || autoApproveResult.commandLineResult.result === 'denied'; + const canCreateAutoApproval = ( + autoApproveResult.subCommandResults.every(e => e.result !== 'denied') && + autoApproveResult.commandLineResult.result !== 'denied' + ); if (canCreateAutoApproval) { const unapprovedSubCommands = subCommands.filter((_, index) => { return autoApproveResult.subCommandResults[index].result !== 'approved'; @@ -152,9 +155,7 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str if ( firstSubcommandFirstWord !== commandLine && !commandsWithSubcommands.has(commandLine) && - !commandsWithSubSubCommands.has(commandLine) && - autoApproveResult.commandLineResult.result !== 'denied' && - autoApproveResult.subCommandResults.every(e => e.result !== 'denied') + !commandsWithSubSubCommands.has(commandLine) ) { actions.push({ label: localize('autoApprove.exactCommand', 'Always Allow Exact Command Line'), 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 3626204020e..5e6ec0a8d6b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -59,7 +59,7 @@ function createPowerShellModelDescription(shell: string): string { '- Does NOT support multi-line commands', // Even for pwsh 7+ we want to use `;` to chain commands since the tree sitter grammar // doesn't parse `&&` - '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', + // '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', '- Prefer pipelines | for object-based data flow', '', 'Directory Management:', @@ -414,7 +414,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // Log detailed auto approval reasoning for (const reason of autoApproveReasons) { - this._logService.info(`- ${reason}`); + this._logService.info(`RunInTerminalTool: autoApprove: - ${reason}`); } // Apply auto approval or force it off depending on enablement/opt-in state @@ -1001,7 +1001,7 @@ class BackgroundTerminalExecution extends Disposable { } } -class TerminalProfileFetcher { +export class TerminalProfileFetcher { readonly osBackend: Promise; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index dd3bce6f4d1..1e03ba416ac 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -91,7 +91,7 @@ suite('RunInTerminalTool', () => { onDidDisposeSession: chatServiceDisposeEmitter.event }); instantiationService.stub(ITerminalProfileResolverService, { - getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) + getDefaultProfile: async () => ({ path: 'bash' } as ITerminalProfile) }); storageService = instantiationService.get(IStorageService); @@ -341,7 +341,7 @@ suite('RunInTerminalTool', () => { command: 'rm file.txt', explanation: 'Remove a file' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); }); test('should require confirmation for commands in deny list even if in allow list', async () => { @@ -354,7 +354,7 @@ suite('RunInTerminalTool', () => { command: 'rm dangerous-file.txt', explanation: 'Remove a dangerous file' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); }); test('should handle background commands with confirmation', async () => { @@ -367,7 +367,7 @@ suite('RunInTerminalTool', () => { explanation: 'Start watching for file changes', isBackground: true }); - assertConfirmationRequired(result, 'Run `pwsh` command? (background terminal)'); + assertConfirmationRequired(result, 'Run `bash` command? (background terminal)'); }); test('should auto-approve background commands in allow list', async () => { @@ -514,7 +514,7 @@ suite('RunInTerminalTool', () => { explanation: 'Build the project' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ { subCommand: 'npm run build' }, 'commandLine', @@ -558,7 +558,7 @@ suite('RunInTerminalTool', () => { explanation: 'Build the project' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ 'configure', ]); @@ -570,7 +570,7 @@ suite('RunInTerminalTool', () => { explanation: 'Install dependencies and build' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ { subCommand: ['npm install', 'npm run build'] }, 'commandLine', @@ -588,7 +588,7 @@ suite('RunInTerminalTool', () => { explanation: 'Run foo command and show first 20 lines' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ { subCommand: 'foo' }, 'commandLine', @@ -620,7 +620,7 @@ suite('RunInTerminalTool', () => { explanation: 'Run multiple piped commands' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ { subCommand: ['foo', 'bar'] }, 'commandLine', @@ -931,7 +931,7 @@ suite('RunInTerminalTool', () => { clearAutoApproveWarningAcceptedState(); - assertConfirmationRequired(await executeToolTest({ command: 'echo hello world' }), 'Run `pwsh` command?'); + assertConfirmationRequired(await executeToolTest({ command: 'echo hello world' }), 'Run `bash` command?'); }); test('should auto-approve commands when both auto-approve enabled and warning accepted', async () => { @@ -950,7 +950,7 @@ suite('RunInTerminalTool', () => { }); const result = await executeToolTest({ command: 'echo hello world' }); - assertConfirmationRequired(result, 'Run `pwsh` command?'); + assertConfirmationRequired(result, 'Run `bash` command?'); }); }); From d62af7efaf8e0f5a7827563405f185f1d4687dc2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 18:00:17 +0200 Subject: [PATCH 1477/4355] chat - adjustments to the tools and server picker in input (#272475) * chat - adjustments to the tools and server picker in input * revert accidental changes * decorate * back to & * polish --- .../chat/browser/actions/chatToolActions.ts | 6 +-- .../contrib/chat/browser/media/chat.css | 45 +++++++------------ .../contrib/mcp/browser/mcpCommands.ts | 36 +++++++-------- 3 files changed, 35 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index 581abeab7aa..88002b6b00c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -122,9 +122,9 @@ class ConfigureToolsAction extends Action2 { precondition: ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Agent), menu: [{ when: ContextKeyExpr.and(ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Agent), ChatContextKeys.lockedToCodingAgent.negate()), - id: MenuId.ChatExecute, + id: MenuId.ChatInput, group: 'navigation', - order: 1, + order: 100, }] }); } @@ -189,7 +189,7 @@ class ConfigureToolsActionRendering implements IWorkbenchContribution { constructor( @IActionViewItemService actionViewItemService: IActionViewItemService, ) { - const disposable = actionViewItemService.register(MenuId.ChatExecute, ConfigureToolsAction.ID, (action, _opts, instantiationService) => { + const disposable = actionViewItemService.register(MenuId.ChatInput, ConfigureToolsAction.ID, (action, _opts, instantiationService) => { if (!(action instanceof MenuItemAction)) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index bea4e48024b..3450b4aca04 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1308,8 +1308,8 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-input-toolbars .tool-warning-indicator { position: absolute; - bottom: 0; - right: 0; + bottom: 1px; + right: 1px; font-size: 9px !important; color: var(--vscode-problemsWarningIcon-foreground); background: var(--vscode-input-background); @@ -1599,40 +1599,27 @@ have to be updated for changes to the rules above, or to support more deeply nes } .action-item.chat-mcp { - display: flex !important; - - &.chat-mcp-has-action .action-label { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right: 0; - } - .chat-mcp-action { - align-self: stretch; - padding: 0 2px; - border-radius: 0; - outline: 0; - border: 0; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - background: var(--vscode-button-background); - cursor: pointer; + .chat-mcp-state-indicator { + position: absolute; + bottom: 1px; + right: 1px; + font-size: 9px !important; + background: var(--vscode-input-background); + width: fit-content; + height: fit-content; + border-radius: 100%; - .codicon { - width: fit-content; + &.chat-mcp-state-new { color: var(--vscode-button-foreground); } - .codicon::before { - font-size: 14px; + &.chat-mcp-state-error { + color: var(--vscode-problemsErrorIcon-foreground); } - &.chat-mcp-action-error { - background: var(--vscode-activityErrorBadge-background); - - .codicon { - color: var(--vscode-activityErrorBadge-foreground); - } + &.chat-mcp-state-refreshing { + color: var(--vscode-button-foreground); } } } diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts index eae0316f8c0..d7eb2ae115c 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, addDisposableListener, disposableWindowInterval, EventType, h } from '../../../../base/browser/dom.js'; +import { $, addDisposableListener, disposableWindowInterval, EventType } from '../../../../base/browser/dom.js'; import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js'; import { IManagedHoverTooltipHTMLElement } from '../../../../base/browser/ui/hover/hover.js'; import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; @@ -90,9 +90,9 @@ export class ListMcpServerCommand extends Action2 { ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Agent), ChatContextKeys.lockedToCodingAgent.negate() ), - id: MenuId.ChatExecute, + id: MenuId.ChatInput, group: 'navigation', - order: 2, + order: 101, }], }); } @@ -478,7 +478,7 @@ export class MCPServerActionRendering extends Disposable implements IWorkbenchCo } }); - this._store.add(actionViewItemService.register(MenuId.ChatExecute, McpCommandIds.ListServer, (action, options) => { + this._store.add(actionViewItemService.register(MenuId.ChatInput, McpCommandIds.ListServer, (action, options) => { if (!(action instanceof MenuItemAction)) { return undefined; } @@ -489,34 +489,30 @@ export class MCPServerActionRendering extends Disposable implements IWorkbenchCo super.render(container); container.classList.add('chat-mcp'); + container.style.position = 'relative'; - const action = h('button.chat-mcp-action', [h('span@icon')]); + const stateIndicator = container.appendChild($('.chat-mcp-state-indicator')); + stateIndicator.style.display = 'none'; this._register(autorun(r => { const displayed = displayedState.read(r); const { state } = displayed; - const { root, icon } = action; this.updateTooltip(); - container.classList.toggle('chat-mcp-has-action', state !== DisplayedState.None); - if (!root.parentElement) { - container.appendChild(root); - } - root.ariaLabel = this.getLabelForState(displayed); - root.className = 'chat-mcp-action'; - icon.className = ''; + stateIndicator.ariaLabel = this.getLabelForState(displayed); + stateIndicator.className = 'chat-mcp-state-indicator'; if (state === DisplayedState.NewTools) { - root.classList.add('chat-mcp-action-new'); - icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.refresh)); + stateIndicator.style.display = 'block'; + stateIndicator.classList.add('chat-mcp-state-new', ...ThemeIcon.asClassNameArray(Codicon.refresh)); } else if (state === DisplayedState.Error) { - root.classList.add('chat-mcp-action-error'); - icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.warning)); + stateIndicator.style.display = 'block'; + stateIndicator.classList.add('chat-mcp-state-error', ...ThemeIcon.asClassNameArray(Codicon.warning)); } else if (state === DisplayedState.Refreshing) { - root.classList.add('chat-mcp-action-refreshing'); - icon.classList.add(...ThemeIcon.asClassNameArray(spinningLoading)); + stateIndicator.style.display = 'block'; + stateIndicator.classList.add('chat-mcp-state-refreshing', ...ThemeIcon.asClassNameArray(spinningLoading)); } else { - root.remove(); + stateIndicator.style.display = 'none'; } })); } From 6f54e47dc86827f6228c1c53eec62abf3e4a8039 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:32:20 -0700 Subject: [PATCH 1478/4355] Reduce duplication in test --- .../test/browser/runInTerminalTool.test.ts | 86 ++++--------------- 1 file changed, 16 insertions(+), 70 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 1e03ba416ac..0102e1b7ca4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -290,19 +290,6 @@ suite('RunInTerminalTool', () => { 'HTTP_PROXY=proxy:8080 wget https://example.com', 'VAR1=value1 VAR2=value2 echo test', 'A=1 B=2 C=3 ./script.sh', - - // Dangerous patterns - 'echo $(whoami)', - 'ls $(pwd)', - 'echo `date`', - 'cat `which ls`', - 'echo ${HOME}', - 'ls {a,b,c}', - 'echo (Get-Date)', - - // Dangerous patterns - multi-line - 'echo "{\n}"', - 'echo @"\n{\n}"@', ]; suite('auto approved', () => { @@ -970,66 +957,25 @@ suite('RunInTerminalTool', () => { }); }); -}); - -suite('TerminalProfileFetcher', () => { - const store = ensureNoDisposablesAreLeakedInTestSuite(); + suite('TerminalProfileFetcher', () => { + suite('getCopilotProfile', () => { + (isWindows ? test : test.skip)('should return custom profile when configured', async () => { + runInTerminalTool.setBackendOs(OperatingSystem.Windows); + const customProfile = Object.freeze({ path: 'C:\\Windows\\System32\\powershell.exe', args: ['-NoProfile'] }); + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, customProfile); - let instantiationService: TestInstantiationService; - let configurationService: TestConfigurationService; - let testTool: TestRunInTerminalTool; - - setup(() => { - configurationService = new TestConfigurationService(); - - instantiationService = workbenchInstantiationService({ - configurationService: () => configurationService, - }, store); - instantiationService.stub(ILanguageModelToolsService, { - getTools() { - return []; - }, - }); - instantiationService.stub(ITerminalService, { - onDidDisposeInstance: new Emitter().event - }); - instantiationService.stub(IChatService, { - onDidDisposeSession: new Emitter<{ sessionId: string; reason: 'cleared' }>().event - }); - instantiationService.stub(ITerminalProfileResolverService, { - getDefaultProfile: async () => ({ path: 'pwsh' } as ITerminalProfile) - }); - - testTool = store.add(instantiationService.createInstance(TestRunInTerminalTool)); - }); - - function setConfig(key: string, value: unknown) { - configurationService.setUserConfiguration(key, value); - configurationService.onDidChangeConfigurationEmitter.fire({ - affectsConfiguration: () => true, - affectedKeys: new Set([key]), - source: ConfigurationTarget.USER, - change: null!, - }); - } - - suite('getCopilotProfile', () => { - (isWindows ? test : test.skip)('should return custom profile when configured', async () => { - testTool.setBackendOs(OperatingSystem.Windows); - const customProfile = Object.freeze({ path: 'C:\\Windows\\System32\\powershell.exe', args: ['-NoProfile'] }); - setConfig(TerminalChatAgentToolsSettingId.TerminalProfileWindows, customProfile); - - const result = await testTool.profileFetcher.getCopilotProfile(); - strictEqual(result, customProfile); - }); + const result = await runInTerminalTool.profileFetcher.getCopilotProfile(); + strictEqual(result, customProfile); + }); - (isLinux ? test : test.skip)('should fall back to default shell when no custom profile is configured', async () => { - testTool.setBackendOs(OperatingSystem.Linux); - setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); + (isLinux ? test : test.skip)('should fall back to default shell when no custom profile is configured', async () => { + runInTerminalTool.setBackendOs(OperatingSystem.Linux); + setConfig(TerminalChatAgentToolsSettingId.TerminalProfileLinux, null); - const result = await testTool.profileFetcher.getCopilotProfile(); - strictEqual(typeof result, 'object'); - strictEqual((result as ITerminalProfile).path, 'pwsh'); // From the mock ITerminalProfileResolverService + const result = await runInTerminalTool.profileFetcher.getCopilotProfile(); + strictEqual(typeof result, 'object'); + strictEqual((result as ITerminalProfile).path, 'pwsh'); + }); }); }); }); From dafe2d8d38fd55c0fdd947d3a59cb39a8505d790 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:33:15 -0700 Subject: [PATCH 1479/4355] Sync tree sitter wasm in web --- remote/web/package-lock.json | 8 ++++---- remote/web/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index fab2167c05e..b3a80a4e8db 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -11,7 +11,7 @@ "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.114", "@xterm/addon-image": "^0.9.0-beta.131", @@ -78,9 +78,9 @@ "license": "MIT" }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.2.0.tgz", + "integrity": "sha512-abvLfKwmriqgdS4WrIzFK7mzdPUVqIIW1UWarp2lA8lpOZ1EDPL1snRBKe7g+5R5ri173mNJEuPLnG/NlpMp4w==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { diff --git a/remote/web/package.json b/remote/web/package.json index 8494a8b5090..46a1fefe151 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -6,7 +6,7 @@ "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.2.0", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.114", "@xterm/addon-image": "^0.9.0-beta.131", From 75e6b12909d439716f22d30a215f5b3e7365d449 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 22 Oct 2025 18:42:02 +0200 Subject: [PATCH 1480/4355] Adds args to editor.action.inlineSuggest.trigger to allow triggering a specific provider (#272724) * Adds args to editor.action.inlineSuggest.trigger to allow triggering a specific provider * Fixes CI --- src/vs/base/common/arrays.ts | 16 + src/vs/base/common/validation.ts | 396 ++++++++++++++++++ src/vs/editor/common/languages.ts | 11 + .../browser/controller/commands.ts | 59 ++- .../browser/model/inlineCompletionsModel.ts | 30 +- .../browser/inlineCompletions.contribution.ts | 34 +- 6 files changed, 527 insertions(+), 19 deletions(-) create mode 100644 src/vs/base/common/validation.ts diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 2f16dbccda4..6fef8c5c881 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -563,6 +563,22 @@ export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { fn(items); } +export function mapFilter(array: ReadonlyArray, fn: (t: T) => U | undefined): U[] { + const result: U[] = []; + for (const item of array) { + const mapped = fn(item); + if (mapped !== undefined) { + result.push(mapped); + } + } + return result; +} + +export function withoutDuplicates(array: ReadonlyArray): T[] { + const s = new Set(array); + return Array.from(s); +} + export function asArray(x: T | T[]): T[]; export function asArray(x: T | readonly T[]): readonly T[]; export function asArray(x: T | T[]): T[] { diff --git a/src/vs/base/common/validation.ts b/src/vs/base/common/validation.ts new file mode 100644 index 00000000000..e94e451a00b --- /dev/null +++ b/src/vs/base/common/validation.ts @@ -0,0 +1,396 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mapFilter } from './arrays.js'; +import { IJSONSchema } from './jsonSchema.js'; + +export interface IValidator { + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError }; + + getJSONSchema(): IJSONSchema; +} + +export abstract class ValidatorBase implements IValidator { + abstract validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError }; + + abstract getJSONSchema(): IJSONSchema; + + validateOrThrow(content: unknown): T { + const result = this.validate(content); + if (result.error) { + throw new Error(result.error.message); + } + return result.content; + } +} + +export type ValidatorType = T extends IValidator ? U : never; + +export interface ValidationError { + message: string; +} + +type TypeOfMap = { + string: string; + number: number; + boolean: boolean; + object: object; + null: null; +}; + +class TypeofValidator extends ValidatorBase { + constructor(private readonly type: TKey) { + super(); + } + + validate(content: unknown): { content: TypeOfMap[TKey]; error: undefined } | { content: undefined; error: ValidationError } { + if (typeof content !== this.type) { + return { content: undefined, error: { message: `Expected ${this.type}, but got ${typeof content}` } }; + } + + return { content: content as TypeOfMap[TKey], error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { type: this.type }; + } +} + +const vStringValidator = new TypeofValidator('string'); +export function vString(): ValidatorBase { return vStringValidator; } + +const vNumberValidator = new TypeofValidator('number'); +export function vNumber(): ValidatorBase { return vNumberValidator; } + +const vBooleanValidator = new TypeofValidator('boolean'); +export function vBoolean(): ValidatorBase { return vBooleanValidator; } + +const vObjAnyValidator = new TypeofValidator('object'); +export function vObjAny(): ValidatorBase { return vObjAnyValidator; } + + +class UncheckedValidator extends ValidatorBase { + validate(content: unknown): { content: T; error: undefined } { + return { content: content as T, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return {}; + } +} + +export function vUnchecked(): ValidatorBase { + return new UncheckedValidator(); +} + +class UndefinedValidator extends ValidatorBase { + validate(content: unknown): { content: undefined; error: undefined } | { content: undefined; error: ValidationError } { + if (content !== undefined) { + return { content: undefined, error: { message: `Expected undefined, but got ${typeof content}` } }; + } + + return { content: undefined, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return {}; + } +} + +export function vUndefined(): ValidatorBase { + return new UndefinedValidator(); +} + +export function vUnknown(): ValidatorBase { + return vUnchecked(); +} + +export type ObjectProperties = Record; + +export class Optional> { + constructor(public readonly validator: T) { } +} + +export function vOptionalProp(validator: IValidator): Optional> { + return new Optional(validator); +} + +type ExtractOptionalKeys = { + [K in keyof T]: T[K] extends Optional> ? K : never; +}[keyof T]; + +type ExtractRequiredKeys = { + [K in keyof T]: T[K] extends Optional> ? never : K; +}[keyof T]; + +export type vObjType | Optional>>> = { + [K in ExtractRequiredKeys]: T[K] extends IValidator ? U : never; +} & { + [K in ExtractOptionalKeys]?: T[K] extends Optional> ? U : never; +}; + +class ObjValidator | Optional>>> extends ValidatorBase> { + constructor(private readonly properties: T) { + super(); + } + + validate(content: unknown): { content: vObjType; error: undefined } | { content: undefined; error: ValidationError } { + if (typeof content !== 'object' || content === null) { + return { content: undefined, error: { message: 'Expected object' } }; + } + + // eslint-disable-next-line local/code-no-dangerous-type-assertions + const result: vObjType = {} as vObjType; + + for (const key in this.properties) { + const prop = this.properties[key]; + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + const fieldValue = (content as any)[key]; + + const isOptional = prop instanceof Optional; + const validator: IValidator = isOptional ? prop.validator : prop; + + if (isOptional && fieldValue === undefined) { + // Optional field not provided, skip validation + continue; + } + + const { content: value, error } = validator.validate(fieldValue); + if (error) { + return { content: undefined, error: { message: `Error in property '${key}': ${error.message}` } }; + } + + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + (result as any)[key] = value; + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + const requiredFields: string[] = []; + const schemaProperties: Record = {}; + + for (const [key, prop] of Object.entries(this.properties)) { + const isOptional = prop instanceof Optional; + const validator: IValidator = isOptional ? prop.validator : prop; + schemaProperties[key] = validator.getJSONSchema(); + if (!isOptional) { + requiredFields.push(key); + } + } + + const schema: IJSONSchema = { + type: 'object', + properties: schemaProperties, + ...(requiredFields.length > 0 ? { required: requiredFields } : {}) + }; + + return schema; + } +} + +export function vObj | Optional>>>(properties: T): ValidatorBase> { + return new ObjValidator(properties); +} + +class ArrayValidator extends ValidatorBase { + constructor(private readonly validator: IValidator) { + super(); + } + + validate(content: unknown): { content: T[]; error: undefined } | { content: undefined; error: ValidationError } { + if (!Array.isArray(content)) { + return { content: undefined, error: { message: 'Expected array' } }; + } + + const result: T[] = []; + for (let i = 0; i < content.length; i++) { + const { content: value, error } = this.validator.validate(content[i]); + if (error) { + return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } }; + } + + result.push(value); + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + type: 'array', + items: this.validator.getJSONSchema(), + }; + } +} + +export function vArray(validator: IValidator): ValidatorBase { + return new ArrayValidator(validator); +} + +type vTupleType[]> = { [K in keyof T]: ValidatorType }; + +class TupleValidator[]> extends ValidatorBase> { + constructor(private readonly validators: T) { + super(); + } + + validate(content: unknown): { content: vTupleType; error: undefined } | { content: undefined; error: ValidationError } { + if (!Array.isArray(content)) { + return { content: undefined, error: { message: 'Expected array' } }; + } + + if (content.length !== this.validators.length) { + return { content: undefined, error: { message: `Expected tuple of length ${this.validators.length}, but got ${content.length}` } }; + } + + const result = [] as vTupleType; + for (let i = 0; i < this.validators.length; i++) { + const validator = this.validators[i]; + const { content: value, error } = validator.validate(content[i]); + if (error) { + return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } }; + } + result.push(value); + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + type: 'array', + items: this.validators.map(validator => validator.getJSONSchema()), + }; + } +} + +export function vTuple[]>(...validators: T): ValidatorBase> { + return new TupleValidator(validators); +} + +class UnionValidator[]> extends ValidatorBase> { + constructor(private readonly validators: T) { + super(); + } + + validate(content: unknown): { content: ValidatorType; error: undefined } | { content: undefined; error: ValidationError } { + let lastError: ValidationError | undefined; + for (const validator of this.validators) { + const { content: value, error } = validator.validate(content); + if (!error) { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + return { content: value as any, error: undefined }; + } + + lastError = error; + } + + return { content: undefined, error: lastError! }; + } + + getJSONSchema(): IJSONSchema { + return { + oneOf: mapFilter(this.validators, validator => { + if (validator instanceof UndefinedValidator) { + return undefined; + } + return validator.getJSONSchema(); + }), + }; + } +} + +export function vUnion[]>(...validators: T): ValidatorBase> { + return new UnionValidator(validators); +} + +class EnumValidator extends ValidatorBase { + constructor(private readonly values: T) { + super(); + } + + validate(content: unknown): { content: T[number]; error: undefined } | { content: undefined; error: ValidationError } { + if (this.values.indexOf(content as string) === -1) { + return { content: undefined, error: { message: `Expected one of: ${this.values.join(', ')}` } }; + } + + return { content: content as T[number], error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + enum: this.values, + }; + } +} + +export function vEnum(...values: T): ValidatorBase { + return new EnumValidator(values); +} + +class LiteralValidator extends ValidatorBase { + constructor(private readonly value: T) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + if (content !== this.value) { + return { content: undefined, error: { message: `Expected: ${this.value}` } }; + } + + return { content: content as T, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + const: this.value, + }; + } +} + +export function vLiteral(value: T): ValidatorBase { + return new LiteralValidator(value); +} + +class LazyValidator extends ValidatorBase { + constructor(private readonly fn: () => IValidator) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + return this.fn().validate(content); + } + + getJSONSchema(): IJSONSchema { + return this.fn().getJSONSchema(); + } +} + +export function vLazy(fn: () => IValidator): ValidatorBase { + return new LazyValidator(fn); +} + +class UseRefSchemaValidator extends ValidatorBase { + constructor( + private readonly _ref: string, + private readonly _validator: IValidator + ) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + return this._validator.validate(content); + } + + getJSONSchema(): IJSONSchema { + return { $ref: this._ref }; + } +} + +export function vWithJsonSchemaRef(ref: string, validator: IValidator): ValidatorBase { + return new UseRefSchemaValidator(ref, validator); +} diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 4fdbf3ba67d..b2729dd3a38 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -967,6 +967,17 @@ export class ProviderId { } return result; } + + toStringWithoutVersion(): string { + let result = ''; + if (this.extensionId) { + result += this.extensionId; + } + if (this.providerId) { + result += `:${this.providerId}`; + } + return result; + } } /** @internal */ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index 58846c3a386..ad50948be71 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -6,6 +6,7 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { asyncTransaction, transaction } from '../../../../../base/common/observable.js'; import { splitLines } from '../../../../../base/common/strings.js'; +import { vBoolean, vObj, vOptionalProp, vString, vUndefined, vUnion, vWithJsonSchemaRef } from '../../../../../base/common/validation.js'; import * as nls from '../../../../../nls.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../platform/accessibility/common/accessibility.js'; import { Action2, MenuId } from '../../../../../platform/actions/common/actions.js'; @@ -13,9 +14,12 @@ import { IClipboardService } from '../../../../../platform/clipboard/common/clip import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../common/editorContextKeys.js'; +import { InlineCompletionsProvider } from '../../../../common/languages.js'; +import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; import { Context as SuggestContext } from '../../../suggest/browser/suggest.js'; import { hideInlineCompletionId, inlineSuggestCommitId, jumpToNextInlineEditId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, toggleShowCollapsedId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; @@ -61,22 +65,71 @@ export class ShowPreviousInlineSuggestionAction extends EditorAction { } } +export const providerIdSchemaUri = 'vscode://schemas/inlineCompletionProviderIdArgs'; + +export function inlineCompletionProviderGetMatcher(provider: InlineCompletionsProvider): string[] { + const result: string[] = []; + if (provider.providerId) { + result.push(provider.providerId.toStringWithoutVersion()); + result.push(provider.providerId.extensionId + ':*'); + } + return result; +} + +const argsValidator = vUnion(vObj({ + showNoResultNotification: vOptionalProp(vBoolean()), + providerId: vOptionalProp(vWithJsonSchemaRef(providerIdSchemaUri, vString())), + explicit: vOptionalProp(vBoolean()), +}), vUndefined()); + export class TriggerInlineSuggestionAction extends EditorAction { constructor() { super({ id: 'editor.action.inlineSuggest.trigger', label: nls.localize2('action.inlineSuggest.trigger', "Trigger Inline Suggestion"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + metadata: { + description: nls.localize('inlineSuggest.trigger.description', "Triggers an inline suggestion in the editor."), + args: [{ + name: 'args', + description: nls.localize('inlineSuggest.trigger.args', "Options for triggering inline suggestions."), + isOptional: true, + schema: argsValidator.getJSONSchema(), + }] + } }); } - public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + public override async run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): Promise { + const notificationService = accessor.get(INotificationService); + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const controller = InlineCompletionsController.get(editor); + + const validatedArgs = argsValidator.validateOrThrow(args); + + const provider = validatedArgs?.providerId ? + languageFeaturesService.inlineCompletionsProvider.all(editor.getModel()!) + .find(p => inlineCompletionProviderGetMatcher(p).some(m => m === validatedArgs!.providerId)) + : undefined; + await asyncTransaction(async tx => { /** @description triggerExplicitly from command */ - await controller?.model.get()?.triggerExplicitly(tx); + await controller?.model.get()?.trigger(tx, { + provider: provider, + explicit: validatedArgs?.explicit ?? true, + }); controller?.playAccessibilitySignal(tx); }); + + if (validatedArgs?.showNoResultNotification) { + if (!controller?.model.get()?.state.get()) { + notificationService.notify({ + severity: Severity.Info, + message: nls.localize('noInlineSuggestionAvailable', "No inline suggestion is available.") + }); + } + } } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 9683a5eaad5..1e937933ea0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -25,7 +25,7 @@ import { Selection } from '../../../../common/core/selection.js'; import { TextReplacement, TextEdit } from '../../../../common/core/edits/textEdit.js'; import { TextLength } from '../../../../common/core/text/textLength.js'; import { ScrollType } from '../../../../common/editorCommon.js'; -import { InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionTriggerKind, PartialAcceptTriggerKind, InlineCompletionsProvider, InlineCompletionCommand, InlineCompletions } from '../../../../common/languages.js'; +import { InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionTriggerKind, PartialAcceptTriggerKind, InlineCompletionsProvider, InlineCompletionCommand } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { EndOfLinePreference, IModelDeltaDecoration, ITextModel } from '../../../../common/model.js'; import { TextModelText } from '../../../../common/model/textModelText.js'; @@ -441,7 +441,7 @@ export class InlineCompletionsModel extends Disposable { // TODO: This is not an ideal implementation of excludesGroupIds, however as this is currently still behind proposed API // and due to the time constraints, we are using a simplified approach - private getAvailableProviders(providers: InlineCompletionsProvider>[]): InlineCompletionsProvider[] { + private getAvailableProviders(providers: InlineCompletionsProvider[]): InlineCompletionsProvider[] { const suppressedProviderGroupIds = this._suppressedInlineCompletionGroupIds.get(); const unsuppressedProviders = providers.filter(provider => !(provider.groupId && suppressedProviderGroupIds.has(provider.groupId))); @@ -450,7 +450,7 @@ export class InlineCompletionsModel extends Disposable { provider.excludesGroupIds?.forEach(p => excludedGroupIds.add(p)); } - const availableProviders: InlineCompletionsProvider>[] = []; + const availableProviders: InlineCompletionsProvider[] = []; for (const provider of unsuppressedProviders) { if (provider.groupId && excludedGroupIds.has(provider.groupId)) { continue; @@ -461,29 +461,29 @@ export class InlineCompletionsModel extends Disposable { return availableProviders; } - public async trigger(tx?: ITransaction, options?: { onlyFetchInlineEdits?: boolean; noDelay?: boolean }): Promise { + public async trigger(tx?: ITransaction, options: { onlyFetchInlineEdits?: boolean; noDelay?: boolean; provider?: InlineCompletionsProvider; explicit?: boolean } = {}): Promise { subtransaction(tx, tx => { - if (options?.onlyFetchInlineEdits) { + if (options.onlyFetchInlineEdits) { this._onlyRequestInlineEditsSignal.trigger(tx); } - if (options?.noDelay) { + if (options.noDelay) { this._noDelaySignal.trigger(tx); } this._isActive.set(true, tx); + + if (options.explicit) { + this._inAcceptFlow.set(true, tx); + this._forceUpdateExplicitlySignal.trigger(tx); + } + if (options.provider) { + this._fetchSpecificProviderSignal.trigger(tx, options.provider); + } }); await this._fetchInlineCompletionsPromise.get(); } public async triggerExplicitly(tx?: ITransaction, onlyFetchInlineEdits: boolean = false): Promise { - subtransaction(tx, tx => { - if (onlyFetchInlineEdits) { - this._onlyRequestInlineEditsSignal.trigger(tx); - } - this._isActive.set(true, tx); - this._inAcceptFlow.set(true, tx); - this._forceUpdateExplicitlySignal.trigger(tx); - }); - await this._fetchInlineCompletionsPromise.get(); + return this.trigger(tx, { onlyFetchInlineEdits, explicit: true }); } public stop(stopReason: 'explicitCancel' | 'automatic' = 'automatic', tx?: ITransaction): void { diff --git a/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts b/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts index d7cd1537bbb..b1de77827c0 100644 --- a/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts +++ b/src/vs/workbench/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts @@ -3,8 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { withoutDuplicates } from '../../../../base/common/arrays.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { autorun, observableFromEvent } from '../../../../base/common/observable.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { inlineCompletionProviderGetMatcher, providerIdSchemaUri } from '../../../../editor/contrib/inlineCompletions/browser/controller/commands.js'; +import { Extensions, IJSONContributionRegistry } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js'; import { wrapInHotClass1 } from '../../../../platform/observable/common/wrapInHotClass.js'; -import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { InlineCompletionLanguageStatusBarContribution } from './inlineCompletionLanguageStatusBarContribution.js'; registerWorkbenchContribution2(InlineCompletionLanguageStatusBarContribution.Id, wrapInHotClass1(InlineCompletionLanguageStatusBarContribution.hot), WorkbenchPhase.Eventually); + +export class InlineCompletionSchemaContribution extends Disposable implements IWorkbenchContribution { + public static Id = 'vs.contrib.InlineCompletionSchemaContribution'; + + constructor( + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + ) { + super(); + + const registry = Registry.as(Extensions.JSONContribution); + const inlineCompletionsProvider = observableFromEvent(this, + this._languageFeaturesService.inlineCompletionsProvider.onDidChange, + () => this._languageFeaturesService.inlineCompletionsProvider.allNoModel() + ); + + this._register(autorun(reader => { + const provider = inlineCompletionsProvider.read(reader); + registry.registerSchema(providerIdSchemaUri, { + enum: withoutDuplicates(provider.flatMap(p => inlineCompletionProviderGetMatcher(p))), + }, reader.store); + })); + } +} + +registerWorkbenchContribution2(InlineCompletionSchemaContribution.Id, InlineCompletionSchemaContribution, WorkbenchPhase.Eventually); From 9b98fab87950a58afd1d3c29275ba9c01930d1a9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 22 Oct 2025 09:43:20 -0700 Subject: [PATCH 1481/4355] tools: add postapproval step for tool calls (#272628) * wip * finish implementation --- .../chat/browser/actions/chatToolActions.ts | 4 +- .../chatToolInputOutputContentPart.ts | 224 +++------------ .../chatToolOutputContentSubPart.ts | 267 ++++++++++++++++++ .../media/chatConfirmationWidget.css | 4 + .../abstractToolConfirmationSubPart.ts | 163 +++++++++++ .../chatExtensionsInstallToolSubPart.ts | 2 +- .../chatInputOutputMarkdownProgressPart.ts | 9 + .../chatTerminalToolConfirmationSubPart.ts | 6 +- .../chatToolConfirmationSubPart.ts | 199 +++++-------- .../chatToolInvocationPart.ts | 9 +- .../chatToolInvocationSubPart.ts | 2 +- .../chatToolPostExecuteConfirmationPart.ts | 266 +++++++++++++++++ .../chatToolProgressPart.ts | 2 +- .../browser/chatResponseAccessibleView.ts | 4 +- .../chat/browser/languageModelToolsService.ts | 165 +++++++---- .../chatProgressTypes/chatToolInvocation.ts | 47 ++- .../contrib/chat/common/chatService.ts | 50 +++- .../chat/common/languageModelToolsService.ts | 9 +- .../browser/languageModelToolsService.test.ts | 43 --- .../common/mockLanguageModelToolsService.ts | 15 +- .../mcpLanguageModelToolContribution.ts | 21 +- 21 files changed, 1055 insertions(+), 456 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolOutputContentSubPart.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index 88002b6b00c..6f47c82e88c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -42,6 +42,8 @@ type SelectedToolClassification = { export const AcceptToolConfirmationActionId = 'workbench.action.chat.acceptTool'; export const SkipToolConfirmationActionId = 'workbench.action.chat.skipTool'; +export const AcceptToolPostConfirmationActionId = 'workbench.action.chat.acceptToolPostExecution'; +export const SkipToolPostConfirmationActionId = 'workbench.action.chat.skipToolPostExecution'; abstract class ToolConfirmationAction extends Action2 { protected abstract getReason(): ConfirmedReason; @@ -56,7 +58,7 @@ abstract class ToolConfirmationAction extends Action2 { for (const item of lastItem.model.response.value) { const state = item.kind === 'toolInvocation' ? item.state.get() : undefined; - if (state?.type === IChatToolInvocation.StateKind.WaitingForConfirmation) { + if (state?.type === IChatToolInvocation.StateKind.WaitingForConfirmation || state?.type === IChatToolInvocation.StateKind.WaitingForPostApproval) { state.confirm(this.getReason()); break; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts index 8ed2dc40970..e60c9be8d16 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolInputOutputContentPart.ts @@ -10,36 +10,21 @@ import { Emitter } from '../../../../../base/common/event.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { autorun, ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; -import { basename, joinPath } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; -import { generateUuid } from '../../../../../base/common/uuid.js'; import { ITextModel } from '../../../../../editor/common/model.js'; -import { localize, localize2 } from '../../../../../nls.js'; -import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { localize } from '../../../../../nls.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; -import { IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; -import { IFileService } from '../../../../../platform/files/common/files.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; -import { ILabelService } from '../../../../../platform/label/common/label.js'; -import { INotificationService } from '../../../../../platform/notification/common/notification.js'; -import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; -import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; -import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../../files/browser/fileConstants.js'; -import { getAttachableImageExtension } from '../../common/chatModel.js'; -import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { LanguageModelPartAudience } from '../../common/languageModels.js'; import { ChatTreeItem, IChatCodeBlockInfo } from '../chat.js'; import { CodeBlockPart, ICodeBlockData, ICodeBlockRenderOptions } from '../codeBlockPart.js'; -import { ChatAttachmentsContentPart } from './chatAttachmentsContentPart.js'; import { IDisposableReference } from './chatCollections.js'; import { ChatQueryTitlePart } from './chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from './chatContentParts.js'; import { EditorPool } from './chatMarkdownContentPart.js'; +import { ChatToolOutputContentSubPart } from './chatToolOutputContentSubPart.js'; export interface IChatCollapsibleIOCodePart { kind: 'code'; @@ -71,9 +56,17 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { private _currentWidth: number = 0; private readonly _editorReferences: IDisposableReference[] = []; private readonly _titlePart: ChatQueryTitlePart; + private _outputSubPart: ChatToolOutputContentSubPart | undefined; public readonly domNode: HTMLElement; - readonly codeblocks: IChatCodeBlockInfo[] = []; + get codeblocks(): IChatCodeBlockInfo[] { + const inputCodeblocks = this._editorReferences.map(ref => { + const cbi = this.input.codeBlockInfo; + return cbi; + }); + const outputCodeblocks = this._outputSubPart?.codeblocks ?? []; + return [...inputCodeblocks, ...outputCodeblocks]; + } public set title(s: string | IMarkdownString) { this._titlePart.title = s; @@ -101,8 +94,6 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { width: number, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IFileService private readonly _fileService: IFileService, ) { super(); this._currentWidth = width; @@ -163,8 +154,16 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { .filter(p => p.kind === 'data') .filter(p => !p.audience || p.audience.includes(LanguageModelPartAudience.User)); if (topLevelResources?.length) { - const group = this.addResourceGroup(topLevelResources, container.root); + const resourceSubPart = this._register(this._instantiationService.createInstance( + ChatToolOutputContentSubPart, + this.context, + this.editorPool, + topLevelResources, + this._currentWidth + )); + const group = resourceSubPart.domNode; group.classList.add('chat-collapsible-top-level-resource-group'); + container.root.appendChild(group); this._register(autorun(r => { group.style.display = expanded.read(r) ? 'none' : ''; })); @@ -189,89 +188,21 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { contents.outputTitle.remove(); } else { contents.outputTitle.textContent = localize('chat.output', "Output"); - for (let i = 0; i < output.parts.length; i++) { - const part = output.parts[i]; - if (part.kind === 'code') { - this.addCodeBlock(part, contents.output); - continue; - } - - const group: IChatCollapsibleIODataPart[] = []; - for (let k = i; k < output.parts.length; k++) { - const part = output.parts[k]; - if (part.kind !== 'data') { - break; - } - group.push(part); - } - - this.addResourceGroup(group, contents.output); - i += group.length - 1; // Skip the parts we just added - } + const outputSubPart = this._register(this._instantiationService.createInstance( + ChatToolOutputContentSubPart, + this.context, + this.editorPool, + output.parts, + this._currentWidth + )); + this._outputSubPart = outputSubPart; + this._register(outputSubPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); + contents.output.appendChild(outputSubPart.domNode); } return contents.root; } - private addResourceGroup(parts: IChatCollapsibleIODataPart[], container: HTMLElement) { - const el = dom.h('.chat-collapsible-io-resource-group', [ - dom.h('.chat-collapsible-io-resource-items@items'), - dom.h('.chat-collapsible-io-resource-actions@actions'), - ]); - - this.fillInResourceGroup(parts, el.items, el.actions).then(() => this._onDidChangeHeight.fire()); - - container.appendChild(el.root); - return el.root; - } - - private async fillInResourceGroup(parts: IChatCollapsibleIODataPart[], itemsContainer: HTMLElement, actionsContainer: HTMLElement) { - const entries = await Promise.all(parts.map(async (part): Promise => { - if (part.mimeType && getAttachableImageExtension(part.mimeType)) { - const value = part.value ?? await this._fileService.readFile(part.uri).then(f => f.value.buffer, () => undefined); - return { kind: 'image', id: generateUuid(), name: basename(part.uri), value, mimeType: part.mimeType, isURL: false, references: [{ kind: 'reference', reference: part.uri }] }; - } else { - return { kind: 'file', id: generateUuid(), name: basename(part.uri), fullName: part.uri.path, value: part.uri }; - } - })); - - const attachments = this._register(this._instantiationService.createInstance( - ChatAttachmentsContentPart, - { - variables: entries, - limit: 5, - contentReferences: undefined, - domNode: undefined - } - )); - - attachments.contextMenuHandler = (attachment, event) => { - const index = entries.indexOf(attachment); - const part = parts[index]; - if (part) { - event.preventDefault(); - event.stopPropagation(); - - this._contextMenuService.showContextMenu({ - menuId: MenuId.ChatToolOutputResourceContext, - menuActionOptions: { shouldForwardArgs: true }, - getAnchor: () => ({ x: event.pageX, y: event.pageY }), - getActionsContext: () => ({ parts: [part] } satisfies IChatToolOutputResourceToolbarContext), - }); - } - }; - - itemsContainer.appendChild(attachments.domNode!); - - const toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, actionsContainer, MenuId.ChatToolOutputResourceToolbar, { - menuOptions: { - shouldForwardArgs: true, - }, - })); - toolbar.context = { parts } satisfies IChatToolOutputResourceToolbarContext; - } - - private addCodeBlock(part: IChatCollapsibleIOCodePart, container: HTMLElement) { const data: ICodeBlockData = { languageId: part.languageId, @@ -298,97 +229,6 @@ export class ChatCollapsibleInputOutputContentPart extends Disposable { layout(width: number): void { this._currentWidth = width; this._editorReferences.forEach(r => r.object.layout(width)); + this._outputSubPart?.layout(width); } } - -interface IChatToolOutputResourceToolbarContext { - parts: IChatCollapsibleIODataPart[]; -} - -class SaveResourcesAction extends Action2 { - public static readonly ID = 'chat.toolOutput.save'; - constructor() { - super({ - id: SaveResourcesAction.ID, - title: localize2('chat.saveResources', "Save As..."), - icon: Codicon.cloudDownload, - menu: [{ - id: MenuId.ChatToolOutputResourceToolbar, - group: 'navigation', - order: 1 - }, { - id: MenuId.ChatToolOutputResourceContext, - }] - }); - } - - async run(accessor: ServicesAccessor, context: IChatToolOutputResourceToolbarContext) { - const fileDialog = accessor.get(IFileDialogService); - const fileService = accessor.get(IFileService); - const notificationService = accessor.get(INotificationService); - const progressService = accessor.get(IProgressService); - const workspaceContextService = accessor.get(IWorkspaceContextService); - const commandService = accessor.get(ICommandService); - const labelService = accessor.get(ILabelService); - const defaultFilepath = await fileDialog.defaultFilePath(); - - const savePart = async (part: IChatCollapsibleIODataPart, isFolder: boolean, uri: URI) => { - const target = isFolder ? joinPath(uri, basename(part.uri)) : uri; - try { - if (part.kind === 'data') { - await fileService.copy(part.uri, target, true); - } else { - // MCP doesn't support streaming data, so no sense trying - const contents = await fileService.readFile(part.uri); - await fileService.writeFile(target, contents.value); - } - } catch (e) { - notificationService.error(localize('chat.saveResources.error', "Failed to save {0}: {1}", basename(part.uri), e)); - } - }; - - const withProgress = async (thenReveal: URI, todo: (() => Promise)[]) => { - await progressService.withProgress({ - location: ProgressLocation.Notification, - delay: 5_000, - title: localize('chat.saveResources.progress', "Saving resources..."), - }, async report => { - for (const task of todo) { - await task(); - report.report({ increment: 1, total: todo.length }); - } - }); - - if (workspaceContextService.isInsideWorkspace(thenReveal)) { - commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, thenReveal); - } else { - notificationService.info(localize('chat.saveResources.reveal', "Saved resources to {0}", labelService.getUriLabel(thenReveal))); - } - }; - - if (context.parts.length === 1) { - const part = context.parts[0]; - const uri = await fileDialog.pickFileToSave(joinPath(defaultFilepath, basename(part.uri))); - if (!uri) { - return; - } - await withProgress(uri, [() => savePart(part, false, uri)]); - } else { - const uris = await fileDialog.showOpenDialog({ - title: localize('chat.saveResources.title', "Pick folder to save resources"), - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: workspaceContextService.getWorkspace().folders[0]?.uri, - }); - - if (!uris?.length) { - return; - } - - await withProgress(uris[0], context.parts.map(part => () => savePart(part, true, uris[0]))); - } - } -} - -registerAction2(SaveResourcesAction); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolOutputContentSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolOutputContentSubPart.ts new file mode 100644 index 00000000000..6fe6d0847dc --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatToolOutputContentSubPart.ts @@ -0,0 +1,267 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../../base/browser/dom.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { basename, joinPath } from '../../../../../base/common/resources.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; +import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { INotificationService } from '../../../../../platform/notification/common/notification.js'; +import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; +import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; +import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../../files/browser/fileConstants.js'; +import { getAttachableImageExtension } from '../../common/chatModel.js'; +import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; +import { IChatCodeBlockInfo } from '../chat.js'; +import { CodeBlockPart, ICodeBlockData } from '../codeBlockPart.js'; +import { ChatAttachmentsContentPart } from './chatAttachmentsContentPart.js'; +import { IDisposableReference } from './chatCollections.js'; +import { IChatContentPartRenderContext } from './chatContentParts.js'; +import { EditorPool } from './chatMarkdownContentPart.js'; +import { ChatCollapsibleIOPart, IChatCollapsibleIOCodePart, IChatCollapsibleIODataPart } from './chatToolInputOutputContentPart.js'; + +/** + * A reusable component for rendering tool output consisting of code blocks and/or resources. + * This is used by both ChatCollapsibleInputOutputContentPart and ChatToolPostExecuteConfirmationPart. + */ +export class ChatToolOutputContentSubPart extends Disposable { + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + private _currentWidth: number = 0; + private readonly _editorReferences: IDisposableReference[] = []; + public readonly domNode: HTMLElement; + + readonly codeblocks: IChatCodeBlockInfo[] = []; + + constructor( + private readonly context: IChatContentPartRenderContext, + private readonly editorPool: EditorPool, + private readonly parts: ChatCollapsibleIOPart[], + width: number, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IFileService private readonly _fileService: IFileService, + ) { + super(); + this._currentWidth = width; + this.domNode = this.createOutputContents(); + } + + private createOutputContents(): HTMLElement { + const container = dom.$('div'); + + for (let i = 0; i < this.parts.length; i++) { + const part = this.parts[i]; + if (part.kind === 'code') { + this.addCodeBlock(part, container); + continue; + } + + const group: IChatCollapsibleIODataPart[] = []; + for (let k = i; k < this.parts.length; k++) { + const part = this.parts[k]; + if (part.kind !== 'data') { + break; + } + group.push(part); + } + + this.addResourceGroup(group, container); + i += group.length - 1; // Skip the parts we just added + } + + return container; + } + + private addResourceGroup(parts: IChatCollapsibleIODataPart[], container: HTMLElement) { + const el = dom.h('.chat-collapsible-io-resource-group', [ + dom.h('.chat-collapsible-io-resource-items@items'), + dom.h('.chat-collapsible-io-resource-actions@actions'), + ]); + + this.fillInResourceGroup(parts, el.items, el.actions).then(() => this._onDidChangeHeight.fire()); + + container.appendChild(el.root); + return el.root; + } + + private async fillInResourceGroup(parts: IChatCollapsibleIODataPart[], itemsContainer: HTMLElement, actionsContainer: HTMLElement) { + const entries = await Promise.all(parts.map(async (part): Promise => { + if (part.mimeType && getAttachableImageExtension(part.mimeType)) { + const value = part.value ?? await this._fileService.readFile(part.uri).then(f => f.value.buffer, () => undefined); + return { kind: 'image', id: generateUuid(), name: basename(part.uri), value, mimeType: part.mimeType, isURL: false, references: [{ kind: 'reference', reference: part.uri }] }; + } else { + return { kind: 'file', id: generateUuid(), name: basename(part.uri), fullName: part.uri.path, value: part.uri }; + } + })); + + const attachments = this._register(this._instantiationService.createInstance( + ChatAttachmentsContentPart, + { + variables: entries, + limit: 5, + contentReferences: undefined, + domNode: undefined + } + )); + + attachments.contextMenuHandler = (attachment, event) => { + const index = entries.indexOf(attachment); + const part = parts[index]; + if (part) { + event.preventDefault(); + event.stopPropagation(); + + this._contextMenuService.showContextMenu({ + menuId: MenuId.ChatToolOutputResourceContext, + menuActionOptions: { shouldForwardArgs: true }, + getAnchor: () => ({ x: event.pageX, y: event.pageY }), + getActionsContext: () => ({ parts: [part] } satisfies IChatToolOutputResourceToolbarContext), + }); + } + }; + + itemsContainer.appendChild(attachments.domNode!); + + const toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, actionsContainer, MenuId.ChatToolOutputResourceToolbar, { + menuOptions: { + shouldForwardArgs: true, + }, + })); + toolbar.context = { parts } satisfies IChatToolOutputResourceToolbarContext; + } + + private addCodeBlock(part: IChatCollapsibleIOCodePart, container: HTMLElement) { + const data: ICodeBlockData = { + languageId: part.languageId, + textModel: Promise.resolve(part.textModel), + codeBlockIndex: part.codeBlockInfo.codeBlockIndex, + codeBlockPartIndex: 0, + element: this.context.element, + parentContextKeyService: this.contextKeyService, + renderOptions: part.options, + chatSessionId: this.context.element.sessionId, + }; + const editorReference = this._register(this.editorPool.get()); + editorReference.object.render(data, this._currentWidth || 300); + this._register(editorReference.object.onDidChangeContentHeight(() => this._onDidChangeHeight.fire())); + container.appendChild(editorReference.object.element); + this._editorReferences.push(editorReference); + this.codeblocks.push(part.codeBlockInfo); + } + + layout(width: number): void { + this._currentWidth = width; + this._editorReferences.forEach(r => r.object.layout(width)); + } +} + +interface IChatToolOutputResourceToolbarContext { + parts: IChatCollapsibleIODataPart[]; +} + + + +class SaveResourcesAction extends Action2 { + public static readonly ID = 'chat.toolOutput.save'; + constructor() { + super({ + id: SaveResourcesAction.ID, + title: localize2('chat.saveResources', "Save As..."), + icon: Codicon.cloudDownload, + menu: [{ + id: MenuId.ChatToolOutputResourceToolbar, + group: 'navigation', + order: 1 + }, { + id: MenuId.ChatToolOutputResourceContext, + }] + }); + } + + async run(accessor: ServicesAccessor, context: IChatToolOutputResourceToolbarContext) { + const fileDialog = accessor.get(IFileDialogService); + const fileService = accessor.get(IFileService); + const notificationService = accessor.get(INotificationService); + const progressService = accessor.get(IProgressService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const commandService = accessor.get(ICommandService); + const labelService = accessor.get(ILabelService); + const defaultFilepath = await fileDialog.defaultFilePath(); + + const savePart = async (part: IChatCollapsibleIODataPart, isFolder: boolean, uri: URI) => { + const target = isFolder ? joinPath(uri, basename(part.uri)) : uri; + try { + if (part.kind === 'data') { + await fileService.copy(part.uri, target, true); + } else { + // MCP doesn't support streaming data, so no sense trying + const contents = await fileService.readFile(part.uri); + await fileService.writeFile(target, contents.value); + } + } catch (e) { + notificationService.error(localize('chat.saveResources.error', "Failed to save {0}: {1}", basename(part.uri), e)); + } + }; + + const withProgress = async (thenReveal: URI, todo: (() => Promise)[]) => { + await progressService.withProgress({ + location: ProgressLocation.Notification, + delay: 5_000, + title: localize('chat.saveResources.progress', "Saving resources..."), + }, async report => { + for (const task of todo) { + await task(); + report.report({ increment: 1, total: todo.length }); + } + }); + + if (workspaceContextService.isInsideWorkspace(thenReveal)) { + commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, thenReveal); + } else { + notificationService.info(localize('chat.saveResources.reveal', "Saved resources to {0}", labelService.getUriLabel(thenReveal))); + } + }; + + if (context.parts.length === 1) { + const part = context.parts[0]; + const uri = await fileDialog.pickFileToSave(joinPath(defaultFilepath, basename(part.uri))); + if (!uri) { + return; + } + await withProgress(uri, [() => savePart(part, false, uri)]); + } else { + const uris = await fileDialog.showOpenDialog({ + title: localize('chat.saveResources.title', "Pick folder to save resources"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: workspaceContextService.getWorkspace().folders[0]?.uri, + }); + + if (!uris?.length) { + return; + } + + await withProgress(uris[0], context.parts.map(part => () => savePart(part, true, uris[0]))); + } + } +} + +registerAction2(SaveResourcesAction); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index 22c3a1f4d26..5b26440cc8c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -246,6 +246,10 @@ border-bottom: 1px solid var(--vscode-chat-requestBorder); padding: 6px 9px 0 9px; + .tool-postconfirm-display { + padding-bottom: 6px; + } + p { margin-top: 0; margin-bottom: 9px; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts new file mode 100644 index 00000000000..a6dda0631bd --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Separator } from '../../../../../../base/common/actions.js'; +import { assertNever } from '../../../../../../base/common/assert.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { autorunSelfDisposable, IReader } from '../../../../../../base/common/observable.js'; +import { localize } from '../../../../../../nls.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { ChatContextKeys } from '../../../common/chatContextKeys.js'; +import { ConfirmedReason, IChatToolInvocation, ToolConfirmKind } from '../../../common/chatService.js'; +import { ILanguageModelToolsService } from '../../../common/languageModelToolsService.js'; +import { IChatWidgetService } from '../../chat.js'; +import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatConfirmationWidget.js'; +import { IChatContentPartRenderContext } from '../chatContentParts.js'; +import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; + +export const enum ConfirmationOutcome { + Allow, + Skip, + AllowWorkspace, + AllowGlobally, + AllowSession, +} + +export interface IToolConfirmationConfig { + allowActionId: string; + skipActionId: string; + allowLabel: string; + skipLabel: string; + partType: string; + subtitle?: string; +} + +type AbstractToolPrimaryAction = IChatConfirmationButton<(() => void) | ConfirmationOutcome> | Separator; + +/** + * Base class for a tool confirmation. + * + * note that implementors MUST call render() after they construct. + */ +export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvocationSubPart { + public domNode!: HTMLElement; + + constructor( + protected override readonly toolInvocation: IChatToolInvocation, + protected readonly context: IChatContentPartRenderContext, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IKeybindingService protected readonly keybindingService: IKeybindingService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + @IChatWidgetService protected readonly chatWidgetService: IChatWidgetService, + @ILanguageModelToolsService protected readonly languageModelToolsService: ILanguageModelToolsService, + ) { + super(toolInvocation); + + if (toolInvocation.kind !== 'toolInvocation') { + throw new Error('Confirmation only works with live tool invocations'); + } + } + protected render(config: IToolConfirmationConfig) { + const { keybindingService, languageModelToolsService, toolInvocation } = this; + const allowKeybinding = keybindingService.lookupKeybinding(config.allowActionId)?.getLabel(); + const allowTooltip = allowKeybinding ? `${config.allowLabel} (${allowKeybinding})` : config.allowLabel; + const skipKeybinding = keybindingService.lookupKeybinding(config.skipActionId)?.getLabel(); + const skipTooltip = skipKeybinding ? `${config.skipLabel} (${skipKeybinding})` : config.skipLabel; + + + const buttons: IChatConfirmationButton void)>[] = [ + { + label: config.allowLabel, + tooltip: allowTooltip, + data: ConfirmationOutcome.Allow, + moreActions: this.additionalPrimaryActions(), + }, + { + label: localize('skip', "Skip"), + tooltip: skipTooltip, + data: ConfirmationOutcome.Skip, + isSecondary: true, + } + ]; + + const contentElement = this.createContentElement(); + const tool = languageModelToolsService.getTool(toolInvocation.toolId); + const confirmWidget = this._register(this.instantiationService.createInstance( + ChatCustomConfirmationWidget void)>, + this.context.container, + { + title: this.getTitle(), + icon: tool?.icon && 'id' in tool.icon ? tool.icon : Codicon.tools, + subtitle: config.subtitle, + buttons, + message: contentElement, + toolbarData: { + arg: toolInvocation, + partType: config.partType, + partSource: toolInvocation.source.type + } + } + )); + + const hasToolConfirmation = ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService); + hasToolConfirmation.set(true); + + this._register(confirmWidget.onDidClick(button => { + const confirm = (reason: ConfirmedReason) => this.confirmWith(toolInvocation, reason); + if (typeof button.data === 'function') { + button.data(); + } else { + switch (button.data) { + case ConfirmationOutcome.AllowGlobally: + confirm({ type: ToolConfirmKind.LmServicePerTool, scope: 'profile' }); + break; + case ConfirmationOutcome.AllowWorkspace: + confirm({ type: ToolConfirmKind.LmServicePerTool, scope: 'workspace' }); + break; + case ConfirmationOutcome.AllowSession: + confirm({ type: ToolConfirmKind.LmServicePerTool, scope: 'session' }); + break; + case ConfirmationOutcome.Allow: + confirm({ type: ToolConfirmKind.UserAction }); + break; + case ConfirmationOutcome.Skip: + confirm({ type: ToolConfirmKind.Skipped }); + break; + default: + assertNever(button.data); + } + } + + this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.focusInput(); + })); + + this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); + this._register(toDisposable(() => hasToolConfirmation.reset())); + this._register(autorunSelfDisposable(reader => { + if (this.shouldDismiss(toolInvocation, reader)) { + reader.dispose(); + hasToolConfirmation.reset(); + this._onNeedsRerender.fire(); + } + })); + + this.domNode = confirmWidget.domNode; + } + + protected confirmWith(toolInvocation: IChatToolInvocation, reason: ConfirmedReason): void { + IChatToolInvocation.confirmWith(toolInvocation, reason); + } + + protected additionalPrimaryActions(): AbstractToolPrimaryAction[] { + return []; + } + + protected abstract createContentElement(): HTMLElement | string; + protected abstract getTitle(): string; + protected abstract shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts index 5b00de85d3b..621a7863918 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts @@ -89,7 +89,7 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo chatWidgetService.getWidgetBySessionId(context.element.sessionId)?.focusInput(); })); this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { + if (IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader)) { reader.dispose(); ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); this._onNeedsRerender.fire(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index 9d187b52e5d..c84eb0fab14 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -136,6 +136,15 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS this._register(collapsibleListPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(toDisposable(() => ChatInputOutputMarkdownProgressPart._expandedByDefault.set(toolInvocation, collapsibleListPart.expanded))); + // Make sure to rerender back into a confirmation when reviewing output + this._register(autorun(reader => { + if (toolInvocation.kind === 'toolInvocation') { + if (toolInvocation.state.read(reader).type === IChatToolInvocation.StateKind.WaitingForPostApproval) { + this._onNeedsRerender.fire(); + } + } + })); + const progressObservable = toolInvocation.kind === 'toolInvocation' ? toolInvocation.state.map((s, r) => s.type === IChatToolInvocation.StateKind.Executing ? s.progress.read(r) : undefined) : undefined; const progressBar = new Lazy(() => this._register(new ProgressBar(collapsibleListPart.domNode))); if (progressObservable) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index d45127c7579..968d74e2564 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -97,7 +97,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS context.container.classList.add('from-sub-agent'); } - if (!toolInvocation.confirmationMessages) { + if (!toolInvocation.confirmationMessages?.title) { throw new Error('Confirmation messages are missing'); } @@ -185,7 +185,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS ]); append(elements.editor, editor.object.element); this._register(hoverService.setupDelayedHover(elements.editor, { - content: message, + content: message || '', style: HoverStyle.Pointer, position: { hoverPosition: HoverPosition.LEFT }, })); @@ -307,7 +307,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { + if (IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader)) { reader.dispose(); ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); this._onNeedsRerender.fire(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index abf101e7968..fe1615e5f60 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../../base/browser/dom.js'; +import { Separator } from '../../../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../../../base/common/async.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { IReader } from '../../../../../../base/common/observable.js'; import { count } from '../../../../../../base/common/strings.js'; import { isEmptyObject } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { ElementSizeObserver } from '../../../../../../editor/browser/config/elementSizeObserver.js'; -import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { localize } from '../../../../../../nls.js'; @@ -20,27 +20,23 @@ import { ICommandService } from '../../../../../../platform/commands/common/comm import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; -import { ChatContextKeys } from '../../../common/chatContextKeys.js'; -import { ConfirmedReason, IChatToolInvocation, ToolConfirmKind } from '../../../common/chatService.js'; +import { IChatToolInvocation, ToolConfirmKind } from '../../../common/chatService.js'; import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js'; import { createToolInputUri, createToolSchemaUri, ILanguageModelToolsService } from '../../../common/languageModelToolsService.js'; import { AcceptToolConfirmationActionId, SkipToolConfirmationActionId } from '../../actions/chatToolActions.js'; import { IChatCodeBlockInfo, IChatWidgetService } from '../../chat.js'; import { renderFileWidgets } from '../../chatInlineAnchorWidget.js'; import { ICodeBlockRenderOptions } from '../../codeBlockPart.js'; -import { ChatConfirmationWidget, ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { IChatMarkdownAnchorService } from '../chatMarkdownAnchorService.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; -import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; -import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; +import { AbstractToolConfirmationSubPart, ConfirmationOutcome } from './abstractToolConfirmationSubPart.js'; const SHOW_MORE_MESSAGE_HEIGHT_TRIGGER = 45; -export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { - public readonly domNode: HTMLElement; - +export class ToolConfirmationSubPart extends AbstractToolConfirmationSubPart { private markdownParts: ChatMarkdownContentPart[] = []; public get codeblocks(): IChatCodeBlockInfo[] { return this.markdownParts.flatMap(part => part.codeblocks); @@ -48,86 +44,75 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { constructor( toolInvocation: IChatToolInvocation, - private readonly context: IChatContentPartRenderContext, + context: IChatContentPartRenderContext, private readonly renderer: IMarkdownRenderer, private readonly editorPool: EditorPool, private readonly currentWidthDelegate: () => number, private readonly codeBlockModelCollection: CodeBlockModelCollection, private readonly codeBlockStartIndex: number, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IModelService private readonly modelService: IModelService, @ILanguageService private readonly languageService: ILanguageService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatWidgetService chatWidgetService: IChatWidgetService, @ICommandService private readonly commandService: ICommandService, @IMarkerService private readonly markerService: IMarkerService, - @ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService, + @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService, @IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService, ) { - super(toolInvocation); + if (!toolInvocation.confirmationMessages?.title) { + throw new Error('Confirmation messages are missing'); + } + + super(toolInvocation, context, instantiationService, keybindingService, contextKeyService, chatWidgetService, languageModelToolsService); + + this.render({ + allowActionId: AcceptToolConfirmationActionId, + skipActionId: SkipToolConfirmationActionId, + allowLabel: toolInvocation.confirmationMessages.confirmResults ? localize('allowReview', "Allow and Review") : localize('allow', "Allow"), + skipLabel: localize('skip.detail', 'Proceed without running this tool'), + partType: 'chatToolConfirmation', + subtitle: typeof toolInvocation.originMessage === 'string' ? toolInvocation.originMessage : toolInvocation.originMessage?.value, + }); // Tag for sub-agent styling if (toolInvocation.fromSubAgent) { context.container.classList.add('from-sub-agent'); } + } - if (!toolInvocation.confirmationMessages) { - throw new Error('Confirmation messages are missing'); + protected override additionalPrimaryActions() { + const actions = super.additionalPrimaryActions(); + if (this.toolInvocation.confirmationMessages?.allowAutoConfirm !== false) { + actions.push( + { label: localize('allowSession', 'Allow in this Session'), data: ConfirmationOutcome.AllowSession, tooltip: localize('allowSesssionTooltip', 'Allow this tool to run in this session without confirmation.') }, + { label: localize('allowWorkspace', 'Allow in this Workspace'), data: ConfirmationOutcome.AllowWorkspace, tooltip: localize('allowWorkspaceTooltip', 'Allow this tool to run in this workspace without confirmation.') }, + { label: localize('allowGlobally', 'Always Allow'), data: ConfirmationOutcome.AllowGlobally, tooltip: localize('allowGloballTooltip', 'Always allow this tool to run without confirmation.') }, + ); } - const { title, message, allowAutoConfirm, disclaimer } = toolInvocation.confirmationMessages; - const allowLabel = localize('allow', "Allow"); - const allowKeybinding = keybindingService.lookupKeybinding(AcceptToolConfirmationActionId)?.getLabel(); - const allowTooltip = allowKeybinding ? `${allowLabel} (${allowKeybinding})` : allowLabel; - const skipLabel = localize('skip.detail', 'Proceed without running this tool'); - const skipKeybinding = keybindingService.lookupKeybinding(SkipToolConfirmationActionId)?.getLabel(); - const skipTooltip = skipKeybinding ? `${skipLabel} (${skipKeybinding})` : skipLabel; - - const enum ConfirmationOutcome { - Allow, - Skip, - AllowWorkspace, - AllowGlobally, - AllowSession, + if (this.toolInvocation.confirmationMessages?.confirmResults) { + actions.unshift( + { + label: localize('allowSkip', 'Allow and Skip Reviewing Result'), + data: () => { + this.toolInvocation.confirmationMessages!.confirmResults = undefined; + this.confirmWith(this.toolInvocation, { type: ToolConfirmKind.UserAction }); + } + }, + new Separator(), + ); } - const buttons: IChatConfirmationButton[] = [ - { - label: allowLabel, - tooltip: allowTooltip, - data: ConfirmationOutcome.Allow, - moreActions: !allowAutoConfirm ? undefined : [ - { label: localize('allowSession', 'Allow in this Session'), data: ConfirmationOutcome.AllowSession, tooltip: localize('allowSesssionTooltip', 'Allow this tool to run in this session without confirmation.') }, - { label: localize('allowWorkspace', 'Allow in this Workspace'), data: ConfirmationOutcome.AllowWorkspace, tooltip: localize('allowWorkspaceTooltip', 'Allow this tool to run in this workspace without confirmation.') }, - { label: localize('allowGlobally', 'Always Allow'), data: ConfirmationOutcome.AllowGlobally, tooltip: localize('allowGloballTooltip', 'Always allow this tool to run without confirmation.') }, - ], - }, - { - label: localize('skip', "Skip"), - tooltip: skipTooltip, - data: ConfirmationOutcome.Skip, - isSecondary: true, - }]; + return actions; + } + + protected createContentElement(): HTMLElement | string { + const { message, disclaimer } = this.toolInvocation.confirmationMessages!; + const toolInvocation = this.toolInvocation as IChatToolInvocation; - let confirmWidget: ChatCustomConfirmationWidget; if (typeof message === 'string') { - const tool = languageModelToolsService.getTool(toolInvocation.toolId); - confirmWidget = this._register(this.instantiationService.createInstance( - ChatConfirmationWidget, - this.context.container, - { - title, - icon: tool?.icon && 'id' in tool.icon ? tool.icon : Codicon.tools, - subtitle: toolInvocation.originMessage, - buttons, - message, - toolbarData: { - arg: toolInvocation, - partType: 'chatToolConfirmation', - partSource: toolInvocation.source.type - } - } - )); + return message; } else { const codeBlockRenderOptions: ICodeBlockRenderOptions = { hideToolbar: true, @@ -135,7 +120,7 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { verticalPadding: 5, editorOptions: { tabFocusMode: true, - ariaLabel: typeof title === 'string' ? title : title.value + ariaLabel: this.getTitle(), }, }; @@ -152,9 +137,9 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { if (toolInvocation.toolSpecificData?.kind === 'input' && toolInvocation.toolSpecificData.rawInput && !isEmptyObject(toolInvocation.toolSpecificData.rawInput)) { - const title = document.createElement('h3'); - title.textContent = localize('chat.input', "Input"); - elements.editor.appendChild(title); + const titleEl = document.createElement('h3'); + titleEl.textContent = localize('chat.input', "Input"); + elements.editor.appendChild(titleEl); const inputData = toolInvocation.toolSpecificData; @@ -166,7 +151,7 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { editorOptions: { wordWrap: 'off', readOnly: false, - ariaLabel: typeof toolInvocation.confirmationMessages.title === 'string' ? toolInvocation.confirmationMessages.title : toolInvocation.confirmationMessages.title.value + ariaLabel: this.getTitle(), } }; @@ -263,7 +248,7 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { } } - const mdPart = this._makeMarkdownPart(elements.message, message, codeBlockRenderOptions); + const mdPart = this._makeMarkdownPart(elements.message, message!, codeBlockRenderOptions); const messageSeeMoreObserver = this._register(new ElementSizeObserver(mdPart.domNode, undefined)); const updateSeeMoreDisplayed = () => { @@ -290,69 +275,17 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart { elements.disclaimer.remove(); } - const tool = languageModelToolsService.getTool(toolInvocation.toolId); - confirmWidget = this._register(this.instantiationService.createInstance( - ChatCustomConfirmationWidget, - this.context.container, - { - title, - icon: tool?.icon && 'id' in tool.icon ? tool.icon : Codicon.tools, - subtitle: toolInvocation.originMessage, - buttons, - message: elements.root, - toolbarData: { - arg: toolInvocation, - partType: 'chatToolConfirmation', - partSource: toolInvocation.source?.type - } - }, - )); + return elements.root; } + } - const hasToolConfirmation = ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService); - hasToolConfirmation.set(true); - - this._register(confirmWidget.onDidClick(button => { - const confirmAndSave = (kind: 'profile' | 'workspace' | 'session') => { - this.languageModelToolsService.setToolAutoConfirmation(toolInvocation.toolId, kind); - confirm({ type: ToolConfirmKind.LmServicePerTool, scope: kind }); - }; - - const confirm = (reason: ConfirmedReason) => { - IChatToolInvocation.confirmWith(toolInvocation, reason); - }; - - - switch (button.data as ConfirmationOutcome) { - case ConfirmationOutcome.AllowGlobally: - confirmAndSave('profile'); - break; - case ConfirmationOutcome.AllowWorkspace: - confirmAndSave('workspace'); - break; - case ConfirmationOutcome.AllowSession: - confirmAndSave('session'); - break; - case ConfirmationOutcome.Allow: - confirm({ type: ToolConfirmKind.UserAction }); - break; - case ConfirmationOutcome.Skip: - confirm({ type: ToolConfirmKind.Skipped }); - break; - } + protected getTitle(): string { + const { title } = this.toolInvocation.confirmationMessages!; + return typeof title === 'string' ? title : title!.value; + } - this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.focusInput(); - })); - this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); - this._register(toDisposable(() => hasToolConfirmation.reset())); - this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.isConfirmed(toolInvocation, reader)) { - reader.dispose(); - hasToolConfirmation.reset(); - this._onNeedsRerender.fire(); - } - })); - this.domNode = confirmWidget.domNode; + protected shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean { + return !!IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader); } private _makeMarkdownPart(container: HTMLElement, message: string | IMarkdownString, codeBlockRenderOptions: ICodeBlockRenderOptions) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 7ba55ee90fc..1afa8169b0a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -26,6 +26,7 @@ import { ChatTerminalToolProgressPart } from './chatTerminalToolProgressPart.js' import { ToolConfirmationSubPart } from './chatToolConfirmationSubPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import { ChatToolOutputSubPart } from './chatToolOutputPart.js'; +import { ChatToolPostExecuteConfirmationPart } from './chatToolPostExecuteConfirmationPart.js'; import { ChatToolProgressSubPart } from './chatToolProgressPart.js'; export class ChatToolInvocationPart extends Disposable implements IChatContentPart { @@ -98,7 +99,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa } private get autoApproveMessageContent() { - const reason = IChatToolInvocation.isConfirmed(this.toolInvocation); + const reason = IChatToolInvocation.executionConfirmedOrDenied(this.toolInvocation); if (!reason || typeof reason === 'boolean') { return; } @@ -146,13 +147,17 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa if (this.toolInvocation.toolSpecificData?.kind === 'extensions') { return this.instantiationService.createInstance(ExtensionsInstallConfirmationWidgetSubPart, this.toolInvocation, this.context); } - if (this.toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { + const state = this.toolInvocation.state.get(); + if (state.type === IChatToolInvocation.StateKind.WaitingForConfirmation) { if (this.toolInvocation.toolSpecificData?.kind === 'terminal') { return this.instantiationService.createInstance(ChatTerminalToolConfirmationSubPart, this.toolInvocation, this.toolInvocation.toolSpecificData, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockModelCollection, this.codeBlockStartIndex); } else { return this.instantiationService.createInstance(ToolConfirmationSubPart, this.toolInvocation, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockModelCollection, this.codeBlockStartIndex); } } + if (state.type === IChatToolInvocation.StateKind.WaitingForPostApproval) { + return this.instantiationService.createInstance(ChatToolPostExecuteConfirmationPart, this.toolInvocation, this.context, this.editorPool, this.currentWidthDelegate); + } } if (this.toolInvocation.toolSpecificData?.kind === 'terminal') { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts index 3886e5475b6..2715d261342 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts @@ -42,7 +42,7 @@ export abstract class BaseChatToolInvocationSubPart extends Disposable { protected getIcon() { const toolInvocation = this.toolInvocation; - const confirmState = IChatToolInvocation.isConfirmed(toolInvocation); + const confirmState = IChatToolInvocation.executionConfirmedOrDenied(toolInvocation); const isSkipped = confirmState?.type === ToolConfirmKind.Skipped; if (isSkipped) { return Codicon.circleSlash; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts new file mode 100644 index 00000000000..1dfa909a115 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../../../base/browser/dom.js'; +import { getExtensionForMimeType } from '../../../../../../base/common/mime.js'; +import { IReader } from '../../../../../../base/common/observable.js'; +import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../../editor/common/services/model.js'; +import { localize } from '../../../../../../nls.js'; +import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { ChatResponseResource } from '../../../common/chatModel.js'; +import { IChatToolInvocation } from '../../../common/chatService.js'; +import { ILanguageModelToolsService, IToolResultDataPart, IToolResultPromptTsxPart, IToolResultTextPart, stringifyPromptTsxPart } from '../../../common/languageModelToolsService.js'; +import { AcceptToolPostConfirmationActionId, SkipToolPostConfirmationActionId } from '../../actions/chatToolActions.js'; +import { IChatCodeBlockInfo, IChatWidgetService } from '../../chat.js'; +import { IChatContentPartRenderContext } from '../chatContentParts.js'; +import { EditorPool } from '../chatMarkdownContentPart.js'; +import { ChatCollapsibleIOPart } from '../chatToolInputOutputContentPart.js'; +import { ChatToolOutputContentSubPart } from '../chatToolOutputContentSubPart.js'; +import { AbstractToolConfirmationSubPart, ConfirmationOutcome } from './abstractToolConfirmationSubPart.js'; + +export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmationSubPart { + private _codeblocks: IChatCodeBlockInfo[] = []; + public get codeblocks(): IChatCodeBlockInfo[] { + return this._codeblocks; + } + + constructor( + toolInvocation: IChatToolInvocation, + context: IChatContentPartRenderContext, + private readonly editorPool: EditorPool, + private readonly currentWidthDelegate: () => number, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService keybindingService: IKeybindingService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatWidgetService chatWidgetService: IChatWidgetService, + @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService, + ) { + super(toolInvocation, context, instantiationService, keybindingService, contextKeyService, chatWidgetService, languageModelToolsService); + const subtitle = toolInvocation.pastTenseMessage || toolInvocation.invocationMessage; + this.render({ + allowActionId: AcceptToolPostConfirmationActionId, + skipActionId: SkipToolPostConfirmationActionId, + allowLabel: localize('allow', "Allow"), + skipLabel: localize('skip.post', 'Skip Results'), + partType: 'chatToolPostConfirmation', + subtitle: typeof subtitle === 'string' ? subtitle : subtitle?.value, + }); + } + + protected createContentElement(): HTMLElement { + if (this.toolInvocation.kind !== 'toolInvocation') { + throw new Error('post-approval not supported for serialized data'); + } + const state = this.toolInvocation.state.get(); + if (state.type !== IChatToolInvocation.StateKind.WaitingForPostApproval) { + throw new Error('Tool invocation is not waiting for post-approval'); + } + + return this.createResultsDisplay(this.toolInvocation, state.contentForModel); + } + + protected getTitle(): string { + return localize('approveToolResult', "Approve Tool Result"); + } + + protected shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean { + const currentState = toolInvocation.state.read(reader); + return currentState.type !== IChatToolInvocation.StateKind.WaitingForPostApproval; + } + + protected override additionalPrimaryActions() { + const actions = super.additionalPrimaryActions(); + actions.push( + { label: localize('allowSession', 'Allow Without Review in this Session'), data: ConfirmationOutcome.AllowSession, tooltip: localize('allowSessionTooltip', 'Allow results from this tool to be sent without confirmation in this session.') }, + { label: localize('allowWorkspace', 'Allow Without Review in this Workspace'), data: ConfirmationOutcome.AllowWorkspace, tooltip: localize('allowWorkspaceTooltip', 'Allow results from this tool to be sent without confirmation in this workspace.') }, + { label: localize('allowGlobally', 'Always Allow Without Review'), data: ConfirmationOutcome.AllowGlobally, tooltip: localize('allowGloballyTooltip', 'Always allow results from this tool to be sent without confirmation.') }, + ); + + return actions; + } + + private createResultsDisplay(toolInvocation: IChatToolInvocation, contentForModel: (IToolResultPromptTsxPart | IToolResultTextPart | IToolResultDataPart)[]): HTMLElement { + const container = dom.$('.tool-postconfirm-display'); + + if (!contentForModel || contentForModel.length === 0) { + container.textContent = localize('noResults', 'No results to display'); + return container; + } + + const parts: ChatCollapsibleIOPart[] = []; + + for (const [i, part] of contentForModel.entries()) { + if (part.kind === 'text') { + // Display text parts + const model = this._register(this.modelService.createModel( + part.value, + this.languageService.createById('plaintext'), + undefined, + true + )); + + parts.push({ + kind: 'code', + textModel: model, + languageId: model.getLanguageId(), + options: { + hideToolbar: true, + reserveWidth: 19, + maxHeightInLines: 13, + verticalPadding: 5, + editorOptions: { wordWrap: 'on', readOnly: true } + }, + codeBlockInfo: { + codeBlockIndex: i, + codemapperUri: undefined, + elementId: this.context.element.id, + focus: () => { }, + isStreaming: false, + ownerMarkdownPartId: this.codeblocksPartId, + uri: model.uri, + chatSessionId: this.context.element.sessionId, + uriPromise: Promise.resolve(model.uri) + } + }); + } else if (part.kind === 'promptTsx') { + // Display TSX parts as JSON-stringified + const stringified = stringifyPromptTsxPart(part); + const model = this._register(this.modelService.createModel( + stringified, + this.languageService.createById('json'), + undefined, + true + )); + + parts.push({ + kind: 'code', + textModel: model, + languageId: model.getLanguageId(), + options: { + hideToolbar: true, + reserveWidth: 19, + maxHeightInLines: 13, + verticalPadding: 5, + editorOptions: { wordWrap: 'on', readOnly: true } + }, + codeBlockInfo: { + codeBlockIndex: i, + codemapperUri: undefined, + elementId: this.context.element.id, + focus: () => { }, + isStreaming: false, + ownerMarkdownPartId: this.codeblocksPartId, + uri: model.uri, + chatSessionId: this.context.element.sessionId, + uriPromise: Promise.resolve(model.uri) + } + }); + } else if (part.kind === 'data') { + // Display data parts + const mimeType = part.value.mimeType; + const data = part.value.data; + + // Check if it's an image + if (mimeType?.startsWith('image/')) { + const permalinkBasename = getExtensionForMimeType(mimeType) ? `image${getExtensionForMimeType(mimeType)}` : 'image.bin'; + const permalinkUri = ChatResponseResource.createUri(this.context.element.sessionId, toolInvocation.toolCallId, i, permalinkBasename); + parts.push({ kind: 'data', value: data.buffer, mimeType, uri: permalinkUri, audience: part.audience }); + } else { + // Try to display as UTF-8 text, otherwise base64 + const decoder = new TextDecoder('utf-8', { fatal: true }); + try { + const text = decoder.decode(data.buffer); + const model = this._register(this.modelService.createModel( + text, + this.languageService.createById('plaintext'), + undefined, + true + )); + + parts.push({ + kind: 'code', + textModel: model, + languageId: model.getLanguageId(), + options: { + hideToolbar: true, + reserveWidth: 19, + maxHeightInLines: 13, + verticalPadding: 5, + editorOptions: { wordWrap: 'on', readOnly: true } + }, + codeBlockInfo: { + codeBlockIndex: i, + codemapperUri: undefined, + elementId: this.context.element.id, + focus: () => { }, + isStreaming: false, + ownerMarkdownPartId: this.codeblocksPartId, + uri: model.uri, + chatSessionId: this.context.element.sessionId, + uriPromise: Promise.resolve(model.uri) + } + }); + } catch { + // Not valid UTF-8, show base64 + const base64 = data.toString(); + const model = this._register(this.modelService.createModel( + base64, + this.languageService.createById('plaintext'), + undefined, + true + )); + + parts.push({ + kind: 'code', + textModel: model, + languageId: model.getLanguageId(), + options: { + hideToolbar: true, + reserveWidth: 19, + maxHeightInLines: 13, + verticalPadding: 5, + editorOptions: { wordWrap: 'on', readOnly: true } + }, + codeBlockInfo: { + codeBlockIndex: i, + codemapperUri: undefined, + elementId: this.context.element.id, + focus: () => { }, + isStreaming: false, + ownerMarkdownPartId: this.codeblocksPartId, + uri: model.uri, + chatSessionId: this.context.element.sessionId, + uriPromise: Promise.resolve(model.uri) + } + }); + } + } + } + } + + if (parts.length > 0) { + const outputSubPart = this._register(this.instantiationService.createInstance( + ChatToolOutputContentSubPart, + this.context, + this.editorPool, + parts, + this.currentWidthDelegate() + )); + + this._codeblocks.push(...outputSubPart.codeblocks); + this._register(outputSubPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); + outputSubPart.domNode.classList.add('tool-postconfirm-display'); + return outputSubPart.domNode; + } + + container.textContent = localize('noDisplayableResults', 'No displayable results'); + return container; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index 19c532c5871..a39fd4bf0df 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -59,7 +59,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { } private get toolIsConfirmed() { - const c = IChatToolInvocation.isConfirmed(this.toolInvocation); + const c = IChatToolInvocation.executionConfirmedOrDenied(this.toolInvocation); return !!c && c.type !== ToolConfirmKind.Denied; } diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index 41b1decc2f1..9c8788ced8f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -84,9 +84,9 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi }); const toolInvocations = item.response.value.filter(item => item.kind === 'toolInvocation'); for (const toolInvocation of toolInvocations) { - if (toolInvocation.confirmationMessages && toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { + if (toolInvocation.confirmationMessages?.title && toolInvocation.state.get().type === IChatToolInvocation.StateKind.WaitingForConfirmation) { const title = typeof toolInvocation.confirmationMessages.title === 'string' ? toolInvocation.confirmationMessages.title : toolInvocation.confirmationMessages.title.value; - const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderAsPlaintext(toolInvocation.confirmationMessages.message)); + const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderAsPlaintext(toolInvocation.confirmationMessages.message!)); let input = ''; if (toolInvocation.toolSpecificData) { if (toolInvocation.toolSpecificData?.kind === 'terminal') { diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 6aa5a10ef76..a40f4f2c911 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -90,9 +90,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _callsByRequestId = new Map(); - private _workspaceToolConfirmStore: Lazy; - private _profileToolConfirmStore: Lazy; - private _memoryToolConfirmStore = new Set(); + private _preExecutionConfirmStore: GenericConfirmStore; + private _postExecutionConfirmStore: GenericConfirmStore; constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -109,8 +108,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo ) { super(); - this._workspaceToolConfirmStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.WORKSPACE))); - this._profileToolConfirmStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.PROFILE))); + this._preExecutionConfirmStore = this._register(new GenericConfirmStore('chat/autoconfirm', this._instantiationService)); + this._postExecutionConfirmStore = this._register(new GenericConfirmStore('chat/autoconfirm-post', this._instantiationService)); this._register(this._contextKeyService.onDidChangeContext(e => { if (e.affectsSome(this._toolContextKeys)) { @@ -236,33 +235,20 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void { - this._workspaceToolConfirmStore.value.setAutoConfirm(toolId, scope === 'workspace'); - this._profileToolConfirmStore.value.setAutoConfirm(toolId, scope === 'profile'); - - if (scope === 'session') { - this._memoryToolConfirmStore.add(toolId); - } else { - this._memoryToolConfirmStore.delete(toolId); - } + this._preExecutionConfirmStore.setAutoConfirmation(toolId, scope); } getToolAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never' { - if (this._workspaceToolConfirmStore.value.getAutoConfirm(toolId)) { - return 'workspace'; - } - if (this._profileToolConfirmStore.value.getAutoConfirm(toolId)) { - return 'profile'; - } - if (this._memoryToolConfirmStore.has(toolId)) { - return 'session'; - } - return 'never'; + return this._preExecutionConfirmStore.getAutoConfirmation(toolId); } resetToolAutoConfirmation(): void { - this._workspaceToolConfirmStore.value.reset(); - this._profileToolConfirmStore.value.reset(); - this._memoryToolConfirmStore.clear(); + this._preExecutionConfirmStore.reset(); + this._postExecutionConfirmStore.reset(); + } + + getToolPostExecutionAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never' { + return this._postExecutionConfirmStore.getAutoConfirmation(toolId); } async invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { @@ -292,6 +278,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo let toolResult: IToolResult | undefined; let prepareTimeWatch: StopWatch | undefined; let invocationTimeWatch: StopWatch | undefined; + let preparedInvocation: IPreparedToolInvocation | undefined; try { if (dto.context) { store = new DisposableStore(); @@ -325,10 +312,10 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo token = source.token; prepareTimeWatch = StopWatch.create(true); - const prepared = await this.prepareToolInvocation(tool, dto, token); + preparedInvocation = await this.prepareToolInvocation(tool, dto, token); prepareTimeWatch.stop(); - toolInvocation = new ChatToolInvocation(prepared, tool.data, dto.callId, dto.fromSubAgent); + toolInvocation = new ChatToolInvocation(preparedInvocation, tool.data, dto.callId, dto.fromSubAgent); trackedCall.invocation = toolInvocation; const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace); if (autoConfirmed) { @@ -338,8 +325,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo model.acceptResponseProgress(request, toolInvocation); dto.toolSpecificData = toolInvocation?.toolSpecificData; - if (prepared?.confirmationMessages) { - if (!IChatToolInvocation.isConfirmed(toolInvocation) && !autoConfirmed) { + if (preparedInvocation?.confirmationMessages?.title) { + if (!IChatToolInvocation.executionConfirmedOrDenied(toolInvocation) && !autoConfirmed) { this.playAccessibilitySignal([toolInvocation]); } const userConfirmed = await IChatToolInvocation.awaitConfirmation(toolInvocation, token); @@ -355,6 +342,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo }; return toolResult; } + if (userConfirmed.type === ToolConfirmKind.LmServicePerTool) { + this._preExecutionConfirmStore.setAutoConfirmation(dto.toolId, userConfirmed.scope); + } if (dto.toolSpecificData?.kind === 'input') { dto.parameters = dto.toolSpecificData.rawInput; @@ -363,16 +353,16 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } } else { prepareTimeWatch = StopWatch.create(true); - const prepared = await this.prepareToolInvocation(tool, dto, token); + preparedInvocation = await this.prepareToolInvocation(tool, dto, token); prepareTimeWatch.stop(); - if (prepared?.confirmationMessages && !(await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace))) { - const result = await this._dialogService.confirm({ message: renderAsPlaintext(prepared.confirmationMessages.title), detail: renderAsPlaintext(prepared.confirmationMessages.message) }); + if (preparedInvocation?.confirmationMessages?.title && !(await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace))) { + const result = await this._dialogService.confirm({ message: renderAsPlaintext(preparedInvocation.confirmationMessages.title), detail: renderAsPlaintext(preparedInvocation.confirmationMessages.message!) }); if (!result.confirmed) { throw new CancellationError(); } } - dto.toolSpecificData = prepared?.toolSpecificData; + dto.toolSpecificData = preparedInvocation?.toolSpecificData; } if (token.isCancellationRequested) { @@ -388,6 +378,29 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo invocationTimeWatch.stop(); this.ensureToolDetails(dto, toolResult, tool.data); + if (toolInvocation?.didExecuteTool(toolResult).type === IChatToolInvocation.StateKind.WaitingForPostApproval) { + const autoConfirmedPost = await this.shouldAutoConfirmPostExecution(tool.data.id, tool.data.runsInWorkspace); + if (autoConfirmedPost) { + IChatToolInvocation.confirmWith(toolInvocation, autoConfirmedPost); + } + + const postConfirm = await IChatToolInvocation.awaitPostConfirmation(toolInvocation, token); + if (postConfirm.type === ToolConfirmKind.Denied) { + throw new CancellationError(); + } + if (postConfirm.type === ToolConfirmKind.Skipped) { + toolResult = { + content: [{ + kind: 'text', + value: 'The tool executed but the user chose not to share the results' + }] + }; + } + if (postConfirm.type === ToolConfirmKind.LmServicePerTool) { + this._postExecutionConfirmStore.setAutoConfirmation(dto.toolId, postConfirm.scope); + } + } + this._telemetryService.publicLog2( 'languageModelToolInvoked', { @@ -423,7 +436,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo throw err; } finally { - toolInvocation?.complete(toolResult); + toolInvocation?.didExecuteTool(toolResult, true); if (store) { this.cleanupCallDisposables(requestId, store); } @@ -440,7 +453,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo }, token) : undefined; - if (prepared?.confirmationMessages) { + if (prepared?.confirmationMessages?.title) { if (prepared.toolSpecificData?.kind !== 'terminal' && typeof prepared.confirmationMessages.allowAutoConfirm !== 'boolean') { prepared.confirmationMessages.allowAutoConfirm = true; } @@ -500,14 +513,9 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } private async shouldAutoConfirm(toolId: string, runsInWorkspace: boolean | undefined): Promise { - if (this._workspaceToolConfirmStore.value.getAutoConfirm(toolId)) { - return { type: ToolConfirmKind.LmServicePerTool, scope: 'workspace' }; - } - if (this._profileToolConfirmStore.value.getAutoConfirm(toolId)) { - return { type: ToolConfirmKind.LmServicePerTool, scope: 'profile' }; - } - if (this._memoryToolConfirmStore.has(toolId)) { - return { type: ToolConfirmKind.LmServicePerTool, scope: 'session' }; + const reason = this._preExecutionConfirmStore.checkAutoConfirmation(toolId); + if (reason) { + return reason; } const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); @@ -532,6 +540,10 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo return undefined; } + private async shouldAutoConfirmPostExecution(toolId: string, runsInWorkspace: boolean | undefined): Promise { + return this._postExecutionConfirmStore.checkAutoConfirmation(toolId); + } + private async _checkGlobalAutoApprove(): Promise { const optedIn = this._storageService.getBoolean(AutoApproveStorageKeys.GlobalAutoApproveOptIn, StorageScope.APPLICATION, false); if (optedIn) { @@ -817,19 +829,76 @@ type LanguageModelToolInvokedClassification = { comment: 'Provides insight into the usage of language model tools.'; }; -class ToolConfirmStore extends Disposable { - private static readonly STORED_KEY = 'chat/autoconfirm'; +class GenericConfirmStore extends Disposable { + private _workspaceStore: Lazy; + private _profileStore: Lazy; + private _memoryStore = new Set(); + + constructor( + private readonly _storageKey: string, + private readonly _instantiationService: IInstantiationService, + ) { + super(); + this._workspaceStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.WORKSPACE, this._storageKey))); + this._profileStore = new Lazy(() => this._register(this._instantiationService.createInstance(ToolConfirmStore, StorageScope.PROFILE, this._storageKey))); + } + + public setAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void { + this._workspaceStore.value.setAutoConfirm(toolId, scope === 'workspace'); + this._profileStore.value.setAutoConfirm(toolId, scope === 'profile'); + + if (scope === 'session') { + this._memoryStore.add(toolId); + } else { + this._memoryStore.delete(toolId); + } + } + public getAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never' { + if (this._workspaceStore.value.getAutoConfirm(toolId)) { + return 'workspace'; + } + if (this._profileStore.value.getAutoConfirm(toolId)) { + return 'profile'; + } + if (this._memoryStore.has(toolId)) { + return 'session'; + } + return 'never'; + } + + public reset(): void { + this._workspaceStore.value.reset(); + this._profileStore.value.reset(); + this._memoryStore.clear(); + } + + public checkAutoConfirmation(toolId: string): ConfirmedReason | undefined { + if (this._workspaceStore.value.getAutoConfirm(toolId)) { + return { type: ToolConfirmKind.LmServicePerTool, scope: 'workspace' }; + } + if (this._profileStore.value.getAutoConfirm(toolId)) { + return { type: ToolConfirmKind.LmServicePerTool, scope: 'profile' }; + } + if (this._memoryStore.has(toolId)) { + return { type: ToolConfirmKind.LmServicePerTool, scope: 'session' }; + } + return undefined; + } +} + +class ToolConfirmStore extends Disposable { private _autoConfirmTools: LRUCache = new LRUCache(100); private _didChange = false; constructor( private readonly _scope: StorageScope, + private readonly _storageKey: string, @IStorageService private readonly storageService: IStorageService, ) { super(); - const stored = storageService.getObject(ToolConfirmStore.STORED_KEY, this._scope); + const stored = storageService.getObject(this._storageKey, this._scope); if (stored) { for (const key of stored) { this._autoConfirmTools.set(key, true); @@ -838,7 +907,7 @@ class ToolConfirmStore extends Disposable { this._register(storageService.onWillSaveState(() => { if (this._didChange) { - this.storageService.store(ToolConfirmStore.STORED_KEY, [...this._autoConfirmTools.keys()], this._scope, StorageTarget.MACHINE); + this.storageService.store(this._storageKey, [...this._autoConfirmTools.keys()], this._scope, StorageTarget.MACHINE); this._didChange = false; } })); diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index af526f1f699..07fce57b24a 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -7,7 +7,7 @@ import { encodeBase64 } from '../../../../../base/common/buffer.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { IObservable, ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; -import { IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type IChatTerminalToolInvocationData } from '../chatService.js'; +import { ConfirmedReason, IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type IChatTerminalToolInvocationData } from '../chatService.js'; import { IPreparedToolInvocation, isToolResultOutputDetails, IToolConfirmationMessages, IToolData, IToolProgressStep, IToolResult, ToolDataSource } from '../languageModelToolsService.js'; export class ChatToolInvocation implements IChatToolInvocation { @@ -45,7 +45,7 @@ export class ChatToolInvocation implements IChatToolInvocation { this.source = toolData.source; this.fromSubAgent = fromSubAgent; - if (!this.confirmationMessages) { + if (!this.confirmationMessages?.title) { this._state = observableValue(this, { type: IChatToolInvocation.StateKind.Executing, confirmed: { type: ToolConfirmKind.ConfirmationNotNeeded }, progress: this._progress }); } else { this._state = observableValue(this, { @@ -61,21 +61,43 @@ export class ChatToolInvocation implements IChatToolInvocation { } } - public complete(result: IToolResult | undefined): void { - if (result?.toolResultMessage) { - this.pastTenseMessage = result.toolResultMessage; - } else if (this._progress.get().message) { - this.pastTenseMessage = this._progress.get().message; + private _setCompleted(result: IToolResult | undefined, postConfirmed?: ConfirmedReason | undefined) { + if (postConfirmed && (postConfirmed.type === ToolConfirmKind.Denied || postConfirmed.type === ToolConfirmKind.Skipped)) { + this._state.set({ type: IChatToolInvocation.StateKind.Cancelled, reason: postConfirmed.type }, undefined); + return; } this._state.set({ type: IChatToolInvocation.StateKind.Completed, - confirmed: IChatToolInvocation.isConfirmed(this) || { type: ToolConfirmKind.UserAction }, + confirmed: IChatToolInvocation.executionConfirmedOrDenied(this) || { type: ToolConfirmKind.ConfirmationNotNeeded }, resultDetails: result?.toolResultDetails, - postConfirmed: undefined, + postConfirmed, + contentForModel: result?.content || [], }, undefined); } + public didExecuteTool(result: IToolResult | undefined, final?: boolean): IChatToolInvocation.State { + if (result?.toolResultMessage) { + this.pastTenseMessage = result.toolResultMessage; + } else if (this._progress.get().message) { + this.pastTenseMessage = this._progress.get().message; + } + + if (this.confirmationMessages?.confirmResults && !result?.toolResultError && !final) { + this._state.set({ + type: IChatToolInvocation.StateKind.WaitingForPostApproval, + confirmed: IChatToolInvocation.executionConfirmedOrDenied(this) || { type: ToolConfirmKind.ConfirmationNotNeeded }, + resultDetails: result?.toolResultDetails, + contentForModel: result?.content || [], + confirm: reason => this._setCompleted(result, reason), + }, undefined); + } else { + this._setCompleted(result); + } + + return this._state.get(); + } + public acceptProgress(step: IToolProgressStep) { const prev = this._progress.get(); this._progress.set({ @@ -85,14 +107,17 @@ export class ChatToolInvocation implements IChatToolInvocation { } public toJSON(): IChatToolInvocationSerialized { - const details = IChatToolInvocation.resultDetails(this); + // persist the serialized call as 'skipped' if we were waiting for postapproval + const waitingForPostApproval = this.state.get().type === IChatToolInvocation.StateKind.WaitingForPostApproval; + const details = waitingForPostApproval ? undefined : IChatToolInvocation.resultDetails(this); + return { kind: 'toolInvocationSerialized', presentation: this.presentation, invocationMessage: this.invocationMessage, pastTenseMessage: this.pastTenseMessage, originMessage: this.originMessage, - isConfirmed: IChatToolInvocation.isConfirmed(this), + isConfirmed: waitingForPostApproval ? { type: ToolConfirmKind.Skipped } : IChatToolInvocation.executionConfirmedOrDenied(this), isComplete: true, source: this.source, resultDetails: isToolResultOutputDetails(details) diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index f4d14ba7a8c..34cd1279c58 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -389,12 +389,14 @@ export namespace IChatToolInvocation { interface IChatToolWaitingForPostApprovalState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState { type: StateKind.WaitingForPostApproval; - postConfirm(reason: ConfirmedReason): void; + confirm(reason: ConfirmedReason): void; + contentForModel: IToolResult['content']; } interface IChatToolInvocationCompleteState extends IChatToolInvocationStateBase, IChatToolInvocationPostExecuteState { type: StateKind.Completed; postConfirmed: ConfirmedReason | undefined; + contentForModel: IToolResult['content']; } interface IChatToolInvocationCancelledState extends IChatToolInvocationStateBase { @@ -409,7 +411,7 @@ export namespace IChatToolInvocation { | IChatToolInvocationCompleteState | IChatToolInvocationCancelledState; - export function isConfirmed(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): ConfirmedReason | undefined { + export function executionConfirmedOrDenied(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader): ConfirmedReason | undefined { if (invocation.kind === 'toolInvocationSerialized') { if (invocation.isConfirmed === undefined || typeof invocation.isConfirmed === 'boolean') { return { type: invocation.isConfirmed ? ToolConfirmKind.UserAction : ToolConfirmKind.Denied }; @@ -429,7 +431,7 @@ export namespace IChatToolInvocation { } export function awaitConfirmation(invocation: IChatToolInvocation, token?: CancellationToken): Promise { - const reason = isConfirmed(invocation); + const reason = executionConfirmedOrDenied(invocation); if (reason) { return Promise.resolve(reason); } @@ -443,7 +445,7 @@ export namespace IChatToolInvocation { } store.add(autorun(reader => { - const reason = isConfirmed(invocation, reader); + const reason = executionConfirmedOrDenied(invocation, reader); if (reason) { store.dispose(); resolve(reason); @@ -454,15 +456,53 @@ export namespace IChatToolInvocation { }); } + function postApprovalConfirmedOrDenied(invocation: IChatToolInvocation, reader?: IReader): ConfirmedReason | undefined { + const state = invocation.state.read(reader); + if (state.type === StateKind.Completed) { + return state.postConfirmed || { type: ToolConfirmKind.ConfirmationNotNeeded }; + } + if (state.type === StateKind.Cancelled) { + return { type: state.reason }; + } + + return undefined; + } + export function confirmWith(invocation: IChatToolInvocation | undefined, reason: ConfirmedReason) { const state = invocation?.state.get(); - if (state?.type === StateKind.WaitingForConfirmation) { + if (state?.type === StateKind.WaitingForConfirmation || state?.type === StateKind.WaitingForPostApproval) { state.confirm(reason); return true; } return false; } + export function awaitPostConfirmation(invocation: IChatToolInvocation, token?: CancellationToken): Promise { + const reason = postApprovalConfirmedOrDenied(invocation); + if (reason) { + return Promise.resolve(reason); + } + + const store = new DisposableStore(); + return new Promise(resolve => { + if (token) { + store.add(token.onCancellationRequested(() => { + resolve({ type: ToolConfirmKind.Denied }); + })); + } + + store.add(autorun(reader => { + const reason = postApprovalConfirmedOrDenied(invocation, reader); + if (reason) { + store.dispose(); + resolve(reason); + } + })); + }).finally(() => { + store.dispose(); + }); + } + export function resultDetails(invocation: IChatToolInvocation | IChatToolInvocationSerialized, reader?: IReader) { if (invocation.kind === 'toolInvocationSerialized') { return invocation.resultDetails; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 134a4228516..30ad9c066f8 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -222,11 +222,15 @@ export interface IToolResultDataPart { } export interface IToolConfirmationMessages { - title: string | IMarkdownString; - message: string | IMarkdownString; + /** Title for the confirmation. If set, the user will be asked to confirm execution of the tool */ + title?: string | IMarkdownString; + /** MUST be set if `title` is also set */ + message?: string | IMarkdownString; disclaimer?: string | IMarkdownString; allowAutoConfirm?: boolean; terminalCustomActions?: ToolConfirmationAction[]; + /** If true, confirmation will be requested after the tool executes and before results are sent to the model */ + confirmResults?: boolean; } export interface IToolConfirmationAction { @@ -327,6 +331,7 @@ export interface ILanguageModelToolsService { setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void; getToolAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never'; resetToolAutoConfirmation(): void; + getToolPostExecutionAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never'; cancelToolCallsForRequest(requestId: string): void; readonly toolSets: IObservable>; diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index da3683e38eb..6b57f0181e7 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -1202,49 +1202,6 @@ suite('LanguageModelToolsService', () => { await promise3; }); - test('setToolAutoConfirmation and getToolAutoConfirmation', () => { - const toolId = 'testAutoConfirmTool'; - - // Initially should be 'never' - assert.strictEqual(service.getToolAutoConfirmation(toolId), 'never'); - - // Set to workspace scope - service.setToolAutoConfirmation(toolId, 'workspace'); - assert.strictEqual(service.getToolAutoConfirmation(toolId), 'workspace'); - - // Set to profile scope - service.setToolAutoConfirmation(toolId, 'profile'); - assert.strictEqual(service.getToolAutoConfirmation(toolId), 'profile'); - - // Set to session scope - service.setToolAutoConfirmation(toolId, 'session'); - assert.strictEqual(service.getToolAutoConfirmation(toolId), 'session'); - - // Set back to never - service.setToolAutoConfirmation(toolId, 'never'); - assert.strictEqual(service.getToolAutoConfirmation(toolId), 'never'); - }); - - test('resetToolAutoConfirmation', () => { - const toolId1 = 'testTool1'; - const toolId2 = 'testTool2'; - - // Set different auto-confirmations - service.setToolAutoConfirmation(toolId1, 'workspace'); - service.setToolAutoConfirmation(toolId2, 'session'); - - // Verify they're set - assert.strictEqual(service.getToolAutoConfirmation(toolId1), 'workspace'); - assert.strictEqual(service.getToolAutoConfirmation(toolId2), 'session'); - - // Reset all - service.resetToolAutoConfirmation(); - - // Should all be back to 'never' - assert.strictEqual(service.getToolAutoConfirmation(toolId1), 'never'); - assert.strictEqual(service.getToolAutoConfirmation(toolId2), 'never'); - }); - test('createToolSet and getToolSet', () => { const toolSet = store.add(service.createToolSet( ToolDataSource.Internal, diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index d97873b3eb2..6573346705a 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -17,9 +17,6 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService constructor() { } - cancelToolCallsForRequest(requestId: string): void { - } - readonly onDidChangeTools: Event = Event.None; registerToolData(toolData: IToolData): IDisposable { @@ -30,6 +27,18 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService } + getToolPostExecutionAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never' { + return 'never'; + } + + resetToolPostExecutionAutoConfirmation(): void { + + } + + cancelToolCallsForRequest(requestId: string): void { + + } + setToolAutoConfirmation(toolId: string, scope: any): void { } diff --git a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts index b15ca909029..037cd29abf4 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts @@ -23,7 +23,7 @@ import { StorageScope } from '../../../../platform/storage/common/storage.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ChatResponseResource, getAttachableImageExtension } from '../../chat/common/chatModel.js'; import { LanguageModelPartAudience } from '../../chat/common/languageModels.js'; -import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultInputOutputDetails, ToolDataSource, ToolProgress, ToolSet } from '../../chat/common/languageModelToolsService.js'; +import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolConfirmationMessages, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultInputOutputDetails, ToolDataSource, ToolProgress, ToolSet } from '../../chat/common/languageModelToolsService.js'; import { IMcpRegistry } from './mcpRegistryTypes.js'; import { IMcpServer, IMcpService, IMcpTool, IMcpToolResourceLinkContents, McpResourceURI, McpToolResourceLinkMimeType } from './mcpTypes.js'; import { mcpServerToSourceData } from './mcpTypesUtils.js'; @@ -175,17 +175,22 @@ class McpToolImplementation implements IToolImpl { this._productService.nameShort ); - const needsConfirmation = !tool.definition.annotations?.readOnlyHint || !!tool.definition.annotations.openWorldHint; // duplicative: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/813 const title = tool.definition.annotations?.title || tool.definition.title || ('`' + tool.definition.name + '`'); + const confirm: IToolConfirmationMessages = {}; + if (!tool.definition.annotations?.readOnlyHint) { + confirm.title = new MarkdownString(localize('msg.title', "Run {0}", title)); + confirm.message = new MarkdownString(tool.definition.description, { supportThemeIcons: true }); + confirm.disclaimer = mcpToolWarning; + confirm.allowAutoConfirm = true; + } + if (tool.definition.annotations?.openWorldHint) { + confirm.confirmResults = true; + } + return { - confirmationMessages: needsConfirmation ? { - title: new MarkdownString(localize('msg.title', "Run {0}", title)), - message: new MarkdownString(tool.definition.description, { supportThemeIcons: true }), - disclaimer: mcpToolWarning, - allowAutoConfirm: true, - } : undefined, + confirmationMessages: confirm, invocationMessage: new MarkdownString(localize('msg.run', "Running {0}", title)), pastTenseMessage: new MarkdownString(localize('msg.ran', "Ran {0} ", title)), originMessage: localize('msg.subtitle', "{0} (MCP Server)", server.definition.label), From 3da5878de54cf294d899299b6e117cfe8dcc2430 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 22 Oct 2025 18:45:42 +0200 Subject: [PATCH 1482/4355] agents: fix syntax highlighting (#272738) * agents: fix syntax highlighting * use chatagent --- extensions/prompt-basics/package.json | 6 +++--- .../contrib/chat/common/promptSyntax/promptTypes.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index caeeb0b73ff..846766d297b 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -38,7 +38,7 @@ "configuration": "./language-configuration.json" }, { - "id": "agent", + "id": "chatagent", "aliases": [ "Agent", "chat agent" @@ -70,7 +70,7 @@ ] }, { - "language": "chatmode", + "language": "chatagent", "path": "./syntaxes/prompt.tmLanguage.json", "scopeName": "text.html.markdown.prompt", "unbalancedBracketScopes": [ @@ -102,7 +102,7 @@ "other": "off" } }, - "[chatmode]": { + "[chatagent]": { "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, "diffEditor.ignoreTrimWhitespace": false, diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts index bae4b5626c4..9ae26e570af 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptTypes.ts @@ -25,7 +25,7 @@ export const INSTRUCTIONS_LANGUAGE_ID = 'instructions'; /** * Language ID for agent syntax. */ -export const AGENT_LANGUAGE_ID = 'agent'; +export const AGENT_LANGUAGE_ID = 'chatagent'; /** * Prompt and instructions files language selector. From 88c238e686ca3d6a93290d85afc9c26506b104f5 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:55:49 -0700 Subject: [PATCH 1483/4355] Enable 'cloud button v2' by default (#272749) enable cloud button v2 by default --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index b379cafa08f..20ba0d97eae 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -735,7 +735,7 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.UseCloudButtonV2]: { type: 'boolean', description: nls.localize('chat.useCloudButtonV2', "Experimental implementation of 'cloud button'"), - default: false, + default: true, tags: ['experimental'], }, From f13b84cf8020b8fe1ed0db5a54be25f9f94ae416 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 20:42:39 +0200 Subject: [PATCH 1484/4355] agent sessions - use provider display name in dropdown (#272754) --- .../chat/browser/agentSessions/agentSessionsView.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 0d0f77a7ead..22988c16368 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -228,15 +228,15 @@ export class AgentSessionsView extends FilterViewPane { actions: { getActions: () => { const actions: IAction[] = []; - for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) { - if (provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE) { + for (const provider of this.chatSessionsService.getAllChatSessionContributions()) { + if (provider.type === LOCAL_AGENT_SESSION_TYPE) { continue; // local is the primary action } actions.push(toAction({ - id: `newChatSessionFromProvider.${provider.chatSessionType}`, - label: localize('newChatSessionFromProvider', "New Session ({0})", provider.chatSessionType), - run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.chatSessionType}`) + id: `newChatSessionFromProvider.${provider.type}`, + label: localize('newChatSessionFromProvider', "New Session ({0})", provider.displayName), + run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.type}`) })); } return actions; From dda6e09f62ef2acc69ade50343458c4dd4f39402 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:06:20 +0200 Subject: [PATCH 1485/4355] Update git clone to use new repo cache (#272276) --- extensions/git/src/api/api1.ts | 8 +- extensions/git/src/api/git.d.ts | 20 +- extensions/git/src/commands.ts | 144 +---------- extensions/git/src/model.ts | 235 +++++++++++++++++- extensions/git/src/repositoryCache.ts | 85 ++++--- .../git/src/test/repositoryCache.test.ts | 26 +- 6 files changed, 324 insertions(+), 194 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 2be6cec8dea..b57a18e988b 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -7,7 +7,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, filterEvent, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -399,6 +399,12 @@ export class ApiImpl implements API { return this.getRepository(root) || null; } + async clone(uri: Uri, options?: CloneOptions): Promise { + const parentPath = options?.parentPath?.fsPath; + const result = await this.#model.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction, skipCache: options?.skipCache }); + return result ? Uri.file(result) : null; + } + async openRepository(root: Uri): Promise { if (root.scheme !== 'file') { return null; diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index fdfb8b397bc..f7813f13e8c 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -183,6 +183,20 @@ export interface InitOptions { defaultBranch?: string; } +export interface CloneOptions { + parentPath?: Uri; + /** + * ref is only used if the repository cache is missed. + */ + ref?: string; + recursive?: boolean; + /** + * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. + */ + postCloneAction?: 'none' | 'open' | 'prompt'; + skipCache?: boolean; +} + export interface RefQuery { readonly contains?: string; readonly count?: number; @@ -366,7 +380,11 @@ export interface API { getRepository(uri: Uri): Repository | null; getRepositoryRoot(uri: Uri): Promise; init(root: Uri, options?: InitOptions): Promise; - openRepository(root: Uri): Promise + /** + * @returns The URI of either the cloned repository, or the workspace file or folder which contains the cloned repository. + */ + clone(uri: Uri, options?: CloneOptions): Promise; + openRepository(root: Uri): Promise; registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 301f6bdef9f..a7452e9d8f4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -944,144 +944,6 @@ export class CommandCenter { } } - async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string } = {}): Promise { - if (!url || typeof url !== 'string') { - url = await pickRemoteSource({ - providerLabel: provider => l10n.t('Clone from {0}', provider.name), - urlLabel: l10n.t('Clone from URL') - }); - } - - if (!url) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); - return; - } - - url = url.trim().replace(/^git\s+clone\s+/, ''); - - if (!parentPath) { - const config = workspace.getConfiguration('git'); - let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); - defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - - const uris = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: Uri.file(defaultCloneDirectory), - title: l10n.t('Choose a folder to clone {0} into', url), - openLabel: l10n.t('Select as Repository Destination') - }); - - if (!uris || uris.length === 0) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); - return; - } - - const uri = uris[0]; - parentPath = uri.fsPath; - } - - try { - const opts = { - location: ProgressLocation.Notification, - title: l10n.t('Cloning git repository "{0}"...', url), - cancellable: true - }; - - const repositoryPath = await window.withProgress( - opts, - (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) - ); - - const config = workspace.getConfiguration('git'); - const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - - enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace } - let action: PostCloneAction | undefined = undefined; - - if (openAfterClone === 'always') { - action = PostCloneAction.Open; - } else if (openAfterClone === 'alwaysNewWindow') { - action = PostCloneAction.OpenNewWindow; - } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { - action = PostCloneAction.Open; - } - - if (action === undefined) { - let message = l10n.t('Would you like to open the cloned repository?'); - const open = l10n.t('Open'); - const openNewWindow = l10n.t('Open in New Window'); - const choices = [open, openNewWindow]; - - const addToWorkspace = l10n.t('Add to Workspace'); - if (workspace.workspaceFolders) { - message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, { modal: true }, ...choices); - - action = result === open ? PostCloneAction.Open - : result === openNewWindow ? PostCloneAction.OpenNewWindow - : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; - } - - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); - - const uri = Uri.file(repositoryPath); - - if (action === PostCloneAction.Open) { - commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (action === PostCloneAction.AddToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (action === PostCloneAction.OpenNewWindow) { - commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); - } - } catch (err) { - if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); - } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { - return; - } else { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); - } - - throw err; - } - } - private getRepositoriesWithRemote(repositories: Repository[]) { return repositories.reduce<(QuickPickItem & { repository: Repository })[]>((items, repository) => { const remote = repository.remotes.find((r) => r.name === repository.HEAD?.upstream?.remote); @@ -1154,12 +1016,12 @@ export class CommandCenter { @command('git.clone') async clone(url?: string, parentPath?: string, options?: { ref?: string }): Promise { - await this.cloneRepository(url, parentPath, options); + await this.model.clone(url, { parentPath, ...options }); } @command('git.cloneRecursive') async cloneRecursive(url?: string, parentPath?: string): Promise { - await this.cloneRepository(url, parentPath, { recursive: true }); + await this.model.clone(url, { parentPath, recursive: true }); } @command('git.init') diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index e96a9d31290..2d70991f527 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder, ThemeIcon } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { IRepositoryResolver, Repository, RepositoryState } from './repository'; @@ -20,7 +21,8 @@ import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; import { IBranchProtectionProviderRegistry } from './branchProtection'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; -import { RepositoryCache } from './repositoryCache'; +import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; +import { pickRemoteSource } from './remoteSource'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { @@ -61,6 +63,16 @@ interface OpenRepository extends Disposable { repository: Repository; } +type PostCloneAction = 'none' | 'open' | 'prompt'; + +export interface CloneOptions { + parentPath?: string; + ref?: string; + recursive?: boolean; + postCloneAction?: PostCloneAction; + skipCache?: boolean; +} + class ClosedRepositoriesManager { private _repositories: Set; @@ -1092,6 +1104,227 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return this._unsafeRepositoriesManager.deleteRepository(repository); } + async clone(url?: string, options: CloneOptions = {}) { + const cachedRepository = url ? this.repositoryCache.get(url) : undefined; + if (url && !options.skipCache && cachedRepository && (cachedRepository.length > 0)) { + return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); + } + return this.cloneRepository(url, options.parentPath, options); + } + + async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: PostCloneAction } = {}): Promise { + if (!url || typeof url !== 'string') { + url = await pickRemoteSource({ + providerLabel: provider => l10n.t('Clone from {0}', provider.name), + urlLabel: l10n.t('Clone from URL') + }); + } + + if (!url) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); + return; + } + + url = url.trim().replace(/^git\s+clone\s+/, ''); + + if (!parentPath) { + const config = workspace.getConfiguration('git'); + let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); + defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); + + const uris = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(defaultCloneDirectory), + title: l10n.t('Choose a folder to clone {0} into', url), + openLabel: l10n.t('Select as Repository Destination') + }); + + if (!uris || uris.length === 0) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); + return; + } + + const uri = uris[0]; + parentPath = uri.fsPath; + } + + try { + const opts = { + location: ProgressLocation.Notification, + title: l10n.t('Cloning git repository "{0}"...', url), + cancellable: true + }; + + const repositoryPath = await window.withProgress( + opts, + (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) + ); + + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); + + enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } + let action: PostCloneAction | undefined = undefined; + + if (options.postCloneAction) { + if (options.postCloneAction === 'open') { + action = PostCloneAction.Open; + } else if (options.postCloneAction === 'none') { + action = PostCloneAction.None; + } + } else { + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; + } + } + + if (action === undefined) { + let message = l10n.t('Would you like to open the cloned repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); + const choices = [open, openNewWindow]; + + const addToWorkspace = l10n.t('Add to Workspace'); + if (workspace.workspaceFolders) { + message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, { modal: true }, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } + + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); + + const uri = Uri.file(repositoryPath); + + if (action === PostCloneAction.Open) { + commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } else if (action === PostCloneAction.AddToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (action === PostCloneAction.OpenNewWindow) { + commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); + } + + return repositoryPath; + } catch (err) { + if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); + } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } else { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); + } + + throw err; + } + } + + private async postCloneAction(target: string, postCloneAction?: PostCloneAction): Promise { + const forceReuseWindow = ((workspace.workspaceFile === undefined) && (workspace.workspaceFolders === undefined)); + if (postCloneAction === 'open') { + await commands.executeCommand('vscode.openFolder', Uri.file(target), { forceReuseWindow }); + } + } + + private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: PostCloneAction): Promise { + try { + const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { + const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); + const label = isWorkspace ? l10n.t('Workspace: {0}', path.basename(knownFolder.workspacePath, '.code-workspace')) : path.basename(knownFolder.workspacePath); + return { label, description: knownFolder.workspacePath, item: knownFolder }; + }); + const cloneAgain = { label: l10n.t('Clone again') }; + items.push(cloneAgain); + const placeHolder = l10n.t('Open Existing Repository Clone'); + const pick = await window.showQuickPick(items, { placeHolder, canPickMany: false }); + if (pick === cloneAgain) { + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction })) ?? undefined; + } + if (!pick?.item) { + return undefined; + } + return pick.item.workspacePath; + } catch { + return undefined; + } + } + + private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: PostCloneAction, parentPath?: string, ref?: string): Promise { + // Gather existing folders/workspace files (ignore ones that no longer exist) + const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { + const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); + if (stat) { + return folder; + } + return undefined; + } + ))).filter((folder): folder is RepositoryCacheInfo => folder !== undefined); + + if (!existingCachedRepositories.length) { + // fallback to clone + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction }) ?? undefined); + } + + // First, find the cached repo that exists in the current workspace + const matchingInCurrentWorkspace = existingCachedRepositories?.find(cachedRepo => { + return workspace.workspaceFolders?.some(workspaceFolder => workspaceFolder.uri.fsPath === cachedRepo.workspacePath); + }); + + if (matchingInCurrentWorkspace) { + return matchingInCurrentWorkspace.workspacePath; + } + + let repoForWorkspace: string | undefined = (existingCachedRepositories.length === 1 ? existingCachedRepositories[0].workspacePath : undefined); + if (!repoForWorkspace) { + repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); + } + if (repoForWorkspace) { + return this.postCloneAction(repoForWorkspace, postCloneAction); + } + return undefined; + } + private async isRepositoryOutsideWorkspace(repositoryPath: string): Promise { const workspaceFolders = (workspace.workspaceFolders || []) .filter(folder => folder.uri.scheme === 'file'); diff --git a/extensions/git/src/repositoryCache.ts b/extensions/git/src/repositoryCache.ts index 6a00974e0b3..ed798726953 100644 --- a/extensions/git/src/repositoryCache.ts +++ b/extensions/git/src/repositoryCache.ts @@ -4,9 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { LogOutputChannel, Memento, workspace } from 'vscode'; -import * as path from 'path'; import { LRUCache } from './cache'; import { Remote } from './api/git'; +import { isDescendant } from './util'; + +export interface RepositoryCacheInfo { + workspacePath: string; // path of the workspace folder or workspace file +} + +function isRepositoryCacheInfo(obj: unknown): obj is RepositoryCacheInfo { + if (!obj || typeof obj !== 'object') { + return false; + } + const rec = obj as Record; + return typeof rec.workspacePath === 'string'; +} export class RepositoryCache { @@ -14,8 +26,8 @@ export class RepositoryCache { private static readonly MAX_REPO_ENTRIES = 30; // Max repositories tracked private static readonly MAX_FOLDER_ENTRIES = 10; // Max folders per repository - // Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> true). Only keys matter. - private readonly lru = new LRUCache>(RepositoryCache.MAX_REPO_ENTRIES); + // Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> RepositoryCacheInfo). + private readonly lru = new LRUCache>(RepositoryCache.MAX_REPO_ENTRIES); constructor(public readonly _globalState: Memento, private readonly _logger: LogOutputChannel) { this.load(); @@ -40,14 +52,16 @@ export class RepositoryCache { set(repoUrl: string, rootPath: string): void { let foldersLru = this.lru.get(repoUrl); if (!foldersLru) { - foldersLru = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + foldersLru = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); } const folderPathOrWorkspaceFile: string | undefined = this._findWorkspaceForRepo(rootPath); if (!folderPathOrWorkspaceFile) { return; } - foldersLru.set(folderPathOrWorkspaceFile, true); // touch entry + foldersLru.set(folderPathOrWorkspaceFile, { + workspacePath: folderPathOrWorkspaceFile + }); // touch entry this.lru.set(repoUrl, foldersLru); this.save(); } @@ -62,13 +76,7 @@ export class RepositoryCache { const sorted = [...this._workspaceFolders].sort((a, b) => b.uri.fsPath.length - a.uri.fsPath.length); for (const folder of sorted) { const folderPath = folder.uri.fsPath; - const relToFolder = path.relative(folderPath, rootPath); - if (relToFolder === '' || (!relToFolder.startsWith('..') && !path.isAbsolute(relToFolder))) { - folderPathOrWorkspaceFile = folderPath; - break; - } - const relFromFolder = path.relative(rootPath, folderPath); - if (relFromFolder === '' || (!relFromFolder.startsWith('..') && !path.isAbsolute(relFromFolder))) { + if (isDescendant(folderPath, rootPath) || isDescendant(rootPath, folderPath)) { folderPathOrWorkspaceFile = folderPath; break; } @@ -105,9 +113,9 @@ export class RepositoryCache { /** * We should possibly support converting between ssh remotes and http remotes. */ - get(repoUrl: string): string[] | undefined { + get(repoUrl: string): RepositoryCacheInfo[] | undefined { const inner = this.lru.get(repoUrl); - return inner ? Array.from(inner.keys()) : undefined; + return inner ? Array.from(inner.values()) : undefined; } delete(repoUrl: string, folderPathOrWorkspaceFile: string) { @@ -129,42 +137,43 @@ export class RepositoryCache { private load(): void { try { - const raw = this._globalState.get<[string, [string, true][]][]>(RepositoryCache.STORAGE_KEY); - if (Array.isArray(raw)) { - for (const [repo, storedFolders] of raw) { - if (typeof repo !== 'string' || !Array.isArray(storedFolders)) { + const raw = this._globalState.get<[string, [string, RepositoryCacheInfo][]][]>(RepositoryCache.STORAGE_KEY); + if (!Array.isArray(raw)) { + return; + } + for (const [repo, storedFolders] of raw) { + if (typeof repo !== 'string' || !Array.isArray(storedFolders)) { + continue; + } + const inner = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + for (const entry of storedFolders) { + if (!Array.isArray(entry) || entry.length !== 2) { continue; } - const inner = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); - for (const entry of storedFolders) { - let folderPath: string | undefined; - if (Array.isArray(entry) && entry.length === 2) { - const [workspaceFolder, _] = entry; - if (typeof workspaceFolder === 'string') { - folderPath = workspaceFolder; - } - } - if (folderPath) { - inner.set(folderPath, true); - } - } - if (inner.size) { - this.lru.set(repo, inner); + const [folderPath, info] = entry; + if (typeof folderPath !== 'string' || !isRepositoryCacheInfo(info)) { + continue; } + + inner.set(folderPath, info); + } + if (inner.size) { + this.lru.set(repo, inner); } } + } catch { this._logger.warn('[CachedRepositories][load] Failed to load cached repositories from global state.'); } } private save(): void { - // Serialize as [repoUrl, [folderPathOrWorkspaceFile, true][]] preserving outer LRU order. - const serialized: [string, [string, true][]][] = []; + // Serialize as [repoUrl, [folderPathOrWorkspaceFile, RepositoryCacheInfo][]] preserving outer LRU order. + const serialized: [string, [string, RepositoryCacheInfo][]][] = []; for (const [repo, inner] of this.lru) { - const folders: [string, true][] = []; - for (const [folder, _] of inner) { - folders.push([folder, true]); + const folders: [string, RepositoryCacheInfo][] = []; + for (const [folder, info] of inner) { + folders.push([folder, info]); } serialized.push([repo, folders]); } diff --git a/extensions/git/src/test/repositoryCache.test.ts b/extensions/git/src/test/repositoryCache.test.ts index f5a8e315817..8e289334fab 100644 --- a/extensions/git/src/test/repositoryCache.test.ts +++ b/extensions/git/src/test/repositoryCache.test.ts @@ -74,11 +74,13 @@ suite('RepositoryCache', () => { test('set & get basic', () => { const memento = new InMemoryMemento(); - const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: Uri.file('/workspace/repo'), name: 'workspace', index: 0 }]); - cache.set('https://example.com/repo.git', '/workspace/repo'); - const folders = cache.get('https://example.com/repo.git')!.map(folder => folder.replace(/\\/g, '/')); + const folder = Uri.file('/workspace/repo'); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: folder, name: 'workspace', index: 0 }]); + + cache.set('https://example.com/repo.git', folder.fsPath); + const folders = cache.get('https://example.com/repo.git')!.map(folder => folder.workspacePath); assert.ok(folders, 'folders should be defined'); - assert.deepStrictEqual(folders, ['/workspace/repo']); + assert.deepStrictEqual(folders, [folder.fsPath]); }); test('inner LRU capped at 10 entries', () => { @@ -90,13 +92,13 @@ suite('RepositoryCache', () => { const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); const repo = 'https://example.com/repo.git'; for (let i = 1; i <= 12; i++) { - cache.set(repo, `/ws/folder-${i.toString().padStart(2, '0')}`); + cache.set(repo, Uri.file(`/ws/folder-${i.toString().padStart(2, '0')}`).fsPath); } - const folders = cache.get(repo)!.map(folder => folder.replace(/\\/g, '/')); + const folders = cache.get(repo)!.map(folder => folder.workspacePath); assert.strictEqual(folders.length, 10, 'should only retain 10 most recent folders'); - assert.ok(!folders.includes('/ws/folder-01'), 'oldest folder-01 should be evicted'); - assert.ok(!folders.includes('/ws/folder-02'), 'second oldest folder-02 should be evicted'); - assert.ok(folders.includes('/ws/folder-12'), 'latest folder should be present'); + assert.ok(!folders.includes(Uri.file('/ws/folder-01').fsPath), 'oldest folder-01 should be evicted'); + assert.ok(!folders.includes(Uri.file('/ws/folder-02').fsPath), 'second oldest folder-02 should be evicted'); + assert.ok(folders.includes(Uri.file('/ws/folder-12').fsPath), 'latest folder should be present'); }); test('outer LRU capped at 30 repos', () => { @@ -108,7 +110,7 @@ suite('RepositoryCache', () => { const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); for (let i = 1; i <= 35; i++) { const repo = `https://example.com/r${i}.git`; - cache.set(repo, `/ws/r${i}`); + cache.set(repo, Uri.file(`/ws/r${i}`).fsPath); } assert.strictEqual(cache.get('https://example.com/r1.git'), undefined, 'oldest repo should be trimmed'); assert.ok(cache.get('https://example.com/r35.git'), 'newest repo should remain'); @@ -126,9 +128,9 @@ suite('RepositoryCache', () => { const b = Uri.file('/ws/b').fsPath; cache.set(repo, a); cache.set(repo, b); - assert.deepStrictEqual(new Set(cache.get(repo)!), new Set([a, b])); + assert.deepStrictEqual(new Set(cache.get(repo)?.map(folder => folder.workspacePath)), new Set([a, b])); cache.delete(repo, a); - assert.deepStrictEqual(cache.get(repo)!, [b]); + assert.deepStrictEqual(cache.get(repo)!.map(folder => folder.workspacePath), [b]); cache.delete(repo, b); assert.strictEqual(cache.get(repo), undefined, 'repo should be pruned when last folder removed'); }); From 5156ca62bfb0708d6beb5277b21363dc8386d05b Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Wed, 22 Oct 2025 12:08:07 -0700 Subject: [PATCH 1486/4355] Specify hot key for 'Select folder' button on the Open Folder dialog (#272554) * Specify hot key for 'Select folder' button on the Open Folder dialog * PR feedback --- .../electron-main/dialogMainService.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/dialogs/electron-main/dialogMainService.ts b/src/vs/platform/dialogs/electron-main/dialogMainService.ts index 833a51ce90d..fe90ca4d9f0 100644 --- a/src/vs/platform/dialogs/electron-main/dialogMainService.ts +++ b/src/vs/platform/dialogs/electron-main/dialogMainService.ts @@ -9,7 +9,7 @@ import { hash } from '../../../base/common/hash.js'; import { mnemonicButtonLabel } from '../../../base/common/labels.js'; import { Disposable, dispose, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { normalizeNFC } from '../../../base/common/normalization.js'; -import { isMacintosh } from '../../../base/common/platform.js'; +import { isMacintosh, isWindows } from '../../../base/common/platform.js'; import { Promises } from '../../../base/node/pfs.js'; import { localize } from '../../../nls.js'; import { INativeOpenDialogOptions, massageMessageBoxOptions } from '../common/dialogs.js'; @@ -62,7 +62,23 @@ export class DialogMainService implements IDialogMainService { } pickFolder(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { - return this.doPick({ ...options, pickFolders: true, title: localize('openFolder', "Open Folder") }, window); + let optionsInternal: IInternalNativeOpenDialogOptions = { + ...options, + pickFolders: true, + title: localize('openFolder', "Open Folder") + }; + + if (isWindows) { + // Due to Windows/Electron issue the labels on Open Folder dialog have no hot keys. + // We can fix this here for the button label, but some other labels remain inaccessible. + // See https://github.com/electron/electron/issues/48631 for more info. + optionsInternal = { + ...optionsInternal, + buttonLabel: mnemonicButtonLabel(localize({ key: 'selectFolder', comment: ['&& denotes a mnemonic'] }, "&&Select folder")).withMnemonic + }; + } + + return this.doPick(optionsInternal, window); } pickFile(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { From 130489ebcd8d263cfb37c348f966cfec0925dbaa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 21:15:49 +0200 Subject: [PATCH 1487/4355] agent sessions - fix list height (#272756) --- .../agentSessions/agentSessionViewModel.ts | 9 ++++++--- .../browser/agentSessions/agentSessionsView.ts | 15 +++++++-------- .../agentSessions/media/agentsessionsview.css | 1 + .../agentSessions/media/agentsessionsviewer.css | 4 ++++ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 7ea9e3ff9e0..e7b586a853a 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -79,7 +79,7 @@ export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSe //#endregion -const INCLUDE_HISTORY = false; // TODO@bpasero figure out how to best support history +const INCLUDE_HISTORY = false; export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel { readonly sessions: IAgentSessionViewModel[] = []; @@ -172,14 +172,17 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions } if (INCLUDE_HISTORY && provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE) { - for (const history of await this.chatService.getHistory()) { // TODO@bpasero this needs to come from the local provider + // TODO@bpasero this needs to come from the local provider: + // - do we want to show history or not and how + // - can we support all properties including `startTime` properly + for (const history of await this.chatService.getHistory()) { newSessions.push({ id: history.sessionId, resource: ChatSessionUri.forSession(LOCAL_AGENT_SESSION_TYPE, history.sessionId), label: history.title, provider: provider, timing: { - startTime: history.lastMessageDate ?? Date.now() /* TODO@bpasero BAD */ + startTime: history.lastMessageDate ?? Date.now() }, description: new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), }); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 22988c16368..0c8be96b59e 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -53,9 +53,6 @@ export class AgentSessionsView extends FilterViewPane { private static FILTER_FOCUS_CONTEXT_KEY = new RawContextKey('agentSessionsViewFilterFocus', false); - private list: WorkbenchCompressibleAsyncDataTree | undefined; - private filter: AgentSessionsFilter | undefined; - private sessionsViewModel: IAgentSessionsViewModel | undefined; constructor( @@ -255,14 +252,18 @@ export class AgentSessionsView extends FilterViewPane { //#region Sessions List + private listContainer: HTMLElement | undefined; + private list: WorkbenchCompressibleAsyncDataTree | undefined; + private filter: AgentSessionsFilter | undefined; + private createList(container: HTMLElement): void { - const listContainer = append(container, $('.agent-sessions-viewer')); + this.listContainer = append(container, $('.agent-sessions-viewer')); this.filter = this._register(new AgentSessionsFilter()); this.list = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'AgentSessionsView', - listContainer, + this.listContainer, new AgentSessionsListDelegate(), new AgentSessionsCompressionDelegate(), [ @@ -310,9 +311,7 @@ export class AgentSessionsView extends FilterViewPane { let treeHeight = height; treeHeight -= this.filterContainer?.offsetHeight ?? 0; - if (this.newSessionContainer) { - treeHeight -= this.newSessionContainer.offsetHeight; - } + treeHeight -= this.newSessionContainer?.offsetHeight ?? 0; this.list?.layout(treeHeight, width); } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css index bb8b4742503..b59a6d0c133 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css @@ -10,6 +10,7 @@ .agent-sessions-viewer { flex: 1 1 auto !important; + min-height: 0; } .viewpane-filter-container, diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css index 51a1a5cc087..567d786f74a 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -73,6 +73,10 @@ overflow: hidden; } + .agent-session-timestamp { + font-size: 11px; + } + /* #region Diff Styling */ .agent-session-diff { From 81a9252210bc64a201449a75995c708aa7a5db96 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:18:51 +0200 Subject: [PATCH 1488/4355] SCM - more repositories view polish (#272755) --- .../contrib/scm/browser/media/scm.css | 8 +++- .../scm/browser/scmRepositoryRenderer.ts | 42 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index ec19d55ee61..d963780a537 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -74,8 +74,14 @@ align-items: center; } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) { +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1), +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(2) { min-width: 20px; + + .action-label > span:nth-child(1), + .action-label > span:nth-child(2) { + padding-right: 2px; + } } .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label, diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 400a449eaa1..505c482f476 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -26,6 +26,10 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { shorten } from '../../../../base/common/labels.js'; +import { dirname } from '../../../../base/common/resources.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -72,9 +76,11 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer r.provider.rootUri !== undefined && + this.uriIdentityService.extUri.isEqual(r.provider.rootUri, repository.provider.rootUri)); + + const repositoriesWithSameName = this.scmViewService.repositories + .filter(r => r.provider.rootUri !== undefined && + r.provider.name === repository.provider.name); + + if (repositoriesWithRootUri.length > 1) { + description = repository.provider.label; + } else if (repositoriesWithSameName.length > 1) { + const repositoryIndex = repositoriesWithSameName.findIndex(r => r === repository); + const shortDescription = shorten(repositoriesWithSameName + .map(r => this.labelService.getUriLabel(dirname(r.provider.rootUri!), { relative: true }))); + + description = shortDescription[repositoryIndex]; + } } + const title = repository.provider.rootUri + ? `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` + : repository.provider.label; + + templateData.label.setLabel(repository.provider.name, description, { title }); + + // Label minimum width set to fit the three default actions (checkout, sync, more actions) + // 170px (activity bar) - 30px (indentation) - 18px (icon) - 3 x 24px (actions) - 12px (padding) - 1px (border) + templateData.label.element.style.minWidth = '37px'; + let statusPrimaryActions: IAction[] = []; let menuPrimaryActions: IAction[] = []; let menuSecondaryActions: IAction[] = []; From ffce48ec2526e5adf4b581b4a21a1ec067cbe7dd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 22 Oct 2025 12:29:25 -0700 Subject: [PATCH 1489/4355] Enable extension view contributions to agentSessions view (#272606) * Enable extension view contributions to agentSessions view To enable a welcome view from the extension when certain extension is not installed This is a bit more complicated than expected, it has these parts - Enable agentSessions view contribution behind api proposal - Change workbench contrib to activate earlier, because the view container has to exist by the time the view contribution is processed - Instead of dynamically registering/deregistering the view container, always register it, set `hideIfEmpty`, and set a when clause on all views in it, including extension-contributed views - This avoids an error when the extension contrib is processed, if we don't want to be showing the view container * comment * Register views after viewcontainer * Cleanup * fix --- .../api/browser/viewsExtensionPoint.ts | 37 +++- .../contrib/chat/browser/chat.contribution.ts | 18 +- .../chatSessions/view/chatSessionsView.ts | 209 +++++++----------- .../contrib/chat/common/chatContextKeys.ts | 7 +- 4 files changed, 125 insertions(+), 146 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 3c2ba18b1b1..1a55cadb237 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -3,33 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { MarkdownString } from '../../../base/common/htmlContent.js'; import { IJSONSchema } from '../../../base/common/jsonSchema.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; import * as resources from '../../../base/common/resources.js'; import { isFalsyOrWhitespace } from '../../../base/common/strings.js'; +import { ThemeIcon } from '../../../base/common/themables.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier, ExtensionIdentifierSet, IExtensionDescription, IExtensionManifest } from '../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../platform/instantiation/common/descriptors.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../platform/log/common/log.js'; import { Registry } from '../../../platform/registry/common/platform.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { Extensions as ViewletExtensions, PaneCompositeRegistry } from '../../browser/panecomposite.js'; +import { PaneCompositeRegistry, Extensions as ViewletExtensions } from '../../browser/panecomposite.js'; import { CustomTreeView, TreeViewPane } from '../../browser/parts/views/treeView.js'; import { ViewPaneContainer } from '../../browser/parts/views/viewPaneContainer.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../common/contributions.js'; -import { Extensions as ViewContainerExtensions, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from '../../common/views.js'; +import { ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../common/views.js'; +import { ChatContextKeyExprs } from '../../contrib/chat/common/chatContextKeys.js'; +import { AGENT_SESSIONS_VIEWLET_ID as CHAT_SESSIONS } from '../../contrib/chat/common/constants.js'; import { VIEWLET_ID as DEBUG } from '../../contrib/debug/common/debug.js'; import { VIEWLET_ID as EXPLORER } from '../../contrib/files/common/files.js'; import { VIEWLET_ID as REMOTE } from '../../contrib/remote/browser/remoteExplorer.js'; import { VIEWLET_ID as SCM } from '../../contrib/scm/common/scm.js'; import { WebviewViewPane } from '../../contrib/webviewView/browser/webviewViewPane.js'; +import { Extensions as ExtensionFeaturesRegistryExtensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from '../../services/extensionManagement/common/extensionFeatures.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from '../../services/extensions/common/extensionsRegistry.js'; -import { ILogService } from '../../../platform/log/common/log.js'; -import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions as ExtensionFeaturesRegistryExtensions } from '../../services/extensionManagement/common/extensionFeatures.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; -import { MarkdownString } from '../../../base/common/htmlContent.js'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -234,10 +236,16 @@ const viewsContribution: IJSONSchema = { default: [] }, 'remote': { - description: localize('views.remote', "Contributes views to Remote container in the Activity bar. To contribute to this container, enableProposedApi needs to be turned on"), + description: localize('views.remote', "Contributes views to Remote container in the Activity bar. To contribute to this container, the 'contribViewsRemote' API proposal must be enabled."), type: 'array', items: remoteViewDescriptor, default: [] + }, + 'agentSessions': { + description: localize('views.agentSessions', "Contributes views to Agent Sessions container in the Activity bar. To contribute to this container, the 'chatSessionsProvider' API proposal must be enabled."), + type: 'array', + items: viewDescriptor, + default: [] } }, additionalProperties: { @@ -457,6 +465,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return; } + if (key === 'agentSessions' && !isProposedApiEnabled(extension.description, 'chatSessionsProvider')) { + collector.warn(localize('RequiresChatSessionsProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"chatSessionsProvider\"]'.", key)); + return; + } + const viewContainer = this.getViewContainer(key); if (!viewContainer) { collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", key)); @@ -509,12 +522,17 @@ class ViewsExtensionHandler implements IWorkbenchContribution { accessibilityHelpContent = new MarkdownString(item.accessibilityHelpContent); } + let when = ContextKeyExpr.deserialize(item.when); + if (key === 'agentSessions') { + when = ContextKeyExpr.and(when, ChatContextKeyExprs.agentViewWhen); + } + const viewDescriptor: ICustomViewDescriptor = { type: type, ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane), id: item.id, name: { value: item.name, original: item.name }, - when: ContextKeyExpr.deserialize(item.when), + when, containerIcon: icon || viewContainer?.icon, containerTitle: item.contextualTitle || (viewContainer && (typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value)), canToggleVisibility: true, @@ -626,6 +644,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { case 'debug': return this.viewContainersRegistry.get(DEBUG); case 'scm': return this.viewContainersRegistry.get(SCM); case 'remote': return this.viewContainersRegistry.get(REMOTE); + case 'agentSessions': return this.viewContainersRegistry.get(CHAT_SESSIONS); default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 20ba0d97eae..afa7240fdf1 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -9,8 +9,8 @@ import { MarkdownString, isMarkdownString } from '../../../../base/common/htmlCo import { Disposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isMacintosh } from '../../../../base/common/platform.js'; +import { PolicyCategory } from '../../../../base/common/policy.js'; import { assertDefined } from '../../../../base/common/types.js'; -import product from '../../../../platform/product/common/product.js'; import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; @@ -21,12 +21,14 @@ import { SyncDescriptor } from '../../../../platform/instantiation/common/descri import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { McpAccessValue, McpAutoStartValue, mcpAccessConfig, mcpAutoStartConfig, mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; +import product from '../../../../platform/product/common/product.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { AddConfigurationType, AssistedTypes } from '../../mcp/browser/mcpCommandsAddConfiguration.js'; import { allDiscoverySources, discoverySourceSettingsLabel, mcpDiscoverySection, mcpServerSamplingSection } from '../../mcp/common/mcpConfiguration.js'; @@ -34,7 +36,6 @@ import { ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAge import { CodeMapperService, ICodeMapperService } from '../common/chatCodeMapperService.js'; import '../common/chatColors.js'; import { IChatEditingService } from '../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IChatLayoutService } from '../common/chatLayoutService.js'; import { ChatModeService, IChatModeService } from '../common/chatModes.js'; import { ChatResponseResourceFileSystemProvider } from '../common/chatResponseResourceFileSystemProvider.js'; @@ -50,20 +51,19 @@ import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } f import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; +import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js'; -import { INSTRUCTIONS_DOCUMENTATION_URL, AGENT_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; +import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../common/promptSyntax/service/promptsService.js'; import { PromptsService } from '../common/promptSyntax/service/promptsServiceImpl.js'; import { LanguageModelToolsExtensionPointHandler } from '../common/tools/languageModelToolsContribution.js'; -import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; import { IVoiceChatService, VoiceChatService } from '../common/voiceChatService.js'; import { registerChatAccessibilityActions } from './actions/chatAccessibilityActions.js'; import { AgentChatAccessibilityHelp, EditsChatAccessibilityHelp, PanelChatAccessibilityHelp, QuickChatAccessibilityHelp } from './actions/chatAccessibilityHelp.js'; import { ACTION_ID_NEW_CHAT, CopilotTitleBarMenuRendering, registerChatActions } from './actions/chatActions.js'; -import { registerNewChatActions } from './actions/chatNewActions.js'; import { CodeBlockActionRendering, registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from './actions/chatCodeblockActions.js'; import { ChatContextContributions } from './actions/chatContext.js'; import { registerChatContextActions } from './actions/chatContextActions.js'; @@ -72,10 +72,11 @@ import { registerChatDeveloperActions } from './actions/chatDeveloperActions.js' import { ChatSubmitAction, registerChatExecuteActions } from './actions/chatExecuteActions.js'; import { registerChatFileTreeActions } from './actions/chatFileTreeActions.js'; import { ChatGettingStartedContribution } from './actions/chatGettingStarted.js'; -import { registerChatPromptNavigationActions } from './actions/chatPromptNavigationActions.js'; import { registerChatExportActions } from './actions/chatImportExport.js'; import { registerLanguageModelActions } from './actions/chatLanguageModelActions.js'; import { registerMoveActions } from './actions/chatMoveActions.js'; +import { registerNewChatActions } from './actions/chatNewActions.js'; +import { registerChatPromptNavigationActions } from './actions/chatPromptNavigationActions.js'; import { registerQuickChatActions } from './actions/chatQuickInputActions.js'; import { ChatSessionsGettingStartedAction, DeleteChatSessionAction, OpenChatSessionInNewEditorGroupAction, OpenChatSessionInNewWindowAction, OpenChatSessionInSidebarAction, RenameChatSessionAction, ToggleChatSessionsDescriptionDisplayAction } from './actions/chatSessionActions.js'; import { registerChatTitleActions } from './actions/chatTitleActions.js'; @@ -104,6 +105,7 @@ import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatPart import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; import { QuickChatService } from './chatQuick.js'; import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; +import { ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js'; import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; import { ChatVariablesService } from './chatVariables.js'; @@ -123,8 +125,6 @@ import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; -import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -983,7 +983,7 @@ registerWorkbenchContribution2(ChatTransferContribution.ID, ChatTransferContribu registerWorkbenchContribution2(ChatContextContributions.ID, ChatContextContributions, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatResponseResourceFileSystemProvider.ID, ChatResponseResourceFileSystemProvider, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(PromptUrlHandler.ID, PromptUrlHandler, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(ChatSessionsView.ID, ChatSessionsView, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ChatSessionsViewContrib.ID, ChatSessionsViewContrib, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(UserToolSetsContributions.ID, UserToolSetsContributions, WorkbenchPhase.Eventually); registerWorkbenchContribution2(PromptLanguageFeaturesProvider.ID, PromptLanguageFeaturesProvider, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 5fbd70753b3..fe75173cab4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -20,30 +20,29 @@ import { IThemeService } from '../../../../../../platform/theme/common/themeServ import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { ViewPaneContainer } from '../../../../../browser/parts/views/viewPaneContainer.js'; import { IWorkbenchContribution } from '../../../../../common/contributions.js'; -import { ViewContainer, IViewContainersRegistry, Extensions, ViewContainerLocation, IViewsRegistry, IViewDescriptor, IViewDescriptorService } from '../../../../../common/views.js'; -import { IChatEntitlementService } from '../../../../../services/chat/common/chatEntitlementService.js'; +import { Extensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorService, IViewsRegistry, ViewContainerLocation } from '../../../../../common/views.js'; import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; -import { IChatSessionsService, IChatSessionItemProvider, IChatSessionsExtensionPoint } from '../../../common/chatSessionsService.js'; -import { ChatConfiguration, AGENT_SESSIONS_VIEWLET_ID } from '../../../common/constants.js'; +import { ChatContextKeyExprs } from '../../../common/chatContextKeys.js'; +import { IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../../../common/chatSessionsService.js'; +import { AGENT_SESSIONS_VIEWLET_ID } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { SessionsViewPane } from './sessionsViewPane.js'; -export class ChatSessionsView extends Disposable implements IWorkbenchContribution { +export class ChatSessionsViewContrib extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatSessions'; - private isViewContainerRegistered = false; private localProvider: LocalChatSessionsProvider | undefined; private readonly sessionTracker: ChatSessionTracker; - private viewContainer: ViewContainer | undefined; + private readonly registeredViewDescriptors: Map = new Map(); constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + @ILogService private readonly logService: ILogService, + @IProductService private readonly productService: IProductService, ) { super(); @@ -56,16 +55,15 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi this._register(this.chatSessionsService.registerChatSessionItemProvider(this.localProvider)); // Initial check - this.updateViewContainerRegistration(); + this.registerViewContainer(); + void this.updateViewRegistration(); - // Listen for configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatConfiguration.AgentSessionsViewLocation)) { - this.updateViewContainerRegistration(); - } + this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { + void this.updateViewRegistration(); })); - this._register(this.chatEntitlementService.onDidChangeSentiment(e => { - this.updateViewContainerRegistration(); + + this._register(this.chatSessionsService.onDidChangeAvailability(() => { + void this.updateViewRegistration(); })); } @@ -75,127 +73,22 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi })); } - private updateViewContainerRegistration(): void { - const location = this.configurationService.getValue(ChatConfiguration.AgentSessionsViewLocation); - const sentiment = this.chatEntitlementService.sentiment; - if (sentiment.disabled || sentiment.hidden || (location !== 'view' && this.isViewContainerRegistered)) { - this.deregisterViewContainer(); - } else if (location === 'view' && !this.isViewContainerRegistered) { - this.registerViewContainer(); - } - } - private registerViewContainer(): void { - if (this.isViewContainerRegistered) { - return; - } - - this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: AGENT_SESSIONS_VIEWLET_ID, title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), - hideIfEmpty: false, + hideIfEmpty: true, icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); - this.isViewContainerRegistered = true; - } - - private deregisterViewContainer(): void { - if (this.viewContainer) { - const allViews = Registry.as(Extensions.ViewsRegistry).getViews(this.viewContainer); - if (allViews.length > 0) { - Registry.as(Extensions.ViewsRegistry).deregisterViews(allViews, this.viewContainer); - } - - Registry.as(Extensions.ViewContainersRegistry).deregisterViewContainer(this.viewContainer); - this.viewContainer = undefined; - this.isViewContainerRegistered = false; - } - } -} - -// Chat sessions container -class ChatSessionsViewPaneContainer extends ViewPaneContainer { - private registeredViewDescriptors: Map = new Map(); - - constructor( - private readonly sessionTracker: ChatSessionTracker, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService contextMenuService: IContextMenuService, - @ITelemetryService telemetryService: ITelemetryService, - @IExtensionService extensionService: IExtensionService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @ILogService logService: ILogService, - @IProductService private readonly productService: IProductService, - @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - ) { - super( - AGENT_SESSIONS_VIEWLET_ID, - { - mergeViewWithContainerWhenSingleView: false, - }, - instantiationService, - configurationService, - layoutService, - contextMenuService, - telemetryService, - extensionService, - themeService, - storageService, - contextService, - viewDescriptorService, - logService - ); - - this.updateViewRegistration(); - - // Listen for provider changes and register/unregister views accordingly - this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { - this.updateViewRegistration(); - })); - - // Listen for session items changes and refresh the appropriate provider tree - this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => { - this.refreshProviderTree(chatSessionType); - })); - - // Listen for contribution availability changes and update view registration - this._register(this.chatSessionsService.onDidChangeAvailability(() => { - this.updateViewRegistration(); - })); - } - - override getTitle(): string { - const title = nls.localize('chat.agent.sessions.title', "Agent Sessions"); - return title; } private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); } - private refreshProviderTree(chatSessionType: string): void { - // Find the provider with the matching chatSessionType - const providers = this.getAllChatSessionItemProviders(); - const targetProvider = providers.find(provider => provider.chatSessionType === chatSessionType); - - if (targetProvider) { - // Find the corresponding view and refresh its tree - const viewId = `${AGENT_SESSIONS_VIEWLET_ID}.${chatSessionType}`; - const view = this.getView(viewId) as SessionsViewPane | undefined; - if (view) { - view.refreshTree(); - } - } - } - private async updateViewRegistration(): Promise { // prepare all chat session providers const contributions = this.chatSessionsService.getAllChatSessionContributions(); @@ -278,12 +171,12 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { // Register views in priority order: local, history, then alphabetically sorted others const orderedProviders = [ - ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Agent', baseOrder: 0 }] : []), - ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: undefined }] : []), + ...(localProvider ? [{ provider: localProvider, displayName: 'Local Chat Agent', baseOrder: 0, when: ChatContextKeyExprs.agentViewWhen }] : []), + ...(historyProvider ? [{ provider: historyProvider, displayName: 'History', baseOrder: 1, when: ChatContextKeyExprs.agentViewWhen }] : []), ...providersWithDisplayNames.map((item, index) => ({ ...item, baseOrder: 2 + index, // Start from 2 for other providers - when: undefined, + when: ChatContextKeyExprs.agentViewWhen, })) ]; @@ -331,6 +224,7 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { canMoveView: true, order: 1000, collapsed: !!otherProviders.length, + when: ChatContextKeyExprs.agentViewWhen }; viewDescriptorsToRegister.push(gettingStartedDescriptor); this.registeredViewDescriptors.set('gettingStarted', gettingStartedDescriptor); @@ -356,3 +250,64 @@ class ChatSessionsViewPaneContainer extends ViewPaneContainer { super.dispose(); } } + +// Chat sessions container +class ChatSessionsViewPaneContainer extends ViewPaneContainer { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IContextMenuService contextMenuService: IContextMenuService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionService extensionService: IExtensionService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @ILogService logService: ILogService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + ) { + super( + AGENT_SESSIONS_VIEWLET_ID, + { + mergeViewWithContainerWhenSingleView: false, + }, + instantiationService, + configurationService, + layoutService, + contextMenuService, + telemetryService, + extensionService, + themeService, + storageService, + contextService, + viewDescriptorService, + logService + ); + + // Listen for session items changes and refresh the appropriate provider tree + this._register(this.chatSessionsService.onDidChangeSessionItems((chatSessionType) => { + this.refreshProviderTree(chatSessionType); + })); + } + + override getTitle(): string { + const title = nls.localize('chat.agent.sessions.title', "Agent Sessions"); + return title; + } + + private refreshProviderTree(chatSessionType: string): void { + // Find the provider with the matching chatSessionType + const providers = Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); + const targetProvider = providers.find(provider => provider.chatSessionType === chatSessionType); + + if (targetProvider) { + // Find the corresponding view and refresh its tree + const viewId = `${AGENT_SESSIONS_VIEWLET_ID}.${chatSessionType}`; + const view = this.getView(viewId) as SessionsViewPane | undefined; + if (view) { + view.refreshTree(); + } + } + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 49c2317cad2..f51121a4bf6 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -9,7 +9,7 @@ import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys import { RemoteNameContext } from '../../../common/contextkeys.js'; import { ViewContainerLocation } from '../../../common/views.js'; import { ChatEntitlementContextKeys } from '../../../services/chat/common/chatEntitlementService.js'; -import { ChatAgentLocation, ChatModeKind } from './constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; export namespace ChatContextKeys { export const responseVote = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); @@ -110,4 +110,9 @@ export namespace ChatContextKeyExprs { ChatContextKeys.Setup.installed.negate(), ChatContextKeys.Entitlement.canSignUp ); + + export const agentViewWhen = ContextKeyExpr.and( + ChatEntitlementContextKeys.Setup.hidden.negate(), + ChatEntitlementContextKeys.Setup.disabled.negate(), + ContextKeyExpr.equals(`config.${ChatConfiguration.AgentSessionsViewLocation}`, 'view')); } From 475319107669d1e76e583928e4cc0e7543d066e6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 Oct 2025 21:31:17 +0200 Subject: [PATCH 1490/4355] agent sessions - improve focus handling (#272757) * agent sessions - improve focus handling * Update src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/browser/parts/views/viewPane.ts | 5 ++++- .../chat/browser/agentSessions/agentSessionsView.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 4eb9f243c31..2f3f9b52751 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -762,7 +762,7 @@ export abstract class FilterViewPane extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService, accessibleViewService); const childInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); this.filterWidget = this._register(childInstantiationService.createInstance(FilterWidget, options.filterOptions)); - this._register(this.filterWidget.onDidAcceptFilterText(() => this.focus())); + this._register(this.filterWidget.onDidAcceptFilterText(() => this.focusBodyContent())); } override getFilterWidget(): FilterWidget { @@ -802,6 +802,9 @@ export abstract class FilterViewPane extends ViewPane { protected abstract layoutBodyContent(height: number, width: number): void; + protected focusBodyContent(): void { + this.focus(); + } } export interface IViewPaneLocationColors { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 0c8be96b59e..e5ed65bdeba 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -323,6 +323,14 @@ export class AgentSessionsView extends FilterViewPane { override focus(): void { super.focus(); + if (this.list?.getFocus().length) { + this.list.domFocus(); + } else { + this.filterWidget.focus(); + } + } + + protected override focusBodyContent(): void { this.list?.domFocus(); } } From 2e77c17d50b9e2bc71c5e41b9b0c33ab42f32a83 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 22 Oct 2025 12:34:25 -0700 Subject: [PATCH 1491/4355] Add setting for chat session experiment (#272759) --- .../contrib/chat/browser/chatSessions.contribution.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 81647bf3b0e..2851afea909 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -12,6 +12,7 @@ import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -25,7 +26,7 @@ import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IC import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSessionUri } from '../common/chatUri.js'; -import { ChatAgentLocation, ChatModeKind, AGENT_SESSIONS_VIEWLET_ID } from '../common/constants.js'; +import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; import { NEW_CHAT_SESSION_ACTION_ID } from './chatSessions/common.js'; @@ -228,6 +229,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ @IExtensionService private readonly _extensionService: IExtensionService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IMenuService private readonly _menuService: IMenuService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this._register(extensionPoint.setHandler(extensions => { @@ -239,6 +241,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ continue; } for (const contribution of ext.value) { + if (contribution.type === 'openai-codex' && !this._configurationService.getValue('chat.experimental.codex.enabled')) { + continue; + } + const c: IChatSessionsExtensionPoint = { type: contribution.type, name: contribution.name, From 30f6d3fd160d504416f35641e385723b7e7fc3a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:55:14 -0700 Subject: [PATCH 1492/4355] Fix Linux getCopilotProfile test --- .../chatAgentTools/test/browser/runInTerminalTool.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts index 0102e1b7ca4..909f65a94df 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts @@ -974,7 +974,7 @@ suite('RunInTerminalTool', () => { const result = await runInTerminalTool.profileFetcher.getCopilotProfile(); strictEqual(typeof result, 'object'); - strictEqual((result as ITerminalProfile).path, 'pwsh'); + strictEqual((result as ITerminalProfile).path, 'bash'); }); }); }); From a17bde11ef266cd7741eee3fb2c18388e4e64642 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:57:53 -0700 Subject: [PATCH 1493/4355] Fix webviews not saving icon path on restore --- .../workbench/contrib/webviewPanel/browser/webviewEditorInput.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts index f957fe35955..1aaa7fa1b9a 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts @@ -68,6 +68,7 @@ export class WebviewInput extends EditorInput { this.providedId = init.providedId; this._name = init.name; + this._iconPath = init.iconPath; this._webview = webview; this._register(_themeService.onDidColorThemeChange(() => { From 2f3cdd58a665d18c4de942253540c4244620ca16 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 22 Oct 2025 13:13:46 -0700 Subject: [PATCH 1494/4355] tools: adopt postapproval in the fetch tool - Trusted domains won't require preapproval any longer - All #fetch'es will ask for postapproval - Cleanup some re-rendering logic now that we have an observable `state`, previously the fetch tool UI did not show the postapproval. --- .../abstractToolConfirmationSubPart.ts | 9 ---- .../chatExtensionsInstallToolSubPart.ts | 14 ++---- .../chatInputOutputMarkdownProgressPart.ts | 9 ---- .../chatTerminalToolConfirmationSubPart.ts | 21 +++----- .../chatToolConfirmationSubPart.ts | 5 -- .../chatToolInvocationPart.ts | 17 +++++-- .../chatToolInvocationSubPart.ts | 10 ---- .../chatToolPostExecuteConfirmationPart.ts | 5 -- .../electron-browser/tools/fetchPageTool.ts | 23 +++++---- .../electron-browser/fetchPageTool.test.ts | 49 +++++++++++++------ 10 files changed, 73 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts index a6dda0631bd..175e88021e9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts @@ -7,7 +7,6 @@ import { Separator } from '../../../../../../base/common/actions.js'; import { assertNever } from '../../../../../../base/common/assert.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { autorunSelfDisposable, IReader } from '../../../../../../base/common/observable.js'; import { localize } from '../../../../../../nls.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -138,13 +137,6 @@ export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvoca this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(toDisposable(() => hasToolConfirmation.reset())); - this._register(autorunSelfDisposable(reader => { - if (this.shouldDismiss(toolInvocation, reader)) { - reader.dispose(); - hasToolConfirmation.reset(); - this._onNeedsRerender.fire(); - } - })); this.domNode = confirmWidget.domNode; } @@ -159,5 +151,4 @@ export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvoca protected abstract createContentElement(): HTMLElement | string; protected abstract getTitle(): string; - protected abstract shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts index 621a7863918..b6dd38169b4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts @@ -5,7 +5,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { Emitter } from '../../../../../../base/common/event.js'; -import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; +import { toDisposable } from '../../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../../nls.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IExtensionManagementService } from '../../../../../../platform/extensionManagement/common/extensionManagement.js'; @@ -17,7 +17,7 @@ import { ConfirmedReason, IChatToolInvocation, ToolConfirmKind } from '../../../ import { CancelChatActionId } from '../../actions/chatExecuteActions.js'; import { AcceptToolConfirmationActionId } from '../../actions/chatToolActions.js'; import { IChatCodeBlockInfo, IChatWidgetService } from '../../chat.js'; -import { IChatConfirmationButton, ChatConfirmationWidget } from '../chatConfirmationWidget.js'; +import { ChatConfirmationWidget, IChatConfirmationButton } from '../chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatExtensionsContentPart } from '../chatExtensionsContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; @@ -88,13 +88,9 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo IChatToolInvocation.confirmWith(toolInvocation, button.data); chatWidgetService.getWidgetBySessionId(context.element.sessionId)?.focusInput(); })); - this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader)) { - reader.dispose(); - ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); - this._onNeedsRerender.fire(); - } - })); + const hasToolConfirmationKey = ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService); + hasToolConfirmationKey.set(true); + this._register(toDisposable(() => hasToolConfirmationKey.reset())); const disposable = this._register(extensionManagementService.onInstallExtension(e => { if (extensionsContent.extensions.some(id => areSameExtensions({ id }, e.identifier))) { disposable.dispose(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index c84eb0fab14..9d187b52e5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -136,15 +136,6 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS this._register(collapsibleListPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); this._register(toDisposable(() => ChatInputOutputMarkdownProgressPart._expandedByDefault.set(toolInvocation, collapsibleListPart.expanded))); - // Make sure to rerender back into a confirmation when reviewing output - this._register(autorun(reader => { - if (toolInvocation.kind === 'toolInvocation') { - if (toolInvocation.state.read(reader).type === IChatToolInvocation.StateKind.WaitingForPostApproval) { - this._onNeedsRerender.fire(); - } - } - })); - const progressObservable = toolInvocation.kind === 'toolInvocation' ? toolInvocation.state.map((s, r) => s.type === IChatToolInvocation.StateKind.Executing ? s.progress.read(r) : undefined) : undefined; const progressBar = new Lazy(() => this._register(new ProgressBar(collapsibleListPart.domNode))); if (progressObservable) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 968d74e2564..2a1fa388f0e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { append, h } from '../../../../../../base/browser/dom.js'; +import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; import { Separator } from '../../../../../../base/common/actions.js'; import { asArray } from '../../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { ErrorNoTelemetry } from '../../../../../../base/common/errors.js'; import { createCommandUri, MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { thenIfNotDisposed, thenRegisterOrDispose } from '../../../../../../base/common/lifecycle.js'; +import { thenIfNotDisposed, thenRegisterOrDispose, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../../base/common/network.js'; import Severity from '../../../../../../base/common/severity.js'; import { isObject } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; -import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; @@ -27,6 +27,7 @@ import { IDialogService } from '../../../../../../platform/dialogs/common/dialog import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { IPreferencesService } from '../../../../../services/preferences/common/preferences.js'; import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; @@ -40,10 +41,8 @@ import { ICodeBlockRenderOptions } from '../../codeBlockPart.js'; import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; -import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; -import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; -import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; +import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; export const enum TerminalToolConfirmationStorageKeys { TerminalAutoApproveWarningAccepted = 'chat.tools.terminal.autoApprove.warningAccepted' @@ -204,7 +203,10 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS this._appendMarkdownPart(elements.disclaimer, disclaimer, codeBlockRenderOptions); } - ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService).set(true); + const hasToolConfirmationKey = ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService); + hasToolConfirmationKey.set(true); + this._register(toDisposable(() => hasToolConfirmationKey.reset())); + this._register(confirmWidget.onDidClick(async button => { let doComplete = true; const data = button.data; @@ -306,13 +308,6 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS } })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); - this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader)) { - reader.dispose(); - ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService).set(false); - this._onNeedsRerender.fire(); - } - })); this.domNode = confirmWidget.domNode; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index fe1615e5f60..fb66e426394 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -8,7 +8,6 @@ import { Separator } from '../../../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../../../base/common/async.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { IReader } from '../../../../../../base/common/observable.js'; import { count } from '../../../../../../base/common/strings.js'; import { isEmptyObject } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; @@ -284,10 +283,6 @@ export class ToolConfirmationSubPart extends AbstractToolConfirmationSubPart { return typeof title === 'string' ? title : title!.value; } - protected shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean { - return !!IChatToolInvocation.executionConfirmedOrDenied(toolInvocation, reader); - } - private _makeMarkdownPart(container: HTMLElement, message: string | IMarkdownString, codeBlockRenderOptions: ICodeBlockRenderOptions) { const part = this._register(this.instantiationService.createInstance(ChatMarkdownContentPart, { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 1afa8169b0a..9bd6801bbe2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -28,6 +28,7 @@ import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import { ChatToolOutputSubPart } from './chatToolOutputPart.js'; import { ChatToolPostExecuteConfirmationPart } from './chatToolPostExecuteConfirmationPart.js'; import { ChatToolProgressSubPart } from './chatToolProgressPart.js'; +import { autorun } from '../../../../../../base/common/observable.js'; export class ChatToolInvocationPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -67,6 +68,15 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa return; } + if (toolInvocation.kind === 'toolInvocation') { + const initialState = toolInvocation.state.get().type; + this._register(autorun(reader => { + if (toolInvocation.state.read(reader).type !== initialState) { + render(); + } + })); + } + // This part is a bit different, since IChatToolInvocation is not an immutable model object. So this part is able to rerender itself. // If this turns out to be a typical pattern, we could come up with a more reusable pattern, like telling the list to rerender an element // when the model changes, or trying to make the model immutable and swap out one content part for a new one based on user actions in the view. @@ -82,10 +92,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa this.subPart = partStore.add(this.createToolInvocationSubPart()); this.domNode.appendChild(this.subPart.domNode); partStore.add(this.subPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); - partStore.add(this.subPart.onNeedsRerender(() => { - render(); - this._onDidChangeHeight.fire(); - })); + partStore.add(this.subPart.onNeedsRerender(render)); // todo@connor4312/tyriar: standardize how these are displayed if (!(this.subPart instanceof ChatTerminalToolProgressPart)) { @@ -94,6 +101,8 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa this.domNode.appendChild(approval); } } + + this._onDidChangeHeight.fire(); }; render(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts index 2715d261342..2e772e46081 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationSubPart.ts @@ -6,7 +6,6 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorunSelfDisposable } from '../../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; import { IChatCodeBlockInfo } from '../../chat.js'; @@ -29,15 +28,6 @@ export abstract class BaseChatToolInvocationSubPart extends Disposable { protected readonly toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, ) { super(); - - if (toolInvocation.kind === 'toolInvocation' && !IChatToolInvocation.isComplete(toolInvocation)) { - this._register(autorunSelfDisposable(reader => { - if (IChatToolInvocation.isComplete(toolInvocation, reader)) { - this._onNeedsRerender.fire(); - reader.dispose(); - } - })); - } } protected getIcon() { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts index 1dfa909a115..834a4ea13ee 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts @@ -70,11 +70,6 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio return localize('approveToolResult', "Approve Tool Result"); } - protected shouldDismiss(toolInvocation: IChatToolInvocation, reader: IReader): boolean { - const currentState = toolInvocation.state.read(reader); - return currentState.type !== IChatToolInvocation.StateKind.WaitingForPostApproval; - } - protected override additionalPrimaryActions() { const actions = super.additionalPrimaryActions(); actions.push( diff --git a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts index 62ff79579e5..6f1300b5240 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts @@ -11,6 +11,7 @@ import { localize } from '../../../../../nls.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { detectEncodingFromBuffer } from '../../../../services/textfile/common/encoding.js'; +import { ITrustedDomainService } from '../../../url/browser/trustedDomainService.js'; import { ChatImageMimeType } from '../../common/languageModels.js'; import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, IToolResultDataPart, IToolResultTextPart, ToolDataSource, ToolProgress } from '../../common/languageModelToolsService.js'; import { InternalFetchWebPageToolId } from '../../common/tools/tools.js'; @@ -41,6 +42,7 @@ export class FetchWebPageTool implements IToolImpl { constructor( @IWebContentExtractorService private readonly _readerModeService: IWebContentExtractorService, @IFileService private readonly _fileService: IFileService, + @ITrustedDomainService private readonly _trustedDomainService: ITrustedDomainService ) { } async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { @@ -188,9 +190,11 @@ export class FetchWebPageTool implements IToolImpl { } const result: IPreparedToolInvocation = { invocationMessage, pastTenseMessage }; - if (urlsNeedingConfirmation.length) { - let confirmationTitle: string; - let confirmationMessage: string | MarkdownString; + const allDomainsTrusted = urlsNeedingConfirmation.every(u => this._trustedDomainService.isValid(u)); + let confirmationTitle: string | undefined; + let confirmationMessage: string | MarkdownString | undefined; + + if (urlsNeedingConfirmation.length && !allDomainsTrusted) { if (urlsNeedingConfirmation.length === 1) { confirmationTitle = localize('fetchWebPage.confirmationTitle.singular', 'Fetch web page?'); confirmationMessage = new MarkdownString( @@ -204,13 +208,14 @@ export class FetchWebPageTool implements IToolImpl { { supportThemeIcons: true } ); } - result.confirmationMessages = { - title: confirmationTitle, - message: confirmationMessage, - allowAutoConfirm: true, - disclaimer: new MarkdownString('$(info) ' + localize('fetchWebPage.confirmationMessage.plural', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }) - }; } + result.confirmationMessages = { + title: confirmationTitle, + message: confirmationMessage, + confirmResults: urlsNeedingConfirmation.length > 0, + allowAutoConfirm: true, + disclaimer: new MarkdownString('$(info) ' + localize('fetchWebPage.confirmationMessage.plural', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }) + }; return result; } diff --git a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts index 618e4ed1362..ed6a33e5b0c 100644 --- a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts @@ -13,6 +13,7 @@ import { IFileContent, IReadFileOptions } from '../../../../../platform/files/co import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { FetchWebPageTool } from '../../electron-browser/tools/fetchPageTool.js'; import { TestFileService } from '../../../../test/common/workbenchTestServices.js'; +import { MockTrustedDomainService } from '../../../url/test/browser/mockTrustedDomainService.js'; class TestWebContentExtractorService implements IWebContentExtractorService { _serviceBrand: undefined; @@ -81,7 +82,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(webContentMap), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const testUrls = [ @@ -128,7 +130,8 @@ suite('FetchWebPageTool', () => { test('should handle empty and undefined URLs', async () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(new ResourceMap()) + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService([]), ); // Test empty array @@ -175,7 +178,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(webContentMap), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const preparation = await tool.prepareToolInvocation( @@ -202,7 +206,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -248,7 +253,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -287,7 +293,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -332,7 +339,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -396,7 +404,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -434,7 +443,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( @@ -476,7 +486,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(webContentMap), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const testUrls = [ @@ -533,7 +544,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(webContentMap), - new ExtendedTestFileService(new ResourceMap()) + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService([]), ); const testUrls = [ @@ -567,7 +579,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const testUrls = [ @@ -607,7 +620,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(webContentMap), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const testUrls = [ @@ -653,7 +667,8 @@ suite('FetchWebPageTool', () => { test('should return empty toolResultDetails when all requests fail', async () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), // Empty - all web requests fail - new ExtendedTestFileService(new ResourceMap()) // Empty - all file requests fail + new ExtendedTestFileService(new ResourceMap()), // Empty - all file , + new MockTrustedDomainService([]), ); const testUrls = [ @@ -685,7 +700,8 @@ suite('FetchWebPageTool', () => { test('should handle empty URL array', async () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(new ResourceMap()) + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService([]), ); const result = await tool.invoke( @@ -709,7 +725,8 @@ suite('FetchWebPageTool', () => { const tool = new FetchWebPageTool( new TestWebContentExtractorService(new ResourceMap()), - new ExtendedTestFileService(fileContentMap) + new ExtendedTestFileService(fileContentMap), + new MockTrustedDomainService(), ); const result = await tool.invoke( From 2f3804cf0648484a3750f3ac3c04343616dcae11 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 22 Oct 2025 13:27:01 -0700 Subject: [PATCH 1495/4355] fix hygenie --- .../toolInvocationParts/chatToolPostExecuteConfirmationPart.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts index 834a4ea13ee..cee19f1b0ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts @@ -5,7 +5,6 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { getExtensionForMimeType } from '../../../../../../base/common/mime.js'; -import { IReader } from '../../../../../../base/common/observable.js'; import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { localize } from '../../../../../../nls.js'; From a4ba39c710a50b04de75cbd55aef093e28266115 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:43:20 -0700 Subject: [PATCH 1496/4355] Fix some formatting of negative numbers For #272750 Current TS formatter doesn't care about these but tsgo does --- .../editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts | 4 ++-- src/vs/editor/common/viewModel/viewModelLines.ts | 2 +- .../inlineCompletions/browser/model/suggestWidgetAdapter.ts | 2 +- .../contrib/notebook/browser/view/cellParts/codeCell.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 0d52df0cc51..bf2f4a91fee 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -1703,7 +1703,7 @@ export class PieceTreeBase { prevNewLength ); - updateTreeMetadata(this, prev, - 1, -1); + updateTreeMetadata(this, prev, -1, -1); if (prev.piece.length === 0) { nodesToDel.push(prev); } @@ -1720,7 +1720,7 @@ export class PieceTreeBase { newLength ); - updateTreeMetadata(this, next, - 1, -1); + updateTreeMetadata(this, next, -1, -1); if (next.piece.length === 0) { nodesToDel.push(next); } diff --git a/src/vs/editor/common/viewModel/viewModelLines.ts b/src/vs/editor/common/viewModel/viewModelLines.ts index 8d76ec87fc6..a4e1bd9beac 100644 --- a/src/vs/editor/common/viewModel/viewModelLines.ts +++ b/src/vs/editor/common/viewModel/viewModelLines.ts @@ -624,7 +624,7 @@ export class ViewModelLinesFromProjectedModel implements IViewModelLines { return new IndentGuide(g.visibleColumn, column, g.className, new IndentGuideHorizontalLine(g.horizontalLine.top, viewPosition.column), - - 1, + -1, -1, ); } else if (p.lineNumber < viewLineInfo.modelLineWrappedLineIdx) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts index f8d5dd08e69..b051250a58e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts @@ -84,7 +84,7 @@ export class SuggestWidgetAdaptor extends Disposable { candidates, compareBy(s => s!.prefixLength, numberComparator) ); - return result ? result.index : - 1; + return result ? result.index : -1; } })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index c3ce0b1db55..70d9b9f8e11 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -291,7 +291,7 @@ export class CodeCell extends Disposable { if (this._useNewApproachForEditorLayout) { return; } - const extraOffset = - 6 /** distance to the top of the cell editor, which is 6px under the focus indicator */ - 1 /** border */; + const extraOffset = -6 /** distance to the top of the cell editor, which is 6px under the focus indicator */ - 1 /** border */; const min = 0; const scrollTop = this.notebookEditor.scrollTop; From 5682b2e0ebe3a3400791a0a4e357da5c9f5ea8a7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:44:22 +0200 Subject: [PATCH 1497/4355] SCM - add text overflow ellipsis to the checkout action (#272763) * SCM - add text overflow ellipsis to the checkout action * Remove martin-top * Remove min-width --- src/vs/workbench/contrib/scm/browser/media/scm.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index d963780a537..8ca40733e2e 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -84,6 +84,18 @@ } } +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; +} + +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > .codicon { + display: inline-block; + vertical-align: middle; +} + .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label, .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .monaco-dropdown > .dropdown-label > .action-label { display: flex; From cbd176096363946cd82cbaaebc74a8c94ffe9b90 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:10:59 -0700 Subject: [PATCH 1498/4355] Remove line about multi-line commands These work now with tree sitter --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 5e6ec0a8d6b..0817703f554 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -56,10 +56,9 @@ function createPowerShellModelDescription(shell: string): string { `This tool allows you to execute ${isWinPwsh ? 'Windows PowerShell 5.1' : 'PowerShell'} commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.`, '', 'Command Execution:', - '- Does NOT support multi-line commands', // Even for pwsh 7+ we want to use `;` to chain commands since the tree sitter grammar - // doesn't parse `&&` - // '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', + // doesn't parse `&&`. See https://github.com/airbus-cert/tree-sitter-powershell/issues/27 + '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', '- Prefer pipelines | for object-based data flow', '', 'Directory Management:', From 9cdad99de22d170e725d09a543840b380ad814aa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:11:19 -0700 Subject: [PATCH 1499/4355] Move test to electron-browser to avoid web test problems --- .../{browser => electron-browser}/runInTerminalTool.test.ts | 2 ++ 1 file changed, 2 insertions(+) rename src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/{browser => electron-browser}/runInTerminalTool.test.ts (99%) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts similarity index 99% rename from src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts rename to src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 909f65a94df..62723155742 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -31,6 +31,8 @@ import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { Schemas } from '../../../../../../base/common/network.js'; +// HACK: This test lives in electron-browser/ to ensure this node import works if the test is run in +// web tests https://github.com/microsoft/vscode/issues/272777 // eslint-disable-next-line local/code-layering, local/code-import-patterns import { DiskFileSystemProvider } from '../../../../../../platform/files/node/diskFileSystemProvider.js'; From d786e57c353e0f291ef11b1fe3b7bd027479532e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:57:17 -0700 Subject: [PATCH 1500/4355] Clean up custom editor handling of names Removing duplicated code and using clearer names --- .../api/browser/mainThreadCustomEditors.ts | 2 +- .../api/browser/mainThreadWebviewPanels.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 64 +++++-------------- .../browser/customEditorInputFactory.ts | 4 +- .../customEditor/browser/customEditors.ts | 10 +-- .../update/browser/releaseNotesEditor.ts | 2 +- .../browser/webviewEditorInput.ts | 14 ++-- 7 files changed, 35 insertions(+), 63 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index c31db977685..e8a2659cd9a 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -670,7 +670,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom const backupMeta: CustomDocumentBackupData = { viewType: this.viewType, editorResource: this._editorResource, - customTitle: primaryEditor.getCustomTitle(), + customTitle: primaryEditor.getWebviewTitle(), iconPath: primaryEditor.iconPath, backupId: '', extension: primaryEditor.extension ? { diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index b085bb73a77..1bb2d7a9016 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -184,7 +184,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc } public $setTitle(handle: extHostProtocol.WebviewHandle, value: string): void { - this.tryGetWebviewInput(handle)?.setName(value); + this.tryGetWebviewInput(handle)?.setWebviewTitle(value); } public $setIconPath(handle: extHostProtocol.WebviewHandle, value: extHostProtocol.IWebviewIconPath | undefined): void { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 5317ef95d91..6ced9bac527 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -38,7 +38,7 @@ import { WebviewIcons } from '../../webviewPanel/browser/webviewEditorInput.js'; interface CustomEditorInputInitInfo { readonly resource: URI; readonly viewType: string; - readonly customTitle: string | undefined; + readonly webviewTitle: string | undefined; readonly iconPath: WebviewIcons | undefined; } @@ -56,7 +56,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { const untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; const webview = accessor.get(IWebviewService).createWebviewOverlay({ providedViewType: init.viewType, - title: init.customTitle, + title: init.webviewTitle, options: { customClasses: options?.customClasses }, contentOptions: {}, extension: undefined, @@ -76,7 +76,6 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private _defaultDirtyState: boolean | undefined; private _editorName: string | undefined = undefined; - private _customTitle: string | undefined = undefined; private readonly _backupId: string | undefined; @@ -110,13 +109,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._backupId = options.backupId; this._untitledDocumentData = options.untitledDocumentData; - this._customTitle = init.customTitle; - this.registerListeners(); } private registerListeners(): void { - // Clear our labels on certain label related events this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme))); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme))); @@ -181,26 +177,15 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } override getName(): string { - if (this._customTitle) { - return this._customTitle; - } - - if (typeof this._editorName !== 'string') { - this._editorName = this.customEditorLabelService.getName(this.resource) ?? basename(this.labelService.getUriLabel(this.resource)); + const customTitle = this.getWebviewTitle(); + if (customTitle) { + return customTitle; } + this._editorName ??= this.customEditorLabelService.getName(this.resource) ?? basename(this.labelService.getUriLabel(this.resource)); return this._editorName; } - override setName(value: string): void { - this._customTitle = value; - super.setName(value); - } - - getCustomTitle(): string | undefined { - return this._customTitle; - } - override getDescription(verbosity = Verbosity.MEDIUM): string | undefined { switch (verbosity) { case Verbosity.SHORT: @@ -215,61 +200,44 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private _shortDescription: string | undefined = undefined; private get shortDescription(): string { - if (typeof this._shortDescription !== 'string') { - this._shortDescription = this.labelService.getUriBasenameLabel(dirname(this.resource)); - } - + this._shortDescription ??= this.labelService.getUriBasenameLabel(dirname(this.resource)); return this._shortDescription; } private _mediumDescription: string | undefined = undefined; private get mediumDescription(): string { - if (typeof this._mediumDescription !== 'string') { - this._mediumDescription = this.labelService.getUriLabel(dirname(this.resource), { relative: true }); - } - + this._mediumDescription ??= this.labelService.getUriLabel(dirname(this.resource), { relative: true }); return this._mediumDescription; } private _longDescription: string | undefined = undefined; private get longDescription(): string { - if (typeof this._longDescription !== 'string') { - this._longDescription = this.labelService.getUriLabel(dirname(this.resource)); - } - + this._longDescription ??= this.labelService.getUriLabel(dirname(this.resource)); return this._longDescription; } private _shortTitle: string | undefined = undefined; private get shortTitle(): string { - if (typeof this._shortTitle !== 'string') { - this._shortTitle = this.getName(); - } - + this._shortTitle ??= this.getName(); return this._shortTitle; } private _mediumTitle: string | undefined = undefined; private get mediumTitle(): string { - if (typeof this._mediumTitle !== 'string') { - this._mediumTitle = this.labelService.getUriLabel(this.resource, { relative: true }); - } - + this._mediumTitle ??= this.labelService.getUriLabel(this.resource, { relative: true }); return this._mediumTitle; } private _longTitle: string | undefined = undefined; private get longTitle(): string { - if (typeof this._longTitle !== 'string') { - this._longTitle = this.labelService.getUriLabel(this.resource); - } - + this._longTitle ??= this.labelService.getUriLabel(this.resource); return this._longTitle; } override getTitle(verbosity?: Verbosity): string { - if (this._customTitle) { - return this._customTitle; + const customTitle = this.getWebviewTitle(); + if (customTitle) { + return customTitle; } switch (verbosity) { @@ -294,7 +262,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public override copy(): EditorInput { return CustomEditorInput.create(this.instantiationService, - { resource: this.resource, viewType: this.viewType, customTitle: this._customTitle, iconPath: this.iconPath, }, + { resource: this.resource, viewType: this.viewType, webviewTitle: this.getWebviewTitle(), iconPath: this.iconPath, }, this.group, this.webview.options); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index a9d522182e0..02c270e4661 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -99,7 +99,7 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { const customInput = this._instantiationService.createInstance(CustomEditorInput, { resource: data.editorResource, viewType: data.viewType, - customTitle: data.title, + webviewTitle: data.title, iconPath: data.iconPath, }, webview, { startsDirty: data.dirty, backupId: data.backupId }); if (typeof data.group === 'number') { @@ -200,7 +200,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType, - customTitle: backupData.customTitle, + webviewTitle: backupData.customTitle, iconPath: backupData.iconPath ? { dark: URI.revive(backupData.iconPath.dark), light: URI.revive(backupData.iconPath.light) } : undefined diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 93c582d4773..62b3f0a2305 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -138,10 +138,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }, { createEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, { resource, viewType: contributedEditor.id, customTitle: undefined, iconPath: undefined }, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource, viewType: contributedEditor.id, webviewTitle: undefined, iconPath: undefined }, group.id) }; }, createUntitledEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, { resource: resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), viewType: contributedEditor.id, customTitle: undefined, iconPath: undefined }, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, { resource: resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), viewType: contributedEditor.id, webviewTitle: undefined, iconPath: undefined }, group.id) }; }, createDiffEditorInput: (diffEditorInput, group) => { return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; @@ -157,8 +157,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ editorID: string, group: IEditorGroup ): DiffEditorInput { - const modifiedOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.modified.resource), viewType: editorID, customTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'modified' }); - const originalOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.original.resource), viewType: editorID, customTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'original' }); + const modifiedOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.modified.resource), viewType: editorID, webviewTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'modified' }); + const originalOverride = CustomEditorInput.create(this.instantiationService, { resource: assertReturnsDefined(editor.original.resource), viewType: editorID, webviewTitle: undefined, iconPath: undefined }, group.id, { customClasses: 'original' }); return this.instantiationService.createInstance(DiffEditorInput, editor.label, editor.description, originalOverride, modifiedOverride, true); } @@ -259,7 +259,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: EditorInput | IResourceEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = CustomEditorInput.create(this.instantiationService, { resource: newResource, viewType, customTitle: undefined, iconPath: undefined }, group); + replacement = CustomEditorInput.create(this.instantiationService, { resource: newResource, viewType, webviewTitle: undefined, iconPath: undefined }, group); } else { replacement = { resource: newResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index ab69916efea..68738027aee 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -99,7 +99,7 @@ export class ReleaseNotesManager extends Disposable { const activeEditorPane = this._editorService.activeEditorPane; if (this._currentReleaseNotes) { - this._currentReleaseNotes.setName(title); + this._currentReleaseNotes.setWebviewTitle(title); this._currentReleaseNotes.webview.setHtml(html); this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeEditorPane ? activeEditorPane.group : this._editorGroupService.activeGroup, false); } else { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts index 1aaa7fa1b9a..3fe3eff9607 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts @@ -39,7 +39,7 @@ export class WebviewInput extends EditorInput { private readonly _resourceId = generateUuid(); - private _name: string; + private _webviewTitle: string; private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; @@ -67,7 +67,7 @@ export class WebviewInput extends EditorInput { this.viewType = init.viewType; this.providedId = init.providedId; - this._name = init.name; + this._webviewTitle = init.name; this._iconPath = init.iconPath; this._webview = webview; @@ -87,7 +87,7 @@ export class WebviewInput extends EditorInput { } public override getName(): string { - return this._name; + return this._webviewTitle; } public override getTitle(_verbosity?: Verbosity): string { @@ -98,12 +98,16 @@ export class WebviewInput extends EditorInput { return undefined; } - public setName(value: string): void { - this._name = value; + public setWebviewTitle(value: string): void { + this._webviewTitle = value; this.webview.setTitle(value); this._onDidChangeLabel.fire(); } + public getWebviewTitle(): string | undefined { + return this._webviewTitle; + } + public get webview(): IOverlayWebview { return this._webview; } From 3a814a65df6c9c2de663787c8b07f473c5cca348 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 22 Oct 2025 15:06:00 -0700 Subject: [PATCH 1501/4355] :lipstick: align agent button name --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 4fbf5de92fb..f0d1a566327 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -179,7 +179,7 @@ export class ChatSubmitAction extends SubmitAction { toggled: { condition: ChatContextKeys.lockedToCodingAgent, icon: Codicon.send, - tooltip: localize('sendToRemoteAgent', "Send to Coding Agent"), + tooltip: localize('sendToAgent', "Send to Agent"), }, keybinding: { when: ContextKeyExpr.and( From 6aa08bd4f1a6d46897f0be8a5d4f1ecfb2ed095a Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:23:17 -0700 Subject: [PATCH 1502/4355] Show chat session display name in tooltip (#272782) --- .../contrib/chat/browser/chatEditorInput.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 0e2d8e0c989..ca272425830 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -16,7 +16,7 @@ import * as nls from '../../../../nls.js'; import { ConfirmResult, IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; -import { EditorInputCapabilities, IEditorIdentifier, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js'; +import { EditorInputCapabilities, IEditorIdentifier, IEditorSerializer, IUntypedEditorInput, Verbosity } from '../../../common/editor.js'; import { EditorInput, IEditorCloseHandler } from '../../../common/editor/editorInput.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatModel } from '../common/chatModel.js'; @@ -191,6 +191,27 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return defaultName + inputCountSuffix; } + override getTitle(verbosity?: Verbosity): string { + const name = this.getName(); + if (verbosity === Verbosity.LONG) { // Verbosity LONG is used for tooltips + const sessionType = this.getSessionType(); + const sessionTypeDisplayName = this.getSessionTypeDisplayName(sessionType); + if (sessionTypeDisplayName) { + return `${name} | ${sessionTypeDisplayName}`; + } + } + return name; + } + + private getSessionTypeDisplayName(sessionType: string): string | undefined { + if (sessionType === 'local') { + return; + } + const contributions = this.chatSessionsService.getAllChatSessionContributions(); + const contribution = contributions.find(c => c.type === sessionType); + return contribution?.displayName; + } + override getIcon(): ThemeIcon | undefined { // Return cached icon if available if (this.cachedIcon) { From 8830ab92c026fce0ab3e9a9b196114002ca00286 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 22 Oct 2025 15:34:28 -0700 Subject: [PATCH 1503/4355] Fix input height calculation with todos (#272792) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 6 +++++- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 90e99dd5386..1d721ae3bb5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -257,6 +257,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._editSessionWidgetHeight; } + get todoListWidgetHeight() { + return this.chatInputTodoListWidgetContainer.offsetHeight; + } + get attachmentsHeight() { return this.attachmentsContainer.offsetHeight + (this.attachmentsContainer.checkVisibility() ? 6 : 0); } @@ -2111,7 +2115,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge followupsHeight: this.followupsContainer.offsetHeight, inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, - inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (3 * 4) /* flex gap: todo|edits|input */), + inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (2 * 4) /* flex gap: edits|input */), attachmentsHeight: this.attachmentsHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e11ee96b923..629b5d7dcc9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2781,7 +2781,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.layout(contentHeight, width); // Push the welcome message down so it doesn't change position - // when followups, attachments, working set, or suggest next widget appear + // when followups, attachments, working set, todo list, or suggest next widget appear let welcomeOffset = 100; if (this.viewOptions.renderFollowups) { welcomeOffset = Math.max(welcomeOffset - this.input.followupsHeight, 0); @@ -2789,6 +2789,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (this.viewOptions.enableWorkingSet) { welcomeOffset = Math.max(welcomeOffset - this.input.editSessionWidgetHeight, 0); } + welcomeOffset = Math.max(welcomeOffset - this.input.todoListWidgetHeight, 0); welcomeOffset = Math.max(welcomeOffset - this.input.attachmentsHeight, 0); this.welcomeMessageContainer.style.height = `${contentHeight - welcomeOffset}px`; this.welcomeMessageContainer.style.paddingBottom = `${welcomeOffset}px`; From cf7637273dd7aa6382ac9c5d20624e60471ce7a1 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:43:12 -0700 Subject: [PATCH 1504/4355] ensure we carry over title when 'swapping' chat sessions (#272793) quick patch for title when swapping chat sessions --- .../workbench/api/browser/mainThreadChatSessions.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index 3ac0731adb7..5e0587c5560 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -28,6 +28,7 @@ import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; import { IViewsService } from '../../services/views/common/viewsService.js'; import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { ExtHostChatSessionsShape, ExtHostContext, IChatProgressDto, IChatSessionHistoryItemDto, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; +import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; export class ObservableChatSession extends Disposable implements ChatSession { static generateSessionKey(providerHandle: number, sessionId: string) { @@ -396,6 +397,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat const originalResource = ChatSessionUri.forSession(chatSessionType, original.id); const modifiedResource = ChatSessionUri.forSession(chatSessionType, modified.id); const originalEditor = this._editorService.editors.find(editor => editor.resource?.toString() === originalResource.toString()); + const contribution = this._chatSessionsService.getAllChatSessionContributions().find(c => c.type === chatSessionType); // Find the group containing the original editor let originalGroup: IEditorGroup | undefined; @@ -409,6 +411,13 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat originalGroup = this.editorGroupService.activeGroup; } + const options: IChatEditorOptions = { + title: { + preferred: originalEditor?.getName() || undefined, + fallback: localize('chatEditorContributionName', "{0}", contribution?.displayName), + } + }; + if (originalEditor) { // Prefetch the chat session content to make the subsequent editor swap quick this._chatSessionsService.provideChatSessionContent( @@ -421,7 +430,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat editor: originalEditor, replacement: { resource: modifiedResource, - options: {} + options, }, }], originalGroup); }); From 75ac91d656751b5a69d8b969c070b0aa16bbfb06 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 22 Oct 2025 15:44:50 -0700 Subject: [PATCH 1505/4355] Show user description for tool tooltips --- .../chat/common/promptSyntax/languageProviders/promptHovers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 1d5a18c044c..2ae788e8137 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -128,7 +128,7 @@ export class PromptHoverProvider implements HoverProvider { if (tool instanceof ToolSet) { return this.getToolsetHover(tool, range); } else { - return this.createHover(tool.modelDescription, range); + return this.createHover(tool.userDescription ?? tool.modelDescription, range); } } return undefined; From e81696f60db0936e9622ba1074dbbb015e0652ca Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 22 Oct 2025 16:01:59 -0700 Subject: [PATCH 1506/4355] Add policy support for linux (#272579) --- build/lib/policies/basePolicy.ts | 1 + build/lib/policies/booleanPolicy.js | 3 + build/lib/policies/booleanPolicy.ts | 4 + build/lib/policies/numberPolicy.js | 3 + build/lib/policies/numberPolicy.ts | 4 + build/lib/policies/objectPolicy.js | 3 + build/lib/policies/objectPolicy.ts | 4 + build/lib/policies/policyGenerator.js | 15 +- build/lib/policies/policyGenerator.ts | 19 +- build/lib/policies/render.js | 8 + build/lib/policies/render.ts | 8 + build/lib/policies/stringEnumPolicy.js | 3 + build/lib/policies/stringEnumPolicy.ts | 4 + build/lib/policies/stringPolicy.js | 3 + build/lib/policies/stringPolicy.ts | 4 + build/lib/policies/types.ts | 1 + build/lib/test/booleanPolicy.test.js | 6 + build/lib/test/booleanPolicy.test.ts | 10 + .../test/fixtures/policies/linux/policy.json | 14 ++ build/lib/test/numberPolicy.test.js | 6 + build/lib/test/numberPolicy.test.ts | 10 + build/lib/test/objectPolicy.test.js | 6 + build/lib/test/objectPolicy.test.ts | 10 + build/lib/test/policyConversion.test.js | 10 + build/lib/test/policyConversion.test.ts | 15 +- build/lib/test/render.test.js | 184 ++++++++++++++- build/lib/test/render.test.ts | 216 +++++++++++++++++- build/lib/test/stringEnumPolicy.test.js | 6 + build/lib/test/stringEnumPolicy.test.ts | 10 + build/lib/test/stringPolicy.test.js | 6 + build/lib/test/stringPolicy.test.ts | 10 + src/vs/base/common/policy.ts | 5 + src/vs/code/electron-main/main.ts | 5 +- src/vs/code/node/cliProcessMain.ts | 5 +- 34 files changed, 600 insertions(+), 21 deletions(-) create mode 100644 build/lib/test/fixtures/policies/linux/policy.json diff --git a/build/lib/policies/basePolicy.ts b/build/lib/policies/basePolicy.ts index 064467f7485..f0477d244f0 100644 --- a/build/lib/policies/basePolicy.ts +++ b/build/lib/policies/basePolicy.ts @@ -57,6 +57,7 @@ ${this.renderProfileManifestValue(translations)} `; } + abstract renderJsonValue(): string | number | boolean | object | null; abstract renderProfileValue(): string; abstract renderProfileManifestValue(translations?: LanguageTranslations): string; } diff --git a/build/lib/policies/booleanPolicy.js b/build/lib/policies/booleanPolicy.js index 1ec18d003ec..b7772650337 100644 --- a/build/lib/policies/booleanPolicy.js +++ b/build/lib/policies/booleanPolicy.js @@ -29,6 +29,9 @@ class BooleanPolicy extends basePolicy_1.BasePolicy { renderADMLPresentationContents() { return `${this.name}`; } + renderJsonValue() { + return false; + } renderProfileValue() { return ``; } diff --git a/build/lib/policies/booleanPolicy.ts b/build/lib/policies/booleanPolicy.ts index b37bb1e1816..538140b3db2 100644 --- a/build/lib/policies/booleanPolicy.ts +++ b/build/lib/policies/booleanPolicy.ts @@ -42,6 +42,10 @@ export class BooleanPolicy extends BasePolicy { return `${this.name}`; } + renderJsonValue() { + return false; + } + renderProfileValue(): string { return ``; } diff --git a/build/lib/policies/numberPolicy.js b/build/lib/policies/numberPolicy.js index 4cdc03ef738..62f6981f01a 100644 --- a/build/lib/policies/numberPolicy.js +++ b/build/lib/policies/numberPolicy.js @@ -33,6 +33,9 @@ class NumberPolicy extends basePolicy_1.BasePolicy { renderADMLPresentationContents() { return `${this.name}`; } + renderJsonValue() { + return this.defaultValue; + } renderProfileValue() { return `${this.defaultValue}`; } diff --git a/build/lib/policies/numberPolicy.ts b/build/lib/policies/numberPolicy.ts index 9bbbbddc07e..db4143e1f7f 100644 --- a/build/lib/policies/numberPolicy.ts +++ b/build/lib/policies/numberPolicy.ts @@ -46,6 +46,10 @@ export class NumberPolicy extends BasePolicy { return `${this.name}`; } + renderJsonValue() { + return this.defaultValue; + } + renderProfileValue() { return `${this.defaultValue}`; } diff --git a/build/lib/policies/objectPolicy.js b/build/lib/policies/objectPolicy.js index ab15ce9ec3e..be83c76613d 100644 --- a/build/lib/policies/objectPolicy.js +++ b/build/lib/policies/objectPolicy.js @@ -25,6 +25,9 @@ class ObjectPolicy extends basePolicy_1.BasePolicy { renderADMLPresentationContents() { return ``; } + renderJsonValue() { + return ''; + } renderProfileValue() { return ``; } diff --git a/build/lib/policies/objectPolicy.ts b/build/lib/policies/objectPolicy.ts index 8e555133f38..3bbc916636f 100644 --- a/build/lib/policies/objectPolicy.ts +++ b/build/lib/policies/objectPolicy.ts @@ -38,6 +38,10 @@ export class ObjectPolicy extends BasePolicy { return ``; } + renderJsonValue() { + return ''; + } + renderProfileValue(): string { return ``; } diff --git a/build/lib/policies/policyGenerator.js b/build/lib/policies/policyGenerator.js index e9f97b49092..bd549b0092d 100644 --- a/build/lib/policies/policyGenerator.js +++ b/build/lib/policies/policyGenerator.js @@ -203,10 +203,18 @@ async function darwinMain(policies, translations) { await fs_1.promises.writeFile(path_1.default.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); } } +async function linuxMain(policies) { + const root = '.build/policies/linux'; + const policyFileContents = JSON.stringify((0, render_1.renderJsonPolicies)(policies), undefined, 4); + await fs_1.promises.rm(root, { recursive: true, force: true }); + await fs_1.promises.mkdir(root, { recursive: true }); + const jsonPath = path_1.default.join(root, `policy.json`); + await fs_1.promises.writeFile(jsonPath, policyFileContents.replace(/\r?\n/g, '\n')); +} async function main() { const args = (0, minimist_1.default)(process.argv.slice(2)); if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } const policyDataFile = args._[0]; @@ -218,8 +226,11 @@ async function main() { else if (platform === 'win32') { await windowsMain(policies, translations); } + else if (platform === 'linux') { + await linuxMain(policies); + } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } diff --git a/build/lib/policies/policyGenerator.ts b/build/lib/policies/policyGenerator.ts index 4c5e1d616f7..50ea96b1280 100644 --- a/build/lib/policies/policyGenerator.ts +++ b/build/lib/policies/policyGenerator.ts @@ -14,7 +14,7 @@ import { ObjectPolicy } from './objectPolicy'; import { StringEnumPolicy } from './stringEnumPolicy'; import { StringPolicy } from './stringPolicy'; import { Version, LanguageTranslations, Policy, Translations, Languages, ProductJson } from './types'; -import { renderGP, renderMacOSPolicy } from './render'; +import { renderGP, renderJsonPolicies, renderMacOSPolicy } from './render'; const product = require('../../../product.json') as ProductJson; const packageJson = require('../../../package.json'); @@ -202,10 +202,21 @@ async function darwinMain(policies: Policy[], translations: Translations) { } } +async function linuxMain(policies: Policy[]) { + const root = '.build/policies/linux'; + const policyFileContents = JSON.stringify(renderJsonPolicies(policies), undefined, 4); + + await fs.rm(root, { recursive: true, force: true }); + await fs.mkdir(root, { recursive: true }); + + const jsonPath = path.join(root, `policy.json`); + await fs.writeFile(jsonPath, policyFileContents.replace(/\r?\n/g, '\n')); +} + async function main() { const args = minimist(process.argv.slice(2)); if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } @@ -217,8 +228,10 @@ async function main() { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); + } else if (platform === 'linux') { + await linuxMain(policies); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } diff --git a/build/lib/policies/render.js b/build/lib/policies/render.js index 7f312314e35..c867442cf50 100644 --- a/build/lib/policies/render.js +++ b/build/lib/policies/render.js @@ -7,6 +7,7 @@ exports.renderADML = renderADML; exports.renderProfileManifest = renderProfileManifest; exports.renderMacOSPolicy = renderMacOSPolicy; exports.renderGP = renderGP; +exports.renderJsonPolicies = renderJsonPolicies; function renderADMLString(prefix, moduleName, nlsString, translations) { let value; if (translations) { @@ -268,4 +269,11 @@ function renderGP(product, policies, translations) { ] }; } +function renderJsonPolicies(policies) { + const policyObject = {}; + for (const policy of policies) { + policyObject[policy.name] = policy.renderJsonValue(); + } + return policyObject; +} //# sourceMappingURL=render.js.map \ No newline at end of file diff --git a/build/lib/policies/render.ts b/build/lib/policies/render.ts index 8953a6b7fde..8aa4181753d 100644 --- a/build/lib/policies/render.ts +++ b/build/lib/policies/render.ts @@ -293,3 +293,11 @@ export function renderGP(product: ProductJson, policies: Policy[], translations: ] }; } + +export function renderJsonPolicies(policies: Policy[]) { + const policyObject: { [key: string]: string | number | boolean | object | null } = {}; + for (const policy of policies) { + policyObject[policy.name] = policy.renderJsonValue(); + } + return policyObject; +} diff --git a/build/lib/policies/stringEnumPolicy.js b/build/lib/policies/stringEnumPolicy.js index 03508238bab..2d43c275ea3 100644 --- a/build/lib/policies/stringEnumPolicy.js +++ b/build/lib/policies/stringEnumPolicy.js @@ -47,6 +47,9 @@ class StringEnumPolicy extends basePolicy_1.BasePolicy { renderADMLPresentationContents() { return ``; } + renderJsonValue() { + return this.enum_[0]; + } renderProfileValue() { return `${this.enum_[0]}`; } diff --git a/build/lib/policies/stringEnumPolicy.ts b/build/lib/policies/stringEnumPolicy.ts index b8f8263fb68..c4adabdace7 100644 --- a/build/lib/policies/stringEnumPolicy.ts +++ b/build/lib/policies/stringEnumPolicy.ts @@ -69,6 +69,10 @@ export class StringEnumPolicy extends BasePolicy { return ``; } + renderJsonValue() { + return this.enum_[0]; + } + renderProfileValue() { return `${this.enum_[0]}`; } diff --git a/build/lib/policies/stringPolicy.js b/build/lib/policies/stringPolicy.js index 493bc33af40..62018248e6a 100644 --- a/build/lib/policies/stringPolicy.js +++ b/build/lib/policies/stringPolicy.js @@ -22,6 +22,9 @@ class StringPolicy extends basePolicy_1.BasePolicy { renderADMXElements() { return [``]; } + renderJsonValue() { + return ''; + } renderADMLPresentationContents() { return ``; } diff --git a/build/lib/policies/stringPolicy.ts b/build/lib/policies/stringPolicy.ts index b18160640a8..e318a6165d8 100644 --- a/build/lib/policies/stringPolicy.ts +++ b/build/lib/policies/stringPolicy.ts @@ -34,6 +34,10 @@ export class StringPolicy extends BasePolicy { return [``]; } + renderJsonValue() { + return ''; + } + renderADMLPresentationContents() { return ``; } diff --git a/build/lib/policies/types.ts b/build/lib/policies/types.ts index 35e93c435ef..861b5205f69 100644 --- a/build/lib/policies/types.ts +++ b/build/lib/policies/types.ts @@ -23,6 +23,7 @@ export interface Policy { renderADMX(regKey: string): string[]; renderADMLStrings(translations?: LanguageTranslations): string[]; renderADMLPresentation(): string; + renderJsonValue(): string | number | boolean | object | null; renderProfile(): string[]; // https://github.com/ProfileManifests/ProfileManifests/wiki/Manifest-Format renderProfileManifest(translations?: LanguageTranslations): string; diff --git a/build/lib/test/booleanPolicy.test.js b/build/lib/test/booleanPolicy.test.js index 65bb0f5b88e..0c6ded63c2b 100644 --- a/build/lib/test/booleanPolicy.test.js +++ b/build/lib/test/booleanPolicy.test.js @@ -79,6 +79,12 @@ suite('BooleanPolicy', () => { const presentation = policy.renderADMLPresentation(); assert_1.default.strictEqual(presentation, 'TestBooleanPolicy'); }); + test('should render JSON value correctly', () => { + const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const jsonValue = policy.renderJsonValue(); + assert_1.default.strictEqual(jsonValue, false); + }); test('should render profile value correctly', () => { const policy = booleanPolicy_js_1.BooleanPolicy.from(mockCategory, mockPolicy); assert_1.default.ok(policy); diff --git a/build/lib/test/booleanPolicy.test.ts b/build/lib/test/booleanPolicy.test.ts index eed38d99d00..8da223530b9 100644 --- a/build/lib/test/booleanPolicy.test.ts +++ b/build/lib/test/booleanPolicy.test.ts @@ -98,6 +98,16 @@ suite('BooleanPolicy', () => { assert.strictEqual(presentation, 'TestBooleanPolicy'); }); + test('should render JSON value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, false); + }); + test('should render profile value correctly', () => { const policy = BooleanPolicy.from(mockCategory, mockPolicy); diff --git a/build/lib/test/fixtures/policies/linux/policy.json b/build/lib/test/fixtures/policies/linux/policy.json new file mode 100644 index 00000000000..3a941999da6 --- /dev/null +++ b/build/lib/test/fixtures/policies/linux/policy.json @@ -0,0 +1,14 @@ +{ + "ChatAgentExtensionTools": false, + "ChatAgentMode": false, + "ChatMCP": "none", + "ChatPromptFiles": false, + "ChatToolsAutoApprove": false, + "McpGalleryServiceUrl": "", + "AllowedExtensions": "", + "ExtensionGalleryServiceUrl": "", + "ChatToolsTerminalEnableAutoApprove": false, + "EnableFeedback": false, + "TelemetryLevel": "all", + "UpdateMode": "none" +} \ No newline at end of file diff --git a/build/lib/test/numberPolicy.test.js b/build/lib/test/numberPolicy.test.js index de3a73565be..9a1d498edde 100644 --- a/build/lib/test/numberPolicy.test.js +++ b/build/lib/test/numberPolicy.test.js @@ -78,6 +78,12 @@ suite('NumberPolicy', () => { const presentation = policy.renderADMLPresentation(); assert_1.default.strictEqual(presentation, 'TestNumberPolicy'); }); + test('should render JSON value correctly', () => { + const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const jsonValue = policy.renderJsonValue(); + assert_1.default.strictEqual(jsonValue, 42); + }); test('should render profile value correctly', () => { const policy = numberPolicy_js_1.NumberPolicy.from(mockCategory, mockPolicy); assert_1.default.ok(policy); diff --git a/build/lib/test/numberPolicy.test.ts b/build/lib/test/numberPolicy.test.ts index 4f27891ec61..dfb6276e34e 100644 --- a/build/lib/test/numberPolicy.test.ts +++ b/build/lib/test/numberPolicy.test.ts @@ -97,6 +97,16 @@ suite('NumberPolicy', () => { assert.strictEqual(presentation, 'TestNumberPolicy'); }); + test('should render JSON value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, 42); + }); + test('should render profile value correctly', () => { const policy = NumberPolicy.from(mockCategory, mockPolicy); diff --git a/build/lib/test/objectPolicy.test.js b/build/lib/test/objectPolicy.test.js index 1c25d52cf61..987b45242c4 100644 --- a/build/lib/test/objectPolicy.test.js +++ b/build/lib/test/objectPolicy.test.js @@ -77,6 +77,12 @@ suite('ObjectPolicy', () => { const presentation = policy.renderADMLPresentation(); assert_1.default.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const jsonValue = policy.renderJsonValue(); + assert_1.default.strictEqual(jsonValue, ''); + }); test('should render profile value correctly', () => { const policy = objectPolicy_js_1.ObjectPolicy.from(mockCategory, mockPolicy); assert_1.default.ok(policy); diff --git a/build/lib/test/objectPolicy.test.ts b/build/lib/test/objectPolicy.test.ts index a4c2638a52f..6012b8012da 100644 --- a/build/lib/test/objectPolicy.test.ts +++ b/build/lib/test/objectPolicy.test.ts @@ -96,6 +96,16 @@ suite('ObjectPolicy', () => { assert.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, ''); + }); + test('should render profile value correctly', () => { const policy = ObjectPolicy.from(mockCategory, mockPolicy); diff --git a/build/lib/test/policyConversion.test.js b/build/lib/test/policyConversion.test.js index 0f61c856f04..2e079b3106e 100644 --- a/build/lib/test/policyConversion.test.js +++ b/build/lib/test/policyConversion.test.js @@ -451,5 +451,15 @@ suite('Policy E2E conversion', () => { // Compare the rendered ADML with the fixture assert_1.default.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture'); }); + test('should render Linux policy JSON from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = (0, render_1.renderJsonPolicies)(parsedPolicies); + // Load the expected fixture file + const fixturePath = path_1.default.join(__dirname, 'fixtures', 'policies', 'linux', 'policy.json'); + const expectedContent = await fs_1.promises.readFile(fixturePath, 'utf-8'); + const expectedJson = JSON.parse(expectedContent); + // Compare the rendered JSON with the fixture + assert_1.default.deepStrictEqual(result, expectedJson, 'Linux policy JSON should match the fixture'); + }); }); //# sourceMappingURL=policyConversion.test.js.map \ No newline at end of file diff --git a/build/lib/test/policyConversion.test.ts b/build/lib/test/policyConversion.test.ts index 3be9fd8c10d..0610b0cd980 100644 --- a/build/lib/test/policyConversion.test.ts +++ b/build/lib/test/policyConversion.test.ts @@ -13,7 +13,7 @@ import { ObjectPolicy } from '../policies/objectPolicy'; import { StringEnumPolicy } from '../policies/stringEnumPolicy'; import { StringPolicy } from '../policies/stringPolicy'; import { Policy, ProductJson } from '../policies/types'; -import { renderGP, renderMacOSPolicy } from '../policies/render'; +import { renderGP, renderMacOSPolicy, renderJsonPolicies } from '../policies/render'; const PolicyTypes = [ BooleanPolicy, @@ -492,4 +492,17 @@ suite('Policy E2E conversion', () => { assert.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture'); }); + test('should render Linux policy JSON from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderJsonPolicies(parsedPolicies); + + // Load the expected fixture file + const fixturePath = path.join(__dirname, 'fixtures', 'policies', 'linux', 'policy.json'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + const expectedJson = JSON.parse(expectedContent); + + // Compare the rendered JSON with the fixture + assert.deepStrictEqual(result, expectedJson, 'Linux policy JSON should match the fixture'); + }); + }); diff --git a/build/lib/test/render.test.js b/build/lib/test/render.test.js index 005460c6204..1853dda04e3 100644 --- a/build/lib/test/render.test.js +++ b/build/lib/test/render.test.js @@ -109,7 +109,8 @@ suite('Render Functions', () => { renderADMLStrings: () => ['Test Policy'], renderADMLPresentation: () => '', renderProfile: () => ['TestPolicy', ''], - renderProfileManifest: () => 'pfm_nameTestPolicy' + renderProfileManifest: () => 'pfm_nameTestPolicy', + renderJsonValue: () => null }; test('should render ADMX with correct XML structure', () => { const result = (0, render_js_1.renderADMX)('VSCode', ['1.85'], [mockCategory], [mockPolicy]); @@ -167,7 +168,8 @@ suite('Render Functions', () => { renderADMLStrings: () => ['Test Policy 2'], renderADMLPresentation: () => '', renderProfile: () => ['TestPolicy2', ''], - renderProfileManifest: () => 'pfm_nameTestPolicy2' + renderProfileManifest: () => 'pfm_nameTestPolicy2', + renderJsonValue: () => null }; const result = (0, render_js_1.renderADMX)('VSCode', ['1.0'], [mockCategory], [mockPolicy, policy2]); assert_1.default.ok(result.includes('TestPolicy')); @@ -190,7 +192,8 @@ suite('Render Functions', () => { ], renderADMLPresentation: () => '', renderProfile: () => [], - renderProfileManifest: () => '' + renderProfileManifest: () => '', + renderJsonValue: () => null }; test('should render ADML with correct XML structure', () => { const result = (0, render_js_1.renderADML)('VS Code', ['1.85'], [mockCategory], [mockPolicy]); @@ -258,7 +261,8 @@ suite('Render Functions', () => { TestPolicy pfm_description ${translations?.['testModule']?.['test.desc'] || 'Default Desc'} -` +`, + renderJsonValue: () => null }; test('should render profile manifest with correct XML structure', () => { const result = (0, render_js_1.renderProfileManifest)('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); @@ -364,7 +368,8 @@ suite('Render Functions', () => { TestPolicy pfm_description ${translations?.['testModule']?.['test.desc'] || 'Default Desc'} -` +`, + renderJsonValue: () => null }; test('should render complete macOS policy profile', () => { const product = { @@ -525,7 +530,8 @@ suite('Render Functions', () => { ], renderADMLPresentation: () => '', renderProfile: () => [], - renderProfileManifest: () => '' + renderProfileManifest: () => '', + renderJsonValue: () => null }; test('should render complete GP with ADMX and ADML', () => { const product = { @@ -679,5 +685,171 @@ suite('Render Functions', () => { assert_1.default.ok(Array.isArray(result.adml)); }); }); + suite('renderJsonPolicies', () => { + const mockCategory = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + test('should render boolean policy JSON value', () => { + const booleanPolicy = { + name: 'BooleanPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => false + }; + const result = (0, render_js_1.renderJsonPolicies)([booleanPolicy]); + assert_1.default.deepStrictEqual(result, { BooleanPolicy: false }); + }); + test('should render number policy JSON value', () => { + const numberPolicy = { + name: 'NumberPolicy', + type: types_js_1.PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 42 + }; + const result = (0, render_js_1.renderJsonPolicies)([numberPolicy]); + assert_1.default.deepStrictEqual(result, { NumberPolicy: 42 }); + }); + test('should render string policy JSON value', () => { + const stringPolicy = { + name: 'StringPolicy', + type: types_js_1.PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + const result = (0, render_js_1.renderJsonPolicies)([stringPolicy]); + assert_1.default.deepStrictEqual(result, { StringPolicy: '' }); + }); + test('should render string enum policy JSON value', () => { + const stringEnumPolicy = { + name: 'StringEnumPolicy', + type: types_js_1.PolicyType.StringEnum, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'auto' + }; + const result = (0, render_js_1.renderJsonPolicies)([stringEnumPolicy]); + assert_1.default.deepStrictEqual(result, { StringEnumPolicy: 'auto' }); + }); + test('should render object policy JSON value', () => { + const objectPolicy = { + name: 'ObjectPolicy', + type: types_js_1.PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + const result = (0, render_js_1.renderJsonPolicies)([objectPolicy]); + assert_1.default.deepStrictEqual(result, { ObjectPolicy: '' }); + }); + test('should render multiple policies', () => { + const booleanPolicy = { + name: 'BooleanPolicy', + type: types_js_1.PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => true + }; + const numberPolicy = { + name: 'NumberPolicy', + type: types_js_1.PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 100 + }; + const stringPolicy = { + name: 'StringPolicy', + type: types_js_1.PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'test-value' + }; + const result = (0, render_js_1.renderJsonPolicies)([booleanPolicy, numberPolicy, stringPolicy]); + assert_1.default.deepStrictEqual(result, { + BooleanPolicy: true, + NumberPolicy: 100, + StringPolicy: 'test-value' + }); + }); + test('should handle empty policies array', () => { + const result = (0, render_js_1.renderJsonPolicies)([]); + assert_1.default.deepStrictEqual(result, {}); + }); + test('should handle null JSON value', () => { + const nullPolicy = { + name: 'NullPolicy', + type: types_js_1.PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => null + }; + const result = (0, render_js_1.renderJsonPolicies)([nullPolicy]); + assert_1.default.deepStrictEqual(result, { NullPolicy: null }); + }); + test('should handle object JSON value', () => { + const objectPolicy = { + name: 'ComplexObjectPolicy', + type: types_js_1.PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => ({ nested: { value: 123 } }) + }; + const result = (0, render_js_1.renderJsonPolicies)([objectPolicy]); + assert_1.default.deepStrictEqual(result, { ComplexObjectPolicy: { nested: { value: 123 } } }); + }); + }); }); //# sourceMappingURL=render.test.js.map \ No newline at end of file diff --git a/build/lib/test/render.test.ts b/build/lib/test/render.test.ts index cbe24ba0725..325831247c4 100644 --- a/build/lib/test/render.test.ts +++ b/build/lib/test/render.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { renderADMLString, renderProfileString, renderADMX, renderADML, renderProfileManifest, renderMacOSPolicy, renderGP } from '../policies/render.js'; +import { renderADMLString, renderProfileString, renderADMX, renderADML, renderProfileManifest, renderMacOSPolicy, renderGP, renderJsonPolicies } from '../policies/render.js'; import { NlsString, LanguageTranslations, Category, Policy, PolicyType } from '../policies/types.js'; suite('Render Functions', () => { @@ -136,7 +136,8 @@ suite('Render Functions', () => { renderADMLStrings: () => ['Test Policy'], renderADMLPresentation: () => '', renderProfile: () => ['TestPolicy', ''], - renderProfileManifest: () => 'pfm_nameTestPolicy' + renderProfileManifest: () => 'pfm_nameTestPolicy', + renderJsonValue: () => null }; test('should render ADMX with correct XML structure', () => { @@ -210,7 +211,8 @@ suite('Render Functions', () => { renderADMLStrings: () => ['Test Policy 2'], renderADMLPresentation: () => '', renderProfile: () => ['TestPolicy2', ''], - renderProfileManifest: () => 'pfm_nameTestPolicy2' + renderProfileManifest: () => 'pfm_nameTestPolicy2', + renderJsonValue: () => null }; const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy, policy2]); @@ -237,7 +239,8 @@ suite('Render Functions', () => { ], renderADMLPresentation: () => '', renderProfile: () => [], - renderProfileManifest: () => '' + renderProfileManifest: () => '', + renderJsonValue: () => null }; test('should render ADML with correct XML structure', () => { @@ -326,7 +329,8 @@ suite('Render Functions', () => { TestPolicy pfm_description ${translations?.['testModule']?.['test.desc'] || 'Default Desc'} -` +`, + renderJsonValue: () => null }; test('should render profile manifest with correct XML structure', () => { @@ -463,7 +467,8 @@ suite('Render Functions', () => { TestPolicy pfm_description ${translations?.['testModule']?.['test.desc'] || 'Default Desc'} -` +`, + renderJsonValue: () => null }; test('should render complete macOS policy profile', () => { @@ -645,7 +650,8 @@ suite('Render Functions', () => { ], renderADMLPresentation: () => '', renderProfile: () => [], - renderProfileManifest: () => '' + renderProfileManifest: () => '', + renderJsonValue: () => null }; test('should render complete GP with ADMX and ADML', () => { @@ -824,4 +830,200 @@ suite('Render Functions', () => { assert.ok(Array.isArray(result.adml)); }); }); + + suite('renderJsonPolicies', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + test('should render boolean policy JSON value', () => { + const booleanPolicy: Policy = { + name: 'BooleanPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => false + }; + + const result = renderJsonPolicies([booleanPolicy]); + + assert.deepStrictEqual(result, { BooleanPolicy: false }); + }); + + test('should render number policy JSON value', () => { + const numberPolicy: Policy = { + name: 'NumberPolicy', + type: PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 42 + }; + + const result = renderJsonPolicies([numberPolicy]); + + assert.deepStrictEqual(result, { NumberPolicy: 42 }); + }); + + test('should render string policy JSON value', () => { + const stringPolicy: Policy = { + name: 'StringPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + + const result = renderJsonPolicies([stringPolicy]); + + assert.deepStrictEqual(result, { StringPolicy: '' }); + }); + + test('should render string enum policy JSON value', () => { + const stringEnumPolicy: Policy = { + name: 'StringEnumPolicy', + type: PolicyType.StringEnum, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'auto' + }; + + const result = renderJsonPolicies([stringEnumPolicy]); + + assert.deepStrictEqual(result, { StringEnumPolicy: 'auto' }); + }); + + test('should render object policy JSON value', () => { + const objectPolicy: Policy = { + name: 'ObjectPolicy', + type: PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + + const result = renderJsonPolicies([objectPolicy]); + + assert.deepStrictEqual(result, { ObjectPolicy: '' }); + }); + + test('should render multiple policies', () => { + const booleanPolicy: Policy = { + name: 'BooleanPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => true + }; + + const numberPolicy: Policy = { + name: 'NumberPolicy', + type: PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 100 + }; + + const stringPolicy: Policy = { + name: 'StringPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'test-value' + }; + + const result = renderJsonPolicies([booleanPolicy, numberPolicy, stringPolicy]); + + assert.deepStrictEqual(result, { + BooleanPolicy: true, + NumberPolicy: 100, + StringPolicy: 'test-value' + }); + }); + + test('should handle empty policies array', () => { + const result = renderJsonPolicies([]); + + assert.deepStrictEqual(result, {}); + }); + + test('should handle null JSON value', () => { + const nullPolicy: Policy = { + name: 'NullPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => null + }; + + const result = renderJsonPolicies([nullPolicy]); + + assert.deepStrictEqual(result, { NullPolicy: null }); + }); + + test('should handle object JSON value', () => { + const objectPolicy: Policy = { + name: 'ComplexObjectPolicy', + type: PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => ({ nested: { value: 123 } }) + }; + + const result = renderJsonPolicies([objectPolicy]); + + assert.deepStrictEqual(result, { ComplexObjectPolicy: { nested: { value: 123 } } }); + }); + }); }); diff --git a/build/lib/test/stringEnumPolicy.test.js b/build/lib/test/stringEnumPolicy.test.js index 8b45b754c0c..860ad3e6c0f 100644 --- a/build/lib/test/stringEnumPolicy.test.js +++ b/build/lib/test/stringEnumPolicy.test.js @@ -95,6 +95,12 @@ suite('StringEnumPolicy', () => { const presentation = policy.renderADMLPresentation(); assert_1.default.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const jsonValue = policy.renderJsonValue(); + assert_1.default.strictEqual(jsonValue, 'auto'); + }); test('should render profile value correctly', () => { const policy = stringEnumPolicy_js_1.StringEnumPolicy.from(mockCategory, mockPolicy); assert_1.default.ok(policy); diff --git a/build/lib/test/stringEnumPolicy.test.ts b/build/lib/test/stringEnumPolicy.test.ts index feac73ed44f..3ee3856afd7 100644 --- a/build/lib/test/stringEnumPolicy.test.ts +++ b/build/lib/test/stringEnumPolicy.test.ts @@ -114,6 +114,16 @@ suite('StringEnumPolicy', () => { assert.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, 'auto'); + }); + test('should render profile value correctly', () => { const policy = StringEnumPolicy.from(mockCategory, mockPolicy); diff --git a/build/lib/test/stringPolicy.test.js b/build/lib/test/stringPolicy.test.js index 75d6365878c..059510ca89d 100644 --- a/build/lib/test/stringPolicy.test.js +++ b/build/lib/test/stringPolicy.test.js @@ -78,6 +78,12 @@ suite('StringPolicy', () => { const presentation = policy.renderADMLPresentation(); assert_1.default.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); + assert_1.default.ok(policy); + const jsonValue = policy.renderJsonValue(); + assert_1.default.strictEqual(jsonValue, ''); + }); test('should render profile value correctly', () => { const policy = stringPolicy_js_1.StringPolicy.from(mockCategory, mockPolicy); assert_1.default.ok(policy); diff --git a/build/lib/test/stringPolicy.test.ts b/build/lib/test/stringPolicy.test.ts index ae692d82bbe..a76c38c7dcb 100644 --- a/build/lib/test/stringPolicy.test.ts +++ b/build/lib/test/stringPolicy.test.ts @@ -97,6 +97,16 @@ suite('StringPolicy', () => { assert.strictEqual(presentation, ''); }); + test('should render JSON value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, ''); + }); + test('should render profile value correctly', () => { const policy = StringPolicy.from(mockCategory, mockPolicy); diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 7fa484a477e..8141b0f9b5d 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -6,6 +6,11 @@ import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; +/** + * System-wide policy file path for Linux systems. + */ +export const LINUX_SYSTEM_POLICY_FILE_PATH = '/etc/vscode/policy.json'; + export type PolicyName = string; export type LocalizedValue = { key: string; diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index fb06a689254..922e141d1ef 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -18,7 +18,7 @@ import { getPathLabel } from '../../base/common/labels.js'; import { Schemas } from '../../base/common/network.js'; import { basename, resolve } from '../../base/common/path.js'; import { mark } from '../../base/common/performance.js'; -import { IProcessEnvironment, isMacintosh, isWindows, OS } from '../../base/common/platform.js'; +import { IProcessEnvironment, isLinux, isMacintosh, isWindows, OS } from '../../base/common/platform.js'; import { cwd } from '../../base/common/process.js'; import { rtrim, trim } from '../../base/common/strings.js'; import { Promises as FSPromises } from '../../base/node/pfs.js'; @@ -73,6 +73,7 @@ import { SaveStrategy, StateService } from '../../platform/state/node/stateServi import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; import { addUNCHostToAllowlist, getUNCHost } from '../../base/node/unc.js'; import { ThemeMainService } from '../../platform/theme/electron-main/themeMainServiceImpl.js'; +import { LINUX_SYSTEM_POLICY_FILE_PATH } from '../../base/common/policy.js'; /** * The main VS Code entry point. @@ -204,6 +205,8 @@ class CodeMain { policyService = disposables.add(new NativePolicyService(logService, productService.win32RegValueName)); } else if (isMacintosh && productService.darwinBundleIdentifier) { policyService = disposables.add(new NativePolicyService(logService, productService.darwinBundleIdentifier)); + } else if (isLinux) { + policyService = disposables.add(new FilePolicyService(URI.file(LINUX_SYSTEM_POLICY_FILE_PATH), fileService, logService)); } else if (environmentMainService.policyFile) { policyService = disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)); } else { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index da60ab85a19..e0a2115ade8 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -12,7 +12,7 @@ import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from '.. import { Disposable } from '../../base/common/lifecycle.js'; import { Schemas } from '../../base/common/network.js'; import { isAbsolute, join } from '../../base/common/path.js'; -import { isWindows, isMacintosh } from '../../base/common/platform.js'; +import { isWindows, isMacintosh, isLinux } from '../../base/common/platform.js'; import { cwd } from '../../base/common/process.js'; import { URI } from '../../base/common/uri.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; @@ -76,6 +76,7 @@ import { McpGalleryService } from '../../platform/mcp/common/mcpGalleryService.j import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js'; import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js'; import { McpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifestService.js'; +import { LINUX_SYSTEM_POLICY_FILE_PATH } from '../../base/common/policy.js'; class CliMain extends Disposable { @@ -182,6 +183,8 @@ class CliMain extends Disposable { policyService = this._register(new NativePolicyService(logService, productService.win32RegValueName)); } else if (isMacintosh && productService.darwinBundleIdentifier) { policyService = this._register(new NativePolicyService(logService, productService.darwinBundleIdentifier)); + } else if (isLinux) { + policyService = this._register(new FilePolicyService(URI.file(LINUX_SYSTEM_POLICY_FILE_PATH), fileService, logService)); } else if (environmentService.policyFile) { policyService = this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)); } else { From 88853066e0cf52e4ce1af35f6d6b1923de1e1275 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 22 Oct 2025 16:26:28 -0700 Subject: [PATCH 1507/4355] Fixed issue with collapsed files --- .../widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index fc5b2e2970d..9310e9de5e0 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -372,6 +372,12 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } const activeViewItem = viewItems[activeViewItemIndex]; + + // Ensure the current file is expanded before navigating + if (activeViewItem.viewModel.collapsed.get()) { + activeViewItem.viewModel.collapsed.set(false, undefined); + } + const template = activeViewItem.template.get(); if (!template) { return; From be653eb37add88006cf28ce5d537f267118c779b Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Wed, 22 Oct 2025 17:39:07 -0700 Subject: [PATCH 1508/4355] Fixed PR bot suggestions --- .../widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts | 9 +-------- .../workbench/contrib/multiDiffEditor/browser/actions.ts | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 9310e9de5e0..110dad73751 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -426,14 +426,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { const nextIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1; // Wrap around if needed - let targetIndex: number; - if (nextIndex >= viewItems.length) { - targetIndex = 0; // Wrap to first file - } else if (nextIndex < 0) { - targetIndex = viewItems.length - 1; // Wrap to last file - } else { - targetIndex = nextIndex; - } + const targetIndex = (nextIndex + viewItems.length) % viewItems.length; const targetViewItem = viewItems[targetIndex]; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index ff0395aa3c4..8ac6c435abc 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -98,7 +98,7 @@ export class GoToNextChangeAction extends Action2 { return; } - (activeEditorPane as MultiDiffEditor).goToNextChange(); + activeEditorPane.goToNextChange(); } } @@ -132,7 +132,7 @@ export class GoToPreviousChangeAction extends Action2 { return; } - (activeEditorPane as MultiDiffEditor).goToPreviousChange(); + activeEditorPane.goToPreviousChange(); } } From a50072ee2cb611f48ee6d782663235ddfa7fda25 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 22 Oct 2025 17:39:24 -0700 Subject: [PATCH 1509/4355] Todo polishes (#272800) * More Todo polishes * Polish chat todo list item hover and focus styles --- .../contrib/chat/browser/chatInputPart.ts | 6 +++ .../contrib/chat/browser/chatWidget.ts | 1 + .../contrib/chat/browser/media/chat.css | 41 ++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 1d721ae3bb5..98d3112e8f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1788,6 +1788,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } async renderChatTodoListWidget(chatSessionId: string) { + + const isTodoWidgetEnabled = this.configurationService.getValue(ChatConfiguration.TodosShowWidget) !== false; + if (!isTodoWidgetEnabled) { + return; + } + if (!this._chatInputTodoListWidget.value) { const widget = this._chatEditingTodosDisposables.add(this.instantiationService.createInstance(ChatTodoListWidget)); this._chatInputTodoListWidget.value = widget; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 629b5d7dcc9..cd418bd3caa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2402,6 +2402,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.renderer.updateViewModel(this.viewModel); this.updateChatInputContext(); + this.input.renderChatTodoListWidget(this.viewModel.sessionId); } getFocus(): ChatTreeItem | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 3450b4aca04..5a4006d8d13 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1088,7 +1088,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { - padding: 6px 8px 6px 12px; + padding: 4px 8px 4px 12px; box-sizing: border-box; border: 1px solid var(--vscode-input-border, transparent); background-color: var(--vscode-editor-background); @@ -1123,11 +1123,13 @@ have to be updated for changes to the rules above, or to support more deeply nes white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - align-content: center; + line-height: 16px; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section .codicon { font-size: 16px; + line-height: 16px; + flex-shrink: 0; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container { @@ -1137,21 +1139,24 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button { - padding: 2px; - margin-right: 2px; - min-width: unset; background-color: transparent; + border-color: transparent; color: var(--vscode-foreground); - border: none; - border-radius: 3px; - height: 14px; - width: 14px; - display: flex; + cursor: pointer; + height: 16px; + padding: 2px; + border-radius: 2px; + display: inline-flex; align-items: center; justify-content: center; - cursor: pointer; + min-width: unset; + width: 14px; + margin-right: 2px; } +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button:hover { + background-color: var(--vscode-toolbar-hoverBackground); +} .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container .monaco-button:focus { outline: 1px solid var(--vscode-focusBorder); @@ -1223,16 +1228,22 @@ have to be updated for changes to the rules above, or to support more deeply nes scroll-snap-align: start; min-height: 22px; font-size: var(--vscode-chat-font-size-body-m); + padding: 0px 3px; + border-radius: 2px; + cursor: pointer; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:hover { + background-color: var(--vscode-list-hoverBackground); } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:focus { outline: 1px solid var(--vscode-focusBorder); - outline-offset: 1px; - background-color: var(--vscode-list-focusBackground); + outline-offset: -1px; } -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:hover { - background-color: var(--vscode-list-hoverBackground); +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item.focused { + background-color: var(--vscode-list-focusBackground); } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item > .todo-status-icon.codicon { From c323ed4bbf6fc1c95274022ac1abe3482a1ef279 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 22 Oct 2025 20:19:51 -0700 Subject: [PATCH 1510/4355] Support agent session contribution alt ids --- .../chat/browser/chatSessions.contribution.ts | 88 +++++++++++++++++-- .../chat/common/chatSessionsService.ts | 1 + 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 2851afea909..02bb739f6c8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -69,6 +69,13 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = this._onDidChangeItemsProviders.event; private readonly _contentProviders: Map = new Map(); private readonly _contributions: Map = new Map(); + private readonly _alternativeIdMap: Map = new Map(); private readonly _disposableStores: Map = new Map(); private readonly _contextKeys = new Set(); private readonly _onDidChangeSessionItems = this._register(new Emitter()); @@ -252,6 +260,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ description: contribution.description, when: contribution.when, icon: contribution.icon, + alternativeIds: contribution.alternativeIds, welcomeTitle: contribution.welcomeTitle, welcomeMessage: contribution.welcomeMessage, welcomeTips: contribution.welcomeTips, @@ -325,6 +334,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._contributions.set(contribution.type, contribution); + // Register alternative IDs if provided + if (contribution.alternativeIds) { + for (const altId of contribution.alternativeIds) { + if (this._alternativeIdMap.has(altId)) { + this._logService.warn(`Alternative ID '${altId}' is already mapped to '${this._alternativeIdMap.get(altId)}'. Remapping to '${contribution.type}'.`); + } + this._alternativeIdMap.set(altId, contribution.type); + } + } + // Store icon mapping if provided let icon: ThemeIcon | undefined; @@ -358,6 +377,14 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return { dispose: () => { this._contributions.delete(contribution.type); + // Remove alternative ID mappings + if (contribution.alternativeIds) { + for (const altId of contribution.alternativeIds) { + if (this._alternativeIdMap.get(altId) === contribution.type) { + this._alternativeIdMap.delete(altId); + } + } + } this._sessionTypeIcons.delete(contribution.type); this._sessionTypeWelcomeTitles.delete(contribution.type); this._sessionTypeWelcomeMessages.delete(contribution.type); @@ -380,6 +407,35 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return !whenExpr || this._contextKeyService.contextMatchesRules(whenExpr); } + /** + * Resolves a session type to its primary type, checking for alternative IDs. + * @param sessionType The session type or alternative ID to resolve + * @returns The primary session type, or undefined if not found or not available + */ + private _resolveToPrimaryType(sessionType: string): string | undefined { + // Try to find the primary type first + const contribution = this._contributions.get(sessionType); + if (contribution) { + // If the contribution is available, use it + if (this._isContributionAvailable(contribution)) { + return sessionType; + } + // If not available, fall through to check for alternatives + } + + // Check if this is an alternative ID, or if the primary type is not available + const primaryType = this._alternativeIdMap.get(sessionType); + if (primaryType) { + const altContribution = this._contributions.get(primaryType); + if (altContribution && this._isContributionAvailable(altContribution)) { + this._logService.info(`Resolving chat session type '${sessionType}' to alternative type '${primaryType}'`); + return primaryType; + } + } + + return undefined; + } + private _registerMenuItems(contribution: IChatSessionsExtensionPoint): IDisposable { // If provider registers anything for the create submenu, let it fully control the creation const contextKeyService = this._contextKeyService.createOverlay([ @@ -566,9 +622,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ async canResolveItemProvider(chatViewType: string): Promise { await this._extensionService.whenInstalledExtensionsRegistered(); - const contribution = this._contributions.get(chatViewType); - if (contribution && !this._isContributionAvailable(contribution)) { + const resolvedType = this._resolveToPrimaryType(chatViewType); + if (!resolvedType) { return false; + } else { + chatViewType = resolvedType; } if (this._itemsProviders.has(chatViewType)) { @@ -582,9 +640,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ async canResolveContentProvider(chatViewType: string) { await this._extensionService.whenInstalledExtensionsRegistered(); - const contribution = this._contributions.get(chatViewType); - if (contribution && !this._isContributionAvailable(contribution)) { + const resolvedType = this._resolveToPrimaryType(chatViewType); + if (!resolvedType) { return false; + } else { + chatViewType = resolvedType; } if (this._contentProviders.has(chatViewType)) { @@ -601,8 +661,12 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return []; } - const provider = this._itemsProviders.get(chatSessionType); + const resolvedType = this._resolveToPrimaryType(chatSessionType); + if (!resolvedType) { + return []; + } + const provider = this._itemsProviders.get(resolvedType); if (provider?.provideChatSessionItems) { const sessions = await provider.provideChatSessionItems(token); return sessions; @@ -675,6 +739,13 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ throw Error(`Cannot find provider for ${chatSessionType}`); } + const resolvedType = this._resolveToPrimaryType(chatSessionType); + if (!resolvedType) { + throw Error(`Cannot find provider for ${chatSessionType}`); + } else { + chatSessionType = resolvedType; + } + const provider = this._itemsProviders.get(chatSessionType); if (!provider?.provideNewChatSessionItem) { throw Error(`Provider for ${chatSessionType} does not support creating sessions`); @@ -689,6 +760,13 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ throw Error(`Can not find provider for ${chatSessionType}`); } + const resolvedType = this._resolveToPrimaryType(chatSessionType); + if (!resolvedType) { + throw Error(`Can not find provider for ${chatSessionType}`); + } else { + chatSessionType = resolvedType; + } + const provider = this._contentProviders.get(chatSessionType); if (!provider) { throw Error(`Can not find provider for ${chatSessionType}`); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index c7240288d02..2f8c2983b8c 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -51,6 +51,7 @@ export interface IChatSessionsExtensionPoint { readonly when?: string; readonly icon?: string; readonly order?: number; + readonly alternativeIds?: string[]; readonly welcomeTitle?: string; readonly welcomeMessage?: string; readonly welcomeTips?: string; From 1f11adbce8ebdba7167b167ef1206ee54f321c91 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 22 Oct 2025 20:25:17 -0700 Subject: [PATCH 1511/4355] resolve comments. --- .../chat/browser/chatSessions.contribution.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 02bb739f6c8..8648c2995d8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -625,10 +625,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const resolvedType = this._resolveToPrimaryType(chatViewType); if (!resolvedType) { return false; - } else { - chatViewType = resolvedType; } + chatViewType = resolvedType; + if (this._itemsProviders.has(chatViewType)) { return true; } @@ -643,10 +643,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const resolvedType = this._resolveToPrimaryType(chatViewType); if (!resolvedType) { return false; - } else { - chatViewType = resolvedType; } + chatViewType = resolvedType; + if (this._contentProviders.has(chatViewType)) { return true; } @@ -742,10 +742,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const resolvedType = this._resolveToPrimaryType(chatSessionType); if (!resolvedType) { throw Error(`Cannot find provider for ${chatSessionType}`); - } else { - chatSessionType = resolvedType; } + chatSessionType = resolvedType; + const provider = this._itemsProviders.get(chatSessionType); if (!provider?.provideNewChatSessionItem) { throw Error(`Provider for ${chatSessionType} does not support creating sessions`); @@ -757,19 +757,19 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public async provideChatSessionContent(chatSessionType: string, id: string, resource: URI, token: CancellationToken): Promise { if (!(await this.canResolveContentProvider(chatSessionType))) { - throw Error(`Can not find provider for ${chatSessionType}`); + throw Error(`Cannot find provider for ${chatSessionType}`); } const resolvedType = this._resolveToPrimaryType(chatSessionType); if (!resolvedType) { - throw Error(`Can not find provider for ${chatSessionType}`); - } else { - chatSessionType = resolvedType; + throw Error(`Cannot find provider for ${chatSessionType}`); } + chatSessionType = resolvedType; + const provider = this._contentProviders.get(chatSessionType); if (!provider) { - throw Error(`Can not find provider for ${chatSessionType}`); + throw Error(`Cannot find provider for ${chatSessionType}`); } const sessionKey = `${chatSessionType}_${id}`; From 4efd3ae616e98652f0ec39a600f0569dd8dff1c5 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 22 Oct 2025 21:06:25 -0700 Subject: [PATCH 1512/4355] Use WorkbenchList with renderer for chat todo list (#272808) Use WorkbenchList with renderer for chat todo list; improve accessibility & layout --- .../chatContentParts/chatTodoListWidget.ts | 243 +++++++++++------- .../contrib/chat/browser/media/chat.css | 24 -- .../test/browser/chatTodoListWidget.test.ts | 8 +- 3 files changed, 161 insertions(+), 114 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index efb113d88af..6399e7919f0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -5,14 +5,130 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; +import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { WorkbenchList } from '../../../../../platform/list/browser/listService.js'; import { IChatTodoListService, IChatTodo } from '../../common/chatTodoListService.js'; import { TodoListToolDescriptionFieldSettingId } from '../../common/tools/manageTodoListTool.js'; +class TodoListDelegate implements IListVirtualDelegate { + getHeight(element: IChatTodo): number { + return 22; + } + + getTemplateId(element: IChatTodo): string { + return TodoListRenderer.TEMPLATE_ID; + } +} + +interface ITodoListTemplate { + readonly templateDisposables: DisposableStore; + readonly todoElement: HTMLElement; + readonly statusIcon: HTMLElement; + readonly iconLabel: IconLabel; + readonly statusElement: HTMLElement; +} + +class TodoListRenderer implements IListRenderer { + static TEMPLATE_ID = 'todoListRenderer'; + readonly templateId: string = TodoListRenderer.TEMPLATE_ID; + + constructor( + private readonly configurationService: IConfigurationService + ) { } + + renderTemplate(container: HTMLElement): ITodoListTemplate { + const templateDisposables = new DisposableStore(); + const todoElement = dom.append(container, dom.$('li.todo-item')); + todoElement.setAttribute('role', 'listitem'); + + const statusIcon = dom.append(todoElement, dom.$('.todo-status-icon.codicon')); + statusIcon.setAttribute('aria-hidden', 'true'); + + const todoContent = dom.append(todoElement, dom.$('.todo-content')); + const iconLabel = templateDisposables.add(new IconLabel(todoContent, { supportIcons: false })); + const statusElement = dom.append(todoContent, dom.$('.todo-status-text')); + statusElement.style.position = 'absolute'; + statusElement.style.left = '-10000px'; + statusElement.style.width = '1px'; + statusElement.style.height = '1px'; + statusElement.style.overflow = 'hidden'; + + return { templateDisposables, todoElement, statusIcon, iconLabel, statusElement }; + } + + renderElement(todo: IChatTodo, index: number, templateData: ITodoListTemplate): void { + const { todoElement, statusIcon, iconLabel, statusElement } = templateData; + + // Update status icon + statusIcon.className = 'todo-status-icon codicon ' + this.getStatusIconClass(todo.status); + statusIcon.style.color = this.getStatusIconColor(todo.status); + + // Update title with tooltip if description exists and description field is enabled + const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; + const title = includeDescription && todo.description && todo.description.trim() ? todo.description : undefined; + iconLabel.setLabel(todo.title, undefined, { title }); + + // Update hidden status text for screen readers + const statusText = this.getStatusText(todo.status); + statusElement.id = `todo-status-${index}`; + statusElement.textContent = statusText; + + // Update aria-label + const ariaLabel = includeDescription && todo.description && todo.description.trim() + ? localize('chat.todoList.itemWithDescription', '{0}, {1}, {2}', todo.title, statusText, todo.description) + : localize('chat.todoList.item', '{0}, {1}', todo.title, statusText); + todoElement.setAttribute('aria-label', ariaLabel); + todoElement.setAttribute('aria-describedby', `todo-status-${index}`); + } + + disposeTemplate(templateData: ITodoListTemplate): void { + templateData.templateDisposables.dispose(); + } + + private getStatusText(status: string): string { + switch (status) { + case 'completed': + return localize('chat.todoList.status.completed', 'completed'); + case 'in-progress': + return localize('chat.todoList.status.inProgress', 'in progress'); + case 'not-started': + default: + return localize('chat.todoList.status.notStarted', 'not started'); + } + } + + private getStatusIconClass(status: string): string { + switch (status) { + case 'completed': + return 'codicon-check'; + case 'in-progress': + return 'codicon-record'; + case 'not-started': + default: + return 'codicon-circle-large-outline'; + } + } + + private getStatusIconColor(status: string): string { + switch (status) { + case 'completed': + return 'var(--vscode-charts-green)'; + case 'in-progress': + return 'var(--vscode-charts-blue)'; + case 'not-started': + default: + return 'var(--vscode-foreground)'; + } + } +} + export class ChatTodoListWidget extends Disposable { public readonly domNode: HTMLElement; @@ -27,10 +143,13 @@ export class ChatTodoListWidget extends Disposable { private clearButton!: Button; private _currentSessionId: string | undefined; private _userHasScrolledManually: boolean = false; + private _todoList: WorkbenchList | undefined; + private readonly _listDisposables = this._register(new DisposableStore()); constructor( @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -161,8 +280,6 @@ export class ChatTodoListWidget extends Disposable { } private renderTodoList(todoList: IChatTodo[]): void { - this.todoListContainer.textContent = ''; - const titleElement = this.expandoElement.querySelector('.todo-list-title') as HTMLElement; if (titleElement) { this.updateTitleElement(titleElement, todoList); @@ -178,52 +295,8 @@ export class ChatTodoListWidget extends Disposable { let firstCompletedIndex = -1; let firstPendingAfterCompletedIndex = -1; + // Track indices for smart scrolling todoList.forEach((todo, index) => { - const todoElement = dom.$('li.todo-item'); - todoElement.setAttribute('role', 'listitem'); - todoElement.setAttribute('tabindex', '0'); - - // Add tooltip if description exists and description field is enabled - const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; - if (includeDescription && todo.description && todo.description.trim()) { - todoElement.title = todo.description; - } - - const statusIcon = dom.$('.todo-status-icon.codicon'); - statusIcon.classList.add(this.getStatusIconClass(todo.status)); - statusIcon.style.color = this.getStatusIconColor(todo.status); - // Hide decorative icon from screen readers - statusIcon.setAttribute('aria-hidden', 'true'); - - const titleElement = dom.$('.todo-title'); - titleElement.textContent = todo.title; - - // Add hidden status text for screen readers - const statusText = this.getStatusText(todo.status); - const statusElement = dom.$('.todo-status-text'); - statusElement.id = `todo-status-${index}`; - statusElement.textContent = statusText; - statusElement.style.position = 'absolute'; - statusElement.style.left = '-10000px'; - statusElement.style.width = '1px'; - statusElement.style.height = '1px'; - statusElement.style.overflow = 'hidden'; - - const todoContent = dom.$('.todo-content'); - todoContent.appendChild(titleElement); - todoContent.appendChild(statusElement); - - const ariaLabel = includeDescription && todo.description && todo.description.trim() - ? localize('chat.todoList.itemWithDescription', '{0}, {1}, {2}', todo.title, statusText, todo.description) - : localize('chat.todoList.item', '{0}, {1}', todo.title, statusText); - todoElement.setAttribute('aria-label', ariaLabel); - todoElement.setAttribute('aria-describedby', `todo-status-${index}`); - todoElement.appendChild(statusIcon); - todoElement.appendChild(todoContent); - - this.todoListContainer.appendChild(todoElement); - - // Track indices for smart scrolling if (todo.status === 'completed' && firstCompletedIndex === -1) { firstCompletedIndex = index; } @@ -235,6 +308,40 @@ export class ChatTodoListWidget extends Disposable { } }); + // Create or update the WorkbenchList + if (!this._todoList) { + this._todoList = this.instantiationService.createInstance( + WorkbenchList, + 'ChatTodoListRenderer', + this.todoListContainer, + new TodoListDelegate(), + [new TodoListRenderer(this.configurationService)], + { + alwaysConsumeMouseWheel: false, + accessibilityProvider: { + getAriaLabel: (todo: IChatTodo) => { + const statusText = this.getStatusText(todo.status); + const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; + return includeDescription && todo.description && todo.description.trim() + ? localize('chat.todoList.itemWithDescription', '{0}, {1}, {2}', todo.title, statusText, todo.description) + : localize('chat.todoList.item', '{0}, {1}', todo.title, statusText); + }, + getWidgetAriaLabel: () => localize('chatTodoList', 'Chat Todo List') + } + } + ); + + this._listDisposables.add(this._todoList); + } + + // Update list contents + const maxItemsShown = 6; + const itemsShown = Math.min(todoList.length, maxItemsShown); + const height = itemsShown * 22; + this._todoList.layout(height); + this._todoList.getHTMLElement().style.height = `${height}px`; + this._todoList.splice(0, this._todoList.length, todoList); + const hasInProgressTask = todoList.some(todo => todo.status === 'in-progress'); const hasCompletedTask = todoList.some(todo => todo.status === 'completed'); @@ -351,12 +458,9 @@ export class ChatTodoListWidget extends Disposable { : localize('chat.todoList.expandButtonWithProgress', 'Expand {0}', progressText.textContent); this.expandoElement.setAttribute('aria-label', expandButtonLabel); this.expandoElement.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); - let title = progressText.textContent || ''; if (!this._isExpanded) { - let currentTodo: IChatTodo | undefined; // Priority 1: Show first in-progress todo (matches manageTodoListTool logic) if (firstInProgressTodo) { - currentTodo = firstInProgressTodo; const separator = dom.$('span'); separator.textContent = ' - '; titleElement.appendChild(separator); @@ -374,8 +478,6 @@ export class ChatTodoListWidget extends Disposable { } // Priority 2: Show last completed todo if not all completed (matches manageTodoListTool logic) else if (completedCount > 0 && completedCount < totalCount && lastCompletedTodo) { - currentTodo = lastCompletedTodo; - const separator = dom.$('span'); separator.textContent = ' - '; titleElement.appendChild(separator); @@ -391,15 +493,6 @@ export class ChatTodoListWidget extends Disposable { completedText.style.verticalAlign = 'middle'; titleElement.appendChild(completedText); } - const includeDescription = this.configurationService.getValue(TodoListToolDescriptionFieldSettingId) !== false; - if (includeDescription && currentTodo && currentTodo.description && currentTodo.description.trim()) { - title = currentTodo.description; - } - } - - const titleSection = this.expandoElement.querySelector('.todo-list-title-section') as HTMLElement; - if (titleSection) { - titleSection.title = title; } } @@ -414,28 +507,4 @@ export class ChatTodoListWidget extends Disposable { return localize('chat.todoList.status.notStarted', 'not started'); } } - - private getStatusIconClass(status: string): string { - switch (status) { - case 'completed': - return 'codicon-check'; - case 'in-progress': - return 'codicon-record'; - case 'not-started': - default: - return 'codicon-circle-large-outline'; - } - } - - private getStatusIconColor(status: string): string { - switch (status) { - case 'completed': - return 'var(--vscode-charts-green)'; - case 'in-progress': - return 'var(--vscode-charts-blue)'; - case 'not-started': - default: - return 'var(--vscode-foreground)'; - } - } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 5a4006d8d13..4c137ba57d2 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1185,8 +1185,6 @@ have to be updated for changes to the rules above, or to support more deeply nes /* 6.5 items to show half-line affordance */ overflow-y: auto; overscroll-behavior: contain; - scrollbar-width: thin; - scrollbar-color: var(--vscode-scrollbarSlider-background) transparent; scroll-behavior: smooth; scroll-padding-top: 24px; /* Half item height to show partial next item */ @@ -1194,25 +1192,6 @@ have to be updated for changes to the rules above, or to support more deeply nes /* Half item height to show partial previous item */ } -/* Modern scrollbar styling for WebKit browsers */ -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar { - width: 8px; -} - -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-track { - background: transparent; -} - -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb { - background-color: var(--vscode-scrollbarSlider-background); - border-radius: 4px; - border: 2px solid transparent; - background-clip: content-box; -} - -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container::-webkit-scrollbar-thumb:hover { - background-color: var(--vscode-scrollbarSlider-hoverBackground); -} .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list { display: flex; @@ -1233,9 +1212,6 @@ have to be updated for changes to the rules above, or to support more deeply nes cursor: pointer; } -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:hover { - background-color: var(--vscode-list-hoverBackground); -} .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item:focus { outline: 1px solid var(--vscode-focusBorder); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index b5cc3e286d1..65c93cb6bc9 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -10,6 +10,7 @@ import { ChatTodoListWidget } from '../../browser/chatContentParts/chatTodoListW import { IChatTodo, IChatTodoListService } from '../../common/chatTodoListService.js'; import { mainWindow } from '../../../../../base/browser/window.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; suite('ChatTodoListWidget Accessibility', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -40,7 +41,8 @@ suite('ChatTodoListWidget Accessibility', () => { getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined } as any; - widget = store.add(new ChatTodoListWidget(mockTodoListService, mockConfigurationService)); + const instantiationService = workbenchInstantiationService(undefined, store); + widget = store.add(new ChatTodoListWidget(mockTodoListService, mockConfigurationService, instantiationService)); mainWindow.document.body.appendChild(widget.domNode); }); @@ -76,7 +78,6 @@ suite('ChatTodoListWidget Accessibility', () => { // Check first item (not-started) const firstItem = todoItems[0] as HTMLElement; assert.strictEqual(firstItem.getAttribute('role'), 'listitem'); - assert.strictEqual(firstItem.getAttribute('tabindex'), '0'); assert.ok(firstItem.getAttribute('aria-label')?.includes('First task')); assert.ok(firstItem.getAttribute('aria-label')?.includes('not started')); @@ -150,7 +151,8 @@ suite('ChatTodoListWidget Accessibility', () => { getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined } as any; - const emptyWidget = store.add(new ChatTodoListWidget(emptyTodoListService, emptyConfigurationService)); + const instantiationService = workbenchInstantiationService(undefined, store); + const emptyWidget = store.add(new ChatTodoListWidget(emptyTodoListService, emptyConfigurationService, instantiationService)); mainWindow.document.body.appendChild(emptyWidget.domNode); emptyWidget.render('test-session'); From 0ce5faaf741b65e9ba7fcd19bc2b144e46983a5f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 22 Oct 2025 21:07:33 -0700 Subject: [PATCH 1513/4355] fix contribution loading --- .../contrib/chat/browser/chatSessions.contribution.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 8648c2995d8..b5ebfc3be72 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -628,6 +628,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } chatViewType = resolvedType; + const contribution = this._contributions.get(chatViewType); + if (contribution && !this._isContributionAvailable(contribution)) { + return false; + } if (this._itemsProviders.has(chatViewType)) { return true; @@ -647,6 +651,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ chatViewType = resolvedType; + const contribution = this._contributions.get(chatViewType); + if (contribution && !this._isContributionAvailable(contribution)) { + return false; + } + if (this._contentProviders.has(chatViewType)) { return true; } From df291784dee330ef1383fab4d47e435787da0716 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 22 Oct 2025 21:12:17 -0700 Subject: [PATCH 1514/4355] fix tests. --- .../chat/browser/chatSessions.contribution.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index b5ebfc3be72..0ebd8373351 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -623,11 +623,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ async canResolveItemProvider(chatViewType: string): Promise { await this._extensionService.whenInstalledExtensionsRegistered(); const resolvedType = this._resolveToPrimaryType(chatViewType); - if (!resolvedType) { - return false; + if (resolvedType) { + chatViewType = resolvedType; } - chatViewType = resolvedType; const contribution = this._contributions.get(chatViewType); if (contribution && !this._isContributionAvailable(contribution)) { return false; @@ -645,12 +644,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ async canResolveContentProvider(chatViewType: string) { await this._extensionService.whenInstalledExtensionsRegistered(); const resolvedType = this._resolveToPrimaryType(chatViewType); - if (!resolvedType) { - return false; + if (resolvedType) { + chatViewType = resolvedType; } - chatViewType = resolvedType; - const contribution = this._contributions.get(chatViewType); if (contribution && !this._isContributionAvailable(contribution)) { return false; @@ -671,11 +668,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } const resolvedType = this._resolveToPrimaryType(chatSessionType); - if (!resolvedType) { - return []; + if (resolvedType) { + chatSessionType = resolvedType; } - const provider = this._itemsProviders.get(resolvedType); + const provider = this._itemsProviders.get(chatSessionType); if (provider?.provideChatSessionItems) { const sessions = await provider.provideChatSessionItems(token); return sessions; @@ -749,11 +746,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } const resolvedType = this._resolveToPrimaryType(chatSessionType); - if (!resolvedType) { - throw Error(`Cannot find provider for ${chatSessionType}`); + if (resolvedType) { + chatSessionType = resolvedType; } - chatSessionType = resolvedType; const provider = this._itemsProviders.get(chatSessionType); if (!provider?.provideNewChatSessionItem) { @@ -766,19 +762,18 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public async provideChatSessionContent(chatSessionType: string, id: string, resource: URI, token: CancellationToken): Promise { if (!(await this.canResolveContentProvider(chatSessionType))) { - throw Error(`Cannot find provider for ${chatSessionType}`); + throw Error(`Can not find provider for ${chatSessionType}`); } const resolvedType = this._resolveToPrimaryType(chatSessionType); - if (!resolvedType) { - throw Error(`Cannot find provider for ${chatSessionType}`); + if (resolvedType) { + chatSessionType = resolvedType; } - chatSessionType = resolvedType; const provider = this._contentProviders.get(chatSessionType); if (!provider) { - throw Error(`Cannot find provider for ${chatSessionType}`); + throw Error(`Can not find provider for ${chatSessionType}`); } const sessionKey = `${chatSessionType}_${id}`; From ff891375f466d46ff9f3c97486c5d06ce87c8d08 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 23 Oct 2025 14:37:46 +0900 Subject: [PATCH 1515/4355] feat: reenable asar support (#272552) --- build/gulpfile.vscode.js | 10 +-- build/lib/asar.js | 20 +++--- build/lib/asar.ts | 22 ++++--- build/linux/dependencies-generator.js | 3 +- build/linux/dependencies-generator.ts | 3 +- src/bootstrap-esm.ts | 66 +++++++++++++++---- src/bootstrap-node.ts | 28 ++++++++ src/vs/amdX.ts | 6 +- src/vs/base/common/network.ts | 4 +- src/vs/base/common/platform.ts | 1 + .../languageDetectionWorkerServiceImpl.ts | 3 +- .../threadedBackgroundTokenizerFactory.ts | 3 +- .../textMateTokenizationFeatureImpl.ts | 4 +- .../browser/treeSitterLibraryService.ts | 4 +- 14 files changed, 124 insertions(+), 53 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index ed06b6a5aa8..0cd5be668a5 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -288,14 +288,17 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const productionDependencies = getProductionDependencies(root); const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk'); - const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) + const deps = es.merge( + gulp.src(dependenciesSrc, { base: '.', dot: true }), + gulp.src(['node_modules/vsda/**'], { base: 'node_modules', dot: true }) // retain vsda at root level of asar for backward compatibility + ) .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) .pipe(jsFilter.restore) - .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ + .pipe(createAsar(process.cwd(), [ '**/*.node', '**/@vscode/ripgrep/bin/*', '**/node-pty/build/Release/*', @@ -306,9 +309,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op '**/@vscode/vsce-sign/bin/*', ], [ '**/*.mk', - '!node_modules/vsda/**' // stay compatible with extensions that depend on us shipping `vsda` into ASAR ], [ - 'node_modules/vsda/**' // retain copy of `vsda` in node_modules for internal use + 'node_modules/vsda/**', // duplicate vsda in node_modules.asar.unpacked for backward compatibility ], 'node_modules.asar')); let all = es.merge( diff --git a/build/lib/asar.js b/build/lib/asar.js index 2da31a93904..c5691f96f2e 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -32,7 +32,7 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile return false; }; // Files that should be duplicated between - // node_modules.asar and node_modules + // node_modules.asar.unpacked/node_modules and node_modules.asar.unpacked const shouldDuplicateFile = (file) => { for (const duplicateGlob of duplicateGlobs) { if ((0, minimatch_1.default)(file.relative, duplicateGlob)) { @@ -94,14 +94,6 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile })); return; } - if (shouldDuplicateFile(file)) { - this.queue(new vinyl_1.default({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - } const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); if (shouldUnpack) { @@ -113,6 +105,16 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile stat: file.stat, contents: file.contents })); + const shouldDuplicate = shouldDuplicateFile(file); + if (shouldDuplicate) { + const rootRelative = file.relative.replace(/^node_modules\//, ''); + this.queue(new vinyl_1.default({ + base: '.', + path: path_1.default.join(destFilename + '.unpacked', rootRelative), + stat: file.stat, + contents: file.contents + })); + } } else { // The file goes inside of xx.asar diff --git a/build/lib/asar.ts b/build/lib/asar.ts index 5f2df925bde..55d44314000 100644 --- a/build/lib/asar.ts +++ b/build/lib/asar.ts @@ -38,7 +38,7 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: }; // Files that should be duplicated between - // node_modules.asar and node_modules + // node_modules.asar.unpacked/node_modules and node_modules.asar.unpacked const shouldDuplicateFile = (file: VinylFile): boolean => { for (const duplicateGlob of duplicateGlobs) { if (minimatch(file.relative, duplicateGlob)) { @@ -107,14 +107,7 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: })); return; } - if (shouldDuplicateFile(file)) { - this.queue(new VinylFile({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - } + const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); @@ -127,6 +120,17 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: stat: file.stat, contents: file.contents })); + + const shouldDuplicate = shouldDuplicateFile(file); + if (shouldDuplicate) { + const rootRelative = file.relative.replace(/^node_modules\//, ''); + this.queue(new VinylFile({ + base: '.', + path: path.join(destFilename + '.unpacked', rootRelative), + stat: file.stat, + contents: file.contents + })); + } } else { // The file goes inside of xx.asar out.push(file.contents); diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js index ae05d175da8..db0264ad927 100644 --- a/build/linux/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -46,8 +46,7 @@ async function getDependencies(packageType, buildDir, applicationName, arch) { throw new Error('Invalid RPM arch string ' + arch); } // Get the files for which we want to find dependencies. - const canAsar = false; // TODO@esm ASAR disabled in ESM - const nativeModulesPath = path_1.default.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); + const nativeModulesPath = path_1.default.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 46c6d6c099a..24cbcdc5bd3 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -47,8 +47,7 @@ export async function getDependencies(packageType: 'deb' | 'rpm', buildDir: stri } // Get the files for which we want to find dependencies. - const canAsar = false; // TODO@esm ASAR disabled in ESM - const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); + const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/src/bootstrap-esm.ts b/src/bootstrap-esm.ts index 54681a2fa9c..2031f34a2da 100644 --- a/src/bootstrap-esm.ts +++ b/src/bootstrap-esm.ts @@ -5,34 +5,74 @@ import * as fs from 'node:fs'; import { register } from 'node:module'; +import { sep } from 'node:path'; import { product, pkg } from './bootstrap-meta.js'; import './bootstrap-node.js'; import * as performance from './vs/base/common/performance.js'; import { INLSConfiguration } from './vs/nls.js'; -// Install a hook to module resolution to map 'fs' to 'original-fs' -if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { +// Prepare globals that are needed for running +globalThis._VSCODE_PRODUCT_JSON = { ...product }; +globalThis._VSCODE_PACKAGE_JSON = { ...pkg }; +globalThis._VSCODE_FILE_ROOT = import.meta.dirname; + +// Install a hook to module resolution to map dependencies into the asar archive +function enableASARSupport() { + if (!process.env['ELECTRON_RUN_AS_NODE'] && !process.versions['electron']) { + return; + } + + if (process.env['VSCODE_DEV']) { + return; + } + const jsCode = ` + import { pathToFileURL, fileURLToPath } from 'node:url'; + function isRelativeSpecifier(specifier) { + if (specifier[0] === '.') { + if (specifier.length === 1 || specifier[1] === '/') { return true; } + if (specifier[1] === '.') { + if (specifier.length === 2 || specifier[2] === '/') { return true; } + } + } + return false; + } + function normalizeDriveLetter(path) { + if (process.platform === 'win32' + && path.length >= 2 + && (path.charCodeAt(0) >= 65 && path.charCodeAt(0) <= 90 || path.charCodeAt(0) >= 97 && path.charCodeAt(0) <= 122) + && path.charCodeAt(1) === 58) { + return path[0].toLowerCase() + path.slice(1); + } + return path; + } + export async function initialize({ resourcesPath, asarPath }) { + globalThis.__resourcesPath = normalizeDriveLetter(resourcesPath); + globalThis.__asarPath = asarPath; + } export async function resolve(specifier, context, nextResolve) { - if (specifier === 'fs') { - return { - format: 'builtin', - shortCircuit: true, - url: 'node:original-fs' - }; + if (!isRelativeSpecifier(specifier) && context.parentURL) { + const currentPath = fileURLToPath(context.parentURL); + const normalizedCurrentPath = normalizeDriveLetter(currentPath); + if (normalizedCurrentPath.startsWith(globalThis.__resourcesPath)) { + const asarPath = normalizedCurrentPath.replace(globalThis.__resourcesPath, globalThis.__asarPath); + context.parentURL = pathToFileURL(asarPath); + } } // Defer to the next hook in the chain, which would be the // Node.js default resolve if this is the last user-specified loader. return nextResolve(specifier, context); }`; - register(`data:text/javascript;base64,${Buffer.from(jsCode).toString('base64')}`, import.meta.url); + register(`data:text/javascript;base64,${Buffer.from(jsCode).toString('base64')}`, import.meta.url, { + data: { + resourcesPath: `${process.resourcesPath}${sep}app`, + asarPath: `${process.resourcesPath}${sep}app${sep}node_modules.asar`, + } + }); } -// Prepare globals that are needed for running -globalThis._VSCODE_PRODUCT_JSON = { ...product }; -globalThis._VSCODE_PACKAGE_JSON = { ...pkg }; -globalThis._VSCODE_FILE_ROOT = import.meta.dirname; +enableASARSupport(); //#region NLS helpers diff --git a/src/bootstrap-node.ts b/src/bootstrap-node.ts index 8cb580e738b..354347707fa 100644 --- a/src/bootstrap-node.ts +++ b/src/bootstrap-node.ts @@ -29,6 +29,34 @@ if (!process.env['VSCODE_HANDLES_SIGPIPE']) { }); } +/** + * Helper to enable ASAR support. + */ +function enableASARSupport(): void { + if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { + const Module = require('node:module'); + const NODE_MODULES_PATH = path.join(import.meta.dirname, '../node_modules'); + const NODE_MODULES_ASAR_PATH = path.join(import.meta.dirname, '../node_modules.asar'); + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; + // @ts-ignore + Module._resolveLookupPaths = function (request, parent) { + const paths = originalResolveLookupPaths(request, parent); + if (Array.isArray(paths)) { + for (let i = 0, len = paths.length; i < len; i++) { + if (paths[i] === NODE_MODULES_PATH) { + paths.splice(i, 0, NODE_MODULES_ASAR_PATH); + break; + } + } + } + return paths; + }; + } +} + +enableASARSupport(); + // Setup current working directory in all our node & electron processes // - Windows: call `process.chdir()` to always set application folder as cwd // - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index 374d4f19faf..7de12318c11 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -9,8 +9,6 @@ import { IProductConfiguration } from './base/common/product.js'; import { URI } from './base/common/uri.js'; import { generateUuid } from './base/common/uuid.js'; -export const canASAR = false; // TODO@esm: ASAR disabled in ESM - declare const window: any; declare const document: any; declare const self: any; @@ -218,7 +216,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN // bit of a special case for: src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts scriptSrc = nodeModulePath; } else { - const useASAR = (canASAR && isBuilt && !platform.isWeb); + const useASAR = (isBuilt && (platform.isElectron || (platform.isWebWorker && platform.hasElectronUserAgent))); const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); const resourcePath: AppResourcePath = `${actualNodeModulesPath}/${nodeModulePath}`; scriptSrc = FileAccess.asBrowserUri(resourcePath).toString(true); @@ -231,7 +229,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN export function resolveAmdNodeModulePath(nodeModuleName: string, pathInsideNodeModule: string): string { const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; const isBuilt = Boolean((product ?? globalThis.vscode?.context?.configuration()?.product)?.commit); - const useASAR = (canASAR && isBuilt && !platform.isWeb); + const useASAR = (isBuilt && (platform.isElectron || (platform.isWebWorker && platform.hasElectronUserAgent))); const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`; const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 64ebe94abd9..2d7f85821e1 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -257,8 +257,8 @@ export type AppResourcePath = ( export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions'; export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules'; -export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar'; -export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked'; +export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar/node_modules'; +export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked/node_modules'; export const VSCODE_AUTHORITY = 'vscode-app'; diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 10bb68dfd97..4dcb6cae37e 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -274,6 +274,7 @@ export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0); export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0)); export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0); export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); +export const hasElectronUserAgent = !!(userAgent && userAgent.indexOf('Electron') >= 0); export function isBigSurOrNewer(osVersion: string): boolean { return parseFloat(osVersion) >= 20; diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 5a2459918f4..ceccd357414 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -21,7 +21,6 @@ import { IEditorService } from '../../editor/common/editorService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { LRUCache } from '../../../../base/common/map.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { canASAR } from '../../../../amdX.js'; import { createWebWorker } from '../../../../base/browser/webWorkerFactory.js'; import { WorkerTextModelSyncClient } from '../../../../editor/common/services/textModelSync/textModelSync.impl.js'; import { ILanguageDetectionWorker, LanguageDetectionWorkerHost } from './languageDetectionWorker.protocol.js'; @@ -66,7 +65,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet ) { super(); - const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; + const useAsar = this._environmentService.isBuilt && !isWeb; this._languageDetectionWorkerClient = this._register(new LanguageDetectionWorkerClient( modelService, languageService, diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index 59502ab69cc..8af23291b6d 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { canASAR } from '../../../../../amdX.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } from '../../../../../base/common/network.js'; import { IObservable } from '../../../../../base/common/observable.js'; @@ -129,7 +128,7 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`; - const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; + const useAsar = this._environmentService.isBuilt && !isWeb; const onigurumaLocation: AppResourcePath = useAsar ? onigurumaModuleLocationAsar : onigurumaModuleLocation; const onigurumaWASM: AppResourcePath = `${onigurumaLocation}/release/onig.wasm`; diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts index 304c7c18d0b..36239e1abf8 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { canASAR, importAMDNodeModule, resolveAmdNodeModulePath } from '../../../../amdX.js'; +import { importAMDNodeModule, resolveAmdNodeModulePath } from '../../../../amdX.js'; import * as domStylesheets from '../../../../base/browser/domStylesheets.js'; import { equals as equalArray } from '../../../../base/common/arrays.js'; import { Color } from '../../../../base/common/color.js'; @@ -390,7 +390,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate // We therefore use the non-streaming compiler :(. return await response.arrayBuffer(); } else { - const response = await fetch(canASAR && this._environmentService.isBuilt + const response = await fetch(this._environmentService.isBuilt ? FileAccess.asBrowserUri(`${nodeModulesAsarUnpackedPath}/vscode-oniguruma/release/onig.wasm`).toString(true) : FileAccess.asBrowserUri(`${nodeModulesPath}/vscode-oniguruma/release/onig.wasm`).toString(true)); return response; diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index b6e82609b2d..6a2cef15913 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -7,7 +7,7 @@ import type { Parser, Language, Query } from '@vscode/tree-sitter-wasm'; import { IReader, ObservablePromise } from '../../../../base/common/observable.js'; import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import { canASAR, importAMDNodeModule } from '../../../../amdX.js'; +import { importAMDNodeModule } from '../../../../amdX.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { FileOperationResult, IFileContent, IFileService, toFileOperationResult } from '../../../../platform/files/common/files.js'; @@ -25,7 +25,7 @@ const MODULE_LOCATION_SUBPATH = `@vscode/tree-sitter-wasm/wasm`; const FILENAME_TREESITTER_WASM = `tree-sitter.wasm`; export function getModuleLocation(environmentService: IEnvironmentService): AppResourcePath { - return `${(canASAR && environmentService.isBuilt) ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; + return `${environmentService.isBuilt ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; } export class TreeSitterLibraryService extends Disposable implements ITreeSitterLibraryService { From 1395059974d8f5e904f9fa1b1359c0b5d8643bdf Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:00:48 +0200 Subject: [PATCH 1516/4355] SCM - move overflow ellipsis to the left hand side of the branch label (#272823) * SCM - move overflow ellipsis to the left hand side of the branch label * Update src/vs/workbench/contrib/scm/browser/util.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/scm/browser/media/scm.css | 8 ++++---- src/vs/workbench/contrib/scm/browser/util.ts | 14 +++++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 8ca40733e2e..f9263e19e20 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -78,17 +78,17 @@ .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(2) { min-width: 20px; - .action-label > span:nth-child(1), - .action-label > span:nth-child(2) { + .action-label > span:nth-child(1) { padding-right: 2px; } } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label { +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > span:nth-child(2) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - display: block; + min-width: 0; + direction: rtl; } .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > .codicon { diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 1d9b9215cec..ce442e443fe 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -122,7 +122,19 @@ class StatusBarActionViewItem extends ActionViewItem { protected override updateLabel(): void { if (this.options.label && this.label) { - reset(this.label, ...renderLabelWithIcons(this.action.label)); + // Convert text nodes to span elements to enable + // text overflow on the left hand side of the label + const elements = renderLabelWithIcons(this.action.label) + .map(element => { + if (typeof element === 'string') { + const span = document.createElement('span'); + span.textContent = element; + return span; + } + return element; + }); + + reset(this.label, ...elements); } } } From 898436fbe6ac9c42ebd63754ab59a65f1e8c4a73 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 09:28:38 +0200 Subject: [PATCH 1517/4355] Clean up code layering suppressions (fix #272777) (#272834) * Clean up code layering suppressions (fix #272777) * Update test/unit/electron/index.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 5 +- .../externalTerminalService.test.ts | 0 .../workspaces.test.ts | 0 .../workspacesHistoryStorage.test.ts | 0 .../runInTerminalTool.test.ts | 10 +--- .../treeSitterTokenizationFeature.test.ts | 8 +-- .../electron-browser/workbenchTestServices.ts | 41 ++++++++++++- test/unit/electron/index.js | 58 ++++++++++++++++++- 8 files changed, 106 insertions(+), 16 deletions(-) rename src/vs/platform/externalTerminal/test/{electron-main => node}/externalTerminalService.test.ts (100%) rename src/vs/platform/workspaces/test/{electron-main => node}/workspaces.test.ts (100%) rename src/vs/platform/workspaces/test/{electron-main => node}/workspacesHistoryStorage.test.ts (100%) rename src/vs/workbench/test/{electron-main => electron-browser}/treeSitterTokenizationFeature.test.ts (97%) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ef2d6e8aa67..d3e08231baf 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -48,10 +48,10 @@ Each extension follows the standard VS Code extension structure with `package.js ## Validating TypeScript changes -MANDATORY: Always check the `VS Code - Build` watch task output (via #get_task_output) for compilation errors before running ANY script or declaring work complete, then fix all compilation errors before moving forward. +MANDATORY: Always check the `VS Code - Build` watch task output via #runTasks/getTaskOutput for compilation errors before running ANY script or declaring work complete, then fix all compilation errors before moving forward. - NEVER run tests if there are compilation errors -- NEVER use `npm run compile` to compile TypeScript files but call #get_task_output instead +- NEVER use `npm run compile` to compile TypeScript files but call #runTasks/getTaskOutput instead ### TypeScript compilation steps - Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes @@ -132,3 +132,4 @@ function f(x: number, y: string): void { } - Use `describe` and `test` consistently with existing patterns - If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task - Do not use `any` or `unknown` as the type for variables, parameters, or return values unless absolutely necessary. If they need type annotations, they should have proper types or interfaces defined. +- Never duplicate imports. Always reuse existing imports if they are present. diff --git a/src/vs/platform/externalTerminal/test/electron-main/externalTerminalService.test.ts b/src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts similarity index 100% rename from src/vs/platform/externalTerminal/test/electron-main/externalTerminalService.test.ts rename to src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts diff --git a/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts b/src/vs/platform/workspaces/test/node/workspaces.test.ts similarity index 100% rename from src/vs/platform/workspaces/test/electron-main/workspaces.test.ts rename to src/vs/platform/workspaces/test/node/workspaces.test.ts diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts b/src/vs/platform/workspaces/test/node/workspacesHistoryStorage.test.ts similarity index 100% rename from src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts rename to src/vs/platform/workspaces/test/node/workspacesHistoryStorage.test.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 62723155742..4b3c93c0bf2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -30,11 +30,7 @@ import { FileService } from '../../../../../../platform/files/common/fileService import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { Schemas } from '../../../../../../base/common/network.js'; - -// HACK: This test lives in electron-browser/ to ensure this node import works if the test is run in -// web tests https://github.com/microsoft/vscode/issues/272777 -// eslint-disable-next-line local/code-layering, local/code-import-patterns -import { DiskFileSystemProvider } from '../../../../../../platform/files/node/diskFileSystemProvider.js'; +import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -65,8 +61,8 @@ suite('RunInTerminalTool', () => { const logService = new NullLogService(); fileService = store.add(new FileService(logService)); - const diskFileSystemProvider = store.add(new DiskFileSystemProvider(logService)); - store.add(fileService.registerProvider(Schemas.file, diskFileSystemProvider)); + const fileSystemProvider = new TestIPCFileSystemProvider(); + store.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); setConfig(TerminalChatAgentToolsSettingId.EnableAutoApprove, true); terminalServiceDisposeEmitter = new Emitter(); diff --git a/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts b/src/vs/workbench/test/electron-browser/treeSitterTokenizationFeature.test.ts similarity index 97% rename from src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts rename to src/vs/workbench/test/electron-browser/treeSitterTokenizationFeature.test.ts index 1be3ab3d78d..4abd6dce6de 100644 --- a/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts +++ b/src/vs/workbench/test/electron-browser/treeSitterTokenizationFeature.test.ts @@ -20,7 +20,7 @@ import { ModelService } from '../../../editor/common/services/modelService.js'; import { FileService } from '../../../platform/files/common/fileService.js'; import { Schemas } from '../../../base/common/network.js'; -import { DiskFileSystemProvider } from '../../../platform/files/node/diskFileSystemProvider.js'; +import { TestIPCFileSystemProvider } from './workbenchTestServices.js'; import { ILanguageService } from '../../../editor/common/languages/language.js'; import { LanguageService } from '../../../editor/common/services/languageService.js'; import { TestColorTheme, TestThemeService } from '../../../platform/theme/test/common/testThemeService.js'; @@ -40,7 +40,6 @@ import { Color } from '../../../base/common/color.js'; import { Range } from '../../../editor/common/core/range.js'; import { TokenUpdate } from '../../../editor/common/model/tokens/treeSitter/tokenStore.js'; import { ITreeSitterLibraryService } from '../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -// eslint-disable-next-line local/code-layering, local/code-import-patterns import { TreeSitterLibraryService } from '../../services/treeSitter/browser/treeSitterLibraryService.js'; import { TokenizationTextModelPart } from '../../../editor/common/model/tokens/tokenizationTextModelPart.js'; import { TreeSitterSyntaxTokenBackend } from '../../../editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.js'; @@ -49,7 +48,6 @@ import { ITextModel } from '../../../editor/common/model.js'; import { TreeSitterTokenizationImpl } from '../../../editor/common/model/tokens/treeSitter/treeSitterTokenizationImpl.js'; import { autorunHandleChanges, recordChanges, waitForState } from '../../../base/common/observable.js'; import { ITreeSitterThemeService } from '../../../editor/common/services/treeSitter/treeSitterThemeService.js'; -// eslint-disable-next-line local/code-layering, local/code-import-patterns import { TreeSitterThemeService } from '../../services/treeSitter/browser/treeSitterThemeService.js'; class MockTelemetryService implements ITelemetryService { @@ -122,8 +120,8 @@ suite('Tree Sitter TokenizationFeature', function () { instantiationService.set(ILanguageConfigurationService, languageConfigurationService); fileService = disposables.add(instantiationService.createInstance(FileService)); - const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(logService)); - disposables.add(fileService.registerProvider(Schemas.file, diskFileSystemProvider)); + const fileSystemProvider = new TestIPCFileSystemProvider(); + disposables.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); instantiationService.set(IFileService, fileService); const libraryService = disposables.add(instantiationService.createInstance(TreeSitterLibraryService)); diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 05f88477f79..773284e3a43 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -19,7 +19,7 @@ import { IEnvironmentService, INativeEnvironmentService } from '../../../platfor import { IExtensionManagementService } from '../../../platform/extensionManagement/common/extensionManagement.js'; import { AbstractNativeExtensionTipsService } from '../../../platform/extensionManagement/common/extensionTipsService.js'; import { IExtensionRecommendationNotificationService } from '../../../platform/extensionRecommendations/common/extensionRecommendations.js'; -import { IFileService } from '../../../platform/files/common/files.js'; +import { IFileService, IFileSystemProvider, FileSystemProviderCapabilities, IFileReadStreamOptions, IFileWriteOptions, IFileOpenOptions, IFileDeleteOptions, IFileOverwriteOptions, IStat, FileType, IWatchOptions } from '../../../platform/files/common/files.js'; import { FileService } from '../../../platform/files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../platform/files/common/inMemoryFilesystemProvider.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; @@ -49,6 +49,7 @@ import { IWorkingCopyService } from '../../services/workingCopy/common/workingCo import { NativeWorkingCopyBackupService } from '../../services/workingCopy/electron-browser/workingCopyBackupService.js'; import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestLifecycleService, TestTextFileService } from '../browser/workbenchTestServices.js'; import { TestContextService, TestFileService } from '../common/workbenchTestServices.js'; +import { ReadableStreamEvents } from '../../../base/common/stream.js'; export class TestSharedProcessService implements ISharedProcessService { @@ -325,3 +326,41 @@ export class TestNativeWorkingCopyBackupService extends NativeWorkingCopyBackupS return fileContents.value.toString(); } } + +export class TestIPCFileSystemProvider implements IFileSystemProvider { + + readonly capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive; + + readonly onDidChangeCapabilities = Event.None; + readonly onDidChangeFile = Event.None; + + async stat(resource: URI): Promise { + const { ipcRenderer } = require('electron'); + const stats = await ipcRenderer.invoke('vscode:statFile', resource.fsPath); + return { + type: stats.isDirectory ? FileType.Directory : (stats.isFile ? FileType.File : FileType.Unknown), + ctime: stats.ctimeMs, + mtime: stats.mtimeMs, + size: stats.size, + permissions: stats.isReadonly ? 1 /* FilePermission.Readonly */ : undefined + }; + } + + async readFile(resource: URI): Promise { + const { ipcRenderer } = require('electron'); + const result = await ipcRenderer.invoke('vscode:readFile', resource.fsPath); + return VSBuffer.wrap(result).buffer; + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { return { dispose: () => { } }; } + mkdir(resource: URI): Promise { throw new Error('mkdir not implemented in test provider'); } + readdir(resource: URI): Promise<[string, FileType][]> { throw new Error('readdir not implemented in test provider'); } + delete(resource: URI, opts: IFileDeleteOptions): Promise { throw new Error('delete not implemented in test provider'); } + rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { throw new Error('rename not implemented in test provider'); } + writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { throw new Error('writeFile not implemented in test provider'); } + readFileStream?(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { throw new Error('readFileStream not implemented in test provider'); } + open?(resource: URI, opts: IFileOpenOptions): Promise { throw new Error('open not implemented in test provider'); } + close?(fd: number): Promise { throw new Error('close not implemented in test provider'); } + read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { throw new Error('read not implemented in test provider'); } + write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { throw new Error('write not implemented in test provider'); } +} diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index bf89650d0aa..842feb80e90 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -13,7 +13,7 @@ process.env.MOCHA_COLORS = '1'; const { app, BrowserWindow, ipcMain, crashReporter, session } = require('electron'); const product = require('../../../product.json'); const { tmpdir } = require('os'); -const { existsSync, mkdirSync } = require('fs'); +const { existsSync, mkdirSync, promises } = require('fs'); const path = require('path'); const mocha = require('mocha'); const events = require('events'); @@ -252,6 +252,62 @@ app.on('ready', () => { // No-op since invoke the IPC as part of IIFE in the preload. ipcMain.handle('vscode:fetchShellEnv', event => { }); + /** + * Validates that a file path is within the project root for security purposes. + * @param {string} filePath - The file path to validate + * @throws {Error} If the path is outside the project root + */ + function validatePathWithinProject(filePath) { + const projectRoot = path.join(__dirname, '../../..'); + const resolvedPath = path.resolve(filePath); + const normalizedRoot = path.resolve(projectRoot); + + // On Windows, paths are case-insensitive + const isWindows = process.platform === 'win32'; + const rel = path.relative( + isWindows ? normalizedRoot.toLowerCase() : normalizedRoot, + isWindows ? resolvedPath.toLowerCase() : resolvedPath + ); + if (rel.startsWith('..') || path.isAbsolute(rel)) { + const error = new Error(`Access denied: Path '${filePath}' is outside the project root`); + console.error(error.message); + throw error; + } + } + + // Handle file reading for tests + ipcMain.handle('vscode:readFile', async (event, filePath) => { + validatePathWithinProject(filePath); + + try { + return await promises.readFile(path.resolve(filePath)); + } catch (error) { + console.error(`Error reading file ${filePath}:`, error); + throw error; + } + }); + + // Handle file stat for tests + ipcMain.handle('vscode:statFile', async (event, filePath) => { + validatePathWithinProject(filePath); + + try { + const stats = await promises.stat(path.resolve(filePath)); + return { + isFile: stats.isFile(), + isDirectory: stats.isDirectory(), + isSymbolicLink: stats.isSymbolicLink(), + ctimeMs: stats.ctimeMs, + mtimeMs: stats.mtimeMs, + size: stats.size, + isReadonly: (stats.mode & 0o200) === 0 // Check if owner write bit is not set + }; + } catch (error) { + console.error(`Error stating file ${filePath}:`, error); + throw error; + } + }); + const win = new BrowserWindow({ height: 600, width: 800, From 6c7b7fd8281647c7a84284f28c3e69299b25bd02 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 09:29:06 +0200 Subject: [PATCH 1518/4355] chat - tweak size of icon state overlays (#272835) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 4c137ba57d2..5f154290a74 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1295,9 +1295,9 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-input-toolbars .tool-warning-indicator { position: absolute; - bottom: 1px; - right: 1px; - font-size: 9px !important; + bottom: 0; + right: 0; + font-size: 12px !important; color: var(--vscode-problemsWarningIcon-foreground); background: var(--vscode-input-background); width: fit-content; @@ -1589,9 +1589,9 @@ have to be updated for changes to the rules above, or to support more deeply nes .chat-mcp-state-indicator { position: absolute; - bottom: 1px; - right: 1px; - font-size: 9px !important; + bottom: 0; + right: 0; + font-size: 12px !important; background: var(--vscode-input-background); width: fit-content; height: fit-content; From 02c7af9c58c316d12e5574b6229c582af6382a59 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Thu, 23 Oct 2025 00:34:37 -0700 Subject: [PATCH 1519/4355] Enabled search in agent sessions panes for GH coding agent, local sessions, Codex CLI --- .../chat/browser/chatSessions/view/sessionsViewPane.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 519b9574af9..615e0d67915 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -348,6 +348,16 @@ export class SessionsViewPane extends ViewPane { }, accessibilityProvider, identityProvider, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (session: ChatSessionItemWithProvider) => { + const parts = [ + session.label || '', + session.id || '', + typeof session.description === 'string' ? session.description : (session.description?.value || '') + ]; + return parts.filter(text => text.length > 0).join(' '); + } + }, multipleSelectionSupport: false, overrideStyles: { listBackground: undefined From 8fd821a0c7c0a04724bc51f0566b438e6f35052e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:22:25 +0200 Subject: [PATCH 1520/4355] Remove experimental badge from prompt file support (#272541) * Initial plan * Remove chat.promptFiles setting and experimental tags from prompt file settings Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Fix compilation errors - remove unused imports and fix variable references Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * Update policy data file to remove chat.promptFiles entry Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> * remove unused collectAgentInstructionsOnly --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> Co-authored-by: Harald Kirschner Co-authored-by: Martin Aeschlimann --- build/lib/policies/policyData.jsonc | 14 -- .../contrib/chat/browser/chat.contribution.ts | 35 +---- .../contrib/chat/browser/chatWidget.ts | 17 +-- .../promptSyntax/attachInstructionsAction.ts | 15 +-- .../browser/promptSyntax/chatModeActions.ts | 7 +- .../promptSyntax/newPromptFileActions.ts | 8 +- .../browser/promptSyntax/runPromptAction.ts | 17 ++- .../promptSyntax/saveToPromptAction.ts | 4 +- .../computeAutomaticInstructions.ts | 6 - .../chat/common/promptSyntax/config/config.ts | 25 +--- .../languageProviders/promptValidator.ts | 11 -- .../service/promptsServiceImpl.ts | 12 -- .../promptSytntax/promptValidator.test.ts | 4 +- .../common/promptSyntax/config/config.test.ts | 127 +----------------- .../service/promptsService.test.ts | 1 - .../utils/promptFilesLocator.test.ts | 2 +- 16 files changed, 31 insertions(+), 274 deletions(-) diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc index 096b911e5d3..3b8f1cdf0f5 100644 --- a/build/lib/policies/policyData.jsonc +++ b/build/lib/policies/policyData.jsonc @@ -155,20 +155,6 @@ "type": "boolean", "default": true }, - { - "key": "chat.promptFiles", - "name": "ChatPromptFiles", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.promptFiles.policy", - "value": "Enables reusable prompt and instruction files in Chat sessions." - } - }, - "type": "boolean", - "default": true - }, { "key": "chat.tools.terminal.enableAutoApprove", "name": "ChatToolsTerminalEnableAutoApprove", diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index afa7240fdf1..f7e955dae90 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -519,35 +519,6 @@ configurationRegistry.registerConfiguration({ } }, }, - [PromptsConfig.KEY]: { - type: 'boolean', - title: nls.localize( - 'chat.reusablePrompts.config.enabled.title', - "Prompt Files", - ), - markdownDescription: nls.localize( - 'chat.reusablePrompts.config.enabled.description', - "Enable reusable prompt (`*{0}`) and instruction files (`*{1}`) in Chat sessions. [Learn More]({2}).", - PROMPT_FILE_EXTENSION, - INSTRUCTION_FILE_EXTENSION, - PROMPT_DOCUMENTATION_URL, - ), - default: true, - restricted: true, - disallowConfigurationDefault: true, - tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], - policy: { - name: 'ChatPromptFiles', - category: PolicyCategory.InteractiveSession, - minimumVersion: '1.99', - localization: { - description: { - key: 'chat.promptFiles.policy', - value: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") - } - } - } - }, [PromptsConfig.INSTRUCTIONS_LOCATION_KEY]: { type: 'object', title: nls.localize( @@ -565,7 +536,7 @@ configurationRegistry.registerConfiguration({ }, additionalProperties: { type: 'boolean' }, restricted: true, - tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], + tags: ['prompts', 'reusable prompts', 'prompt snippets', 'instructions'], examples: [ { [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true, @@ -594,7 +565,7 @@ configurationRegistry.registerConfiguration({ additionalProperties: { type: 'boolean' }, unevaluatedProperties: { type: 'boolean' }, restricted: true, - tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], + tags: ['prompts', 'reusable prompts', 'prompt snippets', 'instructions'], examples: [ { [PROMPT_DEFAULT_SOURCE_FOLDER]: true, @@ -671,7 +642,7 @@ configurationRegistry.registerConfiguration({ { type: 'string' } ] }, - tags: ['experimental'], + tags: ['prompts', 'reusable prompts', 'prompt snippets', 'instructions'], examples: [ { 'plan': true, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index cd418bd3caa..7903df3b7d9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2537,11 +2537,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } private async _applyPromptFileIfSet(requestInput: IChatRequestInputOptions): Promise { - if (!PromptsConfig.enabled(this.configurationService)) { - // if prompts are not enabled, we don't need to do anything - return undefined; - } - let parseResult: ParsedPromptFile | undefined; // first check if the input has a prompt slash command @@ -2975,16 +2970,10 @@ export class ChatWidget extends Disposable implements IChatWidget { * - instructions referenced in an already included instruction file */ private async _autoAttachInstructions({ attachedContext }: IChatRequestInputOptions): Promise { - const promptsConfigEnabled = PromptsConfig.enabled(this.configurationService); - this.logService.debug(`ChatWidget#_autoAttachInstructions: ${PromptsConfig.KEY}: ${promptsConfigEnabled}`); + this.logService.debug(`ChatWidget#_autoAttachInstructions: prompt files are always enabled`); - if (promptsConfigEnabled) { - const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, this._getReadTool()); - await computer.collect(attachedContext, CancellationToken.None); - } else { - const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, undefined); - await computer.collectAgentInstructionsOnly(attachedContext, CancellationToken.None); - } + const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, this._getReadTool()); + await computer.collect(attachedContext, CancellationToken.None); } private _getReadTool(): IToolData | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index c485b72a2e1..28bed45e5ba 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -9,17 +9,16 @@ import { URI } from '../../../../../base/common/uri.js'; import { localize, localize2 } from '../../../../../nls.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { PromptFilePickers } from './pickers/promptFilePickers.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPicker } from '../chatContextPickService.js'; import { IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { getCleanPromptName } from '../../common/promptSyntax/config/promptFileLocations.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { INSTRUCTIONS_LANGUAGE_ID, PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { compare } from '../../../../../base/common/strings.js'; import { IPromptFileVariableEntry, PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js'; @@ -28,7 +27,6 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; /** * Action ID for the `Attach Instruction` action. @@ -78,7 +76,7 @@ class AttachInstructionsAction extends Action2 { id: ATTACH_INSTRUCTIONS_ACTION_ID, title: localize2('attach-instructions.capitalized.ellipses', "Attach Instructions..."), f1: false, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Slash, @@ -86,7 +84,7 @@ class AttachInstructionsAction extends Action2 { }, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled) + when: ChatContextKeys.enabled } }); } @@ -143,11 +141,11 @@ class ManageInstructionsFilesAction extends Action2 { shortTitle: localize2('configure-instructions.short', "Chat Instructions"), icon: Codicon.bookmark, f1: true, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, menu: { id: CHAT_CONFIG_MENU_ID, - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), + when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), order: 10, group: '1_level' } @@ -222,11 +220,10 @@ export class ChatInstructionsPickerPick implements IChatContextPickerItem { constructor( @IPromptsService private readonly promptsService: IPromptsService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { } isEnabled(widget: IChatWidget): Promise | boolean { - return PromptsConfig.enabled(this.configurationService) && !!widget.attachmentCapabilities.supportsInstructionAttachments; + return !!widget.attachmentCapabilities.supportsInstructionAttachments; } asPicker(): IChatContextPicker { diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts index 6944ab99f59..23d7081f147 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/chatModeActions.ts @@ -7,15 +7,14 @@ import { CHAT_CATEGORY, CHAT_CONFIG_MENU_ID } from '../actions/chatActions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { localize, localize2 } from '../../../../../nls.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { PromptFilePickers } from './pickers/promptFilePickers.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { ChatViewId } from '../chat.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; abstract class ConfigAgentActionImpl extends Action2 { public override async run(accessor: ServicesAccessor): Promise { @@ -67,12 +66,12 @@ class ManageAgentsAction extends ConfigAgentActionImpl { shortTitle: localize('configure-agents.short', "Agents"), icon: Codicon.bookmark, f1: true, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, menu: [ { id: CHAT_CONFIG_MENU_ID, - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), + when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), order: 10, group: '0_level' } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index d96288a817e..39cbb0f4a50 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -10,14 +10,12 @@ import { SnippetController2 } from '../../../../../editor/contrib/snippet/browse import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { INotificationService, NeverShowAgainScope, Severity } from '../../../../../platform/notification/common/notification.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { getLanguageIdForPromptsType, PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { IUserDataSyncEnablementService, SyncResource } from '../../../../../platform/userDataSync/common/userDataSync.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; @@ -37,14 +35,14 @@ class AbstractNewPromptFileAction extends Action2 { id, title, f1: false, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib }, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled) + when: ChatContextKeys.enabled } }); } @@ -202,7 +200,7 @@ class NewUntitledPromptFileAction extends Action2 { id: 'workbench.command.new.untitled.prompt', title: localize2('commands.new.untitled.prompt.title', "New Untitled Prompt File"), f1: true, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts index 4d8ee257b2d..048a3b6c432 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts @@ -11,21 +11,20 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { assertDefined } from '../../../../../base/common/types.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { ResourceContextKey } from '../../../../common/contextkeys.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; import { ILocalizedString, localize, localize2 } from '../../../../../nls.js'; import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js'; import { ICommandAction } from '../../../../../platform/action/common/action.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { PromptFilePickers } from './pickers/promptFilePickers.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; @@ -35,7 +34,7 @@ import { IPromptsService } from '../../common/promptSyntax/service/promptsServic * Condition for the `Run Current Prompt` action. */ const EDITOR_ACTIONS_CONDITION = ContextKeyExpr.and( - ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + ChatContextKeys.enabled, ResourceContextKey.HasResource, ResourceContextKey.LangId.isEqualTo(PROMPT_LANGUAGE_ID), ); @@ -101,7 +100,7 @@ abstract class RunPromptBaseAction extends Action2 { id: options.id, title: options.title, f1: false, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, icon: options.icon, keybinding: { @@ -194,9 +193,9 @@ class RunSelectedPromptAction extends Action2 { title: localize2('run-prompt.capitalized.ellipses', "Run Prompt..."), icon: Codicon.bookmark, f1: true, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, keybinding: { - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + when: ChatContextKeys.enabled, weight: KeybindingWeight.WorkbenchContrib, primary: COMMAND_KEY_BINDING, }, @@ -250,11 +249,11 @@ class ManagePromptFilesAction extends Action2 { shortTitle: localize2('configure-prompts.short', "Prompt Files"), icon: Codicon.bookmark, f1: true, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, menu: { id: CHAT_CONFIG_MENU_ID, - when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), + when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), order: 11, group: '0_level' }, diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts index e28099c4039..500ebd4a9aa 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts @@ -6,10 +6,8 @@ import { localize2 } from '../../../../../nls.js'; import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { PromptsConfig } from '../../common/promptSyntax/config/config.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { chatSubcommandLeader, IParsedChatRequest } from '../../common/chatParserTypes.js'; @@ -54,7 +52,7 @@ class SaveToPromptAction extends Action2 { "Save chat session to a prompt file", ), f1: false, - precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled), + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, }); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index a879a7c594e..ace86460b2f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -104,12 +104,6 @@ export class ComputeAutomaticInstructions { this.sendTelemetry(telemetryEvent); } - public async collectAgentInstructionsOnly(variables: ChatRequestVariableSet, token: CancellationToken): Promise { - const telemetryEvent: InstructionsCollectionEvent = newInstructionsCollectionEvent(); - await this._addAgentInstructions(variables, telemetryEvent, token); - this.sendTelemetry(telemetryEvent); - } - private sendTelemetry(telemetryEvent: InstructionsCollectionEvent): void { // Emit telemetry telemetryEvent.totalInstructionsCount = telemetryEvent.agentInstructionsCount + telemetryEvent.referencedInstructionsCount + telemetryEvent.applyingInstructionsCount + telemetryEvent.listedInstructionsCount; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts index 9f83b30ae32..a1b91611fdb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/config.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import type { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; import { URI } from '../../../../../../base/common/uri.js'; import { PromptsType } from '../promptTypes.js'; import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, getPromptFileDefaultLocation } from './promptFileLocations.js'; /** * Configuration helper for the `reusable prompts` feature. - * @see {@link PromptsConfig.KEY}, {@link PromptsConfig.PROMPT_LOCATIONS_KEY}, {@link PromptsConfig.INSTRUCTIONS_LOCATION_KEY}, {@link PromptsConfig.MODE_LOCATION_KEY}, or {@link PromptsConfig.PROMPT_FILES_SUGGEST_KEY}. + * @see {@link PromptsConfig.PROMPT_LOCATIONS_KEY}, {@link PromptsConfig.INSTRUCTIONS_LOCATION_KEY}, {@link PromptsConfig.MODE_LOCATION_KEY}, or {@link PromptsConfig.PROMPT_FILES_SUGGEST_KEY}. * * ### Functions * - * - {@link enabled} allows to check if the feature is enabled * - {@link getLocationsValue} allows to current read configuration value * - {@link promptSourceFolders} gets list of source folders for prompt files * - {@link getPromptFilesRecommendationsValue} gets prompt file recommendation configuration @@ -46,12 +44,6 @@ import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, getPr * ``` */ export namespace PromptsConfig { - /** - * Configuration key for the `reusable prompts` feature - * (also known as `prompt files`, `prompt instructions`, etc.). - */ - export const KEY = 'chat.promptFiles'; - /** * Configuration key for the locations of reusable prompt files. */ @@ -86,21 +78,6 @@ export namespace PromptsConfig { */ export const USE_NESTED_AGENT_MD = 'chat.useNestedAgentsMdFiles'; - /** - * Checks if the feature is enabled. - * @see {@link PromptsConfig.KEY}. - */ - export function enabled(configService: IConfigurationService): boolean { - const enabledValue = configService.getValue(PromptsConfig.KEY); - - return asBoolean(enabledValue) ?? false; - } - - /** - * Context key expression for the `reusable prompts` feature `enabled` status. - */ - export const enabledCtx = ContextKeyExpr.equals(`config.${PromptsConfig.KEY}`, true); - /** * Get value of the `reusable prompt locations` configuration setting. * @see {@link PROMPT_LOCATIONS_CONFIG_KEY}, {@link INSTRUCTIONS_LOCATIONS_CONFIG_KEY}, {@link MODE_LOCATIONS_CONFIG_KEY}. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index fecabd585c0..54a2b836a8a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -9,7 +9,6 @@ import { Range } from '../../../../../../editor/common/core/range.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { localize } from '../../../../../../nls.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js'; import { IChatMode, IChatModeService } from '../../chatModes.js'; @@ -18,7 +17,6 @@ import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../langua import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IArrayValue, IHeaderAttribute, ParsedPromptFile } from '../promptFileParser.js'; -import { PromptsConfig } from '../config/config.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -411,7 +409,6 @@ export class PromptValidatorContribution extends Disposable { constructor( @IModelService private modelService: IModelService, @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService private configService: IConfigurationService, @IMarkerService private readonly markerService: IMarkerService, @IPromptsService private readonly promptsService: IPromptsService, @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @@ -422,18 +419,10 @@ export class PromptValidatorContribution extends Disposable { this.validator = instantiationService.createInstance(PromptValidator); this.updateRegistration(); - this._register(this.configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(PromptsConfig.KEY)) { - this.updateRegistration(); - } - })); } updateRegistration(): void { this.localDisposables.clear(); - if (!PromptsConfig.enabled(this.configService)) { - return; - } const trackers = new ResourceMap(); this.localDisposables.add(toDisposable(() => { trackers.forEach(tracker => tracker.dispose()); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 9a53d93c2f9..5a8bd72a3e4 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -155,10 +155,6 @@ export class PromptsService extends Disposable implements IPromptsService { } public async listPromptFiles(type: PromptsType, token: CancellationToken): Promise { - if (!PromptsConfig.enabled(this.configurationService)) { - return []; - } - const prompts = await Promise.all([ this.fileLocator.listFiles(type, PromptsStorage.user, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.user, type } satisfies IUserPromptPath))), this.fileLocator.listFiles(type, PromptsStorage.local, token).then(uris => uris.map(uri => ({ uri, storage: PromptsStorage.local, type } satisfies ILocalPromptPath))), @@ -169,10 +165,6 @@ export class PromptsService extends Disposable implements IPromptsService { } public async listPromptFilesForStorage(type: PromptsType, storage: PromptsStorage, token: CancellationToken): Promise { - if (!PromptsConfig.enabled(this.configurationService)) { - return []; - } - switch (storage) { case PromptsStorage.extension: return this.getExtensionContributions(type); @@ -190,10 +182,6 @@ export class PromptsService extends Disposable implements IPromptsService { } public getSourceFolders(type: PromptsType): readonly IPromptPath[] { - if (!PromptsConfig.enabled(this.configurationService)) { - return []; - } - const result: IPromptPath[] = []; if (type === PromptsType.agent) { diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index c6b0d403231..241fe702ba7 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -8,8 +8,8 @@ import assert from 'assert'; import { ResourceSet } from '../../../../../../base/common/map.js'; import { URI } from '../../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; -import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; +import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; @@ -22,7 +22,6 @@ import { IChatService } from '../../../common/chatService.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../common/languageModels.js'; -import { PromptsConfig } from '../../../common/promptSyntax/config/config.js'; import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; import { PromptValidator } from '../../../common/promptSyntax/languageProviders/promptValidator.js'; import { PromptsType } from '../../../common/promptSyntax/promptTypes.js'; @@ -42,7 +41,6 @@ suite('PromptValidator', () => { setup(async () => { const testConfigService = new TestConfigurationService(); - testConfigService.setUserConfiguration(PromptsConfig.KEY, true); testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true); instaService = workbenchInstantiationService({ contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)), diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts index 7f7b37f4336..98302f3211c 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/config/config.test.ts @@ -22,7 +22,7 @@ function createMock(value: T): IConfigurationService { ); assert( - [PromptsConfig.KEY, PromptsConfig.PROMPT_LOCATIONS_KEY, PromptsConfig.INSTRUCTIONS_LOCATION_KEY, PromptsConfig.MODE_LOCATION_KEY].includes(key), + [PromptsConfig.PROMPT_LOCATIONS_KEY, PromptsConfig.INSTRUCTIONS_LOCATION_KEY, PromptsConfig.MODE_LOCATION_KEY].includes(key), `Unsupported configuration key '${key}'.`, ); @@ -34,131 +34,6 @@ function createMock(value: T): IConfigurationService { suite('PromptsConfig', () => { ensureNoDisposablesAreLeakedInTestSuite(); - suite('enabled', () => { - test('true', () => { - const configService = createMock(true); - - assert.strictEqual( - PromptsConfig.enabled(configService), - true, - 'Must read correct enablement value.', - ); - }); - - test('false', () => { - const configService = createMock(false); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('null', () => { - const configService = createMock(null); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('string', () => { - const configService = createMock(''); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('true string', () => { - const configService = createMock('TRUE'); - - assert.strictEqual( - PromptsConfig.enabled(configService), - true, - 'Must read correct enablement value.', - ); - }); - - test('false string', () => { - const configService = createMock('FaLsE'); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('number', () => { - const configService = createMock(3456); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('NaN', () => { - const configService = createMock(NaN); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('bigint', () => { - const configService = createMock(BigInt(5257)); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('symbol', () => { - const configService = createMock(Symbol('test')); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('object', () => { - const configService = createMock({ - '.github/prompts': false, - }); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - - test('array', () => { - const configService = createMock(['.github/prompts']); - - assert.strictEqual( - PromptsConfig.enabled(configService), - false, - 'Must read correct enablement value.', - ); - }); - }); - - suite('getLocationsValue', () => { test('undefined', () => { const configService = createMock(undefined); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 74a503d572f..05f5a718a0b 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -55,7 +55,6 @@ suite('PromptsService', () => { instaService.stub(IWorkspaceContextService, workspaceContextService); const testConfigService = new TestConfigurationService(); - testConfigService.setUserConfiguration(PromptsConfig.KEY, true); testConfigService.setUserConfiguration(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES, true); testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, true); testConfigService.setUserConfiguration(PromptsConfig.USE_NESTED_AGENT_MD, false); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts index d14f3881a08..fa243a307bc 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/utils/promptFilesLocator.test.ts @@ -44,7 +44,7 @@ function mockConfigService(value: T): IConfigurationService { } assert( - [PromptsConfig.KEY, PromptsConfig.PROMPT_LOCATIONS_KEY, PromptsConfig.INSTRUCTIONS_LOCATION_KEY, PromptsConfig.MODE_LOCATION_KEY].includes(key), + [PromptsConfig.PROMPT_LOCATIONS_KEY, PromptsConfig.INSTRUCTIONS_LOCATION_KEY, PromptsConfig.MODE_LOCATION_KEY].includes(key), `Unsupported configuration key '${key}'.`, ); From 2a2ad53bce2f4076c250dcaa7d7ce370838ff8af Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:33:11 +0200 Subject: [PATCH 1521/4355] SCM - fix sync action spinning icon (#272872) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index f9263e19e20..0f5654434be 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -79,7 +79,7 @@ min-width: 20px; .action-label > span:nth-child(1) { - padding-right: 2px; + margin-right: 2px; } } @@ -88,7 +88,6 @@ text-overflow: ellipsis; white-space: nowrap; min-width: 0; - direction: rtl; } .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > .codicon { From 72e7433eb588674efa8c0a894c935ad173a0358b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 13:29:02 +0200 Subject: [PATCH 1522/4355] agent sessions - do an initial resolve upon creation (#272847) --- .../chat/browser/agentSessions/agentSessionViewModel.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index e7b586a853a..a96849930a5 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -103,10 +103,12 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions super(); this.registerListeners(); + + this.resolve(undefined); } private registerListeners(): void { - this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType }) => this.resolve(chatSessionType))); + this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType: provider }) => this.resolve(provider))); this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined))); this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider))); } From 26c186704d7739a5f5386f34fb860a97509e77ef Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 13:29:40 +0200 Subject: [PATCH 1523/4355] agent sessions - respect view visibility (#272855) --- .../chat/browser/agentSessions/agentSessionsView.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index e5ed65bdeba..967d19db527 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -119,7 +119,11 @@ export class AgentSessionsView extends FilterViewPane { return; } - this.createViewModel(); + if (!this.sessionsViewModel) { + this.createViewModel(); + } else { + this.list?.updateChildren(); + } })); this._register(list.onDidOpen(e => { @@ -286,7 +290,11 @@ export class AgentSessionsView extends FilterViewPane { const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel)); this.list?.setInput(sessionsViewModel); - this._register(sessionsViewModel.onDidChangeSessions(() => this.list?.updateChildren())); + this._register(sessionsViewModel.onDidChangeSessions(() => { + if (this.isBodyVisible()) { + this.list?.updateChildren(); + } + })); const didResolveDisposable = this._register(new MutableDisposable()); this._register(sessionsViewModel.onWillResolve(() => { From b41b69ba4373fbf859bc365d32b4538dcb7e8867 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 13:36:20 +0200 Subject: [PATCH 1524/4355] agent sessions - adopt native list filterting (#272880) --- .../agentSessions/agentSessionsView.ts | 78 ++++++------------- .../agentSessions/agentSessionsViewer.ts | 46 ++++------- .../agentSessions/media/agentsessionsview.css | 25 ++---- 3 files changed, 46 insertions(+), 103 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 967d19db527..8422e0d77aa 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -6,11 +6,11 @@ import './media/agentsessionsview.css'; import { Codicon } from '../../../../../base/common/codicons.js'; import { localize, localize2 } from '../../../../../nls.js'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; import { Registry } from '../../../../../platform/registry/common/platform.js'; import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js'; -import { FilterViewPane, IViewPaneOptions, ViewAction } from '../../../../browser/parts/views/viewPane.js'; +import { IViewPaneOptions, ViewAction, ViewPane } from '../../../../browser/parts/views/viewPane.js'; import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js'; import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewDescriptor, IViewDescriptorService } from '../../../../common/views.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; @@ -25,14 +25,12 @@ import { IThemeService } from '../../../../../platform/theme/common/themeService import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { $, append } from '../../../../../base/browser/dom.js'; import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem } from './agentSessionViewModel.js'; -import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsFilter, AgentSessionsIdentityProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; +import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; import { IAction, toAction } from '../../../../../base/common/actions.js'; import { FuzzyScore } from '../../../../../base/common/filters.js'; import { MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; -import { KeyCode } from '../../../../../base/common/keyCodes.js'; -import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { findExistingChatEditorByUri, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; @@ -49,9 +47,7 @@ import { DeferredPromise } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; -export class AgentSessionsView extends FilterViewPane { - - private static FILTER_FOCUS_CONTEXT_KEY = new RawContextKey('agentSessionsViewFilterFocus', false); +export class AgentSessionsView extends ViewPane { private sessionsViewModel: IAgentSessionsViewModel | undefined; @@ -72,22 +68,11 @@ export class AgentSessionsView extends FilterViewPane { @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, ) { - super({ - ...options, - filterOptions: { - placeholder: localize('agentSessions.filterPlaceholder', "Type to filter agent sessions"), - ariaLabel: localize('agentSessions.filterAriaLabel', "Filter Agent Sessions"), - focusContextKey: AgentSessionsView.FILTER_FOCUS_CONTEXT_KEY.key - } - }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); this.registerActions(); } - override shouldShowFilterInHeader(): boolean { - return false; - } - protected override renderBody(container: HTMLElement): void { super.renderBody(container); @@ -105,14 +90,6 @@ export class AgentSessionsView extends FilterViewPane { private registerListeners(): void { const list = assertReturnsDefined(this.list); - // Sessions Filter - this._register(this.filterWidget.onDidChangeFilterText(() => { - if (this.filter) { - this.filter.pattern = this.filterWidget.getFilterText() || ''; - list.refilter(); - } - })); - // Sessions List this._register(this.onDidChangeBodyVisibility(visible => { if (!visible || this.sessionsViewModel) { @@ -171,40 +148,44 @@ export class AgentSessionsView extends FilterViewPane { } private registerActions(): void { + this._register(registerAction2(class extends ViewAction { constructor() { super({ - id: 'agentSessionsView.clearFilterText', - title: localize('clearFiltersText', "Clear Filter"), - keybinding: { - when: AgentSessionsView.FILTER_FOCUS_CONTEXT_KEY, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Escape + id: 'agentSessionsView.refresh', + title: localize2('refresh', "Refresh Agent Sessions"), + icon: Codicon.refresh, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', AGENT_SESSIONS_VIEW_ID), + group: 'navigation', + order: 1 }, viewId: AGENT_SESSIONS_VIEW_ID }); } runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { - view.filterWidget?.setFilterText(''); + view.sessionsViewModel?.resolve(undefined); } })); this._register(registerAction2(class extends ViewAction { constructor() { super({ - id: 'agentSessionsView.refresh', - title: localize2('refresh', "Refresh Agent Sessions"), - icon: Codicon.refresh, + id: 'agentSessionsView.find', + title: localize2('find', "Find Agent Session"), + icon: Codicon.search, menu: { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', AGENT_SESSIONS_VIEW_ID), - group: 'navigation' + group: 'navigation', + order: 2 }, viewId: AGENT_SESSIONS_VIEW_ID }); } runInView(accessor: ServicesAccessor, view: AgentSessionsView): void { - view.sessionsViewModel?.resolve(undefined); + view.list?.openFind(); } })); } @@ -258,13 +239,10 @@ export class AgentSessionsView extends FilterViewPane { private listContainer: HTMLElement | undefined; private list: WorkbenchCompressibleAsyncDataTree | undefined; - private filter: AgentSessionsFilter | undefined; private createList(container: HTMLElement): void { this.listContainer = append(container, $('.agent-sessions-viewer')); - this.filter = this._register(new AgentSessionsFilter()); - this.list = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'AgentSessionsView', this.listContainer, @@ -279,7 +257,8 @@ export class AgentSessionsView extends FilterViewPane { identityProvider: new AgentSessionsIdentityProvider(), horizontalScrolling: false, multipleSelectionSupport: false, - filter: this.filter, + findWidgetEnabled: true, + keyboardNavigationLabelProvider: new AgentSessionsKeyboardNavigationLabelProvider(), sorter: new AgentSessionsSorter(), paddingBottom: AgentSessionsListDelegate.ITEM_HEIGHT } @@ -318,29 +297,18 @@ export class AgentSessionsView extends FilterViewPane { super.layoutBody(height, width); let treeHeight = height; - treeHeight -= this.filterContainer?.offsetHeight ?? 0; treeHeight -= this.newSessionContainer?.offsetHeight ?? 0; this.list?.layout(treeHeight, width); } - protected override layoutBodyContent(height: number, width: number): void { - // TODO@bpasero we deal with layout in layoutBody because we heavily customize it, reconsider using view filter inheritance - } - override focus(): void { super.focus(); if (this.list?.getFocus().length) { this.list.domFocus(); - } else { - this.filterWidget.focus(); } } - - protected override focusBodyContent(): void { - this.list?.domFocus(); - } } //#region View Registration diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 0a96b2f2729..853666596a4 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -10,15 +10,15 @@ import { IIdentityProvider, IListVirtualDelegate } from '../../../../../base/bro import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; -import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; -import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeFilter, ITreeSorter, TreeFilterResult, TreeVisibility } from '../../../../../base/browser/ui/tree/tree.js'; -import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; +import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter } from '../../../../../base/browser/ui/tree/tree.js'; +import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; import { AgentSessionStatus, IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js'; import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { fromNow } from '../../../../../base/common/date.js'; -import { FuzzyScore, createMatches, matchesFuzzy } from '../../../../../base/common/filters.js'; +import { FuzzyScore, createMatches } from '../../../../../base/common/filters.js'; import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { allowedChatMarkdownHtmlTags } from '../chatContentMarkdownRenderer.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; @@ -196,33 +196,6 @@ export class AgentSessionsIdentityProvider implements IIdentityProvider { - - private _pattern: string = ''; - set pattern(pattern: string) { this._pattern = pattern; } - - filter(element: IAgentSessionViewModel, parentVisibility: TreeVisibility): TreeFilterResult { - if (!this._pattern) { - return TreeVisibility.Visible; - } - - const score = matchesFuzzy(this._pattern, element.label, true); - if (score) { - const fuzzyScore: FuzzyScore = [0, 0]; - for (let matchIndex = score.length - 1; matchIndex >= 0; matchIndex--) { - const match = score[matchIndex]; - for (let i = match.end - 1; i >= match.start; i--) { - fuzzyScore.push(i); - } - } - - return { data: fuzzyScore, visibility: TreeVisibility.Visible }; - } - - return TreeVisibility.Hidden; - } -} - export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate { isIncompressible(element: IAgentSessionViewModel): boolean { @@ -247,3 +220,14 @@ export class AgentSessionsSorter implements ITreeSorter return sessionB.timing.startTime - sessionA.timing.startTime; } } + +export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider { + + getKeyboardNavigationLabel(element: IAgentSessionViewModel): string { + return element.label; + } + + getCompressedNodeKeyboardNavigationLabel(elements: IAgentSessionViewModel[]): { toString(): string | undefined } | undefined { + return undefined; // not enabled + } +} diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css index b59a6d0c133..ecbf0ca5b82 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsview.css @@ -8,24 +8,15 @@ display: flex; flex-direction: column; - .agent-sessions-viewer { - flex: 1 1 auto !important; - min-height: 0; - } - - .viewpane-filter-container, - .agent-sessions-new-session-container { - padding: 5px 12px 6px 20px; - flex: 0 0 auto !important; - } - - .viewpane-filter-container { - margin: 0 !important; + .agent-sessions-viewer { + flex: 1 1 auto !important; + min-height: 0; + } - .monaco-inputbox .input { - font-size: 13px; - } - } + .agent-sessions-new-session-container { + padding: 5px 12px 6px 20px; + flex: 0 0 auto !important; + } .agent-sessions-new-session-container .monaco-dropdown-button { padding: 0 4px; From 672c1c1d94f7339ec7a1d57ec61d2a62f9443c4e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Oct 2025 13:40:49 +0200 Subject: [PATCH 1525/4355] chat - tweak wording around chat and reduce use of copilot (#272839) --- .../src/extension.ts | 2 +- .../chat/browser/actions/chatActions.ts | 10 +++-- .../browser/actions/chatContextActions.ts | 4 +- .../browser/actions/chatGettingStarted.ts | 6 +-- src/vs/workbench/contrib/chat/browser/chat.ts | 9 +--- .../chatConfirmationWidget.ts | 16 ++++--- .../chatEditing/simpleBrowserEditorOverlay.ts | 4 +- .../browser/chatParticipant.contribution.ts | 2 +- .../contrib/chat/browser/chatQuick.ts | 6 +-- .../contrib/chat/browser/chatSetup.ts | 44 +++++++++---------- .../contrib/chat/browser/chatStatus.ts | 34 +++++++------- .../contrib/chat/browser/chatViewPane.ts | 2 +- .../promptSyntax/attachInstructionsAction.ts | 4 +- .../browser/promptSyntax/runPromptAction.ts | 7 ++- .../actions/voiceChatActions.ts | 5 ++- .../electron-browser/chat.contribution.ts | 5 ++- .../browser/inlineChatController.ts | 4 +- .../cellDiagnostics/cellDiagnosticsActions.ts | 4 +- .../chat/notebook.chat.contribution.ts | 4 +- .../scm/browser/scmHistoryChatContext.ts | 10 +++-- .../chat/browser/terminalChatController.ts | 4 +- .../chat/browser/terminalChatWidget.ts | 4 +- .../terminal.chatAgentTools.contribution.ts | 4 +- .../browser/tools/runInTerminalTool.ts | 2 +- .../tools/task/createAndRunTaskTool.ts | 4 +- .../browser/tools/task/runTaskTool.ts | 24 +++++----- .../common/gettingStartedContent.ts | 8 ++-- 27 files changed, 131 insertions(+), 101 deletions(-) diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts index cbb8d50bf99..2f9ac62abe4 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts @@ -45,7 +45,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.tests.registerTestFollowupProvider({ async provideFollowup(_result, test, taskIndex, messageIndex, _token) { return [{ - title: '$(sparkle) Fix with Copilot', + title: '$(sparkle) Fix', command: 'github.copilot.tests.fixTestFailure', arguments: [{ source: 'peekFollowup', test, message: test.taskStates[taskIndex].messages[messageIndex] }] }]; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 666308c49ae..bed78771f07 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -70,7 +70,7 @@ import { ChatAgentLocation, ChatConfiguration, ChatModeKind, AGENT_SESSIONS_VIEW import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js'; +import { ChatViewId, IChatWidget, IChatWidgetService, showChatView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEditingSessionConfirmation } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; @@ -193,6 +193,7 @@ abstract class OpenChatGlobalAction extends Action2 { const widgetService = accessor.get(IChatWidgetService); const toolsService = accessor.get(ILanguageModelToolsService); const viewsService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); const hostService = accessor.get(IHostService); const chatAgentService = accessor.get(IChatAgentService); const instaService = accessor.get(IInstantiationService); @@ -206,7 +207,7 @@ abstract class OpenChatGlobalAction extends Action2 { // When this was invoked to switch to a mode via keybinding, and some chat widget is focused, use that one. // Otherwise, open the view. if (!this.mode || !chatWidget || !isAncestorOfActiveElement(chatWidget.domNode)) { - chatWidget = await showChatView(viewsService); + chatWidget = await showChatView(viewsService, layoutService); } if (!chatWidget) { @@ -480,7 +481,7 @@ export function registerChatActions() { this.updatePartVisibility(layoutService, chatLocation, false); } else { this.updatePartVisibility(layoutService, chatLocation, true); - (await showCopilotView(viewsService, layoutService))?.focusInput(); + (await showChatView(viewsService, layoutService))?.focusInput(); } } @@ -1158,9 +1159,10 @@ export function registerChatActions() { async run(accessor: ServicesAccessor) { const viewsService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); // Open the chat view in the sidebar and get the widget - const chatWidget = await showChatView(viewsService); + const chatWidget = await showChatView(viewsService, layoutService); if (chatWidget) { // Clear the current chat to start a new one diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index a9bcf5423ae..1bc9a6fa925 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -35,6 +35,7 @@ import { ResourceContextKey } from '../../../../common/contextkeys.js'; import { EditorResourceAccessor, isEditorCommandsContext, SideBySideEditor } from '../../../../common/editor.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ExplorerFolderContext } from '../../../files/common/files.js'; import { AnythingQuickAccessProvider } from '../../../search/browser/anythingQuickAccess.js'; @@ -66,10 +67,11 @@ export function registerChatContextActions() { async function withChatView(accessor: ServicesAccessor): Promise { const viewsService = accessor.get(IViewsService); const chatWidgetService = accessor.get(IChatWidgetService); + const layoutService = accessor.get(IWorkbenchLayoutService); const lastFocusedWidget = chatWidgetService.lastFocusedWidget; if (!lastFocusedWidget || lastFocusedWidget.location === ChatAgentLocation.Chat) { - return showChatView(viewsService); // only show chat view if we either have no chat view or its located in view container + return showChatView(viewsService, layoutService); // only show chat view if we either have no chat view or its located in view container } return lastFocusedWidget; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts index e87ed5854b6..852880b6bac 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts @@ -12,8 +12,8 @@ import { IExtensionManagementService, InstallOperation } from '../../../../../pl import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IDefaultChatAgent } from '../../../../../base/common/product.js'; import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; -import { showCopilotView } from '../chat.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; +import { showChatView } from '../chat.js'; export class ChatGettingStartedContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatGettingStarted'; @@ -66,8 +66,8 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb private async onDidInstallChat() { - // Open Copilot view - showCopilotView(this.viewsService, this.layoutService); + // Open Chat view + showChatView(this.viewsService, this.layoutService); // Only do this once this.storageService.store(ChatGettingStartedContribution.hideWelcomeView, true, StorageScope.APPLICATION, StorageTarget.MACHINE); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index a49b3f71e0d..10bbc7de8a8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -66,19 +66,14 @@ export async function showChatWidgetInViewOrEditor(accessor: ServicesAccessor, w } } - -export async function showChatView(viewsService: IViewsService): Promise { - return (await viewsService.openView(ChatViewId))?.widget; -} - -export function showCopilotView(viewsService: IViewsService, layoutService: IWorkbenchLayoutService): Promise { +export async function showChatView(viewsService: IViewsService, layoutService: IWorkbenchLayoutService): Promise { // Ensure main window is in front if (layoutService.activeContainer !== layoutService.mainContainer) { layoutService.mainContainer.focus(); } - return showChatView(viewsService); + return (await viewsService.openView(ChatViewId))?.widget; } export const IQuickChatService = createDecorator('quickChatService'); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index fd67aa14e0e..9fa0b918370 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -26,6 +26,7 @@ import { IHostService } from '../../../../services/host/browser/host.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { showChatView } from '../chat.js'; import './media/chatConfirmationWidget.css'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; export interface IChatConfirmationButton { label: string; @@ -141,6 +142,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { @IHostService private readonly _hostService: IHostService, @IViewsService private readonly _viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -262,7 +264,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { disposables.add(Event.once(notification.onClick)(() => { this._hostService.focus(targetWindow, { mode: FocusMode.Force }); - showChatView(this._viewsService); + showChatView(this._viewsService, this._layoutService); })); disposables.add(this._hostService.onDidChangeFocus(focus => { @@ -288,8 +290,9 @@ export class SimpleChatConfirmationWidget extends BaseSimpleChatConfirmationW @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); this.updateMessage(options.message); } @@ -349,6 +352,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { @IHostService private readonly _hostService: IHostService, @IViewsService private readonly _viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -490,7 +494,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { disposables.add(Event.once(notification.onClick)(() => { this._hostService.focus(targetWindow, { mode: FocusMode.Force }); - showChatView(this._viewsService); + showChatView(this._viewsService, this._layoutService); })); disposables.add(this._hostService.onDidChangeFocus(focus => { @@ -514,8 +518,9 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget { @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); this.renderMessage(options.message, this._container); } @@ -540,8 +545,9 @@ export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget< @IHostService hostService: IHostService, @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService); + super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); this.renderMessage(options.message, container); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts index 5015b2af65c..f0e9820dd0a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts @@ -37,6 +37,7 @@ import { IBrowserElementsService } from '../../../../services/browserElements/br import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { IAction, toAction } from '../../../../../base/common/actions.js'; import { BrowserType } from '../../../../../platform/browserElements/common/browserElements.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; class SimpleBrowserOverlayWidget { @@ -63,6 +64,7 @@ class SimpleBrowserOverlayWidget { @IPreferencesService private readonly _preferencesService: IPreferencesService, @IBrowserElementsService private readonly _browserElementsService: IBrowserElementsService, @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { this._showStore.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('chat.sendElementsToChat.enabled')) { @@ -253,7 +255,7 @@ class SimpleBrowserOverlayWidget { const bounds = elementData.bounds; const toAttach: IChatRequestVariableEntry[] = []; - const widget = await showChatView(this._viewService) ?? this._chatWidgetService.lastFocusedWidget; + const widget = await showChatView(this._viewService, this._layoutService) ?? this._chatWidgetService.lastFocusedWidget; let value = 'Attached HTML and CSS Context\n\n' + elementData.outerHTML; if (this.configurationService.getValue('chat.sendElementsToChat.attachCSS')) { value += '\n\n' + elementData.computedStyle; diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index f9404af1a5a..a1c2acbdd72 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -329,7 +329,7 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC super(); // It may be better to have some generic UI for this, for any extension that is incompatible, - // but this is only enabled for Copilot Chat now and it needs to be obvious. + // but this is only enabled for Chat now and it needs to be obvious. const isInvalid = ChatContextKeys.extensionInvalid.bindTo(contextKeyService); this._register(Event.runAndSubscribe( extensionsWorkbenchService.onDidChangeExtensionsNotification, diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 8220b7357f8..92f7f0ffea6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -17,13 +17,13 @@ import { MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import product from '../../../../platform/product/common/product.js'; import { IQuickInputService, IQuickWidget } from '../../../../platform/quickinput/common/quickInput.js'; import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from '../../../../platform/theme/common/colorRegistry.js'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { ChatModel, isCellTextEditOperation } from '../common/chatModel.js'; import { ChatMode } from '../common/chatModes.js'; @@ -160,7 +160,7 @@ class QuickChat extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatService private readonly chatService: IChatService, - @ILayoutService private readonly layoutService: ILayoutService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IViewsService private readonly viewsService: IViewsService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, @@ -318,7 +318,7 @@ class QuickChat extends Disposable { } async openChatView(): Promise { - const widget = await showChatView(this.viewsService); + const widget = await showChatView(this.viewsService, this.layoutService); if (!widget?.viewModel || !this.model) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index b4a8394b149..880a3f17d9e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -72,7 +72,7 @@ import { IChatRequestToolEntry } from '../common/chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; import { ILanguageModelsService } from '../common/languageModels.js'; import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID, CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from './actions/chatActions.js'; -import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; +import { ChatViewId, IChatWidgetService, showChatView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; @@ -145,7 +145,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { break; } - return SetupAgent.doRegisterAgent(instantiationService, chatAgentService, id, `${defaultChat.provider.default.name} Copilot`, true, description, location, mode, context, controller); + return SetupAgent.doRegisterAgent(instantiationService, chatAgentService, id, `${defaultChat.provider.default.name} Copilot` /* Do NOT change, this hides the username altogether in Chat */, true, description, location, mode, context, controller); }); } @@ -277,14 +277,14 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { content: new MarkdownString(localize('waitingChat', "Getting chat ready...")), }); - await this.forwardRequestToCopilot(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); + await this.forwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); return {}; } - private async forwardRequestToCopilot(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { + private async forwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { try { - await this.doForwardRequestToCopilot(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); + await this.doForwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); } catch (error) { progress({ kind: 'warning', @@ -293,12 +293,12 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } } - private async doForwardRequestToCopilot(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { + private async doForwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { if (this.pendingForwardedRequests.has(requestModel.session.sessionId)) { throw new Error('Request already in progress'); } - const forwardRequest = this.doForwardRequestToCopilotWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); + const forwardRequest = this.doForwardRequestToChatWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); this.pendingForwardedRequests.set(requestModel.session.sessionId, forwardRequest); try { @@ -308,12 +308,12 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { } } - private async doForwardRequestToCopilotWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { + private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { const widget = chatWidgetService.getWidgetBySessionId(requestModel.session.sessionId); const modeInfo = widget?.input.currentModeInfo; // We need a signal to know when we can resend the request to - // Copilot. Waiting for the registration of the agent is not + // Chat. Waiting for the registration of the agent is not // enough, we also need a language/tools model to be available. let agentReady = false; @@ -366,7 +366,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { content: new MarkdownString(warningMessage) }); - // This means Copilot is unhealthy and we cannot retry the + // This means Chat is unhealthy and we cannot retry the // request. Signal this to the outside via an event. this._onUnresolvableError.fire(); return; @@ -489,10 +489,10 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { if (result.dialogSkipped) { widget?.clear(); // make room for the Chat welcome experience } else if (requestModel) { - let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Copilot agent... - newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Copilot tools + let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Chat agent... + newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Chat tools - await this.forwardRequestToCopilot(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); + await this.forwardRequestToChat(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService); } } else { progress({ @@ -766,7 +766,7 @@ class ChatSetup { if (setupStrategy !== ChatSetupStrategy.Canceled && !options?.disableChatViewReveal) { // Show the chat view now to better indicate progress // while installing the extension or returning from sign in - showCopilotView(this.viewsService, this.layoutService); + showChatView(this.viewsService, this.layoutService); } let success: ChatSetupResultValue = undefined; @@ -860,7 +860,7 @@ class ChatSetup { ]); } } else { - buttons = [[localize('setupCopilotButton', "Set up Copilot"), ChatSetupStrategy.DefaultSetup, undefined]]; + buttons = [[localize('setupAIButton', "Use AI Features"), ChatSetupStrategy.DefaultSetup, undefined]]; } buttons.push([localize('skipForNow', "Skip for now"), ChatSetupStrategy.Canceled, styleButton('link-button', 'skip-button')]); @@ -871,17 +871,17 @@ class ChatSetup { private getDialogTitle(options?: { forceSignInDialog?: boolean; forceAnonymous?: ChatSetupAnonymous }): string { if (this.chatEntitlementService.anonymous) { if (options?.forceAnonymous) { - return localize('startUsing', "Start using GitHub Copilot"); + return localize('startUsing', "Start using AI Features"); } else { return localize('enableMore', "Enable more AI features"); } } if (this.context.state.entitlement === ChatEntitlement.Unknown || options?.forceSignInDialog) { - return localize('signIn', "Sign in to use GitHub Copilot"); + return localize('signIn', "Sign in to use AI Features"); } - return localize('startUsing', "Start using GitHub Copilot"); + return localize('startUsing', "Start using AI Features"); } private createDialogFooter(disposables: DisposableStore, options?: { forceAnonymous?: ChatSetupAnonymous }): HTMLElement { @@ -964,11 +964,11 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); if (panelAgentHasGuidance) { // An unresolvable error from our agent registrations means that - // Copilot is unhealthy for some reason. We clear our panel - // registration to give Copilot a chance to show a custom message + // Chat is unhealthy for some reason. We clear our panel + // registration to give Chat a chance to show a custom message // to the user from the views and stop pretending as if there was // a functional agent. - this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.'); + this.logService.error('[chat setup] Unresolvable error from Chat agent registration, clearing registration.'); panelAgentDisposables.dispose(); } })); @@ -1045,7 +1045,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr configurationService.updateValue(ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY, false); if (mode) { - const chatWidget = await showCopilotView(viewsService, layoutService); + const chatWidget = await showChatView(viewsService, layoutService); chatWidget?.input.setChatMode(mode); } diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index f166fe1d0a5..64c9bcc57eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -182,7 +182,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu private getEntryProps(): IStatusbarEntry { let text = '$(copilot)'; - let ariaLabel = localize('chatStatus', "Copilot Status"); + let ariaLabel = localize('chatStatusAria', "Copilot status"); let kind: StatusbarEntryKind | undefined; if (isNewUser(this.chatEntitlementService)) { @@ -195,7 +195,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu isProUser(entitlement) || // user is already pro entitlement === ChatEntitlement.Free // user is already free ) { - const finishSetup = localize('copilotLaterStatus', "Finish Setup"); + const finishSetup = localize('finishSetup', "Finish Setup"); text = `$(copilot) ${finishSetup}`; ariaLabel = finishSetup; @@ -224,7 +224,7 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu // Signed out else if (this.chatEntitlementService.entitlement === ChatEntitlement.Unknown) { - const signedOutWarning = localize('notSignedIntoCopilot', "Signed out"); + const signedOutWarning = localize('notSignedIn', "Signed out"); text = `${this.chatEntitlementService.anonymous ? '$(copilot)' : '$(copilot-not-connected)'} ${signedOutWarning}`; ariaLabel = signedOutWarning; @@ -282,17 +282,17 @@ export class ChatStatusBarEntry extends Disposable implements IWorkbenchContribu } function isNewUser(chatEntitlementService: IChatEntitlementService): boolean { - return !chatEntitlementService.sentiment.installed || // copilot not installed - chatEntitlementService.entitlement === ChatEntitlement.Available; // not yet signed up to copilot + return !chatEntitlementService.sentiment.installed || // chat not installed + chatEntitlementService.entitlement === ChatEntitlement.Available; // not yet signed up to chat } -function canUseCopilot(chatEntitlementService: IChatEntitlementService): boolean { +function canUseChat(chatEntitlementService: IChatEntitlementService): boolean { if (!chatEntitlementService.sentiment.installed || chatEntitlementService.sentiment.disabled || chatEntitlementService.sentiment.untrusted) { - return false; // copilot not installed or not enabled + return false; // chat not installed or not enabled } if (chatEntitlementService.entitlement === ChatEntitlement.Unknown || chatEntitlementService.entitlement === ChatEntitlement.Available) { - return chatEntitlementService.anonymous; // signed out or not-yet-signed-up users can only use Copilot if anonymous access is allowed + return chatEntitlementService.anonymous; // signed out or not-yet-signed-up users can only use Chat if anonymous access is allowed } if (chatEntitlementService.entitlement === ChatEntitlement.Free && chatEntitlementService.quotas.chat?.percentRemaining === 0 && chatEntitlementService.quotas.completions?.percentRemaining === 0) { @@ -402,7 +402,7 @@ class ChatStatusDashboard extends Disposable { } if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (Number(chatQuota?.percentRemaining) <= 25 || Number(completionsQuota?.percentRemaining) <= 25)) { - const upgradeProButton = disposables.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: canUseCopilot(this.chatEntitlementService) /* use secondary color when copilot can still be used */ })); + const upgradeProButton = disposables.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: canUseChat(this.chatEntitlementService) /* use secondary color when chat can still be used */ })); upgradeProButton.label = localize('upgradeToCopilotPro', "Upgrade to GitHub Copilot Pro"); disposables.add(upgradeProButton.onDidClick(() => this.runCommandAndClose('workbench.action.chat.upgradePlan'))); } @@ -513,12 +513,12 @@ class ChatStatusDashboard extends Disposable { } // Completions Snooze - if (canUseCopilot(this.chatEntitlementService)) { + if (canUseChat(this.chatEntitlementService)) { const snooze = append(this.element, $('div.snooze-completions')); this.createCompletionsSnooze(snooze, localize('settings.snooze', "Snooze"), disposables); } - // New to Copilot / Signed out + // New to Chat / Signed out { const newUser = isNewUser(this.chatEntitlementService); const anonymousUser = this.chatEntitlementService.anonymous; @@ -544,13 +544,13 @@ class ChatStatusDashboard extends Disposable { let buttonLabel: string; if (newUser) { - buttonLabel = localize('activateCopilotButton', "Set up Copilot"); + buttonLabel = localize('enableAIFeatures', "Use AI Features"); } else if (anonymousUser) { - buttonLabel = localize('enableMoreCopilotButton', "Enable more AI Features"); + buttonLabel = localize('enableMoreAIFeatures', "Enable more AI Features"); } else if (disabled) { - buttonLabel = localize('enableCopilotButton', "Enable Copilot"); + buttonLabel = localize('enableCopilotButton', "Enable AI Features"); } else { - buttonLabel = localize('signInToUseCopilotButton', "Sign in to use Copilot"); + buttonLabel = localize('signInToUseAIFeatures', "Sign in to use AI Features"); } let commandId: string; @@ -755,7 +755,7 @@ class ChatStatusDashboard extends Disposable { } })); - if (!canUseCopilot(this.chatEntitlementService)) { + if (!canUseChat(this.chatEntitlementService)) { container.classList.add('disabled'); checkbox.disable(); checkbox.checked = false; @@ -817,7 +817,7 @@ class ChatStatusDashboard extends Disposable { disposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(completionsSettingId)) { - if (completionsSettingAccessor.readSetting() && canUseCopilot(this.chatEntitlementService)) { + if (completionsSettingAccessor.readSetting() && canUseChat(this.chatEntitlementService)) { checkbox.enable(); container.classList.remove('disabled'); } else { diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 983599641e0..fdde10d58f3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -161,7 +161,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { override shouldShowWelcome(): boolean { const noPersistedSessions = !this.chatService.hasSessions(); const hasCoreAgent = this.chatAgentService.getAgents().some(agent => agent.isCore && agent.locations.includes(this.chatOptions.location)); - const hasDefaultAgent = this.chatAgentService.getDefaultAgent(this.chatOptions.location) !== undefined; // only false when Hide Copilot has run and unregistered the setup agents + const hasDefaultAgent = this.chatAgentService.getDefaultAgent(this.chatOptions.location) !== undefined; // only false when Hide AI Features has run and unregistered the setup agents const shouldShow = !hasCoreAgent && (!hasDefaultAgent || !this._widget?.viewModel && noPersistedSessions); this.logService.trace(`ChatViewPane#shouldShowWelcome(${this.chatOptions.location}) = ${shouldShow}: hasCoreAgent=${hasCoreAgent} hasDefaultAgent=${hasDefaultAgent} || noViewModel=${!this._widget?.viewModel} && noPersistedSessions=${noPersistedSessions}`); return !!shouldShow; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts index 28bed45e5ba..b3c3b3e7ee9 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/attachInstructionsAction.ts @@ -27,6 +27,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; /** * Action ID for the `Attach Instruction` action. @@ -95,6 +96,7 @@ class AttachInstructionsAction extends Action2 { ): Promise { const viewsService = accessor.get(IViewsService); const instaService = accessor.get(IInstantiationService); + const layoutService = accessor.get(IWorkbenchLayoutService); if (!options) { options = { @@ -108,7 +110,7 @@ class AttachInstructionsAction extends Action2 { const { skipSelectionDialog, resource } = options; - const widget = options.widget ?? (await showChatView(viewsService)); + const widget = options.widget ?? (await showChatView(viewsService, layoutService)); if (!widget) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts index 048a3b6c432..0721f33c099 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts @@ -29,6 +29,7 @@ import { Action2, MenuId, registerAction2 } from '../../../../../platform/action import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; /** * Condition for the `Run Current Prompt` action. @@ -134,6 +135,7 @@ abstract class RunPromptBaseAction extends Action2 { const viewsService = accessor.get(IViewsService); const commandService = accessor.get(ICommandService); const promptsService = accessor.get(IPromptsService); + const layoutService = accessor.get(IWorkbenchLayoutService); resource ||= getActivePromptFileUri(accessor); assertDefined( @@ -145,7 +147,7 @@ abstract class RunPromptBaseAction extends Action2 { await commandService.executeCommand(ACTION_ID_NEW_CHAT); } - const widget = await showChatView(viewsService); + const widget = await showChatView(viewsService, layoutService); if (widget) { widget.setInput(`/${await promptsService.getPromptCommandName(resource)}`); // submit the prompt immediately @@ -210,6 +212,7 @@ class RunSelectedPromptAction extends Action2 { const commandService = accessor.get(ICommandService); const instaService = accessor.get(IInstantiationService); const promptsService = accessor.get(IPromptsService); + const layoutService = accessor.get(IWorkbenchLayoutService); const pickers = instaService.createInstance(PromptFilePickers); @@ -231,7 +234,7 @@ class RunSelectedPromptAction extends Action2 { await commandService.executeCommand(ACTION_ID_NEW_CHAT); } - const widget = await showChatView(viewsService); + const widget = await showChatView(viewsService, layoutService); if (widget) { widget.setInput(`/${await promptsService.getPromptCommandName(promptFile)}`); // submit the prompt immediately diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index acadfa9722c..f71f7cd84e2 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -111,7 +111,7 @@ class VoiceChatSessionControllerFactory { return controller ?? VoiceChatSessionControllerFactory.create(accessor, 'view'); // fallback to 'view' } case 'view': { - const chatWidget = await showChatView(viewsService); + const chatWidget = await showChatView(viewsService, layoutService); if (chatWidget) { return VoiceChatSessionControllerFactory.doCreateForChatWidget('view', chatWidget); } @@ -476,6 +476,7 @@ export class HoldToVoiceChatInChatViewAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const keybindingService = accessor.get(IKeybindingService); const viewsService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); const holdMode = keybindingService.enableKeybindingHoldMode(HoldToVoiceChatInChatViewAction.ID); @@ -488,7 +489,7 @@ export class HoldToVoiceChatInChatViewAction extends Action2 { } }, VOICE_KEY_HOLD_THRESHOLD); - (await showChatView(viewsService))?.focusInput(); + (await showChatView(viewsService, layoutService))?.focusInput(); await holdMode; handle.dispose(); diff --git a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts index 3ecc2fc8bf5..0459a36db92 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts @@ -96,7 +96,7 @@ class ChatCommandLineHandler extends Disposable { attachFiles: args['add-file']?.map(file => URI.file(resolve(file))), // use `resolve` to deal with relative paths properly }; - const chatWidget = await showChatView(this.viewsService); + const chatWidget = await showChatView(this.viewsService, this.layoutService); if (args.maximize) { const location = this.contextKeyService.getContextKeyValue(ChatContextKeys.panelLocation.key); @@ -145,6 +145,7 @@ class ChatLifecycleHandler extends Disposable { @IViewsService private readonly viewsService: IViewsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService extensionService: IExtensionService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, ) { super(); @@ -172,7 +173,7 @@ class ChatLifecycleHandler extends Disposable { private async doShouldVetoShutdown(reason: ShutdownReason): Promise { - showChatView(this.viewsService); + showChatView(this.viewsService, this.layoutService); let message: string; let detail: string; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index b20bfbdec5a..6de941f48cd 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -45,6 +45,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showChatView } from '../../chat/browser/chat.js'; import { IChatAttachmentResolveService } from '../../chat/browser/chatAttachmentResolveService.js'; @@ -1677,8 +1678,9 @@ async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | un const viewsService = accessor.get(IViewsService); const chatService = accessor.get(IChatService); + const layoutService = accessor.get(IWorkbenchLayoutService); - const widget = await showChatView(viewsService); + const widget = await showChatView(viewsService, layoutService); if (widget && widget.viewModel && model) { for (const request of model.getRequests().slice()) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts index d41ae0fcfd4..0241529aaa2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnosticsActions.ts @@ -18,6 +18,7 @@ import { NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; import { showChatView } from '../../../../chat/browser/chat.js'; import { IViewsService } from '../../../../../services/views/common/viewsService.js'; +import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions'; export const FIX_CELL_ERROR_COMMAND_ID = 'notebook.cell.chat.fixError'; @@ -111,7 +112,8 @@ registerAction2(class extends NotebookCellAction { const error = context.cell.executionErrorDiagnostic.get(); if (error?.message) { const viewsService = accessor.get(IViewsService); - const chatWidget = await showChatView(viewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + const chatWidget = await showChatView(viewsService, layoutService); const message = error.name ? `${error.name}: ${error.message}` : error.message; // TODO: can we add special prompt instructions? e.g. use "%pip install" chatWidget?.acceptInput('@workspace /explain ' + message,); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index 9a22155e2d2..2d05e10fe9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -21,6 +21,7 @@ import { ServicesAccessor } from '../../../../../../platform/instantiation/commo import { IQuickInputService, IQuickPickItem } from '../../../../../../platform/quickinput/common/quickInput.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../common/contributions.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; +import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../../../services/views/common/viewsService.js'; import { IChatWidget, IChatWidgetService, showChatView } from '../../../../chat/browser/chat.js'; import { IChatContextPicker, IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../../../chat/browser/chatContextPickService.js'; @@ -338,6 +339,7 @@ registerAction2(class CopyCellOutputAction extends Action2 { async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise { const notebookEditor = this.getNoteboookEditor(accessor.get(IEditorService), outputContext); const viewService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); if (!notebookEditor) { return; @@ -389,7 +391,7 @@ registerAction2(class CopyCellOutputAction extends Action2 { } widget.attachmentModel.addContext(entry); - (await showChatView(viewService))?.focusInput(); + (await showChatView(viewService, layoutService))?.focusInput(); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 44c5a3ed214..7701d7dfbc5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -20,6 +20,7 @@ import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/c import { CodeDataTransfers } from '../../../../platform/dnd/browser/dnd.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IChatWidget, showChatView } from '../../chat/browser/chat.js'; import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService, picksWithPromiseFn } from '../../chat/browser/chatContextPickService.js'; @@ -268,7 +269,8 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, provider: ISCMProvider, historyItem: ISCMHistoryItem): Promise { const viewsService = accessor.get(IViewsService); - const widget = await showChatView(viewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + const widget = await showChatView(viewsService, layoutService); if (!provider || !historyItem || !widget) { return; } @@ -295,7 +297,8 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, provider: ISCMProvider, historyItem: ISCMHistoryItem): Promise { const viewsService = accessor.get(IViewsService); - const widget = await showChatView(viewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + const widget = await showChatView(viewsService, layoutService); if (!provider || !historyItem || !widget) { return; } @@ -322,7 +325,8 @@ registerAction2(class extends Action2 { override async run(accessor: ServicesAccessor, historyItem: ISCMHistoryItem, historyItemChange: ISCMHistoryItemChange): Promise { const viewsService = accessor.get(IViewsService); - const widget = await showChatView(viewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + const widget = await showChatView(viewsService, layoutService); if (!historyItem || !historyItemChange.modifiedUri || !widget) { return; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 70b2d546ee4..08304afdaf5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -16,6 +16,7 @@ import { IViewsService } from '../../../../services/views/common/viewsService.js import type { ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js'; import type { IChatModel } from '../../../chat/common/chatModel.js'; import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.chat'; @@ -156,8 +157,9 @@ async function moveToPanelChat(accessor: ServicesAccessor, model: IChatModel | u const viewsService = accessor.get(IViewsService); const chatService = accessor.get(IChatService); + const layoutService = accessor.get(IWorkbenchLayoutService); - const widget = await showChatView(viewsService); + const widget = await showChatView(viewsService, layoutService); if (widget && widget.viewModel && model) { for (const request of model.getRequests().slice()) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b531eb39c50..b9fecd30410 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -30,6 +30,7 @@ import { TerminalStickyScrollContribution } from '../../stickyScroll/browser/ter import './media/terminalChatWidget.css'; import { MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from './terminalChat.js'; import { ChatMode } from '../../../chat/common/chatModes.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; const enum Constants { HorizontalMargin = 10, @@ -103,6 +104,7 @@ export class TerminalChatWidget extends Disposable { @IViewsService private readonly _viewsService: IViewsService, @IInstantiationService instantiationService: IInstantiationService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -430,7 +432,7 @@ export class TerminalChatWidget extends Disposable { } async viewInChat(): Promise { - const widget = await showChatView(this._viewsService); + const widget = await showChatView(this._viewsService, this._layoutService); const currentRequest = this._inlineChatWidget.chatWidget.viewModel?.model.getRequests().find(r => r.id === this._currentRequestId); if (!widget || !currentRequest?.response) { return; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts index fa9e71a2976..0c49635cd41 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts @@ -11,6 +11,7 @@ import { MenuId } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { registerWorkbenchContribution2, WorkbenchPhase, type IWorkbenchContribution } from '../../../../common/contributions.js'; +import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { IChatWidgetService, showChatView } from '../../../chat/browser/chat.js'; import { ChatContextKeys } from '../../../chat/common/chatContextKeys.js'; @@ -112,13 +113,14 @@ registerActiveInstanceAction({ run: async (activeInstance, _c, accessor) => { const viewsService = accessor.get(IViewsService); const chatWidgetService = accessor.get(IChatWidgetService); + const layoutService = accessor.get(IWorkbenchLayoutService); const selection = activeInstance.selection; if (!selection) { return; } - const chatView = chatWidgetService.lastFocusedWidget || await showChatView(viewsService); + const chatView = chatWidgetService.lastFocusedWidget || await showChatView(viewsService, layoutService); if (!chatView) { return; } 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 0817703f554..720ed55c58f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -1036,7 +1036,7 @@ export class TerminalProfileFetcher { }; } - // Setting icon: undefined allows the system to use the default Copilot terminal icon (not overridden or removed) + // Setting icon: undefined allows the system to use the default AI terminal icon (not overridden or removed) return { ...defaultProfile, icon: undefined }; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts index b045488aa24..2620dab3810 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/createAndRunTaskTool.ts @@ -178,8 +178,8 @@ export class CreateAndRunTaskTool implements IToolImpl { title: localize('allowTaskCreationExecution', 'Allow task creation and execution?'), message: new MarkdownString( localize( - 'copilotCreateTask', - 'Copilot will create the task \`{0}\` with command \`{1}\`{2}.', + 'createTask', + 'A task \`{0}\` with command \`{1}\`{2} will be created.', task.label, task.command, task.args?.length ? ` and args \`${task.args.join(' ')}\`` : '' diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts index 6d6bbcfd7b7..8c4b3358298 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/task/runTaskTool.ts @@ -45,12 +45,12 @@ export class RunTaskTool implements IToolImpl { const taskDefinition = getTaskDefinition(args.id); const task = await getTaskForTool(args.id, taskDefinition, args.workspaceFolder, this._configurationService, this._tasksService, true); if (!task) { - return { content: [{ kind: 'text', value: `Task not found: ${args.id}` }], toolResultMessage: new MarkdownString(localize('copilotChat.taskNotFound', 'Task not found: `{0}`', args.id)) }; + return { content: [{ kind: 'text', value: `Task not found: ${args.id}` }], toolResultMessage: new MarkdownString(localize('chat.taskNotFound', 'Task not found: `{0}`', args.id)) }; } const taskLabel = task._label; const activeTasks = await this._tasksService.getActiveTasks(); if (activeTasks.includes(task)) { - return { content: [{ kind: 'text', value: `The task ${taskLabel} is already running.` }], toolResultMessage: new MarkdownString(localize('copilotChat.taskAlreadyRunning', 'The task `{0}` is already running.', taskLabel)) }; + return { content: [{ kind: 'text', value: `The task ${taskLabel} is already running.` }], toolResultMessage: new MarkdownString(localize('chat.taskAlreadyRunning', 'The task `{0}` is already running.', taskLabel)) }; } const raceResult = await Promise.race([this._tasksService.run(task, undefined, TaskRunSource.ChatAgent), timeout(3000)]); @@ -59,11 +59,11 @@ export class RunTaskTool implements IToolImpl { const dependencyTasks = await resolveDependencyTasks(task, args.workspaceFolder, this._configurationService, this._tasksService); const resources = this._tasksService.getTerminalsForTasks(dependencyTasks ?? task); if (!resources || resources.length === 0) { - return { content: [{ kind: 'text', value: `Task started but no terminal was found for: ${taskLabel}` }], toolResultMessage: new MarkdownString(localize('copilotChat.noTerminal', 'Task started but no terminal was found for: `{0}`', taskLabel)) }; + return { content: [{ kind: 'text', value: `Task started but no terminal was found for: ${taskLabel}` }], toolResultMessage: new MarkdownString(localize('chat.noTerminal', 'Task started but no terminal was found for: `{0}`', taskLabel)) }; } const terminals = this._terminalService.instances.filter(t => resources.some(r => r.path === t.resource.path && r.scheme === t.resource.scheme)); if (terminals.length === 0) { - return { content: [{ kind: 'text', value: `Task started but no terminal was found for: ${taskLabel}` }], toolResultMessage: new MarkdownString(localize('copilotChat.noTerminal', 'Task started but no terminal was found for: `{0}`', taskLabel)) }; + return { content: [{ kind: 'text', value: `Task started but no terminal was found for: ${taskLabel}` }], toolResultMessage: new MarkdownString(localize('chat.noTerminal', 'Task started but no terminal was found for: `{0}`', taskLabel)) }; } const store = new DisposableStore(); @@ -116,29 +116,29 @@ export class RunTaskTool implements IToolImpl { const task = await getTaskForTool(args.id, taskDefinition, args.workspaceFolder, this._configurationService, this._tasksService, true); if (!task) { - return { invocationMessage: new MarkdownString(localize('copilotChat.taskNotFound', 'Task not found: `{0}`', args.id)) }; + return { invocationMessage: new MarkdownString(localize('chat.taskNotFound', 'Task not found: `{0}`', args.id)) }; } const taskLabel = task._label; const activeTasks = await this._tasksService.getActiveTasks(); if (task && activeTasks.includes(task)) { - return { invocationMessage: new MarkdownString(localize('copilotChat.taskAlreadyActive', 'The task is already running.')) }; + return { invocationMessage: new MarkdownString(localize('chat.taskAlreadyActive', 'The task is already running.')) }; } if (await this._isTaskActive(task)) { return { - invocationMessage: new MarkdownString(localize('copilotChat.taskIsAlreadyRunning', '`{0}` is already running.', taskLabel)), - pastTenseMessage: new MarkdownString(localize('copilotChat.taskWasAlreadyRunning', '`{0}` was already running.', taskLabel)), + invocationMessage: new MarkdownString(localize('chat.taskIsAlreadyRunning', '`{0}` is already running.', taskLabel)), + pastTenseMessage: new MarkdownString(localize('chat.taskWasAlreadyRunning', '`{0}` was already running.', taskLabel)), confirmationMessages: undefined }; } return { - invocationMessage: new MarkdownString(localize('copilotChat.runningTask', 'Running `{0}`', taskLabel)), + invocationMessage: new MarkdownString(localize('chat.runningTask', 'Running `{0}`', taskLabel)), pastTenseMessage: new MarkdownString(task?.configurationProperties.isBackground - ? localize('copilotChat.startedTask', 'Started `{0}`', taskLabel) - : localize('copilotChat.ranTask', 'Ran `{0}`', taskLabel)), + ? localize('chat.startedTask', 'Started `{0}`', taskLabel) + : localize('chat.ranTask', 'Ran `{0}`', taskLabel)), confirmationMessages: task - ? { title: localize('copilotChat.allowTaskRunTitle', 'Allow task run?'), message: localize('copilotChat.allowTaskRunMsg', 'Allow Copilot to run the task `{0}`?', taskLabel) } + ? { title: localize('chat.allowTaskRunTitle', 'Allow task run?'), message: localize('chat.allowTaskRunMsg', 'Allow to run the task `{0}`?', taskLabel) } : undefined }; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 8264534af8d..60f47c9a8c1 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -227,10 +227,10 @@ const Button = (title: string, href: string) => `[${title}](${href})`; const CopilotStepTitle = localize('gettingStarted.copilotSetup.title', "Use AI features with Copilot for free"); const CopilotDescription = localize({ key: 'gettingStarted.copilotSetup.description', comment: ['{Locked="["}', '{Locked="]({0})"}'] }, "You can use [Copilot]({0}) to generate code across multiple files, fix errors, ask questions about your code, and much more using natural language.", defaultChat.documentationUrl ?? ''); const CopilotTermsString = localize({ key: 'gettingStarted.copilotSetup.terms', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", defaultChat.provider.default.name, defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl); -const CopilotAnonymousButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetupAnonymousWithoutDialog`); -const CopilotSignedOutButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetup`); -const CopilotSignedInButton = Button(localize('setupCopilotButton.setup', "Set up Copilot"), `command:workbench.action.chat.triggerSetup`); -const CopilotCompleteButton = Button(localize('setupCopilotButton.chatWithCopilot', "Chat with Copilot"), 'command:workbench.action.chat.open'); +const CopilotAnonymousButton = Button(localize('setupCopilotButton.setup', "Use AI Features"), `command:workbench.action.chat.triggerSetupAnonymousWithoutDialog`); +const CopilotSignedOutButton = Button(localize('setupCopilotButton.setup', "Use AI Features"), `command:workbench.action.chat.triggerSetup`); +const CopilotSignedInButton = Button(localize('setupCopilotButton.setup', "Use AI Features"), `command:workbench.action.chat.triggerSetup`); +const CopilotCompleteButton = Button(localize('setupCopilotButton.chatWithCopilot', "Start to Chat"), 'command:workbench.action.chat.open'); function createCopilotSetupStep(id: string, button: string, when: string, includeTerms: boolean): BuiltinGettingStartedStep { const description = includeTerms ? From 5860e60dca472ad5b5eb49ead4fbae3ba8b61cca Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 23 Oct 2025 04:55:38 -0700 Subject: [PATCH 1526/4355] Revert "Merge pull request #272692 from microsoft/tyriar/272656" This reverts commit d47a8dee5ffafcf7d8fe4d2a97dc4a5a443a7eef, reversing changes made to 0734942d32769ea1e77e42e056d6f16579e78fd6. --- .../terminal/browser/terminalService.ts | 76 ++++++++----------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index d6fc8a493a8..ee1546ab0c4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -29,6 +29,7 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ThemeIcon } from '../../../../base/common/themables.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { VirtualWorkspaceContext } from '../../../common/contextkeys.js'; + import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from './terminal.js'; import { getCwdForSplit } from './terminalActions.js'; import { TerminalEditorInput } from './terminalEditorInput.js'; @@ -57,11 +58,6 @@ import { isAuxiliaryWindow, mainWindow } from '../../../../base/browser/window.j import { GroupIdentifier } from '../../../common/editor.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; -interface IBackgroundTerminal { - instance: ITerminalInstance; - terminalLocationOptions?: ITerminalLocationOptions; -} - export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; @@ -72,7 +68,7 @@ export class TerminalService extends Disposable implements ITerminalService { private readonly _terminalShellTypeContextKey: IContextKey; private _isShuttingDown: boolean = false; - private _backgroundedTerminalInstances: IBackgroundTerminal[] = []; + private _backgroundedTerminalInstances: ITerminalInstance[] = []; private _backgroundedTerminalDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -94,7 +90,7 @@ export class TerminalService extends Disposable implements ITerminalService { get restoredGroupCount(): number { return this._restoredGroupCount; } get instances(): ITerminalInstance[] { - return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances.map(bg => bg.instance)); + return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances); } /** Gets all non-background terminals. */ get foregroundInstances(): ITerminalInstance[] { @@ -474,8 +470,7 @@ export class TerminalService extends Disposable implements ITerminalService { if (layoutInfo && (layoutInfo.tabs.length > 0 || layoutInfo?.background?.length)) { mark('code/terminal/willRecreateTerminalGroups'); this._reconnectedTerminalGroups = this._recreateTerminalGroups(layoutInfo); - const revivedInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); - this._backgroundedTerminalInstances = revivedInstances.map(instance => ({ instance })); + this._backgroundedTerminalInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); mark('code/terminal/didRecreateTerminalGroups'); } // now that terminals have been restored, @@ -697,7 +692,7 @@ export class TerminalService extends Disposable implements ITerminalService { // Don't touch processes if the shutdown was a result of reload as they will be reattached const shouldPersistTerminals = this._terminalConfigurationService.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD; - for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances.map(bg => bg.instance)]) { + for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances]) { if (shouldPersistTerminals && instance.shouldPersist) { instance.detachProcessAndDispose(TerminalExitReason.Shutdown); } else { @@ -721,7 +716,7 @@ export class TerminalService extends Disposable implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.map(bg => bg.instance).filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; + const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; this._primaryBackend?.setTerminalLayoutInfo(state); } @@ -751,13 +746,13 @@ export class TerminalService extends Disposable implements ITerminalService { getInstanceFromId(terminalId: number): ITerminalInstance | undefined { let bgIndex = -1; - this._backgroundedTerminalInstances.forEach((bg, i) => { - if (bg.instance.instanceId === terminalId) { + this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { + if (terminalInstance.instanceId === terminalId) { bgIndex = i; } }); if (bgIndex !== -1) { - return this._backgroundedTerminalInstances[bgIndex].instance; + return this._backgroundedTerminalInstances[bgIndex]; } try { return this.instances[this._getIndexFromId(terminalId)]; @@ -1006,6 +1001,21 @@ export class TerminalService extends Disposable implements ITerminalService { if (!shellLaunchConfig.customPtyImplementation && !this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } + if (shellLaunchConfig.hideFromUser) { + const instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Panel); + this._backgroundedTerminalInstances.push(instance); + this._backgroundedTerminalDisposables.set(instance.instanceId, [ + instance.onDisposed(instance => { + const idx = this._backgroundedTerminalInstances.indexOf(instance); + if (idx !== -1) { + this._backgroundedTerminalInstances.splice(idx, 1); + } + this._onDidDisposeInstance.fire(instance); + }) + ]); + this._terminalHasBeenCreated.set(true); + return instance; + } this._evaluateLocalCwd(shellLaunchConfig); const location = await this.resolveLocation(options?.location) || this._terminalConfigurationService.defaultLocation; @@ -1021,20 +1031,6 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance.shellType) { this._extensionService.activateByEvent(`onTerminal:${instance.shellType}`); } - - if (shellLaunchConfig.hideFromUser) { - this._backgroundedTerminalInstances.push({ instance, terminalLocationOptions: options?.location }); - this._backgroundedTerminalDisposables.set(instance.instanceId, [ - instance.onDisposed(instance => { - const idx = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); - if (idx !== -1) { - this._backgroundedTerminalInstances.splice(idx, 1); - } - this._onDidDisposeInstance.fire(instance); - }) - ]); - } - return instance; } @@ -1119,12 +1115,10 @@ export class TerminalService extends Disposable implements ITerminalService { private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance { let instance; + const editorOptions = this._getEditorOptions(options?.location); if (location === TerminalLocation.Editor) { instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Editor); - if (!shellLaunchConfig.hideFromUser) { - const editorOptions = this._getEditorOptions(options?.location); - this._terminalEditorService.openEditor(instance, editorOptions); - } + this._terminalEditorService.openEditor(instance, editorOptions); } else { // TODO: pass resource? const group = this._terminalGroupService.createGroup(shellLaunchConfig); @@ -1187,27 +1181,21 @@ export class TerminalService extends Disposable implements ITerminalService { } public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean): Promise { - const index = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); + const index = this._backgroundedTerminalInstances.indexOf(instance); if (index === -1) { return; } - const backgroundTerminal = this._backgroundedTerminalInstances[index]; - this._backgroundedTerminalInstances.splice(index, 1); + this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); const disposables = this._backgroundedTerminalDisposables.get(instance.instanceId); if (disposables) { dispose(disposables); } this._backgroundedTerminalDisposables.delete(instance.instanceId); - if (instance.target === TerminalLocation.Panel) { - this._terminalGroupService.createGroup(instance); + this._terminalGroupService.createGroup(instance); - // Make active automatically if it's the first instance - if (this.instances.length === 1 && !suppressSetActive) { - this._terminalGroupService.setActiveInstanceByIndex(0); - } - } else { - const editorOptions = backgroundTerminal.terminalLocationOptions ? this._getEditorOptions(backgroundTerminal.terminalLocationOptions) : this._getEditorOptions(instance.target); - this._terminalEditorService.openEditor(instance, editorOptions); + // Make active automatically if it's the first instance + if (this.instances.length === 1 && !suppressSetActive) { + this._terminalGroupService.setActiveInstanceByIndex(0); } this._onDidChangeInstances.fire(); From 9b59d31df279bd7358c0e8933f1eb32a57841089 Mon Sep 17 00:00:00 2001 From: sinsincp Date: Thu, 23 Oct 2025 20:05:14 +0800 Subject: [PATCH 1527/4355] Fix AppUserModelID for code-workspace association (#272753) --- build/win32/code.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index a1847c512ec..a67faad1726 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -279,7 +279,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls\s Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.code-workspace"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Code Workspace}"; Flags: uninsdeletekey; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles From e5e1fca76c2dc15d7d4428d1d417dd3fc05842a9 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 23 Oct 2025 05:52:08 -0700 Subject: [PATCH 1528/4355] Hide quick pick's progress bar when not in use (#272632) --- .../browser/ui/progressbar/progressAccessibilitySignal.ts | 4 ++-- src/vs/base/browser/ui/progressbar/progressbar.ts | 4 ++-- src/vs/platform/quickinput/browser/quickInput.ts | 4 ++-- src/vs/platform/quickinput/browser/quickInputController.ts | 2 +- src/vs/workbench/browser/workbench.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts b/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts index db5d0b0cee3..5419c8819c6 100644 --- a/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts +++ b/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts @@ -14,10 +14,10 @@ const nullScopedAccessibilityProgressSignalFactory = () => ({ }); let progressAccessibilitySignalSchedulerFactory: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate = nullScopedAccessibilityProgressSignalFactory; -export function setProgressAcccessibilitySignalScheduler(progressAccessibilitySignalScheduler: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate) { +export function setProgressAccessibilitySignalScheduler(progressAccessibilitySignalScheduler: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate) { progressAccessibilitySignalSchedulerFactory = progressAccessibilitySignalScheduler; } -export function getProgressAcccessibilitySignalScheduler(msDelayTime: number, msLoopTime?: number): IScopedAccessibilityProgressSignalDelegate { +export function getProgressAccessibilitySignalScheduler(msDelayTime: number, msLoopTime?: number): IScopedAccessibilityProgressSignalDelegate { return progressAccessibilitySignalSchedulerFactory(msDelayTime, msLoopTime); } diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index 3b0a5e2f5e0..a369fb81871 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { hide, show } from '../../dom.js'; -import { getProgressAcccessibilitySignalScheduler } from './progressAccessibilitySignal.js'; +import { getProgressAccessibilitySignalScheduler } from './progressAccessibilitySignal.js'; import { RunOnceScheduler } from '../../../common/async.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../common/lifecycle.js'; import { isNumber } from '../../../common/types.js'; @@ -206,7 +206,7 @@ export class ProgressBar extends Disposable { show(delay?: number): void { this.showDelayedScheduler.cancel(); - this.progressSignal.value = getProgressAcccessibilitySignalScheduler(ProgressBar.PROGRESS_SIGNAL_DEFAULT_DELAY); + this.progressSignal.value = getProgressAccessibilitySignalScheduler(ProgressBar.PROGRESS_SIGNAL_DEFAULT_DELAY); if (typeof delay === 'number') { this.showDelayedScheduler.schedule(delay); diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index bb46541a8cd..6e728d7a8b3 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -420,12 +420,12 @@ export abstract class QuickInput extends Disposable implements IQuickInput { this.busyDelay = new TimeoutTimer(); this.busyDelay.setIfNotSet(() => { if (this.visible) { - this.ui.progressBar.infinite(); + this.ui.progressBar.infinite().show(); } }, 800); } if (!this.busy && this.busyDelay) { - this.ui.progressBar.stop(); + this.ui.progressBar.stop().hide(); this.busyDelay.cancel(); this.busyDelay = undefined; } diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 7f002ac337e..8c4fc2d6a47 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -648,7 +648,7 @@ export class QuickInputController extends Disposable { ui.visibleCount.setCount(0); ui.count.setCount(0); dom.reset(ui.message); - ui.progressBar.stop(); + ui.progressBar.stop().hide(); ui.list.setElements([]); ui.list.matchOnDescription = false; ui.list.matchOnDetail = false; diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 711f8e18b20..afe48b84b2e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -45,7 +45,7 @@ import { IHoverService, WorkbenchHoverDelegate } from '../../platform/hover/brow import { setHoverDelegateFactory } from '../../base/browser/ui/hover/hoverDelegateFactory.js'; import { setBaseLayerHoverDelegate } from '../../base/browser/ui/hover/hoverDelegate2.js'; import { AccessibilityProgressSignalScheduler } from '../../platform/accessibilitySignal/browser/progressAccessibilitySignalScheduler.js'; -import { setProgressAcccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js'; +import { setProgressAccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js'; import { AccessibleViewRegistry } from '../../platform/accessibility/browser/accessibleViewRegistry.js'; import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js'; import { IMarkdownRendererService } from '../../platform/markdown/browser/markdownRenderer.js'; @@ -316,7 +316,7 @@ export class Workbench extends Layout { // ARIA & Signals setARIAContainer(this.mainContainer); - setProgressAcccessibilitySignalScheduler((msDelayTime: number, msLoopTime?: number) => instantiationService.createInstance(AccessibilityProgressSignalScheduler, msDelayTime, msLoopTime)); + setProgressAccessibilitySignalScheduler((msDelayTime: number, msLoopTime?: number) => instantiationService.createInstance(AccessibilityProgressSignalScheduler, msDelayTime, msLoopTime)); // State specific classes const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; From 5e18e088a928d730bae24033f42a8d9c1a9f5491 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 23 Oct 2025 15:38:06 +0200 Subject: [PATCH 1529/4355] Fixes treeshaking on windows (#272899) --- build/lib/treeshaking.js | 6 +++--- build/lib/treeshaking.ts | 6 +++--- build/lib/typeScriptLanguageServiceHost.js | 7 +++++++ build/lib/typeScriptLanguageServiceHost.ts | 11 +++++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index a679446f60b..baaf029861d 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -71,16 +71,16 @@ function createTypeScriptLanguageService(ts, options) { // Add entrypoints options.entryPoints.forEach(entryPoint => { const filePath = path_1.default.join(options.sourcesRoot, entryPoint); - FILES.set(filePath, fs_1.default.readFileSync(filePath).toString()); + FILES.set(path_1.default.normalize(filePath), fs_1.default.readFileSync(filePath).toString()); }); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES.set(path_1.default.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`), inlineEntryPoint); + FILES.set(path_1.default.normalize(path_1.default.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`)), inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path_1.default.join(options.sourcesRoot, typing); - FILES.set(filePath, fs_1.default.readFileSync(filePath).toString()); + FILES.set(path_1.default.normalize(filePath), fs_1.default.readFileSync(filePath).toString()); }); const basePath = path_1.default.join(options.sourcesRoot, '..'); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, basePath).options; diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 9c2fcc11925..3d1e785e073 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -112,18 +112,18 @@ function createTypeScriptLanguageService(ts: typeof import('typescript'), option // Add entrypoints options.entryPoints.forEach(entryPoint => { const filePath = path.join(options.sourcesRoot, entryPoint); - FILES.set(filePath, fs.readFileSync(filePath).toString()); + FILES.set(path.normalize(filePath), fs.readFileSync(filePath).toString()); }); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES.set(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`), inlineEntryPoint); + FILES.set(path.normalize(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`)), inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path.join(options.sourcesRoot, typing); - FILES.set(filePath, fs.readFileSync(filePath).toString()); + FILES.set(path.normalize(filePath), fs.readFileSync(filePath).toString()); }); const basePath = path.join(options.sourcesRoot, '..'); diff --git a/build/lib/typeScriptLanguageServiceHost.js b/build/lib/typeScriptLanguageServiceHost.js index d90ad86f9be..7abc708cc64 100644 --- a/build/lib/typeScriptLanguageServiceHost.js +++ b/build/lib/typeScriptLanguageServiceHost.js @@ -10,6 +10,10 @@ exports.TypeScriptLanguageServiceHost = void 0; *--------------------------------------------------------------------------------------------*/ const typescript_1 = __importDefault(require("typescript")); const node_fs_1 = __importDefault(require("node:fs")); +const node_path_1 = require("node:path"); +function normalizePath(filePath) { + return (0, node_path_1.normalize)(filePath); +} /** * A TypeScript language service host */ @@ -39,6 +43,7 @@ class TypeScriptLanguageServiceHost { return '1'; } getScriptSnapshot(fileName) { + fileName = normalizePath(fileName); if (this.topLevelFiles.has(fileName)) { return this.ts.ScriptSnapshot.fromString(this.topLevelFiles.get(fileName)); } @@ -56,12 +61,14 @@ class TypeScriptLanguageServiceHost { return this.ts.getDefaultLibFilePath(options); } readFile(path, encoding) { + path = normalizePath(path); if (this.topLevelFiles.get(path)) { return this.topLevelFiles.get(path); } return typescript_1.default.sys.readFile(path, encoding); } fileExists(path) { + path = normalizePath(path); if (this.topLevelFiles.has(path)) { return true; } diff --git a/build/lib/typeScriptLanguageServiceHost.ts b/build/lib/typeScriptLanguageServiceHost.ts index faa11d44da3..f3bacd617d5 100644 --- a/build/lib/typeScriptLanguageServiceHost.ts +++ b/build/lib/typeScriptLanguageServiceHost.ts @@ -5,9 +5,14 @@ import ts from 'typescript'; import fs from 'node:fs'; +import { normalize } from 'node:path'; export type IFileMap = Map; +function normalizePath(filePath: string): string { + return normalize(filePath); +} + /** * A TypeScript language service host */ @@ -36,6 +41,8 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return '1'; } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + fileName = normalizePath(fileName); + if (this.topLevelFiles.has(fileName)) { return this.ts.ScriptSnapshot.fromString(this.topLevelFiles.get(fileName)!); } else { @@ -52,12 +59,16 @@ export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { return this.ts.getDefaultLibFilePath(options); } readFile(path: string, encoding?: string): string | undefined { + path = normalizePath(path); + if (this.topLevelFiles.get(path)) { return this.topLevelFiles.get(path); } return ts.sys.readFile(path, encoding); } fileExists(path: string): boolean { + path = normalizePath(path); + if (this.topLevelFiles.has(path)) { return true; } From 3cc447e709f1ed5556923c329e2fbf009d7aeab7 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:04:35 +0200 Subject: [PATCH 1530/4355] Improve layering for git model (#272857) * Improve layering for git model - Git clone doesn't belong in the model, removed it - All the extra repo picking didn't seem to fit into `Git` though, as that is really about git operations - Added a `CloneUtils` namespace for all the clone stuff to live. * Update extensions/git/src/clone.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * CloneManager class * public/private --- extensions/git/src/api/api1.ts | 9 +- extensions/git/src/api/extension.ts | 17 +- extensions/git/src/cloneManager.ts | 250 ++++++++++++++++++++++++++++ extensions/git/src/commands.ts | 8 +- extensions/git/src/main.ts | 19 ++- extensions/git/src/model.ts | 250 ++-------------------------- 6 files changed, 296 insertions(+), 257 deletions(-) create mode 100644 extensions/git/src/cloneManager.ts diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index b57a18e988b..55dbdf19d34 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -15,6 +15,7 @@ import { GitExtensionImpl } from './extension'; import { GitBaseApi } from '../git-base'; import { PickRemoteSourceOptions } from '../typings/git-base'; import { OperationKind, OperationResult } from '../operation'; +import { CloneManager } from '../cloneManager'; class ApiInputBox implements InputBox { #inputBox: SourceControlInputBox; @@ -331,10 +332,12 @@ export class ApiGit implements Git { export class ApiImpl implements API { #model: Model; + #cloneManager: CloneManager; readonly git: ApiGit; - constructor(model: Model) { - this.#model = model; + constructor(privates: { model: Model; cloneManager: CloneManager }) { + this.#model = privates.model; + this.#cloneManager = privates.cloneManager; this.git = new ApiGit(this.#model); } @@ -401,7 +404,7 @@ export class ApiImpl implements API { async clone(uri: Uri, options?: CloneOptions): Promise { const parentPath = options?.parentPath?.fsPath; - const result = await this.#model.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction, skipCache: options?.skipCache }); + const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction, skipCache: options?.skipCache }); return result ? Uri.file(result) : null; } diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index d381ebc7f64..3bbb717e23f 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -7,6 +7,7 @@ import { Model } from '../model'; import { GitExtension, Repository, API } from './git'; import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; +import { CloneManager } from '../cloneManager'; function deprecated(original: any, context: ClassMemberDecoratorContext) { if (context.kind !== 'method') { @@ -28,6 +29,7 @@ export class GitExtensionImpl implements GitExtension { readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; private _model: Model | undefined = undefined; + private _cloneManager: CloneManager | undefined = undefined; set model(model: Model | undefined) { this._model = model; @@ -46,10 +48,15 @@ export class GitExtensionImpl implements GitExtension { return this._model; } - constructor(model?: Model) { - if (model) { + set cloneManager(cloneManager: CloneManager | undefined) { + this._cloneManager = cloneManager; + } + + constructor(privates?: { model: Model; cloneManager: CloneManager }) { + if (privates) { this.enabled = true; - this._model = model; + this._model = privates.model; + this._cloneManager = privates.cloneManager; } } @@ -72,7 +79,7 @@ export class GitExtensionImpl implements GitExtension { } getAPI(version: number): API { - if (!this._model) { + if (!this._model || !this._cloneManager) { throw new Error('Git model not found'); } @@ -80,6 +87,6 @@ export class GitExtensionImpl implements GitExtension { throw new Error(`No API version ${version} found.`); } - return new ApiImpl(this._model); + return new ApiImpl({ model: this._model, cloneManager: this._cloneManager }); } } diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts new file mode 100644 index 00000000000..bea8aa64ad3 --- /dev/null +++ b/extensions/git/src/cloneManager.ts @@ -0,0 +1,250 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { pickRemoteSource } from './remoteSource'; +import { l10n, workspace, window, Uri, ProgressLocation, commands } from 'vscode'; +import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { Model } from './model'; + +type PostCloneAction = 'none' | 'open' | 'prompt'; + +export interface CloneOptions { + parentPath?: string; + ref?: string; + recursive?: boolean; + postCloneAction?: PostCloneAction; + skipCache?: boolean; +} + +export class CloneManager { + constructor(private readonly model: Model, + private readonly telemetryReporter: TelemetryReporter, + private readonly repositoryCache: RepositoryCache) { } + + clone(url?: string, options: CloneOptions = {}) { + const cachedRepository = url ? this.repositoryCache.get(url) : undefined; + if (url && !options.skipCache && cachedRepository && (cachedRepository.length > 0)) { + return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); + } + return this.cloneRepository(url, options.parentPath, options); + } + + private async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: PostCloneAction } = {}): Promise { + if (!url || typeof url !== 'string') { + url = await pickRemoteSource({ + providerLabel: provider => l10n.t('Clone from {0}', provider.name), + urlLabel: l10n.t('Clone from URL') + }); + } + + if (!url) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); + return; + } + + url = url.trim().replace(/^git\s+clone\s+/, ''); + + if (!parentPath) { + const config = workspace.getConfiguration('git'); + let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); + defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); + + const uris = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(defaultCloneDirectory), + title: l10n.t('Choose a folder to clone {0} into', url), + openLabel: l10n.t('Select as Repository Destination') + }); + + if (!uris || uris.length === 0) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); + return; + } + + const uri = uris[0]; + parentPath = uri.fsPath; + } + + try { + const opts = { + location: ProgressLocation.Notification, + title: l10n.t('Cloning git repository "{0}"...', url), + cancellable: true + }; + + const repositoryPath = await window.withProgress( + opts, + (progress, token) => this.model.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) + ); + + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); + + enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } + let action: PostCloneAction | undefined = undefined; + + if (options.postCloneAction) { + if (options.postCloneAction === 'open') { + action = PostCloneAction.Open; + } else if (options.postCloneAction === 'none') { + action = PostCloneAction.None; + } + } else { + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; + } + } + + if (action === undefined) { + let message = l10n.t('Would you like to open the cloned repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); + const choices = [open, openNewWindow]; + + const addToWorkspace = l10n.t('Add to Workspace'); + if (workspace.workspaceFolders) { + message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, { modal: true }, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } + + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); + + const uri = Uri.file(repositoryPath); + + if (action === PostCloneAction.Open) { + commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } else if (action === PostCloneAction.AddToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (action === PostCloneAction.OpenNewWindow) { + commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); + } + + return repositoryPath; + } catch (err) { + if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); + } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } else { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); + } + + throw err; + } + } + + private async doPostCloneAction(target: string, postCloneAction?: PostCloneAction): Promise { + const forceReuseWindow = ((workspace.workspaceFile === undefined) && (workspace.workspaceFolders === undefined)); + if (postCloneAction === 'open') { + await commands.executeCommand('vscode.openFolder', Uri.file(target), { forceReuseWindow }); + } + } + + private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: PostCloneAction): Promise { + try { + const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { + const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); + const label = isWorkspace ? l10n.t('Workspace: {0}', path.basename(knownFolder.workspacePath, '.code-workspace')) : path.basename(knownFolder.workspacePath); + return { label, description: knownFolder.workspacePath, item: knownFolder }; + }); + const cloneAgain = { label: l10n.t('Clone again') }; + items.push(cloneAgain); + const placeHolder = l10n.t('Open Existing Repository Clone'); + const pick = await window.showQuickPick(items, { placeHolder, canPickMany: false }); + if (pick === cloneAgain) { + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction })) ?? undefined; + } + if (!pick?.item) { + return undefined; + } + return pick.item.workspacePath; + } catch { + return undefined; + } + } + + private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: PostCloneAction, parentPath?: string, ref?: string): Promise { + // Gather existing folders/workspace files (ignore ones that no longer exist) + const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { + const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); + if (stat) { + return folder; + } + return undefined; + } + ))).filter((folder): folder is RepositoryCacheInfo => folder !== undefined); + + if (!existingCachedRepositories.length) { + // fallback to clone + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction }) ?? undefined); + } + + // First, find the cached repo that exists in the current workspace + const matchingInCurrentWorkspace = existingCachedRepositories?.find(cachedRepo => { + return workspace.workspaceFolders?.some(workspaceFolder => workspaceFolder.uri.fsPath === cachedRepo.workspacePath); + }); + + if (matchingInCurrentWorkspace) { + return matchingInCurrentWorkspace.workspacePath; + } + + let repoForWorkspace: string | undefined = (existingCachedRepositories.length === 1 ? existingCachedRepositories[0].workspacePath : undefined); + if (!repoForWorkspace) { + repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); + } + if (repoForWorkspace) { + return this.doPostCloneAction(repoForWorkspace, postCloneAction); + } + return undefined; + } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a7452e9d8f4..cc0ad43098c 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -19,6 +19,7 @@ import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; import { RemoteSourceAction } from './typings/git-base'; +import { CloneManager } from './cloneManager'; abstract class CheckoutCommandItem implements QuickPickItem { abstract get label(): string; @@ -774,7 +775,8 @@ export class CommandCenter { private model: Model, private globalState: Memento, private logger: LogOutputChannel, - private telemetryReporter: TelemetryReporter + private telemetryReporter: TelemetryReporter, + private cloneManager: CloneManager ) { this.disposables = Commands.map(({ commandId, key, method, options }) => { const command = this.createCommand(commandId, key, method, options); @@ -1016,12 +1018,12 @@ export class CommandCenter { @command('git.clone') async clone(url?: string, parentPath?: string, options?: { ref?: string }): Promise { - await this.model.clone(url, { parentPath, ...options }); + await this.cloneManager.clone(url, { parentPath, ...options }); } @command('git.cloneRecursive') async cloneRecursive(url?: string, parentPath?: string): Promise { - await this.model.clone(url, { parentPath, recursive: true }); + await this.cloneManager.clone(url, { parentPath, recursive: true }); } @command('git.init') diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index e48e2e339b5..30c8fbaacdb 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -27,6 +27,7 @@ import { GitPostCommitCommandsProvider } from './postCommitCommands'; import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider'; import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManager } from './diagnostics'; import { GitBlameController } from './blame'; +import { CloneManager } from './cloneManager'; const deactivateTasks: { (): Promise }[] = []; @@ -36,7 +37,7 @@ export async function deactivate(): Promise { } } -async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { +async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise<{ model: Model; cloneManager: CloneManager }> { const pathValue = workspace.getConfiguration('git').get('path'); let pathHints = Array.isArray(pathValue) ? pathValue : pathValue ? [pathValue] : []; @@ -90,6 +91,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, }); const model = new Model(git, askpass, context.globalState, context.workspaceState, logger, telemetryReporter); disposables.push(model); + const cloneManager = new CloneManager(model, telemetryReporter, model.repositoryCache); const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`); model.onDidOpenRepository(onRepository, null, disposables); @@ -108,7 +110,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter); + const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter, cloneManager); disposables.push( cc, new GitFileSystemProvider(model, logger), @@ -134,7 +136,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, checkGitVersion(info); commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0); - return model; + return { model, cloneManager }; } async function isGitRepository(folder: WorkspaceFolder): Promise { @@ -210,13 +212,18 @@ export async function _activate(context: ExtensionContext): Promise workspace.getConfiguration('git', null).get('enabled') === true); const result = new GitExtensionImpl(); - eventToPromise(onEnabled).then(async () => result.model = await createModel(context, logger, telemetryReporter, disposables)); + eventToPromise(onEnabled).then(async () => { + const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables); + result.model = model; + result.cloneManager = cloneManager; + }); return result; } try { - const model = await createModel(context, logger, telemetryReporter, disposables); - return new GitExtensionImpl(model); + const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables); + + return new GitExtensionImpl({ model, cloneManager }); } catch (err) { console.warn(err.message); logger.warn(`[main] Failed to create model: ${err}`); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 2d70991f527..b2c536e5e07 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, WorkspaceFolder, ThemeIcon } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { IRepositoryResolver, Repository, RepositoryState } from './repository'; @@ -21,8 +20,7 @@ import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; import { IBranchProtectionProviderRegistry } from './branchProtection'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; -import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; -import { pickRemoteSource } from './remoteSource'; +import { RepositoryCache } from './repositoryCache'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { @@ -63,16 +61,6 @@ interface OpenRepository extends Disposable { repository: Repository; } -type PostCloneAction = 'none' | 'open' | 'prompt'; - -export interface CloneOptions { - parentPath?: string; - ref?: string; - recursive?: boolean; - postCloneAction?: PostCloneAction; - skipCache?: boolean; -} - class ClosedRepositoriesManager { private _repositories: Set; @@ -288,11 +276,14 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi */ private _workspaceFolders = new Map(); - private repositoryCache: RepositoryCache; + private readonly _repositoryCache: RepositoryCache; + get repositoryCache(): RepositoryCache { + return this._repositoryCache; + } private disposables: Disposable[] = []; - constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private readonly telemetryReporter: TelemetryReporter) { // Repositories managers this._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState); this._parentRepositoriesManager = new ParentRepositoriesManager(globalState); @@ -313,7 +304,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.setState('uninitialized'); this.doInitialScan().finally(() => this.setState('initialized')); - this.repositoryCache = new RepositoryCache(globalState, logger); + this._repositoryCache = new RepositoryCache(globalState, logger); } private async doInitialScan(): Promise { @@ -669,7 +660,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Open repository const [dotGit, repositoryRootRealPath] = await Promise.all([this.git.getRepositoryDotGit(repositoryRoot), this.getRepositoryRootRealPath(repositoryRoot)]); const gitRepository = this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger); - const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter, this.repositoryCache); + const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter, this._repositoryCache); this.open(repository); this._closedRepositoriesManager.deleteRepository(repository.root); @@ -681,7 +672,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Do not await this, we want SCM // to know about the repo asap repository.status().then(() => { - this.repositoryCache.update(repository.remotes, [], repository.root); + this._repositoryCache.update(repository.remotes, [], repository.root); }); } catch (err) { // noop @@ -876,7 +867,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.logger.info(`[Model][close] Repository: ${repository.root}`); this._closedRepositoriesManager.addRepository(openRepository.repository.root); - this.repositoryCache.update(repository.remotes, [], repository.root); + this._repositoryCache.update(repository.remotes, [], repository.root); openRepository.dispose(); } @@ -1104,227 +1095,6 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return this._unsafeRepositoriesManager.deleteRepository(repository); } - async clone(url?: string, options: CloneOptions = {}) { - const cachedRepository = url ? this.repositoryCache.get(url) : undefined; - if (url && !options.skipCache && cachedRepository && (cachedRepository.length > 0)) { - return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); - } - return this.cloneRepository(url, options.parentPath, options); - } - - async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: PostCloneAction } = {}): Promise { - if (!url || typeof url !== 'string') { - url = await pickRemoteSource({ - providerLabel: provider => l10n.t('Clone from {0}', provider.name), - urlLabel: l10n.t('Clone from URL') - }); - } - - if (!url) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); - return; - } - - url = url.trim().replace(/^git\s+clone\s+/, ''); - - if (!parentPath) { - const config = workspace.getConfiguration('git'); - let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); - defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - - const uris = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: Uri.file(defaultCloneDirectory), - title: l10n.t('Choose a folder to clone {0} into', url), - openLabel: l10n.t('Select as Repository Destination') - }); - - if (!uris || uris.length === 0) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); - return; - } - - const uri = uris[0]; - parentPath = uri.fsPath; - } - - try { - const opts = { - location: ProgressLocation.Notification, - title: l10n.t('Cloning git repository "{0}"...', url), - cancellable: true - }; - - const repositoryPath = await window.withProgress( - opts, - (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) - ); - - const config = workspace.getConfiguration('git'); - const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - - enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } - let action: PostCloneAction | undefined = undefined; - - if (options.postCloneAction) { - if (options.postCloneAction === 'open') { - action = PostCloneAction.Open; - } else if (options.postCloneAction === 'none') { - action = PostCloneAction.None; - } - } else { - if (openAfterClone === 'always') { - action = PostCloneAction.Open; - } else if (openAfterClone === 'alwaysNewWindow') { - action = PostCloneAction.OpenNewWindow; - } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { - action = PostCloneAction.Open; - } - } - - if (action === undefined) { - let message = l10n.t('Would you like to open the cloned repository?'); - const open = l10n.t('Open'); - const openNewWindow = l10n.t('Open in New Window'); - const choices = [open, openNewWindow]; - - const addToWorkspace = l10n.t('Add to Workspace'); - if (workspace.workspaceFolders) { - message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, { modal: true }, ...choices); - - action = result === open ? PostCloneAction.Open - : result === openNewWindow ? PostCloneAction.OpenNewWindow - : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; - } - - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); - - const uri = Uri.file(repositoryPath); - - if (action === PostCloneAction.Open) { - commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (action === PostCloneAction.AddToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (action === PostCloneAction.OpenNewWindow) { - commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); - } - - return repositoryPath; - } catch (err) { - if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); - } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { - return; - } else { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); - } - - throw err; - } - } - - private async postCloneAction(target: string, postCloneAction?: PostCloneAction): Promise { - const forceReuseWindow = ((workspace.workspaceFile === undefined) && (workspace.workspaceFolders === undefined)); - if (postCloneAction === 'open') { - await commands.executeCommand('vscode.openFolder', Uri.file(target), { forceReuseWindow }); - } - } - - private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: PostCloneAction): Promise { - try { - const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { - const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); - const label = isWorkspace ? l10n.t('Workspace: {0}', path.basename(knownFolder.workspacePath, '.code-workspace')) : path.basename(knownFolder.workspacePath); - return { label, description: knownFolder.workspacePath, item: knownFolder }; - }); - const cloneAgain = { label: l10n.t('Clone again') }; - items.push(cloneAgain); - const placeHolder = l10n.t('Open Existing Repository Clone'); - const pick = await window.showQuickPick(items, { placeHolder, canPickMany: false }); - if (pick === cloneAgain) { - return (await this.cloneRepository(url, parentPath, { ref, postCloneAction })) ?? undefined; - } - if (!pick?.item) { - return undefined; - } - return pick.item.workspacePath; - } catch { - return undefined; - } - } - - private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: PostCloneAction, parentPath?: string, ref?: string): Promise { - // Gather existing folders/workspace files (ignore ones that no longer exist) - const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { - const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); - if (stat) { - return folder; - } - return undefined; - } - ))).filter((folder): folder is RepositoryCacheInfo => folder !== undefined); - - if (!existingCachedRepositories.length) { - // fallback to clone - return (await this.cloneRepository(url, parentPath, { ref, postCloneAction }) ?? undefined); - } - - // First, find the cached repo that exists in the current workspace - const matchingInCurrentWorkspace = existingCachedRepositories?.find(cachedRepo => { - return workspace.workspaceFolders?.some(workspaceFolder => workspaceFolder.uri.fsPath === cachedRepo.workspacePath); - }); - - if (matchingInCurrentWorkspace) { - return matchingInCurrentWorkspace.workspacePath; - } - - let repoForWorkspace: string | undefined = (existingCachedRepositories.length === 1 ? existingCachedRepositories[0].workspacePath : undefined); - if (!repoForWorkspace) { - repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); - } - if (repoForWorkspace) { - return this.postCloneAction(repoForWorkspace, postCloneAction); - } - return undefined; - } - private async isRepositoryOutsideWorkspace(repositoryPath: string): Promise { const workspaceFolders = (workspace.workspaceFolders || []) .filter(folder => folder.uri.scheme === 'file'); From edf4ea5879f5e15302ac4923cebd1d444ee35f7e Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:55:21 +0200 Subject: [PATCH 1531/4355] Simplify git clone API (#272912) - Don't allow to skip the cache - Only have "none" post commit action - Consolidate post commit logic - I ended up keeping the `Uri | null` return type for the `clone` API. It's just too useful, and there's no reason why we couldn't pass it back if the user didn't decide to open the clone and reload the window. --- extensions/git/src/api/api1.ts | 7 +- extensions/git/src/api/git.d.ts | 8 +- extensions/git/src/cloneManager.ts | 137 ++++++++++++++--------------- 3 files changed, 76 insertions(+), 76 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 55dbdf19d34..818dfc536e3 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -395,6 +395,11 @@ export class ApiImpl implements API { } } + async getRepositoryWorkspace(uri: Uri): Promise { + const workspaces = this.#model.repositoryCache.get(uri.toString()); + return workspaces ? workspaces.map(r => Uri.file(r.workspacePath)) : null; + } + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; await this.#model.git.init(path, options); @@ -404,7 +409,7 @@ export class ApiImpl implements API { async clone(uri: Uri, options?: CloneOptions): Promise { const parentPath = options?.parentPath?.fsPath; - const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction, skipCache: options?.skipCache }); + const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction }); return result ? Uri.file(result) : null; } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index f7813f13e8c..c59c2f90658 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -193,8 +193,7 @@ export interface CloneOptions { /** * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. */ - postCloneAction?: 'none' | 'open' | 'prompt'; - skipCache?: boolean; + postCloneAction?: 'none'; } export interface RefQuery { @@ -379,9 +378,12 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; init(root: Uri, options?: InitOptions): Promise; /** - * @returns The URI of either the cloned repository, or the workspace file or folder which contains the cloned repository. + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. */ clone(uri: Uri, options?: CloneOptions): Promise; openRepository(root: Uri): Promise; diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts index bea8aa64ad3..e25ee8d0df7 100644 --- a/extensions/git/src/cloneManager.ts +++ b/extensions/git/src/cloneManager.ts @@ -12,14 +12,14 @@ import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; import TelemetryReporter from '@vscode/extension-telemetry'; import { Model } from './model'; -type PostCloneAction = 'none' | 'open' | 'prompt'; +type ApiPostCloneAction = 'none'; +enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } export interface CloneOptions { parentPath?: string; ref?: string; recursive?: boolean; - postCloneAction?: PostCloneAction; - skipCache?: boolean; + postCloneAction?: ApiPostCloneAction; } export class CloneManager { @@ -29,13 +29,13 @@ export class CloneManager { clone(url?: string, options: CloneOptions = {}) { const cachedRepository = url ? this.repositoryCache.get(url) : undefined; - if (url && !options.skipCache && cachedRepository && (cachedRepository.length > 0)) { + if (url && cachedRepository && (cachedRepository.length > 0)) { return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); } return this.cloneRepository(url, options.parentPath, options); } - private async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: PostCloneAction } = {}): Promise { + private async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: ApiPostCloneAction } = {}): Promise { if (!url || typeof url !== 'string') { url = await pickRemoteSource({ providerLabel: provider => l10n.t('Clone from {0}', provider.name), @@ -97,65 +97,7 @@ export class CloneManager { (progress, token) => this.model.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) ); - const config = workspace.getConfiguration('git'); - const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - - enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } - let action: PostCloneAction | undefined = undefined; - - if (options.postCloneAction) { - if (options.postCloneAction === 'open') { - action = PostCloneAction.Open; - } else if (options.postCloneAction === 'none') { - action = PostCloneAction.None; - } - } else { - if (openAfterClone === 'always') { - action = PostCloneAction.Open; - } else if (openAfterClone === 'alwaysNewWindow') { - action = PostCloneAction.OpenNewWindow; - } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { - action = PostCloneAction.Open; - } - } - - if (action === undefined) { - let message = l10n.t('Would you like to open the cloned repository?'); - const open = l10n.t('Open'); - const openNewWindow = l10n.t('Open in New Window'); - const choices = [open, openNewWindow]; - - const addToWorkspace = l10n.t('Add to Workspace'); - if (workspace.workspaceFolders) { - message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, { modal: true }, ...choices); - - action = result === open ? PostCloneAction.Open - : result === openNewWindow ? PostCloneAction.OpenNewWindow - : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; - } - - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); - - const uri = Uri.file(repositoryPath); - - if (action === PostCloneAction.Open) { - commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (action === PostCloneAction.AddToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (action === PostCloneAction.OpenNewWindow) { - commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); - } + await this.doPostCloneAction(repositoryPath, options.postCloneAction); return repositoryPath; } catch (err) { @@ -183,14 +125,64 @@ export class CloneManager { } } - private async doPostCloneAction(target: string, postCloneAction?: PostCloneAction): Promise { - const forceReuseWindow = ((workspace.workspaceFile === undefined) && (workspace.workspaceFolders === undefined)); - if (postCloneAction === 'open') { - await commands.executeCommand('vscode.openFolder', Uri.file(target), { forceReuseWindow }); + private async doPostCloneAction(target: string, postCloneAction?: ApiPostCloneAction): Promise { + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); + + let action: PostCloneAction | undefined = undefined; + + if (postCloneAction && postCloneAction === 'none') { + action = PostCloneAction.None; + } else { + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; + } + } + + if (action === undefined) { + let message = l10n.t('Would you like to open the cloned repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); + const choices = [open, openNewWindow]; + + const addToWorkspace = l10n.t('Add to Workspace'); + if (workspace.workspaceFolders) { + message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, { modal: true }, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } + + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); + + const uri = Uri.file(target); + + if (action === PostCloneAction.Open) { + commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } else if (action === PostCloneAction.AddToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (action === PostCloneAction.OpenNewWindow) { + commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); } } - private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: PostCloneAction): Promise { + private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: ApiPostCloneAction): Promise { try { const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); @@ -213,7 +205,7 @@ export class CloneManager { } } - private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: PostCloneAction, parentPath?: string, ref?: string): Promise { + private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: ApiPostCloneAction, parentPath?: string, ref?: string): Promise { // Gather existing folders/workspace files (ignore ones that no longer exist) const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); @@ -243,8 +235,9 @@ export class CloneManager { repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); } if (repoForWorkspace) { - return this.doPostCloneAction(repoForWorkspace, postCloneAction); + await this.doPostCloneAction(repoForWorkspace, postCloneAction); + return repoForWorkspace; } - return undefined; + return; } } From bbf25583ab7caf8ed7b119d1cb3ab4d6f1c99e7b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 23 Oct 2025 17:11:33 +0200 Subject: [PATCH 1532/4355] Policy improvements (#272923) - show settings configured with policy even if they are excluded - Make settings configured with policy read only - Rename filter action to Organization policies --- .../configuration/common/configuration.ts | 1 + .../common/configurationModels.ts | 2 + .../common/configurationRegistry.ts | 20 +- .../common/configurationService.ts | 1 + .../test/common/testConfigurationService.ts | 1 + .../contrib/chat/browser/chat.contribution.ts | 2 +- .../preferences/browser/settingsSearchMenu.ts | 4 +- .../preferences/browser/settingsTree.ts | 5 +- .../preferences/browser/settingsWidgets.ts | 7 +- .../browser/configurationService.ts | 1 + .../common/configurationModels.ts | 1 + .../preferences/common/preferences.ts | 1 - .../preferences/common/preferencesModels.ts | 288 ++++++++++-------- 13 files changed, 192 insertions(+), 142 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 7ab5f84c82f..8e6c465dfcc 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -196,6 +196,7 @@ export interface IConfigurationService { keys(): { default: string[]; + policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 531820e4b85..bf1507505d0 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -790,6 +790,7 @@ export class Configuration { keys(workspace: Workspace | undefined): { default: string[]; + policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[]; @@ -797,6 +798,7 @@ export class Configuration { const folderConfigurationModel = this.getFolderConfigurationModelForResource(undefined, workspace); return { default: this._defaultConfiguration.keys.slice(0), + policy: this._policyConfiguration.keys.slice(0), user: this.userConfiguration.keys.slice(0), workspace: this._workspaceConfiguration.keys.slice(0), workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys.slice(0) : [] diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 60e11a0e00c..d49d0c373c5 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -262,6 +262,12 @@ export interface IConfigurationDefaults { } export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { + section?: { + id?: string; + title?: string; + order?: number; + extensionInfo?: IExtensionInfo; + }; defaultDefaultValue?: any; source?: IExtensionInfo; // Source of the Property defaultValueSource?: ConfigurationDefaultValueSource; // Source of the Default Value @@ -487,6 +493,12 @@ class ConfigurationRegistry extends Disposable implements IConfigurationRegistry private updateDefaultOverrideProperty(key: string, newDefaultOverride: IConfigurationDefaultOverrideValue, source: IExtensionInfo | undefined): void { const property: IRegisteredConfigurationPropertySchema = { + section: { + id: this.defaultLanguageConfigurationOverridesNode.id, + title: this.defaultLanguageConfigurationOverridesNode.title, + order: this.defaultLanguageConfigurationOverridesNode.order, + extensionInfo: this.defaultLanguageConfigurationOverridesNode.extensionInfo + }, type: 'object', default: newDefaultOverride.value, description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0}.", getLanguageTagSettingPlainKey(key)), @@ -655,6 +667,12 @@ class ConfigurationRegistry extends Disposable implements IConfigurationRegistry if (properties) { for (const key in properties) { const property: IRegisteredConfigurationPropertySchema = properties[key]; + property.section = { + id: configuration.id, + title: configuration.title, + order: configuration.order, + extensionInfo: configuration.extensionInfo + }; if (validate && validateProperty(key, property)) { delete properties[key]; continue; @@ -717,7 +735,7 @@ class ConfigurationRegistry extends Disposable implements IConfigurationRegistry } } - // TODO: @sandy081 - Remove this method and include required info in getConfigurationProperties + // Only for tests getConfigurations(): IConfigurationNode[] { return this.configurationContributors; } diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 59ce69beda6..dc47c790348 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -149,6 +149,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe keys(): { default: string[]; + policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[]; diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index da0001d0fdb..17e4c818380 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -78,6 +78,7 @@ export class TestConfigurationService implements IConfigurationService { public keys() { return { default: Object.keys(Registry.as(Extensions.Configuration).getConfigurationProperties()), + policy: [], user: Object.keys(this.configuration), workspace: [], workspaceFolder: [] diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f7e955dae90..e04e2096b86 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -504,7 +504,7 @@ configurationRegistry.registerConfiguration({ description: nls.localize('mcp.gallery.serviceUrl', "Configure the MCP Gallery service URL to connect to"), default: '', scope: ConfigurationScope.APPLICATION, - tags: ['usesOnlineServices'], + tags: ['usesOnlineServices', 'advanced'], included: false, policy: { name: 'McpGalleryServiceUrl', diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 620532e60c4..335373d99db 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -147,8 +147,8 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu ), this.createToggleAction( 'policySettingsSearch', - localize('policySettingsSearch', "Policy services"), - localize('policySettingsSearchTooltip', "Show settings for policy services"), + localize('policySettingsSearch', "Organization policies"), + localize('policySettingsSearchTooltip', "Show organization policy settings"), `@${POLICY_SETTING_TAG}` ), new Separator(), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 8b0615038dd..ffd51c1da27 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1700,7 +1700,7 @@ abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer imp protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingIncludeExcludeItemTemplate, onChange: (value: string) => void): void { const value = getIncludeExcludeDisplayValue(dataElement); - template.includeExcludeWidget.setValue(value); + template.includeExcludeWidget.setValue(value, { isReadOnly: dataElement.hasPolicyValue }); template.context = dataElement; template.elementDisposables.add(toDisposable(() => { template.includeExcludeWidget.cancelEdit(); @@ -1771,6 +1771,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void { template.onChange = undefined; template.inputBox.value = dataElement.value; + template.inputBox.setEnabled(!dataElement.hasPolicyValue); template.inputBox.setAriaLabel(dataElement.setting.key); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { @@ -1919,6 +1920,7 @@ class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRender template.selectBox.setOptions(displayOptions); template.selectBox.setAriaLabel(dataElement.setting.key); + template.selectBox.setEnabled(!dataElement.hasPolicyValue); let idx = settingEnum.indexOf(dataElement.value); if (idx === -1) { @@ -1989,6 +1991,7 @@ class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRend dataElement.value.toString() : ''; template.inputBox.step = dataElement.valueType.includes('integer') ? '1' : 'any'; template.inputBox.setAriaLabel(dataElement.setting.key); + template.inputBox.setEnabled(!dataElement.hasPolicyValue); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(nullNumParseFn(value)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 681f5a06c0a..0222b7dd360 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -434,8 +434,9 @@ export abstract class AbstractListSettingWidget extend } interface IListSetValueOptions { - showAddButton: boolean; + showAddButton?: boolean; keySuggester?: IObjectKeySuggester; + isReadOnly?: boolean; } export interface IListDataItem { @@ -452,10 +453,12 @@ interface ListSettingWidgetDragDetails { export class ListSettingWidget extends AbstractListSettingWidget { private keyValueSuggester: IObjectKeySuggester | undefined; private showAddButton: boolean = true; + private isEditable: boolean = true; override setValue(listData: TListDataItem[], options?: IListSetValueOptions) { this.keyValueSuggester = options?.keySuggester; - this.showAddButton = options?.showAddButton ?? true; + this.isEditable = options?.isReadOnly === undefined ? true : !options.isReadOnly; + this.showAddButton = this.isEditable ? (options?.showAddButton ?? true) : false; super.setValue(listData); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 548ad0850dd..f7b58d5891f 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -412,6 +412,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat keys(): { default: string[]; + policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[]; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 00ca9f10b88..c73a1e38999 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -123,6 +123,7 @@ export class Configuration extends BaseConfiguration { override keys(): { default: string[]; + policy: string[]; user: string[]; workspace: string[]; workspaceFolder: string[]; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 56b95c1017d..2547976da46 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -53,7 +53,6 @@ export interface ISettingsGroup { } export interface ISettingsSection { - titleRange?: IRange; title?: string; settings: ISetting[]; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index dbb67285d19..8ac0088dde4 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -16,7 +16,7 @@ import { ISingleEditOperation } from '../../../../editor/common/core/editOperati import { ITextEditorModel } from '../../../../editor/common/services/resolverService.js'; import * as nls from '../../../../nls.js'; import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ConfigurationDefaultValueSource, ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { ConfigurationDefaultValueSource, ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorModel } from '../../../common/editor/editorModel.js'; @@ -512,10 +512,18 @@ export class DefaultSettings extends Disposable { } getRegisteredGroups(): ISettingsGroup[] { - const configurations = Registry.as(Extensions.Configuration).getConfigurations().slice(); - const groups = this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes) - .reduce((result, config, index, array) => this.parseConfig(config, result, array), [])); + const registry = Registry.as(Extensions.Configuration); + const allConfigurations: IStringDictionary = { ...registry.getConfigurationProperties() }; + const excludedConfigurations = registry.getExcludedConfigurationProperties(); + + for (const policyKey of this.configurationService.keys().policy ?? []) { + const policyConfiguration = excludedConfigurations[policyKey]; + if (policyConfiguration) { + allConfigurations[policyKey] = policyConfiguration; + } + } + const groups = this.removeEmptySettingsGroups(this.parseProperties(allConfigurations).sort(this.compareGroups)); return this.sortGroups(groups); } @@ -575,43 +583,68 @@ export class DefaultSettings extends Disposable { } satisfies ISettingsGroup; } - private parseConfig(config: IConfigurationNode, result: ISettingsGroup[], configurations: IConfigurationNode[], settingsGroup?: ISettingsGroup, seenSettings?: { [key: string]: boolean }): ISettingsGroup[] { - seenSettings = seenSettings ? seenSettings : {}; - let title = config.title; - if (!title) { - const configWithTitleAndSameId = configurations.find(c => (c.id === config.id) && c.title); - if (configWithTitleAndSameId) { - title = configWithTitleAndSameId.title; + private parseProperties(properties: IStringDictionary): ISettingsGroup[] { + const result: ISettingsGroup[] = []; + const byTitle = new Map(); + const byId = new Map(); + for (const [key, property] of Object.entries(properties)) { + if (!property.section) { + continue; } - } - if (title) { - if (!settingsGroup) { - settingsGroup = result.find(g => g.title === title && g.extensionInfo?.id === config.extensionInfo?.id); - if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; - result.push(settingsGroup); + + let settingsGroup: ISettingsGroup | undefined; + + if (property.section.title) { + const groups = byTitle.get(property.section.title); + if (groups) { + const extensionId = property.section.extensionInfo?.id; + settingsGroup = groups.find(g => g.extensionInfo?.id === extensionId); } - } else { - settingsGroup.sections[settingsGroup.sections.length - 1].title = title; } - } - if (config.properties) { + + if (!settingsGroup && property.section.id) { + const groups = byId.get(property.section.id); + if (groups) { + const extensionId = property.section.extensionInfo?.id; + settingsGroup = groups.find(g => g.extensionInfo?.id === extensionId); + } + if (settingsGroup && !settingsGroup?.title && property.section.title) { + settingsGroup.title = property.section.title; + const byTitleGroups = byTitle.get(property.section.title); + if (byTitleGroups) { + byTitleGroups.push(settingsGroup); + } else { + byTitle.set(property.section.title, [settingsGroup]); + } + } + } + if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: config.id || '', titleRange: nullRange, order: config.order, range: nullRange, extensionInfo: config.extensionInfo }; + settingsGroup = { sections: [{ title: property.section.title, settings: [] }], id: property.section.id || '', title: property.section.title ?? '', titleRange: nullRange, order: property.section.order, range: nullRange, extensionInfo: property.source }; result.push(settingsGroup); - } - const configurationSettings: ISetting[] = []; - for (const setting of [...settingsGroup.sections[settingsGroup.sections.length - 1].settings, ...this.parseSettings(config)]) { - if (!seenSettings[setting.key]) { - configurationSettings.push(setting); - seenSettings[setting.key] = true; + if (property.section.title) { + const byTitleGroups = byTitle.get(property.section.title); + if (byTitleGroups) { + byTitleGroups.push(settingsGroup); + } else { + byTitle.set(property.section.title, [settingsGroup]); + } + } + if (property.section.id) { + const byIdGroups = byId.get(property.section.id); + if (byIdGroups) { + byIdGroups.push(settingsGroup); + } else { + byId.set(property.section.id, [settingsGroup]); + } } } - if (configurationSettings.length) { - settingsGroup.sections[settingsGroup.sections.length - 1].settings = configurationSettings; + + const setting = this.parseSetting(key, property); + if (setting) { + settingsGroup.sections[0].settings.push(setting); } } - config.allOf?.forEach(c => this.parseConfig(c, result, configurations, settingsGroup, seenSettings)); return result; } @@ -626,110 +659,99 @@ export class DefaultSettings extends Disposable { return result; } - private parseSettings(config: IConfigurationNode): ISetting[] { - const result: ISetting[] = []; - - const settingsObject = config.properties; - const extensionInfo = config.extensionInfo; - - // Try using the title if the category id wasn't given - // (in which case the category id is the same as the extension id) - const categoryLabel = config.extensionInfo?.id === config.id ? config.title : config.id; - - for (const key in settingsObject) { - const prop: IConfigurationPropertySchema = settingsObject[key]; - if (this.matchesScope(prop)) { - const value = prop.default; - let description = (prop.markdownDescription || prop.description || ''); - if (typeof description !== 'string') { - description = ''; - } - const descriptionLines = description.split('\n'); - const overrides = OVERRIDE_PROPERTY_REGEX.test(key) ? this.parseOverrideSettings(prop.default) : []; - let listItemType: string | undefined; - if (prop.type === 'array' && prop.items && !Array.isArray(prop.items) && prop.items.type) { - if (prop.items.enum) { - listItemType = 'enum'; - } else if (!Array.isArray(prop.items.type)) { - listItemType = prop.items.type; - } - } - - const objectProperties = prop.type === 'object' ? prop.properties : undefined; - const objectPatternProperties = prop.type === 'object' ? prop.patternProperties : undefined; - const objectAdditionalProperties = prop.type === 'object' ? prop.additionalProperties : undefined; - - let enumToUse = prop.enum; - let enumDescriptions = prop.markdownEnumDescriptions ?? prop.enumDescriptions; - let enumDescriptionsAreMarkdown = !!prop.markdownEnumDescriptions; - if (listItemType === 'enum' && !Array.isArray(prop.items)) { - enumToUse = prop.items!.enum; - enumDescriptions = prop.items!.markdownEnumDescriptions ?? prop.items!.enumDescriptions; - enumDescriptionsAreMarkdown = !!prop.items!.markdownEnumDescriptions; - } + private parseSetting(key: string, prop: IRegisteredConfigurationPropertySchema): ISetting | undefined { + if (!this.matchesScope(prop)) { + return undefined; + } - let allKeysAreBoolean = false; - if (prop.type === 'object' && !prop.additionalProperties && prop.properties && Object.keys(prop.properties).length) { - allKeysAreBoolean = Object.keys(prop.properties).every(key => { - return prop.properties![key].type === 'boolean'; - }); - } + const value = prop.default; + let description = (prop.markdownDescription || prop.description || ''); + if (typeof description !== 'string') { + description = ''; + } + const descriptionLines = description.split('\n'); + const overrides = OVERRIDE_PROPERTY_REGEX.test(key) ? this.parseOverrideSettings(prop.default) : []; + let listItemType: string | undefined; + if (prop.type === 'array' && prop.items && !Array.isArray(prop.items) && prop.items.type) { + if (prop.items.enum) { + listItemType = 'enum'; + } else if (!Array.isArray(prop.items.type)) { + listItemType = prop.items.type; + } + } - let isLanguageTagSetting = false; - if (OVERRIDE_PROPERTY_REGEX.test(key)) { - isLanguageTagSetting = true; - } + const objectProperties = prop.type === 'object' ? prop.properties : undefined; + const objectPatternProperties = prop.type === 'object' ? prop.patternProperties : undefined; + const objectAdditionalProperties = prop.type === 'object' ? prop.additionalProperties : undefined; + + let enumToUse = prop.enum; + let enumDescriptions = prop.markdownEnumDescriptions ?? prop.enumDescriptions; + let enumDescriptionsAreMarkdown = !!prop.markdownEnumDescriptions; + if (listItemType === 'enum' && !Array.isArray(prop.items)) { + enumToUse = prop.items!.enum; + enumDescriptions = prop.items!.markdownEnumDescriptions ?? prop.items!.enumDescriptions; + enumDescriptionsAreMarkdown = !!prop.items!.markdownEnumDescriptions; + } - let defaultValueSource: ConfigurationDefaultValueSource | undefined; - if (!isLanguageTagSetting) { - const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; - if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { - defaultValueSource = registeredConfigurationProp.defaultValueSource; - } - } + let allKeysAreBoolean = false; + if (prop.type === 'object' && !prop.additionalProperties && prop.properties && Object.keys(prop.properties).length) { + allKeysAreBoolean = Object.keys(prop.properties).every(key => { + return prop.properties![key].type === 'boolean'; + }); + } - if (!enumToUse && (prop.enumItemLabels || enumDescriptions || enumDescriptionsAreMarkdown)) { - console.error(`The setting ${key} has enum-related fields, but doesn't have an enum field. This setting may render improperly in the Settings editor.`); - } + let isLanguageTagSetting = false; + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + isLanguageTagSetting = true; + } - result.push({ - key, - value, - description: descriptionLines, - descriptionIsMarkdown: !!prop.markdownDescription, - range: nullRange, - keyRange: nullRange, - valueRange: nullRange, - descriptionRanges: [], - overrides, - scope: prop.scope, - type: prop.type, - arrayItemType: listItemType, - objectProperties, - objectPatternProperties, - objectAdditionalProperties, - enum: enumToUse, - enumDescriptions: enumDescriptions, - enumDescriptionsAreMarkdown: enumDescriptionsAreMarkdown, - enumItemLabels: prop.enumItemLabels, - uniqueItems: prop.uniqueItems, - tags: prop.tags, - disallowSyncIgnore: prop.disallowSyncIgnore, - restricted: prop.restricted, - extensionInfo: extensionInfo, - deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, - deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, - validator: createValidator(prop), - allKeysAreBoolean, - editPresentation: prop.editPresentation, - order: prop.order, - nonLanguageSpecificDefaultValueSource: defaultValueSource, - isLanguageTagSetting, - categoryLabel - }); + let defaultValueSource: ConfigurationDefaultValueSource | undefined; + if (!isLanguageTagSetting) { + const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; + if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { + defaultValueSource = registeredConfigurationProp.defaultValueSource; } } - return result; + + if (!enumToUse && (prop.enumItemLabels || enumDescriptions || enumDescriptionsAreMarkdown)) { + console.error(`The setting ${key} has enum-related fields, but doesn't have an enum field. This setting may render improperly in the Settings editor.`); + } + + return { + key, + value, + description: descriptionLines, + descriptionIsMarkdown: !!prop.markdownDescription, + range: nullRange, + keyRange: nullRange, + valueRange: nullRange, + descriptionRanges: [], + overrides, + scope: prop.scope, + type: prop.type, + arrayItemType: listItemType, + objectProperties, + objectPatternProperties, + objectAdditionalProperties, + enum: enumToUse, + enumDescriptions: enumDescriptions, + enumDescriptionsAreMarkdown: enumDescriptionsAreMarkdown, + enumItemLabels: prop.enumItemLabels, + uniqueItems: prop.uniqueItems, + tags: prop.tags, + disallowSyncIgnore: prop.disallowSyncIgnore, + restricted: prop.restricted, + extensionInfo: prop.source, + deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, + deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, + validator: createValidator(prop), + allKeysAreBoolean, + editPresentation: prop.editPresentation, + order: prop.order, + nonLanguageSpecificDefaultValueSource: defaultValueSource, + isLanguageTagSetting, + categoryLabel: prop.source?.id === prop.section?.id ? prop.title : prop.section?.id + }; } private parseOverrideSettings(overrideSettings: any): ISetting[] { @@ -759,11 +781,11 @@ export class DefaultSettings extends Disposable { return true; } - private compareConfigurationNodes(c1: IConfigurationNode, c2: IConfigurationNode): number { - if (typeof c1.order !== 'number') { + private compareGroups(c1: ISettingsGroup, c2: ISettingsGroup): number { + if (typeof c1?.order !== 'number') { return 1; } - if (typeof c2.order !== 'number') { + if (typeof c2?.order !== 'number') { return -1; } if (c1.order === c2.order) { @@ -1006,9 +1028,7 @@ class SettingsContentBuilder { const groupStart = this.lineCountWithOffset + 1; for (const section of group.sections) { if (section.title) { - const sectionTitleStart = this.lineCountWithOffset + 1; this.addDescription([section.title], indent, this._contentByLines); - section.titleRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length }; } if (section.settings.length) { From 8b2b50543ae196e2e3a5eb83d6263738717a8afe Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:31:02 +0200 Subject: [PATCH 1533/4355] SCM - revert commits to hide "inline" actions in the Repositories view (#272931) * Revert "Fix unintended hiding of inline actions on SCM Changes view (fix #272716) (#272717)" This reverts commit ad758a48495f5be94365a573f888819323406d96. * Revert "SCM - hide inline actions in the Repositories view by default (#272471)" This reverts commit b42e7ba8b417522d99d47ae43770ac6d7e2de9f6. --- src/vs/workbench/contrib/scm/browser/menus.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index e8534f7bc99..59a953b6dfa 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -10,7 +10,7 @@ import { DisposableStore, IDisposable, dispose } from '../../../../base/common/l import './media/scm.css'; import { localize } from '../../../../nls.js'; import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IMenu, IMenuService, isIMenuItem, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { IMenu, IMenuService, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; @@ -345,14 +345,7 @@ export class SCMMenus implements ISCMMenus, IDisposable { this.repositoryMenuDisposables.clear(); for (const menuItem of MenuRegistry.getMenuItems(MenuId.SCMTitle)) { - // In order to keep the Repositories view clean, we hide the - // primary actions by default. Users can always promote actions - // from the `...` menu to inline actions. - let itemToAppend = menuItem; - if (isIMenuItem(menuItem) && menuItem.group === 'navigation') { - itemToAppend = { ...menuItem, isHiddenByDefault: true }; - } - this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, itemToAppend)); + this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); } })); } From 5e4cf386d9c3d354551e4ce7267e40278fa2bccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 23 Oct 2025 17:50:48 +0200 Subject: [PATCH 1534/4355] Revert "feat: reenable asar support" (#272935) Revert "feat: reenable asar support (#272552)" This reverts commit ff891375f466d46ff9f3c97486c5d06ce87c8d08. --- build/gulpfile.vscode.js | 10 ++- build/lib/asar.js | 20 +++--- build/lib/asar.ts | 22 +++---- build/linux/dependencies-generator.js | 3 +- build/linux/dependencies-generator.ts | 3 +- src/bootstrap-esm.ts | 66 ++++--------------- src/bootstrap-node.ts | 28 -------- src/vs/amdX.ts | 6 +- src/vs/base/common/network.ts | 4 +- src/vs/base/common/platform.ts | 1 - .../languageDetectionWorkerServiceImpl.ts | 3 +- .../threadedBackgroundTokenizerFactory.ts | 3 +- .../textMateTokenizationFeatureImpl.ts | 4 +- .../browser/treeSitterLibraryService.ts | 4 +- 14 files changed, 53 insertions(+), 124 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 0cd5be668a5..ed06b6a5aa8 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -288,17 +288,14 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const productionDependencies = getProductionDependencies(root); const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk'); - const deps = es.merge( - gulp.src(dependenciesSrc, { base: '.', dot: true }), - gulp.src(['node_modules/vsda/**'], { base: 'node_modules', dot: true }) // retain vsda at root level of asar for backward compatibility - ) + const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) .pipe(jsFilter.restore) - .pipe(createAsar(process.cwd(), [ + .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ '**/*.node', '**/@vscode/ripgrep/bin/*', '**/node-pty/build/Release/*', @@ -309,8 +306,9 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op '**/@vscode/vsce-sign/bin/*', ], [ '**/*.mk', + '!node_modules/vsda/**' // stay compatible with extensions that depend on us shipping `vsda` into ASAR ], [ - 'node_modules/vsda/**', // duplicate vsda in node_modules.asar.unpacked for backward compatibility + 'node_modules/vsda/**' // retain copy of `vsda` in node_modules for internal use ], 'node_modules.asar')); let all = es.merge( diff --git a/build/lib/asar.js b/build/lib/asar.js index c5691f96f2e..2da31a93904 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -32,7 +32,7 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile return false; }; // Files that should be duplicated between - // node_modules.asar.unpacked/node_modules and node_modules.asar.unpacked + // node_modules.asar and node_modules const shouldDuplicateFile = (file) => { for (const duplicateGlob of duplicateGlobs) { if ((0, minimatch_1.default)(file.relative, duplicateGlob)) { @@ -94,6 +94,14 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile })); return; } + if (shouldDuplicateFile(file)) { + this.queue(new vinyl_1.default({ + base: '.', + path: file.path, + stat: file.stat, + contents: file.contents + })); + } const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); if (shouldUnpack) { @@ -105,16 +113,6 @@ function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFile stat: file.stat, contents: file.contents })); - const shouldDuplicate = shouldDuplicateFile(file); - if (shouldDuplicate) { - const rootRelative = file.relative.replace(/^node_modules\//, ''); - this.queue(new vinyl_1.default({ - base: '.', - path: path_1.default.join(destFilename + '.unpacked', rootRelative), - stat: file.stat, - contents: file.contents - })); - } } else { // The file goes inside of xx.asar diff --git a/build/lib/asar.ts b/build/lib/asar.ts index 55d44314000..5f2df925bde 100644 --- a/build/lib/asar.ts +++ b/build/lib/asar.ts @@ -38,7 +38,7 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: }; // Files that should be duplicated between - // node_modules.asar.unpacked/node_modules and node_modules.asar.unpacked + // node_modules.asar and node_modules const shouldDuplicateFile = (file: VinylFile): boolean => { for (const duplicateGlob of duplicateGlobs) { if (minimatch(file.relative, duplicateGlob)) { @@ -107,7 +107,14 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: })); return; } - + if (shouldDuplicateFile(file)) { + this.queue(new VinylFile({ + base: '.', + path: file.path, + stat: file.stat, + contents: file.contents + })); + } const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); @@ -120,17 +127,6 @@ export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: stat: file.stat, contents: file.contents })); - - const shouldDuplicate = shouldDuplicateFile(file); - if (shouldDuplicate) { - const rootRelative = file.relative.replace(/^node_modules\//, ''); - this.queue(new VinylFile({ - base: '.', - path: path.join(destFilename + '.unpacked', rootRelative), - stat: file.stat, - contents: file.contents - })); - } } else { // The file goes inside of xx.asar out.push(file.contents); diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js index db0264ad927..ae05d175da8 100644 --- a/build/linux/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -46,7 +46,8 @@ async function getDependencies(packageType, buildDir, applicationName, arch) { throw new Error('Invalid RPM arch string ' + arch); } // Get the files for which we want to find dependencies. - const nativeModulesPath = path_1.default.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const canAsar = false; // TODO@esm ASAR disabled in ESM + const nativeModulesPath = path_1.default.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 24cbcdc5bd3..46c6d6c099a 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -47,7 +47,8 @@ export async function getDependencies(packageType: 'deb' | 'rpm', buildDir: stri } // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const canAsar = false; // TODO@esm ASAR disabled in ESM + const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/src/bootstrap-esm.ts b/src/bootstrap-esm.ts index 2031f34a2da..54681a2fa9c 100644 --- a/src/bootstrap-esm.ts +++ b/src/bootstrap-esm.ts @@ -5,74 +5,34 @@ import * as fs from 'node:fs'; import { register } from 'node:module'; -import { sep } from 'node:path'; import { product, pkg } from './bootstrap-meta.js'; import './bootstrap-node.js'; import * as performance from './vs/base/common/performance.js'; import { INLSConfiguration } from './vs/nls.js'; -// Prepare globals that are needed for running -globalThis._VSCODE_PRODUCT_JSON = { ...product }; -globalThis._VSCODE_PACKAGE_JSON = { ...pkg }; -globalThis._VSCODE_FILE_ROOT = import.meta.dirname; - -// Install a hook to module resolution to map dependencies into the asar archive -function enableASARSupport() { - if (!process.env['ELECTRON_RUN_AS_NODE'] && !process.versions['electron']) { - return; - } - - if (process.env['VSCODE_DEV']) { - return; - } - +// Install a hook to module resolution to map 'fs' to 'original-fs' +if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { const jsCode = ` - import { pathToFileURL, fileURLToPath } from 'node:url'; - function isRelativeSpecifier(specifier) { - if (specifier[0] === '.') { - if (specifier.length === 1 || specifier[1] === '/') { return true; } - if (specifier[1] === '.') { - if (specifier.length === 2 || specifier[2] === '/') { return true; } - } - } - return false; - } - function normalizeDriveLetter(path) { - if (process.platform === 'win32' - && path.length >= 2 - && (path.charCodeAt(0) >= 65 && path.charCodeAt(0) <= 90 || path.charCodeAt(0) >= 97 && path.charCodeAt(0) <= 122) - && path.charCodeAt(1) === 58) { - return path[0].toLowerCase() + path.slice(1); - } - return path; - } - export async function initialize({ resourcesPath, asarPath }) { - globalThis.__resourcesPath = normalizeDriveLetter(resourcesPath); - globalThis.__asarPath = asarPath; - } export async function resolve(specifier, context, nextResolve) { - if (!isRelativeSpecifier(specifier) && context.parentURL) { - const currentPath = fileURLToPath(context.parentURL); - const normalizedCurrentPath = normalizeDriveLetter(currentPath); - if (normalizedCurrentPath.startsWith(globalThis.__resourcesPath)) { - const asarPath = normalizedCurrentPath.replace(globalThis.__resourcesPath, globalThis.__asarPath); - context.parentURL = pathToFileURL(asarPath); - } + if (specifier === 'fs') { + return { + format: 'builtin', + shortCircuit: true, + url: 'node:original-fs' + }; } // Defer to the next hook in the chain, which would be the // Node.js default resolve if this is the last user-specified loader. return nextResolve(specifier, context); }`; - register(`data:text/javascript;base64,${Buffer.from(jsCode).toString('base64')}`, import.meta.url, { - data: { - resourcesPath: `${process.resourcesPath}${sep}app`, - asarPath: `${process.resourcesPath}${sep}app${sep}node_modules.asar`, - } - }); + register(`data:text/javascript;base64,${Buffer.from(jsCode).toString('base64')}`, import.meta.url); } -enableASARSupport(); +// Prepare globals that are needed for running +globalThis._VSCODE_PRODUCT_JSON = { ...product }; +globalThis._VSCODE_PACKAGE_JSON = { ...pkg }; +globalThis._VSCODE_FILE_ROOT = import.meta.dirname; //#region NLS helpers diff --git a/src/bootstrap-node.ts b/src/bootstrap-node.ts index 354347707fa..8cb580e738b 100644 --- a/src/bootstrap-node.ts +++ b/src/bootstrap-node.ts @@ -29,34 +29,6 @@ if (!process.env['VSCODE_HANDLES_SIGPIPE']) { }); } -/** - * Helper to enable ASAR support. - */ -function enableASARSupport(): void { - if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { - const Module = require('node:module'); - const NODE_MODULES_PATH = path.join(import.meta.dirname, '../node_modules'); - const NODE_MODULES_ASAR_PATH = path.join(import.meta.dirname, '../node_modules.asar'); - // @ts-ignore - const originalResolveLookupPaths = Module._resolveLookupPaths; - // @ts-ignore - Module._resolveLookupPaths = function (request, parent) { - const paths = originalResolveLookupPaths(request, parent); - if (Array.isArray(paths)) { - for (let i = 0, len = paths.length; i < len; i++) { - if (paths[i] === NODE_MODULES_PATH) { - paths.splice(i, 0, NODE_MODULES_ASAR_PATH); - break; - } - } - } - return paths; - }; - } -} - -enableASARSupport(); - // Setup current working directory in all our node & electron processes // - Windows: call `process.chdir()` to always set application folder as cwd // - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index 7de12318c11..374d4f19faf 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -9,6 +9,8 @@ import { IProductConfiguration } from './base/common/product.js'; import { URI } from './base/common/uri.js'; import { generateUuid } from './base/common/uuid.js'; +export const canASAR = false; // TODO@esm: ASAR disabled in ESM + declare const window: any; declare const document: any; declare const self: any; @@ -216,7 +218,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN // bit of a special case for: src/vs/workbench/services/languageDetection/browser/languageDetectionWebWorker.ts scriptSrc = nodeModulePath; } else { - const useASAR = (isBuilt && (platform.isElectron || (platform.isWebWorker && platform.hasElectronUserAgent))); + const useASAR = (canASAR && isBuilt && !platform.isWeb); const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); const resourcePath: AppResourcePath = `${actualNodeModulesPath}/${nodeModulePath}`; scriptSrc = FileAccess.asBrowserUri(resourcePath).toString(true); @@ -229,7 +231,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN export function resolveAmdNodeModulePath(nodeModuleName: string, pathInsideNodeModule: string): string { const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; const isBuilt = Boolean((product ?? globalThis.vscode?.context?.configuration()?.product)?.commit); - const useASAR = (isBuilt && (platform.isElectron || (platform.isWebWorker && platform.hasElectronUserAgent))); + const useASAR = (canASAR && isBuilt && !platform.isWeb); const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`; const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 2d7f85821e1..64ebe94abd9 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -257,8 +257,8 @@ export type AppResourcePath = ( export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions'; export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules'; -export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar/node_modules'; -export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked/node_modules'; +export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar'; +export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked'; export const VSCODE_AUTHORITY = 'vscode-app'; diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 4dcb6cae37e..10bb68dfd97 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -274,7 +274,6 @@ export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0); export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0)); export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0); export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); -export const hasElectronUserAgent = !!(userAgent && userAgent.indexOf('Electron') >= 0); export function isBigSurOrNewer(osVersion: string): boolean { return parseFloat(osVersion) >= 20; diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index ceccd357414..5a2459918f4 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -21,6 +21,7 @@ import { IEditorService } from '../../editor/common/editorService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { LRUCache } from '../../../../base/common/map.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { canASAR } from '../../../../amdX.js'; import { createWebWorker } from '../../../../base/browser/webWorkerFactory.js'; import { WorkerTextModelSyncClient } from '../../../../editor/common/services/textModelSync/textModelSync.impl.js'; import { ILanguageDetectionWorker, LanguageDetectionWorkerHost } from './languageDetectionWorker.protocol.js'; @@ -65,7 +66,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet ) { super(); - const useAsar = this._environmentService.isBuilt && !isWeb; + const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; this._languageDetectionWorkerClient = this._register(new LanguageDetectionWorkerClient( modelService, languageService, diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index 8af23291b6d..59502ab69cc 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { canASAR } from '../../../../../amdX.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } from '../../../../../base/common/network.js'; import { IObservable } from '../../../../../base/common/observable.js'; @@ -128,7 +129,7 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`; - const useAsar = this._environmentService.isBuilt && !isWeb; + const useAsar = canASAR && this._environmentService.isBuilt && !isWeb; const onigurumaLocation: AppResourcePath = useAsar ? onigurumaModuleLocationAsar : onigurumaModuleLocation; const onigurumaWASM: AppResourcePath = `${onigurumaLocation}/release/onig.wasm`; diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts index 36239e1abf8..304c7c18d0b 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { importAMDNodeModule, resolveAmdNodeModulePath } from '../../../../amdX.js'; +import { canASAR, importAMDNodeModule, resolveAmdNodeModulePath } from '../../../../amdX.js'; import * as domStylesheets from '../../../../base/browser/domStylesheets.js'; import { equals as equalArray } from '../../../../base/common/arrays.js'; import { Color } from '../../../../base/common/color.js'; @@ -390,7 +390,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate // We therefore use the non-streaming compiler :(. return await response.arrayBuffer(); } else { - const response = await fetch(this._environmentService.isBuilt + const response = await fetch(canASAR && this._environmentService.isBuilt ? FileAccess.asBrowserUri(`${nodeModulesAsarUnpackedPath}/vscode-oniguruma/release/onig.wasm`).toString(true) : FileAccess.asBrowserUri(`${nodeModulesPath}/vscode-oniguruma/release/onig.wasm`).toString(true)); return response; diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index 6a2cef15913..b6e82609b2d 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -7,7 +7,7 @@ import type { Parser, Language, Query } from '@vscode/tree-sitter-wasm'; import { IReader, ObservablePromise } from '../../../../base/common/observable.js'; import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import { importAMDNodeModule } from '../../../../amdX.js'; +import { canASAR, importAMDNodeModule } from '../../../../amdX.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { FileOperationResult, IFileContent, IFileService, toFileOperationResult } from '../../../../platform/files/common/files.js'; @@ -25,7 +25,7 @@ const MODULE_LOCATION_SUBPATH = `@vscode/tree-sitter-wasm/wasm`; const FILENAME_TREESITTER_WASM = `tree-sitter.wasm`; export function getModuleLocation(environmentService: IEnvironmentService): AppResourcePath { - return `${environmentService.isBuilt ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; + return `${(canASAR && environmentService.isBuilt) ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; } export class TreeSitterLibraryService extends Disposable implements ITreeSitterLibraryService { From 1c2e29a3c7a5148cf6dbc6e4ea862e11b773bb2a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Oct 2025 17:55:06 +0200 Subject: [PATCH 1535/4355] support `argument-hint` in agents and prompt files (#272918) * add argumentHint * IChatMode.argumentHint and tests * use hint as placeholder for agents --- .../browser/contrib/chatInputEditorContrib.ts | 8 +- .../contrib/chat/common/chatModes.ts | 12 +++ .../languageProviders/promptHovers.ts | 8 ++ .../languageProviders/promptValidator.ts | 19 ++++- .../common/promptSyntax/promptFileParser.ts | 5 ++ .../promptSyntax/service/promptsService.ts | 5 ++ .../service/promptsServiceImpl.ts | 13 ++- .../service/promptsService.test.ts | 84 +++++++++++++++++++ 8 files changed, 142 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index f6d9ee5bb77..81fa082bb8c 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -142,9 +142,11 @@ class InputEditorDecorations extends Disposable { if (!inputValue) { const mode = this.widget.input.currentModeObs.get(); - let description = mode.description.get(); + let placeholder; if (this.configurationService.getValue('chat.emptyChatState.enabled')) { - description = localize('chatPlaceholderHint', "Add context (#), extensions (@), commands (/)"); + placeholder = localize('chatPlaceholderHint', "Add context (#), extensions (@), commands (/)"); + } else { + placeholder = mode.description.get() ?? ''; } const decoration: IDecorationOptions[] = [ @@ -157,7 +159,7 @@ class InputEditorDecorations extends Disposable { }, renderOptions: { after: { - contentText: viewModel.inputPlaceholder || (description ?? ''), + contentText: viewModel.inputPlaceholder || placeholder, color: this.getPlaceholderColor() } } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index ba23fc77fec..e95d0236868 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -100,6 +100,7 @@ export class ChatModeService extends Disposable implements IChatModeService { description: cachedMode.description, tools: cachedMode.customTools, model: cachedMode.model, + argumentHint: cachedMode.argumentHint, agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, handOffs: cachedMode.handOffs, source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } @@ -203,6 +204,7 @@ export interface IChatModeData { readonly kind: ChatModeKind; readonly customTools?: readonly string[]; readonly model?: string; + readonly argumentHint?: string; readonly modeInstructions?: IChatModeInstructions; readonly body?: string; /* deprecated */ readonly handOffs?: readonly IHandOff[]; @@ -220,6 +222,7 @@ export interface IChatMode { readonly customTools?: IObservable; readonly handOffs?: IObservable; readonly model?: IObservable; + readonly argumentHint?: IObservable; readonly modeInstructions?: IObservable; readonly uri?: IObservable; readonly source?: IAgentSource; @@ -249,6 +252,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { (mode.customTools === undefined || Array.isArray(mode.customTools)) && (mode.modeInstructions === undefined || (typeof mode.modeInstructions === 'object' && mode.modeInstructions !== null)) && (mode.model === undefined || typeof mode.model === 'string') && + (mode.argumentHint === undefined || typeof mode.argumentHint === 'string') && (mode.handOffs === undefined || Array.isArray(mode.handOffs)) && (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)) && (mode.source === undefined || isChatModeSourceData(mode.source)); @@ -260,6 +264,7 @@ export class CustomChatMode implements IChatMode { private readonly _modeInstructions: ISettableObservable; private readonly _uriObservable: ISettableObservable; private readonly _modelObservable: ISettableObservable; + private readonly _argumentHintObservable: ISettableObservable; private readonly _handoffsObservable: ISettableObservable; private _source: IAgentSource; @@ -282,6 +287,10 @@ export class CustomChatMode implements IChatMode { return this._modelObservable; } + get argumentHint(): IObservable { + return this._argumentHintObservable; + } + get modeInstructions(): IObservable { return this._modeInstructions; } @@ -312,6 +321,7 @@ export class CustomChatMode implements IChatMode { this._descriptionObservable = observableValue('description', customChatMode.description); this._customToolsObservable = observableValue('customTools', customChatMode.tools); this._modelObservable = observableValue('model', customChatMode.model); + this._argumentHintObservable = observableValue('argumentHint', customChatMode.argumentHint); this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs); this._modeInstructions = observableValue('_modeInstructions', customChatMode.agentInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); @@ -327,6 +337,7 @@ export class CustomChatMode implements IChatMode { this._descriptionObservable.set(newData.description, tx); this._customToolsObservable.set(newData.tools, tx); this._modelObservable.set(newData.model, tx); + this._argumentHintObservable.set(newData.argumentHint, tx); this._handoffsObservable.set(newData.handOffs, tx); this._modeInstructions.set(newData.agentInstructions, tx); this._uriObservable.set(newData.uri, tx); @@ -342,6 +353,7 @@ export class CustomChatMode implements IChatMode { kind: this.kind, customTools: this.customTools.get(), model: this.model.get(), + argumentHint: this.argumentHint.get(), modeInstructions: this.modeInstructions.get(), uri: this.uri.get(), handOffs: this.handOffs.get(), diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 2ae788e8137..b291f3f1ebd 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -82,6 +82,10 @@ export class PromptHoverProvider implements HoverProvider { if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.agent.description', 'The description of the custom agent, what it does and when to use it.'), descriptionRange); } + const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; + if (argumentHintRange?.containsPosition(position)) { + return this.createHover('The argument-hint describes what inputs the custom agent expects or supports', argumentHintRange); + } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'Specify the model that runs this custom agent.')); @@ -95,6 +99,10 @@ export class PromptHoverProvider implements HoverProvider { if (descriptionRange?.containsPosition(position)) { return this.createHover(localize('promptHeader.prompt.description', 'The description of the reusable prompt, what it does and when to use it.'), descriptionRange); } + const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; + if (argumentHintRange?.containsPosition(position)) { + return this.createHover('The argument-hint describes what inputs the prompt expects or supports', argumentHintRange); + } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { return this.getModelHover(model, model.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.')); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 54a2b836a8a..75c8dcda3e8 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -136,6 +136,7 @@ export class PromptValidator { } } this.validateDescription(attributes, report); + this.validateArgumentHint(attributes, report); switch (promptType) { case PromptsType.prompt: { const agent = this.validateAgent(attributes, report); @@ -172,6 +173,20 @@ export class PromptValidator { } } + private validateArgumentHint(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { + const argumentHintAttribute = attributes.find(attr => attr.key === 'argument-hint'); + if (!argumentHintAttribute) { + return; + } + if (argumentHintAttribute.value.type !== 'string') { + report(toMarker(localize('promptValidator.argumentHintMustBeString', "The 'argument-hint' attribute must be a string."), argumentHintAttribute.range, MarkerSeverity.Error)); + return; + } + if (argumentHintAttribute.value.value.trim().length === 0) { + report(toMarker(localize('promptValidator.argumentHintShouldNotBeEmpty', "The 'argument-hint' attribute should not be empty."), argumentHintAttribute.value.range, MarkerSeverity.Error)); + return; + } + } private validateModel(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): void { const attribute = attributes.find(attr => attr.key === 'model'); @@ -379,9 +394,9 @@ export class PromptValidator { } const allAttributeNames = { - [PromptsType.prompt]: ['description', 'model', 'tools', 'mode', 'agent'], + [PromptsType.prompt]: ['description', 'model', 'tools', 'mode', 'agent', 'argument-hint'], [PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'], - [PromptsType.agent]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs'] + [PromptsType.agent]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs', 'argument-hint'] }; const recommendedAttributeNames = { [PromptsType.prompt]: allAttributeNames[PromptsType.prompt].filter(name => !isNonRecommendedAttribute(name)), diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index 3d61ea2d726..fc5b526135e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -70,6 +70,7 @@ export namespace PromptHeaderAttributes { export const tools = 'tools'; export const handOffs = 'handoffs'; export const advancedOptions = 'advancedOptions'; + export const argumentHint = 'argument-hint'; } export class PromptHeader { @@ -162,6 +163,10 @@ export class PromptHeader { return this.getStringAttribute(PromptHeaderAttributes.applyTo); } + public get argumentHint(): string | undefined { + return this.getStringAttribute(PromptHeaderAttributes.argumentHint); + } + public get tools(): string[] | undefined { const toolsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.tools); if (!toolsAttribute) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 2f914c2f957..eab0c6be20d 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -107,6 +107,11 @@ export interface ICustomAgent { */ readonly model?: string; + /** + * Argument hint metadata in the prompt header that describes what inputs the agent expects or supports. + */ + readonly argumentHint?: string; + /** * Contents of the custom agent file body and other agent instructions. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 5a8bd72a3e4..e0e78c1b07b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -28,7 +28,7 @@ import { IUserDataProfileService } from '../../../../../services/userDataProfile import { IVariableReference } from '../../chatModes.js'; import { PromptsConfig } from '../config/config.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; -import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType, getLanguageIdForPromptsType } from '../promptTypes.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; @@ -345,9 +345,8 @@ export class PromptsService extends Disposable implements IPromptsService { if (!ast.header) { return { uri, name, agentInstructions, source }; } - const { description, model, tools, handOffs } = ast.header; - return { uri, name, description, model, tools, handOffs, agentInstructions, source }; - + const { description, model, tools, handOffs, argumentHint } = ast.header; + return { uri, name, description, model, tools, handOffs, argumentHint, agentInstructions, source }; }) ); return customAgents; @@ -459,7 +458,7 @@ export class UpdateTracker extends Disposable { constructor( fileLocator: PromptFilesLocator, - promptTypes: PromptsType, + promptType: PromptsType, @IModelService modelService: IModelService, ) { super(); @@ -467,11 +466,11 @@ export class UpdateTracker extends Disposable { const delayer = this._register(new Delayer(UpdateTracker.CHAT_AGENT_UPDATE_DELAY_MS)); const trigger = () => delayer.trigger(() => this.onDidChangeContentEmitter.fire()); - const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(promptTypes)); + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(promptType)); this._register(filesUpdatedEventRegistration.event(() => trigger())); const onAdd = (model: ITextModel) => { - if (model.getLanguageId() === AGENT_LANGUAGE_ID) { + if (model.getLanguageId() === getLanguageIdForPromptsType(promptType)) { this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); } }; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 05f5a718a0b..fbc688244d9 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -785,6 +785,7 @@ suite('PromptsService', () => { metadata: undefined }, model: undefined, + argumentHint: undefined, tools: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } @@ -848,6 +849,7 @@ suite('PromptsService', () => { }, handOffs: undefined, model: undefined, + argumentHint: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local }, }, @@ -872,6 +874,88 @@ suite('PromptsService', () => { 'Must get custom agents.', ); }); + + test('header with argumentHint', async () => { + const rootFolderName = 'custom-agents-with-argument-hint'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + await (instaService.createInstance(MockFilesystem, + [{ + name: rootFolderName, + children: [ + { + name: '.github/agents', + children: [ + { + name: 'agent1.agent.md', + contents: [ + '---', + 'description: \'Code review agent.\'', + 'argument-hint: \'Provide file path or code snippet to review\'', + 'tools: [ code-analyzer, linter ]', + '---', + 'I will help review your code for best practices.', + ], + }, + { + name: 'agent2.agent.md', + contents: [ + '---', + 'description: \'Documentation generator.\'', + 'argument-hint: \'Specify function or class name to document\'', + '---', + 'I generate comprehensive documentation.', + ], + } + ], + + }, + ], + }])).mock(); + + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + const expected: ICustomAgent[] = [ + { + name: 'agent1', + description: 'Code review agent.', + argumentHint: 'Provide file path or code snippet to review', + tools: ['code-analyzer', 'linter'], + agentInstructions: { + content: 'I will help review your code for best practices.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + model: undefined, + uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), + source: { storage: PromptsStorage.local } + }, + { + name: 'agent2', + description: 'Documentation generator.', + argumentHint: 'Specify function or class name to document', + agentInstructions: { + content: 'I generate comprehensive documentation.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + model: undefined, + tools: undefined, + uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.agent.md'), + source: { storage: PromptsStorage.local } + }, + ]; + + assert.deepEqual( + result, + expected, + 'Must get custom agents with argumentHint.', + ); + }); }); suite('listPromptFiles - extensions', () => { From a1b4ecaa32eea1784ee34bc7d5eabc6b76ce2e03 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 23 Oct 2025 11:57:05 -0400 Subject: [PATCH 1536/4355] finalize terminal completion provider API (#271338) --- .../src/helpers/completionItem.ts | 3 +- .../src/terminalSuggestMain.ts | 12 +- .../src/test/terminalSuggestMain.test.ts | 2 +- .../common/extensionsApiProposals.ts | 3 - .../api/browser/mainThreadTerminalService.ts | 4 +- .../workbench/api/common/extHost.api.impl.ts | 1 - .../workbench/api/common/extHost.protocol.ts | 4 +- src/vs/workbench/api/common/extHostTypes.ts | 8 +- .../browser/lspCompletionProviderAddon.ts | 17 +- .../browser/terminalCompletionService.ts | 39 ++- .../suggest/browser/terminalSuggestAddon.ts | 13 +- .../browser/terminalCompletionModel.test.ts | 6 +- .../browser/terminalCompletionService.test.ts | 120 ++++---- .../suggest/browser/simpleCompletionItem.ts | 10 +- .../suggest/browser/simpleCompletionModel.ts | 6 +- .../browser/simpleSuggestWidgetDetails.ts | 4 +- src/vscode-dts/vscode.d.ts | 265 ++++++++++++++++++ ...e.proposed.terminalCompletionProvider.d.ts | 218 -------------- 18 files changed, 371 insertions(+), 364 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts diff --git a/extensions/terminal-suggest/src/helpers/completionItem.ts b/extensions/terminal-suggest/src/helpers/completionItem.ts index 7cb174223c1..b5dba9a2874 100644 --- a/extensions/terminal-suggest/src/helpers/completionItem.ts +++ b/extensions/terminal-suggest/src/helpers/completionItem.ts @@ -13,8 +13,7 @@ export function createCompletionItem(cursorPosition: number, currentCommandStrin label: commandResource.label, detail: detail ?? commandResource.detail ?? '', documentation, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length, + replacementRange: [cursorPosition - lastWord.length, cursorPosition], kind: kind ?? commandResource.kind ?? vscode.TerminalCompletionItemKind.Method }; } diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index e075bba947d..7ecc8affa05 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -495,13 +495,9 @@ export async function getCompletionItemsFromSpecs( } showFiles = true; showFolders = true; - } - // For arguments when no fig suggestions are found these are fallback suggestions - else if (!items.length && !showFiles && !showFolders && !hasCurrentArg) { - if (terminalContext.allowFallbackCompletions) { - showFiles = true; - showFolders = true; - } + } else if (!items.length && !showFiles && !showFolders && !hasCurrentArg) { + showFiles = true; + showFolders = true; } let cwd: vscode.Uri | undefined; @@ -509,7 +505,7 @@ export async function getCompletionItemsFromSpecs( cwd = await resolveCwdFromCurrentCommandString(currentCommandString, shellIntegrationCwd); } - return { items, showFiles: showFiles, showFolders: showFolders, fileExtensions, cwd }; + return { items, showFiles, showFolders, fileExtensions, cwd }; } function getEnvAsRecord(shellIntegrationEnv: ITerminalEnvironment): Record { diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index e60099c6089..cb4cbbddd5d 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -95,7 +95,7 @@ suite('Terminal Suggest', () => { const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorIndex, undefined); const showFiles = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; const showFolders = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; - const terminalContext = { commandLine, cursorIndex, allowFallbackCompletions: true }; + const terminalContext = { commandLine, cursorIndex }; const result = await getCompletionItemsFromSpecs( completionSpecs, terminalContext, diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index cae07b0fd1b..79e8cfa17c0 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -394,9 +394,6 @@ const _allApiProposals = { telemetry: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', }, - terminalCompletionProvider: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts', - }, terminalDataWriteEvent: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', }, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 533e27a5d6e..28ed1215289 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -277,8 +277,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape public $registerCompletionProvider(id: string, extensionIdentifier: string, ...triggerCharacters: string[]): void { this._completionProviders.set(id, this._terminalCompletionService.registerTerminalCompletionProvider(extensionIdentifier, id, { id, - provideCompletions: async (commandLine, cursorPosition, allowFallbackCompletions, token) => { - const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorIndex: cursorPosition, allowFallbackCompletions }, token); + provideCompletions: async (commandLine, cursorIndex, token) => { + const completions = await this._proxy.$provideTerminalCompletions(id, { commandLine, cursorIndex }, token); if (!completions) { return undefined; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 67a604b3816..d3482f995c8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -878,7 +878,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTerminalService.registerProfileProvider(extension, id, provider); }, registerTerminalCompletionProvider(provider: vscode.TerminalCompletionProvider, ...triggerCharacters: string[]): vscode.Disposable { - checkProposedApiEnabled(extension, 'terminalCompletionProvider'); return extHostTerminalService.registerTerminalCompletionProvider(extension, provider, ...triggerCharacters); }, registerTerminalQuickFixProvider(id: string, provider: vscode.TerminalQuickFixProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c764273e963..f640cf8e4a5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2498,7 +2498,6 @@ export interface ITerminalCommandDto { export interface ITerminalCompletionContextDto { commandLine: string; cursorIndex: number; - allowFallbackCompletions: boolean; } export interface ITerminalCompletionItemDto { @@ -2509,8 +2508,7 @@ export interface ITerminalCompletionItemDto { isFile?: boolean | undefined; isDirectory?: boolean | undefined; isKeyword?: boolean | undefined; - replacementIndex: number; - replacementLength: number; + replacementRange: readonly [number, number]; } export interface ITerminalCompletionProvider { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0d99b23840c..dda4138c9e3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -940,25 +940,23 @@ export enum TerminalCompletionItemKind { export class TerminalCompletionItem implements vscode.TerminalCompletionItem { label: string | CompletionItemLabel; + replacementRange: readonly [number, number]; icon?: ThemeIcon | undefined; detail?: string | undefined; documentation?: string | vscode.MarkdownString | undefined; isFile?: boolean | undefined; isDirectory?: boolean | undefined; isKeyword?: boolean | undefined; - replacementIndex: number; - replacementLength: number; - constructor(label: string | CompletionItemLabel, icon?: ThemeIcon, detail?: string, documentation?: string | vscode.MarkdownString, isFile?: boolean, isDirectory?: boolean, isKeyword?: boolean, replacementIndex?: number, replacementLength?: number) { + constructor(label: string | CompletionItemLabel, replacementRange: readonly [number, number], icon?: ThemeIcon, detail?: string, documentation?: string | vscode.MarkdownString, isFile?: boolean, isDirectory?: boolean, isKeyword?: boolean) { this.label = label; + this.replacementRange = replacementRange; this.icon = icon; this.detail = detail; this.documentation = documentation; this.isFile = isFile; this.isDirectory = isDirectory; this.isKeyword = isKeyword; - this.replacementIndex = replacementIndex ?? 0; - this.replacementLength = replacementLength ?? 0; } } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts index 8c02694f332..710fd39ddfc 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/lspCompletionProviderAddon.ts @@ -38,7 +38,7 @@ export class LspCompletionProviderAddon extends Disposable implements ITerminalA // console.log('activate'); } - async provideCompletions(value: string, cursorPosition: number, allowFallbackCompletions: false, token: CancellationToken): Promise | undefined> { + async provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise | undefined> { // Apply edit for non-executed current commandline --> Pretend we are typing in the real-document. this._lspTerminalModelContentProvider.trackPromptInputToVirtualFile(value); @@ -65,8 +65,7 @@ export class LspCompletionProviderAddon extends Disposable implements ITerminalA detail: item.detail, documentation: item.documentation, kind: convertedKind, - replacementIndex: completionItemTemp.replacementIndex, - replacementLength: completionItemTemp.replacementLength, + replacementRange: completionItemTemp.replacementRange, }; // Store unresolved item and provider for lazy resolution if needed @@ -95,8 +94,7 @@ export function createCompletionItemPython( return { label, detail: detail ?? '', - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length, + replacementRange: [cursorPosition - lastWord.length, cursorPosition], kind: kind ?? TerminalCompletionItemKind.Method }; } @@ -133,14 +131,9 @@ export interface TerminalCompletionItem { label: string | CompletionItemLabel; /** - * The index of the start of the range to replace. + * Selection range (inclusive start, exclusive end) to replace when this completion is applied. */ - replacementIndex: number; - - /** - * The length of the range to replace. - */ - replacementLength: number; + replacementRange: readonly [number, number] | undefined; /** * The completion's detail which appears on the right of the list. diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index dce6f24dbe7..270bd346951 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -55,7 +55,7 @@ export class TerminalCompletionList { export interface TerminalCompletionResourceOptions { showFiles?: boolean; - showFolders?: boolean; + showDirectories?: boolean; globPattern?: string | IRelativePattern; cwd: UriComponents; pathSeparator: string; @@ -65,7 +65,7 @@ export interface TerminalCompletionResourceOptions { export interface ITerminalCompletionProvider { id: string; shellTypes?: TerminalShellType[]; - provideCompletions(value: string, cursorPosition: number, allowFallbackCompletions: boolean, token: CancellationToken): Promise | undefined>; + provideCompletions(value: string, cursorPosition: number, token: CancellationToken): Promise | undefined>; triggerCharacters?: string[]; isBuiltin?: boolean; } @@ -190,7 +190,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo let completions; try { completions = await Promise.race([ - provider.provideCompletions(promptValue, cursorPosition, allowFallbackCompletions, token).then(result => { + provider.provideCompletions(promptValue, cursorPosition, token).then(result => { this._logService.trace(`TerminalCompletionService#_collectCompletions provider ${provider.id} finished`); return result; }), @@ -211,7 +211,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo this._logService.trace(`TerminalCompletionService#_collectCompletions amend ${completionItems.length} completion items`); if (shellType === GeneralShellType.PowerShell) { for (const completion of completionItems) { - completion.isFileOverride ??= completion.kind === TerminalCompletionItemKind.Method && completion.replacementIndex === 0; + const start = completion.replacementRange ? completion.replacementRange[0] : 0; + completion.isFileOverride ??= completion.kind === TerminalCompletionItemKind.Method && start === 0; } } if (provider.isBuiltin) { @@ -256,11 +257,11 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // Files requested implies folders requested since the file could be in any folder. We could // provide diagnostics when a folder is provided where a file is expected. - const showFolders = (resourceOptions.showFolders || resourceOptions.showFiles) ?? false; + const showDirectories = (resourceOptions.showDirectories || resourceOptions.showFiles) ?? false; const showFiles = resourceOptions.showFiles ?? false; const globPattern = resourceOptions.globPattern ?? undefined; - if (!showFolders && !showFiles) { + if (!showDirectories && !showFiles) { return; } @@ -350,8 +351,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind: TerminalCompletionItemKind.Folder, detail: lastWordFolderResource, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); return resourceCompletions; } @@ -374,7 +374,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // - (tilde) `~/|` -> `~/` // - (tilde) `~/src/|` -> `~/src/` this._logService.trace(`TerminalCompletionService#resolveResources cwd`); - if (showFolders) { + if (showDirectories) { let label: string; switch (type) { case 'tilde': { @@ -398,8 +398,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind: TerminalCompletionItemKind.Folder, detail: getFriendlyPath(this._labelService, lastWordFolderResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); } @@ -412,7 +411,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo await Promise.all(stat.children.map(child => (async () => { let kind: TerminalCompletionItemKind | undefined; let detail: string | undefined = undefined; - if (showFolders && child.isDirectory) { + if (showDirectories && child.isDirectory) { if (child.isSymbolicLink) { kind = TerminalCompletionItemKind.SymbolicLinkFolder; } else { @@ -468,8 +467,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind, detail: detail ?? getFriendlyPath(this._labelService, child.resource, resourceOptions.pathSeparator, kind, shellType), - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); })())); @@ -477,7 +475,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // // - (relative) `|` -> `/foo/vscode` (CDPATH has /foo which contains vscode folder) this._logService.trace(`TerminalCompletionService#resolveResources CDPATH`); - if (type === 'relative' && showFolders) { + if (type === 'relative' && showDirectories) { if (promptValue.startsWith('cd ')) { const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath); if (config === 'absolute' || config === 'relative') { @@ -507,8 +505,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind, detail, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); } } @@ -524,7 +521,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // - (relative) `|` -> `../` // - (relative) `./src/|` -> `./src/../` this._logService.trace(`TerminalCompletionService#resolveResources parent dir`); - if (type === 'relative' && showFolders) { + if (type === 'relative' && showDirectories) { let label = `..${resourceOptions.pathSeparator}`; if (lastWordFolder.length > 0) { label = addPathRelativePrefix(lastWordFolder + label, resourceOptions, lastWordFolderHasDotPrefix); @@ -535,8 +532,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind: TerminalCompletionItemKind.Folder, detail: getFriendlyPath(this._labelService, parentDir, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); } @@ -561,8 +557,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo provider, kind: TerminalCompletionItemKind.Folder, detail: typeof homeResource === 'string' ? homeResource : getFriendlyPath(this._labelService, homeResource, resourceOptions.pathSeparator, TerminalCompletionItemKind.Folder, shellType), - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length + replacementRange: [cursorPosition - lastWord.length, cursorPosition] }); } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 567624f5a2c..1981a275da2 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -156,8 +156,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest // Right arrow is used to accept the completion. This is a common keybinding in pwsh, zsh // and fish. inputData: '\x1b[C', - replacementIndex: 0, - replacementLength: 0, + replacementRange: [0, 0], provider: 'core:inlineSuggestion', detail: 'Inline suggestion', kind: TerminalCompletionItemKind.InlineSuggestion, @@ -650,10 +649,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest const replacementIndex = spaceIndex === -1 ? 0 : spaceIndex + 1; const suggestion = this._currentPromptInputState.value.substring(replacementIndex); this._inlineCompletion.label = suggestion; - this._inlineCompletion.replacementIndex = replacementIndex; - // Note that the cursor index delta must be taken into account here, otherwise filtering - // wont work correctly. - this._inlineCompletion.replacementLength = this._currentPromptInputState.cursorIndex - replacementIndex - this._cursorIndexDelta; + // Update replacementRange (inclusive start, exclusive end) for replacement + const end = this._currentPromptInputState.cursorIndex - this._cursorIndexDelta; + this._inlineCompletion.replacementRange = [replacementIndex, end]; // Reset the completion item as the object reference must remain the same but its // contents will differ across syncs. This is done so we don't need to reassign the // model and the slowdown/flickering that could potentially cause. @@ -928,7 +926,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest // The replacement text is any text after the replacement index for the completions, this // includes any text that was there before the completions were requested and any text added // since to refine the completion. - const replacementText = currentPromptInputState.value.substring(suggestion.item.completion.replacementIndex, currentPromptInputState.cursorIndex); + const startIndex = suggestion.item.completion.replacementRange?.[0] ?? currentPromptInputState.cursorIndex; + const replacementText = currentPromptInputState.value.substring(startIndex, currentPromptInputState.cursorIndex); // Right side of replacement text in the same word let rightSideReplacementText = ''; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts index 8630afb6430..c0db8abfb87 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts @@ -15,8 +15,7 @@ function createItem(options: Partial): TerminalCompletionIt kind: options.kind ?? TerminalCompletionItemKind.Method, label: options.label || 'defaultLabel', provider: options.provider || 'defaultProvider', - replacementIndex: options.replacementIndex || 0, - replacementLength: options.replacementLength || 1, + replacementRange: options.replacementRange || [0, 1], }); } @@ -254,8 +253,7 @@ suite('TerminalCompletionModel', function () { new TerminalCompletionItem({ label: 'ab', provider: 'core', - replacementIndex: 0, - replacementLength: 0, + replacementRange: [0, 0], kind }) ]; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 27a681094ae..92eff34116c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -33,8 +33,7 @@ interface IAssertionTerminalCompletion { } interface IAssertionCommandLineConfig { - replacementIndex: number; - replacementLength: number; + replacementRange: [number, number]; } /** @@ -47,14 +46,12 @@ function assertCompletions(actual: ITerminalCompletion[] | undefined, expected: label: e.label, detail: e.detail ?? '', kind: e.kind ?? TerminalCompletionItemKind.Folder, - replacementIndex: e.replacementIndex, - replacementLength: e.replacementLength, + replacementRange: e.replacementRange, })), expected.map(e => ({ label: e.label.replaceAll('/', sep), detail: e.detail ? e.detail.replaceAll('/', sep) : '', kind: e.kind ?? TerminalCompletionItemKind.Folder, - replacementIndex: expectedConfig.replacementIndex, - replacementLength: expectedConfig.replacementLength, + replacementRange: expectedConfig.replacementRange, })) ); } @@ -70,16 +67,14 @@ function assertPartialCompletionsExist(actual: ITerminalCompletion[] | undefined label: e.label.replaceAll('/', pathSeparator), detail: e.detail ? e.detail.replaceAll('/', pathSeparator) : '', kind: e.kind ?? TerminalCompletionItemKind.Folder, - replacementIndex: expectedConfig.replacementIndex, - replacementLength: expectedConfig.replacementLength, + replacementRange: expectedConfig.replacementRange, })); for (const expectedItem of expectedMapped) { assert.deepStrictEqual(actual.map(e => ({ label: e.label, detail: e.detail ?? '', kind: e.kind ?? TerminalCompletionItemKind.Folder, - replacementIndex: e.replacementIndex, - replacementLength: e.replacementLength, + replacementRange: e.replacementRange, })).find(e => e.detail === expectedItem.detail), expectedItem); } } @@ -170,7 +165,7 @@ suite('TerminalCompletionService', () => { test('| should return root-level completions', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; const result = await terminalCompletionService.resolveResources(resourceOptions, '', 1, provider, capabilities); @@ -180,13 +175,13 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: '../', detail: '/' }, standardTidleItem, - ], { replacementIndex: 1, replacementLength: 0 }); + ], { replacementRange: [1, 1] }); }); test('./| should return folder completions', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; const result = await terminalCompletionService.resolveResources(resourceOptions, './', 3, provider, capabilities); @@ -195,13 +190,13 @@ suite('TerminalCompletionService', () => { { label: './', detail: '/test/' }, { label: './folder1/', detail: '/test/folder1/' }, { label: './../', detail: '/' }, - ], { replacementIndex: 1, replacementLength: 2 }); + ], { replacementRange: [1, 3] }); }); test('cd ./| should return folder completions', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./', 5, provider, capabilities); @@ -210,12 +205,12 @@ suite('TerminalCompletionService', () => { { label: './', detail: '/test/' }, { label: './folder1/', detail: '/test/folder1/' }, { label: './../', detail: '/' }, - ], { replacementIndex: 3, replacementLength: 2 }); + ], { replacementRange: [3, 5] }); }); test('cd ./f| should return folder completions', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd ./f', 6, provider, capabilities); @@ -224,7 +219,7 @@ suite('TerminalCompletionService', () => { { label: './', detail: '/test/' }, { label: './folder1/', detail: '/test/folder1/' }, { label: './../', detail: '/' }, - ], { replacementIndex: 3, replacementLength: 3 }); + ], { replacementRange: [3, 6] }); }); }); @@ -242,7 +237,7 @@ suite('TerminalCompletionService', () => { test('./| should handle hidden files and folders', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -255,13 +250,13 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: './file1.txt', detail: '/test/file1.txt', kind: TerminalCompletionItemKind.File }, { label: './../', detail: '/' }, - ], { replacementIndex: 0, replacementLength: 2 }); + ], { replacementRange: [0, 2] }); }); test('./h| should handle hidden files and folders', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -274,7 +269,7 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: './file1.txt', detail: '/test/file1.txt', kind: TerminalCompletionItemKind.File }, { label: './../', detail: '/' }, - ], { replacementIndex: 0, replacementLength: 3 }); + ], { replacementRange: [0, 3] }); }); }); @@ -293,7 +288,7 @@ suite('TerminalCompletionService', () => { resourceOptions = { cwd: URI.parse('file:///test/folder1'),// Updated to reflect home directory showFiles: true, - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [ @@ -314,14 +309,14 @@ suite('TerminalCompletionService', () => { test('~| should return completion for ~', async () => { assertPartialCompletionsExist(await terminalCompletionService.resolveResources(resourceOptions, '~', 1, provider, capabilities), [ { label: '~', detail: '/home/' }, - ], { replacementIndex: 0, replacementLength: 1 }); + ], { replacementRange: [0, 1] }); }); test('~/| should return folder completions relative to $HOME', async () => { assertCompletions(await terminalCompletionService.resolveResources(resourceOptions, '~/', 2, provider, capabilities), [ { label: '~/', detail: '/home/' }, { label: '~/vscode/', detail: '/home/vscode/' }, - ], { replacementIndex: 0, replacementLength: 2 }); + ], { replacementRange: [0, 2] }); }); test('~/vscode/| should return folder completions relative to $HOME/vscode', async () => { @@ -329,7 +324,7 @@ suite('TerminalCompletionService', () => { { label: '~/vscode/', detail: '/home/vscode/' }, { label: '~/vscode/foo/', detail: '/home/vscode/foo/' }, { label: '~/vscode/bar.txt', detail: '/home/vscode/bar.txt', kind: TerminalCompletionItemKind.File }, - ], { replacementIndex: 0, replacementLength: 9 }); + ], { replacementRange: [0, 9] }); }); }); @@ -343,7 +338,7 @@ suite('TerminalCompletionService', () => { test('C:/Foo/| absolute paths on Windows', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///C:'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///C:/Foo')]; @@ -356,12 +351,12 @@ suite('TerminalCompletionService', () => { assertCompletions(result, [ { label: 'C:/Foo/', detail: 'C:/Foo/' }, { label: 'C:/Foo/Bar/', detail: 'C:/Foo/Bar/' }, - ], { replacementIndex: 0, replacementLength: 7 }); + ], { replacementRange: [0, 7] }); }); test('c:/foo/| case insensitivity on Windows', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///c:'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///c:/foo')]; @@ -374,13 +369,13 @@ suite('TerminalCompletionService', () => { // Note that the detail is normalizes drive letters to capital case intentionally { label: 'c:/foo/', detail: 'C:/foo/' }, { label: 'c:/foo/Bar/', detail: 'C:/foo/Bar/' }, - ], { replacementIndex: 0, replacementLength: 7 }); + ], { replacementRange: [0, 7] }); }); } else { test('/foo/| absolute paths NOT on Windows', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///foo')]; @@ -393,7 +388,7 @@ suite('TerminalCompletionService', () => { assertCompletions(result, [ { label: '/foo/', detail: '/foo/' }, { label: '/foo/Bar/', detail: '/foo/Bar/' }, - ], { replacementIndex: 0, replacementLength: 5 }); + ], { replacementRange: [0, 5] }); }); } @@ -401,7 +396,7 @@ suite('TerminalCompletionService', () => { test('.\\folder | Case insensitivity should resolve correctly on Windows', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///C:/test'), - showFolders: true, + showDirectories: true, pathSeparator: '\\' }; @@ -418,13 +413,13 @@ suite('TerminalCompletionService', () => { { label: '.\\FolderA\\', detail: 'C:\\test\\FolderA\\' }, { label: '.\\anotherFolder\\', detail: 'C:\\test\\anotherFolder\\' }, { label: '.\\..\\', detail: 'C:\\' }, - ], { replacementIndex: 0, replacementLength: 8 }); + ], { replacementRange: [0, 8] }); }); } else { test('./folder | Case sensitivity should resolve correctly on Mac/Unix', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator: '/' }; validResources = [URI.parse('file:///test')]; @@ -440,14 +435,14 @@ suite('TerminalCompletionService', () => { { label: './FolderA/', detail: '/test/FolderA/' }, { label: './foldera/', detail: '/test/foldera/' }, { label: './../', detail: '/' } - ], { replacementIndex: 0, replacementLength: 8 }); + ], { replacementRange: [0, 8] }); }); } test('| Empty input should resolve to current directory', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -463,13 +458,13 @@ suite('TerminalCompletionService', () => { { label: './folder2/', detail: '/test/folder2/' }, { label: '../', detail: '/' }, standardTidleItem, - ], { replacementIndex: 0, replacementLength: 0 }); + ], { replacementRange: [0, 0] }); }); test('./| should handle large directories with many results gracefully', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -489,7 +484,7 @@ suite('TerminalCompletionService', () => { test('./folder| should include current folder with trailing / is missing', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [URI.parse('file:///test')]; @@ -504,20 +499,18 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: './folder2/', detail: '/test/folder2/' }, { label: './../', detail: '/' } - ], { replacementIndex: 1, replacementLength: 9 }); + ], { replacementRange: [1, 10] }); }); - - test('folder/| should normalize current and parent folders', async () => { + test('test/| should normalize current and parent folders', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, pathSeparator }; validResources = [ - URI.parse('file:///'), URI.parse('file:///test'), URI.parse('file:///test/folder1'), - URI.parse('file:///test/folder2'), + URI.parse('file:///test/folder2') ]; childResources = [ { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, @@ -530,7 +523,7 @@ suite('TerminalCompletionService', () => { { label: './test/folder1/', detail: '/test/folder1/' }, { label: './test/folder2/', detail: '/test/folder2/' }, { label: './test/../', detail: '/' } - ], { replacementIndex: 0, replacementLength: 5 }); + ], { replacementRange: [0, 5] }); }); }); @@ -553,7 +546,7 @@ suite('TerminalCompletionService', () => { configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative'); const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -561,14 +554,14 @@ suite('TerminalCompletionService', () => { assertPartialCompletionsExist(result, [ { label: 'folder1', detail: 'CDPATH /cdpath_value/folder1/' }, - ], { replacementIndex: 3, replacementLength: 0 }); + ], { replacementRange: [3, 3] }); }); test('cd | should show paths from $CDPATH (absolute)', async () => { configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'absolute'); const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -576,7 +569,7 @@ suite('TerminalCompletionService', () => { assertPartialCompletionsExist(result, [ { label: '/cdpath_value/folder1/', detail: 'CDPATH' }, - ], { replacementIndex: 3, replacementLength: 0 }); + ], { replacementRange: [3, 3] }); }); test('cd | should support pulling from multiple paths in $CDPATH', async () => { @@ -604,7 +597,7 @@ suite('TerminalCompletionService', () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse(`${uriPathPrefix}test`), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -616,7 +609,7 @@ suite('TerminalCompletionService', () => { { label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath1_value/folder2/` }, { label: 'folder1', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder1/` }, { label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder2/` }, - ], { replacementIndex: 3, replacementLength: 0 }); + ], { replacementRange: [3, 3] }); }); }); @@ -639,7 +632,7 @@ suite('TerminalCompletionService', () => { test('resolveResources with c:/ style absolute path for Git Bash', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator: '/' }; @@ -657,12 +650,12 @@ suite('TerminalCompletionService', () => { { label: 'C:/Users/foo/', detail: 'C:\\Users\\foo\\' }, { label: 'C:/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' }, { label: 'C:/Users/foo/baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File }, - ], { replacementIndex: 0, replacementLength: 13 }, '/'); + ], { replacementRange: [0, 13] }, '/'); }); test('resolveResources with cwd as Windows path (relative)', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator: '/' }; @@ -681,13 +674,13 @@ suite('TerminalCompletionService', () => { { label: './bar/', detail: 'C:\\Users\\foo\\bar\\' }, { label: './baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File }, { label: './../', detail: 'C:\\Users\\' } - ], { replacementIndex: 0, replacementLength: 2 }, '/'); + ], { replacementRange: [0, 2] }, '/'); }); test('resolveResources with cwd as Windows path (absolute)', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.file('C:\\Users\\foo'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator: '/' }; @@ -705,7 +698,7 @@ suite('TerminalCompletionService', () => { { label: '/c/Users/foo/', detail: 'C:\\Users\\foo\\' }, { label: '/c/Users/foo/bar/', detail: 'C:\\Users\\foo\\bar\\' }, { label: '/c/Users/foo/baz.txt', detail: 'C:\\Users\\foo\\baz.txt', kind: TerminalCompletionItemKind.File }, - ], { replacementIndex: 0, replacementLength: 13 }, '/'); + ], { replacementRange: [0, 13] }, '/'); }); }); } @@ -716,7 +709,7 @@ suite('TerminalCompletionService', () => { cwd: URI.parse('file:///test'), pathSeparator, showFiles: true, - showFolders: true + showDirectories: true }; validResources = [URI.parse('file:///test')]; @@ -743,7 +736,7 @@ suite('TerminalCompletionService', () => { test('| should escape special characters in file/folder names for POSIX shells', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), - showFolders: true, + showDirectories: true, showFiles: true, pathSeparator }; @@ -764,7 +757,7 @@ suite('TerminalCompletionService', () => { { label: './\!special\$chars2\&', detail: '/test/\!special\$chars2\&', kind: TerminalCompletionItemKind.File }, { label: '../', detail: '/' }, standardTidleItem, - ], { replacementIndex: 0, replacementLength: 0 }); + ], { replacementRange: [0, 0] }); }); }); @@ -790,8 +783,7 @@ suite('TerminalCompletionService', () => { provideCompletions: async () => [{ label: `completion-from-${id}`, kind: TerminalCompletionItemKind.Method, - replacementIndex: 0, - replacementLength: 0, + replacementRange: [0, 0], provider: id }] }; diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts index 05ce1746f1b..a97a5338e63 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts @@ -45,14 +45,10 @@ export interface ISimpleCompletion { documentation?: string | IMarkdownString; /** - * The start of the replacement. + * Replacement range (inclusive start, exclusive end) of text in the line to be replaced when + * this completion is applied. */ - replacementIndex: number; - - /** - * The length of the replacement. - */ - replacementLength: number; + replacementRange: readonly [number, number] | undefined; } export class SimpleCompletionItem { diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts index 90d1342b597..0e13e0c2636 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts @@ -112,9 +112,9 @@ export class SimpleCompletionModel { // 'word' is that remainder of the current line that we // filter and score against. In theory each suggestion uses a // different word, but in practice not - that's why we cache - // TODO: Fix - const overwriteBefore = item.completion.replacementLength; // item.position.column - item.editStart.column; - const wordLen = overwriteBefore + characterCountDelta; // - (item.position.column - this._column); + + const overwriteBefore = item.completion.replacementRange ? (item.completion.replacementRange[1] - item.completion.replacementRange[0]) : 0; + const wordLen = overwriteBefore + characterCountDelta; if (word.length !== wordLen) { word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen); wordLow = word.toLowerCase(); diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts index 214dfb4c047..e45ec4fbd70 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidgetDetails.ts @@ -134,8 +134,8 @@ export class SimpleSuggestDetailsWidget { if (explainMode) { md += `score: ${item.score[0]}\n`; md += `prefix: ${item.word ?? '(no prefix)'}\n`; - md += `replacementIndex: ${item.completion.replacementIndex}\n`; - md += `replacementLength: ${item.completion.replacementLength}\n`; + const vs = item.completion.replacementRange; + md += `valueSelection: ${vs ? `[${vs[0]}, ${vs[1]}]` : 'undefined'}\\n`; md += `index: ${item.idx}\n`; if (this._getAdvancedExplainModeDetails) { const advancedDetails = this._getAdvancedExplainModeDetails(); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index ff8d57f61ec..a6c205d280c 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -8192,6 +8192,256 @@ declare module 'vscode' { constructor(options: TerminalOptions | ExtensionTerminalOptions); } + /** + * A provider that supplies terminal completion items. + * + * Implementations of this interface should return an array of {@link TerminalCompletionItem} or a + * {@link TerminalCompletionList} describing completions for the current command line. + * + * @example Simple provider returning a single completion + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return [{ label: '--help', replacementRange: [Math.max(0, context.cursorPosition - 2), context.cursorPosition] }]; + * } + * }); + */ + export interface TerminalCompletionProvider { + /** + * Provide completions for the given terminal and context. + * @param terminal The terminal for which completions are being provided. + * @param context Information about the terminal's current state. + * @param token A cancellation token. + * @return A list of completions. + */ + provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult>; + } + + + /** + * Represents a completion suggestion for a terminal command line. + * + * @example Completion item for `ls -|` + * const item = { + * label: '-A', + * replacementRange: [3, 4], // replace the single character at index 3 + * detail: 'List all entries except for . and .. (always set for the super-user)', + * kind: TerminalCompletionItemKind.Flag + * }; + * + * The fields on a completion item describe what text should be shown to the user + * and which portion of the command line should be replaced when the item is accepted. + */ + export class TerminalCompletionItem { + /** + * The label of the completion. + */ + label: string | CompletionItemLabel; + + /** + * The range in the command line to replace when the completion is accepted. Defined + * as a tuple where the first entry is the inclusive start index and the second entry is the + * exclusive end index. When `undefined` the completion will be inserted at the cursor + * position. When the two numbers are equal only the cursor position changes (insertion). + * + */ + replacementRange: readonly [number, number]; + + /** + * The completion's detail which appears on the right of the list. + */ + detail?: string; + + /** + * A human-readable string that represents a doc-comment. + */ + documentation?: string | MarkdownString; + + /** + * The completion's kind. Note that this will map to an icon. + */ + kind?: TerminalCompletionItemKind; + + /** + * Creates a new terminal completion item. + * + * @param label The label of the completion. + * @param replacementRange The inclusive start and exclusive end index of the text to replace. + * @param kind The completion's kind. + */ + constructor( + label: string | CompletionItemLabel, + replacementRange: readonly [number, number], + kind?: TerminalCompletionItemKind + ); + } + + /** + * The kind of an individual terminal completion item. + * + * The kind is used to render an appropriate icon in the suggest list and to convey the semantic + * meaning of the suggestion (file, folder, flag, commit, branch, etc.). + */ + export enum TerminalCompletionItemKind { + /** + * A file completion item. + * Example: `README.md` + */ + File = 0, + /** + * A folder completion item. + * Example: `src/` + */ + Folder = 1, + /** + * A method completion item. + * Example: `git commit` + */ + Method = 2, + /** + * An alias completion item. + * Example: `ll` as an alias for `ls -l` + */ + Alias = 3, + /** + * An argument completion item. + * Example: `origin` in `git push origin master` + */ + Argument = 4, + /** + * An option completion item. An option value is expected to follow. + * Example: `--locale` in `code --locale en` + */ + Option = 5, + /** + * The value of an option completion item. + * Example: `en-US` in `code --locale en-US` + */ + OptionValue = 6, + /** + * A flag completion item. + * Example: `--amend` in `git commit --amend` + */ + Flag = 7, + /** + * A symbolic link file completion item. + * Example: `link.txt` (symlink to a file) + */ + SymbolicLinkFile = 8, + /** + * A symbolic link folder completion item. + * Example: `node_modules/` (symlink to a folder) + */ + SymbolicLinkFolder = 9, + /** + * A source control commit completion item. + * Example: `abc1234` (commit hash) + */ + ScmCommit = 10, + /** + * A source control branch completion item. + * Example: `main` + */ + ScmBranch = 11, + /** + * A source control tag completion item. + * Example: `v1.0.0` + */ + ScmTag = 12, + /** + * A source control stash completion item. + * Example: `stash@{0}` + */ + ScmStash = 13, + /** + * A source control remote completion item. + * Example: `origin` + */ + ScmRemote = 14, + /** + * A pull request completion item. + * Example: `#42 Add new feature` + */ + PullRequest = 15, + /** + * A closed pull request completion item. + * Example: `#41 Fix bug (closed)` + */ + PullRequestDone = 16, + } + + /** + * Context information passed to {@link TerminalCompletionProvider.provideTerminalCompletions}. + * + * It contains the full command line, the current cursor position, and a flag indicating whether + * completions were explicitly invoked. + */ + export interface TerminalCompletionContext { + /** + * The complete terminal command line. + */ + readonly commandLine: string; + /** + * The index of the cursor in the command line. + */ + readonly cursorIndex: number; + } + + /** + * Represents a collection of {@link TerminalCompletionItem completion items} to be presented + * in the terminal. + * + * @example Create a completion list that requests files for the terminal cwd + * const list = new TerminalCompletionList([ + * { label: 'ls', replacementRange: [0, 0], kind: TerminalCompletionItemKind.Method } + * ], { showFiles: true, cwd: Uri.file('/home/user') }); + */ + export class TerminalCompletionList { + + /** + * Resources that should be shown in the completions list for the cwd of the terminal. + */ + resourceOptions?: TerminalCompletionResourceOptions; + + /** + * The completion items. + */ + items: T[]; + + /** + * Creates a new completion list. + * + * @param items The completion items. + * @param resourceOptions Indicates which resources should be shown as completions for the cwd of the terminal. + */ + constructor(items: T[], resourceOptions?: TerminalCompletionResourceOptions); + } + + + /** + * Configuration for requesting file and folder resources to be shown as completions. + * + * When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and + * folders that match {@link globPattern} from the provided {@link cwd}. + */ + export interface TerminalCompletionResourceOptions { + /** + * Show files as completion items. + */ + showFiles: boolean; + /** + * Show folders as completion items. + */ + showDirectories: boolean; + /** + * A glob pattern string that controls which files suggest should surface. Note that this will only apply if {@param showFiles} or {@param showDirectories} is set to true. + */ + globPattern?: string; + /** + * The cwd from which to request resources. + */ + cwd: Uri; + } + /** * A file decoration represents metadata that can be rendered with a file. */ @@ -11768,6 +12018,21 @@ declare module 'vscode' { * @returns A {@link Disposable disposable} that unregisters the provider. */ export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable; + /** + * Register a completion provider for terminals. + * @param provider The completion provider. + * @returns A {@link Disposable} that unregisters this provider when being disposed. + * + * @example Register a provider for an extension + * window.registerTerminalCompletionProvider('extension-provider-id', { + * provideTerminalCompletions(terminal, context) { + * return new TerminalCompletionList([ + * { label: '--version', replacementRange: [Math.max(0, context.cursorPosition - 2), 2] } + * ]); + * } + * }); + */ + export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; /** * Register a file decoration provider. * diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts deleted file mode 100644 index 4b3836f7a16..00000000000 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ /dev/null @@ -1,218 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/226562 - - /** - * A provider that supplies terminal completion items. - * - * Implementations of this interface should return an array of {@link TerminalCompletionItem} or a - * {@link TerminalCompletionList} describing completions for the current command line. - * - * @example Simple provider returning a single completion - * window.registerTerminalCompletionProvider('extension-provider-id', { - * provideTerminalCompletions(terminal, context) { - * return [{ label: '--help', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 }]; - * } - * }); - */ - export interface TerminalCompletionProvider { - /** - * Provide completions for the given terminal and context. - * @param terminal The terminal for which completions are being provided. - * @param context Information about the terminal's current state. - * @param token A cancellation token. - * @return A list of completions. - */ - provideTerminalCompletions(terminal: Terminal, context: TerminalCompletionContext, token: CancellationToken): ProviderResult>; - } - - - /** - * Represents a completion suggestion for a terminal command line. - * - * @example Completion item for `ls -|` - * const item = { - * label: '-A', - * replacementIndex: 3, - * replacementLength: 1, - * detail: 'List all entries except for . and .. (always set for the super-user)', - * kind: TerminalCompletionItemKind.Flag - * }; - * - * The fields on a completion item describe what text should be shown to the user - * and which portion of the command line should be replaced when the item is accepted. - */ - export interface TerminalCompletionItem { - /** - * The label of the completion. - */ - label: string | CompletionItemLabel; - - /** - * The index of the start of the range to replace. - */ - replacementIndex: number; - - /** - * The length of the range to replace. - */ - replacementLength: number; - - /** - * The completion's detail which appears on the right of the list. - */ - detail?: string; - - /** - * A human-readable string that represents a doc-comment. - */ - documentation?: string | MarkdownString; - - /** - * The completion's kind. Note that this will map to an icon. - */ - kind?: TerminalCompletionItemKind; - } - - - /** - * The kind of an individual terminal completion item. - * - * The kind is used to render an appropriate icon in the suggest list and to convey the semantic - * meaning of the suggestion (file, folder, flag, commit, branch, etc.). - * - */ - export enum TerminalCompletionItemKind { - File = 0, - Folder = 1, - Method = 2, - Alias = 3, - Argument = 4, - /** - * An option, for example in `code --locale`, `--locale` is the option - */ - Option = 5, - /** - * The value of an option, for example in `code --locale en-US`, `en-US` is the option value - */ - OptionValue = 6, - /** - * A flag, for example in `git commit --amend"`, `--amend` is the flag - */ - Flag = 7, - SymbolicLinkFile = 8, - SymbolicLinkFolder = 9, - ScmCommit = 10, - ScmBranch = 11, - ScmTag = 12, - ScmStash = 13, - ScmRemote = 14, - PullRequest = 15, - /** - * The pull request has been closed - */ - PullRequestDone = 16, - } - - - /** - * Context information passed to {@link TerminalCompletionProvider.provideTerminalCompletions}. - * - * It contains the full command line, the current cursor position, and a flag indicating whether - * fallback completions are allowed when the exact completion type cannot be determined. - */ - export interface TerminalCompletionContext { - /** - * The complete terminal command line. - */ - readonly commandLine: string; - /** - * The index of the cursor in the command line. - */ - readonly cursorIndex: number; - /** - * Whether completions should be provided when none are explicitly suggested. This will display - * fallback suggestions like files and folders. - */ - readonly allowFallbackCompletions: boolean; - } - - export namespace window { - /** - * Register a completion provider for terminals. - * @param provider The completion provider. - * @returns A {@link Disposable} that unregisters this provider when being disposed. - * - * @example Register a provider for an extension - * window.registerTerminalCompletionProvider('extension-provider-id', { - * provideTerminalCompletions(terminal, context) { - * return new TerminalCompletionList([ - * { label: '--version', replacementIndex: Math.max(0, context.cursorPosition - 2), replacementLength: 2 } - * ]); - * } - * }); - */ - export function registerTerminalCompletionProvider(provider: TerminalCompletionProvider, ...triggerCharacters: string[]): Disposable; - } - - /** - * Represents a collection of {@link TerminalCompletionItem completion items} to be presented - * in the terminal. - * - * @example Create a completion list that requests files for the terminal cwd - * const list = new TerminalCompletionList([ - * { label: 'ls', replacementIndex: 0, replacementLength: 0, kind: TerminalCompletionItemKind.Method } - * ], { showFiles: true, cwd: Uri.file('/home/user') }); - */ - export class TerminalCompletionList { - - /** - * Resources that should be shown in the completions list for the cwd of the terminal. - */ - resourceOptions?: TerminalCompletionResourceOptions; - - /** - * The completion items. - */ - items: T[]; - - /** - * Creates a new completion list. - * - * @param items The completion items. - * @param resourceOptions Indicates which resources should be shown as completions for the cwd of the terminal. - */ - constructor(items: T[], resourceOptions?: TerminalCompletionResourceOptions); - } - - - /** - * Configuration for requesting file and folder resources to be shown as completions. - * - * When a provider indicates that it wants file/folder resources, the terminal will surface completions for files and - * folders that match {@link globPattern} from the provided {@link cwd}. - */ - export interface TerminalCompletionResourceOptions { - /** - * Show files as completion items. - */ - showFiles?: boolean; - /** - * Show folders as completion items. - */ - showDirectories?: boolean; - /** - * A glob pattern string that controls which files suggest should surface. Note that this will only apply if {@param showFiles} or {@param showDirectories} is set to true. - */ - globPattern?: string; - /** - * The cwd from which to request resources. - */ - cwd: Uri; - } -} From 281f94f487b53bb2274215538e36fb4062d38c33 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:58:55 -0700 Subject: [PATCH 1537/4355] Include powershell and bash grammars in bundle Fixes #272852 --- build/.moduleignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/.moduleignore b/build/.moduleignore index 3e654cfe5c3..0459b46f743 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -60,6 +60,8 @@ fsevents/test/** !@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm !@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm !@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +!@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +!@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm native-keymap/binding.gyp native-keymap/build/** From d6dc73c37dbe3d4dd27620e78ac3e90cc3288418 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Oct 2025 18:18:35 +0200 Subject: [PATCH 1538/4355] do not change tools in promot files from contribution points (#272940) --- .../chat/browser/actions/chatToolActions.ts | 12 ++++++++---- .../contrib/chat/browser/chatSelectedTools.ts | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index 6f47c82e88c..fd559a9a844 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -159,11 +159,15 @@ class ConfigureToolsAction extends Action2 { switch (entriesScope) { case ToolsScope.Session: placeholder = localize('chat.tools.placeholder.session', "Select tools for this chat session"); - description = localize('chat.tools.description.session', "The selected tools were configured by a prompt command and only apply to this chat session."); + description = localize('chat.tools.description.session', "The selected tools were configured only for this chat session."); break; - case ToolsScope.Mode: - placeholder = localize('chat.tools.placeholder.mode', "Select tools for this custom agent"); - description = localize('chat.tools.description.mode', "The selected tools are configured by the '{0}' custom agent. Changes to the tools will be applied to the custom agent file as well.", widget.input.currentModeObs.get().label); + case ToolsScope.Agent: + placeholder = localize('chat.tools.placeholder.agent', "Select tools for this custom agent"); + description = localize('chat.tools.description.agent', "The selected tools are configured by the '{0}' custom agent. Changes to the tools will be applied to the custom agent file as well.", widget.input.currentModeObs.get().label); + break; + case ToolsScope.Agent_ReadOnly: + placeholder = localize('chat.tools.placeholder.readOnlyAgent', "Select tools for this custom agent"); + description = localize('chat.tools.description.readOnlyAgent', "The selected tools are configured by the '{0}' custom agent. Changes to the tools will only be used for this session and will not change the '{0}' custom agent.", widget.input.currentModeObs.get().label); break; case ToolsScope.Global: placeholder = localize('chat.tools.placeholder.global', "Select tools that are available to chat."); diff --git a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts index 09fc4a1a0d9..2a124651edb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts @@ -15,6 +15,7 @@ import { UserSelectedTools } from '../common/chatAgents.js'; import { IChatMode } from '../common/chatModes.js'; import { ChatModeKind } from '../common/constants.js'; import { ILanguageModelToolsService, IToolAndToolSetEnablementMap, IToolData, ToolSet } from '../common/languageModelToolsService.js'; +import { PromptsStorage } from '../common/promptSyntax/service/promptsService.js'; import { PromptFileRewriter } from './promptSyntax/promptFileRewriter.js'; @@ -88,7 +89,8 @@ namespace ToolEnablementStates { export enum ToolsScope { Global, Session, - Mode + Agent, + Agent_ReadOnly, } export class ChatSelectedTools extends Disposable { @@ -169,7 +171,7 @@ export class ChatSelectedTools extends Disposable { return ToolsScope.Session; } if (mode.kind === ChatModeKind.Agent && mode.customTools?.get() && mode.uri) { - return ToolsScope.Mode; + return mode.source?.storage !== PromptsStorage.extension ? ToolsScope.Agent : ToolsScope.Agent_ReadOnly; } return ToolsScope.Global; } @@ -190,9 +192,15 @@ export class ChatSelectedTools extends Disposable { return; } if (mode.kind === ChatModeKind.Agent && mode.customTools?.get() && mode.uri) { - // apply directly to mode file. - this.updateCustomModeTools(mode.uri.get(), enablementMap); - return; + if (mode.source?.storage !== PromptsStorage.extension) { + // apply directly to mode file. + this.updateCustomModeTools(mode.uri.get(), enablementMap); + return; + } else { + // can not write to extensions, store + this._sessionStates.set(mode.id, ToolEnablementStates.fromMap(enablementMap)); + return; + } } this._globalState.set(ToolEnablementStates.fromMap(enablementMap), undefined); } From 578fc30603d5bfb73815ea11bdfbbbdb8db4050d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Oct 2025 18:25:05 +0200 Subject: [PATCH 1539/4355] Store global tools selection per profile (not window) (#272860) --- src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts index 2a124651edb..a3f11cdc6e9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSelectedTools.ts @@ -116,7 +116,7 @@ export class ChatSelectedTools extends Disposable { toStorage: ToolEnablementStates.toStorage }); - this._globalState = this._store.add(globalStateMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE, _storageService)); + this._globalState = this._store.add(globalStateMemento(StorageScope.PROFILE, StorageTarget.MACHINE, _storageService)); this._allTools = observableFromEvent(_toolsService.onDidChangeTools, () => Array.from(_toolsService.getTools())); } From b409152750e821c13dbb73423b41d7d9303de1a5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 23 Oct 2025 12:57:54 -0400 Subject: [PATCH 1540/4355] Remove default model experiment but keep default mode experiment (#272949) --- .../contrib/chat/browser/chatInputPart.ts | 68 ++++++------------- 1 file changed, 19 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 98d3112e8f1..b8725f54746 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -828,7 +828,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - initForNewChatModel(state: IChatViewState, modelIsEmpty: boolean): void { + initForNewChatModel(state: IChatViewState, chatSessionIsEmpty: boolean): void { this.history = this.loadHistory(); this.history.add({ text: state.inputValue ?? this.history.current().text, @@ -858,64 +858,34 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } // TODO@roblourens This is for an experiment which will be obsolete in a month or two and can then be removed. - if (modelIsEmpty) { + if (chatSessionIsEmpty) { const storageKey = this.getDefaultModeExperimentStorageKey(); const hasSetDefaultMode = this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false); if (!hasSetDefaultMode) { const isAnonymous = this.entitlementService.anonymous; - Promise.all([ - this.experimentService.getTreatment('chat.defaultMode'), - this.experimentService.getTreatment('chat.defaultLanguageModel'), - ]).then(([defaultModeTreatment, defaultLanguageModelTreatment]) => { - if (isAnonymous) { - // be deterministic for anonymous users - // to support agentic flows with default - // model. - defaultModeTreatment = ChatModeKind.Agent; - defaultLanguageModelTreatment = undefined; - } - - if (typeof defaultModeTreatment === 'string') { - this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); - const defaultMode = validateChatMode(defaultModeTreatment); - if (defaultMode) { - this.logService.trace(`Applying default mode from experiment: ${defaultMode}`); - this.setChatMode(defaultMode, false); - this.checkModelSupported(); + this.experimentService.getTreatment('chat.defaultMode') + .then((defaultModeTreatment => { + if (isAnonymous) { + // be deterministic for anonymous users + // to support agentic flows with default + // model. + defaultModeTreatment = ChatModeKind.Agent; } - } - if (typeof defaultLanguageModelTreatment === 'string') { - this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); - this.logService.trace(`Applying default language model from experiment: ${defaultLanguageModelTreatment}`); - this.setExpModelOrWait(defaultLanguageModelTreatment); - } - }); + if (typeof defaultModeTreatment === 'string') { + this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + const defaultMode = validateChatMode(defaultModeTreatment); + if (defaultMode) { + this.logService.trace(`Applying default mode from experiment: ${defaultMode}`); + this.setChatMode(defaultMode, false); + this.checkModelSupported(); + } + } + })); } } } - private setExpModelOrWait(modelId: string) { - const model = this.languageModelsService.lookupLanguageModel(modelId); - if (model) { - this.setCurrentLanguageModel({ metadata: model, identifier: modelId }); - this.checkModelSupported(); - this._waitForPersistedLanguageModel.clear(); - } else { - this._waitForPersistedLanguageModel.value = this.languageModelsService.onDidChangeLanguageModels(() => { - const model = this.languageModelsService.lookupLanguageModel(modelId); - if (model) { - this._waitForPersistedLanguageModel.clear(); - - if (model.isUserSelectable) { - this.setCurrentLanguageModel({ metadata: model, identifier: modelId }); - this.checkModelSupported(); - } - } - }); - } - } - private getDefaultModeExperimentStorageKey(): string { const tag = this.options.widgetViewKindTag; return `chat.${tag}.hasSetDefaultModeByExperiment`; From 8a1ffa71e4b38504cbfcc5ebe97108e92292d8b5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 23 Oct 2025 10:00:32 -0700 Subject: [PATCH 1541/4355] request approval for cross-origin redirects --- .../common/webContentExtractor.ts | 16 ++- .../webContentExtractorService.ts | 77 ++++++++--- .../chatProgressTypes/chatToolInvocation.ts | 2 +- .../chat/common/languageModelToolsService.ts | 2 + .../electron-browser/tools/fetchPageTool.ts | 48 +++++-- .../electron-browser/fetchPageTool.test.ts | 120 +++++++++++++++++- 6 files changed, 230 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/webContentExtractor/common/webContentExtractor.ts b/src/vs/platform/webContentExtractor/common/webContentExtractor.ts index 322ecd9ecdb..6c9230d227b 100644 --- a/src/vs/platform/webContentExtractor/common/webContentExtractor.ts +++ b/src/vs/platform/webContentExtractor/common/webContentExtractor.ts @@ -11,10 +11,22 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; export const IWebContentExtractorService = createDecorator('IWebContentExtractorService'); export const ISharedWebContentExtractorService = createDecorator('ISharedWebContentExtractorService'); +export interface IWebContentExtractorOptions { + /** + * Whether to allow cross-authority redirects on the web content. + * 'false' by default. + */ + followRedirects?: boolean; +} + +export type WebContentExtractResult = + | { status: 'ok'; result: string } + | { status: 'error'; error: string } + | { status: 'redirect'; toURI: URI }; export interface IWebContentExtractorService { _serviceBrand: undefined; - extract(uri: URI[]): Promise; + extract(uri: URI[], options?: IWebContentExtractorOptions): Promise; } /* @@ -33,7 +45,7 @@ export interface ISharedWebContentExtractorService { export class NullWebContentExtractorService implements IWebContentExtractorService { _serviceBrand: undefined; - extract(_uri: URI[]): Promise { + extract(_uri: URI[]): Promise { throw new Error('Not implemented'); } } diff --git a/src/vs/platform/webContentExtractor/electron-main/webContentExtractorService.ts b/src/vs/platform/webContentExtractor/electron-main/webContentExtractorService.ts index 63147f55108..2783aaed88d 100644 --- a/src/vs/platform/webContentExtractor/electron-main/webContentExtractorService.ts +++ b/src/vs/platform/webContentExtractor/electron-main/webContentExtractorService.ts @@ -3,17 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow } from 'electron'; -import { IWebContentExtractorService } from '../common/webContentExtractor.js'; +import { BrowserWindow, Event as ElectronEvent, WebContentsWillNavigateEventParams, WebContentsWillRedirectEventParams } from 'electron'; +import { IWebContentExtractorOptions, IWebContentExtractorService, WebContentExtractResult } from '../common/webContentExtractor.js'; import { URI } from '../../../base/common/uri.js'; import { AXNode, convertAXTreeToMarkdown } from './cdpAccessibilityDomain.js'; import { Limiter } from '../../../base/common/async.js'; import { ResourceMap } from '../../../base/common/map.js'; import { ILogService } from '../../log/common/log.js'; +import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; +import { CancellationError } from '../../../base/common/errors.js'; +import { generateUuid } from '../../../base/common/uuid.js'; interface CacheEntry { - content: string; + result: string; timestamp: number; + finalURI: URI; } export class NativeWebContentExtractorService implements IWebContentExtractorService { @@ -21,7 +25,7 @@ export class NativeWebContentExtractorService implements IWebContentExtractorSer // Only allow 3 windows to be opened at a time // to avoid overwhelming the system with too many processes. - private _limiter = new Limiter(3); + private _limiter = new Limiter(3); private _webContentsCache = new ResourceMap(); private readonly _cacheDuration = 24 * 60 * 60 * 1000; // 1 day in milliseconds @@ -31,53 +35,90 @@ export class NativeWebContentExtractorService implements IWebContentExtractorSer return Date.now() - entry.timestamp > this._cacheDuration; } - extract(uris: URI[]): Promise { + extract(uris: URI[], options?: IWebContentExtractorOptions): Promise { if (uris.length === 0) { this._logger.info('[NativeWebContentExtractorService] No URIs provided for extraction'); return Promise.resolve([]); } this._logger.info(`[NativeWebContentExtractorService] Extracting content from ${uris.length} URIs`); - return Promise.all(uris.map((uri) => this._limiter.queue(() => this.doExtract(uri)))); + return Promise.all(uris.map((uri) => this._limiter.queue(() => this.doExtract(uri, options)))); } - async doExtract(uri: URI): Promise { + async doExtract(uri: URI, options: IWebContentExtractorOptions | undefined): Promise { const cached = this._webContentsCache.get(uri); if (cached) { this._logger.info(`[NativeWebContentExtractorService] Found cached content for ${uri}`); if (this.isExpired(cached)) { this._logger.info(`[NativeWebContentExtractorService] Cache expired for ${uri}, removing entry...`); this._webContentsCache.delete(uri); + } else if (!options?.followRedirects && cached.finalURI.authority !== uri.authority) { + return { status: 'redirect', toURI: cached.finalURI }; } else { - return cached.content; + return { status: 'ok', result: cached.result }; } } this._logger.info(`[NativeWebContentExtractorService] Extracting content from ${uri}...`); + const store = new DisposableStore(); const win = new BrowserWindow({ width: 800, height: 600, show: false, webPreferences: { + partition: generateUuid(), // do not share any state with the default renderer session javascript: true, offscreen: true, sandbox: true, webgl: false } }); + + store.add(toDisposable(() => win.destroy())); + try { - await win.loadURL(uri.toString(true)); - win.webContents.debugger.attach('1.1'); - const result: { nodes: AXNode[] } = await win.webContents.debugger.sendCommand('Accessibility.getFullAXTree'); - const str = convertAXTreeToMarkdown(uri, result.nodes); - this._logger.info(`[NativeWebContentExtractorService] Content extracted from ${uri}`); - this._logger.trace(`[NativeWebContentExtractorService] Extracted content: ${str}`); - this._webContentsCache.set(uri, { content: str, timestamp: Date.now() }); - return str; + const result = options?.followRedirects + ? await this.extractAX(win, uri) + : await Promise.race([this.interceptRedirects(win, uri, store), this.extractAX(win, uri)]); + + if (result.status === 'ok') { + this._webContentsCache.set(uri, { result: result.result, timestamp: Date.now(), finalURI: URI.parse(win.webContents.getURL()) }); + } + + return result; } catch (err) { this._logger.error(`[NativeWebContentExtractorService] Error extracting content from ${uri}: ${err}`); + return { status: 'error', error: String(err) }; } finally { - win.destroy(); + store.dispose(); } - return ''; + } + + private async extractAX(win: BrowserWindow, uri: URI): Promise { + await win.loadURL(uri.toString(true)); + win.webContents.debugger.attach('1.1'); + const result: { nodes: AXNode[] } = await win.webContents.debugger.sendCommand('Accessibility.getFullAXTree'); + const str = convertAXTreeToMarkdown(uri, result.nodes); + this._logger.info(`[NativeWebContentExtractorService] Content extracted from ${uri}`); + this._logger.trace(`[NativeWebContentExtractorService] Extracted content: ${str}`); + return { status: 'ok', result: str }; + } + + private interceptRedirects(win: BrowserWindow, uri: URI, store: DisposableStore) { + return new Promise((resolve, reject) => { + const onNavigation = (e: ElectronEvent) => { + const newURI = URI.parse(e.url); + if (newURI.authority !== uri.authority) { + e.preventDefault(); + resolve({ status: 'redirect', toURI: newURI }); + } + }; + + win.webContents.on('will-navigate', onNavigation); + win.webContents.on('will-redirect', onNavigation); + + store.add(toDisposable(() => { + reject(new CancellationError()); + })); + }); } } diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 07fce57b24a..31cdbe2c8e3 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -83,7 +83,7 @@ export class ChatToolInvocation implements IChatToolInvocation { this.pastTenseMessage = this._progress.get().message; } - if (this.confirmationMessages?.confirmResults && !result?.toolResultError && !final) { + if (this.confirmationMessages?.confirmResults && !result?.toolResultError && result?.confirmResults !== false && !final) { this._state.set({ type: IChatToolInvocation.StateKind.WaitingForPostApproval, confirmed: IChatToolInvocation.executionConfirmedOrDenied(this) || { type: ToolConfirmKind.ConfirmationNotNeeded }, diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 30ad9c066f8..bf8b027c295 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -191,6 +191,8 @@ export interface IToolResult { toolResultDetails?: Array | IToolResultInputOutputDetails | IToolResultOutputDetails; toolResultError?: string; toolMetadata?: unknown; + /** Whether to ask the user to confirm these tool results. Overrides {@link IToolConfirmationMessages.confirmResults}. */ + confirmResults?: boolean; } export function toolResultHasBuffers(result: IToolResult): boolean { diff --git a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts index 6f1300b5240..5c106bce3dd 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assertNever } from '../../../../../base/common/assert.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { extname } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; -import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; +import { IWebContentExtractorService, WebContentExtractResult } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { detectEncodingFromBuffer } from '../../../../services/textfile/common/encoding.js'; import { ITrustedDomainService } from '../../../url/browser/trustedDomainService.js'; import { ChatImageMimeType } from '../../common/languageModels.js'; @@ -37,6 +38,8 @@ export const FetchWebPageToolData: IToolData = { } }; +type ResultType = string | { type: 'tooldata'; value: IToolResultDataPart } | { type: 'extracted'; value: WebContentExtractResult } | undefined; + export class FetchWebPageTool implements IToolImpl { constructor( @@ -60,7 +63,7 @@ export class FetchWebPageTool implements IToolImpl { const webContents = webUris.size > 0 ? await this._readerModeService.extract([...webUris.values()]) : []; // Get contents from file URIs - const fileContents: (string | IToolResultDataPart | undefined)[] = []; + const fileContents: (string | { type: 'tooldata'; value: IToolResultDataPart } | undefined)[] = []; const successfulFileUris: URI[] = []; for (const uri of fileUris.values()) { try { @@ -71,10 +74,13 @@ export class FetchWebPageTool implements IToolImpl { if (imageMimeType) { // For supported image files, return as IToolResultDataPart fileContents.push({ - kind: 'data', + type: 'tooldata', value: { - mimeType: imageMimeType, - data: fileContent.value + kind: 'data', + value: { + mimeType: imageMimeType, + data: fileContent.value + } } }); } else { @@ -99,14 +105,14 @@ export class FetchWebPageTool implements IToolImpl { } // Build results array in original order - const results: (string | IToolResultDataPart | undefined)[] = []; + const results: ResultType[] = []; let webIndex = 0; let fileIndex = 0; for (const url of urls) { if (invalidUris.has(url)) { results.push(undefined); } else if (webUris.has(url)) { - results.push(webContents[webIndex]); + results.push({ type: 'extracted', value: webContents[webIndex] }); webIndex++; } else if (fileUris.has(url)) { results.push(fileContents[fileIndex]); @@ -116,12 +122,20 @@ export class FetchWebPageTool implements IToolImpl { } } + // Skip confirming any results if every web content we got was an error or redirect + let confirmResults: undefined | boolean; + if (webContents.every(e => e.status === 'error' || e.status === 'redirect')) { + confirmResults = false; + } + + // Only include URIs that actually had content successfully fetched const actuallyValidUris = [...webUris.values(), ...successfulFileUris]; return { content: this._getPromptPartsForResults(results), - toolResultDetails: actuallyValidUris + toolResultDetails: actuallyValidUris, + confirmResults, }; } @@ -241,7 +255,7 @@ export class FetchWebPageTool implements IToolImpl { return { webUris, fileUris, invalidUris }; } - private _getPromptPartsForResults(results: (string | IToolResultDataPart | undefined)[]): (IToolResultTextPart | IToolResultDataPart)[] { + private _getPromptPartsForResults(results: ResultType[]): (IToolResultTextPart | IToolResultDataPart)[] { return results.map(value => { if (!value) { return { @@ -253,9 +267,21 @@ export class FetchWebPageTool implements IToolImpl { kind: 'text', value: value }; + } else if (value.type === 'tooldata') { + return value.value; + } else if (value.type === 'extracted') { + switch (value.value.status) { + case 'ok': + return { kind: 'text', value: value.value.result }; + case 'redirect': + return { kind: 'text', value: `The webpage has redirected to "${value.value.toURI.toString(true)}". Use the ${InternalFetchWebPageToolId} again to get its contents.` }; + case 'error': + return { kind: 'text', value: `An error occurred retrieving the fetch result: ${value.value.error}` }; + default: + assertNever(value.value); + } } else { - // This is an IToolResultDataPart - return value; + throw new Error('unreachable'); } }); } diff --git a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts index ed6a33e5b0c..43cb02cf26a 100644 --- a/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/electron-browser/fetchPageTool.test.ts @@ -10,23 +10,24 @@ import { URI } from '../../../../../base/common/uri.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { IFileContent, IReadFileOptions } from '../../../../../platform/files/common/files.js'; -import { IWebContentExtractorService } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; +import { IWebContentExtractorService, WebContentExtractResult } from '../../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { FetchWebPageTool } from '../../electron-browser/tools/fetchPageTool.js'; import { TestFileService } from '../../../../test/common/workbenchTestServices.js'; import { MockTrustedDomainService } from '../../../url/test/browser/mockTrustedDomainService.js'; +import { InternalFetchWebPageToolId } from '../../common/tools/tools.js'; class TestWebContentExtractorService implements IWebContentExtractorService { _serviceBrand: undefined; constructor(private uriToContentMap: ResourceMap) { } - async extract(uris: URI[]): Promise { + async extract(uris: URI[]): Promise { return uris.map(uri => { const content = this.uriToContentMap.get(uri); if (content === undefined) { throw new Error(`No content configured for URI: ${uri.toString()}`); } - return content; + return { status: 'ok', result: content }; }); } } @@ -748,5 +749,118 @@ suite('FetchWebPageTool', () => { assert.strictEqual(result.content[0].kind, 'data', 'Image should be data part'); assert.strictEqual(result.content[1].kind, 'text', 'Text file should be text part'); }); + + test('confirmResults is false when all web contents are errors or redirects', async () => { + const webContentMap = new ResourceMap(); + + const tool = new FetchWebPageTool( + new class extends TestWebContentExtractorService { + constructor() { + super(webContentMap); + } + override async extract(uris: URI[]): Promise { + return uris.map(() => ({ status: 'error', error: 'Failed to fetch' })); + } + }(), + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService(), + ); + + const result = await tool.invoke( + { callId: 'test-call', toolId: 'fetch-page', parameters: { urls: ['https://example.com'] }, context: undefined }, + () => Promise.resolve(0), + { report: () => { } }, + CancellationToken.None + ); + + assert.strictEqual(result.confirmResults, false, 'confirmResults should be false when all results are errors'); + }); + + test('confirmResults is false when all web contents are redirects', async () => { + const webContentMap = new ResourceMap(); + + const tool = new FetchWebPageTool( + new class extends TestWebContentExtractorService { + constructor() { + super(webContentMap); + } + override async extract(uris: URI[]): Promise { + return uris.map(() => ({ status: 'redirect', toURI: URI.parse('https://redirected.com') })); + } + }(), + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService(), + ); + + const result = await tool.invoke( + { callId: 'test-call', toolId: 'fetch-page', parameters: { urls: ['https://example.com'] }, context: undefined }, + () => Promise.resolve(0), + { report: () => { } }, + CancellationToken.None + ); + + assert.strictEqual(result.confirmResults, false, 'confirmResults should be false when all results are redirects'); + }); + + test('confirmResults is undefined when at least one web content succeeds', async () => { + const webContentMap = new ResourceMap([ + [URI.parse('https://success.com'), 'Success content'] + ]); + + const tool = new FetchWebPageTool( + new class extends TestWebContentExtractorService { + constructor() { + super(webContentMap); + } + override async extract(uris: URI[]): Promise { + return [ + { status: 'ok', result: 'Success content' }, + { status: 'error', error: 'Failed' } + ]; + } + }(), + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService(), + ); + + const result = await tool.invoke( + { callId: 'test-call', toolId: 'fetch-page', parameters: { urls: ['https://success.com', 'https://error.com'] }, context: undefined }, + () => Promise.resolve(0), + { report: () => { } }, + CancellationToken.None + ); + + assert.strictEqual(result.confirmResults, undefined, 'confirmResults should be undefined when at least one result succeeds'); + }); + + test('redirect result provides correct message with new URL', async () => { + const redirectURI = URI.parse('https://redirected.com/page'); + const tool = new FetchWebPageTool( + new class extends TestWebContentExtractorService { + constructor() { + super(new ResourceMap()); + } + override async extract(uris: URI[]): Promise { + return [{ status: 'redirect', toURI: redirectURI }]; + } + }(), + new ExtendedTestFileService(new ResourceMap()), + new MockTrustedDomainService(), + ); + + const result = await tool.invoke( + { callId: 'test-call', toolId: 'fetch-page', parameters: { urls: ['https://example.com'] }, context: undefined }, + () => Promise.resolve(0), + { report: () => { } }, + CancellationToken.None + ); + + assert.strictEqual(result.content.length, 1); + assert.strictEqual(result.content[0].kind, 'text'); + if (result.content[0].kind === 'text') { + assert.ok(result.content[0].value.includes(redirectURI.toString(true)), 'Redirect message should include target URL'); + assert.ok(result.content[0].value.includes(InternalFetchWebPageToolId), 'Redirect message should suggest using tool again'); + } + }); }); }); From f9464a90829709f29e3bb7377b8a69e8440fa949 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Oct 2025 19:13:54 +0200 Subject: [PATCH 1542/4355] localize string in prompt file header (#272947) * localize string in prompt file header * polish: Use PromptHeaderAttributes constant --- .../promptHeaderAutocompletion.ts | 14 ++++----- .../languageProviders/promptHovers.ts | 4 +-- .../languageProviders/promptValidator.ts | 30 +++++++++---------- .../common/promptSyntax/promptFileParser.ts | 1 + 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index 8605abd3eef..ba828a0a341 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -15,7 +15,7 @@ import { IChatModeService } from '../../chatModes.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; -import { PromptHeader } from '../promptFileParser.js'; +import { PromptHeader, PromptHeaderAttributes } from '../promptFileParser.js'; import { getValidAttributeNames } from './promptValidator.js'; import { localize } from '../../../../../../nls.js'; @@ -158,7 +158,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { }; suggestions.push(item); } - if (property === 'handoffs' && (promptType === PromptsType.agent)) { + if (property === PromptHeaderAttributes.handOffs && (promptType === PromptsType.agent)) { const value = [ '', ' - label: Start Implementation', @@ -191,10 +191,10 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { } private getValueSuggestions(promptType: string, property: string): string[] { - if (promptType === PromptsType.instructions && property === 'applyTo') { + if (promptType === PromptsType.instructions && property === PromptHeaderAttributes.applyTo) { return [`'**'`, `'**/*.ts, **/*.js'`, `'**/*.php'`, `'**/*.py'`]; } - if (promptType === PromptsType.prompt && (property === 'agent' || property === 'mode')) { + if (promptType === PromptsType.prompt && (property === PromptHeaderAttributes.agent || property === PromptHeaderAttributes.mode)) { // Get all available agents (builtin + custom) const agents = this.chatModeService.getModes(); const suggestions: string[] = []; @@ -203,10 +203,10 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { } return suggestions; } - if (property === 'tools' && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { + if (property === PromptHeaderAttributes.tools && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { return ['[]', `['search', 'edit', 'fetch']`]; } - if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { + if (property === PromptHeaderAttributes.model && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { return this.getModelNames(promptType === PromptsType.agent); } @@ -227,7 +227,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { } private provideToolCompletions(model: ITextModel, position: Position, header: PromptHeader): CompletionList | undefined { - const toolsAttr = header.getAttribute('tools'); + const toolsAttr = header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.range.containsPosition(position)) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index b291f3f1ebd..b28bf673133 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -84,7 +84,7 @@ export class PromptHoverProvider implements HoverProvider { } const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; if (argumentHintRange?.containsPosition(position)) { - return this.createHover('The argument-hint describes what inputs the custom agent expects or supports', argumentHintRange); + return this.createHover(localize('promptHeader.agent.argumentHint', 'The argument-hint describes what inputs the custom agent expects or supports'), argumentHintRange); } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { @@ -101,7 +101,7 @@ export class PromptHoverProvider implements HoverProvider { } const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; if (argumentHintRange?.containsPosition(position)) { - return this.createHover('The argument-hint describes what inputs the prompt expects or supports', argumentHintRange); + return this.createHover(localize('promptHeader.prompt.argumentHint', 'The argument-hint describes what inputs the prompt expects or supports'), argumentHintRange); } const model = header.getAttribute(PromptHeaderAttributes.model); if (model?.range.containsPosition(position)) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 75c8dcda3e8..807af70c8cb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -16,7 +16,7 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { IArrayValue, IHeaderAttribute, ParsedPromptFile } from '../promptFileParser.js'; +import { IArrayValue, IHeaderAttribute, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -159,7 +159,7 @@ export class PromptValidator { } private validateDescription(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { - const descriptionAttribute = attributes.find(attr => attr.key === 'description'); + const descriptionAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.description); if (!descriptionAttribute) { return; } @@ -174,7 +174,7 @@ export class PromptValidator { } private validateArgumentHint(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { - const argumentHintAttribute = attributes.find(attr => attr.key === 'argument-hint'); + const argumentHintAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.argumentHint); if (!argumentHintAttribute) { return; } @@ -189,7 +189,7 @@ export class PromptValidator { } private validateModel(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): void { - const attribute = attributes.find(attr => attr.key === 'model'); + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.model); if (!attribute) { return; } @@ -228,8 +228,8 @@ export class PromptValidator { } private validateAgent(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): IChatMode | undefined { - const agentAttribute = attributes.find(attr => attr.key === 'agent'); - const modeAttribute = attributes.find(attr => attr.key === 'mode'); + const agentAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.agent); + const modeAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.mode); if (modeAttribute) { if (agentAttribute) { report(toMarker(localize('promptValidator.modeDeprecated', "The 'mode' attribute has been deprecated. The 'agent' attribute is used instead."), modeAttribute.range, MarkerSeverity.Warning)); @@ -238,7 +238,7 @@ export class PromptValidator { } } - const attribute = attributes.find(attr => attr.key === 'agent') ?? modeAttribute; + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.agent) ?? modeAttribute; if (!attribute) { return undefined; // default agent for prompts is Agent } @@ -269,7 +269,7 @@ export class PromptValidator { } private validateTools(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === 'tools'); + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.tools); if (!attribute) { return; } @@ -306,7 +306,7 @@ export class PromptValidator { } private validateApplyTo(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === 'applyTo'); + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.applyTo); if (!attribute) { return; } @@ -334,7 +334,7 @@ export class PromptValidator { } private validateExcludeAgent(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === 'excludeAgent'); + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.excludeAgent); if (!attribute) { return; } @@ -345,7 +345,7 @@ export class PromptValidator { } private validateHandoffs(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { - const attribute = attributes.find(attr => attr.key === 'handoffs'); + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.handOffs); if (!attribute) { return; } @@ -394,9 +394,9 @@ export class PromptValidator { } const allAttributeNames = { - [PromptsType.prompt]: ['description', 'model', 'tools', 'mode', 'agent', 'argument-hint'], - [PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'], - [PromptsType.agent]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs', 'argument-hint'] + [PromptsType.prompt]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint], + [PromptsType.instructions]: [PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], + [PromptsType.agent]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint] }; const recommendedAttributeNames = { [PromptsType.prompt]: allAttributeNames[PromptsType.prompt].filter(name => !isNonRecommendedAttribute(name)), @@ -409,7 +409,7 @@ export function getValidAttributeNames(promptType: PromptsType, includeNonRecomm } export function isNonRecommendedAttribute(attributeName: string): boolean { - return attributeName === 'advancedOptions' || attributeName === 'excludeAgent' || attributeName === 'mode'; + return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode; } function toMarker(message: string, range: Range, severity = MarkerSeverity.Error): IMarkerData { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index fc5b526135e..3e70621a415 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -71,6 +71,7 @@ export namespace PromptHeaderAttributes { export const handOffs = 'handoffs'; export const advancedOptions = 'advancedOptions'; export const argumentHint = 'argument-hint'; + export const excludeAgent = 'excludeAgent'; } export class PromptHeader { From 8ba1f512a3160f9157782118fafdfe3cfe1820fa Mon Sep 17 00:00:00 2001 From: subin Date: Thu, 23 Oct 2025 23:09:53 +0530 Subject: [PATCH 1543/4355] Breadcrumb icons are now responsive to visibility setting (#216662) --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 16f677bae4c..f7e61237eb7 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -103,6 +103,10 @@ class OutlineItem extends BreadcrumbsItem { filterData: undefined }, 0, template, undefined); + if (!this.options.showSymbolIcons) { + dom.hide(template.iconClass); + } + this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); })); if (element instanceof OutlineElement && outline.uri) { From 4b985d9ed2bc22020d1c845dd0d160f50f3b704f Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 23 Oct 2025 11:02:15 -0700 Subject: [PATCH 1544/4355] Fix scrolling glitches --- .../debug/browser/debugEditorContribution.ts | 23 +++++++++++-------- .../contrib/debug/browser/exceptionWidget.ts | 12 ++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 005700db9ec..9ce76504992 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -633,7 +633,16 @@ export class DebugEditorContribution implements IDebugEditorContribution { const currentScrollTop = this.editor.getScrollTop(); const visibleRanges = this.editor.getVisibleRanges(); - const firstVisibleLine = visibleRanges.length > 0 ? visibleRanges[0].startLineNumber : 1; + if (visibleRanges.length === 0) { + // Editor not fully initialized or not visible; skip scroll adjustment + this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession, this.shouldScrollToExceptionWidget); + this.exceptionWidget.show({ lineNumber, column }, 0); + this.exceptionWidgetVisible.set(true); + this.allowScrollToExceptionWidget = true; + return; + } + + const firstVisibleLine = visibleRanges[0].startLineNumber; // Create widget - this may add a zone that pushes content down this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession, this.shouldScrollToExceptionWidget); @@ -642,15 +651,9 @@ export class DebugEditorContribution implements IDebugEditorContribution { // only adjust scroll if the exception widget is above the first visible line if (lineNumber < firstVisibleLine) { - // Get the actual height of the widget that was just added - // by checking the view zones - find the zone we just added - const whitespaces = this.editor.getWhitespaces(); - let scrollAdjustment = 0; - if (whitespaces.length > 0) { - // The most recently added whitespace is our widget - const lastWhitespace = whitespaces[whitespaces.length - 1]; - scrollAdjustment = lastWhitespace.height; - } + // Get the actual height of the widget that was just added from the whitespace + // The whitespace height is more accurate than the container height + const scrollAdjustment = this.exceptionWidget.getWhitespaceHeight(); // Scroll down by the actual widget height to keep the first visible line the same this.editor.setScrollTop(currentScrollTop + scrollAdjustment, ScrollType.Immediate); diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index c408d6dbb10..8461e020354 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -139,4 +139,16 @@ export class ExceptionWidget extends ZoneWidget { return dom.isAncestorOfActiveElement(this.container); } + + getWhitespaceHeight(): number { + // Returns the height of the whitespace zone from the editor's whitespaces + // This is more accurate than the container height as it includes the actual zone dimensions + if (!this._viewZone || !this._viewZone.id) { + return 0; + } + + const whitespaces = this.editor.getWhitespaces(); + const whitespace = whitespaces.find(ws => ws.id === this._viewZone!.id); + return whitespace ? whitespace.height : 0; + } } From a30f318e0fd7bf3eff1a9dcb2cf595b176e13f49 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 23 Oct 2025 11:23:59 -0700 Subject: [PATCH 1545/4355] ensure prerelease is installed with url link in insiders --- .../services/extensions/browser/extensionUrlHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index c1c1e01c961..a7b67f152e3 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -271,7 +271,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { reason: `${localize('installDetail', "This extension wants to open a URI:")}\n${uri.toString()}`, action: localize('openUri', "Open URI") }, - enable: true + enable: true, + installPreReleaseVersion: this.productService.quality !== 'stable' }); } catch (error) { if (!isCancellationError(error)) { From ff343be90d02175b0c37a83fe74fbd259badf2bb Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:31:35 -0700 Subject: [PATCH 1546/4355] Support sortByLabel:false in QuickTree and adopt in Tools picker (#272958) Fixes https://github.com/microsoft/vscode/issues/272773 --- .../browser/quickInputController.ts | 5 ++ .../browser/tree/quickInputTreeController.ts | 35 +++----- .../browser/tree/quickInputTreeSorter.ts | 46 ++++++++++ .../quickinput/browser/tree/quickTree.ts | 8 ++ .../platform/quickinput/common/quickInput.ts | 5 ++ .../browser/tree/quickInputTreeSorter.test.ts | 89 +++++++++++++++++++ .../chat/browser/actions/chatToolPicker.ts | 2 +- 7 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 src/vs/platform/quickinput/browser/tree/quickInputTreeSorter.ts create mode 100644 src/vs/platform/quickinput/test/browser/tree/quickInputTreeSorter.test.ts diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 8c4fc2d6a47..907b7965a71 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -654,6 +654,11 @@ export class QuickInputController extends Disposable { ui.list.matchOnDetail = false; ui.list.matchOnLabel = true; ui.list.sortByLabel = true; + ui.tree.updateFilterOptions({ + matchOnDescription: false, + matchOnLabel: true + }); + ui.tree.sortByLabel = true; ui.ignoreFocusOut = false; ui.inputBox.toggles = undefined; diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts index fdb0e64d7ff..aac6a8e981a 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts @@ -17,12 +17,14 @@ import { getParentNodeState, IQuickTreeFilterData } from './quickInputTree.js'; import { QuickTreeAccessibilityProvider } from './quickInputTreeAccessibilityProvider.js'; import { QuickInputTreeFilter } from './quickInputTreeFilter.js'; import { QuickInputTreeRenderer } from './quickInputTreeRenderer.js'; +import { QuickInputTreeSorter } from './quickInputTreeSorter.js'; const $ = dom.$; export class QuickInputTreeController extends Disposable { private readonly _renderer: QuickInputTreeRenderer; private readonly _filter: QuickInputTreeFilter; + private readonly _sorter: QuickInputTreeSorter; private readonly _tree: WorkbenchObjectTree; private readonly _onDidTriggerButton = this._register(new Emitter>()); @@ -57,6 +59,7 @@ export class QuickInputTreeController extends Disposable { this._container = dom.append(container, $('.quick-input-tree')); this._renderer = this._register(this.instantiationService.createInstance(QuickInputTreeRenderer, hoverDelegate, this._onDidTriggerButton, this.onDidChangeCheckboxState)); this._filter = this.instantiationService.createInstance(QuickInputTreeFilter); + this._sorter = this._register(new QuickInputTreeSorter()); this._tree = this._register(this.instantiationService.createInstance( WorkbenchObjectTree, 'QuickInputTree', @@ -74,28 +77,7 @@ export class QuickInputTreeController extends Disposable { expandOnDoubleClick: true, expandOnlyOnTwistieClick: true, disableExpandOnSpacebar: true, - sorter: { - compare: (a: IQuickTreeItem, b: IQuickTreeItem): number => { - if (a.label < b.label) { - return -1; - } else if (a.label > b.label) { - return 1; - } - // use description to break ties - if (a.description && b.description) { - if (a.description < b.description) { - return -1; - } else if (a.description > b.description) { - return 1; - } - } else if (a.description) { - return -1; - } else if (b.description) { - return 1; - } - return 0; - } - }, + sorter: this._sorter, filter: this._filter } )); @@ -118,6 +100,15 @@ export class QuickInputTreeController extends Disposable { this._container.style.display = value ? '' : 'none'; } + get sortByLabel() { + return this._sorter.sortByLabel; + } + + set sortByLabel(value: boolean) { + this._sorter.sortByLabel = value; + this._tree.resort(null, true); + } + getActiveDescendant() { return this._tree.getHTMLElement().getAttribute('aria-activedescendant'); } diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeSorter.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeSorter.ts new file mode 100644 index 00000000000..70ee0db6fa2 --- /dev/null +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeSorter.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITreeSorter } from '../../../../base/browser/ui/tree/tree.js'; +import { IQuickTreeItem } from '../../common/quickInput.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; + +export class QuickInputTreeSorter extends Disposable implements ITreeSorter { + private _sortByLabel: boolean = true; + + get sortByLabel(): boolean { + return this._sortByLabel; + } + + set sortByLabel(value: boolean) { + this._sortByLabel = value; + } + + compare(a: IQuickTreeItem, b: IQuickTreeItem): number { + // No-op + if (!this._sortByLabel) { + return 0; + } + + if (a.label < b.label) { + return -1; + } else if (a.label > b.label) { + return 1; + } + // use description to break ties + if (a.description && b.description) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } + } else if (a.description) { + return -1; + } else if (b.description) { + return 1; + } + return 0; + } +} diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 44db5bdc24c..4a03c4ab99c 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -23,6 +23,7 @@ export class QuickTree extends QuickInput implements I private readonly _placeholder = observableValue('placeholder', undefined); private readonly _matchOnDescription = observableValue('matchOnDescription', false); private readonly _matchOnLabel = observableValue('matchOnLabel', true); + private readonly _sortByLabel = observableValue('sortByLabel', true); private readonly _activeItems = observableValue('activeItems', []); private readonly _itemTree = observableValue>('itemTree', []); @@ -60,6 +61,9 @@ export class QuickTree extends QuickInput implements I get matchOnLabel(): boolean { return this._matchOnLabel.get(); } set matchOnLabel(matchOnLabel: boolean) { this._matchOnLabel.set(matchOnLabel, undefined); } + get sortByLabel(): boolean { return this._sortByLabel.get(); } + set sortByLabel(sortByLabel: boolean) { this._sortByLabel.set(sortByLabel, undefined); } + get activeItems(): readonly T[] { return this._activeItems.get(); } set activeItems(activeItems: readonly T[]) { this._activeItems.set(activeItems, undefined); } @@ -206,6 +210,10 @@ export class QuickTree extends QuickInput implements I const matchOnDescription = this._matchOnDescription.read(reader); this.ui.tree.updateFilterOptions({ matchOnLabel, matchOnDescription }); }); + this.registerVisibleAutorun((reader) => { + const sortByLabel = this._sortByLabel.read(reader); + this.ui.tree.sortByLabel = sortByLabel; + }); this.registerVisibleAutorun((reader) => { const itemTree = this._itemTree.read(reader); this.ui.tree.setTreeData(itemTree); diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index bafa97203bf..e4d11bcec06 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -1073,6 +1073,11 @@ export interface IQuickTree extends IQuickInput { */ matchOnLabel: boolean; + /** + * Whether to sort the items by label. Defaults to true. + */ + sortByLabel: boolean; + /** * The currently active items. */ diff --git a/src/vs/platform/quickinput/test/browser/tree/quickInputTreeSorter.test.ts b/src/vs/platform/quickinput/test/browser/tree/quickInputTreeSorter.test.ts new file mode 100644 index 00000000000..e887d5f56ae --- /dev/null +++ b/src/vs/platform/quickinput/test/browser/tree/quickInputTreeSorter.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { QuickInputTreeSorter } from '../../../browser/tree/quickInputTreeSorter.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IQuickTreeItem } from '../../../common/quickInput.js'; + +suite('QuickInputTreeSorter', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('sortByLabel property defaults to true', () => { + const sorter = store.add(new QuickInputTreeSorter()); + assert.strictEqual(sorter.sortByLabel, true); + }); + + test('sortByLabel property can be set to false', () => { + const sorter = store.add(new QuickInputTreeSorter()); + sorter.sortByLabel = false; + assert.strictEqual(sorter.sortByLabel, false); + }); + + test('compare returns 0 when sortByLabel is false', () => { + const sorter = store.add(new QuickInputTreeSorter()); + sorter.sortByLabel = false; + + const item1: IQuickTreeItem = { label: 'a' }; + const item2: IQuickTreeItem = { label: 'b' }; + + assert.strictEqual(sorter.compare(item1, item2), 0); + assert.strictEqual(sorter.compare(item2, item1), 0); + assert.strictEqual(sorter.compare(item1, item1), 0); + }); + + test('compare sorts by label alphabetically', () => { + const sorter = store.add(new QuickInputTreeSorter()); + + const item1: IQuickTreeItem = { label: 'a' }; + const item2: IQuickTreeItem = { label: 'b' }; + const item3: IQuickTreeItem = { label: 'c' }; + + assert.strictEqual(sorter.compare(item1, item2), -1); + assert.strictEqual(sorter.compare(item2, item1), 1); + assert.strictEqual(sorter.compare(item1, item1), 0); + assert.strictEqual(sorter.compare(item2, item3), -1); + assert.strictEqual(sorter.compare(item3, item2), 1); + }); + + test('compare sorts by description when labels are equal', () => { + const sorter = store.add(new QuickInputTreeSorter()); + + const item1: IQuickTreeItem = { label: 'a', description: 'x' }; + const item2: IQuickTreeItem = { label: 'a', description: 'y' }; + const item3: IQuickTreeItem = { label: 'a', description: 'z' }; + const item4: IQuickTreeItem = { label: 'a' }; + + assert.strictEqual(sorter.compare(item1, item2), -1); + assert.strictEqual(sorter.compare(item2, item1), 1); + assert.strictEqual(sorter.compare(item2, item3), -1); + assert.strictEqual(sorter.compare(item3, item2), 1); + assert.strictEqual(sorter.compare(item1, item4), -1); + assert.strictEqual(sorter.compare(item4, item1), 1); + assert.strictEqual(sorter.compare(item4, item4), 0); + }); + + test('compare handles items with no description', () => { + const sorter = store.add(new QuickInputTreeSorter()); + + const item1: IQuickTreeItem = { label: 'a', description: 'desc' }; + const item2: IQuickTreeItem = { label: 'b' }; + + assert.strictEqual(sorter.compare(item1, item2), -1); + assert.strictEqual(sorter.compare(item2, item1), 1); + }); + + test('compare handles empty labels', () => { + const sorter = store.add(new QuickInputTreeSorter()); + + const item1: IQuickTreeItem = { label: '' }; + const item2: IQuickTreeItem = { label: 'a' }; + const item3: IQuickTreeItem = { label: '' }; + + assert.strictEqual(sorter.compare(item1, item2), -1); + assert.strictEqual(sorter.compare(item2, item1), 1); + assert.strictEqual(sorter.compare(item1, item3), 0); + }); +}); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index db65a8d09bc..8aa8cef2329 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -445,7 +445,7 @@ export async function showToolsPicker( treePicker.description = description; treePicker.matchOnDescription = true; treePicker.matchOnLabel = true; - + treePicker.sortByLabel = false; computeItems(); From bf902d99ffda2e3cc20c6d86fe0d635a844aa69f Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:22:53 -0700 Subject: [PATCH 1547/4355] ensure we hide options picker if session has nothing to contribute (#272977) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 4 ++++ .../contrib/chat/browser/chatSessions.contribution.ts | 7 +++++++ .../workbench/contrib/chat/common/chatSessionsService.ts | 1 + .../contrib/chat/test/common/mockChatSessionsService.ts | 6 ++++++ 4 files changed, 18 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index b8725f54746..e42bfe32ee5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1139,6 +1139,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return hideAll(); } + if (!this.chatSessionsService.hasAnySessionOptions(ctx.chatSessionType, ctx.chatSessionId)) { + return hideAll(); + } + this.chatSessionHasOptions.set(true); const currentWidgetGroupIds = new Set(this.chatSessionPickerWidgets.keys()); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 0ebd8373351..bab7bd993db 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -795,6 +795,13 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessions.delete(sessionKey); } + public hasAnySessionOptions(chatSessionType: string, id: string): boolean { + const sessionKey = `${chatSessionType}_${id}`; + const session = this._sessions.get(sessionKey); + return !!session && !!session.options && Object.keys(session.options).length > 0; + } + + public getSessionOption(chatSessionType: string, id: string, optionId: string): string | undefined { const sessionKey = `${chatSessionType}_${id}`; const session = this._sessions.get(sessionKey); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 2f8c2983b8c..e87eda82afe 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -183,6 +183,7 @@ export interface IChatSessionsService { // Notify providers about session items changes notifySessionItemsChanged(chatSessionType: string): void; + hasAnySessionOptions(chatSessionType: string, sessionId: string): boolean; getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined; setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index c626224c118..3c03129bec6 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -202,6 +202,12 @@ export class MockChatSessionsService implements IChatSessionsService { return true; } + hasAnySessionOptions(chatSessionType: string, sessionId: string): boolean { + const sessionKey = `${chatSessionType}:${sessionId}`; + const options = this.sessionOptions.get(sessionKey); + return options !== undefined && options.size > 0; + } + getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined { return this.contributions.find(c => c.type === chatSessionType)?.capabilities; } From 1877f27c542ba78c07623da64751a96eedb1f296 Mon Sep 17 00:00:00 2001 From: Lee Murray Date: Thu, 23 Oct 2025 20:33:59 +0100 Subject: [PATCH 1548/4355] Add agent and edit-code icons to codicons library (#272948) update bug, files, copy. add agent and edit-code Co-authored-by: mrleemurray --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119300 -> 119348 bytes src/vs/base/common/codiconsLibrary.ts | 2 ++ 2 files changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index fcd31780d4a5c564db9e60789dfd7b3e70bd69e7..4bd5d8c0d88a28164f526649ed011ade41161f5d 100644 GIT binary patch delta 11137 zcmchd33L@zw*J3;=X(RmO}IA+0YVZWL1scGrhrT$U}Oj}iXvkSGcpMXTm(f#L?9Rt z5fKp)kw#_^5s|i)wo#Er)RsoXsj3<$R76_ece1-*zy7c8_1;_SzaER<{i^B?RcG&g z&fc{v_ipm<-sE52CN{py-f|##254~Xj`6c9QnvNn0yJL*Sv;q5^zc#TD>sw^I7bR9 zdB9cX&gS+Z(ztTMw3+?4{Q5Z{4&ZmjPZ~M==|}y`fSh%J`|^b0Gbg(vvY79u^79!J zhff$?b>%<_WOtayE>E5`wf3|Rd(Zg_vNr(KZ?@u4quM`1_*v3$N8M{yIBn(UUFlNf zs;Y3OR*iM%k&ab=m0;C*cOi9rRc&t`wPjT((0o^I?`cni_O!9>-VG7`^0W zJcLw^bPt}83DQeCNoV;ZzQ8N^K;D$?cuHQ9XYix!mMyYZ4#|Fb0Uycpau6MK0PQ5BVrW5!#~zu0=7r zp*wn@C$2+p^g{{yqZE}Gi*Xo_yD=9rEWjcx#sgS_2eA~(@GzER1y-U8kDwZB@F-&I zu>qT~1M3I)0DScn=@q&-erv z@HxJO1se{&!zFx=AMhhC<0t%8qEc7Vq`owi49S!%$(9^xA+4mfNNYNEhiU z-KD4Wmg{8z7Rg{Klbg^)Zk8c(s|=OfWt5DT3b{inWsHoI@iI{+%T$>rGq74_$}E{J zcgq~PSMHNVa=$E=2W5q;C*Q(*Wj%5Lj-@qW9W-fatnrHB(7ozZpQ&hmR%SjQ*Z@8N=%q!q>1J*EwiwUgZidogH5sFNwj#Rj!z#66CBz3eR<~J%7 zd4+n1A|=#HMINJ$iJ2E(P(F36!i5ReI0ZLR$1Cy;b%G*GsS_1hO`W80DT6gx;i3l1 zTp&S5>J)_w9jvJeVvloUnu4#WW^{z>Agmb*dQtCExHiI?sc@BqHA{i9&}_xz2K&p$N02wNjD& z)GCE51*}yHKB7LN$n#X=s%kUhgWNDaBupT%)+kIRupU*IRA8-Dm|kF+;2}&ju$Y1k zOgXTOI|-8ytn~`h5G-SQ!h{5CgMvhA%mf=@l7h8SVY-54%tV;DU~N*E!eBkEAcMMD zVOoP_tVfvOU>PeBraD;9DolE?Oh6K*KUmKxF=j$o+Z5(RSjIGj*%6lU8)2S=^&5p5 z6V?uexf7N#F<};kwNqg}g|$mzW`$*3NSI?`nSdnBwy^dZ|I?35ys-8uOu?{>4+)bo ztOE+uGA!dN!UPTLMTMyvmhnGf(uVbt!t@R6u);(R>t%&09o7*AJ!0H=MPZ(YbyQ)- zhxMw$+z;!R!YTmPZxz-9uwGMG8Nm9T!WseA8w#rhSZ^w<8({rGVFdx}1c~v5#RROk z6ebN=Z!0V?V7;TzP_RxaEIMGltFZilbxL6&0_%?oOA=V86&5G3-cwknz&fL_VB!3K zpF4!L3#<fptz{1qAD3g|!f@^9rjX zSfA9uVXf!J1qBpqqp#5BZ zmin;G#S*hl*ew+qOf?ryVp7HpbLE8h1hCsEX3e+TDsnS5SCJvqc8c6eH6tZ5l$x)| z?Nl>rBBQ8g)I>&8&8Ue~P}>{-x983s+%Xm)Qc3No$QWuTMaEG(D>9y1tjI*Fu^5rb zRAVtBQ>oV~GL70z;VlhpV@YDFW7s_unMpNfB{GZJOOe^s>lC@0YRpY!4%L{O$i37) zirh!-tB5gsKSl1R-k_LUb-RSb_#zK-qrW06sK#$ZR#FElQbir4$RpHJMb=P_KZ&fR z8g~*|Pc`l&@&vV9kquPiP9hu4{J(`eM4qA=-x7J2YJ5v%EA=)-o}(H+6WK<+UE$px z>|u&2IPKwz?4XWNWG8i`BIYwDE=JM+vX2`kFo+zWn!q6PBGtqNkyojeiX5YkQRENQ zv5LG!H8Dcu9jb{DBJWZsC~}Hw;)Td*>Lla;N!)plYJ!Kz8S0&iyic8?$OlvtR7B2F zO;8azN1d+7$5azwM9x$1Qsh&ri7z6bQD-R<`?ANG^4Ik*aLw5h{A3F>}rL50obb*d`exTuvY;4Q3K6qdRO*Zg$)GQ zk0~&2S*J*Z`nV$3QP(SC%>RTUhp8JBHXmR=so)HiQ_Ub|fM%uWd1SF|ZFQFmwGSMdnhCxrxlDzO1lm1KXU0z#M-?ksqi>6}e1(RgoW! z|Bfm08uhn|{Elk&iM&oV`$XQLntdY2sb-(Z@2O^=utx;j>=Sm2V85xLf@%gq*g=AQ zg4ERi#yoE+Y%9TjTVaC<_B#r`p`KJ2udv@$*nWb2N?{`k_8%3tq+p*`*qnm>p29X2 z>@x}*R-FIubBC~Z1^WYqT`btf4TSwH*v1Wnoh{hLRRnXWXBBq4VEc-NG}tEM38K_56*0eIDN;WM+9YNSv74`;U|5ag^5Vr9-VZRXeuZn5H zbHFu7n95Nbgr9LZY6JU71z?@-Oj3EP-4I5eLDgQ*FMETft$C+u>ozO13RQ}7=#8KrKTF_f3{8G)KySGHKQPMBh`$KNPB91 zMRKXeVuX!WIOz&TQyVH`PL!d@UDQm44Ouu@3RY8NjAn;m6}7Py2zChW4So?C5&9yoJkF^#uhxayhvT;;j8E8_a6H^EJU+ZM zye<4@;>^VDiDwgkNGeJinsjf{xjIR8n$;OtXJ#Z2nGsEoE{^U^&PZO8d_1LJ%AwR) zeCpo1fx1)bu1#x|wzS^-dSBOHR{vy!S`C&oxR~BG{q2TxGRiUonWwV0H0ssZ)%bXm zq1mDAlI)$?mvYi`3UZD&jcbSMz`A#LB4tCD&Jg@WR z;%UWBm$I(tI<)JFYm2Vk)Ge)BS+~vI&UR1h-mClW?pBYC9%Vg__3YDgOHZfQE!P!Y zx2boy_u}4%t}niRQJ+Ac*v!73z8m`G_S# z{A_H5Z$!z61tYeO_;zIW$dZxMM!q(x->BoG(??&b=u@$_;_4keDw8YwS1zgiY|Mx; zOU9fV+hXjTv1i7mjw>BEd)$U`XT~RuuNZ%ELWc=!CY+ksY~t97`zOUuS}^I{*qz@_88+qg)WFn=shg)Qns#aWl*lT7`S{w$|Fk}SecAfz^bUVWX{kOyuBr3%id=@JiF!DU$*wz zy8gM0=T>cNx^4UR?K^JSG4k+^^DmUVu=$0HJ2&k-w<~qm%w5}e$M4>}C$J}bPq#fo z_srR|e$SP?WqV)W*Jt0={U!U)9(es=<-v0=?s)Oap`nM)y;S_thQoowC5KPHoc8jq zFJC&+ig%4~KA2C0%__h@Lx?jE2?`r!W#<)il-zh)wB}aVC#T@rFl;n2ilf^v9?TGp4Yfar=ue+(&?{4Gq zwDknMeph>UI4R!K*6VHK2?zh*x4ZuH`qv{25@YU~QKYfS*_F>FxNbLTl%JPVSdf>UmB3+8e972CmQY>Y1Fh)BrY&H5Lcbe zFS-IgchsAbnVI5^x_to`e=*w|@MWfDns+jz(M)rcyS{+;&g5`7x-ilx!vF34adCbQ z*uavT-P~O-cPS~1rSUOZhIz&ko`nGU#a>7u)xZ?u11>$O6POR`Ig(gR{BB4Om$>fA;z8kJiNPf}0T^$I8 z0?crW@4Ckw^Zm+ye1z}koo^3TGiB_M%;hD~ob3t=7(EFRZKpvL@ux7DvRcu#m%KuygFO{~Wt9 z4DIs2HuisU6m3n5r!=ZDfBt1Nbg#GC8w`eOM}pNszdtx#twwWl?8cliV^0SDKikt6 z%}Dmv41P#g5Zf>&wACo?2W__cae+sJ3(rsYC z>*^k^ zT0S!nQ=`;pb>Zh6Blgk;X1%vzaTJ?2MhFRC4J`2*>ZMUnLMNS3&o)Q${j+}KslRkNAP)r?+u zJseC6_7Avxu3CY*A%Fj1@HO*ozjr#n8>yY;YHYsB@B88+E%;Tx*L;oNW(CtC7#!da z)eY2g`P}~gp}HaLDt+{HawGi0hooV4;quI1TNIjP8qdE;`&+rtMVOqb+Bo%Yt(jga z=IxwT4WrY&;b3{4hDpJ|l(<0mKqO&Sd^8$wa{4TPs8d`*qSw>D+koWsKrpFcod-i< zPmaGwAT%W*5=nSIK9U&nKW{!;nGn~hlgHa_;jtb2j_r`^z4G(E_`UM2+vED#b+!5+ l)4O;09it~s+n04v5(A;&=uu;)wH!HV)aZTfj>n=PaGDfNgt1b<~t`^5Zy~;01uzX6O zQ~6nM8|p;Tk<_nneBv)GtK1R3MetV6HKis1{xbM{LwLw2Qu&L=S?rWKuQ_kHVYhF5 ze0fenyO@V-y^4|x$~$QB0` zcvL1!cWEyjJ8Z|lUH6rbY@ zoWqxJ@GZ{cJ6yo`_yIrSuaYj=QbnpsO{pcdB}a0lzBHDm(oFKDKnkTOCT*pYbe1cm zoAi_`t%opM6TQ*H_8whCO64w86#t5oQ#(VGD&Wh5}7J9WTxDT)pEPsA*FJs z%#wLBUlzzhStR$#GFdJwq+C|Z8d)cg%6i!#kIPfCMV^+e@;g+>cG)2n65Azv9Ztwnc_1cN$wuif zBQZtJqL;M5CVV9Iq*z9wx%87>=zxysCkNyc`B45OAK(vim&}nah{*-{LB5y6@_Ttz zUX$154S5A=7$;X_mwb)8@fTdezu{kTBL?FJWa1kf!CNT7T{0IhGD5QOF=D5sFLF>v z>L5ctm5!2zzX>EDUhzvv!jdQ{k}4h%IS5xqU;_dOB80FcOHd-Hfiu!b^3Vmq9a#W6?k358f;-XNlkv7s&reLgO%5Ab1t79maO|lYy#5;IbzG5!9 zN@6k_%DX1jDgMGUQo|lIggJpYWzM!IR>X1WCd$ z48=$^WA0gt5pqBJqbZu8ue6glk%#Lr5s%?nye>m!qNLq}Qh7qUB97PO9CJ^gd`9xc z^0~>8SRyk6s?^Xa;9*j7MjFf{a7G!DMICL(Ez~gvvk9EB2AE)-afS@1jyL2b>I6gj zP$wGlFm+N)C%mB6)SC?^B{-7}45F48@)zn9LzYmd8uB1jFN83S!I^F_p~0D9pgnb_ z!6XMqZ-OB92xo3J@D=qogEK?=YAb;V|_p%#d*IG>}c5WiV^PxyxYwgfrW~ zqtv?%(Hoq@^Xo*qb3rXZM6YzNAswjq7)-u!<{3=GaON9K$Z!@II7wY-$PwxygXtR1 zy#^kqa=cRH1;pT}DG9SVocj%CHaL2BM4qEAGh{P$xgk5LD-793U1`W}YPrEI0Y_az z@F7)QMdTUkYDMpOFJ~SyIC#KWV{jOOqd`JApukyc=#b~A+Xx34IO_}!H*nO%gaZ$p z^#+F^IFA_|jNoiAI9Sr+Y7W8y3eHA@LlvAS3=UdwHW?hg;HZ@d2QoNn4ZNc!Z-W9Ca1p_zGv2!I2iuZuLL?#X%R&9)rU#9CZodKn&-3gF`YL z^)un%4Ce)d!!#TgOA3mqFB%-W;T$kHh{Mr9B^=J-95m1^#+jE5j`45~86555ykc&U*&S5jZCd7AA1sH&~*;Iccz1;q(6|E)do& za6T|t!NB>@U@ZgZl)mWFv8mx%md{znV zzm79!3|2{SJ~vn|!8vQNa)R@P!5RwAIfK;{oG%U5Rd5`G6&4)VV66owZm{Zt^OeE+ z3(nW-|F3!X8qt3-Sd_u}#$b5{=Uan?8l3Y6OEx&)87$u5TrgP1!TH``K?mmtL#uh` zM}tKkoQnox8hk$)to-2oY_JA|^H+n_Ae?_SSQogx{jU?2mvDUs3r)CwgC!^2fWhJuZqQ&E3O8i1AcY$?Sen9(7%WoZ zCK@bP;U*a@T;V1gEMa4CQ@B7h=ewx}>sq*J1}j{+Q3JGyn{J3|h9TM1OhaFU+$=+? zQFR@Wnp9m!q!zWRLjOx`&gh0ja;Vh}$)(mXq&`&-K%_BM4?v_TRre>-jH>$+$*0ya zq<~u2kV0xbLyD*|JqeMvR6PliPSiX@I#U}Mas{=aA>F8r4CzVLiz0F*wTZzpAFf^? z(LCYm1roWQ+T4iA0M4{9_&xx)rJ*_BZDq&}RJ|i2H&R<0GK8vkM`RdP?~ce#RJ}VQ zqp58S8AH_@B{G)UPW|7G3*)%Z-jMOs4u(vib~I!XRV_p0W~y3-NC~xzAycWvhRmR< z#fZ$Lb~X4q16Qp_v@(X<-H_Xns%9lpN>#HGxs%$#6EOA{+GiS0562oI2Eyr>N>rB3r2HPa;oKha0k$I>O+a9^8?J7M$)VL$*`Z z|3r3B)&E3v8}VRE-59Z&4>3@-|iD zg2+46DTcgD)fgdioH|YYKaC6TQKuVnf~vtoHPQq7ixc3@-`G&jL;A=Qs4O+q% za=1$jzM8|m-{4C++%f}dg$E2R$=#&}U*Y-uFXIB?+dN#2c!Igq6$an%;jT3Jt`E1| zKqKlZgYW-vA2iqrfV;auX)DWJYp{6$_Yni?qjiQvsgD}c zgSy@jb<1Oh?5A!p*m!{ZxPcSYjfUu*>je@~i^Mi@LgXsylZNQVn+@qteaeuL)GY=y zqV?_wTN-fnaU|$Pea2v$1MW714G*}#GoS&x-H1s&&Ztj_6jOH??2N#D)_|J#IYatU zD-8Bc;O;coL4m7}GGQ+Tt_B!^ZokKn1Ju2Sd_sL*{r@~q{2>?i8S*FU3kDl8aMi2? z`mDcb$X!%D7?C;Dmkc&+;2t!1Xt*yMa)EltkRPb881lVtbl4Ep-y5R(sv)Yc8KU~S zA*yc}qI$$&e+aH#EMcbz?wbb2QQtDyJ%W3bRM!7$wzmzol;9pS*j$49j=?q)+;|a8DchGUKYB3HxAhKQ-79gZo(}w7+hA#$cBW?&k(_ zs2bS>b*Nt$?4iLuXCQ<6r6GE7$B>Rx*I=&=Zrs4%s9zbv!@64kyEM53sA^&&UaDGy zh@bkcAtCB{L&DVW3`wMFfDuWdes4%BRfCO)hx(%-JhOYzkk~=a{A6gs?*44Z2PUMBHb9 zu88xl6=_7}2^BUU5!V9{wjmMc2^BUZ5!VwEwk8ozFxaF-Tu(ySu0)(xQt&o4$zTgJ zhPYlK!2l}Fr$`x<=2O_=M4UcU@HI7RNGoc(A$kJFg2GNH;*4wseW(mPh22ravke@i zR#9SHL9@s8$_WaodPhXAp;kAf4OOi~q$Rbc!6qx>wG51<);2^hAjgo~sC5iBV-c@w zU^O*Xj}wA&YOW!hsPzptZ4p<~6Si*=Z(y*Ii+Do=%nNaK6_Kx~jSc-|6mMeSDr!?h zc+>G_hIWAwZ*H(3jCczJ?^E?oiRg*c+=Sg?#PbcDqP8~JK}Nj5U@sYQwIpFz8Sx^6 z{bj_}l7yXR#M`R>+j3zH7up$YKO^4WU?Upw4hCD&h<7yDoJPEp!8SGGoeegu5!c`( z*h4KgL}TL$gY9g@yBZj!rs!s{$BlS*gWYb#dl>9{Bi_@%o75`}_QDbGW#CDwnwW@> zs&_{ufqJ#Uo;l(gkc8cH#IGSSPS{9Cysv?g)N2i>kNO$h13>^&a$|^ELGK^sV+? z^7rzujRg=G7TCc5r-J#x(ZLg;si6jsztUZ7AI~_nwvZ|rFlw8N?B@h zYV*{-sZ&#zrnOI-p0*)vZ`#k%oM_MJ#^~{AJUt`5efp7%wi%~0duNtrewx)Mt1LS? zdrtPTDy3D9R4u4Ftm-$_`d05+{kadoKq#|NS)PnZ>|@r zcR07Keop;2^OEys=IzY;DDQ^`OBx()xS`RY#=geA8!u|SxAE!5mzq>*Qru*ClT}R< zo1SbIYBsppx#m5auWOOeVs?weEiSa|*m7pe4K2TEmEY>tR)<@i&)?fRul3B5qG3h*V?`I*Y;9ZGc4xb+c3sV8#O@8sTdu1>gmbDy?-wqDcmnk{{E`>wy%bM4q`U+p)x z-^uG@Rj%9Hzh(cO*H^oK-u2rD~rpZNpC9)a$0Qn~n`n9X@viMob)WXoNE|b!7LE>qnj( zl{2dUsJWwdjyf|sarA)Ei%0JmeQ`|FF$2cT8uRAZ{$r1g%N_Ug_*_-oko_F&{lWR>LGI{Rg&67VWsZvr>a$!pGl=V|SnA&pc^r;7? zWlUQt@G}+?{y$fjJH5+&<@GY|GrXbC2EQ zxu^J^(s@Pm=FIzG{_y#$=5JdNSeUhN%fb&9jbC*3-XZrMS=@c`#>JQJ>wVwGB@LH+ zbN}@FPnC5qTU2)Zfj$pxS?XIleCeyp`Y$`MyyNoqD-u_ft~j-F)ynhb&C8dT?^{*; z;NjI1AL{kc_BFX{%GR8CI4Ab-ytUD_iyy%wi`VsEw_x4gM>jnB>H3WI!`81`|HEU0 zAKSPA8%iGcJwE91LmT^VT=|6OiSADv-PC8(@h9`2T>0dg%~LnO`&7YGv!6P#C2!0A zr;DH7zSXz2&(=-PwBv=bjz#?6zkwJh%0^vlX=}=2Yz5nXz-* zuH;=!cU{?Q*VtY2c5T{?-NSYt-P3QcXYYW$XP!U0Z|c6YFYMlr{bTo^eX;wCn-3%( z7;xa!OF1u%e(C3fy$|j`c;V&shXx(m@k-NIHotP|aQBK;O$JKE)6Mp$RsO93YaRCf zCg_W2dO=>@KuyWW=9kpw{0e?~F)KT>Ufn=ORtL#U=7;l)to$ObD{50q@^$j-FL@0L zTjv)Q%I&<-L?8DHC4_3FCe}(!t(sd^QgzRiP|z1m z@yRe`rzc&B=LAS{WW@WeOECs=gKoiN#2-cEp?prsnAg^AlWZqAMR`fiV zEANZbdR#@<6;_5vk$TY6S=#6kdC#pcD-WXEn;C+ct@E=QXjGUL@{0;T2?g@K;Ye9H z!5{wA7hD}oP73+Met*!j+T-NGdeGxkF`2t~oz>^&I@aIq%51DN$>^& z-UM$bIp(Vjk%R>GOo}f##9wt)IN9s*R|zI223x56^aK56H`U^+DvOW$YW%zgMTKpO z3g{U%OexTAH7#xCuEQ6xoA||WA)knP zf0vRpAr)FWs&`qL@P7G;&+@P7t}+wy>1I|dYTc?b-?hrmvIJO})nZba@#rplMEHnj zWwue@{+(lalFM#g9t|;Q{UYHYB}#%}VSxJ+LQ##Ns4zEik?|hQiZb3gF9{*hl?_v~ zQX|1=FtRfkoIb(t-^h53#`sfUV=#CPqcRlMLxjCFcyi@j<#MtwDde>^Aq`4CXug0q z(c;@5N=RVJ<#&N3;$7+BNgpL50jn6vZmwMpx$YupW2g%?g z!a;`#|F@!m)j;d8mIaj$NqO>Rca($C0c2;WRCCtK(tCYlFFHC>ESLK z!M|;mUZ-K5^nX}6FsErwW=d{KW@bhPZR^R*^o0E0zn@=Uy?QiP50e{7O|Rt(C)iUE zjNB9n+AM#mm7W^0jiS}7^VHR=rT@B5os5h+zphTNrmMST<>X|Q@T4hqGBdotoY0%0 zC)Z2hg;eA$zcW-ZO{^%O#d0%K zu84$shB8y;q-5p>Baz^oV5CbVHR|_uXdemYWX*{Lr=-_O7fH$FW|2Ge5?)OR?bJ<6 zQX^e_9en=WP(n#oPB6mlF#q(9J*RibRet$tPvhNk-uqC_KB>E>*FH%L?HO}O-bwj) D<>1Uh diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index a786a4331ac..9ecd64c4cf3 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -628,4 +628,6 @@ export const codiconsLibrary = { strikethrough: register('strikethrough', 0xec64), openInProduct: register('open-in-product', 0xec65), indexZero: register('index-zero', 0xec66), + agent: register('agent', 0xec67), + editCode: register('edit-code', 0xec68), } as const; From aaa334f22a56003f0fdc23ccc2ba606ba6cd1e3c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:40:45 +0200 Subject: [PATCH 1549/4355] SCM - introduce new menu for the repositories view (#272946) --- extensions/git/package.json | 74 ++++++++++++++++++- src/vs/workbench/contrib/scm/browser/menus.ts | 17 +---- .../actions/common/menusExtensionPoint.ts | 10 ++- 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 03013a0f90f..9ff385ce77f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1693,13 +1693,85 @@ "when": "scmProvider == git" } ], - "scm/sourceControl/title": [ + "scm/repositories/title": [ { "command": "git.reopenClosedRepositories", "group": "navigation@1", "when": "git.closedRepositoryCount > 0" } ], + "scm/repository": [ + { + "command": "git.pull", + "group": "1_header@1", + "when": "scmProvider == git" + }, + { + "command": "git.push", + "group": "1_header@2", + "when": "scmProvider == git" + }, + { + "command": "git.clone", + "group": "1_header@3", + "when": "scmProvider == git" + }, + { + "command": "git.checkout", + "group": "1_header@4", + "when": "scmProvider == git" + }, + { + "command": "git.fetch", + "group": "1_header@5", + "when": "scmProvider == git" + }, + { + "submenu": "git.commit", + "group": "2_main@1", + "when": "scmProvider == git" + }, + { + "submenu": "git.changes", + "group": "2_main@2", + "when": "scmProvider == git" + }, + { + "submenu": "git.pullpush", + "group": "2_main@3", + "when": "scmProvider == git" + }, + { + "submenu": "git.branch", + "group": "2_main@4", + "when": "scmProvider == git" + }, + { + "submenu": "git.remotes", + "group": "2_main@5", + "when": "scmProvider == git" + }, + { + "submenu": "git.stash", + "group": "2_main@6", + "when": "scmProvider == git" + }, + { + "submenu": "git.tags", + "group": "2_main@7", + "when": "scmProvider == git" + }, + { + "submenu": "git.worktrees", + "group": "2_main@8", + "when": "scmProvider == git" + }, + { + "command": "git.showOutput", + "group": "3_footer", + "when": "scmProvider == git" + } + ], "scm/sourceControl": [ { "command": "git.close", diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 59a953b6dfa..f4732f57be1 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -5,7 +5,7 @@ import { IAction } from '../../../../base/common/actions.js'; import { equals } from '../../../../base/common/arrays.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { Emitter } from '../../../../base/common/event.js'; import { DisposableStore, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; import './media/scm.css'; import { localize } from '../../../../nls.js'; @@ -325,7 +325,6 @@ export class SCMMenus implements ISCMMenus, IDisposable { readonly titleMenu: SCMTitleMenu; private readonly disposables = new DisposableStore(); - private readonly repositoryMenuDisposables = new DisposableStore(); private readonly menus = new Map void }>(); constructor( @@ -334,20 +333,6 @@ export class SCMMenus implements ISCMMenus, IDisposable { ) { this.titleMenu = instantiationService.createInstance(SCMTitleMenu); scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); - - // Duplicate the `SCMTitle` menu items to the `SCMSourceControlInline` menu. We do this - // so that menu items can be independently hidden/shown in the "Source Control" and the - // "Source Control Repositories" views. - this.disposables.add(Event.runAndSubscribe(MenuRegistry.onDidChangeMenu, e => { - if (e && !e.has(MenuId.SCMTitle)) { - return; - } - - this.repositoryMenuDisposables.clear(); - for (const menuItem of MenuRegistry.getMenuItems(MenuId.SCMTitle)) { - this.repositoryMenuDisposables.add(MenuRegistry.appendMenuItem(MenuId.SCMSourceControlInline, menuItem)); - } - })); } private onDidRemoveRepository(repository: ISCMRepository): void { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index f74a85a7d0d..812f5ef7abe 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -149,9 +149,15 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.scmSourceControl', "The Source Control menu") }, { - key: 'scm/sourceControl/title', + key: 'scm/repositories/title', id: MenuId.SCMSourceControlTitle, - description: localize('menus.scmSourceControlTitle', "The Source Control title menu"), + description: localize('menus.scmSourceControlTitle', "The Source Control Repositories title menu"), + proposed: 'contribSourceControlTitleMenu' + }, + { + key: 'scm/repository', + id: MenuId.SCMSourceControlInline, + description: localize('menus.scmSourceControlInline', "The Source Control repository menu"), proposed: 'contribSourceControlTitleMenu' }, { From f089aeec00a580aa78ed31a65901dd292ed08a64 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 23 Oct 2025 13:41:00 -0700 Subject: [PATCH 1550/4355] wip --- .../chatEditingCheckpointTimeline.ts | 2 +- .../chatEditingCheckpointTimelineImpl.ts | 83 ++++++++++++++----- .../chatEditingModifiedDocumentEntry.ts | 4 + .../chatEditingModifiedNotebookEntry.ts | 8 ++ .../chatEditing/chatEditingOperations.ts | 23 +++-- .../browser/chatEditing/chatEditingSession.ts | 37 ++++----- .../chatEditingModifiedNotebookSnapshot.ts | 6 +- .../chatEditingNotebookFileSystemProvider.ts | 14 ++-- .../browser/chatEditingSessionStorage.test.ts | 9 +- .../contrib/notebook/common/notebookCommon.ts | 2 +- 10 files changed, 117 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts index 1a9ef3ce2ac..8a98a060612 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts @@ -35,7 +35,7 @@ export interface IChatEditingCheckpointTimeline { // State reconstruction getContentURIAtStop(requestId: string, fileURI: URI, stopId: string | undefined): URI; - getContentAtStop(requestId: string, contentURI: URI, stopId: string | undefined): string | VSBuffer | undefined; + getContentAtStop(requestId: string, contentURI: URI, stopId: string | undefined): Promise; // Persistence getStateForPersistence(): IChatEditingTimelineState; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index 57ee6dc3445..8643ce63e6e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -5,12 +5,14 @@ import { equals as arraysEqual } from '../../../../../base/common/arrays.js'; import { findLast } from '../../../../../base/common/arraysFind.js'; +import { assertNever } from '../../../../../base/common/assert.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { equals as objectsEqual } from '../../../../../base/common/objects.js'; import { derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableSignalFromEvent, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js'; import { isEqual } from '../../../../../base/common/resources.js'; +import { Mutable } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; @@ -19,16 +21,24 @@ import { TextModel } from '../../../../../editor/common/model/textModel.js'; import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { INotebookTextModel } from '../../../notebook/common/notebookCommon.js'; +import { INotebookEditorModelResolverService } from '../../../notebook/common/notebookEditorModelResolverService.js'; +import { INotebookService } from '../../../notebook/common/notebookService.js'; import { IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js'; import { IChatRequestDisablement } from '../../common/chatModel.js'; import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js'; -import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IFileCreateOperation, IFileDeleteOperation, IFileRenameOperation, IReconstructedFileState } from './chatEditingOperations.js'; +import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IFileCreateOperation, IFileDeleteOperation, IFileRenameOperation, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js'; import { ChatEditingSnapshotTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; +import { createSnapshot as createNotebookSnapshot, restoreSnapshot as restoreNotebookSnapshot } from './notebook/chatEditingModifiedNotebookSnapshot.js'; const START_REQUEST_EPOCH = '$$start'; const STOP_ID_EPOCH_PREFIX = '__epoch_'; +type IReconstructedFileStateWithNotebook = IReconstructedFileNotExistsState | (Mutable & { notebook?: INotebookTextModel }); + + /** * Implementation of the checkpoint-based timeline system. * @@ -105,11 +115,14 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh private readonly _delegate: { setContents(uri: URI, content: string, telemetryInfo: IModifiedEntryTelemetryInfo): Promise; }, + @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, + @INotebookService private readonly _notebookService: INotebookService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @ITextModelService private readonly _textModelService: ITextModelService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); @@ -225,7 +238,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return this._fileBaselines.has(key); } - public getContentAtStop(requestId: string, contentURI: URI, stopId: string | undefined) { + public async getContentAtStop(requestId: string, contentURI: URI, stopId: string | undefined) { let toEpoch: number | undefined; if (stopId?.startsWith(STOP_ID_EPOCH_PREFIX)) { toEpoch = Number(stopId.slice(STOP_ID_EPOCH_PREFIX.length)); @@ -242,13 +255,13 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } - const baseline = this._findBestBaselineForFile(fileURI, toEpoch, requestId); + const baseline = await this._findBestBaselineForFile(fileURI, toEpoch, requestId); if (!baseline) { return ''; } const operations = this._getFileOperationsInRange(fileURI, baseline.epoch, toEpoch); - const replayed = this._replayOperations(baseline, operations); + const replayed = await this._replayOperations(baseline, operations); return replayed.exists ? replayed.content : undefined; } @@ -256,14 +269,14 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return findLast(this._checkpoints.read(reader), c => c.epoch <= epoch); } - private _reconstructFileState(uri: URI, targetEpoch: number): IReconstructedFileState { + private async _reconstructFileState(uri: URI, targetEpoch: number): Promise { const targetCheckpoint = this._getCheckpointBeforeEpoch(targetEpoch); if (!targetCheckpoint) { throw new Error(`Checkpoint for epoch ${targetEpoch} not found`); } // Find the most appropriate baseline for this file - const baseline = this._findBestBaselineForFile(uri, targetEpoch, targetCheckpoint.requestId || ''); + const baseline = await this._findBestBaselineForFile(uri, targetEpoch, targetCheckpoint.requestId || ''); if (!baseline) { // File doesn't exist at this checkpoint return { @@ -355,7 +368,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh // Reconstruct content for each file that needs it for (const uri of filesToReconstruct) { - const reconstructedState = this._reconstructFileState(uri, targetEpoch); + const reconstructedState = await this._reconstructFileState(uri, targetEpoch); if (reconstructedState.exists) { this._delegate.setContents(reconstructedState.uri, reconstructedState.content, reconstructedState.telemetryInfo); } @@ -367,7 +380,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return `${uri.toString()}::${requestId}`; } - private _findBestBaselineForFile(uri: URI, epoch: number, requestId: string): IFileBaseline | undefined { + private async _findBestBaselineForFile(uri: URI, epoch: number, requestId: string): Promise { // First, iterate backwards through operations before the target checkpoint // to see if the file was created/re-created more recently than any baseline @@ -393,14 +406,14 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh // If the file was renamed to this URI, use its old contents as the baseline if (operation.type === FileOperationType.Rename && isEqual(operation.newUri, uri)) { - const prev = this._findBestBaselineForFile(operation.oldUri, operation.epoch, operation.requestId); + const prev = await this._findBestBaselineForFile(operation.oldUri, operation.epoch, operation.requestId); if (!prev) { return undefined; } const operations = this._getFileOperationsInRange(operation.oldUri, prev.epoch, operation.epoch); - const replayed = this._replayOperations(prev, operations); + const replayed = await this._replayOperations(prev, operations); return { uri: uri, epoch: operation.epoch, @@ -433,32 +446,60 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh ).sort((a, b) => a.epoch - b.epoch); } - private _replayOperations(baseline: IFileBaseline, operations: readonly FileOperation[]): IReconstructedFileState { - let currentState: IReconstructedFileState = { + private async _replayOperations(baseline: IFileBaseline, operations: readonly FileOperation[]): Promise { + let currentState: IReconstructedFileStateWithNotebook = { exists: true, content: baseline.content, uri: baseline.uri, - telemetryInfo: baseline.telemetryInfo + telemetryInfo: baseline.telemetryInfo, }; + if (baseline.notebookViewType) { + currentState.notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(baseline.notebookViewType); + if (baseline.content) { + restoreNotebookSnapshot(currentState.notebook, baseline.content); + } + } + for (const operation of operations) { - currentState = this._applyOperationToState(currentState, operation, baseline.telemetryInfo); + currentState = await this._applyOperationToState(currentState, operation, baseline.telemetryInfo); + } + + if (currentState.exists && currentState.notebook) { + const info = await this._notebookService.withNotebookDataProvider(currentState.notebook.viewType); + currentState.content = createNotebookSnapshot(currentState.notebook, info.serializer.options, this._configurationService); + currentState.notebook.dispose(); } return currentState; } - private _applyOperationToState(state: IReconstructedFileState, operation: FileOperation, telemetryInfo: IModifiedEntryTelemetryInfo): IReconstructedFileState { + private async _applyOperationToState(state: IReconstructedFileStateWithNotebook, operation: FileOperation, telemetryInfo: IModifiedEntryTelemetryInfo): Promise { switch (operation.type) { - case FileOperationType.Create: + case FileOperationType.Create: { + let notebook: INotebookTextModel | undefined; + if (operation.notebookViewType) { + notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(operation.notebookViewType); + if (operation.initialContent) { + restoreNotebookSnapshot(notebook, operation.initialContent); + } + } + return { exists: true, content: operation.initialContent, uri: operation.uri, telemetryInfo, + notebookViewType: operation.notebookViewType, + notebook, }; + } case FileOperationType.Delete: + if (state.exists && state.notebook) { + state.notebook.dispose(); + } + return { exists: false, uri: operation.uri @@ -482,16 +523,18 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }; case FileOperationType.NotebookEdit: - // For notebook edits, we'll need to implement proper notebook cell edit application - // For now, return the current state unchanged - // TODO: Implement proper notebook edit application using notebook models if (!state.exists || !state.content) { throw new Error('Cannot apply notebook edits to non-existent file'); } + if (!state.notebook) { + throw new Error('Cannot apply notebook edits to non-notebook file'); + } + + state.notebook.applyEdits(operation.cellEdits.slice(), true, undefined, () => undefined, undefined); return state; default: - throw new Error(`Unknown operation type: ${(operation as any).type}`); + assertNever(operation); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 848fb6eacee..017ee6bf7f6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -194,6 +194,10 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie }; } + public getCurrentContents() { + return this.modifiedModel.getValue(); + } + public override hasModificationAt(location: Location): boolean { return location.uri.toString() === this.modifiedModel.uri.toString() && this._textModelChangeService.hasHunkAt(location.range); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index 3aa69d9d900..67abc764faa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -90,6 +90,10 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie return this._cellsDiffInfo; } + get viewType() { + return this.modifiedModel.viewType; + } + /** * List of Cell URIs that are edited, * Will be cleared once all edits have been accepted. @@ -918,6 +922,10 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } } + public getCurrentSnapshot() { + return createSnapshot(this.modifiedModel, this.transientOptions, this.configurationService); + } + override createSnapshot(requestId: string | undefined, undoStop: string | undefined): ISnapshotEntry { return { resource: this.modifiedURI, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts index ddd156521ea..f31d51af6f9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts @@ -32,6 +32,7 @@ export interface IFileOperation { export interface IFileCreateOperation extends IFileOperation { readonly type: FileOperationType.Create; readonly initialContent: string; + readonly notebookViewType?: string; } /** @@ -81,20 +82,26 @@ export interface IFileBaseline { readonly content: string; readonly epoch: number; readonly telemetryInfo: IModifiedEntryTelemetryInfo; + readonly notebookViewType?: string; } -/** - * The reconstructed state of a file at a specific checkpoint - */ -export type IReconstructedFileState = { - readonly exists: false; - readonly uri: URI; -} | { +export interface IReconstructedFileExistsState { readonly exists: true; readonly content: string; readonly uri: URI; readonly telemetryInfo: IModifiedEntryTelemetryInfo; -}; + readonly notebookViewType?: string; +} + +export interface IReconstructedFileNotExistsState { + readonly exists: false; + readonly uri: URI; +} + +/** + * The reconstructed state of a file at a specific checkpoint + */ +export type IReconstructedFileState = IReconstructedFileNotExistsState | IReconstructedFileExistsState; /** * Checkpoint represents a stable state that can be navigated to diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 0786a211fc0..40ccdc8e6fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -221,13 +221,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._timeline.createCheckpoint(requestId, undoStop, label); } - public getSnapshotContents(requestId: string, uri: URI, stopId: string | undefined): Promise { - const content = this._timeline.getContentAtStop(requestId, uri, stopId); - return Promise.resolve(typeof content === 'string' ? VSBuffer.fromString(content) : content); + public async getSnapshotContents(requestId: string, uri: URI, stopId: string | undefined): Promise { + const content = await this._timeline.getContentAtStop(requestId, uri, stopId); + return typeof content === 'string' ? VSBuffer.fromString(content) : content; } public async getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise { - const content = this._timeline.getContentAtStop(requestId, snapshotUri, undoStop); + const content = await this._timeline.getContentAtStop(requestId, snapshotUri, undoStop); if (!content) { return null; } @@ -408,20 +408,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio await this._timeline.redoToNextCheckpoint(); } - private async _getFileContent(uri: URI): Promise { - try { - const ref = await this._textModelService.createModelReference(uri); - try { - return ref.object.textEditorModel.getValue(); - } finally { - ref.dispose(); - } - } catch (error) { - // File doesn't exist, return empty content - return ''; - } - } - private async _recordEditOperations(resource: URI, edits: (TextEdit | ICellEditOperation)[], responseModel: IChatResponseModel): Promise { // Determine if these are text edits or notebook edits const isNotebookEdits = edits.length > 0 && 'cell' in edits[0]; @@ -429,7 +415,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio if (isNotebookEdits) { // Record notebook edit operation const notebookEdits = edits as ICellEditOperation[]; - this._timeline.recordFileOperation({ type: FileOperationType.NotebookEdit, uri: resource, @@ -440,7 +425,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } else { // Record text edit operation const textEdits = edits as TextEdit[]; - this._timeline.recordFileOperation({ type: FileOperationType.TextEdit, uri: resource, @@ -456,13 +440,24 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio // Record file baseline if this is the first edit for this file in this request if (!this._timeline.hasFileBaseline(resource, responseModel.requestId)) { - const content = await this._getFileContent(resource); + let content: string; + let notebookViewType: string | undefined; + if (entry instanceof ChatEditingModifiedNotebookEntry) { + content = entry.getCurrentSnapshot(); + notebookViewType = entry.viewType; + } else if (entry instanceof ChatEditingModifiedDocumentEntry) { + content = entry.getCurrentContents(); + } else { + throw new Error(`unknown entry type for ${resource}`); + } + this._timeline.recordFileBaseline({ uri: resource, requestId: responseModel.requestId, content, epoch: this._timeline.incrementEpoch(), telemetryInfo: entry.telemetryInfo, + notebookViewType, }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookSnapshot.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookSnapshot.ts index 112875ab2f9..e7a763f7b21 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookSnapshot.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookSnapshot.ts @@ -10,7 +10,7 @@ import { IConfigurationService } from '../../../../../../platform/configuration/ import { SnapshotContext } from '../../../../../services/workingCopy/common/fileWorkingCopy.js'; import { NotebookCellTextModel } from '../../../../notebook/common/model/notebookCellTextModel.js'; import { NotebookTextModel } from '../../../../notebook/common/model/notebookTextModel.js'; -import { CellEditType, ICellDto2, ICellEditOperation, IOutputItemDto, NotebookData, NotebookSetting, TransientOptions } from '../../../../notebook/common/notebookCommon.js'; +import { CellEditType, ICellDto2, ICellEditOperation, INotebookTextModel, IOutputItemDto, NotebookData, NotebookSetting, TransientOptions } from '../../../../notebook/common/notebookCommon.js'; const BufferMarker = 'ArrayBuffer-4f56482b-5a03-49ba-8356-210d3b0c1c3d'; @@ -30,12 +30,12 @@ export function parseNotebookSnapshotFileURI(resource: URI): ChatEditingSnapshot return { sessionId: data.sessionId ?? '', requestId: data.requestId ?? '', undoStop: data.undoStop ?? '', viewType: data.viewType }; } -export function createSnapshot(notebook: NotebookTextModel, transientOptions: TransientOptions | undefined, outputSizeConfig: IConfigurationService | number): string { +export function createSnapshot(notebook: INotebookTextModel, transientOptions: TransientOptions | undefined, outputSizeConfig: IConfigurationService | number): string { const outputSizeLimit = (typeof outputSizeConfig === 'number' ? outputSizeConfig : outputSizeConfig.getValue(NotebookSetting.outputBackupSizeLimit)) * 1024; return serializeSnapshot(notebook.createSnapshot({ context: SnapshotContext.Backup, outputSizeLimit, transientOptions }), transientOptions); } -export function restoreSnapshot(notebook: NotebookTextModel, snapshot: string): void { +export function restoreSnapshot(notebook: INotebookTextModel, snapshot: string): void { try { const { transientOptions, data } = deserializeSnapshot(snapshot); notebook.restoreSnapshot(data, transientOptions); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts index 885797824de..0918eeba8d8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts @@ -13,9 +13,8 @@ import { URI } from '../../../../../../base/common/uri.js'; import { FileSystemProviderCapabilities, FileType, IFileChange, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, IFileService, IFileSystemProvider, IFileWriteOptions, IStat, IWatchOptions } from '../../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContribution } from '../../../../../common/contributions.js'; -import { INotebookService } from '../../../../notebook/common/notebookService.js'; import { IChatEditingService } from '../../../common/chatEditingService.js'; -import { ChatEditingNotebookSnapshotScheme, deserializeSnapshot } from './chatEditingModifiedNotebookSnapshot.js'; +import { ChatEditingNotebookSnapshotScheme } from './chatEditingModifiedNotebookSnapshot.js'; export class ChatEditingNotebookFileSystemProviderContrib extends Disposable implements IWorkbenchContribution { @@ -47,9 +46,8 @@ export class ChatEditingNotebookFileSystemProvider implements IFileSystemProvide }; } - constructor( - @IChatEditingService private readonly _chatEditingService: IChatEditingService, - @INotebookService private readonly notebookService: INotebookService) { } + constructor(@IChatEditingService private readonly _chatEditingService: IChatEditingService) { } + readonly onDidChangeCapabilities = Event.None; readonly onDidChangeFile: Event = Event.None; watch(_resource: URI, _opts: IWatchOptions): IDisposable { @@ -91,14 +89,12 @@ export class ChatEditingNotebookFileSystemProvider implements IFileSystemProvide if (!session || !queryData.requestId) { throw new Error('File not found, session not found'); } - const snapshotEntry = session.getSnapshot(queryData.requestId, queryData.undoStop || undefined, resource); + const snapshotEntry = await session.getSnapshotContents(queryData.requestId, resource, queryData.undoStop || undefined); if (!snapshotEntry) { throw new Error('File not found, snapshot not found'); } - const { data } = deserializeSnapshot(snapshotEntry.current); - const { serializer } = await this.notebookService.withNotebookDataProvider(queryData.viewType); - return serializer.notebookToData(data).then(s => s.buffer); + return snapshotEntry.buffer; } writeFile?(__resource: URI, _content: Uint8Array, _opts: IFileWriteOptions): Promise { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts index f93848857d7..1ff93e0ba3b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts @@ -58,17 +58,10 @@ suite('ChatEditingSessionStorage', () => { const initialFileContents = new ResourceMap(); for (let i = 0; i < 10; i++) { initialFileContents.set(URI.file(`/foo${i}.js`), `fileContents${Math.floor(i / 2)}`); } - const r1 = generateUuid(); - const r2 = generateUuid(); return { initialFileContents, - pendingSnapshot: makeStop(undefined, 'd', 'e'), recentSnapshot: makeStop(undefined, 'd', 'e'), - linearHistoryIndex: 3, - linearHistory: [ - { startIndex: 0, requestId: r1, stops: [makeStop(r1, 'a', 'b')] }, - { startIndex: 1, requestId: r2, stops: [makeStop(r2, 'c', 'd'), makeStop(r2, 'd', 'd')] }, - ] + timeline: undefined, }; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e18c0a8b871..53ed20280cb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -292,7 +292,7 @@ export interface INotebookSnapshotOptions { transientOptions?: TransientOptions; } -export interface INotebookTextModel extends INotebookTextModelLike { +export interface INotebookTextModel extends INotebookTextModelLike, IDisposable { readonly notebookType: string; readonly viewType: string; metadata: NotebookDocumentMetadata; From b8da337c6b90d529ee63981212944b0ecd02226d Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:05:04 +0800 Subject: [PATCH 1551/4355] per-model rendering for thinking (#271089) * per model rendering for thinking * cleanup * fix how we handle non-default thinking for gpt-5 * remove duplicate function * working grouped thinking + tool calls * some logic cleanup * make sure non-special tools are still shown * cleanup * fix comments and css * remove leftover css --- .../contrib/chat/browser/chat.contribution.ts | 6 +- .../chatThinkingContentPart.ts | 49 ++++++-- .../contrib/chat/browser/chatListRenderer.ts | 108 ++++++++++++------ .../contrib/chat/browser/media/chat.css | 14 ++- .../contrib/chat/common/constants.ts | 5 +- 5 files changed, 138 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e04e2096b86..226ec95258d 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -684,15 +684,17 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.ThinkingStyle]: { type: 'string', - default: product.quality === 'insider' ? 'fixedScrolling' : 'collapsed', - enum: ['collapsed', 'collapsedPreview', 'expanded', 'none', 'collapsedPerItem', 'fixedScrolling'], + default: 'default', + enum: ['default', 'collapsed', 'collapsedPreview', 'expanded', 'none', 'collapsedPerItem', 'fixedScrolling', 'fixedScrollingTools'], enumDescriptions: [ + nls.localize('chat.agent.thinkingMode.default', "Let VS Code choose a thinking style for each model."), nls.localize('chat.agent.thinkingMode.collapsed', "Thinking parts will be collapsed by default."), nls.localize('chat.agent.thinkingMode.collapsedPreview', "Thinking parts will be expanded first, then collapse once we reach a part that is not thinking."), nls.localize('chat.agent.thinkingMode.expanded', "Thinking parts will be expanded by default."), nls.localize('chat.agent.thinkingMode.none', "Do not show the thinking"), nls.localize('chat.agent.thinkingMode.collapsedPerItem', "Each thinking subsection is individually collapsible and collapsed by default inside the thinking block."), nls.localize('chat.agent.thinkingMode.fixedScrolling', "Show thinking in a fixed-height streaming panel that auto-scrolls; click header to expand to full height."), + nls.localize('chat.agent.thinkingMode.fixedScrollingTools', "Show thinking and certain tool calls in a fixed-height streaming panel that auto-scrolls; click header to expand to full height."), ], description: nls.localize('chat.agent.thinkingStyle', "Controls how thinking is rendered."), tags: ['experimental'], diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index 1f25a906707..b781a6003d0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -7,7 +7,8 @@ import { $, clearNode } from '../../../../../base/browser/dom.js'; import * as dom from '../../../../../base/browser/dom.js'; import { IChatThinkingPart } from '../../common/chatService.js'; import { IChatContentPartRenderContext, IChatContentPart } from './chatContentParts.js'; -import { IChatRendererContent } from '../../common/chatViewModel.js'; +import { IChatRendererContent, isResponseVM } from '../../common/chatViewModel.js'; +import { ThinkingDisplayMode } from '../../common/constants.js'; import { ChatTreeItem } from '../chat.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -34,6 +35,24 @@ function extractTitleFromThinkingContent(content: string): string | undefined { return headerMatch ? headerMatch[1].trim() : undefined; } +/** + * everything => fixedScrolling + * gpt-5-codex => collapsed with special grouping + */ +function resolvePerModelDefaultThinkingStyle(modelId: string | undefined): ThinkingDisplayMode { + if (!modelId) { + return ThinkingDisplayMode.FixedScrolling; + } + const id = modelId.toLowerCase(); + + // TODO @justschen: could have additional styles specific to gemini/codex besides collapased. + if (id.includes('gpt-5-codex')) { + return ThinkingDisplayMode.FixedScrollingTools; + } + + return ThinkingDisplayMode.FixedScrolling; +} + export class ChatThinkingContentPart extends ChatCollapsibleContentPart implements IChatContentPart { public readonly codeblocks: undefined; public readonly codeblocksPartId: undefined; @@ -70,19 +89,29 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen this.id = content.id; - const mode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? 'none'; - this.perItemCollapsedMode = mode === 'collapsedPerItem'; - this.fixedScrollingMode = mode === 'fixedScrolling'; + const configuredMode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? ThinkingDisplayMode.None; + let effectiveMode = configuredMode; + if (configuredMode === ThinkingDisplayMode.Default) { + let modelId: string | undefined; + if (isResponseVM(context.element)) { + modelId = context.element.model.request?.modelId; + } + effectiveMode = resolvePerModelDefaultThinkingStyle(modelId); + } + + this.perItemCollapsedMode = effectiveMode === ThinkingDisplayMode.CollapsedPerItem; + this.fixedScrollingMode = effectiveMode === ThinkingDisplayMode.FixedScrolling || effectiveMode === ThinkingDisplayMode.FixedScrollingTools; this.currentTitle = extractedTitle; if (extractedTitle !== this.defaultTitle) { this.lastExtractedTitle = extractedTitle; } this.currentThinkingValue = this.parseContent(initialText); - if (mode === 'expanded' || mode === 'collapsedPreview' || mode === 'fixedScrolling') { - this.setExpanded(true); - } else if (mode === 'collapsed') { + + if (effectiveMode === ThinkingDisplayMode.Collapsed) { this.setExpanded(false); + } else { + this.setExpanded(true); } if (this.perItemCollapsedMode) { @@ -309,6 +338,12 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen public appendItem(content: HTMLElement): void { this.wrapper.appendChild(content); + if (this.fixedScrollingMode) { + const container = this.fixedScrollViewport ?? this.textContainer; + if (container) { + container.scrollTop = container.scrollHeight; + } + } } // makes a new text container. when we update, we now update this container. diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 1e3cd408e41..bada2d56d69 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -58,7 +58,7 @@ import { IChatRequestVariableEntry } from '../common/chatVariableEntries.js'; import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM } from '../common/chatViewModel.js'; import { getNWords } from '../common/chatWordCounter.js'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ThinkingDisplayMode } from '../common/constants.js'; import { MarkUnhelpfulActionId } from './actions/chatTitleActions.js'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidgetService } from './chat.js'; import { ChatAgentHover, getChatAgentHoverOptions } from './chatAgentHover.js'; @@ -770,11 +770,15 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer part.kind !== 'markdownContent' || part.content.value.trim().length > 0); - const thinkingStyle = this.configService.getValue('chat.agent.thinkingStyle'); - if (thinkingStyle === 'fixedScrolling' && lastPart?.kind === 'thinking') { + const thinkingStyle = this.configService.getValue('chat.agent.thinkingStyle'); + if ((thinkingStyle === ThinkingDisplayMode.FixedScrolling || thinkingStyle === ThinkingDisplayMode.Default) && lastPart?.kind === 'thinking') { return false; } + if (this.shouldCombineThinking(element)) { + return lastPart?.kind !== 'thinking' && lastPart?.kind !== 'toolInvocation' && lastPart?.kind !== 'prepareToolInvocation'; + } + if ( !lastPart || lastPart.kind === 'references' || lastPart.kind === 'thinking' || @@ -789,6 +793,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.agent.thinkingStyle'); + const defaultCodex = thinkingStyle === ThinkingDisplayMode.Default && element.model.request?.modelId?.toLowerCase().includes('codex'); + return thinkingStyle === ThinkingDisplayMode.FixedScrollingTools || !!defaultCodex; + } + private getChatFileChangesSummaryPart(element: IChatResponseViewModel): IChatChangesSummaryPart | undefined { if (!this.shouldShowFileChangesSummary(element)) { return undefined; @@ -1018,6 +1028,19 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.agent.thinkingStyle') !== 'none'; + return this.configService.getValue('chat.agent.thinkingStyle') !== ThinkingDisplayMode.None; } private getDataForProgressiveRender(element: IChatResponseViewModel) { @@ -1183,27 +1206,40 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer([ - // 'copilot_searchCodebase', - // 'copilot_searchWorkspaceSymbols', - // 'copilot_listCodeUsages', - // 'copilot_think', - // 'copilot_findFiles', - // 'copilot_findTextInFiles', - // 'copilot_readFile', - // 'copilot_listDirectory', - // 'copilot_getChangedFiles', - // ]); - // const isSpecialTool = specialToolIds.has(part.toolId); - // return isSpecialTool && this._hasHitThinking; - // } - - // return false; - // } + private shouldPinPart(part: IChatRendererContent, element?: IChatResponseViewModel): boolean { + + if ((part.kind === 'toolInvocation' || part.kind === 'toolInvocationSerialized') && element) { + // Explicit set of tools that should be pinned when there has been thinking + const specialToolIds = new Set([ + 'copilot_searchCodebase', + 'copilot_searchWorkspaceSymbols', + 'copilot_listCodeUsages', + 'copilot_think', + 'copilot_findFiles', + 'copilot_findTextInFiles', + 'copilot_readFile', + 'copilot_listDirectory', + 'copilot_getChangedFiles', + ]); + const isSpecialTool = specialToolIds.has(part.toolId); + return isSpecialTool; + } + + return part.kind === 'prepareToolInvocation'; + } + + private finalizeCurrentThinkingPart(): void { + if (!this._currentThinkingPart) { + return; + } + const style = this.configService.getValue('chat.agent.thinkingStyle'); + if (style === ThinkingDisplayMode.CollapsedPreview) { + this._currentThinkingPart.collapseContent(); + } + this._currentThinkingPart.finalizeTitleIfDefault(); + this._currentThinkingPart.resetId(); + this._currentThinkingPart = undefined; + } private renderChatContentPart(content: IChatRendererContent, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart | undefined { try { @@ -1216,14 +1252,11 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.kind === other.kind); } - // we got a non-thinking and non-thinking tool content part - if (this._currentThinkingPart && content.kind !== 'working' && content.kind !== 'thinking' && !this._streamingThinking) { - if (this.configService.getValue('chat.agent.thinkingStyle') === 'collapsedPreview') { - this._currentThinkingPart.collapseContent(); - } - this._currentThinkingPart?.finalizeTitleIfDefault(); - this._currentThinkingPart?.resetId(); - this._currentThinkingPart = undefined; + const allowToolStreaming = (isResponseVM(context.element) && this.shouldCombineThinking(context.element)); + const isThinkingContent = content.kind === 'working' || content.kind === 'thinking'; + const isToolStreamingContent = allowToolStreaming && this.shouldPinPart(content, isResponseVM(context.element) ? context.element : undefined); + if (this._currentThinkingPart && !this._streamingThinking && !isThinkingContent && !isToolStreamingContent) { + this.finalizeCurrentThinkingPart(); } } @@ -1430,6 +1463,15 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Fri, 24 Oct 2025 06:15:33 +0900 Subject: [PATCH 1552/4355] Center badge items in explorer find (#238757) --- src/vs/base/browser/ui/iconLabel/iconlabel.css | 2 ++ .../workbench/contrib/files/browser/media/explorerviewlet.css | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index f520d6e8bf9..a1316d90558 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -46,6 +46,8 @@ overflow: hidden; text-overflow: ellipsis; flex: 1; + display: flex; + align-items: center; } .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name { diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index a08c3754676..d34a939637c 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -63,7 +63,9 @@ } .explorer-folders-view .monaco-list-row[aria-expanded="false"] .explorer-item.highlight-badge .monaco-count-badge { - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; } .explorer-folders-view .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { From e495480063909c94e39c823cd5c019e3f079e727 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:36:36 +0200 Subject: [PATCH 1553/4355] SCM - repository label takes precedence over actions (#272992) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 10 ++++++++-- .../contrib/scm/browser/scmRepositoryRenderer.ts | 4 ---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 0f5654434be..fc4fe30ed76 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -50,7 +50,8 @@ } .scm-view .scm-provider > .monaco-icon-label { - flex: 1; + min-width: 0; + flex: 0 1 auto; } .scm-view .scm-provider .monaco-highlighted-label { @@ -60,8 +61,13 @@ } .scm-view .scm-provider > .actions { + min-width: 24px; + flex: 1 10000 auto; overflow: hidden; - justify-content: flex-end; +} + +.scm-view .scm-provider > .actions .actions-container { + justify-content: end; } /** diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 505c482f476..6e1a7a2164a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -141,10 +141,6 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer Date: Thu, 23 Oct 2025 14:43:00 -0700 Subject: [PATCH 1554/4355] Change min body size of chat sessions view pane (#272993) Change min body size --- .../contrib/chat/browser/chatSessions/view/sessionsViewPane.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 519b9574af9..62dfcf4f98b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -103,6 +103,7 @@ export class SessionsViewPane extends ViewPane { @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); + this.minimumBodySize = 44; // Listen for changes in the provider if it's a LocalChatSessionsProvider if (provider instanceof LocalChatSessionsProvider) { From fb6055cb587de8c95dbd6e1619edaf77ec987ced Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:16:46 -0700 Subject: [PATCH 1555/4355] Use URIs to identify sessions (#272013) * Start work on using URIs to identify sessions This starts switching us from id+types to use uris to identify chat sessions. I already added very basic support for this but in this next step I think we need a breaking change so that we can avoid adding more and more hacks and complexity to support this * More work * Add docs * Fix indent * Bump api * Fix quotes * Fix tests * fix compile errors for mockChatSessionService * Fix mockChatSessionService * Update getChatSessionType * Make sure resolver also registers any initial schemes * add ChatSessionService.hasAnySessionOptions() --------- Co-authored-by: Peng Lyu Co-authored-by: Josh Spicer <23246594+joshspicer@users.noreply.github.com> --- src/vs/base/common/network.ts | 6 +- .../common/extensionsApiProposals.ts | 2 +- .../api/browser/mainThreadChatSessions.ts | 143 +++++++---------- .../workbench/api/common/extHost.api.impl.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 34 ++-- .../api/common/extHostChatAgents2.ts | 7 +- .../api/common/extHostChatSessions.ts | 102 ++++++------ .../browser/mainThreadChatSessions.test.ts | 65 ++++---- .../browser/actions/chatSessionActions.ts | 4 +- .../contrib/chat/browser/chat.contribution.ts | 40 ++++- .../contrib/chat/browser/chatEditor.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 4 +- .../contrib/chat/browser/chatInputPart.ts | 10 +- .../chat/browser/chatSessions.contribution.ts | 149 +++++++++--------- .../chat/browser/chatSessions/common.ts | 20 +-- .../chatSessions/view/sessionsTreeRenderer.ts | 2 +- .../contrib/chat/browser/chatViewPane.ts | 4 +- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 34 ++-- .../chat/common/chatSessionsService.ts | 36 +++-- .../workbench/contrib/chat/common/chatUri.ts | 3 + .../test/common/mockChatSessionsService.ts | 69 ++++---- .../vscode.proposed.chatSessionsProvider.d.ts | 68 +++----- 23 files changed, 393 insertions(+), 423 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 64ebe94abd9..3b85c248ef2 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -87,7 +87,11 @@ export namespace Schemas { /** Scheme used for the chat input part */ export const vscodeChatInput = 'chatSessionInput'; - /** Scheme for chat session content */ + /** + * Scheme for chat session content + * + * @deprecated + * */ export const vscodeChatSession = 'vscode-chat-session'; /** diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 79e8cfa17c0..3a9295dcd16 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -62,7 +62,7 @@ const _allApiProposals = { }, chatSessionsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts', - version: 2 + version: 3 }, chatStatusItem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatStatusItem.d.ts', diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index 5e0587c5560..bee6b357b78 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -8,34 +8,26 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Emitter } from '../../../base/common/event.js'; import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; import { revive } from '../../../base/common/marshalling.js'; import { autorun, IObservable, observableValue } from '../../../base/common/observable.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; import { ILogService } from '../../../platform/log/common/log.js'; -import { ChatViewId } from '../../contrib/chat/browser/chat.js'; -import { ChatViewPane } from '../../contrib/chat/browser/chatViewPane.js'; import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; import { ChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; -import { ChatSessionUri } from '../../contrib/chat/common/chatUri.js'; -import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IEditorGroup, IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { IViewsService } from '../../services/views/common/viewsService.js'; import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { ExtHostChatSessionsShape, ExtHostContext, IChatProgressDto, IChatSessionHistoryItemDto, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; export class ObservableChatSession extends Disposable implements ChatSession { - static generateSessionKey(providerHandle: number, sessionId: string) { - return `${providerHandle}_${sessionId}`; - } - readonly sessionId: string; readonly sessionResource: URI; readonly providerHandle: number; readonly history: Array; @@ -69,10 +61,6 @@ export class ObservableChatSession extends Disposable implements ChatSession { private readonly _logService: ILogService; private readonly _dialogService: IDialogService; - get sessionKey(): string { - return ObservableChatSession.generateSessionKey(this.providerHandle, this.sessionId); - } - get progressObs(): IObservable { return this._progressObservable; } @@ -82,7 +70,6 @@ export class ObservableChatSession extends Disposable implements ChatSession { } constructor( - id: string, resource: URI, providerHandle: number, proxy: ExtHostChatSessionsShape, @@ -91,7 +78,6 @@ export class ObservableChatSession extends Disposable implements ChatSession { ) { super(); - this.sessionId = id; this.sessionResource = resource; this.providerHandle = providerHandle; this.history = []; @@ -111,7 +97,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { private async _doInitializeContent(token: CancellationToken): Promise { try { const sessionContent = await raceCancellationError( - this._proxy.$provideChatSessionContent(this._providerHandle, this.sessionId, this.sessionResource, token), + this._proxy.$provideChatSessionContent(this._providerHandle, this.sessionResource, token), token ); @@ -147,10 +133,10 @@ export class ObservableChatSession extends Disposable implements ChatSession { this.interruptActiveResponseCallback = async () => { const confirmInterrupt = () => { if (this._disposalPending) { - this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionId); + this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionResource); this._disposalPending = false; } - this._proxy.$interruptChatSessionActiveResponse(this._providerHandle, this.sessionId, 'ongoing'); + this._proxy.$interruptChatSessionActiveResponse(this._providerHandle, this.sessionResource, 'ongoing'); return true; }; @@ -177,7 +163,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { this._interruptionWasCanceled = true; // User canceled interruption - cancel the deferred disposal if (this._disposalPending) { - this._logService.info(`Canceling deferred disposal for session ${this.sessionId} (user canceled interruption)`); + this._logService.info(`Canceling deferred disposal for session ${this.sessionResource} (user canceled interruption)`); this._disposalPending = false; } return false; @@ -215,7 +201,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { }); try { - await this._proxy.$invokeChatSessionRequestHandler(this._providerHandle, this.sessionId, request, history, token); + await this._proxy.$invokeChatSessionRequestHandler(this._providerHandle, this.sessionResource, request, history, token); // Only mark as complete if there's no active response callback // Sessions with active response callbacks should only complete when explicitly told to via handleProgressComplete @@ -246,7 +232,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { const hasAnyCapability = hasActiveResponse || hasRequestHandler; for (const [requestId, chunks] of this._pendingProgressChunks) { - this._logService.debug(`Processing ${chunks.length} pending progress chunks for session ${this.sessionId}, requestId ${requestId}`); + this._logService.debug(`Processing ${chunks.length} pending progress chunks for session ${this.sessionResource}, requestId ${requestId}`); this._addProgress(chunks); } this._pendingProgressChunks.clear(); @@ -257,7 +243,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { } } catch (error) { - this._logService.error(`Failed to initialize chat session ${this.sessionId}:`, error); + this._logService.error(`Failed to initialize chat session ${this.sessionResource}:`, error); throw error; } } @@ -270,7 +256,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { if (!this._isInitialized) { const existing = this._pendingProgressChunks.get(requestId) || []; this._pendingProgressChunks.set(requestId, [...existing, ...progress]); - this._logService.debug(`Queuing ${progress.length} progress chunks for session ${this.sessionId}, requestId ${requestId} (session not initialized)`); + this._logService.debug(`Queuing ${progress.length} progress chunks for session ${this.sessionResource}, requestId ${requestId} (session not initialized)`); return; } @@ -318,7 +304,7 @@ export class ObservableChatSession extends Disposable implements ChatSession { // The actual disposal will happen in the interruption callback based on user's choice } else { // No active response callback or user already canceled interruption - dispose immediately - this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionId); + this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionResource); } super.dispose(); } @@ -333,8 +319,8 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat private readonly _contentProvidersRegistrations = this._register(new DisposableMap()); private readonly _sessionTypeToHandle = new Map(); - private readonly _activeSessions = new Map(); - private readonly _sessionDisposables = new Map(); + private readonly _activeSessions = new ResourceMap(); + private readonly _sessionDisposables = new ResourceMap(); private readonly _proxy: ExtHostChatSessionsShape; @@ -345,16 +331,15 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @ILogService private readonly _logService: ILogService, - @IViewsService private readonly _viewsService: IViewsService, ) { super(); this._proxy = this._extHostContext.getProxy(ExtHostContext.ExtHostChatSessions); - this._chatSessionsService.setOptionsChangeCallback(async (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => { + this._chatSessionsService.setOptionsChangeCallback(async (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => { const handle = this._getHandleForSessionType(chatSessionType); if (handle !== undefined) { - await this.notifyOptionsChange(handle, sessionId, updates); + await this.notifyOptionsChange(handle, sessionResource, updates); } }); } @@ -387,15 +372,17 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire(); } - $onDidCommitChatSessionItem(handle: number, original: { id: string; resource: UriComponents }, modified: { id: string; resource: UriComponents }): void { - this._logService.trace(`$onDidCommitChatSessionItem: handle(${handle}), original(${original}), modified(${modified})`); + $onDidCommitChatSessionItem(handle: number, originalComponents: UriComponents, modifiedCompoennts: UriComponents): void { + const originalResource = URI.revive(originalComponents); + const modifiedResource = URI.revive(modifiedCompoennts); + + this._logService.trace(`$onDidCommitChatSessionItem: handle(${handle}), original(${originalResource}), modified(${modifiedResource})`); const chatSessionType = this._itemProvidersRegistrations.get(handle)?.provider.chatSessionType; if (!chatSessionType) { this._logService.error(`No chat session type found for provider handle ${handle}`); return; } - const originalResource = ChatSessionUri.forSession(chatSessionType, original.id); - const modifiedResource = ChatSessionUri.forSession(chatSessionType, modified.id); + const originalEditor = this._editorService.editors.find(editor => editor.resource?.toString() === originalResource.toString()); const contribution = this._chatSessionsService.getAllChatSessionContributions().find(c => c.type === chatSessionType); @@ -421,9 +408,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat if (originalEditor) { // Prefetch the chat session content to make the subsequent editor swap quick this._chatSessionsService.provideChatSessionContent( - chatSessionType, - modified.id, - URI.revive(modified.resource), + URI.revive(modifiedResource), CancellationToken.None, ).then(() => { this._editorService.replaceEditors([{ @@ -476,26 +461,24 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } } - private async _provideChatSessionContent(providerHandle: number, id: string, resource: URI, token: CancellationToken): Promise { - const sessionKey = ObservableChatSession.generateSessionKey(providerHandle, id); - let session = this._activeSessions.get(sessionKey); + private async _provideChatSessionContent(providerHandle: number, sessionResource: URI, token: CancellationToken): Promise { + let session = this._activeSessions.get(sessionResource); if (!session) { session = new ObservableChatSession( - id, - resource, + sessionResource, providerHandle, this._proxy, this._logService, this._dialogService ); - this._activeSessions.set(sessionKey, session); + this._activeSessions.set(sessionResource, session); const disposable = session.onWillDispose(() => { - this._activeSessions.delete(sessionKey); - this._sessionDisposables.get(sessionKey)?.dispose(); - this._sessionDisposables.delete(sessionKey); + this._activeSessions.delete(sessionResource); + this._sessionDisposables.get(sessionResource)?.dispose(); + this._sessionDisposables.delete(sessionResource); }); - this._sessionDisposables.set(sessionKey, disposable); + this._sessionDisposables.set(sessionResource, disposable); } try { @@ -504,7 +487,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat for (const [chatSessionType, handle] of this._sessionTypeToHandle) { if (handle === providerHandle) { for (const [optionId, value] of Object.entries(session.options)) { - this._chatSessionsService.setSessionOption(chatSessionType, id, optionId, value); + this._chatSessionsService.setSessionOption(chatSessionType, sessionResource, optionId, value); } break; } @@ -514,7 +497,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat return session; } catch (error) { session.dispose(); - this._logService.error(`Error providing chat session content for handle ${providerHandle} and id ${id}:`, error); + this._logService.error(`Error providing chat session content for handle ${providerHandle} and resource ${sessionResource.toString()}:`, error); throw error; } } @@ -523,16 +506,16 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._itemProvidersRegistrations.deleteAndDispose(handle); } - $registerChatSessionContentProvider(handle: number, chatSessionType: string): void { + $registerChatSessionContentProvider(handle: number, chatSessionScheme: string): void { const provider: IChatSessionContentProvider = { - provideChatSessionContent: (id, resource, token) => this._provideChatSessionContent(handle, id, resource, token) + provideChatSessionContent: (resource, token) => this._provideChatSessionContent(handle, resource, token) }; - this._sessionTypeToHandle.set(chatSessionType, handle); - this._contentProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(chatSessionType, provider)); + this._sessionTypeToHandle.set(chatSessionScheme, handle); + this._contentProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(chatSessionScheme, provider)); this._proxy.$provideChatSessionProviderOptions(handle, CancellationToken.None).then(options => { if (options?.optionGroups && options.optionGroups.length) { - this._chatSessionsService.setOptionGroupsForSessionType(chatSessionType, handle, options.optionGroups); + this._chatSessionsService.setOptionGroupsForSessionType(chatSessionScheme, handle, options.optionGroups); } }).catch(err => this._logService.error('Error fetching chat session options', err)); } @@ -555,34 +538,34 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } } - async $handleProgressChunk(handle: number, sessionId: string, requestId: string, chunks: (IChatProgressDto | [IChatProgressDto, number])[]): Promise { - const sessionKey = ObservableChatSession.generateSessionKey(handle, sessionId); - const observableSession = this._activeSessions.get(sessionKey); + async $handleProgressChunk(handle: number, sessionResource: UriComponents, requestId: string, chunks: (IChatProgressDto | [IChatProgressDto, number])[]): Promise { + const resource = URI.revive(sessionResource); + const observableSession = this._activeSessions.get(resource); + if (!observableSession) { + this._logService.warn(`No session found for progress chunks: handle ${handle}, sessionResource ${resource}, requestId ${requestId}`); + return; + } const chatProgressParts: IChatProgress[] = chunks.map(chunk => { const [progress] = Array.isArray(chunk) ? chunk : [chunk]; return revive(progress) as IChatProgress; }); - if (observableSession) { - observableSession.handleProgressChunk(requestId, chatProgressParts); - } else { - this._logService.warn(`No session found for progress chunks: handle ${handle}, sessionId ${sessionId}, requestId ${requestId}`); - } + observableSession.handleProgressChunk(requestId, chatProgressParts); } - $handleProgressComplete(handle: number, sessionId: string, requestId: string) { - const sessionKey = ObservableChatSession.generateSessionKey(handle, sessionId); - const observableSession = this._activeSessions.get(sessionKey); - - if (observableSession) { - observableSession.handleProgressComplete(requestId); - } else { - this._logService.warn(`No session found for progress completion: handle ${handle}, sessionId ${sessionId}, requestId ${requestId}`); + $handleProgressComplete(handle: number, sessionResource: UriComponents, requestId: string) { + const resource = URI.revive(sessionResource); + const observableSession = this._activeSessions.get(resource); + if (!observableSession) { + this._logService.warn(`No session found for progress completion: handle ${handle}, sessionResource ${resource}, requestId ${requestId}`); + return; } + + observableSession.handleProgressComplete(requestId); } - $handleAnchorResolve(handle: number, sessionId: string, requestId: string, requestHandle: string, anchor: Dto): void { + $handleAnchorResolve(handle: number, sesssionResource: UriComponents, requestId: string, requestHandle: string, anchor: Dto): void { // throw new Error('Method not implemented.'); } @@ -618,28 +601,14 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat return undefined; } - async $showChatSession(chatSessionType: string, sessionId: string, position: EditorGroupColumn | undefined): Promise { - const sessionUri = ChatSessionUri.forSession(chatSessionType, sessionId); - - if (typeof position === 'undefined') { - const chatPanel = await this._viewsService.openView(ChatViewId); - await chatPanel?.loadSession(sessionUri); - } else { - await this._editorService.openEditor({ - resource: sessionUri, - options: { pinned: true }, - }, position); - } - } - /** * Notify the extension about option changes for a session */ - async notifyOptionsChange(handle: number, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>): Promise { + async notifyOptionsChange(handle: number, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>): Promise { try { - await this._proxy.$provideHandleOptionsChange(handle, sessionId, updates, CancellationToken.None); + await this._proxy.$provideHandleOptionsChange(handle, sessionResource, updates, CancellationToken.None); } catch (error) { - this._logService.error(`Error notifying extension about options change for handle ${handle}, sessionId ${sessionId}:`, error); + this._logService.error(`Error notifying extension about options change for handle ${handle}, sessionResource ${sessionResource}:`, error); } } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d3482f995c8..27eaa67856f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -969,10 +969,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatStatusItem'); return extHostChatStatus.createChatStatusItem(extension, id); }, - showChatSession: (chatSessionType: string, sessionId: string, options?: vscode.ChatSessionShowOptions) => { - checkProposedApiEnabled(extension, 'chatSessionsProvider'); - return extHostChatSessions.showChatSession(extension, chatSessionType, sessionId, options); - }, }; // namespace: workspace @@ -1530,9 +1526,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatSessionsProvider'); return extHostChatSessions.registerChatSessionItemProvider(extension, chatSessionType, provider); }, - registerChatSessionContentProvider(chatSessionType: string, provider: vscode.ChatSessionContentProvider, chatParticipant: vscode.ChatParticipant, capabilities?: vscode.ChatSessionCapabilities) { + registerChatSessionContentProvider(scheme: string, provider: vscode.ChatSessionContentProvider, chatParticipant: vscode.ChatParticipant, capabilities?: vscode.ChatSessionCapabilities) { checkProposedApiEnabled(extension, 'chatSessionsProvider'); - return extHostChatSessions.registerChatSessionContentProvider(extension, chatSessionType, chatParticipant, provider, capabilities); + return extHostChatSessions.registerChatSessionContentProvider(extension, scheme, chatParticipant, provider, capabilities); }, registerChatOutputRenderer: (viewType: string, renderer: vscode.ChatOutputRenderer) => { checkProposedApiEnabled(extension, 'chatOutputRenderer'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f640cf8e4a5..44cc8e71bbe 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -58,7 +58,7 @@ import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, UserSelectedTo import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js'; import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatProgressHistoryResponseContent, IChatRequestVariableData } from '../../contrib/chat/common/chatModel.js'; -import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatSessionContext, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; +import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { IChatSessionItem, IChatSessionProviderOptionGroup } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; @@ -1388,8 +1388,15 @@ export type IChatAgentHistoryEntryDto = { result: IChatAgentResult; }; +export interface IChatSessionContextDto { + readonly chatSessionType: string; + readonly chatSessionId: string; + readonly chatSessionResource: UriComponents; + readonly isUntitled: boolean; +} + export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContext; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; + $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise; $provideFollowups(request: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; @@ -3203,26 +3210,25 @@ export interface MainThreadChatSessionsShape extends IDisposable { $registerChatSessionItemProvider(handle: number, chatSessionType: string): void; $unregisterChatSessionItemProvider(handle: number): void; $onDidChangeChatSessionItems(handle: number): void; - $onDidCommitChatSessionItem(handle: number, original: { id: string; resource: UriComponents }, modified: { id: string; resource: UriComponents }): void; - $registerChatSessionContentProvider(handle: number, chatSessionType: string): void; + $onDidCommitChatSessionItem(handle: number, original: UriComponents, modified: UriComponents): void; + $registerChatSessionContentProvider(handle: number, chatSessionScheme: string): void; $unregisterChatSessionContentProvider(handle: number): void; - $handleProgressChunk(handle: number, sessionId: string, requestId: string, chunks: (IChatProgressDto | [IChatProgressDto, number])[]): Promise; - $handleAnchorResolve(handle: number, sessionId: string, requestId: string, requestHandle: string, anchor: Dto): void; - $handleProgressComplete(handle: number, sessionId: string, requestId: string): void; - - $showChatSession(chatSessionType: string, sessionId: string, position: EditorGroupColumn | undefined): Promise; + $handleProgressChunk(handle: number, sessionResource: UriComponents, requestId: string, chunks: (IChatProgressDto | [IChatProgressDto, number])[]): Promise; + $handleAnchorResolve(handle: number, sessionResource: UriComponents, requestId: string, requestHandle: string, anchor: Dto): void; + $handleProgressComplete(handle: number, sessionResource: UriComponents, requestId: string): void; } export interface ExtHostChatSessionsShape { $provideChatSessionItems(providerHandle: number, token: CancellationToken): Promise[]>; $provideNewChatSessionItem(providerHandle: number, options: { request: IChatAgentRequest; metadata?: any }, token: CancellationToken): Promise>; - $provideChatSessionContent(providerHandle: number, sessionId: string, sessionResource: UriComponents, token: CancellationToken): Promise; + + $provideChatSessionContent(providerHandle: number, sessionResource: UriComponents, token: CancellationToken): Promise; + $interruptChatSessionActiveResponse(providerHandle: number, sessionResource: UriComponents, requestId: string): Promise; + $disposeChatSessionContent(providerHandle: number, sessionResource: UriComponents): Promise; + $invokeChatSessionRequestHandler(providerHandle: number, sessionResource: UriComponents, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise; $provideChatSessionProviderOptions(providerHandle: number, token: CancellationToken): Promise; - $provideHandleOptionsChange(providerHandle: number, sessionId: string, updates: ReadonlyArray, token: CancellationToken): Promise; - $interruptChatSessionActiveResponse(providerHandle: number, sessionId: string, requestId: string): Promise; - $disposeChatSessionContent(providerHandle: number, sessionId: string): Promise; - $invokeChatSessionRequestHandler(providerHandle: number, id: string, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise; + $provideHandleOptionsChange(providerHandle: number, sessionResource: UriComponents, updates: ReadonlyArray, token: CancellationToken): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9c6ee6d866c..d827de9e1bb 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -22,11 +22,11 @@ import { ILogService } from '../../../platform/log/common/log.js'; import { isChatViewTitleActionContext } from '../../contrib/chat/common/chatActions.js'; import { IChatAgentRequest, IChatAgentResult, IChatAgentResultTimings, UserSelectedTools } from '../../contrib/chat/common/chatAgents.js'; import { IChatRelatedFile, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; -import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatSessionContext, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; +import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; +import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IChatSessionContextDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; import { CommandsConverter, ExtHostCommands } from './extHostCommands.js'; import { ExtHostDiagnostics } from './extHostDiagnostics.js'; import { ExtHostDocuments } from './extHostDocuments.js'; @@ -541,7 +541,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS this._onDidChangeChatRequestTools.fire(request.extRequest); } - async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContext; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { + async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -581,7 +581,6 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS if (context.chatSessionContext) { chatSessionContext = { chatSessionItem: { - id: context.chatSessionContext.chatSessionId, resource: URI.revive(context.chatSessionContext.chatSessionResource), label: context.chatSessionContext.isUntitled ? 'Untitled Session' : 'Session', }, diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 192f9dc7918..f6dd54e1589 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -6,9 +6,12 @@ import type * as vscode from 'vscode'; import { coalesce } from '../../../base/common/arrays.js'; import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; +import { CancellationError } from '../../../base/common/errors.js'; import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; import { revive } from '../../../base/common/marshalling.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; +import { URI, UriComponents } from '../../../base/common/uri.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; @@ -22,8 +25,6 @@ import { ExtHostLanguageModels } from './extHostLanguageModels.js'; import { IExtHostRpcService } from './extHostRpcService.js'; import * as typeConvert from './extHostTypeConverters.js'; import * as extHostTypes from './extHostTypes.js'; -import { URI, UriComponents } from '../../../base/common/uri.js'; -import { ChatSessionUri } from '../../contrib/chat/common/chatUri.js'; class ExtHostChatSession { private _stream: ChatAgentResponseStream; @@ -66,7 +67,19 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }>(); private _nextChatSessionItemProviderHandle = 0; private _nextChatSessionContentProviderHandle = 0; - private readonly _sessionMap: Map = new Map(); + + /** + * Map of uri -> chat session items + * + * TODO: this isn't cleared/updated properly + */ + private readonly _sessionItems = new ResourceMap(); + + /** + * Map of uri -> chat sessions infos + */ + private readonly _extHostChatSessions = new ResourceMap<{ readonly sessionObj: ExtHostChatSession; readonly disposeCts: CancellationTokenSource }>(); + constructor( private readonly commands: ExtHostCommands, @@ -81,7 +94,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio processArgument: (arg) => { if (arg && arg.$mid === MarshalledId.ChatSessionContext) { const id = arg.session.id; - const sessionContent = this._sessionMap.get(id); + const sessionContent = this._sessionItems.get(id); if (sessionContent) { return sessionContent; } else { @@ -109,9 +122,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio if (provider.onDidCommitChatSessionItem) { disposables.add(provider.onDidCommitChatSessionItem((e) => { const { original, modified } = e; - this._proxy.$onDidCommitChatSessionItem(handle, - { id: original.id, resource: original.resource ?? ChatSessionUri.forSession(chatSessionType, original.id) }, - { id: modified.id, resource: modified.resource ?? ChatSessionUri.forSession(chatSessionType, modified.id) }); + this._proxy.$onDidCommitChatSessionItem(handle, original.resource, modified.resource); })); } return { @@ -123,12 +134,12 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }; } - registerChatSessionContentProvider(extension: IExtensionDescription, chatSessionType: string, chatParticipant: vscode.ChatParticipant, provider: vscode.ChatSessionContentProvider, capabilities?: vscode.ChatSessionCapabilities): vscode.Disposable { + registerChatSessionContentProvider(extension: IExtensionDescription, chatSessionScheme: string, chatParticipant: vscode.ChatParticipant, provider: vscode.ChatSessionContentProvider, capabilities?: vscode.ChatSessionCapabilities): vscode.Disposable { const handle = this._nextChatSessionContentProviderHandle++; const disposables = new DisposableStore(); this._chatSessionContentProviders.set(handle, { provider, extension, capabilities, disposable: disposables }); - this._proxy.$registerChatSessionContentProvider(handle, chatSessionType); + this._proxy.$registerChatSessionContentProvider(handle, chatSessionScheme); return new extHostTypes.Disposable(() => { this._chatSessionContentProviders.delete(handle); @@ -137,14 +148,11 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }); } - async showChatSession(_extension: IExtensionDescription, chatSessionType: string, sessionId: string, options: vscode.ChatSessionShowOptions | undefined): Promise { - await this._proxy.$showChatSession(chatSessionType, sessionId, typeConvert.ViewColumn.from(options?.viewColumn)); - } - private convertChatSessionStatus(status: vscode.ChatSessionStatus | undefined): ChatSessionStatus | undefined { if (status === undefined) { return undefined; } + switch (status) { case 0: // vscode.ChatSessionStatus.Failed return ChatSessionStatus.Failed; @@ -159,8 +167,8 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem { return { - id: sessionContent.id, - resource: sessionContent.resource ?? ChatSessionUri.forSession(sessionType, sessionContent.id), + id: sessionContent.resource.toString(), + resource: sessionContent.resource, label: sessionContent.label, description: sessionContent.description ? typeConvert.MarkdownString.from(sessionContent.description) : undefined, status: this.convertChatSessionStatus(sessionContent.status), @@ -199,13 +207,11 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }; const chatSessionItem = await entry.provider.provideNewChatSessionItem(vscodeOptions, token); - if (!chatSessionItem || !chatSessionItem.id) { + if (!chatSessionItem) { throw new Error('Provider did not create session'); } - this._sessionMap.set( - chatSessionItem.id, - chatSessionItem - ); + + this._sessionItems.set(chatSessionItem.resource, chatSessionItem); return this.convertChatSessionItem(entry.sessionType, chatSessionItem); } catch (error) { this._logService.error(`Error creating chat session: ${error}`); @@ -227,29 +233,28 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio const response: IChatSessionItem[] = []; for (const sessionContent of sessions) { - if (sessionContent.id) { - this._sessionMap.set( - sessionContent.id, - sessionContent - ); - response.push(this.convertChatSessionItem(entry.sessionType, sessionContent)); - } + this._sessionItems.set(sessionContent.resource, sessionContent); + response.push(this.convertChatSessionItem(entry.sessionType, sessionContent)); } return response; } - private readonly _extHostChatSessions = new Map(); - - async $provideChatSessionContent(handle: number, id: string, resource: UriComponents, token: CancellationToken): Promise { + async $provideChatSessionContent(handle: number, sessionResourceComponents: UriComponents, token: CancellationToken): Promise { const provider = this._chatSessionContentProviders.get(handle); if (!provider) { throw new Error(`No provider for handle ${handle}`); } - const session = await provider.provider.provideChatSessionContent(id, token); + const sessionResource = URI.revive(sessionResourceComponents); + + const session = await provider.provider.provideChatSessionContent(sessionResource, token); + if (token.isCancellationRequested) { + throw new CancellationError(); + } const sessionDisposables = new DisposableStore(); const sessionId = ExtHostChatSessions._sessionHandlePool++; + const id = sessionResource.toString(); const chatSession = new ExtHostChatSession(session, provider.extension, { sessionId: `${id}.${sessionId}`, requestId: 'ongoing', @@ -259,27 +264,27 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio location: ChatAgentLocation.Chat, }, { $handleProgressChunk: (requestId, chunks) => { - return this._proxy.$handleProgressChunk(handle, id, requestId, chunks); + return this._proxy.$handleProgressChunk(handle, sessionResource, requestId, chunks); }, $handleAnchorResolve: (requestId, requestHandle, anchor) => { - this._proxy.$handleAnchorResolve(handle, id, requestId, requestHandle, anchor); + this._proxy.$handleAnchorResolve(handle, sessionResource, requestId, requestHandle, anchor); }, }, this.commands.converter, sessionDisposables); const disposeCts = sessionDisposables.add(new CancellationTokenSource()); - this._extHostChatSessions.set(`${handle}_${id}`, { sessionObj: chatSession, disposeCts }); + this._extHostChatSessions.set(sessionResource, { sessionObj: chatSession, disposeCts }); // Call activeResponseCallback immediately for best user experience if (session.activeResponseCallback) { Promise.resolve(session.activeResponseCallback(chatSession.activeResponseStream.apiObject, disposeCts.token)).finally(() => { // complete - this._proxy.$handleProgressComplete(handle, id, 'ongoing'); + this._proxy.$handleProgressComplete(handle, sessionResource, 'ongoing'); }); } const { capabilities } = provider; return { id: sessionId + '', - resource: URI.revive(resource), + resource: URI.revive(sessionResource), hasActiveResponseCallback: !!session.activeResponseCallback, hasRequestHandler: !!session.requestHandler, supportsInterruption: !!capabilities?.supportsInterruptions, @@ -294,7 +299,8 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }; } - async $provideHandleOptionsChange(handle: number, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>, token: CancellationToken): Promise { + async $provideHandleOptionsChange(handle: number, sessionResourceComponents: UriComponents, updates: ReadonlyArray<{ optionId: string; value: string | undefined }>, token: CancellationToken): Promise { + const sessionResource = URI.revive(sessionResourceComponents); const provider = this._chatSessionContentProviders.get(handle); if (!provider) { this._logService.warn(`No provider for handle ${handle}`); @@ -307,9 +313,9 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } try { - await provider.provider.provideHandleOptionsChange(sessionId, updates, token); + await provider.provider.provideHandleOptionsChange(sessionResource, updates, token); } catch (error) { - this._logService.error(`Error calling provideHandleOptionsChange for handle ${handle}, sessionId ${sessionId}:`, error); + this._logService.error(`Error calling provideHandleOptionsChange for handle ${handle}, sessionResource ${sessionResource}:`, error); } } @@ -339,27 +345,25 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } } - async $interruptChatSessionActiveResponse(providerHandle: number, sessionId: string, requestId: string): Promise { - const key = `${providerHandle}_${sessionId}`; - const entry = this._extHostChatSessions.get(key); + async $interruptChatSessionActiveResponse(providerHandle: number, sessionResource: UriComponents, requestId: string): Promise { + const entry = this._extHostChatSessions.get(URI.revive(sessionResource)); entry?.disposeCts.cancel(); } - async $disposeChatSessionContent(providerHandle: number, sessionId: string): Promise { - const key = `${providerHandle}_${sessionId}`; - const entry = this._extHostChatSessions.get(key); + async $disposeChatSessionContent(providerHandle: number, sessionResource: UriComponents): Promise { + const entry = this._extHostChatSessions.get(URI.revive(sessionResource)); if (!entry) { - this._logService.warn(`No chat session found for ID: ${key}`); + this._logService.warn(`No chat session found for resource: ${sessionResource}`); return; } entry.disposeCts.cancel(); entry.sessionObj.sessionDisposables.dispose(); - this._extHostChatSessions.delete(key); + this._extHostChatSessions.delete(URI.revive(sessionResource)); } - async $invokeChatSessionRequestHandler(handle: number, id: string, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise { - const entry = this._extHostChatSessions.get(`${handle}_${id}`); + async $invokeChatSessionRequestHandler(handle: number, sessionResource: UriComponents, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise { + const entry = this._extHostChatSessions.get(URI.revive(sessionResource)); if (!entry || !entry.sessionObj.session.requestHandler) { return {}; } diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 9145bb7d931..d0992ca3851 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -82,7 +82,7 @@ suite('ObservableChatSession', function () { async function createInitializedSession(sessionContent: any, sessionId = 'test-id'): Promise { const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService); + const session = new ObservableChatSession(resource, 1, proxy, logService, dialogService); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); await session.initialize(CancellationToken.None); return session; @@ -91,9 +91,8 @@ suite('ObservableChatSession', function () { test('constructor creates session with proper initial state', function () { const sessionId = 'test-id'; const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); + const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); - assert.strictEqual(session.sessionId, 'test-id'); assert.strictEqual(session.providerHandle, 1); assert.deepStrictEqual(session.history, []); assert.ok(session.progressObs); @@ -107,7 +106,7 @@ suite('ObservableChatSession', function () { test('session queues progress before initialization and processes it after', async function () { const sessionId = 'test-id'; const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); + const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const progress1: IChatProgress = { kind: 'progressMessage', content: { value: 'Hello', isTrusted: false } }; const progress2: IChatProgress = { kind: 'progressMessage', content: { value: 'World', isTrusted: false } }; @@ -158,7 +157,7 @@ suite('ObservableChatSession', function () { test('initialization is idempotent and returns same promise', async function () { const sessionId = 'test-id'; const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); + const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const sessionContent = createSessionContent(); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); @@ -235,7 +234,7 @@ suite('ObservableChatSession', function () { await session.requestHandler!(request, progressCallback, [], CancellationToken.None); - assert.ok((proxy.$invokeChatSessionRequestHandler as sinon.SinonStub).calledOnceWith(1, 'test-id', request, [], CancellationToken.None)); + assert.ok((proxy.$invokeChatSessionRequestHandler as sinon.SinonStubbedMember).calledOnceWith(1, session.sessionResource, request, [], CancellationToken.None)); }); test('request handler forwards progress updates to external callback', async function () { @@ -286,7 +285,7 @@ suite('ObservableChatSession', function () { test('dispose properly cleans up resources and notifies listeners', function () { const sessionId = 'test-id'; const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = disposables.add(new ObservableChatSession(sessionId, resource, 1, proxy, logService, dialogService)); + const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); let disposeEventFired = false; const disposable = session.onWillDispose(() => { @@ -296,22 +295,11 @@ suite('ObservableChatSession', function () { session.dispose(); assert.ok(disposeEventFired); - assert.ok((proxy.$disposeChatSessionContent as sinon.SinonStub).calledOnceWith(1, 'test-id')); + assert.ok((proxy.$disposeChatSessionContent as sinon.SinonStubbedMember).calledOnceWith(1, resource)); disposable.dispose(); }); - test('session key generation is consistent', function () { - const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); - const session = disposables.add(new ObservableChatSession(sessionId, resource, 42, proxy, logService, dialogService)); - - assert.strictEqual(session.sessionKey, '42_test-id'); - assert.strictEqual(ObservableChatSession.generateSessionKey(42, 'test-id'), '42_test-id'); - - session.dispose(); - }); - test('session with multiple request/response pairs in history', async function () { const sessionHistory = [ { type: 'request', prompt: 'First question' }, @@ -436,7 +424,8 @@ suite('MainThreadChatSessions', function () { }); test('provideChatSessionContent creates and initializes session', async function () { - mainThread.$registerChatSessionContentProvider(1, 'test-type'); + const sessionScheme = 'test-session-type'; + mainThread.$registerChatSessionContentProvider(1, sessionScheme); const sessionContent = { id: 'test-session', @@ -445,15 +434,14 @@ suite('MainThreadChatSessions', function () { hasRequestHandler: false }; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); + const resource = URI.parse(`${sessionScheme}:/test-session`); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session1 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None); + const session1 = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None); assert.ok(session1); - assert.strictEqual(session1.sessionId, 'test-session'); - const session2 = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None); + const session2 = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None); assert.strictEqual(session1, session2); assert.ok((proxy.$provideChatSessionContent as sinon.SinonStub).calledOnce); @@ -461,7 +449,9 @@ suite('MainThreadChatSessions', function () { }); test('$handleProgressChunk routes to correct session', async function () { - mainThread.$registerChatSessionContentProvider(1, 'test-type'); + const sessionScheme = 'test-session-type'; + + mainThread.$registerChatSessionContentProvider(1, sessionScheme); const sessionContent = { id: 'test-session', @@ -472,11 +462,11 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${sessionScheme}:/test-session`); + const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; - await mainThread.$handleProgressChunk(1, 'test-session', 'req1', [progressDto]); + await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); assert.strictEqual(session.progressObs.get().length, 1); assert.strictEqual(session.progressObs.get()[0].kind, 'progressMessage'); @@ -485,7 +475,8 @@ suite('MainThreadChatSessions', function () { }); test('$handleProgressComplete marks session complete', async function () { - mainThread.$registerChatSessionContentProvider(1, 'test-type'); + const sessionScheme = 'test-session-type'; + mainThread.$registerChatSessionContentProvider(1, sessionScheme); const sessionContent = { id: 'test-session', @@ -496,12 +487,12 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/test-session`); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'test-session', resource, CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${sessionScheme}:/test-session`); + const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; - await mainThread.$handleProgressChunk(1, 'test-session', 'req1', [progressDto]); - mainThread.$handleProgressComplete(1, 'test-session', 'req1'); + await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); + mainThread.$handleProgressComplete(1, resource, 'req1'); assert.strictEqual(session.isCompleteObs.get(), true); @@ -509,7 +500,8 @@ suite('MainThreadChatSessions', function () { }); test('integration with multiple request/response pairs', async function () { - mainThread.$registerChatSessionContentProvider(1, 'test-type'); + const sessionScheme = 'test-session-type'; + mainThread.$registerChatSessionContentProvider(1, sessionScheme); const sessionContent = { id: 'multi-turn-session', @@ -525,12 +517,11 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test-type/multi-turn-session`); - const session = await chatSessionsService.provideChatSessionContent('test-type', 'multi-turn-session', resource, CancellationToken.None) as ObservableChatSession; + const resource = URI.parse(`${sessionScheme}:/multi-turn-session`); + const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; // Verify the session loaded correctly assert.ok(session); - assert.strictEqual(session.sessionId, 'multi-turn-session'); assert.strictEqual(session.history.length, 4); // Verify all history items are correctly loaded diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 796beb79aee..db4d47ca0f5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -75,7 +75,7 @@ export class RenameChatSessionAction extends Action2 { try { // Find the chat sessions view and trigger inline rename mode // This is similar to how file renaming works in the explorer - await chatSessionsService.setEditableSession(sessionId, { + await chatSessionsService.setEditableSession(context.session.resource, { validationMessage: (value: string) => { if (!value || value.trim().length === 0) { return { content: localize('renameSession.emptyName', "Name cannot be empty"), severity: Severity.Error }; @@ -101,7 +101,7 @@ export class RenameChatSessionAction extends Action2 { ); } } - await chatSessionsService.setEditableSession(sessionId, null); + await chatSessionsService.setEditableSession(context.session.resource, null); } }); } catch (error) { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 226ec95258d..64ad4ad4632 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -6,7 +6,7 @@ import { timeout } from '../../../../base/common/async.js'; import { Event } from '../../../../base/common/event.js'; import { MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { PolicyCategory } from '../../../../base/common/policy.js'; @@ -41,6 +41,7 @@ import { ChatModeService, IChatModeService } from '../common/chatModes.js'; import { ChatResponseResourceFileSystemProvider } from '../common/chatResponseResourceFileSystemProvider.js'; import { IChatService } from '../common/chatService.js'; import { ChatService } from '../common/chatServiceImpl.js'; +import { IChatSessionsService } from '../common/chatSessionsService.js'; import { ChatSlashCommandService, IChatSlashCommandService } from '../common/chatSlashCommands.js'; import { ChatTodoListService, IChatTodoListService } from '../common/chatTodoListService.js'; import { ChatTransferService, IChatTransferService } from '../common/chatTransferService.js'; @@ -82,6 +83,7 @@ import { ChatSessionsGettingStartedAction, DeleteChatSessionAction, OpenChatSess import { registerChatTitleActions } from './actions/chatTitleActions.js'; import { registerChatToolActions } from './actions/chatToolActions.js'; import { ChatTransferContribution } from './actions/chatTransfer.js'; +import './agentSessions/agentSessionsView.js'; import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService, IQuickChatService } from './chat.js'; import { ChatAccessibilityService } from './chatAccessibilityService.js'; import './chatAttachmentModel.js'; @@ -120,7 +122,6 @@ import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesCon import { LanguageModelToolsService, globalAutoApproveDescription } from './languageModelToolsService.js'; import './promptSyntax/promptCodingAgentActionContribution.js'; import './promptSyntax/promptToolsCodeLensProvider.js'; -import './agentSessions/agentSessionsView.js'; import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; @@ -762,14 +763,34 @@ class ChatResolverContribution extends Disposable { static readonly ID = 'workbench.contrib.chatResolver'; + private readonly _editorRegistrations = this._register(new DisposableMap()); + constructor( - @IEditorResolverService editorResolverService: IEditorResolverService, - @IInstantiationService instantiationService: IInstantiationService, + @IChatSessionsService chatSessionsService: IChatSessionsService, + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); - this._register(editorResolverService.registerEditor( - `{${Schemas.vscodeChatEditor},${Schemas.vscodeChatSession}}:**/**`, + this._registerEditor(Schemas.vscodeChatEditor); + this._registerEditor(Schemas.vscodeChatSession); + + this._register(chatSessionsService.onDidChangeContentProviderSchemes((e) => { + for (const scheme of e.added) { + this._registerEditor(scheme); + } + for (const scheme of e.removed) { + this._editorRegistrations.deleteAndDispose(scheme); + } + })); + + for (const scheme of chatSessionsService.getContentProviderSchemes()) { + this._registerEditor(scheme); + } + } + + private _registerEditor(scheme: string): void { + this._editorRegistrations.set(scheme, this.editorResolverService.registerEditor(`${scheme}:**/**`, { id: ChatEditorInput.EditorID, label: nls.localize('chat', "Chat"), @@ -777,11 +798,14 @@ class ChatResolverContribution extends Disposable { }, { singlePerResource: true, - canSupportResource: resource => resource.scheme === Schemas.vscodeChatEditor || resource.scheme === Schemas.vscodeChatSession, + canSupportResource: resource => resource.scheme === scheme, }, { createEditorInput: ({ resource, options }) => { - return { editor: instantiationService.createInstance(ChatEditorInput, resource, options as IChatEditorOptions), options }; + return { + editor: this.instantiationService.createInstance(ChatEditorInput, resource, options as IChatEditorOptions), + options + }; } } )); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index e353947423a..576b2fabec1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -200,7 +200,7 @@ export class ChatEditor extends EditorPane { if (chatSessionType !== 'local') { try { - await raceCancellationError(this.chatSessionsService.canResolveContentProvider(chatSessionType), token); + await raceCancellationError(this.chatSessionsService.canResolveChatSession(input.resource), token); const contributions = this.chatSessionsService.getAllChatSessionContributions(); const contribution = contributions.find(c => c.type === chatSessionType); if (contribution) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index ca272425830..1ce03b15450 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -73,8 +73,6 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler if (!parsed || typeof parsed !== 'number') { throw new Error('Invalid chat URI'); } - } else if (resource.scheme !== Schemas.vscodeChatSession) { - throw new Error('Invalid chat URI'); } this.sessionId = (options.target && 'sessionId' in options.target) ? @@ -269,7 +267,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const searchParams = new URLSearchParams(this.resource.query); const chatSessionType = searchParams.get('chatSessionType'); const inputType = chatSessionType ?? this.resource.authority; - if (this.resource.scheme === Schemas.vscodeChatSession) { + if (this.resource.scheme !== Schemas.vscodeChatEditor) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (typeof this.sessionId === 'string') { this.model = await this.chatService.getOrRestoreSession(this.sessionId) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index e42bfe32ee5..1d41e1380cb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -662,7 +662,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (!ctx) { continue; } - if (!this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroup.id)) { + if (!this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroup.id)) { // This session does not have a value to contribute for this option group continue; } @@ -682,7 +682,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.getOrCreateOptionEmitter(optionGroup.id).fire(option); this.chatSessionsService.notifySessionOptionsChange( ctx.chatSessionType, - ctx.chatSessionId, + ctx.chatSessionResource, [{ optionId: optionGroup.id, value: option.id }] ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); }, @@ -1139,7 +1139,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return hideAll(); } - if (!this.chatSessionsService.hasAnySessionOptions(ctx.chatSessionType, ctx.chatSessionId)) { + if (!this.chatSessionsService.hasAnySessionOptions(ctx.chatSessionResource)) { return hideAll(); } @@ -1167,7 +1167,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } for (const [optionGroupId] of this.chatSessionPickerWidgets.entries()) { - const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); + const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroupId); if (currentOption) { const optionGroup = optionGroups.find(g => g.id === optionGroupId); if (optionGroup) { @@ -1212,7 +1212,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionId, optionGroupId); + const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroupId); return optionGroup.items.find(m => m.id === currentOptionId); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index bab7bd993db..8b78f1b82b8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -7,6 +7,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../base/common/map.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -25,7 +26,6 @@ import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; -import { ChatSessionUri } from '../common/chatUri.js'; import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; @@ -184,9 +184,9 @@ class ContributedChatSessionData implements IDisposable { constructor( readonly session: ChatSession, readonly chatSessionType: string, - readonly id: string, + readonly resource: URI, readonly options: Record | undefined, - private readonly onWillDispose: (session: ChatSession, chatSessionType: string, id: string) => void + private readonly onWillDispose: (resource: URI) => void ) { this._optionsCache = new Map(); if (options) { @@ -196,7 +196,7 @@ class ContributedChatSessionData implements IDisposable { } this._disposableStore = new DisposableStore(); this._disposableStore.add(this.session.onWillDispose(() => { - this.onWillDispose(this.session, this.chatSessionType, this.id); + this.onWillDispose(this.resource); })); } @@ -208,21 +208,29 @@ class ContributedChatSessionData implements IDisposable { export class ChatSessionsService extends Disposable implements IChatSessionsService { readonly _serviceBrand: undefined; - private readonly _itemsProviders: Map = new Map(); - private readonly _onDidChangeItemsProviders = this._register(new Emitter()); - readonly onDidChangeItemsProviders: Event = this._onDidChangeItemsProviders.event; - private readonly _contentProviders: Map = new Map(); - private readonly _contributions: Map = new Map(); + private readonly _itemsProviders: Map = new Map(); + private readonly _contentProviders: Map = new Map(); + private readonly _contributions: Map = new Map(); private readonly _alternativeIdMap: Map = new Map(); private readonly _disposableStores: Map = new Map(); private readonly _contextKeys = new Set(); + + private readonly _onDidChangeItemsProviders = this._register(new Emitter()); + readonly onDidChangeItemsProviders: Event = this._onDidChangeItemsProviders.event; + private readonly _onDidChangeSessionItems = this._register(new Emitter()); readonly onDidChangeSessionItems: Event = this._onDidChangeSessionItems.event; + private readonly _onDidChangeAvailability = this._register(new Emitter()); readonly onDidChangeAvailability: Event = this._onDidChangeAvailability.event; + private readonly _onDidChangeInProgress = this._register(new Emitter()); public get onDidChangeInProgress() { return this._onDidChangeInProgress.event; } + + private readonly _onDidChangeContentProviderSchemes = this._register(new Emitter<{ readonly added: string[]; readonly removed: string[] }>()); + public get onDidChangeContentProviderSchemes() { return this._onDidChangeContentProviderSchemes.event; } + private readonly inProgressMap: Map = new Map(); private readonly _sessionTypeOptions: Map = new Map(); private readonly _sessionTypeIcons: Map = new Map(); @@ -231,6 +239,9 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private readonly _sessionTypeWelcomeTips: Map = new Map(); private readonly _sessionTypeInputPlaceholders: Map = new Map(); + private readonly _sessions = new ResourceMap(); + private readonly _editableSessions = new ResourceMap(); + constructor( @ILogService private readonly _logService: ILogService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @@ -503,11 +514,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ fallback: localize('chatEditorContributionName', "{0}", contribution.displayName), } }; - const untitledId = `untitled-${generateUuid()}`; - await editorService.openEditor({ - resource: ChatSessionUri.forSession(type, untitledId), - options, + const resource = URI.from({ + scheme: type, + path: `/untitled-${generateUuid()}`, }); + await editorService.openEditor({ resource, options }); } catch (e) { logService.error(`Failed to open new '${type}' chat session editor`, e); } @@ -558,10 +569,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private _disposeSessionsForContribution(contributionId: string): void { // Find and dispose all sessions that belong to this contribution - const sessionsToDispose: string[] = []; - for (const [sessionKey, sessionData] of this._sessions) { + const sessionsToDispose: URI[] = []; + for (const [sessionResource, sessionData] of this._sessions) { if (sessionData.chatSessionType === contributionId) { - sessionsToDispose.push(sessionKey); + sessionsToDispose.push(sessionResource); } } @@ -641,25 +652,20 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return this._itemsProviders.has(chatViewType); } - async canResolveContentProvider(chatViewType: string) { + async canResolveChatSession(chatSessionResource: URI) { await this._extensionService.whenInstalledExtensionsRegistered(); - const resolvedType = this._resolveToPrimaryType(chatViewType); - if (resolvedType) { - chatViewType = resolvedType; - } - - const contribution = this._contributions.get(chatViewType); + const resolvedType = this._resolveToPrimaryType(chatSessionResource.scheme) || chatSessionResource.scheme; + const contribution = this._contributions.get(resolvedType); if (contribution && !this._isContributionAvailable(contribution)) { return false; } - if (this._contentProviders.has(chatViewType)) { + if (this._contentProviders.has(chatSessionResource.scheme)) { return true; } - await this._extensionService.activateByEvent(`onChatSession:${chatViewType}`); - - return this._contentProviders.has(chatViewType); + await this._extensionService.activateByEvent(`onChatSession:${chatSessionResource.scheme}`); + return this._contentProviders.has(chatSessionResource.scheme); } public async provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { @@ -709,11 +715,19 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable { + if (this._contentProviders.has(chatSessionType)) { + throw new Error(`Content provider for ${chatSessionType} is already registered.`); + } + this._contentProviders.set(chatSessionType, provider); + this._onDidChangeContentProviderSchemes.fire({ added: [chatSessionType], removed: [] }); + return { dispose: () => { this._contentProviders.delete(chatSessionType); + this._onDidChangeContentProviderSchemes.fire({ added: [], removed: [chatSessionType] }); + // Remove all sessions that were created by this provider for (const [key, session] of this._sessions) { if (session.chatSessionType === chatSessionType) { @@ -725,11 +739,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ }; } - private readonly _sessions = new Map(); - - // Editable session support - private readonly _editableSessions = new Map(); - /** * Creates a new chat session by delegating to the appropriate provider * @param chatSessionType The type of chat session provider to use @@ -760,77 +769,65 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return chatSessionItem; } - public async provideChatSessionContent(chatSessionType: string, id: string, resource: URI, token: CancellationToken): Promise { - if (!(await this.canResolveContentProvider(chatSessionType))) { - throw Error(`Can not find provider for ${chatSessionType}`); - } - - const resolvedType = this._resolveToPrimaryType(chatSessionType); - if (resolvedType) { - chatSessionType = resolvedType; + public async provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + if (!(await this.canResolveChatSession(sessionResource))) { + throw Error(`Can not find provider for ${sessionResource}`); } - - - const provider = this._contentProviders.get(chatSessionType); + const resolvedType = this._resolveToPrimaryType(sessionResource.scheme) || sessionResource.scheme; + const provider = this._contentProviders.get(resolvedType); if (!provider) { - throw Error(`Can not find provider for ${chatSessionType}`); + throw Error(`Can not find provider for ${sessionResource}`); } - const sessionKey = `${chatSessionType}_${id}`; - const existingSessionData = this._sessions.get(sessionKey); + const existingSessionData = this._sessions.get(sessionResource); if (existingSessionData) { return existingSessionData.session; } - const session = await provider.provideChatSessionContent(id, resource, token); - const sessionData = new ContributedChatSessionData(session, chatSessionType, id, session.options, this._onWillDisposeSession.bind(this)); + const session = await provider.provideChatSessionContent(sessionResource, token); + const sessionData = new ContributedChatSessionData(session, sessionResource.scheme, sessionResource, session.options, this._onWillDisposeSession.bind(this)); - this._sessions.set(sessionKey, sessionData); + this._sessions.set(sessionResource, sessionData); return session; } - private _onWillDisposeSession(session: ChatSession, chatSessionType: string, id: string): void { - const sessionKey = `${chatSessionType}_${id}`; - this._sessions.delete(sessionKey); + private _onWillDisposeSession(sessionResource: URI): void { + this._sessions.delete(sessionResource); } - public hasAnySessionOptions(chatSessionType: string, id: string): boolean { - const sessionKey = `${chatSessionType}_${id}`; - const session = this._sessions.get(sessionKey); + public hasAnySessionOptions(resource: URI): boolean { + const session = this._sessions.get(resource); return !!session && !!session.options && Object.keys(session.options).length > 0; } - - public getSessionOption(chatSessionType: string, id: string, optionId: string): string | undefined { - const sessionKey = `${chatSessionType}_${id}`; - const session = this._sessions.get(sessionKey); + public getSessionOption(chatSessionType: string, resource: URI, optionId: string): string | undefined { + const session = this._sessions.get(resource); return session?.getOption(optionId); } - public setSessionOption(chatSessionType: string, id: string, optionId: string, value: string): boolean { - const sessionKey = `${chatSessionType}_${id}`; - const session = this._sessions.get(sessionKey); + public setSessionOption(chatSessionType: string, resource: URI, optionId: string, value: string): boolean { + const session = this._sessions.get(resource); return !!session?.setOption(optionId, value); } // Implementation of editable session methods - public async setEditableSession(sessionId: string, data: IEditableData | null): Promise { + public async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise { if (!data) { - this._editableSessions.delete(sessionId); + this._editableSessions.delete(sessionResource); } else { - this._editableSessions.set(sessionId, data); + this._editableSessions.set(sessionResource, data); } // Trigger refresh of the session views that might need to update their rendering this._onDidChangeSessionItems.fire('local'); } - public getEditableData(sessionId: string): IEditableData | undefined { - return this._editableSessions.get(sessionId); + public getEditableData(sessionResource: URI): IEditableData | undefined { + return this._editableSessions.get(sessionResource); } - public isEditable(sessionId: string): boolean { - return this._editableSessions.has(sessionId); + public isEditable(sessionResource: URI): boolean { + return this._editableSessions.has(sessionResource); } public notifySessionItemsChanged(chatSessionType: string): void { @@ -855,27 +852,27 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return this._sessionTypeOptions.get(chatSessionType); } - private _optionsChangeCallback?: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + private _optionsChangeCallback?: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; /** * Set the callback for notifying extensions about option changes */ - public setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + public setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { this._optionsChangeCallback = callback; } /** * Notify extension about option changes for a session */ - public async notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + public async notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { if (!updates.length) { return; } if (this._optionsChangeCallback) { - await this._optionsChangeCallback(chatSessionType, sessionId, updates); + await this._optionsChangeCallback(chatSessionType, sessionResource, updates); } for (const u of updates) { - this.setSessionOption(chatSessionType, sessionId, u.optionId, u.value); + this.setSessionOption(chatSessionType, sessionResource, u.optionId, u.value); } } @@ -921,6 +918,10 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public getWelcomeTipsForSessionType(chatSessionType: string): string | undefined { return this._sessionTypeWelcomeTips.get(chatSessionType); } + + public getContentProviderSchemes(): string[] { + return Array.from(this._contentProviders.keys()); + } } registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 801aa12a3b7..0e112637713 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -11,7 +11,6 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -49,26 +48,11 @@ export function isChatSession(editor?: EditorInput): boolean { * Returns chat session type from a URI, or 'local' if not specified or cannot be determined. */ export function getChatSessionType(editor: ChatEditorInput): string { - if (!editor.resource) { + if (editor.resource.scheme === Schemas.vscodeChatEditor || editor.resource.scheme === Schemas.vscodeChatSession) { return 'local'; } - const { scheme, query } = editor.resource; - - if (scheme === Schemas.vscodeChatSession) { - const parsed = ChatSessionUri.parse(editor.resource); - if (parsed) { - return parsed.chatSessionType; - } - } - - const sessionTypeFromQuery = new URLSearchParams(query).get('chatSessionType'); - if (sessionTypeFromQuery) { - return sessionTypeFromQuery; - } - - // Default to 'local' for vscode-chat-editor scheme or when type cannot be determined - return 'local'; + return editor.resource.scheme; } /** diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 3ea4e046fd5..2b80c437426 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -200,7 +200,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer c.type === parsed.chatSessionType); if (contribution) { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 34cd1279c58..de81e4ccbf6 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -11,7 +11,7 @@ import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { autorun, autorunSelfDisposable, IObservable, IReader } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { URI } from '../../../../base/common/uri.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { ISelection } from '../../../../editor/common/core/selection.js'; import { Command, Location, TextEdit } from '../../../../editor/common/languages.js'; @@ -938,7 +938,7 @@ export interface IChatService { export interface IChatSessionContext { chatSessionType: string; chatSessionId: string; - chatSessionResource: UriComponents; + chatSessionResource: URI; isUntitled: boolean; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index a17248e0a27..284739f7bea 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -11,6 +11,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../base/common/map.js'; import { revive } from '../../../../base/common/marshalling.js'; import { autorun, derived, IObservable, ObservableMap } from '../../../../base/common/observable.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; @@ -36,7 +37,6 @@ import { IChatSessionsService } from './chatSessionsService.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatTransferService } from './chatTransferService.js'; -import { ChatSessionUri } from './chatUri.js'; import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; import { ChatMessageRole, IChatMessage } from './languageModels.js'; @@ -72,7 +72,7 @@ export class ChatService extends Disposable implements IChatService { declare _serviceBrand: undefined; private readonly _sessionModels = new ObservableMap(); - private readonly _contentProviderSessionModels = new Map>(); + private readonly _contentProviderSessionModels = new Map>(); private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; @@ -81,8 +81,8 @@ export class ChatService extends Disposable implements IChatService { return this._transferredSessionData; } - private readonly _onDidSubmitRequest = this._register(new Emitter<{ chatSessionId: string }>()); - public readonly onDidSubmitRequest: Event<{ chatSessionId: string }> = this._onDidSubmitRequest.event; + private readonly _onDidSubmitRequest = this._register(new Emitter<{ readonly chatSessionId: string }>()); + public readonly onDidSubmitRequest = this._onDidSubmitRequest.event; private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; @@ -452,39 +452,31 @@ export class ChatService extends Disposable implements IChatService { async loadSessionForResource(chatSessionResource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { // TODO: Move this into a new ChatModelService - const parsed = ChatSessionUri.parse(chatSessionResource); - if (!parsed) { - throw new Error('Invalid chat session URI'); - } - const existing = this._contentProviderSessionModels.get(parsed.chatSessionType)?.get(parsed.sessionId); + const existing = this._contentProviderSessionModels.get(chatSessionResource.scheme)?.get(chatSessionResource); if (existing) { return existing.model; } - if (parsed.chatSessionType === 'local') { - return this.getOrRestoreSession(parsed.sessionId); - } - - const chatSessionType = parsed.chatSessionType; - const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, chatSessionResource, CancellationToken.None); + const content = await this.chatSessionService.provideChatSessionContent(chatSessionResource, CancellationToken.None); + const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools const model = this._startSession(undefined, location, true, CancellationToken.None, { canUseTools: false, inputType: chatSessionType }); model.setContributedChatSession({ - chatSessionType, - chatSessionId: parsed.sessionId, + chatSessionType: chatSessionType, + chatSessionId: chatSessionResource.toString(), chatSessionResource, - isUntitled: parsed.sessionId.startsWith('untitled-') //TODO(jospicer) + isUntitled: chatSessionResource.path.startsWith('/untitled-') //TODO(jospicer) }); if (!this._contentProviderSessionModels.has(chatSessionType)) { - this._contentProviderSessionModels.set(chatSessionType, new Map()); + this._contentProviderSessionModels.set(chatSessionType, new ResourceMap()); } const disposables = new DisposableStore(); - this._contentProviderSessionModels.get(chatSessionType)!.set(parsed.sessionId, { model, disposables }); + this._contentProviderSessionModels.get(chatSessionType)!.set(chatSessionResource, { model, disposables }); disposables.add(model.onDidDispose(() => { - this._contentProviderSessionModels?.get(chatSessionType)?.delete(parsed.sessionId); + this._contentProviderSessionModels?.get(chatSessionType)?.delete(chatSessionResource); content.dispose(); })); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index e87eda82afe..faf3836136c 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -91,7 +91,6 @@ export type IChatSessionHistoryItem = { }; export interface ChatSession extends IDisposable { - readonly sessionId: string; readonly sessionResource: URI; readonly onWillDispose: Event; history: Array; @@ -124,7 +123,7 @@ export interface IChatSessionItemProvider { } export interface IChatSessionContentProvider { - provideChatSessionContent(sessionId: string, sessionResource: URI, token: CancellationToken): Promise; + provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; } export interface IChatSessionsService { @@ -159,10 +158,6 @@ export interface IChatSessionsService { reportInProgress(chatSessionType: string, count: number): void; getInProgress(): { displayName: string; count: number }[]; - registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable; - canResolveContentProvider(chatSessionType: string): Promise; - provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise; - // Get available option groups for a session type getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined; @@ -170,27 +165,40 @@ export interface IChatSessionsService { setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void; // Set callback for notifying extensions about option changes - setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void; + setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void; // Notify extension about option changes - notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; + notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; // Editable session support - setEditableSession(sessionId: string, data: IEditableData | null): Promise; - getEditableData(sessionId: string): IEditableData | undefined; - isEditable(sessionId: string): boolean; + setEditableSession(sessionResource: URI, data: IEditableData | null): Promise; + getEditableData(sessionResource: URI): IEditableData | undefined; + isEditable(sessionResource: URI): boolean; // Notify providers about session items changes notifySessionItemsChanged(chatSessionType: string): void; - hasAnySessionOptions(chatSessionType: string, sessionId: string): boolean; - getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined; - setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean; + // #region Content provider support + + // TODO: Split into separate service? + readonly onDidChangeContentProviderSchemes: Event<{ readonly added: string[]; readonly removed: string[] }>; + + getContentProviderSchemes(): string[]; + + registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable; + canResolveChatSession(chatSessionResource: URI): Promise; + provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; + + hasAnySessionOptions(resource: URI): boolean; + getSessionOption(chatSessionType: string, sessionResource: URI, optionId: string): string | undefined; + setSessionOption(chatSessionType: string, sessionResource: URI, optionId: string, value: string): boolean; /** * Get the capabilities for a specific session type */ getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined; + + // #endregion } export const IChatSessionsService = createDecorator('chatSessionsService'); diff --git a/src/vs/workbench/contrib/chat/common/chatUri.ts b/src/vs/workbench/contrib/chat/common/chatUri.ts index 132303190ff..c945748770e 100644 --- a/src/vs/workbench/contrib/chat/common/chatUri.ts +++ b/src/vs/workbench/contrib/chat/common/chatUri.ts @@ -12,6 +12,9 @@ export type ChatSessionIdentifier = { readonly sessionId: string; }; +/** + * @deprecated + */ export namespace ChatSessionUri { export const scheme = Schemas.vscodeChatSession; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 3c03129bec6..a497c9cc0a7 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -6,11 +6,12 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Emitter } from '../../../../../base/common/event.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../../base/common/map.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; +import { IEditableData } from '../../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/chatAgents.js'; import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js'; -import { IEditableData } from '../../../../common/views.js'; export class MockChatSessionsService implements IChatSessionsService { _serviceBrand: undefined; @@ -27,12 +28,15 @@ export class MockChatSessionsService implements IChatSessionsService { private readonly _onDidChangeInProgress = new Emitter(); readonly onDidChangeInProgress = this._onDidChangeInProgress.event; + private readonly _onDidChangeContentProviderSchemes = new Emitter<{ readonly added: string[]; readonly removed: string[] }>(); + readonly onDidChangeContentProviderSchemes = this._onDidChangeContentProviderSchemes.event; + private providers = new Map(); private contentProviders = new Map(); private contributions: IChatSessionsExtensionPoint[] = []; private optionGroups = new Map(); - private sessionOptions = new Map>(); - private editableData = new Map(); + private sessionOptions = new ResourceMap>(); + private editableData = new ResourceMap(); private inProgress = new Map(); // For testing: allow triggering events @@ -125,6 +129,7 @@ export class MockChatSessionsService implements IChatSessionsService { registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable { this.contentProviders.set(chatSessionType, provider); + this._onDidChangeContentProviderSchemes.fire({ added: [chatSessionType], removed: [] }); return { dispose: () => { this.contentProviders.delete(chatSessionType); @@ -136,12 +141,16 @@ export class MockChatSessionsService implements IChatSessionsService { return this.contentProviders.has(chatSessionType); } - async provideChatSessionContent(chatSessionType: string, id: string, sessionResource: URI, token: CancellationToken): Promise { - const provider = this.contentProviders.get(chatSessionType); + async provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + const provider = this.contentProviders.get(sessionResource.scheme); if (!provider) { - throw new Error(`No content provider for ${chatSessionType}`); + throw new Error(`No content provider for ${sessionResource.scheme}`); } - return provider.provideChatSessionContent(id, sessionResource, token); + return provider.provideChatSessionContent(sessionResource, token); + } + + async canResolveChatSession(chatSessionResource: URI): Promise { + return this.contentProviders.has(chatSessionResource.scheme); } getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined { @@ -156,59 +165,59 @@ export class MockChatSessionsService implements IChatSessionsService { } } - private optionsChangeCallback?: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + private optionsChangeCallback?: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; - setOptionsChangeCallback(callback: (chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { this.optionsChangeCallback = callback; } - async notifySessionOptionsChange(chatSessionType: string, sessionId: string, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + async notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { if (this.optionsChangeCallback) { - await this.optionsChangeCallback(chatSessionType, sessionId, updates); + await this.optionsChangeCallback(chatSessionType, sessionResource, updates); } } - async setEditableSession(sessionId: string, data: IEditableData | null): Promise { + async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise { if (data) { - this.editableData.set(sessionId, data); + this.editableData.set(sessionResource, data); } else { - this.editableData.delete(sessionId); + this.editableData.delete(sessionResource); } } - getEditableData(sessionId: string): IEditableData | undefined { - return this.editableData.get(sessionId); + getEditableData(sessionResource: URI): IEditableData | undefined { + return this.editableData.get(sessionResource); } - isEditable(sessionId: string): boolean { - return this.editableData.has(sessionId); + isEditable(sessionResource: URI): boolean { + return this.editableData.has(sessionResource); } notifySessionItemsChanged(chatSessionType: string): void { this._onDidChangeSessionItems.fire(chatSessionType); } - getSessionOption(chatSessionType: string, sessionId: string, optionId: string): string | undefined { - const sessionKey = `${chatSessionType}:${sessionId}`; - return this.sessionOptions.get(sessionKey)?.get(optionId); + getSessionOption(chatSessionType: string, sessionResource: URI, optionId: string): string | undefined { + return this.sessionOptions.get(sessionResource)?.get(optionId); } - setSessionOption(chatSessionType: string, sessionId: string, optionId: string, value: string): boolean { - const sessionKey = `${chatSessionType}:${sessionId}`; - if (!this.sessionOptions.has(sessionKey)) { - this.sessionOptions.set(sessionKey, new Map()); + setSessionOption(chatSessionType: string, sessionResource: URI, optionId: string, value: string): boolean { + if (!this.sessionOptions.has(sessionResource)) { + this.sessionOptions.set(sessionResource, new Map()); } - this.sessionOptions.get(sessionKey)!.set(optionId, value); + this.sessionOptions.get(sessionResource)!.set(optionId, value); return true; } - hasAnySessionOptions(chatSessionType: string, sessionId: string): boolean { - const sessionKey = `${chatSessionType}:${sessionId}`; - const options = this.sessionOptions.get(sessionKey); - return options !== undefined && options.size > 0; + hasAnySessionOptions(resource: URI): boolean { + return this.sessionOptions.has(resource) && this.sessionOptions.get(resource)!.size > 0; } getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined { return this.contributions.find(c => c.type === chatSessionType)?.capabilities; } + + getContentProviderSchemes(): string[] { + return Array.from(this.contentProviders.keys()); + } } diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index e40a444b9c4..3e7c795fbe5 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 2 +// version: 3 declare module 'vscode' { /** @@ -35,6 +35,14 @@ declare module 'vscode' { */ readonly onDidChangeChatSessionItems: Event; + /** + * Provides a list of chat sessions. + */ + // TODO: Do we need a flag to try auth if needed? + provideChatSessionItems(token: CancellationToken): ProviderResult; + + // #region Unstable parts of API + /** * Event that the provider can fire to signal that the current (original) chat session should be replaced with a new (modified) chat session. * The UI can use this information to gracefully migrate the user to the new session. @@ -61,27 +69,16 @@ declare module 'vscode' { metadata?: any; }, token: CancellationToken): ProviderResult; - /** - * Provides a list of chat sessions. - */ - // TODO: Do we need a flag to try auth if needed? - provideChatSessionItems(token: CancellationToken): ProviderResult; + // #endregion } export interface ChatSessionItem { - /** - * Unique identifier for the chat session. - * - * @deprecated Will be replaced by `resource` - */ - id: string; - /** * The resource associated with the chat session. * * This is uniquely identifies the chat session and is used to open the chat session. */ - resource: Uri | undefined; + resource: Uri; /** * Human readable name of the session shown in the UI @@ -175,22 +172,28 @@ declare module 'vscode' { readonly requestHandler: ChatRequestHandler | undefined; } + /** + * Provides the content for a chat session rendered using the native chat UI. + */ export interface ChatSessionContentProvider { /** - * Resolves a chat session into a full `ChatSession` object. + * Provides the chat session content for a given uri. + * + * The returned {@linkcode ChatSession} is used to populate the history of the chat UI. * - * @param sessionId The id of the chat session to open. + * @param resource The URI of the chat session to resolve. * @param token A cancellation token that can be used to cancel the operation. + * + * @return The {@link ChatSession chat session} associated with the given URI. */ - provideChatSessionContent(sessionId: string, token: CancellationToken): Thenable | ChatSession; + provideChatSessionContent(resource: Uri, token: CancellationToken): Thenable | ChatSession; /** - * - * @param sessionId Identifier of the chat session being updated. + * @param resource Identifier of the chat session being updated. * @param updates Collection of option identifiers and their new values. Only the options that changed are included. * @param token A cancellation token that can be used to cancel the notification if the session is disposed. */ - provideHandleOptionsChange?(sessionId: string, updates: ReadonlyArray, token: CancellationToken): void; + provideHandleOptionsChange?(resource: Uri, updates: ReadonlyArray, token: CancellationToken): void; /** * Called as soon as you register (call me once) @@ -227,12 +230,12 @@ declare module 'vscode' { /** * Registers a new {@link ChatSessionContentProvider chat session content provider}. * - * @param chatSessionType A unique identifier for the chat session type. This is used to differentiate between different chat session providers. + * @param scheme The uri-scheme to register for. This must be unique. * @param provider The provider to register. * * @returns A disposable that unregisters the provider when disposed. */ - export function registerChatSessionContentProvider(chatSessionType: string, provider: ChatSessionContentProvider, chatParticipant: ChatParticipant, capabilities?: ChatSessionCapabilities): Disposable; + export function registerChatSessionContentProvider(scheme: string, provider: ChatSessionContentProvider, chatParticipant: ChatParticipant, capabilities?: ChatSessionCapabilities): Disposable; } export interface ChatContext { @@ -302,25 +305,4 @@ declare module 'vscode' { */ optionGroups?: ChatSessionProviderOptionGroup[]; } - - /** - * @deprecated - */ - export interface ChatSessionShowOptions { - /** - * The editor view column to show the chat session in. - * - * If not provided, the chat session will be shown in the chat panel instead. - */ - readonly viewColumn?: ViewColumn; - } - - export namespace window { - /** - * Shows a chat session in the panel or editor. - * - * @deprecated - */ - export function showChatSession(chatSessionType: string, sessionId: string, options: ChatSessionShowOptions): Thenable; - } } From 6292ca5410cc278fe3b5886c0635368a7de188c2 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:54:50 +0800 Subject: [PATCH 1556/4355] fix byok thinking screen cheese (#273006) --- .../browser/chatContentParts/chatThinkingContentPart.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts index b781a6003d0..41ee572bba0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts @@ -379,12 +379,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen return false; } - const otherId = other?.id; - const thisId = this.id; - - const otherValueRaw = Array.isArray(other.value) ? other.value.join('') : (other.value ?? ''); - const isEqual = this.parseContent(otherValueRaw.trim()) === this.currentThinkingValue; - return isEqual || otherId !== thisId; + return other?.id !== this.id; } override dispose(): void { From 8a5d22d3ffa6fcd80d589b75b69f1af4b82b4377 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 23 Oct 2025 17:14:28 -0700 Subject: [PATCH 1557/4355] Keep quick pick's progress bar rendered, and use aria-hidden to hide from a11y tree (#273010) --- src/vs/platform/quickinput/browser/quickInput.ts | 6 ++++-- src/vs/platform/quickinput/browser/quickInputController.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 6e728d7a8b3..e2c05453e8c 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -420,12 +420,14 @@ export abstract class QuickInput extends Disposable implements IQuickInput { this.busyDelay = new TimeoutTimer(); this.busyDelay.setIfNotSet(() => { if (this.visible) { - this.ui.progressBar.infinite().show(); + this.ui.progressBar.infinite(); + this.ui.progressBar.getContainer().removeAttribute('aria-hidden'); } }, 800); } if (!this.busy && this.busyDelay) { - this.ui.progressBar.stop().hide(); + this.ui.progressBar.stop(); + this.ui.progressBar.getContainer().setAttribute('aria-hidden', 'true'); this.busyDelay.cancel(); this.busyDelay = undefined; } diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 907b7965a71..baca194c277 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -648,7 +648,8 @@ export class QuickInputController extends Disposable { ui.visibleCount.setCount(0); ui.count.setCount(0); dom.reset(ui.message); - ui.progressBar.stop().hide(); + ui.progressBar.stop(); + ui.progressBar.getContainer().setAttribute('aria-hidden', 'true'); ui.list.setElements([]); ui.list.matchOnDescription = false; ui.list.matchOnDetail = false; From d3183a6f5a90c0d17ca746cc905c60f960db49af Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Thu, 23 Oct 2025 17:26:39 -0700 Subject: [PATCH 1558/4355] Use argument-hint for prompt placeholders (#273011) --- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 81fa082bb8c..c06c4048aa2 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -146,7 +146,7 @@ class InputEditorDecorations extends Disposable { if (this.configurationService.getValue('chat.emptyChatState.enabled')) { placeholder = localize('chatPlaceholderHint', "Add context (#), extensions (@), commands (/)"); } else { - placeholder = mode.description.get() ?? ''; + placeholder = mode.argumentHint?.get() ?? mode.description.get() ?? ''; } const decoration: IDecorationOptions[] = [ @@ -252,7 +252,7 @@ class InputEditorDecorations extends Disposable { // Resolve the prompt file (this will use cache if available) const promptFile = this.promptsService.resolvePromptSlashCommandFromCache(slashPromptPart.slashPromptCommand.command); - const description = promptFile?.header?.description; + const description = promptFile?.header?.argumentHint ?? promptFile?.header?.description; if (description) { placeholderDecoration = [{ range: getRangeForPlaceholder(slashPromptPart), From 17f8ab572921b7cb66e14ffd1555a1b8f08947fe Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:31:50 -0700 Subject: [PATCH 1559/4355] Fixes for local chat sessions (#273009) --- .../contrib/chat/browser/chat.contribution.ts | 5 +-- .../contrib/chat/browser/chatEditorInput.ts | 20 +++-------- .../chatSessions/chatSessionTracker.ts | 7 ++-- .../chat/browser/chatSessions/common.ts | 4 +-- .../chatSessions/localChatSessionsProvider.ts | 2 +- .../chatSessions/view/chatSessionsView.ts | 33 +++++++++++-------- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 64ad4ad4632..7f9f4d1b130 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -107,7 +107,7 @@ import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatPart import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; import { QuickChatService } from './chatQuick.js'; import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; -import { ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js'; +import { ChatSessionsView, ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js'; import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; import { ChatVariablesService } from './chatVariables.js'; @@ -980,7 +980,8 @@ registerWorkbenchContribution2(ChatTransferContribution.ID, ChatTransferContribu registerWorkbenchContribution2(ChatContextContributions.ID, ChatContextContributions, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatResponseResourceFileSystemProvider.ID, ChatResponseResourceFileSystemProvider, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(PromptUrlHandler.ID, PromptUrlHandler, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(ChatSessionsViewContrib.ID, ChatSessionsViewContrib, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ChatSessionsViewContrib.ID, ChatSessionsViewContrib, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ChatSessionsView.ID, ChatSessionsView, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(UserToolSetsContributions.ID, UserToolSetsContributions, WorkbenchPhase.Eventually); registerWorkbenchContribution2(PromptLanguageFeaturesProvider.ID, PromptLanguageFeaturesProvider, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 1ce03b15450..9c7dbc47999 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -22,7 +22,6 @@ import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditi import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; import { IChatSessionsService } from '../common/chatSessionsService.js'; -import { ChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; import type { IChatEditorOptions } from './chatEditor.js'; @@ -245,22 +244,11 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return 'local'; } - const { scheme, query } = this.resource; - - if (scheme === Schemas.vscodeChatSession) { - const parsed = ChatSessionUri.parse(this.resource); - if (parsed) { - return parsed.chatSessionType; - } - } - - const sessionTypeFromQuery = new URLSearchParams(query).get('chatSessionType'); - if (sessionTypeFromQuery) { - return sessionTypeFromQuery; + const { scheme } = this.resource; + if (scheme === Schemas.vscodeChatEditor || scheme === Schemas.vscodeChatSession) { + return 'local'; } - - // Default to 'local' for vscode-chat-editor scheme or when type cannot be determined - return 'local'; + return scheme; } override async resolve(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index 7c5e82d744d..a7ff66133ac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -10,7 +10,7 @@ import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/ import { ChatEditorInput } from '../chatEditorInput.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; import { IChatService } from '../../common/chatService.js'; import { IChatModel } from '../../common/chatModel.js'; import { ChatSessionUri } from '../../common/chatUri.js'; @@ -21,7 +21,8 @@ export class ChatSessionTracker extends Disposable { constructor( @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - @IChatService private readonly chatService: IChatService + @IChatService private readonly chatService: IChatService, + @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, ) { super(); this.setupEditorTracking(); @@ -41,7 +42,7 @@ export class ChatSessionTracker extends Disposable { private registerGroupListeners(group: IEditorGroup): void { this._register(group.onDidModelChange(e => { - if (!isChatSession(e.editor)) { + if (!isChatSession(this.chatSessionsService.getContentProviderSchemes(), e.editor)) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 0e112637713..ce6639403c4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -28,12 +28,12 @@ export type ChatSessionItemWithProvider = IChatSessionItem & { }; }; -export function isChatSession(editor?: EditorInput): boolean { +export function isChatSession(schemes: string[], editor?: EditorInput): boolean { if (!(editor instanceof ChatEditorInput)) { return false; } - if (editor.resource?.scheme !== 'vscode-chat-editor' && editor.resource?.scheme !== 'vscode-chat-session') { + if (!schemes.includes(editor.resource?.scheme)) { return false; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index f8a842fa38f..155941ec065 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -146,7 +146,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio private isLocalChatSession(editor?: EditorInput): boolean { // For the LocalChatSessionsProvider, we only want to track sessions that are actually 'local' type - if (!isChatSession(editor)) { + if (!isChatSession(this.chatSessionsService.getContentProviderSchemes(), editor)) { return false; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index fe75173cab4..1aa66e14e41 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -31,6 +31,26 @@ import { ChatSessionTracker } from '../chatSessionTracker.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { SessionsViewPane } from './sessionsViewPane.js'; +export class ChatSessionsView extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatSessionsView'; + constructor() { + super(); + this.registerViewContainer(); + } + private registerViewContainer(): void { + Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: AGENT_SESSIONS_VIEWLET_ID, + title: nls.localize2('chat.agent.sessions', "Agent Sessions"), + ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer), + hideIfEmpty: false, + icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), + order: 6 + }, ViewContainerLocation.Sidebar); + } + +} + export class ChatSessionsViewContrib extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatSessions'; @@ -55,7 +75,6 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon this._register(this.chatSessionsService.registerChatSessionItemProvider(this.localProvider)); // Initial check - this.registerViewContainer(); void this.updateViewRegistration(); this._register(this.chatSessionsService.onDidChangeItemsProviders(() => { @@ -73,18 +92,6 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon })); } - private registerViewContainer(): void { - Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( - { - id: AGENT_SESSIONS_VIEWLET_ID, - title: nls.localize2('chat.agent.sessions', "Agent Sessions"), - ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer, [this.sessionTracker]), - hideIfEmpty: true, - icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), - order: 6 - }, ViewContainerLocation.Sidebar); - } - private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); } From e6e0c64a93b51360826de3725b743bda5f44fd34 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:01:51 +0200 Subject: [PATCH 1560/4355] SCM - add menu items to toggle selection mode (#272996) --- .../contrib/scm/browser/scmViewPane.ts | 35 +++++++++++++++++++ .../contrib/scm/browser/scmViewService.ts | 17 +++++++-- src/vs/workbench/contrib/scm/common/scm.ts | 1 + 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 37eb11d07ca..dcae7ec6759 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1204,6 +1204,41 @@ registerAction2(RepositorySortByDiscoveryTimeAction); registerAction2(RepositorySortByNameAction); registerAction2(RepositorySortByPathAction); +abstract class RepositorySelectionModeAction extends ViewAction { + constructor(private readonly selectionMode: 'single' | 'multiple', title: string, order: number) { + super({ + id: `workbench.scm.action.repositories.setSelectionMode.${selectionMode}`, + title, + viewId: VIEW_PANE_ID, + f1: false, + toggled: RepositoryContextKeys.RepositorySelectionMode.isEqualTo(selectionMode), + menu: [ + { id: Menus.Repositories, order, group: '2_selectionMode' }, + { id: MenuId.SCMSourceControlTitle, order, group: '2_selectionMode' }, + ] + }); + } + + override runInView(accessor: ServicesAccessor): void { + accessor.get(ISCMViewService).toggleSelectionMode(this.selectionMode); + } +} + +class RepositorySingleSelectionModeAction extends RepositorySelectionModeAction { + constructor() { + super('single', localize('repositorySingleSelectionMode', "Select Single Repository"), 1); + } +} + +class RepositoryMultiSelectionModeAction extends RepositorySelectionModeAction { + constructor() { + super('multiple', localize('repositoryMultiSelectionMode', "Select Multiple Repositories"), 2); + } +} + +registerAction2(RepositorySingleSelectionModeAction); +registerAction2(RepositoryMultiSelectionModeAction); + abstract class SetSortKeyAction extends ViewAction { constructor(private sortKey: ViewSortKey, title: string) { super({ diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 859431e6d4f..da03e904cc9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -18,7 +18,7 @@ import { binarySearch } from '../../../../base/common/arrays.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; +import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -42,7 +42,8 @@ function getRepositoryName(workspaceContextService: IWorkspaceContextService, re } export const RepositoryContextKeys = { - RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime) + RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime), + RepositorySelectionMode: new RawContextKey<'single' | 'multiple'>('scmRepositorySelectionMode', 'single') }; export type RepositoryQuickPickItem = IQuickPickItem & { repository: 'auto' | ISCMRepository }; @@ -226,6 +227,8 @@ export class SCMViewService implements ISCMViewService { private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; + private _selectionModelContextKey: IContextKey<'multiple' | 'single'>; + constructor( @ISCMService private readonly scmService: ISCMService, @IContextKeyService contextKeyService: IContextKeyService, @@ -311,6 +314,12 @@ export class SCMViewService implements ISCMViewService { this._sortKeyContextKey = RepositoryContextKeys.RepositorySortKey.bindTo(contextKeyService); this._sortKeyContextKey.set(this._repositoriesSortKey); + this._selectionModelContextKey = RepositoryContextKeys.RepositorySelectionMode.bindTo(contextKeyService); + this.disposables.add(autorun(reader => { + const selectionMode = this.selectionModeConfig.read(reader); + this._selectionModelContextKey.set(selectionMode); + })); + scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -480,6 +489,10 @@ export class SCMViewService implements ISCMViewService { this._onDidChangeRepositories.fire({ added: Iterable.empty(), removed: Iterable.empty() }); } + toggleSelectionMode(selectionMode: 'multiple' | 'single'): void { + this.configurationService.updateValue('scm.repositories.selectionMode', selectionMode); + } + focus(repository: ISCMRepository | undefined): void { if (repository && !this.isVisible(repository)) { return; diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 9b7aea28f6b..aa32b15d54f 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -233,6 +233,7 @@ export interface ISCMViewService { toggleVisibility(repository: ISCMRepository, visible?: boolean): void; toggleSortKey(sortKey: ISCMRepositorySortKey): void; + toggleSelectionMode(selectionMode: 'multiple' | 'single'): void; readonly focusedRepository: ISCMRepository | undefined; readonly onDidFocusRepository: Event; From b1ce1d4cefaa889dd008e472f871e6b8f2776f26 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 23 Oct 2025 19:12:10 -0700 Subject: [PATCH 1561/4355] Rename 'partial' checkbox state to 'mixed' to be consistent with a11y value (#273021) --- src/vs/base/browser/ui/toggle/toggle.ts | 10 +++++----- .../quickinput/browser/tree/quickInputTree.ts | 20 +++++++++---------- .../quickInputTreeAccessibilityProvider.ts | 2 +- .../browser/tree/quickInputTreeController.ts | 4 ++-- .../quickinput/browser/tree/quickTree.ts | 4 ++-- .../platform/quickinput/common/quickInput.ts | 8 ++++---- .../chat/browser/actions/chatToolPicker.ts | 4 ++-- .../search/browser/anythingQuickAccess.ts | 4 ++-- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index ecad99575c2..e490c9820d6 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -349,7 +349,7 @@ export class Checkbox extends BaseCheckbox { export class TriStateCheckbox extends BaseCheckbox { constructor( title: string, - private _state: boolean | 'partial', + private _state: boolean | 'mixed', styles: ICheckboxStyles ) { let icon: ThemeIcon | undefined; @@ -357,7 +357,7 @@ export class TriStateCheckbox extends BaseCheckbox { case true: icon = Codicon.check; break; - case 'partial': + case 'mixed': icon = Codicon.dash; break; case false: @@ -386,11 +386,11 @@ export class TriStateCheckbox extends BaseCheckbox { })); } - get checked(): boolean | 'partial' { + get checked(): boolean | 'mixed' { return this._state; } - set checked(newState: boolean | 'partial') { + set checked(newState: boolean | 'mixed') { if (this._state !== newState) { this._state = newState; this.checkbox.checked = newState === true; @@ -403,7 +403,7 @@ export class TriStateCheckbox extends BaseCheckbox { case true: this.checkbox.setIcon(Codicon.check); break; - case 'partial': + case 'mixed': this.checkbox.setIcon(Codicon.dash); break; case false: diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTree.ts b/src/vs/platform/quickinput/browser/tree/quickInputTree.ts index b8eaec2c2ac..6444a1c18f2 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTree.ts @@ -12,15 +12,15 @@ export interface IQuickTreeFilterData { readonly descriptionHighlights?: IMatch[]; } -export function getParentNodeState(parentChildren: ITreeNode[] | IObjectTreeElement[]): boolean | 'partial' { +export function getParentNodeState(parentChildren: ITreeNode[] | IObjectTreeElement[]): boolean | 'mixed' { let containsChecks = false; let containsUnchecks = false; - let containsPartial = false; + let containsMixed = false; for (const element of parentChildren) { switch (element.element?.checked) { - case 'partial': - containsPartial = true; + case 'mixed': + containsMixed = true; break; case true: containsChecks = true; @@ -29,18 +29,18 @@ export function getParentNodeState(parentChildren: ITreeNode implements isChecked(element: T): IValueWithChangeEvent | undefined { return { - get value() { return element.checked === 'partial' ? 'mixed' : !!element.checked; }, + get value() { return element.checked === 'mixed' ? 'mixed' : !!element.checked; }, onDidChange: e => Event.filter(this.onCheckedEvent, e => e.item === element)(_ => e()), }; } diff --git a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts index aac6a8e981a..303bc5227aa 100644 --- a/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts +++ b/src/vs/platform/quickinput/browser/tree/quickInputTreeController.ts @@ -313,7 +313,7 @@ export class QuickInputTreeController extends Disposable { return this._tree.getFocus().filter((item): item is IQuickTreeItem => item !== null); } - check(element: IQuickTreeItem, checked: boolean | 'partial') { + check(element: IQuickTreeItem, checked: boolean | 'mixed') { if (element.checked === checked) { return; } @@ -321,7 +321,7 @@ export class QuickInputTreeController extends Disposable { this._onDidCheckedLeafItemsChange.fire(this.getCheckedLeafItems()); } - checkAll(checked: boolean | 'partial') { + checkAll(checked: boolean | 'mixed') { const updated = new Set(); const toUpdate = [...this._tree.getNode().children]; let fireCheckedChangeEvent = false; diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 4a03c4ab99c..829600d12cb 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -86,7 +86,7 @@ export class QuickTree extends QuickInput implements I return this.ui.tree.tree.getParentElement(element) as T ?? undefined; } - setCheckboxState(element: T, checked: boolean | 'partial'): void { + setCheckboxState(element: T, checked: boolean | 'mixed'): void { this.ui.tree.check(element, checked); } expand(element: T): void { @@ -139,7 +139,7 @@ export class QuickTree extends QuickInput implements I } super.show(); // TODO: Why have show() bubble up while update() trickles down? - // Intial state + // Initial state // TODO@TylerLeonhardt: Without this setTimeout, the screen reader will not read out // the final count of checked items correctly. Investigate a better way // to do this. ref https://github.com/microsoft/vscode/issues/258617 diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index e4d11bcec06..9408348699f 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -1136,7 +1136,7 @@ export interface IQuickTree extends IQuickInput { * @param element The item to update. * @param checked The new checkbox state. */ - setCheckboxState(element: T, checked: boolean | 'partial'): void; + setCheckboxState(element: T, checked: boolean | 'mixed'): void; /** * Expands an item. @@ -1180,12 +1180,12 @@ export interface IQuickTree extends IQuickInput { */ export interface IQuickTreeItem extends IQuickItem { /** - * The checked state of the item. Can be true, false, or 'partial' for tri-state. + * The checked state of the item. Can be true, false, or 'mixed' for tri-state. * When canSelectMany is false, this is ignored and the item is treated as a single selection. * When canSelectMany is true, this indicates the checkbox state of the item. * If undefined, the item is unchecked by default. */ - checked?: boolean | 'partial'; + checked?: boolean | 'mixed'; /** * The collapsible state of the tree item. Defaults to 'Expanded' if children are present. @@ -1217,7 +1217,7 @@ export interface IQuickTreeCheckboxEvent { /** * The new checked state. */ - checked: boolean | 'partial'; + checked: boolean | 'mixed'; } /** diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index 8aa8cef2329..acf25cf9c3b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -56,7 +56,7 @@ interface IBucketTreeItem extends IToolTreeItem { toolset?: ToolSet; // For MCP servers where the bucket represents the ToolSet - mutable readonly status?: string; readonly children: AnyTreeItem[]; - checked: boolean | 'partial' | undefined; + checked: boolean | 'mixed' | undefined; } /** @@ -67,7 +67,7 @@ interface IToolSetTreeItem extends IToolTreeItem { readonly itemType: 'toolset'; readonly toolset: ToolSet; children: AnyTreeItem[] | undefined; - checked: boolean | 'partial'; + checked: boolean | 'mixed'; } /** diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 3fb056bf360..20f4261d129 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -264,7 +264,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider Date: Thu, 23 Oct 2025 19:29:10 -0700 Subject: [PATCH 1562/4355] proper dedup after chat session uri change --- .../browser/chatSessions/view/sessionsTreeRenderer.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 2b80c437426..fb21badeee9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -49,6 +49,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; import { ChatSessionUri } from '../../../common/chatUri.js'; import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; +import { ResourceSet } from '../../../../../../base/common/map.js'; interface ISessionTemplateData { readonly container: HTMLElement; @@ -546,11 +547,13 @@ export class SessionsDataSource implements IAsyncDataSource s.id)); + const existingSessions = new ResourceSet(); + itemsWithProvider.forEach(s => existingSessions.add(s.resource)); + hybridSessions.forEach(session => { - if (!existingIds.has(session.id)) { + if (!existingSessions.has(session.resource)) { itemsWithProvider.push(session as ChatSessionItemWithProvider); - existingIds.add(session.id); + existingSessions.add(session.resource); } }); processSessionsWithTimeGrouping(itemsWithProvider); From 173291efb415c7159e8d6189f9dd8f74dfe8665e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 23 Oct 2025 19:47:13 -0700 Subject: [PATCH 1563/4355] disable getting started for agent session ext recommendation --- .../contrib/chat/browser/chatSessions/view/chatSessionsView.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 1aa66e14e41..0751274f09f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -7,6 +7,7 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import * as nls from '../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; import { SyncDescriptor } from '../../../../../../platform/instantiation/common/descriptors.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; @@ -231,7 +232,7 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon canMoveView: true, order: 1000, collapsed: !!otherProviders.length, - when: ChatContextKeyExprs.agentViewWhen + when: ContextKeyExpr.false() }; viewDescriptorsToRegister.push(gettingStartedDescriptor); this.registeredViewDescriptors.set('gettingStarted', gettingStartedDescriptor); From 511763be764887b72af7ec057415607b9c228d69 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:28:00 -0700 Subject: [PATCH 1564/4355] Fix session open in a new window actions --- .../chat/browser/actions/chatSessionActions.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index db4d47ca0f5..408d19aefd1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -27,7 +27,6 @@ import { IViewsService } from '../../../../services/views/common/viewsService.js import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; import { ChatConfiguration, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; @@ -183,7 +182,8 @@ export class OpenChatSessionInNewWindowAction extends Action2 { const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { - const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); + const uri = context.session.resource; + // Check if this session is already open in another editor const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); if (existingEditor) { @@ -240,7 +240,7 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { - const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); + const uri = context.session.resource; // Check if this session is already open in another editor const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); if (existingEditor) { @@ -298,9 +298,8 @@ export class OpenChatSessionInSidebarAction extends Action2 { const editorGroupsService = accessor.get(IEditorGroupsService); const sessionId = context.session.id; if (context.session.provider?.chatSessionType) { - const uri = ChatSessionUri.forSession(context.session.provider.chatSessionType, sessionId); // Check if this session is already open in another editor - const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + const existingEditor = findExistingChatEditorByUri(context.session.resource, sessionId, editorGroupsService); if (existingEditor) { await editorService.openEditor(existingEditor.editor, existingEditor.groupId); return; @@ -316,10 +315,7 @@ export class OpenChatSessionInSidebarAction extends Action2 { if (context.session && (isLocalChatSessionItem(context.session))) { await chatViewPane.loadSession(sessionId); } else { - // For external provider sessions, create a URI and load using that - const providerType = context.session.provider?.chatSessionType || 'external'; - const sessionUri = ChatSessionUri.forSession(providerType, sessionId); - await chatViewPane.loadSession(sessionUri); + await chatViewPane.loadSession(context.session.resource); } // Focus the chat input From e29c59fce4c0e9a5843862eabcde8d004b256f6f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:36:55 -0700 Subject: [PATCH 1565/4355] Disable `open in sidebar` action for non-native sessions We can only support this for sessions that use our native chat UI. We can't correctly support it for ones that use custom editors or terminals For now, I'm thinking we should just limit it to local sessions. In the future we could instead allow it for any session with a content provider --- .../workbench/contrib/chat/browser/actions/chatSessionActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index db4d47ca0f5..a895998c9fe 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -466,6 +466,7 @@ MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { }, group: 'navigation', order: 3, + when: ChatContextKeys.sessionType.isEqualTo('local'), }); // Register the toggle command for the ViewTitle menu From bb84fe256707d76ade07b24aaf9505366e065d10 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Oct 2025 07:38:12 +0200 Subject: [PATCH 1566/4355] status bar - tweak first/last item margins (#273033) --- .../browser/parts/statusbar/media/statusbarpart.css | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index cee2a874fad..0340f2c9d6b 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -98,9 +98,16 @@ margin-right: 7px; /* Add margin to the most right status bar item. Margin is used to position beak properly. */ } -/* Tweak appearance for items with background to improve hover feedback */ +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label, +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item > .statusbar-item-label { + /* As such, clear margins on label*/ + margin-right: 0; + margin-left: 0; +} + .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.left.first-visible-item, .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.right.last-visible-item { + /* Tweak appearance for items with background to improve hover feedback */ padding-right: 0; padding-left: 0; } From 24dca7d10ce1d837428dde5ea57b263bd1b122a4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Oct 2025 07:53:24 +0200 Subject: [PATCH 1567/4355] Chat: mode picker does not appear when leaving welcome state (fix #273036) (#273038) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index f0d1a566327..bda60c770c1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -29,7 +29,7 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js'; import { IChatAgent, IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js'; -import { ChatContextKeys, ChatContextKeyExprs } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js'; import { IChatMode, IChatModeService } from '../../common/chatModes.js'; import { chatVariableLeader } from '../../common/chatParserTypes.js'; @@ -451,8 +451,7 @@ export class OpenModePickerAction extends Action2 { ChatContextKeys.enabled, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), ChatContextKeys.inQuickChat.negate(), - ChatContextKeys.lockedToCodingAgent.negate(), - ChatContextKeyExprs.chatSetupTriggerContext?.negate()), + ChatContextKeys.lockedToCodingAgent.negate()), group: 'navigation', }, ] From 3e4b25405d799173c8ca37b9a2deb6486ffc6101 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:58:12 -0700 Subject: [PATCH 1568/4355] Fix opening of custom chat sessions from history quick pick --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index bed78771f07..76c524e468a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -63,7 +63,6 @@ import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; @@ -1047,7 +1046,7 @@ export function registerChatActions() { private async showChatSessionInEditor(providerType: string, session: IChatSessionItem, editorService: IEditorService) { // Open the chat editor await editorService.openEditor({ - resource: ChatSessionUri.forSession(providerType, session.id), + resource: session.resource, options: {} satisfies IChatEditorOptions }); } From 9e32b4b81483527a8c49c1466b8fef495bc036c7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:02:07 +0200 Subject: [PATCH 1569/4355] =?UTF-8?q?SCM=20-=20=F0=9F=92=84use=20an=20enum?= =?UTF-8?q?=20for=20selection=20mode=20(#273032)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/scm/browser/scmViewPane.ts | 8 +++---- .../contrib/scm/browser/scmViewService.ts | 24 +++++++++---------- src/vs/workbench/contrib/scm/common/scm.ts | 9 +++++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index dcae7ec6759..0935ba25e4b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -11,7 +11,7 @@ import { ViewPane, IViewPaneOptions, ViewAction } from '../../../browser/parts/v import { append, $, Dimension, trackFocus, clearNode, isPointerEvent, isActiveElement } from '../../../../base/browser/dom.js'; import { asCSSUrl } from '../../../../base/browser/cssValue.js'; import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browser/ui/list/list.js'; -import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, ISCMInputValueProviderContext, ViewMode } from '../common/scm.js'; +import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, ISCMInputValueProviderContext, ViewMode, ISCMRepositorySelectionMode } from '../common/scm.js'; import { ResourceLabels, IResourceLabel, IFileLabelOptions } from '../../../browser/labels.js'; import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -1205,7 +1205,7 @@ registerAction2(RepositorySortByNameAction); registerAction2(RepositorySortByPathAction); abstract class RepositorySelectionModeAction extends ViewAction { - constructor(private readonly selectionMode: 'single' | 'multiple', title: string, order: number) { + constructor(private readonly selectionMode: ISCMRepositorySelectionMode, title: string, order: number) { super({ id: `workbench.scm.action.repositories.setSelectionMode.${selectionMode}`, title, @@ -1226,13 +1226,13 @@ abstract class RepositorySelectionModeAction extends ViewAction { class RepositorySingleSelectionModeAction extends RepositorySelectionModeAction { constructor() { - super('single', localize('repositorySingleSelectionMode', "Select Single Repository"), 1); + super(ISCMRepositorySelectionMode.Single, localize('repositorySingleSelectionMode', "Select Single Repository"), 1); } } class RepositoryMultiSelectionModeAction extends RepositorySelectionModeAction { constructor() { - super('multiple', localize('repositoryMultiSelectionMode', "Select Multiple Repositories"), 2); + super(ISCMRepositorySelectionMode.Multiple, localize('repositoryMultiSelectionMode', "Select Multiple Repositories"), 2); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index da03e904cc9..132463e8cb2 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -5,7 +5,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus, ISCMProvider, ISCMRepositorySortKey } from '../common/scm.js'; +import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus, ISCMProvider, ISCMRepositorySortKey, ISCMRepositorySelectionMode } from '../common/scm.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { SCMMenus } from './menus.js'; @@ -43,7 +43,7 @@ function getRepositoryName(workspaceContextService: IWorkspaceContextService, re export const RepositoryContextKeys = { RepositorySortKey: new RawContextKey('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime), - RepositorySelectionMode: new RawContextKey<'single' | 'multiple'>('scmRepositorySelectionMode', 'single') + RepositorySelectionMode: new RawContextKey('scmRepositorySelectionMode', ISCMRepositorySelectionMode.Single), }; export type RepositoryQuickPickItem = IQuickPickItem & { repository: 'auto' | ISCMRepository }; @@ -111,7 +111,7 @@ export class SCMViewService implements ISCMViewService { declare readonly _serviceBrand: undefined; readonly menus: ISCMMenus; - readonly selectionModeConfig: IObservable<'multiple' | 'single'>; + readonly selectionModeConfig: IObservable; private didFinishLoading: boolean = false; private didSelectRepository: boolean = false; @@ -227,7 +227,7 @@ export class SCMViewService implements ISCMViewService { private _repositoriesSortKey: ISCMRepositorySortKey; private _sortKeyContextKey: IContextKey; - private _selectionModelContextKey: IContextKey<'multiple' | 'single'>; + private _selectionModelContextKey: IContextKey; constructor( @ISCMService private readonly scmService: ISCMService, @@ -241,14 +241,14 @@ export class SCMViewService implements ISCMViewService { ) { this.menus = instantiationService.createInstance(SCMMenus); - this.selectionModeConfig = observableConfigValue<'multiple' | 'single'>('scm.repositories.selectionMode', 'single', this.configurationService); + this.selectionModeConfig = observableConfigValue('scm.repositories.selectionMode', ISCMRepositorySelectionMode.Single, this.configurationService); try { this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); // If previously there were multiple visible repositories but the // view mode is `single`, only restore the first visible repository. - if (this.previousState && this.previousState.visible.length > 1 && this.selectionModeConfig.get() === 'single') { + if (this.previousState && this.previousState.visible.length > 1 && this.selectionModeConfig.get() === ISCMRepositorySelectionMode.Single) { this.previousState = { ...this.previousState, visible: [this.previousState.visible[0]] @@ -302,10 +302,10 @@ export class SCMViewService implements ISCMViewService { }); this.disposables.add(runOnChange(this.selectionModeConfig, selectionMode => { - if (selectionMode === 'single' && this.visibleRepositories.length > 1) { + if (selectionMode === ISCMRepositorySelectionMode.Single && this.visibleRepositories.length > 1) { const repository = this.visibleRepositories[0]; this.visibleRepositories = [repository]; - } else if (selectionMode === 'multiple' && this.repositories.length > 1) { + } else if (selectionMode === ISCMRepositorySelectionMode.Multiple && this.repositories.length > 1) { this.visibleRepositories = this.repositories; } })); @@ -361,7 +361,7 @@ export class SCMViewService implements ISCMViewService { this.insertRepositoryView(this._repositories, repositoryView); - if (this.selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + if (this.selectionModeConfig.get() === ISCMRepositorySelectionMode.Multiple || !this._repositories.find(r => r.selectionIndex !== -1)) { // Multiple selection mode or single selection mode (select first repository) this._repositories.forEach((repositoryView, index) => { if (repositoryView.selectionIndex === -1) { @@ -398,7 +398,7 @@ export class SCMViewService implements ISCMViewService { } } - if (this.selectionModeConfig.get() === 'multiple' || !this._repositories.find(r => r.selectionIndex !== -1)) { + if (this.selectionModeConfig.get() === ISCMRepositorySelectionMode.Multiple || !this._repositories.find(r => r.selectionIndex !== -1)) { // Multiple selection mode or single selection mode (select first repository) const maxSelectionIndex = this.getMaxSelectionIndex(); this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 }); @@ -464,9 +464,9 @@ export class SCMViewService implements ISCMViewService { } if (visible) { - if (this.selectionModeConfig.get() === 'single') { + if (this.selectionModeConfig.get() === ISCMRepositorySelectionMode.Single) { this.visibleRepositories = [repository]; - } else if (this.selectionModeConfig.get() === 'multiple') { + } else if (this.selectionModeConfig.get() === ISCMRepositorySelectionMode.Multiple) { this.visibleRepositories = [...this.visibleRepositories, repository]; } } else { diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index aa32b15d54f..af0aca559ea 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -210,6 +210,11 @@ export const enum ISCMRepositorySortKey { Path = 'path' } +export const enum ISCMRepositorySelectionMode { + Single = 'single', + Multiple = 'multiple' +} + export const ISCMViewService = createDecorator('scmView'); export interface ISCMViewVisibleRepositoryChangeEvent { @@ -221,7 +226,7 @@ export interface ISCMViewService { readonly _serviceBrand: undefined; readonly menus: ISCMMenus; - readonly selectionModeConfig: IObservable<'multiple' | 'single'>; + readonly selectionModeConfig: IObservable; repositories: ISCMRepository[]; readonly onDidChangeRepositories: Event; @@ -233,7 +238,7 @@ export interface ISCMViewService { toggleVisibility(repository: ISCMRepository, visible?: boolean): void; toggleSortKey(sortKey: ISCMRepositorySortKey): void; - toggleSelectionMode(selectionMode: 'multiple' | 'single'): void; + toggleSelectionMode(selectionMode: ISCMRepositorySelectionMode): void; readonly focusedRepository: ISCMRepository | undefined; readonly onDidFocusRepository: Event; From be8c8fb87ea41daa2a91bfd420bf489e2d7b0980 Mon Sep 17 00:00:00 2001 From: Elijah King Date: Thu, 23 Oct 2025 23:17:17 -0700 Subject: [PATCH 1570/4355] Agents description placeholder text (#272556) * replaces with agent descriptions updates default agent description * Improve built-in chat mode descriptions * Simplify Edit mode description * Simplify Ask mode description --------- Co-authored-by: Harald Kirschner --- .../chat/browser/contrib/chatInputEditorContrib.ts | 10 +--------- src/vs/workbench/contrib/chat/common/chatModes.ts | 6 +++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index c06c4048aa2..6d6234e8912 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -12,8 +12,6 @@ import { ICodeEditorService } from '../../../../../editor/browser/services/codeE import { Range } from '../../../../../editor/common/core/range.js'; import { IDecorationOptions } from '../../../../../editor/common/editorCommon.js'; import { TrackedRangeStickiness } from '../../../../../editor/common/model.js'; -import { localize } from '../../../../../nls.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { inputPlaceholderForeground } from '../../../../../platform/theme/common/colorRegistry.js'; @@ -53,7 +51,6 @@ class InputEditorDecorations extends Disposable { @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService private readonly labelService: ILabelService, @IPromptsService private readonly promptsService: IPromptsService, ) { @@ -142,12 +139,7 @@ class InputEditorDecorations extends Disposable { if (!inputValue) { const mode = this.widget.input.currentModeObs.get(); - let placeholder; - if (this.configurationService.getValue('chat.emptyChatState.enabled')) { - placeholder = localize('chatPlaceholderHint', "Add context (#), extensions (@), commands (/)"); - } else { - placeholder = mode.argumentHint?.get() ?? mode.description.get() ?? ''; - } + const placeholder = mode.argumentHint?.get() ?? mode.description.get() ?? ''; const decoration: IDecorationOptions[] = [ { diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index e95d0236868..9d28797b778 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -435,9 +435,9 @@ export class BuiltinChatMode implements IChatMode { } export namespace ChatMode { - export const Ask = new BuiltinChatMode(ChatModeKind.Ask, 'Ask', localize('chatDescription', "Ask a question.")); - export const Edit = new BuiltinChatMode(ChatModeKind.Edit, 'Edit', localize('editsDescription', "Edit files.")); - export const Agent = new BuiltinChatMode(ChatModeKind.Agent, 'Agent', localize('agentDescription', "Provide instructions.")); + export const Ask = new BuiltinChatMode(ChatModeKind.Ask, 'Ask', localize('chatDescription', "Explore and understand your code")); + export const Edit = new BuiltinChatMode(ChatModeKind.Edit, 'Edit', localize('editsDescription', "Edit or refactor selected code")); + export const Agent = new BuiltinChatMode(ChatModeKind.Agent, 'Agent', localize('agentDescription', "Describe what to build next")); } export function isBuiltinChatMode(mode: IChatMode): boolean { From 43e3062b68f7e2053ed039f2723377a1a391f375 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:57:09 +0200 Subject: [PATCH 1571/4355] SCM - fix repository name text ellipsis (#273048) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index fc4fe30ed76..84f948a1148 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -54,6 +54,12 @@ flex: 0 1 auto; } +.scm-view .scm-provider > .monaco-icon-label .monaco-icon-name-container { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .scm-view .scm-provider .monaco-highlighted-label { display: flex; align-items: center; From 4afd3e13ba536aba4d80f4ecf60b6be1332d3dc1 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 24 Oct 2025 00:27:02 -0700 Subject: [PATCH 1572/4355] chat: improve todo list progress, active item display and styling (#273045) * chat: improve todo list progress, active item display and styling * chat: assert collapsed title shows current task in accessibility tests --- .../chatContentParts/chatTodoListWidget.ts | 56 +++++++++++-------- .../contrib/chat/browser/media/chat.css | 14 ++++- .../test/browser/chatTodoListWidget.test.ts | 13 +++-- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 6399e7919f0..0245fa69314 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -271,9 +271,11 @@ export class ChatTodoListWidget extends Disposable { const shouldShow = todoList.length > 2; if (!shouldShow) { + this.domNode.classList.remove('has-todos'); return; } + this.domNode.classList.add('has-todos'); this.renderTodoList(todoList); this.domNode.style.display = 'block'; this._onDidChangeHeight.fire(); @@ -443,14 +445,17 @@ export class ChatTodoListWidget extends Disposable { const totalCount = todoList.length; const inProgressTodos = todoList.filter(todo => todo.status === 'in-progress'); const firstInProgressTodo = inProgressTodos.length > 0 ? inProgressTodos[0] : undefined; - const completedTodos = todoList.filter(todo => todo.status === 'completed'); - const lastCompletedTodo = completedTodos.length > 0 ? completedTodos[completedTodos.length - 1] : undefined; + const notStartedTodos = todoList.filter(todo => todo.status === 'not-started'); + const firstNotStartedTodo = notStartedTodos.length > 0 ? notStartedTodos[0] : undefined; const progressText = dom.$('span'); if (totalCount === 0) { progressText.textContent = localize('chat.todoList.title', 'Todos'); } else { - progressText.textContent = localize('chat.todoList.titleWithProgress', 'Todos ({0}/{1})', completedCount, totalCount); + // Show the current task number (1-indexed): completed + in-progress, or just completed if none in-progress + // Ensure we show at least 1 when tasks exist + const currentTaskNumber = inProgressTodos.length > 0 ? completedCount + 1 : Math.max(1, completedCount); + progressText.textContent = localize('chat.todoList.titleWithProgressExpanded', 'Todos ({0}/{1})', currentTaskNumber, totalCount); } titleElement.appendChild(progressText); const expandButtonLabel = this._isExpanded @@ -459,39 +464,44 @@ export class ChatTodoListWidget extends Disposable { this.expandoElement.setAttribute('aria-label', expandButtonLabel); this.expandoElement.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); if (!this._isExpanded) { - // Priority 1: Show first in-progress todo (matches manageTodoListTool logic) - if (firstInProgressTodo) { + // Show first in-progress todo, or if none, the first not-started todo + const todoToShow = firstInProgressTodo || firstNotStartedTodo; + if (todoToShow) { const separator = dom.$('span'); separator.textContent = ' - '; + separator.style.marginLeft = '4px'; titleElement.appendChild(separator); - const icon = dom.$('.codicon.codicon-record'); - icon.style.color = 'var(--vscode-charts-blue)'; + const icon = dom.$('.codicon'); + if (todoToShow === firstInProgressTodo) { + icon.classList.add('codicon-record'); + icon.style.color = 'var(--vscode-charts-blue)'; + } else { + icon.classList.add('codicon-circle-large-outline'); + icon.style.color = 'var(--vscode-foreground)'; + } + icon.style.marginLeft = '4px'; icon.style.marginRight = '4px'; icon.style.verticalAlign = 'middle'; titleElement.appendChild(icon); - const inProgressText = dom.$('span'); - inProgressText.textContent = firstInProgressTodo.title; - inProgressText.style.verticalAlign = 'middle'; - titleElement.appendChild(inProgressText); + const todoText = dom.$('span'); + todoText.textContent = todoToShow.title; + todoText.style.verticalAlign = 'middle'; + titleElement.appendChild(todoText); } - // Priority 2: Show last completed todo if not all completed (matches manageTodoListTool logic) - else if (completedCount > 0 && completedCount < totalCount && lastCompletedTodo) { + // Show "Done" when all tasks are completed + else if (completedCount > 0 && completedCount === totalCount) { const separator = dom.$('span'); separator.textContent = ' - '; + separator.style.marginLeft = '4px'; titleElement.appendChild(separator); - const icon = dom.$('.codicon.codicon-check'); - icon.style.color = 'var(--vscode-charts-green)'; - icon.style.marginRight = '4px'; - icon.style.verticalAlign = 'middle'; - titleElement.appendChild(icon); - - const completedText = dom.$('span'); - completedText.textContent = lastCompletedTodo.title; - completedText.style.verticalAlign = 'middle'; - titleElement.appendChild(completedText); + const doneText = dom.$('span'); + doneText.textContent = localize('chat.todoList.allDone', 'Done'); + doneText.style.marginLeft = '4px'; + doneText.style.verticalAlign = 'middle'; + titleElement.appendChild(doneText); } } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 71df24d8daf..c19b4aed974 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -783,6 +783,11 @@ have to be updated for changes to the rules above, or to support more deeply nes overflow: hidden; } +.interactive-session .interactive-input-part > .chat-todo-list-widget-container:has(.chat-todo-list-widget.has-todos) + .chat-editing-session .chat-editing-session-container { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + .interactive-session .chat-editing-session .monaco-list-row .chat-collapsible-list-action-bar { padding-left: 5px; display: none; @@ -1089,12 +1094,13 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { - padding: 4px 8px 4px 12px; + padding: 6px 8px 6px 12px; box-sizing: border-box; border: 1px solid var(--vscode-input-border, transparent); background-color: var(--vscode-editor-background); border-bottom: none; - border-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; flex-direction: column; gap: 2px; overflow: hidden; @@ -1105,7 +1111,6 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; gap: 4px; cursor: pointer; - padding: 2px 0; justify-content: space-between; width: 100%; } @@ -1125,6 +1130,7 @@ have to be updated for changes to the rules above, or to support more deeply nes overflow: hidden; text-overflow: ellipsis; line-height: 16px; + margin-left: -1px; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section .codicon { @@ -1178,6 +1184,8 @@ have to be updated for changes to the rules above, or to support more deeply nes font-weight: normal; font-size: 11px; color: var(--vscode-foreground); + display: flex; + align-items: center; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index 65c93cb6bc9..c431e0bbd2c 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -117,7 +117,10 @@ suite('ChatTodoListWidget Accessibility', () => { const titleElement = expandoElement?.querySelector('.todo-list-title'); assert.ok(titleElement, 'Should have title element'); const titleText = titleElement?.textContent; - assert.ok(titleText?.includes('Todos (1/3)'), `Title should show progress format, but got: "${titleText}"`); + // When collapsed, title shows progress and current task: "Todos (2/3) - Second task" + // Progress is 2/3 because: 1 completed + 1 in-progress (current) = task 2 of 3 + assert.ok(titleText?.includes('Todos (2/3)'), `Title should show progress format, but got: "${titleText}"`); + assert.ok(titleText?.includes('Second task'), `Title should show current task when collapsed, but got: "${titleText}"`); }); test('hidden status text elements exist for screen readers', () => { widget.render('test-session'); @@ -175,10 +178,12 @@ suite('ChatTodoListWidget Accessibility', () => { const titleElement = widget.domNode.querySelector('#todo-list-title'); assert.ok(titleElement, 'Should have title element with ID'); - // Title should show progress format: "Todos (1/3)" since one todo is in-progress - // When collapsed, it also shows the current task: "Todos (1/3) - Second task" + // Title should show progress format: "Todos (2/3)" since one todo is completed and one is in-progress + // When collapsed, it also shows the current task: "Todos (2/3) - Second task" + // Progress is 2/3 because: 1 completed + 1 in-progress (current) = task 2 of 3 const titleText = titleElement?.textContent; - assert.ok(titleText?.includes('Todos (1/3)'), `Title should show progress format, but got: "${titleText}"`); + assert.ok(titleText?.includes('Todos (2/3)'), `Title should show progress format, but got: "${titleText}"`); + assert.ok(titleText?.includes('Second task'), `Title should show current task when collapsed, but got: "${titleText}"`); // Verify aria-labelledby connection works const todoListContainer = widget.domNode.querySelector('.todo-list-container'); From 9aebfc7c80311e416e84b4bc3dd77d955cc6d71f Mon Sep 17 00:00:00 2001 From: yavanosta Date: Fri, 24 Oct 2025 10:37:22 +0200 Subject: [PATCH 1573/4355] UriIdentityService: Extend disposable to manage child disposables --- .../uriIdentity/common/uriIdentityService.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/uriIdentity/common/uriIdentityService.ts b/src/vs/platform/uriIdentity/common/uriIdentityService.ts index ba9ca5b7ae8..3133c8fc6df 100644 --- a/src/vs/platform/uriIdentity/common/uriIdentityService.ts +++ b/src/vs/platform/uriIdentity/common/uriIdentityService.ts @@ -10,7 +10,7 @@ import { IFileService, FileSystemProviderCapabilities, IFileSystemProviderCapabi import { ExtUri, IExtUri, normalizePath } from '../../../base/common/resources.js'; import { SkipList } from '../../../base/common/skipList.js'; import { Event } from '../../../base/common/event.js'; -import { DisposableStore } from '../../../base/common/lifecycle.js'; +import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; class Entry { static _clock = 0; @@ -22,17 +22,17 @@ class Entry { } } -export class UriIdentityService implements IUriIdentityService { +export class UriIdentityService extends Disposable implements IUriIdentityService { declare readonly _serviceBrand: undefined; readonly extUri: IExtUri; - private readonly _dispooables = new DisposableStore(); private readonly _canonicalUris: SkipList; private readonly _limit = 2 ** 16; constructor(@IFileService private readonly _fileService: IFileService) { + super(); const schemeIgnoresPathCasingCache = new Map(); @@ -50,7 +50,7 @@ export class UriIdentityService implements IUriIdentityService { } return ignorePathCasing; }; - this._dispooables.add(Event.any( + this._register(Event.any( _fileService.onDidChangeFileSystemProviderRegistrations, _fileService.onDidChangeFileSystemProviderCapabilities )(e => { @@ -60,11 +60,7 @@ export class UriIdentityService implements IUriIdentityService { this.extUri = new ExtUri(ignorePathCasing); this._canonicalUris = new SkipList((a, b) => this.extUri.compare(a, b, true), this._limit); - } - - dispose(): void { - this._dispooables.dispose(); - this._canonicalUris.clear(); + this._register(toDisposable(() => this._canonicalUris.clear())); } asCanonicalUri(uri: URI): URI { From 92ff7ba9134c7323092a72035ab1cf7bacd303ca Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 24 Oct 2025 11:00:54 +0200 Subject: [PATCH 1574/4355] chatToolPicker: fix sorting (#273061) --- .../chat/browser/actions/chatToolPicker.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts index acf25cf9c3b..a2924d68c11 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolPicker.ts @@ -57,6 +57,7 @@ interface IBucketTreeItem extends IToolTreeItem { readonly status?: string; readonly children: AnyTreeItem[]; checked: boolean | 'mixed' | undefined; + readonly sortOrder: number; } /** @@ -316,6 +317,7 @@ export async function showToolsPicker( collapsed, children, buttons, + sortOrder: 2, }; const iconPath = mcpServer.serverMetadata.get()?.icons.getUrl(22); if (iconPath) { @@ -334,7 +336,8 @@ export async function showToolsPicker( children: [], buttons: [], collapsed: true, - iconClass: ThemeIcon.asClassName(Codicon.extensions) + iconClass: ThemeIcon.asClassName(Codicon.extensions), + sortOrder: 3, }; } else if (source.type === 'internal') { return { @@ -345,7 +348,8 @@ export async function showToolsPicker( checked: undefined, children: [], buttons: [], - collapsed: false + collapsed: false, + sortOrder: 1, }; } else { return { @@ -356,7 +360,8 @@ export async function showToolsPicker( checked: undefined, children: [], buttons: [], - collapsed: true + collapsed: true, + sortOrder: 4, }; } }; @@ -425,9 +430,22 @@ export async function showToolsPicker( } // Convert bucket map to sorted tree items - const sortedBuckets = Array.from(bucketMap.values()).sort((a, b) => a.ordinal - b.ordinal); - treeItems.push(...sortedBuckets); - + const sortedBuckets = Array.from(bucketMap.values()).sort((a, b) => { + if (a.sortOrder !== b.sortOrder) { + return a.sortOrder - b.sortOrder; + } + return a.label.localeCompare(b.label); + }); + for (const bucket of sortedBuckets) { + treeItems.push(bucket); + // Sort children alphabetically + bucket.children.sort((a, b) => a.label.localeCompare(b.label)); + for (const child of bucket.children) { + if (isToolSetTreeItem(child) && child.children) { + child.children.sort((a, b) => a.label.localeCompare(b.label)); + } + } + } if (treeItems.length === 0) { treePicker.placeholder = localize('noTools', "Add tools to chat"); } else { From 2a1e5087013fa7b6bce522d4d29c08e1b3600a1e Mon Sep 17 00:00:00 2001 From: yavanosta Date: Fri, 24 Oct 2025 11:10:18 +0200 Subject: [PATCH 1575/4355] UriIdentityService: Extract PathCasingCache in a separate class --- .../uriIdentity/common/uriIdentityService.ts | 88 +++++++++++++------ 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/uriIdentity/common/uriIdentityService.ts b/src/vs/platform/uriIdentity/common/uriIdentityService.ts index 3133c8fc6df..175a7c62ae2 100644 --- a/src/vs/platform/uriIdentity/common/uriIdentityService.ts +++ b/src/vs/platform/uriIdentity/common/uriIdentityService.ts @@ -9,7 +9,7 @@ import { InstantiationType, registerSingleton } from '../../instantiation/common import { IFileService, FileSystemProviderCapabilities, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent } from '../../files/common/files.js'; import { ExtUri, IExtUri, normalizePath } from '../../../base/common/resources.js'; import { SkipList } from '../../../base/common/skipList.js'; -import { Event } from '../../../base/common/event.js'; +import { Event, Emitter } from '../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; class Entry { @@ -22,43 +22,81 @@ class Entry { } } +interface IFileSystemCasingChangedEvent { + scheme: string; +} + +class PathCasingCache extends Disposable { + private readonly _cache = new Map(); + + private _onFileSystemCasingChanged: Emitter; + readonly onFileSystemCasingChanged: Event; + + constructor(private readonly _fileService: IFileService) { + super(); + + this._onFileSystemCasingChanged = this._register(new Emitter()); + this.onFileSystemCasingChanged = this._onFileSystemCasingChanged.event; + + this._register(Event.any< + | IFileSystemProviderCapabilitiesChangeEvent + | IFileSystemProviderRegistrationEvent + >( + _fileService.onDidChangeFileSystemProviderRegistrations, + _fileService.onDidChangeFileSystemProviderCapabilities + )(e => this._handleFileSystemProviderChangeEvent(e))); + } + + private _calculateIgnorePathCasing(scheme: string): boolean { + const uri = URI.from({ scheme }); + return this._fileService.hasProvider(uri) && + !this._fileService.hasCapability(uri, FileSystemProviderCapabilities.PathCaseSensitive); + } + + private _handleFileSystemProviderChangeEvent( + event: + | IFileSystemProviderRegistrationEvent + | IFileSystemProviderCapabilitiesChangeEvent) { + const currentCasing = this._cache.get(event.scheme); + if (currentCasing === undefined) { + return; + } + const newCasing = this._calculateIgnorePathCasing(event.scheme); + if (currentCasing === newCasing) { + return; + } + this._cache.set(event.scheme, newCasing); + this._onFileSystemCasingChanged.fire({ scheme: event.scheme }); + } + + public shouldIgnorePathCasing(uri: URI): boolean { + const cachedValue = this._cache.get(uri.scheme); + if (cachedValue !== undefined) { + return cachedValue; + } + + const ignorePathCasing = this._calculateIgnorePathCasing(uri.scheme); + this._cache.set(uri.scheme, ignorePathCasing); + return ignorePathCasing; + } +} + export class UriIdentityService extends Disposable implements IUriIdentityService { declare readonly _serviceBrand: undefined; readonly extUri: IExtUri; + private readonly _pathCasingCache: PathCasingCache; private readonly _canonicalUris: SkipList; private readonly _limit = 2 ** 16; constructor(@IFileService private readonly _fileService: IFileService) { super(); - const schemeIgnoresPathCasingCache = new Map(); - - // assume path casing matters unless the file system provider spec'ed the opposite. - // for all other cases path casing matters, e.g for - // * virtual documents - // * in-memory uris - // * all kind of "private" schemes - const ignorePathCasing = (uri: URI): boolean => { - let ignorePathCasing = schemeIgnoresPathCasingCache.get(uri.scheme); - if (ignorePathCasing === undefined) { - // retrieve once and then case per scheme until a change happens - ignorePathCasing = _fileService.hasProvider(uri) && !this._fileService.hasCapability(uri, FileSystemProviderCapabilities.PathCaseSensitive); - schemeIgnoresPathCasingCache.set(uri.scheme, ignorePathCasing); - } - return ignorePathCasing; - }; - this._register(Event.any( - _fileService.onDidChangeFileSystemProviderRegistrations, - _fileService.onDidChangeFileSystemProviderCapabilities - )(e => { - // remove from cache - schemeIgnoresPathCasingCache.delete(e.scheme); - })); + this._pathCasingCache = this._register(new PathCasingCache(this._fileService)); - this.extUri = new ExtUri(ignorePathCasing); + this.extUri = new ExtUri(uri => this._pathCasingCache.shouldIgnorePathCasing(uri)); this._canonicalUris = new SkipList((a, b) => this.extUri.compare(a, b, true), this._limit); this._register(toDisposable(() => this._canonicalUris.clear())); } From dfae0912e8fbd75ab55c67cdbd1522b68c4dc07e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 24 Oct 2025 11:20:32 +0200 Subject: [PATCH 1576/4355] agent files: add 'target' property' (#273066) --- .../contrib/chat/common/chatModes.ts | 20 ++- .../languageProviders/promptHovers.ts | 4 + .../languageProviders/promptValidator.ts | 23 +++- .../common/promptSyntax/promptFileParser.ts | 5 + .../promptSyntax/service/promptsService.ts | 5 + .../service/promptsServiceImpl.ts | 4 +- .../service/promptsService.test.ts | 114 ++++++++++++++++++ 7 files changed, 170 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 9d28797b778..90144dde5d7 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -103,6 +103,7 @@ export class ChatModeService extends Disposable implements IChatModeService { argumentHint: cachedMode.argumentHint, agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, handOffs: cachedMode.handOffs, + target: cachedMode.target, source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } }; const instance = new CustomChatMode(customChatMode); @@ -210,6 +211,7 @@ export interface IChatModeData { readonly handOffs?: readonly IHandOff[]; readonly uri?: URI; readonly source?: IChatModeSourceData; + readonly target?: string; } export interface IChatMode { @@ -226,6 +228,7 @@ export interface IChatMode { readonly modeInstructions?: IObservable; readonly uri?: IObservable; readonly source?: IAgentSource; + readonly target?: IObservable; } export interface IVariableReference { @@ -255,7 +258,8 @@ function isCachedChatModeData(data: unknown): data is IChatModeData { (mode.argumentHint === undefined || typeof mode.argumentHint === 'string') && (mode.handOffs === undefined || Array.isArray(mode.handOffs)) && (mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null)) && - (mode.source === undefined || isChatModeSourceData(mode.source)); + (mode.source === undefined || isChatModeSourceData(mode.source)) && + (mode.target === undefined || typeof mode.target === 'string'); } export class CustomChatMode implements IChatMode { @@ -266,6 +270,7 @@ export class CustomChatMode implements IChatMode { private readonly _modelObservable: ISettableObservable; private readonly _argumentHintObservable: ISettableObservable; private readonly _handoffsObservable: ISettableObservable; + private readonly _targetObservable: ISettableObservable; private _source: IAgentSource; public readonly id: string; @@ -311,6 +316,10 @@ export class CustomChatMode implements IChatMode { return this._source; } + get target(): IObservable { + return this._targetObservable; + } + public readonly kind = ChatModeKind.Agent; constructor( @@ -323,6 +332,7 @@ export class CustomChatMode implements IChatMode { this._modelObservable = observableValue('model', customChatMode.model); this._argumentHintObservable = observableValue('argumentHint', customChatMode.argumentHint); this._handoffsObservable = observableValue('handOffs', customChatMode.handOffs); + this._targetObservable = observableValue('target', customChatMode.target); this._modeInstructions = observableValue('_modeInstructions', customChatMode.agentInstructions); this._uriObservable = observableValue('uri', customChatMode.uri); this._source = customChatMode.source; @@ -339,6 +349,7 @@ export class CustomChatMode implements IChatMode { this._modelObservable.set(newData.model, tx); this._argumentHintObservable.set(newData.argumentHint, tx); this._handoffsObservable.set(newData.handOffs, tx); + this._targetObservable.set(newData.target, tx); this._modeInstructions.set(newData.agentInstructions, tx); this._uriObservable.set(newData.uri, tx); this._source = newData.source; @@ -357,7 +368,8 @@ export class CustomChatMode implements IChatMode { modeInstructions: this.modeInstructions.get(), uri: this.uri.get(), handOffs: this.handOffs.get(), - source: serializeChatModeSource(this._source) + source: serializeChatModeSource(this._source), + target: this.target.get() }; } } @@ -421,6 +433,10 @@ export class BuiltinChatMode implements IChatMode { return this.kind; } + get target(): IObservable { + return observableValue('target', undefined); + } + /** * Getters are not json-stringified */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index b28bf673133..2d83e9ab045 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -94,6 +94,10 @@ export class PromptHoverProvider implements HoverProvider { if (tools?.range.containsPosition(position)) { return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The set of tools that the custom agent has access to.')); } + const targetRange = header.getAttribute(PromptHeaderAttributes.target)?.range; + if (targetRange?.containsPosition(position)) { + return this.createHover(localize('promptHeader.agent.target', 'The target to which the header attributes like tools apply to.'), targetRange); + } } else { const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; if (descriptionRange?.containsPosition(position)) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 807af70c8cb..121feaf88c7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -150,6 +150,7 @@ export class PromptValidator { break; case PromptsType.agent: + this.validateTarget(attributes, report); this.validateTools(attributes, ChatModeKind.Agent, report); this.validateModel(attributes, ChatModeKind.Agent, report); this.validateHandoffs(attributes, report); @@ -391,12 +392,32 @@ export class PromptValidator { } } } + + private validateTarget(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined { + const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.target); + if (!attribute) { + return; + } + if (attribute.value.type !== 'string') { + report(toMarker(localize('promptValidator.targetMustBeString', "The 'target' attribute must be a string."), attribute.value.range, MarkerSeverity.Error)); + return; + } + const targetValue = attribute.value.value.trim(); + if (targetValue.length === 0) { + report(toMarker(localize('promptValidator.targetMustBeNonEmpty', "The 'target' attribute must be a non-empty string."), attribute.value.range, MarkerSeverity.Error)); + return; + } + const validTargets = ['github-copilot', 'vscode']; + if (!validTargets.includes(targetValue)) { + report(toMarker(localize('promptValidator.targetInvalidValue', "The 'target' attribute must be one of: {0}.", validTargets.join(', ')), attribute.value.range, MarkerSeverity.Error)); + } + } } const allAttributeNames = { [PromptsType.prompt]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint], [PromptsType.instructions]: [PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], - [PromptsType.agent]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint] + [PromptsType.agent]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target] }; const recommendedAttributeNames = { [PromptsType.prompt]: allAttributeNames[PromptsType.prompt].filter(name => !isNonRecommendedAttribute(name)), diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index 3e70621a415..3627109dee0 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -72,6 +72,7 @@ export namespace PromptHeaderAttributes { export const advancedOptions = 'advancedOptions'; export const argumentHint = 'argument-hint'; export const excludeAgent = 'excludeAgent'; + export const target = 'target'; } export class PromptHeader { @@ -168,6 +169,10 @@ export class PromptHeader { return this.getStringAttribute(PromptHeaderAttributes.argumentHint); } + public get target(): string | undefined { + return this.getStringAttribute(PromptHeaderAttributes.target); + } + public get tools(): string[] | undefined { const toolsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.tools); if (!toolsAttribute) { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index eab0c6be20d..b67c0b5c43e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -112,6 +112,11 @@ export interface ICustomAgent { */ readonly argumentHint?: string; + /** + * Target metadata in the prompt header. + */ + readonly target?: string; + /** * Contents of the custom agent file body and other agent instructions. */ diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index e0e78c1b07b..f3a1723b175 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -345,8 +345,8 @@ export class PromptsService extends Disposable implements IPromptsService { if (!ast.header) { return { uri, name, agentInstructions, source }; } - const { description, model, tools, handOffs, argumentHint } = ast.header; - return { uri, name, description, model, tools, handOffs, argumentHint, agentInstructions, source }; + const { description, model, tools, handOffs, argumentHint, target } = ast.header; + return { uri, name, description, model, tools, handOffs, argumentHint, target, agentInstructions, source }; }) ); return customAgents; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index fbc688244d9..b09333d9d55 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -787,6 +787,7 @@ suite('PromptsService', () => { model: undefined, argumentHint: undefined, tools: undefined, + target: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } }, @@ -850,6 +851,7 @@ suite('PromptsService', () => { handOffs: undefined, model: undefined, argumentHint: undefined, + target: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local }, }, @@ -930,6 +932,7 @@ suite('PromptsService', () => { }, handOffs: undefined, model: undefined, + target: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent1.agent.md'), source: { storage: PromptsStorage.local } }, @@ -945,6 +948,7 @@ suite('PromptsService', () => { handOffs: undefined, model: undefined, tools: undefined, + target: undefined, uri: URI.joinPath(rootFolderUri, '.github/agents/agent2.agent.md'), source: { storage: PromptsStorage.local } }, @@ -956,6 +960,116 @@ suite('PromptsService', () => { 'Must get custom agents with argumentHint.', ); }); + + test('header with target', async () => { + const rootFolderName = 'custom-agents-with-target'; + const rootFolder = `/${rootFolderName}`; + const rootFolderUri = URI.file(rootFolder); + + workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); + + await (instaService.createInstance(MockFilesystem, + [{ + name: rootFolderName, + children: [ + { + name: '.github/agents', + children: [ + { + name: 'github-agent.agent.md', + contents: [ + '---', + 'description: \'GitHub Copilot specialized agent.\'', + 'target: \'github-copilot\'', + 'tools: [ github-api, code-search ]', + '---', + 'I am optimized for GitHub Copilot workflows.', + ], + }, + { + name: 'vscode-agent.agent.md', + contents: [ + '---', + 'description: \'VS Code specialized agent.\'', + 'target: \'vscode\'', + 'model: \'gpt-4\'', + '---', + 'I am specialized for VS Code editor tasks.', + ], + }, + { + name: 'generic-agent.agent.md', + contents: [ + '---', + 'description: \'Generic agent without target.\'', + '---', + 'I work everywhere.', + ], + } + ], + + }, + ], + }])).mock(); + + const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) })); + const expected: ICustomAgent[] = [ + { + name: 'github-agent', + description: 'GitHub Copilot specialized agent.', + target: 'github-copilot', + tools: ['github-api', 'code-search'], + agentInstructions: { + content: 'I am optimized for GitHub Copilot workflows.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + model: undefined, + argumentHint: undefined, + uri: URI.joinPath(rootFolderUri, '.github/agents/github-agent.agent.md'), + source: { storage: PromptsStorage.local } + }, + { + name: 'vscode-agent', + description: 'VS Code specialized agent.', + target: 'vscode', + model: 'gpt-4', + agentInstructions: { + content: 'I am specialized for VS Code editor tasks.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + argumentHint: undefined, + tools: undefined, + uri: URI.joinPath(rootFolderUri, '.github/agents/vscode-agent.agent.md'), + source: { storage: PromptsStorage.local } + }, + { + name: 'generic-agent', + description: 'Generic agent without target.', + agentInstructions: { + content: 'I work everywhere.', + toolReferences: [], + metadata: undefined + }, + handOffs: undefined, + model: undefined, + argumentHint: undefined, + tools: undefined, + target: undefined, + uri: URI.joinPath(rootFolderUri, '.github/agents/generic-agent.agent.md'), + source: { storage: PromptsStorage.local } + }, + ]; + + assert.deepEqual( + result, + expected, + 'Must get custom agents with target attribute.', + ); + }); }); suite('listPromptFiles - extensions', () => { From 2b3de7cd5054bc1da66b18ba1eae9d622e3ebbb2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Oct 2025 11:26:49 +0200 Subject: [PATCH 1577/4355] Revert "fix: file-found Badge vertical (#235159) " (#273068) Revert "Center badge items in explorer find (#238757)" This reverts commit f8a575fa8a6c1ecdd8c086406d11b94b82a36ea5. --- src/vs/base/browser/ui/iconLabel/iconlabel.css | 2 -- .../workbench/contrib/files/browser/media/explorerviewlet.css | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index a1316d90558..f520d6e8bf9 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -46,8 +46,6 @@ overflow: hidden; text-overflow: ellipsis; flex: 1; - display: flex; - align-items: center; } .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name { diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index d34a939637c..a08c3754676 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -63,9 +63,7 @@ } .explorer-folders-view .monaco-list-row[aria-expanded="false"] .explorer-item.highlight-badge .monaco-count-badge { - display: inline-flex; - align-items: center; - justify-content: center; + display: inline-block; } .explorer-folders-view .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { From b26bab615af684579dcbf6ebf502e718211f27ba Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 24 Oct 2025 10:52:46 +0100 Subject: [PATCH 1578/4355] scm: increase codicon font-size for graph repository/history pickers to 16px --- src/vs/workbench/contrib/scm/browser/media/scm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 84f948a1148..8bf64ea4496 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -615,7 +615,7 @@ .monaco-toolbar .action-label.scm-graph-repository-picker .codicon, .monaco-toolbar .action-label.scm-graph-history-item-picker .codicon { - font-size: 14px; + font-size: 16px; color: inherit; } From efdafe855c5d304c9b89d2b4079157360d565e89 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 24 Oct 2025 10:58:17 +0100 Subject: [PATCH 1579/4355] scm: increase codicon font-size for history item labels to 16px --- src/vs/workbench/contrib/scm/browser/media/scm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 8bf64ea4496..60e5da00b7f 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -205,7 +205,7 @@ } .scm-view .monaco-list-row .history-item > .label-container > .label > .codicon { - font-size: 14px; + font-size: 16px; color: inherit !important; padding: 2px; } From ba3b946b5c1784199cea0077de5da4d4cc3a14b6 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 24 Oct 2025 11:16:17 +0100 Subject: [PATCH 1580/4355] scm: reduce history label codicon padding to 1px and increase hover codicon font-size to 16px --- src/vs/workbench/contrib/scm/browser/media/scm.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 60e5da00b7f..9d62bd5dd4d 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -207,7 +207,7 @@ .scm-view .monaco-list-row .history-item > .label-container > .label > .codicon { font-size: 16px; color: inherit !important; - padding: 2px; + padding: 1px; } .scm-view .monaco-list-row .history-item > .label-container > .label > .codicon.codicon-git-branch { @@ -572,7 +572,11 @@ .monaco-hover.history-item-hover .hover-row.status-bar .action .codicon { color: inherit; - font-size: 12px; + font-size: 16px; +} + +.monaco-hover.history-item-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span.codicon { + font-size: 16px; } /* Graph */ From f958c6f0e6e9e862c7b6ba438e256a1b0488d4be Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 24 Oct 2025 12:19:23 +0200 Subject: [PATCH 1581/4355] Improves fetch log (#273072) --- .../browser/model/inlineCompletionsSource.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index ac8d6ff2516..9303f54ea27 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -268,8 +268,9 @@ export class InlineCompletionsSource extends Disposable { const result = suggestions.map(c => ({ range: c.editRange.toString(), text: c.insertText, - isInlineEdit: !!c.isInlineEdit, - source: c.source.provider.groupId, + isInlineEdit: c.isInlineEdit, + showInlineEditMenu: c.showInlineEditMenu, + providerId: c.source.provider.providerId?.toString(), })); this._log({ sourceId: 'InlineCompletions.fetch', kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now(), didAllProvidersReturn }); } From e6e3d31ffcfb9d5ed8fa52f7379d4507cedce477 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:27:10 -0700 Subject: [PATCH 1582/4355] Add tree sitter command parser tests Fixes #272780 --- .../treeSitterCommandParser.test.ts | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts new file mode 100644 index 00000000000..0b93620a2db --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { deepStrictEqual } from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import type { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { ITreeSitterLibraryService } from '../../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; +import { FileService } from '../../../../../../platform/files/common/fileService.js'; +import { NullLogService } from '../../../../../../platform/log/common/log.js'; +import { Schemas } from '../../../../../../base/common/network.js'; +import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; +import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../../browser/treeSitterCommandParser.js'; + +suite('TreeSitterCommandParser', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + let parser: TreeSitterCommandParser; + + setup(() => { + const logService = new NullLogService(); + const fileService = store.add(new FileService(logService)); + const fileSystemProvider = new TestIPCFileSystemProvider(); + store.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); + + instantiationService = workbenchInstantiationService({ + fileService: () => fileService, + }, store); + + const treeSitterLibraryService = store.add(instantiationService.createInstance(TreeSitterLibraryService)); + treeSitterLibraryService.isTest = true; + instantiationService.stub(ITreeSitterLibraryService, treeSitterLibraryService); + + parser = instantiationService.createInstance(TreeSitterCommandParser); + }); + + suite('Bash command parsing', () => { + async function t(commandLine: string, expectedCommands: string[]) { + const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.Bash, commandLine); + deepStrictEqual(result, expectedCommands); + } + + test('simple commands', () => t('ls -la', ['ls -la'])); + test('commands with &&', () => t('echo hello && ls -la', ['echo hello', 'ls -la'])); + test('commands with ||', () => t('test -f file.txt || touch file.txt', ['test -f file.txt', 'touch file.txt'])); + test('commands with semicolons', () => t('cd /tmp; ls; pwd', ['cd /tmp', 'ls', 'pwd'])); + test('pipe chains', () => t('cat file.txt | grep pattern | sort | uniq', ['cat file.txt', 'grep pattern', 'sort', 'uniq'])); + test('commands with subshells', () => t('echo $(date +%Y) && ls', ['echo $(date +%Y)', 'date +%Y', 'ls'])); + test('complex quoting', () => t('echo "hello && world" && echo \'test\'', ['echo "hello && world"', 'echo \'test\''])); + test('escaped characters', () => t('echo hello\\ world && ls', ['echo hello\\ world', 'ls'])); + test('background commands', () => t('sleep 10 & echo done', ['sleep 10', 'echo done'])); + test('variable assignments', () => t('VAR=value command1 && echo $VAR', ['VAR=value command1', 'echo $VAR'])); + test('redirections', () => t('echo hello > file.txt && cat < file.txt', ['echo hello', 'cat'])); + test('arithmetic expansion', () => t('echo $((1 + 2)) && ls', ['echo $((1 + 2))', 'ls'])); + + suite('control flow and structures', () => { + test('if-then-else', () => t('if [ -f file.txt ]; then cat file.txt; else echo "not found"; fi', ['cat file.txt', 'echo "not found"'])); + test('simple iteration', () => t('for file in *.txt; do cat "$file"; done', ['cat "$file"'])); + test('function declaration and call', () => t('function test_func() { echo "inside function"; } && test_func', ['echo "inside function"', 'test_func'])); + test('heredoc with commands', () => t('cat << EOF\nhello\nworld\nEOF\necho done', ['cat', 'echo done'])); + }); + + suite('edge cases', () => { + test('malformed syntax', () => t('echo "unclosed quote && ls', ['echo'])); + test('unmatched parentheses', () => t('echo $(missing closing && ls', ['echo $(missing closing && ls', 'missing closing', 'ls'])); + test('very long command lines', () => t('echo ' + 'a'.repeat(10000) + ' && ls', ['echo ' + 'a'.repeat(10000), 'ls'])); + test('special characters', () => t('echo "πλαςε 测试 🚀" && ls', ['echo "πλαςε 测试 🚀"', 'ls'])); + test('multiline with continuations', () => t('echo hello \\\n&& echo world \\\n&& ls', ['echo hello', 'echo world', 'ls'])); + test('commands with comments', () => t('echo hello # this is a comment\nls # another comment', ['echo hello', 'ls'])); + }); + + // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704 + suite('real-world scenarios', () => { + test('complex Docker commands', () => t('docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', ['docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', 'pwd'])); + test('Git workflow commands', () => t('git add . && git commit -m "Update feature" && git push origin main', [ + 'git add .', + 'git commit -m "Update feature"', + 'git push origin main' + ])); + test('npm/yarn workflow commands', () => t('npm ci && npm run build && npm test && npm run lint', [ + 'npm ci', + 'npm run build', + 'npm test', + 'npm run lint' + ])); + test('build system commands', () => t('make clean && make -j$(nproc) && make install PREFIX=/usr/local', [ + 'make clean', + 'make -j$(nproc)', + 'nproc', + 'make install PREFIX=/usr/local' + ])); + }); + }); + + suite('PowerShell command parsing', () => { + async function t(commandLine: string, expectedCommands: string[]) { + const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.PowerShell, commandLine); + deepStrictEqual(result, expectedCommands); + } + + test('simple commands', () => t('Get-ChildItem -Path C:\\', ['Get-ChildItem -Path C:\\'])); + test('commands with semicolons', () => t('Get-Date; Get-Location; Write-Host "done"', ['Get-Date', 'Get-Location', 'Write-Host "done"'])); + test('pipeline commands', () => t('Get-Process | Where-Object {$_.CPU -gt 100} | Sort-Object CPU', ['Get-Process ', 'Where-Object {$_.CPU -gt 100} ', 'Sort-Object CPU'])); + test('command substitution', () => t('Write-Host $(Get-Date) ; Get-Location', ['Write-Host $(Get-Date)', 'Get-Date', 'Get-Location'])); + test('complex parameters', () => t('Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"', ['Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"'])); + test('splatting', () => t('$params = @{Path="C:\\"; Recurse=$true}; Get-ChildItem @params', ['Get-ChildItem @params'])); + test('here-strings', () => t('Write-Host @"\nhello\nworld\n"@ ; Get-Date', ['Write-Host @"\nhello\nworld\n"@', 'Get-Date'])); + test('method calls', () => t('"hello".ToUpper() ; Get-Date', ['Get-Date'])); + test('complex quoting', () => t('Write-Host "She said `"Hello`"" ; Write-Host \'Single quotes\'', ['Write-Host "She said `"Hello`""', 'Write-Host \'Single quotes\''])); + + suite('Control flow and structures', () => { + test('logical and', () => t('Test-Path "file.txt" -and Get-Content "file.txt"', ['Test-Path "file.txt" -and Get-Content "file.txt"'])); + test('foreach with script block', () => t('ForEach-Object { Write-Host $_.Name } ; Get-Date', ['ForEach-Object { Write-Host $_.Name }', 'Write-Host $_.Name', 'Get-Date'])); + test('if-else', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" } else { Write-Host "not found" }', ['Test-Path "file.txt"', 'Get-Content "file.txt"', 'Write-Host "not found"'])); + test('error handling', () => t('try { Get-Content "file.txt" } catch { Write-Error "failed" }', ['Get-Content "file.txt"', 'Write-Error "failed"'])); + }); + }); + + suite('all shell command parsing', () => { + async function t(commandLine: string, expectedCommands: string[]) { + for (const shell of [TreeSitterCommandParserLanguage.Bash, TreeSitterCommandParserLanguage.PowerShell]) { + const result = await parser.extractSubCommands(shell, commandLine); + deepStrictEqual(result, expectedCommands); + } + } + + suite('edge cases', () => { + test('empty strings', () => t('', [])); + test('whitespace-only strings', () => t(' \n\t ', [])); + }); + }); +}); From f649b7c167888d840b8304d7f40d14489a70a98f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:36:58 -0700 Subject: [PATCH 1583/4355] More bash/pwsh tests --- .../treeSitterCommandParser.test.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 0b93620a2db..7e1f6412fc9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -56,12 +56,23 @@ suite('TreeSitterCommandParser', () => { test('variable assignments', () => t('VAR=value command1 && echo $VAR', ['VAR=value command1', 'echo $VAR'])); test('redirections', () => t('echo hello > file.txt && cat < file.txt', ['echo hello', 'cat'])); test('arithmetic expansion', () => t('echo $((1 + 2)) && ls', ['echo $((1 + 2))', 'ls'])); + test('nested command substitution', () => t('echo $(cat $(echo file.txt)) && ls', ['echo $(cat $(echo file.txt))', 'cat $(echo file.txt)', 'echo file.txt', 'ls'])); + test('mixed operators', () => t('cmd1 && cmd2 || cmd3; cmd4 | cmd5 & cmd6', ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd5', 'cmd6'])); + test('parameter expansion', () => t('echo ${VAR:-default} && echo ${#VAR}', ['echo ${VAR:-default}', 'echo ${#VAR}'])); + test('process substitution', () => t('diff <(sort file1) <(sort file2) && echo done', ['diff <(sort file1) <(sort file2)', 'sort file1', 'sort file2', 'echo done'])); + test('brace expansion', () => t('echo {a,b,c}.txt && ls', ['echo {a,b,c}.txt', 'ls'])); + test('tilde expansion', () => t('cd ~/Documents && ls ~/.bashrc', ['cd ~/Documents', 'ls ~/.bashrc'])); suite('control flow and structures', () => { test('if-then-else', () => t('if [ -f file.txt ]; then cat file.txt; else echo "not found"; fi', ['cat file.txt', 'echo "not found"'])); test('simple iteration', () => t('for file in *.txt; do cat "$file"; done', ['cat "$file"'])); test('function declaration and call', () => t('function test_func() { echo "inside function"; } && test_func', ['echo "inside function"', 'test_func'])); test('heredoc with commands', () => t('cat << EOF\nhello\nworld\nEOF\necho done', ['cat', 'echo done'])); + test('while loop', () => t('while read line; do echo "$line"; done < file.txt', ['read line', 'echo "$line"'])); + test('case statement', () => t('case $var in pattern1) echo "match1" ;; pattern2) echo "match2" ;; esac', ['echo "match1"', 'echo "match2"'])); + test('until loop', () => t('until [ -f ready.txt ]; do sleep 1; done && echo ready', ['sleep 1', 'echo ready'])); + test('nested conditionals', () => t('if [ -f file ]; then if [ -r file ]; then cat file; fi; fi', ['cat file'])); + test('test command alternatives', () => t('[[ -f file ]] && cat file || echo missing', ['cat file', 'echo missing'])); }); suite('edge cases', () => { @@ -71,6 +82,14 @@ suite('TreeSitterCommandParser', () => { test('special characters', () => t('echo "πλαςε 测试 🚀" && ls', ['echo "πλαςε 测试 🚀"', 'ls'])); test('multiline with continuations', () => t('echo hello \\\n&& echo world \\\n&& ls', ['echo hello', 'echo world', 'ls'])); test('commands with comments', () => t('echo hello # this is a comment\nls # another comment', ['echo hello', 'ls'])); + test('empty command in chain', () => t('echo hello && && echo world', ['echo hello', 'echo world'])); + test('trailing operators', () => t('echo hello &&', ['echo hello', ''])); + test('only operators', () => t('&& || ;', [])); + test('nested quotes', () => t('echo "outer \"inner\" outer" && ls', ['echo "outer \"inner\" outer"', 'ls'])); + test('incomplete escape sequences', () => t('echo hello\\ && ls', ['echo hello\\ ', 'ls'])); + test('mixed quote types', () => t('echo "hello \`world\`" && echo \'test\'', ['echo "hello \`world\`"', 'world', 'echo \'test\''])); + test('deeply nested structures', () => t('echo $(echo $(echo $(echo nested))) && ls', ['echo $(echo $(echo $(echo nested)))', 'echo $(echo $(echo nested))', 'echo $(echo nested)', 'echo nested', 'ls'])); + test('unicode command names', () => t('测试命令 && echo done', ['测试命令', 'echo done'])); }); // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704 @@ -93,6 +112,32 @@ suite('TreeSitterCommandParser', () => { 'nproc', 'make install PREFIX=/usr/local' ])); + test('deployment script', () => t('rsync -avz --delete src/ user@server:/path/ && ssh user@server "systemctl restart service" && echo "Deployed successfully"', [ + 'rsync -avz --delete src/ user@server:/path/', + 'ssh user@server "systemctl restart service"', + 'echo "Deployed successfully"' + ])); + test('database backup script', () => t('mysqldump -u user -p database > backup_$(date +%Y%m%d).sql && gzip backup_$(date +%Y%m%d).sql && echo "Backup complete"', [ + 'mysqldump -u user -p database', + 'date +%Y%m%d', + 'gzip backup_$(date +%Y%m%d).sql', + 'date +%Y%m%d', + 'echo "Backup complete"' + ])); + test('log analysis pipeline', () => t('tail -f /var/log/app.log | grep ERROR | while read line; do echo "$(date): $line" >> error.log; done', [ + 'tail -f /var/log/app.log', + 'grep ERROR', + 'read line', + 'echo "$(date): $line"', + 'date' + ])); + test('conditional installation', () => t('which docker || (curl -fsSL https://get.docker.com | sh && systemctl enable docker) && docker --version', [ + 'which docker', + 'curl -fsSL https://get.docker.com', + 'sh', + 'systemctl enable docker', + 'docker --version' + ])); }); }); @@ -111,12 +156,27 @@ suite('TreeSitterCommandParser', () => { test('here-strings', () => t('Write-Host @"\nhello\nworld\n"@ ; Get-Date', ['Write-Host @"\nhello\nworld\n"@', 'Get-Date'])); test('method calls', () => t('"hello".ToUpper() ; Get-Date', ['Get-Date'])); test('complex quoting', () => t('Write-Host "She said `"Hello`"" ; Write-Host \'Single quotes\'', ['Write-Host "She said `"Hello`""', 'Write-Host \'Single quotes\''])); + test('array operations', () => t('$arr = @(1,2,3); $arr | ForEach-Object { $_ * 2 }', ['ForEach-Object { $_ * 2 }'])); + test('hashtable operations', () => t('$hash = @{key="value"}; Write-Host $hash.key', ['Write-Host $hash.key'])); + test('type casting', () => t('[int]"123" + [int]"456" ; Write-Host "done"', ['Write-Host "done"'])); + test('regex operations', () => t('"hello world" -match "w.*d" ; Get-Date', ['Get-Date'])); + test('comparison operators', () => t('5 -gt 3 -and "hello" -like "h*" ; Write-Host "true"', ['Write-Host "true"'])); + test('null-conditional operators', () => t('$obj?.Property?.SubProperty ; Get-Date', ['Get-Date'])); + test('string interpolation', () => t('$name="World"; "Hello $name" ; Get-Date', ['Get-Date'])); + test('expandable strings', () => t('$var="test"; "Value: $($var.ToUpper())" ; Get-Date', ['Get-Date'])); suite('Control flow and structures', () => { test('logical and', () => t('Test-Path "file.txt" -and Get-Content "file.txt"', ['Test-Path "file.txt" -and Get-Content "file.txt"'])); test('foreach with script block', () => t('ForEach-Object { Write-Host $_.Name } ; Get-Date', ['ForEach-Object { Write-Host $_.Name }', 'Write-Host $_.Name', 'Get-Date'])); test('if-else', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" } else { Write-Host "not found" }', ['Test-Path "file.txt"', 'Get-Content "file.txt"', 'Write-Host "not found"'])); test('error handling', () => t('try { Get-Content "file.txt" } catch { Write-Error "failed" }', ['Get-Content "file.txt"', 'Write-Error "failed"'])); + test('switch statement', () => t('switch ($var) { 1 { "one" } 2 { "two" } default { "other" } } ; Get-Date', ['Get-Date'])); + test('do-while loop', () => t('do { Write-Host $i; $i++ } while ($i -lt 5) ; Get-Date', ['Write-Host $i', 'Get-Date'])); + test('for loop', () => t('for ($i=0; $i -lt 5; $i++) { Write-Host $i } ; Get-Date', ['Write-Host $i', 'Get-Date'])); + test('foreach loop with range', () => t('foreach ($i in 1..5) { Write-Host $i } ; Get-Date', ['1..5', 'Write-Host $i', 'Get-Date'])); + test('break and continue', () => t('while ($true) { if ($condition) { break } ; Write-Host "running" } ; Get-Date', ['Write-Host "running"', 'Get-Date'])); + test('nested try-catch-finally', () => t('try { try { Get-Content "file" } catch { throw } } catch { Write-Error "outer" } finally { Write-Host "cleanup" }', ['Get-Content "file"', 'Write-Error "outer"', 'Write-Host "cleanup"'])); + test('parallel processing', () => t('1..10 | ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ } ; Get-Date', ['1..10 ', 'ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ }', 'Start-Sleep 1', 'Write-Host $_', 'Get-Date'])); }); }); From fe455cd3f5b3e5b6b24004452bbaf4ce490e52ba Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:41:12 -0700 Subject: [PATCH 1584/4355] Revert "Merge pull request #272886 from microsoft/tyriar/272852" This reverts commit 08221e77c3e6f1eb810eb0219d3abe72caf1b37e, reversing changes made to 9b59d31df279bd7358c0e8933f1eb32a57841089. --- .../terminal/browser/terminalService.ts | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index ee1546ab0c4..d6fc8a493a8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -29,7 +29,6 @@ import { IThemeService, Themable } from '../../../../platform/theme/common/theme import { ThemeIcon } from '../../../../base/common/themables.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { VirtualWorkspaceContext } from '../../../common/contextkeys.js'; - import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalConfigurationService, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from './terminal.js'; import { getCwdForSplit } from './terminalActions.js'; import { TerminalEditorInput } from './terminalEditorInput.js'; @@ -58,6 +57,11 @@ import { isAuxiliaryWindow, mainWindow } from '../../../../base/browser/window.j import { GroupIdentifier } from '../../../common/editor.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; +interface IBackgroundTerminal { + instance: ITerminalInstance; + terminalLocationOptions?: ITerminalLocationOptions; +} + export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; @@ -68,7 +72,7 @@ export class TerminalService extends Disposable implements ITerminalService { private readonly _terminalShellTypeContextKey: IContextKey; private _isShuttingDown: boolean = false; - private _backgroundedTerminalInstances: ITerminalInstance[] = []; + private _backgroundedTerminalInstances: IBackgroundTerminal[] = []; private _backgroundedTerminalDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -90,7 +94,7 @@ export class TerminalService extends Disposable implements ITerminalService { get restoredGroupCount(): number { return this._restoredGroupCount; } get instances(): ITerminalInstance[] { - return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances); + return this._terminalGroupService.instances.concat(this._terminalEditorService.instances).concat(this._backgroundedTerminalInstances.map(bg => bg.instance)); } /** Gets all non-background terminals. */ get foregroundInstances(): ITerminalInstance[] { @@ -470,7 +474,8 @@ export class TerminalService extends Disposable implements ITerminalService { if (layoutInfo && (layoutInfo.tabs.length > 0 || layoutInfo?.background?.length)) { mark('code/terminal/willRecreateTerminalGroups'); this._reconnectedTerminalGroups = this._recreateTerminalGroups(layoutInfo); - this._backgroundedTerminalInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); + const revivedInstances = await this._reviveBackgroundTerminalInstances(layoutInfo.background || []); + this._backgroundedTerminalInstances = revivedInstances.map(instance => ({ instance })); mark('code/terminal/didRecreateTerminalGroups'); } // now that terminals have been restored, @@ -692,7 +697,7 @@ export class TerminalService extends Disposable implements ITerminalService { // Don't touch processes if the shutdown was a result of reload as they will be reattached const shouldPersistTerminals = this._terminalConfigurationService.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD; - for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances]) { + for (const instance of [...this._terminalGroupService.instances, ...this._backgroundedTerminalInstances.map(bg => bg.instance)]) { if (shouldPersistTerminals && instance.shouldPersist) { instance.detachProcessAndDispose(TerminalExitReason.Shutdown); } else { @@ -716,7 +721,7 @@ export class TerminalService extends Disposable implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; + const state: ITerminalsLayoutInfoById = { tabs, background: this._backgroundedTerminalInstances.map(bg => bg.instance).filter(i => i.shellLaunchConfig.forcePersist).map(i => i.persistentProcessId).filter((e): e is number => e !== undefined) }; this._primaryBackend?.setTerminalLayoutInfo(state); } @@ -746,13 +751,13 @@ export class TerminalService extends Disposable implements ITerminalService { getInstanceFromId(terminalId: number): ITerminalInstance | undefined { let bgIndex = -1; - this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { - if (terminalInstance.instanceId === terminalId) { + this._backgroundedTerminalInstances.forEach((bg, i) => { + if (bg.instance.instanceId === terminalId) { bgIndex = i; } }); if (bgIndex !== -1) { - return this._backgroundedTerminalInstances[bgIndex]; + return this._backgroundedTerminalInstances[bgIndex].instance; } try { return this.instances[this._getIndexFromId(terminalId)]; @@ -1001,21 +1006,6 @@ export class TerminalService extends Disposable implements ITerminalService { if (!shellLaunchConfig.customPtyImplementation && !this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } - if (shellLaunchConfig.hideFromUser) { - const instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Panel); - this._backgroundedTerminalInstances.push(instance); - this._backgroundedTerminalDisposables.set(instance.instanceId, [ - instance.onDisposed(instance => { - const idx = this._backgroundedTerminalInstances.indexOf(instance); - if (idx !== -1) { - this._backgroundedTerminalInstances.splice(idx, 1); - } - this._onDidDisposeInstance.fire(instance); - }) - ]); - this._terminalHasBeenCreated.set(true); - return instance; - } this._evaluateLocalCwd(shellLaunchConfig); const location = await this.resolveLocation(options?.location) || this._terminalConfigurationService.defaultLocation; @@ -1031,6 +1021,20 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance.shellType) { this._extensionService.activateByEvent(`onTerminal:${instance.shellType}`); } + + if (shellLaunchConfig.hideFromUser) { + this._backgroundedTerminalInstances.push({ instance, terminalLocationOptions: options?.location }); + this._backgroundedTerminalDisposables.set(instance.instanceId, [ + instance.onDisposed(instance => { + const idx = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); + if (idx !== -1) { + this._backgroundedTerminalInstances.splice(idx, 1); + } + this._onDidDisposeInstance.fire(instance); + }) + ]); + } + return instance; } @@ -1115,10 +1119,12 @@ export class TerminalService extends Disposable implements ITerminalService { private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance { let instance; - const editorOptions = this._getEditorOptions(options?.location); if (location === TerminalLocation.Editor) { instance = this._terminalInstanceService.createInstance(shellLaunchConfig, TerminalLocation.Editor); - this._terminalEditorService.openEditor(instance, editorOptions); + if (!shellLaunchConfig.hideFromUser) { + const editorOptions = this._getEditorOptions(options?.location); + this._terminalEditorService.openEditor(instance, editorOptions); + } } else { // TODO: pass resource? const group = this._terminalGroupService.createGroup(shellLaunchConfig); @@ -1181,21 +1187,27 @@ export class TerminalService extends Disposable implements ITerminalService { } public async showBackgroundTerminal(instance: ITerminalInstance, suppressSetActive?: boolean): Promise { - const index = this._backgroundedTerminalInstances.indexOf(instance); + const index = this._backgroundedTerminalInstances.findIndex(bg => bg.instance === instance); if (index === -1) { return; } - this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); + const backgroundTerminal = this._backgroundedTerminalInstances[index]; + this._backgroundedTerminalInstances.splice(index, 1); const disposables = this._backgroundedTerminalDisposables.get(instance.instanceId); if (disposables) { dispose(disposables); } this._backgroundedTerminalDisposables.delete(instance.instanceId); - this._terminalGroupService.createGroup(instance); + if (instance.target === TerminalLocation.Panel) { + this._terminalGroupService.createGroup(instance); - // Make active automatically if it's the first instance - if (this.instances.length === 1 && !suppressSetActive) { - this._terminalGroupService.setActiveInstanceByIndex(0); + // Make active automatically if it's the first instance + if (this.instances.length === 1 && !suppressSetActive) { + this._terminalGroupService.setActiveInstanceByIndex(0); + } + } else { + const editorOptions = backgroundTerminal.terminalLocationOptions ? this._getEditorOptions(backgroundTerminal.terminalLocationOptions) : this._getEditorOptions(instance.target); + this._terminalEditorService.openEditor(instance, editorOptions); } this._onDidChangeInstances.fire(); From 977e3df0734229ebace11727cfea548ebb2cc6d5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 04:15:44 -0700 Subject: [PATCH 1585/4355] Take 2 at fixing hideFromUser editor terminals Fixes #272656 --- .../terminal/browser/terminalService.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index d6fc8a493a8..884828d1d94 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -1009,20 +1009,9 @@ export class TerminalService extends Disposable implements ITerminalService { this._evaluateLocalCwd(shellLaunchConfig); const location = await this.resolveLocation(options?.location) || this._terminalConfigurationService.defaultLocation; - const parent = await this._getSplitParent(options?.location); - this._terminalHasBeenCreated.set(true); - this._extensionService.activateByEvent('onTerminal:*'); - let instance; - if (parent) { - instance = this._splitTerminal(shellLaunchConfig, location, parent); - } else { - instance = this._createTerminal(shellLaunchConfig, location, options); - } - if (instance.shellType) { - this._extensionService.activateByEvent(`onTerminal:${instance.shellType}`); - } if (shellLaunchConfig.hideFromUser) { + const instance = this._terminalInstanceService.createInstance(shellLaunchConfig, location); this._backgroundedTerminalInstances.push({ instance, terminalLocationOptions: options?.location }); this._backgroundedTerminalDisposables.set(instance.instanceId, [ instance.onDisposed(instance => { @@ -1033,6 +1022,20 @@ export class TerminalService extends Disposable implements ITerminalService { this._onDidDisposeInstance.fire(instance); }) ]); + return instance; + } + + const parent = await this._getSplitParent(options?.location); + this._terminalHasBeenCreated.set(true); + this._extensionService.activateByEvent('onTerminal:*'); + let instance; + if (parent) { + instance = this._splitTerminal(shellLaunchConfig, location, parent); + } else { + instance = this._createTerminal(shellLaunchConfig, location, options); + } + if (instance.shellType) { + this._extensionService.activateByEvent(`onTerminal:${instance.shellType}`); } return instance; From edff076b3ae7e6147430f32440818068fe3bf145 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 24 Oct 2025 04:17:45 -0700 Subject: [PATCH 1586/4355] Hookup semantic tokens range refresh notification to the viewport contribution (#271419) * Hookup the semantic tokens range refresh notification to the viewport contribution * cleanup event subscription and dispose --- .../browser/viewportSemanticTokens.ts | 34 ++++- .../browser/viewportSemanticTokens.test.ts | 125 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/vs/editor/contrib/semanticTokens/test/browser/viewportSemanticTokens.test.ts diff --git a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts index 1662f1fec59..b0a434539db 100644 --- a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from '../../../../base/common/async.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, dispose, IDisposable } from '../../../../base/common/lifecycle.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js'; import { Range } from '../../../common/core/range.js'; @@ -35,6 +35,7 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE private readonly _debounceInformation: IFeatureDebounceInformation; private readonly _tokenizeViewport: RunOnceScheduler; private _outstandingRequests: CancelablePromise[]; + private _rangeProvidersChangeListeners: IDisposable[]; constructor( editor: ICodeEditor, @@ -50,15 +51,33 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentRangeSemanticTokens', { min: 100, max: 500 }); this._tokenizeViewport = this._register(new RunOnceScheduler(() => this._tokenizeViewportNow(), 100)); this._outstandingRequests = []; + this._rangeProvidersChangeListeners = []; const scheduleTokenizeViewport = () => { if (this._editor.hasModel()) { this._tokenizeViewport.schedule(this._debounceInformation.get(this._editor.getModel())); } }; + const bindRangeProvidersChangeListeners = () => { + this._cleanupProviderListeners(); + if (this._editor.hasModel()) { + const model = this._editor.getModel(); + for (const provider of this._provider.all(model)) { + const disposable = provider.onDidChange?.(() => { + this._cancelAll(); + scheduleTokenizeViewport(); + }); + if (disposable) { + this._rangeProvidersChangeListeners.push(disposable); + } + } + } + }; + this._register(this._editor.onDidScrollChange(() => { scheduleTokenizeViewport(); })); this._register(this._editor.onDidChangeModel(() => { + bindRangeProvidersChangeListeners(); this._cancelAll(); scheduleTokenizeViewport(); })); @@ -66,7 +85,10 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE this._cancelAll(); scheduleTokenizeViewport(); })); + + bindRangeProvidersChangeListeners(); this._register(this._provider.onDidChange(() => { + bindRangeProvidersChangeListeners(); this._cancelAll(); scheduleTokenizeViewport(); })); @@ -83,6 +105,16 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE scheduleTokenizeViewport(); } + public override dispose(): void { + this._cleanupProviderListeners(); + super.dispose(); + } + + private _cleanupProviderListeners(): void { + dispose(this._rangeProvidersChangeListeners); + this._rangeProvidersChangeListeners = []; + } + private _cancelAll(): void { for (const request of this._outstandingRequests) { request.cancel(); diff --git a/src/vs/editor/contrib/semanticTokens/test/browser/viewportSemanticTokens.test.ts b/src/vs/editor/contrib/semanticTokens/test/browser/viewportSemanticTokens.test.ts new file mode 100644 index 00000000000..339151a121a --- /dev/null +++ b/src/vs/editor/contrib/semanticTokens/test/browser/viewportSemanticTokens.test.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { Barrier, timeout } from '../../../../../base/common/async.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { mock } from '../../../../../base/test/common/mock.js'; +import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { Range } from '../../../../common/core/range.js'; +import { DocumentRangeSemanticTokensProvider, SemanticTokens, SemanticTokensLegend } from '../../../../common/languages.js'; +import { ILanguageService } from '../../../../common/languages/language.js'; +import { ITextModel } from '../../../../common/model.js'; +import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; +import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js'; +import { LanguageService } from '../../../../common/services/languageService.js'; +import { ISemanticTokensStylingService } from '../../../../common/services/semanticTokensStyling.js'; +import { SemanticTokensStylingService } from '../../../../common/services/semanticTokensStylingService.js'; +import { ViewportSemanticTokensContribution } from '../../browser/viewportSemanticTokens.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js'; +import { NullLogService } from '../../../../../platform/log/common/log.js'; +import { ColorScheme } from '../../../../../platform/theme/common/theme.js'; +import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { TestColorTheme, TestThemeService } from '../../../../../platform/theme/test/common/testThemeService.js'; +import { createTextModel } from '../../../../test/common/testTextModel.js'; +import { createTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; +import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; +import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; + +suite('ViewportSemanticTokens', () => { + + const disposables = new DisposableStore(); + let languageService: ILanguageService; + let languageFeaturesService: ILanguageFeaturesService; + let serviceCollection: ServiceCollection; + + setup(() => { + const configService = new TestConfigurationService({ editor: { semanticHighlighting: true } }); + const themeService = new TestThemeService(); + themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true)); + languageFeaturesService = new LanguageFeaturesService(); + languageService = disposables.add(new LanguageService(false)); + + const logService = new NullLogService(); + const semanticTokensStylingService = new SemanticTokensStylingService(themeService, logService, languageService); + const envService = new class extends mock() { + override isBuilt: boolean = true; + override isExtensionDevelopment: boolean = false; + }; + const languageFeatureDebounceService = new LanguageFeatureDebounceService(logService, envService); + + serviceCollection = new ServiceCollection( + [ILanguageFeaturesService, languageFeaturesService], + [ILanguageFeatureDebounceService, languageFeatureDebounceService], + [ISemanticTokensStylingService, semanticTokensStylingService], + [IThemeService, themeService], + [IConfigurationService, configService] + ); + }); + + teardown(() => { + disposables.clear(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('DocumentRangeSemanticTokens provider onDidChange event should trigger refresh', async () => { + await runWithFakedTimers({}, async () => { + + disposables.add(languageService.registerLanguage({ id: 'testMode' })); + + const inFirstCall = new Barrier(); + const inRefreshCall = new Barrier(); + + const emitter = new Emitter(); + let requestCount = 0; + disposables.add(languageFeaturesService.documentRangeSemanticTokensProvider.register('testMode', new class implements DocumentRangeSemanticTokensProvider { + onDidChange = emitter.event; + getLegend(): SemanticTokensLegend { + return { tokenTypes: ['class'], tokenModifiers: [] }; + } + async provideDocumentRangeSemanticTokens(model: ITextModel, range: Range, token: CancellationToken): Promise { + requestCount++; + if (requestCount === 1) { + inFirstCall.open(); + } else if (requestCount === 2) { + inRefreshCall.open(); + } + return { + data: new Uint32Array([0, 1, 1, 1, 1]) + }; + } + })); + + const textModel = disposables.add(createTextModel('Hello world', 'testMode')); + const editor = disposables.add(createTestCodeEditor(textModel, { serviceCollection })); + const instantiationService = new TestInstantiationService(serviceCollection); + disposables.add(instantiationService.createInstance(ViewportSemanticTokensContribution, editor)); + + textModel.onBeforeAttached(); + + await inFirstCall.wait(); + + assert.strictEqual(requestCount, 1, 'Initial request should have been made'); + + // Make sure no other requests are made for a little while + await timeout(1000); + assert.strictEqual(requestCount, 1, 'No additional requests should have been made'); + + // Fire the provider's onDidChange event + emitter.fire(); + + await inRefreshCall.wait(); + + assert.strictEqual(requestCount, 2, 'Provider onDidChange should trigger a refresh of viewport semantic tokens'); + }); + }); +}); From 1ea1331e8fc5645697e4ba8ecb15a330bc1a77bc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:04:44 -0700 Subject: [PATCH 1587/4355] Remove unneeded param --- .../chatContentParts/chatMcpServersInteractionContentPart.ts | 1 - .../chat/browser/chatContentParts/chatProgressContentPart.ts | 5 ++--- .../chat/browser/chatContentParts/chatTaskContentPart.ts | 2 +- .../toolInvocationParts/chatToolProgressPart.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts index 265fa6d2093..be91ac66405 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMcpServersInteractionContentPart.ts @@ -142,7 +142,6 @@ export class ChatMcpServersInteractionContentPart extends Disposable implements true, // forceShowSpinner true, // forceShowMessage undefined, // icon - undefined, // confirmedReason undefined, // toolInvocation )); this.domNode.appendChild(this.workingProgressPart.domNode); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index f7733de2c55..5b14e4efb17 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -13,7 +13,7 @@ import { IMarkdownRenderer } from '../../../../../platform/markdown/browser/mark import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { localize } from '../../../../../nls.js'; -import { IChatProgressMessage, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind, type ConfirmedReason } from '../../common/chatService.js'; +import { IChatProgressMessage, IChatTask, IChatTaskSerialized, IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../common/chatService.js'; import { IChatRendererContent, isResponseVM } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { renderFileWidgets } from '../chatInlineAnchorWidget.js'; @@ -37,7 +37,6 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP forceShowSpinner: boolean | undefined, forceShowMessage: boolean | undefined, icon: ThemeIcon | undefined, - confirmedReason: boolean | ConfirmedReason | undefined, private readonly toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService, @@ -190,7 +189,7 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; - super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); + super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 0dd6a5045c4..6cd94f46a5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -42,7 +42,7 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart this.isSettled = isSettled; const showSpinner = !isSettled && !context.element.isComplete; // TODO: Pass in confirmed reason - const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined, undefined)); + const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined)); this.domNode = progressPart.domNode; this.onDidChangeHeight = Event.None; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts index efc64678567..098b95e261e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolProgressPart.ts @@ -77,7 +77,7 @@ export class ChatToolProgressSubPart extends BaseChatToolInvocationSubPart { this.provideScreenReaderStatus(content); } - return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon(), this.toolInvocation.isConfirmed, this.toolInvocation); + return this.instantiationService.createInstance(ChatProgressContentPart, progressMessage, this.renderer, this.context, undefined, true, this.getIcon(), this.toolInvocation); } private getAnnouncementKey(kind: 'progress' | 'complete'): string { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 98adc8c6413..6af7955af7e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1265,7 +1265,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Fri, 24 Oct 2025 05:14:28 -0700 Subject: [PATCH 1588/4355] Suggestion fixes --- .../chat/browser/chatContentParts/chatProgressContentPart.ts | 4 +--- .../toolInvocationParts/chatToolOutputPart.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 5b14e4efb17..3a63acec018 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -122,9 +122,7 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP : reason.scope === 'workspace' ? localize('chat.autoapprove.lmServicePerTool.workspace', 'Auto approved for this workspace') : localize('chat.autoapprove.lmServicePerTool.profile', 'Auto approved for this profile'); - if (this.toolInvocation?.toolId) { - md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; - } + md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; break; case ToolConfirmKind.UserAction: case ToolConfirmKind.Denied: diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts index e7fff180f00..524e9fcd414 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts @@ -104,7 +104,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { const progressMessage = dom.$('span'); progressMessage.textContent = localize('loading', 'Rendering tool output...'); - const progressPart = this._register(this.instantiationService.createInstance(ChatProgressSubPart, progressMessage, ThemeIcon.modify(Codicon.loading, 'spin'), 'hello world')); + const progressPart = this._register(this.instantiationService.createInstance(ChatProgressSubPart, progressMessage, ThemeIcon.modify(Codicon.loading, 'spin'), undefined)); parent.appendChild(progressPart.domNode); // TODO: we also need to show the tool output in the UI From 250c8e923468c875affe3bca8478ff265b34f48a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:25:05 -0700 Subject: [PATCH 1589/4355] Show old UX for MCP for now --- .../chatToolInvocationPart.ts | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 31ae1ac46ac..5167579f1cc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -8,7 +8,7 @@ import { Emitter } from '../../../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IChatToolInvocation, IChatToolInvocationSerialized } from '../../../common/chatService.js'; +import { IChatToolInvocation, IChatToolInvocationSerialized, ToolConfirmKind } from '../../../common/chatService.js'; import { IChatRendererContent } from '../../../common/chatViewModel.js'; import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js'; import { isToolResultInputOutputDetails, isToolResultOutputDetails, ToolInvocationPresentation } from '../../../common/languageModelToolsService.js'; @@ -27,6 +27,9 @@ import { ChatToolOutputSubPart } from './chatToolOutputPart.js'; import { ChatToolPostExecuteConfirmationPart } from './chatToolPostExecuteConfirmationPart.js'; import { ChatToolProgressSubPart } from './chatToolProgressPart.js'; import { autorun } from '../../../../../../base/common/observable.js'; +import { ChatMcpServersInteractionContentPart } from '../chatMcpServersInteractionContentPart.js'; +import { localize } from '../../../../../../nls.js'; +import { markdownCommandLink, MarkdownString } from '../../../../../../base/common/htmlContent.js'; export class ChatToolInvocationPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -92,11 +95,65 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa partStore.add(this.subPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); partStore.add(this.subPart.onNeedsRerender(render)); + // todo@connor4312: Move MCP spinner to left to get consistent auto approval presentation + if (this.subPart instanceof ChatInputOutputMarkdownProgressPart) { + const approval = this.createApprovalMessage(); + if (approval) { + this.domNode.appendChild(approval); + } + } + this._onDidChangeHeight.fire(); }; render(); } + /** @deprecated Approval should be centrally managed by passing tool invocation ChatProgressContentPart */ + private get autoApproveMessageContent() { + const reason = IChatToolInvocation.executionConfirmedOrDenied(this.toolInvocation); + if (!reason || typeof reason === 'boolean') { + return; + } + + let md: string; + switch (reason.type) { + case ToolConfirmKind.Setting: + md = localize('chat.autoapprove.setting', 'Auto approved by {0}', markdownCommandLink({ title: '`' + reason.id + '`', id: 'workbench.action.openSettings', arguments: [reason.id] }, false)); + break; + case ToolConfirmKind.LmServicePerTool: + md = reason.scope === 'session' + ? localize('chat.autoapprove.lmServicePerTool.session', 'Auto approved for this session') + : reason.scope === 'workspace' + ? localize('chat.autoapprove.lmServicePerTool.workspace', 'Auto approved for this workspace') + : localize('chat.autoapprove.lmServicePerTool.profile', 'Auto approved for this profile'); + md += ' (' + markdownCommandLink({ title: localize('edit', 'Edit'), id: 'workbench.action.chat.editToolApproval', arguments: [this.toolInvocation.toolId] }) + ')'; + break; + case ToolConfirmKind.UserAction: + case ToolConfirmKind.Denied: + case ToolConfirmKind.ConfirmationNotNeeded: + default: + return; + } + + + return md; + } + + /** @deprecated Approval should be centrally managed by passing tool invocation ChatProgressContentPart */ + private createApprovalMessage(): HTMLElement | undefined { + const md = this.autoApproveMessageContent; + if (!md) { + return undefined; + } + + const markdownString = new MarkdownString('_' + md + '_', { isTrusted: true }); + const result = this.renderer.render(markdownString); + this._register(result); + result.element.classList.add('chat-tool-approval-message'); + + return result.element; + } + private createToolInvocationSubPart(): BaseChatToolInvocationSubPart { if (this.toolInvocation.kind === 'toolInvocation') { if (this.toolInvocation.toolSpecificData?.kind === 'extensions') { From c469aef2f94d3a10c9f45a2467779e9e22ebb78e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:29:28 -0700 Subject: [PATCH 1590/4355] Use hover style --- .../browser/chatContentParts/chatProgressContentPart.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 3a63acec018..59ed0d64f34 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -22,6 +22,7 @@ import { IChatMarkdownAnchorService } from './chatMarkdownAnchorService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; export class ChatProgressContentPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -161,10 +162,7 @@ export class ChatProgressSubPart extends Disposable { if (tooltip) { this._register(hoverService.setupDelayedHover(iconElement, { content: tooltip, - appearance: { - compact: true, - showPointer: true, - }, + style: HoverStyle.Pointer, })); } append(this.domNode, iconElement); From eee499206f2c7c19b302c8fb40bf2839a63a15e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:29:34 -0700 Subject: [PATCH 1591/4355] Remove uneeded todo --- .../contrib/chat/browser/chatContentParts/chatTaskContentPart.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts index 6cd94f46a5d..78c23e52fdd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -41,7 +41,6 @@ export class ChatTaskContentPart extends Disposable implements IChatContentPart true; this.isSettled = isSettled; const showSpinner = !isSettled && !context.element.isComplete; - // TODO: Pass in confirmed reason const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, chatContentMarkdownRenderer, context, showSpinner, true, undefined, undefined)); this.domNode = progressPart.domNode; this.onDidChangeHeight = Event.None; From cfba06bd0e1b746f5bba6e2860a6c4d3eb84abd3 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:33:49 -0700 Subject: [PATCH 1592/4355] Merge pull request #272369 from microsoft/anthonykim1/shellIntegrationTimeout Use unified timeout setting under shell integration in runCommand --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/terminalInstance.ts | 24 +++++++++++--- .../browser/terminalProcessManager.ts | 2 ++ .../contrib/terminal/common/terminal.ts | 1 + .../terminal/common/terminalConfiguration.ts | 8 +++++ .../terminal/common/terminalEnvironment.ts | 33 +++++++++++++++++-- .../terminal.chatAgentTools.contribution.ts | 23 ++++++++++++- .../browser/toolTerminalCreator.ts | 21 ++++++------ .../terminalChatAgentToolsConfiguration.ts | 3 +- 9 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 39cd340b442..322ef0bced2 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -109,6 +109,7 @@ export const enum TerminalSettingId { ShellIntegrationEnabled = 'terminal.integrated.shellIntegration.enabled', ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome', ShellIntegrationDecorationsEnabled = 'terminal.integrated.shellIntegration.decorationsEnabled', + ShellIntegrationTimeout = 'terminal.integrated.shellIntegration.timeout', ShellIntegrationQuickFixEnabled = 'terminal.integrated.shellIntegration.quickFixEnabled', ShellIntegrationEnvironmentReporting = 'terminal.integrated.shellIntegration.environmentReporting', EnableImages = 'terminal.integrated.enableImages', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 76121a56773..136eafc5f50 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -72,7 +72,7 @@ import { IEnvironmentVariableInfo } from '../common/environmentVariable.js'; import { DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalProcessManager, ITerminalProfileResolverService, ProcessState, TERMINAL_CREATION_COMMANDS, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; import { TERMINAL_BACKGROUND_COLOR } from '../common/terminalColorRegistry.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; -import { getWorkspaceForTerminal, preparePathForShell } from '../common/terminalEnvironment.js'; +import { getShellIntegrationTimeout, getWorkspaceForTerminal, preparePathForShell } from '../common/terminalEnvironment.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IHistoryService } from '../../../services/history/common/history.js'; @@ -92,6 +92,7 @@ import { TerminalContribCommandId } from '../terminalContribExports.js'; import type { IProgressState } from '@xterm/addon-progress'; import { refreshShellIntegrationInfoStatus } from './terminalTooltip.js'; import { generateUuid } from '../../../../base/common/uuid.js'; +import { PromptInputState } from '../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; const enum Constants { /** @@ -916,18 +917,31 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async runCommand(commandLine: string, shouldExecute: boolean): Promise { let commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); + const siInjectionEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled) === true; + const timeoutMs = getShellIntegrationTimeout( + this._configurationService, + siInjectionEnabled, + this.isRemote, + this._processManager.processReadyTimestamp + ); - // Await command detection if the terminal is starting up - if (!commandDetection && (this._processManager.processState === ProcessState.Uninitialized || this._processManager.processState === ProcessState.Launching)) { + if (!commandDetection || commandDetection.promptInputModel.state !== PromptInputState.Input) { const store = new DisposableStore(); + await Promise.race([ new Promise(r => { store.add(this.capabilities.onDidAddCommandDetectionCapability(e => { commandDetection = e; - r(); + if (commandDetection.promptInputModel.state === PromptInputState.Input) { + r(); + } else { + store.add(commandDetection.promptInputModel.onDidStartInput(() => { + r(); + })); + } })); }), - timeout(2000), + timeout(timeoutMs) ]); store.dispose(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index e0ac131aa5b..e3f3757ee9d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -80,6 +80,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce backend: ITerminalBackend | undefined; readonly capabilities = this._register(new TerminalCapabilityStore()); readonly shellIntegrationNonce: string; + processReadyTimestamp: number = 0; private _isDisposed: boolean = false; private _process: ITerminalChildProcess | null = null; @@ -370,6 +371,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._processTraits = e; this.shellProcessId = e.pid; this._initialCwd = e.cwd; + this.processReadyTimestamp = Date.now(); this._onDidChangeProperty.fire({ type: ProcessPropertyType.InitialCwd, value: this._initialCwd }); this._onProcessReady.fire(e); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 866dbe84131..362bb34b32a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -277,6 +277,7 @@ export const isTerminalProcessManager = (t: ITerminalProcessInfo | ITerminalProc export interface ITerminalProcessManager extends IDisposable, ITerminalProcessInfo { readonly processTraits: IProcessReadyEvent | undefined; + readonly processReadyTimestamp: number; readonly onPtyDisconnect: Event; readonly onPtyReconnect: Event; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 16dc4a81020..27a0cba1f5e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -594,6 +594,14 @@ const terminalConfiguration: IStringDictionary = { ], default: 'both' }, + [TerminalSettingId.ShellIntegrationTimeout]: { + restricted: true, + markdownDescription: localize('terminal.integrated.shellIntegration.timeout', "Configures the duration in milliseconds to wait for shell integration after launch before declaring it's not there. Set to {0} to wait the minimum time (500ms), the default value {1} means the wait time is variable based on whether shell integration injection is enabled and whether it's a remote window. Consider setting this to a small value if you intentionally disabled shell integration, or a large value if your shell starts very slowly.", '`0`', '`-1`'), + type: 'integer', + minimum: -1, + maximum: 60000, + default: -1 + }, [TerminalSettingId.ShellIntegrationQuickFixEnabled]: { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.quickFixEnabled', "When shell integration is enabled, enables quick fixes for terminal commands that appear as a lightbulb or sparkle icon to the left of the prompt."), diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 1377335d7fe..8e42cecb139 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -12,12 +12,13 @@ import { URI } from '../../../../base/common/uri.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js'; import { sanitizeProcessEnvironment } from '../../../../base/common/processes.js'; -import { IShellLaunchConfig, ITerminalBackend, ITerminalEnvironment, TerminalShellType, WindowsShellType } from '../../../../platform/terminal/common/terminal.js'; +import { IShellLaunchConfig, ITerminalBackend, ITerminalEnvironment, TerminalSettingId, TerminalShellType, WindowsShellType } from '../../../../platform/terminal/common/terminal.js'; import { IProcessEnvironment, isWindows, isMacintosh, language, OperatingSystem } from '../../../../base/common/platform.js'; import { escapeNonWindowsPath, sanitizeCwd } from '../../../../platform/terminal/common/terminalEnvironment.js'; -import { isString } from '../../../../base/common/types.js'; +import { isNumber, isString } from '../../../../base/common/types.js'; import { IHistoryService } from '../../../services/history/common/history.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import type { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; export function mergeEnvironments(parent: IProcessEnvironment, other: ITerminalEnvironment | undefined): void { if (!other) { @@ -390,3 +391,31 @@ export function getWorkspaceForTerminal(cwd: URI | string | undefined, workspace } return workspaceFolder; } + +/** + * Gets the unified duration to wait for shell integration after the terminal launches before + * declaring the terminal lacks shell integration. + */ +export function getShellIntegrationTimeout( + configurationService: IConfigurationService, + siInjectionEnabled: boolean, + isRemote: boolean, + processReadyTimestamp?: number +): number { + const timeoutValue = configurationService.getValue(TerminalSettingId.ShellIntegrationTimeout); + let timeoutMs: number; + + if (!isNumber(timeoutValue) || timeoutValue < 0) { + timeoutMs = siInjectionEnabled ? 5000 : (isRemote ? 3000 : 2000); + } else { + timeoutMs = Math.max(timeoutValue, 500); + } + + // Adjust timeout based on how long the process has already been running + if (processReadyTimestamp !== undefined) { + const elapsed = Date.now() - processReadyTimestamp; + timeoutMs = Math.max(0, timeoutMs - elapsed); + } + + return timeoutMs; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts index 0c49635cd41..9b25f7f2ce2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/terminal.chatAgentTools.contribution.ts @@ -6,10 +6,13 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { isNumber } from '../../../../../base/common/types.js'; import { localize } from '../../../../../nls.js'; import { MenuId } from '../../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; import { registerWorkbenchContribution2, WorkbenchPhase, type IWorkbenchContribution } from '../../../../common/contributions.js'; import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; @@ -20,6 +23,7 @@ import { registerActiveInstanceAction, sharedWhenClause } from '../../../termina import { TerminalContextMenuGroup } from '../../../terminal/browser/terminalMenus.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { TerminalChatAgentToolsCommandId } from '../common/terminal.chatAgentTools.js'; +import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; import { GetTerminalLastCommandTool, GetTerminalLastCommandToolData } from './tools/getTerminalLastCommandTool.js'; import { GetTerminalOutputTool, GetTerminalOutputToolData } from './tools/getTerminalOutputTool.js'; import { GetTerminalSelectionTool, GetTerminalSelectionToolData } from './tools/getTerminalSelectionTool.js'; @@ -29,7 +33,24 @@ import { CreateAndRunTaskTool, CreateAndRunTaskToolData } from './tools/task/cre import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOutputTool.js'; import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js'; -// #region Workbench contributions +class ShellIntegrationTimeoutMigrationContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'terminal.shellIntegrationTimeoutMigration'; + + constructor( + @IConfigurationService configurationService: IConfigurationService, + ) { + super(); + const deprecatedSettingValue = configurationService.getValue(TerminalChatAgentToolsSettingId.ShellIntegrationTimeout); + if (!isNumber(deprecatedSettingValue)) { + return; + } + const newSettingValue = configurationService.getValue(TerminalSettingId.ShellIntegrationTimeout); + if (!isNumber(newSettingValue)) { + configurationService.updateValue(TerminalSettingId.ShellIntegrationTimeout, deprecatedSettingValue); + } + } +} +registerWorkbenchContribution2(ShellIntegrationTimeoutMigrationContribution.ID, ShellIntegrationTimeoutMigrationContribution, WorkbenchPhase.Eventually); class ChatAgentToolsContribution extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index 0cfb2df160b..50eb06c2ee7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -16,7 +16,7 @@ import { TerminalCapability } from '../../../../../platform/terminal/common/capa import { PromptInputState } from '../../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; import { ITerminalLogService, ITerminalProfile, TerminalSettingId, type IShellLaunchConfig } from '../../../../../platform/terminal/common/terminal.js'; import { ITerminalService, type ITerminalInstance } from '../../../terminal/browser/terminal.js'; -import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js'; +import { getShellIntegrationTimeout } from '../../../terminal/common/terminalEnvironment.js'; const enum ShellLaunchType { Unknown = 0, @@ -56,10 +56,11 @@ export class ToolTerminalCreator { instance, shellIntegrationQuality: ShellIntegrationQuality.None, }; + let processReadyTimestamp = 0; // Ensure the shell process launches successfully const initResult = await Promise.any([ - instance.processReady, + instance.processReady.then(() => processReadyTimestamp = Date.now()), Event.toPromise(instance.onExit), ]); if (!isNumber(initResult) && isObject(initResult) && 'message' in initResult) { @@ -69,17 +70,15 @@ export class ToolTerminalCreator { // Wait for shell integration when the fallback case has not been hit or when shell // integration injection is enabled. Note that it's possible for the fallback case to happen // and then for SI to activate again later in the session. - const siInjectionEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); + const siInjectionEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled) === true; // Get the configurable timeout to wait for shell integration - const configuredTimeout = this._configurationService.getValue(TerminalChatAgentToolsSettingId.ShellIntegrationTimeout) as number | undefined; - let waitTime: number; - if (configuredTimeout === undefined || typeof configuredTimeout !== 'number' || configuredTimeout < 0) { - waitTime = siInjectionEnabled ? 5000 : (instance.isRemote ? 3000 : 2000); - } else { - // There's an absolute minimum is 500ms - waitTime = Math.max(configuredTimeout, 500); - } + const waitTime = getShellIntegrationTimeout( + this._configurationService, + siInjectionEnabled, + instance.isRemote, + processReadyTimestamp + ); if ( ToolTerminalCreator._lastSuccessfulShell !== ShellLaunchType.Fallback || diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 263012d096c..a5ede40bb3b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -319,7 +319,8 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Fri, 24 Oct 2025 05:36:27 -0700 Subject: [PATCH 1593/4355] Update xterm Fixes #238273 --- package-lock.json | 96 ++++++++++++++++++------------------ package.json | 22 ++++----- remote/package-lock.json | 96 ++++++++++++++++++------------------ remote/package.json | 20 ++++---- remote/web/package-lock.json | 88 ++++++++++++++++----------------- remote/web/package.json | 18 +++---- 6 files changed, 170 insertions(+), 170 deletions(-) diff --git a/package-lock.json b/package-lock.json index 879bca4ff26..5e15c534b47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,16 +27,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/headless": "^5.6.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/headless": "^5.6.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -3619,30 +3619,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.114", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", - "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", + "version": "0.2.0-beta.118", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", + "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", - "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", + "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", - "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", + "version": "0.10.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", + "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -3652,64 +3652,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.37", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", - "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", + "version": "0.2.0-beta.41", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", + "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", - "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", + "version": "0.16.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", + "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", - "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", + "version": "0.14.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", + "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", - "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", + "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", - "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", + "version": "0.19.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", + "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.131.tgz", - "integrity": "sha512-LUaXCWsBj/RJfNTO89z1jzZZ8pnANWTL8M2TBmimsFpz3+KbkOmrVt+XfpWSCY0Y1HmQqI1/xjBCCSwN2Ij3hA==", + "version": "5.6.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.135.tgz", + "integrity": "sha512-fcBUf8zLaTWUyAcahK48dihb1BQYdWvOH021Xge/LfMvCFzK0gmvSMnBbM5fgOyejbmtNLQe+gXBBKVxSaW2pA==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", - "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", + "version": "5.6.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", + "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", "license": "MIT" }, "node_modules/@xtuc/ieee754": { diff --git a/package.json b/package.json index e91ff636b1a..65cda6b97a6 100644 --- a/package.json +++ b/package.json @@ -88,16 +88,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/headless": "^5.6.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/headless": "^5.6.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -239,4 +239,4 @@ "optionalDependencies": { "windows-foreground-love": "0.5.0" } -} \ No newline at end of file +} diff --git a/remote/package-lock.json b/remote/package-lock.json index 09d6c70275c..bdeffca90b6 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -20,16 +20,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/headless": "^5.6.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/headless": "^5.6.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -238,30 +238,30 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.114", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", - "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", + "version": "0.2.0-beta.118", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", + "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", - "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", + "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", - "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", + "version": "0.10.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", + "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -271,64 +271,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.37", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", - "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", + "version": "0.2.0-beta.41", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", + "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", - "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", + "version": "0.16.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", + "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", - "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", + "version": "0.14.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", + "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", - "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", + "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", - "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", + "version": "0.19.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", + "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.131.tgz", - "integrity": "sha512-LUaXCWsBj/RJfNTO89z1jzZZ8pnANWTL8M2TBmimsFpz3+KbkOmrVt+XfpWSCY0Y1HmQqI1/xjBCCSwN2Ij3hA==", + "version": "5.6.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.135.tgz", + "integrity": "sha512-fcBUf8zLaTWUyAcahK48dihb1BQYdWvOH021Xge/LfMvCFzK0gmvSMnBbM5fgOyejbmtNLQe+gXBBKVxSaW2pA==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", - "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", + "version": "5.6.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", + "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", "license": "MIT" }, "node_modules/agent-base": { diff --git a/remote/package.json b/remote/package.json index 41979903e57..44a825ec29b 100644 --- a/remote/package.json +++ b/remote/package.json @@ -15,16 +15,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/headless": "^5.6.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/headless": "^5.6.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "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 b3a80a4e8db..d6b531b8974 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.2.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", @@ -92,30 +92,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.114", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.114.tgz", - "integrity": "sha512-AvSZZifwhk8Rse8Bm8eXqrJHatg+y9oRgmNGT7iWVjOucYotA+VTBRvE4tiseHZ77UFRVCA/GXmTnmm/NDvjDQ==", + "version": "0.2.0-beta.118", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", + "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.131.tgz", - "integrity": "sha512-XcUhFXRbAymssmD7WDeLkyK+oBAgrwmP7P1mnUXaYlRPFhRqj3+k309BjlobDntp0HKIdLyNH5ngcTEuMoXQNw==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", + "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.131.tgz", - "integrity": "sha512-aqRNYeUv1MpHBsUHojEpUJpremB0al4i+f4Qe4Sl0XGrpfXHurYFRnqREtkx0xq+P4lKGlQH+uXhI5ZdQgNnzw==", + "version": "0.10.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", + "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -125,58 +125,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.37", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.37.tgz", - "integrity": "sha512-nu6LaM6pCXqnKwJv3rMO8oU5qNiJb/PFmO1OUPxYQ3IHdIbdMKQ7rV0cShGD4NvsABnN9k8gmXerpXrPWhwCeg==", + "version": "0.2.0-beta.41", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", + "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.131.tgz", - "integrity": "sha512-AC+OnHQz0NSNMD0VVyDC2QTtQ81ovcB9WFCdcUP0RFEUOUvGX66A9PjMuDWBB61CHntVx+EncNl5AyBlpwQCnQ==", + "version": "0.16.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", + "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.131.tgz", - "integrity": "sha512-3FPOfG68rxMLYFLRNfd8bMPqTg2doEZJ3+6nLCmfl4L3hEswpTtBiKj7L1x2uBSfLfExz+w8m++F7LFL5BCQ0A==", + "version": "0.14.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", + "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.131.tgz", - "integrity": "sha512-ueIH3RPmL7KuXqKlu59xT9nNTTuxLiH7pHoy2T4LQXDpv/5Wx5jkSSs/8HhAl9egh9aogXc4Syav50m9Yk6MvA==", + "version": "0.9.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", + "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.131.tgz", - "integrity": "sha512-DvLB5dwVYsyyl4F/6MatE1v7RFFU4/4CEcA/f+JDQa4XHM9djYZJBDFUnH86JWua3OGWEFW+JA2Q/4VsL2wZfA==", + "version": "0.19.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", + "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.131" + "@xterm/xterm": "^5.6.0-beta.135" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.131", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.131.tgz", - "integrity": "sha512-EyVxU9Ewm09kN9JSZCPCP7Kr8LDk4OX6a+STFFOU+Q3ZQ+KFCq74WuvGx2hGNBM9eBemJMksu03ooRl8BlSnHA==", + "version": "5.6.0-beta.135", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", + "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", "license": "MIT" }, "node_modules/commander": { diff --git a/remote/web/package.json b/remote/web/package.json index 46a1fefe151..cff9e6ecb0a 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.2.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.114", - "@xterm/addon-image": "^0.9.0-beta.131", - "@xterm/addon-ligatures": "^0.10.0-beta.131", - "@xterm/addon-progress": "^0.2.0-beta.37", - "@xterm/addon-search": "^0.16.0-beta.131", - "@xterm/addon-serialize": "^0.14.0-beta.131", - "@xterm/addon-unicode11": "^0.9.0-beta.131", - "@xterm/addon-webgl": "^0.19.0-beta.131", - "@xterm/xterm": "^5.6.0-beta.131", + "@xterm/addon-clipboard": "^0.2.0-beta.118", + "@xterm/addon-image": "^0.9.0-beta.135", + "@xterm/addon-ligatures": "^0.10.0-beta.135", + "@xterm/addon-progress": "^0.2.0-beta.41", + "@xterm/addon-search": "^0.16.0-beta.135", + "@xterm/addon-serialize": "^0.14.0-beta.135", + "@xterm/addon-unicode11": "^0.9.0-beta.135", + "@xterm/addon-webgl": "^0.19.0-beta.135", + "@xterm/xterm": "^5.6.0-beta.135", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", From 70c31a490931f115d8f4e95d399819c03b246778 Mon Sep 17 00:00:00 2001 From: Kyle Cutler <67761731+kycutler@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:47:31 +0200 Subject: [PATCH 1594/4355] Support basic MarkdownStrings in tree view labels (#272435) * Support rendering icons in tree view labels * Use MarkdownStrings instead of a flag * Add TreeItem2 to ext API * Remove API in TreeItem, better highlight support * Support bold, improve docstring * PR feedback * Fix default, PR feedback --- .../ui/highlightedlabel/highlightedLabel.ts | 20 +--- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 21 +++- .../base/browser/ui/iconLabel/iconlabel.css | 5 + .../test/browser/highlightedLabel.test.ts | 2 +- .../common/extensionsApiProposals.ts | 3 + .../workbench/api/common/extHostTreeViews.ts | 21 +++- src/vs/workbench/browser/labels.ts | 2 + .../browser/parts/views/media/views.css | 5 + .../workbench/browser/parts/views/treeView.ts | 110 +++++++++++++++--- src/vs/workbench/common/views.ts | 4 +- .../browser/userDataProfilesEditorModel.ts | 3 +- .../userDataProfileImportExportService.ts | 6 +- ...vscode.proposed.treeItemMarkdownLabel.d.ts | 23 ++++ 13 files changed, 177 insertions(+), 48 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 0c6cb1f1fa7..006a832f11e 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -22,12 +22,6 @@ export interface IHighlight { } export interface IHighlightedLabelOptions { - - /** - * Whether the label supports rendering icons. - */ - readonly supportIcons?: boolean; - readonly hoverDelegate?: IHoverDelegate; } @@ -41,7 +35,6 @@ export class HighlightedLabel extends Disposable { private text: string = ''; private title: string = ''; private highlights: readonly IHighlight[] = []; - private supportIcons: boolean; private didEverRender: boolean = false; private customHover: IManagedHover | undefined; @@ -53,7 +46,6 @@ export class HighlightedLabel extends Disposable { constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) { super(); - this.supportIcons = options?.supportIcons ?? false; this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label')); } @@ -73,7 +65,7 @@ export class HighlightedLabel extends Disposable { * @param escapeNewLines Whether to escape new lines. * @returns */ - set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) { + set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean, supportIcons?: boolean) { if (!text) { text = ''; } @@ -90,10 +82,10 @@ export class HighlightedLabel extends Disposable { this.text = text; this.title = title; this.highlights = highlights; - this.render(); + this.render(supportIcons); } - private render(): void { + private render(supportIcons?: boolean): void { const children: Array = []; let pos = 0; @@ -105,7 +97,7 @@ export class HighlightedLabel extends Disposable { if (pos < highlight.start) { const substring = this.text.substring(pos, highlight.start); - if (this.supportIcons) { + if (supportIcons) { children.push(...renderLabelWithIcons(substring)); } else { children.push(substring); @@ -114,7 +106,7 @@ export class HighlightedLabel extends Disposable { } const substring = this.text.substring(pos, highlight.end); - const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]); + const element = dom.$('span.highlight', undefined, ...supportIcons ? renderLabelWithIcons(substring) : [substring]); if (highlight.extraClasses) { element.classList.add(...highlight.extraClasses); @@ -126,7 +118,7 @@ export class HighlightedLabel extends Disposable { if (pos < this.text.length) { const substring = this.text.substring(pos,); - if (this.supportIcons) { + if (supportIcons) { children.push(...renderLabelWithIcons(substring)); } else { children.push(substring); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index b3eedce65c8..d9ea705c8da 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -31,6 +31,7 @@ export interface IIconLabelValueOptions { suffix?: string; hideIcon?: boolean; extraClasses?: readonly string[]; + bold?: boolean; italic?: boolean; strikethrough?: boolean; matches?: readonly IMatch[]; @@ -40,6 +41,7 @@ export interface IIconLabelValueOptions { readonly separator?: string; readonly domId?: string; iconPath?: URI; + supportIcons?: boolean; } class FastLabelNode { @@ -136,6 +138,10 @@ export class IconLabel extends Disposable { labelClasses.push(...options.extraClasses); } + if (options.bold) { + labelClasses.push('bold'); + } + if (options.italic) { labelClasses.push('italic'); } @@ -185,7 +191,7 @@ export class IconLabel extends Disposable { if (description || this.descriptionNode) { const descriptionNode = this.getOrCreateDescriptionNode(); if (descriptionNode instanceof HighlightedLabel) { - descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines); + descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, options?.supportIcons); this.setupHover(descriptionNode.element, options?.descriptionTitle); } else { descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || ''); @@ -247,7 +253,7 @@ export class IconLabel extends Disposable { if (!this.descriptionNode) { const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); if (this.creationOptions?.supportDescriptionHighlights) { - this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons })); + this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } else { this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } @@ -338,14 +344,17 @@ class LabelWithHighlights extends Disposable { this.label = label; this.options = options; + // Determine supportIcons: use option if provided, otherwise use constructor value + const supportIcons = options?.supportIcons ?? this.supportIcons; + if (typeof label === 'string') { if (!this.singleLabel) { this.container.textContent = ''; this.container.classList.remove('multiple'); - this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons })); + this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })))); } - this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines); + this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines, supportIcons); } else { this.container.textContent = ''; this.container.classList.add('multiple'); @@ -360,8 +369,8 @@ class LabelWithHighlights extends Disposable { const id = options?.domId && `${options?.domId}_${i}`; const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); - const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons })); - highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines); + const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name))); + highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines, supportIcons); if (i < label.length - 1) { dom.append(name, dom.$('span.label-separator', undefined, separator)); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index f520d6e8bf9..d3dfd9a586b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -78,6 +78,11 @@ opacity: .95; } +.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, +.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { + font-weight: bold; +} + .monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, .monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { font-style: italic; diff --git a/src/vs/base/test/browser/highlightedLabel.test.ts b/src/vs/base/test/browser/highlightedLabel.test.ts index 27ca48e4906..66906743633 100644 --- a/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/src/vs/base/test/browser/highlightedLabel.test.ts @@ -11,7 +11,7 @@ suite('HighlightedLabel', () => { let label: HighlightedLabel; setup(() => { - label = new HighlightedLabel(document.createElement('div'), { supportIcons: true }); + label = new HighlightedLabel(document.createElement('div')); }); test('empty label', function () { diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 3a9295dcd16..a99e39b15a2 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -442,6 +442,9 @@ const _allApiProposals = { toolProgress: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.toolProgress.d.ts', }, + treeItemMarkdownLabel: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts', + }, treeViewActiveItem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', }, diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index c59833f377c..ebd9978ddbe 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -32,15 +32,18 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte return { label }; } - if (label - && typeof label === 'object' - && typeof label.label === 'string') { + if (label && typeof label === 'object' && label.label) { let highlights: [number, number][] | undefined = undefined; if (Array.isArray(label.highlights)) { highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number')); highlights = highlights.length ? highlights : undefined; } - return { label: label.label, highlights }; + if (isString(label.label)) { + return { label: label.label, highlights }; + } else if (extHostTypes.MarkdownString.isMarkdownString(label.label)) { + checkProposedApiEnabled(extension, 'treeItemMarkdownLabel'); + return { label: MarkdownString.from(label.label), highlights }; + } } return undefined; @@ -925,7 +928,15 @@ class ExtHostTreeView extends Disposable { const treeItemLabel = toTreeItemLabel(label, this._extension); const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX; - let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri) : ''; + let labelValue = ''; + if (treeItemLabel) { + if (isMarkdownString(treeItemLabel.label)) { + labelValue = treeItemLabel.label.value; + } else { + labelValue = treeItemLabel.label; + } + } + let elementId = labelValue || (resourceUri ? basename(resourceUri) : ''); elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId; const existingHandle = this._nodes.has(element) ? this._nodes.get(element)!.item.handle : undefined; const childrenNodes = (this._getChildrenNodes(parent) || []); diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 38a8fd496de..546140926c9 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -631,6 +631,7 @@ class ResourceLabelWidget extends IconLabel { const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', + bold: this.options?.bold, italic: this.options?.italic, strikethrough: this.options?.strikethrough, matches: this.options?.matches, @@ -641,6 +642,7 @@ class ResourceLabelWidget extends IconLabel { disabledCommand: this.options?.disabledCommand, labelEscapeNewLines: this.options?.labelEscapeNewLines, descriptionTitle: this.options?.descriptionTitle, + supportIcons: this.options?.supportIcons, }; const resource = toResource(this.label); diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 815cf995f24..d04b9a6dbf9 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -184,6 +184,11 @@ overflow: hidden; } +.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-highlighted-label .codicon { + position: relative; + top: 2px; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label-container::after { content: ''; display: block; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index b88f240aa31..2bfb3920f31 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -719,7 +719,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } let buildAriaLabel: string = ''; if (element.label) { - buildAriaLabel += element.label.label + ' '; + const labelText = isMarkdownString(element.label.label) ? element.label.label.value : element.label.label; + buildAriaLabel += labelText + ' '; } if (element.description) { buildAriaLabel += element.description; @@ -736,7 +737,10 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (item: ITreeItem) => { - return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined); + if (item.label) { + return isMarkdownString(item.label.label) ? item.label.label.value : item.label.label; + } + return item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined; } }, expandOnlyOnTwistieClick: (e: ITreeItem) => { @@ -1296,12 +1300,16 @@ class TreeRenderer extends Disposable implements ITreeRenderer resolve(node.tooltip)); }); }, - markdownNotSupportedFallback: resource ? undefined : (label ?? '') // Passing undefined as the fallback for a resource falls back to the old native hover + markdownNotSupportedFallback: resource ? undefined : (label ? (isMarkdownString(label) ? label.value : label) : '') // Passing undefined as the fallback for a resource falls back to the old native hover + }; + } + + private processLabel(label: string | IMarkdownString | undefined, matches: { start: number; end: number }[] | undefined): { label: string | undefined; bold?: boolean; italic?: boolean; strikethrough?: boolean; supportIcons?: boolean } { + if (!isMarkdownString(label)) { + return { label }; + } + + let text = label.value.trim(); + let bold = false; + let italic = false; + let strikethrough = false; + + function moveMatches(offset: number) { + if (matches) { + for (const match of matches) { + match.start -= offset; + match.end -= offset; + } + } + } + + const syntaxes = [ + { open: '~~', close: '~~', mark: () => { strikethrough = true; } }, + { open: '**', close: '**', mark: () => { bold = true; } }, + { open: '*', close: '*', mark: () => { italic = true; } }, + { open: '_', close: '_', mark: () => { italic = true; } } + ]; + + function checkSyntaxes(): boolean { + let didChange = false; + for (const syntax of syntaxes) { + if (text.startsWith(syntax.open) && text.endsWith(syntax.close)) { + // If there is a match within the markers, stop processing + if (matches && matches.some(match => match.start < syntax.open.length || match.end > text.length - syntax.close.length)) { + return false; + } + + syntax.mark(); + text = text.substring(syntax.open.length, text.length - syntax.close.length); + moveMatches(syntax.open.length); + didChange = true; + } + } + return didChange; + } + + // Arbitrary max # of iterations + for (let i = 0; i < 10; i++) { + if (!checkSyntaxes()) { + break; + } + } + + return { + label: text, + bold, + italic, + strikethrough, + supportIcons: label.supportThemeIcons }; } @@ -1327,15 +1395,15 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + const labelStr = treeItemLabel ? isMarkdownString(treeItemLabel.label) ? treeItemLabel.label.value : treeItemLabel.label : undefined; + const matches = (treeItemLabel?.highlights && labelStr) ? treeItemLabel.highlights.map(([start, end]) => { if (start < 0) { - start = label.length + start; + start = labelStr.length + start; } if (end < 0) { - end = label.length + end; + end = labelStr.length + end; } - if ((start >= label.length) || (end > label.length)) { + if ((start >= labelStr.length) || (end > labelStr.length)) { return ({ start: 0, end: 0 }); } if (start > end) { @@ -1345,9 +1413,10 @@ class TreeRenderer extends Disposable implements ITreeRenderer { return String(elements.length); } const element = elements[0]; - return element.label ? element.label.label : (element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined); + if (element.label) { + return isMarkdownString(element.label.label) ? element.label.label.value : element.label.label; + } + return element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined; } async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 1f629e73c32..9c60b15706c 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -721,12 +721,10 @@ export enum TreeItemCollapsibleState { export interface ITreeItemLabel { - label: string; + label: string | IMarkdownString; highlights?: [number, number][]; - strikethrough?: boolean; - } export type TreeCommand = Command & { originalId?: string }; diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts index 78cda40da60..3d05a9bef6d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts @@ -6,6 +6,7 @@ import { Action, IAction, Separator, toAction } from '../../../../base/common/actions.js'; import { Emitter } from '../../../../base/common/event.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; +import { isMarkdownString } from '../../../../base/common/htmlContent.js'; import { localize } from '../../../../nls.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; @@ -312,7 +313,7 @@ export abstract class AbstractUserDataProfileElement extends Disposable { return { handle: child.handle, checkbox: child.checkbox, - label: child.label?.label ?? '', + label: child.label ? (isMarkdownString(child.label.label) ? child.label.label.value : child.label.label) : '', description: isString(child.description) ? child.description : undefined, resource: URI.revive(child.resourceUri), icon: child.themeIcon, diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 02b3c5e6ec3..c3aff7cf4c5 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -5,6 +5,7 @@ import './media/userDataProfileView.css'; import { localize } from '../../../../nls.js'; +import { isMarkdownString } from '../../../../base/common/htmlContent.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Emitter } from '../../../../base/common/event.js'; @@ -580,11 +581,12 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT this.rootsPromise = (async () => { this.roots = await this.fetchRoots(); for (const root of this.roots) { + const labelText = isMarkdownString(root.label.label) ? root.label.label.value : root.label.label; root.checkbox = { isChecked: !root.isFromDefaultProfile(), - tooltip: localize('select', "Select {0}", root.label.label), + tooltip: localize('select', "Select {0}", labelText), accessibilityInformation: { - label: localize('select', "Select {0}", root.label.label), + label: localize('select', "Select {0}", labelText), } }; if (root.isFromDefaultProfile()) { diff --git a/src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts b/src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts new file mode 100644 index 00000000000..6150fa0667e --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeItemMarkdownLabel.d.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // @kycutler https://github.com/microsoft/vscode/issues/271523 + + export interface TreeItemLabel2 { + highlights?: [number, number][]; + + /** + * A human-readable string or MarkdownString describing the {@link TreeItem Tree item}. + * + * When using MarkdownString, only the following Markdown syntax is supported: + * - Icons (e.g., `$(icon-name)`, when the `supportIcons` flag is also set) + * - Bold, italics, and strikethrough formatting, but only when the syntax wraps the entire string + * (e.g., `**bold**`, `_italic_`, `~~strikethrough~~`) + */ + label: string | MarkdownString; + } +} From 2f71d5eea2a89f0877f9cee30c5c299055e5b0c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:51:06 -0700 Subject: [PATCH 1595/4355] Make checks grey, errors red Fixes #273104 --- src/vs/workbench/contrib/chat/browser/media/chat.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index c19b4aed974..1298989ef57 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2179,8 +2179,8 @@ have to be updated for changes to the rules above, or to support more deeply nes /* Very aggressive list styles try to apply focus colors to every codicon in a list row. */ color: var(--vscode-icon-foreground) !important; - &.codicon-check { - color: var(--vscode-debugIcon-startForeground) !important; + &.codicon-error { + color: var(--vscode-editorError-foreground) !important; } } From cbea9a048d458d6a924c362eeccc7a237da44a4b Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 24 Oct 2025 14:51:23 +0200 Subject: [PATCH 1596/4355] jumpToEdit support --- src/vs/editor/common/languages.ts | 1 + .../browser/model/inlineCompletionsModel.ts | 24 ++++++++++++------- .../browser/model/inlineCompletionsSource.ts | 1 + .../browser/model/inlineSuggestionItem.ts | 21 ++++++++++++---- .../browser/model/provideInlineCompletions.ts | 4 +++- .../view/inlineEdits/inlineEditsView.ts | 6 ++--- src/vs/monaco.d.ts | 1 + .../api/common/extHostLanguageFeatures.ts | 1 + ...e.proposed.inlineCompletionsAdditions.d.ts | 1 + 9 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index b2729dd3a38..29d1b55b5cb 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -854,6 +854,7 @@ export interface InlineCompletionDisplayLocation { range: IRange; kind: InlineCompletionDisplayLocationKind; label: string; + jumpToEdit: boolean; } /** diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 1e937933ea0..fe2da384cb7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -73,14 +73,7 @@ export class InlineCompletionsModel extends Disposable { return false; } - const targetRange = state.inlineCompletion.targetRange; - const visibleRanges = this._editorObs.editor.getVisibleRanges(); - if (visibleRanges.length < 1) { - return false; - } - - const viewportRange = new Range(visibleRanges[0].startLineNumber, visibleRanges[0].startColumn, visibleRanges[visibleRanges.length - 1].endLineNumber, visibleRanges[visibleRanges.length - 1].endColumn); - return viewportRange.containsRange(targetRange); + return isSuggestionInViewport(this._editor, state.inlineCompletion); }); public get isAcceptingPartially() { return this._isAcceptingPartially; } @@ -828,7 +821,7 @@ export class InlineCompletionsModel extends Disposable { if (this._tabShouldIndent.read(reader)) { return false; } - if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader)) { + if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.displayLocation?.jumpToEdit) { return true; } if (s.inlineCompletion.targetRange.startLineNumber === this._editorObs.cursorLineNumber.read(reader)) { @@ -1120,6 +1113,8 @@ export class InlineCompletionsModel extends Disposable { this._editor.revealRange(revealRange, ScrollType.Immediate); } + s.inlineCompletion.identity.setJumpTo(tx); + this._editor.focus(); }); } @@ -1210,3 +1205,14 @@ class FadeoutDecoration extends Disposable { })); } } + +function isSuggestionInViewport(editor: ICodeEditor, suggestion: InlineSuggestionItem): boolean { + const targetRange = suggestion.targetRange; + const visibleRanges = editor.getVisibleRanges(); + if (visibleRanges.length < 1) { + return false; + } + + const viewportRange = new Range(visibleRanges[0].startLineNumber, visibleRanges[0].startColumn, visibleRanges[visibleRanges.length - 1].endLineNumber, visibleRanges[visibleRanges.length - 1].endColumn); + return viewportRange.containsRange(targetRange); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index ac8d6ff2516..0cf635456cf 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -270,6 +270,7 @@ export class InlineCompletionsSource extends Disposable { text: c.insertText, isInlineEdit: !!c.isInlineEdit, source: c.source.provider.groupId, + displayLocation: c.displayLocation ? { label: c.displayLocation.label, range: c.displayLocation.range.toString(), kind: c.displayLocation.kind, jumpToEdit: c.displayLocation.jumpToEdit } : undefined, })); this._log({ sourceId: 'InlineCompletions.fetch', kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now(), didAllProvidersReturn }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index bfd2af15d05..08c321e12c1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -5,7 +5,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { matchesSubString } from '../../../../../base/common/filters.js'; -import { IObservable, observableSignal } from '../../../../../base/common/observable.js'; +import { IObservable, ITransaction, observableSignal, observableValue } from '../../../../../base/common/observable.js'; import { commonPrefixLength, commonSuffixLength, splitLines } from '../../../../../base/common/strings.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ISingleEditOperation } from '../../../../common/core/editOperation.js'; @@ -57,7 +57,7 @@ abstract class InlineSuggestionItemBase { public get isFromExplicitRequest(): boolean { return this._data.context.triggerKind === InlineCompletionTriggerKind.Explicit; } public get forwardStable(): boolean { return this.source.inlineSuggestions.enableForwardStability ?? false; } public get editRange(): Range { return this.getSingleTextEdit().range; } - public get targetRange(): Range { return this.displayLocation?.range ?? this.editRange; } + public get targetRange(): Range { return this.displayLocation?.range && !this.displayLocation.jumpToEdit ? this.displayLocation?.range : this.editRange; } public get insertText(): string { return this.getSingleTextEdit().text; } public get semanticId(): string { return this.hash; } public get action(): Command | undefined { return this._sourceInlineCompletion.action; } @@ -143,6 +143,11 @@ export class InlineSuggestionIdentity { private readonly _onDispose = observableSignal(this); public readonly onDispose: IObservable = this._onDispose; + private readonly _jumpedTo = observableValue(this, false); + public get jumpedTo(): IObservable { + return this._jumpedTo; + } + private _refCount = 1; public readonly id = 'InlineCompletionIdentity' + InlineSuggestionIdentity.idCounter++; @@ -156,6 +161,10 @@ export class InlineSuggestionIdentity { this._onDispose.trigger(undefined); } } + + setJumpTo(tx: ITransaction | undefined): void { + this._jumpedTo.set(true, tx); + } } class InlineSuggestDisplayLocation implements IDisplayLocation { @@ -164,14 +173,16 @@ class InlineSuggestDisplayLocation implements IDisplayLocation { return new InlineSuggestDisplayLocation( displayLocation.range, displayLocation.label, - displayLocation.kind + displayLocation.kind, + displayLocation.jumpToEdit ); } private constructor( public readonly range: Range, public readonly label: string, - public readonly kind: InlineCompletionDisplayLocationKind + public readonly kind: InlineCompletionDisplayLocationKind, + public readonly jumpToEdit: boolean, ) { } public withEdit(edit: StringEdit, positionOffsetTransformer: PositionOffsetTransformerBase): InlineSuggestDisplayLocation | undefined { @@ -187,7 +198,7 @@ class InlineSuggestDisplayLocation implements IDisplayLocation { const newRange = positionOffsetTransformer.getRange(newOffsetRange); - return new InlineSuggestDisplayLocation(newRange, this.label, this.kind); + return new InlineSuggestDisplayLocation(newRange, this.label, this.kind, this.jumpToEdit); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 1a377b78c68..06999ae802a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -231,7 +231,8 @@ function toInlineSuggestData( const displayLocation = inlineCompletion.displayLocation ? { range: Range.lift(inlineCompletion.displayLocation.range), label: inlineCompletion.displayLocation.label, - kind: inlineCompletion.displayLocation.kind + kind: inlineCompletion.displayLocation.kind, + jumpToEdit: inlineCompletion.displayLocation.jumpToEdit, } : undefined; return new InlineSuggestData( @@ -476,6 +477,7 @@ export interface IDisplayLocation { range: Range; label: string; kind: InlineCompletionDisplayLocationKind; + jumpToEdit: boolean; } export enum InlineCompletionEditorType { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 2d3babdead7..f0a32c4843a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -46,7 +46,7 @@ export class InlineEditsView extends Disposable { private readonly _tabAction; - private _previousView: { + private _previousView: { // TODO, move into identitiy id: string; view: ReturnType; editorWidth: number; @@ -384,7 +384,7 @@ export class InlineEditsView extends Disposable { private determineView(model: IInlineEditModel, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText): InlineCompletionViewKind { // Check if we can use the previous view if it is the same InlineCompletion as previously shown const inlineEdit = model.inlineEdit; - const canUseCache = this._previousView?.id === this.getCacheId(model); + const canUseCache = this._previousView?.id === this.getCacheId(model) && !model.displayLocation?.jumpToEdit; const reconsiderViewEditorWidthChange = this._previousView?.editorWidth !== this._editorObs.layoutInfoWidth.read(reader) && ( this._previousView?.view === InlineCompletionViewKind.SideBySide || @@ -395,7 +395,7 @@ export class InlineEditsView extends Disposable { return this._previousView!.view; } - if (model.displayLocation) { + if (model.displayLocation && !model.inlineEdit.inlineCompletion.identity.jumpedTo.read(reader)) { return InlineCompletionViewKind.Custom; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cad4767ed96..7932b302e1d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7566,6 +7566,7 @@ declare namespace monaco.languages { range: IRange; kind: InlineCompletionDisplayLocationKind; label: string; + jumpToEdit: boolean; } /** diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b29a46c820b..fd3fcf47653 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1440,6 +1440,7 @@ class InlineCompletionAdapter { range: typeConvert.Range.from(item.displayLocation.range), label: item.displayLocation.label, kind: item.displayLocation.kind ? typeConvert.InlineCompletionDisplayLocationKind.from(item.displayLocation.kind) : InlineCompletionDisplayLocationKind.Code, + jumpToEdit: item.displayLocation.jumpToEdit ?? false, } : undefined, warning: (item.warning && this._isAdditionsProposedApiEnabled) ? { message: typeConvert.MarkdownString.from(item.warning.message), diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index ec929299fdf..2fad488e561 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -78,6 +78,7 @@ declare module 'vscode' { range: Range; kind: InlineCompletionDisplayLocationKind; label: string; + jumpToEdit?: boolean; } export interface InlineCompletionWarning { From 9e290174570fa95ee8ad30917a3e9eb3c06e67de Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Fri, 24 Oct 2025 14:58:20 +0200 Subject: [PATCH 1597/4355] Update src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../browser/view/inlineEdits/inlineEditsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index f0a32c4843a..2f2de846e80 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -46,7 +46,7 @@ export class InlineEditsView extends Disposable { private readonly _tabAction; - private _previousView: { // TODO, move into identitiy + private _previousView: { // TODO, move into identity id: string; view: ReturnType; editorWidth: number; From 1d2743ade61b21c72a8f6b9c053a8ae096b9694e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Oct 2025 15:05:09 +0200 Subject: [PATCH 1598/4355] status - make first/last items align to the window border (#273103) * status - make first/last items align to the window border * Update src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../parts/statusbar/media/statusbarpart.css | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 0340f2c9d6b..22410c9c8ea 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -90,24 +90,8 @@ border-right: 5px solid transparent; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item { - padding-left: 7px; /* Add padding to the most left status bar item */ -} - +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item, .monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item { - margin-right: 7px; /* Add margin to the most right status bar item. Margin is used to position beak properly. */ -} - -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label, -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item > .statusbar-item-label { - /* As such, clear margins on label*/ - margin-right: 0; - margin-left: 0; -} - -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.left.first-visible-item, -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.right.last-visible-item { - /* Tweak appearance for items with background to improve hover feedback */ padding-right: 0; padding-left: 0; } @@ -143,6 +127,8 @@ margin-right:0; } +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label, +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item > .statusbar-item-label, .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color > .statusbar-item-label { margin-left: 0; margin-right: 0; From 8457c8d536979b98b8cb16f70dd654a7ef5e195c Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 24 Oct 2025 15:09:32 +0200 Subject: [PATCH 1599/4355] remove error from endoflife --- src/vs/editor/common/languages.ts | 1 - .../browser/model/inlineSuggestionItem.ts | 4 ---- .../browser/model/provideInlineCompletions.ts | 10 ---------- .../contrib/inlineCompletions/browser/telemetry.ts | 2 -- .../browser/view/inlineEdits/inlineEditsModel.ts | 6 ------ .../browser/view/inlineEdits/inlineEditsView.ts | 4 ++-- .../view/inlineEdits/inlineEditsViewInterface.ts | 1 - src/vs/monaco.d.ts | 1 - .../api/browser/mainThreadLanguageFeatures.ts | 1 - 9 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index b2729dd3a38..d0ecd3c1627 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1033,7 +1033,6 @@ export type LifetimeSummary = { notShownReason: string | undefined; editorType: string; viewKind: string | undefined; - error: string | undefined; preceeded: boolean; languageId: string; requestReason: string; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index bfd2af15d05..701355e6180 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -118,10 +118,6 @@ abstract class InlineSuggestionItemBase { this._data.setEndOfLifeReason(reason); } - public reportInlineEditError(reason: string): void { - this._data.reportInlineEditError(reason); - } - public setIsPreceeded(item: InlineSuggestionItem): void { this._data.setIsPreceeded(item.partialAccepts); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 1a377b78c68..4c9eed414c7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -275,7 +275,6 @@ export type InlineSuggestViewData = { editorType: InlineCompletionEditorType; renderData?: InlineCompletionViewData; viewKind?: InlineCompletionViewKind; - error?: string; }; export class InlineSuggestData { @@ -395,7 +394,6 @@ export class InlineSuggestData { requestReason: this._requestInfo.reason, viewKind: this._viewData.viewKind, notShownReason: this._notShownReason, - error: this._viewData.error, typingInterval: this._requestInfo.typingInterval, typingIntervalCharacterCount: this._requestInfo.typingIntervalCharacterCount, availableProviders: this._requestInfo.availableProviders.map(p => p.toString()).join(','), @@ -405,14 +403,6 @@ export class InlineSuggestData { } } - public reportInlineEditError(message: string): void { - if (this._viewData.error) { - this._viewData.error += `; ${message}`; - } else { - this._viewData.error = message; - } - } - public setIsPreceeded(partialAccepts: PartialAcceptance): void { this._isPreceeded = true; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts index bda92ed5c84..b8f1ca93c6c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/telemetry.ts @@ -38,7 +38,6 @@ export type InlineCompletionEndOfLifeEvent = { partiallyAcceptedCharactersSinceOriginal: number | undefined; preceeded: boolean | undefined; superseded: boolean | undefined; - error: string | undefined; notShownReason: string | undefined; // rendering viewKind: string | undefined; @@ -78,7 +77,6 @@ type InlineCompletionsEndOfLifeClassification = { preceeded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was preceeded by another one' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language ID of the document where the inline completion was shown' }; requestReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason for the inline completion request' }; - error: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The error message if the inline completion failed' }; typingInterval: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The average typing interval of the user at the moment the inline completion was requested' }; typingIntervalCharacterCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The character count involved in the typing interval calculation' }; superseded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the inline completion was superseded by another one' }; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts index a99ca8009b4..c475f9b6545 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts @@ -49,12 +49,6 @@ export class InlineEditModel implements IInlineEditModel { this._model.jump(); } - abort(reason: string) { - console.error(reason); - this.inlineEdit.inlineCompletion.reportInlineEditError(reason); - this._model.stop(); - } - handleInlineEditShown(viewKind: InlineCompletionViewKind, viewData: InlineCompletionViewData) { this._model.handleInlineSuggestionShown(this.inlineEdit.inlineCompletion, viewKind, viewData); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 2d3babdead7..ddb9e0ff559 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -5,7 +5,7 @@ import { $ } from '../../../../../../base/browser/dom.js'; import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equals.js'; -import { BugIndicatingError } from '../../../../../../base/common/errors.js'; +import { BugIndicatingError, onUnexpectedError } from '../../../../../../base/common/errors.js'; import { Event } from '../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { autorun, autorunWithStore, derived, derivedOpts, IObservable, IReader, ISettableObservable, mapObservableArrayCached, observableValue } from '../../../../../../base/common/observable.js'; @@ -85,7 +85,7 @@ export class InlineEditsView extends Disposable { let state = this.determineRenderState(model, reader, diff, new StringText(newText)); if (!state) { - model.abort(`unable to determine view: tried to render ${this._previousView?.view}`); + onUnexpectedError(`unable to determine view: tried to render ${this._previousView?.view}`); return undefined; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts index 0f7f8cb25f2..d93a10438dc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts @@ -39,7 +39,6 @@ export interface IInlineEditModel { handleInlineEditShown(viewKind: string, viewData?: InlineCompletionViewData): void; accept(): void; jump(): void; - abort(reason: string): void; } // TODO: Move this out of here as it is also includes ghosttext diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cad4767ed96..f8c6a3648cb 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7671,7 +7671,6 @@ declare namespace monaco.languages { notShownReason: string | undefined; editorType: string; viewKind: string | undefined; - error: string | undefined; preceeded: boolean; languageId: string; requestReason: string; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 799de991883..60c3eb0e0c2 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -731,7 +731,6 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread viewKind: lifetimeSummary.viewKind, preceeded: lifetimeSummary.preceeded, requestReason: lifetimeSummary.requestReason, - error: lifetimeSummary.error, typingInterval: lifetimeSummary.typingInterval, typingIntervalCharacterCount: lifetimeSummary.typingIntervalCharacterCount, languageId: lifetimeSummary.languageId, From 4af82e4001bc0c3f8c5a65d3a881684005225ffc Mon Sep 17 00:00:00 2001 From: yavanosta Date: Fri, 24 Oct 2025 11:16:41 +0200 Subject: [PATCH 1600/4355] UriIdentityService: use strings as keys for _canonicalUris cache (#273108) --- .../uriIdentity/common/uriIdentityService.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/uriIdentity/common/uriIdentityService.ts b/src/vs/platform/uriIdentity/common/uriIdentityService.ts index 175a7c62ae2..1d3668bc1f7 100644 --- a/src/vs/platform/uriIdentity/common/uriIdentityService.ts +++ b/src/vs/platform/uriIdentity/common/uriIdentityService.ts @@ -11,6 +11,7 @@ import { ExtUri, IExtUri, normalizePath } from '../../../base/common/resources.j import { SkipList } from '../../../base/common/skipList.js'; import { Event, Emitter } from '../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { compare as strCompare } from '../../../base/common/strings.js'; class Entry { static _clock = 0; @@ -88,7 +89,7 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic readonly extUri: IExtUri; private readonly _pathCasingCache: PathCasingCache; - private readonly _canonicalUris: SkipList; + private readonly _canonicalUris: SkipList; private readonly _limit = 2 ** 16; constructor(@IFileService private readonly _fileService: IFileService) { @@ -96,11 +97,23 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic this._pathCasingCache = this._register(new PathCasingCache(this._fileService)); + this._register(this._pathCasingCache.onFileSystemCasingChanged( + e => this._handleFileSystemCasingChanged(e))); + this.extUri = new ExtUri(uri => this._pathCasingCache.shouldIgnorePathCasing(uri)); - this._canonicalUris = new SkipList((a, b) => this.extUri.compare(a, b, true), this._limit); + this._canonicalUris = new SkipList(strCompare, this._limit); this._register(toDisposable(() => this._canonicalUris.clear())); } + private _handleFileSystemCasingChanged(e: IFileSystemCasingChangedEvent): void { + for (const [key, entry] of this._canonicalUris.entries()) { + if (entry.uri.scheme !== e.scheme) { + continue; + } + this._canonicalUris.delete(key); + } + } + asCanonicalUri(uri: URI): URI { // (1) normalize URI @@ -109,13 +122,14 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic } // (2) find the uri in its canonical form or use this uri to define it - const item = this._canonicalUris.get(uri); + const uriKey = this.extUri.getComparisonKey(uri, true); + const item = this._canonicalUris.get(uriKey); if (item) { return item.touch().uri.with({ fragment: uri.fragment }); } // this uri is first and defines the canonical form - this._canonicalUris.set(uri, new Entry(uri)); + this._canonicalUris.set(uriKey, new Entry(uri)); this._checkTrim(); return uri; From 87b5e61cc490d7999f4066437f19f077ed588460 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:13:52 +0200 Subject: [PATCH 1601/4355] Update src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../browser/view/inlineEdits/inlineEditsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index ddb9e0ff559..c96c8849218 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -85,7 +85,7 @@ export class InlineEditsView extends Disposable { let state = this.determineRenderState(model, reader, diff, new StringText(newText)); if (!state) { - onUnexpectedError(`unable to determine view: tried to render ${this._previousView?.view}`); + onUnexpectedError(new Error(`unable to determine view: tried to render ${this._previousView?.view}`)); return undefined; } From 7a2952815d578473efef00a58596a68b2a5b7b4e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:19:32 -0700 Subject: [PATCH 1602/4355] Remove unused import --- .../toolInvocationParts/chatToolInvocationPart.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts index 5167579f1cc..1bc1111a5ee 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts @@ -27,7 +27,6 @@ import { ChatToolOutputSubPart } from './chatToolOutputPart.js'; import { ChatToolPostExecuteConfirmationPart } from './chatToolPostExecuteConfirmationPart.js'; import { ChatToolProgressSubPart } from './chatToolProgressPart.js'; import { autorun } from '../../../../../../base/common/observable.js'; -import { ChatMcpServersInteractionContentPart } from '../chatMcpServersInteractionContentPart.js'; import { localize } from '../../../../../../nls.js'; import { markdownCommandLink, MarkdownString } from '../../../../../../base/common/htmlContent.js'; From b537b64aa36030d09f2e7cd1ebc8e8116576dfac Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:24:00 -0700 Subject: [PATCH 1603/4355] Handle terminal exit while terminal tool is in progress Fixes #264565 --- .../executeStrategy/basicExecuteStrategy.ts | 16 +++++++++++++-- .../executeStrategy/richExecuteStrategy.ts | 20 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts index 68b83061852..2d2107a4b99 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/basicExecuteStrategy.ts @@ -66,12 +66,19 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { this._log('onDone 1 of 2 via end event, waiting for short idle prompt'); return idlePromptPromise.then(() => { this._log('onDone 2 of 2 via short idle prompt'); - return e; + return { + 'type': 'success', + command: e + } as const; }); }), Event.toPromise(token.onCancellationRequested as Event, store).then(() => { this._log('onDone via cancellation'); }), + Event.toPromise(this._instance.onDisposed, store).then(() => { + this._log('onDone via terminal disposal'); + return { type: 'disposal' } as const; + }), // A longer idle prompt event is used here as a catch all for unexpected cases where // the end event doesn't fire for some reason. trackIdleOnPrompt(this._instance, 3000, store).then(() => { @@ -115,7 +122,12 @@ export class BasicExecuteStrategy implements ITerminalExecuteStrategy { // Wait for the next end execution event - note that this may not correspond to the actual // execution requested - const finishedCommand = await onDone; + this._log('Waiting for done event'); + const onDoneResult = await onDone; + if (onDoneResult && onDoneResult.type === 'disposal') { + throw new Error('The terminal was closed'); + } + const finishedCommand = onDoneResult && onDoneResult.type === 'success' ? onDoneResult.command : undefined; // Wait for the terminal to idle this._log('Waiting for idle'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts index 242b829bfce..014a9bd52c5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/executeStrategy/richExecuteStrategy.ts @@ -8,7 +8,7 @@ import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { DisposableStore, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { isNumber } from '../../../../../../base/common/types.js'; -import type { ICommandDetectionCapability, ITerminalCommand } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; +import type { ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService } from '../../../../../../platform/terminal/common/terminal.js'; import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { trackIdleOnPrompt, type ITerminalExecuteStrategy, type ITerminalExecuteStrategyResult } from './executeStrategy.js'; @@ -46,14 +46,21 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy { throw new Error('Xterm is not available'); } - const onDone: Promise = Promise.race([ + const onDone = Promise.race([ Event.toPromise(this._commandDetection.onCommandFinished, store).then(e => { this._log('onDone via end event'); - return e; + return { + 'type': 'success', + command: e + } as const; }), Event.toPromise(token.onCancellationRequested as Event, store).then(() => { this._log('onDone via cancellation'); }), + Event.toPromise(this._instance.onDisposed, store).then(() => { + this._log('onDone via terminal disposal'); + return { type: 'disposal' } as const; + }), trackIdleOnPrompt(this._instance, 1000, store).then(() => { this._log('onDone via idle prompt'); }), @@ -73,7 +80,12 @@ export class RichExecuteStrategy implements ITerminalExecuteStrategy { // Wait for the terminal to idle this._log('Waiting for done event'); - const finishedCommand = await onDone; + const onDoneResult = await onDone; + if (onDoneResult && onDoneResult.type === 'disposal') { + throw new Error('The terminal was closed'); + } + const finishedCommand = onDoneResult && onDoneResult.type === 'success' ? onDoneResult.command : undefined; + if (token.isCancellationRequested) { throw new CancellationError(); } From 838f5bfd449517b1bf2fe185aad651c44816e28b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:27:25 -0700 Subject: [PATCH 1604/4355] Remove multi-line hint from terminal tool desc Part of #262386 --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 1 - .../test/electron-browser/treeSitterCommandParser.test.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) 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 720ed55c58f..9bc47ccc12b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -94,7 +94,6 @@ function createPowerShellModelDescription(shell: string): string { const genericDescription = ` Command Execution: -- Does NOT support multi-line commands - Use && to chain simple commands on one line - Prefer pipelines | over temporary files for data flow diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 7e1f6412fc9..45ad4e6caaa 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -90,6 +90,7 @@ suite('TreeSitterCommandParser', () => { test('mixed quote types', () => t('echo "hello \`world\`" && echo \'test\'', ['echo "hello \`world\`"', 'world', 'echo \'test\''])); test('deeply nested structures', () => t('echo $(echo $(echo $(echo nested))) && ls', ['echo $(echo $(echo $(echo nested)))', 'echo $(echo $(echo nested))', 'echo $(echo nested)', 'echo nested', 'ls'])); test('unicode command names', () => t('测试命令 && echo done', ['测试命令', 'echo done'])); + test('multi-line', () => t('echo a\necho b', ['echo a', 'echo b'])); }); // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704 From 6e1e1b91fe7a7b4718530f8eb37c820824607f91 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 24 Oct 2025 15:28:37 +0200 Subject: [PATCH 1605/4355] jump instead of indent --- .../inlineCompletions/browser/model/inlineCompletionsModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index fe2da384cb7..7e1951e8e9d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -803,7 +803,7 @@ export class InlineCompletionsModel extends Disposable { return true; } - if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader)) { + if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.displayLocation?.jumpToEdit) { return false; } From cde8af31f7c7962b4c47a2ea5a06060e4a439a7c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:31:47 -0700 Subject: [PATCH 1606/4355] Hint again using sub-shells Fixes #271351 --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 2 ++ 1 file changed, 2 insertions(+) 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 720ed55c58f..ffa97941a5b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -60,6 +60,7 @@ function createPowerShellModelDescription(shell: string): string { // doesn't parse `&&`. See https://github.com/airbus-cert/tree-sitter-powershell/issues/27 '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', '- Prefer pipelines | for object-based data flow', + '- Never create a sub-shell (eg. powershell -c "command") unless explicitly asked', '', 'Directory Management:', '- Must use absolute paths to avoid navigation issues', @@ -97,6 +98,7 @@ Command Execution: - Does NOT support multi-line commands - Use && to chain simple commands on one line - Prefer pipelines | over temporary files for data flow +- Never create a sub-shell (eg. bash -c "command") unless explicitly asked Directory Management: - Must use absolute paths to avoid navigation issues From 57b5d9e720edb7aa7623b0963aae481fe378296a Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 24 Oct 2025 14:47:41 +0100 Subject: [PATCH 1607/4355] scm(history): use panel background for history item label text when badge color present to increase legibility. Replace historyItemHoverLabelForeground with PANEL_BACKGROUND and update import to ensure label text contrasts against colored badges. Removed unused import. --- src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 21a3f1e9ae0..201ed8b57fa 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -28,7 +28,8 @@ import { asCssVariable, ColorIdentifier, foreground } from '../../../../platform import { IFileIconTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IViewPaneOptions, ViewAction, ViewPane, ViewPaneShowActions } from '../../../browser/parts/views/viewPane.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; -import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverLabelForeground, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; +import { PANEL_BACKGROUND } from '../../../common/theme.js'; +import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; import { getHistoryItemEditorTitle, getProviderKey, isSCMHistoryItemChangeNode, isSCMHistoryItemChangeViewModelTreeElement, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService, ViewMode } from '../common/scm.js'; @@ -529,7 +530,7 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer Date: Fri, 24 Oct 2025 11:22:28 +0200 Subject: [PATCH 1608/4355] UriIdentityService: use quickSelect for cache trim instead of sorting (#273108) --- .../uriIdentity/common/uriIdentityService.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/uriIdentity/common/uriIdentityService.ts b/src/vs/platform/uriIdentity/common/uriIdentityService.ts index 1d3668bc1f7..a094f3bc2f0 100644 --- a/src/vs/platform/uriIdentity/common/uriIdentityService.ts +++ b/src/vs/platform/uriIdentity/common/uriIdentityService.ts @@ -11,6 +11,7 @@ import { ExtUri, IExtUri, normalizePath } from '../../../base/common/resources.j import { SkipList } from '../../../base/common/skipList.js'; import { Event, Emitter } from '../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { quickSelect } from '../../../base/common/arrays.js'; import { compare as strCompare } from '../../../base/common/strings.js'; class Entry { @@ -140,24 +141,21 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic return; } - // get all entries, sort by time (MRU) and re-initalize - // the uri cache and the entry clock. this is an expensive - // operation and should happen rarely - const entries = [...this._canonicalUris.entries()].sort((a, b) => { - if (a[1].time < b[1].time) { - return 1; - } else if (a[1].time > b[1].time) { - return -1; + Entry._clock = 0; + const times = [...this._canonicalUris.values()].map(e => e.time); + const median = quickSelect( + Math.floor(times.length / 2), + times, + (a, b) => a - b); + for (const [key, entry] of this._canonicalUris.entries()) { + // Its important to remove the median value here (<= not <). + // If we have not touched any items since the last trim, the + // median will be 0 and no items will be removed otherwise. + if (entry.time <= median) { + this._canonicalUris.delete(key); } else { - return 0; + entry.time = 0; } - }); - - Entry._clock = 0; - this._canonicalUris.clear(); - const newSize = this._limit * 0.5; - for (let i = 0; i < newSize; i++) { - this._canonicalUris.set(entries[i][0], entries[i][1].touch()); } } } From 00a48dab2f53ec70eb653c4f1b99a376f71dd4c3 Mon Sep 17 00:00:00 2001 From: yavanosta Date: Fri, 24 Oct 2025 11:23:26 +0200 Subject: [PATCH 1609/4355] UriIdentityService: use native Map instead of skip list for cache (#273108) --- src/vs/platform/uriIdentity/common/uriIdentityService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/uriIdentity/common/uriIdentityService.ts b/src/vs/platform/uriIdentity/common/uriIdentityService.ts index a094f3bc2f0..cb537503209 100644 --- a/src/vs/platform/uriIdentity/common/uriIdentityService.ts +++ b/src/vs/platform/uriIdentity/common/uriIdentityService.ts @@ -8,11 +8,9 @@ import { URI } from '../../../base/common/uri.js'; import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; import { IFileService, FileSystemProviderCapabilities, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent } from '../../files/common/files.js'; import { ExtUri, IExtUri, normalizePath } from '../../../base/common/resources.js'; -import { SkipList } from '../../../base/common/skipList.js'; import { Event, Emitter } from '../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; import { quickSelect } from '../../../base/common/arrays.js'; -import { compare as strCompare } from '../../../base/common/strings.js'; class Entry { static _clock = 0; @@ -90,7 +88,7 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic readonly extUri: IExtUri; private readonly _pathCasingCache: PathCasingCache; - private readonly _canonicalUris: SkipList; + private readonly _canonicalUris: Map; private readonly _limit = 2 ** 16; constructor(@IFileService private readonly _fileService: IFileService) { @@ -102,7 +100,7 @@ export class UriIdentityService extends Disposable implements IUriIdentityServic e => this._handleFileSystemCasingChanged(e))); this.extUri = new ExtUri(uri => this._pathCasingCache.shouldIgnorePathCasing(uri)); - this._canonicalUris = new SkipList(strCompare, this._limit); + this._canonicalUris = new Map(); this._register(toDisposable(() => this._canonicalUris.clear())); } From 01f4c0104b4de6a7588488d0f8c13a082c970fe3 Mon Sep 17 00:00:00 2001 From: yavanosta Date: Fri, 24 Oct 2025 15:08:07 +0200 Subject: [PATCH 1610/4355] UriIdentityService: add test for cache cleanup order --- .../test/common/uriIdentityService.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts b/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts index bb0a791f378..fac6d800d6c 100644 --- a/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts +++ b/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts @@ -102,6 +102,73 @@ suite('URI Identity', function () { assertCanonical(URI.parse('foo://bar/BAZZ#DDD'), b.with({ fragment: 'DDD' })); // lower-case path, but fragment is kept }); + test('clears cache when overflown with respect to access time', () => { + const CACHE_SIZE = 2 ** 16; + const getUri = (i: number) => URI.parse(`foo://bar/${i}`); + + const FIRST = 0; + const SECOND = 1; + const firstCached = _service.asCanonicalUri(getUri(FIRST)); + const secondCached = _service.asCanonicalUri(getUri(SECOND)); + for (let i = 2; i < CACHE_SIZE - 1; i++) { + _service.asCanonicalUri(getUri(i)); + } + + // Assert that the first URI is still the same object. + assert.strictEqual(_service.asCanonicalUri(getUri(FIRST)), firstCached); + + // Clear the cache. + _service.asCanonicalUri(getUri(CACHE_SIZE - 1)); + + // First URI should still be the same object. + assert.strictEqual(_service.asCanonicalUri(getUri(FIRST)), firstCached); + // But the second URI should be a new object, since it was evicted. + assert.notStrictEqual(_service.asCanonicalUri(getUri(SECOND)), secondCached); + }); + + test('preserves order of access time on cache cleanup', () => { + const SIZE = 2 ** 16; + const getUri = (i: number) => URI.parse(`foo://bar/${i}`); + + const FIRST = 0; + const firstCached = _service.asCanonicalUri(getUri(FIRST)); + for (let i = 1; i < SIZE - 2; i++) { + _service.asCanonicalUri(getUri(i)); + } + const LAST = SIZE - 2; + const lastCached = _service.asCanonicalUri(getUri(LAST)); + + // Clear the cache. + _service.asCanonicalUri(getUri(SIZE - 1)); + + // Batch 2 + const BATCH2_FIRST = SIZE; + const batch2FirstCached = _service.asCanonicalUri(getUri(BATCH2_FIRST)); + const BATCH2_SECOND = SIZE + 1; + const batch2SecondCached = _service.asCanonicalUri(getUri(BATCH2_SECOND)); + const BATCH2_THIRD = SIZE + 2; + const batch2ThirdCached = _service.asCanonicalUri(getUri(BATCH2_THIRD)); + for (let i = SIZE + 3; i < SIZE + Math.floor(SIZE / 2) - 1; i++) { + _service.asCanonicalUri(getUri(i)); + } + const BATCH2_LAST = SIZE + Math.floor(SIZE / 2); + const batch2LastCached = _service.asCanonicalUri(getUri(BATCH2_LAST)); + + // Clean up the cache. + _service.asCanonicalUri(getUri(SIZE + Math.ceil(SIZE / 2) + 1)); + + // Both URIs from the first batch should be evicted. + assert.notStrictEqual(_service.asCanonicalUri(getUri(FIRST)), firstCached); + assert.notStrictEqual(_service.asCanonicalUri(getUri(LAST)), lastCached); + + // But the URIs from the second batch should still be the same objects. + // Except for the first one, which is removed as a median value. + assert.notStrictEqual(_service.asCanonicalUri(getUri(BATCH2_FIRST)), batch2FirstCached); + assert.strictEqual(_service.asCanonicalUri(getUri(BATCH2_SECOND)), batch2SecondCached); + assert.strictEqual(_service.asCanonicalUri(getUri(BATCH2_THIRD)), batch2ThirdCached); + assert.strictEqual(_service.asCanonicalUri(getUri(BATCH2_LAST)), batch2LastCached); + }); + test.skip('[perf] CPU pegged after some builds #194853', function () { const n = 100 + (2 ** 16); From fc298c243e0dcf607a86270fc51aafe9bc2ca887 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:54:11 -0700 Subject: [PATCH 1611/4355] Re-write && to ; in pwsh Fixes #272704 --- .../browser/commandSimplifier.ts | 32 +++++++++++++++++-- .../browser/tools/runInTerminalTool.ts | 7 ++-- .../browser/treeSitterCommandParser.ts | 25 ++++++++++++++- .../treeSitterCommandParser.test.ts | 3 +- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts index b3edc53ab26..6558c4f488b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts @@ -9,22 +9,32 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm import type { ITerminalInstance } from '../../../terminal/browser/terminal.js'; import { isPowerShell } from './runInTerminalHelpers.js'; import type { IRunInTerminalInputParams } from './tools/runInTerminalTool.js'; +import { TreeSitterCommandParserLanguage, type TreeSitterCommandParser } from './treeSitterCommandParser.js'; export class CommandSimplifier { constructor( private readonly _osBackend: Promise, + private readonly _treeSitterCommandParser: TreeSitterCommandParser, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { } async rewriteIfNeeded(args: IRunInTerminalInputParams, instance: Pick | undefined, shell: string): Promise { - const commandLine = args.command; const os = await this._osBackend; + let commandLine = args.command; + commandLine = await this._removeRedundantCdPrefix(instance, commandLine, os, shell); + commandLine = await this._rewriteUnsupportedPwshChainOperators(commandLine, os, shell); + + return commandLine; + } + + private async _removeRedundantCdPrefix(instance: Pick | undefined, commandLine: string, os: OperatingSystem, shell: string): Promise { + const isPwsh = isPowerShell(shell, 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 isPwsh = isPowerShell(shell, os); const cdPrefixMatch = commandLine.match( isPwsh ? /^(?:cd(?: \/d)?|Set-Location(?: -Path)?) (?[^\s]+) ?(?:&&|;)\s+(?.+)$/i @@ -68,7 +78,25 @@ export class CommandSimplifier { } } } + return commandLine; + } + private async _rewriteUnsupportedPwshChainOperators(commandLine: string, os: OperatingSystem, shell: string) { + // TODO: This should just be Windows PowerShell in the future when the powershell grammar + // supports chain operators https://github.com/airbus-cert/tree-sitter-powershell/issues/27 + if (isPowerShell(shell, os)) { + const doubleAmpersandCaptures = await this._treeSitterCommandParser.queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ + '(', + ' (command', + ' (command_elements', + ' (generic_token) @double.ampersand', + ' (#eq? @double.ampersand "&&")))', + ')', + ].join('\n')); + for (const capture of doubleAmpersandCaptures.reverse()) { + commandLine = `${commandLine.substring(0, capture.node.startIndex)};${commandLine.substring(capture.node.endIndex)}`; + } + } return commandLine; } } 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 720ed55c58f..bd0baf2cf67 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -56,8 +56,9 @@ function createPowerShellModelDescription(shell: string): string { `This tool allows you to execute ${isWinPwsh ? 'Windows PowerShell 5.1' : 'PowerShell'} commands in a persistent terminal session, preserving environment variables, working directory, and other context across multiple commands.`, '', 'Command Execution:', - // Even for pwsh 7+ we want to use `;` to chain commands since the tree sitter grammar - // doesn't parse `&&`. See https://github.com/airbus-cert/tree-sitter-powershell/issues/27 + // TODO: Even for pwsh 7+ we want to use `;` to chain commands since the tree sitter grammar + // doesn't parse `&&`. We want to change this to avoid `&&` only in Windows PowerShell when + // the grammar supports it https://github.com/airbus-cert/tree-sitter-powershell/issues/27 '- Use semicolons ; to chain commands on one line, NEVER use && even when asked explicitly', '- Prefer pipelines | for object-based data flow', '', @@ -292,8 +293,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._osBackend = this._remoteAgentService.getEnvironment().then(remoteEnv => remoteEnv?.os ?? OS); this._terminalToolCreator = _instantiationService.createInstance(ToolTerminalCreator); - this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend); this._treeSitterCommandParser = this._instantiationService.createInstance(TreeSitterCommandParser); + this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend, this._treeSitterCommandParser); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 44d4a4446a0..829f686fbca 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -6,7 +6,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { derived, waitForState } from '../../../../../base/common/observable.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import type { Language, Parser, Query } from '@vscode/tree-sitter-wasm'; +import type { Language, Parser, Query, QueryCapture } from '@vscode/tree-sitter-wasm'; export const enum TreeSitterCommandParserLanguage { Bash = 'bash', @@ -42,9 +42,32 @@ export class TreeSitterCommandParser { const captures = query.captures(tree.rootNode); const subCommands = captures.map(e => e.node.text); + return subCommands; } + async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { + const parser = await this._parser; + const language = await waitForState(derived(reader => { + return this._treeSitterLibraryService.getLanguage(languageId, true, reader); + })); + parser.setLanguage(language); + + const tree = parser.parse(commandLine); + if (!tree) { + throw new BugIndicatingError('Failed to parse tree'); + } + + const query = await this._treeSitterLibraryService.createQuery(language, querySource); + if (!query) { + throw new BugIndicatingError('Failed to create tree sitter query'); + } + + const captures = query.captures(tree.rootNode); + + return captures; + } + private async _getQuery(language: Language): Promise { let query = this._queries.get(language); if (!query) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 7e1f6412fc9..10f0dd13bb1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -22,8 +22,7 @@ suite('TreeSitterCommandParser', () => { let parser: TreeSitterCommandParser; setup(() => { - const logService = new NullLogService(); - const fileService = store.add(new FileService(logService)); + const fileService = store.add(new FileService(new NullLogService())); const fileSystemProvider = new TestIPCFileSystemProvider(); store.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); From a334aa3cb20a791b4ccc1ff40eb90739023964d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:07:06 -0700 Subject: [PATCH 1612/4355] Add tests for double ampersand rewriting --- .../commandSimplifier.test.ts | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) rename src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/{browser => electron-browser}/commandSimplifier.test.ts (83%) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandSimplifier.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts similarity index 83% rename from src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandSimplifier.test.ts rename to src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts index 16d79df4c23..4d59d244d4f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandSimplifier.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts @@ -15,6 +15,13 @@ import type { ITerminalInstance } from '../../../../terminal/browser/terminal.js import { URI } from '../../../../../../base/common/uri.js'; import type { IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; import { Workspace } from '../../../../../../platform/workspace/test/common/testWorkspace.js'; +import { TreeSitterCommandParser } from '../../browser/treeSitterCommandParser.js'; +import { ITreeSitterLibraryService } from '../../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; +import { NullLogService } from '../../../../../../platform/log/common/log.js'; +import { FileService } from '../../../../../../platform/files/common/fileService.js'; +import { Schemas } from '../../../../../../base/common/network.js'; +import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; suite('command re-writing', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -22,6 +29,7 @@ suite('command re-writing', () => { let instantiationService: TestInstantiationService; let workspaceService: TestContextService; + let parser: TreeSitterCommandParser; let commandSimplifier: CommandSimplifier; function createRewriteParams(command: string, chatSessionId?: string): IRunInTerminalInputParams { @@ -45,14 +53,27 @@ suite('command re-writing', () => { } setup(() => { - instantiationService = workbenchInstantiationService(undefined, store); + const fileService = store.add(new FileService(new NullLogService())); + const fileSystemProvider = new TestIPCFileSystemProvider(); + store.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); + + instantiationService = workbenchInstantiationService({ + fileService: () => fileService, + }, store); + + const treeSitterLibraryService = store.add(instantiationService.createInstance(TreeSitterLibraryService)); + treeSitterLibraryService.isTest = true; + instantiationService.stub(ITreeSitterLibraryService, treeSitterLibraryService); + + parser = instantiationService.createInstance(TreeSitterCommandParser); + workspaceService = instantiationService.get(IWorkspaceContextService) as TestContextService; }); suite('cd && -> ', () => { (!isWindows ? suite : suite.skip)('Posix', () => { setup(() => { - commandSimplifier = instantiationService.createInstance(CommandSimplifier, Promise.resolve(OperatingSystem.Linux)); + commandSimplifier = instantiationService.createInstance(CommandSimplifier, Promise.resolve(OperatingSystem.Linux), parser); }); test('should return original command when no cd prefix pattern matches', async () => { @@ -168,7 +189,7 @@ suite('command re-writing', () => { (isWindows ? suite : suite.skip)('Windows', () => { setup(() => { - commandSimplifier = instantiationService.createInstance(CommandSimplifier, Promise.resolve(OperatingSystem.Windows)); + commandSimplifier = instantiationService.createInstance(CommandSimplifier, Promise.resolve(OperatingSystem.Windows), parser); }); test('should ignore any trailing back slash', async () => { @@ -344,4 +365,22 @@ suite('command re-writing', () => { }); }); }); + + suite('PowerShell: && -> ;', () => { + async function t(originalCommandLine: string, expectedResult: string) { + const parameters = createRewriteParams(originalCommandLine); + const result = await commandSimplifier.rewriteIfNeeded(parameters, undefined, 'pwsh'); + strictEqual(result, expectedResult); + } + setup(() => { + commandSimplifier = instantiationService.createInstance(CommandSimplifier, Promise.resolve(OperatingSystem.Windows), parser); + }); + + test('should rewrite && to ; in PowerShell commands', () => t('echo hello && echo world', 'echo hello ; echo world')); + test('should rewrite multiple && to ; in PowerShell commands', () => t('echo first && echo second && echo third', 'echo first ; echo second ; echo third')); + test('should handle complex commands with && operators', () => t('npm install && npm test && echo "build complete"', 'npm install ; npm test ; echo "build complete"')); + test('should work with Windows PowerShell shell identifier', () => t('Get-Process && Stop-Process', 'Get-Process ; Stop-Process')); + test('should preserve existing semicolons', () => t('echo hello; echo world && echo final', 'echo hello; echo world ; echo final')); + test('should not rewrite strings', () => t('echo "&&" && Write-Host "&& &&" && "&&"', 'echo "&&" ; Write-Host "&& &&" ; "&&"')); + }); }); From e1f78f13a11e6a1f20dfacbe03f8c7b64b031ac2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:19:09 -0700 Subject: [PATCH 1613/4355] Fix old test to play nicely with new rewriting --- .../test/electron-browser/commandSimplifier.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts index 4d59d244d4f..afc5113eded 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts @@ -331,7 +331,7 @@ suite('command re-writing', () => { test('should not rewrite cd /d when directory does not match cwd', async () => { const testDir = 'C:\\test\\workspace'; const differentDir = 'C:\\different\\path'; - const command = `cd /d ${differentDir} && echo hello`; + const command = `cd /d ${differentDir} ; echo hello`; const options = createRewriteParams(command, 'session-1'); setWorkspaceFolders([URI.file(testDir)]); From 0c1b9a07349d75bc3e5518c18b126e4aed4bb336 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 24 Oct 2025 17:24:50 +0100 Subject: [PATCH 1614/4355] chore(codicons): add repo-selected, skip, and merge-into icons; regenerate codicon.ttf --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119348 -> 119864 bytes src/vs/base/common/codiconsLibrary.ts | 3 +++ 2 files changed, 3 insertions(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4bd5d8c0d88a28164f526649ed011ade41161f5d..5adbf1621cd9607cee95825f55a62933f9d5dbb1 100644 GIT binary patch delta 4389 zcmXY!2Yij!AI5*ry;()fNYIE7iIxx%wO8$3d(;d`>?9OHSz({?CshaEo{s zj!hUI?K6B=Es<(BMVzn4MW-Y>12CV*%kli+_~>!5*2|+^1&5zc3p8I#P_v;=aSe89ll!Oj>PMnAlRPPD6bT2y7wyU2agErZ!EA1JZjJtts-wc zd?)kYYs6!@IRD)Wr;CVqW{QVL4DVK2N^*~*V1B{kf=>!A+i%!i-D9laA`M-|c+`n< zn2ZT>1JSsE_pk}|uwDA%IhJBA*2r#YjQ+AwenC}fi*vF>%HbC|ESnLJc4&%bxGj(6 ztlYt8I3U|_2D{}CO#P9WU?)AFdFu@V6Yno2ZB&NtTH+Tbj!p zN#PGqmw7T%669^Ej@q(A_Q>~m#4ai?sqjO4%*0s?=_R+Z5-ZRdJJB1%B_6hPKpn}J z2M9$g#7G2sqLVa{rqUhf@dF;_H;leDmlr;Q_zwi>5k&jPt1y^wmm)PfV=qh>m z%?kIe*l@O-mZ=gT59JZMOJ%8o5Gjiv(Hvy}IN$?Mc%ul4qBu&SG(6zM$MQ3VS+jgY zU5lhQmLp4^qAtF`m(m3rBY-@wVJY7x9z3vR*n#3XwlYwn zr4|;;1{sQF(ot{+4hnJO7BE&UWq+qzhp&$huHU_yt$0~@X;}l$=M<{rY9;sjx zJxW16dbGj;B?V&?=}V7Q@SGke5@ZT2WhGw0S~@|&8mmFc5cIbOmTZi3)*5dAVx1@% z5^2tzp`ab@QqYu6Q_zf_so=I%vQ$LmXROUtI9{b-o+8_5&Q%7Sp%*IHZ6%fph|Xhe znSwp^as@}}6$(z%D-|54IkOr#5TqbOk$dzi1^ekt1t)VJmulzbdPl^rte`$!ML`2P zL_tHks)ELJsDc)Bn1XldY6@D@Zz*U?a{@P@16^G~N4kcH%M_e@#12=4EoIkK(1)(2 zfSqVZD4e6juC0i9m`DZh({&UKq@xrJqU$OcLYp5=FjQP(Gc^V+6x}AdQbbAF%tZkZ}z_hMudV+bhs}l=i{xt2*3Kq~^ z6fC5>Dp*2yQ^1(n-4!ruw&_BGRdi1U=AnBjSWov>u#q-hOI+B(jlK%D(xz_-I6Cb9 z3U<(@Zwa#L0Sflgrk@Euqz5V3M-Nu;5pDXM@ZU%5p$h*`#f~=pAI$?ftPE3N4h&at zgpN^gj5Zk|I6=oLI7OR`5L~29MhHHkM=7{Yn~V_LpvNe<$^N%ZUpPf<`n zrz&RgWt-p;JfWv4_?D(*wvm#HYqSyY*uiV&Qf5awndTKv`G`eO4_7}U(>- z5@!nh#fo_lf|v9K1()fM739+w6?{sYU=v)SFDWn&WUe8&Mw@F0E?I%WA?Rwg2oB)N zs9$gnW?RK7w?lWUSLMdOKk_USEQ03NhRQq3o9{FaNaSbQJQLx%Xke9ZvB+u>65v`c z;x4Hmi!PLj#axT?$=~#a2vM2$os%!y5ZG;T8QAd~RJ2 z>)CWLYtafg4Y`LYT*!%gxFY768Ds+(e0QvZHrCK;VNKklSS8@x;U2AE9c^w$xB`@4 zp$G%z3D#bi__1g-I!l)>eY~vOvb)RMFTc3rtreLo z9V_FkAZwa+B%@qL>x}%&u9?qP-&oUN&Azq%Ylp7gy|!Ro=XH12_gJ65p~uF+ja@g+ z+BAC8-c9#6hi#7Ad?u?(*4V84E%mls+Zw!e@itfBwt?HOY%jij%?|$^n|8d)9+#^cD~-#c30M}rytDSU1WF4?rVGe_r&jcx;JKT;fFImnsK1|fu#oy9t=7->ENlH z|y!;=o*KKvrLbZ)KO&bjfq`FUY^YmQVna^+~y(S66(9B*)Z|B18{ z`6p|g+<&V0sl-#aPX`5^&OTjmCj88Wvwmmio;`JLRn%Z?7t0b(^DJol5Zc^QsbovVK8`s^eewxtB*^L|~-T$J5c$ z=|jAH9CZT10zAATJUkItJFtJG)63i0(#emvmou_|K(zo*56NrnjXw3za`noli1b5! zb7L0%ekEY;9#*&h>Z?srsZ=GWx6|L;-PwR!fllY@XH8M1%w(stY^2lEhkLox+sO$d zZ+26(2`+6m1r<<%Z@%UE!Xjl>Pj86|J@wA|8v-(`HVFx7(%uwRD@6qbMKz16R4FRJ z;TUXw2MY(v|96}x{r2DSW>G=eP9NtW^Sy%|4#~ST2+{rUI+|~{>ckI zhCwBY78w^iVMJ`rQSr$MdDAark)ycpgxJJ{nn|%^V}~cl#^m{2;ro-PN79&4iFrv^ Han|GiM`;6% delta 3942 zcmXBXd3;Rw8prYHE5ve}h+Y*@G-Oe#wX{uON;`tCpL3x%d0)^T+#q<~K7qbIx;Sj*6m&hj%v2>FS8- zQx+!D_=-rIvr}UdqrCUOStRn@IzeV~bY#Sou(jJpi%2a6Me~VzW8DJzxRU&%XU=!%J(9IL9?|4$Y_)I-#rZda=vGcs$*ba3?Tb-NqoZ-$_ z-Ov*AFk3Dm9H$V9&3GBxBdp$}fe8+a1~F$izT zQjEb^ye$EE2k+uNOu$4;K_sFu715Z68HhnFW?>HIV!muZ0uqsgg-FI?EWuJNLnc;Y z4c5ZK``CzFILFp%mpff@3&?v-k|3;|qL=8eGQLxPotR z6*o|e?{O19;b;7U-(ce&{=j|i+@JU_>ZAfsqYc{1T$v|}rH?F;1b)g4Ste-`Cmp2| zy2>uuCqLmko(~^MLURm&167zfO0FOWt1$#SF&dL37Qf0Mbdy4d;&*#5(y(zLXl=#w{6)G^EH>`39ZPQEp(UG?#B>vkZ|b7$*~CvOJP^ zWuhEG3zWzt#9O|eK0Q}Ro`hqSjFRW2vkXTts-=UBlxebEO5_5@V>&V<5xdY&Zb^N~ zmGf}&8gRGLJnO6+&&Exk;J`}Dm&{hAo}0Czsb9oA5$9yZF&5_(C70Mpg>W`X;S@Vn zA(V|)*vw8-c$uB9u-)?X@(#Gm)l7vfHdbK+8>g_|8tmnVFe}z87$>Z~eC)ESz5KeF zKbNA=pLHm_%BCvxX44eDvf4N6;_)e0OBIT(3C+A6$GKXec#(;7rIH7%*(btbcC`Y( zz??Y>W$YRSerY+^DpatR;t-2-os#S9`wAbh>qXqnhF5Z3vjOf7o&&p`LJ-?t;T6_T zp$F@)(2EUF=)(poyvBA=c!Pab;Z3%q!a%l@!XWlJ5r^6Ei$mMqBz7hpPImVv(2X_aNcb5sR>bR zPX*4KZH@qe^JWJtaNcZAfq@w;r@%lA8=}BTv&|7B%wo+EdyON6IehTC!d$km;uoaY z=1>wZU9tNqB(UbN5)#?|3Q6n$g@vp++=OJ-9B#s5cCf+{)-i+|f;sF%6_&BXl=-o< zLlsuCBNRA;wmEGC&Y(R~fiq~2QsCU#qZKx?=5!KrS#vtcfqXtN=aaCN4O7_0n)690 zV8|xEhCG2I#E9_&_fR{K-|C!hhLxg*vOcbp{SvBim%)JL__rV8;Ny zlCNM6kQoa?6>G+VFpAx-#2g4SB7_{)j0j;hyHjBZyGy}5SEyj-bGPE}xY$J^oyX|N%lU!9O z{J}ioGv%y0y z&L})!&CC)$Vn0;)m^Ev{Io7NR=UKBRTwu+b@Cj?ygb%G3eEoceaaFCvTt(&zAl~Op z_boxX)wbOLjIg5G1$+F+SDcFXImPXE`*1I9Gc6>)vvrF1KC$h8a4Z*bc`6yhHdV-G zy%g_^#MMm69rj6u7ukO+Tx6Ro-a(1$DJ9%`eBuo8yM^n%vat~ zsJ3nfj`A7LRfNKH*8F)wh85bu+k;n;D^j7K72hGS#a)OP!qb1Zo`SQarbA>sN2l27 zu@!Niap`fjvtnkI&8|N?eD=Ng@c7Gf8qJBCQ#g0&-23z5=bfG3e*Q-bhAyZ~@J|R! z$V>P&F)HzNQtPCcq~fH93wte0SXh;03Ku*Qt}RM$=&j8zGwc}{Pp>FwvODIx3z9t)V9alM{KVw z7+R23@Mwo4WXHLk{dZRH>bxs!*NwvH!cTX&nKK zohu(){-`3f;>MA4N28C{9y@TXu5vg;rfY(dxu|El%7&+3#f8$y=v3 zo*r>}@0q|e+s-^Z8&Xp9T=BJXXG07RD!&_o9=_#!CL%lxf0s9@z+Z0V?v)tdv^@3` TtgKhwu$upED_`(APSyWEN?N<2 diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 9ecd64c4cf3..30f7fb60e14 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -630,4 +630,7 @@ export const codiconsLibrary = { indexZero: register('index-zero', 0xec66), agent: register('agent', 0xec67), editCode: register('edit-code', 0xec68), + repoSelected: register('repo-selected', 0xec69), + skip: register('skip', 0xec6a), + mergeInto: register('merge-into', 0xec6b), } as const; From 4597a2f362fcfa0fb46eb60f1c8dc9c8e3f0013d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 09:41:42 -0700 Subject: [PATCH 1615/4355] wip --- .../chatEditingCheckpointTimelineImpl.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index 8643ce63e6e..7ade860b5d4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -59,7 +59,8 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh /** Gets the checkpoint, if any, we can 'undo' to. */ private readonly _willUndoToCheckpoint = derived(reader => { const currentEpoch = this._currentEpoch.read(reader); - const maxEpoch = this._operations.read(reader).findLast(op => op.epoch < currentEpoch)?.epoch || 0; + const operations = this._operations.read(reader); + const maxEpoch = operations.findLast(op => op.epoch <= currentEpoch)?.epoch || 0; return this._checkpoints.read(reader).findLast(cp => cp.epoch < maxEpoch); }); @@ -73,14 +74,17 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh private readonly _willRedoToEpoch = derived(reader => { const currentEpoch = this._currentEpoch.read(reader); const operations = this._operations.read(reader); - const maxOperationEpoch = operations.at(-1)?.epoch || 0; - if (currentEpoch > maxOperationEpoch) { + const checkpoints = this._checkpoints.read(reader); + const maxEncounteredEpoch = Math.max(operations.at(-1)?.epoch || 0, checkpoints.at(-1)?.epoch || 0); + if (currentEpoch > maxEncounteredEpoch) { return undefined; } + // Find either the first checkpoint that would apply operations, or just + // use the last checkpoint. const minEpoch = operations.find(op => op.epoch >= currentEpoch)?.epoch; - const checkpointEpoch = minEpoch && this._checkpoints.read(reader).find(op => op.epoch > minEpoch)?.epoch; - return checkpointEpoch || (maxOperationEpoch + 1); + const checkpointEpoch = minEpoch && checkpoints.find(op => op.epoch > minEpoch)?.epoch; + return checkpointEpoch || (maxEncounteredEpoch + 1); }); public readonly canRedo: IObservable = this._willRedoToEpoch.map(e => !!e); @@ -148,7 +152,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh transaction(tx => { this._checkpoints.set([...existingCheckpoints, checkpoint], tx); - this._currentEpoch.set(checkpoint.epoch, tx); + this._currentEpoch.set(checkpoint.epoch + 1, tx); }); } @@ -542,9 +546,9 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh const isMovingForward = toEpoch > fromEpoch; const operations = this._operations.get().filter(op => { if (isMovingForward) { - return op.epoch > fromEpoch && op.epoch <= toEpoch; + return op.epoch >= fromEpoch && op.epoch < toEpoch; } else { - return op.epoch > toEpoch && op.epoch <= fromEpoch; + return op.epoch < fromEpoch && op.epoch >= toEpoch; } }).sort((a, b) => isMovingForward ? a.epoch - b.epoch : b.epoch - a.epoch); From 111fec7c029f7e242952249dbdba7ef2cf7dafde Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 24 Oct 2025 18:46:13 +0200 Subject: [PATCH 1616/4355] agent files: validation, completion and hovers is target dependend (#273133) * agent files: validation, completion and hovers is target dependend * use Lazy * Update src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../promptSyntax/promptFileRewriter.ts | 6 +- .../promptToolsCodeLensProvider.ts | 18 +- .../PromptHeaderDefinitionProvider.ts | 4 +- .../languageProviders/promptCodeActions.ts | 16 +- .../promptDocumentSemanticTokensProvider.ts | 12 +- .../promptHeaderAutocompletion.ts | 117 ++++--- .../languageProviders/promptHovers.ts | 126 ++++--- .../languageProviders/promptLinkProvider.ts | 8 +- .../languageProviders/promptValidator.ts | 95 +++-- .../common/promptSyntax/promptFileParser.ts | 5 + .../promptSytntax/promptHovers.test.ts | 326 ++++++++++++++++++ .../promptSytntax/promptValidator.test.ts | 120 ++++++- 12 files changed, 688 insertions(+), 165 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts index 67fb1f57082..d3a0d8be7d8 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileRewriter.ts @@ -28,12 +28,12 @@ export class PromptFileRewriter { } const model = editor.getModel(); - const parser = this._promptsService.getParsedPromptFile(model); - if (!parser.header) { + const promptAST = this._promptsService.getParsedPromptFile(model); + if (!promptAST.header) { return undefined; } - const toolsAttr = parser.header.getAttribute(PromptHeaderAttributes.tools); + const toolsAttr = promptAST.header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index e256c983407..fb0b64a7127 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -14,13 +14,14 @@ import { CommandsRegistry } from '../../../../../platform/commands/common/comman import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { showToolsPicker } from '../actions/chatToolPicker.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { ALL_PROMPTS_LANGUAGE_SELECTOR } from '../../common/promptSyntax/promptTypes.js'; +import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../../common/promptSyntax/promptTypes.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { registerEditorFeature } from '../../../../../editor/common/editorFeatures.js'; import { PromptFileRewriter } from './promptFileRewriter.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IEditorModel } from '../../../../../editor/common/editorCommon.js'; import { PromptHeaderAttributes } from '../../common/promptSyntax/promptFileParser.js'; +import { isGithubTarget } from '../../common/promptSyntax/languageProviders/promptValidator.js'; class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider { @@ -48,14 +49,23 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider } async provideCodeLenses(model: ITextModel, token: CancellationToken): Promise { + const promptType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (!promptType || promptType === PromptsType.instructions) { + // if the model is not a prompt, we don't provide any code actions + return undefined; + } - const parser = this.promptsService.getParsedPromptFile(model); - if (!parser.header) { + const promptAST = this.promptsService.getParsedPromptFile(model); + const header = promptAST.header; + if (!header) { return undefined; } + if (isGithubTarget(promptType, header.target)) { + return undefined; + } - const toolsAttr = parser.header.getAttribute(PromptHeaderAttributes.tools); + const toolsAttr = header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr || toolsAttr.value.type !== 'array') { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts index b1baef7e24b..076f9f2d1eb 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.ts @@ -32,8 +32,8 @@ export class PromptHeaderDefinitionProvider implements DefinitionProvider { return undefined; } - const parser = this.promptsService.getParsedPromptFile(model); - const header = parser.header; + const promptAST = this.promptsService.getParsedPromptFile(model); + const header = promptAST.header; if (!header) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts index 56d8bd2d7c0..4902446ac48 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptCodeActions.ts @@ -17,6 +17,7 @@ import { Lazy } from '../../../../../../base/common/lazy.js'; import { LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { URI } from '../../../../../../base/common/uri.js'; +import { isGithubTarget } from './promptValidator.js'; export class PromptCodeActionProvider implements CodeActionProvider { /** @@ -40,15 +41,15 @@ export class PromptCodeActionProvider implements CodeActionProvider { const result: CodeAction[] = []; - const parser = this.promptsService.getParsedPromptFile(model); + const promptAST = this.promptsService.getParsedPromptFile(model); switch (promptType) { case PromptsType.agent: - this.getUpdateToolsCodeActions(parser, model, range, result); + this.getUpdateToolsCodeActions(promptAST, promptType, model, range, result); await this.getMigrateModeFileCodeActions(model.uri, result); break; case PromptsType.prompt: - this.getUpdateModeCodeActions(parser, model, range, result); - this.getUpdateToolsCodeActions(parser, model, range, result); + this.getUpdateModeCodeActions(promptAST, model, range, result); + this.getUpdateToolsCodeActions(promptAST, promptType, model, range, result); break; } @@ -91,11 +92,16 @@ export class PromptCodeActionProvider implements CodeActionProvider { } } - private getUpdateToolsCodeActions(promptFile: ParsedPromptFile, model: ITextModel, range: Range, result: CodeAction[]): void { + private getUpdateToolsCodeActions(promptFile: ParsedPromptFile, promptType: PromptsType, model: ITextModel, range: Range, result: CodeAction[]): void { const toolsAttr = promptFile.header?.getAttribute(PromptHeaderAttributes.tools); if (toolsAttr?.value.type !== 'array' || !toolsAttr.value.range.containsRange(range)) { return; } + if (isGithubTarget(promptType, promptFile.header?.target)) { + // GitHub Copilot custom agents use a fixed set of tool names that are not deprecated + return; + } + const values = toolsAttr.value.items; const deprecatedNames = new Lazy(() => this.languageModelToolsService.getDeprecatedQualifiedToolNames()); const edits: TextEdit[] = []; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts index 9d4ff46fcea..0502655781c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts @@ -8,6 +8,7 @@ import { DocumentSemanticTokensProvider, ProviderResult, SemanticTokens, Semanti import { ITextModel } from '../../../../../../editor/common/model.js'; import { getPromptsTypeForLanguageId } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; +import { isGithubTarget } from './promptValidator.js'; export class PromptDocumentSemanticTokensProvider implements DocumentSemanticTokensProvider { /** @@ -27,12 +28,17 @@ export class PromptDocumentSemanticTokensProvider implements DocumentSemanticTok return undefined; } - const parser = this.promptsService.getParsedPromptFile(model); - if (!parser.body) { + const promptAST = this.promptsService.getParsedPromptFile(model); + if (!promptAST.body) { return undefined; } - const variableReferences = parser.body.variableReferences; + if (isGithubTarget(promptType, promptAST.header?.target)) { + // In GitHub Copilot mode, we don't provide variable semantic tokens to tool references + return undefined; + } + + const variableReferences = promptAST.body.variableReferences; if (!variableReferences.length) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts index ba828a0a341..d3b7f081498 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts @@ -16,7 +16,7 @@ import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; import { Iterable } from '../../../../../../base/common/iterator.js'; import { PromptHeader, PromptHeaderAttributes } from '../promptFileParser.js'; -import { getValidAttributeNames } from './promptValidator.js'; +import { getValidAttributeNames, isGithubTarget, knownGithubCopilotTools } from './promptValidator.js'; import { localize } from '../../../../../../nls.js'; export class PromptHeaderAutocompletion implements CompletionItemProvider { @@ -55,13 +55,13 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return undefined; } - const parser = this.promptsService.getParsedPromptFile(model); - const header = parser.header; + const parsedAST = this.promptsService.getParsedPromptFile(model); + const header = parsedAST.header; if (!header) { return undefined; } - const headerRange = parser.header.range; + const headerRange = parsedAST.header.range; if (position.lineNumber < headerRange.startLineNumber || position.lineNumber >= headerRange.endLineNumber) { // if the position is not inside the header, we don't provide any completions return undefined; @@ -72,42 +72,45 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { const colonPosition = colonIndex !== -1 ? new Position(position.lineNumber, colonIndex + 1) : undefined; if (!colonPosition || position.isBeforeOrEqual(colonPosition)) { - return this.providePropertyCompletions(model, position, headerRange, colonPosition, promptType); + return this.provideAttributeNameCompletions(model, position, header, colonPosition, promptType); } else if (colonPosition && colonPosition.isBefore(position)) { return this.provideValueCompletions(model, position, header, colonPosition, promptType); } return undefined; } - private async providePropertyCompletions( + private async provideAttributeNameCompletions( model: ITextModel, position: Position, - headerRange: Range, + header: PromptHeader, colonPosition: Position | undefined, promptType: PromptsType, ): Promise { const suggestions: CompletionItem[] = []; - const supportedProperties = new Set(getValidAttributeNames(promptType, false)); - this.removeUsedProperties(supportedProperties, model, headerRange, position); - const getInsertText = (property: string): string => { + const isGitHubTarget = isGithubTarget(promptType, header.target); + const attributesToPropose = new Set(getValidAttributeNames(promptType, false, isGitHubTarget)); + for (const attr of header.attributes) { + attributesToPropose.delete(attr.key); + } + const getInsertText = (key: string): string => { if (colonPosition) { - return property; + return key; } - const valueSuggestions = this.getValueSuggestions(promptType, property); + const valueSuggestions = this.getValueSuggestions(promptType, key); if (valueSuggestions.length > 0) { - return `${property}: \${0:${valueSuggestions[0]}}`; + return `${key}: \${0:${valueSuggestions[0]}}`; } else { - return `${property}: \$0`; + return `${key}: \$0`; } }; - for (const property of supportedProperties) { + for (const attribute of attributesToPropose) { const item: CompletionItem = { - label: property, + label: attribute, kind: CompletionItemKind.Property, - insertText: getInsertText(property), + insertText: getInsertText(attribute), insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet, range: new Range(position.lineNumber, 1, position.lineNumber, !colonPosition ? model.getLineMaxColumn(position.lineNumber) : colonPosition.column), }; @@ -127,15 +130,16 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { const suggestions: CompletionItem[] = []; const lineContent = model.getLineContent(position.lineNumber); - const property = lineContent.substring(0, colonPosition.column - 1).trim(); + const attribute = lineContent.substring(0, colonPosition.column - 1).trim(); - if (!getValidAttributeNames(promptType, true).includes(property)) { + const isGitHubTarget = isGithubTarget(promptType, header.target); + if (!getValidAttributeNames(promptType, true, isGitHubTarget).includes(attribute)) { return undefined; } if (promptType === PromptsType.prompt || promptType === PromptsType.agent) { // if the position is inside the tools metadata, we provide tool name completions - const result = this.provideToolCompletions(model, position, header); + const result = this.provideToolCompletions(model, position, header, isGitHubTarget); if (result) { return result; } @@ -143,12 +147,12 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { const bracketIndex = lineContent.indexOf('['); if (bracketIndex !== -1 && bracketIndex <= position.column - 1) { - // if the property is already inside a bracket, we don't provide value completions + // if the value is already inside a bracket, we don't provide value completions return undefined; } const whilespaceAfterColon = (lineContent.substring(colonPosition.column).match(/^\s*/)?.[0].length) ?? 0; - const values = this.getValueSuggestions(promptType, property); + const values = this.getValueSuggestions(promptType, attribute); for (const value of values) { const item: CompletionItem = { label: value, @@ -158,7 +162,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { }; suggestions.push(item); } - if (property === PromptHeaderAttributes.handOffs && (promptType === PromptsType.agent)) { + if (attribute === PromptHeaderAttributes.handOffs && (promptType === PromptsType.agent)) { const value = [ '', ' - label: Start Implementation', @@ -177,39 +181,39 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return { suggestions }; } - private removeUsedProperties(properties: Set, model: ITextModel, headerRange: Range, position: Position): void { - for (let i = headerRange.startLineNumber; i <= headerRange.endLineNumber; i++) { - if (i !== position.lineNumber) { - const lineText = model.getLineContent(i); - const colonIndex = lineText.indexOf(':'); - if (colonIndex !== -1) { - const property = lineText.substring(0, colonIndex).trim(); - properties.delete(property); + private getValueSuggestions(promptType: string, attribute: string): string[] { + switch (attribute) { + case PromptHeaderAttributes.applyTo: + if (promptType === PromptsType.instructions) { + return [`'**'`, `'**/*.ts, **/*.js'`, `'**/*.php'`, `'**/*.py'`]; + } + break; + case PromptHeaderAttributes.agent: + case PromptHeaderAttributes.mode: + if (promptType === PromptsType.prompt) { + // Get all available agents (builtin + custom) + const agents = this.chatModeService.getModes(); + const suggestions: string[] = []; + for (const agent of Iterable.concat(agents.builtin, agents.custom)) { + suggestions.push(agent.name); + } + return suggestions; + } + case PromptHeaderAttributes.target: + if (promptType === PromptsType.agent) { + return ['vscode', 'github-copilot']; + } + break; + case PromptHeaderAttributes.tools: + if (promptType === PromptsType.prompt || promptType === PromptsType.agent) { + return ['[]', `['search', 'edit', 'fetch']`]; + } + break; + case PromptHeaderAttributes.model: + if (promptType === PromptsType.prompt || promptType === PromptsType.agent) { + return this.getModelNames(promptType === PromptsType.agent); } - } - } - } - - private getValueSuggestions(promptType: string, property: string): string[] { - if (promptType === PromptsType.instructions && property === PromptHeaderAttributes.applyTo) { - return [`'**'`, `'**/*.ts, **/*.js'`, `'**/*.php'`, `'**/*.py'`]; - } - if (promptType === PromptsType.prompt && (property === PromptHeaderAttributes.agent || property === PromptHeaderAttributes.mode)) { - // Get all available agents (builtin + custom) - const agents = this.chatModeService.getModes(); - const suggestions: string[] = []; - for (const agent of Iterable.concat(agents.builtin, agents.custom)) { - suggestions.push(agent.name); - } - return suggestions; - } - if (property === PromptHeaderAttributes.tools && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { - return ['[]', `['search', 'edit', 'fetch']`]; - } - if (property === PromptHeaderAttributes.model && (promptType === PromptsType.prompt || promptType === PromptsType.agent)) { - return this.getModelNames(promptType === PromptsType.agent); } - return []; } @@ -226,14 +230,15 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider { return result; } - private provideToolCompletions(model: ITextModel, position: Position, header: PromptHeader): CompletionList | undefined { + private provideToolCompletions(model: ITextModel, position: Position, header: PromptHeader, isGitHubTarget: boolean): CompletionList | undefined { const toolsAttr = header.getAttribute(PromptHeaderAttributes.tools); if (!toolsAttr || toolsAttr.value.type !== 'array' || !toolsAttr.range.containsPosition(position)) { return undefined; } const getSuggestions = (toolRange: Range) => { const suggestions: CompletionItem[] = []; - for (const toolName of this.languageModelToolsService.getQualifiedToolNames()) { + const toolNames = isGitHubTarget ? Object.keys(knownGithubCopilotTools) : this.languageModelToolsService.getQualifiedToolNames(); + for (const toolName of toolNames) { let insertText: string; if (!toolRange.isEmpty()) { const firstChar = model.getValueInRange(toolRange).charCodeAt(0); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 2d83e9ab045..5e3f5fc5119 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -16,6 +16,7 @@ import { IChatModeService, isBuiltinChatMode } from '../../chatModes.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { IPromptsService } from '../service/promptsService.js'; import { IHeaderAttribute, PromptBody, PromptHeader, PromptHeaderAttributes } from '../promptFileParser.js'; +import { isGithubTarget, knownGithubCopilotTools } from './promptValidator.js'; export class PromptHoverProvider implements HoverProvider { /** @@ -46,12 +47,12 @@ export class PromptHoverProvider implements HoverProvider { return undefined; } - const parser = this.promptsService.getParsedPromptFile(model); - if (parser.header?.range.containsPosition(position)) { - return this.provideHeaderHover(position, promptType, parser.header); + const promptAST = this.promptsService.getParsedPromptFile(model); + if (promptAST.header?.range.containsPosition(position)) { + return this.provideHeaderHover(position, promptType, promptAST.header); } - if (parser.body?.range.containsPosition(position)) { - return this.provideBodyHover(position, parser.body); + if (promptAST.body?.range.containsPosition(position)) { + return this.provideBodyHover(position, promptAST.body); } return undefined; } @@ -68,66 +69,70 @@ export class PromptHoverProvider implements HoverProvider { private async provideHeaderHover(position: Position, promptType: PromptsType, header: PromptHeader): Promise { if (promptType === PromptsType.instructions) { - const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; - if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), descriptionRange); - } - const applyToRange = header.getAttribute(PromptHeaderAttributes.applyTo)?.range; - if (applyToRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: `**/*.ts`, `**/*.js`, `client/**`'), applyToRange); + for (const attribute of header.attributes) { + if (attribute.range.containsPosition(position)) { + switch (attribute.key) { + case PromptHeaderAttributes.description: + return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), attribute.range); + case PromptHeaderAttributes.applyTo: + return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: `**/*.ts`, `**/*.js`, `client/**`'), attribute.range); + } + } } - } else if (promptType === PromptsType.agent) { - const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; - if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.agent.description', 'The description of the custom agent, what it does and when to use it.'), descriptionRange); - } - const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; - if (argumentHintRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.agent.argumentHint', 'The argument-hint describes what inputs the custom agent expects or supports'), argumentHintRange); - } - const model = header.getAttribute(PromptHeaderAttributes.model); - if (model?.range.containsPosition(position)) { - return this.getModelHover(model, model.range, localize('promptHeader.agent.model', 'Specify the model that runs this custom agent.')); - } - const tools = header.getAttribute(PromptHeaderAttributes.tools); - if (tools?.range.containsPosition(position)) { - return this.getToolHover(tools, position, localize('promptHeader.agent.tools', 'The set of tools that the custom agent has access to.')); - } - const targetRange = header.getAttribute(PromptHeaderAttributes.target)?.range; - if (targetRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.agent.target', 'The target to which the header attributes like tools apply to.'), targetRange); + const isGitHubTarget = isGithubTarget(promptType, header.target); + for (const attribute of header.attributes) { + if (attribute.range.containsPosition(position)) { + switch (attribute.key) { + case PromptHeaderAttributes.description: + return this.createHover(localize('promptHeader.agent.description', 'The description of the custom agent, what it does and when to use it.'), attribute.range); + case PromptHeaderAttributes.argumentHint: + return this.createHover(localize('promptHeader.agent.argumentHint', 'The argument-hint describes what inputs the custom agent expects or supports.'), attribute.range); + case PromptHeaderAttributes.model: + return this.getModelHover(attribute, attribute.range, localize('promptHeader.agent.model', 'Specify the model that runs this custom agent.'), isGitHubTarget); + case PromptHeaderAttributes.tools: + return this.getToolHover(attribute, position, localize('promptHeader.agent.tools', 'The set of tools that the custom agent has access to.'), isGitHubTarget); + case PromptHeaderAttributes.handOffs: + return this.getHandsOffHover(attribute, position, isGitHubTarget); + case PromptHeaderAttributes.target: + return this.createHover(localize('promptHeader.agent.target', 'The target to which the header attributes like tools apply to. Possible values are `github-copilot` and `vscode`.'), attribute.range); + } + } } } else { - const descriptionRange = header.getAttribute(PromptHeaderAttributes.description)?.range; - if (descriptionRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.prompt.description', 'The description of the reusable prompt, what it does and when to use it.'), descriptionRange); - } - const argumentHintRange = header.getAttribute(PromptHeaderAttributes.argumentHint)?.range; - if (argumentHintRange?.containsPosition(position)) { - return this.createHover(localize('promptHeader.prompt.argumentHint', 'The argument-hint describes what inputs the prompt expects or supports'), argumentHintRange); - } - const model = header.getAttribute(PromptHeaderAttributes.model); - if (model?.range.containsPosition(position)) { - return this.getModelHover(model, model.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.')); - } - const tools = header.getAttribute(PromptHeaderAttributes.tools); - if (tools?.range.containsPosition(position)) { - return this.getToolHover(tools, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.')); - } - const agent = header.getAttribute(PromptHeaderAttributes.agent) ?? header.getAttribute(PromptHeaderAttributes.mode); - if (agent?.range.containsPosition(position)) { - return this.getAgentHover(agent, position, localize('promptHeader.prompt.agent', 'The agent to use in this prompt.')); + for (const attribute of header.attributes) { + if (attribute.range.containsPosition(position)) { + switch (attribute.key) { + case PromptHeaderAttributes.description: + return this.createHover(localize('promptHeader.prompt.description', 'The description of the reusable prompt, what it does and when to use it.'), attribute.range); + case PromptHeaderAttributes.argumentHint: + return this.createHover(localize('promptHeader.prompt.argumentHint', 'The argument-hint describes what inputs the prompt expects or supports.'), attribute.range); + case PromptHeaderAttributes.model: + return this.getModelHover(attribute, attribute.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.'), false); + case PromptHeaderAttributes.tools: + return this.getToolHover(attribute, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.')); + case PromptHeaderAttributes.agent: + case PromptHeaderAttributes.mode: + return this.getAgentHover(attribute, position); + } + } } } return undefined; } - private getToolHover(node: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { + private getToolHover(node: IHeaderAttribute, position: Position, baseMessage: string, isGithubCopilotTarget?: boolean): Hover | undefined { if (node.value.type === 'array') { for (const toolName of node.value.items) { if (toolName.type === 'string' && toolName.range.containsPosition(position)) { - return this.getToolHoverByName(toolName.value, toolName.range); + if (isGithubCopilotTarget) { + const description = knownGithubCopilotTools[toolName.value]; + if (description) { + return this.createHover(description, toolName.range); + } + } else { + return this.getToolHoverByName(toolName.value, toolName.range); + } } } } @@ -158,7 +163,10 @@ export class PromptHoverProvider implements HoverProvider { return this.createHover(lines.join('\n'), range); } - private getModelHover(node: IHeaderAttribute, range: Range, baseMessage: string): Hover | undefined { + private getModelHover(node: IHeaderAttribute, range: Range, baseMessage: string, isGitHubTarget: boolean): Hover | undefined { + if (isGitHubTarget) { + return this.createHover(baseMessage + '\n\n' + localize('promptHeader.agent.model.githubCopilot', 'Note: This attribute is not used when target is github-copilot.'), range); + } if (node.value.type === 'string') { for (const id of this.languageModelsService.getLanguageModelIds()) { const meta = this.languageModelsService.lookupLanguageModel(id); @@ -178,7 +186,7 @@ export class PromptHoverProvider implements HoverProvider { return this.createHover(baseMessage, range); } - private getAgentHover(agentAttribute: IHeaderAttribute, position: Position, baseMessage: string): Hover | undefined { + private getAgentHover(agentAttribute: IHeaderAttribute, position: Position): Hover | undefined { const lines: string[] = []; const value = agentAttribute.value; if (value.type === 'string' && value.range.containsPosition(position)) { @@ -211,4 +219,12 @@ export class PromptHoverProvider implements HoverProvider { return this.createHover(lines.join('\n'), agentAttribute.range); } + private getHandsOffHover(attribute: IHeaderAttribute, position: Position, isGitHubTarget: boolean): Hover | undefined { + const handoffsBaseMessage = localize('promptHeader.agent.handoffs', 'Possible handoff actions when the agent has completed its task.'); + if (isGitHubTarget) { + return this.createHover(handoffsBaseMessage + '\n\n' + localize('promptHeader.agent.handoffs.githubCopilot', 'Note: This attribute is not used when target is github-copilot.'), attribute.range); + } + return this.createHover(handoffsBaseMessage, attribute.range); + + } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts index c62ca12cdc8..fa837293353 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptLinkProvider.ts @@ -21,14 +21,14 @@ export class PromptLinkProvider implements LinkProvider { * Provide list of links for the provided text model. */ public async provideLinks(model: ITextModel, token: CancellationToken): Promise { - const parser = this.promptsService.getParsedPromptFile(model); - if (!parser.body) { + const promptAST = this.promptsService.getParsedPromptFile(model); + if (!promptAST.body) { return; } const links: ILink[] = []; - for (const ref of parser.body.fileReferences) { + for (const ref of promptAST.body.fileReferences) { if (!ref.isMarkdownLink) { - const url = parser.body.resolveFilePath(ref.content); + const url = promptAST.body.resolveFilePath(ref.content); if (url) { links.push({ range: ref.range, url }); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index 121feaf88c7..a79544217c9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -16,7 +16,7 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { IArrayValue, IHeaderAttribute, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; +import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -24,6 +24,7 @@ import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IPromptsService } from '../service/promptsService.js'; import { ILabelService } from '../../../../../../platform/label/common/label.js'; import { AGENTS_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js'; +import { Lazy } from '../../../../../../base/common/lazy.js'; const MARKERS_OWNER_ID = 'prompts-diagnostics-provider'; @@ -40,7 +41,7 @@ export class PromptValidator { public async validate(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { promptAST.header?.errors.forEach(error => report(toMarker(error.message, error.range, MarkerSeverity.Error))); this.validateHeader(promptAST, promptType, report); - await this.validateBody(promptAST, report); + await this.validateBody(promptAST, promptType, report); await this.validateFileName(promptAST, promptType, report); } @@ -55,7 +56,7 @@ export class PromptValidator { } } - private async validateBody(promptAST: ParsedPromptFile, report: (markers: IMarkerData) => void): Promise { + private async validateBody(promptAST: ParsedPromptFile, promptType: PromptsType, report: (markers: IMarkerData) => void): Promise { const body = promptAST.body; if (!body) { return; @@ -85,8 +86,10 @@ export class PromptValidator { } } + const isGitHubTarget = isGithubTarget(promptType, promptAST.header?.target); + // Validate variable references (tool or toolset names) - if (body.variableReferences.length) { + if (body.variableReferences.length && !isGitHubTarget) { const headerTools = promptAST.header?.tools; const headerToolsMap = headerTools ? this.languageModelToolsService.toToolAndToolSetEnablementMap(headerTools) : undefined; @@ -117,30 +120,16 @@ export class PromptValidator { if (!header) { return; } - const validAttributeNames = getValidAttributeNames(promptType, true); const attributes = header.attributes; - for (const attribute of attributes) { - if (!validAttributeNames.includes(attribute.key)) { - const supportedNames = getValidAttributeNames(promptType, false).join(', '); - switch (promptType) { - case PromptsType.prompt: - report(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); - break; - case PromptsType.agent: - report(toMarker(localize('promptValidator.unknownAttribute.agent', "Attribute '{0}' is not supported in custom agent files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); - break; - case PromptsType.instructions: - report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames), attribute.range, MarkerSeverity.Warning)); - break; - } - } - } + const isGitHubTarget = isGithubTarget(promptType, header.target); + this.checkForInvalidArguments(attributes, promptType, isGitHubTarget, report); + this.validateDescription(attributes, report); this.validateArgumentHint(attributes, report); switch (promptType) { case PromptsType.prompt: { const agent = this.validateAgent(attributes, report); - this.validateTools(attributes, agent?.kind ?? ChatModeKind.Agent, report); + this.validateTools(attributes, agent?.kind ?? ChatModeKind.Agent, false, report); this.validateModel(attributes, agent?.kind ?? ChatModeKind.Agent, report); break; } @@ -149,13 +138,40 @@ export class PromptValidator { this.validateExcludeAgent(attributes, report); break; - case PromptsType.agent: + case PromptsType.agent: { this.validateTarget(attributes, report); - this.validateTools(attributes, ChatModeKind.Agent, report); - this.validateModel(attributes, ChatModeKind.Agent, report); - this.validateHandoffs(attributes, report); + this.validateTools(attributes, ChatModeKind.Agent, isGitHubTarget, report); + if (!isGitHubTarget) { + this.validateModel(attributes, ChatModeKind.Agent, report); + this.validateHandoffs(attributes, report); + } break; + } + + } + } + private checkForInvalidArguments(attributes: IHeaderAttribute[], promptType: PromptsType, isGitHubTarget: boolean, report: (markers: IMarkerData) => void): void { + const validAttributeNames = getValidAttributeNames(promptType, true, isGitHubTarget); + for (const attribute of attributes) { + if (!validAttributeNames.includes(attribute.key)) { + const supportedNames = new Lazy(() => getValidAttributeNames(promptType, false, isGitHubTarget).sort().join(', ')); + switch (promptType) { + case PromptsType.prompt: + report(toMarker(localize('promptValidator.unknownAttribute.prompt', "Attribute '{0}' is not supported in prompt files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Warning)); + break; + case PromptsType.agent: + if (isGitHubTarget) { + report(toMarker(localize('promptValidator.unknownAttribute.github-agent', "Attribute '{0}' is not supported in custom GitHub Copilot agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Warning)); + } else { + report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in VS Code agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Warning)); + } + break; + case PromptsType.instructions: + report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Warning)); + break; + } + } } } @@ -269,7 +285,7 @@ export class PromptValidator { return undefined; } - private validateTools(attributes: IHeaderAttribute[], agentKind: ChatModeKind, report: (markers: IMarkerData) => void): undefined { + private validateTools(attributes: IHeaderAttribute[], agentKind: ChatModeKind, isGitHubTarget: boolean, report: (markers: IMarkerData) => void): undefined { const attribute = attributes.find(attr => attr.key === PromptHeaderAttributes.tools); if (!attribute) { return; @@ -280,14 +296,18 @@ export class PromptValidator { switch (attribute.value.type) { case 'array': - this.validateToolsArray(attribute.value, report); + if (isGitHubTarget) { + // no validation for github-copilot target + } else { + this.validateVSCodeTools(attribute.value, report); + } break; default: report(toMarker(localize('promptValidator.toolsMustBeArrayOrMap', "The 'tools' attribute must be an array."), attribute.value.range, MarkerSeverity.Error)); } } - private validateToolsArray(valueItem: IArrayValue, report: (markers: IMarkerData) => void) { + private validateVSCodeTools(valueItem: IArrayValue, report: (markers: IMarkerData) => void) { if (valueItem.items.length > 0) { const available = new Set(this.languageModelToolsService.getQualifiedToolNames()); const deprecatedNames = this.languageModelToolsService.getDeprecatedQualifiedToolNames(); @@ -419,13 +439,17 @@ const allAttributeNames = { [PromptsType.instructions]: [PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], [PromptsType.agent]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target] }; +const githubCopilotAgentAttributeNames = [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.tools, PromptHeaderAttributes.target, GithubPromptHeaderAttributes.mcpServers]; const recommendedAttributeNames = { [PromptsType.prompt]: allAttributeNames[PromptsType.prompt].filter(name => !isNonRecommendedAttribute(name)), [PromptsType.instructions]: allAttributeNames[PromptsType.instructions].filter(name => !isNonRecommendedAttribute(name)), [PromptsType.agent]: allAttributeNames[PromptsType.agent].filter(name => !isNonRecommendedAttribute(name)) }; -export function getValidAttributeNames(promptType: PromptsType, includeNonRecommended: boolean): string[] { +export function getValidAttributeNames(promptType: PromptsType, includeNonRecommended: boolean, isGitHubTarget: boolean): string[] { + if (isGitHubTarget && promptType === PromptsType.agent) { + return githubCopilotAgentAttributeNames; + } return includeNonRecommended ? allAttributeNames[promptType] : recommendedAttributeNames[promptType]; } @@ -433,6 +457,17 @@ export function isNonRecommendedAttribute(attributeName: string): boolean { return attributeName === PromptHeaderAttributes.advancedOptions || attributeName === PromptHeaderAttributes.excludeAgent || attributeName === PromptHeaderAttributes.mode; } +// The list of tools known to be used by GitHub Copilot custom agents +export const knownGithubCopilotTools: Record = { + 'shell': localize('githubCopilotTools.shell', 'Execute shell commands'), + 'edit': localize('githubCopilotTools.edit', 'Edit files'), + 'search': localize('githubCopilotTools.search', 'Search in files'), + 'custom-agent': localize('githubCopilotTools.customAgent', 'Call custom agents') +}; +export function isGithubTarget(promptType: PromptsType, target: string | undefined): boolean { + return promptType === PromptsType.agent && target === 'github-copilot'; +} + function toMarker(message: string, range: Range, severity = MarkerSeverity.Error): IMarkerData { return { severity, message, ...range }; } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index 3627109dee0..a85823c2c83 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -62,6 +62,7 @@ interface ParsedHeader { } export namespace PromptHeaderAttributes { + export const name = 'name'; export const description = 'description'; export const agent = 'agent'; export const mode = 'mode'; @@ -75,6 +76,10 @@ export namespace PromptHeaderAttributes { export const target = 'target'; } +export namespace GithubPromptHeaderAttributes { + export const mcpServers = 'mcp-servers'; +} + export class PromptHeader { private _parsed: ParsedHeader | undefined; diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts new file mode 100644 index 00000000000..92ac905d346 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts @@ -0,0 +1,326 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { Position } from '../../../../../../editor/common/core/position.js'; +import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; +import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { LanguageModelToolsService } from '../../../browser/languageModelToolsService.js'; +import { ChatMode, CustomChatMode, IChatModeService } from '../../../common/chatModes.js'; +import { IChatService } from '../../../common/chatService.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js'; +import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../../common/languageModels.js'; +import { PromptHoverProvider } from '../../../common/promptSyntax/languageProviders/promptHovers.js'; +import { IPromptsService, PromptsStorage } from '../../../common/promptSyntax/service/promptsService.js'; +import { MockChatModeService } from '../../common/mockChatModeService.js'; +import { MockChatService } from '../../common/mockChatService.js'; +import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { PromptFileParser } from '../../../common/promptSyntax/promptFileParser.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { MarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { getLanguageIdForPromptsType, PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; + +suite('PromptHoverProvider', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instaService: TestInstantiationService; + let hoverProvider: PromptHoverProvider; + + setup(async () => { + const testConfigService = new TestConfigurationService(); + testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true); + instaService = workbenchInstantiationService({ + contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)), + configurationService: () => testConfigService + }, disposables); + + const chatService = new MockChatService(); + instaService.stub(IChatService, chatService); + + const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); + + const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + + disposables.add(toolService.registerToolData(testTool1)); + disposables.add(toolService.registerToolData(testTool2)); + + instaService.set(ILanguageModelToolsService, toolService); + + const testModels: ILanguageModelChatMetadata[] = [ + { id: 'mae-4', name: 'MAE 4', vendor: 'olama', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true } } satisfies ILanguageModelChatMetadata, + { id: 'mae-4.1', name: 'MAE 4.1', vendor: 'copilot', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true } } satisfies ILanguageModelChatMetadata, + ]; + + instaService.stub(ILanguageModelsService, { + getLanguageModelIds() { return testModels.map(m => m.id); }, + lookupLanguageModel(name: string) { + return testModels.find(m => m.id === name); + } + }); + + const customChatMode = new CustomChatMode({ + uri: URI.parse('myFs://test/test/chatmode.md'), + name: 'BeastMode', + agentInstructions: { content: 'Beast mode instructions', toolReferences: [] }, + source: { storage: PromptsStorage.local } + }); + instaService.stub(IChatModeService, new MockChatModeService({ builtin: [ChatMode.Agent, ChatMode.Ask, ChatMode.Edit], custom: [customChatMode] })); + + const parser = new PromptFileParser(); + instaService.stub(IPromptsService, { + getParsedPromptFile(model: ITextModel) { + return parser.parse(model.uri, model.getValue()); + } + }); + + hoverProvider = instaService.createInstance(PromptHoverProvider); + }); + + async function getHover(content: string, line: number, column: number, promptType: PromptsType): Promise { + const languageId = getLanguageIdForPromptsType(promptType); + const model = disposables.add(createTextModel(content, languageId, undefined, URI.parse('test://test' + getPromptFileExtension(promptType)))); + const position = new Position(line, column); + const hover = await hoverProvider.provideHover(model, position, CancellationToken.None); + if (!hover || hover.contents.length === 0) { + return undefined; + } + // Return the markdown value from the first content + const firstContent = hover.contents[0]; + if (firstContent instanceof MarkdownString) { + return firstContent.value; + } + return undefined; + } + + suite('agent hovers', () => { + test('hover on target attribute shows description', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: vscode', + '---', + ].join('\n'); + const hover = await getHover(content, 3, 1, PromptsType.agent); + assert.strictEqual(hover, 'The target to which the header attributes like tools apply to. Possible values are `github-copilot` and `vscode`.'); + }); + + test('hover on model attribute with github-copilot target shows note', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: github-copilot', + 'model: MAE 4', + '---', + ].join('\n'); + const hover = await getHover(content, 4, 1, PromptsType.agent); + const expected = [ + 'Specify the model that runs this custom agent.', + '', + 'Note: This attribute is not used when target is github-copilot.' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + + test('hover on model attribute with vscode target shows model info', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: vscode', + 'model: MAE 4 (olama)', + '---', + ].join('\n'); + const hover = await getHover(content, 4, 1, PromptsType.agent); + const expected = [ + 'Specify the model that runs this custom agent.', + '', + '- Name: MAE 4', + '- Family: mae', + '- Vendor: olama' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + + test('hover on handoffs attribute with github-copilot target shows note', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: github-copilot', + 'handoffs:', + ' - label: Test', + ' agent: Default', + ' prompt: Test', + '---', + ].join('\n'); + const hover = await getHover(content, 4, 1, PromptsType.agent); + const expected = [ + 'Possible handoff actions when the agent has completed its task.', + '', + 'Note: This attribute is not used when target is github-copilot.' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + + test('hover on handoffs attribute with vscode target shows description', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: vscode', + 'handoffs:', + ' - label: Test', + ' agent: Default', + ' prompt: Test', + '---', + ].join('\n'); + const hover = await getHover(content, 4, 1, PromptsType.agent); + assert.strictEqual(hover, 'Possible handoff actions when the agent has completed its task.'); + }); + + test('hover on github-copilot tool shows simple description', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: github-copilot', + `tools: ['shell', 'edit', 'search']`, + '---', + ].join('\n'); + // Hover on 'shell' tool + const hoverShell = await getHover(content, 4, 10, PromptsType.agent); + assert.strictEqual(hoverShell, 'Execute shell commands'); + + // Hover on 'edit' tool + const hoverEdit = await getHover(content, 4, 20, PromptsType.agent); + assert.strictEqual(hoverEdit, 'Edit files'); + + // Hover on 'search' tool + const hoverSearch = await getHover(content, 4, 28, PromptsType.agent); + assert.strictEqual(hoverSearch, 'Search in files'); + }); + + test('hover on vscode tool shows detailed description', async () => { + const content = [ + '---', + 'description: "Test"', + 'target: vscode', + `tools: ['tool1', 'tool2']`, + '---', + ].join('\n'); + // Hover on 'tool1' + const hover = await getHover(content, 4, 10, PromptsType.agent); + assert.strictEqual(hover, 'Test Tool 1'); + }); + + test('hover on description attribute', async () => { + const content = [ + '---', + 'description: "Test agent"', + 'target: vscode', + '---', + ].join('\n'); + const hover = await getHover(content, 2, 1, PromptsType.agent); + assert.strictEqual(hover, 'The description of the custom agent, what it does and when to use it.'); + }); + + test('hover on argument-hint attribute', async () => { + const content = [ + '---', + 'description: "Test"', + 'argument-hint: "test hint"', + '---', + ].join('\n'); + const hover = await getHover(content, 3, 1, PromptsType.agent); + assert.strictEqual(hover, 'The argument-hint describes what inputs the custom agent expects or supports.'); + }); + }); + + suite('prompt hovers', () => { + test('hover on model attribute shows model info', async () => { + const content = [ + '---', + 'description: "Test"', + 'model: MAE 4 (olama)', + '---', + ].join('\n'); + const hover = await getHover(content, 3, 1, PromptsType.prompt); + const expected = [ + 'The model to use in this prompt.', + '', + '- Name: MAE 4', + '- Family: mae', + '- Vendor: olama' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + + test('hover on tools attribute shows tool description', async () => { + const content = [ + '---', + 'description: "Test"', + `tools: ['tool1']`, + '---', + ].join('\n'); + const hover = await getHover(content, 3, 10, PromptsType.prompt); + assert.strictEqual(hover, 'Test Tool 1'); + }); + + test('hover on agent attribute shows agent info', async () => { + const content = [ + '---', + 'description: "Test"', + 'agent: BeastMode', + '---', + ].join('\n'); + const hover = await getHover(content, 3, 1, PromptsType.prompt); + const expected = [ + 'The agent to use when running this prompt.', + '', + '**Built-in agents:**', + '- `agent`: Describe what to build next', + '- `ask`: Explore and understand your code', + '- `edit`: Edit or refactor selected code', + '', + '**Custom agents:**', + '- `BeastMode`: Custom agent' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + }); + + suite('instructions hovers', () => { + test('hover on description attribute', async () => { + const content = [ + '---', + 'description: "Test instruction"', + 'applyTo: "**/*.ts"', + '---', + ].join('\n'); + const hover = await getHover(content, 2, 1, PromptsType.instructions); + assert.strictEqual(hover, 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'); + }); + + test('hover on applyTo attribute', async () => { + const content = [ + '---', + 'description: "Test"', + 'applyTo: "**/*.ts"', + '---', + ].join('\n'); + const hover = await getHover(content, 3, 1, PromptsType.instructions); + const expected = [ + 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.', + 'Example: `**/*.ts`, `**/*.js`, `client/**`' + ].join('\n'); + assert.strictEqual(hover, expected); + }); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index 241fe702ba7..b58376ac44e 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -188,9 +188,12 @@ suite('PromptValidator', () => { '---', ].join('\n'); const markers = await validate(content, PromptsType.agent); - assert.strictEqual(markers.length, 1); - assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); - assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in custom agent files.`)); + assert.deepStrictEqual( + markers.map(m => ({ severity: m.severity, message: m.message })), + [ + { severity: MarkerSeverity.Warning, message: `Attribute 'applyTo' is not supported in VS Code agent files. Supported: argument-hint, description, handoffs, model, target, tools.` }, + ] + ); }); test('tools with invalid handoffs', async () => { @@ -251,6 +254,117 @@ suite('PromptValidator', () => { const markers = await validate(content, PromptsType.agent); assert.deepStrictEqual(markers, [], 'Expected no validation issues for handoffs attribute'); }); + + test('github-copilot agent with supported attributes', async () => { + const content = [ + '---', + 'name: "GitHub Copilot Custom Agent"', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + `tools: ['shell', 'edit', 'search', 'custom-agent']`, + 'mcp-servers: []', + '---', + 'Body with #search and #edit references', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'Expected no validation issues for github-copilot target'); + }); + + test('github-copilot agent warns about model and handoffs attributes', async () => { + const content = [ + '---', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + 'model: MAE 4.1', + `tools: ['shell', 'edit']`, + `handoffs:`, + ' - label: Test', + ' agent: Default', + ' prompt: Test', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + const messages = markers.map(m => m.message); + assert.deepStrictEqual(messages, [ + 'Attribute \'model\' is not supported in custom GitHub Copilot agent files. Supported: description, mcp-servers, name, target, tools.', + 'Attribute \'handoffs\' is not supported in custom GitHub Copilot agent files. Supported: description, mcp-servers, name, target, tools.', + ], 'Model and handoffs are not validated for github-copilot target'); + }); + + test('github-copilot agent does not validate variable references', async () => { + const content = [ + '---', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + `tools: ['shell', 'edit']`, + '---', + 'Body with #unknownTool reference', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + // Variable references should not be validated for github-copilot target + assert.deepStrictEqual(markers, [], 'Variable references are not validated for github-copilot target'); + }); + + test('github-copilot agent rejects unsupported attributes', async () => { + const content = [ + '---', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + 'argument-hint: "test hint"', + `tools: ['shell']`, + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.ok(markers[0].message.includes(`Attribute 'argument-hint' is not supported`), 'Expected warning about unsupported attribute'); + }); + + test('vscode target agent validates normally', async () => { + const content = [ + '---', + 'description: "VS Code agent"', + 'target: vscode', + 'model: MAE 4.1', + `tools: ['tool1', 'tool2']`, + '---', + 'Body with #tool1', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'VS Code target should validate normally'); + }); + + test('vscode target agent warns about unknown tools', async () => { + const content = [ + '---', + 'description: "VS Code agent"', + 'target: vscode', + `tools: ['tool1', 'unknownTool']`, + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); + assert.strictEqual(markers[0].message, `Unknown tool 'unknownTool'.`); + }); + + test('default target (no target specified) validates as vscode', async () => { + const content = [ + '---', + 'description: "Agent without target"', + 'model: MAE 4.1', + `tools: ['tool1']`, + 'argument-hint: "test hint"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + // Should validate normally as if target was vscode + assert.deepStrictEqual(markers, [], 'Agent without target should validate as vscode'); + }); }); suite('instructions', () => { From 92c689909c938fed5641b7bb3c53cf5284e188a4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:54:05 +0000 Subject: [PATCH 1617/4355] Add when clause support to languageModelChatProviders contribution (#272299) * Initial plan * Add when clause support to languageModelChatProviders contribution Co-authored-by: lramos15 <4544166+lramos15@users.noreply.github.com> * Use contextMatchesRules instead of evaluate for when clause evaluation Co-authored-by: lramos15 <4544166+lramos15@users.noreply.github.com> * Fix test failure by using separate test suite with proper lifecycle Co-authored-by: lramos15 <4544166+lramos15@users.noreply.github.com> * Fix when clause evaluation in tests by implementing contextMatchesRules Co-authored-by: lramos15 <4544166+lramos15@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lramos15 <4544166+lramos15@users.noreply.github.com> Co-authored-by: Logan Ramos --- .../contrib/chat/common/languageModels.ts | 17 +++- .../chat/test/common/languageModels.test.ts | 91 +++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 6ebea2be968..2f35f9b8b4b 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -15,7 +15,7 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -279,6 +279,10 @@ const languageModelChatProviderType: IJSONSchema = { managementCommand: { type: 'string', description: localize('vscode.extension.contributes.languageModels.managementCommand', "A command to manage the language model chat provider, e.g. 'Manage Copilot models'. This is used in the chat model picker. If not provided, a gear icon is not rendered during vendor selection.") + }, + when: { + type: 'string', + description: localize('vscode.extension.contributes.languageModels.when', "Condition which must be true to show this language model chat provider in the Manage Models list.") } } }; @@ -287,6 +291,7 @@ export interface IUserFriendlyLanguageModel { vendor: string; displayName: string; managementCommand?: string; + when?: string; } export const languageModelChatProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ @@ -320,6 +325,7 @@ export class LanguageModelsService implements ILanguageModelsService { private readonly _resolveLMSequencer = new SequencerByKey(); private _modelPickerUserPreferences: Record = {}; private readonly _hasUserSelectableModels: IContextKey; + private readonly _contextKeyService: IContextKeyService; private readonly _onLanguageModelChange = this._store.add(new Emitter()); readonly onDidChangeLanguageModels: Event = this._onLanguageModelChange.event; @@ -332,6 +338,7 @@ export class LanguageModelsService implements ILanguageModelsService { @IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService, ) { this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(_contextKeyService); + this._contextKeyService = _contextKeyService; this._modelPickerUserPreferences = this._storageService.getObject>('chatModelPickerPreferences', StorageScope.PROFILE, this._modelPickerUserPreferences); // TODO @lramos15 - Remove after a few releases, as this is just cleaning a bad storage state const entitlementChangeHandler = () => { @@ -411,7 +418,13 @@ export class LanguageModelsService implements ILanguageModelsService { } getVendors(): IUserFriendlyLanguageModel[] { - return Array.from(this._vendors.values()); + return Array.from(this._vendors.values()).filter(vendor => { + if (!vendor.when) { + return true; // No when clause means always visible + } + const whenClause = ContextKeyExpr.deserialize(vendor.when); + return whenClause ? this._contextKeyService.contextMatchesRules(whenClause) : false; + }); } getLanguageModelIds(): string[] { diff --git a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts index 2c7590b1569..ac50ccf476a 100644 --- a/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts @@ -19,6 +19,7 @@ import { TestChatEntitlementService, TestStorageService } from '../../../../test import { Event } from '../../../../../base/common/event.js'; import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ContextKeyExpression } from '../../../../../platform/contextkey/common/contextkey.js'; suite('LanguageModels', function () { @@ -198,4 +199,94 @@ suite('LanguageModels', function () { await request.result; }); + + test('when clause defaults to true when omitted', async function () { + const vendors = languageModels.getVendors(); + // Both test-vendor and actual-vendor have no when clause, so they should be visible + assert.ok(vendors.length >= 2); + assert.ok(vendors.some(v => v.vendor === 'test-vendor')); + assert.ok(vendors.some(v => v.vendor === 'actual-vendor')); + }); +}); + +suite('LanguageModels - When Clause', function () { + + class TestContextKeyService extends MockContextKeyService { + override contextMatchesRules(rules: ContextKeyExpression): boolean { + if (!rules) { + return true; + } + // Simple evaluation based on stored keys + const keys = rules.keys(); + for (const key of keys) { + const contextKey = this.getContextKeyValue(key); + // If the key exists and is truthy, the rule matches + if (contextKey) { + return true; + } + } + return false; + } + } + + let languageModelsWithWhen: LanguageModelsService; + let contextKeyService: TestContextKeyService; + + setup(function () { + contextKeyService = new TestContextKeyService(); + contextKeyService.createKey('testKey', true); + + languageModelsWithWhen = new LanguageModelsService( + new class extends mock() { + override activateByEvent(name: string) { + return Promise.resolve(); + } + }, + new NullLogService(), + new TestStorageService(), + contextKeyService, + new TestConfigurationService(), + new TestChatEntitlementService() + ); + + const ext = ExtensionsRegistry.getExtensionPoints().find(e => e.name === languageModelChatProviderExtensionPoint.name)!; + + ext.acceptUsers([{ + description: { ...nullExtensionDescription }, + value: { vendor: 'visible-vendor', displayName: 'Visible Vendor' }, + collector: null! + }, { + description: { ...nullExtensionDescription }, + value: { vendor: 'conditional-vendor', displayName: 'Conditional Vendor', when: 'testKey' }, + collector: null! + }, { + description: { ...nullExtensionDescription }, + value: { vendor: 'hidden-vendor', displayName: 'Hidden Vendor', when: 'falseKey' }, + collector: null! + }]); + }); + + teardown(function () { + languageModelsWithWhen.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('when clause filters vendors correctly', async function () { + const vendors = languageModelsWithWhen.getVendors(); + assert.strictEqual(vendors.length, 2); + assert.ok(vendors.some(v => v.vendor === 'visible-vendor')); + assert.ok(vendors.some(v => v.vendor === 'conditional-vendor')); + assert.ok(!vendors.some(v => v.vendor === 'hidden-vendor')); + }); + + test('when clause evaluates to true when context key is true', async function () { + const vendors = languageModelsWithWhen.getVendors(); + assert.ok(vendors.some(v => v.vendor === 'conditional-vendor'), 'conditional-vendor should be visible when testKey is true'); + }); + + test('when clause evaluates to false when context key is false', async function () { + const vendors = languageModelsWithWhen.getVendors(); + assert.ok(!vendors.some(v => v.vendor === 'hidden-vendor'), 'hidden-vendor should be hidden when falseKey is false'); + }); }); From 90460f61a70eb51ce9fc215c926a0b342431be47 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:56:50 -0700 Subject: [PATCH 1618/4355] Have playwright MCP always trace (#273145) Have MCP always trace so that traces can be looked at easily. --- test/mcp/src/application.ts | 6 +++--- test/mcp/src/automationTools/core.ts | 1 + test/mcp/src/playwright.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index 7754eab111c..a60c7b9764d 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -254,13 +254,13 @@ export async function getApplication({ recordVideo }: { recordVideo?: boolean } codePath: opts.build, workspacePath: rootPath, logger, - logsPath: path.join(logsRootPath, 'suite_unknown'), - crashesPath: path.join(crashesRootPath, 'suite_unknown'), + logsPath: logsRootPath, + crashesPath: crashesRootPath, videosPath: (recordVideo || opts.video) ? videoRootPath : undefined, verbose: opts.verbose, remote: opts.remote, web: opts.web, - tracing: opts.tracing, + tracing: true, headless: opts.headless, browser: opts.browser, extraArgs: (opts.electronArgs || '').split(' ').map(arg => arg.trim()).filter(arg => !!arg), diff --git a/test/mcp/src/automationTools/core.ts b/test/mcp/src/automationTools/core.ts index 3f9e34cf1e6..0ef9f5bb800 100644 --- a/test/mcp/src/automationTools/core.ts +++ b/test/mcp/src/automationTools/core.ts @@ -37,6 +37,7 @@ export function applyCoreTools(server: McpServer, appService: ApplicationService 'Stop the VS Code application', async () => { const app = await appService.getOrCreateApplication(); + await app.stopTracing('mcp', true); await app.stop(); return { content: [{ diff --git a/test/mcp/src/playwright.ts b/test/mcp/src/playwright.ts index 5a1a229e3e9..6549e13095f 100644 --- a/test/mcp/src/playwright.ts +++ b/test/mcp/src/playwright.ts @@ -12,7 +12,8 @@ export async function getServer(app?: Application): Promise { const application = app ?? await getApplication(); const connection = await createConnection( { - capabilities: ['core', 'pdf', 'vision'] + capabilities: ['core', 'pdf', 'vision'], + saveTrace: true }, // eslint-disable-next-line local/code-no-any-casts () => Promise.resolve(application.code.driver.browserContext as any) From 062275278c4c9d1b754aea67edab9640542bc13e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:18:16 -0700 Subject: [PATCH 1619/4355] Fix setting chatSessionType context key in multi-diff cases --- .../chatMultiDiffContentPart.ts | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index b8ec66f0193..a270d1c44fd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -4,33 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; +import { ActionBar, ActionsOrientation } from '../../../../../base/browser/ui/actionbar/actionbar.js'; import { ButtonWithIcon } from '../../../../../base/browser/ui/button/button.js'; +import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; +import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { FileKind } from '../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IChatContentPart } from './chatContentParts.js'; -import { IChatMultiDiffData } from '../../common/chatService.js'; -import { ChatTreeItem } from '../chat.js'; -import { IResourceLabel, ResourceLabels } from '../../../../browser/labels.js'; import { WorkbenchList } from '../../../../../platform/list/browser/listService.js'; -import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; -import { FileKind } from '../../../../../platform/files/common/files.js'; -import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; -import { IEditSessionEntryDiff } from '../../common/chatEditingService.js'; +import { IResourceLabel, ResourceLabels } from '../../../../browser/labels.js'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../services/editor/common/editorService.js'; +import { createFileIconThemableTreeContainerScope } from '../../../files/browser/views/explorerView.js'; import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; -import { Codicon } from '../../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../../base/common/themables.js'; -import { IChatRendererContent } from '../../common/chatViewModel.js'; -import { Emitter, Event } from '../../../../../base/common/event.js'; -import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; -import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { ActionBar, ActionsOrientation } from '../../../../../base/browser/ui/actionbar/actionbar.js'; -import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IEditSessionEntryDiff } from '../../common/chatEditingService.js'; +import { IChatMultiDiffData } from '../../common/chatService.js'; +import { IChatRendererContent } from '../../common/chatViewModel.js'; +import { ChatTreeItem } from '../chat.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { getChatSessionType } from '../chatSessions/common.js'; +import { IChatContentPart } from './chatContentParts.js'; const $ = dom.$; @@ -135,18 +137,15 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent const setupActionBar = () => { actionBar.clear(); - const activeEditorUri = this.editorService.activeEditor?.resource; let marshalledUri: any | undefined = undefined; let contextKeyService: IContextKeyService = this.contextKeyService; - if (activeEditorUri) { - const { authority } = activeEditorUri; - const overlay: [string, unknown][] = []; - if (authority) { - overlay.push([ChatContextKeys.sessionType.key, authority]); - } - contextKeyService = this.contextKeyService.createOverlay(overlay); + if (this.editorService.activeEditor instanceof ChatEditorInput) { + contextKeyService = this.contextKeyService.createOverlay([ + [ChatContextKeys.sessionType.key, getChatSessionType(this.editorService.activeEditor)] + ]); + marshalledUri = { - ...activeEditorUri, + ...this.editorService.activeEditor.resource, $mid: MarshalledId.Uri }; } From cc6bd675bbeceb24c1d13ac796c2e3823647ece5 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 24 Oct 2025 10:22:54 -0700 Subject: [PATCH 1620/4355] Update agents view default --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7f9f4d1b130..000a983f7ef 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -480,7 +480,7 @@ configurationRegistry.registerConfiguration({ type: 'string', enum: ['disabled', 'view', 'single-view'], description: nls.localize('chat.sessionsViewLocation.description', "Controls where to show the agent sessions menu."), - default: 'disabled', + default: 'view', tags: ['experimental'], experiment: { mode: 'auto' From c16ea0524224667153d89cd96f18f09b56660917 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:46:48 -0700 Subject: [PATCH 1621/4355] Update distro for env extension (#273148) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 65cda6b97a6..880f7560ba1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "e3691f9d32e65359c32b6b83a24dd5a927e3765a", + "distro": "b84862654999878b2382237454c745614992e9d7", "author": { "name": "Microsoft Corporation" }, From 137e8e22efa4f3703acff5156d07ae27dafe7dfa Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 24 Oct 2025 14:24:29 -0400 Subject: [PATCH 1622/4355] for conf prompt focus terminal action, reveal the terminal (#272289) fixes #272186 --- .../chatTerminalToolProgressPart.ts | 4 +--- .../browser/tools/monitoring/outputMonitor.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 1871b84f9bf..ea9aff59ef0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -185,9 +185,8 @@ export class FocusChatInstanceAction extends Action implements IAction { constructor( private readonly _instance: ITerminalInstance, isTerminalHidden: boolean, - @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, - @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService ) { super( 'chat.focusTerminalInstance', @@ -199,7 +198,6 @@ export class FocusChatInstanceAction extends Action implements IAction { public override async run() { this.label = localize('focusTerminal', 'Focus Terminal'); - this._terminalService.setActiveInstance(this._instance); if (this._instance.target === TerminalLocation.Editor) { this._terminalEditorService.openEditor(this._instance); } else { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 9fc673c68ef..78e3c2100fd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -28,6 +28,7 @@ import { getTextResponseFromStream } from './utils.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; import { ILogService } from '../../../../../../../platform/log/common/log.js'; +import { ITerminalService } from '../../../../../terminal/browser/terminal.js'; export interface IOutputMonitor extends Disposable { readonly pollingResult: IPollingResult & { pollDurationMs: number } | undefined; @@ -87,6 +88,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILogService private readonly _logService: ILogService, + @ITerminalService private readonly _terminalService: ITerminalService, ) { super(); @@ -526,7 +528,10 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { '', localize('poll.terminal.enterInput', 'Focus terminal'), undefined, - async () => { execution.instance.focus(true); return true; }, + async () => { + this._showInstance(execution.instance.instanceId); + return true; + } ); const inputPromise = new Promise(resolve => { @@ -567,6 +572,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return option; }, async () => { + this._showInstance(execution.instance.instanceId); this._state = OutputMonitorState.Cancelled; this._outputMonitorTelemetryCounters.inputToolManualRejectCount++; inputDataDisposable.dispose(); @@ -590,6 +596,17 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return optionToRun; } + private _showInstance(instanceId?: number): void { + if (!instanceId) { + return; + } + const instance = this._terminalService.getInstanceFromId(instanceId); + if (!instance) { + return; + } + this._terminalService.setActiveInstance(instance); + this._terminalService.revealActiveTerminal(true); + } // Helper to create, register, and wire a ChatElicitationRequestPart. Returns the promise that // resolves when the part is accepted/rejected and the registered part itself so callers can // attach additional listeners (e.g., onDidRequestHide) or compose with other promises. From 8579d1208556885deaebc3fc92c83f98b68216ce Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 11:31:57 -0700 Subject: [PATCH 1623/4355] wip --- .../chatEditingCheckpointTimelineImpl.ts | 162 ++++++++---------- .../chatEditingModifiedDocumentEntry.ts | 18 +- .../chatEditingModifiedNotebookEntry.ts | 5 + .../browser/chatEditing/chatEditingSession.ts | 69 +++++--- 4 files changed, 120 insertions(+), 134 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index 7ade860b5d4..77fdb20f6fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equals as arraysEqual } from '../../../../../base/common/arrays.js'; -import { findLast } from '../../../../../base/common/arraysFind.js'; +import { findLast, findLastIdx } from '../../../../../base/common/arraysFind.js'; import { assertNever } from '../../../../../base/common/assert.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; @@ -29,7 +29,7 @@ import { INotebookService } from '../../../notebook/common/notebookService.js'; import { IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js'; import { IChatRequestDisablement } from '../../common/chatModel.js'; import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js'; -import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IFileCreateOperation, IFileDeleteOperation, IFileRenameOperation, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js'; +import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IFileCreateOperation, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js'; import { ChatEditingSnapshotTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; import { createSnapshot as createNotebookSnapshot, restoreSnapshot as restoreNotebookSnapshot } from './notebook/chatEditingModifiedNotebookSnapshot.js'; @@ -38,6 +38,18 @@ const STOP_ID_EPOCH_PREFIX = '__epoch_'; type IReconstructedFileStateWithNotebook = IReconstructedFileNotExistsState | (Mutable & { notebook?: INotebookTextModel }); +/** + * A filesystem delegate used by the checkpointing timeline such that + * navigating in the timeline tracks the changes as agent-initiated. + */ +export interface IChatEditingTimelineFsDelegate { + /** Delete a URI */ + deleteFile: (uri: URI) => Promise; + /** Rename a URI, retaining contents */ + renameFile: (fromUri: URI, toUri: URI) => Promise; + /** Set a URI contents, should create it if it does not already exist */ + setContents(uri: URI, content: string, telemetryInfo: IModifiedEntryTelemetryInfo): Promise; +} /** * Implementation of the checkpoint-based timeline system. @@ -60,8 +72,24 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh private readonly _willUndoToCheckpoint = derived(reader => { const currentEpoch = this._currentEpoch.read(reader); const operations = this._operations.read(reader); - const maxEpoch = operations.findLast(op => op.epoch <= currentEpoch)?.epoch || 0; - return this._checkpoints.read(reader).findLast(cp => cp.epoch < maxEpoch); + const checkpoints = this._checkpoints.read(reader); + + const previousOperationEpoch = operations.findLast(op => op.epoch <= currentEpoch)?.epoch || 0; + const previousCheckpointIdx = findLastIdx(checkpoints, cp => cp.epoch < previousOperationEpoch); + if (previousCheckpointIdx === -1) { + return undefined; + } + + // If we're backing up to a checkpoint and there are no other operations between + // that checkpoint at the checkpoint marking the start of the request, undo the + // entire request. + const previousCheckpoint = checkpoints[previousCheckpointIdx]; + const startOfRequest = findLast(checkpoints, cp => cp.undoStopId === undefined, previousCheckpointIdx); + if (startOfRequest && !operations.some(op => op.epoch > startOfRequest.epoch && op.epoch < previousCheckpoint.epoch)) { + return startOfRequest; + } + + return previousCheckpoint; }); public readonly canUndo: IObservable = this._willUndoToCheckpoint.map(cp => !!cp); @@ -111,14 +139,12 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } } - return [...disablement].map(([requestId, undoStopId]) => ({ requestId, undoStopId })); + return [...disablement].map(([requestId, afterUndoStop]): IChatRequestDisablement => ({ requestId, afterUndoStop })); }); constructor( private readonly chatSessionId: string, - private readonly _delegate: { - setContents(uri: URI, content: string, telemetryInfo: IModifiedEntryTelemetryInfo): Promise; - }, + private readonly _delegate: IChatEditingTimelineFsDelegate, @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, @INotebookService private readonly _notebookService: INotebookService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -189,10 +215,10 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return; // Already at target epoch } - await this._applyFileSystemOperations(currentEpoch, targetEpoch); + const urisToRestore = await this._applyFileSystemOperations(currentEpoch, targetEpoch); // Reconstruct content for files affected by operations in the range - await this._reconstructAllFileContents(targetEpoch); + await this._reconstructAllFileContents(targetEpoch, urisToRestore); // Update current epoch this._currentEpoch.set(targetEpoch, undefined); @@ -323,63 +349,15 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return checkpoints.find(c => c.requestId === requestId && c.undoStopId === undoStopId)?.checkpointId; } - private async _reconstructAllFileContents(targetEpoch: number): Promise { - const currentEpoch = this._currentEpoch.get(); - const isMovingForward = targetEpoch > currentEpoch; - - // Get operations between current and target epochs - const relevantOperations = this._operations.get().filter(op => { - return op.epoch > Math.min(currentEpoch, targetEpoch) && - op.epoch <= Math.max(currentEpoch, targetEpoch); - }).sort((a, b) => isMovingForward ? a.epoch - b.epoch : b.epoch - a.epoch); - - const filesToReconstruct = new ResourceSet(); - for (const operation of relevantOperations) { - switch (operation.type) { - case FileOperationType.Create: - if (isMovingForward) { - filesToReconstruct.add(operation.uri); - } else { - filesToReconstruct.delete(operation.uri); - } - break; - - case FileOperationType.Delete: - if (isMovingForward) { - filesToReconstruct.delete(operation.uri); - } else { - filesToReconstruct.add(operation.uri); - } - break; - - case FileOperationType.Rename: { - const renameOp = operation as IFileRenameOperation; - if (isMovingForward) { - filesToReconstruct.delete(renameOp.oldUri); - filesToReconstruct.add(renameOp.newUri); - } else { - filesToReconstruct.delete(renameOp.newUri); - filesToReconstruct.add(renameOp.oldUri); - } - break; - } - case FileOperationType.TextEdit: - case FileOperationType.NotebookEdit: - filesToReconstruct.add(operation.uri); - break; - } - } - - // Reconstruct content for each file that needs it - for (const uri of filesToReconstruct) { + private async _reconstructAllFileContents(targetEpoch: number, filesToReconstruct: ResourceSet): Promise { + await Promise.all(Array.from(filesToReconstruct).map(async uri => { const reconstructedState = await this._reconstructFileState(uri, targetEpoch); if (reconstructedState.exists) { - this._delegate.setContents(reconstructedState.uri, reconstructedState.content, reconstructedState.telemetryInfo); + await this._delegate.setContents(reconstructedState.uri, reconstructedState.content, reconstructedState.telemetryInfo); } - } + })); } - private _getBaselineKey(uri: URI, requestId: string): string { return `${uri.toString()}::${requestId}`; } @@ -424,6 +402,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh content: replayed.exists ? replayed.content : '', requestId: operation.requestId, telemetryInfo: prev.telemetryInfo, + notebookViewType: replayed.exists ? replayed.notebookViewType : undefined, }; } @@ -516,7 +495,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }; case FileOperationType.TextEdit: - if (!state.exists || !state.content) { + if (!state.exists) { throw new Error('Cannot apply text edits to non-existent file'); } @@ -527,7 +506,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }; case FileOperationType.NotebookEdit: - if (!state.exists || !state.content) { + if (!state.exists) { throw new Error('Cannot apply notebook edits to non-existent file'); } if (!state.notebook) { @@ -542,7 +521,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } } - private async _applyFileSystemOperations(fromEpoch: number, toEpoch: number): Promise { + private async _applyFileSystemOperations(fromEpoch: number, toEpoch: number): Promise { const isMovingForward = toEpoch > fromEpoch; const operations = this._operations.get().filter(op => { if (isMovingForward) { @@ -553,48 +532,56 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }).sort((a, b) => isMovingForward ? a.epoch - b.epoch : b.epoch - a.epoch); // Apply file system operations in the correct direction + const urisToRestore = new ResourceSet(); for (const operation of operations) { - await this._applyFileSystemOperation(operation, isMovingForward); + await this._applyFileSystemOperation(operation, isMovingForward, urisToRestore); } + + return urisToRestore; } - private async _applyFileSystemOperation(operation: FileOperation, isMovingForward: boolean): Promise { + private async _applyFileSystemOperation(operation: FileOperation, isMovingForward: boolean, urisToRestore: ResourceSet): Promise { switch (operation.type) { case FileOperationType.Create: if (isMovingForward) { - // Moving forward: create the file await this._createFile(operation.uri, (operation as IFileCreateOperation).initialContent); + urisToRestore.add(operation.uri); } else { - // Moving backward: delete the file - await this._deleteFile(operation.uri); + await this._delegate.deleteFile(operation.uri); + urisToRestore.delete(operation.uri); } break; case FileOperationType.Delete: if (isMovingForward) { - // Moving forward: delete the file - await this._deleteFile(operation.uri); + await this._delegate.deleteFile(operation.uri); + urisToRestore.delete(operation.uri); } else { - // Moving backward: recreate the file with its final content - const deleteOp = operation as IFileDeleteOperation; - await this._createFile(operation.uri, deleteOp.finalContent); + await this._createFile(operation.uri, operation.finalContent); + urisToRestore.add(operation.uri); } break; case FileOperationType.Rename: if (isMovingForward) { - // Moving forward: rename from old to new - await this._renameFile(operation.oldUri, operation.newUri); + await this._delegate.renameFile(operation.oldUri, operation.newUri); + urisToRestore.delete(operation.oldUri); + urisToRestore.add(operation.newUri); } else { - // Moving backward: rename from new to old - await this._renameFile(operation.newUri, operation.oldUri); + await this._delegate.renameFile(operation.newUri, operation.oldUri); + urisToRestore.delete(operation.newUri); + urisToRestore.add(operation.oldUri); } break; // Text and notebook edits don't affect file system structure case FileOperationType.TextEdit: case FileOperationType.NotebookEdit: + urisToRestore.add(operation.uri); break; + + default: + assertNever(operation); } } @@ -611,14 +598,6 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }); } - private async _deleteFile(uri: URI): Promise { - await this._bulkEditService.apply({ edits: [{ oldResource: uri }] }); - } - - private async _renameFile(fromUri: URI, toUri: URI): Promise { - await this._bulkEditService.apply({ edits: [{ oldResource: fromUri, newResource: toUri }] }); - } - private _applyTextEditsToContent(content: string, edits: readonly TextEdit[]): string { // Use the example pattern provided by the user const makeModel = (uri: URI, contents: string) => this._instantiationService.createInstance(TextModel, contents, '', this._modelService.getCreationOptions('', uri, true), uri); @@ -628,15 +607,8 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh const model = makeModel(tempUri, content); try { - // Sort edits by position (end to start) to avoid position shifts - const sortedEdits = [...edits].sort((a, b) => { - const aStart = a.range.startLineNumber * 1000000 + a.range.startColumn; - const bStart = b.range.startLineNumber * 1000000 + b.range.startColumn; - return bStart - aStart; // reverse order - }); - // Apply edits - model.applyEdits(sortedEdits.map(edit => ({ + model.applyEdits(edits.map(edit => ({ range: { startLineNumber: edit.range.startLineNumber, startColumn: edit.range.startColumn, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 017ee6bf7f6..96d81480226 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -32,13 +32,11 @@ import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingCodeEditorIntegration } from './chatEditingCodeEditorIntegration.js'; import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; -import { FileOperation, FileOperationType } from './chatEditingOperations.js'; import { ChatEditingTextModelChangeService } from './chatEditingTextModelChangeService.js'; import { ChatEditingSnapshotTextModelContentProvider, ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; interface IMultiDiffEntryDelegate { collapse: (transaction: ITransaction | undefined) => void; - recordOperation?: (operation: FileOperation) => void; } @@ -273,21 +271,11 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie protected override async _doReject(): Promise { if (this.createdInRequestId === this._telemetryInfo.requestId) { - // Record file deletion operation before actually deleting - const finalContent = this.modifiedModel.getValue(); - if (this._multiDiffEntryDelegate.recordOperation) { - this._multiDiffEntryDelegate.recordOperation({ - type: FileOperationType.Delete, - uri: this.modifiedURI, - requestId: this._telemetryInfo.requestId, - epoch: 0, - finalContent, - }); - } - if (isTextFileEditorModel(this._docFileEditorModel)) { await this._docFileEditorModel.revert({ soft: true }); - await this._fileService.del(this.modifiedURI); + await this._fileService.del(this.modifiedURI).catch(err => { + // don't block if file is already deleted + }); } this._onDidDelete.fire(); } else { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index 67abc764faa..177029583a4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -963,6 +963,11 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie this.initializeModelsFromDiff(); } + public restoreModifiedModelFromSnapshot(snapshot: string) { + this.restoreSnapshotInModifiedModel(snapshot); + return this.initializeModelsFromDiff(); + } + private restoreSnapshotInModifiedModel(snapshot: string) { if (snapshot === createSnapshot(this.modifiedModel, this.transientOptions, this.configurationService)) { return; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 40ccdc8e6fb..2dbc04ff3ce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -38,7 +38,7 @@ import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js'; -import { ChatEditingCheckpointTimelineImpl } from './chatEditingCheckpointTimelineImpl.js'; +import { ChatEditingCheckpointTimelineImpl, IChatEditingTimelineFsDelegate } from './chatEditingCheckpointTimelineImpl.js'; import { ChatEditingModifiedDocumentEntry } from './chatEditingModifiedDocumentEntry.js'; import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; import { ChatEditingModifiedNotebookEntry } from './chatEditingModifiedNotebookEntry.js'; @@ -133,12 +133,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._timeline = this._instantiationService.createInstance( ChatEditingCheckpointTimelineImpl, chatSessionId, - { - setContents: async (uri, content, telemetryInfo) => { - const entry = await this._getOrCreateModifiedFileEntry(uri, NotExistBehavior.Create, telemetryInfo); - await entry.acceptAgentEdits(uri, [{ range: new Range(1, 1, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), text: content }], true, undefined); - } - } + this._getTimelineDelegate(), ); this.canRedo = this._timeline.canRedo.map((hasHistory, reader) => hasHistory && this._state.read(reader) === ChatEditingSessionState.Idle); @@ -151,6 +146,33 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio })); } + private _getTimelineDelegate(): IChatEditingTimelineFsDelegate { + return { + deleteFile: async (uri) => { + const entries = this._entriesObs.get().filter(e => !isEqual(e.modifiedURI, uri)); + this._entriesObs.set(entries, undefined); + await this._bulkEditService.apply({ edits: [{ oldResource: uri, options: { ignoreIfNotExists: true } }] }); + }, + renameFile: async (fromUri, toUri) => { + const entries = this._entriesObs.get(); + const previousEntry = entries.find(e => isEqual(e.modifiedURI, fromUri)); + if (previousEntry) { + const newEntry = await this._getOrCreateModifiedFileEntry(toUri, NotExistBehavior.Create, previousEntry.telemetryInfo, this._getCurrentTextOrNotebookSnapshot(previousEntry)); + previousEntry.dispose(); + this._entriesObs.set(entries.map(e => e === previousEntry ? newEntry : e), undefined); + } + }, + setContents: async (uri, content, telemetryInfo) => { + const entry = await this._getOrCreateModifiedFileEntry(uri, NotExistBehavior.Create, telemetryInfo); + if (entry instanceof ChatEditingModifiedNotebookEntry) { + await entry.restoreModifiedModelFromSnapshot(content); + } else { + await entry.acceptAgentEdits(uri, [{ range: new Range(1, 1, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), text: content }], true, undefined); + } + } + }; + } + public async init(): Promise { const restoredSessionState = await this._instantiationService.createInstance(ChatEditingSessionStorage, this.chatSessionId).restoreState(); if (restoredSessionState) { @@ -435,29 +457,28 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } } + private _getCurrentTextOrNotebookSnapshot(entry: AbstractChatEditingModifiedFileEntry): string { + if (entry instanceof ChatEditingModifiedNotebookEntry) { + return entry.getCurrentSnapshot(); + } else if (entry instanceof ChatEditingModifiedDocumentEntry) { + return entry.getCurrentContents(); + } else { + throw new Error(`unknown entry type for ${entry.modifiedURI}`); + } + } + private async _acceptStreamingEditsStart(responseModel: IChatResponseModel, undoStop: string | undefined, resource: URI) { const entry = await this._getOrCreateModifiedFileEntry(resource, NotExistBehavior.Create, this._getTelemetryInfoForModel(responseModel)); // Record file baseline if this is the first edit for this file in this request if (!this._timeline.hasFileBaseline(resource, responseModel.requestId)) { - let content: string; - let notebookViewType: string | undefined; - if (entry instanceof ChatEditingModifiedNotebookEntry) { - content = entry.getCurrentSnapshot(); - notebookViewType = entry.viewType; - } else if (entry instanceof ChatEditingModifiedDocumentEntry) { - content = entry.getCurrentContents(); - } else { - throw new Error(`unknown entry type for ${resource}`); - } - this._timeline.recordFileBaseline({ uri: resource, requestId: responseModel.requestId, - content, + content: this._getCurrentTextOrNotebookSnapshot(entry), epoch: this._timeline.incrementEpoch(), telemetryInfo: entry.telemetryInfo, - notebookViewType, + notebookViewType: entry instanceof ChatEditingModifiedNotebookEntry ? entry.viewType : undefined, }); } @@ -550,9 +571,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio * * @returns The modified file entry. */ - private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior.Create, telemetryInfo: IModifiedEntryTelemetryInfo): Promise; - private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior, telemetryInfo: IModifiedEntryTelemetryInfo): Promise; - private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior, telemetryInfo: IModifiedEntryTelemetryInfo): Promise { + private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior.Create, telemetryInfo: IModifiedEntryTelemetryInfo, initialContent?: string): Promise; + private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior, telemetryInfo: IModifiedEntryTelemetryInfo, initialContent?: string): Promise; + private async _getOrCreateModifiedFileEntry(resource: URI, ifNotExists: NotExistBehavior, telemetryInfo: IModifiedEntryTelemetryInfo, _initialContent?: string): Promise { resource = CellUri.parse(resource)?.notebook ?? resource; @@ -573,7 +594,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio entry.updateTelemetryInfo(telemetryInfo); } } else { - const initialContent = this._initialFileContents.get(resource); + const initialContent = _initialContent ?? this._initialFileContents.get(resource); // This gets manually disposed in .dispose() or in .restoreSnapshot() const maybeEntry = await this._createModifiedFileEntry(resource, telemetryInfo, ifNotExists, initialContent); if (!maybeEntry) { From bf119b0e9fe911a17f17ea01afb52d9543f93f08 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 24 Oct 2025 12:19:10 -0700 Subject: [PATCH 1624/4355] Improve chat todo list progress title, ARIA labels, and truncation (#273158) * Improve chat todo list progress title, ARIA labels, and truncation; adjust input padding * Reduce bottom padding for chat todo list widget to tighten spacing --- .../chatContentParts/chatTodoListWidget.ts | 28 +++++++++++-------- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/media/chat.css | 4 ++- .../test/browser/chatTodoListWidget.test.ts | 15 +++++----- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 0245fa69314..8608e038c5e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -448,19 +448,19 @@ export class ChatTodoListWidget extends Disposable { const notStartedTodos = todoList.filter(todo => todo.status === 'not-started'); const firstNotStartedTodo = notStartedTodos.length > 0 ? notStartedTodos[0] : undefined; - const progressText = dom.$('span'); - if (totalCount === 0) { - progressText.textContent = localize('chat.todoList.title', 'Todos'); - } else { - // Show the current task number (1-indexed): completed + in-progress, or just completed if none in-progress - // Ensure we show at least 1 when tasks exist - const currentTaskNumber = inProgressTodos.length > 0 ? completedCount + 1 : Math.max(1, completedCount); - progressText.textContent = localize('chat.todoList.titleWithProgressExpanded', 'Todos ({0}/{1})', currentTaskNumber, totalCount); - } - titleElement.appendChild(progressText); + // Calculate current task number (1-indexed) + const currentTaskNumber = inProgressTodos.length > 0 ? completedCount + 1 : Math.max(1, completedCount); + const progressCount = totalCount > 0 ? ` (${currentTaskNumber}/${totalCount})` : ''; + + const titleText = dom.$('span'); + titleText.textContent = this._isExpanded + ? localize('chat.todoList.title', 'Todos') + progressCount + : progressCount; + titleElement.appendChild(titleText); + const expandButtonLabel = this._isExpanded - ? localize('chat.todoList.collapseButtonWithProgress', 'Collapse {0}', progressText.textContent) - : localize('chat.todoList.expandButtonWithProgress', 'Expand {0}', progressText.textContent); + ? localize('chat.todoList.collapseButton', 'Collapse Todos {0}', progressCount) + : localize('chat.todoList.expandButton', 'Expand Todos {0}', progressCount); this.expandoElement.setAttribute('aria-label', expandButtonLabel); this.expandoElement.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); if (!this._isExpanded) { @@ -488,6 +488,10 @@ export class ChatTodoListWidget extends Disposable { const todoText = dom.$('span'); todoText.textContent = todoToShow.title; todoText.style.verticalAlign = 'middle'; + todoText.style.overflow = 'hidden'; + todoText.style.textOverflow = 'ellipsis'; + todoText.style.whiteSpace = 'nowrap'; + todoText.style.minWidth = '0'; titleElement.appendChild(todoText); } // Show "Done" when all tasks are completed diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 1d41e1380cb..ae3318dabd3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -2095,7 +2095,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge followupsHeight: this.followupsContainer.offsetHeight, inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, - inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (2 * 4) /* flex gap: edits|input */), + inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (1 * 4) /* flex gap: todo|edits|input */), attachmentsHeight: this.attachmentsHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index c19b4aed974..72a022d6a17 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1094,7 +1094,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { - padding: 6px 8px 6px 12px; + padding: 6px 8px 5px 12px; box-sizing: border-box; border: 1px solid var(--vscode-input-border, transparent); background-color: var(--vscode-editor-background); @@ -1186,6 +1186,8 @@ have to be updated for changes to the rules above, or to support more deeply nes color: var(--vscode-foreground); display: flex; align-items: center; + overflow: hidden; + text-overflow: ellipsis; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index c431e0bbd2c..837fa586822 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -62,7 +62,8 @@ suite('ChatTodoListWidget Accessibility', () => { const titleElement = widget.domNode.querySelector('#todo-list-title'); assert.ok(titleElement, 'Should have title element with ID todo-list-title'); - assert.ok(titleElement?.textContent?.includes('Todos')); + // When collapsed, title shows progress and current task without "Todos" prefix + assert.ok(titleElement?.textContent, 'Title should have content'); // The todo list container itself acts as the list (no nested ul element) const todoItems = todoListContainer?.querySelectorAll('li.todo-item'); @@ -113,13 +114,13 @@ suite('ChatTodoListWidget Accessibility', () => { assert.strictEqual(expandoElement?.getAttribute('aria-expanded'), 'false'); // Should be collapsed due to in-progress task assert.strictEqual(expandoElement?.getAttribute('aria-controls'), 'todo-list-container'); - // The title element should have aria-label with progress information + // The title element should have progress information const titleElement = expandoElement?.querySelector('.todo-list-title'); assert.ok(titleElement, 'Should have title element'); const titleText = titleElement?.textContent; - // When collapsed, title shows progress and current task: "Todos (2/3) - Second task" + // When collapsed, title shows progress and current task: " (2/3) - Second task" // Progress is 2/3 because: 1 completed + 1 in-progress (current) = task 2 of 3 - assert.ok(titleText?.includes('Todos (2/3)'), `Title should show progress format, but got: "${titleText}"`); + assert.ok(titleText?.includes('(2/3)'), `Title should show progress format, but got: "${titleText}"`); assert.ok(titleText?.includes('Second task'), `Title should show current task when collapsed, but got: "${titleText}"`); }); test('hidden status text elements exist for screen readers', () => { widget.render('test-session'); @@ -178,11 +179,11 @@ suite('ChatTodoListWidget Accessibility', () => { const titleElement = widget.domNode.querySelector('#todo-list-title'); assert.ok(titleElement, 'Should have title element with ID'); - // Title should show progress format: "Todos (2/3)" since one todo is completed and one is in-progress - // When collapsed, it also shows the current task: "Todos (2/3) - Second task" + // Title should show progress format: " (2/3)" since one todo is completed and one is in-progress + // When collapsed, it also shows the current task: " (2/3) - Second task" // Progress is 2/3 because: 1 completed + 1 in-progress (current) = task 2 of 3 const titleText = titleElement?.textContent; - assert.ok(titleText?.includes('Todos (2/3)'), `Title should show progress format, but got: "${titleText}"`); + assert.ok(titleText?.includes('(2/3)'), `Title should show progress format, but got: "${titleText}"`); assert.ok(titleText?.includes('Second task'), `Title should show current task when collapsed, but got: "${titleText}"`); // Verify aria-labelledby connection works From 77342324057cbbe6a5a78331f57c130ca8dee889 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:19:37 +0200 Subject: [PATCH 1625/4355] SCM - adopt new icon for selected repository (#273165) --- .../contrib/scm/browser/media/scm.css | 4 +++ .../scm/browser/scmRepositoryRenderer.ts | 28 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 84f948a1148..4169f639c25 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -54,6 +54,10 @@ flex: 0 1 auto; } +.scm-view .scm-provider > .monaco-icon-label.visible { + font-weight: bold; +} + .scm-view .scm-provider > .monaco-icon-label .monaco-icon-name-container { overflow: hidden; text-overflow: ellipsis; diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 6e1a7a2164a..e0bc77020a5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -5,7 +5,7 @@ import './media/scm.css'; import { IDisposable, DisposableStore, combinedDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun } from '../../../../base/common/observable.js'; +import { autorun, IObservable, observableSignalFromEvent } from '../../../../base/common/observable.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { ISCMProvider, ISCMRepository, ISCMViewService } from '../common/scm.js'; import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; @@ -30,6 +30,7 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri import { shorten } from '../../../../base/common/labels.js'; import { dirname } from '../../../../base/common/resources.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; +import { Codicon } from '../../../../base/common/codicons.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -69,6 +70,8 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer; + constructor( private readonly toolbarMenuId: MenuId, private readonly actionViewItemProvider: IActionViewItemProvider, @@ -81,7 +84,9 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer, index: number, templateData: RepositoryTemplate): void { const repository = isSCMRepository(arg) ? arg : arg.element; - if (ThemeIcon.isThemeIcon(repository.provider.iconPath)) { - templateData.icon.classList.add(...ThemeIcon.asClassNameArray(repository.provider.iconPath)); - } + templateData.elementDisposables.add(autorun(reader => { + this.onDidChangeVisibleRepositoriesSignal.read(reader); + + const isVisible = this.scmViewService.isVisible(repository); + templateData.label.element.classList.toggle('visible', isVisible); + + if (ThemeIcon.isThemeIcon(repository.provider.iconPath)) { + if (isVisible && repository.provider.iconPath.id === Codicon.repo.id) { + templateData.icon.className = ThemeIcon.asClassName(Codicon.repoSelected); + } else { + templateData.icon.className = ThemeIcon.asClassName(repository.provider.iconPath); + } + } else { + templateData.icon.className = ThemeIcon.asClassName(Codicon.repo); + } + })); // Use the description to disambiguate repositories with the same name and have // a `rootUri`. Use the `provider.rootUri` for disambiguation. If they have the From 41d5f1ce55cc6de512f21d2f2c252f1676cb7edc Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:32:08 -0700 Subject: [PATCH 1626/4355] Disable tracing for now due to Playwright MCP behavior (#273171) Haven't been able to figure out how to get tracing working without issues... so reverting the change. Opened an issue here: https://github.com/microsoft/playwright-mcp/issues/1166 --- test/mcp/src/application.ts | 2 +- test/mcp/src/automationTools/core.ts | 1 - test/mcp/src/playwright.ts | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index a60c7b9764d..fe64d941b4d 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -260,7 +260,7 @@ export async function getApplication({ recordVideo }: { recordVideo?: boolean } verbose: opts.verbose, remote: opts.remote, web: opts.web, - tracing: true, + tracing: opts.tracing, headless: opts.headless, browser: opts.browser, extraArgs: (opts.electronArgs || '').split(' ').map(arg => arg.trim()).filter(arg => !!arg), diff --git a/test/mcp/src/automationTools/core.ts b/test/mcp/src/automationTools/core.ts index 0ef9f5bb800..3f9e34cf1e6 100644 --- a/test/mcp/src/automationTools/core.ts +++ b/test/mcp/src/automationTools/core.ts @@ -37,7 +37,6 @@ export function applyCoreTools(server: McpServer, appService: ApplicationService 'Stop the VS Code application', async () => { const app = await appService.getOrCreateApplication(); - await app.stopTracing('mcp', true); await app.stop(); return { content: [{ diff --git a/test/mcp/src/playwright.ts b/test/mcp/src/playwright.ts index 6549e13095f..5a1a229e3e9 100644 --- a/test/mcp/src/playwright.ts +++ b/test/mcp/src/playwright.ts @@ -12,8 +12,7 @@ export async function getServer(app?: Application): Promise { const application = app ?? await getApplication(); const connection = await createConnection( { - capabilities: ['core', 'pdf', 'vision'], - saveTrace: true + capabilities: ['core', 'pdf', 'vision'] }, // eslint-disable-next-line local/code-no-any-casts () => Promise.resolve(application.code.driver.browserContext as any) From 0a33122dea46f980fc474221e25270c3d37c12e4 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:35:13 -0700 Subject: [PATCH 1627/4355] Fix for local chat sessions not showing in list (#273170) * Fix for local chat sessions not showing in list * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/chatSessions/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index ce6639403c4..f0b189af949 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -33,7 +33,7 @@ export function isChatSession(schemes: string[], editor?: EditorInput): boolean return false; } - if (!schemes.includes(editor.resource?.scheme)) { + if (!schemes.includes(editor.resource?.scheme) && editor.resource?.scheme !== Schemas.vscodeChatSession && editor.resource?.scheme !== Schemas.vscodeChatEditor) { return false; } From 34c74a02e1cc7f6eb2b7c22e93b2897316a0e828 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 24 Oct 2025 12:59:45 -0700 Subject: [PATCH 1628/4355] Ensure filename is correct in relative/absolute path matches. --- .../search/browser/anythingQuickAccess.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index e5df9227821..7d451a4cf68 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -751,8 +751,9 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { + const parent = dirname(resource); + const stat = await this.fileService.resolve(parent, { resolveTo: [resource] }); + if (stat?.children) { + const match = stat.children.find(child => this.uriIdentityService.extUri.isEqual(child.resource, resource)); + if (match) { + return URI.joinPath(parent, match.name); + } + } + return resource; + } + //#endregion //#region Command Center (if enabled) From de70d47577cc57d444a75e4f7c4a09bedf85b636 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 24 Oct 2025 22:01:18 +0200 Subject: [PATCH 1629/4355] Fixes https://github.com/microsoft/vscode/issues/273177 --- .../browser/commandSimplifier.ts | 3 +- .../browser/treeSitterCommandParser.ts | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts index 6558c4f488b..437e9a7bf8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts @@ -84,7 +84,8 @@ export class CommandSimplifier { private async _rewriteUnsupportedPwshChainOperators(commandLine: string, os: OperatingSystem, shell: string) { // TODO: This should just be Windows PowerShell in the future when the powershell grammar // supports chain operators https://github.com/airbus-cert/tree-sitter-powershell/issues/27 - if (isPowerShell(shell, os)) { + const disablePowerShellRewrites = true; // see https://github.com/microsoft/vscode/issues/273177 + if (!disablePowerShellRewrites && isPowerShell(shell, os)) { const doubleAmpersandCaptures = await this._treeSitterCommandParser.queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ '(', ' (command', diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 829f686fbca..4b1f9965bdb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -24,26 +24,32 @@ export class TreeSitterCommandParser { } async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { - const parser = await this._parser; - const language = await waitForState(derived(reader => { - return this._treeSitterLibraryService.getLanguage(languageId, true, reader); - })); - parser.setLanguage(language); + const disableSubCommandExtraction = true; // see https://github.com/microsoft/vscode/issues/273177 - const tree = parser.parse(commandLine); - if (!tree) { - throw new BugIndicatingError('Failed to parse tree'); - } + if (disableSubCommandExtraction) { + throw new Error('not supported'); + } else { + const parser = await this._parser; + const language = await waitForState(derived(reader => { + return this._treeSitterLibraryService.getLanguage(languageId, true, reader); + })); + parser.setLanguage(language); - const query = await this._getQuery(language); - if (!query) { - throw new BugIndicatingError('Failed to create tree sitter query'); - } + const tree = parser.parse(commandLine); + if (!tree) { + throw new BugIndicatingError('Failed to parse tree'); + } - const captures = query.captures(tree.rootNode); - const subCommands = captures.map(e => e.node.text); + const query = await this._getQuery(language); + if (!query) { + throw new BugIndicatingError('Failed to create tree sitter query'); + } + + const captures = query.captures(tree.rootNode); + const subCommands = captures.map(e => e.node.text); - return subCommands; + return subCommands; + } } async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { From 2ca79c442ac3813c1531b8fea152299f64f012c7 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 24 Oct 2025 16:05:17 -0400 Subject: [PATCH 1630/4355] add `id` for `TerminalCommand`, add `Add to Chat` action for commands (#272943) --- .../common/capabilities/capabilities.ts | 1 + .../commandDetection/terminalCommand.ts | 11 +- .../chat/browser/actions/chatContext.ts | 72 +++++++++- .../chat/browser/chatAttachmentModel.ts | 2 +- .../chat/browser/chatAttachmentWidgets.ts | 123 +++++++++++++++++- .../chatAttachmentsContentPart.ts | 6 +- .../contrib/chat/browser/chatInputPart.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 1 + .../contrib/chat/browser/media/chat.css | 13 ++ .../contrib/chat/common/chatAgents.ts | 1 + .../chat/common/chatVariableEntries.ts | 15 ++- .../contrib/terminal/browser/terminal.ts | 8 +- .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/terminalService.ts | 14 +- .../contrib/terminal/browser/terminalUri.ts | 11 +- .../terminal/browser/xterm/decorationAddon.ts | 46 ++++++- .../terminal/browser/xterm/xtermTerminal.ts | 4 +- .../contrib/terminal/common/terminal.ts | 1 + .../terminal/common/terminalStrings.ts | 3 +- .../terminal/test/browser/terminalUri.test.ts | 4 +- .../browser/xterm/decorationAddon.test.ts | 2 +- .../test/browser/xterm/xtermTerminal.test.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 2 +- .../test/browser/terminalLinkOpeners.test.ts | 6 +- 24 files changed, 335 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 5190dad97bd..51dc043abe3 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -291,6 +291,7 @@ interface IBaseTerminalCommand { isTrusted: boolean; timestamp: number; duration: number; + id: string; // Optional serializable cwd: string | undefined; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts index ecc7f984d5c..757cb94b83a 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts @@ -6,6 +6,7 @@ import { IMarkProperties, ISerializedTerminalCommand, ITerminalCommand } from '../capabilities.js'; import { ITerminalOutputMatcher, ITerminalOutputMatch } from '../../terminal.js'; import type { IBuffer, IBufferLine, IMarker, Terminal } from '@xterm/headless'; +import { generateUuid } from '../../../../../base/common/uuid.js'; export interface ITerminalCommandProperties { command: string; @@ -13,6 +14,7 @@ export interface ITerminalCommandProperties { isTrusted: boolean; timestamp: number; duration: number; + id: string; marker: IMarker | undefined; cwd: string | undefined; exitCode: number | undefined; @@ -48,6 +50,7 @@ export class TerminalCommand implements ITerminalCommand { get markProperties() { return this._properties.markProperties; } get executedX() { return this._properties.executedX; } get startX() { return this._properties.startX; } + get id() { return this._properties.id; } constructor( private readonly _xterm: Terminal, @@ -72,6 +75,7 @@ export class TerminalCommand implements ITerminalCommand { command: isCommandStorageDisabled ? '' : serialized.command, commandLineConfidence: serialized.commandLineConfidence ?? 'low', isTrusted: serialized.isTrusted, + id: serialized.id, promptStartMarker, marker, startX: serialized.startX, @@ -107,6 +111,7 @@ export class TerminalCommand implements ITerminalCommand { timestamp: this.timestamp, duration: this.duration, markProperties: this.markProperties, + id: this.id, }; } @@ -271,6 +276,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { cwd?: string; command?: string; commandLineConfidence?: 'low' | 'medium' | 'high'; + id: string; isTrusted?: boolean; isInvalid?: boolean; @@ -278,6 +284,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { constructor( private readonly _xterm: Terminal, ) { + this.id = generateUuid(); } serialize(cwd: string | undefined): ISerializedTerminalCommand | undefined { @@ -300,7 +307,8 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { commandStartLineContent: undefined, timestamp: 0, duration: 0, - markProperties: undefined + markProperties: undefined, + id: this.id }; } @@ -315,6 +323,7 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { command: ignoreCommandLine ? '' : (this.command || ''), commandLineConfidence: ignoreCommandLine ? 'low' : (this.commandLineConfidence || 'low'), isTrusted: !!this.isTrusted, + id: this.id, promptStartMarker: this.promptStartMarker, marker: this.commandStartMarker, startX: this.commandStartX, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 9b428597510..2cb4a3cd43a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { isElectron } from '../../../../../base/common/platform.js'; import { dirname } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; @@ -29,6 +29,9 @@ import { IChatWidget } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { ChatInstructionsPickerPick } from '../promptSyntax/attachInstructionsAction.js'; +import { ITerminalService } from '../../../terminal/browser/terminal.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { ITerminalCommand, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; export class ChatContextContributions extends Disposable implements IWorkbenchContribution { @@ -258,6 +261,73 @@ class ClipboardImageContextValuePick implements IChatContextValueItem { } } +export class TerminalContext implements IChatContextValueItem { + + readonly type = 'valuePick'; + readonly icon = Codicon.terminal; + readonly label = localize('terminal', 'Terminal'); + constructor(private readonly _resource: URI, @ITerminalService private readonly _terminalService: ITerminalService) { + + } + async isEnabled(widget: IChatWidget) { + const terminal = this._terminalService.getInstanceFromResource(this._resource); + return !!widget.attachmentCapabilities.supportsTerminalAttachments && terminal?.isDisposed === false; + } + async asAttachment(widget: IChatWidget): Promise { + const terminal = this._terminalService.getInstanceFromResource(this._resource); + if (!terminal) { + return; + } + + const command = terminal.capabilities.get(TerminalCapability.CommandDetection)?.commands.find(cmd => cmd.id === this._resource.query); + if (!command) { + return; + } + const attachment: IChatRequestVariableEntry = { + kind: 'terminalCommand', + id: `terminalCommand:${Date.now()}}`, + value: this.asValue(command), + name: command.command, + command: command.command, + output: command.getOutput(), + exitCode: command.exitCode, + resource: this._resource + }; + const cleanup = new DisposableStore(); + let disposed = false; + const disposeCleanup = () => { + if (disposed) { + return; + } + disposed = true; + cleanup.dispose(); + }; + cleanup.add(widget.attachmentModel.onDidChange(e => { + if (e.deleted.includes(attachment.id)) { + disposeCleanup(); + } + })); + cleanup.add(terminal.onDisposed(() => { + widget.attachmentModel.delete(attachment.id); + widget.refreshParsedInput(); + disposeCleanup(); + })); + return attachment; + } + + private asValue(command: ITerminalCommand): string { + let value = `Command: ${command.command}`; + const output = command.getOutput(); + if (output) { + value += `\nOutput:\n${output}`; + } + if (typeof command.exitCode === 'number') { + value += `\nExit Code: ${command.exitCode}`; + } + return value; + } +} + class ScreenshotContextValuePick implements IChatContextValueItem { readonly type = 'valuePick'; diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts index 870aa5f4237..d93a730c581 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts @@ -33,7 +33,7 @@ export class ChatAttachmentModel extends Disposable { constructor( @IFileService private readonly fileService: IFileService, @ISharedWebContentExtractorService private readonly webContentExtractorService: ISharedWebContentExtractorService, - @IChatAttachmentResolveService private readonly chatAttachmentResolveService: IChatAttachmentResolveService + @IChatAttachmentResolveService private readonly chatAttachmentResolveService: IChatAttachmentResolveService, ) { super(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index d93d47517ff..0f291521cfa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -17,6 +17,7 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Schemas } from '../../../../base/common/network.js'; import { basename, dirname } from '../../../../base/common/path.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; @@ -52,8 +53,9 @@ import { revealInSideBarCommand } from '../../files/browser/fileActions.contribu import { CellUri } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; import { getHistoryItemEditorTitle } from '../../scm/browser/util.js'; +import { ITerminalService } from '../../terminal/browser/terminal.js'; import { IChatContentReference } from '../common/chatService.js'; -import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry, ISCMHistoryItemChangeRangeVariableEntry } from '../common/chatVariableEntries.js'; +import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, IPromptFileVariableEntry, IPromptTextVariableEntry, ISCMHistoryItemVariableEntry, OmittedState, PromptFileVariableKind, ChatRequestToolReferenceEntry, ISCMHistoryItemChangeVariableEntry, ISCMHistoryItemChangeRangeVariableEntry, ITerminalVariableEntry } from '../common/chatVariableEntries.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelToolsService, ToolSet } from '../common/languageModelToolsService.js'; import { getCleanPromptName } from '../common/promptSyntax/config/promptFileLocations.js'; @@ -91,6 +93,7 @@ abstract class AbstractChatAttachmentWidget extends Disposable { protected readonly currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, @ICommandService protected readonly commandService: ICommandService, @IOpenerService protected readonly openerService: IOpenerService, + @ITerminalService protected readonly terminalService?: ITerminalService, ) { super(); this.element = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); @@ -160,6 +163,11 @@ abstract class AbstractChatAttachmentWidget extends Disposable { return; } + if (resource.scheme === Schemas.vscodeTerminal) { + this.terminalService?.openResource(resource); + return; + } + // Open file in editor const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; const options: OpenInternalOptions = { @@ -170,6 +178,7 @@ abstract class AbstractChatAttachmentWidget extends Disposable { ...openOptions.editorOptions }, }; + await this.openerService.open(resource, options); this._onDidOpen.fire(); this.element.focus(); @@ -249,6 +258,118 @@ export class FileAttachmentWidget extends AbstractChatAttachmentWidget { } } + +export class TerminalCommandAttachmentWidget extends AbstractChatAttachmentWidget { + + constructor( + attachment: ITerminalVariableEntry, + currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + contextResourceLabels: ResourceLabels, + @ICommandService commandService: ICommandService, + @IOpenerService openerService: IOpenerService, + @IHoverService private readonly hoverService: IHoverService, + @ITerminalService protected override readonly terminalService: ITerminalService, + ) { + super(attachment, options, container, contextResourceLabels, currentLanguageModel, commandService, openerService, terminalService); + + const ariaLabel = localize('chat.terminalCommand', "Terminal command, {0}", attachment.command); + const clickHandler = () => this.openResource(attachment.resource, { editorOptions: { preserveFocus: true } }, false, undefined); + + this._register(createTerminalCommandElements(this.element, attachment, ariaLabel, this.hoverService, clickHandler)); + + this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, async (e: KeyboardEvent) => { + if ((e.target as HTMLElement | null)?.closest('.monaco-button')) { + return; + } + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + dom.EventHelper.stop(e, true); + await clickHandler(); + } + })); + + this.attachClearButton(); + } +} + +const MAX_TERMINAL_ATTACHMENT_OUTPUT_LENGTH = 2000; + +function createTerminalCommandElements( + element: HTMLElement, + attachment: ITerminalVariableEntry, + ariaLabel: string, + hoverService: IHoverService, + clickHandler: () => Promise +): IDisposable { + const disposable = new DisposableStore(); + element.ariaLabel = ariaLabel; + element.style.cursor = 'pointer'; + + const pillIcon = dom.$('div.chat-attached-context-pill', {}, dom.$('span.codicon.codicon-terminal')); + const textLabel = dom.$('span.chat-attached-context-custom-text', {}, attachment.command); + element.appendChild(pillIcon); + element.appendChild(textLabel); + + disposable.add(dom.addDisposableListener(element, dom.EventType.CLICK, e => { + if ((e.target as HTMLElement | null)?.closest('.monaco-button')) { + return; + } + void clickHandler(); + })); + + const hoverElement = dom.$('div.chat-attached-context-hover'); + hoverElement.setAttribute('aria-label', ariaLabel); + + const commandTitle = dom.$('div', {}, typeof attachment.exitCode === 'number' + ? localize('chat.terminalCommandHoverCommandTitleExit', "Command: {0}, exit code: {1}", attachment.command, attachment.exitCode) + : localize('chat.terminalCommandHoverCommandTitle', "Command")); + commandTitle.classList.add('attachment-additional-info'); + const commandBlock = dom.$('pre.chat-terminal-command-block'); + hoverElement.append(commandTitle, commandBlock); + + if (attachment.output && attachment.output.trim().length > 0) { + const outputTitle = dom.$('div', {}, localize('chat.terminalCommandHoverOutputTitle', "Output")); + outputTitle.classList.add('attachment-additional-info'); + const outputBlock = dom.$('pre.chat-terminal-command-output'); + let outputText = attachment.output; + let truncated = false; + if (outputText.length > MAX_TERMINAL_ATTACHMENT_OUTPUT_LENGTH) { + outputText = `${outputText.slice(0, MAX_TERMINAL_ATTACHMENT_OUTPUT_LENGTH)}...`; + truncated = true; + } + outputBlock.textContent = outputText; + hoverElement.append(outputTitle, outputBlock); + + if (truncated) { + const truncatedInfo = dom.$('div', {}, localize('chat.terminalCommandHoverOutputTruncated', "Output truncated to first {0} characters.", MAX_TERMINAL_ATTACHMENT_OUTPUT_LENGTH)); + truncatedInfo.classList.add('attachment-additional-info'); + hoverElement.appendChild(truncatedInfo); + } + } + + const hint = dom.$('div', {}, localize('chat.terminalCommandHoverHint', "Click to focus this command in the terminal.")); + hint.classList.add('attachment-additional-info'); + hoverElement.appendChild(hint); + + const separator = dom.$('div.chat-attached-context-url-separator'); + const openLink = dom.$('a.chat-attached-context-url', {}, localize('chat.terminalCommandHoverOpen', "Open in terminal")); + disposable.add(dom.addDisposableListener(openLink, 'click', e => { + e.preventDefault(); + e.stopPropagation(); + void clickHandler(); + })); + hoverElement.append(separator, openLink); + + disposable.add(hoverService.setupDelayedHover(element, { + ...commonHoverOptions, + content: hoverElement, + }, commonHoverLifecycleOptions)); + + return disposable; +} + export class ImageAttachmentWidget extends AbstractChatAttachmentWidget { constructor( diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index 2dee68f7d4b..4e750be61a0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -11,9 +11,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ResourceLabels } from '../../../../browser/labels.js'; -import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js'; export interface IChatAttachmentsContentPartOptions { readonly variables: IChatRequestVariableEntry[]; @@ -141,6 +141,8 @@ export class ChatAttachmentsContentPart extends Disposable { widget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { widget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); + } else if (isTerminalVariableEntry(attachment)) { + widget = this.instantiationService.createInstance(TerminalCommandAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (isPasteVariableEntry(attachment)) { widget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels); } else if (resource && isNotebookOutputVariableEntry(attachment)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index ae3318dabd3..150d24d4d8a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -90,7 +90,7 @@ import { ChatOpenModelPickerActionId, ChatSessionPrimaryPickerAction, ChatSubmit import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; +import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from './chatAttachmentWidgets.js'; import { IDisposableReference } from './chatContentParts/chatCollections.js'; import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js'; import { ChatTodoListWidget } from './chatContentParts/chatTodoListWidget.js'; @@ -1612,6 +1612,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge attachmentWidget = this.instantiationService.createInstance(PromptTextAttachmentWidget, attachment, undefined, options, container, this._contextResourceLabels); } else if (resource && (attachment.kind === 'file' || attachment.kind === 'directory')) { attachmentWidget = this.instantiationService.createInstance(FileAttachmentWidget, resource, range, attachment, undefined, this._currentLanguageModel, options, container, this._contextResourceLabels); + } else if (attachment.kind === 'terminalCommand') { + attachmentWidget = this.instantiationService.createInstance(TerminalCommandAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isImageVariableEntry(attachment)) { attachmentWidget = this.instantiationService.createInstance(ImageAttachmentWidget, resource, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels); } else if (isElementVariableEntry(attachment)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 7903df3b7d9..a6fe0c2a041 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -257,6 +257,7 @@ const supportsAllAttachments: Required = { supportsSourceControlAttachments: true, supportsProblemAttachments: true, supportsSymbolAttachments: true, + supportsTerminalAttachments: true, }; export class ChatWidget extends Disposable implements IChatWidget { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 72a022d6a17..b2cf7d39645 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2288,6 +2288,19 @@ have to be updated for changes to the rules above, or to support more deeply nes margin-top: 2px; } +.chat-attached-context-hover .chat-terminal-command-block, +.chat-attached-context-hover .chat-terminal-command-output { + font-family: var(--monaco-monospace-font); + white-space: pre-wrap; + word-break: break-word; + margin: 4px 0; +} + +.chat-attached-context-hover .chat-terminal-command-output { + max-height: 200px; + overflow: auto; +} + .chat-attached-context-attachment .chat-attached-context-pill { font-size: 12px; display: inline-flex; diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index bf1d9a0da19..eb2c0723332 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -48,6 +48,7 @@ export interface IChatAgentAttachmentCapabilities { supportsSourceControlAttachments?: boolean; supportsProblemAttachments?: boolean; supportsSymbolAttachments?: boolean; + supportsTerminalAttachments?: boolean; } export interface IChatAgentData { diff --git a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts index 1a820718a9c..a86223fa9d5 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts @@ -228,12 +228,21 @@ export interface ISCMHistoryItemChangeRangeVariableEntry extends IBaseChatReques }; } +export interface ITerminalVariableEntry extends IBaseChatRequestVariableEntry { + readonly kind: 'terminalCommand'; + readonly value: string; + readonly resource: URI; + readonly command: string; + readonly output?: string; + readonly exitCode?: number; +} + export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry | IChatRequestToolEntry | IChatRequestToolSetEntry | IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry | IPromptFileVariableEntry | IPromptTextVariableEntry - | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry; + | ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry | ITerminalVariableEntry; export namespace IChatRequestVariableEntry { @@ -254,6 +263,10 @@ export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is return obj.kind === 'implicit'; } +export function isTerminalVariableEntry(obj: IChatRequestVariableEntry): obj is ITerminalVariableEntry { + return obj.kind === 'terminalCommand'; +} + export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry { return obj.kind === 'paste'; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index af68229d537..de55c4d52b6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -200,7 +200,7 @@ export interface IMarkTracker { scrollToClosestMarker(startMarkerId: string, endMarkerId?: string, highlight?: boolean | undefined): void; scrollToLine(line: number, position: ScrollPosition): void; - revealCommand(command: ITerminalCommand | ICurrentPartialCommand, position?: ScrollPosition): void; + revealCommand(command: ITerminalCommand | ICurrentPartialCommand | URI, position?: ScrollPosition): void; revealRange(range: IBufferRange): void; registerTemporaryDecoration(marker: IMarker, endMarker: IMarker | undefined, showOutline: boolean): void; showCommandGuide(command: ITerminalCommand | undefined): void; @@ -445,6 +445,12 @@ export interface ITerminalService extends ITerminalInstanceHost { * @param getEvent Maps the capability to the event. */ createOnInstanceCapabilityEvent(capabilityId: T, getEvent: (capability: ITerminalCapabilityImplMap[T]) => Event): IDynamicListEventMultiplexer<{ instance: ITerminalInstance; data: K }>; + + /** + * Reveals the terminal and, if provided, scrolls to the command mark. + * @param resource the terminal resource + */ + openResource(resource: URI): void; } /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 136eafc5f50..99c6d385c37 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -789,7 +789,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } const disableShellIntegrationReporting = (this.shellLaunchConfig.executable === undefined || this.shellType === undefined) || !shellIntegrationSupportedShellTypes.includes(this.shellType); - const xterm = this._scopedInstantiationService.createInstance(XtermTerminal, Terminal, { + const xterm = this._scopedInstantiationService.createInstance(XtermTerminal, this._resource, Terminal, { cols: this._cols, rows: this._rows, xtermColorProvider: this._scopedInstantiationService.createInstance(TerminalInstanceColorProvider, this._targetRef), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 884828d1d94..1bc2d22031b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -770,6 +770,18 @@ export class TerminalService extends Disposable implements ITerminalService { return getInstanceFromResource(this.instances, resource); } + openResource(resource: URI): void { + const instance = this.getInstanceFromResource(resource); + if (instance) { + this.revealTerminal(instance); + const commands = instance.capabilities.get(TerminalCapability.CommandDetection)?.commands; + const relevantCommand = commands?.find(c => c.id === resource.query); + if (relevantCommand) { + instance.xterm?.markTracker.revealCommand(relevantCommand); + } + } + } + isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean { return this.instances.some(term => term.processId === remoteTerm.pid); } @@ -1058,7 +1070,7 @@ export class TerminalService extends Disposable implements ITerminalService { async createDetachedTerminal(options: IDetachedXTermOptions): Promise { const ctor = await TerminalInstance.getXtermConstructor(this._keybindingService, this._contextKeyService); - const xterm = this._instantiationService.createInstance(XtermTerminal, ctor, { + const xterm = this._instantiationService.createInstance(XtermTerminal, undefined, ctor, { cols: options.cols, rows: options.rows, xtermColorProvider: options.colorProvider, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts index 123f258b5e2..ac7c34e4fa5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts @@ -7,6 +7,12 @@ import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { ITerminalInstance, TerminalDataTransfers } from './terminal.js'; +export interface ITerminalUriMetadata { + title?: string; + commandId?: string; + commandLine?: string; +} + export function parseTerminalUri(resource: URI): ITerminalIdentifier { const [, workspaceId, instanceId] = resource.path.split('/'); if (!workspaceId || !Number.parseInt(instanceId)) { @@ -15,14 +21,17 @@ export function parseTerminalUri(resource: URI): ITerminalIdentifier { return { workspaceId, instanceId: Number.parseInt(instanceId) }; } -export function getTerminalUri(workspaceId: string, instanceId: number, title?: string): URI { +export function getTerminalUri(workspaceId: string, instanceId: number, title?: string, commandId?: string): URI { + return URI.from({ scheme: Schemas.vscodeTerminal, path: `/${workspaceId}/${instanceId}`, fragment: title || undefined, + query: commandId }); } + export interface ITerminalIdentifier { workspaceId: string; instanceId: number | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 8d8793fdeea..e59ac53d462 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -27,6 +27,12 @@ import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_ import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { IChatContextPickService } from '../../../chat/browser/chatContextPickService.js'; +import { IChatWidgetService } from '../../../chat/browser/chat.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { TerminalContext } from '../../../chat/browser/actions/chatContext.js'; +import { getTerminalUri, parseTerminalUri } from '../terminalUri.js'; +import { URI } from '../../../../../base/common/uri.js'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } @@ -45,6 +51,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco readonly onDidRequestCopyAsHtml = this._onDidRequestCopyAsHtml.event; constructor( + private readonly _resource: URI | undefined, private readonly _capabilities: ITerminalCapabilityStore, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @@ -56,7 +63,10 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco @ICommandService private readonly _commandService: ICommandService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @INotificationService private readonly _notificationService: INotificationService, - @IHoverService private readonly _hoverService: IHoverService + @IHoverService private readonly _hoverService: IHoverService, + @IChatContextPickService private readonly _contextPickService: IChatContextPickService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); this._register(toDisposable(() => this._dispose())); @@ -413,11 +423,18 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco } private async _getCommandActions(command: ITerminalCommand): Promise { + const actions: IAction[] = []; const registeredMenuItems = this._registeredMenuItems.get(command); if (registeredMenuItems?.length) { actions.push(...registeredMenuItems, new Separator()); } + + const attachToChatAction = this._createAttachToChatAction(command); + if (attachToChatAction) { + actions.push(attachToChatAction, new Separator()); + } + if (command.command !== '') { const labelRun = localize("terminal.rerunCommand", 'Rerun Command'); actions.push({ @@ -502,6 +519,33 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco return actions; } + private _createAttachToChatAction(command: ITerminalCommand): IAction | undefined { + const labelAttachToChat = localize("terminal.attachToChat", 'Attach To Chat'); + return { + class: undefined, tooltip: labelAttachToChat, id: 'terminal.attachToChat', label: labelAttachToChat, enabled: true, + run: async () => { + const widget = this._chatWidgetService.lastFocusedWidget; + let terminalContext: TerminalContext | undefined; + if (this._resource) { + const parsedUri = parseTerminalUri(this._resource); + terminalContext = this._instantiationService.createInstance(TerminalContext, getTerminalUri(parsedUri.workspaceId, parsedUri.instanceId!, undefined, command.id)); + } + if (terminalContext && widget && widget.attachmentCapabilities.supportsTerminalAttachments) { + try { + const attachment = await terminalContext.asAttachment(widget); + if (attachment) { + widget.attachmentModel.addContext(attachment); + widget.focusInput(); + return; + } + } catch (err) { + } + this._store.add(this._contextPickService.registerChatContextItem(terminalContext)); + } + } + }; + } + private _showToggleVisibilityQuickPick() { const quickPick = this._register(this._quickInputService.createQuickPick()); quickPick.hideInput = true; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index e1293b426a9..7655503b1ce 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -45,6 +45,7 @@ import { XtermAddonImporter } from './xtermAddonImporter.js'; import { equals } from '../../../../../base/common/objects.js'; import type { IProgressState } from '@xterm/addon-progress'; import type { CommandDetectionCapability } from '../../../../../platform/terminal/common/capabilities/commandDetectionCapability.js'; +import { URI } from '../../../../../base/common/uri.js'; const enum RenderConstants { SmoothScrollDuration = 125 @@ -172,6 +173,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach * outside of this class such that {@link raw} is not nullable. */ constructor( + resource: URI | undefined, xtermCtor: typeof RawXtermTerminal, options: IXtermTerminalOptions, private readonly _onDidExecuteText: Event | undefined, @@ -277,7 +279,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach this._updateUnicodeVersion(); this._markNavigationAddon = this._instantiationService.createInstance(MarkNavigationAddon, options.capabilities); this.raw.loadAddon(this._markNavigationAddon); - this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, this._capabilities); + this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, resource, this._capabilities); this._register(this._decorationAddon.onDidRequestRunCommand(e => this._onDidRequestRunCommand.fire(e))); this._register(this._decorationAddon.onDidRequestCopyAsHtml(e => this._onDidRequestCopyAsHtml.fire(e))); this.raw.loadAddon(this._decorationAddon); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 362bb34b32a..8a86ef4b30c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -487,6 +487,7 @@ export const enum TerminalCommandId { ShowEnvironmentContributions = 'workbench.action.terminal.showEnvironmentContributions', StartVoice = 'workbench.action.terminal.startVoice', StopVoice = 'workbench.action.terminal.stopVoice', + RevealCommand = 'workbench.action.terminal.revealCommand', } export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index 08ce9eacc9c..7181caa9829 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -41,5 +41,6 @@ export const terminalStrings = { newWithCwd: localize2('workbench.action.terminal.newWithCwd', "Create New Terminal Starting in a Custom Working Directory"), renameWithArgs: localize2('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"), scrollToPreviousCommand: localize2('workbench.action.terminal.scrollToPreviousCommand', "Scroll to Previous Command"), - scrollToNextCommand: localize2('workbench.action.terminal.scrollToNextCommand', "Scroll to Next Command") + scrollToNextCommand: localize2('workbench.action.terminal.scrollToNextCommand', "Scroll to Next Command"), + revealCommand: localize2('workbench.action.terminal.revealCommand', "Reveal Command in Terminal"), }; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalUri.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalUri.test.ts index 8da7c8713f5..4a6d8c089a2 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalUri.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalUri.test.ts @@ -51,7 +51,7 @@ suite('terminalUri', () => { strictEqual( getInstanceFromResource([ { resource: getTerminalUri('workspace', 2, 'title') } - ], getTerminalUri('workspace', 1)), + ], getTerminalUri('workspace', 1, 'title')), undefined ); }); @@ -62,7 +62,7 @@ suite('terminalUri', () => { { resource: getTerminalUri('workspace', 1, 'title') }, instance, { resource: getTerminalUri('workspace', 3, 'title') } - ], getTerminalUri('workspace', 2)), + ], getTerminalUri('workspace', 2, 'title')), instance ); }); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts index 326796dce01..8f74a602218 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts @@ -54,7 +54,7 @@ suite('DecorationAddon', () => { })); const capabilities = store.add(new TerminalCapabilityStore()); capabilities.add(TerminalCapability.CommandDetection, store.add(instantiationService.createInstance(CommandDetectionCapability, xterm))); - decorationAddon = store.add(instantiationService.createInstance(DecorationAddon, capabilities)); + decorationAddon = store.add(instantiationService.createInstance(DecorationAddon, undefined, capabilities)); xterm.loadAddon(decorationAddon); }); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index 91e5744bf9d..978445fced7 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -122,7 +122,7 @@ suite('XtermTerminal', () => { XTermBaseCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; const capabilityStore = store.add(new TerminalCapabilityStore()); - xterm = store.add(instantiationService.createInstance(XtermTerminal, XTermBaseCtor, { + xterm = store.add(instantiationService.createInstance(XtermTerminal, undefined, XTermBaseCtor, { cols: 80, rows: 30, xtermColorProvider: { getBackgroundColor: () => undefined }, @@ -280,7 +280,7 @@ suite('XtermTerminal', () => { [PANEL_BACKGROUND]: '#ff0000', [SIDE_BAR_BACKGROUND]: '#00ff00' })); - xterm = store.add(instantiationService.createInstance(XtermTerminal, XTermBaseCtor, { + xterm = store.add(instantiationService.createInstance(XtermTerminal, undefined, XTermBaseCtor, { cols: 80, rows: 30, xtermAddonImporter: new TestXtermAddonImporter(), @@ -316,7 +316,7 @@ suite('XtermTerminal', () => { 'terminal.ansiBrightCyan': '#150000', 'terminal.ansiBrightWhite': '#160000', })); - xterm = store.add(instantiationService.createInstance(XtermTerminal, XTermBaseCtor, { + xterm = store.add(instantiationService.createInstance(XtermTerminal, undefined, XTermBaseCtor, { cols: 80, rows: 30, xtermAddonImporter: new TestXtermAddonImporter(), diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 6f53c6f734a..743463eb71d 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -80,7 +80,7 @@ suite('Buffer Content Tracker', () => { capabilities.add(TerminalCapability.NaiveCwdDetection, null!); } const TerminalCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; - xterm = store.add(instantiationService.createInstance(XtermTerminal, TerminalCtor, { + xterm = store.add(instantiationService.createInstance(XtermTerminal, undefined, TerminalCtor, { cols: 80, rows: 30, xtermColorProvider: { getBackgroundColor: () => undefined }, diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts index dacba63de69..93aa8fdb632 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts @@ -30,6 +30,7 @@ import { importAMDNodeModule } from '../../../../../../amdX.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { TerminalCommand } from '../../../../../../platform/terminal/common/capabilities/commandDetection/terminalCommand.js'; import type { IMarker } from '@xterm/headless'; +import { generateUuid } from '../../../../../../base/common/uuid.js'; interface ITerminalLinkActivationResult { source: 'editor' | 'search'; @@ -151,6 +152,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, + id: generateUuid() })]); fileService.setFiles([ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), @@ -293,7 +295,8 @@ suite('Workbench - TerminalLinkOpeners', () => { } as Partial as any, exitCode: 0, commandStartLineContent: '', - markProperties: {} + markProperties: {}, + id: generateUuid() })]); await opener.open({ text: 'file.txt', @@ -554,6 +557,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, + id: generateUuid() })]); await opener.open({ text: 'file.txt', From 7f2c0e12f66085c5dad551ceac99062d4d681fa4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 24 Oct 2025 22:10:39 +0200 Subject: [PATCH 1631/4355] Disables command re-writing tests, as the implementation is disabled for now. --- .../test/electron-browser/commandSimplifier.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts index afc5113eded..8c1093cb8fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts @@ -23,7 +23,7 @@ import { FileService } from '../../../../../../platform/files/common/fileService import { Schemas } from '../../../../../../base/common/network.js'; import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; -suite('command re-writing', () => { +suite.skip('command re-writing', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; From f26d82c20eb18755fb699e93ac3bf65056ae1056 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 24 Oct 2025 22:14:27 +0200 Subject: [PATCH 1632/4355] Disables another test --- .../test/electron-browser/runInTerminalTool.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 4b3c93c0bf2..9df091177e3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -290,7 +290,7 @@ suite('RunInTerminalTool', () => { 'A=1 B=2 C=3 ./script.sh', ]; - suite('auto approved', () => { + suite.skip('auto approved', () => { for (const command of autoApprovedTestCases) { test(command.replaceAll('\n', '\\n'), async () => { assertAutoApproved(await executeToolTest({ command })); From f8a94f9f3f48b71dd1b98c17b9b2d185044314df Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:53:54 -0700 Subject: [PATCH 1633/4355] Fix chatInput matches check for custom schemes This could result in duplicate editors being opened in some cases --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 9c7dbc47999..a9e88c09e77 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -140,15 +140,11 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return false; } - if (this.resource.scheme === Schemas.vscodeChatSession) { - return isEqual(this.resource, otherInput.resource); - } - if (this.resource.scheme === Schemas.vscodeChatEditor && otherInput.resource.scheme === Schemas.vscodeChatEditor) { return this.sessionId === otherInput.sessionId; } - return false; + return isEqual(this.resource, otherInput.resource); } override get typeId(): string { From bd2aa0c0984a30dc6af506ab8b5c3cb4f472c803 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:00:57 -0700 Subject: [PATCH 1634/4355] Fix key string generation (#273188) Wanna get this in to unblock azure folks. Fixes https://github.com/microsoft/vscode/issues/273176 --- .../api/common/extHostAuthentication.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 27a36da458a..0735bcf159d 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -86,7 +86,27 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { async getSession(requestingExtension: IExtensionDescription, providerId: string, scopesOrRequest: readonly string[] | vscode.AuthenticationWwwAuthenticateRequest, options: vscode.AuthenticationGetSessionOptions = {}): Promise { const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); const keys: (keyof vscode.AuthenticationGetSessionOptions)[] = Object.keys(options) as (keyof vscode.AuthenticationGetSessionOptions)[]; - const optionsStr = keys.sort().map(key => `${key}:${!!options[key]}`).join(', '); + // TODO: pull this out into a utility function somewhere + const optionsStr = keys + .map(key => { + switch (key) { + case 'account': + return `${key}:${options.account?.id}`; + case 'createIfNone': + case 'forceNewSession': { + const value = typeof options[key] === 'boolean' + ? `${options[key]}` + : `'${options[key]?.detail}/${options[key]?.learnMore?.toString()}'`; + return `${key}:${value}`; + } + case 'authorizationServer': + return `${key}:${options.authorizationServer?.toString(true)}`; + default: + return `${key}:${!!options[key]}`; + } + }) + .sort() + .join(', '); let singlerKey: string; if (isAuthenticationWwwAuthenticateRequest(scopesOrRequest)) { From 5aa53371e4f1cb2108a71b9e876bafda93239840 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:06:51 -0700 Subject: [PATCH 1635/4355] Only disable for arm, clean up --- .../browser/commandSimplifier.ts | 27 +++++---- .../browser/treeSitterCommandParser.ts | 56 +++++++++++-------- .../commandSimplifier.test.ts | 3 +- .../runInTerminalTool.test.ts | 3 +- .../treeSitterCommandParser.test.ts | 3 +- 5 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts index 437e9a7bf8a..52bf2b5daca 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts @@ -84,18 +84,21 @@ export class CommandSimplifier { private async _rewriteUnsupportedPwshChainOperators(commandLine: string, os: OperatingSystem, shell: string) { // TODO: This should just be Windows PowerShell in the future when the powershell grammar // supports chain operators https://github.com/airbus-cert/tree-sitter-powershell/issues/27 - const disablePowerShellRewrites = true; // see https://github.com/microsoft/vscode/issues/273177 - if (!disablePowerShellRewrites && isPowerShell(shell, os)) { - const doubleAmpersandCaptures = await this._treeSitterCommandParser.queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ - '(', - ' (command', - ' (command_elements', - ' (generic_token) @double.ampersand', - ' (#eq? @double.ampersand "&&")))', - ')', - ].join('\n')); - for (const capture of doubleAmpersandCaptures.reverse()) { - commandLine = `${commandLine.substring(0, capture.node.startIndex)};${commandLine.substring(capture.node.endIndex)}`; + if (isPowerShell(shell, os)) { + try { + const doubleAmpersandCaptures = await this._treeSitterCommandParser.queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ + '(', + ' (command', + ' (command_elements', + ' (generic_token) @double.ampersand', + ' (#eq? @double.ampersand "&&")))', + ')', + ].join('\n')); + for (const capture of doubleAmpersandCaptures.reverse()) { + commandLine = `${commandLine.substring(0, capture.node.startIndex)};${commandLine.substring(capture.node.endIndex)}`; + } + } catch { + // Swallow tree sitter failures } } return commandLine; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 4b1f9965bdb..7cf078cb8b4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -24,35 +24,33 @@ export class TreeSitterCommandParser { } async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { - const disableSubCommandExtraction = true; // see https://github.com/microsoft/vscode/issues/273177 + this._throwIfCanCrash(languageId); - if (disableSubCommandExtraction) { - throw new Error('not supported'); - } else { - const parser = await this._parser; - const language = await waitForState(derived(reader => { - return this._treeSitterLibraryService.getLanguage(languageId, true, reader); - })); - parser.setLanguage(language); - - const tree = parser.parse(commandLine); - if (!tree) { - throw new BugIndicatingError('Failed to parse tree'); - } - - const query = await this._getQuery(language); - if (!query) { - throw new BugIndicatingError('Failed to create tree sitter query'); - } - - const captures = query.captures(tree.rootNode); - const subCommands = captures.map(e => e.node.text); - - return subCommands; + const parser = await this._parser; + const language = await waitForState(derived(reader => { + return this._treeSitterLibraryService.getLanguage(languageId, true, reader); + })); + parser.setLanguage(language); + + const tree = parser.parse(commandLine); + if (!tree) { + throw new BugIndicatingError('Failed to parse tree'); + } + + const query = await this._getQuery(language); + if (!query) { + throw new BugIndicatingError('Failed to create tree sitter query'); } + + const captures = query.captures(tree.rootNode); + const subCommands = captures.map(e => e.node.text); + + return subCommands; } async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { + this._throwIfCanCrash(languageId); + const parser = await this._parser; const language = await waitForState(derived(reader => { return this._treeSitterLibraryService.getLanguage(languageId, true, reader); @@ -82,4 +80,14 @@ export class TreeSitterCommandParser { } return query; } + + private _throwIfCanCrash(languageId: TreeSitterCommandParserLanguage) { + // TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 + if ( + (process.arch === 'arm' || process.arch === 'arm64') && + languageId === TreeSitterCommandParserLanguage.PowerShell + ) { + throw new Error('not supported'); + } + } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts index 8c1093cb8fb..2c3e2d77574 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts @@ -23,7 +23,8 @@ import { FileService } from '../../../../../../platform/files/common/fileService import { Schemas } from '../../../../../../base/common/network.js'; import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; -suite.skip('command re-writing', () => { +// TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 +(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('command re-writing', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 9df091177e3..74ac371c8d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -44,7 +44,8 @@ class TestRunInTerminalTool extends RunInTerminalTool { } } -suite('RunInTerminalTool', () => { +// TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 +(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('RunInTerminalTool', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 5d4913313a2..f8872eb9da0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -15,7 +15,8 @@ import { Schemas } from '../../../../../../base/common/network.js'; import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../../browser/treeSitterCommandParser.js'; -suite('TreeSitterCommandParser', () => { +// TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 +(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('TreeSitterCommandParser', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; From 2d2226ff7c8535e20000f253743afd7c635606fd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:08:59 -0700 Subject: [PATCH 1636/4355] Improve error message --- .../chatAgentTools/browser/treeSitterCommandParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 7cf078cb8b4..f262bd75bd6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -87,7 +87,7 @@ export class TreeSitterCommandParser { (process.arch === 'arm' || process.arch === 'arm64') && languageId === TreeSitterCommandParserLanguage.PowerShell ) { - throw new Error('not supported'); + throw new Error('powershell grammar is not supported on arm or arm64'); } } } From e858db0848b7be2055635767a7703ebd8f628d4c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 24 Oct 2025 17:13:50 -0400 Subject: [PATCH 1637/4355] terminal command attachment follow-ups (#273187) --- .../contrib/chat/browser/actions/chatContext.ts | 4 ++-- .../contrib/chat/browser/chatAttachmentWidgets.ts | 10 +++------- .../contrib/terminal/browser/terminalService.ts | 2 +- .../workbench/contrib/terminal/browser/terminalUri.ts | 2 +- .../contrib/terminal/browser/xterm/decorationAddon.ts | 3 ++- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 2cb4a3cd43a..41dad4bc2e0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -269,7 +269,7 @@ export class TerminalContext implements IChatContextValueItem { constructor(private readonly _resource: URI, @ITerminalService private readonly _terminalService: ITerminalService) { } - async isEnabled(widget: IChatWidget) { + isEnabled(widget: IChatWidget) { const terminal = this._terminalService.getInstanceFromResource(this._resource); return !!widget.attachmentCapabilities.supportsTerminalAttachments && terminal?.isDisposed === false; } @@ -279,7 +279,7 @@ export class TerminalContext implements IChatContextValueItem { return; } - const command = terminal.capabilities.get(TerminalCapability.CommandDetection)?.commands.find(cmd => cmd.id === this._resource.query); + const command = terminal.capabilities.get(TerminalCapability.CommandDetection)?.commands.find(cmd => cmd.id === this._resource.query.replace('command=', '')); if (!command) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index 0f291521cfa..e5f98cdd01f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -280,9 +280,6 @@ export class TerminalCommandAttachmentWidget extends AbstractChatAttachmentWidge this._register(createTerminalCommandElements(this.element, attachment, ariaLabel, this.hoverService, clickHandler)); this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, async (e: KeyboardEvent) => { - if ((e.target as HTMLElement | null)?.closest('.monaco-button')) { - return; - } const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { dom.EventHelper.stop(e, true); @@ -307,15 +304,14 @@ function createTerminalCommandElements( element.ariaLabel = ariaLabel; element.style.cursor = 'pointer'; - const pillIcon = dom.$('div.chat-attached-context-pill', {}, dom.$('span.codicon.codicon-terminal')); + const terminalIconSpan = dom.$('span'); + terminalIconSpan.classList.add(...ThemeIcon.asClassNameArray(Codicon.terminal)); + const pillIcon = dom.$('div.chat-attached-context-pill', {}, terminalIconSpan); const textLabel = dom.$('span.chat-attached-context-custom-text', {}, attachment.command); element.appendChild(pillIcon); element.appendChild(textLabel); disposable.add(dom.addDisposableListener(element, dom.EventType.CLICK, e => { - if ((e.target as HTMLElement | null)?.closest('.monaco-button')) { - return; - } void clickHandler(); })); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 1bc2d22031b..c408a6f182e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -775,7 +775,7 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance) { this.revealTerminal(instance); const commands = instance.capabilities.get(TerminalCapability.CommandDetection)?.commands; - const relevantCommand = commands?.find(c => c.id === resource.query); + const relevantCommand = commands?.find(c => c.id === resource.query.replace('command=', '')); if (relevantCommand) { instance.xterm?.markTracker.revealCommand(relevantCommand); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts index ac7c34e4fa5..d8662856b8d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts @@ -27,7 +27,7 @@ export function getTerminalUri(workspaceId: string, instanceId: number, title?: scheme: Schemas.vscodeTerminal, path: `/${workspaceId}/${instanceId}`, fragment: title || undefined, - query: commandId + query: `command=${commandId}` }); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index e59ac53d462..a0893d5a3dd 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -405,8 +405,9 @@ export class DecorationAddon extends Disposable implements ITerminalAddon, IDeco }), dom.addDisposableListener(element, dom.EventType.CONTEXT_MENU, async (e) => { e.stopImmediatePropagation(); + const chatActions = await this._getCommandActions(command); const actions = this._getContextMenuActions(); - this._contextMenuService.showContextMenu({ getAnchor: () => element, getActions: () => actions }); + this._contextMenuService.showContextMenu({ getAnchor: () => element, getActions: () => [...actions, ...chatActions] }); }), ]; } From be0da6c3ce38733592a5b33a258af6ccd56ddcaa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:17:51 -0700 Subject: [PATCH 1638/4355] Use correct arch --- .../chatAgentTools/browser/treeSitterCommandParser.ts | 3 ++- .../test/electron-browser/runInTerminalTool.test.ts | 3 ++- .../test/electron-browser/treeSitterCommandParser.test.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index f262bd75bd6..eda2b6b43e2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -5,6 +5,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { derived, waitForState } from '../../../../../base/common/observable.js'; +import { arch } from '../../../../../base/common/process.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; import type { Language, Parser, Query, QueryCapture } from '@vscode/tree-sitter-wasm'; @@ -84,7 +85,7 @@ export class TreeSitterCommandParser { private _throwIfCanCrash(languageId: TreeSitterCommandParserLanguage) { // TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 if ( - (process.arch === 'arm' || process.arch === 'arm64') && + (arch === 'arm' || arch === 'arm64') && languageId === TreeSitterCommandParserLanguage.PowerShell ) { throw new Error('powershell grammar is not supported on arm or arm64'); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 74ac371c8d8..0b2958cc989 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -31,6 +31,7 @@ import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; +import { arch } from '../../../../../../base/common/process.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -45,7 +46,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { } // TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 -(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('RunInTerminalTool', () => { +(arch === 'arm' || arch === 'arm64' ? suite.skip : suite)('RunInTerminalTool', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index f8872eb9da0..3fadbc9bb16 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -14,9 +14,10 @@ import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../../browser/treeSitterCommandParser.js'; +import { arch } from '../../../../../../base/common/process.js'; // TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 -(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('TreeSitterCommandParser', () => { +(arch === 'arm' || arch === 'arm64' ? suite.skip : suite)('TreeSitterCommandParser', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; From 001632a7f7cf583404632252531149bd7bd3ddff Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:30:03 -0700 Subject: [PATCH 1639/4355] Fix weird typings Fixes an overly generic eslint selector. This lets us remove a weird typing workaround --- .../code-no-potentially-unsafe-disposables.ts | 4 ++-- .../workbench/contrib/chat/browser/actions/chatActions.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts b/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts index 69976275051..077ad081901 100644 --- a/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts +++ b/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts @@ -28,10 +28,10 @@ export = new class implements eslint.Rule.RuleModule { } return { - 'VariableDeclaration[kind!="const"] NewExpression[callee.name="DisposableStore"]': checkVariableDeclaration, + 'VariableDeclaration[kind!="const"] > VariableDeclarator > NewExpression[callee.name="DisposableStore"]': checkVariableDeclaration, 'PropertyDefinition[readonly!=true][typeAnnotation.typeAnnotation.typeName.name=/DisposableStore|MutableDisposable/]': checkProperty, - 'PropertyDefinition[readonly!=true] NewExpression[callee.name=/DisposableStore|MutableDisposable/]': checkProperty, + 'PropertyDefinition[readonly!=true] > NewExpression[callee.name=/DisposableStore|MutableDisposable/]': checkProperty, }; } }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 76c524e468a..c58d381ac9c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -638,7 +638,7 @@ export function registerChatActions() { picker.show(); }; - private showIntegratedPicker = async ( + private async showIntegratedPicker( chatService: IChatService, quickInputService: IQuickInputService, commandService: ICommandService, @@ -650,7 +650,7 @@ export function registerChatActions() { menuService: IMenuService, showAllChats: boolean = false, showAllAgents: boolean = false - ) => { + ) { const clearChatHistoryButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(Codicon.clearAll), tooltip: localize('interactiveSession.history.clear', "Clear All Workspace Chats"), @@ -835,7 +835,7 @@ export function registerChatActions() { }; }; - const store = new (DisposableStore as { new(): DisposableStore })(); + const store = new DisposableStore(); const picker = store.add(quickInputService.createQuickPick({ useSeparators: true })); picker.title = (showAllChats || showAllAgents) ? localize('interactiveSession.history.titleAll', "All Workspace Chat History") : @@ -990,7 +990,7 @@ export function registerChatActions() { store.add(picker.onDidHide(() => store.dispose())); picker.show(); - }; + } async run(accessor: ServicesAccessor) { const chatService = accessor.get(IChatService); From 981e9f583f74c5ee190c074a3da236b0fda43860 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:37:48 -0700 Subject: [PATCH 1640/4355] Pull arch from right place --- .../test/electron-browser/commandSimplifier.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts index 2c3e2d77574..69f97c7dc70 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandSimplifier.test.ts @@ -22,9 +22,10 @@ import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { FileService } from '../../../../../../platform/files/common/fileService.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { TreeSitterLibraryService } from '../../../../../services/treeSitter/browser/treeSitterLibraryService.js'; +import { arch } from '../../../../../../base/common/process.js'; // TODO: The powershell grammar can cause an OOM crash on arm https://github.com/microsoft/vscode/issues/273177 -(process.arch === 'arm' || process.arch === 'arm64' ? suite.skip : suite)('command re-writing', () => { +(arch === 'arm' || arch === 'arm64' ? suite.skip : suite)('command re-writing', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; From 479d07df8b45ffa70601525f384a14b03c54b6a7 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 15:16:09 -0700 Subject: [PATCH 1641/4355] notebooks working --- plan.md | 483 ---------------- .../chatEditingCheckpointTimelineImpl.ts | 35 +- .../chatEditingModifiedNotebookEntry.ts | 5 +- .../chatEditing/chatEditingOperations.ts | 5 + .../browser/chatEditing/chatEditingSession.ts | 21 +- .../chatEditing/chatEditingTimeline.ts | 544 ------------------ .../test/browser/chatEditingTimeline.test.ts | 507 ---------------- .../common/services/notebookWebWorker.ts | 2 +- 8 files changed, 52 insertions(+), 1550 deletions(-) delete mode 100644 plan.md delete mode 100644 src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts delete mode 100644 src/vs/workbench/contrib/chat/test/browser/chatEditingTimeline.test.ts diff --git a/plan.md b/plan.md deleted file mode 100644 index d5e0a017f1d..00000000000 --- a/plan.md +++ /dev/null @@ -1,483 +0,0 @@ -connor4312: Currently the chatEditingSession and its timeline store the state of changes based on snapshots of the working set. However, this is not good because it does not accurately represent file deletions, renames, and modifications. I think there are also some bugs with it. Create a plan to architect a new version of the edit session timline. Roughly: - -1. It should have a 'checkpoint' feature that are used to implement navigation to and from undo stops. It should also be aware of requests (edits can be tagged with a requestId) -2. The first time a file is edited within for a request, we should 'snapshot' its initial content. But then each subsequent modification should be stored _as an edit_ to that file. -3. We should be able to represent file creations, deletions, and renames as operations. In addition to text and notebook edits. -4. Undo/redo is implemented by navigation to the previous or subsequent checkpoint. To implement this, we see what file-level operations (rename/delete/create) happened in the meantime, and undo those. Then for files that had edits, we actually take the last snapshot and then replay edits to that content to get to the snapshotted state. - -Note that the new edit timeline should not actually use the existing _createSnapshot etc. functionality at all. - -Research and create a plan on how to implement this. Let me know if you have any open questions or ambiguities. - -GitHub Copilot: I'll analyze the current chat editing session architecture and create a plan for a new timeline system. Let me first research the existing implementation to understand the current challenges. - -Created 7 todos - -Starting (1/7) *Research current timeline implementation* - -Read [](file:///c%3A/Users/conno/Github/vscode/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts) - -Starting (2/7) *Research file entry types and operations* - -Read [](file:///c%3A/Users/conno/Github/vscode/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts) - -Read [](file:///c%3A/Users/conno/Github/vscode/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts#1-1), lines 1 to 101 - -Starting (3/7) *Analyze current snapshot/restore logic* - -Let me examine the snapshot creation and restoration logic in the main session file: - -Searched text for `_createSnapshot|_restoreSnapshot|createSnapshot|restoreFromSnapshot` (`**/*.ts`), 20 results - -Read [](file:///c%3A/Users/conno/Github/vscode/src/vs/workbench/contrib/chat/common/chatEditingService.ts#1-1), lines 1 to 151 - -Starting (4/7) *Design checkpoint-based timeline architecture* - -Now I have a comprehensive understanding of the current architecture. Let me design the new checkpoint-based timeline system. - -## Analysis of Current System Limitations - -Based on my research, the current system has several issues: - -1. **Snapshot-based approach**: The current timeline stores complete snapshots of file contents at each undo stop, which doesn't accurately represent operations like file deletions, renames, or creation. - -2. **Inefficient storage**: Full snapshots consume significant memory and disk space, especially for large files with minor changes. - -3. **Limited operation tracking**: The system only tracks text content changes, not file system operations like create/delete/rename. - -4. **Complex restore logic**: `_restoreSnapshot` has to completely recreate the file state, which can be error-prone with file operations. - -5. **Timeline complexity**: The current `ChatEditingTimeline` uses a complex linear history with snapshot indices that's difficult to reason about. - -Let me now design a new checkpoint-based timeline architecture: - -Starting (5/7) *Design file operation representation* - -## New Checkpoint-Based Timeline Architecture - -Here's my comprehensive plan for redesigning the edit session timeline: - -### 1. Core Data Structures - -```typescript -// Base operation interface -interface IFileOperation { - readonly type: FileOperationType; - readonly uri: URI; - readonly requestId: string; - readonly timestamp: number; - readonly operationId: string; // unique identifier -} - -enum FileOperationType { - Create = 'create', - Delete = 'delete', - Rename = 'rename', - TextEdit = 'textEdit', - NotebookEdit = 'notebookEdit' -} - -// Specific operation types -interface IFileCreateOperation extends IFileOperation { - readonly type: FileOperationType.Create; - readonly initialContent: string; - readonly languageId: string; -} - -interface IFileDeleteOperation extends IFileOperation { - readonly type: FileOperationType.Delete; - readonly finalContent: string; // content before deletion - readonly languageId: string; -} - -interface IFileRenameOperation extends IFileOperation { - readonly type: FileOperationType.Rename; - readonly oldUri: URI; - readonly newUri: URI; -} - -interface ITextEditOperation extends IFileOperation { - readonly type: FileOperationType.TextEdit; - readonly edits: TextEdit[]; - readonly contentBefore: string; // for diff computation - readonly contentAfter: string; -} - -interface INotebookEditOperation extends IFileOperation { - readonly type: FileOperationType.NotebookEdit; - readonly cellEdits: ICellEditOperation[]; - readonly notebookContentBefore: string; - readonly notebookContentAfter: string; -} - -// Checkpoint represents a stable state that can be navigated to -interface ICheckpoint { - readonly checkpointId: string; - readonly requestId: string | undefined; // undefined for initial state - readonly undoStopId: string | undefined; - readonly timestamp: number; - readonly label: string; // for UI display - readonly operations: readonly IFileOperation[]; // operations that led to this checkpoint -} - -// File baseline - the first snapshot taken when a file is first edited in a request -interface IFileBaseline { - readonly uri: URI; - readonly requestId: string; - readonly content: string; - readonly languageId: string; - readonly timestamp: number; -} -``` - -Starting (6/7) *Design undo/redo implementation* - -### 2. New Timeline Architecture - -```typescript -interface IChatEditingCheckpointTimeline { - // Navigation - readonly currentCheckpoint: IObservable; - readonly canUndo: IObservable; - readonly canRedo: IObservable; - - // Checkpoint management - createCheckpoint(requestId: string | undefined, undoStopId: string | undefined, label: string): void; - navigateToCheckpoint(checkpointId: string): Promise; - undoToLastCheckpoint(): Promise; - redoToNextCheckpoint(): Promise; - - // Operation tracking - recordFileOperation(operation: IFileOperation): void; - getOperationsSince(checkpointId: string): readonly IFileOperation[]; - getOperationsBetween(fromCheckpointId: string, toCheckpointId: string): readonly IFileOperation[]; - - // File baselines - recordFileBaseline(baseline: IFileBaseline): void; - getFileBaseline(uri: URI, requestId: string): IFileBaseline | undefined; - - // State reconstruction - reconstructFileState(uri: URI, targetCheckpointId: string): Promise; - - // Persistence - getStateForPersistence(): IChatEditingTimelineState; - restoreFromState(state: IChatEditingTimelineState): void; -} - -interface IReconstructedFileState { - readonly exists: boolean; - readonly content: string | undefined; - readonly languageId: string | undefined; - readonly uri: URI; // final URI after any renames -} - -interface IChatEditingTimelineState { - readonly checkpoints: readonly ICheckpoint[]; - readonly currentCheckpointId: string | undefined; - readonly fileBaselines: ReadonlyMap; // key: `${uri}::${requestId}` - readonly operations: readonly IFileOperation[]; -} -``` - -### 3. Timeline Implementation Strategy - -The new timeline will work as follows: - -1. **Operation Recording**: Instead of storing full snapshots, record discrete operations as they happen -2. **Baseline Capture**: When a file is first edited in a request, capture its initial state as a baseline -3. **Checkpoint Creation**: Create checkpoints at undo stops that reference the operations leading to that state -4. **State Reconstruction**: To restore to a checkpoint, start from the appropriate baseline and replay operations - -### 4. Undo/Redo Implementation - -```typescript -class ChatEditingCheckpointTimeline implements IChatEditingCheckpointTimeline { - - async navigateToCheckpoint(targetCheckpointId: string): Promise { - const targetCheckpoint = this.findCheckpoint(targetCheckpointId); - if (!targetCheckpoint) { - throw new Error(`Checkpoint ${targetCheckpointId} not found`); - } - - // 1. Get all affected files by analyzing operations between current and target - const affectedFiles = this.getAffectedFilesBetweenCheckpoints( - this.currentCheckpointId, - targetCheckpointId - ); - - // 2. For each affected file, reconstruct its state at the target checkpoint - const reconstructions = new Map(); - for (const uri of affectedFiles) { - const reconstructed = await this.reconstructFileState(uri, targetCheckpointId); - reconstructions.set(uri, reconstructed); - } - - // 3. Apply all file system operations (creates, deletes, renames) first - await this.applyFileSystemChanges(reconstructions); - - // 4. Apply content changes to existing files - await this.applyContentChanges(reconstructions); - - // 5. Update current checkpoint - this.setCurrentCheckpoint(targetCheckpointId); - } - - async reconstructFileState(uri: URI, targetCheckpointId: string): Promise { - const targetCheckpoint = this.findCheckpoint(targetCheckpointId); - - // Find the appropriate baseline (most recent one before target checkpoint) - const baseline = this.findBaselineForFile(uri, targetCheckpoint.requestId); - if (!baseline) { - return { exists: false, content: undefined, languageId: undefined, uri }; - } - - // Get all operations for this file from baseline to target checkpoint - const operations = this.getFileOperationsInRange(uri, baseline.requestId, targetCheckpointId); - - // Replay operations to reconstruct state - return this.replayOperations(baseline, operations, targetCheckpointId); - } - - private async replayOperations( - baseline: IFileBaseline, - operations: readonly IFileOperation[], - targetCheckpointId: string - ): Promise { - let currentState: IReconstructedFileState = { - exists: true, - content: baseline.content, - languageId: baseline.languageId, - uri: baseline.uri - }; - - for (const operation of operations) { - // Stop if we've reached operations beyond our target checkpoint - if (!this.isOperationBeforeCheckpoint(operation, targetCheckpointId)) { - break; - } - - currentState = await this.applyOperationToState(currentState, operation); - } - - return currentState; - } - - private async applyOperationToState( - state: IReconstructedFileState, - operation: IFileOperation - ): Promise { - switch (operation.type) { - case FileOperationType.Create: - const createOp = operation as IFileCreateOperation; - return { - exists: true, - content: createOp.initialContent, - languageId: createOp.languageId, - uri: operation.uri - }; - - case FileOperationType.Delete: - return { - exists: false, - content: undefined, - languageId: undefined, - uri: operation.uri - }; - - case FileOperationType.Rename: - const renameOp = operation as IFileRenameOperation; - return { - ...state, - uri: renameOp.newUri - }; - - case FileOperationType.TextEdit: - const textOp = operation as ITextEditOperation; - if (!state.exists) { - throw new Error('Cannot apply text edits to non-existent file'); - } - // Apply text edits to reconstruct content - const newContent = await this.applyTextEditsToContent(state.content!, textOp.edits); - return { - ...state, - content: newContent - }; - - case FileOperationType.NotebookEdit: - const notebookOp = operation as INotebookEditOperation; - if (!state.exists) { - throw new Error('Cannot apply notebook edits to non-existent file'); - } - // Apply notebook edits - const newNotebookContent = await this.applyNotebookEditsToContent( - state.content!, - notebookOp.cellEdits - ); - return { - ...state, - content: newNotebookContent - }; - } - } -} -``` - -Starting (7/7) *Create implementation plan* - -### 5. Integration with Existing Session - -```typescript -// Modified ChatEditingSession to use the new timeline -class ChatEditingSession extends Disposable implements IChatEditingSession { - private readonly _checkpointTimeline: IChatEditingCheckpointTimeline; - - constructor(/* existing params */) { - super(); - this._checkpointTimeline = this._instantiationService.createInstance(ChatEditingCheckpointTimeline); - } - - // Replace existing snapshot methods - public createCheckpoint(requestId: string, undoStop: string | undefined): void { - const label = undoStop ? `Request ${requestId} - Stop ${undoStop}` : `Request ${requestId}`; - this._checkpointTimeline.createCheckpoint(requestId, undoStop, label); - } - - public async restoreCheckpoint(requestId: string, stopId: string | undefined): Promise { - const checkpointId = this.getCheckpointId(requestId, stopId); - if (checkpointId) { - await this._checkpointTimeline.navigateToCheckpoint(checkpointId); - } - } - - async undoInteraction(): Promise { - await this._checkpointTimeline.undoToLastCheckpoint(); - } - - async redoInteraction(): Promise { - await this._checkpointTimeline.redoToNextCheckpoint(); - } - - // Modified file operation recording - private async _recordFileOperation(operation: IFileOperation): Promise { - this._checkpointTimeline.recordFileOperation(operation); - } - - private async _ensureFileBaseline(uri: URI, requestId: string): Promise { - if (!this._checkpointTimeline.getFileBaseline(uri, requestId)) { - // Capture initial content as baseline - const content = await this._getFileContent(uri); - const languageId = this._getLanguageId(uri); - - this._checkpointTimeline.recordFileBaseline({ - uri, - requestId, - content, - languageId, - timestamp: Date.now() - }); - } - } -} -``` - -## Implementation Plan - -Completed (7/7) *Create implementation plan* - -## Complete Implementation Plan - -### Phase 1: Core Data Structures and Interfaces -1. **Create new interface files**: - - `chatEditingOperations.ts` - Define `IFileOperation` and its subtypes - - `chatEditingCheckpointTimeline.ts` - Define `IChatEditingCheckpointTimeline` interface - - `chatEditingTimelineState.ts` - Define persistence interfaces - -2. **Create new classes**: - - `ChatEditingCheckpointTimeline` - Core timeline implementation - - `FileOperationRecorder` - Helper for recording operations - - `FileStateReconstructor` - Helper for replaying operations - -### Phase 2: Timeline Implementation -1. **Implement `ChatEditingCheckpointTimeline`**: - - Checkpoint creation and navigation - - Operation recording and retrieval - - File baseline management - - State reconstruction logic - - Persistence support - -2. **Create operation replay system**: - - Text edit application logic - - Notebook edit application logic - - File system operation handling - - URI tracking for renames - -### Phase 3: Integration -1. **Modify `ChatEditingSession`**: - - Replace `_timeline` with `_checkpointTimeline` - - Update `createSnapshot` → `createCheckpoint` - - Update `restoreSnapshot` → `navigateToCheckpoint` - - Modify streaming edit methods to record operations - - Update undo/redo methods - -2. **Update file entry classes**: - - Modify `AbstractChatEditingModifiedFileEntry` to record operations - - Remove snapshot creation logic - - Update edit acceptance to record operations - -### Phase 4: Migration and Testing -1. **Create migration logic**: - - Convert existing timeline state to checkpoint format - - Preserve existing undo/redo functionality during transition - -2. **Add comprehensive tests**: - - Operation recording and replay - - File system operation handling - - Complex undo/redo scenarios - - State persistence and restoration - -### Phase 5: Performance and Polish -1. **Optimize operation storage**: - - Implement operation compaction for old requests - - Add efficient querying for file operations - - Memory usage optimization - -2. **UI improvements**: - - Enhanced checkpoint labeling - - Better progress indication during navigation - - Improved error handling and recovery - -## Key Benefits of New Architecture - -1. **Accurate File Operations**: Properly represents creates, deletes, and renames -2. **Efficient Storage**: Only stores deltas instead of full file snapshots -3. **Better Debugging**: Clear operation trail for troubleshooting -4. **Scalability**: More efficient with large files and many operations -5. **Extensibility**: Easy to add new operation types in the future -6. **Consistency**: Deterministic state reconstruction through operation replay - -## Open Questions for Clarification - -1. **Operation Granularity**: Should we record individual text edits or batch them per streaming request? - -2. **Baseline Strategy**: Should we create baselines for every request or only when files are first modified in a session? - -3. **Operation Compaction**: How aggressive should we be in compacting old operations for performance? - -4. **Error Recovery**: How should we handle cases where operation replay fails (e.g., conflicting file states)? - -5. **Migration Timeline**: Should we implement this as a feature flag initially or replace the existing system entirely? - -6. **Testing Strategy**: Would you prefer to implement this incrementally with backwards compatibility, or as a complete replacement? - -This architecture provides a robust foundation for accurate file operation tracking while maintaining the existing API surface for minimal disruption to consumers. - -connor4312: 1. Record individual edits -2. Baselines for each file should be created the first time they're edited per-request, because the user might have modified the files in the interim. -3. Don't worry about compacting old operations yet. -4. Replay should never error, because you're replaying edits off an older baseline so you know they were applied already. -5. Replace the old system entirely. -6. Test is as a complete replacement. - -If this sounds good, let's start implementing! - -GitHub Copilot: diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index 77fdb20f6fb..af9b5a164b4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -23,7 +23,7 @@ import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { INotebookTextModel } from '../../../notebook/common/notebookCommon.js'; +import { CellEditType, CellUri, INotebookTextModel } from '../../../notebook/common/notebookCommon.js'; import { INotebookEditorModelResolverService } from '../../../notebook/common/notebookEditorModelResolverService.js'; import { INotebookService } from '../../../notebook/common/notebookService.js'; import { IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js'; @@ -422,11 +422,12 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } private _getFileOperationsInRange(uri: URI, fromEpoch: number, toEpoch: number): readonly FileOperation[] { - return this._operations.get().filter(op => - isEqual(op.uri, uri) && - op.epoch >= fromEpoch && - op.epoch <= toEpoch - ).sort((a, b) => a.epoch - b.epoch); + return this._operations.get().filter(op => { + const cellUri = CellUri.parse(op.uri); + return op.epoch >= fromEpoch && + op.epoch < toEpoch && + (isEqual(op.uri, uri) || (cellUri && isEqual(cellUri.notebook, uri))); + }).sort((a, b) => a.epoch - b.epoch); } private async _replayOperations(baseline: IFileBaseline, operations: readonly FileOperation[]): Promise { @@ -460,6 +461,10 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh private async _applyOperationToState(state: IReconstructedFileStateWithNotebook, operation: FileOperation, telemetryInfo: IModifiedEntryTelemetryInfo): Promise { switch (operation.type) { case FileOperationType.Create: { + if (state.exists && state.notebook) { + state.notebook.dispose(); + } + let notebook: INotebookTextModel | undefined; if (operation.notebookViewType) { notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(operation.notebookViewType); @@ -494,17 +499,29 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh uri: operation.newUri }; - case FileOperationType.TextEdit: + case FileOperationType.TextEdit: { if (!state.exists) { throw new Error('Cannot apply text edits to non-existent file'); } + const nbCell = operation.cellIndex !== undefined && state.notebook?.cells.at(operation.cellIndex); + if (nbCell) { + const newContent = this._applyTextEditsToContent(nbCell.getValue(), operation.edits); + state.notebook!.applyEdits([{ + editType: CellEditType.Replace, + index: operation.cellIndex, + count: 1, + cells: [{ cellKind: nbCell.cellKind, language: nbCell.language, mime: nbCell.language, source: newContent, outputs: nbCell.outputs }] + }], true, undefined, () => undefined, undefined); + return state; + } + // Apply text edits using a temporary text model return { ...state, content: this._applyTextEditsToContent(state.content, operation.edits) }; - + } case FileOperationType.NotebookEdit: if (!state.exists) { throw new Error('Cannot apply notebook edits to non-existent file'); @@ -577,7 +594,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh // Text and notebook edits don't affect file system structure case FileOperationType.TextEdit: case FileOperationType.NotebookEdit: - urisToRestore.add(operation.uri); + urisToRestore.add(CellUri.parse(operation.uri)?.notebook ?? operation.uri); break; default: diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index 177029583a4..0ecca5ae333 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -222,6 +222,10 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie this._changesCount.set(countChanges(diffs), undefined); } + getIndexOfCellHandle(handle: number) { + return this.modifiedModel.cells.findIndex(c => c.handle === handle); + } + private computeRequestId: number = 0; async initializeModelsFromDiff() { const id = ++this.computeRequestId; @@ -253,7 +257,6 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie updateCellDiffInfo(cellsDiffInfo: ICellDiffInfo[], transcation: ITransaction | undefined) { this._cellsDiffInfo.set(sortCellChanges(cellsDiffInfo), transcation); this._changesCount.set(countChanges(cellsDiffInfo), transcation); - } mirrorNotebookEdits(e: NotebookTextModelChangedEvent) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts index f31d51af6f9..3f0f3fb5ef9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts @@ -58,6 +58,11 @@ export interface IFileRenameOperation extends IFileOperation { export interface ITextEditOperation extends IFileOperation { readonly type: FileOperationType.TextEdit; readonly edits: readonly TextEdit[]; + /** + * For cell URIs, the cell index that was edited. Needed because the original + * edit URI only contains the `handle` which is not portable between notebooks. + */ + readonly cellIndex?: number; } /** diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 2dbc04ff3ce..6c301cf4567 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -430,9 +430,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio await this._timeline.redoToNextCheckpoint(); } - private async _recordEditOperations(resource: URI, edits: (TextEdit | ICellEditOperation)[], responseModel: IChatResponseModel): Promise { + private async _recordEditOperations(entry: AbstractChatEditingModifiedFileEntry, resource: URI, edits: (TextEdit | ICellEditOperation)[], responseModel: IChatResponseModel): Promise { // Determine if these are text edits or notebook edits - const isNotebookEdits = edits.length > 0 && 'cell' in edits[0]; + const isNotebookEdits = edits.length > 0 && 'cells' in edits[0]; if (isNotebookEdits) { // Record notebook edit operation @@ -445,14 +445,25 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio cellEdits: notebookEdits }); } else { - // Record text edit operation + let cellIndex: number | undefined; + if (entry instanceof ChatEditingModifiedNotebookEntry) { + const cellUri = CellUri.parse(resource); + if (cellUri) { + const i = entry.getIndexOfCellHandle(cellUri.handle); + if (i !== -1) { + cellIndex = i; + } + } + } + const textEdits = edits as TextEdit[]; this._timeline.recordFileOperation({ type: FileOperationType.TextEdit, uri: resource, requestId: responseModel.requestId, epoch: this._timeline.incrementEpoch(), - edits: textEdits + edits: textEdits, + cellIndex, }); } } @@ -519,7 +530,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio // Record edit operations in the timeline if there are actual edits if (textEdits.length > 0) { - await this._recordEditOperations(resource, textEdits, responseModel); + await this._recordEditOperations(entry, resource, textEdits, responseModel); } await entry.acceptAgentEdits(resource, textEdits, isLastEdits, responseModel); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts deleted file mode 100644 index e2d6d2ffb15..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTimeline.ts +++ /dev/null @@ -1,544 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - - -import { equals as arraysEqual, binarySearch2 } from '../../../../../base/common/arrays.js'; -import { findLast } from '../../../../../base/common/arraysFind.js'; -import { Iterable } from '../../../../../base/common/iterator.js'; -import { DisposableStore, thenRegisterOrDispose } from '../../../../../base/common/lifecycle.js'; -import { ResourceMap } from '../../../../../base/common/map.js'; -import { equals as objectsEqual } from '../../../../../base/common/objects.js'; -import { derived, derivedOpts, IObservable, ITransaction, ObservablePromise, observableValue, transaction } from '../../../../../base/common/observable.js'; -import { isEqual } from '../../../../../base/common/resources.js'; -import { URI } from '../../../../../base/common/uri.js'; -import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; -import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { observableConfigValue } from '../../../../../platform/observable/common/platformObservableUtils.js'; -import { IEditSessionEntryDiff, ISnapshotEntry } from '../../common/chatEditingService.js'; -import { IChatRequestDisablement } from '../../common/chatModel.js'; -import { AbstractChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; -import { ChatEditingModifiedNotebookEntry } from './chatEditingModifiedNotebookEntry.js'; -import { IChatEditingSessionSnapshot, IChatEditingSessionStop } from './chatEditingSessionStorage.js'; -import { ChatEditingModifiedNotebookDiff } from './notebook/chatEditingModifiedNotebookDiff.js'; - -/** - * Timeline/undo-redo stack for ChatEditingSession. - */ -export class ChatEditingTimeline { - public static readonly POST_EDIT_STOP_ID = 'd19944f6-f46c-4e17-911b-79a8e843c7c0'; // randomly generated - public static createEmptySnapshot(undoStop: string | undefined): IChatEditingSessionStop { - return { - stopId: undoStop, - entries: new ResourceMap(), - }; - } - - private readonly _linearHistory = observableValue(this, []); - private readonly _linearHistoryIndex = observableValue(this, 0); - - private readonly _diffsBetweenStops = new Map>(); - private readonly _fullDiffs = new Map>(); - private readonly _ignoreTrimWhitespaceObservable: IObservable; - - public readonly canUndo: IObservable; - public readonly canRedo: IObservable; - - public readonly requestDisablement = derivedOpts({ equalsFn: (a, b) => arraysEqual(a, b, objectsEqual) }, reader => { - const history = this._linearHistory.read(reader); - const index = this._linearHistoryIndex.read(reader); - const undoRequests: IChatRequestDisablement[] = []; - for (const entry of history) { - if (!entry.requestId) { - // ignored - } else if (entry.startIndex >= index) { - undoRequests.push({ requestId: entry.requestId }); - } else if (entry.startIndex + entry.stops.length > index) { - undoRequests.push({ requestId: entry.requestId, afterUndoStop: entry.stops[(index - 1) - entry.startIndex].stopId }); - } - } - return undoRequests; - }); - - constructor( - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @ITextModelService private readonly _textModelService: ITextModelService, - ) { - this._ignoreTrimWhitespaceObservable = observableConfigValue('diffEditor.ignoreTrimWhitespace', true, configurationService); - - this.canUndo = derived(r => { - const linearHistoryIndex = this._linearHistoryIndex.read(r); - return linearHistoryIndex > 1; - }); - this.canRedo = derived(r => { - const linearHistoryIndex = this._linearHistoryIndex.read(r); - return linearHistoryIndex < getMaxHistoryIndex(this._linearHistory.read(r)); - }); - } - - /** - * Restore the timeline from a saved state (history array and index). - */ - public restoreFromState(state: { history: readonly IChatEditingSessionSnapshot[]; index: number }, tx: ITransaction): void { - this._linearHistory.set(state.history, tx); - this._linearHistoryIndex.set(state.index, tx); - } - - /** - * Get the snapshot and history index for restoring, given requestId and stopId. - * If requestId is undefined, returns undefined (pending snapshot is managed by session). - */ - public getSnapshotForRestore(requestId: string | undefined, stopId: string | undefined): { stop: IChatEditingSessionStop; apply(): void } | undefined { - if (requestId === undefined) { - return undefined; - } - const stopRef = this.findEditStop(requestId, stopId); - if (!stopRef) { - return undefined; - } - - // When rolling back to the first snapshot taken for a request, mark the - // entire request as undone. - const toIndex = stopRef.stop.stopId === undefined ? stopRef.historyIndex : stopRef.historyIndex + 1; - return { - stop: stopRef.stop, - apply: () => this._linearHistoryIndex.set(toIndex, undefined) - }; - } - - /** - * Ensures the state of the file in the given snapshot matches the current - * state of the {@param entry}. This is used to handle concurrent file edits. - * - * Given the case of two different edits, we will place and undo stop right - * before we `textEditGroup` in the underlying markdown stream, but at the - * time those are added the edits haven't been made yet, so both files will - * simply have the unmodified state. - * - * This method is called after each edit, so after the first file finishes - * being edits, it will update its content in the second undo snapshot such - * that it can be undone successfully. - * - * We ensure that the same file is not concurrently edited via the - * {@link _streamingEditLocks}, avoiding race conditions. - * - * @param next If true, this will edit the snapshot _after_ the undo stop - */ - public ensureEditInUndoStopMatches( - requestId: string, - undoStop: string | undefined, - entry: Pick, - next: boolean, - tx: ITransaction | undefined - ) { - const history = this._linearHistory.get(); - const snapIndex = history.findIndex((s) => s.requestId === requestId); - if (snapIndex === -1) { - return; - } - - const snap = { ...history[snapIndex] }; - let stopIndex = snap.stops.findIndex((s) => s.stopId === undoStop); - if (stopIndex === -1) { - return; - } - - let linearHistoryIndexIncr = 0; - if (next) { - if (stopIndex === snap.stops.length - 1) { - if (snap.stops[stopIndex].stopId === ChatEditingTimeline.POST_EDIT_STOP_ID) { - throw new Error('cannot duplicate post-edit stop'); - } - - snap.stops = snap.stops.concat(ChatEditingTimeline.createEmptySnapshot(ChatEditingTimeline.POST_EDIT_STOP_ID)); - linearHistoryIndexIncr++; - } - stopIndex++; - } - - const stop = snap.stops[stopIndex]; - if (entry.equalsSnapshot(stop.entries.get(entry.modifiedURI))) { - return; - } - - const newMap = new ResourceMap(stop.entries); - newMap.set(entry.modifiedURI, entry.createSnapshot(requestId, stop.stopId)); - - const newStop = snap.stops.slice(); - newStop[stopIndex] = { ...stop, entries: newMap }; - snap.stops = newStop; - - const newHistory = history.slice(); - newHistory[snapIndex] = snap; - - this._linearHistory.set(newHistory, tx); - if (linearHistoryIndexIncr) { - this._linearHistoryIndex.set(this._linearHistoryIndex.get() + linearHistoryIndexIncr, tx); - } - } - - /** - * Get the undo snapshot (previous in history), or undefined if at start. - * If the timeline is at the end of the history, it will return the last stop - * pushed into the history. - */ - public getUndoSnapshot(): { stop: IChatEditingSessionStop; apply(): void } | undefined { - return this.getUndoRedoSnapshot(-1); - } - - /** - * Get the redo snapshot (next in history), or undefined if at end. - */ - public getRedoSnapshot(): { stop: IChatEditingSessionStop; apply(): void } | undefined { - return this.getUndoRedoSnapshot(1); - } - - private getUndoRedoSnapshot(direction: number) { - let idx = this._linearHistoryIndex.get() - 1; - const max = getMaxHistoryIndex(this._linearHistory.get()); - const startEntry = this.getHistoryEntryByLinearIndex(idx); - let entry = startEntry; - if (!startEntry) { - return undefined; - } - - do { - idx += direction; - entry = this.getHistoryEntryByLinearIndex(idx); - } while ( - idx + direction < max && - idx + direction >= 0 && - entry && - !(direction === -1 && entry.entry.requestId !== startEntry.entry.requestId) && - !stopProvidesNewData(startEntry.stop, entry.stop) - ); - - if (entry) { - return { stop: entry.stop, apply: () => this._linearHistoryIndex.set(idx + 1, undefined) }; - } - - return undefined; - } - - /** - * Get the state for persistence (history and index). - */ - public getStateForPersistence(): { history: readonly IChatEditingSessionSnapshot[]; index: number } { - return { history: this._linearHistory.get(), index: this._linearHistoryIndex.get() }; - } - - private findSnapshot(requestId: string): IChatEditingSessionSnapshot | undefined { - return this._linearHistory.get().find((s) => s.requestId === requestId); - } - - private findEditStop(requestId: string, undoStop: string | undefined) { - const snapshot = this.findSnapshot(requestId); - if (!snapshot) { - return undefined; - } - const idx = snapshot.stops.findIndex((s) => s.stopId === undoStop); - return idx === -1 ? undefined : { stop: snapshot.stops[idx], snapshot, historyIndex: snapshot.startIndex + idx }; - } - - private getHistoryEntryByLinearIndex(index: number) { - const history = this._linearHistory.get(); - const searchedIndex = binarySearch2(history.length, (e) => history[e].startIndex - index); - const entry = history[searchedIndex < 0 ? (~searchedIndex) - 1 : searchedIndex]; - if (!entry || index - entry.startIndex >= entry.stops.length) { - return undefined; - } - return { - entry, - stop: entry.stops[index - entry.startIndex] - }; - } - - public pushSnapshot(requestId: string, undoStop: string | undefined, snapshot: IChatEditingSessionStop) { - const linearHistoryPtr = this._linearHistoryIndex.get(); - const newLinearHistory: IChatEditingSessionSnapshot[] = []; - for (const entry of this._linearHistory.get()) { - if (entry.startIndex >= linearHistoryPtr) { - break; - } else if (linearHistoryPtr - entry.startIndex < entry.stops.length) { - newLinearHistory.push({ requestId: entry.requestId, stops: entry.stops.slice(0, linearHistoryPtr - entry.startIndex), startIndex: entry.startIndex }); - } else { - newLinearHistory.push(entry); - } - } - - const lastEntry = newLinearHistory.at(-1); - if (requestId && lastEntry?.requestId === requestId) { - const hadPostEditStop = lastEntry.stops.at(-1)?.stopId === ChatEditingTimeline.POST_EDIT_STOP_ID && undoStop; - if (hadPostEditStop) { - const rebaseUri = (uri: URI) => uri.with({ query: uri.query.replace(ChatEditingTimeline.POST_EDIT_STOP_ID, undoStop) }); - for (const [uri, prev] of lastEntry.stops.at(-1)!.entries) { - snapshot.entries.set(uri, { ...prev, snapshotUri: rebaseUri(prev.snapshotUri), resource: rebaseUri(prev.resource) }); - } - } - newLinearHistory[newLinearHistory.length - 1] = { - ...lastEntry, - stops: [...hadPostEditStop ? lastEntry.stops.slice(0, -1) : lastEntry.stops, snapshot] - }; - } else { - newLinearHistory.push({ requestId, startIndex: lastEntry ? lastEntry.startIndex + lastEntry.stops.length : 0, stops: [snapshot] }); - } - - transaction((tx) => { - const last = newLinearHistory[newLinearHistory.length - 1]; - this._linearHistory.set(newLinearHistory, tx); - this._linearHistoryIndex.set(last.startIndex + last.stops.length, tx); - }); - } - - /** - * Gets diff for text entries between stops. - * @param entriesContent Observable that observes either snapshot entry - * @param modelUrisObservable Observable that observes only the snapshot URIs. - */ - private _entryDiffBetweenTextStops( - entriesContent: IObservable<{ before: ISnapshotEntry; after: ISnapshotEntry } | undefined>, - modelUrisObservable: IObservable<[URI, URI] | undefined>, - ): IObservable | undefined> { - const modelRefsPromise = derived(this, (reader) => { - const modelUris = modelUrisObservable.read(reader); - if (!modelUris) { return undefined; } - - const store = reader.store.add(new DisposableStore()); - const promise = Promise.all(modelUris.map(u => this._textModelService.createModelReference(u))).then(refs => { - if (store.isDisposed) { - refs.forEach(r => r.dispose()); - } else { - refs.forEach(r => store.add(r)); - } - - return refs; - }); - - return new ObservablePromise(promise); - }); - - return derived((reader): ObservablePromise | undefined => { - const refs2 = modelRefsPromise.read(reader)?.promiseResult.read(reader); - const refs = refs2?.data; - if (!refs) { - return; - } - - const entries = entriesContent.read(reader); // trigger re-diffing when contents change - - if (entries?.before && ChatEditingModifiedNotebookEntry.canHandleSnapshot(entries.before)) { - const diffService = this._instantiationService.createInstance(ChatEditingModifiedNotebookDiff, entries.before, entries.after); - return new ObservablePromise(diffService.computeDiff()); - - } - const ignoreTrimWhitespace = this._ignoreTrimWhitespaceObservable.read(reader); - const promise = this._computeDiff(refs[0].object.textEditorModel.uri, refs[1].object.textEditorModel.uri, ignoreTrimWhitespace); - - return new ObservablePromise(promise); - }); - } - - private _createDiffBetweenStopsObservable(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable { - const entries = derivedOpts( - { - equalsFn: (a, b) => snapshotsEqualForDiff(a?.before, b?.before) && snapshotsEqualForDiff(a?.after, b?.after), - }, - reader => { - const stops = requestId ? - getCurrentAndNextStop(requestId, stopId, this._linearHistory.read(reader)) : - getFirstAndLastStop(uri, this._linearHistory.read(reader)); - if (!stops) { return undefined; } - const before = stops.current.get(uri); - const after = stops.next.get(uri); - if (!before || !after) { return undefined; } - return { before, after }; - }, - ); - - // Separate observable for model refs to avoid unnecessary disposal - const modelUrisObservable = derivedOpts<[URI, URI] | undefined>({ equalsFn: (a, b) => arraysEqual(a, b, isEqual) }, reader => { - const entriesValue = entries.read(reader); - if (!entriesValue) { return undefined; } - return [entriesValue.before.snapshotUri, entriesValue.after.snapshotUri]; - }); - - const diff = this._entryDiffBetweenTextStops(entries, modelUrisObservable); - - return derived(reader => { - return diff.read(reader)?.promiseResult.read(reader)?.data || undefined; - }); - } - - public getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined) { - if (requestId) { - const key = `${uri}\0${requestId}\0${stopId}`; - let observable = this._diffsBetweenStops.get(key); - if (!observable) { - observable = this._createDiffBetweenStopsObservable(uri, requestId, stopId); - this._diffsBetweenStops.set(key, observable); - } - - return observable; - } else { - const key = uri.toString(); - let observable = this._fullDiffs.get(key); - if (!observable) { - observable = this._createDiffBetweenStopsObservable(uri, requestId, stopId); - this._fullDiffs.set(key, observable); - } - - return observable; - } - } - - public getEntryDiffBetweenRequests(uri: URI, startRequestId: string, stopRequestId: string): IObservable { - const snapshotUris = derivedOpts<[URI | undefined, URI | undefined]>( - { equalsFn: (a, b) => arraysEqual(a, b, isEqual) }, - reader => { - const history = this._linearHistory.read(reader); - const firstSnapshotUri = this._getFirstSnapshotForUriAfterRequest(history, uri, startRequestId, true); - const lastSnapshotUri = this._getFirstSnapshotForUriAfterRequest(history, uri, stopRequestId, false); - return [firstSnapshotUri, lastSnapshotUri]; - }, - ); - const modelRefs = derived((reader) => { - const snapshots = snapshotUris.read(reader); - const firstSnapshotUri = snapshots[0]; - const lastSnapshotUri = snapshots[1]; - if (!firstSnapshotUri || !lastSnapshotUri) { - return; - } - const store = new DisposableStore(); - reader.store.add(store); - const referencesPromise = Promise.all([firstSnapshotUri, lastSnapshotUri].map(u => { - return thenRegisterOrDispose(this._textModelService.createModelReference(u), store); - })); - return new ObservablePromise(referencesPromise); - }); - const diff = derived((reader): ObservablePromise | undefined => { - const references = modelRefs.read(reader)?.promiseResult.read(reader); - const refs = references?.data; - if (!refs) { - return; - } - const ignoreTrimWhitespace = this._ignoreTrimWhitespaceObservable.read(reader); - const promise = this._computeDiff(refs[0].object.textEditorModel.uri, refs[1].object.textEditorModel.uri, ignoreTrimWhitespace); - return new ObservablePromise(promise); - }); - return derived(reader => { - return diff.read(reader)?.promiseResult.read(reader)?.data || undefined; - }); - } - - private _computeDiff(originalUri: URI, modifiedUri: URI, ignoreTrimWhitespace: boolean): Promise { - return this._editorWorkerService.computeDiff( - originalUri, - modifiedUri, - { ignoreTrimWhitespace, computeMoves: false, maxComputationTimeMs: 3000 }, - 'advanced' - ).then((diff): IEditSessionEntryDiff => { - const entryDiff: IEditSessionEntryDiff = { - originalURI: originalUri, - modifiedURI: modifiedUri, - identical: !!diff?.identical, - quitEarly: !diff || diff.quitEarly, - added: 0, - removed: 0, - }; - if (diff) { - for (const change of diff.changes) { - entryDiff.removed += change.original.endLineNumberExclusive - change.original.startLineNumber; - entryDiff.added += change.modified.endLineNumberExclusive - change.modified.startLineNumber; - } - } - return entryDiff; - }); - } - - private _getFirstSnapshotForUriAfterRequest(history: readonly IChatEditingSessionSnapshot[], uri: URI, requestId: string, inclusive: boolean): URI | undefined { - const requestIndex = history.findIndex(s => s.requestId === requestId); - if (requestIndex === -1) { return undefined; } - const processedIndex = requestIndex + (inclusive ? 0 : 1); - for (let i = processedIndex; i < history.length; i++) { - const snapshot = history[i]; - for (const stop of snapshot.stops) { - const entry = stop.entries.get(uri); - if (entry) { - return entry.snapshotUri; - } - } - } - return uri; - } -} - -function stopProvidesNewData(origin: IChatEditingSessionStop, target: IChatEditingSessionStop) { - return Iterable.some(target.entries, ([uri, e]) => origin.entries.get(uri)?.current !== e.current); -} - -function getMaxHistoryIndex(history: readonly IChatEditingSessionSnapshot[]) { - const lastHistory = history.at(-1); - return lastHistory ? lastHistory.startIndex + lastHistory.stops.length : 0; -} - -function snapshotsEqualForDiff(a: ISnapshotEntry | undefined, b: ISnapshotEntry | undefined) { - if (!a || !b) { - return a === b; - } - - return isEqual(a.snapshotUri, b.snapshotUri) && a.current === b.current; -} - -function getCurrentAndNextStop(requestId: string, stopId: string | undefined, history: readonly IChatEditingSessionSnapshot[]) { - const snapshotIndex = history.findIndex(s => s.requestId === requestId); - if (snapshotIndex === -1) { return undefined; } - const snapshot = history[snapshotIndex]; - const stopIndex = snapshot.stops.findIndex(s => s.stopId === stopId); - if (stopIndex === -1) { return undefined; } - - const currentStop = snapshot.stops[stopIndex]; - const current = currentStop.entries; - const nextStop = stopIndex < snapshot.stops.length - 1 - ? snapshot.stops[stopIndex + 1] - : undefined; - if (!nextStop) { - return undefined; - } - - return { current, currentStopId: currentStop.stopId, next: nextStop.entries, nextStopId: nextStop.stopId }; -} - -function getFirstAndLastStop(uri: URI, history: readonly IChatEditingSessionSnapshot[]) { - let firstStopWithUri: IChatEditingSessionStop | undefined; - for (const snapshot of history) { - const stop = snapshot.stops.find(s => s.entries.has(uri)); - if (stop) { - firstStopWithUri = stop; - break; - } - } - - let lastStopWithUri: ResourceMap | undefined; - let lastStopWithUriId: string | undefined; - for (let i = history.length - 1; i >= 0; i--) { - const snapshot = history[i]; - const stop = findLast(snapshot.stops, s => s.entries.has(uri)); - if (stop) { - lastStopWithUri = stop.entries; - lastStopWithUriId = stop.stopId; - break; - } - } - - if (!firstStopWithUri || !lastStopWithUri) { - return undefined; - } - - return { current: firstStopWithUri.entries, currentStopId: firstStopWithUri.stopId, next: lastStopWithUri, nextStopId: lastStopWithUriId! }; -} diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingTimeline.test.ts deleted file mode 100644 index 00e0b5c759e..00000000000 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingTimeline.test.ts +++ /dev/null @@ -1,507 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; -import { ChatEditingTimeline } from '../../browser/chatEditing/chatEditingTimeline.js'; -import { IChatEditingSessionStop } from '../../browser/chatEditing/chatEditingSessionStorage.js'; -import { transaction } from '../../../../../base/common/observable.js'; -import { IChatRequestDisablement } from '../../common/chatModel.js'; -import { ResourceMap } from '../../../../../base/common/map.js'; -import { URI } from '../../../../../base/common/uri.js'; -import { ISnapshotEntry } from '../../common/chatEditingService.js'; - -suite('ChatEditingTimeline', () => { - const ds = ensureNoDisposablesAreLeakedInTestSuite(); - let timeline: ChatEditingTimeline; - - setup(() => { - const instaService = workbenchInstantiationService(undefined, ds); - timeline = instaService.createInstance(ChatEditingTimeline); - }); - - suite('undo/redo', () => { - test('undo/redo with empty history', () => { - assert.strictEqual(timeline.getUndoSnapshot(), undefined); - assert.strictEqual(timeline.getRedoSnapshot(), undefined); - assert.strictEqual(timeline.canRedo.get(), false); - assert.strictEqual(timeline.canUndo.get(), false); - }); - }); - - function createSnapshot(stopId: string | undefined, requestId = 'req1'): IChatEditingSessionStop { - return { - stopId, - entries: stopId === undefined ? new ResourceMap() : new ResourceMap([[ - URI.file(`file:///path/to/${stopId}`), - { requestId, current: `Content for ${stopId}` } as Partial as ISnapshotEntry - ]]), - }; - } - - suite('Basic functionality', () => { - test('pushSnapshot and undo/redo navigation', () => { - // Push two snapshots - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - - // After two pushes, canUndo should be true, canRedo false - assert.strictEqual(timeline.canUndo.get(), true); - assert.strictEqual(timeline.canRedo.get(), false); - - // Undo should move back to stop1 - const undoSnap = timeline.getUndoSnapshot(); - assert.ok(undoSnap); - assert.strictEqual(undoSnap.stop.stopId, 'stop1'); - undoSnap.apply(); - assert.strictEqual(timeline.canUndo.get(), false); - assert.strictEqual(timeline.canRedo.get(), true); - - // Redo should move forward to stop2 - const redoSnap = timeline.getRedoSnapshot(); - assert.ok(redoSnap); - assert.strictEqual(redoSnap.stop.stopId, 'stop2'); - redoSnap.apply(); - assert.strictEqual(timeline.canUndo.get(), true); - assert.strictEqual(timeline.canRedo.get(), false); - }); - - test('restoreFromState restores history and index', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - const state = timeline.getStateForPersistence(); - - // Move back - timeline.getUndoSnapshot()?.apply(); - - // Restore state - transaction(tx => timeline.restoreFromState(state, tx)); - assert.strictEqual(timeline.canUndo.get(), true); - assert.strictEqual(timeline.canRedo.get(), false); - }); - - test('getSnapshotForRestore returns correct snapshot', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - - const snap = timeline.getSnapshotForRestore('req1', 'stop1'); - assert.ok(snap); - assert.strictEqual(snap.stop.stopId, 'stop1'); - snap.apply(); - - assert.strictEqual(timeline.canRedo.get(), true); - assert.strictEqual(timeline.canUndo.get(), false); - - const snap2 = timeline.getSnapshotForRestore('req1', 'stop2'); - assert.ok(snap2); - assert.strictEqual(snap2.stop.stopId, 'stop2'); - snap2.apply(); - - assert.strictEqual(timeline.canRedo.get(), false); - assert.strictEqual(timeline.canUndo.get(), true); - }); - - test('getRequestDisablement returns correct requests', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - - // Move back to first - timeline.getUndoSnapshot()?.apply(); - - const disables = timeline.requestDisablement.get(); - assert.ok(Array.isArray(disables)); - assert.ok(disables.some(d => d.requestId === 'req2')); - }); - }); - - suite('Multiple requests', () => { - test('handles multiple requests with separate snapshots', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - assert.strictEqual(timeline.canUndo.get(), true); - assert.strictEqual(timeline.canRedo.get(), false); - - // Undo should go back through requests - let undoSnap = timeline.getUndoSnapshot(); - assert.ok(undoSnap); - assert.strictEqual(undoSnap.stop.stopId, 'stop2'); - undoSnap.apply(); - - undoSnap = timeline.getUndoSnapshot(); - assert.ok(undoSnap); - assert.strictEqual(undoSnap.stop.stopId, 'stop1'); - }); - - test('handles same request with multiple stops', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req1', 'stop3', createSnapshot('stop3')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 1); - assert.strictEqual(state.history[0].stops.length, 3); - assert.strictEqual(state.history[0].requestId, 'req1'); - }); - - test('mixed requests and stops', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req2', 'stop3', createSnapshot('stop3', 'req2')); - timeline.pushSnapshot('req2', 'stop4', createSnapshot('stop4', 'req2')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 2); - assert.strictEqual(state.history[0].stops.length, 2); - assert.strictEqual(state.history[1].stops.length, 2); - }); - }); - - suite('Edge cases', () => { - test('getSnapshotForRestore with non-existent request', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - - const snap = timeline.getSnapshotForRestore('nonexistent', 'stop1'); - assert.strictEqual(snap, undefined); - }); - - test('getSnapshotForRestore with non-existent stop', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - - const snap = timeline.getSnapshotForRestore('req1', 'nonexistent'); - assert.strictEqual(snap, undefined); - }); - }); - - suite('History manipulation', () => { - test('pushing snapshots after undo truncates future history', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req1', 'stop3', createSnapshot('stop3')); - - // Undo twice - timeline.getUndoSnapshot()?.apply(); - timeline.getUndoSnapshot()?.apply(); - - // Push new snapshot - should truncate stop3 - timeline.pushSnapshot('req1', 'new_stop', createSnapshot('new_stop')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history[0].stops.length, 2); // stop1 + new_stop - assert.strictEqual(state.history[0].stops[1].stopId, 'new_stop'); - }); - - test('branching from middle of history creates new branch', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - // Undo to middle - timeline.getUndoSnapshot()?.apply(); - - // Push new request - timeline.pushSnapshot('req4', 'stop4', createSnapshot('stop4')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 3); // req1, req2, req4 - assert.strictEqual(state.history[2].requestId, 'req4'); - }); - }); - - suite('State persistence', () => { - test('getStateForPersistence returns complete state', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - - const state = timeline.getStateForPersistence(); - assert.ok(state.history); - assert.ok(typeof state.index === 'number'); - assert.strictEqual(state.history.length, 2); - assert.strictEqual(state.index, 2); - }); - - test('restoreFromState handles empty history', () => { - const emptyState = { history: [], index: 0 }; - - transaction(tx => timeline.restoreFromState(emptyState, tx)); - - assert.strictEqual(timeline.canUndo.get(), false); - assert.strictEqual(timeline.canRedo.get(), false); - }); - - test('restoreFromState with complex history', () => { - // Create complex state - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req2', 'stop3', createSnapshot('stop3', 'req2')); - - const originalState = timeline.getStateForPersistence(); - - // Create new timeline and restore - const instaService = workbenchInstantiationService(undefined, ds); - const newTimeline = instaService.createInstance(ChatEditingTimeline); - transaction(tx => newTimeline.restoreFromState(originalState, tx)); - - const restoredState = newTimeline.getStateForPersistence(); - assert.deepStrictEqual(restoredState.index, originalState.index); - assert.strictEqual(restoredState.history.length, originalState.history.length); - }); - }); - - suite('Request disablement', () => { - test('getRequestDisablement at various positions', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - // At end - no disabled requests - let disables = timeline.requestDisablement.get(); - assert.strictEqual(disables.length, 0); - - // Move back one - timeline.getUndoSnapshot()?.apply(); - disables = timeline.requestDisablement.get(); - assert.strictEqual(disables.length, 1); - assert.strictEqual(disables[0].requestId, 'req3'); - - // Move back to beginning - timeline.getUndoSnapshot()?.apply(); - timeline.getUndoSnapshot()?.apply(); - disables = timeline.requestDisablement.get(); - assert.strictEqual(disables.length, 2); - }); - - test('getRequestDisablement with mixed request/stop structure', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req2', 'stop3', createSnapshot('stop3', 'req2')); - - // Move to middle of req1 - timeline.getUndoSnapshot()?.apply(); - timeline.getUndoSnapshot()?.apply(); - - const disables = timeline.requestDisablement.get(); - assert.strictEqual(disables.length, 2); - - // Should have partial disable for req1 and full disable for req2 - const req1Disable = disables.find(d => d.requestId === 'req1'); - const req2Disable = disables.find(d => d.requestId === 'req2'); - - assert.ok(req1Disable); - assert.ok(req2Disable); - assert.ok(req1Disable.afterUndoStop); - assert.strictEqual(req2Disable.afterUndoStop, undefined); - }); - }); - - suite('Boundary conditions', () => { - test('undo/redo at boundaries', () => { - // Empty timeline - assert.strictEqual(timeline.getUndoSnapshot(), undefined); - assert.strictEqual(timeline.getRedoSnapshot(), undefined); - - // Single snapshot - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - assert.ok(timeline.getUndoSnapshot()); - assert.strictEqual(timeline.getRedoSnapshot(), undefined); - - // At beginning after undo - timeline.getUndoSnapshot()?.apply(); - assert.strictEqual(timeline.getUndoSnapshot(), undefined); - assert.ok(timeline.getRedoSnapshot()); - }); - - test('multiple undos and redos', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - // Undo all - const stops: string[] = []; - let undoSnap = timeline.getUndoSnapshot(); - while (undoSnap) { - stops.push(undoSnap.stop.stopId!); - undoSnap.apply(); - undoSnap = timeline.getUndoSnapshot(); - } - assert.deepStrictEqual(stops, ['stop2', 'stop1']); - - // Redo all - const redoStops: string[] = []; - let redoSnap = timeline.getRedoSnapshot(); - while (redoSnap) { - redoStops.push(redoSnap.stop.stopId!); - redoSnap.apply(); - redoSnap = timeline.getRedoSnapshot(); - } - assert.deepStrictEqual(redoStops, ['stop2', 'stop3']); - }); - - test('getRequestDisablement with root request ID', () => { - timeline.pushSnapshot('req1', undefined, createSnapshot(undefined)); - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - - timeline.pushSnapshot('req2', undefined, createSnapshot(undefined, 'req2')); - timeline.pushSnapshot('req2', 'stop1-2', createSnapshot('stop1-2', 'req2')); - timeline.pushSnapshot('req2', 'stop2-2', createSnapshot('stop2-2', 'req2')); - - const expected: IChatRequestDisablement[][] = [ - [{ requestId: 'req2', afterUndoStop: 'stop1-2' }], - [{ requestId: 'req2' }], - // stop2 is not in this because we're at stop2 when undoing req2 - [{ requestId: 'req1', afterUndoStop: 'stop1' }, { requestId: 'req2' }], - [{ requestId: 'req1', afterUndoStop: undefined }, { requestId: 'req2' }], - ]; - - let ei = 0; - while (timeline.canUndo.get()) { - timeline.getUndoSnapshot()!.apply(); - const actual = timeline.requestDisablement.get(); - - assert.deepStrictEqual(actual, expected[ei++]); - } - - expected.unshift([]); - - while (timeline.canRedo.get()) { - timeline.getRedoSnapshot()!.apply(); - const actual = timeline.requestDisablement.get(); - assert.deepStrictEqual(actual, expected[--ei]); - } - }); - }); - - suite('Static methods', () => { - test('createEmptySnapshot creates valid snapshot', () => { - const snapshot = ChatEditingTimeline.createEmptySnapshot('test-stop'); - assert.strictEqual(snapshot.stopId, 'test-stop'); - assert.ok(snapshot.entries); - assert.strictEqual(snapshot.entries.size, 0); - }); - - test('createEmptySnapshot with undefined stopId', () => { - const snapshot = ChatEditingTimeline.createEmptySnapshot(undefined); - assert.strictEqual(snapshot.stopId, undefined); - assert.ok(snapshot.entries); - }); - - test('POST_EDIT_STOP_ID is consistent', () => { - assert.strictEqual(typeof ChatEditingTimeline.POST_EDIT_STOP_ID, 'string'); - assert.ok(ChatEditingTimeline.POST_EDIT_STOP_ID.length > 0); - }); - }); - - suite('Observable behavior', () => { - test('canUndo observable updates correctly', () => { - assert.strictEqual(timeline.canUndo.get(), false); - - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - assert.strictEqual(timeline.canUndo.get(), true); - - timeline.getUndoSnapshot()?.apply(); - assert.strictEqual(timeline.canUndo.get(), false); - }); - - test('canRedo observable updates correctly', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - assert.strictEqual(timeline.canRedo.get(), false); - - timeline.getUndoSnapshot()?.apply(); - assert.strictEqual(timeline.canRedo.get(), true); - - timeline.getRedoSnapshot()?.apply(); - assert.strictEqual(timeline.canRedo.get(), false); - }); - }); - - suite('Complex scenarios', () => { - test('interleaved requests and undos', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req2', 'stop2', createSnapshot('stop2', 'req2')); - - // Undo req2 - timeline.getUndoSnapshot()?.apply(); - - // Add req3 (should branch from req1) - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 2); // req1, req3 - assert.strictEqual(state.history[1].requestId, 'req3'); - }); - - test('large number of snapshots', () => { - // Push 100 snapshots - for (let i = 1; i <= 100; i++) { - timeline.pushSnapshot(`req${i}`, `stop${i}`, createSnapshot(`stop${i}`)); - } - - assert.strictEqual(timeline.canUndo.get(), true); - assert.strictEqual(timeline.canRedo.get(), false); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 100); - assert.strictEqual(state.index, 100); - }); - - test('alternating single and multi-stop requests', () => { - // Single stop request - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - - // Multi-stop request - timeline.pushSnapshot('req2', 'stop2a', createSnapshot('stop2a', 'req2')); - timeline.pushSnapshot('req2', 'stop2b', createSnapshot('stop2b', 'req2')); - timeline.pushSnapshot('req2', 'stop2c', createSnapshot('stop2c', 'req2')); - - // Single stop request - timeline.pushSnapshot('req3', 'stop3', createSnapshot('stop3')); - - const state = timeline.getStateForPersistence(); - assert.strictEqual(state.history.length, 3); - assert.strictEqual(state.history[0].stops.length, 1); - assert.strictEqual(state.history[1].stops.length, 3); - assert.strictEqual(state.history[2].stops.length, 1); - }); - }); - - suite('Error resilience', () => { - test('handles invalid apply calls gracefully', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - timeline.pushSnapshot('req1', 'stop2', createSnapshot('stop2')); - - const undoSnap = timeline.getUndoSnapshot(); - assert.ok(undoSnap); - - // Apply twice - second should be safe - undoSnap.apply(); - undoSnap.apply(); // Should not throw - - assert.strictEqual(timeline.canUndo.get(), false); - }); - - test('getSnapshotForRestore with malformed stopId', () => { - timeline.pushSnapshot('req1', 'stop1', createSnapshot('stop1')); - - const snap = timeline.getSnapshotForRestore('req1', ''); - assert.strictEqual(snap, undefined); - }); - - test('handles restoration edge cases', () => { - const emptyState = { history: [], index: 0 }; - transaction(tx => timeline.restoreFromState(emptyState, tx)); - - // Should be safe to call methods on empty timeline - assert.strictEqual(timeline.getUndoSnapshot(), undefined); - assert.strictEqual(timeline.getRedoSnapshot(), undefined); - assert.deepStrictEqual(timeline.requestDisablement.get(), []); - }); - }); -}); diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts index 213b8856c8f..ba83733d6ca 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts @@ -249,7 +249,7 @@ export class NotebookWorker implements IWebWorkerServerRequestHandler, IDisposab const cellMapping = computeDiff(originalModel, modifiedModel, { cellsDiff: { changes: originalDiff.changes, quitEarly: false }, metadataChanged: false, }).cellDiffInfo; // If we have no insertions/deletions, then this is a good diffing. - if (cellMapping.every(c => c.type === 'modified')) { + if (cellMapping.every(c => c.type === 'modified' || c.type === 'unchanged')) { return { metadataChanged, cellsDiff: originalDiff From 6304f115bb9344c7e1b02e3d0d5f862c359b0789 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 16:03:56 -0700 Subject: [PATCH 1642/4355] tests --- .../chatEditingCheckpointTimelineImpl.ts | 22 +- .../browser/chatEditing/chatEditingSession.ts | 11 + .../chatEditingCheckpointTimeline.test.ts | 637 ++++++++++++++++++ 3 files changed, 652 insertions(+), 18 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index af9b5a164b4..df940a91cb8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -6,7 +6,6 @@ import { equals as arraysEqual } from '../../../../../base/common/arrays.js'; import { findLast, findLastIdx } from '../../../../../base/common/arraysFind.js'; import { assertNever } from '../../../../../base/common/assert.js'; -import { VSBuffer } from '../../../../../base/common/buffer.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { equals as objectsEqual } from '../../../../../base/common/objects.js'; @@ -15,7 +14,6 @@ import { isEqual } from '../../../../../base/common/resources.js'; import { Mutable } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; -import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { TextModel } from '../../../../../editor/common/model/textModel.js'; import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; @@ -43,6 +41,8 @@ type IReconstructedFileStateWithNotebook = IReconstructedFileNotExistsState | (M * navigating in the timeline tracks the changes as agent-initiated. */ export interface IChatEditingTimelineFsDelegate { + /** Creates a file with initial content. */ + createFile: (uri: URI, initialContent: string) => Promise; /** Delete a URI */ deleteFile: (uri: URI) => Promise; /** Rename a URI, retaining contents */ @@ -149,7 +149,6 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh @INotebookService private readonly _notebookService: INotebookService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, - @IBulkEditService private readonly _bulkEditService: IBulkEditService, @ITextModelService private readonly _textModelService: ITextModelService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IConfigurationService private readonly _configurationService: IConfigurationService @@ -561,7 +560,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh switch (operation.type) { case FileOperationType.Create: if (isMovingForward) { - await this._createFile(operation.uri, (operation as IFileCreateOperation).initialContent); + await this._delegate.createFile(operation.uri, (operation as IFileCreateOperation).initialContent); urisToRestore.add(operation.uri); } else { await this._delegate.deleteFile(operation.uri); @@ -574,7 +573,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh await this._delegate.deleteFile(operation.uri); urisToRestore.delete(operation.uri); } else { - await this._createFile(operation.uri, operation.finalContent); + await this._delegate.createFile(operation.uri, operation.finalContent); urisToRestore.add(operation.uri); } break; @@ -602,19 +601,6 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } } - // File system operation implementations - these would integrate with VS Code's file system - private async _createFile(uri: URI, content: string): Promise { - await this._bulkEditService.apply({ - edits: [{ - newResource: uri, - options: { - overwrite: true, - contents: content ? Promise.resolve(VSBuffer.fromString(content)) : undefined, - }, - }], - }); - } - private _applyTextEditsToContent(content: string, edits: readonly TextEdit[]): string { // Use the example pattern provided by the user const makeModel = (uri: URI, contents: string) => this._instantiationService.createInstance(TextModel, contents, '', this._modelService.getCreationOptions('', uri, true), uri); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 6c301cf4567..cfb451d2668 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -148,6 +148,17 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio private _getTimelineDelegate(): IChatEditingTimelineFsDelegate { return { + createFile: (uri, content) => { + return this._bulkEditService.apply({ + edits: [{ + newResource: uri, + options: { + overwrite: true, + contents: content ? Promise.resolve(VSBuffer.fromString(content)) : undefined, + }, + }], + }); + }, deleteFile: async (uri) => { const entries = this._entriesObs.get().filter(e => !isEqual(e.modifiedURI, uri)); this._entriesObs.set(entries, undefined); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts new file mode 100644 index 00000000000..74d4adc71d3 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts @@ -0,0 +1,637 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../../base/common/map.js'; +import { autorun, transaction } from '../../../../../base/common/observable.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { upcastPartial } from '../../../../../base/test/common/mock.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; +import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js'; +import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { INotebookService } from '../../../notebook/common/notebookService.js'; +import { ChatEditingCheckpointTimelineImpl, IChatEditingTimelineFsDelegate } from '../../browser/chatEditing/chatEditingCheckpointTimelineImpl.js'; +import { FileOperation, FileOperationType } from '../../browser/chatEditing/chatEditingOperations.js'; +import { IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js'; + +suite('ChatEditingCheckpointTimeline', function () { + + const store = new DisposableStore(); + let timeline: ChatEditingCheckpointTimelineImpl; + let fileContents: ResourceMap; + let fileDelegate: IChatEditingTimelineFsDelegate; + + const DEFAULT_TELEMETRY_INFO: IModifiedEntryTelemetryInfo = upcastPartial({ + agentId: 'testAgent', + command: undefined, + sessionId: 'test-session', + requestId: 'test-request', + result: undefined, + modelId: undefined, + modeId: undefined, + applyCodeBlockSuggestionId: undefined, + feature: undefined, + }); + + function createTextEditOperation(uri: URI, requestId: string, epoch: number, edits: { range: Range; text: string }[]): FileOperation { + return upcastPartial({ + type: FileOperationType.TextEdit, + uri, + requestId, + epoch, + edits + }); + } + + function createFileCreateOperation(uri: URI, requestId: string, epoch: number, initialContent: string): FileOperation { + return upcastPartial({ + type: FileOperationType.Create, + uri, + requestId, + epoch, + initialContent + }); + } + + function createFileDeleteOperation(uri: URI, requestId: string, epoch: number, finalContent: string): FileOperation { + return upcastPartial({ + type: FileOperationType.Delete, + uri, + requestId, + epoch, + finalContent + }); + } + + function createFileRenameOperation(oldUri: URI, newUri: URI, requestId: string, epoch: number): FileOperation { + return upcastPartial({ + type: FileOperationType.Rename, + uri: newUri, + requestId, + epoch, + oldUri, + newUri + }); + } + + setup(function () { + fileContents = new ResourceMap(); + + fileDelegate = { + createFile: async (uri: URI, initialContent: string) => { + fileContents.set(uri, initialContent); + }, + deleteFile: async (uri: URI) => { + fileContents.delete(uri); + }, + renameFile: async (fromUri: URI, toUri: URI) => { + const content = fileContents.get(fromUri); + if (content !== undefined) { + fileContents.set(toUri, content); + fileContents.delete(fromUri); + } + }, + setContents: async (uri: URI, content: string) => { + fileContents.set(uri, content); + } + }; + + const collection = new ServiceCollection(); + collection.set(INotebookService, new SyncDescriptor(TestNotebookService)); + const insta = store.add(workbenchInstantiationService(undefined, store).createChild(collection)); + + timeline = store.add(insta.createInstance(ChatEditingCheckpointTimelineImpl, 'test-session', fileDelegate)); + }); + + teardown(() => { + store.clear(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('creates initial checkpoint on construction', function () { + const checkpoints = timeline.getStateForPersistence().checkpoints; + assert.strictEqual(checkpoints.length, 1); + assert.strictEqual(checkpoints[0].requestId, undefined); + assert.strictEqual(checkpoints[0].label, 'Initial State'); + }); + + test('canUndo and canRedo are initially false', function () { + assert.strictEqual(timeline.canUndo.get(), false); + assert.strictEqual(timeline.canRedo.get(), false); + }); + + test('createCheckpoint increments epoch and creates checkpoint', function () { + const initialEpoch = timeline.getStateForPersistence().epochCounter; + + timeline.createCheckpoint('req1', 'stop1', 'Checkpoint 1'); + + const state = timeline.getStateForPersistence(); + assert.strictEqual(state.checkpoints.length, 2); // Initial + new checkpoint + assert.strictEqual(state.checkpoints[1].requestId, 'req1'); + assert.strictEqual(state.checkpoints[1].undoStopId, 'stop1'); + assert.strictEqual(state.checkpoints[1].label, 'Checkpoint 1'); + assert.strictEqual(state.epochCounter, initialEpoch + 1); + }); + + test('createCheckpoint does not create duplicate checkpoints', function () { + timeline.createCheckpoint('req1', 'stop1', 'Checkpoint 1'); + timeline.createCheckpoint('req1', 'stop1', 'Checkpoint 1 Duplicate'); + + const checkpoints = timeline.getStateForPersistence().checkpoints; + assert.strictEqual(checkpoints.length, 2); // Only initial + first checkpoint + assert.strictEqual(checkpoints[1].label, 'Checkpoint 1'); // Original label preserved + }); + + test('incrementEpoch increases epoch counter', function () { + const initialEpoch = timeline.getStateForPersistence().epochCounter; + + const epoch1 = timeline.incrementEpoch(); + const epoch2 = timeline.incrementEpoch(); + + assert.strictEqual(epoch1, initialEpoch); + assert.strictEqual(epoch2, initialEpoch + 1); + assert.strictEqual(timeline.getStateForPersistence().epochCounter, initialEpoch + 2); + }); + + test('recordFileBaseline stores baseline', function () { + const uri = URI.parse('file:///test.txt'); + const baseline = upcastPartial({ + uri, + requestId: 'req1', + content: 'initial content', + epoch: 1, + telemetryInfo: DEFAULT_TELEMETRY_INFO + }); + + timeline.recordFileBaseline(baseline); + + assert.strictEqual(timeline.hasFileBaseline(uri, 'req1'), true); + assert.strictEqual(timeline.hasFileBaseline(uri, 'req2'), false); + }); + + test('recordFileOperation stores operation', function () { + const uri = URI.parse('file:///test.txt'); + const operation = createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 1), text: 'hello' }] + ); + + timeline.recordFileOperation(operation); + + const state = timeline.getStateForPersistence(); + assert.strictEqual(state.operations.length, 1); + assert.strictEqual(state.operations[0].type, FileOperationType.TextEdit); + assert.strictEqual(state.operations[0].requestId, 'req1'); + }); + + test('basic undo/redo with text edits', async function () { + const uri = URI.parse('file:///test.txt'); + + // Record baseline + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'hello', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + // Create checkpoint before edit - marks state with baseline + timeline.createCheckpoint('req1', undefined, 'Start of Request'); + + // Record edit at a new epoch + const editEpoch = timeline.incrementEpoch(); + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + editEpoch, + [{ range: new Range(1, 1, 1, 6), text: 'goodbye' }] + )); + + // Create checkpoint after edit - marks state with edit applied + timeline.createCheckpoint('req1', 'stop1', 'After Edit'); + + // canUndo and canRedo are based on checkpoint positions, not delegate state + // We have: Initial, Start of Request, After Edit + // Current epoch is after 'After Edit', so we can undo but not redo + assert.strictEqual(timeline.canUndo.get(), true); + assert.strictEqual(timeline.canRedo.get(), false); + + // Undo (goes to start of request) + await timeline.undoToLastCheckpoint(); + + // After undoing to start of request, we can't undo within this request anymore + // but we can redo to the 'stop1' checkpoint + assert.strictEqual(timeline.canUndo.get(), false); // No more undo stops in req1 before this + assert.strictEqual(timeline.canRedo.get(), true); // Can redo to 'stop1' + + // Redo + await timeline.redoToNextCheckpoint(); + + // After redo to 'stop1', we can undo again + assert.strictEqual(timeline.canUndo.get(), true); + // canRedo might still be true if currentEpoch is less than the max epoch + // This is because checkpoints are created with incrementEpoch, so there are epochs after them + }); + + test('file creation and deletion operations', async function () { + const uri = URI.parse('file:///new.txt'); + + // Create file + const createEpoch = timeline.incrementEpoch(); + + // Record baseline for the created file + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'new file content', + epoch: createEpoch, + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createFileCreateOperation( + uri, + 'req1', + createEpoch, + 'new file content' + )); + + // Checkpoint marks state after file creation + timeline.createCheckpoint('req1', 'created', 'File Created'); + + // Navigate to initial to sync delegate, then to created + await timeline.navigateToCheckpoint(timeline.getStateForPersistence().checkpoints[0].checkpointId); + assert.strictEqual(fileContents.has(uri), false); + + // Navigate to created checkpoint + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'created')!); + assert.strictEqual(fileContents.get(uri), 'new file content'); + + // Delete file + const deleteEpoch = timeline.incrementEpoch(); + timeline.recordFileOperation(createFileDeleteOperation( + uri, + 'req1', + deleteEpoch, + 'new file content' + )); + + timeline.createCheckpoint('req1', 'deleted', 'File Deleted'); + + // Navigate back to initial, then to deleted to properly apply operations + await timeline.navigateToCheckpoint(timeline.getStateForPersistence().checkpoints[0].checkpointId); + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'deleted')!); + assert.strictEqual(fileContents.has(uri), false); + + // Undo deletion - goes back to 'created' checkpoint + await timeline.undoToLastCheckpoint(); + assert.strictEqual(fileContents.get(uri), 'new file content'); + + // Undo creation - goes back to initial state + await timeline.undoToLastCheckpoint(); + assert.strictEqual(fileContents.has(uri), false); + }); + + test('file rename operations', async function () { + const oldUri = URI.parse('file:///old.txt'); + const newUri = URI.parse('file:///new.txt'); + + // Create initial file + const createEpoch = timeline.incrementEpoch(); + + // Record baseline for the created file + timeline.recordFileBaseline(upcastPartial({ + uri: oldUri, + requestId: 'req1', + content: 'content', + epoch: createEpoch, + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createFileCreateOperation( + oldUri, + 'req1', + createEpoch, + 'content' + )); + + timeline.createCheckpoint('req1', 'created', 'File Created'); + + // Navigate to initial, then to created to apply create operation + await timeline.navigateToCheckpoint(timeline.getStateForPersistence().checkpoints[0].checkpointId); + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'created')!); + assert.strictEqual(fileContents.get(oldUri), 'content'); + + // Rename file + const renameEpoch = timeline.incrementEpoch(); + timeline.recordFileOperation(createFileRenameOperation( + oldUri, + newUri, + 'req1', + renameEpoch + )); + + timeline.createCheckpoint('req1', 'renamed', 'File Renamed'); + + // Navigate back to initial, then to renamed to properly apply operations + await timeline.navigateToCheckpoint(timeline.getStateForPersistence().checkpoints[0].checkpointId); + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'renamed')!); + assert.strictEqual(fileContents.has(oldUri), false); + assert.strictEqual(fileContents.get(newUri), 'content'); + + // Undo rename - goes back to 'created' checkpoint + await timeline.undoToLastCheckpoint(); + assert.strictEqual(fileContents.get(oldUri), 'content'); + assert.strictEqual(fileContents.has(newUri), false); + }); + + test('multiple sequential edits to same file', async function () { + const uri = URI.parse('file:///test.txt'); + + // Record baseline + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'line1\nline2\nline3', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + // First edit + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 6), text: 'LINE1' }] + )); + + timeline.createCheckpoint('req1', 'edit1', 'Edit 1'); + + // Second edit + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(2, 1, 2, 6), text: 'LINE2' }] + )); + + timeline.createCheckpoint('req1', 'edit2', 'Edit 2'); + + // Navigate to first edit + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'edit1')!); + assert.strictEqual(fileContents.get(uri), 'LINE1\nline2\nline3'); + + // Navigate to second edit + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'edit2')!); + assert.strictEqual(fileContents.get(uri), 'LINE1\nLINE2\nline3'); + + // Navigate back to start + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', undefined)!); + assert.strictEqual(fileContents.get(uri), 'line1\nline2\nline3'); + }); + + test('getCheckpointIdForRequest returns correct checkpoint', function () { + timeline.createCheckpoint('req1', undefined, 'Start of req1'); + timeline.createCheckpoint('req1', 'stop1', 'Stop 1'); + timeline.createCheckpoint('req2', undefined, 'Start of req2'); + + const req1Start = timeline.getCheckpointIdForRequest('req1', undefined); + const req1Stop = timeline.getCheckpointIdForRequest('req1', 'stop1'); + const req2Start = timeline.getCheckpointIdForRequest('req2', undefined); + + assert.ok(req1Start); + assert.ok(req1Stop); + assert.ok(req2Start); + assert.notStrictEqual(req1Start, req1Stop); + assert.notStrictEqual(req1Start, req2Start); + }); + + test('getCheckpointIdForRequest returns undefined for non-existent checkpoint', function () { + const checkpoint = timeline.getCheckpointIdForRequest('nonexistent', 'stop1'); + assert.strictEqual(checkpoint, undefined); + }); + + test('requestDisablement tracks disabled requests', async function () { + const disabledRequests: string[] = []; + store.add(autorun(reader => { + const disabled = timeline.requestDisablement.read(reader); + disabledRequests.length = 0; + disabledRequests.push(...disabled.map(d => d.requestId)); + })); + + // Create checkpoints for multiple requests + timeline.createCheckpoint('req1', undefined, 'Start req1'); + timeline.createCheckpoint('req1', 'stop1', 'Stop req1'); + timeline.createCheckpoint('req2', undefined, 'Start req2'); + + // Navigate to req1 stop1 + const req1StopId = timeline.getCheckpointIdForRequest('req1', 'stop1')!; + await timeline.navigateToCheckpoint(req1StopId); + + // req2 should be disabled as we're before it. req1 is also disabled because + // we're in the past relative to the current tip + assert.ok(disabledRequests.includes('req2')); + }); + + test('persistence - save and restore state', function () { + const uri = URI.parse('file:///test.txt'); + + // Setup some state + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 8), text: 'modified' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'Edit Complete'); + + // Save state + const savedState = timeline.getStateForPersistence(); + + // Create new timeline and restore + const collection = new ServiceCollection(); + collection.set(INotebookService, new SyncDescriptor(TestNotebookService)); + const insta = store.add(workbenchInstantiationService(undefined, store).createChild(collection)); + + const newTimeline = store.add(insta.createInstance( + ChatEditingCheckpointTimelineImpl, + 'test-session-2', + fileDelegate + )); + + transaction(tx => { + newTimeline.restoreFromState(savedState, tx); + }); + + // Verify state was restored + const restoredState = newTimeline.getStateForPersistence(); + assert.strictEqual(restoredState.checkpoints.length, savedState.checkpoints.length); + assert.strictEqual(restoredState.operations.length, savedState.operations.length); + assert.strictEqual(restoredState.currentEpoch, savedState.currentEpoch); + assert.strictEqual(restoredState.epochCounter, savedState.epochCounter); + + newTimeline.dispose(); + }); + + test('navigating between multiple requests', async function () { + const uri1 = URI.parse('file:///file1.txt'); + const uri2 = URI.parse('file:///file2.txt'); + + // Request 1 - create file + timeline.createCheckpoint('req1', undefined, 'Start req1'); + + const create1Epoch = timeline.incrementEpoch(); + timeline.recordFileBaseline(upcastPartial({ + uri: uri1, + requestId: 'req1', + content: 'file1 modified', + epoch: create1Epoch, + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createFileCreateOperation( + uri1, + 'req1', + create1Epoch, + 'file1 modified' + )); + + timeline.createCheckpoint('req1', 'stop1', 'Req1 complete'); + + // Request 2 - create another file + timeline.createCheckpoint('req2', undefined, 'Start req2'); + + const create2Epoch = timeline.incrementEpoch(); + timeline.recordFileBaseline(upcastPartial({ + uri: uri2, + requestId: 'req2', + content: 'file2 modified', + epoch: create2Epoch, + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createFileCreateOperation( + uri2, + 'req2', + create2Epoch, + 'file2 modified' + )); + + timeline.createCheckpoint('req2', 'stop1', 'Req2 complete'); + + // Navigate to initial, then to req1 completion to apply its operations + await timeline.navigateToCheckpoint(timeline.getStateForPersistence().checkpoints[0].checkpointId); + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'stop1')!); + assert.strictEqual(fileContents.get(uri1), 'file1 modified'); + assert.strictEqual(fileContents.has(uri2), false); // req2 hasn't happened yet + + // Navigate to req2 completion + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req2', 'stop1')!); + assert.strictEqual(fileContents.get(uri1), 'file1 modified'); + assert.strictEqual(fileContents.get(uri2), 'file2 modified'); + + // Navigate back to initial state by getting the first checkpoint + const initialCheckpoint = timeline.getStateForPersistence().checkpoints[0]; + await timeline.navigateToCheckpoint(initialCheckpoint.checkpointId); + assert.strictEqual(fileContents.has(uri1), false); + assert.strictEqual(fileContents.has(uri2), false); + }); + + test('getContentURIAtStop returns snapshot URI', function () { + const fileUri = URI.parse('file:///test.txt'); + const snapshotUri = timeline.getContentURIAtStop('req1', fileUri, 'stop1'); + + assert.ok(snapshotUri); + assert.notStrictEqual(snapshotUri.toString(), fileUri.toString()); + assert.ok(snapshotUri.toString().includes('req1')); + }); + + test('undoing entire request when appropriate', async function () { + const uri = URI.parse('file:///test.txt'); + + // Create initial baseline and checkpoint + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start req1'); + + // Single edit with checkpoint + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 8), text: 'modified' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'Edit complete'); + + // Should be able to undo + assert.strictEqual(timeline.canUndo.get(), true); + + // Undo should go back to start of request, not just previous checkpoint + await timeline.undoToLastCheckpoint(); + + // Verify we're at the start of req1, which has epoch 2 (0 = initial, 1 = baseline, 2 = start checkpoint) + const state = timeline.getStateForPersistence(); + assert.strictEqual(state.currentEpoch, 2); // Should be at the "Start req1" checkpoint epoch + }); + + test('operations use incrementing epochs', function () { + const uri = URI.parse('file:///test.txt'); + + const epoch1 = timeline.incrementEpoch(); + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + epoch1, + [{ range: new Range(1, 1, 1, 1), text: 'edit1' }] + )); + + const epoch2 = timeline.incrementEpoch(); + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + epoch2, + [{ range: new Range(2, 1, 2, 1), text: 'edit2' }] + )); + + // Both operations should be recorded + const operations = timeline.getStateForPersistence().operations; + assert.strictEqual(operations.length, 2); + assert.strictEqual(operations[0].epoch, epoch1); + assert.strictEqual(operations[1].epoch, epoch2); + }); +}); + +// Mock notebook service for tests that don't need notebook functionality +class TestNotebookService { + getNotebookTextModel() { return undefined; } + hasSupportedNotebooks() { return false; } +} + From 945d7d5a00607c4cf7dd9899fd56bfb7831303e3 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 16:13:22 -0700 Subject: [PATCH 1643/4355] testing: pass controller+run name in result context menu actions (#273210) --- .../testResultsView/testResultsTree.ts | 83 ++++++++++++------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index 4fb500ae21e..cec00a8dec8 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -10,7 +10,7 @@ import { IIdentityProvider } from '../../../../../base/browser/ui/list/list.js'; import { ICompressedTreeElement, ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; import { ITreeContextMenuEvent, ITreeNode } from '../../../../../base/browser/ui/tree/tree.js'; -import { Action, IAction, Separator } from '../../../../../base/common/actions.js'; +import { Action, ActionRunner, IAction, Separator } from '../../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; @@ -34,22 +34,22 @@ import { WorkbenchCompressibleObjectTree } from '../../../../../platform/list/br import { IProgressService } from '../../../../../platform/progress/common/progress.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { widgetClose } from '../../../../../platform/theme/common/iconRegistry.js'; -import { getTestItemContextOverlay } from '../explorerProjections/testItemContextOverlay.js'; -import * as icons from '../icons.js'; -import { renderTestMessageAsText } from '../testMessageColorizer.js'; -import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, getMessageArgs, mapFindTestMessage } from './testResultsSubject.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { TestCommandId, Testing } from '../../common/constants.js'; import { ITestCoverageService } from '../../common/testCoverageService.js'; import { ITestExplorerFilterState } from '../../common/testExplorerFilterState.js'; +import { TestId } from '../../common/testId.js'; import { ITestProfileService } from '../../common/testProfileService.js'; import { ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChangeReason, maxCountPriority } from '../../common/testResult.js'; import { ITestResultService } from '../../common/testResultService.js'; -import { IRichLocation, ITestItemContext, ITestMessage, ITestMessageMenuArgs, InternalTestItem, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, testResultStateToContextValues } from '../../common/testTypes.js'; +import { IRichLocation, ITestItemContext, ITestMessage, InternalTestItem, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, testResultStateToContextValues } from '../../common/testTypes.js'; import { TestingContextKeys } from '../../common/testingContextKeys.js'; import { cmpPriority, isFailedState } from '../../common/testingStates.js'; import { TestUriType, buildTestUri } from '../../common/testingUri.js'; -import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { TestId } from '../../common/testId.js'; +import { getTestItemContextOverlay } from '../explorerProjections/testItemContextOverlay.js'; +import * as icons from '../icons.js'; +import { renderTestMessageAsText } from '../testMessageColorizer.js'; +import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, getMessageArgs, mapFindTestMessage } from './testResultsSubject.js'; interface ITreeElement { @@ -64,16 +64,15 @@ interface ITreeElement { ariaLabel?: string; } -interface ITreeElement { - type: string; - context: unknown; - id: string; - label: string; - readonly onDidChange: Event; - labelWithIcons?: readonly (HTMLSpanElement | string)[]; - icon?: ThemeIcon; - description?: string; - ariaLabel?: string; +interface ITaskContext { + testRunName: string; + controllerId: string; + resultId: string; + taskId: string; +} + +function getTaskContext(resultId: string, task: ITestRunTaskResults): ITaskContext { + return { testRunName: task.name, controllerId: task.ctrlId, resultId, taskId: task.id }; } class TestResultElement implements ITreeElement { @@ -142,13 +141,12 @@ class OlderResultsElement implements ITreeElement { ? localize('oneOlderResult', '1 older result') : localize('nOlderResults', '{0} older results', n); this.id = `older-${this.n}`; - } } class TestCaseElement implements ITreeElement { public readonly type = 'test'; - public readonly context: ITestItemContext; + public readonly context: ActionSpreadArgs<[ITestItemContext, ITaskContext]>; public readonly id: string; public readonly description?: string; @@ -203,10 +201,13 @@ class TestCaseElement implements ITreeElement { } } - this.context = { - $mid: MarshalledId.TestItemContext, - tests: [InternalTestItem.serialize(test)], - }; + this.context = new ActionSpreadArgs([ + { + $mid: MarshalledId.TestItemContext, + tests: [InternalTestItem.serialize(test)], + }, + getTaskContext(results.id, results.tasks[this.taskIndex]) + ]); } } @@ -214,7 +215,7 @@ class TaskElement implements ITreeElement { public readonly changeEmitter = new Emitter(); public readonly onDidChange = this.changeEmitter.event; public readonly type = 'task'; - public readonly context: { resultId: string; taskId: string }; + public readonly context: ITaskContext; public readonly id: string; public readonly label: string; public readonly itemsCache = new CreationCache(); @@ -226,7 +227,7 @@ class TaskElement implements ITreeElement { constructor(public readonly results: ITestResult, public readonly task: ITestRunTaskResults, public readonly index: number) { this.id = `${results.id}/${index}`; this.task = results.tasks[index]; - this.context = { resultId: results.id, taskId: this.task.id }; + this.context = getTaskContext(results.id, this.task); this.label = this.task.name; } } @@ -250,8 +251,11 @@ class TestMessageElement implements ITreeElement { return Event.filter(this.result.onChange, e => e.item.item.extId === this.test.item.extId && e.reason !== TestResultItemChangeReason.NewMessage); } - public get context(): ITestMessageMenuArgs { - return getMessageArgs(this.test, this.message); + public get context() { + return new ActionSpreadArgs([ + getMessageArgs(this.test, this.message), + getTaskContext(this.result.id, this.result.tasks[this.taskIndex]) + ]); } public get outputSubject() { @@ -296,6 +300,7 @@ export class OutputPeekTree extends Disposable { private readonly tree: WorkbenchCompressibleObjectTree; private readonly treeActions: TreeActionsProvider; private readonly requestReveal = this._register(new Emitter()); + private readonly contextMenuActionRunner = this._register(new SpreadableActionRunner()); public readonly onDidRequestReview = this.requestReveal.event; @@ -656,7 +661,8 @@ export class OutputPeekTree extends Disposable { getActions: () => actions.secondary.length ? [...actions.primary, new Separator(), ...actions.secondary] : actions.primary, - getActionsContext: () => evt.element?.context + getActionsContext: () => evt.element?.context, + actionRunner: this.contextMenuActionRunner, }); } @@ -702,6 +708,7 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) @@ -981,7 +988,7 @@ class TreeActionsProvider { const contextOverlay = this.contextKeyService.createOverlay(contextKeys); const result = { primary, secondary }; - const menu = this.menuService.getMenuActions(id, contextOverlay, { arg: element.context }); + const menu = this.menuService.getMenuActions(id, contextOverlay, { shouldForwardArgs: true }); fillInActionBarActions(menu, result, 'inline'); return result; } @@ -1010,3 +1017,19 @@ const firstLine = (str: string) => { const index = str.indexOf('\n'); return index === -1 ? str : str.slice(0, index); }; + + + +class ActionSpreadArgs { + constructor(public readonly value: T) { } +} + +class SpreadableActionRunner extends ActionRunner { + protected override async runAction(action: IAction, context?: unknown): Promise { + if (context instanceof ActionSpreadArgs) { + await action.run(...context.value); + } else { + await action.run(context); + } + } +} From 6d68f2ce9aad700510c35d71a7c8f0b219ba103e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Oct 2025 16:37:56 -0700 Subject: [PATCH 1644/4355] fix leaked handle to mcp collection (#273211) * Revert "Refreshing cached MCP servers when the extension MCP's are disabled. (#270476)" This reverts commit 691bcc63e64898057b6947be840b9f1eaa8d4690. * fix leaked handle to mcp collection --- src/vs/workbench/api/browser/mainThreadMcp.ts | 2 +- .../common/discovery/extensionMcpDiscovery.ts | 70 ++-- .../contrib/mcp/common/mcpRegistry.ts | 2 +- .../discovery/extensionMcpDiscovery.test.ts | 382 ------------------ 4 files changed, 31 insertions(+), 425 deletions(-) delete mode 100644 src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts diff --git a/src/vs/workbench/api/browser/mainThreadMcp.ts b/src/vs/workbench/api/browser/mainThreadMcp.ts index aa848a44304..a7dd2b539d3 100644 --- a/src/vs/workbench/api/browser/mainThreadMcp.ts +++ b/src/vs/workbench/api/browser/mainThreadMcp.ts @@ -105,7 +105,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape { const serverDefinitions = observableValue('mcpServers', servers); const extensionId = new ExtensionIdentifier(collection.extensionId); const store = new DisposableStore(); - const handle = new MutableDisposable(); + const handle = store.add(new MutableDisposable()); const register = () => { handle.value ??= this._mcpRegistry.registerCollection({ ...collection, diff --git a/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts b/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts index 34c6457d0ce..004919f456f 100644 --- a/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts +++ b/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.ts @@ -13,7 +13,6 @@ import { IMcpCollectionContribution } from '../../../../../platform/extensions/c import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; -import { ExtensionPointUserDelta, IExtensionPointUser } from '../../../../services/extensions/common/extensionsRegistry.js'; import { mcpActivationEvent, mcpContributionPoint } from '../mcpConfiguration.js'; import { IMcpRegistry } from '../mcpRegistryTypes.js'; import { extensionPrefixedIdentifier, McpServerDefinition, McpServerTrust } from '../mcpTypes.js'; @@ -30,7 +29,6 @@ const _mcpExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi const enum PersistWhen { CollectionExists, Always, - Delete } export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { @@ -40,7 +38,6 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { private readonly _extensionCollectionIdsToPersist = new Map(); private readonly cachedServers: { [collcetionId: string]: IServerCacheEntry }; private readonly _conditionalCollections = this._register(new DisposableMap()); - private _extensionCollections?: DisposableMap; constructor( @IMcpRegistry private readonly _mcpRegistry: IMcpRegistry, @@ -54,12 +51,6 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { this._register(storageService.onWillSaveState(() => { let updated = false; for (const [collectionId, behavior] of this._extensionCollectionIdsToPersist.entries()) { - if (behavior === PersistWhen.Delete) { - updated = true; - delete this.cachedServers[collectionId]; - this._extensionCollectionIdsToPersist.delete(collectionId); - continue; - } const collection = this._mcpRegistry.collections.get().find(c => c.id === collectionId); let defs = collection?.serverDefinitions.get(); if (!collection || collection.lazy) { @@ -75,50 +66,47 @@ export class ExtensionMcpDiscovery extends Disposable implements IMcpDiscovery { this.cachedServers[collectionId] = { servers: defs.map(McpServerDefinition.toSerialized) }; } } + if (updated) { storageService.store(cacheKey, this.cachedServers, StorageScope.WORKSPACE, StorageTarget.MACHINE); } })); } + public start(): void { - this._extensionCollections = this._register(new DisposableMap()); - this._register(_mcpExtensionPoint.setHandler(this.handleExtensionChange.bind(this))); - } + const extensionCollections = this._register(new DisposableMap()); + this._register(_mcpExtensionPoint.setHandler((_extensions, delta) => { + const { added, removed } = delta; - protected handleExtensionChange(_extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta) { - const extensionCollections = this._extensionCollections!; - for (const collections of delta.removed) { - for (const coll of collections.value) { - const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); - this.deleteCollection(id); + for (const collections of removed) { + for (const coll of collections.value) { + const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); + extensionCollections.deleteAndDispose(id); + this._conditionalCollections.deleteAndDispose(id); + } } - } - for (const collections of delta.added) { - if (!ExtensionMcpDiscovery._validate(collections)) { - continue; - } - for (const coll of collections.value) { - const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); - this._extensionCollectionIdsToPersist.set(id, PersistWhen.CollectionExists); - - // Handle conditional collections with 'when' clause - if (coll.when) { - this._registerConditionalCollection(id, coll, collections, extensionCollections); - } else { - // Register collection immediately if no 'when' clause - this._registerCollection(id, coll, collections, extensionCollections); + for (const collections of added) { + + if (!ExtensionMcpDiscovery._validate(collections)) { + continue; } - } - } - } - protected deleteCollection(id: string) { - this._extensionCollections!.deleteAndDispose(id); - this._conditionalCollections.deleteAndDispose(id); - this._extensionCollectionIdsToPersist.set(id, PersistWhen.Delete); - } + for (const coll of collections.value) { + const id = extensionPrefixedIdentifier(collections.description.identifier, coll.id); + this._extensionCollectionIdsToPersist.set(id, PersistWhen.CollectionExists); + // Handle conditional collections with 'when' clause + if (coll.when) { + this._registerConditionalCollection(id, coll, collections, extensionCollections); + } else { + // Register collection immediately if no 'when' clause + this._registerCollection(id, coll, collections, extensionCollections); + } + } + } + })); + } private _registerCollection( id: string, diff --git a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts index 7e419e7ab0d..6d95d0f068a 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpRegistry.ts @@ -119,7 +119,7 @@ export class McpRegistry extends Disposable implements IMcpRegistry { return { dispose: () => { const currentCollections = this._collections.get(); - this._collections.set(currentCollections.filter(c => c.id !== collection.id), undefined); + this._collections.set(currentCollections.filter(c => c !== collection), undefined); } }; } diff --git a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts b/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts deleted file mode 100644 index 0e66e150ab8..00000000000 --- a/src/vs/workbench/contrib/mcp/test/common/discovery/extensionMcpDiscovery.test.ts +++ /dev/null @@ -1,382 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { Event } from '../../../../../../base/common/event.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; -import { TestStorageService, TestExtensionService } from '../../../../../test/common/workbenchTestServices.js'; -import { ExtensionMcpDiscovery } from '../../../common/discovery/extensionMcpDiscovery.js'; -import { ServiceCollection } from '../../../../../../platform/instantiation/common/serviceCollection.js'; -import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; -import { IStorageService } from '../../../../../../platform/storage/common/storage.js'; -import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; -import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; -import { MockContextKeyService } from '../../../../../../platform/keybinding/test/common/mockKeybindingService.js'; -import { IMcpRegistry } from '../../../common/mcpRegistryTypes.js'; -import { TestMcpRegistry } from '../mcpRegistryTypes.js'; -import { ExtensionMessageCollector, ExtensionPointUserDelta, IExtensionPointUser } from '../../../../../services/extensions/common/extensionsRegistry.js'; -import { IMcpCollectionContribution, IExtensionDescription, ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; - -// #region Test Helper Classes - -/** - * Testable version of ExtensionMcpDiscovery that exposes protected methods - */ -class TestableExtensionMcpDiscovery extends ExtensionMcpDiscovery { - - public handleExtensionChangeTest( - extensions: readonly IExtensionPointUser[], - delta: ExtensionPointUserDelta - ): void { - super.handleExtensionChange(extensions, delta); - } - -} - -/** - * Test implementation of IContextKeyService for MCP discovery tests - * Provides configurable context matching for testing conditional collections - */ -class TestContextKeyService extends MockContextKeyService { - private _contextMatchesRules: boolean = true; - - public override contextMatchesRules(): boolean { - return this._contextMatchesRules; - } - - public override get onDidChangeContext() { - return Event.None; - } - - public setContextMatchesRules(value: boolean): void { - this._contextMatchesRules = value; - } - - public override dispose(): void { - // Test implementation - no cleanup needed - } -} - -// #endregion - - -/** - * Test suite for ExtensionMcpDiscovery - * - * This class manages the discovery and registration of MCP (Model Context Protocol) - * collections from VS Code extensions. It handles: - * - Extension point registration and validation - * - Collection registration with conditional support ("when" clauses) - * - Caching of server definitions - * - Extension lifecycle (add/remove) - */ -suite('ExtensionMcpDiscovery', () => { - // #region Test State - interface TestFixture { - extensionMcpDiscovery: TestableExtensionMcpDiscovery; - storageService: TestStorageService; - extensionService: TestExtensionService; - contextKeyService: TestContextKeyService; - mcpRegistry: TestMcpRegistry; - } - - let fixture: TestFixture; - const store = ensureNoDisposablesAreLeakedInTestSuite(); - // #endregion - - // #region Helper Methods - /** - * Creates a fresh ExtensionMcpDiscovery instance with new services - * Useful for testing constructor behavior in isolation - */ - function createDiscoveryInstance(): ExtensionMcpDiscovery { - const services = new ServiceCollection( - [IStorageService, fixture.storageService], - [IExtensionService, fixture.extensionService], - [IContextKeyService, fixture.contextKeyService] - ); - const serviceInstantiation = store.add(new TestInstantiationService(services)); - const registry = new TestMcpRegistry(serviceInstantiation); - const instance = store.add(serviceInstantiation.createChild(new ServiceCollection([IMcpRegistry, registry]))); - return store.add(instance.createInstance(ExtensionMcpDiscovery)); - } - - /** - * Sets up the test fixture with all required services - */ - function setupTestFixture(): TestFixture { - const storageService = store.add(new TestStorageService()); - const extensionService = new TestExtensionService(); - const contextKeyService = new TestContextKeyService(); - - const services = new ServiceCollection( - [IStorageService, storageService], - [IExtensionService, extensionService], - [IContextKeyService, contextKeyService] - ); - const serviceInstantiation = store.add(new TestInstantiationService(services)); - const mcpRegistry = new TestMcpRegistry(serviceInstantiation); - - const instance = store.add(serviceInstantiation.createChild(new ServiceCollection([IMcpRegistry, mcpRegistry]))); - const extensionMcpDiscovery = store.add(instance.createInstance(TestableExtensionMcpDiscovery)); - - return { - extensionMcpDiscovery, - storageService, - extensionService, - contextKeyService, - mcpRegistry - }; - } - // #endregion - - // #region Test Setup/Teardown - setup(() => { - fixture = setupTestFixture(); - }); - - teardown(() => { - sinon.restore(); - }); - // #endregion - - // #region Basic Functionality Tests - suite('Basic Functionality', () => { - test('should start without throwing errors', () => { - assert.doesNotThrow(() => { - fixture.extensionMcpDiscovery.start(); - }, 'start() should not throw any errors'); - }); - }); - // #endregion - - // #region Constructor Tests - suite('Constructor Behavior', () => { - test('should register onWillSaveState listener during construction', () => { - // Spy on the storage service's onWillSaveState method - const onWillSaveStateSpy = sinon.spy(fixture.storageService, 'onWillSaveState'); - - // Create a new instance to test the constructor behavior - createDiscoveryInstance(); - - // Verify that onWillSaveState was called to register a listener - assert.ok(onWillSaveStateSpy.calledOnce, 'onWillSaveState should be called to register a listener'); - assert.strictEqual( - typeof onWillSaveStateSpy.getCall(0).args[0], - 'function', - 'onWillSaveState should be called with a function callback' - ); - - onWillSaveStateSpy.restore(); - }); - - test('should not call storageService.store when onWillSaveState event is emitted with no changes', () => { - // Spy on the storage service's store method - const storeSpy = sinon.spy(fixture.storageService, 'store'); - - // Create a new instance to ensure the event handler is registered - createDiscoveryInstance(); - - // Trigger the onWillSaveState event (no collections have been modified) - fixture.storageService.testEmitWillSaveState(0 /* WillSaveStateReason.NONE */); - - // Verify that store was not called since there are no changes to persist - assert.ok(storeSpy.notCalled, 'storageService.store should not be called when there are no changes to persist'); - - storeSpy.restore(); - }); - }); - // #endregion - - // #region Extension Point Handler Tests - suite('Extension Point Handler', () => { - /** - * Creates mock extension point user data for testing - */ - function createMockExtensionPointUser( - id: string, - label: string, - extensionId: string = 'test.extension', - when?: string - ): IExtensionPointUser { - const contribution: IMcpCollectionContribution = { - id, - label, - ...(when && { when }) - }; - - // Create a mock collector that satisfies the ExtensionMessageCollector interface - const mockCollector = { - error: sinon.stub(), - warn: sinon.stub(), - info: sinon.stub(), - _messageHandler: sinon.stub(), - _extension: null, - _extensionPointId: 'test-point', - _msg: [] - } as unknown as ExtensionMessageCollector; // Use any for the mock to avoid complex type requirements - - return { - value: [contribution], - description: { - identifier: new ExtensionIdentifier(extensionId) - } as IExtensionDescription, - collector: mockCollector - }; - } - - /** - * Creates a mock ExtensionPointUserDelta for testing - */ - function createMockDelta( - added: IExtensionPointUser[] = [], - removed: IExtensionPointUser[] = [] - ): ExtensionPointUserDelta { - return { - added, - removed - } as ExtensionPointUserDelta; - } - - test('should handle added extensions without when clause', () => { - // Start the discovery to initialize internal state - fixture.extensionMcpDiscovery.start(); - - // Spy on the mcpRegistry to verify collection registration - const registerCollectionStub = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - - // Create mock extension data - const mockExtension = createMockExtensionPointUser('test-collection', 'Test Collection'); - const delta = createMockDelta([mockExtension], []); - - //spy on registercollection - - // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], delta); - - // Verify that registerCollection was called for the added extension - assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for added extension'); - - const registrationCall = registerCollectionStub.getCall(0); - const registrationArgs = registrationCall.args[0]; - assert.strictEqual(registrationArgs.id, 'test.extension/test-collection', 'Collection should be registered with prefixed ID'); - assert.strictEqual(registrationArgs.label, 'Test Collection', 'Collection should have correct label'); - - registerCollectionStub.restore(); - }); - - test('should handle added extensions with when clause', () => { - // Start the discovery to initialize internal state - fixture.extensionMcpDiscovery.start(); - - // Configure context to match the when clause - fixture.contextKeyService.setContextMatchesRules(true); - - // Spy on the mcpRegistry to verify collection registration - const registerCollectionStub = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - - // Create mock extension data with when clause - const mockExtension = createMockExtensionPointUser( - 'conditional-collection', - 'Conditional Collection', - 'test.extension', - 'config.someFlag' - ); - const delta = createMockDelta([mockExtension], []); - - // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], delta); - - // Verify that registerCollection was called for the conditional extension - assert.ok(registerCollectionStub.calledOnce, 'registerCollection should be called once for conditional extension when context matches'); - assert.strictEqual(registerCollectionStub.getCall(0).args[0].id, 'test.extension/conditional-collection', 'Collection should be registered with prefixed ID'); - assert.strictEqual(registerCollectionStub.getCall(0).args[0].label, 'Conditional Collection', 'Collection should have correct label'); - - registerCollectionStub.restore(); - }); - - test('should handle removed extensions', () => { - // Start the discovery to initialize internal state - fixture.extensionMcpDiscovery.start(); - - // First add an extension to be removed later - const mockExtension = createMockExtensionPointUser('removable-collection', 'Removable Collection'); - const addDelta = createMockDelta([mockExtension], []); - - // Spy on registry to monitor any new registrations during removal - const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], addDelta); - - registerCollectionSpy.resetHistory(); // Clear any previous calls - - // Now remove the extension - const removeDelta = createMockDelta([], [mockExtension]); - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], removeDelta); - - // Verify that no new registrations occurred during removal - assert.ok(registerCollectionSpy.notCalled, 'No new collections should be registered when extensions are removed'); - registerCollectionSpy.restore(); - }); - - test('should skip invalid extensions', () => { - // Start the discovery to initialize internal state - fixture.extensionMcpDiscovery.start(); - - const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - - // Create mock extension data with invalid structure (empty id should fail validation) - const invalidExtension = createMockExtensionPointUser('', 'Invalid Collection'); // Empty id - const invalidDelta = createMockDelta([invalidExtension], []); - - // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], invalidDelta); - - // Verify that registerCollection was not called for invalid extension - assert.ok(registerCollectionSpy.notCalled, 'registerCollection should not be called for invalid extensions'); - - registerCollectionSpy.restore(); - }); - - test('should handle mixed add and remove operations', () => { - // Start the discovery to initialize internal state - fixture.extensionMcpDiscovery.start(); - - // First add an extension to be removed later - const existingExtension = createMockExtensionPointUser('existing-collection', 'Existing Collection'); - const addExistingDelta = createMockDelta([existingExtension], []); - // Prepare spy for the mixed operation - const registerCollectionSpy = sinon.stub(fixture.mcpRegistry, 'registerCollection').returns({ dispose: sinon.stub() }); - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], addExistingDelta); - // Create mixed delta: add new extension, remove existing one - const newExtension = createMockExtensionPointUser('new-collection', 'New Collection', 'new.extension'); - const mixedDelta = createMockDelta([newExtension], [existingExtension]); - - // Reset spy call counts after initial setup - registerCollectionSpy.resetHistory(); - - // Call the method under test - fixture.extensionMcpDiscovery.handleExtensionChangeTest([], mixedDelta); - - // Verify new collection was registered (we can't easily test removal with current test setup) - assert.ok(registerCollectionSpy.calledOnce, 'New collection should be registered'); - - const registrationCall = registerCollectionSpy.getCall(0); - assert.strictEqual(registrationCall.args[0].id, 'new.extension/new-collection', 'New collection should have correct prefixed ID'); - - registerCollectionSpy.restore(); - }); - }); - // #endregion - - // #region Future Test Suites - // TODO: Add test suites for: - // - Collection Validation Details - // - Conditional Collections Context Changes - // - Storage Persistence Edge Cases - // - Error Handling - // #endregion - -}); From ad03e60118add4b748591cee0449f0742dd932a1 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:06:07 -0700 Subject: [PATCH 1645/4355] Fix initial chat title Makes sure we update the title after resolving the model and that we pass in an initial title --- .../workbench/contrib/chat/browser/chatEditorInput.ts | 3 ++- .../chat/browser/chatSessions/view/sessionsViewPane.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 9c7dbc47999..604d8f7d9cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -289,9 +289,10 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const newIcon = this.resolveIcon(); if (newIcon && (!this.cachedIcon || !this.iconsEqual(this.cachedIcon, newIcon))) { this.cachedIcon = newIcon; - this._onDidChangeLabel.fire(); } + this._onDidChangeLabel.fire(); + return this._register(new ChatEditorModel(this.model)); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 62dfcf4f98b..e8acea7b7c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -51,6 +51,8 @@ import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../common.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { GettingStartedDelegate, GettingStartedRenderer, IGettingStartedItem, SessionsDataSource, SessionsDelegate, SessionsRenderer } from './sessionsTreeRenderer.js'; +import { upcast } from '../../../../../../base/common/types.js'; +import { IEditorOptions } from '../../../../../../platform/editor/common/editor.js'; // Identity provider for session items class SessionsIdentityProvider { @@ -462,7 +464,13 @@ export class SessionsViewPane extends ViewPane { } if (session.resource.scheme !== ChatSessionUri.scheme) { - await this.openerService.open(session.resource); + await this.openerService.open(session.resource, { + editorOptions: upcast({ + title: { + preferred: session.label + } + }) + }); return; } From 03006f02bff8fb8b4420ca48019f38440e6949f4 Mon Sep 17 00:00:00 2001 From: Osvaldo Ortega <48293249+osortega@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:28:12 -0700 Subject: [PATCH 1646/4355] Chat sessions view hide if empty (#273215) --- .../contrib/chat/browser/chatSessions/view/chatSessionsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 0751274f09f..f0e05da8da9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -44,7 +44,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi id: AGENT_SESSIONS_VIEWLET_ID, title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer), - hideIfEmpty: false, + hideIfEmpty: true, icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); From 9d4166232c75724c6c16a9089607d69e054028b8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 25 Oct 2025 08:45:55 +0200 Subject: [PATCH 1647/4355] chat - fix CSS of working set (#273157) * chat - fix CSS of working set The working set box was put with negative margins and did overflow down into the chat input. * adopt for todos * Update src/vs/workbench/contrib/chat/browser/media/chat.css Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * more alignemnts in todo widget --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../chatContentParts/chatTodoListWidget.ts | 6 +++--- .../contrib/chat/browser/media/chat.css | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 8608e038c5e..6df089ee23e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -107,12 +107,12 @@ class TodoListRenderer implements IListRenderer { private getStatusIconClass(status: string): string { switch (status) { case 'completed': - return 'codicon-check'; + return 'codicon-pass'; case 'in-progress': return 'codicon-record'; case 'not-started': default: - return 'codicon-circle-large-outline'; + return 'codicon-circle-outline'; } } @@ -477,7 +477,7 @@ export class ChatTodoListWidget extends Disposable { icon.classList.add('codicon-record'); icon.style.color = 'var(--vscode-charts-blue)'; } else { - icon.classList.add('codicon-circle-large-outline'); + icon.classList.add('codicon-circle-outline'); icon.style.color = 'var(--vscode-foreground)'; } icon.style.marginLeft = '4px'; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index b2cf7d39645..f447e81cd22 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -763,20 +763,19 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-editing-session { - margin-bottom: -4px; + margin-bottom: -4px; /* Counter the flex gap */ /* reset the 4px gap of the container for editing sessions */ width: 100%; position: relative; } .interactive-session .chat-editing-session .chat-editing-session-container { - margin-bottom: -13px; - padding: 6px 8px 18px 8px; + padding: 6px 3px 6px 3px; box-sizing: border-box; background-color: var(--vscode-editor-background); border: 1px solid var(--vscode-input-border, transparent); border-bottom: none; - border-radius: 4px; + border-radius: 4px 4px 0 0; display: flex; flex-direction: column; gap: 2px; @@ -1094,7 +1093,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { - padding: 6px 8px 5px 12px; + padding: 6px 3px 6px 3px; box-sizing: border-box; border: 1px solid var(--vscode-input-border, transparent); background-color: var(--vscode-editor-background); @@ -1120,6 +1119,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section { + padding-left: 3px; display: flex; align-items: center; gap: 4px; @@ -1130,7 +1130,6 @@ have to be updated for changes to the rules above, or to support more deeply nes overflow: hidden; text-overflow: ellipsis; line-height: 16px; - margin-left: -1px; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .todo-list-title-section .codicon { @@ -1140,6 +1139,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-clear-button-container { + padding-right: 2px; display: flex; align-items: center; opacity: 1; @@ -1247,6 +1247,10 @@ have to be updated for changes to the rules above, or to support more deeply nes min-width: 0; } +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .monaco-scrollable-element .monaco-list-rows .monaco-list-row { + border-radius: 2px; +} + .interactive-session .interactive-input-part.compact .chat-input-container { display: flex; justify-content: space-between; From ebec808605d890e079c52c6996c95be7cc94605e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sat, 25 Oct 2025 08:54:18 +0200 Subject: [PATCH 1648/4355] SCM - repository renderer polish (#273249) * SCM - repository renderer polish * Keep the spacing --- src/vs/workbench/contrib/scm/browser/media/scm.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 4169f639c25..79416fd0db1 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -45,7 +45,7 @@ display: none; } -.scm-view .scm-provider > .icon { +.scm-view .scm-provider > .codicon { padding-right: 2px; } @@ -94,8 +94,8 @@ .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(2) { min-width: 20px; - .action-label > span:nth-child(1) { - margin-right: 2px; + .action-label > span:not(.codicon) { + margin-left: 2px; } } From 377f08975d38c23ae034916f5a1e00566f30934e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 25 Oct 2025 10:46:24 +0200 Subject: [PATCH 1649/4355] Clicking OS notification always opens secondary side bar, even if I'm working with Chat in editor (fix #270619) (#273252) --- .../chat/browser/chatAccessibilityService.ts | 2 +- .../chatConfirmationContentPart.ts | 2 +- .../chatConfirmationWidget.ts | 160 ++++++++---------- .../chatElicitationContentPart.ts | 2 +- .../abstractToolConfirmationSubPart.ts | 2 +- .../chatExtensionsInstallToolSubPart.ts | 2 +- .../chatTerminalToolConfirmationSubPart.ts | 2 +- 7 files changed, 75 insertions(+), 97 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 9ece65d4f73..18bb6b2583a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -129,7 +129,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi disposables.add(Event.once(notification.onClick)(async () => { await this._hostService.focus(targetWindow, { mode: FocusMode.Force }); await this._instantiationService.invokeFunction(showChatWidgetInViewOrEditor, widget); - widget.input.focus(); + widget.focusInput(); disposables.dispose(); this.notifications.delete(disposables); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts index 97773d6afb1..4cde29a6de3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -40,7 +40,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont { label: localize('accept', "Accept"), data: confirmation.data }, { label: localize('dismiss', "Dismiss"), data: confirmation.data, isSecondary: true }, ]; - const confirmationWidget = this._register(this.instantiationService.createInstance(SimpleChatConfirmationWidget, context.container, { title: confirmation.title, buttons, message: confirmation.message, silent: confirmation.isLive === false })); + const confirmationWidget = this._register(this.instantiationService.createInstance(SimpleChatConfirmationWidget, context, { title: confirmation.title, buttons, message: confirmation.message, silent: confirmation.isLive === false })); confirmationWidget.setShowButtons(!confirmation.isUsed); this._register(confirmationWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 9fa0b918370..725ff39e21d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './media/chatConfirmationWidget.css'; import * as dom from '../../../../../base/browser/dom.js'; import { IRenderedMarkdown, renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js'; import { Button, ButtonWithDropdown, IButton, IButtonOptions } from '../../../../../base/browser/ui/button/button.js'; @@ -23,10 +24,8 @@ import { ServiceCollection } from '../../../../../platform/instantiation/common/ import { FocusMode } from '../../../../../platform/native/common/native.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { IHostService } from '../../../../services/host/browser/host.js'; -import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { showChatView } from '../chat.js'; -import './media/chatConfirmationWidget.css'; -import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; +import { IChatWidgetService, showChatWidgetInViewOrEditor } from '../chat.js'; +import { IChatContentPartRenderContext } from './chatContentParts.js'; export interface IChatConfirmationButton { label: string; @@ -107,6 +106,53 @@ export class ChatQueryTitlePart extends Disposable { } } +class ChatConfirmationNotifier extends Disposable { + + private readonly disposables = this._register(new MutableDisposable()); + + constructor( + @IHostService private readonly _hostService: IHostService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + ) { + super(); + } + + async notify(targetWindow: Window, title: string | IMarkdownString, sessionId: string): Promise { + + // Focus Window + this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + + // Notify + const titleText = renderAsPlaintext(title); + const notification = await dom.triggerNotification(titleText ? localize('notificationTitle', "Chat: {0}", titleText) : localize('defaultTitle', "Chat: Confirmation Required"), + { + detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed.") + } + ); + if (notification) { + const disposables = this.disposables.value = new DisposableStore(); + disposables.add(notification); + + disposables.add(Event.once(notification.onClick)(async () => { + await this._hostService.focus(targetWindow, { mode: FocusMode.Force }); + const widget = this._chatWidgetService.getWidgetBySessionId(sessionId); + if (widget) { + await this._instantiationService.invokeFunction(showChatWidgetInViewOrEditor, widget); + widget.focusInput(); + } + disposables.dispose(); + })); + + disposables.add(this._hostService.onDidChangeFocus(focus => { + if (focus) { + disposables.dispose(); + } + })); + } + } +} + abstract class BaseSimpleChatConfirmationWidget extends Disposable { private _onDidClick = this._register(new Emitter>()); get onDidClick(): Event> { return this._onDidClick.event; } @@ -131,18 +177,16 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { private readonly title: string | IMarkdownString; private readonly silent: boolean; - private readonly notification = this._register(new MutableDisposable()); + private readonly notificationManager: ChatConfirmationNotifier; constructor( + protected readonly context: IChatContentPartRenderContext, options: IChatConfirmationWidgetOptions, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IMarkdownRendererService protected readonly _markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IHostService private readonly _hostService: IHostService, - @IViewsService private readonly _viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -150,6 +194,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { this.title = title; this.silent = !!silent; + this.notificationManager = this._register(instantiationService.createInstance(ChatConfirmationNotifier)); const elements = dom.h('.chat-confirmation-widget-container@container', [ dom.h('.chat-confirmation-widget@root', [ @@ -241,39 +286,10 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation') && !this.silent) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { - this.notifyConfirmationNeeded(targetWindow); + this.notificationManager.notify(targetWindow, this.title, this.context.element.sessionId); } } } - - private async notifyConfirmationNeeded(targetWindow: Window): Promise { - - // Focus Window - this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); - - // Notify - const title = renderAsPlaintext(this.title); - const notification = await dom.triggerNotification(title ? localize('notificationTitle', "Chat: {0}", title) : localize('defaultTitle', "Chat: Confirmation Required"), - { - detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed.") - } - ); - if (notification) { - const disposables = this.notification.value = new DisposableStore(); - disposables.add(notification); - - disposables.add(Event.once(notification.onClick)(() => { - this._hostService.focus(targetWindow, { mode: FocusMode.Force }); - showChatView(this._viewsService, this._layoutService); - })); - - disposables.add(this._hostService.onDidChangeFocus(focus => { - if (focus) { - disposables.dispose(); - } - })); - } - } } /** @deprecated Use ChatConfirmationWidget instead */ @@ -281,18 +297,15 @@ export class SimpleChatConfirmationWidget extends BaseSimpleChatConfirmationW private _renderedMessage: HTMLElement | undefined; constructor( - private readonly _container: HTMLElement, + context: IChatContentPartRenderContext, options: IChatConfirmationWidgetOptions, @IInstantiationService instantiationService: IInstantiationService, @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, - @IHostService hostService: IHostService, - @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); + super(context, options, instantiationService, markdownRendererService, contextMenuService, configurationService, contextKeyService); this.updateMessage(options.message); } @@ -302,7 +315,7 @@ export class SimpleChatConfirmationWidget extends BaseSimpleChatConfirmationW typeof message === 'string' ? new MarkdownString(message) : message, { asyncRenderCallback: () => this._onDidChangeHeight.fire() } )); - this.renderMessage(renderedMessage.element, this._container); + this.renderMessage(renderedMessage.element, this.context.container); this._renderedMessage = renderedMessage.element; } } @@ -341,24 +354,24 @@ abstract class BaseChatConfirmationWidget extends Disposable { private readonly messageElement: HTMLElement; private readonly title: string | IMarkdownString; - private readonly notification = this._register(new MutableDisposable()); + private readonly notificationManager: ChatConfirmationNotifier; constructor( + protected readonly _context: IChatContentPartRenderContext, options: IChatConfirmationWidget2Options, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IMarkdownRendererService protected readonly markdownRendererService: IMarkdownRendererService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IHostService private readonly _hostService: IHostService, - @IViewsService private readonly _viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); const { title, subtitle, message, buttons, icon } = options; this.title = title; + this.notificationManager = this._register(instantiationService.createInstance(ChatConfirmationNotifier)); + const elements = dom.h('.chat-confirmation-widget-container@container', [ dom.h('.chat-confirmation-widget2@root', [ dom.h('.chat-confirmation-widget-title', [ @@ -471,37 +484,8 @@ abstract class BaseChatConfirmationWidget extends Disposable { if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { - this.notifyConfirmationNeeded(targetWindow); - } - } - } - - private async notifyConfirmationNeeded(targetWindow: Window): Promise { - - // Focus Window - this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); - - // Notify - const title = renderAsPlaintext(this.title); - const notification = await dom.triggerNotification(title ? localize('notificationTitle', "Chat: {0}", title) : localize('defaultTitle', "Chat: Confirmation Required"), - { - detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed.") + this.notificationManager.notify(targetWindow, this.title, this._context.element.sessionId); } - ); - if (notification) { - const disposables = this.notification.value = new DisposableStore(); - disposables.add(notification); - - disposables.add(Event.once(notification.onClick)(() => { - this._hostService.focus(targetWindow, { mode: FocusMode.Force }); - showChatView(this._viewsService, this._layoutService); - })); - - disposables.add(this._hostService.onDidChangeFocus(focus => { - if (focus) { - disposables.dispose(); - } - })); } } } @@ -509,19 +493,16 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget { private _renderedMessage: HTMLElement | undefined; constructor( - private readonly _container: HTMLElement, + context: IChatContentPartRenderContext, options: IChatConfirmationWidget2Options, @IInstantiationService instantiationService: IInstantiationService, @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, - @IHostService hostService: IHostService, - @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); - this.renderMessage(options.message, this._container); + super(context, options, instantiationService, markdownRendererService, contextMenuService, configurationService, contextKeyService); + this.renderMessage(options.message, context.container); } public updateMessage(message: string | IMarkdownString): void { @@ -530,25 +511,22 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget { typeof message === 'string' ? new MarkdownString(message) : message, { asyncRenderCallback: () => this._onDidChangeHeight.fire() } )); - this.renderMessage(renderedMessage.element, this._container); + this.renderMessage(renderedMessage.element, this._context.container); this._renderedMessage = renderedMessage.element; } } export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget { constructor( - container: HTMLElement, + context: IChatContentPartRenderContext, options: IChatConfirmationWidget2Options, @IInstantiationService instantiationService: IInstantiationService, @IMarkdownRendererService markdownRendererService: IMarkdownRendererService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, - @IHostService hostService: IHostService, - @IViewsService viewsService: IViewsService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super(options, instantiationService, markdownRendererService, contextMenuService, configurationService, hostService, viewsService, contextKeyService, layoutService); - this.renderMessage(options.message, container); + super(context, options, instantiationService, markdownRendererService, contextMenuService, configurationService, contextKeyService); + this.renderMessage(options.message, context.container); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts index 09a26ea596d..38ec3f5a058 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts @@ -43,7 +43,7 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte if (elicitation.rejectButtonLabel && elicitation.reject) { buttons.push({ label: elicitation.rejectButtonLabel, data: false, isSecondary: true }); } - const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, context.container, { + const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, context, { title: elicitation.title, subtitle: elicitation.subtitle, buttons, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts index 175e88021e9..9c9cb351ff9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts @@ -88,7 +88,7 @@ export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvoca const tool = languageModelToolsService.getTool(toolInvocation.toolId); const confirmWidget = this._register(this.instantiationService.createInstance( ChatCustomConfirmationWidget void)>, - this.context.container, + this.context, { title: this.getTitle(), icon: tool?.icon && 'id' in tool.icon ? tool.icon : Codicon.tools, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts index b6dd38169b4..6ed5bc64375 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts @@ -75,7 +75,7 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo const confirmWidget = this._register(instantiationService.createInstance( ChatConfirmationWidget, - context.container, + context, { title: toolInvocation.confirmationMessages?.title ?? localize('installExtensions', "Install Extensions"), message: toolInvocation.confirmationMessages?.message ?? localize('installExtensionsConfirmation', "Click the Install button on the extension and then press Allow when finished."), diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 5e1a6503f7d..34137f92239 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -190,7 +190,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS })); const confirmWidget = this._register(this.instantiationService.createInstance( ChatCustomConfirmationWidget, - this.context.container, + this.context, { title, icon: Codicon.terminal, From baf06734a500e464fec51234e7197ba203941377 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sat, 25 Oct 2025 11:13:10 +0200 Subject: [PATCH 1650/4355] SCM - more repository renderer cleanup (#273257) --- .../contrib/scm/browser/scmRepositoryRenderer.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index e0bc77020a5..bc4b8c74bb5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -118,15 +118,13 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer Date: Sat, 25 Oct 2025 02:22:16 -0700 Subject: [PATCH 1651/4355] Deduplicate and format entries in terminal's Go To quick pick (#273008) * Deduplicate and format items in terminal's Go To quick pick * Add unit-tests * PR feedback, add missing unit-tests * PR feedback --- .../contrib/terminal/browser/terminal.ts | 6 ++ .../terminal/browser/terminalInstance.ts | 8 ++- .../terminal/common/terminalEnvironment.ts | 24 ++++++- .../test/common/terminalEnvironment.test.ts | 69 ++++++++++++++----- .../browser/terminalRunRecentQuickPick.ts | 36 +++++++--- 5 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index de55c4d52b6..d479aa32a77 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -1039,6 +1039,12 @@ export interface ITerminalInstance extends IBaseTerminalInstance { */ preparePathForShell(originalPath: string): Promise; + /** + * Formats a file system URI for display in UI so that it appears in the terminal shell's format. + * @param uri The URI to format. + */ + getUriLabelForShell(uri: URI): Promise; + /** Scroll the terminal buffer down 1 line. */ scrollDownLine(): void; /** Scroll the terminal buffer down 1 page. */ scrollDownPage(): void; /** Scroll the terminal buffer to the bottom. */ scrollToBottom(): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 99c6d385c37..3168e53a12c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -72,7 +72,7 @@ import { IEnvironmentVariableInfo } from '../common/environmentVariable.js'; import { DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalProcessManager, ITerminalProfileResolverService, ProcessState, TERMINAL_CREATION_COMMANDS, TERMINAL_VIEW_ID, TerminalCommandId } from '../common/terminal.js'; import { TERMINAL_BACKGROUND_COLOR } from '../common/terminalColorRegistry.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; -import { getShellIntegrationTimeout, getWorkspaceForTerminal, preparePathForShell } from '../common/terminalEnvironment.js'; +import { getUriLabelForShell, getShellIntegrationTimeout, getWorkspaceForTerminal, preparePathForShell } from '../common/terminalEnvironment.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IHistoryService } from '../../../services/history/common/history.js'; @@ -1343,6 +1343,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return preparePathForShell(originalPath, this.shellLaunchConfig.executable, this.title, this.shellType, this._processManager.backend, this._processManager.os); } + async getUriLabelForShell(uri: URI): Promise { + // Wait for shell type to be ready + await this.processReady; + return getUriLabelForShell(uri, this._processManager.backend!, this.shellType, this.os); + } + setVisible(visible: boolean): void { const didChange = this._isVisible !== visible; this._isVisible = visible; diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 8e42cecb139..a0e95b1b934 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -8,7 +8,7 @@ */ import * as path from '../../../../base/common/path.js'; -import { URI } from '../../../../base/common/uri.js'; +import { URI, uriToFsPath } from '../../../../base/common/uri.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js'; import { sanitizeProcessEnvironment } from '../../../../base/common/processes.js'; @@ -315,7 +315,7 @@ export async function createTerminalEnvironment( * @param backend The backend for the terminal. * @param isWindowsFrontend Whether the frontend is Windows, this is only exposed for injection via * tests. - * @returns An escaped version of the path to be execuded in the terminal. + * @returns An escaped version of the path to be executed in the terminal. */ export async function preparePathForShell(resource: string | URI, executable: string | undefined, title: string, shellType: TerminalShellType | undefined, backend: Pick | undefined, os: OperatingSystem | undefined, isWindowsFrontend: boolean = isWindows): Promise { let originalPath: string; @@ -344,7 +344,6 @@ export async function preparePathForShell(resource: string | URI, executable: st pathBasename === 'powershell' || title === 'powershell'; - if (isPowerShell && (hasSpace || originalPath.includes('\''))) { return `& '${originalPath.replace(/'/g, '\'\'')}'`; } @@ -392,6 +391,25 @@ export function getWorkspaceForTerminal(cwd: URI | string | undefined, workspace return workspaceFolder; } +export async function getUriLabelForShell(uri: URI | string, backend: Pick, shellType?: TerminalShellType, os?: OperatingSystem, isWindowsFrontend: boolean = isWindows): Promise { + let path = typeof uri === 'string' ? uri : uri.fsPath; + if (os === OperatingSystem.Windows) { + if (shellType === WindowsShellType.Wsl) { + return backend.getWslPath(path.replaceAll('/', '\\'), 'win-to-unix'); + } else if (shellType === WindowsShellType.GitBash) { + // Convert \ to / and replace 'c:\' with '/c/'. + return path.replaceAll('\\', '/').replace(/^([a-zA-Z]):\//, '/$1/'); + } else { + // If the frontend is not Windows but the terminal is, convert / to \. + path = typeof uri === 'string' ? path : uriToFsPath(uri, true); + return !isWindowsFrontend ? path.replaceAll('/', '\\') : path; + } + } else { + // If the frontend is Windows but the terminal is not, convert \ to /. + return isWindowsFrontend ? path.replaceAll('\\', '/') : path; + } +} + /** * Gets the unified duration to wait for shell integration after the terminal launches before * declaring the terminal lacks shell integration. diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts index 79796d480fe..dad61911059 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts @@ -7,10 +7,29 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { IStringDictionary } from '../../../../../base/common/collections.js'; import { isWindows, OperatingSystem } from '../../../../../base/common/platform.js'; import { URI as Uri } from '../../../../../base/common/uri.js'; -import { addTerminalEnvironmentKeys, createTerminalEnvironment, getCwd, getLangEnvVariable, mergeEnvironments, preparePathForShell, shouldSetLangEnvVariable } from '../../common/terminalEnvironment.js'; +import { addTerminalEnvironmentKeys, createTerminalEnvironment, getUriLabelForShell, getCwd, getLangEnvVariable, mergeEnvironments, preparePathForShell, shouldSetLangEnvVariable } from '../../common/terminalEnvironment.js'; import { GeneralShellType, PosixShellType, WindowsShellType } from '../../../../../platform/terminal/common/terminal.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +const wslPathBackend = { + getWslPath: async (original: string, direction: 'unix-to-win' | 'win-to-unix') => { + if (direction === 'unix-to-win') { + const match = original.match(/^\/mnt\/(?[a-zA-Z])\/(?.+)$/); + const groups = match?.groups; + if (!groups) { + return original; + } + return `${groups.drive}:\\${groups.path.replace(/\//g, '\\')}`; + } + const match = original.match(/(?[a-zA-Z]):\\(?.+)/); + const groups = match?.groups; + if (!groups) { + return original; + } + return `/mnt/${groups.drive.toLowerCase()}/${groups.path.replace(/\\/g, '/')}`; + } +}; + suite('Workbench - TerminalEnvironment', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -214,24 +233,6 @@ suite('Workbench - TerminalEnvironment', () => { }); suite('preparePathForShell', () => { - const wslPathBackend = { - getWslPath: async (original: string, direction: 'unix-to-win' | 'win-to-unix') => { - if (direction === 'unix-to-win') { - const match = original.match(/^\/mnt\/(?[a-zA-Z])\/(?.+)$/); - const groups = match?.groups; - if (!groups) { - return original; - } - return `${groups.drive}:\\${groups.path.replace(/\//g, '\\')}`; - } - const match = original.match(/(?[a-zA-Z]):\\(?.+)/); - const groups = match?.groups; - if (!groups) { - return original; - } - return `/mnt/${groups.drive.toLowerCase()}/${groups.path.replace(/\\/g, '/')}`; - } - }; suite('Windows frontend, Windows backend', () => { test('Command Prompt', async () => { strictEqual(await preparePathForShell('c:\\foo\\bar', 'cmd', 'cmd', WindowsShellType.CommandPrompt, wslPathBackend, OperatingSystem.Windows, true), `c:\\foo\\bar`); @@ -319,4 +320,34 @@ suite('Workbench - TerminalEnvironment', () => { ); }); }); + suite('formatUriForShellDisplay', () => { + test('Wsl', async () => { + strictEqual(await getUriLabelForShell('c:\\foo\\bar', wslPathBackend, WindowsShellType.Wsl, OperatingSystem.Windows, true), '/mnt/c/foo/bar'); + strictEqual(await getUriLabelForShell('c:/foo/bar', wslPathBackend, WindowsShellType.Wsl, OperatingSystem.Windows, false), '/mnt/c/foo/bar'); + }); + test('GitBash', async () => { + strictEqual(await getUriLabelForShell('c:\\foo\\bar', wslPathBackend, WindowsShellType.GitBash, OperatingSystem.Windows, true), '/c/foo/bar'); + strictEqual(await getUriLabelForShell('c:/foo/bar', wslPathBackend, WindowsShellType.GitBash, OperatingSystem.Windows, false), '/c/foo/bar'); + }); + suite('PowerShell', () => { + test('Windows frontend', async () => { + strictEqual(await getUriLabelForShell('c:\\foo\\bar', wslPathBackend, GeneralShellType.PowerShell, OperatingSystem.Windows, true), 'c:\\foo\\bar'); + strictEqual(await getUriLabelForShell('C:\\Foo\\Bar', wslPathBackend, GeneralShellType.PowerShell, OperatingSystem.Windows, true), 'C:\\Foo\\Bar'); + }); + test('Non-Windows frontend', async () => { + strictEqual(await getUriLabelForShell('c:/foo/bar', wslPathBackend, GeneralShellType.PowerShell, OperatingSystem.Windows, false), 'c:\\foo\\bar'); + strictEqual(await getUriLabelForShell('C:/Foo/Bar', wslPathBackend, GeneralShellType.PowerShell, OperatingSystem.Windows, false), 'C:\\Foo\\Bar'); + }); + }); + suite('Bash', () => { + test('Windows frontend', async () => { + strictEqual(await getUriLabelForShell('\\foo\\bar', wslPathBackend, PosixShellType.Bash, OperatingSystem.Linux, true), '/foo/bar'); + strictEqual(await getUriLabelForShell('/foo/bar', wslPathBackend, PosixShellType.Bash, OperatingSystem.Linux, true), '/foo/bar'); + }); + test('Non-Windows frontend', async () => { + strictEqual(await getUriLabelForShell('\\foo\\bar', wslPathBackend, PosixShellType.Bash, OperatingSystem.Linux, false), '\\foo\\bar'); + strictEqual(await getUriLabelForShell('/foo/bar', wslPathBackend, PosixShellType.Bash, OperatingSystem.Linux, false), '/foo/bar'); + }); + }); + }); }); diff --git a/src/vs/workbench/contrib/terminalContrib/history/browser/terminalRunRecentQuickPick.ts b/src/vs/workbench/contrib/terminalContrib/history/browser/terminalRunRecentQuickPick.ts index be6fde5fe2a..51cd32ab0dd 100644 --- a/src/vs/workbench/contrib/terminalContrib/history/browser/terminalRunRecentQuickPick.ts +++ b/src/vs/workbench/contrib/terminalContrib/history/browser/terminalRunRecentQuickPick.ts @@ -28,6 +28,9 @@ import { IContextKey } from '../../../../../platform/contextkey/common/contextke import { AccessibleViewProviderId, IAccessibleViewService } from '../../../../../platform/accessibility/browser/accessibleView.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { getCommandHistory, getDirectoryHistory, getShellFileHistory } from '../common/history.js'; +import { ResourceSet } from '../../../../../base/common/map.js'; +import { extUri, extUriIgnorePathCase } from '../../../../../base/common/resources.js'; +import { IPathService } from '../../../../services/path/common/pathService.js'; export async function showRunRecentQuickPick( accessor: ServicesAccessor, @@ -46,6 +49,7 @@ export async function showRunRecentQuickPick( const instantiationService = accessor.get(IInstantiationService); const quickInputService = accessor.get(IQuickInputService); const storageService = accessor.get(IStorageService); + const pathService = accessor.get(IPathService); const runRecentStorageKey = `${TerminalStorageKeys.PinnedRecentCommandsPrefix}.${instance.shellType}`; let placeholder: string; @@ -198,10 +202,22 @@ export async function showRunRecentQuickPick( placeholder = isMacintosh ? localize('selectRecentDirectoryMac', 'Select a directory to go to (hold Option-key to edit the command)') : localize('selectRecentDirectory', 'Select a directory to go to (hold Alt-key to edit the command)'); + + // Check path uniqueness following target platform's case sensitivity rules. + const uriComparer = instance.os === OperatingSystem.Windows ? extUriIgnorePathCase : extUri; + const uniqueUris = new ResourceSet(o => uriComparer.getComparisonKey(o)); + const cwds = instance.capabilities.get(TerminalCapability.CwdDetection)?.cwds || []; if (cwds && cwds.length > 0) { for (const label of cwds) { - items.push({ label, rawLabel: label }); + const itemUri = URI.file(label); + if (!uniqueUris.has(itemUri)) { + uniqueUris.add(itemUri); + items.push({ + label: await instance.getUriLabelForShell(itemUri), + rawLabel: label + }); + } } items = items.reverse(); items.unshift({ type: 'separator', label: terminalStrings.currentSessionCategory }); @@ -212,12 +228,16 @@ export async function showRunRecentQuickPick( const previousSessionItems: (IQuickPickItem & { rawLabel: string })[] = []; // Only add previous session item if it's not in this session and it matches the remote authority for (const [label, info] of history.entries) { - if ((info === null || info.remoteAuthority === instance.remoteAuthority) && !cwds.includes(label)) { - previousSessionItems.unshift({ - label, - rawLabel: label, - buttons: [removeFromCommandHistoryButton] - }); + if (info === null || info.remoteAuthority === instance.remoteAuthority) { + const itemUri = info?.remoteAuthority ? await pathService.fileURI(label) : URI.file(label); + if (!uniqueUris.has(itemUri)) { + uniqueUris.add(itemUri); + previousSessionItems.unshift({ + label: await instance.getUriLabelForShell(itemUri), + rawLabel: label, + buttons: [removeFromCommandHistoryButton] + }); + } } } if (previousSessionItems.length > 0) { @@ -255,7 +275,7 @@ export async function showRunRecentQuickPick( if (type === 'command') { instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label); } else { - instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label); + instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.rawLabel); } } else if (e.button === commandOutputButton) { const selectedCommand = (e.item as Item).command; From b808edabcfbf07206d0c57d42723377b8c8e55fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 25 Oct 2025 03:42:19 -0700 Subject: [PATCH 1652/4355] Add basic copilot fig spec Fixes #272921 --- .../src/completions/copilot.ts | 164 ++++++++++++++++++ .../src/terminalSuggestMain.ts | 2 + 2 files changed, 166 insertions(+) create mode 100644 extensions/terminal-suggest/src/completions/copilot.ts diff --git a/extensions/terminal-suggest/src/completions/copilot.ts b/extensions/terminal-suggest/src/completions/copilot.ts new file mode 100644 index 00000000000..6457479e708 --- /dev/null +++ b/extensions/terminal-suggest/src/completions/copilot.ts @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const copilotSpec: Fig.Spec = { + name: 'copilot', + description: 'GitHub Copilot CLI - An AI-powered coding assistant', + options: [ + { + name: '--add-dir', + description: 'Add a directory to the allowed list for file access (can be used multiple times)', + args: { + name: 'directory', + template: 'folders' + }, + isRepeatable: true + }, + { + name: '--additional-mcp-config', + description: 'Additional MCP servers configuration as JSON string or file path (prefix with @)', + args: { + name: 'json', + description: 'JSON string or file path (prefix with @)' + }, + isRepeatable: true + }, + { + name: '--allow-all-paths', + description: 'Disable file path verification and allow access to any path' + }, + { + name: '--allow-all-tools', + description: 'Allow all tools to run automatically without confirmation; required for non-interactive mode' + }, + { + name: '--allow-tool', + description: 'Allow specific tools', + args: { + name: 'tools', + isVariadic: true, + isOptional: true + } + }, + { + name: '--banner', + description: 'Show the startup banner' + }, + { + name: '--continue', + description: 'Resume the most recent session' + }, + { + name: '--deny-tool', + description: 'Deny specific tools, takes precedence over --allow-tool or --allow-all-tools', + args: { + name: 'tools', + isVariadic: true, + isOptional: true + } + }, + { + name: '--disable-builtin-mcps', + description: 'Disable all built-in MCP servers (currently: github-mcp-server)' + }, + { + name: '--disable-mcp-server', + description: 'Disable a specific MCP server (can be used multiple times)', + args: { + name: 'server-name' + }, + isRepeatable: true + }, + { + name: '--disable-parallel-tools-execution', + description: 'Disable parallel execution of tools (LLM can still make parallel tool calls, but they will be executed sequentially)' + }, + { + name: '--disallow-temp-dir', + description: 'Prevent automatic access to the system temporary directory' + }, + { + name: ['-h', '--help'], + description: 'Display help for command' + }, + { + name: '--log-dir', + description: 'Set log file directory (default: ~/.copilot/logs/)', + args: { + name: 'directory', + template: 'folders' + } + }, + { + name: '--log-level', + description: 'Set the log level', + args: { + name: 'level', + suggestions: ['none', 'error', 'warning', 'info', 'debug', 'all', 'default'] + } + }, + { + name: '--model', + description: 'Set the AI model to use', + args: { + name: 'model', + suggestions: ['claude-sonnet-4.5', 'claude-sonnet-4', 'claude-haiku-4.5', 'gpt-5'] + } + }, + { + name: '--no-color', + description: 'Disable all color output' + }, + { + name: '--no-custom-instructions', + description: 'Disable loading of custom instructions from AGENTS.md and related files' + }, + { + name: ['-p', '--prompt'], + description: 'Execute a prompt directly without interactive mode', + args: { + name: 'text', + description: 'The prompt text to execute' + } + }, + { + name: '--resume', + description: 'Resume from a previous session (optionally specify session ID)', + args: { + name: 'sessionId', + isOptional: true + } + }, + { + name: '--screen-reader', + description: 'Enable screen reader optimizations' + }, + { + name: '--stream', + description: 'Enable or disable streaming mode', + args: { + name: 'mode', + suggestions: ['on', 'off'] + } + }, + { + name: ['-v', '--version'], + description: 'Show version information' + } + ], + subcommands: [ + { + name: 'help', + description: 'Display help information', + args: { + name: 'topic', + isOptional: true, + suggestions: ['config', 'commands', 'environment', 'logging', 'permissions'] + } + } + ] +}; + +export default copilotSpec; diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 7ecc8affa05..13f2399d908 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -11,6 +11,7 @@ import codeCompletionSpec from './completions/code'; import codeInsidersCompletionSpec from './completions/code-insiders'; import codeTunnelCompletionSpec from './completions/code-tunnel'; import codeTunnelInsidersCompletionSpec from './completions/code-tunnel-insiders'; +import copilotSpec from './completions/copilot'; import gitCompletionSpec from './completions/git'; import ghCompletionSpec from './completions/gh'; import npxCompletionSpec from './completions/npx'; @@ -65,6 +66,7 @@ export const availableSpecs: Fig.Spec[] = [ codeCompletionSpec, codeTunnelCompletionSpec, codeTunnelInsidersCompletionSpec, + copilotSpec, gitCompletionSpec, ghCompletionSpec, npxCompletionSpec, From 3af7ff8ad2c1f388f2c4ce3d866c12b6b3e5a44e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 25 Oct 2025 04:29:59 -0700 Subject: [PATCH 1653/4355] Revert "remove comments from code block before running in the terminal (#270779)" This reverts commit 22deaf61a50ecf9f21137164c7afecbd30f5cd93. --- .../browser/actions/chatCodeblockActions.ts | 3 +- .../contrib/chat/common/codeBlockCleaning.ts | 56 ------------------- .../test/common/codeBlockCleaning.test.ts | 49 ---------------- 3 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts delete mode 100644 src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 2a5db594042..863d366db7d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -39,7 +39,6 @@ import { IChatCodeBlockContextProviderService, IChatWidgetService } from '../cha import { DefaultChatTextEditor, ICodeBlockActionContext, ICodeCompareBlockActionContext } from '../codeBlockPart.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { ApplyCodeBlockOperation, InsertCodeBlockOperation } from './codeBlockOperations.js'; -import { stripCommentsForShellExecution } from '../../common/codeBlockCleaning.js'; const shellLangIds = [ 'fish', @@ -483,7 +482,7 @@ export function registerChatCodeBlockActions() { terminalGroupService.showPanel(true); } - terminal.runCommand(stripCommentsForShellExecution(context.code), false); + terminal.runCommand(context.code, false); if (isResponseVM(context.element)) { chatService.notifyUserAction({ diff --git a/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts b/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts deleted file mode 100644 index 6a03100e4c4..00000000000 --- a/src/vs/workbench/contrib/chat/common/codeBlockCleaning.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Strip comments from a shell-like code snippet before executing it in the terminal. - * - * Behaviour: - * - Removes /* *\/ block comments (non-nested) across lines. - * - Removes // line comments unless part of a URL protocol pattern like `http://` or `file://`. - * - Removes # line comments when the hash is the first non-whitespace character (or at column 0), - * but preserves shebangs (#!) and leaves inline hashes that are part of tokens. - * - Preserves shebang line entirely. - * - Trims leading/trailing blank lines and surrounding whitespace at the very end. - */ -export function stripCommentsForShellExecution(code: string): string { - // Fast return for empty input - if (!code) { - return ''; - } - - // 1) Remove block comments - const withoutBlocks = code.replace(/\/\*[\s\S]*?\*\//g, ''); - - // 2) Process each line for // and # - const lines = withoutBlocks.split(/\r?\n/); - const processed = lines.map(line => { - // Preserve shebangs (e.g. #!/usr/bin/env bash) - if (/^\s*#!/.test(line)) { - return line; - } - - // Remove '//' comments unless inside a protocol pattern '://' - const slashIdx = line.indexOf('//'); - if (slashIdx >= 0) { - const protoIdx = line.lastIndexOf('://', slashIdx); - if (protoIdx === -1) { - line = line.slice(0, slashIdx); - } - } - - // Remove shell-style '#' comments unless it's a shebang (already handled) - // Treat as comment only if at start or preceded by whitespace - const hashIdx = line.indexOf('#'); - if (hashIdx >= 0) { - if (hashIdx === 0 || /\s/.test(line[hashIdx - 1])) { - line = line.slice(0, hashIdx); - } - } - - return line; - }); - - return processed.join('\n').trim(); -} diff --git a/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts b/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts deleted file mode 100644 index 9abf0d0aae7..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/codeBlockCleaning.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { stripCommentsForShellExecution } from '../../common/codeBlockCleaning.js'; - -suite('ChatCodeBlockCleaning', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('removes block comments', () => { - const input = 'echo 1; /* remove this */\necho 2'; - assert.strictEqual(stripCommentsForShellExecution(input), 'echo 1; \necho 2'); - }); - - test('removes line // comments but keeps protocols', () => { - const input = 'echo 1 // comment\n# full hash comment\nhttp://example.com//still'; - const expected = 'echo 1 \n\nhttp://example.com//still'; - assert.strictEqual(stripCommentsForShellExecution(input), expected); - }); - - test('preserves shebang', () => { - const input = '#!/usr/bin/env bash\n# comment\necho 1'; - const expected = '#!/usr/bin/env bash\n\necho 1'; - assert.strictEqual(stripCommentsForShellExecution(input), expected); - }); - - test('hash mid-line not preceded by space is preserved', () => { - const input = 'VAR=foo#bar\necho done'; - assert.strictEqual(stripCommentsForShellExecution(input), 'VAR=foo#bar\necho done'); - }); - - test('hash preceded by whitespace becomes comment', () => { - const input = 'echo 1 # trailing comment'; - assert.strictEqual(stripCommentsForShellExecution(input), 'echo 1'); - }); - - test('empty input', () => { - assert.strictEqual(stripCommentsForShellExecution(''), ''); - }); - - test('comment above input', () => { - const input = '# comment\necho 1'; - const expected = 'echo 1'; - assert.strictEqual(stripCommentsForShellExecution(input), expected); - }); -}); From 99364a3448a79cca47c3455bcd19b849aca83dd8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 25 Oct 2025 05:14:00 -0700 Subject: [PATCH 1654/4355] Move HoverService into platform Fixes #206228 --- .../components/gutterIndicatorView.ts | 4 +- .../standalone/browser/standaloneServices.ts | 2 +- .../hover/browser}/hover.css | 0 .../hover/browser}/hoverService.ts | 54 +++++++++---------- .../hover/browser}/hoverWidget.ts | 36 ++++++------- .../hover/browser}/updatableHoverWidget.ts | 18 +++---- src/vs/workbench/workbench.common.main.ts | 2 +- 7 files changed, 57 insertions(+), 59 deletions(-) rename src/vs/{editor/browser/services/hoverService => platform/hover/browser}/hover.css (100%) rename src/vs/{editor/browser/services/hoverService => platform/hover/browser}/hoverService.ts (90%) rename src/vs/{editor/browser/services/hoverService => platform/hover/browser}/hoverWidget.ts (94%) rename src/vs/{editor/browser/services/hoverService => platform/hover/browser}/updatableHoverWidget.ts (88%) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index 9d842398bd6..457171b97ad 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -18,8 +18,8 @@ import { IEditorMouseEvent } from '../../../../../../browser/editorBrowser.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { Point } from '../../../../../../common/core/2d/point.js'; import { Rect } from '../../../../../../common/core/2d/rect.js'; -import { HoverService } from '../../../../../../browser/services/hoverService/hoverService.js'; -import { HoverWidget } from '../../../../../../browser/services/hoverService/hoverWidget.js'; +import { HoverService } from '../../../../../../../platform/hover/browser/hoverService.js'; +import { HoverWidget } from '../../../../../../../platform/hover/browser/hoverWidget.js'; import { EditorOption, RenderLineNumbersType } from '../../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js'; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f715f0ebae2..f7ccab00378 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -9,7 +9,7 @@ import '../../../platform/undoRedo/common/undoRedoService.js'; import '../../common/services/languageFeatureDebounce.js'; import '../../common/services/semanticTokensStylingService.js'; import '../../common/services/languageFeaturesService.js'; -import '../../browser/services/hoverService/hoverService.js'; +import '../../../platform/hover/browser/hoverService.js'; import '../../browser/services/inlineCompletionsService.js'; import * as strings from '../../../base/common/strings.js'; diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/platform/hover/browser/hover.css similarity index 100% rename from src/vs/editor/browser/services/hoverService/hover.css rename to src/vs/platform/hover/browser/hover.css diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/platform/hover/browser/hoverService.ts similarity index 90% rename from src/vs/editor/browser/services/hoverService/hoverService.ts rename to src/vs/platform/hover/browser/hoverService.ts index b93fb6e88ca..6fff21328a9 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/platform/hover/browser/hoverService.ts @@ -3,34 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js'; -import { editorHoverBorder } from '../../../../platform/theme/common/colorRegistry.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; +import { registerThemingParticipant } from '../../theme/common/themeService.js'; +import { editorHoverBorder } from '../../theme/common/colorRegistry.js'; +import { IHoverService } from './hover.js'; +import { IContextMenuService } from '../../contextview/browser/contextView.js'; +import { IInstantiationService } from '../../instantiation/common/instantiation.js'; import { HoverWidget } from './hoverWidget.js'; -import { IContextViewProvider, IDelegate } from '../../../../base/browser/ui/contextview/contextview.js'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement, isEditableElement } from '../../../../base/browser/dom.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; -import { ResultKind } from '../../../../platform/keybinding/common/keybindingResolver.js'; -import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; -import { mainWindow } from '../../../../base/browser/window.js'; -import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import { isManagedHoverTooltipMarkdownString, type IHoverLifecycleOptions, type IHoverOptions, type IHoverWidget, type IManagedHover, type IManagedHoverContentOrFactory, type IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; -import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import { IContextViewProvider, IDelegate } from '../../../base/browser/ui/contextview/contextview.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement, isEditableElement } from '../../../base/browser/dom.js'; +import { IKeybindingService } from '../../keybinding/common/keybinding.js'; +import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; +import { ResultKind } from '../../keybinding/common/keybindingResolver.js'; +import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; +import { ILayoutService } from '../../layout/browser/layoutService.js'; +import { mainWindow } from '../../../base/browser/window.js'; +import { ContextViewHandler } from '../../contextview/browser/contextViewService.js'; +import { isManagedHoverTooltipMarkdownString, type IHoverLifecycleOptions, type IHoverOptions, type IHoverWidget, type IManagedHover, type IManagedHoverContentOrFactory, type IManagedHoverOptions } from '../../../base/browser/ui/hover/hover.js'; +import type { IHoverDelegate, IHoverDelegateTarget } from '../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; -import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { isNumber, isString } from '../../../../base/common/types.js'; -import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { EditorContextKeys } from '../../../common/editorContextKeys.js'; -import { IMarkdownString } from '../../../../base/common/htmlContent.js'; -import { stripIcons } from '../../../../base/common/iconLabels.js'; +import { timeout, TimeoutTimer } from '../../../base/common/async.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { isNumber, isString } from '../../../base/common/types.js'; +import { KeyChord, KeyCode, KeyMod } from '../../../base/common/keyCodes.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../keybinding/common/keybindingsRegistry.js'; +import { IMarkdownString } from '../../../base/common/htmlContent.js'; +import { stripIcons } from '../../../base/common/iconLabels.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -63,8 +62,7 @@ export class HoverService extends Disposable implements IHoverService { this._register(KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.showHover', - weight: KeybindingWeight.WorkbenchContrib - 1, - when: EditorContextKeys.editorTextFocus.negate(), + weight: KeybindingWeight.EditorCore, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyI), handler: () => { this._showAndFocusHoverForActiveElement(); }, })); diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/platform/hover/browser/hoverWidget.ts similarity index 94% rename from src/vs/editor/browser/services/hoverService/hoverWidget.ts rename to src/vs/platform/hover/browser/hoverWidget.ts index 6e393602895..f3aa8447477 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/platform/hover/browser/hoverWidget.ts @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import './hover.css'; -import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { Event, Emitter } from '../../../../base/common/event.js'; -import * as dom from '../../../../base/browser/dom.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { KeyCode } from '../../../../base/common/keyCodes.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import { Widget } from '../../../../base/browser/ui/widget.js'; -import { AnchorPosition } from '../../../../base/browser/ui/contextview/contextview.js'; -import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; -import { isMarkdownString } from '../../../../base/common/htmlContent.js'; -import { localize } from '../../../../nls.js'; -import { isMacintosh } from '../../../../base/common/platform.js'; -import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { status } from '../../../../base/browser/ui/aria/aria.js'; -import { HoverStyle, type IHoverOptions, type IHoverTarget, type IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; -import { TimeoutTimer } from '../../../../base/common/async.js'; -import { isNumber } from '../../../../base/common/types.js'; +import { DisposableStore, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { Event, Emitter } from '../../../base/common/event.js'; +import * as dom from '../../../base/browser/dom.js'; +import { IKeybindingService } from '../../keybinding/common/keybinding.js'; +import { KeyCode } from '../../../base/common/keyCodes.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { HoverAction, HoverPosition, HoverWidget as BaseHoverWidget, getHoverAccessibleViewHint } from '../../../base/browser/ui/hover/hoverWidget.js'; +import { Widget } from '../../../base/browser/ui/widget.js'; +import { AnchorPosition } from '../../../base/browser/ui/contextview/contextview.js'; +import { IMarkdownRendererService } from '../../markdown/browser/markdownRenderer.js'; +import { isMarkdownString } from '../../../base/common/htmlContent.js'; +import { localize } from '../../../nls.js'; +import { isMacintosh } from '../../../base/common/platform.js'; +import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; +import { status } from '../../../base/browser/ui/aria/aria.js'; +import { HoverStyle, type IHoverOptions, type IHoverTarget, type IHoverWidget } from '../../../base/browser/ui/hover/hover.js'; +import { TimeoutTimer } from '../../../base/common/async.js'; +import { isNumber } from '../../../base/common/types.js'; const $ = dom.$; type TargetRect = { diff --git a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts b/src/vs/platform/hover/browser/updatableHoverWidget.ts similarity index 88% rename from src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts rename to src/vs/platform/hover/browser/updatableHoverWidget.ts index 8aabaea90de..848c22a9578 100644 --- a/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts +++ b/src/vs/platform/hover/browser/updatableHoverWidget.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isHTMLElement } from '../../../../base/browser/dom.js'; -import { isManagedHoverTooltipMarkdownString, type IHoverWidget, type IManagedHoverContent, type IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; -import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; -import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; -import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; -import { isMarkdownString, type IMarkdownString } from '../../../../base/common/htmlContent.js'; -import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { isFunction, isString } from '../../../../base/common/types.js'; -import { localize } from '../../../../nls.js'; +import { isHTMLElement } from '../../../base/browser/dom.js'; +import { isManagedHoverTooltipMarkdownString, type IHoverWidget, type IManagedHoverContent, type IManagedHoverOptions } from '../../../base/browser/ui/hover/hover.js'; +import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from '../../../base/browser/ui/hover/hoverDelegate.js'; +import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; +import { CancellationTokenSource } from '../../../base/common/cancellation.js'; +import { isMarkdownString, type IMarkdownString } from '../../../base/common/htmlContent.js'; +import { IDisposable } from '../../../base/common/lifecycle.js'; +import { isFunction, isString } from '../../../base/common/types.js'; +import { localize } from '../../../nls.js'; type IManagedHoverResolvedContent = IMarkdownString | string | HTMLElement | undefined; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 71f45d748a8..f7a38101ec9 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -116,7 +116,7 @@ import './services/authentication/browser/authenticationMcpService.js'; import './services/authentication/browser/dynamicAuthenticationProviderStorageService.js'; import './services/authentication/browser/authenticationQueryService.js'; import './services/accounts/common/defaultAccount.js'; -import '../editor/browser/services/hoverService/hoverService.js'; +import '../platform/hover/browser/hoverService.js'; import './services/assignment/common/assignmentService.js'; import './services/outline/browser/outlineService.js'; import './services/languageDetection/browser/languageDetectionWorkerServiceImpl.js'; From c23478af52b09c278e21e8396a0e120eb134ae3a Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Sun, 26 Oct 2025 05:16:11 +1300 Subject: [PATCH 1655/4355] Fix soft wrapping on \n (#258407) * Fix soft wrapping on \n * Support infinite backslashes * Removing newline checking in `createLineBreaksFromPreviousLineBreaks()` * Check for double quote outside `createLineBreaks()` * Fix wrapping on long lines * Also check up front for the presence of \n --------- Co-authored-by: Alex Dima --- .../viewModel/monospaceLineBreaksComputer.ts | 42 +++++++++++-------- .../monospaceLineBreaksComputer.test.ts | 4 +- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 86c33d54f2a..37fba226625 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -42,10 +42,12 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa for (let i = 0, len = requests.length; i < len; i++) { const injectedText = injectedTexts[i]; const previousLineBreakData = previousBreakingData[i]; - if (previousLineBreakData && !previousLineBreakData.injectionOptions && !injectedText && !wrapOnEscapedLineFeeds) { - result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak); + const lineText = requests[i]; + const isLineFeedWrappingEnabled = wrapOnEscapedLineFeeds && lineText.includes('"') && lineText.includes('\\n'); + if (previousLineBreakData && !previousLineBreakData.injectionOptions && !injectedText && !isLineFeedWrappingEnabled) { + result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, lineText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak); } else { - result[i] = createLineBreaks(this.classifier, requests[i], injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak, wrapOnEscapedLineFeeds); + result[i] = createLineBreaks(this.classifier, lineText, injectedText, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent, wordBreak, isLineFeedWrappingEnabled); } } arrPool1.length = 0; @@ -416,6 +418,7 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: st const charCode = lineText.charCodeAt(i); let charCodeClass: CharacterClass; let charWidth: number; + let wrapEscapedLineFeed = false; if (strings.isHighSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken @@ -427,20 +430,20 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, _lineText: st charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } - if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) { + // literal \n shall trigger a softwrap + if (wrapOnEscapedLineFeeds && isEscapedLineBreakAtPosition(lineText, i)) { + breakOffset = charStartOffset; + breakOffsetVisibleColumn = visibleColumn; + wrapEscapedLineFeed = true; + } else if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass, isKeepAll)) { breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } visibleColumn += charWidth; - // literal \n shall trigger a softwrap - if (wrapOnEscapedLineFeeds && isEscapedLineBreakAtPosition(lineText, i)) { - visibleColumn += breakingColumn; - } - // check if adding character at `i` will go over the breaking column - if (visibleColumn > breakingColumn) { + if (visibleColumn > breakingColumn || wrapEscapedLineFeed) { // We need to break at least before character at `i`: if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) { @@ -494,13 +497,18 @@ function tabCharacterWidth(visibleColumn: number, tabSize: number): number { * This handles the wrapOnEscapedLineFeeds feature which allows \n sequences in strings to trigger wrapping. */ function isEscapedLineBreakAtPosition(lineText: string, i: number): boolean { - return ( - i >= 2 - && (i < 3 || lineText.charAt(i - 3) !== '\\') - && lineText.charAt(i - 2) === '\\' - && lineText.charAt(i - 1) === 'n' - && lineText.includes('"') - ); + if (i >= 2 && lineText.charAt(i - 1) === 'n') { + // Check if there's an odd number of backslashes + let escapeCount = 0; + for (let j = i - 2; j >= 0; j--) { + if (lineText.charAt(j) === '\\') { + escapeCount++; + } else { + return escapeCount % 2 === 1; + } + } + } + return false; } /** diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 7861bb31c5b..bed861e44a1 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -318,12 +318,12 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { assertLineBreaks(factory, 4, 8, '你好1111', WrappingIndent.Same, 'keepAll'); }); - test('issue wrapOnEscapedLineFeeds: should work correctly after editor resize', () => { + test('issue #258022: wrapOnEscapedLineFeeds: should work correctly after editor resize', () => { const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); // Test text with escaped line feeds - simulates a JSON string with \n // The \n should trigger a soft wrap when wrapOnEscapedLineFeeds is enabled - const text = '"Short text with\\nescaped newline and more text after"'; + const text = '"Short text with\\nescaped newline and an escaped\\\\nbackslash"'; // First, compute line breaks with wrapOnEscapedLineFeeds enabled at initial width const initialBreakData = getLineBreakData(factory, 4, 30, 2, WrappingIndent.None, 'normal', true, text, null); From 5fbf0596c1954d0e84cfdb6b43ca0d6937994be5 Mon Sep 17 00:00:00 2001 From: Josh Spicer <23246594+joshspicer@users.noreply.github.com> Date: Sat, 25 Oct 2025 11:05:33 -0700 Subject: [PATCH 1656/4355] fix "cloud button v2" bugs --- .../browser/actions/chatExecuteActions.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index bda60c770c1..cbba0f422fa 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -664,6 +664,7 @@ export class CreateRemoteAgentJobAction extends Action2 { } private async createWithChatSessions( + targetAgentId: string, chatSessionsService: IChatSessionsService, chatService: IChatService, quickPickService: IQuickInputService, @@ -675,16 +676,8 @@ export class CreateRemoteAgentJobAction extends Action2 { history?: string; } ) { - const contributions = chatSessionsService.getAllChatSessionContributions(); - const agent = await this.pickCodingAgent(quickPickService, contributions); - if (!agent) { - throw new Error('No coding agent selected'); - } - // TODO(jospicer): The previous chat history doesn't get sent to chat participants! - const { type } = agent; - await chatService.sendRequest(sessionId, userPrompt, { - agentIdSilent: type, + agentIdSilent: targetAgentId, attachedContext: attachedContext.asArray(), chatSummary, }); @@ -839,6 +832,14 @@ export class CreateRemoteAgentJobAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const requestParser = instantiationService.createInstance(ChatRequestParser); + const contributions = chatSessionsService.getAllChatSessionContributions(); + const agent = await this.pickCodingAgent(quickPickService, contributions); + if (!agent) { + widget.setInput(userPrompt); // Restore prompt + throw new Error('No coding agent selected'); + } + const { type } = agent; + // Add the request to the model first const parsedRequest = requestParser.parseChatRequest(sessionId, userPrompt, ChatAgentLocation.Chat); const addedRequest = chatModel.addRequest( @@ -917,6 +918,7 @@ export class CreateRemoteAgentJobAction extends Action2 { if (isChatSessionsExperimentEnabled) { await chatService.removeRequest(sessionId, addedRequest.id); return await this.createWithChatSessions( + type, chatSessionsService, chatService, quickPickService, @@ -930,6 +932,8 @@ export class CreateRemoteAgentJobAction extends Action2 { ); } + // -- Below is the legacy implementation + chatModel.acceptResponseProgress(addedRequest, { kind: 'progressMessage', content: new MarkdownString( From 4ff57b5afd0bbe7d9ae342b327b73ea4a2f13c71 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 25 Oct 2025 20:41:02 +0200 Subject: [PATCH 1657/4355] Chat todos: do not auto expand by default (fix #273076) (#273305) --- .../contrib/chat/browser/chatContentParts/chatTodoListWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 6df089ee23e..9551ba0303e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -135,7 +135,7 @@ export class ChatTodoListWidget extends Disposable { private readonly _onDidChangeHeight = this._register(new Emitter()); public readonly onDidChangeHeight: Event = this._onDidChangeHeight.event; - private _isExpanded: boolean = true; + private _isExpanded: boolean = false; private _userManuallyExpanded: boolean = false; private expandoElement!: HTMLElement; private todoListContainer!: HTMLElement; From 0d7c04acbd845ff1081ea861b0d90726af170d1e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sat, 25 Oct 2025 13:11:00 -0700 Subject: [PATCH 1658/4355] Polish quick pick. --- .../browser/actions/chatExecuteActions.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index cbba0f422fa..7a19f83042d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -654,7 +654,7 @@ export class CreateRemoteAgentJobAction extends Action2 { agent: a, })), { - title: localize('selectCodingAgent', "Select Coding Agent"), + placeHolder: localize('selectBackgroundAgent', "Select Agent to delegate the task to"), } ); if (!pick) { @@ -833,7 +833,35 @@ export class CreateRemoteAgentJobAction extends Action2 { const requestParser = instantiationService.createInstance(ChatRequestParser); const contributions = chatSessionsService.getAllChatSessionContributions(); - const agent = await this.pickCodingAgent(quickPickService, contributions); + + // Sort contributions by order, then alphabetically by display name + const sortedContributions = [...contributions].sort((a, b) => { + // Both have no order - sort by display name + if (a.order === undefined && b.order === undefined) { + return a.displayName.localeCompare(b.displayName); + } + + // Only a has no order - push it to the end + if (a.order === undefined) { + return 1; + } + + // Only b has no order - push it to the end + if (b.order === undefined) { + return -1; + } + + // Both have orders - compare numerically + const orderCompare = a.order - b.order; + if (orderCompare !== 0) { + return orderCompare; + } + + // Same order - sort by display name + return a.displayName.localeCompare(b.displayName); + }); + + const agent = await this.pickCodingAgent(quickPickService, sortedContributions); if (!agent) { widget.setInput(userPrompt); // Restore prompt throw new Error('No coding agent selected'); From e04fb09e12479ec9a955686993cb8f99356f8cf0 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sat, 25 Oct 2025 22:56:30 +0200 Subject: [PATCH 1659/4355] Fixes #265096 (#273324) * Fixes #265096 * swap super call --- .../extensions/common/lazyCreateExtensionHostManager.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/services/extensions/common/lazyCreateExtensionHostManager.ts b/src/vs/workbench/services/extensions/common/lazyCreateExtensionHostManager.ts index 5611f64799b..f9acb2a03c9 100644 --- a/src/vs/workbench/services/extensions/common/lazyCreateExtensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/lazyCreateExtensionHostManager.ts @@ -66,6 +66,13 @@ export class LazyCreateExtensionHostManager extends Disposable implements IExten this._actual = null; } + override dispose(): void { + if (!this._actual) { + this._extensionHost.dispose(); + } + super.dispose(); + } + private _createActual(reason: string): ExtensionHostManager { this._logService.info(`Creating lazy extension host (${this.friendyName}). Reason: ${reason}`); this._actual = this._register(this._instantiationService.createInstance(ExtensionHostManager, this._extensionHost, this._initialActivationEvents, this._internalExtensionService)); From cf18bbb21e8ecef54f4149fbc78654091998b53c Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sat, 25 Oct 2025 12:06:30 -0700 Subject: [PATCH 1660/4355] Resize agent emtpy view icon and support non codicon icons. --- .../contrib/chat/browser/chatEditorInput.ts | 6 ++-- .../chat/browser/chatSessions.contribution.ts | 14 ++++----- .../chatSessions/view/chatSessionsView.ts | 2 +- .../contrib/chat/browser/chatWidget.ts | 4 ++- .../chat/browser/media/chatViewWelcome.css | 23 +++++++++++++++ .../viewsWelcome/chatViewWelcomeController.ts | 29 ++++++++++++++++--- .../chat/common/chatSessionsService.ts | 2 +- .../test/common/mockChatSessionsService.ts | 2 +- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 5001800d41d..184718d2283 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -205,17 +205,17 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return contribution?.displayName; } - override getIcon(): ThemeIcon | undefined { + override getIcon(): ThemeIcon | URI | undefined { // Return cached icon if available if (this.cachedIcon) { - return ThemeIcon.isThemeIcon(this.cachedIcon) ? this.cachedIcon : undefined; + return this.cachedIcon; } // Try to resolve icon and cache it const resolvedIcon = this.resolveIcon(); if (resolvedIcon) { this.cachedIcon = resolvedIcon; - return ThemeIcon.isThemeIcon(resolvedIcon) ? resolvedIcon : undefined; + return resolvedIcon; } // Fall back to default icon diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 8b78f1b82b8..182e940ed30 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -8,6 +8,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; +import * as resources from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -233,7 +234,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private readonly inProgressMap: Map = new Map(); private readonly _sessionTypeOptions: Map = new Map(); - private readonly _sessionTypeIcons: Map = new Map(); + private readonly _sessionTypeIcons: Map = new Map(); private readonly _sessionTypeWelcomeTitles: Map = new Map(); private readonly _sessionTypeWelcomeMessages: Map = new Map(); private readonly _sessionTypeWelcomeTips: Map = new Map(); @@ -356,13 +357,12 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } // Store icon mapping if provided - let icon: ThemeIcon | undefined; + let icon: ThemeIcon | URI | undefined; if (contribution.icon) { - // Parse icon string - support both "$(iconId)" and "iconId" formats - icon = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')') - ? ThemeIcon.fromString(contribution.icon) - : ThemeIcon.fromId(contribution.icon); + // Parse icon string - support ThemeIcon format or file path from extension + const themeIcon = ThemeIcon.fromString(contribution.icon); + icon = themeIcon || resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon); } if (icon) { @@ -879,7 +879,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ /** * Get the icon for a specific session type */ - public getIconForSessionType(chatSessionType: string): ThemeIcon | undefined { + public getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined { return this._sessionTypeIcons.get(chatSessionType); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index f0e05da8da9..0751274f09f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -44,7 +44,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi id: AGENT_SESSIONS_VIEWLET_ID, title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer), - hideIfEmpty: true, + hideIfEmpty: false, icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index a6fe0c2a041..354e906d223 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1401,6 +1401,7 @@ export class ChatWidget extends Disposable implements IChatWidget { message, icon: providerIcon ?? Codicon.sendToRemoteAgent, additionalMessage, + useLargeIcon: !!providerIcon, }; } @@ -1460,7 +1461,8 @@ export class ChatWidget extends Disposable implements IChatWidget { inputPart: this.inputPart.element, additionalMessage, isNew: true, - suggestedPrompts + suggestedPrompts, + useLargeIcon: !!providerIcon, }; // Add contributed tips if available diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index de042ba4d46..48b2c207a48 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -96,6 +96,29 @@ div.chat-welcome-view { font-size: 40px; } + & > .chat-welcome-view-icon.custom-icon { + mask-size: 40px; + -webkit-mask-size: 40px; + } + + & > .chat-welcome-view-icon.large-icon { + min-width: 72px; + min-height: 72px; + } + + & > .chat-welcome-view-icon.large-icon .codicon { + font-size: 72px; + width: 72px; + height: 72px; + } + + & > .chat-welcome-view-icon.large-icon.custom-icon { + mask-size: 72px !important; + -webkit-mask-size: 72px !important; + width: 72px; + height: 72px; + } + & > .chat-welcome-view-title { font-size: 24px; margin-top: 5px; diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index 6d058c13372..569034f55d1 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; +import { asCSSUrl } from '../../../../../base/browser/cssValue.js'; +import { createCSSRule } from '../../../../../base/browser/domStylesheets.js'; import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { Action, IAction } from '../../../../../base/common/actions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Event } from '../../../../../base/common/event.js'; +import { StringSHA1 } from '../../../../../base/common/hash.js'; import { URI } from '../../../../../base/common/uri.js'; import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; @@ -110,7 +113,7 @@ export class ChatViewWelcomeController extends Disposable { } export interface IChatViewWelcomeContent { - readonly icon?: ThemeIcon; + readonly icon?: ThemeIcon | URI; readonly title: string; readonly message: IMarkdownString; readonly additionalMessage?: string | IMarkdownString; @@ -118,6 +121,7 @@ export interface IChatViewWelcomeContent { readonly inputPart?: HTMLElement; readonly isNew?: boolean; readonly suggestedPrompts?: readonly IChatSuggestedPrompts[]; + readonly useLargeIcon?: boolean; } export interface IChatSuggestedPrompts { @@ -156,11 +160,28 @@ export class ChatViewWelcomePart extends Disposable { // Icon const icon = dom.append(this.element, $('.chat-welcome-view-icon')); + if (content.useLargeIcon) { + icon.classList.add('large-icon'); + } if (content.icon) { - icon.appendChild(renderIcon(content.icon)); + if (ThemeIcon.isThemeIcon(content.icon)) { + const iconElement = renderIcon(content.icon); + icon.appendChild(iconElement); + } else if (URI.isUri(content.icon)) { + const cssUrl = asCSSUrl(content.icon); + const hash = new StringSHA1(); + hash.update(cssUrl); + const iconId = `chat-welcome-icon-${hash.digest()}`; + const iconClass = `.chat-welcome-view-icon.${iconId}`; + + createCSSRule(iconClass, ` + mask: ${cssUrl} no-repeat 50% 50%; + -webkit-mask: ${cssUrl} no-repeat 50% 50%; + background-color: var(--vscode-icon-foreground); + `); + icon.classList.add(iconId, 'custom-icon'); + } } - - // Title const title = dom.append(this.element, $('.chat-welcome-view-title')); title.textContent = content.title; diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index faf3836136c..4d5270ae1f6 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -138,7 +138,7 @@ export interface IChatSessionsService { getAllChatSessionContributions(): IChatSessionsExtensionPoint[]; canResolveItemProvider(chatSessionType: string): Promise; getAllChatSessionItemProviders(): IChatSessionItemProvider[]; - getIconForSessionType(chatSessionType: string): ThemeIcon | undefined; + getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined; getWelcomeTitleForSessionType(chatSessionType: string): string | undefined; getWelcomeMessageForSessionType(chatSessionType: string): string | undefined; /** diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index a497c9cc0a7..04390f038f3 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -81,7 +81,7 @@ export class MockChatSessionsService implements IChatSessionsService { return Array.from(this.providers.values()); } - getIconForSessionType(chatSessionType: string): ThemeIcon | undefined { + getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined { const contribution = this.contributions.find(c => c.type === chatSessionType); return contribution?.icon ? ThemeIcon.fromId(contribution.icon) : undefined; } From b8dac9d17d2d497bdc8b53def381d3fa90d9e0a7 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sat, 25 Oct 2025 13:18:26 -0700 Subject: [PATCH 1661/4355] revert hideIfEmpty change. --- .../contrib/chat/browser/chatSessions/view/chatSessionsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 0751274f09f..f0e05da8da9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -44,7 +44,7 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi id: AGENT_SESSIONS_VIEWLET_ID, title: nls.localize2('chat.agent.sessions', "Agent Sessions"), ctorDescriptor: new SyncDescriptor(ChatSessionsViewPaneContainer), - hideIfEmpty: false, + hideIfEmpty: true, icon: registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'), order: 6 }, ViewContainerLocation.Sidebar); From c19a2c34abc27cbbef7caaa8f871bbefc7ddfd5b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sat, 25 Oct 2025 14:43:35 -0700 Subject: [PATCH 1662/4355] chat agent editor should not be replaced after open --- .../contrib/chat/browser/chatSessions/view/sessionsViewPane.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index e8acea7b7c0..7288cae8188 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -468,7 +468,8 @@ export class SessionsViewPane extends ViewPane { editorOptions: upcast({ title: { preferred: session.label - } + }, + pinned: true }) }); return; From d15f5022f7d3944df42b12be15d5ef6239b03ea3 Mon Sep 17 00:00:00 2001 From: kieferrm <4674940+kieferrm@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:50:43 -0700 Subject: [PATCH 1663/4355] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 880f7560ba1..18cb41e4fde 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "b84862654999878b2382237454c745614992e9d7", + "distro": "e6400daee823077bac8803d05ce425ac82fb30f0", "author": { "name": "Microsoft Corporation" }, From 72b541fa194091e08ae92540bf99e1f200431669 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:18:35 +0200 Subject: [PATCH 1664/4355] SCM - more text overflow polish in the repositories view (#273322) --- .../contrib/scm/browser/media/scm.css | 19 +++++++++++-------- src/vs/workbench/contrib/scm/browser/util.ts | 5 +++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 79416fd0db1..36805b13a4b 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -90,8 +90,11 @@ align-items: center; } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1), -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(2) { +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) { + flex-shrink: 10000; +} + +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item.scm-status-bar-action { min-width: 20px; .action-label > span:not(.codicon) { @@ -99,18 +102,18 @@ } } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > span:nth-child(2) { +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item.scm-status-bar-action > .action-label > .codicon { + display: inline-block; + vertical-align: middle; +} + +.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item.scm-status-bar-action > .action-label > span:not(.codicon) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) > .action-label > .codicon { - display: inline-block; - vertical-align: middle; -} - .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label, .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .monaco-dropdown > .dropdown-label > .action-label { display: flex; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index ce442e443fe..d789d0adcd9 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -120,6 +120,11 @@ class StatusBarActionViewItem extends ActionViewItem { super(null, action, { ...options, icon: false, label: true }); } + override render(container: HTMLElement): void { + container.classList.add('scm-status-bar-action'); + super.render(container); + } + protected override updateLabel(): void { if (this.options.label && this.label) { // Convert text nodes to span elements to enable From 7907ff788e80bc14c2db8e1087aed5f849f26919 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sun, 26 Oct 2025 00:34:12 +0200 Subject: [PATCH 1665/4355] strict no explicit `any` (#273329) --- eslint.config.js | 15 ----------- src/vs/base/browser/browser.ts | 21 +++++++++++++++ src/vs/base/browser/trustedTypes.ts | 10 ++----- src/vs/base/browser/webWorkerFactory.ts | 8 ++---- .../contrib/clipboard/browser/clipboard.ts | 8 +++--- .../contrib/cursorUndo/browser/cursorUndo.ts | 4 +-- .../editorState/browser/editorState.ts | 2 +- .../browser/insertFinalNewLine.ts | 2 +- .../lineSelection/browser/lineSelection.ts | 6 ++++- .../browser/linesOperations.ts | 8 ++++-- .../linkedEditing/browser/linkedEditing.ts | 8 +++--- .../multicursor/browser/multicursor.ts | 17 +++++++++--- .../browser/viewportSemanticTokens.ts | 6 ++--- .../common/getSemanticTokens.ts | 2 +- .../wordOperations/browser/wordOperations.ts | 6 ++--- .../wordPartOperations/test/browser/utils.ts | 6 ++--- src/vs/editor/editor.api.ts | 26 +++++++++++-------- .../browser/standaloneCodeEditor.ts | 4 +-- .../standalone/browser/standaloneServices.ts | 26 +++++++++---------- .../standalone/browser/standaloneWebWorker.ts | 4 +-- src/vs/monaco.d.ts | 6 ++--- 21 files changed, 106 insertions(+), 89 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 9fb6db94c50..b32083866de 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -416,26 +416,20 @@ export default tseslint.config( 'src/vs/platform/quickinput/browser/tree/quickTree.ts', 'src/vs/platform/userDataSync/test/common/userDataSyncClient.ts', // Editor - 'src/vs/editor/editor.api.ts', - 'src/vs/editor/standalone/browser/standaloneCodeEditor.ts', 'src/vs/editor/standalone/browser/standaloneEditor.ts', 'src/vs/editor/standalone/browser/standaloneLanguages.ts', 'src/vs/editor/standalone/browser/standaloneServices.ts', - 'src/vs/editor/standalone/browser/standaloneWebWorker.ts', 'src/vs/editor/test/browser/testCodeEditor.ts', 'src/vs/editor/test/common/testTextModel.ts', 'src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts', - 'src/vs/editor/contrib/clipboard/browser/clipboard.ts', 'src/vs/editor/contrib/codeAction/browser/codeAction.ts', 'src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts', 'src/vs/editor/contrib/codeAction/common/types.ts', 'src/vs/editor/contrib/codelens/browser/codelens.ts', 'src/vs/editor/contrib/codelens/browser/codelensController.ts', 'src/vs/editor/contrib/colorPicker/browser/colorDetector.ts', - 'src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts', 'src/vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution.ts', 'src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts', - 'src/vs/editor/contrib/editorState/browser/editorState.ts', 'src/vs/editor/contrib/find/browser/findController.ts', 'src/vs/editor/contrib/find/browser/findModel.ts', 'src/vs/editor/contrib/find/browser/findWidgetSearchHistory.ts', @@ -447,13 +441,6 @@ export default tseslint.config( 'src/vs/editor/contrib/hover/browser/hoverActions.ts', 'src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts', 'src/vs/editor/contrib/inlineCompletions/browser/utils.ts', - 'src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts', - 'src/vs/editor/contrib/lineSelection/browser/lineSelection.ts', - 'src/vs/editor/contrib/linesOperations/browser/linesOperations.ts', - 'src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts', - 'src/vs/editor/contrib/multicursor/browser/multicursor.ts', - 'src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts', - 'src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts', 'src/vs/editor/contrib/smartSelect/browser/smartSelect.ts', 'src/vs/editor/contrib/snippet/browser/snippetParser.ts', 'src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts', @@ -463,7 +450,6 @@ export default tseslint.config( 'src/vs/editor/contrib/suggest/browser/suggestController.ts', 'src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts', 'src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts', - 'src/vs/editor/contrib/wordOperations/browser/wordOperations.ts', 'src/vs/editor/standalone/common/monarch/monarchCommon.ts', 'src/vs/editor/standalone/common/monarch/monarchCompile.ts', 'src/vs/editor/standalone/common/monarch/monarchLexer.ts', @@ -474,7 +460,6 @@ export default tseslint.config( 'src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts', 'src/vs/editor/contrib/inlineCompletions/browser/model/typingSpeed.ts', 'src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts', - 'src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts', 'src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts', 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts', 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts', diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 5a9ad48587f..b6e9ec09fff 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -139,3 +139,24 @@ export function isWCOEnabled(): boolean { export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { return (targetWindow.navigator as Navigator & { windowControlsOverlay?: { getTitlebarAreaRect: () => DOMRect } })?.windowControlsOverlay?.getTitlebarAreaRect(); } + +export interface IMonacoEnvironment { + + createTrustedTypesPolicy?( + policyName: string, + policyOptions?: Options, + ): undefined | Pick>; + + getWorker?(moduleId: string, label: string): Worker | Promise; + + getWorkerUrl?(moduleId: string, label: string): string; + + globalAPI?: boolean; + +} +interface IGlobalWithMonacoEnvironment { + MonacoEnvironment?: IMonacoEnvironment; +} +export function getMonacoEnvironment(): IMonacoEnvironment | undefined { + return (globalThis as IGlobalWithMonacoEnvironment).MonacoEnvironment; +} diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts index c7d977b505b..ac3fb0eea3b 100644 --- a/src/vs/base/browser/trustedTypes.ts +++ b/src/vs/base/browser/trustedTypes.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from '../common/errors.js'; +import { getMonacoEnvironment } from './browser.js'; type TrustedTypePolicyOptions = import('trusted-types/lib/index.d.ts').TrustedTypePolicyOptions; @@ -12,14 +13,7 @@ export function createTrustedTypesPolicy> { - interface IMonacoEnvironment { - createTrustedTypesPolicy( - policyName: string, - policyOptions?: Options, - ): undefined | Pick>; - } - // eslint-disable-next-line local/code-no-any-casts - const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + const monacoEnvironment = getMonacoEnvironment(); if (monacoEnvironment?.createTrustedTypesPolicy) { try { diff --git a/src/vs/base/browser/webWorkerFactory.ts b/src/vs/base/browser/webWorkerFactory.ts index 723127fd30b..83c587f16f1 100644 --- a/src/vs/base/browser/webWorkerFactory.ts +++ b/src/vs/base/browser/webWorkerFactory.ts @@ -12,6 +12,7 @@ import { Disposable, toDisposable } from '../common/lifecycle.js'; import { coalesce } from '../common/arrays.js'; import { getNLSLanguage, getNLSMessages } from '../../nls.js'; import { Emitter } from '../common/event.js'; +import { getMonacoEnvironment } from './browser.js'; // Reuse the trusted types policy defined from worker bootstrap // when available. @@ -36,12 +37,7 @@ function getWorker(descriptor: IWebWorkerDescriptor, id: number): Worker | Promi const label = descriptor.label || 'anonymous' + id; // Option for hosts to overwrite the worker script (used in the standalone editor) - interface IMonacoEnvironment { - getWorker?(moduleId: string, label: string): Worker | Promise; - getWorkerUrl?(moduleId: string, label: string): string; - } - // eslint-disable-next-line local/code-no-any-casts - const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + const monacoEnvironment = getMonacoEnvironment(); if (monacoEnvironment) { if (typeof monacoEnvironment.getWorker === 'function') { return monacoEnvironment.getWorker('workerMain.js', label); diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 18af008c72c..f795302fae8 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -199,7 +199,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman } // 1. handle case when focus is in editor. - target.addImplementation(10000, 'code-editor', (accessor: ServicesAccessor, args: any) => { + target.addImplementation(10000, 'code-editor', (accessor: ServicesAccessor, args: unknown) => { const logService = accessor.get(ILogService); logService.trace('registerExecCommandImpl (addImplementation code-editor for : ', browserCommand, ')'); // Only if editor text focus (i.e. not if editor has widget focus). @@ -231,7 +231,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman }); // 2. (default) handle case when focus is somewhere else. - target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { + target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: unknown) => { const logService = accessor.get(ILogService); logService.trace('registerExecCommandImpl (addImplementation generic-dom for : ', browserCommand, ')'); logService.trace('registerExecCommandImpl (before execCommand ' + browserCommand + ')'); @@ -256,7 +256,7 @@ registerExecCommandImpl(CopyAction, 'copy'); if (PasteAction) { // 1. Paste: handle case when focus is in editor. - PasteAction.addImplementation(10000, 'code-editor', (accessor: ServicesAccessor, args: any) => { + PasteAction.addImplementation(10000, 'code-editor', (accessor: ServicesAccessor, args: unknown) => { const logService = accessor.get(ILogService); logService.trace('registerExecCommandImpl (addImplementation code-editor for : paste)'); const codeEditorService = accessor.get(ICodeEditorService); @@ -335,7 +335,7 @@ if (PasteAction) { }); // 2. Paste: (default) handle case when focus is somewhere else. - PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { + PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: unknown) => { const logService = accessor.get(ILogService); logService.trace('registerExecCommandImpl (addImplementation generic-dom for : paste)'); const triggerPaste = accessor.get(IClipboardService).triggerPaste(getActiveWindow().vscodeWindowId); diff --git a/src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts index a05f5f6354c..945321d8936 100644 --- a/src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/browser/cursorUndo.ts @@ -139,7 +139,7 @@ export class CursorUndo extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { CursorUndoRedoController.get(editor)?.cursorUndo(); } } @@ -153,7 +153,7 @@ export class CursorRedo extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { CursorUndoRedoController.get(editor)?.cursorRedo(); } } diff --git a/src/vs/editor/contrib/editorState/browser/editorState.ts b/src/vs/editor/contrib/editorState/browser/editorState.ts index fdb296ee7f7..88ff8bd191a 100644 --- a/src/vs/editor/contrib/editorState/browser/editorState.ts +++ b/src/vs/editor/contrib/editorState/browser/editorState.ts @@ -57,7 +57,7 @@ export class EditorState { } } - private _equals(other: any): boolean { + private _equals(other: unknown): boolean { if (!(other instanceof EditorState)) { return false; diff --git a/src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts b/src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts index 091ccf9788c..c415b77e689 100644 --- a/src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts +++ b/src/vs/editor/contrib/insertFinalNewLine/browser/insertFinalNewLine.ts @@ -21,7 +21,7 @@ export class InsertFinalNewLineAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const selection = editor.getSelection(); if (selection === null) { return; diff --git a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts index c111f826df9..10cc1fb0092 100644 --- a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts +++ b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts @@ -12,6 +12,10 @@ import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import * as nls from '../../../../nls.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +interface ExpandLinesSelectionArgs { + source?: string; +} + export class ExpandLineSelectionAction extends EditorAction { constructor() { super({ @@ -26,7 +30,7 @@ export class ExpandLineSelectionAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: ExpandLinesSelectionArgs): void { args = args || {}; if (!editor.hasModel()) { return; diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index f1e2ea27710..407e6c291a0 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -135,7 +135,7 @@ export class DuplicateSelectionAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { if (!editor.hasModel()) { return; } @@ -438,6 +438,10 @@ export class ReverseLinesAction extends EditorAction { } } +interface TrimTrailingWhitespaceArgs { + reason?: 'auto-save'; +} + export class TrimTrailingWhitespaceAction extends EditorAction { public static readonly ID = 'editor.action.trimTrailingWhitespace'; @@ -455,7 +459,7 @@ export class TrimTrailingWhitespaceAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: TrimTrailingWhitespaceArgs): void { let cursors: Position[] = []; if (args.reason === 'auto-save') { diff --git a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts index 00073c50f0f..09f9257f2d7 100644 --- a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts @@ -63,8 +63,8 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont private readonly _visibleContextKey: IContextKey; private readonly _debounceInformation: IFeatureDebounceInformation; - private _rangeUpdateTriggerPromise: Promise | null; - private _rangeSyncTriggerPromise: Promise | null; + private _rangeUpdateTriggerPromise: Promise | null; + private _rangeSyncTriggerPromise: Promise | null; private _currentRequestCts: CancellationTokenSource | null; private _currentRequestPosition: Position | null; @@ -265,11 +265,11 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont } } - public get currentUpdateTriggerPromise(): Promise { + public get currentUpdateTriggerPromise(): Promise { return this._rangeUpdateTriggerPromise || Promise.resolve(); } - public get currentSyncTriggerPromise(): Promise { + public get currentSyncTriggerPromise(): Promise { return this._rangeSyncTriggerPromise || Promise.resolve(); } diff --git a/src/vs/editor/contrib/multicursor/browser/multicursor.ts b/src/vs/editor/contrib/multicursor/browser/multicursor.ts index 92b7bb7cdb4..a144dce617f 100644 --- a/src/vs/editor/contrib/multicursor/browser/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/browser/multicursor.ts @@ -38,6 +38,11 @@ function announceCursorChange(previousCursorState: CursorState[], cursorState: C } } +interface InsertCursorArgs { + source?: string; + logicalLine?: boolean; +} + export class InsertCursorAbove extends EditorAction { constructor() { @@ -63,7 +68,7 @@ export class InsertCursorAbove extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: InsertCursorArgs): void { if (!editor.hasModel()) { return; } @@ -115,7 +120,7 @@ export class InsertCursorBelow extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: InsertCursorArgs): void { if (!editor.hasModel()) { return; } @@ -1073,6 +1078,10 @@ function getValueInRange(model: ITextModel, range: Range, toLowerCase: boolean): return (toLowerCase ? text.toLowerCase() : text); } +interface FocusCursorArgs { + source?: string; +} + export class FocusNextCursor extends EditorAction { constructor() { super({ @@ -1086,7 +1095,7 @@ export class FocusNextCursor extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: FocusCursorArgs): void { if (!editor.hasModel()) { return; } @@ -1124,7 +1133,7 @@ export class FocusPreviousCursor extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: FocusCursorArgs): void { if (!editor.hasModel()) { return; } diff --git a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts index b0a434539db..a9f89d1c41c 100644 --- a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts @@ -34,7 +34,7 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE private readonly _provider: LanguageFeatureRegistry; private readonly _debounceInformation: IFeatureDebounceInformation; private readonly _tokenizeViewport: RunOnceScheduler; - private _outstandingRequests: CancelablePromise[]; + private _outstandingRequests: CancelablePromise[]; private _rangeProvidersChangeListeners: IDisposable[]; constructor( @@ -122,7 +122,7 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE this._outstandingRequests = []; } - private _removeOutstandingRequest(req: CancelablePromise): void { + private _removeOutstandingRequest(req: CancelablePromise): void { for (let i = 0, len = this._outstandingRequests.length; i < len; i++) { if (this._outstandingRequests[i] === req) { this._outstandingRequests.splice(i, 1); @@ -156,7 +156,7 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range))); } - private _requestRange(model: ITextModel, range: Range): CancelablePromise { + private _requestRange(model: ITextModel, range: Range): CancelablePromise { const requestVersionId = model.getVersionId(); const request = createCancelablePromise(token => Promise.resolve(getDocumentRangeSemanticTokens(this._provider, model, range, token))); const sw = new StopWatch(false); diff --git a/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts index 137abcc4826..07a71ae3f53 100644 --- a/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts @@ -29,7 +29,7 @@ export class DocumentSemanticTokensResult { constructor( public readonly provider: DocumentSemanticTokensProvider, public readonly tokens: SemanticTokens | SemanticTokensEdits | null, - public readonly error: any + public readonly error: unknown ) { } } diff --git a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts index b2c0f01f76c..6e324867d78 100644 --- a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts @@ -41,7 +41,7 @@ export abstract class MoveWordCommand extends EditorCommand { this._wordNavigationType = opts.wordNavigationType; } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { if (!editor.hasModel()) { return; } @@ -330,7 +330,7 @@ export abstract class DeleteWordCommand extends EditorCommand { this._wordNavigationType = opts.wordNavigationType; } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const languageConfigurationService = accessor?.get(ILanguageConfigurationService); if (!editor.hasModel() || !languageConfigurationService) { @@ -477,7 +477,7 @@ export class DeleteInsideWord extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { if (!editor.hasModel()) { return; } diff --git a/src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts b/src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts index 2414483f16e..9a7366828cf 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/browser/utils.ts @@ -6,7 +6,7 @@ import { ServiceIdentifier, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; export class StaticServiceAccessor implements ServicesAccessor { - private services = new Map, any>(); + private services = new Map, unknown>(); public withService(id: ServiceIdentifier, service: T): this { this.services.set(id, service); @@ -18,11 +18,11 @@ export class StaticServiceAccessor implements ServicesAccessor { if (!value) { throw new Error('Service does not exist'); } - return value; + return value as T; } getIfExists(id: ServiceIdentifier): T | undefined { const value = this.services.get(id); - return value; + return value as T | undefined; } } diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index 645d902f7b6..8e414c22ed8 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -8,6 +8,7 @@ import { createMonacoBaseAPI } from './common/services/editorBaseApi.js'; import { createMonacoEditorAPI } from './standalone/browser/standaloneEditor.js'; import { createMonacoLanguagesAPI } from './standalone/browser/standaloneLanguages.js'; import { FormattingConflicts } from './contrib/format/browser/format.js'; +import { getMonacoEnvironment } from '../base/browser/browser.js'; // Set defaults for standalone editor EditorOptions.wrappingIndent.defaultValue = WrappingIndent.None; @@ -37,21 +38,24 @@ export const Token = api.Token; export const editor = api.editor; export const languages = api.languages; -interface IMonacoEnvironment { - globalAPI?: boolean; +interface IFunctionWithAMD extends Function { + amd?: boolean; } -// eslint-disable-next-line local/code-no-any-casts -const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; -// eslint-disable-next-line local/code-no-any-casts -if (monacoEnvironment?.globalAPI || (typeof (globalThis as any).define === 'function' && ((globalThis as any).define).amd)) { - globalThis.monaco = api; +interface GlobalWithAMD { + define?: IFunctionWithAMD; + require?: { config?: (options: { ignoreDuplicateModules: string[] }) => void }; + monaco?: typeof api; } -// eslint-disable-next-line local/code-no-any-casts -if (typeof (globalThis as any).require !== 'undefined' && typeof (globalThis as any).require.config === 'function') { - // eslint-disable-next-line local/code-no-any-casts - (globalThis as any).require.config({ +const monacoEnvironment = getMonacoEnvironment(); +const globalWithAMD = globalThis as GlobalWithAMD; +if (monacoEnvironment?.globalAPI || (typeof globalWithAMD.define === 'function' && globalWithAMD.define.amd)) { + globalWithAMD.monaco = api; +} + +if (typeof globalWithAMD.require !== 'undefined' && typeof globalWithAMD.require.config === 'function') { + globalWithAMD.require.config({ ignoreDuplicateModules: [ 'vscode-languageserver-types', 'vscode-languageserver-types/main', diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index c3cf79fccc2..6041f65a055 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -87,7 +87,7 @@ export interface IActionDescriptor { * Method that will be executed when the action is triggered. * @param editor The editor instance is passed in as a convenience */ - run(editor: ICodeEditor, ...args: any[]): void | Promise; + run(editor: ICodeEditor, ...args: unknown[]): void | Promise; } /** @@ -394,7 +394,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon return toDispose; } - protected override _triggerCommand(handlerId: string, payload: any): void { + protected override _triggerCommand(handlerId: string, payload: unknown): void { if (this._codeEditorService instanceof StandaloneCodeEditorService) { // Help commands find this editor as the active editor try { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f7ccab00378..9ffecaa15ec 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -195,7 +195,7 @@ class StandaloneEditorProgressService implements IEditorProgressService { return StandaloneEditorProgressService.NULL_PROGRESS_RUNNER; } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { await promise; } } @@ -403,7 +403,7 @@ export class StandaloneCommandService implements ICommandService { export interface IKeybindingRule { keybinding: number; command?: string | null; - commandArgs?: any; + commandArgs?: unknown; when?: ContextKeyExpression | null; } @@ -616,11 +616,11 @@ class DomNodeListeners extends Disposable { } } -function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { - return thing +function isConfigurationOverrides(thing: unknown): thing is IConfigurationOverrides { + return !!thing && typeof thing === 'object' - && (!thing.overrideIdentifier || typeof thing.overrideIdentifier === 'string') - && (!thing.resource || thing.resource instanceof URI); + && (!(thing as IConfigurationOverrides).overrideIdentifier || typeof (thing as IConfigurationOverrides).overrideIdentifier === 'string') + && (!(thing as IConfigurationOverrides).resource || (thing as IConfigurationOverrides).resource instanceof URI); } export class StandaloneConfigurationService implements IConfigurationService { @@ -655,13 +655,13 @@ export class StandaloneConfigurationService implements IConfigurationService { getValue(section: string): T; getValue(overrides: IConfigurationOverrides): T; getValue(section: string, overrides: IConfigurationOverrides): T; - getValue(arg1?: any, arg2?: any): any { + getValue(arg1?: unknown, arg2?: unknown): unknown { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; return this._configuration.getValue(section, overrides, undefined); } - public updateValues(values: [string, any][]): Promise { + public updateValues(values: [string, unknown][]): Promise { const previous = { data: this._configuration.toData() }; const changedKeys: string[] = []; @@ -684,7 +684,7 @@ export class StandaloneConfigurationService implements IConfigurationService { return Promise.resolve(); } - public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { + public updateValue(key: string, value: unknown, arg3?: unknown, arg4?: unknown): Promise { return this.updateValues([[key, value]]); } @@ -737,7 +737,7 @@ class StandaloneResourceConfigurationService implements ITextResourceConfigurati getValue(resource: URI, section?: string): T; getValue(resource: URI, position?: IPosition, section?: string): T; - getValue(resource: URI | undefined, arg2?: any, arg3?: any) { + getValue(resource: URI | undefined, arg2?: unknown, arg3?: unknown) { const position: IPosition | null = Pos.isIPosition(arg2) ? arg2 : null; const section: string | undefined = position ? (typeof arg3 === 'string' ? arg3 : undefined) : (typeof arg2 === 'string' ? arg2 : undefined); const language = resource ? this.getLanguage(resource, position) : undefined; @@ -766,7 +766,7 @@ class StandaloneResourceConfigurationService implements ITextResourceConfigurati return this.languageService.guessLanguageIdByFilepathOrFirstLine(resource); } - updateValue(resource: URI, key: string, value: any, configurationTarget?: ConfigurationTarget): Promise { + updateValue(resource: URI, key: string, value: unknown, configurationTarget?: ConfigurationTarget): Promise { return this.configurationService.updateValue(key, value, { resource }, configurationTarget); } } @@ -869,7 +869,7 @@ export function updateConfigurationService(configurationService: IConfigurationS if (!(configurationService instanceof StandaloneConfigurationService)) { return; } - const toUpdate: [string, any][] = []; + const toUpdate: [string, unknown][] = []; Object.keys(source).forEach((key) => { if (isEditorConfigurationKey(key)) { toUpdate.push([`editor.${key}`, source[key]]); @@ -1128,7 +1128,7 @@ class StandaloneAccessbilitySignalService implements IAccessibilitySignalService } export interface IEditorOverrideServices { - [index: string]: any; + [index: string]: unknown; } diff --git a/src/vs/editor/standalone/browser/standaloneWebWorker.ts b/src/vs/editor/standalone/browser/standaloneWebWorker.ts index 43f1cc93608..a34425aa444 100644 --- a/src/vs/editor/standalone/browser/standaloneWebWorker.ts +++ b/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -42,7 +42,7 @@ export interface IInternalWebWorkerOptions { /** * An object that can be used by the web worker to make calls back to the main thread. */ - host?: any; + host?: Record; /** * Keep idle models. * Defaults to false, which means that idle models will stop syncing after a while. @@ -77,7 +77,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement } // foreign host request - public override fhr(method: string, args: unknown[]): Promise { + public override fhr(method: string, args: unknown[]): Promise { if (!this._foreignModuleHost || typeof this._foreignModuleHost[method] !== 'function') { return Promise.reject(new Error('Missing method ' + method + ' or missing main thread foreign host.')); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 7932b302e1d..22a9003f9fc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1224,7 +1224,7 @@ declare namespace monaco.editor { /** * An object that can be used by the web worker to make calls back to the main thread. */ - host?: any; + host?: Record; /** * Keep idle models. * Defaults to false, which means that idle models will stop syncing after a while. @@ -1274,7 +1274,7 @@ declare namespace monaco.editor { * Method that will be executed when the action is triggered. * @param editor The editor instance is passed in as a convenience */ - run(editor: ICodeEditor, ...args: any[]): void | Promise; + run(editor: ICodeEditor, ...args: unknown[]): void | Promise; } /** @@ -1448,7 +1448,7 @@ declare namespace monaco.editor { export type ContextKeyValue = null | undefined | boolean | number | string | Array | Record; export interface IEditorOverrideServices { - [index: string]: any; + [index: string]: unknown; } export interface IMarker { From 8fcede8bce6d5a5f5125457369f831d7d5cf67fe Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sat, 25 Oct 2025 17:13:02 -0700 Subject: [PATCH 1666/4355] Merge pull request #273345 from microsoft/rebornix/important-hoverfly Support light/dark theme for agent icons --- .../contrib/chat/browser/chatEditorInput.ts | 6 --- .../chat/browser/chatSessions.contribution.ts | 48 ++++++++++++++++--- .../chat/common/chatSessionsService.ts | 2 +- .../test/common/mockChatSessionsService.ts | 2 +- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 184718d2283..1fb1cf6f96c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -206,12 +206,6 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } override getIcon(): ThemeIcon | URI | undefined { - // Return cached icon if available - if (this.cachedIcon) { - return this.cachedIcon; - } - - // Try to resolve icon and cache it const resolvedIcon = this.resolveIcon(); if (resolvedIcon) { this.cachedIcon = resolvedIcon; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 182e940ed30..9fbe7e2dcca 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -19,6 +19,8 @@ import { ContextKeyExpr, IContextKeyService } from '../../../../platform/context import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { isDark } from '../../../../platform/theme/common/theme.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IEditableData } from '../../../common/views.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; @@ -64,7 +66,22 @@ const extensionPoint = ExtensionsRegistry.registerExtensionPoint = new Map(); private readonly _sessionTypeOptions: Map = new Map(); - private readonly _sessionTypeIcons: Map = new Map(); + private readonly _sessionTypeIcons: Map = new Map(); private readonly _sessionTypeWelcomeTitles: Map = new Map(); private readonly _sessionTypeWelcomeMessages: Map = new Map(); private readonly _sessionTypeWelcomeTips: Map = new Map(); @@ -250,6 +267,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IMenuService private readonly _menuService: IMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IThemeService private readonly _themeService: IThemeService ) { super(); this._register(extensionPoint.setHandler(extensions => { @@ -357,12 +375,20 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } // Store icon mapping if provided - let icon: ThemeIcon | URI | undefined; + let icon: ThemeIcon | { dark: URI; light: URI } | undefined; if (contribution.icon) { // Parse icon string - support ThemeIcon format or file path from extension - const themeIcon = ThemeIcon.fromString(contribution.icon); - icon = themeIcon || resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon); + if (typeof contribution.icon === 'string') { + icon = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')') + ? ThemeIcon.fromString(contribution.icon) + : ThemeIcon.fromId(contribution.icon); + } else { + icon = { + dark: resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon.dark), + light: resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon.light) + }; + } } if (icon) { @@ -880,7 +906,17 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ * Get the icon for a specific session type */ public getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined { - return this._sessionTypeIcons.get(chatSessionType); + const sessionTypeIcon = this._sessionTypeIcons.get(chatSessionType); + + if (ThemeIcon.isThemeIcon(sessionTypeIcon)) { + return sessionTypeIcon; + } + + if (isDark(this._themeService.getColorTheme().type)) { + return sessionTypeIcon?.dark; + } else { + return sessionTypeIcon?.light; + } } /** diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 4d5270ae1f6..f4430b9ee87 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -49,7 +49,7 @@ export interface IChatSessionsExtensionPoint { readonly description: string; readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; - readonly icon?: string; + readonly icon?: string | { light: string; dark: string }; readonly order?: number; readonly alternativeIds?: string[]; readonly welcomeTitle?: string; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 04390f038f3..1aeae970f06 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -83,7 +83,7 @@ export class MockChatSessionsService implements IChatSessionsService { getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined { const contribution = this.contributions.find(c => c.type === chatSessionType); - return contribution?.icon ? ThemeIcon.fromId(contribution.icon) : undefined; + return contribution?.icon && typeof contribution.icon === 'string' ? ThemeIcon.fromId(contribution.icon) : undefined; } getWelcomeTitleForSessionType(chatSessionType: string): string | undefined { From 01b2538f72a8473457e9803cef368355061d1954 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sun, 26 Oct 2025 04:47:37 +0100 Subject: [PATCH 1667/4355] Adopt AsyncIterableProducer (#273340) --- .../workbench/services/extensions/browser/extensionService.ts | 4 ++-- .../extensions/electron-browser/nativeExtensionService.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 69aad42da57..880beae0916 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -41,7 +41,7 @@ import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; import { IRemoteExplorerService } from '../../remote/common/remoteExplorerService.js'; import { IUserDataInitializationService } from '../../userData/browser/userDataInit.js'; import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js'; -import { AsyncIterableEmitter, AsyncIterableObject } from '../../../../base/common/async.js'; +import { AsyncIterableEmitter, AsyncIterableProducer } from '../../../../base/common/async.js'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -152,7 +152,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } protected _resolveExtensions(): AsyncIterable { - return new AsyncIterableObject(emitter => this._doResolveExtensions(emitter)); + return new AsyncIterableProducer(emitter => this._doResolveExtensions(emitter)); } private async _doResolveExtensions(emitter: AsyncIterableEmitter): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts index a86ef714159..3080657390a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts @@ -57,7 +57,7 @@ import { IHostService } from '../../host/browser/host.js'; import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; import { IRemoteExplorerService } from '../../remote/common/remoteExplorerService.js'; -import { AsyncIterableEmitter, AsyncIterableObject } from '../../../../base/common/async.js'; +import { AsyncIterableEmitter, AsyncIterableProducer } from '../../../../base/common/async.js'; export class NativeExtensionService extends AbstractExtensionService implements IExtensionService { @@ -318,7 +318,7 @@ export class NativeExtensionService extends AbstractExtensionService implements } protected _resolveExtensions(): AsyncIterable { - return new AsyncIterableObject(emitter => this._doResolveExtensions(emitter)); + return new AsyncIterableProducer(emitter => this._doResolveExtensions(emitter)); } private async _doResolveExtensions(emitter: AsyncIterableEmitter): Promise { From bcb34d6004eb6c95e8c13915a9be78de9d585662 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:45:33 +0800 Subject: [PATCH 1668/4355] add special case handling for thinking with create tools and all tools in genera; (#273348) add special case handling for thinking with create tools --- .../contrib/chat/browser/chat.contribution.ts | 6 ++ .../contrib/chat/browser/chatListRenderer.ts | 57 +++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 000a983f7ef..15967097e6f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -700,6 +700,12 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.agent.thinkingStyle', "Controls how thinking is rendered."), tags: ['experimental'], }, + 'chat.agent.thinking.collapsedTools': { + type: 'boolean', + default: false, + description: nls.localize('chat.agent.thinking.collapsedTools', "When enabled, tool calls will be added by default inside the thinking block."), + tags: ['experimental'], + }, 'chat.disableAIFeatures': { type: 'boolean', description: nls.localize('chat.disableAIFeatures', "Disable and hide built-in AI features provided by GitHub Copilot, including chat and inline suggestions."), diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6af7955af7e..02c8c6c35c5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -21,7 +21,7 @@ import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { canceledName } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable, dispose, thenIfNotDisposed, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -1208,6 +1208,20 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.agent.thinkingStyle'); + const collapsedTools = this.configService.getValue('chat.agent.thinking.collapsedTools'); + const collapseToolsForFixedScrolling = thinkingStyle === ThinkingDisplayMode.FixedScrollingTools && collapsedTools; + + if (collapseToolsForFixedScrolling) { + if (part.kind === 'toolInvocation') { + return !part.confirmationMessages; + } + + if (part.kind === 'toolInvocationSerialized') { + return true; + } + } + if ((part.kind === 'toolInvocation' || part.kind === 'toolInvocationSerialized') && element) { // Explicit set of tools that should be pinned when there has been thinking const specialToolIds = new Set([ @@ -1228,6 +1242,26 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + if (!value) { + return false; + } + const text = typeof value === 'string' ? value : value.value; + return text.toLowerCase().includes('create'); + }; + + if (containsCreate(content.invocationMessage) || containsCreate(content.pastTenseMessage)) { + return true; + } + + return content.toolId.toLowerCase().includes('create'); + } + private finalizeCurrentThinkingPart(): void { if (!this._currentThinkingPart) { return; @@ -1252,11 +1286,22 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer content.kind === other.kind); } - const allowToolStreaming = (isResponseVM(context.element) && this.shouldCombineThinking(context.element)); - const isThinkingContent = content.kind === 'working' || content.kind === 'thinking'; - const isToolStreamingContent = allowToolStreaming && this.shouldPinPart(content, isResponseVM(context.element) ? context.element : undefined); - if (this._currentThinkingPart && !this._streamingThinking && !isThinkingContent && !isToolStreamingContent) { - this.finalizeCurrentThinkingPart(); + const lastRenderedPart = context.preceedingContentParts.length ? context.preceedingContentParts[context.preceedingContentParts.length - 1] : undefined; + const previousContent = context.contentIndex > 0 ? context.content[context.contentIndex - 1] : undefined; + + // Special handling for "create" tool invocations- do not end thinking if previous part is a create tool invocation and config is set. + const collapsedTools = this.configService.getValue('chat.agent.thinking.collapsedTools'); + const shouldKeepThinkingForCreateTool = collapsedTools && lastRenderedPart instanceof ChatToolInvocationPart && this.isCreateToolInvocationContent(previousContent); + + if (!shouldKeepThinkingForCreateTool) { + const isResponseElement = isResponseVM(context.element); + const allowToolStreaming = isResponseElement && this.shouldCombineThinking(context.element); + const isThinkingContent = content.kind === 'working' || content.kind === 'thinking'; + const toolInvocationContext = isResponseElement ? context.element : undefined; + const isToolStreamingContent = allowToolStreaming && this.shouldPinPart(content, toolInvocationContext); + if (this._currentThinkingPart && !this._streamingThinking && !isThinkingContent && !isToolStreamingContent) { + this.finalizeCurrentThinkingPart(); + } } } From 08b8ea2f3807ca8772294f5d7e871530f2187d57 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Oct 2025 05:50:40 +0100 Subject: [PATCH 1669/4355] debt - reduce more `any` types (#273358) * debt - reduce more `any` types * Update src/vs/workbench/api/browser/mainThreadNotebook.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/api/browser/mainThreadDebugService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- eslint.config.js | 28 ------------------- src/vs/base/common/history.ts | 2 +- src/vs/base/node/id.ts | 8 +++--- .../actionWidget/browser/actionList.ts | 2 +- .../quickinput/browser/tree/quickTree.ts | 2 +- .../platform/quickinput/common/quickAccess.ts | 1 + .../api/browser/mainThreadClipboard.ts | 4 +-- .../api/browser/mainThreadCodeInsets.ts | 2 +- .../api/browser/mainThreadComments.ts | 2 +- .../api/browser/mainThreadConfiguration.ts | 2 +- .../api/browser/mainThreadDebugService.ts | 2 +- .../api/browser/mainThreadDocuments.ts | 2 +- ...ainThreadEditSessionIdentityParticipant.ts | 2 +- .../workbench/api/browser/mainThreadErrors.ts | 6 ++-- .../api/browser/mainThreadExtensionService.ts | 2 +- .../api/browser/mainThreadFileSystem.ts | 2 +- .../api/browser/mainThreadLanguageFeatures.ts | 10 +++---- .../api/browser/mainThreadLanguageModels.ts | 6 ++-- .../api/browser/mainThreadNotebook.ts | 5 ++-- .../api/browser/mainThreadNotebookKernels.ts | 2 +- .../mainThreadNotebookSaveParticipant.ts | 2 +- .../api/browser/mainThreadOutputService.ts | 2 +- .../api/browser/mainThreadQuickOpen.ts | 2 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 6 ++-- .../api/browser/mainThreadSaveParticipant.ts | 2 +- .../workbench/api/browser/mainThreadSearch.ts | 6 ++-- .../workbench/api/browser/mainThreadTask.ts | 6 ++-- .../api/browser/mainThreadTelemetry.ts | 4 +-- 28 files changed, 48 insertions(+), 74 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index b32083866de..ff3e0c735aa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -192,7 +192,6 @@ export default tseslint.config( 'src/vs/base/browser/pixelRatio.ts', 'src/vs/base/browser/trustedTypes.ts', 'src/vs/base/browser/webWorkerFactory.ts', - 'src/vs/base/node/id.ts', 'src/vs/base/node/osDisplayProtocolInfo.ts', 'src/vs/base/node/osReleaseInfo.ts', 'src/vs/base/node/processes.ts', @@ -207,7 +206,6 @@ export default tseslint.config( 'src/vs/base/common/errorMessage.ts', 'src/vs/base/common/errors.ts', 'src/vs/base/common/event.ts', - 'src/vs/base/common/history.ts', 'src/vs/base/common/hotReload.ts', 'src/vs/base/common/hotReloadHelpers.ts', 'src/vs/base/common/iterator.ts', @@ -287,7 +285,6 @@ export default tseslint.config( 'src/vs/platform/accessibility/browser/accessibleView.ts', 'src/vs/platform/accessibility/common/accessibility.ts', 'src/vs/platform/action/common/action.ts', - 'src/vs/platform/actionWidget/browser/actionList.ts', 'src/vs/platform/actions/common/actions.ts', 'src/vs/platform/assignment/common/assignment.ts', 'src/vs/platform/browserElements/electron-main/nativeBrowserElementsMainService.ts', @@ -346,9 +343,7 @@ export default tseslint.config( 'src/vs/platform/policy/common/policyIpc.ts', 'src/vs/platform/policy/node/nativePolicyService.ts', 'src/vs/platform/profiling/common/profilingTelemetrySpec.ts', - 'src/vs/platform/quickinput/browser/commandsQuickAccess.ts', 'src/vs/platform/quickinput/browser/quickInputActions.ts', - 'src/vs/platform/quickinput/common/quickAccess.ts', 'src/vs/platform/quickinput/common/quickInput.ts', 'src/vs/platform/registry/common/platform.ts', 'src/vs/platform/remote/browser/browserSocketFactory.ts', @@ -413,7 +408,6 @@ export default tseslint.config( 'src/vs/platform/configuration/test/common/testConfigurationService.ts', 'src/vs/platform/instantiation/test/common/instantiationServiceMock.ts', 'src/vs/platform/keybinding/test/common/mockKeybindingService.ts', - 'src/vs/platform/quickinput/browser/tree/quickTree.ts', 'src/vs/platform/userDataSync/test/common/userDataSyncClient.ts', // Editor 'src/vs/editor/standalone/browser/standaloneEditor.ts', @@ -466,28 +460,6 @@ export default tseslint.config( 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts', // Workbench 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', - 'src/vs/workbench/api/browser/mainThreadClipboard.ts', - 'src/vs/workbench/api/browser/mainThreadCodeInsets.ts', - 'src/vs/workbench/api/browser/mainThreadComments.ts', - 'src/vs/workbench/api/browser/mainThreadConfiguration.ts', - 'src/vs/workbench/api/browser/mainThreadDebugService.ts', - 'src/vs/workbench/api/browser/mainThreadDocuments.ts', - 'src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts', - 'src/vs/workbench/api/browser/mainThreadErrors.ts', - 'src/vs/workbench/api/browser/mainThreadExtensionService.ts', - 'src/vs/workbench/api/browser/mainThreadFileSystem.ts', - 'src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts', - 'src/vs/workbench/api/browser/mainThreadLanguageModels.ts', - 'src/vs/workbench/api/browser/mainThreadNotebook.ts', - 'src/vs/workbench/api/browser/mainThreadNotebookKernels.ts', - 'src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts', - 'src/vs/workbench/api/browser/mainThreadOutputService.ts', - 'src/vs/workbench/api/browser/mainThreadQuickOpen.ts', - 'src/vs/workbench/api/browser/mainThreadSCM.ts', - 'src/vs/workbench/api/browser/mainThreadSaveParticipant.ts', - 'src/vs/workbench/api/browser/mainThreadSearch.ts', - 'src/vs/workbench/api/browser/mainThreadTask.ts', - 'src/vs/workbench/api/browser/mainThreadTelemetry.ts', 'src/vs/workbench/api/browser/mainThreadTerminalService.ts', 'src/vs/workbench/api/browser/mainThreadTreeViews.ts', 'src/vs/workbench/api/browser/statusBarExtensionPoint.ts', diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 1e34d03e159..d75120336f6 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -13,7 +13,7 @@ export interface IHistory { add(t: T): this; has(t: T): boolean; clear(): void; - forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void; + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: unknown): void; replace?(t: T[]): void; readonly onDidChange?: Event; } diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index c74fcf4ae61..b37534ac0bc 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -78,7 +78,7 @@ export const virtualMachineHint: { value(): number } = new class { }; let machineId: Promise; -export async function getMachineId(errorLogger: (error: any) => void): Promise { +export async function getMachineId(errorLogger: (error: Error) => void): Promise { if (!machineId) { machineId = (async () => { const id = await getMacMachineId(errorLogger); @@ -90,7 +90,7 @@ export async function getMachineId(errorLogger: (error: any) => void): Promise void): Promise { +async function getMacMachineId(errorLogger: (error: Error) => void): Promise { try { const crypto = await import('crypto'); const macAddress = getMac(); @@ -102,7 +102,7 @@ async function getMacMachineId(errorLogger: (error: any) => void): Promise void): Promise { +export async function getSqmMachineId(errorLogger: (error: Error) => void): Promise { if (isWindows) { const Registry = await import('@vscode/windows-registry'); try { @@ -115,7 +115,7 @@ export async function getSqmMachineId(errorLogger: (error: any) => void): Promis return ''; } -export async function getDevDeviceId(errorLogger: (error: any) => void): Promise { +export async function getDevDeviceId(errorLogger: (error: Error) => void): Promise { try { const deviceIdPackage = await import('@vscode/deviceid'); const id = await deviceIdPackage.getDeviceId(); diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 76ddb42ff5b..f89a4e7db1b 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -33,7 +33,7 @@ export interface IActionListDelegate { export interface IActionListItem { readonly item?: T; readonly kind: ActionListItemKind; - readonly group?: { kind?: any; icon?: ThemeIcon; title: string }; + readonly group?: { kind?: unknown; icon?: ThemeIcon; title: string }; readonly disabled?: boolean; readonly label?: string; readonly description?: string; diff --git a/src/vs/platform/quickinput/browser/tree/quickTree.ts b/src/vs/platform/quickinput/browser/tree/quickTree.ts index 829600d12cb..18f8137fabd 100644 --- a/src/vs/platform/quickinput/browser/tree/quickTree.ts +++ b/src/vs/platform/quickinput/browser/tree/quickTree.ts @@ -75,7 +75,7 @@ export class QuickTree extends QuickInput implements I } // TODO: Fix the any casting - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any get checkedLeafItems(): readonly T[] { return this.ui.tree.getCheckedLeafItems() as any as readonly T[]; } setItemTree(itemTree: T[]): void { diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index bf108ebd7cc..3645acd7131 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -169,6 +169,7 @@ export interface IQuickAccessProviderDescriptor { /** * The actual provider that will be instantiated as needed. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): IQuickAccessProvider }; /** diff --git a/src/vs/workbench/api/browser/mainThreadClipboard.ts b/src/vs/workbench/api/browser/mainThreadClipboard.ts index 636fa69163d..84946f0b845 100644 --- a/src/vs/workbench/api/browser/mainThreadClipboard.ts +++ b/src/vs/workbench/api/browser/mainThreadClipboard.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; +import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { MainContext, MainThreadClipboardShape } from '../common/extHost.protocol.js'; import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js'; import { ILogService } from '../../../platform/log/common/log.js'; @@ -12,7 +12,7 @@ import { ILogService } from '../../../platform/log/common/log.js'; export class MainThreadClipboard implements MainThreadClipboardShape { constructor( - _context: any, + _context: IExtHostContext, @IClipboardService private readonly _clipboardService: IClipboardService, @ILogService private readonly _logService: ILogService ) { } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index efc220d3c31..ee7e9d98df6 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -132,7 +132,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { inset.webview.contentOptions = reviveWebviewContentOptions(options); } - async $postMessage(handle: number, value: any): Promise { + async $postMessage(handle: number, value: unknown): Promise { const inset = this.getInset(handle); inset.webview.postMessage(value); return true; diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index d8bfdd63b6f..5a7b3ad78cd 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -521,7 +521,7 @@ export class MainThreadCommentController extends Disposable implements ICommentC await this._proxy.$updateCommentThreadTemplate(this.handle, threadHandle, range); } - toJSON(): any { + toJSON() { return { $mid: MarshalledId.CommentController, handle: this.handle diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index 07c29c26ee9..31a20b7cc09 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -72,7 +72,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { } } - private _updateValue(key: string, value: unknown, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + private _updateValue(key: string, value: unknown, configurationTarget: ConfigurationTarget, overriddenValue: unknown | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { overrides = scopeToLanguage === true ? overrides : scopeToLanguage === false ? { resource: overrides.resource } : overrides.overrideIdentifier && overriddenValue !== undefined ? overrides diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 16cd23d554b..e0f345da6f1 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -347,7 +347,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb session?.setName(name); } - public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): Promise { + public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: unknown): Promise { const session = this.debugService.getModel().getSession(sessionId, true); if (session) { return session.customRequest(request, args).then(response => { diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 772493301e6..9827a1a573f 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -51,7 +51,7 @@ export class BoundModelReferenceCollection { } } - add(uri: URI, ref: IReference, length: number = 0): void { + add(uri: URI, ref: IReference, length: number = 0): void { // const length = ref.object.textEditorModel.getValueLength(); const dispose = () => { const idx = this._data.indexOf(entry); diff --git a/src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts b/src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts index ae1198c5455..f834b6bbaf6 100644 --- a/src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadEditSessionIdentityParticipant.ts @@ -23,7 +23,7 @@ class ExtHostEditSessionIdentityCreateParticipant implements IEditSessionIdentit } async participate(workspaceFolder: WorkspaceFolder, token: CancellationToken): Promise { - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve, reject) => { setTimeout( () => reject(new Error(localize('timeout.onWillCreateEditSessionIdentity', "Aborted onWillCreateEditSessionIdentity-event after 10000ms"))), diff --git a/src/vs/workbench/api/browser/mainThreadErrors.ts b/src/vs/workbench/api/browser/mainThreadErrors.ts index becba7680c6..ef3eaf20eb8 100644 --- a/src/vs/workbench/api/browser/mainThreadErrors.ts +++ b/src/vs/workbench/api/browser/mainThreadErrors.ts @@ -14,9 +14,9 @@ export class MainThreadErrors implements MainThreadErrorsShape { // } - $onUnexpectedError(err: any | SerializedError): void { - if (err && err.$isError) { - err = transformErrorFromSerialization(err); + $onUnexpectedError(err: unknown | SerializedError): void { + if ((err as SerializedError | undefined)?.$isError) { + err = transformErrorFromSerialization(err as SerializedError); } onUnexpectedError(err); } diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 20daca562a4..4cfaf6a58cb 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -54,7 +54,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha internalExtHostContext._setExtensionHostProxy( new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService)) ); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any internalExtHostContext._setAllMainProxyIdentifiers(Object.keys(MainContext).map((key) => (MainContext)[key])); } diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index dd402147c27..ebdbeb4c398 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -150,7 +150,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } } - private static _handleError(err: any): never { + private static _handleError(err: unknown): never { if (err instanceof FileOperationError) { switch (err.fileOperationResult) { case FileOperationResult.FILE_NOT_FOUND: diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 799de991883..0af936d1771 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -222,7 +222,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.codeLensProvider.register(selector, provider)); } - $emitCodeLensEvent(eventHandle: number, event?: any): void { + $emitCodeLensEvent(eventHandle: number, event?: unknown): void { const obj = this._registrations.get(eventHandle); if (obj instanceof Emitter) { obj.fire(event); @@ -314,7 +314,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.inlineValuesProvider.register(selector, provider)); } - $emitInlineValuesEvent(eventHandle: number, event?: any): void { + $emitInlineValuesEvent(eventHandle: number, event?: unknown): void { const obj = this._registrations.get(eventHandle); if (obj instanceof Emitter) { obj.fire(event); @@ -964,7 +964,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.foldingRangeProvider.register(selector, provider)); } - $emitFoldingRangeEvent(eventHandle: number, event?: any): void { + $emitFoldingRangeEvent(eventHandle: number, event?: unknown): void { const obj = this._registrations.get(eventHandle); if (obj instanceof Emitter) { obj.fire(event); @@ -1009,7 +1009,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread outgoing.forEach(value => { value.to = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.to); }); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return outgoing; }, provideIncomingCalls: async (item, token) => { @@ -1020,7 +1020,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread incoming.forEach(value => { value.from = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.from); }); - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return incoming; } })); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 2f67a6ad620..e6f4e400938 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -32,7 +32,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { private readonly _store = new DisposableStore(); private readonly _providerRegistrations = new DisposableMap(); private readonly _lmProviderChange = new Emitter<{ vendor: string }>(); - private readonly _pendingProgress = new Map; stream: AsyncIterableSource }>(); + private readonly _pendingProgress = new Map; stream: AsyncIterableSource }>(); private readonly _ignoredFileProviderRegistrations = new DisposableMap(); constructor( @@ -70,7 +70,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { }, sendChatRequest: async (modelId, messages, from, options, token) => { const requestId = (Math.random() * 1e6) | 0; - const defer = new DeferredPromise(); + const defer = new DeferredPromise(); const stream = new AsyncIterableSource(); try { @@ -140,7 +140,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { return this._chatProviderService.selectLanguageModels(selector); } - async $tryStartChatRequest(extension: ExtensionIdentifier, modelIdentifier: string, requestId: number, messages: SerializableObjectWithBuffers, options: {}, token: CancellationToken): Promise { + async $tryStartChatRequest(extension: ExtensionIdentifier, modelIdentifier: string, requestId: number, messages: SerializableObjectWithBuffers, options: {}, token: CancellationToken): Promise { this._logService.trace('[CHAT] request STARTED', extension.value, requestId); let response: ILanguageModelChatResponse; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 4aec2291c33..799f50babd9 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -224,6 +224,7 @@ CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, note return bytes; }); -function isFileOperationError(error: any): error is FileOperationError { - return typeof error.fileOperationResult === 'number' && typeof error.message === 'string'; +function isFileOperationError(error: unknown): error is FileOperationError { + const candidate = error as FileOperationError | undefined; + return typeof candidate?.fileOperationResult === 'number' && typeof candidate?.message === 'string'; } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index e69273a0ca6..e35fcf9a1b8 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -197,7 +197,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape this._editors.deleteAndDispose(editor); } - async $postMessage(handle: number, editorId: string | undefined, message: any): Promise { + async $postMessage(handle: number, editorId: string | undefined, message: unknown): Promise { const tuple = this._kernels.get(handle); if (!tuple) { throw new Error('kernel already disposed'); diff --git a/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts index 60bc697adb9..375f88ea580 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookSaveParticipant.ts @@ -31,7 +31,7 @@ class ExtHostNotebookDocumentSaveParticipant implements IStoredFileWorkingCopySa let _warningTimeout: Timeout; - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve, reject) => { _warningTimeout = setTimeout( () => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveNotebookDocument-event after 1750ms"))), diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 434fc88d497..e95214aa087 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -49,7 +49,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut this._proxy.$setVisibleChannel(visibleChannel ? visibleChannel.id : null); this._outputStatusItem.value = undefined; }; - this._register(Event.any(this._outputService.onActiveOutputChannel, Event.filter(this._viewsService.onDidChangeViewVisibility, ({ id }) => id === OUTPUT_VIEW_ID))(() => setVisibleChannel())); + this._register(Event.any(this._outputService.onActiveOutputChannel, Event.filter(this._viewsService.onDidChangeViewVisibility, ({ id }) => id === OUTPUT_VIEW_ID))(() => setVisibleChannel())); setVisibleChannel(); } diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index ba715692e9d..2d276f83f7f 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -238,7 +238,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } default: - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any (input as any)[param] = params[param]; break; } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index de23ab0e428..9c421b05165 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -115,7 +115,7 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { private readonly _uriIdentService: IUriIdentityService ) { } - toJSON(): any { + toJSON() { return { $mid: MarshalledId.ScmResourceGroup, sourceControlHandle: this.sourceControlHandle, @@ -161,7 +161,7 @@ class MainThreadSCMResource implements ISCMResource { return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle, preserveFocus); } - toJSON(): any { + toJSON() { return { $mid: MarshalledId.ScmResource, sourceControlHandle: this.sourceControlHandle, @@ -534,7 +534,7 @@ class MainThreadSCMProvider implements ISCMProvider { this._historyProvider.get()?.$onDidChangeHistoryItemRefs(historyItemRefs); } - toJSON(): any { + toJSON() { return { $mid: MarshalledId.ScmProvider, handle: this.handle diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 9070caa10dd..f42487dffbb 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -30,7 +30,7 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant { return undefined; } - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve, reject) => { setTimeout( () => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))), diff --git a/src/vs/workbench/api/browser/mainThreadSearch.ts b/src/vs/workbench/api/browser/mainThreadSearch.ts index d511fe69bb9..19fe9b8fc63 100644 --- a/src/vs/workbench/api/browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/browser/mainThreadSearch.ts @@ -7,7 +7,7 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { DisposableStore, dispose, IDisposable } from '../../../base/common/lifecycle.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js'; +import { ITelemetryData, ITelemetryService } from '../../../platform/telemetry/common/telemetry.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from '../../services/search/common/search.js'; import { ExtHostContext, ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol.js'; @@ -83,7 +83,7 @@ export class MainThreadSearch implements MainThreadSearchShape { provider.handleKeywordResult(session, data); } - $handleTelemetry(eventName: string, data: any): void { + $handleTelemetry(eventName: string, data: ITelemetryData | undefined): void { this._telemetryService.publicLog(eventName, data); } } @@ -93,7 +93,7 @@ class SearchOperation { private static _idPool = 0; constructor( - readonly progress?: (match: IFileMatch | AISearchKeyword) => any, + readonly progress?: (match: IFileMatch | AISearchKeyword) => unknown, readonly id: number = ++SearchOperation._idPool, readonly matches = new Map(), readonly keywords: AISearchKeyword[] = [] diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 5a11aa6e92f..e151f5e4c2b 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -337,9 +337,9 @@ namespace TaskSourceDTO { } namespace TaskHandleDTO { - export function is(value: any): value is ITaskHandleDTO { - const candidate: ITaskHandleDTO = value; - return candidate && Types.isString(candidate.id) && !!candidate.workspaceFolder; + export function is(value: unknown): value is ITaskHandleDTO { + const candidate = value as ITaskHandleDTO | undefined; + return !!candidate && Types.isString(candidate.id) && !!candidate.workspaceFolder; } } diff --git a/src/vs/workbench/api/browser/mainThreadTelemetry.ts b/src/vs/workbench/api/browser/mainThreadTelemetry.ts index 7f49a22c20f..e4d5e7e5b6c 100644 --- a/src/vs/workbench/api/browser/mainThreadTelemetry.ts +++ b/src/vs/workbench/api/browser/mainThreadTelemetry.ts @@ -8,7 +8,7 @@ import { IConfigurationService } from '../../../platform/configuration/common/co import { IEnvironmentService } from '../../../platform/environment/common/environment.js'; import { IProductService } from '../../../platform/product/common/productService.js'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from '../../../platform/telemetry/common/gdprTypings.js'; -import { ITelemetryService, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../../platform/telemetry/common/telemetry.js'; +import { ITelemetryService, TelemetryLevel, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID, ITelemetryData } from '../../../platform/telemetry/common/telemetry.js'; import { supportsTelemetry } from '../../../platform/telemetry/common/telemetryUtils.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { ExtHostContext, ExtHostTelemetryShape, MainContext, MainThreadTelemetryShape } from '../common/extHost.protocol.js'; @@ -48,7 +48,7 @@ export class MainThreadTelemetry extends Disposable implements MainThreadTelemet return this._telemetryService.telemetryLevel; } - $publicLog(eventName: string, data: any = Object.create(null)): void { + $publicLog(eventName: string, data: ITelemetryData = Object.create(null)): void { // __GDPR__COMMON__ "pluginHostTelemetry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } data[MainThreadTelemetry._name] = true; this._telemetryService.publicLog(eventName, data); From 0279a827fae09b98b4bc6a7b64b01af7a32ad955 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Oct 2025 07:08:34 +0100 Subject: [PATCH 1670/4355] chat - remove auto reveal for todos that is not working anymore (#273365) chat - remove for todos that is not working anymore --- .../chatContentParts/chatTodoListWidget.ts | 75 +------------------ .../contrib/chat/browser/media/chat.css | 2 +- 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 9551ba0303e..948481ae8bf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -67,7 +67,7 @@ class TodoListRenderer implements IListRenderer { const { todoElement, statusIcon, iconLabel, statusElement } = templateData; // Update status icon - statusIcon.className = 'todo-status-icon codicon ' + this.getStatusIconClass(todo.status); + statusIcon.className = `todo-status-icon codicon ${this.getStatusIconClass(todo.status)}`; statusIcon.style.color = this.getStatusIconColor(todo.status); // Update title with tooltip if description exists and description field is enabled @@ -142,9 +142,7 @@ export class ChatTodoListWidget extends Disposable { private clearButtonContainer!: HTMLElement; private clearButton!: Button; private _currentSessionId: string | undefined; - private _userHasScrolledManually: boolean = false; private _todoList: WorkbenchList | undefined; - private readonly _listDisposables = this._register(new DisposableStore()); constructor( @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, @@ -211,11 +209,6 @@ export class ChatTodoListWidget extends Disposable { } })); - this._register(dom.addDisposableListener(this.todoListContainer, 'scroll', () => { - this.updateScrollShadow(); - this._userHasScrolledManually = true; - })); - return container; } @@ -242,7 +235,6 @@ export class ChatTodoListWidget extends Disposable { } if (this._currentSessionId !== sessionId) { - this._userHasScrolledManually = false; this._userManuallyExpanded = false; this._currentSessionId = sessionId; } @@ -289,30 +281,12 @@ export class ChatTodoListWidget extends Disposable { const allIncomplete = todoList.every(todo => todo.status === 'not-started'); if (allIncomplete) { - this._userHasScrolledManually = false; this._userManuallyExpanded = false; } - let lastActiveIndex = -1; - let firstCompletedIndex = -1; - let firstPendingAfterCompletedIndex = -1; - - // Track indices for smart scrolling - todoList.forEach((todo, index) => { - if (todo.status === 'completed' && firstCompletedIndex === -1) { - firstCompletedIndex = index; - } - if (todo.status === 'in-progress' || todo.status === 'completed') { - lastActiveIndex = index; - } - if (firstCompletedIndex !== -1 && todo.status === 'not-started' && firstPendingAfterCompletedIndex === -1) { - firstPendingAfterCompletedIndex = index; - } - }); - // Create or update the WorkbenchList if (!this._todoList) { - this._todoList = this.instantiationService.createInstance( + this._todoList = this._register(this.instantiationService.createInstance( WorkbenchList, 'ChatTodoListRenderer', this.todoListContainer, @@ -331,9 +305,7 @@ export class ChatTodoListWidget extends Disposable { getWidgetAriaLabel: () => localize('chatTodoList', 'Chat Todo List') } } - ); - - this._listDisposables.add(this._todoList); + )); } // Update list contents @@ -362,9 +334,6 @@ export class ChatTodoListWidget extends Disposable { this.updateTitleElement(titleElement, todoList); this._onDidChangeHeight.fire(); } - - // Auto-scroll to show the most relevant item - this.scrollToRelevantItem(lastActiveIndex, firstCompletedIndex, firstPendingAfterCompletedIndex, todoList.length); } private toggleExpanded(): void { @@ -400,44 +369,6 @@ export class ChatTodoListWidget extends Disposable { this._onDidChangeHeight.fire(); } - private scrollToRelevantItem(lastActiveIndex: number, firstCompletedIndex: number, firstPendingAfterCompletedIndex: number, totalItems: number): void { - if (totalItems <= 6 || this._userHasScrolledManually) { - return; - } - - setTimeout(() => { - const items = this.todoListContainer.querySelectorAll('.todo-item'); - - if (lastActiveIndex === -1 && firstCompletedIndex === -1) { - this.todoListContainer.scrollTo({ - top: 0, - behavior: 'instant' - }); - return; - } - - let targetIndex = lastActiveIndex; - - // Only show next pending if no in-progress items exist - if (firstCompletedIndex !== -1 && firstPendingAfterCompletedIndex !== -1 && lastActiveIndex < firstCompletedIndex) { - targetIndex = firstPendingAfterCompletedIndex; - } - - if (targetIndex >= 0 && targetIndex < items.length) { - const targetElement = items[targetIndex] as HTMLElement; - targetElement.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'nearest' - }); - } - }, 50); - } - - private updateScrollShadow(): void { - this.domNode.classList.toggle('scrolled', this.todoListContainer.scrollTop > 0); - } - private updateTitleElement(titleElement: HTMLElement, todoList: IChatTodo[]): void { titleElement.textContent = ''; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index f447e81cd22..b5ec00dc6da 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1214,7 +1214,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-item { display: flex; align-items: center; - gap: 8px; + gap: 6px; scroll-snap-align: start; min-height: 22px; font-size: var(--vscode-chat-font-size-body-m); From c40db410b92e347f863bf3dd496eef066b564345 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:35:03 -0700 Subject: [PATCH 1671/4355] Add a send message tool for automation & mcp (#273306) * Add a send message tool for automation & mcp This also sents up the posibility of smoke tests. * Add demonstrate.md and remove prompt * add simple notification util * notif --- .github/agents/demonstrate.md | 109 ++++++++++++++++++++++++++ .github/prompts/playwright.prompt.md | 15 ---- test/automation/src/chat.ts | 53 +++++++++++++ test/automation/src/index.ts | 2 + test/automation/src/notification.ts | 22 ++++++ test/automation/src/workbench.ts | 6 ++ test/mcp/src/automationTools/chat.ts | 45 +++++++++++ test/mcp/src/automationTools/index.ts | 7 +- 8 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 .github/agents/demonstrate.md delete mode 100644 .github/prompts/playwright.prompt.md create mode 100644 test/automation/src/chat.ts create mode 100644 test/automation/src/notification.ts create mode 100644 test/mcp/src/automationTools/chat.ts diff --git a/.github/agents/demonstrate.md b/.github/agents/demonstrate.md new file mode 100644 index 00000000000..1a59be8b211 --- /dev/null +++ b/.github/agents/demonstrate.md @@ -0,0 +1,109 @@ +--- +name: Demonstrate +description: Agent for demonstrating VS Code features +target: github-copilot +tools: ['edit', 'search', 'vscode-playwright-mcp/*', 'github/github-mcp-server/*', 'usages', 'fetch', 'githubRepo', 'todos'] +--- + +# Role and Objective + +You are a QA testing agent. Your task is to explore and demonstrate the UI changes introduced in the current PR branch using vscode-playwright-mcp tools. Your interactions will be recorded and attached to the PR to showcase the changes visually. + +# Core Requirements + +## Setup Phase + +1. Use GitHub MCP tools to get PR details (description, linked issues, comments) +2. Search the `microsoft/vscode-docs` repository for relevant documentation about the feature area +3. Examine changed files and commit messages to understand the scope +4. Identify what UI features or behaviors were modified +5. Start VS Code automation using `vscode_automation_start` +6. ALWAYS start by setting the setting `"chat.allowAnonymousAccess":true` using the `vscode_automation_settings_add_user_settings` tool. This will ensure that Chat works without requiring sign-in. + +## Testing Phase + +1. Use `browser_snapshot` to capture the current state +2. Execute the user workflows affected by the PR changes + +## Demonstration Goals + +- Show the new or modified UI in action +- Exercise the changed code paths through realistic user interactions +- Capture clear visual evidence of the improvements or changes +- Test edge cases or variations if applicable + +# Important Guidelines + +- Focus on DEMONSTRATING the changes, not verifying correctness +- You are NOT writing playwright tests - use the tools interactively to explore +- If the PR description or commits mention specific scenarios, prioritize testing those +- Make multiple passes if needed to capture different aspects of the changes +- You may make temporary modifications to facilitate better demonstration (e.g., adjusting settings, opening specific views) + +## GitHub MCP Tools + +**Prefer using GitHub MCP tools over `gh` CLI commands** - these provide structured data and better integration: + +### Pull Request Tools +- `pull_request_read` - Get PR details, diff, status, files, reviews, and comments + - Use `method="get"` for PR metadata (title, description, labels, etc.) + - Use `method="get_diff"` for the full diff + - Use `method="get_files"` for list of changed files + - Use `method="get_reviews"` for review summaries + - Use `method="get_review_comments"` for line-specific review comments +- `search_pull_requests` - Search PRs with filters (author, state, etc.) + +### Issue Tools +- `get_issue` - Get full issue details (description, labels, assignees, etc.) +- `get_issue_comments` - Get all comments on an issue +- `search_issues` - Search issues with filters +- `list_sub_issues` - Get sub-issues if using issue hierarchies + +## Pointers for Controlling VS Code + +- **Prefer `vscode_automation_*` tools over `browser_*` tools** when available - these are designed specifically for VS Code interactions and provide more reliable control. For example: + - `vscode_automation_chat_send_message` over using `browser_*` tools to send chat messages + - `vscode_automation_editor_type_text` over using `browser_*` tools to type in editors + +If you are typing into a monaco input and you can't use the standard methods, follow this sequence: + +**Monaco editors (used throughout VS Code) DO NOT work with standard Playwright methods like `.click()` on textareas or `.fill()` / `.type()`** + +**YOU MUST follow this exact sequence:** + +1. **Take a page snapshot** to identify the editor structure in the accessibility tree +2. **Find the parent `code` role element** that wraps the Monaco editor + - ❌ DO NOT click on `textarea` or `textbox` elements - these are overlaid by Monaco's rendering + - ✅ DO click on the `code` role element that is the parent container +3. **Click on the `code` element** to focus the editor - this properly delegates focus to Monaco's internal text handling +4. **Verify focus** by checking that the nested textbox element has the `[active]` attribute in a new snapshot +5. **Use `page.keyboard.press()` for EACH character individually** - standard Playwright `type()` or `fill()` methods don't work with Monaco editors since they intercept keyboard events at the page level + +**Example:** +```js +// ❌ WRONG - this will fail with timeout +await page.locator('textarea').click(); +await page.locator('textarea').fill('text'); + +// ✅ CORRECT +await page.locator('[role="code"]').click(); +await page.keyboard.press('t'); +await page.keyboard.press('e'); +await page.keyboard.press('x'); +await page.keyboard.press('t'); +``` + +**Why this is required:** Monaco editors intercept keyboard events at the page level and use a virtualized rendering system. Clicking textareas directly or using `.fill()` bypasses Monaco's event handling, causing timeouts and failures. + +# Workflow Pattern + +1. Gather context: + - Retrieve PR details using GitHub MCP (description, linked issues, review comments) + - Search microsoft/vscode-docs for documentation on the affected feature areas + - Examine changed files and commit messages +2. Plan which user interactions will best showcase the changes +3. Start automation and navigate to the relevant area +4. Perform the interactions +5. Document what you're demonstrating as you go +6. Ensure the recording clearly shows the before/after or new functionality +7. **ALWAYS stop the automation** by calling `vscode_automation_stop` - this is REQUIRED whether you successfully demonstrated the feature or encountered issues that prevented testing diff --git a/.github/prompts/playwright.prompt.md b/.github/prompts/playwright.prompt.md deleted file mode 100644 index 107c91475c0..00000000000 --- a/.github/prompts/playwright.prompt.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -mode: agent -description: 'Use playwright & automation tools to _see_ the code changes you have made' -tools: ['codebase', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'findTestFiles', 'searchResults', 'githubRepo', 'todos', 'runTests', 'editFiles', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'vscode-playwright-mcp', 'get_commit', 'get_discussion', 'get_discussion_comments', 'get_issue', 'get_issue_comments'] ---- -You are being requested to visually confirm the code changes you are making using vscode-playwright-mcp. - -You MUST run vscode_automation_start & browser_snapshot. -You MUST verify the bad behavior you are investigating using vscode-playwright-mcp. -You MUST verify the code changes you have made using vscode-playwright-mcp. -You MUST take before and after screenshots. -Remember, you are NOT writing playwright tests; instead, focus on using the tools to validate and explore the changes. -You MAY need to make multiple passes, iterating between making code changes and verifying them with the tools. -You MUST reload the window (`Developer: Reload Window` command) after making changes to ensure they are applied correctly. -You MAY make temporary changes to the code to facilitate testing and exploration. For example, using the quick pick in the Configure Display Language action as a scratch pad to add buttons to it. diff --git a/test/automation/src/chat.ts b/test/automation/src/chat.ts new file mode 100644 index 00000000000..ce308b51a5c --- /dev/null +++ b/test/automation/src/chat.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Code } from './code'; +import { Notification } from './notification'; + +const CHAT_VIEW = 'div[id="workbench.panel.chat"]'; +const CHAT_INPUT = `${CHAT_VIEW} .monaco-editor[role="code"]`; +const CHAT_INPUT_FOCUSED = `${CHAT_VIEW} .monaco-editor.focused[role="code"]`; + +export class Chat { + + constructor(private code: Code, private notification: Notification) { } + + async waitForChatView(): Promise { + await this.code.waitForElement(CHAT_VIEW); + } + + async waitForInputFocus(): Promise { + await this.code.waitForElement(CHAT_INPUT_FOCUSED); + } + + async sendMessage(message: string): Promise { + if (await this.notification.isNotificationVisible()) { + throw new Error('Notification is visible'); + } + // Click on the chat input to focus it + await this.code.waitAndClick(CHAT_INPUT); + + // Wait for the editor to be focused + await this.waitForInputFocus(); + + // Dispatch a paste event with the message + await this.code.driver.currentPage.evaluate(({ selector, text }: { selector: string; text: string }) => { + const element = document.querySelector(selector); + if (element) { + const dataTransfer = new DataTransfer(); + dataTransfer.setData('text/plain', text); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true + }); + element.dispatchEvent(pasteEvent); + } + }, { selector: CHAT_INPUT, text: message }); + + // Submit the message + await this.code.dispatchKeybinding('enter', () => Promise.resolve()); + } +} diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index b0f1b0f1422..29fdf0fd54f 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -26,4 +26,6 @@ export * from './viewlet'; export * from './localization'; export * from './workbench'; export * from './task'; +export * from './chat'; +export * from './notification'; export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electron'; diff --git a/test/automation/src/notification.ts b/test/automation/src/notification.ts new file mode 100644 index 00000000000..1145dc4a29b --- /dev/null +++ b/test/automation/src/notification.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Code } from './code'; + +const NOTIFICATION_TOAST = '.notification-toast'; + +export class Notification { + + constructor(private code: Code) { } + + async isNotificationVisible(): Promise { + try { + await this.code.waitForElement(NOTIFICATION_TOAST, undefined, 1); + return true; + } catch { + return false; + } + } +} diff --git a/test/automation/src/workbench.ts b/test/automation/src/workbench.ts index 34905874310..1c624db35d7 100644 --- a/test/automation/src/workbench.ts +++ b/test/automation/src/workbench.ts @@ -22,6 +22,8 @@ import { Terminal } from './terminal'; import { Notebook } from './notebook'; import { Localization } from './localization'; import { Task } from './task'; +import { Chat } from './chat'; +import { Notification } from './notification'; export interface Commands { runCommand(command: string, options?: { exactLabelMatch?: boolean }): Promise; @@ -47,6 +49,8 @@ export class Workbench { readonly notebook: Notebook; readonly localization: Localization; readonly task: Task; + readonly chat: Chat; + readonly notification: Notification; constructor(code: Code) { this.editors = new Editors(code); @@ -67,5 +71,7 @@ export class Workbench { this.notebook = new Notebook(this.quickaccess, this.quickinput, code); this.localization = new Localization(code); this.task = new Task(code, this.editor, this.editors, this.quickaccess, this.quickinput, this.terminal); + this.notification = new Notification(code); + this.chat = new Chat(code, this.notification); } } diff --git a/test/mcp/src/automationTools/chat.ts b/test/mcp/src/automationTools/chat.ts new file mode 100644 index 00000000000..5157bf19ddd --- /dev/null +++ b/test/mcp/src/automationTools/chat.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { ApplicationService } from '../application'; +import { z } from 'zod'; + +/** + * Chat Tools + */ +export function applyChatTools(server: McpServer, appService: ApplicationService): RegisteredTool[] { + const tools: RegisteredTool[] = []; + + tools.push(server.tool( + 'vscode_automation_chat_send_message', + 'Send a message to the VS Code chat panel', + { + message: z.string().describe('The message to send to the chat') + }, + async (args) => { + const { message } = args; + const app = await appService.getOrCreateApplication(); + try { + await app.workbench.chat.sendMessage(message); + return { + content: [{ + type: 'text' as const, + text: `Sent chat message: "${message}"` + }] + }; + } catch (error) { + return { + content: [{ + type: 'text' as const, + text: `Failed to send chat message: ${error}` + }] + }; + } + } + )); + + return tools; +} diff --git a/test/mcp/src/automationTools/index.ts b/test/mcp/src/automationTools/index.ts index 8bcbc3705b8..12c5d04a725 100644 --- a/test/mcp/src/automationTools/index.ts +++ b/test/mcp/src/automationTools/index.ts @@ -24,6 +24,7 @@ import { applyNotebookTools } from './notebook.js'; import { applyLocalizationTools } from './localization.js'; import { applyTaskTools } from './task.js'; import { applyProfilerTools } from './profiler.js'; +import { applyChatTools } from './chat.js'; import { ApplicationService } from '../application'; /** @@ -89,6 +90,9 @@ export function applyAllTools(server: McpServer, appService: ApplicationService) // Profiler Tools tools = tools.concat(applyProfilerTools(server, appService)); + // Chat Tools + tools = tools.concat(applyChatTools(server, appService)); + // Return all registered tools return tools; } @@ -112,5 +116,6 @@ export { applyNotebookTools, applyLocalizationTools, applyTaskTools, - applyProfilerTools + applyProfilerTools, + applyChatTools }; From d0d29fef37676b1b4c480b2ae9cc88a34197f99a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 26 Oct 2025 09:28:55 +0100 Subject: [PATCH 1672/4355] SCM - only use bold/different icon when there are multiple repositories (#273373) --- .../workbench/contrib/scm/browser/scmRepositoryRenderer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index bc4b8c74bb5..838abb0d34a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -116,13 +116,16 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer 1); const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) ? repository.provider.iconPath : Codicon.repo; - templateData.icon.className = icon.id === Codicon.repo.id && isVisible + // Only show the selected icon if there are multiple repositories in the workspace + const showSelectedIcon = icon.id === Codicon.repo.id && isVisible && this.scmViewService.repositories.length > 1; + + templateData.icon.className = showSelectedIcon ? `icon ${ThemeIcon.asClassName(Codicon.repoSelected)}` : `icon ${ThemeIcon.asClassName(icon)}`; })); From 2b0fdddd91b1d77fb1352f613f6869ea60e60b94 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:24:48 -0700 Subject: [PATCH 1673/4355] Align terminal suggest command with editor's Fixes #273375 --- .../suggest/browser/terminal.suggest.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 3a4ff1bbe08..93761653b31 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -276,7 +276,8 @@ registerTerminalAction({ registerActiveInstanceAction({ id: TerminalSuggestCommandId.RequestCompletions, - title: localize2('workbench.action.terminal.requestCompletions', 'Request Completions'), + title: localize2('workbench.action.terminal.triggerSuggest', 'Trigger Suggest'), + f1: false, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Space, mac: { primary: KeyMod.WinCtrl | KeyCode.Space }, From de73c0382b34c1ac1d7e8884845168eee4b2a44c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:28:30 -0700 Subject: [PATCH 1674/4355] Change command name as well --- .../accessibility/browser/terminalAccessibilityHelp.ts | 2 +- .../suggest/browser/terminal.suggest.contribution.ts | 2 +- .../terminalContrib/suggest/common/terminal.suggest.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index e42ca6c1379..3c8691a2cd1 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -69,7 +69,7 @@ export class TerminalAccessibilityHelpProvider extends Disposable implements IAc } if (this._configurationService.getValue(TerminalSuggestSettingId.Enabled)) { - content.push(localize('suggestTrigger', 'The terminal request completions command can be invoked manually, but also appears while typing.', TerminalSuggestCommandId.RequestCompletions)); + content.push(localize('suggestTrigger', 'The terminal request completions command can be invoked manually, but also appears while typing.', TerminalSuggestCommandId.TriggerSuggest)); content.push(localize('suggest', 'When the terminal suggest widget is focused:')); content.push(localize('suggestCommands', '- Accept the suggestion and configure suggest settings.', TerminalSuggestCommandId.AcceptSelectedSuggestion, TerminalSuggestCommandId.ConfigureSettings)); content.push(localize('suggestCommandsMore', '- Toggle between the widget and terminal and toggle details focus to learn more about the suggestion.', TerminalSuggestCommandId.ToggleDetails, TerminalSuggestCommandId.ToggleDetailsFocus)); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 93761653b31..53e375c966a 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -275,7 +275,7 @@ registerTerminalAction({ }); registerActiveInstanceAction({ - id: TerminalSuggestCommandId.RequestCompletions, + id: TerminalSuggestCommandId.TriggerSuggest, title: localize2('workbench.action.terminal.triggerSuggest', 'Trigger Suggest'), f1: false, keybinding: { diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts index 04f9fe16baf..4b026d37019 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts @@ -12,7 +12,7 @@ export const enum TerminalSuggestCommandId { AcceptSelectedSuggestionEnter = 'workbench.action.terminal.acceptSelectedSuggestionEnter', HideSuggestWidget = 'workbench.action.terminal.hideSuggestWidget', HideSuggestWidgetAndNavigateHistory = 'workbench.action.terminal.hideSuggestWidgetAndNavigateHistory', - RequestCompletions = 'workbench.action.terminal.requestCompletions', + TriggerSuggest = 'workbench.action.terminal.triggerSuggest', ResetWidgetSize = 'workbench.action.terminal.resetSuggestWidgetSize', ToggleDetails = 'workbench.action.terminal.suggestToggleDetails', ToggleDetailsFocus = 'workbench.action.terminal.suggestToggleDetailsFocus', @@ -29,7 +29,7 @@ export const defaultTerminalSuggestCommandsToSkipShell = [ TerminalSuggestCommandId.AcceptSelectedSuggestion, TerminalSuggestCommandId.AcceptSelectedSuggestionEnter, TerminalSuggestCommandId.HideSuggestWidget, - TerminalSuggestCommandId.RequestCompletions, + TerminalSuggestCommandId.TriggerSuggest, TerminalSuggestCommandId.ToggleDetails, TerminalSuggestCommandId.ToggleDetailsFocus, ]; From 7d01e5892bb51cec8d89869fc366f00736989202 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 03:08:43 -0700 Subject: [PATCH 1675/4355] Enable terminal suggest by default on stable Fixes #226562 --- .../suggest/common/terminalSuggestConfiguration.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index d23f9691546..1ac503d9e5b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -66,15 +66,13 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Sun, 26 Oct 2025 10:37:32 +0000 Subject: [PATCH 1676/4355] Initial plan From a80e31495d167d28c7dbad795f9745b4d34f9fa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:52:34 +0000 Subject: [PATCH 1677/4355] Add insertTrailingSpace setting to terminal suggest configuration Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../suggest/browser/terminalSuggestAddon.ts | 6 ++++++ .../suggest/common/terminalSuggestConfiguration.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 1981a275da2..d80f3c69516 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -1000,6 +1000,12 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest SuggestAddon.lastAcceptedCompletionTimestamp = 0; } + // Add trailing space if enabled and not a folder + const config = this._configurationService.getValue(terminalSuggestConfigSection); + if (config.insertTrailingSpace && completion.kind !== TerminalCompletionItemKind.Folder) { + resultSequence += ' '; + } + // Send the completion this._onAcceptedCompletion.fire(resultSequence); this._suggestTelemetry?.acceptCompletion(this._sessionId, completion, this._mostRecentPromptInputState?.value); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 4be30d1c7d7..322c5ddfe14 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -22,6 +22,7 @@ export const enum TerminalSuggestSettingId { InlineSuggestion = 'terminal.integrated.suggest.inlineSuggestion', UpArrowNavigatesHistory = 'terminal.integrated.suggest.upArrowNavigatesHistory', SelectionMode = 'terminal.integrated.suggest.selectionMode', + InsertTrailingSpace = 'terminal.integrated.suggest.insertTrailingSpace', } export const windowsDefaultExecutableExtensions: string[] = [ @@ -59,6 +60,7 @@ export interface ITerminalSuggestConfiguration { showStatusBar: boolean; cdPath: 'off' | 'relative' | 'absolute'; inlineSuggestion: 'off' | 'alwaysOnTopExceptExactMatch' | 'alwaysOnTop'; + insertTrailingSpace: boolean; } export const terminalSuggestConfiguration: IStringDictionary = { @@ -174,6 +176,12 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Sun, 26 Oct 2025 10:54:26 +0000 Subject: [PATCH 1678/4355] Initial plan From f296ec5ca121d127755d3c18a97f08d7d9639f51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:55:25 +0000 Subject: [PATCH 1679/4355] Exclude symbolic link folders from trailing space insertion Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- .../terminalContrib/suggest/browser/terminalSuggestAddon.ts | 4 ++-- .../suggest/common/terminalSuggestConfiguration.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index d80f3c69516..10389412de8 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -1000,9 +1000,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest SuggestAddon.lastAcceptedCompletionTimestamp = 0; } - // Add trailing space if enabled and not a folder + // Add trailing space if enabled and not a folder or symbolic link folder const config = this._configurationService.getValue(terminalSuggestConfigSection); - if (config.insertTrailingSpace && completion.kind !== TerminalCompletionItemKind.Folder) { + if (config.insertTrailingSpace && completion.kind !== TerminalCompletionItemKind.Folder && completion.kind !== TerminalCompletionItemKind.SymbolicLinkFolder) { resultSequence += ' '; } diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 322c5ddfe14..fd5625a735e 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -178,7 +178,7 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Sun, 26 Oct 2025 11:08:29 +0000 Subject: [PATCH 1680/4355] Add advanced settings registration for terminal developer settings - Register DeveloperPtyHostLatency setting with description and advanced tag - Register DeveloperPtyHostStartupDelay setting with description and advanced tag - Register DevMode setting with description and advanced tag - Update comment in terminal.ts to reflect settings are no longer hidden Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- src/vs/platform/terminal/common/terminal.ts | 2 +- .../terminal/common/terminalConfiguration.ts | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 322ef0bced2..339ceb02883 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -120,7 +120,7 @@ export const enum TerminalSettingId { FontLigaturesFeatureSettings = 'terminal.integrated.fontLigatures.featureSettings', FontLigaturesFallbackLigatures = 'terminal.integrated.fontLigatures.fallbackLigatures', - // Debug settings that are hidden from user + // Developer/debug settings /** Simulated latency applied to all calls made to the pty host */ DeveloperPtyHostLatency = 'terminal.integrated.developer.ptyHost.latency', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 27a0cba1f5e..f3546adfadf 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -640,6 +640,26 @@ const terminalConfiguration: IStringDictionary = { localize('terminal.integrated.focusAfterRun.none', "Do nothing."), ] }, + [TerminalSettingId.DeveloperPtyHostLatency]: { + description: localize('terminal.integrated.developer.ptyHost.latency', "Simulated latency in milliseconds applied to all calls made to the pty host. This is useful for testing terminal behavior under high latency conditions."), + type: 'number', + minimum: 0, + default: 0, + tags: ['advanced'] + }, + [TerminalSettingId.DeveloperPtyHostStartupDelay]: { + description: localize('terminal.integrated.developer.ptyHost.startupDelay', "Simulated startup delay in milliseconds for the pty host process. This is useful for testing terminal initialization under slow startup conditions."), + type: 'number', + minimum: 0, + default: 0, + tags: ['advanced'] + }, + [TerminalSettingId.DevMode]: { + description: localize('terminal.integrated.developer.devMode', "Enable developer mode for the terminal. This shows additional debug information and visualizations for shell integration sequences."), + type: 'boolean', + default: false, + tags: ['advanced'] + }, ...terminalContribConfiguration, }; From 7af5d1ff4123bf8dc02177582d2f3ef275b50bc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:18:49 -0700 Subject: [PATCH 1681/4355] Handle single character on line prompt terminators This issue happened because using \u2b9e as a prompt character is not handled explicitly, unlike \u276f. The fix is to detect single character prompt terminators (excluding whitespace). Fixes #224553 --- .../common/capabilities/commandDetectionCapability.ts | 2 +- .../terminal/common/xterm/shellIntegrationAddon.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index d0184883d20..0bcb3f93cbe 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -968,7 +968,7 @@ class WindowsPtyHeuristics extends Disposable { } // Dynamic prompt detection - if (this._capability.promptTerminator && lineText.trim().endsWith(this._capability.promptTerminator)) { + if (this._capability.promptTerminator && (lineText === this._capability.promptTerminator || lineText.trim().endsWith(this._capability.promptTerminator))) { const adjustedPrompt = this._adjustPrompt(lineText, lineText, this._capability.promptTerminator); if (adjustedPrompt) { return adjustedPrompt; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 4113a6bc1bc..1bd2eec9b6d 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -618,7 +618,14 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return; } const lastPromptLine = prompt.substring(prompt.lastIndexOf('\n') + 1); - const promptTerminator = lastPromptLine.substring(lastPromptLine.lastIndexOf(' ')); + const lastPromptLineTrimmed = lastPromptLine.trim(); + const promptTerminator = ( + lastPromptLineTrimmed.length === 1 + // The prompt line contains a single character, treat the full line as the + // terminator for example "\u2b9e " + ? lastPromptLine + : lastPromptLine.substring(lastPromptLine.lastIndexOf(' ')) + ); if (promptTerminator) { this._createOrGetCommandDetection(this._terminal).setPromptTerminator(promptTerminator, lastPromptLine); } From e0af9ccaffadc04130f1dc0e1ca45fd59e557623 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:40:33 -0700 Subject: [PATCH 1682/4355] Re-trigger after completing with trailing space --- .../suggest/browser/terminalSuggestAddon.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 10389412de8..d24f4a2689b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -93,6 +93,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _currentSuggestionDetails?: CancelablePromise; private _focusedItem?: TerminalCompletionItem; private _ignoreFocusEvents: boolean = false; + private _requestCompletionsOnNextSync: boolean = false; isPasting: boolean = false; shellType: TerminalShellType | undefined; @@ -506,6 +507,12 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest { let sent = false; + // If completions were requested from the addon + if (this._requestCompletionsOnNextSync) { + this._requestCompletionsOnNextSync = false; + sent = this._requestTriggerCharQuickSuggestCompletions(); + } + // If the cursor moved to the right if (!this._mostRecentPromptInputState || promptInputState.cursorIndex > this._mostRecentPromptInputState.cursorIndex) { // Quick suggestions - Trigger whenever a new non-whitespace character is used @@ -1004,6 +1011,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest const config = this._configurationService.getValue(terminalSuggestConfigSection); if (config.insertTrailingSpace && completion.kind !== TerminalCompletionItemKind.Folder && completion.kind !== TerminalCompletionItemKind.SymbolicLinkFolder) { resultSequence += ' '; + this._lastUserDataTimestamp = Date.now(); + this._requestCompletionsOnNextSync = true; } // Send the completion From ba52695ec3f33ed19737631c53b97fe611f0adbc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:45:22 -0700 Subject: [PATCH 1683/4355] Mention insertTrailingSpace retriggers completions --- .../suggest/common/terminalSuggestConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index fd5625a735e..c388d702063 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -178,7 +178,7 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Sun, 26 Oct 2025 04:53:55 -0700 Subject: [PATCH 1684/4355] Ignore inline env var setting prefix in path completions Fixes #255709 --- .../browser/terminalCompletionService.ts | 8 ++++++- .../browser/terminalCompletionService.test.ts | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 270bd346951..cb86a71e169 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -271,7 +271,13 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // TODO: Leverage Fig's tokens array here? // The last word (or argument). When the cursor is following a space it will be the empty // string - const lastWord = cursorPrefix.endsWith(' ') ? '' : cursorPrefix.split(/(?.+)$/); + if (matchEnvVarPrefix?.groups?.rhs) { + lastWord = matchEnvVarPrefix.groups.rhs; + } // Get the nearest folder path from the prefix. This ignores everything after the `/` as // they are what triggers changes in the directory. diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 92eff34116c..46676ea96bd 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -461,6 +461,29 @@ suite('TerminalCompletionService', () => { ], { replacementRange: [0, 0] }); }); + test('should ignore environment variable setting prefixes', async () => { + const resourceOptions: TerminalCompletionResourceOptions = { + cwd: URI.parse('file:///test'), + showDirectories: true, + pathSeparator + }; + validResources = [URI.parse('file:///test')]; + childResources = [ + { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, + { resource: URI.parse('file:///test/folder2/'), isDirectory: true } + ]; + const result = await terminalCompletionService.resolveResources(resourceOptions, 'FOO=./', 2, provider, capabilities); + + // Must not include FOO= prefix in completions + assertCompletions(result, [ + { label: '.', detail: '/test/' }, + { label: './folder1/', detail: '/test/folder1/' }, + { label: './folder2/', detail: '/test/folder2/' }, + { label: '../', detail: '/' }, + standardTidleItem, + ], { replacementRange: [0, 2] }); + }); + test('./| should handle large directories with many results gracefully', async () => { const resourceOptions: TerminalCompletionResourceOptions = { cwd: URI.parse('file:///test'), From 65b22997d7addf8d34400207bc8a3d7c4cf07f70 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sun, 26 Oct 2025 15:11:46 +0100 Subject: [PATCH 1685/4355] Convert flaky API tests to unit tests (#273398) Convert flaky API test to unit test (#253863 , #254041) I maintain my conviction that there is an unrelated run-away API test which steals focus while these tests execute which then leads to these tests failing, since the undo command is sensitive to the current focused editor. --- .../src/singlefolder-tests/editor.test.ts | 49 +----- .../src/singlefolder-tests/workspace.test.ts | 34 ---- .../test/browser/mainThreadEditors.test.ts | 157 ++++++++++++++++-- 3 files changed, 147 insertions(+), 93 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 1bd22369152..ce8e68e0c1e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { commands, env, Position, Range, Selection, SnippetString, TextDocument, TextEditor, TextEditorCursorStyle, TextEditorLineNumbersStyle, Uri, window, workspace } from 'vscode'; +import { env, Position, Range, Selection, SnippetString, TextDocument, TextEditor, TextEditorCursorStyle, TextEditorLineNumbersStyle, Uri, window, workspace } from 'vscode'; import { assertNoRpc, closeAllEditors, createRandomFile, deleteFile } from '../utils'; suite('vscode API - editors', () => { @@ -167,53 +167,6 @@ suite('vscode API - editors', () => { }); }); - function executeReplace(editor: TextEditor, range: Range, text: string, undoStopBefore: boolean, undoStopAfter: boolean): Thenable { - return editor.edit((builder) => { - builder.replace(range, text); - }, { undoStopBefore: undoStopBefore, undoStopAfter: undoStopAfter }); - } - - test.skip('TextEditor.edit can control undo/redo stack 1', () => { - return withRandomFileEditor('Hello world!', async (editor, doc) => { - const applied1 = await executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false); - assert.ok(applied1); - assert.strictEqual(doc.getText(), 'hello world!'); - assert.ok(doc.isDirty); - - const applied2 = await executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', false, false); - assert.ok(applied2); - assert.strictEqual(doc.getText(), 'hELLO world!'); - assert.ok(doc.isDirty); - - await commands.executeCommand('undo'); - if (doc.getText() === 'hello world!') { - // see https://github.com/microsoft/vscode/issues/109131 - // it looks like an undo stop was inserted in between these two edits - // it is unclear why this happens, but it can happen for a multitude of reasons - await commands.executeCommand('undo'); - } - assert.strictEqual(doc.getText(), 'Hello world!'); - }); - }); - - test.skip('TextEditor.edit can control undo/redo stack 2', () => { - return withRandomFileEditor('Hello world!', (editor, doc) => { - return executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false).then(applied => { - assert.ok(applied); - assert.strictEqual(doc.getText(), 'hello world!'); - assert.ok(doc.isDirty); - return executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', true, false); - }).then(applied => { - assert.ok(applied); - assert.strictEqual(doc.getText(), 'hELLO world!'); - assert.ok(doc.isDirty); - return commands.executeCommand('undo'); - }).then(_ => { - assert.strictEqual(doc.getText(), 'hello world!'); - }); - }); - }); - test('issue #16573: Extension API: insertSpaces and tabSize are undefined', () => { return withRandomFileEditor('Hello world!\n\tHello world!', (editor, _doc) => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 3bcaecb2a71..19f72931512 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -1179,40 +1179,6 @@ suite('vscode API - workspace', () => { }); - test.skip('issue #110141 - TextEdit.setEndOfLine applies an edit and invalidates redo stack even when no change is made', async () => { - const file = await createRandomFile('hello\nworld'); - - const document = await vscode.workspace.openTextDocument(file); - await vscode.window.showTextDocument(document); - - // apply edit - { - const we = new vscode.WorkspaceEdit(); - we.insert(file, new vscode.Position(0, 5), '2'); - await vscode.workspace.applyEdit(we); - } - - // check the document - { - assert.strictEqual(document.getText(), 'hello2\nworld'); - assert.strictEqual(document.isDirty, true); - } - - // apply no-op edit - { - const we = new vscode.WorkspaceEdit(); - we.set(file, [vscode.TextEdit.setEndOfLine(vscode.EndOfLine.LF)]); - await vscode.workspace.applyEdit(we); - } - - // undo - { - await vscode.commands.executeCommand('undo'); - assert.strictEqual(document.getText(), 'hello\nworld'); - assert.strictEqual(document.isDirty, false); - } - }); - test('SnippetString in WorkspaceEdit', async function (): Promise { const file = await createRandomFile('hello\nworld'); diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 511856e6607..dc04aae1ce1 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -11,12 +11,12 @@ import { mock } from '../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; -import { EditOperation } from '../../../../editor/common/core/editOperation.js'; +import { EditOperation, ISingleEditOperation } from '../../../../editor/common/core/editOperation.js'; import { Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js'; -import { ITextSnapshot } from '../../../../editor/common/model.js'; +import { EndOfLineSequence, ITextSnapshot } from '../../../../editor/common/model.js'; import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; import { LanguageService } from '../../../../editor/common/services/languageService.js'; import { IModelService } from '../../../../editor/common/services/model.js'; @@ -59,23 +59,36 @@ import { IWorkingCopyService } from '../../../services/workingCopy/common/workin import { TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestWorkingCopyService } from '../../../test/browser/workbenchTestServices.js'; import { TestContextService, TestFileService, TestTextResourcePropertiesService } from '../../../test/common/workbenchTestServices.js'; import { MainThreadBulkEdits } from '../../browser/mainThreadBulkEdits.js'; +import { MainThreadTextEditors, IMainThreadEditorLocator } from '../../browser/mainThreadEditors.js'; +import { MainThreadTextEditor } from '../../browser/mainThreadEditor.js'; +import { MainThreadDocuments } from '../../browser/mainThreadDocuments.js'; import { IWorkspaceTextEditDto } from '../../common/extHost.protocol.js'; import { SingleProxyRPCProtocol } from '../common/testRPCProtocol.js'; +import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { TestClipboardService } from '../../../../platform/clipboard/test/common/testClipboardService.js'; +import { createTestCodeEditor } from '../../../../editor/test/browser/testCodeEditor.js'; suite('MainThreadEditors', () => { let disposables: DisposableStore; + const existingResource = URI.parse('foo:existing'); const resource = URI.parse('foo:bar'); let modelService: IModelService; let bulkEdits: MainThreadBulkEdits; + let editors: MainThreadTextEditors; + let editorLocator: IMainThreadEditorLocator; + let testEditor: MainThreadTextEditor; const movedResources = new Map(); const copiedResources = new Map(); const createdResources = new Set(); const deletedResources = new Set(); + const editorId = 'testEditorId'; + setup(() => { disposables = new DisposableStore(); @@ -101,7 +114,8 @@ suite('MainThreadEditors', () => { services.set(IDialogService, dialogService); services.set(INotificationService, notificationService); services.set(IUndoRedoService, undoRedoService); - services.set(IModelService, modelService); + services.set(ITextResourcePropertiesService, new SyncDescriptor(TestTextResourcePropertiesService)); + services.set(IModelService, new SyncDescriptor(ModelService)); services.set(ICodeEditorService, new TestCodeEditorService(themeService)); services.set(IFileService, new TestFileService()); services.set(IUriIdentityService, new SyncDescriptor(UriIdentityService)); @@ -110,13 +124,19 @@ suite('MainThreadEditors', () => { services.set(ILifecycleService, new TestLifecycleService()); services.set(IWorkingCopyService, new TestWorkingCopyService()); services.set(IEditorGroupsService, new TestEditorGroupsService()); + services.set(IClipboardService, new TestClipboardService()); services.set(ITextFileService, new class extends mock() { override isDirty() { return false; } // eslint-disable-next-line local/code-no-any-casts override files = { onDidSave: Event.None, onDidRevert: Event.None, - onDidChangeDirty: Event.None + onDidChangeDirty: Event.None, + onDidChangeEncoding: Event.None + }; + // eslint-disable-next-line local/code-no-any-casts + override untitled = { + onDidChangeEncoding: Event.None }; override create(operations: { resource: URI }[]) { for (const o of operations) { @@ -181,14 +201,33 @@ suite('MainThreadEditors', () => { const instaService = new InstantiationService(services); - modelService = new ModelService( - configService, - new TestTextResourcePropertiesService(configService), - undoRedoService, - instaService - ); - bulkEdits = instaService.createInstance(MainThreadBulkEdits, SingleProxyRPCProtocol(null)); + const documents = instaService.createInstance(MainThreadDocuments, SingleProxyRPCProtocol(null)); + + // Create editor locator + editorLocator = { + getEditor(id: string): MainThreadTextEditor | undefined { + return id === editorId ? testEditor : undefined; + }, + findTextEditorIdFor() { return undefined; }, + getIdOfCodeEditor() { return undefined; } + }; + + editors = instaService.createInstance(MainThreadTextEditors, editorLocator, SingleProxyRPCProtocol(null)); + modelService = instaService.invokeFunction(accessor => accessor.get(IModelService)); + + // Create a test code editor using the helper + const model = modelService.createModel('Hello world!', null, existingResource); + const testCodeEditor = disposables.add(createTestCodeEditor(model)); + + testEditor = disposables.add(instaService.createInstance( + MainThreadTextEditor, + editorId, + model, + testCodeEditor, + { onGainedFocus() { }, onLostFocus() { } }, + documents + )); }); teardown(() => { @@ -250,6 +289,48 @@ suite('MainThreadEditors', () => { return Promise.all([p1, p2]); }); + test('applyWorkspaceEdit: noop eol edit keeps undo stack clean', async () => { + + const initialText = 'hello\nworld'; + const model = disposables.add(modelService.createModel(initialText, null, resource)); + const initialAlternativeVersionId = model.getAlternativeVersionId(); + + const insertEdit: IWorkspaceTextEditDto = { + resource: resource, + versionId: model.getVersionId(), + textEdit: { + range: new Range(1, 6, 1, 6), + text: '2' + } + }; + + const insertResult = await bulkEdits.$tryApplyWorkspaceEdit(new SerializableObjectWithBuffers({ edits: [insertEdit] })); + assert.strictEqual(insertResult, true); + assert.strictEqual(model.getValue(), 'hello2\nworld'); + assert.notStrictEqual(model.getAlternativeVersionId(), initialAlternativeVersionId); + + const eolEdit: IWorkspaceTextEditDto = { + resource: resource, + versionId: model.getVersionId(), + textEdit: { + range: new Range(1, 1, 1, 1), + text: '', + eol: EndOfLineSequence.LF + } + }; + + const eolResult = await bulkEdits.$tryApplyWorkspaceEdit(new SerializableObjectWithBuffers({ edits: [eolEdit] })); + assert.strictEqual(eolResult, true); + assert.strictEqual(model.getValue(), 'hello2\nworld'); + + const undoResult = model.undo(); + if (undoResult) { + await undoResult; + } + assert.strictEqual(model.getValue(), initialText); + assert.strictEqual(model.getAlternativeVersionId(), initialAlternativeVersionId); + }); + test(`applyWorkspaceEdit with only resource edit`, () => { return bulkEdits.$tryApplyWorkspaceEdit(new SerializableObjectWithBuffers({ edits: [ @@ -264,4 +345,58 @@ suite('MainThreadEditors', () => { assert.strictEqual(deletedResources.has(resource), true); }); }); + + test('applyWorkspaceEdit can control undo/redo stack 1', async () => { + const model = modelService.getModel(existingResource)!; + + const edit1: ISingleEditOperation = { + range: new Range(1, 1, 1, 2), + text: 'h', + forceMoveMarkers: false + }; + + const applied1 = await editors.$tryApplyEdits(editorId, model.getVersionId(), [edit1], { undoStopBefore: false, undoStopAfter: false }); + assert.strictEqual(applied1, true); + assert.strictEqual(model.getValue(), 'hello world!'); + + const edit2: ISingleEditOperation = { + range: new Range(1, 2, 1, 6), + text: 'ELLO', + forceMoveMarkers: false + }; + + const applied2 = await editors.$tryApplyEdits(editorId, model.getVersionId(), [edit2], { undoStopBefore: false, undoStopAfter: false }); + assert.strictEqual(applied2, true); + assert.strictEqual(model.getValue(), 'hELLO world!'); + + await model.undo(); + assert.strictEqual(model.getValue(), 'Hello world!'); + }); + + test('applyWorkspaceEdit can control undo/redo stack 2', async () => { + const model = modelService.getModel(existingResource)!; + + const edit1: ISingleEditOperation = { + range: new Range(1, 1, 1, 2), + text: 'h', + forceMoveMarkers: false + }; + + const applied1 = await editors.$tryApplyEdits(editorId, model.getVersionId(), [edit1], { undoStopBefore: false, undoStopAfter: false }); + assert.strictEqual(applied1, true); + assert.strictEqual(model.getValue(), 'hello world!'); + + const edit2: ISingleEditOperation = { + range: new Range(1, 2, 1, 6), + text: 'ELLO', + forceMoveMarkers: false + }; + + const applied2 = await editors.$tryApplyEdits(editorId, model.getVersionId(), [edit2], { undoStopBefore: true, undoStopAfter: false }); + assert.strictEqual(applied2, true); + assert.strictEqual(model.getValue(), 'hELLO world!'); + + await model.undo(); + assert.strictEqual(model.getValue(), 'hello world!'); + }); }); From d8d8f423a16c543a02872374346812ddd839aa42 Mon Sep 17 00:00:00 2001 From: barroit Date: Sun, 26 Oct 2025 23:12:41 +0900 Subject: [PATCH 1686/4355] Fix tabstop calc in tokenizeLineToHTML() (#263387) * editor: fix tabstop calc in tokenizeLineToHTML() * Add unit test and make code a bit more readable --------- Signed-off-by: Jiamu Sun --- .../common/languages/textToHtmlTokenizer.ts | 36 ++++++---- .../common/modes/textToHtmlTokenizer.test.ts | 69 +++++++++++++++++++ 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/common/languages/textToHtmlTokenizer.ts b/src/vs/editor/common/languages/textToHtmlTokenizer.ts index b0745eda9f8..5f176a27b72 100644 --- a/src/vs/editor/common/languages/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/languages/textToHtmlTokenizer.ts @@ -32,28 +32,36 @@ export async function tokenizeToString(languageService: ILanguageService, text: export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number, useNbsp: boolean): string { let result = `
    `; - let charIndex = startOffset; - let tabsCharDelta = 0; + let charIndex = 0; + let width = 0; let prevIsSpace = true; for (let tokenIndex = 0, tokenCount = viewLineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) { const tokenEndIndex = viewLineTokens.getEndOffset(tokenIndex); - - if (tokenEndIndex <= startOffset) { - continue; - } - let partContent = ''; for (; charIndex < tokenEndIndex && charIndex < endOffset; charIndex++) { const charCode = text.charCodeAt(charIndex); + const isTab = charCode === CharCode.Tab; + + width += strings.isFullWidthCharacter(charCode) ? 2 : (isTab ? 0 : 1); + + if (charIndex < startOffset) { + if (isTab) { + const remainder = width % tabSize; + width += remainder === 0 ? tabSize : tabSize - remainder; + } + continue; + } switch (charCode) { case CharCode.Tab: { - let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; - tabsCharDelta += insertSpacesCount - 1; - while (insertSpacesCount > 0) { + const remainder = width % tabSize; + const insertSpacesCount = remainder === 0 ? tabSize : tabSize - remainder; + width += insertSpacesCount; + let spacesRemaining = insertSpacesCount; + while (spacesRemaining > 0) { if (useNbsp && prevIsSpace) { partContent += ' '; prevIsSpace = false; @@ -61,7 +69,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens partContent += ' '; prevIsSpace = true; } - insertSpacesCount--; + spacesRemaining--; } break; } @@ -115,9 +123,13 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens } } + if (tokenEndIndex <= startOffset) { + continue; + } + result += `${partContent}`; - if (tokenEndIndex > endOffset || charIndex >= endOffset) { + if (tokenEndIndex > endOffset || charIndex >= endOffset || startOffset >= endOffset) { break; } } diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 31230b13358..7c0ab5d725b 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -292,6 +292,75 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); }); + test('tokenizeLineToHTML with tabs and non-zero startOffset #263387', () => { + // This test demonstrates the issue where tab padding is calculated incorrectly + // when startOffset is non-zero and there are tabs AFTER the start position. + // The bug: tabsCharDelta doesn't account for characters before startOffset. + + const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00']; + + // Critical test case: "\ta\tb" starting at position 2 (skipping first tab and 'a') + // Layout: First tab (pos 0) goes to column 4, 'a' (pos 1) at column 4, + // second tab (pos 2) should go from column 5 to column 8 (3 spaces) + // With the bug: charIndex starts at 2, tabsCharDelta=0 (first tab was never seen) + // When processing second tab: insertSpacesCount = 4 - (2 + 0) % 4 = 2 spaces (WRONG!) + // The old code thinks it's at column 2, but it's actually at column 5 + const text = '\ta\tb'; + const lineTokens = new TestLineTokens([ + new TestLineToken( + 1, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new TestLineToken( + 2, + ( + (3 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new TestLineToken( + 3, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new TestLineToken( + 4, + ( + (4 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ) + ]); + + // First, verify the full line works correctly + assert.strictEqual( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 4, 4, true), + [ + '
    ', + '    ', // First tab: 4 spaces + 'a', // 'a' at column 4 + '   ', // Second tab: 3 spaces (column 5 to 8) + 'b', + '
    ' + ].join('') + ); + + // THE BUG: Starting at position 2 (after first tab and 'a') + // Expected (with fix): 3 spaces for the second tab (column 5 to 8) + // Buggy behavior (old code): 2 spaces (thinks it's at column 2, gives   ) + // The fix correctly accounts for the skipped tab and 'a', outputting     + assert.strictEqual( + tokenizeLineToHTML(text, lineTokens, colorMap, 2, 4, 4, true), + [ + '
    ', + '   ', // With fix: 3 spaces; with bug: only 2 spaces + 'b', + '
    ' + ].join('') + ); + }); + }); class Mode extends Disposable { From 6055fcf8d8b9256741e181238d0f0f8caa3f97cc Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:13:22 +0000 Subject: [PATCH 1687/4355] Fix double-click on punctuation selecting adjacent space instead of character (#273321) * Initial plan * Fix double-click on punctuation selecting adjacent space - Modified word selection logic to check for both Regular and Separator word types - Previously only WordType.Regular was checked, causing Separator characters (punctuation) to be ignored - Added comprehensive test cases for punctuation selection Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> * Fix double-click on punctuation selecting adjacent space (revised) - Modified word selection logic to check for Separator word types in initial selection - Kept drag selection behavior unchanged for Separator words to maintain existing behavior - Added comprehensive test cases for punctuation selection - All existing tests pass Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> --- .../common/cursor/cursorWordOperations.ts | 16 ++++++--- .../test/browser/controller/cursor.test.ts | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/cursor/cursorWordOperations.ts b/src/vs/editor/common/cursor/cursorWordOperations.ts index 3e1d066587d..9c7aa6f182a 100644 --- a/src/vs/editor/common/cursor/cursorWordOperations.ts +++ b/src/vs/editor/common/cursor/cursorWordOperations.ts @@ -746,11 +746,19 @@ export class WordOperations { let endColumn: number; if (prevWord && prevWord.wordType === WordType.Regular && prevWord.start <= position.column - 1 && position.column - 1 <= prevWord.end) { - // isTouchingPrevWord + // isTouchingPrevWord (Regular word) + startColumn = prevWord.start + 1; + endColumn = prevWord.end + 1; + } else if (prevWord && prevWord.wordType === WordType.Separator && prevWord.start <= position.column - 1 && position.column - 1 < prevWord.end) { + // isTouchingPrevWord (Separator word) - stricter check, don't include end boundary startColumn = prevWord.start + 1; endColumn = prevWord.end + 1; } else if (nextWord && nextWord.wordType === WordType.Regular && nextWord.start <= position.column - 1 && position.column - 1 <= nextWord.end) { - // isTouchingNextWord + // isTouchingNextWord (Regular word) + startColumn = nextWord.start + 1; + endColumn = nextWord.end + 1; + } else if (nextWord && nextWord.wordType === WordType.Separator && nextWord.start <= position.column - 1 && position.column - 1 < nextWord.end) { + // isTouchingNextWord (Separator word) - stricter check, don't include end boundary startColumn = nextWord.start + 1; endColumn = nextWord.end + 1; } else { @@ -776,11 +784,11 @@ export class WordOperations { let endColumn: number; if (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 < prevWord.end) { - // isInsidePrevWord + // isInsidePrevWord (Regular word) startColumn = prevWord.start + 1; endColumn = prevWord.end + 1; } else if (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 < nextWord.end) { - // isInsideNextWord + // isInsideNextWord (Regular word) startColumn = nextWord.start + 1; endColumn = nextWord.end + 1; } else { diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 210dbed3713..1322aae8fba 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -2457,6 +2457,40 @@ suite('Editor Controller', () => { }); }); + test('Double-click on punctuation should select the character, not adjacent space', () => { + const model = createTextModel( + [ + '// a b c 1 2 3 ~ ! @ # $ % ^ & * ( ) _ + \\ /' + ].join('\n') + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + // Test double-click on '@' at position 20 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 20) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 20, 1, 21), 'Should select @ character'); + + // Test double-click on '#' at position 22 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 22) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 22, 1, 23), 'Should select # character'); + + // Test double-click on '!' at position 18 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 18) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 18, 1, 19), 'Should select ! character'); + + // Test double-click on first '/' in '//' at position 1 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 1) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 1, 1, 3), 'Should select // token'); + + // Test double-click on second '/' in '//' at position 2 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 2) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 1, 1, 3), 'Should select // token'); + + // Test double-click on '\' at position 42 + CoreNavigationCommands.WordSelect.runCoreEditorCommand(viewModel, { position: new Position(1, 42) }); + assert.deepStrictEqual(viewModel.getSelection(), new Selection(1, 42, 1, 43), 'Should select \\ character'); + }); + }); + test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => { withTestCodeEditor([], {}, (editor, viewModel) => { const model = editor.getModel()!; From 84fed05516884c03062782cd45adf04739c4ea04 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:18:16 -0700 Subject: [PATCH 1688/4355] Add placeholder to target picker in Add MCP Server flow (#272398) It was missing a placeholder and our UX guidelines say we should have one. --- .../contrib/mcp/browser/mcpCommandsAddConfiguration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts index b1aaf374e0c..acdc7d98954 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpCommandsAddConfiguration.ts @@ -291,7 +291,8 @@ export class McpAddConfigurationCommand { } const targetPick = await this._quickInputService.pick(options, { - title: localize('mcp.target.title', "Choose where to install the MCP server"), + title: localize('mcp.target.title', "Add MCP Server"), + placeHolder: localize('mcp.target.placeholder', "Select the configuration target") }); return targetPick?.target; From c19948ed52c6d31d56db4c4639315cb039977c35 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:38:49 -0700 Subject: [PATCH 1689/4355] Use a bigger video size (#273449) --- test/automation/src/playwrightBrowser.ts | 6 +++++- test/automation/src/playwrightElectron.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/automation/src/playwrightBrowser.ts b/test/automation/src/playwrightBrowser.ts index 463ebb842d1..a459826b571 100644 --- a/test/automation/src/playwrightBrowser.ts +++ b/test/automation/src/playwrightBrowser.ts @@ -107,7 +107,11 @@ async function launchBrowser(options: LaunchOptions, endpoint: string) { const context = await measureAndLog( () => browser.newContext({ - recordVideo: options.videosPath ? { dir: options.videosPath } : undefined + recordVideo: options.videosPath + ? { + dir: options.videosPath, + size: { width: 1920, height: 1080 } + } : undefined, }), 'browser.newContext', logger diff --git a/test/automation/src/playwrightElectron.ts b/test/automation/src/playwrightElectron.ts index 29555eb8b02..9ebf37fe827 100644 --- a/test/automation/src/playwrightElectron.ts +++ b/test/automation/src/playwrightElectron.ts @@ -33,7 +33,11 @@ async function launchElectron(configuration: IElectronConfiguration, options: La const electron = await measureAndLog(() => playwrightImpl._electron.launch({ executablePath: configuration.electronPath, args: configuration.args, - recordVideo: options.videosPath ? { dir: options.videosPath } : undefined, + recordVideo: options.videosPath + ? { + dir: options.videosPath, + size: { width: 1920, height: 1080 } + } : undefined, env: configuration.env as { [key: string]: string }, timeout: 0 }), 'playwright-electron#launch', logger); From 1502127f5d11882e8478f9f7ce29378a3df13763 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 07:18:26 +0100 Subject: [PATCH 1690/4355] css - some tweaks to status widths and format (#273478) --- src/vs/workbench/browser/actions/media/actions.css | 6 +++--- .../parts/editor/media/breadcrumbscontrol.css | 2 +- .../browser/parts/editor/media/editorgroupview.css | 4 ++-- .../parts/editor/media/singleeditortabscontrol.css | 2 +- .../browser/parts/media/paneCompositePart.css | 8 ++++---- .../parts/statusbar/media/statusbarpart.css | 14 +++++++------- .../parts/titlebar/media/menubarControl.css | 8 +++----- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/browser/actions/media/actions.css index 24f01c279c7..3fe27d61027 100644 --- a/src/vs/workbench/browser/actions/media/actions.css +++ b/src/vs/workbench/browser/actions/media/actions.css @@ -22,7 +22,7 @@ .monaco-workbench .screencast-keyboard { position: absolute; - background-color: rgba(0, 0, 0 ,0.5); + background-color: rgba(0, 0, 0, 0.5); width: 100%; left: 0; z-index: 100000; @@ -45,9 +45,9 @@ .monaco-workbench .screencast-keyboard > .key { padding: 0 8px; - box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4); + box-shadow: inset 0 -3px 0 hsla(0, 0%, 73%, .4); margin-right: 6px; - border: 1px solid hsla(0,0%,80%,.4); + border: 1px solid hsla(0, 0%, 80%, .4); border-radius: 5px; background-color: rgba(255, 255, 255, 0.05); } diff --git a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css index ec3ef9c9c13..4e4e923ad9b 100644 --- a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css @@ -60,6 +60,6 @@ height: 100%; } -.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree .monaco-highlighted-label .highlight{ +.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree .monaco-highlighted-label .highlight { font-weight: bold; } diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 76c8a561b4e..0974b591f16 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -109,7 +109,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title { position: relative; - box-sizing: border-box; + box-sizing: border-box; overflow: hidden; } @@ -157,7 +157,7 @@ /* Editor */ -.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container { +.monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container { display: none; } diff --git a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css index 216c71f9926..2c3cee55f9a 100644 --- a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css @@ -52,7 +52,7 @@ font-size: 0.9em; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.backslash-path .monaco-breadcrumb-item::before { +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.backslash-path .monaco-breadcrumb-item::before { content: '\\'; font-size: 0.9em; } diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index de625937628..beeb1de096d 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -148,12 +148,12 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, -.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, .monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { opacity: 1; } @@ -279,8 +279,8 @@ width: calc(100% - 4px); } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon.checked, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon.checked { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon.checked, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon.checked { background-color: var(--vscode-activityBarTop-activeBackground); } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 22410c9c8ea..5632cca0ced 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -60,7 +60,7 @@ flex-grow: 1; /* left items push right items to the far right end */ } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item { display: inline-block; line-height: 22px; height: 100%; @@ -102,7 +102,7 @@ height: 100%; margin-right: 3px; margin-left: 3px; - padding: 0 5px 0 5px; + padding: 0 5px; white-space: pre; /* gives some degree of styling */ align-items: center; text-overflow: ellipsis; @@ -124,16 +124,16 @@ .monaco-workbench .part.statusbar > .items-container > .statusbar-item.compact-left.compact-right > .statusbar-item-label { margin-left: 0; - margin-right:0; + margin-right: 0; } .monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label, .monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item > .statusbar-item-label, .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color > .statusbar-item-label { - margin-left: 0; + margin-left: 0; /* Reduce margin to let element attach to the corners */ margin-right: 0; - padding-left: 10px; - padding-right: 10px; + padding-left: 8px; /* But increase padding to preserve our usual spacing */ + padding-right: 8px; } .monaco-workbench .part.statusbar > .items-container > .statusbar-item.compact-left.has-background-color > .statusbar-item-label { @@ -155,7 +155,7 @@ cursor: default; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.codicon { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.codicon { text-align: center; color: inherit; } diff --git a/src/vs/workbench/browser/parts/titlebar/media/menubarControl.css b/src/vs/workbench/browser/parts/titlebar/media/menubarControl.css index 923c221df25..9cbfa4fbd85 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/menubarControl.css +++ b/src/vs/workbench/browser/parts/titlebar/media/menubarControl.css @@ -16,7 +16,7 @@ .monaco-workbench .activitybar .menubar.compact > .menubar-menu-button.open, .monaco-workbench .activitybar .menubar.compact > .menubar-menu-button:focus, .monaco-workbench .activitybar .menubar.compact:not(:focus-within) > .menubar-menu-button:hover, -.monaco-workbench .activitybar .menubar.compact > .menubar-menu-button.open .toolbar-toggle-more, +.monaco-workbench .activitybar .menubar.compact > .menubar-menu-button.open .toolbar-toggle-more, .monaco-workbench .activitybar .menubar.compact > .menubar-menu-button:focus .toolbar-toggle-more, .monaco-workbench .activitybar .menubar.compact:not(:focus-within) > .menubar-menu-button:hover .toolbar-toggle-more { color: var(--vscode-activityBar-foreground); @@ -27,7 +27,7 @@ } .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button, -.monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button .toolbar-toggle-more { +.monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button .toolbar-toggle-more { color: var(--vscode-titleBar-inactiveForeground); } @@ -46,7 +46,7 @@ background-color: var(--vscode-menubar-selectionBackground); } -.monaco-workbench .menubar > .menubar-menu-button:hover .menubar-menu-title { +.monaco-workbench .menubar > .menubar-menu-button:hover .menubar-menu-title { outline: dashed 1px var(--vscode-menubar-selectionBorder); } @@ -61,5 +61,3 @@ outline-color: var(--vscode-menubar-selectionBorder); outline-offset: -1px; } - - From 908f34c37dd98bf1e6d88e695cc8b5a4cf0addc0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 07:18:37 +0000 Subject: [PATCH 1691/4355] Fix scrollbar background color customization issue (#273399) * Initial plan * Add scrollbar.background color customization Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> * Update known variables --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> Co-authored-by: Alex Dima --- build/lib/stylelint/vscode-known-variables.json | 1 + src/vs/base/browser/ui/scrollbar/media/scrollbars.css | 4 ++++ src/vs/platform/theme/common/colors/miscColors.ts | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 3ff1a9f624b..8a63b6fe995 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -622,6 +622,7 @@ "--vscode-scmGraph-historyItemHoverLabelForeground", "--vscode-scmGraph-historyItemRefColor", "--vscode-scmGraph-historyItemRemoteRefColor", + "--vscode-scrollbar-background", "--vscode-scrollbar-shadow", "--vscode-scrollbarSlider-activeBackground", "--vscode-scrollbarSlider-background", diff --git a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css index 84c17370894..be500bc2e56 100644 --- a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css +++ b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css @@ -59,6 +59,10 @@ box-shadow: var(--vscode-scrollbar-shadow) 6px 0 6px -6px inset; } +.monaco-scrollable-element > .scrollbar { + background: var(--vscode-scrollbar-background); +} + .monaco-scrollable-element > .scrollbar > .slider { background: var(--vscode-scrollbarSlider-background); } diff --git a/src/vs/platform/theme/common/colors/miscColors.ts b/src/vs/platform/theme/common/colors/miscColors.ts index f816bf493f9..d1e8e2b4195 100644 --- a/src/vs/platform/theme/common/colors/miscColors.ts +++ b/src/vs/platform/theme/common/colors/miscColors.ts @@ -65,6 +65,10 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac { dark: Color.fromHex('#BFBFBF').transparent(0.4), light: Color.fromHex('#000000').transparent(0.6), hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('scrollbarSliderActiveBackground', "Scrollbar slider background color when clicked on.")); +export const scrollbarBackground = registerColor('scrollbar.background', + null, + nls.localize('scrollbarBackground', "Scrollbar track background color.")); + // ----- progress bar From b583069cf5efe08655916beb05fc8b56e2e25cd2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 27 Oct 2025 00:28:28 -0700 Subject: [PATCH 1692/4355] Add experiment object to terminal suggest Part of #273414 --- .../suggest/common/terminalSuggestConfiguration.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index 4be30d1c7d7..b068c938302 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -66,7 +66,10 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Mon, 27 Oct 2025 09:05:06 +0100 Subject: [PATCH 1693/4355] :lipstick: --- .../inlineCompletions/browser/model/inlineCompletionsSource.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 9303f54ea27..68961626e9b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -418,7 +418,6 @@ export class InlineCompletionsSource extends Disposable { timeUntilProviderResponse: undefined, viewKind: undefined, preceeded: undefined, - error: undefined, superseded: undefined, reason: undefined, correlationId: undefined, From c5eda378307c3a7e40fab30eb3364b3e0f94ebc2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 27 Oct 2025 01:36:29 -0700 Subject: [PATCH 1694/4355] Fix focus terminal action in chat part Fixes #273437 --- .../toolInvocationParts/chatTerminalToolProgressPart.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index ea9aff59ef0..ee1ea89b17d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -185,6 +185,7 @@ export class FocusChatInstanceAction extends Action implements IAction { constructor( private readonly _instance: ITerminalInstance, isTerminalHidden: boolean, + @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, ) { @@ -203,6 +204,7 @@ export class FocusChatInstanceAction extends Action implements IAction { } else { this._terminalGroupService.showPanel(true); } + this._terminalService.setActiveInstance(this._instance); await this._instance?.focusWhenReady(true); } } From b6980a7c3a88d9d24f75ce5425ddcb4dd651d789 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:44:29 +0100 Subject: [PATCH 1695/4355] Replace console.warn with ILogService.warn in tasks (#269138) --- .../workbench/contrib/tasks/browser/abstractTaskService.ts | 6 +++--- .../workbench/contrib/tasks/browser/terminalTaskSystem.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 44835396e86..37bf04aa2e1 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -768,7 +768,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const result = await raceTimeout( Promise.all(this._getActivationEvents(type).map(activationEvent => this._extensionService.activateByEvent(activationEvent))), 5000, - () => console.warn('Timed out activating extensions for task providers') + () => this._logService.warn('Timed out activating extensions for task providers') ); if (result) { this._activatedTaskProviders.add(type ?? 'all'); @@ -2643,7 +2643,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (!this._jsonTasksSupported && (parseResult.custom.length > 0)) { - console.warn('Custom workspace tasks are not supported.'); + this._logService.warn('Custom workspace tasks are not supported.'); } return { workspaceFolder, set: { tasks: this._jsonTasksSupported ? parseResult.custom : [] }, configurations: customizedTasks, hasErrors }; } @@ -2746,7 +2746,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (!this._jsonTasksSupported && (parseResult.custom.length > 0)) { - console.warn('Custom workspace tasks are not supported.'); + this._logService.warn('Custom workspace tasks are not supported.'); } else { for (const task of parseResult.custom) { custom.push(task); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 66f7156ac2a..bd6813a51ec 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1138,7 +1138,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (this._activeTasks[task.getMapKey()]) { this._activeTasks[task.getMapKey()].terminal = terminal; } else { - console.warn('No active tasks found for the terminal.'); + this._logService.warn('No active tasks found for the terminal.'); } this._fireTaskEvent(TaskEvent.changed()); return promise; From 749ee49fcc100b234b0eefc75220cf37a19943c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:18:58 -0700 Subject: [PATCH 1696/4355] Add hasRemoteAuthority to createInstance event Fixes #273142 --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 ++-- .../workbench/contrib/terminal/browser/terminalTelemetry.ts | 4 +++- .../chatAgentTools/browser/toolTerminalCreator.ts | 2 +- .../browser/terminal.sendSequence.contribution.ts | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d479aa32a77..090b8aaa839 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -764,7 +764,7 @@ export interface ITerminalInstance extends IBaseTerminalInstance { /** * Whether the terminal's pty is hosted on a remote. */ - readonly isRemote: boolean; + readonly hasRemoteAuthority: boolean; /** * The remote authority of the terminal's pty. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 4541bfaf300..8491708286e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -596,7 +596,7 @@ export function registerTerminalActions() { } const instance = await c.service.getActiveOrCreateInstance({ acceptsInput: true }); - const isRemote = instance ? instance.isRemote : (workbenchEnvironmentService.remoteAuthority ? true : false); + const isRemote = instance ? instance.hasRemoteAuthority : (workbenchEnvironmentService.remoteAuthority ? true : false); const uri = editor.getModel().uri; if ((!isRemote && uri.scheme !== Schemas.file && uri.scheme !== Schemas.vscodeUserData) || (isRemote && uri.scheme !== Schemas.vscodeRemote)) { notificationService.warn(localize('workbench.action.terminal.runActiveFile.noFile', 'Only files on disk can be run in the terminal')); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3168e53a12c..254432f6794 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -275,7 +275,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } get shellType(): TerminalShellType | undefined { return this._shellType; } get os(): OperatingSystem | undefined { return this._processManager.os; } - get isRemote(): boolean { return this._processManager.remoteAuthority !== undefined; } + get hasRemoteAuthority(): boolean { return this._processManager.remoteAuthority !== undefined; } get remoteAuthority(): string | undefined { return this._processManager.remoteAuthority; } get hasFocus(): boolean { return dom.isAncestorOfActiveElement(this._wrapperElement); } get title(): string { return this._title; } @@ -921,7 +921,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const timeoutMs = getShellIntegrationTimeout( this._configurationService, siInjectionEnabled, - this.isRemote, + this.hasRemoteAuthority, this._processManager.processReadyTimestamp ); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts b/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts index 18d4347808b..5ddaea73b99 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTelemetry.ts @@ -32,7 +32,6 @@ export class TerminalTelemetryContribution extends Disposable implements IWorkbe const store = new DisposableStore(); this._store.add(store); - await Promise.race([ // Wait for process ready so the shell launch config is fully resolved, then // allow another 10 seconds for the shell integration to be fully initialized @@ -74,6 +73,7 @@ export class TerminalTelemetryContribution extends Disposable implements IWorkbe isExtensionOwnedTerminal: boolean; isLoginShell: boolean; isReconnect: boolean; + hasRemoteAuthority: boolean; shellIntegrationQuality: number; shellIntegrationInjected: boolean; @@ -94,6 +94,7 @@ export class TerminalTelemetryContribution extends Disposable implements IWorkbe isExtensionOwnedTerminal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the terminal was created by an extension.' }; isLoginShell: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the arguments contain -l or --login.' }; isReconnect: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the terminal is reconnecting to an existing instance.' }; + hasRemoteAuthority: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the terminal has a remote authority, this is likely a connection terminal when undefined in a window with a remote authority.' }; shellIntegrationQuality: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The shell integration quality (rich=2, basic=1 or none=0).' }; shellIntegrationInjected: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the shell integration script was injected.' }; @@ -115,6 +116,7 @@ export class TerminalTelemetryContribution extends Disposable implements IWorkbe isExtensionOwnedTerminal: !!slc.isExtensionOwnedTerminal, isLoginShell: (typeof slc.args === 'string' ? slc.args.split(' ') : slc.args)?.some(arg => arg === '-l' || arg === '--login') ?? false, isReconnect: !!slc.attachPersistentProcess, + hasRemoteAuthority: instance.hasRemoteAuthority, shellIntegrationQuality: commandDetection?.hasRichCommandDetection ? 2 : commandDetection ? 1 : 0, shellIntegrationInjected: instance.usedShellIntegrationInjection, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts index 50eb06c2ee7..a018f0aeddb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts @@ -76,7 +76,7 @@ export class ToolTerminalCreator { const waitTime = getShellIntegrationTimeout( this._configurationService, siInjectionEnabled, - instance.isRemote, + instance.hasRemoteAuthority, processReadyTimestamp ); diff --git a/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts b/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts index ff07b5c0c5e..caaea9837e5 100644 --- a/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts @@ -67,7 +67,7 @@ export const terminalSendSequenceCommand = async (accessor: ServicesAccessor, ar text = processedText; } - const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file); + const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.hasRemoteAuthority ? Schemas.vscodeRemote : Schemas.file); const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, text); instance.sendText(resolvedText, false); From ac8de0b2dfc0fdb3a18e3672de9642cf07eff3e3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:27:46 +0100 Subject: [PATCH 1697/4355] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20extract=20ge?= =?UTF-8?q?neric=20command=20name=20calculation=20into=20a=20helper=20func?= =?UTF-8?q?tion=20so=20that=20it=20can=20be=20reused=20(#273513)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workbench/contrib/scm/browser/activity.ts | 17 +++-------------- src/vs/workbench/contrib/scm/browser/util.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 3a3b77f7be8..9eb6078a098 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -19,7 +19,7 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { ITitleService } from '../../../services/title/browser/titleService.js'; import { IEditorGroupContextKeyProvider, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { getRepositoryResourceCount, getSCMRepositoryIcon } from './util.js'; +import { getRepositoryResourceCount, getSCMRepositoryIcon, getStatusBarCommandGenericName } from './util.js'; import { autorun, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { Command } from '../../../../editor/common/languages.js'; @@ -147,21 +147,10 @@ export class SCMActiveRepositoryController extends Disposable implements IWorkbe for (let index = 0; index < commands.length; index++) { const command = commands[index]; const tooltip = `${label}${command.tooltip ? ` - ${command.tooltip}` : ''}`; - - // Get a repository agnostic name for the status bar action, derive this from the - // first command argument which is in the form of "./" - let repoAgnosticActionName = ''; - if (typeof command.arguments?.[0] === 'string') { - repoAgnosticActionName = command.arguments[0] - .substring(0, command.arguments[0].lastIndexOf('/')) - .replace(/^(?:git\.|remoteHub\.)/, ''); - if (repoAgnosticActionName.length > 1) { - repoAgnosticActionName = repoAgnosticActionName[0].toLocaleUpperCase() + repoAgnosticActionName.slice(1); - } - } + const genericCommandName = getStatusBarCommandGenericName(command); const statusbarEntry: IStatusbarEntry = { - name: localize('status.scm', "Source Control") + (repoAgnosticActionName ? ` ${repoAgnosticActionName}` : ''), + name: localize('status.scm', "Source Control") + (genericCommandName ? ` ${genericCommandName}` : ''), text: command.title, ariaLabel: tooltip, tooltip, diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index d789d0adcd9..2ebe2601ef2 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -184,3 +184,21 @@ export function getSCMRepositoryIcon( return repository.provider.iconPath; } + +export function getStatusBarCommandGenericName(command: Command): string | undefined { + let genericName: string | undefined = undefined; + + // Get a generic name for the status bar action, derive this from the first + // command argument which is in the form of "./" + if (typeof command.arguments?.[0] === 'string') { + genericName = command.arguments[0] + .substring(0, command.arguments[0].lastIndexOf('/')) + .replace(/^(?:git\.|remoteHub\.)/, '') + .trim(); + if (genericName.length > 1) { + genericName = genericName[0].toLocaleUpperCase() + genericName.slice(1); + } + } + + return genericName; +} From 3e7f0342219ed0fb0b09433de8e5cecc09f0c497 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:29:42 -0700 Subject: [PATCH 1698/4355] Fix tilde spelling --- .../test/browser/terminalCompletionService.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 46676ea96bd..3865948f2ee 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -88,7 +88,7 @@ let homeDir = isWindows ? testEnv['USERPROFILE'] : testEnv['HOME']; if (!homeDir!.endsWith('/')) { homeDir += '/'; } -const standardTidleItem = Object.freeze({ label: '~', detail: homeDir }); +const standardTildeItem = Object.freeze({ label: '~', detail: homeDir }); suite('TerminalCompletionService', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -174,7 +174,7 @@ suite('TerminalCompletionService', () => { { label: '.', detail: '/test/' }, { label: './folder1/', detail: '/test/folder1/' }, { label: '../', detail: '/' }, - standardTidleItem, + standardTildeItem, ], { replacementRange: [1, 1] }); }); @@ -457,7 +457,7 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: './folder2/', detail: '/test/folder2/' }, { label: '../', detail: '/' }, - standardTidleItem, + standardTildeItem, ], { replacementRange: [0, 0] }); }); @@ -480,7 +480,7 @@ suite('TerminalCompletionService', () => { { label: './folder1/', detail: '/test/folder1/' }, { label: './folder2/', detail: '/test/folder2/' }, { label: '../', detail: '/' }, - standardTidleItem, + standardTildeItem, ], { replacementRange: [0, 2] }); }); @@ -779,7 +779,7 @@ suite('TerminalCompletionService', () => { { label: './\!special\$chars\&/', detail: '/test/\!special\$chars\&/' }, { label: './\!special\$chars2\&', detail: '/test/\!special\$chars2\&', kind: TerminalCompletionItemKind.File }, { label: '../', detail: '/' }, - standardTidleItem, + standardTildeItem, ], { replacementRange: [0, 0] }); }); From 4be81fce779619b9645d8e8ae8ce9b589b2fabc6 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:54:25 +0100 Subject: [PATCH 1699/4355] Fix codicon in iconlabel (#273518) --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index d9ea705c8da..562b759ff10 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -191,7 +191,8 @@ export class IconLabel extends Disposable { if (description || this.descriptionNode) { const descriptionNode = this.getOrCreateDescriptionNode(); if (descriptionNode instanceof HighlightedLabel) { - descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, options?.supportIcons); + const supportIcons = options?.supportIcons ?? this.creationOptions?.supportIcons; + descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, supportIcons); this.setupHover(descriptionNode.element, options?.descriptionTitle); } else { descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || ''); From d10a2054f99e083c8b81b3fdbe3b22fc3217794c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 27 Oct 2025 03:12:54 -0700 Subject: [PATCH 1700/4355] Throw error when grammar loading fails Part of #261794 --- .../treeSitter/treeSitterLibraryService.ts | 10 +++ .../standaloneTreeSitterLibraryService.ts | 4 ++ .../services/testTreeSitterLibraryService.ts | 4 ++ .../browser/treeSitterCommandParser.ts | 61 ++++++------------- .../browser/treeSitterLibraryService.ts | 4 ++ 5 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts index 3c907113854..5b4b0c6dd51 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterLibraryService.ts @@ -32,6 +32,16 @@ export interface ITreeSitterLibraryService { */ getLanguage(languageId: string, ignoreSupportsCheck: boolean, reader: IReader | undefined): Language | undefined; + /** + * Gets the language as a promise, as opposed to via observables. This ignores the automatic + * supportsLanguage check. + * + * Warning: This approach is generally not recommended as it's not reactive, but it's the only + * way to catch and handle import errors when the grammar fails to load. + * @param languageId The language identifier to retrieve. + */ + getLanguagePromise(languageId: string): Promise; + /** * Gets the injection queries for a language. A return value of `null` * indicates that there are no highlights queries for this language. diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts index f724e9019ee..90c4bf71814 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterLibraryService.ts @@ -22,6 +22,10 @@ export class StandaloneTreeSitterLibraryService implements ITreeSitterLibrarySer return undefined; } + async getLanguagePromise(languageId: string): Promise { + return undefined; + } + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } diff --git a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts index b724892f883..7c7afa4f660 100644 --- a/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterLibraryService.ts @@ -22,6 +22,10 @@ export class TestTreeSitterLibraryService implements ITreeSitterLibraryService { return undefined; } + async getLanguagePromise(languageId: string): Promise { + return undefined; + } + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { return null; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index eda2b6b43e2..054ef899355 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BugIndicatingError } from '../../../../../base/common/errors.js'; -import { derived, waitForState } from '../../../../../base/common/observable.js'; +import type { Parser, Query, QueryCapture, Tree } from '@vscode/tree-sitter-wasm'; +import { BugIndicatingError, ErrorNoTelemetry } from '../../../../../base/common/errors.js'; import { arch } from '../../../../../base/common/process.js'; import { ITreeSitterLibraryService } from '../../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; -import type { Language, Parser, Query, QueryCapture } from '@vscode/tree-sitter-wasm'; export const enum TreeSitterCommandParserLanguage { Bash = 'bash', @@ -16,51 +15,38 @@ export const enum TreeSitterCommandParserLanguage { export class TreeSitterCommandParser { private readonly _parser: Promise; - private readonly _queries: Map = new Map(); constructor( - @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService + @ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService, ) { this._parser = this._treeSitterLibraryService.getParserClass().then(ParserCtor => new ParserCtor()); } async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { - this._throwIfCanCrash(languageId); - - const parser = await this._parser; - const language = await waitForState(derived(reader => { - return this._treeSitterLibraryService.getLanguage(languageId, true, reader); - })); - parser.setLanguage(language); - - const tree = parser.parse(commandLine); - if (!tree) { - throw new BugIndicatingError('Failed to parse tree'); - } - - const query = await this._getQuery(language); - if (!query) { - throw new BugIndicatingError('Failed to create tree sitter query'); - } - + const { tree, query } = await this._doQuery(languageId, commandLine, '(command) @command'); const captures = query.captures(tree.rootNode); - const subCommands = captures.map(e => e.node.text); - - return subCommands; + return captures.map(e => e.node.text); } async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { + const { tree, query } = await this._doQuery(languageId, commandLine, querySource); + return query.captures(tree.rootNode); + } + + private async _doQuery(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise<{ tree: Tree; query: Query }> { this._throwIfCanCrash(languageId); const parser = await this._parser; - const language = await waitForState(derived(reader => { - return this._treeSitterLibraryService.getLanguage(languageId, true, reader); - })); + const language = await this._treeSitterLibraryService.getLanguagePromise(languageId); + if (!language) { + throw new BugIndicatingError('Failed to fetch language grammar'); + } + parser.setLanguage(language); const tree = parser.parse(commandLine); if (!tree) { - throw new BugIndicatingError('Failed to parse tree'); + throw new ErrorNoTelemetry('Failed to parse tree'); } const query = await this._treeSitterLibraryService.createQuery(language, querySource); @@ -68,18 +54,7 @@ export class TreeSitterCommandParser { throw new BugIndicatingError('Failed to create tree sitter query'); } - const captures = query.captures(tree.rootNode); - - return captures; - } - - private async _getQuery(language: Language): Promise { - let query = this._queries.get(language); - if (!query) { - query = await this._treeSitterLibraryService.createQuery(language, '(command) @command'); - this._queries.set(language, query); - } - return query; + return { tree, query }; } private _throwIfCanCrash(languageId: TreeSitterCommandParserLanguage) { @@ -88,7 +63,7 @@ export class TreeSitterCommandParser { (arch === 'arm' || arch === 'arm64') && languageId === TreeSitterCommandParserLanguage.PowerShell ) { - throw new Error('powershell grammar is not supported on arm or arm64'); + throw new ErrorNoTelemetry('powershell grammar is not supported on arm or arm64'); } } } diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts index b6e82609b2d..a3a921f7b7c 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterLibraryService.ts @@ -129,6 +129,10 @@ export class TreeSitterLibraryService extends Disposable implements ITreeSitterL return lang; } + async getLanguagePromise(languageId: string): Promise { + return this._languagesCache.get(languageId).promise; + } + getInjectionQueries(languageId: string, reader: IReader | undefined): Query | null | undefined { if (!this.supportsLanguage(languageId, reader)) { return undefined; From ff811b0104778c263ca605497cde633c98dbdc86 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:43:56 +0100 Subject: [PATCH 1701/4355] SCM - fix edge cases with the helper function (#273522) --- src/vs/workbench/contrib/scm/browser/util.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 2ebe2601ef2..c099ef39a7b 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -191,13 +191,22 @@ export function getStatusBarCommandGenericName(command: Command): string | undef // Get a generic name for the status bar action, derive this from the first // command argument which is in the form of "./" if (typeof command.arguments?.[0] === 'string') { - genericName = command.arguments[0] - .substring(0, command.arguments[0].lastIndexOf('/')) + const lastIndex = command.arguments[0].lastIndexOf('/'); + + genericName = lastIndex !== -1 + ? command.arguments[0].substring(0, lastIndex) + : command.arguments[0]; + + genericName = genericName .replace(/^(?:git\.|remoteHub\.)/, '') .trim(); - if (genericName.length > 1) { - genericName = genericName[0].toLocaleUpperCase() + genericName.slice(1); + + if (genericName.length === 0) { + return undefined; } + + // Capitalize first letter + genericName = genericName[0].toLocaleUpperCase() + genericName.slice(1); } return genericName; From 1fd433a435837749b804fa61b3dc6f31fe4717b4 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 27 Oct 2025 11:03:21 +0000 Subject: [PATCH 1702/4355] scm(history): use panel background for hover label foreground and export token Change historyItemHoverLabelForeground default to the panel background token and export the identifier for use in the view pane. Use the exported token in scmHistoryViewPane and remove the now-unused buttonForeground import. --- src/vs/workbench/contrib/scm/browser/scmHistory.ts | 5 +++-- src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index 71faef70db0..3d18b30716a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -5,11 +5,12 @@ import { localize } from '../../../../nls.js'; import { deepClone } from '../../../../base/common/objects.js'; -import { badgeBackground, buttonForeground, chartsBlue, chartsPurple, foreground } from '../../../../platform/theme/common/colorRegistry.js'; +import { badgeBackground, chartsBlue, chartsPurple, foreground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable, ColorIdentifier, registerColor } from '../../../../platform/theme/common/colorUtils.js'; import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel } from '../common/history.js'; import { rot } from '../../../../base/common/numbers.js'; import { svgElem } from '../../../../base/browser/dom.js'; +import { PANEL_BACKGROUND } from '../../../common/theme.js'; export const SWIMLANE_HEIGHT = 22; export const SWIMLANE_WIDTH = 11; @@ -29,7 +30,7 @@ export const historyItemBaseRefColor = registerColor('scmGraph.historyItemBaseRe */ export const historyItemHoverDefaultLabelForeground = registerColor('scmGraph.historyItemHoverDefaultLabelForeground', foreground, localize('scmGraphHistoryItemHoverDefaultLabelForeground', "History item hover default label foreground color.")); export const historyItemHoverDefaultLabelBackground = registerColor('scmGraph.historyItemHoverDefaultLabelBackground', badgeBackground, localize('scmGraphHistoryItemHoverDefaultLabelBackground', "History item hover default label background color.")); -export const historyItemHoverLabelForeground = registerColor('scmGraph.historyItemHoverLabelForeground', buttonForeground, localize('scmGraphHistoryItemHoverLabelForeground', "History item hover label foreground color.")); +export const historyItemHoverLabelForeground = registerColor('scmGraph.historyItemHoverLabelForeground', PANEL_BACKGROUND, localize('scmGraphHistoryItemHoverLabelForeground', "History item hover label foreground color.")); export const historyItemHoverAdditionsForeground = registerColor('scmGraph.historyItemHoverAdditionsForeground', { light: '#587C0C', dark: '#81B88B', hcDark: '#A1E3AD', hcLight: '#374E06' }, localize('scmGraph.HistoryItemHoverAdditionsForeground', "History item hover additions foreground color.")); export const historyItemHoverDeletionsForeground = registerColor('scmGraph.historyItemHoverDeletionsForeground', { light: '#AD0707', dark: '#C74E39', hcDark: '#C74E39', hcLight: '#AD0707' }, localize('scmGraph.HistoryItemHoverDeletionsForeground', "History item hover deletions foreground color.")); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 201ed8b57fa..21a3f1e9ae0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -28,8 +28,7 @@ import { asCssVariable, ColorIdentifier, foreground } from '../../../../platform import { IFileIconTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IViewPaneOptions, ViewAction, ViewPane, ViewPaneShowActions } from '../../../browser/parts/views/viewPane.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; -import { PANEL_BACKGROUND } from '../../../common/theme.js'; -import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; +import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverLabelForeground, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; import { getHistoryItemEditorTitle, getProviderKey, isSCMHistoryItemChangeNode, isSCMHistoryItemChangeViewModelTreeElement, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService, ViewMode } from '../common/scm.js'; @@ -530,7 +529,7 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer Date: Mon, 27 Oct 2025 12:09:45 +0100 Subject: [PATCH 1703/4355] SCM - only show Single/Multiple selection actions when there is more than one repository in the workspace (#273526) --- .../workbench/contrib/scm/browser/scmViewPane.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 0935ba25e4b..7d2cac7cb02 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1213,8 +1213,18 @@ abstract class RepositorySelectionModeAction extends ViewAction { f1: false, toggled: RepositoryContextKeys.RepositorySelectionMode.isEqualTo(selectionMode), menu: [ - { id: Menus.Repositories, order, group: '2_selectionMode' }, - { id: MenuId.SCMSourceControlTitle, order, group: '2_selectionMode' }, + { + id: Menus.Repositories, + when: ContextKeyExpr.greater(ContextKeys.RepositoryCount.key, 1), + group: '2_selectionMode', + order + }, + { + id: MenuId.SCMSourceControlTitle, + when: ContextKeyExpr.greater(ContextKeys.RepositoryCount.key, 1), + group: '2_selectionMode', + order + }, ] }); } From 55feeb18e6d33f52f678bc4a8287a292133d4e68 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 27 Oct 2025 11:16:25 +0000 Subject: [PATCH 1704/4355] scm: remove explicit codicon font-size overrides to rely on inherited sizing for history labels, hover status bar, and graph pickers --- src/vs/workbench/contrib/scm/browser/media/scm.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 9d62bd5dd4d..a5fa77a16b9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -205,7 +205,6 @@ } .scm-view .monaco-list-row .history-item > .label-container > .label > .codicon { - font-size: 16px; color: inherit !important; padding: 1px; } @@ -572,7 +571,6 @@ .monaco-hover.history-item-hover .hover-row.status-bar .action .codicon { color: inherit; - font-size: 16px; } .monaco-hover.history-item-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span.codicon { @@ -619,7 +617,6 @@ .monaco-toolbar .action-label.scm-graph-repository-picker .codicon, .monaco-toolbar .action-label.scm-graph-history-item-picker .codicon { - font-size: 16px; color: inherit; } From 6916fd13563fd5d3e285cd74e0fcd6f7aca22d0d Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 27 Oct 2025 13:03:39 +0100 Subject: [PATCH 1705/4355] Reschedule fetching viewport tokens when the document language id changes (#247053) (#273536) --- .../semanticTokens/browser/viewportSemanticTokens.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts index a9f89d1c41c..a7f6b745b39 100644 --- a/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts @@ -81,6 +81,12 @@ export class ViewportSemanticTokensContribution extends Disposable implements IE this._cancelAll(); scheduleTokenizeViewport(); })); + this._register(this._editor.onDidChangeModelLanguage(() => { + // The cleanup of the model's semantic tokens happens in the DocumentSemanticTokensFeature + bindRangeProvidersChangeListeners(); + this._cancelAll(); + scheduleTokenizeViewport(); + })); this._register(this._editor.onDidChangeModelContent((e) => { this._cancelAll(); scheduleTokenizeViewport(); From c51ec27b9b06377ccd9916ea692d0e175857ae8d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 27 Oct 2025 13:12:44 +0100 Subject: [PATCH 1706/4355] Fixes https://github.com/microsoft/vscode/issues/272709 (#273538) --- .../inlineCompletions/browser/model/inlineCompletionsSource.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 219439dc76c..0e5b5cba9de 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -109,6 +109,8 @@ export class InlineCompletionsSource extends Disposable { } })); } + + this._state.recomputeInitiallyAndOnChange(this._store); } private _updateCompletionsEnablement(enalementSetting: string) { From d9bb7e23c43db84bc7a0a49589f135e6e2600a76 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Mon, 27 Oct 2025 12:28:29 +0000 Subject: [PATCH 1707/4355] scm: remove explicit codicon font-size for history-item hover to rely on inherited sizing --- src/vs/workbench/contrib/scm/browser/media/scm.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index a5fa77a16b9..2423a707dbc 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -573,10 +573,6 @@ color: inherit; } -.monaco-hover.history-item-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span.codicon { - font-size: 16px; -} - /* Graph */ .pane-header .scm-graph-view-badge-container { From ab2cef5ab10644d89a4bb74f6cdd45c8d6a61aba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 07:11:05 -0700 Subject: [PATCH 1708/4355] Bump actions/upload-artifact from 4 to 5 (#273539) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-darwin-test.yml | 6 +++--- .github/workflows/pr-linux-test.yml | 6 +++--- .github/workflows/pr-win32-test.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index ff671ee251c..e48140b9569 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -212,7 +212,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -223,7 +223,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -232,7 +232,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: always() continue-on-error: true with: diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index 7f892be30f2..992be267cf9 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -258,7 +258,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -269,7 +269,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -278,7 +278,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: always() continue-on-error: true with: diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index b2427d0fad3..ec2baa2f5b9 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -249,7 +249,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -260,7 +260,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() continue-on-error: true with: @@ -269,7 +269,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: always() continue-on-error: true with: From 24dc95ef6477947c2aab441c6936b5af920bda66 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:42:35 +0100 Subject: [PATCH 1709/4355] Git - add telemetry to track down stale diagnostics (#273565) --- extensions/git/src/commands.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index cc0ad43098c..c57ae4606c0 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -682,10 +682,11 @@ class CommandErrorOutputTextDocumentContentProvider implements TextDocumentConte } } -async function evaluateDiagnosticsCommitHook(repository: Repository, options: CommitOptions): Promise { +async function evaluateDiagnosticsCommitHook(repository: Repository, options: CommitOptions, logger: LogOutputChannel): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); const enabled = config.get('diagnosticsCommitHook.enabled', false) === true; const sourceSeverity = config.get>('diagnosticsCommitHook.sources', { '*': 'error' }); + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Diagnostics Commit Hook: enabled=${enabled}, sources=${JSON.stringify(sourceSeverity)}`); if (!enabled) { return true; @@ -711,23 +712,27 @@ async function evaluateDiagnosticsCommitHook(repository: Repository, options: Co for (const resource of resources) { const unresolvedDiagnostics = languages.getDiagnostics(resource) .filter(d => { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Evaluating diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); + // No source or ignored source if (!d.source || (Object.keys(sourceSeverity).includes(d.source) && sourceSeverity[d.source] === 'none')) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Ignoring diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return false; } // Source severity - if (Object.keys(sourceSeverity).includes(d.source) && - d.severity <= toDiagnosticSeverity(sourceSeverity[d.source])) { + if (Object.keys(sourceSeverity).includes(d.source) && d.severity <= toDiagnosticSeverity(sourceSeverity[d.source])) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Found unresolved diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return true; } // Wildcard severity - if (Object.keys(sourceSeverity).includes('*') && - d.severity <= toDiagnosticSeverity(sourceSeverity['*'])) { + if (Object.keys(sourceSeverity).includes('*') && d.severity <= toDiagnosticSeverity(sourceSeverity['*'])) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Found unresolved diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return true; } + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Ignoring diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return false; }); @@ -2412,7 +2417,7 @@ export class CommandCenter { } // Diagnostics commit hook - const diagnosticsResult = await evaluateDiagnosticsCommitHook(repository, opts); + const diagnosticsResult = await evaluateDiagnosticsCommitHook(repository, opts, this.logger); if (!diagnosticsResult) { return; } From c165ba3b668b49bff2db53740cd21fb1b92c97e6 Mon Sep 17 00:00:00 2001 From: Jerome Lelong Date: Mon, 27 Oct 2025 15:43:34 +0100 Subject: [PATCH 1710/4355] Exclude trailing underscore from bracket pairs in LaTeX (#272758) * Remove trailing underscore from bracket scopes * Add unbalancedBracketScopes to TeX also --- extensions/latex/package.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/extensions/latex/package.json b/extensions/latex/package.json index 18bbd6cec32..197343cd6a1 100644 --- a/extensions/latex/package.json +++ b/extensions/latex/package.json @@ -66,14 +66,19 @@ { "language": "tex", "scopeName": "text.tex", - "path": "./syntaxes/TeX.tmLanguage.json" + "path": "./syntaxes/TeX.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.control.ifnextchar.tex", + "punctuation.math.operator.tex" + ] }, { "language": "latex", "scopeName": "text.tex.latex", "path": "./syntaxes/LaTeX.tmLanguage.json", "unbalancedBracketScopes": [ - "keyword.control.ifnextchar.tex" + "keyword.control.ifnextchar.tex", + "punctuation.math.operator.tex" ], "embeddedLanguages": { "source.cpp": "cpp_embedded_latex", From 062d4a5c2b6e1d6b1c02f6109d1d5816c9357068 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:45:06 +0100 Subject: [PATCH 1711/4355] SCM - do not use bold repository name to indicate selection (#273567) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 4 ---- src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 36805b13a4b..99f13c95523 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -54,10 +54,6 @@ flex: 0 1 auto; } -.scm-view .scm-provider > .monaco-icon-label.visible { - font-weight: bold; -} - .scm-view .scm-provider > .monaco-icon-label .monaco-icon-name-container { overflow: hidden; text-overflow: ellipsis; diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 838abb0d34a..bda5acc7b0c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -116,8 +116,6 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer 1); - const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) ? repository.provider.iconPath : Codicon.repo; From 0c5276f0a6ccbc12c1818a7d2db89017e15f62b2 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 27 Oct 2025 10:50:51 -0400 Subject: [PATCH 1712/4355] fix kb conflict (#273568) fix #273167 --- .../browser/parts/notifications/notificationsCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index d3fff55ef7c..280bbbe6394 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -154,7 +154,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl // Accept Primary Action KeybindingsRegistry.registerCommandAndKeybindingRule({ id: ACCEPT_PRIMARY_ACTION_NOTIFICATION, - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.WorkbenchContrib + 1, when: ContextKeyExpr.or(NotificationFocusedContext, NotificationsToastsVisibleContext), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA, handler: (accessor) => { From 6cf95b098dddb2b3359d4bce444d834a7a740911 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:53:44 +0000 Subject: [PATCH 1713/4355] Show latest commit message for branch in terminal suggest widget details view (#272296) --- .../terminal-suggest/src/completions/git.ts | 142 +++++++++--------- .../src/test/completions/git-branch.test.ts | 89 +++++++++++ 2 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/completions/git-branch.test.ts diff --git a/extensions/terminal-suggest/src/completions/git.ts b/extensions/terminal-suggest/src/completions/git.ts index 7c0f74fb843..8b45edf5a7e 100644 --- a/extensions/terminal-suggest/src/completions/git.ts +++ b/extensions/terminal-suggest/src/completions/git.ts @@ -74,46 +74,67 @@ const postProcessBranches = const seen = new Set(); return output .split("\n") - .filter((line) => !line.trim().startsWith("HEAD")) + .filter((line) => line.trim() && !line.trim().startsWith("HEAD")) .map((branch) => { - let name = branch.trim(); - const parts = branch.match(/\S+/g); - if (parts && parts.length > 1) { - if (parts[0] === "*") { - // We are in a detached HEAD state - if (branch.includes("HEAD detached")) { - return null; + // Parse the format: branchName|author|hash|subject|timeAgo + const parts = branch.split("|"); + if (parts.length < 5) { + // Fallback to old parsing if format doesn't match + let name = branch.trim(); + const oldParts = branch.match(/\S+/g); + if (oldParts && oldParts.length > 1) { + if (oldParts[0] === "*") { + if (branch.includes("HEAD detached")) { + return null; + } + return { + name: branch.replaceAll("*", "").trim(), + description: "Current branch", + priority: 100, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` + }; + } else if (oldParts[0] === "+") { + name = branch.replaceAll("+", "").trim(); } - // Current branch - return { - name: branch.replace("*", "").trim(), - description: "Current branch", - priority: 100, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` - }; - } else if (parts[0] === "+") { - // Branch checked out in another worktree. - name = branch.replace("+", "").trim(); } + + let description = "Branch"; + if (insertWithoutRemotes && name.startsWith("remotes/")) { + name = name.slice(name.indexOf("/", 8) + 1); + description = "Remote branch"; + } + + const space = name.indexOf(" "); + if (space !== -1) { + name = name.slice(0, space); + } + + return { + name, + description, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority: 75, + }; } - let description = "Branch"; + let name = parts[0].trim(); + const author = parts[1].trim(); + const hash = parts[2].trim(); + const subject = parts[3].trim(); + const timeAgo = parts[4].trim(); + + const description = `${timeAgo} • ${author} • ${hash} • ${subject}`; + const priority = 75; if (insertWithoutRemotes && name.startsWith("remotes/")) { name = name.slice(name.indexOf("/", 8) + 1); - description = "Remote branch"; - } - - const space = name.indexOf(" "); - if (space !== -1) { - name = name.slice(0, space); } return { name, description, icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, - priority: 75, + priority, }; }) .filter((suggestion) => { @@ -128,6 +149,15 @@ const postProcessBranches = }); }; +// Common git for-each-ref arguments for branch queries with commit details +const gitBranchForEachRefArgs = [ + "git", + "--no-optional-locks", + "for-each-ref", + "--sort=-committerdate", + "--format=%(refname:short)|%(authorname)|%(objectname:short)|%(subject)|%(committerdate:relative)", +] as const; + export const gitGenerators = { // Commit history commits: { @@ -252,23 +282,17 @@ export const gitGenerators = { // All branches remoteLocalBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "-a", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", + "refs/remotes/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), } satisfies Fig.Generator, localBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), } satisfies Fig.Generator, @@ -278,37 +302,19 @@ export const gitGenerators = { localOrRemoteBranches: { custom: async (tokens, executeShellCommand) => { const pp = postProcessBranches({ insertWithoutRemotes: true }); - if (tokens.includes("-r")) { - return pp?.( - ( - await executeShellCommand({ - command: "git", - args: [ - "--no-optional-locks", - "-r", - "--no-color", - "--sort=-committerdate", - ], - }) - ).stdout, - tokens - ); - } else { - return pp?.( - ( - await executeShellCommand({ - command: "git", - args: [ - "--no-optional-locks", - "branch", - "--no-color", - "--sort=-committerdate", - ], - }) - ).stdout, - tokens - ); - } + const refs = tokens.includes("-r") ? "refs/remotes/" : "refs/heads/"; + return pp?.( + ( + await executeShellCommand({ + command: gitBranchForEachRefArgs[0], + args: [ + ...gitBranchForEachRefArgs.slice(1), + refs, + ], + }) + ).stdout, + tokens + ); }, } satisfies Fig.Generator, diff --git a/extensions/terminal-suggest/src/test/completions/git-branch.test.ts b/extensions/terminal-suggest/src/test/completions/git-branch.test.ts new file mode 100644 index 00000000000..20d140c3e7c --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/git-branch.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import 'mocha'; +import * as vscode from 'vscode'; +import { gitGenerators } from '../../completions/git'; + +suite('Git Branch Completions', () => { + test('postProcessBranches should parse git for-each-ref output with commit details', () => { + const input = `main|John Doe|abc1234|Fix response codeblock in debug view|2 days ago +feature/test|Jane Smith|def5678|Add new feature|1 week ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 2); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix response codeblock in debug view'); + assert.strictEqual(result[0].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`); + + assert.ok(result[1]); + assert.strictEqual(result[1].name, 'feature/test'); + assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add new feature'); + assert.strictEqual(result[1].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`); + }); + + test('postProcessBranches should handle remote branches', () => { + const input = `remotes/origin/main|John Doe|abc1234|Fix bug|2 days ago +remotes/origin/feature|Jane Smith|def5678|Add feature|1 week ago`; + + const result = gitGenerators.remoteLocalBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 2); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix bug'); + + assert.ok(result[1]); + assert.strictEqual(result[1].name, 'feature'); + assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add feature'); + }); + + test('postProcessBranches should filter out HEAD branches', () => { + const input = `main|John Doe|abc1234|Fix bug|2 days ago +HEAD -> main|John Doe|abc1234|Fix bug|2 days ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + }); + + test('postProcessBranches should handle empty input', () => { + const input = ''; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 0); + }); + + test('postProcessBranches should handle git error output', () => { + const input = 'fatal: not a git repository'; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 0); + }); + + test('postProcessBranches should deduplicate branches', () => { + const input = `main|John Doe|abc1234|Fix bug|2 days ago +main|John Doe|abc1234|Fix bug|2 days ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + }); +}); From 6470f9b8835aca91f36118bedc87519ba8ff1243 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:10:19 +0100 Subject: [PATCH 1714/4355] Toolbar - add responsive option (#273501) * Toolbar - saving my work * Toolbar - use minWidth for width computation * Toolbar - more polish * Add responsive option * More polish on the rendering * More polish * Remove code from a bad merge * Extract action min width --- src/vs/base/browser/ui/toolbar/toolbar.css | 11 ++ src/vs/base/browser/ui/toolbar/toolbar.ts | 140 +++++++++++++++++- .../contrib/scm/browser/media/scm.css | 6 - .../scm/browser/scmRepositoryRenderer.ts | 2 +- 4 files changed, 150 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/toolbar/toolbar.css b/src/vs/base/browser/ui/toolbar/toolbar.css index 8182fbd9c29..8319f4d06f3 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/src/vs/base/browser/ui/toolbar/toolbar.css @@ -11,3 +11,14 @@ display: inline-block; padding: 0; } + +.monaco-toolbar.responsive { + .monaco-action-bar > .actions-container > .action-item { + flex-shrink: 0; + min-width: 20px; + } + + .monaco-action-bar > .actions-container > .action-item.responsive { + flex-shrink: 10000 !important; + } +} diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index b493b10af6c..3559e37f2c4 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -12,11 +12,14 @@ import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.js'; import { EventMultiplexer } from '../../../common/event.js'; import { ResolvedKeybinding } from '../../../common/keybindings.js'; -import { Disposable, DisposableStore } from '../../../common/lifecycle.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js'; import './toolbar.css'; import * as nls from '../../../../nls.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; +import { BaseActionViewItem } from '../actionbar/actionViewItems.js'; + +const ACTION_MIN_WIDTH = 24; /* 20px codicon + 4px left padding*/ export interface IToolBarOptions { orientation?: ActionsOrientation; @@ -47,6 +50,11 @@ export interface IToolBarOptions { * Render action with label (default: `false`) */ label?: boolean; + + /** + * Hiding actions that are not visible + */ + responsive?: boolean; } /** @@ -63,6 +71,9 @@ export class ToolBar extends Disposable { private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); get onDidChangeDropdownVisibility() { return this._onDidChangeDropdownVisibility.event; } + private originalPrimaryActions: ReadonlyArray = []; + private originalSecondaryActions: ReadonlyArray = []; + private hiddenActions: { action: IAction; size: number }[] = []; private readonly disposables = this._register(new DisposableStore()); constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { @@ -88,7 +99,7 @@ export class ToolBar extends Disposable { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, - (action).menuActions, + { getActions: () => this.toggleMenuAction.menuActions }, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, @@ -142,6 +153,17 @@ export class ToolBar extends Disposable { return undefined; } })); + + // Responsive support + if (this.options.responsive) { + this.element.classList.add('responsive'); + + const observer = new ResizeObserver(() => { + this.setToolbarMaxWidth(this.element.getBoundingClientRect().width); + }); + observer.observe(this.element); + this._store.add(toDisposable(() => observer.disconnect())); + } } set actionRunner(actionRunner: IActionRunner) { @@ -195,6 +217,10 @@ export class ToolBar extends Disposable { setActions(primaryActions: ReadonlyArray, secondaryActions?: ReadonlyArray): void { this.clear(); + // Store primary and secondary actions as rendered initially + this.originalPrimaryActions = primaryActions ? primaryActions.slice(0) : []; + this.originalSecondaryActions = secondaryActions ? secondaryActions.slice(0) : []; + const primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present @@ -211,6 +237,17 @@ export class ToolBar extends Disposable { primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) }); }); + + if (this.options.responsive) { + // Reset hidden actions + this.hiddenActions.length = 0; + + // Set `responsive` class + this.setToolbarResponsiveAction(); + + // Update toolbar to fit with container width + this.setToolbarMaxWidth(this.element.getBoundingClientRect().width); + } } isEmpty(): boolean { @@ -223,6 +260,105 @@ export class ToolBar extends Disposable { return key?.getLabel() ?? undefined; } + private getItemsWidthResponsive(): number { + let itemsWidth = 0; + for (let index = 0; index < this.actionBar.length(); index++) { + // If the last visible primary action is wider than 24px, it means that it has a label. We + // need to return the minimum width (24px) for this action so that we allow it to shrink to + // the minimum width. + const width = index === this.originalPrimaryActions.length - this.hiddenActions.length - 1 + ? Math.min(ACTION_MIN_WIDTH, this.actionBar.getWidth(index)) + : this.actionBar.getWidth(index); + + itemsWidth += width; + } + + return itemsWidth; + } + + private setToolbarMaxWidth(maxWidth: number) { + if ( + this.actionBar.isEmpty() || + (this.getItemsWidthResponsive() <= maxWidth && this.hiddenActions.length === 0) + ) { + return; + } + + if (this.getItemsWidthResponsive() > maxWidth) { + // Hide actions from the right + while (this.getItemsWidthResponsive() > maxWidth && this.actionBar.length() > 0) { + const index = this.originalPrimaryActions.length - this.hiddenActions.length - 1; + if (index < 0) { + break; + } + + // Store the action and its size + const size = Math.min(ACTION_MIN_WIDTH, this.getItemWidth(index)); + const action = this.originalPrimaryActions[index]; + this.hiddenActions.unshift({ action, size }); + + // Remove the action + this.actionBar.pull(index); + + // There are no secondary actions, but we have actions that we need to hide so we + // create the overflow menu. This will ensure that another primary action will be + // removed making space for the overflow menu. + if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { + this.actionBar.push(this.toggleMenuAction, { + icon: this.options.icon ?? true, + label: this.options.label ?? false, + keybinding: this.getKeybindingLabel(this.toggleMenuAction), + }); + } + } + } else { + // Show actions from the top of the toggle menu + while (this.hiddenActions.length > 0) { + const entry = this.hiddenActions.shift()!; + if (this.getItemsWidthResponsive() + entry.size > maxWidth) { + // Not enough space to show the action + this.hiddenActions.unshift(entry); + break; + } + + // Add the action + this.actionBar.push(entry.action, { + icon: this.options.icon ?? true, + label: this.options.label ?? false, + keybinding: this.getKeybindingLabel(entry.action), + index: this.originalPrimaryActions.length - this.hiddenActions.length - 1 + }); + + // There are no secondary actions, and there is only one hidden item left so we + // remove the overflow menu making space for the last hidden action to be shown. + if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { + this.toggleMenuAction.menuActions = []; + this.actionBar.pull(this.actionBar.length() - 1); + } + } + } + + // Update `responsive` class + this.setToolbarResponsiveAction(); + + // Update overflow menu + const hiddenActions = this.hiddenActions.map(entry => entry.action); + if (this.originalSecondaryActions.length > 0 || hiddenActions.length > 0) { + const secondaryActions = this.originalSecondaryActions.slice(0); + this.toggleMenuAction.menuActions = Separator.join(hiddenActions, secondaryActions); + } + } + + private setToolbarResponsiveAction(): void { + // Set the `responsive` class on the last visible primary action + for (let index = 0; index < this.actionBar.viewItems.length; index++) { + if (this.actionBar.viewItems[index] instanceof BaseActionViewItem) { + const isLastVisiblePrimaryAction = index === this.originalPrimaryActions.length - this.hiddenActions.length - 1; + (this.actionBar.viewItems[index] as BaseActionViewItem).element?.classList.toggle('responsive', isLastVisiblePrimaryAction); + } + } + } + private clear(): void { this.submenuActionViewItems = []; this.disposables.clear(); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 99f13c95523..6799312c3e9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -86,13 +86,7 @@ align-items: center; } -.scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item:nth-child(1) { - flex-shrink: 10000; -} - .scm-view .scm-provider > .actions > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item.scm-status-bar-action { - min-width: 20px; - .action-label > span:not(.codicon) { margin-left: 2px; } diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index bda5acc7b0c..e1737f32e06 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -99,7 +99,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer provider.classList.toggle('active', e)); From bb7ccba08949c05f147faed8f0e63cc3a9e33571 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:17:16 -0700 Subject: [PATCH 1715/4355] Don't use split button if there's only one action --- .../workbench/contrib/chat/browser/chatSessions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 9fbe7e2dcca..5ca12de371e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -490,7 +490,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ ContextKeyExpr.equals('view', `${AGENT_SESSIONS_VIEWLET_ID}.${contribution.type}`) ), submenu: MenuId.ChatSessionsCreateSubMenu, - isSplitButton: true + isSplitButton: menuActions.length > 1 }); } else { // We control creation instead From 4c6d0bac16d32c1f2f5ad25de0e3c8c81e0a0142 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 16:31:06 +0100 Subject: [PATCH 1716/4355] Color issue with Default Light+ theme in chat todos (fix #273139) (#273364) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index ddcf2139c5b..32f0e446d39 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1124,7 +1124,6 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; gap: 4px; flex: 1; - color: var(--vscode-foreground); font-size: 11px; white-space: nowrap; overflow: hidden; @@ -1183,7 +1182,6 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title { font-weight: normal; font-size: 11px; - color: var(--vscode-foreground); display: flex; align-items: center; overflow: hidden; @@ -1239,7 +1237,6 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-content { - color: var(--vscode-foreground); flex-grow: 1; overflow: hidden; white-space: nowrap; From b7d8cdd64d08f90b3828c357f144a7c6e7c0ad23 Mon Sep 17 00:00:00 2001 From: Kyle Cutler <67761731+kycutler@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:33:21 +0100 Subject: [PATCH 1717/4355] Remove `/** */` auto-closing from gitignore syntax (#273553) Fixes #273372 --- .../git-base/languages/ignore.language-configuration.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/git-base/languages/ignore.language-configuration.json b/extensions/git-base/languages/ignore.language-configuration.json index ad8b8ee5cd8..03e8ed3611c 100644 --- a/extensions/git-base/languages/ignore.language-configuration.json +++ b/extensions/git-base/languages/ignore.language-configuration.json @@ -8,7 +8,6 @@ { "open": "(", "close": ")" }, { "open": "'", "close": "'", "notIn": ["string", "comment"] }, { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "`", "close": "`", "notIn": ["string", "comment"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } + { "open": "`", "close": "`", "notIn": ["string", "comment"] } ] } From 04c61273bd35fe7e36bb02f1b65fd534a3699edf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:34:26 -0400 Subject: [PATCH 1718/4355] Include model info in chat input aria label (#273160) --- .../workbench/contrib/chat/browser/chatInputPart.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 150d24d4d8a..5d72b222d23 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -520,6 +520,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (this._currentLanguageModel?.metadata.name) { this.accessibilityService.alert(this._currentLanguageModel.metadata.name); } + this._inputEditor?.updateOptions({ ariaLabel: this._getAriaLabel() }); })); this._register(this.chatModeService.onDidChangeChatModes(() => this.validateCurrentChatMode())); this._register(autorun(r => { @@ -797,6 +798,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (verbose) { kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); } + + // Include model information if available + const modelName = this._currentLanguageModel?.metadata.name; + const modelInfo = modelName ? localize('chatInput.model', ", {0}. ", modelName) : ''; + let modeLabel = ''; switch (this.currentModeKind) { case ChatModeKind.Agent: @@ -812,10 +818,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } if (verbose) { return kbLabel - ? localize('actions.chat.accessibiltyHelp', "Chat Input {0} Press Enter to send out the request. Use {1} for Chat Accessibility Help.", modeLabel, kbLabel) - : localize('chatInput.accessibilityHelpNoKb', "Chat Input {0} Press Enter to send out the request. Use the Chat Accessibility Help command for more information.", modeLabel); + ? localize('actions.chat.accessibiltyHelp', "Chat Input {0}{1} Press Enter to send out the request. Use {2} for Chat Accessibility Help.", modelInfo, modeLabel, kbLabel) + : localize('chatInput.accessibilityHelpNoKb', "Chat Input {0}{1} Press Enter to send out the request. Use the Chat Accessibility Help command for more information.", modelInfo, modeLabel); } else { - return localize('chatInput.accessibilityHelp', "Chat Input {0}.", modeLabel); + return localize('chatInput.accessibilityHelp', "Chat Input {0}{1}.", modelInfo, modeLabel); } } From d8864b2b9a463dc3e88173a95a9f0739b5407f87 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 27 Oct 2025 11:42:44 -0400 Subject: [PATCH 1719/4355] use params for query (#273563) --- .../workbench/contrib/chat/browser/actions/chatContext.ts | 4 ++-- .../workbench/contrib/terminal/browser/terminalService.ts | 3 ++- src/vs/workbench/contrib/terminal/browser/terminalUri.ts | 7 +++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts index 41dad4bc2e0..7d8a91898f6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContext.ts @@ -278,8 +278,8 @@ export class TerminalContext implements IChatContextValueItem { if (!terminal) { return; } - - const command = terminal.capabilities.get(TerminalCapability.CommandDetection)?.commands.find(cmd => cmd.id === this._resource.query.replace('command=', '')); + const params = new URLSearchParams(this._resource.query); + const command = terminal.capabilities.get(TerminalCapability.CommandDetection)?.commands.find(cmd => cmd.id === params.get('command')); if (!command) { return; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c408a6f182e..0675715a279 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -775,7 +775,8 @@ export class TerminalService extends Disposable implements ITerminalService { if (instance) { this.revealTerminal(instance); const commands = instance.capabilities.get(TerminalCapability.CommandDetection)?.commands; - const relevantCommand = commands?.find(c => c.id === resource.query.replace('command=', '')); + const params = new URLSearchParams(resource.query); + const relevantCommand = commands?.find(c => c.id === params.get('command')); if (relevantCommand) { instance.xterm?.markTracker.revealCommand(relevantCommand); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts index d8662856b8d..421842caa24 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalUri.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalUri.ts @@ -22,12 +22,15 @@ export function parseTerminalUri(resource: URI): ITerminalIdentifier { } export function getTerminalUri(workspaceId: string, instanceId: number, title?: string, commandId?: string): URI { - + const params = new URLSearchParams(); + if (commandId) { + params.set('command', commandId); + } return URI.from({ scheme: Schemas.vscodeTerminal, path: `/${workspaceId}/${instanceId}`, fragment: title || undefined, - query: `command=${commandId}` + query: commandId ? params.toString() : undefined }); } From bb0aeca5deb4e27286bec38088ca1736ad045e23 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 27 Oct 2025 12:01:57 -0400 Subject: [PATCH 1720/4355] Indicate custom modes to screen reader users in chat input (#273579) fixes #273161 --- .../contrib/chat/browser/chatInputPart.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 5d72b222d23..8dde3112bdd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -137,6 +137,11 @@ export interface IWorkingSetEntry { uri: URI; } +interface ICustomMode { + mode: string; + argumentHint?: string; +} + const GlobalLastChatModeKey = 'chat.lastChatMode'; export class ChatInputPart extends Disposable implements IHistoryNavigationWidget { @@ -319,6 +324,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge readonly onDidChangeCurrentChatMode: Event = this._onDidChangeCurrentChatMode.event; private readonly _currentModeObservable: ISettableObservable; + private readonly _currentModeLabelObservable: ISettableObservable; public get currentModeKind(): ChatModeKind { const mode = this._currentModeObservable.get(); return mode.kind === ChatModeKind.Agent && !this.agentService.hasToolsAgent ? @@ -326,6 +332,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge mode.kind; } + public get currentModeLabel(): IObservable { + return this._currentModeLabelObservable; + } + public get currentModeObs(): IObservable { return this._currentModeObservable; } @@ -425,6 +435,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge super(); this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event })); this._currentModeObservable = observableValue('currentMode', this.options.defaultMode ?? ChatMode.Agent); + this._currentModeLabelObservable = observableValue('currentModeLabel', { mode: (this.options.defaultMode ?? ChatMode.Agent).label, argumentHint: (this.options.defaultMode ?? ChatMode.Agent).description.get() }); this._register(this.editorService.onDidActiveEditorChange(() => { this._indexOfLastOpenedContext = -1; @@ -746,6 +757,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this._currentModeObservable.set(mode, undefined); + this._currentModeLabelObservable.set({ mode: mode.label, argumentHint: mode.argumentHint?.get() }, undefined); this.chatModeKindKey.set(mode.kind); this._onDidChangeCurrentChatMode.fire(); @@ -798,23 +810,29 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (verbose) { kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel(); } + const mode = this._currentModeObservable.get(); // Include model information if available const modelName = this._currentLanguageModel?.metadata.name; const modelInfo = modelName ? localize('chatInput.model', ", {0}. ", modelName) : ''; let modeLabel = ''; - switch (this.currentModeKind) { - case ChatModeKind.Agent: - modeLabel = localize('chatInput.mode.agent', "(Agent), edit files in your workspace."); - break; - case ChatModeKind.Edit: - modeLabel = localize('chatInput.mode.edit', "(Edit), edit files in your workspace."); - break; - case ChatModeKind.Ask: - default: - modeLabel = localize('chatInput.mode.ask', "(Ask), ask questions or type / for topics."); - break; + if (!mode.isBuiltin) { + const mode = this.currentModeLabel.get(); + modeLabel = localize('chatInput.mode.custom', "({0}), {1}", mode.mode, mode.argumentHint); + } else { + switch (this.currentModeKind) { + case ChatModeKind.Agent: + modeLabel = localize('chatInput.mode.agent', "(Agent), edit files in your workspace."); + break; + case ChatModeKind.Edit: + modeLabel = localize('chatInput.mode.edit', "(Edit), edit files in your workspace."); + break; + case ChatModeKind.Ask: + default: + modeLabel = localize('chatInput.mode.ask', "(Ask), ask questions or type / for topics."); + break; + } } if (verbose) { return kbLabel From 2132fb830bc8e25970cf471a16d4c34344fe0c2f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:08:06 -0700 Subject: [PATCH 1721/4355] Start reducing use of chatSessionType We should be able to use the resource for this instead --- .../api/browser/mainThreadChatSessions.ts | 8 +++---- .../contrib/chat/browser/chatInputPart.ts | 7 +++--- .../chat/browser/chatSessions.contribution.ts | 24 +++++++++---------- .../chat/common/chatSessionsService.ts | 19 +++++++++------ .../test/common/mockChatSessionsService.ts | 16 ++++++------- 5 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index bee6b357b78..c03547eae97 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -336,8 +336,8 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._proxy = this._extHostContext.getProxy(ExtHostContext.ExtHostChatSessions); - this._chatSessionsService.setOptionsChangeCallback(async (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => { - const handle = this._getHandleForSessionType(chatSessionType); + this._chatSessionsService.setOptionsChangeCallback(async (sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => { + const handle = this._getHandleForSessionType(sessionResource.scheme); if (handle !== undefined) { await this.notifyOptionsChange(handle, sessionResource, updates); } @@ -484,10 +484,10 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat try { await session.initialize(token); if (session.options) { - for (const [chatSessionType, handle] of this._sessionTypeToHandle) { + for (const [_, handle] of this._sessionTypeToHandle) { if (handle === providerHandle) { for (const [optionId, value] of Object.entries(session.options)) { - this._chatSessionsService.setSessionOption(chatSessionType, sessionResource, optionId, value); + this._chatSessionsService.setSessionOption(sessionResource, optionId, value); } break; } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 5d72b222d23..da9994f31e2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -663,7 +663,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (!ctx) { continue; } - if (!this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroup.id)) { + if (!this.chatSessionsService.getSessionOption(ctx.chatSessionResource, optionGroup.id)) { // This session does not have a value to contribute for this option group continue; } @@ -682,7 +682,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this.getOrCreateOptionEmitter(optionGroup.id).fire(option); this.chatSessionsService.notifySessionOptionsChange( - ctx.chatSessionType, ctx.chatSessionResource, [{ optionId: optionGroup.id, value: option.id }] ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); @@ -1173,7 +1172,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } for (const [optionGroupId] of this.chatSessionPickerWidgets.entries()) { - const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroupId); + const currentOption = this.chatSessionsService.getSessionOption(ctx.chatSessionResource, optionGroupId); if (currentOption) { const optionGroup = optionGroups.find(g => g.id === optionGroupId); if (optionGroup) { @@ -1218,7 +1217,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionType, ctx.chatSessionResource, optionGroupId); + const currentOptionId = this.chatSessionsService.getSessionOption(ctx.chatSessionResource, optionGroupId); return optionGroup.items.find(m => m.id === currentOptionId); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 9fbe7e2dcca..b3bb74aae2f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -28,7 +28,7 @@ import { ExtensionsRegistry } from '../../../services/extensions/common/extensio import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js'; +import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; @@ -822,18 +822,18 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessions.delete(sessionResource); } - public hasAnySessionOptions(resource: URI): boolean { - const session = this._sessions.get(resource); + public hasAnySessionOptions(sessionResource: URI): boolean { + const session = this._sessions.get(sessionResource); return !!session && !!session.options && Object.keys(session.options).length > 0; } - public getSessionOption(chatSessionType: string, resource: URI, optionId: string): string | undefined { - const session = this._sessions.get(resource); + public getSessionOption(sessionResource: URI, optionId: string): string | undefined { + const session = this._sessions.get(sessionResource); return session?.getOption(optionId); } - public setSessionOption(chatSessionType: string, resource: URI, optionId: string, value: string): boolean { - const session = this._sessions.get(resource); + public setSessionOption(sessionResource: URI, optionId: string, value: string): boolean { + const session = this._sessions.get(sessionResource); return !!session?.setOption(optionId, value); } @@ -878,27 +878,27 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return this._sessionTypeOptions.get(chatSessionType); } - private _optionsChangeCallback?: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + private _optionsChangeCallback?: SessionOptionsChangedCallback; /** * Set the callback for notifying extensions about option changes */ - public setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + public setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void { this._optionsChangeCallback = callback; } /** * Notify extension about option changes for a session */ - public async notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + public async notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { if (!updates.length) { return; } if (this._optionsChangeCallback) { - await this._optionsChangeCallback(chatSessionType, sessionResource, updates); + await this._optionsChangeCallback(sessionResource, updates); } for (const u of updates) { - this.setSessionOption(chatSessionType, sessionResource, u.optionId, u.value); + this.setSessionOption(sessionResource, u.optionId, u.value); } } diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index f4430b9ee87..0b0d09ab9ec 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -126,6 +126,11 @@ export interface IChatSessionContentProvider { provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; } +export type SessionOptionsChangedCallback = (sessionResource: URI, updates: ReadonlyArray<{ + optionId: string; + value: string; +}>) => Promise; + export interface IChatSessionsService { readonly _serviceBrand: undefined; @@ -165,10 +170,10 @@ export interface IChatSessionsService { setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void; // Set callback for notifying extensions about option changes - setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void; + setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void; // Notify extension about option changes - notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; + notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; // Editable session support setEditableSession(sessionResource: URI, data: IEditableData | null): Promise; @@ -185,13 +190,13 @@ export interface IChatSessionsService { getContentProviderSchemes(): string[]; - registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable; - canResolveChatSession(chatSessionResource: URI): Promise; + registerChatSessionContentProvider(scheme: string, provider: IChatSessionContentProvider): IDisposable; + canResolveChatSession(sessionResource: URI): Promise; provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; - hasAnySessionOptions(resource: URI): boolean; - getSessionOption(chatSessionType: string, sessionResource: URI, optionId: string): string | undefined; - setSessionOption(chatSessionType: string, sessionResource: URI, optionId: string, value: string): boolean; + hasAnySessionOptions(sessionResource: URI): boolean; + getSessionOption(sessionResource: URI, optionId: string): string | undefined; + setSessionOption(sessionResource: URI, optionId: string, value: string): boolean; /** * Get the capabilities for a specific session type diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 1aeae970f06..2e23261c46c 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -11,7 +11,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { IEditableData } from '../../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/chatAgents.js'; -import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../../common/chatSessionsService.js'; export class MockChatSessionsService implements IChatSessionsService { _serviceBrand: undefined; @@ -165,16 +165,14 @@ export class MockChatSessionsService implements IChatSessionsService { } } - private optionsChangeCallback?: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise; + private optionsChangeCallback?: SessionOptionsChangedCallback; - setOptionsChangeCallback(callback: (chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>) => Promise): void { + setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void { this.optionsChangeCallback = callback; } - async notifySessionOptionsChange(chatSessionType: string, sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { - if (this.optionsChangeCallback) { - await this.optionsChangeCallback(chatSessionType, sessionResource, updates); - } + async notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise { + await this.optionsChangeCallback?.(sessionResource, updates); } async setEditableSession(sessionResource: URI, data: IEditableData | null): Promise { @@ -197,11 +195,11 @@ export class MockChatSessionsService implements IChatSessionsService { this._onDidChangeSessionItems.fire(chatSessionType); } - getSessionOption(chatSessionType: string, sessionResource: URI, optionId: string): string | undefined { + getSessionOption(sessionResource: URI, optionId: string): string | undefined { return this.sessionOptions.get(sessionResource)?.get(optionId); } - setSessionOption(chatSessionType: string, sessionResource: URI, optionId: string, value: string): boolean { + setSessionOption(sessionResource: URI, optionId: string, value: string): boolean { if (!this.sessionOptions.has(sessionResource)) { this.sessionOptions.set(sessionResource, new Map()); } From 31dca034fef789e7b78ceda766dcc2bdf508571e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20Ruales?= <1588988+jruales@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:17:33 +0100 Subject: [PATCH 1722/4355] Require '#tool:' prefix for tool references in prompt/instruction/agent files (#272565) * Require '#tool:' prefix for tool references in prompt/instruction/agent files * Fix tests * Don't match parentheses or brackets * Stricter parsing for tool names * Small comment fix * Improve regex match group names and comments * Fix semantic tokens provider * support tools with `/` and `.` in qualified names * improve completions, add tests --------- Co-authored-by: Martin Aeschlimann --- .../promptBodyAutocompletion.ts | 61 +++++-- .../promptDocumentSemanticTokensProvider.ts | 6 +- .../common/promptSyntax/promptFileParser.ts | 32 ++-- .../promptBodyAutocompletion.test.ts | 169 ++++++++++++++++++ .../promptSytntax/promptHovers.test.ts | 4 +- .../promptSytntax/promptValidator.test.ts | 32 +++- .../service/newPromptsParser.test.ts | 19 +- .../service/promptsService.test.ts | 18 +- 8 files changed, 283 insertions(+), 58 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptBodyAutocompletion.test.ts diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts index 0796fb1be1a..42ccfc02811 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptBodyAutocompletion.ts @@ -5,7 +5,7 @@ import { dirname, extUri } from '../../../../../../base/common/resources.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; -import { PromptsType } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; import { Position } from '../../../../../../editor/common/core/position.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; @@ -15,7 +15,6 @@ import { CharCode } from '../../../../../../base/common/charCode.js'; import { getWordAtText } from '../../../../../../editor/common/core/wordHelper.js'; import { chatVariableLeader } from '../../chatParserTypes.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; -import { getPromptFileType } from '../config/promptFileLocations.js'; /** * Provides autocompletion for the variables inside prompt bodies. @@ -44,21 +43,35 @@ export class PromptBodyAutocompletion implements CompletionItemProvider { * completion items based on the provided arguments. */ public async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken): Promise { + const promptsType = getPromptsTypeForLanguageId(model.getLanguageId()); + if (!promptsType) { + return undefined; + } const reference = await this.findVariableReference(model, position, token); if (!reference) { return undefined; } const suggestions: CompletionItem[] = []; - if (reference.type === 'file') { - if (reference.contentRange.containsPosition(position)) { - // inside the link range - await this.collectFilePathCompletions(model, position, reference.contentRange, suggestions); - } - } else if (reference.type === '') { - const promptFileType = getPromptFileType(model.uri); - if (promptFileType === PromptsType.agent || promptFileType === PromptsType.prompt) { - await this.collectToolCompletions(model, position, reference.contentRange, suggestions); - } + switch (reference.type) { + case 'file': + if (reference.contentRange.containsPosition(position)) { + // inside the link range + await this.collectFilePathCompletions(model, position, reference.contentRange, suggestions); + } else { + await this.collectDefaultCompletions(model, reference.range, promptsType, suggestions); + } + break; + case 'tool': + if (reference.contentRange.containsPosition(position)) { + if (promptsType === PromptsType.agent || promptsType === PromptsType.prompt) { + await this.collectToolCompletions(model, position, reference.contentRange, suggestions); + } + } else { + await this.collectDefaultCompletions(model, reference.range, promptsType, suggestions); + } + break; + default: + await this.collectDefaultCompletions(model, reference.range, promptsType, suggestions); } return { suggestions }; } @@ -125,7 +138,7 @@ export class PromptBodyAutocompletion implements CompletionItemProvider { /** * Finds a file reference that suites the provided `position`. */ - private async findVariableReference(model: ITextModel, position: Position, token: CancellationToken): Promise<{ contentRange: Range; type: string } | undefined> { + private async findVariableReference(model: ITextModel, position: Position, token: CancellationToken): Promise<{ contentRange: Range; type: string; range: Range } | undefined> { if (model.getLineContent(1).trimEnd() === '---') { let i = 2; while (i <= model.getLineCount() && model.getLineContent(i).trimEnd() !== '---') { @@ -142,15 +155,29 @@ export class PromptBodyAutocompletion implements CompletionItemProvider { if (!varWord) { return undefined; } + const range = new Range(position.lineNumber, varWord.startColumn + 1, position.lineNumber, varWord.endColumn); const nameMatch = varWord.word.match(/^#(\w+:)?/); if (nameMatch) { + const contentCol = varWord.startColumn + nameMatch[0].length; if (nameMatch[1] === 'file:') { - const contentCol = varWord.startColumn + nameMatch[0].length; - return { type: 'file', contentRange: new Range(position.lineNumber, contentCol, position.lineNumber, varWord.endColumn) }; + return { type: 'file', contentRange: new Range(position.lineNumber, contentCol, position.lineNumber, varWord.endColumn), range }; + } else if (nameMatch[1] === 'tool:') { + return { type: 'tool', contentRange: new Range(position.lineNumber, contentCol, position.lineNumber, varWord.endColumn), range }; } } - return { type: '', contentRange: new Range(position.lineNumber, varWord.startColumn + 1, position.lineNumber, varWord.endColumn) }; + return { type: '', contentRange: range, range }; } - + private async collectDefaultCompletions(model: ITextModel, range: Range, promptFileType: PromptsType, suggestions: CompletionItem[]): Promise { + const labels = promptFileType === PromptsType.instructions ? ['file'] : ['file', 'tool']; + labels.forEach(label => { + suggestions.push({ + label: `${label}:`, + kind: CompletionItemKind.Keyword, + insertText: `${label}:`, + range: range, + command: { id: 'editor.action.triggerSuggest', title: 'Suggest' } + }); + }); + } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts index 0502655781c..aa7474566aa 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.ts @@ -56,9 +56,11 @@ export class PromptDocumentSemanticTokensProvider implements DocumentSemanticTok : a.range.startLineNumber - b.range.startLineNumber); for (const ref of ordered) { + // Also include the '#tool:' prefix for syntax highlighting purposes, even if it's not originally part of the variable name itself. + const extraCharCount = '#tool:'.length; const line = ref.range.startLineNumber - 1; // zero-based - const char = ref.range.startColumn - 2; // zero-based, include the leading # - const length = ref.range.endColumn - ref.range.startColumn + 1; + const char = ref.range.startColumn - extraCharCount - 1; // zero-based + const length = ref.range.endColumn - ref.range.startColumn + extraCharCount; const deltaLine = line - lastLine; const deltaChar = deltaLine === 0 ? char - lastChar : char; data.push(deltaLine, deltaChar, length, 0 /* variable token type index */, 0 /* no modifiers */); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index a85823c2c83..e40f3e7787a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -302,6 +302,7 @@ export class PromptBody { const bodyOffset = Iterable.reduce(Iterable.slice(this.linesWithEOL, 0, this.range.startLineNumber - 1), (len, line) => line.length + len, 0); for (let i = this.range.startLineNumber - 1, lineStartOffset = bodyOffset; i < this.range.endLineNumber - 1; i++) { const line = this.linesWithEOL[i]; + // Match markdown links: [text](link) const linkMatch = line.matchAll(/\[(.*?)\]\((.+?)\)/g); for (const match of linkMatch) { const linkEndOffset = match.index + match[0].length - 1; // before the parenthesis @@ -310,26 +311,27 @@ export class PromptBody { fileReferences.push({ content: match[2], range, isMarkdownLink: true }); markdownLinkRanges.push(new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)); } - const reg = new RegExp(`#([\\w]+:)?([^\\s#]+)`, 'g'); + // Match #file: and #tool: + // Regarding the pattern below, see also the variableReg regex in chatRequestParser.ts. + const reg = /#file:(?[^\s#]+)|#tool:(?[\w_\-\.\/]+)/gi; const matches = line.matchAll(reg); for (const match of matches) { - const fullRange = new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1); + const fullMatch = match[0]; + const fullRange = new Range(i + 1, match.index + 1, i + 1, match.index + fullMatch.length + 1); if (markdownLinkRanges.some(mdRange => Range.areIntersectingOrTouching(mdRange, fullRange))) { continue; } - const varType = match[1]; - if (varType) { - if (varType === 'file:') { - const linkStartOffset = match.index + match[0].length - match[2].length; - const linkEndOffset = match.index + match[0].length; - const range = new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1); - fileReferences.push({ content: match[2], range, isMarkdownLink: false }); - } - } else { - const contentStartOffset = match.index + 1; // after the # - const contentEndOffset = match.index + match[0].length; - const range = new Range(i + 1, contentStartOffset + 1, i + 1, contentEndOffset + 1); - variableReferences.push({ name: match[2], range, offset: lineStartOffset + match.index }); + const contentMatch = match.groups?.['filePath'] || match.groups?.['toolName']; + if (!contentMatch) { + continue; + } + const startOffset = match.index + fullMatch.length - contentMatch.length; + const endOffset = match.index + fullMatch.length; + const range = new Range(i + 1, startOffset + 1, i + 1, endOffset + 1); + if (match.groups?.['filePath']) { + fileReferences.push({ content: match.groups?.['filePath'], range, isMarkdownLink: false }); + } else if (match.groups?.['toolName']) { + variableReferences.push({ name: match.groups?.['toolName'], range, offset: lineStartOffset + match.index }); } } lineStartOffset += line.length; diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptBodyAutocompletion.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptBodyAutocompletion.test.ts new file mode 100644 index 00000000000..51e62eff220 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptBodyAutocompletion.test.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { Position } from '../../../../../../editor/common/core/position.js'; +import { CompletionContext, CompletionTriggerKind } from '../../../../../../editor/common/languages.js'; +import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; +import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { LanguageModelToolsService } from '../../../browser/languageModelToolsService.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/languageModelToolsService.js'; +import { PromptBodyAutocompletion } from '../../../common/promptSyntax/languageProviders/promptBodyAutocompletion.js'; +import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { getLanguageIdForPromptsType, PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { getPromptFileExtension } from '../../../common/promptSyntax/config/promptFileLocations.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { FileService } from '../../../../../../platform/files/common/fileService.js'; +import { VSBuffer } from '../../../../../../base/common/buffer.js'; +import { InMemoryFileSystemProvider } from '../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; +import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js'; +import { Range } from '../../../../../../editor/common/core/range.js'; + +suite('PromptBodyAutocompletion', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instaService: TestInstantiationService; + let completionProvider: PromptBodyAutocompletion; + + setup(async () => { + const testConfigService = new TestConfigurationService(); + testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true); + instaService = workbenchInstantiationService({ + contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)), + configurationService: () => testConfigService + }, disposables); + instaService.stub(ILogService, new NullLogService()); + const fileService = disposables.add(instaService.createInstance(FileService)); + instaService.stub(IFileService, fileService); + + const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + disposables.add(fileService.registerProvider('test', fileSystemProvider)); + + // Create some test files and directories + await fileService.createFolder(URI.parse('test:///workspace')); + await fileService.createFolder(URI.parse('test:///workspace/src')); + await fileService.createFolder(URI.parse('test:///workspace/docs')); + await fileService.writeFile(URI.parse('test:///workspace/src/index.ts'), VSBuffer.fromString('export function hello() {}')); + await fileService.writeFile(URI.parse('test:///workspace/README.md'), VSBuffer.fromString('# Project')); + await fileService.writeFile(URI.parse('test:///workspace/package.json'), VSBuffer.fromString('{}')); + + const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); + + const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool1)); + + const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool2)); + + const myExtSource = { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('My.extension') } satisfies ToolDataSource; + const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: myExtSource, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool3)); + + const prExtSource = { type: 'extension', label: 'GitHub Pull Request Extension', extensionId: new ExtensionIdentifier('github.vscode-pull-request-github') } satisfies ToolDataSource; + const prExtTool1 = { id: 'suggestFix', canBeReferencedInPrompt: true, toolReferenceName: 'suggest-fix', modelDescription: 'tool4', displayName: 'Test Tool 4', source: prExtSource, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(prExtTool1)); + + instaService.set(ILanguageModelToolsService, toolService); + + completionProvider = instaService.createInstance(PromptBodyAutocompletion); + }); + + async function getCompletions(content: string, line: number, column: number, promptType: PromptsType) { + const languageId = getLanguageIdForPromptsType(promptType); + const model = disposables.add(createTextModel(content, languageId, undefined, URI.parse('test://workspace/test' + getPromptFileExtension(promptType)))); + const position = new Position(line, column); + const context: CompletionContext = { triggerKind: CompletionTriggerKind.Invoke }; + const result = await completionProvider.provideCompletionItems(model, position, context, CancellationToken.None); + if (!result || !result.suggestions) { + return []; + } + const lineContent = model.getLineContent(position.lineNumber); + return result.suggestions.map(s => { + assert(s.range instanceof Range); + return { + label: s.label, + result: lineContent.substring(0, s.range.startColumn - 1) + s.insertText + lineContent.substring(s.range.endColumn - 1) + }; + }); + } + + suite('prompt body completions', () => { + test('default suggestions', async () => { + const content = [ + '---', + 'description: "Test"', + '---', + '', + 'Use # to reference a file or tool.', + 'One more #to' + ].join('\n'); + + { + const actual = (await getCompletions(content, 5, 6, PromptsType.prompt)); + assert.deepEqual(actual, [ + { + label: 'file:', + result: 'Use #file: to reference a file or tool.' + }, + { + label: 'tool:', + result: 'Use #tool: to reference a file or tool.' + } + ]); + } + { + const actual = (await getCompletions(content, 6, 13, PromptsType.prompt)); + assert.deepEqual(actual, [ + { + label: 'file:', + result: 'One more #file:' + }, + { + label: 'tool:', + result: 'One more #tool:' + } + ]); + } + }); + + test('tool suggestions', async () => { + const content = [ + '---', + 'description: "Test"', + '---', + '', + 'Use #tool: to reference a tool.', + ].join('\n'); + { + const actual = (await getCompletions(content, 5, 11, PromptsType.prompt)); + assert.deepEqual(actual, [ + { + label: 'tool1', + result: 'Use #tool:tool1 to reference a tool.' + }, + { + label: 'tool2', + result: 'Use #tool:tool2 to reference a tool.' + }, + { + label: 'my.extension/tool3', + result: 'Use #tool:my.extension/tool3 to reference a tool.' + }, + { + label: 'github.vscode-pull-request-github/suggest-fix', + result: 'Use #tool:github.vscode-pull-request-github/suggest-fix to reference a tool.' + } + ]); + } + }); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts index 92ac905d346..bd251e20563 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts @@ -50,9 +50,9 @@ suite('PromptHoverProvider', () => { const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; - const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; - disposables.add(toolService.registerToolData(testTool1)); + + const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; disposables.add(toolService.registerToolData(testTool2)); instaService.set(ILanguageModelToolsService, toolService); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index b58376ac44e..fdeaec59e07 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -53,13 +53,18 @@ suite('PromptValidator', () => { const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); const testTool1 = { id: 'testTool1', displayName: 'tool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; - const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; - const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('My.extension') }, inputSchema: {} } satisfies IToolData; - disposables.add(toolService.registerToolData(testTool1)); + const testTool2 = { id: 'testTool2', displayName: 'tool2', canBeReferencedInPrompt: true, toolReferenceName: 'tool2', modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; disposables.add(toolService.registerToolData(testTool2)); + + const myExtSource = { type: 'extension', label: 'My Extension', extensionId: new ExtensionIdentifier('My.extension') } satisfies ToolDataSource; + const testTool3 = { id: 'testTool3', displayName: 'tool3', canBeReferencedInPrompt: true, toolReferenceName: 'tool3', modelDescription: 'Test Tool 3', source: myExtSource, inputSchema: {} } satisfies IToolData; disposables.add(toolService.registerToolData(testTool3)); + const prExtSource = { type: 'extension', label: 'GitHub Pull Request Extension', extensionId: new ExtensionIdentifier('github.vscode-pull-request-github') } satisfies ToolDataSource; + const prExtTool1 = { id: 'suggestFix', canBeReferencedInPrompt: true, toolReferenceName: 'suggest-fix', modelDescription: 'tool4', displayName: 'Test Tool 4', source: prExtSource, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(prExtTool1)); + instaService.set(ILanguageModelToolsService, toolService); const testModels: ILanguageModelChatMetadata[] = [ @@ -588,7 +593,7 @@ suite('PromptValidator', () => { '---', 'description: "Unknown tool var"', '---', - 'This line references known #tool1 and unknown #toolX' + 'This line references known #tool:tool1 and unknown #tool:toolX' ].join('\n'); const markers = await validate(content, PromptsType.prompt); assert.strictEqual(markers.length, 1, 'Expected one warning for unknown tool variable'); @@ -596,6 +601,25 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].message, `Unknown tool or toolset 'toolX'.`); }); + test('body with tool not present in tools list', async () => { + const content = [ + '---', + 'tools: []', + '---', + 'I need', + '#tool:ms-azuretools.vscode-azure-github-copilot/azure_recommend_custom_modes', + '#tool:github.vscode-pull-request-github/suggest-fix', + '#tool:openSimpleBrowser', + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + const actual = markers.sort((a, b) => a.startLineNumber - b.startLineNumber).map(m => ({ message: m.message, startColumn: m.startColumn, endColumn: m.endColumn })); + assert.deepEqual(actual, [ + { message: `Unknown tool or toolset 'ms-azuretools.vscode-azure-github-copilot/azure_recommend_custom_modes'.`, startColumn: 7, endColumn: 77 }, + { message: `Tool or toolset 'github.vscode-pull-request-github/suggest-fix' also needs to be enabled in the header.`, startColumn: 7, endColumn: 52 }, + { message: `Unknown tool or toolset 'openSimpleBrowser'.`, startColumn: 7, endColumn: 24 }, + ]); + }); + }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts index 89af0fd6f4a..7f761c90a59 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/newPromptsParser.test.ts @@ -22,7 +22,7 @@ suite('NewPromptsParser', () => { /* 04 */`tools: ['tool1', 'tool2']`, /* 05 */'---', /* 06 */'This is an agent test.', - /* 07 */'Here is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).', + /* 07 */'Here is a #tool:tool1 variable (and one with closing parenthesis after: #tool:tool-2) and a #file:./reference1.md as well as a [reference](./reference2.md).', ].join('\n'); const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); @@ -42,14 +42,15 @@ suite('NewPromptsParser', () => { ]); assert.deepEqual(result.body.range, { startLineNumber: 6, startColumn: 1, endLineNumber: 8, endColumn: 1 }); assert.equal(result.body.offset, 75); - assert.equal(result.body.getContent(), 'This is an agent test.\nHere is a #tool1 variable and a #file:./reference1.md as well as a [reference](./reference2.md).'); + assert.equal(result.body.getContent(), 'This is an agent test.\nHere is a #tool:tool1 variable (and one with closing parenthesis after: #tool:tool-2) and a #file:./reference1.md as well as a [reference](./reference2.md).'); assert.deepEqual(result.body.fileReferences, [ - { range: new Range(7, 39, 7, 54), content: './reference1.md', isMarkdownLink: false }, - { range: new Range(7, 80, 7, 95), content: './reference2.md', isMarkdownLink: true } + { range: new Range(7, 99, 7, 114), content: './reference1.md', isMarkdownLink: false }, + { range: new Range(7, 140, 7, 155), content: './reference2.md', isMarkdownLink: true } ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 12, 7, 17), name: 'tool1', offset: 108 } + { range: new Range(7, 17, 7, 22), name: 'tool1', offset: 108 }, + { range: new Range(7, 79, 7, 85), name: 'tool-2', offset: 170 } ]); assert.deepEqual(result.header.description, 'Agent test'); assert.deepEqual(result.header.model, 'GPT 4.1'); @@ -156,7 +157,7 @@ suite('NewPromptsParser', () => { /* 04 */'model: GPT 4.1', /* 05 */`tools: ['search', 'terminal']`, /* 06 */'---', - /* 07 */'This is a prompt file body referencing #search and [docs](https://example.com/docs).', + /* 07 */'This is a prompt file body referencing #tool:search and [docs](https://example.com/docs).', ].join('\n'); const result = new PromptFileParser().parse(uri, content); assert.deepEqual(result.uri, uri); @@ -177,12 +178,12 @@ suite('NewPromptsParser', () => { ]); assert.deepEqual(result.body.range, { startLineNumber: 7, startColumn: 1, endLineNumber: 8, endColumn: 1 }); assert.equal(result.body.offset, 114); - assert.equal(result.body.getContent(), 'This is a prompt file body referencing #search and [docs](https://example.com/docs).'); + assert.equal(result.body.getContent(), 'This is a prompt file body referencing #tool:search and [docs](https://example.com/docs).'); assert.deepEqual(result.body.fileReferences, [ - { range: new Range(7, 59, 7, 83), content: 'https://example.com/docs', isMarkdownLink: true }, + { range: new Range(7, 64, 7, 88), content: 'https://example.com/docs', isMarkdownLink: true }, ]); assert.deepEqual(result.body.variableReferences, [ - { range: new Range(7, 41, 7, 47), name: 'search', offset: 153 } + { range: new Range(7, 46, 7, 52), name: 'search', offset: 153 } ]); assert.deepEqual(result.header.description, 'General purpose coding assistant'); assert.deepEqual(result.header.agent, 'agent'); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index b09333d9d55..36e8edb8ba0 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -134,8 +134,8 @@ suite('PromptsService', () => { '\t- this file #file:folder1/file3.prompt.md ', '\t- also this [file4.prompt.md](./folder1/some-other-folder/file4.prompt.md) please!', '## Vars', - '\t- #my-tool', - '\t- #my-other-tool', + '\t- #tool:my-tool', + '\t- #tool:my-other-tool', ' ', ], }, @@ -232,8 +232,8 @@ suite('PromptsService', () => { assert.deepEqual( result1.body.variableReferences, [ - { name: 'my-tool', range: new Range(10, 5, 10, 12), offset: 240 }, - { name: 'my-other-tool', range: new Range(11, 5, 11, 18), offset: 252 }, + { name: 'my-tool', range: new Range(10, 10, 10, 17), offset: 240 }, + { name: 'my-other-tool', range: new Range(11, 10, 11, 23), offset: 257 }, ] ); @@ -822,13 +822,13 @@ suite('PromptsService', () => { 'description: \'Agent file 1.\'', 'tools: [ tool1, tool2 ]', '---', - 'Do it with #tool1', + 'Do it with #tool:tool1', ], }, { name: 'agent2.agent.md', contents: [ - 'First use #tool2\nThen use #tool1', + 'First use #tool:tool2\nThen use #tool:tool1', ], } ], @@ -844,7 +844,7 @@ suite('PromptsService', () => { description: 'Agent file 1.', tools: ['tool1', 'tool2'], agentInstructions: { - content: 'Do it with #tool1', + content: 'Do it with #tool:tool1', toolReferences: [{ name: 'tool1', range: { start: 11, endExclusive: 17 } }], metadata: undefined }, @@ -858,9 +858,9 @@ suite('PromptsService', () => { { name: 'agent2', agentInstructions: { - content: 'First use #tool2\nThen use #tool1', + content: 'First use #tool:tool2\nThen use #tool:tool1', toolReferences: [ - { name: 'tool1', range: { start: 26, endExclusive: 32 } }, + { name: 'tool1', range: { start: 31, endExclusive: 37 } }, { name: 'tool2', range: { start: 10, endExclusive: 16 } } ], metadata: undefined From c1d7cfd7099944599430151222b08dddb56a6ce1 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 27 Oct 2025 17:39:39 +0100 Subject: [PATCH 1723/4355] finaalize secondary side bar view contribution point --- src/vs/platform/extensions/common/extensionsApiProposals.ts | 3 --- src/vs/workbench/api/browser/viewsExtensionPoint.ts | 3 +-- src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts | 6 ------ 3 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index a99e39b15a2..5fa66e7d9b2 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -142,9 +142,6 @@ const _allApiProposals = { contribRemoteHelp: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', }, - contribSecondarySidebar: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts', - }, contribShareMenu: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', }, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 1a55cadb237..8c6534f4865 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -30,7 +30,7 @@ import { VIEWLET_ID as REMOTE } from '../../contrib/remote/browser/remoteExplore import { VIEWLET_ID as SCM } from '../../contrib/scm/common/scm.js'; import { WebviewViewPane } from '../../contrib/webviewView/browser/webviewViewPane.js'; import { Extensions as ExtensionFeaturesRegistryExtensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from '../../services/extensionManagement/common/extensionFeatures.js'; -import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; +import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from '../../services/extensions/common/extensionsRegistry.js'; export interface IUserFriendlyViewsContainerDescriptor { @@ -329,7 +329,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution { panelOrder = this.registerCustomViewContainers(value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel); break; case 'secondarySidebar': - checkProposedApiEnabled(description, 'contribSecondarySidebar'); auxiliaryBarOrder = this.registerCustomViewContainers(value, description, auxiliaryBarOrder, existingViewContainers, ViewContainerLocation.AuxiliaryBar); break; } diff --git a/src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts b/src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts deleted file mode 100644 index 2bdbedd01b2..00000000000 --- a/src/vscode-dts/vscode.proposed.contribSecondarySidebar.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// empty placeholder declaration for the `secondarySidebar`-contribution point From b2e281f448efe58be9aea3e62a64e0f2261f8241 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:44:00 -0700 Subject: [PATCH 1724/4355] Don't include ext description in IChatSessionsExtensionPoint This isn't part of the contribution so the types are incorrect --- .../chat/browser/chatSessions.contribution.ts | 81 ++++++++----------- .../chat/common/chatSessionsService.ts | 2 - 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index b3bb74aae2f..f2a344d0625 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -16,6 +16,7 @@ import { localize, localize2 } from '../../../../nls.js'; import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IRelaxedExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -228,8 +229,8 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ readonly _serviceBrand: undefined; private readonly _itemsProviders: Map = new Map(); + private readonly _contributions: Map = new Map(); private readonly _contentProviders: Map = new Map(); - private readonly _contributions: Map = new Map(); private readonly _alternativeIdMap: Map = new Map(); private readonly _disposableStores: Map = new Map(); private readonly _contextKeys = new Set(); @@ -270,6 +271,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ @IThemeService private readonly _themeService: IThemeService ) { super(); + this._register(extensionPoint.setHandler(extensions => { for (const ext of extensions) { if (!isProposedApiEnabled(ext.description, 'chatSessionsProvider')) { @@ -283,24 +285,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ continue; } - const c: IChatSessionsExtensionPoint = { - type: contribution.type, - name: contribution.name, - displayName: contribution.displayName, - description: contribution.description, - when: contribution.when, - icon: contribution.icon, - alternativeIds: contribution.alternativeIds, - welcomeTitle: contribution.welcomeTitle, - welcomeMessage: contribution.welcomeMessage, - welcomeTips: contribution.welcomeTips, - inputPlaceholder: contribution.inputPlaceholder, - order: contribution.order, - capabilities: contribution.capabilities, - extensionDescription: ext.description, - commands: contribution.commands - }; - this._register(this.registerContribution(c)); + this._register(this.registerContribution(contribution, ext.description)); } } })); @@ -323,7 +308,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ if (chatSessionType === 'local') { displayName = 'Local Chat Agent'; } else { - displayName = this._contributions.get(chatSessionType)?.displayName; + displayName = this._contributions.get(chatSessionType)?.contribution.displayName; } if (displayName) { @@ -346,7 +331,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } } - private registerContribution(contribution: IChatSessionsExtensionPoint): IDisposable { + private registerContribution(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): IDisposable { if (this._contributions.has(contribution.type)) { this._logService.warn(`Chat session contribution with id '${contribution.type}' is already registered.`); return { dispose: () => { } }; @@ -362,7 +347,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } } - this._contributions.set(contribution.type, contribution); + this._contributions.set(contribution.type, { contribution, extension: ext }); // Register alternative IDs if provided if (contribution.alternativeIds) { @@ -385,8 +370,8 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ : ThemeIcon.fromId(contribution.icon); } else { icon = { - dark: resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon.dark), - light: resources.joinPath(contribution.extensionDescription.extensionLocation, contribution.icon.light) + dark: resources.joinPath(ext.extensionLocation, contribution.icon.dark), + light: resources.joinPath(ext.extensionLocation, contribution.icon.light) }; } } @@ -451,7 +436,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ */ private _resolveToPrimaryType(sessionType: string): string | undefined { // Try to find the primary type first - const contribution = this._contributions.get(sessionType); + const contribution = this._contributions.get(sessionType)?.contribution; if (contribution) { // If the contribution is available, use it if (this._isContributionAvailable(contribution)) { @@ -463,7 +448,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ // Check if this is an alternative ID, or if the primary type is not available const primaryType = this._alternativeIdMap.get(sessionType); if (primaryType) { - const altContribution = this._contributions.get(primaryType); + const altContribution = this._contributions.get(primaryType)?.contribution; if (altContribution && this._isContributionAvailable(altContribution)) { this._logService.info(`Resolving chat session type '${sessionType}' to alternative type '${primaryType}'`); return primaryType; @@ -473,7 +458,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return undefined; } - private _registerMenuItems(contribution: IChatSessionsExtensionPoint): IDisposable { + private _registerMenuItems(contribution: IChatSessionsExtensionPoint, extensionDescription: IRelaxedExtensionDescription): IDisposable { // If provider registers anything for the create submenu, let it fully control the creation const contextKeyService = this._contextKeyService.createOverlay([ ['chatSessionType', contribution.type] @@ -500,8 +485,8 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ title: localize('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName), icon: Codicon.plus, source: { - id: contribution.extensionDescription.identifier.value, - title: contribution.extensionDescription.displayName || contribution.extensionDescription.name, + id: extensionDescription.identifier.value, + title: extensionDescription.displayName || extensionDescription.name, } }, group: 'navigation', @@ -554,7 +539,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private _evaluateAvailability(): void { let hasChanges = false; - for (const contribution of this._contributions.values()) { + for (const { contribution, extension } of this._contributions.values()) { const isCurrentlyRegistered = this._disposableStores.has(contribution.type); const shouldBeRegistered = this._isContributionAvailable(contribution); if (isCurrentlyRegistered && !shouldBeRegistered) { @@ -569,7 +554,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ hasChanges = true; } else if (!isCurrentlyRegistered && shouldBeRegistered) { // Enable the contribution by registering it - this._enableContribution(contribution); + this._enableContribution(contribution, extension); hasChanges = true; } } @@ -578,19 +563,19 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ for (const provider of this._itemsProviders.values()) { this._onDidChangeItemsProviders.fire(provider); } - for (const contribution of this._contributions.values()) { + for (const { contribution } of this._contributions.values()) { this._onDidChangeSessionItems.fire(contribution.type); } } } - private _enableContribution(contribution: IChatSessionsExtensionPoint): void { + private _enableContribution(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): void { const disposableStore = new DisposableStore(); this._disposableStores.set(contribution.type, disposableStore); - disposableStore.add(this._registerAgent(contribution)); + disposableStore.add(this._registerAgent(contribution, ext)); disposableStore.add(this._registerCommands(contribution)); - disposableStore.add(this._registerMenuItems(contribution)); + disposableStore.add(this._registerMenuItems(contribution, ext)); } private _disposeSessionsForContribution(contributionId: string): void { @@ -614,9 +599,8 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } } - private _registerAgent(contribution: IChatSessionsExtensionPoint): IDisposable { - const { type: id, name, displayName, description, extensionDescription } = contribution; - const { identifier: extensionId, name: extensionName, displayName: extensionDisplayName, publisher: extensionPublisherId } = extensionDescription; + private _registerAgent(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): IDisposable { + const { type: id, name, displayName, description } = contribution; const agentData: IChatAgentData = { id, name, @@ -634,25 +618,24 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ isSticky: false, }, capabilities: contribution.capabilities, - extensionId, - extensionVersion: extensionDescription.version, - extensionDisplayName: extensionDisplayName || extensionName, - extensionPublisherId, + extensionId: ext.identifier, + extensionVersion: ext.version, + extensionDisplayName: ext.displayName || ext.name, + extensionPublisherId: ext.publisher, }; return this._chatAgentService.registerAgent(id, agentData); } getAllChatSessionContributions(): IChatSessionsExtensionPoint[] { - return Array.from(this._contributions.values()).filter(contribution => - this._isContributionAvailable(contribution) - ); + return Array.from(this._contributions.values(), x => x.contribution) + .filter(contribution => this._isContributionAvailable(contribution)); } getAllChatSessionItemProviders(): IChatSessionItemProvider[] { return [...this._itemsProviders.values()].filter(provider => { // Check if the provider's corresponding contribution is available - const contribution = this._contributions.get(provider.chatSessionType); + const contribution = this._contributions.get(provider.chatSessionType)?.contribution; return !contribution || this._isContributionAvailable(contribution); }); } @@ -664,7 +647,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ chatViewType = resolvedType; } - const contribution = this._contributions.get(chatViewType); + const contribution = this._contributions.get(chatViewType)?.contribution; if (contribution && !this._isContributionAvailable(contribution)) { return false; } @@ -681,7 +664,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ async canResolveChatSession(chatSessionResource: URI) { await this._extensionService.whenInstalledExtensionsRegistered(); const resolvedType = this._resolveToPrimaryType(chatSessionResource.scheme) || chatSessionResource.scheme; - const contribution = this._contributions.get(resolvedType); + const contribution = this._contributions.get(resolvedType)?.contribution; if (contribution && !this._isContributionAvailable(contribution)) { return false; } @@ -944,7 +927,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ * Get the capabilities for a specific session type */ public getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined { - const contribution = this._contributions.get(chatSessionType); + const contribution = this._contributions.get(chatSessionType)?.contribution; return contribution?.capabilities; } diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 0b0d09ab9ec..ed325167f7e 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -10,7 +10,6 @@ import { IDisposable } from '../../../../base/common/lifecycle.js'; import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { IRelaxedExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditableData } from '../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './chatAgents.js'; @@ -47,7 +46,6 @@ export interface IChatSessionsExtensionPoint { readonly name: string; readonly displayName: string; readonly description: string; - readonly extensionDescription: IRelaxedExtensionDescription; readonly when?: string; readonly icon?: string | { light: string; dark: string }; readonly order?: number; From 23b5892dfd6777da88d3f54aaddf6d37a60f6d21 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 18:14:08 +0100 Subject: [PATCH 1725/4355] chat todos - drop offscreen status element (#273578) * chat todos - drop offscreen status element * fix text --- .../chatContentParts/chatTodoListWidget.ts | 20 ++-------- .../test/browser/chatTodoListWidget.test.ts | 38 ++++++++++++------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 948481ae8bf..4eaf1819242 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -32,7 +32,6 @@ interface ITodoListTemplate { readonly todoElement: HTMLElement; readonly statusIcon: HTMLElement; readonly iconLabel: IconLabel; - readonly statusElement: HTMLElement; } class TodoListRenderer implements IListRenderer { @@ -53,18 +52,12 @@ class TodoListRenderer implements IListRenderer { const todoContent = dom.append(todoElement, dom.$('.todo-content')); const iconLabel = templateDisposables.add(new IconLabel(todoContent, { supportIcons: false })); - const statusElement = dom.append(todoContent, dom.$('.todo-status-text')); - statusElement.style.position = 'absolute'; - statusElement.style.left = '-10000px'; - statusElement.style.width = '1px'; - statusElement.style.height = '1px'; - statusElement.style.overflow = 'hidden'; - - return { templateDisposables, todoElement, statusIcon, iconLabel, statusElement }; + + return { templateDisposables, todoElement, statusIcon, iconLabel }; } renderElement(todo: IChatTodo, index: number, templateData: ITodoListTemplate): void { - const { todoElement, statusIcon, iconLabel, statusElement } = templateData; + const { todoElement, statusIcon, iconLabel } = templateData; // Update status icon statusIcon.className = `todo-status-icon codicon ${this.getStatusIconClass(todo.status)}`; @@ -75,17 +68,12 @@ class TodoListRenderer implements IListRenderer { const title = includeDescription && todo.description && todo.description.trim() ? todo.description : undefined; iconLabel.setLabel(todo.title, undefined, { title }); - // Update hidden status text for screen readers - const statusText = this.getStatusText(todo.status); - statusElement.id = `todo-status-${index}`; - statusElement.textContent = statusText; - // Update aria-label + const statusText = this.getStatusText(todo.status); const ariaLabel = includeDescription && todo.description && todo.description.trim() ? localize('chat.todoList.itemWithDescription', '{0}, {1}, {2}', todo.title, statusText, todo.description) : localize('chat.todoList.item', '{0}, {1}', todo.title, statusText); todoElement.setAttribute('aria-label', ariaLabel); - todoElement.setAttribute('aria-describedby', `todo-status-${index}`); } disposeTemplate(templateData: ITodoListTemplate): void { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index 837fa586822..0cafd836be0 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -122,22 +122,32 @@ suite('ChatTodoListWidget Accessibility', () => { // Progress is 2/3 because: 1 completed + 1 in-progress (current) = task 2 of 3 assert.ok(titleText?.includes('(2/3)'), `Title should show progress format, but got: "${titleText}"`); assert.ok(titleText?.includes('Second task'), `Title should show current task when collapsed, but got: "${titleText}"`); - }); test('hidden status text elements exist for screen readers', () => { + }); + + test('todo items have complete aria-label with status information', () => { widget.render('test-session'); - const statusElements = widget.domNode.querySelectorAll('.todo-status-text'); - assert.strictEqual(statusElements.length, 3, 'Should have 3 status text elements'); - - statusElements.forEach((element, index) => { - assert.strictEqual(element.id, `todo-status-${index}`, 'Should have proper ID'); - // Check that it's visually hidden but accessible to screen readers - const style = (element as HTMLElement).style; - assert.strictEqual(style.position, 'absolute'); - assert.strictEqual(style.left, '-10000px'); - assert.strictEqual(style.width, '1px'); - assert.strictEqual(style.height, '1px'); - assert.strictEqual(style.overflow, 'hidden'); - }); + const todoItems = widget.domNode.querySelectorAll('.todo-item'); + assert.strictEqual(todoItems.length, 3, 'Should have 3 todo items'); + + // Check first item (not-started) - aria-label should include title and status + const firstItem = todoItems[0] as HTMLElement; + const firstAriaLabel = firstItem.getAttribute('aria-label'); + assert.ok(firstAriaLabel?.includes('First task'), 'First item aria-label should include title'); + assert.ok(firstAriaLabel?.includes('not started'), 'First item aria-label should include status'); + + // Check second item (in-progress with description) - aria-label should include title, status, and description + const secondItem = todoItems[1] as HTMLElement; + const secondAriaLabel = secondItem.getAttribute('aria-label'); + assert.ok(secondAriaLabel?.includes('Second task'), 'Second item aria-label should include title'); + assert.ok(secondAriaLabel?.includes('in progress'), 'Second item aria-label should include status'); + assert.ok(secondAriaLabel?.includes('This is a task description'), 'Second item aria-label should include description'); + + // Check third item (completed) - aria-label should include title and status + const thirdItem = todoItems[2] as HTMLElement; + const thirdAriaLabel = thirdItem.getAttribute('aria-label'); + assert.ok(thirdAriaLabel?.includes('Third task'), 'Third item aria-label should include title'); + assert.ok(thirdAriaLabel?.includes('completed'), 'Third item aria-label should include status'); }); test('widget displays properly when no todos exist', () => { From 23be380a847504ea98d92fb10233f5dd827dbff6 Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Mon, 27 Oct 2025 10:27:32 -0700 Subject: [PATCH 1726/4355] Debug log terminal resize --- .../workbench/contrib/terminal/browser/terminalInstance.ts | 6 +++--- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e9c79b32bf1..c3d362eb406 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -796,15 +796,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { () => this._isVisible, () => xterm, async (cols, rows) => { - xterm.raw.resize(cols, rows); + xterm.resize(cols, rows); await this._updatePtyDimensions(xterm.raw); }, async (cols) => { - xterm.raw.resize(cols, xterm.raw.rows); + xterm.resize(cols, xterm.raw.rows); await this._updatePtyDimensions(xterm.raw); }, async (rows) => { - xterm.raw.resize(xterm.raw.cols, rows); + xterm.resize(xterm.raw.cols, rows); await this._updatePtyDimensions(xterm.raw); } )); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index e1293b426a9..b909b59bad7 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -440,6 +440,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach } resize(columns: number, rows: number): void { + this._logService.debug(`Resizing terminal to ${columns} col, ${rows} row`); this.raw.resize(columns, rows); } From 3ade1235a0a68dc76880f3b037664ac3a07e5bbe Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:29:52 -0700 Subject: [PATCH 1727/4355] Update src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index b909b59bad7..02d6db4874c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -440,7 +440,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach } resize(columns: number, rows: number): void { - this._logService.debug(`Resizing terminal to ${columns} col, ${rows} row`); + this._logService.debug(`Resizing terminal to ${columns} cols, ${rows} rows`); this.raw.resize(columns, rows); } From 13197dcc1b9e3897fd8549e1ace1cce607e706d0 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 27 Oct 2025 18:49:55 +0100 Subject: [PATCH 1728/4355] Allows insertText to be empty, introduces URI and renames DisplayLocation to Hint. (#273125) * Allows insertText to be empty, introduces URI and renames DisplayLocation to Hint. * Fixes CI * Fixes CI --- src/vs/editor/common/languages.ts | 38 +++++++------ .../common/standalone/standaloneEnums.ts | 10 ++-- .../browser/model/inlineCompletionsModel.ts | 10 ++-- .../browser/model/inlineCompletionsSource.ts | 2 +- .../browser/model/inlineSuggestionItem.ts | 56 ++++++++++--------- .../browser/model/provideInlineCompletions.ts | 37 ++++++------ .../view/inlineEdits/inlineEditWithChanges.ts | 2 +- .../view/inlineEdits/inlineEditsModel.ts | 8 +-- .../view/inlineEdits/inlineEditsView.ts | 5 ++ .../inlineEdits/inlineEditsViewInterface.ts | 5 +- .../inlineEditsViews/inlineEditsCustomView.ts | 19 ++++--- .../browser/suggestInlineCompletions.ts | 2 +- .../standalone/browser/standaloneLanguages.ts | 2 +- src/vs/monaco.d.ts | 37 ++++++------ .../api/common/extHostLanguageFeatures.ts | 14 ++--- .../api/common/extHostTypeConverters.ts | 12 ++-- ...e.proposed.inlineCompletionsAdditions.d.ts | 49 +++++++++------- 17 files changed, 165 insertions(+), 143 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index e4a3388c8e5..2729d4014e6 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -788,30 +788,34 @@ export interface InlineCompletion { * The text can also be a snippet. In that case, a preview with default parameters is shown. * When accepting the suggestion, the full snippet is inserted. */ - readonly insertText: string | { snippet: string }; + readonly insertText: string | { snippet: string } | undefined; /** - * A text that is used to decide if this inline completion should be shown. - * An inline completion is shown if the text to replace is a subword of the filter text. - */ - readonly filterText?: string; + * The range to replace. + * Must begin and end on the same line. + * Refers to the current document or `uri` if provided. + */ + readonly range?: IRange; /** * An optional array of additional text edits that are applied when * selecting this completion. Edits must not overlap with the main edit * nor with themselves. + * Refers to the current document or `uri` if provided. */ readonly additionalTextEdits?: ISingleEditOperation[]; /** - * The range to replace. - * Must begin and end on the same line. + * The file for which the edit applies to. */ - readonly range?: IRange; + readonly uri?: UriComponents; + /** + * A command that is run upon acceptance of this item. + */ readonly command?: Command; - readonly action?: Command; + readonly gutterMenuLinkAction?: Command; /** * Is called the first time an inline completion is shown. @@ -828,11 +832,12 @@ export interface InlineCompletion { readonly isInlineEdit?: boolean; readonly showInlineEditMenu?: boolean; + /** Only show the inline suggestion when the cursor is in the showRange. */ readonly showRange?: IRange; readonly warning?: InlineCompletionWarning; - readonly displayLocation?: InlineCompletionDisplayLocation; + readonly hint?: InlineCompletionHint; /** * Used for telemetry. @@ -845,21 +850,20 @@ export interface InlineCompletionWarning { icon?: IconPath; } -export enum InlineCompletionDisplayLocationKind { +export enum InlineCompletionHintStyle { Code = 1, Label = 2 } -export interface InlineCompletionDisplayLocation { +export interface InlineCompletionHint { + /** Refers to the current document. */ range: IRange; - kind: InlineCompletionDisplayLocationKind; - label: string; + style: InlineCompletionHintStyle; + content: string; jumpToEdit: boolean; } -/** - * TODO: add `| URI | { light: URI; dark: URI }`. -*/ +// TODO: add `| URI | { light: URI; dark: URI }`. export type IconPath = ThemeIcon; export interface InlineCompletions { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index acecb4a41c4..1caf3ced18f 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -437,17 +437,17 @@ export enum InlayHintKind { Parameter = 2 } -export enum InlineCompletionDisplayLocationKind { - Code = 1, - Label = 2 -} - export enum InlineCompletionEndOfLifeReasonKind { Accepted = 0, Rejected = 1, Ignored = 2 } +export enum InlineCompletionHintStyle { + Code = 1, + Label = 2 +} + /** * How an {@link InlineCompletionsProvider inline completion provider} was triggered. */ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 7e1951e8e9d..defa17938d1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -753,7 +753,7 @@ export class InlineCompletionsModel extends Disposable { return false; } - if (state.inlineCompletion.displayLocation) { + if (state.inlineCompletion.hint) { return false; } @@ -803,7 +803,7 @@ export class InlineCompletionsModel extends Disposable { return true; } - if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.displayLocation?.jumpToEdit) { + if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.hint?.jumpToEdit) { return false; } @@ -821,7 +821,7 @@ export class InlineCompletionsModel extends Disposable { if (this._tabShouldIndent.read(reader)) { return false; } - if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.displayLocation?.jumpToEdit) { + if (this._inAcceptFlow.read(reader) && this._appearedInsideViewport.read(reader) && !s.inlineCompletion.hint?.jumpToEdit) { return true; } if (s.inlineCompletion.targetRange.startLineNumber === this._editorObs.cursorLineNumber.read(reader)) { @@ -925,7 +925,7 @@ export class InlineCompletionsModel extends Disposable { editor.edit(edit, this._getMetadata(completion, this.textModel.getLanguageId())); - if (completion.displayLocation === undefined) { + if (completion.hint === undefined) { // do not move the cursor when the completion is displayed in a different location editor.setSelections(state.kind === 'inlineEdit' ? selections.slice(-1) : selections, 'inlineCompletionAccept'); } @@ -1105,7 +1105,7 @@ export class InlineCompletionsModel extends Disposable { this._editor.setPosition(targetPosition, 'inlineCompletions.jump'); // TODO: consider using view information to reveal it - const isSingleLineChange = targetRange.isSingleLine() && (s.inlineCompletion.displayLocation || !s.inlineCompletion.insertText.includes('\n')); + const isSingleLineChange = targetRange.isSingleLine() && (s.inlineCompletion.hint || !s.inlineCompletion.insertText.includes('\n')); if (isSingleLineChange) { this._editor.revealPosition(targetPosition); } else { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 0e5b5cba9de..80f78e37d59 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -270,7 +270,7 @@ export class InlineCompletionsSource extends Disposable { const result = suggestions.map(c => ({ range: c.editRange.toString(), text: c.insertText, - displayLocation: c.displayLocation ? { label: c.displayLocation.label, range: c.displayLocation.range.toString(), kind: c.displayLocation.kind, jumpToEdit: c.displayLocation.jumpToEdit } : undefined, + hint: c.hint, isInlineEdit: c.isInlineEdit, showInlineEditMenu: c.showInlineEditMenu, providerId: c.source.provider.providerId?.toString(), diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts index 0d7a85d3fb1..6444e2040b2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts @@ -7,6 +7,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { matchesSubString } from '../../../../../base/common/filters.js'; import { IObservable, ITransaction, observableSignal, observableValue } from '../../../../../base/common/observable.js'; import { commonPrefixLength, commonSuffixLength, splitLines } from '../../../../../base/common/strings.js'; +import { URI } from '../../../../../base/common/uri.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ISingleEditOperation } from '../../../../common/core/editOperation.js'; import { applyEditsToRanges, StringEdit, StringReplacement } from '../../../../common/core/edits/stringEdit.js'; @@ -19,11 +20,11 @@ import { getPositionOffsetTransformerFromTextModel } from '../../../../common/co import { PositionOffsetTransformerBase } from '../../../../common/core/text/positionToOffset.js'; import { TextLength } from '../../../../common/core/text/textLength.js'; import { linesDiffComputers } from '../../../../common/diff/linesDiffComputers.js'; -import { Command, InlineCompletion, InlineCompletionDisplayLocationKind, InlineCompletionEndOfLifeReason, InlineCompletionTriggerKind, InlineCompletionWarning, PartialAcceptInfo } from '../../../../common/languages.js'; +import { Command, InlineCompletion, InlineCompletionHintStyle, InlineCompletionEndOfLifeReason, InlineCompletionTriggerKind, InlineCompletionWarning, PartialAcceptInfo, InlineCompletionHint } from '../../../../common/languages.js'; import { EndOfLinePreference, ITextModel } from '../../../../common/model.js'; import { TextModelText } from '../../../../common/model/textModelText.js'; import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js'; -import { IDisplayLocation, InlineSuggestData, InlineSuggestionList, PartialAcceptance, SnippetInfo } from './provideInlineCompletions.js'; +import { InlineSuggestData, InlineSuggestionList, PartialAcceptance, SnippetInfo } from './provideInlineCompletions.js'; import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js'; export type InlineSuggestionItem = InlineEditItem | InlineCompletionItem; @@ -33,7 +34,7 @@ export namespace InlineSuggestionItem { data: InlineSuggestData, textModel: ITextModel, ): InlineSuggestionItem { - if (!data.isInlineEdit) { + if (!data.isInlineEdit && !data.uri) { return InlineCompletionItem.create(data, textModel); } else { return InlineEditItem.create(data, textModel); @@ -45,7 +46,7 @@ abstract class InlineSuggestionItemBase { constructor( protected readonly _data: InlineSuggestData, public readonly identity: InlineSuggestionIdentity, - public readonly displayLocation: InlineSuggestDisplayLocation | undefined + public readonly hint: InlineSuggestHint | undefined ) { } /** @@ -57,10 +58,10 @@ abstract class InlineSuggestionItemBase { public get isFromExplicitRequest(): boolean { return this._data.context.triggerKind === InlineCompletionTriggerKind.Explicit; } public get forwardStable(): boolean { return this.source.inlineSuggestions.enableForwardStability ?? false; } public get editRange(): Range { return this.getSingleTextEdit().range; } - public get targetRange(): Range { return this.displayLocation?.range && !this.displayLocation.jumpToEdit ? this.displayLocation?.range : this.editRange; } + public get targetRange(): Range { return this.hint?.range && !this.hint.jumpToEdit ? this.hint?.range : this.editRange; } public get insertText(): string { return this.getSingleTextEdit().text; } public get semanticId(): string { return this.hash; } - public get action(): Command | undefined { return this._sourceInlineCompletion.action; } + public get action(): Command | undefined { return this._sourceInlineCompletion.gutterMenuLinkAction; } public get command(): Command | undefined { return this._sourceInlineCompletion.command; } public get warning(): InlineCompletionWarning | undefined { return this._sourceInlineCompletion.warning; } public get showInlineEditMenu(): boolean { return !!this._sourceInlineCompletion.showInlineEditMenu; } @@ -163,25 +164,25 @@ export class InlineSuggestionIdentity { } } -class InlineSuggestDisplayLocation implements IDisplayLocation { +export class InlineSuggestHint { - public static create(displayLocation: IDisplayLocation) { - return new InlineSuggestDisplayLocation( - displayLocation.range, - displayLocation.label, - displayLocation.kind, + public static create(displayLocation: InlineCompletionHint) { + return new InlineSuggestHint( + Range.lift(displayLocation.range), + displayLocation.content, + displayLocation.style, displayLocation.jumpToEdit ); } private constructor( public readonly range: Range, - public readonly label: string, - public readonly kind: InlineCompletionDisplayLocationKind, + public readonly content: string, + public readonly style: InlineCompletionHintStyle, public readonly jumpToEdit: boolean, ) { } - public withEdit(edit: StringEdit, positionOffsetTransformer: PositionOffsetTransformerBase): InlineSuggestDisplayLocation | undefined { + public withEdit(edit: StringEdit, positionOffsetTransformer: PositionOffsetTransformerBase): InlineSuggestHint | undefined { const offsetRange = new OffsetRange( positionOffsetTransformer.getOffset(this.range.getStartPosition()), positionOffsetTransformer.getOffset(this.range.getEndPosition()) @@ -194,7 +195,7 @@ class InlineSuggestDisplayLocation implements IDisplayLocation { const newRange = positionOffsetTransformer.getRange(newOffsetRange); - return new InlineSuggestDisplayLocation(newRange, this.label, this.kind, this.jumpToEdit); + return new InlineSuggestHint(newRange, this.content, this.style, this.jumpToEdit); } } @@ -212,7 +213,7 @@ export class InlineCompletionItem extends InlineSuggestionItemBase { const trimmedEdit = edit.removeCommonSuffixAndPrefix(textModel.getValue()); const textEdit = transformer.getTextReplacement(edit); - const displayLocation = data.displayLocation ? InlineSuggestDisplayLocation.create(data.displayLocation) : undefined; + const displayLocation = data.hint ? InlineSuggestHint.create(data.hint) : undefined; return new InlineCompletionItem(edit, trimmedEdit, textEdit, textEdit.range, data.snippetInfo, data.additionalTextEdits, data, identity, displayLocation); } @@ -229,7 +230,7 @@ export class InlineCompletionItem extends InlineSuggestionItemBase { data: InlineSuggestData, identity: InlineSuggestionIdentity, - displayLocation: InlineSuggestDisplayLocation | undefined, + displayLocation: InlineSuggestHint | undefined, ) { super(data, identity, displayLocation); } @@ -250,7 +251,7 @@ export class InlineCompletionItem extends InlineSuggestionItemBase { this.additionalTextEdits, this._data, identity, - this.displayLocation + this.hint ); } @@ -263,7 +264,7 @@ export class InlineCompletionItem extends InlineSuggestionItemBase { const positionOffsetTransformer = getPositionOffsetTransformerFromTextModel(textModel); const newTextEdit = positionOffsetTransformer.getTextReplacement(newEdit); - let newDisplayLocation = this.displayLocation; + let newDisplayLocation = this.hint; if (newDisplayLocation) { newDisplayLocation = newDisplayLocation.withEdit(textModelEdit, positionOffsetTransformer); if (!newDisplayLocation) { @@ -358,8 +359,8 @@ export class InlineEditItem extends InlineSuggestionItemBase { const replacedText = textModel.getValueInRange(replacedRange); return SingleUpdatedNextEdit.create(edit, replacedText); }); - const displayLocation = data.displayLocation ? InlineSuggestDisplayLocation.create(data.displayLocation) : undefined; - return new InlineEditItem(offsetEdit, singleTextEdit, data, identity, edits, displayLocation, false, textModel.getVersionId()); + const hint = data.hint ? InlineSuggestHint.create(data.hint) : undefined; + return new InlineEditItem(offsetEdit, singleTextEdit, data.uri, data, identity, edits, hint, false, textModel.getVersionId()); } public readonly snippetInfo: SnippetInfo | undefined = undefined; @@ -369,16 +370,17 @@ export class InlineEditItem extends InlineSuggestionItemBase { private constructor( private readonly _edit: StringEdit, private readonly _textEdit: TextReplacement, + public readonly uri: URI | undefined, data: InlineSuggestData, identity: InlineSuggestionIdentity, private readonly _edits: readonly SingleUpdatedNextEdit[], - displayLocation: InlineSuggestDisplayLocation | undefined, + hint: InlineSuggestHint | undefined, private readonly _lastChangePartOfInlineEdit = false, private readonly _inlineEditModelVersion: number, ) { - super(data, identity, displayLocation); + super(data, identity, hint); } public get updatedEditModelVersion(): number { return this._inlineEditModelVersion; } @@ -392,10 +394,11 @@ export class InlineEditItem extends InlineSuggestionItemBase { return new InlineEditItem( this._edit, this._textEdit, + this.uri, this._data, identity, this._edits, - this.displayLocation, + this.hint, this._lastChangePartOfInlineEdit, this._inlineEditModelVersion, ); @@ -439,7 +442,7 @@ export class InlineEditItem extends InlineSuggestionItemBase { const positionOffsetTransformer = getPositionOffsetTransformerFromTextModel(textModel); const newTextEdit = positionOffsetTransformer.getTextEdit(newEdit).toReplacement(new TextModelText(textModel)); - let newDisplayLocation = this.displayLocation; + let newDisplayLocation = this.hint; if (newDisplayLocation) { newDisplayLocation = newDisplayLocation.withEdit(textModelChanges, positionOffsetTransformer); if (!newDisplayLocation) { @@ -450,6 +453,7 @@ export class InlineEditItem extends InlineSuggestionItemBase { return new InlineEditItem( newEdit, newTextEdit, + this.uri, this._data, this.identity, edits, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index c72db52c849..2a8813dca58 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -16,7 +16,7 @@ import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; import { TextReplacement } from '../../../../common/core/edits/textEdit.js'; -import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletionDisplayLocationKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId } from '../../../../common/languages.js'; +import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId, InlineCompletionHint } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { ITextModel } from '../../../../common/model.js'; import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js'; @@ -29,6 +29,7 @@ import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inli import { isDefined } from '../../../../../base/common/types.js'; import { inlineCompletionIsVisible } from './inlineSuggestionItem.js'; import { EditDeltaInfo } from '../../../../common/textModelEditSource.js'; +import { URI } from '../../../../../base/common/uri.js'; export type InlineCompletionContextWithoutUuid = Omit; @@ -74,12 +75,14 @@ export function provideInlineCompletions( const result = await queryProvider.get(p); if (result) { for (const item of result.inlineSuggestions.items) { - if (item.isInlineEdit || typeof item.insertText !== 'string') { + if (item.isInlineEdit || typeof item.insertText !== 'string' && item.insertText !== undefined) { return undefined; } - const t = new TextReplacement(Range.lift(item.range) ?? defaultReplaceRange, item.insertText); - if (inlineCompletionIsVisible(t, undefined, model, position)) { - return undefined; + if (item.insertText !== undefined) { + const t = new TextReplacement(Range.lift(item.range) ?? defaultReplaceRange, item.insertText); + if (inlineCompletionIsVisible(t, undefined, model, position)) { + return undefined; + } } // else: inline completion is not visible, so lets not block @@ -194,6 +197,10 @@ function toInlineSuggestData( } snippetInfo = undefined; + } else if (inlineCompletion.insertText === undefined) { + insertText = ''; // TODO use undefined + snippetInfo = undefined; + range = new Range(1, 1, 1, 1); } else if ('snippet' in inlineCompletion.insertText) { const preBracketCompletionLength = inlineCompletion.insertText.snippet.length; @@ -228,18 +235,12 @@ function toInlineSuggestData( assertNever(inlineCompletion.insertText); } - const displayLocation = inlineCompletion.displayLocation ? { - range: Range.lift(inlineCompletion.displayLocation.range), - label: inlineCompletion.displayLocation.label, - kind: inlineCompletion.displayLocation.kind, - jumpToEdit: inlineCompletion.displayLocation.jumpToEdit, - } : undefined; - return new InlineSuggestData( range, insertText, snippetInfo, - displayLocation, + URI.revive(inlineCompletion.uri), + inlineCompletion.hint, inlineCompletion.additionalTextEdits || getReadonlyEmptyArray(), inlineCompletion, source, @@ -298,7 +299,8 @@ export class InlineSuggestData { public readonly range: Range, public readonly insertText: string, public readonly snippetInfo: SnippetInfo | undefined, - public readonly displayLocation: IDisplayLocation | undefined, + public readonly uri: URI | undefined, + public readonly hint: InlineCompletionHint | undefined, public readonly additionalTextEdits: readonly ISingleEditOperation[], public readonly sourceInlineCompletion: InlineCompletion, @@ -463,13 +465,6 @@ export interface SnippetInfo { range: Range; } -export interface IDisplayLocation { - range: Range; - label: string; - kind: InlineCompletionDisplayLocationKind; - jumpToEdit: boolean; -} - export enum InlineCompletionEditorType { TextEditor = 'textEditor', DiffEditor = 'diffEditor', diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts index 96bc17bdbab..e4fdbbe7448 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditWithChanges.ts @@ -36,7 +36,7 @@ export class InlineEditWithChanges { public readonly cursorPosition: Position, public readonly multiCursorPositions: readonly Position[], public readonly commands: readonly InlineCompletionCommand[], - public readonly inlineCompletion: InlineSuggestionItem + public readonly inlineCompletion: InlineSuggestionItem, ) { } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts index c475f9b6545..b387c46ca1c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts @@ -11,9 +11,9 @@ import { observableCodeEditor } from '../../../../../browser/observableCodeEdito import { LineRange } from '../../../../../common/core/ranges/lineRange.js'; import { TextEdit } from '../../../../../common/core/edits/textEdit.js'; import { StringText } from '../../../../../common/core/text/abstractText.js'; -import { Command, InlineCompletionCommand, InlineCompletionDisplayLocation } from '../../../../../common/languages.js'; +import { Command, InlineCompletionCommand } from '../../../../../common/languages.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; -import { InlineCompletionItem } from '../../model/inlineSuggestionItem.js'; +import { InlineCompletionItem, InlineSuggestHint } from '../../model/inlineSuggestionItem.js'; import { IInlineEditHost, IInlineEditModel, InlineCompletionViewData, InlineCompletionViewKind, InlineEditTabAction } from './inlineEditsViewInterface.js'; import { InlineEditWithChanges } from './inlineEditWithChanges.js'; @@ -24,7 +24,7 @@ export class InlineEditModel implements IInlineEditModel { readonly extensionCommands: InlineCompletionCommand[]; readonly isInDiffEditor: boolean; - readonly displayLocation: InlineCompletionDisplayLocation | undefined; + readonly displayLocation: InlineSuggestHint | undefined; readonly showCollapsed: IObservable; constructor( @@ -37,7 +37,7 @@ export class InlineEditModel implements IInlineEditModel { this.extensionCommands = this.inlineEdit.inlineCompletion.source.inlineSuggestions.commands ?? []; this.isInDiffEditor = this._model.isInDiffEditor; - this.displayLocation = this.inlineEdit.inlineCompletion.displayLocation; + this.displayLocation = this.inlineEdit.inlineCompletion.hint; this.showCollapsed = this._model.showCollapsed; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 330c49c7ca3..70324757f87 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -21,6 +21,7 @@ import { AbstractText, StringText } from '../../../../../common/core/text/abstra import { TextLength } from '../../../../../common/core/text/textLength.js'; import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; import { TextModel } from '../../../../../common/model/textModel.js'; +import { InlineEditItem } from '../../model/inlineSuggestionItem.js'; import { InlineEditsGutterIndicator } from './components/gutterIndicatorView.js'; import { InlineEditWithChanges } from './inlineEditWithChanges.js'; import { GhostTextIndicator, InlineEditHost, InlineEditModel } from './inlineEditsModel.js'; @@ -395,6 +396,10 @@ export class InlineEditsView extends Disposable { return this._previousView!.view; } + if (model.inlineEdit.inlineCompletion instanceof InlineEditItem && model.inlineEdit.inlineCompletion.uri) { + return InlineCompletionViewKind.Custom; + } + if (model.displayLocation && !model.inlineEdit.inlineCompletion.identity.jumpedTo.read(reader)) { return InlineCompletionViewKind.Custom; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts index d93a10438dc..3e225b6a7c4 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts @@ -6,7 +6,8 @@ import { IMouseEvent } from '../../../../../../base/browser/mouseEvent.js'; import { Event } from '../../../../../../base/common/event.js'; import { IObservable } from '../../../../../../base/common/observable.js'; -import { Command, InlineCompletionCommand, InlineCompletionDisplayLocation } from '../../../../../common/languages.js'; +import { Command, InlineCompletionCommand } from '../../../../../common/languages.js'; +import { InlineSuggestHint } from '../../model/inlineSuggestionItem.js'; import { InlineEditWithChanges } from './inlineEditWithChanges.js'; export enum InlineEditTabAction { @@ -34,7 +35,7 @@ export interface IInlineEditModel { inlineEdit: InlineEditWithChanges; tabAction: IObservable; showCollapsed: IObservable; - displayLocation: InlineCompletionDisplayLocation | undefined; + displayLocation: InlineSuggestHint | undefined; handleInlineEditShown(viewKind: string, viewData?: InlineCompletionViewData): void; accept(): void; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts index 2bd2e77ce71..b6d96623118 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsCustomView.ts @@ -16,9 +16,10 @@ import { LineSource, renderLines, RenderOptions } from '../../../../../../browse import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { Rect } from '../../../../../../common/core/2d/rect.js'; import { LineRange } from '../../../../../../common/core/ranges/lineRange.js'; -import { InlineCompletionDisplayLocation, InlineCompletionDisplayLocationKind } from '../../../../../../common/languages.js'; +import { InlineCompletionHintStyle } from '../../../../../../common/languages.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; import { LineTokens, TokenArray } from '../../../../../../common/tokens/lineTokens.js'; +import { InlineSuggestHint } from '../../../model/inlineSuggestionItem.js'; import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getEditorBlendedColor, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorsuccessfulBackground } from '../theme.js'; import { getContentRenderWidth, maxContentWidthInRange, rectToProps } from '../utils/utils.js'; @@ -45,7 +46,7 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie constructor( private readonly _editor: ICodeEditor, - displayLocation: IObservable, + displayLocation: IObservable, tabAction: IObservable, @IThemeService themeService: IThemeService, @ILanguageService private readonly _languageService: ILanguageService, @@ -127,7 +128,7 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie return maxOriginalContent + maxModifiedContent + padding < editorWidth - editorContentLeft - editorVerticalScrollbar - minimapWidth; } - private getState(displayLocation: InlineCompletionDisplayLocation): { rect: IObservable; label: string; kind: InlineCompletionDisplayLocationKind } { + private getState(displayLocation: InlineSuggestHint): { rect: IObservable; label: string; kind: InlineCompletionHintStyle } { const contentState = derived(this, (reader) => { const startLineNumber = displayLocation.range.startLineNumber; @@ -154,7 +155,7 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie const startLineNumber = displayLocation.range.startLineNumber; const endLineNumber = displayLocation.range.endLineNumber; // only check viewport once in the beginning when rendering the view - const fitsInsideViewport = this.fitsInsideViewport(new LineRange(startLineNumber, endLineNumber + 1), displayLocation.label, undefined); + const fitsInsideViewport = this.fitsInsideViewport(new LineRange(startLineNumber, endLineNumber + 1), displayLocation.content, undefined); const rect = derived(this, reader => { const w = this._editorObs.getOption(EditorOption.fontInfo).read(reader).typicalHalfwidthCharacterWidth; @@ -208,7 +209,7 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie const textRect = Rect.fromLeftTopWidthHeight( contentLeft + contentStartOffset - scrollLeft, topOfLine - scrollTop, - w * displayLocation.label.length, + w * displayLocation.content.length, lineHeight ); @@ -217,17 +218,17 @@ export class InlineEditsCustomView extends Disposable implements IInlineEditsVie return { rect, - label: displayLocation.label, - kind: displayLocation.kind + label: displayLocation.content, + kind: displayLocation.style }; } - private getRendering(state: { rect: IObservable; label: string; kind: InlineCompletionDisplayLocationKind }, styles: IObservable<{ background: string; border: string }>) { + private getRendering(state: { rect: IObservable; label: string; kind: InlineCompletionHintStyle }, styles: IObservable<{ background: string; border: string }>) { const line = document.createElement('div'); const t = this._editor.getModel()!.tokenization.tokenizeLinesAt(1, [state.label])?.[0]; let tokens: LineTokens; - if (t && state.kind === InlineCompletionDisplayLocationKind.Code) { + if (t && state.kind === InlineCompletionHintStyle.Code) { tokens = TokenArray.fromLineTokens(t).toLineTokens(state.label, this._languageService.languageIdCodec); } else { tokens = LineTokens.createEmpty(state.label, this._languageService.languageIdCodec); diff --git a/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts b/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts index 45127df8f0e..ae310dc3616 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestInlineCompletions.ts @@ -33,7 +33,7 @@ class SuggestInlineCompletion implements InlineCompletion { readonly filterText: string, readonly additionalTextEdits: ISingleEditOperation[] | undefined, readonly command: Command | undefined, - readonly action: Command | undefined, + readonly gutterMenuLinkAction: Command | undefined, readonly completion: CompletionItem, ) { } } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 91125ebc41a..31b294e1d03 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -842,7 +842,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { PartialAcceptTriggerKind: standaloneEnums.PartialAcceptTriggerKind, HoverVerbosityAction: standaloneEnums.HoverVerbosityAction, InlineCompletionEndOfLifeReasonKind: standaloneEnums.InlineCompletionEndOfLifeReasonKind, - InlineCompletionDisplayLocationKind: standaloneEnums.InlineCompletionDisplayLocationKind, + InlineCompletionHintStyle: standaloneEnums.InlineCompletionHintStyle, // classes FoldingRangeKind: languages.FoldingRangeKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4767927d7bf..986248861e7 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7512,25 +7512,29 @@ declare namespace monaco.languages { */ readonly insertText: string | { snippet: string; - }; + } | undefined; /** - * A text that is used to decide if this inline completion should be shown. - * An inline completion is shown if the text to replace is a subword of the filter text. - */ - readonly filterText?: string; + * The range to replace. + * Must begin and end on the same line. + * Refers to the current document or `uri` if provided. + */ + readonly range?: IRange; /** * An optional array of additional text edits that are applied when * selecting this completion. Edits must not overlap with the main edit * nor with themselves. + * Refers to the current document or `uri` if provided. */ readonly additionalTextEdits?: editor.ISingleEditOperation[]; /** - * The range to replace. - * Must begin and end on the same line. + * The file for which the edit applies to. + */ + readonly uri?: UriComponents; + /** + * A command that is run upon acceptance of this item. */ - readonly range?: IRange; readonly command?: Command; - readonly action?: Command; + readonly gutterMenuLinkAction?: Command; /** * Is called the first time an inline completion is shown. * @deprecated. Use `onDidShow` of the provider instead. @@ -7543,9 +7547,10 @@ declare namespace monaco.languages { readonly completeBracketPairs?: boolean; readonly isInlineEdit?: boolean; readonly showInlineEditMenu?: boolean; + /** Only show the inline suggestion when the cursor is in the showRange. */ readonly showRange?: IRange; readonly warning?: InlineCompletionWarning; - readonly displayLocation?: InlineCompletionDisplayLocation; + readonly hint?: InlineCompletionHint; /** * Used for telemetry. */ @@ -7557,21 +7562,19 @@ declare namespace monaco.languages { icon?: IconPath; } - export enum InlineCompletionDisplayLocationKind { + export enum InlineCompletionHintStyle { Code = 1, Label = 2 } - export interface InlineCompletionDisplayLocation { + export interface InlineCompletionHint { + /** Refers to the current document. */ range: IRange; - kind: InlineCompletionDisplayLocationKind; - label: string; + style: InlineCompletionHintStyle; + content: string; jumpToEdit: boolean; } - /** - * TODO: add `| Uri | { light: Uri; dark: Uri }`. - */ export type IconPath = editor.ThemeIcon; export interface InlineCompletions { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index fd3fcf47653..86f4da85d7b 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -36,7 +36,7 @@ import { ExtHostDiagnostics } from './extHostDiagnostics.js'; import { ExtHostDocuments } from './extHostDocuments.js'; import { ExtHostTelemetry, IExtHostTelemetry } from './extHostTelemetry.js'; import * as typeConvert from './extHostTypeConverters.js'; -import { CodeAction, CodeActionKind, CompletionList, DataTransfer, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionsDisposeReasonKind, InlineCompletionDisplayLocationKind, InlineCompletionTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from './extHostTypes.js'; +import { CodeAction, CodeActionKind, CompletionList, DataTransfer, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionsDisposeReasonKind, InlineCompletionTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from './extHostTypes.js'; import { Emitter } from '../../../base/common/event.js'; import { IInlineCompletionsUnificationState } from '../../services/inlineCompletions/common/inlineCompletionsUnification.js'; @@ -1426,20 +1426,19 @@ class InlineCompletionAdapter { const insertText = item.insertText; return ({ - insertText: typeof insertText === 'string' ? insertText : { snippet: insertText.value }, - filterText: item.filterText, + insertText: insertText ? (typeof insertText === 'string' ? insertText : { snippet: insertText.value }) : undefined, range: item.range ? typeConvert.Range.from(item.range) : undefined, showRange: (this._isAdditionsProposedApiEnabled && item.showRange) ? typeConvert.Range.from(item.showRange) : undefined, command, - action, + gutterMenuLinkAction: action, idx: idx, completeBracketPairs: this._isAdditionsProposedApiEnabled ? item.completeBracketPairs : false, isInlineEdit: this._isAdditionsProposedApiEnabled ? item.isInlineEdit : false, showInlineEditMenu: this._isAdditionsProposedApiEnabled ? item.showInlineEditMenu : false, - displayLocation: (item.displayLocation && this._isAdditionsProposedApiEnabled) ? { + hint: (item.displayLocation && this._isAdditionsProposedApiEnabled) ? { range: typeConvert.Range.from(item.displayLocation.range), - label: item.displayLocation.label, - kind: item.displayLocation.kind ? typeConvert.InlineCompletionDisplayLocationKind.from(item.displayLocation.kind) : InlineCompletionDisplayLocationKind.Code, + content: item.displayLocation.label, + style: item.displayLocation.kind ? typeConvert.InlineCompletionHintStyle.from(item.displayLocation.kind) : languages.InlineCompletionHintStyle.Code, jumpToEdit: item.displayLocation.jumpToEdit ?? false, } : undefined, warning: (item.warning && this._isAdditionsProposedApiEnabled) ? { @@ -1448,6 +1447,7 @@ class InlineCompletionAdapter { } : undefined, correlationId: this._isAdditionsProposedApiEnabled ? item.correlationId : undefined, suggestionId: undefined, + uri: (this._isAdditionsProposedApiEnabled && item.uri) ? item.uri : undefined, }); }), commands: commands.map(c => { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 72978c57bc6..a8532f4a749 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3537,18 +3537,18 @@ export namespace InlineCompletionEndOfLifeReason { } } -export namespace InlineCompletionDisplayLocationKind { - export function from(value: vscode.InlineCompletionDisplayLocationKind): types.InlineCompletionDisplayLocationKind { +export namespace InlineCompletionHintStyle { + export function from(value: vscode.InlineCompletionDisplayLocationKind): languages.InlineCompletionHintStyle { if (value === types.InlineCompletionDisplayLocationKind.Label) { - return types.InlineCompletionDisplayLocationKind.Label; + return languages.InlineCompletionHintStyle.Label; } else { - return types.InlineCompletionDisplayLocationKind.Code; + return languages.InlineCompletionHintStyle.Code; } } - export function to(kind: languages.InlineCompletionDisplayLocationKind): types.InlineCompletionDisplayLocationKind { + export function to(kind: languages.InlineCompletionHintStyle): types.InlineCompletionDisplayLocationKind { switch (kind) { - case languages.InlineCompletionDisplayLocationKind.Label: + case languages.InlineCompletionHintStyle.Label: return types.InlineCompletionDisplayLocationKind.Label; default: return types.InlineCompletionDisplayLocationKind.Code; diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index 2fad488e561..c999b7466f7 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -32,23 +32,8 @@ declare module 'vscode' { export const onDidChangeCompletionsUnificationState: Event; } - /** - * temporary: to be removed - */ - export interface InlineCompletionsUnificationState { - codeUnification: boolean; - modelUnification: boolean; - expAssignments: string[]; - } - export interface InlineCompletionItem { - /** - * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. - * Defaults to `false`. - */ - completeBracketPairs?: boolean; - - warning?: InlineCompletionWarning; + // insertText: string | SnippetString | undefined; /** If set to `true`, this item is treated as inline edit. */ isInlineEdit?: boolean; @@ -61,19 +46,29 @@ declare module 'vscode' { showInlineEditMenu?: boolean; + /** + * If set, specifies where insertText, filterText and range apply to. + */ + uri?: Uri; + + // TODO: rename to gutterMenuLinkAction action?: Command; displayLocation?: InlineCompletionDisplayLocation; /** Used for telemetry. Can be an arbitrary string. */ correlationId?: string; - } - export enum InlineCompletionDisplayLocationKind { - Code = 1, - Label = 2 + /** + * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. + * Defaults to `false`. + */ + completeBracketPairs?: boolean; + + warning?: InlineCompletionWarning; } + export interface InlineCompletionDisplayLocation { range: Range; kind: InlineCompletionDisplayLocationKind; @@ -81,6 +76,11 @@ declare module 'vscode' { jumpToEdit?: boolean; } + export enum InlineCompletionDisplayLocationKind { + Code = 1, + Label = 2 + } + export interface InlineCompletionWarning { message: MarkdownString | string; icon?: ThemeIcon; @@ -218,4 +218,13 @@ declare module 'vscode' { */ enableForwardStability?: boolean; } + + /** + * temporary: to be removed + */ + export interface InlineCompletionsUnificationState { + codeUnification: boolean; + modelUnification: boolean; + expAssignments: string[]; + } } From ec22048131a22ecd2cc3bab761755c5c97f10697 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:01:23 -0700 Subject: [PATCH 1729/4355] Add a bit more typing around json schemas for extension points - Adds helper to go from type -> basic schema - Uses schema -> type in one more place --- src/vs/base/common/jsonSchema.ts | 47 +++++++++++++++++-- .../browser/copyPasteContribution.ts | 4 +- .../chat/browser/chatOutputItemRenderer.ts | 46 +++++++++--------- .../viewsWelcome/chatViewsWelcomeHandler.ts | 3 +- .../contrib/chat/common/languageModels.ts | 4 +- .../keybinding/browser/keybindingService.ts | 4 +- 6 files changed, 73 insertions(+), 35 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 6932da1b2d6..7f93be5ba33 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -103,9 +103,9 @@ export interface IJSONSchemaSnippet { /** * Converts a basic JSON schema to a TypeScript type. * - * TODO: only supports basic schemas. Doesn't support all JSON schema features. + * Only supports basic schemas. Doesn't support all JSON schema features, such as `additionalProperties` or optional vs required properties. */ -export type SchemaToType = T extends { type: 'string' } +export type TypeForJsonSchema = T extends { type: 'string' } ? string : T extends { type: 'number' } ? number @@ -115,10 +115,10 @@ export type SchemaToType = T extends { type: 'string' } ? null // Object : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: SchemaToType } + ? { [K in keyof P]: TypeForJsonSchema } // Array : T extends { type: 'array'; items: infer I } - ? Array> + ? Array> // OneOf : T extends { oneOf: infer I } ? MapSchemaToType @@ -126,9 +126,46 @@ export type SchemaToType = T extends { type: 'string' } : never; type MapSchemaToType = T extends [infer First, ...infer Rest] - ? SchemaToType | MapSchemaToType + ? TypeForJsonSchema | MapSchemaToType : never; +/** + * Converts a type into a JSON schema shape with basic typing. + * + * This enforces that the schema has the expected properties and types. + * + * Doesn't support all JSON schema features. Notably, doesn't support converting unions or intersections to `oneOf` or `anyOf`. + */ +export type JsonSchemaForType = + // String + T extends string + ? IJSONSchema & { type: 'string' } + + // Number + : T extends number + ? IJSONSchema & { type: 'number' | 'integer' } + + // Boolean + : T extends boolean + ? IJSONSchema & { type: 'boolean' } + + // Any + // https://stackoverflow.com/questions/61624719/how-to-conditionally-detect-the-any-type-in-typescript + : 0 extends (1 & T) + ? IJSONSchema + + // Array + : T extends ReadonlyArray + ? IJSONSchema & { items: JsonSchemaForType } + + // Record + : T extends Record + ? IJSONSchema & { additionalProperties: JsonSchemaForType } + + // Object + : IJSONSchema & { properties: { [K in keyof T]: JsonSchemaForType } }; + + interface Equals { schemas: IJSONSchema[]; id?: string } export function getCompressedContent(schema: IJSONSchema): string { diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts index c8bcdcf31e7..6a4c84dcac7 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { IJSONSchema, SchemaToType } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import * as nls from '../../../../nls.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -96,7 +96,7 @@ registerEditorAction(class PasteAsAction extends EditorAction { }); } - public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: SchemaToType) { + public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: TypeForJsonSchema) { let preference: PastePreference | undefined; if (args) { if ('kind' in args) { diff --git a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts index b6f7ee9ffb6..8af61e8dc12 100644 --- a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts @@ -9,6 +9,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { matchesMimeType } from '../../../../base/common/dataTransfer.js'; import { CancellationError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; +import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { autorun } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; @@ -171,14 +172,30 @@ export class ChatOutputRendererService extends Disposable implements IChatOutput } } -interface IChatOutputRendererContribution { - readonly viewType: string; - readonly mimeTypes: readonly string[]; -} +const chatOutputRendererContributionSchema = { + type: 'object', + additionalProperties: false, + required: ['viewType', 'mimeTypes'], + properties: { + viewType: { + type: 'string', + description: nls.localize('chatOutputRenderer.viewType', 'Unique identifier for the renderer.'), + }, + mimeTypes: { + type: 'array', + description: nls.localize('chatOutputRenderer.mimeTypes', 'MIME types that this renderer can handle'), + items: { + type: 'string' + } + } + } +} as const satisfies IJSONSchema; + +type IChatOutputRendererContribution = TypeForJsonSchema; const chatOutputRenderContributionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatOutputRenderers', - activationEventsGenerator: function* (contributions: readonly IChatOutputRendererContribution[]) { + activationEventsGenerator: function* (contributions) { for (const contrib of contributions) { yield `onChatOutputRenderer:${contrib.viewType}`; } @@ -186,24 +203,7 @@ const chatOutputRenderContributionPoint = ExtensionsRegistry.registerExtensionPo jsonSchema: { description: nls.localize('vscode.extension.contributes.chatOutputRenderer', 'Contributes a renderer for specific MIME types in chat outputs'), type: 'array', - items: { - type: 'object', - additionalProperties: false, - required: ['viewType', 'mimeTypes'], - properties: { - viewType: { - type: 'string', - description: nls.localize('chatOutputRenderer.viewType', 'Unique identifier for the renderer.'), - }, - mimeTypes: { - type: 'array', - description: nls.localize('chatOutputRenderer.mimeTypes', 'MIME types that this renderer can handle'), - items: { - type: 'string' - } - } - } - } + items: chatOutputRendererContributionSchema, } }); diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts index 27602c06093..d402020ce0f 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { JsonSchemaForType } from '../../../../../base/common/jsonSchema.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -49,7 +50,7 @@ const chatViewsWelcomeExtensionPoint = extensionsRegistry.ExtensionsRegistry.reg } }, required: ['icon', 'title', 'contents', 'when'], - } + } satisfies JsonSchemaForType, }); export class ChatViewsWelcomeHandler implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 2f35f9b8b4b..01260b69fa0 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -8,7 +8,7 @@ import { VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { JsonSchemaForType } from '../../../../base/common/jsonSchema.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -265,7 +265,7 @@ export interface ILanguageModelsService { computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise; } -const languageModelChatProviderType: IJSONSchema = { +const languageModelChatProviderType: JsonSchemaForType = { type: 'object', properties: { vendor: { diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 86a3d7785d0..93006dbd0cc 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -14,7 +14,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { parse } from '../../../../base/common/json.js'; -import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, JsonSchemaForType } from '../../../../base/common/jsonSchema.js'; import { UserSettingsLabelProvider } from '../../../../base/common/keybindingLabels.js'; import { KeybindingParser } from '../../../../base/common/keybindingParser.js'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from '../../../../base/common/keybindings.js'; @@ -99,7 +99,7 @@ function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects return true; } -const keybindingType: IJSONSchema = { +const keybindingType: JsonSchemaForType = { type: 'object', default: { command: '', key: '' }, properties: { From 8ce6b78e97664a3ded69294d310b47f099d07066 Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Mon, 27 Oct 2025 11:05:40 -0700 Subject: [PATCH 1730/4355] One more xterm.resize --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c3d362eb406..ddb0eb7b973 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1508,7 +1508,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Re-evaluate dimensions if the container has been set since the xterm instance was created if (this._container && this._cols === 0 && this._rows === 0) { this._initDimensions(); - this.xterm?.raw.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows); + this.xterm?.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows); } const originalIcon = this.shellLaunchConfig.icon; await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows).then(result => { From 6021f3cb345a4d7d85debb6f7487399cee0b0b7b Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Mon, 27 Oct 2025 11:07:24 -0700 Subject: [PATCH 1731/4355] save from making bunch of unique strings in js engine --- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 02d6db4874c..f8dfc9d6bff 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -440,7 +440,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach } resize(columns: number, rows: number): void { - this._logService.debug(`Resizing terminal to ${columns} cols, ${rows} rows`); + this._logService.debug('resizing', columns, rows); this.raw.resize(columns, rows); } From 61993b3c821c7c270161d26817e5ee88907d3a57 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:16:49 -0700 Subject: [PATCH 1732/4355] Improve required checking --- src/vs/base/common/jsonSchema.ts | 42 ++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 7f93be5ba33..da8828eaa5f 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -103,28 +103,60 @@ export interface IJSONSchemaSnippet { /** * Converts a basic JSON schema to a TypeScript type. * - * Only supports basic schemas. Doesn't support all JSON schema features, such as `additionalProperties` or optional vs required properties. + * Doesn't support all JSON schema features, such as `additionalProperties`. */ -export type TypeForJsonSchema = T extends { type: 'string' } +export type TypeForJsonSchema = + // String + T extends { type: 'string' } ? string - : T extends { type: 'number' } + + // Number + : T extends { type: 'number' | 'integer' } ? number + + // Boolean : T extends { type: 'boolean' } ? boolean + + // Null : T extends { type: 'null' } ? null - // Object + + // Object with list of required properties. + // Values are required or optional based on `required` list. + : T extends { type: 'object'; properties: infer P; required: infer RequiredList } + ? { + [K in keyof P]: IsRequired extends true ? TypeForJsonSchema : TypeForJsonSchema | undefined; + } + + // Object with no required properties. + // All values are optional : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: TypeForJsonSchema } + ? { [K in keyof P]: TypeForJsonSchema | undefined } + // Array : T extends { type: 'array'; items: infer I } ? Array> + // OneOf : T extends { oneOf: infer I } ? MapSchemaToType + // Fallthrough : never; +type IsRequired = + RequiredList extends [] + ? false + + : RequiredList extends [K, ...infer R] + ? true + + : RequiredList extends [infer _, ...infer R] + ? IsRequired + + : false; + type MapSchemaToType = T extends [infer First, ...infer Rest] ? TypeForJsonSchema | MapSchemaToType : never; From d6720d0c80c7e6ce17483a301489a9ec129f7c6c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 19:19:01 +0100 Subject: [PATCH 1733/4355] Chat todos: make the label briefer for the widget (fix #273084) (#273368) * Chat todos: make the label briefer for the widget (fix #273084) * cleanup --- .../chatContentParts/chatTodoListWidget.ts | 36 ++++++------------- .../contrib/chat/browser/media/chat.css | 2 +- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 4eaf1819242..00b084a36ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -366,31 +366,24 @@ export class ChatTodoListWidget extends Disposable { const firstInProgressTodo = inProgressTodos.length > 0 ? inProgressTodos[0] : undefined; const notStartedTodos = todoList.filter(todo => todo.status === 'not-started'); const firstNotStartedTodo = notStartedTodos.length > 0 ? notStartedTodos[0] : undefined; - - // Calculate current task number (1-indexed) const currentTaskNumber = inProgressTodos.length > 0 ? completedCount + 1 : Math.max(1, completedCount); - const progressCount = totalCount > 0 ? ` (${currentTaskNumber}/${totalCount})` : ''; - - const titleText = dom.$('span'); - titleText.textContent = this._isExpanded - ? localize('chat.todoList.title', 'Todos') + progressCount - : progressCount; - titleElement.appendChild(titleText); const expandButtonLabel = this._isExpanded - ? localize('chat.todoList.collapseButton', 'Collapse Todos {0}', progressCount) - : localize('chat.todoList.expandButton', 'Expand Todos {0}', progressCount); + ? localize('chat.todoList.collapseButton', 'Collapse Todos') + : localize('chat.todoList.expandButton', 'Expand Todos'); this.expandoElement.setAttribute('aria-label', expandButtonLabel); this.expandoElement.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); - if (!this._isExpanded) { + + if (this._isExpanded) { + const titleText = dom.$('span'); + titleText.textContent = totalCount > 0 ? + localize('chat.todoList.titleWithCount', 'Todos ({0}/{1})', currentTaskNumber, totalCount) : + localize('chat.todoList.title', 'Todos'); + titleElement.appendChild(titleText); + } else { // Show first in-progress todo, or if none, the first not-started todo const todoToShow = firstInProgressTodo || firstNotStartedTodo; if (todoToShow) { - const separator = dom.$('span'); - separator.textContent = ' - '; - separator.style.marginLeft = '4px'; - titleElement.appendChild(separator); - const icon = dom.$('.codicon'); if (todoToShow === firstInProgressTodo) { icon.classList.add('codicon-record'); @@ -399,13 +392,12 @@ export class ChatTodoListWidget extends Disposable { icon.classList.add('codicon-circle-outline'); icon.style.color = 'var(--vscode-foreground)'; } - icon.style.marginLeft = '4px'; icon.style.marginRight = '4px'; icon.style.verticalAlign = 'middle'; titleElement.appendChild(icon); const todoText = dom.$('span'); - todoText.textContent = todoToShow.title; + todoText.textContent = localize('chat.todoList.currentTask', '{0} ({1}/{2})', todoToShow.title, currentTaskNumber, totalCount); todoText.style.verticalAlign = 'middle'; todoText.style.overflow = 'hidden'; todoText.style.textOverflow = 'ellipsis'; @@ -415,14 +407,8 @@ export class ChatTodoListWidget extends Disposable { } // Show "Done" when all tasks are completed else if (completedCount > 0 && completedCount === totalCount) { - const separator = dom.$('span'); - separator.textContent = ' - '; - separator.style.marginLeft = '4px'; - titleElement.appendChild(separator); - const doneText = dom.$('span'); doneText.textContent = localize('chat.todoList.allDone', 'Done'); - doneText.style.marginLeft = '4px'; doneText.style.verticalAlign = 'middle'; titleElement.appendChild(doneText); } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 32f0e446d39..e47600d6a4f 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1122,7 +1122,6 @@ have to be updated for changes to the rules above, or to support more deeply nes padding-left: 3px; display: flex; align-items: center; - gap: 4px; flex: 1; font-size: 11px; white-space: nowrap; @@ -1177,6 +1176,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .expand-icon { flex-shrink: 0; + margin-right: 3px; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title { From c514ae60cfce13839622a86ce8a8e72450ccffd3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 19:20:43 +0100 Subject: [PATCH 1734/4355] chat - drop special centered layout for out of the box (#273054) * chat - drop special centered layout for out of the box * fix condition for terms * restore suggested actions --- .../contrib/chat/browser/chatWidget.ts | 162 +++++------------- .../chat/browser/media/chatViewWelcome.css | 44 ----- 2 files changed, 46 insertions(+), 160 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 354e906d223..1b72123f271 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -3,6 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './media/chat.css'; +import './media/chatAgentHover.css'; +import './media/chatViewWelcome.css'; import * as dom from '../../../../base/browser/dom.js'; import { IMouseWheelEvent } from '../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; @@ -50,11 +53,10 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { EditorResourceAccessor } from '../../../../workbench/common/editor.js'; import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js'; import { ViewContainerLocation } from '../../../common/views.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; +import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { checkModeOption } from '../common/chat.js'; @@ -90,10 +92,8 @@ import { ChatInputPart, IChatInputPartOptions, IChatInputStyles } from './chatIn import { ChatListDelegate, ChatListItemRenderer, IChatListItemTemplate, IChatRendererDelegate } from './chatListRenderer.js'; import { ChatEditorOptions } from './chatOptions.js'; import { ChatViewPane } from './chatViewPane.js'; -import './media/chat.css'; -import './media/chatAgentHover.css'; -import './media/chatViewWelcome.css'; import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js'; +import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; const $ = dom.$; @@ -379,7 +379,6 @@ export class ChatWidget extends Disposable implements IChatWidget { private readonly _lockedToCodingAgentContextKey: IContextKey; private readonly _agentSupportsAttachmentsContextKey: IContextKey; private _attachmentCapabilities: IChatAgentAttachmentCapabilities = supportsAllAttachments; - private lastWelcomeViewChatMode: ChatModeKind | undefined; // Cache for prompt file descriptions to avoid async calls during rendering private readonly promptDescriptionsCache = new Map(); @@ -455,12 +454,6 @@ export class ChatWidget extends Disposable implements IChatWidget { readonly viewContext: IChatWidgetViewContext; - private shouldShowChatSetup(): boolean { - // Check if chat is not installed OR user can sign up for free - // Equivalent to: ChatContextKeys.Setup.installed.negate() OR ChatContextKeys.Entitlement.canSignUp - return !this.chatEntitlementService.sentiment.installed || this.chatEntitlementService.entitlement === ChatEntitlement.Available; - } - get supportsChangingModes(): boolean { return !!this.viewOptions.supportsChangingModes; } @@ -495,14 +488,14 @@ export class ChatWidget extends Disposable implements IChatWidget { @ITelemetryService private readonly telemetryService: ITelemetryService, @IPromptsService private readonly promptsService: IPromptsService, @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IChatModeService private readonly chatModeService: IChatModeService, @IChatLayoutService private readonly chatLayoutService: IChatLayoutService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, @ICommandService private readonly commandService: ICommandService, @IHoverService private readonly hoverService: IHoverService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @IChatTodoListService private readonly chatTodoListService: IChatTodoListService + @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, ) { super(); this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService); @@ -692,17 +685,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.onDidChangeParsedInput(() => this.updateChatInputContext())); - // Listen to entitlement and sentiment changes instead of context keys - this._register(this.chatEntitlementService.onDidChangeEntitlement(() => { - if (!this.shouldShowChatSetup()) { - this.resetWelcomeViewInput(); - } - })); - this._register(this.chatEntitlementService.onDidChangeSentiment(() => { - if (!this.shouldShowChatSetup()) { - this.resetWelcomeViewInput(); - } - })); this._register(this.chatTodoListService.onDidUpdateTodos((sessionId) => { if (this.viewModel?.sessionId === sessionId) { this.inputPart.renderChatTodoListWidget(sessionId); @@ -710,17 +692,6 @@ export class ChatWidget extends Disposable implements IChatWidget { })); } - private resetWelcomeViewInput(): void { - // reset the input in welcome view if it was rendered in experimental mode - if (this.container.classList.contains('new-welcome-view')) { - this.container.classList.remove('new-welcome-view'); - const renderFollowups = this.viewOptions.renderFollowups ?? false; - const renderStyle = this.viewOptions.renderStyle; - this.createInput(this.container, { renderFollowups, renderStyle }); - this.input.setChatMode(this.lastWelcomeViewChatMode ?? ChatModeKind.Agent); - } - } - private _lastSelectedAgent: IChatAgentData | undefined; set lastSelectedAgent(agent: IChatAgentData | undefined) { this.parsedChatRequest = undefined; @@ -1004,13 +975,6 @@ export class ChatWidget extends Disposable implements IChatWidget { }); - // reset the input in welcome view if it was rendered in experimental mode - if (this.viewModel?.getItems().length) { - this.resetWelcomeViewInput(); - // TODO@bhavyaus - // this.focusInput(); - } - if (treeItems.length > 0) { this.updateChatViewVisibility(); } else { @@ -1089,14 +1053,16 @@ export class ChatWidget extends Disposable implements IChatWidget { let welcomeContent: IChatViewWelcomeContent; const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind); - let additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage; + let additionalMessage: string | IMarkdownString | undefined; + if (this.chatEntitlementService.anonymous && !this.chatEntitlementService.sentiment.installed) { + additionalMessage = new MarkdownString(localize({ key: 'settings', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3}).", defaultChat.provider.default.name, defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl), { isTrusted: true }); + } else { + additionalMessage = defaultAgent?.metadata.additionalWelcomeMessage; + } if (!additionalMessage && !this._lockedAgent) { additionalMessage = this._getGenerateInstructionsMessage(); } - if (this.shouldShowChatSetup()) { - welcomeContent = this.getNewWelcomeViewContent(); - this.container.classList.add('new-welcome-view'); - } else if (expEmptyState) { + if (expEmptyState) { welcomeContent = this.getWelcomeViewContent(additionalMessage, expEmptyState); } else { const defaultTips = this.input.currentModeKind === ChatModeKind.Ask @@ -1440,71 +1406,40 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private getNewWelcomeViewContent(): IChatViewWelcomeContent { - let additionalMessage: string | IMarkdownString | undefined = undefined; - if (this.chatEntitlementService.anonymous) { - additionalMessage = new MarkdownString(localize({ key: 'settings', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "AI responses may be inaccurate.\nBy continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3}).", defaultChat.provider.default.name, defaultChat.provider.default.name, defaultChat.termsStatementUrl, defaultChat.privacyStatementUrl), { isTrusted: true }); - } else { - additionalMessage = localize('expChatAdditionalMessage', "AI responses may be inaccurate."); - } - - // Check for provider-specific customizations - const providerIcon = this._lockedAgent?.id ? this.chatSessionsService.getIconForSessionType(this._lockedAgent.id) : undefined; - const providerTitle = this._lockedAgent ? this.chatSessionsService.getWelcomeTitleForSessionType(this._lockedAgent.id) : undefined; - const providerMessage = this._lockedAgent ? this.chatSessionsService.getWelcomeMessageForSessionType(this._lockedAgent.id) : undefined; - const providerTips = this._lockedAgent ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; - const suggestedPrompts = this._lockedAgent ? undefined : this.getNewSuggestedPrompts(); - const welcomeContent: IChatViewWelcomeContent = { - title: providerTitle ?? localize('expChatTitle', 'Build with agents'), - message: providerMessage ? new MarkdownString(providerMessage) : new MarkdownString(localize('expchatMessage', "Let's get started")), - icon: providerIcon ?? Codicon.chatSparkle, - inputPart: this.inputPart.element, - additionalMessage, - isNew: true, - suggestedPrompts, - useLargeIcon: !!providerIcon, - }; - - // Add contributed tips if available - if (providerTips) { - welcomeContent.tips = new MarkdownString(providerTips, { supportThemeIcons: true }); - } - return welcomeContent; - } + private getPromptFileSuggestions(): IChatSuggestedPrompts[] { - private getNewSuggestedPrompts(): IChatSuggestedPrompts[] { - // Check if the workbench is empty - const isEmpty = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; - if (isEmpty) { - return [ - { - icon: Codicon.vscode, - label: localize('chatWidget.suggestedPrompts.gettingStarted', "Ask @vscode"), - prompt: localize('chatWidget.suggestedPrompts.gettingStartedPrompt', "@vscode How do I change the theme to light mode?"), - }, - { - icon: Codicon.newFolder, - label: localize('chatWidget.suggestedPrompts.newProject', "Create Project"), - prompt: localize('chatWidget.suggestedPrompts.newProjectPrompt', "Create a #new Hello World project in TypeScript"), - } - ]; - } else { - return [ - { - icon: Codicon.debugAlt, - label: localize('chatWidget.suggestedPrompts.buildWorkspace', "Build Workspace"), - prompt: localize('chatWidget.suggestedPrompts.buildWorkspacePrompt', "How do I build this workspace?"), - }, - { - icon: Codicon.gear, - label: localize('chatWidget.suggestedPrompts.findConfig', "Show Config"), - prompt: localize('chatWidget.suggestedPrompts.findConfigPrompt', "Where is the configuration for this project defined?"), - } - ]; + // Use predefined suggestions for new users + if (!this.chatEntitlementService.sentiment.installed) { + const isEmpty = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; + if (isEmpty) { + return [ + { + icon: Codicon.vscode, + label: localize('chatWidget.suggestedPrompts.gettingStarted', "Ask @vscode"), + prompt: localize('chatWidget.suggestedPrompts.gettingStartedPrompt', "@vscode How do I change the theme to light mode?"), + }, + { + icon: Codicon.newFolder, + label: localize('chatWidget.suggestedPrompts.newProject', "Create Project"), + prompt: localize('chatWidget.suggestedPrompts.newProjectPrompt', "Create a #new Hello World project in TypeScript"), + } + ]; + } else { + return [ + { + icon: Codicon.debugAlt, + label: localize('chatWidget.suggestedPrompts.buildWorkspace', "Build Workspace"), + prompt: localize('chatWidget.suggestedPrompts.buildWorkspacePrompt', "How do I build this workspace?"), + }, + { + icon: Codicon.gear, + label: localize('chatWidget.suggestedPrompts.findConfig', "Show Config"), + prompt: localize('chatWidget.suggestedPrompts.findConfigPrompt', "Where is the configuration for this project defined?"), + } + ]; + } } - } - private getPromptFileSuggestions(): IChatSuggestedPrompts[] { // Get the current workspace folder context if available const activeEditor = this.editorService.activeEditor; const resource = activeEditor ? EditorResourceAccessor.getOriginalUri(activeEditor) : undefined; @@ -2268,7 +2203,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this._welcomeRenderScheduler.schedule(); })); this._register(this.input.onDidChangeCurrentChatMode(() => { - this.lastWelcomeViewChatMode = this.input.currentModeKind; this._welcomeRenderScheduler.schedule(); this.refreshParsedInput(); this.renderFollowups(); @@ -2757,11 +2691,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inlineInputPart?.layout(layoutHeight, width); } - if (this.container.classList.contains('new-welcome-view')) { - this.inputPart.layout(layoutHeight, Math.min(width, 650)); - } else { - this.inputPart.layout(layoutHeight, width); - } + this.inputPart.layout(layoutHeight, width); const inputHeight = this.inputPart.inputPartHeight; const chatSuggestNextWidgetHeight = this.chatSuggestNextWidget.height; diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index 48b2c207a48..0c671e2d922 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -21,26 +21,6 @@ flex-direction: column; height: 100%; - &.new-welcome-view { - .interactive-input-part { - .dropdown-action-container { - display: none; - } - - .chat-attachments-container { - display: none; - } - } - - .chat-input-toolbars > .chat-input-toolbar > div { - display: none; - } - - .chat-input-toolbars .action-item:not(:has(.monaco-dropdown-with-primary)) { - display: none; - } - } - /* chat welcome container */ .chat-welcome-view-container { display: flex; @@ -65,12 +45,6 @@ } } } - - .new-welcome-view & > .chat-welcome-view-input-part { - max-width: 650px; - margin-bottom: 8px; - /* Reduced margin to make room for prompts below */ - } } /* Container for ChatViewPane welcome view */ @@ -291,24 +265,6 @@ div.chat-welcome-view { } } -/* Fresh login view - move prompts below input box instead of at bottom */ -.new-welcome-view .chat-welcome-view-suggested-prompts { - position: relative; - bottom: auto; - left: auto; - right: auto; - margin-top: 8px; - margin-bottom: 48px; - justify-content: center; - padding: 0 12px; -} - -/* Hide the title for fresh login view */ -.new-welcome-view .chat-welcome-view-suggested-prompts .chat-welcome-view-suggested-prompts-title { - display: none; -} - - .chat-welcome-history-root { width: 100%; padding: 8px; From d71f3db425d180194bb6ca1ab47ccc39009c10c4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:22:28 -0700 Subject: [PATCH 1735/4355] Adopt in code actions --- .../contrib/codeAction/browser/codeActionCommands.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 3f5f5917ef2..15739bef415 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; @@ -24,7 +24,7 @@ function contextKeyForSupportedActions(kind: HierarchicalKind) { new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b')); } -const argsSchema: IJSONSchema = { +const argsSchema = { type: 'object', defaultSnippets: [{ body: { kind: '' } }], properties: { @@ -49,7 +49,7 @@ const argsSchema: IJSONSchema = { description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."), } } -}; +} as const satisfies IJSONSchema; function triggerCodeActionsForEditorSelection( editor: ICodeEditor, @@ -97,7 +97,7 @@ export class CodeActionCommand extends EditorCommand { }); } - public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any) { + public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: HierarchicalKind.Empty, apply: CodeActionAutoApply.IfSingle, @@ -149,7 +149,7 @@ export class RefactorAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Refactor, apply: CodeActionAutoApply.Never @@ -191,7 +191,7 @@ export class SourceAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Source, apply: CodeActionAutoApply.Never From d312fbcd736023119a47779a7c6c925c3f53f59a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 19:30:53 +0100 Subject: [PATCH 1736/4355] Walkthrough Guide is not opened, Welcome tab is opened instead (no extensions) (fix #271542) (#273423) --- .../browser/gettingStarted.contribution.ts | 12 ++----- .../browser/gettingStarted.ts | 31 +++++++++++++++---- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts index 7df25f006a0..17258bd180f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts @@ -61,13 +61,7 @@ registerAction2(class extends Action2 { const commandService = accessor.get(ICommandService); const toSide = typeof optionsOrToSide === 'object' ? optionsOrToSide.toSide : optionsOrToSide; - let inactive = typeof optionsOrToSide === 'object' ? optionsOrToSide.inactive : false; - const activeEditor = editorService.activeEditor; - // If there's already a walkthrough open, open new walkthroughs in the background - if (!inactive && !toSide && activeEditor instanceof GettingStartedInput) { - inactive = true; - } if (walkthroughID) { const selectedCategory = typeof walkthroughID === 'string' ? walkthroughID : walkthroughID.category; @@ -88,10 +82,10 @@ registerAction2(class extends Action2 { let options: GettingStartedEditorOptions; if (selectedCategory) { // Otherwise open the walkthrough editor with the selected category and step - options = { selectedCategory: selectedCategory, selectedStep: selectedStep, showWelcome: false, preserveFocus: toSide ?? false, inactive }; + options = { selectedCategory, selectedStep, showWelcome: false, preserveFocus: toSide ?? false }; } else { // Open Welcome page - options = { selectedCategory: selectedCategory, selectedStep: selectedStep, showWelcome: true, preserveFocus: toSide ?? false, inactive }; + options = { selectedCategory, selectedStep, showWelcome: true, preserveFocus: toSide ?? false }; } editorService.openEditor({ resource: GettingStartedInput.RESOURCE, @@ -101,7 +95,7 @@ registerAction2(class extends Action2 { } else { editorService.openEditor({ resource: GettingStartedInput.RESOURCE, - options: { preserveFocus: toSide ?? false, inactive } + options: { preserveFocus: toSide ?? false } }, toSide ? SIDE_GROUP : undefined); } } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 1c86c5a1f6f..7b8b23ec6b6 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -34,7 +34,6 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; @@ -121,7 +120,6 @@ export class GettingStartedPage extends EditorPane { public static readonly ID = 'gettingStartedPage'; - private editorInput!: GettingStartedInput; private inProgressScroll = Promise.resolve(); private readonly dispatchListeners: DisposableStore = new DisposableStore(); @@ -165,6 +163,10 @@ export class GettingStartedPage extends EditorPane { private readonly categoriesSlideDisposables: DisposableStore; private showFeaturedWalkthrough = true; + get editorInput(): GettingStartedInput { + return this._input as GettingStartedInput; + } + constructor( group: IEditorGroup, @ICommandService private readonly commandService: ICommandService, @@ -340,11 +342,28 @@ export class GettingStartedPage extends EditorPane { }; } - override async setInput(newInput: GettingStartedInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { - this.container.classList.remove('animatable'); - this.editorInput = newInput; - this.editorInput.showTelemetryNotice = (options as GettingStartedEditorOptions)?.showTelemetryNotice ?? true; + override async setInput(newInput: GettingStartedInput, options: GettingStartedEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) { await super.setInput(newInput, options, context, token); + await this.applyInput(options); + } + + override async setOptions(options: GettingStartedEditorOptions | undefined): Promise { + super.setOptions(options); + + if ( + this.editorInput.selectedCategory !== options?.selectedCategory || + this.editorInput.selectedStep !== options?.selectedStep + ) { + await this.applyInput(options); + } + } + + private async applyInput(options: GettingStartedEditorOptions | undefined): Promise { + this.editorInput.showTelemetryNotice = options?.showTelemetryNotice ?? true; + this.editorInput.selectedCategory = options?.selectedCategory; + this.editorInput.selectedStep = options?.selectedStep; + + this.container.classList.remove('animatable'); await this.buildCategoriesSlide(); if (this.shouldAnimate()) { setTimeout(() => this.container.classList.add('animatable'), 0); From ae807c34c06aac94bf9d19e0c8af648cc5a39742 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:54:28 -0700 Subject: [PATCH 1737/4355] For -> from --- src/vs/base/common/jsonSchema.ts | 18 +++++++++--------- .../codeAction/browser/codeActionCommands.ts | 8 ++++---- .../browser/copyPasteContribution.ts | 4 ++-- .../chat/browser/chatOutputItemRenderer.ts | 4 ++-- .../viewsWelcome/chatViewsWelcomeHandler.ts | 4 ++-- .../contrib/chat/common/languageModels.ts | 4 ++-- .../keybinding/browser/keybindingService.ts | 4 ++-- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index da8828eaa5f..e3dcb957637 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -105,7 +105,7 @@ export interface IJSONSchemaSnippet { * * Doesn't support all JSON schema features, such as `additionalProperties`. */ -export type TypeForJsonSchema = +export type TypeFromJsonSchema = // String T extends { type: 'string' } ? string @@ -126,17 +126,17 @@ export type TypeForJsonSchema = // Values are required or optional based on `required` list. : T extends { type: 'object'; properties: infer P; required: infer RequiredList } ? { - [K in keyof P]: IsRequired extends true ? TypeForJsonSchema : TypeForJsonSchema | undefined; + [K in keyof P]: IsRequired extends true ? TypeFromJsonSchema : TypeFromJsonSchema | undefined; } // Object with no required properties. // All values are optional : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: TypeForJsonSchema | undefined } + ? { [K in keyof P]: TypeFromJsonSchema | undefined } // Array : T extends { type: 'array'; items: infer I } - ? Array> + ? Array> // OneOf : T extends { oneOf: infer I } @@ -158,7 +158,7 @@ type IsRequired = : false; type MapSchemaToType = T extends [infer First, ...infer Rest] - ? TypeForJsonSchema | MapSchemaToType + ? TypeFromJsonSchema | MapSchemaToType : never; /** @@ -168,7 +168,7 @@ type MapSchemaToType = T extends [infer First, ...infer Rest] * * Doesn't support all JSON schema features. Notably, doesn't support converting unions or intersections to `oneOf` or `anyOf`. */ -export type JsonSchemaForType = +export type JsonSchemaFromType = // String T extends string ? IJSONSchema & { type: 'string' } @@ -188,14 +188,14 @@ export type JsonSchemaForType = // Array : T extends ReadonlyArray - ? IJSONSchema & { items: JsonSchemaForType } + ? IJSONSchema & { items: JsonSchemaFromType } // Record : T extends Record - ? IJSONSchema & { additionalProperties: JsonSchemaForType } + ? IJSONSchema & { additionalProperties: JsonSchemaFromType } // Object - : IJSONSchema & { properties: { [K in keyof T]: JsonSchemaForType } }; + : IJSONSchema & { properties: { [K in keyof T]: JsonSchemaFromType } }; interface Equals { schemas: IJSONSchema[]; id?: string } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 15739bef415..6b5c45ccd79 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { escapeRegExpCharacters } from '../../../../base/common/strings.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; @@ -97,7 +97,7 @@ export class CodeActionCommand extends EditorCommand { }); } - public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { + public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeFromJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: HierarchicalKind.Empty, apply: CodeActionAutoApply.IfSingle, @@ -149,7 +149,7 @@ export class RefactorAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeFromJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Refactor, apply: CodeActionAutoApply.Never @@ -191,7 +191,7 @@ export class SourceAction extends EditorAction { }); } - public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeForJsonSchema): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs?: TypeFromJsonSchema): void { const args = CodeActionCommandArgs.fromUser(userArgs, { kind: CodeActionKind.Source, apply: CodeActionAutoApply.Never diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts index 6a4c84dcac7..5ab18c88457 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import * as nls from '../../../../nls.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -96,7 +96,7 @@ registerEditorAction(class PasteAsAction extends EditorAction { }); } - public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: TypeForJsonSchema) { + public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: TypeFromJsonSchema) { let preference: PastePreference | undefined; if (args) { if ('kind' in args) { diff --git a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts index 8af61e8dc12..b41fa1750eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts @@ -9,7 +9,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { matchesMimeType } from '../../../../base/common/dataTransfer.js'; import { CancellationError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { IJSONSchema, TypeForJsonSchema } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { autorun } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; @@ -191,7 +191,7 @@ const chatOutputRendererContributionSchema = { } } as const satisfies IJSONSchema; -type IChatOutputRendererContribution = TypeForJsonSchema; +type IChatOutputRendererContribution = TypeFromJsonSchema; const chatOutputRenderContributionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatOutputRenderers', diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts index d402020ce0f..f00771893cf 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { JsonSchemaForType } from '../../../../../base/common/jsonSchema.js'; +import { JsonSchemaFromType } from '../../../../../base/common/jsonSchema.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -50,7 +50,7 @@ const chatViewsWelcomeExtensionPoint = extensionsRegistry.ExtensionsRegistry.reg } }, required: ['icon', 'title', 'contents', 'when'], - } satisfies JsonSchemaForType, + } satisfies JsonSchemaFromType, }); export class ChatViewsWelcomeHandler implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 01260b69fa0..fb6a97e5c05 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -8,7 +8,7 @@ import { VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { JsonSchemaForType } from '../../../../base/common/jsonSchema.js'; +import { JsonSchemaFromType } from '../../../../base/common/jsonSchema.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -265,7 +265,7 @@ export interface ILanguageModelsService { computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise; } -const languageModelChatProviderType: JsonSchemaForType = { +const languageModelChatProviderType: JsonSchemaFromType = { type: 'object', properties: { vendor: { diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 93006dbd0cc..4ddd9f4d00b 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -14,7 +14,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { parse } from '../../../../base/common/json.js'; -import { IJSONSchema, JsonSchemaForType } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, JsonSchemaFromType } from '../../../../base/common/jsonSchema.js'; import { UserSettingsLabelProvider } from '../../../../base/common/keybindingLabels.js'; import { KeybindingParser } from '../../../../base/common/keybindingParser.js'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from '../../../../base/common/keybindings.js'; @@ -99,7 +99,7 @@ function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects return true; } -const keybindingType: JsonSchemaForType = { +const keybindingType: JsonSchemaFromType = { type: 'object', default: { command: '', key: '' }, properties: { From 80d650774b0d683a9219cac3e53c2ace3ef9288a Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 27 Oct 2025 12:03:18 -0700 Subject: [PATCH 1738/4355] Add eslint rule for policy localization (#272334) --- .../code-policy-localization-key-match.ts | 145 ++++++++++++++++++ eslint.config.js | 1 + 2 files changed, 146 insertions(+) create mode 100644 .eslint-plugin-local/code-policy-localization-key-match.ts diff --git a/.eslint-plugin-local/code-policy-localization-key-match.ts b/.eslint-plugin-local/code-policy-localization-key-match.ts new file mode 100644 index 00000000000..6cfc7cbfbc7 --- /dev/null +++ b/.eslint-plugin-local/code-policy-localization-key-match.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; + +/** + * Ensures that localization keys in policy blocks match the keys used in nls.localize() calls. + * + * For example, in a policy block with: + * ``` + * localization: { + * description: { + * key: 'autoApprove2.description', + * value: nls.localize('autoApprove2.description', '...') + * } + * } + * ``` + * + * The key property ('autoApprove2.description') must match the first argument + * to nls.localize() ('autoApprove2.description'). + */ +export = new class PolicyLocalizationKeyMatch implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + mismatch: 'Localization key "{{keyValue}}" does not match the key used in nls.localize("{{localizeKey}}", ...). They must be identical.' + }, + docs: { + description: 'Ensures that localization keys in policy blocks match the keys used in nls.localize() calls', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + function checkLocalizationObject(node: any) { + // Look for objects with structure: { key: '...', value: nls.localize('...', '...') } + + let keyProperty: any; + let valueProperty: any; + + for (const property of node.properties) { + if (property.type !== 'Property') { + continue; + } + + const propertyKey = property.key; + if (propertyKey.type === 'Identifier') { + if (propertyKey.name === 'key') { + keyProperty = property; + } else if (propertyKey.name === 'value') { + valueProperty = property; + } + } + } + + if (!keyProperty || !valueProperty) { + return; + } + + // Extract the key value (should be a string literal) + let keyValue: string | undefined; + if (keyProperty.value.type === 'Literal' && typeof keyProperty.value.value === 'string') { + keyValue = keyProperty.value.value; + } + + if (!keyValue) { + return; + } + + // Check if value is a call to localize or any namespace's localize method + if (valueProperty.value.type === 'CallExpression') { + const callee = valueProperty.value.callee; + + // Check if it's .localize or just localize + let isLocalizeCall = false; + if (callee.type === 'MemberExpression') { + const object = callee.object; + const property = callee.property; + if (object.type === 'Identifier' && + property.type === 'Identifier' && property.name === 'localize') { + isLocalizeCall = true; + } + } else if (callee.type === 'Identifier' && callee.name === 'localize') { + // Direct localize() call + isLocalizeCall = true; + } + + if (isLocalizeCall) { + // Get the first argument to localize (the key) + const args = valueProperty.value.arguments; + if (args.length > 0) { + const firstArg = args[0]; + if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') { + const localizeKey = firstArg.value; + + // Compare the keys + if (keyValue !== localizeKey) { + context.report({ + node: keyProperty.value, + messageId: 'mismatch', + data: { + keyValue, + localizeKey + } + }); + } + } + } + } + } + } + + function isInPolicyBlock(node: any): boolean { + // Walk up the AST to see if we're inside a policy object + const ancestors = context.sourceCode.getAncestors(node); + + for (const ancestor of ancestors) { + if (ancestor.type === 'Property') { + // eslint-disable-next-line local/code-no-any-casts + const property = ancestor as any; + if (property.key && property.key.type === 'Identifier' && property.key.name === 'policy') { + return true; + } + } + } + + return false; + } + + return { + 'ObjectExpression': (node: any) => { + // Only check objects inside policy blocks + if (!isInPolicyBlock(node)) { + return; + } + + // Check if this object has the pattern we're looking for + checkLocalizationObject(node); + } + }; + } +}; diff --git a/eslint.config.js b/eslint.config.js index ff3e0c735aa..af60b5b9124 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -89,6 +89,7 @@ export default tseslint.config( 'local/code-declare-service-brand': 'warn', 'local/code-no-reader-after-await': 'warn', 'local/code-no-observable-get-in-reactive-context': 'warn', + 'local/code-policy-localization-key-match': 'warn', 'local/code-no-deep-import-of-internal': ['error', { '.*Internal': true, 'searchExtTypesInternal': false }], 'local/code-layering': [ 'warn', From 525017c3597078ecba01d3ddfff9d78b6b985f5d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:11:21 -0700 Subject: [PATCH 1739/4355] Start renaming and grouping chat sessionservice properties Renames and grouping of methods to make different functions more clear --- .../browser/mainThreadChatSessions.test.ts | 4 +- .../chat/browser/actions/chatActions.ts | 2 +- .../chat/browser/chatSessions.contribution.ts | 12 ++--- .../chatSessions/view/chatSessionsView.ts | 2 +- .../chat/common/chatSessionsService.ts | 49 ++++++++----------- .../test/common/mockChatSessionsService.ts | 20 ++++---- 6 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index d0992ca3851..457a7ea8db6 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -405,7 +405,7 @@ suite('MainThreadChatSessions', function () { }; // Valid - const chatSessionItem = await chatSessionsService.provideNewChatSessionItem('test-type', { + const chatSessionItem = await chatSessionsService.getNewChatSessionItem('test-type', { request: mockRequest, metadata: {} }, CancellationToken.None); @@ -414,7 +414,7 @@ suite('MainThreadChatSessions', function () { // Invalid session type should throw await assert.rejects( - chatSessionsService.provideNewChatSessionItem('invalid-type', { + chatSessionsService.getNewChatSessionItem('invalid-type', { request: mockRequest, metadata: {} }, CancellationToken.None) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index c58d381ac9c..289b27a073e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -738,7 +738,7 @@ export function registerChatActions() { const providerNSessions: { providerType: string; session: IChatSessionItem }[] = []; for (const provider of providers) { - const sessions = await chatSessionsService.provideChatSessionItems(provider.type, cancellationToken.token); + const sessions = await chatSessionsService.getChatSessionItems(provider.type, cancellationToken.token); if (!sessions?.length) { continue; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index f2a344d0625..02708bb5dd4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -323,7 +323,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private async updateInProgressStatus(chatSessionType: string): Promise { try { - const items = await this.provideChatSessionItems(chatSessionType, CancellationToken.None); + const items = await this.getChatSessionItems(chatSessionType, CancellationToken.None); const inProgress = items.filter(item => item.status === ChatSessionStatus.InProgress); this.reportInProgress(chatSessionType, inProgress.length); } catch (error) { @@ -640,7 +640,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ }); } - async canResolveItemProvider(chatViewType: string): Promise { + async hasChatSessionItemProvider(chatViewType: string): Promise { await this._extensionService.whenInstalledExtensionsRegistered(); const resolvedType = this._resolveToPrimaryType(chatViewType); if (resolvedType) { @@ -677,8 +677,8 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return this._contentProviders.has(chatSessionResource.scheme); } - public async provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { - if (!(await this.canResolveItemProvider(chatSessionType))) { + public async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { + if (!(await this.hasChatSessionItemProvider(chatSessionType))) { return []; } @@ -755,11 +755,11 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ * @param token A cancellation token * @returns A session ID for the newly created session */ - public async provideNewChatSessionItem(chatSessionType: string, options: { + public async getNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: any; }, token: CancellationToken): Promise { - if (!(await this.canResolveItemProvider(chatSessionType))) { + if (!(await this.hasChatSessionItemProvider(chatSessionType))) { throw Error(`Cannot find provider for ${chatSessionType}`); } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index f0e05da8da9..b85da822f9c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -100,7 +100,7 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon private async updateViewRegistration(): Promise { // prepare all chat session providers const contributions = this.chatSessionsService.getAllChatSessionContributions(); - await Promise.all(contributions.map(contrib => this.chatSessionsService.canResolveItemProvider(contrib.type))); + await Promise.all(contributions.map(contrib => this.chatSessionsService.hasChatSessionItemProvider(contrib.type))); const currentProviders = this.getAllChatSessionItemProviders(); const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType)); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index ed325167f7e..8cbfbbd7f1d 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -132,58 +132,42 @@ export type SessionOptionsChangedCallback = (sessionResource: URI, updates: Read export interface IChatSessionsService { readonly _serviceBrand: undefined; + // #region Chat session item provider support readonly onDidChangeItemsProviders: Event; readonly onDidChangeSessionItems: Event; + readonly onDidChangeAvailability: Event; readonly onDidChangeInProgress: Event; registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable; - getAllChatSessionContributions(): IChatSessionsExtensionPoint[]; - canResolveItemProvider(chatSessionType: string): Promise; + hasChatSessionItemProvider(chatSessionType: string): Promise; getAllChatSessionItemProviders(): IChatSessionItemProvider[]; + + getAllChatSessionContributions(): IChatSessionsExtensionPoint[]; getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined; getWelcomeTitleForSessionType(chatSessionType: string): string | undefined; getWelcomeMessageForSessionType(chatSessionType: string): string | undefined; - /** - * Get the input placeholder for a specific session type - */ getInputPlaceholderForSessionType(chatSessionType: string): string | undefined; + getWelcomeTipsForSessionType(chatSessionType: string): string | undefined; /** - * Get the welcome tips for a specific session type + * Get the list of chat session items for a specific session type. */ - getWelcomeTipsForSessionType(chatSessionType: string): string | undefined; - provideNewChatSessionItem(chatSessionType: string, options: { + getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise; + + getNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: any; }, token: CancellationToken): Promise; - provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise; + reportInProgress(chatSessionType: string, count: number): void; getInProgress(): { displayName: string; count: number }[]; - // Get available option groups for a session type - getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined; - - // Set available option groups for a session type (called by MainThreadChatSessions) - setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void; - - // Set callback for notifying extensions about option changes - setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void; - - // Notify extension about option changes - notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; - - // Editable session support - setEditableSession(sessionResource: URI, data: IEditableData | null): Promise; - getEditableData(sessionResource: URI): IEditableData | undefined; - isEditable(sessionResource: URI): boolean; - // Notify providers about session items changes notifySessionItemsChanged(chatSessionType: string): void; + // #endregion // #region Content provider support - - // TODO: Split into separate service? readonly onDidChangeContentProviderSchemes: Event<{ readonly added: string[]; readonly removed: string[] }>; getContentProviderSchemes(): string[]; @@ -201,6 +185,15 @@ export interface IChatSessionsService { */ getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined; + getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined; + setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void; + setOptionsChangeCallback(callback: SessionOptionsChangedCallback): void; + notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string }>): Promise; + + // Editable session support + setEditableSession(sessionResource: URI, data: IEditableData | null): Promise; + getEditableData(sessionResource: URI): IEditableData | undefined; + isEditable(sessionResource: URI): boolean; // #endregion } diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 2e23261c46c..9ec8dafc2c4 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -31,7 +31,7 @@ export class MockChatSessionsService implements IChatSessionsService { private readonly _onDidChangeContentProviderSchemes = new Emitter<{ readonly added: string[]; readonly removed: string[] }>(); readonly onDidChangeContentProviderSchemes = this._onDidChangeContentProviderSchemes.event; - private providers = new Map(); + private sessionItemProviders = new Map(); private contentProviders = new Map(); private contributions: IChatSessionsExtensionPoint[] = []; private optionGroups = new Map(); @@ -57,10 +57,10 @@ export class MockChatSessionsService implements IChatSessionsService { } registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable { - this.providers.set(provider.chatSessionType, provider); + this.sessionItemProviders.set(provider.chatSessionType, provider); return { dispose: () => { - this.providers.delete(provider.chatSessionType); + this.sessionItemProviders.delete(provider.chatSessionType); } }; } @@ -73,12 +73,12 @@ export class MockChatSessionsService implements IChatSessionsService { this.contributions = contributions; } - async canResolveItemProvider(chatSessionType: string): Promise { - return this.providers.has(chatSessionType); + async hasChatSessionItemProvider(chatSessionType: string): Promise { + return this.sessionItemProviders.has(chatSessionType); } getAllChatSessionItemProviders(): IChatSessionItemProvider[] { - return Array.from(this.providers.values()); + return Array.from(this.sessionItemProviders.values()); } getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined { @@ -102,16 +102,16 @@ export class MockChatSessionsService implements IChatSessionsService { return this.contributions.find(c => c.type === chatSessionType)?.welcomeTips; } - async provideNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: unknown }, token: CancellationToken): Promise { - const provider = this.providers.get(chatSessionType); + async getNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: unknown }, token: CancellationToken): Promise { + const provider = this.sessionItemProviders.get(chatSessionType); if (!provider?.provideNewChatSessionItem) { throw new Error(`No provider for ${chatSessionType}`); } return provider.provideNewChatSessionItem(options, token); } - async provideChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { - const provider = this.providers.get(chatSessionType); + async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { + const provider = this.sessionItemProviders.get(chatSessionType); if (!provider) { return []; } From ce1542380c615ba896bbf561baa87fc94f0aace9 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 27 Oct 2025 12:11:21 -0700 Subject: [PATCH 1740/4355] Update todo progress messages to show title first with position (#273620) --- .../workbench/contrib/chat/common/tools/manageTodoListTool.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts index f98bc47f21b..25fb6bebeac 100644 --- a/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts @@ -227,7 +227,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { const startedTodo = startedTodos[0]; // Should only be one in-progress at a time const totalTodos = newTodos.length; const currentPosition = newTodos.findIndex(todo => todo.id === startedTodo.id) + 1; - return localize('todo.starting', "Starting ({0}/{1}) *{2}*", currentPosition, totalTodos, startedTodo.title); + return localize('todo.starting', "Starting: *{0}* ({1}/{2})", startedTodo.title, currentPosition, totalTodos); } // Check for newly completed todos @@ -240,7 +240,7 @@ export class ManageTodoListTool extends Disposable implements IToolImpl { const completedTodo = completedTodos[0]; // Get the first completed todo for the message const totalTodos = newTodos.length; const currentPosition = newTodos.findIndex(todo => todo.id === completedTodo.id) + 1; - return localize('todo.completed', "Completed ({0}/{1}) *{2}*", currentPosition, totalTodos, completedTodo.title); + return localize('todo.completed', "Completed: *{0}* ({1}/{2})", completedTodo.title, currentPosition, totalTodos); } // Check for new todos added From 418d045519b8611b9291c45aa1e6fdcf0d9b7d37 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:15:15 -0700 Subject: [PATCH 1741/4355] provide -> get --- src/vs/workbench/api/browser/mainThreadChatSessions.ts | 2 +- .../api/test/browser/mainThreadChatSessions.test.ts | 10 +++++----- .../contrib/chat/browser/chatSessions.contribution.ts | 2 +- .../workbench/contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatSessionsService.ts | 2 +- .../chat/test/common/mockChatSessionsService.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index c03547eae97..d0532d2346c 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -407,7 +407,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat if (originalEditor) { // Prefetch the chat session content to make the subsequent editor swap quick - this._chatSessionsService.provideChatSessionContent( + this._chatSessionsService.getChatSessionContent( URI.revive(modifiedResource), CancellationToken.None, ).then(() => { diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 457a7ea8db6..21e33f95a48 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -437,11 +437,11 @@ suite('MainThreadChatSessions', function () { const resource = URI.parse(`${sessionScheme}:/test-session`); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session1 = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None); + const session1 = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None); assert.ok(session1); - const session2 = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None); + const session2 = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None); assert.strictEqual(session1, session2); assert.ok((proxy.$provideChatSessionContent as sinon.SinonStub).calledOnce); @@ -463,7 +463,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/test-session`); - const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); @@ -488,7 +488,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/test-session`); - const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); @@ -518,7 +518,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/multi-turn-session`); - const session = await chatSessionsService.provideChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; // Verify the session loaded correctly assert.ok(session); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 02708bb5dd4..213864520e2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -778,7 +778,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return chatSessionItem; } - public async provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + public async getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { if (!(await this.canResolveChatSession(sessionResource))) { throw Error(`Can not find provider for ${sessionResource}`); } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 284739f7bea..232ea297b76 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -458,7 +458,7 @@ export class ChatService extends Disposable implements IChatService { return existing.model; } - const content = await this.chatSessionService.provideChatSessionContent(chatSessionResource, CancellationToken.None); + const content = await this.chatSessionService.getChatSessionContent(chatSessionResource, CancellationToken.None); const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 8cbfbbd7f1d..e6b2e9cde92 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -174,7 +174,7 @@ export interface IChatSessionsService { registerChatSessionContentProvider(scheme: string, provider: IChatSessionContentProvider): IDisposable; canResolveChatSession(sessionResource: URI): Promise; - provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; + getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; hasAnySessionOptions(sessionResource: URI): boolean; getSessionOption(sessionResource: URI, optionId: string): string | undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 9ec8dafc2c4..d4c7917b1c4 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -141,7 +141,7 @@ export class MockChatSessionsService implements IChatSessionsService { return this.contentProviders.has(chatSessionType); } - async provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + async getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { const provider = this.contentProviders.get(sessionResource.scheme); if (!provider) { throw new Error(`No content provider for ${sessionResource.scheme}`); From f7c805de3e61eb173045b956bc7085396b92a82f Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 27 Oct 2025 12:32:26 -0700 Subject: [PATCH 1742/4355] Update terminal config policies (#272324) --- .../terminal/common/terminalConfiguration.ts | 23 +------------------ .../terminalChatAgentToolsConfiguration.ts | 17 ++++++++++++++ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index f3546adfadf..bd3ef396fc1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -7,7 +7,6 @@ import { Codicon } from '../../../../base/common/codicons.js'; import type { IStringDictionary } from '../../../../base/common/collections.js'; import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; import { localize } from '../../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import product from '../../../../platform/product/common/product.js'; @@ -670,27 +669,7 @@ export async function registerTerminalConfiguration(getFontSnippets: () => Promi order: 100, title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), type: 'object', - properties: { - ...terminalConfiguration, - // HACK: This is included here in order instead of in the contrib to support policy - // extraction as it doesn't compute runtime values. - [TerminalContribSettingId.EnableAutoApprove]: { - description: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), - type: 'boolean', - default: true, - policy: { - name: 'ChatToolsTerminalEnableAutoApprove', - category: PolicyCategory.IntegratedTerminal, - minimumVersion: '1.104', - localization: { - description: { - key: 'autoApproveMode.description', - value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), - } - } - } - } - } + properties: terminalConfiguration, }); terminalConfiguration[TerminalSettingId.FontFamily].defaultSnippets = await getFontSnippets(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index a5ede40bb3b..37a22a9fcd4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -10,6 +10,7 @@ import { type IConfigurationPropertySchema } from '../../../../../platform/confi import { TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js'; import product from '../../../../../platform/product/common/product.js'; import { terminalProfileBaseProperties } from '../../../../../platform/terminal/common/terminalPlatformConfiguration.js'; +import { PolicyCategory } from '../../../../../base/common/policy.js'; export const enum TerminalChatAgentToolsSettingId { EnableAutoApprove = 'chat.tools.terminal.enableAutoApprove', @@ -62,6 +63,22 @@ const terminalChatAgentProfileSchema: IJSONSchema = { }; export const terminalChatAgentToolsConfiguration: IStringDictionary = { + [TerminalChatAgentToolsSettingId.EnableAutoApprove]: { + description: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), + type: 'boolean', + default: true, + policy: { + name: 'ChatToolsTerminalEnableAutoApprove', + category: PolicyCategory.IntegratedTerminal, + minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), + } + } + } + }, [TerminalChatAgentToolsSettingId.AutoApprove]: { markdownDescription: [ localize('autoApprove.description.intro', "A list of commands or regular expressions that control whether the run in terminal tool commands require explicit approval. These will be matched against the start of a command. A regular expression can be provided by wrapping the string in {0} characters followed by optional flags such as {1} for case-insensitivity.", '`/`', '`i`'), From 1e728b8a92d9a99deeda53c96fb7a3b8ff0993d2 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:41:21 -0700 Subject: [PATCH 1743/4355] Remove `JsonSchemaFromType` Fun experiment but it seems better to always do the reverse (schema -> type). We're much more limited in how well we can convert from a type to the schema --- src/vs/base/common/jsonSchema.ts | 37 ------------ .../viewsWelcome/chatViewsWelcomeHandler.ts | 60 +++++++++---------- .../contrib/chat/common/languageModels.ts | 14 ++--- .../keybinding/browser/keybindingService.ts | 19 ++---- 4 files changed, 40 insertions(+), 90 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index e3dcb957637..6e3f420ba37 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -161,43 +161,6 @@ type MapSchemaToType = T extends [infer First, ...infer Rest] ? TypeFromJsonSchema | MapSchemaToType : never; -/** - * Converts a type into a JSON schema shape with basic typing. - * - * This enforces that the schema has the expected properties and types. - * - * Doesn't support all JSON schema features. Notably, doesn't support converting unions or intersections to `oneOf` or `anyOf`. - */ -export type JsonSchemaFromType = - // String - T extends string - ? IJSONSchema & { type: 'string' } - - // Number - : T extends number - ? IJSONSchema & { type: 'number' | 'integer' } - - // Boolean - : T extends boolean - ? IJSONSchema & { type: 'boolean' } - - // Any - // https://stackoverflow.com/questions/61624719/how-to-conditionally-detect-the-any-type-in-typescript - : 0 extends (1 & T) - ? IJSONSchema - - // Array - : T extends ReadonlyArray - ? IJSONSchema & { items: JsonSchemaFromType } - - // Record - : T extends Record - ? IJSONSchema & { additionalProperties: JsonSchemaFromType } - - // Object - : IJSONSchema & { properties: { [K in keyof T]: JsonSchemaFromType } }; - - interface Equals { schemas: IJSONSchema[]; id?: string } export function getCompressedContent(schema: IJSONSchema): string { diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts index f00771893cf..dceb67976bc 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcomeHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { JsonSchemaFromType } from '../../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../../base/common/jsonSchema.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -15,42 +15,40 @@ import { checkProposedApiEnabled } from '../../../../services/extensions/common/ import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js'; import { ChatViewsWelcomeExtensions, IChatViewsWelcomeContributionRegistry, IChatViewsWelcomeDescriptor } from './chatViewsWelcome.js'; -interface IRawChatViewsWelcomeContribution { - icon: string; - title: string; - content: string; - when: string; -} + +const chatViewsWelcomeJsonSchema = { + type: 'object', + additionalProperties: false, + required: ['icon', 'title', 'contents', 'when'], + properties: { + icon: { + type: 'string', + description: localize('chatViewsWelcome.icon', 'The icon for the welcome message.'), + }, + title: { + type: 'string', + description: localize('chatViewsWelcome.title', 'The title of the welcome message.'), + }, + content: { + type: 'string', + description: localize('chatViewsWelcome.content', 'The content of the welcome message. The first command link will be rendered as a button.'), + }, + when: { + type: 'string', + description: localize('chatViewsWelcome.when', 'Condition when the welcome message is shown.'), + } + } +} as const satisfies IJSONSchema; + +type IRawChatViewsWelcomeContribution = TypeFromJsonSchema; const chatViewsWelcomeExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatViewsWelcome', jsonSchema: { description: localize('vscode.extension.contributes.chatViewsWelcome', 'Contributes a welcome message to a chat view'), type: 'array', - items: { - additionalProperties: false, - type: 'object', - properties: { - icon: { - type: 'string', - description: localize('chatViewsWelcome.icon', 'The icon for the welcome message.'), - }, - title: { - type: 'string', - description: localize('chatViewsWelcome.title', 'The title of the welcome message.'), - }, - content: { - type: 'string', - description: localize('chatViewsWelcome.content', 'The content of the welcome message. The first command link will be rendered as a button.'), - }, - when: { - type: 'string', - description: localize('chatViewsWelcome.when', 'Condition when the welcome message is shown.'), - } - } - }, - required: ['icon', 'title', 'contents', 'when'], - } satisfies JsonSchemaFromType, + items: chatViewsWelcomeJsonSchema, + }, }); export class ChatViewsWelcomeHandler implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index fb6a97e5c05..8cd9074b5e3 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -8,7 +8,7 @@ import { VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { JsonSchemaFromType } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -265,8 +265,9 @@ export interface ILanguageModelsService { computeTokenLength(modelId: string, message: string | IChatMessage, token: CancellationToken): Promise; } -const languageModelChatProviderType: JsonSchemaFromType = { +const languageModelChatProviderType = { type: 'object', + required: ['vendor', 'displayName'], properties: { vendor: { type: 'string', @@ -285,14 +286,9 @@ const languageModelChatProviderType: JsonSchemaFromType; export const languageModelChatProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'languageModelChatProviders', diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 4ddd9f4d00b..aa62f865df0 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -14,7 +14,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { parse } from '../../../../base/common/json.js'; -import { IJSONSchema, JsonSchemaFromType } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../../base/common/jsonSchema.js'; import { UserSettingsLabelProvider } from '../../../../base/common/keybindingLabels.js'; import { KeybindingParser } from '../../../../base/common/keybindingParser.js'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from '../../../../base/common/keybindings.js'; @@ -57,16 +57,6 @@ import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from '../common/keyb import { IKeyboard, INavigatorWithKeyboard } from './navigatorKeyboard.js'; import { getAllUnboundCommands } from './unboundCommands.js'; -interface ContributedKeyBinding { - command: string; - args?: any; - key: string; - when?: string; - mac?: string; - linux?: string; - win?: string; -} - function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects: string[]): boolean { if (!keyBinding) { rejects.push(nls.localize('nonempty', "expected non-empty value.")); @@ -99,9 +89,10 @@ function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects return true; } -const keybindingType: JsonSchemaFromType = { +const keybindingType = { type: 'object', default: { command: '', key: '' }, + required: ['command', 'key'], properties: { command: { description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'), @@ -131,7 +122,9 @@ const keybindingType: JsonSchemaFromType = { type: 'string' }, } -}; +} as const satisfies IJSONSchema; + +type ContributedKeyBinding = TypeFromJsonSchema; const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'keybindings', From 1f1eb4ee1c15311e8e70b2d1aaa78bc4a435da9e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:52:26 -0700 Subject: [PATCH 1744/4355] Add support for enums in `TypeFromJsonSchema` Also adopts in statusBarExtensionPoint --- src/vs/base/common/jsonSchema.ts | 13 +++++++++++-- .../api/browser/statusBarExtensionPoint.ts | 18 +++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index e3dcb957637..f32f67ec2c2 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -106,8 +106,12 @@ export interface IJSONSchemaSnippet { * Doesn't support all JSON schema features, such as `additionalProperties`. */ export type TypeFromJsonSchema = + // enum + T extends { enum: infer EnumValues } + ? UnionOf + // String - T extends { type: 'string' } + : T extends { type: 'string' } ? string // Number @@ -145,11 +149,16 @@ export type TypeFromJsonSchema = // Fallthrough : never; +type UnionOf = + T extends [infer First, ...infer Rest] + ? First | UnionOf + : never; + type IsRequired = RequiredList extends [] ? false - : RequiredList extends [K, ...infer R] + : RequiredList extends [K, ...infer _] ? true : RequiredList extends [infer _, ...infer R] diff --git a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts index ca43443fec3..2e23155cc73 100644 --- a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts +++ b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IJSONSchema } from '../../../base/common/jsonSchema.js'; +import { IJSONSchema, TypeFromJsonSchema } from '../../../base/common/jsonSchema.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { localize } from '../../../nls.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; @@ -170,16 +170,7 @@ registerSingleton(IExtensionStatusBarItemService, ExtensionStatusBarItemService, // --- extension point and reading of it -interface IUserFriendlyStatusItemEntry { - id: string; - name: string; - text: string; - alignment: 'left' | 'right'; - command?: string; - priority?: number; - tooltip?: string; - accessibilityInformation?: IAccessibilityInformation; -} +type IUserFriendlyStatusItemEntry = TypeFromJsonSchema; function isUserFriendlyStatusItemEntry(candidate: any): candidate is IUserFriendlyStatusItemEntry { const obj = candidate as IUserFriendlyStatusItemEntry; @@ -194,7 +185,7 @@ function isUserFriendlyStatusItemEntry(candidate: any): candidate is IUserFriend ; } -const statusBarItemSchema: IJSONSchema = { +const statusBarItemSchema = { type: 'object', required: ['id', 'text', 'alignment', 'name'], properties: { @@ -230,6 +221,7 @@ const statusBarItemSchema: IJSONSchema = { accessibilityInformation: { type: 'object', description: localize('accessibilityInformation', 'Defines the role and aria label to be used when the status bar entry is focused.'), + required: ['label'], properties: { role: { type: 'string', @@ -242,7 +234,7 @@ const statusBarItemSchema: IJSONSchema = { } } } -}; +} as const satisfies IJSONSchema; const statusBarItemsSchema: IJSONSchema = { description: localize('vscode.extension.contributes.statusBarItems', "Contributes items to the status bar."), From f8b19c09a5cd79ec35ef8ecc935965797420a41b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Oct 2025 21:03:28 +0100 Subject: [PATCH 1745/4355] :up: `playwright@1.56.1` (#273629) --- package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e15c534b47..c03b6394d9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "yazl": "^2.4.3" }, "devDependencies": { - "@playwright/test": "^1.55.1", + "@playwright/test": "^1.56.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", @@ -1721,13 +1721,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", - "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.1" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -14097,13 +14097,13 @@ } }, "node_modules/playwright": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", - "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.1" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -14129,9 +14129,9 @@ } }, "node_modules/playwright/node_modules/playwright-core": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", - "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 18cb41e4fde..3abc7bb4498 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "yazl": "^2.4.3" }, "devDependencies": { - "@playwright/test": "^1.55.1", + "@playwright/test": "^1.56.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", From 12b90e50ef2a184e5bf95d0c4ac7712681e361ad Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Tue, 28 Oct 2025 00:39:58 +0300 Subject: [PATCH 1746/4355] fix: memory leak in gettingStarted (#216876) Co-authored-by: Benjamin Pasero --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 7b8b23ec6b6..d286ec2f3e2 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -560,6 +560,7 @@ export class GettingStartedPage extends EditorPane { }); if (this.currentMediaType !== stepToExpand.media.type) { + this.mediaDisposables.clear(); this.currentMediaType = stepToExpand.media.type; From 78445d0ac54fa61bb251093198dbb22f74553146 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Mon, 27 Oct 2025 14:40:25 -0700 Subject: [PATCH 1747/4355] Add `/build-champ` command (#273638) --- .github/prompts/build-champ.prompt.md | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/prompts/build-champ.prompt.md diff --git a/.github/prompts/build-champ.prompt.md b/.github/prompts/build-champ.prompt.md new file mode 100644 index 00000000000..58d22ecc41d --- /dev/null +++ b/.github/prompts/build-champ.prompt.md @@ -0,0 +1,50 @@ +--- +agent: agent +tools: ['github/github-mcp-server/*', 'microsoft/azure-devops-mcp/*', 'todos'] +--- +# Role +You are the build champion for the VS Code team. Your task is to triage a {{build}} by following these steps: + +# Instructions +1. Display the warning message written below. +2. Investigate the failing jobs of a given {{build}}. + - **Prioritize investigating failing unit test steps first** - these often reveal the root cause of failures +3. Find the most recent {{successful-build}} prior to the failed {{build}}, then identify the {{first-failing-build}} after the {{successful-build}}. Note the commit ids of {{successful-build}} and {{first-failing-build}}. + - Ensure the branch is the same for all builds involved. +4. Using the commit id between the two builds, identify all PRs that were merged in that range. +5. For each PR, analyze the changes to determine if they could have caused the failure. +6. Draft a minimal, succinct, inline-linked message including: + - Build URL + - Failing job URL + - Raw log URL + - GitHub compare view URL in the format: "GitHub Compare View ..." + - List of possible root cause PRs. Ensure the PR numbers are linked to the actual PRs. +7. If no PRs seem to be the cause, suggest rerunning the failed tests and filing an issue on GitHub if the problem persists. + +# Variables +- {{build}}: Provided by the user. If the build is provided as a github url, decode the build URL from it. +- {{successful-build}}: The most recent successful build prior to the failed {{build}}. +- {{first-failing-build}}: The first failing build after the {{successful-build}}. + +## Guidelines +- Include links to relevant PRs, commits, and builds in your output. +- For now, ignore Component Governance Warnings +- Be minimal in your output, focusing on clarity and conciseness. + +## Warning Message + +**⚠️ Known Issues with Build Champion Agent ⚠️** +This agent should be used in parallel while investigating build failures, as it has some known issues: +1. **Double check the error discovered by the agent:** The agent often confuses missing `.build/logs` as an infrastructure issue. This is incorrect, as the missing logs are typically caused by test or build failures. +2. **Pay attention to the build numbers discovered by the agent:** The agent sometimes incorrectly finds the previous successful build. +3. **Double check the list of PRs:** The agent sometimes fails to list all PRs merged between builds. Use the github compare link provided. + +**Please update this prompt file as you discover ways it can be improved.** + +--- + + +## Known Scenarios + +### Expired Approval Step +If a build appears to have an elapsed time of 30 days, this indicates this build was meant to be a release build, but no one approved the release. There is no action needed in this scenario. From 872be742d2ca92a8aaf978a7ba54a9a5bc1e6340 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 27 Oct 2025 14:42:22 -0700 Subject: [PATCH 1748/4355] fix pill diff bug --- src/vs/workbench/contrib/chat/browser/chat.ts | 1 - .../chatMarkdownContentPart.ts | 126 +++-- .../chatMultiDiffContentPart.ts | 1 + .../chatInputOutputMarkdownProgressPart.ts | 1 - .../chatTerminalToolConfirmationSubPart.ts | 1 - .../chatToolConfirmationSubPart.ts | 1 - .../chatToolPostExecuteConfirmationPart.ts | 4 - .../chatEditingCheckpointTimeline.ts | 2 + .../chatEditingCheckpointTimelineImpl.ts | 75 ++- .../chatEditingModifiedDocumentEntry.ts | 1 - .../chatEditingModifiedFileEntry.ts | 8 +- .../chatEditingModifiedNotebookEntry.ts | 1 - .../browser/chatEditing/chatEditingSession.ts | 12 +- .../chatEditingModifiedNotebookDiff.ts | 1 + .../contrib/chat/common/chatEditingService.ts | 7 +- .../chatEditingCheckpointTimeline.test.ts | 437 +++++++++++++++++- .../test/browser/chatEditingService.test.ts | 2 +- 17 files changed, 575 insertions(+), 106 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 10bbc7de8a8..c918ac82755 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -119,7 +119,6 @@ export interface IChatCodeBlockInfo { readonly uri: URI | undefined; readonly uriPromise: Promise; codemapperUri: URI | undefined; - readonly isStreaming: boolean; readonly chatSessionId: string; focus(): void; readonly languageId?: string | undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 7e34977acaf..317514614e9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -16,7 +16,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Lazy } from '../../../../../base/common/lazy.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; -import { autorun, IObservable } from '../../../../../base/common/observable.js'; +import { autorun, autorunSelfDisposable, derived, observableValue } from '../../../../../base/common/observable.js'; import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js'; import { equalsIgnoreCase } from '../../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; @@ -201,7 +201,6 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP readonly ownerMarkdownPartId = ownerMarkdownPartId; readonly codeBlockIndex = globalIndex; readonly elementId = element.id; - readonly isStreaming = false; readonly chatSessionId = element.sessionId; readonly languageId = languageId; readonly editDeltaInfo = EditDeltaInfo.fromText(text); @@ -221,7 +220,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP return ref.object.element; } else { const requestId = isRequestVM(element) ? element.id : element.requestId; - const ref = this.renderCodeBlockPill(element.sessionId, requestId, inUndoStop, codeBlockInfo.codemapperUri, !isCodeBlockComplete); + const ref = this.renderCodeBlockPill(element.sessionId, requestId, inUndoStop, codeBlockInfo.codemapperUri); if (isResponseVM(codeBlockInfo.element)) { // TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => { @@ -236,7 +235,6 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP readonly ownerMarkdownPartId = ownerMarkdownPartId; readonly codeBlockIndex = globalIndex; readonly elementId = element.id; - readonly isStreaming = !isCodeBlockComplete; readonly codemapperUri = codeblockEntry?.codemapperUri; readonly chatSessionId = element.sessionId; get uri() { @@ -324,10 +322,10 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP } } - private renderCodeBlockPill(sessionId: string, requestId: string, inUndoStop: string | undefined, codemapperUri: URI | undefined, isStreaming: boolean): IDisposableReference { + private renderCodeBlockPill(sessionId: string, requestId: string, inUndoStop: string | undefined, codemapperUri: URI | undefined): IDisposableReference { const codeBlock = this.instantiationService.createInstance(CollapsedCodeBlock, sessionId, requestId, inUndoStop); if (codemapperUri) { - codeBlock.render(codemapperUri, isStreaming); + codeBlock.render(codemapperUri); } return { object: codeBlock, @@ -354,7 +352,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP hasSameContent(other: IChatProgressRenderableResponseContent): boolean { return other.kind === 'markdownContent' && !!(other.content.value === this.markdown.content.value - || this.codeblocks.at(-1)?.isStreaming && this.codeblocks.at(-1)?.codemapperUri !== undefined && other.content.value.lastIndexOf('```') === this.markdown.content.value.lastIndexOf('```')); + || this.codeblocks.at(-1)?.codemapperUri !== undefined && other.content.value.lastIndexOf('```') === this.markdown.content.value.lastIndexOf('```')); } layout(width: number): void { @@ -364,7 +362,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP } else if (ref.object instanceof CollapsedCodeBlock) { const codeblockModel = this.codeblocks[index]; if (codeblockModel.codemapperUri && ref.object.uri?.toString() !== codeblockModel.codemapperUri.toString()) { - ref.object.render(codeblockModel.codemapperUri, codeblockModel.isStreaming); + ref.object.render(codeblockModel.codemapperUri); } } }); @@ -494,7 +492,11 @@ export class CollapsedCodeBlock extends Disposable { } } - render(uri: URI, isStreaming?: boolean): void { + /** + * @param uri URI of the file on-disk being changed + * @param isStreaming Whether the edit has completed (at the time of this being rendered) + */ + render(uri: URI): void { this.progressStore.clear(); this._uri = uri; @@ -502,34 +504,61 @@ export class CollapsedCodeBlock extends Disposable { const session = this.chatService.getSession(this.sessionId); const iconText = this.labelService.getUriBasenameLabel(uri); - let editSession = session?.editingSessionObs?.promiseResult.get()?.data; - let modifiedEntry = editSession?.getEntry(uri); - let modifiedByResponse = modifiedEntry?.isCurrentlyBeingModifiedBy.get(); - const isComplete = !modifiedByResponse || modifiedByResponse.requestId !== this.requestId; - - let iconClasses: string[] = []; - if (isStreaming || !isComplete) { - const codicon = ThemeIcon.modify(Codicon.loading, 'spin'); - iconClasses = ThemeIcon.asClassNameArray(codicon); - } else { - const fileKind = uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; - iconClasses = getIconClasses(this.modelService, this.languageService, uri, fileKind); - } - const iconEl = dom.$('span.icon'); - iconEl.classList.add(...iconClasses); - const children = [dom.$('span.icon-label', {}, iconText)]; const labelDetail = dom.$('span.label-detail', {}, ''); children.push(labelDetail); - if (isStreaming) { - labelDetail.textContent = localize('chat.codeblock.generating', "Generating edits..."); - } this.element.replaceChildren(iconEl, ...children); this.updateTooltip(this.labelService.getUriLabel(uri, { relative: false })); - const renderDiff = (changes: IEditSessionEntryDiff | undefined) => { + const editSessionObservable = session?.editingSessionObs?.promiseResult.map(r => r?.data) || observableValue(this, undefined); + const editSessionEntry = editSessionObservable.map((es, r) => es?.readEntry(uri, r)); + + const diffObservable = derived(reader => { + const entry = editSessionEntry.read(reader); + const editSession = entry && editSessionObservable.read(reader); + return entry && editSession && editSession.getEntryDiffBetweenStops(entry.modifiedURI, this.requestId, this.inUndoStop); + }).map((d, r) => d?.read(r)); + + const isStreaming = derived(r => { + const entry = editSessionEntry.read(r); + const currentlyModified = entry?.isCurrentlyBeingModifiedBy.read(r); + return !!currentlyModified && currentlyModified.responseModel.requestId === this.requestId && currentlyModified.undoStopId === this.inUndoStop; + }); + + // Set the icon/classes while edits are streaming + let iconClasses: string[] = []; + this.progressStore.add(autorun(r => { + iconEl.classList.remove(...iconClasses); + if (isStreaming.read(r)) { + const codicon = ThemeIcon.modify(Codicon.loading, 'spin'); + iconClasses = ThemeIcon.asClassNameArray(codicon); + } else { + iconEl.classList.remove(...iconClasses); + const fileKind = uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; + iconEl.classList.add(...getIconClasses(this.modelService, this.languageService, uri, fileKind)); + } + })); + + // Set the label detail for streaming progress + this.progressStore.add(autorun(r => { + if (isStreaming.read(r)) { + const entry = editSessionEntry.read(r); + const rwRatio = Math.floor((entry?.rewriteRatio.read(r) || 0) * 100); + labelDetail.textContent = rwRatio === 0 || !rwRatio ? localize('chat.codeblock.generating', "Generating edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)..."); + } else { + labelDetail.textContent = ''; + } + })); + + // Render the +/- diff + this.progressStore.add(autorunSelfDisposable(r => { + const changes = diffObservable.read(r); + if (changes === undefined) { + return; + } + const labelAdded = this.element.querySelector('.label-added') ?? this.element.appendChild(dom.$('span.label-added')); const labelRemoved = this.element.querySelector('.label-removed') ?? this.element.appendChild(dom.$('span.label-removed')); if (changes && !changes?.identical && !changes?.quitEarly) { @@ -541,42 +570,11 @@ export class CollapsedCodeBlock extends Disposable { const summary = localize('summary', 'Edited {0}, {1}, {2}', iconText, insertionsFragment, deletionsFragment); this.element.ariaLabel = summary; this.updateTooltip(summary); - } - }; - - let diffBetweenStops: IObservable | undefined; - - // Show a percentage progress that is driven by the rewrite - this.progressStore.add(autorun(r => { - if (!editSession) { - editSession = session?.editingSessionObs?.promiseResult.read(r)?.data; - modifiedEntry = editSession?.getEntry(uri); - } - modifiedByResponse = modifiedEntry?.isCurrentlyBeingModifiedBy.read(r); - let diffValue = diffBetweenStops?.read(r); - const isComplete = !!diffValue || !modifiedByResponse || modifiedByResponse.requestId !== this.requestId; - const rewriteRatio = modifiedEntry?.rewriteRatio.read(r); - - if (!isStreaming && !isComplete) { - const value = rewriteRatio; - labelDetail.textContent = value === 0 || !value ? localize('chat.codeblock.generating', "Generating edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)...", Math.round(value * 100)); - } else if (!isStreaming && isComplete) { - iconEl.classList.remove(...iconClasses); - const fileKind = uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; - iconEl.classList.add(...getIconClasses(this.modelService, this.languageService, uri, fileKind)); - labelDetail.textContent = ''; - } - - if (!diffBetweenStops) { - diffBetweenStops = modifiedEntry && editSession - ? editSession.getEntryDiffBetweenStops(modifiedEntry.modifiedURI, this.requestId, this.inUndoStop) - : undefined; - diffValue = diffBetweenStops?.read(r); - } - - if (!isStreaming && isComplete) { - renderDiff(diffValue); + // No need to keep updating once we get the diff info + if (changes.isFinal) { + r.dispose(); + } } })); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index b8ec66f0193..48ead9b0525 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -207,6 +207,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent item.diff = { originalURI: resource.originalUri, modifiedURI: resource.modifiedUri, + isFinal: true, quitEarly: false, identical: false, added: resource.added || 0, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts index 9d187b52e5d..9c2d9ceb643 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatInputOutputMarkdownProgressPart.ts @@ -78,7 +78,6 @@ export class ChatInputOutputMarkdownProgressPart extends BaseChatToolInvocationS codemapperUri: undefined, elementId: context.element.id, focus: () => { }, - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, chatSessionId: context.element.sessionId, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 2a1fa388f0e..191fd0c7f8b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -165,7 +165,6 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS codemapperUri: undefined, elementId: this.context.element.id, focus: () => editor.object.focus(), - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, uriPromise: Promise.resolve(model.uri), diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index fb66e426394..d0da2f51657 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -209,7 +209,6 @@ export class ToolConfirmationSubPart extends AbstractToolConfirmationSubPart { codemapperUri: undefined, elementId: this.context.element.id, focus: () => editor.object.focus(), - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, uriPromise: Promise.resolve(model.uri), diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts index cee19f1b0ca..d795bdacae7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts @@ -116,7 +116,6 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio codemapperUri: undefined, elementId: this.context.element.id, focus: () => { }, - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, chatSessionId: this.context.element.sessionId, @@ -149,7 +148,6 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio codemapperUri: undefined, elementId: this.context.element.id, focus: () => { }, - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, chatSessionId: this.context.element.sessionId, @@ -194,7 +192,6 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio codemapperUri: undefined, elementId: this.context.element.id, focus: () => { }, - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, chatSessionId: this.context.element.sessionId, @@ -227,7 +224,6 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio codemapperUri: undefined, elementId: this.context.element.id, focus: () => { }, - isStreaming: false, ownerMarkdownPartId: this.codeblocksPartId, uri: model.uri, chatSessionId: this.context.element.sessionId, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts index 8a98a060612..20ba3681da5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimeline.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from '../../../../../base/common/buffer.js'; +import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { IObservable, ITransaction } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { IEditSessionEntryDiff } from '../../common/chatEditingService.js'; @@ -36,6 +37,7 @@ export interface IChatEditingCheckpointTimeline { // State reconstruction getContentURIAtStop(requestId: string, fileURI: URI, stopId: string | undefined): URI; getContentAtStop(requestId: string, contentURI: URI, stopId: string | undefined): Promise; + onDidChangeContentsAtStop(requestId: string, contentURI: URI, stopId: string | undefined, callback: (data: string) => void): IDisposable; // Persistence getStateForPersistence(): IChatEditingTimelineState; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index df940a91cb8..f9f5bc4c84b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -6,7 +6,9 @@ import { equals as arraysEqual } from '../../../../../base/common/arrays.js'; import { findLast, findLastIdx } from '../../../../../base/common/arraysFind.js'; import { assertNever } from '../../../../../base/common/assert.js'; -import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ThrottledDelayer } from '../../../../../base/common/async.js'; +import { Event } from '../../../../../base/common/event.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { equals as objectsEqual } from '../../../../../base/common/objects.js'; import { derived, derivedOpts, IObservable, IReader, ITransaction, ObservablePromise, observableSignalFromEvent, observableValue, observableValueOpts, transaction } from '../../../../../base/common/observable.js'; @@ -60,7 +62,7 @@ export interface IChatEditingTimelineFsDelegate { * - _currentEpoch being equal to the epoch of an operation means that * operation is _not_ currently applied */ -export class ChatEditingCheckpointTimelineImpl extends Disposable implements IChatEditingCheckpointTimeline { +export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpointTimeline { private _epochCounter = 0; private readonly _checkpoints = observableValue(this, []); @@ -153,9 +155,6 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - super(); - - // Create initial checkpoint this.createCheckpoint(undefined, undefined, 'Initial State', 'Starting point before any edits'); } @@ -294,6 +293,42 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh return replayed.exists ? replayed.content : undefined; } + /** + * Creates a callback that is invoked when data at the stop changes. This + * will not fire initially and may be debounced internally. + */ + public onDidChangeContentsAtStop(requestId: string, contentURI: URI, stopId: string | undefined, callback: (data: string) => void): IDisposable { + // The only case where we have data that updates is if we have an epoch pointer that's + // after our know epochs (e.g. pointing to the end file state after all operations). + // If this isn't the case, abort. + if (!stopId || !stopId.startsWith(STOP_ID_EPOCH_PREFIX)) { + return Disposable.None; + } + + const target = Number(stopId.slice(STOP_ID_EPOCH_PREFIX.length)); + if (target <= this._epochCounter) { + return Disposable.None; // already finalized + } + + const store = new DisposableStore(); + const scheduler = store.add(new ThrottledDelayer(500)); + + store.add(Event.fromObservableLight(this._operations)(() => { + scheduler.trigger(async () => { + if (this._operations.get().at(-1)?.epoch! >= target) { + store.dispose(); + } + + const content = await this.getContentAtStop(requestId, contentURI, stopId); + if (content !== undefined) { + callback(content); + } + }); + })); + + return store; + } + private _getCheckpointBeforeEpoch(epoch: number, reader?: IReader) { return findLast(this._checkpoints.read(reader), c => c.epoch <= epoch); } @@ -628,7 +663,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } public getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable { - const epochs = derivedOpts<{ start: ICheckpoint | undefined; end: ICheckpoint | undefined }>({ equalsFn: (a, b) => a.start === b.start && a.end === b.end }, reader => { + const epochs = derivedOpts<{ start: ICheckpoint; end: ICheckpoint | undefined }>({ equalsFn: (a, b) => a.start === b.start && a.end === b.end }, reader => { const checkpoints = this._checkpoints.read(reader); const startIndex = checkpoints.findIndex(c => c.requestId === requestId && c.undoStopId === stopId); return { start: checkpoints[startIndex], end: checkpoints[startIndex + 1] }; @@ -638,7 +673,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh } public getEntryDiffBetweenRequests(uri: URI, startRequestId: string, stopRequestId: string): IObservable { - const epochs = derivedOpts<{ start: ICheckpoint | undefined; end: ICheckpoint | undefined }>({ equalsFn: (a, b) => a.start === b.start && a.end === b.end }, reader => { + const epochs = derivedOpts<{ start: ICheckpoint; end: ICheckpoint | undefined }>({ equalsFn: (a, b) => a.start === b.start && a.end === b.end }, reader => { const checkpoints = this._checkpoints.read(reader); const startIndex = checkpoints.findIndex(c => c.requestId === startRequestId); const start = startIndex === -1 ? checkpoints[0] : checkpoints[startIndex]; @@ -665,7 +700,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh refs.forEach(r => store.add(r)); } - return refs; + return { refs, isFinal: !!end }; }); return new ObservablePromise(promise); @@ -673,21 +708,26 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh const resolvedModels = derived(reader => { const refs2 = modelRefsPromise.read(reader)?.promiseResult.read(reader); - return refs2?.data?.map(r => ({ - model: r.object.textEditorModel, - onChange: observableSignalFromEvent(this, r.object.textEditorModel.onDidChangeContent.bind(r.object.textEditorModel)), - })); + return refs2?.data && { + isFinal: refs2.data.isFinal, + refs: refs2.data.refs.map(r => ({ + model: r.object.textEditorModel, + onChange: observableSignalFromEvent(this, r.object.textEditorModel.onDidChangeContent.bind(r.object.textEditorModel)), + })), + }; }); const diff = derived((reader): ObservablePromise | undefined => { - const models = resolvedModels.read(reader); - if (!models) { + const modelsData = resolvedModels.read(reader); + if (!modelsData) { return; } - models.forEach(m => m.onChange.read(reader)); // re-read when contents change + const { refs, isFinal } = modelsData; + + refs.forEach(m => m.onChange.read(reader)); // re-read when contents change - const promise = this._computeDiff(models[0].model.uri, models[1].model.uri); + const promise = this._computeDiff(refs[0].model.uri, refs[1].model.uri, isFinal); return new ObservablePromise(promise); }); @@ -696,7 +736,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh }); } - private _computeDiff(originalUri: URI, modifiedUri: URI): Promise { + private _computeDiff(originalUri: URI, modifiedUri: URI, isFinal: boolean): Promise { return this._editorWorkerService.computeDiff( originalUri, modifiedUri, @@ -707,6 +747,7 @@ export class ChatEditingCheckpointTimelineImpl extends Disposable implements ICh originalURI: originalUri, modifiedURI: modifiedUri, identical: !!diff?.identical, + isFinal, quitEarly: !diff || diff.quitEarly, added: 0, removed: 0, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 96d81480226..578145b205a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -233,7 +233,6 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie this._stateObs.set(ModifiedFileEntryState.Modified, tx); if (!isLastEdits) { - this._isCurrentlyBeingModifiedByObs.set(responseModel, tx); this._rewriteRatioObs.set(result.rewriteRatio, tx); } else { this._resetEditsState(tx); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index d2804012c9b..23253994e5e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -57,8 +57,8 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im protected readonly _waitsForLastEdits = observableValue(this, false); readonly waitsForLastEdits: IObservable = this._waitsForLastEdits; - protected readonly _isCurrentlyBeingModifiedByObs = observableValue(this, undefined); - readonly isCurrentlyBeingModifiedBy: IObservable = this._isCurrentlyBeingModifiedByObs; + protected readonly _isCurrentlyBeingModifiedByObs = observableValue<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined>(this, undefined); + readonly isCurrentlyBeingModifiedBy: IObservable<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined> = this._isCurrentlyBeingModifiedByObs; protected readonly _lastModifyingResponseObs = observableValueOpts({ equalsFn: (a, b) => a?.requestId === b?.requestId }, undefined); readonly lastModifyingResponse: IObservable = this._lastModifyingResponseObs; @@ -300,9 +300,9 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im abstract readonly changesCount: IObservable; - acceptStreamingEditsStart(responseModel: IChatResponseModel, tx: ITransaction) { + acceptStreamingEditsStart(responseModel: IChatResponseModel, undoStopId: string | undefined, tx: ITransaction) { this._resetEditsState(tx); - this._isCurrentlyBeingModifiedByObs.set(responseModel, tx); + this._isCurrentlyBeingModifiedByObs.set({ responseModel, undoStopId }, tx); this._lastModifyingResponseObs.set(responseModel, tx); this._autoAcceptCtrl.get()?.cancel(); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index 0ecca5ae333..d90b658c4c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -619,7 +619,6 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie transaction((tx) => { this._stateObs.set(ModifiedFileEntryState.Modified, tx); - this._isCurrentlyBeingModifiedByObs.set(responseModel, tx); if (!isLastEdits) { const newRewriteRation = Math.max(this._rewriteRatioObs.get(), calculateNotebookRewriteRatio(this._cellsDiffInfo.get(), this.originalModel, this.modifiedModel)); this._rewriteRatioObs.set(Math.min(1, newRewriteRation), tx); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index cfb451d2668..dc3f7348e8a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -9,7 +9,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Iterable } from '../../../../../base/common/iterator.js'; -import { Disposable, dispose } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, dispose } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { autorun, IObservable, IReader, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { isEqual } from '../../../../../base/common/resources.js'; @@ -266,7 +266,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } const contentStr = typeof content === 'string' ? content : content.toString(); - return this._modelService.createModel(contentStr, this._languageService.createByFilepathOrFirstLine(snapshotUri), snapshotUri, false); + const model = this._modelService.createModel(contentStr, this._languageService.createByFilepathOrFirstLine(snapshotUri), snapshotUri, false); + + const store = new DisposableStore(); + store.add(model.onWillDispose(() => store.dispose())); + store.add(this._timeline.onDidChangeContentsAtStop(requestId, snapshotUri, undoStop, c => model.setValue(c))); + + return model; } public getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined { @@ -506,7 +512,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio transaction((tx) => { this._state.set(ChatEditingSessionState.StreamingEdits, tx); - entry.acceptStreamingEditsStart(responseModel, tx); + entry.acceptStreamingEditsStart(responseModel, undoStop, tx); // Note: Individual edit operations will be recorded by the file entries }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts index 4d7726defd0..c3f03f08eac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingModifiedNotebookDiff.ts @@ -62,6 +62,7 @@ export class ChatEditingModifiedNotebookDiff { removed, identical: added === 0 && removed === 0, quitEarly: false, + isFinal: true, modifiedURI: this.modified.snapshotUri, originalURI: this.original.snapshotUri, }; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 754ccc5ce2b..4f650cedee6 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -113,7 +113,7 @@ export interface IChatEditingSession extends IDisposable { accept(...uris: URI[]): Promise; reject(...uris: URI[]): Promise; getEntry(uri: URI): IModifiedFileEntry | undefined; - readEntry(uri: URI, reader?: IReader): IModifiedFileEntry | undefined; + readEntry(uri: URI, reader: IReader): IModifiedFileEntry | undefined; restoreSnapshot(requestId: string, stopId: string | undefined): Promise; @@ -167,6 +167,9 @@ export interface IEditSessionEntryDiff { quitEarly: boolean; identical: boolean; + /** True if nothing else will be added to this diff. */ + isFinal: boolean; + /** Added data (e.g. line numbers) to show in the UI */ added: number; /** Removed data (e.g. line numbers) to show in the UI */ @@ -243,7 +246,7 @@ export interface IModifiedFileEntry { readonly lastModifyingRequestId: string; readonly state: IObservable; - readonly isCurrentlyBeingModifiedBy: IObservable; + readonly isCurrentlyBeingModifiedBy: IObservable<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined>; readonly lastModifyingResponse: IObservable; readonly rewriteRatio: IObservable; diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts index 74d4adc71d3..9bc7a25b9ae 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts @@ -105,7 +105,7 @@ suite('ChatEditingCheckpointTimeline', function () { collection.set(INotebookService, new SyncDescriptor(TestNotebookService)); const insta = store.add(workbenchInstantiationService(undefined, store).createChild(collection)); - timeline = store.add(insta.createInstance(ChatEditingCheckpointTimelineImpl, 'test-session', fileDelegate)); + timeline = insta.createInstance(ChatEditingCheckpointTimelineImpl, 'test-session', fileDelegate); }); teardown(() => { @@ -474,11 +474,11 @@ suite('ChatEditingCheckpointTimeline', function () { collection.set(INotebookService, new SyncDescriptor(TestNotebookService)); const insta = store.add(workbenchInstantiationService(undefined, store).createChild(collection)); - const newTimeline = store.add(insta.createInstance( + const newTimeline = insta.createInstance( ChatEditingCheckpointTimelineImpl, 'test-session-2', fileDelegate - )); + ); transaction(tx => { newTimeline.restoreFromState(savedState, tx); @@ -490,8 +490,6 @@ suite('ChatEditingCheckpointTimeline', function () { assert.strictEqual(restoredState.operations.length, savedState.operations.length); assert.strictEqual(restoredState.currentEpoch, savedState.currentEpoch); assert.strictEqual(restoredState.epochCounter, savedState.epochCounter); - - newTimeline.dispose(); }); test('navigating between multiple requests', async function () { @@ -627,6 +625,434 @@ suite('ChatEditingCheckpointTimeline', function () { assert.strictEqual(operations[0].epoch, epoch1); assert.strictEqual(operations[1].epoch, epoch2); }); + + test('navigateToCheckpoint throws error for invalid checkpoint ID', async function () { + let errorThrown = false; + try { + await timeline.navigateToCheckpoint('invalid-checkpoint-id'); + } catch (error) { + errorThrown = true; + assert.ok(error instanceof Error); + assert.ok((error as Error).message.includes('not found')); + } + assert.ok(errorThrown, 'Expected error to be thrown'); + }); + + test('navigateToCheckpoint does nothing when already at target epoch', async function () { + const uri = URI.parse('file:///test.txt'); + + // Record baseline and operation + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + const createEpoch = timeline.incrementEpoch(); + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + createEpoch, + [{ range: new Range(1, 1, 1, 8), text: 'modified' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'Checkpoint'); + + // Navigate to checkpoint + const checkpointId = timeline.getCheckpointIdForRequest('req1', 'stop1')!; + await timeline.navigateToCheckpoint(checkpointId); + + // Navigate again to same checkpoint - should be a no-op + const stateBefore = timeline.getStateForPersistence(); + await timeline.navigateToCheckpoint(checkpointId); + const stateAfter = timeline.getStateForPersistence(); + + assert.strictEqual(stateBefore.currentEpoch, stateAfter.currentEpoch); + }); + + test('recording operation after undo truncates future history', async function () { + const uri = URI.parse('file:///test.txt'); + + // Setup initial operations + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 8), text: 'edit1' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'Edit 1'); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 6), text: 'edit2' }] + )); + + timeline.createCheckpoint('req1', 'stop2', 'Edit 2'); + + const stateWithTwoEdits = timeline.getStateForPersistence(); + assert.strictEqual(stateWithTwoEdits.operations.length, 2); + + // Undo to stop1 + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'stop1')!); + + // Record new operation - this should truncate the second edit + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 6), text: 'edit3' }] + )); + + const stateAfterNewEdit = timeline.getStateForPersistence(); + assert.strictEqual(stateAfterNewEdit.operations.length, 2); + assert.strictEqual(stateAfterNewEdit.operations[1].type, FileOperationType.TextEdit); + // The second operation should be the new edit3, not edit2 + }); + + test('redo after recording new operation should work', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 8), text: 'edit1' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'Edit 1'); + + // Undo + await timeline.undoToLastCheckpoint(); + assert.strictEqual(timeline.canRedo.get(), true); + + // Redo + await timeline.redoToNextCheckpoint(); + + // After redo, canRedo depends on whether we're at the latest epoch + // Since we created a checkpoint after the operation, currentEpoch is ahead + // of the checkpoint epoch, so canRedo may still be true + assert.strictEqual(timeline.canUndo.get(), true); + }); + + test('redo when there is no checkpoint after operation', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + // Record operation but don't create checkpoint after it + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 8), text: 'edit1' }] + )); + + // Undo to start + const startCheckpoint = timeline.getCheckpointIdForRequest('req1', undefined)!; + await timeline.navigateToCheckpoint(startCheckpoint); + + // Should be able to redo even without a checkpoint after the operation + assert.strictEqual(timeline.canRedo.get(), true); + + await timeline.redoToNextCheckpoint(); + // After redo, we should be at the operation's epoch + 1 + const state = timeline.getStateForPersistence(); + assert.ok(state.currentEpoch > 1); + }); + + test('getContentAtStop returns empty for non-existent file', async function () { + const uri = URI.parse('file:///nonexistent.txt'); + const content = await timeline.getContentAtStop('req1', uri, 'stop1'); + + assert.strictEqual(content, ''); + }); + + test('getContentAtStop with epoch-based stopId', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + const editEpoch = timeline.incrementEpoch(); + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + editEpoch, + [{ range: new Range(1, 1, 1, 8), text: 'modified' }] + )); + + // Use epoch-based stop ID + const content = await timeline.getContentAtStop('req1', uri, `__epoch_${editEpoch + 1}`); + + assert.ok(content); + assert.strictEqual(content, 'modified'); + }); + + test('hasFileBaseline correctly reports baseline existence', function () { + const uri = URI.parse('file:///test.txt'); + + assert.strictEqual(timeline.hasFileBaseline(uri, 'req1'), false); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + assert.strictEqual(timeline.hasFileBaseline(uri, 'req1'), true); + assert.strictEqual(timeline.hasFileBaseline(uri, 'req2'), false); + }); + + test('multiple text edits to same file are properly replayed', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'line1\nline2\nline3', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', undefined, 'Start'); + + // First edit - uppercase line 1 + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 6), text: 'LINE1' }] + )); + + // Second edit - uppercase line 2 + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(2, 1, 2, 6), text: 'LINE2' }] + )); + + // Third edit - uppercase line 3 + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(3, 1, 3, 6), text: 'LINE3' }] + )); + + timeline.createCheckpoint('req1', 'all-edits', 'All edits'); + + // Navigate to see all edits applied + const initialCheckpoint = timeline.getStateForPersistence().checkpoints[0]; + await timeline.navigateToCheckpoint(initialCheckpoint.checkpointId); + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'all-edits')!); + + assert.strictEqual(fileContents.get(uri), 'LINE1\nLINE2\nLINE3'); + }); + + test('checkpoint with same requestId and undoStopId is not duplicated', function () { + timeline.createCheckpoint('req1', 'stop1', 'First'); + timeline.createCheckpoint('req1', 'stop1', 'Second'); // Should be ignored + + const checkpoints = timeline.getStateForPersistence().checkpoints; + const req1Stop1Checkpoints = checkpoints.filter(c => c.requestId === 'req1' && c.undoStopId === 'stop1'); + + assert.strictEqual(req1Stop1Checkpoints.length, 1); + assert.strictEqual(req1Stop1Checkpoints[0].label, 'First'); + }); + + test('finding baseline after file rename operation', async function () { + const oldUri = URI.parse('file:///old.txt'); + const newUri = URI.parse('file:///new.txt'); + + // Create baseline for old URI + timeline.recordFileBaseline(upcastPartial({ + uri: oldUri, + requestId: 'req1', + content: 'initial content', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + // Edit the file before rename (replace entire content) + timeline.recordFileOperation(createTextEditOperation( + oldUri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 16), text: 'modified content' }] + )); + + // Rename operation + timeline.recordFileOperation(createFileRenameOperation( + oldUri, + newUri, + 'req1', + timeline.incrementEpoch() + )); + + timeline.createCheckpoint('req1', 'renamed', 'After rename'); + + // Get content at the renamed URI - should find the baseline through rename chain + const content = await timeline.getContentAtStop('req1', newUri, 'renamed'); + assert.strictEqual(content, 'modified content'); + }); + + test('baseline lookup across different request IDs', async function () { + const uri = URI.parse('file:///test.txt'); + + // First request baseline + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'req1 content', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 13), text: 'req1 modified' }] + )); + + // Second request baseline + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req2', + content: 'req2 content', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req2', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 13), text: 'req2 modified' }] + )); + + timeline.createCheckpoint('req2', 'stop1', 'Req2 checkpoint'); + + // Getting content should use req2 baseline + const content = await timeline.getContentAtStop('req2', uri, 'stop1'); + assert.strictEqual(content, 'req2 modified'); + }); + + test('undoing entire request when no operations between start and checkpoint', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'initial', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + // Create start checkpoint + timeline.createCheckpoint('req1', undefined, 'Start of Request'); + + // Create end checkpoint without any operations in between + timeline.createCheckpoint('req1', 'stop1', 'End of Request'); + + // At this point, we're past both checkpoints. _willUndoToCheckpoint finds + // the previous checkpoint before the current epoch. Since there are no operations + // between the start and stop1, undoing should target the start. + const undoTarget = timeline['_willUndoToCheckpoint'].get(); + + // The logic checks if there are operations between start and the previous checkpoint. + // If not, it returns the start checkpoint. However, if currentEpoch is beyond + // the last checkpoint and there are no operations, undoTarget might be undefined + // or it might be the start checkpoint. Let's just verify the behavior. + if (undoTarget) { + assert.strictEqual(undoTarget.undoStopId, undefined); // Should target the start checkpoint + } + }); + + test('getContentAtStop with file that does not exist in operations', async function () { + const uri = URI.parse('file:///test.txt'); + + timeline.recordFileBaseline(upcastPartial({ + uri, + requestId: 'req1', + content: 'content', + epoch: timeline.incrementEpoch(), + telemetryInfo: DEFAULT_TELEMETRY_INFO + })); + + timeline.createCheckpoint('req1', 'stop1', 'Checkpoint'); + + // Try to get content for a different URI that doesn't have any operations + const differentUri = URI.parse('file:///different.txt'); + const content = await timeline.getContentAtStop('req1', differentUri, 'stop1'); + + assert.strictEqual(content, ''); + }); + + test('undoToLastCheckpoint when canUndo is false does nothing', async function () { + // At initial state, canUndo should be false + assert.strictEqual(timeline.canUndo.get(), false); + + const stateBefore = timeline.getStateForPersistence(); + await timeline.undoToLastCheckpoint(); + const stateAfter = timeline.getStateForPersistence(); + + // Should not have changed + assert.strictEqual(stateBefore.currentEpoch, stateAfter.currentEpoch); + }); + + test('redoToNextCheckpoint when canRedo is false does nothing', async function () { + // At initial state with no future operations, canRedo should be false + assert.strictEqual(timeline.canRedo.get(), false); + + const stateBefore = timeline.getStateForPersistence(); + await timeline.redoToNextCheckpoint(); + const stateAfter = timeline.getStateForPersistence(); + + // Should not have changed + assert.strictEqual(stateBefore.currentEpoch, stateAfter.currentEpoch); + }); }); // Mock notebook service for tests that don't need notebook functionality @@ -635,3 +1061,4 @@ class TestNotebookService { hasSupportedNotebooks() { return false; } } + diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts index 7c13b19fd7e..f7c668fe95a 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts @@ -174,7 +174,7 @@ suite('ChatEditingService', function () { assert.ok(isEqual(entry.modifiedURI, uri)); await waitForState(entry.isCurrentlyBeingModifiedBy.map(value => value === chatRequest.response)); - assert.ok(entry.isCurrentlyBeingModifiedBy.get() === chatRequest.response); + assert.ok(entry.isCurrentlyBeingModifiedBy.get()?.responseModel === chatRequest.response); const unset = waitForState(entry.isCurrentlyBeingModifiedBy.map(res => res === undefined)); From 3fff78b18aa9f125868f831b4ab5ab26c13de402 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 27 Oct 2025 23:37:10 +0100 Subject: [PATCH 1749/4355] Graph - add command to compare references (#273635) --- extensions/git/package.json | 15 +++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 59 +++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index 9ff385ce77f..bc8b9d3d37b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -514,6 +514,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.graph.compareBranches", + "title": "%command.graphCompareBranches%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.deleteRemoteBranch", "title": "%command.deleteRemoteBranch%", @@ -1594,6 +1600,10 @@ "command": "git.graph.deleteBranch", "when": "false" }, + { + "command": "git.graph.compareBranches", + "when": "false" + }, { "command": "git.graph.deleteTag", "when": "false" @@ -2265,6 +2275,11 @@ "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\/|^refs\\/remotes\\//", "group": "2_branch@2" }, + { + "command": "git.graph.compareBranches", + "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\//", + "group": "2_branch@3" + }, { "command": "git.graph.deleteTag", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index ae4159e566d..dd41ad67f38 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -135,6 +135,7 @@ "command.graphCheckout": "Checkout", "command.graphCheckoutDetached": "Checkout (Detached)", "command.graphCherryPick": "Cherry Pick", + "command.graphCompareBranches": "Compare Branches...", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c57ae4606c0..4a59b05ee36 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3114,6 +3114,65 @@ export class CommandCenter { await this._deleteBranch(repository, undefined, undefined, { remote: true }); } + @command('git.graph.compareBranches', { repository: true }) + async compareBranches(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); + if (!historyItemRefId || !historyItemRef) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getBranchPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a source reference to compare to'); + const sourceRef = await this.pickRef(getBranchPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem)) { + return; + } + + if (historyItemRefId === sourceRef.refId) { + window.showInformationMessage(l10n.t('The selected references are the same.')); + return; + } + + try { + const changes = await repository.diffTrees(historyItemRefId, sourceRef.refId); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('The selected references have no differences.')); + return; + } + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRefId)); + + const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; + const multiDiffSourceUri = Uri.from({ + scheme: 'git-branch-compare', + path: `${repository.root}/${sourceRef.refId}..${historyItemRefId}` + }); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title, + resources + }); + } catch (err) { + throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); + } + } + private async _deleteBranch(repository: Repository, remote: string | undefined, name: string | undefined, options: { remote: boolean; force?: boolean }): Promise { let run: (force?: boolean) => Promise; From 5b788a89e9185d6183132c03fe02e05bbf5a9c87 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 27 Oct 2025 15:38:19 -0700 Subject: [PATCH 1750/4355] chat todo list: disable clear while request in progress; update styles and tests (#273643) * chat todo list: disable clear while request in progress; update styles and tests * Update src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../chatContentParts/chatTodoListWidget.ts | 79 ++++++++++++------- .../contrib/chat/browser/media/chat.css | 11 ++- .../test/browser/chatTodoListWidget.test.ts | 42 +++++----- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts index 00b084a36ba..1e899ffc48f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTodoListWidget.ts @@ -12,9 +12,11 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { localize } from '../../../../../nls.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { WorkbenchList } from '../../../../../platform/list/browser/listService.js'; import { IChatTodoListService, IChatTodo } from '../../common/chatTodoListService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { TodoListToolDescriptionFieldSettingId } from '../../common/tools/manageTodoListTool.js'; class TodoListDelegate implements IListVirtualDelegate { @@ -125,7 +127,7 @@ export class ChatTodoListWidget extends Disposable { private _isExpanded: boolean = false; private _userManuallyExpanded: boolean = false; - private expandoElement!: HTMLElement; + private expandoButton!: Button; private todoListContainer!: HTMLElement; private clearButtonContainer!: HTMLElement; private clearButton!: Button; @@ -135,11 +137,19 @@ export class ChatTodoListWidget extends Disposable { constructor( @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.domNode = this.createChatTodoWidget(); + + // Listen to context key changes to update clear button state when request state changes + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set([ChatContextKeys.requestInProgress.key]))) { + this.updateClearButtonState(); + } + })); } public get height(): number { @@ -150,11 +160,12 @@ export class ChatTodoListWidget extends Disposable { const container = dom.$('.chat-todo-list-widget'); container.style.display = 'none'; - this.expandoElement = dom.$('.todo-list-expand'); - this.expandoElement.setAttribute('role', 'button'); - this.expandoElement.setAttribute('aria-expanded', 'true'); - this.expandoElement.setAttribute('tabindex', '0'); - this.expandoElement.setAttribute('aria-controls', 'todo-list-container'); + const expandoContainer = dom.$('.todo-list-expand'); + this.expandoButton = this._register(new Button(expandoContainer, { + supportIcons: true + })); + this.expandoButton.element.setAttribute('aria-expanded', String(this._isExpanded)); + this.expandoButton.element.setAttribute('aria-controls', 'todo-list-container'); // Create title section to group icon and title const titleSection = dom.$('.todo-list-title-section'); @@ -174,8 +185,8 @@ export class ChatTodoListWidget extends Disposable { titleSection.appendChild(expandIcon); titleSection.appendChild(titleElement); - this.expandoElement.appendChild(titleSection); - this.expandoElement.appendChild(this.clearButtonContainer); + this.expandoButton.element.appendChild(titleSection); + this.expandoButton.element.appendChild(this.clearButtonContainer); this.todoListContainer = dom.$('.todo-list-container'); this.todoListContainer.style.display = this._isExpanded ? 'block' : 'none'; @@ -183,28 +194,19 @@ export class ChatTodoListWidget extends Disposable { this.todoListContainer.setAttribute('role', 'list'); this.todoListContainer.setAttribute('aria-labelledby', 'todo-list-title'); - container.appendChild(this.expandoElement); + container.appendChild(expandoContainer); container.appendChild(this.todoListContainer); - this._register(dom.addDisposableListener(this.expandoElement, 'click', () => { + this._register(this.expandoButton.onDidClick(() => { this.toggleExpanded(); })); - this._register(dom.addDisposableListener(this.expandoElement, 'keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - this.toggleExpanded(); - } - })); - return container; } private createClearButton(): void { this.clearButton = new Button(this.clearButtonContainer, { supportIcons: true, - title: localize('chat.todoList.clearButton', 'Clear all todos'), - ariaLabel: localize('chat.todoList.clearButton.ariaLabel', 'Clear all todos') }); this.clearButton.element.tabIndex = 0; this.clearButton.icon = Codicon.clearAll; @@ -262,7 +264,7 @@ export class ChatTodoListWidget extends Disposable { } private renderTodoList(todoList: IChatTodo[]): void { - const titleElement = this.expandoElement.querySelector('.todo-list-title') as HTMLElement; + const titleElement = this.expandoButton.element.querySelector('.todo-list-title') as HTMLElement; if (titleElement) { this.updateTitleElement(titleElement, todoList); } @@ -307,13 +309,16 @@ export class ChatTodoListWidget extends Disposable { const hasInProgressTask = todoList.some(todo => todo.status === 'in-progress'); const hasCompletedTask = todoList.some(todo => todo.status === 'completed'); + // Update clear button state based on request progress + this.updateClearButtonState(); + // Only auto-collapse if there are in-progress or completed tasks AND user hasn't manually expanded if ((hasInProgressTask || hasCompletedTask) && this._isExpanded && !this._userManuallyExpanded) { this._isExpanded = false; - this.expandoElement.setAttribute('aria-expanded', 'false'); + this.expandoButton.element.setAttribute('aria-expanded', 'false'); this.todoListContainer.style.display = 'none'; - const expandIcon = this.expandoElement.querySelector('.expand-icon') as HTMLElement; + const expandIcon = this.expandoButton.element.querySelector('.expand-icon') as HTMLElement; if (expandIcon) { expandIcon.classList.remove('codicon-chevron-down'); expandIcon.classList.add('codicon-chevron-right'); @@ -328,7 +333,7 @@ export class ChatTodoListWidget extends Disposable { this._isExpanded = !this._isExpanded; this._userManuallyExpanded = true; - const expandIcon = this.expandoElement.querySelector('.expand-icon') as HTMLElement; + const expandIcon = this.expandoButton.element.querySelector('.expand-icon') as HTMLElement; if (expandIcon) { expandIcon.classList.toggle('codicon-chevron-down', this._isExpanded); expandIcon.classList.toggle('codicon-chevron-right', !this._isExpanded); @@ -338,7 +343,7 @@ export class ChatTodoListWidget extends Disposable { if (this._currentSessionId) { const todoList = this.chatTodoListService.getTodos(this._currentSessionId); - const titleElement = this.expandoElement.querySelector('.todo-list-title') as HTMLElement; + const titleElement = this.expandoButton.element.querySelector('.todo-list-title') as HTMLElement; if (titleElement) { this.updateTitleElement(titleElement, todoList); } @@ -357,6 +362,26 @@ export class ChatTodoListWidget extends Disposable { this._onDidChangeHeight.fire(); } + private updateClearButtonState(): void { + if (!this._currentSessionId) { + return; + } + + const todoList = this.chatTodoListService.getTodos(this._currentSessionId); + const hasInProgressTask = todoList.some(todo => todo.status === 'in-progress'); + const isRequestInProgress = ChatContextKeys.requestInProgress.getValue(this.contextKeyService) ?? false; + const shouldDisable = isRequestInProgress && hasInProgressTask; + + this.clearButton.enabled = !shouldDisable; + + // Update tooltip based on state + if (shouldDisable) { + this.clearButton.setTitle(localize('chat.todoList.clearButton.disabled', 'Cannot clear todos while a task is in progress')); + } else { + this.clearButton.setTitle(localize('chat.todoList.clearButton', 'Clear all todos')); + } + } + private updateTitleElement(titleElement: HTMLElement, todoList: IChatTodo[]): void { titleElement.textContent = ''; @@ -371,8 +396,8 @@ export class ChatTodoListWidget extends Disposable { const expandButtonLabel = this._isExpanded ? localize('chat.todoList.collapseButton', 'Collapse Todos') : localize('chat.todoList.expandButton', 'Expand Todos'); - this.expandoElement.setAttribute('aria-label', expandButtonLabel); - this.expandoElement.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); + this.expandoButton.element.setAttribute('aria-label', expandButtonLabel); + this.expandoButton.element.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); if (this._isExpanded) { const titleText = dom.$('span'); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index e47600d6a4f..025533ab6e9 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1106,15 +1106,24 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand { + width: 100%; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .monaco-button { display: flex; align-items: center; gap: 4px; cursor: pointer; justify-content: space-between; width: 100%; + background-color: transparent; + border-color: transparent; + color: var(--vscode-foreground); + padding: 0; + min-width: unset; } -.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand:focus:not(:focus-visible) { +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand .monaco-button:focus:not(:focus-visible) { outline: none; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts index 0cafd836be0..7f6e637feaa 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTodoListWidget.test.ts @@ -10,14 +10,13 @@ import { ChatTodoListWidget } from '../../browser/chatContentParts/chatTodoListW import { IChatTodo, IChatTodoListService } from '../../common/chatTodoListService.js'; import { mainWindow } from '../../../../../base/browser/window.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; suite('ChatTodoListWidget Accessibility', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let widget: ChatTodoListWidget; - let mockTodoListService: IChatTodoListService; - let mockConfigurationService: IConfigurationService; const sampleTodos: IChatTodo[] = [ { id: 1, title: 'First task', status: 'not-started' }, @@ -27,7 +26,7 @@ suite('ChatTodoListWidget Accessibility', () => { setup(() => { // Mock the todo list service - mockTodoListService = { + const mockTodoListService: IChatTodoListService = { _serviceBrand: undefined, onDidUpdateTodos: Event.None, getTodos: (sessionId: string) => sampleTodos, @@ -35,14 +34,12 @@ suite('ChatTodoListWidget Accessibility', () => { }; // Mock the configuration service - // eslint-disable-next-line local/code-no-any-casts - mockConfigurationService = { - _serviceBrand: undefined, - getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined - } as any; + const mockConfigurationService = new TestConfigurationService({ 'chat.todoListTool.descriptionField': true }); const instantiationService = workbenchInstantiationService(undefined, store); - widget = store.add(new ChatTodoListWidget(mockTodoListService, mockConfigurationService, instantiationService)); + instantiationService.stub(IChatTodoListService, mockTodoListService); + instantiationService.stub(IConfigurationService, mockConfigurationService); + widget = store.add(instantiationService.createInstance(ChatTodoListWidget)); mainWindow.document.body.appendChild(widget.domNode); }); @@ -106,16 +103,17 @@ suite('ChatTodoListWidget Accessibility', () => { test('expand button has proper accessibility attributes', () => { widget.render('test-session'); - // The expandoElement has the accessibility attributes - const expandoElement = widget.domNode.querySelector('.todo-list-expand'); - assert.ok(expandoElement, 'Should have expando element'); - assert.strictEqual(expandoElement?.getAttribute('role'), 'button'); - assert.strictEqual(expandoElement?.getAttribute('tabindex'), '0'); - assert.strictEqual(expandoElement?.getAttribute('aria-expanded'), 'false'); // Should be collapsed due to in-progress task - assert.strictEqual(expandoElement?.getAttribute('aria-controls'), 'todo-list-container'); + // The expandoButton is now a Monaco Button, so we need to check its element + const expandoContainer = widget.domNode.querySelector('.todo-list-expand'); + assert.ok(expandoContainer, 'Should have expando container'); + + const expandoButton = expandoContainer?.querySelector('.monaco-button'); + assert.ok(expandoButton, 'Should have Monaco button'); + assert.strictEqual(expandoButton?.getAttribute('aria-expanded'), 'false'); // Should be collapsed due to in-progress task + assert.strictEqual(expandoButton?.getAttribute('aria-controls'), 'todo-list-container'); // The title element should have progress information - const titleElement = expandoElement?.querySelector('.todo-list-title'); + const titleElement = expandoButton?.querySelector('.todo-list-title'); assert.ok(titleElement, 'Should have title element'); const titleText = titleElement?.textContent; // When collapsed, title shows progress and current task: " (2/3) - Second task" @@ -159,14 +157,12 @@ suite('ChatTodoListWidget Accessibility', () => { setTodos: (sessionId: string, todos: IChatTodo[]) => { } }; - // eslint-disable-next-line local/code-no-any-casts - const emptyConfigurationService: IConfigurationService = { - _serviceBrand: undefined, - getValue: (key: string) => key === 'chat.todoListTool.descriptionField' ? true : undefined - } as any; + const emptyConfigurationService = new TestConfigurationService({ 'chat.todoListTool.descriptionField': true }); const instantiationService = workbenchInstantiationService(undefined, store); - const emptyWidget = store.add(new ChatTodoListWidget(emptyTodoListService, emptyConfigurationService, instantiationService)); + instantiationService.stub(IChatTodoListService, emptyTodoListService); + instantiationService.stub(IConfigurationService, emptyConfigurationService); + const emptyWidget = store.add(instantiationService.createInstance(ChatTodoListWidget)); mainWindow.document.body.appendChild(emptyWidget.domNode); emptyWidget.render('test-session'); From 5f33f4bb301061b1c7983373f2cab1e5ff759b80 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:05:32 -0700 Subject: [PATCH 1751/4355] Add support for `additionalProperties` in `TypeFromJsonSchema` --- src/vs/base/common/jsonSchema.ts | 11 +- .../common/contributedCustomEditors.ts | 14 +- .../customEditor/common/extensionPoint.ts | 133 +++++++++--------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index ac172de6d56..1eb750a7f38 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -102,8 +102,6 @@ export interface IJSONSchemaSnippet { /** * Converts a basic JSON schema to a TypeScript type. - * - * Doesn't support all JSON schema features, such as `additionalProperties`. */ export type TypeFromJsonSchema = // enum @@ -131,12 +129,12 @@ export type TypeFromJsonSchema = : T extends { type: 'object'; properties: infer P; required: infer RequiredList } ? { [K in keyof P]: IsRequired extends true ? TypeFromJsonSchema : TypeFromJsonSchema | undefined; - } + } & AdditionalPropertiesType // Object with no required properties. // All values are optional : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: TypeFromJsonSchema | undefined } + ? { [K in keyof P]: TypeFromJsonSchema | undefined } & AdditionalPropertiesType // Array : T extends { type: 'array'; items: infer I } @@ -166,6 +164,11 @@ type IsRequired = : false; +type AdditionalPropertiesType = + Schema extends { additionalProperties: infer AP } + ? AP extends false ? {} : { [key: string]: TypeFromJsonSchema } + : {}; + type MapSchemaToType = T extends [infer First, ...infer Rest] ? TypeFromJsonSchema | MapSchemaToType : never; diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts index fbf895d16ac..c142eaa482f 100644 --- a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts @@ -10,7 +10,7 @@ import * as nls from '../../../../nls.js'; import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { Memento } from '../../../common/memento.js'; -import { CustomEditorDescriptor, CustomEditorInfo } from './customEditor.js'; +import { CustomEditorPriority, CustomEditorDescriptor, CustomEditorInfo } from './customEditor.js'; import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from './extensionPoint.js'; import { RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { IExtensionPointUser } from '../../../services/extensions/common/extensionsRegistry.js'; @@ -93,12 +93,14 @@ function getPriorityFromContribution( contribution: ICustomEditorsExtensionPoint, extension: IExtensionDescription, ): RegisteredEditorPriority { - switch (contribution.priority) { - case RegisteredEditorPriority.default: - case RegisteredEditorPriority.option: - return contribution.priority; + switch (contribution.priority as CustomEditorPriority | undefined) { + case CustomEditorPriority.default: + return RegisteredEditorPriority.default; + + case CustomEditorPriority.option: + return RegisteredEditorPriority.option; - case RegisteredEditorPriority.builtin: + case CustomEditorPriority.builtin: // Builtin is only valid for builtin extensions return extension.isBuiltin ? RegisteredEditorPriority.builtin : RegisteredEditorPriority.default; diff --git a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index 93c7612628b..9187a0b2688 100644 --- a/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from '../../../../base/common/arrays.js'; -import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { TypeFromJsonSchema, IJSONSchema } from '../../../../base/common/jsonSchema.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import * as nls from '../../../../nls.js'; import { IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; -import { CustomEditorPriority, CustomEditorSelector } from './customEditor.js'; +import { CustomEditorPriority } from './customEditor.js'; import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; import { languagesExtPoint } from '../../../services/language/common/languageService.js'; @@ -22,82 +22,77 @@ const Fields = Object.freeze({ priority: 'priority', }); -export interface ICustomEditorsExtensionPoint { - readonly [Fields.viewType]: string; - readonly [Fields.displayName]: string; - readonly [Fields.selector]?: readonly CustomEditorSelector[]; - readonly [Fields.priority]?: string; -} - -const CustomEditorsContribution: IJSONSchema = { - description: nls.localize('contributes.customEditors', 'Contributed custom editors.'), - type: 'array', - defaultSnippets: [{ - body: [{ - [Fields.viewType]: '$1', - [Fields.displayName]: '$2', - [Fields.selector]: [{ - filenamePattern: '$3' - }], - }] - }], - items: { - type: 'object', - required: [ - Fields.viewType, - Fields.displayName, - Fields.selector, - ], - additionalProperties: false, - properties: { - [Fields.viewType]: { - type: 'string', - markdownDescription: nls.localize('contributes.viewType', 'Identifier for the custom editor. This must be unique across all custom editors, so we recommend including your extension id as part of `viewType`. The `viewType` is used when registering custom editors with `vscode.registerCustomEditorProvider` and in the `onCustomEditor:${id}` [activation event](https://code.visualstudio.com/api/references/activation-events).'), - }, - [Fields.displayName]: { - type: 'string', - description: nls.localize('contributes.displayName', 'Human readable name of the custom editor. This is displayed to users when selecting which editor to use.'), - }, - [Fields.selector]: { - type: 'array', - description: nls.localize('contributes.selector', 'Set of globs that the custom editor is enabled for.'), - items: { - type: 'object', - defaultSnippets: [{ - body: { - filenamePattern: '$1', - } - }], - additionalProperties: false, - properties: { - filenamePattern: { - type: 'string', - description: nls.localize('contributes.selector.filenamePattern', 'Glob that the custom editor is enabled for.'), - }, +const customEditorsContributionSchema = { + type: 'object', + required: [ + Fields.viewType, + Fields.displayName, + Fields.selector, + ], + additionalProperties: false, + properties: { + [Fields.viewType]: { + type: 'string', + markdownDescription: nls.localize('contributes.viewType', 'Identifier for the custom editor. This must be unique across all custom editors, so we recommend including your extension id as part of `viewType`. The `viewType` is used when registering custom editors with `vscode.registerCustomEditorProvider` and in the `onCustomEditor:${id}` [activation event](https://code.visualstudio.com/api/references/activation-events).'), + }, + [Fields.displayName]: { + type: 'string', + description: nls.localize('contributes.displayName', 'Human readable name of the custom editor. This is displayed to users when selecting which editor to use.'), + }, + [Fields.selector]: { + type: 'array', + description: nls.localize('contributes.selector', 'Set of globs that the custom editor is enabled for.'), + items: { + type: 'object', + defaultSnippets: [{ + body: { + filenamePattern: '$1', } + }], + additionalProperties: false, + properties: { + filenamePattern: { + type: 'string', + description: nls.localize('contributes.selector.filenamePattern', 'Glob that the custom editor is enabled for.'), + }, } - }, - [Fields.priority]: { - type: 'string', - markdownDeprecationMessage: nls.localize('contributes.priority', 'Controls if the custom editor is enabled automatically when the user opens a file. This may be overridden by users using the `workbench.editorAssociations` setting.'), - enum: [ - CustomEditorPriority.default, - CustomEditorPriority.option, - ], - markdownEnumDescriptions: [ - nls.localize('contributes.priority.default', 'The editor is automatically used when the user opens a resource, provided that no other default custom editors are registered for that resource.'), - nls.localize('contributes.priority.option', 'The editor is not automatically used when the user opens a resource, but a user can switch to the editor using the `Reopen With` command.'), - ], - default: 'default' } + }, + [Fields.priority]: { + type: 'string', + markdownDeprecationMessage: nls.localize('contributes.priority', 'Controls if the custom editor is enabled automatically when the user opens a file. This may be overridden by users using the `workbench.editorAssociations` setting.'), + enum: [ + CustomEditorPriority.default, + CustomEditorPriority.option, + ], + markdownEnumDescriptions: [ + nls.localize('contributes.priority.default', 'The editor is automatically used when the user opens a resource, provided that no other default custom editors are registered for that resource.'), + nls.localize('contributes.priority.option', 'The editor is not automatically used when the user opens a resource, but a user can switch to the editor using the `Reopen With` command.'), + ], + default: CustomEditorPriority.default } } -}; +} as const satisfies IJSONSchema; + +export type ICustomEditorsExtensionPoint = TypeFromJsonSchema; export const customEditorsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'customEditors', deps: [languagesExtPoint], - jsonSchema: CustomEditorsContribution, + jsonSchema: { + description: nls.localize('contributes.customEditors', 'Contributed custom editors.'), + type: 'array', + defaultSnippets: [{ + body: [{ + [Fields.viewType]: '$1', + [Fields.displayName]: '$2', + [Fields.selector]: [{ + filenamePattern: '$3' + }], + }] + }], + items: customEditorsContributionSchema + }, activationEventsGenerator: function* (contribs: readonly ICustomEditorsExtensionPoint[]) { for (const contrib of contribs) { const viewType = contrib[Fields.viewType]; From 231ae7462146a66f17c230ac263c308313ec8cec Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:21:56 -0700 Subject: [PATCH 1752/4355] Also support a few more forms --- src/vs/base/common/jsonSchema.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 1eb750a7f38..112a492a75f 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -137,12 +137,19 @@ export type TypeFromJsonSchema = ? { [K in keyof P]: TypeFromJsonSchema | undefined } & AdditionalPropertiesType // Array - : T extends { type: 'array'; items: infer I } - ? Array> - - // OneOf + : T extends { type: 'array'; items: infer Items } + ? Items extends [...infer R] + // If items is an array, we treat it like a tuple + ? { [K in keyof R]: TypeFromJsonSchema } + : Array> + + // oneOf / anyof + // These are handled the same way as they both represent a union type. + // However at the validation level, they have different semantics. : T extends { oneOf: infer I } ? MapSchemaToType + : T extends { anyOf: infer I } + ? MapSchemaToType // Fallthrough : never; From 92561b77d3872044a0b19655577a58b9abc1c262 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:27:33 -0700 Subject: [PATCH 1753/4355] Also add support for array of types --- src/vs/base/common/jsonSchema.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 112a492a75f..116d63575ce 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -108,21 +108,13 @@ export type TypeFromJsonSchema = T extends { enum: infer EnumValues } ? UnionOf - // String - : T extends { type: 'string' } - ? string + // Primitive types + : T extends { type: 'string' | 'number' | 'integer' | 'boolean' | 'null' } + ? SchemaPrimitiveTypeNameToType - // Number - : T extends { type: 'number' | 'integer' } - ? number - - // Boolean - : T extends { type: 'boolean' } - ? boolean - - // Null - : T extends { type: 'null' } - ? null + // Union of primitive types + : T extends { type: [...infer R] } + ? UnionOf<{ [K in keyof R]: SchemaPrimitiveTypeNameToType }> // Object with list of required properties. // Values are required or optional based on `required` list. @@ -154,6 +146,13 @@ export type TypeFromJsonSchema = // Fallthrough : never; +type SchemaPrimitiveTypeNameToType = + T extends 'string' ? string : + T extends 'number' | 'integer' ? number : + T extends 'boolean' ? boolean : + T extends 'null' ? null : + never; + type UnionOf = T extends [infer First, ...infer Rest] ? First | UnionOf From 63814385d1304896c0a269629236df1271261b4c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 27 Oct 2025 16:38:56 -0700 Subject: [PATCH 1754/4355] fix some off-by errors, more edit pill errors --- src/vs/base/common/async.ts | 4 + .../chatEditingCheckpointTimelineImpl.ts | 19 +-- .../browser/chatEditing/chatEditingSession.ts | 11 +- .../chatEditingCheckpointTimeline.test.ts | 112 +++++++++++++++++- 4 files changed, 135 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 7f44d84b8c4..c37f2e3433f 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -313,6 +313,10 @@ export class SequencerByKey { return newPromise; } + peek(key: TKey): Promise | undefined { + return this.promiseMap.get(key) || undefined; + } + keys(): IterableIterator { return this.promiseMap.keys(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index f9f5bc4c84b..e9ec3f36f94 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -76,7 +76,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint const operations = this._operations.read(reader); const checkpoints = this._checkpoints.read(reader); - const previousOperationEpoch = operations.findLast(op => op.epoch <= currentEpoch)?.epoch || 0; + const previousOperationEpoch = operations.findLast(op => op.epoch < currentEpoch)?.epoch || 0; const previousCheckpointIdx = findLastIdx(checkpoints, cp => cp.epoch < previousOperationEpoch); if (previousCheckpointIdx === -1) { return undefined; @@ -158,10 +158,11 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint this.createCheckpoint(undefined, undefined, 'Initial State', 'Starting point before any edits'); } - public createCheckpoint(requestId: string | undefined, undoStopId: string | undefined, label: string, description?: string): void { + public createCheckpoint(requestId: string | undefined, undoStopId: string | undefined, label: string, description?: string): string { const existingCheckpoints = this._checkpoints.get(); - if (existingCheckpoints.some(c => c.undoStopId === undoStopId && c.requestId === requestId)) { - return; + const existing = existingCheckpoints.find(c => c.undoStopId === undoStopId && c.requestId === requestId); + if (existing) { + return existing.checkpointId; } const checkpointId = generateUuid(); @@ -178,6 +179,8 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint this._checkpoints.set([...existingCheckpoints, checkpoint], tx); this._currentEpoch.set(checkpoint.epoch + 1, tx); }); + + return checkpointId; } public async undoToLastCheckpoint(): Promise { @@ -190,7 +193,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint public async redoToNextCheckpoint(): Promise { const targetEpoch = this._willRedoToEpoch.get(); if (targetEpoch) { - await this._navigateToEpoch(targetEpoch); + await this._navigateToEpoch(targetEpoch + 1); } } @@ -200,7 +203,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint throw new Error(`Checkpoint ${checkpointId} not found`); } - return this._navigateToEpoch(targetCheckpoint.epoch); + return this._navigateToEpoch(targetCheckpoint.epoch + 1); } public getContentURIAtStop(requestId: string, fileURI: URI, stopId: string | undefined): URI { @@ -235,13 +238,13 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint const currentCheckpoints = this._checkpoints.get(); const operations = this._operations.get(); - const insertAt = operations.findLastIndex(op => op.epoch <= currentEpoch); + const insertAt = operations.findLastIndex(op => op.epoch < currentEpoch); operations[insertAt + 1] = operation; operations.length = insertAt + 2; // Truncate any operations beyond this point // If we undid some operations and are dropping them out of history, also remove // any associated checkpoints. - const newCheckpoints = currentCheckpoints.filter(c => c.epoch <= currentEpoch || c.epoch >= operation.epoch); + const newCheckpoints = currentCheckpoints.filter(c => c.epoch < currentEpoch); transaction(tx => { if (newCheckpoints.length !== currentCheckpoints.length) { this._checkpoints.set(newCheckpoints, tx); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index dc3f7348e8a..44e978a7b0f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -94,6 +94,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio */ private readonly _initialFileContents = new ResourceMap(); + private readonly _baselineCreationLocks = new SequencerByKey(); + private readonly _streamingEditLocks = new SequencerByKey(); + private readonly _entriesObs = observableValue(this, []); public get entries(): IObservable { return this._entriesObs; @@ -260,6 +263,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } public async getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise { + await this._baselineCreationLocks.peek(snapshotUri.path); + const content = await this._timeline.getContentAtStop(requestId, snapshotUri, undoStop); if (!content) { return null; @@ -372,8 +377,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._onDidDispose.dispose(); } - private _streamingEditLocks = new SequencerByKey(); - private get isDisposed() { return this._state.get() === ChatEditingSessionState.Disposed; } @@ -388,6 +391,10 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio const sequencer = new ThrottledSequencer(15, 1000); sequencer.queue(() => startPromise.p); + // Lock around creating the baseline so we don't fail to resolve models + // in the edit pills if they render quickly + this._baselineCreationLocks.queue(resource.path, () => startPromise.p); + this._streamingEditLocks.queue(resource.toString(), async () => { if (!this.isDisposed) { await this._acceptStreamingEditsStart(responseModel, inUndoStop, resource); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts index 9bc7a25b9ae..c48b5e7aa29 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts @@ -597,7 +597,7 @@ suite('ChatEditingCheckpointTimeline', function () { // Verify we're at the start of req1, which has epoch 2 (0 = initial, 1 = baseline, 2 = start checkpoint) const state = timeline.getStateForPersistence(); - assert.strictEqual(state.currentEpoch, 2); // Should be at the "Start req1" checkpoint epoch + assert.strictEqual(state.currentEpoch, 3); // Should be at the "Start req1" checkpoint epoch }); test('operations use incrementing epochs', function () { @@ -1053,6 +1053,116 @@ suite('ChatEditingCheckpointTimeline', function () { // Should not have changed assert.strictEqual(stateBefore.currentEpoch, stateAfter.currentEpoch); }); + + test('orphaned operations and checkpoints are removed after undo and new changes', async function () { + const uri = URI.parse('file:///test.txt'); + + // Create the file first + const createEpoch = timeline.incrementEpoch(); + + timeline.recordFileOperation(createFileCreateOperation( + uri, + 'req1', + createEpoch, + 'initial content' + )); + + timeline.createCheckpoint('req1', undefined, 'Start req1'); + + // First set of changes + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 16), text: 'first edit' }] + )); + + timeline.createCheckpoint('req1', 'stop1', 'First Edit'); + + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 11), text: 'second edit' }] + )); + + timeline.createCheckpoint('req1', 'stop2', 'Second Edit'); + + // Verify we have 3 operations (create + 2 edits) and 4 checkpoints (initial, start, stop1, stop2) + let state = timeline.getStateForPersistence(); + assert.strictEqual(state.operations.length, 3); + assert.strictEqual(state.checkpoints.length, 4); + + // Undo to stop1 (before second edit) + await timeline.navigateToCheckpoint(timeline.getCheckpointIdForRequest('req1', 'stop1')!); + + // Record a new operation - this should truncate the "second edit" operation + // and remove the stop2 checkpoint + timeline.recordFileOperation(createTextEditOperation( + uri, + 'req1', + timeline.incrementEpoch(), + [{ range: new Range(1, 1, 1, 11), text: 'replacement edit' }] + )); + + timeline.createCheckpoint('req1', 'stop2-new', 'Replacement Edit'); + + // Verify the orphaned operation and checkpoint are gone + state = timeline.getStateForPersistence(); + assert.strictEqual(state.operations.length, 3, 'Should still have 3 operations (create + first + replacement)'); + assert.strictEqual(state.checkpoints.length, 4, 'Should have 4 checkpoints (initial, start, stop1, stop2-new)'); + + // Verify the third operation is the replacement, not the original second edit + const thirdOp = state.operations[2]; + assert.strictEqual(thirdOp.type, FileOperationType.TextEdit); + if (thirdOp.type === FileOperationType.TextEdit) { + assert.strictEqual(thirdOp.edits[0].text, 'replacement edit'); + } + + // Verify the stop2-new checkpoint exists, not stop2 + const stop2NewCheckpoint = timeline.getCheckpointIdForRequest('req1', 'stop2-new'); + const stop2OldCheckpoint = timeline.getCheckpointIdForRequest('req1', 'stop2'); + assert.ok(stop2NewCheckpoint, 'New checkpoint should exist'); + assert.strictEqual(stop2OldCheckpoint, undefined, 'Old orphaned checkpoint should be removed'); + + // Now navigate through the entire timeline to verify consistency + const initialCheckpoint = state.checkpoints[0]; + const startCheckpoint = timeline.getCheckpointIdForRequest('req1', undefined)!; + const stop1Checkpoint = timeline.getCheckpointIdForRequest('req1', 'stop1')!; + const stop2NewCheckpointId = timeline.getCheckpointIdForRequest('req1', 'stop2-new')!; + + // Navigate to initial to clear everything + await timeline.navigateToCheckpoint(initialCheckpoint.checkpointId); + assert.strictEqual(fileContents.has(uri), false); + + // Navigate to start - file should be created + await timeline.navigateToCheckpoint(startCheckpoint); + assert.strictEqual(fileContents.get(uri), 'initial content'); + + // Navigate to stop1 - first edit should be applied + await timeline.navigateToCheckpoint(stop1Checkpoint); + assert.strictEqual(fileContents.get(uri), 'first edit'); + + // Navigate to stop2-new - replacement edit should be applied, NOT the orphaned "second edit" + await timeline.navigateToCheckpoint(stop2NewCheckpointId); + assert.strictEqual(fileContents.get(uri), 'replacement edit'); + + // Navigate back to start + await timeline.navigateToCheckpoint(startCheckpoint); + assert.strictEqual(fileContents.get(uri), 'initial content'); + + // Navigate forward through all checkpoints again to ensure redo works correctly + await timeline.navigateToCheckpoint(stop1Checkpoint); + assert.strictEqual(fileContents.get(uri), 'first edit'); + + await timeline.navigateToCheckpoint(stop2NewCheckpointId); + assert.strictEqual(fileContents.get(uri), 'replacement edit', 'Orphaned edit should never reappear'); + + // Go back to initial and forward again to thoroughly test + await timeline.navigateToCheckpoint(initialCheckpoint.checkpointId); + await timeline.navigateToCheckpoint(stop2NewCheckpointId); + assert.strictEqual(fileContents.get(uri), 'replacement edit', 'Content should still be correct after full timeline traversal'); + }); }); // Mock notebook service for tests that don't need notebook functionality From db1e837c96b638d6fcff4dff59b2ddf028aee333 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:49:38 -0700 Subject: [PATCH 1755/4355] Get chat session items in parallel --- .../chat/browser/actions/chatActions.ts | 89 +++++++++---------- .../chat/browser/chatSessions.contribution.ts | 11 ++- .../chat/common/chatSessionsService.ts | 4 +- .../test/common/mockChatSessionsService.ts | 13 +-- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 289b27a073e..2613e1849d7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -734,55 +734,46 @@ export function registerChatActions() { // Use the new Promise-based API to get chat sessions const cancellationToken = new CancellationTokenSource(); try { - const providers = chatSessionsService.getAllChatSessionContributions(); - const providerNSessions: { providerType: string; session: IChatSessionItem }[] = []; - - for (const provider of providers) { - const sessions = await chatSessionsService.getChatSessionItems(provider.type, cancellationToken.token); - if (!sessions?.length) { - continue; - } - providerNSessions.push(...sessions.map(session => ({ providerType: provider.type, session }))); - } - - for (const session of providerNSessions) { - const sessionContent = session.session; - - const ckey = contextKeyService.createKey('chatSessionType', session.providerType); - const actions = menuService.getMenuActions(MenuId.ChatSessionsMenu, contextKeyService); - const { primary } = getContextMenuActions(actions, 'inline'); - ckey.reset(); - - // Use primary actions if available, otherwise fall back to secondary actions - const buttons = primary.map(action => ({ - id: action.id, - tooltip: action.tooltip, - iconClass: action.class || ThemeIcon.asClassName(Codicon.symbolClass), - })); - // Create agent pick from the session content - const agentPick: ICodingAgentPickerItem = { - label: sessionContent.label, - description: '', - session: { providerType: session.providerType, session: sessionContent }, - chat: { - sessionId: sessionContent.id, - title: sessionContent.label, - isActive: false, - lastMessageDate: 0, - }, - buttons, - id: sessionContent.id - }; - - // Check if this agent already exists (update existing or add new) - const existingIndex = agentPicks.findIndex(pick => pick.chat.sessionId === sessionContent.id); - if (existingIndex >= 0) { - agentPicks[existingIndex] = agentPick; - } else { - // Respect show limits - const maxToShow = showAllAgents ? Number.MAX_SAFE_INTEGER : 5; - if (agentPicks.length < maxToShow) { - agentPicks.push(agentPick); + const providerNSessions = await chatSessionsService.getAllChatSessionItems(cancellationToken.token); + for (const { chatSessionType, items } of providerNSessions) { + for (const session of items) { + + const ckey = contextKeyService.createKey('chatSessionType', chatSessionType); + const actions = menuService.getMenuActions(MenuId.ChatSessionsMenu, contextKeyService); + const { primary } = getContextMenuActions(actions, 'inline'); + ckey.reset(); + + // Use primary actions if available, otherwise fall back to secondary actions + const buttons = primary.map(action => ({ + id: action.id, + tooltip: action.tooltip, + iconClass: action.class || ThemeIcon.asClassName(Codicon.symbolClass), + })); + // Create agent pick from the session content + const agentPick: ICodingAgentPickerItem = { + label: session.label, + description: '', + session: { providerType: chatSessionType, session: session }, + chat: { + sessionId: session.id, + title: session.label, + isActive: false, + lastMessageDate: 0, + }, + buttons, + id: session.id + }; + + // Check if this agent already exists (update existing or add new) + const existingIndex = agentPicks.findIndex(pick => pick.chat.sessionId === session.id); + if (existingIndex >= 0) { + agentPicks[existingIndex] = agentPick; + } else { + // Respect show limits + const maxToShow = showAllAgents ? Number.MAX_SAFE_INTEGER : 5; + if (agentPicks.length < maxToShow) { + agentPicks.push(agentPick); + } } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 213864520e2..8ce2d9371c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -677,7 +677,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return this._contentProviders.has(chatSessionResource.scheme); } - public async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { + async getAllChatSessionItems(token: CancellationToken): Promise> { + return Promise.all(Array.from(this.getAllChatSessionContributions(), async contrib => { + return { + chatSessionType: contrib.type, + items: await this.getChatSessionItems(contrib.type, token) + }; + })); + } + + private async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { if (!(await this.hasChatSessionItemProvider(chatSessionType))) { return []; } diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index e6b2e9cde92..687dcb05a4f 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -151,9 +151,9 @@ export interface IChatSessionsService { getWelcomeTipsForSessionType(chatSessionType: string): string | undefined; /** - * Get the list of chat session items for a specific session type. + * Get the list of chat session items grouped by session type. */ - getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise; + getAllChatSessionItems(token: CancellationToken): Promise>; getNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index d4c7917b1c4..1efcc595905 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -110,12 +110,13 @@ export class MockChatSessionsService implements IChatSessionsService { return provider.provideNewChatSessionItem(options, token); } - async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise { - const provider = this.sessionItemProviders.get(chatSessionType); - if (!provider) { - return []; - } - return provider.provideChatSessionItems(token); + getAllChatSessionItems(token: CancellationToken): Promise> { + return Promise.all(Array.from(this.sessionItemProviders.values(), async provider => { + return { + chatSessionType: provider.chatSessionType, + items: await provider.provideChatSessionItems(token), + }; + })); } reportInProgress(chatSessionType: string, count: number): void { From 38f84dc085055e9966cdd888aadef403f7dfb1fb Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 27 Oct 2025 16:46:11 -0700 Subject: [PATCH 1756/4355] rm dead code, hygenie, target cleanup --- .../chatMarkdownContentPart.ts | 2 +- .../chatEditingCheckpointTimelineImpl.ts | 13 +-- .../chatEditing/chatEditingOperationUtils.ts | 104 ------------------ .../chatEditing/chatEditingOperations.ts | 1 + .../browser/chatEditing/chatEditingSession.ts | 1 + .../common/services/notebookWebWorker.ts | 2 +- 6 files changed, 10 insertions(+), 113 deletions(-) delete mode 100644 src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperationUtils.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 317514614e9..e3f0d01a4ed 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -546,7 +546,7 @@ export class CollapsedCodeBlock extends Disposable { if (isStreaming.read(r)) { const entry = editSessionEntry.read(r); const rwRatio = Math.floor((entry?.rewriteRatio.read(r) || 0) * 100); - labelDetail.textContent = rwRatio === 0 || !rwRatio ? localize('chat.codeblock.generating', "Generating edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)..."); + labelDetail.textContent = rwRatio === 0 || !rwRatio ? localize('chat.codeblock.generating', "Generating edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)...", rwRatio); } else { labelDetail.textContent = ''; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index e9ec3f36f94..499b722a4fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -29,7 +29,7 @@ import { INotebookService } from '../../../notebook/common/notebookService.js'; import { IEditSessionEntryDiff, IModifiedEntryTelemetryInfo } from '../../common/chatEditingService.js'; import { IChatRequestDisablement } from '../../common/chatModel.js'; import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js'; -import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IFileCreateOperation, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js'; +import { FileOperation, FileOperationType, IChatEditingTimelineState, ICheckpoint, IFileBaseline, IReconstructedFileExistsState, IReconstructedFileNotExistsState, IReconstructedFileState } from './chatEditingOperations.js'; import { ChatEditingSnapshotTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; import { createSnapshot as createNotebookSnapshot, restoreSnapshot as restoreNotebookSnapshot } from './notebook/chatEditingModifiedNotebookSnapshot.js'; @@ -76,7 +76,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint const operations = this._operations.read(reader); const checkpoints = this._checkpoints.read(reader); - const previousOperationEpoch = operations.findLast(op => op.epoch < currentEpoch)?.epoch || 0; + const previousOperationEpoch = findLast(operations, op => op.epoch < currentEpoch)?.epoch || 0; const previousCheckpointIdx = findLastIdx(checkpoints, cp => cp.epoch < previousOperationEpoch); if (previousCheckpointIdx === -1) { return undefined; @@ -238,7 +238,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint const currentCheckpoints = this._checkpoints.get(); const operations = this._operations.get(); - const insertAt = operations.findLastIndex(op => op.epoch < currentEpoch); + const insertAt = findLastIdx(operations, op => op.epoch < currentEpoch); operations[insertAt + 1] = operation; operations.length = insertAt + 2; // Truncate any operations beyond this point @@ -413,13 +413,12 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint // If the file was just created, use that as its updated baseline if (operation.type === FileOperationType.Create && isEqual(operation.uri, uri)) { - const createOp = operation as IFileCreateOperation; return { uri: operation.uri, requestId: operation.requestId, - content: createOp.initialContent, + content: operation.initialContent, epoch: operation.epoch, - telemetryInfo: this._getFileBaseline(uri, currentRequestId)?.telemetryInfo!, + telemetryInfo: operation.telemetryInfo, }; } @@ -598,7 +597,7 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint switch (operation.type) { case FileOperationType.Create: if (isMovingForward) { - await this._delegate.createFile(operation.uri, (operation as IFileCreateOperation).initialContent); + await this._delegate.createFile(operation.uri, operation.initialContent); urisToRestore.add(operation.uri); } else { await this._delegate.deleteFile(operation.uri); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperationUtils.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperationUtils.ts deleted file mode 100644 index ce0d72ceedb..00000000000 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperationUtils.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from '../../../../../base/common/uri.js'; -import { FileOperation, FileOperationType, IFileRenameOperation } from './chatEditingOperations.js'; - -/** - * Utility class for working with operation collections - */ -export class OperationUtils { - - /** - * Filter operations by file URI - */ - static filterByFile(operations: readonly FileOperation[], uri: URI): readonly FileOperation[] { - return operations.filter(op => op.uri.toString() === uri.toString()); - } - - /** - * Filter operations by request ID - */ - static filterByRequest(operations: readonly FileOperation[], requestId: string): readonly FileOperation[] { - return operations.filter(op => op.requestId === requestId); - } - - /** - * Filter operations by type - */ - static filterByType(operations: readonly FileOperation[], type: FileOperationType): readonly T[] { - return operations.filter(op => op.type === type) as T[]; - } - - /** - * Group operations by file URI - */ - static groupByFile(operations: readonly FileOperation[]): Map { - const grouped = new Map(); - - for (const operation of operations) { - const key = operation.uri.toString(); - const existing = grouped.get(key) || []; - existing.push(operation); - grouped.set(key, existing); - } - - // Convert to readonly - const result = new Map(); - for (const [key, ops] of grouped) { - result.set(key, ops); - } - - return result; - } - - /** - * Sort operations by timestamp - */ - static sortByTimestamp(operations: readonly FileOperation[]): readonly FileOperation[] { - return [...operations].sort((a, b) => a.epoch - b.epoch); - } - - /** - * Get the final URI for a file after all rename operations - */ - static getFinalUri(operations: readonly FileOperation[], initialUri: URI): URI { - let currentUri = initialUri; - - const renameOps = OperationUtils.filterByType(operations, FileOperationType.Rename); - - for (const renameOp of renameOps) { - if (renameOp.oldUri.toString() === currentUri.toString()) { - currentUri = renameOp.newUri; - } - } - - return currentUri; - } - - /** - * Check if a file exists after applying all operations - */ - static fileExistsAfterOperations(operations: readonly FileOperation[], uri: URI): boolean { - const fileOps = OperationUtils.filterByFile(operations, uri); - const sortedOps = OperationUtils.sortByTimestamp(fileOps); - - let exists = false; // assume file doesn't exist initially - - for (const op of sortedOps) { - switch (op.type) { - case FileOperationType.Create: - exists = true; - break; - case FileOperationType.Delete: - exists = false; - break; - // Rename and edit operations don't change existence - } - } - - return exists; - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts index 3f0f3fb5ef9..1ab3412e3ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingOperations.ts @@ -33,6 +33,7 @@ export interface IFileCreateOperation extends IFileOperation { readonly type: FileOperationType.Create; readonly initialContent: string; readonly notebookViewType?: string; + readonly telemetryInfo: IModifiedEntryTelemetryInfo; } /** diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 44e978a7b0f..46d1c6d0f29 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -703,6 +703,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio requestId: telemetryInfo.requestId, epoch: this._timeline.incrementEpoch(), initialContent: initialContent || '', + telemetryInfo, }); if (this._notebookService.hasSupportedNotebooks(notebookUri)) { diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts index ba83733d6ca..213b8856c8f 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts @@ -249,7 +249,7 @@ export class NotebookWorker implements IWebWorkerServerRequestHandler, IDisposab const cellMapping = computeDiff(originalModel, modifiedModel, { cellsDiff: { changes: originalDiff.changes, quitEarly: false }, metadataChanged: false, }).cellDiffInfo; // If we have no insertions/deletions, then this is a good diffing. - if (cellMapping.every(c => c.type === 'modified' || c.type === 'unchanged')) { + if (cellMapping.every(c => c.type === 'modified')) { return { metadataChanged, cellsDiff: originalDiff From 13244b5b2dffe4b4f833961ec344eb24993d4e25 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:58:20 -0700 Subject: [PATCH 1757/4355] Fix order --- src/vs/base/common/jsonSchema.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 116d63575ce..ac98a0ddd5f 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -108,14 +108,6 @@ export type TypeFromJsonSchema = T extends { enum: infer EnumValues } ? UnionOf - // Primitive types - : T extends { type: 'string' | 'number' | 'integer' | 'boolean' | 'null' } - ? SchemaPrimitiveTypeNameToType - - // Union of primitive types - : T extends { type: [...infer R] } - ? UnionOf<{ [K in keyof R]: SchemaPrimitiveTypeNameToType }> - // Object with list of required properties. // Values are required or optional based on `required` list. : T extends { type: 'object'; properties: infer P; required: infer RequiredList } @@ -143,6 +135,16 @@ export type TypeFromJsonSchema = : T extends { anyOf: infer I } ? MapSchemaToType + // Primitive types + : T extends { type: infer Type } + // Basic type + ? Type extends 'string' | 'number' | 'integer' | 'boolean' | 'null' + ? SchemaPrimitiveTypeNameToType + // Union of primitive types + : Type extends [...infer R] + ? UnionOf<{ [K in keyof R]: SchemaPrimitiveTypeNameToType }> + : never + // Fallthrough : never; From 1be558e4d58b7a5239fbe70171c474c56f524527 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 28 Oct 2025 05:49:13 +0000 Subject: [PATCH 1758/4355] Resize cell editor width when resizing window horizontally (#273665) * Resize cell editor width when resizing window horizontally * add comments --- .../notebook/browser/view/cellParts/codeCell.ts | 17 +++++++++++------ .../notebook/test/browser/view/cellPart.test.ts | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 70d9b9f8e11..765aa0fe09d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -334,7 +334,7 @@ export class CodeCell extends Disposable { this._register(this.templateData.editor.onDidContentSizeChange((e) => { if (e.contentHeightChanged) { if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { - this.onCellEditorHeightChange(`onDidContentSizeChange: ${e.contentHeight}`); + this.onCellEditorHeightChange(`onDidContentSizeChange`); this.adjustEditorPosition(); } } @@ -601,7 +601,7 @@ export class CodeCell extends Disposable { }, true); } - private onCellWidthChange(dbgReasonForChange: string): void { + private onCellWidthChange(dbgReasonForChange: CellLayoutChangeReason): void { this._debug(`Cell Editor Width Change, ${dbgReasonForChange}, Content Height = ${this.templateData.editor.getContentHeight()}`); const height = this.templateData.editor.getContentHeight(); if (this.templateData.editor.hasModel()) { @@ -620,7 +620,7 @@ export class CodeCell extends Disposable { this._cellLayout.layoutEditor(dbgReasonForChange); } - private onCellEditorHeightChange(dbgReasonForChange: string): void { + private onCellEditorHeightChange(dbgReasonForChange: CellLayoutChangeReason): void { const height = this.templateData.editor.getContentHeight(); if (!this.templateData.editor.hasModel()) { this._debug(`Cell Editor Height Change without model, return (2), ContentHeight: ${height}, CodeCellLayoutInfo.EditorWidth ${this.viewCell.layoutInfo.editorWidth}, EditorLayoutInfo ${this.templateData.editor.getLayoutInfo()}`); @@ -661,6 +661,8 @@ export class CodeCell extends Disposable { } } +type CellLayoutChangeReason = 'nbLayoutChange' | 'nbDidScroll' | 'viewCellLayoutChange' | 'init' | 'onDidChangeCursorSelection' | 'onDidContentSizeChange' | 'onDidResolveTextModel'; + export class CodeCellLayout { private _editorVisibility?: 'Full' | 'Top Clipped' | 'Bottom Clipped' | 'Full (Small Viewport)' | 'Invisible'; public get editorVisibility() { @@ -755,7 +757,7 @@ export class CodeCellLayout { * │ (Outputs region begins at outputContainerOffset below input area) │ * └──────────────────────────────────────────────────────────────────────────────────┘ */ - public layoutEditor(reason: string) { + public layoutEditor(reason: CellLayoutChangeReason): void { if (!this._enabled) { return; } @@ -773,6 +775,9 @@ export class CodeCellLayout { const editor = this.templateData.editor; const editorLayout = this.templateData.editor.getLayoutInfo(); + // If we've already initialized once, we should use the viewCell layout info for editor width. + // E.g. when resizing VS Code window or notebook editor (horizontal space changes). + const editorWidth = this._initialized && (reason === 'nbLayoutChange' || reason === 'viewCellLayoutChange') ? this.viewCell.layoutInfo.editorWidth : editorLayout.width; const editorHeight = this.viewCell.layoutInfo.editorHeight; const scrollTop = this.notebookEditor.scrollTop; const elementTop = this.notebookEditor.getAbsoluteTopOfElement(this.viewCell); @@ -831,13 +836,13 @@ export class CodeCellLayout { this._logService.debug(`=> scrollTop = ${scrollTop}, top = ${top}`); this._logService.debug(`=> cellTopMargin = ${CELL_TOP_MARGIN}, cellBottomMargin = ${this.viewCell.layoutInfo.topMargin}, cellOutline = ${CELL_OUTLINE_WIDTH}`); this._logService.debug(`=> scrollBottom: ${scrollBottom}, editBottom: ${editorBottom}, viewport: ${viewportHeight}, scroll: ${scrollDirection}, contOffset: ${outputContainerOffset})`); - this._logService.debug(`=> Editor Height = ${height}px, Width: ${editorLayout.width}px, Initial Width: ${this._initialEditorDimension.width}, EditorScrollTop = ${editorScrollTop}px, StatusbarHeight = ${STATUSBAR_HEIGHT}, lineHeight = ${this.notebookEditor.getLayoutInfo().fontInfo.lineHeight}`); + this._logService.debug(`=> Editor Height = ${height}px, Width: ${editorWidth}px, Initial Width: ${this._initialEditorDimension.width}, EditorScrollTop = ${editorScrollTop}px, StatusbarHeight = ${STATUSBAR_HEIGHT}, lineHeight = ${this.notebookEditor.getLayoutInfo().fontInfo.lineHeight}`); try { this._isUpdatingLayout = true; element.style.top = `${top}px`; editor.layout({ - width: this._initialized ? editorLayout.width : this._initialEditorDimension.width, + width: this._initialized ? editorWidth : this._initialEditorDimension.width, height }, true); if (editorScrollTop >= 0) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts index cc7beec45b4..b7c5f2d5f85 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/view/cellPart.test.ts @@ -186,7 +186,7 @@ suite('CellPart', () => { { width: 600, height: s.editorHeight } ); - layout.layoutEditor(s.name); + layout.layoutEditor('init'); assert.strictEqual( layout.editorVisibility, s.expected, @@ -380,7 +380,7 @@ suite('CellPart', () => { { debug: () => { } }, { width: 600, height: EDITOR_HEIGHT } ); - layout.layoutEditor('scroll'); + layout.layoutEditor('nbDidScroll'); const actualTop = parseInt( (editorPart.style.top || '0').replace(/px$/, '') ); From 8496250cd11ca3ac7628b2e1625e5476f603a4c0 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Mon, 27 Oct 2025 22:58:57 -0700 Subject: [PATCH 1759/4355] Migrate mode to agent in prompts --- .github/prompts/codenotify.prompt.md | 2 +- .github/prompts/component.prompt.md | 2 +- .github/prompts/data.prompt.md | 2 +- .github/prompts/fixIssueNo.prompt.md | 2 +- .github/prompts/implement.prompt.md | 2 +- .github/prompts/no-any.prompt.md | 2 +- .github/prompts/plan-deep.prompt.md | 2 +- .github/prompts/plan-fast.prompt.md | 2 +- .github/prompts/plan.prompt.md | 2 +- .github/prompts/setup-environment.prompt.md | 2 +- .github/prompts/update-instructions.prompt.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/prompts/codenotify.prompt.md b/.github/prompts/codenotify.prompt.md index 15f679fc1b1..23b42157397 100644 --- a/.github/prompts/codenotify.prompt.md +++ b/.github/prompts/codenotify.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent tools: ['edit', 'search', 'runCommands', 'fetch', 'todos'] --- diff --git a/.github/prompts/component.prompt.md b/.github/prompts/component.prompt.md index 9aa2eb6d176..5297e2b6ad4 100644 --- a/.github/prompts/component.prompt.md +++ b/.github/prompts/component.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent description: 'Help author a component specification for an agent.' tools: ['edit', 'search', 'usages', 'vscodeAPI', 'fetch', 'extensions', 'todos'] --- diff --git a/.github/prompts/data.prompt.md b/.github/prompts/data.prompt.md index f3fe3292aaf..4185ebb4141 100644 --- a/.github/prompts/data.prompt.md +++ b/.github/prompts/data.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent description: 'Answer telemetry questions with data queries' tools: ['search', 'runCommands/runInTerminal', 'Azure MCP/kusto_query', 'githubRepo', 'extensions', 'todos'] --- diff --git a/.github/prompts/fixIssueNo.prompt.md b/.github/prompts/fixIssueNo.prompt.md index 6438f4614fe..0184204d3b4 100644 --- a/.github/prompts/fixIssueNo.prompt.md +++ b/.github/prompts/fixIssueNo.prompt.md @@ -1,5 +1,5 @@ --- -mode: Plan +agent: Plan tools: ['runCommands', 'runTasks', 'runNotebooks', 'search', 'new', 'usages', 'vscodeAPI', 'problems', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests', 'get_issue', 'get_issue_comments', 'get_me', 'get_pull_request', 'get_pull_request_diff', 'get_pull_request_files'] --- diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/implement.prompt.md index 190ff25f9ec..4deb7ba76d7 100644 --- a/.github/prompts/implement.prompt.md +++ b/.github/prompts/implement.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent description: 'Implement the plan' tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] --- diff --git a/.github/prompts/no-any.prompt.md b/.github/prompts/no-any.prompt.md index f5a4b595e5c..7e78fefa27e 100644 --- a/.github/prompts/no-any.prompt.md +++ b/.github/prompts/no-any.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent description: 'Remove any usage of the any type in TypeScript files' --- diff --git a/.github/prompts/plan-deep.prompt.md b/.github/prompts/plan-deep.prompt.md index 412bae87cbc..a321fbe2cbb 100644 --- a/.github/prompts/plan-deep.prompt.md +++ b/.github/prompts/plan-deep.prompt.md @@ -1,5 +1,5 @@ --- -mode: Plan +agent: Plan description: Clarify before planning in more detail --- Before doing your research workflow, gather preliminary context using #runSubagent (instructed to use max 5 tool calls) to get a high-level overview. diff --git a/.github/prompts/plan-fast.prompt.md b/.github/prompts/plan-fast.prompt.md index a3d320fa04e..2ae8191933a 100644 --- a/.github/prompts/plan-fast.prompt.md +++ b/.github/prompts/plan-fast.prompt.md @@ -1,5 +1,5 @@ --- -mode: Plan +agent: Plan description: Iterate quicker on simple tasks --- Planning for faster iteration: Research as usual, but draft a much more shorter implementation plan that focused on just the main steps diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index 53a5fa9b0fb..e94ee7b24eb 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,5 +1,5 @@ --- -mode: Plan +agent: Plan description: 'Start planning' --- Start planning. diff --git a/.github/prompts/setup-environment.prompt.md b/.github/prompts/setup-environment.prompt.md index e35870dd477..fa934ad7029 100644 --- a/.github/prompts/setup-environment.prompt.md +++ b/.github/prompts/setup-environment.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent description: First Time Setup tools: ['runCommands', 'runTasks/runTask', 'search', 'todos', 'fetch'] --- diff --git a/.github/prompts/update-instructions.prompt.md b/.github/prompts/update-instructions.prompt.md index 8ba53454822..b38001671ef 100644 --- a/.github/prompts/update-instructions.prompt.md +++ b/.github/prompts/update-instructions.prompt.md @@ -1,5 +1,5 @@ --- -mode: agent +agent: agent --- Read the changes introduced on the current branch, including BOTH: From fd49ad959b0a6a8370124cb00cd2719ef1551736 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 07:35:39 +0100 Subject: [PATCH 1760/4355] debt - reduce use of `any` type (#273692) --- eslint.config.js | 18 ------------------ src/vs/server/node/extensionHostConnection.ts | 2 +- src/vs/server/node/server.cli.ts | 2 +- src/vs/server/node/serverConnectionToken.ts | 4 ++-- .../api/browser/mainThreadTreeViews.ts | 2 +- .../api/browser/statusBarExtensionPoint.ts | 2 +- .../api/browser/viewsExtensionPoint.ts | 8 ++++---- .../api/common/extHostNotebookKernels.ts | 2 +- .../comments/browser/commentThreadBody.ts | 2 +- .../contrib/outline/browser/outlinePane.ts | 4 ++-- .../outline/browser/outlineViewState.ts | 2 +- .../contrib/output/browser/outputView.ts | 4 ++-- .../output/common/outputChannelModel.ts | 4 ++-- .../themes/browser/themes.contribution.ts | 4 ++-- .../actions/common/menusExtensionPoint.ts | 2 +- .../services/driver/browser/driver.ts | 4 ++-- .../common/languageStatusService.ts | 2 +- .../remote/browser/remoteAgentService.ts | 2 +- .../browser/workbenchCommonProperties.ts | 2 +- 19 files changed, 27 insertions(+), 45 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index af60b5b9124..92f5f25e2ed 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -462,9 +462,6 @@ export default tseslint.config( // Workbench 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', 'src/vs/workbench/api/browser/mainThreadTerminalService.ts', - 'src/vs/workbench/api/browser/mainThreadTreeViews.ts', - 'src/vs/workbench/api/browser/statusBarExtensionPoint.ts', - 'src/vs/workbench/api/browser/viewsExtensionPoint.ts', 'src/vs/workbench/api/common/configurationExtensionPoint.ts', 'src/vs/workbench/api/common/extHost.api.impl.ts', 'src/vs/workbench/api/common/extHost.protocol.ts', @@ -488,7 +485,6 @@ export default tseslint.config( 'src/vs/workbench/api/common/extHostMessageService.ts', 'src/vs/workbench/api/common/extHostNotebookDocument.ts', 'src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts', - 'src/vs/workbench/api/common/extHostNotebookKernels.ts', 'src/vs/workbench/api/common/extHostQuickOpen.ts', 'src/vs/workbench/api/common/extHostRequireInterceptor.ts', 'src/vs/workbench/api/common/extHostRpcService.ts', @@ -580,7 +576,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts', 'src/vs/workbench/contrib/commands/common/commands.contribution.ts', 'src/vs/workbench/contrib/comments/browser/commentNode.ts', - 'src/vs/workbench/contrib/comments/browser/commentThreadBody.ts', 'src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts', 'src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts', 'src/vs/workbench/contrib/comments/browser/commentsView.ts', @@ -677,10 +672,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts', 'src/vs/workbench/contrib/notebook/common/notebookRange.ts', 'src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts', - 'src/vs/workbench/contrib/outline/browser/outlinePane.ts', - 'src/vs/workbench/contrib/outline/browser/outlineViewState.ts', - 'src/vs/workbench/contrib/output/browser/outputView.ts', - 'src/vs/workbench/contrib/output/common/outputChannelModel.ts', 'src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts', 'src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts', 'src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts', @@ -749,7 +740,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/testing/common/storedValue.ts', 'src/vs/workbench/contrib/testing/common/testItemCollection.ts', 'src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts', - 'src/vs/workbench/contrib/themes/browser/themes.contribution.ts', 'src/vs/workbench/contrib/timeline/browser/timelinePane.ts', 'src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts', 'src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts', @@ -768,7 +758,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts', 'src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts', 'src/vs/workbench/services/accounts/common/defaultAccount.ts', - 'src/vs/workbench/services/actions/common/menusExtensionPoint.ts', 'src/vs/workbench/services/assignment/common/assignmentFilters.ts', 'src/vs/workbench/services/authentication/common/authentication.ts', 'src/vs/workbench/services/authentication/test/browser/authenticationQueryServiceMocks.ts', @@ -782,7 +771,6 @@ export default tseslint.config( 'src/vs/workbench/services/configurationResolver/common/configurationResolver.ts', 'src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts', 'src/vs/workbench/services/configurationResolver/common/variableResolver.ts', - 'src/vs/workbench/services/driver/browser/driver.ts', 'src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts', 'src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts', 'src/vs/workbench/services/extensions/common/extHostCustomers.ts', @@ -804,13 +792,11 @@ export default tseslint.config( 'src/vs/workbench/services/keybinding/common/keymapInfo.ts', 'src/vs/workbench/services/language/common/languageService.ts', 'src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts', - 'src/vs/workbench/services/languageStatus/common/languageStatusService.ts', 'src/vs/workbench/services/outline/browser/outline.ts', 'src/vs/workbench/services/outline/browser/outlineService.ts', 'src/vs/workbench/services/preferences/common/preferences.ts', 'src/vs/workbench/services/preferences/common/preferencesModels.ts', 'src/vs/workbench/services/preferences/common/preferencesValidation.ts', - 'src/vs/workbench/services/remote/browser/remoteAgentService.ts', 'src/vs/workbench/services/remote/common/tunnelModel.ts', 'src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts', 'src/vs/workbench/services/search/common/replace.ts', @@ -822,7 +808,6 @@ export default tseslint.config( 'src/vs/workbench/services/search/node/rawSearchService.ts', 'src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts', 'src/vs/workbench/services/search/worker/localFileSearch.ts', - 'src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts', 'src/vs/workbench/services/terminal/common/embedderTerminalService.ts', 'src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts', 'src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts', @@ -847,13 +832,10 @@ export default tseslint.config( 'src/vs/workbench/workbench.web.main.internal.ts', 'src/vs/workbench/workbench.web.main.ts', // Server - 'src/vs/server/node/extensionHostConnection.ts', 'src/vs/server/node/remoteAgentEnvironmentImpl.ts', 'src/vs/server/node/remoteExtensionHostAgentServer.ts', 'src/vs/server/node/remoteExtensionsScanner.ts', 'src/vs/server/node/remoteTerminalChannel.ts', - 'src/vs/server/node/server.cli.ts', - 'src/vs/server/node/serverConnectionToken.ts', // Tests '**/*.test.ts', '**/*.integrationTest.ts' diff --git a/src/vs/server/node/extensionHostConnection.ts b/src/vs/server/node/extensionHostConnection.ts index 5c2c38b010b..05b7d038d26 100644 --- a/src/vs/server/node/extensionHostConnection.ts +++ b/src/vs/server/node/extensionHostConnection.ts @@ -237,7 +237,7 @@ export class ExtensionHostConnection extends Disposable { public async start(startParams: IRemoteExtensionHostStartParams): Promise { try { let execArgv: string[] = process.execArgv ? process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)) : []; - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any if (startParams.port && !(process).pkg) { execArgv = [ `--inspect${startParams.break ? '-brk' : ''}=${startParams.port}`, diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 0535ddd998f..69b17e13a86 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -466,7 +466,7 @@ function asExtensionIdOrVSIX(inputs: string[] | undefined) { return inputs?.map(input => /\.vsix$/i.test(input) ? pathToURI(input).href : input); } -function fatal(message: string, err: any): void { +function fatal(message: string, err: unknown): void { console.error('Unable to connect to VS Code server: ' + message); console.error(err); process.exit(1); diff --git a/src/vs/server/node/serverConnectionToken.ts b/src/vs/server/node/serverConnectionToken.ts index b2036788008..11089eea3c3 100644 --- a/src/vs/server/node/serverConnectionToken.ts +++ b/src/vs/server/node/serverConnectionToken.ts @@ -24,7 +24,7 @@ export const enum ServerConnectionTokenType { export class NoneServerConnectionToken { public readonly type = ServerConnectionTokenType.None; - public validate(connectionToken: any): boolean { + public validate(connectionToken: unknown): boolean { return true; } } @@ -35,7 +35,7 @@ export class MandatoryServerConnectionToken { constructor(public readonly value: string) { } - public validate(connectionToken: any): boolean { + public validate(connectionToken: unknown): boolean { return (connectionToken === this.value); } } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 1b6172fa9a7..4a4a6526b90 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -375,7 +375,7 @@ class TreeViewDataProvider implements ITreeViewDataProvider { const properties = distinct([...Object.keys(current instanceof ResolvableTreeItem ? current.asTreeItem() : current), ...Object.keys(treeItem)]); for (const property of properties) { - (current as { [key: string]: any })[property] = (treeItem as { [key: string]: any })[property]; + (current as unknown as { [key: string]: unknown })[property] = (treeItem as unknown as { [key: string]: unknown })[property]; } if (current instanceof ResolvableTreeItem) { current.resetResolve(); diff --git a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts index 2e23155cc73..4da1f68eeb1 100644 --- a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts +++ b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts @@ -172,7 +172,7 @@ registerSingleton(IExtensionStatusBarItemService, ExtensionStatusBarItemService, type IUserFriendlyStatusItemEntry = TypeFromJsonSchema; -function isUserFriendlyStatusItemEntry(candidate: any): candidate is IUserFriendlyStatusItemEntry { +function isUserFriendlyStatusItemEntry(candidate: unknown): candidate is IUserFriendlyStatusItemEntry { const obj = candidate as IUserFriendlyStatusItemEntry; return (typeof obj.id === 'string' && obj.id.length > 0) && typeof obj.name === 'string' diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 8c6534f4865..c0ff40a3313 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -542,7 +542,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { extensionId: extension.description.identifier, originalContainerId: key, group: item.group, - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated virtualWorkspace: item.virtualWorkspace, hideByDefault: initialVisibility === InitialVisibility.Hidden, @@ -594,9 +594,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } - private convertInitialVisibility(value: any): InitialVisibility | undefined { - if (Object.values(InitialVisibility).includes(value)) { - return value; + private convertInitialVisibility(value: string | undefined): InitialVisibility | undefined { + if (Object.values(InitialVisibility).includes(value as InitialVisibility)) { + return value as InitialVisibility; } return undefined; } diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 6e11a2c8d6b..52eaf027866 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -497,7 +497,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } } - $acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void { + $acceptKernelMessageFromRenderer(handle: number, editorId: string, message: unknown): void { const obj = this._kernelData.get(handle); if (!obj) { // extension can dispose kernels in the meantime diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 9fd66298b75..c6ff74b5541 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -23,7 +23,7 @@ import { LayoutableEditor } from './simpleCommentEditor.js'; export class CommentThreadBody extends Disposable { private _commentsElement!: HTMLElement; private _commentElements: CommentNode[] = []; - private _resizeObserver: any; + private _resizeObserver: MutationObserver | null = null; private _focusedComment: number | undefined = undefined; private _onDidResize = new Emitter(); onDidResize = this._onDidResize.event; diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 84bed3cda75..f2d6664fd4a 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -71,7 +71,7 @@ export class OutlinePane extends ViewPane implements IOutlinePane { private _message!: HTMLDivElement; private _progressBar!: ProgressBar; private _treeContainer!: HTMLElement; - private _tree?: WorkbenchDataTree | undefined, any, FuzzyScore>; + private _tree?: WorkbenchDataTree | undefined, unknown, FuzzyScore>; private _treeDimensions?: dom.Dimension; private _treeStates = new LRUCache(10); @@ -253,7 +253,7 @@ export class OutlinePane extends ViewPane implements IOutlinePane { const sorter = new OutlineTreeSorter(newOutline.config.comparator, this._outlineViewState.sortBy); const tree = this._instantiationService.createInstance( - WorkbenchDataTree | undefined, any, FuzzyScore>, + WorkbenchDataTree | undefined, unknown, FuzzyScore>, 'OutlinePane', this._treeContainer, newOutline.config.delegate, diff --git a/src/vs/workbench/contrib/outline/browser/outlineViewState.ts b/src/vs/workbench/contrib/outline/browser/outlineViewState.ts index 2adc50df7c6..7d779db1522 100644 --- a/src/vs/workbench/contrib/outline/browser/outlineViewState.ts +++ b/src/vs/workbench/contrib/outline/browser/outlineViewState.ts @@ -66,7 +66,7 @@ export class OutlineViewState implements IOutlineViewState { if (!raw) { return; } - let data: any; + let data; try { data = JSON.parse(raw); } catch (e) { diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index e0d64c4acd2..9cb7c8962ee 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -274,12 +274,12 @@ export class OutputEditor extends AbstractTextResourceEditor { ambiguousCharacters: false, }; - const outputConfig = this.configurationService.getValue('[Log]'); + const outputConfig = this.configurationService.getValue<{ 'editor.minimap.enabled'?: boolean; 'editor.wordWrap'?: 'off' | 'on' | 'wordWrapColumn' | 'bounded' }>('[Log]'); if (outputConfig) { if (outputConfig['editor.minimap.enabled']) { options.minimap = { enabled: true }; } - if ('editor.wordWrap' in outputConfig) { + if (outputConfig['editor.wordWrap']) { options.wordWrap = outputConfig['editor.wordWrap']; } } diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index d55e42ba771..34940ddcb5a 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -683,7 +683,7 @@ export class FileOutputChannelModel extends AbstractFileOutputChannelModel imple } override update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { - const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); + const loadModelPromise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); loadModelPromise.then(() => { if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) { if (isNumber(till)) { @@ -729,7 +729,7 @@ export class MultiFileOutputChannelModel extends AbstractFileOutputChannelModel } override clear(): void { - const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); + const loadModelPromise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); loadModelPromise.then(() => { this.multifileOutput.resetToEnd(); this.doUpdate(OutputChannelUpdateMode.Clear, true); diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 8d1758abdba..562f4d1504c 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -293,7 +293,7 @@ interface InstalledThemesPickerOptions { class InstalledThemesPicker { constructor( private readonly options: InstalledThemesPickerOptions, - private readonly setTheme: (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => Promise, + private readonly setTheme: (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => Promise, private readonly getMarketplaceColorThemes: (publisher: string, name: string, version: string) => Promise, @IQuickInputService private readonly quickInputService: IQuickInputService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @@ -611,7 +611,7 @@ interface ThemeItem extends IQuickPickItem { } function isItem(i: QuickPickInput): i is ThemeItem { - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any return (i)['type'] !== 'separator'; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 812f5ef7abe..06f909eac18 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -824,7 +824,7 @@ export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint< commandsExtensionPoint.setHandler(extensions => { - function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser) { + function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser) { if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) { return; diff --git a/src/vs/workbench/services/driver/browser/driver.ts b/src/vs/workbench/services/driver/browser/driver.ts index 779dd9b0997..e1c8c1b0ace 100644 --- a/src/vs/workbench/services/driver/browser/driver.ts +++ b/src/vs/workbench/services/driver/browser/driver.ts @@ -190,7 +190,7 @@ export class BrowserWindowDriver implements IWindowDriver { throw new Error(`Terminal not found: ${selector}`); } - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const xterm = (element as any).xterm; if (!xterm) { @@ -212,7 +212,7 @@ export class BrowserWindowDriver implements IWindowDriver { throw new Error(`Element not found: ${selector}`); } - // eslint-disable-next-line local/code-no-any-casts + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const xterm = (element as any).xterm as (XtermTerminal | undefined); if (!xterm) { diff --git a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts index 94edbbdfe39..458e0221dd7 100644 --- a/src/vs/workbench/services/languageStatus/common/languageStatusService.ts +++ b/src/vs/workbench/services/languageStatus/common/languageStatusService.ts @@ -53,7 +53,7 @@ class LanguageStatusServiceImpl implements ILanguageStatusService { private readonly _provider = new LanguageFeatureRegistry(); - readonly onDidChange: Event = this._provider.onDidChange; + readonly onDidChange = Event.map(this._provider.onDidChange, () => undefined); addStatus(status: ILanguageStatus): IDisposable { return this._provider.register(status.selector, status); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 8bf44ae3d3e..b29f437eaaf 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -51,7 +51,7 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr }); } - private async _presentConnectionError(err: any): Promise { + private async _presentConnectionError(err: Error): Promise { await this._dialogService.prompt({ type: Severity.Error, message: nls.localize('connectionError', "An unexpected error occurred that requires a reload of this page."), diff --git a/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts index 5199be0dc2f..1ab972c1ea5 100644 --- a/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/browser/workbenchCommonProperties.ts @@ -28,7 +28,7 @@ export function resolveWorkbenchCommonProperties( remoteAuthority?: string, productIdentifier?: string, removeMachineId?: boolean, - resolveAdditionalProperties?: () => { [key: string]: any } + resolveAdditionalProperties?: () => { [key: string]: unknown } ): ICommonProperties { const result: ICommonProperties = Object.create(null); const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION)!; From 124710c5ffc06cedba2e4975d89f3fabcc0dada9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 07:43:12 +0100 Subject: [PATCH 1761/4355] Git - add command to compare a tag (#273694) * Git - update command label * Git - add command to compare a tag --- extensions/git/package.json | 23 +++++- extensions/git/package.nls.json | 3 +- extensions/git/src/commands.ts | 130 +++++++++++++++++--------------- 3 files changed, 91 insertions(+), 65 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index bc8b9d3d37b..775a19bd5c6 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -515,8 +515,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.graph.compareBranches", - "title": "%command.graphCompareBranches%", + "command": "git.graph.compareBranch", + "title": "%command.graphCompareBranch%", "category": "Git", "enablement": "!operationInProgress" }, @@ -592,6 +592,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.graph.compareTag", + "title": "%command.graphCompareTag%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.deleteRemoteTag", "title": "%command.deleteRemoteTag%", @@ -1601,13 +1607,17 @@ "when": "false" }, { - "command": "git.graph.compareBranches", + "command": "git.graph.compareBranch", "when": "false" }, { "command": "git.graph.deleteTag", "when": "false" }, + { + "command": "git.graph.compareTag", + "when": "false" + }, { "command": "git.graph.cherryPick", "when": "false" @@ -2276,7 +2286,7 @@ "group": "2_branch@2" }, { - "command": "git.graph.compareBranches", + "command": "git.graph.compareBranch", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\//", "group": "2_branch@3" }, @@ -2284,6 +2294,11 @@ "command": "git.graph.deleteTag", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", "group": "3_tag@2" + }, + { + "command": "git.graph.compareTag", + "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", + "group": "3_tag@3" } ], "editor/title": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index dd41ad67f38..924d219a095 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -135,9 +135,10 @@ "command.graphCheckout": "Checkout", "command.graphCheckoutDetached": "Checkout (Detached)", "command.graphCherryPick": "Cherry Pick", - "command.graphCompareBranches": "Compare Branches...", + "command.graphCompareBranch": "Compare Branch...", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", + "command.graphCompareTag": "Compare Tag...", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 4a59b05ee36..0fd7fe5e095 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -3109,70 +3109,16 @@ export class CommandCenter { await this._deleteBranch(repository, remoteName, refName, { remote: true }); } + @command('git.graph.compareBranch', { repository: true }) + async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + await this._compareRef(repository, historyItem, historyItemRefId); + } + @command('git.deleteRemoteBranch', { repository: true }) async deleteRemoteBranch(repository: Repository): Promise { await this._deleteBranch(repository, undefined, undefined, { remote: true }); } - @command('git.graph.compareBranches', { repository: true }) - async compareBranches(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { - const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); - if (!historyItemRefId || !historyItemRef) { - return; - } - - const config = workspace.getConfiguration('git'); - const showRefDetails = config.get('showReferenceDetails') === true; - - const getBranchPicks = async () => { - const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); - const processors = [ - new RefProcessor(RefType.Head, BranchItem), - new RefProcessor(RefType.Tag, BranchItem) - ]; - - const itemsProcessor = new RefItemsProcessor(repository, processors); - return itemsProcessor.processRefs(refs); - }; - - const placeHolder = l10n.t('Select a source reference to compare to'); - const sourceRef = await this.pickRef(getBranchPicks(), placeHolder); - - if (!(sourceRef instanceof BranchItem)) { - return; - } - - if (historyItemRefId === sourceRef.refId) { - window.showInformationMessage(l10n.t('The selected references are the same.')); - return; - } - - try { - const changes = await repository.diffTrees(historyItemRefId, sourceRef.refId); - - if (changes.length === 0) { - window.showInformationMessage(l10n.t('The selected references have no differences.')); - return; - } - - const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRefId)); - - const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; - const multiDiffSourceUri = Uri.from({ - scheme: 'git-branch-compare', - path: `${repository.root}/${sourceRef.refId}..${historyItemRefId}` - }); - - await commands.executeCommand('_workbench.openMultiDiffEditor', { - multiDiffSourceUri, - title, - resources - }); - } catch (err) { - throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); - } - } - private async _deleteBranch(repository: Repository, remote: string | undefined, name: string | undefined, options: { remote: boolean; force?: boolean }): Promise { let run: (force?: boolean) => Promise; @@ -3806,6 +3752,11 @@ export class CommandCenter { await repository.deleteTag(historyItemRef.name); } + @command('git.graph.compareTag', { repository: true }) + async compareTags(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + await this._compareRef(repository, historyItem, historyItemRefId); + } + @command('git.deleteRemoteTag', { repository: true }) async deleteRemoteTag(repository: Repository): Promise { const config = workspace.getConfiguration('git'); @@ -5145,6 +5096,65 @@ export class CommandCenter { config.update(setting, !enabled, true); } + async _compareRef(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); + if (!historyItemRefId || !historyItemRef) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a source reference to compare to'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem)) { + return; + } + + if (historyItemRef.id === sourceRef.refId) { + window.showInformationMessage(l10n.t('The selected references are the same.')); + return; + } + + try { + const changes = await repository.diffTrees(historyItemRef.id, sourceRef.refId); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('The selected references have no differences.')); + return; + } + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRef.id)); + + const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; + const multiDiffSourceUri = Uri.from({ + scheme: 'git-ref-compare', + path: `${repository.root}/${sourceRef.refId}..${historyItemRef.id}` + }); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title, + resources + }); + } catch (err) { + throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); + } + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; From 74b9fc76c78839b7ab913eadd421a131b55723ce Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 08:53:30 +0100 Subject: [PATCH 1762/4355] chat - more feature support for agent sessions view --- .github/CODENOTIFY | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 4a6d179c43d..2bc747df423 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -103,6 +103,7 @@ src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero src/vs/workbench/contrib/chat/browser/agentSessions/** @bpasero +src/vs/workbench/contrib/chat/browser/chatSessions/** @bpasero src/vs/workbench/contrib/localization/** @TylerLeonhardt src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @TylerLeonhardt src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno From f97f4346980ab796ddda7d0125caa828fabd00f5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 09:28:56 +0100 Subject: [PATCH 1763/4355] agent sessions - support list DND --- .../agentSessions/agentSessionsView.ts | 22 ++++++++-- .../agentSessions/agentSessionsViewer.ts | 42 ++++++++++++++++++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 8422e0d77aa..aecaf904ef5 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -25,7 +25,7 @@ import { IThemeService } from '../../../../../platform/theme/common/themeService import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { $, append } from '../../../../../base/browser/dom.js'; import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem } from './agentSessionViewModel.js'; -import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; +import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; import { IAction, toAction } from '../../../../../base/common/actions.js'; @@ -40,12 +40,13 @@ import { ChatSessionUri } from '../../common/chatUri.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { assertReturnsDefined } from '../../../../../base/common/types.js'; +import { assertReturnsDefined, upcast } from '../../../../../base/common/types.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { URI } from '../../../../../base/common/uri.js'; import { DeferredPromise } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { IEditorOptions } from '../../../../../platform/editor/common/editor.js'; export class AgentSessionsView extends ViewPane { @@ -121,7 +122,12 @@ export class AgentSessionsView extends ViewPane { } if (session.resource.scheme !== ChatSessionUri.scheme) { - await this.openerService.open(session.resource); + await this.openerService.open(session.resource, { + editorOptions: upcast({ + ...e.editorOptions, + title: { preferred: session.label } + }) + }); return; } @@ -144,7 +150,14 @@ export class AgentSessionsView extends ViewPane { sessionOptions.ignoreInView = true; - await this.editorService.openEditor({ resource: sessionResource, options: sessionOptions }); + await this.editorService.openEditor({ + resource: sessionResource, + options: upcast({ + ...sessionOptions, + title: { preferred: session.label }, + ...e.editorOptions + }) + }); } private registerActions(): void { @@ -254,6 +267,7 @@ export class AgentSessionsView extends ViewPane { new AgentSessionsDataSource(), { accessibilityProvider: new AgentSessionsAccessibilityProvider(), + dnd: this.instantiationService.createInstance(AgentSessionsDragAndDrop), identityProvider: new AgentSessionsIdentityProvider(), horizontalScrolling: false, multipleSelectionSupport: false, diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 853666596a4..11c7c2b2cae 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -11,8 +11,8 @@ import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/ import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; -import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter } from '../../../../../base/browser/ui/tree/tree.js'; -import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction } from '../../../../../base/browser/ui/tree/tree.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; import { AgentSessionStatus, IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js'; import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; @@ -22,6 +22,11 @@ import { FuzzyScore, createMatches } from '../../../../../base/common/filters.js import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { allowedChatMarkdownHtmlTags } from '../chatContentMarkdownRenderer.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; +import { IDragAndDropData } from '../../../../../base/browser/dnd.js'; +import { ListViewTargetSector } from '../../../../../base/browser/ui/list/listView.js'; +import { coalesce } from '../../../../../base/common/arrays.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { fillEditorsDragData } from '../../../../browser/dnd.js'; interface IAgentSessionItemTemplate { readonly element: HTMLElement; @@ -231,3 +236,36 @@ export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressib return undefined; // not enabled } } + +export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAndDrop { + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + } + + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { + const elements = data.getData() as IAgentSessionViewModel[]; + const uris = coalesce(elements.map(e => e.resource)); + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent)); + } + + getDragURI(element: IAgentSessionViewModel): string | null { + return element.resource.toString(); + } + + getDragLabel?(elements: IAgentSessionViewModel[], originalEvent: DragEvent): string | undefined { + if (elements.length === 1) { + return elements[0].label; + } + + return localize('agentSessions.dragLabel', "{0} agent sessions", elements.length); + } + + onDragOver(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + return false; + } + + drop(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { } +} From 50835cbc1f6f5fabfd174023137b7ff13dc0c999 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 10:01:21 +0100 Subject: [PATCH 1764/4355] agent sessions - support list context menu --- .../agentSessions/agentSessionViewModel.ts | 12 ++---- .../agentSessions/agentSessionsView.ts | 39 ++++++++++++++++++- .../agentSessions/agentSessionsViewer.ts | 11 +++--- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index a96849930a5..180a5abe5b3 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -12,7 +12,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; //#region Interfaces, Types @@ -29,12 +29,6 @@ export interface IAgentSessionsViewModel { resolve(provider: string | string[] | undefined): Promise; } -export const enum AgentSessionStatus { - Failed = 0, - Completed = 1, - InProgress = 2 -} - export interface IAgentSessionViewModel { readonly provider: IChatSessionItemProvider; @@ -42,7 +36,7 @@ export interface IAgentSessionViewModel { readonly id: string; readonly resource: URI; - readonly status?: AgentSessionStatus; + readonly status?: ChatSessionStatus; readonly label: string; readonly description: string | IMarkdownString; @@ -164,7 +158,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions label: session.label, description: session.description || new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), icon: session.iconPath, - status: session.status as unknown as AgentSessionStatus, + status: session.status, timing: { startTime: session.timing.startTime, endTime: session.timing.endTime diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index aecaf904ef5..e064c482276 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -30,10 +30,10 @@ import { defaultButtonStyles } from '../../../../../platform/theme/browser/defau import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; import { IAction, toAction } from '../../../../../base/common/actions.js'; import { FuzzyScore } from '../../../../../base/common/filters.js'; -import { MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; -import { findExistingChatEditorByUri, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; +import { findExistingChatEditorByUri, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; import { ChatSessionUri } from '../../common/chatUri.js'; @@ -47,6 +47,11 @@ import { DeferredPromise } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { IEditorOptions } from '../../../../../platform/editor/common/editor.js'; +import { ITreeContextMenuEvent } from '../../../../../base/browser/ui/tree/tree.js'; +import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; +import { getActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IChatService } from '../../common/chatService.js'; +import { IChatWidgetService } from '../chat.js'; export class AgentSessionsView extends ViewPane { @@ -68,6 +73,9 @@ export class AgentSessionsView extends ViewPane { @IProgressService private readonly progressService: IProgressService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, + @IChatService private readonly chatService: IChatService, + @IMenuService private readonly menuService: IMenuService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); @@ -113,6 +121,10 @@ export class AgentSessionsView extends ViewPane { this.commandService.executeCommand(ACTION_ID_OPEN_CHAT); } })); + + this._register(list.onContextMenu((e) => { + this.showContextMenu(e); + })); } private async openAgentSession(e: IOpenEvent) { @@ -160,6 +172,29 @@ export class AgentSessionsView extends ViewPane { }); } + private showContextMenu({ element: session, anchor }: ITreeContextMenuEvent): void { + if (!session) { + return; + } + + const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.contextKeyService.createOverlay(getSessionItemContextOverlay( + session, + session.provider, + this.chatWidgetService, + this.chatService, + this.editorGroupsService + ))); + + const marshalledSession = { session, $mid: MarshalledId.ChatSessionContext }; + const { secondary } = getActionBarActions(menu.getActions({ arg: marshalledSession, shouldForwardArgs: true }), 'inline'); this.contextMenuService.showContextMenu({ + getActions: () => secondary, + getAnchor: () => anchor, + getActionsContext: () => marshalledSession, + }); + + menu.dispose(); + } + private registerActions(): void { this._register(registerAction2(class extends ViewAction { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 11c7c2b2cae..df35463d5b3 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -13,7 +13,7 @@ import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compres import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction } from '../../../../../base/browser/ui/tree/tree.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; -import { AgentSessionStatus, IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js'; +import { IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js'; import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { Codicon } from '../../../../../base/common/codicons.js'; @@ -27,6 +27,7 @@ import { ListViewTargetSector } from '../../../../../base/browser/ui/list/listVi import { coalesce } from '../../../../../base/common/arrays.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { fillEditorsDragData } from '../../../../browser/dnd.js'; +import { ChatSessionStatus } from '../../common/chatSessionsService.js'; interface IAgentSessionItemTemplate { readonly element: HTMLElement; @@ -125,13 +126,13 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer Date: Tue, 28 Oct 2025 10:09:27 +0100 Subject: [PATCH 1765/4355] agent sessions - surface action to install extensions --- .../chat/browser/agentSessions/agentSessionsView.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index e064c482276..8803b758d83 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -28,7 +28,7 @@ import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; -import { IAction, toAction } from '../../../../../base/common/actions.js'; +import { IAction, Separator, toAction } from '../../../../../base/common/actions.js'; import { FuzzyScore } from '../../../../../base/common/filters.js'; import { IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IChatSessionsService } from '../../common/chatSessionsService.js'; @@ -269,6 +269,15 @@ export class AgentSessionsView extends ViewPane { run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.type}`) })); } + + actions.push(new Separator()); + + actions.push(toAction({ + id: 'install-extensions', + label: localize('chatSessions.installExtensions', "Install Chat Extensions..."), + run: () => this.commandService.executeCommand('chat.sessions.gettingStarted') + })); + return actions; } }, From 7d0c1c208ccab9cf09aef7380f5229da522dfca2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 10:53:43 +0100 Subject: [PATCH 1766/4355] agent sessions - add additional provider specific actions --- .../agentSessions/agentSessionsView.ts | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 8803b758d83..d537e6f3153 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -24,7 +24,7 @@ import { IOpenerService } from '../../../../../platform/opener/common/opener.js' import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { $, append } from '../../../../../base/browser/dom.js'; -import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem } from './agentSessionViewModel.js'; +import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, isLocalAgentSessionItem } from './agentSessionViewModel.js'; import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js'; @@ -257,28 +257,7 @@ export class AgentSessionsView extends ViewPane { contextMenuProvider: this.contextMenuService, actions: { getActions: () => { - const actions: IAction[] = []; - for (const provider of this.chatSessionsService.getAllChatSessionContributions()) { - if (provider.type === LOCAL_AGENT_SESSION_TYPE) { - continue; // local is the primary action - } - - actions.push(toAction({ - id: `newChatSessionFromProvider.${provider.type}`, - label: localize('newChatSessionFromProvider', "New Session ({0})", provider.displayName), - run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.type}`) - })); - } - - actions.push(new Separator()); - - actions.push(toAction({ - id: 'install-extensions', - label: localize('chatSessions.installExtensions', "Install Chat Extensions..."), - run: () => this.commandService.executeCommand('chat.sessions.gettingStarted') - })); - - return actions; + return this.getNewSessionActions(); } }, addPrimaryActionToDropdown: false, @@ -290,6 +269,41 @@ export class AgentSessionsView extends ViewPane { this._register(newSessionButton.onDidClick(() => primaryAction.run())); } + private getNewSessionActions(): IAction[] { + const actions: IAction[] = []; + for (const provider of this.chatSessionsService.getAllChatSessionContributions()) { + + // Generic action to create new provider specific session + actions.push(toAction({ + id: `newChatSessionFromProvider.${provider.type}`, + label: localize('newChatSessionFromProvider', "New {0}", provider.displayName), + run: () => this.commandService.executeCommand(`${NEW_CHAT_SESSION_ACTION_ID}.${provider.type}`) + })); + + // Collect provider specific additional actions + const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.scopedContextKeyService.createOverlay([[ChatContextKeys.sessionType.key, provider.type]])); + const primaryActions = getActionBarActions( + menu.getActions({ shouldForwardArgs: true }), + 'submenu', + ).primary; + if (primaryActions.length > 0) { + actions.push(...primaryActions); + actions.push(new Separator()); + } + menu.dispose(); + } + + // Install more + actions.push(new Separator()); + actions.push(toAction({ + id: 'install-extensions', + label: localize('chatSessions.installExtensions', "Install Chat Extensions..."), + run: () => this.commandService.executeCommand('chat.sessions.gettingStarted') + })); + + return actions; + } + //#endregion //#region Sessions List From 294b045d4b11bc52f54918996bed945d2bbd330d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:10:07 +0100 Subject: [PATCH 1767/4355] Git - fix bug with comparing with a remote branch (#273705) * Git - fix bug with comparing with a remote branch * Update placeholder --- extensions/git/src/commands.ts | 6 +++--- extensions/git/src/git.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 0fd7fe5e095..baab4069724 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -116,7 +116,7 @@ class RefItem implements QuickPickItem { case RefType.Head: return `refs/heads/${this.ref.name}`; case RefType.RemoteHead: - return `refs/remotes/${this.ref.remote}/${this.ref.name}`; + return `refs/remotes/${this.ref.name}`; case RefType.Tag: return `refs/tags/${this.ref.name}`; } @@ -5117,7 +5117,7 @@ export class CommandCenter { return itemsProcessor.processRefs(refs); }; - const placeHolder = l10n.t('Select a source reference to compare to'); + const placeHolder = l10n.t('Select a source reference to compare with'); const sourceRef = await this.pickRef(getRefPicks(), placeHolder); if (!(sourceRef instanceof BranchItem)) { @@ -5130,7 +5130,7 @@ export class CommandCenter { } try { - const changes = await repository.diffTrees(historyItemRef.id, sourceRef.refId); + const changes = await repository.diffTrees(sourceRef.refId, historyItemRef.id); if (changes.length === 0) { window.showInformationMessage(l10n.t('The selected references have no differences.')); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index bb3685a53e4..98fbc032ef8 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1730,6 +1730,8 @@ export class Repository { args.push(treeish2); } + args.push('--'); + const gitResult = await this.exec(args); if (gitResult.exitCode) { return []; From ea4bbcf47d5d365560529615f4690bcaab63d491 Mon Sep 17 00:00:00 2001 From: Abrifq Date: Tue, 21 Oct 2025 10:42:59 +0300 Subject: [PATCH 1768/4355] Replace command prompt input "`" wrap with wrap --- src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index ed400202bb9..8c05fc28f23 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -109,7 +109,7 @@ export function refreshShellIntegrationInfoStatus(instance: ITerminalInstance) { } const combinedString = instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.getCombinedString(); if (combinedString !== undefined) { - detailedAdditions.push(`Prompt input: \`${combinedString}\``); + detailedAdditions.push(`Prompt input: ${combinedString.replaceAll('<', '<').replaceAll('>', '>')}`); } const detailedAdditionsString = detailedAdditions.length > 0 ? '\n\n' + detailedAdditions.map(e => `- ${e}`).join('\n') From dcf03f0941e430be365a29eeb7567aae53d23250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 09:46:38 +0000 Subject: [PATCH 1769/4355] Enable rendering HTML in terminal tooltip to fix command input wrapping --- src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index 8c05fc28f23..93263646130 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -44,7 +44,7 @@ export function getInstanceHoverInfo(instance: ITerminalInstance, storageService }); const shellProcessString = getShellProcessTooltip(instance, !!showDetailed); - const content = new MarkdownString(instance.title + shellProcessString + statusString, { supportThemeIcons: true }); + const content = new MarkdownString(instance.title + shellProcessString + statusString, { supportThemeIcons: true, supportHtml: true }); return { content, actions }; } From 9b8c33631014f6448b69bab1227e16a3f1e536fa Mon Sep 17 00:00:00 2001 From: Abrifq Date: Tue, 28 Oct 2025 09:03:06 +0300 Subject: [PATCH 1770/4355] Fix backticks not showing up in prompt input --- src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index 93263646130..4fc06c39cac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -109,7 +109,7 @@ export function refreshShellIntegrationInfoStatus(instance: ITerminalInstance) { } const combinedString = instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.getCombinedString(); if (combinedString !== undefined) { - detailedAdditions.push(`Prompt input: ${combinedString.replaceAll('<', '<').replaceAll('>', '>')}`); + detailedAdditions.push(`Prompt input: ${combinedString.replaceAll('<', '<').replaceAll('>', '>').replaceAll('`', '\\`')}`); } const detailedAdditionsString = detailedAdditions.length > 0 ? '\n\n' + detailedAdditions.map(e => `- ${e}`).join('\n') From d30c10e799cb6f41dd385fb126371750250922e0 Mon Sep 17 00:00:00 2001 From: Abrifq Date: Tue, 28 Oct 2025 10:59:42 +0300 Subject: [PATCH 1771/4355] Escape more markdown to keep prompt input as visually same as possible --- .../workbench/contrib/terminal/browser/terminalTooltip.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts index 4fc06c39cac..ed6bde8b938 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTooltip.ts @@ -109,7 +109,11 @@ export function refreshShellIntegrationInfoStatus(instance: ITerminalInstance) { } const combinedString = instance.capabilities.get(TerminalCapability.CommandDetection)?.promptInputModel.getCombinedString(); if (combinedString !== undefined) { - detailedAdditions.push(`Prompt input: ${combinedString.replaceAll('<', '<').replaceAll('>', '>').replaceAll('`', '\\`')}`); + const escapedPromptInput = combinedString + .replaceAll('<', '<').replaceAll('>', '>') //Prevent escaping from wrapping + .replaceAll(/\((.+?)(\|?(?: (?:.+?)?)?)\)/g, '(<$1>$2)') //Escape links as clickable links + .replaceAll(/([\[\]\(\)\-\*\!\#\`])/g, '\\$1'); //Comment most of the markdown elements to not render them inside + detailedAdditions.push(`Prompt input: \n${escapedPromptInput}\n`); } const detailedAdditionsString = detailedAdditions.length > 0 ? '\n\n' + detailedAdditions.map(e => `- ${e}`).join('\n') From d60f13ee3fa3d51a886a1126a0588a21ac50c133 Mon Sep 17 00:00:00 2001 From: Dmitry Guketlev Date: Tue, 28 Oct 2025 11:39:55 +0100 Subject: [PATCH 1772/4355] Fix memory leak in InlineEditsGutterIndicator (#273549) (#273550) * Fix memory leak in InlineEditsGutterIndicator (#273549) --------- Co-authored-by: Henning Dieterichs --- .../browser/view/inlineEdits/components/gutterIndicatorView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index 457171b97ad..7eae42f5edb 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -96,7 +96,7 @@ export class InlineEditsGutterIndicator extends Disposable { if (!range) { return undefined; } return { range, - lineOffsetRange: this._editorObs.observeLineOffsetRange(range, this._store), + lineOffsetRange: this._editorObs.observeLineOffsetRange(range, reader.store), }; }); this._stickyScrollController = StickyScrollController.get(this._editorObs.editor); From f8fbbc0366f536ff101e1ad8622c8ff16c2ce470 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 12:02:42 +0100 Subject: [PATCH 1773/4355] agent sessions - move local provider into its own contribution --- .../contrib/chat/browser/chat.contribution.ts | 2 ++ .../browser/chatSessions/chatSessionTracker.ts | 2 ++ .../chatSessions/localChatSessionsProvider.ts | 6 +++++- .../browser/chatSessions/view/chatSessionsView.ts | 14 -------------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 15967097e6f..06588e8160e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -107,6 +107,7 @@ import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from './chatPart import { ChatPasteProvidersFeature } from './chatPasteProviders.js'; import { QuickChatService } from './chatQuick.js'; import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js'; +import { LocalChatSessionsProvider } from './chatSessions/localChatSessionsProvider.js'; import { ChatSessionsView, ChatSessionsViewContrib } from './chatSessions/view/chatSessionsView.js'; import { ChatSetupContribution, ChatTeardownContribution } from './chatSetup.js'; import { ChatStatusBarEntry } from './chatStatus.js'; @@ -986,6 +987,7 @@ registerWorkbenchContribution2(ChatTransferContribution.ID, ChatTransferContribu registerWorkbenchContribution2(ChatContextContributions.ID, ChatContextContributions, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatResponseResourceFileSystemProvider.ID, ChatResponseResourceFileSystemProvider, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(PromptUrlHandler.ID, PromptUrlHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(LocalChatSessionsProvider.ID, LocalChatSessionsProvider, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatSessionsViewContrib.ID, ChatSessionsViewContrib, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatSessionsView.ID, ChatSessionsView, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index a7ff66133ac..6b9e3e9866d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -49,6 +49,8 @@ export class ChatSessionTracker extends Disposable { const editor = e.editor as ChatEditorInput; const sessionType = getChatSessionType(editor); + this.chatSessionsService.notifySessionItemsChanged(sessionType); + // Emit targeted event for this session type this._onDidChangeEditors.fire({ sessionType, kind: e.kind }); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 155941ec065..c7979b74897 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -10,6 +10,7 @@ import { Schemas } from '../../../../../base/common/network.js'; import { IObservable } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import * as nls from '../../../../../nls.js'; +import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroupsService, IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js'; import { IChatModel } from '../../common/chatModel.js'; @@ -20,7 +21,8 @@ import { IChatWidgetService, IChatWidget } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { isChatSession, getChatSessionType, ChatSessionItemWithProvider } from './common.js'; -export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider { +export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { + static readonly ID = 'workbench.contrib.localChatSessionsProvider'; static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot'; static readonly HISTORY_NODE_ID = 'show-history'; readonly chatSessionType = 'local'; @@ -45,6 +47,8 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio ) { super(); + this._register(this.chatSessionsService.registerChatSessionItemProvider(this)); + this.initializeCurrentEditorSet(); this.registerWidgetListeners(); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index b85da822f9c..3e0dfd3e909 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -29,7 +29,6 @@ import { IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsSer import { AGENT_SESSIONS_VIEWLET_ID } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; -import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { SessionsViewPane } from './sessionsViewPane.js'; export class ChatSessionsView extends Disposable implements IWorkbenchContribution { @@ -55,7 +54,6 @@ export class ChatSessionsView extends Disposable implements IWorkbenchContributi export class ChatSessionsViewContrib extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatSessions'; - private localProvider: LocalChatSessionsProvider | undefined; private readonly sessionTracker: ChatSessionTracker; private readonly registeredViewDescriptors: Map = new Map(); @@ -68,12 +66,6 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon super(); this.sessionTracker = this._register(this.instantiationService.createInstance(ChatSessionTracker)); - this.setupEditorTracking(); - - // Create and register the local chat sessions provider immediately - // This ensures it's available even when the view container is not initialized - this.localProvider = this._register(this.instantiationService.createInstance(LocalChatSessionsProvider)); - this._register(this.chatSessionsService.registerChatSessionItemProvider(this.localProvider)); // Initial check void this.updateViewRegistration(); @@ -87,12 +79,6 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon })); } - private setupEditorTracking(): void { - this._register(this.sessionTracker.onDidChangeEditors(e => { - this.chatSessionsService.notifySessionItemsChanged(e.sessionType); - })); - } - private getAllChatSessionItemProviders(): IChatSessionItemProvider[] { return Array.from(this.chatSessionsService.getAllChatSessionItemProviders()); } From 22c141cc9614a2fa5d81e4ead7cebe9a514326fc Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 28 Oct 2025 11:03:38 +0000 Subject: [PATCH 1774/4355] codicons: add branch status icons and reassign git-branch codepoints Add branchDirty, branchStagedChanges and branchMergeRebaseInProgress codicons, and move git-branch, git-branch-create and git-branch-delete to new codepoints. Update codicon.ttf to include the glyph changes. --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 119864 -> 121112 bytes src/vs/base/common/codiconsLibrary.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 5adbf1621cd9607cee95825f55a62933f9d5dbb1..1249a8b970829937929a6e01ddbe33a3628e7d7d 100644 GIT binary patch delta 10082 zcmb{233L=yx(D$8*PV10LiSFF5JE^GVMzjnY=#gP5fKoTB`hMcBoMX`11LtIK@brU zfkZ@Q6A>|@A}AmtA_6L-5fKp)5fv4yN=00TLB#jJ`8;pVoA=(Fne(29U;n$3w!7-q zt$S~Mon4!QR&5Jf*1^AR_8m=tke`91wDFVg92Z^{`W2AT60&q?dD-aFp)1x80&s@% z%J~CVi93_e_fX67Dbr^TJ@v;ofH**K@Z_muM|Wske;#O^2n2YhjGkHPZYqoT`*^-S zxnlH`vYJaT_lNAB#h+cQoI0)X^c}A!H-qe%4kT_|z9%Kz<%00V^!CuyCd-`;E|+V2 zk`%dWu1MJS;qF(YW`d`nW{M|l`{2M00l8}%UXFlOa5p^5V1@#LYY`AInQs)0NdClS zEwYwaZ(48LA$EUfYE4GyRg+@*Tel?2U2;2)OPRbS17(90%4Q6gi?Tx2$Z9-`Vz~wD z@sqU1b#e&H5HCMr7dA+h^p){cE~~}FU5gej=NRLsF#+>Lv1AMVEzJb)`S3eR8*p7&!b zUcfeN$BTFgJMl7J!K>Jd*RT(-;|(0bn|KRv<1csz@8Kjq!Dsjq-{2fEF;ibM#?A|En}rrCdfpYB$H)|R7j=VCevhw%#=H279N#3;+MHHPwtk*a=$E* z2V|)%mxpDQJR*WwtzlNAa*cE05x1d<6dq`Bi?w^>VK)z(jm1 zS(1q_K?k(Q0O=+lqAdnvGB#od>Sc^fmHRMDw#aq3fPi?ck!o2hxkyDTw8UY&D?f6e z@zl%rF1~SW)Y+z3Cn+NVtA#G(#>jrzly$tWdIsIaP7=f>o(Fj={Q3 zp_o~vIJUu>rr=-CCme?j{KTB0IP}3XM@P7hd57X~2y3R|zzFM31rGLBwc=n2%d`@4 z*o4J_-+(#d*-FgL%mMu7LHcsTv;;|a=3FH`nDZ3JTv&H2j=r$wD~`jk7ATx%-lOCL z=0e3$8rHoEo0yA~9DpCzVr~!zaai{&j&-n>D0zkXppxgA=BPs`f38CJdG^bG4g#bFrM5rw(T_Z25^SVtA7aabQHPU!6V$M`^; z+hKjEILpI2t~lSr`bcr+hjl`64FKz7#nk|;lZxvCSf41a5MY_>hqzXN^{L{j0hYOD z$j=1?tj`pe60puFE-GM|zDrzQ!1_XQp#kel#U%%Il{^3j3J9Dz2Dd{Z(=81nZ*WDhk%$6xUO*E-9|8VEv}J z#)5TOakT~Ocg1xVtSjW_1D9a1{!m2aXOMJYn$IA~W;W;fx0`dL6VrSk$yLl2N;)%BloT<|j*xU=njInO$}~Gd(vxX+ zgyb4#x{}_^)=K&?+bFJ-`NZrkNq?rwWN+vQ3luTlpRw9|qEL1XuX^b`jN~?Ev65-L{%zBIBr}-Zl+0vyS8^w_hvHoV?5hN}gt#zCcpPG<|_&E7SA^ zk{6h_DA~p|eSu^<^H$~O4HRtCG03diY|}AFb~48(d6{YY2+1C%=_4e2nPp1$F~=#{ z&m6DhFtc3AUzny(ksM=AH0OUJH;!||bSjb)Ow*}IPBKlWBKd@AIu*&M%&AIFGff90 z`HX2g7|9uCm6Fey)0BM0oUY^>o`2hPJd$(F+m(FFyhE9ShdootcTCeENxo-RD>=`+ zOUVVM>8K=sW6oA`i8)8{zLV$P;6uapI+q(HmzndFi@&l>PbJ=`5Q9Yo{y4GH*cxMdz zLB)Gy*vk~}mSHbfyl;lRLh%k7c8x+Nv$jDe@U|NELy9-puvaSHYQuh5!JLj&O1`c+ z-YnH`8g;cY6(@U*l77s!N=&<~Q*w~GUh#$<_Tvhtm>ZOsPkus)*`tk0Ze%{Gx0IM2dRxg+=3yo8)MO?5{1dok-jY~jfobzmKCqAj`-Eai2lmH` z#U0ou70W!ZKT$0Bz&72LSo(qesbXJ%eOj3Uto@l{VF>maW$sPv&lQVBsj$tNB-V{! zf2r^_)0}OR!8PMrr25mjbxy&{{8ow0G<}5RYNpvH@i84Gfz0oec$nWS31j}CB!qch zNh9WuO2U~JOgr&GBDnFBl4$17O3V(KlR;wI&$Js!Fw>kok|5?q#i|!nqruV|9JYgj51HnDiRCspJaz*^nLKnuYMGIW#W*-o3Tz68O=3s^Ge!x! zmt*#uShRz~rt+Hy+{g{Kl_BeyJYfb7GUFAidvKa4T*XXKGKkq!Nij1~Nj|fgVm%N} zl0rGNxss;rrA{(8NM9Rkq$snZb5a!^X0}xFEVGqjfe}ucVyO`hd#J&pBb?R> zCzx%NutPX)4PGTINy2HTa6QwUB$9iXnTiEVI9ZCNOE~7F5_q*a9h8{gcT_BF!pT

    H(%s9-ISPf+Fh~q3a5uc8Iu>Z!E!8|o{EK8IM*naXyNoy zILf?Mv1|*cw?ZBBIwhgZKIZ)Q;hUOX+*h%_3#XsLD5g0j#F{T0(;CF;FPs|`%mbn|_slVQ5n5-bQU3ZEAEPY(UtF#=RS# z3aZlP>k4Ei|E{YxzJu7;3^q!dVn6)wWF<-~Fj_n^iJ@$y#H&YtRju+`T}a!V-aq|P>*CfMTAytb-X^Wh?lxE2KG&|W-Jy)IjNTd5 z85=V8Wt_}(WhQ5qWY%X@XVqn0YQMPsxemo0wscJDIIiQ$j;FI@vwLK($UfF7uG6?q zwK;h?8*|R*4$57Tdp0j6Z)Dz?{N((~{F4QR1yu#7uc|E^Q@FM>I{Py^FYA1)D7k2Q zm%J{Ib~##HTwK>Ru4_ftbKOe2o$B7J`}rRId+fcs_0_dKLwok>xw2>dHKEt!U9<6; zlf5E)_3HKFwT0KNy7pA>KG(IruC@>Q%QRuB1p=!&8HhlLND zIBehWoZ+>@otu+x9(nVo5wRmmZ|QZ*)?3bvj2>A&^7yT;TdQu}aqFc~g`?_5w;jD= zv@<4lOyQV{F>j4^jqNjb*4VnSr%S_2^GfHGZYw=mmQq$yHm7W3*{|bfjk`3ye|gLD z>hi-AGA2x)*lVJH?!?`b;wG(_w0m;ocpvU zRR&ZRR4%Qozb)~$s@uM<>REMUTEevQX$Pj4Og}KAaK_{rhi?zP-MVAw9mi+3p1JVO zws&r=POe^5z2`36)$gvuv(jeGopoe(^z0F{*UmmQCuL6coJ;=J{)uxF=PvZmJvOi8 zZqMB-=V#3yHGl2=;|ppRoV+LRp56DHUl_b_%)M2M0v6>h+J9e<`*ti&TfBbpmHUU^ zzhjAO$?zp-9@xCJ_0rm3JBT0{3d9?J=laKXyY{_HCSI4cMxyB#7X3m;(Yb)0kuA97W{rW}g z53j%cc+bZdJbrpZw+)LooO@!>6VArsjdf2JK3V;wwJBrMj;C^-+Os)fbM@wTo-TQM zcU@v#Y2Dsu!k^i=rS+DTTbyTeo?Y@>_;YtZANTy~7glfYy?x8}BQF-cSo`9smsY$~ zzr(X*+>X^dF7K?}`RlIOT^)Ay>$z*-8zjy84 zbFcM#t$ts|zNPzqeZA=Q1N(#b_u2o&8`*DceB;!Cg$Iidt~wNZXz8J^-^|#4wB5R> zAs%ymOD0z_u`K)-v(`QcbAkM{+?>4ZIB#rFT6$h?PFjj5*5?W8q45QC%QsITv5JAPjEMHpu2HMggeqJl9=Aq8yV^4uf-eLIId9?UlAD= z78&V_iu%l#mX+@FrDvu2dc?S00YL%Lfx(_=rpp}@<9E9P1KqA@PjFzg+vVZU>cS#o z5))%QK`sdl!SC8Lxhg5(mtcK($PK44HqIB$zqd#U$}QFl$IG91 z!+E*zROj+aDdyi`WashHN;em1EU!bKc`zIK8nK?AdC|m1x*|dgA{s}={pS_blvj{D zBBUTHVURm2uHIZhzQ_V|J^7->^KxKkKiJUsn*;=hg$(AE@DCS7WP&>+%p2uSxK3Za zU!y{=kL_po%t`k18~z~vsX+{yn?g`bCvU2k(sT10(%UINH^^&tnBA7U9He7|Ik+y8 z*uGV(_94HwPfKeb(ziuc3;v6aOZIw`y%H3&Cpx4)wBd13ny*C*UWMFzYj`lt zX&QBV?&*pF{sb<2x%6wr>muBhk|jCClFhLvmBUz^X+FJXnsInaO$ir{MA`fkhnzHt z4Dh&`XBTx!O6t@lCqjw>JRWyHw}%%*bbeCHqSVx)UWF~)fhz+85<4_a%IVUnc~TQs zV6f@ZreC*-Pe?85U6dM_oSzcl+1ak6Y;Mu8FFv!{YQ=Mxo0BE_i5&LweLU=(_VVWr zCP{3#h_8QGSYrEv_KAt@6QfhYuXhIry4Lv8YXPR+WtzJ*_NrLPcg})ztA-Wz(j4|NQ;5>7&P&m1d4D aA6+rNY}(HIUX_`_9{t_U%Lk<}{67F3w|WZz delta 8907 zcmYk?3z$u1-v;peo*84zj4?CD9592yI8Ea`#%U4~(&S8$9LM>PQ_fqIB!rQqLK2cx z#+i_GnztlLrSeKDt+kSRC3(I2?#KUozw5g$zjg1uXCKyjo@cM;S^sVIj?#O#lwQ^# z)Q%I2fHD_=^h?9XOfPIZcu+A=?-aPG@`z!BhW1^tz9)clk~=~Vg!K-uquU3i#)z?# zr}fPK`zL?_Q2N@K@k0hh4cU?d)H(%(-yAz=+Jx{FF4W&s_57;i28|t7bbVg|xa+bW zyEb9`q_UH{et2;SxaT5JxnTLh%sAcz^<)`VvC|jJg9c%|S)9REVMW`+lZt1AZ)VZ# z$R-k7Jgn4)h^DJD&_rCX`U&b#8Mi;e; zZaz9ze+_3UALc}yo%*@>lG_I*#LA zY|VUriHmp}XZQ)<#7jKFt@x9B_$v4DFb{AizU3QytLR*GmCyt{go&7hsdyOGQ3ExR zh1#fx255*zXpAOkie|_|bF{!6$VXeWLwnqbyU-C`P=K!Ji4hou(HMi7n2iwTVF4E6 z2`s{sSd6833d^t@D^P@|u?nm4EY@N@Hew52!L|@y!*;xmH}EEQU?+BCFZSbry7Da? z!V$cKV|W+u;eC97f8aE}z}NUEzQ+apgrDKTM}U9f3jV-V{E2J$3;$sSrm-R`vkI%S z8Z(*2TFhn+>$54FF^|pJl5Kbg+p_~ho!FV(v4FkVhka3#_p?7A;Da2y5$lth$`*9!oA{oEqBYc9fn8inNSR*6_-{CyF zp&Bw-9Th_SiMO*n$uLH-6iYLjF^ppZ6B)s9zKdTt7#mO;Q7D6G#FfgOJ0;cpd}!6nfw`WaD18;-{#E?ih&|QH&A};AlRE>DrmccsZ zY(uxJa}4iNh74OP=NjfKA2B+u!kcGwf`vEV=u`{uQG+we$AFOA@DtrxV00#i_qf4J zO3gqHk0=)zou}bFX>>Y+x7cux@+retmCFqGDVG}_R<1BSfco$>g*rMnz*}kXt@3HZ zHZ8n+~;B7IQAmF`XG*!TJyD6G9;JMuqO&{>K8BHYcUNf3f;B7aW zT;RQKG|j+sdmx%{;Hh~+Zlk#e-VUQ#2wt(#d<4%OifCqn=Q>q1N5R`|G+V)QZ6%tw z;O#YJ63u1s_8ZM=@D3QwZy|VELO7c0;Jsxu>A^c>H2uMI4K11o;TUU68eui1dyOztS;;U<>0U3arA#->R%RIHD66Rd zeZ4vBE8QCln=0KK3!5q38w>N4)eV~~YZ$gv)--IRbRRCfL+L(T*j`!Nu!FLW(PCIP z>Kb-d)-zfl!*_ct?nL3=X4qTl_FULU>2_SSkcQvDxO2YW(C~g`Bg6j6#)c0lo4Eeh zh7anFJ8I!TWv<~crE39Uq0+U0aJbU7fN+Geh2co0YYpKjWh=um%GQQsmHCF__5Js6 z*B#+QO4oeC$x7FJ!YRtOMi&C`+ZnAX;k#xP&QRWII8*7GSU5|0m*H$>N5eTv*Ycf0 zdXj5i*Yv{2m3JF=z3O){d_sAT;UZ;$;Syz6!y;uj!<9u2G1nJgsyCM)-wtoZ(l>@rGxV6AZsrx^X1@Mmf>&TjeCf@04zQ3BOnW`%?@r zD5o0!pnTZyqS6gK;m^wHhMsbUadi*h4Lso`nU0SN|9c^xXU+3zM z@S5@w!yC$ZM%SS5=NmVh`Hvb_Qa)ywu5^Q0bgc^C4QA0*EBu8<*RSy1U>04;>ih3T zv*@B0{*y+Rx9}GmxFdI?T->tOUutym3;!vj%V7A+j4p`bFE_e0hQGq-A{oB>h6w5^ zSKiVXx^jm9w9z#*{8dI*)9{}$aNV)m@O;sw^y(qk@Xs42DPJ(`s9bC4c4eL6+sgGu z*W~bDH26~KzGFhStL{4{bce9fu#3{|nQ*gmlWR9^*h_cZ1ENcN_*)FzFnz_)S8g@v zq;!WRssO;>W>gD+|C+%$<#xlG%GV9sDc>-v8R&cSrtXL?JK*my?4c|+Dk;Es9VRL+ zz~5z5W`Mujpqp}!;X6t_Zhk!a$j`eS>*vo!&Ws4_y-L>R=#C)DGmRSp&P!3 zjcOV2j~L8Vx-Jp^r95i*Z>8H2;dSLP!w;438kQ(S?`b3ahw^>HkCY!6x*c*a5*}B& z7l}$I@ZD=fMHKk%HG)E=dyS~D0^hwx;9BPsqhbsE6DFj(3;a(Fep8+_)NB0DjH)v5 zPZ`x`;D2sZseymmsAdEI3!~}{d^aFPbsYF#8C7)PpE0WK!2enoLfSxO9{ArF6@1`- zYcNac`bty;g73OYR1SiF&R}LyLA8{$?{x2bgY(J@hTV$hRI47Uu6q{^Dky(8{7LB< z-mdfw%PRvzwS@nRVVLrgVU+S$!&1u2hNYFRA%)RO9dyWT7^6F`>4kC1D~1WmKMWI< zR}CYSe;S4>uNf7#;JdLVa69J4m#EkU-;Fy_*$e&+qXJmby3Cwj@md?}SR9s7>R24r z#1QE19aYB==wluHsMK*etf!1Ls+}RwF*;~dlv%xcsJHIv;2hP{5X2gMs&wxzs<9!^ zK|AQBEN8e(>Gn*xUa7`#a9OFwaM(nt9&z}YGRde?hd{jRyJ&`bWI{E0wTyT7^bXZn4)u7P*rz?)0NeXs)YzLja%ad z8iNj2Dr*?NqO56DRYZ_wR9{3;%c#(+$^2g3iip!}-eF z4Cg9yjOvvL>KlBeY+&f#u%S^k6G0;beE|d48lnm(0@o#?S|@@~Q*A_LPXw-6MFmg< z&5TN+2=WYuDw`XXM-jN@78Obnv@|N2B4}k)JVnsjsEmpr->9I9;C2(jKHX?zn5?|R zsNRa8t-)YrJEIybg7!w$SOj+()nyTMFgUKf%cxe1prgTNWhX=TmFVpHUrkOo>fMow zin<887?pPsxF!=7dJz;Dm3$H0Yv8)Y4MI^F7(q9qf-r*a1_PBnjEckvdK&al{|CK{ zD#r+V8`Y2z^f7QB)Ys58?R`cSW(4;e)tV8wCKgql5xAWd)t?c#gAi4s5xA}r)ua)) zZU||E65SYR=$_z?PB>cWnqE}AM&SBZFkR`!f^ds+m{DmPL7~AVr5mV%2;~TaHA=Tr z!l^(`FiLlX?hoz*MP>0@KPb2#74V^MhZ+7-RI&Emp%!I!m$?`{IQn8t-b6`MTx1m!Fmtl{B?N zMumkHb|+U&UX)yt(k10!YFz5>w5YU+X=^IxR9swXZl%kWmsUQR9-F=>{ZdAoj1yI6 zRqb6Zs@kc{E!8{J2n*FHsW~7kI;$XSN7j{E8MSh2mDG-_eW*_7I;ZPauG_cnqPjck zo~Y~9ORU$RUSYkn*^9FG-xht_n%l1B6yzMNpHqKo{Y~{RHK@^`dxH%PE;P()IJM!% zMjaaMY>dW38*gZQrAebE)9#MAdwrKiT{hm6d(Vb~ zjDpn#H|`y9?~$$px}NNo*lkDmy4|<;Na``G$CjQId-e|XJld;mua&*?dXMP6vrl}V z!aisEw&=T}@3H%G?wfkwh5MWIOYS$f-`;-b`{(uF)c@oIZ68?hK*@mU0W$_%e{k@F z2OqpJFnVDAz|{j!4yrh)%b*#9whuZxIC5~o;CX|$4*q>e){ufBlZU)Fw9C+vVHv}& z6m~9LQ+Q)|`w___x{g>h;>VGLM}`)SJU1$P)T~ioj!qrjbM%bS>qmb%CUH#Rm`h_@ zj9oqU)VQc|1IDc#7mRN^e)oi=3F{|#59K|ya$?rRm6M_;wVJeM(wWKor_`RZY09;! zy{BG(c<#fOr}dq-c-o2Sm8Y+nesxCI8S7`9pP4;#+{|q=f1lNPR>`buvs=s#=JcMk zF_aZLJ-0A4_vRxF9vSz@rg=l>ZJQr8fARdI^UplmNVCCaUk1u@u+QNwo zPdt(R#KI>|Ey`ZBiX0-%Q`N5Z+Y$It5$@q zC@jh-npL!SW$McID@#@ttor?#ldJPqZ+|xV*@4e)eb!skWz9Fwb$_np`R*@-D!)+h z!o0Pk)^1ySc3sxG!gWX1XRaT!zU0N+7mshKx?%B4m0#-r(nlNPHm-g-`Q^1Q-`LcD z)Ar4Io1fo&b4!OU>$hBfWx>|yt<$z1e>M5laj#z9Hgwyy*XF!F=gqorE`4*yj*J~s zcN{97T>M7y#ho2@&e?f(*VJ8K?z*}=ad*z{F1yF=F4>dSYR~Gu759F$FJs^K{i_e; z9eCs5+=C@=<-GOAp}0d64t;qzj*fVz>N_*uxq58Eu@Byj zE6&Vbmr(rT9g{=FKfgV@YCNuJ%~eT-`gZO#*Q~-bmn$(TloVNynUT$^$1y3nB^xED z(;m5XFSj+DWRB1=V;ijUnAn-CdQr&MGbbvH8N|2;e)t6Xw&xm%m4 z7)D2BMa9HK6`wsnrF*EfjzN=fvnKg$+$cGvdS+x&a^q&nw{|o$GF`txSJxgUWw22` zn>ESGj4T}&mYKu6wCuE$T4ifSL`H@;*G@-8mdj47(l{fdajS+^!b_EoWLU*I4YSi$ z>3Kj zmCvS4a;X2wpighyD7RTk26LO#r}nf_29x5b-C%mJ*s|#jS~RR&xnX+w>Tx~8qe_L{ zOs!qNPD)Ch`n6Njd&X5SpYHzJqCtAu*j{0bj0#_!T08ZBpQ3X#bb{7tJ6r9LiuQZ1 z_hH>~E&l#nKkK?*L-X`Ai6+OI+L5@h45q~CN8HTPP4m?*+Og!63USN`%V(B$R-K*O zEWarI-OLb6$E8+GE)$Vhwp3zF&E)vR3gMM%R0^+}o- zgxK)NxH7Tvd1d0=&2mw(@x#l+MTW;Fl#5Pyw`@vmSbSQ=r10pNa{a>7Yow;uNDrSF zQMpE1SaOYwQqeIL62dBV`oHHN)vW}3>A3&CbxgvZE5o_Ir~U`Xo>d2VBYe-ABl`7t S&%mRc7PaU55;l!fBm56?VA+=d diff --git a/src/vs/base/common/codiconsLibrary.ts b/src/vs/base/common/codiconsLibrary.ts index 30f7fb60e14..dc47f9715c6 100644 --- a/src/vs/base/common/codiconsLibrary.ts +++ b/src/vs/base/common/codiconsLibrary.ts @@ -31,9 +31,6 @@ export const codiconsLibrary = { personFollow: register('person-follow', 0xea67), personOutline: register('person-outline', 0xea67), personFilled: register('person-filled', 0xea67), - gitBranch: register('git-branch', 0xea68), - gitBranchCreate: register('git-branch-create', 0xea68), - gitBranchDelete: register('git-branch-delete', 0xea68), sourceControl: register('source-control', 0xea68), mirror: register('mirror', 0xea69), mirrorPublic: register('mirror-public', 0xea69), @@ -633,4 +630,10 @@ export const codiconsLibrary = { repoSelected: register('repo-selected', 0xec69), skip: register('skip', 0xec6a), mergeInto: register('merge-into', 0xec6b), + branchDirty: register('branch-dirty', 0xec6c), + branchStagedChanges: register('branch-staged-changes', 0xec6d), + branchMergeRebaseInProgress: register('branch-merge-rebase-in-progress', 0xec6e), + gitBranch: register('git-branch', 0xec6f), + gitBranchCreate: register('git-branch-create', 0xec6f), + gitBranchDelete: register('git-branch-delete', 0xec6f), } as const; From a8e54617a4444ca8b06e32891cf30c6294edc550 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 12:28:45 +0100 Subject: [PATCH 1775/4355] Chat: input lost rounded corners to the top (fix #273724) (#273725) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 025533ab6e9..828972f3e7c 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -756,12 +756,6 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } -/* When suggest-next widget is present above input, remove top border-radius for connected appearance */ -.interactive-session .chat-suggest-next-widget ~ .interactive-input-part .chat-input-container { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - .interactive-session .chat-editing-session { margin-bottom: -4px; /* Counter the flex gap */ /* reset the 4px gap of the container for editing sessions */ From c2dd1966871890cdc01ce9d576f56da05dccd15e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 28 Oct 2025 12:49:00 +0100 Subject: [PATCH 1776/4355] Chat: tree view no longer gets the right height (#273729) (#273730) --- .../workbench/contrib/chat/browser/chatInputPart.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index e604500f943..00bf0421605 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -2111,6 +2111,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } private getLayoutData() { + + // ########################################################################### + // # # + // # CHANGING THIS METHOD HAS RENDERING IMPLICATIONS FOR THE CHAT VIEW # + // # IF YOU MAKE CHANGES HERE, PLEASE TEST THE CHAT VIEW THOROUGHLY: # + // # - produce various chat responses # + // # - click the response to get a focus outline # + // # - ensure the outline is not cut off at the bottom # + // # # + // ########################################################################### + const executeToolbarWidth = this.cachedExecuteToolbarWidth = this.executeToolbar.getItemsWidth(); const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth(); const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4; @@ -2120,7 +2131,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge followupsHeight: this.followupsContainer.offsetHeight, inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 16 : 32, - inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (1 * 4) /* flex gap: todo|edits|input */), + inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : (16 /* entire part */ + 6 /* input container */ + (2 * 4) /* flex gap: todo|edits|input */), attachmentsHeight: this.attachmentsHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, From 200a5e8426440d566b54b6076aba1ef157a81a5e Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 28 Oct 2025 13:11:29 +0100 Subject: [PATCH 1777/4355] Add source of truth --- .../api/common/extHostLanguageFeatures.ts | 1 + .../contrib/chat/browser/chat.contribution.ts | 9 +++ .../assignment/common/assignmentService.ts | 57 ++++++++++++++++++- .../common/inlineCompletionsUnification.ts | 37 +++++++++++- ...e.proposed.inlineCompletionsAdditions.d.ts | 1 + 5 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 86f4da85d7b..a7f34b535d4 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2137,6 +2137,7 @@ export class ExtHostLanguageFeatures extends CoreDisposable implements extHostPr this._inlineCompletionsUnificationState = { codeUnification: false, modelUnification: false, + extensionUnification: false, expAssignments: [] }; } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 15967097e6f..c212e53d3c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -733,6 +733,15 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, + 'copilot.extensionUnification.enabled': { + type: 'boolean', + description: nls.localize('copilot.extensionUnification.enabled', "Enables unification of GitHub Copilot extensions. When enabled, only the GitHub Copilot Chat extension will be active and all functionality will be served by it. When disabled, GitHub Copilot Chat and GitHub Copilot extensions will operate independently."), + default: false, + tags: ['experimental'], + experiment: { + mode: 'auto' + } + } } }); Registry.as(EditorExtensions.EditorPane).registerEditorPane( diff --git a/src/vs/workbench/services/assignment/common/assignmentService.ts b/src/vs/workbench/services/assignment/common/assignmentService.ts index 6d82a59b340..4e26da6bdb1 100644 --- a/src/vs/workbench/services/assignment/common/assignmentService.ts +++ b/src/vs/workbench/services/assignment/common/assignmentService.ts @@ -23,12 +23,18 @@ import { importAMDNodeModule } from '../../../../amdX.js'; import { timeout } from '../../../../base/common/async.js'; import { CopilotAssignmentFilterProvider } from './assignmentFilters.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { Emitter } from '../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; + +export interface IAssignmentFilter { + exclude(assignment: string): boolean; + onDidChange: Event; +} export const IWorkbenchAssignmentService = createDecorator('assignmentService'); export interface IWorkbenchAssignmentService extends IAssignmentService { getCurrentExperiments(): Promise; + addTelemetryAssignmentFilter(filter: IAssignmentFilter): void; } class MementoKeyValueStorage implements IKeyValueStorage { @@ -56,11 +62,15 @@ class WorkbenchAssignmentServiceTelemetry extends Disposable implements IExperim private readonly _onDidUpdateAssignmentContext = this._register(new Emitter()); readonly onDidUpdateAssignmentContext = this._onDidUpdateAssignmentContext.event; + private _previousAssignmentContext: string | undefined; private _lastAssignmentContext: string | undefined; get assignmentContext(): string[] | undefined { return this._lastAssignmentContext?.split(';'); } + private _assignmentFilters: IAssignmentFilter[] = []; + private _assignmentFilterDisposables = this._register(new DisposableStore()); + constructor( private readonly telemetryService: ITelemetryService, private readonly productService: IProductService @@ -68,11 +78,48 @@ class WorkbenchAssignmentServiceTelemetry extends Disposable implements IExperim super(); } + private _filterAssignmentContext(assignmentContext: string): string { + const assignments = assignmentContext.split(';'); + + const filteredAssignments = assignments.filter(assignment => { + for (const filter of this._assignmentFilters) { + if (filter.exclude(assignment)) { + return false; + } + } + return true; + }); + + return filteredAssignments.join(';'); + } + + private _setAssignmentContext(value: string): void { + value = this._filterAssignmentContext(value); + this._lastAssignmentContext = value; + this._onDidUpdateAssignmentContext.fire(); + + if (this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { + this.telemetryService.setExperimentProperty(this.productService.tasConfig.assignmentContextTelemetryPropertyName, value); + } + } + + addAssignmentFilter(filter: IAssignmentFilter): void { + this._assignmentFilters.push(filter); + this._assignmentFilterDisposables.add(filter.onDidChange(() => { + if (this._previousAssignmentContext) { + this._setAssignmentContext(this._previousAssignmentContext); + } + })); + if (this._previousAssignmentContext) { + this._setAssignmentContext(this._previousAssignmentContext); + } + } + // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } setSharedProperty(name: string, value: string): void { if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { - this._lastAssignmentContext = value; - this._onDidUpdateAssignmentContext.fire(); + this._previousAssignmentContext = value; + return this._setAssignmentContext(value); } this.telemetryService.setExperimentProperty(name, value); @@ -265,6 +312,10 @@ export class WorkbenchAssignmentService extends Disposable implements IAssignmen return this.telemetry.assignmentContext; } + + addTelemetryAssignmentFilter(filter: IAssignmentFilter): void { + this.telemetry.addAssignmentFilter(filter); + } } registerSingleton(IWorkbenchAssignmentService, WorkbenchAssignmentService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts index 47dee57b47e..7424063838f 100644 --- a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts +++ b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts @@ -6,6 +6,7 @@ import { equals } from '../../../../base/common/arrays.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -16,6 +17,7 @@ export const IInlineCompletionsUnificationService = createDecorator('isRunningUnificationExperiment', false); +const ExtensionUnificationSetting = 'copilot.extensionUnification.enabled'; + export class InlineCompletionsUnificationImpl extends Disposable implements IInlineCompletionsUnificationService { readonly _serviceBrand: undefined; - private _state = new InlineCompletionsUnificationState(false, false, []); + private _state = new InlineCompletionsUnificationState(false, false, false, []); public get state(): IInlineCompletionsUnificationState { return this._state; } private isRunningUnificationExperiment; @@ -43,34 +48,58 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl private readonly _onDidStateChange = this._register(new Emitter()); public readonly onDidStateChange = this._onDidStateChange.event; + private readonly _onDidChangeExtensionUnification = this._register(new Emitter()); + constructor( @IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this.isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService); + + this._assignmentService.addTelemetryAssignmentFilter({ + exclude: (assignment) => !this._state.extensionUnification && assignment.startsWith(EXTENSION_UNIFICATION_PREFIX), + onDidChange: this._onDidChangeExtensionUnification.event + }); + + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ExtensionUnificationSetting)) { + this._update(); + } + }); this._register(this._assignmentService.onDidRefetchAssignments(() => this._update())); this._update(); } private async _update(): Promise { + // TODO@benibenj@sandy081 extensionUnificationEnabled should depend on extension enablement state + const extensionUnificationEnabled = this._configurationService.getValue(ExtensionUnificationSetting); const [codeUnificationFF, modelUnificationFF] = await Promise.all([ this._assignmentService.getTreatment(CODE_UNIFICATION_FF), this._assignmentService.getTreatment(MODEL_UNIFICATION_FF), ]); + // Intentionally read the current experiments after fetching the treatments const currentExperiments = await this._assignmentService.getCurrentExperiments(); const newState = new InlineCompletionsUnificationState( codeUnificationFF === true, modelUnificationFF === true, - currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX)) ?? [] + extensionUnificationEnabled, + currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX) || (extensionUnificationEnabled && exp.startsWith(EXTENSION_UNIFICATION_PREFIX))) ?? [] ); if (this._state.equals(newState)) { return; } + + const previousState = this._state; this._state = newState; - this.isRunningUnificationExperiment.set(this._state.codeUnification || this._state.modelUnification); + this.isRunningUnificationExperiment.set(this._state.codeUnification || this._state.modelUnification || this._state.extensionUnification); this._onDidStateChange.fire(); + + if (previousState.extensionUnification !== this._state.extensionUnification) { + this._onDidChangeExtensionUnification.fire(); + } } } @@ -78,6 +107,7 @@ class InlineCompletionsUnificationState implements IInlineCompletionsUnification constructor( public readonly codeUnification: boolean, public readonly modelUnification: boolean, + public readonly extensionUnification: boolean, public readonly expAssignments: string[] ) { } @@ -85,6 +115,7 @@ class InlineCompletionsUnificationState implements IInlineCompletionsUnification equals(other: IInlineCompletionsUnificationState): boolean { return this.codeUnification === other.codeUnification && this.modelUnification === other.modelUnification + && this.extensionUnification === other.extensionUnification && equals(this.expAssignments, other.expAssignments); } } diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index c999b7466f7..df34d17ee9a 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -225,6 +225,7 @@ declare module 'vscode' { export interface InlineCompletionsUnificationState { codeUnification: boolean; modelUnification: boolean; + extensionUnification: boolean; expAssignments: string[]; } } From ea2eb0dc07a9014103a608e843a0f8454061b1cf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 05:33:49 -0700 Subject: [PATCH 1778/4355] Use offset not client height for determining widget height Fixes #273193 --- src/vs/platform/hover/browser/hoverWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hoverWidget.ts b/src/vs/platform/hover/browser/hoverWidget.ts index f3aa8447477..ea477bf0b37 100644 --- a/src/vs/platform/hover/browser/hoverWidget.ts +++ b/src/vs/platform/hover/browser/hoverWidget.ts @@ -553,7 +553,7 @@ export class HoverWidget extends Widget implements IHoverWidget { // Position hover below the target else if (this._hoverPosition === HoverPosition.BELOW) { // Hover on bottom is going beyond window - if (target.bottom + this._hover.containerDomNode.clientHeight + hoverPointerOffset > this._targetWindow.innerHeight) { + if (target.bottom + this._hover.containerDomNode.offsetHeight + hoverPointerOffset > this._targetWindow.innerHeight) { this._hoverPosition = HoverPosition.ABOVE; } } From 3e7195c5169579f9d70146aef0c069fd63a81b43 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 05:47:06 -0700 Subject: [PATCH 1779/4355] Surface slow tool prepare calls to user Fixes #273528 --- .../chatProgressContentPart.ts | 7 +++++- .../chat/browser/languageModelToolsService.ts | 23 +++++++++++++++---- .../chat/common/languageModelToolsService.ts | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index 59ed0d64f34..fae21df698f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -23,6 +23,7 @@ import { IConfigurationService } from '../../../../../platform/configuration/com import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; +import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; export class ChatProgressContentPart extends Disposable implements IChatContentPart { public readonly domNode: HTMLElement; @@ -179,13 +180,17 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl context: IChatContentPartRenderContext, @IInstantiationService instantiationService: IInstantiationService, @IChatMarkdownAnchorService chatMarkdownAnchorService: IChatMarkdownAnchorService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService ) { const progressMessage: IChatProgressMessage = { kind: 'progressMessage', content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); + this._register(languageModelToolsService.onDidPrepareToolCallBecomeUnresponsive(toolData => { + this.updateMessage(new MarkdownString(localize('toolCallUnresponsive', "Waiting for tool '{0}' to respond...", toolData.displayName))); + })); } override hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index a40f4f2c911..70fc53b24f7 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -5,7 +5,7 @@ import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; import { assertNever } from '../../../../base/common/assert.js'; -import { RunOnceScheduler } from '../../../../base/common/async.js'; +import { RunOnceScheduler, timeout } from '../../../../base/common/async.js'; import { encodeBase64 } from '../../../../base/common/buffer.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; @@ -80,6 +80,8 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _onDidChangeTools = new Emitter(); readonly onDidChangeTools = this._onDidChangeTools.event; + private _onDidPrepareToolCallBecomeUnresponsive = new Emitter(); + readonly onDidPrepareToolCallBecomeUnresponsive = this._onDidPrepareToolCallBecomeUnresponsive.event; /** Throttle tools updates because it sends all tools and runs on context key updates */ private _onDidChangeToolsScheduler = new RunOnceScheduler(() => this._onDidChangeTools.fire(), 750); @@ -444,14 +446,25 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } private async prepareToolInvocation(tool: IToolEntry, dto: IToolInvocation, token: CancellationToken): Promise { - const prepared = tool.impl!.prepareToolInvocation ? - await tool.impl!.prepareToolInvocation({ + let prepared: IPreparedToolInvocation | undefined; + if (tool.impl!.prepareToolInvocation) { + const preparePromise = tool.impl!.prepareToolInvocation({ parameters: dto.parameters, chatRequestId: dto.chatRequestId, chatSessionId: dto.context?.sessionId, chatInteractionId: dto.chatInteractionId - }, token) - : undefined; + }, token); + + const raceResult = await Promise.race([ + timeout(3000), + preparePromise + ]); + if (raceResult === void 0) { + this._onDidPrepareToolCallBecomeUnresponsive.fire(tool.data); + } + + prepared = await preparePromise; + } if (prepared?.confirmationMessages?.title) { if (prepared.toolSpecificData?.kind !== 'terminal' && typeof prepared.confirmationMessages.allowAutoConfirm !== 'boolean') { diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index bf8b027c295..20529b5946f 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -323,6 +323,7 @@ export type CountTokensCallback = (input: string, token: CancellationToken) => P export interface ILanguageModelToolsService { _serviceBrand: undefined; readonly onDidChangeTools: Event; + readonly onDidPrepareToolCallBecomeUnresponsive: Event; registerToolData(toolData: IToolData): IDisposable; registerToolImplementation(id: string, tool: IToolImpl): IDisposable; registerTool(toolData: IToolData, tool: IToolImpl): IDisposable; From 9306ddcd064723d18836ac75b94d17fc6fda7ff8 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 28 Oct 2025 14:01:27 +0100 Subject: [PATCH 1780/4355] Merge pull request #273714 from microsoft/ulugbekna/nes-trigger-on-more-editoraction-commands nes: trigger on more EditorAction commands --- src/vs/editor/browser/editorExtensions.ts | 8 ++ .../triggerInlineEditCommandsRegistry.ts | 20 +++++ .../browser/bracketMatching.ts | 4 +- .../editor/contrib/comment/browser/comment.ts | 18 ++-- .../browser/copyPasteContribution.ts | 4 +- .../indentation/browser/indentation.ts | 22 ++--- .../controller/inlineCompletionsController.ts | 2 + .../browser/linesOperations.ts | 90 ++++++++++++------- .../editor/contrib/rename/browser/rename.ts | 3 +- .../wordOperations/browser/wordOperations.ts | 22 ++--- 10 files changed, 129 insertions(+), 64 deletions(-) create mode 100644 src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index e175a348e03..d5961dd4f1e 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -25,6 +25,7 @@ import { IDisposable } from '../../base/common/lifecycle.js'; import { KeyMod, KeyCode } from '../../base/common/keyCodes.js'; import { ILogService } from '../../platform/log/common/log.js'; import { getActiveElement } from '../../base/browser/dom.js'; +import { TriggerInlineEditCommandsRegistry } from './triggerInlineEditCommandsRegistry.js'; export type ServicesAccessor = InstantiationServicesAccessor; export type EditorContributionCtor = IConstructorSignature; @@ -98,6 +99,7 @@ export interface ICommandOptions { kbOpts?: ICommandKeybindingsOptions | ICommandKeybindingsOptions[]; metadata?: ICommandMetadata; menuOpts?: ICommandMenuOptions | ICommandMenuOptions[]; + canTriggerInlineEdits?: boolean; } export abstract class Command { public readonly id: string; @@ -105,6 +107,7 @@ export abstract class Command { private readonly _kbOpts: ICommandKeybindingsOptions | ICommandKeybindingsOptions[] | undefined; private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined; public readonly metadata: ICommandMetadata | undefined; + public readonly canTriggerInlineEdits: boolean | undefined; constructor(opts: ICommandOptions) { this.id = opts.id; @@ -112,6 +115,7 @@ export abstract class Command { this._kbOpts = opts.kbOpts; this._menuOpts = opts.menuOpts; this.metadata = opts.metadata; + this.canTriggerInlineEdits = opts.canTriggerInlineEdits; } public register(): void { @@ -155,6 +159,10 @@ export abstract class Command { handler: (accessor, args) => this.runCommand(accessor, args), metadata: this.metadata }); + + if (this.canTriggerInlineEdits) { + TriggerInlineEditCommandsRegistry.registerCommand(this.id); + } } private _registerMenuItem(item: ICommandMenuOptions): void { diff --git a/src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts b/src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts new file mode 100644 index 00000000000..21cd1c5fe1d --- /dev/null +++ b/src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Registry for commands that can trigger Inline Edits (NES) when invoked. + */ +export abstract class TriggerInlineEditCommandsRegistry { + + private static REGISTERED_COMMANDS = new Set(); + + public static getRegisteredCommands(): readonly string[] { + return [...TriggerInlineEditCommandsRegistry.REGISTERED_COMMANDS]; + } + + public static registerCommand(commandId: string): void { + TriggerInlineEditCommandsRegistry.REGISTERED_COMMANDS.add(commandId); + } +} diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index d121efd2500..7e64b4e28c8 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -76,6 +76,7 @@ class SelectToBracketAction extends EditorAction { BracketMatchingController.get(editor)?.selectToBracket(selectBrackets); } } + class RemoveBracketsAction extends EditorAction { constructor() { super({ @@ -86,7 +87,8 @@ class RemoveBracketsAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/comment/browser/comment.ts b/src/vs/editor/contrib/comment/browser/comment.ts index 1d225b85bcb..4b2a1461b9a 100644 --- a/src/vs/editor/contrib/comment/browser/comment.ts +++ b/src/vs/editor/contrib/comment/browser/comment.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import * as nls from '../../../../nls.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from '../../../browser/editorExtensions.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; @@ -13,9 +16,6 @@ import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; import { BlockCommentCommand } from './blockCommentCommand.js'; import { LineCommentCommand, Type } from './lineCommentCommand.js'; -import * as nls from '../../../../nls.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; abstract class CommentLineAction extends EditorAction { @@ -94,7 +94,8 @@ class ToggleCommentLineAction extends CommentLineAction { group: '5_insert', title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), order: 1 - } + }, + canTriggerInlineEdits: true, }); } } @@ -109,7 +110,8 @@ class AddLineCommentAction extends CommentLineAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } } @@ -124,7 +126,8 @@ class RemoveLineCommentAction extends CommentLineAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyU), weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } } @@ -147,7 +150,8 @@ class BlockCommentAction extends EditorAction { group: '5_insert', title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), order: 2 - } + }, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts index 5ab18c88457..1acfdcd5c27 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts @@ -92,7 +92,8 @@ registerEditorAction(class PasteAsAction extends EditorAction { name: 'args', schema: PasteAsAction.argsSchema }] - } + }, + canTriggerInlineEdits: true, }); } @@ -115,6 +116,7 @@ registerEditorAction(class extends EditorAction { id: 'editor.action.pasteAsText', label: nls.localize2('pasteAsText', "Paste as Text"), precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/indentation/browser/indentation.ts b/src/vs/editor/contrib/indentation/browser/indentation.ts index d01770a81cd..ad6371e1a9b 100644 --- a/src/vs/editor/contrib/indentation/browser/indentation.ts +++ b/src/vs/editor/contrib/indentation/browser/indentation.ts @@ -5,28 +5,28 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import * as strings from '../../../../base/common/strings.js'; +import * as nls from '../../../../nls.js'; +import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorAction, EditorContributionInstantiation, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js'; import { ShiftCommand } from '../../../common/commands/shiftCommand.js'; import { EditorAutoIndentStrategy, EditorOption } from '../../../common/config/editorOptions.js'; import { ISingleEditOperation } from '../../../common/core/editOperation.js'; +import { Position } from '../../../common/core/position.js'; import { IRange, Range } from '../../../common/core/range.js'; import { Selection } from '../../../common/core/selection.js'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder, IEditorContribution } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; -import { EndOfLineSequence, ITextModel } from '../../../common/model.js'; -import { TextEdit } from '../../../common/languages.js'; import { StandardTokenType } from '../../../common/encodedTokenAttributes.js'; +import { TextEdit } from '../../../common/languages.js'; +import { getGoodIndentForLine, getIndentMetadata } from '../../../common/languages/autoIndent.js'; import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; import { IndentConsts } from '../../../common/languages/supports/indentRules.js'; +import { EndOfLineSequence, ITextModel } from '../../../common/model.js'; import { IModelService } from '../../../common/services/model.js'; -import * as indentUtils from '../common/indentUtils.js'; -import * as nls from '../../../../nls.js'; -import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; -import { getGoodIndentForLine, getIndentMetadata } from '../../../common/languages/autoIndent.js'; -import { getReindentEditOperations } from '../common/indentation.js'; import { getStandardTokenTypeAtPosition } from '../../../common/tokens/lineTokens.js'; -import { Position } from '../../../common/core/position.js'; +import { getReindentEditOperations } from '../common/indentation.js'; +import * as indentUtils from '../common/indentUtils.js'; export class IndentationToSpacesAction extends EditorAction { public static readonly ID = 'editor.action.indentationToSpaces'; @@ -242,7 +242,8 @@ export class ReindentLinesAction extends EditorAction { precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('editor.reindentlinesDescription', "Reindent the lines of the editor."), - } + }, + canTriggerInlineEdits: true, }); } @@ -270,7 +271,8 @@ export class ReindentSelectedLinesAction extends EditorAction { precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('editor.reindentselectedlinesDescription', "Reindent the selected lines of the editor."), - } + }, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 97ae55daa2f..d5a51839367 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -23,6 +23,7 @@ import { hotClassGetOriginalInstance } from '../../../../../platform/observable/ import { CoreEditingCommands } from '../../../../browser/coreCommands.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../browser/observableCodeEditor.js'; +import { TriggerInlineEditCommandsRegistry } from '../../../../browser/triggerInlineEditCommandsRegistry.js'; import { getOuterEditor } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Position } from '../../../../common/core/position.js'; @@ -226,6 +227,7 @@ export class InlineCompletionsController extends Disposable { InsertLineAfterAction.ID, InsertLineBeforeAction.ID, FIND_IDS.NextMatchFindAction, + ...TriggerInlineEditCommandsRegistry.getRegisteredCommands(), ]); this._register(this._commandService.onDidExecuteCommand((e) => { if (triggerCommands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 407e6c291a0..e67488579e1 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -4,29 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import * as nls from '../../../../nls.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CoreEditingCommands } from '../../../browser/coreCommands.js'; import { IActiveCodeEditor, ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from '../../../browser/editorExtensions.js'; import { ReplaceCommand, ReplaceCommandThatPreservesSelection, ReplaceCommandThatSelectsText } from '../../../common/commands/replaceCommand.js'; import { TrimTrailingWhitespaceCommand } from '../../../common/commands/trimTrailingWhitespaceCommand.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; -import { TypeOperations } from '../../../common/cursor/cursorTypeOperations.js'; -import { EnterOperation } from '../../../common/cursor/cursorTypeEditOperations.js'; import { EditOperation, ISingleEditOperation } from '../../../common/core/editOperation.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { Selection } from '../../../common/core/selection.js'; +import { EnterOperation } from '../../../common/cursor/cursorTypeEditOperations.js'; +import { TypeOperations } from '../../../common/cursor/cursorTypeOperations.js'; import { ICommand } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; +import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; import { ITextModel } from '../../../common/model.js'; import { CopyLinesCommand } from './copyLinesCommand.js'; import { MoveLinesCommand } from './moveLinesCommand.js'; import { SortLinesCommand } from './sortLinesCommand.js'; -import * as nls from '../../../../nls.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; // copy lines @@ -92,7 +92,8 @@ class CopyLinesUpAction extends AbstractCopyLinesAction { group: '2_line', title: nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"), order: 1 - } + }, + canTriggerInlineEdits: true, }); } } @@ -114,7 +115,8 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { group: '2_line', title: nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"), order: 2 - } + }, + canTriggerInlineEdits: true, }); } } @@ -131,7 +133,8 @@ export class DuplicateSelectionAction extends EditorAction { group: '2_line', title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"), order: 5 - } + }, + canTriggerInlineEdits: true, }); } @@ -204,7 +207,8 @@ class MoveLinesUpAction extends AbstractMoveLinesAction { group: '2_line', title: nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"), order: 3 - } + }, + canTriggerInlineEdits: true, }); } } @@ -226,7 +230,8 @@ class MoveLinesDownAction extends AbstractMoveLinesAction { group: '2_line', title: nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"), order: 4 - } + }, + canTriggerInlineEdits: true, }); } } @@ -273,7 +278,8 @@ export class SortLinesAscendingAction extends AbstractSortLinesAction { super(false, { id: 'editor.action.sortLinesAscending', label: nls.localize2('lines.sortAscending', "Sort Lines Ascending"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } } @@ -283,7 +289,8 @@ export class SortLinesDescendingAction extends AbstractSortLinesAction { super(true, { id: 'editor.action.sortLinesDescending', label: nls.localize2('lines.sortDescending', "Sort Lines Descending"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } } @@ -293,7 +300,8 @@ export class DeleteDuplicateLinesAction extends EditorAction { super({ id: 'editor.action.removeDuplicateLines', label: nls.localize2('lines.deleteDuplicates', "Delete Duplicate Lines"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } @@ -368,7 +376,8 @@ export class ReverseLinesAction extends EditorAction { super({ id: 'editor.action.reverseLines', label: nls.localize2('lines.reverseLines', "Reverse lines"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true }); } @@ -506,7 +515,8 @@ export class DeleteLinesAction extends EditorAction { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyK, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -607,7 +617,8 @@ export class IndentLinesAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.BracketRight, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -632,7 +643,8 @@ class OutdentLinesAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.BracketLeft, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -652,7 +664,8 @@ export class InsertLineBeforeAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -677,7 +690,8 @@ export class InsertLineAfterAction extends EditorAction { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.Enter, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -745,7 +759,8 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -822,7 +837,8 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KeyK, secondary: [KeyMod.CtrlCmd | KeyCode.Delete] }, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -888,7 +904,8 @@ export class JoinLinesAction extends EditorAction { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KeyJ }, weight: KeybindingWeight.EditorContrib - } + }, + canTriggerInlineEdits: true, }); } @@ -1041,7 +1058,8 @@ export class TransposeAction extends EditorAction { super({ id: 'editor.action.transpose', label: nls.localize2('editor.transpose', "Transpose Characters around the Cursor"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } @@ -1139,7 +1157,8 @@ export class UpperCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToUppercase', label: nls.localize2('editor.transformToUppercase', "Transform to Uppercase"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } @@ -1153,7 +1172,8 @@ export class LowerCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToLowercase', label: nls.localize2('editor.transformToLowercase', "Transform to Lowercase"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true }); } @@ -1200,7 +1220,8 @@ export class TitleCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToTitlecase', label: nls.localize2('editor.transformToTitlecase', "Transform to Title Case"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true }); } @@ -1225,7 +1246,8 @@ export class SnakeCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToSnakecase', label: nls.localize2('editor.transformToSnakecase', "Transform to Snake Case"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } @@ -1253,7 +1275,8 @@ export class CamelCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToCamelcase', label: nls.localize2('editor.transformToCamelcase', "Transform to Camel Case"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true }); } @@ -1280,7 +1303,8 @@ export class PascalCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToPascalcase', label: nls.localize2('editor.transformToPascalcase', "Transform to Pascal Case"), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } @@ -1308,7 +1332,6 @@ export class PascalCaseAction extends AbstractCaseAction { } } - export class KebabCaseAction extends AbstractCaseAction { public static isSupported(): boolean { @@ -1329,7 +1352,8 @@ export class KebabCaseAction extends AbstractCaseAction { super({ id: 'editor.action.transformToKebabcase', label: nls.localize2('editor.transformToKebabcase', 'Transform to Kebab Case'), - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 120232f050b..27a7eb5f518 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -358,7 +358,8 @@ export class RenameAction extends EditorAction { contextMenuOpts: { group: '1_modification', order: 1.1 - } + }, + canTriggerInlineEdits: true, }); } diff --git a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts index 6e324867d78..61ce84b25df 100644 --- a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts @@ -4,26 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import * as nls from '../../../../nls.js'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorAction, EditorCommand, ICommandOptions, registerEditorAction, registerEditorCommand, ServicesAccessor } from '../../../browser/editorExtensions.js'; import { ReplaceCommand } from '../../../common/commands/replaceCommand.js'; import { EditorOption, EditorOptions } from '../../../common/config/editorOptions.js'; -import { CursorState } from '../../../common/cursorCommon.js'; -import { CursorChangeReason } from '../../../common/cursorEvents.js'; -import { DeleteWordContext, WordNavigationType, WordOperations } from '../../../common/cursor/cursorWordOperations.js'; -import { getMapForWordSeparators, WordCharacterClassifier } from '../../../common/core/wordCharacterClassifier.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { Selection } from '../../../common/core/selection.js'; +import { getMapForWordSeparators, WordCharacterClassifier } from '../../../common/core/wordCharacterClassifier.js'; +import { DeleteWordContext, WordNavigationType, WordOperations } from '../../../common/cursor/cursorWordOperations.js'; +import { CursorState } from '../../../common/cursorCommon.js'; +import { CursorChangeReason } from '../../../common/cursorEvents.js'; import { ScrollType } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; -import { ITextModel } from '../../../common/model.js'; import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js'; -import * as nls from '../../../../nls.js'; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; -import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ITextModel } from '../../../common/model.js'; export interface MoveWordOptions extends ICommandOptions { inSelectionMode: boolean; @@ -325,7 +325,7 @@ export abstract class DeleteWordCommand extends EditorCommand { private readonly _wordNavigationType: WordNavigationType; constructor(opts: DeleteWordOptions) { - super(opts); + super({ canTriggerInlineEdits: true, ...opts }); this._whitespaceHeuristics = opts.whitespaceHeuristics; this._wordNavigationType = opts.wordNavigationType; } From f6077e3fb587bb61348c2483d4330c068208b566 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:03:25 -0700 Subject: [PATCH 1781/4355] Clarify timeout result, fix compile, link to sessionId --- .../chatContentParts/chatProgressContentPart.ts | 6 ++++-- .../contrib/chat/browser/languageModelToolsService.ts | 11 +++++++---- .../contrib/chat/common/languageModelToolsService.ts | 2 +- .../chat/test/common/mockLanguageModelToolsService.ts | 1 + 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index fae21df698f..fe0d3c4d7ae 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -188,8 +188,10 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl content: new MarkdownString().appendText(localize('workingMessage', "Working...")) }; super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, instantiationService, chatMarkdownAnchorService, configurationService); - this._register(languageModelToolsService.onDidPrepareToolCallBecomeUnresponsive(toolData => { - this.updateMessage(new MarkdownString(localize('toolCallUnresponsive', "Waiting for tool '{0}' to respond...", toolData.displayName))); + this._register(languageModelToolsService.onDidPrepareToolCallBecomeUnresponsive(e => { + if (context.element.sessionId === e.sessionId) { + this.updateMessage(new MarkdownString(localize('toolCallUnresponsive', "Waiting for tool '{0}' to respond...", e.toolData.displayName))); + } })); } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 70fc53b24f7..298b3991011 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -80,7 +80,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo private _onDidChangeTools = new Emitter(); readonly onDidChangeTools = this._onDidChangeTools.event; - private _onDidPrepareToolCallBecomeUnresponsive = new Emitter(); + private _onDidPrepareToolCallBecomeUnresponsive = new Emitter<{ sessionId: string; toolData: IToolData }>(); readonly onDidPrepareToolCallBecomeUnresponsive = this._onDidPrepareToolCallBecomeUnresponsive.event; /** Throttle tools updates because it sends all tools and runs on context key updates */ @@ -456,11 +456,14 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo }, token); const raceResult = await Promise.race([ - timeout(3000), + timeout(3000).then(() => 'timeout'), preparePromise ]); - if (raceResult === void 0) { - this._onDidPrepareToolCallBecomeUnresponsive.fire(tool.data); + if (raceResult === 'timeout') { + this._onDidPrepareToolCallBecomeUnresponsive.fire({ + sessionId: dto.context?.sessionId ?? '', + toolData: tool.data + }); } prepared = await preparePromise; diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index 20529b5946f..079620ad69c 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -323,7 +323,7 @@ export type CountTokensCallback = (input: string, token: CancellationToken) => P export interface ILanguageModelToolsService { _serviceBrand: undefined; readonly onDidChangeTools: Event; - readonly onDidPrepareToolCallBecomeUnresponsive: Event; + readonly onDidPrepareToolCallBecomeUnresponsive: Event<{ sessionId: string; toolData: IToolData }>; registerToolData(toolData: IToolData): IDisposable; registerToolImplementation(id: string, tool: IToolImpl): IDisposable; registerTool(toolData: IToolData, tool: IToolImpl): IDisposable; diff --git a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts index 6573346705a..3f2b1cc0cd4 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -18,6 +18,7 @@ export class MockLanguageModelToolsService implements ILanguageModelToolsService constructor() { } readonly onDidChangeTools: Event = Event.None; + readonly onDidPrepareToolCallBecomeUnresponsive: Event<{ sessionId: string; toolData: IToolData }> = Event.None; registerToolData(toolData: IToolData): IDisposable { return Disposable.None; From 9cb500071e06e5eacddc90fbc403143122d5723d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:10:55 -0700 Subject: [PATCH 1782/4355] Fix leaks --- .../contrib/chat/browser/languageModelToolsService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 298b3991011..5abc6be3e4e 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -78,9 +78,9 @@ export const globalAutoApproveDescription = localize2( export class LanguageModelToolsService extends Disposable implements ILanguageModelToolsService { _serviceBrand: undefined; - private _onDidChangeTools = new Emitter(); + private _onDidChangeTools = this._register(new Emitter()); readonly onDidChangeTools = this._onDidChangeTools.event; - private _onDidPrepareToolCallBecomeUnresponsive = new Emitter<{ sessionId: string; toolData: IToolData }>(); + private _onDidPrepareToolCallBecomeUnresponsive = this._register(new Emitter<{ sessionId: string; toolData: IToolData }>()); readonly onDidPrepareToolCallBecomeUnresponsive = this._onDidPrepareToolCallBecomeUnresponsive.event; /** Throttle tools updates because it sends all tools and runs on context key updates */ @@ -456,7 +456,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo }, token); const raceResult = await Promise.race([ - timeout(3000).then(() => 'timeout'), + timeout(3000, token).then(() => 'timeout'), preparePromise ]); if (raceResult === 'timeout') { From 9173dc98dd043afec86096478023c9087353ef6e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:17:03 -0700 Subject: [PATCH 1783/4355] Reuse query code in tree sitter parser Part of #272984 --- .../chatAgentTools/browser/treeSitterCommandParser.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 054ef899355..30f9cd40118 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -22,17 +22,16 @@ export class TreeSitterCommandParser { this._parser = this._treeSitterLibraryService.getParserClass().then(ParserCtor => new ParserCtor()); } - async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { - const { tree, query } = await this._doQuery(languageId, commandLine, '(command) @command'); - const captures = query.captures(tree.rootNode); - return captures.map(e => e.node.text); - } - async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { const { tree, query } = await this._doQuery(languageId, commandLine, querySource); return query.captures(tree.rootNode); } + async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { + const captures = await this.queryTree(languageId, commandLine, '(command) @command'); + return captures.map(e => e.node.text); + } + private async _doQuery(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise<{ tree: Tree; query: Query }> { this._throwIfCanCrash(languageId); From 745c8f4ba2edeae79afbe588cac5c50aafe0ce40 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:24:01 -0700 Subject: [PATCH 1784/4355] Move && query into tree sitter parser file --- .../browser/commandSimplifier.ts | 9 +------ .../browser/treeSitterCommandParser.ts | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts index 52bf2b5daca..78c46c32301 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts @@ -86,14 +86,7 @@ export class CommandSimplifier { // supports chain operators https://github.com/airbus-cert/tree-sitter-powershell/issues/27 if (isPowerShell(shell, os)) { try { - const doubleAmpersandCaptures = await this._treeSitterCommandParser.queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ - '(', - ' (command', - ' (command_elements', - ' (generic_token) @double.ampersand', - ' (#eq? @double.ampersand "&&")))', - ')', - ].join('\n')); + const doubleAmpersandCaptures = await this._treeSitterCommandParser.extractPwshDoubleAmpersandChainOperators(commandLine); for (const capture of doubleAmpersandCaptures.reverse()) { commandLine = `${commandLine.substring(0, capture.node.startIndex)};${commandLine.substring(capture.node.endIndex)}`; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 30f9cd40118..ea5dd566a8b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -22,16 +22,28 @@ export class TreeSitterCommandParser { this._parser = this._treeSitterLibraryService.getParserClass().then(ParserCtor => new ParserCtor()); } - async queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { - const { tree, query } = await this._doQuery(languageId, commandLine, querySource); - return query.captures(tree.rootNode); - } - async extractSubCommands(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { - const captures = await this.queryTree(languageId, commandLine, '(command) @command'); + const captures = await this._queryTree(languageId, commandLine, '(command) @command'); return captures.map(e => e.node.text); } + async extractPwshDoubleAmpersandChainOperators(commandLine: string): Promise { + const captures = await this._queryTree(TreeSitterCommandParserLanguage.PowerShell, commandLine, [ + '(', + ' (command', + ' (command_elements', + ' (generic_token) @double.ampersand', + ' (#eq? @double.ampersand "&&")))', + ')', + ].join('\n')); + return captures; + } + + private async _queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { + const { tree, query } = await this._doQuery(languageId, commandLine, querySource); + return query.captures(tree.rootNode); + } + private async _doQuery(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise<{ tree: Tree; query: Query }> { this._throwIfCanCrash(languageId); From 014c7afe1c9f94bc92558c487a37ecd0c1785ac9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 28 Oct 2025 07:46:42 -0700 Subject: [PATCH 1785/4355] Remove unused import --- .../terminalContrib/chatAgentTools/browser/commandSimplifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts index 78c46c32301..46b3fba6bb5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandSimplifier.ts @@ -9,7 +9,7 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm import type { ITerminalInstance } from '../../../terminal/browser/terminal.js'; import { isPowerShell } from './runInTerminalHelpers.js'; import type { IRunInTerminalInputParams } from './tools/runInTerminalTool.js'; -import { TreeSitterCommandParserLanguage, type TreeSitterCommandParser } from './treeSitterCommandParser.js'; +import { type TreeSitterCommandParser } from './treeSitterCommandParser.js'; export class CommandSimplifier { constructor( From c902d53e167205a4f72c547bd45050ec036c3bcd Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 28 Oct 2025 15:46:57 +0100 Subject: [PATCH 1786/4355] :lipstick: --- .../services/assignment/common/assignmentService.ts | 6 +++--- .../assignment/test/common/nullAssignmentService.ts | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/assignment/common/assignmentService.ts b/src/vs/workbench/services/assignment/common/assignmentService.ts index 4e26da6bdb1..ec5119138f1 100644 --- a/src/vs/workbench/services/assignment/common/assignmentService.ts +++ b/src/vs/workbench/services/assignment/common/assignmentService.ts @@ -94,12 +94,12 @@ class WorkbenchAssignmentServiceTelemetry extends Disposable implements IExperim } private _setAssignmentContext(value: string): void { - value = this._filterAssignmentContext(value); - this._lastAssignmentContext = value; + const filteredValue = this._filterAssignmentContext(value); + this._lastAssignmentContext = filteredValue; this._onDidUpdateAssignmentContext.fire(); if (this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { - this.telemetryService.setExperimentProperty(this.productService.tasConfig.assignmentContextTelemetryPropertyName, value); + this.telemetryService.setExperimentProperty(this.productService.tasConfig.assignmentContextTelemetryPropertyName, filteredValue); } } diff --git a/src/vs/workbench/services/assignment/test/common/nullAssignmentService.ts b/src/vs/workbench/services/assignment/test/common/nullAssignmentService.ts index fe96966ce86..1dc25f0eccb 100644 --- a/src/vs/workbench/services/assignment/test/common/nullAssignmentService.ts +++ b/src/vs/workbench/services/assignment/test/common/nullAssignmentService.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../../../base/common/event.js'; -import { IWorkbenchAssignmentService } from '../../common/assignmentService.js'; +import { IAssignmentFilter, IWorkbenchAssignmentService } from '../../common/assignmentService.js'; export class NullWorkbenchAssignmentService implements IWorkbenchAssignmentService { _serviceBrand: undefined; readonly onDidRefetchAssignments: Event = Event.None; - async getCurrentExperiments(): Promise { return []; } @@ -19,4 +18,6 @@ export class NullWorkbenchAssignmentService implements IWorkbenchAssignmentServi async getTreatment(name: string): Promise { return undefined; } + + addTelemetryAssignmentFilter(filter: IAssignmentFilter): void { } } From 23894eb8331a058a92e7a1190f4478029188ec97 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:23:22 +0100 Subject: [PATCH 1787/4355] Git - fix theme icon (#273767) --- extensions/git/src/historyProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 68cd1b2fbd9..99f68fbf534 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -124,7 +124,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec id: `refs/heads/${this.repository.HEAD.upstream.name}`, name: this.repository.HEAD.upstream.name, revision: this.repository.HEAD.upstream.commit, - icon: new ThemeIcon('gi-branch') + icon: new ThemeIcon('git-branch') }; } else { // Remote branch From 90627f095003ceace999409d014a52dbf5e45e08 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:00:51 +0100 Subject: [PATCH 1788/4355] Git - unify compare commands for the graph (#273771) * Git - unify compare commands for the graph * Update placeholder * More changes --- extensions/git/package.json | 39 ++++------ extensions/git/package.nls.json | 3 +- extensions/git/src/commands.ts | 124 +++++++++++++++----------------- 3 files changed, 70 insertions(+), 96 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 775a19bd5c6..7e6339d29dc 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -514,12 +514,6 @@ "category": "Git", "enablement": "!operationInProgress" }, - { - "command": "git.graph.compareBranch", - "title": "%command.graphCompareBranch%", - "category": "Git", - "enablement": "!operationInProgress" - }, { "command": "git.deleteRemoteBranch", "title": "%command.deleteRemoteBranch%", @@ -592,12 +586,6 @@ "category": "Git", "enablement": "!operationInProgress" }, - { - "command": "git.graph.compareTag", - "title": "%command.graphCompareTag%", - "category": "Git", - "enablement": "!operationInProgress" - }, { "command": "git.deleteRemoteTag", "title": "%command.deleteRemoteTag%", @@ -1010,6 +998,12 @@ "command": "git.blame.toggleStatusBarItem", "title": "%command.blameToggleStatusBarItem%", "category": "Git" + }, + { + "command": "git.graph.compareRef", + "title": "%command.graphCompareRef%", + "category": "Git", + "enablement": "!operationInProgress" } ], "continueEditSession": [ @@ -1607,17 +1601,13 @@ "when": "false" }, { - "command": "git.graph.compareBranch", + "command": "git.graph.compareRef", "when": "false" }, { "command": "git.graph.deleteTag", "when": "false" }, - { - "command": "git.graph.compareTag", - "when": "false" - }, { "command": "git.graph.cherryPick", "when": "false" @@ -2263,6 +2253,11 @@ "when": "scmProvider == git", "group": "4_modify@1" }, + { + "command": "git.graph.compareRef", + "when": "scmProvider == git", + "group": "5_compare@1" + }, { "command": "git.copyCommitId", "when": "scmProvider == git && !listMultiSelection", @@ -2285,20 +2280,10 @@ "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\/|^refs\\/remotes\\//", "group": "2_branch@2" }, - { - "command": "git.graph.compareBranch", - "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\//", - "group": "2_branch@3" - }, { "command": "git.graph.deleteTag", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", "group": "3_tag@2" - }, - { - "command": "git.graph.compareTag", - "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", - "group": "3_tag@3" } ], "editor/title": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 924d219a095..80ffec8c433 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -135,10 +135,9 @@ "command.graphCheckout": "Checkout", "command.graphCheckoutDetached": "Checkout (Detached)", "command.graphCherryPick": "Cherry Pick", - "command.graphCompareBranch": "Compare Branch...", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", - "command.graphCompareTag": "Compare Tag...", + "command.graphCompareRef": "Compare With...", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index baab4069724..b6ff0a6ff30 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3109,9 +3109,63 @@ export class CommandCenter { await this._deleteBranch(repository, remoteName, refName, { remote: true }); } - @command('git.graph.compareBranch', { repository: true }) - async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { - await this._compareRef(repository, historyItem, historyItemRefId); + @command('git.graph.compareRef', { repository: true }) + async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a reference to compare with'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem) || !sourceRef.ref.commit) { + return; + } + + if (historyItem.id === sourceRef.ref.commit) { + window.showInformationMessage(l10n.t('The selected references are the same.')); + return; + } + + try { + const sourceCommit = sourceRef.ref.commit; + const changes = await repository.diffTrees(sourceCommit, historyItem.id); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('The selected references have no differences.')); + return; + } + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceCommit, historyItem.id)); + const title = `${sourceRef.ref.name ?? sourceCommit} ↔ ${historyItem.references?.[0].name ?? historyItem.id}`; + const multiDiffSourceUri = Uri.from({ + scheme: 'git-ref-compare', + path: `${repository.root}/${sourceCommit}..${historyItem.id}` + }); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title, + resources + }); + } catch (err) { + throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); + } } @command('git.deleteRemoteBranch', { repository: true }) @@ -3752,11 +3806,6 @@ export class CommandCenter { await repository.deleteTag(historyItemRef.name); } - @command('git.graph.compareTag', { repository: true }) - async compareTags(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { - await this._compareRef(repository, historyItem, historyItemRefId); - } - @command('git.deleteRemoteTag', { repository: true }) async deleteRemoteTag(repository: Repository): Promise { const config = workspace.getConfiguration('git'); @@ -5096,65 +5145,6 @@ export class CommandCenter { config.update(setting, !enabled, true); } - async _compareRef(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { - const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); - if (!historyItemRefId || !historyItemRef) { - return; - } - - const config = workspace.getConfiguration('git'); - const showRefDetails = config.get('showReferenceDetails') === true; - - const getRefPicks = async () => { - const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); - const processors = [ - new RefProcessor(RefType.Head, BranchItem), - new RefProcessor(RefType.RemoteHead, BranchItem), - new RefProcessor(RefType.Tag, BranchItem) - ]; - - const itemsProcessor = new RefItemsProcessor(repository, processors); - return itemsProcessor.processRefs(refs); - }; - - const placeHolder = l10n.t('Select a source reference to compare with'); - const sourceRef = await this.pickRef(getRefPicks(), placeHolder); - - if (!(sourceRef instanceof BranchItem)) { - return; - } - - if (historyItemRef.id === sourceRef.refId) { - window.showInformationMessage(l10n.t('The selected references are the same.')); - return; - } - - try { - const changes = await repository.diffTrees(sourceRef.refId, historyItemRef.id); - - if (changes.length === 0) { - window.showInformationMessage(l10n.t('The selected references have no differences.')); - return; - } - - const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRef.id)); - - const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; - const multiDiffSourceUri = Uri.from({ - scheme: 'git-ref-compare', - path: `${repository.root}/${sourceRef.refId}..${historyItemRef.id}` - }); - - await commands.executeCommand('_workbench.openMultiDiffEditor', { - multiDiffSourceUri, - title, - resources - }); - } catch (err) { - throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); - } - } - private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; From fe8fc75455a31a120d819779e6ac76df6aa43aa5 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:18:27 -0700 Subject: [PATCH 1789/4355] Pick up latest TS native preview Fixes the emit for copyright headers --- .../common/checkForArtifact.js | 2 +- build/azure-pipelines/common/codesign.js | 8 +-- .../common/computeBuiltInDepsCacheKey.js | 8 +-- .../common/computeNodeModulesCacheKey.js | 8 +-- build/azure-pipelines/common/createBuild.js | 2 +- .../common/getPublishAuthTokens.js | 4 ++ .../azure-pipelines/common/listNodeModules.js | 8 +-- build/azure-pipelines/common/publish.js | 8 +-- build/azure-pipelines/common/releaseBuild.js | 2 +- build/azure-pipelines/common/retry.js | 4 +- build/azure-pipelines/common/sign-win32.js | 8 +-- build/azure-pipelines/common/sign.js | 8 +-- .../common/waitForArtifacts.js | 2 +- build/azure-pipelines/darwin/codesign.js | 2 +- build/azure-pipelines/distro/mixin-npm.js | 8 +-- build/azure-pipelines/distro/mixin-quality.js | 8 +-- build/azure-pipelines/linux/codesign.js | 2 +- .../publish-types/check-version.js | 8 +-- .../publish-types/update-types.js | 8 +-- build/azure-pipelines/upload-cdn.js | 8 +-- build/azure-pipelines/upload-nlsmetadata.js | 8 +-- build/azure-pipelines/upload-sourcemaps.js | 8 +-- build/azure-pipelines/win32/codesign.js | 2 +- build/checker/layersChecker.js | 8 +-- build/darwin/create-universal-app.js | 8 +-- build/darwin/sign.js | 8 +-- build/darwin/verify-macho.js | 8 +-- build/lib/asar.js | 8 +-- build/lib/builtInExtensions.js | 8 +-- build/lib/builtInExtensionsCG.js | 8 +-- build/lib/bundle.js | 4 ++ build/lib/compilation.js | 8 +-- build/lib/date.js | 8 +-- build/lib/dependencies.js | 8 +-- build/lib/electron.js | 8 +-- build/lib/extensions.js | 8 +-- build/lib/fetch.js | 8 +-- build/lib/getVersion.js | 8 +-- build/lib/i18n.js | 8 +-- build/lib/inlineMeta.js | 8 +-- build/lib/mangle/index.js | 12 ++-- build/lib/mangle/renameWorker.js | 8 +-- build/lib/mangle/staticLanguageServiceHost.js | 8 +-- build/lib/monaco-api.js | 8 +-- build/lib/nls.js | 4 ++ build/lib/node.js | 8 +-- build/lib/optimize.js | 8 +-- build/lib/policies/basePolicy.js | 4 +- build/lib/policies/booleanPolicy.js | 4 +- build/lib/policies/copyPolicyDto.js | 8 +-- build/lib/policies/numberPolicy.js | 4 +- build/lib/policies/objectPolicy.js | 4 +- build/lib/policies/policyGenerator.js | 8 +-- build/lib/policies/render.js | 4 ++ build/lib/policies/stringEnumPolicy.js | 4 +- build/lib/policies/stringPolicy.js | 4 +- build/lib/policies/types.js | 4 ++ build/lib/preLaunch.js | 8 +-- build/lib/propertyInitOrderChecker.js | 8 +-- build/lib/reporter.js | 8 +-- build/lib/snapshotLoader.js | 4 +- build/lib/standalone.js | 8 +-- build/lib/stats.js | 8 +-- build/lib/stylelint/validateVariableNames.js | 8 +-- build/lib/task.js | 8 +-- build/lib/test/booleanPolicy.test.js | 8 +-- build/lib/test/i18n.test.js | 8 +-- build/lib/test/numberPolicy.test.js | 8 +-- build/lib/test/objectPolicy.test.js | 8 +-- build/lib/test/policyConversion.test.js | 8 +-- build/lib/test/render.test.js | 8 +-- build/lib/test/stringEnumPolicy.test.js | 8 +-- build/lib/test/stringPolicy.test.js | 8 +-- build/lib/treeshaking.js | 8 +-- build/lib/tsb/builder.js | 8 +-- build/lib/tsb/index.js | 8 +-- build/lib/tsb/transpiler.js | 8 +-- build/lib/tsb/utils.js | 4 +- build/lib/typeScriptLanguageServiceHost.js | 8 +-- build/lib/util.js | 8 +-- build/lib/watch/index.js | 2 +- build/lib/watch/watch-win32.js | 8 +-- build/linux/debian/calculate-deps.js | 8 +-- build/linux/debian/dep-lists.js | 4 +- build/linux/debian/install-sysroot.js | 8 +-- build/linux/debian/types.js | 4 ++ build/linux/libcxx-fetcher.js | 8 +-- build/linux/rpm/calculate-deps.js | 4 +- build/linux/rpm/dep-lists.js | 4 +- build/linux/rpm/types.js | 4 ++ package-lock.json | 62 +++++++++---------- 91 files changed, 345 insertions(+), 317 deletions(-) diff --git a/build/azure-pipelines/common/checkForArtifact.js b/build/azure-pipelines/common/checkForArtifact.js index 371ca6b9520..899448f78bd 100644 --- a/build/azure-pipelines/common/checkForArtifact.js +++ b/build/azure-pipelines/common/checkForArtifact.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const publish_1 = require("./publish"); const retry_1 = require("./retry"); async function getPipelineArtifacts() { diff --git a/build/azure-pipelines/common/codesign.js b/build/azure-pipelines/common/codesign.js index 4e82538d105..e3a8f330dcd 100644 --- a/build/azure-pipelines/common/codesign.js +++ b/build/azure-pipelines/common/codesign.js @@ -1,12 +1,12 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.printBanner = printBanner; -exports.streamProcessOutputAndCheckResult = streamProcessOutputAndCheckResult; -exports.spawnCodesignProcess = spawnCodesignProcess; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.printBanner = printBanner; +exports.streamProcessOutputAndCheckResult = streamProcessOutputAndCheckResult; +exports.spawnCodesignProcess = spawnCodesignProcess; const zx_1 = require("zx"); function printBanner(title) { title = `${title} (${new Date().toISOString()})`; diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js index 8f8c833f226..10fa9087454 100644 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const crypto_1 = __importDefault(require("crypto")); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/build/azure-pipelines/common/computeNodeModulesCacheKey.js index 59d570e96e6..c09c13be9d4 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const crypto_1 = __importDefault(require("crypto")); diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js index feb06cbe67f..c605ed6218e 100644 --- a/build/azure-pipelines/common/createBuild.js +++ b/build/azure-pipelines/common/createBuild.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const identity_1 = require("@azure/identity"); const cosmos_1 = require("@azure/cosmos"); const retry_1 = require("./retry"); diff --git a/build/azure-pipelines/common/getPublishAuthTokens.js b/build/azure-pipelines/common/getPublishAuthTokens.js index d0ead104b1c..9c22e9ad94b 100644 --- a/build/azure-pipelines/common/getPublishAuthTokens.js +++ b/build/azure-pipelines/common/getPublishAuthTokens.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.getAccessToken = getAccessToken; const msal_node_1 = require("@azure/msal-node"); diff --git a/build/azure-pipelines/common/listNodeModules.js b/build/azure-pipelines/common/listNodeModules.js index 7112ecab9cc..301b5f930b6 100644 --- a/build/azure-pipelines/common/listNodeModules.js +++ b/build/azure-pipelines/common/listNodeModules.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); if (process.argv.length !== 3) { diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 1886bd4c939..49b718344a0 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.e = e; exports.requestAZDOAPI = requestAZDOAPI; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const stream_1 = require("stream"); diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js index 2eaef6de443..b74e2847cbc 100644 --- a/build/azure-pipelines/common/releaseBuild.js +++ b/build/azure-pipelines/common/releaseBuild.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const cosmos_1 = require("@azure/cosmos"); const retry_1 = require("./retry"); function getEnv(name) { diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js index aaa90dd7006..91f60bf24b2 100644 --- a/build/azure-pipelines/common/retry.js +++ b/build/azure-pipelines/common/retry.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.retry = retry; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.retry = retry; async function retry(fn) { let lastError; for (let run = 1; run <= 10; run++) { diff --git a/build/azure-pipelines/common/sign-win32.js b/build/azure-pipelines/common/sign-win32.js index 1a6a6a97e02..f4e3f27c1f2 100644 --- a/build/azure-pipelines/common/sign-win32.js +++ b/build/azure-pipelines/common/sign-win32.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const sign_1 = require("./sign"); const path_1 = __importDefault(require("path")); (0, sign_1.main)([ diff --git a/build/azure-pipelines/common/sign.js b/build/azure-pipelines/common/sign.js index 2136e03d1f1..47c034dea1c 100644 --- a/build/azure-pipelines/common/sign.js +++ b/build/azure-pipelines/common/sign.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Temp = void 0; exports.main = main; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const child_process_1 = __importDefault(require("child_process")); const fs_1 = __importDefault(require("fs")); const crypto_1 = __importDefault(require("crypto")); diff --git a/build/azure-pipelines/common/waitForArtifacts.js b/build/azure-pipelines/common/waitForArtifacts.js index d84a34ecea7..b9ffb73962d 100644 --- a/build/azure-pipelines/common/waitForArtifacts.js +++ b/build/azure-pipelines/common/waitForArtifacts.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const publish_1 = require("../common/publish"); const retry_1 = require("../common/retry"); async function getPipelineArtifacts() { diff --git a/build/azure-pipelines/darwin/codesign.js b/build/azure-pipelines/darwin/codesign.js index d779280a7be..30a3bdc332b 100644 --- a/build/azure-pipelines/darwin/codesign.js +++ b/build/azure-pipelines/darwin/codesign.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const codesign_1 = require("../common/codesign"); const publish_1 = require("../common/publish"); async function main() { diff --git a/build/azure-pipelines/distro/mixin-npm.js b/build/azure-pipelines/distro/mixin-npm.js index 40f5ee134af..87958a5d449 100644 --- a/build/azure-pipelines/distro/mixin-npm.js +++ b/build/azure-pipelines/distro/mixin-npm.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const { dirs } = require('../../npm/dirs'); diff --git a/build/azure-pipelines/distro/mixin-quality.js b/build/azure-pipelines/distro/mixin-quality.js index 08aee7c89bd..335f63ca1fc 100644 --- a/build/azure-pipelines/distro/mixin-quality.js +++ b/build/azure-pipelines/distro/mixin-quality.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); function log(...args) { diff --git a/build/azure-pipelines/linux/codesign.js b/build/azure-pipelines/linux/codesign.js index 93ec2434ea8..98b97db5666 100644 --- a/build/azure-pipelines/linux/codesign.js +++ b/build/azure-pipelines/linux/codesign.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const codesign_1 = require("../common/codesign"); const publish_1 = require("../common/publish"); async function main() { diff --git a/build/azure-pipelines/publish-types/check-version.js b/build/azure-pipelines/publish-types/check-version.js index e9564a469d1..5bd80a69bbf 100644 --- a/build/azure-pipelines/publish-types/check-version.js +++ b/build/azure-pipelines/publish-types/check-version.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = __importDefault(require("child_process")); let tag = ''; try { diff --git a/build/azure-pipelines/publish-types/update-types.js b/build/azure-pipelines/publish-types/update-types.js index a528a6027d9..29f9bfcf66e 100644 --- a/build/azure-pipelines/publish-types/update-types.js +++ b/build/azure-pipelines/publish-types/update-types.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const child_process_1 = __importDefault(require("child_process")); const path_1 = __importDefault(require("path")); diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 603fbdbf95f..adff2c9401d 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const event_stream_1 = __importDefault(require("event-stream")); const vinyl_1 = __importDefault(require("vinyl")); const vinyl_fs_1 = __importDefault(require("vinyl-fs")); diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js index 18ff71b7191..e89a6497d70 100644 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ b/build/azure-pipelines/upload-nlsmetadata.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const event_stream_1 = __importDefault(require("event-stream")); const vinyl_fs_1 = __importDefault(require("vinyl-fs")); const gulp_merge_json_1 = __importDefault(require("gulp-merge-json")); diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index ecb8679d684..cac1ae3caf2 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -36,10 +40,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const path_1 = __importDefault(require("path")); const event_stream_1 = __importDefault(require("event-stream")); const vinyl_fs_1 = __importDefault(require("vinyl-fs")); diff --git a/build/azure-pipelines/win32/codesign.js b/build/azure-pipelines/win32/codesign.js index cee33ae3208..630f9a64ba1 100644 --- a/build/azure-pipelines/win32/codesign.js +++ b/build/azure-pipelines/win32/codesign.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const zx_1 = require("zx"); const codesign_1 = require("../common/codesign"); const publish_1 = require("../common/publish"); diff --git a/build/checker/layersChecker.js b/build/checker/layersChecker.js index a59901d8a57..ae84e8ffeb9 100644 --- a/build/checker/layersChecker.js +++ b/build/checker/layersChecker.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const typescript_1 = __importDefault(require("typescript")); const fs_1 = require("fs"); const path_1 = require("path"); diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index b5ae1a86745..98e14ef2160 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const minimatch_1 = __importDefault(require("minimatch")); diff --git a/build/darwin/sign.js b/build/darwin/sign.js index af8dad69b34..d640e94fbf5 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const osx_sign_1 = require("@electron/osx-sign"); diff --git a/build/darwin/verify-macho.js b/build/darwin/verify-macho.js index 6a5b4a0895d..8202e8d7b76 100644 --- a/build/darwin/verify-macho.js +++ b/build/darwin/verify-macho.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const path_1 = __importDefault(require("path")); const promises_1 = require("fs/promises"); diff --git a/build/lib/asar.js b/build/lib/asar.js index 2da31a93904..20c982a6621 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAsar = createAsar; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const path_1 = __importDefault(require("path")); const event_stream_1 = __importDefault(require("event-stream")); const pickle = require('chromium-pickle-js'); diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 95fe7a09bac..249777c4458 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -38,10 +42,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.getExtensionStream = getExtensionStream; exports.getBuiltInExtensions = getBuiltInExtensions; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); diff --git a/build/lib/builtInExtensionsCG.js b/build/lib/builtInExtensionsCG.js index 70546237ba4..3dc0ae27f0a 100644 --- a/build/lib/builtInExtensionsCG.js +++ b/build/lib/builtInExtensionsCG.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const url_1 = __importDefault(require("url")); diff --git a/build/lib/bundle.js b/build/lib/bundle.js index 08f29d10847..382b648defb 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.removeAllTSBoilerplate = removeAllTSBoilerplate; function removeAllTSBoilerplate(source) { diff --git a/build/lib/compilation.js b/build/lib/compilation.js index fb326dfd2b1..ac6eae352b0 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -41,10 +45,6 @@ exports.createCompile = createCompile; exports.transpileTask = transpileTask; exports.compileTask = compileTask; exports.watchTask = watchTask; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const fs_1 = __importDefault(require("fs")); const gulp_1 = __importDefault(require("gulp")); diff --git a/build/lib/date.js b/build/lib/date.js index d189815ab06..1ed884fb7ee 100644 --- a/build/lib/date.js +++ b/build/lib/date.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.writeISODate = writeISODate; exports.readISODate = readISODate; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const root = path_1.default.join(__dirname, '..', '..'); diff --git a/build/lib/dependencies.js b/build/lib/dependencies.js index c6baaafa070..04a09f98708 100644 --- a/build/lib/dependencies.js +++ b/build/lib/dependencies.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getProductionDependencies = getProductionDependencies; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const child_process_1 = __importDefault(require("child_process")); diff --git a/build/lib/electron.js b/build/lib/electron.js index 0602307f4c3..56992d8a7f7 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -37,10 +41,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const vinyl_fs_1 = __importDefault(require("vinyl-fs")); diff --git a/build/lib/extensions.js b/build/lib/extensions.js index c80a1be1a84..e3736888924 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -47,10 +51,6 @@ exports.scanBuiltinExtensions = scanBuiltinExtensions; exports.translatePackageJSON = translatePackageJSON; exports.webpackExtensions = webpackExtensions; exports.buildExtensionMedia = buildExtensionMedia; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const fs_1 = __importDefault(require("fs")); const child_process_1 = __importDefault(require("child_process")); diff --git a/build/lib/fetch.js b/build/lib/fetch.js index e4705101569..b0876cda75a 100644 --- a/build/lib/fetch.js +++ b/build/lib/fetch.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -6,10 +10,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fetchUrls = fetchUrls; exports.fetchUrl = fetchUrl; exports.fetchGithub = fetchGithub; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const vinyl_1 = __importDefault(require("vinyl")); const fancy_log_1 = __importDefault(require("fancy-log")); diff --git a/build/lib/getVersion.js b/build/lib/getVersion.js index 94744415d60..7606c17ab14 100644 --- a/build/lib/getVersion.js +++ b/build/lib/getVersion.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -34,10 +38,6 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getVersion = getVersion; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const git = __importStar(require("./git")); function getVersion(root) { let version = process.env['BUILD_SOURCEVERSION']; diff --git a/build/lib/i18n.js b/build/lib/i18n.js index e0dafe44aaf..0b371c8b812 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -11,10 +15,6 @@ exports.createXlfFilesForExtensions = createXlfFilesForExtensions; exports.createXlfFilesForIsl = createXlfFilesForIsl; exports.prepareI18nPackFiles = prepareI18nPackFiles; exports.prepareIslFiles = prepareIslFiles; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const event_stream_1 = require("event-stream"); diff --git a/build/lib/inlineMeta.js b/build/lib/inlineMeta.js index b997f5e9a78..3b473ae091e 100644 --- a/build/lib/inlineMeta.js +++ b/build/lib/inlineMeta.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.inlineMeta = inlineMeta; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const path_1 = require("path"); const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js index 40a9f7bbe66..fa729052f7c 100644 --- a/build/lib/mangle/index.js +++ b/build/lib/mangle/index.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Mangler = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const node_v8_1 = __importDefault(require("node:v8")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); @@ -63,10 +63,10 @@ class ClassData { parent; children; constructor(fileName, node) { - this.fileName = fileName; - this.node = node; // analyse all fields (properties and methods). Find usages of all protected and // private ones and keep track of all public ones (to prevent naming collisions) + this.fileName = fileName; + this.node = node; const candidates = []; for (const member of node.members) { if (typescript_1.default.isMethodDeclaration(member)) { diff --git a/build/lib/mangle/renameWorker.js b/build/lib/mangle/renameWorker.js index d34e0a2346f..8bd59a4e2d5 100644 --- a/build/lib/mangle/renameWorker.js +++ b/build/lib/mangle/renameWorker.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const typescript_1 = __importDefault(require("typescript")); const workerpool_1 = __importDefault(require("workerpool")); const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost"); diff --git a/build/lib/mangle/staticLanguageServiceHost.js b/build/lib/mangle/staticLanguageServiceHost.js index e17846f717f..7777888dd06 100644 --- a/build/lib/mangle/staticLanguageServiceHost.js +++ b/build/lib/mangle/staticLanguageServiceHost.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StaticLanguageServiceHost = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const typescript_1 = __importDefault(require("typescript")); const path_1 = __importDefault(require("path")); class StaticLanguageServiceHost { diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index 3d9a4c7ca10..1112b47370d 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -6,10 +10,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; exports.run3 = run3; exports.execute = execute; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const fancy_log_1 = __importDefault(require("fancy-log")); diff --git a/build/lib/nls.js b/build/lib/nls.js index 492dbdae8ce..55984151ddb 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; diff --git a/build/lib/node.js b/build/lib/node.js index 62533fbe6ca..01a381183ff 100644 --- a/build/lib/node.js +++ b/build/lib/node.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const root = path_1.default.dirname(path_1.default.dirname(__dirname)); diff --git a/build/lib/optimize.js b/build/lib/optimize.js index fbc455b1cd1..2a87c239c94 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -38,10 +42,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.bundleTask = bundleTask; exports.minifyTask = minifyTask; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const gulp_1 = __importDefault(require("gulp")); const gulp_filter_1 = __importDefault(require("gulp-filter")); diff --git a/build/lib/policies/basePolicy.js b/build/lib/policies/basePolicy.js index 844b395a088..5c1b919d428 100644 --- a/build/lib/policies/basePolicy.js +++ b/build/lib/policies/basePolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.BasePolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BasePolicy = void 0; const render_1 = require("./render"); class BasePolicy { type; diff --git a/build/lib/policies/booleanPolicy.js b/build/lib/policies/booleanPolicy.js index b7772650337..77ea3d9a42e 100644 --- a/build/lib/policies/booleanPolicy.js +++ b/build/lib/policies/booleanPolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.BooleanPolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BooleanPolicy = void 0; const basePolicy_1 = require("./basePolicy"); const render_1 = require("./render"); const types_1 = require("./types"); diff --git a/build/lib/policies/copyPolicyDto.js b/build/lib/policies/copyPolicyDto.js index 8ce193770c7..a223bb4c0ef 100644 --- a/build/lib/policies/copyPolicyDto.js +++ b/build/lib/policies/copyPolicyDto.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -33,10 +37,6 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs = __importStar(require("fs")); const path = __importStar(require("path")); const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); diff --git a/build/lib/policies/numberPolicy.js b/build/lib/policies/numberPolicy.js index 62f6981f01a..3bc0b98d19a 100644 --- a/build/lib/policies/numberPolicy.js +++ b/build/lib/policies/numberPolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.NumberPolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NumberPolicy = void 0; const basePolicy_1 = require("./basePolicy"); const render_1 = require("./render"); const types_1 = require("./types"); diff --git a/build/lib/policies/objectPolicy.js b/build/lib/policies/objectPolicy.js index be83c76613d..43a7aaa3fc9 100644 --- a/build/lib/policies/objectPolicy.js +++ b/build/lib/policies/objectPolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ObjectPolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ObjectPolicy = void 0; const basePolicy_1 = require("./basePolicy"); const render_1 = require("./render"); const types_1 = require("./types"); diff --git a/build/lib/policies/policyGenerator.js b/build/lib/policies/policyGenerator.js index bd549b0092d..132e55873da 100644 --- a/build/lib/policies/policyGenerator.js +++ b/build/lib/policies/policyGenerator.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -36,10 +40,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const minimist_1 = __importDefault(require("minimist")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); diff --git a/build/lib/policies/render.js b/build/lib/policies/render.js index c867442cf50..8661dab9154 100644 --- a/build/lib/policies/render.js +++ b/build/lib/policies/render.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.renderADMLString = renderADMLString; exports.renderProfileString = renderProfileString; diff --git a/build/lib/policies/stringEnumPolicy.js b/build/lib/policies/stringEnumPolicy.js index 2d43c275ea3..20403b3590a 100644 --- a/build/lib/policies/stringEnumPolicy.js +++ b/build/lib/policies/stringEnumPolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.StringEnumPolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StringEnumPolicy = void 0; const basePolicy_1 = require("./basePolicy"); const render_1 = require("./render"); const types_1 = require("./types"); diff --git a/build/lib/policies/stringPolicy.js b/build/lib/policies/stringPolicy.js index 62018248e6a..1db9e53649b 100644 --- a/build/lib/policies/stringPolicy.js +++ b/build/lib/policies/stringPolicy.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.StringPolicy = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StringPolicy = void 0; const basePolicy_1 = require("./basePolicy"); const render_1 = require("./render"); const types_1 = require("./types"); diff --git a/build/lib/policies/types.js b/build/lib/policies/types.js index f0456389d79..9eab676dec5 100644 --- a/build/lib/policies/types.js +++ b/build/lib/policies/types.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.Languages = exports.PolicyType = void 0; var PolicyType; diff --git a/build/lib/preLaunch.js b/build/lib/preLaunch.js index ca79a09b068..75207fe50c0 100644 --- a/build/lib/preLaunch.js +++ b/build/lib/preLaunch.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); // @ts-check const path_1 = __importDefault(require("path")); const child_process_1 = require("child_process"); diff --git a/build/lib/propertyInitOrderChecker.js b/build/lib/propertyInitOrderChecker.js index a8dab0f29fe..58921645599 100644 --- a/build/lib/propertyInitOrderChecker.js +++ b/build/lib/propertyInitOrderChecker.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -33,10 +37,6 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const ts = __importStar(require("typescript")); const path = __importStar(require("path")); const fs = __importStar(require("fs")); diff --git a/build/lib/reporter.js b/build/lib/reporter.js index f07b7626199..cb7fd272d5d 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createReporter = createReporter; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); diff --git a/build/lib/snapshotLoader.js b/build/lib/snapshotLoader.js index 315ebcc1e01..7d9b3f154f1 100644 --- a/build/lib/snapshotLoader.js +++ b/build/lib/snapshotLoader.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.snaps = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.snaps = void 0; var snaps; (function (snaps) { const fs = require('fs'); diff --git a/build/lib/standalone.js b/build/lib/standalone.js index b153e70348b..9d38b863b51 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -37,10 +41,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractEditor = extractEditor; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const tss = __importStar(require("./treeshaking")); diff --git a/build/lib/stats.js b/build/lib/stats.js index e6a4f9f633e..3f6d953ae40 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createStatsStream = createStatsStream; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); diff --git a/build/lib/stylelint/validateVariableNames.js b/build/lib/stylelint/validateVariableNames.js index a5e84d415ba..b0e064e7b56 100644 --- a/build/lib/stylelint/validateVariableNames.js +++ b/build/lib/stylelint/validateVariableNames.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getVariableNameValidator = getVariableNameValidator; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; diff --git a/build/lib/task.js b/build/lib/task.js index 23b8d968584..a30b65b288c 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -6,10 +10,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.series = series; exports.parallel = parallel; exports.define = define; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); function _isPromise(p) { diff --git a/build/lib/test/booleanPolicy.test.js b/build/lib/test/booleanPolicy.test.js index 0c6ded63c2b..944916c3d76 100644 --- a/build/lib/test/booleanPolicy.test.js +++ b/build/lib/test/booleanPolicy.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const booleanPolicy_js_1 = require("../policies/booleanPolicy.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/test/i18n.test.js b/build/lib/test/i18n.test.js index da7a426a103..41aa8a7f668 100644 --- a/build/lib/test/i18n.test.js +++ b/build/lib/test/i18n.test.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -36,10 +40,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const assert_1 = __importDefault(require("assert")); const i18n = __importStar(require("../i18n")); suite('XLF Parser Tests', () => { diff --git a/build/lib/test/numberPolicy.test.js b/build/lib/test/numberPolicy.test.js index 9a1d498edde..312ec7587ee 100644 --- a/build/lib/test/numberPolicy.test.js +++ b/build/lib/test/numberPolicy.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const numberPolicy_js_1 = require("../policies/numberPolicy.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/test/objectPolicy.test.js b/build/lib/test/objectPolicy.test.js index 987b45242c4..a34d71383d2 100644 --- a/build/lib/test/objectPolicy.test.js +++ b/build/lib/test/objectPolicy.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const objectPolicy_js_1 = require("../policies/objectPolicy.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/test/policyConversion.test.js b/build/lib/test/policyConversion.test.js index 2e079b3106e..6fc735f1127 100644 --- a/build/lib/test/policyConversion.test.js +++ b/build/lib/test/policyConversion.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); diff --git a/build/lib/test/render.test.js b/build/lib/test/render.test.js index 1853dda04e3..87c7fa14621 100644 --- a/build/lib/test/render.test.js +++ b/build/lib/test/render.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const render_js_1 = require("../policies/render.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/test/stringEnumPolicy.test.js b/build/lib/test/stringEnumPolicy.test.js index 860ad3e6c0f..d1700730544 100644 --- a/build/lib/test/stringEnumPolicy.test.js +++ b/build/lib/test/stringEnumPolicy.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const stringEnumPolicy_js_1 = require("../policies/stringEnumPolicy.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/test/stringPolicy.test.js b/build/lib/test/stringPolicy.test.js index 059510ca89d..6919da78f88 100644 --- a/build/lib/test/stringPolicy.test.js +++ b/build/lib/test/stringPolicy.test.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = __importDefault(require("assert")); const stringPolicy_js_1 = require("../policies/stringPolicy.js"); const types_js_1 = require("../policies/types.js"); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index baaf029861d..feca811d9f9 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.toStringShakeLevel = toStringShakeLevel; exports.shake = shake; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const typeScriptLanguageServiceHost_1 = require("./typeScriptLanguageServiceHost"); diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 1c0640a11e7..eb8e7bca1b3 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -38,10 +42,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.CancellationToken = void 0; exports.createTypeScriptBuilder = createTypeScriptBuilder; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const crypto_1 = __importDefault(require("crypto")); diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index af10bf8ce19..552eea5014f 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -37,10 +41,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const vinyl_1 = __importDefault(require("vinyl")); const through_1 = __importDefault(require("through")); const builder = __importStar(require("./builder")); diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 4720ce43975..2bca1be4426 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ESBuildTranspiler = exports.TscTranspiler = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const esbuild_1 = __importDefault(require("esbuild")); const typescript_1 = __importDefault(require("typescript")); const node_worker_threads_1 = __importDefault(require("node:worker_threads")); diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js index 72de33b5ac7..2ea820c6e6b 100644 --- a/build/lib/tsb/utils.js +++ b/build/lib/tsb/utils.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.graph = exports.strings = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.graph = exports.strings = void 0; var strings; (function (strings) { function format(value, ...rest) { diff --git a/build/lib/typeScriptLanguageServiceHost.js b/build/lib/typeScriptLanguageServiceHost.js index 7abc708cc64..6ba0802102d 100644 --- a/build/lib/typeScriptLanguageServiceHost.js +++ b/build/lib/typeScriptLanguageServiceHost.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeScriptLanguageServiceHost = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const typescript_1 = __importDefault(require("typescript")); const node_fs_1 = __importDefault(require("node:fs")); const node_path_1 = require("node:path"); diff --git a/build/lib/util.js b/build/lib/util.js index d907b3e6322..9d2f3b13a06 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -23,10 +27,6 @@ exports.rebase = rebase; exports.filter = filter; exports.streamToPromise = streamToPromise; exports.getElectronVersion = getElectronVersion; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const event_stream_1 = __importDefault(require("event-stream")); const debounce_1 = __importDefault(require("debounce")); const gulp_filter_1 = __importDefault(require("gulp-filter")); diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js index 21dc978dbfc..69eca78fd70 100644 --- a/build/lib/watch/index.js +++ b/build/lib/watch/index.js @@ -1,9 +1,9 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); module.exports = function () { return watch.apply(null, arguments); diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index 4113d93526e..7b77981d620 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -1,12 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const child_process_1 = __importDefault(require("child_process")); const fs_1 = __importDefault(require("fs")); diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js index c9c96967f47..34276ce7705 100644 --- a/build/linux/debian/calculate-deps.js +++ b/build/linux/debian/calculate-deps.js @@ -1,13 +1,13 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generatePackageDeps = generatePackageDeps; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const child_process_1 = require("child_process"); const fs_1 = require("fs"); const os_1 = require("os"); diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index f58d0f8e866..4ef448d454e 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps // Additional dependencies not in the dpkg-shlibdeps output. exports.additionalDeps = [ diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index 1134130d780..4a9a46e6bd6 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getVSCodeSysroot = getVSCodeSysroot; exports.getChromiumSysroot = getChromiumSysroot; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ const child_process_1 = require("child_process"); const os_1 = require("os"); const fs_1 = __importDefault(require("fs")); diff --git a/build/linux/debian/types.js b/build/linux/debian/types.js index f2a80aebb7a..ce21d50e1a9 100644 --- a/build/linux/debian/types.js +++ b/build/linux/debian/types.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.isDebianArchString = isDebianArchString; function isDebianArchString(s) { diff --git a/build/linux/libcxx-fetcher.js b/build/linux/libcxx-fetcher.js index 710a8be7434..d6c998e5aea 100644 --- a/build/linux/libcxx-fetcher.js +++ b/build/linux/libcxx-fetcher.js @@ -1,14 +1,14 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadLibcxxHeaders = downloadLibcxxHeaders; exports.downloadLibcxxObjects = downloadLibcxxObjects; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); diff --git a/build/linux/rpm/calculate-deps.js b/build/linux/rpm/calculate-deps.js index b31a5aa9d5c..b19e26f1854 100644 --- a/build/linux/rpm/calculate-deps.js +++ b/build/linux/rpm/calculate-deps.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = generatePackageDeps; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generatePackageDeps = generatePackageDeps; const child_process_1 = require("child_process"); const fs_1 = require("fs"); const dep_lists_1 = require("./dep-lists"); diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index 74156ebe47a..2f742daf2f8 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -1,10 +1,10 @@ "use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps // Additional dependencies not in the rpm find-requires output. exports.additionalDeps = [ diff --git a/build/linux/rpm/types.js b/build/linux/rpm/types.js index 39b1334cc7b..a20b9c2fe02 100644 --- a/build/linux/rpm/types.js +++ b/build/linux/rpm/types.js @@ -1,4 +1,8 @@ "use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.isRpmArchString = isRpmArchString; function isRpmArchString(s) { diff --git a/package-lock.json b/package-lock.json index c03b6394d9a..75a9b72de7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2506,28 +2506,28 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-B7svR7Fm4cLLL8ARBVc9Xx2KFcZuCkNsdR7LBZjSqaDM6am11XizhPORrI7x8rlVQ71CBoQVgOp/2/Bn3x+OgA==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-djbOSIm8Or967wMuO209ydMp2nq34hEulah1EhjUsLSqLplsbOk8RSOyVJJphU+CMP33rULDcnDAzvylU8Tq9Q==", "dev": true, "license": "Apache-2.0", "bin": { "tsgo": "bin/tsgo.js" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250922.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250922.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20250922.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250922.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20250922.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250922.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20250922.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251027.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251027.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20251027.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251027.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20251027.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251027.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20251027.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-R4sKE/cQYMGrmaz8OBnnseABdB6W0ZCQ9muHQnoxuyukf7m0wLLqvGNorQZSWVl9xQ7qNniXcZJlCKp4JmRr6w==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-4Nysrmep6Z4C722nQF07XkEk22qyI2/vCfvfPSlhOxpJJcIFAroxSkSH7Qy8EDZWhNer9D4CMTYX9q5I8B75lQ==", "cpu": [ "arm64" ], @@ -2539,9 +2539,9 @@ ] }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-Hhh+9e/75ZQxdatdA8TFKOKjPwqjXWNI2a8vswvMU6zOmiVYZoYaUGEGFhk0W1WPGnt/kZSM1Mb4a9nVaEwhZQ==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-WvHLb6Mry214ZTuhfvv6fP1FLgYZ4oTw55+B2hTAo/O6qq9KX3OW90dvFYSMJKPhgvWR5B9tIEcMkIXGjxfv1w==", "cpu": [ "x64" ], @@ -2553,9 +2553,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-4JbSk4B0SUo2c+S9YKGLAaMQXA16Pd9Cd7GLhMiABRtOLEHFDAaFmFbVoGYX5Dyicix711oziX/WAhD6nuC73A==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-epAynE0qbU9nuPwaOgr9N6WANoYAdwhyteNB+PG2qRWYoFDYPXSgParjO1FAkY0uMt88QaS6vQ6ZglInHsxvXQ==", "cpu": [ "arm" ], @@ -2567,9 +2567,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-Rw74z0tf0NEujnl0W58fQ5pbh8P1fr2ujQTMkQYz9WpH+hYuysQgcOiDEoht62CxtLHflexu3dUWJI8/LUPyHA==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-CNbTvppx8wsoRS3g4RcpDapRp4tNYp1eu+94HmtKT7ch3RJOliKIhAa/8odXIrkqnT+kc0wrQCzFiICMW4YieQ==", "cpu": [ "arm64" ], @@ -2581,9 +2581,9 @@ ] }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-lY6JAZgzeJxLwciC8Wc9l7OlDYwVbuo3JK+qHfLlyxu8h1Q3cGF5VMpgSlAj9CKLDQ0h4kDAecLLiEEOod78aw==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-lzSUTdWYfKvsQJPQF/BtYil1Xmzn0f3jpgk8/4uVg4NQeDtzW0J3ceWl2lw1TuGnhISq2dwyupjKJfLQhe4AVQ==", "cpu": [ "x64" ], @@ -2595,9 +2595,9 @@ ] }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-pGzWqNx7x6Sqs20Ea5Xn/JC9eZ9Do6bu6K+dmfpewYYhwUv8jiOhh+fr9TJFZ8MlaZDTp9FclQIGpW+2MZ8UOw==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-K9K8t3HW/35ejgVJALPW9Fqo0PHOxh1/ir01C8r5qbhIdPQqwGlBHAGwLzrfH0ZF1R2nR2X4T+z+gB8tLULsow==", "cpu": [ "arm64" ], @@ -2609,9 +2609,9 @@ ] }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20250922.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250922.1.tgz", - "integrity": "sha512-RoNm3P69p5F/ZJ7O7nIoUJB0OVG5nGIEVzP/VF2lm8jBbDN8aCRNT+DkZrQvdXJEBg2fM0WrTzcik9KVXAyEag==", + "version": "7.0.0-dev.20251027.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251027.1.tgz", + "integrity": "sha512-n7hb7ZjAEgoNBWYSt87+eMtSK2h6Xl9NWUd2ocw3Znz/tw8lwpUaG35FVd/Aj72kT1/5kiCBlM+7MxA214KGiw==", "cpu": [ "x64" ], From 672c6e0164c3dda354ee76e7b8115370d4811da8 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 28 Oct 2025 16:21:31 +0000 Subject: [PATCH 1790/4355] codicons: rename branch status icons and update font - Rename branch-dirty -> git-branch-changes - Rename branch-staged-changes -> git-branch-staged-changes - Rename branch-merge-rebase-in-progress -> git-branch-conflicts - Update codicon.ttf to reflect new glyph mappings --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 121112 -> 121112 bytes src/vs/base/common/codiconsLibrary.ts | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 1249a8b970829937929a6e01ddbe33a3628e7d7d..fcb9911db5777498883022efa2ea85b9de35a4a6 100644 GIT binary patch delta 3175 zcmYk;dvr~A760A*M+tQpGhGRij=% zGYnNq1|?E*C0?nDDw;*ao0&FaA`HKKM%5!VovF?~Yp+?We|*l#IX7$N+6yX&^dgFj`R!_*g^U91`VJz z`UVE7QBT*X0=v}(&jjah@DB1(fFdk_4Iv0c7`me;q7j2w^hO-|AOVR;LSGC(KcpcY z12G7LF$BYriIEtC*?1ej#$3FI#c*O7R$>)Cz-s&fYp@pUupS$*5ek3ApRfgg#>d!+ z670jLC=J6wl;JZR!eNx-2r6+5CvXx~_#CHj8eifpzQQ?G(t1*>l{A6gq_+^Nyu3nb zvx@afpbGW2S4vVS{nrB?gk#i&x>7e3;cZqv$+oYNQ6bhby$4R#FC*(ogg=y`oE0 zOP8sRuFzGw2p`O(Y#gN@ummrtIW;2>Ou(-&9)4&-9j;*>7SSqv!5@=9?&ATCMOSpA za8=wUoOY?5ZTy|7=!5{|;5u&5I*g@FRD$P}NVn(?M$k^2q34LFG1QL|s1P%#6D_9Q z*o;kdkhbCuZs9gH(F+Wt_h|)Y;aiHLNHowW_~I_M;SEfuNyx=C0^Y2K|lTe1ra0aMD573nzyj^xp-? zAM{RN>0(R6u<1OrV{Psi%SVltRGGb710nfSc#Q#6 z98fnqJ7}Tu3ED<^sw~LgI+R~IAh@95=C9R+J*qy)L3S0;#b!p3 z$BfZYaM!`G3dE})Z4R2D;zR6hHuF83z$TR+GKq$%t06X;rJ6z}S-0^k34;3$MxwxU zl^5!tlFL^PfoXi^56?vW^ag);CT>O;{RDR;jQ#@0)tOKmO;ry<2h&~^6=ru%$#dOks=?T&pm$1t#-(wBV|RF-CCd!Wb*KeqrPYE@BwtfEMNf zS*jt-78B05%m-)O&M?f!VcgR&%*SNh*f7iwfWf8W!yWXl%ID)&RTgfya!4EV1a~)# zJb??UDSW=OZ>!2y4?U-PK5RX{wc)kItFg_gwx#Xnw%gyX#xmS8*RsY^YPslL>|N$v z@7?5+=#%TS!sl*#pY{>$v)dQ@w)8FP5Y%B+hf2R-ztw&<{=@yNI=1atX>DoEw{8uH z3Ru%=X{RTFYXh%#_UgR4^W&h@pt>%Lf^)jI?0UW1K3hhJhcl!mG&jsMEHkV;?0I-l zczk$G_qN?n^%&OUW<+4b_=wdJrex~EUi=$qMky&X&)UwEPh%1(OHy*u&ZNe^Bcl7RaAY|4I~tNRlTW0Cr7TH#*{`sl(LW~@sky1O0}=<6qy?nq zr0q$oPY+1XNI#OU2L=z!8F+TkutEC<85t7>Ck`$dVi~e(XzI}0u0uV%eO;dnD0I52 zQ|>og9#DNWpql?{a%%pRylJx|r_G*{H)}?L?=Nr7&zm`S&a}b; Y>o0B=6i%5nWBUJp{phtC8q}8XKLgCeivR!s delta 3170 zcmXZdX;_r!6$aqvpfM=|0c0DLMTS)w5M+}PP!Ld~7;%j|D2g_s0u6?!(?o03wo!Sl zu|$nE)I_4Vj9U;lVl?K8sJL50say4(X=B`CZAyB+>*U8h-@G$FX8F!}o(+3n-mv%O zH3^*-SDDr(4eQ*;z2)KBWwCAtPJ{-5nks=Fb!(eqJD(YEHtlhixu_0uvrp)BjMU;T zNzBb&@=~vt`c0tcR70C+BX%N_CSwa8Q#6LqDXc*NJ;otyrE+?W22wWNgcB#xMi*&6 zwyCdOQ*9Nf#BcBx-bOVd5rt^Ppf}>s7fDD)3R01VbXd_J85o3t$VDFVF&IPf8b)9w zMqwO&jkzepJS@R)@g9DMmH0jWfIs2`tj1c@Kw%yJg!R~fKjR~8#9#0UKE>zQfnAZ< zjXl^4JN98e4&nt_Ys#S?@sdPxq zam(lzO+UH7g>aZcs3(P?3JdW*vatg1&><|xDl9`eVqu{qe1--*pnD*guvoRbnQRN_ zdkmu#Y{wmnp?oSuB8{UF$ie`OrqiV94&9>LxIjy3Iptv~{f~a84mwLMbdJu`1!|>l z;f>i;h(`Jm@8T(SrcUI7$(V$R@WV5l$3@JCjaHzU|0aLj#XTB>o(Q8Twa`6^{;IaP z``dER9f5cQmvEU@V?3>+Iy|8?x=PpaI&HyM^aTBAG-XmMRic!-(_;DrwfK;BQZ26I zDz4EpdWzxnJ}tu>d`EF)!7UmIAGBjL3NVYNq6jkyn1W(7Q6M#QXB z&5}MIXeYkOt6Y^uidti8>rW3l-BmT8TxpgNEP{I%5-6(`HJB z84(D_H#kc_0My04*29HV&8r{N1eNG%jo1n31p=9TEf+k#&=mq(RE?+EG=xu8f=3#9 zp}-o|;%Rn2#HU39TUCeWC_1SMz2a$`{XMS{#M2G^so*(>{!H-1LvI&6`_T1*w*dNc z!TSKcL-1xm8(*36jzBjE-WKRxg7*e`x8Mze-UHZ-2VN%Vy@J;Y+Aes(p!W%0HR%0< zmk#=X;Prz(D0mT}jo-_7C856%yqwUDg4Y!Ku;7KYK{xS%@$Nz&5xm9FM+NUQ^fAGk z4SihjjzgaiyzS7Jy6r69+-w94S=r&>F(1ZTF;P``X7aWAp{}3FB z(6y4?@g2NWtAvkuS zoq_`x`d@;h82aCWLmB#e!SM|JP;gK~{~$QBp&tnjZ|KJYN2l?VCxR0l`l-NizCIJ2 z^3XpD&U@(Rf|DQmKY}{|`i0;&fc~$*8GF57mJ6*^Iv}2+RiNoM)!I8uBZv;!3%XYj zzt-F%o(`)=J>oN}0LKc!^#BJCh=Gd&j#Yvy0}h@Q1D6LJJSzsgqoxG;7cJ)#Plkaj z1&%d>%LNXe5(C!^96TikE*v;`N(`{@m8ZnOB?JdgiGk|~4jvK%8q}2_D=k*u!5`5A z)e!7&8piMNEjDmL!LdVNyJ`!zQlbhBF`KgZHNHy*4B+blfzfJgNEIzrEg`FEwwe;^ zZ{m<}oD*DAaPT@A@UHqG)M}c{rz?VM3y!M-6VQS)re zU4vbZcJ0-5Ti0uDg>Ki}2fN?&DD$}VYJ=xSuSs6DUX9-Uy(f7u^4{p()UCAJ=58(B z?)gOf6!=v7ocDF{4fM_SE%klwH`Cw4f1&@j0MCHxfJRe)(+*Qd;I_c$-HW@g>Ji*y zQPBLL_TVMK&A~r~REM;M_6a@S^Ubilu;=EJw(t!RDG`q%8>0%MUPNa`Z;iea;}H`X z(-_mytD$##?^CgEv4dl)W4Fd0kG)}Wv4mR2T3X_&jQb Date: Tue, 28 Oct 2025 17:51:49 +0100 Subject: [PATCH 1791/4355] Add eslint rule for no in-operator (#273779) * add no-in-operator rule, config, and ignore-list * add `hasKey` util and tests * updates * sample adoption, no `in` in strings.ts --- .eslint-plugin-local/code-no-in-operator.ts | 57 +++++ eslint.config.js | 240 ++++++++++++++++++++ src/vs/base/common/strings.ts | 2 +- src/vs/base/common/types.ts | 33 +++ src/vs/base/test/common/types.test.ts | 87 +++++++ 5 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 .eslint-plugin-local/code-no-in-operator.ts diff --git a/.eslint-plugin-local/code-no-in-operator.ts b/.eslint-plugin-local/code-no-in-operator.ts new file mode 100644 index 00000000000..dcfb1afc22e --- /dev/null +++ b/.eslint-plugin-local/code-no-in-operator.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; + +/** + * Disallows the use of the `in` operator in TypeScript code, except within + * type predicate functions (functions with `arg is Type` return types). + * + * The `in` operator can lead to runtime errors and type safety issues. + * Consider using Object.hasOwn(), hasOwnProperty(), or other safer patterns. + * + * Exception: Type predicate functions are allowed to use the `in` operator + * since they are the standard way to perform runtime type checking. + */ +export = new class NoInOperator implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + noInOperator: 'The "in" operator should not be used. Use type discriminator properties and classes instead or the `hasKey`-utility.', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + function checkInOperator(inNode: any) { + // Check if we're inside a type predicate function + const ancestors = context.sourceCode.getAncestors(inNode); + + for (const ancestor of ancestors) { + if (ancestor.type === 'FunctionDeclaration' || + ancestor.type === 'FunctionExpression' || + ancestor.type === 'ArrowFunctionExpression') { + + // Check if this function has a type predicate return type + // Type predicates have the form: `arg is SomeType` + if ((ancestor as { returnType?: any }).returnType?.typeAnnotation?.type === 'TSTypePredicate') { + // This is a type predicate function, allow the "in" operator + return; + } + } + } + + context.report({ + node: inNode, + messageId: 'noInOperator' + }); + } + + return { + ['BinaryExpression[operator="in"]']: checkInOperator, + }; + } +}; diff --git a/eslint.config.js b/eslint.config.js index 92f5f25e2ed..b88d1400efd 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -178,6 +178,246 @@ export default tseslint.config( ] } }, + // Disallow 'in' operator except in type predicates + { + files: [ + '**/*.ts' + ], + ignores: [ + 'src/bootstrap-node.ts', + 'build/lib/extensions.ts', + 'build/lib/test/render.test.ts', + 'extensions/debug-auto-launch/src/extension.ts', + 'extensions/emmet/src/updateImageSize.ts', + 'extensions/emmet/src/util.ts', + 'extensions/git/src/blame.ts', + 'extensions/github/src/links.ts', + 'extensions/github-authentication/src/node/fetch.ts', + 'extensions/ipynb/src/deserializers.ts', + 'extensions/ipynb/src/notebookImagePaste.ts', + 'extensions/ipynb/src/serializers.ts', + 'extensions/notebook-renderers/src/index.ts', + 'extensions/terminal-suggest/src/fig/figInterface.ts', + 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts', + 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts', + 'extensions/terminal-suggest/src/terminalSuggestMain.ts', + 'extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts', + 'extensions/tunnel-forwarding/src/extension.ts', + 'extensions/typescript-language-features/src/utils/platform.ts', + 'extensions/typescript-language-features/web/src/webServer.ts', + 'src/vs/base/browser/broadcast.ts', + 'src/vs/base/browser/canIUse.ts', + 'src/vs/base/browser/dom.ts', + 'src/vs/base/browser/markdownRenderer.ts', + 'src/vs/base/browser/touch.ts', + 'src/vs/base/browser/webWorkerFactory.ts', + 'src/vs/base/common/async.ts', + 'src/vs/base/common/desktopEnvironmentInfo.ts', + 'src/vs/base/common/objects.ts', + 'src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts', + 'src/vs/base/test/common/snapshot.ts', + 'src/vs/base/test/common/timeTravelScheduler.ts', + 'src/vs/editor/browser/controller/editContext/native/debugEditContext.ts', + 'src/vs/editor/browser/gpu/gpuUtils.ts', + 'src/vs/editor/browser/gpu/taskQueue.ts', + 'src/vs/editor/browser/view.ts', + 'src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts', + 'src/vs/editor/browser/widget/diffEditor/utils.ts', + 'src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts', + 'src/vs/editor/common/config/editorOptions.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts', + 'src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts', + 'src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts', + 'src/vs/platform/configuration/common/configuration.ts', + 'src/vs/platform/configuration/common/configurationModels.ts', + 'src/vs/platform/contextkey/browser/contextKeyService.ts', + 'src/vs/platform/contextkey/test/common/scanner.test.ts', + 'src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts', + 'src/vs/platform/externalTerminal/node/externalTerminalService.ts', + 'src/vs/platform/hover/browser/hoverService.ts', + 'src/vs/platform/hover/browser/hoverWidget.ts', + 'src/vs/platform/instantiation/common/instantiationService.ts', + 'src/vs/platform/mcp/common/mcpManagementCli.ts', + 'src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts', + 'src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts', + 'src/vs/platform/terminal/common/terminalProfiles.ts', + 'src/vs/platform/terminal/node/ptyService.ts', + 'src/vs/platform/terminal/node/terminalProcess.ts', + 'src/vs/platform/terminal/node/terminalProfiles.ts', + 'src/vs/platform/terminal/test/node/terminalEnvironment.test.ts', + 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', + 'src/vs/workbench/api/browser/mainThreadDebugService.ts', + 'src/vs/workbench/api/browser/mainThreadTerminalService.ts', + 'src/vs/workbench/api/browser/mainThreadTesting.ts', + 'src/vs/workbench/api/common/extHost.api.impl.ts', + 'src/vs/workbench/api/common/extHostChatAgents2.ts', + 'src/vs/workbench/api/common/extHostChatSessions.ts', + 'src/vs/workbench/api/common/extHostDebugService.ts', + 'src/vs/workbench/api/common/extHostNotebookKernels.ts', + 'src/vs/workbench/api/common/extHostQuickOpen.ts', + 'src/vs/workbench/api/common/extHostRequireInterceptor.ts', + 'src/vs/workbench/api/common/extHostTelemetry.ts', + 'src/vs/workbench/api/common/extHostTerminalService.ts', + 'src/vs/workbench/api/common/extHostTypeConverters.ts', + 'src/vs/workbench/api/common/extHostTypes.ts', + 'src/vs/workbench/api/node/loopbackServer.ts', + 'src/vs/workbench/api/node/proxyResolver.ts', + 'src/vs/workbench/api/test/common/extHostTypeConverters.test.ts', + 'src/vs/workbench/api/test/common/testRPCProtocol.ts', + 'src/vs/workbench/api/worker/extHostExtensionService.ts', + 'src/vs/workbench/browser/parts/paneCompositeBar.ts', + 'src/vs/workbench/browser/parts/titlebar/titlebarPart.ts', + 'src/vs/workbench/browser/workbench.ts', + 'src/vs/workbench/common/notifications.ts', + 'src/vs/workbench/contrib/accessibility/browser/accessibleView.ts', + 'src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts', + 'src/vs/workbench/contrib/chat/browser/chat.ts', + 'src/vs/workbench/contrib/chat/browser/chatAttachmentResolveService.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditorInput.ts', + 'src/vs/workbench/contrib/chat/browser/chatFollowups.ts', + 'src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts', + 'src/vs/workbench/contrib/chat/browser/chatInputPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatListRenderer.ts', + 'src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions/common.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts', + 'src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts', + 'src/vs/workbench/contrib/chat/browser/chatWidget.ts', + 'src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts', + 'src/vs/workbench/contrib/chat/common/annotations.ts', + 'src/vs/workbench/contrib/chat/common/chat.ts', + 'src/vs/workbench/contrib/chat/common/chatAgents.ts', + 'src/vs/workbench/contrib/chat/common/chatModel.ts', + 'src/vs/workbench/contrib/chat/common/chatService.ts', + 'src/vs/workbench/contrib/chat/common/chatServiceImpl.ts', + 'src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts', + 'src/vs/workbench/contrib/chat/test/common/chatModel.test.ts', + 'src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts', + 'src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts', + 'src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts', + 'src/vs/workbench/contrib/debug/browser/breakpointsView.ts', + 'src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts', + 'src/vs/workbench/contrib/debug/browser/variablesView.ts', + 'src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts', + 'src/vs/workbench/contrib/debug/common/debugModel.ts', + 'src/vs/workbench/contrib/debug/common/debugger.ts', + 'src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts', + 'src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts', + 'src/vs/workbench/contrib/extensions/common/extensionQuery.ts', + 'src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts', + 'src/vs/workbench/contrib/issue/browser/issueFormService.ts', + 'src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts', + 'src/vs/workbench/contrib/markers/browser/markersView.ts', + 'src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts', + 'src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts', + 'src/vs/workbench/contrib/mcp/common/mcpResourceFilesystem.ts', + 'src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts', + 'src/vs/workbench/contrib/mcp/common/mcpServer.ts', + 'src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts', + 'src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts', + 'src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts', + 'src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts', + 'src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts', + 'src/vs/workbench/contrib/output/browser/outputView.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', + 'src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts', + 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', + 'src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts', + 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', + 'src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts', + 'src/vs/workbench/contrib/terminal/browser/remotePty.ts', + 'src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalActions.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalGroup.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalIcon.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalInstance.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalMenus.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalService.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts', + 'src/vs/workbench/contrib/terminal/browser/terminalView.ts', + 'src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts', + 'src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts', + 'src/vs/workbench/contrib/terminal/common/terminalExtensionPoints.ts', + 'src/vs/workbench/contrib/terminal/electron-browser/localTerminalBackend.ts', + 'src/vs/workbench/contrib/terminal/test/common/terminalDataBuffering.test.ts', + 'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts', + 'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts', + 'src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.initialHint.contribution.ts', + 'src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/toolTerminalCreator.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts', + 'src/vs/workbench/contrib/terminalContrib/commandGuide/browser/terminal.commandGuide.contribution.ts', + 'src/vs/workbench/contrib/terminalContrib/history/browser/terminalRunRecentQuickPick.ts', + 'src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts', + 'src/vs/workbench/contrib/terminalContrib/links/test/browser/linkTestUtils.ts', + 'src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts', + 'src/vs/workbench/contrib/terminalContrib/sendSequence/browser/terminal.sendSequence.contribution.ts', + 'src/vs/workbench/contrib/terminalContrib/sendSignal/browser/terminal.sendSignal.contribution.ts', + 'src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts', + 'src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts', + 'src/vs/workbench/contrib/testing/browser/explorerProjections/listProjection.ts', + 'src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts', + 'src/vs/workbench/contrib/testing/browser/testCoverageBars.ts', + 'src/vs/workbench/contrib/testing/browser/testExplorerActions.ts', + 'src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts', + 'src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts', + 'src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts', + 'src/vs/workbench/contrib/testing/common/testCoverageService.ts', + 'src/vs/workbench/contrib/testing/common/testResultService.ts', + 'src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts', + 'src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts', + 'src/vs/workbench/contrib/themes/browser/themes.contribution.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts', + 'src/vs/workbench/services/environment/electron-browser/environmentService.ts', + 'src/vs/workbench/services/keybinding/common/keybindingIO.ts', + 'src/vs/workbench/services/preferences/common/preferencesValidation.ts', + 'src/vs/workbench/services/remote/common/tunnelModel.ts', + 'src/vs/workbench/services/search/common/textSearchManager.ts', + 'src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts', + 'src/vs/workbench/services/telemetry/test/node/commonProperties.test.ts', + 'src/vs/workbench/test/browser/workbenchTestServices.ts', + 'test/automation/src/playwrightDriver.ts', + '.eslint-plugin-local/**/*', + ], + plugins: { + 'local': pluginLocal, + }, + rules: { + 'local/code-no-in-operator': 'warn', + } + }, // vscode TS: strict no explicit `any` { files: [ diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index aae729560f8..fd64d65fcbc 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -1232,7 +1232,7 @@ export class AmbiguousCharacters { const data = this.ambiguousCharacterData.value; let filteredLocales = locales.filter( - (l) => !l.startsWith('_') && l in data + (l) => !l.startsWith('_') && Object.hasOwn(data, l) ); if (filteredLocales.length === 0) { filteredLocales = ['_default']; diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index cef83d93e3a..d0452557104 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -351,3 +351,36 @@ export type DeepPartial = { * Represents a type that is a partial version of a given type `T`, except a subset. */ export type PartialExcept = Partial> & Pick; + + +type KeysOfUnionType = T extends T ? keyof T : never; +type FilterType = T extends TTest ? T : never; +type MakeOptionalAndBool = { [K in keyof T]?: boolean }; + +/** + * Type guard that checks if an object has specific keys and narrows the type accordingly. + * + * @param x - The object to check + * @param key - An object with boolean values indicating which keys to check for + * @returns true if all specified keys exist in the object, false otherwise + * + * @example + * ```typescript + * type A = { a: string }; + * type B = { b: number }; + * const obj: A | B = getObject(); + * + * if (hasKey(obj, { a: true })) { + * // obj is now narrowed to type A + * console.log(obj.a); + * } + * ``` + */ +export function hasKey(x: T, key: TKeys & MakeOptionalAndBool): x is FilterType & keyof TKeys]: unknown }> { + for (const k in key) { + if (!(k in x)) { + return false; + } + } + return true; +} diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 50139125802..d940031606a 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -905,4 +905,91 @@ suite('Types', () => { assert.throws(() => types.validateConstraints(['2'], [types.isNumber])); assert.throws(() => types.validateConstraints([1, 'test', true], [Number, String, Number])); }); + + suite('hasKey', () => { + test('should return true when object has specified key', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { a: 'test' }; + + assert(types.hasKey(obj, { a: true })); + // After this check, TypeScript knows obj is type A + assert.strictEqual(obj.a, 'test'); + }); + + test('should return false when object does not have specified key', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { b: 42 }; + + assert(!types.hasKey(obj, { a: true })); + }); + + test('should work with multiple keys', () => { + type A = { a: string; b: number }; + type B = { c: boolean }; + const obj: A | B = { a: 'test', b: 42 }; + + assert(types.hasKey(obj, { a: true, b: true })); + // After this check, TypeScript knows obj is type A + assert.strictEqual(obj.a, 'test'); + assert.strictEqual(obj.b, 42); + }); + + test('should return false if any key is missing', () => { + type A = { a: string; b: number }; + type B = { a: string }; + const obj: A | B = { a: 'test' }; + + assert(!types.hasKey(obj, { a: true, b: true })); + }); + + test('should work with empty key object', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { a: 'test' }; + + // Empty key object should return true (all zero keys exist) + assert(types.hasKey(obj, {})); + }); + + test('should work with complex union types', () => { + type TypeA = { kind: 'a'; value: string }; + type TypeB = { kind: 'b'; count: number }; + type TypeC = { kind: 'c'; items: string[] }; + + const objA: TypeA | TypeB | TypeC = { kind: 'a', value: 'hello' }; + const objB: TypeA | TypeB | TypeC = { kind: 'b', count: 5 }; + + assert(types.hasKey(objA, { value: true })); + assert(!types.hasKey(objA, { count: true })); + // assert(!types.hasKey(objA, { items: true })); + + assert(!types.hasKey(objB, { value: true })); + // assert(types.hasKey(objB, { count: true })); + // assert(!types.hasKey(objB, { items: true })); + }); + + test('should handle objects with optional properties', () => { + type A = { a: string; b?: number }; + type B = { c: boolean }; + const obj1: A | B = { a: 'test', b: 42 }; + const obj2: A | B = { a: 'test' }; + + assert(types.hasKey(obj1, { a: true })); + assert(types.hasKey(obj1, { b: true })); + + assert(types.hasKey(obj2, { a: true })); + assert(!types.hasKey(obj2, { b: true })); + }); + + test('should work with nested objects', () => { + type A = { data: { nested: string } }; + type B = { value: number }; + const obj: A | B = { data: { nested: 'test' } }; + + assert(types.hasKey(obj, { data: true })); + assert(!types.hasKey(obj, { value: true })); + }); + }); }); From a2cf16dfac7e3cb1a7fc0e66bc13f693b9d565f4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:52:20 -0700 Subject: [PATCH 1792/4355] Don't include chat sessions as chat context options Fixes #273436 Follow up on #270660 to use single function to see if we --- .../browser/actions/chatContextActions.ts | 7 ++--- .../browser/contrib/chatInputCompletions.ts | 5 +-- .../contrib/chat/common/constants.ts | 31 ++++++++++++++++++- .../search/browser/searchChatContext.ts | 5 +-- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 1bc9a6fa925..f66164bd388 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -44,7 +44,7 @@ import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../searc import { SearchContext } from '../../../search/common/constants.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatRequestVariableEntry, OmittedState } from '../../common/chatVariableEntries.js'; -import { ChatAgentLocation } from '../../common/constants.js'; +import { isSupportedChatFileScheme, ChatAgentLocation } from '../../common/constants.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; import { IChatContextPickerItem, IChatContextPickService, IChatContextValueItem, isChatContextPickerPickItem } from '../chatContextPickService.js'; import { isQuickChat } from '../chatWidget.js'; @@ -52,9 +52,6 @@ import { resizeImage } from '../imageUtils.js'; import { registerPromptActions } from '../promptSyntax/promptFileActions.js'; import { CHAT_CATEGORY } from './chatActions.js'; -// Schemes that should not be available for chat context attach -const UNSUPPORTED_CONTEXT_SCHEMES = new Set(['webview-panel', 'walkThrough', 'vscode-settings']); - export function registerChatContextActions() { registerAction2(AttachContextAction); registerAction2(AttachFileToChatAction); @@ -465,7 +462,7 @@ export class AttachContextAction extends Action2 { const providerOptions: AnythingQuickAccessProviderRunOptions = { filter: (pick) => { if (isIQuickPickItemWithResource(pick) && pick.resource) { - return !UNSUPPORTED_CONTEXT_SCHEMES.has(pick.resource.scheme); + return instantiationService.invokeFunction(accessor => isSupportedChatFileScheme(accessor, pick.resource!.scheme)); } return true; }, diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 0364d20f09c..c1e1befadbb 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -52,7 +52,7 @@ import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashP import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IChatRequestVariableEntry } from '../../common/chatVariableEntries.js'; import { IDynamicVariable } from '../../common/chatVariables.js'; -import { ChatAgentLocation, ChatModeKind, ChatUnsupportedFileSchemes } from '../../common/constants.js'; +import { ChatAgentLocation, ChatModeKind, isSupportedChatFileScheme } from '../../common/constants.js'; import { ToolSet } from '../../common/languageModelToolsService.js'; import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js'; import { ChatSubmitAction } from '../actions/chatExecuteActions.js'; @@ -791,6 +791,7 @@ class BuiltinDynamicCompletions extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IChatAgentService private readonly chatAgentService: IChatAgentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -972,7 +973,7 @@ class BuiltinDynamicCompletions extends Disposable { // HISTORY // always take the last N items for (const [i, item] of this.historyService.getHistory().entries()) { - if (!item.resource || seen.has(item.resource) || ChatUnsupportedFileSchemes.has(item.resource.scheme)) { + if (!item.resource || seen.has(item.resource) || !this.instantiationService.invokeFunction(accessor => isSupportedChatFileScheme(accessor, item.resource!.scheme))) { // ignore editors without a resource continue; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 325ea688e09..622818e9840 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from '../../../../base/common/network.js'; +import { IChatSessionsService } from './chatSessionsService.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; export enum ChatConfiguration { AgentEnabled = 'chat.agent.enabled', @@ -87,7 +89,34 @@ export namespace ChatAgentLocation { } } -export const ChatUnsupportedFileSchemes = new Set([Schemas.vscodeChatEditor, Schemas.walkThrough, Schemas.vscodeChatSession, 'ccreq']); +/** + * List of file schemes that are always unsupported for use in chat + */ +const chatAlwaysUnsupportedFileSchemes = new Set([ + Schemas.vscodeChatEditor, + Schemas.walkThrough, + Schemas.vscodeChatSession, + Schemas.vscodeSettings, + Schemas.webviewPanel, + 'ccreq', +]); + +export function isSupportedChatFileScheme(accessor: ServicesAccessor, scheme: string): boolean { + const chatService = accessor.get(IChatSessionsService); + + // Exclude schemes we always know are bad + if (chatAlwaysUnsupportedFileSchemes.has(scheme)) { + return false; + } + + // Plus any schemes used by content providers + if (chatService.getContentProviderSchemes().includes(scheme)) { + return false; + } + + // Everything else is supported + return true; +} export const AGENT_SESSIONS_VIEWLET_ID = 'workbench.view.chat.sessions'; // TODO@bpasero clear once settled diff --git a/src/vs/workbench/contrib/search/browser/searchChatContext.ts b/src/vs/workbench/contrib/search/browser/searchChatContext.ts index 7b8c6245d85..ce0f46ef2d0 100644 --- a/src/vs/workbench/contrib/search/browser/searchChatContext.ts +++ b/src/vs/workbench/contrib/search/browser/searchChatContext.ts @@ -33,7 +33,7 @@ import * as glob from '../../../../base/common/glob.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { SymbolsQuickAccessProvider } from './symbolsQuickAccess.js'; import { SymbolKinds } from '../../../../editor/common/languages.js'; -import { ChatUnsupportedFileSchemes } from '../../chat/common/constants.js'; +import { isSupportedChatFileScheme } from '../../chat/common/constants.js'; import { IChatWidget } from '../../chat/browser/chat.js'; export class SearchChatContextContribution extends Disposable implements IWorkbenchContribution { @@ -160,6 +160,7 @@ class FilesAndFoldersPickerPick implements IChatContextPickerItem { @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IFileService private readonly _fileService: IFileService, @IHistoryService private readonly _historyService: IHistoryService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } asPicker() { @@ -173,7 +174,7 @@ class FilesAndFoldersPickerPick implements IChatContextPickerItem { const defaultItems: IChatContextPickerPickItem[] = []; (await getTopLevelFolders(workspaces, this._fileService)).forEach(uri => defaultItems.push(this._createPickItem(uri, FileKind.FOLDER))); this._historyService.getHistory() - .filter(a => a.resource && !ChatUnsupportedFileSchemes.has(a.resource.scheme)) + .filter(a => a.resource && this._instantiationService.invokeFunction(accessor => isSupportedChatFileScheme(accessor, a.resource!.scheme))) .slice(0, 30) .forEach(uri => defaultItems.push(this._createPickItem(uri.resource!, FileKind.FILE))); From 7fa5b07caeb4513bebc6e267ccaf2d58d7a8d730 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:24:54 -0700 Subject: [PATCH 1793/4355] Adopt DisposableMap to track contribution disposables --- .../chat/browser/chatSessions.contribution.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 213864520e2..21c5afe36ab 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -6,7 +6,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; import * as resources from '../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -228,11 +228,13 @@ class ContributedChatSessionData implements IDisposable { export class ChatSessionsService extends Disposable implements IChatSessionsService { readonly _serviceBrand: undefined; - private readonly _itemsProviders: Map = new Map(); + private readonly _itemsProviders: Map = new Map(); + private readonly _contributions: Map = new Map(); + private readonly _contributionDisposables = this._register(new DisposableMap()); + private readonly _contentProviders: Map = new Map(); private readonly _alternativeIdMap: Map = new Map(); - private readonly _disposableStores: Map = new Map(); private readonly _contextKeys = new Set(); private readonly _onDidChangeItemsProviders = this._register(new Emitter()); @@ -412,11 +414,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._sessionTypeWelcomeMessages.delete(contribution.type); this._sessionTypeWelcomeTips.delete(contribution.type); this._sessionTypeInputPlaceholders.delete(contribution.type); - const store = this._disposableStores.get(contribution.type); - if (store) { - store.dispose(); - this._disposableStores.delete(contribution.type); - } + this._contributionDisposables.deleteAndDispose(contribution.type); } }; } @@ -540,15 +538,12 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private _evaluateAvailability(): void { let hasChanges = false; for (const { contribution, extension } of this._contributions.values()) { - const isCurrentlyRegistered = this._disposableStores.has(contribution.type); + const isCurrentlyRegistered = this._contributionDisposables.has(contribution.type); const shouldBeRegistered = this._isContributionAvailable(contribution); if (isCurrentlyRegistered && !shouldBeRegistered) { // Disable the contribution by disposing its disposable store - const store = this._disposableStores.get(contribution.type); - if (store) { - store.dispose(); - this._disposableStores.delete(contribution.type); - } + this._contributionDisposables.deleteAndDispose(contribution.type); + // Also dispose any cached sessions for this contribution this._disposeSessionsForContribution(contribution.type); hasChanges = true; @@ -571,7 +566,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ private _enableContribution(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): void { const disposableStore = new DisposableStore(); - this._disposableStores.set(contribution.type, disposableStore); + this._contributionDisposables.set(contribution.type, disposableStore); disposableStore.add(this._registerAgent(contribution, ext)); disposableStore.add(this._registerCommands(contribution)); From b520a7bd49cb41930a79c152f4f8b39a9357594b Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:33:53 -0700 Subject: [PATCH 1794/4355] Use `isEqual` to compare uris We should always prefer using this over comparing strings Also switches to use find --- .../api/browser/mainThreadChatSessions.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index d0532d2346c..c6e878c33f8 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -11,20 +11,21 @@ import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../. import { ResourceMap } from '../../../base/common/map.js'; import { revive } from '../../../base/common/marshalling.js'; import { autorun, IObservable, observableValue } from '../../../base/common/observable.js'; +import { isEqual } from '../../../base/common/resources.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; import { ILogService } from '../../../platform/log/common/log.js'; +import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; import { ChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; -import { IEditorGroup, IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; +import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; +import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { ExtHostChatSessionsShape, ExtHostContext, IChatProgressDto, IChatSessionHistoryItemDto, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; -import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; export class ObservableChatSession extends Disposable implements ChatSession { @@ -387,16 +388,9 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat const contribution = this._chatSessionsService.getAllChatSessionContributions().find(c => c.type === chatSessionType); // Find the group containing the original editor - let originalGroup: IEditorGroup | undefined; - for (const group of this.editorGroupService.groups) { - if (group.editors.some(editor => editor.resource?.toString() === originalResource.toString())) { - originalGroup = group; - break; - } - } - if (!originalGroup) { - originalGroup = this.editorGroupService.activeGroup; - } + const originalGroup = + this.editorGroupService.groups.find(group => group.editors.some(editor => isEqual(editor.resource, originalResource))) + ?? this.editorGroupService.activeGroup; const options: IChatEditorOptions = { title: { From 6740f01ea86ef42e65a1f6ceb9963af68cba670a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:59:55 -0700 Subject: [PATCH 1795/4355] Small naming and readonly cleanup --- .../api/browser/mainThreadChatSessions.ts | 8 ++++---- .../test/browser/mainThreadChatSessions.test.ts | 10 +++++----- .../chat/browser/chatSessions.contribution.ts | 6 +++--- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatSessionsService.ts | 15 +++++++++------ .../chat/test/common/mockChatSessionsService.ts | 4 ++-- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index c6e878c33f8..e11a72866b5 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -19,7 +19,7 @@ import { ILogService } from '../../../platform/log/common/log.js'; import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; -import { ChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; +import { IChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; @@ -27,7 +27,7 @@ import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; import { ExtHostChatSessionsShape, ExtHostContext, IChatProgressDto, IChatSessionHistoryItemDto, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; -export class ObservableChatSession extends Disposable implements ChatSession { +export class ObservableChatSession extends Disposable implements IChatSession { readonly sessionResource: URI; readonly providerHandle: number; @@ -401,7 +401,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat if (originalEditor) { // Prefetch the chat session content to make the subsequent editor swap quick - this._chatSessionsService.getChatSessionContent( + this._chatSessionsService.getOrCreateChatSession( URI.revive(modifiedResource), CancellationToken.None, ).then(() => { @@ -455,7 +455,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } } - private async _provideChatSessionContent(providerHandle: number, sessionResource: URI, token: CancellationToken): Promise { + private async _provideChatSessionContent(providerHandle: number, sessionResource: URI, token: CancellationToken): Promise { let session = this._activeSessions.get(sessionResource); if (!session) { diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 21e33f95a48..4f4c42818f8 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -437,11 +437,11 @@ suite('MainThreadChatSessions', function () { const resource = URI.parse(`${sessionScheme}:/test-session`); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); - const session1 = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None); + const session1 = await chatSessionsService.getOrCreateChatSession(resource, CancellationToken.None); assert.ok(session1); - const session2 = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None); + const session2 = await chatSessionsService.getOrCreateChatSession(resource, CancellationToken.None); assert.strictEqual(session1, session2); assert.ok((proxy.$provideChatSessionContent as sinon.SinonStub).calledOnce); @@ -463,7 +463,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/test-session`); - const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getOrCreateChatSession(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); @@ -488,7 +488,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/test-session`); - const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getOrCreateChatSession(resource, CancellationToken.None) as ObservableChatSession; const progressDto: IChatProgressDto = { kind: 'progressMessage', content: { value: 'Test', isTrusted: false } }; await mainThread.$handleProgressChunk(1, resource, 'req1', [progressDto]); @@ -518,7 +518,7 @@ suite('MainThreadChatSessions', function () { (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); const resource = URI.parse(`${sessionScheme}:/multi-turn-session`); - const session = await chatSessionsService.getChatSessionContent(resource, CancellationToken.None) as ObservableChatSession; + const session = await chatSessionsService.getOrCreateChatSession(resource, CancellationToken.None) as ObservableChatSession; // Verify the session loaded correctly assert.ok(session); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 8ce2d9371c1..e5ba0d19332 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -29,7 +29,7 @@ import { ExtensionsRegistry } from '../../../services/extensions/common/extensio import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { ChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; +import { IChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; @@ -201,7 +201,7 @@ class ContributedChatSessionData implements IDisposable { } constructor( - readonly session: ChatSession, + readonly session: IChatSession, readonly chatSessionType: string, readonly resource: URI, readonly options: Record | undefined, @@ -787,7 +787,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return chatSessionItem; } - public async getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + public async getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise { if (!(await this.canResolveChatSession(sessionResource))) { throw Error(`Can not find provider for ${sessionResource}`); } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 232ea297b76..4c7bef18bdb 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -458,7 +458,7 @@ export class ChatService extends Disposable implements IChatService { return existing.model; } - const content = await this.chatSessionService.getChatSessionContent(chatSessionResource, CancellationToken.None); + const content = await this.chatSessionService.getOrCreateChatSession(chatSessionResource, CancellationToken.None); const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 687dcb05a4f..51c94e9012c 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -88,15 +88,18 @@ export type IChatSessionHistoryItem = { participant: string; }; -export interface ChatSession extends IDisposable { - readonly sessionResource: URI; +export interface IChatSession extends IDisposable { readonly onWillDispose: Event; - history: Array; + + readonly sessionResource: URI; + + readonly history: readonly IChatSessionHistoryItem[]; + /** * Session options as key-value pairs. Keys correspond to option group IDs (e.g., 'models', 'subagents') * and values are the selected option item IDs. */ - options?: Record; + readonly options?: Record; readonly progressObs?: IObservable; readonly isCompleteObs?: IObservable; @@ -121,7 +124,7 @@ export interface IChatSessionItemProvider { } export interface IChatSessionContentProvider { - provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; + provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; } export type SessionOptionsChangedCallback = (sessionResource: URI, updates: ReadonlyArray<{ @@ -174,7 +177,7 @@ export interface IChatSessionsService { registerChatSessionContentProvider(scheme: string, provider: IChatSessionContentProvider): IDisposable; canResolveChatSession(sessionResource: URI): Promise; - getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise; + getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise; hasAnySessionOptions(sessionResource: URI): boolean; getSessionOption(sessionResource: URI, optionId: string): string | undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index 1efcc595905..e110f4bde9f 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -11,7 +11,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { IEditableData } from '../../../../common/views.js'; import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/chatAgents.js'; -import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../../common/chatSessionsService.js'; +import { IChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../../common/chatSessionsService.js'; export class MockChatSessionsService implements IChatSessionsService { _serviceBrand: undefined; @@ -142,7 +142,7 @@ export class MockChatSessionsService implements IChatSessionsService { return this.contentProviders.has(chatSessionType); } - async getChatSessionContent(sessionResource: URI, token: CancellationToken): Promise { + async getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise { const provider = this.contentProviders.get(sessionResource.scheme); if (!provider) { throw new Error(`No content provider for ${sessionResource.scheme}`); From 1de515c25fc7bdd300bfc70fb1afe46af5ff5b6b Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:18:57 -0700 Subject: [PATCH 1796/4355] Allow relative media paths in markdown strings with base urls Fixes #273521 --- src/vs/base/browser/markdownRenderer.ts | 1 + src/vs/base/test/browser/markdownRenderer.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 90a597ff5b7..cd244b65259 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -568,6 +568,7 @@ function getDomSanitizerConfig(mdStrConfig: MdStrConfig, options: MarkdownSaniti Schemas.vscodeRemoteResource, ] }, + allowRelativeMediaPaths: !!mdStrConfig.baseUri, replaceWithPlaintext: options.replaceWithPlaintext, }; } diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 072d1871b29..693a957f29a 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -267,14 +267,14 @@ suite('MarkdownRenderer', () => { }); test('Should support relative links if baseurl is set', () => { - const md = new MarkdownString(`[text](./foo) bar`, { + const md = new MarkdownString(`[text](./foo) bar `, { isTrusted: true, supportHtml: true, }); md.baseUri = URI.parse('https://example.com/path/'); const result = store.add(renderMarkdown(md)).element; - assert.strictEqual(result.innerHTML, `

    text bar

    `); + assert.strictEqual(result.innerHTML, `

    text bar

    `); }); suite('PlaintextMarkdownRender', () => { From 703665b473768c3c952930d7d9d51e8003b24847 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:20:37 -0700 Subject: [PATCH 1797/4355] Simplify chat session storage We can use a simple resource map instead now --- .../contrib/chat/common/chatServiceImpl.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 4c7bef18bdb..3347a97e93c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -72,7 +72,7 @@ export class ChatService extends Disposable implements IChatService { declare _serviceBrand: undefined; private readonly _sessionModels = new ObservableMap(); - private readonly _contentProviderSessionModels = new Map>(); + private readonly _contentProviderSessionModels = new ResourceMap<{ readonly model: IChatModel; readonly disposables: DisposableStore }>(); private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; @@ -453,12 +453,12 @@ export class ChatService extends Disposable implements IChatService { async loadSessionForResource(chatSessionResource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { // TODO: Move this into a new ChatModelService - const existing = this._contentProviderSessionModels.get(chatSessionResource.scheme)?.get(chatSessionResource); + const existing = this._contentProviderSessionModels.get(chatSessionResource); if (existing) { return existing.model; } - const content = await this.chatSessionService.getOrCreateChatSession(chatSessionResource, CancellationToken.None); + const providedSession = await this.chatSessionService.getOrCreateChatSession(chatSessionResource, CancellationToken.None); const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools @@ -469,19 +469,17 @@ export class ChatService extends Disposable implements IChatService { chatSessionResource, isUntitled: chatSessionResource.path.startsWith('/untitled-') //TODO(jospicer) }); - if (!this._contentProviderSessionModels.has(chatSessionType)) { - this._contentProviderSessionModels.set(chatSessionType, new ResourceMap()); - } + const disposables = new DisposableStore(); - this._contentProviderSessionModels.get(chatSessionType)!.set(chatSessionResource, { model, disposables }); + this._contentProviderSessionModels.set(chatSessionResource, { model, disposables }); disposables.add(model.onDidDispose(() => { - this._contentProviderSessionModels?.get(chatSessionType)?.delete(chatSessionResource); - content.dispose(); + this._contentProviderSessionModels.delete(chatSessionResource); + providedSession.dispose(); })); let lastRequest: ChatRequestModel | undefined; - for (const message of content.history) { + for (const message of providedSession.history) { if (message.type === 'request') { if (lastRequest) { model.completeResponse(lastRequest); @@ -522,14 +520,14 @@ export class ChatService extends Disposable implements IChatService { } } - if (content.progressObs && lastRequest && content.interruptActiveResponseCallback) { + if (providedSession.progressObs && lastRequest && providedSession.interruptActiveResponseCallback) { const initialCancellationRequest = this.instantiationService.createInstance(CancellableRequest, new CancellationTokenSource(), undefined); this._pendingRequests.set(model.sessionId, initialCancellationRequest); const cancellationListener = new MutableDisposable(); const createCancellationListener = (token: CancellationToken) => { return token.onCancellationRequested(() => { - content.interruptActiveResponseCallback?.().then(userConfirmedInterruption => { + providedSession.interruptActiveResponseCallback?.().then(userConfirmedInterruption => { if (!userConfirmedInterruption) { // User cancelled the interruption const newCancellationRequest = this.instantiationService.createInstance(CancellableRequest, new CancellationTokenSource(), undefined); @@ -545,8 +543,8 @@ export class ChatService extends Disposable implements IChatService { let lastProgressLength = 0; disposables.add(autorun(reader => { - const progressArray = content.progressObs?.read(reader) ?? []; - const isComplete = content.isCompleteObs?.read(reader) ?? false; + const progressArray = providedSession.progressObs?.read(reader) ?? []; + const isComplete = providedSession.isCompleteObs?.read(reader) ?? false; // Process only new progress items if (progressArray.length > lastProgressLength) { From ec0c7f0445721892c7a79d0bb71dd5e78f8c89ef Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:42:58 -0700 Subject: [PATCH 1798/4355] Reuse getSessionType --- .../workbench/contrib/chat/browser/chatEditorInput.ts | 10 +++------- .../contrib/chat/browser/chatSessions/common.ts | 8 ++------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 1fb1cf6f96c..ec54863c5bc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -229,16 +229,12 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return undefined; } - private getSessionType(): string { - if (!this.resource) { + public getSessionType(): string { + if (this.resource.scheme === Schemas.vscodeChatEditor || this.resource.scheme === Schemas.vscodeChatSession) { return 'local'; } - const { scheme } = this.resource; - if (scheme === Schemas.vscodeChatEditor || scheme === Schemas.vscodeChatSession) { - return 'local'; - } - return scheme; + return this.resource.scheme; } override async resolve(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index f0b189af949..85a74c36937 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -28,7 +28,7 @@ export type ChatSessionItemWithProvider = IChatSessionItem & { }; }; -export function isChatSession(schemes: string[], editor?: EditorInput): boolean { +export function isChatSession(schemes: readonly string[], editor?: EditorInput): boolean { if (!(editor instanceof ChatEditorInput)) { return false; } @@ -48,11 +48,7 @@ export function isChatSession(schemes: string[], editor?: EditorInput): boolean * Returns chat session type from a URI, or 'local' if not specified or cannot be determined. */ export function getChatSessionType(editor: ChatEditorInput): string { - if (editor.resource.scheme === Schemas.vscodeChatEditor || editor.resource.scheme === Schemas.vscodeChatSession) { - return 'local'; - } - - return editor.resource.scheme; + return editor.getSessionType(); } /** From 271879b06dfadd23d50e54571a645eda3b316201 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:48:14 -0700 Subject: [PATCH 1799/4355] Exclude chatEditingSession.ts from in check Looks like this snuck through somehow --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index b88d1400efd..bb16343dba1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -286,6 +286,7 @@ export default tseslint.config( 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts', 'src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts', 'src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts', 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts', 'src/vs/workbench/contrib/chat/browser/chatEditorInput.ts', 'src/vs/workbench/contrib/chat/browser/chatFollowups.ts', From 4c3d8cf64ae750c6308a9a3485fb7d08c14cd2c9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 20:37:40 +0100 Subject: [PATCH 1800/4355] Git - adopt the new icons for the statusbar command (#273789) * Git - adopt the new icons * Attempt to fix tests --- extensions/git/src/statusbar.ts | 13 ++++++++++++- test/automation/src/statusbar.ts | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index e58096442f2..6a06209eb41 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -71,7 +71,18 @@ class CheckoutStatusBar { // Branch if (this.repository.HEAD.type === RefType.Head && this.repository.HEAD.name) { - return this.repository.isBranchProtected() ? '$(lock)' : '$(git-branch)'; + switch (true) { + case this.repository.isBranchProtected(): + return '$(lock)'; + case this.repository.mergeInProgress || !!this.repository.rebaseCommit: + return '$(git-branch-conflicts)'; + case this.repository.indexGroup.resourceStates.length > 0: + return '$(git-branch-staged-changes)'; + case this.repository.workingTreeGroup.resourceStates.length + this.repository.untrackedGroup.resourceStates.length > 0: + return '$(git-branch-changes)'; + default: + return '$(git-branch)'; + } } // Tag diff --git a/test/automation/src/statusbar.ts b/test/automation/src/statusbar.ts index 423a7585c04..4b934aa0949 100644 --- a/test/automation/src/statusbar.ts +++ b/test/automation/src/statusbar.ts @@ -41,9 +41,9 @@ export class StatusBar { private getSelector(element: StatusBarElement): string { switch (element) { case StatusBarElement.BRANCH_STATUS: - return `.statusbar-item[id^="status.scm."] .codicon.codicon-git-branch`; + return `.statusbar-item[id="status.scm.0"] .codicon`; case StatusBarElement.SYNC_STATUS: - return `.statusbar-item[id^="status.scm."] .codicon.codicon-sync`; + return `.statusbar-item[id="status.scm.1"] .codicon.codicon-sync`; case StatusBarElement.PROBLEMS_STATUS: return `.statusbar-item[id="status.problems"]`; case StatusBarElement.SELECTION_STATUS: From f9adc943e4e269453a1b34d723ed6f7b18e65384 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Oct 2025 13:02:22 -0700 Subject: [PATCH 1801/4355] fix linting failure (#273811) --- .../contrib/chat/browser/chatEditing/chatEditingSession.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 46d1c6d0f29..ef7badf9af3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -13,7 +13,7 @@ import { Disposable, DisposableStore, dispose } from '../../../../../base/common import { ResourceMap } from '../../../../../base/common/map.js'; import { autorun, IObservable, IReader, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { isEqual } from '../../../../../base/common/resources.js'; -import { Mutable } from '../../../../../base/common/types.js'; +import { hasKey, Mutable } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; import { Range } from '../../../../../editor/common/core/range.js'; @@ -456,7 +456,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio private async _recordEditOperations(entry: AbstractChatEditingModifiedFileEntry, resource: URI, edits: (TextEdit | ICellEditOperation)[], responseModel: IChatResponseModel): Promise { // Determine if these are text edits or notebook edits - const isNotebookEdits = edits.length > 0 && 'cells' in edits[0]; + const isNotebookEdits = edits.length > 0 && hasKey(edits[0], { cells: true }); if (isNotebookEdits) { // Record notebook edit operation From 6e17002f7cf50c2dca3951e97e393dca829e8798 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 28 Oct 2025 21:23:55 +0100 Subject: [PATCH 1802/4355] Testing - Uncompleted result should be marked as skipped (#273742) * Testing - Uncompleted result should be marked as skipped Closes https://github.com/microsoft/vscode/issues/272888 * update test --- src/vs/workbench/contrib/testing/common/testResult.ts | 2 +- .../contrib/testing/test/common/testResultService.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/testing/common/testResult.ts b/src/vs/workbench/contrib/testing/common/testResult.ts index ed3db492667..80da653ea77 100644 --- a/src/vs/workbench/contrib/testing/common/testResult.ts +++ b/src/vs/workbench/contrib/testing/common/testResult.ts @@ -465,7 +465,7 @@ export class LiveTestResult extends Disposable implements ITestResult { task.output.end(); this.setAllToState( - TestResultState.Unset, + TestResultState.Skipped, taskId, t => t.state === TestResultState.Queued || t.state === TestResultState.Running, ); diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 5ade88fce1e..272d02a8a83 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -194,11 +194,11 @@ suite('Workbench - Test Results Service', () => { r.markComplete(); const c = makeEmptyCounts(); - c[TestResultState.Unset] = 3; + c[TestResultState.Skipped] = 3; c[TestResultState.Passed] = 1; assert.deepStrictEqual(r.counts, c); - assert.deepStrictEqual(r.getStateById(tests.root.id)?.ownComputedState, TestResultState.Unset); + assert.deepStrictEqual(r.getStateById(tests.root.id)?.ownComputedState, TestResultState.Skipped); assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())?.ownComputedState, TestResultState.Passed); }); }); From fa2661b8572621092897d2f828abec9c0ad9da1f Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:59:41 -0700 Subject: [PATCH 1803/4355] Extract `localChatSessionType` constant Makes it easier to maintain and find where local sessions are used --- .../chat/browser/actions/chatActions.ts | 16 ++++----- .../browser/actions/chatSessionActions.ts | 16 ++++----- .../agentSessions/agentSessionViewModel.ts | 10 +++--- .../contrib/chat/browser/chatEditor.ts | 12 +++---- .../contrib/chat/browser/chatEditorInput.ts | 8 ++--- .../chat/browser/chatSessions.contribution.ts | 6 ++-- .../chatSessions/chatSessionTracker.ts | 14 ++++---- .../chat/browser/chatSessions/common.ts | 8 ++--- .../chatSessions/localChatSessionsProvider.ts | 12 +++---- .../chatSessions/view/chatSessionsView.ts | 8 ++--- .../chatSessions/view/sessionsTreeRenderer.ts | 34 +++++++++---------- .../chatSessions/view/sessionsViewPane.ts | 18 +++++----- .../chat/common/chatSessionsService.ts | 5 +++ .../browser/agentSessionViewModel.test.ts | 12 +++---- 14 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 2613e1849d7..0dd2be9bb88 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -17,6 +17,7 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, markAsSingleton } from '../../../../../base/common/lifecycle.js'; import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { language } from '../../../../../base/common/platform.js'; +import { basename } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; @@ -47,6 +48,7 @@ import { ToggleTitleBarConfigAction } from '../../../../browser/parts/titlebar/t import { ActiveEditorContext, IsCompactTitleBarContext } from '../../../../common/contextkeys.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { GroupDirection, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHostService } from '../../../../services/host/browser/host.js'; @@ -54,18 +56,20 @@ import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/brow import { IPreferencesService } from '../../../../services/preferences/common/preferences.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { EXTENSIONS_CATEGORY, IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; +import { SCMHistoryItemChangeRangeContentProvider, ScmHistoryItemChangeRangeUriFields } from '../../../scm/browser/scmHistoryChatContext.js'; +import { ISCMService } from '../../../scm/common/scm.js'; import { IChatAgentResult, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; -import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; +import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; -import { ChatAgentLocation, ChatConfiguration, ChatModeKind, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; +import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; import { ILanguageModelChatSelector, ILanguageModelsService } from '../../common/languageModels.js'; import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; @@ -75,10 +79,6 @@ import { ChatEditorInput, shouldShowClearEditingSessionConfirmation, showClearEd import { ChatViewPane } from '../chatViewPane.js'; import { convertBufferToScreenshotVariable } from '../contrib/screenshot.js'; import { clearChatEditor } from './chatClear.js'; -import { ISCMService } from '../../../scm/common/scm.js'; -import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; -import { basename } from '../../../../../base/common/resources.js'; -import { SCMHistoryItemChangeRangeContentProvider, ScmHistoryItemChangeRangeUriFields } from '../../../scm/browser/scmHistoryChatContext.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); @@ -1015,7 +1015,7 @@ export function registerChatActions() { // Check if there are any non-local chat session item providers registered const allProviders = chatSessionsService.getAllChatSessionItemProviders(); - const hasNonLocalProviders = allProviders.some(provider => provider.chatSessionType !== 'local'); + const hasNonLocalProviders = allProviders.some(provider => provider.chatSessionType !== localChatSessionType); if (hasNonLocalProviders) { await this.showIntegratedPicker( diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 5a3ccd1d44f..381462df4c0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from '../../../../../nls.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { IChatSessionRecommendation } from '../../../../../base/common/product.js'; import Severity from '../../../../../base/common/severity.js'; +import * as nls from '../../../../../nls.js'; import { localize } from '../../../../../nls.js'; import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; @@ -26,15 +27,14 @@ import { IWorkbenchExtensionManagementService } from '../../../../services/exten import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; -import { IChatSessionsService } from '../../common/chatSessionsService.js'; -import { ChatConfiguration, AGENT_SESSIONS_VIEWLET_ID } from '../../common/constants.js'; +import { IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; +import { AGENT_SESSIONS_VIEWLET_ID, ChatConfiguration } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; -import { CancellationToken } from '../../../../../base/common/cancellation.js'; interface IMarshalledChatSessionContext { $mid: MarshalledId.ChatSessionContext; @@ -92,7 +92,7 @@ export class RenameChatSessionAction extends Action2 { const newTitle = value.trim(); chatService.setChatSessionTitle(sessionId, newTitle); // Notify the local sessions provider that items have changed - chatSessionsService.notifySessionItemsChanged('local'); + chatSessionsService.notifySessionItemsChanged(localChatSessionType); } catch (error) { logService.error( localize('renameSession.error', "Failed to rename chat session: {0}", @@ -149,7 +149,7 @@ export class DeleteChatSessionAction extends Action2 { if (result.confirmed) { await chatService.removeHistoryEntry(sessionId); // Notify the local sessions provider that items have changed - chatSessionsService.notifySessionItemsChanged('local'); + chatSessionsService.notifySessionItemsChanged(localChatSessionType); } } catch (error) { logService.error('Failed to delete chat session', error instanceof Error ? error.message : String(error)); @@ -419,7 +419,7 @@ MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { }, group: 'inline', order: 1, - when: ChatContextKeys.sessionType.isEqualTo('local') + when: ChatContextKeys.sessionType.isEqualTo(localChatSessionType) }); // Register delete menu item - only show for non-active sessions (history items) @@ -462,7 +462,7 @@ MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, { }, group: 'navigation', order: 3, - when: ChatContextKeys.sessionType.isEqualTo('local'), + when: ChatContextKeys.sessionType.isEqualTo(localChatSessionType), }); // Register the toggle command for the ViewTitle menu diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 180a5abe5b3..50975a00ce1 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -12,7 +12,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IChatService } from '../../common/chatService.js'; -import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; //#region Interfaces, Types @@ -53,10 +53,8 @@ export interface IAgentSessionViewModel { }; } -export const LOCAL_AGENT_SESSION_TYPE = 'local'; - export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean { - return session.provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE; + return session.provider.chatSessionType === localChatSessionType; } export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel { @@ -167,14 +165,14 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions }); } - if (INCLUDE_HISTORY && provider.chatSessionType === LOCAL_AGENT_SESSION_TYPE) { + if (INCLUDE_HISTORY && provider.chatSessionType === localChatSessionType) { // TODO@bpasero this needs to come from the local provider: // - do we want to show history or not and how // - can we support all properties including `startTime` properly for (const history of await this.chatService.getHistory()) { newSessions.push({ id: history.sessionId, - resource: ChatSessionUri.forSession(LOCAL_AGENT_SESSION_TYPE, history.sessionId), + resource: ChatSessionUri.forSession(localChatSessionType, history.sessionId), label: history.title, provider: provider, timing: { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 576b2fabec1..6f4e65f355d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; -import * as nls from '../../../../nls.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { raceCancellationError } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import * as nls from '../../../../nls.js'; import { IContextKeyService, IScopedContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IEditorOptions } from '../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -26,7 +26,7 @@ import { IEditorGroup } from '../../../services/editor/common/editorGroupsServic import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatModel, IExportableChatData, ISerializableChatData } from '../common/chatModel.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; -import { IChatSessionsService } from '../common/chatSessionsService.js'; +import { IChatSessionsService, localChatSessionType } from '../common/chatSessionsService.js'; import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { clearChatEditor } from './actions/chatClear.js'; import { ChatEditorInput } from './chatEditorInput.js'; @@ -183,7 +183,7 @@ export class ChatEditor extends EditorPane { // Show loading indicator early for non-local sessions to prevent layout shifts let isContributedChatSession = false; const chatSessionType = getChatSessionType(input); - if (chatSessionType !== 'local') { + if (chatSessionType !== localChatSessionType) { const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); this.showLoadingInChatWidget(loadingMessage); } @@ -198,7 +198,7 @@ export class ChatEditor extends EditorPane { throw new Error('ChatEditor lifecycle issue: no editor widget'); } - if (chatSessionType !== 'local') { + if (chatSessionType !== localChatSessionType) { try { await raceCancellationError(this.chatSessionsService.canResolveChatSession(input.resource), token); const contributions = this.chatSessionsService.getAllChatSessionContributions(); @@ -225,7 +225,7 @@ export class ChatEditor extends EditorPane { } // Hide loading state before updating model - if (chatSessionType !== 'local') { + if (chatSessionType !== localChatSessionType) { this.hideLoadingInChatWidget(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index ec54863c5bc..7da6d2012ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -21,7 +21,7 @@ import { EditorInput, IEditorCloseHandler } from '../../../common/editor/editorI import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; -import { IChatSessionsService } from '../common/chatSessionsService.js'; +import { IChatSessionsService, localChatSessionType } from '../common/chatSessionsService.js'; import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; import type { IChatEditorOptions } from './chatEditor.js'; @@ -197,7 +197,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } private getSessionTypeDisplayName(sessionType: string): string | undefined { - if (sessionType === 'local') { + if (sessionType === localChatSessionType) { return; } const contributions = this.chatSessionsService.getAllChatSessionContributions(); @@ -219,7 +219,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler private resolveIcon(): ThemeIcon | URI | undefined { // TODO@osortega,@rebornix double check: Chat Session Item icon is reserved for chat session list and deprecated for chat session status. thus here we use session type icon. We may want to show status for the Editor Title. const sessionType = this.getSessionType(); - if (sessionType !== 'local') { + if (sessionType !== localChatSessionType) { const typeIcon = this.chatSessionsService.getIconForSessionType(sessionType); if (typeIcon) { return typeIcon; @@ -231,7 +231,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler public getSessionType(): string { if (this.resource.scheme === Schemas.vscodeChatEditor || this.resource.scheme === Schemas.vscodeChatSession) { - return 'local'; + return localChatSessionType; } return this.resource.scheme; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index d37ad4cfc63..d4957cf4570 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -29,7 +29,7 @@ import { ExtensionsRegistry } from '../../../services/extensions/common/extensio import { ChatEditorInput } from '../browser/chatEditorInput.js'; import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatSession, ChatSessionStatus, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionProviderOptionGroup, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType, SessionOptionsChangedCallback } from '../common/chatSessionsService.js'; import { AGENT_SESSIONS_VIEWLET_ID, ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { CHAT_CATEGORY } from './actions/chatActions.js'; import { IChatEditorOptions } from './chatEditor.js'; @@ -307,7 +307,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ public reportInProgress(chatSessionType: string, count: number): void { let displayName: string | undefined; - if (chatSessionType === 'local') { + if (chatSessionType === localChatSessionType) { displayName = 'Local Chat Agent'; } else { displayName = this._contributions.get(chatSessionType)?.contribution.displayName; @@ -832,7 +832,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ this._editableSessions.set(sessionResource, data); } // Trigger refresh of the session views that might need to update their rendering - this._onDidChangeSessionItems.fire('local'); + this._onDidChangeSessionItems.fire(localChatSessionType); } public getEditableData(sessionResource: URI): IEditableData | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index 6b9e3e9866d..4c25e20efbb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from '../../../../../base/common/lifecycle.js'; import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; import { GroupModelChangeKind } from '../../../../common/editor.js'; -import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; -import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../common/chatSessionsService.js'; -import { IChatService } from '../../common/chatService.js'; +import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IChatModel } from '../../common/chatModel.js'; +import { IChatService } from '../../common/chatService.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; +import { ChatEditorInput } from '../chatEditorInput.js'; +import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; export class ChatSessionTracker extends Disposable { private readonly _onDidChangeEditors = this._register(new Emitter<{ sessionType: string; kind: GroupModelChangeKind }>()); @@ -71,7 +71,7 @@ export class ChatSessionTracker extends Disposable { } async getHybridSessionsForProvider(provider: IChatSessionItemProvider): Promise { - if (provider.chatSessionType === 'local') { + if (provider.chatSessionType === localChatSessionType) { return []; // Local provider doesn't need hybrid sessions } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 85a74c36937..0dcf73a5a2b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -10,7 +10,7 @@ import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItem, IChatSessionItemProvider } from '../../common/chatSessionsService.js'; +import { IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js'; import { IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; @@ -45,7 +45,7 @@ export function isChatSession(schemes: readonly string[], editor?: EditorInput): } /** - * Returns chat session type from a URI, or 'local' if not specified or cannot be determined. + * Returns chat session type from a URI, or {@linkcode localChatSessionType} if not specified or cannot be determined. */ export function getChatSessionType(editor: ChatEditorInput): string { return editor.getSessionType(); @@ -70,7 +70,7 @@ export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, } export function isLocalChatSessionItem(item: ChatSessionItemWithProvider): boolean { - return item.provider.chatSessionType === 'local'; + return item.provider.chatSessionType === localChatSessionType; } // Helper function to update relative time for chat sessions (similar to timeline) @@ -159,7 +159,7 @@ export function getSessionItemContextOverlay( // Mark active sessions - check if session is currently open in editor or widget let isActiveSession = false; - if (!session.isHistory && provider?.chatSessionType === 'local') { + if (!session.isHistory && provider?.chatSessionType === localChatSessionType) { // Local non-history sessions are always active isActiveSession = true; } else if (session.isHistory && chatWidgetService && chatService && editorGroupsService) { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index c7979b74897..67536f29acb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -12,20 +12,20 @@ import { URI } from '../../../../../base/common/uri.js'; import * as nls from '../../../../../nls.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; -import { IEditorGroupsService, IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; -import { IChatSessionItemProvider, IChatSessionsService, ChatSessionStatus, IChatSessionItem } from '../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { ChatAgentLocation } from '../../common/constants.js'; -import { IChatWidgetService, IChatWidget } from '../chat.js'; +import { IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { isChatSession, getChatSessionType, ChatSessionItemWithProvider } from './common.js'; +import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { static readonly ID = 'workbench.contrib.localChatSessionsProvider'; static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot'; static readonly HISTORY_NODE_ID = 'show-history'; - readonly chatSessionType = 'local'; + readonly chatSessionType = localChatSessionType; private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; @@ -159,7 +159,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio } const sessionType = getChatSessionType(editor); - return sessionType === 'local'; + return sessionType === localChatSessionType; } private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts index 3e0dfd3e909..a906ee26d3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/chatSessionsView.ts @@ -25,7 +25,7 @@ import { Extensions, IViewContainersRegistry, IViewDescriptor, IViewDescriptorSe import { IExtensionService } from '../../../../../services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js'; import { ChatContextKeyExprs } from '../../../common/chatContextKeys.js'; -import { IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../../../common/chatSessionsService.js'; +import { IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; import { AGENT_SESSIONS_VIEWLET_ID } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; @@ -119,9 +119,9 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon const viewDescriptorsToRegister: IViewDescriptor[] = []; // Separate providers by type and prepare display names with order - const localProvider = providers.find(p => p.chatSessionType === 'local'); + const localProvider = providers.find(p => p.chatSessionType === localChatSessionType); const historyProvider = providers.find(p => p.chatSessionType === 'history'); - const otherProviders = providers.filter(p => p.chatSessionType !== 'local' && p.chatSessionType !== 'history'); + const otherProviders = providers.filter(p => p.chatSessionType !== localChatSessionType && p.chatSessionType !== 'history'); // Sort other providers by order, then alphabetically by display name const providersWithDisplayNames = otherProviders.map(provider => { @@ -194,7 +194,7 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon viewDescriptorsToRegister.push(viewDescriptor); this.registeredViewDescriptors.set(provider.chatSessionType, viewDescriptor); - if (provider.chatSessionType === 'local') { + if (provider.chatSessionType === localChatSessionType) { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); this._register(viewsRegistry.registerViewWelcomeContent(viewDescriptor.id, { content: nls.localize('chatSessions.noResults', "No local chat agent sessions\n[Start an Agent Session](command:{0})", ACTION_ID_OPEN_CHAT), diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index fb21badeee9..3cc85c92567 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -7,19 +7,24 @@ import * as DOM from '../../../../../../base/browser/dom.js'; import { $, append } from '../../../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../../../base/browser/keyboardEvent.js'; import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; +import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; +import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; +import { IconLabel } from '../../../../../../base/browser/ui/iconLabel/iconLabel.js'; import { InputBox, MessageType } from '../../../../../../base/browser/ui/inputbox/inputBox.js'; +import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/browser/ui/list/list.js'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from '../../../../../../base/browser/ui/tree/tree.js'; import { timeout } from '../../../../../../base/common/async.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { FuzzyScore, createMatches } from '../../../../../../base/common/filters.js'; import { createSingleCallFunction } from '../../../../../../base/common/functional.js'; import { isMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { KeyCode } from '../../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { ResourceSet } from '../../../../../../base/common/map.js'; import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import Severity from '../../../../../../base/common/severity.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; -import { IMarkdownRendererService } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import * as nls from '../../../../../../nls.js'; import { getActionBarActions } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../../platform/actions/common/actions.js'; @@ -27,29 +32,24 @@ import { IConfigurationService } from '../../../../../../platform/configuration/ import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IContextViewService } from '../../../../../../platform/contextview/browser/contextView.js'; import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; +import { IMarkdownRendererService } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import product from '../../../../../../platform/product/common/product.js'; import { defaultInputBoxStyles } from '../../../../../../platform/theme/browser/defaultStyles.js'; -import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; -import { IWorkbenchLayoutService, Position } from '../../../../../services/layout/browser/layoutService.js'; -import { ViewContainerLocation, IEditableData } from '../../../../../common/views.js'; import { IResourceLabel, ResourceLabels } from '../../../../../browser/labels.js'; -import { IconLabel } from '../../../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { IEditableData, ViewContainerLocation } from '../../../../../common/views.js'; import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js'; +import { IWorkbenchLayoutService, Position } from '../../../../../services/layout/browser/layoutService.js'; +import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; import { IChatService } from '../../../common/chatService.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; +import { ChatSessionUri } from '../../../common/chatUri.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { IChatWidgetService } from '../../chat.js'; import { allowedChatMarkdownHtmlTags } from '../../chatContentMarkdownRenderer.js'; -import { ChatSessionItemWithProvider, extractTimestamp, getSessionItemContextOverlay, isLocalChatSessionItem, processSessionsWithTimeGrouping } from '../common.js'; import '../../media/chatSessions.css'; -import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; -import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/browser/ui/list/list.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; -import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; -import { ChatSessionUri } from '../../../common/chatUri.js'; -import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; -import { ResourceSet } from '../../../../../../base/common/map.js'; +import { ChatSessionItemWithProvider, extractTimestamp, getSessionItemContextOverlay, isLocalChatSessionItem, processSessionsWithTimeGrouping } from '../common.js'; +import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; interface ISessionTemplateData { readonly container: HTMLElement; @@ -226,7 +226,7 @@ export class SessionsRenderer extends Disposable implements ITreeRenderer(ChatConfiguration.ShowAgentSessionsViewDescription) && session.provider.chatSessionType !== 'local'; + const renderDescriptionOnSecondRow = this.configurationService.getValue(ChatConfiguration.ShowAgentSessionsViewDescription) && session.provider.chatSessionType !== localChatSessionType; if (renderDescriptionOnSecondRow && session.description) { templateData.container.classList.toggle('multiline', true); @@ -545,7 +545,7 @@ export class SessionsDataSource implements IAsyncDataSource existingSessions.add(s.resource)); @@ -612,7 +612,7 @@ export class SessionsDelegate implements IListVirtualDelegate { const scrollingByPage = this.configurationService.getValue('workbench.list.scrollByPage'); if (e.element === null && !scrollingByPage) { - if (this.provider?.chatSessionType && this.provider.chatSessionType !== 'local') { + if (this.provider?.chatSessionType && this.provider.chatSessionType !== localChatSessionType) { this.commandService.executeCommand(`workbench.action.chat.openNewSessionEditor.${this.provider?.chatSessionType}`); } else { this.commandService.executeCommand(ACTION_ID_OPEN_CHAT); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 51c94e9012c..0fdcbfb03f6 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -88,6 +88,11 @@ export type IChatSessionHistoryItem = { participant: string; }; +/** + * The session type used for local agent chat sessions. + */ +export const localChatSessionType = 'local'; + export interface IChatSession extends IDisposable { readonly onWillDispose: Event; diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts index 5737e1e148a..d2cee10b748 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts @@ -10,9 +10,9 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { IChatSessionItem, IChatSessionItemProvider, ChatSessionStatus } from '../../common/chatSessionsService.js'; +import { AgentSessionsViewModel, IAgentSessionViewModel, isAgentSession, isAgentSessionsViewModel, isLocalAgentSessionItem } from '../../browser/agentSessions/agentSessionViewModel.js'; +import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js'; import { ChatSessionUri } from '../../common/chatUri.js'; -import { AgentSessionsViewModel, IAgentSessionViewModel, LOCAL_AGENT_SESSION_TYPE, isLocalAgentSessionItem, isAgentSession, isAgentSessionsViewModel } from '../../browser/agentSessions/agentSessionViewModel.js'; import { MockChatService } from '../common/mockChatService.js'; import { MockChatSessionsService } from '../common/mockChatSessionsService.js'; @@ -593,12 +593,12 @@ suite('AgentSessionsViewModel', () => { test('should handle local agent session type specially', async () => { const provider: IChatSessionItemProvider = { - chatSessionType: LOCAL_AGENT_SESSION_TYPE, + chatSessionType: localChatSessionType, onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [ { id: 'local-session', - resource: ChatSessionUri.forSession(LOCAL_AGENT_SESSION_TYPE, 'local-session'), + resource: ChatSessionUri.forSession(localChatSessionType, 'local-session'), label: 'Local Session', timing: { startTime: Date.now() } } @@ -614,7 +614,7 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 1); - assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, LOCAL_AGENT_SESSION_TYPE); + assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, localChatSessionType); }); test('should correctly construct resource URIs for sessions', async () => { @@ -813,7 +813,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => { test('isLocalAgentSessionItem should identify local sessions', () => { const localSession: IAgentSessionViewModel = { provider: { - chatSessionType: LOCAL_AGENT_SESSION_TYPE, + chatSessionType: localChatSessionType, onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [] }, From 8d5670cc17887d06edae5dc67906db8fae34881f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Oct 2025 16:31:14 -0700 Subject: [PATCH 1804/4355] edits: make keep/undo a bit more atomic (#273822) * fix linting failure * edits: make keep/undo a bit more atomic Closes #272972 --- .../chatEditingModifiedFileEntry.ts | 31 ++++++++++++++----- .../browser/chatEditing/chatEditingSession.ts | 22 +++++++++++-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 23253994e5e..64569cc55c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -210,34 +210,51 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im } async accept(): Promise { + const callback = await this.acceptDeferred(); + if (callback) { + transaction(callback); + } + } + + /** Accepts and returns a function used to transition the state. This MUST be called by the consumer. */ + async acceptDeferred(): Promise<((tx: ITransaction) => void) | undefined> { if (this._stateObs.get() !== ModifiedFileEntryState.Modified) { // already accepted or rejected return; } await this._doAccept(); - transaction(tx => { + + return (tx: ITransaction) => { this._stateObs.set(ModifiedFileEntryState.Accepted, tx); this._autoAcceptCtrl.set(undefined, tx); - }); - - this._notifySessionAction('accepted'); + this._notifySessionAction('accepted'); + }; } protected abstract _doAccept(): Promise; async reject(): Promise { + const callback = await this.rejectDeferred(); + if (callback) { + transaction(callback); + } + } + + /** Rejects and returns a function used to transition the state. This MUST be called by the consumer. */ + async rejectDeferred(): Promise<((tx: ITransaction) => void) | undefined> { if (this._stateObs.get() !== ModifiedFileEntryState.Modified) { // already accepted or rejected - return; + return undefined; } this._notifySessionAction('rejected'); await this._doReject(); - transaction(tx => { + + return (tx: ITransaction) => { this._stateObs.set(ModifiedFileEntryState.Rejected, tx); this._autoAcceptCtrl.set(undefined, tx); - }); + }; } protected abstract _doReject(): Promise; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index ef7badf9af3..344c0bc90a1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -26,6 +26,7 @@ import { localize } from '../../../../../nls.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { EditorActivation } from '../../../../../platform/editor/common/editor.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; import { DiffEditorInput } from '../../../../common/editor/diffEditorInput.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; @@ -131,6 +132,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio @IChatService private readonly _chatService: IChatService, @INotebookService private readonly _notebookService: INotebookService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, + @ILogService private readonly _logService: ILogService, ) { super(); this._timeline = this._instantiationService.createInstance( @@ -315,12 +317,26 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio const applicableEntries = this._entriesObs.get() .filter(e => uris.length === 0 || uris.some(u => isEqual(u, e.modifiedURI))) - .filter(e => !e.isCurrentlyBeingModifiedBy.get()); + .filter(e => !e.isCurrentlyBeingModifiedBy.get()) + .filter(e => e.state.get() === ModifiedFileEntryState.Modified); - for (const entry of applicableEntries) { - await entry[action](); + if (applicableEntries.length === 0) { + return 0; } + // Perform all I/O operations in parallel, each resolving to a state transition callback + const method = action === 'accept' ? 'acceptDeferred' : 'rejectDeferred'; + const transitionCallbacks = await Promise.all( + applicableEntries.map(entry => entry[method]().catch(err => { + this._logService.error(`Error calling ${method} on entry ${entry.modifiedURI}`, err); + })) + ); + + // Execute all state transitions atomically in a single transaction + transaction(tx => { + transitionCallbacks.forEach(callback => callback?.(tx)); + }); + return applicableEntries.length; } From 3d528fc294191c5c2341b395257e15d34372883a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Oct 2025 16:31:49 -0700 Subject: [PATCH 1805/4355] tools: make global auto approve apply to results too (#273838) Closes https://github.com/microsoft/vscode-copilot-evaluation/issues/669 --- .../contrib/chat/browser/languageModelToolsService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 5abc6be3e4e..835bb26968b 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -557,6 +557,10 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo } private async shouldAutoConfirmPostExecution(toolId: string, runsInWorkspace: boolean | undefined): Promise { + if (this._configurationService.getValue(ChatConfiguration.GlobalAutoApprove) && await this._checkGlobalAutoApprove()) { + return { type: ToolConfirmKind.Setting, id: ChatConfiguration.GlobalAutoApprove }; + } + return this._postExecutionConfirmStore.checkAutoConfirmation(toolId); } From 5840155aced8f3addac37e0fbbbc90d45fbf8b1e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 01:22:06 +0100 Subject: [PATCH 1806/4355] Git - add commands to easily compare a local branch with upstream and merge base (#273839) --- extensions/git/package.json | 45 +++++++++++++++++++ extensions/git/package.nls.json | 3 ++ extensions/git/src/commands.ts | 65 +++++++++++++++++++++++++-- extensions/git/src/historyProvider.ts | 11 ++++- 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 7e6339d29dc..96ece71a59f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1004,6 +1004,24 @@ "title": "%command.graphCompareRef%", "category": "Git", "enablement": "!operationInProgress" + }, + { + "command": "git.graph.openIncomingChanges", + "title": "%command.graphOpenIncomingChanges%", + "category": "Git", + "enablement": "!operationInProgress && git.currentHistoryItemIsBehind" + }, + { + "command": "git.graph.openOutgoingChanges", + "title": "%command.graphOpenOutgoingChanges%", + "category": "Git", + "enablement": "!operationInProgress && git.currentHistoryItemIsAhead" + }, + { + "command": "git.graph.compareWithMergeBase", + "title": "%command.graphCompareWithMergeBase%", + "category": "Git", + "enablement": "!operationInProgress && git.currentHistoryItemHasMergeBase" } ], "continueEditSession": [ @@ -1612,6 +1630,18 @@ "command": "git.graph.cherryPick", "when": "false" }, + { + "command": "git.graph.openIncomingChanges", + "when": "false" + }, + { + "command": "git.graph.openOutgoingChanges", + "when": "false" + }, + { + "command": "git.graph.compareWithMergeBase", + "when": "false" + }, { "command": "git.diff.stageHunk", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/" @@ -2230,6 +2260,21 @@ "command": "git.publish", "when": "scmProvider == git && !scmCurrentHistoryItemRefHasRemote", "group": "navigation@903" + }, + { + "command": "git.graph.openIncomingChanges", + "when": "scmProvider == git", + "group": "1_changes@1" + }, + { + "command": "git.graph.openOutgoingChanges", + "when": "scmProvider == git", + "group": "1_changes@2" + }, + { + "command": "git.graph.compareWithMergeBase", + "when": "scmProvider == git", + "group": "2_compare@1" } ], "scm/historyItem/context": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 80ffec8c433..fe81fc4322c 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -138,6 +138,9 @@ "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", "command.graphCompareRef": "Compare With...", + "command.graphOpenIncomingChanges": "Open Incoming Changes", + "command.graphOpenOutgoingChanges": "Open Outgoing Changes", + "command.graphCompareWithMergeBase": "Compare With Merge Base", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b6ff0a6ff30..096a1b4b8f3 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlHistoryItemRef } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -3142,8 +3142,9 @@ export class CommandCenter { return; } + const sourceCommit = sourceRef.ref.commit; + try { - const sourceCommit = sourceRef.ref.commit; const changes = await repository.diffTrees(sourceCommit, historyItem.id); if (changes.length === 0) { @@ -3164,7 +3165,65 @@ export class CommandCenter { resources }); } catch (err) { - throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); + window.showErrorMessage(l10n.t('Failed to compare references "{0}" and "{1}": {2}', sourceCommit, historyItem.id, err.message)); + } + } + + @command('git.graph.openIncomingChanges', { repository: true }) + async openIncomingChanges(repository: Repository): Promise { + await this._openChangesBetweenRefs( + repository, + repository.historyProvider.currentHistoryItemRef, + repository.historyProvider.currentHistoryItemRemoteRef); + } + + @command('git.graph.openOutgoingChanges', { repository: true }) + async openOutgoingChanges(repository: Repository): Promise { + await this._openChangesBetweenRefs( + repository, + repository.historyProvider.currentHistoryItemRemoteRef, + repository.historyProvider.currentHistoryItemRef); + } + + @command('git.graph.compareWithMergeBase', { repository: true }) + async compareWithMergeBase(repository: Repository): Promise { + await this._openChangesBetweenRefs( + repository, + repository.historyProvider.currentHistoryItemBaseRef, + repository.historyProvider.currentHistoryItemRef); + } + + private async _openChangesBetweenRefs( + repository: Repository, + ref1: SourceControlHistoryItemRef | undefined, + ref2: SourceControlHistoryItemRef | undefined + ): Promise { + if (!repository || !ref1 || !ref2) { + return; + } + + try { + const changes = await repository.diffTrees(ref1.id, ref2.id); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref1.name, ref2.name)); + return; + } + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref1.id, ref2.id)); + const title = `${ref1.name} ↔ ${ref2.name}`; + const multiDiffSourceUri = Uri.from({ + scheme: 'git-ref-compare', + path: `${repository.root}/${ref1.id}..${ref2.id}` + }); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title, + resources + }); + } catch (err) { + window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref1.name, ref2.name, err.message)); } } diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 99f68fbf534..abd6dccdc23 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent, MarkdownString, Command } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent, MarkdownString, Command, commands } from 'vscode'; import { Repository, Resource } from './repository'; import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, fromNow, getCommitShortHash, subject, truncate } from './util'; import { toMultiFileDiffEditorUris } from './uri'; @@ -185,6 +185,15 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } } + // Update context keys for HEAD + if (this._HEAD?.ahead !== this.repository.HEAD?.ahead) { + commands.executeCommand('setContext', 'git.currentHistoryItemIsAhead', (this.repository.HEAD?.ahead ?? 0) > 0); + } + if (this._HEAD?.behind !== this.repository.HEAD?.behind) { + commands.executeCommand('setContext', 'git.currentHistoryItemIsBehind', (this.repository.HEAD?.behind ?? 0) > 0); + } + commands.executeCommand('setContext', 'git.currentHistoryItemHasMergeBase', this._currentHistoryItemBaseRef !== undefined); + this._HEAD = this.repository.HEAD; this._currentHistoryItemRef = { From 4ef612207547cb08c22cf39397d48aeb88949813 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:33:00 -0700 Subject: [PATCH 1807/4355] Rename `ChatService.getHistory` to `getLocalSessionHistory` Trying to make it more clear that this only returns the local sessions, not those from providers --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 4 ++-- .../chat/browser/agentSessions/agentSessionViewModel.ts | 2 +- .../chat/browser/chatSessions/view/sessionsTreeRenderer.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- src/vs/workbench/contrib/chat/common/chatService.ts | 2 +- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 2 +- src/vs/workbench/contrib/chat/test/common/mockChatService.ts | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 0dd2be9bb88..bd5264dc834 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -566,7 +566,7 @@ export function registerChatActions() { } const getPicks = async () => { - const items = await chatService.getHistory(); + const items = await chatService.getLocalSessionHistory(); items.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)); let lastDate: string | undefined = undefined; @@ -681,7 +681,7 @@ export function registerChatActions() { const getPicks = async (showAllChats: boolean = false, showAllAgents: boolean = false) => { // Fast picks: Get cached/immediate items first - const cachedItems = await chatService.getHistory(); + const cachedItems = await chatService.getLocalSessionHistory(); cachedItems.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)); const allFastPickItems: IChatPickerItem[] = cachedItems.map((i) => { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 50975a00ce1..aa95db33af4 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -169,7 +169,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions // TODO@bpasero this needs to come from the local provider: // - do we want to show history or not and how // - can we support all properties including `startTime` properly - for (const history of await this.chatService.getHistory()) { + for (const history of await this.chatService.getLocalSessionHistory()) { newSessions.push({ id: history.sessionId, resource: ChatSessionUri.forSession(localChatSessionType, history.sessionId), diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 3cc85c92567..85fa5155a13 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -577,7 +577,7 @@ export class SessionsDataSource implements IAsyncDataSource { try { // Get all chat history - const allHistory = await this.chatService.getHistory(); + const allHistory = await this.chatService.getLocalSessionHistory(); // Create history items with provider reference and timestamps const historyItems = allHistory.map((historyDetail): ChatSessionItemWithProvider => ({ diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 474de100447..0ce8c67d654 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1251,7 +1251,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private async computeHistoryItems(): Promise { try { - const items = await this.chatService.getHistory(); + const items = await this.chatService.getLocalSessionHistory(); return items .filter(i => !i.isActive) .sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)) diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index de81e4ccbf6..6169617a68b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -915,7 +915,7 @@ export interface IChatService { cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): Promise; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void; - getHistory(): Promise; + getLocalSessionHistory(): Promise; setChatSessionTitle(sessionId: string, title: string): void; clearAllHistoryEntries(): Promise; removeHistoryEntry(sessionId: string): Promise; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 3347a97e93c..7aa163a5b44 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -296,7 +296,7 @@ export class ChatService extends Disposable implements IChatService { * Chat sessions that have already been loaded into the chat view are excluded from the result. * Imported chat sessions are also excluded from the result. */ - async getHistory(): Promise { + async getLocalSessionHistory(): Promise { const liveSessionItems = Array.from(this._sessionModels.values()) .filter(session => !session.isImported && !session.inputType) .map(session => { diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index d0e350af990..f356f87a407 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -80,7 +80,7 @@ export class MockChatService implements IChatService { addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void { throw new Error('Method not implemented.'); } - async getHistory(): Promise { + async getLocalSessionHistory(): Promise { throw new Error('Method not implemented.'); } async clearAllHistoryEntries() { From 8fa832aa5264c1774f38cc3cbacd72042b0803e5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:36:42 +0100 Subject: [PATCH 1808/4355] Engineering - comment out code that breaks mangling (#273876) --- .../chatEditingCheckpointTimeline.test.ts | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts index c48b5e7aa29..942376175e2 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts @@ -979,36 +979,38 @@ suite('ChatEditingCheckpointTimeline', function () { assert.strictEqual(content, 'req2 modified'); }); - test('undoing entire request when no operations between start and checkpoint', async function () { - const uri = URI.parse('file:///test.txt'); - - timeline.recordFileBaseline(upcastPartial({ - uri, - requestId: 'req1', - content: 'initial', - epoch: timeline.incrementEpoch(), - telemetryInfo: DEFAULT_TELEMETRY_INFO - })); - - // Create start checkpoint - timeline.createCheckpoint('req1', undefined, 'Start of Request'); - - // Create end checkpoint without any operations in between - timeline.createCheckpoint('req1', 'stop1', 'End of Request'); - - // At this point, we're past both checkpoints. _willUndoToCheckpoint finds - // the previous checkpoint before the current epoch. Since there are no operations - // between the start and stop1, undoing should target the start. - const undoTarget = timeline['_willUndoToCheckpoint'].get(); - - // The logic checks if there are operations between start and the previous checkpoint. - // If not, it returns the start checkpoint. However, if currentEpoch is beyond - // the last checkpoint and there are no operations, undoTarget might be undefined - // or it might be the start checkpoint. Let's just verify the behavior. - if (undoTarget) { - assert.strictEqual(undoTarget.undoStopId, undefined); // Should target the start checkpoint - } - }); + // @connor4312 - this test breaks mangling + // Element implicitly has an 'any' type because expression of type '"_willUndoToCheckpoint"' can't be used to index type '$$ic' + // test('undoing entire request when no operations between start and checkpoint', async function () { + // const uri = URI.parse('file:///test.txt'); + + // timeline.recordFileBaseline(upcastPartial({ + // uri, + // requestId: 'req1', + // content: 'initial', + // epoch: timeline.incrementEpoch(), + // telemetryInfo: DEFAULT_TELEMETRY_INFO + // })); + + // // Create start checkpoint + // timeline.createCheckpoint('req1', undefined, 'Start of Request'); + + // // Create end checkpoint without any operations in between + // timeline.createCheckpoint('req1', 'stop1', 'End of Request'); + + // // At this point, we're past both checkpoints. _willUndoToCheckpoint finds + // // the previous checkpoint before the current epoch. Since there are no operations + // // between the start and stop1, undoing should target the start. + // const undoTarget = timeline['_willUndoToCheckpoint'].get(); + + // // The logic checks if there are operations between start and the previous checkpoint. + // // If not, it returns the start checkpoint. However, if currentEpoch is beyond + // // the last checkpoint and there are no operations, undoTarget might be undefined + // // or it might be the start checkpoint. Let's just verify the behavior. + // if (undoTarget) { + // assert.strictEqual(undoTarget.undoStopId, undefined); // Should target the start checkpoint + // } + // }); test('getContentAtStop with file that does not exist in operations', async function () { const uri = URI.parse('file:///test.txt'); From 0e3668dff2f6700b98006f10a409f906f250d77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20Ruales?= <1588988+jruales@users.noreply.github.com> Date: Wed, 29 Oct 2025 01:36:24 -0700 Subject: [PATCH 1809/4355] Consolidate duplicated UpdateTracker code (#273858) Consolidate duplicated UpdateTracker code in promptsServiceImpl.ts --- .../service/promptsServiceImpl.ts | 91 +++++-------------- 1 file changed, 22 insertions(+), 69 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index f3a1723b175..4d43c09fd17 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -28,7 +28,7 @@ import { IUserDataProfileService } from '../../../../../services/userDataProfile import { IVariableReference } from '../../chatModes.js'; import { PromptsConfig } from '../config/config.js'; import { getCleanPromptName, PROMPT_FILE_EXTENSION } from '../config/promptFileLocations.js'; -import { getPromptsTypeForLanguageId, AGENT_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType, getLanguageIdForPromptsType } from '../promptTypes.js'; +import { getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType, getLanguageIdForPromptsType } from '../promptTypes.js'; import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; import { IAgentInstructions, IAgentSource, IChatPromptSlashCommand, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPromptPath, IPromptsService, IUserPromptPath, PromptsStorage } from './promptsService.js'; @@ -49,7 +49,10 @@ export class PromptsService extends Disposable implements IPromptsService { */ private cachedCustomAgents: Promise | undefined; - + /** + * Cache for parsed prompt files keyed by URI. + * The number in the returned tuple is textModel.getVersionId(), which is an internal VS Code counter that increments every time the text model's content changes. + */ private parsedPromptFileCache = new ResourceMap<[number, ParsedPromptFile]>(); /** @@ -90,13 +93,13 @@ export class PromptsService extends Disposable implements IPromptsService { this.fileLocator = this._register(this.instantiationService.createInstance(PromptFilesLocator)); - const promptUpdateTracker = this._register(new PromptUpdateTracker(this.fileLocator, this.modelService)); - this._register(promptUpdateTracker.onDiDPromptChange((event) => { + const promptUpdateTracker = this._register(new UpdateTracker(this.fileLocator, PromptsType.prompt, this.modelService)); + this._register(promptUpdateTracker.onDidPromptChange((event) => { if (event.kind === 'fileSystem') { this.promptFileByCommandCache.clear(); } else { - // Clear cache for prompt files that match the changed URI\ + // Clear cache for prompt files that match the changed URI const pendingDeletes: string[] = []; for (const [key, value] of this.promptFileByCommandCache) { if (isEqual(value.value?.uri, event.uri)) { @@ -124,7 +127,7 @@ export class PromptsService extends Disposable implements IPromptsService { if (!this.onDidChangeCustomAgentsEmitter) { const emitter = this.onDidChangeCustomAgentsEmitter = this._register(new Emitter()); const updateTracker = this._register(new UpdateTracker(this.fileLocator, PromptsType.agent, this.modelService)); - this._register(updateTracker.onDidChangeContent(() => { + this._register(updateTracker.onDidPromptChange((event) => { this.cachedCustomAgents = undefined; // reset cached custom agents emitter.fire(); })); @@ -445,94 +448,44 @@ function getCommandNameFromURI(uri: URI): string { return basename(uri.fsPath, PROMPT_FILE_EXTENSION); } -export class UpdateTracker extends Disposable { - - private static readonly CHAT_AGENT_UPDATE_DELAY_MS = 200; - - private readonly listeners = new ResourceMap(); - private readonly onDidChangeContentEmitter: Emitter; - - public get onDidChangeContent(): Event { - return this.onDidChangeContentEmitter.event; - } - - constructor( - fileLocator: PromptFilesLocator, - promptType: PromptsType, - @IModelService modelService: IModelService, - ) { - super(); - this.onDidChangeContentEmitter = this._register(new Emitter()); - const delayer = this._register(new Delayer(UpdateTracker.CHAT_AGENT_UPDATE_DELAY_MS)); - const trigger = () => delayer.trigger(() => this.onDidChangeContentEmitter.fire()); - - const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(promptType)); - this._register(filesUpdatedEventRegistration.event(() => trigger())); +export type UpdateKind = 'fileSystem' | 'textModel'; - const onAdd = (model: ITextModel) => { - if (model.getLanguageId() === getLanguageIdForPromptsType(promptType)) { - this.listeners.set(model.uri, model.onDidChangeContent(() => trigger())); - } - }; - const onRemove = (languageId: string, uri: URI) => { - if (languageId === AGENT_LANGUAGE_ID) { - this.listeners.get(uri)?.dispose(); - this.listeners.delete(uri); - trigger(); - } - }; - this._register(modelService.onModelAdded(model => onAdd(model))); - this._register(modelService.onModelLanguageChanged(e => { - onRemove(e.oldLanguageId, e.model.uri); - onAdd(e.model); - })); - this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri))); - } - - public override dispose(): void { - super.dispose(); - this.listeners.forEach(listener => listener.dispose()); - this.listeners.clear(); - } -} - -export type PromptUpdateKind = 'fileSystem' | 'textModel'; - -export interface IPromptUpdateEvent { - kind: PromptUpdateKind; +export interface IUpdateEvent { + kind: UpdateKind; uri?: URI; } -export class PromptUpdateTracker extends Disposable { +export class UpdateTracker extends Disposable { private static readonly PROMPT_UPDATE_DELAY_MS = 200; private readonly listeners = new ResourceMap(); - private readonly onDidPromptModelChange: Emitter; + private readonly onDidPromptModelChange: Emitter; - public get onDiDPromptChange(): Event { + public get onDidPromptChange(): Event { return this.onDidPromptModelChange.event; } constructor( fileLocator: PromptFilesLocator, + promptType: PromptsType, @IModelService modelService: IModelService, ) { super(); - this.onDidPromptModelChange = this._register(new Emitter()); - const delayer = this._register(new Delayer(PromptUpdateTracker.PROMPT_UPDATE_DELAY_MS)); - const trigger = (event: IPromptUpdateEvent) => delayer.trigger(() => this.onDidPromptModelChange.fire(event)); + this.onDidPromptModelChange = this._register(new Emitter()); + const delayer = this._register(new Delayer(UpdateTracker.PROMPT_UPDATE_DELAY_MS)); + const trigger = (event: IUpdateEvent) => delayer.trigger(() => this.onDidPromptModelChange.fire(event)); - const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.prompt)); + const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(promptType)); this._register(filesUpdatedEventRegistration.event(() => trigger({ kind: 'fileSystem' }))); const onAdd = (model: ITextModel) => { - if (model.getLanguageId() === PROMPT_LANGUAGE_ID) { + if (model.getLanguageId() === getLanguageIdForPromptsType(promptType)) { this.listeners.set(model.uri, model.onDidChangeContent(() => trigger({ kind: 'textModel', uri: model.uri }))); } }; const onRemove = (languageId: string, uri: URI) => { - if (languageId === PROMPT_LANGUAGE_ID) { + if (languageId === getLanguageIdForPromptsType(promptType)) { this.listeners.get(uri)?.dispose(); this.listeners.delete(uri); trigger({ kind: 'textModel', uri }); From 0aead435a08a9c4208f962c656d2346f7eb15e89 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:04:43 +0100 Subject: [PATCH 1810/4355] Engineering - try to run mangling in the PR check (#268428) --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 26ad219d114..6143914601f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -80,7 +80,7 @@ jobs: run: .github/workflows/check-clean-git-state.sh - name: Compile & Hygiene - run: npm exec -- npm-run-all -lp core-ci-pr extensions-ci-pr hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check + run: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From bb3afa7c8d019f5d6a09f67a01e4d73e11a9da4f Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 29 Oct 2025 10:36:11 +0000 Subject: [PATCH 1811/4355] codicons: update merge & diff-multiple icons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 121112 -> 121056 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index fcb9911db5777498883022efa2ea85b9de35a4a6..4bd23249453c03ff5acd050027898932837b0149 100644 GIT binary patch delta 1124 zcmbQSiT%Mw_6ZI=EvIkHVqnlwVPNc2nCN4|@?5gDYhsJCpbG;7&D-){@YcK0{ z*3WE$Z1&k|**4k!v8%BAU?1hc;*jKU$bb0`oS&E-Nt=|hlodyr;FzuuNtps-csIkyr1}_`CRi&^8M#G!Jnz#e@XyDKuy56 zz^K4IL2^NRf(?QTLIgrOLU}@qLT`n!g;j)Y30DYT5dJA*TEwwPp~$?*BT*JnGon63 zD@7+ouZod~$%yre-4gpIPAo1b?p8cod|CXi_NMbQ_8lKd#UwmsZps-sVh={r8T7eOApG>$f(OWmuZk$k>!=ulC>{e zDtlV?z8sO9gq#hzJh?u(9l7grujKLMx#i8uSIW=JKU2U_U{g?3u&I!tFs$%Ykw(#$ zqJPCc#VbmzN>-H0l?IfqD}7gXuH2@4R|QW+RwZBMlq#00oT>#?PpS>7*VWf>)uhyH zsrgpxP}@{{ppLCBuI^qvPrY9QLqk@>jz+7-S4~yTLd`DCP0hPnDq8lnO0~{wz0&%o z&8p9Zf)5p_S(f6QVslTBA#RQiL3nu)U zm^AU-q^LD+iz|Eu|s5s!4AJ21v}2{)Yv&|m&~qn zyA^hC*fVRd)!t3}n)aR9Z?J#U0f7TW2W}lyIJn~An?rhsjvW>|+;RB8k*uRiM{AD$ zI97D*)Nz5$N;*$6q!^_bbr}R1lo>1;+!<0C7|hkQ8I{=BMdcVLD@;|E(PPwRv}3ep zG!m0%6k``*14^oyn1T4B=96Qm%hsRZf;XciFT;O6FN1`(MoOe)M4TpWBV z?1G#rtb*Lmyb_!$!m(^D-Yl%FAcB#NMTV7wO$Nl3VdG#0GX8Oj3G5aS6BAg#!?6Wu z3i}=oZe|r`7RDMbLDo6Kd@5}0oLpQ$WBP!aIM~2u2|^4&n8dP~W!j9gBt{*eml%Z2 zjDXG-VN=p)6crH{Gd2RUm6g;*fg)7;hEbHhh#=6BYM~$nAB>n-IheQG@iN{z4FEz6r$hh% delta 1190 zcmaE`k$uJ{_6ZI=d_`PO85nd_7#RB^Ci^amf^+V0{M0 zJtY%&7_jt|*lgJNCR(HZgQTg-cg_XD3KpG&@RzQ6o>{QlMZPw@W}P!aGc zFf4FKkW|o)V4dKc5T1~hP>#^N&}(5#VI^T3!ezqegujcJ6mck$FET6gK$J<;l&Ck+ za?x?o%VI=gQexd=H^jb(6N<}-yB5zBUlhMB{#Sxk!m32A#7T*7l0=fMlJb&HBr_#@ zCD$acO1_uEmtvMum$E74R%*RcYFKJr>XOuNX*FrT()}`2GO998W$I*>WVvNEWbMip z%bt|ID@PzFCTC49N3KV1OYW-N3wa!QPI=Sva!YT8rmAJG=?>?HBD>QYffmM*LZq0U@-81{z9I-hS zb2;YL&3!SiV7}G-ocRkDbSyZv;Lk#rg)Iy3Epk}Ywdlp-ki~D7*ezML)M{zf(pSrL zmTg;ZvV6}9sTEZ#&a4buxoefos+3jxR`ac1vPNUgv^8(mnyl?v$G5I&z1aFW8|Q5H z*t}-*g@i3OTc&KevvtzebKBUqrEQzD?a%fp+rRA)*i@3a5h0i6SV2fiJ&Ie6p{$04snj}99hUUK-(k+!3DM`s)p zIo5aV*>RoCN;*$6q!@lPax(}rC^J|vxH2R$Fqo@pGb*vkGKz>zR+y?R1?1T=S~D7n z$uo+vi?9LZ)J)7keADR*HZY3T8;Qv>8XJjiWaqNs6BgzR<`d!IBL-`rZww5TmDKbZ z^%z0E5)lKs9Ow~oHhxBsS0Dmr_Fylvv&k_U*)y7%=rgJ*@iEFX-jL@KW|L(T=28dR z#-h&4A;Zqe$u7ge3*kd04YZjV+1Qyan3yb>+1VJGwdZqkD6(@ZvvRO;DR8o~aw>4K gaj From bfdddc0e8470632df6742d1847d47ab81ab62770 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 29 Oct 2025 04:23:05 -0700 Subject: [PATCH 1812/4355] notebooks: fix check to take simple path in diff operation (#273664) Noticed this when working on edit sessions, this seems like what the intended behavior should be --- .../common/services/notebookWebWorker.ts | 2 +- .../browser/diff/notebookDiffService.test.ts | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts index 213b8856c8f..ba83733d6ca 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWebWorker.ts @@ -249,7 +249,7 @@ export class NotebookWorker implements IWebWorkerServerRequestHandler, IDisposab const cellMapping = computeDiff(originalModel, modifiedModel, { cellsDiff: { changes: originalDiff.changes, quitEarly: false }, metadataChanged: false, }).cellDiffInfo; // If we have no insertions/deletions, then this is a good diffing. - if (cellMapping.every(c => c.type === 'modified')) { + if (cellMapping.every(c => c.type === 'modified' || c.type === 'unchanged')) { return { metadataChanged, cellsDiff: originalDiff diff --git a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts index 4939fafd498..ec7a75cf817 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/diff/notebookDiffService.test.ts @@ -167,9 +167,7 @@ suite('NotebookDiff Diff Service', () => { { modified: 2, original: 2 } ]); assert.deepStrictEqual(diff.cellsDiff.changes, [ - { originalStart: 0, originalLength: 0, modifiedStart: 0, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 0, originalLength: 1, modifiedStart: 1, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 1, originalLength: 1, modifiedStart: 2, modifiedLength: 0 } satisfies IDiffChange, + { originalStart: 0, originalLength: 2, modifiedStart: 0, modifiedLength: 2 } satisfies IDiffChange, ]); }); @@ -441,10 +439,8 @@ suite('NotebookDiff Diff Service', () => { { modified: 6, original: 6 } ]); assert.deepStrictEqual(diff.cellsDiff.changes, [ - { originalStart: 2, originalLength: 1, modifiedStart: 2, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 3, originalLength: 1, modifiedStart: 3, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 5, originalLength: 1, modifiedStart: 5, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 6, originalLength: 1, modifiedStart: 6, modifiedLength: 1 } satisfies IDiffChange, + { originalStart: 2, originalLength: 2, modifiedStart: 2, modifiedLength: 2 } satisfies IDiffChange, + { originalStart: 5, originalLength: 2, modifiedStart: 5, modifiedLength: 2 } satisfies IDiffChange, ]); }); @@ -894,9 +890,7 @@ suite('NotebookDiff Diff Service', () => { { modified: 5, original: 5 } ]); assert.deepStrictEqual(diff.cellsDiff.changes, [ - { originalStart: 3, originalLength: 1, modifiedStart: 3, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 4, originalLength: 1, modifiedStart: 4, modifiedLength: 1 } satisfies IDiffChange, - // { originalStart: 5, originalLength: 1, modifiedStart: 5, modifiedLength: 1 } satisfies IDiffChange, + { originalStart: 3, originalLength: 2, modifiedStart: 3, modifiedLength: 2 } satisfies IDiffChange, ]); }); @@ -1262,8 +1256,7 @@ suite('NotebookDiff Diff Service', () => { { modified: 4, original: 4 }, ]); assert.deepStrictEqual(diff.cellsDiff.changes, [ - { originalStart: 0, originalLength: 1, modifiedStart: 0, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 1, originalLength: 1, modifiedStart: 1, modifiedLength: 1 } satisfies IDiffChange, + { originalStart: 0, originalLength: 2, modifiedStart: 0, modifiedLength: 2 } satisfies IDiffChange, ]); }); test('Detect modification and insertion of cells', async () => { @@ -10228,8 +10221,7 @@ suite('NotebookDiff Diff Service', () => { { modified: 46, original: 46 }, ]); assert.deepStrictEqual(diff.cellsDiff.changes, [ - { originalStart: 5, originalLength: 1, modifiedStart: 5, modifiedLength: 1 } satisfies IDiffChange, - { originalStart: 6, originalLength: 1, modifiedStart: 6, modifiedLength: 1 } satisfies IDiffChange, + { originalStart: 5, originalLength: 2, modifiedStart: 5, modifiedLength: 2 } satisfies IDiffChange, { originalStart: 25, originalLength: 1, modifiedStart: 25, modifiedLength: 1 } satisfies IDiffChange, { originalStart: 42, originalLength: 1, modifiedStart: 42, modifiedLength: 1 } satisfies IDiffChange, { originalStart: 44, originalLength: 1, modifiedStart: 44, modifiedLength: 1 } satisfies IDiffChange, From 20f0bded08aad9dfbdd4d33b7fcefe6e6cbe178b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 04:44:50 -0700 Subject: [PATCH 1813/4355] Force terminal tool specific data to be immutable Fixes #273931 --- src/vs/base/common/types.ts | 7 +++++++ .../chatAgentTools/browser/tools/runInTerminalTool.ts | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index d0452557104..430e61ebd6a 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -326,6 +326,13 @@ export type Mutable = { -readonly [P in keyof T]: T[P] }; +/** + * A type that adds readonly to all properties of T, recursively. + */ +export type DeepImmutable = { + readonly [P in keyof T]: T[P] extends object ? DeepImmutable : T[P]; +}; + /** * A single object or an array of the objects. */ 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 6c43cd13adc..c7dee25520b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -15,7 +15,7 @@ import { Disposable, DisposableStore } from '../../../../../../base/common/lifec import { basename } from '../../../../../../base/common/path.js'; import { OperatingSystem, OS } from '../../../../../../base/common/platform.js'; import { count } from '../../../../../../base/common/strings.js'; -import type { SingleOrMany } from '../../../../../../base/common/types.js'; +import type { DeepImmutable, SingleOrMany } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; @@ -488,7 +488,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { - const toolSpecificData = invocation.toolSpecificData as IChatTerminalToolInvocationData | undefined; + + const toolSpecificData = invocation.toolSpecificData as DeepImmutable | undefined; if (!toolSpecificData) { throw new Error('toolSpecificData must be provided for this tool'); } From 42dab51b3b4e305c8d6a615c4a204af8c7ad7486 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Wed, 29 Oct 2025 04:49:47 -0700 Subject: [PATCH 1814/4355] Add Copy Button to Hover Content (#271990) --- .../hover/browser/contentHoverRendered.ts | 23 ++- .../browser/contentHoverWidgetWrapper.ts | 6 +- src/vs/editor/contrib/hover/browser/hover.css | 36 +++++ .../contrib/hover/browser/hoverCopyButton.ts | 46 ++++++ .../test/browser/hoverCopyButton.test.ts | 144 ++++++++++++++++++ 5 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 src/vs/editor/contrib/hover/browser/hoverCopyButton.ts create mode 100644 src/vs/editor/contrib/hover/test/browser/hoverCopyButton.test.ts diff --git a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts index 25e5747ab6f..969b7984846 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts @@ -7,6 +7,7 @@ import { IEditorHoverContext, IEditorHoverParticipant, IEditorHoverRenderContext import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { EditorHoverStatusBar } from './contentHoverStatusBar.js'; import { HoverStartSource } from './hoverOperation.js'; +import { HoverCopyButton } from './hoverCopyButton.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ModelDecorationOptions } from '../../../common/model/textModel.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; @@ -23,6 +24,8 @@ import { BugIndicatingError } from '../../../../base/common/errors.js'; import { HoverAction } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IOffsetRange } from '../../../common/core/ranges/offsetRange.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { MarkerHover } from './markerHoverParticipant.js'; export class RenderedContentHover extends Disposable { @@ -44,7 +47,8 @@ export class RenderedContentHover extends Disposable { participants: IEditorHoverParticipant[], context: IEditorHoverContext, @IKeybindingService keybindingService: IKeybindingService, - @IHoverService hoverService: IHoverService + @IHoverService hoverService: IHoverService, + @IClipboardService clipboardService: IClipboardService ) { super(); const parts = hoverResult.hoverParts; @@ -54,7 +58,8 @@ export class RenderedContentHover extends Disposable { parts, context, keybindingService, - hoverService + hoverService, + clipboardService )); const contentHoverComputerOptions = hoverResult.options; const anchor = contentHoverComputerOptions.anchor; @@ -235,12 +240,13 @@ class RenderedContentHoverParts extends Disposable { hoverParts: IHoverPart[], context: IEditorHoverContext, @IKeybindingService keybindingService: IKeybindingService, - @IHoverService hoverService: IHoverService + @IHoverService private readonly _hoverService: IHoverService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { super(); this._context = context; this._fragment = document.createDocumentFragment(); - this._register(this._renderParts(participants, hoverParts, context, keybindingService, hoverService)); + this._register(this._renderParts(participants, hoverParts, context, keybindingService, this._hoverService)); this._register(this._registerListenersOnRenderedParts()); this._register(this._createEditorDecorations(editor, hoverParts)); this._updateMarkdownAndColorParticipantInfo(participants); @@ -327,6 +333,15 @@ class RenderedContentHoverParts extends Disposable { event.stopPropagation(); this._focusedHoverPartIndex = -1; })); + // Add copy button for marker hovers + if (renderedPart.type === 'hoverPart' && renderedPart.hoverPart instanceof MarkerHover) { + disposables.add(new HoverCopyButton( + element, + () => renderedPart.participant.getAccessibleContent(renderedPart.hoverPart), + this._clipboardService, + this._hoverService + )); + } }); return disposables; } diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts index 305ef15f35e..1fa7ff1c38f 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -22,6 +22,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { RenderedContentHover } from './contentHoverRendered.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidget { @@ -39,7 +40,8 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IHoverService private readonly _hoverService: IHoverService + @IHoverService private readonly _hoverService: IHoverService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { super(); this._contentHoverWidget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); @@ -216,7 +218,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _showHover(hoverResult: ContentHoverResult): void { const context = this._getHoverContext(); - this._renderedContentHover.value = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService, this._hoverService); + this._renderedContentHover.value = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService, this._hoverService, this._clipboardService); if (this._renderedContentHover.value.domNodeHasChildren) { this._contentHoverWidget.show(this._renderedContentHover.value); } else { diff --git a/src/vs/editor/contrib/hover/browser/hover.css b/src/vs/editor/contrib/hover/browser/hover.css index 9a00f2d4d03..9b8dbf70463 100644 --- a/src/vs/editor/contrib/hover/browser/hover.css +++ b/src/vs/editor/contrib/hover/browser/hover.css @@ -37,6 +37,11 @@ display: flex; } +.monaco-editor .monaco-hover .hover-row.hover-row-with-copy { + position: relative; + padding-right: 20px; +} + .monaco-editor .monaco-hover .hover-row .hover-row-contents { min-width: 0; display: flex; @@ -78,3 +83,34 @@ .monaco-editor .monaco-hover code { background-color: var(--vscode-textCodeBlock-background); } + +.monaco-editor .monaco-hover .hover-copy-button { + position: absolute; + top: 4px; + right: 4px; + padding: 2px 4px; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; +} + +.monaco-editor .monaco-hover .hover-row-with-copy:hover .hover-copy-button, +.monaco-editor .monaco-hover .hover-row-with-copy:focus-within .hover-copy-button { + opacity: 1; +} + +.monaco-editor .monaco-hover .hover-copy-button:hover { + background-color: var(--vscode-toolbar-hoverBackground); +} + +.monaco-editor .monaco-hover .hover-copy-button:focus { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; +} + +.monaco-editor .monaco-hover .hover-copy-button .codicon { + font-size: 16px; + color: var(--vscode-foreground); +} diff --git a/src/vs/editor/contrib/hover/browser/hoverCopyButton.ts b/src/vs/editor/contrib/hover/browser/hoverCopyButton.ts new file mode 100644 index 00000000000..f711bb04874 --- /dev/null +++ b/src/vs/editor/contrib/hover/browser/hoverCopyButton.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { localize } from '../../../../nls.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { SimpleButton } from '../../find/browser/findWidget.js'; + +/** + * A button that appears in hover parts to copy their content to the clipboard. + */ +export class HoverCopyButton extends Disposable { + + private readonly _button: SimpleButton; + + constructor( + private readonly _container: HTMLElement, + private readonly _getContent: () => string, + @IClipboardService private readonly _clipboardService: IClipboardService, + @IHoverService private readonly _hoverService: IHoverService + ) { + super(); + + this._container.classList.add('hover-row-with-copy'); + + this._button = this._register(new SimpleButton({ + label: localize('hover.copy', "Copy"), + icon: Codicon.copy, + onTrigger: () => this._copyContent(), + className: 'hover-copy-button', + }, this._hoverService)); + + this._container.appendChild(this._button.domNode); + } + + private async _copyContent(): Promise { + const content = this._getContent(); + if (content) { + await this._clipboardService.writeText(content); + } + } +} diff --git a/src/vs/editor/contrib/hover/test/browser/hoverCopyButton.test.ts b/src/vs/editor/contrib/hover/test/browser/hoverCopyButton.test.ts new file mode 100644 index 00000000000..ceecd7d6adc --- /dev/null +++ b/src/vs/editor/contrib/hover/test/browser/hoverCopyButton.test.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { HoverCopyButton } from '../../browser/hoverCopyButton.js'; +import { TestClipboardService } from '../../../../../platform/clipboard/test/common/testClipboardService.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { NullHoverService } from '../../../../../platform/hover/test/browser/nullHoverService.js'; +import { mainWindow } from '../../../../../base/browser/window.js'; + +suite('Hover Copy Button', () => { + const disposables = new DisposableStore(); + let clipboardService: TestClipboardService; + let hoverService: IHoverService; + let container: HTMLElement; + + setup(() => { + clipboardService = new TestClipboardService(); + hoverService = NullHoverService; + container = mainWindow.document.createElement('div'); + mainWindow.document.body.appendChild(container); + }); + + teardown(() => { + disposables.clear(); + if (container.parentElement) { + container.parentElement.removeChild(container); + } + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('should create button element in container', () => { + disposables.add(new HoverCopyButton( + container, + () => 'test content', + clipboardService, + hoverService + )); + + const buttonElement = container.querySelector('.hover-copy-button'); + assert.ok(buttonElement, 'Button element should be created'); + assert.strictEqual(buttonElement?.getAttribute('role'), 'button'); + assert.strictEqual(buttonElement?.getAttribute('tabindex'), '0'); + assert.strictEqual(buttonElement?.getAttribute('aria-label'), 'Copy'); + }); + + test('should add hover-row-with-copy class to container', () => { + assert.ok(!container.classList.contains('hover-row-with-copy'), 'Container should not have class before button creation'); + + disposables.add(new HoverCopyButton( + container, + () => 'test content', + clipboardService, + hoverService + )); + + assert.ok(container.classList.contains('hover-row-with-copy'), 'Container should have hover-row-with-copy class after button creation'); + }); + + test('should have copy icon', () => { + disposables.add(new HoverCopyButton( + container, + () => 'test content', + clipboardService, + hoverService + )); + + const icon = container.querySelector('.codicon-copy'); + assert.ok(icon, 'Copy icon should be present'); + }); + + test('should copy content on click', async () => { + const testContent = 'test content to copy'; + disposables.add(new HoverCopyButton( + container, + () => testContent, + clipboardService, + hoverService + )); + + const buttonElement = container.querySelector('.hover-copy-button') as HTMLElement; + assert.ok(buttonElement); + + buttonElement.click(); + + const copiedText = await clipboardService.readText(); + assert.strictEqual(copiedText, testContent, 'Content should be copied to clipboard'); + }); + + test('should copy content on Enter key', async () => { + const testContent = 'test content for enter key'; + disposables.add(new HoverCopyButton( + container, + () => testContent, + clipboardService, + hoverService + )); + + const buttonElement = container.querySelector('.hover-copy-button') as HTMLElement; + assert.ok(buttonElement); + + // Simulate Enter key press - need to override keyCode for StandardKeyboardEvent + const keyEvent = new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + bubbles: true + }); + Object.defineProperty(keyEvent, 'keyCode', { get: () => 13 }); // Enter keyCode + buttonElement.dispatchEvent(keyEvent); + + const copiedText = await clipboardService.readText(); + assert.strictEqual(copiedText, testContent, 'Content should be copied on Enter key'); + }); + + test('should copy content on Space key', async () => { + const testContent = 'test content for space key'; + disposables.add(new HoverCopyButton( + container, + () => testContent, + clipboardService, + hoverService + )); + + const buttonElement = container.querySelector('.hover-copy-button') as HTMLElement; + assert.ok(buttonElement); + + // Simulate Space key press - need to override keyCode for StandardKeyboardEvent + const keyEvent = new KeyboardEvent('keydown', { + key: ' ', + code: 'Space', + bubbles: true + }); + Object.defineProperty(keyEvent, 'keyCode', { get: () => 32 }); // Space keyCode + buttonElement.dispatchEvent(keyEvent); + + const copiedText = await clipboardService.readText(); + assert.strictEqual(copiedText, testContent, 'Content should be copied on Space key'); + }); +}); From 02b7beb29ffa6f4409aed0f49895e75c92c50ac1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 05:02:35 -0700 Subject: [PATCH 1815/4355] Use more advanced version of recursive deep readonly --- src/vs/base/common/types.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 430e61ebd6a..1517e0ceb68 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -329,9 +329,19 @@ export type Mutable = { /** * A type that adds readonly to all properties of T, recursively. */ -export type DeepImmutable = { - readonly [P in keyof T]: T[P] extends object ? DeepImmutable : T[P]; -}; +export type DeepImmutable = T extends (infer U)[] + ? ReadonlyArray> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends Map + ? ReadonlyMap> + : T extends Set + ? ReadonlySet> + : T extends object + ? { + readonly [K in keyof T]: DeepImmutable; + } + : T; /** * A single object or an array of the objects. From 8fb6a9c257b2df265d07f8e49e9c96081e156639 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:14:58 +0100 Subject: [PATCH 1816/4355] Git - improve graph/timeline/blame hover rendering (#273932) --- extensions/git/src/historyProvider.ts | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index abd6dccdc23..cefdd2eda76 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -639,28 +639,47 @@ export function getHistoryItemHover(authorAvatar: string | undefined, authorName enabledCommands: commands?.flat().map(c => c.command) ?? [] }; + // Author if (authorName) { - const avatar = authorAvatar ? `![${authorName}](${authorAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)'; + // Avatar + if (authorAvatar) { + markdownString.appendMarkdown('!['); + markdownString.appendText(authorName); + markdownString.appendMarkdown(']('); + markdownString.appendText(authorAvatar); + markdownString.appendMarkdown(`|width=${AVATAR_SIZE},height=${AVATAR_SIZE})`); + } else { + markdownString.appendMarkdown('$(account)'); + } + // Email if (authorEmail) { - const emailTitle = l10n.t('Email'); - markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`); + markdownString.appendMarkdown(' [**'); + markdownString.appendText(authorName); + markdownString.appendMarkdown('**](mailto:'); + markdownString.appendText(authorEmail); + markdownString.appendMarkdown(')'); } else { - markdownString.appendMarkdown(`${avatar} **${authorName}**`); + markdownString.appendMarkdown(' **'); + markdownString.appendText(authorName); + markdownString.appendMarkdown('**'); } - if (authorDate) { + // Date + if (authorDate && !isNaN(new Date(authorDate).getTime())) { const dateString = new Date(authorDate).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); - markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`); + + markdownString.appendMarkdown(', $(history)'); + markdownString.appendText(` ${fromNow(authorDate, true, true)} (${dateString})`); } markdownString.appendMarkdown('\n\n'); } - // Subject | Message - markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`); + // Subject | Message (escape image syntax) + markdownString.appendMarkdown(`${emojify(message.replace(/!\[/g, '![').replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`); markdownString.appendMarkdown(`---\n\n`); // Short stats From 2f153d24b1091870f501bb017484c35e6ca78f79 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 05:39:22 -0700 Subject: [PATCH 1817/4355] Start of file redirection detection Part of #272984 --- .../browser/tools/runInTerminalTool.ts | 22 ++++++++++++++++++- .../browser/treeSitterCommandParser.ts | 21 ++++++++++++++++++ .../terminalChatAgentToolsConfiguration.ts | 13 +++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) 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 6c43cd13adc..3a8e2027b93 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -47,6 +47,9 @@ import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from import { OutputMonitor } from './monitoring/outputMonitor.js'; import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; +import { IHistoryService } from '../../../../../services/history/common/history.js'; // #region Tool data @@ -281,13 +284,15 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IHistoryService private readonly _historyService: IHistoryService, @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IStorageService private readonly _storageService: IStorageService, @ITerminalLogService private readonly _logService: ITerminalLogService, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, - @IChatService private readonly _chatService: IChatService + @IChatService private readonly _chatService: IChatService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { super(); @@ -371,6 +376,21 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._logService.info(`RunInTerminalTool: autoApprove: Failed to parse sub-commands via ${treeSitterLanguage} grammar`); } + const fileWriteCaptures = await this._treeSitterCommandParser.getFileWrites(treeSitterLanguage, actualCommand); + if (fileWriteCaptures.length) { + this._logService.info('RunInTerminalTool: autoApprove: Detected file writes'); + let cwd = await instance?.getCwdResource(); + if (!cwd) { + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); + const workspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; + cwd = workspaceFolder?.uri; + } + if (cwd) { + const fileUris = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); + console.log('.'); + } + } + if (subCommands) { const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index ea5dd566a8b..97bb80ec8f0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -39,6 +39,27 @@ export class TreeSitterCommandParser { return captures; } + async getFileWrites(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { + let query: string; + switch (languageId) { + case TreeSitterCommandParserLanguage.Bash: + query = [ + '(file_redirect', + ' (word) @file)', + ].join('\n'); + break; + case TreeSitterCommandParserLanguage.PowerShell: + query = [ + '(redirection', + ' (redirected_file_name', + ' (generic_token) @file))', + ].join('\n'); + break; + } + const captures = await this._queryTree(languageId, commandLine, query); + return captures; + } + private async _queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { const { tree, query } = await this._doQuery(languageId, commandLine, querySource); return query.captures(tree.rootNode); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 37a22a9fcd4..fa56a0eb1c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -16,6 +16,7 @@ export const enum TerminalChatAgentToolsSettingId { EnableAutoApprove = 'chat.tools.terminal.enableAutoApprove', AutoApprove = 'chat.tools.terminal.autoApprove', IgnoreDefaultAutoApproveRules = 'chat.tools.terminal.ignoreDefaultAutoApproveRules', + BlockDetectedFileWrites = 'chat.tools.terminal.blockDetectedFileWrites', ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout', AutoReplyToPrompts = 'chat.tools.terminal.autoReplyToPrompts', OutputLocation = 'chat.tools.terminal.outputLocation', @@ -331,6 +332,18 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Wed, 29 Oct 2025 12:54:35 +0000 Subject: [PATCH 1818/4355] titlebar: reduce Command Center spacer icon horizontal padding to 8px --- src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 3ff05bb7454..2f28eca7419 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -209,7 +209,7 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { // spacer if (i < groups.length - 1) { const icon = renderIcon(Codicon.circleSmallFilled); - icon.style.padding = '0 12px'; + icon.style.padding = '0 8px'; icon.style.height = '100%'; icon.style.opacity = '0.5'; container.appendChild(icon); From 3eb736f6f67b1d75af163d1ea49c7fe72d93a20b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 05:59:12 -0700 Subject: [PATCH 1819/4355] Handle when cwd isn't detected --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 11 ++++++++--- .../common/terminalChatAgentToolsConfiguration.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) 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 3a8e2027b93..498da83307d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -376,9 +376,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._logService.info(`RunInTerminalTool: autoApprove: Failed to parse sub-commands via ${treeSitterLanguage} grammar`); } + let fileWrites: URI[] | string[] = []; const fileWriteCaptures = await this._treeSitterCommandParser.getFileWrites(treeSitterLanguage, actualCommand); if (fileWriteCaptures.length) { - this._logService.info('RunInTerminalTool: autoApprove: Detected file writes'); let cwd = await instance?.getCwdResource(); if (!cwd) { const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); @@ -386,10 +386,15 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { cwd = workspaceFolder?.uri; } if (cwd) { - const fileUris = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); - console.log('.'); + fileWrites = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); + } else { + this._logService.info('RunInTerminalTool: autoApprove: Cwd could not be detected'); + fileWrites = fileWriteCaptures.map(e => e.node.text); } } + this._logService.info('RunInTerminalTool: autoApprove: File writes detected', fileWrites.map(e => e.toString())); + + if (subCommands) { const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index fa56a0eb1c0..a7cc2e38927 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -337,7 +337,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary Date: Wed, 29 Oct 2025 14:16:26 +0100 Subject: [PATCH 1820/4355] chat - add a smoke test to check for AI disabling support (#273940) --- .../chat/browser/terminalChatActions.ts | 1 + test/automation/src/electron.ts | 1 + test/automation/src/quickaccess.ts | 18 +++++++ test/smoke/src/areas/chat/chat.test.ts | 47 +++++++++++++++++++ test/smoke/src/main.ts | 2 + 5 files changed, 69 insertions(+) create mode 100644 test/smoke/src/areas/chat/chat.test.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 746b791fa7d..7944bf13cb1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -309,6 +309,7 @@ registerAction2(class ShowChatTerminalsAction extends Action2 { title: localize2('viewChatTerminals', 'View Chat Terminals'), category: localize2('terminalCategory2', 'Terminal'), f1: true, + precondition: ChatContextKeys.enabled, menu: [{ id: MenuId.ViewTitle, when: ContextKeyExpr.and(TerminalContextKeys.hasToolTerminal, ContextKeyExpr.equals('view', ChatViewId)), diff --git a/test/automation/src/electron.ts b/test/automation/src/electron.ts index 79e8c840a98..d5707850bf7 100644 --- a/test/automation/src/electron.ts +++ b/test/automation/src/electron.ts @@ -30,6 +30,7 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom '--disable-experiments', '--no-cached-data', '--disable-updates', + '--disable-extension=vscode.vscode-api-tests', `--crash-reporter-directory=${crashesPath}`, '--disable-workspace-trust', `--logsPath=${logsPath}` diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index 2c7c4054252..fe20542a470 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -243,4 +243,22 @@ export class QuickAccess { } } } + + async getVisibleCommandNames(searchValue: string): Promise { + + // open commands picker + await this.openQuickAccessWithRetry(QuickAccessKind.Commands, `>${searchValue}`); + + // wait for quick input elements to be available + let commandNames: string[] = []; + await this.quickInput.waitForQuickInputElements(elementNames => { + commandNames = elementNames; + return true; + }); + + // close the quick input + await this.quickInput.closeQuickInput(); + + return commandNames; + } } diff --git a/test/smoke/src/areas/chat/chat.test.ts b/test/smoke/src/areas/chat/chat.test.ts new file mode 100644 index 00000000000..8a9a8536dde --- /dev/null +++ b/test/smoke/src/areas/chat/chat.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Application, Logger } from '../../../../automation'; +import { installAllHandlers } from '../../utils'; + +export function setup(logger: Logger) { + describe('Chat', () => { + + // Shared before/after handling + installAllHandlers(logger); + + it('can disable AI features', async function () { + const app = this.app as Application; + + await app.workbench.settingsEditor.addUserSetting('chat.disableAIFeatures', 'true'); + + // await for setting to apply in the UI + await app.code.waitForElements('.noauxiliarybar', true, elements => elements.length === 1); + + // assert that AI related commands are not present + const commands = await app.workbench.quickaccess.getVisibleCommandNames('chat'); + let expectedFound = false; + const unexpectedFound: string[] = []; + for (const command of commands) { + if (command === 'Chat: Use AI Features with Copilot for free...') { + expectedFound = true; + continue; + } + + if (command.includes('Chat') || command.includes('Agent') || command.includes('Copilot')) { + unexpectedFound.push(command); + } + } + + if (!expectedFound) { + throw new Error(`Expected AI related command not found`); + } + + if (unexpectedFound.length > 0) { + throw new Error(`Unexpected AI related commands found after having disabled AI features: ${JSON.stringify(unexpectedFound, undefined, 0)}`); + } + }); + }); +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 15fb7848087..ffb09e1217d 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -26,6 +26,7 @@ import { setup as setupLocalizationTests } from './areas/workbench/localization. import { setup as setupLaunchTests } from './areas/workbench/launch.test'; import { setup as setupTerminalTests } from './areas/terminal/terminal.test'; import { setup as setupTaskTests } from './areas/task/task.test'; +import { setup as setupChatTests } from './areas/chat/chat.test'; const rootPath = path.join(__dirname, '..', '..', '..'); @@ -403,4 +404,5 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { setupMultirootTests(logger); if (!opts.web && !opts.remote && quality !== Quality.Dev && quality !== Quality.OSS) { setupLocalizationTests(logger); } if (!opts.web && !opts.remote) { setupLaunchTests(logger); } + if (!opts.web) { setupChatTests(logger); } }); From 512471a495856d603377ff7faa771e67f9ad8aeb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:34:41 +0000 Subject: [PATCH 1821/4355] Fix: Up arrow on wrapped lines navigates history instead of moving cursor (#273833) * Initial plan * Fix cursor navigation in wrapped lines Change cursor position check to only enable history navigation when cursor is at line 1, column 1. This prevents accidental history navigation when pressing up arrow on wrapped lines. Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexdima <5047891+alexdima@users.noreply.github.com> --- .../contrib/chat/browser/chatInputPart.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 00bf0421605..dfe43815c2e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1021,16 +1021,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } if (previous) { - const endOfFirstViewLine = this._inputEditor._getViewModel()?.getLineLength(1) ?? 1; - const endOfFirstModelLine = model.getLineLength(1); - if (endOfFirstViewLine === endOfFirstModelLine) { - // Not wrapped - set cursor to the end of the first line - this._inputEditor.setPosition({ lineNumber: 1, column: endOfFirstViewLine + 1 }); - } else { - // Wrapped - set cursor one char short of the end of the first view line. - // If it's after the next character, the cursor shows on the second line. - this._inputEditor.setPosition({ lineNumber: 1, column: endOfFirstViewLine }); - } + // When navigating to previous history, always position cursor at the start (line 1, column 1) + // This ensures that pressing up again will continue to navigate history + this._inputEditor.setPosition({ lineNumber: 1, column: 1 }); } else { this._inputEditor.setPosition(getLastPosition(model)); } @@ -1537,7 +1530,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - const atTop = position.lineNumber === 1 && position.column - 1 <= (this._inputEditor._getViewModel()?.getLineLength(1) ?? 0); + const atTop = position.lineNumber === 1 && position.column === 1; this.chatCursorAtTop.set(atTop); this.historyNavigationBackwardsEnablement.set(atTop); From 4c9941caed2e493cfcf8ae6b60c46b8f82893801 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:02:51 -0700 Subject: [PATCH 1822/4355] File write operation blocking --- .../browser/tools/runInTerminalTool.ts | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) 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 498da83307d..93b49edcecc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -394,7 +394,46 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } this._logService.info('RunInTerminalTool: autoApprove: File writes detected', fileWrites.map(e => e.toString())); - + // Check file write protection + let isFileWriteBlocked = false; + if (fileWrites.length > 0) { + const blockDetectedFileWrites = this._configurationService.getValue(TerminalChatAgentToolsSettingId.BlockDetectedFileWrites); + switch (blockDetectedFileWrites) { + case 'all': { + isFileWriteBlocked = true; + this._logService.info('RunInTerminalTool: autoApprove: File writes blocked due to "all" setting'); + break; + } + case 'outsideWorkspace': { + // Check if any file writes are outside the workspace + const workspaceFolders = this._workspaceContextService.getWorkspace().folders; + if (workspaceFolders.length > 0) { + for (const fileWrite of fileWrites) { + const fileUri = URI.isUri(fileWrite) ? fileWrite : URI.file(fileWrite); + const isInsideWorkspace = workspaceFolders.some(folder => + folder.uri.scheme === fileUri.scheme && + (fileUri.path.startsWith(folder.uri.path + '/') || fileUri.path === folder.uri.path) + ); + if (!isInsideWorkspace) { + isFileWriteBlocked = true; + this._logService.info(`RunInTerminalTool: autoApprove: File write blocked outside workspace: ${fileUri.toString()}`); + break; + } + } + } else { + // No workspace folders, consider all writes as outside workspace + isFileWriteBlocked = true; + this._logService.info('RunInTerminalTool: autoApprove: File writes blocked - no workspace folders'); + } + break; + } + case 'never': + default: { + // Allow all file writes + break; + } + } + } if (subCommands) { const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); @@ -444,7 +483,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // Apply auto approval or force it off depending on enablement/opt-in state - if (isAutoApproveEnabled) { + if (isAutoApproveEnabled && !isFileWriteBlocked) { autoApproveInfo = this._createAutoApproveInfo( isAutoApproved, isDenied, @@ -454,6 +493,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { ); } else { isAutoApproved = false; + if (isFileWriteBlocked) { + this._logService.info('RunInTerminalTool: autoApprove: Auto approval disabled due to blocked file writes'); + } } // Send telemetry about auto approval process @@ -466,14 +508,31 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { autoApproveDefault }); - // Add a disclaimer warning about prompt injection for common commands that return - // content from the web + // Add disclaimers for various security concerns + const disclaimers: string[] = []; + + // File write warning + if (fileWrites.length > 0) { + const fileWritesList = fileWrites.map(fw => URI.isUri(fw) ? fw.fsPath : fw).join(', '); + if (isFileWriteBlocked) { + disclaimers.push(localize('runInTerminal.fileWriteBlockedDisclaimer', 'File write operations detected that cannot be auto approved: {0}', fileWritesList)); + } else { + disclaimers.push(localize('runInTerminal.fileWriteDisclaimer', 'File write operations detected: {0}', fileWritesList)); + } + } + + // Prompt injection warning for common commands that return content from the web const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); if (!isAutoApproved && ( subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) )) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true }); + disclaimers.push(localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.')); + } + + // Combine disclaimers + if (disclaimers.length > 0) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); } if (!isAutoApproved && isAutoApproveEnabled) { @@ -485,7 +544,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { if (shellType === 'powershell') { shellType = 'pwsh'; } - confirmationMessages = (isAutoApproved && isAutoApproveAllowed) ? undefined : { + confirmationMessages = (isAutoApproved && isAutoApproveAllowed && !isFileWriteBlocked) ? undefined : { title: args.isBackground ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) : localize('runInTerminal', "Run `{0}` command?", shellType), From d49049e5263a64cba8c9ca33f89bb0ad198f3391 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 29 Oct 2025 10:48:45 -0400 Subject: [PATCH 1823/4355] Fix clear model cache race (#273929) --- src/vs/workbench/api/common/extHostLanguageModels.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 05a8e6c10e7..faab44c5751 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -173,7 +173,6 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { if (!data) { return []; } - this._clearModelCache(vendor); const modelInformation: vscode.LanguageModelChatInformation[] = await data.provider.provideLanguageModelChatInformation(options, token) ?? []; const modelMetadataAndIdentifier: ILanguageModelChatMetadataAndIdentifier[] = modelInformation.map((m): ILanguageModelChatMetadataAndIdentifier => { let auth; @@ -215,8 +214,8 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { }; }); + this._clearModelCache(vendor); for (let i = 0; i < modelMetadataAndIdentifier.length; i++) { - this._localModels.set(modelMetadataAndIdentifier[i].identifier, { metadata: modelMetadataAndIdentifier[i].metadata, info: modelInformation[i] From bb692977fafccc13c2011cdb3e64a7a5581650f2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:58:38 -0700 Subject: [PATCH 1824/4355] Pull file write detection into ICommandLineAnalyzer --- .../commandLineAnalyzer.ts | 25 +++++ .../commandLineFileWriteAnalyzer.ts | 103 ++++++++++++++++++ .../browser/tools/runInTerminalTool.ts | 72 +++--------- 3 files changed, 145 insertions(+), 55 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts new file mode 100644 index 00000000000..01edfddae46 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { OperatingSystem } from '../../../../../../../base/common/platform.js'; +import type { ITerminalInstance } from '../../../../../terminal/browser/terminal.js'; +import type { TreeSitterCommandParserLanguage } from '../../treeSitterCommandParser.js'; + +export interface ICommandLineAnalyzer { + analyze(options: ICommandLineAnalyzerOptions): Promise; +} + +export interface ICommandLineAnalyzerOptions { + commandLine: string; + instance: ITerminalInstance | undefined; + shell: string; + os: OperatingSystem; + treeSitterLanguage: TreeSitterCommandParserLanguage; +} + +export interface ICommandLineAnalyzerResult { + readonly isAutoApproveAllowed: boolean; + readonly disclaimers: string[]; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts new file mode 100644 index 00000000000..bafcd3c939d --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../../../../base/common/uri.js'; +import { localize } from '../../../../../../../nls.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'; +import { IHistoryService } from '../../../../../../services/history/common/history.js'; +import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; +import type { TreeSitterCommandParser } from '../../treeSitterCommandParser.js'; +import type { ICommandLineAnalyzer, ICommandLineAnalyzerOptions, ICommandLineAnalyzerResult } from './commandLineAnalyzer.js'; + +export class CommandLineFileWriteAnalyzer implements ICommandLineAnalyzer { + constructor( + private readonly _treeSitterCommandParser: TreeSitterCommandParser, + private readonly _log: (message: string, ...args: unknown[]) => void, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IHistoryService private readonly _historyService: IHistoryService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, + ) { + } + + async analyze(options: ICommandLineAnalyzerOptions): Promise { + return this._getResult(await this._getFileWrites(options)); + } + + private async _getFileWrites(options: ICommandLineAnalyzerOptions): Promise { + let fileWrites: URI[] | string[] = []; + const fileWriteCaptures = await this._treeSitterCommandParser.getFileWrites(options.treeSitterLanguage, options.commandLine); + if (fileWriteCaptures.length) { + let cwd = await options.instance?.getCwdResource(); + if (!cwd) { + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); + const workspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; + cwd = workspaceFolder?.uri; + } + if (cwd) { + fileWrites = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); + } else { + this._log('Cwd could not be detected'); + fileWrites = fileWriteCaptures.map(e => e.node.text); + } + } + this._log('File writes detected', fileWrites.map(e => e.toString())); + return fileWrites; + } + + private _getResult(fileWrites: URI[] | string[]): ICommandLineAnalyzerResult { + let isAutoApproveAllowed = true; + if (fileWrites.length > 0) { + const blockDetectedFileWrites = this._configurationService.getValue(TerminalChatAgentToolsSettingId.BlockDetectedFileWrites); + switch (blockDetectedFileWrites) { + case 'all': { + isAutoApproveAllowed = false; + this._log('File writes blocked due to "all" setting'); + break; + } + case 'outsideWorkspace': { + const workspaceFolders = this._workspaceContextService.getWorkspace().folders; + if (workspaceFolders.length > 0) { + for (const fileWrite of fileWrites) { + const fileUri = URI.isUri(fileWrite) ? fileWrite : URI.file(fileWrite); + const isInsideWorkspace = workspaceFolders.some(folder => + folder.uri.scheme === fileUri.scheme && + (fileUri.path.startsWith(folder.uri.path + '/') || fileUri.path === folder.uri.path) + ); + if (!isInsideWorkspace) { + isAutoApproveAllowed = false; + this._log(`File write blocked outside workspace: ${fileUri.toString()}`); + break; + } + } + } else { + // No workspace folders, consider all writes as outside workspace + isAutoApproveAllowed = false; + this._log('File writes blocked - no workspace folders'); + } + break; + } + case 'never': + default: { + break; + } + } + } + + const disclaimers: string[] = []; + if (fileWrites.length > 0) { + const fileWritesList = fileWrites.map(fw => `\`${URI.isUri(fw) ? fw.fsPath : fw}\``).join(', '); + if (!isAutoApproveAllowed) { + disclaimers.push(localize('runInTerminal.fileWriteBlockedDisclaimer', 'File write operations detected that cannot be auto approved: {0}', fileWritesList)); + } else { + disclaimers.push(localize('runInTerminal.fileWriteDisclaimer', 'File write operations detected: {0}', fileWritesList)); + } + } + return { + isAutoApproveAllowed, + disclaimers, + }; + } +} 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 93b49edcecc..debafab4b41 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -50,6 +50,8 @@ import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../tre import { URI } from '../../../../../../base/common/uri.js'; import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; import { IHistoryService } from '../../../../../services/history/common/history.js'; +import { type ICommandLineAnalyzer, type ICommandLineAnalyzerOptions } from './commandLineAnalyzer/commandLineAnalyzer.js'; +import { CommandLineFileWriteAnalyzer } from './commandLineAnalyzer/commandLineFileWriteAnalyzer.js'; // #region Tool data @@ -265,6 +267,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _commandSimplifier: CommandSimplifier; private readonly _treeSitterCommandParser: TreeSitterCommandParser; private readonly _telemetry: RunInTerminalToolTelemetry; + private readonly _commandLineAnalyzers: ICommandLineAnalyzer[]; protected readonly _commandLineAutoApprover: CommandLineAutoApprover; protected readonly _profileFetcher: TerminalProfileFetcher; protected readonly _sessionTerminalAssociations: Map = new Map(); @@ -302,6 +305,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._treeSitterCommandParser = this._instantiationService.createInstance(TreeSitterCommandParser); this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend, this._treeSitterCommandParser); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); + this._commandLineAnalyzers = [ + this._instantiationService.createInstance(CommandLineFileWriteAnalyzer, this._treeSitterCommandParser, (message, args) => this._logService.info(`CommandLineFileWriteAnalyzer: ${message}`, args)), + ]; this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); @@ -394,46 +400,14 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } this._logService.info('RunInTerminalTool: autoApprove: File writes detected', fileWrites.map(e => e.toString())); - // Check file write protection - let isFileWriteBlocked = false; - if (fileWrites.length > 0) { - const blockDetectedFileWrites = this._configurationService.getValue(TerminalChatAgentToolsSettingId.BlockDetectedFileWrites); - switch (blockDetectedFileWrites) { - case 'all': { - isFileWriteBlocked = true; - this._logService.info('RunInTerminalTool: autoApprove: File writes blocked due to "all" setting'); - break; - } - case 'outsideWorkspace': { - // Check if any file writes are outside the workspace - const workspaceFolders = this._workspaceContextService.getWorkspace().folders; - if (workspaceFolders.length > 0) { - for (const fileWrite of fileWrites) { - const fileUri = URI.isUri(fileWrite) ? fileWrite : URI.file(fileWrite); - const isInsideWorkspace = workspaceFolders.some(folder => - folder.uri.scheme === fileUri.scheme && - (fileUri.path.startsWith(folder.uri.path + '/') || fileUri.path === folder.uri.path) - ); - if (!isInsideWorkspace) { - isFileWriteBlocked = true; - this._logService.info(`RunInTerminalTool: autoApprove: File write blocked outside workspace: ${fileUri.toString()}`); - break; - } - } - } else { - // No workspace folders, consider all writes as outside workspace - isFileWriteBlocked = true; - this._logService.info('RunInTerminalTool: autoApprove: File writes blocked - no workspace folders'); - } - break; - } - case 'never': - default: { - // Allow all file writes - break; - } - } - } + const commandLineAnalyzerOptions: ICommandLineAnalyzerOptions = { + instance, + commandLine: actualCommand, + os, + shell, + treeSitterLanguage, + }; + const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); if (subCommands) { const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); @@ -483,7 +457,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // Apply auto approval or force it off depending on enablement/opt-in state - if (isAutoApproveEnabled && !isFileWriteBlocked) { + if (isAutoApproveEnabled && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) { autoApproveInfo = this._createAutoApproveInfo( isAutoApproved, isDenied, @@ -493,9 +467,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { ); } else { isAutoApproved = false; - if (isFileWriteBlocked) { - this._logService.info('RunInTerminalTool: autoApprove: Auto approval disabled due to blocked file writes'); - } } // Send telemetry about auto approval process @@ -510,16 +481,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // Add disclaimers for various security concerns const disclaimers: string[] = []; - - // File write warning - if (fileWrites.length > 0) { - const fileWritesList = fileWrites.map(fw => URI.isUri(fw) ? fw.fsPath : fw).join(', '); - if (isFileWriteBlocked) { - disclaimers.push(localize('runInTerminal.fileWriteBlockedDisclaimer', 'File write operations detected that cannot be auto approved: {0}', fileWritesList)); - } else { - disclaimers.push(localize('runInTerminal.fileWriteDisclaimer', 'File write operations detected: {0}', fileWritesList)); - } - } + disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers).flat()); // Prompt injection warning for common commands that return content from the web const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); @@ -544,7 +506,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { if (shellType === 'powershell') { shellType = 'pwsh'; } - confirmationMessages = (isAutoApproved && isAutoApproveAllowed && !isFileWriteBlocked) ? undefined : { + confirmationMessages = (isAutoApproved && isAutoApproveAllowed && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) ? undefined : { title: args.isBackground ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) : localize('runInTerminal', "Run `{0}` command?", shellType), From 637a03c370ae8955bab2552461aeffcdc3bc7350 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:06:17 -0700 Subject: [PATCH 1825/4355] Remove unused code --- .../browser/tools/runInTerminalTool.ts | 23 ------------------- 1 file changed, 23 deletions(-) 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 debafab4b41..7288dedf8a3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -47,9 +47,6 @@ import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from import { OutputMonitor } from './monitoring/outputMonitor.js'; import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; -import { URI } from '../../../../../../base/common/uri.js'; -import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js'; -import { IHistoryService } from '../../../../../services/history/common/history.js'; import { type ICommandLineAnalyzer, type ICommandLineAnalyzerOptions } from './commandLineAnalyzer/commandLineAnalyzer.js'; import { CommandLineFileWriteAnalyzer } from './commandLineAnalyzer/commandLineFileWriteAnalyzer.js'; @@ -287,7 +284,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IHistoryService private readonly _historyService: IHistoryService, @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, @IStorageService private readonly _storageService: IStorageService, @ITerminalLogService private readonly _logService: ITerminalLogService, @@ -295,7 +291,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IChatService private readonly _chatService: IChatService, - @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { super(); @@ -382,24 +377,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._logService.info(`RunInTerminalTool: autoApprove: Failed to parse sub-commands via ${treeSitterLanguage} grammar`); } - let fileWrites: URI[] | string[] = []; - const fileWriteCaptures = await this._treeSitterCommandParser.getFileWrites(treeSitterLanguage, actualCommand); - if (fileWriteCaptures.length) { - let cwd = await instance?.getCwdResource(); - if (!cwd) { - const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); - const workspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined; - cwd = workspaceFolder?.uri; - } - if (cwd) { - fileWrites = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); - } else { - this._logService.info('RunInTerminalTool: autoApprove: Cwd could not be detected'); - fileWrites = fileWriteCaptures.map(e => e.node.text); - } - } - this._logService.info('RunInTerminalTool: autoApprove: File writes detected', fileWrites.map(e => e.toString())); - const commandLineAnalyzerOptions: ICommandLineAnalyzerOptions = { instance, commandLine: actualCommand, From 20ded57d689be354d9072671ac6a4a87888dde03 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:28:46 +0100 Subject: [PATCH 1826/4355] Git - use diff instead of diff-tree for showing commit data and comparing branches (#273969) --- extensions/git/src/commands.ts | 8 ++-- extensions/git/src/decorationProvider.ts | 2 +- extensions/git/src/git.ts | 50 +++++++----------------- extensions/git/src/historyProvider.ts | 2 +- extensions/git/src/repository.ts | 8 +--- 5 files changed, 22 insertions(+), 48 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 096a1b4b8f3..913fc1f2a93 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3145,7 +3145,7 @@ export class CommandCenter { const sourceCommit = sourceRef.ref.commit; try { - const changes = await repository.diffTrees(sourceCommit, historyItem.id); + const changes = await repository.diffBetween2(sourceCommit, historyItem.id); if (changes.length === 0) { window.showInformationMessage(l10n.t('The selected references have no differences.')); @@ -3203,7 +3203,7 @@ export class CommandCenter { } try { - const changes = await repository.diffTrees(ref1.id, ref2.id); + const changes = await repository.diffBetween2(ref1.id, ref2.id); if (changes.length === 0) { window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref1.name, ref2.name)); @@ -4897,7 +4897,7 @@ export class CommandCenter { const commit = await repository.getCommit(item.ref); const commitParentId = commit.parents.length > 0 ? commit.parents[0] : await repository.getEmptyTree(); - const changes = await repository.diffTrees(commitParentId, commit.hash); + const changes = await repository.diffBetween2(commitParentId, commit.hash); const resources = changes.map(c => toMultiFileDiffEditorUris(c, commitParentId, commit.hash)); const title = `${item.shortRef} - ${subject(commit.message)}`; @@ -5171,7 +5171,7 @@ export class CommandCenter { const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItemId}` }); - const changes = await repository.diffTrees(historyItemParentId, historyItemId); + const changes = await repository.diffBetween2(historyItemParentId, historyItemId); const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId)); const reveal = revealUri ? { modifiedUri: toGitUri(revealUri, historyItemId) } : undefined; diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 763d4aaa751..ea4f031e0f9 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -257,7 +257,7 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider return []; } - const changes = await this.repository.diffBetween(ancestor, currentHistoryItemRemoteRef.id); + const changes = await this.repository.diffBetween2(ancestor, currentHistoryItemRemoteRef.id); return changes; } catch (err) { return []; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 98fbc032ef8..2b0e2d321ee 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1620,7 +1620,7 @@ export class Repository { diffWithHEAD(path?: string | undefined): Promise; async diffWithHEAD(path?: string | undefined): Promise { if (!path) { - return await this.diffFiles(false); + return await this.diffFiles(undefined, { cached: false }); } const args = ['diff', '--', this.sanitizeRelativePath(path)]; @@ -1633,7 +1633,7 @@ export class Repository { diffWith(ref: string, path?: string | undefined): Promise; async diffWith(ref: string, path?: string): Promise { if (!path) { - return await this.diffFiles(false, ref); + return await this.diffFiles(ref, { cached: false }); } const args = ['diff', ref, '--', this.sanitizeRelativePath(path)]; @@ -1646,7 +1646,7 @@ export class Repository { diffIndexWithHEAD(path?: string | undefined): Promise; async diffIndexWithHEAD(path?: string): Promise { if (!path) { - return await this.diffFiles(true); + return await this.diffFiles(undefined, { cached: true }); } const args = ['diff', '--cached', '--', this.sanitizeRelativePath(path)]; @@ -1659,7 +1659,7 @@ export class Repository { diffIndexWith(ref: string, path?: string | undefined): Promise; async diffIndexWith(ref: string, path?: string): Promise { if (!path) { - return await this.diffFiles(true, ref); + return await this.diffFiles(ref, { cached: true }); } const args = ['diff', '--cached', ref, '--', this.sanitizeRelativePath(path)]; @@ -1679,7 +1679,7 @@ export class Repository { async diffBetween(ref1: string, ref2: string, path?: string): Promise { const range = `${ref1}...${ref2}`; if (!path) { - return await this.diffFiles(false, range); + return await this.diffFiles(range, { cached: false }); } const args = ['diff', range, '--', this.sanitizeRelativePath(path)]; @@ -1688,46 +1688,24 @@ export class Repository { return result.stdout.trim(); } - async diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { - const args = ['diff', '--shortstat', `${ref1}...${ref2}`]; - - const result = await this.exec(args); - if (result.exitCode) { - return { files: 0, insertions: 0, deletions: 0 }; - } - - return parseGitDiffShortStat(result.stdout.trim()); + async diffBetween2(ref1: string, ref2: string, options: { similarityThreshold?: number; symmetric?: boolean }): Promise { + const range = options.symmetric ? `${ref1}...${ref2}` : `${ref1}..${ref2}`; + return await this.diffFiles(range, { cached: false, similarityThreshold: options.similarityThreshold }); } - private async diffFiles(cached: boolean, ref?: string): Promise { + private async diffFiles(ref: string | undefined, options: { cached: boolean; similarityThreshold?: number }): Promise { const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR']; - if (cached) { - args.push('--cached'); - } - - if (ref) { - args.push(ref); - } - const gitResult = await this.exec(args); - if (gitResult.exitCode) { - return []; + if (options.cached) { + args.push('--cached'); } - return parseGitChanges(this.repositoryRoot, gitResult.stdout); - } - - async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise { - const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR']; - - if (options?.similarityThreshold) { + if (options.similarityThreshold) { args.push(`--find-renames=${options.similarityThreshold}%`); } - args.push(treeish1); - - if (treeish2) { - args.push(treeish2); + if (ref) { + args.push(ref); } args.push('--'); diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index cefdd2eda76..1ed44906ab3 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -339,7 +339,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const historyItemChangesUri: Uri[] = []; const historyItemChanges: SourceControlHistoryItemChange[] = []; - const changes = await this.repository.diffTrees(historyItemParentId, historyItemId); + const changes = await this.repository.diffBetween2(historyItemParentId, historyItemId); for (const change of changes) { const historyItemUri = change.uri.with({ diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6db96628cc7..12516966e3d 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1231,15 +1231,11 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path)); } - diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { - return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2)); - } - - diffTrees(treeish1: string, treeish2?: string): Promise { + diffBetween2(ref1: string, ref2: string): Promise { const scopedConfig = workspace.getConfiguration('git', Uri.file(this.root)); const similarityThreshold = scopedConfig.get('similarityThreshold', 50); - return this.run(Operation.Diff, () => this.repository.diffTrees(treeish1, treeish2, { similarityThreshold })); + return this.run(Operation.Diff, () => this.repository.diffBetween2(ref1, ref2, { similarityThreshold })); } getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { From 0cb2a72fd1b0400bf94b7a4de8e09535a3d65173 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:02:59 -0700 Subject: [PATCH 1827/4355] Use `isEqual` to compare uris --- .../workbench/contrib/chat/browser/chatSessions/common.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 0dcf73a5a2b..02dcf4fde20 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -5,6 +5,7 @@ import { fromNow } from '../../../../../base/common/date.js'; import { Schemas } from '../../../../../base/common/network.js'; +import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; @@ -55,13 +56,9 @@ export function getChatSessionType(editor: ChatEditorInput): string { * Find existing chat editors that have the same session URI (for external providers) */ export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, editorGroupsService: IEditorGroupsService): { editor: ChatEditorInput; groupId: number } | undefined { - if (!sessionUri) { - return undefined; - } - for (const group of editorGroupsService.groups) { for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && (editor.resource.toString() === sessionUri.toString() || editor.sessionId === sessionId)) { + if (editor instanceof ChatEditorInput && (isEqual(editor.resource, sessionUri) || editor.sessionId === sessionId)) { return { editor, groupId: group.id }; } } From f8e2f71c2f78ac1ce63389e761e2aefc724646fc Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:14:03 +0000 Subject: [PATCH 1828/4355] Git - move compare with merge base action into the context menu (#273981) --- extensions/git/package.json | 12 ++++++------ extensions/git/src/historyProvider.ts | 1 - .../contrib/scm/browser/scmHistoryViewPane.ts | 17 ++++++++++++++++- .../contrib/scm/browser/scmViewPane.ts | 1 + 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 96ece71a59f..faa5715d64c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1021,7 +1021,7 @@ "command": "git.graph.compareWithMergeBase", "title": "%command.graphCompareWithMergeBase%", "category": "Git", - "enablement": "!operationInProgress && git.currentHistoryItemHasMergeBase" + "enablement": "!operationInProgress && scmCurrentHistoryItemRefHasBase" } ], "continueEditSession": [ @@ -2270,11 +2270,6 @@ "command": "git.graph.openOutgoingChanges", "when": "scmProvider == git", "group": "1_changes@2" - }, - { - "command": "git.graph.compareWithMergeBase", - "when": "scmProvider == git", - "group": "2_compare@1" } ], "scm/historyItem/context": [ @@ -2303,6 +2298,11 @@ "when": "scmProvider == git", "group": "5_compare@1" }, + { + "command": "git.graph.compareWithMergeBase", + "when": "scmProvider == git && scmHistoryItemHasCurrentHistoryItemRef", + "group": "5_compare@2" + }, { "command": "git.copyCommitId", "when": "scmProvider == git && !listMultiSelection", diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 1ed44906ab3..5c323c6acd6 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -192,7 +192,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec if (this._HEAD?.behind !== this.repository.HEAD?.behind) { commands.executeCommand('setContext', 'git.currentHistoryItemIsBehind', (this.repository.HEAD?.behind ?? 0) > 0); } - commands.executeCommand('setContext', 'git.currentHistoryItemHasMergeBase', this._currentHistoryItemBaseRef !== undefined); this._HEAD = this.repository.HEAD; diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 21a3f1e9ae0..848f02a5185 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -1531,6 +1531,7 @@ export class SCMHistoryViewPane extends ViewPane { private readonly _scmProviderCtx: IContextKey; private readonly _scmCurrentHistoryItemRefHasRemote: IContextKey; + private readonly _scmCurrentHistoryItemRefHasBase: IContextKey; private readonly _scmCurrentHistoryItemRefInFilter: IContextKey; private readonly _contextMenuDisposables = new MutableDisposable(); @@ -1559,6 +1560,7 @@ export class SCMHistoryViewPane extends ViewPane { this._scmProviderCtx = ContextKeys.SCMProvider.bindTo(this.scopedContextKeyService); this._scmCurrentHistoryItemRefHasRemote = ContextKeys.SCMCurrentHistoryItemRefHasRemote.bindTo(this.scopedContextKeyService); + this._scmCurrentHistoryItemRefHasBase = ContextKeys.SCMCurrentHistoryItemRefHasBase.bindTo(this.scopedContextKeyService); this._scmCurrentHistoryItemRefInFilter = ContextKeys.SCMCurrentHistoryItemRefInFilter.bindTo(this.scopedContextKeyService); this._actionRunner = this.instantiationService.createInstance(SCMHistoryViewPaneActionRunner); @@ -1685,6 +1687,11 @@ export class SCMHistoryViewPane extends ViewPane { this._scmCurrentHistoryItemRefHasRemote.set(!!historyProvider.historyItemRemoteRef.read(reader)); })); + // HistoryItemBaseRef changed + reader.store.add(autorun(reader => { + this._scmCurrentHistoryItemRefHasBase.set(!!historyProvider.historyItemBaseRef.read(reader)); + })); + // ViewMode changed reader.store.add(runOnChange(this._treeViewModel.viewMode, async () => { await this._updateChildren(); @@ -1958,6 +1965,10 @@ export class SCMHistoryViewPane extends ViewPane { // HistoryItem this._contextMenuDisposables.value = new DisposableStore(); + const historyProvider = element.repository.provider.historyProvider.get(); + const historyItemRef = historyProvider?.historyItemRef.get(); + const historyItem = element.historyItemViewModel.historyItem; + const historyItemRefMenuItems = MenuRegistry.getMenuItems(MenuId.SCMHistoryItemRefContext).filter(item => isIMenuItem(item)); // If there are any history item references we have to add a submenu item for each orignal action, @@ -2020,9 +2031,13 @@ export class SCMHistoryViewPane extends ViewPane { } } + const contextKeyService = this.scopedContextKeyService.createOverlay([ + ['scmHistoryItemHasCurrentHistoryItemRef', historyItem.references?.find(ref => ref.id === historyItemRef?.id) !== undefined] + ]); + const menuActions = this._menuService.getMenuActions( MenuId.SCMHistoryItemContext, - this.scopedContextKeyService, { + contextKeyService, { arg: element.repository.provider, shouldForwardArgs: true }).filter(group => group[0] !== 'inline'); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 7d2cac7cb02..1b0117826d3 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -986,6 +986,7 @@ export const ContextKeys = { SCMHistoryItemCount: new RawContextKey('scmHistoryItemCount', 0), SCMHistoryViewMode: new RawContextKey('scmHistoryViewMode', ViewMode.List), SCMCurrentHistoryItemRefHasRemote: new RawContextKey('scmCurrentHistoryItemRefHasRemote', false), + SCMCurrentHistoryItemRefHasBase: new RawContextKey('scmCurrentHistoryItemRefHasBase', false), SCMCurrentHistoryItemRefInFilter: new RawContextKey('scmCurrentHistoryItemRefInFilter', false), RepositoryCount: new RawContextKey('scmRepositoryCount', 0), RepositoryVisibilityCount: new RawContextKey('scmRepositoryVisibleCount', 0), From 92e4d87089f2b33da9f2c9f18ab5d21be275a591 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:23:26 -0700 Subject: [PATCH 1829/4355] More clean up to chat session uris - We will use `vscodeChatEditor` for the `new chat editor` flow - Keep `vscode-chat-session` but make it clear it's only for local chat sessions - Get rid of the `target: sessionId` support for chat editors and instead use `vscode-chat-session` uris - Mark `Session.id` as deprecated as the resource should be preferred --- src/vs/base/common/network.ts | 8 ++--- .../browser/mainThreadChatSessions.test.ts | 10 +++--- .../chat/browser/actions/chatActions.ts | 14 +++++--- .../contrib/chat/browser/actions/chatClear.ts | 6 ++-- .../chat/browser/actions/chatMoveActions.ts | 6 ++-- .../browser/actions/chatSessionActions.ts | 7 ++-- .../agentSessions/agentSessionViewModel.ts | 4 +-- .../agentSessions/agentSessionsView.ts | 15 +++------ .../contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/chat/browser/chatEditor.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 32 +++++++++++++------ .../chatSessions/chatSessionTracker.ts | 4 +-- .../chat/browser/chatSessions/common.ts | 2 +- .../chatSessions/localChatSessionsProvider.ts | 4 +-- .../chatSessions/view/sessionsTreeRenderer.ts | 4 +-- .../chatSessions/view/sessionsViewPane.ts | 27 +++------------- .../contrib/chat/browser/chatViewPane.ts | 6 ++-- .../chat/common/chatSessionsService.ts | 3 +- .../workbench/contrib/chat/common/chatUri.ts | 18 +++++++---- .../contrib/chat/common/constants.ts | 2 +- .../browser/agentSessionViewModel.test.ts | 4 +-- 21 files changed, 90 insertions(+), 90 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 3b85c248ef2..bd688cbbf92 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -87,12 +87,8 @@ export namespace Schemas { /** Scheme used for the chat input part */ export const vscodeChatInput = 'chatSessionInput'; - /** - * Scheme for chat session content - * - * @deprecated - * */ - export const vscodeChatSession = 'vscode-chat-session'; + /** Scheme used for local chat session content */ + export const vscodeLocalChatSession = 'vscode-chat-session'; /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 4f4c42818f8..9b27395350e 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -81,7 +81,7 @@ suite('ObservableChatSession', function () { } async function createInitializedSession(sessionContent: any, sessionId = 'test-id'): Promise { - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); const session = new ObservableChatSession(resource, 1, proxy, logService, dialogService); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); await session.initialize(CancellationToken.None); @@ -90,7 +90,7 @@ suite('ObservableChatSession', function () { test('constructor creates session with proper initial state', function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); assert.strictEqual(session.providerHandle, 1); @@ -105,7 +105,7 @@ suite('ObservableChatSession', function () { test('session queues progress before initialization and processes it after', async function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const progress1: IChatProgress = { kind: 'progressMessage', content: { value: 'Hello', isTrusted: false } }; @@ -156,7 +156,7 @@ suite('ObservableChatSession', function () { test('initialization is idempotent and returns same promise', async function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const sessionContent = createSessionContent(); @@ -284,7 +284,7 @@ suite('ObservableChatSession', function () { test('dispose properly cleans up resources and notifies listeners', function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeChatSession}:/test/${sessionId}`); + const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); let disposeEventFired = false; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index bd5264dc834..001b20214c7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -66,6 +66,7 @@ import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; @@ -608,8 +609,10 @@ export function registerChatActions() { })); store.add(picker.onDidTriggerItemButton(async context => { if (context.button === openInEditorButton) { - const options: IChatEditorOptions = { target: { sessionId: context.item.chat.sessionId }, pinned: true }; - editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, ACTIVE_GROUP); + editorService.openEditor({ + resource: LocalChatSessionUri.forSession(context.item.chat.sessionId), + options: { pinned: true } + }, ACTIVE_GROUP); picker.hide(); } else if (context.button === deleteButton) { chatService.removeHistoryEntry(context.item.chat.sessionId); @@ -864,8 +867,11 @@ export function registerChatActions() { })); store.add(picker.onDidTriggerItemButton(async context => { if (context.button === openInEditorButton) { - const options: IChatEditorOptions = { target: { sessionId: context.item.chat.sessionId }, pinned: true }; - editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, ACTIVE_GROUP); + const options: IChatEditorOptions = { pinned: true }; + editorService.openEditor({ + resource: LocalChatSessionUri.forSession(context.item.chat.sessionId), + options, + }, ACTIVE_GROUP); picker.hide(); } else if (context.button === deleteButton) { chatService.removeHistoryEntry(context.item.chat.sessionId); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts index e3a802dbaf5..f33f0bffe35 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts @@ -7,7 +7,7 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; export async function clearChatEditor(accessor: ServicesAccessor, chatEditorInput?: ChatEditorInput): Promise { @@ -19,8 +19,8 @@ export async function clearChatEditor(accessor: ServicesAccessor, chatEditorInpu } if (chatEditorInput instanceof ChatEditorInput) { - const parsedInfo = ChatSessionUri.parse(chatEditorInput.resource); - const resource = parsedInfo?.chatSessionType ? ChatSessionUri.forSession(parsedInfo.chatSessionType, `untitled-${generateUuid()}`) : ChatEditorInput.getNewEditorUri(); + const parsedInfo = LocalChatSessionUri.parse(chatEditorInput.resource); + const resource = parsedInfo?.chatSessionType ? LocalChatSessionUri.forChatSessionTypeAndId(parsedInfo.chatSessionType, `untitled-${generateUuid()}`) : ChatEditorInput.getNewEditorUri(); // A chat editor can only be open in one group const identifier = editorService.findEditors(chatEditorInput.resource)[0]; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 2ee4914c152..74ce6a7775b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -16,6 +16,7 @@ import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../serv import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { ChatEditor, IChatEditorOptions } from '../chatEditor.js'; @@ -133,8 +134,9 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew widget.clear(); await widget.waitForReady(); - const options: IChatEditorOptions = { target: { sessionId }, pinned: true, viewState, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } }; - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + const resource = LocalChatSessionUri.forSession(sessionId); + const options: IChatEditorOptions = { pinned: true, viewState, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } }; + await editorService.openEditor({ resource, options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } async function moveToSidebar(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 381462df4c0..f9f646f5c27 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -31,7 +31,6 @@ import { IChatSessionsService, localChatSessionType } from '../../common/chatSes import { AGENT_SESSIONS_VIEWLET_ID, ChatConfiguration } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; @@ -193,12 +192,11 @@ export class OpenChatSessionInNewWindowAction extends Action2 { return; } else if (isLocalChatSessionItem(context.session)) { const options: IChatEditorOptions = { - target: { sessionId }, ignoreInView: true, }; // For local sessions, create a new chat editor await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), + resource: context.session.resource, options, }, AUX_WINDOW_GROUP); @@ -250,12 +248,11 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { return; } else if (isLocalChatSessionItem(context.session)) { const options: IChatEditorOptions = { - target: { sessionId }, ignoreInView: true, }; // For local sessions, create a new chat editor await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), + resource: context.session.resource, options, }, SIDE_GROUP); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index aa95db33af4..7e21c7a089f 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -13,7 +13,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IChatService } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; //#region Interfaces, Types @@ -172,7 +172,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions for (const history of await this.chatService.getLocalSessionHistory()) { newSessions.push({ id: history.sessionId, - resource: ChatSessionUri.forSession(localChatSessionType, history.sessionId), + resource: LocalChatSessionUri.forSession(history.sessionId), label: history.title, provider: provider, timing: { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index d537e6f3153..ee203cadbf7 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -36,13 +36,11 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { findExistingChatEditorByUri, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { ChatEditorInput } from '../chatEditorInput.js'; import { assertReturnsDefined, upcast } from '../../../../../base/common/types.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; -import { URI } from '../../../../../base/common/uri.js'; import { DeferredPromise } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { MutableDisposable } from '../../../../../base/common/lifecycle.js'; @@ -133,7 +131,7 @@ export class AgentSessionsView extends ViewPane { return; } - if (session.resource.scheme !== ChatSessionUri.scheme) { + if (session.resource.scheme !== LocalChatSessionUri.scheme) { await this.openerService.open(session.resource, { editorOptions: upcast({ ...e.editorOptions, @@ -143,27 +141,24 @@ export class AgentSessionsView extends ViewPane { return; } - const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + const uri = session.resource; const existingSessionEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); if (existingSessionEditor) { await this.editorGroupsService.getGroup(existingSessionEditor.groupId)?.openEditor(existingSessionEditor.editor, e.editorOptions); return; } - let sessionResource: URI; let sessionOptions: IChatEditorOptions; if (isLocalAgentSessionItem(session)) { - sessionResource = ChatEditorInput.getNewEditorUri(); - sessionOptions = { target: { sessionId: session.id } }; + sessionOptions = {}; } else { - sessionResource = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); sessionOptions = { title: { preferred: session.label } }; } sessionOptions.ignoreInView = true; await this.editorService.openEditor({ - resource: sessionResource, + resource: uri, options: upcast({ ...sessionOptions, title: { preferred: session.label }, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d734c4c73f2..14840bd3a12 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -789,7 +789,7 @@ class ChatResolverContribution extends Disposable { super(); this._registerEditor(Schemas.vscodeChatEditor); - this._registerEditor(Schemas.vscodeChatSession); + this._registerEditor(Schemas.vscodeLocalChatSession); this._register(chatSessionsService.onDidChangeContentProviderSchemes((e) => { for (const scheme of e.added) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 6f4e65f355d..6fe04cd1046 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -34,7 +34,7 @@ import { getChatSessionType } from './chatSessions/common.js'; import { ChatWidget, IChatViewState } from './chatWidget.js'; export interface IChatEditorOptions extends IEditorOptions { - target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData }; + target?: { data: IExportableChatData | ISerializableChatData }; title?: { preferred?: string; fallback?: string; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 7da6d2012ff..971a0fc9ad1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -22,6 +22,7 @@ import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditi import { IChatModel } from '../common/chatModel.js'; import { IChatService } from '../common/chatService.js'; import { IChatSessionsService, localChatSessionType } from '../common/chatSessionsService.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; import { ChatAgentLocation, ChatEditorTitleMaxLength } from '../common/constants.js'; import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js'; import type { IChatEditorOptions } from './chatEditor.js'; @@ -46,7 +47,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler static getNewEditorUri(): URI { const handle = Math.floor(Math.random() * 1e9); - return ChatEditorUri.generate(handle); + return ChatEditorUri.forHandle(handle); } static getNextCount(inputName: string): number { @@ -74,9 +75,16 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } } - this.sessionId = (options.target && 'sessionId' in options.target) ? - options.target.sessionId : - undefined; + if (resource.scheme === Schemas.vscodeLocalChatSession) { + const parsed = LocalChatSessionUri.parse(resource); + if (!parsed?.sessionId) { + throw new Error('Invalid chat session URI'); + } + if (parsed.chatSessionType !== localChatSessionType) { + throw new Error('Chat session URI must be of local chat session type'); + } + this.sessionId = parsed.sessionId; + } // Check if we already have a custom title for this session const hasExistingCustomTitle = this.sessionId && ( @@ -230,7 +238,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } public getSessionType(): string { - if (this.resource.scheme === Schemas.vscodeChatEditor || this.resource.scheme === Schemas.vscodeChatSession) { + if (this.resource.scheme === Schemas.vscodeChatEditor || this.resource.scheme === Schemas.vscodeLocalChatSession) { return localChatSessionType; } @@ -241,7 +249,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const searchParams = new URLSearchParams(this.resource.query); const chatSessionType = searchParams.get('chatSessionType'); const inputType = chatSessionType ?? this.resource.authority; - if (this.resource.scheme !== Schemas.vscodeChatEditor) { + if (this.resource.scheme !== Schemas.vscodeChatEditor && this.resource.scheme !== Schemas.vscodeLocalChatSession) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (typeof this.sessionId === 'string') { this.model = await this.chatService.getOrRestoreSession(this.sessionId) @@ -328,7 +336,7 @@ export namespace ChatEditorUri { export const scheme = Schemas.vscodeChatEditor; - export function generate(handle: number): URI { + export function forHandle(handle: number): URI { return URI.from({ scheme, path: `chat-${handle}` }); } @@ -379,8 +387,14 @@ export class ChatEditorInputSerializer implements IEditorSerializer { deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { try { const parsed: ISerializedChatEditorInput = JSON.parse(serializedEditor); - const resource = URI.revive(parsed.resource); - return instantiationService.createInstance(ChatEditorInput, resource, { ...parsed.options, target: { sessionId: parsed.sessionId } }); + + let resource = URI.revive(parsed.resource); + if (resource.scheme === Schemas.vscodeChatEditor) { + // We don't have a sessionId in the URI, so we need to create a new one + resource = LocalChatSessionUri.forSession(parsed.sessionId); + } + + return instantiationService.createInstance(ChatEditorInput, resource, { ...parsed.options }); } catch (err) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index 4c25e20efbb..608b2b65ebe 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -11,7 +11,7 @@ import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/ import { IChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; @@ -102,7 +102,7 @@ export class ChatSessionTracker extends Disposable { } } - const parsed = ChatSessionUri.parse(editor.resource); + const parsed = LocalChatSessionUri.parse(editor.resource); const hybridSession: ChatSessionItemWithProvider = { id: parsed?.sessionId || editor.sessionId || `${provider.chatSessionType}-local-${index}`, resource: editor.resource, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index 0dcf73a5a2b..5a99eae88d3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -33,7 +33,7 @@ export function isChatSession(schemes: readonly string[], editor?: EditorInput): return false; } - if (!schemes.includes(editor.resource?.scheme) && editor.resource?.scheme !== Schemas.vscodeChatSession && editor.resource?.scheme !== Schemas.vscodeChatEditor) { + if (!schemes.includes(editor.resource?.scheme) && editor.resource?.scheme !== Schemas.vscodeLocalChatSession && editor.resource?.scheme !== Schemas.vscodeChatEditor) { return false; } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 67536f29acb..1dd4c570600 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -204,7 +204,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio const status = chatWidget?.viewModel?.model ? this.modelToStatus(chatWidget.viewModel.model) : undefined; const widgetSession: ChatSessionItemWithProvider = { id: LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID, - resource: URI.parse(`${Schemas.vscodeChatSession}://widget`), + resource: URI.parse(`${Schemas.vscodeLocalChatSession}://widget`), label: chatWidget?.viewModel?.model.title || nls.localize2('chat.sessions.chatView', "Chat").value, description: nls.localize('chat.sessions.chatView.description', "Chat View"), iconPath: Codicon.chatSparkle, @@ -254,7 +254,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio // TODO: This should not be a session items const historyNode: IChatSessionItem = { id: LocalChatSessionsProvider.HISTORY_NODE_ID, - resource: URI.parse(`${Schemas.vscodeChatSession}://history`), + resource: URI.parse(`${Schemas.vscodeLocalChatSession}://history`), label: nls.localize('chat.sessions.showHistory', "History"), timing: { startTime: 0 } }; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 85fa5155a13..15b46a879d5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -42,7 +42,7 @@ import { IWorkbenchLayoutService, Position } from '../../../../../services/layou import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; import { IChatService } from '../../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../../common/chatUri.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { IChatWidgetService } from '../../chat.js'; import { allowedChatMarkdownHtmlTags } from '../../chatContentMarkdownRenderer.js'; @@ -582,7 +582,7 @@ export class SessionsDataSource implements IAsyncDataSource ({ id: historyDetail.sessionId, - resource: ChatSessionUri.forSession(this.provider.chatSessionType, historyDetail.sessionId), + resource: LocalChatSessionUri.forSession(historyDetail.sessionId), label: historyDetail.title, iconPath: Codicon.chatSparkle, provider: this.provider, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index dafc2f7378d..02b11a63c96 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -14,7 +14,6 @@ import { Codicon } from '../../../../../../base/common/codicons.js'; import { FuzzyScore } from '../../../../../../base/common/filters.js'; import { MarshalledId } from '../../../../../../base/common/marshallingIds.js'; import { truncate } from '../../../../../../base/common/strings.js'; -import { upcast } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import * as nls from '../../../../../../nls.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; @@ -24,7 +23,6 @@ import { ICommandService } from '../../../../../../platform/commands/common/comm import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; -import { IEditorOptions } from '../../../../../../platform/editor/common/editor.js'; import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; @@ -42,12 +40,10 @@ import { IEditorService } from '../../../../../services/editor/common/editorServ import { IViewsService } from '../../../../../services/views/common/viewsService.js'; import { IChatService } from '../../../common/chatService.js'; import { IChatSessionItemProvider, localChatSessionType } from '../../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../../common/chatUri.js'; import { ChatConfiguration, ChatEditorTitleMaxLength } from '../../../common/constants.js'; import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatViewId, IChatWidgetService } from '../../chat.js'; import { IChatEditorOptions } from '../../chatEditor.js'; -import { ChatEditorInput } from '../../chatEditorInput.js'; import { ChatViewPane } from '../../chatViewPane.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getSessionItemContextOverlay, isLocalChatSessionItem, NEW_CHAT_SESSION_ACTION_ID } from '../common.js'; @@ -57,7 +53,7 @@ import { GettingStartedDelegate, GettingStartedRenderer, IGettingStartedItem, Se // Identity provider for session items class SessionsIdentityProvider { getId(element: ChatSessionItemWithProvider): string { - return element.id; + return element.resource.toString(); } } @@ -68,7 +64,7 @@ class SessionsAccessibilityProvider { } getAriaLabel(element: ChatSessionItemWithProvider): string | null { - return element.label || element.id; + return element.label; } } @@ -463,21 +459,9 @@ export class SessionsViewPane extends ViewPane { return; } - if (session.resource.scheme !== ChatSessionUri.scheme) { - await this.openerService.open(session.resource, { - editorOptions: upcast({ - title: { - preferred: session.label - }, - pinned: true - }) - }); - return; - } - try { // Check first if we already have an open editor for this session - const uri = ChatSessionUri.forSession(session.provider.chatSessionType, session.id); + const uri = session.resource; const existingEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); if (existingEditor) { await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); @@ -495,12 +479,11 @@ export class SessionsViewPane extends ViewPane { // Handle history items first if (isLocalChatSessionItem(session)) { const options: IChatEditorOptions = { - target: { sessionId: session.id }, pinned: true, ignoreInView: true, preserveFocus: true, }; - await this.editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); + await this.editorService.openEditor({ resource: session.resource, options }); return; } else if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; @@ -519,7 +502,7 @@ export class SessionsViewPane extends ViewPane { preserveFocus: true, }; await this.editorService.openEditor({ - resource: ChatSessionUri.forSession(session.provider.chatSessionType, session.id), + resource: session.resource, options, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 18b629b8c55..4facfb1aedb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -36,7 +36,7 @@ import { IChatSessionsExtensionPoint, IChatSessionsService } from '../common/cha import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { ChatWidget, IChatViewState } from './chatWidget.js'; import { ChatViewWelcomeController, IViewWelcomeDelegate } from './viewsWelcome/chatViewWelcomeController.js'; -import { ChatSessionUri } from '../common/chatUri.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; interface IViewPaneState extends IChatViewState { sessionId?: string; @@ -253,8 +253,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } // Handle locking for contributed chat sessions - if (URI.isUri(sessionId) && sessionId.scheme === Schemas.vscodeChatSession) { - const parsed = ChatSessionUri.parse(sessionId); + if (URI.isUri(sessionId) && sessionId.scheme === Schemas.vscodeLocalChatSession) { + const parsed = LocalChatSessionUri.parse(sessionId); if (parsed?.chatSessionType) { await this.chatSessionsService.canResolveChatSession(sessionId); const contributions = this.chatSessionsService.getAllChatSessionContributions(); diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 0fdcbfb03f6..153b85b6d74 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -58,7 +58,8 @@ export interface IChatSessionsExtensionPoint { readonly commands?: IChatSessionCommandContribution[]; } export interface IChatSessionItem { - id: string; // TODO: remove + /** @deprecated Use {@link resource} instead */ + id: string; resource: URI; label: string; iconPath?: ThemeIcon; diff --git a/src/vs/workbench/contrib/chat/common/chatUri.ts b/src/vs/workbench/contrib/chat/common/chatUri.ts index c945748770e..c9d5e4c08e1 100644 --- a/src/vs/workbench/contrib/chat/common/chatUri.ts +++ b/src/vs/workbench/contrib/chat/common/chatUri.ts @@ -6,20 +6,26 @@ import { encodeBase64, VSBuffer, decodeBase64 } from '../../../../base/common/buffer.js'; import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; +import { localChatSessionType } from './chatSessionsService.js'; export type ChatSessionIdentifier = { readonly chatSessionType: string; readonly sessionId: string; }; -/** - * @deprecated - */ -export namespace ChatSessionUri { - export const scheme = Schemas.vscodeChatSession; +export namespace LocalChatSessionUri { - export function forSession(chatSessionType: string, sessionId: string): URI { + export const scheme = Schemas.vscodeLocalChatSession; + + export function forSession(sessionId: string): URI { + return forChatSessionTypeAndId(localChatSessionType, sessionId); + } + + /** + * @deprecated Does not support non-local sessions + */ + export function forChatSessionTypeAndId(chatSessionType: string, sessionId: string): URI { const encodedId = encodeBase64(VSBuffer.wrap(new TextEncoder().encode(sessionId)), false, true); // TODO: Do we need to encode the authority too? return URI.from({ scheme, authority: chatSessionType, path: '/' + encodedId }); diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 622818e9840..15f60a21ccd 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -95,7 +95,7 @@ export namespace ChatAgentLocation { const chatAlwaysUnsupportedFileSchemes = new Set([ Schemas.vscodeChatEditor, Schemas.walkThrough, - Schemas.vscodeChatSession, + Schemas.vscodeLocalChatSession, Schemas.vscodeSettings, Schemas.webviewPanel, 'ccreq', diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts index d2cee10b748..df0edc8dffc 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessionViewModel.test.ts @@ -12,7 +12,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { AgentSessionsViewModel, IAgentSessionViewModel, isAgentSession, isAgentSessionsViewModel, isLocalAgentSessionItem } from '../../browser/agentSessions/agentSessionViewModel.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js'; -import { ChatSessionUri } from '../../common/chatUri.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { MockChatService } from '../common/mockChatService.js'; import { MockChatSessionsService } from '../common/mockChatSessionsService.js'; @@ -598,7 +598,7 @@ suite('AgentSessionsViewModel', () => { provideChatSessionItems: async () => [ { id: 'local-session', - resource: ChatSessionUri.forSession(localChatSessionType, 'local-session'), + resource: LocalChatSessionUri.forSession('local-session'), label: 'Local Session', timing: { startTime: Date.now() } } From 6bd66f43003e01bd46288449adf56f3c667ca31e Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:25:39 -0700 Subject: [PATCH 1830/4355] reuse helper --- .../api/test/browser/mainThreadChatSessions.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts index 9b27395350e..752b365b4e5 100644 --- a/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts @@ -7,7 +7,6 @@ import assert from 'assert'; import * as sinon from 'sinon'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -21,6 +20,7 @@ import { ChatSessionsService } from '../../../contrib/chat/browser/chatSessions. import { IChatAgentRequest } from '../../../contrib/chat/common/chatAgents.js'; import { IChatProgress, IChatProgressMessage } from '../../../contrib/chat/common/chatService.js'; import { IChatSessionItem, IChatSessionsService } from '../../../contrib/chat/common/chatSessionsService.js'; +import { LocalChatSessionUri } from '../../../contrib/chat/common/chatUri.js'; import { ChatAgentLocation } from '../../../contrib/chat/common/constants.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js'; @@ -81,7 +81,7 @@ suite('ObservableChatSession', function () { } async function createInitializedSession(sessionContent: any, sessionId = 'test-id'): Promise { - const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); + const resource = LocalChatSessionUri.forSession(sessionId); const session = new ObservableChatSession(resource, 1, proxy, logService, dialogService); (proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent); await session.initialize(CancellationToken.None); @@ -90,7 +90,7 @@ suite('ObservableChatSession', function () { test('constructor creates session with proper initial state', function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); + const resource = LocalChatSessionUri.forSession(sessionId); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); assert.strictEqual(session.providerHandle, 1); @@ -105,7 +105,7 @@ suite('ObservableChatSession', function () { test('session queues progress before initialization and processes it after', async function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); + const resource = LocalChatSessionUri.forSession(sessionId); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const progress1: IChatProgress = { kind: 'progressMessage', content: { value: 'Hello', isTrusted: false } }; @@ -156,7 +156,7 @@ suite('ObservableChatSession', function () { test('initialization is idempotent and returns same promise', async function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); + const resource = LocalChatSessionUri.forSession(sessionId); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); const sessionContent = createSessionContent(); @@ -284,7 +284,7 @@ suite('ObservableChatSession', function () { test('dispose properly cleans up resources and notifies listeners', function () { const sessionId = 'test-id'; - const resource = URI.parse(`${Schemas.vscodeLocalChatSession}:/test/${sessionId}`); + const resource = LocalChatSessionUri.forSession(sessionId); const session = disposables.add(new ObservableChatSession(resource, 1, proxy, logService, dialogService)); let disposeEventFired = false; From b7b11cf3d158bc3bbe5388b1719fbef66278faa8 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:42:15 -0700 Subject: [PATCH 1831/4355] Use config to disable rule in tests --- .eslint-plugin-local/code-no-dangerous-type-assertions.ts | 5 ----- eslint.config.js | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.eslint-plugin-local/code-no-dangerous-type-assertions.ts b/.eslint-plugin-local/code-no-dangerous-type-assertions.ts index f900d778a94..6c0fa26ca1a 100644 --- a/.eslint-plugin-local/code-no-dangerous-type-assertions.ts +++ b/.eslint-plugin-local/code-no-dangerous-type-assertions.ts @@ -9,11 +9,6 @@ import { TSESTree } from '@typescript-eslint/utils'; export = new class NoDangerousTypeAssertions implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - // Disable in tests for now - if (context.getFilename().includes('.test')) { - return {}; - } - return { // Disallow type assertions on object literals: { ... } or {} as T ['TSTypeAssertion > ObjectExpression, TSAsExpression > ObjectExpression']: (node: any) => { diff --git a/eslint.config.js b/eslint.config.js index bb16343dba1..44e229046e7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1108,6 +1108,7 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'local/code-no-dangerous-type-assertions': 'off', 'local/code-must-use-super-dispose': 'off', 'local/code-no-test-only': 'error', 'local/code-no-test-async-suite': 'warn', From c47a56c7bc2f36b2a11cebbca42c6820cddeefc4 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:53:47 -0700 Subject: [PATCH 1832/4355] Adopt uuids for generating webview nonces --- .../src/preview/documentRenderer.ts | 5 +- .../src/util/dom.ts | 8 --- .../src/util/uuid.ts | 58 +++++++++++++++++++ extensions/media-preview/package-lock.json | 20 +++++++ extensions/media-preview/package.json | 3 + extensions/media-preview/src/audioPreview.ts | 5 +- .../media-preview/src/imagePreview/index.ts | 5 +- extensions/media-preview/src/util/dom.ts | 9 --- extensions/media-preview/src/util/uuid.ts | 58 +++++++++++++++++++ extensions/media-preview/src/videoPreview.ts | 5 +- .../mermaid-chat-features/src/extension.ts | 12 +--- extensions/mermaid-chat-features/src/uuid.ts | 58 +++++++++++++++++++ extensions/simple-browser/package-lock.json | 18 ++++++ extensions/simple-browser/package.json | 1 + .../simple-browser/src/simpleBrowserView.ts | 12 +--- extensions/simple-browser/src/uuid.ts | 58 +++++++++++++++++++ extensions/simple-browser/tsconfig.json | 1 - 17 files changed, 291 insertions(+), 45 deletions(-) create mode 100644 extensions/markdown-language-features/src/util/uuid.ts create mode 100644 extensions/media-preview/src/util/uuid.ts create mode 100644 extensions/mermaid-chat-features/src/uuid.ts create mode 100644 extensions/simple-browser/src/uuid.ts diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts index c7bc75b9849..61182a24436 100644 --- a/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -8,8 +8,9 @@ import * as uri from 'vscode-uri'; import { ILogger } from '../logging'; import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; -import { escapeAttribute, getNonce } from '../util/dom'; +import { escapeAttribute } from '../util/dom'; import { WebviewResourceProvider } from '../util/resources'; +import { generateUuid } from '../util/uuid'; import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig'; import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from './security'; @@ -82,7 +83,7 @@ export class MdDocumentRenderer { this._logger.trace('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData); // Content Security Policy - const nonce = getNonce(); + const nonce = generateUuid(); const csp = this._getCsp(resourceProvider, sourceUri, nonce); const body = await this.renderBody(markdownDocument, resourceProvider); diff --git a/extensions/markdown-language-features/src/util/dom.ts b/extensions/markdown-language-features/src/util/dom.ts index 8bbce79c303..16c825c68ff 100644 --- a/extensions/markdown-language-features/src/util/dom.ts +++ b/extensions/markdown-language-features/src/util/dom.ts @@ -11,11 +11,3 @@ export function escapeAttribute(value: string | vscode.Uri): string { .replace(/'/g, '''); } -export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/markdown-language-features/src/util/uuid.ts b/extensions/markdown-language-features/src/util/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/extensions/markdown-language-features/src/util/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/media-preview/package-lock.json b/extensions/media-preview/package-lock.json index d26855f3ad2..fcd827cb0c3 100644 --- a/extensions/media-preview/package-lock.json +++ b/extensions/media-preview/package-lock.json @@ -12,6 +12,9 @@ "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, + "devDependencies": { + "@types/node": "22.x" + }, "engines": { "vscode": "^1.70.0" } @@ -140,6 +143,16 @@ "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", + "integrity": "sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", @@ -154,6 +167,13 @@ "vscode": "^1.75.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index 02b0134e4cf..18cc50bfb3d 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -163,6 +163,9 @@ "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, + "devDependencies": { + "@types/node": "22.x" + }, "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" diff --git a/extensions/media-preview/src/audioPreview.ts b/extensions/media-preview/src/audioPreview.ts index 5058f7e978e..282d579b380 100644 --- a/extensions/media-preview/src/audioPreview.ts +++ b/extensions/media-preview/src/audioPreview.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { MediaPreview, reopenAsText } from './mediaPreview'; -import { escapeAttribute, getNonce } from './util/dom'; +import { escapeAttribute } from './util/dom'; +import { generateUuid } from './util/uuid'; class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider { @@ -57,7 +58,7 @@ class AudioPreview extends MediaPreview { src: await this.getResourcePath(this._webviewEditor, this._resource, version), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/media-preview/src/imagePreview/index.ts b/extensions/media-preview/src/imagePreview/index.ts index b405cd652c4..6c2c8a73f66 100644 --- a/extensions/media-preview/src/imagePreview/index.ts +++ b/extensions/media-preview/src/imagePreview/index.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from '../binarySizeStatusBarEntry'; import { MediaPreview, PreviewState, reopenAsText } from '../mediaPreview'; -import { escapeAttribute, getNonce } from '../util/dom'; +import { escapeAttribute } from '../util/dom'; +import { generateUuid } from '../util/uuid'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; @@ -184,7 +185,7 @@ class ImagePreview extends MediaPreview { src: await this.getResourcePath(this._webviewEditor, this._resource, version), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/media-preview/src/util/dom.ts b/extensions/media-preview/src/util/dom.ts index 0f6c00da9da..f89d668c74d 100644 --- a/extensions/media-preview/src/util/dom.ts +++ b/extensions/media-preview/src/util/dom.ts @@ -7,12 +7,3 @@ import * as vscode from 'vscode'; export function escapeAttribute(value: string | vscode.Uri): string { return value.toString().replace(/"/g, '"'); } - -export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/media-preview/src/util/uuid.ts b/extensions/media-preview/src/util/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/extensions/media-preview/src/util/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/media-preview/src/videoPreview.ts b/extensions/media-preview/src/videoPreview.ts index 67012128cf7..1cb74c58426 100644 --- a/extensions/media-preview/src/videoPreview.ts +++ b/extensions/media-preview/src/videoPreview.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { MediaPreview, reopenAsText } from './mediaPreview'; -import { escapeAttribute, getNonce } from './util/dom'; +import { escapeAttribute } from './util/dom'; +import { generateUuid } from './util/uuid'; class VideoPreviewProvider implements vscode.CustomReadonlyEditorProvider { @@ -61,7 +62,7 @@ class VideoPreview extends MediaPreview { loop: configurations.get('loop'), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/mermaid-chat-features/src/extension.ts b/extensions/mermaid-chat-features/src/extension.ts index d66518a8db3..51294649f4f 100644 --- a/extensions/mermaid-chat-features/src/extension.ts +++ b/extensions/mermaid-chat-features/src/extension.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { generateUuid } from './uuid'; /** * View type that uniquely identifies the Mermaid chat output renderer. @@ -42,7 +43,7 @@ export function activate(context: vscode.ExtensionContext) { }; // Set the HTML content for the webview - const nonce = getNonce(); + const nonce = generateUuid(); const mermaidScript = vscode.Uri.joinPath(mediaRoot, 'index.js'); webview.html = ` @@ -96,11 +97,4 @@ function escapeHtmlText(str: string): string { .replace(/'/g, '''); } -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} + diff --git a/extensions/mermaid-chat-features/src/uuid.ts b/extensions/mermaid-chat-features/src/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/extensions/mermaid-chat-features/src/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/simple-browser/package-lock.json b/extensions/simple-browser/package-lock.json index c6d9b23636a..8aa3894ba1e 100644 --- a/extensions/simple-browser/package-lock.json +++ b/extensions/simple-browser/package-lock.json @@ -12,6 +12,7 @@ "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { + "@types/node": "22.x", "@types/vscode-webview": "^1.57.0", "@vscode/codicons": "^0.0.36" }, @@ -143,6 +144,16 @@ "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", + "integrity": "sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/vscode-webview": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.0.tgz", @@ -169,6 +180,13 @@ "engines": { "vscode": "^1.75.0" } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 5e081c4bbd2..6dd737b08f5 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -69,6 +69,7 @@ "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { + "@types/node": "22.x", "@types/vscode-webview": "^1.57.0", "@vscode/codicons": "^0.0.36" }, diff --git a/extensions/simple-browser/src/simpleBrowserView.ts b/extensions/simple-browser/src/simpleBrowserView.ts index 5725dcf4f9b..56c5aff5c8a 100644 --- a/extensions/simple-browser/src/simpleBrowserView.ts +++ b/extensions/simple-browser/src/simpleBrowserView.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { Disposable } from './dispose'; +import { generateUuid } from './uuid'; export interface ShowOptions { @@ -112,7 +113,7 @@ export class SimpleBrowserView extends Disposable { private getHtml(url: string) { const configuration = vscode.workspace.getConfiguration('simpleBrowser'); - const nonce = getNonce(); + const nonce = generateUuid(); const mainJs = this.extensionResourceUrl('media', 'index.js'); const mainCss = this.extensionResourceUrl('media', 'main.css'); @@ -181,12 +182,3 @@ export class SimpleBrowserView extends Disposable { function escapeAttribute(value: string | vscode.Uri): string { return value.toString().replace(/"/g, '"'); } - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/simple-browser/src/uuid.ts b/extensions/simple-browser/src/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/extensions/simple-browser/src/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/simple-browser/tsconfig.json b/extensions/simple-browser/tsconfig.json index 60b13d71670..43ed762ce7d 100644 --- a/extensions/simple-browser/tsconfig.json +++ b/extensions/simple-browser/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [], "typeRoots": [ "./node_modules/@types" ] From 38a44e89ce01885045aeb5b00b0389c1cc6509de Mon Sep 17 00:00:00 2001 From: Kyle Cutler <67761731+kycutler@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:00:10 -0700 Subject: [PATCH 1833/4355] Support GitHub-style alert syntax in MarkdownStrings (#272228) * Support GitHub-style alerts in MarkdownStrings * PR feedback, mark internal * Rename to `supportAlertSyntax` * Update snapshots * Fix lower case tags * Remove title="" from span * Marked extension * New theme variables * Fix test * Update variable names --- .../lib/stylelint/vscode-known-variables.json | 5 ++ src/vs/base/browser/markdownRenderer.ts | 63 ++++++++++++++++++- src/vs/base/common/htmlContent.ts | 11 +++- .../test/browser/markdownRenderer.test.ts | 30 +++++++++ .../common/extensionsApiProposals.ts | 3 + .../api/common/extHostTypeConverters.ts | 3 +- .../api/common/extHostTypes/markdownString.ts | 8 +++ .../Response_inline_reference.0.snap | 6 +- .../Response_not_mergeable_markdown.0.snap | 6 +- .../comments/browser/commentThreadWidget.ts | 7 +-- .../contrib/comments/browser/media/review.css | 5 +- .../markdown/browser/markdown.contribution.ts | 7 +++ .../markdown/browser/media/markdown.css | 38 +++++++++++ .../contrib/markdown/common/markdownColors.ts | 32 ++++++++++ src/vs/workbench/workbench.common.main.ts | 3 + .../vscode.proposed.markdownAlertSyntax.d.ts | 29 +++++++++ 16 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/markdown/browser/markdown.contribution.ts create mode 100644 src/vs/workbench/contrib/markdown/browser/media/markdown.css create mode 100644 src/vs/workbench/contrib/markdown/common/markdownColors.ts create mode 100644 src/vscode-dts/vscode.proposed.markdownAlertSyntax.d.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 8a63b6fe995..b56584abc8a 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -464,6 +464,11 @@ "--vscode-listFilterWidget-noMatchesOutline", "--vscode-listFilterWidget-outline", "--vscode-listFilterWidget-shadow", + "--vscode-markdownAlert-caution-foreground", + "--vscode-markdownAlert-important-foreground", + "--vscode-markdownAlert-note-foreground", + "--vscode-markdownAlert-tip-foreground", + "--vscode-markdownAlert-warning-foreground", "--vscode-mcpIcon-starForeground", "--vscode-menu-background", "--vscode-menu-border", diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index cd244b65259..b4eda9a8cb5 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -22,7 +22,7 @@ import * as domSanitize from './domSanitize.js'; import { convertTagToPlaintext } from './domSanitize.js'; import { StandardKeyboardEvent } from './keyboardEvent.js'; import { StandardMouseEvent } from './mouseEvent.js'; -import { renderLabelWithIcons } from './ui/iconLabel/iconLabels.js'; +import { renderIcon, renderLabelWithIcons } from './ui/iconLabel/iconLabels.js'; export type MarkdownActionHandler = (linkContent: string, mdStr: IMarkdownString) => void; @@ -115,6 +115,62 @@ const defaultMarkedRenderers = Object.freeze({ }, }); +/** + * Blockquote renderer that processes GitHub-style alert syntax. + * Transforms blockquotes like "> [!NOTE]" into structured alert markup with icons. + * + * Based on GitHub's alert syntax: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts + */ +function createAlertBlockquoteRenderer(fallbackRenderer: (this: marked.Renderer, token: marked.Tokens.Blockquote) => string) { + return function (this: marked.Renderer, token: marked.Tokens.Blockquote): string { + const { tokens } = token; + // Check if this blockquote starts with alert syntax [!TYPE] + const firstToken = tokens[0]; + if (firstToken?.type !== 'paragraph') { + return fallbackRenderer.call(this, token); + } + + const paragraphTokens = firstToken.tokens; + if (!paragraphTokens || paragraphTokens.length === 0) { + return fallbackRenderer.call(this, token); + } + + const firstTextToken = paragraphTokens[0]; + if (firstTextToken?.type !== 'text') { + return fallbackRenderer.call(this, token); + } + + const pattern = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*?\n*/i; + const match = firstTextToken.raw.match(pattern); + if (!match) { + return fallbackRenderer.call(this, token); + } + + // Remove the alert marker from the token + firstTextToken.raw = firstTextToken.raw.replace(pattern, ''); + firstTextToken.text = firstTextToken.text.replace(pattern, ''); + + const alertIcons: Record = { + 'note': 'info', + 'tip': 'light-bulb', + 'important': 'comment', + 'warning': 'alert', + 'caution': 'stop' + }; + + const type = match[1]; + const typeCapitalized = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); + const severity = type.toLowerCase(); + const iconHtml = renderIcon({ id: alertIcons[severity] }).outerHTML; + + // Render the remaining content + const content = this.parser.parse(tokens); + + // Return alert markup with icon and severity (skipping the first 3 characters: `

    `) + return `

    ${iconHtml}${typeCapitalized}${content.substring(3)}

    \n`; + }; +} + export interface IRenderedMarkdown extends IDisposable { readonly element: HTMLElement; } @@ -300,6 +356,10 @@ function createMarkdownRenderer(marked: marked.Marked, options: MarkdownRenderOp renderer.link = defaultMarkedRenderers.link; renderer.paragraph = defaultMarkedRenderers.paragraph; + if (markdown.supportAlertSyntax) { + renderer.blockquote = createAlertBlockquoteRenderer(renderer.blockquote); + } + // Will collect [id, renderedElement] tuples const codeBlocks: Promise<[string, HTMLElement]>[] = []; const syncCodeBlocks: [string, HTMLElement][] = []; @@ -493,6 +553,7 @@ export const allowedMarkdownHtmlAttributes = Object.freezething).value === 'string' && (typeof (thing).isTrusted === 'boolean' || typeof (thing).isTrusted === 'object' || (thing).isTrusted === undefined) - && (typeof (thing).supportThemeIcons === 'boolean' || (thing).supportThemeIcons === undefined); + && (typeof (thing).supportThemeIcons === 'boolean' || (thing).supportThemeIcons === undefined) + && (typeof (thing).supportAlertSyntax === 'boolean' || (thing).supportAlertSyntax === undefined); } return false; } @@ -139,6 +145,7 @@ export function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boo && a.isTrusted === b.isTrusted && a.supportThemeIcons === b.supportThemeIcons && a.supportHtml === b.supportHtml + && a.supportAlertSyntax === b.supportAlertSyntax && (a.baseUri === b.baseUri || !!a.baseUri && !!b.baseUri && isEqual(URI.from(a.baseUri), URI.from(b.baseUri))); } } diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 693a957f29a..cdfbe914fa9 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -220,6 +220,36 @@ suite('MarkdownRenderer', () => { }); }); + suite('Alerts', () => { + test('Should render alert with data-severity attribute and icon', () => { + const markdown = new MarkdownString('> [!NOTE]\n> This is a note alert', { supportAlertSyntax: true }); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote[data-severity="note"]'); + assert.ok(blockquote, 'Should have blockquote with data-severity="note"'); + assert.ok(result.innerHTML.includes('This is a note alert'), 'Should contain alert text'); + assert.ok(result.innerHTML.includes('codicon-info'), 'Should contain info icon'); + }); + + test('Should render regular blockquote when supportAlertSyntax is disabled', () => { + const markdown = new MarkdownString('> [!NOTE]\n> This should be a regular blockquote'); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote'); + assert.ok(blockquote, 'Should have blockquote'); + assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute'); + assert.ok(result.innerHTML.includes('[!NOTE]'), 'Should contain literal [!NOTE] text'); + }); + + test('Should not transform blockquotes without alert syntax', () => { + const markdown = new MarkdownString('> This is a regular blockquote', { supportAlertSyntax: true }); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote'); + assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute'); + }); + }); + test('npm Hover Run Script not working #90855', function () { const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}'); diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 5fa66e7d9b2..ae85a2fadfb 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -271,6 +271,9 @@ const _allApiProposals = { mappedEditsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', }, + markdownAlertSyntax: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.markdownAlertSyntax.d.ts', + }, mcpToolDefinitions: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mcpToolDefinitions.d.ts', }, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a8532f4a749..c670cd47375 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -369,7 +369,7 @@ export namespace MarkdownString { const { language, value } = markup; res = { value: '```' + language + '\n' + value + '\n```\n' }; } else if (types.MarkdownString.isMarkdownString(markup)) { - res = { value: markup.value, isTrusted: markup.isTrusted, supportThemeIcons: markup.supportThemeIcons, supportHtml: markup.supportHtml, baseUri: markup.baseUri }; + res = { value: markup.value, isTrusted: markup.isTrusted, supportThemeIcons: markup.supportThemeIcons, supportHtml: markup.supportHtml, supportAlertSyntax: markup.supportAlertSyntax, baseUri: markup.baseUri }; } else if (typeof markup === 'string') { res = { value: markup }; } else { @@ -440,6 +440,7 @@ export namespace MarkdownString { const result = new types.MarkdownString(value.value, value.supportThemeIcons); result.isTrusted = value.isTrusted; result.supportHtml = value.supportHtml; + result.supportAlertSyntax = value.supportAlertSyntax; result.baseUri = value.baseUri ? URI.from(value.baseUri) : undefined; return result; } diff --git a/src/vs/workbench/api/common/extHostTypes/markdownString.ts b/src/vs/workbench/api/common/extHostTypes/markdownString.ts index 7a364bf1a35..b0097e834a4 100644 --- a/src/vs/workbench/api/common/extHostTypes/markdownString.ts +++ b/src/vs/workbench/api/common/extHostTypes/markdownString.ts @@ -56,6 +56,14 @@ export class MarkdownString implements vscode.MarkdownString { this.#delegate.supportHtml = value; } + get supportAlertSyntax(): boolean | undefined { + return this.#delegate.supportAlertSyntax; + } + + set supportAlertSyntax(value: boolean | undefined) { + this.#delegate.supportAlertSyntax = value; + } + get baseUri(): vscode.Uri | undefined { return this.#delegate.baseUri; } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap index 3a54719571d..d07bec48778 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap @@ -4,7 +4,8 @@ value: "text before ", isTrusted: false, supportThemeIcons: false, - supportHtml: false + supportHtml: false, + supportAlertSyntax: false }, kind: "markdownContent" }, @@ -17,7 +18,8 @@ value: " text after", isTrusted: false, supportThemeIcons: false, - supportHtml: false + supportHtml: false, + supportAlertSyntax: false }, kind: "markdownContent" } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_not_mergeable_markdown.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_not_mergeable_markdown.0.snap index 05c671ef5aa..b2438693d3e 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_not_mergeable_markdown.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_not_mergeable_markdown.0.snap @@ -4,7 +4,8 @@ value: "markdown1", isTrusted: false, supportThemeIcons: false, - supportHtml: true + supportHtml: true, + supportAlertSyntax: false }, kind: "markdownContent" }, @@ -13,7 +14,8 @@ value: "markdown2", isTrusted: false, supportThemeIcons: false, - supportHtml: false + supportHtml: false, + supportAlertSyntax: false }, kind: "markdownContent" } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index d60094c6f00..dc1b70d06b2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -22,7 +22,7 @@ import { CommentThreadAdditionalActions } from './commentThreadAdditionalActions import { CommentContextKeys } from '../common/commentContextKeys.js'; import { ICommentThreadWidget } from '../common/commentThreadWidget.js'; import { IColorTheme } from '../../../../platform/theme/common/themeService.js'; -import { contrastBorder, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { contrastBorder, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js'; import { PANEL_BORDER } from '../../../common/theme.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar } from './commentColors.js'; @@ -409,11 +409,6 @@ export class CommentThreadWidget extends content.push(`.review-widget .body .review-comment blockquote { background: ${blockQuoteBackground}; }`); } - const blockQuoteBOrder = theme.getColor(textBlockQuoteBorder); - if (blockQuoteBOrder) { - content.push(`.review-widget .body .review-comment blockquote { border-color: ${blockQuoteBOrder}; }`); - } - const border = theme.getColor(PANEL_BORDER); if (border) { content.push(`.review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label { border-color: ${border}; }`); diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 19e5fd127cd..d09628d47d4 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -68,10 +68,11 @@ } .review-widget .body .comment-body blockquote { - margin: 0 7px 0 5px; - padding: 0 16px 0 10px; + margin: 8px 7px 8px 5px; + padding: 2px 16px 2px 10px; border-left-width: 5px; border-left-style: solid; + border-left-color: var(--vscode-textBlockQuote-border); } .review-widget .body .review-comment .avatar-container { diff --git a/src/vs/workbench/contrib/markdown/browser/markdown.contribution.ts b/src/vs/workbench/contrib/markdown/browser/markdown.contribution.ts new file mode 100644 index 00000000000..b4ec74b4b50 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/browser/markdown.contribution.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import '../common/markdownColors.js'; +import './media/markdown.css'; diff --git a/src/vs/workbench/contrib/markdown/browser/media/markdown.css b/src/vs/workbench/contrib/markdown/browser/media/markdown.css new file mode 100644 index 00000000000..64408d113e8 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/browser/media/markdown.css @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Alert color mappings using theme variables */ +blockquote[data-severity="note"] { + --vscode-textBlockQuote-border: var(--vscode-markdownAlert-note-foreground); +} + +blockquote[data-severity="tip"] { + --vscode-textBlockQuote-border: var(--vscode-markdownAlert-tip-foreground); +} + +blockquote[data-severity="important"] { + --vscode-textBlockQuote-border: var(--vscode-markdownAlert-important-foreground); +} + +blockquote[data-severity="warning"] { + --vscode-textBlockQuote-border: var(--vscode-markdownAlert-warning-foreground); +} + +blockquote[data-severity="caution"] { + --vscode-textBlockQuote-border: var(--vscode-markdownAlert-caution-foreground); +} + +/* Alert title styling */ +blockquote[data-severity] > p > :first-child { + display: inline-flex; + align-items: center; + color: var(--vscode-textBlockQuote-border); + font-weight: bolder; +} + +blockquote[data-severity] > p > :first-child .codicon { + color: var(--vscode-textBlockQuote-border); + padding-right: 6px; +} diff --git a/src/vs/workbench/contrib/markdown/common/markdownColors.ts b/src/vs/workbench/contrib/markdown/common/markdownColors.ts new file mode 100644 index 00000000000..32f45f454b4 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/common/markdownColors.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from '../../../../nls.js'; +import { registerColor, editorInfoForeground, editorWarningForeground, editorErrorForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { chartsGreen, chartsPurple } from '../../../../platform/theme/common/colors/chartsColors.js'; + +/* + * Markdown alert colors for GitHub-style alert syntax. + */ + +export const markdownAlertNoteColor = registerColor('markdownAlert.note.foreground', + editorInfoForeground, + localize('markdownAlertNoteForeground', "Foreground color for note alerts in markdown.")); + +export const markdownAlertTipColor = registerColor('markdownAlert.tip.foreground', + chartsGreen, + localize('markdownAlertTipForeground', "Foreground color for tip alerts in markdown.")); + +export const markdownAlertImportantColor = registerColor('markdownAlert.important.foreground', + chartsPurple, + localize('markdownAlertImportantForeground', "Foreground color for important alerts in markdown.")); + +export const markdownAlertWarningColor = registerColor('markdownAlert.warning.foreground', + editorWarningForeground, + localize('markdownAlertWarningForeground', "Foreground color for warning alerts in markdown.")); + +export const markdownAlertCautionColor = registerColor('markdownAlert.caution.foreground', + editorErrorForeground, + localize('markdownAlertCautionForeground', "Foreground color for caution alerts in markdown.")); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f7a38101ec9..f5a7b9ee7f1 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -311,6 +311,9 @@ import './contrib/emmet/browser/emmet.contribution.js'; // CodeEditor Contributions import './contrib/codeEditor/browser/codeEditor.contribution.js'; +// Markdown +import './contrib/markdown/browser/markdown.contribution.js'; + // Keybindings Contributions import './contrib/keybindings/browser/keybindings.contribution.js'; diff --git a/src/vscode-dts/vscode.proposed.markdownAlertSyntax.d.ts b/src/vscode-dts/vscode.proposed.markdownAlertSyntax.d.ts new file mode 100644 index 00000000000..bb02da446f0 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.markdownAlertSyntax.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // @kycutler https://github.com/microsoft/vscode/issues/209652 + + export interface MarkdownString { + + /** + * Indicates that this markdown string can contain alert syntax. Defaults to `false`. + * + * When `supportAlertSyntax` is true, the markdown renderer will parse GitHub-style alert syntax: + * + * ```markdown + * > [!NOTE] + * > This is a note alert + * + * > [!WARNING] + * > This is a warning alert + * ``` + * + * Supported alert types: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`. + */ + supportAlertSyntax?: boolean; + } +} From 80be1e27954525863e76ddfc89ee09b3f22a722f Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Wed, 29 Oct 2025 14:21:56 -0700 Subject: [PATCH 1834/4355] Support case-insensitive glob (#273837) * Support case-insensitive glob * PR feedback * PR feedback --- src/vs/base/common/glob.ts | 96 ++++++++++++++++--------- src/vs/base/common/strings.ts | 16 +++-- src/vs/base/test/common/glob.test.ts | 75 ++++++++++++------- src/vs/base/test/common/strings.test.ts | 43 ++++++++++- 4 files changed, 164 insertions(+), 66 deletions(-) diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 9914ab91e43..8057d46ffdc 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -10,7 +10,7 @@ import { isEqualOrParent } from './extpath.js'; import { LRUCache } from './map.js'; import { basename, extname, posix, sep } from './path.js'; import { isLinux } from './platform.js'; -import { escapeRegExpCharacters, ltrim } from './strings.js'; +import { endsWithIgnoreCase, equalsIgnoreCase, escapeRegExpCharacters, ltrim } from './strings.js'; export interface IRelativePattern { @@ -270,7 +270,7 @@ export type ParsedPattern = (path: string, basename?: string) => boolean; // iff `hasSibling` returns a `Promise`. export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise) => string | null | Promise /* the matching pattern */; -interface IGlobOptions { +export interface IGlobOptions { /** * Simplify patterns for use as exclusion filters during @@ -278,6 +278,17 @@ interface IGlobOptions { * outside of a tree traversal. */ trimForExclusions?: boolean; + + /** + * Whether glob pattern matching should be case insensitive. + */ + ignoreCase?: boolean; +} + +interface IGlobOptionsInternal extends IGlobOptions { + equals: (a: string, b: string) => boolean; + endsWith: (str: string, candidate: string) => boolean; + isEqualOrParent: (base: string, candidate: string) => boolean; } interface ParsedStringPattern { @@ -339,45 +350,55 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P // Whitespace trimming pattern = pattern.trim(); + const ignoreCase = options.ignoreCase ?? false; + const internalOptions = { + ...options, + equals: ignoreCase ? equalsIgnoreCase : (a: string, b: string) => a === b, + endsWith: ignoreCase ? endsWithIgnoreCase : (str: string, candidate: string) => str.endsWith(candidate), + // TODO: the '!isLinux' part below is to keep current behavior unchanged, but it should probably be removed + // in favor of passing correct options from the caller. + isEqualOrParent: (base: string, candidate: string) => isEqualOrParent(base, candidate, !isLinux || ignoreCase) + }; + // Check cache - const patternKey = `${pattern}_${!!options.trimForExclusions}`; + const patternKey = `${ignoreCase ? pattern.toLowerCase() : pattern}_${!!options.trimForExclusions}_${ignoreCase}`; let parsedPattern = CACHE.get(patternKey); if (parsedPattern) { - return wrapRelativePattern(parsedPattern, arg1); + return wrapRelativePattern(parsedPattern, arg1, internalOptions); } // Check for Trivials let match: RegExpExecArray | null; if (T1.test(pattern)) { - parsedPattern = trivia1(pattern.substr(4), pattern); // common pattern: **/*.txt just need endsWith check - } else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check - parsedPattern = trivia2(match[1], pattern); + parsedPattern = trivia1(pattern.substring(4), pattern, internalOptions); // common pattern: **/*.txt just need endsWith check + } else if (match = T2.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/some.txt just need basename check + parsedPattern = trivia2(match[1], pattern, internalOptions); } else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png} - parsedPattern = trivia3(pattern, options); - } else if (match = T4.exec(trimForExclusions(pattern, options))) { // common pattern: **/something/else just need endsWith check - parsedPattern = trivia4and5(match[1].substr(1), pattern, true); - } else if (match = T5.exec(trimForExclusions(pattern, options))) { // common pattern: something/else just need equals check - parsedPattern = trivia4and5(match[1], pattern, false); + parsedPattern = trivia3(pattern, internalOptions); + } else if (match = T4.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/something/else just need endsWith check + parsedPattern = trivia4and5(match[1].substring(1), pattern, true, internalOptions); + } else if (match = T5.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: something/else just need equals check + parsedPattern = trivia4and5(match[1], pattern, false, internalOptions); } // Otherwise convert to pattern else { - parsedPattern = toRegExp(pattern); + parsedPattern = toRegExp(pattern, internalOptions); } // Cache CACHE.set(patternKey, parsedPattern); - return wrapRelativePattern(parsedPattern, arg1); + return wrapRelativePattern(parsedPattern, arg1, internalOptions); } -function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern): ParsedStringPattern { +function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern, options: IGlobOptionsInternal): ParsedStringPattern { if (typeof arg2 === 'string') { return parsedPattern; } const wrappedPattern: ParsedStringPattern = function (path, basename) { - if (!isEqualOrParent(path, arg2.base, !isLinux)) { + if (!options.isEqualOrParent(path, arg2.base)) { // skip glob matching if `base` is not a parent of `path` return null; } @@ -390,7 +411,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | // for the fact that `base` might end in a path separator // (https://github.com/microsoft/vscode/issues/162498) - return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename); + return parsedPattern(ltrim(path.substring(arg2.base.length), sep), basename); }; // Make sure to preserve associated metadata @@ -403,18 +424,18 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | } function trimForExclusions(pattern: string, options: IGlobOptions): string { - return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later + return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substring(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later } // common pattern: **/*.txt just need endsWith check -function trivia1(base: string, pattern: string): ParsedStringPattern { +function trivia1(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { return function (path: string, basename?: string) { - return typeof path === 'string' && path.endsWith(base) ? pattern : null; + return typeof path === 'string' && options.endsWith(path, base) ? pattern : null; }; } // common pattern: **/some.txt just need basename check -function trivia2(base: string, pattern: string): ParsedStringPattern { +function trivia2(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { const slashBase = `/${base}`; const backslashBase = `\\${base}`; @@ -424,10 +445,10 @@ function trivia2(base: string, pattern: string): ParsedStringPattern { } if (basename) { - return basename === base ? pattern : null; + return options.equals(basename, base) ? pattern : null; } - return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null; + return options.equals(path, base) || options.endsWith(path, slashBase) || options.endsWith(path, backslashBase) ? pattern : null; }; const basenames = [base]; @@ -439,7 +460,7 @@ function trivia2(base: string, pattern: string): ParsedStringPattern { } // repetition of common patterns (see above) {**/*.txt,**/*.png} -function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { +function trivia3(pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1) .split(',') .map(pattern => parsePattern(pattern, options)) @@ -478,7 +499,7 @@ function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { } // common patterns: **/something/else just need endsWith check, something/else just needs and equals check -function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern { +function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean, options: IGlobOptionsInternal): ParsedStringPattern { const usingPosixSep = sep === posix.sep; const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep); const nativePathEnd = sep + nativePath; @@ -487,11 +508,14 @@ function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean let parsedPattern: ParsedStringPattern; if (matchPathEnds) { parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null; + return typeof path === 'string' && ( + (options.equals(path, nativePath) || options.endsWith(path, nativePathEnd)) || + !usingPosixSep && (options.equals(path, targetPath) || options.endsWith(path, targetPathEnd)) + ) ? pattern : null; }; } else { parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null; + return typeof path === 'string' && (options.equals(path, nativePath) || (!usingPosixSep && options.equals(path, targetPath))) ? pattern : null; }; } @@ -500,9 +524,9 @@ function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean return parsedPattern; } -function toRegExp(pattern: string): ParsedStringPattern { +function toRegExp(pattern: string, options: IGlobOptions): ParsedStringPattern { try { - const regExp = new RegExp(`^${parseRegExp(pattern)}$`); + const regExp = new RegExp(`^${parseRegExp(pattern)}$`, options.ignoreCase ? 'i' : undefined); return function (path: string) { regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! @@ -522,14 +546,14 @@ function toRegExp(pattern: string): ParsedStringPattern { * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) */ -export function match(pattern: string | IRelativePattern, path: string): boolean; -export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */; -export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): boolean | string | null | Promise { +export function match(pattern: string | IRelativePattern, path: string, options?: IGlobOptions): boolean; +export function match(expression: IExpression, path: string, options?: IGlobOptions): boolean; +export function match(arg1: string | IExpression | IRelativePattern, path: string, options?: IGlobOptions): boolean { if (!arg1 || typeof path !== 'string') { return false; } - return parse(arg1)(path, undefined, hasSibling); + return parse(arg1, options)(path) as boolean; } /** @@ -672,7 +696,7 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse } if (!name) { - name = base.substr(0, base.length - extname(path).length); + name = base.substring(0, base.length - extname(path).length); } } @@ -805,7 +829,7 @@ function aggregateBasenameMatches(parsedPatterns: Array | undefined, patternsB: Array | undefined): boolean { return equals(patternsA, patternsB, (a, b) => { if (typeof a === 'string' && typeof b === 'string') { diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index fd64d65fcbc..3d60229f8fa 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -438,13 +438,19 @@ export function equalsIgnoreCase(a: string, b: string): boolean { return a.length === b.length && compareSubstringIgnoreCase(a, b) === 0; } +export function equals(a: string | undefined, b: string | undefined, ignoreCase?: boolean): boolean { + return a === b || (!!ignoreCase && a !== undefined && b !== undefined && equalsIgnoreCase(a, b)); +} + export function startsWithIgnoreCase(str: string, candidate: string): boolean { - const candidateLength = candidate.length; - if (candidate.length > str.length) { - return false; - } + const len = candidate.length; + return len <= str.length && compareSubstringIgnoreCase(str, candidate, 0, len) === 0; +} - return compareSubstringIgnoreCase(str, candidate, 0, candidateLength) === 0; +export function endsWithIgnoreCase(str: string, candidate: string): boolean { + const len = str.length; + const start = len - candidate.length; + return start >= 0 && compareSubstringIgnoreCase(str, candidate, start, len) === 0; } /** diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 3658ec22419..3021b924988 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -64,14 +64,14 @@ suite('Glob', () => { // console.profileEnd(); // }); - function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string) { - assert(glob.match(pattern, input), `${JSON.stringify(pattern)} should match ${input}`); - assert(glob.match(pattern, nativeSep(input)), `${pattern} should match ${nativeSep(input)}`); + function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string, ignoreCase?: boolean) { + assert(glob.match(pattern, input, { ignoreCase }), `${JSON.stringify(pattern)} should match ${input}`); + assert(glob.match(pattern, nativeSep(input), { ignoreCase }), `${pattern} should match ${nativeSep(input)}`); } - function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string) { - assert(!glob.match(pattern, input), `${pattern} should not match ${input}`); - assert(!glob.match(pattern, nativeSep(input)), `${pattern} should not match ${nativeSep(input)}`); + function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string, ignoreCase?: boolean) { + assert(!glob.match(pattern, input, { ignoreCase }), `${pattern} should not match ${input}`); + assert(!glob.match(pattern, nativeSep(input), { ignoreCase }), `${pattern} should not match ${nativeSep(input)}`); } test('simple', () => { @@ -480,10 +480,10 @@ suite('Glob', () => { } }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); - assert.strictEqual(glob.match(expression, 'test.js', () => false), null); - assert.strictEqual(glob.match(expression, 'test.js', name => name === 'te.ts'), null); - assert.strictEqual(glob.match(expression, 'test.js'), null); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); + assert.strictEqual(glob.parse(expression)('test.js', undefined, () => false), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, name => name === 'te.ts'), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined), null); expression = { '**/*.js': { @@ -491,7 +491,7 @@ suite('Glob', () => { } }; - assert.strictEqual(glob.match(expression, 'test.js', hasSibling), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, hasSibling), null); expression = { // eslint-disable-next-line local/code-no-any-casts @@ -499,11 +499,11 @@ suite('Glob', () => { } as any }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); expression = {}; - assert.strictEqual(glob.match(expression, 'test.js', hasSibling), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, hasSibling), null); }); test('expression support (multiple)', function () { @@ -519,11 +519,11 @@ suite('Glob', () => { '**/*.bananas': { bananas: true } as any }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); - assert.strictEqual('**/*.as', glob.match(expression, 'test.as', hasSibling)); - assert.strictEqual('**/*.bananas', glob.match(expression, 'test.bananas', hasSibling)); - assert.strictEqual('**/*.bananas', glob.match(expression, 'test.bananas')); - assert.strictEqual(glob.match(expression, 'test.foo', hasSibling), null); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); + assert.strictEqual('**/*.as', glob.parse(expression)('test.as', undefined, hasSibling)); + assert.strictEqual('**/*.bananas', glob.parse(expression)('test.bananas', undefined, hasSibling)); + assert.strictEqual('**/*.bananas', glob.parse(expression)('test.bananas', undefined)); + assert.strictEqual(glob.parse(expression)('test.foo', undefined, hasSibling), null); }); test('brackets', () => { @@ -789,16 +789,16 @@ suite('Glob', () => { const siblings = ['foo.ts', 'foo.js', 'foo', 'bar']; const hasSibling = (name: string) => siblings.indexOf(name) !== -1; - assert.strictEqual(glob.match(expr, 'bar', hasSibling), '**/bar'); - assert.strictEqual(glob.match(expr, 'foo', hasSibling), null); - assert.strictEqual(glob.match(expr, 'foo/bar', hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('bar', undefined, hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('foo', undefined, hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo/bar', undefined, hasSibling), '**/bar'); if (isWindows) { // backslash is a valid file name character on posix - assert.strictEqual(glob.match(expr, 'foo\\bar', hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('foo\\bar', undefined, hasSibling), '**/bar'); } - assert.strictEqual(glob.match(expr, 'foo/foo', hasSibling), null); - assert.strictEqual(glob.match(expr, 'foo.js', hasSibling), '**/*.js'); - assert.strictEqual(glob.match(expr, 'bar.js', hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo/foo', undefined, hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo.js', undefined, hasSibling), '**/*.js'); + assert.strictEqual(glob.parse(expr)('bar.js', undefined, hasSibling), null); }); test('expression with multipe basename globs', function () { @@ -1171,5 +1171,30 @@ suite('Glob', () => { assert.ok(glob.isEmptyPattern(glob.parse({ '**/*.js': false }))); }); + test('caseInsensitiveMatch', () => { + assertNoGlobMatch('PATH/FOO.js', 'path/foo.js'); + assertGlobMatch('PATH/FOO.js', 'path/foo.js', true); + // T1 + assertNoGlobMatch('**/*.JS', 'bar/foo.js'); + assertGlobMatch('**/*.JS', 'bar/foo.js', true); + // T2 + assertNoGlobMatch('**/package', 'bar/Package'); + assertGlobMatch('**/package', 'bar/Package', true); + // T3 + assertNoGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.ts'); + assertNoGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js'); + assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.ts', true); + assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js', true); + // T4 + assertNoGlobMatch('**/FOO/Bar', 'bar/foo/bar'); + assertGlobMatch('**/FOO/Bar', 'bar/foo/bar', true); + // T5 + assertNoGlobMatch('FOO/Bar', 'foo/bar'); + assertGlobMatch('FOO/Bar', 'foo/bar', true); + // Other + assertNoGlobMatch('some/*/Random/*/Path.FILE', 'some/very/random/unusual/path.file'); + assertGlobMatch('some/*/Random/*/Path.FILE', 'some/very/random/unusual/path.file', true); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 8b7656f4758..cfdf7836392 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -19,7 +19,20 @@ suite('Strings', () => { assert(strings.equalsIgnoreCase('ÖL', 'Öl')); }); - test('beginsWithIgnoreCase', () => { + test('equals', () => { + assert(!strings.equals(undefined, 'abc')); + assert(!strings.equals('abc', undefined)); + assert(strings.equals(undefined, undefined)); + assert(strings.equals('', '')); + assert(strings.equals('a', 'a')); + assert(!strings.equals('abc', 'Abc')); + assert(strings.equals('abc', 'ABC', true)); + assert(!strings.equals('Höhenmeter', 'HÖhenmeter')); + assert(!strings.equals('ÖL', 'Öl')); + assert(strings.equals('ÖL', 'Öl', true)); + }); + + test('startsWithIgnoreCase', () => { assert(strings.startsWithIgnoreCase('', '')); assert(!strings.startsWithIgnoreCase('', '1')); assert(strings.startsWithIgnoreCase('1', '')); @@ -45,6 +58,34 @@ suite('Strings', () => { assert(!strings.startsWithIgnoreCase('alles klar', 'ö')); }); + test('endsWithIgnoreCase', () => { + assert(strings.endsWithIgnoreCase('', '')); + assert(!strings.endsWithIgnoreCase('', '1')); + assert(strings.endsWithIgnoreCase('1', '')); + + assert(!strings.endsWithIgnoreCase('abcd', 'abcde')); + + assert(strings.endsWithIgnoreCase('a', 'a')); + assert(strings.endsWithIgnoreCase('abc', 'Abc')); + assert(strings.endsWithIgnoreCase('abc', 'ABC')); + assert(strings.endsWithIgnoreCase('Höhenmeter', 'HÖhenmeter')); + assert(strings.endsWithIgnoreCase('ÖL', 'Öl')); + + assert(strings.endsWithIgnoreCase('alles klar', 'r')); + assert(strings.endsWithIgnoreCase('alles klar', 'R')); + assert(strings.endsWithIgnoreCase('alles klar', 's klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'S klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'S KLAR')); + assert(strings.endsWithIgnoreCase('alles klar', 'alles klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'ALLES KLAR')); + + assert(!strings.endsWithIgnoreCase('alles klar', 'S KLAR ')); + assert(!strings.endsWithIgnoreCase('alles klar', ' S KLAR')); + assert(!strings.endsWithIgnoreCase('alles klar', 'S KLARö')); + assert(!strings.endsWithIgnoreCase('alles klar', ' ')); + assert(!strings.endsWithIgnoreCase('alles klar', 'ö')); + }); + test('compareIgnoreCase', () => { function assertCompareIgnoreCase(a: string, b: string, recurse = true): void { From dfba020e0aa1bbc812a4b3eb825337ddf8fa84ce Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:58:36 -0700 Subject: [PATCH 1835/4355] Remove extra expect error --- extensions/simple-browser/src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/simple-browser/src/extension.ts b/extensions/simple-browser/src/extension.ts index 927167a851d..885afe28712 100644 --- a/extensions/simple-browser/src/extension.ts +++ b/extensions/simple-browser/src/extension.ts @@ -86,6 +86,5 @@ export function activate(context: vscode.ExtensionContext) { } function isWeb(): boolean { - // @ts-expect-error return !(typeof process === 'object' && !!process.versions.node) && vscode.env.uiKind === vscode.UIKind.Web; } From 35d4ea1cd20294e2af910d70116dd443f7ca62b1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 29 Oct 2025 23:21:45 +0100 Subject: [PATCH 1836/4355] Inline chat v2 updates (#273986) * wip - have a tool that allows inline chat to escape * wip * improve move session * inline chat v2 UI tweaks * pick up "selected" diagnostics and add them as attachments * show inline chat widget always above selection * tweak inline escape tool * remove `hideOnRequest` * use `inlineChatApplyEdit` when using inline chat * make sure to position zone above changes if otherwise inbetween * :lipstick: * add logging * * add inline v2 specific execute toolbar * add the minimun number of command only * add keep/discard commands * Update src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/editor/common/textModelEditSource.ts | 3 +- src/vs/platform/actions/common/actions.ts | 2 + .../browser/actions/chatContextActions.ts | 7 +- .../browser/actions/chatExecuteActions.ts | 19 +++ .../chatEditing/chatEditingEditorActions.ts | 7 +- .../chatEditing/chatEditingEditorOverlay.ts | 2 +- .../chatEditingModifiedDocumentEntry.ts | 4 + .../chatEditingTextModelChangeService.ts | 56 +++++--- .../contrib/chat/common/chatEditingService.ts | 6 + .../browser/inlineChat.contribution.ts | 6 +- .../inlineChat/browser/inlineChatActions.ts | 99 +++++++------- .../browser/inlineChatController.ts | 123 ++++++++---------- .../browser/inlineChatSessionService.ts | 36 ++++- .../browser/inlineChatSessionServiceImpl.ts | 121 +++++++++++++---- .../browser/inlineChatStrategies.ts | 1 + .../inlineChat/browser/inlineChatWidget.ts | 1 + .../contrib/inlineChat/common/inlineChat.ts | 10 -- 17 files changed, 310 insertions(+), 193 deletions(-) diff --git a/src/vs/editor/common/textModelEditSource.ts b/src/vs/editor/common/textModelEditSource.ts index 1d826455f04..a5b25d274c8 100644 --- a/src/vs/editor/common/textModelEditSource.ts +++ b/src/vs/editor/common/textModelEditSource.ts @@ -146,12 +146,13 @@ export const EditSources = { } as const); }, - inlineChatApplyEdit(data: { modelId: string | undefined; requestId: string | undefined; languageId: string; extensionId: VersionedExtensionId | undefined }) { + inlineChatApplyEdit(data: { modelId: string | undefined; requestId: string | undefined; sessionId: string | undefined; languageId: string; extensionId: VersionedExtensionId | undefined }) { return createEditSource({ source: 'inlineChat.applyEdits', $modelId: avoidPathRedaction(data.modelId), $extensionId: data.extensionId?.extensionId, $extensionVersion: data.extensionId?.version, + $$sessionId: data.sessionId, $$requestId: data.requestId, $$languageId: data.languageId, } as const); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 5ceff4add17..a73d59d6cf4 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -275,6 +275,8 @@ export class MenuId { static readonly ChatSessionsMenu = new MenuId('ChatSessionsMenu'); static readonly ChatSessionsCreateSubMenu = new MenuId('ChatSessionsCreateSubMenu'); static readonly ChatConfirmationMenu = new MenuId('ChatConfirmationMenu'); + static readonly ChatEditorInlineExecute = new MenuId('ChatEditorInputExecute'); + static readonly ChatEditorInlineInputSide = new MenuId('ChatEditorInputSide'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index f66164bd388..e4df779de71 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -38,6 +38,7 @@ import { IEditorService } from '../../../../services/editor/common/editorService import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ExplorerFolderContext } from '../../../files/common/files.js'; +import { CTX_INLINE_CHAT_V2_ENABLED } from '../../../inlineChat/common/inlineChat.js'; import { AnythingQuickAccessProvider } from '../../../search/browser/anythingQuickAccess.js'; import { isSearchTreeFileMatch, isSearchTreeMatch } from '../../../search/browser/searchTreeModel/searchTreeCommon.js'; import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js'; @@ -406,7 +407,10 @@ export class AttachContextAction extends Action2 { }, menu: { when: ContextKeyExpr.and( - ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), + ContextKeyExpr.or( + ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline), CTX_INLINE_CHAT_V2_ENABLED) + ), ContextKeyExpr.or( ChatContextKeys.lockedToCodingAgent.negate(), ChatContextKeys.agentSupportsAttachments @@ -416,6 +420,7 @@ export class AttachContextAction extends Action2 { group: 'navigation', order: 3 }, + }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 7a19f83042d..404b636c5f7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -43,6 +43,7 @@ import { ILanguageModelToolsService } from '../../common/languageModelToolsServi import { IChatWidget, IChatWidgetService, showChatWidgetInViewOrEditor } from '../chat.js'; import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js'; import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js'; +import { ctxHasEditorModification } from '../chatEditing/chatEditingEditorContextKeys.js'; export interface IVoiceChatExecuteActionContext { readonly disableTimeout?: boolean; @@ -204,6 +205,16 @@ export class ChatSubmitAction extends SubmitAction { title: localize2('chat.newChat.label', "Send to New Chat"), icon: Codicon.plus } + }, { + id: MenuId.ChatEditorInlineExecute, + group: 'navigation', + order: 4, + when: ContextKeyExpr.and( + ContextKeyExpr.or(ctxHasEditorModification.negate(), ChatContextKeys.inputHasText), + whenNotInProgress, + ChatContextKeys.requestInProgress.negate(), + menuCondition + ), }] }); } @@ -1142,6 +1153,14 @@ export class CancelAction extends Action2 { ), order: 4, group: 'navigation', + }, { + id: MenuId.ChatEditorInlineExecute, + when: ContextKeyExpr.and( + ChatContextKeys.requestInProgress, + ChatContextKeys.remoteJobCreating.negate() + ), + order: 4, + group: 'navigation', }, ], keybinding: { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 1f401c1054d..12115827235 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -170,7 +170,7 @@ abstract class KeepOrUndoAction extends ChatEditingEditorAction { tooltip: _keep ? localize2('accept3', 'Keep Chat Edits in this File') : localize2('discard3', 'Undo Chat Edits in this File'), - precondition: ContextKeyExpr.and(ctxHasEditorModification, ctxIsCurrentlyBeingModified.negate()), + precondition: ContextKeyExpr.and(ctxIsGlobalEditingSession, ctxHasEditorModification, ctxIsCurrentlyBeingModified.negate()), icon: _keep ? Codicon.check : Codicon.discard, @@ -186,10 +186,7 @@ abstract class KeepOrUndoAction extends ChatEditingEditorAction { id: MenuId.ChatEditingEditorContent, group: 'a_resolve', order: _keep ? 0 : 1, - when: ContextKeyExpr.or( - ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ctxIsCurrentlyBeingModified.negate()), // Inline chat - ContextKeyExpr.and(ctxIsGlobalEditingSession, !_keep ? ctxReviewModeEnabled : undefined), // Panel chat - ) + when: !_keep ? ctxReviewModeEnabled : undefined } }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts index dca0470dcf3..677f3c5c0ca 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts @@ -390,7 +390,7 @@ class ChatEditingOverlayController { const { session, entry } = data; - if (!session.isGlobalEditingSession && !inlineChatService.hideOnRequest.read(r)) { + if (!session.isGlobalEditingSession) { // inline chat - no chat overlay unless hideOnRequest is on hide(); return; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 578145b205a..619334cb86b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -53,6 +53,10 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie return this._textModelChangeService.diffInfo.map(diff => diff.changes.length); } + get diffInfo() { + return this._textModelChangeService.diffInfo; + } + get linesAdded() { return this._textModelChangeService.diffInfo.map(diff => { let added = 0; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts index 1e4367be6bf..90c11f6311c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts @@ -31,6 +31,7 @@ import { editorSelectionBackground } from '../../../../../platform/theme/common/ import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; import { ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; +import { ChatAgentLocation } from '../../common/constants.js'; import { IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js'; import { pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js'; @@ -171,26 +172,7 @@ export class ChatEditingTextModelChangeService extends Disposable { let maxLineNumber = 0; let rewriteRatio = 0; - let source: TextModelEditSource; - if (responseModel) { - const sessionId = responseModel.session.sessionId; - const request = responseModel.session.getRequests().at(-1); - const languageId = this.modifiedModel.getLanguageId(); - const agent = responseModel.agent; - const extensionId = VersionedExtensionId.tryCreate(agent?.extensionId.value, agent?.extensionVersion); - - source = EditSources.chatApplyEdits({ - modelId: request?.modelId, - requestId: request?.id, - sessionId: sessionId, - languageId, - mode: request?.modeInfo?.modeId, - extensionId, - codeBlockSuggestionId: request?.modeInfo?.applyCodeBlockSuggestionId, - }); - } else { - source = EditSources.unknown({ name: 'editSessionUndoRedo' }); - } + const source = this._createEditSource(responseModel); if (isAtomicEdits) { // EDIT and DONE @@ -263,6 +245,40 @@ export class ChatEditingTextModelChangeService extends Disposable { return { rewriteRatio, maxLineNumber }; } + private _createEditSource(responseModel: IChatResponseModel | undefined) { + + if (!responseModel) { + return EditSources.unknown({ name: 'editSessionUndoRedo' }); + } + + const sessionId = responseModel.session.sessionId; + const request = responseModel.session.getRequests().at(-1); + const languageId = this.modifiedModel.getLanguageId(); + const agent = responseModel.agent; + const extensionId = VersionedExtensionId.tryCreate(agent?.extensionId.value, agent?.extensionVersion); + + if (responseModel.request?.locationData?.type === ChatAgentLocation.EditorInline) { + + return EditSources.inlineChatApplyEdit({ + modelId: request?.modelId, + requestId: request?.id, + sessionId, + languageId, + extensionId, + }); + } + + return EditSources.chatApplyEdits({ + modelId: request?.modelId, + requestId: request?.id, + sessionId, + languageId, + mode: request?.modeInfo?.modeId, + extensionId, + codeBlockSuggestionId: request?.modeInfo?.applyCodeBlockSuggestionId, + }); + } + private _applyEdits(edits: ISingleEditOperation[], source: TextModelEditSource) { try { this._isEditFromUs = true; diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 4f650cedee6..a0ea9611bf5 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -19,6 +19,7 @@ import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; import { IChatAgentResult } from './chatAgents.js'; import { ChatModel, IChatResponseModel } from './chatModel.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; +import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js'; export const IChatEditingService = createDecorator('chatEditingService'); @@ -264,6 +265,11 @@ export interface IModifiedFileEntry { */ readonly changesCount: IObservable; + /** + * Diff information for this entry + */ + readonly diffInfo?: IObservable; + /** * Number of lines added in this entry. */ diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 1b6ef5d874e..48e9e7ea6dd 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -15,7 +15,7 @@ import { InlineChatNotebookContribution } from './inlineChatNotebook.js'; import { IWorkbenchContributionsRegistry, registerWorkbenchContribution2, Extensions as WorkbenchExtensions, WorkbenchPhase } from '../../../common/contributions.js'; import { InlineChatAccessibleView } from './inlineChatAccessibleView.js'; import { IInlineChatSessionService } from './inlineChatSessionService.js'; -import { InlineChatEnabler, InlineChatSessionServiceImpl } from './inlineChatSessionServiceImpl.js'; +import { InlineChatEnabler, InlineChatEscapeToolContribution, InlineChatSessionServiceImpl } from './inlineChatSessionServiceImpl.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { CancelAction, ChatSubmitAction } from '../../chat/browser/actions/chatExecuteActions.js'; import { localize } from '../../../../nls.js'; @@ -29,8 +29,7 @@ registerEditorContribution(INLINE_CHAT_ID, InlineChatController1, EditorContribu registerEditorContribution(InlineChatController.ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerAction2(InlineChatActions.KeepSessionAction2); -registerAction2(InlineChatActions.UndoSessionAction2); -registerAction2(InlineChatActions.CloseSessionAction2); +registerAction2(InlineChatActions.UndoAndCloseSessionAction2); registerAction2(InlineChatActions.RevealWidget); registerAction2(InlineChatActions.CancelRequestAction); @@ -117,5 +116,6 @@ const workbenchContributionsRegistry = Registry.as { @@ -662,59 +643,68 @@ class KeepOrUndoSessionAction extends AbstractInline2ChatAction { export class KeepSessionAction2 extends KeepOrUndoSessionAction { constructor() { - super('inlineChat2.keep', true); + super(true, { + id: 'inlineChat2.keep', + title: localize2('Keep', "Keep"), + f1: true, + icon: Codicon.check, + precondition: ContextKeyExpr.and( + CTX_INLINE_CHAT_VISIBLE, + ctxHasRequestInProgress.negate(), + ctxHasEditorModification, + ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline) + ), + keybinding: [{ + when: ChatContextKeys.inputHasFocus, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Enter + }, { + weight: KeybindingWeight.WorkbenchContrib + 10, + primary: KeyMod.CtrlCmd | KeyCode.Enter + }], + menu: [{ + id: MenuId.ChatEditorInlineExecute, + group: 'navigation', + order: 4, + when: ContextKeyExpr.and( + ctxHasRequestInProgress.negate(), + ctxHasEditorModification, + ChatContextKeys.inputHasText.toNegated() + ), + }] + }); } } -export class UndoSessionAction2 extends KeepOrUndoSessionAction { - constructor() { - super('inlineChat2.undo', false); - } -} -export class CloseSessionAction2 extends AbstractInline2ChatAction { +export class UndoAndCloseSessionAction2 extends KeepOrUndoSessionAction { constructor() { - super({ + super(false, { id: 'inlineChat2.close', title: localize2('close2', "Close"), f1: true, icon: Codicon.close, - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_VISIBLE, - ctxHasRequestInProgress.negate(), - ContextKeyExpr.or(ctxRequestCount.isEqualTo(0), ctxHasEditorModification.negate()) - ), + precondition: CTX_INLINE_CHAT_VISIBLE, keybinding: [{ - when: ctxRequestCount.isEqualTo(0), - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI, + when: ChatContextKeys.inputHasFocus, + weight: KeybindingWeight.WorkbenchContrib + 1, + primary: KeyCode.Escape, }, { + when: ContextKeyExpr.and(ctxRequestCount.isEqualTo(0), ChatContextKeys.inputHasText), weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Escape, + primary: KeyMod.CtrlCmd | KeyCode.KeyI, }], menu: [{ - id: MENU_INLINE_CHAT_SIDE, + id: MenuId.ChatEditorInlineExecute, group: 'navigation', - when: ContextKeyExpr.and(ctxRequestCount.isEqualTo(0)), - }, { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: '0_main', - order: 1, - when: ContextKeyExpr.and(ctxHasEditorModification.negate()), + order: 100 }] }); } - - runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: unknown[]): void { - const inlineChatSessions = accessor.get(IInlineChatSessionService); - if (editor.hasModel()) { - const textModel = editor.getModel(); - inlineChatSessions.getSession2(textModel.uri)?.dispose(); - } - } } +// TODO@jrieken REMOVE this export class RevealWidget extends AbstractInline2ChatAction { constructor() { super({ @@ -748,6 +738,7 @@ export class RevealWidget extends AbstractInline2ChatAction { } } +// TODO@jrieken REMOVE this export class CancelRequestAction extends AbstractInline2ChatAction { constructor() { super({ diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 6de941f48cd..4268eda2d0c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -14,7 +14,7 @@ import { Lazy } from '../../../../base/common/lazy.js'; import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { MovingAverage } from '../../../../base/common/numbers.js'; -import { autorun, autorunWithStore, derived, IObservable, observableFromEvent, observableSignalFromEvent, observableValue, transaction, waitForState } from '../../../../base/common/observable.js'; +import { autorun, derived, IObservable, observableSignalFromEvent, observableValue, waitForState } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { assertType } from '../../../../base/common/types.js'; @@ -31,11 +31,13 @@ import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; import { TextEdit, VersionedExtensionId } from '../../../../editor/common/languages.js'; import { IValidEditOperation } from '../../../../editor/common/model.js'; import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; +import { IMarkerDecorationsService } from '../../../../editor/common/services/markerDecorations.js'; import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js'; import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js'; import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; import { MessageController } from '../../../../editor/contrib/message/browser/messageController.js'; import { localize } from '../../../../nls.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; @@ -45,17 +47,14 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; -import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; -import { IViewsService } from '../../../services/views/common/viewsService.js'; -import { showChatView } from '../../chat/browser/chat.js'; import { IChatAttachmentResolveService } from '../../chat/browser/chatAttachmentResolveService.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; -import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; +import { ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; import { ChatMode } from '../../chat/common/chatModes.js'; import { IChatService } from '../../chat/common/chatService.js'; -import { IChatRequestVariableEntry } from '../../chat/common/chatVariableEntries.js'; +import { IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData } from '../../chat/common/chatVariableEntries.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; import { isNotebookContainingCellEditor as isNotebookWithCellEditor } from '../../notebook/browser/notebookEditor.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; @@ -63,7 +62,7 @@ import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; import { INotebookService } from '../../notebook/common/notebookService.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; -import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; +import { IInlineChatSession2, IInlineChatSessionService, moveToPanelChat } from './inlineChatSessionService.js'; import { InlineChatError } from './inlineChatSessionServiceImpl.js'; import { HunkAction, IEditObserver, IInlineChatMetadata, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; @@ -1133,7 +1132,7 @@ export class InlineChatController1 implements IEditorContribution { lastEdit.edits = [doEdits]; } - await this._instaService.invokeFunction(moveToPanelChat, this._session?.chatModel); + await this._instaService.invokeFunction(moveToPanelChat, this._session?.chatModel, false); this.cancelSession(); } @@ -1270,8 +1269,8 @@ export class InlineChatController2 implements IEditorContribution { @IFileService private readonly _fileService: IFileService, @IChatAttachmentResolveService private readonly _chatAttachmentResolveService: IChatAttachmentResolveService, @IEditorService private readonly _editorService: IEditorService, + @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IInlineChatSessionService inlineChatService: IInlineChatSessionService, - @IConfigurationService configurationService: IConfigurationService, @IChatService chatService: IChatService, ) { @@ -1323,8 +1322,14 @@ export class InlineChatController2 implements IEditorContribution { location, { enableWorkingSet: 'implicit', - rendererOptions: { - renderTextEditsAsSummary: _uri => true + enableImplicitContext: false, + renderInputOnTop: false, + renderStyle: 'compact', + filter: _item => false, // filter ALL items + menus: { + telemetrySource: 'inlineChatWidget', + executeToolbar: MenuId.ChatEditorInlineExecute, + inputSideToolbar: MenuId.ChatEditorInlineInputSide }, defaultMode: ChatMode.Ask }, @@ -1369,7 +1374,7 @@ export class InlineChatController2 implements IEditorContribution { const visibleSessionObs = observableValue(this, undefined); - this._store.add(autorunWithStore((r, store) => { + this._store.add(autorun(r => { const model = editorObs.model.read(r); const session = this._currentSession.read(r); @@ -1383,46 +1388,8 @@ export class InlineChatController2 implements IEditorContribution { const { chatModel } = session; const showShowUntil = this._showWidgetOverrideObs.read(r); const hasNoRequests = chatModel.getRequests().length === 0; - const hideOnRequest = inlineChatService.hideOnRequest.read(r); - - const responseListener = store.add(new MutableDisposable()); - - if (hideOnRequest) { - // hide the request once the request has been added, reveal it again when no edit was made - // or when an error happened - store.add(chatModel.onDidChange(e => { - if (e.kind === 'addRequest') { - transaction(tx => { - this._showWidgetOverrideObs.set(false, tx); - visibleSessionObs.set(undefined, tx); - }); - const { response } = e.request; - if (!response) { - return; - } - responseListener.value = response.onDidChange(async e => { - - if (!response.isComplete) { - return; - } - responseListener.value = undefined; // listen only ONCE - - const shouldShow = response.isCanceled // cancelled - || response.result?.errorDetails // errors - || !response.response.value.find(part => part.kind === 'textEditGroup' - && part.edits.length > 0 - && isEqual(part.uri, model.uri)); // NO edits for file - - if (shouldShow) { - visibleSessionObs.set(session, undefined); - } - }); - } - })); - } - - if (showShowUntil || hasNoRequests || !hideOnRequest) { + if (showShowUntil || hasNoRequests) { visibleSessionObs.set(session, undefined); } else { visibleSessionObs.set(undefined, undefined); @@ -1441,17 +1408,15 @@ export class InlineChatController2 implements IEditorContribution { ctxInlineChatVisible.set(true); this._zone.value.widget.setChatModel(session.chatModel); if (!this._zone.value.position) { + this._zone.value.widget.chatWidget.input.renderAttachedContext(); // TODO - fights layout bug this._zone.value.show(session.initialPosition); } this._zone.value.reveal(this._zone.value.position!); this._zone.value.widget.focus(); - this._zone.value.widget.updateToolbar(true); const entry = session.editingSession.getEntry(session.uri); entry?.autoAcceptController.read(undefined)?.cancel(); - const requestCount = observableFromEvent(this, session.chatModel.onDidChange, () => session.chatModel.getRequests().length).read(r); - this._zone.value.widget.updateToolbar(requestCount > 0); } })); @@ -1459,10 +1424,25 @@ export class InlineChatController2 implements IEditorContribution { const session = visibleSessionObs.read(r); const entry = session?.editingSession.readEntry(session.uri, r); + + // make sure there is an editor integration const pane = this._editorService.visibleEditorPanes.find(candidate => candidate.getControl() === this._editor || isNotebookWithCellEditor(candidate, this._editor)); if (pane && entry) { entry?.getEditorIntegration(pane); } + + // make sure the ZONE isn't inbetween a diff and move above if so + if (entry?.diffInfo && this._zone.value.position) { + const { position } = this._zone.value; + const diff = entry.diffInfo.read(r); + + for (const change of diff.changes) { + if (change.modified.contains(position.lineNumber)) { + this._zone.value.updatePositionAndHeight(new Position(change.modified.startLineNumber - 1, 1)); + break; + } + } + } })); } @@ -1496,6 +1476,25 @@ export class InlineChatController2 implements IEditorContribution { const session = this._inlineChatSessions.getSession2(uri) ?? await this._inlineChatSessions.createSession2(this._editor, uri, CancellationToken.None); + // ADD diagnostics + const entries: IChatRequestVariableEntry[] = []; + for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(uri)) { + if (range.intersectRanges(this._editor.getSelection())) { + const filter = IDiagnosticVariableEntryFilterData.fromMarker(marker); + entries.push(IDiagnosticVariableEntryFilterData.toEntry(filter)); + } + } + if (entries.length > 0) { + this._zone.value.widget.chatWidget.attachmentModel.addContext(...entries); + this._zone.value.widget.chatWidget.input.setValue(entries.length > 1 + ? localize('fixN', "Fix the attached problems") + : localize('fix1', "Fix the attached problem"), + true + ); + this._zone.value.widget.chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1)); + } + + // Check args if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) { if (arg.initialRange) { this._editor.revealRange(arg.initialRange); @@ -1673,19 +1672,3 @@ function isCellEditOperation(edit: URI | TextEdit[] | ICellEditOperation): edit } return true; } - -async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | undefined) { - - const viewsService = accessor.get(IViewsService); - const chatService = accessor.get(IChatService); - const layoutService = accessor.get(IWorkbenchLayoutService); - - const widget = await showChatView(viewsService, layoutService); - - if (widget && widget.viewModel && model) { - for (const request of model.getRequests().slice()) { - await chatService.adoptRequest(widget.viewModel.model.sessionId, request); - } - widget.focusResponseItem(); - } -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index 3ccd951ca2e..bd0cc6ed0ef 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -5,15 +5,18 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { IActiveCodeEditor, ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { Position } from '../../../../editor/common/core/position.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { IValidEditOperation } from '../../../../editor/common/model.js'; -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { createDecorator, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { showChatView } from '../../chat/browser/chat.js'; import { IChatEditingSession } from '../../chat/common/chatEditingService.js'; -import { IChatModel } from '../../chat/common/chatModel.js'; +import { IChatModel, IChatRequestModel } from '../../chat/common/chatModel.js'; +import { IChatService } from '../../chat/common/chatService.js'; import { Session, StashedSession } from './inlineChatSession.js'; export interface ISessionKeyComputer { @@ -63,10 +66,31 @@ export interface IInlineChatSessionService { dispose(): void; - - hideOnRequest: IObservable; - createSession2(editor: ICodeEditor, uri: URI, token: CancellationToken): Promise; getSession2(uri: URI): IInlineChatSession2 | undefined; + getSession2(sessionId: string): IInlineChatSession2 | undefined; readonly onDidChangeSessions: Event; } + +export async function moveToPanelChat(accessor: ServicesAccessor, model: IChatModel | undefined, resend: boolean) { + + const viewsService = accessor.get(IViewsService); + const chatService = accessor.get(IChatService); + const layoutService = accessor.get(IWorkbenchLayoutService); + + const widget = await showChatView(viewsService, layoutService); + + if (widget && widget.viewModel && model) { + let lastRequest: IChatRequestModel | undefined; + for (const request of model.getRequests().slice()) { + await chatService.adoptRequest(widget.viewModel.model.sessionId, request); + lastRequest = request; + } + + if (lastRequest && resend) { + chatService.resendRequest(lastRequest, { location: widget.location }); + } + + widget.focusResponseItem(); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index f87efcc4d43..0c5d8c5b4e5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; -import { autorun, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; +import { autorun, observableFromEvent } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { assertType } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { IActiveCodeEditor, ICodeEditor, isCodeEditor, isCompositeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { IValidEditOperation } from '../../../../editor/common/model.js'; @@ -20,8 +21,10 @@ import { createTextBufferFactoryFromSnapshot } from '../../../../editor/common/m import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; @@ -35,9 +38,10 @@ import { IChatAgentService } from '../../chat/common/chatAgents.js'; import { ModifiedFileEntryState } from '../../chat/common/chatEditingService.js'; import { IChatService } from '../../chat/common/chatService.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; +import { ILanguageModelToolsService, ToolDataSource, IToolData } from '../../chat/common/languageModelToolsService.js'; import { CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT, CTX_INLINE_CHAT_HAS_NOTEBOOK_INLINE, CTX_INLINE_CHAT_POSSIBLE, InlineChatConfigKeys } from '../common/inlineChat.js'; import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession.js'; -import { IInlineChatSession2, IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService.js'; +import { IInlineChatSession2, IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, moveToPanelChat } from './inlineChatSessionService.js'; type SessionData = { @@ -76,8 +80,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _sessions = new Map(); private readonly _keyComputers = new Map(); - readonly hideOnRequest: IObservable; - constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, @IModelService private readonly _modelService: IModelService, @@ -91,13 +93,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @IChatService private readonly _chatService: IChatService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, - @IConfigurationService private readonly _configurationService: IConfigurationService, ) { - const v2 = observableConfigValue(InlineChatConfigKeys.EnableV2, false, this._configurationService); - - this.hideOnRequest = observableConfigValue(InlineChatConfigKeys.HideOnRequest, false, this._configurationService) - .map((value, r) => v2.read(r) && value); } dispose() { @@ -384,7 +381,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const result: IInlineChatSession2 = { uri, - initialPosition: editor.getPosition().delta(-1), + initialPosition: editor.getSelection().getStartPosition().delta(-1), /* one line above selection start */ chatModel, editingSession, dispose: store.dispose.bind(store) @@ -394,20 +391,29 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return result; } - getSession2(uri: URI): IInlineChatSession2 | undefined { - let result = this._sessions2.get(uri); - if (!result) { - // no direct session, try to find an editing session which has a file entry for the uri - for (const [_, candidate] of this._sessions2) { - const entry = candidate.editingSession.getEntry(uri); - if (entry) { - result = candidate; - break; + getSession2(uriOrSessionId: URI | string): IInlineChatSession2 | undefined { + if (URI.isUri(uriOrSessionId)) { + + let result = this._sessions2.get(uriOrSessionId); + if (!result) { + // no direct session, try to find an editing session which has a file entry for the uri + for (const [_, candidate] of this._sessions2) { + const entry = candidate.editingSession.getEntry(uriOrSessionId); + if (entry) { + result = candidate; + break; + } + } + } + return result; + } else { + for (const session of this._sessions2.values()) { + if (session.chatModel.sessionId === uriOrSessionId) { + return session; } } } - - return result; + return undefined; } } @@ -476,3 +482,74 @@ export class InlineChatEnabler { this._store.dispose(); } } + + +export class InlineChatEscapeToolContribution extends Disposable { + + static readonly Id = 'inlineChat.escapeTool'; + + private static readonly _data: IToolData = { + id: 'inline_chat_exit', + source: ToolDataSource.Internal, + canBeReferencedInPrompt: false, + alwaysDisplayInputOutput: false, + displayName: localize('name', "Inline Chat to Panel Chat"), + modelDescription: 'Moves the inline chat session to the richer panel chat which supports edits across files, creating and deleting files, multi-turn conversations between the user and the assistant, and access to more IDE tools, like retrieve problems, interact with source control, run terminal commands etc.', + }; + + constructor( + @ILanguageModelToolsService lmTools: ILanguageModelToolsService, + @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, + @IDialogService dialogService: IDialogService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @IChatService chatService: IChatService, + @ILogService logService: ILogService, + @IInstantiationService instaService: IInstantiationService, + ) { + + super(); + + this._store.add(lmTools.registerTool(InlineChatEscapeToolContribution._data, { + invoke: async (invocation, _tokenCountFn, _progress, _token) => { + + const sessionId = invocation.context?.sessionId; + + if (!sessionId) { + logService.warn('InlineChatEscapeToolContribution: no sessionId in tool invocation context'); + return { content: [{ kind: 'text', value: 'Cancel' }] }; + } + + const session = inlineChatSessionService.getSession2(sessionId); + + if (!session) { + logService.warn(`InlineChatEscapeToolContribution: no session found for id ${sessionId}`); + return { content: [{ kind: 'text', value: 'Cancel' }] }; + } + + const result = await dialogService.confirm({ + type: 'question', + title: localize('confirm.title', "Continue in Panel Chat?"), + message: localize('confirm', "Do you want to continue in panel chat or rephrase your prompt?"), + detail: localize('confirm.detail', "This task is too complex for Inline Chat. You can rephrase your prompt or continue in the panel chat."), + primaryButton: localize('confirm.yes', "Rephrase"), + cancelButton: localize('confirm.no', "Continue in Chat"), + }); + + + const editor = codeEditorService.getFocusedCodeEditor(); + + if (!result.confirmed || !editor) { + logService.trace('InlineChatEscapeToolContribution: moving session to panel chat'); + await instaService.invokeFunction(moveToPanelChat, session.chatModel, true); + session.dispose(); + + } else { + logService.trace('InlineChatEscapeToolContribution: rephrase prompt'); + chatService.removeRequest(session.chatModel.sessionId, session.chatModel.getRequests().at(-1)!.id); + } + + return { content: [{ kind: 'text', value: 'Success' }] }; + } + })); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 3cf153e78f9..cfc65f55ef5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -182,6 +182,7 @@ export class LiveStrategy { modelId: metadata.modelId, extensionId: metadata.extensionId, requestId: metadata.requestId, + sessionId: undefined, languageId: this._session.textModelN.getLanguageId(), }); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 3b321059d02..1d90a7602be 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -593,6 +593,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { override reset() { this._accessibleViewer.clear(); + this.chatWidget.setInput(); super.reset(); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f700838a728..64d678e8fea 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -22,7 +22,6 @@ export const enum InlineChatConfigKeys { LineNLHint = 'inlineChat.lineNaturalLanguageHint', EnableV2 = 'inlineChat.enableV2', notebookAgent = 'inlineChat.notebookAgent', - HideOnRequest = 'inlineChat.hideOnRequest' } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -70,15 +69,6 @@ Registry.as(Extensions.Configuration).registerConfigurat mode: 'auto' } }, - [InlineChatConfigKeys.HideOnRequest]: { - markdownDescription: localize('hideOnRequest', "Whether to hide the inline chat widget after making a request. When enabled, the widget hides after a request has been made and instead the chat overlay shows. When hidden, the widget can always be shown again with the inline chat keybinding or from the chat overlay widget. *Note* that this setting requires `#inlineChat.enableV2#` to be enabled."), - default: false, - type: 'boolean', - tags: ['preview'], - experiment: { - mode: 'auto' - } - }, [InlineChatConfigKeys.notebookAgent]: { markdownDescription: localize('notebookAgent', "Enable agent-like behavior for inline chat widget in notebooks."), default: false, From 8f24b33b0d806e7f0f5908ef3c9c483a564c2f44 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 29 Oct 2025 15:52:52 -0700 Subject: [PATCH 1837/4355] edits: integration point for externally-editing agent clis Allows external agent/CLI edits to be harnessed in our edit session implementation. There is a callback API to make an external edit. While the callback is running, we assume that all edits made were done by the agent and update our internal models based on a before/after snapshot. --- .../api/browser/mainThreadChatAgents2.ts | 39 +++- .../workbench/api/common/extHost.protocol.ts | 3 +- .../api/common/extHostChatAgents2.ts | 24 ++- src/vs/workbench/api/common/extHostTypes.ts | 14 ++ .../chatEditingCheckpointTimelineImpl.ts | 15 +- .../chatEditingModifiedDocumentEntry.ts | 44 ++++- .../chatEditingModifiedFileEntry.ts | 39 ++++ .../chatEditingModifiedNotebookEntry.ts | 57 +++++- .../chatEditing/chatEditingServiceImpl.ts | 5 + .../browser/chatEditing/chatEditingSession.ts | 178 +++++++++++++++++- .../chatEditingTextModelChangeService.ts | 5 +- .../notebook/chatEditingNotebookCellEntry.ts | 3 +- .../contrib/chat/browser/chatInputPart.ts | 144 ++++++++------ .../contrib/chat/browser/chatWidget.ts | 16 +- .../contrib/chat/common/chatEditingService.ts | 10 + .../contrib/chat/common/chatModel.ts | 2 + .../contrib/chat/common/chatService.ts | 10 +- ...ode.proposed.chatParticipantAdditions.d.ts | 17 +- 18 files changed, 529 insertions(+), 96 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index f3b7b7bb092..6f725a98193 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -27,6 +27,7 @@ import { IChatWidgetService } from '../../contrib/chat/browser/chat.js'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/contrib/chatDynamicVariables.js'; import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/chatAgents.js'; import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/chatEditingService.js'; +import { IChatModel } from '../../contrib/chat/common/chatModel.js'; import { ChatRequestAgentPart } from '../../contrib/chat/common/chatParserTypes.js'; import { ChatRequestParser } from '../../contrib/chat/common/chatRequestParser.js'; import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatService, IChatTask, IChatTaskSerialized, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; @@ -93,7 +94,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA private readonly _chatRelatedFilesProviders = this._register(new DisposableMap()); - private readonly _pendingProgress = new Map void>(); + private readonly _pendingProgress = new Map void; chatSession: IChatModel | undefined }>(); private readonly _proxy: ExtHostChatAgentsShape2; private readonly _activeTasks = new Map(); @@ -167,12 +168,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA const impl: IChatAgentImplementation = { invoke: async (request, progress, history, token) => { - this._pendingProgress.set(request.requestId, progress); + const chatSession = this._chatService.getSession(request.sessionId); + this._pendingProgress.set(request.requestId, { progress, chatSession }); try { - const chatSessionContext = this._chatService.getChatSessionFromInternalId(request.sessionId); return await this._proxy.$invokeAgent(handle, request, { history, - chatSessionContext, + chatSessionContext: chatSession?.contributedChatSession, chatSummary: request.chatSummary }, token) ?? {}; } finally { @@ -242,12 +243,30 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } async $handleProgressChunk(requestId: string, chunks: (IChatProgressDto | [IChatProgressDto, number])[]): Promise { + const pendingProgress = this._pendingProgress.get(requestId); + if (!pendingProgress) { + this._logService.warn(`MainThreadChatAgents2#$handleProgressChunk: No pending progress for requestId ${requestId}`); + return; + } + const { progress, chatSession } = pendingProgress; const chatProgressParts: IChatProgress[] = []; - chunks.forEach(item => { + for (const item of chunks) { const [progress, responsePartHandle] = Array.isArray(item) ? item : [item]; + if (progress.kind === 'externalEdits') { + // todo@connor4312: be more specific here, pass response model through to invocation? + const response = chatSession?.getRequests().at(-1)?.response; + if (chatSession?.editingSession && responsePartHandle !== undefined && response) { + const parts = progress.start + ? await chatSession.editingSession.startExternalEdits(response, responsePartHandle, revive(progress.resources)) + : await chatSession.editingSession.stopExternalEdits(response, responsePartHandle); + chatProgressParts.push(...parts); + } + continue; + } + const revivedProgress = progress.kind === 'notebookEdit' ? ChatNotebookEdit.fromChatEdit(progress) : revive(progress) as IChatProgress; @@ -268,7 +287,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA const task = new MainThreadChatTask(revivedProgress.content); this._activeTasks.set(responsePartId, task); chatProgressParts.push(task); - return; } else if (responsePartHandle !== undefined) { const responsePartId = `${requestId}_${responsePartHandle}`; const task = this._activeTasks.get(responsePartId); @@ -280,13 +298,14 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } else { task?.complete(undefined); } - return; + break; case 'warning': case 'reference': task?.add(revivedProgress); - return; + break; } } + continue; } if (revivedProgress.kind === 'inlineReference' && revivedProgress.resolveId) { @@ -297,9 +316,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } chatProgressParts.push(revivedProgress); - }); + } - this._pendingProgress.get(requestId)?.(chatProgressParts); + progress(chatProgressParts); } $handleAnchorResolve(requestId: string, handle: string, resolveAnchor: Dto | undefined): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 44cc8e71bbe..59d6f48557a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -58,7 +58,7 @@ import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, UserSelectedTo import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js'; import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatProgressHistoryResponseContent, IChatRequestVariableData } from '../../contrib/chat/common/chatModel.js'; -import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; +import { ChatResponseClearToPreviousToolInvocationReason, IChatContentInlineReference, IChatExternalEditsDto, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { IChatSessionItem, IChatSessionProviderOptionGroup } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariables.js'; import { ChatAgentLocation } from '../../contrib/chat/common/constants.js'; @@ -1471,6 +1471,7 @@ export type IChatProgressDto = | Dto> | IChatTaskDto | IChatNotebookEditDto + | IChatExternalEditsDto | IChatResponseClearToPreviousToolInvocationDto; export interface ExtHostUrlsShape { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index d827de9e1bb..81244cd066d 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -82,7 +82,7 @@ export class ChatAgentResponseStream { const sendQueue: (IChatProgressDto | [IChatProgressDto, number])[] = []; - const notify: Function[] = []; + let notify: Function[] = []; function send(chunk: IChatProgressDto): void; function send(chunk: IChatProgressDto, handle: number): Promise; @@ -92,9 +92,10 @@ export class ChatAgentResponseStream { const newLen = sendQueue.push(handle !== undefined ? [chunk, handle] : chunk); if (newLen === 1) { queueMicrotask(() => { + const toNotify = notify; + notify = []; that._proxy.$handleProgressChunk(that._request.requestId, sendQueue).finally(() => { - notify.forEach(f => f()); - notify.length = 0; + toNotify.forEach(f => f()); }); sendQueue.length = 0; }); @@ -274,6 +275,18 @@ export class ChatAgentResponseStream { _report(dto); return this; }, + async externalEdit(target, callback) { + throwIfDone(this.externalEdit); + const resources = Array.isArray(target) ? target : [target]; + const operationId = taskHandlePool++; + + await send({ kind: 'externalEdits', start: true, resources }, operationId); + try { + return await callback(); + } finally { + await send({ kind: 'externalEdits', start: false, resources }, operationId); + } + }, confirmation(title, message, data, buttons) { throwIfDone(this.confirmation); checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); @@ -304,6 +317,7 @@ export class ChatAgentResponseStream { part instanceof extHostTypes.ChatResponseCodeCitationPart || part instanceof extHostTypes.ChatResponseMovePart || part instanceof extHostTypes.ChatResponseExtensionsPart || + part instanceof extHostTypes.ChatResponseExternalEditPart || part instanceof extHostTypes.ChatResponseThinkingProgressPart || part instanceof extHostTypes.ChatResponsePullRequestPart || part instanceof extHostTypes.ChatResponseProgressPart2 @@ -343,6 +357,10 @@ export class ChatAgentResponseStream { const dto = typeConvert.ChatPrepareToolInvocationPart.from(part); _report(dto); return this; + } else if (part instanceof extHostTypes.ChatResponseExternalEditPart) { + const p = this.externalEdit(part.uris, part.callback); + p.then(() => part.didGetApplied()); + return this; } else { const dto = typeConvert.ChatResponsePart.from(part, that._commandsConverter, that._sessionDisposables); _report(dto); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index dda4138c9e3..8c3ab998da1 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3165,6 +3165,20 @@ export class ChatResponseMultiDiffPart { } } +export class ChatResponseExternalEditPart { + applied: Thenable; + didGetApplied!: () => void; + + constructor( + public uris: vscode.Uri[], + public callback: () => Thenable, + ) { + this.applied = new Promise((resolve) => { + this.didGetApplied = resolve; + }); + } +} + export class ChatResponseAnchorPart implements vscode.ChatResponseAnchorPart { value: vscode.Uri | vscode.Location; title?: string; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts index 499b722a4fa..a904ab1304d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts @@ -279,13 +279,12 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint // The content URI doesn't preserve the original scheme or authority. Look through // to find the operation that touched that path to get its actual URI - const fileURI = this._operations.get().find(o => o.uri.path === contentURI.path)?.uri; + const fileURI = this._getTimelineCanonicalUriForPath(contentURI); if (!toEpoch || !fileURI) { return ''; } - const baseline = await this._findBestBaselineForFile(fileURI, toEpoch, requestId); if (!baseline) { return ''; @@ -296,6 +295,18 @@ export class ChatEditingCheckpointTimelineImpl implements IChatEditingCheckpoint return replayed.exists ? replayed.content : undefined; } + private _getTimelineCanonicalUriForPath(contentURI: URI) { + for (const it of [this._fileBaselines.values(), this._operations.get()]) { + for (const thing of it) { + if (thing.uri.path === contentURI.path) { + return thing.uri; + } + } + } + + return undefined; + } + /** * Creates a callback that is invoked when data at the stop changes. This * will not fire initially and may be debounced internally. diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index 578145b205a..e485ca92f74 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -122,7 +122,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie ); this._textModelChangeService = this._register(instantiationService.createInstance(ChatEditingTextModelChangeService, - this.originalModel, this.modifiedModel, this._stateObs)); + this.originalModel, this.modifiedModel, this._stateObs, () => this._isExternalEditInProgress)); this._register(this._textModelChangeService.onDidAcceptOrRejectAllHunks(action => { this._stateObs.set(action, undefined); @@ -300,4 +300,46 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie private _shouldAutoSave() { return this.modifiedURI.scheme !== Schemas.untitled; } + + async computeEditsFromSnapshots(beforeSnapshot: string, afterSnapshot: string): Promise<(TextEdit | ICellEditOperation)[]> { + // Simple full-content replacement approach + // This is similar to how streaming edits work - we just replace the entire content + const endLine = beforeSnapshot.split(/\r?\n/).length; + + return [{ + range: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: endLine, + endColumn: beforeSnapshot.split(/\r?\n/)[endLine - 1]?.length + 1 || 1 + }, + text: afterSnapshot + }]; + } + + async save(): Promise { + if (this.modifiedModel.uri.scheme === Schemas.untitled) { + return; + } + + // Save the current model state to disk if dirty + if (this._textFileService.isDirty(this.modifiedModel.uri)) { + await this._textFileService.save(this.modifiedModel.uri, { + reason: SaveReason.EXPLICIT, + skipSaveParticipants: true + }); + } + } + + async revertToDisk(): Promise { + if (this.modifiedModel.uri.scheme === Schemas.untitled) { + return; + } + + // Revert to reload from disk, ensuring in-memory model matches disk + const fileModel = this._textFileService.files.get(this.modifiedModel.uri); + if (fileModel && !fileModel.isDisposed()) { + await fileModel.revert({ soft: false }); + } + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 64569cc55c1..a774a1968c1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -60,6 +60,12 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im protected readonly _isCurrentlyBeingModifiedByObs = observableValue<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined>(this, undefined); readonly isCurrentlyBeingModifiedBy: IObservable<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined> = this._isCurrentlyBeingModifiedByObs; + /** + * Flag to track if we're currently in an external edit operation. + * When true, file system changes should be treated as agent edits, not user edits. + */ + protected _isExternalEditInProgress = false; + protected readonly _lastModifyingResponseObs = observableValueOpts({ equalsFn: (a, b) => a?.requestId === b?.requestId }, undefined); readonly lastModifyingResponse: IObservable = this._lastModifyingResponseObs; @@ -363,4 +369,37 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im abstract resetToInitialContent(): Promise; abstract initialContent: string; + + /** + * Computes the edits between two snapshots of the file content. + * @param beforeSnapshot The content before the changes + * @param afterSnapshot The content after the changes + * @returns Array of text edits or cell edit operations + */ + abstract computeEditsFromSnapshots(beforeSnapshot: string, afterSnapshot: string): Promise<(TextEdit | ICellEditOperation)[]>; + + /** + * Marks the start of an external edit operation. + * File system changes will be treated as agent edits until stopExternalEdit is called. + */ + startExternalEdit(): void { + this._isExternalEditInProgress = true; + } + + /** + * Marks the end of an external edit operation. + */ + stopExternalEdit(): void { + this._isExternalEditInProgress = false; + } + + /** + * Saves the current model state to disk. + */ + abstract save(): Promise; + + /** + * Reloads the model from disk to ensure it's in sync with file system changes. + */ + abstract revertToDisk(): Promise; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts index d90b658c4c1..f392c5536d2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookEntry.ts @@ -260,7 +260,7 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie } mirrorNotebookEdits(e: NotebookTextModelChangedEvent) { - if (this._isEditFromUs || Array.from(this.cellEntryMap.values()).some(entry => entry.isEditFromUs)) { + if (this._isEditFromUs || this._isExternalEditInProgress || Array.from(this.cellEntryMap.values()).some(entry => entry.isEditFromUs)) { return; } @@ -1011,7 +1011,7 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie return; } const disposables = new DisposableStore(); - cellEntry = this._register(this._instantiationService.createInstance(ChatEditingNotebookCellEntry, this.modifiedResourceRef.object.resource, cell, modifiedCellModel, originalCellModel, disposables)); + cellEntry = this._register(this._instantiationService.createInstance(ChatEditingNotebookCellEntry, this.modifiedResourceRef.object.resource, cell, modifiedCellModel, originalCellModel, () => this._isExternalEditInProgress, disposables)); this.cellEntryMap.set(cell.uri, cellEntry); disposables.add(autorun(r => { if (this.modifiedModel.cells.indexOf(cell) === -1) { @@ -1060,6 +1060,59 @@ export class ChatEditingModifiedNotebookEntry extends AbstractChatEditingModifie return cellEntry; } + + async computeEditsFromSnapshots(beforeSnapshot: string, afterSnapshot: string): Promise<(TextEdit | ICellEditOperation)[]> { + // For notebooks, we restore the snapshot and compute the cell-level edits + // This is a simplified approach that replaces cells as needed + + const beforeData = deserializeSnapshot(beforeSnapshot); + const afterData = deserializeSnapshot(afterSnapshot); + + const edits: ICellEditOperation[] = []; + + // Simple approach: replace all cells + // A more sophisticated approach would diff individual cells + if (beforeData.data.cells.length > 0) { + edits.push({ + editType: CellEditType.Replace, + index: 0, + count: beforeData.data.cells.length, + cells: afterData.data.cells + }); + } else if (afterData.data.cells.length > 0) { + edits.push({ + editType: CellEditType.Replace, + index: 0, + count: 0, + cells: afterData.data.cells + }); + } + + return edits; + } + + async save(): Promise { + if (this.modifiedModel.uri.scheme === Schemas.untitled) { + return; + } + + // Save the notebook if dirty + if (this.notebookResolver.isDirty(this.modifiedModel.uri)) { + await this.modifiedResourceRef.object.save({ + reason: SaveReason.EXPLICIT, + skipSaveParticipants: true + }); + } + } + + async revertToDisk(): Promise { + if (this.modifiedModel.uri.scheme === Schemas.untitled) { + return; + } + + // Revert to reload from disk + await this.modifiedResourceRef.object.revert({ soft: false }); + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index f6dafc422ff..b821c79fc6b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -278,6 +278,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic continue; } + // Skip external edits - they're already applied on disk + if (part.isExternalEdit) { + continue; + } + ensureEditorOpen(part.uri); // get new edits and start editing session diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 344c0bc90a1..f0397b987ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -8,6 +8,7 @@ import { VSBuffer } from '../../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Emitter } from '../../../../../base/common/event.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; import { Iterable } from '../../../../../base/common/iterator.js'; import { Disposable, DisposableStore, dispose } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; @@ -15,6 +16,7 @@ import { autorun, IObservable, IReader, ITransaction, observableValue, transacti import { isEqual } from '../../../../../base/common/resources.js'; import { hasKey, Mutable } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; @@ -36,7 +38,7 @@ import { CellUri, ICellEditOperation } from '../../../notebook/common/notebookCo import { INotebookService } from '../../../notebook/common/notebookService.js'; import { ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IModifiedEntryTelemetryInfo, IModifiedFileEntry, ISnapshotEntry, IStreamingEdits, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; -import { IChatService } from '../../common/chatService.js'; +import { IChatProgress, IChatService } from '../../common/chatService.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { IChatEditingCheckpointTimeline } from './chatEditingCheckpointTimeline.js'; import { ChatEditingCheckpointTimelineImpl, IChatEditingTimelineFsDelegate } from './chatEditingCheckpointTimelineImpl.js'; @@ -86,6 +88,40 @@ class ThrottledSequencer extends Sequencer { } } +function createOpeningEditCodeBlock(entry: AbstractChatEditingModifiedFileEntry): IChatProgress[] { + return [ + { + kind: 'markdownContent', + content: new MarkdownString('\n````\n') + }, + { + kind: 'codeblockUri', + uri: entry.modifiedURI, + isEdit: true + }, + { + kind: 'markdownContent', + content: new MarkdownString('\n````\n') + }, + entry instanceof ChatEditingModifiedNotebookEntry + ? { + kind: 'notebookEdit', + uri: entry.modifiedURI, + edits: [], + done: false, + isExternalEdit: true + } + : { + kind: 'textEdit', + uri: entry.modifiedURI, + edits: [], + done: false, + isExternalEdit: true + }, + ]; +} + + export class ChatEditingSession extends Disposable implements IChatEditingSession { private readonly _state = observableValue(this, ChatEditingSessionState.Initial); private readonly _timeline: IChatEditingCheckpointTimeline; @@ -98,6 +134,16 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio private readonly _baselineCreationLocks = new SequencerByKey(); private readonly _streamingEditLocks = new SequencerByKey(); + /** + * Tracks active external edit operations. + * Key is operationId, value contains the operation state. + */ + private readonly _externalEditOperations = new Map; + releaseLocks: () => void; + }>(); + private readonly _entriesObs = observableValue(this, []); public get entries(): IObservable { return this._entriesObs; @@ -254,7 +300,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio return this._timeline.getEntryDiffBetweenRequests(uri, startRequestId, stopRequestId); } - public createSnapshot(requestId: string, undoStop: string | undefined, makeEmpty = undoStop !== undefined): void { + public createSnapshot(requestId: string, undoStop: string | undefined): void { const label = undoStop ? `Request ${requestId} - Stop ${undoStop}` : `Request ${requestId}`; this._timeline.createCheckpoint(requestId, undoStop, label); } @@ -268,7 +314,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio await this._baselineCreationLocks.peek(snapshotUri.path); const content = await this._timeline.getContentAtStop(requestId, snapshotUri, undoStop); - if (!content) { + if (content === undefined) { return null; } @@ -462,6 +508,130 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio }; } + async startExternalEdits(responseModel: IChatResponseModel, operationId: number, resources: URI[]): Promise { + const snapshots = new ResourceMap(); + const acquiredLockPromises: DeferredPromise[] = []; + const releaseLockPromises: DeferredPromise[] = []; + const undoStopId = generateUuid(); + const progress: IChatProgress[] = [{ + kind: 'undoStop', + id: undoStopId, + }]; + + // Acquire locks for each resource and take snapshots + for (const resource of resources) { + const releaseLock = new DeferredPromise(); + releaseLockPromises.push(releaseLock); + + const acquiredLock = new DeferredPromise(); + acquiredLockPromises.push(acquiredLock); + + this._streamingEditLocks.queue(resource.toString(), async () => { + if (this.isDisposed) { + acquiredLock.complete(); + return; + } + + const entry = await this._acceptStreamingEditsStart(responseModel, undoStopId, resource); + progress.push(...createOpeningEditCodeBlock(entry)); + + // Save to disk to ensure disk state is current before external edits + await entry.save(); + + // Take snapshot of current state + const snapshot = this._getCurrentTextOrNotebookSnapshot(entry); + snapshots.set(resource, snapshot); + entry.startExternalEdit(); + acquiredLock.complete(); + + // Wait for the lock to be released by stopExternalEdits + return releaseLock.p; + }); + } + + await Promise.all(acquiredLockPromises.map(p => p.p)); + this.createSnapshot(responseModel.requestId, undoStopId); + + // Store the operation state + this._externalEditOperations.set(operationId, { + responseModel, + snapshots, + releaseLocks: () => releaseLockPromises.forEach(p => p.complete()) + }); + + return progress; + } + + async stopExternalEdits(responseModel: IChatResponseModel, operationId: number): Promise { + const operation = this._externalEditOperations.get(operationId); + if (!operation) { + this._logService.warn(`stopExternalEdits called for unknown operation ${operationId}`); + return []; + } + + this._externalEditOperations.delete(operationId); + + const progress: IChatProgress[] = []; + + try { + // For each resource, compute the diff and create edit parts + for (const [resource, beforeSnapshot] of operation.snapshots) { + const entry = this._getEntry(resource); + if (!entry) { + continue; + } + + // Reload from disk to ensure in-memory model is in sync with file system + await entry.revertToDisk(); + + // Take new snapshot after external changes + const afterSnapshot = this._getCurrentTextOrNotebookSnapshot(entry); + + // Skip if no changes + if (beforeSnapshot === afterSnapshot) { + continue; + } + + // Compute edits from the snapshots + const edits = await entry.computeEditsFromSnapshots(beforeSnapshot, afterSnapshot); + + // Record operations on timeline + await this._recordEditOperations(entry, resource, edits, responseModel); + + progress.push(entry instanceof ChatEditingModifiedNotebookEntry ? { + kind: 'notebookEdit', + uri: resource, + edits: edits as ICellEditOperation[], + done: true, + isExternalEdit: true + } : { + kind: 'textEdit', + uri: resource, + edits: edits as TextEdit[], + done: true, + isExternalEdit: true + }); + + // Mark as no longer being modified + await entry.acceptStreamingEditsEnd(); + + // Clear external edit mode + entry.stopExternalEdit(); + } + } finally { + // Release all the locks + operation.releaseLocks(); + + const hasOtherTasks = Iterable.some(this._streamingEditLocks.keys(), k => !operation.snapshots.has(URI.parse(k))); + if (!hasOtherTasks) { + this._state.set(ChatEditingSessionState.Idle, undefined); + } + } + + + return progress; + } + async undoInteraction(): Promise { await this._timeline.undoToLastCheckpoint(); } @@ -538,6 +708,8 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio entry.acceptStreamingEditsStart(responseModel, undoStop, tx); // Note: Individual edit operations will be recorded by the file entries }); + + return entry; } private async _initEntries({ entries }: IChatEditingSessionStop): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts index 1e4367be6bf..3029ff95f7c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts @@ -78,6 +78,7 @@ export class ChatEditingTextModelChangeService extends Disposable { public get allEditsAreFromUs() { return this._allEditsAreFromUs; } + private _isExternalEditInProgress: (() => boolean) | undefined; private _diffOperation: Promise | undefined; private _diffOperationIds: number = 0; @@ -123,10 +124,12 @@ export class ChatEditingTextModelChangeService extends Disposable { private readonly originalModel: ITextModel, private readonly modifiedModel: ITextModel, private readonly state: IObservable, + isExternalEditInProgress: (() => boolean) | undefined, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, ) { super(); + this._isExternalEditInProgress = isExternalEditInProgress; this._register(this.modifiedModel.onDidChangeContent(e => { this._mirrorEdits(e); })); @@ -327,7 +330,7 @@ export class ChatEditingTextModelChangeService extends Disposable { private _mirrorEdits(event: IModelContentChangedEvent) { const edit = offsetEditFromContentChanges(event.changes); - if (this._isEditFromUs) { + if (this._isEditFromUs || this._isExternalEditInProgress?.()) { const e_sum = this._originalToModifiedEdit; const e_ai = edit; this._originalToModifiedEdit = e_sum.compose(e_ai); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts index 83af6881d30..684fdf67ea5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookCellEntry.ts @@ -53,6 +53,7 @@ export class ChatEditingNotebookCellEntry extends Disposable { public readonly cell: NotebookCellTextModel, private readonly modifiedModel: ITextModel, private readonly originalModel: ITextModel, + isExternalEditInProgress: (() => boolean) | undefined, disposables: DisposableStore, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService @@ -60,7 +61,7 @@ export class ChatEditingNotebookCellEntry extends Disposable { super(); this.initialContent = this.originalModel.getValue(); this._register(disposables); - this._textModelChangeService = this._register(this.instantiationService.createInstance(ChatEditingTextModelChangeService, this.originalModel, this.modifiedModel, this.state)); + this._textModelChangeService = this._register(this.instantiationService.createInstance(ChatEditingTextModelChangeService, this.originalModel, this.modifiedModel, this.state, isExternalEditInProgress)); this._register(this._textModelChangeService.onDidAcceptOrRejectAllHunks(action => { this.revertMarkdownPreviewState(); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index dfe43815c2e..c137a1f9cf0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -14,16 +14,18 @@ import { Button, ButtonWithIcon } from '../../../../base/browser/ui/button/butto import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../base/common/actions.js'; +import { equals as arraysEqual } from '../../../../base/common/arrays.js'; import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { HistoryNavigator2 } from '../../../../base/common/history.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; +import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; -import { autorun, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; +import { autorun, derived, derivedOpts, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { isEqual } from '../../../../base/common/resources.js'; import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; @@ -73,7 +75,7 @@ import { AccessibilityCommandId } from '../../accessibility/common/accessibility import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from '../../codeEditor/browser/simpleEditorOptions.js'; import { IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatEditingSession, ModifiedFileEntryState } from '../common/chatEditingService.js'; +import { IChatEditingSession, IModifiedFileEntry, ModifiedFileEntryState } from '../common/chatEditingService.js'; import { IChatRequestModeInfo } from '../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../common/chatModes.js'; import { IChatFollowup, IChatService } from '../common/chatService.js'; @@ -365,11 +367,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge readonly inputUri: URI = URI.parse(`${Schemas.vscodeChatInput}:input-${ChatInputPart._counter++}`); - private _workingSetLinesAddedSpan?: HTMLElement; - private _workingSetLinesRemovedSpan?: HTMLElement; + private _workingSetLinesAddedSpan = new Lazy(() => dom.$('.working-set-lines-added')); + private _workingSetLinesRemovedSpan = new Lazy(() => dom.$('.working-set-lines-removed')); private readonly _chatEditsActionsDisposables: DisposableStore = this._register(new DisposableStore()); private readonly _chatEditsDisposables: DisposableStore = this._register(new DisposableStore()); + private readonly _renderingChatEdits = this._register(new MutableDisposable()); + private _chatEditsListPool: CollapsibleListPool; private _chatEditList: IDisposableReference> | undefined; get selectedElements(): URI[] { @@ -1308,6 +1312,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this._handleAttachedContextChange(); })); + this.renderChatEditingSessionState(null); if (this.options.renderWorkingSet) { @@ -1807,7 +1812,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._chatInputTodoListWidget.value?.clear(sessionId, force); } - async renderChatEditingSessionState(chatEditingSession: IChatEditingSession | null) { + renderChatEditingSessionState(chatEditingSession: IChatEditingSession | null) { dom.setVisibility(Boolean(chatEditingSession), this.chatEditingSessionWidgetContainer); if (chatEditingSession) { @@ -1817,21 +1822,25 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._lastEditingSessionId = chatEditingSession.chatSessionId; } - const seenEntries = new ResourceSet(); - const entries: IChatCollapsibleListItem[] = []; - if (chatEditingSession) { - for (const entry of chatEditingSession.entries.get()) { - if (entry.state.get() !== ModifiedFileEntryState.Modified) { + const modifiedEntries = derivedOpts({ equalsFn: arraysEqual }, r => { + return chatEditingSession?.entries.read(r).filter(entry => entry.state.read(r) === ModifiedFileEntryState.Modified) || []; + }); + + const listEntries = derived((reader): IChatCollapsibleListItem[] => { + const seenEntries = new ResourceSet(); + const entries: IChatCollapsibleListItem[] = []; + for (const entry of modifiedEntries.read(reader)) { + if (entry.state.read(reader) !== ModifiedFileEntryState.Modified) { continue; } if (!seenEntries.has(entry.modifiedURI)) { seenEntries.add(entry.modifiedURI); - const linesAdded = entry.linesAdded?.get(); - const linesRemoved = entry.linesRemoved?.get(); + const linesAdded = entry.linesAdded?.read(reader); + const linesRemoved = entry.linesRemoved?.read(reader); entries.push({ reference: entry.modifiedURI, - state: entry.state.get(), + state: ModifiedFileEntryState.Modified, kind: 'reference', options: { status: undefined, @@ -1840,27 +1849,46 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }); } } - } - if (!chatEditingSession || !this.options.renderWorkingSet || !entries.length) { - dom.clearNode(this.chatEditingSessionWidgetContainer); - this._chatEditsDisposables.clear(); - this._chatEditList = undefined; - return; - } + entries.sort((a, b) => { + if (a.kind === 'reference' && b.kind === 'reference') { + if (a.state === b.state || a.state === undefined || b.state === undefined) { + return a.reference.toString().localeCompare(b.reference.toString()); + } + return a.state - b.state; + } + return 0; + }); - // Summary of number of files changed - const innerContainer = this.chatEditingSessionWidgetContainer.querySelector('.chat-editing-session-container.show-file-icons') as HTMLElement ?? dom.append(this.chatEditingSessionWidgetContainer, $('.chat-editing-session-container.show-file-icons')); + return entries; + }); - entries.sort((a, b) => { - if (a.kind === 'reference' && b.kind === 'reference') { - if (a.state === b.state || a.state === undefined || b.state === undefined) { - return a.reference.toString().localeCompare(b.reference.toString()); - } - return a.state - b.state; + const shouldRender = listEntries.map(r => r.length > 0); + + this._renderingChatEdits.value = autorun(reader => { + if (shouldRender.read(reader)) { + this.renderChatEditingSessionWithEntries( + reader.store, + chatEditingSession!, + modifiedEntries, + listEntries, + ); + } else { + dom.clearNode(this.chatEditingSessionWidgetContainer); + this._chatEditsDisposables.clear(); + this._chatEditList = undefined; } - return 0; }); + } + + private renderChatEditingSessionWithEntries( + store: DisposableStore, + chatEditingSession: IChatEditingSession, + modifiedEntries: IObservable, + listEntries: IObservable, + ) { + // Summary of number of files changed + const innerContainer = this.chatEditingSessionWidgetContainer.querySelector('.chat-editing-session-container.show-file-icons') as HTMLElement ?? dom.append(this.chatEditingSessionWidgetContainer, $('.chat-editing-session-container.show-file-icons')); const overviewRegion = innerContainer.querySelector('.chat-editing-session-overview') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-overview')); const overviewTitle = overviewRegion.querySelector('.working-set-title') as HTMLElement ?? dom.append(overviewRegion, $('.working-set-title')); @@ -1897,35 +1925,30 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge ariaLabel: localize('chatEditingSession.toggleWorkingSet', 'Toggle changed files.'), })); - let added = 0; - let removed = 0; - if (chatEditingSession) { - for (const entry of chatEditingSession.entries.get()) { + + + store.add(autorun(reader => { + let added = 0; + let removed = 0; + const entries = modifiedEntries.read(reader); + for (const entry of modifiedEntries.read(reader)) { if (entry.linesAdded && entry.linesRemoved) { - added += entry.linesAdded.get(); - removed += entry.linesRemoved.get(); + added += entry.linesAdded.read(reader); + removed += entry.linesRemoved.read(reader); } } - } + const baseLabel = entries.length === 1 ? localize('chatEditingSession.oneFile.1', '1 file changed') : localize('chatEditingSession.manyFiles.1', '{0} files changed', entries.length); + button.label = baseLabel; - const baseLabel = entries.length === 1 ? localize('chatEditingSession.oneFile.1', '1 file changed') : localize('chatEditingSession.manyFiles.1', '{0} files changed', entries.length); - button.label = baseLabel; - - if (!this._workingSetLinesAddedSpan) { - this._workingSetLinesAddedSpan = dom.$('.working-set-lines-added'); - } - if (!this._workingSetLinesRemovedSpan) { - this._workingSetLinesRemovedSpan = dom.$('.working-set-lines-removed'); - } + 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)); + })); const countsContainer = dom.$('.working-set-line-counts'); button.element.appendChild(countsContainer); - countsContainer.appendChild(this._workingSetLinesAddedSpan); - countsContainer.appendChild(this._workingSetLinesRemovedSpan); - - this._workingSetLinesAddedSpan.textContent = `+${added}`; - this._workingSetLinesRemovedSpan.textContent = `-${removed}`; - button.element.setAttribute('aria-label', localize('chatEditingSession.ariaLabelWithCounts', '{0}, {1} lines added, {2} lines removed', baseLabel, added, removed)); + countsContainer.appendChild(this._workingSetLinesAddedSpan.value); + countsContainer.appendChild(this._workingSetLinesRemovedSpan.value); const applyCollapseState = () => { button.icon = this._workingSetCollapsed ? Codicon.chevronRight : Codicon.chevronDown; @@ -1984,14 +2007,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.append(innerContainer, workingSetContainer); } - const maxItemsShown = 6; - const itemsShown = Math.min(entries.length, maxItemsShown); - const height = itemsShown * 22; - const list = this._chatEditList.object; - list.layout(height); - list.getHTMLElement().style.height = `${height}px`; - list.splice(0, list.length, entries); - this._onDidChangeHeight.fire(); + autorun(reader => { + const entries = listEntries.read(reader); + const maxItemsShown = 6; + const itemsShown = Math.min(entries.length, maxItemsShown); + const height = itemsShown * 22; + const list = this._chatEditList!.object; + list.layout(height); + list.getHTMLElement().style.height = `${height}px`; + list.splice(0, list.length, entries); + this._onDidChangeHeight.fire(); + }); } async renderChatRelatedFiles() { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 0ce8c67d654..21eb3b47104 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -609,7 +609,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration('chat.renderRelatedFiles')) { - this.renderChatEditingSessionState(); + this.input.renderChatRelatedFiles(); } if (e.affectsConfiguration(ChatConfiguration.EditRequests) || e.affectsConfiguration(ChatConfiguration.CheckpointsEnabled)) { @@ -2234,12 +2234,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidChangeContentHeight.fire(); })); - this._register(this.input.attachmentModel.onDidChange(() => { - if (this._editingSession) { - // TODO still needed? Do this inside input part and fire onDidChangeHeight? - this.renderChatEditingSessionState(); - } - })); this._register(this.inputEditor.onDidChangeModelContent(() => { this.parsedChatRequest = undefined; this.updateChatInputContext(); @@ -2335,11 +2329,11 @@ export class ChatWidget extends Disposable implements IChatWidget { if (events?.some(e => e?.kind === 'addRequest') && this.visible) { this.scrollToEnd(); } - - if (this._editingSession) { - this.renderChatEditingSessionState(); - } }))); + this.viewModelDisposables.add(autorun(reader => { + this._editingSession.read(reader); // re-render when the session changes + this.renderChatEditingSessionState(); + })); this.viewModelDisposables.add(this.viewModel.onDidDisposeModel(() => { // Ensure that view state is saved here, because we will load it again when a new model is assigned this.input.saveState(); diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 4f650cedee6..edb1fec84cb 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -19,6 +19,7 @@ import { ICellEditOperation } from '../../notebook/common/notebookCommon.js'; import { IChatAgentResult } from './chatAgents.js'; import { ChatModel, IChatResponseModel } from './chatModel.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; +import { IChatProgress } from './chatService.js'; export const IChatEditingService = createDecorator('chatEditingService'); @@ -117,6 +118,15 @@ export interface IChatEditingSession extends IDisposable { restoreSnapshot(requestId: string, stopId: string | undefined): Promise; + /** + * Marks all edits to the given resources as agent edits until + * {@link stopExternalEdits} is called with the same ID. This is used for + * agents that make changes on-disk rather than streaming edits through the + * chat session. + */ + startExternalEdits(responseModel: IChatResponseModel, operationId: number, resources: URI[]): Promise; + stopExternalEdits(responseModel: IChatResponseModel, operationId: number): Promise; + /** * Gets the snapshot URI of a file at the request and _after_ changes made in the undo stop. * @param uri File in the workspace diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 74403fd6d09..09050924f13 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -85,6 +85,7 @@ export interface IChatTextEditGroup { state?: IChatTextEditGroupState; kind: 'textEditGroup'; done: boolean | undefined; + isExternalEdit?: boolean; } export function isCellTextEditOperation(value: unknown): value is ICellTextEditOperation { @@ -103,6 +104,7 @@ export interface IChatNotebookEditGroup { state?: IChatTextEditGroupState; kind: 'notebookEditGroup'; done: boolean | undefined; + isExternalEdit?: boolean; } /** diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 6169617a68b..a4c25b6b00c 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -11,7 +11,7 @@ import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { autorun, autorunSelfDisposable, IObservable, IReader } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { URI } from '../../../../base/common/uri.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; import { ISelection } from '../../../../editor/common/core/selection.js'; import { Command, Location, TextEdit } from '../../../../editor/common/languages.js'; @@ -191,6 +191,12 @@ export interface IChatUndoStop { id: string; } +export interface IChatExternalEditsDto { + kind: 'externalEdits'; + start: boolean; /** true=start, false=stop */ + resources: UriComponents[]; +} + export interface IChatTaskDto { content: IMarkdownString; kind: 'progressTask'; @@ -245,6 +251,7 @@ export interface IChatTextEdit { edits: TextEdit[]; kind: 'textEdit'; done?: boolean; + isExternalEdit?: boolean; } export interface IChatClearToPreviousToolInvocation { @@ -257,6 +264,7 @@ export interface IChatNotebookEdit { edits: ICellEditOperation[]; kind: 'notebookEdit'; done?: boolean; + isExternalEdit?: boolean; } export interface IChatConfirmation { diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index f05e4fe6915..f4ce44cd9e4 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -167,7 +167,14 @@ declare module 'vscode' { constructor(value: ChatResponseDiffEntry[], title: string, readOnly?: boolean); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart; + export class ChatResponseExternalEditPart { + uris: Uri[]; + callback: () => Thenable; + applied: Thenable; + constructor(uris: Uri[], callback: () => Thenable); + } + + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart; export class ChatResponseWarningPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -301,6 +308,14 @@ declare module 'vscode' { notebookEdit(target: Uri, isDone: true): void; + /** + * Makes an external edit to one or more resources. Changes to the + * resources made within the `callback` and before it resolves will be + * tracked as agent edits. This can be used to track edits made from + * external tools that don't generate simple {@link textEdit textEdits}. + */ + externalEdit(target: Uri | Uri[], callback: () => Thenable): Thenable; + markdownWithVulnerabilities(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]): void; codeblockUri(uri: Uri, isEdit?: boolean): void; push(part: ChatResponsePart | ChatResponseTextEditPart | ChatResponseWarningPart | ChatResponseProgressPart2): void; From 75f33b390ece204e455a797afd1db91529896355 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 30 Oct 2025 00:04:49 +0100 Subject: [PATCH 1838/4355] prompt files: support name (#274020) * prompt files: support name * Update src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../promptSyntax/pickers/promptFilePickers.ts | 39 +-- .../languageProviders/promptHovers.ts | 6 + .../languageProviders/promptValidator.ts | 32 ++- .../common/promptSyntax/promptFileParser.ts | 10 + .../service/promptsServiceImpl.ts | 39 +-- .../promptSytntax/promptHovers.test.ts | 35 +++ .../promptSytntax/promptValidator.test.ts | 225 +++++++++++++++++- 7 files changed, 348 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index 8096052598e..7ade0a9e42c 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -21,7 +21,7 @@ import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID, NEW_AGENT_COMMAND_I import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from '../../../../../../platform/quickinput/common/quickInput.js'; import { askForPromptFileName } from './askForPromptName.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../../base/common/cancellation.js'; import { UILabelProvider } from '../../../../../../base/common/keybindingLabels.js'; import { OS } from '../../../../../../base/common/platform.js'; import { askForPromptSourceFolder } from './askForPromptSourceFolder.js'; @@ -207,11 +207,14 @@ export class PromptFilePickers { * the resource pre-selected in the prompts list. */ async selectPromptFile(options: ISelectOptions): Promise { + + const cts = new CancellationTokenSource(); const quickPick: IPromptQuickPick = this._quickInputService.createQuickPick({ useSeparators: true }); quickPick.busy = true; quickPick.placeholder = localize('searching', 'Searching file system...'); + try { - const fileOptions = await this._createPromptPickItems(options); + const fileOptions = await this._createPromptPickItems(options, cts.token); const activeItem = options.resource && fileOptions.find(f => f.type === 'item' && extUri.isEqual(f.value, options.resource)) as IPromptPickerQuickPickItem | undefined; if (activeItem) { quickPick.activeItems = [activeItem]; @@ -234,6 +237,7 @@ export class PromptFilePickers { disposables.add({ dispose() { quickPick.dispose(); + cts.dispose(true); if (!isResolved) { resolve(undefined); isResolved = true; @@ -270,6 +274,7 @@ export class PromptFilePickers { // when the dialog is hidden, dispose everything disposables.add(quickPick.onDidHide( + disposables.dispose.bind(disposables), )); @@ -279,7 +284,7 @@ export class PromptFilePickers { } - private async _createPromptPickItems(options: ISelectOptions): Promise<(IPromptPickerQuickPickItem | IQuickPickSeparator)[]> { + private async _createPromptPickItems(options: ISelectOptions, token: CancellationToken): Promise<(IPromptPickerQuickPickItem | IQuickPickSeparator)[]> { const buttons: IQuickInputButton[] = []; if (options.optionEdit !== false) { buttons.push(EDIT_BUTTON); @@ -298,11 +303,11 @@ export class PromptFilePickers { if (newItems.length > 0) { result.push(...newItems); } - const locals = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.local, CancellationToken.None); + const locals = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.local, token); if (locals.length) { // Note: No need to localize the label ".github/instructions', since it's the same in all languages. result.push({ type: 'separator', label: '.github/instructions' }); - result.push(...locals.map(l => this._createPromptPickItem(l, buttons))); + result.push(...await Promise.all(locals.map(l => this._createPromptPickItem(l, buttons, token)))); } // Agent instruction files (copilot-instructions.md and AGENTS.md) are added here and not included in the output of @@ -311,8 +316,8 @@ export class PromptFilePickers { if (options.type === PromptsType.instructions) { const useNestedAgentMD = this._configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD); const agentInstructionUris = [ - ...await this._promptsService.listCopilotInstructionsMDs(CancellationToken.None), - ...await this._promptsService.listAgentMDs(CancellationToken.None, !!useNestedAgentMD) + ...await this._promptsService.listCopilotInstructionsMDs(token), + ...await this._promptsService.listAgentMDs(token, !!useNestedAgentMD) ]; agentInstructionFiles = agentInstructionUris.map(uri => { const folderName = this._labelService.getUriLabel(dirname(uri), { relative: true }); @@ -328,18 +333,18 @@ export class PromptFilePickers { } if (agentInstructionFiles.length) { result.push({ type: 'separator', label: localize('separator.workspace-agent-instructions', "Agent Instructions") }); - result.push(...agentInstructionFiles.map(l => this._createPromptPickItem(l, buttons))); + result.push(...await Promise.all(agentInstructionFiles.map(l => this._createPromptPickItem(l, buttons, token)))); } - const exts = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.extension, CancellationToken.None); + const exts = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.extension, token); if (exts.length) { result.push({ type: 'separator', label: localize('separator.extensions', "Extensions") }); - result.push(...exts.map(e => this._createPromptPickItem(e, undefined))); + result.push(...await Promise.all(exts.map(e => this._createPromptPickItem(e, undefined, token)))); } - const users = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.user, CancellationToken.None); + const users = await this._promptsService.listPromptFilesForStorage(options.type, PromptsStorage.user, token); if (users.length) { result.push({ type: 'separator', label: localize('separator.user', "User Data") }); - result.push(...users.map(u => this._createPromptPickItem(u, buttons))); + result.push(...await Promise.all(users.map(u => this._createPromptPickItem(u, buttons, token)))); } return result; } @@ -357,8 +362,10 @@ export class PromptFilePickers { } } - private _createPromptPickItem(promptFile: IPromptPath, buttons: IQuickInputButton[] | undefined): IPromptPickerQuickPickItem { - const promptName = promptFile.name ?? getCleanPromptName(promptFile.uri); + private async _createPromptPickItem(promptFile: IPromptPath, buttons: IQuickInputButton[] | undefined, token: CancellationToken): Promise { + const parsedPromptFile = await this._promptsService.parseNew(promptFile.uri, token).catch(() => undefined); + const promptName = parsedPromptFile?.header?.name ?? promptFile.name ?? getCleanPromptName(promptFile.uri); + const promptDescription = parsedPromptFile?.header?.description ?? promptFile.description; let tooltip: string | undefined; @@ -377,7 +384,7 @@ export class PromptFilePickers { id: promptFile.uri.toString(), type: 'item', label: promptName, - description: promptFile.description, + description: promptDescription, tooltip, value: promptFile.uri, buttons: buttons @@ -476,7 +483,7 @@ export class PromptFilePickers { // prompt deletion was confirmed so delete the prompt file await this._fileService.del(value); - const newEntries = this._createPromptPickItems(options); + const newEntries = this._createPromptPickItems(options, CancellationToken.None); quickPick.items = await newEntries; }); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts index 5e3f5fc5119..9a12834015e 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHovers.ts @@ -72,6 +72,8 @@ export class PromptHoverProvider implements HoverProvider { for (const attribute of header.attributes) { if (attribute.range.containsPosition(position)) { switch (attribute.key) { + case PromptHeaderAttributes.name: + return this.createHover(localize('promptHeader.instructions.name', 'The name of the instruction file as shown in the UI. If not set, the name is derived from the file name.'), attribute.range); case PromptHeaderAttributes.description: return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), attribute.range); case PromptHeaderAttributes.applyTo: @@ -84,6 +86,8 @@ export class PromptHoverProvider implements HoverProvider { for (const attribute of header.attributes) { if (attribute.range.containsPosition(position)) { switch (attribute.key) { + case PromptHeaderAttributes.name: + return this.createHover(localize('promptHeader.agent.name', 'The name of the agent as shown in the UI.'), attribute.range); case PromptHeaderAttributes.description: return this.createHover(localize('promptHeader.agent.description', 'The description of the custom agent, what it does and when to use it.'), attribute.range); case PromptHeaderAttributes.argumentHint: @@ -103,6 +107,8 @@ export class PromptHoverProvider implements HoverProvider { for (const attribute of header.attributes) { if (attribute.range.containsPosition(position)) { switch (attribute.key) { + case PromptHeaderAttributes.name: + return this.createHover(localize('promptHeader.prompt.name', 'The name of the prompt. This is also the name of the slash command that will run this prompt.'), attribute.range); case PromptHeaderAttributes.description: return this.createHover(localize('promptHeader.prompt.description', 'The description of the reusable prompt, what it does and when to use it.'), attribute.range); case PromptHeaderAttributes.argumentHint: diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts index a79544217c9..637aa5f0ca2 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts @@ -16,7 +16,7 @@ import { ChatModeKind } from '../../constants.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js'; import { ILanguageModelToolsService } from '../../languageModelToolsService.js'; import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js'; -import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; +import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, ParsedPromptFile, PROMPT_NAME_REGEXP, PromptHeaderAttributes } from '../promptFileParser.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { ResourceMap } from '../../../../../../base/common/map.js'; @@ -124,6 +124,7 @@ export class PromptValidator { const isGitHubTarget = isGithubTarget(promptType, header.target); this.checkForInvalidArguments(attributes, promptType, isGitHubTarget, report); + this.validateName(attributes, isGitHubTarget, report); this.validateDescription(attributes, report); this.validateArgumentHint(attributes, report); switch (promptType) { @@ -175,6 +176,29 @@ export class PromptValidator { } } + + + private validateName(attributes: IHeaderAttribute[], isGitHubTarget: boolean, report: (markers: IMarkerData) => void): void { + const nameAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.name); + if (!nameAttribute) { + if (isGitHubTarget) { + report(toMarker(localize('promptValidator.nameRequiredForGithubTarget', "The 'name' attribute is required when target is 'github-copilot'."), new Range(1, 1, 1, 4), MarkerSeverity.Error)); + } + return; + } + if (nameAttribute.value.type !== 'string') { + report(toMarker(localize('promptValidator.nameMustBeString', "The 'name' attribute must be a string."), nameAttribute.range, MarkerSeverity.Error)); + return; + } + if (nameAttribute.value.value.trim().length === 0) { + report(toMarker(localize('promptValidator.nameShouldNotBeEmpty', "The 'name' attribute must not be empty."), nameAttribute.value.range, MarkerSeverity.Error)); + return; + } + if (!PROMPT_NAME_REGEXP.test(nameAttribute.value.value)) { + report(toMarker(localize('promptValidator.nameInvalidCharacters', "The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods."), nameAttribute.value.range, MarkerSeverity.Error)); + } + } + private validateDescription(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void { const descriptionAttribute = attributes.find(attr => attr.key === PromptHeaderAttributes.description); if (!descriptionAttribute) { @@ -435,9 +459,9 @@ export class PromptValidator { } const allAttributeNames = { - [PromptsType.prompt]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint], - [PromptsType.instructions]: [PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], - [PromptsType.agent]: [PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target] + [PromptsType.prompt]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.mode, PromptHeaderAttributes.agent, PromptHeaderAttributes.argumentHint], + [PromptsType.instructions]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo, PromptHeaderAttributes.excludeAgent], + [PromptsType.agent]: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.advancedOptions, PromptHeaderAttributes.handOffs, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target] }; const githubCopilotAgentAttributeNames = [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.tools, PromptHeaderAttributes.target, GithubPromptHeaderAttributes.mcpServers]; const recommendedAttributeNames = { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts index e40f3e7787a..33f8415d683 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser.ts @@ -10,6 +10,8 @@ import { URI } from '../../../../../base/common/uri.js'; import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../base/common/yaml.js'; import { Range } from '../../../../../editor/common/core/range.js'; +export const PROMPT_NAME_REGEXP = /^[\p{L}\d_\-\.]+$/u; + export class PromptFileParser { constructor() { } @@ -154,6 +156,14 @@ export class PromptHeader { return undefined; } + public get name(): string | undefined { + const name = this.getStringAttribute(PromptHeaderAttributes.name); + if (name && PROMPT_NAME_REGEXP.test(name)) { + return name; + } + return undefined; + } + public get description(): string | undefined { return this.getStringAttribute(PromptHeaderAttributes.description); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 4d43c09fd17..781160076e5 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -260,11 +260,21 @@ export class PromptsService extends Disposable implements IPromptsService { return value; } + private async getPromptDetails(promptPath: IPromptPath): Promise<{ name: string; description?: string }> { + const parsedPromptFile = await this.parseNew(promptPath.uri, CancellationToken.None).catch(() => undefined); + return { + name: parsedPromptFile?.header?.name ?? promptPath.name ?? getCleanPromptName(promptPath.uri), + description: parsedPromptFile?.header?.description ?? promptPath.description + }; + } + private async getPromptPath(command: string): Promise { const promptPaths = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - const result = promptPaths.find(promptPath => getCommandNameFromPromptPath(promptPath) === command); - if (result) { - return result.uri; + for (const promptPath of promptPaths) { + const details = await this.getPromptDetails(promptPath); + if (details.name === command) { + return promptPath.uri; + } } const textModel = this.modelService.getModels().find(model => model.getLanguageId() === PROMPT_LANGUAGE_ID && getCommandNameFromURI(model.uri) === command); if (textModel) { @@ -275,23 +285,24 @@ export class PromptsService extends Disposable implements IPromptsService { public async getPromptCommandName(uri: URI): Promise { const promptPaths = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - const promptPath = promptPaths.find(promptPath => isEqual(promptPath.uri, uri)); + let promptPath = promptPaths.find(promptPath => isEqual(promptPath.uri, uri)); if (!promptPath) { - return getCommandNameFromURI(uri); + promptPath = { uri, storage: PromptsStorage.local, type: PromptsType.prompt }; // make up a prompt path } - return getCommandNameFromPromptPath(promptPath); + const { name } = await this.getPromptDetails(promptPath); + return name; } public async findPromptSlashCommands(): Promise { const promptFiles = await this.listPromptFiles(PromptsType.prompt, CancellationToken.None); - return promptFiles.map(promptPath => { - const command = getCommandNameFromPromptPath(promptPath); + return Promise.all(promptFiles.map(async promptPath => { + const { name } = await this.getPromptDetails(promptPath); return { - command, + command: name, detail: localize('prompt.file.detail', 'Prompt file: {0}', this.labelService.getUriLabel(promptPath.uri, { relative: true })), promptPath }; - }); + })); } public async getCustomAgents(token: CancellationToken): Promise { @@ -310,7 +321,7 @@ export class PromptsService extends Disposable implements IPromptsService { const customAgents = await Promise.all( agentFiles.map(async (promptPath): Promise => { - const { uri, name: agentName } = promptPath; + const uri = promptPath.uri; const ast = await this.parseNew(uri, token); let metadata: any | undefined; @@ -342,7 +353,7 @@ export class PromptsService extends Disposable implements IPromptsService { metadata, } satisfies IAgentInstructions; - const name = agentName ?? getCleanPromptName(uri); + const name = ast.header?.name ?? promptPath.name ?? getCleanPromptName(uri); const source: IAgentSource = IAgentSource.fromPromptPath(promptPath); if (!ast.header) { @@ -440,10 +451,6 @@ export class PromptsService extends Disposable implements IPromptsService { } -function getCommandNameFromPromptPath(promptPath: IPromptPath): string { - return promptPath.name ?? getCommandNameFromURI(promptPath.uri); -} - function getCommandNameFromURI(uri: URI): string { return basename(uri.fsPath, PROMPT_FILE_EXTENSION); } diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts index bd251e20563..8c263c0b5bd 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptHovers.test.ts @@ -241,6 +241,18 @@ suite('PromptHoverProvider', () => { const hover = await getHover(content, 3, 1, PromptsType.agent); assert.strictEqual(hover, 'The argument-hint describes what inputs the custom agent expects or supports.'); }); + + test('hover on name attribute', async () => { + const content = [ + '---', + 'name: "My Agent"', + 'description: "Test agent"', + 'target: vscode', + '---', + ].join('\n'); + const hover = await getHover(content, 2, 1, PromptsType.agent); + assert.strictEqual(hover, 'The name of the agent as shown in the UI.'); + }); }); suite('prompt hovers', () => { @@ -294,6 +306,17 @@ suite('PromptHoverProvider', () => { ].join('\n'); assert.strictEqual(hover, expected); }); + + test('hover on name attribute', async () => { + const content = [ + '---', + 'name: "My Prompt"', + 'description: "Test prompt"', + '---', + ].join('\n'); + const hover = await getHover(content, 2, 1, PromptsType.prompt); + assert.strictEqual(hover, 'The name of the prompt. This is also the name of the slash command that will run this prompt.'); + }); }); suite('instructions hovers', () => { @@ -322,5 +345,17 @@ suite('PromptHoverProvider', () => { ].join('\n'); assert.strictEqual(hover, expected); }); + + test('hover on name attribute', async () => { + const content = [ + '---', + 'name: "My Instructions"', + 'description: "Test instruction"', + 'applyTo: "**/*.ts"', + '---', + ].join('\n'); + const hover = await getHover(content, 2, 1, PromptsType.instructions); + assert.strictEqual(hover, 'The name of the instruction file as shown in the UI. If not set, the name is derived from the file name.'); + }); }); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts index fdeaec59e07..b6e851fd2e5 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSytntax/promptValidator.test.ts @@ -196,7 +196,7 @@ suite('PromptValidator', () => { assert.deepStrictEqual( markers.map(m => ({ severity: m.severity, message: m.message })), [ - { severity: MarkerSeverity.Warning, message: `Attribute 'applyTo' is not supported in VS Code agent files. Supported: argument-hint, description, handoffs, model, target, tools.` }, + { severity: MarkerSeverity.Warning, message: `Attribute 'applyTo' is not supported in VS Code agent files. Supported: argument-hint, description, handoffs, model, name, target, tools.` }, ] ); }); @@ -263,7 +263,7 @@ suite('PromptValidator', () => { test('github-copilot agent with supported attributes', async () => { const content = [ '---', - 'name: "GitHub Copilot Custom Agent"', + 'name: "GitHub_Copilot_Custom_Agent"', 'description: "GitHub Copilot agent"', 'target: github-copilot', `tools: ['shell', 'edit', 'search', 'custom-agent']`, @@ -278,6 +278,7 @@ suite('PromptValidator', () => { test('github-copilot agent warns about model and handoffs attributes', async () => { const content = [ '---', + 'name: "GitHubAgent"', 'description: "GitHub Copilot agent"', 'target: github-copilot', 'model: MAE 4.1', @@ -300,6 +301,7 @@ suite('PromptValidator', () => { test('github-copilot agent does not validate variable references', async () => { const content = [ '---', + 'name: "GitHubAgent"', 'description: "GitHub Copilot agent"', 'target: github-copilot', `tools: ['shell', 'edit']`, @@ -314,6 +316,7 @@ suite('PromptValidator', () => { test('github-copilot agent rejects unsupported attributes', async () => { const content = [ '---', + 'name: "GitHubAgent"', 'description: "GitHub Copilot agent"', 'target: github-copilot', 'argument-hint: "test hint"', @@ -370,6 +373,131 @@ suite('PromptValidator', () => { // Should validate normally as if target was vscode assert.deepStrictEqual(markers, [], 'Agent without target should validate as vscode'); }); + + test('name attribute validation', async () => { + // Valid name + { + const content = [ + '---', + 'name: "MyAgent"', + 'description: "Test agent"', + 'target: vscode', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'Valid name should not produce errors'); + } + + // Empty name + { + const content = [ + '---', + 'name: ""', + 'description: "Test agent"', + 'target: vscode', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`); + } + + // Non-string name + { + const content = [ + '---', + 'name: 123', + 'description: "Test agent"', + 'target: vscode', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute must be a string.`); + } + + // Invalid characters in name + { + const content = [ + '---', + 'name: "My@Agent!"', + 'description: "Test agent"', + 'target: vscode', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); + } + + // Valid name with allowed characters + { + const content = [ + '---', + 'name: "My_Agent-2.0"', + 'description: "Test agent"', + 'target: vscode', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'Name with allowed characters should be valid'); + } + }); + + test('github-copilot target requires name attribute', async () => { + // Missing name with github-copilot target + { + const content = [ + '---', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + `tools: ['shell']`, + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute is required when target is 'github-copilot'.`); + } + + // Valid name with github-copilot target + { + const content = [ + '---', + 'name: "GitHubAgent"', + 'description: "GitHub Copilot agent"', + 'target: github-copilot', + `tools: ['shell']`, + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'Valid github-copilot agent with name should not produce errors'); + } + + // Missing name with vscode target (should be optional) + { + const content = [ + '---', + 'description: "VS Code agent"', + 'target: vscode', + `tools: ['tool1']`, + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.agent); + assert.deepStrictEqual(markers, [], 'Name should be optional for vscode target'); + } + }); }); suite('instructions', () => { @@ -424,6 +552,54 @@ suite('PromptValidator', () => { assert.strictEqual(markers.length, 1); assert.strictEqual(markers[0].message, 'Invalid header, expecting pairs'); }); + + test('name attribute validation in instructions', async () => { + // Valid name + { + const content = [ + '---', + 'name: "MyInstructions"', + 'description: "Test instructions"', + 'applyTo: "**/*.ts"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.instructions); + assert.deepStrictEqual(markers, [], 'Valid name should not produce errors'); + } + + // Empty name + { + const content = [ + '---', + 'name: ""', + 'description: "Test instructions"', + 'applyTo: "**/*.ts"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.instructions); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`); + } + + // Invalid characters in name + { + const content = [ + '---', + 'name: "My Instructions#"', + 'description: "Test instructions"', + 'applyTo: "**/*.ts"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.instructions); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); + } + }); }); suite('prompts', () => { @@ -532,6 +708,51 @@ suite('PromptValidator', () => { assert.strictEqual(markers[0].severity, MarkerSeverity.Warning); assert.strictEqual(markers[0].message, `The 'tools' attribute is only supported when using agents. Attribute will be ignored.`); }); + + test('name attribute validation in prompts', async () => { + // Valid name + { + const content = [ + '---', + 'name: "MyPrompt"', + 'description: "Test prompt"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.deepStrictEqual(markers, [], 'Valid name should not produce errors'); + } + + // Empty name + { + const content = [ + '---', + 'name: ""', + 'description: "Test prompt"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`); + } + + // Invalid characters in name + { + const content = [ + '---', + 'name: "My Prompt!"', + 'description: "Test prompt"', + '---', + 'Body', + ].join('\n'); + const markers = await validate(content, PromptsType.prompt); + assert.strictEqual(markers.length, 1); + assert.strictEqual(markers[0].severity, MarkerSeverity.Error); + assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`); + } + }); }); suite('body', () => { From 1f29cf93626a98265d02b10527aa20bb4144abd6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 29 Oct 2025 16:10:03 -0700 Subject: [PATCH 1839/4355] fixup --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index c137a1f9cf0..c4abb9d4b91 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -1931,7 +1931,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge let added = 0; let removed = 0; const entries = modifiedEntries.read(reader); - for (const entry of modifiedEntries.read(reader)) { + for (const entry of entries) { if (entry.linesAdded && entry.linesRemoved) { added += entry.linesAdded.read(reader); removed += entry.linesRemoved.read(reader); @@ -2007,7 +2007,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.append(innerContainer, workingSetContainer); } - autorun(reader => { + store.add(autorun(reader => { const entries = listEntries.read(reader); const maxItemsShown = 6; const itemsShown = Math.min(entries.length, maxItemsShown); @@ -2017,7 +2017,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge list.getHTMLElement().style.height = `${height}px`; list.splice(0, list.length, entries); this._onDidChangeHeight.fire(); - }); + })); } async renderChatRelatedFiles() { From ef45feaf4b1cd94ad4d1963b66b23d4352060e6b Mon Sep 17 00:00:00 2001 From: Ross Wollman Date: Wed, 29 Oct 2025 16:12:24 -0700 Subject: [PATCH 1840/4355] chore: non-interactive `workbench.action.exportLogs` variant (#273831) * programmatic export * add programmatic selection of logs --- .../output/browser/output.contribution.ts | 28 +++++++++++++++---- .../contrib/output/browser/outputServices.ts | 25 +++++++++-------- .../services/output/common/output.ts | 2 +- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 63e3d0de084..407aef31dbe 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -41,6 +41,8 @@ import { ViewAction } from '../../../browser/parts/views/viewPane.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { basename } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; +import { hasKey } from '../../../../base/common/types.js'; const IMPORTED_LOG_ID_PREFIX = 'importedLog.'; @@ -426,7 +428,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { if (channel) { const descriptor = outputService.getChannelDescriptors().find(c => c.id === channel.id); if (descriptor) { - await outputService.saveOutputAs(descriptor); + await outputService.saveOutputAs(undefined, descriptor); } } } @@ -718,7 +720,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { }], }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, arg?: { outputPath?: URI; outputChannelIds?: string[] }): Promise { const outputService = accessor.get(IOutputService); const quickInputService = accessor.get(IQuickInputService); const extensionLogs: IOutputChannelDescriptor[] = [], logs: IOutputChannelDescriptor[] = [], userLogs: IOutputChannelDescriptor[] = []; @@ -749,9 +751,25 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { for (const log of userLogs.sort((a, b) => a.label.localeCompare(b.label))) { entries.push(log); } - const result = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log"), canPickMany: true }); - if (result?.length) { - await outputService.saveOutputAs(...result); + + let selectedOutputChannels: IOutputChannelDescriptor[] | undefined; + if (arg?.outputChannelIds) { + const requestedIdsNormalized = arg.outputChannelIds.map(id => id.trim().toLowerCase()); + const candidates = entries.filter((e): e is IOutputChannelDescriptor => { + const isSeparator = hasKey(e, { type: true }) && e.type === 'separator'; + return !isSeparator; + }); + if (requestedIdsNormalized.includes('*')) { + selectedOutputChannels = candidates; + } else { + selectedOutputChannels = candidates.filter(candidate => requestedIdsNormalized.includes(candidate.id.toLowerCase())); + } + } else { + selectedOutputChannels = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log"), canPickMany: true }); + } + + if (selectedOutputChannels?.length) { + await outputService.saveOutputAs(arg?.outputPath, ...selectedOutputChannels); } } })); diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index bd0506795c6..cb0a48affe3 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -414,7 +414,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo return id; } - async saveOutputAs(...channels: IOutputChannelDescriptor[]): Promise { + async saveOutputAs(outputPath?: URI, ...channels: IOutputChannelDescriptor[]): Promise { let channel: IOutputChannel | undefined; if (channels.length > 1) { const compoundChannelId = this.registerCompoundLogChannel(channels); @@ -428,16 +428,19 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } try { - const name = channels.length > 1 ? 'output' : channels[0].label; - const uri = await this.fileDialogService.showSaveDialog({ - title: localize('saveLog.dialogTitle', "Save Output As"), - availableFileSystems: [Schemas.file], - defaultUri: joinPath(await this.fileDialogService.defaultFilePath(), `${name}.log`), - filters: [{ - name, - extensions: ['log'] - }] - }); + let uri: URI | undefined = outputPath; + if (!uri) { + const name = channels.length > 1 ? 'output' : channels[0].label; + uri = await this.fileDialogService.showSaveDialog({ + title: localize('saveLog.dialogTitle', "Save Output As"), + availableFileSystems: [Schemas.file], + defaultUri: joinPath(await this.fileDialogService.defaultFilePath(), `${name}.log`), + filters: [{ + name, + extensions: ['log'] + }] + }); + } if (!uri) { return; diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 42355d4c432..00a11a6f566 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -119,7 +119,7 @@ export interface IOutputService { /** * Save the logs to a file. */ - saveOutputAs(...channels: IOutputChannelDescriptor[]): Promise; + saveOutputAs(outputPath?: URI, ...channels: IOutputChannelDescriptor[]): Promise; /** * Checks if the log level can be set for the given channel. From 75166343309e5aeddf92a6511befec0a62b26c58 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 29 Oct 2025 16:26:42 -0700 Subject: [PATCH 1841/4355] fixup --- src/vs/workbench/api/common/extHost.api.impl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 27eaa67856f..a11edcf042a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1883,6 +1883,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseConfirmationPart: extHostTypes.ChatResponseConfirmationPart, ChatResponseMovePart: extHostTypes.ChatResponseMovePart, ChatResponseExtensionsPart: extHostTypes.ChatResponseExtensionsPart, + ChatResponseExternalEditPart: extHostTypes.ChatResponseExternalEditPart, ChatResponsePullRequestPart: extHostTypes.ChatResponsePullRequestPart, ChatPrepareToolInvocationPart: extHostTypes.ChatPrepareToolInvocationPart, ChatResponseMultiDiffPart: extHostTypes.ChatResponseMultiDiffPart, From eef25fc6d68facf70b457602ec10b7805fede0c6 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:52:21 -0700 Subject: [PATCH 1842/4355] More use of session resource instead of ids This also removes some code that was duplicated to handle local vs contributed sessions --- .../browser/actions/chatSessionActions.ts | 41 ++++----------- .../agentSessions/agentSessionsView.ts | 7 ++- src/vs/workbench/contrib/chat/browser/chat.ts | 14 +++--- .../contrib/chat/browser/chatEditorInput.ts | 50 +++++++++++++------ .../chat/browser/chatSessions/common.ts | 22 +++----- .../chatSessions/view/sessionsViewPane.ts | 28 ++--------- .../contrib/chat/browser/chatWidget.ts | 13 ++--- .../contrib/chat/common/chatServiceImpl.ts | 11 ++++ 8 files changed, 84 insertions(+), 102 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index f9f646f5c27..de6eec0dbf9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -31,7 +31,7 @@ import { IChatSessionsService, localChatSessionType } from '../../common/chatSes import { AGENT_SESSIONS_VIEWLET_ID, ChatConfiguration } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; -import { ChatSessionItemWithProvider, findExistingChatEditorByUri, isLocalChatSessionItem } from '../chatSessions/common.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri } from '../chatSessions/common.js'; import { ChatViewPane } from '../chatViewPane.js'; import { ACTION_ID_OPEN_CHAT, CHAT_CATEGORY } from './chatActions.js'; @@ -184,22 +184,12 @@ export class OpenChatSessionInNewWindowAction extends Action2 { const uri = context.session.resource; // Check if this session is already open in another editor - const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + const existingEditor = findExistingChatEditorByUri(uri, editorGroupsService); if (existingEditor) { - await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + await editorService.openEditor(existingEditor.editor, existingEditor.group); return; } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { return; - } else if (isLocalChatSessionItem(context.session)) { - const options: IChatEditorOptions = { - ignoreInView: true, - }; - // For local sessions, create a new chat editor - await editorService.openEditor({ - resource: context.session.resource, - options, - }, AUX_WINDOW_GROUP); - } else { const options: IChatEditorOptions = { ignoreInView: true, @@ -240,22 +230,13 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { if (context.session.provider?.chatSessionType) { const uri = context.session.resource; // Check if this session is already open in another editor - const existingEditor = findExistingChatEditorByUri(uri, sessionId, editorGroupsService); + const existingEditor = findExistingChatEditorByUri(uri, editorGroupsService); if (existingEditor) { - await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + await editorService.openEditor(existingEditor.editor, existingEditor.group); return; } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + // Already opened in chat widget return; - } else if (isLocalChatSessionItem(context.session)) { - const options: IChatEditorOptions = { - ignoreInView: true, - }; - // For local sessions, create a new chat editor - await editorService.openEditor({ - resource: context.session.resource, - options, - }, SIDE_GROUP); - } else { const options: IChatEditorOptions = { ignoreInView: true, @@ -296,9 +277,9 @@ export class OpenChatSessionInSidebarAction extends Action2 { const sessionId = context.session.id; if (context.session.provider?.chatSessionType) { // Check if this session is already open in another editor - const existingEditor = findExistingChatEditorByUri(context.session.resource, sessionId, editorGroupsService); + const existingEditor = findExistingChatEditorByUri(context.session.resource, editorGroupsService); if (existingEditor) { - await editorService.openEditor(existingEditor.editor, existingEditor.groupId); + await editorService.openEditor(existingEditor.editor, existingEditor.group); return; } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { return; @@ -309,11 +290,7 @@ export class OpenChatSessionInSidebarAction extends Action2 { const chatViewPane = await viewsService.openView(ChatViewId) as ChatViewPane; if (chatViewPane) { // Handle different session types - if (context.session && (isLocalChatSessionItem(context.session))) { - await chatViewPane.loadSession(sessionId); - } else { - await chatViewPane.loadSession(context.session.resource); - } + await chatViewPane.loadSession(context.session.resource); // Focus the chat input chatViewPane.focusInput(); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index ee203cadbf7..38e62ba5161 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -141,10 +141,9 @@ export class AgentSessionsView extends ViewPane { return; } - const uri = session.resource; - const existingSessionEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); + const existingSessionEditor = findExistingChatEditorByUri(session.resource, this.editorGroupsService); if (existingSessionEditor) { - await this.editorGroupsService.getGroup(existingSessionEditor.groupId)?.openEditor(existingSessionEditor.editor, e.editorOptions); + await existingSessionEditor.group.openEditor(existingSessionEditor.editor, e.editorOptions); return; } @@ -158,7 +157,7 @@ export class AgentSessionsView extends ViewPane { sessionOptions.ignoreInView = true; await this.editorService.openEditor({ - resource: uri, + resource: session.resource, options: upcast({ ...sessionOptions, title: { preferred: session.label }, diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index c918ac82755..da33da8c6dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -23,11 +23,12 @@ import { IChatMode } from '../common/chatModes.js'; import { IParsedChatRequest } from '../common/chatParserTypes.js'; import { CHAT_PROVIDER_ID } from '../common/chatParticipantContribTypes.js'; import { IChatElicitationRequest, IChatLocationData, IChatSendRequestOptions } from '../common/chatService.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel } from '../common/chatViewModel.js'; import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; -import { ChatEditorInput } from './chatEditorInput.js'; import { ChatInputPart } from './chatInputPart.js'; +import { findExistingChatEditorByUri } from './chatSessions/common.js'; import { ChatViewPane } from './chatViewPane.js'; import { ChatWidget, IChatViewState, IChatWidgetContrib } from './chatWidget.js'; import { ICodeBlockActionContext } from './codeBlockPart.js'; @@ -55,12 +56,11 @@ export async function showChatWidgetInViewOrEditor(accessor: ServicesAccessor, w if ('viewId' in widget.viewContext) { await accessor.get(IViewsService).openView(widget.location); } else { - for (const group of accessor.get(IEditorGroupsService).groups) { - for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && editor.sessionId === widget.viewModel?.sessionId) { - group.openEditor(editor); - return; - } + const sessionId = widget.viewModel?.sessionId; + if (sessionId) { + const existing = findExistingChatEditorByUri(LocalChatSessionUri.forSession(sessionId), accessor.get(IEditorGroupsService)); + if (existing) { + existing.group.openEditor(existing.editor); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 971a0fc9ad1..835c5e3cd72 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -39,7 +39,12 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler private readonly inputCount: number; private readonly inputName: string; - public sessionId: string | undefined; + private _sessionId: string | undefined; + /** + * @deprecated The session id is only useful for local session. In most cases you should check the resource instead. + */ + public get sessionId() { return this._sessionId; } + private hasCustomTitle: boolean = false; private cachedIcon: ThemeIcon | URI | undefined; @@ -83,13 +88,13 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler if (parsed.chatSessionType !== localChatSessionType) { throw new Error('Chat session URI must be of local chat session type'); } - this.sessionId = parsed.sessionId; + this._sessionId = parsed.sessionId; } // Check if we already have a custom title for this session - const hasExistingCustomTitle = this.sessionId && ( - this.chatService.getSession(this.sessionId)?.title || - this.chatService.getPersistedSessionTitle(this.sessionId)?.trim() + const hasExistingCustomTitle = this._sessionId && ( + this.chatService.getSession(this._sessionId)?.title || + this.chatService.getPersistedSessionTitle(this._sessionId)?.trim() ); this.hasCustomTitle = Boolean(hasExistingCustomTitle); @@ -149,12 +154,29 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } if (this.resource.scheme === Schemas.vscodeChatEditor && otherInput.resource.scheme === Schemas.vscodeChatEditor) { - return this.sessionId === otherInput.sessionId; + return this._sessionId === otherInput._sessionId; } return isEqual(this.resource, otherInput.resource); } + isForSession(sessionUri: URI): boolean { + // Special case where we are a chat editor that has been linked to a local session. + // In this case, check if our id matches. + if (this.resource.scheme === Schemas.vscodeChatEditor) { + if (sessionUri.scheme === Schemas.vscodeLocalChatSession) { + const parsed = LocalChatSessionUri.parse(sessionUri); + if (this._sessionId === parsed?.sessionId) { + return true; + } + } + + return false; + } + + return isEqual(this.resource, sessionUri); + } + override get typeId(): string { return ChatEditorInput.TypeID; } @@ -167,15 +189,15 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } // If we have a sessionId but no resolved model, try to get the title from persisted sessions - if (this.sessionId) { + if (this._sessionId) { // First try the active session registry - const existingSession = this.chatService.getSession(this.sessionId); + const existingSession = this.chatService.getSession(this._sessionId); if (existingSession?.title) { return existingSession.title; } // If not in active registry, try persisted session data - const persistedTitle = this.chatService.getPersistedSessionTitle(this.sessionId); + const persistedTitle = this.chatService.getPersistedSessionTitle(this._sessionId); if (persistedTitle && persistedTitle.trim()) { // Only use non-empty persisted titles return persistedTitle; } @@ -251,8 +273,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const inputType = chatSessionType ?? this.resource.authority; if (this.resource.scheme !== Schemas.vscodeChatEditor && this.resource.scheme !== Schemas.vscodeLocalChatSession) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); - } else if (typeof this.sessionId === 'string') { - this.model = await this.chatService.getOrRestoreSession(this.sessionId) + } else if (typeof this._sessionId === 'string') { + this.model = await this.chatService.getOrRestoreSession(this._sessionId) ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); } else if (!this.options.target) { this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType, inputType: inputType }); @@ -264,7 +286,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return null; } - this.sessionId = this.model.sessionId; + this._sessionId = this.model.sessionId; this._register(this.model.onDidChange((e) => { // When a custom title is set, we no longer need the numeric count if (e && e.kind === 'setCustomTitle' && !this.hasCustomTitle) { @@ -302,8 +324,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override dispose(): void { super.dispose(); - if (this.sessionId) { - this.chatService.clearSession(this.sessionId); + if (this._sessionId) { + this.chatService.clearSession(this._sessionId); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index f3748eaf0c2..fe8720e7165 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -5,10 +5,9 @@ import { fromNow } from '../../../../../base/common/date.js'; import { Schemas } from '../../../../../base/common/network.js'; -import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; -import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js'; @@ -55,11 +54,11 @@ export function getChatSessionType(editor: ChatEditorInput): string { /** * Find existing chat editors that have the same session URI (for external providers) */ -export function findExistingChatEditorByUri(sessionUri: URI, sessionId: string, editorGroupsService: IEditorGroupsService): { editor: ChatEditorInput; groupId: number } | undefined { +export function findExistingChatEditorByUri(sessionUri: URI, editorGroupsService: IEditorGroupsService): { editor: ChatEditorInput; group: IEditorGroup } | undefined { for (const group of editorGroupsService.groups) { for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && (isEqual(editor.resource, sessionUri) || editor.sessionId === sessionId)) { - return { editor, groupId: group.id }; + if (editor instanceof ChatEditorInput && editor.isForSession(sessionUri)) { + return { editor, group }; } } } @@ -146,6 +145,7 @@ export function getSessionItemContextOverlay( if (session.id === 'show-history') { return overlay; } + if (provider) { overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]); } @@ -166,17 +166,7 @@ export function getSessionItemContextOverlay( isActiveSession = true; } else { // Check if session is open in any editor - for (const group of editorGroupsService.groups) { - for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && editor.sessionId === session.id) { - isActiveSession = true; - break; - } - } - if (isActiveSession) { - break; - } - } + isActiveSession = !!findExistingChatEditorByUri(session.resource, editorGroupsService); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts index 02b11a63c96..ce314949ceb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsViewPane.ts @@ -44,9 +44,8 @@ import { ChatConfiguration, ChatEditorTitleMaxLength } from '../../../common/con import { ACTION_ID_OPEN_CHAT } from '../../actions/chatActions.js'; import { ChatViewId, IChatWidgetService } from '../../chat.js'; import { IChatEditorOptions } from '../../chatEditor.js'; -import { ChatViewPane } from '../../chatViewPane.js'; import { ChatSessionTracker } from '../chatSessionTracker.js'; -import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getSessionItemContextOverlay, isLocalChatSessionItem, NEW_CHAT_SESSION_ACTION_ID } from '../common.js'; +import { ChatSessionItemWithProvider, findExistingChatEditorByUri, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../common.js'; import { LocalChatSessionsProvider } from '../localChatSessionsProvider.js'; import { GettingStartedDelegate, GettingStartedRenderer, IGettingStartedItem, SessionsDataSource, SessionsDelegate, SessionsRenderer } from './sessionsTreeRenderer.js'; @@ -455,16 +454,11 @@ export class SessionsViewPane extends ViewPane { } private async openChatSession(session: ChatSessionItemWithProvider) { - if (!session || !session.id) { - return; - } - try { // Check first if we already have an open editor for this session - const uri = session.resource; - const existingEditor = findExistingChatEditorByUri(uri, session.id, this.editorGroupsService); + const existingEditor = findExistingChatEditorByUri(session.resource, this.editorGroupsService); if (existingEditor) { - await this.editorService.openEditor(existingEditor.editor, existingEditor.groupId); + await this.editorService.openEditor(existingEditor.editor, existingEditor.group); return; } if (this.chatWidgetService.getWidgetBySessionId(session.id)) { @@ -476,20 +470,8 @@ export class SessionsViewPane extends ViewPane { return; } - // Handle history items first - if (isLocalChatSessionItem(session)) { - const options: IChatEditorOptions = { - pinned: true, - ignoreInView: true, - preserveFocus: true, - }; - await this.editorService.openEditor({ resource: session.resource, options }); - return; - } else if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { - const chatViewPane = await this.viewsService.openView(ChatViewId) as ChatViewPane; - if (chatViewPane) { - await chatViewPane.loadSession(session.id); - } + if (session.id === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) { + await this.viewsService.openView(ChatViewId); return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 0ce8c67d654..a73d07ec849 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -94,6 +94,7 @@ import { ChatEditorOptions } from './chatOptions.js'; import { ChatViewPane } from './chatViewPane.js'; import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; const $ = dom.$; @@ -146,7 +147,7 @@ export function isInlineChat(widget: IChatWidget): boolean { } interface IChatHistoryListItem { - readonly sessionId: string; + readonly sessionResource: URI; readonly title: string; readonly lastMessageDate: number; readonly isActive: boolean; @@ -1188,7 +1189,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const renderer = this.instantiationService.createInstance( ChatHistoryListRenderer, - async (item) => await this.openHistorySession(item.sessionId), + async (item) => await this.openHistorySession(item.sessionResource), (timestamp, todayMs) => this.formatHistoryTimestamp(timestamp, todayMs), todayMidnightMs ); @@ -1256,8 +1257,8 @@ export class ChatWidget extends Disposable implements IChatWidget { .filter(i => !i.isActive) .sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)) .slice(0, 3) - .map(item => ({ - sessionId: item.sessionId, + .map((item): IChatHistoryListItem => ({ + sessionResource: LocalChatSessionUri.forSession(item.sessionId), title: item.title, lastMessageDate: typeof item.lastMessageDate === 'number' ? item.lastMessageDate : Date.now(), isActive: item.isActive @@ -1291,11 +1292,11 @@ export class ChatWidget extends Disposable implements IChatWidget { return fromNowByDay(last, true, true); } - private async openHistorySession(sessionId: string): Promise { + private async openHistorySession(sessionResource: URI): Promise { try { const viewsService = this.instantiationService.invokeFunction(accessor => accessor.get(IViewsService)); const chatView = await viewsService.openView(ChatViewId); - await chatView?.loadSession?.(sessionId); + await chatView?.loadSession(sessionResource); } catch (e) { this.logService.error('Failed to open chat session from history', e); } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 7aa163a5b44..88d04a229d5 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -13,6 +13,7 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { revive } from '../../../../base/common/marshalling.js'; +import { Schemas } from '../../../../base/common/network.js'; import { autorun, derived, IObservable, ObservableMap } from '../../../../base/common/observable.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { isDefined } from '../../../../base/common/types.js'; @@ -37,6 +38,7 @@ import { IChatSessionsService } from './chatSessionsService.js'; import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; import { IChatSlashCommandService } from './chatSlashCommands.js'; import { IChatTransferService } from './chatTransferService.js'; +import { LocalChatSessionUri } from './chatUri.js'; import { IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js'; import { ChatMessageRole, IChatMessage } from './languageModels.js'; @@ -453,6 +455,15 @@ export class ChatService extends Disposable implements IChatService { async loadSessionForResource(chatSessionResource: URI, location: ChatAgentLocation, token: CancellationToken): Promise { // TODO: Move this into a new ChatModelService + if (chatSessionResource.scheme === Schemas.vscodeLocalChatSession) { + const parsed = LocalChatSessionUri.parse(chatSessionResource); + if (!parsed) { + throw new ErrorNoTelemetry('Invalid local chat session URI'); + } + + return this.getOrRestoreSession(parsed.sessionId); + } + const existing = this._contentProviderSessionModels.get(chatSessionResource); if (existing) { return existing.model; From f9990253e87315618f9a88a4dfa3a63ea2f3467f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 30 Oct 2025 01:17:05 +0100 Subject: [PATCH 1843/4355] Unify extension activation logic (#273989) * extension unification activation logic * fix test --- .../contrib/chat/browser/chat.contribution.ts | 4 +- .../extensions/browser/extensionsActions.ts | 6 ++ .../browser/extensionEnablementService.ts | 25 +++++++ .../common/extensionManagement.ts | 1 + .../extensionEnablementService.test.ts | 5 +- .../common/inlineCompletionsUnification.ts | 70 +++++++++++++++++-- 6 files changed, 100 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 14840bd3a12..81435e2dafa 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -734,9 +734,9 @@ configurationRegistry.registerConfiguration({ mode: 'auto' } }, - 'copilot.extensionUnification.enabled': { + 'chat.extensionUnification.enabled': { type: 'boolean', - description: nls.localize('copilot.extensionUnification.enabled', "Enables unification of GitHub Copilot extensions. When enabled, only the GitHub Copilot Chat extension will be active and all functionality will be served by it. When disabled, GitHub Copilot Chat and GitHub Copilot extensions will operate independently."), + description: nls.localize('chat.extensionUnification.enabled', "Enables unification of GitHub Copilot extensions. When enabled, only the GitHub Copilot Chat extension will be active and all functionality will be served by it. When disabled, GitHub Copilot Chat and GitHub Copilot extensions will operate independently."), default: false, tags: ['experimental'], experiment: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index a84f2db63ae..503999eb9fb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2687,6 +2687,12 @@ export class ExtensionStatusAction extends ExtensionAction { } } + // Unification + if (this.extension.enablementState === EnablementState.DisabledByUnification) { + this.updateStatus({ icon: infoIcon, message: new MarkdownString(localize('extension disabled because of unification', "The Copilot extension has been disabled. Copilot functionality will now be served by the Copilot Chat extension. In case this is causing issues, please create an issue on the vscode repository. The setting {0} can be used to revert back to having both extensions enabled.", '`chat.extensionUnification.enabled`')) }, true); + return; + } + if (!this.workspaceTrustService.isWorkspaceTrusted() && // Extension is disabled by untrusted workspace (this.extension.enablementState === EnablementState.DisabledByTrustRequirement || diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index b1baabfef30..198a5d9dc96 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -31,11 +31,14 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { equals } from '../../../../base/common/arrays.js'; import { isString } from '../../../../base/common/types.js'; import { Delayer } from '../../../../base/common/async.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; const SOURCE = 'IWorkbenchExtensionEnablementService'; type WorkspaceType = { readonly virtual: boolean; readonly trusted: boolean }; +const EXTENSION_UNIFICATION_SETTING = 'chat.extensionUnification.enabled'; + export class ExtensionEnablementService extends Disposable implements IWorkbenchExtensionEnablementService { declare readonly _serviceBrand: undefined; @@ -48,6 +51,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench private extensionsDisabledExtensions: IExtension[] = []; private readonly delayer = this._register(new Delayer(0)); + private readonly _completionsExtensionId: string | undefined; + private readonly _chatExtensionId: string | undefined; + constructor( @IStorageService private readonly storageService: IStorageService, @IGlobalExtensionEnablementService protected readonly globalExtensionEnablementService: IGlobalExtensionEnablementService, @@ -68,6 +74,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IInstantiationService instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, + @IProductService productService: IProductService ) { super(); this.storageManager = this._register(new StorageManager(storageService)); @@ -88,6 +95,16 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this._onDidChangeGloballyDisabledExtensions(extensions, source))); this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this._onDidChangeExtensions([], [], false))); + // Extension unification + this._completionsExtensionId = productService.defaultChatAgent?.extensionId.toLowerCase(); + this._chatExtensionId = productService.defaultChatAgent?.chatExtensionId.toLowerCase(); + const unificationExtensions = [this._completionsExtensionId, this._chatExtensionId].filter(id => !!id); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(EXTENSION_UNIFICATION_SETTING)) { + this._onEnablementChanged.fire(this.extensionsManager.extensions.filter(ext => unificationExtensions.includes(ext.identifier.id.toLowerCase()))); + } + })); + // delay notification for extensions disabled until workbench restored if (this.allUserExtensionsDisabled) { this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { @@ -384,6 +401,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench enablementState = EnablementState.DisabledByExtensionDependency; } + else if (this._isDisabledByUnification(extension.identifier)) { + enablementState = EnablementState.DisabledByUnification; + } + else if (!isEnabled && this._isEnabledInEnv(extension)) { enablementState = EnablementState.EnabledByEnvironment; } @@ -539,6 +560,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return this.globalExtensionEnablementService.getDisabledExtensions().some(e => areSameExtensions(e, identifier)); } + private _isDisabledByUnification(identifier: IExtensionIdentifier): boolean { + return identifier.id.toLowerCase() === this._completionsExtensionId && this.configurationService.getValue(EXTENSION_UNIFICATION_SETTING); + } + private _enableExtension(identifier: IExtensionIdentifier): Promise { this._removeFromWorkspaceDisabledExtensions(identifier); this._removeFromWorkspaceEnabledExtensions(identifier); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 5e8a14cf039..de72cdf9671 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -108,6 +108,7 @@ export const enum EnablementState { DisabledByInvalidExtension, DisabledByAllowlist, DisabledByExtensionDependency, + DisabledByUnification, // Temporary TODO@benibenj remove when unification transition is complete DisabledGlobally, DisabledWorkspace, EnabledGlobally, diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 1f5a36a652f..bddf2b55a64 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from '../../../../../platform/configuration/com import { URI } from '../../../../../base/common/uri.js'; import { Schemas } from '../../../../../base/common/network.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; -import { TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js'; +import { productService, TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js'; import { GlobalExtensionEnablementService } from '../../../../../platform/extensionManagement/common/extensionEnablementService.js'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from '../../../../../platform/userDataSync/common/userDataSyncAccount.js'; import { IUserDataSyncEnablementService } from '../../../../../platform/userDataSync/common/userDataSync.js'; @@ -97,7 +97,8 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { new class extends mock() { override requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { return Promise.resolve(true); } }, instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, disposables.add(new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService()))), instantiationService, - new NullLogService() + new NullLogService(), + productService ); this._register(disposables); } diff --git a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts index 7424063838f..f5876c2fa10 100644 --- a/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts +++ b/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts @@ -8,9 +8,14 @@ import { Event, Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionType } from '../../../../platform/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; import { IWorkbenchAssignmentService } from '../../assignment/common/assignmentService.js'; +import { EnablementState, IWorkbenchExtensionEnablementService } from '../../extensionManagement/common/extensionManagement.js'; +import { IExtensionService } from '../../extensions/common/extensions.js'; export const IInlineCompletionsUnificationService = createDecorator('inlineCompletionsUnificationService'); @@ -35,7 +40,7 @@ const MODEL_UNIFICATION_FF = 'inlineCompletionsUnificationModel'; export const isRunningUnificationExperiment = new RawContextKey('isRunningUnificationExperiment', false); -const ExtensionUnificationSetting = 'copilot.extensionUnification.enabled'; +const ExtensionUnificationSetting = 'chat.extensionUnification.enabled'; export class InlineCompletionsUnificationImpl extends Disposable implements IInlineCompletionsUnificationService { readonly _serviceBrand: undefined; @@ -50,12 +55,23 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl private readonly _onDidChangeExtensionUnification = this._register(new Emitter()); + private readonly _completionsExtensionId: string | undefined; + private readonly _chatExtensionId: string | undefined; + constructor( @IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, + @IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IProductService productService: IProductService ) { super(); + this._completionsExtensionId = productService.defaultChatAgent?.extensionId.toLowerCase(); + this._chatExtensionId = productService.defaultChatAgent?.chatExtensionId.toLowerCase(); + const relevantExtensions = [this._completionsExtensionId, this._chatExtensionId].filter((id): id is string => !!id); + this.isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService); this._assignmentService.addTelemetryAssignmentFilter({ @@ -63,30 +79,41 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl onDidChange: this._onDidChangeExtensionUnification.event }); - this._configurationService.onDidChangeConfiguration(e => { + this._register(this._extensionEnablementService.onEnablementChanged((extensions) => { + if (extensions.some(ext => relevantExtensions.includes(ext.identifier.id.toLowerCase()))) { + this._update(); + } + })); + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(ExtensionUnificationSetting)) { this._update(); } - }); + })); + this._register(this._extensionService.onDidChangeExtensions(({ added }) => { + if (added.some(ext => relevantExtensions.includes(ext.identifier.value.toLowerCase()))) { + this._update(); + } + })); this._register(this._assignmentService.onDidRefetchAssignments(() => this._update())); this._update(); } private async _update(): Promise { - // TODO@benibenj@sandy081 extensionUnificationEnabled should depend on extension enablement state - const extensionUnificationEnabled = this._configurationService.getValue(ExtensionUnificationSetting); - const [codeUnificationFF, modelUnificationFF] = await Promise.all([ + const [codeUnificationFF, modelUnificationFF, extensionUnificationEnabled] = await Promise.all([ this._assignmentService.getTreatment(CODE_UNIFICATION_FF), this._assignmentService.getTreatment(MODEL_UNIFICATION_FF), + this._isExtensionUnificationActive() ]); + const extensionStatesMatchUnificationSetting = this._configurationService.getValue(ExtensionUnificationSetting) === extensionUnificationEnabled; + // Intentionally read the current experiments after fetching the treatments const currentExperiments = await this._assignmentService.getCurrentExperiments(); const newState = new InlineCompletionsUnificationState( codeUnificationFF === true, modelUnificationFF === true, extensionUnificationEnabled, - currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX) || (extensionUnificationEnabled && exp.startsWith(EXTENSION_UNIFICATION_PREFIX))) ?? [] + currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX) || (extensionStatesMatchUnificationSetting && exp.startsWith(EXTENSION_UNIFICATION_PREFIX))) ?? [] ); if (this._state.equals(newState)) { return; @@ -101,6 +128,35 @@ export class InlineCompletionsUnificationImpl extends Disposable implements IInl this._onDidChangeExtensionUnification.fire(); } } + + private async _isExtensionUnificationActive(): Promise { + if (!this._configurationService.getValue(ExtensionUnificationSetting)) { + return false; + } + + if (!this._completionsExtensionId || !this._chatExtensionId) { + return false; + } + + const [completionsExtension, chatExtension, installedExtensions] = await Promise.all([ + this._extensionService.getExtension(this._completionsExtensionId), + this._extensionService.getExtension(this._chatExtensionId), + this._extensionManagementService.getInstalled(ExtensionType.User) + ]); + + if (!chatExtension || completionsExtension) { + return false; + } + + const completionExtensionInstalled = installedExtensions.find(ext => ext.identifier.id.toLowerCase() === this._completionsExtensionId); + if (!completionExtensionInstalled) { + return false; + } + + const completionsExtensionDisabledByUnification = this._extensionEnablementService.getEnablementState(completionExtensionInstalled) === EnablementState.DisabledByUnification; + + return !!chatExtension && completionsExtensionDisabledByUnification; + } } class InlineCompletionsUnificationState implements IInlineCompletionsUnificationState { From 5eac64bc9ef10c41100c28b2bc1c373318abdbf0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 30 Oct 2025 07:42:50 +0100 Subject: [PATCH 1844/4355] tools not applied globally (#273985) tools not applied globaly --- .../observable/common/observableMemento.ts | 62 ++++++++----------- .../chat/browser/actions/chatToolActions.ts | 3 +- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/vs/platform/observable/common/observableMemento.ts b/src/vs/platform/observable/common/observableMemento.ts index 222f62c2da8..e9495ffd820 100644 --- a/src/vs/platform/observable/common/observableMemento.ts +++ b/src/vs/platform/observable/common/observableMemento.ts @@ -15,9 +15,8 @@ import { IStorageService, StorageScope, StorageTarget } from '../../storage/comm interface IObservableMementoOpts { defaultValue: T; key: string; - /** Storage options, defaults to JSON storage if the defaultValue is an object */ - toStorage?: (value: T) => string; - fromStorage?: (value: string) => T; + toStorage: (value: T) => string; + fromStorage: (value: string) => T; } /** @@ -38,50 +37,37 @@ export function observableMemento(opts: IObservableMementoOpts) { */ export class ObservableMemento extends ObservableValue implements IDisposable { private readonly _store = new DisposableStore(); - private _didChange = false; + private _noStorageUpdateNeeded = false; constructor( - opts: IObservableMementoOpts, - storageScope: StorageScope, - storageTarget: StorageTarget, - @IStorageService storageService: IStorageService, + private readonly opts: IObservableMementoOpts, + private readonly storageScope: StorageScope, + private readonly storageTarget: StorageTarget, + @IStorageService private readonly storageService: IStorageService, ) { - if (opts.defaultValue && typeof opts.defaultValue === 'object') { - opts.toStorage ??= (value: T) => JSON.stringify(value); - opts.fromStorage ??= (value: string) => JSON.parse(value); - } - - let initialValue = opts.defaultValue; - - const fromStorage = storageService.get(opts.key, storageScope); - if (fromStorage !== undefined) { - if (opts.fromStorage) { + const getStorageValue = (): T => { + const fromStorage = storageService.get(opts.key, storageScope); + if (fromStorage !== undefined) { try { - initialValue = opts.fromStorage(fromStorage); + return opts.fromStorage(fromStorage); } catch { - initialValue = opts.defaultValue; + return opts.defaultValue; } } - } + return opts.defaultValue; + }; + const initialValue = getStorageValue(); super(new DebugNameData(undefined, `storage/${opts.key}`, undefined), initialValue, strictEquals, DebugLocation.ofCaller()); const didChange = storageService.onDidChangeValue(storageScope, opts.key, this._store); - // only take external changes if there aren't local changes we've made this._store.add(didChange((e) => { - if (e.external && e.key === opts.key && !this._didChange) { - this.set(opts.defaultValue, undefined); - } - })); - - this._store.add(storageService.onWillSaveState(() => { - if (this._didChange) { - this._didChange = false; - const value = this.get(); - if (opts.toStorage) { - storageService.store(opts.key, opts.toStorage(value), storageScope, storageTarget); - } else { - storageService.store(opts.key, String(value), storageScope, storageTarget); + if (e.external && e.key === opts.key) { + this._noStorageUpdateNeeded = true; + try { + this.set(getStorageValue(), undefined); + } finally { + this._noStorageUpdateNeeded = false; } } })); @@ -89,7 +75,11 @@ export class ObservableMemento extends ObservableValue implements IDisposa protected override _setValue(newValue: T): void { super._setValue(newValue); - this._didChange = true; + if (this._noStorageUpdateNeeded) { + return; + } + const valueToStore = this.opts.toStorage(this.get()); + this.storageService.store(this.opts.key, valueToStore, this.storageScope, this.storageTarget); } dispose(): void { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts index fd559a9a844..82e8b10b6bc 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts @@ -171,8 +171,9 @@ class ConfigureToolsAction extends Action2 { break; case ToolsScope.Global: placeholder = localize('chat.tools.placeholder.global', "Select tools that are available to chat."); - description = undefined; + description = localize('chat.tools.description.global', "The selected tools will be applied globally for all chat sessions that use the default agent."); break; + } const result = await instaService.invokeFunction(showToolsPicker, placeholder, description, () => entriesMap.get()); From e972f646b5596ab322d23555e8d06829cb07425e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 29 Oct 2025 23:43:07 -0700 Subject: [PATCH 1845/4355] Remove setting to hide contrib (#273991) --- .../contrib/chat/browser/chatSessions.contribution.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index d4957cf4570..75c238c4f26 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -14,7 +14,6 @@ import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IRelaxedExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; @@ -269,7 +268,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ @IExtensionService private readonly _extensionService: IExtensionService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IMenuService private readonly _menuService: IMenuService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService ) { super(); @@ -283,10 +281,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ continue; } for (const contribution of ext.value) { - if (contribution.type === 'openai-codex' && !this._configurationService.getValue('chat.experimental.codex.enabled')) { - continue; - } - this._register(this.registerContribution(contribution, ext.description)); } } From 6ec31b78c3a84f42c8dff884bf854ce8f3731ab5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:01:49 -0700 Subject: [PATCH 1846/4355] Enable rich command detection on tasks (#274000) * Enable rich command detection on tasks Fixes #272945 * Fix test case --- .../common/xterm/shellIntegrationAddon.ts | 28 ++++++++++--- .../tasks/browser/terminalTaskSystem.ts | 30 ++++++++++---- .../browser/terminalEscapeSequences.ts | 3 +- .../xterm/shellIntegrationAddon.test.ts | 39 ++++++++++++++++++- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 1bd2eec9b6d..f33f37e68e8 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -490,7 +490,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati const arg1 = args[1]; let commandLine: string; if (arg0 !== undefined) { - commandLine = deserializeMessage(arg0); + commandLine = deserializeVSCodeOscMessage(arg0); } else { commandLine = ''; } @@ -510,7 +510,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati const arg1 = args[1]; if (arg0 !== undefined) { try { - const env = JSON.parse(deserializeMessage(arg0)); + const env = JSON.parse(deserializeVSCodeOscMessage(arg0)); this._createOrGetShellEnvDetection().setEnvironment(env, arg1 === this._nonce); } catch (e) { this._logService.warn('Failed to parse environment from shell integration sequence', arg0); @@ -528,7 +528,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati const arg1 = args[1]; const arg2 = args[2]; if (arg0 !== undefined && arg1 !== undefined) { - const env = deserializeMessage(arg1); + const env = deserializeVSCodeOscMessage(arg1); this._createOrGetShellEnvDetection().deleteEnvironmentSingleVar(arg0, env, arg2 === this._nonce); } return true; @@ -538,7 +538,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati const arg1 = args[1]; const arg2 = args[2]; if (arg0 !== undefined && arg1 !== undefined) { - const env = deserializeMessage(arg1); + const env = deserializeVSCodeOscMessage(arg1); this._createOrGetShellEnvDetection().setEnvironmentSingleVar(arg0, env, arg2 === this._nonce); } return true; @@ -557,7 +557,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } case VSCodeOscPt.Property: { const arg0 = args[0]; - const deserialized = arg0 !== undefined ? deserializeMessage(arg0) : ''; + const deserialized = arg0 !== undefined ? deserializeVSCodeOscMessage(arg0) : ''; const { key, value } = parseKeyValueAssignment(deserialized); if (value === undefined) { return true; @@ -787,7 +787,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } } -export function deserializeMessage(message: string): string { +export function deserializeVSCodeOscMessage(message: string): string { return message.replaceAll( // Backslash ('\') followed by an escape operator: either another '\', or 'x' and two hex chars. /\\(\\|x([0-9a-f]{2}))/gi, @@ -796,6 +796,22 @@ export function deserializeMessage(message: string): string { (_match: string, op: string, hex?: string) => hex ? String.fromCharCode(parseInt(hex, 16)) : op); } +export function serializeVSCodeOscMessage(message: string): string { + return message.replace( + // Match backslash ('\'), semicolon (';'), or characters 0x20 and below + /[\\;\x00-\x20]/g, + (char: string) => { + // Escape backslash as '\\' + if (char === '\\') { + return '\\\\'; + } + // Escape other characters as '\xAB' where AB is the hex representation + const charCode = char.charCodeAt(0); + return `\\x${charCode.toString(16).padStart(2, '0')}`; + } + ); +} + export function parseKeyValueAssignment(message: string): { key: string; value: string | undefined } { const separatorIndex = message.indexOf('='); if (separatorIndex === -1) { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index bd6813a51ec..d587c7434fd 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -52,6 +52,8 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows import { IPathService } from '../../../services/path/common/pathService.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { TaskProblemMonitor } from './taskProblemMonitor.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; +import { serializeVSCodeOscMessage } from '../../../../platform/terminal/common/xterm/shellIntegrationAddon.js'; interface ITerminalData { terminal: ITerminalInstance; @@ -176,6 +178,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { taskShellIntegrationStartSequence(cwd: string | URI | undefined): string { return ( + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.HasRichCommandDetection}=True`) + VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + (cwd @@ -185,8 +188,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { VSCodeSequence(VSCodeOscPt.CommandStart) ); } - get taskShellIntegrationOutputSequence(): string { - return VSCodeSequence(VSCodeOscPt.CommandExecuted); + getTaskShellIntegrationOutputSequence(commandLineInfo: { commandLine: string; nonce: string } | undefined): string { + return ( + (commandLineInfo + ? VSCodeSequence(VSCodeOscPt.CommandLine, `${serializeVSCodeOscMessage(commandLineInfo.commandLine)};${commandLineInfo.nonce}`) + : '' + ) + + VSCodeSequence(VSCodeOscPt.CommandExecuted) + ); } constructor( @@ -1285,6 +1294,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } const combinedShellArgs = this._addAllArgument(toAdd, shellArgs); combinedShellArgs.push(commandLine); + shellLaunchConfig.shellIntegrationNonce = generateUuid(); + const commandLineInfo = { + commandLine, + nonce: shellLaunchConfig.shellIntegrationNonce + }; shellLaunchConfig.args = windowsShellArgs ? combinedShellArgs.join(' ') : combinedShellArgs; if (task.command.presentation && task.command.presentation.echo) { if (needsFolderQualification && workspaceFolder) { @@ -1293,16 +1307,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', folder, commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', folder, commandLine), { excludeLeadingNewLine: true }) + this.getTaskShellIntegrationOutputSequence(commandLineInfo); } else { shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence(cwd) + formatMessageForTerminal(nls.localize({ key: 'task.executing.shellIntegration', comment: ['The task command line or label'] - }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.getTaskShellIntegrationOutputSequence(commandLineInfo); } } else { shellLaunchConfig.initialText = { - text: this.taskShellIntegrationStartSequence(cwd) + this.taskShellIntegrationOutputSequence, + text: this.taskShellIntegrationStartSequence(cwd) + this.getTaskShellIntegrationOutputSequence(commandLineInfo), trailingNewLine: false }; } @@ -1336,16 +1350,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence(cwd) + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.getTaskShellIntegrationOutputSequence(undefined); } else { shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence(cwd) + formatMessageForTerminal(nls.localize({ key: 'task.executing.shell-integration', comment: ['The task command line or label'] - }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.getTaskShellIntegrationOutputSequence(undefined); } } else { shellLaunchConfig.initialText = { - text: this.taskShellIntegrationStartSequence(cwd) + this.taskShellIntegrationOutputSequence, + text: this.taskShellIntegrationStartSequence(cwd) + this.getTaskShellIntegrationOutputSequence(undefined), trailingNewLine: false }; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts b/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts index 2d703d86c37..ce9e5a89ab1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts @@ -90,7 +90,8 @@ export const enum VSCodeOscPt { export const enum VSCodeOscProperty { Task = 'Task', - Cwd = 'Cwd' + Cwd = 'Cwd', + HasRichCommandDetection = 'HasRichCommandDetection', } /** diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts index c33a16cbf88..a18a4dff7b5 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts @@ -10,7 +10,7 @@ import { importAMDNodeModule } from '../../../../../../amdX.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { NullLogService } from '../../../../../../platform/log/common/log.js'; import { ITerminalCapabilityStore, TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; -import { deserializeMessage, parseKeyValueAssignment, parseMarkSequence, ShellIntegrationAddon } from '../../../../../../platform/terminal/common/xterm/shellIntegrationAddon.js'; +import { deserializeVSCodeOscMessage, serializeVSCodeOscMessage, parseKeyValueAssignment, parseMarkSequence, ShellIntegrationAddon } from '../../../../../../platform/terminal/common/xterm/shellIntegrationAddon.js'; import { writeP } from '../../../browser/terminalTestHelpers.js'; class TestShellIntegrationAddon extends ShellIntegrationAddon { @@ -292,7 +292,42 @@ suite('ShellIntegrationAddon', () => { ]; cases.forEach(([title, input, expected]) => { - test(title, () => strictEqual(deserializeMessage(input), expected)); + test(title, () => strictEqual(deserializeVSCodeOscMessage(input), expected)); + }); + }); + + suite('serializeVSCodeOscMessage', () => { + // A single literal backslash, in order to avoid confusion about whether we are escaping test data or testing escapes. + const Backslash = '\\' as const; + const Newline = '\n' as const; + const Semicolon = ';' as const; + + type TestCase = [title: string, input: string, expected: string]; + const cases: TestCase[] = [ + ['empty', '', ''], + ['basic', 'value', 'value'], + ['space', 'some thing', `some${Backslash}x20thing`], + ['backslash', Backslash, `${Backslash}${Backslash}`], + ['non-initial backslash', `foo${Backslash}`, `foo${Backslash}${Backslash}`], + ['two backslashes', `${Backslash}${Backslash}`, `${Backslash}${Backslash}${Backslash}${Backslash}`], + ['backslash amidst text', `Hello${Backslash}there`, `Hello${Backslash}${Backslash}there`], + ['semicolon', Semicolon, `${Backslash}x3b`], + ['non-initial semicolon', `foo${Semicolon}`, `foo${Backslash}x3b`], + ['semicolon amidst text', `some${Semicolon}thing`, `some${Backslash}x3bthing`], + ['newline', Newline, `${Backslash}x0a`], + ['non-initial newline', `foo${Newline}`, `foo${Backslash}x0a`], + ['newline amidst text', `some${Newline}thing`, `some${Backslash}x0athing`], + ['tab character', '\t', `${Backslash}x09`], + ['carriage return', '\r', `${Backslash}x0d`], + ['null character', '\x00', `${Backslash}x00`], + ['space character (0x20)', ' ', `${Backslash}x20`], + ['character above 0x20', '!', '!'], + ['multiple special chars', `hello${Newline}world${Semicolon}test${Backslash}end`, `hello${Backslash}x0aworld${Backslash}x3btest${Backslash}${Backslash}end`], + ['PS1 with escape sequences', `\x1b]633;A\x07\\[\x1b]0;\\u@\\h:\\w\\a\\]\x1b]633;B\x07`, `${Backslash}x1b]633${Backslash}x3bA${Backslash}x07${Backslash}${Backslash}[${Backslash}x1b]0${Backslash}x3b${Backslash}${Backslash}u@${Backslash}${Backslash}h:${Backslash}${Backslash}w${Backslash}${Backslash}a${Backslash}${Backslash}]${Backslash}x1b]633${Backslash}x3bB${Backslash}x07`] + ]; + + cases.forEach(([title, input, expected]) => { + test(title, () => strictEqual(serializeVSCodeOscMessage(input), expected)); }); }); From 08d06ad8c19216f733806f31f872b18031593acc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 08:24:49 +0100 Subject: [PATCH 1847/4355] smoke - more checks for AI test (#274002) * smoke - more checks for AI test * change to set --- test/smoke/src/areas/chat/chat.test.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/smoke/src/areas/chat/chat.test.ts b/test/smoke/src/areas/chat/chat.test.ts index 8a9a8536dde..2299df8a3ff 100644 --- a/test/smoke/src/areas/chat/chat.test.ts +++ b/test/smoke/src/areas/chat/chat.test.ts @@ -21,17 +21,19 @@ export function setup(logger: Logger) { await app.code.waitForElements('.noauxiliarybar', true, elements => elements.length === 1); // assert that AI related commands are not present - const commands = await app.workbench.quickaccess.getVisibleCommandNames('chat'); let expectedFound = false; - const unexpectedFound: string[] = []; - for (const command of commands) { - if (command === 'Chat: Use AI Features with Copilot for free...') { - expectedFound = true; - continue; - } - - if (command.includes('Chat') || command.includes('Agent') || command.includes('Copilot')) { - unexpectedFound.push(command); + const unexpectedFound: Set = new Set(); + for (const term of ['chat', 'agent', 'copilot']) { + const commands = await app.workbench.quickaccess.getVisibleCommandNames(term); + for (const command of commands) { + if (command === 'Chat: Use AI Features with Copilot for free...') { + expectedFound = true; + continue; + } + + if (command.includes('Chat') || command.includes('Agent') || command.includes('Copilot')) { + unexpectedFound.add(command); + } } } @@ -39,8 +41,8 @@ export function setup(logger: Logger) { throw new Error(`Expected AI related command not found`); } - if (unexpectedFound.length > 0) { - throw new Error(`Unexpected AI related commands found after having disabled AI features: ${JSON.stringify(unexpectedFound, undefined, 0)}`); + if (unexpectedFound.size > 0) { + throw new Error(`Unexpected AI related commands found after having disabled AI features: ${JSON.stringify(Array.from(unexpectedFound), undefined, 0)}`); } }); }); From afd4a5ee0cda0902c789df5deff9effd7736545f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 09:08:05 +0100 Subject: [PATCH 1848/4355] Debugging web server + edge shows odd coloring in chat input (fix #273973) (#274051) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 828972f3e7c..95b268a1c86 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1280,7 +1280,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor, .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor .monaco-editor-background { - background-color: var(--vscode-input-background); + background-color: var(--vscode-input-background) !important; } .interactive-session .interactive-input-part.editing .chat-input-container .chat-editor-container .monaco-editor, From ec7b5e7fa4f16991c7024a4224b790436d15a789 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 30 Oct 2025 10:51:57 +0100 Subject: [PATCH 1849/4355] Add dotenv support (#273074) * Add dotenv support This adds basic language support for the dotenv language. This includes syntax highlighting and a language configuration file. The dotenv language is applied to files with the `.env` extension, files named `.env`, `.flaskenv`, or `user-dirs.dirs`, and file names starting with `.env.`. The grammar is downloaded from https://github.com/dotenv-org/dotenv-vscode. Closes #267333 * Add tests * Remove ini and shellscript language conflicting with dotenv --------- Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com> --- extensions/dotenv/.vscodeignore | 2 + extensions/dotenv/cgmanifest.json | 40 ++++++ extensions/dotenv/language-configuration.json | 24 ++++ extensions/dotenv/package.json | 48 +++++++ extensions/dotenv/package.nls.json | 4 + .../dotenv/syntaxes/dotenv.tmLanguage.json | 127 ++++++++++++++++++ extensions/ini/package.json | 6 +- extensions/shellscript/package.json | 3 - .../test/colorize-fixtures/test.env | 3 + .../test/colorize-results/test_env.json | 100 ++++++++++++++ .../test_env.json | 1 + 11 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 extensions/dotenv/.vscodeignore create mode 100644 extensions/dotenv/cgmanifest.json create mode 100644 extensions/dotenv/language-configuration.json create mode 100644 extensions/dotenv/package.json create mode 100644 extensions/dotenv/package.nls.json create mode 100644 extensions/dotenv/syntaxes/dotenv.tmLanguage.json create mode 100644 extensions/vscode-colorize-tests/test/colorize-fixtures/test.env create mode 100644 extensions/vscode-colorize-tests/test/colorize-results/test_env.json create mode 100644 extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json diff --git a/extensions/dotenv/.vscodeignore b/extensions/dotenv/.vscodeignore new file mode 100644 index 00000000000..0a622e7e300 --- /dev/null +++ b/extensions/dotenv/.vscodeignore @@ -0,0 +1,2 @@ +test/** +cgmanifest.json diff --git a/extensions/dotenv/cgmanifest.json b/extensions/dotenv/cgmanifest.json new file mode 100644 index 00000000000..0d7c5c8dc98 --- /dev/null +++ b/extensions/dotenv/cgmanifest.json @@ -0,0 +1,40 @@ +{ + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "dotenv-org/dotenv-vscode", + "repositoryUrl": "https://github.com/dotenv-org/dotenv-vscode", + "commitHash": "e7e41baa5b23e01c1ff0567a4e596c24860e7def" + } + }, + "licenseDetail": [ + "MIT License", + "", + "Copyright (c) 2022 Scott Motte", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ], + "license": "MIT License", + "version": "0.26.0" + } + ], + "version": 1 +} diff --git a/extensions/dotenv/language-configuration.json b/extensions/dotenv/language-configuration.json new file mode 100644 index 00000000000..77e01182ddd --- /dev/null +++ b/extensions/dotenv/language-configuration.json @@ -0,0 +1,24 @@ +{ + "comments": { + "lineComment": "#" + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} diff --git a/extensions/dotenv/package.json b/extensions/dotenv/package.json new file mode 100644 index 00000000000..2adbc86ff36 --- /dev/null +++ b/extensions/dotenv/package.json @@ -0,0 +1,48 @@ +{ + "name": "dotenv", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "scripts": { + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dotenv-org/dotenv-vscode syntaxes/dotenv.tmLanguage.json ./syntaxes/dotenv.tmLanguage.json" + }, + "categories": ["Programming Languages"], + "contributes": { + "languages": [ + { + "id": "dotenv", + "extensions": [ + ".env" + ], + "filenames": [ + ".env", + ".flaskenv", + "user-dirs.dirs" + ], + "filenamePatterns": [ + ".env.*" + ], + "aliases": [ + "Dotenv" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "dotenv", + "scopeName": "source.dotenv", + "path": "./syntaxes/dotenv.tmLanguage.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } +} diff --git a/extensions/dotenv/package.nls.json b/extensions/dotenv/package.nls.json new file mode 100644 index 00000000000..acebc955157 --- /dev/null +++ b/extensions/dotenv/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "Dotenv Language Basics", + "description": "Provides syntax highlighting and bracket matching in dotenv files." +} diff --git a/extensions/dotenv/syntaxes/dotenv.tmLanguage.json b/extensions/dotenv/syntaxes/dotenv.tmLanguage.json new file mode 100644 index 00000000000..1cf105b0c18 --- /dev/null +++ b/extensions/dotenv/syntaxes/dotenv.tmLanguage.json @@ -0,0 +1,127 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/dotenv-org/dotenv-vscode/blob/master/syntaxes/dotenv.tmLanguage.json", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/dotenv-org/dotenv-vscode/commit/e7e41baa5b23e01c1ff0567a4e596c24860e7def", + "scopeName": "source.dotenv", + "patterns": [ + { + "comment": "Full Line Comment", + "match": "^\\s?(#.*$)\\n", + "captures": { + "1": { + "patterns": [ + { + "include": "#line-comment" + } + ] + } + } + }, + { + "comment": "ENV entry", + "match": "^\\s?(.*?)\\s?(\\=)(.*)$", + "captures": { + "1": { + "patterns": [ + { + "include": "#key" + } + ] + }, + "2": { + "name": "keyword.operator.assignment.dotenv" + }, + "3": { + "name": "property.value.dotenv", + "patterns": [ + { + "include": "#line-comment" + }, + { + "include": "#double-quoted-string" + }, + { + "include": "#single-quoted-string" + }, + { + "include": "#interpolation" + } + ] + } + } + } + ], + "repository": { + "variable": { + "comment": "env variable", + "match": "[a-zA-Z_]+[a-zA-Z0-9_]*" + }, + "line-comment": { + "comment": "Comment", + "match": "#.*$", + "name": "comment.line.dotenv" + }, + "interpolation": { + "comment": "Interpolation (variable substitution)", + "match": "(\\$\\{)(.*)(\\})", + "captures": { + "1": { + "name": "keyword.interpolation.begin.dotenv" + }, + "2": { + "name": "variable.interpolation.dotenv" + }, + "3": { + "name": "keyword.interpolation.end.dotenv" + } + } + }, + "escape-characters": { + "comment": "Escape characters", + "match": "\\\\[nrtfb\"'\\\\]|\\\\u[0123456789ABCDEF]{4}", + "name": "constant.character.escape.dotenv" + }, + "double-quoted-string": { + "comment": "Double Quoted String", + "match": "\"(.*)\"", + "name": "string.quoted.double.dotenv", + "captures": { + "1": { + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#escape-characters" + } + ] + } + } + }, + "single-quoted-string": { + "comment": "Single Quoted String", + "match": "'(.*)'", + "name": "string.quoted.single.dotenv" + }, + "key": { + "comment": "Key", + "match": "(export\\s)?(.*)", + "captures": { + "1": { + "name": "keyword.key.export.dotenv" + }, + "2": { + "name": "variable.key.dotenv", + "patterns": [ + { + "include": "#variable" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/extensions/ini/package.json b/extensions/ini/package.json index 8523df264c1..f4837eb881f 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -40,13 +40,11 @@ ".repo" ], "filenames": [ - "gitconfig", - ".env" + "gitconfig" ], "filenamePatterns": [ "**/.config/git/config", - "**/.git/config", - ".*.env" + "**/.git/config" ], "aliases": [ "Properties", diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index ab9be7b29ad..9cad54150bb 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -69,9 +69,6 @@ "bashrc_Apple_Terminal", "zshrc_Apple_Terminal" ], - "filenamePatterns": [ - ".env.*" - ], "firstLine": "^#!.*\\b(bash|fish|zsh|sh|ksh|dtksh|pdksh|mksh|ash|dash|yash|sh|csh|jcsh|tcsh|itcsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": [ diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env new file mode 100644 index 00000000000..b2972ab3751 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env @@ -0,0 +1,3 @@ +# dev +HELLO=123 +GOODBYE=456 diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_env.json b/extensions/vscode-colorize-tests/test/colorize-results/test_env.json new file mode 100644 index 00000000000..a0eb219fea5 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_env.json @@ -0,0 +1,100 @@ +[ + { + "c": "# dev", + "t": "source.dotenv comment.line.dotenv", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "HELLO", + "t": "source.dotenv variable.key.dotenv", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": "=", + "t": "source.dotenv keyword.operator.assignment.dotenv", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": "123", + "t": "source.dotenv property.value.dotenv", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "GOODBYE", + "t": "source.dotenv variable.key.dotenv", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": "=", + "t": "source.dotenv keyword.operator.assignment.dotenv", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": "456", + "t": "source.dotenv property.value.dotenv", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + } +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json @@ -0,0 +1 @@ +[] \ No newline at end of file From 09f3d1da995bbb6530193f95c713d54c9d6bb1b1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 30 Oct 2025 11:37:18 +0100 Subject: [PATCH 1850/4355] do not render working set unless `options.renderWorkingSet` is set (#274066) fy @connor4312 you broke this with 8f24b33b0d806e7f0f5908ef3c9c483a564c2f44 --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index c4abb9d4b91..682fb4aa0b8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -128,7 +128,7 @@ export interface IChatInputPartOptions { inputSideToolbar?: MenuId; }; editorOverflowWidgetsDomNode?: HTMLElement; - renderWorkingSet?: boolean; + renderWorkingSet: boolean; enableImplicitContext?: boolean; supportsChangingModes?: boolean; dndContainer?: HTMLElement; @@ -1866,7 +1866,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const shouldRender = listEntries.map(r => r.length > 0); this._renderingChatEdits.value = autorun(reader => { - if (shouldRender.read(reader)) { + if (this.options.renderWorkingSet && shouldRender.read(reader)) { this.renderChatEditingSessionWithEntries( reader.store, chatEditingSession!, From 9b00600cd05980549d3ed5ee04a70950015edb23 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:39:13 +0000 Subject: [PATCH 1851/4355] Toolbar - action visibility takes precedence over action label (#274074) --- src/vs/base/browser/ui/toolbar/toolbar.css | 6 +--- src/vs/base/browser/ui/toolbar/toolbar.ts | 34 +++------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/vs/base/browser/ui/toolbar/toolbar.css b/src/vs/base/browser/ui/toolbar/toolbar.css index 8319f4d06f3..4c4c684755e 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/src/vs/base/browser/ui/toolbar/toolbar.css @@ -14,11 +14,7 @@ .monaco-toolbar.responsive { .monaco-action-bar > .actions-container > .action-item { - flex-shrink: 0; + flex-shrink: 1; min-width: 20px; } - - .monaco-action-bar > .actions-container > .action-item.responsive { - flex-shrink: 10000 !important; - } } diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 3559e37f2c4..343761f6e6a 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -17,7 +17,6 @@ import './toolbar.css'; import * as nls from '../../../../nls.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; -import { BaseActionViewItem } from '../actionbar/actionViewItems.js'; const ACTION_MIN_WIDTH = 24; /* 20px codicon + 4px left padding*/ @@ -242,9 +241,6 @@ export class ToolBar extends Disposable { // Reset hidden actions this.hiddenActions.length = 0; - // Set `responsive` class - this.setToolbarResponsiveAction(); - // Update toolbar to fit with container width this.setToolbarMaxWidth(this.element.getBoundingClientRect().width); } @@ -261,19 +257,10 @@ export class ToolBar extends Disposable { } private getItemsWidthResponsive(): number { - let itemsWidth = 0; - for (let index = 0; index < this.actionBar.length(); index++) { - // If the last visible primary action is wider than 24px, it means that it has a label. We - // need to return the minimum width (24px) for this action so that we allow it to shrink to - // the minimum width. - const width = index === this.originalPrimaryActions.length - this.hiddenActions.length - 1 - ? Math.min(ACTION_MIN_WIDTH, this.actionBar.getWidth(index)) - : this.actionBar.getWidth(index); - - itemsWidth += width; - } - - return itemsWidth; + // Each action is assumed to have a minimum width so that actions with a label + // can shrink to the action's minimum width. We do this so that action visibility + // takes precedence over the action label. + return this.actionBar.length() * ACTION_MIN_WIDTH; } private setToolbarMaxWidth(maxWidth: number) { @@ -338,9 +325,6 @@ export class ToolBar extends Disposable { } } - // Update `responsive` class - this.setToolbarResponsiveAction(); - // Update overflow menu const hiddenActions = this.hiddenActions.map(entry => entry.action); if (this.originalSecondaryActions.length > 0 || hiddenActions.length > 0) { @@ -349,16 +333,6 @@ export class ToolBar extends Disposable { } } - private setToolbarResponsiveAction(): void { - // Set the `responsive` class on the last visible primary action - for (let index = 0; index < this.actionBar.viewItems.length; index++) { - if (this.actionBar.viewItems[index] instanceof BaseActionViewItem) { - const isLastVisiblePrimaryAction = index === this.originalPrimaryActions.length - this.hiddenActions.length - 1; - (this.actionBar.viewItems[index] as BaseActionViewItem).element?.classList.toggle('responsive', isLastVisiblePrimaryAction); - } - } - } - private clear(): void { this.submenuActionViewItems = []; this.disposables.clear(); From 86af996c9d04dcec133c7b853921ed5aa03b02cb Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Oct 2025 11:19:57 +0100 Subject: [PATCH 1852/4355] make prefix trim computation reactive --- .../inlineEditsViews/inlineEditsLineReplacementView.ts | 2 +- .../browser/view/inlineEdits/utils/utils.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts index 0fcc91e642e..9f5e223814c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts @@ -64,7 +64,7 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin super(); this._onDidClick = this._register(new Emitter()); this.onDidClick = this._onDidClick.event; - this._maxPrefixTrim = this._edit.map(e => e ? getPrefixTrim(e.replacements.flatMap(r => [r.originalRange, r.modifiedRange]), e.originalRange, e.modifiedLines, this._editor.editor) : undefined); + this._maxPrefixTrim = this._edit.map((e, reader) => e ? getPrefixTrim(e.replacements.flatMap(r => [r.originalRange, r.modifiedRange]), e.originalRange, e.modifiedLines, this._editor.editor, reader) : undefined); this._modifiedLineElements = derived(this, reader => { const lines = []; let requiredWidth = 0; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts index 72d65cb9aba..d5bf886436a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts @@ -14,7 +14,7 @@ import { splitLines } from '../../../../../../../base/common/strings.js'; import { URI } from '../../../../../../../base/common/uri.js'; import { MenuEntryActionViewItem } from '../../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { ICodeEditor } from '../../../../../../browser/editorBrowser.js'; -import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; +import { observableCodeEditor, ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { Point } from '../../../../../../common/core/2d/point.js'; import { Rect } from '../../../../../../common/core/2d/rect.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; @@ -70,7 +70,7 @@ export function getOffsetForPos(editor: ObservableCodeEditor, pos: Position, rea return lineContentWidth; } -export function getPrefixTrim(diffRanges: Range[], originalLinesRange: LineRange, modifiedLines: string[], editor: ICodeEditor): { prefixTrim: number; prefixLeftOffset: number } { +export function getPrefixTrim(diffRanges: Range[], originalLinesRange: LineRange, modifiedLines: string[], editor: ICodeEditor, reader: IReader | undefined = undefined): { prefixTrim: number; prefixLeftOffset: number } { const textModel = editor.getModel(); if (!textModel) { return { prefixTrim: 0, prefixLeftOffset: 0 }; @@ -85,6 +85,8 @@ export function getPrefixTrim(diffRanges: Range[], originalLinesRange: LineRange const startLineIndent = textModel.getLineIndentColumn(originalLinesRange.startLineNumber); if (startLineIndent >= prefixTrim + 1) { // We can use the editor to get the offset + // TODO go through other usages of getOffsetForColumn and come up with a robust reactive solution to read it + observableCodeEditor(editor).scrollTop.read(reader); // getOffsetForColumn requires the line number to be visible. This might change on scroll top. prefixLeftOffset = editor.getOffsetForColumn(originalLinesRange.startLineNumber, prefixTrim + 1); } else if (modifiedLines.length > 0) { // Content is not in the editor, we can use the content width to calculate the offset From 73de9d554c5f6d364131f31522966124ad8ea7a1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 29 Oct 2025 11:20:37 +0100 Subject: [PATCH 1853/4355] Improves debug location --- .../common/observableInternal/observables/derived.ts | 4 ++-- .../browser/view/inlineEdits/utils/utils.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/base/common/observableInternal/observables/derived.ts b/src/vs/base/common/observableInternal/observables/derived.ts index 74b631b2a4d..bb8f9ce5578 100644 --- a/src/vs/base/common/observableInternal/observables/derived.ts +++ b/src/vs/base/common/observableInternal/observables/derived.ts @@ -17,8 +17,8 @@ import { IDerivedReader, Derived, DerivedWithSetter } from './derivedImpl.js'; * * {@link computeFn} should start with a JS Doc using `@description` to name the derived. */ -export function derived(computeFn: (reader: IDerivedReader) => T): IObservableWithChange; -export function derived(owner: DebugOwner, computeFn: (reader: IDerivedReader) => T): IObservableWithChange; +export function derived(computeFn: (reader: IDerivedReader, debugLocation?: DebugLocation) => T): IObservableWithChange; +export function derived(owner: DebugOwner, computeFn: (reader: IDerivedReader) => T, debugLocation?: DebugLocation): IObservableWithChange; export function derived( computeFnOrOwner: ((reader: IDerivedReader) => T) | DebugOwner, computeFn?: ((reader: IDerivedReader) => T) | undefined, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts index d5bf886436a..9e8cd3197a2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts @@ -8,7 +8,7 @@ import { KeybindingLabel, unthemedKeybindingLabelOptions } from '../../../../../ import { numberComparator } from '../../../../../../../base/common/arrays.js'; import { findFirstMin } from '../../../../../../../base/common/arraysFind.js'; import { DisposableStore, toDisposable } from '../../../../../../../base/common/lifecycle.js'; -import { derived, derivedObservableWithCache, derivedOpts, IObservable, IReader, observableValue, transaction } from '../../../../../../../base/common/observable.js'; +import { DebugLocation, derived, derivedObservableWithCache, derivedOpts, IObservable, IReader, observableValue, transaction } from '../../../../../../../base/common/observable.js'; import { OS } from '../../../../../../../base/common/platform.js'; import { splitLines } from '../../../../../../../base/common/strings.js'; import { URI } from '../../../../../../../base/common/uri.js'; @@ -394,12 +394,12 @@ export function observeElementPosition(element: HTMLElement, store: DisposableSt }; } -export function rectToProps(fn: (reader: IReader) => Rect) { +export function rectToProps(fn: (reader: IReader) => Rect, debugLocation: DebugLocation = DebugLocation.ofCaller()) { return { - left: derived({ name: 'editor.validOverlay.left' }, reader => /** @description left */ fn(reader).left), - top: derived({ name: 'editor.validOverlay.top' }, reader => /** @description top */ fn(reader).top), - width: derived({ name: 'editor.validOverlay.width' }, reader => /** @description width */ fn(reader).right - fn(reader).left), - height: derived({ name: 'editor.validOverlay.height' }, reader => /** @description height */ fn(reader).bottom - fn(reader).top), + left: derived({ name: 'editor.validOverlay.left' }, reader => /** @description left */ fn(reader).left, debugLocation), + top: derived({ name: 'editor.validOverlay.top' }, reader => /** @description top */ fn(reader).top, debugLocation), + width: derived({ name: 'editor.validOverlay.width' }, reader => /** @description width */ fn(reader).right - fn(reader).left, debugLocation), + height: derived({ name: 'editor.validOverlay.height' }, reader => /** @description height */ fn(reader).bottom - fn(reader).top, debugLocation), }; } From 68e4cfa41ee073bfdfed4f7f7cfc9386cecd8129 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 03:54:35 -0700 Subject: [PATCH 1854/4355] Pull auto approve into new analyzer --- .../commandLineAnalyzer.ts | 10 +- .../commandLineAutoApproveAnalyzer.ts | 235 +++++++++++++++ .../commandLineFileWriteAnalyzer.ts | 4 +- .../browser/tools/runInTerminalTool.ts | 274 ++++++------------ .../runInTerminalTool.test.ts | 2 +- 5 files changed, 338 insertions(+), 187 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts index 01edfddae46..e3f8494b7c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { IMarkdownString } from '../../../../../../../base/common/htmlContent.js'; +import type { IDisposable } from '../../../../../../../base/common/lifecycle.js'; import type { OperatingSystem } from '../../../../../../../base/common/platform.js'; +import type { ToolConfirmationAction } from '../../../../../chat/common/languageModelToolsService.js'; import type { ITerminalInstance } from '../../../../../terminal/browser/terminal.js'; import type { TreeSitterCommandParserLanguage } from '../../treeSitterCommandParser.js'; -export interface ICommandLineAnalyzer { +export interface ICommandLineAnalyzer extends IDisposable { analyze(options: ICommandLineAnalyzerOptions): Promise; } @@ -17,9 +20,12 @@ export interface ICommandLineAnalyzerOptions { shell: string; os: OperatingSystem; treeSitterLanguage: TreeSitterCommandParserLanguage; + terminalToolSessionId: string; } export interface ICommandLineAnalyzerResult { readonly isAutoApproveAllowed: boolean; - readonly disclaimers: string[]; + readonly disclaimers?: readonly string[]; + readonly autoApproveInfo?: IMarkdownString; + readonly customActions?: ToolConfirmationAction[]; } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts new file mode 100644 index 00000000000..a5b26284048 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { asArray } from '../../../../../../../base/common/arrays.js'; +import { createCommandUri, MarkdownString, type IMarkdownString } from '../../../../../../../base/common/htmlContent.js'; +import { Disposable } from '../../../../../../../base/common/lifecycle.js'; +import type { SingleOrMany } from '../../../../../../../base/common/types.js'; +import { localize } from '../../../../../../../nls.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js'; +import { TerminalToolConfirmationStorageKeys } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; +import { openTerminalSettingsLinkCommandId } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.js'; +import { ChatConfiguration } from '../../../../../chat/common/constants.js'; +import type { ToolConfirmationAction } from '../../../../../chat/common/languageModelToolsService.js'; +import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; +import { CommandLineAutoApprover, type IAutoApproveRule, type ICommandApprovalResult, type ICommandApprovalResultWithReason } from '../../commandLineAutoApprover.js'; +import { dedupeRules, generateAutoApproveActions, isPowerShell } from '../../runInTerminalHelpers.js'; +import type { RunInTerminalToolTelemetry } from '../../runInTerminalToolTelemetry.js'; +import { type TreeSitterCommandParser } from '../../treeSitterCommandParser.js'; +import type { ICommandLineAnalyzer, ICommandLineAnalyzerOptions, ICommandLineAnalyzerResult } from './commandLineAnalyzer.js'; + +const promptInjectionWarningCommandsLower = [ + 'curl', + 'wget', +]; +const promptInjectionWarningCommandsLowerPwshOnly = [ + 'invoke-restmethod', + 'invoke-webrequest', + 'irm', + 'iwr', +]; + +export class CommandLineAutoApproveAnalyzer extends Disposable implements ICommandLineAnalyzer { + private readonly _commandLineAutoApprover: CommandLineAutoApprover; + + constructor( + private readonly _treeSitterCommandParser: TreeSitterCommandParser, + private readonly _telemetry: RunInTerminalToolTelemetry, + private readonly _log: (message: string, ...args: unknown[]) => void, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService private readonly _storageService: IStorageService, + ) { + super(); + this._commandLineAutoApprover = this._register(instantiationService.createInstance(CommandLineAutoApprover)); + } + + async analyze(options: ICommandLineAnalyzerOptions): Promise { + let subCommands: string[] | undefined; + try { + subCommands = await this._treeSitterCommandParser.extractSubCommands(options.treeSitterLanguage, options.commandLine); + this._log(`Parsed sub-commands via ${options.treeSitterLanguage} grammar`, subCommands); + } catch (e) { + console.error(e); + this._log(`Failed to parse sub-commands via ${options.treeSitterLanguage} grammar`); + } + + let isAutoApproved = false; + let autoApproveInfo: IMarkdownString | undefined; + let customActions: ToolConfirmationAction[] | undefined; + + if (!subCommands) { + return { + isAutoApproveAllowed: false, + disclaimers: [], + }; + } + + const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, options.shell, options.os)); + const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(options.commandLine); + const autoApproveReasons: string[] = [ + ...subCommandResults.map(e => e.reason), + commandLineResult.reason, + ]; + + let isDenied = false; + let autoApproveReason: 'subCommand' | 'commandLine' | undefined; + let autoApproveDefault: boolean | undefined; + + const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); + if (deniedSubCommandResult) { + this._log('Sub-command DENIED auto approval'); + isDenied = true; + autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; + autoApproveReason = 'subCommand'; + } else if (commandLineResult.result === 'denied') { + this._log('Command line DENIED auto approval'); + isDenied = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + autoApproveReason = 'commandLine'; + } else { + if (subCommandResults.every(e => e.result === 'approved')) { + this._log('All sub-commands auto-approved'); + autoApproveReason = 'subCommand'; + isAutoApproved = true; + autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); + } else { + this._log('All sub-commands NOT auto-approved'); + if (commandLineResult.result === 'approved') { + this._log('Command line auto-approved'); + autoApproveReason = 'commandLine'; + isAutoApproved = true; + autoApproveDefault = commandLineResult.rule?.isDefaultRule; + } else { + this._log('Command line NOT auto-approved'); + } + } + } + + // Log detailed auto approval reasoning + for (const reason of autoApproveReasons) { + this._log(`- ${reason}`); + } + + // Apply auto approval or force it off depending on enablement/opt-in state + const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; + const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); + if (isAutoApproveEnabled && isAutoApproved) { + autoApproveInfo = this._createAutoApproveInfo( + isAutoApproved, + isDenied, + autoApproveReason, + subCommandResults, + commandLineResult, + ); + } else { + isAutoApproved = false; + } + + // Send telemetry about auto approval process + this._telemetry.logPrepare({ + terminalToolSessionId: options.terminalToolSessionId, + subCommands, + autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', + autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', + autoApproveReason, + autoApproveDefault + }); + + // Add disclaimers for various security concerns + const disclaimers: string[] = []; + // disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers).flat()); + + // Prompt injection warning for common commands that return content from the web + const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); + if (!isAutoApproved && ( + subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || + (isPowerShell(options.shell, options.os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) + )) { + disclaimers.push(localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.')); + } + + if (!isAutoApproved && isAutoApproveEnabled) { + customActions = generateAutoApproveActions(options.commandLine, subCommands, { subCommandResults, commandLineResult }); + } + + return { + isAutoApproveAllowed: isAutoApproved, + disclaimers, + autoApproveInfo, + customActions, + }; + } + + private _createAutoApproveInfo( + isAutoApproved: boolean, + isDenied: boolean, + autoApproveReason: 'subCommand' | 'commandLine' | undefined, + subCommandResults: ICommandApprovalResultWithReason[], + commandLineResult: ICommandApprovalResultWithReason, + ): IMarkdownString | undefined { + const formatRuleLinks = (result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string => { + return asArray(result).map(e => { + const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, e.rule!.sourceTarget); + return `[\`${e.rule!.sourceText}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; + }).join(', '); + }; + + const mdTrustSettings = { + isTrusted: { + enabledCommands: [openTerminalSettingsLinkCommandId] + } + }; + + const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); + const isGlobalAutoApproved = config?.value ?? config.defaultValue; + if (isGlobalAutoApproved) { + const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); + return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); + } + + if (isAutoApproved) { + switch (autoApproveReason) { + case 'commandLine': { + if (commandLineResult.rule) { + return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); + } + break; + } + case 'subCommand': { + const uniqueRules = dedupeRules(subCommandResults); + if (uniqueRules.length === 1) { + return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); + } else if (uniqueRules.length > 1) { + return new MarkdownString(localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); + } + break; + } + } + } else if (isDenied) { + switch (autoApproveReason) { + case 'commandLine': { + if (commandLineResult.rule) { + return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); + } + break; + } + case 'subCommand': { + const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); + if (uniqueRules.length === 1) { + return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))); + } else if (uniqueRules.length > 1) { + return new MarkdownString(localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))); + } + break; + } + } + } + + return undefined; + } +} diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts index bafcd3c939d..bddbdcd9f68 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from '../../../../../../../base/common/lifecycle.js'; import { URI } from '../../../../../../../base/common/uri.js'; import { localize } from '../../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; @@ -12,7 +13,7 @@ import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAge import type { TreeSitterCommandParser } from '../../treeSitterCommandParser.js'; import type { ICommandLineAnalyzer, ICommandLineAnalyzerOptions, ICommandLineAnalyzerResult } from './commandLineAnalyzer.js'; -export class CommandLineFileWriteAnalyzer implements ICommandLineAnalyzer { +export class CommandLineFileWriteAnalyzer extends Disposable implements ICommandLineAnalyzer { constructor( private readonly _treeSitterCommandParser: TreeSitterCommandParser, private readonly _log: (message: string, ...args: unknown[]) => void, @@ -20,6 +21,7 @@ export class CommandLineFileWriteAnalyzer implements ICommandLineAnalyzer { @IHistoryService private readonly _historyService: IHistoryService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { + super(); } async analyze(options: ICommandLineAnalyzerOptions): Promise { 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 32e111b66e2..7d85ab59173 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import type { IMarker as IXtermMarker } from '@xterm/xterm'; -import { asArray } from '../../../../../../base/common/arrays.js'; import { timeout } from '../../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; import { Event } from '../../../../../../base/common/event.js'; -import { createCommandUri, MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; +import { MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { basename } from '../../../../../../base/common/path.js'; import { OperatingSystem, OS } from '../../../../../../base/common/platform.js'; import { count } from '../../../../../../base/common/strings.js'; -import type { DeepImmutable, SingleOrMany } from '../../../../../../base/common/types.js'; +import type { DeepImmutable } from '../../../../../../base/common/types.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; import { localize } from '../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; @@ -25,30 +24,28 @@ import { TerminalCapability } from '../../../../../../platform/terminal/common/c import { ITerminalLogService, ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; -import { openTerminalSettingsLinkCommandId } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.js'; import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; -import { ChatConfiguration } from '../../../../chat/common/constants.js'; -import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress, type IToolConfirmationMessages, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; -import { ITerminalService, type ITerminalInstance, ITerminalChatService } from '../../../../terminal/browser/terminal.js'; +import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress, type IToolConfirmationMessages } from '../../../../chat/common/languageModelToolsService.js'; +import { ITerminalChatService, ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; import { getRecommendedToolsOverRunInTerminal } from '../alternativeRecommendation.js'; -import { CommandLineAutoApprover, type IAutoApproveRule, type ICommandApprovalResult, type ICommandApprovalResultWithReason } from '../commandLineAutoApprover.js'; import { CommandSimplifier } from '../commandSimplifier.js'; import { BasicExecuteStrategy } from '../executeStrategy/basicExecuteStrategy.js'; import type { ITerminalExecuteStrategy } from '../executeStrategy/executeStrategy.js'; import { NoneExecuteStrategy } from '../executeStrategy/noneExecuteStrategy.js'; import { RichExecuteStrategy } from '../executeStrategy/richExecuteStrategy.js'; import { getOutput } from '../outputHelpers.js'; -import { dedupeRules, generateAutoApproveActions, isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; +import { isFish, isPowerShell, isWindowsPowerShell, isZsh } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; -import { OutputMonitor } from './monitoring/outputMonitor.js'; -import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; import { type ICommandLineAnalyzer, type ICommandLineAnalyzerOptions } from './commandLineAnalyzer/commandLineAnalyzer.js'; +import { CommandLineAutoApproveAnalyzer } from './commandLineAnalyzer/commandLineAutoApproveAnalyzer.js'; import { CommandLineFileWriteAnalyzer } from './commandLineAnalyzer/commandLineFileWriteAnalyzer.js'; +import { OutputMonitor } from './monitoring/outputMonitor.js'; +import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; // #region Tool data @@ -247,16 +244,6 @@ const telemetryIgnoredSequences = [ '\x1b[O', // Focus out ]; -const promptInjectionWarningCommandsLower = [ - 'curl', - 'wget', -]; -const promptInjectionWarningCommandsLowerPwshOnly = [ - 'invoke-restmethod', - 'invoke-webrequest', - 'irm', - 'iwr', -]; export class RunInTerminalTool extends Disposable implements IToolImpl { @@ -265,7 +252,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { private readonly _treeSitterCommandParser: TreeSitterCommandParser; private readonly _telemetry: RunInTerminalToolTelemetry; private readonly _commandLineAnalyzers: ICommandLineAnalyzer[]; - protected readonly _commandLineAutoApprover: CommandLineAutoApprover; protected readonly _profileFetcher: TerminalProfileFetcher; protected readonly _sessionTerminalAssociations: Map = new Map(); @@ -301,9 +287,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { this._commandSimplifier = _instantiationService.createInstance(CommandSimplifier, this._osBackend, this._treeSitterCommandParser); this._telemetry = _instantiationService.createInstance(RunInTerminalToolTelemetry); this._commandLineAnalyzers = [ - this._instantiationService.createInstance(CommandLineFileWriteAnalyzer, this._treeSitterCommandParser, (message, args) => this._logService.info(`CommandLineFileWriteAnalyzer: ${message}`, args)), + this._register(this._instantiationService.createInstance(CommandLineFileWriteAnalyzer, this._treeSitterCommandParser, (message, args) => this._logService.info(`RunInTerminalTool#CommandLineFileWriteAnalyzer: ${message}`, args))), + this._register(this._instantiationService.createInstance(CommandLineAutoApproveAnalyzer, this._treeSitterCommandParser, this._telemetry, (message, args) => this._logService.info(`RunInTerminalTool#CommandLineAutoApproveAnalyzer: ${message}`, args))), ]; - this._commandLineAutoApprover = this._register(_instantiationService.createInstance(CommandLineAutoApprover)); this._profileFetcher = _instantiationService.createInstance(TerminalProfileFetcher); // Clear out warning accepted state if the setting is disabled @@ -359,13 +345,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // commands that would be auto approved if it were enabled. const actualCommand = toolEditedCommand ?? args.command; - let disclaimer: IMarkdownString | undefined; - let customActions: ToolConfirmationAction[] | undefined; - const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - let isAutoApproved = false; let subCommands: string[] | undefined; const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; @@ -383,101 +365,27 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { os, shell, treeSitterLanguage, + terminalToolSessionId, }; const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); - if (subCommands) { - const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, shell, os)); - const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(actualCommand); - const autoApproveReasons: string[] = [ - ...subCommandResults.map(e => e.reason), - commandLineResult.reason, - ]; - - let isDenied = false; - let autoApproveReason: 'subCommand' | 'commandLine' | undefined; - let autoApproveDefault: boolean | undefined; - - const deniedSubCommandResult = subCommandResults.find(e => e.result === 'denied'); - if (deniedSubCommandResult) { - this._logService.info('RunInTerminalTool: autoApprove: Sub-command DENIED auto approval'); - isDenied = true; - autoApproveDefault = deniedSubCommandResult.rule?.isDefaultRule; - autoApproveReason = 'subCommand'; - } else if (commandLineResult.result === 'denied') { - this._logService.info('RunInTerminalTool: autoApprove: Command line DENIED auto approval'); - isDenied = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - autoApproveReason = 'commandLine'; - } else { - if (subCommandResults.every(e => e.result === 'approved')) { - this._logService.info('RunInTerminalTool: autoApprove: All sub-commands auto-approved'); - autoApproveReason = 'subCommand'; - isAutoApproved = true; - autoApproveDefault = subCommandResults.every(e => e.rule?.isDefaultRule); - } else { - this._logService.info('RunInTerminalTool: autoApprove: All sub-commands NOT auto-approved'); - if (commandLineResult.result === 'approved') { - this._logService.info('RunInTerminalTool: autoApprove: Command line auto-approved'); - autoApproveReason = 'commandLine'; - isAutoApproved = true; - autoApproveDefault = commandLineResult.rule?.isDefaultRule; - } else { - this._logService.info('RunInTerminalTool: autoApprove: Command line NOT auto-approved'); - } - } - } - - // Log detailed auto approval reasoning - for (const reason of autoApproveReasons) { - this._logService.info(`RunInTerminalTool: autoApprove: - ${reason}`); - } - - // Apply auto approval or force it off depending on enablement/opt-in state - if (isAutoApproveEnabled && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) { - autoApproveInfo = this._createAutoApproveInfo( - isAutoApproved, - isDenied, - autoApproveReason, - subCommandResults, - commandLineResult, - ); - } else { - isAutoApproved = false; - } + // TODO: Should this require auto approve and veto instead? + const isAutoApproved = commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed); - // Send telemetry about auto approval process - this._telemetry.logPrepare({ - terminalToolSessionId, - subCommands, - autoApproveAllowed: !isAutoApproveEnabled ? 'off' : isAutoApproveWarningAccepted ? 'allowed' : 'needsOptIn', - autoApproveResult: isAutoApproved ? 'approved' : isDenied ? 'denied' : 'manual', - autoApproveReason, - autoApproveDefault - }); + // Add disclaimers for various security concerns + const disclaimers: string[] = []; + disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat()); - // Add disclaimers for various security concerns - const disclaimers: string[] = []; - disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers).flat()); - - // Prompt injection warning for common commands that return content from the web - const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); - if (!isAutoApproved && ( - subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || - (isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command))) - )) { - disclaimers.push(localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.')); - } + // Combine disclaimers + let disclaimer: IMarkdownString | undefined; + if (disclaimers.length > 0) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); + } - // Combine disclaimers - if (disclaimers.length > 0) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); - } + const customActions = commandLineAnalyzerResults.map(e => e.customActions ?? []).flat(); - if (!isAutoApproved && isAutoApproveEnabled) { - customActions = generateAutoApproveActions(actualCommand, subCommands, { subCommandResults, commandLineResult }); - } - } + // TODO: This isn't great + autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo; let shellType = basename(shell, '.exe'); if (shellType === 'powershell') { @@ -925,73 +833,73 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // #region Auto approve - private _createAutoApproveInfo( - isAutoApproved: boolean, - isDenied: boolean, - autoApproveReason: 'subCommand' | 'commandLine' | undefined, - subCommandResults: ICommandApprovalResultWithReason[], - commandLineResult: ICommandApprovalResultWithReason, - ): MarkdownString | undefined { - const formatRuleLinks = (result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string => { - return asArray(result).map(e => { - const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, e.rule!.sourceTarget); - return `[\`${e.rule!.sourceText}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; - }).join(', '); - }; - - const mdTrustSettings = { - isTrusted: { - enabledCommands: [openTerminalSettingsLinkCommandId] - } - }; - - const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); - const isGlobalAutoApproved = config?.value ?? config.defaultValue; - if (isGlobalAutoApproved) { - const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); - return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); - } - - if (isAutoApproved) { - switch (autoApproveReason) { - case 'commandLine': { - if (commandLineResult.rule) { - return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); - } - break; - } - case 'subCommand': { - const uniqueRules = dedupeRules(subCommandResults); - if (uniqueRules.length === 1) { - return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); - } else if (uniqueRules.length > 1) { - return new MarkdownString(localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); - } - break; - } - } - } else if (isDenied) { - switch (autoApproveReason) { - case 'commandLine': { - if (commandLineResult.rule) { - return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); - } - break; - } - case 'subCommand': { - const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); - if (uniqueRules.length === 1) { - return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))); - } else if (uniqueRules.length > 1) { - return new MarkdownString(localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))); - } - break; - } - } - } - - return undefined; - } + // private _createAutoApproveInfo( + // isAutoApproved: boolean, + // isDenied: boolean, + // autoApproveReason: 'subCommand' | 'commandLine' | undefined, + // subCommandResults: ICommandApprovalResultWithReason[], + // commandLineResult: ICommandApprovalResultWithReason, + // ): MarkdownString | undefined { + // const formatRuleLinks = (result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string => { + // return asArray(result).map(e => { + // const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, e.rule!.sourceTarget); + // return `[\`${e.rule!.sourceText}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; + // }).join(', '); + // }; + + // const mdTrustSettings = { + // isTrusted: { + // enabledCommands: [openTerminalSettingsLinkCommandId] + // } + // }; + + // const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); + // const isGlobalAutoApproved = config?.value ?? config.defaultValue; + // if (isGlobalAutoApproved) { + // const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); + // return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); + // } + + // if (isAutoApproved) { + // switch (autoApproveReason) { + // case 'commandLine': { + // if (commandLineResult.rule) { + // return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); + // } + // break; + // } + // case 'subCommand': { + // const uniqueRules = dedupeRules(subCommandResults); + // if (uniqueRules.length === 1) { + // return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); + // } else if (uniqueRules.length > 1) { + // return new MarkdownString(localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); + // } + // break; + // } + // } + // } else if (isDenied) { + // switch (autoApproveReason) { + // case 'commandLine': { + // if (commandLineResult.rule) { + // return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); + // } + // break; + // } + // case 'subCommand': { + // const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); + // if (uniqueRules.length === 1) { + // return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))); + // } else if (uniqueRules.length > 1) { + // return new MarkdownString(localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))); + // } + // break; + // } + // } + // } + + // return undefined; + // } // #endregion } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 0b2958cc989..f5b22542405 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -36,7 +36,7 @@ import { arch } from '../../../../../../base/common/process.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); - get commandLineAutoApprover() { return this._commandLineAutoApprover; } + // get commandLineAutoApprover() { return this._commandLineAutoApprover; } get sessionTerminalAssociations() { return this._sessionTerminalAssociations; } get profileFetcher() { return this._profileFetcher; } From 6bc469de1775f68175a2f35cacf3e5d358a8f92c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:11:49 +0000 Subject: [PATCH 1855/4355] SCM - use generic names when the checkout/sync commands are in the context menu (#274076) --- src/vs/workbench/contrib/scm/browser/util.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index c099ef39a7b..904fde6cf7b 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -100,12 +100,15 @@ export function collectContextMenuActions(menu: IMenu): IAction[] { } export class StatusBarAction extends Action { + readonly commandTitle: string | undefined; constructor( private command: Command, private commandService: ICommandService ) { - super(`statusbaraction{${command.id}}`, command.title, '', true); + super(`statusbaraction{${command.id}}`, getStatusBarCommandGenericName(command), '', true); + + this.commandTitle = command.title; this.tooltip = command.tooltip || ''; } @@ -115,9 +118,11 @@ export class StatusBarAction extends Action { } class StatusBarActionViewItem extends ActionViewItem { + private readonly _commandTitle: string | undefined; constructor(action: StatusBarAction, options: IBaseActionViewItemOptions) { super(null, action, { ...options, icon: false, label: true }); + this._commandTitle = action.commandTitle; } override render(container: HTMLElement): void { @@ -129,7 +134,7 @@ class StatusBarActionViewItem extends ActionViewItem { if (this.options.label && this.label) { // Convert text nodes to span elements to enable // text overflow on the left hand side of the label - const elements = renderLabelWithIcons(this.action.label) + const elements = renderLabelWithIcons(this._commandTitle ?? this.action.label) .map(element => { if (typeof element === 'string') { const span = document.createElement('span'); From d32bd36721346edc37e2f88909a37ba66da25ea0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 04:00:58 -0700 Subject: [PATCH 1856/4355] More simplification of terminal tool prepare call --- .../commandLineAutoApproveAnalyzer.ts | 5 +- .../browser/tools/runInTerminalTool.ts | 131 ++++++++---------- 2 files changed, 60 insertions(+), 76 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts index a5b26284048..c4b9810bb00 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts @@ -140,11 +140,8 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma autoApproveDefault }); - // Add disclaimers for various security concerns - const disclaimers: string[] = []; - // disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers).flat()); - // Prompt injection warning for common commands that return content from the web + const disclaimers: string[] = []; const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase()); if (!isAutoApproved && ( subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) || 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 7d85ab59173..bf011b0e93f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -25,7 +25,7 @@ import { ITerminalLogService, ITerminalProfile } from '../../../../../../platfor import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; -import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress, type IToolConfirmationMessages } from '../../../../chat/common/languageModelToolsService.js'; +import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/languageModelToolsService.js'; import { ITerminalChatService, ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; @@ -320,9 +320,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { async prepareToolInvocation(context: IToolInvocationPreparationContext, token: CancellationToken): Promise { const args = context.parameters as IRunInTerminalInputParams; - const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); - const presentation = alternativeRecommendation ? ToolInvocationPresentation.Hidden : undefined; - const os = await this._osBackend; const shell = await this._profileFetcher.getCopilotShell(); const language = os === OperatingSystem.Windows ? 'pwsh' : 'sh'; @@ -334,87 +331,77 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { if (toolEditedCommand === args.command) { toolEditedCommand = undefined; } + const toolSpecificData: IChatTerminalToolInvocationData = { + kind: 'terminal', + terminalToolSessionId, + commandLine: { + original: args.command, + toolEdited: toolEditedCommand + }, + language, + }; - let autoApproveInfo: IMarkdownString | undefined; - let confirmationMessages: IToolConfirmationMessages | undefined; + // HACK: Exit early if there's an alternative recommendation, this is a little hacky but + // it's the current mechanism for re-routing terminal tool calls to something else. + const alternativeRecommendation = getRecommendedToolsOverRunInTerminal(args.command, this._languageModelToolsService); if (alternativeRecommendation) { - confirmationMessages = undefined; - } else { - // Determine auto approval, this happens even when auto approve is off to that reasoning - // can be reviewed in the terminal channel. It also allows gauging the effective set of - // commands that would be auto approved if it were enabled. - const actualCommand = toolEditedCommand ?? args.command; - - const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; - const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); - const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - - let subCommands: string[] | undefined; - const treeSitterLanguage = isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash; - try { - subCommands = await this._treeSitterCommandParser.extractSubCommands(treeSitterLanguage, actualCommand); - this._logService.info(`RunInTerminalTool: autoApprove: Parsed sub-commands via ${treeSitterLanguage} grammar`, subCommands); - } catch (e) { - console.error(e); - this._logService.info(`RunInTerminalTool: autoApprove: Failed to parse sub-commands via ${treeSitterLanguage} grammar`); - } - - const commandLineAnalyzerOptions: ICommandLineAnalyzerOptions = { - instance, - commandLine: actualCommand, - os, - shell, - treeSitterLanguage, - terminalToolSessionId, + toolSpecificData.alternativeRecommendation = alternativeRecommendation; + return { + confirmationMessages: undefined, + presentation: ToolInvocationPresentation.Hidden, + toolSpecificData, }; - const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); + } - // TODO: Should this require auto approve and veto instead? - const isAutoApproved = commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed); + // Determine auto approval, this happens even when auto approve is off to that reasoning + // can be reviewed in the terminal channel. It also allows gauging the effective set of + // commands that would be auto approved if it were enabled. + const actualCommand = toolEditedCommand ?? args.command; - // Add disclaimers for various security concerns - const disclaimers: string[] = []; - disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat()); + const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; + const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); + const isAutoApproveAllowed = isAutoApproveEnabled && isAutoApproveWarningAccepted; - // Combine disclaimers - let disclaimer: IMarkdownString | undefined; - if (disclaimers.length > 0) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); - } + const commandLineAnalyzerOptions: ICommandLineAnalyzerOptions = { + instance, + commandLine: actualCommand, + os, + shell, + treeSitterLanguage: isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash, + terminalToolSessionId, + }; + const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); + + // Add disclaimers for various security concerns + const disclaimers: string[] = []; + disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat()); - const customActions = commandLineAnalyzerResults.map(e => e.customActions ?? []).flat(); + // Combine disclaimers + let disclaimer: IMarkdownString | undefined; + if (disclaimers.length > 0) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); + } - // TODO: This isn't great - autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo; + const isAutoApproved = commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed); + const customActions = commandLineAnalyzerResults.map(e => e.customActions ?? []).flat(); + toolSpecificData.autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo; - let shellType = basename(shell, '.exe'); - if (shellType === 'powershell') { - shellType = 'pwsh'; - } - confirmationMessages = (isAutoApproved && isAutoApproveAllowed && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) ? undefined : { - title: args.isBackground - ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) - : localize('runInTerminal', "Run `{0}` command?", shellType), - message: new MarkdownString(args.explanation), - disclaimer, - terminalCustomActions: customActions, - }; + let shellType = basename(shell, '.exe'); + if (shellType === 'powershell') { + shellType = 'pwsh'; } + const confirmationMessages = (isAutoApproved && isAutoApproveAllowed && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) ? undefined : { + title: args.isBackground + ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) + : localize('runInTerminal', "Run `{0}` command?", shellType), + message: new MarkdownString(args.explanation), + disclaimer, + terminalCustomActions: customActions, + }; return { confirmationMessages, - presentation, - toolSpecificData: { - kind: 'terminal', - terminalToolSessionId, - commandLine: { - original: args.command, - toolEdited: toolEditedCommand - }, - language, - alternativeRecommendation, - autoApproveInfo, - } + toolSpecificData, }; } From 62f86ba471a8a3ae5e5508cdfd13ec97dbe390c3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 04:36:04 -0700 Subject: [PATCH 1857/4355] Further simplify code flow --- .../browser/tools/runInTerminalTool.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) 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 bf011b0e93f..3074815f1b8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -356,7 +356,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // Determine auto approval, this happens even when auto approve is off to that reasoning // can be reviewed in the terminal channel. It also allows gauging the effective set of // commands that would be auto approved if it were enabled. - const actualCommand = toolEditedCommand ?? args.command; + const commandLine = toolEditedCommand ?? args.command; const isAutoApproveEnabled = this._configurationService.getValue(TerminalChatAgentToolsSettingId.EnableAutoApprove) === true; const isAutoApproveWarningAccepted = this._storageService.getBoolean(TerminalToolConfirmationStorageKeys.TerminalAutoApproveWarningAccepted, StorageScope.APPLICATION, false); @@ -364,7 +364,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { const commandLineAnalyzerOptions: ICommandLineAnalyzerOptions = { instance, - commandLine: actualCommand, + commandLine, os, shell, treeSitterLanguage: isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash, @@ -372,17 +372,12 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { }; const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); - // Add disclaimers for various security concerns - const disclaimers: string[] = []; - disclaimers.push(...commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat()); - - // Combine disclaimers + const disclaimersRaw = commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat(); let disclaimer: IMarkdownString | undefined; - if (disclaimers.length > 0) { - disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimers.join(' '), { supportThemeIcons: true }); + if (disclaimersRaw.length > 0) { + disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimersRaw.join(' '), { supportThemeIcons: true }); } - const isAutoApproved = commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed); const customActions = commandLineAnalyzerResults.map(e => e.customActions ?? []).flat(); toolSpecificData.autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo; @@ -390,7 +385,9 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { if (shellType === 'powershell') { shellType = 'pwsh'; } - const confirmationMessages = (isAutoApproved && isAutoApproveAllowed && commandLineAnalyzerResults.every(r => r.isAutoApproveAllowed)) ? undefined : { + + const isFinalAutoApproved = isAutoApproveAllowed && commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed); + const confirmationMessages = isFinalAutoApproved ? undefined : { title: args.isBackground ? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType) : localize('runInTerminal', "Run `{0}` command?", shellType), From 7dcd13ac70adb364eba40b95fd2817e1f8dcc7b0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 04:37:39 -0700 Subject: [PATCH 1858/4355] Remove now unused comments --- .../browser/tools/runInTerminalTool.ts | 72 ------------------- .../runInTerminalTool.test.ts | 1 - 2 files changed, 73 deletions(-) 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 3074815f1b8..caae5e741c0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -814,78 +814,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } // #endregion - - // #region Auto approve - - // private _createAutoApproveInfo( - // isAutoApproved: boolean, - // isDenied: boolean, - // autoApproveReason: 'subCommand' | 'commandLine' | undefined, - // subCommandResults: ICommandApprovalResultWithReason[], - // commandLineResult: ICommandApprovalResultWithReason, - // ): MarkdownString | undefined { - // const formatRuleLinks = (result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string => { - // return asArray(result).map(e => { - // const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, e.rule!.sourceTarget); - // return `[\`${e.rule!.sourceText}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; - // }).join(', '); - // }; - - // const mdTrustSettings = { - // isTrusted: { - // enabledCommands: [openTerminalSettingsLinkCommandId] - // } - // }; - - // const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); - // const isGlobalAutoApproved = config?.value ?? config.defaultValue; - // if (isGlobalAutoApproved) { - // const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); - // return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); - // } - - // if (isAutoApproved) { - // switch (autoApproveReason) { - // case 'commandLine': { - // if (commandLineResult.rule) { - // return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); - // } - // break; - // } - // case 'subCommand': { - // const uniqueRules = dedupeRules(subCommandResults); - // if (uniqueRules.length === 1) { - // return new MarkdownString(localize('autoApprove.rule', 'Auto approved by rule {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); - // } else if (uniqueRules.length > 1) { - // return new MarkdownString(localize('autoApprove.rules', 'Auto approved by rules {0}', formatRuleLinks(uniqueRules)), mdTrustSettings); - // } - // break; - // } - // } - // } else if (isDenied) { - // switch (autoApproveReason) { - // case 'commandLine': { - // if (commandLineResult.rule) { - // return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(commandLineResult)), mdTrustSettings); - // } - // break; - // } - // case 'subCommand': { - // const uniqueRules = dedupeRules(subCommandResults.filter(e => e.result === 'denied')); - // if (uniqueRules.length === 1) { - // return new MarkdownString(localize('autoApproveDenied.rule', 'Auto approval denied by rule {0}', formatRuleLinks(uniqueRules))); - // } else if (uniqueRules.length > 1) { - // return new MarkdownString(localize('autoApproveDenied.rules', 'Auto approval denied by rules {0}', formatRuleLinks(uniqueRules))); - // } - // break; - // } - // } - // } - - // return undefined; - // } - - // #endregion } class BackgroundTerminalExecution extends Disposable { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index f5b22542405..995f72a352a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -36,7 +36,6 @@ import { arch } from '../../../../../../base/common/process.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); - // get commandLineAutoApprover() { return this._commandLineAutoApprover; } get sessionTerminalAssociations() { return this._sessionTerminalAssociations; } get profileFetcher() { return this._profileFetcher; } From cbe79e3b173ee3503acea54559e04b2a50241c1d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 04:40:48 -0700 Subject: [PATCH 1859/4355] Simplify disclaimer mapping suggestion --- .../chatAgentTools/browser/tools/runInTerminalTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 caae5e741c0..0dd4a1779f6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -372,7 +372,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { }; const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); - const disclaimersRaw = commandLineAnalyzerResults.map(e => e.disclaimers ?? []).flat(); + const disclaimersRaw = commandLineAnalyzerResults.filter(e => e.disclaimers).flatMap(e => e.disclaimers); let disclaimer: IMarkdownString | undefined; if (disclaimersRaw.length > 0) { disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimersRaw.join(' '), { supportThemeIcons: true }); From 5343a0077785c028f3987879b8d2fce143a6e31b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 13:57:45 +0100 Subject: [PATCH 1860/4355] Chat: "Configure Empty State" is hard to understand (fix #271081) (#274054) --- src/vs/platform/actions/common/actions.ts | 2 +- .../chat/browser/actions/chatActions.ts | 71 +++++-------------- src/vs/workbench/contrib/chat/browser/chat.ts | 1 - .../contrib/chat/browser/chatWidget.ts | 30 ++------ .../chat/browser/media/chatViewWelcome.css | 4 -- 5 files changed, 24 insertions(+), 84 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index a73d59d6cf4..bada5ee4562 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -246,7 +246,7 @@ export class MenuId { static readonly ChatCompareBlock = new MenuId('ChatCompareBlock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatHistory = new MenuId('ChatHistory'); - static readonly ChatWelcomeHistoryContext = new MenuId('ChatWelcomeHistoryContext'); + static readonly ChatWelcomeContext = new MenuId('ChatWelcomeContext'); static readonly ChatMessageFooter = new MenuId('ChatMessageFooter'); static readonly ChatExecute = new MenuId('ChatExecute'); static readonly ChatInput = new MenuId('ChatInput'); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 001b20214c7..0a97470f8e6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1618,12 +1618,17 @@ Update \`.github/copilot-instructions.md\` for the user, then ask for feedback o category: CHAT_CATEGORY, f1: true, precondition: ChatContextKeys.enabled, - menu: { + menu: [{ id: CHAT_CONFIG_MENU_ID, when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)), order: 15, group: '3_configure' - } + }, + { + id: MenuId.ChatWelcomeContext, + group: '2_settings', + order: 1 + }] }); } @@ -2034,67 +2039,25 @@ registerAction2(class EditToolApproval extends Action2 { }); // Register actions for chat welcome history context menu -MenuRegistry.appendMenuItem(MenuId.ChatWelcomeHistoryContext, { - command: { - id: 'workbench.action.chat.toggleChatHistoryVisibility', - title: localize('chat.showChatHistory.label', "✓ Chat History") - }, - group: '1_modify', - order: 1, - when: ContextKeyExpr.equals('chatHistoryVisible', true) -}); - -MenuRegistry.appendMenuItem(MenuId.ChatWelcomeHistoryContext, { - command: { - id: 'workbench.action.chat.toggleChatHistoryVisibility', - title: localize('chat.hideChatHistory.label', "Chat History") - }, - group: '1_modify', - order: 1, - when: ContextKeyExpr.equals('chatHistoryVisible', false) -}); - registerAction2(class ToggleChatHistoryVisibilityAction extends Action2 { constructor() { super({ id: 'workbench.action.chat.toggleChatHistoryVisibility', title: localize2('chat.toggleChatHistoryVisibility.label', "Chat History"), category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled - }); - } - - async run(accessor: ServicesAccessor): Promise { - const chatWidgetService = accessor.get(IChatWidgetService); - const widgets = chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat); - const widget = widgets?.[0]; - if (widget) { - widget.toggleHistoryVisibility(); - } - } -}); - -registerAction2(class OpenChatEmptyStateSettingsAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.openChatEmptyStateSettings', - title: localize2('chat.openChatEmptyStateSettings.label', "Configure Empty State"), - menu: [ - { - id: MenuId.ChatWelcomeHistoryContext, - group: '2_settings', - order: 1 - } - ], - category: CHAT_CATEGORY, - precondition: ChatContextKeys.enabled + precondition: ChatContextKeys.enabled, + toggled: ContextKeyExpr.equals('config.chat.emptyState.history.enabled', true), + menu: { + id: MenuId.ChatWelcomeContext, + group: '1_modify', + order: 1 + } }); } async run(accessor: ServicesAccessor): Promise { - const preferencesService = accessor.get(IPreferencesService); - await preferencesService.openUserSettings({ - query: 'chat.emptyState chat.promptFilesRecommendations' - }); + const configurationService = accessor.get(IConfigurationService); + const current = configurationService.getValue('chat.emptyState.history.enabled'); + await configurationService.updateValue('chat.emptyState.history.enabled', !current); } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index da33da8c6dd..a2e2c60e0f2 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -256,7 +256,6 @@ export interface IChatWidget { lockToCodingAgent(name: string, displayName: string, agentId?: string): void; delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void; - toggleHistoryVisibility(): void; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 62082d7f34f..e177dda5fea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -7,7 +7,7 @@ import './media/chat.css'; import './media/chatAgentHover.css'; import './media/chatViewWelcome.css'; import * as dom from '../../../../base/browser/dom.js'; -import { IMouseWheelEvent } from '../../../../base/browser/mouseEvent.js'; +import { IMouseWheelEvent, StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { IHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; @@ -414,8 +414,6 @@ export class ChatWidget extends Disposable implements IChatWidget { private readonly promptUriCache = new Map(); private _isLoadingPromptDescriptions = false; - // UI state for temporarily hiding chat history - private _historyVisible = true; private _mostRecentlyFocusedItemIndex: number = -1; private set viewModel(viewModel: ChatViewModel | undefined) { @@ -978,17 +976,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidClear.fire(); } - public toggleHistoryVisibility(): void { - this._historyVisible = !this._historyVisible; - // Find and hide/show the existing history section via CSS class toggles - const historyRoot = this.welcomeMessageContainer.querySelector('.chat-welcome-history-root'); - if (historyRoot) { - historyRoot.classList.toggle('chat-welcome-history-hidden', !this._historyVisible); - } - const shouldShowHistory = this._historyVisible && !!historyRoot; - this.welcomeMessageContainer.classList.toggle('has-chat-history', shouldShowHistory); - } - private onDidChangeItems(skipDynamicLayout?: boolean) { // Update context key when items change this.updateEmptyStateWithHistoryContext(); @@ -1113,7 +1100,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Optional: recent chat history above welcome content when enabled const showHistory = this.configurationService.getValue(ChatConfiguration.EmptyStateHistoryEnabled); - if (showHistory && !this._lockedAgent && this._historyVisible) { + if (showHistory && !this._lockedAgent) { this.renderWelcomeHistorySection(); } this.welcomePart.value = this.instantiationService.createInstance( @@ -1131,13 +1118,9 @@ export class ChatWidget extends Disposable implements IChatWidget { e.preventDefault(); e.stopPropagation(); this.contextMenuService.showContextMenu({ - menuId: MenuId.ChatWelcomeHistoryContext, - menuActionOptions: { shouldForwardArgs: true }, - contextKeyService: this.contextKeyService.createOverlay([ - ['chatHistoryVisible', this._historyVisible] - ]), - getAnchor: () => ({ x: e.clientX, y: e.clientY }), - getActionsContext: () => ({}) + menuId: MenuId.ChatWelcomeContext, + contextKeyService: this.contextKeyService, + getAnchor: () => new StandardMouseEvent(dom.getWindow(this.welcomeMessageContainer), e) }); }); } @@ -1169,8 +1152,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.historyListContainer = dom.append(container, $('.chat-welcome-history-list')); - historyRoot.classList.toggle('chat-welcome-history-hidden', !this._historyVisible); - this.welcomeMessageContainer.classList.toggle('has-chat-history', this._historyVisible && initialHistoryItems.length > 0); + this.welcomeMessageContainer.classList.toggle('has-chat-history', initialHistoryItems.length > 0); // Compute today's midnight once for label decisions const todayMidnight = new Date(); diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index 0c671e2d922..d5be607b666 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -269,10 +269,6 @@ div.chat-welcome-view { width: 100%; padding: 8px; - &.chat-welcome-history-hidden { - display: none; - } - .chat-welcome-history-header { display: flex; align-items: center; From 319ed600b6b87cccdc9af537d5a4051d373978fc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 13:58:01 +0100 Subject: [PATCH 1861/4355] chat - skip updating UI when we shutdown (#274057) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e177dda5fea..8829f005b44 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -95,6 +95,7 @@ import { ChatViewPane } from './chatViewPane.js'; import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { LocalChatSessionUri } from '../common/chatUri.js'; +import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; const $ = dom.$; @@ -523,6 +524,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, @IChatTodoListService private readonly chatTodoListService: IChatTodoListService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(); this._lockedToCodingAgentContextKey = ChatContextKeys.lockedToCodingAgent.bindTo(this.contextKeyService); @@ -1059,7 +1061,7 @@ export class ChatWidget extends Disposable implements IChatWidget { * @internal */ private renderWelcomeViewContentIfNeeded() { - if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal') { + if (this.viewOptions.renderStyle === 'compact' || this.viewOptions.renderStyle === 'minimal' || this.lifecycleService.willShutdown) { return; } @@ -1629,6 +1631,10 @@ export class ChatWidget extends Disposable implements IChatWidget { } private renderChatSuggestNextWidget(): void { + if (this.lifecycleService.willShutdown) { + return; + } + // Skip rendering in coding agent sessions if (this.isLockedToCodingAgent) { this.chatSuggestNextWidget.hide(); From 58da1f43b2a8ae90fea2e33c19e725f2f47f20d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 15:05:56 +0100 Subject: [PATCH 1862/4355] agents view - support hover and avoid deprecated `id` --- .../agentSessions/agentSessionViewModel.ts | 15 +++---- .../browser/agentSessions/agentSessions.ts | 7 ++++ .../agentSessions/agentSessionsView.ts | 8 ++-- .../agentSessions/agentSessionsViewer.ts | 41 ++++++++++++++++++- .../browser/agentSessionViewModel.test.ts | 18 ++++---- 5 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index 7e21c7a089f..b04662deccb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -33,14 +33,14 @@ export interface IAgentSessionViewModel { readonly provider: IChatSessionItemProvider; - readonly id: string; readonly resource: URI; readonly status?: ChatSessionStatus; + readonly tooltip?: string | IMarkdownString; readonly label: string; readonly description: string | IMarkdownString; - readonly icon?: ThemeIcon; // TODO@bpasero support + readonly icon?: ThemeIcon; readonly timing: { readonly startTime: number; @@ -60,7 +60,7 @@ export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolea export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel { const session = obj as IAgentSessionViewModel | undefined; - return typeof session?.id === 'string'; + return URI.isUri(session?.resource); } export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionsViewModel { @@ -74,6 +74,8 @@ export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSe const INCLUDE_HISTORY = false; export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel { + private static NO_DESCRIPTION_LABEL = `_<${localize('chat.session.noDescription', 'No description')}>_`; + readonly sessions: IAgentSessionViewModel[] = []; private readonly _onWillResolve = this._register(new Emitter()); @@ -151,11 +153,11 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions newSessions.push({ provider, - id: session.id, resource: session.resource, label: session.label, - description: session.description || new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), + description: session.description || new MarkdownString(AgentSessionsViewModel.NO_DESCRIPTION_LABEL), icon: session.iconPath, + tooltip: session.tooltip, status: session.status, timing: { startTime: session.timing.startTime, @@ -171,14 +173,13 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions // - can we support all properties including `startTime` properly for (const history of await this.chatService.getLocalSessionHistory()) { newSessions.push({ - id: history.sessionId, resource: LocalChatSessionUri.forSession(history.sessionId), label: history.title, provider: provider, timing: { startTime: history.lastMessageDate ?? Date.now() }, - description: new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`), + description: new MarkdownString(AgentSessionsViewModel.NO_DESCRIPTION_LABEL), }); } } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts new file mode 100644 index 00000000000..0d54b5c2fa1 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const AGENT_SESSIONS_VIEW_CONTAINER_ID = 'workbench.viewContainer.agentSessions'; +export const AGENT_SESSIONS_VIEW_ID = 'workbench.view.agentSessions'; diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index 38e62ba5161..dbd451c1b4c 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -50,6 +50,7 @@ import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { getActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IChatService } from '../../common/chatService.js'; import { IChatWidgetService } from '../chat.js'; +import { AGENT_SESSIONS_VIEW_ID, AGENT_SESSIONS_VIEW_CONTAINER_ID } from './agentSessions.js'; export class AgentSessionsView extends ViewPane { @@ -172,7 +173,10 @@ export class AgentSessionsView extends ViewPane { } const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.contextKeyService.createOverlay(getSessionItemContextOverlay( - session, + { + id: session.resource.toString(), + ...session + }, session.provider, this.chatWidgetService, this.chatService, @@ -381,8 +385,6 @@ export class AgentSessionsView extends ViewPane { const chatAgentsIcon = registerIcon('chat-sessions-icon', Codicon.commentDiscussionSparkle, 'Icon for Agent Sessions View'); -const AGENT_SESSIONS_VIEW_CONTAINER_ID = 'workbench.viewContainer.agentSessions'; -const AGENT_SESSIONS_VIEW_ID = 'workbench.view.agentSessions'; const AGENT_SESSIONS_VIEW_TITLE = localize2('agentSessions.view.label', "Agent Sessions"); const agentSessionsViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index df35463d5b3..f6d7f669384 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -28,6 +28,12 @@ import { coalesce } from '../../../../../base/common/arrays.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { fillEditorsDragData } from '../../../../browser/dnd.js'; import { ChatSessionStatus } from '../../common/chatSessionsService.js'; +import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; +import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; +import { IWorkbenchLayoutService, Position } from '../../../../services/layout/browser/layoutService.js'; +import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js'; interface IAgentSessionItemTemplate { readonly element: HTMLElement; @@ -52,7 +58,10 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer, template: IAgentSessionItemTemplate): void { + const tooltip = session.element.tooltip; + if (tooltip) { + template.elementDisposable.add( + this.hoverService.setupDelayedHover(template.element, () => ({ + content: tooltip, + style: HoverStyle.Pointer, + position: { + hoverPosition: (() => { + const sideBarPosition = this.layoutService.getSideBarPosition(); + const viewLocation = this.viewDescriptorService.getViewLocationById(AGENT_SESSIONS_VIEW_ID); + switch (viewLocation) { + case ViewContainerLocation.Sidebar: + return sideBarPosition === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; + case ViewContainerLocation.AuxiliaryBar: + return sideBarPosition === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; + default: + return HoverPosition.RIGHT; + } + })() + } + })) + ); + } } private statusToIcon(status?: ChatSessionStatus): ThemeIcon { @@ -195,7 +232,7 @@ export class AgentSessionsIdentityProvider implements IIdentityProvider { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 2); - assert.strictEqual(viewModel.sessions[0].id, 'session-1'); + assert.strictEqual(viewModel.sessions[0].resource.toString(), 'test://session-1'); assert.strictEqual(viewModel.sessions[0].label, 'Test Session 1'); - assert.strictEqual(viewModel.sessions[1].id, 'session-2'); + assert.strictEqual(viewModel.sessions[1].resource.toString(), 'test://session-2'); assert.strictEqual(viewModel.sessions[1].label, 'Test Session 2'); }); @@ -117,8 +117,8 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 2); - assert.strictEqual(viewModel.sessions[0].id, 'session-1'); - assert.strictEqual(viewModel.sessions[1].id, 'session-2'); + assert.strictEqual(viewModel.sessions[0].resource.toString(), 'test://session-1'); + assert.strictEqual(viewModel.sessions[1].resource.toString(), 'test://session-2'); }); test('should fire onWillResolve and onDidResolve events', async () => { @@ -215,7 +215,7 @@ suite('AgentSessionsViewModel', () => { assert.strictEqual(viewModel.sessions.length, 1); const session = viewModel.sessions[0]; - assert.strictEqual(session.id, 'session-1'); + assert.strictEqual(session.resource.toString(), 'test://session-1'); assert.strictEqual(session.label, 'Test Session'); assert.ok(session.description instanceof MarkdownString); if (session.description instanceof MarkdownString) { @@ -288,7 +288,7 @@ suite('AgentSessionsViewModel', () => { await viewModel.resolve(undefined); assert.strictEqual(viewModel.sessions.length, 1); - assert.strictEqual(viewModel.sessions[0].id, 'valid-session'); + assert.strictEqual(viewModel.sessions[0].resource.toString(), 'test://valid'); }); test('should handle resolve with specific provider', async () => { @@ -745,7 +745,7 @@ suite('AgentSessionsViewModel', () => { // Provider 2 should be called again assert.strictEqual(provider2CallCount, 2); // Session 1 should be preserved with original label - assert.strictEqual(viewModel.sessions.find(s => s.id === 'session-1')?.label, originalSession1Label); + assert.strictEqual(viewModel.sessions.find(s => s.resource.toString() === 'test://session-1')?.label, originalSession1Label); }); test('should accumulate providers when resolve is called with different provider types', async () => { @@ -817,7 +817,6 @@ suite('AgentSessionsViewModel - Helper Functions', () => { onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [] }, - id: 'local-1', resource: URI.parse('test://local-1'), label: 'Local', description: 'test', @@ -830,7 +829,6 @@ suite('AgentSessionsViewModel - Helper Functions', () => { onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [] }, - id: 'remote-1', resource: URI.parse('test://remote-1'), label: 'Remote', description: 'test', @@ -848,7 +846,6 @@ suite('AgentSessionsViewModel - Helper Functions', () => { onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [] }, - id: 'test-1', resource: URI.parse('test://test-1'), label: 'Test', description: 'test', @@ -870,7 +867,6 @@ suite('AgentSessionsViewModel - Helper Functions', () => { onDidChangeChatSessionItems: Event.None, provideChatSessionItems: async () => [] }, - id: 'test-1', resource: URI.parse('test://test-1'), label: 'Test', description: 'test', From 4df8d67121ffe247d7920ecdae462acc6e4812a1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 15:18:38 +0100 Subject: [PATCH 1863/4355] build - update `tsconfig` lib to `ES2024` for layers checker (#274115) --- build/checker/tsconfig.browser.json | 2 +- build/checker/tsconfig.node.json | 2 +- build/checker/tsconfig.worker.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/checker/tsconfig.browser.json b/build/checker/tsconfig.browser.json index 67868ef7575..91cb9fe0e70 100644 --- a/build/checker/tsconfig.browser.json +++ b/build/checker/tsconfig.browser.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022", + "ES2024", "DOM", "DOM.Iterable" ], diff --git a/build/checker/tsconfig.node.json b/build/checker/tsconfig.node.json index 2e483136e08..4fe5c10623d 100644 --- a/build/checker/tsconfig.node.json +++ b/build/checker/tsconfig.node.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022" + "ES2024" ], "types": [ "node" diff --git a/build/checker/tsconfig.worker.json b/build/checker/tsconfig.worker.json index ebb919bf0f2..39d3a584532 100644 --- a/build/checker/tsconfig.worker.json +++ b/build/checker/tsconfig.worker.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022", + "ES2024", "WebWorker", "Webworker.Iterable", "WebWorker.AsyncIterable" From 429a4fad6f1e0e71847202985d07acac7a89790c Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 30 Oct 2025 15:33:42 +0100 Subject: [PATCH 1864/4355] Improve wording --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 81435e2dafa..de32969274f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -736,7 +736,7 @@ configurationRegistry.registerConfiguration({ }, 'chat.extensionUnification.enabled': { type: 'boolean', - description: nls.localize('chat.extensionUnification.enabled', "Enables unification of GitHub Copilot extensions. When enabled, only the GitHub Copilot Chat extension will be active and all functionality will be served by it. When disabled, GitHub Copilot Chat and GitHub Copilot extensions will operate independently."), + description: nls.localize('chat.extensionUnification.enabled', "Enables the unification of GitHub Copilot extensions. When enabled, all GitHub Copilot functionality is served from the GitHub Copilot Chat extension. When disabled, the GitHub Copilot and GitHub Copilot Chat extensions operate independently."), default: false, tags: ['experimental'], experiment: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 503999eb9fb..96c84340fc2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2689,7 +2689,7 @@ export class ExtensionStatusAction extends ExtensionAction { // Unification if (this.extension.enablementState === EnablementState.DisabledByUnification) { - this.updateStatus({ icon: infoIcon, message: new MarkdownString(localize('extension disabled because of unification', "The Copilot extension has been disabled. Copilot functionality will now be served by the Copilot Chat extension. In case this is causing issues, please create an issue on the vscode repository. The setting {0} can be used to revert back to having both extensions enabled.", '`chat.extensionUnification.enabled`')) }, true); + this.updateStatus({ icon: infoIcon, message: new MarkdownString(localize('extension disabled because of unification', "All GitHub Copilot functionality is now being served from the GitHub Copilot extension. To temporarily opt out of this extension unification, toggle the {0} setting.", '`chat.extensionUnification.enabled`')) }, true); return; } From e6963b4a1f32d54807841b3d2af31de8126b2b42 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 15:58:45 +0100 Subject: [PATCH 1865/4355] agents view - clean up description --- .../agentSessions/agentSessionViewModel.ts | 25 ++++++++++++++++--- .../chatSessions/localChatSessionsProvider.ts | 9 +++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index b04662deccb..bd6837ff8bb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -74,7 +74,7 @@ export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSe const INCLUDE_HISTORY = false; export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel { - private static NO_DESCRIPTION_LABEL = `_<${localize('chat.session.noDescription', 'No description')}>_`; + private static NO_DESCRIPTION_LABEL = new MarkdownString(`_<${localize('chat.session.noDescription', 'No description')}>_`); readonly sessions: IAgentSessionViewModel[] = []; @@ -151,11 +151,30 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions continue; // TODO@bpasero this needs to be fixed at the provider level } + let description; + if (session.description) { + description = session.description; + } else { + switch (session.status) { + case ChatSessionStatus.InProgress: + description = localize('chat.session.status.inProgress', 'Working...'); + break; + case ChatSessionStatus.Failed: + description = localize('chat.session.status.error', 'Failed'); + break; + case ChatSessionStatus.Completed: + description = localize('chat.session.status.completed', 'Finished'); + break; + default: + description = AgentSessionsViewModel.NO_DESCRIPTION_LABEL; + } + } + newSessions.push({ provider, resource: session.resource, label: session.label, - description: session.description || new MarkdownString(AgentSessionsViewModel.NO_DESCRIPTION_LABEL), + description, icon: session.iconPath, tooltip: session.tooltip, status: session.status, @@ -179,7 +198,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions timing: { startTime: history.lastMessageDate ?? Date.now() }, - description: new MarkdownString(AgentSessionsViewModel.NO_DESCRIPTION_LABEL), + description: AgentSessionsViewModel.NO_DESCRIPTION_LABEL, }); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 1dd4c570600..9c93622ec49 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -220,7 +220,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio if (editorInfo) { // Determine status and timestamp for editor-based session let status: ChatSessionStatus | undefined; - let timestamp: number | undefined; + let startTime: number | undefined; if (editorInfo.editor instanceof ChatEditorInput && editorInfo.editor.sessionId) { const model = this.chatService.getSession(editorInfo.editor.sessionId); if (model) { @@ -228,11 +228,10 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio // Get the last interaction timestamp from the model const requests = model.getRequests(); if (requests.length > 0) { - const lastRequest = requests[requests.length - 1]; - timestamp = lastRequest.timestamp; + startTime = requests.at(0)?.timestamp; } else { // Fallback to current time if no requests yet - timestamp = Date.now(); + startTime = Date.now(); } } const editorSession: ChatSessionItemWithProvider = { @@ -243,7 +242,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio status, provider: this, timing: { - startTime: timestamp ?? 0 + startTime: startTime ?? 0 } }; sessions.push(editorSession); From 64f9dbc0e9b171f5aee6c7afd75477f806f5246c Mon Sep 17 00:00:00 2001 From: Kyle Cutler <67761731+kycutler@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:08:56 -0700 Subject: [PATCH 1866/4355] update distro (#274125) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3abc7bb4498..3f06f400886 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.106.0", - "distro": "e6400daee823077bac8803d05ce425ac82fb30f0", + "distro": "e6c6e53f1b7ee2c57426da19a39af7d21410deb1", "author": { "name": "Microsoft Corporation" }, From 9a08cd4a8f77e9a7ac4517f6fe1b59a4176480eb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 16:38:36 +0100 Subject: [PATCH 1867/4355] agents view - element styling --- .../agentSessions/agentSessionsViewer.ts | 27 +++++++++-------- .../media/agentsessionsviewer.css | 30 ++++++++----------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index f6d7f669384..52cb86d6c95 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -38,11 +38,13 @@ import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js'; interface IAgentSessionItemTemplate { readonly element: HTMLElement; + // Row 1 readonly title: IconLabel; readonly icon: HTMLElement; + readonly timestamp: HTMLElement; + // Row 2 readonly description: HTMLElement; - readonly timestamp: HTMLElement; readonly diffAdded: HTMLElement; readonly diffRemoved: HTMLElement; @@ -73,20 +75,18 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void { template.elementDisposable.clear(); - template.icon.className = `agent-session-icon ${ThemeIcon.asClassName(this.statusToIcon(session.element.status))}`; + const icon = this.statusToIcon(session.element.status) ?? session.element.icon; + if (icon) { + template.icon.className = `agent-session-icon ${ThemeIcon.asClassName(icon)}`; + } template.title.setLabel(session.element.label, undefined, { matches: createMatches(session.filterData) }); @@ -163,7 +166,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css index 567d786f74a..3d3427b7473 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -20,23 +20,6 @@ flex-direction: row; padding: 0 6px; - /* #region Icon Column */ - - .agent-session-icon-col { - display: flex; - align-items: flex-start; - padding-top: 3px; - } - - .agent-session-icon { - flex-shrink: 0; - width: 16px; - height: 16px; - font-size: 16px; - } - - /* #endregion */ - .agent-session-main-col, .agent-session-title-row, .agent-session-details-row { @@ -47,6 +30,7 @@ .agent-session-title-row, .agent-session-details-row { display: flex; + align-items: center; line-height: 22px; gap: 6px; padding: 0 6px; @@ -66,13 +50,23 @@ } } + .agent-session-title { + flex: 1; + } + .agent-session-title, .agent-session-description { - flex: 1; text-overflow: ellipsis; overflow: hidden; } + .agent-session-icon { + flex-shrink: 0; + width: 16px; + height: 16px; + font-size: 16px; + } + .agent-session-timestamp { font-size: 11px; } From 883485b3b5de4d258b3f4c65f53fc1d8612c9708 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:53:24 -0700 Subject: [PATCH 1868/4355] Pick up esbuild target from tsconfig files --- build/lib/optimize.js | 11 +++++++++-- build/lib/optimize.ts | 14 ++++++++++++-- build/lib/tsb/transpiler.js | 4 +++- build/lib/tsb/transpiler.ts | 5 ++++- build/lib/tsconfig.js | 28 ++++++++++++++++++++++++++++ build/lib/tsconfig.ts | 24 ++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 build/lib/tsconfig.js create mode 100644 build/lib/tsconfig.ts diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 2a87c239c94..060c1692918 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -54,6 +54,7 @@ const esbuild_1 = __importDefault(require("esbuild")); const gulp_sourcemaps_1 = __importDefault(require("gulp-sourcemaps")); const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); +const tsconfig_1 = require("./tsconfig"); const REPO_ROOT_PATH = path_1.default.join(__dirname, '../..'); const DEFAULT_FILE_HEADER = [ '/*!--------------------------------------------------------', @@ -63,6 +64,7 @@ const DEFAULT_FILE_HEADER = [ function bundleESMTask(opts) { const resourcesStream = event_stream_1.default.through(); // this stream will contain the resources const bundlesStream = event_stream_1.default.through(); // this stream will contain the bundled files + const target = getBuildTarget(); const entryPoints = opts.entryPoints.map(entryPoint => { if (typeof entryPoint === 'string') { return { name: path_1.default.parse(entryPoint).name }; @@ -125,7 +127,7 @@ function bundleESMTask(opts) { format: 'esm', sourcemap: 'external', plugins: [contentsMapper, externalOverride], - target: ['es2022'], + target: [target], loader: { '.ttf': 'file', '.svg': 'file', @@ -185,6 +187,7 @@ function bundleTask(opts) { } function minifyTask(src, sourceMapBaseUrl) { const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; + const target = getBuildTarget(); return cb => { const svgmin = require('gulp-svgmin'); const esbuildFilter = (0, gulp_filter_1.default)('**/*.{js,css}', { restore: true }); @@ -197,7 +200,7 @@ function minifyTask(src, sourceMapBaseUrl) { outdir: '.', packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages platform: 'neutral', // makes esm - target: ['es2022'], + target: [target], write: false, }).then(res => { const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path)); @@ -221,4 +224,8 @@ function minifyTask(src, sourceMapBaseUrl) { }), gulp_1.default.dest(src + '-min'), (err) => cb(err)); }; } +function getBuildTarget() { + const tsconfigPath = path_1.default.join(REPO_ROOT_PATH, 'src', 'tsconfig.base.json'); + return (0, tsconfig_1.getTargetStringFromTsConfig)(tsconfigPath); +} //# sourceMappingURL=optimize.js.map \ No newline at end of file diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 7dde3204703..24ee7f8b724 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -15,6 +15,7 @@ import esbuild from 'esbuild'; import sourcemaps from 'gulp-sourcemaps'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; +import { getTargetStringFromTsConfig } from './tsconfig'; declare module 'gulp-sourcemaps' { interface WriteOptions { @@ -64,6 +65,8 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { const resourcesStream = es.through(); // this stream will contain the resources const bundlesStream = es.through(); // this stream will contain the bundled files + const target = getBuildTarget(); + const entryPoints = opts.entryPoints.map(entryPoint => { if (typeof entryPoint === 'string') { return { name: path.parse(entryPoint).name }; @@ -137,7 +140,7 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { format: 'esm', sourcemap: 'external', plugins: [contentsMapper, externalOverride], - target: ['es2022'], + target: [target], loader: { '.ttf': 'file', '.svg': 'file', @@ -221,6 +224,7 @@ export function bundleTask(opts: IBundleESMTaskOpts): () => NodeJS.ReadWriteStre export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void { const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; + const target = getBuildTarget(); return cb => { const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); @@ -240,7 +244,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => outdir: '.', packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages platform: 'neutral', // makes esm - target: ['es2022'], + target: [target], write: false, }).then(res => { const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path))!; @@ -272,3 +276,9 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => (err: any) => cb(err)); }; } + +function getBuildTarget() { + const tsconfigPath = path.join(REPO_ROOT_PATH, 'src', 'tsconfig.base.json'); + return getTargetStringFromTsConfig(tsconfigPath); +} + diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 2bca1be4426..cbc408114d6 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -13,6 +13,7 @@ const typescript_1 = __importDefault(require("typescript")); const node_worker_threads_1 = __importDefault(require("node:worker_threads")); const vinyl_1 = __importDefault(require("vinyl")); const node_os_1 = require("node:os"); +const tsconfig_1 = require("../tsconfig"); function transpile(tsSrc, options) { const isAmd = /\n(import|export)/m.test(tsSrc); if (!isAmd && options.compilerOptions?.module === typescript_1.default.ModuleKind.AMD) { @@ -240,8 +241,9 @@ class ESBuildTranspiler { _logFn('Transpile', `will use ESBuild to transpile source files`); this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); const isExtension = configFilePath.includes('extensions'); + const target = (0, tsconfig_1.getTargetStringFromTsConfig)(configFilePath); this._transformOpts = { - target: ['es2022'], + target: [target], format: isExtension ? 'cjs' : 'esm', platform: isExtension ? 'node' : undefined, loader: 'ts', diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 0d8d5fdf821..95a1df95b02 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -8,6 +8,7 @@ import ts from 'typescript'; import threads from 'node:worker_threads'; import Vinyl from 'vinyl'; import { cpus } from 'node:os'; +import { getTargetStringFromTsConfig } from '../tsconfig'; interface TranspileReq { readonly tsSrcs: string[]; @@ -311,8 +312,10 @@ export class ESBuildTranspiler implements ITranspiler { const isExtension = configFilePath.includes('extensions'); + const target = getTargetStringFromTsConfig(configFilePath); + this._transformOpts = { - target: ['es2022'], + target: [target], format: isExtension ? 'cjs' : 'esm', platform: isExtension ? 'node' : undefined, loader: 'ts', diff --git a/build/lib/tsconfig.js b/build/lib/tsconfig.js new file mode 100644 index 00000000000..929d74e3b57 --- /dev/null +++ b/build/lib/tsconfig.js @@ -0,0 +1,28 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getTargetStringFromTsConfig = getTargetStringFromTsConfig; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const posix_1 = require("path/posix"); +const typescript_1 = __importDefault(require("typescript")); +/** + * Get the target (e.g. 'ES2024') from a tsconfig.json file. + */ +function getTargetStringFromTsConfig(configFilePath) { + const parsed = typescript_1.default.readConfigFile(configFilePath, typescript_1.default.sys.readFile); + if (parsed.error) { + throw new Error(`Cannot determine target from ${configFilePath}. TS error: ${parsed.error.messageText}`); + } + const cmdLine = typescript_1.default.parseJsonConfigFileContent(parsed.config, typescript_1.default.sys, (0, posix_1.dirname)(configFilePath), {}); + const resolved = typeof cmdLine.options.target !== 'undefined' ? typescript_1.default.ScriptTarget[cmdLine.options.target] : undefined; + if (!resolved) { + throw new Error(`Could not resolve target in ${configFilePath}`); + } + return resolved; +} +//# sourceMappingURL=tsconfig.js.map \ No newline at end of file diff --git a/build/lib/tsconfig.ts b/build/lib/tsconfig.ts new file mode 100644 index 00000000000..9a6077a3b44 --- /dev/null +++ b/build/lib/tsconfig.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { dirname } from 'path/posix'; +import ts from 'typescript'; + +/** + * Get the target (e.g. 'ES2024') from a tsconfig.json file. + */ +export function getTargetStringFromTsConfig(configFilePath: string): string { + const parsed = ts.readConfigFile(configFilePath, ts.sys.readFile); + if (parsed.error) { + throw new Error(`Cannot determine target from ${configFilePath}. TS error: ${parsed.error.messageText}`); + } + + const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname(configFilePath), {}); + const resolved = typeof cmdLine.options.target !== 'undefined' ? ts.ScriptTarget[cmdLine.options.target] : undefined; + if (!resolved) { + throw new Error(`Could not resolve target in ${configFilePath}`); + } + return resolved; +} + From c79433c9de3fd8fcffe087c505086bfdddd94a5d Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:57:26 -0700 Subject: [PATCH 1869/4355] Fix import and use clearer file name --- build/lib/optimize.js | 4 +-- build/lib/optimize.ts | 2 +- build/lib/tsb/transpiler.js | 4 +-- build/lib/tsb/transpiler.ts | 2 +- build/lib/tsconfigUtils.js | 28 +++++++++++++++++++++ build/lib/{tsconfig.ts => tsconfigUtils.ts} | 2 +- 6 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 build/lib/tsconfigUtils.js rename build/lib/{tsconfig.ts => tsconfigUtils.ts} (96%) diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 060c1692918..2ba72a97159 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -54,7 +54,7 @@ const esbuild_1 = __importDefault(require("esbuild")); const gulp_sourcemaps_1 = __importDefault(require("gulp-sourcemaps")); const fancy_log_1 = __importDefault(require("fancy-log")); const ansi_colors_1 = __importDefault(require("ansi-colors")); -const tsconfig_1 = require("./tsconfig"); +const tsconfigUtils_1 = require("./tsconfigUtils"); const REPO_ROOT_PATH = path_1.default.join(__dirname, '../..'); const DEFAULT_FILE_HEADER = [ '/*!--------------------------------------------------------', @@ -226,6 +226,6 @@ function minifyTask(src, sourceMapBaseUrl) { } function getBuildTarget() { const tsconfigPath = path_1.default.join(REPO_ROOT_PATH, 'src', 'tsconfig.base.json'); - return (0, tsconfig_1.getTargetStringFromTsConfig)(tsconfigPath); + return (0, tsconfigUtils_1.getTargetStringFromTsConfig)(tsconfigPath); } //# sourceMappingURL=optimize.js.map \ No newline at end of file diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 24ee7f8b724..1e824a54106 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -15,7 +15,7 @@ import esbuild from 'esbuild'; import sourcemaps from 'gulp-sourcemaps'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; -import { getTargetStringFromTsConfig } from './tsconfig'; +import { getTargetStringFromTsConfig } from './tsconfigUtils'; declare module 'gulp-sourcemaps' { interface WriteOptions { diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index cbc408114d6..07c19c5bae2 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -13,7 +13,7 @@ const typescript_1 = __importDefault(require("typescript")); const node_worker_threads_1 = __importDefault(require("node:worker_threads")); const vinyl_1 = __importDefault(require("vinyl")); const node_os_1 = require("node:os"); -const tsconfig_1 = require("../tsconfig"); +const tsconfigUtils_1 = require("../tsconfigUtils"); function transpile(tsSrc, options) { const isAmd = /\n(import|export)/m.test(tsSrc); if (!isAmd && options.compilerOptions?.module === typescript_1.default.ModuleKind.AMD) { @@ -241,7 +241,7 @@ class ESBuildTranspiler { _logFn('Transpile', `will use ESBuild to transpile source files`); this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); const isExtension = configFilePath.includes('extensions'); - const target = (0, tsconfig_1.getTargetStringFromTsConfig)(configFilePath); + const target = (0, tsconfigUtils_1.getTargetStringFromTsConfig)(configFilePath); this._transformOpts = { target: [target], format: isExtension ? 'cjs' : 'esm', diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 95a1df95b02..f81039d70b6 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -8,7 +8,7 @@ import ts from 'typescript'; import threads from 'node:worker_threads'; import Vinyl from 'vinyl'; import { cpus } from 'node:os'; -import { getTargetStringFromTsConfig } from '../tsconfig'; +import { getTargetStringFromTsConfig } from '../tsconfigUtils'; interface TranspileReq { readonly tsSrcs: string[]; diff --git a/build/lib/tsconfigUtils.js b/build/lib/tsconfigUtils.js new file mode 100644 index 00000000000..a20e2d6f77d --- /dev/null +++ b/build/lib/tsconfigUtils.js @@ -0,0 +1,28 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getTargetStringFromTsConfig = getTargetStringFromTsConfig; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const path_1 = require("path"); +const typescript_1 = __importDefault(require("typescript")); +/** + * Get the target (e.g. 'ES2024') from a tsconfig.json file. + */ +function getTargetStringFromTsConfig(configFilePath) { + const parsed = typescript_1.default.readConfigFile(configFilePath, typescript_1.default.sys.readFile); + if (parsed.error) { + throw new Error(`Cannot determine target from ${configFilePath}. TS error: ${parsed.error.messageText}`); + } + const cmdLine = typescript_1.default.parseJsonConfigFileContent(parsed.config, typescript_1.default.sys, (0, path_1.dirname)(configFilePath), {}); + const resolved = typeof cmdLine.options.target !== 'undefined' ? typescript_1.default.ScriptTarget[cmdLine.options.target] : undefined; + if (!resolved) { + throw new Error(`Could not resolve target in ${configFilePath}`); + } + return resolved; +} +//# sourceMappingURL=tsconfigUtils.js.map \ No newline at end of file diff --git a/build/lib/tsconfig.ts b/build/lib/tsconfigUtils.ts similarity index 96% rename from build/lib/tsconfig.ts rename to build/lib/tsconfigUtils.ts index 9a6077a3b44..0afb2f02ae7 100644 --- a/build/lib/tsconfig.ts +++ b/build/lib/tsconfigUtils.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dirname } from 'path/posix'; +import { dirname } from 'path'; import ts from 'typescript'; /** From 16f58dd3ac0b855df43dcd6a9d32a0911dca320f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:04:24 +0000 Subject: [PATCH 1870/4355] SCM - add incoming/outgoing nodes to graph (#274059) --- extensions/git/package.json | 30 ---- extensions/git/package.nls.json | 2 - extensions/git/src/commands.ts | 16 --- .../browser/scmMultiDiffSourceResolver.ts | 11 +- .../contrib/scm/browser/media/scm.css | 8 +- .../contrib/scm/browser/scm.contribution.ts | 10 ++ .../contrib/scm/browser/scmHistory.ts | 131 +++++++++++++++++- .../scm/browser/scmHistoryChatContext.ts | 3 +- .../contrib/scm/browser/scmHistoryViewPane.ts | 98 +++++++++++-- .../contrib/scm/browser/scmViewService.ts | 4 + .../workbench/contrib/scm/common/history.ts | 8 +- src/vs/workbench/contrib/scm/common/scm.ts | 2 + 12 files changed, 239 insertions(+), 84 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index faa5715d64c..3ffc47c6358 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1005,18 +1005,6 @@ "category": "Git", "enablement": "!operationInProgress" }, - { - "command": "git.graph.openIncomingChanges", - "title": "%command.graphOpenIncomingChanges%", - "category": "Git", - "enablement": "!operationInProgress && git.currentHistoryItemIsBehind" - }, - { - "command": "git.graph.openOutgoingChanges", - "title": "%command.graphOpenOutgoingChanges%", - "category": "Git", - "enablement": "!operationInProgress && git.currentHistoryItemIsAhead" - }, { "command": "git.graph.compareWithMergeBase", "title": "%command.graphCompareWithMergeBase%", @@ -1630,14 +1618,6 @@ "command": "git.graph.cherryPick", "when": "false" }, - { - "command": "git.graph.openIncomingChanges", - "when": "false" - }, - { - "command": "git.graph.openOutgoingChanges", - "when": "false" - }, { "command": "git.graph.compareWithMergeBase", "when": "false" @@ -2260,16 +2240,6 @@ "command": "git.publish", "when": "scmProvider == git && !scmCurrentHistoryItemRefHasRemote", "group": "navigation@903" - }, - { - "command": "git.graph.openIncomingChanges", - "when": "scmProvider == git", - "group": "1_changes@1" - }, - { - "command": "git.graph.openOutgoingChanges", - "when": "scmProvider == git", - "group": "1_changes@2" } ], "scm/historyItem/context": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index fe81fc4322c..b006c426821 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -138,8 +138,6 @@ "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", "command.graphCompareRef": "Compare With...", - "command.graphOpenIncomingChanges": "Open Incoming Changes", - "command.graphOpenOutgoingChanges": "Open Outgoing Changes", "command.graphCompareWithMergeBase": "Compare With Merge Base", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 913fc1f2a93..77bac8b4b1a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3169,22 +3169,6 @@ export class CommandCenter { } } - @command('git.graph.openIncomingChanges', { repository: true }) - async openIncomingChanges(repository: Repository): Promise { - await this._openChangesBetweenRefs( - repository, - repository.historyProvider.currentHistoryItemRef, - repository.historyProvider.currentHistoryItemRemoteRef); - } - - @command('git.graph.openOutgoingChanges', { repository: true }) - async openOutgoingChanges(repository: Repository): Promise { - await this._openChangesBetweenRefs( - repository, - repository.historyProvider.currentHistoryItemRemoteRef, - repository.historyProvider.currentHistoryItemRef); - } - @command('git.graph.compareWithMergeBase', { repository: true }) async compareWithMergeBase(repository: Repository): Promise { await this._openChangesBetweenRefs( diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index 0fb1200a727..ccb9ae59724 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -15,7 +15,6 @@ import { ContextKeyValue } from '../../../../platform/contextkey/common/contextk import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { ISCMHistoryItem } from '../../scm/common/history.js'; import { ISCMProvider, ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js'; import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from './multiDiffSourceResolverService.js'; @@ -96,17 +95,15 @@ interface ScmHistoryItemUriFields { export class ScmHistoryItemResolver implements IMultiDiffSourceResolver { static readonly scheme = 'scm-history-item'; - public static getMultiDiffSourceUri(provider: ISCMProvider, historyItem: ISCMHistoryItem): URI { + public static getMultiDiffSourceUri(provider: ISCMProvider, historyItemId: string, historyItemParentId: string | undefined, historyItemDisplayId: string | undefined): URI { return URI.from({ scheme: ScmHistoryItemResolver.scheme, path: provider.rootUri?.fsPath, query: JSON.stringify({ repositoryId: provider.id, - historyItemId: historyItem.id, - historyItemParentId: historyItem.parentIds.length > 0 - ? historyItem.parentIds[0] - : undefined, - historyItemDisplayId: historyItem.displayId + historyItemId, + historyItemParentId, + historyItemDisplayId } satisfies ScmHistoryItemUriFields) }, true); } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index c14e234b99f..96283850528 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -165,11 +165,15 @@ height: 22px; } -.scm-view .monaco-list-row .history-item > .graph-container.current > .graph > circle:last-child { +.scm-view .monaco-list-row .history-item > .graph-container.current > .graph > circle:last-child, +.scm-view .monaco-list-row .history-item > .graph-container.incoming-changes > .graph > circle:last-child, +.scm-view .monaco-list-row .history-item > .graph-container.outgoing-changes > .graph > circle:last-child { fill: var(--vscode-sideBar-background); } -.scm-view .monaco-list-row:hover .history-item > .graph-container.current > .graph > circle:last-child { +.scm-view .monaco-list-row:hover .history-item > .graph-container.current > .graph > circle:last-child, +.scm-view .monaco-list-row:hover .history-item > .graph-container.incoming-changes > .graph > circle:last-child, +.scm-view .monaco-list-row:hover .history-item > .graph-container.outgoing-changes > .graph > circle:last-child { fill: var(--vscode-list-hoverBackground); } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 89474f78eef..6af5611da1e 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -409,6 +409,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis ], description: localize('scm.graph.badges', "Controls which badges are shown in the Source Control Graph view. The badges are shown on the right side of the graph indicating the names of history item groups."), default: 'filter' + }, + 'scm.graph.showIncomingChanges': { + type: 'boolean', + description: localize('scm.graph.showIncomingChanges', "Controls whether to show incoming changes in the Source Control Graph view."), + default: true + }, + 'scm.graph.showOutgoingChanges': { + type: 'boolean', + description: localize('scm.graph.showOutgoingChanges', "Controls whether to show outgoing changes in the Source Control Graph view."), + default: true } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index 3d18b30716a..1a5926690ce 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -7,10 +7,11 @@ import { localize } from '../../../../nls.js'; import { deepClone } from '../../../../base/common/objects.js'; import { badgeBackground, chartsBlue, chartsPurple, foreground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable, ColorIdentifier, registerColor } from '../../../../platform/theme/common/colorUtils.js'; -import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel } from '../common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, SCMIncomingHistoryItemId, SCMOutgoingHistoryItemId } from '../common/history.js'; import { rot } from '../../../../base/common/numbers.js'; import { svgElem } from '../../../../base/browser/dom.js'; import { PANEL_BACKGROUND } from '../../../common/theme.js'; +import { findLastIdx } from '../../../../base/common/arraysFind.js'; export const SWIMLANE_HEIGHT = 22; export const SWIMLANE_WIDTH = 11; @@ -80,6 +81,19 @@ function drawCircle(index: number, radius: number, strokeWidth: number, colorIde return circle; } +function drawDashedCircle(index: number, radius: number, strokeWidth: number, colorIdentifier: string): SVGCircleElement { + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + circle.setAttribute('cx', `${SWIMLANE_WIDTH * (index + 1)}`); + circle.setAttribute('cy', `${SWIMLANE_WIDTH}`); + circle.setAttribute('r', `${CIRCLE_RADIUS + 1}`); + + circle.style.stroke = asCssVariable(colorIdentifier); + circle.style.strokeWidth = `${strokeWidth}px`; + circle.style.strokeDasharray = '4,2'; + + return circle; +} + function drawVerticalLine(x1: number, y1: number, y2: number, color: string, strokeWidth = 1): SVGPathElement { const path = createPath(color, strokeWidth); path.setAttribute('d', `M ${x1} ${y1} V ${y2}`); @@ -211,13 +225,23 @@ export function renderSCMHistoryItemGraph(historyItemViewModel: ISCMHistoryItemV } // Draw * - if (historyItemViewModel.isCurrent) { + if (historyItemViewModel.kind === 'HEAD') { // HEAD const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 3, CIRCLE_STROKE_WIDTH, circleColor); svg.append(outerCircle); const innerCircle = drawCircle(circleIndex, CIRCLE_STROKE_WIDTH, CIRCLE_RADIUS); svg.append(innerCircle); + } else if (historyItemViewModel.kind === 'incoming-changes' || historyItemViewModel.kind === 'outgoing-changes') { + // Incoming/Outgoing changes + const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 3, CIRCLE_STROKE_WIDTH, circleColor); + svg.append(outerCircle); + + const innerCircle = drawCircle(circleIndex, CIRCLE_STROKE_WIDTH, CIRCLE_RADIUS + 5); + svg.append(innerCircle); + + const dashedCircle = drawDashedCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH, circleColor); + svg.append(dashedCircle); } else { if (historyItem.parentIds.length > 1) { // Multi-parent node @@ -260,7 +284,10 @@ export function toISCMHistoryItemViewModelArray( colorMap = new Map(), currentHistoryItemRef?: ISCMHistoryItemRef, currentHistoryItemRemoteRef?: ISCMHistoryItemRef, - currentHistoryItemBaseRef?: ISCMHistoryItemRef + currentHistoryItemBaseRef?: ISCMHistoryItemRef, + addIncomingChanges?: boolean, + addOutgoingChanges?: boolean, + mergeBase?: string ): ISCMHistoryItemViewModel[] { let colorIndex = -1; const viewModels: ISCMHistoryItemViewModel[] = []; @@ -268,7 +295,7 @@ export function toISCMHistoryItemViewModelArray( for (let index = 0; index < historyItems.length; index++) { const historyItem = historyItems[index]; - const isCurrent = historyItem.id === currentHistoryItemRef?.revision; + const kind = historyItem.id === currentHistoryItemRef?.revision ? 'HEAD' : 'node'; const outputSwimlanesFromPreviousItem = viewModels.at(-1)?.outputSwimlanes ?? []; const inputSwimlanes = outputSwimlanesFromPreviousItem.map(i => deepClone(i)); const outputSwimlanes: ISCMHistoryItemGraphNode[] = []; @@ -346,10 +373,100 @@ export function toISCMHistoryItemViewModelArray( ...historyItem, references }, - isCurrent, + kind, inputSwimlanes, - outputSwimlanes, - }); + outputSwimlanes + } satisfies ISCMHistoryItemViewModel); + } + + // Inject incoming/outgoing changes nodes if ahead/behind and there is a merge base + if (currentHistoryItemRef?.revision !== currentHistoryItemRemoteRef?.revision && mergeBase) { + // Incoming changes node + if (addIncomingChanges && currentHistoryItemRemoteRef && currentHistoryItemRemoteRef.revision !== mergeBase) { + // Find the before/after indices + const beforeHistoryItemIndex = findLastIdx(viewModels, vm => vm.outputSwimlanes.some(node => node.id === mergeBase)); + const afterHistoryItemIndex = viewModels.findIndex(vm => vm.historyItem.id === mergeBase); + + // Update the before node so that the incoming and outgoing swimlanes + // point to the `incoming-changes` node instead of the merge base + viewModels[beforeHistoryItemIndex] = { + ...viewModels[beforeHistoryItemIndex], + inputSwimlanes: viewModels[beforeHistoryItemIndex].inputSwimlanes + .map(node => { + return node.id === mergeBase && node.color === historyItemRemoteRefColor + ? { ...node, id: SCMIncomingHistoryItemId } + : node; + }), + outputSwimlanes: viewModels[beforeHistoryItemIndex].outputSwimlanes + .map(node => { + return node.id === mergeBase && node.color === historyItemRemoteRefColor + ? { ...node, id: SCMIncomingHistoryItemId } + : node; + }) + }; + + // Create incoming changes node + const inputSwimlanes = viewModels[beforeHistoryItemIndex].outputSwimlanes.map(i => deepClone(i)); + const outputSwimlanes = viewModels[afterHistoryItemIndex].inputSwimlanes.map(i => deepClone(i)); + + const incomingChangesHistoryItem = { + id: SCMIncomingHistoryItemId, + parentIds: [mergeBase], + author: currentHistoryItemRemoteRef?.name, + subject: localize('incomingChanges', 'Incoming Changes'), + message: '' + } satisfies ISCMHistoryItem; + + // Insert incoming changes node + viewModels.splice(afterHistoryItemIndex, 0, { + historyItem: incomingChangesHistoryItem, + kind: 'incoming-changes', + inputSwimlanes, + outputSwimlanes + }); + } + + // Outgoing changes node + if (addOutgoingChanges && currentHistoryItemRef?.revision && currentHistoryItemRef.revision !== mergeBase) { + // Find the before/after indices + let beforeHistoryItemIndex = findLastIdx(viewModels, vm => vm.outputSwimlanes.some(node => node.id === currentHistoryItemRef.revision)); + const afterHistoryItemIndex = viewModels.findIndex(vm => vm.historyItem.id === currentHistoryItemRef.revision); + if (beforeHistoryItemIndex === -1 && afterHistoryItemIndex > 0) { + beforeHistoryItemIndex = afterHistoryItemIndex - 1; + } + + // Update the after node to point to the `outgoing-changes` node + viewModels[afterHistoryItemIndex].inputSwimlanes.push({ + id: currentHistoryItemRef.revision, + color: historyItemRefColor + }); + + const inputSwimlanes = beforeHistoryItemIndex !== -1 + ? viewModels[beforeHistoryItemIndex].outputSwimlanes + .map(node => { + return addIncomingChanges && node.id === mergeBase && node.color === historyItemRemoteRefColor + ? { ...node, id: SCMIncomingHistoryItemId } + : node; + }) + : []; + const outputSwimlanes = viewModels[afterHistoryItemIndex].inputSwimlanes.slice(0); + + const outgoingChangesHistoryItem = { + id: SCMOutgoingHistoryItemId, + parentIds: [mergeBase], + author: currentHistoryItemRef?.name, + subject: localize('outgoingChanges', 'Outgoing Changes'), + message: '' + } satisfies ISCMHistoryItem; + + // Insert outgoing changes node + viewModels.splice(afterHistoryItemIndex, 0, { + historyItem: outgoingChangesHistoryItem, + kind: 'outgoing-changes', + inputSwimlanes, + outputSwimlanes + }); + } } return viewModels; diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts index 7701d7dfbc5..dde9e7d5f6b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts @@ -80,7 +80,8 @@ class SCMHistoryItemContext implements IChatContextPickerItem { private readonly _delayer = new ThrottledDelayer(200); public static asAttachment(provider: ISCMProvider, historyItem: ISCMHistoryItem): ISCMHistoryItemVariableEntry { - const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem); + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem.id, historyItemParentId, historyItem.displayId); const attachmentName = `$(${Codicon.repo.id})\u00A0${provider.name}\u00A0$(${Codicon.gitCommit.id})\u00A0${historyItem.displayId ?? historyItem.id}`; return { diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 848f02a5185..d67ea2f1331 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -30,7 +30,7 @@ import { IViewPaneOptions, ViewAction, ViewPane, ViewPaneShowActions } from '../ import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverLabelForeground, historyItemHoverDefaultLabelBackground, getHistoryItemIndex } from './scmHistory.js'; import { getHistoryItemEditorTitle, getProviderKey, isSCMHistoryItemChangeNode, isSCMHistoryItemChangeViewModelTreeElement, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGraphNode, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemChangeViewModelTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement, SCMIncomingHistoryItemId, SCMOutgoingHistoryItemId } from '../common/history.js'; import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService, ViewMode } from '../common/scm.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { stripIcons } from '../../../../base/common/iconLabels.js'; @@ -328,11 +328,23 @@ registerAction2(class extends Action2 { } } - const title = historyItems.length === 1 ? - getHistoryItemEditorTitle(historyItem) : - localize('historyItemChangesEditorTitle', "All Changes ({0} ↔ {1})", historyItemLast.displayId ?? historyItemLast.id, historyItem.displayId ?? historyItem.id); + let title: string, historyItemId: string, historyItemParentId: string | undefined; - const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem); + if (historyItem.id === SCMIncomingHistoryItemId) { + title = historyItem.subject; + historyItemId = historyProvider!.historyItemRemoteRef.get()!.id; + historyItemParentId = historyItem.parentIds[0]; + } else if (historyItem.id === SCMOutgoingHistoryItemId) { + title = historyItem.subject; + historyItemId = historyProvider!.historyItemRef.get()!.id; + historyItemParentId = historyItem.parentIds[0]; + } else { + title = getHistoryItemEditorTitle(historyItem); + historyItemId = historyItem.id; + historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + } + + const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItemId, historyItemParentId, ''); commandService.executeCommand('_workbench.openMultiDiffEditor', { title, multiDiffSourceUri }); } }); @@ -454,7 +466,9 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer 0 ? historyItem.parentIds[0] : undefined; - const historyProvider = inputOrElement.repository.provider.historyProvider.get(); - const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItem.id, historyItemParentId) ?? []; + const historyItemViewModel = inputOrElement.historyItemViewModel; + const historyItem = historyItemViewModel.historyItem; + + let historyItemId: string, historyItemParentId: string | undefined; + + if ( + historyItemViewModel.kind === 'incoming-changes' || + historyItemViewModel.kind === 'outgoing-changes' + ) { + // Incoming/Outgoing changes node + const historyItemRef = historyProvider?.historyItemRef.get(); + const historyItemRemoteRef = historyProvider?.historyItemRemoteRef.get(); + + if (!historyProvider || !historyItemRef || !historyItemRemoteRef) { + return []; + } + + historyItemId = historyItemViewModel.kind === 'incoming-changes' + ? historyItemRemoteRef.id + : historyItemRef.id; + historyItemParentId = historyItem.parentIds[0]; + } else { + // Regular node + historyItemId = historyItem.id; + historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + } + + const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? []; if (this.viewMode() === ViewMode.List) { // List @@ -981,10 +1019,11 @@ class SCMHistoryTreeDragAndDrop implements ITreeDragAndDrop { const provider = element.repository.provider; const historyItem = element.historyItemViewModel.historyItem; const attachmentName = `$(${Codicon.repo.id})\u00A0${provider.name}\u00A0$(${Codicon.gitCommit.id})\u00A0${historyItem.displayId ?? historyItem.id}`; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; historyItems.push({ name: attachmentName, - resource: ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem), + resource: ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem.id, historyItemParentId, historyItem.displayId), historyItem: historyItem }); } @@ -1005,8 +1044,9 @@ class SCMHistoryTreeDragAndDrop implements ITreeDragAndDrop { if (isSCMHistoryItemViewModelTreeElement(element)) { const provider = element.repository.provider; const historyItem = element.historyItemViewModel.historyItem; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; - return ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem); + return ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem.id, historyItemParentId, historyItem.displayId); } return undefined; @@ -1020,6 +1060,7 @@ type HistoryItemRefsFilter = 'all' | 'auto' | string[]; type RepositoryState = { viewModels: SCMHistoryItemViewModelTreeElement[]; historyItemsFilter: ISCMHistoryItemRef[]; + mergeBase: string | undefined; loadMore: boolean | string; }; @@ -1157,6 +1198,8 @@ class SCMHistoryViewModel extends Disposable { async getHistoryItems(): Promise { const repository = this.repository.get(); const historyProvider = repository?.provider.historyProvider.get(); + const historyItemRef = historyProvider?.historyItemRef.get(); + const historyItemRemoteRef = historyProvider?.historyItemRemoteRef.get(); if (!repository || !historyProvider) { this._scmHistoryItemCountCtx.set(0); @@ -1168,6 +1211,9 @@ class SCMHistoryViewModel extends Disposable { if (!state || state.loadMore !== false) { const historyItems = state?.viewModels + .filter(vm => + vm.historyItemViewModel.kind !== 'incoming-changes' && + vm.historyItemViewModel.kind !== 'outgoing-changes') .map(vm => vm.historyItemViewModel.historyItem) ?? []; const historyItemRefs = state?.historyItemsFilter ?? @@ -1183,17 +1229,32 @@ class SCMHistoryViewModel extends Disposable { }) ?? [])); } while (typeof state?.loadMore === 'string' && !historyItems.find(item => item.id === state?.loadMore)); + // Compute the merge base + const mergeBase = historyItemRef && historyItemRemoteRef && state?.mergeBase === undefined + ? await historyProvider.resolveHistoryItemRefsCommonAncestor([ + historyItemRef.name, + historyItemRemoteRef.name]) + : state?.mergeBase; + // Create the color map const colorMap = this._getGraphColorMap(historyItemRefs); - const viewModels = toISCMHistoryItemViewModelArray(historyItems, colorMap, historyProvider.historyItemRef.get()) + const viewModels = toISCMHistoryItemViewModelArray( + historyItems, + colorMap, + historyProvider.historyItemRef.get(), + historyProvider.historyItemRemoteRef.get(), + historyProvider.historyItemBaseRef.get(), + this._scmViewService.graphShowIncomingChangesConfig.get(), + this._scmViewService.graphShowOutgoingChangesConfig.get(), + mergeBase) .map(historyItemViewModel => ({ repository, historyItemViewModel, type: 'historyItemViewModel' }) satisfies SCMHistoryItemViewModelTreeElement); - state = { historyItemsFilter: historyItemRefs, viewModels, loadMore: false }; + state = { historyItemsFilter: historyItemRefs, viewModels, mergeBase, loadMore: false }; this._repositoryState.set(repository, state); this._scmHistoryItemCountCtx.set(viewModels.length); @@ -1542,6 +1603,7 @@ export class SCMHistoryViewPane extends ViewPane { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IMenuService private readonly _menuService: IMenuService, @IProgressService private readonly _progressService: IProgressService, + @ISCMViewService private readonly _scmViewService: ISCMViewService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService keybindingService: IKeybindingService, @@ -1635,6 +1697,14 @@ export class SCMHistoryViewPane extends ViewPane { this._onDidChangeViewWelcomeState.fire(); })); + // Settings change + this._visibilityDisposables.add(runOnChange(this._scmViewService.graphShowIncomingChangesConfig, async () => { + await this.refresh(); + })); + this._visibilityDisposables.add(runOnChange(this._scmViewService.graphShowOutgoingChangesConfig, async () => { + await this.refresh(); + })); + // Repository change let isFirstRun = true; this._visibilityDisposables.add(autorun(reader => { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 132463e8cb2..e3695df1ddc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -112,6 +112,8 @@ export class SCMViewService implements ISCMViewService { readonly menus: ISCMMenus; readonly selectionModeConfig: IObservable; + readonly graphShowIncomingChangesConfig: IObservable; + readonly graphShowOutgoingChangesConfig: IObservable; private didFinishLoading: boolean = false; private didSelectRepository: boolean = false; @@ -241,6 +243,8 @@ export class SCMViewService implements ISCMViewService { ) { this.menus = instantiationService.createInstance(SCMMenus); + this.graphShowIncomingChangesConfig = observableConfigValue('scm.graph.showIncomingChanges', true, this.configurationService); + this.graphShowOutgoingChangesConfig = observableConfigValue('scm.graph.showOutgoingChanges', true, this.configurationService); this.selectionModeConfig = observableConfigValue('scm.repositories.selectionMode', ISCMRepositorySelectionMode.Single, this.configurationService); try { diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index def84c84214..64763e49591 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -8,13 +8,11 @@ import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; -import { IMenu } from '../../../../platform/actions/common/actions.js'; import { ColorIdentifier } from '../../../../platform/theme/common/colorUtils.js'; import { ISCMRepository } from './scm.js'; -export interface ISCMHistoryProviderMenus { - getHistoryItemMenu(historyItem: SCMHistoryItemViewModelTreeElement): IMenu; -} +export const SCMIncomingHistoryItemId = 'scm-graph-incoming-changes'; +export const SCMOutgoingHistoryItemId = 'scm-graph-outgoing-changes'; export interface ISCMHistoryProvider { readonly historyItemRef: IObservable; @@ -84,9 +82,9 @@ export interface ISCMHistoryItemGraphNode { export interface ISCMHistoryItemViewModel { readonly historyItem: ISCMHistoryItem; - readonly isCurrent: boolean; readonly inputSwimlanes: ISCMHistoryItemGraphNode[]; readonly outputSwimlanes: ISCMHistoryItemGraphNode[]; + readonly kind: 'HEAD' | 'node' | 'incoming-changes' | 'outgoing-changes'; } export interface SCMHistoryItemViewModelTreeElement { diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index af0aca559ea..7ee5b227a34 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -227,6 +227,8 @@ export interface ISCMViewService { readonly menus: ISCMMenus; readonly selectionModeConfig: IObservable; + readonly graphShowIncomingChangesConfig: IObservable; + readonly graphShowOutgoingChangesConfig: IObservable; repositories: ISCMRepository[]; readonly onDidChangeRepositories: Event; From 4137ae1e537b0e54b85d8cc0b975cc87fd6e02c7 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:41:46 -0700 Subject: [PATCH 1871/4355] Don't create fake sessions to use as quick pick items This is very error prone. We should use proper typings instead --- .../chat/browser/actions/chatActions.ts | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 0a97470f8e6..9763d4f5dc5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -19,6 +19,7 @@ import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { language } from '../../../../../base/common/platform.js'; import { basename } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { hasKey } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; @@ -682,6 +683,22 @@ export function registerChatActions() { uri?: URI; } + function isChatPickerItem(item: IQuickPickItem): item is IChatPickerItem { + return hasKey(item, 'chat'); + } + + function isCodingAgentPickerItem(item: IQuickPickItem): item is ICodingAgentPickerItem { + return isChatPickerItem(item) && hasKey(item, 'id'); + } + + const showMorePick: IQuickPickItem = { + label: localize('chat.history.showMore', 'Show more...'), + }; + + const showMoreAgentsPick: IQuickPickItem = { + label: localize('chat.history.showMoreAgents', 'Show more...'), + }; + const getPicks = async (showAllChats: boolean = false, showAllAgents: boolean = false) => { // Fast picks: Get cached/immediate items first const cachedItems = await chatService.getLocalSessionHistory(); @@ -705,7 +722,8 @@ export function registerChatActions() { }); const fastPickItems = showAllChats ? allFastPickItems : allFastPickItems.slice(0, 5); - const fastPicks: (IQuickPickSeparator | IChatPickerItem)[] = []; + + const fastPicks: Array = []; if (fastPickItems.length > 0) { fastPicks.push({ type: 'separator', @@ -715,22 +733,13 @@ export function registerChatActions() { // Add "Show more..." if there are more items and we're not showing all chats if (!showAllChats && allFastPickItems.length > 5) { - fastPicks.push({ - label: localize('chat.history.showMore', 'Show more...'), - description: '', - chat: { - sessionId: 'show-more-chats', - title: 'Show more...', - isActive: false, - lastMessageDate: 0, - }, - buttons: [] - }); + + fastPicks.push(showMorePick); } } // Slow picks: Get coding agents asynchronously via AsyncIterable - const slowPicks = (async function* (): AsyncGenerator<(IQuickPickSeparator | ICodingAgentPickerItem)[]> { + const slowPicks = (async function* (): AsyncGenerator> { try { const agentPicks: ICodingAgentPickerItem[] = []; @@ -782,7 +791,7 @@ export function registerChatActions() { } // Create current picks with separator if we have agents - const currentPicks: (IQuickPickSeparator | ICodingAgentPickerItem)[] = []; + const currentPicks: Array = []; if (agentPicks.length > 0) { // Always add separator for coding agents section @@ -794,18 +803,7 @@ export function registerChatActions() { // Add "Show more..." if needed and not showing all agents if (!showAllAgents && providerNSessions.length > 5) { - currentPicks.push({ - label: localize('chat.history.showMoreAgents', 'Show more...'), - description: '', - chat: { - sessionId: 'show-more-agents', - title: 'Show more...', - isActive: false, - lastMessageDate: 0, - }, - buttons: [], - uri: undefined, - }); + currentPicks.push(showMoreAgentsPick); } } @@ -830,7 +828,7 @@ export function registerChatActions() { }; const store = new DisposableStore(); - const picker = store.add(quickInputService.createQuickPick({ useSeparators: true })); + const picker = store.add(quickInputService.createQuickPick({ useSeparators: true })); picker.title = (showAllChats || showAllAgents) ? localize('interactiveSession.history.titleAll', "All Workspace Chat History") : localize('interactiveSession.history.title', "Workspace Chat History"); @@ -866,6 +864,10 @@ export function registerChatActions() { } })); store.add(picker.onDidTriggerItemButton(async context => { + if (!isChatPickerItem(context.item)) { + return; + } + if (context.button === openInEditorButton) { const options: IChatEditorOptions = { pinned: true }; editorService.openEditor({ @@ -934,10 +936,9 @@ export function registerChatActions() { store.add(picker.onDidAccept(async () => { try { const item = picker.selectedItems[0]; - const sessionId = item.chat.sessionId; // Handle "Show more..." options - if (sessionId === 'show-more-chats') { + if (item === showMorePick) { picker.hide(); // Create a new picker with all chat items expanded await this.showIntegratedPicker( @@ -954,7 +955,7 @@ export function registerChatActions() { showAllAgents ); return; - } else if (sessionId === 'show-more-agents') { + } else if (item === showMoreAgentsPick) { picker.hide(); // Create a new picker with all agent items expanded await this.showIntegratedPicker( @@ -971,15 +972,14 @@ export function registerChatActions() { true ); return; - } else if ((item as ICodingAgentPickerItem).id !== undefined) { + } else if (isCodingAgentPickerItem(item)) { // TODO: This is a temporary change that will be replaced by opening a new chat instance - const codingAgentItem = item as ICodingAgentPickerItem; - if (codingAgentItem.session) { - await this.showChatSessionInEditor(codingAgentItem.session.providerType, codingAgentItem.session.session, editorService); + if (item.session) { + await this.showChatSessionInEditor(item.session.providerType, item.session.session, editorService); } + } else if (isChatPickerItem(item)) { + await view.loadSession(item.chat.sessionId); } - - await view.loadSession(sessionId); } finally { picker.hide(); } @@ -1131,7 +1131,7 @@ export function registerChatActions() { options: { pinned: true, auxiliary: { compact: false } - } satisfies IChatEditorOptions + } }, AUX_WINDOW_GROUP); } }); From d2c16dbf3e73246bb393158f6c0d62a19cb8a9c2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:53:37 +0000 Subject: [PATCH 1872/4355] Git - use git diff-tree only for the first commit in the repository (#274107) --- extensions/git/src/git.ts | 28 +++++++++++++++++++++++++--- extensions/git/src/repository.ts | 13 +++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 2b0e2d321ee..d9f930dd398 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1688,9 +1688,8 @@ export class Repository { return result.stdout.trim(); } - async diffBetween2(ref1: string, ref2: string, options: { similarityThreshold?: number; symmetric?: boolean }): Promise { - const range = options.symmetric ? `${ref1}...${ref2}` : `${ref1}..${ref2}`; - return await this.diffFiles(range, { cached: false, similarityThreshold: options.similarityThreshold }); + async diffBetween2(ref1: string, ref2: string, options: { similarityThreshold?: number }): Promise { + return await this.diffFiles(`${ref1}...${ref2}`, { cached: false, similarityThreshold: options.similarityThreshold }); } private async diffFiles(ref: string | undefined, options: { cached: boolean; similarityThreshold?: number }): Promise { @@ -1718,6 +1717,29 @@ export class Repository { return parseGitChanges(this.repositoryRoot, gitResult.stdout); } + async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise { + const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR']; + + if (options?.similarityThreshold) { + args.push(`--find-renames=${options.similarityThreshold}%`); + } + + args.push(treeish1); + + if (treeish2) { + args.push(treeish2); + } + + args.push('--'); + + const gitResult = await this.exec(args); + if (gitResult.exitCode) { + return []; + } + + return parseGitChanges(this.repositoryRoot, gitResult.stdout); + } + async getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { try { const args = ['merge-base']; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 12516966e3d..00d9186fd46 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1232,12 +1232,25 @@ export class Repository implements Disposable { } diffBetween2(ref1: string, ref2: string): Promise { + if (ref1 === this._EMPTY_TREE) { + // Use git diff-tree to get the + // changes in the first commit + return this.diffTrees(ref1, ref2); + } + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.root)); const similarityThreshold = scopedConfig.get('similarityThreshold', 50); return this.run(Operation.Diff, () => this.repository.diffBetween2(ref1, ref2, { similarityThreshold })); } + diffTrees(treeish1: string, treeish2?: string): Promise { + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.root)); + const similarityThreshold = scopedConfig.get('similarityThreshold', 50); + + return this.run(Operation.Diff, () => this.repository.diffTrees(treeish1, treeish2, { similarityThreshold })); + } + getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2, ...refs)); } From f6d9f37da041c72f691eb63054872a267fe77998 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 30 Oct 2025 09:54:23 -0700 Subject: [PATCH 1873/4355] rm the no-op test (#273975) Closes #273879 --- .../chatEditingCheckpointTimeline.test.ts | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts index 942376175e2..25fc09cf623 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingCheckpointTimeline.test.ts @@ -979,39 +979,6 @@ suite('ChatEditingCheckpointTimeline', function () { assert.strictEqual(content, 'req2 modified'); }); - // @connor4312 - this test breaks mangling - // Element implicitly has an 'any' type because expression of type '"_willUndoToCheckpoint"' can't be used to index type '$$ic' - // test('undoing entire request when no operations between start and checkpoint', async function () { - // const uri = URI.parse('file:///test.txt'); - - // timeline.recordFileBaseline(upcastPartial({ - // uri, - // requestId: 'req1', - // content: 'initial', - // epoch: timeline.incrementEpoch(), - // telemetryInfo: DEFAULT_TELEMETRY_INFO - // })); - - // // Create start checkpoint - // timeline.createCheckpoint('req1', undefined, 'Start of Request'); - - // // Create end checkpoint without any operations in between - // timeline.createCheckpoint('req1', 'stop1', 'End of Request'); - - // // At this point, we're past both checkpoints. _willUndoToCheckpoint finds - // // the previous checkpoint before the current epoch. Since there are no operations - // // between the start and stop1, undoing should target the start. - // const undoTarget = timeline['_willUndoToCheckpoint'].get(); - - // // The logic checks if there are operations between start and the previous checkpoint. - // // If not, it returns the start checkpoint. However, if currentEpoch is beyond - // // the last checkpoint and there are no operations, undoTarget might be undefined - // // or it might be the start checkpoint. Let's just verify the behavior. - // if (undoTarget) { - // assert.strictEqual(undoTarget.undoStopId, undefined); // Should target the start checkpoint - // } - // }); - test('getContentAtStop with file that does not exist in operations', async function () { const uri = URI.parse('file:///test.txt'); From be4289696cb0ff121ab80efa300fd423bea0a970 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 30 Oct 2025 17:54:35 +0100 Subject: [PATCH 1874/4355] tweak border style (#274082) --- .../inlineChat/browser/media/inlineChat.css | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 8ec72036592..a566e1e09b9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -83,10 +83,24 @@ padding: 4px 0 0 0; } -.monaco-workbench .inline-chat-2 .inline-chat .chat-widget .interactive-session .interactive-input-part { - padding: 8px 0 0 0; + + +.monaco-workbench .zone-widget.inline-chat-widget.inline-chat-2 { + + .inline-chat .chat-widget .interactive-session .interactive-input-part { + padding: 8px 0 0 0; + } + + .interactive-session .chat-input-container { + border-color: var(--vscode-input-border); + } + + .interactive-session .chat-input-container:focus-within { + border-color: var(--vscode-input-border); + } } + .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { margin-bottom: 1px; } From a4f5e3771142fae9544022e60b374a29552a2b00 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 30 Oct 2025 17:56:37 +0100 Subject: [PATCH 1875/4355] Remove empty pieces when handling an edit (#274079) --- .../editor/common/tokens/sparseTokensStore.ts | 9 ++++- .../test/common/model/tokensStore.test.ts | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/tokens/sparseTokensStore.ts b/src/vs/editor/common/tokens/sparseTokensStore.ts index 69d217f6036..02f80329221 100644 --- a/src/vs/editor/common/tokens/sparseTokensStore.ts +++ b/src/vs/editor/common/tokens/sparseTokensStore.ts @@ -246,8 +246,15 @@ export class SparseTokensStore { } public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void { - for (const piece of this._pieces) { + for (let i = 0; i < this._pieces.length; i++) { + const piece = this._pieces[i]; piece.acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode); + + if (piece.isEmpty()) { + // Remove empty pieces + this._pieces.splice(i, 1); + i--; + } } } } diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index c40695bd46f..8cbfc26afd7 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -494,4 +494,43 @@ suite('TokensStore', () => { 18, createTMMetadata(1, FontStyle.None, 53) ]); }); + + test('piece with startLineNumber 0 and endLineNumber -1 after encompassing deletion', () => { + const codec = new LanguageIdCodec(); + const store = new SparseTokensStore(codec); + + // Set initial tokens on lines 5-10 + const piece = SparseMultilineTokens.create(5, new Uint32Array([ + 0, 0, 5, 1, // line 5, chars 0-5 + 5, 0, 5, 2, // line 10, chars 0-5 + ])); + + store.set([piece], false); + + // Verify initial state + assert.strictEqual(piece.startLineNumber, 5); + assert.strictEqual(piece.endLineNumber, 10); + assert.strictEqual(piece.isEmpty(), false); + + // Perform an edit that completely encompasses the token range + // Delete from line 1 to line 20 (encompasses lines 5-10) + // This triggers the case in _acceptDeleteRange where: + // if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1) + // Which sets this._startLineNumber = 0 and calls this._tokens.clear() + store.acceptEdit( + { startLineNumber: 1, startColumn: 1, endLineNumber: 20, endColumn: 1 }, + 0, // eolCount - no new lines inserted + 0, // firstLineLength + 0, // lastLineLength + 0 // firstCharCode + ); + + // After an encompassing deletion, the piece should be empty + assert.strictEqual(piece.isEmpty(), true, 'Piece should be empty after encompassing deletion'); + + // EXPECTED BEHAVIOR: The store should be empty (no pieces with invalid line numbers) + // Currently fails because the piece remains with startLineNumber=0, endLineNumber=-1 + assert.strictEqual(store.isEmpty(), true, 'Store should be empty after all tokens are deleted by encompassing edit'); + }); }); + From b5aa31586e2042cbb408b569f75a49c6caf91a47 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:56:42 -0700 Subject: [PATCH 1876/4355] Fix haskey checks --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 9763d4f5dc5..db827b245f7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -684,11 +684,11 @@ export function registerChatActions() { } function isChatPickerItem(item: IQuickPickItem): item is IChatPickerItem { - return hasKey(item, 'chat'); + return hasKey(item, { chat: true }); } function isCodingAgentPickerItem(item: IQuickPickItem): item is ICodingAgentPickerItem { - return isChatPickerItem(item) && hasKey(item, 'id'); + return isChatPickerItem(item) && hasKey(item, { id: true }); } const showMorePick: IQuickPickItem = { From f46638bfc70b5e7518607b68bf5521dc018a90be Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:00:30 +0000 Subject: [PATCH 1877/4355] Git - improve reference comparison commands (#274143) * Git - improve compare commands * Remove an extra check * Fix bad manual merge --- extensions/git/package.json | 19 ++++++- extensions/git/package.nls.json | 5 +- extensions/git/src/commands.ts | 92 +++++++++++++++------------------ 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 3ffc47c6358..ec20b66799d 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1005,6 +1005,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.graph.compareWithRemote", + "title": "%command.graphCompareWithRemote%", + "category": "Git", + "enablement": "!operationInProgress && scmCurrentHistoryItemRefHasRemote" + }, { "command": "git.graph.compareWithMergeBase", "title": "%command.graphCompareWithMergeBase%", @@ -1622,6 +1628,10 @@ "command": "git.graph.compareWithMergeBase", "when": "false" }, + { + "command": "git.graph.compareWithRemote", + "when": "false" + }, { "command": "git.diff.stageHunk", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/" @@ -2264,15 +2274,20 @@ "group": "4_modify@1" }, { - "command": "git.graph.compareRef", + "command": "git.graph.compareWithRemote", "when": "scmProvider == git", "group": "5_compare@1" }, { "command": "git.graph.compareWithMergeBase", - "when": "scmProvider == git && scmHistoryItemHasCurrentHistoryItemRef", + "when": "scmProvider == git", "group": "5_compare@2" }, + { + "command": "git.graph.compareRef", + "when": "scmProvider == git", + "group": "5_compare@3" + }, { "command": "git.copyCommitId", "when": "scmProvider == git && !listMultiSelection", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index b006c426821..284767f8b48 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -137,8 +137,9 @@ "command.graphCherryPick": "Cherry Pick", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", - "command.graphCompareRef": "Compare With...", - "command.graphCompareWithMergeBase": "Compare With Merge Base", + "command.graphCompareRef": "Compare with...", + "command.graphCompareWithMergeBase": "Compare with Merge Base", + "command.graphCompareWithRemote": "Compare with Remote", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 77bac8b4b1a..f8bd467ad68 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlHistoryItemRef } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -3109,6 +3109,30 @@ export class CommandCenter { await this._deleteBranch(repository, remoteName, refName, { remote: true }); } + @command('git.graph.compareWithRemote', { repository: true }) + async compareWithRemote(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!historyItem || !repository.historyProvider.currentHistoryItemRemoteRef) { + return; + } + + await this._openChangesBetweenRefs( + repository, + repository.historyProvider.currentHistoryItemRemoteRef.name, + historyItem); + } + + @command('git.graph.compareWithMergeBase', { repository: true }) + async compareWithMergeBase(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!historyItem || !repository.historyProvider.currentHistoryItemBaseRef) { + return; + } + + await this._openChangesBetweenRefs( + repository, + repository.historyProvider.currentHistoryItemBaseRef.name, + historyItem); + } + @command('git.graph.compareRef', { repository: true }) async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { if (!repository || !historyItem) { @@ -3137,68 +3161,38 @@ export class CommandCenter { return; } - if (historyItem.id === sourceRef.ref.commit) { - window.showInformationMessage(l10n.t('The selected references are the same.')); - return; - } - - const sourceCommit = sourceRef.ref.commit; - - try { - const changes = await repository.diffBetween2(sourceCommit, historyItem.id); - - if (changes.length === 0) { - window.showInformationMessage(l10n.t('The selected references have no differences.')); - return; - } - - const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceCommit, historyItem.id)); - const title = `${sourceRef.ref.name ?? sourceCommit} ↔ ${historyItem.references?.[0].name ?? historyItem.id}`; - const multiDiffSourceUri = Uri.from({ - scheme: 'git-ref-compare', - path: `${repository.root}/${sourceCommit}..${historyItem.id}` - }); - - await commands.executeCommand('_workbench.openMultiDiffEditor', { - multiDiffSourceUri, - title, - resources - }); - } catch (err) { - window.showErrorMessage(l10n.t('Failed to compare references "{0}" and "{1}": {2}', sourceCommit, historyItem.id, err.message)); - } - } - - @command('git.graph.compareWithMergeBase', { repository: true }) - async compareWithMergeBase(repository: Repository): Promise { await this._openChangesBetweenRefs( repository, - repository.historyProvider.currentHistoryItemBaseRef, - repository.historyProvider.currentHistoryItemRef); + sourceRef.ref.name, + historyItem); } - private async _openChangesBetweenRefs( - repository: Repository, - ref1: SourceControlHistoryItemRef | undefined, - ref2: SourceControlHistoryItemRef | undefined - ): Promise { - if (!repository || !ref1 || !ref2) { + private async _openChangesBetweenRefs(repository: Repository, ref: string | undefined, historyItem: SourceControlHistoryItem | undefined): Promise { + if (!repository || !ref || !historyItem) { return; } + const ref2 = historyItem.references?.length + ? historyItem.references[0].name + : historyItem.id; + try { - const changes = await repository.diffBetween2(ref1.id, ref2.id); + const changes = await repository.diffBetween2(ref, historyItem.id); if (changes.length === 0) { - window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref1.name, ref2.name)); + window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref, ref2)); return; } - const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref1.id, ref2.id)); - const title = `${ref1.name} ↔ ${ref2.name}`; + const refDisplayName = historyItem.references?.length + ? historyItem.references[0].name + : `${historyItem.displayId || historyItem.id} - ${historyItem.subject}`; + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref, ref2)); + const title = `${ref} ↔ ${refDisplayName}`; const multiDiffSourceUri = Uri.from({ scheme: 'git-ref-compare', - path: `${repository.root}/${ref1.id}..${ref2.id}` + path: `${repository.root}/${ref}..${ref2}` }); await commands.executeCommand('_workbench.openMultiDiffEditor', { @@ -3207,7 +3201,7 @@ export class CommandCenter { resources }); } catch (err) { - window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref1.name, ref2.name, err.message)); + window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref, ref2, err.message)); } } From 75d57a0557009cc9061ebd3302d0d027c6f9876e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Oct 2025 18:01:33 +0100 Subject: [PATCH 1878/4355] add advanced badge (#274147) --- .../browser/settingsEditorSettingIndicators.ts | 15 ++++++++++----- .../contrib/preferences/common/preferences.ts | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 50ce47140b2..8b3463d3c33 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -21,7 +21,7 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js'; import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; -import { EXPERIMENTAL_INDICATOR_DESCRIPTION, POLICY_SETTING_TAG, PREVIEW_INDICATOR_DESCRIPTION } from '../common/preferences.js'; +import { ADVANCED_INDICATOR_DESCRIPTION, EXPERIMENTAL_INDICATOR_DESCRIPTION, POLICY_SETTING_TAG, PREVIEW_INDICATOR_DESCRIPTION } from '../common/preferences.js'; import { SettingsTreeSettingElement } from './settingsTreeModels.js'; const $ = DOM.$; @@ -319,12 +319,15 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { updatePreviewIndicator(element: SettingsTreeSettingElement) { const isPreviewSetting = element.tags?.has('preview'); const isExperimentalSetting = element.tags?.has('experimental'); - this.previewIndicator.element.style.display = (isPreviewSetting || isExperimentalSetting) ? 'inline' : 'none'; + const isAdvancedSetting = element.tags?.has('advanced'); + this.previewIndicator.element.style.display = (isPreviewSetting || isExperimentalSetting || isAdvancedSetting) ? 'inline' : 'none'; this.previewIndicator.label.text = isPreviewSetting ? localize('previewLabel', "Preview") : - localize('experimentalLabel', "Experimental"); + isExperimentalSetting ? + localize('experimentalLabel', "Experimental") : + localize('advancedLabel', "Advanced"); - const content = isPreviewSetting ? PREVIEW_INDICATOR_DESCRIPTION : EXPERIMENTAL_INDICATOR_DESCRIPTION; + const content = isPreviewSetting ? PREVIEW_INDICATOR_DESCRIPTION : isExperimentalSetting ? EXPERIMENTAL_INDICATOR_DESCRIPTION : ADVANCED_INDICATOR_DESCRIPTION; const showHover = (focus: boolean) => { return this.hoverService.showInstantHover({ ...this.defaultHoverOptions, @@ -570,11 +573,13 @@ function getAccessibleScopeDisplayMidSentenceText(completeScope: string, languag export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IWorkbenchConfigurationService, userDataProfilesService: IUserDataProfilesService, languageService: ILanguageService): string { const ariaLabelSections: string[] = []; - // Add preview or experimental indicator text + // Add preview or experimental or advanced indicator text if (element.tags?.has('preview')) { ariaLabelSections.push(localize('previewLabel', "Preview")); } else if (element.tags?.has('experimental')) { ariaLabelSections.push(localize('experimentalLabel', "Experimental")); + } else if (element.tags?.has('advanced')) { + ariaLabelSections.push(localize('advancedLabel', "Advanced")); } // Add workspace trust text diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 812be33d82d..1d7d9e4cdaa 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -213,6 +213,7 @@ export function compareTwoNullableNumbers(a: number | undefined, b: number | und export const PREVIEW_INDICATOR_DESCRIPTION = localize('previewIndicatorDescription', "Preview setting: this setting controls a new feature that is still under refinement yet ready to use. Feedback is welcome."); export const EXPERIMENTAL_INDICATOR_DESCRIPTION = localize('experimentalIndicatorDescription', "Experimental setting: this setting controls a new feature that is actively being developed and may be unstable. It is subject to change or removal."); +export const ADVANCED_INDICATOR_DESCRIPTION = localize('advancedIndicatorDescription', "Advanced setting: this setting is intended for advanced scenarios and configurations. Only modify this if you know what it does."); export const knownAcronyms = new Set(); [ From 52393883c34b5ff807dc7dbe316c7f410ad742a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:08:22 -0700 Subject: [PATCH 1879/4355] Move extractSubCommands tests into new suite --- .../treeSitterCommandParser.test.ts | 294 +++++++++--------- 1 file changed, 148 insertions(+), 146 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 3fadbc9bb16..6ad0ad313c6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -39,160 +39,162 @@ import { arch } from '../../../../../../base/common/process.js'; parser = instantiationService.createInstance(TreeSitterCommandParser); }); - suite('Bash command parsing', () => { - async function t(commandLine: string, expectedCommands: string[]) { - const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.Bash, commandLine); - deepStrictEqual(result, expectedCommands); - } - - test('simple commands', () => t('ls -la', ['ls -la'])); - test('commands with &&', () => t('echo hello && ls -la', ['echo hello', 'ls -la'])); - test('commands with ||', () => t('test -f file.txt || touch file.txt', ['test -f file.txt', 'touch file.txt'])); - test('commands with semicolons', () => t('cd /tmp; ls; pwd', ['cd /tmp', 'ls', 'pwd'])); - test('pipe chains', () => t('cat file.txt | grep pattern | sort | uniq', ['cat file.txt', 'grep pattern', 'sort', 'uniq'])); - test('commands with subshells', () => t('echo $(date +%Y) && ls', ['echo $(date +%Y)', 'date +%Y', 'ls'])); - test('complex quoting', () => t('echo "hello && world" && echo \'test\'', ['echo "hello && world"', 'echo \'test\''])); - test('escaped characters', () => t('echo hello\\ world && ls', ['echo hello\\ world', 'ls'])); - test('background commands', () => t('sleep 10 & echo done', ['sleep 10', 'echo done'])); - test('variable assignments', () => t('VAR=value command1 && echo $VAR', ['VAR=value command1', 'echo $VAR'])); - test('redirections', () => t('echo hello > file.txt && cat < file.txt', ['echo hello', 'cat'])); - test('arithmetic expansion', () => t('echo $((1 + 2)) && ls', ['echo $((1 + 2))', 'ls'])); - test('nested command substitution', () => t('echo $(cat $(echo file.txt)) && ls', ['echo $(cat $(echo file.txt))', 'cat $(echo file.txt)', 'echo file.txt', 'ls'])); - test('mixed operators', () => t('cmd1 && cmd2 || cmd3; cmd4 | cmd5 & cmd6', ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd5', 'cmd6'])); - test('parameter expansion', () => t('echo ${VAR:-default} && echo ${#VAR}', ['echo ${VAR:-default}', 'echo ${#VAR}'])); - test('process substitution', () => t('diff <(sort file1) <(sort file2) && echo done', ['diff <(sort file1) <(sort file2)', 'sort file1', 'sort file2', 'echo done'])); - test('brace expansion', () => t('echo {a,b,c}.txt && ls', ['echo {a,b,c}.txt', 'ls'])); - test('tilde expansion', () => t('cd ~/Documents && ls ~/.bashrc', ['cd ~/Documents', 'ls ~/.bashrc'])); - - suite('control flow and structures', () => { - test('if-then-else', () => t('if [ -f file.txt ]; then cat file.txt; else echo "not found"; fi', ['cat file.txt', 'echo "not found"'])); - test('simple iteration', () => t('for file in *.txt; do cat "$file"; done', ['cat "$file"'])); - test('function declaration and call', () => t('function test_func() { echo "inside function"; } && test_func', ['echo "inside function"', 'test_func'])); - test('heredoc with commands', () => t('cat << EOF\nhello\nworld\nEOF\necho done', ['cat', 'echo done'])); - test('while loop', () => t('while read line; do echo "$line"; done < file.txt', ['read line', 'echo "$line"'])); - test('case statement', () => t('case $var in pattern1) echo "match1" ;; pattern2) echo "match2" ;; esac', ['echo "match1"', 'echo "match2"'])); - test('until loop', () => t('until [ -f ready.txt ]; do sleep 1; done && echo ready', ['sleep 1', 'echo ready'])); - test('nested conditionals', () => t('if [ -f file ]; then if [ -r file ]; then cat file; fi; fi', ['cat file'])); - test('test command alternatives', () => t('[[ -f file ]] && cat file || echo missing', ['cat file', 'echo missing'])); - }); + suite('extractSubCommands', () => { + suite('bash', () => { + async function t(commandLine: string, expectedCommands: string[]) { + const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.Bash, commandLine); + deepStrictEqual(result, expectedCommands); + } - suite('edge cases', () => { - test('malformed syntax', () => t('echo "unclosed quote && ls', ['echo'])); - test('unmatched parentheses', () => t('echo $(missing closing && ls', ['echo $(missing closing && ls', 'missing closing', 'ls'])); - test('very long command lines', () => t('echo ' + 'a'.repeat(10000) + ' && ls', ['echo ' + 'a'.repeat(10000), 'ls'])); - test('special characters', () => t('echo "πλαςε 测试 🚀" && ls', ['echo "πλαςε 测试 🚀"', 'ls'])); - test('multiline with continuations', () => t('echo hello \\\n&& echo world \\\n&& ls', ['echo hello', 'echo world', 'ls'])); - test('commands with comments', () => t('echo hello # this is a comment\nls # another comment', ['echo hello', 'ls'])); - test('empty command in chain', () => t('echo hello && && echo world', ['echo hello', 'echo world'])); - test('trailing operators', () => t('echo hello &&', ['echo hello', ''])); - test('only operators', () => t('&& || ;', [])); - test('nested quotes', () => t('echo "outer \"inner\" outer" && ls', ['echo "outer \"inner\" outer"', 'ls'])); - test('incomplete escape sequences', () => t('echo hello\\ && ls', ['echo hello\\ ', 'ls'])); - test('mixed quote types', () => t('echo "hello \`world\`" && echo \'test\'', ['echo "hello \`world\`"', 'world', 'echo \'test\''])); - test('deeply nested structures', () => t('echo $(echo $(echo $(echo nested))) && ls', ['echo $(echo $(echo $(echo nested)))', 'echo $(echo $(echo nested))', 'echo $(echo nested)', 'echo nested', 'ls'])); - test('unicode command names', () => t('测试命令 && echo done', ['测试命令', 'echo done'])); - test('multi-line', () => t('echo a\necho b', ['echo a', 'echo b'])); + test('simple commands', () => t('ls -la', ['ls -la'])); + test('commands with &&', () => t('echo hello && ls -la', ['echo hello', 'ls -la'])); + test('commands with ||', () => t('test -f file.txt || touch file.txt', ['test -f file.txt', 'touch file.txt'])); + test('commands with semicolons', () => t('cd /tmp; ls; pwd', ['cd /tmp', 'ls', 'pwd'])); + test('pipe chains', () => t('cat file.txt | grep pattern | sort | uniq', ['cat file.txt', 'grep pattern', 'sort', 'uniq'])); + test('commands with subshells', () => t('echo $(date +%Y) && ls', ['echo $(date +%Y)', 'date +%Y', 'ls'])); + test('complex quoting', () => t('echo "hello && world" && echo \'test\'', ['echo "hello && world"', 'echo \'test\''])); + test('escaped characters', () => t('echo hello\\ world && ls', ['echo hello\\ world', 'ls'])); + test('background commands', () => t('sleep 10 & echo done', ['sleep 10', 'echo done'])); + test('variable assignments', () => t('VAR=value command1 && echo $VAR', ['VAR=value command1', 'echo $VAR'])); + test('redirections', () => t('echo hello > file.txt && cat < file.txt', ['echo hello', 'cat'])); + test('arithmetic expansion', () => t('echo $((1 + 2)) && ls', ['echo $((1 + 2))', 'ls'])); + test('nested command substitution', () => t('echo $(cat $(echo file.txt)) && ls', ['echo $(cat $(echo file.txt))', 'cat $(echo file.txt)', 'echo file.txt', 'ls'])); + test('mixed operators', () => t('cmd1 && cmd2 || cmd3; cmd4 | cmd5 & cmd6', ['cmd1', 'cmd2', 'cmd3', 'cmd4', 'cmd5', 'cmd6'])); + test('parameter expansion', () => t('echo ${VAR:-default} && echo ${#VAR}', ['echo ${VAR:-default}', 'echo ${#VAR}'])); + test('process substitution', () => t('diff <(sort file1) <(sort file2) && echo done', ['diff <(sort file1) <(sort file2)', 'sort file1', 'sort file2', 'echo done'])); + test('brace expansion', () => t('echo {a,b,c}.txt && ls', ['echo {a,b,c}.txt', 'ls'])); + test('tilde expansion', () => t('cd ~/Documents && ls ~/.bashrc', ['cd ~/Documents', 'ls ~/.bashrc'])); + + suite('control flow and structures', () => { + test('if-then-else', () => t('if [ -f file.txt ]; then cat file.txt; else echo "not found"; fi', ['cat file.txt', 'echo "not found"'])); + test('simple iteration', () => t('for file in *.txt; do cat "$file"; done', ['cat "$file"'])); + test('function declaration and call', () => t('function test_func() { echo "inside function"; } && test_func', ['echo "inside function"', 'test_func'])); + test('heredoc with commands', () => t('cat << EOF\nhello\nworld\nEOF\necho done', ['cat', 'echo done'])); + test('while loop', () => t('while read line; do echo "$line"; done < file.txt', ['read line', 'echo "$line"'])); + test('case statement', () => t('case $var in pattern1) echo "match1" ;; pattern2) echo "match2" ;; esac', ['echo "match1"', 'echo "match2"'])); + test('until loop', () => t('until [ -f ready.txt ]; do sleep 1; done && echo ready', ['sleep 1', 'echo ready'])); + test('nested conditionals', () => t('if [ -f file ]; then if [ -r file ]; then cat file; fi; fi', ['cat file'])); + test('test command alternatives', () => t('[[ -f file ]] && cat file || echo missing', ['cat file', 'echo missing'])); + }); + + suite('edge cases', () => { + test('malformed syntax', () => t('echo "unclosed quote && ls', ['echo'])); + test('unmatched parentheses', () => t('echo $(missing closing && ls', ['echo $(missing closing && ls', 'missing closing', 'ls'])); + test('very long command lines', () => t('echo ' + 'a'.repeat(10000) + ' && ls', ['echo ' + 'a'.repeat(10000), 'ls'])); + test('special characters', () => t('echo "πλαςε 测试 🚀" && ls', ['echo "πλαςε 测试 🚀"', 'ls'])); + test('multiline with continuations', () => t('echo hello \\\n&& echo world \\\n&& ls', ['echo hello', 'echo world', 'ls'])); + test('commands with comments', () => t('echo hello # this is a comment\nls # another comment', ['echo hello', 'ls'])); + test('empty command in chain', () => t('echo hello && && echo world', ['echo hello', 'echo world'])); + test('trailing operators', () => t('echo hello &&', ['echo hello', ''])); + test('only operators', () => t('&& || ;', [])); + test('nested quotes', () => t('echo "outer \"inner\" outer" && ls', ['echo "outer \"inner\" outer"', 'ls'])); + test('incomplete escape sequences', () => t('echo hello\\ && ls', ['echo hello\\ ', 'ls'])); + test('mixed quote types', () => t('echo "hello \`world\`" && echo \'test\'', ['echo "hello \`world\`"', 'world', 'echo \'test\''])); + test('deeply nested structures', () => t('echo $(echo $(echo $(echo nested))) && ls', ['echo $(echo $(echo $(echo nested)))', 'echo $(echo $(echo nested))', 'echo $(echo nested)', 'echo nested', 'ls'])); + test('unicode command names', () => t('测试命令 && echo done', ['测试命令', 'echo done'])); + test('multi-line', () => t('echo a\necho b', ['echo a', 'echo b'])); + }); + + // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704 + suite('real-world scenarios', () => { + test('complex Docker commands', () => t('docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', ['docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', 'pwd'])); + test('Git workflow commands', () => t('git add . && git commit -m "Update feature" && git push origin main', [ + 'git add .', + 'git commit -m "Update feature"', + 'git push origin main' + ])); + test('npm/yarn workflow commands', () => t('npm ci && npm run build && npm test && npm run lint', [ + 'npm ci', + 'npm run build', + 'npm test', + 'npm run lint' + ])); + test('build system commands', () => t('make clean && make -j$(nproc) && make install PREFIX=/usr/local', [ + 'make clean', + 'make -j$(nproc)', + 'nproc', + 'make install PREFIX=/usr/local' + ])); + test('deployment script', () => t('rsync -avz --delete src/ user@server:/path/ && ssh user@server "systemctl restart service" && echo "Deployed successfully"', [ + 'rsync -avz --delete src/ user@server:/path/', + 'ssh user@server "systemctl restart service"', + 'echo "Deployed successfully"' + ])); + test('database backup script', () => t('mysqldump -u user -p database > backup_$(date +%Y%m%d).sql && gzip backup_$(date +%Y%m%d).sql && echo "Backup complete"', [ + 'mysqldump -u user -p database', + 'date +%Y%m%d', + 'gzip backup_$(date +%Y%m%d).sql', + 'date +%Y%m%d', + 'echo "Backup complete"' + ])); + test('log analysis pipeline', () => t('tail -f /var/log/app.log | grep ERROR | while read line; do echo "$(date): $line" >> error.log; done', [ + 'tail -f /var/log/app.log', + 'grep ERROR', + 'read line', + 'echo "$(date): $line"', + 'date' + ])); + test('conditional installation', () => t('which docker || (curl -fsSL https://get.docker.com | sh && systemctl enable docker) && docker --version', [ + 'which docker', + 'curl -fsSL https://get.docker.com', + 'sh', + 'systemctl enable docker', + 'docker --version' + ])); + }); }); - // TODO: These should be common but the pwsh grammar doesn't handle && yet https://github.com/microsoft/vscode/issues/272704 - suite('real-world scenarios', () => { - test('complex Docker commands', () => t('docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', ['docker run -it --rm -v $(pwd):/app ubuntu:latest bash -c "cd /app && npm install && npm test"', 'pwd'])); - test('Git workflow commands', () => t('git add . && git commit -m "Update feature" && git push origin main', [ - 'git add .', - 'git commit -m "Update feature"', - 'git push origin main' - ])); - test('npm/yarn workflow commands', () => t('npm ci && npm run build && npm test && npm run lint', [ - 'npm ci', - 'npm run build', - 'npm test', - 'npm run lint' - ])); - test('build system commands', () => t('make clean && make -j$(nproc) && make install PREFIX=/usr/local', [ - 'make clean', - 'make -j$(nproc)', - 'nproc', - 'make install PREFIX=/usr/local' - ])); - test('deployment script', () => t('rsync -avz --delete src/ user@server:/path/ && ssh user@server "systemctl restart service" && echo "Deployed successfully"', [ - 'rsync -avz --delete src/ user@server:/path/', - 'ssh user@server "systemctl restart service"', - 'echo "Deployed successfully"' - ])); - test('database backup script', () => t('mysqldump -u user -p database > backup_$(date +%Y%m%d).sql && gzip backup_$(date +%Y%m%d).sql && echo "Backup complete"', [ - 'mysqldump -u user -p database', - 'date +%Y%m%d', - 'gzip backup_$(date +%Y%m%d).sql', - 'date +%Y%m%d', - 'echo "Backup complete"' - ])); - test('log analysis pipeline', () => t('tail -f /var/log/app.log | grep ERROR | while read line; do echo "$(date): $line" >> error.log; done', [ - 'tail -f /var/log/app.log', - 'grep ERROR', - 'read line', - 'echo "$(date): $line"', - 'date' - ])); - test('conditional installation', () => t('which docker || (curl -fsSL https://get.docker.com | sh && systemctl enable docker) && docker --version', [ - 'which docker', - 'curl -fsSL https://get.docker.com', - 'sh', - 'systemctl enable docker', - 'docker --version' - ])); - }); - }); + suite('pwsh', () => { + async function t(commandLine: string, expectedCommands: string[]) { + const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.PowerShell, commandLine); + deepStrictEqual(result, expectedCommands); + } - suite('PowerShell command parsing', () => { - async function t(commandLine: string, expectedCommands: string[]) { - const result = await parser.extractSubCommands(TreeSitterCommandParserLanguage.PowerShell, commandLine); - deepStrictEqual(result, expectedCommands); - } - - test('simple commands', () => t('Get-ChildItem -Path C:\\', ['Get-ChildItem -Path C:\\'])); - test('commands with semicolons', () => t('Get-Date; Get-Location; Write-Host "done"', ['Get-Date', 'Get-Location', 'Write-Host "done"'])); - test('pipeline commands', () => t('Get-Process | Where-Object {$_.CPU -gt 100} | Sort-Object CPU', ['Get-Process ', 'Where-Object {$_.CPU -gt 100} ', 'Sort-Object CPU'])); - test('command substitution', () => t('Write-Host $(Get-Date) ; Get-Location', ['Write-Host $(Get-Date)', 'Get-Date', 'Get-Location'])); - test('complex parameters', () => t('Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"', ['Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"'])); - test('splatting', () => t('$params = @{Path="C:\\"; Recurse=$true}; Get-ChildItem @params', ['Get-ChildItem @params'])); - test('here-strings', () => t('Write-Host @"\nhello\nworld\n"@ ; Get-Date', ['Write-Host @"\nhello\nworld\n"@', 'Get-Date'])); - test('method calls', () => t('"hello".ToUpper() ; Get-Date', ['Get-Date'])); - test('complex quoting', () => t('Write-Host "She said `"Hello`"" ; Write-Host \'Single quotes\'', ['Write-Host "She said `"Hello`""', 'Write-Host \'Single quotes\''])); - test('array operations', () => t('$arr = @(1,2,3); $arr | ForEach-Object { $_ * 2 }', ['ForEach-Object { $_ * 2 }'])); - test('hashtable operations', () => t('$hash = @{key="value"}; Write-Host $hash.key', ['Write-Host $hash.key'])); - test('type casting', () => t('[int]"123" + [int]"456" ; Write-Host "done"', ['Write-Host "done"'])); - test('regex operations', () => t('"hello world" -match "w.*d" ; Get-Date', ['Get-Date'])); - test('comparison operators', () => t('5 -gt 3 -and "hello" -like "h*" ; Write-Host "true"', ['Write-Host "true"'])); - test('null-conditional operators', () => t('$obj?.Property?.SubProperty ; Get-Date', ['Get-Date'])); - test('string interpolation', () => t('$name="World"; "Hello $name" ; Get-Date', ['Get-Date'])); - test('expandable strings', () => t('$var="test"; "Value: $($var.ToUpper())" ; Get-Date', ['Get-Date'])); - - suite('Control flow and structures', () => { - test('logical and', () => t('Test-Path "file.txt" -and Get-Content "file.txt"', ['Test-Path "file.txt" -and Get-Content "file.txt"'])); - test('foreach with script block', () => t('ForEach-Object { Write-Host $_.Name } ; Get-Date', ['ForEach-Object { Write-Host $_.Name }', 'Write-Host $_.Name', 'Get-Date'])); - test('if-else', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" } else { Write-Host "not found" }', ['Test-Path "file.txt"', 'Get-Content "file.txt"', 'Write-Host "not found"'])); - test('error handling', () => t('try { Get-Content "file.txt" } catch { Write-Error "failed" }', ['Get-Content "file.txt"', 'Write-Error "failed"'])); - test('switch statement', () => t('switch ($var) { 1 { "one" } 2 { "two" } default { "other" } } ; Get-Date', ['Get-Date'])); - test('do-while loop', () => t('do { Write-Host $i; $i++ } while ($i -lt 5) ; Get-Date', ['Write-Host $i', 'Get-Date'])); - test('for loop', () => t('for ($i=0; $i -lt 5; $i++) { Write-Host $i } ; Get-Date', ['Write-Host $i', 'Get-Date'])); - test('foreach loop with range', () => t('foreach ($i in 1..5) { Write-Host $i } ; Get-Date', ['1..5', 'Write-Host $i', 'Get-Date'])); - test('break and continue', () => t('while ($true) { if ($condition) { break } ; Write-Host "running" } ; Get-Date', ['Write-Host "running"', 'Get-Date'])); - test('nested try-catch-finally', () => t('try { try { Get-Content "file" } catch { throw } } catch { Write-Error "outer" } finally { Write-Host "cleanup" }', ['Get-Content "file"', 'Write-Error "outer"', 'Write-Host "cleanup"'])); - test('parallel processing', () => t('1..10 | ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ } ; Get-Date', ['1..10 ', 'ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ }', 'Start-Sleep 1', 'Write-Host $_', 'Get-Date'])); + test('simple commands', () => t('Get-ChildItem -Path C:\\', ['Get-ChildItem -Path C:\\'])); + test('commands with semicolons', () => t('Get-Date; Get-Location; Write-Host "done"', ['Get-Date', 'Get-Location', 'Write-Host "done"'])); + test('pipeline commands', () => t('Get-Process | Where-Object {$_.CPU -gt 100} | Sort-Object CPU', ['Get-Process ', 'Where-Object {$_.CPU -gt 100} ', 'Sort-Object CPU'])); + test('command substitution', () => t('Write-Host $(Get-Date) ; Get-Location', ['Write-Host $(Get-Date)', 'Get-Date', 'Get-Location'])); + test('complex parameters', () => t('Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"', ['Get-ChildItem -Path "C:\\Program Files" -Recurse -Include "*.exe"'])); + test('splatting', () => t('$params = @{Path="C:\\"; Recurse=$true}; Get-ChildItem @params', ['Get-ChildItem @params'])); + test('here-strings', () => t('Write-Host @"\nhello\nworld\n"@ ; Get-Date', ['Write-Host @"\nhello\nworld\n"@', 'Get-Date'])); + test('method calls', () => t('"hello".ToUpper() ; Get-Date', ['Get-Date'])); + test('complex quoting', () => t('Write-Host "She said `"Hello`"" ; Write-Host \'Single quotes\'', ['Write-Host "She said `"Hello`""', 'Write-Host \'Single quotes\''])); + test('array operations', () => t('$arr = @(1,2,3); $arr | ForEach-Object { $_ * 2 }', ['ForEach-Object { $_ * 2 }'])); + test('hashtable operations', () => t('$hash = @{key="value"}; Write-Host $hash.key', ['Write-Host $hash.key'])); + test('type casting', () => t('[int]"123" + [int]"456" ; Write-Host "done"', ['Write-Host "done"'])); + test('regex operations', () => t('"hello world" -match "w.*d" ; Get-Date', ['Get-Date'])); + test('comparison operators', () => t('5 -gt 3 -and "hello" -like "h*" ; Write-Host "true"', ['Write-Host "true"'])); + test('null-conditional operators', () => t('$obj?.Property?.SubProperty ; Get-Date', ['Get-Date'])); + test('string interpolation', () => t('$name="World"; "Hello $name" ; Get-Date', ['Get-Date'])); + test('expandable strings', () => t('$var="test"; "Value: $($var.ToUpper())" ; Get-Date', ['Get-Date'])); + + suite('Control flow and structures', () => { + test('logical and', () => t('Test-Path "file.txt" -and Get-Content "file.txt"', ['Test-Path "file.txt" -and Get-Content "file.txt"'])); + test('foreach with script block', () => t('ForEach-Object { Write-Host $_.Name } ; Get-Date', ['ForEach-Object { Write-Host $_.Name }', 'Write-Host $_.Name', 'Get-Date'])); + test('if-else', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" } else { Write-Host "not found" }', ['Test-Path "file.txt"', 'Get-Content "file.txt"', 'Write-Host "not found"'])); + test('error handling', () => t('try { Get-Content "file.txt" } catch { Write-Error "failed" }', ['Get-Content "file.txt"', 'Write-Error "failed"'])); + test('switch statement', () => t('switch ($var) { 1 { "one" } 2 { "two" } default { "other" } } ; Get-Date', ['Get-Date'])); + test('do-while loop', () => t('do { Write-Host $i; $i++ } while ($i -lt 5) ; Get-Date', ['Write-Host $i', 'Get-Date'])); + test('for loop', () => t('for ($i=0; $i -lt 5; $i++) { Write-Host $i } ; Get-Date', ['Write-Host $i', 'Get-Date'])); + test('foreach loop with range', () => t('foreach ($i in 1..5) { Write-Host $i } ; Get-Date', ['1..5', 'Write-Host $i', 'Get-Date'])); + test('break and continue', () => t('while ($true) { if ($condition) { break } ; Write-Host "running" } ; Get-Date', ['Write-Host "running"', 'Get-Date'])); + test('nested try-catch-finally', () => t('try { try { Get-Content "file" } catch { throw } } catch { Write-Error "outer" } finally { Write-Host "cleanup" }', ['Get-Content "file"', 'Write-Error "outer"', 'Write-Host "cleanup"'])); + test('parallel processing', () => t('1..10 | ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ } ; Get-Date', ['1..10 ', 'ForEach-Object -Parallel { Start-Sleep 1; Write-Host $_ }', 'Start-Sleep 1', 'Write-Host $_', 'Get-Date'])); + }); }); - }); - suite('all shell command parsing', () => { - async function t(commandLine: string, expectedCommands: string[]) { - for (const shell of [TreeSitterCommandParserLanguage.Bash, TreeSitterCommandParserLanguage.PowerShell]) { - const result = await parser.extractSubCommands(shell, commandLine); - deepStrictEqual(result, expectedCommands); + suite('all shells', () => { + async function t(commandLine: string, expectedCommands: string[]) { + for (const shell of [TreeSitterCommandParserLanguage.Bash, TreeSitterCommandParserLanguage.PowerShell]) { + const result = await parser.extractSubCommands(shell, commandLine); + deepStrictEqual(result, expectedCommands); + } } - } - suite('edge cases', () => { - test('empty strings', () => t('', [])); - test('whitespace-only strings', () => t(' \n\t ', [])); + suite('edge cases', () => { + test('empty strings', () => t('', [])); + test('whitespace-only strings', () => t(' \n\t ', [])); + }); }); }); }); From 53ae86994092aa23c35700d2193cf2c10e08214e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 30 Oct 2025 18:16:23 +0100 Subject: [PATCH 1880/4355] Add 'Save as Prompt File` commands and blue button (#274140) * save * update * update * handle untitled in ensureEditorOpen * fix New Untitled Prompt File * support instructions and agents, remove old `/save` * update * fix duplicated actions ids * remove commented line * remove '/save' --- .../contrib/chat/browser/chat.contribution.ts | 18 -- .../chatEditing/chatEditingServiceImpl.ts | 29 ++-- .../promptSyntax/newPromptFileActions.ts | 76 ++++----- .../browser/promptSyntax/promptFileActions.ts | 7 +- .../promptSyntax/saveAsPromptFileActions.ts | 104 ++++++++++++ .../promptSyntax/saveToPromptAction.ts | 155 ------------------ 6 files changed, 159 insertions(+), 230 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/promptSyntax/saveAsPromptFileActions.ts delete mode 100644 src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index de32969274f..ed5915844b5 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -10,7 +10,6 @@ import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js' import { Schemas } from '../../../../base/common/network.js'; import { isMacintosh } from '../../../../base/common/platform.js'; import { PolicyCategory } from '../../../../base/common/policy.js'; -import { assertDefined } from '../../../../base/common/types.js'; import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; @@ -124,7 +123,6 @@ import { LanguageModelToolsService, globalAutoApproveDescription } from './langu import './promptSyntax/promptCodingAgentActionContribution.js'; import './promptSyntax/promptToolsCodeLensProvider.js'; import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; -import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './promptSyntax/saveToPromptAction.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; @@ -898,22 +896,6 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { }, async () => { commandService.executeCommand(ACTION_ID_NEW_CHAT); })); - this._store.add(slashCommandService.registerSlashCommand({ - command: SAVE_TO_PROMPT_SLASH_COMMAND_NAME, - detail: nls.localize('save-chat-to-prompt-file', "Save chat to a prompt file"), - sortText: `z3_${SAVE_TO_PROMPT_SLASH_COMMAND_NAME}`, - executeImmediately: true, - silent: true, - locations: [ChatAgentLocation.Chat] - }, async () => { - const { lastFocusedWidget } = chatWidgetService; - assertDefined( - lastFocusedWidget, - 'No currently active chat widget found.', - ); - const options = { chat: lastFocusedWidget }; - return commandService.executeCommand(SAVE_TO_PROMPT_ACTION_ID, options,); - })); this._store.add(slashCommandService.registerSlashCommand({ command: 'help', detail: '', diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index b821c79fc6b..facffdc816c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -227,27 +227,24 @@ export class ChatEditingService extends Disposable implements IChatEditingServic const editorListener = Event.once(this._editorService.onDidActiveEditorChange)(() => { editorDidChange = true; }); + const editorOpenPromises = new ResourceMap>(); + const openChatEditedFiles = this._configurationService.getValue('accessibility.openChatEditedFiles'); - const editedFilesExist = new ResourceMap>(); const ensureEditorOpen = (partUri: URI) => { const uri = CellUri.parse(partUri)?.notebook ?? partUri; - if (editedFilesExist.has(uri)) { + if (editorOpenPromises.has(uri)) { return; } + editorOpenPromises.set(uri, (async () => { + if (this.notebookService.getNotebookTextModel(uri) || uri.scheme === Schemas.untitled || await this._fileService.exists(uri).catch(() => false)) { + const activeUri = this._editorService.activeEditorPane?.input.resource; + const inactive = editorDidChange + || this._editorService.activeEditorPane?.input instanceof ChatEditorInput && this._editorService.activeEditorPane.input.sessionId === session.chatSessionId + || Boolean(activeUri && session.entries.get().find(entry => isEqual(activeUri, entry.modifiedURI))); - const fileExists = this.notebookService.getNotebookTextModel(uri) ? Promise.resolve(true) : this._fileService.exists(uri); - editedFilesExist.set(uri, fileExists.then((e) => { - if (!e) { - return; - } - const activeUri = this._editorService.activeEditorPane?.input.resource; - const inactive = editorDidChange - || this._editorService.activeEditorPane?.input instanceof ChatEditorInput && this._editorService.activeEditorPane.input.sessionId === session.chatSessionId - || Boolean(activeUri && session.entries.get().find(entry => isEqual(activeUri, entry.modifiedURI))); - if (this._configurationService.getValue('accessibility.openChatEditedFiles')) { this._editorService.openEditor({ resource: uri, options: { inactive, preserveFocus: true, pinned: true } }); } - })); + })()); }; const onResponseComplete = () => { @@ -256,7 +253,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic } editsSeen.length = 0; - editedFilesExist.clear(); + editorOpenPromises.clear(); editorListener.dispose(); }; @@ -283,7 +280,9 @@ export class ChatEditingService extends Disposable implements IChatEditingServic continue; } - ensureEditorOpen(part.uri); + if (openChatEditedFiles) { + ensureEditorOpen(part.uri); + } // get new edits and start editing session let entry = editsSeen[i]; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts index 39cbb0f4a50..5c7b12b7700 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts @@ -20,7 +20,6 @@ import { getLanguageIdForPromptsType, PromptsType } from '../../common/promptSyn import { IUserDataSyncEnablementService, SyncResource } from '../../../../../platform/userDataSync/common/userDataSync.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { CONFIGURE_SYNC_COMMAND_ID } from '../../../../services/userDataSync/common/userDataSync.js'; -import { ISnippetsService } from '../../../snippets/browser/snippets.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; import { askForPromptFileName } from './pickers/askForPromptName.js'; @@ -81,7 +80,7 @@ class AbstractNewPromptFileAction extends Action2 { if (editor && editor.hasModel() && isEqual(editor.getModel().uri, promptUri)) { SnippetController2.get(editor)?.apply([{ range: editor.getModel().getFullModelRange(), - template: this.getDefaultContentSnippet(this.type, chatModeService), + template: getDefaultContentSnippet(this.type, chatModeService), }]); } @@ -137,41 +136,40 @@ class AbstractNewPromptFileAction extends Action2 { }, ); } +} - private getDefaultContentSnippet(promptType: PromptsType, chatModeService: IChatModeService): string { - const agents = chatModeService.getModes(); - const agentNames = agents.builtin.map(agent => agent.name).join(',') + (agents.custom.length ? (',' + agents.custom.map(agent => agent.name).join(',')) : ''); - switch (promptType) { - case PromptsType.prompt: - return [ - `---`, - `agent: \${1|${agentNames}|}`, - `---`, - `\${2:Define the task to achieve, including specific requirements, constraints, and success criteria.}`, - ].join('\n'); - case PromptsType.instructions: - return [ - `---`, - `applyTo: '\${1|**,**/*.ts|}'`, - `---`, - `\${2:Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.}`, - ].join('\n'); - case PromptsType.agent: - return [ - `---`, - `description: '\${1:Describe what this custom agent does and when to use it.}'`, - `tools: []`, - `---`, - `\${2:Define what this custom agent accomplishes for the user, when to use it, and the edges it won't cross. Specify its ideal inputs/outputs, the tools it may call, and how it reports progress or asks for help.}`, - ].join('\n'); - default: - throw new Error(`Unknown prompt type: ${promptType}`); - } +function getDefaultContentSnippet(promptType: PromptsType, chatModeService: IChatModeService): string { + const agents = chatModeService.getModes(); + const agentNames = agents.builtin.map(agent => agent.name).join(',') + (agents.custom.length ? (',' + agents.custom.map(agent => agent.name).join(',')) : ''); + switch (promptType) { + case PromptsType.prompt: + return [ + `---`, + `agent: \${1|${agentNames}|}`, + `---`, + `\${2:Define the task to achieve, including specific requirements, constraints, and success criteria.}`, + ].join('\n'); + case PromptsType.instructions: + return [ + `---`, + `applyTo: '\${1|**,**/*.ts|}'`, + `---`, + `\${2:Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.}`, + ].join('\n'); + case PromptsType.agent: + return [ + `---`, + `description: '\${1:Describe what this custom agent does and when to use it.}'`, + `tools: []`, + `---`, + `\${2:Define what this custom agent accomplishes for the user, when to use it, and the edges it won't cross. Specify its ideal inputs/outputs, the tools it may call, and how it reports progress or asks for help.}`, + ].join('\n'); + default: + throw new Error(`Unknown prompt type: ${promptType}`); } } - export const NEW_PROMPT_COMMAND_ID = 'workbench.command.new.prompt'; export const NEW_INSTRUCTIONS_COMMAND_ID = 'workbench.command.new.instructions'; export const NEW_AGENT_COMMAND_ID = 'workbench.command.new.agent'; @@ -210,7 +208,7 @@ class NewUntitledPromptFileAction extends Action2 { public override async run(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - const snippetService = accessor.get(ISnippetsService); + const chatModeService = accessor.get(IChatModeService); const languageId = getLanguageIdForPromptsType(PromptsType.prompt); @@ -221,16 +219,14 @@ class NewUntitledPromptFileAction extends Action2 { pinned: true } }); + const type = PromptsType.prompt; const editor = getCodeEditor(editorService.activeTextEditorControl); if (editor && editor.hasModel()) { - const snippets = await snippetService.getSnippets(languageId, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); - if (snippets.length > 0) { - SnippetController2.get(editor)?.apply([{ - range: editor.getModel().getFullModelRange(), - template: snippets[0].body - }]); - } + SnippetController2.get(editor)?.apply([{ + range: editor.getModel().getFullModelRange(), + template: getDefaultContentSnippet(type, chatModeService), + }]); } return input; diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts index bac1d683495..cbeef093b90 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileActions.ts @@ -6,8 +6,9 @@ import { registerAttachPromptActions } from './attachInstructionsAction.js'; import { registerAgentActions } from './chatModeActions.js'; import { registerRunPromptActions } from './runPromptAction.js'; -import { registerSaveToPromptActions } from './saveToPromptAction.js'; import { registerNewPromptFileActions } from './newPromptFileActions.js'; +import { registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { SaveAsAgentFileAction, SaveAsInstructionsFileAction, SaveAsPromptFileAction } from './saveAsPromptFileActions.js'; /** @@ -16,7 +17,9 @@ import { registerNewPromptFileActions } from './newPromptFileActions.js'; export function registerPromptActions(): void { registerRunPromptActions(); registerAttachPromptActions(); - registerSaveToPromptActions(); + registerAction2(SaveAsPromptFileAction); + registerAction2(SaveAsInstructionsFileAction); + registerAction2(SaveAsAgentFileAction); registerAgentActions(); registerNewPromptFileActions(); } diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/saveAsPromptFileActions.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveAsPromptFileActions.ts new file mode 100644 index 00000000000..8406276fb84 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveAsPromptFileActions.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from '../../../../../base/common/network.js'; +import { joinPath } from '../../../../../base/common/resources.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; +import { ILocalizedString, localize2 } from '../../../../../nls.js'; +import { ICommandActionTitle } from '../../../../../platform/action/common/action.js'; +import { Action2, IAction2Options, MenuId } from '../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; +import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; +import { chatEditingWidgetFileStateContextKey, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { getCleanPromptName } from '../../common/promptSyntax/config/promptFileLocations.js'; +import { AGENT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../../common/promptSyntax/promptTypes.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { askForPromptFileName } from './pickers/askForPromptName.js'; +import { askForPromptSourceFolder } from './pickers/askForPromptSourceFolder.js'; + +class BaseSaveAsPromptFileAction extends Action2 { + constructor(opts: Readonly, private readonly promptType: PromptsType) { + super(opts); + } + + async run(accessor: ServicesAccessor, configUri?: string): Promise { + const instantiationService = accessor.get(IInstantiationService); + const codeEditorService = accessor.get(ICodeEditorService); + const textFileService = accessor.get(ITextFileService); + const fileService = accessor.get(IFileService); + const activeCodeEditor = codeEditorService.getActiveCodeEditor(); + if (!activeCodeEditor) { + return; + } + const model = activeCodeEditor.getModel(); + if (!model) { + return; + } + const newFolder = await instantiationService.invokeFunction(askForPromptSourceFolder, this.promptType, undefined, true); + if (!newFolder) { + return; + } + const newName = await instantiationService.invokeFunction(askForPromptFileName, this.promptType, newFolder.uri, getCleanPromptName(model.uri)); + if (!newName) { + return; + } + const newFile = joinPath(newFolder.uri, newName); + if (model.uri.scheme === Schemas.untitled) { + await textFileService.saveAs(model.uri, newFile, { from: model.uri }); + } else { + await fileService.copy(model.uri, newFile); + } + await codeEditorService.openCodeEditor({ resource: newFile }, activeCodeEditor); + } +} + +function createOptions(id: string, title: ICommandActionTitle, description: ILocalizedString, languageId: string): Readonly { + return { + id: id, + title: title, + metadata: { + description: description, + }, + category: CHAT_CATEGORY, + f1: false, + menu: { + id: MenuId.EditorContent, + when: ContextKeyExpr.and( + ContextKeyExpr.equals(ResourceContextKey.Scheme.key, Schemas.untitled), + ContextKeyExpr.equals(ResourceContextKey.LangId.key, languageId), + ContextKeyExpr.notEquals(chatEditingWidgetFileStateContextKey.key, ModifiedFileEntryState.Modified), + ) + } + }; +} + +export const SAVE_AS_PROMPT_FILE_ACTION_ID = 'workbench.action.chat.save-as-prompt'; + +export class SaveAsPromptFileAction extends BaseSaveAsPromptFileAction { + constructor() { + super(createOptions(SAVE_AS_PROMPT_FILE_ACTION_ID, localize2('promptfile.savePromptFile', "Save As Prompt File"), localize2('promptfile.savePromptFile.description', "Save as prompt file"), PROMPT_LANGUAGE_ID), PromptsType.prompt); + } +} + +export const SAVE_AS_AGENT_FILE_ACTION_ID = 'workbench.action.chat.save-as-agent'; + +export class SaveAsAgentFileAction extends BaseSaveAsPromptFileAction { + constructor() { + super(createOptions(SAVE_AS_AGENT_FILE_ACTION_ID, localize2('promptfile.saveAgentFile', "Save As Agent File"), localize2('promptfile.saveAgentFile.description', "Save as agent file"), AGENT_LANGUAGE_ID), PromptsType.agent); + } +} + +export const SAVE_AS_INSTRUCTIONS_FILE_ACTION_ID = 'workbench.action.chat.save-as-instructions'; + +export class SaveAsInstructionsFileAction extends BaseSaveAsPromptFileAction { + constructor() { + super(createOptions(SAVE_AS_INSTRUCTIONS_FILE_ACTION_ID, localize2('promptfile.saveInstructionsFile', "Save As Instructions File"), localize2('promptfile.saveInstructionsFile.description', "Save as instructions file"), INSTRUCTIONS_LANGUAGE_ID), PromptsType.instructions); + } +} + diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts deleted file mode 100644 index 500ebd4a9aa..00000000000 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/saveToPromptAction.ts +++ /dev/null @@ -1,155 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize2 } from '../../../../../nls.js'; -import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; - -import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; -import { ILogService } from '../../../../../platform/log/common/log.js'; -import { IEditorService } from '../../../../services/editor/common/editorService.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { chatSubcommandLeader, IParsedChatRequest } from '../../common/chatParserTypes.js'; -import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js'; -import { CHAT_CATEGORY } from '../actions/chatActions.js'; -import { IChatWidget } from '../chat.js'; -import { ChatModeKind } from '../../common/constants.js'; -import { PromptFileRewriter } from './promptFileRewriter.js'; -import { ILanguageModelChatMetadata } from '../../common/languageModels.js'; -import { URI } from '../../../../../base/common/uri.js'; -import { Schemas } from '../../../../../base/common/network.js'; - -/** - * Action ID for the `Save Prompt` action. - */ -export const SAVE_TO_PROMPT_ACTION_ID = 'workbench.action.chat.save-to-prompt'; - -/** - * Name of the in-chat slash command associated with this action. - */ -export const SAVE_TO_PROMPT_SLASH_COMMAND_NAME = 'save'; - -/** - * Options for the {@link SaveToPromptAction} action. - */ -interface ISaveToPromptActionOptions { - /** - * Chat widget reference to save session of. - */ - readonly chat: IChatWidget; -} - -/** - * Class that defines the `Save Prompt` action. - */ -class SaveToPromptAction extends Action2 { - constructor() { - super({ - id: SAVE_TO_PROMPT_ACTION_ID, - title: localize2( - 'workbench.actions.save-to-prompt.label', - "Save chat session to a prompt file", - ), - f1: false, - precondition: ChatContextKeys.enabled, - category: CHAT_CATEGORY, - }); - } - - public async run( - accessor: ServicesAccessor, - options: ISaveToPromptActionOptions, - ): Promise { - const logService = accessor.get(ILogService); - const editorService = accessor.get(IEditorService); - const rewriter = accessor.get(IInstantiationService).createInstance(PromptFileRewriter); - - const logPrefix = 'save to prompt'; - const chatWidget = options.chat; - const mode = chatWidget.input.currentModeObs.get(); - const model = chatWidget.input.selectedLanguageModel; - - const output = []; - output.push('---'); - output.push(`description: New prompt created from chat session`); - output.push(`mode: ${mode.kind}`); - if (mode.kind === ChatModeKind.Agent) { - const toolAndToolsetMap = chatWidget.input.selectedToolsModel.entriesMap.get(); - output.push(`tools: ${rewriter.getNewValueString(toolAndToolsetMap)}`); - } - if (model) { - output.push(`model: ${ILanguageModelChatMetadata.asQualifiedName(model.metadata)}`); - } - output.push('---'); - - const viewModel = chatWidget.viewModel; - if (viewModel) { - - for (const request of viewModel.model.getRequests()) { - const { message, response: responseModel } = request; - - if (isSaveToPromptSlashCommand(message)) { - continue; - } - - if (responseModel === undefined) { - logService.warn(`[${logPrefix}]: skipping request '${request.id}' with no response`); - continue; - } - - const { response } = responseModel; - - output.push(``); - output.push(request.message.text); - output.push(``); - output.push(); - output.push(``); - output.push(response.getMarkdown()); - output.push(``); - output.push(); - } - const promptText = output.join('\n'); - - const untitledPath = 'new.prompt.md'; - const untitledResource = URI.from({ scheme: Schemas.untitled, path: untitledPath }); - - const editor = await editorService.openEditor({ - resource: untitledResource, - contents: promptText, - languageId: PROMPT_LANGUAGE_ID, - }); - - editor?.focus(); - } - } -} - -/** - * Check if provided message belongs to the `save to prompt` slash - * command itself that was run in the chat to invoke this action. - */ -function isSaveToPromptSlashCommand(message: IParsedChatRequest): boolean { - const { parts } = message; - if (parts.length < 1) { - return false; - } - - const firstPart = parts[0]; - if (firstPart.kind !== 'slash') { - return false; - } - - if (firstPart.text !== `${chatSubcommandLeader}${SAVE_TO_PROMPT_SLASH_COMMAND_NAME}`) { - return false; - } - - return true; -} - -/** - * Helper to register all the `Save Prompt` actions. - */ -export function registerSaveToPromptActions(): void { - registerAction2(SaveToPromptAction); -} From 74671b9f5130a8b2f2473bf0a0ecc07848fb3241 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:27:21 +0000 Subject: [PATCH 1881/4355] Fix suggest widget positioning in comment editors within diff views (#274093) * Initial plan * Fix suggest widget positioning by changing fixedOverflowWidgets to false Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../workbench/contrib/comments/browser/simpleCommentEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 7be817e3592..ca11d3e435b 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -137,7 +137,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { lineDecorationsWidth: 0, scrollBeyondLastLine: false, renderLineHighlight: 'none', - fixedOverflowWidgets: true, + fixedOverflowWidgets: false, acceptSuggestionOnEnter: 'smart', minimap: { enabled: false From 8ea8dc2fc6b06388b0d539b91f4cbdb1c4ba22ed Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:49:28 -0700 Subject: [PATCH 1882/4355] Add unit tests for getFileWrites and extract && --- .../commandLineFileWriteAnalyzer.ts | 8 +- .../browser/treeSitterCommandParser.ts | 9 +- .../treeSitterCommandParser.test.ts | 170 ++++++++++++++++++ 3 files changed, 178 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts index bddbdcd9f68..b835f7bf9a6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts @@ -30,8 +30,8 @@ export class CommandLineFileWriteAnalyzer extends Disposable implements ICommand private async _getFileWrites(options: ICommandLineAnalyzerOptions): Promise { let fileWrites: URI[] | string[] = []; - const fileWriteCaptures = await this._treeSitterCommandParser.getFileWrites(options.treeSitterLanguage, options.commandLine); - if (fileWriteCaptures.length) { + const capturedFileWrites = await this._treeSitterCommandParser.getFileWrites(options.treeSitterLanguage, options.commandLine); + if (capturedFileWrites.length) { let cwd = await options.instance?.getCwdResource(); if (!cwd) { const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); @@ -39,10 +39,10 @@ export class CommandLineFileWriteAnalyzer extends Disposable implements ICommand cwd = workspaceFolder?.uri; } if (cwd) { - fileWrites = fileWriteCaptures.map(e => URI.joinPath(cwd, e.node.text)); + fileWrites = capturedFileWrites.map(e => URI.joinPath(cwd, e)); } else { this._log('Cwd could not be detected'); - fileWrites = fileWriteCaptures.map(e => e.node.text); + fileWrites = capturedFileWrites; } } this._log('File writes detected', fileWrites.map(e => e.toString())); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index 97bb80ec8f0..b5c3f2043fd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -39,25 +39,24 @@ export class TreeSitterCommandParser { return captures; } - async getFileWrites(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { + async getFileWrites(languageId: TreeSitterCommandParserLanguage, commandLine: string): Promise { let query: string; switch (languageId) { case TreeSitterCommandParserLanguage.Bash: query = [ '(file_redirect', - ' (word) @file)', + ' destination: [(word) (string (string_content)) (raw_string)] @file)', ].join('\n'); break; case TreeSitterCommandParserLanguage.PowerShell: query = [ '(redirection', - ' (redirected_file_name', - ' (generic_token) @file))', + ' (redirected_file_name) @file)', ].join('\n'); break; } const captures = await this._queryTree(languageId, commandLine, query); - return captures; + return captures.map(e => e.node.text.trim()); } private async _queryTree(languageId: TreeSitterCommandParserLanguage, commandLine: string, querySource: string): Promise { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index 6ad0ad313c6..e9d6f4e3b59 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -197,4 +197,174 @@ import { arch } from '../../../../../../base/common/process.js'; }); }); }); + + suite('extractPwshDoubleAmpersandChainOperators', () => { + async function t(commandLine: string, expectedMatches: string[]) { + const result = await parser.extractPwshDoubleAmpersandChainOperators(commandLine); + const actualMatches = result.map(capture => capture.node.text); + deepStrictEqual(actualMatches, expectedMatches); + } + + test('simple command with &&', () => t('Get-Date && Get-Location', ['&&'])); + test('multiple && operators', () => t('echo first && echo second && echo third', ['&&', '&&'])); + test('mixed operators - && and ;', () => t('echo hello && echo world ; echo done', ['&&'])); + test('no && operators', () => t('Get-Date ; Get-Location', [])); + test('&& in string literal should not match', () => t('Write-Host "test && test"', [])); + test('&& in single quotes should not match', () => t('Write-Host \'test && test\'', [])); + test('&& with complex commands', () => t('Get-ChildItem -Path C:\\ && Set-Location C:\\Users', ['&&'])); + test('&& with parameters', () => t('Get-Process -Name notepad && Stop-Process -Name notepad', ['&&'])); + test('&& with pipeline inside', () => t('Get-Process | Where-Object {$_.Name -eq "notepad"} && Write-Host "Found"', ['&&'])); + // TODO: A lot of these tests are skipped until proper parsing of && is supported in https://github.com/airbus-cert/tree-sitter-powershell/issues/27 + test.skip('nested && in script blocks', () => t('if ($true) { echo hello && echo world } && echo done', ['&&', '&&'])); + test.skip('&& with method calls', () => t('"hello".ToUpper() && "world".ToLower()', ['&&'])); + test.skip('&& with array operations', () => t('@(1,2,3) | ForEach-Object { $_ } && Write-Host "done"', ['&&'])); + test.skip('&& with hashtable', () => t('@{key="value"} && Write-Host "created"', ['&&'])); + test.skip('&& with type casting', () => t('[int]"123" && [string]456', ['&&'])); + test.skip('&& with comparison operators', () => t('5 -gt 3 && "hello" -like "h*"', ['&&'])); + test.skip('&& with variable assignment', () => t('$var = "test" && Write-Host $var', ['&&'])); + test.skip('&& with expandable strings', () => t('$name="World" && "Hello $name"', ['&&'])); + test('&& with subexpressions', () => t('Write-Host $(Get-Date) && Get-Location', ['&&'])); + test('&& with here-strings', () => t('Write-Host @"\nhello\nworld\n"@ && Get-Date', ['&&'])); + test('&& with splatting', () => t('$params = @{Path="C:\\"}; Get-ChildItem @params && Write-Host "done"', ['&&'])); + + suite('complex scenarios', () => { + test('multiple && with different command types', () => t('Get-Service && Start-Service spooler && Get-Process', ['&&', '&&'])); + test('&& with error handling', () => t('try { Get-Content "file.txt" && Write-Host "success" } catch { Write-Error "failed" }', ['&&'])); + test('&& inside foreach', () => t('ForEach-Object { Write-Host $_.Name && Write-Host $_.Length }', ['&&'])); + test('&& with conditional logic', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" && Write-Host "read" }', ['&&'])); + test.skip('&& with switch statement', () => t('switch ($var) { 1 { "one" && "first" } 2 { "two" && "second" } }', ['&&', '&&'])); + test('&& in do-while', () => t('do { Write-Host $i && $i++ } while ($i -lt 5)', ['&&'])); + test('&& in for loop', () => t('for ($i=0; $i -lt 5; $i++) { Write-Host $i && Start-Sleep 1 }', ['&&'])); + test('&& with parallel processing', () => t('1..10 | ForEach-Object -Parallel { Write-Host $_ && Start-Sleep 1 }', ['&&'])); + }); + + suite('edge cases', () => { + test('empty string', () => t('', [])); + test('whitespace only', () => t(' \n\t ', [])); + test('single &', () => t('Get-Date & Get-Location', [])); + test.skip('triple &&&', () => t('echo hello &&& echo world', ['&&'])); + test.skip('&& at beginning', () => t('&& echo hello', ['&&'])); + test('&& at end', () => t('echo hello &&', ['&&'])); + test('spaced && operators', () => t('echo hello & & echo world', [])); + test('&& with unicode', () => t('Write-Host "测试" && Write-Host "🚀"', ['&&'])); + test('very long command with &&', () => t('Write-Host "' + 'a'.repeat(1000) + '" && Get-Date', ['&&'])); + test('deeply nested with &&', () => t('if ($true) { if ($true) { if ($true) { echo nested && echo deep } } }', ['&&'])); + test('&& with escaped characters', () => t('Write-Host "hello`"world" && Get-Date', ['&&'])); + test.skip('&& with backticks', () => t('Write-Host `hello && Get-Date', ['&&'])); + test.skip('malformed syntax with &&', () => t('echo "unclosed && Get-Date', ['&&'])); + }); + + suite('real-world scenarios', () => { + test('git workflow', () => t('git add . && git commit -m "message" && git push', ['&&', '&&'])); + test.skip('build and test', () => t('dotnet build && dotnet test && dotnet publish', ['&&', '&&'])); + test('file operations', () => t('New-Item -Type File "test.txt" && Add-Content "test.txt" "hello" && Get-Content "test.txt"', ['&&', '&&'])); + test('service management', () => t('Stop-Service spooler && Set-Service spooler -StartupType Manual && Start-Service spooler', ['&&', '&&'])); + test('registry operations', () => t('New-Item -Path "HKCU:\\Software\\Test" && Set-ItemProperty -Path "HKCU:\\Software\\Test" -Name "Value" -Value "Data"', ['&&'])); + test('module import and usage', () => t('Import-Module ActiveDirectory && Get-ADUser -Filter *', ['&&'])); + test('remote operations', () => t('Enter-PSSession -ComputerName server && Get-Process && Exit-PSSession', ['&&', '&&'])); + test('scheduled task', () => t('Register-ScheduledTask -TaskName "MyTask" -Action (New-ScheduledTaskAction -Execute "powershell.exe") && Start-ScheduledTask "MyTask"', ['&&'])); + }); + }); + + suite('getFileWrites', () => { + suite('bash', () => { + async function t(commandLine: string, expectedFiles: string[]) { + const actualFiles = await parser.getFileWrites(TreeSitterCommandParserLanguage.Bash, commandLine); + deepStrictEqual(actualFiles, expectedFiles); + } + + test('simple output redirection', () => t('echo hello > file.txt', ['file.txt'])); + test('append redirection', () => t('echo hello >> file.txt', ['file.txt'])); + test('multiple redirections', () => t('echo hello > file1.txt && echo world > file2.txt', ['file1.txt', 'file2.txt'])); + test('error redirection', () => t('command 2> error.log', ['error.log'])); + test('combined stdout and stderr', () => t('command > output.txt 2>&1', ['output.txt'])); + test('here document', () => t('cat > file.txt << EOF\nhello\nworld\nEOF', ['file.txt'])); + test('quoted filenames', () => t('echo hello > "file with spaces.txt"', ['"file with spaces.txt"'])); + test('single quoted filenames', () => t('echo hello > \'file.txt\'', ['\'file.txt\''])); + test.skip('variable in filename', () => t('echo hello > $HOME/file.txt', ['$HOME/file.txt'])); + test.skip('command substitution in filename', () => t('echo hello > $(date +%Y%m%d).log', ['$(date +%Y%m%d).log'])); + test('tilde expansion in filename', () => t('echo hello > ~/file.txt', ['~/file.txt'])); + test('absolute path', () => t('echo hello > /tmp/file.txt', ['/tmp/file.txt'])); + test('relative path', () => t('echo hello > ./output/file.txt', ['./output/file.txt'])); + test('file descriptor redirection', () => t('command 3> file.txt', ['file.txt'])); + test('redirection with numeric file descriptor', () => t('command 1> stdout.txt 2> stderr.txt', ['stdout.txt', 'stderr.txt'])); + test('append with error redirection', () => t('command >> output.log 2>> error.log', ['output.log', 'error.log'])); + + suite('complex scenarios', () => { + test('multiple commands with redirections', () => t('echo first > file1.txt; echo second > file2.txt; echo third > file3.txt', ['file1.txt', 'file2.txt', 'file3.txt'])); + test('pipeline with redirection', () => t('cat input.txt | grep pattern > output.txt', ['output.txt'])); + test('redirection in subshell', () => t('(echo hello; echo world) > combined.txt', ['combined.txt'])); + test('redirection with background job', () => t('long_command > output.txt &', ['output.txt'])); + test('conditional redirection', () => t('test -f input.txt && cat input.txt > output.txt || echo "not found" > error.txt', ['output.txt', 'error.txt'])); + test('loop with redirection', () => t('for file in *.txt; do cat "$file" >> combined.txt; done', ['combined.txt'])); + test.skip('function with redirection', () => t('function backup() { cp "$1" > backup_"$1"; }', ['backup_$1'])); + }); + + suite('edge cases', () => { + test('no redirections', () => t('echo hello', [])); + test('input redirection only', () => t('sort < input.txt', ['input.txt'])); + test('pipe without redirection', () => t('echo hello | grep hello', [])); + test('redirection to /dev/null', () => t('command > /dev/null', ['/dev/null'])); + test('redirection to device', () => t('echo hello > /dev/tty', ['/dev/tty'])); + test('special characters in filename', () => t('echo hello > file-with_special.chars123.txt', ['file-with_special.chars123.txt'])); + test('unicode filename', () => t('echo hello > 测试文件.txt', ['测试文件.txt'])); + test('very long filename', () => t('echo hello > ' + 'a'.repeat(100) + '.txt', [Array(100).fill('a').join('') + '.txt'])); + }); + }); + + suite('pwsh', () => { + async function t(commandLine: string, expectedFiles: string[]) { + const actualFiles = await parser.getFileWrites(TreeSitterCommandParserLanguage.PowerShell, commandLine); + deepStrictEqual(actualFiles, expectedFiles); + } + + test('simple output redirection', () => t('Write-Host "hello" > file.txt', ['file.txt'])); + test('append redirection', () => t('Write-Host "hello" >> file.txt', ['file.txt'])); + test('multiple redirections', () => t('Write-Host "hello" > file1.txt ; Write-Host "world" > file2.txt', ['file1.txt', 'file2.txt'])); + test('error redirection', () => t('Get-Content missing.txt 2> error.log', ['error.log'])); + test('warning redirection', () => t('Write-Warning "test" 3> warning.log', ['warning.log'])); + test('verbose redirection', () => t('Write-Verbose "test" 4> verbose.log', ['verbose.log'])); + test('debug redirection', () => t('Write-Debug "test" 5> debug.log', ['debug.log'])); + test('information redirection', () => t('Write-Information "test" 6> info.log', ['info.log'])); + test('all streams redirection', () => t('Get-Process *> all.log', ['all.log'])); + test('quoted filenames', () => t('Write-Host "hello" > "file with spaces.txt"', ['"file with spaces.txt"'])); + test('single quoted filenames', () => t('Write-Host "hello" > \'file.txt\'', ['\'file.txt\''])); + test('variable in filename', () => t('Write-Host "hello" > $env:TEMP\\file.txt', ['$env:TEMP\\file.txt'])); + test('subexpression in filename', () => t('Write-Host "hello" > $(Get-Date -Format "yyyyMMdd").log', ['$(Get-Date -Format "yyyyMMdd").log'])); + test('Windows path', () => t('Write-Host "hello" > C:\\temp\\file.txt', ['C:\\temp\\file.txt'])); + test('UNC path', () => t('Write-Host "hello" > \\\\server\\share\\file.txt', ['\\\\server\\share\\file.txt'])); + test('relative path', () => t('Write-Host "hello" > .\\output\\file.txt', ['.\\output\\file.txt'])); + + suite('complex scenarios', () => { + test('pipeline with redirection', () => t('Get-Process | Where-Object {$_.CPU -gt 100} > processes.txt', ['processes.txt'])); + test('multiple streams to different files', () => t('Get-Content missing.txt > output.txt 2> error.txt 3> warning.txt', ['output.txt', 'error.txt', 'warning.txt'])); + test('redirection in script block', () => t('ForEach-Object { Write-Host $_.Name > names.txt }', ['names.txt'])); + test('conditional redirection', () => t('if (Test-Path "file.txt") { Get-Content "file.txt" > output.txt } else { Write-Host "not found" > error.txt }', ['output.txt', 'error.txt'])); + test('try-catch with redirection', () => t('try { Get-Content "file.txt" > output.txt } catch { $_.Exception.Message > error.txt }', ['output.txt', 'error.txt'])); + test('foreach loop with redirection', () => t('foreach ($file in Get-ChildItem) { $file.Name >> filelist.txt }', ['filelist.txt'])); + test('switch with redirection', () => t('switch ($var) { 1 { "one" > output1.txt } 2 { "two" > output2.txt } }', ['output1.txt', 'output2.txt'])); + }); + + suite('edge cases', () => { + test('no redirections', () => t('Write-Host "hello"', [])); + test('redirection to null', () => t('Write-Host "hello" > $null', ['$null'])); + test('redirection to console', () => t('Write-Host "hello" > CON', ['CON'])); + test('special characters in filename', () => t('Write-Host "hello" > file-with_special.chars123.txt', ['file-with_special.chars123.txt'])); + test('unicode filename', () => t('Write-Host "hello" > 测试文件.txt', ['测试文件.txt'])); + test('very long filename', () => t('Write-Host "hello" > ' + 'a'.repeat(100) + '.txt', [Array(100).fill('a').join('') + '.txt'])); + test('redirection operator in string', () => t('Write-Host "test > redirect" > file.txt', ['file.txt'])); + test('multiple redirection operators', () => t('Write-Host "hello" >> file.txt > otherfile.txt', ['file.txt', 'otherfile.txt'])); + }); + + suite('real-world scenarios', () => { + test('logging script output', () => t('Get-EventLog -LogName System -Newest 100 > system_events.log', ['system_events.log'])); + test('error logging', () => t('Start-Process -FilePath "nonexistent.exe" 2> process_errors.log', ['process_errors.log'])); + test('backup script with logging', () => t('Copy-Item -Path "source/*" -Destination "backup/" -Recurse > backup.log 2> backup_errors.log', ['backup.log', 'backup_errors.log'])); + test('system information export', () => t('Get-ComputerInfo | Out-String > system_info.txt', ['system_info.txt'])); + test('service status report', () => t('Get-Service | Where-Object {$_.Status -eq "Running"} | Select-Object Name, Status > running_services.csv', ['running_services.csv'])); + test('registry export', () => t('Get-ItemProperty -Path "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion" > registry_info.txt', ['registry_info.txt'])); + test('process monitoring', () => t('while ($true) { Get-Process | Measure-Object WorkingSet -Sum >> memory_usage.log; Start-Sleep 60 }', ['memory_usage.log'])); + }); + }); + }); }); From 36c5f8705fd5750268c63476ed11006cbbade52c Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:59:48 -0700 Subject: [PATCH 1883/4355] Add `IChatDetail.sessionResource` --- .../chat/browser/actions/chatActions.ts | 24 ++++++++--------- .../browser/actions/chatSessionActions.ts | 6 ++--- .../agentSessions/agentSessionViewModel.ts | 3 +-- .../chatSessions/view/sessionsTreeRenderer.ts | 3 +-- .../contrib/chat/browser/chatWidget.ts | 3 +-- .../contrib/chat/common/chatService.ts | 6 +++-- .../contrib/chat/common/chatServiceImpl.ts | 26 +++++++++++++++---- .../chat/test/common/mockChatService.ts | 4 +-- 8 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index db827b245f7..dc8de8e0b74 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -17,7 +17,7 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, markAsSingleton } from '../../../../../base/common/lifecycle.js'; import { MarshalledId } from '../../../../../base/common/marshallingIds.js'; import { language } from '../../../../../base/common/platform.js'; -import { basename } from '../../../../../base/common/resources.js'; +import { basename, isEqual } from '../../../../../base/common/resources.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { hasKey } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -67,7 +67,6 @@ import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; @@ -611,17 +610,17 @@ export function registerChatActions() { store.add(picker.onDidTriggerItemButton(async context => { if (context.button === openInEditorButton) { editorService.openEditor({ - resource: LocalChatSessionUri.forSession(context.item.chat.sessionId), + resource: context.item.chat.sessionResource, options: { pinned: true } }, ACTIVE_GROUP); picker.hide(); } else if (context.button === deleteButton) { - chatService.removeHistoryEntry(context.item.chat.sessionId); + chatService.removeHistoryEntry(context.item.chat.sessionResource); picker.items = await getPicks(); } else if (context.button === renameButton) { const title = await quickInputService.input({ title: localize('newChatTitle', "New chat title"), value: context.item.chat.title }); if (title) { - chatService.setChatSessionTitle(context.item.chat.sessionId, title); + chatService.setChatSessionTitle(context.item.chat.sessionResource, title); } // The quick input hides the picker, it gets disposed, so we kick it off from scratch @@ -631,8 +630,7 @@ export function registerChatActions() { store.add(picker.onDidAccept(async () => { try { const item = picker.selectedItems[0]; - const sessionId = item.chat.sessionId; - await view.loadSession(sessionId); + await view.loadSession(item.chat.sessionResource); } finally { picker.hide(); } @@ -749,7 +747,6 @@ export function registerChatActions() { const providerNSessions = await chatSessionsService.getAllChatSessionItems(cancellationToken.token); for (const { chatSessionType, items } of providerNSessions) { for (const session of items) { - const ckey = contextKeyService.createKey('chatSessionType', chatSessionType); const actions = menuService.getMenuActions(MenuId.ChatSessionsMenu, contextKeyService); const { primary } = getContextMenuActions(actions, 'inline'); @@ -768,6 +765,7 @@ export function registerChatActions() { session: { providerType: chatSessionType, session: session }, chat: { sessionId: session.id, + sessionResource: session.resource, title: session.label, isActive: false, lastMessageDate: 0, @@ -777,7 +775,7 @@ export function registerChatActions() { }; // Check if this agent already exists (update existing or add new) - const existingIndex = agentPicks.findIndex(pick => pick.chat.sessionId === session.id); + const existingIndex = agentPicks.findIndex(pick => isEqual(pick.chat.sessionResource, session.resource)); if (existingIndex >= 0) { agentPicks[existingIndex] = agentPick; } else { @@ -871,12 +869,12 @@ export function registerChatActions() { if (context.button === openInEditorButton) { const options: IChatEditorOptions = { pinned: true }; editorService.openEditor({ - resource: LocalChatSessionUri.forSession(context.item.chat.sessionId), + resource: context.item.chat.sessionResource, options, }, ACTIVE_GROUP); picker.hide(); } else if (context.button === deleteButton) { - chatService.removeHistoryEntry(context.item.chat.sessionId); + chatService.removeHistoryEntry(context.item.chat.sessionResource); // Refresh picker items after deletion const { fast, slow } = await getPicks(showAllChats, showAllAgents); picker.items = fast; @@ -901,7 +899,7 @@ export function registerChatActions() { } else if (context.button === renameButton) { const title = await quickInputService.input({ title: localize('newChatTitle', "New chat title"), value: context.item.chat.title }); if (title) { - chatService.setChatSessionTitle(context.item.chat.sessionId, title); + chatService.setChatSessionTitle(context.item.chat.sessionResource, title); } // The quick input hides the picker, it gets disposed, so we kick it off from scratch @@ -978,7 +976,7 @@ export function registerChatActions() { await this.showChatSessionInEditor(item.session.providerType, item.session.session, editorService); } } else if (isChatPickerItem(item)) { - await view.loadSession(item.chat.sessionId); + await view.loadSession(item.chat.sessionResource); } } finally { picker.hide(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index de6eec0dbf9..2e690df8315 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -64,7 +64,6 @@ export class RenameChatSessionAction extends Action2 { } // Handle marshalled context from menu actions - const sessionId = context.session.id.replace('history-', ''); const label = context.session.label; const chatSessionsService = accessor.get(IChatSessionsService); const logService = accessor.get(ILogService); @@ -89,7 +88,7 @@ export class RenameChatSessionAction extends Action2 { if (success && value && value.trim() !== label) { try { const newTitle = value.trim(); - chatService.setChatSessionTitle(sessionId, newTitle); + chatService.setChatSessionTitle(context.session.resource, newTitle); // Notify the local sessions provider that items have changed chatSessionsService.notifySessionItemsChanged(localChatSessionType); } catch (error) { @@ -130,7 +129,6 @@ export class DeleteChatSessionAction extends Action2 { } // Handle marshalled context from menu actions - const sessionId = context.session.id; const chatService = accessor.get(IChatService); const dialogService = accessor.get(IDialogService); const logService = accessor.get(ILogService); @@ -146,7 +144,7 @@ export class DeleteChatSessionAction extends Action2 { }); if (result.confirmed) { - await chatService.removeHistoryEntry(sessionId); + await chatService.removeHistoryEntry(context.session.resource); // Notify the local sessions provider that items have changed chatSessionsService.notifySessionItemsChanged(localChatSessionType); } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts index bd6837ff8bb..5aa0e0161f2 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionViewModel.ts @@ -13,7 +13,6 @@ import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IChatService } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { LocalChatSessionUri } from '../../common/chatUri.js'; //#region Interfaces, Types @@ -192,7 +191,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions // - can we support all properties including `startTime` properly for (const history of await this.chatService.getLocalSessionHistory()) { newSessions.push({ - resource: LocalChatSessionUri.forSession(history.sessionId), + resource: history.sessionResource, label: history.title, provider: provider, timing: { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts index 15b46a879d5..f902173bb3b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts @@ -42,7 +42,6 @@ import { IWorkbenchLayoutService, Position } from '../../../../../services/layou import { getLocalHistoryDateFormatter } from '../../../../localHistory/browser/localHistory.js'; import { IChatService } from '../../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; -import { LocalChatSessionUri } from '../../../common/chatUri.js'; import { ChatConfiguration } from '../../../common/constants.js'; import { IChatWidgetService } from '../../chat.js'; import { allowedChatMarkdownHtmlTags } from '../../chatContentMarkdownRenderer.js'; @@ -582,7 +581,7 @@ export class SessionsDataSource implements IAsyncDataSource ({ id: historyDetail.sessionId, - resource: LocalChatSessionUri.forSession(historyDetail.sessionId), + resource: historyDetail.sessionResource, label: historyDetail.title, iconPath: Codicon.chatSparkle, provider: this.provider, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8829f005b44..2db00c9ee81 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -94,7 +94,6 @@ import { ChatEditorOptions } from './chatOptions.js'; import { ChatViewPane } from './chatViewPane.js'; import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; -import { LocalChatSessionUri } from '../common/chatUri.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; const $ = dom.$; @@ -1242,7 +1241,7 @@ export class ChatWidget extends Disposable implements IChatWidget { .sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)) .slice(0, 3) .map((item): IChatHistoryListItem => ({ - sessionResource: LocalChatSessionUri.forSession(item.sessionId), + sessionResource: item.sessionResource, title: item.title, lastMessageDate: typeof item.lastMessageDate === 'number' ? item.lastMessageDate : Date.now(), isActive: item.isActive diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index a4c25b6b00c..c5cecf4ad78 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -811,7 +811,9 @@ export interface IChatCompleteResponse { } export interface IChatDetail { + /** @deprecated Use {@link sessionResource} instead */ sessionId: string; + sessionResource: URI; title: string; lastMessageDate: number; isActive: boolean; @@ -923,10 +925,10 @@ export interface IChatService { cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): Promise; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void; + setChatSessionTitle(sessionResource: URI, title: string): void; getLocalSessionHistory(): Promise; - setChatSessionTitle(sessionId: string, title: string): void; clearAllHistoryEntries(): Promise; - removeHistoryEntry(sessionId: string): Promise; + removeHistoryEntry(sessionResource: URI): Promise; getChatStorageFolder(): URI; logChatIndex(): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 88d04a229d5..a93f221237a 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -190,7 +190,8 @@ export class ChatService extends Disposable implements IChatService { } } - async setChatSessionTitle(sessionId: string, title: string): Promise { + async setChatSessionTitle(sessionResource: URI, title: string): Promise { + const sessionId = this.toLocalSessionId(sessionResource); const model = this._sessionModels.get(sessionId); if (model) { model.setCustomTitle(title); @@ -301,14 +302,15 @@ export class ChatService extends Disposable implements IChatService { async getLocalSessionHistory(): Promise { const liveSessionItems = Array.from(this._sessionModels.values()) .filter(session => !session.isImported && !session.inputType) - .map(session => { + .map((session): IChatDetail => { const title = session.title || localize('newChat', "New Chat"); return { sessionId: session.sessionId, + sessionResource: LocalChatSessionUri.forSession(session.sessionId), title, lastMessageDate: session.lastMessageDate, isActive: true, - } satisfies IChatDetail; + }; }); const index = await this._chatSessionStore.getIndex(); @@ -316,13 +318,14 @@ export class ChatService extends Disposable implements IChatService { .filter(entry => !this._sessionModels.has(entry.sessionId) && !entry.isImported && !entry.isEmpty) .map((entry): IChatDetail => ({ ...entry, + sessionResource: LocalChatSessionUri.forSession(entry.sessionId), isActive: this._sessionModels.has(entry.sessionId), })); return [...liveSessionItems, ...entries]; } - async removeHistoryEntry(sessionId: string): Promise { - await this._chatSessionStore.deleteSession(sessionId); + async removeHistoryEntry(sessionResource: URI): Promise { + await this._chatSessionStore.deleteSession(this.toLocalSessionId(sessionResource)); } async clearAllHistoryEntries(): Promise { @@ -1171,4 +1174,17 @@ export class ChatService extends Disposable implements IChatService { logChatIndex(): void { this._chatSessionStore.logIndex(); } + + private toLocalSessionId(sessionResource: URI) { + const parsed = LocalChatSessionUri.parse(sessionResource); + if (!parsed) { + throw new Error(`Invalid local chat session resource: ${sessionResource.toString()}`); + } + if (parsed.chatSessionType !== 'local') { + throw new Error(`Can only delete local chat sessions, got: ${parsed.chatSessionType}`); + } + + const sessionId = parsed.sessionId; + return sessionId; + } } diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index f356f87a407..6ae9cb3a5b6 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -86,7 +86,7 @@ export class MockChatService implements IChatService { async clearAllHistoryEntries() { throw new Error('Method not implemented.'); } - async removeHistoryEntry(sessionId: string) { + async removeHistoryEntry(resource: URI) { throw new Error('Method not implemented.'); } @@ -100,7 +100,7 @@ export class MockChatService implements IChatService { throw new Error('Method not implemented.'); } - setChatSessionTitle(sessionId: string, title: string): void { + setChatSessionTitle(sessionResource: URI, title: string): void { throw new Error('Method not implemented.'); } From 6383127d0d8387fad14b7f3ef7dbde7e4681c68a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:08:35 -0700 Subject: [PATCH 1884/4355] Fix some test cases --- .../commandLineAnalyzer/commandLineFileWriteAnalyzer.ts | 2 ++ .../chatAgentTools/browser/treeSitterCommandParser.ts | 2 +- .../test/electron-browser/treeSitterCommandParser.test.ts | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts index b835f7bf9a6..a28941f4f52 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineFileWriteAnalyzer.ts @@ -31,6 +31,8 @@ export class CommandLineFileWriteAnalyzer extends Disposable implements ICommand private async _getFileWrites(options: ICommandLineAnalyzerOptions): Promise { let fileWrites: URI[] | string[] = []; const capturedFileWrites = await this._treeSitterCommandParser.getFileWrites(options.treeSitterLanguage, options.commandLine); + // TODO: Handle environment variables https://github.com/microsoft/vscode/issues/274166 + // TODO: Handle command substitions/complex destinations https://github.com/microsoft/vscode/issues/274167 if (capturedFileWrites.length) { let cwd = await options.instance?.getCwdResource(); if (!cwd) { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts index b5c3f2043fd..99676aeef8b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/treeSitterCommandParser.ts @@ -45,7 +45,7 @@ export class TreeSitterCommandParser { case TreeSitterCommandParserLanguage.Bash: query = [ '(file_redirect', - ' destination: [(word) (string (string_content)) (raw_string)] @file)', + ' destination: [(word) (string (string_content)) (raw_string) (concatenation)] @file)', ].join('\n'); break; case TreeSitterCommandParserLanguage.PowerShell: diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts index e9d6f4e3b59..fcaeef38f66 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/treeSitterCommandParser.test.ts @@ -281,8 +281,8 @@ import { arch } from '../../../../../../base/common/process.js'; test('here document', () => t('cat > file.txt << EOF\nhello\nworld\nEOF', ['file.txt'])); test('quoted filenames', () => t('echo hello > "file with spaces.txt"', ['"file with spaces.txt"'])); test('single quoted filenames', () => t('echo hello > \'file.txt\'', ['\'file.txt\''])); - test.skip('variable in filename', () => t('echo hello > $HOME/file.txt', ['$HOME/file.txt'])); - test.skip('command substitution in filename', () => t('echo hello > $(date +%Y%m%d).log', ['$(date +%Y%m%d).log'])); + test('variable in filename', () => t('echo hello > $HOME/file.txt', ['$HOME/file.txt'])); + test('command substitution in filename', () => t('echo hello > $(date +%Y%m%d).log', ['$(date +%Y%m%d).log'])); test('tilde expansion in filename', () => t('echo hello > ~/file.txt', ['~/file.txt'])); test('absolute path', () => t('echo hello > /tmp/file.txt', ['/tmp/file.txt'])); test('relative path', () => t('echo hello > ./output/file.txt', ['./output/file.txt'])); @@ -297,7 +297,7 @@ import { arch } from '../../../../../../base/common/process.js'; test('redirection with background job', () => t('long_command > output.txt &', ['output.txt'])); test('conditional redirection', () => t('test -f input.txt && cat input.txt > output.txt || echo "not found" > error.txt', ['output.txt', 'error.txt'])); test('loop with redirection', () => t('for file in *.txt; do cat "$file" >> combined.txt; done', ['combined.txt'])); - test.skip('function with redirection', () => t('function backup() { cp "$1" > backup_"$1"; }', ['backup_$1'])); + test('function with redirection', () => t('function backup() { cp "$1" > backup_"$1"; }', ['backup_"$1"'])); }); suite('edge cases', () => { From e332ff48f396915477bac5c04c32e03488a03820 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:52:18 -0700 Subject: [PATCH 1885/4355] Update src/vs/workbench/contrib/chat/common/chatServiceImpl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index a93f221237a..59879f0c3c9 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -1184,7 +1184,6 @@ export class ChatService extends Disposable implements IChatService { throw new Error(`Can only delete local chat sessions, got: ${parsed.chatSessionType}`); } - const sessionId = parsed.sessionId; - return sessionId; + return parsed.sessionId; } } From f0d06a9bc3ee25f42ee733eaed3f5815e7b14371 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Oct 2025 20:21:34 +0100 Subject: [PATCH 1886/4355] move advanced to separate section and introduce stable filter (#274144) --- .../preferences/browser/settingsSearchMenu.ts | 7 +++++++ .../preferences/browser/settingsTreeModels.ts | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 335373d99db..941759b0a2e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -152,6 +152,12 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu `@${POLICY_SETTING_TAG}` ), new Separator(), + this.createToggleAction( + 'stableSettingsSearch', + localize('stableSettings', "Stable"), + localize('stableSettingsSearchTooltip', "Show stable settings"), + `@stable`, + ), this.createToggleAction( 'previewSettingsSearch', localize('previewSettings', "Preview"), @@ -164,6 +170,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu localize('experimentalSettingsSearchTooltip', "Show experimental settings"), `@tag:experimental`, ), + new Separator(), this.createToggleAction( 'advancedSettingsSearch', localize('advancedSettingsSearch', "Advanced"), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index d5e92a2a193..af28df144b9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -402,6 +402,21 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.inspectSelf(); } + // Handle the special 'stable' tag filter + if (tagFilters.has('stable')) { + // For stable filter, exclude preview and experimental settings + if (this.tags?.has('preview') || this.tags?.has('experimental')) { + return false; + } + // Check other filters (excluding 'stable' itself) + const otherFilters = new Set(Array.from(tagFilters).filter(tag => tag !== 'stable')); + if (otherFilters.size === 0) { + return true; + } + return !!this.tags?.size && + Array.from(otherFilters).every(tag => this.tags!.has(tag)); + } + // Check that the filter tags are a subset of this setting's tags return !!this.tags?.size && Array.from(tagFilters).every(tag => this.tags!.has(tag)); @@ -1169,6 +1184,12 @@ export function parseQuery(query: string): IParsedQuery { return ''; }); + // Handle @stable by excluding preview and experimental tags + query = query.replace(/@stable/g, () => { + tags.push('stable'); + return ''; + }); + const extensions: string[] = []; const features: string[] = []; const ids: string[] = []; From 5a8185ae67ecc4c6e7b711f0d073da7abd32876d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Oct 2025 20:21:49 +0100 Subject: [PATCH 1887/4355] show advanced setting only when filter is set or matches id filter (#274159) --- .../preferences/browser/settingsEditor2.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index c42375d865e..1d9a15732b7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -351,6 +351,30 @@ export class SettingsEditor2 extends EditorPane { }); } + private canShowAdvancedSettings(): boolean { + return this.viewState.tagFilters?.has(ADVANCED_SETTING_TAG) ?? false; + } + + /** + * Determines whether a setting should be shown even when advanced settings are filtered out. + * Returns true if: + * - The setting is not tagged as advanced, OR + * - The setting matches an ID filter (@id:settingKey), OR + * - The setting key appears in the search query + */ + private shouldShowSetting(setting: ISetting): boolean { + if (!setting.tags?.includes(ADVANCED_SETTING_TAG)) { + return true; + } + if (this.viewState.idFilters?.has(setting.key)) { + return true; + } + if (this.viewState.query?.includes(setting.key)) { + return true; + } + return false; + } + private disableAiSearchToggle(): void { if (this.showAiResultsAction) { this.showAiResultsAction.checked = false; @@ -1406,7 +1430,7 @@ export class SettingsEditor2 extends EditorPane { coreSettingsGroups.push(group); } } - const filter = this.viewState.tagFilters?.has(ADVANCED_SETTING_TAG) ? undefined : { exclude: { tags: [ADVANCED_SETTING_TAG] } }; + const filter = this.canShowAdvancedSettings() ? undefined : { exclude: { tags: [ADVANCED_SETTING_TAG] } }; const settingsResult = resolveSettingsTree(tocData, coreSettingsGroups, filter, this.logService); const resolvedSettingsRoot = settingsResult.tree; @@ -1798,9 +1822,13 @@ export class SettingsEditor2 extends EditorPane { filterMatches: [], exactMatch: false, }; + const shouldShowAdvanced = this.canShowAdvancedSettings(); for (const g of this.defaultSettingsEditorModel.settingsGroups.slice(1)) { for (const sect of g.sections) { for (const setting of sect.settings) { + if (!shouldShowAdvanced && !this.shouldShowSetting(setting)) { + continue; + } fullResult.filterMatches.push({ setting, matches: [], @@ -1958,6 +1986,11 @@ export class SettingsEditor2 extends EditorPane { return null; } + // Filter out advanced settings unless the advanced tag is explicitly set or setting matches an ID filter + if (result && !this.canShowAdvancedSettings()) { + result.filterMatches = result.filterMatches.filter(match => this.shouldShowSetting(match.setting)); + } + // Only log the elapsed time if there are actual results. if (result && result.filterMatches.length > 0) { const elapsed = this.stopWatch.elapsed(); From 186f1b99d2cfca381051de72984234d72cd5d0ee Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 20:36:54 +0100 Subject: [PATCH 1888/4355] Goto implementation UI has a contrast issue (fix #212548) (#274178) --- .../contrib/gotoSymbol/browser/peek/referencesWidget.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.css b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.css index ca0391c5eb7..76040e347a6 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.css +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.css @@ -54,7 +54,8 @@ } .monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { - background-color: var(--vscode-peekViewResult-matchHighlightBackground); + color: var(--vscode-peekViewResult-fileForeground) !important; + background-color: var(--vscode-peekViewResult-matchHighlightBackground) !important; } .monaco-editor .reference-zone-widget .preview .reference-decoration { From 9e8f5336da77d8f049f68a7d5d68f1164ee65607 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:37:22 +0000 Subject: [PATCH 1889/4355] SCM - plumbing to support artifacts in the repositories view (#274173) * WIP - ported changes from the old branch * More work getting the changes ready * Tweak things * Git - wire-up checkout command --- extensions/git/package.json | 21 ++ extensions/git/src/artifactProvider.ts | 62 ++++ extensions/git/src/commands.ts | 12 +- extensions/git/src/repository.ts | 8 + extensions/git/tsconfig.json | 1 + src/vs/platform/actions/common/actions.ts | 2 + .../common/extensionsApiProposals.ts | 9 + src/vs/workbench/api/browser/mainThreadSCM.ts | 48 +++ .../workbench/api/common/extHost.protocol.ts | 18 + src/vs/workbench/api/common/extHostSCM.ts | 56 ++- .../contrib/scm/browser/media/scm.css | 33 ++ .../contrib/scm/browser/scm.contribution.ts | 6 + .../scm/browser/scmRepositoriesViewPane.ts | 337 +++++++++++++++--- .../contrib/scm/browser/scmViewService.ts | 7 +- src/vs/workbench/contrib/scm/browser/util.ts | 9 + .../workbench/contrib/scm/common/artifact.ts | 38 ++ src/vs/workbench/contrib/scm/common/scm.ts | 3 + .../actions/common/menusExtensionPoint.ts | 12 + ...contribSourceControlArtifactGroupMenu.d.ts | 7 + ...osed.contribSourceControlArtifactMenu.d.ts | 7 + .../vscode.proposed.scmArtifactProvider.d.ts | 31 ++ 21 files changed, 673 insertions(+), 54 deletions(-) create mode 100644 extensions/git/src/artifactProvider.ts create mode 100644 src/vs/workbench/contrib/scm/common/artifact.ts create mode 100644 src/vscode-dts/vscode.proposed.contribSourceControlArtifactGroupMenu.d.ts create mode 100644 src/vscode-dts/vscode.proposed.contribSourceControlArtifactMenu.d.ts create mode 100644 src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index ec20b66799d..dc5ff357642 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -16,6 +16,8 @@ "contribMergeEditorMenus", "contribMultiDiffEditorMenus", "contribDiffEditorGutterToolBarMenus", + "contribSourceControlArtifactGroupMenu", + "contribSourceControlArtifactMenu", "contribSourceControlHistoryItemMenu", "contribSourceControlHistoryTitleMenu", "contribSourceControlInputBoxMenu", @@ -26,6 +28,7 @@ "quickInputButtonLocation", "quickPickSortByLabel", "scmActionButton", + "scmArtifactProvider", "scmHistoryProvider", "scmMultiDiffEditor", "scmProviderOptions", @@ -1016,6 +1019,13 @@ "title": "%command.graphCompareWithMergeBase%", "category": "Git", "enablement": "!operationInProgress && scmCurrentHistoryItemRefHasBase" + }, + { + "command": "git.repositories.checkout", + "title": "%command.graphCheckout%", + "icon": "$(target)", + "category": "Git", + "enablement": "!operationInProgress" } ], "continueEditSession": [ @@ -1639,6 +1649,10 @@ { "command": "git.diff.stageSelection", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/" + }, + { + "command": "git.repositories.checkout", + "when": "false" } ], "scm/title": [ @@ -1829,6 +1843,13 @@ "when": "scmProvider == git && scmProviderContext == worktree" } ], + "scm/artifact/context": [ + { + "command": "git.repositories.checkout", + "group": "inline@1", + "when": "scmProvider == git" + } + ], "scm/resourceGroup/context": [ { "command": "git.stageAllMerge", diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts new file mode 100644 index 00000000000..4344d60d929 --- /dev/null +++ b/extensions/git/src/artifactProvider.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri } from 'vscode'; +import { IDisposable } from './util'; +import { Repository } from './repository'; + +export class GitArtifactProvider implements SourceControlArtifactProvider, IDisposable { + private readonly _onDidChangeArtifacts = new EventEmitter(); + readonly onDidChangeArtifacts: Event = this._onDidChangeArtifacts.event; + + private readonly _groups: SourceControlArtifactGroup[]; + + constructor( + private readonly repository: Repository, + private readonly logger: LogOutputChannel + ) { + this._groups = [ + { id: 'branches', name: l10n.t('Branches'), icon: new ThemeIcon('git-branch') }, + { id: 'tags', name: l10n.t('Tags'), icon: new ThemeIcon('tag') } + ]; + } + provideArtifactGroups(): SourceControlArtifactGroup[] { + return this._groups; + } + + async provideArtifacts(group: string): Promise { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const shortCommitLength = config.get('commitShortHashLength', 7); + + try { + if (group === 'branches') { + const refs = await this.repository + .getRefs({ pattern: 'refs/heads', includeCommitDetails: true }); + + return refs.map(r => ({ + id: `refs/heads/${r.name}`, + name: r.name ?? r.commit ?? '', + description: `${r.commit?.substring(0, shortCommitLength)}` + })); + } else if (group === 'tags') { + const refs = await this.repository + .getRefs({ pattern: 'refs/tags', includeCommitDetails: true }); + + return refs.map(r => ({ + id: `refs/tags/${r.name}`, + name: r.name ?? r.commit ?? '', + description: r.commitDetails?.message ?? r.commit?.substring(0, shortCommitLength) + })); + } + } catch (err) { + this.logger.error(`[GitArtifactProvider][provideArtifacts] Error while providing artifacts for group '${group}': `, err); + return []; + } + + return []; + } + + dispose(): void { } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f8bd467ad68..ce59f9919be 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlArtifact } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -5182,6 +5182,16 @@ export class CommandCenter { config.update(setting, !enabled, true); } + @command('git.repositories.checkout', { repository: true }) + async artifactCheckout(repository: Repository, artifact: SourceControlArtifact): Promise { + console.log(repository, artifact); + if (!repository || !artifact) { + return; + } + + await this._checkout(repository, { treeish: artifact.name }); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 00d9186fd46..4180f0a5c70 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -25,6 +25,7 @@ import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; +import { GitArtifactProvider } from './artifactProvider'; import { RepositoryCache } from './repositoryCache'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -873,6 +874,9 @@ export class Repository implements Disposable { return this.repository.kind; } + private _artifactProvider: GitArtifactProvider; + get artifactProvider(): GitArtifactProvider { return this._artifactProvider; } + private _historyProvider: GitHistoryProvider; get historyProvider(): GitHistoryProvider { return this._historyProvider; } @@ -952,6 +956,10 @@ export class Repository implements Disposable { this._sourceControl.quickDiffProvider = this; this._sourceControl.secondaryQuickDiffProvider = new StagedResourceQuickDiffProvider(this, logger); + this._artifactProvider = new GitArtifactProvider(this, logger); + this._sourceControl.artifactProvider = this._artifactProvider; + this.disposables.push(this._artifactProvider); + this._historyProvider = new GitHistoryProvider(historyItemDetailProviderRegistry, this, logger); this._sourceControl.historyProvider = this._historyProvider; this.disposables.push(this._historyProvider); diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 3fdc14300e5..eac688f81de 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -16,6 +16,7 @@ "../../src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts", "../../src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index bada5ee4562..ee796ec40a6 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -146,6 +146,8 @@ export class MenuId { static readonly SCMHistoryItemContext = new MenuId('SCMHistoryItemContext'); static readonly SCMHistoryItemChangeContext = new MenuId('SCMHistoryItemChangeContext'); static readonly SCMHistoryItemRefContext = new MenuId('SCMHistoryItemRefContext'); + static readonly SCMArtifactGroupContext = new MenuId('SCMArtifactGroupContext'); + static readonly SCMArtifactContext = new MenuId('SCMArtifactContext'); static readonly SCMQuickDiffDecorations = new MenuId('SCMQuickDiffDecorations'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index ae85a2fadfb..583395ffb39 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -145,6 +145,12 @@ const _allApiProposals = { contribShareMenu: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', }, + contribSourceControlArtifactGroupMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlArtifactGroupMenu.d.ts', + }, + contribSourceControlArtifactMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlArtifactMenu.d.ts', + }, contribSourceControlHistoryItemMenu: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', }, @@ -349,6 +355,9 @@ const _allApiProposals = { scmActionButton: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', }, + scmArtifactProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts', + }, scmHistoryProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', }, diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 9c421b05165..f73d726c720 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -30,6 +30,7 @@ import { ITextModel } from '../../../editor/common/model.js'; import { structuralEquals } from '../../../base/common/equals.js'; import { historyItemBaseRefColor, historyItemRefColor, historyItemRemoteRefColor } from '../../contrib/scm/browser/scmHistory.js'; import { ColorIdentifier } from '../../../platform/theme/common/colorUtils.js'; +import { ISCMArtifact, ISCMArtifactGroup, ISCMArtifactProvider } from '../../contrib/scm/common/artifact.js'; function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon): URI | { light: URI; dark: URI } | ThemeIcon | undefined { if (iconDto === undefined) { @@ -171,6 +172,23 @@ class MainThreadSCMResource implements ISCMResource { } } +class MainThreadSCMArtifactProvider implements ISCMArtifactProvider { + constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } + + async provideArtifactGroups(token?: CancellationToken): Promise { + const artifactGroups = await this.proxy.$provideArtifactGroups(this.handle, token ?? CancellationToken.None); + return artifactGroups?.map(group => ({ ...group, icon: getIconFromIconDto(group.icon) })); + } + + async provideArtifacts(group: string, token?: CancellationToken): Promise { + return this.proxy.$provideArtifacts(this.handle, group, token ?? CancellationToken.None); + } + + $onDidChangeArtifacts(group: string): void { + throw new Error('Method not implemented.'); + } +} + class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { private readonly _historyItemRef = observableValueOpts({ owner: this, @@ -309,6 +327,9 @@ class MainThreadSCMProvider implements ISCMProvider { private _quickDiff: IDisposable | undefined; private _stagedQuickDiff: IDisposable | undefined; + private readonly _artifactProvider = observableValue(this, undefined); + get artifactProvider() { return this._artifactProvider; } + private readonly _historyProvider = observableValue(this, undefined); get historyProvider() { return this._historyProvider; } @@ -398,6 +419,13 @@ class MainThreadSCMProvider implements ISCMProvider { this._stagedQuickDiff = undefined; } + if (features.hasArtifactProvider && !this.artifactProvider.get()) { + const artifactProvider = new MainThreadSCMArtifactProvider(this.proxy, this.handle); + this._artifactProvider.set(artifactProvider, undefined); + } else if (features.hasArtifactProvider === false && this.artifactProvider.get()) { + this._artifactProvider.set(undefined, undefined); + } + if (features.hasHistoryProvider && !this.historyProvider.get()) { const historyProvider = new MainThreadSCMHistoryProvider(this.proxy, this.handle); this._historyProvider.set(historyProvider, undefined); @@ -534,6 +562,14 @@ class MainThreadSCMProvider implements ISCMProvider { this._historyProvider.get()?.$onDidChangeHistoryItemRefs(historyItemRefs); } + $onDidChangeArtifacts(group: string): void { + if (!this.artifactProvider.get()) { + return; + } + + this._artifactProvider.get()?.$onDidChangeArtifacts(group); + } + toJSON() { return { $mid: MarshalledId.ScmProvider, @@ -791,4 +827,16 @@ export class MainThreadSCM implements MainThreadSCMShape { const provider = repository.provider as MainThreadSCMProvider; provider.$onDidChangeHistoryProviderHistoryItemRefs(historyItemRefs); } + + async $onDidChangeArtifacts(sourceControlHandle: number, group: string): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); + const repository = this._repositories.get(sourceControlHandle); + + if (!repository) { + return; + } + + const provider = repository.provider as MainThreadSCMProvider; + provider.$onDidChangeArtifacts(group); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 59d6f48557a..3ac2dc28783 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1606,6 +1606,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { } export interface SCMProviderFeatures { + hasArtifactProvider?: boolean; hasHistoryProvider?: boolean; hasQuickDiffProvider?: boolean; quickDiffLabel?: string; @@ -1695,6 +1696,18 @@ export interface SCMHistoryItemChangeDto { readonly modifiedUri: UriComponents | undefined; } +export interface SCMArtifactGroupDto { + readonly id: string; + readonly name: string; + readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; +} + +export interface SCMArtifactDto { + readonly id: string; + readonly name: string; + readonly description?: string; +} + export interface MainThreadSCMShape extends IDisposable { $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: IconPathDto | undefined, inputBoxDocumentUri: UriComponents): Promise; $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise; @@ -1716,6 +1729,8 @@ export interface MainThreadSCMShape extends IDisposable { $onDidChangeHistoryProviderCurrentHistoryItemRefs(sourceControlHandle: number, historyItemRef?: SCMHistoryItemRefDto, historyItemRemoteRef?: SCMHistoryItemRefDto, historyItemBaseRef?: SCMHistoryItemRefDto): Promise; $onDidChangeHistoryProviderHistoryItemRefs(sourceControlHandle: number, historyItemRefs: SCMHistoryItemRefsChangeEventDto): Promise; + + $onDidChangeArtifacts(sourceControlHandle: number, group: string): Promise; } export interface MainThreadQuickDiffShape extends IDisposable { @@ -2616,6 +2631,9 @@ export interface ExtHostSCMShape { $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; $resolveHistoryItemChangeRangeChatContext(sourceControlHandle: number, historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise; $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise; + + $provideArtifactGroups(sourceControlHandle: number, token: CancellationToken): Promise; + $provideArtifacts(sourceControlHandle: number, group: string, token: CancellationToken): Promise; } export interface ExtHostQuickDiffShape { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 01eb10ff0ba..7d97fa52826 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from '../../../base/common/decorators.js'; import { DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js'; import { asPromise } from '../../../base/common/async.js'; import { ExtHostCommands } from './extHostCommands.js'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemRefDto, SCMActionButtonDto } from './extHost.protocol.js'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemRefDto, SCMActionButtonDto, SCMArtifactGroupDto, SCMArtifactDto } from './extHost.protocol.js'; import { sortedDiff, equals } from '../../../base/common/arrays.js'; import { comparePaths } from '../../../base/common/comparers.js'; import type * as vscode from 'vscode'; @@ -670,6 +670,29 @@ class ExtHostSourceControl implements vscode.SourceControl { } } + private _artifactProvider: vscode.SourceControlArtifactProvider | undefined; + private readonly _artifactProviderDisposable = new MutableDisposable(); + + get artifactProvider(): vscode.SourceControlArtifactProvider | undefined { + checkProposedApiEnabled(this._extension, 'scmArtifactProvider'); + return this._artifactProvider; + } + + set artifactProvider(artifactProvider: vscode.SourceControlArtifactProvider | undefined) { + checkProposedApiEnabled(this._extension, 'scmArtifactProvider'); + + this._artifactProvider = artifactProvider; + this._artifactProviderDisposable.value = new DisposableStore(); + + this.#proxy.$updateSourceControl(this.handle, { hasArtifactProvider: !!artifactProvider }); + + if (artifactProvider) { + this._artifactProviderDisposable.value.add(artifactProvider.onDidChangeArtifacts((group: string) => { + this.#proxy.$onDidChangeArtifacts(this.handle, group); + })); + } + } + private _commitTemplate: string | undefined = undefined; get commitTemplate(): string | undefined { @@ -887,6 +910,8 @@ class ExtHostSourceControl implements vscode.SourceControl { this._acceptInputDisposables.dispose(); this._actionButtonDisposables.dispose(); this._statusBarDisposables.dispose(); + this._historyProviderDisposable.dispose(); + this._artifactProviderDisposable.dispose(); this._groups.forEach(group => group.dispose()); this.#proxy.$unregisterSourceControl(this.handle); @@ -1180,4 +1205,33 @@ export class ExtHostSCM implements ExtHostSCMShape { return undefined; } } + + async $provideArtifactGroups(sourceControlHandle: number, token: CancellationToken): Promise { + try { + const artifactProvider = this._sourceControls.get(sourceControlHandle)?.artifactProvider; + const groups = await artifactProvider?.provideArtifactGroups(token); + + return groups?.map(group => ({ + ...group, + icon: getHistoryItemIconDto(group.icon) + })); + } + catch (err) { + this.logService.error('ExtHostSCM#$provideArtifactGroups', err); + return undefined; + } + } + + async $provideArtifacts(sourceControlHandle: number, group: string, token: CancellationToken): Promise { + try { + const artifactProvider = this._sourceControls.get(sourceControlHandle)?.artifactProvider; + const artifacts = await artifactProvider?.provideArtifacts(group, token); + + return artifacts ?? undefined; + } + catch (err) { + this.logService.error('ExtHostSCM#$provideArtifacts', err); + return undefined; + } + } } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 96283850528..72329d786e3 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -518,10 +518,43 @@ /* Repositories */ +.scm-repositories-view .monaco-list .monaco-list-row .scm-artifact-group > .actions, +.scm-repositories-view .monaco-list .monaco-list-row .scm-artifact > .actions { + display: none; + max-width: fit-content; +} + +.scm-repositories-view .monaco-list .monaco-list-row:hover .scm-artifact-group > .actions, +.scm-repositories-view .monaco-list .monaco-list-row.focused .scm-artifact-group > .actions, +.scm-repositories-view .monaco-list .monaco-list-row:hover .scm-artifact > .actions, +.scm-repositories-view .monaco-list .monaco-list-row.focused .scm-artifact > .actions { + display: block; +} + .scm-view.scm-repositories-view .monaco-highlighted-label { font-weight: normal; } +.scm-repositories-view .scm-artifact-group, +.scm-repositories-view .scm-artifact { + display: flex; +} + +.scm-repositories-view .scm-artifact-group .monaco-icon-label, +.scm-repositories-view .scm-artifact .monaco-icon-label { + flex-grow: 1; +} + +.scm-repositories-view .scm-artifact .monaco-icon-label-container { + display: flex; +} + +.scm-repositories-view .scm-artifact-group .monaco-highlighted-label, +.scm-repositories-view .scm-artifact .monaco-highlighted-label { + display: flex; + align-items: center; +} + /* History item hover */ .monaco-hover.history-item-hover p:first-child { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 6af5611da1e..8ac71377598 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -358,6 +358,12 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('scm.repositories.selectionMode', "Controls the selection mode of the repositories in the Source Control Repositories view."), default: 'single' }, + 'scm.repositories.explorer': { + type: 'boolean', + markdownDescription: localize('scm.repositories.explorer', "Controls whether to show repository artifacts in the Source Control Repositories view. This feature is experimental and only works when {0} is set to {1}.", '\`#scm.repositories.selectionMode#\`', 'single'), + default: false, + tags: ['experimental'] + }, 'scm.showActionButton': { type: 'boolean', markdownDescription: localize('showActionButton', "Controls whether an action button can be shown in the Source Control view."), diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 1a45f6bebb5..eca40f08967 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -8,7 +8,7 @@ import { localize } from '../../../../nls.js'; import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browser/ui/list/list.js'; -import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent } from '../../../../base/browser/ui/tree/tree.js'; +import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -16,19 +16,30 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { combinedDisposable, Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRenderer.js'; -import { collectContextMenuActions, getActionViewItemProvider, isSCMRepository } from './util.js'; +import { collectContextMenuActions, getActionViewItemProvider, isSCMArtifactGroupTreeElement, isSCMArtifactTreeElement, isSCMRepository } from './util.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { Iterable } from '../../../../base/common/iterator.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { autorun, IObservable, observableFromEvent, observableSignalFromEvent } from '../../../../base/common/observable.js'; +import { autorun, IObservable, observableFromEvent, observableSignalFromEvent, runOnChange } from '../../../../base/common/observable.js'; import { Sequencer } from '../../../../base/common/async.js'; +import { SCMArtifactGroupTreeElement, SCMArtifactTreeElement } from '../common/artifact.js'; +import { FuzzyScore } from '../../../../base/common/fuzzyScorer.js'; +import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { SCMViewService } from './scmViewService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; + +type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement; class ListDelegate implements IListVirtualDelegate { @@ -36,48 +47,213 @@ class ListDelegate implements IListVirtualDelegate { return 22; } - getTemplateId(): string { - return RepositoryRenderer.TEMPLATE_ID; + getTemplateId(element: TreeElement): string { + if (isSCMRepository(element)) { + return RepositoryRenderer.TEMPLATE_ID; + } else if (isSCMArtifactGroupTreeElement(element)) { + return ArtifactGroupRenderer.TEMPLATE_ID; + } else if (isSCMArtifactTreeElement(element)) { + return ArtifactRenderer.TEMPLATE_ID; + } else { + throw new Error('Invalid tree element'); + } + } +} + +interface ArtifactGroupTemplate { + readonly label: IconLabel; + readonly actionBar: WorkbenchToolBar; + readonly templateDisposable: IDisposable; +} + +class ArtifactGroupRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'artifactGroup'; + get templateId(): string { return ArtifactGroupRenderer.TEMPLATE_ID; } + + constructor( + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IMenuService private readonly _menuService: IMenuService, + @ICommandService private readonly _commandService: ICommandService, + @ITelemetryService private readonly _telemetryService: ITelemetryService + ) { } + + renderTemplate(container: HTMLElement): ArtifactGroupTemplate { + const element = append(container, $('.scm-artifact-group')); + const label = new IconLabel(element, { supportIcons: true }); + + const actionsContainer = append(element, $('.actions')); + const actionBar = new WorkbenchToolBar(actionsContainer, undefined, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService); + + return { label, actionBar, templateDisposable: combinedDisposable(label, actionBar) }; + } + + renderElement(node: ITreeNode, index: number, templateData: ArtifactGroupTemplate): void { + const provider = node.element.repository.provider; + const artifactGroup = node.element.artifactGroup; + const artifactGroupIcon = ThemeIcon.isThemeIcon(artifactGroup.icon) + ? `$(${artifactGroup.icon.id}) ` : ''; + + templateData.label.setLabel(`${artifactGroupIcon}${artifactGroup.name}`); + + const actions = this._menuService.getMenuActions( + MenuId.SCMArtifactGroupContext, + this._contextKeyService.createOverlay([['scmArtifactGroup', artifactGroup.id]]), + { arg: provider, shouldForwardArgs: true }); + + templateData.actionBar.context = node.element.artifactGroup; + templateData.actionBar.setActions(getActionBarActions(actions, 'inline').primary); + } + + disposeTemplate(templateData: ArtifactGroupTemplate): void { + templateData.templateDisposable.dispose(); } } -class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource { +interface ArtifactTemplate { + readonly label: IconLabel; + readonly actionBar: WorkbenchToolBar; + readonly templateDisposable: IDisposable; +} + +class ArtifactRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'artifact'; + get templateId(): string { return ArtifactRenderer.TEMPLATE_ID; } + + constructor( + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IMenuService private readonly _menuService: IMenuService, + @ICommandService private readonly _commandService: ICommandService, + @ITelemetryService private readonly _telemetryService: ITelemetryService + ) { } + + renderTemplate(container: HTMLElement): ArtifactTemplate { + const element = append(container, $('.scm-artifact')); + const label = new IconLabel(element, { supportIcons: true }); + + const actionsContainer = append(element, $('.actions')); + const actionBar = new WorkbenchToolBar(actionsContainer, undefined, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService); + + return { label, actionBar, templateDisposable: combinedDisposable(label, actionBar) }; + } + + renderElement(node: ITreeNode, index: number, templateData: ArtifactTemplate): void { + const provider = node.element.repository.provider; + const artifact = node.element.artifact; + + const artifactGroup = node.element.group; + const artifactGroupIcon = ThemeIcon.isThemeIcon(artifactGroup.icon) + ? `$(${artifactGroup.icon.id}) ` : ''; + + templateData.label.setLabel(`${artifactGroupIcon}${artifact.name}`, artifact.description); + + const actions = this._menuService.getMenuActions( + MenuId.SCMArtifactContext, + this._contextKeyService.createOverlay([['scmArtifactGroup', artifactGroup.id]]), + { arg: provider, shouldForwardArgs: true }); + + templateData.actionBar.context = node.element.artifact; + templateData.actionBar.setActions(getActionBarActions(actions, 'inline').primary); + } + + disposeTemplate(templateData: ArtifactTemplate): void { + templateData.templateDisposable.dispose(); + } +} + +class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource { constructor(@ISCMViewService private readonly scmViewService: ISCMViewService) { super(); } - getChildren(inputOrElement: ISCMViewService | ISCMRepository): Iterable { - const parentId = isSCMRepository(inputOrElement) - ? inputOrElement.provider.id - : undefined; + async getChildren(inputOrElement: ISCMViewService | TreeElement): Promise> { + if (this.scmViewService.explorerEnabledConfig.get() === false) { + const parentId = isSCMRepository(inputOrElement) + ? inputOrElement.provider.id + : undefined; - const repositories = this.scmViewService.repositories - .filter(r => r.provider.parentId === parentId); + const repositories = this.scmViewService.repositories + .filter(r => r.provider.parentId === parentId); + + return repositories; + } - return repositories; + // Explorer mode + if (inputOrElement instanceof SCMViewService) { + return this.scmViewService.repositories; + } else if (isSCMRepository(inputOrElement)) { + const artifactGroups = await inputOrElement.provider.artifactProvider.get()?.provideArtifactGroups() ?? []; + return artifactGroups.map(group => ({ + repository: inputOrElement, + artifactGroup: group, + type: 'artifactGroup' + })); + } else if (isSCMArtifactGroupTreeElement(inputOrElement)) { + const repository = inputOrElement.repository; + const artifacts = await repository.provider.artifactProvider.get()?.provideArtifacts(inputOrElement.artifactGroup.id) ?? []; + + return artifacts.map(artifact => ({ + repository, + group: inputOrElement.artifactGroup, + artifact, + type: 'artifact' + })); + } else if (isSCMArtifactTreeElement(inputOrElement)) { + return []; + } else { + return []; + } } - hasChildren(inputOrElement: ISCMViewService | ISCMRepository): boolean { - const parentId = isSCMRepository(inputOrElement) - ? inputOrElement.provider.id - : undefined; + hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { + if (this.scmViewService.explorerEnabledConfig.get() === false) { + const parentId = isSCMRepository(inputOrElement) + ? inputOrElement.provider.id + : undefined; + + const repositories = this.scmViewService.repositories + .filter(r => r.provider.parentId === parentId); - const repositories = this.scmViewService.repositories - .filter(r => r.provider.parentId === parentId); + return repositories.length > 0; + } - return repositories.length > 0; + // Explorer mode + if (inputOrElement instanceof SCMViewService) { + return this.scmViewService.repositories.length > 0; + } else if (isSCMRepository(inputOrElement)) { + return true; + } else if (isSCMArtifactGroupTreeElement(inputOrElement)) { + return true; + } else if (isSCMArtifactTreeElement(inputOrElement)) { + return false; + } else { + return false; + } } } -class RepositoryTreeIdentityProvider implements IIdentityProvider { - getId(element: ISCMRepository): string { - return element.provider.id; +class RepositoryTreeIdentityProvider implements IIdentityProvider { + getId(element: TreeElement): string { + if (isSCMRepository(element)) { + return `repo:${element.provider.id}`; + } else if (isSCMArtifactGroupTreeElement(element)) { + return `artifactGroup:${element.repository.provider.id}/${element.artifactGroup.id}`; + } else if (isSCMArtifactTreeElement(element)) { + return `artifact:${element.repository.provider.id}/${element.group.id}/${element.artifact.id}`; + } else { + throw new Error('Invalid tree element'); + } } } export class SCMRepositoriesViewPane extends ViewPane { - private tree!: WorkbenchCompressibleAsyncDataTree; + private tree!: WorkbenchCompressibleAsyncDataTree; private treeDataSource!: RepositoryTreeDataSource; private treeIdentityProvider!: RepositoryTreeIdentityProvider; private readonly treeOperationSequencer = new Sequencer(); @@ -137,6 +313,12 @@ export class SCMRepositoriesViewPane extends ViewPane { this.updateBodySize(this.tree.contentHeight, visibleCount); })); + // scm.repositories.explorer setting + this.visibilityDisposables.add(runOnChange(this.scmViewService.explorerEnabledConfig, async () => { + await this.updateChildren(); + this.updateBodySize(this.tree.contentHeight); + })); + // Update tree (add/remove repositories) const addedRepositoryObs = observableFromEvent( this, this.scmService.onDidAddRepository, e => e); @@ -200,16 +382,23 @@ export class SCMRepositoriesViewPane extends ViewPane { isIncompressible: () => true }, [ - this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMSourceControlInline, getActionViewItemProvider(this.instantiationService)) + this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMSourceControlInline, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(ArtifactGroupRenderer), + this.instantiationService.createInstance(ArtifactRenderer) ], this.treeDataSource, { identityProvider: this.treeIdentityProvider, horizontalScrolling: false, collapseByDefault: (e: unknown) => { - if (isSCMRepository(e) && e.provider.parentId === undefined) { - return false; + if (this.scmViewService.explorerEnabledConfig.get() === false) { + if (isSCMRepository(e) && e.provider.parentId === undefined) { + return false; + } + return true; } + + // Explorer mode return true; }, compressionEnabled: compressionEnabled.get(), @@ -218,15 +407,23 @@ export class SCMRepositoriesViewPane extends ViewPane { expandOnDoubleClick: false, expandOnlyOnTwistieClick: true, accessibilityProvider: { - getAriaLabel(r: ISCMRepository) { - return r.provider.label; + getAriaLabel(element: TreeElement): string { + if (isSCMRepository(element)) { + return element.provider.label; + } else if (isSCMArtifactGroupTreeElement(element)) { + return element.artifactGroup.name; + } else if (isSCMArtifactTreeElement(element)) { + return element.artifact.name; + } else { + return ''; + } }, getWidgetAriaLabel() { return localize('scm', "Source Control Repositories"); } } } - ) as WorkbenchCompressibleAsyncDataTree; + ) as WorkbenchCompressibleAsyncDataTree; this._register(this.tree); this._register(autorun(reader => { @@ -241,11 +438,15 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.tree.onDidChangeContentHeight(this.onTreeContentHeightChange, this)); } - private onTreeContextMenu(e: ITreeContextMenuEvent): void { + private onTreeContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { return; } + if (!isSCMRepository(e.element)) { + return; + } + const provider = e.element.provider; const menus = this.scmViewService.menus.getRepositoryMenus(provider); const menu = menus.getRepositoryContextMenu(e.element); @@ -253,7 +454,7 @@ export class SCMRepositoriesViewPane extends ViewPane { const disposables = new DisposableStore(); const actionRunner = new RepositoryActionRunner(() => { - return this.tree.getSelection(); + return this.getTreeSelection(); }); disposables.add(actionRunner); disposables.add(actionRunner.onWillRun(() => this.tree.domFocus())); @@ -267,24 +468,36 @@ export class SCMRepositoriesViewPane extends ViewPane { }); } - private onTreeSelectionChange(e: ITreeEvent): void { + private onTreeSelectionChange(e: ITreeEvent): void { if (e.browserEvent && e.elements.length > 0) { const scrollTop = this.tree.scrollTop; - this.scmViewService.visibleRepositories = e.elements; + + if (e.elements.every(e => isSCMRepository(e))) { + this.scmViewService.visibleRepositories = e.elements; + } else if (e.elements.every(e => isSCMArtifactGroupTreeElement(e) || isSCMArtifactTreeElement(e))) { + this.scmViewService.visibleRepositories = e.elements.map(e => e.repository); + } + this.tree.scrollTop = scrollTop; } } - private onTreeDidChangeFocus(e: ITreeEvent): void { + private onTreeDidChangeFocus(e: ITreeEvent): void { if (e.browserEvent && e.elements.length > 0) { - this.scmViewService.focus(e.elements[0]); + if (isSCMRepository(e.elements[0])) { + this.scmViewService.focus(e.elements[0]); + } } } private onDidTreeFocus(): void { const focused = this.tree.getFocus(); if (focused.length > 0) { - this.scmViewService.focus(focused[0]); + if (isSCMRepository(focused[0])) { + this.scmViewService.focus(focused[0]); + } else if (isSCMArtifactGroupTreeElement(focused[0]) || isSCMArtifactTreeElement(focused[0])) { + this.scmViewService.focus(focused[0].repository); + } } } @@ -295,7 +508,7 @@ export class SCMRepositoriesViewPane extends ViewPane { this.treeOperationSequencer.queue(() => this.updateTreeSelection()); } - private async updateChildren(element?: ISCMRepository): Promise { + private async updateChildren(element?: TreeElement): Promise { await this.treeOperationSequencer.queue(async () => { if (element && this.tree.hasNode(element)) { await this.tree.updateChildren(element, true); @@ -305,17 +518,22 @@ export class SCMRepositoriesViewPane extends ViewPane { }); } - private async expand(element: ISCMRepository): Promise { + private async expand(element: TreeElement): Promise { await this.treeOperationSequencer.queue(() => this.tree.expand(element, true)); } private async updateRepository(repository: ISCMRepository): Promise { - if (repository.provider.parentId === undefined) { - await this.updateChildren(); - return; + if (this.scmViewService.explorerEnabledConfig.get() === false) { + if (repository.provider.parentId === undefined) { + await this.updateChildren(); + return; + } + + await this.updateParentRepository(repository); } - await this.updateParentRepository(repository); + // Explorer mode + await this.updateChildren(); } private async updateParentRepository(repository: ISCMRepository): Promise { @@ -334,16 +552,20 @@ export class SCMRepositoriesViewPane extends ViewPane { return; } - visibleCount = visibleCount ?? this.visibleCountObs.get(); - const empty = this.scmViewService.repositories.length === 0; - const size = Math.min(contentHeight / 22, visibleCount) * 22; + if (this.scmViewService.explorerEnabledConfig.get() === false) { + visibleCount = visibleCount ?? this.visibleCountObs.get(); + const empty = this.scmViewService.repositories.length === 0; + const size = Math.min(contentHeight / 22, visibleCount) * 22; - this.minimumBodySize = visibleCount === 0 ? 22 : size; - this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size; + this.minimumBodySize = visibleCount === 0 ? 22 : size; + this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size; + } else { + this.maximumBodySize = Number.POSITIVE_INFINITY; + } } private async updateTreeSelection(): Promise { - const oldSelection = this.tree.getSelection(); + const oldSelection = this.getTreeSelection(); const oldSet = new Set(oldSelection); const set = new Set(this.scmViewService.visibleRepositories); @@ -373,6 +595,19 @@ export class SCMRepositoriesViewPane extends ViewPane { } } + private getTreeSelection(): ISCMRepository[] { + return this.tree.getSelection() + .map(e => { + if (isSCMRepository(e)) { + return e; + } else if (isSCMArtifactGroupTreeElement(e) || isSCMArtifactTreeElement(e)) { + return e.repository; + } else { + throw new Error('Invalid tree element'); + } + }); + } + override dispose(): void { this.visibilityDisposables.dispose(); super.dispose(); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index e3695df1ddc..b7a00c4e433 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -18,7 +18,7 @@ import { binarySearch } from '../../../../base/common/arrays.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { autorun, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; +import { autorun, derived, derivedObservableWithCache, derivedOpts, IObservable, ISettableObservable, latestChangedValue, observableFromEventOpts, observableValue, runOnChange } from '../../../../base/common/observable.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorResourceAccessor } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; @@ -111,6 +111,7 @@ export class SCMViewService implements ISCMViewService { declare readonly _serviceBrand: undefined; readonly menus: ISCMMenus; + readonly explorerEnabledConfig: IObservable; readonly selectionModeConfig: IObservable; readonly graphShowIncomingChangesConfig: IObservable; readonly graphShowOutgoingChangesConfig: IObservable; @@ -243,9 +244,13 @@ export class SCMViewService implements ISCMViewService { ) { this.menus = instantiationService.createInstance(SCMMenus); + const explorerEnabledConfig = observableConfigValue('scm.repositories.explorer', false, this.configurationService); this.graphShowIncomingChangesConfig = observableConfigValue('scm.graph.showIncomingChanges', true, this.configurationService); this.graphShowOutgoingChangesConfig = observableConfigValue('scm.graph.showOutgoingChanges', true, this.configurationService); this.selectionModeConfig = observableConfigValue('scm.repositories.selectionMode', ISCMRepositorySelectionMode.Single, this.configurationService); + this.explorerEnabledConfig = derived(reader => { + return explorerEnabledConfig.read(reader) === true && this.selectionModeConfig.read(reader) === ISCMRepositorySelectionMode.Single; + }); try { this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, '')); diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 904fde6cf7b..7510400c264 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -20,6 +20,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { IResourceNode, ResourceTree } from '../../../../base/common/resourceTree.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { SCMArtifactGroupTreeElement, SCMArtifactTreeElement } from '../common/artifact.js'; export function isSCMViewService(element: unknown): element is ISCMViewService { return Array.isArray((element as ISCMViewService).repositories) && Array.isArray((element as ISCMViewService).visibleRepositories); @@ -65,6 +66,14 @@ export function isSCMHistoryItemChangeNode(element: unknown): element is IResour return ResourceTree.isResourceNode(element) && isSCMHistoryItemViewModelTreeElement(element.context); } +export function isSCMArtifactGroupTreeElement(element: unknown): element is SCMArtifactGroupTreeElement { + return (element as SCMArtifactGroupTreeElement).type === 'artifactGroup'; +} + +export function isSCMArtifactTreeElement(element: unknown): element is SCMArtifactTreeElement { + return (element as SCMArtifactTreeElement).type === 'artifact'; +} + const compareActions = (a: IAction, b: IAction) => { if (a instanceof MenuItemAction && b instanceof MenuItemAction) { return a.id === b.id && a.enabled === b.enabled && a.hideActions?.isHidden === b.hideActions?.isHidden; diff --git a/src/vs/workbench/contrib/scm/common/artifact.ts b/src/vs/workbench/contrib/scm/common/artifact.ts new file mode 100644 index 00000000000..de2b5ec2f60 --- /dev/null +++ b/src/vs/workbench/contrib/scm/common/artifact.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../base/common/uri.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { ISCMRepository } from './scm.js'; + +export interface ISCMArtifactProvider { + provideArtifactGroups(): Promise; + provideArtifacts(group: string): Promise; +} + +export interface ISCMArtifactGroup { + readonly id: string; + readonly name: string; + readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; +} + +export interface ISCMArtifact { + readonly id: string; + readonly name: string; + readonly description?: string; +} + +export interface SCMArtifactGroupTreeElement { + readonly repository: ISCMRepository; + readonly artifactGroup: ISCMArtifactGroup; + readonly type: 'artifactGroup'; +} + +export interface SCMArtifactTreeElement { + readonly repository: ISCMRepository; + readonly group: ISCMArtifactGroup; + readonly artifact: ISCMArtifact; + readonly type: 'artifact'; +} diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 7ee5b227a34..eb0ce02c857 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -16,6 +16,7 @@ import { ResourceTree } from '../../../../base/common/resourceTree.js'; import { ISCMHistoryProvider } from './history.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IObservable } from '../../../../base/common/observable.js'; +import { ISCMArtifactProvider } from './artifact.js'; export const VIEWLET_ID = 'workbench.view.scm'; export const VIEW_PANE_ID = 'workbench.scm'; @@ -85,6 +86,7 @@ export interface ISCMProvider extends IDisposable { readonly contextValue: IObservable; readonly count: IObservable; readonly commitTemplate: IObservable; + readonly artifactProvider: IObservable; readonly historyProvider: IObservable; readonly acceptInputCommand?: Command; readonly actionButton: IObservable; @@ -227,6 +229,7 @@ export interface ISCMViewService { readonly menus: ISCMMenus; readonly selectionModeConfig: IObservable; + readonly explorerEnabledConfig: IObservable; readonly graphShowIncomingChangesConfig: IObservable; readonly graphShowOutgoingChangesConfig: IObservable; diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 06f909eac18..4792c090099 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -204,6 +204,18 @@ const apiMenus: IAPIMenu[] = [ description: localize('menus.historyItemRefContext', "The Source Control history item reference context menu"), proposed: 'contribSourceControlHistoryItemMenu' }, + { + key: 'scm/artifactGroup/context', + id: MenuId.SCMArtifactGroupContext, + description: localize('menus.artifactGroupContext', "The Source Control artifact group context menu"), + proposed: 'contribSourceControlArtifactGroupMenu' + }, + { + key: 'scm/artifact/context', + id: MenuId.SCMArtifactContext, + description: localize('menus.artifactContext', "The Source Control artifact context menu"), + proposed: 'contribSourceControlArtifactMenu' + }, { key: 'statusBar/remoteIndicator', id: MenuId.StatusBarRemoteIndicatorMenu, diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlArtifactGroupMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlArtifactGroupMenu.d.ts new file mode 100644 index 00000000000..33686d7476e --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribSourceControlArtifactGroupMenu.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `scm/artifactGroup/context`-menu contribution point +// https://github.com/microsoft/vscode/issues/253665 diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlArtifactMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlArtifactMenu.d.ts new file mode 100644 index 00000000000..68fc4940231 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribSourceControlArtifactMenu.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `scm/artifact/context`-menu contribution point +// https://github.com/microsoft/vscode/issues/253665 diff --git a/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts new file mode 100644 index 00000000000..fcef4a44c43 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/253665 + + export interface SourceControl { + artifactProvider?: SourceControlArtifactProvider; + } + + export interface SourceControlArtifactProvider { + readonly onDidChangeArtifacts: Event; + + provideArtifactGroups(token: CancellationToken): ProviderResult; + provideArtifacts(group: string, token: CancellationToken): ProviderResult; + } + + export interface SourceControlArtifactGroup { + readonly id: string; + readonly name: string; + readonly icon?: IconPath; + } + + export interface SourceControlArtifact { + readonly id: string; + readonly name: string; + readonly description?: string; + } +} From 3bb2ff6262ea3f52723f14bdd1ce533872b430cb Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:37:54 -0700 Subject: [PATCH 1890/4355] More plumbing through of session resources instead of ids --- .../chat/browser/actions/chatMoveActions.ts | 8 +- .../browser/actions/chatSessionActions.ts | 32 ++++--- .../agentSessions/agentSessionsView.ts | 11 --- .../chatMultiDiffContentPart.ts | 3 +- .../contrib/chat/browser/chatEditor.ts | 5 +- .../contrib/chat/browser/chatEditorInput.ts | 89 +++++++++---------- .../chatSessions/chatSessionTracker.ts | 6 +- .../chat/browser/chatSessions/common.ts | 10 +-- .../chatSessions/localChatSessionsProvider.ts | 5 +- .../contrib/chat/browser/chatWidget.ts | 2 +- .../contrib/chat/common/chatModel.ts | 13 ++- .../contrib/chat/common/chatServiceImpl.ts | 6 +- .../contrib/chat/common/chatViewModel.ts | 5 ++ 13 files changed, 93 insertions(+), 102 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 74ce6a7775b..2b01846972b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -16,7 +16,6 @@ import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../serv import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { ChatViewId, IChatWidgetService } from '../chat.js'; import { ChatEditor, IChatEditorOptions } from '../chatEditor.js'; @@ -134,9 +133,8 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew widget.clear(); await widget.waitForReady(); - const resource = LocalChatSessionUri.forSession(sessionId); const options: IChatEditorOptions = { pinned: true, viewState, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } }; - await editorService.openEditor({ resource, options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: widget.viewModel.sessionResource, options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } async function moveToSidebar(accessor: ServicesAccessor): Promise { @@ -147,10 +145,10 @@ async function moveToSidebar(accessor: ServicesAccessor): Promise { const chatEditor = editorService.activeEditorPane; const chatEditorInput = chatEditor?.input; let view: ChatViewPane; - if (chatEditor instanceof ChatEditor && chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId) { + if (chatEditor instanceof ChatEditor && chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionResource) { await editorService.closeEditor({ editor: chatEditor.input, groupId: editorGroupService.activeGroup.id }); view = await viewsService.openView(ChatViewId) as ChatViewPane; - await view.loadSession(chatEditorInput.sessionId, chatEditor.getViewState()); + await view.loadSession(chatEditorInput.sessionResource, chatEditor.getViewState()); } else { view = await viewsService.openView(ChatViewId) as ChatViewPane; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 2e690df8315..16b26ff67be 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -264,24 +264,28 @@ export class OpenChatSessionInSidebarAction extends Action2 { } async run(accessor: ServicesAccessor, context?: IMarshalledChatSessionContext): Promise { - if (!context) { - return; - } - const editorService = accessor.get(IEditorService); const viewsService = accessor.get(IViewsService); const chatWidgetService = accessor.get(IChatWidgetService); const editorGroupsService = accessor.get(IEditorGroupsService); - const sessionId = context.session.id; - if (context.session.provider?.chatSessionType) { - // Check if this session is already open in another editor - const existingEditor = findExistingChatEditorByUri(context.session.resource, editorGroupsService); - if (existingEditor) { - await editorService.openEditor(existingEditor.editor, existingEditor.group); - return; - } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { - return; - } + + if (!context) { + return; + } + + if (context.session.provider.chatSessionType !== localChatSessionType) { + // We only allow local sessions to be opened in the side bar + return; + } + + // Check if this session is already open in another editor + // TODO: this feels strange. Should we prefer moving the editor to the sidebar instead? + const existingEditor = findExistingChatEditorByUri(context.session.resource, editorGroupsService); + if (existingEditor) { + await editorService.openEditor(existingEditor.editor, existingEditor.group); + return; + } else if (chatWidgetService.getWidgetBySessionId(context.session.id)) { + return; } // Open the chat view in the sidebar diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts index dbd451c1b4c..58cddb2fcdf 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsView.ts @@ -36,7 +36,6 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { findExistingChatEditorByUri, getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js'; import { ACTION_ID_OPEN_CHAT } from '../actions/chatActions.js'; import { IProgressService } from '../../../../../platform/progress/common/progress.js'; -import { LocalChatSessionUri } from '../../common/chatUri.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { assertReturnsDefined, upcast } from '../../../../../base/common/types.js'; @@ -132,16 +131,6 @@ export class AgentSessionsView extends ViewPane { return; } - if (session.resource.scheme !== LocalChatSessionUri.scheme) { - await this.openerService.open(session.resource, { - editorOptions: upcast({ - ...e.editorOptions, - title: { preferred: session.label } - }) - }); - return; - } - const existingSessionEditor = findExistingChatEditorByUri(session.resource, this.editorGroupsService); if (existingSessionEditor) { await existingSessionEditor.group.openEditor(existingSessionEditor.editor, e.editorOptions); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts index 5ab66217307..7bbf6a6f64d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMultiDiffContentPart.ts @@ -31,7 +31,6 @@ import { IChatMultiDiffData } from '../../common/chatService.js'; import { IChatRendererContent } from '../../common/chatViewModel.js'; import { ChatTreeItem } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { getChatSessionType } from '../chatSessions/common.js'; import { IChatContentPart } from './chatContentParts.js'; const $ = dom.$; @@ -141,7 +140,7 @@ export class ChatMultiDiffContentPart extends Disposable implements IChatContent let contextKeyService: IContextKeyService = this.contextKeyService; if (this.editorService.activeEditor instanceof ChatEditorInput) { contextKeyService = this.contextKeyService.createOverlay([ - [ChatContextKeys.sessionType.key, getChatSessionType(this.editorService.activeEditor)] + [ChatContextKeys.sessionType.key, this.editorService.activeEditor.getSessionType()] ]); marshalledUri = { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 6fe04cd1046..35a76c25048 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -30,7 +30,6 @@ import { IChatSessionsService, localChatSessionType } from '../common/chatSessio import { ChatAgentLocation, ChatModeKind } from '../common/constants.js'; import { clearChatEditor } from './actions/chatClear.js'; import { ChatEditorInput } from './chatEditorInput.js'; -import { getChatSessionType } from './chatSessions/common.js'; import { ChatWidget, IChatViewState } from './chatWidget.js'; export interface IChatEditorOptions extends IEditorOptions { @@ -182,7 +181,7 @@ export class ChatEditor extends EditorPane { override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { // Show loading indicator early for non-local sessions to prevent layout shifts let isContributedChatSession = false; - const chatSessionType = getChatSessionType(input); + const chatSessionType = input.getSessionType(); if (chatSessionType !== localChatSessionType) { const loadingMessage = nls.localize('chatEditor.loadingSession', "Loading..."); this.showLoadingInChatWidget(loadingMessage); @@ -221,7 +220,7 @@ export class ChatEditor extends EditorPane { const editorModel = await raceCancellationError(input.resolve(), token); if (!editorModel) { - throw new Error(`Failed to get model for chat editor. id: ${input.sessionId}`); + throw new Error(`Failed to get model for chat editor. resource: ${input.sessionResource}`); } // Hide loading state before updating model diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 835c5e3cd72..076582ee245 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -39,11 +39,19 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler private readonly inputCount: number; private readonly inputName: string; - private _sessionId: string | undefined; + private _sessionInfo: { readonly sessionId: string | undefined; readonly resource: URI } | undefined; + + /** + * Get the uri of the session this editor input is associated with. + * + * This should be preferred over using `resource` directly, as it handles cases where a chat editor becomes a session + */ + public get sessionResource(): URI | undefined { return this._sessionInfo?.resource; } + /** - * @deprecated The session id is only useful for local session. In most cases you should check the resource instead. + * @deprecated Use {@link sessionResource} instead. */ - public get sessionId() { return this._sessionId; } + public get sessionId(): string | undefined { return this._sessionInfo?.sessionId; } private hasCustomTitle: boolean = false; private cachedIcon: ThemeIcon | URI | undefined; @@ -78,9 +86,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler if (!parsed || typeof parsed !== 'number') { throw new Error('Invalid chat URI'); } - } - - if (resource.scheme === Schemas.vscodeLocalChatSession) { + } else if (resource.scheme === Schemas.vscodeLocalChatSession) { const parsed = LocalChatSessionUri.parse(resource); if (!parsed?.sessionId) { throw new Error('Invalid chat session URI'); @@ -88,13 +94,15 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler if (parsed.chatSessionType !== localChatSessionType) { throw new Error('Chat session URI must be of local chat session type'); } - this._sessionId = parsed.sessionId; + this._sessionInfo = { resource, sessionId: parsed.sessionId }; + } else { + this._sessionInfo = { resource, sessionId: undefined }; } // Check if we already have a custom title for this session - const hasExistingCustomTitle = this._sessionId && ( - this.chatService.getSession(this._sessionId)?.title || - this.chatService.getPersistedSessionTitle(this._sessionId)?.trim() + const hasExistingCustomTitle = this._sessionInfo?.sessionId && ( + this.chatService.getSession(this._sessionInfo?.sessionId)?.title || + this.chatService.getPersistedSessionTitle(this._sessionInfo?.sessionId)?.trim() ); this.hasCustomTitle = Boolean(hasExistingCustomTitle); @@ -153,28 +161,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return false; } - if (this.resource.scheme === Schemas.vscodeChatEditor && otherInput.resource.scheme === Schemas.vscodeChatEditor) { - return this._sessionId === otherInput._sessionId; - } - - return isEqual(this.resource, otherInput.resource); - } - - isForSession(sessionUri: URI): boolean { - // Special case where we are a chat editor that has been linked to a local session. - // In this case, check if our id matches. - if (this.resource.scheme === Schemas.vscodeChatEditor) { - if (sessionUri.scheme === Schemas.vscodeLocalChatSession) { - const parsed = LocalChatSessionUri.parse(sessionUri); - if (this._sessionId === parsed?.sessionId) { - return true; - } - } - - return false; - } - - return isEqual(this.resource, sessionUri); + return isEqual(this.sessionResource, otherInput.sessionResource); } override get typeId(): string { @@ -189,15 +176,15 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } // If we have a sessionId but no resolved model, try to get the title from persisted sessions - if (this._sessionId) { + if (this._sessionInfo?.sessionId) { // First try the active session registry - const existingSession = this.chatService.getSession(this._sessionId); + const existingSession = this.chatService.getSession(this._sessionInfo?.sessionId); if (existingSession?.title) { return existingSession.title; } // If not in active registry, try persisted session data - const persistedTitle = this.chatService.getPersistedSessionTitle(this._sessionId); + const persistedTitle = this.chatService.getPersistedSessionTitle(this._sessionInfo?.sessionId); if (persistedTitle && persistedTitle.trim()) { // Only use non-empty persisted titles return persistedTitle; } @@ -217,8 +204,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override getTitle(verbosity?: Verbosity): string { const name = this.getName(); if (verbosity === Verbosity.LONG) { // Verbosity LONG is used for tooltips - const sessionType = this.getSessionType(); - const sessionTypeDisplayName = this.getSessionTypeDisplayName(sessionType); + const sessionTypeDisplayName = this.getSessionTypeDisplayName(); if (sessionTypeDisplayName) { return `${name} | ${sessionTypeDisplayName}`; } @@ -226,7 +212,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return name; } - private getSessionTypeDisplayName(sessionType: string): string | undefined { + private getSessionTypeDisplayName(): string | undefined { + const sessionType = this.getSessionType(); if (sessionType === localChatSessionType) { return; } @@ -259,6 +246,9 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return undefined; } + /** + * Returns chat session type from a URI, or {@linkcode localChatSessionType} if not specified or cannot be determined. + */ public getSessionType(): string { if (this.resource.scheme === Schemas.vscodeChatEditor || this.resource.scheme === Schemas.vscodeLocalChatSession) { return localChatSessionType; @@ -271,10 +261,11 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const searchParams = new URLSearchParams(this.resource.query); const chatSessionType = searchParams.get('chatSessionType'); const inputType = chatSessionType ?? this.resource.authority; + if (this.resource.scheme !== Schemas.vscodeChatEditor && this.resource.scheme !== Schemas.vscodeLocalChatSession) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); - } else if (typeof this._sessionId === 'string') { - this.model = await this.chatService.getOrRestoreSession(this._sessionId) + } else if (this._sessionInfo?.sessionId) { + this.model = await this.chatService.getOrRestoreSession(this._sessionInfo.sessionId) ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); } else if (!this.options.target) { this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType, inputType: inputType }); @@ -286,7 +277,10 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler return null; } - this._sessionId = this.model.sessionId; + this._sessionInfo = { + sessionId: this.model.sessionId, + resource: this.model.sessionResource, + }; this._register(this.model.onDidChange((e) => { // When a custom title is set, we no longer need the numeric count if (e && e.kind === 'setCustomTitle' && !this.hasCustomTitle) { @@ -324,8 +318,9 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override dispose(): void { super.dispose(); - if (this._sessionId) { - this.chatService.clearSession(this._sessionId); + + if (this._sessionInfo?.sessionId) { + this.chatService.clearSession(this._sessionInfo.sessionId); } } } @@ -383,14 +378,14 @@ export namespace ChatEditorUri { } interface ISerializedChatEditorInput { - options: IChatEditorOptions; - sessionId: string; - resource: URI; + readonly options: IChatEditorOptions; + readonly sessionId: string; + readonly resource: URI; } export class ChatEditorInputSerializer implements IEditorSerializer { canSerialize(input: EditorInput): input is ChatEditorInput & { readonly sessionId: string } { - return input instanceof ChatEditorInput && typeof input.sessionId === 'string'; + return input instanceof ChatEditorInput && !!input.sessionId; } serialize(input: EditorInput): string | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index 608b2b65ebe..e43a6c54d23 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -13,7 +13,7 @@ import { IChatService } from '../../common/chatService.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { LocalChatSessionUri } from '../../common/chatUri.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; +import { ChatSessionItemWithProvider, isChatSession } from './common.js'; export class ChatSessionTracker extends Disposable { private readonly _onDidChangeEditors = this._register(new Emitter<{ sessionType: string; kind: GroupModelChangeKind }>()); @@ -47,7 +47,7 @@ export class ChatSessionTracker extends Disposable { } const editor = e.editor as ChatEditorInput; - const sessionType = getChatSessionType(editor); + const sessionType = editor.getSessionType(); this.chatSessionsService.notifySessionItemsChanged(sessionType); @@ -61,7 +61,7 @@ export class ChatSessionTracker extends Disposable { this.editorGroupsService.groups.forEach(group => { group.editors.forEach(editor => { - if (editor instanceof ChatEditorInput && getChatSessionType(editor) === sessionType) { + if (editor instanceof ChatEditorInput && editor.getSessionType() === sessionType) { localEditors.push(editor); } }); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts index fe8720e7165..e067facaf32 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/common.ts @@ -5,6 +5,7 @@ import { fromNow } from '../../../../../base/common/date.js'; import { Schemas } from '../../../../../base/common/network.js'; +import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { EditorInput } from '../../../../common/editor/editorInput.js'; import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; @@ -44,20 +45,13 @@ export function isChatSession(schemes: readonly string[], editor?: EditorInput): return true; } -/** - * Returns chat session type from a URI, or {@linkcode localChatSessionType} if not specified or cannot be determined. - */ -export function getChatSessionType(editor: ChatEditorInput): string { - return editor.getSessionType(); -} - /** * Find existing chat editors that have the same session URI (for external providers) */ export function findExistingChatEditorByUri(sessionUri: URI, editorGroupsService: IEditorGroupsService): { editor: ChatEditorInput; group: IEditorGroup } | undefined { for (const group of editorGroupsService.groups) { for (const editor of group.editors) { - if (editor instanceof ChatEditorInput && editor.isForSession(sessionUri)) { + if (editor instanceof ChatEditorInput && isEqual(editor.sessionResource, sessionUri)) { return { editor, group }; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 9c93622ec49..191fa4f7812 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -19,7 +19,7 @@ import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSes import { ChatAgentLocation } from '../../common/constants.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { ChatSessionItemWithProvider, getChatSessionType, isChatSession } from './common.js'; +import { ChatSessionItemWithProvider, isChatSession } from './common.js'; export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution { static readonly ID = 'workbench.contrib.localChatSessionsProvider'; @@ -158,8 +158,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio return false; } - const sessionType = getChatSessionType(editor); - return sessionType === localChatSessionType; + return editor.getSessionType() === localChatSessionType; } private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2db00c9ee81..cbd8cabe7fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -2271,7 +2271,7 @@ export class ChatWidget extends Disposable implements IChatWidget { throw new Error('Call render() before setModel()'); } - if (model.sessionId === this.viewModel?.sessionId) { + if (isEqual(model.sessionResource, this.viewModel?.sessionResource)) { return; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 09050924f13..497694140f8 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -29,6 +29,7 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, import { IChatEditingService, IChatEditingSession } from './chatEditingService.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from './chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatMultiDiffData, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatSessionContext, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, isIUsedContext } from './chatService.js'; +import { LocalChatSessionUri } from './chatUri.js'; import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry } from './chatVariableEntries.js'; import { ChatAgentLocation, ChatModeKind } from './constants.js'; @@ -1058,6 +1059,7 @@ export interface IChatModel extends IDisposable { readonly onDidDispose: Event; readonly onDidChange: Event; readonly sessionId: string; + readonly sessionResource: URI; readonly initialLocation: ChatAgentLocation; readonly title: string; readonly hasCustomTitle: boolean; @@ -1360,11 +1362,16 @@ export class ChatModel extends Disposable implements IChatModel { // TODO to be clear, this is not the same as the id from the session object, which belongs to the provider. // It's easier to be able to identify this model before its async initialization is complete - private _sessionId: string; + private readonly _sessionId: string; get sessionId(): string { return this._sessionId; } + private readonly _sessionResource: URI; + get sessionResource(): URI { + return this._sessionResource; + } + get requestInProgress(): boolean { return this.requestInProgressObs.get(); } @@ -1462,7 +1469,7 @@ export class ChatModel extends Disposable implements IChatModel { constructor( initialData: ISerializableChatData | IExportableChatData | undefined, - initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; inputType?: string }, + initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; inputType?: string; resource?: URI }, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @@ -1476,6 +1483,8 @@ export class ChatModel extends Disposable implements IChatModel { this._isImported = (!!initialData && !isValid) || (initialData?.isImported ?? false); this._sessionId = (isValid && initialData.sessionId) || generateUuid(); + this._sessionResource = initialModelProps.resource ?? LocalChatSessionUri.forSession(this._sessionId); + this._requests = initialData ? this._deserialize(initialData) : []; this._creationDate = (isValid && initialData.creationDate) || Date.now(); this._lastMessageDate = (isValid && initialData.lastMessageDate) || this._creationDate; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 59879f0c3c9..cc9ca0e21c5 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -306,7 +306,7 @@ export class ChatService extends Disposable implements IChatService { const title = session.title || localize('newChat', "New Chat"); return { sessionId: session.sessionId, - sessionResource: LocalChatSessionUri.forSession(session.sessionId), + sessionResource: session.sessionResource, title, lastMessageDate: session.lastMessageDate, isActive: true, @@ -337,7 +337,7 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(undefined, location, isGlobalEditingSession, token, options); } - private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, options?: { canUseTools?: boolean; inputType?: string }): ChatModel { + private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, options?: { sessionResource?: URI; canUseTools?: boolean; inputType?: string }): ChatModel { const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, canUseTools: options?.canUseTools ?? true, inputType: options?.inputType }); if (location === ChatAgentLocation.Chat) { model.startEditingSession(isGlobalEditingSession); @@ -476,7 +476,7 @@ export class ChatService extends Disposable implements IChatService { const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools - const model = this._startSession(undefined, location, true, CancellationToken.None, { canUseTools: false, inputType: chatSessionType }); + const model = this._startSession(undefined, location, true, CancellationToken.None, { sessionResource: chatSessionResource, canUseTools: false, inputType: chatSessionType }); model.setContributedChatSession({ chatSessionType: chatSessionType, chatSessionId: chatSessionResource.toString(), diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index fb8f5eeae9d..9e4d1e23824 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -64,6 +64,7 @@ export interface IChatSetCheckpointEvent { export interface IChatViewModel { readonly model: IChatModel; readonly sessionId: string; + readonly sessionResource: URI; readonly onDidDisposeModel: Event; readonly onDidChange: Event; readonly requestInProgress: boolean; @@ -262,6 +263,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { return this._model.sessionId; } + get sessionResource(): URI { + return this._model.sessionResource; + } + get requestInProgress(): boolean { return this._model.requestInProgress; } From 1393bde719465678fdebe71a7721bfa29ee85d05 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 30 Oct 2025 20:45:25 +0100 Subject: [PATCH 1891/4355] Revert "Debugging web server + edge shows odd coloring in chat input (fix #273973)" (#274148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Debugging web server + edge shows odd coloring in chat input (fix #27…" This reverts commit afd4a5ee0cda0902c789df5deff9effd7736545f. --- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 95b268a1c86..828972f3e7c 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1280,7 +1280,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor, .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor .monaco-editor-background { - background-color: var(--vscode-input-background) !important; + background-color: var(--vscode-input-background); } .interactive-session .interactive-input-part.editing .chat-input-container .chat-editor-container .monaco-editor, From e1805b9c01da17e2c2fa57bdc4503dea922a7078 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 30 Oct 2025 13:26:33 -0700 Subject: [PATCH 1892/4355] edits: fix duplicate lines in edit files (#274195) - Fix external edits being applied twice - Compute minimal edits for text document changes --- .../chatEditingModifiedDocumentEntry.ts | 26 +++++++++---------- .../chatEditingTextModelChangeService.ts | 6 ++++- .../contrib/chat/common/chatModel.ts | 4 ++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts index de10bafe410..f28e76fbb88 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedDocumentEntry.ts @@ -9,11 +9,14 @@ import { ITransaction, autorun, transaction } from '../../../../../base/common/o import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { TextEdit as EditorTextEdit } from '../../../../../editor/common/core/edits/textEdit.js'; +import { StringText } from '../../../../../editor/common/core/text/abstractText.js'; import { Location, TextEdit } from '../../../../../editor/common/languages.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { SingleModelEditStackElement } from '../../../../../editor/common/model/editStack.js'; import { createTextBufferFactoryFromSnapshot } from '../../../../../editor/common/model/textModel.js'; +import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { localize } from '../../../../../nls.js'; @@ -97,6 +100,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie @IUndoRedoService undoRedoService: IUndoRedoService, @IInstantiationService instantiationService: IInstantiationService, @IAiEditTelemetryService aiEditTelemetryService: IAiEditTelemetryService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, ) { super( resourceRef.object.textEditorModel.uri, @@ -306,19 +310,15 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie } async computeEditsFromSnapshots(beforeSnapshot: string, afterSnapshot: string): Promise<(TextEdit | ICellEditOperation)[]> { - // Simple full-content replacement approach - // This is similar to how streaming edits work - we just replace the entire content - const endLine = beforeSnapshot.split(/\r?\n/).length; - - return [{ - range: { - startLineNumber: 1, - startColumn: 1, - endLineNumber: endLine, - endColumn: beforeSnapshot.split(/\r?\n/)[endLine - 1]?.length + 1 || 1 - }, - text: afterSnapshot - }]; + const stringEdit = await this._editorWorkerService.computeStringEditFromDiff( + beforeSnapshot, + afterSnapshot, + { maxComputationTimeMs: 5000 }, + 'advanced' + ); + + const editorTextEdit = EditorTextEdit.fromStringEdit(stringEdit, new StringText(beforeSnapshot)); + return editorTextEdit.replacements.slice(); } async save(): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts index 13e2816d6d5..de05653c65c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelChangeService.ts @@ -345,11 +345,15 @@ export class ChatEditingTextModelChangeService extends Disposable { private _mirrorEdits(event: IModelContentChangedEvent) { const edit = offsetEditFromContentChanges(event.changes); + const isExternalEdit = this._isExternalEditInProgress?.(); - if (this._isEditFromUs || this._isExternalEditInProgress?.()) { + if (this._isEditFromUs || isExternalEdit) { const e_sum = this._originalToModifiedEdit; const e_ai = edit; this._originalToModifiedEdit = e_sum.compose(e_ai); + if (isExternalEdit) { + this._updateDiffInfoSeq(); + } } else { // e_ai diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 09050924f13..533376c2d14 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -645,6 +645,7 @@ export class Response extends AbstractResponse implements IDisposable { let found = false; const groupKind = progress.kind === 'textEdit' && !notebookUri ? 'textEditGroup' : 'notebookEditGroup'; const edits: any = groupKind === 'textEditGroup' ? progress.edits : progress.edits.map(edit => TextEdit.isTextEdit(edit) ? { uri: progress.uri, edit } : edit); + const isExternalEdit = progress.isExternalEdit; for (let i = 0; !found && i < this._responseParts.length; i++) { const candidate = this._responseParts[i]; if (candidate.kind === groupKind && !candidate.done && isEqual(candidate.uri, uri)) { @@ -658,7 +659,8 @@ export class Response extends AbstractResponse implements IDisposable { kind: groupKind, uri, edits: groupKind === 'textEditGroup' ? [edits] : edits, - done: progress.done + done: progress.done, + isExternalEdit, }); } this._updateRepr(quiet); From 7e1fd498bb01cf9350f684db063d75125ee2051a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Oct 2025 13:30:22 -0700 Subject: [PATCH 1893/4355] Some no explicit any in contrib/debug/ (#273469) * Reduce explicit any * More no explicit any * CCR thank you * Fix * Fix * Fix context computation --- eslint.config.js | 5 -- .../contrib/debug/browser/breakpointWidget.ts | 2 +- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 84 +++++++++++-------- .../workbench/contrib/debug/common/debug.ts | 46 +++++----- .../contrib/debug/common/debugModel.ts | 24 +++--- .../contrib/debug/common/debugger.ts | 3 +- .../debug/test/browser/callStack.test.ts | 18 ++-- .../contrib/debug/test/node/debugger.test.ts | 6 +- 9 files changed, 98 insertions(+), 94 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 44e229046e7..e9ef3a95ef4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -824,9 +824,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts', 'src/vs/workbench/contrib/customEditor/browser/customEditors.ts', 'src/vs/workbench/contrib/customEditor/common/customEditor.ts', - 'src/vs/workbench/contrib/debug/browser/breakpointWidget.ts', - 'src/vs/workbench/contrib/debug/browser/breakpointsView.ts', - 'src/vs/workbench/contrib/debug/browser/callStackView.ts', 'src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts', 'src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts', 'src/vs/workbench/contrib/debug/browser/debugCommands.ts', @@ -842,8 +839,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/debug/browser/variablesView.ts', 'src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts', 'src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts', - 'src/vs/workbench/contrib/debug/common/debug.ts', - 'src/vs/workbench/contrib/debug/common/debugModel.ts', 'src/vs/workbench/contrib/debug/common/debugger.ts', 'src/vs/workbench/contrib/debug/common/replModel.ts', 'src/vs/workbench/contrib/debug/test/common/mockDebug.ts', diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 07b92710f72..80ab07f1e33 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -549,7 +549,7 @@ class CloseBreakpointWidgetCommand extends EditorCommand { }); } - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const debugContribution = editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID); if (debugContribution) { // if focus is in outer editor we need to use the debug contribution to close diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 9f52557a0ae..17b363d9b5e 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -580,9 +580,7 @@ class BreakpointsRenderer implements IListRenderer(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession); + const onFocusChange = Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession); this._register(onFocusChange(async () => { if (this.ignoreFocusStackFrameEvent) { return; @@ -451,7 +463,7 @@ export class CallStackView extends ViewPane { private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; - let overlay: [string, any][] = []; + let overlay: [string, ContextKeyValue][] = []; if (isDebugSession(element)) { overlay = getSessionContextOverlay(element); } else if (element instanceof Thread) { @@ -511,7 +523,7 @@ interface IStackFrameTemplateData { elementDisposables: DisposableStore; } -function getSessionContextOverlay(session: IDebugSession): [string, any][] { +function getSessionContextOverlay(session: IDebugSession): [string, ContextKeyValue][] { return [ [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'session'], [CONTEXT_CALLSTACK_SESSION_IS_ATTACH.key, isSessionAttach(session)], @@ -627,7 +639,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer, _index: number, templateData: IThreadTemplateData): void { templateData.elementDisposable.clear(); } @@ -697,7 +709,7 @@ class ThreadsRenderer implements ICompressibleTreeRenderer { @@ -1008,7 +1020,7 @@ class CallStackDataSource implements IAsyncDataSource> { - let callStack: any[] = thread.getCallStack(); + let callStack: Array = thread.getCallStack(); if (!callStack || !callStack.length) { await thread.fetchCallStack(); callStack = thread.getCallStack(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 385ce2bfff9..286b0c0f315 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -8,7 +8,7 @@ import { VSBuffer } from '../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Color } from '../../../../base/common/color.js'; import { Event } from '../../../../base/common/event.js'; -import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; +import { IJSONSchema, IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import severity from '../../../../base/common/severity.js'; import { URI, UriComponents, URI as uri } from '../../../../base/common/uri.js'; @@ -459,7 +459,7 @@ export interface IDebugSession extends ITreeElement, IDisposable { scopes(frameId: number, threadId: number): Promise; variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; evaluate(expression: string, frameId?: number, context?: string, location?: IDebugEvaluatePosition): Promise; - customRequest(request: string, args: any): Promise; + customRequest(request: string, args: unknown): Promise; cancel(progressId: string): Promise; disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise; readMemory(memoryReference: string, offset: number, count: number): Promise; @@ -538,14 +538,14 @@ export interface IThread extends ITreeElement { */ readonly stopped: boolean; - next(granularity?: DebugProtocol.SteppingGranularity): Promise; - stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise; - stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise; - stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise; - continue(): Promise; - pause(): Promise; - terminate(): Promise; - reverseContinue(): Promise; + next(granularity?: DebugProtocol.SteppingGranularity): Promise; + stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise; + stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise; + stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise; + continue(): Promise; + pause(): Promise; + terminate(): Promise; + reverseContinue(): Promise; } export interface IScope extends IExpressionContainer { @@ -567,7 +567,7 @@ export interface IStackFrame extends ITreeElement { getScopes(): Promise; getMostSpecificScopes(range: IRange): Promise>; forgetScopes(): void; - restart(): Promise; + restart(): Promise; toString(): string; openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise; equals(other: IStackFrame): boolean; @@ -630,7 +630,7 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endLineNumber?: number; readonly column?: number; readonly endColumn?: number; - readonly adapterData: any; + readonly adapterData: unknown; readonly sessionAgnosticData: { lineNumber: number; column: number | undefined }; /** An ID of the breakpoint that triggers this breakpoint. */ readonly triggeredBy?: string; @@ -865,7 +865,7 @@ export interface IConfig extends IEnvConfig { // internals __configurationTarget?: ConfigurationTarget; __sessionId?: string; - __restart?: any; + __restart?: unknown; __autoAttach?: boolean; port?: number; // TODO } @@ -886,7 +886,7 @@ export interface IDebugAdapter extends IDisposable { startSession(): Promise; sendMessage(message: DebugProtocol.ProtocolMessage): void; sendResponse(response: DebugProtocol.Response): void; - sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): number; + sendRequest(command: string, args: unknown, clb: (result: DebugProtocol.Response) => void, timeout?: number): number; stopSession(): Promise; } @@ -952,8 +952,8 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut languages?: string[]; // debug configuration support - configurationAttributes?: any; - initialConfigurations?: any[]; + configurationAttributes?: Record; + initialConfigurations?: unknown[]; configurationSnippets?: IJSONSchemaSnippet[]; variables?: { [key: string]: string }; when?: string; @@ -1036,7 +1036,7 @@ export interface IConfigurationManager { registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void; - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any, token: CancellationToken): Promise; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: unknown, token: CancellationToken): Promise; } export enum DebuggerString { @@ -1207,7 +1207,7 @@ export interface IDebugService { * Removes all breakpoints. If id is passed only removes the breakpoint associated with that id. * Notifies debug adapter of breakpoint changes. */ - removeBreakpoints(id?: string): Promise; + removeBreakpoints(id?: string): Promise; /** * Adds a new function breakpoint for the given name. @@ -1268,12 +1268,12 @@ export interface IDebugService { * Sends all breakpoints to the passed session. * If session is not passed, sends all breakpoints to each session. */ - sendAllBreakpoints(session?: IDebugSession): Promise; + sendAllBreakpoints(session?: IDebugSession): Promise; /** * Sends breakpoints of the given source to the passed session. */ - sendBreakpoints(modelUri: uri, sourceModified?: boolean, session?: IDebugSession): Promise; + sendBreakpoints(modelUri: uri, sourceModified?: boolean, session?: IDebugSession): Promise; /** * Adds a new watch expression and evaluates it against the debug adapter. @@ -1308,12 +1308,12 @@ export interface IDebugService { /** * Restarts a session or creates a new one if there is no active session. */ - restartSession(session: IDebugSession, restartData?: any): Promise; + restartSession(session: IDebugSession, restartData?: unknown): Promise; /** * Stops the session. If no session is specified then all sessions are stopped. */ - stopSession(session: IDebugSession | undefined, disconnect?: boolean, suspend?: boolean): Promise; + stopSession(session: IDebugSession | undefined, disconnect?: boolean, suspend?: boolean): Promise; /** * Makes unavailable all sources with the passed uri. Source will appear as grayed out in callstack view. @@ -1346,7 +1346,7 @@ export const enum BreakpointWidgetContext { export interface IDebugEditorContribution extends editorCommon.IEditorContribution { showHover(range: Position, focus: boolean): Promise; - addLaunchConfiguration(): Promise; + addLaunchConfiguration(): Promise; closeExceptionWidget(): void; } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index b9f3442157d..4a4a0fdf9f4 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -394,7 +394,7 @@ export class Variable extends ExpressionContainer implements IExpression { return this.threadId; } - async setVariable(value: string, stackFrame: IStackFrame): Promise { + async setVariable(value: string, stackFrame: IStackFrame): Promise { if (!this.session) { return; } @@ -717,35 +717,35 @@ export class Thread implements IThread { return Promise.resolve(undefined); } - next(granularity?: DebugProtocol.SteppingGranularity): Promise { + next(granularity?: DebugProtocol.SteppingGranularity): Promise { return this.session.next(this.threadId, granularity); } - stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise { + stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise { return this.session.stepIn(this.threadId, undefined, granularity); } - stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise { + stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise { return this.session.stepOut(this.threadId, granularity); } - stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise { + stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise { return this.session.stepBack(this.threadId, granularity); } - continue(): Promise { + continue(): Promise { return this.session.continue(this.threadId); } - pause(): Promise { + pause(): Promise { return this.session.pause(this.threadId); } - terminate(): Promise { + terminate(): Promise { return this.session.terminateThreads([this.threadId]); } - reverseContinue(): Promise { + reverseContinue(): Promise { return this.session.reverseContinue(this.threadId); } } @@ -987,14 +987,14 @@ export interface IBreakpointOptions extends IBaseBreakpointOptions { uri: uri; lineNumber: number; column: number | undefined; - adapterData: any; + adapterData: unknown; triggeredBy: string | undefined; } export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private sessionsDidTrigger?: Set; private readonly _uri: uri; - private _adapterData: any; + private _adapterData: unknown; private _lineNumber: number; private _column: number | undefined; public triggeredBy: string | undefined; @@ -1064,7 +1064,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return super.message; } - get adapterData(): any { + get adapterData(): unknown { return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData; } diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 55453c8b282..7461a886bf0 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -241,10 +241,9 @@ export class Debugger implements IDebugger, IDebuggerMetadata { } // fill in the default configuration attributes shared by all adapters. - return Object.keys(this.debuggerContribution.configurationAttributes).map(request => { + return Object.entries(this.debuggerContribution.configurationAttributes).map(([request, attributes]) => { const definitionId = `${this.type}:${request}`; const platformSpecificDefinitionId = `${this.type}:${request}:platform`; - const attributes: IJSONSchema = this.debuggerContribution.configurationAttributes[request]; const defaultRequired = ['name', 'type', 'request']; attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired; attributes.additionalProperties = false; diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 2d0dd6821ab..77d571e4df9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -415,19 +415,19 @@ suite('Debug - CallStack', () => { model.addSession(session); const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session); let context = getContext(firstStackFrame); - assert.strictEqual(context.sessionId, firstStackFrame.thread.session.getId()); - assert.strictEqual(context.threadId, firstStackFrame.thread.getId()); - assert.strictEqual(context.frameId, firstStackFrame.getId()); + assert.strictEqual(context?.sessionId, firstStackFrame.thread.session.getId()); + assert.strictEqual(context?.threadId, firstStackFrame.thread.getId()); + assert.strictEqual(context?.frameId, firstStackFrame.getId()); context = getContext(secondStackFrame.thread); - assert.strictEqual(context.sessionId, secondStackFrame.thread.session.getId()); - assert.strictEqual(context.threadId, secondStackFrame.thread.getId()); - assert.strictEqual(context.frameId, undefined); + assert.strictEqual(context?.sessionId, secondStackFrame.thread.session.getId()); + assert.strictEqual(context?.threadId, secondStackFrame.thread.getId()); + assert.strictEqual(context?.frameId, undefined); context = getContext(session); - assert.strictEqual(context.sessionId, session.getId()); - assert.strictEqual(context.threadId, undefined); - assert.strictEqual(context.frameId, undefined); + assert.strictEqual(context?.sessionId, session.getId()); + assert.strictEqual(context?.threadId, undefined); + assert.strictEqual(context?.frameId, undefined); let contributedContext = getContextForContributedActions(firstStackFrame); assert.strictEqual(contributedContext, firstStackFrame.source.raw.path); diff --git a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 73f5b3a2396..379a813f5e7 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -6,7 +6,7 @@ import assert from 'assert'; import { join, normalize } from '../../../../../base/common/path.js'; import * as platform from '../../../../../base/common/platform.js'; -import { IDebugAdapterExecutable, IConfig, IDebugSession, IAdapterManager } from '../../common/debug.js'; +import { IDebugAdapterExecutable, IConfig, IDebugSession, IAdapterManager, IDebuggerContribution } from '../../common/debug.js'; import { Debugger } from '../../common/debugger.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -20,7 +20,7 @@ suite('Debug - Debugger', () => { let _debugger: Debugger; const extensionFolderPath = '/a/b/c/'; - const debuggerContribution = { + const debuggerContribution: IDebuggerContribution = { type: 'mock', label: 'Mock Debug', program: './out/mock/mockDebug.js', @@ -157,7 +157,7 @@ suite('Debug - Debugger', () => { const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); - assert.strictEqual(ae!.command, join(extensionFolderPath, debuggerContribution.program)); + assert.strictEqual(ae!.command, join(extensionFolderPath, debuggerContribution.program!)); assert.deepStrictEqual(ae!.args, debuggerContribution.args); }); From 105d8dd5832f611d7472ec453b575e97e3a97ee9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:55:23 +0000 Subject: [PATCH 1894/4355] =?UTF-8?q?SCM=20-=20=F0=9F=92=84some=20follow-u?= =?UTF-8?q?p=20cleanup=20(#274193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/git/src/artifactProvider.ts | 12 +++++++++--- extensions/git/src/commands.ts | 1 - src/vs/workbench/api/browser/mainThreadSCM.ts | 15 +++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index 4344d60d929..355a8641b9d 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri } from 'vscode'; -import { IDisposable } from './util'; +import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable } from 'vscode'; +import { dispose, IDisposable } from './util'; import { Repository } from './repository'; export class GitArtifactProvider implements SourceControlArtifactProvider, IDisposable { @@ -12,6 +12,7 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp readonly onDidChangeArtifacts: Event = this._onDidChangeArtifacts.event; private readonly _groups: SourceControlArtifactGroup[]; + private readonly _disposables: Disposable[] = []; constructor( private readonly repository: Repository, @@ -21,7 +22,10 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp { id: 'branches', name: l10n.t('Branches'), icon: new ThemeIcon('git-branch') }, { id: 'tags', name: l10n.t('Tags'), icon: new ThemeIcon('tag') } ]; + + this._disposables.push(this._onDidChangeArtifacts); } + provideArtifactGroups(): SourceControlArtifactGroup[] { return this._groups; } @@ -58,5 +62,7 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp return []; } - dispose(): void { } + dispose(): void { + dispose(this._disposables); + } } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ce59f9919be..5e60b8de373 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5184,7 +5184,6 @@ export class CommandCenter { @command('git.repositories.checkout', { repository: true }) async artifactCheckout(repository: Repository, artifact: SourceControlArtifact): Promise { - console.log(repository, artifact); if (!repository || !artifact) { return; } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index f73d726c720..ce1cd1578ba 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -547,27 +547,30 @@ class MainThreadSCMProvider implements ISCMProvider { } $onDidChangeHistoryProviderCurrentHistoryItemRefs(historyItemRef?: SCMHistoryItemRefDto, historyItemRemoteRef?: SCMHistoryItemRefDto, historyItemBaseRef?: SCMHistoryItemRefDto): void { - if (!this.historyProvider.get()) { + const provider = this.historyProvider.get(); + if (!provider) { return; } - this._historyProvider.get()?.$onDidChangeCurrentHistoryItemRefs(historyItemRef, historyItemRemoteRef, historyItemBaseRef); + provider.$onDidChangeCurrentHistoryItemRefs(historyItemRef, historyItemRemoteRef, historyItemBaseRef); } $onDidChangeHistoryProviderHistoryItemRefs(historyItemRefs: SCMHistoryItemRefsChangeEventDto): void { - if (!this.historyProvider.get()) { + const provider = this.historyProvider.get(); + if (!provider) { return; } - this._historyProvider.get()?.$onDidChangeHistoryItemRefs(historyItemRefs); + provider.$onDidChangeHistoryItemRefs(historyItemRefs); } $onDidChangeArtifacts(group: string): void { - if (!this.artifactProvider.get()) { + const provider = this.artifactProvider.get(); + if (!provider) { return; } - this._artifactProvider.get()?.$onDidChangeArtifacts(group); + provider.$onDidChangeArtifacts(group); } toJSON() { From a077f8fd2660196583464dfc40c8bee2ad29baa2 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 30 Oct 2025 17:38:29 -0400 Subject: [PATCH 1895/4355] add UI for managing terminal chat sessions (#274149) fix #271386 --- .../terminal/browser/media/terminal.css | 44 ++++++++- .../contrib/terminal/browser/terminalMenus.ts | 1 + .../terminal/browser/terminalTabbedView.ts | 40 +++++++-- .../terminal/browser/terminalTabsChatEntry.ts | 89 +++++++++++++++++++ .../terminal/common/terminalContextKey.ts | 4 +- .../chat/browser/terminalChat.ts | 4 + .../chat/browser/terminalChatActions.ts | 6 +- .../chat/browser/terminalChatService.ts | 7 +- 8 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTabsChatEntry.ts diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 90d55597fb0..f6fcd8bb9b8 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -303,6 +303,7 @@ .monaco-workbench .pane-body.integrated-terminal .tabs-list-container { height: 100%; overflow: hidden; + position: relative; } .monaco-workbench .pane-body.integrated-terminal .tabs-container > .monaco-toolbar { @@ -328,8 +329,49 @@ text-align: left; } -.monaco-workbench .pane-body.integrated-terminal .tabs-list { +.monaco-workbench .pane-body.integrated-terminal .terminal-tabs-chat-entry { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 22px; + line-height: 22px; + display: flex; + align-items: center; + padding: 0; + cursor: pointer; + background-color: transparent; + color: inherit; +} + +.monaco-workbench .pane-body.integrated-terminal .terminal-tabs-chat-entry .terminal-tabs-entry { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + width: 100%; height: 100%; + padding: 0; +} + +.monaco-workbench .pane-body.integrated-terminal .terminal-tabs-chat-entry .terminal-tabs-entry:hover { + background-color: var(--vscode-toolbar-hoverBackground); +} + +.monaco-workbench .pane-body.integrated-terminal .terminal-tabs-chat-entry .terminal-tabs-chat-entry-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.monaco-workbench .pane-body.integrated-terminal .tabs-container.has-text .terminal-tabs-chat-entry .terminal-tabs-entry { + justify-content: center; + padding: 0 10px; +} + +.monaco-workbench .pane-body.integrated-terminal .terminal-tabs-chat-entry .terminal-tabs-chat-entry-label:empty { + display: none; } .monaco-workbench .pane-body.integrated-terminal .tabs-list .terminal-tabs-entry { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 27e74b241a7..20358187449 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -409,6 +409,7 @@ export function setupTerminalMenus(): void { group: 'navigation', order: 0, when: ContextKeyExpr.and( + ContextKeyExpr.equals('hasChatTerminals', false), ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), ContextKeyExpr.has(`config.${TerminalSettingId.TabsEnabled}`), ContextKeyExpr.or( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 3213dd89247..f47a4250d8f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -5,16 +5,17 @@ import { LayoutPriority, Orientation, Sizing, SplitView } from '../../../../base/browser/ui/splitview/splitview.js'; import { Disposable, DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Event } from '../../../../base/common/event.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { ITerminalConfigurationService, ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalConnectionState } from './terminal.js'; +import { ITerminalChatService, ITerminalConfigurationService, ITerminalGroupService, ITerminalInstance, ITerminalService, TerminalConnectionState } from './terminal.js'; import { TerminalTabsListSizes, TerminalTabList } from './terminalTabsList.js'; import * as dom from '../../../../base/browser/dom.js'; import { Action, IAction, Separator } from '../../../../base/common/actions.js'; import { IMenu, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { TerminalSettingId } from '../../../../platform/terminal/common/terminal.js'; +import { TerminalLocation, TerminalSettingId } from '../../../../platform/terminal/common/terminal.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { localize } from '../../../../nls.js'; import { openContextMenu } from './terminalContextMenu.js'; @@ -22,6 +23,7 @@ import { TerminalStorageKeys } from '../common/terminalStorageKeys.js'; import { TerminalContextKeys } from '../common/terminalContextKey.js'; import { getInstanceHoverInfo } from './terminalTooltip.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { TerminalTabsChatEntry } from './terminalTabsChatEntry.js'; const $ = dom.$; @@ -46,6 +48,7 @@ export class TerminalTabbedView extends Disposable { private _sashDisposables: IDisposable[] | undefined; private _plusButton: HTMLElement | undefined; + private _chatEntry: TerminalTabsChatEntry | undefined; private _tabTreeIndex: number; private _terminalContainerIndex: number; @@ -67,6 +70,7 @@ export class TerminalTabbedView extends Disposable { constructor( parentElement: HTMLElement, @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @ITerminalConfigurationService private readonly _terminalConfigurationService: ITerminalConfigurationService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -85,11 +89,18 @@ export class TerminalTabbedView extends Disposable { tabListContainer.appendChild(this._tabListElement); this._tabContainer.appendChild(tabListContainer); + this._register(dom.addDisposableListener(this._tabContainer, dom.EventType.DBLCLICK, async () => { + const instance = await this._terminalService.createTerminal({ location: TerminalLocation.Panel }); + this._terminalGroupService.setActiveInstance(instance); + await instance.focusWhenReady(); + })); + this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalInstanceContext, contextKeyService)); this._tabsListMenu = this._register(menuService.createMenu(MenuId.TerminalTabContext, contextKeyService)); this._tabsListEmptyMenu = this._register(menuService.createMenu(MenuId.TerminalTabEmptyAreaContext, contextKeyService)); this._tabList = this._register(this._instantiationService.createInstance(TerminalTabList, this._tabListElement, this._register(new DisposableStore()))); + this._chatEntry = this._register(this._instantiationService.createInstance(TerminalTabsChatEntry, tabListContainer, this._tabContainer)); const terminalOuterContainer = $('.terminal-outer-container'); this._terminalContainer = $('.terminal-groups-container'); @@ -119,8 +130,15 @@ export class TerminalTabbedView extends Disposable { } } })); - this._register(this._terminalGroupService.onDidChangeInstances(() => this._refreshShowTabs())); - this._register(this._terminalGroupService.onDidChangeGroups(() => this._refreshShowTabs())); + this._register(Event.any(this._terminalGroupService.onDidChangeInstances, this._terminalGroupService.onDidChangeGroups)(() => { + this._refreshShowTabs(); + this._updateChatTerminalsEntry(); + })); + + this._register(Event.any(this._terminalChatService.onDidRegisterTerminalInstanceWithToolSession, this._terminalService.onDidDisposeInstance)(() => { + this._refreshShowTabs(); + this._updateChatTerminalsEntry(); + })); this._attachEventListeners(parentElement, this._terminalContainer); @@ -135,11 +153,13 @@ export class TerminalTabbedView extends Disposable { this._splitView = new SplitView(parentElement, { orientation: Orientation.HORIZONTAL, proportionalLayout: false }); this._setupSplitView(terminalOuterContainer); + this._updateChatTerminalsEntry(); } private _shouldShowTabs(): boolean { const enabled = this._terminalConfigurationService.config.tabs.enabled; const hide = this._terminalConfigurationService.config.tabs.hideCondition; + const hasChatTerminals = this._terminalChatService.getToolSessionTerminalInstances().length > 0; if (!enabled) { return false; } @@ -148,6 +168,10 @@ export class TerminalTabbedView extends Disposable { return true; } + if (this._terminalGroupService.instances.length && hasChatTerminals) { + return true; + } + if (hide === 'singleTerminal' && this._terminalGroupService.instances.length > 1) { return true; } @@ -176,6 +200,10 @@ export class TerminalTabbedView extends Disposable { } } + private _updateChatTerminalsEntry(): void { + this._chatEntry?.update(); + } + private _getLastListWidth(): number { const widthKey = this._panelOrientation === Orientation.VERTICAL ? TerminalStorageKeys.TabsListWidthVertical : TerminalStorageKeys.TabsListWidthHorizontal; const storedValue = this._storageService.get(widthKey, StorageScope.PROFILE); @@ -265,7 +293,7 @@ export class TerminalTabbedView extends Disposable { private _addTabTree() { this._splitView.addView({ element: this._tabContainer, - layout: width => this._tabList.layout(this._height || 0, width), + layout: width => this._tabList.layout(width), minimumSize: TerminalTabsListSizes.NarrowViewWidth, maximumSize: TerminalTabsListSizes.MaximumWidth, onDidChange: () => Disposable.None, @@ -304,6 +332,7 @@ export class TerminalTabbedView extends Disposable { const hasText = this._tabListElement.clientWidth > TerminalTabsListSizes.MidpointViewWidth; this._tabContainer.classList.toggle('has-text', hasText); this._terminalIsTabsNarrowContextKey.set(!hasText); + this._updateChatTerminalsEntry(); } layout(width: number, height: number): void { @@ -316,6 +345,7 @@ export class TerminalTabbedView extends Disposable { this._updateHasText(); } + private _attachEventListeners(parentDomElement: HTMLElement, terminalContainer: HTMLElement): void { this._register(dom.addDisposableListener(this._tabContainer, 'mouseleave', async (event: MouseEvent) => { this._terminalTabsMouseContextKey.set(false); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsChatEntry.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsChatEntry.ts new file mode 100644 index 00000000000..fc0976bdb12 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsChatEntry.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { $ } from '../../../../base/browser/dom.js'; +import { localize } from '../../../../nls.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { ITerminalChatService } from './terminal.js'; +import * as dom from '../../../../base/browser/dom.js'; + +export class TerminalTabsChatEntry extends Disposable { + + private readonly _entry: HTMLElement; + private readonly _label: HTMLElement; + + override dispose(): void { + this._entry.remove(); + this._label.remove(); + super.dispose(); + } + + constructor( + container: HTMLElement, + private readonly _tabContainer: HTMLElement, + @ICommandService private readonly _commandService: ICommandService, + @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, + ) { + super(); + + this._entry = dom.append(container, $('.terminal-tabs-chat-entry')); + this._entry.tabIndex = 0; + this._entry.setAttribute('role', 'button'); + + const entry = dom.append(this._entry, $('.terminal-tabs-entry')); + const icon = dom.append(entry, $('.terminal-tabs-chat-entry-icon')); + icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.commentDiscussionSparkle)); + this._label = dom.append(entry, $('.terminal-tabs-chat-entry-label')); + + const runChatTerminalsCommand = () => { + void this._commandService.executeCommand('workbench.action.terminal.chat.viewChatTerminals'); + }; + this._register(dom.addDisposableListener(this._entry, dom.EventType.CLICK, e => { + e.preventDefault(); + runChatTerminalsCommand(); + })); + this._register(dom.addDisposableListener(this._entry, dom.EventType.KEY_DOWN, e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + runChatTerminalsCommand(); + } + })); + } + + get element(): HTMLElement { + return this._entry; + } + + update(): void { + const chatTerminalCount = this._terminalChatService.getToolSessionTerminalInstances().length; + const hasChatTerminals = chatTerminalCount > 0; + + if (!hasChatTerminals) { + this._entry.style.display = 'none'; + this._label.textContent = ''; + this._entry.removeAttribute('aria-label'); + + return; + } + + this._entry.style.display = ''; + const hasText = this._tabContainer.classList.contains('has-text'); + if (hasText) { + this._label.textContent = chatTerminalCount === 1 + ? localize('terminal.tabs.chatEntryLabelSingle', "{0} Chat Terminal", chatTerminalCount) + : localize('terminal.tabs.chatEntryLabelPlural', "{0} Chat Terminals", chatTerminalCount); + } else { + this._label.textContent = `${chatTerminalCount}`; + } + + const ariaLabel = chatTerminalCount === 1 + ? localize('terminal.tabs.chatEntryAriaLabelSingle', "Show 1 chat terminal") + : localize('terminal.tabs.chatEntryAriaLabelPlural', "Show {0} chat terminals", chatTerminalCount); + this._entry.setAttribute('aria-label', ariaLabel); + } +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 9db5585cc44..b78bfd70c77 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -140,15 +140,13 @@ export namespace TerminalContextKeys { /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */ export const terminalShellIntegrationEnabled = new RawContextKey(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal")); - /** Whether there is at least one tool terminal (created via run_in_terminal). */ - export const hasToolTerminal = new RawContextKey('hasToolTerminal', false, localize('hasToolTerminalContextKey', "Whether there is at least one tool terminal (created via run_in_terminal).")); - /** Whether a speech to text (dictation) session is in progress. */ export const terminalDictationInProgress = new RawContextKey(TerminalContextKeyStrings.DictationInProgress, false); export const shouldShowViewInlineActions = ContextKeyExpr.and( ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), + ContextKeyExpr.equals('hasChatTerminals', false), ContextKeyExpr.or( ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), ContextKeyExpr.and( diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index f5333183b09..3b478d1c284 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -36,6 +36,7 @@ export const enum TerminalChatContextKeyStrings { ChatResponseContainsMultipleCodeBlocks = 'terminalChatResponseContainsMultipleCodeBlocks', ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', ChatSessionResponseVote = 'terminalChatSessionResponseVote', + ChatHasTerminals = 'hasChatTerminals', } @@ -61,4 +62,7 @@ export namespace TerminalChatContextKeys { /** A chat agent exists for the terminal location */ export const hasChatAgent = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether a chat agent is registered for the terminal location.")); + + /** Has terminals created via chat */ + export const hasChatTerminals = new RawContextKey(TerminalChatContextKeyStrings.ChatHasTerminals, false, localize('terminalHasChatTerminals', "Whether there are any chat terminals.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 7944bf13cb1..4d5096f58da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -312,7 +312,7 @@ registerAction2(class ShowChatTerminalsAction extends Action2 { precondition: ChatContextKeys.enabled, menu: [{ id: MenuId.ViewTitle, - when: ContextKeyExpr.and(TerminalContextKeys.hasToolTerminal, ContextKeyExpr.equals('view', ChatViewId)), + when: ContextKeyExpr.and(TerminalChatContextKeys.hasChatTerminals, ContextKeyExpr.equals('view', ChatViewId)), group: 'terminal', order: 0, isHiddenByDefault: true @@ -329,9 +329,9 @@ registerAction2(class ShowChatTerminalsAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const visible = new Set([...groupService.instances, ...editorService.instances]); - const toolInstances = new Set(terminalChatService.getToolSessionTerminalInstances()); + const toolInstances = terminalChatService.getToolSessionTerminalInstances(); - if (toolInstances.size === 0) { + if (toolInstances.length === 0) { return; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index af8f9a02a40..c52bc85339d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -8,8 +8,8 @@ import { Disposable, DisposableMap, IDisposable } from '../../../../../base/comm import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITerminalChatService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { TerminalChatContextKeys } from './terminalChat.js'; /** * Used to manage chat tool invocations and the underlying terminal instances they create/use. @@ -41,7 +41,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ ) { super(); - this._hasToolTerminalContext = TerminalContextKeys.hasToolTerminal.bindTo(this._contextKeyService); + this._hasToolTerminalContext = TerminalChatContextKeys.hasChatTerminals.bindTo(this._contextKeyService); this._restoreFromStorage(); } @@ -82,7 +82,8 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ } getToolSessionTerminalInstances(): readonly ITerminalInstance[] { - return Array.from(this._terminalInstancesByToolSessionId.values()); + // Ensure unique instances in case multiple tool sessions map to the same terminal + return Array.from(new Set(this._terminalInstancesByToolSessionId.values())); } isBackgroundTerminal(terminalToolSessionId?: string): boolean { From f959fba2606cce7d5e2663c1a22d5b6217ef7fba Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 30 Oct 2025 14:51:24 -0700 Subject: [PATCH 1896/4355] Update language model usage in output monitoring (#274205) update llm usages --- .../browser/tools/monitoring/outputMonitor.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 78e3c2100fd..c91b7f92604 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -354,13 +354,13 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { private async _assessOutputForErrors(buffer: string, token: CancellationToken): Promise { - const models = await this._languageModelsService.selectLanguageModels({ vendor: 'copilot', family: 'gpt-4o-mini' }); - if (!models.length) { + const model = await this._getLanguageModel(); + if (!model) { return 'No models available'; } const response = await this._languageModelsService.sendChatRequest( - models[0], + model, new ExtensionIdentifier('core'), [{ role: ChatMessageRole.User, content: [{ type: 'text', value: `Evaluate this terminal output to determine if there were errors. If there are errors, return them. Otherwise, return undefined: ${buffer}.` }] }], {}, @@ -380,8 +380,8 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { if (token.isCancellationRequested) { return; } - const models = await this._languageModelsService.selectLanguageModels({ vendor: 'copilot', family: 'gpt-4o-mini' }); - if (!models.length) { + const model = await this._getLanguageModel(); + if (!model) { return undefined; } const lastFiveLines = execution.getOutput(this._lastPromptMarker).trimEnd().split('\n').slice(-5).join('\n'); @@ -421,7 +421,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { ${lastFiveLines} `; - const response = await this._languageModelsService.sendChatRequest(models[0], new ExtensionIdentifier('core'), [{ role: ChatMessageRole.User, content: [{ type: 'text', value: promptText }] }], {}, token); + const response = await this._languageModelsService.sendChatRequest(model, new ExtensionIdentifier('core'), [{ role: ChatMessageRole.User, content: [{ type: 'text', value: promptText }] }], {}, token); const responseText = await getTextResponseFromStream(response); try { const match = responseText.match(/\{[\s\S]*\}/); @@ -674,6 +674,16 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { return { promise, part }; } + private async _getLanguageModel(): Promise { + let models = await this._languageModelsService.selectLanguageModels({ vendor: 'copilot', id: 'copilot-fast' }); + + // Fallback to gpt-4o-mini if copilot-fast is not available for backwards compatibility + if (!models.length) { + models = await this._languageModelsService.selectLanguageModels({ vendor: 'copilot', family: 'gpt-4o-mini' }); + } + + return models.length ? models[0] : undefined; + } } function getMoreActions(suggestedOption: SuggestedOption, confirmationPrompt: IConfirmationPrompt): IAction[] | undefined { From 738d624e21361362e2bc8375b85f6e75a82e0a72 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Oct 2025 23:10:39 +0100 Subject: [PATCH 1897/4355] enable mcp registry policy in stable (#274208) --- src/vs/workbench/services/accounts/common/defaultAccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/accounts/common/defaultAccount.ts b/src/vs/workbench/services/accounts/common/defaultAccount.ts index 44131f363c1..80ca205ee31 100644 --- a/src/vs/workbench/services/accounts/common/defaultAccount.ts +++ b/src/vs/workbench/services/accounts/common/defaultAccount.ts @@ -214,7 +214,7 @@ export class DefaultAccountManagementContribution extends Disposable implements this.getTokenEntitlements(session.accessToken, tokenEntitlementUrl), ]); - const mcpRegistryProvider = this.productService.quality !== 'stable' && tokenEntitlements.mcp ? await this.getMcpRegistryProvider(session.accessToken, mcpRegistryDataUrl) : undefined; + const mcpRegistryProvider = tokenEntitlements.mcp ? await this.getMcpRegistryProvider(session.accessToken, mcpRegistryDataUrl) : undefined; return { sessionId: session.id, From ef37dc6393c07e6e1de01c77a442dc2e6aa8e4db Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 30 Oct 2025 23:19:53 +0100 Subject: [PATCH 1898/4355] Manage language models editor (#274207) * copilot management editor * fix command id * fix command id * fix commands * more feedback * clean up * more enhancements * add unit tests * only keep it in insiders * cleanup * copilot feedback * fix smoke tests --- .../contrib/chat/browser/chat.contribution.ts | 1 + .../chatManagement.contribution.ts | 121 +++ .../chatManagement/chatManagementEditor.ts | 454 ++++++++ .../chatManagementEditorInput.ts | 78 ++ .../chatManagement/chatModelsViewModel.ts | 421 ++++++++ .../chatManagement/chatModelsWidget.ts | 972 ++++++++++++++++++ .../browser/chatManagement/chatUsageWidget.ts | 141 +++ .../media/chatManagementEditor.css | 122 +++ .../chatManagement/media/chatModelsWidget.css | 197 ++++ .../chatManagement/media/chatUsageWidget.css | 69 ++ .../contrib/chat/browser/chatStatus.ts | 124 +-- .../contrib/chat/common/constants.ts | 2 +- .../test/browser/chatModelsViewModel.test.ts | 533 ++++++++++ 13 files changed, 3123 insertions(+), 112 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditorInput.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css create mode 100644 src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css create mode 100644 src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index ed5915844b5..adc852a5175 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -125,6 +125,7 @@ import './promptSyntax/promptToolsCodeLensProvider.js'; import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js'; import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; +import './chatManagement/chatManagement.contribution.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts new file mode 100644 index 00000000000..381f84e8424 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isObject, isString } from '../../../../../base/common/types.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ProductQualityContext } from '../../../../../platform/contextkey/common/contextkeys.js'; +import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; +import { Registry } from '../../../../../platform/registry/common/platform.js'; +import { IEditorPaneRegistry, EditorPaneDescriptor } from '../../../../browser/editor.js'; +import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from '../../../../common/editor.js'; +import { EditorInput } from '../../../../common/editor/editorInput.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { MANAGE_CHAT_COMMAND_ID } from '../../common/constants.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { ChatManagementEditor, ModelsManagementEditor } from './chatManagementEditor.js'; +import { ChatManagementEditorInput, ModelsManagementEditorInput } from './chatManagementEditorInput.js'; + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + ChatManagementEditor, + ChatManagementEditor.ID, + localize('chatManagementEditor', "Chat Management Editor") + ), + [ + new SyncDescriptor(ChatManagementEditorInput) + ] +); + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + ModelsManagementEditor, + ModelsManagementEditor.ID, + localize('modelsManagementEditor', "Models Management Editor") + ), + [ + new SyncDescriptor(ModelsManagementEditorInput) + ] +); + +class ChatManagementEditorInputSerializer implements IEditorSerializer { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(input: ChatManagementEditorInput): string { + return ''; + } + + deserialize(instantiationService: IInstantiationService): ChatManagementEditorInput { + return instantiationService.createInstance(ChatManagementEditorInput); + } +} + +class ModelsManagementEditorInputSerializer implements IEditorSerializer { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(input: ModelsManagementEditorInput): string { + return ''; + } + + deserialize(instantiationService: IInstantiationService): ModelsManagementEditorInput { + return instantiationService.createInstance(ModelsManagementEditorInput); + } +} + +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatManagementEditorInput.ID, ChatManagementEditorInputSerializer); +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ModelsManagementEditorInput.ID, ModelsManagementEditorInputSerializer); + +interface IOpenManageCopilotEditorActionOptions { + query?: string; + section?: string; +} + +function sanitizeString(arg: unknown): string | undefined { + return isString(arg) ? arg : undefined; +} + +function sanitizeOpenManageCopilotEditorArgs(input: unknown): IOpenManageCopilotEditorActionOptions { + if (!isObject(input)) { + input = {}; + } + + const args = input; + + return { + query: sanitizeString(args?.query), + section: sanitizeString(args?.section) + }; +} + +registerAction2(class extends Action2 { + constructor() { + super({ + id: MANAGE_CHAT_COMMAND_ID, + title: localize2('openAiManagement', "Manage Language Models"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ChatContextKeys.enabled, ContextKeyExpr.or( + ChatContextKeys.Entitlement.planFree, + ChatContextKeys.Entitlement.planPro, + ChatContextKeys.Entitlement.planProPlus, + ChatContextKeys.Entitlement.internal + )), + f1: true, + }); + } + async run(accessor: ServicesAccessor, args: string | IOpenManageCopilotEditorActionOptions) { + const editorGroupsService = accessor.get(IEditorGroupsService); + args = sanitizeOpenManageCopilotEditorArgs(args); + return editorGroupsService.activeGroup.openEditor(new ModelsManagementEditorInput()); + } +}); diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts new file mode 100644 index 00000000000..58996bac09a --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts @@ -0,0 +1,454 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/chatManagementEditor.css'; +import * as DOM from '../../../../../base/browser/dom.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IStorageService } from '../../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; +import { IEditorOptions } from '../../../../../platform/editor/common/editor.js'; +import { EditorPane } from '../../../../browser/parts/editor/editorPane.js'; +import { IEditorOpenContext } from '../../../../common/editor.js'; +import { IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { ChatManagementEditorInput, CHAT_MANAGEMENT_SECTION_USAGE, CHAT_MANAGEMENT_SECTION_MODELS, ModelsManagementEditorInput } from './chatManagementEditorInput.js'; +import { ChatModelsWidget } from './chatModelsWidget.js'; +import { Button } from '../../../../../base/browser/ui/button/button.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { localize } from '../../../../../nls.js'; +import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { IChatEntitlementService, ChatEntitlement } from '../../../../services/chat/common/chatEntitlementService.js'; +import { ChatUsageWidget } from './chatUsageWidget.js'; +import { Orientation, Sizing, SplitView } from '../../../../../base/browser/ui/splitview/splitview.js'; +import { IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { WorkbenchList } from '../../../../../platform/list/browser/listService.js'; +import { Event } from '../../../../../base/common/event.js'; +import { Dimension } from '../../../../../base/browser/dom.js'; +import { registerColor } from '../../../../../platform/theme/common/colorRegistry.js'; +import { PANEL_BORDER } from '../../../../common/theme.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; + +const $ = DOM.$; + +export class ModelsManagementEditor extends EditorPane { + + static readonly ID: string = 'workbench.editor.modelsManagement'; + + private readonly editorDisposables = this._register(new DisposableStore()); + private dimension: Dimension | undefined; + private modelsWidget: ChatModelsWidget | undefined; + private bodyContainer: HTMLElement | undefined; + + constructor( + group: IEditorGroup, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(ModelsManagementEditor.ID, group, telemetryService, themeService, storageService); + } + + protected override createEditor(parent: HTMLElement): void { + this.editorDisposables.clear(); + this.bodyContainer = DOM.append(parent, $('.ai-models-management-editor')); + this.modelsWidget = this.editorDisposables.add(this.instantiationService.createInstance(ChatModelsWidget)); + this.bodyContainer.appendChild(this.modelsWidget.element); + } + + override async setInput(input: ModelsManagementEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + await super.setInput(input, options, context, token); + if (this.dimension) { + this.layout(this.dimension); + } + } + + override layout(dimension: Dimension): void { + this.dimension = dimension; + if (this.bodyContainer) { + this.modelsWidget?.layout(dimension.height - 30, this.bodyContainer!.clientWidth - 30); + } + } + + override focus(): void { + super.focus(); + this.modelsWidget?.focusSearch(); + } +} + +export const chatManagementSashBorder = registerColor('chatManagement.sashBorder', PANEL_BORDER, localize('chatManagementSashBorder', "The color of the Chat Management editor splitview sash border.")); + +function isNewUser(chatEntitlementService: IChatEntitlementService): boolean { + return !chatEntitlementService.sentiment.installed || + chatEntitlementService.entitlement === ChatEntitlement.Available; +} + +interface SectionItem { + id: string; + label: string; +} + +export class ChatManagementEditor extends EditorPane { + + static readonly ID: string = 'workbench.editor.chatManagement'; + + private container: HTMLElement | undefined; + private splitView: SplitView | undefined; + private sectionsList: WorkbenchList | undefined; + private headerContainer!: HTMLElement; + private contentsContainer!: HTMLElement; + + private planBadge!: HTMLElement; + private actionButton!: Button; + + private chatUsageWidget!: ChatUsageWidget; + private modelsWidget!: ChatModelsWidget; + + private dimension: Dimension | undefined; + private selectedSection: string = CHAT_MANAGEMENT_SECTION_USAGE; + private sections: SectionItem[] = []; + + private readonly commandService: ICommandService; + private readonly chatEntitlementService: IChatEntitlementService; + + constructor( + group: IEditorGroup, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IChatEntitlementService chatEntitlementService: IChatEntitlementService + ) { + super(ChatManagementEditor.ID, group, telemetryService, themeService, storageService); + this.commandService = commandService; + this.chatEntitlementService = chatEntitlementService; + } + + protected override createEditor(parent: HTMLElement): void { + this.container = DOM.append(parent, $('.ai-management-editor')); + + // Header spans across entire width + this.renderHeader(this.container); + + // Create split view container + const splitViewContainer = DOM.append(this.container, $('.split-view-container')); + + const sidebarView = DOM.append(splitViewContainer, $('.sidebar-view')); + const sidebarContainer = DOM.append(sidebarView, $('.sidebar-container')); + + const contentsView = DOM.append(splitViewContainer, $('.contents-view')); + this.contentsContainer = DOM.append(contentsView, $('.contents-container')); + + this.splitView = new SplitView(splitViewContainer, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: true + }); + + this.renderSidebar(sidebarContainer); + this.renderContents(this.contentsContainer); + + this.splitView.addView({ + onDidChange: Event.None, + element: sidebarView, + minimumSize: 150, + maximumSize: 350, + layout: (width, _, height) => { + sidebarContainer.style.width = `${width}px`; + if (this.sectionsList && height !== undefined) { + this.sectionsList.layout(height, width); + } + } + }, 200, undefined, true); + + this.splitView.addView({ + onDidChange: Event.None, + element: contentsView, + minimumSize: 550, + maximumSize: Number.POSITIVE_INFINITY, + layout: (width, _, height) => { + contentsView.style.width = `${width}px`; + if (height !== undefined) { + this.layoutContents(width, height); + } + } + }, Sizing.Distribute, undefined, true); + + this.updateStyles(); + + // Update header data when quotas or entitlements change + this.updateHeaderData(); + this._register(this.chatEntitlementService.onDidChangeQuotaRemaining(() => this.updateHeaderData())); + this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.updateHeaderData())); + } + + override updateStyles(): void { + const borderColor = this.theme.getColor(chatManagementSashBorder)!; + this.splitView?.style({ separatorBorder: borderColor }); + } + + private renderSidebar(parent: HTMLElement): void { + // Define sections + this.sections = [ + { id: CHAT_MANAGEMENT_SECTION_USAGE, label: localize('plan.usage', 'Usage') }, + { id: CHAT_MANAGEMENT_SECTION_MODELS, label: localize('plan.models', 'Models') } + ]; + + const delegate = new SectionItemDelegate(); + const renderer = new SectionItemRenderer(); + + this.sectionsList = this._register(this.instantiationService.createInstance( + WorkbenchList, + 'ChatManagementSections', + parent, + delegate, + [renderer], + { + multipleSelectionSupport: false, + setRowLineHeight: false, + horizontalScrolling: false, + accessibilityProvider: { + getAriaLabel(element: SectionItem) { + return element.label; + }, + getWidgetAriaLabel() { + return localize('sectionsListAriaLabel', "Sections"); + } + }, + openOnSingleClick: true, + identityProvider: { + getId(element: SectionItem) { + return element.id; + } + } + } + )); + + this.sectionsList.splice(0, this.sectionsList.length, this.sections); + this.sectionsList.setSelection([0]); + + this._register(this.sectionsList.onDidChangeSelection(e => { + if (e.elements.length > 0) { + this.selectedSection = e.elements[0].id; + this.renderSelectedSection(); + } + })); + } + + private renderHeader(parent: HTMLElement): void { + this.headerContainer = DOM.append(parent, $('.ai-management-header')); + const headerTitleContainer = DOM.append(this.headerContainer, $('.header-title-container')); + const headerTitleWrapper = DOM.append(headerTitleContainer, $('.header-title-wrapper')); + + // Copilot label + const tile = DOM.append(headerTitleWrapper, $('.ai-management-editor-title')); + tile.textContent = localize('plan.copilot', 'Copilot'); + + // Plan badge + this.planBadge = DOM.append(headerTitleWrapper, $('.plan-badge')); + + // Action button container in title + const titleButtonContainer = DOM.append(headerTitleContainer, $('.header-upgrade-button-container')); + this.actionButton = this._register(new Button(titleButtonContainer, { ...defaultButtonStyles })); + this.actionButton.element.classList.add('header-upgrade-button'); + this.actionButton.element.style.display = 'none'; + } + + private renderContents(parent: HTMLElement): void { + // Body container for widgets + const bodyContainer = DOM.append(parent, $('.ai-management-body')); + + // Create widgets + this.chatUsageWidget = this._register(this.instantiationService.createInstance(ChatUsageWidget)); + this.modelsWidget = this._register(this.instantiationService.createInstance(ChatModelsWidget)); + + // Append widgets to body + bodyContainer.appendChild(this.chatUsageWidget.element); + bodyContainer.appendChild(this.modelsWidget.element); + + // Initially show only the selected section + this.renderSelectedSection(); + } + + private renderSelectedSection(): void { + // Hide all widgets + this.chatUsageWidget.element.style.display = 'none'; + this.modelsWidget.element.style.display = 'none'; + + // Show selected widget + if (this.selectedSection === CHAT_MANAGEMENT_SECTION_USAGE) { + this.chatUsageWidget.element.style.display = ''; + } else if (this.selectedSection === CHAT_MANAGEMENT_SECTION_MODELS) { + this.modelsWidget.element.style.display = ''; + } + + // Trigger layout + if (this.dimension) { + this.layout(this.dimension); + } + } + + private layoutContents(width: number, height: number): void { + if (!this.contentsContainer) { + return; + } + + if (this.selectedSection === CHAT_MANAGEMENT_SECTION_MODELS) { + this.modelsWidget.layout(height - 30, width - 30); + } + } + + selectSection(sectionId: string): void { + const index = this.sections.findIndex(s => s.id === sectionId); + if (index >= 0) { + this.sectionsList?.setFocus([index]); + this.sectionsList?.setSelection([index]); + } + } + + private updateHeaderData(): void { + const newUser = isNewUser(this.chatEntitlementService); + const anonymousUser = this.chatEntitlementService.anonymous; + const disabled = this.chatEntitlementService.sentiment.disabled || this.chatEntitlementService.sentiment.untrusted; + const signedOut = this.chatEntitlementService.entitlement === ChatEntitlement.Unknown; + const isFreePlan = this.chatEntitlementService.entitlement === ChatEntitlement.Free; + + // Set plan name and toggle visibility based on plan type + if (anonymousUser || isFreePlan) { + if (anonymousUser) { + // Hide badge for anonymous users, only show "Copilot" label + this.planBadge.style.display = 'none'; + } else { + // Show "Free" badge for free plan + this.planBadge.style.display = ''; + this.planBadge.textContent = localize('plan.free', 'Free'); + } + } else { + this.planBadge.style.display = ''; + // Extract just the plan type (Pro, Pro+, Business, Enterprise) + const planName = this.getCurrentPlanName(); + this.planBadge.textContent = planName.replace('Copilot ', ''); + } + + const shouldUpgrade = this.shouldShowUpgradeButton(); + + // Configure action button + if (newUser || signedOut || disabled || shouldUpgrade) { + this.actionButton.element.style.display = ''; + + let buttonLabel: string; + let commandId: string; + + if (shouldUpgrade && !isFreePlan && !anonymousUser) { + // Upgrade for paid plans + if (this.chatEntitlementService.entitlement === ChatEntitlement.Pro) { + buttonLabel = localize('plan.upgradeToProPlus', 'Upgrade to Copilot Pro+'); + } else { + buttonLabel = localize('plan.upgradeToPro', 'Upgrade to Copilot Pro'); + } + commandId = 'workbench.action.chat.upgradePlan'; + } else if (shouldUpgrade && (isFreePlan || anonymousUser)) { + // Upgrade case for free plan + buttonLabel = localize('upgradeToCopilotPro', 'Upgrade to Copilot Pro'); + commandId = 'workbench.action.chat.upgradePlan'; + } else if (newUser) { + buttonLabel = localize('enableAIFeatures', "Use AI Features"); + commandId = newUser && anonymousUser ? 'workbench.action.chat.triggerSetupAnonymousWithoutDialog' : 'workbench.action.chat.triggerSetup'; + } else if (anonymousUser) { + buttonLabel = localize('enableMoreAIFeatures', "Enable more AI Features"); + commandId = 'workbench.action.chat.triggerSetup'; + } else if (disabled) { + buttonLabel = localize('enableCopilotButton', "Enable AI Features"); + commandId = 'workbench.action.chat.triggerSetup'; + } else { + buttonLabel = localize('signInToUseAIFeatures', "Sign in to use AI Features"); + commandId = 'workbench.action.chat.triggerSetup'; + } + + this.actionButton.label = buttonLabel; + this.actionButton.onDidClick(() => { + this.commandService.executeCommand(commandId); + }); + } else { + this.actionButton.element.style.display = 'none'; + } + } + + private getCurrentPlanName(): string { + const entitlement = this.chatEntitlementService.entitlement; + switch (entitlement) { + case ChatEntitlement.Pro: + return localize('plan.proName', 'Copilot Pro'); + case ChatEntitlement.ProPlus: + return localize('plan.proPlusName', 'Copilot Pro+'); + case ChatEntitlement.Business: + return localize('plan.businessName', 'Copilot Business'); + case ChatEntitlement.Enterprise: + return localize('plan.enterpriseName', 'Copilot Enterprise'); + default: + return localize('plan.freeName', 'Copilot Free'); + } + } + + private shouldShowUpgradeButton(): boolean { + const entitlement = this.chatEntitlementService.entitlement; + return entitlement === ChatEntitlement.Available || + entitlement === ChatEntitlement.Free || + entitlement === ChatEntitlement.Pro; + } + + override async setInput(input: ChatManagementEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + await super.setInput(input, options, context, token); + + if (this.dimension) { + this.layout(this.dimension); + } + } + + override layout(dimension: Dimension): void { + this.dimension = dimension; + + if (this.container && this.splitView) { + const headerHeight = this.headerContainer?.offsetHeight || 0; + const splitViewHeight = dimension.height - headerHeight; + this.splitView.layout(this.container.clientWidth, splitViewHeight); + this.splitView.el.style.height = `${splitViewHeight}px`; + } + } + + override focus(): void { + super.focus(); + this.sectionsList?.domFocus(); + } +} + +class SectionItemDelegate implements IListVirtualDelegate { + getHeight(element: SectionItem) { + return 22; + } + getTemplateId() { return 'sectionItem'; } +} + +interface ISectionItemTemplateData { + readonly label: HTMLElement; +} + +class SectionItemRenderer { + readonly templateId = 'sectionItem'; + + renderTemplate(container: HTMLElement): ISectionItemTemplateData { + container.classList.add('section-list-item'); + const label = DOM.append(container, $('.section-list-item-label')); + return { label }; + } + + renderElement(element: SectionItem, index: number, templateData: ISectionItemTemplateData): void { + templateData.label.textContent = element.label; + } + + disposeTemplate(templateData: ISectionItemTemplateData): void { + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditorInput.ts new file mode 100644 index 00000000000..c27ca9f3492 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditorInput.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from '../../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import * as nls from '../../../../../nls.js'; +import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js'; +import { IUntypedEditorInput } from '../../../../common/editor.js'; +import { EditorInput } from '../../../../common/editor/editorInput.js'; + +const ChatManagementEditorIcon = registerIcon('ai-management-editor-label-icon', Codicon.copilot, nls.localize('aiManagementEditorLabelIcon', 'Icon of the AI Management editor label.')); + +export const CHAT_MANAGEMENT_SECTION_USAGE = 'usage'; +export const CHAT_MANAGEMENT_SECTION_MODELS = 'models'; + +export class ChatManagementEditorInput extends EditorInput { + + static readonly ID: string = 'workbench.input.chatManagement'; + + readonly resource = undefined; + + constructor() { + super(); + } + + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { + return super.matches(otherInput) || otherInput instanceof ChatManagementEditorInput; + } + + override get typeId(): string { + return ChatManagementEditorInput.ID; + } + + override getName(): string { + return nls.localize('aiManagementEditorInputName', "Manage Copilot"); + } + + override getIcon(): ThemeIcon { + return ChatManagementEditorIcon; + } + + override async resolve(): Promise { + return null; + } +} + +export class ModelsManagementEditorInput extends EditorInput { + + static readonly ID: string = 'workbench.input.modelsManagement'; + + readonly resource = undefined; + + constructor() { + super(); + } + + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { + return super.matches(otherInput) || otherInput instanceof ModelsManagementEditorInput; + } + + override get typeId(): string { + return ModelsManagementEditorInput.ID; + } + + override getName(): string { + return nls.localize('aiManagementEditorInputName', "Manage Copilot"); + } + + override getIcon(): ThemeIcon { + return ChatManagementEditorIcon; + } + + override async resolve(): Promise { + return null; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts new file mode 100644 index 00000000000..a6fb278fb7b --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts @@ -0,0 +1,421 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { distinct, coalesce } from '../../../../../base/common/arrays.js'; +import { IMatch, IFilter, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from '../../../../../base/common/filters.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import { EditorModel } from '../../../../common/editor/editorModel.js'; +import { ILanguageModelsService, ILanguageModelChatMetadata } from '../../../chat/common/languageModels.js'; +import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; + +export const MODEL_ENTRY_TEMPLATE_ID = 'model.entry.template'; +export const VENDOR_ENTRY_TEMPLATE_ID = 'vendor.entry.template'; + +const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString); +const CAPABILITY_REGEX = /@capability:\s*([^\s]+)/gi; +const VISIBLE_REGEX = /@visible:\s*(true|false)/i; + +export const SEARCH_SUGGESTIONS = { + FILTER_TYPES: [ + '@provider:', + '@capability:', + '@visible:' + ], + CAPABILITIES: [ + '@capability:tools', + '@capability:vision', + '@capability:agent' + ], + VISIBILITY: [ + '@visible:true', + '@visible:false' + ] +}; + +export interface IVendorEntry { + vendor: string; + vendorDisplayName: string; + managementCommand?: string; +} + +export interface IModelEntry { + vendor: string; + vendorDisplayName: string; + identifier: string; + metadata: ILanguageModelChatMetadata; +} + +export interface IModelItemEntry { + type: 'model'; + id: string; + modelEntry: IModelEntry; + templateId: string; + providerMatches?: IMatch[]; + modelNameMatches?: IMatch[]; + capabilityMatches?: string[]; +} + +export interface IVendorItemEntry { + type: 'vendor'; + id: string; + vendorEntry: IVendorEntry; + templateId: string; + collapsed: boolean; +} + +export function isVendorEntry(entry: IModelItemEntry | IVendorItemEntry): entry is IVendorItemEntry { + return entry.type === 'vendor'; +} + +export class ChatModelsViewModel extends EditorModel { + + private readonly _onDidChangeModelEntries = this._register(new Emitter()); + readonly onDidChangeModelEntries = this._onDidChangeModelEntries.event; + + private modelEntries: IModelEntry[]; + private readonly collapsedVendors = new Set(); + + constructor( + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService + ) { + super(); + this.modelEntries = []; + + this._register(this.chatEntitlementService.onDidChangeEntitlement(async () => { + await this.resolve(); + this._onDidChangeModelEntries.fire(); + })); + } + + fetch(searchValue: string): (IModelItemEntry | IVendorItemEntry)[] { + let modelEntries = this.modelEntries; + const capabilityMatchesMap = new Map(); + + const visibleMatches = VISIBLE_REGEX.exec(searchValue); + if (visibleMatches && visibleMatches[1]) { + const visible = visibleMatches[1].toLowerCase() === 'true'; + modelEntries = this.filterByVisible(modelEntries, visible); + searchValue = searchValue.replace(VISIBLE_REGEX, ''); + } + + const providerNames: string[] = []; + let match: RegExpExecArray | null; + + const providerRegexGlobal = /@provider:\s*((".+?")|([^\s]+))/gi; + while ((match = providerRegexGlobal.exec(searchValue)) !== null) { + const providerName = match[2] ? match[2].substring(1, match[2].length - 1) : match[3]; + providerNames.push(providerName); + } + + // Apply provider filter with OR logic if multiple providers + if (providerNames.length > 0) { + modelEntries = this.filterByProviders(modelEntries, providerNames); + searchValue = searchValue.replace(/@provider:\s*((".+?")|([^\s]+))/gi, '').replace(/@vendor:\s*((".+?")|([^\s]+))/gi, ''); + } + + // Apply capability filters with AND logic if multiple capabilities + const capabilityNames: string[] = []; + let capabilityMatch: RegExpExecArray | null; + + while ((capabilityMatch = CAPABILITY_REGEX.exec(searchValue)) !== null) { + capabilityNames.push(capabilityMatch[1].toLowerCase()); + } + + if (capabilityNames.length > 0) { + const filteredEntries = this.filterByCapabilities(modelEntries, capabilityNames); + modelEntries = []; + for (const { entry, matchedCapabilities } of filteredEntries) { + modelEntries.push(entry); + capabilityMatchesMap.set(ChatModelsViewModel.getId(entry), matchedCapabilities); + } + searchValue = searchValue.replace(/@capability:\s*([^\s]+)/gi, ''); + } + + searchValue = searchValue.trim(); + if (!searchValue) { + return this.groupByVendor(modelEntries, capabilityMatchesMap); + } + + return this.filterByText(modelEntries, searchValue, capabilityMatchesMap); + } + + private filterByProviders(modelEntries: IModelEntry[], providers: string[]): IModelEntry[] { + const lowerProviders = providers.map(p => p.toLowerCase().trim()); + return modelEntries.filter(m => + lowerProviders.some(provider => + m.vendor.toLowerCase() === provider || + m.vendorDisplayName.toLowerCase() === provider + ) + ); + } + + private filterByVisible(modelEntries: IModelEntry[], visible: boolean): IModelEntry[] { + return modelEntries.filter(m => (m.metadata.isUserSelectable ?? false) === visible); + } + + private filterByCapabilities(modelEntries: IModelEntry[], capabilities: string[]): { entry: IModelEntry; matchedCapabilities: string[] }[] { + const result: { entry: IModelEntry; matchedCapabilities: string[] }[] = []; + for (const m of modelEntries) { + if (!m.metadata.capabilities) { + continue; + } + const allMatchedCapabilities: string[] = []; + let matchesAll = true; + + for (const capability of capabilities) { + const matchedForThisCapability = this.getMatchingCapabilities(m, capability); + if (matchedForThisCapability.length === 0) { + matchesAll = false; + break; + } + allMatchedCapabilities.push(...matchedForThisCapability); + } + + if (matchesAll) { + result.push({ entry: m, matchedCapabilities: distinct(allMatchedCapabilities) }); + } + } + return result; + } + + private getMatchingCapabilities(modelEntry: IModelEntry, capability: string): string[] { + const matchedCapabilities: string[] = []; + if (!modelEntry.metadata.capabilities) { + return matchedCapabilities; + } + + switch (capability) { + case 'tools': + case 'toolcalling': + if (modelEntry.metadata.capabilities.toolCalling === true) { + matchedCapabilities.push('toolCalling'); + } + break; + case 'vision': + if (modelEntry.metadata.capabilities.vision === true) { + matchedCapabilities.push('vision'); + } + break; + case 'agent': + case 'agentmode': + if (modelEntry.metadata.capabilities.agentMode === true) { + matchedCapabilities.push('agentMode'); + } + break; + default: + // Check edit tools + if (modelEntry.metadata.capabilities.editTools) { + for (const tool of modelEntry.metadata.capabilities.editTools) { + if (tool.toLowerCase().includes(capability)) { + matchedCapabilities.push(tool); + } + } + } + break; + } + return matchedCapabilities; + } + + private filterByText(modelEntries: IModelEntry[], searchValue: string, capabilityMatchesMap: Map): IModelItemEntry[] { + const quoteAtFirstChar = searchValue.charAt(0) === '"'; + const quoteAtLastChar = searchValue.charAt(searchValue.length - 1) === '"'; + const completeMatch = quoteAtFirstChar && quoteAtLastChar; + if (quoteAtFirstChar) { + searchValue = searchValue.substring(1); + } + if (quoteAtLastChar) { + searchValue = searchValue.substring(0, searchValue.length - 1); + } + searchValue = searchValue.trim(); + + const result: IModelItemEntry[] = []; + const words = searchValue.split(' '); + + for (const modelEntry of modelEntries) { + const modelMatches = new ModelItemMatches(modelEntry, searchValue, words, completeMatch); + if (modelMatches.modelNameMatches + || modelMatches.providerMatches + || modelMatches.capabilityMatches + ) { + const modelId = ChatModelsViewModel.getId(modelEntry); + result.push({ + type: 'model', + id: modelId, + templateId: MODEL_ENTRY_TEMPLATE_ID, + modelEntry, + modelNameMatches: modelMatches.modelNameMatches || undefined, + providerMatches: modelMatches.providerMatches || undefined, + capabilityMatches: capabilityMatchesMap.get(modelId), + }); + } + } + return result; + } + + override async resolve(): Promise { + this.modelEntries = []; + + const vendors = this.languageModelsService.getVendors(); + + for (const vendor of vendors) { + const modelIdentifiers = await this.languageModelsService.selectLanguageModels({ vendor: vendor.vendor }, vendor.vendor === 'copilot'); + const models = coalesce(modelIdentifiers.map(identifier => { + const metadata = this.languageModelsService.lookupLanguageModel(identifier); + if (!metadata) { + return undefined; + } + if (vendor.vendor === 'copilot' && metadata.id === 'auto') { + return undefined; + } + return { + vendor: vendor.vendor, + vendorDisplayName: vendor.displayName, + identifier, + metadata + }; + })); + + this.modelEntries.push(...models.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name))); + } + + this.modelEntries = distinct(this.modelEntries, modelEntry => ChatModelsViewModel.getId(modelEntry)); + + return super.resolve(); + } + + private static getId(modelEntry: IModelEntry): string { + return modelEntry.identifier + modelEntry.vendor + (modelEntry.metadata.version || ''); + } + + toggleVendorCollapsed(vendorId: string): void { + if (this.collapsedVendors.has(vendorId)) { + this.collapsedVendors.delete(vendorId); + } else { + this.collapsedVendors.add(vendorId); + } + this._onDidChangeModelEntries.fire(); + } + + private groupByVendor(modelEntries: IModelEntry[], capabilityMatchesMap: Map): (IVendorItemEntry | IModelItemEntry)[] { + const result: (IVendorItemEntry | IModelItemEntry)[] = []; + const vendorMap = new Map(); + + for (const modelEntry of modelEntries) { + const models = vendorMap.get(modelEntry.vendor) || []; + models.push(modelEntry); + vendorMap.set(modelEntry.vendor, models); + } + + for (const [vendor, models] of vendorMap) { + const firstModel = models[0]; + const isCollapsed = this.collapsedVendors.has(vendor); + const vendorInfo = this.languageModelsService.getVendors().find(v => v.vendor === vendor); + result.push({ + type: 'vendor', + id: `vendor-${vendor}`, + vendorEntry: { + vendor: firstModel.vendor, + vendorDisplayName: firstModel.vendorDisplayName, + managementCommand: vendorInfo?.managementCommand + }, + templateId: VENDOR_ENTRY_TEMPLATE_ID, + collapsed: isCollapsed + }); + + if (!isCollapsed) { + for (const modelEntry of models) { + const modelId = ChatModelsViewModel.getId(modelEntry); + result.push({ + type: 'model', + id: modelId, + modelEntry, + templateId: MODEL_ENTRY_TEMPLATE_ID, + capabilityMatches: capabilityMatchesMap.get(modelId), + }); + } + } + } + + return result; + } +} + +class ModelItemMatches { + + readonly modelNameMatches: IMatch[] | null = null; + readonly providerMatches: IMatch[] | null = null; + readonly capabilityMatches: IMatch[] | null = null; + + constructor(modelEntry: IModelEntry, searchValue: string, words: string[], completeMatch: boolean) { + if (!completeMatch) { + // Match against model name + this.modelNameMatches = modelEntry.metadata.name ? + this.matches(searchValue, modelEntry.metadata.name, (word, wordToMatchAgainst) => matchesWords(word, wordToMatchAgainst, true), words) : + null; + + // Match against model identifier + if (!this.modelNameMatches) { + this.modelNameMatches = this.matches(searchValue, modelEntry.identifier, or(matchesWords, matchesCamelCase), words); + } + + // Match against vendor display name + this.providerMatches = this.matches(searchValue, modelEntry.vendorDisplayName, (word, wordToMatchAgainst) => matchesWords(word, wordToMatchAgainst, true), words); + + // Match against capabilities + if (modelEntry.metadata.capabilities) { + const capabilityStrings: string[] = []; + if (modelEntry.metadata.capabilities.toolCalling) { + capabilityStrings.push('tools', 'toolCalling'); + } + if (modelEntry.metadata.capabilities.vision) { + capabilityStrings.push('vision'); + } + if (modelEntry.metadata.capabilities.agentMode) { + capabilityStrings.push('agent', 'agentMode'); + } + if (modelEntry.metadata.capabilities.editTools) { + capabilityStrings.push(...modelEntry.metadata.capabilities.editTools); + } + + const capabilityString = capabilityStrings.join(' '); + if (capabilityString) { + this.capabilityMatches = this.matches(searchValue, capabilityString, or(matchesWords, matchesCamelCase), words); + } + } + } + } + + private matches(searchValue: string | null, wordToMatchAgainst: string, wordMatchesFilter: IFilter, words: string[]): IMatch[] | null { + let matches = searchValue ? wordFilter(searchValue, wordToMatchAgainst) : null; + if (!matches) { + matches = this.matchesWords(words, wordToMatchAgainst, wordMatchesFilter); + } + if (matches) { + matches = this.filterAndSort(matches); + } + return matches; + } + + private matchesWords(words: string[], wordToMatchAgainst: string, wordMatchesFilter: IFilter): IMatch[] | null { + let matches: IMatch[] | null = []; + for (const word of words) { + const wordMatches = wordMatchesFilter(word, wordToMatchAgainst); + if (wordMatches) { + matches = [...(matches || []), ...wordMatches]; + } else { + matches = null; + break; + } + } + return matches; + } + + private filterAndSort(matches: IMatch[]): IMatch[] { + return distinct(matches, (a => a.start + '.' + a.end)) + .filter(match => !matches.some(m => !(m.start === match.start && m.end === match.end) && (m.start <= match.start && m.end >= match.end))) + .sort((a, b) => a.start - b.start); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts new file mode 100644 index 00000000000..ac98be013be --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -0,0 +1,972 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/chatModelsWidget.css'; +import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import * as DOM from '../../../../../base/browser/dom.js'; +import { KeyCode } from '../../../../../base/common/keyCodes.js'; +import { Button, IButtonOptions } from '../../../../../base/browser/ui/button/button.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { ILanguageModelsService } from '../../../chat/common/languageModels.js'; +import { localize } from '../../../../../nls.js'; +import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { WorkbenchTable } from '../../../../../platform/list/browser/listService.js'; +import { ITableVirtualDelegate, ITableRenderer } from '../../../../../base/browser/ui/table/table.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; +import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; +import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { IAction, toAction, Action, Separator } from '../../../../../base/common/actions.js'; +import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { ChatModelsViewModel, IModelEntry, IModelItemEntry, IVendorItemEntry, SEARCH_SUGGESTIONS, isVendorEntry } from './chatModelsViewModel.js'; +import { HighlightedLabel } from '../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; +import { SuggestEnabledInput } from '../../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js'; +import { Delayer } from '../../../../../base/common/async.js'; +import { settingsTextInputBorder } from '../../../preferences/common/settingsEditorColorRegistry.js'; +import { IChatEntitlementService, ChatEntitlement } from '../../../../services/chat/common/chatEntitlementService.js'; +import { DropdownMenuActionViewItem } from '../../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { AnchorAlignment } from '../../../../../base/browser/ui/contextview/contextview.js'; +import { ToolBar } from '../../../../../base/browser/ui/toolbar/toolbar.js'; +import { preferencesClearInputIcon } from '../../../preferences/browser/preferencesIcons.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; + +const $ = DOM.$; + +const HEADER_HEIGHT = 30; +const VENDOR_ROW_HEIGHT = 30; +const MODEL_ROW_HEIGHT = 26; + +type TableEntry = IModelItemEntry | IVendorItemEntry; + +export function getModelHoverContent(model: IModelEntry): MarkdownString { + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + markdown.appendMarkdown(`**${model.metadata.name}**`); + if (model.metadata.id !== model.metadata.version) { + markdown.appendMarkdown(`  _${model.metadata.id}@${model.metadata.version}_ `); + } else { + markdown.appendMarkdown(`  _${model.metadata.id}_ `); + } + markdown.appendText(`\n`); + + if (model.metadata.statusIcon && model.metadata.tooltip) { + if (model.metadata.statusIcon) { + markdown.appendMarkdown(`$(${model.metadata.statusIcon.id}) `); + } + markdown.appendMarkdown(`${model.metadata.tooltip}`); + markdown.appendText(`\n`); + } + + if (model.metadata.detail) { + markdown.appendMarkdown(`${localize('models.cost', 'Multiplier')}: `); + markdown.appendMarkdown(model.metadata.detail); + markdown.appendText(`\n`); + } + + if (model.metadata.maxInputTokens || model.metadata.maxOutputTokens) { + markdown.appendMarkdown(`${localize('models.contextSize', 'Context Size')}: `); + let addSeparator = false; + if (model.metadata.maxInputTokens) { + markdown.appendMarkdown(`$(arrow-down) ${formatTokenCount(model.metadata.maxInputTokens)} (${localize('models.input', 'Input')})`); + addSeparator = true; + } + if (model.metadata.maxOutputTokens) { + if (addSeparator) { + markdown.appendText(` | `); + } + markdown.appendMarkdown(`$(arrow-up) ${formatTokenCount(model.metadata.maxOutputTokens)} (${localize('models.output', 'Output')})`); + } + markdown.appendText(`\n`); + } + + if (model.metadata.capabilities) { + markdown.appendMarkdown(`${localize('models.capabilities', 'Capabilities')}: `); + if (model.metadata.capabilities?.toolCalling) { + markdown.appendMarkdown(`  _${localize('models.toolCalling', 'Tools')}_ `); + } + if (model.metadata.capabilities?.vision) { + markdown.appendMarkdown(`  _${localize('models.vision', 'Vision')}_ `); + } + if (model.metadata.capabilities?.agentMode) { + markdown.appendMarkdown(`  _${localize('models.agentMode', 'Agent Mode')}_ `); + } + for (const editTool of model.metadata.capabilities.editTools ?? []) { + markdown.appendMarkdown(`  _${editTool}_ `); + } + markdown.appendText(`\n`); + } + + return markdown; +} + +class ModelsFilterAction extends Action { + constructor() { + super('workbench.models.filter', localize('filter', "Filter"), ThemeIcon.asClassName(Codicon.filter)); + } + override async run(): Promise { + } +} + +function toggleFilter(currentQuery: string, query: string, alternativeQueries: string[] = []): string { + const allQueries = [query, ...alternativeQueries]; + const isChecked = allQueries.some(q => currentQuery.includes(q)); + + if (!isChecked) { + const trimmedQuery = currentQuery.trim(); + return trimmedQuery ? `${trimmedQuery} ${query}` : query; + } else { + let queryWithRemovedFilter = currentQuery; + for (const q of allQueries) { + queryWithRemovedFilter = queryWithRemovedFilter.replace(q, ''); + } + return queryWithRemovedFilter.replace(/\s+/g, ' ').trim(); + } +} + +class ModelsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionViewItem { + + constructor( + action: IAction, + options: IActionViewItemOptions, + private readonly searchWidget: SuggestEnabledInput, + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @IContextMenuService contextMenuService: IContextMenuService + ) { + super(action, + { getActions: () => this.getActions() }, + contextMenuService, + { + ...options, + classNames: action.class, + anchorAlignmentProvider: () => AnchorAlignment.RIGHT, + menuAsChild: true + } + ); + } + + private createProviderAction(vendor: string, displayName: string): IAction { + const query = `@provider:"${displayName}"`; + const currentQuery = this.searchWidget.getValue(); + const isChecked = currentQuery.includes(query) || currentQuery.includes(`@provider:${vendor}`); + + return { + id: `provider-${vendor}`, + label: displayName, + tooltip: localize('filterByProvider', "Filter by {0}", displayName), + class: undefined, + enabled: true, + checked: isChecked, + run: () => this.toggleFilterAndSearch(query, [`@provider:${vendor}`]) + }; + } + + private createCapabilityAction(capability: string, label: string): IAction { + const query = `@capability:${capability}`; + const currentQuery = this.searchWidget.getValue(); + const isChecked = currentQuery.includes(query); + + return { + id: `capability-${capability}`, + label, + tooltip: localize('filterByCapability', "Filter by {0}", label), + class: undefined, + enabled: true, + checked: isChecked, + run: () => this.toggleFilterAndSearch(query) + }; + } + + private createVisibleAction(visible: boolean, label: string): IAction { + const query = `@visible:${visible}`; + const oppositeQuery = `@visible:${!visible}`; + const currentQuery = this.searchWidget.getValue(); + const isChecked = currentQuery.includes(query); + + return { + id: `visible-${visible}`, + label, + tooltip: localize('filterByVisible', "Filter by {0}", label), + class: undefined, + enabled: true, + checked: isChecked, + run: () => this.toggleFilterAndSearch(query, [oppositeQuery]) + }; + } + + private toggleFilterAndSearch(query: string, alternativeQueries: string[] = []): void { + const currentQuery = this.searchWidget.getValue(); + const newQuery = toggleFilter(currentQuery, query, alternativeQueries); + this.searchWidget.setValue(newQuery); + this.searchWidget.focus(); + } + + private getActions(): IAction[] { + const vendors = this.languageModelsService.getVendors(); + const actions: IAction[] = []; + + actions.push(this.createVisibleAction(true, localize('filter.visible', 'Visible'))); + + if (vendors.length > 0) { + actions.push(new Separator()); + actions.push(...vendors.map(vendor => this.createProviderAction(vendor.vendor, vendor.displayName))); + } + + if (actions.length > 1) { + actions.push(new Separator()); + } + + actions.push( + this.createCapabilityAction('tools', localize('capability.tools', 'Tools')), + this.createCapabilityAction('vision', localize('capability.vision', 'Vision')), + this.createCapabilityAction('agent', localize('capability.agent', 'Agent Mode')) + ); + + return actions; + } +} + +class Delegate implements ITableVirtualDelegate { + readonly headerRowHeight = HEADER_HEIGHT; + getHeight(element: TableEntry): number { + return isVendorEntry(element) ? VENDOR_ROW_HEIGHT : MODEL_ROW_HEIGHT; + } +} + +interface IModelTableColumnTemplateData { + readonly container: HTMLElement; + readonly disposables: DisposableStore; + readonly elementDisposables: DisposableStore; +} + +abstract class ModelsTableColumnRenderer implements ITableRenderer { + abstract readonly templateId: string; + abstract renderTemplate(container: HTMLElement): T; + + renderElement(element: TableEntry, index: number, templateData: T): void { + templateData.elementDisposables.clear(); + const isVendor = isVendorEntry(element); + templateData.container.parentElement!.classList.toggle('models-vendor-row', isVendor); + if (isVendor) { + this.renderVendorElement(element, index, templateData); + } else { + this.renderModelElement(element, index, templateData); + } + } + + abstract renderVendorElement(element: IVendorItemEntry, index: number, templateData: T): void; + abstract renderModelElement(element: IModelItemEntry, index: number, templateData: T): void; + + disposeTemplate(templateData: T): void { + templateData.elementDisposables.dispose(); + templateData.disposables.dispose(); + } +} + +interface IToggleCollapseColumnTemplateData extends IModelTableColumnTemplateData { + readonly container: HTMLElement; + readonly actionBar: ActionBar; +} + +class ToggleCollapseColumnRenderer extends ModelsTableColumnRenderer { + + static readonly TEMPLATE_ID = 'toggleCollapse'; + + readonly templateId: string = ToggleCollapseColumnRenderer.TEMPLATE_ID; + + private readonly _onDidToggleCollapse = new Emitter(); + readonly onDidToggleCollapse = this._onDidToggleCollapse.event; + + renderTemplate(container: HTMLElement): IToggleCollapseColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const actionBar = disposables.add(new ActionBar(DOM.append(container, $('.collapse-actions-column')))); + return { + container, + actionBar, + disposables, + elementDisposables + }; + } + + override renderElement(entry: TableEntry, index: number, templateData: IToggleCollapseColumnTemplateData): void { + templateData.actionBar.clear(); + super.renderElement(entry, index, templateData); + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: IToggleCollapseColumnTemplateData): void { + templateData.actionBar.push(this.createToggleCollapseAction(entry), { icon: true, label: false }); + } + + private createToggleCollapseAction(entry: IVendorItemEntry): IAction { + const label = entry.collapsed ? localize('expand', 'Expand') : localize('collapse', 'Collapse'); + return { + id: 'toggleCollapse', + label, + tooltip: label, + enabled: true, + class: ThemeIcon.asClassName(entry.collapsed ? Codicon.chevronRight : Codicon.chevronDown), + run: () => { + this._onDidToggleCollapse.fire(entry.vendorEntry.vendor); + } + }; + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: IToggleCollapseColumnTemplateData): void { + } +} + +interface IModelNameColumnTemplateData extends IModelTableColumnTemplateData { + readonly statusIcon: HTMLElement; + readonly nameLabel: HighlightedLabel; +} + +class ModelNameColumnRenderer extends ModelsTableColumnRenderer { + static readonly TEMPLATE_ID = 'modelName'; + + readonly templateId: string = ModelNameColumnRenderer.TEMPLATE_ID; + + constructor( + @IHoverService private readonly hoverService: IHoverService + ) { + super(); + } + + renderTemplate(container: HTMLElement): IModelNameColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const nameContainer = DOM.append(container, $('.model-name-container')); + const nameLabel = disposables.add(new HighlightedLabel(DOM.append(nameContainer, $('.model-name')))); + const statusIcon = DOM.append(nameContainer, $('.model-status-icon')); + return { + container, + statusIcon, + nameLabel, + disposables, + elementDisposables + }; + } + + override renderElement(entry: TableEntry, index: number, templateData: IModelNameColumnTemplateData): void { + DOM.clearNode(templateData.statusIcon); + super.renderElement(entry, index, templateData); + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: IModelNameColumnTemplateData): void { + templateData.nameLabel.set(entry.vendorEntry.vendorDisplayName, undefined); + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: IModelNameColumnTemplateData): void { + const { modelEntry, modelNameMatches } = entry; + + templateData.statusIcon.className = 'model-status-icon'; + if (modelEntry.metadata.statusIcon) { + templateData.statusIcon.classList.add(...ThemeIcon.asClassNameArray(modelEntry.metadata.statusIcon)); + templateData.statusIcon.style.display = ''; + } else { + templateData.statusIcon.style.display = 'none'; + } + + templateData.nameLabel.set(modelEntry.metadata.name, modelNameMatches); + + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + markdown.appendMarkdown(`**${entry.modelEntry.metadata.name}**`); + if (entry.modelEntry.metadata.id !== entry.modelEntry.metadata.version) { + markdown.appendMarkdown(`  _${entry.modelEntry.metadata.id}@${entry.modelEntry.metadata.version}_ `); + } else { + markdown.appendMarkdown(`  _${entry.modelEntry.metadata.id}_ `); + } + markdown.appendText(`\n`); + + if (entry.modelEntry.metadata.statusIcon && entry.modelEntry.metadata.tooltip) { + if (entry.modelEntry.metadata.statusIcon) { + markdown.appendMarkdown(`$(${entry.modelEntry.metadata.statusIcon.id}) `); + } + markdown.appendMarkdown(`${entry.modelEntry.metadata.tooltip}`); + markdown.appendText(`\n`); + } + + templateData.elementDisposables.add(this.hoverService.setupDelayedHover(templateData.container!, () => ({ + content: markdown, + appearance: { + showPointer: true, + skipFadeInAnimation: true, + }, + position: { + hoverPosition: HoverPosition.BELOW + } + }))); + } +} + +interface IMultiplierColumnTemplateData extends IModelTableColumnTemplateData { + readonly multiplierElement: HTMLElement; +} + +class MultiplierColumnRenderer extends ModelsTableColumnRenderer { + static readonly TEMPLATE_ID = 'multiplier'; + + readonly templateId: string = MultiplierColumnRenderer.TEMPLATE_ID; + + renderTemplate(container: HTMLElement): IMultiplierColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const multiplierElement = DOM.append(container, $('.model-multiplier')); + return { + container, + multiplierElement, + disposables, + elementDisposables + }; + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: IMultiplierColumnTemplateData): void { + templateData.multiplierElement.textContent = ''; + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: IMultiplierColumnTemplateData): void { + templateData.multiplierElement.textContent = (entry.modelEntry.metadata.detail && entry.modelEntry.metadata.detail.trim().toLowerCase() !== entry.modelEntry.vendor.trim().toLowerCase()) ? entry.modelEntry.metadata.detail : '-'; + } +} + +interface ITokenLimitsColumnTemplateData extends IModelTableColumnTemplateData { + readonly tokenLimitsElement: HTMLElement; +} + +class TokenLimitsColumnRenderer extends ModelsTableColumnRenderer { + static readonly TEMPLATE_ID = 'tokenLimits'; + + readonly templateId: string = TokenLimitsColumnRenderer.TEMPLATE_ID; + + constructor( + @IHoverService private readonly hoverService: IHoverService + ) { + super(); + } + + renderTemplate(container: HTMLElement): ITokenLimitsColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const tokenLimitsElement = DOM.append(container, $('.model-token-limits')); + return { + container, + tokenLimitsElement, + disposables, + elementDisposables + }; + } + + override renderElement(entry: TableEntry, index: number, templateData: ITokenLimitsColumnTemplateData): void { + DOM.clearNode(templateData.tokenLimitsElement); + super.renderElement(entry, index, templateData); + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: ITokenLimitsColumnTemplateData): void { + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: ITokenLimitsColumnTemplateData): void { + const { modelEntry } = entry; + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + if (modelEntry.metadata.maxInputTokens || modelEntry.metadata.maxOutputTokens) { + let addSeparator = false; + markdown.appendMarkdown(`${localize('models.contextSize', 'Context Size')}: `); + if (modelEntry.metadata.maxInputTokens) { + const inputDiv = DOM.append(templateData.tokenLimitsElement, $('.token-limit-item')); + DOM.append(inputDiv, $('span.codicon.codicon-arrow-down')); + const inputText = DOM.append(inputDiv, $('span')); + inputText.textContent = formatTokenCount(modelEntry.metadata.maxInputTokens); + + markdown.appendMarkdown(`$(arrow-down) ${modelEntry.metadata.maxInputTokens} (${localize('models.input', 'Input')})`); + addSeparator = true; + } + if (modelEntry.metadata.maxOutputTokens) { + const outputDiv = DOM.append(templateData.tokenLimitsElement, $('.token-limit-item')); + DOM.append(outputDiv, $('span.codicon.codicon-arrow-up')); + const outputText = DOM.append(outputDiv, $('span')); + outputText.textContent = formatTokenCount(modelEntry.metadata.maxOutputTokens); + if (addSeparator) { + markdown.appendText(` | `); + } + markdown.appendMarkdown(`$(arrow-up) ${modelEntry.metadata.maxOutputTokens} (${localize('models.output', 'Output')})`); + } + } + + templateData.elementDisposables.add(this.hoverService.setupDelayedHover(templateData.container, () => ({ + content: markdown, + appearance: { + showPointer: true, + skipFadeInAnimation: true, + }, + position: { + hoverPosition: HoverPosition.BELOW + } + }))); + } +} + +interface ICapabilitiesColumnTemplateData extends IModelTableColumnTemplateData { + readonly metadataRow: HTMLElement; +} + +class CapabilitiesColumnRenderer extends ModelsTableColumnRenderer { + static readonly TEMPLATE_ID = 'capabilities'; + + readonly templateId: string = CapabilitiesColumnRenderer.TEMPLATE_ID; + + private readonly _onDidClickCapability = new Emitter(); + readonly onDidClickCapability = this._onDidClickCapability.event; + + renderTemplate(container: HTMLElement): ICapabilitiesColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const metadataRow = DOM.append(container, $('.model-metadata')); + return { + container, + metadataRow, + disposables, + elementDisposables + }; + } + + override renderElement(entry: TableEntry, index: number, templateData: ICapabilitiesColumnTemplateData): void { + DOM.clearNode(templateData.metadataRow); + super.renderElement(entry, index, templateData); + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: ICapabilitiesColumnTemplateData): void { + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: ICapabilitiesColumnTemplateData): void { + const { modelEntry, capabilityMatches } = entry; + + if (modelEntry.metadata.capabilities?.toolCalling) { + templateData.elementDisposables.add(this.createCapabilityButton( + templateData.metadataRow, + capabilityMatches?.includes('toolCalling') || false, + localize('models.tools', 'Tools'), + 'tools' + )); + } + + if (modelEntry.metadata.capabilities?.vision) { + templateData.elementDisposables.add(this.createCapabilityButton( + templateData.metadataRow, + capabilityMatches?.includes('vision') || false, + localize('models.vision', 'Vision'), + 'vision' + )); + } + } + + private createCapabilityButton(container: HTMLElement, isActive: boolean, label: string, capability: string): IDisposable { + const disposables = new DisposableStore(); + const buttonContainer = DOM.append(container, $('.model-badge-container')); + const button = disposables.add(new Button(buttonContainer, { secondary: true })); + button.element.classList.add('model-capability'); + button.element.classList.toggle('active', isActive); + button.label = label; + disposables.add(button.onDidClick(() => this._onDidClickCapability.fire(capability))); + return disposables; + } +} + +interface IActionsColumnTemplateData extends IModelTableColumnTemplateData { + readonly actionBar: ActionBar; +} + +class ActionsColumnRenderer extends ModelsTableColumnRenderer { + static readonly TEMPLATE_ID = 'actions'; + + readonly templateId: string = ActionsColumnRenderer.TEMPLATE_ID; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor( + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @ICommandService private readonly commandService: ICommandService, + ) { + super(); + } + + renderTemplate(container: HTMLElement): IActionsColumnTemplateData { + const disposables = new DisposableStore(); + const elementDisposables = new DisposableStore(); + const parent = DOM.append(container, $('.actions-column')); + const actionBar = disposables.add(new ActionBar(parent)); + + return { + container, + actionBar, + disposables, + elementDisposables + }; + } + + override renderElement(entry: TableEntry, index: number, templateData: IActionsColumnTemplateData): void { + templateData.actionBar.clear(); + super.renderElement(entry, index, templateData); + } + + override renderVendorElement(entry: IVendorItemEntry, index: number, templateData: IActionsColumnTemplateData): void { + if (entry.vendorEntry.managementCommand) { + const { vendorEntry } = entry; + const action = toAction({ + id: 'manageVendor', + label: localize('models.manageProvider', 'Manage {0}...', entry.vendorEntry.vendorDisplayName), + class: ThemeIcon.asClassName(Codicon.gear), + run: async () => { + await this.commandService.executeCommand(vendorEntry.managementCommand!, vendorEntry.vendor); + this._onDidChange.fire(); + } + + }); + templateData.actionBar.push(action, { icon: true, label: false }); + } + } + + override renderModelElement(entry: IModelItemEntry, index: number, templateData: IActionsColumnTemplateData): void { + const { modelEntry } = entry; + const isVisible = modelEntry.metadata.isUserSelectable ?? false; + const toggleVisibilityAction = toAction({ + id: 'toggleVisibility', + label: isVisible ? localize('models.hide', 'Hide') : localize('models.show', 'Show'), + class: ThemeIcon.asClassName(isVisible ? Codicon.eye : Codicon.eyeClosed), + tooltip: isVisible ? localize('models.visible', 'Visible') : localize('models.hidden', 'Hidden'), + run: async () => { + const newVisibility = !isVisible; + this.languageModelsService.updateModelPickerPreference(modelEntry.identifier, newVisibility); + this._onDidChange.fire(); + } + }); + templateData.actionBar.push(toggleVisibilityAction, { icon: true, label: false }); + } +} + +function formatTokenCount(count: number): string { + if (count >= 1000000) { + return `${(count / 1000000).toFixed(1)}M`; + } else if (count >= 1000) { + return `${(count / 1000).toFixed(0)}K`; + } + return count.toString(); +} + +export class ChatModelsWidget extends Disposable { + + readonly element: HTMLElement; + private searchWidget!: SuggestEnabledInput; + private searchActionsContainer!: HTMLElement; + private table!: WorkbenchTable; + private tableContainer!: HTMLElement; + private addButtonContainer!: HTMLElement; + private addButton!: Button; + private dropdownActions: IAction[] = []; + private viewModel: ChatModelsViewModel; + private delayedFiltering: Delayer; + + constructor( + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IExtensionService extensionService: IExtensionService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + ) { + super(); + + this.delayedFiltering = new Delayer(300); + this.viewModel = this._register(this.instantiationService.createInstance(ChatModelsViewModel)); + this.element = DOM.$('.models-widget'); + this.create(this.element); + + extensionService.whenInstalledExtensionsRegistered().then(async () => { + await this.viewModel.resolve(); + this.refreshTable(); + }); + this._register(this.viewModel.onDidChangeModelEntries(() => this.refreshTable())); + } + + private create(container: HTMLElement): void { + const searchAndButtonContainer = DOM.append(container, $('.models-search-and-button-container')); + + const placeholder = localize('Search.FullTextSearchPlaceholder', "Type to search..."); + const searchContainer = DOM.append(searchAndButtonContainer, $('.models-search-container')); + this.searchWidget = this._register(this.instantiationService.createInstance( + SuggestEnabledInput, + 'chatModelsWidget.searchbox', + searchContainer, + { + triggerCharacters: ['@', ':'], + provideResults: (query: string) => { + const queryParts = query.split(/\s/g); + const lastPart = queryParts[queryParts.length - 1]; + if (lastPart.startsWith('@provider:')) { + const vendors = this.languageModelsService.getVendors(); + return vendors.map(v => `@provider:"${v.displayName}"`); + } else if (lastPart.startsWith('@capability:')) { + return SEARCH_SUGGESTIONS.CAPABILITIES; + } else if (lastPart.startsWith('@visible:')) { + return SEARCH_SUGGESTIONS.VISIBILITY; + } else if (lastPart.startsWith('@')) { + return SEARCH_SUGGESTIONS.FILTER_TYPES; + } + return []; + } + }, + placeholder, + 'chatModelsWidget:searchinput', + { + placeholderText: placeholder, + styleOverrides: { + inputBorder: settingsTextInputBorder + } + } + )); + this._register(this.searchWidget.onInputDidChange(() => this.filterModels())); + + const filterAction = new ModelsFilterAction(); + const refreshAction = this._register(new Action( + 'workbench.models.refresh', + localize('refresh', "Refresh"), + ThemeIcon.asClassName(Codicon.refresh), + true, + () => this.refresh() + )); + const clearSearchAction = this._register(new Action( + 'workbench.models.clearSearch', + localize('clearSearch', "Clear Search"), + ThemeIcon.asClassName(preferencesClearInputIcon), + false, + () => { + this.searchWidget.setValue(''); + this.searchWidget.focus(); + } + )); + + this._register(this.searchWidget.onInputDidChange(() => { + clearSearchAction.enabled = !!this.searchWidget.getValue(); + })); + + this._register(this.searchWidget.inputWidget.onKeyDown((e) => { + if (e.keyCode === KeyCode.Escape) { + e.preventDefault(); + e.stopPropagation(); + if (this.searchWidget.getValue()) { + this.searchWidget.setValue(''); + this.searchWidget.focus(); + } + } + })); + + this.searchActionsContainer = DOM.append(searchContainer, $('.models-search-actions')); + const actions = [clearSearchAction, refreshAction, filterAction]; + const toolBar = this._register(new ToolBar(this.searchActionsContainer, this.contextMenuService, { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { + if (action.id === filterAction.id) { + return this.instantiationService.createInstance(ModelsSearchFilterDropdownMenuActionViewItem, action, options, this.searchWidget); + } + return undefined; + }, + getKeyBinding: () => undefined + })); + toolBar.setActions(actions); + + // Add padding to input box for toolbar + this.searchWidget.inputWidget.getContainerDomNode().style.paddingRight = `${DOM.getTotalWidth(this.searchActionsContainer) + 12}px`; + + this.addButtonContainer = DOM.append(searchAndButtonContainer, $('.section-title-actions')); + const buttonOptions: IButtonOptions = { + ...defaultButtonStyles, + supportIcons: true, + }; + this.addButton = this._register(new Button(this.addButtonContainer, buttonOptions)); + this.addButton.label = `$(${Codicon.add.id}) ${localize('models.enableModelProvider', 'Add Models...')}`; + this.addButton.element.classList.add('models-add-model-button'); + this.addButton.enabled = false; + this._register(this.addButton.onDidClick((e) => { + if (this.dropdownActions.length > 0) { + this.contextMenuService.showContextMenu({ + getAnchor: () => this.addButton.element, + getActions: () => this.dropdownActions, + }); + } + })); + + // Table container + this.tableContainer = DOM.append(container, $('.models-table-container')); + + // Create table + const twistieColumnRenderer = new ToggleCollapseColumnRenderer(); + const modelNameColumnRenderer = this.instantiationService.createInstance(ModelNameColumnRenderer); + const costColumnRenderer = this.instantiationService.createInstance(MultiplierColumnRenderer); + const tokenLimitsColumnRenderer = this.instantiationService.createInstance(TokenLimitsColumnRenderer); + const capabilitiesColumnRenderer = this.instantiationService.createInstance(CapabilitiesColumnRenderer); + const actionsColumnRenderer = this.instantiationService.createInstance(ActionsColumnRenderer); + + this._register(twistieColumnRenderer.onDidToggleCollapse(vendorId => { + this.viewModel.toggleVendorCollapsed(vendorId); + })); + + this._register(actionsColumnRenderer.onDidChange(e => { + this.viewModel.resolve().then(() => { + this.refreshTable(); + }); + })); + + this._register(capabilitiesColumnRenderer.onDidClickCapability(capability => { + const currentQuery = this.searchWidget.getValue(); + const query = `@capability:${capability}`; + const newQuery = toggleFilter(currentQuery, query); + this.searchWidget.setValue(newQuery); + this.searchWidget.focus(); + })); + + this.table = this._register(this.instantiationService.createInstance( + WorkbenchTable, + 'ModelsWidget', + this.tableContainer, + new Delegate(), + [ + { + label: '', + tooltip: '', + weight: 0, + minimumWidth: 40, + maximumWidth: 40, + templateId: ToggleCollapseColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + { + label: localize('modelName', 'Name'), + tooltip: '', + weight: 0.28, + minimumWidth: 200, + templateId: ModelNameColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + { + label: localize('capabilities', 'Capabilities'), + tooltip: '', + weight: 0.24, + minimumWidth: 180, + templateId: CapabilitiesColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + { + label: localize('tokenLimits', 'Context Size'), + tooltip: '', + weight: 0.16, + minimumWidth: 140, + templateId: TokenLimitsColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + { + label: localize('cost', 'Multiplier'), + tooltip: '', + weight: 0.1, + minimumWidth: 60, + templateId: MultiplierColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + { + label: '', + tooltip: '', + weight: 0.1, + minimumWidth: 64, + maximumWidth: 64, + templateId: ActionsColumnRenderer.TEMPLATE_ID, + project(row: TableEntry): TableEntry { return row; } + }, + ], + [ + twistieColumnRenderer, + modelNameColumnRenderer, + costColumnRenderer, + tokenLimitsColumnRenderer, + capabilitiesColumnRenderer, + actionsColumnRenderer, + ], + { + identityProvider: { getId: (e: TableEntry) => e.id }, + horizontalScrolling: false, + accessibilityProvider: { + getAriaLabel: (e: TableEntry) => { + if (isVendorEntry(e)) { + return localize('vendor.ariaLabel', '{0} provider', e.vendorEntry.vendorDisplayName); + } + return localize('model.ariaLabel', '{0} from {1}', e.modelEntry.metadata.name, e.modelEntry.vendorDisplayName); + }, + getWidgetAriaLabel: () => localize('modelsTable.ariaLabel', 'Language Models') + }, + multipleSelectionSupport: false, + setRowLineHeight: false, + openOnSingleClick: false, + alwaysConsumeMouseWheel: false + } + )) as WorkbenchTable; + + } + + private filterModels(): void { + this.delayedFiltering.trigger(() => this.refreshTable()); + } + + private async refreshTable(): Promise { + const searchValue = this.searchWidget.getValue(); + const modelItems = this.viewModel.fetch(searchValue); + + const vendors = this.languageModelsService.getVendors(); + const vendorsWithModels = new Set(modelItems + .filter((item): item is IModelItemEntry => !isVendorEntry(item)) + .map(item => item.modelEntry.vendor) + ); + const vendorsWithoutModels = vendors.filter(v => !vendorsWithModels.has(v.vendor)); + + this.table.splice(0, this.table.length, modelItems); + + const hasPlan = this.chatEntitlementService.entitlement !== ChatEntitlement.Unknown && this.chatEntitlementService.entitlement !== ChatEntitlement.Available; + this.addButton.enabled = hasPlan && vendorsWithoutModels.length > 0; + + this.dropdownActions = vendorsWithoutModels.map(vendor => toAction({ + id: `enable-${vendor.vendor}`, + label: vendor.displayName, + run: async () => { + await this.enableProvider(vendor.vendor); + } + })); + } + + private async enableProvider(vendorId: string): Promise { + await this.languageModelsService.selectLanguageModels({ vendor: vendorId }, true); + await this.viewModel.resolve(); + this.refreshTable(); + } + + public layout(height: number, width: number): void { + this.searchWidget.layout(new DOM.Dimension(width - this.searchActionsContainer.clientWidth - this.addButtonContainer.clientWidth - 8, 22)); + this.table.layout(height - 40, width); + } + + public focusSearch(): void { + this.searchWidget.focus(); + } + + public search(filter: string): void { + this.focusSearch(); + this.searchWidget.setValue(filter); + } + + public clearSearch(): void { + this.searchWidget.setValue(''); + } + + public async refresh(): Promise { + await this.viewModel.resolve(); + this.refreshTable(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts new file mode 100644 index 00000000000..b8ed68c27f1 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts @@ -0,0 +1,141 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/chatUsageWidget.css'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Emitter } from '../../../../../base/common/event.js'; +import * as DOM from '../../../../../base/browser/dom.js'; +import { localize } from '../../../../../nls.js'; +import { IChatEntitlementService, IQuotaSnapshot } from '../../../../services/chat/common/chatEntitlementService.js'; +import { language } from '../../../../../base/common/platform.js'; +import { safeIntl } from '../../../../../base/common/date.js'; + +const $ = DOM.$; + +export class ChatUsageWidget extends Disposable { + + private readonly _onDidChangeContentHeight = new Emitter(); + readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; + + readonly element: HTMLElement; + private usageSection!: HTMLElement; + + private readonly dateFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric' }); + private readonly dateTimeFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + + constructor( + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService + ) { + super(); + + this.element = DOM.$('.chat-usage-widget'); + this.create(this.element); + this.render(); + + // Update when quotas or entitlements change + this._register(this.chatEntitlementService.onDidChangeQuotaRemaining(() => this.render())); + this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.render())); + } + + private create(container: HTMLElement): void { + // Content container + this.usageSection = DOM.append(container, $('.copilot-usage-section')); + } + + private render(): void { + DOM.clearNode(this.usageSection); + + const { chat: chatQuota, completions: completionsQuota, premiumChat: premiumChatQuota, resetDate, resetDateHasTime } = this.chatEntitlementService.quotas; + + // Anonymous Indicator - show limited quotas + if (this.chatEntitlementService.anonymous && this.chatEntitlementService.sentiment.installed && !completionsQuota && !chatQuota && !premiumChatQuota) { + this.renderLimitedQuotaItem(this.usageSection, localize('completionsLabel', 'Inline Suggestions')); + this.renderLimitedQuotaItem(this.usageSection, localize('chatsLabel', 'Chat messages')); + } + // Copilot Usage section - show detailed breakdown of all quotas + else if (completionsQuota || chatQuota || premiumChatQuota) { + // Inline Suggestions + if (completionsQuota) { + this.renderQuotaItem(this.usageSection, localize('plan.inlineSuggestions', 'Inline Suggestions'), completionsQuota); + } + + // Chat messages + if (chatQuota) { + this.renderQuotaItem(this.usageSection, localize('plan.chatMessages', 'Chat messages'), chatQuota); + } + + // Premium requests + if (premiumChatQuota) { + this.renderQuotaItem(this.usageSection, localize('plan.premiumRequests', 'Premium requests'), premiumChatQuota); + + // Additional overage message + if (premiumChatQuota.overageEnabled) { + const overageMessage = DOM.append(this.usageSection, $('.overage-message')); + overageMessage.textContent = localize('plan.additionalPaidEnabled', 'Additional paid premium requests enabled.'); + } + } + + // Reset date + if (resetDate) { + const resetText = DOM.append(this.usageSection, $('.allowance-resets')); + resetText.textContent = localize('plan.allowanceResets', 'Allowance resets {0}.', resetDateHasTime ? this.dateTimeFormatter.value.format(new Date(resetDate)) : this.dateFormatter.value.format(new Date(resetDate))); + } + } + + // Emit height change + const height = this.element.offsetHeight || 400; + this._onDidChangeContentHeight.fire(height); + } + + private renderQuotaItem(container: HTMLElement, label: string, quota: IQuotaSnapshot): void { + const quotaItem = DOM.append(container, $('.quota-item')); + + const quotaItemHeader = DOM.append(quotaItem, $('.quota-item-header')); + const quotaItemLabel = DOM.append(quotaItemHeader, $('.quota-item-label')); + quotaItemLabel.textContent = label; + + const quotaItemValue = DOM.append(quotaItemHeader, $('.quota-item-value')); + if (quota.unlimited) { + quotaItemValue.textContent = localize('plan.included', 'Included'); + } else { + quotaItemValue.textContent = localize('plan.included', 'Included'); + } + + // Progress bar - using same structure as chat status + const progressBarContainer = DOM.append(quotaItem, $('.quota-bar')); + const progressBar = DOM.append(progressBarContainer, $('.quota-bit')); + const percentageUsed = this.getQuotaPercentageUsed(quota); + progressBar.style.width = percentageUsed + '%'; + + // Apply warning/error classes based on usage + if (percentageUsed >= 90) { + quotaItem.classList.add('error'); + } else if (percentageUsed >= 75) { + quotaItem.classList.add('warning'); + } + } + + private getQuotaPercentageUsed(quota: IQuotaSnapshot): number { + if (quota.unlimited) { + return 0; + } + return Math.max(0, 100 - quota.percentRemaining); + } + + private renderLimitedQuotaItem(container: HTMLElement, label: string): void { + const quotaItem = DOM.append(container, $('.quota-item')); + + const quotaItemHeader = DOM.append(quotaItem, $('.quota-item-header')); + const quotaItemLabel = DOM.append(quotaItemHeader, $('.quota-item-label')); + quotaItemLabel.textContent = label; + + const quotaItemValue = DOM.append(quotaItemHeader, $('.quota-item-value')); + quotaItemValue.textContent = localize('quotaLimited', 'Limited'); + + // Progress bar - using same structure as chat status + const progressBarContainer = DOM.append(quotaItem, $('.quota-bar')); + DOM.append(progressBarContainer, $('.quota-bit')); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css new file mode 100644 index 00000000000..e5e4be4fd5d --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.ai-management-editor { + height: 100%; + overflow: hidden; + max-width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; +} + +.ai-models-management-editor { + height: 100%; + overflow: hidden; + max-width: 1200px; + margin: 0 auto; + padding: 15px; +} + +/* Header - spans full width */ +.ai-management-editor .ai-management-header { + box-sizing: border-box; + margin-left: 20px; + padding: 10px 15px 5px 15px; + border-bottom: 1px solid var(--vscode-settings-headerBorder); + flex-shrink: 0; +} + +/* Split view container - takes remaining space */ +.ai-management-editor .split-view-container { + flex: 1; + min-height: 0; + position: relative; +} + +.ai-management-editor .sidebar-view, +.ai-management-editor .contents-view { + height: 100%; + overflow: hidden; +} + +.ai-management-editor .contents-container { + height: 100%; + overflow: hidden; +} + +.ai-management-editor .sidebar-container { + padding-left: 20px; + padding-top: 15px; + height: 100%; + overflow: hidden; + box-sizing: border-box; +} + +.ai-management-editor .sidebar-container .section-list-item { + padding-left: 20px; + display: flex; + align-items: center; +} + +.ai-management-editor .sidebar-container .section-list-item > .section-list-item-label { + overflow: hidden; + text-overflow: ellipsis; +} + +.ai-management-editor .ai-management-body { + padding: 15px; + height: 100%; + box-sizing: border-box; +} + +.ai-management-editor .header-title-container { + display: flex; + align-items: end; + justify-content: space-between; + margin-bottom: 0; +} + +.ai-management-editor .header-title-wrapper { + display: flex; + align-items: center; + gap: 4px; +} + +.ai-management-editor .ai-management-editor-title { + font-size: 26px; + font-weight: 600; + color: var(--vscode-foreground); +} + +.ai-management-editor .hide { + display: none !important; +} + +.ai-management-editor .plan-badge { + padding: 4px 12px; + border-radius: 12px; + background-color: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + font-weight: 500; + margin-left: 4px; +} + +.ai-management-editor .header-upgrade-button-container { + margin-left: auto; +} + +.ai-management-editor .header-upgrade-button { + white-space: nowrap; +} + +/* Widget sections */ +.ai-management-editor .ai-management-body > * { + margin-bottom: 20px; +} + +.ai-management-editor .ai-management-body > *:last-child { + margin-bottom: 0; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css new file mode 100644 index 00000000000..c961b39171a --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** Search and button container styling **/ + +.models-widget .models-search-and-button-container { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.models-widget .models-search-container { + flex: 1; + position: relative; +} + +.models-widget .models-search-container .settings-header-widget { + width: 100%; +} + +.models-widget .models-search-container .models-search-actions { + position: absolute; + top: 0; + right: 0; + height: 100%; + margin-right: 1px; + display: flex; + align-items: center; +} + +.models-widget .models-search-container .models-search-actions .monaco-toolbar { + height: 100%; +} + +.models-widget .models-search-container .models-search-actions .action-label { + padding: 3px; + margin-left: 0; + box-sizing: content-box; +} + +/** Table styling **/ + +.models-widget .models-table-container { + width: 100%; + border-spacing: 0; + border-collapse: separate; +} + +.models-widget .models-table-container .monaco-table-tr { + cursor: default; +} + +.models-widget .models-table-container .monaco-table-td { + align-items: center; + display: flex; + overflow: hidden; + padding-left: 10px; +} + +.models-widget .models-table-container .monaco-table-td[data-col-index="1"] { + padding-left: 8px; +} + +.models-widget .models-table-container .monaco-table-th { + padding-left: 10px; + background-color: var(--vscode-keybindingTable-headerBackground); +} + +.models-widget .models-table-container .monaco-table-th[data-col-index="1"] { + padding-left: 8px; +} + + +/** Twistie column styling **/ + +.models-widget .models-table-container .monaco-table-td .models-twistie { + cursor: pointer; + display: flex; + align-items: center; + align-self: stretch; + justify-content: center; + padding: 0 6px; +} + +.models-widget .models-table-container .monaco-table-td .models-twistie:hover { + background-color: var(--vscode-toolbar-hoverBackground); + border-radius: 3px; +} + +/** Model Name column styling **/ + +.models-widget .models-table-container .monaco-table-td .model-name-container { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; + width: 100%; +} + +.models-widget .models-table-container .monaco-table-td .model-name-container .model-status-icon { + flex-shrink: 0; + margin-left: auto; + margin-right: 4px; +} + +.models-widget .models-table-container .monaco-table-td .model-name-container .model-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 0 1 auto; +} + +/** Actions column styling **/ + +.models-widget .models-table-container .monaco-table-td .actions-column { + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} + +.models-widget .models-table-container .monaco-table-td .actions-column .monaco-action-bar { + margin-right: 9px; +} + +/** Cost column styling **/ + +.models-widget .models-table-container .monaco-table-td .model-multiplier { + overflow: hidden; + text-overflow: ellipsis; +} + +/** Token Limits column styling **/ + +.models-widget .models-table-container .monaco-table-td .model-token-limits { + display: flex; + gap: 12px; + white-space: nowrap; +} + +.models-widget .models-table-container .monaco-table-td .model-token-limits .token-limit-item { + display: flex; + align-items: center; + gap: 4px; +} + +.models-widget .models-table-container .monaco-table-td .model-token-limits .codicon { + font-size: 12px; +} + +/** Capabilities column styling **/ + +.models-widget .models-table-container .monaco-table-td .model-metadata { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.models-widget .models-table-container .monaco-table-td .model-capability { + padding: 0 6px; + border-radius: 3px; + font-size: 11px; + color: var(--vscode-radio-inactiveForeground); + background-color: var(--vscode-radio-inactiveBackground); + border-color: var(--vscode-radio-inactiveBorder, transparent); +} + +.models-widget .models-table-container .monaco-table-td .model-capability.active { + color: var(--vscode-radio-activeForeground); + background-color: var(--vscode-radio-activeBackground); + border-color: var(--vscode-radio-activeBorder, transparent); +} + +/** Vendor row styling **/ + +.models-widget .models-table-container .models-vendor-row { + background-color: var(--vscode-keybindingTable-headerBackground); +} + +.models-widget .models-table-container .monaco-table-tr:hover .models-vendor-row { + background-color: var(--vscode-toolbar-hoverBackground); +} + +.models-widget .models-table-container .models-vendor-row .model-name { + font-weight: bold; +} + +/** Row alternating colors **/ +.models-widget .models-table-container .monaco-table .monaco-list-row[data-parity=odd]:not(.focused):not(.selected):not(:hover) .monaco-table-tr:not(.models-vendor-row), +.models-widget .models-table-container .monaco-table .monaco-list:not(:focus) .monaco-list-row[data-parity=odd].focused:not(.selected):not(:hover) .monaco-table-tr:not(.models-vendor-row), +.models-widget .models-table-container .monaco-table .monaco-list:not(.focused) .monaco-list-row[data-parity=odd].focused:not(.selected):not(:hover) .monaco-table-tr:not(.models-vendor-row) { + background-color: var(--vscode-editor-background); +} diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css new file mode 100644 index 00000000000..1ce18d0901d --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.chat-usage-widget .quota-item { + margin-bottom: 12px; +} + +.chat-usage-widget .quota-item-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; +} + +.chat-usage-widget .quota-item-label { + color: var(--vscode-foreground); +} + +.chat-usage-widget .quota-item-value { + color: var(--vscode-descriptionForeground); +} + +/* Progress bar - matching chat status implementation */ +.chat-usage-widget .quota-item .quota-bar { + width: 100%; + height: 4px; + background-color: var(--vscode-gauge-background); + border-radius: 4px; + border: 1px solid var(--vscode-gauge-border); + margin: 4px 0; +} + +.chat-usage-widget .quota-item .quota-bar .quota-bit { + height: 100%; + background-color: var(--vscode-gauge-foreground); + border-radius: 4px; + transition: width 0.3s ease; +} + +.chat-usage-widget .quota-item.warning .quota-bar { + background-color: var(--vscode-gauge-warningBackground); +} + +.chat-usage-widget .quota-item.warning .quota-bar .quota-bit { + background-color: var(--vscode-gauge-warningForeground); +} + +.chat-usage-widget .quota-item.error .quota-bar { + background-color: var(--vscode-gauge-errorBackground); +} + +.chat-usage-widget .quota-item.error .quota-bar .quota-bit { + background-color: var(--vscode-gauge-errorForeground); +} + +.chat-usage-widget .overage-message { + font-size: 13px; + color: var(--vscode-foreground); + margin-top: 12px; + margin-bottom: 8px; +} + +.chat-usage-widget .allowance-resets { + font-size: 13px; + color: var(--vscode-foreground); + margin-top: 12px; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatStatus.ts b/src/vs/workbench/contrib/chat/browser/chatStatus.ts index 64c9bcc57eb..44b987f5176 100644 --- a/src/vs/workbench/contrib/chat/browser/chatStatus.ts +++ b/src/vs/workbench/contrib/chat/browser/chatStatus.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import './media/chatStatus.css'; -import { safeIntl } from '../../../../base/common/date.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { language } from '../../../../base/common/platform.js'; import { localize } from '../../../../nls.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, ShowTooltipCommand, StatusbarAlignment, StatusbarEntryKind } from '../../../services/statusbar/browser/statusbar.js'; import { $, addDisposableListener, append, clearNode, disposableWindowInterval, EventHelper, EventType, getWindow } from '../../../../base/browser/dom.js'; -import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, IQuotaSnapshot, isProUser } from '../../../services/chat/common/chatEntitlementService.js'; +import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../services/chat/common/chatEntitlementService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { defaultButtonStyles, defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; @@ -47,6 +45,7 @@ import { IChatSessionsService } from '../common/chatSessionsService.js'; import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { AGENT_SESSIONS_VIEWLET_ID } from '../common/constants.js'; +import { ChatUsageWidget } from './chatManagement/chatUsageWidget.js'; const gaugeForeground = registerColor('gauge.foreground', { dark: inputValidationInfoBorder, @@ -337,11 +336,6 @@ class ChatStatusDashboard extends Disposable { private readonly element = $('div.chat-status-bar-entry-tooltip'); - private readonly dateFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric' }); - private readonly dateTimeFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); - private readonly quotaPercentageFormatter = safeIntl.NumberFormat(undefined, { maximumFractionDigits: 1, minimumFractionDigits: 0 }); - private readonly quotaOverageFormatter = safeIntl.NumberFormat(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 0 }); - private readonly entryDisposables = this._register(new MutableDisposable()); constructor( @@ -382,7 +376,7 @@ class ChatStatusDashboard extends Disposable { }; // Quota Indicator - const { chat: chatQuota, completions: completionsQuota, premiumChat: premiumChatQuota, resetDate, resetDateHasTime } = this.chatEntitlementService.quotas; + const { chat: chatQuota, completions: completionsQuota, premiumChat: premiumChatQuota } = this.chatEntitlementService.quotas; if (chatQuota || completionsQuota || premiumChatQuota) { addSeparator(localize('usageTitle', "Copilot Usage"), toAction({ @@ -393,45 +387,22 @@ class ChatStatusDashboard extends Disposable { run: () => this.runCommandAndClose(() => this.openerService.open(URI.parse(defaultChat.manageSettingsUrl))), })); - const completionsQuotaIndicator = completionsQuota && (completionsQuota.total > 0 || completionsQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, completionsQuota, localize('completionsLabel', "Inline Suggestions"), false) : undefined; - const chatQuotaIndicator = chatQuota && (chatQuota.total > 0 || chatQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, chatQuota, localize('chatsLabel', "Chat messages"), false) : undefined; - const premiumChatQuotaIndicator = premiumChatQuota && (premiumChatQuota.total > 0 || premiumChatQuota.unlimited) ? this.createQuotaIndicator(this.element, disposables, premiumChatQuota, localize('premiumChatsLabel', "Premium requests"), true) : undefined; - - if (resetDate) { - this.element.appendChild($('div.description', undefined, localize('limitQuota', "Allowance resets {0}.", resetDateHasTime ? this.dateTimeFormatter.value.format(new Date(resetDate)) : this.dateFormatter.value.format(new Date(resetDate))))); - } + // Create and append usage widget + const usageWidget = disposables.add(new ChatUsageWidget(this.chatEntitlementService)); + this.element.appendChild(usageWidget.element); if (this.chatEntitlementService.entitlement === ChatEntitlement.Free && (Number(chatQuota?.percentRemaining) <= 25 || Number(completionsQuota?.percentRemaining) <= 25)) { - const upgradeProButton = disposables.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: canUseChat(this.chatEntitlementService) /* use secondary color when chat can still be used */ })); - upgradeProButton.label = localize('upgradeToCopilotPro', "Upgrade to GitHub Copilot Pro"); - disposables.add(upgradeProButton.onDidClick(() => this.runCommandAndClose('workbench.action.chat.upgradePlan'))); + const upgradeButton = disposables.add(new Button(this.element, { ...defaultButtonStyles, hoverDelegate: nativeHoverDelegate, secondary: canUseChat(this.chatEntitlementService) /* use secondary color when chat can still be used */ })); + upgradeButton.label = localize('upgradeToCopilotPro', "Upgrade to GitHub Copilot Pro"); + disposables.add(upgradeButton.onDidClick(() => this.runCommandAndClose('workbench.action.chat.upgradePlan'))); } - - (async () => { - await this.chatEntitlementService.update(token); - if (token.isCancellationRequested) { - return; - } - - const { chat: chatQuota, completions: completionsQuota, premiumChat: premiumChatQuota } = this.chatEntitlementService.quotas; - if (completionsQuota) { - completionsQuotaIndicator?.(completionsQuota); - } - if (chatQuota) { - chatQuotaIndicator?.(chatQuota); - } - if (premiumChatQuota) { - premiumChatQuotaIndicator?.(premiumChatQuota); - } - })(); - } - - // Anonymous Indicator + } // Anonymous Indicator else if (this.chatEntitlementService.anonymous && this.chatEntitlementService.sentiment.installed) { addSeparator(localize('anonymousTitle', "Copilot Usage")); - this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('completionsLabel', "Inline Suggestions"), false); - this.createQuotaIndicator(this.element, disposables, localize('quotaLimited', "Limited"), localize('chatsLabel', "Chat messages"), false); + // Create and append usage widget for anonymous users + const usageWidget = disposables.add(new ChatUsageWidget(this.chatEntitlementService)); + this.element.appendChild(usageWidget.element); } // Chat sessions @@ -634,75 +605,6 @@ class ChatStatusDashboard extends Disposable { this.hoverService.hideHover(true); } - private createQuotaIndicator(container: HTMLElement, disposables: DisposableStore, quota: IQuotaSnapshot | string, label: string, supportsOverage: boolean): (quota: IQuotaSnapshot | string) => void { - const quotaValue = $('span.quota-value'); - const quotaBit = $('div.quota-bit'); - const overageLabel = $('span.overage-label'); - - const quotaIndicator = container.appendChild($('div.quota-indicator', undefined, - $('div.quota-label', undefined, - $('span', undefined, label), - quotaValue - ), - $('div.quota-bar', undefined, - quotaBit - ), - $('div.description', undefined, - overageLabel - ) - )); - - if (supportsOverage && (this.chatEntitlementService.entitlement === ChatEntitlement.Pro || this.chatEntitlementService.entitlement === ChatEntitlement.ProPlus)) { - const manageOverageButton = disposables.add(new Button(quotaIndicator, { ...defaultButtonStyles, secondary: true, hoverDelegate: nativeHoverDelegate })); - manageOverageButton.label = localize('enableAdditionalUsage', "Manage paid premium requests"); - disposables.add(manageOverageButton.onDidClick(() => this.runCommandAndClose(() => this.openerService.open(URI.parse(defaultChat.manageOverageUrl))))); - } - - const update = (quota: IQuotaSnapshot | string) => { - quotaIndicator.classList.remove('error'); - quotaIndicator.classList.remove('warning'); - - let usedPercentage: number; - if (typeof quota === 'string' || quota.unlimited) { - usedPercentage = 0; - } else { - usedPercentage = Math.max(0, 100 - quota.percentRemaining); - } - - if (typeof quota === 'string') { - quotaValue.textContent = quota; - } else if (quota.unlimited) { - quotaValue.textContent = localize('quotaUnlimited', "Included"); - } else if (quota.overageCount) { - quotaValue.textContent = localize('quotaDisplayWithOverage', "+{0} requests", this.quotaOverageFormatter.value.format(quota.overageCount)); - } else { - quotaValue.textContent = localize('quotaDisplay', "{0}%", this.quotaPercentageFormatter.value.format(usedPercentage)); - } - - quotaBit.style.width = `${usedPercentage}%`; - - if (usedPercentage >= 90) { - quotaIndicator.classList.add('error'); - } else if (usedPercentage >= 75) { - quotaIndicator.classList.add('warning'); - } - - if (supportsOverage) { - if (typeof quota !== 'string' && quota?.overageEnabled) { - overageLabel.textContent = localize('additionalUsageEnabled', "Additional paid premium requests enabled."); - } else { - overageLabel.textContent = localize('additionalUsageDisabled', "Additional paid premium requests disabled."); - } - } else { - overageLabel.textContent = ''; - } - }; - - update(quota); - - return update; - } - private createSettings(container: HTMLElement, disposables: DisposableStore): HTMLElement { const modeId = this.editorService.activeTextEditorLanguageId; const settings = container.appendChild($('div.settings')); diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 15f60a21ccd..61f16849f71 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -119,5 +119,5 @@ export function isSupportedChatFileScheme(accessor: ServicesAccessor, scheme: st } export const AGENT_SESSIONS_VIEWLET_ID = 'workbench.view.chat.sessions'; // TODO@bpasero clear once settled - +export const MANAGE_CHAT_COMMAND_ID = 'workbench.action.chat.manage'; export const ChatEditorTitleMaxLength = 30; diff --git a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts new file mode 100644 index 00000000000..d241e421127 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts @@ -0,0 +1,533 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatProvider, ILanguageModelChatSelector, ILanguageModelsService, IUserFriendlyLanguageModel } from '../../common/languageModels.js'; +import { ChatModelsViewModel, IModelItemEntry, IVendorItemEntry, isVendorEntry } from '../../browser/chatManagement/chatModelsViewModel.js'; +import { IChatEntitlementService, ChatEntitlement } from '../../../../services/chat/common/chatEntitlementService.js'; +import { IObservable, observableValue } from '../../../../../base/common/observable.js'; +import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; + +class MockLanguageModelsService implements ILanguageModelsService { + _serviceBrand: undefined; + + private vendors: IUserFriendlyLanguageModel[] = []; + private models = new Map(); + private modelsByVendor = new Map(); + + private readonly _onDidChangeLanguageModels = new Emitter(); + readonly onDidChangeLanguageModels = this._onDidChangeLanguageModels.event; + + addVendor(vendor: IUserFriendlyLanguageModel): void { + this.vendors.push(vendor); + this.modelsByVendor.set(vendor.vendor, []); + } + + addModel(vendorId: string, identifier: string, metadata: ILanguageModelChatMetadata): void { + this.models.set(identifier, metadata); + const models = this.modelsByVendor.get(vendorId) || []; + models.push(identifier); + this.modelsByVendor.set(vendorId, models); + } + + registerLanguageModelProvider(vendor: string, provider: ILanguageModelChatProvider): IDisposable { + throw new Error('Method not implemented.'); + } + + updateModelPickerPreference(modelIdentifier: string, showInModelPicker: boolean): void { + throw new Error('Method not implemented.'); + } + + getVendors(): IUserFriendlyLanguageModel[] { + return this.vendors; + } + + getLanguageModelIds(): string[] { + return Array.from(this.models.keys()); + } + + lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined { + return this.models.get(identifier); + } + + getLanguageModels(): ILanguageModelChatMetadataAndIdentifier[] { + const result: ILanguageModelChatMetadataAndIdentifier[] = []; + for (const [identifier, metadata] of this.models.entries()) { + result.push({ identifier, metadata }); + } + return result; + } + + setContributedSessionModels(): void { + } + + clearContributedSessionModels(): void { + } + + async selectLanguageModels(selector: ILanguageModelChatSelector, allowHidden?: boolean): Promise { + if (selector.vendor) { + return this.modelsByVendor.get(selector.vendor) || []; + } + return Array.from(this.models.keys()); + } + + sendChatRequest(): Promise { + throw new Error('Method not implemented.'); + } + + computeTokenLength(): Promise { + throw new Error('Method not implemented.'); + } +} + +class MockChatEntitlementService implements IChatEntitlementService { + _serviceBrand: undefined; + + private readonly _onDidChangeEntitlement = new Emitter(); + readonly onDidChangeEntitlement = this._onDidChangeEntitlement.event; + + readonly entitlement = ChatEntitlement.Unknown; + readonly entitlementObs: IObservable = observableValue('entitlement', ChatEntitlement.Unknown); + + readonly organisations: string[] | undefined = undefined; + readonly isInternal = false; + readonly sku: string | undefined = undefined; + + readonly onDidChangeQuotaExceeded = Event.None; + readonly onDidChangeQuotaRemaining = Event.None; + + readonly quotas = { + chat: { + total: 100, + remaining: 100, + percentRemaining: 100, + overageEnabled: false, + overageCount: 0, + unlimited: false + }, + completions: { + total: 100, + remaining: 100, + percentRemaining: 100, + overageEnabled: false, + overageCount: 0, + unlimited: false + } + }; + + readonly onDidChangeSentiment = Event.None; + readonly sentiment: any = { installed: true, hidden: false, disabled: false }; + readonly sentimentObs: IObservable = observableValue('sentiment', { installed: true, hidden: false, disabled: false }); + + readonly onDidChangeAnonymous = Event.None; + readonly anonymous = false; + readonly anonymousObs: IObservable = observableValue('anonymous', false); + + fireEntitlementChange(): void { + this._onDidChangeEntitlement.fire(); + } + + async update(): Promise { + // Not needed for tests + } +} + +suite('ChatModelsViewModel', () => { + let store: DisposableStore; + let languageModelsService: MockLanguageModelsService; + let chatEntitlementService: MockChatEntitlementService; + let viewModel: ChatModelsViewModel; + + setup(async () => { + store = new DisposableStore(); + languageModelsService = new MockLanguageModelsService(); + chatEntitlementService = new MockChatEntitlementService(); + + // Setup test data + languageModelsService.addVendor({ + vendor: 'copilot', + displayName: 'GitHub Copilot', + managementCommand: undefined, + when: undefined + }); + + languageModelsService.addVendor({ + vendor: 'openai', + displayName: 'OpenAI', + managementCommand: undefined, + when: undefined + }); + + languageModelsService.addModel('copilot', 'copilot-gpt-4', { + extension: new ExtensionIdentifier('github.copilot'), + id: 'gpt-4', + name: 'GPT-4', + family: 'gpt-4', + version: '1.0', + vendor: 'copilot', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Copilot', order: 1 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: true, + agentMode: false + } + }); + + languageModelsService.addModel('copilot', 'copilot-gpt-4o', { + extension: new ExtensionIdentifier('github.copilot'), + id: 'gpt-4o', + name: 'GPT-4o', + family: 'gpt-4', + version: '1.0', + vendor: 'copilot', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Copilot', order: 1 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: true, + agentMode: true + } + }); + + languageModelsService.addModel('openai', 'openai-gpt-3.5', { + extension: new ExtensionIdentifier('openai.api'), + id: 'gpt-3.5-turbo', + name: 'GPT-3.5 Turbo', + family: 'gpt-3.5', + version: '1.0', + vendor: 'openai', + maxInputTokens: 4096, + maxOutputTokens: 2048, + modelPickerCategory: { label: 'OpenAI', order: 2 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: false, + agentMode: false + } + }); + + languageModelsService.addModel('openai', 'openai-gpt-4-vision', { + extension: new ExtensionIdentifier('openai.api'), + id: 'gpt-4-vision', + name: 'GPT-4 Vision', + family: 'gpt-4', + version: '1.0', + vendor: 'openai', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'OpenAI', order: 2 }, + isUserSelectable: false, + capabilities: { + toolCalling: false, + vision: true, + agentMode: false + } + }); + + viewModel = store.add(new ChatModelsViewModel( + languageModelsService, + chatEntitlementService + )); + + await viewModel.resolve(); + }); + + teardown(() => { + store.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('should fetch all models without filters', () => { + const results = viewModel.fetch(''); + + // Should have 2 vendor entries and 4 model entries (grouped by vendor) + assert.strictEqual(results.length, 6); + + const vendors = results.filter(isVendorEntry); + assert.strictEqual(vendors.length, 2); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 4); + }); + + test('should filter by provider name', () => { + const results = viewModel.fetch('@provider:copilot'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => m.modelEntry.vendor === 'copilot')); + }); + + test('should filter by provider display name', () => { + const results = viewModel.fetch('@provider:OpenAI'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => m.modelEntry.vendor === 'openai')); + }); + + test('should filter by multiple providers with OR logic', () => { + const results = viewModel.fetch('@provider:copilot @provider:openai'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 4); + }); + + test('should filter by single capability - tools', () => { + const results = viewModel.fetch('@capability:tools'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 3); + assert.ok(models.every(m => m.modelEntry.metadata.capabilities?.toolCalling === true)); + }); + + test('should filter by single capability - vision', () => { + const results = viewModel.fetch('@capability:vision'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 3); + assert.ok(models.every(m => m.modelEntry.metadata.capabilities?.vision === true)); + }); + + test('should filter by single capability - agent', () => { + const results = viewModel.fetch('@capability:agent'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4o'); + }); + + test('should filter by multiple capabilities with AND logic', () => { + const results = viewModel.fetch('@capability:tools @capability:vision'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Should only return models that have BOTH tools and vision + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => + m.modelEntry.metadata.capabilities?.toolCalling === true && + m.modelEntry.metadata.capabilities?.vision === true + )); + }); + + test('should filter by three capabilities with AND logic', () => { + const results = viewModel.fetch('@capability:tools @capability:vision @capability:agent'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Should only return gpt-4o which has all three + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4o'); + }); + + test('should return no results when filtering by incompatible capabilities', () => { + const results = viewModel.fetch('@capability:vision @capability:agent'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Only gpt-4o has both vision and agent, but gpt-4-vision doesn't have agent + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4o'); + }); + + test('should filter by visibility - visible:true', () => { + const results = viewModel.fetch('@visible:true'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 3); + assert.ok(models.every(m => m.modelEntry.metadata.isUserSelectable === true)); + }); + + test('should filter by visibility - visible:false', () => { + const results = viewModel.fetch('@visible:false'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.isUserSelectable, false); + }); + + test('should combine provider and capability filters', () => { + const results = viewModel.fetch('@provider:copilot @capability:vision'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => + m.modelEntry.vendor === 'copilot' && + m.modelEntry.metadata.capabilities?.vision === true + )); + }); + + test('should combine provider, capability, and visibility filters', () => { + const results = viewModel.fetch('@provider:openai @capability:vision @visible:false'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4-vision'); + }); + + test('should filter by text matching model name', () => { + const results = viewModel.fetch('GPT-4o'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.name, 'GPT-4o'); + assert.ok(models[0].modelNameMatches); + }); + + test('should filter by text matching vendor name', () => { + const results = viewModel.fetch('GitHub'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => m.modelEntry.vendorDisplayName === 'GitHub Copilot')); + }); + + test('should combine text search with capability filter', () => { + const results = viewModel.fetch('@capability:tools GPT'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Should match all models with tools capability and 'GPT' in name + assert.strictEqual(models.length, 3); + assert.ok(models.every(m => m.modelEntry.metadata.capabilities?.toolCalling === true)); + }); + + test('should handle empty search value', () => { + const results = viewModel.fetch(''); + + // Should return all models grouped by vendor + assert.ok(results.length > 0); + }); + + test('should handle search value with only whitespace', () => { + const results = viewModel.fetch(' '); + + // Should return all models grouped by vendor + assert.ok(results.length > 0); + }); + + test('should match capability text in free text search', () => { + const results = viewModel.fetch('vision'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Should match models that have vision capability or "vision" in their name + assert.ok(models.length > 0); + assert.ok(models.every(m => + m.modelEntry.metadata.capabilities?.vision === true || + m.modelEntry.metadata.name.toLowerCase().includes('vision') + )); + }); + + test('should toggle vendor collapsed state', () => { + viewModel.toggleVendorCollapsed('copilot'); + + const results = viewModel.fetch(''); + const copilotVendor = results.find(r => isVendorEntry(r) && (r as IVendorItemEntry).vendorEntry.vendor === 'copilot') as IVendorItemEntry; + + assert.ok(copilotVendor); + assert.strictEqual(copilotVendor.collapsed, true); + + // Models should not be shown when vendor is collapsed + const copilotModelsAfterCollapse = results.filter(r => + !isVendorEntry(r) && (r as IModelItemEntry).modelEntry.vendor === 'copilot' + ); + assert.strictEqual(copilotModelsAfterCollapse.length, 0); + + // Toggle back + viewModel.toggleVendorCollapsed('copilot'); + const resultsAfterExpand = viewModel.fetch(''); + const copilotModelsAfterExpand = resultsAfterExpand.filter(r => + !isVendorEntry(r) && (r as IModelItemEntry).modelEntry.vendor === 'copilot' + ); + assert.strictEqual(copilotModelsAfterExpand.length, 2); + }); + + test('should fire onDidChangeModelEntries when entitlement changes', async () => { + let fired = false; + store.add(viewModel.onDidChangeModelEntries(() => { + fired = true; + })); + + chatEntitlementService.fireEntitlementChange(); + + // Wait a bit for async resolve + await new Promise(resolve => setTimeout(resolve, 10)); + + assert.strictEqual(fired, true); + }); + + test('should handle quoted search strings', () => { + // When a search string is fully quoted (starts and ends with quotes), + // the completeMatch flag is set to true, which currently skips all matching + // This test verifies the quotes are processed without errors + const results = viewModel.fetch('"GPT"'); + + // The function should complete without error + // Note: complete match logic (both quotes) currently doesn't perform matching + assert.ok(Array.isArray(results)); + }); + + test('should remove filter keywords from text search', () => { + const results = viewModel.fetch('@provider:copilot @capability:vision GPT'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + // Should only search 'GPT' in model names, not the filter keywords + assert.strictEqual(models.length, 2); + assert.ok(models.every(m => m.modelEntry.vendor === 'copilot')); + }); + + test('should handle case-insensitive capability matching', () => { + const results1 = viewModel.fetch('@capability:TOOLS'); + const results2 = viewModel.fetch('@capability:tools'); + const results3 = viewModel.fetch('@capability:Tools'); + + const models1 = results1.filter(r => !isVendorEntry(r)); + const models2 = results2.filter(r => !isVendorEntry(r)); + const models3 = results3.filter(r => !isVendorEntry(r)); + + assert.strictEqual(models1.length, models2.length); + assert.strictEqual(models2.length, models3.length); + }); + + test('should support toolcalling alias for tools capability', () => { + const resultsTools = viewModel.fetch('@capability:tools'); + const resultsToolCalling = viewModel.fetch('@capability:toolcalling'); + + const modelsTools = resultsTools.filter(r => !isVendorEntry(r)); + const modelsToolCalling = resultsToolCalling.filter(r => !isVendorEntry(r)); + + assert.strictEqual(modelsTools.length, modelsToolCalling.length); + }); + + test('should support agentmode alias for agent capability', () => { + const resultsAgent = viewModel.fetch('@capability:agent'); + const resultsAgentMode = viewModel.fetch('@capability:agentmode'); + + const modelsAgent = resultsAgent.filter(r => !isVendorEntry(r)); + const modelsAgentMode = resultsAgentMode.filter(r => !isVendorEntry(r)); + + assert.strictEqual(modelsAgent.length, modelsAgentMode.length); + }); + + test('should include matched capabilities in results', () => { + const results = viewModel.fetch('@capability:tools @capability:vision'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.ok(models.length > 0); + + for (const model of models) { + assert.ok(model.capabilityMatches); + assert.ok(model.capabilityMatches.length > 0); + // Should include both toolCalling and vision + assert.ok(model.capabilityMatches.some(c => c === 'toolCalling' || c === 'vision')); + } + }); +}); From 5b8296c45664992e9730529b8ea48a00fb4f4417 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:21:22 -0700 Subject: [PATCH 1899/4355] do not cancel current session when editing a previous request (#274192) --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2db00c9ee81..a40f7a56722 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1877,12 +1877,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } private clickedRequest(item: IChatListItemTemplate) { - - // cancel current request before we start editing. - if (this.viewModel) { - this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionId); - } - const currentElement = item.currentElement; if (isRequestVM(currentElement) && !this.viewModel?.editing) { From 0681eb9fd0d8c3cd1a7e97799fbec3119fcdd127 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 30 Oct 2025 15:50:23 -0700 Subject: [PATCH 1900/4355] Updates to Go To quick access (#273668) * Don't wait for async setting update of zero-based offset value * Fix labels/descriptions * Update unit-tests --- .../browser/gotoLineQuickAccess.ts | 165 +++++++++--------- .../test/browser/gotoLineQuickAccess.test.ts | 8 +- .../quickaccess/gotoLineQuickAccess.ts | 2 +- 3 files changed, 84 insertions(+), 91 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 36f333b4d33..baf84ba1fb1 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -24,8 +24,23 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor static PREFIX = ':'; - constructor(private useZeroBasedOffset: { value: boolean } = { value: false }) { + private _useZeroBasedOffset: boolean; + + constructor(private useZeroBasedOffsetSetting?: { value: boolean }) { super({ canAcceptInBackground: true }); + this._useZeroBasedOffset = useZeroBasedOffsetSetting?.value === true; + } + + private get useZeroBasedOffset() { + return this._useZeroBasedOffset; + } + + private set useZeroBasedOffset(value: boolean) { + this._useZeroBasedOffset = value; + if (this.useZeroBasedOffsetSetting) { + // Asynchronously persist the setting change + this.useZeroBasedOffsetSetting.value = value; + } } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { @@ -45,7 +60,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor disposables.add(picker.onDidAccept(event => { const [item] = picker.selectedItems; if (item) { - if (!this.isValidLineNumber(editor, item.lineNumber)) { + if (!item.lineNumber) { return; } @@ -60,34 +75,29 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // React to picker changes const updatePickerAndEditor = () => { const inputText = picker.value.trim().substring(AbstractGotoLineQuickAccessProvider.PREFIX.length); - const inOffsetMode = inputText.startsWith(':'); - const position = this.parsePosition(editor, inputText); - const label = this.getPickLabel(editor, position.lineNumber, position.column, inOffsetMode); + const { inOffsetMode, lineNumber, column, label } = this.parsePosition(editor, inputText); // Show toggle only when input text starts with '::'. - toggle.visible = inOffsetMode; + toggle.visible = !!inOffsetMode; // Picker picker.items = [{ - lineNumber: position.lineNumber, - column: position.column, + lineNumber, + column, label, - detail: inputText.length ? - undefined : // Don't show hint once the user has started typing. - localize('gotoLineQuickAccessDescription', "Use :line[:column] or ::offset to go to a position. Negative values are counted from the end.") }]; // ARIA Label picker.ariaLabel = label; // Clear decorations for invalid range - if (!this.isValidLineNumber(editor, position.lineNumber)) { + if (!lineNumber) { this.clearDecorations(editor); return; } // Reveal - const range = this.toRange(position.lineNumber, position.column); + const range = this.toRange(lineNumber, column); editor.revealRangeInCenter(range, ScrollType.Smooth); // Decorate @@ -96,9 +106,9 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // Add a toggle to switch between 1- and 0-based offsets. const toggle = new Toggle({ - title: localize('gotoLineToggle', "Use zero-based offset"), + title: localize('gotoLineToggle', "Use Zero-Based Offset"), icon: Codicon.indexZero, - isChecked: this.useZeroBasedOffset.value, + isChecked: this.useZeroBasedOffset, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) @@ -106,7 +116,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor disposables.add( toggle.onChange(() => { - this.useZeroBasedOffset.value = !this.useZeroBasedOffset.value; + this.useZeroBasedOffset = !this.useZeroBasedOffset; updatePickerAndEditor(); })); @@ -139,95 +149,78 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor }; } - protected parsePosition(editor: IEditor, value: string): IPosition { + protected parsePosition(editor: IEditor, value: string): Partial & { inOffsetMode?: boolean; label: string } { const model = this.getModel(editor); + if (!model) { + return { + label: localize('gotoLine.noEditor', "Open a text editor first to go to a line.") + }; + } // Support :: notation to navigate to a specific offset in the model. if (value.startsWith(':')) { let offset = parseInt(value.substring(1), 10); - if (!isNaN(offset) && model) { + const maxOffset = model.getValueLength(); + if (isNaN(offset)) { + // No valid offset specified. + return { + inOffsetMode: true, + label: localize('gotoLine.offsetPrompt', "Type a character number in the file from 1 to {0} to go to.", maxOffset) + }; + } else { const reverse = offset < 0; - if (!this.useZeroBasedOffset.value) { + if (!this.useZeroBasedOffset) { // Convert 1-based offset to model's 0-based. offset -= Math.sign(offset); } if (reverse) { // Offset from the end of the buffer - offset += model.getValueLength(); + offset += maxOffset; } - return model.getPositionAt(offset); + const pos = model.getPositionAt(offset); + return { + ...pos, + inOffsetMode: true, + label: localize('gotoLine.goToPosition', "Press Enter to go to line {0} and column {1}.", pos.lineNumber, pos.column) + }; + } + } else { + // Support line-col formats of `line,col`, `line:col`, `line#col` + const parts = value.split(/,|:|#/); + + const maxLine = model.getLineCount(); + let lineNumber = parseInt(parts[0]?.trim(), 10); + if (parts.length < 1 || isNaN(lineNumber)) { + return { + label: localize('gotoLine.linePrompt', "Type a line number from 1 to {0} to go to.", maxLine) + }; } - } - - // Support line-col formats of `line,col`, `line:col`, `line#col` - let [lineNumber, column] = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - // Handle negative line numbers and clip to valid range. - const maxLine = (model?.getLineCount() ?? 0) + 1; - lineNumber = lineNumber >= 0 ? lineNumber : maxLine + lineNumber; - lineNumber = Math.min(Math.max(1, lineNumber), maxLine); + // Handle negative line numbers and clip to valid range. + lineNumber = lineNumber >= 0 ? lineNumber : (maxLine + 1) + lineNumber; + lineNumber = Math.min(Math.max(1, lineNumber), maxLine); - // Handle negative column numbers and clip to valid range. - if (column !== undefined && model) { const maxColumn = model.getLineMaxColumn(lineNumber); - column = column >= 0 ? column : maxColumn + column; - column = Math.min(Math.max(1, column), maxColumn); - } - - return { lineNumber, column }; - } - - private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined, inOffsetMode: boolean): string { - - // Location valid: indicate this as picker label - if (this.isValidLineNumber(editor, lineNumber)) { - if (this.isValidColumn(editor, lineNumber, column)) { - return localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", lineNumber, column); + let column = parseInt(parts[1]?.trim(), 10); + if (parts.length < 2 || isNaN(column)) { + return { + lineNumber, + column: 1, + label: parts.length < 2 ? + localize('gotoLine.lineColumnPrompt', "Press Enter to go to line {0}. Type : to enter column number.", lineNumber) : + localize('gotoLine.columnPrompt', "Press Enter to go to line {0} or enter column number from 1 to {1}.", lineNumber, maxColumn) + }; } - return localize('gotoLineLabel', "Go to line {0}.", lineNumber); - } - - // Location invalid: show generic label - const position = editor.getPosition() || { lineNumber: 1, column: 1 }; - - // When in offset mode, prompt for an offset. - if (inOffsetMode) { - return localize('gotoLineOffsetLabel', "Current Line: {0}, Character: {1}. Type a character offset to navigate to.", position.lineNumber, position.column); - } - - const lineCount = this.lineCount(editor); - if (lineCount > 1) { - return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount); - } - - return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column); - } - - private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean { - if (!lineNumber || typeof lineNumber !== 'number') { - return false; - } - - return lineNumber > 0 && lineNumber <= this.lineCount(editor); - } - - private isValidColumn(editor: IEditor, lineNumber: number, column: number | undefined): boolean { - if (!column || typeof column !== 'number') { - return false; - } + // Handle negative column numbers and clip to valid range. + column = column >= 0 ? column : maxColumn + column; + column = Math.min(Math.max(1, column), maxColumn); - const model = this.getModel(editor); - if (!model) { - return false; + return { + lineNumber, + column, + label: localize('gotoLine.goToPosition', "Press Enter to go to line {0} and column {1}.", lineNumber, column) + }; } - - const positionCandidate = { lineNumber, column }; - - return model.validatePosition(positionCandidate).equals(positionCandidate); - } - - private lineCount(editor: IEditor): number { - return this.getModel(editor)?.getLineCount() ?? 0; } } diff --git a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts index 27175748811..72bf69c7dfc 100644 --- a/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts +++ b/src/vs/editor/contrib/quickAccess/test/browser/gotoLineQuickAccess.test.ts @@ -35,7 +35,7 @@ suite('AbstractGotoLineQuickAccessProvider', () => { ], {}, (editor, _) => { const { lineNumber, column } = provider.parsePositionTest(editor, input); assert.strictEqual(lineNumber, expectedLine); - assert.strictEqual(column, expectedColumn); + assert.strictEqual(column, expectedColumn ?? 1); }); } @@ -48,9 +48,9 @@ suite('AbstractGotoLineQuickAccessProvider', () => { runTest('1', 1); runTest('2', 2); runTest('5', 5); - runTest('6', 6); - runTest('7', 6); - runTest('100', 6); + runTest('6', 5); + runTest('7', 5); + runTest('100', 5); // :line,column runTest('2:-100', 2, 1); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 67f604b17f6..e84b6769f6d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -104,6 +104,6 @@ registerAction2(GotoLineAction); Registry.as(QuickaccesExtensions.Quickaccess).registerQuickAccessProvider({ ctor: GotoLineQuickAccessProvider, prefix: AbstractGotoLineQuickAccessProvider.PREFIX, - placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."), + placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. :42:5 for line 42, column 5). Type :: to go to a character offset (e.g. ::1024 for character 1024 from the start of the file). Use negative values to navigate backwards."), helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line/Column"), commandId: GotoLineAction.ID }] }); From 144fdf16ca68f52416bed838d8ddd77bafcecb52 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 30 Oct 2025 19:08:43 -0400 Subject: [PATCH 1901/4355] add terminal output dropdown, reveal command on focus (#273175) --- package-lock.json | 96 ++-- package.json | 20 +- remote/package-lock.json | 96 ++-- remote/package.json | 20 +- remote/web/package-lock.json | 88 ++-- remote/web/package.json | 18 +- .../commandDetection/terminalCommand.ts | 4 +- .../chat/browser/chatAttachmentWidgets.ts | 4 +- .../media/chatTerminalToolProgressPart.css | 44 ++ .../chatTerminalToolProgressPart.ts | 409 ++++++++++++++++-- .../contrib/chat/browser/media/chat.css | 10 + .../contrib/chat/common/chatService.ts | 5 + .../contrib/chat/common/constants.ts | 2 + .../contrib/terminal/browser/terminal.ts | 11 +- .../terminal/browser/xterm/xtermTerminal.ts | 59 +++ .../chat/browser/terminalChatService.ts | 65 ++- .../browser/tools/runInTerminalTool.ts | 79 +++- 17 files changed, 816 insertions(+), 214 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75a9b72de7e..be209b18375 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,16 +27,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/headless": "^5.6.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/headless": "^5.6.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -3619,30 +3619,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", - "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", + "version": "0.2.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.119.tgz", + "integrity": "sha512-yWmCpGuTvSaIeEfdSijdf8K8qRAYuEGnKkaJZ6er+cOzdmGHBNzyBDKKeyins0aV2j4CGKPDiWHQF5+qGzZDGw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", - "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.136.tgz", + "integrity": "sha512-syWhqpFMAcQ1+US0JjFzj0ORokj8hkz2VgXcCCbTfO0cDtpSYYxMNLaY2fpL459rnOFB4olI9Nf9PZdonmBPDw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", - "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", + "version": "0.10.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.136.tgz", + "integrity": "sha512-WkvL7BVdoqpNf8QsH4n37Pu7jEZTiJ+OD4FmLMVavw0euhgG18zzJKNKIYRuKcddR52dT/Q8TrspVJofpL98GQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -3652,64 +3652,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.41", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", - "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", + "version": "0.2.0-beta.42", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.42.tgz", + "integrity": "sha512-C5w7y6rwSUdRcEiJHFnB2qJI/6DBOi/fJAvTmIpmNZE60cVnrLUuyLmXh6aKbSQ44J6W3PrD5xthb8re3UVUOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", - "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", + "version": "0.16.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.136.tgz", + "integrity": "sha512-Y2T/ShQBelmOGy7lup3VEfFF/yXeNkkMXqhGftmjzmwSA+eylFW+92vczMSrckTW++EFvVLR/L5jMXiSw0qOWQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", - "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", + "version": "0.14.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.136.tgz", + "integrity": "sha512-ursvqITzhZrBQT8XsbOyAQJJKohv33NEm6ToLtMZUmPurBG6KXlVZ9LAPs2YpCBqkifLktSE1GdsofJCpADWuA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", - "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.136.tgz", + "integrity": "sha512-RwtNbON1uNndrtPCM6qMMElTTpxs7ZLRQVbSm4/BMW6GAt6AbW1RAqwoxMRhbz7VVTux/c3HcKfj3SI1MhqSOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", - "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", + "version": "0.19.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.136.tgz", + "integrity": "sha512-MzVlFKrlgJjKQ6T4/TuamvlvR2FFDvxAPY90lo9u4899k7NNif+M8bBdNea3+bsPMU3fKLhGHoTp0+8MjskaeA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.135.tgz", - "integrity": "sha512-fcBUf8zLaTWUyAcahK48dihb1BQYdWvOH021Xge/LfMvCFzK0gmvSMnBbM5fgOyejbmtNLQe+gXBBKVxSaW2pA==", + "version": "5.6.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.136.tgz", + "integrity": "sha512-3irueWS6Ei+XlTMCuh6ZWj1tBnVvjitDtD4PN+v81RKjaCNO/QN9abGTHQx+651GP291ESwY8ocKThSoQ9yklw==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", - "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", + "version": "5.6.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.136.tgz", + "integrity": "sha512-cOWfdbPUYjV8qJY0yg/HdJBiq/hl8J2NRma563crQbSveDpuiiKV+T+ZVeGKQ2YZztLCz6h+kox6J7LQcPtpiQ==", "license": "MIT" }, "node_modules/@xtuc/ieee754": { diff --git a/package.json b/package.json index 3f06f400886..8141de41fb8 100644 --- a/package.json +++ b/package.json @@ -88,16 +88,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/headless": "^5.6.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/headless": "^5.6.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "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 bdeffca90b6..ff8d5dab11b 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -20,16 +20,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/headless": "^5.6.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/headless": "^5.6.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -238,30 +238,30 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", - "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", + "version": "0.2.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.119.tgz", + "integrity": "sha512-yWmCpGuTvSaIeEfdSijdf8K8qRAYuEGnKkaJZ6er+cOzdmGHBNzyBDKKeyins0aV2j4CGKPDiWHQF5+qGzZDGw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", - "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.136.tgz", + "integrity": "sha512-syWhqpFMAcQ1+US0JjFzj0ORokj8hkz2VgXcCCbTfO0cDtpSYYxMNLaY2fpL459rnOFB4olI9Nf9PZdonmBPDw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", - "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", + "version": "0.10.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.136.tgz", + "integrity": "sha512-WkvL7BVdoqpNf8QsH4n37Pu7jEZTiJ+OD4FmLMVavw0euhgG18zzJKNKIYRuKcddR52dT/Q8TrspVJofpL98GQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -271,64 +271,64 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.41", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", - "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", + "version": "0.2.0-beta.42", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.42.tgz", + "integrity": "sha512-C5w7y6rwSUdRcEiJHFnB2qJI/6DBOi/fJAvTmIpmNZE60cVnrLUuyLmXh6aKbSQ44J6W3PrD5xthb8re3UVUOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", - "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", + "version": "0.16.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.136.tgz", + "integrity": "sha512-Y2T/ShQBelmOGy7lup3VEfFF/yXeNkkMXqhGftmjzmwSA+eylFW+92vczMSrckTW++EFvVLR/L5jMXiSw0qOWQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", - "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", + "version": "0.14.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.136.tgz", + "integrity": "sha512-ursvqITzhZrBQT8XsbOyAQJJKohv33NEm6ToLtMZUmPurBG6KXlVZ9LAPs2YpCBqkifLktSE1GdsofJCpADWuA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", - "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.136.tgz", + "integrity": "sha512-RwtNbON1uNndrtPCM6qMMElTTpxs7ZLRQVbSm4/BMW6GAt6AbW1RAqwoxMRhbz7VVTux/c3HcKfj3SI1MhqSOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", - "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", + "version": "0.19.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.136.tgz", + "integrity": "sha512-MzVlFKrlgJjKQ6T4/TuamvlvR2FFDvxAPY90lo9u4899k7NNif+M8bBdNea3+bsPMU3fKLhGHoTp0+8MjskaeA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.135.tgz", - "integrity": "sha512-fcBUf8zLaTWUyAcahK48dihb1BQYdWvOH021Xge/LfMvCFzK0gmvSMnBbM5fgOyejbmtNLQe+gXBBKVxSaW2pA==", + "version": "5.6.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.136.tgz", + "integrity": "sha512-3irueWS6Ei+XlTMCuh6ZWj1tBnVvjitDtD4PN+v81RKjaCNO/QN9abGTHQx+651GP291ESwY8ocKThSoQ9yklw==", "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", - "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", + "version": "5.6.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.136.tgz", + "integrity": "sha512-cOWfdbPUYjV8qJY0yg/HdJBiq/hl8J2NRma563crQbSveDpuiiKV+T+ZVeGKQ2YZztLCz6h+kox6J7LQcPtpiQ==", "license": "MIT" }, "node_modules/agent-base": { diff --git a/remote/package.json b/remote/package.json index 44a825ec29b..6be18987d05 100644 --- a/remote/package.json +++ b/remote/package.json @@ -15,16 +15,16 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/headless": "^5.6.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/headless": "^5.6.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "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 d6b531b8974..68d70b572c6 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.2.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", @@ -92,30 +92,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.118.tgz", - "integrity": "sha512-wfy/o2PxSKMLy4o75J4RVd1P2W27oa/b+Ay8Z3cq3rWZJPx3onbO8PwAJygJaBxQWmOJ0KD6fxQ99plnd2RdVw==", + "version": "0.2.0-beta.119", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.119.tgz", + "integrity": "sha512-yWmCpGuTvSaIeEfdSijdf8K8qRAYuEGnKkaJZ6er+cOzdmGHBNzyBDKKeyins0aV2j4CGKPDiWHQF5+qGzZDGw==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.135.tgz", - "integrity": "sha512-a3LLZitBX4ybuhEtPhSW4dAPqVnnA++pCrIVNXenQVn737oETsLzL9CW6GMpSk1z8xHLl+Bgjfun+6AKpA3ARw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.136.tgz", + "integrity": "sha512-syWhqpFMAcQ1+US0JjFzj0ORokj8hkz2VgXcCCbTfO0cDtpSYYxMNLaY2fpL459rnOFB4olI9Nf9PZdonmBPDw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.135.tgz", - "integrity": "sha512-qT8CrULnTKP6qh4a+r1WHmdvzVD507ZgYx7PVVxL4mC/VJwsdny0sRjoZTE6YQWjdoznfTriD69gDu+ztiY8YQ==", + "version": "0.10.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.136.tgz", + "integrity": "sha512-WkvL7BVdoqpNf8QsH4n37Pu7jEZTiJ+OD4FmLMVavw0euhgG18zzJKNKIYRuKcddR52dT/Q8TrspVJofpL98GQ==", "license": "MIT", "dependencies": { "font-finder": "^1.1.0", @@ -125,58 +125,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.41", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.41.tgz", - "integrity": "sha512-9fgyz86G2JAR2NBcayFse08YoCwQljQCY720raIkGyh3F7iQXs7klNHFUfswg9fLzuO+uQIEh76dxi3NirmCPg==", + "version": "0.2.0-beta.42", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.42.tgz", + "integrity": "sha512-C5w7y6rwSUdRcEiJHFnB2qJI/6DBOi/fJAvTmIpmNZE60cVnrLUuyLmXh6aKbSQ44J6W3PrD5xthb8re3UVUOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.135.tgz", - "integrity": "sha512-Tk7X0T6AsOcxX9hBjpifHi4iXHrPlvTekroiRnDZEY9S1mdINbUIEphOUwwJxsNuukFsxK1VDIiFsGXsZMNO1Q==", + "version": "0.16.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.136.tgz", + "integrity": "sha512-Y2T/ShQBelmOGy7lup3VEfFF/yXeNkkMXqhGftmjzmwSA+eylFW+92vczMSrckTW++EFvVLR/L5jMXiSw0qOWQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.135.tgz", - "integrity": "sha512-88JtWh9Gm5CRfLtgxCD92JXoz5GyRshhQrythLz83VGI9gZVy9dQnwZxmZ/ZE0G17qzM3Z5yvdfiO70q3cGZHA==", + "version": "0.14.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.136.tgz", + "integrity": "sha512-ursvqITzhZrBQT8XsbOyAQJJKohv33NEm6ToLtMZUmPurBG6KXlVZ9LAPs2YpCBqkifLktSE1GdsofJCpADWuA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.135.tgz", - "integrity": "sha512-IFCCOVVQZ0ZnI7ndy40ZjPGHC64YgSncX9/lUw/py0JXM5bMmeg+VdQvpxsdNlvolkVmU3a26TUbPQeZoi0xzw==", + "version": "0.9.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.136.tgz", + "integrity": "sha512-RwtNbON1uNndrtPCM6qMMElTTpxs7ZLRQVbSm4/BMW6GAt6AbW1RAqwoxMRhbz7VVTux/c3HcKfj3SI1MhqSOw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.135.tgz", - "integrity": "sha512-MuJlcCMlapiVHe1jfk/q2wpciPoW8CCWARjsp38k4hOuGdakyZxtZSUP5iWrIH/OvUh/aHmzIK6Ce6vZ1ObJeg==", + "version": "0.19.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.136.tgz", + "integrity": "sha512-MzVlFKrlgJjKQ6T4/TuamvlvR2FFDvxAPY90lo9u4899k7NNif+M8bBdNea3+bsPMU3fKLhGHoTp0+8MjskaeA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.135" + "@xterm/xterm": "^5.6.0-beta.136" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.135", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.135.tgz", - "integrity": "sha512-LjiC1wH6qPGZyiNLajQbqih5K6FjfHY9WAr+RzGnaN0+u+6kjxNRvq8iQSvXRqkPcSQq/GqC94vZYb6EKyXxag==", + "version": "5.6.0-beta.136", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.136.tgz", + "integrity": "sha512-cOWfdbPUYjV8qJY0yg/HdJBiq/hl8J2NRma563crQbSveDpuiiKV+T+ZVeGKQ2YZztLCz6h+kox6J7LQcPtpiQ==", "license": "MIT" }, "node_modules/commander": { diff --git a/remote/web/package.json b/remote/web/package.json index cff9e6ecb0a..e69c409d24e 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.2.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.118", - "@xterm/addon-image": "^0.9.0-beta.135", - "@xterm/addon-ligatures": "^0.10.0-beta.135", - "@xterm/addon-progress": "^0.2.0-beta.41", - "@xterm/addon-search": "^0.16.0-beta.135", - "@xterm/addon-serialize": "^0.14.0-beta.135", - "@xterm/addon-unicode11": "^0.9.0-beta.135", - "@xterm/addon-webgl": "^0.19.0-beta.135", - "@xterm/xterm": "^5.6.0-beta.135", + "@xterm/addon-clipboard": "^0.2.0-beta.119", + "@xterm/addon-image": "^0.9.0-beta.136", + "@xterm/addon-ligatures": "^0.10.0-beta.136", + "@xterm/addon-progress": "^0.2.0-beta.42", + "@xterm/addon-search": "^0.16.0-beta.136", + "@xterm/addon-serialize": "^0.14.0-beta.136", + "@xterm/addon-unicode11": "^0.9.0-beta.136", + "@xterm/addon-webgl": "^0.19.0-beta.136", + "@xterm/xterm": "^5.6.0-beta.136", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client-umd": "0.2.0", diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts index 757cb94b83a..10102ebc265 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts @@ -283,8 +283,10 @@ export class PartialTerminalCommand implements ICurrentPartialCommand { constructor( private readonly _xterm: Terminal, + id?: string ) { - this.id = generateUuid(); + //TODO: this does not restore properly due to conflicting with the one created in the. PtyHost + this.id = id ?? generateUuid(); } serialize(cwd: string | undefined): ISerializedTerminalCommand | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts index e5f98cdd01f..50f58c6790c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentWidgets.ts @@ -312,7 +312,9 @@ function createTerminalCommandElements( element.appendChild(textLabel); disposable.add(dom.addDisposableListener(element, dom.EventType.CLICK, e => { - void clickHandler(); + e.preventDefault(); + e.stopPropagation(); + clickHandler(); })); const hoverElement = dom.$('div.chat-attached-context-hover'); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css index 1eafedcbe42..f7ffbffebbc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css @@ -30,6 +30,12 @@ } } +.chat-terminal-content-title.expanded { + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + border-bottom: 0; +} + .chat-terminal-content-part .chat-terminal-action-bar { display: flex; gap: 4px; @@ -53,3 +59,41 @@ } } } + +.chat-terminal-output-container.collapsed { display: none; } +.chat-terminal-output-container { + margin-top: 0; + border: 1px solid var(--vscode-chat-requestBorder); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + background: var(--vscode-panel-background); + max-height: 300px; + font-family: var(--monaco-monospace-font); + box-sizing: border-box; + overflow: hidden; + position: relative; +} +.chat-terminal-output-container.expanded { display: block; } +.chat-terminal-output-container > .monaco-scrollable-element { + width: 100%; +} +.chat-terminal-output-body { + padding: 4px 6px; + box-sizing: border-box; + width: 100%; + height: 100%; +} +.chat-terminal-output-content { + display: flex; + flex-direction: column; + gap: 6px; +} +.chat-terminal-output { + margin: 0; + white-space: pre; + font-size: 12px; +} +.chat-terminal-output-info { + color: var(--vscode-descriptionForeground); + font-size: var(--vscode-chat-font-size-body-xs); +} diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index ee1ea89b17d..e3c4c84a1f8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -7,7 +7,6 @@ import { h } from '../../../../../../base/browser/dom.js'; import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { isMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IPreferencesService, type IOpenSettingsOptions } from '../../../../../services/preferences/common/preferences.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; @@ -23,20 +22,54 @@ import '../media/chatTerminalToolProgressPart.css'; import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; import { ConfigurationTarget } from '../../../../../../platform/configuration/common/configuration.js'; import type { ICodeBlockRenderOptions } from '../../codeBlockPart.js'; -import { ChatConfiguration } from '../../../common/constants.js'; +import { ChatConfiguration, CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES } from '../../../common/constants.js'; import { CommandsRegistry } from '../../../../../../platform/commands/common/commands.js'; import { ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../../terminal/browser/terminal.js'; import { Action, IAction } from '../../../../../../base/common/actions.js'; -import { MutableDisposable } from '../../../../../../base/common/lifecycle.js'; +import { MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; +import * as dom from '../../../../../../base/browser/dom.js'; +import { DomScrollableElement } from '../../../../../../base/browser/ui/scrollbar/scrollableElement.js'; +import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js'; import { localize } from '../../../../../../nls.js'; import { TerminalLocation } from '../../../../../../platform/terminal/common/terminal.js'; +import { ITerminalCommand, TerminalCapability, type ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; +import * as domSanitize from '../../../../../../base/browser/domSanitize.js'; +import { DomSanitizerConfig } from '../../../../../../base/browser/domSanitize.js'; +import { allowedMarkdownHtmlAttributes } from '../../../../../../base/browser/markdownRenderer.js'; +import { URI } from '../../../../../../base/common/uri.js'; + +const MAX_TERMINAL_OUTPUT_PREVIEW_HEIGHT = 200; + +const sanitizerConfig = Object.freeze({ + allowedTags: { + augment: ['b', 'i', 'u', 'code', 'span', 'div', 'body', 'pre'], + }, + allowedAttributes: { + augment: [...allowedMarkdownHtmlAttributes, 'style'] + } +}); export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart { public readonly domNode: HTMLElement; private readonly _actionBar = this._register(new MutableDisposable()); + private readonly _outputContainer: HTMLElement; + private readonly _outputBody: HTMLElement; + private readonly _titlePart: HTMLElement; + private _outputScrollbar: DomScrollableElement | undefined; + private _outputContent: HTMLElement | undefined; + private _outputResizeObserver: ResizeObserver | undefined; + + private readonly _showOutputAction = this._register(new MutableDisposable()); + private _showOutputActionAdded = false; + private readonly _focusAction = this._register(new MutableDisposable()); + + private readonly _terminalData: IChatTerminalToolInvocationData; + private _terminalInstance: ITerminalInstance | undefined; + private markdownPart: ChatMarkdownContentPart | undefined; public get codeblocks(): IChatCodeBlockInfo[] { return this.markdownPart?.codeblocks ?? []; @@ -53,19 +86,22 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart codeBlockModelCollection: CodeBlockModelCollection, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, - @ITerminalService private readonly _terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService, ) { super(toolInvocation); terminalData = migrateLegacyTerminalToolSpecificData(terminalData); + this._terminalData = terminalData; const elements = h('.chat-terminal-content-part@container', [ h('.chat-terminal-content-title@title'), - h('.chat-terminal-content-message@message') + h('.chat-terminal-content-message@message'), + h('.chat-terminal-output-container@output') ]); const command = terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original; + this._titlePart = elements.title; const titlePart = this._register(_instantiationService.createInstance( ChatQueryTitlePart, elements.title, @@ -75,11 +111,11 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); // Wait for terminal reconnection to ensure the terminal instance is available - this._terminalService.whenConnected.then(() => { + this._terminalService.whenConnected.then(async () => { // Append the action bar element after the title has been populated so flex order hacks aren't required. const actionBarEl = h('.chat-terminal-action-bar@actionBar'); elements.title.append(actionBarEl.root); - this._createActionBar({ actionBar: actionBarEl.actionBar }, terminalData); + await this._createActionBar({ actionBar: actionBarEl.actionBar }); }); let pastTenseMessage: string | undefined; if (toolInvocation.pastTenseMessage) { @@ -106,42 +142,325 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart this._register(this.markdownPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); elements.message.append(this.markdownPart.domNode); + this._outputContainer = elements.output; + this._outputContainer.classList.add('collapsed'); + this._outputBody = dom.$('.chat-terminal-output-body'); + const progressPart = this._register(_instantiationService.createInstance(ChatProgressSubPart, elements.container, this.getIcon(), terminalData.autoApproveInfo)); this.domNode = progressPart.domNode; } - private _createActionBar(elements: { actionBar: HTMLElement }, terminalData: IChatTerminalToolInvocationData | ILegacyChatTerminalToolInvocationData): void { + private async _createActionBar(elements: { actionBar: HTMLElement }): Promise { this._actionBar.value = new ActionBar(elements.actionBar, {}); - const terminalToolSessionId = 'terminalToolSessionId' in terminalData ? terminalData.terminalToolSessionId : undefined; - if (!terminalToolSessionId || !elements.actionBar) { + const terminalToolSessionId = this._terminalData.terminalToolSessionId; + if (!terminalToolSessionId) { return; } - const terminalInstance = this._terminalChatService.getTerminalInstanceByToolSessionId(terminalToolSessionId); - if (terminalInstance) { - this._registerInstanceListener(terminalInstance); - this._addFocusAction(terminalInstance, terminalToolSessionId); - } else { - const listener = this._register(this._terminalChatService.onDidRegisterTerminalInstanceWithToolSession(terminalInstance => { - this._registerInstanceListener(terminalInstance); - this._addFocusAction(terminalInstance, terminalToolSessionId); - this._store.delete(listener); - })); - } + + const attachInstance = async (instance: ITerminalInstance | undefined) => { + if (!instance || this._terminalInstance === instance) { + return; + } + this._terminalInstance = instance; + this._registerInstanceListener(instance); + await this._addFocusAction(instance, terminalToolSessionId); + if (this._terminalData?.output?.html) { + this._ensureShowOutputAction(); + } + }; + + await attachInstance(await this._terminalChatService.getTerminalInstanceByToolSessionId(terminalToolSessionId)); + + const listener = this._terminalChatService.onDidRegisterTerminalInstanceWithToolSession(async instance => { + if (instance !== await this._terminalChatService.getTerminalInstanceByToolSessionId(terminalToolSessionId)) { + return; + } + attachInstance(instance); + listener.dispose(); + }); + this._register(listener); } - private _addFocusAction(terminalInstance: ITerminalInstance, terminalToolSessionId: string) { + private async _addFocusAction(terminalInstance: ITerminalInstance, terminalToolSessionId: string) { + if (!this._actionBar.value) { + return; + } const isTerminalHidden = this._terminalChatService.isBackgroundTerminal(terminalToolSessionId); - const focusAction = this._register(this._instantiationService.createInstance(FocusChatInstanceAction, terminalInstance, isTerminalHidden)); - this._actionBar.value?.push([focusAction], { icon: true, label: false }); + const command = this._getResolvedCommand(terminalInstance); + const focusAction = this._instantiationService.createInstance(FocusChatInstanceAction, terminalInstance, command, isTerminalHidden); + this._focusAction.value = focusAction; + this._actionBar.value.push(focusAction, { icon: true, label: false, index: 0 }); + this._ensureShowOutputAction(); + } + + private _ensureShowOutputAction(): void { + if (!this._actionBar.value) { + return; + } + const hasSerializedOutput = !!this._terminalData.output?.html; + const commandFinished = !!this._getResolvedCommand()?.endMarker; + if (!hasSerializedOutput && !commandFinished) { + return; + } + let showOutputAction = this._showOutputAction.value; + if (!showOutputAction) { + showOutputAction = new ToggleChatTerminalOutputAction(expanded => this._toggleOutput(expanded)); + this._showOutputAction.value = showOutputAction; + } + showOutputAction.syncPresentation(this._outputContainer.classList.contains('expanded')); + + const actionBar = this._actionBar.value; + if (this._showOutputActionAdded) { + const existingIndex = actionBar.viewItems.findIndex(item => item.action === showOutputAction); + if (existingIndex >= 0 && existingIndex !== actionBar.length() - 1) { + actionBar.pull(existingIndex); + this._showOutputActionAdded = false; + } else if (existingIndex >= 0) { + return; + } + } + + if (this._showOutputActionAdded) { + return; + } + actionBar.push([showOutputAction], { icon: true, label: false }); + this._showOutputActionAdded = true; + } + + private _getResolvedCommand(instance?: ITerminalInstance): ITerminalCommand | undefined { + const target = instance ?? this._terminalInstance; + if (!target) { + return undefined; + } + return this._resolveCommand(target); } private _registerInstanceListener(terminalInstance: ITerminalInstance) { + const commandDetectionListener = this._register(new MutableDisposable()); + const tryResolveCommand = (): ITerminalCommand | undefined => { + const resolvedCommand = this._resolveCommand(terminalInstance); + if (resolvedCommand?.endMarker) { + this._ensureShowOutputAction(); + } + return resolvedCommand; + }; + + const attachCommandDetection = (commandDetection: ICommandDetectionCapability | undefined) => { + commandDetectionListener.clear(); + if (!commandDetection) { + return; + } + + const resolvedImmediately = tryResolveCommand(); + if (resolvedImmediately?.endMarker) { + return; + } + + commandDetectionListener.value = commandDetection.onCommandFinished(() => { + this._ensureShowOutputAction(); + commandDetectionListener.clear(); + }); + }; + + attachCommandDetection(terminalInstance.capabilities.get(TerminalCapability.CommandDetection)); + this._register(terminalInstance.capabilities.onDidAddCommandDetectionCapability(cd => attachCommandDetection(cd))); + const instanceListener = this._register(terminalInstance.onDisposed(() => { + if (this._terminalInstance === terminalInstance) { + this._terminalInstance = undefined; + } + commandDetectionListener.clear(); this._actionBar.clear(); - instanceListener?.dispose(); + this._focusAction.clear(); + const keepOutputAction = !!this._terminalData.output?.html; + this._showOutputActionAdded = false; + if (!keepOutputAction) { + this._showOutputAction.clear(); + } + this._ensureShowOutputAction(); + instanceListener.dispose(); })); } + + private async _toggleOutput(expanded: boolean): Promise { + const currentlyExpanded = this._outputContainer.classList.contains('expanded'); + if (expanded === currentlyExpanded) { + this._showOutputAction.value?.syncPresentation(currentlyExpanded); + return false; + } + + this._setOutputExpanded(expanded); + + if (!expanded) { + this._layoutOutput(); + this._showOutputAction.value?.syncPresentation(false); + return true; + } + + const didCreate = await this._renderOutputIfNeeded(); + this._layoutOutput(); + this._scrollOutputToBottom(); + if (didCreate) { + this._scheduleOutputRelayout(); + } + this._showOutputAction.value?.syncPresentation(expanded); + return true; + } + + private _setOutputExpanded(expanded: boolean): void { + this._outputContainer.classList.toggle('expanded', expanded); + this._outputContainer.classList.toggle('collapsed', !expanded); + this._titlePart.classList.toggle('expanded', expanded); + } + + private async _renderOutputIfNeeded(): Promise { + if (this._outputContent) { + this._ensureOutputResizeObserver(); + return false; + } + + if (!this._terminalInstance) { + const resource = this._getTerminalResource(); + if (resource) { + this._terminalInstance = this._terminalService.getInstanceFromResource(resource); + } + } + const output = await this._collectOutput(this._terminalInstance); + const content = this._renderOutput(output); + const theme = this._terminalInstance?.xterm?.getXtermTheme(); + if (theme) { + const inlineTerminal = content.querySelector('div'); + if (inlineTerminal) { + inlineTerminal.style.setProperty('background-color', theme.background || 'transparent'); + inlineTerminal.style.setProperty('color', theme.foreground || 'inherit'); + } + } + + this._outputBody.replaceChildren(content); + this._outputContent = content; + if (!this._outputScrollbar) { + this._outputScrollbar = this._register(new DomScrollableElement(this._outputBody, { + vertical: ScrollbarVisibility.Auto, + horizontal: ScrollbarVisibility.Auto, + handleMouseWheel: true + })); + const scrollableDomNode = this._outputScrollbar.getDomNode(); + scrollableDomNode.tabIndex = 0; + scrollableDomNode.style.maxHeight = `${MAX_TERMINAL_OUTPUT_PREVIEW_HEIGHT}px`; + this._outputContainer.appendChild(scrollableDomNode); + this._ensureOutputResizeObserver(); + this._outputContent = undefined; + } else { + this._ensureOutputResizeObserver(); + } + + return true; + } + + private _scrollOutputToBottom(): void { + if (!this._outputScrollbar) { + return; + } + const dimensions = this._outputScrollbar.getScrollDimensions(); + this._outputScrollbar.setScrollPosition({ scrollTop: dimensions.scrollHeight }); + } + + private _scheduleOutputRelayout(): void { + dom.getActiveWindow().requestAnimationFrame(() => { + this._layoutOutput(); + this._scrollOutputToBottom(); + }); + } + + private _layoutOutput(): void { + if (!this._outputScrollbar || !this._outputContainer.classList.contains('expanded')) { + return; + } + const scrollableDomNode = this._outputScrollbar.getDomNode(); + const viewportHeight = Math.min(this._outputBody.scrollHeight, MAX_TERMINAL_OUTPUT_PREVIEW_HEIGHT); + scrollableDomNode.style.height = `${viewportHeight}px`; + this._outputScrollbar.scanDomNode(); + } + + private _ensureOutputResizeObserver(): void { + if (this._outputResizeObserver || !this._outputScrollbar) { + return; + } + const observer = new ResizeObserver(() => this._layoutOutput()); + observer.observe(this._outputContainer); + this._outputResizeObserver = observer; + this._register(toDisposable(() => { + observer.disconnect(); + this._outputResizeObserver = undefined; + })); + } + + private async _collectOutput(terminalInstance: ITerminalInstance | undefined): Promise<{ text: string; truncated: boolean }> { + const storedOutput = this._terminalData.output; + if (storedOutput?.html) { + return { text: storedOutput.html, truncated: storedOutput.truncated ?? false }; + } + if (!terminalInstance) { + return { text: '', truncated: false }; + } + const xterm = await terminalInstance.xtermReadyPromise; + if (!xterm) { + return { text: '', truncated: false }; + } + const command = this._resolveCommand(terminalInstance); + if (!command?.endMarker) { + return { text: '', truncated: false }; + } + const text = await xterm.getCommandOutputAsHtml(command, CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES); + if (!text) { + return { text: '', truncated: false }; + } + + return { text, truncated: false }; + } + + private _renderOutput(result: { text: string; truncated: boolean }): HTMLElement { + const container = document.createElement('div'); + container.classList.add('chat-terminal-output-content'); + + const pre = document.createElement('pre'); + pre.classList.add('chat-terminal-output'); + domSanitize.safeSetInnerHtml(pre, result.text, sanitizerConfig); + container.appendChild(pre); + + if (result.truncated) { + const note = document.createElement('div'); + note.classList.add('chat-terminal-output-info'); + note.textContent = localize('chat.terminalOutputTruncated', 'Output truncated to first {0} characters.', CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES); + container.appendChild(note); + } + + return container; + } + + private _getTerminalResource(): URI | undefined { + const commandUri = this._terminalData.terminalCommandUri; + if (!commandUri) { + return undefined; + } + return URI.isUri(commandUri) ? commandUri : URI.revive(commandUri); + } + + + private _resolveCommand(instance: ITerminalInstance): ITerminalCommand | undefined { + const commandDetection = instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = commandDetection?.commands; + if (!commands || commands.length === 0) { + return undefined; + } + + const commandId = this._terminalChatService.getTerminalCommandIdByToolSessionId(this._terminalData.terminalToolSessionId); + if (commandId) { + return commands.find(c => c.id === commandId); + } + return; + } } export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink'; @@ -181,9 +500,43 @@ CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (acces } }); +class ToggleChatTerminalOutputAction extends Action implements IAction { + private _expanded = false; + + constructor(private readonly _toggle: (expanded: boolean) => Promise) { + super( + 'chat.showTerminalOutput', + localize('showTerminalOutput', 'Show Output'), + ThemeIcon.asClassName(Codicon.chevronRight), + true, + ); + } + + public override async run(): Promise { + const target = !this._expanded; + await this._toggle(target); + } + + public syncPresentation(expanded: boolean): void { + this._expanded = expanded; + this._updatePresentation(); + } + + private _updatePresentation(): void { + if (this._expanded) { + this.label = localize('hideTerminalOutput', 'Hide Output'); + this.class = ThemeIcon.asClassName(Codicon.chevronDown); + } else { + this.label = localize('showTerminalOutput', 'Show Output'); + this.class = ThemeIcon.asClassName(Codicon.chevronRight); + } + } +} + export class FocusChatInstanceAction extends Action implements IAction { constructor( private readonly _instance: ITerminalInstance, + private readonly _command: ITerminalCommand | undefined, isTerminalHidden: boolean, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService, @@ -199,12 +552,16 @@ export class FocusChatInstanceAction extends Action implements IAction { public override async run() { this.label = localize('focusTerminal', 'Focus Terminal'); + this._terminalService.setActiveInstance(this._instance); if (this._instance.target === TerminalLocation.Editor) { this._terminalEditorService.openEditor(this._instance); } else { - this._terminalGroupService.showPanel(true); + await this._terminalGroupService.showPanel(true); } this._terminalService.setActiveInstance(this._instance); await this._instance?.focusWhenReady(true); + if (this._command) { + this._instance.xterm?.markTracker.revealCommand(this._command); + } } } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 828972f3e7c..56f43e39750 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -2305,6 +2305,16 @@ have to be updated for changes to the rules above, or to support more deeply nes overflow: auto; } +.chat-terminal-output-container { + max-height: 100%; + max-width: 100%; + margin-right: 20px; +} + +.chat-terminal-content-title.expanded { + margin-right: 20px; +} + .chat-attached-context-attachment .chat-attached-context-pill { font-size: 12px; display: inline-flex; diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index c5cecf4ad78..1037d3ba259 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -313,7 +313,12 @@ export interface IChatTerminalToolInvocationData { alternativeRecommendation?: string; language: string; terminalToolSessionId?: string; + terminalCommandUri?: UriComponents; autoApproveInfo?: IMarkdownString; + output?: { + html: string; + truncated?: boolean; + }; } /** diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 61f16849f71..074eaa63c1b 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -121,3 +121,5 @@ export function isSupportedChatFileScheme(accessor: ServicesAccessor, scheme: st export const AGENT_SESSIONS_VIEWLET_ID = 'workbench.view.chat.sessions'; // TODO@bpasero clear once settled export const MANAGE_CHAT_COMMAND_ID = 'workbench.action.chat.manage'; export const ChatEditorTitleMaxLength = 30; + +export const CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES = 1000; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 090b8aaa839..a43080077ff 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -9,7 +9,7 @@ import { Color } from '../../../../base/common/color.js'; import { Event, IDynamicListEventMultiplexer, type DynamicListEventMultiplexer } from '../../../../base/common/event.js'; import { DisposableStore, IDisposable, type IReference } from '../../../../base/common/lifecycle.js'; import { OperatingSystem } from '../../../../base/common/platform.js'; -import { URI } from '../../../../base/common/uri.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeyMods } from '../../../../platform/quickinput/common/quickInput.js'; import { IMarkProperties, ITerminalCapabilityImplMap, ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from '../../../../platform/terminal/common/capabilities/capabilities.js'; @@ -124,7 +124,12 @@ export interface ITerminalChatService { * @param terminalToolSessionId The tool session id provided in toolSpecificData. * If no tool session ID is provided, we do nothing. */ - getTerminalInstanceByToolSessionId(terminalToolSessionId: string): ITerminalInstance | undefined; + getTerminalInstanceByToolSessionId(terminalToolSessionId: string): Promise; + + /** + * Get the terminal command ID associated with a tool session ID, if any. + */ + getTerminalCommandIdByToolSessionId(terminalToolSessionId: string | undefined): string | undefined; /** * Returns the list of terminal instances that have been registered with a tool session id. @@ -650,7 +655,7 @@ export interface ITerminalInstanceHost { * Gets an instance from a resource if it exists. This MUST be used instead of getInstanceFromId * when you only know about a terminal's URI. (a URI's instance ID may not be this window's instance ID) */ - getInstanceFromResource(resource: URI | undefined): ITerminalInstance | undefined; + getInstanceFromResource(resource: UriComponents | undefined): ITerminalInstance | undefined; } /** diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index af09495cfe6..85375c996d4 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -368,6 +368,65 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach return this._serializeAddon.serializeAsHTML(); } + async getCommandOutputAsHtml(command: ITerminalCommand, maxLines: number): Promise { + if (!this._serializeAddon) { + const Addon = await this._xtermAddonLoader.importAddon('serialize'); + this._serializeAddon = new Addon(); + this.raw.loadAddon(this._serializeAddon); + } + let startLine: number; + let startCol: number; + if (command.executedMarker && command.executedMarker.line >= 0) { + startLine = command.executedMarker.line; + startCol = Math.max(command.executedX ?? 0, 0); + } else { + startLine = command.marker?.line !== undefined ? command.marker.line + 1 : 1; + startCol = Math.max(command.startX ?? 0, 0); + } + + let endLine = command.endMarker?.line !== undefined ? command.endMarker.line - 1 : this.raw.buffer.active.length - 1; + if (endLine < startLine) { + return ''; + } + // Trim empty lines from the end + let emptyLinesFromEnd = 0; + for (let i = endLine; i >= startLine; i--) { + const line = this.raw.buffer.active.getLine(i); + if (line && line.translateToString(true).trim() === '') { + emptyLinesFromEnd++; + } else { + break; + } + } + endLine = endLine - emptyLinesFromEnd; + + // Trim empty lines from the start + let emptyLinesFromStart = 0; + for (let i = startLine; i <= endLine; i++) { + const line = this.raw.buffer.active.getLine(i); + if (line && line.translateToString(true).trim() === '') { + emptyLinesFromStart++; + } else { + break; + } + } + startLine = startLine + emptyLinesFromStart; + + if (maxLines && endLine - startLine > maxLines) { + startLine = endLine - maxLines; + startCol = 0; + } + + const bufferLine = this.raw.buffer.active.getLine(startLine); + if (bufferLine) { + startCol = Math.min(startCol, bufferLine.length); + } + + const range = { startLine, endLine, startCol }; + const result = this._serializeAddon.serializeAsHTML({ range }); + return result; + } + async getSelectionAsHtml(command?: ITerminalCommand): Promise { if (!this._serializeAddon) { const Addon = await this._xtermAddonLoader.importAddon('serialize'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index c52bc85339d..438bac8d1bc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -9,17 +9,24 @@ import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITerminalChatService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { IChatService } from '../../../chat/common/chatService.js'; import { TerminalChatContextKeys } from './terminalChat.js'; +const enum StorageKeys { + ToolSessionMappings = 'terminalChat.toolSessionMappings', + CommandIdMappings = 'terminalChat.commandIdMappings' +} + + /** * Used to manage chat tool invocations and the underlying terminal instances they create/use. */ export class TerminalChatService extends Disposable implements ITerminalChatService { declare _serviceBrand: undefined; - private static readonly _storageKey = 'terminalChat.toolSessionMappings'; - private readonly _terminalInstancesByToolSessionId = new Map(); + private readonly _commandIdByToolSessionId = new Map(); private readonly _terminalInstanceListenersByToolSessionId = this._register(new DisposableMap()); private readonly _onDidRegisterTerminalInstanceForToolSession = new Emitter(); readonly onDidRegisterTerminalInstanceWithToolSession: Event = this._onDidRegisterTerminalInstanceForToolSession.event; @@ -37,7 +44,8 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ @ILogService private readonly _logService: ILogService, @ITerminalService private readonly _terminalService: ITerminalService, @IStorageService private readonly _storageService: IStorageService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatService private readonly _chatService: IChatService, ) { super(); @@ -59,6 +67,20 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._persistToStorage(); this._updateHasToolTerminalContextKey(); })); + const listener = this._register(instance.capabilities.get(TerminalCapability.CommandDetection)!.onCommandFinished(e => { + this._commandIdByToolSessionId.set(terminalToolSessionId, e.id); + this._persistToStorage(); + listener.dispose(); + })); + this._register(this._chatService.onDidDisposeSession(e => { + if (e.sessionId === terminalToolSessionId) { + this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); + this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId); + this._commandIdByToolSessionId.delete(terminalToolSessionId); + this._persistToStorage(); + this._updateHasToolTerminalContextKey(); + } + })); if (typeof instance.persistentProcessId === 'number') { this._persistToStorage(); @@ -67,7 +89,18 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._updateHasToolTerminalContextKey(); } - getTerminalInstanceByToolSessionId(terminalToolSessionId: string | undefined): ITerminalInstance | undefined { + getTerminalCommandIdByToolSessionId(terminalToolSessionId: string | undefined): string | undefined { + if (!terminalToolSessionId) { + return undefined; + } + if (this._commandIdByToolSessionId.size === 0) { + this._restoreFromStorage(); + } + return this._commandIdByToolSessionId.get(terminalToolSessionId); + } + + async getTerminalInstanceByToolSessionId(terminalToolSessionId: string | undefined): Promise { + await this._terminalService.whenConnected; if (!terminalToolSessionId) { return undefined; } @@ -99,7 +132,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ private _restoreFromStorage(): void { try { - const raw = this._storageService.get(TerminalChatService._storageKey, StorageScope.WORKSPACE); + const raw = this._storageService.get(StorageKeys.ToolSessionMappings, StorageScope.WORKSPACE); if (!raw) { return; } @@ -109,6 +142,15 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._pendingRestoredMappings.set(toolSessionId, persistentProcessId); } } + const rawCommandIds = this._storageService.get(StorageKeys.CommandIdMappings, StorageScope.WORKSPACE); + if (rawCommandIds) { + const parsedCommandIds: [string, string][] = JSON.parse(rawCommandIds); + for (const [toolSessionId, commandId] of parsedCommandIds) { + if (typeof toolSessionId === 'string' && typeof commandId === 'string') { + this._commandIdByToolSessionId.set(toolSessionId, commandId); + } + } + } } catch (err) { this._logService.warn('Failed to restore terminal chat tool session mappings', err); } @@ -147,9 +189,18 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ } } if (entries.length > 0) { - this._storageService.store(TerminalChatService._storageKey, JSON.stringify(entries), StorageScope.WORKSPACE, StorageTarget.MACHINE); + this._storageService.store(StorageKeys.ToolSessionMappings, JSON.stringify(entries), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } else { + this._storageService.remove(StorageKeys.ToolSessionMappings, StorageScope.WORKSPACE); + } + const commandEntries: [string, string][] = []; + for (const [toolSessionId, commandId] of this._commandIdByToolSessionId.entries()) { + commandEntries.push([toolSessionId, commandId]); + } + if (commandEntries.length > 0) { + this._storageService.store(StorageKeys.CommandIdMappings, JSON.stringify(commandEntries), StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { - this._storageService.remove(TerminalChatService._storageKey, StorageScope.WORKSPACE); + this._storageService.remove(StorageKeys.CommandIdMappings, StorageScope.WORKSPACE); } } catch (err) { this._logService.warn('Failed to persist terminal chat tool session mappings', err); 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 0dd4a1779f6..233c5a4c62b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -13,18 +13,18 @@ import { MarkdownString, type IMarkdownString } from '../../../../../../base/com import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { basename } from '../../../../../../base/common/path.js'; import { OperatingSystem, OS } from '../../../../../../base/common/platform.js'; -import { count } from '../../../../../../base/common/strings.js'; -import type { DeepImmutable } from '../../../../../../base/common/types.js'; +import { count, escape } 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 { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; -import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { TerminalCapability, type ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { ITerminalLogService, ITerminalProfile } from '../../../../../../platform/terminal/common/terminal.js'; import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; +import { CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES } from '../../../../chat/common/constants.js'; import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../../../chat/common/languageModelToolsService.js'; import { ITerminalChatService, ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import type { XtermTerminal } from '../../../../terminal/browser/xterm/xtermTerminal.js'; @@ -37,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 { isFish, isPowerShell, isWindowsPowerShell, isZsh, sanitizeTerminalOutput } from '../runInTerminalHelpers.js'; import { RunInTerminalToolTelemetry } from '../runInTerminalToolTelemetry.js'; import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from '../toolTerminalCreator.js'; import { TreeSitterCommandParser, TreeSitterCommandParserLanguage } from '../treeSitterCommandParser.js'; @@ -403,8 +403,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, _progress: ToolProgress, token: CancellationToken): Promise { - - const toolSpecificData = invocation.toolSpecificData as DeepImmutable | undefined; + const toolSpecificData = invocation.toolSpecificData as IChatTerminalToolInvocationData | undefined; if (!toolSpecificData) { throw new Error('toolSpecificData must be provided for this tool'); } @@ -460,6 +459,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { throw new Error('Instance was disposed before xterm.js was ready'); } + const commandDetection = toolTerminal.instance.capabilities.get(TerminalCapability.CommandDetection); + let inputUserChars = 0; let inputUserSigint = false; store.add(xterm.raw.onData(data => { @@ -480,6 +481,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { outputMonitor = store.add(this._instantiationService.createInstance(OutputMonitor, execution, undefined, invocation.context!, token, command)); await Event.toPromise(outputMonitor.onDidFinishCommand); const pollingResult = outputMonitor.pollingResult; + await this._updateTerminalCommandMetadata(toolSpecificData, toolTerminal.instance, commandDetection, pollingResult?.output ? { text: pollingResult.output } : undefined); if (token.isCancellationRequested) { throw new CancellationError(); @@ -515,6 +517,12 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { error = e instanceof CancellationError ? 'canceled' : 'unexpectedException'; throw e; } finally { + await this._updateTerminalCommandMetadata( + toolSpecificData, + toolTerminal.instance, + commandDetection, + pollingResult?.output ? { text: pollingResult.output } : undefined + ); store.dispose(); this._logService.debug(`RunInTerminalTool: Finished polling \`${pollingResult?.output.length}\` lines of output in \`${pollingResult?.pollDurationMs}\``); const timingExecuteMs = Date.now() - timingStart; @@ -551,7 +559,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { let exitCode: number | undefined; try { let strategy: ITerminalExecuteStrategy; - const commandDetection = toolTerminal.instance.capabilities.get(TerminalCapability.CommandDetection); switch (toolTerminal.shellIntegrationQuality) { case ShellIntegrationQuality.None: { strategy = this._instantiationService.createInstance(NoneExecuteStrategy, toolTerminal.instance, () => toolTerminal.receivedUserInput ?? false); @@ -600,6 +607,12 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { error = e instanceof CancellationError ? 'canceled' : 'unexpectedException'; throw e; } finally { + await this._updateTerminalCommandMetadata( + toolSpecificData, + toolTerminal.instance, + commandDetection, + terminalResult ? { text: terminalResult } : undefined + ); store.dispose(); const timingExecuteMs = Date.now() - timingStart; this._telemetry.logInvoke(toolTerminal.instance, { @@ -650,6 +663,58 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { } } + private async _updateTerminalCommandMetadata( + toolSpecificData: IChatTerminalToolInvocationData, + instance: ITerminalInstance, + commandDetection: ICommandDetectionCapability | undefined, + fallbackOutput?: { text: string; truncated?: boolean } + ): Promise { + const command = commandDetection?.commands.at(-1); + if (command?.id && !toolSpecificData.terminalCommandUri) { + const params = new URLSearchParams(instance.resource.query); + params.set('command', command.id); + const commandUri = instance.resource.with({ query: params.toString() || undefined }); + toolSpecificData.terminalCommandUri = commandUri; + } + + if (toolSpecificData.output?.html) { + return; + } + + let serializedHtml: string | undefined; + let truncated = fallbackOutput?.truncated ?? false; + + if (command?.endMarker) { + try { + const xterm = await instance.xtermReadyPromise; + if (xterm) { + const html = await xterm.getCommandOutputAsHtml(command, CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES); + if (html) { + serializedHtml = html; + truncated = false; + } + } + } catch (error) { + this._logService.debug('RunInTerminalTool: Failed to capture terminal HTML output for serialization', error); + } + } + + if (!serializedHtml && fallbackOutput?.text) { + const sanitized = sanitizeTerminalOutput(fallbackOutput.text); + if (sanitized) { + serializedHtml = escape(sanitized); + truncated = fallbackOutput.truncated ?? truncated; + } + } + + if (serializedHtml) { + toolSpecificData.output = { + html: serializedHtml, + truncated + }; + } + } + private _handleTerminalVisibility(toolTerminal: IToolTerminal) { if (this._configurationService.getValue(TerminalChatAgentToolsSettingId.OutputLocation) === 'terminal') { this._terminalService.setActiveInstance(toolTerminal.instance); From bc5f6481a5ce4e87962f02f1bcd3348079f6155c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt <2644648+TylerLeonhardt@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:19:54 -0700 Subject: [PATCH 1902/4355] Enable tracing (#274203) * Enable tracing So that we can upload traces instead of just videos. * feedback --- test/automation/src/application.ts | 4 ++-- test/automation/src/code.ts | 4 ++-- test/automation/src/playwrightDriver.ts | 16 +++++++++------- test/mcp/src/application.ts | 2 +- test/mcp/src/automation.ts | 1 + test/mcp/src/automationTools/core.ts | 1 + test/mcp/src/options.ts | 2 -- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 14280091027..81acded3853 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -98,11 +98,11 @@ export class Application { } } - async startTracing(name: string): Promise { + async startTracing(name?: string): Promise { await this._code?.startTracing(name); } - async stopTracing(name: string, persist: boolean): Promise { + async stopTracing(name?: string, persist: boolean = false): Promise { await this._code?.stopTracing(name, persist); } diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 34c3a0717b4..fe498419122 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -143,11 +143,11 @@ export class Code { return !(this.quality === Quality.Stable && this.version.major === 1 && this.version.minor < 101); } - async startTracing(name: string): Promise { + async startTracing(name?: string): Promise { return await this.driver.startTracing(name); } - async stopTracing(name: string, persist: boolean): Promise { + async stopTracing(name?: string, persist: boolean = false): Promise { return await this.driver.stopTracing(name, persist); } diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 5c95e977117..26e2a465c84 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -51,19 +51,19 @@ export class PlaywrightDriver { return this.page; } - async startTracing(name: string): Promise { + async startTracing(name?: string): Promise { if (!this.options.tracing) { return; // tracing disabled } try { - await measureAndLog(() => this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.options.logger); + await measureAndLog(() => this.context.tracing.startChunk({ title: name }), `startTracing${name ? ` for ${name}` : ''}`, this.options.logger); } catch (error) { // Ignore } } - async stopTracing(name: string, persist: boolean): Promise { + async stopTracing(name?: string, persist: boolean = false): Promise { if (!this.options.tracing) { return; // tracing disabled } @@ -71,10 +71,11 @@ export class PlaywrightDriver { try { let persistPath: string | undefined = undefined; if (persist) { - persistPath = join(this.options.logsPath, `playwright-trace-${PlaywrightDriver.traceCounter++}-${name.replace(/\s+/g, '-')}.zip`); + const nameSuffix = name ? `-${name.replace(/\s+/g, '-')}` : ''; + persistPath = join(this.options.logsPath, `playwright-trace-${PlaywrightDriver.traceCounter++}${nameSuffix}.zip`); } - await measureAndLog(() => this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.options.logger); + await measureAndLog(() => this.context.tracing.stopChunk({ path: persistPath }), `stopTracing${name ? ` for ${name}` : ''}`, this.options.logger); // To ensure we have a screenshot at the end where // it failed, also trigger one explicitly. Tracing @@ -168,9 +169,10 @@ export class PlaywrightDriver { return await this._cdpSession.send('Runtime.getProperties', parameters); } - private async takeScreenshot(name: string): Promise { + private async takeScreenshot(name?: string): Promise { try { - const persistPath = join(this.options.logsPath, `playwright-screenshot-${PlaywrightDriver.screenShotCounter++}-${name.replace(/\s+/g, '-')}.png`); + const nameSuffix = name ? `-${name.replace(/\s+/g, '-')}` : ''; + const persistPath = join(this.options.logsPath, `playwright-screenshot-${PlaywrightDriver.screenShotCounter++}${nameSuffix}.png`); await measureAndLog(() => this.page.screenshot({ path: persistPath, type: 'png' }), 'takeScreenshot', this.options.logger); } catch (error) { diff --git a/test/mcp/src/application.ts b/test/mcp/src/application.ts index fe64d941b4d..a60c7b9764d 100644 --- a/test/mcp/src/application.ts +++ b/test/mcp/src/application.ts @@ -260,7 +260,7 @@ export async function getApplication({ recordVideo }: { recordVideo?: boolean } verbose: opts.verbose, remote: opts.remote, web: opts.web, - tracing: opts.tracing, + tracing: true, headless: opts.headless, browser: opts.browser, extraArgs: (opts.electronArgs || '').split(' ').map(arg => arg.trim()).filter(arg => !!arg), diff --git a/test/mcp/src/automation.ts b/test/mcp/src/automation.ts index 8e4f84a25fe..9163af43e89 100644 --- a/test/mcp/src/automation.ts +++ b/test/mcp/src/automation.ts @@ -24,6 +24,7 @@ export async function getServer(appService: ApplicationService): Promise }, async ({ recordVideo }) => { const app = await appService.getOrCreateApplication({ recordVideo }); + await app.startTracing(); return { content: [{ type: 'text' as const, diff --git a/test/mcp/src/automationTools/core.ts b/test/mcp/src/automationTools/core.ts index 3f9e34cf1e6..591d7437896 100644 --- a/test/mcp/src/automationTools/core.ts +++ b/test/mcp/src/automationTools/core.ts @@ -37,6 +37,7 @@ export function applyCoreTools(server: McpServer, appService: ApplicationService 'Stop the VS Code application', async () => { const app = await appService.getOrCreateApplication(); + await app.stopTracing(undefined, true); await app.stop(); return { content: [{ diff --git a/test/mcp/src/options.ts b/test/mcp/src/options.ts index ac4ded14760..eb81d9982d6 100644 --- a/test/mcp/src/options.ts +++ b/test/mcp/src/options.ts @@ -19,7 +19,6 @@ export const opts = minimist(args, { 'remote', 'web', 'headless', - 'tracing', 'video', 'autostart' ], @@ -31,7 +30,6 @@ export const opts = minimist(args, { remote?: boolean; headless?: boolean; web?: boolean; - tracing?: boolean; build?: string; 'stable-build'?: string; browser?: 'chromium' | 'webkit' | 'firefox' | 'chromium-msedge' | 'chromium-chrome' | undefined; From db5ee1e0f4b6d7d5e8fc880f4096116655d915bc Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:38:33 -0700 Subject: [PATCH 1903/4355] Convert ChatService to use uris instead of ids in public apis Moves the chat service public APIs to use uris for identifying sessions instead of ids. These uris are preferred as they work correctly for both contributed and local session This ends up touch a lot of the chat apis. In most cases, the change is simply to pass in the URI instead of the id. In a few cases where the URI hasn't been fully hooked up, I am using `LocalChatSessionUri` to do this conversation For the chat session implementation, I also switched to use resource maps to store session specific information instead of a normal map --- .../api/browser/mainThreadChatAgents2.ts | 10 +- .../workbench/api/common/extHost.protocol.ts | 2 - .../chat/browser/actions/chatActions.ts | 2 +- .../browser/actions/chatCodeblockActions.ts | 8 +- .../browser/actions/chatExecuteActions.ts | 15 +- .../chat/browser/actions/chatImportExport.ts | 2 +- .../chat/browser/actions/chatTitleActions.ts | 9 +- .../browser/actions/codeBlockOperations.ts | 2 +- src/vs/workbench/contrib/chat/browser/chat.ts | 1 + .../chatChangesSummaryPart.ts | 3 +- .../chatConfirmationContentPart.ts | 2 +- .../chatErrorConfirmationPart.ts | 2 +- .../chatMarkdownContentPart.ts | 9 +- .../chatTextEditContentPart.ts | 2 +- .../browser/chatEditing/chatEditingActions.ts | 13 +- .../chatEditing/chatEditingEditorActions.ts | 3 +- .../chatEditingEditorContextKeys.ts | 2 +- .../chatEditing/chatEditingEditorOverlay.ts | 4 +- .../chatEditingModifiedFileEntry.ts | 3 +- .../chatEditing/chatEditingServiceImpl.ts | 12 +- .../browser/chatEditing/chatEditingSession.ts | 5 +- .../chatEditingTextModelContentProviders.ts | 5 +- .../chatEditingNotebookFileSystemProvider.ts | 3 +- .../contrib/chat/browser/chatEditorInput.ts | 14 +- .../contrib/chat/browser/chatInputPart.ts | 18 +- .../chatMarkdownDecorationsRenderer.ts | 4 +- .../chat/browser/chatPasteProviders.ts | 2 +- .../contrib/chat/browser/chatQuick.ts | 2 +- .../chatSessions/chatSessionTracker.ts | 4 +- .../chatSessions/localChatSessionsProvider.ts | 4 +- .../contrib/chat/browser/chatVariables.ts | 10 +- .../contrib/chat/browser/chatViewPane.ts | 14 +- .../contrib/chat/browser/chatWidget.ts | 24 +- .../browser/contrib/chatInputEditorContrib.ts | 2 +- .../contrib/chatInputRelatedFilesContrib.ts | 2 +- .../chat/browser/languageModelToolsService.ts | 3 +- .../contrib/chat/common/chatEditingService.ts | 4 +- .../contrib/chat/common/chatModel.ts | 9 +- .../contrib/chat/common/chatRequestParser.ts | 7 +- .../chatResponseResourceFileSystemProvider.ts | 3 +- .../contrib/chat/common/chatService.ts | 35 ++- .../contrib/chat/common/chatServiceImpl.ts | 263 ++++++++++++------ .../workbench/contrib/chat/common/chatUri.ts | 5 + .../contrib/chat/common/chatVariables.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 12 + .../contrib/chat/common/tools/editFileTool.ts | 3 +- .../browser/languageModelToolsService.test.ts | 2 + .../chat/test/browser/mockChatWidget.ts | 4 + .../test/common/chatRequestParser.test.ts | 55 ++-- .../chat/test/common/chatService.test.ts | 26 +- .../chat/test/common/mockChatService.ts | 36 +-- .../chat/test/common/mockChatVariables.ts | 22 +- .../inlineChat/browser/inlineChatActions.ts | 2 +- .../browser/inlineChatController.ts | 10 +- .../browser/inlineChatSessionService.ts | 2 +- .../browser/inlineChatSessionServiceImpl.ts | 6 +- .../inlineChat/browser/inlineChatWidget.ts | 3 +- .../test/browser/inlineChatController.test.ts | 6 +- .../mcp/browser/mcpElicitationService.ts | 3 +- .../chat/browser/terminalChatController.ts | 3 +- .../chat/browser/terminalChatWidget.ts | 6 +- .../browser/tools/monitoring/outputMonitor.ts | 3 +- .../browser/tools/runInTerminalTool.ts | 6 +- .../runInTerminalTool.test.ts | 12 +- 64 files changed, 453 insertions(+), 321 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 6f725a98193..9bbd4ae55f5 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -31,6 +31,7 @@ import { IChatModel } from '../../contrib/chat/common/chatModel.js'; import { ChatRequestAgentPart } from '../../contrib/chat/common/chatParserTypes.js'; import { ChatRequestParser } from '../../contrib/chat/common/chatRequestParser.js'; import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatService, IChatTask, IChatTaskSerialized, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; +import { LocalChatSessionUri } from '../../contrib/chat/common/chatUri.js'; import { ChatAgentLocation, ChatModeKind } from '../../contrib/chat/common/constants.js'; import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js'; import { IExtensionService } from '../../services/extensions/common/extensions.js'; @@ -117,7 +118,10 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2); this._register(this._chatService.onDidDisposeSession(e => { - this._proxy.$releaseSession(e.sessionId); + const local = LocalChatSessionUri.parse(e.sessionResource); + if (local) { + this._proxy.$releaseSession(local.sessionId); + } })); this._register(this._chatService.onDidPerformUserAction(e => { if (typeof e.agentId === 'string') { @@ -168,7 +172,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA const impl: IChatAgentImplementation = { invoke: async (request, progress, history, token) => { - const chatSession = this._chatService.getSession(request.sessionId); + const chatSession = this._chatService.getSession(LocalChatSessionUri.forSession(request.sessionId)); this._pendingProgress.set(request.requestId, { progress, chatSession }); try { return await this._proxy.$invokeAgent(handle, request, { @@ -358,7 +362,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return; } - const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue()).parts; + const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionResource, model.getValue()).parts; const agentPart = parsedRequest.find((part): part is ChatRequestAgentPart => part instanceof ChatRequestAgentPart); const thisAgentId = this._agents.get(handle)?.id; if (agentPart?.agent.id !== thisAgentId) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3ac2dc28783..e5ffa9ab32a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1389,8 +1389,6 @@ export type IChatAgentHistoryEntryDto = { }; export interface IChatSessionContextDto { - readonly chatSessionType: string; - readonly chatSessionId: string; readonly chatSessionResource: UriComponents; readonly isUntitled: boolean; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index dc8de8e0b74..84b4ff033f7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -236,7 +236,7 @@ abstract class OpenChatGlobalAction extends Action2 { if (opts?.previousRequests?.length && chatWidget.viewModel) { for (const { request, response } of opts.previousRequests) { - chatService.addCompleteRequest(chatWidget.viewModel.sessionId, request, undefined, 0, { message: response }); + chatService.addCompleteRequest(chatWidget.viewModel.sessionResource, request, undefined, 0, { message: response }); } } if (opts?.attachScreenshot) { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 863d366db7d..9f786ece068 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -160,7 +160,7 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, - sessionId: context.element.sessionId, + sessionResource: context.element.sessionResource, requestId: context.element.requestId, result: context.element.result, action: { @@ -227,7 +227,7 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ agentId: element.agent?.id, command: element.slashCommand?.name, - sessionId: element.sessionId, + sessionResource: element.sessionResource, requestId: element.requestId, result: element.result, action: { @@ -386,7 +386,7 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, - sessionId: context.element.sessionId, + sessionResource: context.element.sessionResource, requestId: context.element.requestId, result: context.element.result, action: { @@ -488,7 +488,7 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, - sessionId: context.element.sessionId, + sessionResource: context.element.sessionResource, requestId: context.element.requestId, result: context.element.result, action: { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 404b636c5f7..69156715d17 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -65,7 +65,7 @@ abstract class SubmitAction extends Action2 { const configurationService = accessor.get(IConfigurationService); const dialogService = accessor.get(IDialogService); const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(widget.viewModel.sessionId); + const chatModel = chatService.getSession(widget.viewModel.sessionResource); if (!chatModel) { return; } @@ -679,7 +679,7 @@ export class CreateRemoteAgentJobAction extends Action2 { chatSessionsService: IChatSessionsService, chatService: IChatService, quickPickService: IQuickInputService, - sessionId: string, + sessionResource: URI, attachedContext: ChatRequestVariableSet, userPrompt: string, chatSummary?: { @@ -687,7 +687,7 @@ export class CreateRemoteAgentJobAction extends Action2 { history?: string; } ) { - await chatService.sendRequest(sessionId, userPrompt, { + await chatService.sendRequest(sessionResource, userPrompt, { agentIdSilent: targetAgentId, attachedContext: attachedContext.asArray(), chatSummary, @@ -802,6 +802,7 @@ export class CreateRemoteAgentJobAction extends Action2 { return; } + const sessionResource = widget.viewModel.sessionResource; const chatRequests = chatModel.getRequests(); let userPrompt = widget.getInput(); if (!userPrompt) { @@ -880,7 +881,7 @@ export class CreateRemoteAgentJobAction extends Action2 { const { type } = agent; // Add the request to the model first - const parsedRequest = requestParser.parseChatRequest(sessionId, userPrompt, ChatAgentLocation.Chat); + const parsedRequest = requestParser.parseChatRequest(sessionResource, userPrompt, ChatAgentLocation.Chat); const addedRequest = chatModel.addRequest( parsedRequest, { variables: attachedContext.asArray() }, @@ -955,13 +956,13 @@ export class CreateRemoteAgentJobAction extends Action2 { const isChatSessionsExperimentEnabled = configurationService.getValue(ChatConfiguration.UseCloudButtonV2); if (isChatSessionsExperimentEnabled) { - await chatService.removeRequest(sessionId, addedRequest.id); + await chatService.removeRequest(sessionResource, addedRequest.id); return await this.createWithChatSessions( type, chatSessionsService, chatService, quickPickService, - sessionId, + sessionResource, attachedContext, userPrompt, { @@ -1181,7 +1182,7 @@ export class CancelAction extends Action2 { const chatService = accessor.get(IChatService); if (widget.viewModel) { - chatService.cancelCurrentRequestForSession(widget.viewModel.sessionId); + chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource); } } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index de5f0c49ecc..f594875b2a6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -57,7 +57,7 @@ export function registerChatExportActions() { outputPath = result; } - const model = chatService.getSession(widget.viewModel.sessionId); + const model = chatService.getSession(widget.viewModel.sessionResource); if (!model) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 90beba71336..212145cede1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -22,6 +22,7 @@ import { NOTEBOOK_IS_ACTIVE_EDITOR } from '../../../notebook/common/notebookCont import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, isChatEditingActionContext } from '../../common/chatEditingService.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatService } from '../../common/chatService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { isResponseVM } from '../../common/chatViewModel.js'; import { ChatModeKind } from '../../common/constants.js'; import { IChatWidgetService } from '../chat.js'; @@ -64,7 +65,7 @@ export function registerChatTitleActions() { chatService.notifyUserAction({ agentId: item.agent?.id, command: item.slashCommand?.name, - sessionId: item.sessionId, + sessionResource: item.session.sessionResource, requestId: item.requestId, result: item.result, action: { @@ -119,7 +120,7 @@ export function registerChatTitleActions() { chatService.notifyUserAction({ agentId: item.agent?.id, command: item.slashCommand?.name, - sessionId: item.sessionId, + sessionResource: item.session.sessionResource, requestId: item.requestId, result: item.result, action: { @@ -163,7 +164,7 @@ export function registerChatTitleActions() { chatService.notifyUserAction({ agentId: item.agent?.id, command: item.slashCommand?.name, - sessionId: item.sessionId, + sessionResource: item.session.sessionResource, requestId: item.requestId, result: item.result, action: { @@ -212,7 +213,7 @@ export function registerChatTitleActions() { } const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(item.sessionId); + const chatModel = chatService.getSession(LocalChatSessionUri.forSession(item.sessionId)); const chatRequests = chatModel?.getRequests(); if (!chatRequests) { return; diff --git a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts index da4b786e881..82e8fbe0db3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts @@ -432,7 +432,7 @@ function notifyUserAction(chatService: IChatService, context: ICodeBlockActionCo chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, - sessionId: context.element.sessionId, + sessionResource: context.element.sessionResource, requestId: context.element.requestId, result: context.element.result, action diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index a2e2c60e0f2..ff3ec632d90 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -49,6 +49,7 @@ export interface IChatWidgetService { getAllWidgets(): ReadonlyArray; getWidgetByInputUri(uri: URI): IChatWidget | undefined; getWidgetBySessionId(sessionId: string): IChatWidget | undefined; + getWidgetBySessionResource(sessionResource: URI): IChatWidget | undefined; getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatChangesSummaryPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatChangesSummaryPart.ts index edc61c0d63f..7b25ea957d9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatChangesSummaryPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatChangesSummaryPart.ts @@ -31,6 +31,7 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG import { Emitter } from '../../../../../base/common/event.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { localize2 } from '../../../../../nls.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; export class ChatCheckpointFileChangesSummaryContentPart extends Disposable implements IChatContentPart { @@ -83,7 +84,7 @@ export class ChatCheckpointFileChangesSummaryContentPart extends Disposable impl const lastRequestId = changes[changes.length - 1].requestId; for (const change of changes) { const sessionId = change.sessionId; - const session = this.chatService.getSession(sessionId); + const session = this.chatService.getSession(LocalChatSessionUri.forSession(sessionId)); if (!session || !session.editingSessionObs) { continue; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts index 4cde29a6de3..34f4a66c545 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -60,7 +60,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont options.location = widget?.location; Object.assign(options, widget?.getModeRequestOptions()); - if (await this.chatService.sendRequest(element.sessionId, prompt, options)) { + if (await this.chatService.sendRequest(element.sessionResource, prompt, options)) { confirmation.isUsed = true; confirmationWidget.setShowButtons(false); this._onDidChangeHeight.fire(); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts index 3f7e1e92ddb..ce37a100942 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts @@ -62,7 +62,7 @@ export class ChatErrorConfirmationContentPart extends Disposable implements ICha const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); options.userSelectedModelId = widget?.input.currentLanguageModel; Object.assign(options, widget?.getModeRequestOptions()); - if (await chatService.sendRequest(element.sessionId, prompt, options)) { + if (await chatService.sendRequest(element.sessionResource, prompt, options)) { this._onDidChangeHeight.fire(); } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index e3f0d01a4ed..904006eaad9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -220,7 +220,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP return ref.object.element; } else { const requestId = isRequestVM(element) ? element.id : element.requestId; - const ref = this.renderCodeBlockPill(element.sessionId, requestId, inUndoStop, codeBlockInfo.codemapperUri); + const ref = this.renderCodeBlockPill(element.sessionId, element.sessionResource, requestId, inUndoStop, codeBlockInfo.codemapperUri); if (isResponseVM(codeBlockInfo.element)) { // TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => { @@ -322,8 +322,8 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP } } - private renderCodeBlockPill(sessionId: string, requestId: string, inUndoStop: string | undefined, codemapperUri: URI | undefined): IDisposableReference { - const codeBlock = this.instantiationService.createInstance(CollapsedCodeBlock, sessionId, requestId, inUndoStop); + private renderCodeBlockPill(sessionId: string, sessionResource: URI, requestId: string, inUndoStop: string | undefined, codemapperUri: URI | undefined): IDisposableReference { + const codeBlock = this.instantiationService.createInstance(CollapsedCodeBlock, sessionId, sessionResource, requestId, inUndoStop); if (codemapperUri) { codeBlock.render(codemapperUri); } @@ -432,6 +432,7 @@ export class CollapsedCodeBlock extends Disposable { constructor( private readonly sessionId: string, + private readonly sessionResource: URI, private readonly requestId: string, private readonly inUndoStop: string | undefined, @ILabelService private readonly labelService: ILabelService, @@ -501,7 +502,7 @@ export class CollapsedCodeBlock extends Disposable { this._uri = uri; - const session = this.chatService.getSession(this.sessionId); + const session = this.chatService.getSession(this.sessionResource); const iconText = this.labelService.getUriBasenameLabel(uri); const iconEl = dom.$('span.icon'); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts index 33680984687..8d27b059222 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts @@ -210,7 +210,7 @@ class CodeCompareModelService implements ICodeCompareModelService { } // apply edits to the "modified" model - const chatModel = this.chatService.getSession(element.sessionId)!; + const chatModel = this.chatService.getSession(element.sessionResource)!; const editGroups: ISingleEditOperation[][] = []; for (const request of chatModel.getRequests()) { if (!request.response) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 63b8ba4e937..59d6eff3af4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -30,6 +30,7 @@ import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; @@ -74,9 +75,7 @@ export function getEditingSessionContext(accessor: ServicesAccessor, args: any[] return; } - const chatSessionId = chatWidget.viewModel.model.sessionId; - const editingSession = chatEditingService.getEditingSession(chatSessionId); - + const editingSession = chatEditingService.getEditingSession(chatWidget.viewModel.model.sessionResource); return { editingSession, chatWidget }; } @@ -309,7 +308,7 @@ async function restoreSnapshotWithConfirmation(accessor: ServicesAccessor, item: const chatWidgetService = accessor.get(IChatWidgetService); const widget = chatWidgetService.lastFocusedWidget; const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(item.sessionId); + const chatModel = chatService.getSession(LocalChatSessionUri.forSession(item.sessionId)); if (!chatModel) { return; } @@ -504,7 +503,7 @@ registerAction2(class RestoreLastCheckpoint extends Action2 { return; } - const chatModel = chatService.getSession(item.sessionId); + const chatModel = chatService.getSession(LocalChatSessionUri.forSession(item.sessionId)); if (!chatModel) { return; } @@ -620,12 +619,12 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { const chatEditingService = accessor.get(IChatEditingService); const editorService = accessor.get(IEditorService); - const chatModel = chatService.getSession(context.sessionId); + const chatModel = chatService.getSession(LocalChatSessionUri.forSession(context.sessionId)); if (!chatModel) { return; } - const snapshot = chatEditingService.getEditingSession(chatModel.sessionId)?.getSnapshotUri(context.requestId, context.uri, context.stopId); + const snapshot = chatEditingService.getEditingSession(chatModel.sessionResource)?.getSnapshotUri(context.requestId, context.uri, context.stopId); if (snapshot) { const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot)', basename(context.uri)), options: { activation: EditorActivation.ACTIVATE } }); if (isCodeEditor(editor)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 12115827235..14f8d78514d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -21,6 +21,7 @@ import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiff import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_FOCUSED } from '../../../notebook/common/notebookContextKeys.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; import { ctxHasEditorModification, ctxIsCurrentlyBeingModified, ctxIsGlobalEditingSession, ctxReviewModeEnabled } from './chatEditingEditorContextKeys.js'; @@ -422,7 +423,7 @@ abstract class MultiDiffAcceptDiscardAction extends Action2 { return; } - const session = chatEditingService.getEditingSession(editor.resource.authority); + const session = chatEditingService.getEditingSession(LocalChatSessionUri.forSession(editor.resource.authority)); if (this.accept) { await session?.accept(); } else { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts index f003f941d1a..39275b3d998 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts @@ -115,7 +115,7 @@ class ContextKeyGroup { const { session, entry } = tuple; - const chatModel = chatService.getSession(session.chatSessionId); + const chatModel = chatService.getSession(session.chatSessionResource); this._ctxHasEditorModification.set(entry?.state.read(r) === ModifiedFileEntryState.Modified); this._ctxIsGlobalEditingSession.set(session.isGlobalEditingSession); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts index 677f3c5c0ca..58f63cf2d10 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts @@ -66,7 +66,7 @@ class ChatEditorOverlayWidget extends Disposable { const requestMessage = derived(r => { const session = this._session.read(r); - const chatModel = this._chatService.getSession(session?.chatSessionId ?? ''); + const chatModel = session?.chatSessionResource && this._chatService.getSession(session?.chatSessionResource); if (!session || !chatModel) { return undefined; } @@ -375,7 +375,7 @@ class ChatEditingOverlayController { return false; } - const chatModel = chatService.getSession(session.chatSessionId)!; + const chatModel = chatService.getSession(session.chatSessionResource)!; return chatModel.requestInProgressObs.read(r); }); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index a774a1968c1..738eecbf46f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -26,6 +26,7 @@ import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; import { ChatEditKind, IModifiedEntryTelemetryInfo, IModifiedFileEntry, IModifiedFileEntryEditorIntegration, ISnapshotEntry, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { ChatUserAction, IChatService } from '../../common/chatService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; class AutoAcceptControl { constructor( @@ -296,7 +297,7 @@ export abstract class AbstractChatEditingModifiedFileEntry extends Disposable im modelId: this._telemetryInfo.modelId, modeId: this._telemetryInfo.modeId, command: this._telemetryInfo.command, - sessionId: this._telemetryInfo.sessionId, + sessionResource: LocalChatSessionUri.forSession(this._telemetryInfo.sessionId), requestId: this._telemetryInfo.requestId, result: this._telemetryInfo.result }); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index facffdc816c..35ee7bf526f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -90,7 +90,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic this._register(this._chatService.onDidDisposeSession((e) => { if (e.reason === 'cleared') { - this.getEditingSession(e.sessionId)?.stop(); + this.getEditingSession(e.sessionResource)?.stop(); } })); @@ -143,7 +143,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic await this._restoringEditingSession; } - const session = this.getEditingSession(chatModel.sessionId); + const session = this.getEditingSession(chatModel.sessionResource); if (session) { return session; } @@ -164,16 +164,16 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return undefined; } - getEditingSession(chatSessionId: string): IChatEditingSession | undefined { + getEditingSession(chatSessionResource: URI): IChatEditingSession | undefined { return this.editingSessionsObs.get() - .find(candidate => candidate.chatSessionId === chatSessionId); + .find(candidate => isEqual(candidate.chatSessionResource, chatSessionResource)); } async createEditingSession(chatModel: ChatModel, global: boolean = false): Promise { - assertType(this.getEditingSession(chatModel.sessionId) === undefined, 'CANNOT have more than one editing session per chat session'); + assertType(this.getEditingSession(chatModel.sessionResource) === undefined, 'CANNOT have more than one editing session per chat session'); - const session = this._instantiationService.createInstance(ChatEditingSession, chatModel.sessionId, global, this._lookupEntry.bind(this)); + const session = this._instantiationService.createInstance(ChatEditingSession, chatModel.sessionId, chatModel.sessionResource, global, this._lookupEntry.bind(this)); await session.init(); const list = this._sessionsObs.get(); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index f0397b987ad..d3cfc236c68 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -166,6 +166,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio constructor( readonly chatSessionId: string, + readonly chatSessionResource: URI, readonly isGlobalEditingSession: boolean, private _lookupExternalEntry: (uri: URI) => AbstractChatEditingModifiedFileEntry | undefined, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -193,7 +194,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._register(autorun(reader => { const disabled = this._timeline.requestDisablement.read(reader); - this._chatService.getSession(this.chatSessionId)?.setDisabledRequests(disabled); + this._chatService.getSession(this.chatSessionResource)?.setDisabledRequests(disabled); })); } @@ -430,7 +431,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio override dispose() { this._assertNotDisposed(); - this._chatService.cancelCurrentRequestForSession(this.chatSessionId); + this._chatService.cancelCurrentRequestForSession(this.chatSessionResource); dispose(this._entriesObs.get()); super.dispose(); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts index 85b3b62ccff..1970952db3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts @@ -9,6 +9,7 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelContentProvider } from '../../../../../editor/common/services/resolverService.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; type ChatEditingTextModelContentQueryData = { kind: 'doc'; documentId: string; chatSessionId: string }; @@ -36,7 +37,7 @@ export class ChatEditingTextModelContentProvider implements ITextModelContentPro const data: ChatEditingTextModelContentQueryData = JSON.parse(resource.query); - const session = this._chatEditingService.getEditingSession(data.chatSessionId); + const session = this._chatEditingService.getEditingSession(LocalChatSessionUri.forSession(data.chatSessionId)); const entry = session?.entries.get().find(candidate => candidate.entryId === data.documentId); if (!entry) { @@ -70,7 +71,7 @@ export class ChatEditingSnapshotTextModelContentProvider implements ITextModelCo } const data: ChatEditingSnapshotTextModelContentQueryData = JSON.parse(resource.query); - const session = this._chatEditingService.getEditingSession(data.sessionId); + const session = this._chatEditingService.getEditingSession(LocalChatSessionUri.forSession(data.sessionId)); if (!session || !data.requestId) { return null; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts index 0918eeba8d8..1239dc59a98 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookFileSystemProvider.ts @@ -14,6 +14,7 @@ import { FileSystemProviderCapabilities, FileType, IFileChange, IFileDeleteOptio import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContribution } from '../../../../../common/contributions.js'; import { IChatEditingService } from '../../../common/chatEditingService.js'; +import { LocalChatSessionUri } from '../../../common/chatUri.js'; import { ChatEditingNotebookSnapshotScheme } from './chatEditingModifiedNotebookSnapshot.js'; @@ -85,7 +86,7 @@ export class ChatEditingNotebookFileSystemProvider implements IFileSystemProvide if (!queryData.viewType) { throw new Error('File not found, viewType not found'); } - const session = this._chatEditingService.getEditingSession(queryData.sessionId); + const session = this._chatEditingService.getEditingSession(LocalChatSessionUri.forSession(queryData.sessionId)); if (!session || !queryData.requestId) { throw new Error('File not found, session not found'); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 076582ee245..b9e745f6a5c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -101,8 +101,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler // Check if we already have a custom title for this session const hasExistingCustomTitle = this._sessionInfo?.sessionId && ( - this.chatService.getSession(this._sessionInfo?.sessionId)?.title || - this.chatService.getPersistedSessionTitle(this._sessionInfo?.sessionId)?.trim() + this.chatService.getSession(this._sessionInfo?.resource)?.title || + this.chatService.getPersistedSessionTitle(this._sessionInfo?.resource)?.trim() ); this.hasCustomTitle = Boolean(hasExistingCustomTitle); @@ -178,13 +178,13 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler // If we have a sessionId but no resolved model, try to get the title from persisted sessions if (this._sessionInfo?.sessionId) { // First try the active session registry - const existingSession = this.chatService.getSession(this._sessionInfo?.sessionId); + const existingSession = this.chatService.getSession(this._sessionInfo?.resource); if (existingSession?.title) { return existingSession.title; } // If not in active registry, try persisted session data - const persistedTitle = this.chatService.getPersistedSessionTitle(this._sessionInfo?.sessionId); + const persistedTitle = this.chatService.getPersistedSessionTitle(this._sessionInfo?.resource); if (persistedTitle && persistedTitle.trim()) { // Only use non-empty persisted titles return persistedTitle; } @@ -265,7 +265,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler if (this.resource.scheme !== Schemas.vscodeChatEditor && this.resource.scheme !== Schemas.vscodeLocalChatSession) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (this._sessionInfo?.sessionId) { - this.model = await this.chatService.getOrRestoreSession(this._sessionInfo.sessionId) + this.model = await this.chatService.getOrRestoreSession(this._sessionInfo.resource) ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); } else if (!this.options.target) { this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType, inputType: inputType }); @@ -319,8 +319,8 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler override dispose(): void { super.dispose(); - if (this._sessionInfo?.sessionId) { - this.chatService.clearSession(this._sessionInfo.sessionId); + if (this._sessionInfo) { + this.chatService.clearSession(this._sessionInfo.resource); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 682fb4aa0b8..899f91f2eb8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -655,11 +655,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Helper to resolve chat session context const resolveChatSessionContext = () => { - const sessionId = this._widget?.viewModel?.model.sessionId; - if (!sessionId) { + const sessionResource = this._widget?.viewModel?.model.sessionResource; + if (!sessionResource) { return undefined; } - return this.chatService.getChatSessionFromInternalId(sessionId); + return this.chatService.getChatSessionFromInternalUri(sessionResource); }; // Get all option groups for the current session type @@ -1141,16 +1141,16 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge * Fires events for each option group with their current selection. */ private refreshChatSessionPickers(): void { - const sessionId = this._widget?.viewModel?.model.sessionId; + const sessionResource = this._widget?.viewModel?.model.sessionResource; const hideAll = () => { this.chatSessionHasOptions.set(false); this.hideAllSessionPickerWidgets(); }; - if (!sessionId) { + if (!sessionResource) { return hideAll(); } - const ctx = this.chatService.getChatSessionFromInternalId(sessionId); + const ctx = this.chatService.getChatSessionFromInternalUri(sessionResource); if (!ctx) { return hideAll(); } @@ -1218,11 +1218,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge * If no option is currently set, initializes with the first item as default. */ private getCurrentOptionForGroup(optionGroupId: string): IChatSessionProviderOptionItem | undefined { - const sessionId = this._widget?.viewModel?.model.sessionId; - if (!sessionId) { + const sessionResource = this._widget?.viewModel?.model.sessionResource; + if (!sessionResource) { return; } - const ctx = this.chatService.getChatSessionFromInternalId(sessionId); + const ctx = this.chatService.getChatSessionFromInternalUri(sessionResource); if (!ctx) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 7366a025d39..965d5e13262 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -184,7 +184,7 @@ export class ChatMarkdownDecorationsRenderer { return; } - this.chatService.sendRequest(widget.viewModel!.sessionId, agent.metadata.sampleRequest ?? '', + this.chatService.sendRequest(widget.viewModel!.sessionResource, agent.metadata.sampleRequest ?? '', { location: widget.location, agentId: agent.id, @@ -221,7 +221,7 @@ export class ChatMarkdownDecorationsRenderer { } const command = agent.slashCommands.find(c => c.name === args.command); - this.chatService.sendRequest(widget.viewModel!.sessionId, command?.sampleRequest ?? '', { + this.chatService.sendRequest(widget.viewModel!.sessionResource, command?.sampleRequest ?? '', { location: widget.location, agentId: agent.id, slashCommand: args.command, diff --git a/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts b/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts index 2ef09915ae5..6ea16686299 100644 --- a/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatPasteProviders.ts @@ -212,7 +212,7 @@ class CopyAttachmentsProvider implements DocumentPasteEditProvider { } const attachments = widget.attachmentModel.attachments; - const dynamicVariables = this.chatVariableService.getDynamicVariables(widget.viewModel.sessionId); + const dynamicVariables = this.chatVariableService.getDynamicVariables(widget.viewModel.sessionResource); if (attachments.length === 0 && dynamicVariables.length === 0) { return undefined; diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 92f7f0ffea6..803fe42efe5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -358,7 +358,7 @@ class QuickChat extends Disposable { } } - this.chatService.addCompleteRequest(widget.viewModel.sessionId, + this.chatService.addCompleteRequest(widget.viewModel.sessionResource, request.message as IParsedChatRequest, request.variableData, request.attempt, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts index e43a6c54d23..498a8d5ee90 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionTracker.ts @@ -90,8 +90,8 @@ export class ChatSessionTracker extends Disposable { let status: ChatSessionStatus = ChatSessionStatus.Completed; let timestamp: number | undefined; - if (editor.sessionId) { - const model = this.chatService.getSession(editor.sessionId); + if (editor.sessionResource) { + const model = this.chatService.getSession(editor.sessionResource); const modelStatus = model ? this.modelToStatus(model) : undefined; if (model && modelStatus) { status = modelStatus; diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts index 191fa4f7812..5fbc1be0e61 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts @@ -220,8 +220,8 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio // Determine status and timestamp for editor-based session let status: ChatSessionStatus | undefined; let startTime: number | undefined; - if (editorInfo.editor instanceof ChatEditorInput && editorInfo.editor.sessionId) { - const model = this.chatService.getSession(editorInfo.editor.sessionId); + if (editorInfo.editor instanceof ChatEditorInput && editorInfo.editor.sessionResource && editorInfo.editor.sessionId) { + const model = this.chatService.getSession(editorInfo.editor.sessionResource); if (model) { status = this.modelToStatus(model); // Get the last interaction timestamp from the model diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 63c4c25e37d..6ac4bf70bba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -8,6 +8,7 @@ import { IToolAndToolSetEnablementMap } from '../common/languageModelToolsServic import { IChatWidgetService } from './chat.js'; import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; import { Range } from '../../../../editor/common/core/range.js'; +import { URI } from '../../../../base/common/uri.js'; export class ChatVariablesService implements IChatVariablesService { declare _serviceBrand: undefined; @@ -16,12 +17,12 @@ export class ChatVariablesService implements IChatVariablesService { @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, ) { } - getDynamicVariables(sessionId: string): ReadonlyArray { + getDynamicVariables(sessionResource: URI): ReadonlyArray { // This is slightly wrong... the parser pulls dynamic references from the input widget, but there is no guarantee that message came from the input here. // Need to ... // - Parser takes list of dynamic references (annoying) // - Or the parser is known to implicitly act on the input widget, and we need to call it before calling the chat service (maybe incompatible with the future, but easy) - const widget = this.chatWidgetService.getWidgetBySessionId(sessionId); + const widget = this.chatWidgetService.getWidgetBySessionResource(sessionResource); if (!widget || !widget.viewModel || !widget.supportsFileReferences) { return []; } @@ -56,13 +57,12 @@ export class ChatVariablesService implements IChatVariablesService { return model.variables; } - getSelectedToolAndToolSets(sessionId: string): IToolAndToolSetEnablementMap { - const widget = this.chatWidgetService.getWidgetBySessionId(sessionId); + getSelectedToolAndToolSets(sessionResource: URI): IToolAndToolSetEnablementMap { + const widget = this.chatWidgetService.getWidgetBySessionResource(sessionResource); if (!widget) { return new Map(); } return widget.input.selectedToolsModel.entriesMap.get(); } - } diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 4facfb1aedb..811d4b296b2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -84,7 +84,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const lastEditsState = editsMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); if (lastEditsState.sessionId) { this.logService.trace(`ChatViewPane: last edits session was ${lastEditsState.sessionId}`); - if (!this.chatService.isPersistedSessionEmpty(lastEditsState.sessionId)) { + if (!this.chatService.isPersistedSessionEmpty(LocalChatSessionUri.forSession(lastEditsState.sessionId))) { this.logService.info(`ChatViewPane: migrating ${lastEditsState.sessionId} to unified view`); this.viewState.sessionId = lastEditsState.sessionId; this.viewState.inputValue = lastEditsState.inputValue; @@ -102,7 +102,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { if (!this._widget?.viewModel && !this._restoringSession) { const info = this.getTransferredOrPersistedSessionInfo(); this._restoringSession = - (info.sessionId ? this.chatService.getOrRestoreSession(info.sessionId) : Promise.resolve(undefined)).then(async model => { + (info.sessionId ? this.chatService.getOrRestoreSession(LocalChatSessionUri.forSession(info.sessionId)) : Promise.resolve(undefined)).then(async model => { if (!this._widget) { // renderBody has not been called yet return; @@ -141,7 +141,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.modelDisposables.clear(); model = model ?? (this.chatService.transferredSessionData?.sessionId && this.chatService.transferredSessionData?.location === this.chatOptions.location - ? await this.chatService.getOrRestoreSession(this.chatService.transferredSessionData.sessionId) + ? await this.chatService.getOrRestoreSession(LocalChatSessionUri.forSession(this.chatService.transferredSessionData.sessionId)) : this.chatService.startSession(this.chatOptions.location, CancellationToken.None)); if (!model) { throw new Error('Could not start chat session'); @@ -225,7 +225,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this._widget.render(parent); const info = this.getTransferredOrPersistedSessionInfo(); - const model = info.sessionId ? await this.chatService.getOrRestoreSession(info.sessionId) : undefined; + const model = info.sessionId ? await this.chatService.getOrRestoreSession(LocalChatSessionUri.forSession(info.sessionId)) : undefined; await this.updateModel(model, info.inputValue || info.mode ? { inputState: { chatMode: info.mode }, inputValue: info.inputValue } : undefined); } @@ -236,7 +236,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { private async clear(): Promise { if (this.widget.viewModel) { - await this.chatService.clearSession(this.widget.viewModel.sessionId); + await this.chatService.clearSession(this.widget.viewModel.sessionResource); } // Grab the widget's latest view state because it will be loaded back into the widget @@ -249,7 +249,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { async loadSession(sessionId: string | URI, viewState?: IChatViewState): Promise { if (this.widget.viewModel) { - await this.chatService.clearSession(this.widget.viewModel.sessionId); + await this.chatService.clearSession(this.widget.viewModel.sessionResource); } // Handle locking for contributed chat sessions @@ -265,7 +265,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } } - const newModel = await (URI.isUri(sessionId) ? this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Chat, CancellationToken.None) : this.chatService.getOrRestoreSession(sessionId)); + const newModel = await (URI.isUri(sessionId) ? this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Chat, CancellationToken.None) : this.chatService.getOrRestoreSession(LocalChatSessionUri.forSession(sessionId))); await this.updateModel(newModel, viewState); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d0089fc40ff..9773ce7ee33 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -95,6 +95,7 @@ import { ChatViewPane } from './chatViewPane.js'; import { ChatViewWelcomePart, IChatSuggestedPrompts, IChatViewWelcomeContent } from './viewsWelcome/chatViewWelcomeController.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; const $ = dom.$; @@ -459,7 +460,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser) - .parseChatRequest(this.viewModel!.sessionId, this.getInput(), this.location, { + .parseChatRequest(this.viewModel!.sessionResource, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind, forcedAgent: this._lockedAgent?.id ? this.chatAgentService.getAgent(this._lockedAgent.id) : undefined @@ -929,7 +930,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!this.viewModel) { return; } - this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind }); + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionResource, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind }); this._onDidChangeParsedInput.fire(); } @@ -1788,7 +1789,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.acceptInput(item.message); })); this._register(this.renderer.onDidClickRerunWithAgentOrCommandDetection(item => { - const request = this.chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); + const request = this.chatService.getSession(LocalChatSessionUri.forSession(item.sessionId))?.getRequests().find(candidate => candidate.id === item.requestId); if (request) { const options: IChatSendRequestOptions = { noCommandDetection: true, @@ -1877,6 +1878,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private clickedRequest(item: IChatListItemTemplate) { + const currentElement = item.currentElement; if (isRequestVM(currentElement) && !this.viewModel?.editing) { @@ -2193,7 +2195,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.chatService.notifyUserAction({ - sessionId: this.viewModel.sessionId, + sessionResource: this.viewModel.sessionResource, requestId: e.response.requestId, agentId: e.response.agent?.id, command: e.response.slashCommand?.name, @@ -2462,8 +2464,8 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - const sessionId = this.viewModel.sessionId; - const lastRequest = this.chatService.getSession(sessionId)?.getRequests().at(-1); + const sessionResource = this.viewModel.sessionResource; + const lastRequest = this.chatService.getSession(sessionResource)?.getRequests().at(-1); if (!lastRequest) { return; } @@ -2603,7 +2605,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: uniqueWorkingSetEntries.size, actualSize: uniqueWorkingSetEntries.size }); } - this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionId); + this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionResource); if (this.currentRequest) { // We have to wait the current request to be properly cancelled so that it has a chance to update the model with its result metadata. // This is awkward, it's basically a limitation of the chat provider-based agent. @@ -2617,12 +2619,12 @@ export class ChatWidget extends Disposable implements IChatWidget { for (let i = requests.length - 1; i >= 0; i -= 1) { const request = requests[i]; if (request.shouldBeBlocked) { - this.chatService.removeRequest(this.viewModel.sessionId, request.id); + this.chatService.removeRequest(this.viewModel.sessionResource, request.id); } } } - const result = await this.chatService.sendRequest(this.viewModel.sessionId, requestInputs.input, { + const result = await this.chatService.sendRequest(this.viewModel.sessionResource, requestInputs.input, { userSelectedModelId: this.input.currentLanguageModel, location: this.location, locationData: this._location.resolveData?.(), @@ -2978,6 +2980,10 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService return this._widgets.find(w => w.viewModel?.sessionId === sessionId); } + getWidgetBySessionResource(sessionResource: URI): ChatWidget | undefined { + return this._widgets.find(w => isEqual(w.viewModel?.sessionResource, sessionResource)); + } + private setLastFocusedWidget(widget: ChatWidget | undefined): void { if (widget === this._lastFocusedWidget) { return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 6d6234e8912..316d653e546 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -363,7 +363,7 @@ class ChatTokenDeleter extends Disposable { // If this was a simple delete, try to find out whether it was inside a token if (!change.text && this.widget.viewModel) { - const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind }); + const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionResource, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind }); // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestSlashPromptPart || p instanceof ChatRequestToolPart); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts index 5b1f9bd553c..97b4f7f77e6 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -55,7 +55,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben return; } - const currentEditingSession = this.chatEditingService.getEditingSession(widget.viewModel.sessionId); + const currentEditingSession = this.chatEditingService.getEditingSession(widget.viewModel.sessionResource); if (!currentEditingSession || currentEditingSession.entries.get().length) { return; // Might have disposed while we were calculating } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 835bb26968b..28052c96396 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -43,6 +43,7 @@ import { ChatRequestToolReferenceEntry, toToolSetVariableEntry, toToolVariableEn import { ChatConfiguration } from '../common/constants.js'; import { CountTokensCallback, createToolSchemaUri, ILanguageModelToolsService, IPreparedToolInvocation, IToolAndToolSetEnablementMap, IToolData, IToolImpl, IToolInvocation, IToolResult, IToolResultInputOutputDetails, stringifyPromptTsxPart, ToolDataSource, ToolSet } from '../common/languageModelToolsService.js'; import { getToolConfirmationAlert } from './chatAccessibilityProvider.js'; +import { LocalChatSessionUri } from '../common/chatUri.js'; const jsonSchemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -284,7 +285,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo try { if (dto.context) { store = new DisposableStore(); - const model = this._chatService.getSession(dto.context?.sessionId) as ChatModel | undefined; + const model = this._chatService.getSession(LocalChatSessionUri.forSession(dto.context.sessionId)) as ChatModel | undefined; if (!model) { throw new Error(`Tool called for unknown chat session`); } diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 4017c5b59f6..af58d4da5a5 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -30,7 +30,7 @@ export interface IChatEditingService { startOrContinueGlobalEditingSession(chatModel: ChatModel): Promise; - getEditingSession(chatSessionId: string): IChatEditingSession | undefined; + getEditingSession(chatSessionResource: URI): IChatEditingSession | undefined; /** * All editing sessions, sorted by recency, e.g the last created session comes first. @@ -107,7 +107,9 @@ export interface ISnapshotEntry { export interface IChatEditingSession extends IDisposable { readonly isGlobalEditingSession: boolean; + /** @deprecated */ readonly chatSessionId: string; + readonly chatSessionResource: URI; readonly onDidDispose: Event; readonly state: IObservable; readonly entries: IObservable; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 7b28b9ce796..0551bd7fcf6 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1139,7 +1139,6 @@ export interface IExportableChatData { responderUsername: string; requesterAvatarIconUri: UriComponents | undefined; responderAvatarIconUri: ThemeIcon | UriComponents | undefined; // Keeping Uri name for backcompat - inputType?: string; } /* @@ -1454,11 +1453,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._editingSession?.promiseResult.get()?.data; } - private readonly _inputType: string | undefined; - get inputType(): string | undefined { - return this._inputType; - } - private readonly _initialLocation: ChatAgentLocation; get initialLocation(): ChatAgentLocation { return this._initialLocation; @@ -1471,7 +1465,7 @@ export class ChatModel extends Disposable implements IChatModel { constructor( initialData: ISerializableChatData | IExportableChatData | undefined, - initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; inputType?: string; resource?: URI }, + initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; resource?: URI }, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @@ -1497,7 +1491,6 @@ export class ChatModel extends Disposable implements IChatModel { this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; - this._inputType = initialData?.inputType ?? initialModelProps.inputType; this._initialLocation = initialData?.initialLocation ?? initialModelProps.initialLocation; this._canUseTools = initialModelProps.canUseTools; diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 556da3c6ade..c47116a4c8b 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI } from '../../../../base/common/uri.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; @@ -34,12 +35,12 @@ export class ChatRequestParser { @IPromptsService private readonly promptsService: IPromptsService, ) { } - parseChatRequest(sessionId: string, message: string, location: ChatAgentLocation = ChatAgentLocation.Chat, context?: IChatParserContext): IParsedChatRequest { + parseChatRequest(sessionResource: URI, message: string, location: ChatAgentLocation = ChatAgentLocation.Chat, context?: IChatParserContext): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; - const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls + const references = this.variableService.getDynamicVariables(sessionResource); // must access this list before any async calls const toolsByName = new Map(); const toolSetsByName = new Map(); - for (const [entry, enabled] of this.variableService.getSelectedToolAndToolSets(sessionId)) { + for (const [entry, enabled] of this.variableService.getSelectedToolAndToolSets(sessionResource)) { if (enabled) { if (entry instanceof ToolSet) { toolSetsByName.set(entry.referenceName, entry); diff --git a/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts b/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts index b61ced7561c..98aad3f975e 100644 --- a/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatResponseResourceFileSystemProvider.ts @@ -12,6 +12,7 @@ import { createFileSystemProviderError, FileSystemProviderCapabilities, FileSyst import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ChatResponseResource } from './chatModel.js'; import { IChatService, IChatToolInvocation, IChatToolInvocationSerialized } from './chatService.js'; +import { LocalChatSessionUri } from './chatUri.js'; import { isToolResultInputOutputDetails } from './languageModelToolsService.js'; export class ChatResponseResourceFileSystemProvider extends Disposable implements @@ -90,7 +91,7 @@ export class ChatResponseResourceFileSystemProvider extends Disposable implement throw createFileSystemProviderError(`File not found`, FileSystemProviderErrorCode.FileNotFound); } const { sessionId, toolCallId, index } = parsed; - const session = this.chatService.getSession(sessionId); + const session = this.chatService.getSession(LocalChatSessionUri.forSession(sessionId)); if (!session) { throw createFileSystemProviderError(`File not found`, FileSystemProviderErrorCode.FileNotFound); } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 1037d3ba259..a94e802ed94 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -790,7 +790,7 @@ export interface IChatUserActionEvent { action: ChatUserAction; agentId: string | undefined; command: string | undefined; - sessionId: string; + sessionResource: URI; requestId: string; result: IChatAgentResult | undefined; modelId?: string | undefined; @@ -905,31 +905,31 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - readonly onDidSubmitRequest: Event<{ chatSessionId: string }>; + readonly onDidSubmitRequest: Event<{ readonly chatSessionId: string }>; isEnabled(location: ChatAgentLocation): boolean; hasSessions(): boolean; startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, options?: { canUseTools?: boolean; inputType?: string }): ChatModel; - getSession(sessionId: string): IChatModel | undefined; - getOrRestoreSession(sessionId: string): Promise; - getPersistedSessionTitle(sessionId: string): string | undefined; - isPersistedSessionEmpty(sessionId: string): boolean; + getSession(sessionResource: URI): IChatModel | undefined; + getOrRestoreSession(sessionResource: URI): Promise; + getPersistedSessionTitle(sessionResource: URI): string | undefined; + isPersistedSessionEmpty(sessionResource: URI): boolean; loadSessionFromContent(data: IExportableChatData | ISerializableChatData | URI): IChatModel | undefined; loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise; readonly editingSessions: IChatEditingSession[]; - getChatSessionFromInternalId(sessionId: string): IChatSessionContext | undefined; + getChatSessionFromInternalUri(sessionResource: URI): IChatSessionContext | undefined; /** * Returns whether the request was accepted.` */ - sendRequest(sessionId: string, message: string, options?: IChatSendRequestOptions): Promise; + sendRequest(sessionResource: URI, message: string, options?: IChatSendRequestOptions): Promise; resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions): Promise; - adoptRequest(sessionId: string, request: IChatRequestModel): Promise; - removeRequest(sessionid: string, requestId: string): Promise; - cancelCurrentRequestForSession(sessionId: string): void; - clearSession(sessionId: string): Promise; - addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void; + adoptRequest(sessionResource: URI, request: IChatRequestModel): Promise; + removeRequest(sessionResource: URI, requestId: string): Promise; + cancelCurrentRequestForSession(sessionResource: URI): void; + clearSession(sessionResource: URI): Promise; + addCompleteRequest(sessionResource: URI, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void; setChatSessionTitle(sessionResource: URI, title: string): void; getLocalSessionHistory(): Promise; clearAllHistoryEntries(): Promise; @@ -939,7 +939,7 @@ export interface IChatService { readonly onDidPerformUserAction: Event; notifyUserAction(event: IChatUserActionEvent): void; - readonly onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }>; + readonly onDidDisposeSession: Event<{ readonly sessionResource: URI; readonly reason: 'cleared' }>; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; @@ -951,10 +951,9 @@ export interface IChatService { } export interface IChatSessionContext { - chatSessionType: string; - chatSessionId: string; - chatSessionResource: URI; - isUntitled: boolean; + readonly chatSessionType: string; + readonly chatSessionResource: URI; + readonly isUntitled: boolean; } export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index cc9ca0e21c5..628d16d5b5a 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -70,12 +70,74 @@ class CancellableRequest implements IDisposable { } } +class ChatModelStore { + private readonly _models = new ObservableMap(); + + public get observable() { + return this._models.observable; + } + + public values(): Iterable { + return this._models.values(); + } + + public get(uri: URI): ChatModel | undefined { + return this._models.get(this.toKey(uri)); + } + + public has(uri: URI): boolean { + return this._models.has(this.toKey(uri)); + } + + public set(uri: URI, value: ChatModel): void { + this._models.set(this.toKey(uri), value); + } + + public delete(uri: URI): boolean { + return this._models.delete(this.toKey(uri)); + } + + private toKey(uri: URI): string { + return uri.toString(); + } +} + +class DisposableResourceMap extends Disposable { + + private readonly _map = this._register(new DisposableMap()); + + get(sessionResource: URI) { + return this._map.get(this.toKey(sessionResource)); + } + + set(sessionResource: URI, value: T) { + this._map.set(this.toKey(sessionResource), value); + } + + has(sessionResource: URI) { + return this._map.has(this.toKey(sessionResource)); + } + + deleteAndLeak(sessionResource: URI) { + return this._map.deleteAndLeak(this.toKey(sessionResource)); + } + + deleteAndDispose(sessionResource: URI) { + this._map.deleteAndDispose(this.toKey(sessionResource)); + } + + private toKey(uri: URI): string { + return uri.toString(); + } +} + + export class ChatService extends Disposable implements IChatService { declare _serviceBrand: undefined; - private readonly _sessionModels = new ObservableMap(); + private readonly _sessionModels = new ChatModelStore(); private readonly _contentProviderSessionModels = new ResourceMap<{ readonly model: IChatModel; readonly disposables: DisposableStore }>(); - private readonly _pendingRequests = this._register(new DisposableMap()); + private readonly _pendingRequests = this._register(new DisposableResourceMap()); private _persistedSessions: ISerializableChatsData; private _transferredSessionData: IChatTransferredSessionData | undefined; @@ -89,10 +151,10 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; reason: 'cleared' }>()); + private readonly _onDidDisposeSession = this._register(new Emitter<{ readonly sessionResource: URI; reason: 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; - private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap()); + private readonly _sessionFollowupCancelTokens = this._register(new DisposableResourceMap()); private readonly _chatServiceTelemetry: ChatServiceTelemetry; private readonly _chatSessionStore: ChatSessionStore; @@ -173,8 +235,12 @@ export class ChatService extends Disposable implements IChatService { private saveState(): void { const liveChats = Array.from(this._sessionModels.values()) - .filter(session => - !session.inputType && (session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline)); + .filter(session => { + if (!LocalChatSessionUri.parseLocalSessionId(session.sessionResource)) { + return false; + } + return session.initialLocation === ChatAgentLocation.Chat || session.initialLocation === ChatAgentLocation.EditorInline; + }); this._chatSessionStore.storeSessions(liveChats); } @@ -183,7 +249,7 @@ export class ChatService extends Disposable implements IChatService { this._chatServiceTelemetry.notifyUserAction(action); this._onDidPerformUserAction.fire(action); if (action.action.kind === 'chatEditingSessionAction') { - const model = this._sessionModels.get(action.sessionId); + const model = this._sessionModels.get(action.sessionResource); if (model) { model.notifyEditingAction(action.action); } @@ -192,7 +258,7 @@ export class ChatService extends Disposable implements IChatService { async setChatSessionTitle(sessionResource: URI, title: string): Promise { const sessionId = this.toLocalSessionId(sessionResource); - const model = this._sessionModels.get(sessionId); + const model = this._sessionModels.get(sessionResource); if (model) { model.setCustomTitle(title); } @@ -301,7 +367,7 @@ export class ChatService extends Disposable implements IChatService { */ async getLocalSessionHistory(): Promise { const liveSessionItems = Array.from(this._sessionModels.values()) - .filter(session => !session.isImported && !session.inputType) + .filter(session => !session.isImported && LocalChatSessionUri.parseLocalSessionId(session.sessionResource)) .map((session): IChatDetail => { const title = session.title || localize('newChat', "New Chat"); return { @@ -315,12 +381,15 @@ export class ChatService extends Disposable implements IChatService { const index = await this._chatSessionStore.getIndex(); const entries = Object.values(index) - .filter(entry => !this._sessionModels.has(entry.sessionId) && !entry.isImported && !entry.isEmpty) - .map((entry): IChatDetail => ({ - ...entry, - sessionResource: LocalChatSessionUri.forSession(entry.sessionId), - isActive: this._sessionModels.has(entry.sessionId), - })); + .filter(entry => !this._sessionModels.has(LocalChatSessionUri.forSession(entry.sessionId)) && !entry.isImported && !entry.isEmpty) + .map((entry): IChatDetail => { + const sessionResource = LocalChatSessionUri.forSession(entry.sessionId); + return ({ + ...entry, + sessionResource, + isActive: this._sessionModels.has(sessionResource), + }); + }); return [...liveSessionItems, ...entries]; } @@ -337,13 +406,13 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(undefined, location, isGlobalEditingSession, token, options); } - private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, options?: { sessionResource?: URI; canUseTools?: boolean; inputType?: string }): ChatModel { - const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, canUseTools: options?.canUseTools ?? true, inputType: options?.inputType }); + private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, options?: { sessionResource?: URI; canUseTools?: boolean }): ChatModel { + const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, canUseTools: options?.canUseTools ?? true }); if (location === ChatAgentLocation.Chat) { model.startEditingSession(isGlobalEditingSession); } - this._sessionModels.set(model.sessionId, model); + this._sessionModels.set(model.sessionResource, model); this.initializeSession(model, token); return model; } @@ -382,17 +451,22 @@ export class ChatService extends Disposable implements IChatService { } } - getSession(sessionId: string): IChatModel | undefined { - return this._sessionModels.get(sessionId); + getSession(sessionResource: URI): IChatModel | undefined { + return this._sessionModels.get(sessionResource); } - async getOrRestoreSession(sessionId: string): Promise { - this.trace('getOrRestoreSession', `sessionId: ${sessionId}`); - const model = this._sessionModels.get(sessionId); + async getOrRestoreSession(sessionResource: URI): Promise { + this.trace('getOrRestoreSession', `${sessionResource}`); + const model = this._sessionModels.get(sessionResource); if (model) { return model; } + const sessionId = LocalChatSessionUri.parse(sessionResource)?.sessionId; + if (!sessionId) { + throw new Error(`Cannot restore non-local session ${sessionResource}`); + } + let sessionData: ISerializableChatData | undefined; if (this.transferredSessionData?.sessionId === sessionId) { sessionData = revive(this._persistedSessions[sessionId]); @@ -417,7 +491,12 @@ export class ChatService extends Disposable implements IChatService { /** * This is really just for migrating data from the edit session location to the panel. */ - isPersistedSessionEmpty(sessionId: string): boolean { + isPersistedSessionEmpty(sessionResource: URI): boolean { + const sessionId = LocalChatSessionUri.parse(sessionResource)?.sessionId; + if (!sessionId) { + throw new Error(`Cannot restore non-local session ${sessionResource}`); + } + const session = this._persistedSessions[sessionId]; if (session) { return session.requests.length === 0; @@ -426,7 +505,12 @@ export class ChatService extends Disposable implements IChatService { return this._chatSessionStore.isSessionEmpty(sessionId); } - getPersistedSessionTitle(sessionId: string): string | undefined { + getPersistedSessionTitle(sessionResource: URI): string | undefined { + const sessionId = LocalChatSessionUri.parse(sessionResource)?.sessionId; + if (!sessionId) { + throw new Error(`Cannot restore non-local session ${sessionResource}`); + } + // First check the memory cache (_persistedSessions) const session = this._persistedSessions[sessionId]; if (session) { @@ -459,12 +543,7 @@ export class ChatService extends Disposable implements IChatService { // TODO: Move this into a new ChatModelService if (chatSessionResource.scheme === Schemas.vscodeLocalChatSession) { - const parsed = LocalChatSessionUri.parse(chatSessionResource); - if (!parsed) { - throw new ErrorNoTelemetry('Invalid local chat session URI'); - } - - return this.getOrRestoreSession(parsed.sessionId); + return this.getOrRestoreSession(chatSessionResource); } const existing = this._contentProviderSessionModels.get(chatSessionResource); @@ -476,11 +555,10 @@ export class ChatService extends Disposable implements IChatService { const chatSessionType = chatSessionResource.scheme; // Contributed sessions do not use UI tools - const model = this._startSession(undefined, location, true, CancellationToken.None, { sessionResource: chatSessionResource, canUseTools: false, inputType: chatSessionType }); + const model = this._startSession(undefined, location, true, CancellationToken.None, { sessionResource: chatSessionResource, canUseTools: false }); model.setContributedChatSession({ - chatSessionType: chatSessionType, - chatSessionId: chatSessionResource.toString(), chatSessionResource, + chatSessionType, isUntitled: chatSessionResource.path.startsWith('/untitled-') //TODO(jospicer) }); @@ -536,7 +614,7 @@ export class ChatService extends Disposable implements IChatService { if (providedSession.progressObs && lastRequest && providedSession.interruptActiveResponseCallback) { const initialCancellationRequest = this.instantiationService.createInstance(CancellableRequest, new CancellationTokenSource(), undefined); - this._pendingRequests.set(model.sessionId, initialCancellationRequest); + this._pendingRequests.set(model.sessionResource, initialCancellationRequest); const cancellationListener = new MutableDisposable(); const createCancellationListener = (token: CancellationToken) => { @@ -545,7 +623,7 @@ export class ChatService extends Disposable implements IChatService { if (!userConfirmedInterruption) { // User cancelled the interruption const newCancellationRequest = this.instantiationService.createInstance(CancellableRequest, new CancellationTokenSource(), undefined); - this._pendingRequests.set(model.sessionId, newCancellationRequest); + this._pendingRequests.set(model.sessionResource, newCancellationRequest); cancellationListener.value = createCancellationListener(newCancellationRequest.cancellationTokenSource.token); } }); @@ -584,8 +662,8 @@ export class ChatService extends Disposable implements IChatService { return model; } - getChatSessionFromInternalId(modelSessionId: string): IChatSessionContext | undefined { - const model = this._sessionModels.get(modelSessionId); + getChatSessionFromInternalUri(sessionResource: URI): IChatSessionContext | undefined { + const model = this._sessionModels.get(sessionResource); if (!model) { return; } @@ -594,14 +672,14 @@ export class ChatService extends Disposable implements IChatService { } async resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions): Promise { - const model = this._sessionModels.get(request.session.sessionId); + const model = this._sessionModels.get(request.session.sessionResource); if (!model && model !== request.session) { - throw new Error(`Unknown session: ${request.session.sessionId}`); + throw new Error(`Unknown session: ${request.session.sessionResource}`); } - const cts = this._pendingRequests.get(request.session.sessionId); + const cts = this._pendingRequests.get(request.session.sessionResource); if (cts) { - this.trace('resendRequest', `Session ${request.session.sessionId} already has a pending request, cancelling...`); + this.trace('resendRequest', `Session ${request.session.sessionResource} already has a pending request, cancelling...`); cts.cancel(); } @@ -617,11 +695,11 @@ export class ChatService extends Disposable implements IChatService { locationData: request.locationData, attachedContext: request.attachedContext, }; - await this._sendRequestAsync(model, model.sessionId, request.message, attempt, enableCommandDetection, defaultAgent, location, resendOptions).responseCompletePromise; + await this._sendRequestAsync(model, model.sessionResource, request.message, attempt, enableCommandDetection, defaultAgent, location, resendOptions).responseCompletePromise; } - async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise { - this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); + async sendRequest(sessionResource: URI, request: string, options?: IChatSendRequestOptions): Promise { + this.trace('sendRequest', `sessionResource: ${sessionResource.toString()}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); if (!request.trim() && !options?.slashCommand && !options?.agentId && !options?.agentIdSilent) { @@ -629,13 +707,13 @@ export class ChatService extends Disposable implements IChatService { return; } - const model = this._sessionModels.get(sessionId); + const model = this._sessionModels.get(sessionResource); if (!model) { - throw new Error(`Unknown session: ${sessionId}`); + throw new Error(`Unknown session: ${sessionResource}`); } - if (this._pendingRequests.has(sessionId)) { - this.trace('sendRequest', `Session ${sessionId} already has a pending request`); + if (this._pendingRequests.has(sessionResource)) { + this.trace('sendRequest', `Session ${sessionResource} already has a pending request`); return; } @@ -646,7 +724,7 @@ export class ChatService extends Disposable implements IChatService { if (request.shouldBeRemovedOnSend.afterUndoStop) { request.response?.finalizeUndoState(); } else { - await this.removeRequest(sessionId, request.id); + await this.removeRequest(sessionResource, request.id); } } } @@ -655,20 +733,20 @@ export class ChatService extends Disposable implements IChatService { const attempt = options?.attempt ?? 0; const defaultAgent = this.chatAgentService.getDefaultAgent(location, options?.modeInfo?.kind)!; - const parsedRequest = this.parseChatRequest(sessionId, request, location, options); + const parsedRequest = this.parseChatRequest(sessionResource, request, location, options); const silentAgent = options?.agentIdSilent ? this.chatAgentService.getAgent(options.agentIdSilent) : undefined; const agent = silentAgent ?? parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart)?.agent ?? defaultAgent; const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); // This method is only returning whether the request was accepted - don't block on the actual request return { - ...this._sendRequestAsync(model, sessionId, parsedRequest, attempt, !options?.noCommandDetection, silentAgent ?? defaultAgent, location, options), + ...this._sendRequestAsync(model, sessionResource, parsedRequest, attempt, !options?.noCommandDetection, silentAgent ?? defaultAgent, location, options), agent, slashCommand: agentSlashCommandPart?.command, }; } - private parseChatRequest(sessionId: string, request: string, location: ChatAgentLocation, options: IChatSendRequestOptions | undefined): IParsedChatRequest { + private parseChatRequest(sessionResource: URI, request: string, location: ChatAgentLocation, options: IChatSendRequestOptions | undefined): IParsedChatRequest { let parserContext = options?.parserContext; if (options?.agentId) { const agent = this.chatAgentService.getAgent(options.agentId); @@ -680,20 +758,20 @@ export class ChatService extends Disposable implements IChatService { request = `${chatAgentLeader}${agent.name}${commandPart} ${request}`; } - const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, request, location, parserContext); + const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionResource, request, location, parserContext); return parsedRequest; } - private refreshFollowupsCancellationToken(sessionId: string): CancellationToken { - this._sessionFollowupCancelTokens.get(sessionId)?.cancel(); + private refreshFollowupsCancellationToken(sessionResource: URI): CancellationToken { + this._sessionFollowupCancelTokens.get(sessionResource)?.cancel(); const newTokenSource = new CancellationTokenSource(); - this._sessionFollowupCancelTokens.set(sessionId, newTokenSource); + this._sessionFollowupCancelTokens.set(sessionResource, newTokenSource); return newTokenSource.token; } - private _sendRequestAsync(model: ChatModel, sessionId: string, parsedRequest: IParsedChatRequest, attempt: number, enableCommandDetection: boolean, defaultAgent: IChatAgentData, location: ChatAgentLocation, options?: IChatSendRequestOptions): IChatSendRequestResponseState { - const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); + private _sendRequestAsync(model: ChatModel, sessionResource: URI, parsedRequest: IParsedChatRequest, attempt: number, enableCommandDetection: boolean, defaultAgent: IChatAgentData, location: ChatAgentLocation, options?: IChatSendRequestOptions): IChatSendRequestResponseState { + const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionResource); let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); @@ -809,7 +887,7 @@ export class ChatService extends Disposable implements IChatService { })); return { - sessionId, + sessionId: model.sessionId, requestId: request.id, agentId: agent.id, message, @@ -864,7 +942,7 @@ export class ChatService extends Disposable implements IChatService { // Recompute history in case the agent or command changed const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); const requestProps = prepareChatAgentRequest(agent, command, enableCommandDetection, request /* Reuse the request object if we already created it for participant detection */, !!detectedAgent); - const pendingRequest = this._pendingRequests.get(sessionId); + const pendingRequest = this._pendingRequests.get(sessionResource); if (pendingRequest && !pendingRequest.requestId) { pendingRequest.requestId = requestProps.requestId; } @@ -987,9 +1065,9 @@ export class ChatService extends Disposable implements IChatService { }; const rawResponsePromise = sendRequestInternal(); // Note- requestId is not known at this point, assigned later - this._pendingRequests.set(model.sessionId, this.instantiationService.createInstance(CancellableRequest, source, undefined)); + this._pendingRequests.set(model.sessionResource, this.instantiationService.createInstance(CancellableRequest, source, undefined)); rawResponsePromise.finally(() => { - this._pendingRequests.deleteAndDispose(model.sessionId); + this._pendingRequests.deleteAndDispose(model.sessionResource); }); this._onDidSubmitRequest.fire({ chatSessionId: model.sessionId }); return { @@ -1050,52 +1128,52 @@ export class ChatService extends Disposable implements IChatService { return history; } - async removeRequest(sessionId: string, requestId: string): Promise { - const model = this._sessionModels.get(sessionId); + async removeRequest(sessionResource: URI, requestId: string): Promise { + const model = this._sessionModels.get(sessionResource); if (!model) { - throw new Error(`Unknown session: ${sessionId}`); + throw new Error(`Unknown session: ${sessionResource}`); } - const pendingRequest = this._pendingRequests.get(sessionId); + const pendingRequest = this._pendingRequests.get(sessionResource); if (pendingRequest?.requestId === requestId) { pendingRequest.cancel(); - this._pendingRequests.deleteAndDispose(sessionId); + this._pendingRequests.deleteAndDispose(sessionResource); } model.removeRequest(requestId); } - async adoptRequest(sessionId: string, request: IChatRequestModel) { + async adoptRequest(sessionResource: URI, request: IChatRequestModel) { if (!(request instanceof ChatRequestModel)) { throw new TypeError('Can only adopt requests of type ChatRequestModel'); } - const target = this._sessionModels.get(sessionId); + const target = this._sessionModels.get(sessionResource); if (!target) { - throw new Error(`Unknown session: ${sessionId}`); + throw new Error(`Unknown session: ${sessionResource}`); } const oldOwner = request.session; target.adoptRequest(request); if (request.response && !request.response.isComplete) { - const cts = this._pendingRequests.deleteAndLeak(oldOwner.sessionId); + const cts = this._pendingRequests.deleteAndLeak(oldOwner.sessionResource); if (cts) { cts.requestId = request.id; - this._pendingRequests.set(target.sessionId, cts); + this._pendingRequests.set(target.sessionResource, cts); } } } - async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): Promise { + async addCompleteRequest(sessionResource: URI, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): Promise { this.trace('addCompleteRequest', `message: ${message}`); - const model = this._sessionModels.get(sessionId); + const model = this._sessionModels.get(sessionResource); if (!model) { - throw new Error(`Unknown session: ${sessionId}`); + throw new Error(`Unknown session: ${sessionResource}`); } const parsedRequest = typeof message === 'string' ? - this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : + this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionResource, message) : message; const request = model.addRequest(parsedRequest, variableData || { variables: [] }, attempt ?? 0, undefined, undefined, undefined, undefined, undefined, undefined, true); if (typeof response.message === 'string') { @@ -1113,33 +1191,34 @@ export class ChatService extends Disposable implements IChatService { model.completeResponse(request); } - cancelCurrentRequestForSession(sessionId: string): void { - this.trace('cancelCurrentRequestForSession', `sessionId: ${sessionId}`); - this._pendingRequests.get(sessionId)?.cancel(); - this._pendingRequests.deleteAndDispose(sessionId); + cancelCurrentRequestForSession(sessionResource: URI): void { + this.trace('cancelCurrentRequestForSession', `session: ${sessionResource}`); + this._pendingRequests.get(sessionResource)?.cancel(); + this._pendingRequests.deleteAndDispose(sessionResource); } - async clearSession(sessionId: string): Promise { - this.trace('clearSession', `sessionId: ${sessionId}`); - const model = this._sessionModels.get(sessionId); + async clearSession(sessionResource: URI): Promise { + this.trace('clearSession', `session: ${sessionResource}`); + const model = this._sessionModels.get(sessionResource); if (!model) { - throw new Error(`Unknown session: ${sessionId}`); + throw new Error(`Unknown session: ${sessionResource}`); } - this.trace(`Model input type: ${model.inputType}`); - if (!model.inputType && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { + + const localSession = LocalChatSessionUri.parse(sessionResource); + if (localSession && (model.initialLocation === ChatAgentLocation.Chat || model.initialLocation === ChatAgentLocation.EditorInline)) { // Always preserve sessions that have custom titles, even if empty if (model.getRequests().length === 0 && !model.customTitle) { - await this._chatSessionStore.deleteSession(sessionId); + await this._chatSessionStore.deleteSession(localSession.sessionId); } else { await this._chatSessionStore.storeSessions([model]); } } - this._sessionModels.delete(sessionId); + this._sessionModels.delete(sessionResource); model.dispose(); - this._pendingRequests.get(sessionId)?.cancel(); - this._pendingRequests.deleteAndDispose(sessionId); - this._onDidDisposeSession.fire({ sessionId, reason: 'cleared' }); + this._pendingRequests.get(sessionResource)?.cancel(); + this._pendingRequests.deleteAndDispose(sessionResource); + this._onDidDisposeSession.fire({ sessionResource, reason: 'cleared' }); } public hasSessions(): boolean { diff --git a/src/vs/workbench/contrib/chat/common/chatUri.ts b/src/vs/workbench/contrib/chat/common/chatUri.ts index c9d5e4c08e1..f324137e8cd 100644 --- a/src/vs/workbench/contrib/chat/common/chatUri.ts +++ b/src/vs/workbench/contrib/chat/common/chatUri.ts @@ -31,6 +31,11 @@ export namespace LocalChatSessionUri { return URI.from({ scheme, authority: chatSessionType, path: '/' + encodedId }); } + export function parseLocalSessionId(resource: URI): string | undefined { + const parsed = parse(resource); + return parsed?.chatSessionType === localChatSessionType ? parsed.sessionId : undefined; + } + export function parse(resource: URI): ChatSessionIdentifier | undefined { if (resource.scheme !== scheme) { return undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index b85231b4e87..fed1227651f 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -46,8 +46,8 @@ export const IChatVariablesService = createDecorator('ICh export interface IChatVariablesService { _serviceBrand: undefined; - getDynamicVariables(sessionId: string): ReadonlyArray; - getSelectedToolAndToolSets(sessionId: string): IToolAndToolSetEnablementMap; + getDynamicVariables(sessionResource: URI): ReadonlyArray; + getSelectedToolAndToolSets(sessionResource: URI): IToolAndToolSetEnablementMap; } export interface IDynamicVariable { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 9e4d1e23824..5ccd93bad24 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -78,7 +78,9 @@ export interface IChatViewModel { export interface IChatRequestViewModel { readonly id: string; + /** @deprecated */ readonly sessionId: string; + readonly sessionResource: URI; /** This ID updates every time the underlying data changes */ readonly dataId: string; readonly username: string; @@ -194,7 +196,9 @@ export interface IChatResponseViewModel { readonly model: IChatResponseModel; readonly id: string; readonly session: IChatViewModel; + /** @deprecated */ readonly sessionId: string; + readonly sessionResource: URI; /** This ID updates every time the underlying data changes */ readonly dataId: string; /** The ID of the associated IChatRequestViewModel */ @@ -392,6 +396,10 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.session.sessionId; } + get sessionResource() { + return this._model.session.sessionResource; + } + get username() { return this._model.username; } @@ -483,6 +491,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.session.sessionId; } + get sessionResource(): URI { + return this._model.session.sessionResource; + } + get username() { if (this.agent) { const isAllowed = this.chatAgentNameService.getAgentNameRestriction(this.agent); diff --git a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts index 3b94293123a..8e1e3669b4c 100644 --- a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts @@ -14,6 +14,7 @@ import { ICodeMapperService } from '../../common/chatCodeMapperService.js'; import { ChatModel } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolInvocationPresentation, ToolProgress } from '../../common/languageModelToolsService.js'; +import { LocalChatSessionUri } from '../chatUri.js'; export const ExtensionEditToolId = 'vscode_editFile'; export const InternalEditToolId = 'vscode_editFile_internal'; @@ -47,7 +48,7 @@ export class EditTool implements IToolImpl { const fileUri = URI.revive(parameters.uri); const uri = CellUri.parse(fileUri)?.notebook || fileUri; - const model = this.chatService.getSession(invocation.context?.sessionId) as ChatModel; + const model = this.chatService.getSession(LocalChatSessionUri.forSession(invocation.context?.sessionId)) as ChatModel; const request = model.getRequests().at(-1)!; model.acceptResponseProgress(request, { diff --git a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts index 6b57f0181e7..e581d217a4a 100644 --- a/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -27,6 +27,7 @@ import { ChatConfiguration } from '../../common/constants.js'; import { isToolResultInputOutputDetails, IToolData, IToolImpl, IToolInvocation, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; import { MockChatService } from '../common/mockChatService.js'; import { ChatToolInvocation } from '../../common/chatProgressTypes/chatToolInvocation.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; // --- Test helpers to reduce repetition and improve readability --- @@ -80,6 +81,7 @@ function stubGetSession(chatService: MockChatService, sessionId: string, options const capture = options?.capture; const fakeModel = { sessionId, + sessionResource: LocalChatSessionUri.forSession(sessionId), getRequests: () => [{ id: requestId, modelId: 'test-model' }], acceptResponseProgress: (_req: any, progress: any) => { if (capture) { capture.invocation = progress; } }, } as IChatModel; diff --git a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts index 97e619df5a9..518749d6e57 100644 --- a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts +++ b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts @@ -26,6 +26,10 @@ export class MockChatWidgetService implements IChatWidgetService { return undefined; } + getWidgetBySessionResource(sessionResource: URI): IChatWidget | undefined { + return undefined; + } + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray { return []; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index d77620366fb..86c9dbccfab 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -17,6 +17,7 @@ import { ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService import { ChatRequestParser } from '../../common/chatRequestParser.js'; import { IChatService } from '../../common/chatService.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; +import { LocalChatSessionUri } from '../../common/chatUri.js'; import { IChatVariablesService } from '../../common/chatVariables.js'; import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js'; import { IToolData, ToolDataSource, ToolSet } from '../../common/languageModelToolsService.js'; @@ -25,6 +26,8 @@ import { MockChatService } from './mockChatService.js'; import { MockChatVariablesService } from './mockChatVariables.js'; import { MockPromptsService } from './mockPromptsService.js'; +const testSessionUri = LocalChatSessionUri.forSession('test-session'); + suite('ChatRequestParser', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -48,21 +51,21 @@ suite('ChatRequestParser', () => { test('plain text', async () => { parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', 'test'); + const result = parser.parseChatRequest(testSessionUri, 'test'); await assertSnapshot(result); }); test('plain text with newlines', async () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'line 1\nline 2\r\nline 3'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); test('slash in text', async () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'can we add a new file for an Express router to handle the / route'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -74,7 +77,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix this'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -86,7 +89,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/explain this'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -98,7 +101,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix /fix'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -110,7 +113,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'Hello /fix'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -122,7 +125,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = ' /fix'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -144,7 +147,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = ' /prompt'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -166,7 +169,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = 'handle the / route and the request of /search-option'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -188,7 +191,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/ route and the request of /search-option'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -210,7 +213,7 @@ suite('ChatRequestParser', () => { parser = instantiationService.createInstance(ChatRequestParser); const text = '/001-sample this is a test'; - const result = parser.parseChatRequest('1', text); + const result = parser.parseChatRequest(testSessionUri, text); await assertSnapshot(result); }); @@ -220,7 +223,7 @@ suite('ChatRequestParser', () => { // parser = instantiationService.createInstance(ChatRequestParser); // const text = 'What does #selection mean?'; - // const result = parser.parseChatRequest('1', text); + // const result = parser.parseChatRequest(testSessionUri, text); // await assertSnapshot(result); // }); @@ -230,7 +233,7 @@ suite('ChatRequestParser', () => { // parser = instantiationService.createInstance(ChatRequestParser); // const text = 'What is #selection?'; - // const result = parser.parseChatRequest('1', text); + // const result = parser.parseChatRequest(testSessionUri, text); // await assertSnapshot(result); // }); @@ -239,7 +242,7 @@ suite('ChatRequestParser', () => { // parser = instantiationService.createInstance(ChatRequestParser); // const text = 'What does #selection mean?'; - // const result = parser.parseChatRequest('1', text); + // const result = parser.parseChatRequest(testSessionUri, text); // await assertSnapshot(result); // }); @@ -254,7 +257,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent Please do /subCommand thanks'); + const result = parser.parseChatRequest(testSessionUri, '@agent Please do /subCommand thanks'); await assertSnapshot(result); }); @@ -265,7 +268,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent /subCommand Please do thanks'); + const result = parser.parseChatRequest(testSessionUri, '@agent /subCommand Please do thanks'); await assertSnapshot(result); }); @@ -276,7 +279,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent hello', undefined, { mode: ChatModeKind.Edit }); + const result = parser.parseChatRequest(testSessionUri, '@agent hello', undefined, { mode: ChatModeKind.Edit }); await assertSnapshot(result); }); @@ -287,7 +290,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent? Are you there'); + const result = parser.parseChatRequest(testSessionUri, '@agent? Are you there'); await assertSnapshot(result); }); @@ -298,7 +301,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', ' \r\n\t @agent \r\n\t /subCommand Thanks'); + const result = parser.parseChatRequest(testSessionUri, ' \r\n\t @agent \r\n\t /subCommand Thanks'); await assertSnapshot(result); }); @@ -309,7 +312,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', ' \n@agent\n/subCommand Thanks'); + const result = parser.parseChatRequest(testSessionUri, ' \n@agent\n/subCommand Thanks'); await assertSnapshot(result); }); @@ -320,7 +323,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', 'Hello Mr. @agent'); + const result = parser.parseChatRequest(testSessionUri, 'Hello Mr. @agent'); await assertSnapshot(result); }); @@ -330,13 +333,13 @@ suite('ChatRequestParser', () => { // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); - variableService.setSelectedToolAndToolSets('1', new Map([ + variableService.setSelectedToolAndToolSets(testSessionUri, new Map([ [{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true], [{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true] ] satisfies [IToolData | ToolSet, boolean][])); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); + const result = parser.parseChatRequest(testSessionUri, '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); await assertSnapshot(result); }); @@ -346,13 +349,13 @@ suite('ChatRequestParser', () => { // eslint-disable-next-line local/code-no-any-casts instantiationService.stub(IChatAgentService, agentsService as any); - variableService.setSelectedToolAndToolSets('1', new Map([ + variableService.setSelectedToolAndToolSets(testSessionUri, new Map([ [{ id: 'get_selection', toolReferenceName: 'selection', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true], [{ id: 'get_debugConsole', toolReferenceName: 'debugConsole', canBeReferencedInPrompt: true, displayName: '', modelDescription: '', source: ToolDataSource.Internal }, true] ] satisfies [IToolData | ToolSet, boolean][])); parser = instantiationService.createInstance(ChatRequestParser); - const result = parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); + const result = parser.parseChatRequest(testSessionUri, '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); await assertSnapshot(result); }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 63d98466c50..04862001c12 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -179,8 +179,8 @@ suite('ChatService', () => { session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }, 0); // Clear sessions to trigger persistence to file service - await testService.clearSession(session1.sessionId); - await testService.clearSession(session2.sessionId); + await testService.clearSession(session1.sessionResource); + await testService.clearSession(session2.sessionResource); // Verify that sessions were written to the file service assert.strictEqual(testFileService.writeOperations.length, 2, 'Should have written 2 sessions to file service'); @@ -197,8 +197,8 @@ suite('ChatService', () => { const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); // Retrieve sessions and verify they're loaded from file service - const retrieved1 = testDisposables.add((await testService2.getOrRestoreSession(session1.sessionId))!); - const retrieved2 = testDisposables.add((await testService2.getOrRestoreSession(session2.sessionId))!); + const retrieved1 = testDisposables.add((await testService2.getOrRestoreSession(session1.sessionResource))!); + const retrieved2 = testDisposables.add((await testService2.getOrRestoreSession(session2.sessionResource))!); assert.ok(retrieved1, 'Should retrieve session 1'); assert.ok(retrieved2, 'Should retrieve session 2'); @@ -212,7 +212,7 @@ suite('ChatService', () => { const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); - await testService.addCompleteRequest(model.sessionId, 'test request', undefined, 0, { message: 'test response' }); + await testService.addCompleteRequest(model.sessionResource, 'test request', undefined, 0, { message: 'test response' }); assert.strictEqual(model.getRequests().length, 1); assert.ok(model.getRequests()[0].response); assert.strictEqual(model.getRequests()[0].response?.response.toString(), 'test response'); @@ -222,7 +222,7 @@ suite('ChatService', () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); - const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); + const response = await testService.sendRequest(model.sessionResource, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -247,21 +247,21 @@ suite('ChatService', () => { const model = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); // Send a request to default agent - const response = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'defaultAgent' }); + const response = await testService.sendRequest(model.sessionResource, `test request`, { agentId: 'defaultAgent' }); assert(response); await response.responseCompletePromise; assert.strictEqual(model.getRequests().length, 1); assert.strictEqual(model.getRequests()[0].response?.result?.metadata?.historyLength, 0); // Send a request to agent2- it can't see the default agent's message - const response2 = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'agent2' }); + const response2 = await testService.sendRequest(model.sessionResource, `test request`, { agentId: 'agent2' }); assert(response2); await response2.responseCompletePromise; assert.strictEqual(model.getRequests().length, 2); assert.strictEqual(model.getRequests()[1].response?.result?.metadata?.historyLength, 0); // Send a request to defaultAgent - the default agent can see agent2's message - const response3 = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'defaultAgent' }); + const response3 = await testService.sendRequest(model.sessionResource, `test request`, { agentId: 'defaultAgent' }); assert(response3); await response3.responseCompletePromise; assert.strictEqual(model.getRequests().length, 3); @@ -278,12 +278,12 @@ suite('ChatService', () => { await assertSnapshot(toSnapshotExportData(model)); - const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); + const response = await testService.sendRequest(model.sessionResource, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; assert.strictEqual(model.getRequests().length, 1); - const response2 = await testService.sendRequest(model.sessionId, `test request 2`); + const response2 = await testService.sendRequest(model.sessionResource, `test request 2`); assert(response2); await response2.responseCompletePromise; assert.strictEqual(model.getRequests().length, 2); @@ -302,7 +302,7 @@ suite('ChatService', () => { const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); - const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); + const response = await testService.sendRequest(chatModel1.sessionResource, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -331,7 +331,7 @@ suite('ChatService', () => { const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Chat, CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); - const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); + const response = await testService.sendRequest(chatModel1.sessionResource, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index 6ae9cb3a5b6..81e52e3ca5d 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -5,6 +5,7 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; +import { ResourceMap } from '../../../../../base/common/map.js'; import { observableValue } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ChatModel, IChatModel, IChatRequestModel, IChatRequestVariableData, ISerializableChatData } from '../../common/chatModel.js'; @@ -13,9 +14,6 @@ import { IChatCompleteResponse, IChatDetail, IChatProviderInfo, IChatSendRequest import { ChatAgentLocation } from '../../common/constants.js'; export class MockChatService implements IChatService { - getChatSessionFromInternalId(modelSessionId: string): IChatSessionContext | undefined { - throw new Error('Method not implemented.'); - } requestInProgressObs = observableValue('name', false); edits2Enabled: boolean = false; _serviceBrand: undefined; @@ -23,7 +21,7 @@ export class MockChatService implements IChatService { transferredSessionData: IChatTransferredSessionData | undefined; readonly onDidSubmitRequest: Event<{ chatSessionId: string }> = Event.None; - private sessions = new Map(); + private sessions = new ResourceMap(); isEnabled(location: ChatAgentLocation): boolean { throw new Error('Method not implemented.'); @@ -38,16 +36,16 @@ export class MockChatService implements IChatService { throw new Error('Method not implemented.'); } addSession(session: IChatModel): void { - this.sessions.set(session.sessionId, session); + this.sessions.set(session.sessionResource, session); } - getSession(sessionId: string): IChatModel | undefined { + getSession(sessionResource: URI): IChatModel | undefined { // eslint-disable-next-line local/code-no-dangerous-type-assertions - return this.sessions.get(sessionId) ?? {} as IChatModel; + return this.sessions.get(sessionResource) ?? {} as IChatModel; } - async getOrRestoreSession(sessionId: string): Promise { + async getOrRestoreSession(sessionResource: URI): Promise { throw new Error('Method not implemented.'); } - getPersistedSessionTitle(sessionId: string): string | undefined { + getPersistedSessionTitle(sessionResource: URI): string | undefined { throw new Error('Method not implemented.'); } loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { @@ -59,25 +57,25 @@ export class MockChatService implements IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string): Promise { + sendRequest(sessionResource: URI, message: string): Promise { throw new Error('Method not implemented.'); } resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions | undefined): Promise { throw new Error('Method not implemented.'); } - adoptRequest(sessionId: string, request: IChatRequestModel): Promise { + adoptRequest(sessionResource: URI, request: IChatRequestModel): Promise { throw new Error('Method not implemented.'); } - removeRequest(sessionid: string, requestId: string): Promise { + removeRequest(sessionResource: URI, requestId: string): Promise { throw new Error('Method not implemented.'); } - cancelCurrentRequestForSession(sessionId: string): void { + cancelCurrentRequestForSession(sessionResource: URI): void { throw new Error('Method not implemented.'); } - clearSession(sessionId: string): Promise { + clearSession(sessionResource: URI): Promise { throw new Error('Method not implemented.'); } - addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void { + addCompleteRequest(sessionResource: URI, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void { throw new Error('Method not implemented.'); } async getLocalSessionHistory(): Promise { @@ -94,7 +92,7 @@ export class MockChatService implements IChatService { notifyUserAction(event: IChatUserActionEvent): void { throw new Error('Method not implemented.'); } - readonly onDidDisposeSession: Event<{ sessionId: string; reason: 'cleared' }> = undefined!; + readonly onDidDisposeSession: Event<{ sessionResource: URI; reason: 'cleared' }> = undefined!; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { throw new Error('Method not implemented.'); @@ -116,11 +114,15 @@ export class MockChatService implements IChatService { throw new Error('Method not implemented.'); } - isPersistedSessionEmpty(sessionId: string): boolean { + isPersistedSessionEmpty(sessionResource: URI): boolean { throw new Error('Method not implemented.'); } activateDefaultAgent(location: ChatAgentLocation): Promise { throw new Error('Method not implemented.'); } + + getChatSessionFromInternalUri(sessionResource: URI): IChatSessionContext | undefined { + throw new Error('Method not implemented.'); + } } diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index c65e3177dc9..b075db33b8c 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -3,28 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ResourceMap } from '../../../../../base/common/map.js'; +import { URI } from '../../../../../base/common/uri.js'; import { IChatVariablesService, IDynamicVariable } from '../../common/chatVariables.js'; import { IToolAndToolSetEnablementMap } from '../../common/languageModelToolsService.js'; export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; - private _dynamicVariables = new Map(); - private _selectedToolAndToolSets = new Map(); + private _dynamicVariables = new ResourceMap(); + private _selectedToolAndToolSets = new ResourceMap(); - getDynamicVariables(sessionId: string): readonly IDynamicVariable[] { - return this._dynamicVariables.get(sessionId) ?? []; + getDynamicVariables(sessionResource: URI): readonly IDynamicVariable[] { + return this._dynamicVariables.get(sessionResource) ?? []; } - getSelectedToolAndToolSets(sessionId: string): IToolAndToolSetEnablementMap { - return this._selectedToolAndToolSets.get(sessionId) ?? new Map(); + getSelectedToolAndToolSets(sessionResource: URI): IToolAndToolSetEnablementMap { + return this._selectedToolAndToolSets.get(sessionResource) ?? new Map(); } - setDynamicVariables(sessionId: string, variables: readonly IDynamicVariable[]): void { - this._dynamicVariables.set(sessionId, variables); + setDynamicVariables(sessionResource: URI, variables: readonly IDynamicVariable[]): void { + this._dynamicVariables.set(sessionResource, variables); } - setSelectedToolAndToolSets(sessionId: string, tools: IToolAndToolSetEnablementMap): void { - this._selectedToolAndToolSets.set(sessionId, tools); + setSelectedToolAndToolSets(sessionResource: URI, tools: IToolAndToolSetEnablementMap): void { + this._selectedToolAndToolSets.set(sessionResource, tools); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index d9976a73dbd..1e7fef7aa32 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -764,7 +764,7 @@ export class CancelRequestAction extends AbstractInline2ChatAction { if (viewModel) { ctrl.toggleWidgetUntilNextRequest(); ctrl.markActiveController(); - chatService.cancelCurrentRequestForSession(viewModel.sessionId); + chatService.cancelCurrentRequestForSession(viewModel.sessionResource); } } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4268eda2d0c..cbe8705b987 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -688,7 +688,7 @@ export class InlineChatController1 implements IEditorContribution { let next: State.WAIT_FOR_INPUT | State.SHOW_REQUEST | State.CANCEL | State.PAUSE | State.ACCEPT = State.WAIT_FOR_INPUT; store.add(Event.once(this._messages.event)(message => { this._log('state=_makeRequest) message received', message); - this._chatService.cancelCurrentRequestForSession(chatModel.sessionId); + this._chatService.cancelCurrentRequestForSession(chatModel.sessionResource); if (message & Message.CANCEL_SESSION) { next = State.CANCEL; } else if (message & Message.PAUSE_SESSION) { @@ -755,7 +755,7 @@ export class InlineChatController1 implements IEditorContribution { // cancel the request when the user types store.add(this._ui.value.widget.chatWidget.inputEditor.onDidChangeModelContent(() => { - this._chatService.cancelCurrentRequestForSession(chatModel.sessionId); + this._chatService.cancelCurrentRequestForSession(chatModel.sessionResource); })); let lastLength = 0; @@ -1141,7 +1141,7 @@ export class InlineChatController1 implements IEditorContribution { const response = this._session?.chatModel.getRequests().at(-1)?.response; if (response) { this._chatService.notifyUserAction({ - sessionId: response.session.sessionId, + sessionResource: response.session.sessionResource, requestId: response.requestId, agentId: response.agent?.id, command: response.slashCommand?.name, @@ -1176,7 +1176,7 @@ export class InlineChatController1 implements IEditorContribution { const response = this._session?.chatModel.lastRequest?.response; if (response) { this._chatService.notifyUserAction({ - sessionId: response.session.sessionId, + sessionResource: response.session.sessionResource, requestId: response.requestId, agentId: response.agent?.id, command: response.slashCommand?.name, @@ -1196,7 +1196,7 @@ export class InlineChatController1 implements IEditorContribution { const response = this._session?.chatModel.lastRequest?.response; if (response) { this._chatService.notifyUserAction({ - sessionId: response.session.sessionId, + sessionResource: response.session.sessionResource, requestId: response.requestId, agentId: response.agent?.id, command: response.slashCommand?.name, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index bd0cc6ed0ef..2f99e137c69 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -83,7 +83,7 @@ export async function moveToPanelChat(accessor: ServicesAccessor, model: IChatMo if (widget && widget.viewModel && model) { let lastRequest: IChatRequestModel | undefined; for (const request of model.getRequests().slice()) { - await chatService.adoptRequest(widget.viewModel.model.sessionId, request); + await chatService.adoptRequest(widget.viewModel.model.sessionResource, request); lastRequest = request; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 0c5d8c5b4e5..abdd206223b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -130,7 +130,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const doesOtherSessionUseChatModel = [...this._sessions.values()].some(data => data.session !== session && data.session.chatModel === chatModel); if (!doesOtherSessionUseChatModel) { - this._chatService.clearSession(chatModel.sessionId); + this._chatService.clearSession(chatModel.sessionResource); chatModel.dispose(); } })); @@ -353,7 +353,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const store = new DisposableStore(); store.add(toDisposable(() => { - this._chatService.cancelCurrentRequestForSession(chatModel.sessionId); + this._chatService.cancelCurrentRequestForSession(chatModel.sessionResource); editingSession.reject(); this._sessions2.delete(uri); this._onDidChangeSessions.fire(this); @@ -545,7 +545,7 @@ export class InlineChatEscapeToolContribution extends Disposable { } else { logService.trace('InlineChatEscapeToolContribution: rephrase prompt'); - chatService.removeRequest(session.chatModel.sessionId, session.chatModel.getRequests().at(-1)!.id); + chatService.removeRequest(session.chatModel.sessionResource, session.chatModel.getRequests().at(-1)!.id); } return { content: [{ kind: 'text', value: 'Success' }] }; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 1d90a7602be..2d281f70b8f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -57,6 +57,7 @@ import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBa import { HunkInformation, Session } from './inlineChatSession.js'; import './media/inlineChat.css'; import { ChatMode } from '../../chat/common/chatModes.js'; +import { isEqual } from '../../../../base/common/resources.js'; export interface InlineChatWidgetViewState { editorViewState: ICodeEditorViewState; @@ -282,7 +283,7 @@ export class InlineChatWidget { })); this._store.add(this._chatService.onDidPerformUserAction(e => { - if (e.sessionId === this._chatWidget.viewModel?.model.sessionId && e.action.kind === 'vote') { + if (isEqual(e.sessionResource, this._chatWidget.viewModel?.model.sessionResource) && e.action.kind === 'vote') { this.updateStatus(localize('feedbackThanks', "Thank you for your feedback!"), { resetAfter: 1250 }); } })); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 9c2e82b77c2..9fd01576fff 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -900,7 +900,7 @@ suite('InlineChatController', function () { assert.strictEqual(model.getValue(), 'HelloWorld'); // first word has been streamed const p2 = ctrl.awaitStates([State.WAIT_FOR_INPUT]); - chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionId); + chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionResource); assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'HelloWorld'); // CANCEL just stops the request and progressive typing but doesn't undo @@ -914,7 +914,7 @@ suite('InlineChatController', function () { const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None); assertType(newSession); - await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.EditorInline }))?.responseCreatedPromise; + await (await chatService.sendRequest(newSession.chatModel.sessionResource, 'Existing', { location: ChatAgentLocation.EditorInline }))?.responseCreatedPromise; assert.strictEqual(newSession.chatModel.requestInProgress, true); @@ -1021,7 +1021,7 @@ suite('InlineChatController', function () { assert.strictEqual(await p, undefined); const p2 = ctrl.awaitStates([State.WAIT_FOR_INPUT]); - chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionId); + chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionResource); assert.strictEqual(await p2, undefined); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts index 622ba695d67..443a76f81e7 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts @@ -13,6 +13,7 @@ import { IQuickInputService, IQuickPick, IQuickPickItem } from '../../../../plat import { ChatElicitationRequestPart } from '../../chat/browser/chatElicitationRequestPart.js'; import { ChatModel } from '../../chat/common/chatModel.js'; import { IChatService } from '../../chat/common/chatService.js'; +import { LocalChatSessionUri } from '../../chat/common/chatUri.js'; import { IMcpElicitationService, IMcpServer, IMcpToolCallContext } from '../common/mcpTypes.js'; import { mcpServerToSourceData } from '../common/mcpTypesUtils.js'; import { MCP } from '../common/modelContextProtocol.js'; @@ -31,7 +32,7 @@ export class McpElicitationService implements IMcpElicitationService { public elicit(server: IMcpServer, context: IMcpToolCallContext | undefined, elicitation: MCP.ElicitRequest['params'], token: CancellationToken): Promise { const store = new DisposableStore(); return new Promise(resolve => { - const chatModel = context?.chatSessionId && this._chatService.getSession(context.chatSessionId); + const chatModel = context?.chatSessionId && this._chatService.getSession(LocalChatSessionUri.forSession(context.chatSessionId)); if (chatModel instanceof ChatModel) { const request = chatModel.getRequests().at(-1); if (request) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 08304afdaf5..f82ee250992 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -154,7 +154,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async function moveToPanelChat(accessor: ServicesAccessor, model: IChatModel | undefined) { - const viewsService = accessor.get(IViewsService); const chatService = accessor.get(IChatService); const layoutService = accessor.get(IWorkbenchLayoutService); @@ -163,7 +162,7 @@ async function moveToPanelChat(accessor: ServicesAccessor, model: IChatModel | u if (widget && widget.viewModel && model) { for (const request of model.getRequests().slice()) { - await chatService.adoptRequest(widget.viewModel.model.sessionId, request); + await chatService.adoptRequest(widget.viewModel.model.sessionResource, request); } widget.focusResponseItem(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b9fecd30410..eb4dabc7843 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -425,10 +425,10 @@ export class TerminalChatWidget extends Disposable { this._activeRequestCts?.cancel(); this._requestActiveContextKey.set(false); const model = this._inlineChatWidget.getChatModel(); - if (!model?.sessionId) { + if (!model?.sessionResource) { return; } - this._chatService.cancelCurrentRequestForSession(model?.sessionId); + this._chatService.cancelCurrentRequestForSession(model?.sessionResource); } async viewInChat(): Promise { @@ -469,7 +469,7 @@ export class TerminalChatWidget extends Disposable { } } - this._chatService.addCompleteRequest(widget!.viewModel!.sessionId, + this._chatService.addCompleteRequest(widget!.viewModel!.sessionResource, `@${this._terminalAgentName} ${currentRequest.message.text}`, currentRequest.variableData, currentRequest.attempt, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index c91b7f92604..d9abfc6d885 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -29,6 +29,7 @@ import { IConfigurationService } from '../../../../../../../platform/configurati import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; import { ILogService } from '../../../../../../../platform/log/common/log.js'; import { ITerminalService } from '../../../../../terminal/browser/terminal.js'; +import { LocalChatSessionUri } from '../../../../../chat/common/chatUri.js'; export interface IOutputMonitor extends Disposable { readonly pollingResult: IPollingResult & { pollDurationMs: number } | undefined; @@ -622,7 +623,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { onReject?: () => Promise | T | undefined, moreActions?: IAction[] | undefined ): { promise: Promise; part: ChatElicitationRequestPart } { - const chatModel = sessionId && this._chatService.getSession(sessionId); + const chatModel = sessionId && this._chatService.getSession(LocalChatSessionUri.forSession(sessionId)); if (!(chatModel instanceof ChatModel)) { throw new Error('No model'); } 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 233c5a4c62b..b8ecf842a00 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -46,6 +46,7 @@ import { CommandLineAutoApproveAnalyzer } from './commandLineAnalyzer/commandLin import { CommandLineFileWriteAnalyzer } from './commandLineAnalyzer/commandLineFileWriteAnalyzer.js'; import { OutputMonitor } from './monitoring/outputMonitor.js'; import { IPollingResult, OutputMonitorState } from './monitoring/types.js'; +import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js'; // #region Tool data @@ -313,7 +314,10 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // Listen for chat session disposal to clean up associated terminals this._register(this._chatService.onDidDisposeSession(e => { - this._cleanupSessionTerminals(e.sessionId); + const localSession = LocalChatSessionUri.parse(e.sessionResource); + if (localSession) { + this._cleanupSessionTerminals(localSession.sessionId); + } })); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 995f72a352a..33b26ee0813 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -32,6 +32,8 @@ import { IFileService } from '../../../../../../platform/files/common/files.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { TestIPCFileSystemProvider } from '../../../../../test/electron-browser/workbenchTestServices.js'; import { arch } from '../../../../../../base/common/process.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -53,7 +55,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { let fileService: IFileService; let storageService: IStorageService; let terminalServiceDisposeEmitter: Emitter; - let chatServiceDisposeEmitter: Emitter<{ sessionId: string; reason: 'cleared' }>; + let chatServiceDisposeEmitter: Emitter<{ sessionResource: URI; reason: 'cleared' }>; let runInTerminalTool: TestRunInTerminalTool; @@ -67,7 +69,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { setConfig(TerminalChatAgentToolsSettingId.EnableAutoApprove, true); terminalServiceDisposeEmitter = new Emitter(); - chatServiceDisposeEmitter = new Emitter<{ sessionId: string; reason: 'cleared' }>(); + chatServiceDisposeEmitter = new Emitter<{ sessionResource: URI; reason: 'cleared' }>(); instantiationService = workbenchInstantiationService({ configurationService: () => configurationService, @@ -856,7 +858,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { ok(runInTerminalTool.sessionTerminalAssociations.has(sessionId), 'Terminal association should exist before disposal'); - chatServiceDisposeEmitter.fire({ sessionId, reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResource: LocalChatSessionUri.forSession(sessionId), reason: 'cleared' }); strictEqual(terminalDisposed, true, 'Terminal should have been disposed'); ok(!runInTerminalTool.sessionTerminalAssociations.has(sessionId), 'Terminal association should be removed after disposal'); @@ -893,7 +895,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { ok(runInTerminalTool.sessionTerminalAssociations.has(sessionId1), 'Session 1 terminal association should exist'); ok(runInTerminalTool.sessionTerminalAssociations.has(sessionId2), 'Session 2 terminal association should exist'); - chatServiceDisposeEmitter.fire({ sessionId: sessionId1, reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResource: LocalChatSessionUri.forSession(sessionId1), reason: 'cleared' }); strictEqual(terminal1Disposed, true, 'Terminal 1 should have been disposed'); strictEqual(terminal2Disposed, false, 'Terminal 2 should NOT have been disposed'); @@ -903,7 +905,7 @@ class TestRunInTerminalTool extends RunInTerminalTool { test('should handle disposal of non-existent session gracefully', () => { strictEqual(runInTerminalTool.sessionTerminalAssociations.size, 0, 'No associations should exist initially'); - chatServiceDisposeEmitter.fire({ sessionId: 'non-existent-session', reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResource: LocalChatSessionUri.forSession('non-existent-session'), reason: 'cleared' }); strictEqual(runInTerminalTool.sessionTerminalAssociations.size, 0, 'No associations should exist after handling non-existent session'); }); }); From 6734ad674c001460ddbe1e485a8e23215dcca7f9 Mon Sep 17 00:00:00 2001 From: Dinesh Chandnani Date: Thu, 30 Oct 2025 18:17:06 -0700 Subject: [PATCH 1904/4355] Updated based on PR comments --- .../multiDiffEditor/diffEditorItemTemplate.ts | 1 - .../multiDiffEditorWidgetImpl.ts | 164 ++++++------------ .../browser/multiDiffEditor.ts | 12 -- 3 files changed, 52 insertions(+), 125 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 2e599aff775..7461b28f2b4 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -223,7 +223,6 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< renderOverviewRuler: false, fixedOverflowWidgets: true, overviewRulerBorder: false, - renderGutterMenu: true, }; } diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 110dad73751..0add04cc2d6 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -142,6 +142,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._instantiationService = this._register(this._parentInstantiationService.createChild( new ServiceCollection([IContextKeyService, this._contextKeyService]) )); + + this._contextKeyService.createKey(EditorContextKeys.inMultiDiffEditor.key, true); + this._lastDocStates = {}; this._register(autorunWithStore((reader, store) => { @@ -226,19 +229,22 @@ export class MultiDiffEditorWidgetImpl extends Disposable { return; } - const items = viewModel.items.read(reader); - if (items.length === 0) { - return; - } + // Only initialize when loading is complete + if (!viewModel.isLoading.read(reader)) { + const items = viewModel.items.read(reader); + if (items.length === 0) { + return; + } - // Only initialize if there's no active item yet - const activeDiffItem = viewModel.activeDiffItem.read(reader); - if (activeDiffItem) { - return; - } + // Only initialize if there's no active item yet + const activeDiffItem = viewModel.activeDiffItem.read(reader); + if (activeDiffItem) { + return; + } - // Navigate to the first change using the existing navigation logic - this.goToNextChange(); + // Navigate to the first change using the existing navigation logic + this.goToNextChange(); + } })); this._register(this._register(autorun(reader => { @@ -349,124 +355,58 @@ export class MultiDiffEditorWidgetImpl extends Disposable { return; } - // Get the currently active diff item const activeViewModel = this._viewModel.get()?.activeDiffItem.get(); - if (!activeViewModel) { - // No active item, go to first change in first file - const firstItem = viewItems[0]; - if (firstItem.viewModel.collapsed.get()) { - firstItem.viewModel.collapsed.set(false, undefined); - } - this._viewModel.get()?.activeDiffItem.setCache(firstItem.viewModel, undefined); - const template = firstItem.template.get(); - if (template) { - template.editor.revealFirstDiff(); - } - return; - } + const currentIndex = activeViewModel ? viewItems.findIndex(v => v.viewModel === activeViewModel) : -1; - // Find the active view item and its index - const activeViewItemIndex = viewItems.findIndex(v => v.viewModel === activeViewModel); - if (activeViewItemIndex === -1) { + // Start with first file if no active item + if (currentIndex === -1) { + this._goToFile(0, 'first'); return; } - const activeViewItem = viewItems[activeViewItemIndex]; - - // Ensure the current file is expanded before navigating - if (activeViewItem.viewModel.collapsed.get()) { - activeViewItem.viewModel.collapsed.set(false, undefined); + // Try current file first - expand if collapsed + const currentItem = viewItems[currentIndex]; + if (currentItem.viewModel.collapsed.get()) { + currentItem.viewModel.collapsed.set(false, undefined); } - const template = activeViewItem.template.get(); - if (!template) { - return; - } - - const diffEditor = template.editor; - const diffModel = diffEditor.getDiffComputationResult(); + const editor = currentItem.template.get()?.editor; + if (editor?.getDiffComputationResult()?.changes2?.length) { + const pos = editor.getModifiedEditor().getPosition()?.lineNumber || 1; + const changes = editor.getDiffComputationResult()!.changes2!; + const hasNext = direction === 'next' ? changes.some(c => c.modified.startLineNumber > pos) : changes.some(c => c.modified.endLineNumberExclusive <= pos); - if (!diffModel || !diffModel.changes2 || diffModel.changes2.length === 0) { - // No changes in current file, move to next/previous file - this.moveToAdjacentFile(direction, activeViewItemIndex, viewItems); - return; - } - - // Check if we're at a boundary (first/last change in the file) - const modifiedEditor = diffEditor.getModifiedEditor(); - const currentLineNumber = modifiedEditor.getPosition()?.lineNumber ?? 1; - const changes = diffModel.changes2; - - // Find which change we're currently at or near - let isAtBoundary = false; - - if (direction === 'next') { - // Check if we're at or past the last change - const lastChange = changes[changes.length - 1]; - const lastChangeStartLine = lastChange.modified.startLineNumber; - // If we're at or past the start of the last change, we're at the boundary - isAtBoundary = currentLineNumber >= lastChangeStartLine; - } else { - // Check if we're at or before the first change - const firstChange = changes[0]; - const firstChangeStartLine = firstChange.modified.startLineNumber; - isAtBoundary = currentLineNumber <= firstChangeStartLine; + if (hasNext) { + editor.goToDiff(direction); + return; + } } - if (isAtBoundary) { - // We're at the boundary, move to next/previous file - this.moveToAdjacentFile(direction, activeViewItemIndex, viewItems); - } else { - // Navigate within the current file - diffEditor.goToDiff(direction); - } + // Move to next/previous file + const nextIndex = (currentIndex + (direction === 'next' ? 1 : -1) + viewItems.length) % viewItems.length; + this._goToFile(nextIndex, direction === 'next' ? 'first' : 'last'); } - private moveToAdjacentFile(direction: 'next' | 'previous', currentIndex: number, viewItems: readonly VirtualizedViewItem[]): void { - const nextIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1; - - // Wrap around if needed - const targetIndex = (nextIndex + viewItems.length) % viewItems.length; - - const targetViewItem = viewItems[targetIndex]; - - // Expand if collapsed - if (targetViewItem.viewModel.collapsed.get()) { - targetViewItem.viewModel.collapsed.set(false, undefined); + private _goToFile(index: number, position: 'first' | 'last'): void { + const item = this._viewItems.get()[index]; + if (item.viewModel.collapsed.get()) { + item.viewModel.collapsed.set(false, undefined); } - // Set as active - this._viewModel.get()?.activeDiffItem.setCache(targetViewItem.viewModel, undefined); + this.reveal({ original: item.viewModel.originalUri, modified: item.viewModel.modifiedUri }); - // Scroll the multi-diff viewport to bring the target file into view - let scrollTop = 0; - for (let i = 0; i < targetIndex; i++) { - scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; - } - this._scrollableElement.setScrollPosition({ scrollTop }); - - // Reveal and go to first/last change in the new file - const template = targetViewItem.template.get(); - if (template) { - const diffEditor = template.editor; - const diffModel = diffEditor.getDiffComputationResult(); - - if (diffModel && diffModel.changes2 && diffModel.changes2.length > 0) { - if (direction === 'next') { - diffEditor.revealFirstDiff(); - diffEditor.focus(); - } else { - // For 'previous', position at the last change directly - const changes = diffModel.changes2; - const lastChange = changes[changes.length - 1]; - const modifiedEditor = diffEditor.getModifiedEditor(); - const startLine = lastChange.modified.startLineNumber; - modifiedEditor.setPosition({ lineNumber: startLine, column: 1 }); - modifiedEditor.revealLineInCenter(startLine); - modifiedEditor.focus(); - } + const editor = item.template.get()?.editor; + if (editor?.getDiffComputationResult()?.changes2?.length) { + if (position === 'first') { + editor.revealFirstDiff(); + } else { + const lastChange = editor.getDiffComputationResult()!.changes2!.at(-1)!; + const modifiedEditor = editor.getModifiedEditor(); + modifiedEditor.setPosition({ lineNumber: lastChange.modified.startLineNumber, column: 1 }); + modifiedEditor.revealLineInCenter(lastChange.modified.startLineNumber); } } + editor?.focus(); } private render(reader: IReader | undefined) { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 35592b49f7b..4c17e6f6750 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -26,18 +26,15 @@ import { MultiDiffEditorViewModel } from '../../../../editor/browser/widget/mult import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from '../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IDiffEditor } from '../../../../editor/common/editorCommon.js'; -import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { Range } from '../../../../editor/common/core/range.js'; import { MultiDiffEditorItem } from './multiDiffSourceResolverService.js'; import { IEditorProgressService } from '../../../../platform/progress/common/progress.js'; -import { IContextKeyService, IContextKey } from '../../../../platform/contextkey/common/contextkey.js'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; private _multiDiffEditorWidget: MultiDiffEditorWidget | undefined = undefined; private _viewModel: MultiDiffEditorViewModel | undefined; - private readonly _inMultiDiffEditorContextKey: IContextKey; public get viewModel(): MultiDiffEditorViewModel | undefined { return this._viewModel; @@ -53,7 +50,6 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { this._onDidChangeControl.fire(); })); - - this._inMultiDiffEditorContextKey.set(true); } override async setInput(input: MultiDiffEditorInput, options: IMultiDiffEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -168,11 +161,6 @@ export class MultiDiffEditor extends AbstractEditorWithViewState): Promise { return this.editorProgressService.showWhile(promise); } - - override dispose(): void { - this._inMultiDiffEditorContextKey.reset(); - super.dispose(); - } } From 59c7b3bac3482e890e66dbe31be4f0bfc9ecaa78 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:19:28 -0700 Subject: [PATCH 1905/4355] Update for merge --- .../terminalContrib/chat/browser/terminalChatService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index 438bac8d1bc..160e0f34fd7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -12,6 +12,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../../pla import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { IChatService } from '../../../chat/common/chatService.js'; import { TerminalChatContextKeys } from './terminalChat.js'; +import { LocalChatSessionUri } from '../../../chat/common/chatUri.js'; const enum StorageKeys { ToolSessionMappings = 'terminalChat.toolSessionMappings', @@ -73,7 +74,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ listener.dispose(); })); this._register(this._chatService.onDidDisposeSession(e => { - if (e.sessionId === terminalToolSessionId) { + if (LocalChatSessionUri.parseLocalSessionId(e.sessionResource) === terminalToolSessionId) { this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId); this._commandIdByToolSessionId.delete(terminalToolSessionId); From b6de93830d46bc8ec0d13c7ad143d96ffc7b07da Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:21:03 -0700 Subject: [PATCH 1906/4355] Remove a few more mentions of inputType --- src/vs/workbench/contrib/chat/common/chatModel.ts | 1 - src/vs/workbench/contrib/chat/common/chatService.ts | 2 +- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 0551bd7fcf6..044436b0e9b 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1163,7 +1163,6 @@ export interface ISerializableChatData2 extends ISerializableChatData1 { export interface ISerializableChatData3 extends Omit { version: 3; customTitle: string | undefined; - inputType?: string; } /** diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index a94e802ed94..5b4a1e76b6d 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -909,7 +909,7 @@ export interface IChatService { isEnabled(location: ChatAgentLocation): boolean; hasSessions(): boolean; - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, options?: { canUseTools?: boolean; inputType?: string }): ChatModel; + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession?: boolean, options?: { canUseTools?: boolean }): ChatModel; getSession(sessionResource: URI): IChatModel | undefined; getOrRestoreSession(sessionResource: URI): Promise; getPersistedSessionTitle(sessionResource: URI): string | undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 628d16d5b5a..4f291974682 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -401,7 +401,7 @@ export class ChatService extends Disposable implements IChatService { await this._chatSessionStore.clearAllSessions(); } - startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true, options?: { canUseTools?: boolean; inputType?: string }): ChatModel { + startSession(location: ChatAgentLocation, token: CancellationToken, isGlobalEditingSession: boolean = true, options?: { canUseTools?: boolean }): ChatModel { this.trace('startSession'); return this._startSession(undefined, location, isGlobalEditingSession, token, options); } From b41096547fcef566f58d2c2e371ef85e25f80de5 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:22:03 -0700 Subject: [PATCH 1907/4355] fix simple browser overlay persisting into editors (#274213) --- .../chatEditing/simpleBrowserEditorOverlay.ts | 28 +++++++++++-------- .../browser/media/simpleBrowserOverlay.css | 28 +++++++++---------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts index f0e9820dd0a..99e0c036313 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/simpleBrowserEditorOverlay.ts @@ -82,10 +82,13 @@ class SimpleBrowserOverlayWidget { this._domNode = document.createElement('div'); this._domNode.className = 'element-selection-message'; + const mainContent = document.createElement('div'); + mainContent.className = 'element-selection-main-content'; + const message = document.createElement('span'); const startSelectionMessage = localize('elementSelectionMessage', 'Add element to chat'); message.textContent = startSelectionMessage; - this._domNode.appendChild(message); + mainContent.appendChild(message); let cts: CancellationTokenSource; const actions: IAction[] = []; @@ -124,7 +127,7 @@ class SimpleBrowserOverlayWidget { } })); - const startButton = this._showStore.add(new ButtonWithDropdown(this._domNode, { + const startButton = this._showStore.add(new ButtonWithDropdown(mainContent, { actions: actions, addPrimaryActionToDropdown: false, contextMenuProvider: this.contextMenuService, @@ -137,28 +140,29 @@ class SimpleBrowserOverlayWidget { startButton.primaryButton.label = localize('startSelection', 'Start'); startButton.element.classList.add('element-selection-start'); - const cancelButton = this._showStore.add(new Button(this._domNode, { ...defaultButtonStyles, supportIcons: true, title: localize('cancelSelection', 'Click to cancel selection.') })); + const cancelButton = this._showStore.add(new Button(mainContent, { ...defaultButtonStyles, supportIcons: true, title: localize('cancelSelection', 'Click to cancel selection.') })); cancelButton.element.className = 'element-selection-cancel hidden'; const cancelButtonLabel = localize('cancelSelectionLabel', 'Cancel'); cancelButton.label = cancelButtonLabel; - const configure = this._showStore.add(new Button(this._domNode, { supportIcons: true, title: localize('chat.configureElements', "Configure Attachments Sent") })); + const configure = this._showStore.add(new Button(mainContent, { supportIcons: true, title: localize('chat.configureElements', "Configure Attachments Sent") })); configure.icon = Codicon.gear; - const collapseOverlay = this._showStore.add(new Button(this._domNode, { supportIcons: true, title: localize('chat.hideOverlay', "Collapse Overlay") })); + const collapseOverlay = this._showStore.add(new Button(mainContent, { supportIcons: true, title: localize('chat.hideOverlay', "Collapse Overlay") })); collapseOverlay.icon = Codicon.chevronRight; - const nextSelection = this._showStore.add(new Button(this._domNode, { supportIcons: true, title: localize('chat.nextSelection', "Select Again") })); + const nextSelection = this._showStore.add(new Button(mainContent, { supportIcons: true, title: localize('chat.nextSelection', "Select Again") })); nextSelection.icon = Codicon.close; nextSelection.element.classList.add('hidden'); // shown if the overlay is collapsed - const expandOverlay = this._showStore.add(new Button(this._domNode, { supportIcons: true, title: localize('chat.expandOverlay', "Expand Overlay") })); - expandOverlay.icon = Codicon.layout; const expandContainer = document.createElement('div'); expandContainer.className = 'element-expand-container hidden'; - expandContainer.appendChild(expandOverlay.element); - this._container.appendChild(expandContainer); + const expandOverlay = this._showStore.add(new Button(expandContainer, { supportIcons: true, title: localize('chat.expandOverlay', "Expand Overlay") })); + expandOverlay.icon = Codicon.layout; + + this._domNode.appendChild(mainContent); + this._domNode.appendChild(expandContainer); const resetButtons = () => { this.hideElement(nextSelection.element); @@ -206,12 +210,12 @@ class SimpleBrowserOverlayWidget { })); this._showStore.add(addDisposableListener(collapseOverlay.element, 'click', () => { - this.hideElement(this._domNode); + this.hideElement(mainContent); this.showElement(expandContainer); })); this._showStore.add(addDisposableListener(expandOverlay.element, 'click', () => { - this.showElement(this._domNode); + this.showElement(mainContent); this.hideElement(expandContainer); })); diff --git a/src/vs/workbench/contrib/chat/browser/media/simpleBrowserOverlay.css b/src/vs/workbench/contrib/chat/browser/media/simpleBrowserOverlay.css index 035ecf8e6a9..3a5e84b1fc9 100644 --- a/src/vs/workbench/contrib/chat/browser/media/simpleBrowserOverlay.css +++ b/src/vs/workbench/contrib/chat/browser/media/simpleBrowserOverlay.css @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ .element-selection-message, -.element-expand-container, .connecting-webview-element { position: absolute; bottom: 10px; @@ -23,32 +22,33 @@ height: 42px; } -.element-selection-message { - bottom: 10px; - right: 10px; -} - -.element-expand-container, .connecting-webview-element { bottom: 15px; right: 15px; } +.element-selection-main-content, +.element-expand-container { + display: flex; + align-items: center; + gap: 8px; +} + .element-selection-cancel { padding: 2px 8px; width: fit-content; } -.element-selection-message .monaco-button-dropdown > .monaco-button.monaco-text-button { +.element-selection-main-content .monaco-button-dropdown > .monaco-button.monaco-text-button { height: 24px; align-content: center; padding: 0px 5px; } -.element-selection-message .monaco-button.codicon.codicon-close, +.element-selection-main-content .monaco-button.codicon.codicon-close, .element-expand-container .monaco-button.codicon.codicon-layout, -.element-selection-message .monaco-button.codicon.codicon-chevron-right, -.element-selection-message .monaco-button.codicon.codicon-gear { +.element-selection-main-content .monaco-button.codicon.codicon-chevron-right, +.element-selection-main-content .monaco-button.codicon.codicon-gear { width: 17px; height: 17px; padding: 2px 2px; @@ -74,13 +74,13 @@ color: var(--vscode-button-foreground); } -.element-selection-message .monaco-button:hover, +.element-selection-main-content .monaco-button:hover, .element-expand-container .monaco-button:hover { background-color: var(--vscode-toolbar-hoverBackground); } -.element-selection-message .hidden, +.element-selection-main-content .hidden, .element-expand-container.hidden, -.element-selection-message.hidden { +.element-selection-main-content.hidden { display: none !important; } From 533090c123cf06f33711446d4e631b369c2c41a3 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:44:39 -0700 Subject: [PATCH 1908/4355] Another merge fix --- src/vs/workbench/contrib/chat/browser/chatEditorInput.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index b9e745f6a5c..c47b38df7b2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -262,13 +262,13 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler const chatSessionType = searchParams.get('chatSessionType'); const inputType = chatSessionType ?? this.resource.authority; - if (this.resource.scheme !== Schemas.vscodeChatEditor && this.resource.scheme !== Schemas.vscodeLocalChatSession) { + if (this.resource.scheme !== Schemas.vscodeChatEditor) { this.model = await this.chatService.loadSessionForResource(this.resource, ChatAgentLocation.Chat, CancellationToken.None); } else if (this._sessionInfo?.sessionId) { this.model = await this.chatService.getOrRestoreSession(this._sessionInfo.resource) - ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false, inputType: inputType }); + ?? this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: false }); } else if (!this.options.target) { - this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType, inputType: inputType }); + this.model = this.chatService.startSession(ChatAgentLocation.Chat, CancellationToken.None, undefined, { canUseTools: !inputType }); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } From f5652ea958037fde5d3e86627488e3dcd36195e5 Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:44:58 -0700 Subject: [PATCH 1909/4355] Use session resources for code block collection This should correctly support code block text models in contributed chat sessions Our current models seem to rarely generate these but I don't think they were working correctly before --- .../chatMarkdownContentPart.ts | 23 ++++++---- .../contrib/chat/browser/chatViewPane.ts | 6 +-- .../contrib/chat/browser/codeBlockPart.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 2 +- .../chat/common/codeBlockModelCollection.ts | 46 ++++++++++--------- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 904006eaad9..a82e0cc40d9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -158,7 +158,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP } const globalIndex = globalCodeBlockIndexStart++; const thisPartIndex = thisPartCodeBlockIndexStart++; - let textModel: Promise; + let textModel: Promise | undefined; let range: Range | undefined; let vulns: readonly IMarkdownVulnerability[] | undefined; let codeblockEntry: CodeBlockEntry | undefined; @@ -171,12 +171,15 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP return $('div'); } } else { - const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : ''; - const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, globalIndex); - const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, globalIndex, { text, languageId, isComplete: isCodeBlockComplete }); - vulns = modelEntry.vulns; - codeblockEntry = fastUpdateModelEntry; - textModel = modelEntry.model; + if (isResponseVM(element) || isRequestVM(element)) { + const modelEntry = this.codeBlockModelCollection.getOrCreate(element.sessionResource, element, globalIndex); + const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(element.sessionResource, element, globalIndex, { text, languageId, isComplete: isCodeBlockComplete }); + vulns = modelEntry.vulns; + codeblockEntry = fastUpdateModelEntry; + textModel = modelEntry.model; + } else { + textModel = undefined; + } } const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; @@ -210,7 +213,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP // async and the uri might be undefined when it's read immediately return ref.object.uri; } - readonly uriPromise = textModel.then(model => model.uri); + readonly uriPromise = textModel?.then(model => model.uri) ?? Promise.resolve(undefined); focus() { ref.object.focus(); } @@ -223,7 +226,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP const ref = this.renderCodeBlockPill(element.sessionId, element.sessionResource, requestId, inUndoStop, codeBlockInfo.codemapperUri); if (isResponseVM(codeBlockInfo.element)) { // TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously - this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => { + this.codeBlockModelCollection.update(codeBlockInfo.element.sessionResource, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => { // Update the existing object's codemapperUri this.codeblocks[codeBlockInfo.codeBlockPartIndex].codemapperUri = e.codemapperUri; this._onDidChangeHeight.fire(); @@ -338,7 +341,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP const ref = this.editorPool.get(); const editorInfo = ref.object; if (isResponseVM(data.element)) { - this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId, isComplete }).then((e) => { + this.codeBlockModelCollection.update(data.element.sessionResource, data.element, data.codeBlockIndex, { text, languageId: data.languageId, isComplete }).then((e) => { // Update the existing object's codemapperUri this.codeblocks[data.codeBlockPartIndex].codemapperUri = e.codemapperUri; this._onDidChangeHeight.fire(); diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 811d4b296b2..3121eea7458 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -247,13 +247,13 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.updateActions(); } - async loadSession(sessionId: string | URI, viewState?: IChatViewState): Promise { + async loadSession(sessionId: URI, viewState?: IChatViewState): Promise { if (this.widget.viewModel) { await this.chatService.clearSession(this.widget.viewModel.sessionResource); } // Handle locking for contributed chat sessions - if (URI.isUri(sessionId) && sessionId.scheme === Schemas.vscodeLocalChatSession) { + if (sessionId.scheme === Schemas.vscodeLocalChatSession) { const parsed = LocalChatSessionUri.parse(sessionId); if (parsed?.chatSessionType) { await this.chatSessionsService.canResolveChatSession(sessionId); @@ -265,7 +265,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { } } - const newModel = await (URI.isUri(sessionId) ? this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Chat, CancellationToken.None) : this.chatService.getOrRestoreSession(LocalChatSessionUri.forSession(sessionId))); + const newModel = await this.chatService.loadSessionForResource(sessionId, ChatAgentLocation.Chat, CancellationToken.None); await this.updateModel(newModel, viewState); } diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 324fb65354c..5d06c249f55 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -80,7 +80,7 @@ export interface ICodeBlockData { readonly codeBlockPartIndex: number; readonly element: unknown; - readonly textModel: Promise; + readonly textModel: Promise | undefined; readonly languageId: string; readonly codemapperUri?: URI; @@ -459,7 +459,7 @@ export class CodeBlockPart extends Disposable { private async updateEditor(data: ICodeBlockData): Promise { const textModel = await data.textModel; - if (this.isDisposed || this.currentCodeBlockData !== data || textModel.isDisposed()) { + if (this.isDisposed || this.currentCodeBlockData !== data || !textModel || textModel.isDisposed()) { return false; } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 5ccd93bad24..63b82b743f8 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -377,7 +377,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { if (token.type === 'code') { const lang = token.lang || ''; const text = token.text; - this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text, languageId: lang, isComplete: true }); + this.codeBlockModelCollection.update(this._model.sessionResource, model, codeBlockIndex++, { text, languageId: lang, isComplete: true }); } }); } diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts index fb378c94ab4..470c6554bc9 100644 --- a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { encodeBase64, VSBuffer } from '../../../../base/common/buffer.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { Disposable, IReference } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; @@ -73,8 +74,8 @@ export class CodeBlockModelCollection extends Disposable { this.clear(); } - get(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry | undefined { - const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); + get(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry | undefined { + const entry = this._models.get(this.getKey(sessionResource, chat, codeBlockIndex)); if (!entry) { return; } @@ -86,15 +87,15 @@ export class CodeBlockModelCollection extends Disposable { }; } - getOrCreate(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry { - const existing = this.get(sessionId, chat, codeBlockIndex); + getOrCreate(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry { + const existing = this.get(sessionResource, chat, codeBlockIndex); if (existing) { return existing; } - const uri = this.getCodeBlockUri(sessionId, chat, codeBlockIndex); + const uri = this.getCodeBlockUri(sessionResource, chat, codeBlockIndex); const model = this.textModelService.createModelReference(uri); - this._models.set(this.getKey(sessionId, chat, codeBlockIndex), { + this._models.set(this.getKey(sessionResource, chat, codeBlockIndex), { model: model, vulns: [], inLanguageId: undefined, @@ -127,26 +128,26 @@ export class CodeBlockModelCollection extends Disposable { this._models.clear(); } - updateSync(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): CodeBlockEntry { - const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); + updateSync(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): CodeBlockEntry { + const entry = this.getOrCreate(sessionResource, chat, codeBlockIndex); - this.updateInternalCodeBlockEntry(content, sessionId, chat, codeBlockIndex); + this.updateInternalCodeBlockEntry(content, sessionResource, chat, codeBlockIndex); - return this.get(sessionId, chat, codeBlockIndex) ?? entry; + return this.get(sessionResource, chat, codeBlockIndex) ?? entry; } - markCodeBlockCompleted(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): void { - const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); + markCodeBlockCompleted(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): void { + const entry = this._models.get(this.getKey(sessionResource, chat, codeBlockIndex)); if (!entry) { return; } // TODO: fill this in once we've implemented https://github.com/microsoft/vscode/issues/232538 } - async update(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): Promise { - const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); + async update(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): Promise { + const entry = this.getOrCreate(sessionResource, chat, codeBlockIndex); - const newText = this.updateInternalCodeBlockEntry(content, sessionId, chat, codeBlockIndex); + const newText = this.updateInternalCodeBlockEntry(content, sessionResource, chat, codeBlockIndex); const textModel = await entry.model; if (!textModel || textModel.isDisposed()) { @@ -176,8 +177,8 @@ export class CodeBlockModelCollection extends Disposable { return entry; } - private updateInternalCodeBlockEntry(content: CodeBlockContent, sessionId: string, chat: IChatResponseViewModel | IChatRequestViewModel, codeBlockIndex: number) { - const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); + private updateInternalCodeBlockEntry(content: CodeBlockContent, sessionResource: URI, chat: IChatResponseViewModel | IChatRequestViewModel, codeBlockIndex: number) { + const entry = this._models.get(this.getKey(sessionResource, chat, codeBlockIndex)); if (entry) { entry.inLanguageId = content.languageId; } @@ -199,7 +200,7 @@ export class CodeBlockModelCollection extends Disposable { } if (content.isComplete) { - this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); + this.markCodeBlockCompleted(sessionResource, chat, codeBlockIndex); } return newText; @@ -212,16 +213,17 @@ export class CodeBlockModelCollection extends Disposable { } } - private getKey(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): string { - return `${sessionId}/${chat.id}/${index}`; + private getKey(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): string { + return `${sessionResource.toString()}/${chat.id}/${index}`; } - private getCodeBlockUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): URI { + private getCodeBlockUri(sessionResource: URI, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): URI { const metadata = this.getUriMetaData(chat); const indexPart = this.tag ? `${this.tag}-${index}` : `${index}`; + const encodedSessionId = encodeBase64(VSBuffer.wrap(new TextEncoder().encode(sessionResource.toString())), false, true); return URI.from({ scheme: Schemas.vscodeChatCodeBlock, - authority: sessionId, + authority: encodedSessionId, path: `/${chat.id}/${indexPart}`, fragment: metadata ? JSON.stringify(metadata) : undefined, }); From a316c829949262cd6f542077331d65fe42b8e457 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 08:36:54 +0100 Subject: [PATCH 1910/4355] Clicking "+" (New Chat) in editor area toggles between "Chat" and "Chat 2" (fix #248591) (#274189) --- .../workbench/contrib/chat/browser/actions/chatActions.ts | 7 ++++++- .../contrib/chat/browser/actions/chatNewActions.ts | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 84b4ff033f7..47331dcd9f9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -1052,7 +1052,7 @@ export function registerChatActions() { super({ id: ACTION_ID_OPEN_CHAT, title: localize2('interactiveSession.open', "New Chat Editor"), - icon: Codicon.newFile, + icon: Codicon.plus, f1: true, category: CHAT_CATEGORY, precondition: ChatContextKeys.enabled, @@ -1069,6 +1069,11 @@ export function registerChatActions() { id: MenuId.ChatNewMenu, group: '2_new', order: 2 + }, { + id: MenuId.EditorTitle, + group: 'navigation', + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), ChatContextKeys.lockedToCodingAgent.negate()), + order: 1 }], }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts index 3f3fa75481f..06ad0a46914 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts @@ -94,12 +94,12 @@ export function registerNewChatActions() { group: '1_open', order: 1, }, - ...[MenuId.EditorTitle, MenuId.CompactWindowEditorTitle].map(id => ({ - id, + { + id: MenuId.CompactWindowEditorTitle, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), ChatContextKeys.lockedToCodingAgent.negate()), order: 1 - })) + } ], keybinding: { weight: KeybindingWeight.WorkbenchContrib + 1, From ce5ed6de1cebaef6120be5bfeec709431bda90c5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 31 Oct 2025 00:37:19 -0700 Subject: [PATCH 1911/4355] edits: temp workaround for edit session discard dialog (#274259) Refs #274198, will fix this properly tomorrow. --- .../workbench/api/browser/mainThreadChatSessions.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index e11a72866b5..afe4141c5a4 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -17,8 +17,9 @@ import { localize } from '../../../nls.js'; import { IDialogService } from '../../../platform/dialogs/common/dialogs.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { IChatEditorOptions } from '../../contrib/chat/browser/chatEditor.js'; +import { ChatEditorInput } from '../../contrib/chat/browser/chatEditorInput.js'; import { IChatAgentRequest } from '../../contrib/chat/common/chatAgents.js'; -import { IChatContentInlineReference, IChatProgress } from '../../contrib/chat/common/chatService.js'; +import { IChatContentInlineReference, IChatProgress, IChatService } from '../../contrib/chat/common/chatService.js'; import { IChatSession, IChatSessionContentProvider, IChatSessionHistoryItem, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatVariableEntries.js'; import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; @@ -332,6 +333,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @ILogService private readonly _logService: ILogService, + @IChatService private readonly _chatService: IChatService, ) { super(); @@ -373,7 +375,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire(); } - $onDidCommitChatSessionItem(handle: number, originalComponents: UriComponents, modifiedCompoennts: UriComponents): void { + async $onDidCommitChatSessionItem(handle: number, originalComponents: UriComponents, modifiedCompoennts: UriComponents): Promise { const originalResource = URI.revive(originalComponents); const modifiedResource = URI.revive(modifiedCompoennts); @@ -400,6 +402,11 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat }; if (originalEditor) { + // todo@connor4312: temp work around for https://github.com/microsoft/vscode/issues/274198 + if (originalEditor instanceof ChatEditorInput && originalEditor.sessionResource) { + await this._chatService.getSession(originalEditor.sessionResource)?.editingSession?.accept(); + } + // Prefetch the chat session content to make the subsequent editor swap quick this._chatSessionsService.getOrCreateChatSession( URI.revive(modifiedResource), From 14e845d26cfc421b019c63958a5a781b01d16a6a Mon Sep 17 00:00:00 2001 From: Matt Bierner <12821956+mjbvz@users.noreply.github.com> Date: Fri, 31 Oct 2025 00:50:36 -0700 Subject: [PATCH 1912/4355] Switch many callers to use `getWidgetBySessionResource` (#274253) This new version of the function should be preferred as it helps move us further along with using URIs for chat sessions --- .../contrib/chat/browser/actions/chatMoveActions.ts | 3 +-- .../contrib/chat/browser/actions/chatSessionActions.ts | 8 +++----- .../contrib/chat/browser/actions/chatTitleActions.ts | 2 +- src/vs/workbench/contrib/chat/browser/chat.ts | 3 +++ .../chatContentParts/chatConfirmationContentPart.ts | 2 +- .../browser/chatContentParts/chatErrorConfirmationPart.ts | 2 +- .../browser/chatContentParts/chatQuotaExceededPart.ts | 2 +- .../abstractToolConfirmationSubPart.ts | 2 +- .../chatExtensionsInstallToolSubPart.ts | 2 +- .../chatTerminalToolConfirmationSubPart.ts | 2 +- .../toolInvocationParts/chatToolOutputPart.ts | 6 +++--- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- .../workbench/contrib/chat/browser/chatSessions/common.ts | 2 +- .../chat/browser/chatSessions/view/sessionsViewPane.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatSetup.ts | 2 +- .../chat/browser/contrib/chatInputRelatedFilesContrib.ts | 2 +- .../chat/electron-browser/actions/voiceChatActions.ts | 2 +- .../contrib/inlineChat/browser/inlineChatActions.ts | 2 +- .../inlineChat/browser/inlineChatSessionServiceImpl.ts | 2 +- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 2b01846972b..f1d5b9166a2 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -121,8 +121,7 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew return; } - const sessionId = widget.viewModel.sessionId; - const existingWidget = widgetService.getWidgetBySessionId(sessionId); + const existingWidget = widgetService.getWidgetBySessionResource(widget.viewModel.sessionResource); if (!existingWidget) { // Do NOT attempt to open a session that isn't already open since we cannot guarantee its state. await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true, auxiliary: { compact: true, bounds: { width: 640, height: 640 } } } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts index 16b26ff67be..69906b44f3a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatSessionActions.ts @@ -176,7 +176,6 @@ export class OpenChatSessionInNewWindowAction extends Action2 { const editorService = accessor.get(IEditorService); const chatWidgetService = accessor.get(IChatWidgetService); - const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { const uri = context.session.resource; @@ -186,7 +185,7 @@ export class OpenChatSessionInNewWindowAction extends Action2 { if (existingEditor) { await editorService.openEditor(existingEditor.editor, existingEditor.group); return; - } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + } else if (chatWidgetService.getWidgetBySessionResource(uri)) { return; } else { const options: IChatEditorOptions = { @@ -223,7 +222,6 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { const editorService = accessor.get(IEditorService); const chatWidgetService = accessor.get(IChatWidgetService); - const sessionId = context.session.id; const editorGroupsService = accessor.get(IEditorGroupsService); if (context.session.provider?.chatSessionType) { const uri = context.session.resource; @@ -232,7 +230,7 @@ export class OpenChatSessionInNewEditorGroupAction extends Action2 { if (existingEditor) { await editorService.openEditor(existingEditor.editor, existingEditor.group); return; - } else if (chatWidgetService.getWidgetBySessionId(sessionId)) { + } else if (chatWidgetService.getWidgetBySessionResource(uri)) { // Already opened in chat widget return; } else { @@ -284,7 +282,7 @@ export class OpenChatSessionInSidebarAction extends Action2 { if (existingEditor) { await editorService.openEditor(existingEditor.editor, existingEditor.group); return; - } else if (chatWidgetService.getWidgetBySessionId(context.session.id)) { + } else if (chatWidgetService.getWidgetBySessionResource(context.session.resource)) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 212145cede1..d271377cae3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -219,7 +219,7 @@ export function registerChatTitleActions() { return; } const itemIndex = chatRequests?.findIndex(request => request.id === item.requestId); - const widget = chatWidgetService.getWidgetBySessionId(item.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(item.sessionResource); const mode = widget?.input.currentModeKind; if (chatModel && (mode === ChatModeKind.Edit || mode === ChatModeKind.Agent)) { const configurationService = accessor.get(IConfigurationService); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ff3ec632d90..b2a3b75dd3b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -48,8 +48,11 @@ export interface IChatWidgetService { getAllWidgets(): ReadonlyArray; getWidgetByInputUri(uri: URI): IChatWidget | undefined; + + /** @deprecated Use {@link getWidgetBySessionResource} instead */ getWidgetBySessionId(sessionId: string): IChatWidget | undefined; getWidgetBySessionResource(sessionResource: URI): IChatWidget | undefined; + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts index 34f4a66c545..9794beecf4e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -54,7 +54,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont options.agentId = element.agent?.id; options.slashCommand = element.slashCommand?.name; options.confirmation = e.label; - const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(element.sessionResource); options.userSelectedModelId = widget?.input.currentLanguageModel; options.modeInfo = widget?.input.currentModeInfo; options.location = widget?.location; diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts index ce37a100942..8323b3b87fa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatErrorConfirmationPart.ts @@ -59,7 +59,7 @@ export class ChatErrorConfirmationContentPart extends Disposable implements ICha options.agentId = element.agent?.id; options.slashCommand = element.slashCommand?.name; options.confirmation = buttonData.label; - const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(element.sessionResource); options.userSelectedModelId = widget?.input.currentLanguageModel; Object.assign(options, widget?.getModeRequestOptions()); if (await chatService.sendRequest(element.sessionResource, prompt, options)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts index bb8971f5d79..1b52bd61b51 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatQuotaExceededPart.ts @@ -104,7 +104,7 @@ export class ChatQuotaExceededPart extends Disposable implements IChatContentPar this._onDidChangeHeight.fire(); this._register(retryButton.onDidClick(() => { - const widget = chatWidgetService.getWidgetBySessionId(element.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(element.sessionResource); if (!widget) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts index 9c9cb351ff9..fe06c87648a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts @@ -132,7 +132,7 @@ export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvoca } } - this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.focusInput(); + this.chatWidgetService.getWidgetBySessionResource(this.context.element.sessionResource)?.focusInput(); })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts index 6ed5bc64375..7736c50548e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts @@ -86,7 +86,7 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo dom.append(this.domNode, confirmWidget.domNode); this._register(confirmWidget.onDidClick(button => { IChatToolInvocation.confirmWith(toolInvocation, button.data); - chatWidgetService.getWidgetBySessionId(context.element.sessionId)?.focusInput(); + chatWidgetService.getWidgetBySessionResource(context.element.sessionResource)?.focusInput(); })); const hasToolConfirmationKey = ChatContextKeys.Editing.hasToolConfirmation.bindTo(contextKeyService); hasToolConfirmationKey.set(true); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index e36962507f5..03a8aa47bdf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -303,7 +303,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS if (doComplete) { IChatToolInvocation.confirmWith(toolInvocation, { type: toolConfirmKind }); - this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.focusInput(); + this.chatWidgetService.getWidgetBySessionResource(this.context.element.sessionResource)?.focusInput(); } })); this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts index 524e9fcd414..78165f8709c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts @@ -77,7 +77,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { } private createOutputPart(toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized, details: IToolResultOutputDetails): HTMLElement { - const vm = this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.viewModel; + const vm = this.chatWidgetService.getWidgetBySessionResource(this.context.element.sessionResource)?.viewModel; const parent = dom.$('div.webview-output'); parent.style.maxHeight = '80vh'; @@ -124,7 +124,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { })); this._register(renderedItem.webview.onDidWheel(e => { - this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId)?.delegateScrollFromMouseWheelEvent({ + this.chatWidgetService.getWidgetBySessionResource(this.context.element.sessionResource)?.delegateScrollFromMouseWheelEvent({ ...e, preventDefault: () => { }, stopPropagation: () => { } @@ -132,7 +132,7 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart { })); // When the webview is disconnected from the DOM due to being hidden, we need to reload it when it is shown again. - const widget = this.chatWidgetService.getWidgetBySessionId(this.context.element.sessionId); + const widget = this.chatWidgetService.getWidgetBySessionResource(this.context.element.sessionResource); if (widget) { this._register(widget?.onDidShow(() => { renderedItem.reinitialize(); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 02c8c6c35c5..64a974a0040 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -575,7 +575,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { - const widget = chatWidgetService.getWidgetBySessionId(requestModel.session.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource); const modeInfo = widget?.input.currentModeInfo; // We need a signal to know when we can resend the request to diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts index 97b4f7f77e6..342dab6f528 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -30,7 +30,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben this._register(autorun((reader) => { const sessions = this.chatEditingService.editingSessionsObs.read(reader); sessions.forEach(session => { - const widget = this.chatWidgetService.getWidgetBySessionId(session.chatSessionId); + const widget = this.chatWidgetService.getWidgetBySessionResource(session.chatSessionResource); if (widget && !this.chatEditingSessionDisposables.has(session.chatSessionId)) { this._handleNewEditingSession(session, widget); } diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts index f71f7cd84e2..9f55ce34e57 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/voiceChatActions.ts @@ -686,7 +686,7 @@ class ChatSynthesizerSessionController { private static doCreateForFocusedChat(accessor: ServicesAccessor, response: IChatResponseModel): IChatSynthesizerSessionController { const chatWidgetService = accessor.get(IChatWidgetService); const contextKeyService = accessor.get(IContextKeyService); - let chatWidget = chatWidgetService.getWidgetBySessionId(response.session.sessionId); + let chatWidget = chatWidgetService.getWidgetBySessionResource(response.session.sessionResource); if (chatWidget?.location === ChatAgentLocation.EditorInline) { // workaround for https://github.com/microsoft/vscode/issues/212785 chatWidget = chatWidgetService.lastFocusedWidget; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 1e7fef7aa32..e2587225229 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -382,7 +382,7 @@ export class RerunAction extends AbstractInline1ChatAction { const lastRequest = model.getRequests().at(-1); if (lastRequest) { - const widget = chatWidgetService.getWidgetBySessionId(model.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(model.sessionResource); await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index abdd206223b..1d72b69f660 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -348,7 +348,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const chatModel = this._chatService.startSession(ChatAgentLocation.Chat, token, false); const editingSession = await chatModel.editingSessionObs?.promise!; - const widget = this._chatWidgetService.getWidgetBySessionId(chatModel.sessionId); + const widget = this._chatWidgetService.getWidgetBySessionResource(chatModel.sessionResource); await widget?.attachmentModel.addFile(uri); const store = new DisposableStore(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 4d5096f58da..1b141a6dbd8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -265,7 +265,7 @@ registerActiveXtermAction({ const lastRequest = model.getRequests().at(-1); if (lastRequest) { - const widget = chatWidgetService.getWidgetBySessionId(model.sessionId); + const widget = chatWidgetService.getWidgetBySessionResource(model.sessionResource); await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, From 9e07a1bd980b8d3661b3453330abbe52f876649a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 31 Oct 2025 03:56:17 -0400 Subject: [PATCH 1913/4355] check against correct process id to fix hidden tool terminal restore (#274243) fixes #274237 --- .../terminalContrib/chat/browser/terminalChatService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index 160e0f34fd7..e151ab62d21 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -106,7 +106,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ return undefined; } if (this._pendingRestoredMappings.has(terminalToolSessionId)) { - const instance = this._terminalService.instances.find(i => i.persistentProcessId === this._pendingRestoredMappings.get(terminalToolSessionId)); + const instance = this._terminalService.instances.find(i => i.shellLaunchConfig.attachPersistentProcess?.id === this._pendingRestoredMappings.get(terminalToolSessionId)); if (instance) { this._tryAdoptRestoredMapping(instance); return instance; @@ -165,7 +165,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ return; } for (const [toolSessionId, persistentProcessId] of this._pendingRestoredMappings) { - if (persistentProcessId === instance.persistentProcessId) { + if (persistentProcessId === instance.shellLaunchConfig.attachPersistentProcess?.id) { this._terminalInstancesByToolSessionId.set(toolSessionId, instance); this._onDidRegisterTerminalInstanceForToolSession.fire(instance); this._terminalInstanceListenersByToolSessionId.set(toolSessionId, instance.onDisposed(() => { From d4750bfe2681279e54a6cfa9685daa6992ed9f89 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 31 Oct 2025 01:01:04 -0700 Subject: [PATCH 1914/4355] Use case-insensitive glob API (#274224) --- .../contrib/notebook/common/notebookCommon.ts | 27 +++++++++---------- .../notebook/common/notebookOutputRenderer.ts | 2 +- .../notebook/common/notebookProvider.ts | 15 +++-------- .../editor/common/editorResolverService.ts | 4 +-- .../services/label/common/labelService.ts | 3 +-- 5 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 53ed20280cb..9d3fbfd2ae4 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -693,15 +693,15 @@ export class MimeTypeDisplayOrder { ) { this.order = [...new Set(initialValue)].map(pattern => ({ pattern, - matches: glob.parse(normalizeSlashes(pattern)) + matches: glob.parse(normalizeSlashes(pattern), { ignoreCase: true }) })); } /** - * Returns a sorted array of the input mimetypes. + * Returns a sorted array of the input mimeTypes. */ - public sort(mimetypes: Iterable): string[] { - const remaining = new Map(Iterable.map(mimetypes, m => [m, normalizeSlashes(m)])); + public sort(mimeTypes: Iterable): string[] { + const remaining = new Map(Iterable.map(mimeTypes, m => [m, normalizeSlashes(m)])); let sorted: string[] = []; for (const { matches } of this.order) { @@ -725,21 +725,21 @@ export class MimeTypeDisplayOrder { /** * Records that the user selected the given mimetype over the other - * possible mimetypes, prioritizing it for future reference. + * possible mimeTypes, prioritizing it for future reference. */ - public prioritize(chosenMimetype: string, otherMimetypes: readonly string[]) { + public prioritize(chosenMimetype: string, otherMimeTypes: readonly string[]) { const chosenIndex = this.findIndex(chosenMimetype); if (chosenIndex === -1) { // always first, nothing more to do - this.order.unshift({ pattern: chosenMimetype, matches: glob.parse(normalizeSlashes(chosenMimetype)) }); + this.order.unshift({ pattern: chosenMimetype, matches: glob.parse(normalizeSlashes(chosenMimetype), { ignoreCase: true }) }); return; } - // Get the other mimetypes that are before the chosenMimetype. Then, move + // Get the other mimeTypes that are before the chosenMimetype. Then, move // them after it, retaining order. - const uniqueIndicies = new Set(otherMimetypes.map(m => this.findIndex(m, chosenIndex))); - uniqueIndicies.delete(-1); - const otherIndices = Array.from(uniqueIndicies).sort(); + const uniqueIndices = new Set(otherMimeTypes.map(m => this.findIndex(m, chosenIndex))); + uniqueIndices.delete(-1); + const otherIndices = Array.from(uniqueIndices).sort(); this.order.splice(chosenIndex + 1, 0, ...otherIndices.map(i => this.order[i])); for (let oi = otherIndices.length - 1; oi >= 0; oi--) { @@ -954,11 +954,10 @@ export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, vie const filenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.include : (filter.filenamePattern as string | glob.IRelativePattern); const excludeFilenamePattern = isDocumentExcludePattern(filter.filenamePattern) ? filter.filenamePattern.exclude : undefined; - if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) { + if (glob.match(filenamePattern, basename(resource.fsPath), { ignoreCase: true })) { if (excludeFilenamePattern) { - if (glob.match(excludeFilenamePattern, basename(resource.fsPath).toLowerCase())) { + if (glob.match(excludeFilenamePattern, basename(resource.fsPath), { ignoreCase: true })) { // should exclude - return false; } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts index dc984e4e14d..3863545f528 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts @@ -72,7 +72,7 @@ export class NotebookOutputRendererInfo implements INotebookRendererInfo { this.displayName = descriptor.displayName; this.mimeTypes = descriptor.mimeTypes; - this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern)); + this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern, { ignoreCase: true })); this.hardDependencies = new DependencyList(descriptor.dependencies ?? Iterable.empty()); this.optionalDependencies = new DependencyList(descriptor.optionalDependencies ?? Iterable.empty()); this.messaging = descriptor.requiresMessaging ?? RendererMessagingSpec.Never; diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index ab14d89413d..7e88c69efc8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -77,15 +77,8 @@ export class NotebookProviderInfo { } static selectorMatches(selector: NotebookSelector, resource: URI): boolean { - if (typeof selector === 'string') { - // filenamePattern - if (glob.match(selector.toLowerCase(), basename(resource.fsPath).toLowerCase())) { - return true; - } - } - - if (glob.isRelativePattern(selector)) { - if (glob.match(selector, basename(resource.fsPath).toLowerCase())) { + if (typeof selector === 'string' || glob.isRelativePattern(selector)) { + if (glob.match(selector, basename(resource.fsPath), { ignoreCase: true })) { return true; } } @@ -97,9 +90,9 @@ export class NotebookProviderInfo { const filenamePattern = selector.include; const excludeFilenamePattern = selector.exclude; - if (glob.match(filenamePattern, basename(resource.fsPath).toLowerCase())) { + if (glob.match(filenamePattern, basename(resource.fsPath), { ignoreCase: true })) { if (excludeFilenamePattern) { - if (glob.match(excludeFilenamePattern, basename(resource.fsPath).toLowerCase())) { + if (glob.match(excludeFilenamePattern, basename(resource.fsPath), { ignoreCase: true })) { return false; } } diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index 81115126b50..b45646fe14b 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -164,7 +164,7 @@ export interface IEditorResolverService { ): IDisposable; /** - * Given an editor resolves it to the suitable ResolvedEitor based on user extensions, settings, and built-in editors + * Given an editor resolves it to the suitable ResolvedEditor based on user extensions, settings, and built-in editors * @param editor The editor to resolve * @param preferredGroup The group you want to open the editor in * @returns An EditorInputWithOptionsAndGroup if there is an available editor or a status of how to proceed @@ -220,6 +220,6 @@ export function globMatchesResource(globPattern: string | glob.IRelativePattern, } const matchOnPath = typeof globPattern === 'string' && globPattern.indexOf(posix.sep) >= 0; const target = matchOnPath ? `${resource.scheme}:${resource.path}` : basename(resource); - return glob.match(typeof globPattern === 'string' ? globPattern.toLowerCase() : globPattern, target.toLowerCase()); + return glob.match(globPattern, target, { ignoreCase: true }); } //#endregion diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 7aa12c81fa2..c48db3f7a2f 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -192,8 +192,7 @@ export class LabelService extends Disposable implements ILabelService { continue; } - if ( - match(formatter.authority.toLowerCase(), resource.authority.toLowerCase()) && + if (match(formatter.authority, resource.authority, { ignoreCase: true }) && ( !bestResult || !bestResult.authority || From dc0a2870bfe276d5f475c21cdea79b2ead84b971 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 09:15:45 +0100 Subject: [PATCH 1915/4355] eng - track the new `chatUsageWidget` (#274269) --- .github/CODENOTIFY | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 2bc747df423..efcf992f7cc 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -102,6 +102,8 @@ src/vs/workbench/contrib/files/** @bpasero src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/chat/browser/chatSetup.ts @bpasero src/vs/workbench/contrib/chat/browser/chatStatus.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css @bpasero src/vs/workbench/contrib/chat/browser/agentSessions/** @bpasero src/vs/workbench/contrib/chat/browser/chatSessions/** @bpasero src/vs/workbench/contrib/localization/** @TylerLeonhardt From 2b76aa1113b3d55a9a3c6196d210c2122fa66c9c Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 31 Oct 2025 01:23:04 -0700 Subject: [PATCH 1916/4355] Add `build` folder tests to CI (#272564) --- .github/workflows/pr.yml | 2 +- build/azure-pipelines/product-compile.yml | 2 +- package.json | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6143914601f..07186308186 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -80,7 +80,7 @@ jobs: run: .github/workflows/check-clean-git-state.sh - name: Compile & Hygiene - run: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check + run: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index c02c4d20dbd..e025e84f911 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -98,7 +98,7 @@ jobs: - template: common/install-builtin-extensions.yml@self - - script: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check + - script: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Compile & Hygiene diff --git a/package.json b/package.json index 8141de41fb8..739ec4a6bc7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test-browser-no-install": "node test/unit/browser/index.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", "test-extension": "vscode-test", + "test-build-scripts": "cd build && npm run test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "node ./node_modules/gulp/bin/gulp.js compile", From cf34c7541cdb15f8c60c57ac9e7930596598c3c2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:24:31 +0000 Subject: [PATCH 1917/4355] Git - wire up event for changing artifact groups (#274261) * Git - wire up event for changing artifact groups * Remove code that was commented out * Swap code order * Dispose listener * Dispose event emitter as well --- extensions/git/src/artifactProvider.ts | 16 ++++- extensions/git/src/repository.ts | 8 +-- src/vs/workbench/api/browser/mainThreadSCM.ts | 25 +++++--- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostSCM.ts | 6 +- .../scm/browser/scmRepositoriesViewPane.ts | 60 ++++++++++--------- .../workbench/contrib/scm/common/artifact.ts | 2 + .../vscode.proposed.scmArtifactProvider.d.ts | 2 +- 8 files changed, 77 insertions(+), 44 deletions(-) diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index 355a8641b9d..147731dd000 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -8,8 +8,8 @@ import { dispose, IDisposable } from './util'; import { Repository } from './repository'; export class GitArtifactProvider implements SourceControlArtifactProvider, IDisposable { - private readonly _onDidChangeArtifacts = new EventEmitter(); - readonly onDidChangeArtifacts: Event = this._onDidChangeArtifacts.event; + private readonly _onDidChangeArtifacts = new EventEmitter(); + readonly onDidChangeArtifacts: Event = this._onDidChangeArtifacts.event; private readonly _groups: SourceControlArtifactGroup[]; private readonly _disposables: Disposable[] = []; @@ -24,6 +24,18 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp ]; this._disposables.push(this._onDidChangeArtifacts); + this._disposables.push(repository.historyProvider.onDidChangeHistoryItemRefs(e => { + const groups = new Set(); + for (const ref of e.added.concat(e.modified).concat(e.removed)) { + if (ref.id.startsWith('refs/heads/')) { + groups.add('branches'); + } else if (ref.id.startsWith('refs/tags/')) { + groups.add('tags'); + } + } + + this._onDidChangeArtifacts.fire(Array.from(groups)); + })); } provideArtifactGroups(): SourceControlArtifactGroup[] { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4180f0a5c70..07c6d88e57e 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -956,14 +956,14 @@ export class Repository implements Disposable { this._sourceControl.quickDiffProvider = this; this._sourceControl.secondaryQuickDiffProvider = new StagedResourceQuickDiffProvider(this, logger); - this._artifactProvider = new GitArtifactProvider(this, logger); - this._sourceControl.artifactProvider = this._artifactProvider; - this.disposables.push(this._artifactProvider); - this._historyProvider = new GitHistoryProvider(historyItemDetailProviderRegistry, this, logger); this._sourceControl.historyProvider = this._historyProvider; this.disposables.push(this._historyProvider); + this._artifactProvider = new GitArtifactProvider(this, logger); + this._sourceControl.artifactProvider = this._artifactProvider; + this.disposables.push(this._artifactProvider); + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index ce1cd1578ba..48676ac081d 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -173,7 +173,14 @@ class MainThreadSCMResource implements ISCMResource { } class MainThreadSCMArtifactProvider implements ISCMArtifactProvider { - constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } + private readonly _onDidChangeArtifacts = new Emitter(); + readonly onDidChangeArtifacts = this._onDidChangeArtifacts.event; + + private readonly _disposables = new DisposableStore(); + + constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { + this._disposables.add(this._onDidChangeArtifacts); + } async provideArtifactGroups(token?: CancellationToken): Promise { const artifactGroups = await this.proxy.$provideArtifactGroups(this.handle, token ?? CancellationToken.None); @@ -184,8 +191,12 @@ class MainThreadSCMArtifactProvider implements ISCMArtifactProvider { return this.proxy.$provideArtifacts(this.handle, group, token ?? CancellationToken.None); } - $onDidChangeArtifacts(group: string): void { - throw new Error('Method not implemented.'); + $onDidChangeArtifacts(groups: string[]): void { + this._onDidChangeArtifacts.fire(groups); + } + + dispose(): void { + this._disposables.dispose(); } } @@ -564,13 +575,13 @@ class MainThreadSCMProvider implements ISCMProvider { provider.$onDidChangeHistoryItemRefs(historyItemRefs); } - $onDidChangeArtifacts(group: string): void { + $onDidChangeArtifacts(groups: string[]): void { const provider = this.artifactProvider.get(); if (!provider) { return; } - provider.$onDidChangeArtifacts(group); + provider.$onDidChangeArtifacts(groups); } toJSON() { @@ -831,7 +842,7 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$onDidChangeHistoryProviderHistoryItemRefs(historyItemRefs); } - async $onDidChangeArtifacts(sourceControlHandle: number, group: string): Promise { + async $onDidChangeArtifacts(sourceControlHandle: number, groups: string[]): Promise { await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); @@ -840,6 +851,6 @@ export class MainThreadSCM implements MainThreadSCMShape { } const provider = repository.provider as MainThreadSCMProvider; - provider.$onDidChangeArtifacts(group); + provider.$onDidChangeArtifacts(groups); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e5ffa9ab32a..8d0f687cf48 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1728,7 +1728,7 @@ export interface MainThreadSCMShape extends IDisposable { $onDidChangeHistoryProviderCurrentHistoryItemRefs(sourceControlHandle: number, historyItemRef?: SCMHistoryItemRefDto, historyItemRemoteRef?: SCMHistoryItemRefDto, historyItemBaseRef?: SCMHistoryItemRefDto): Promise; $onDidChangeHistoryProviderHistoryItemRefs(sourceControlHandle: number, historyItemRefs: SCMHistoryItemRefsChangeEventDto): Promise; - $onDidChangeArtifacts(sourceControlHandle: number, group: string): Promise; + $onDidChangeArtifacts(sourceControlHandle: number, groups: string[]): Promise; } export interface MainThreadQuickDiffShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 7d97fa52826..a295f70344c 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -687,8 +687,10 @@ class ExtHostSourceControl implements vscode.SourceControl { this.#proxy.$updateSourceControl(this.handle, { hasArtifactProvider: !!artifactProvider }); if (artifactProvider) { - this._artifactProviderDisposable.value.add(artifactProvider.onDidChangeArtifacts((group: string) => { - this.#proxy.$onDidChangeArtifacts(this.handle, group); + this._artifactProviderDisposable.value.add(artifactProvider.onDidChangeArtifacts((groups: string[]) => { + if (groups.length !== 0) { + this.#proxy.$onDidChangeArtifacts(this.handle, groups); + } })); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index eca40f08967..b34fc848e14 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -16,7 +16,7 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { combinedDisposable, Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IViewDescriptorService } from '../../../common/views.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; @@ -27,7 +27,7 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; -import { autorun, IObservable, observableFromEvent, observableSignalFromEvent, runOnChange } from '../../../../base/common/observable.js'; +import { autorun, IObservable, observableSignalFromEvent, runOnChange } from '../../../../base/common/observable.js'; import { Sequencer } from '../../../../base/common/async.js'; import { SCMArtifactGroupTreeElement, SCMArtifactTreeElement } from '../common/artifact.js'; import { FuzzyScore } from '../../../../base/common/fuzzyScorer.js'; @@ -262,6 +262,7 @@ export class SCMRepositoriesViewPane extends ViewPane { private readonly providerCountBadgeObs: IObservable<'hidden' | 'auto' | 'visible'>; private readonly visibilityDisposables = new DisposableStore(); + private readonly repositoryDisposables = new DisposableMap(); constructor( options: IViewPaneOptions, @@ -319,31 +320,6 @@ export class SCMRepositoriesViewPane extends ViewPane { this.updateBodySize(this.tree.contentHeight); })); - // Update tree (add/remove repositories) - const addedRepositoryObs = observableFromEvent( - this, this.scmService.onDidAddRepository, e => e); - - const removedRepositoryObs = observableFromEvent( - this, this.scmService.onDidRemoveRepository, e => e); - - this.visibilityDisposables.add(autorun(async reader => { - const addedRepository = addedRepositoryObs.read(reader); - const removedRepository = removedRepositoryObs.read(reader); - - if (addedRepository === undefined && removedRepository === undefined) { - await this.updateChildren(); - return; - } - - if (addedRepository) { - await this.updateRepository(addedRepository); - } - - if (removedRepository) { - await this.updateRepository(removedRepository); - } - })); - // Update tree selection const onDidChangeVisibleRepositoriesSignal = observableSignalFromEvent( this, this.scmViewService.onDidChangeVisibleRepositories); @@ -352,6 +328,13 @@ export class SCMRepositoriesViewPane extends ViewPane { onDidChangeVisibleRepositoriesSignal.read(reader); await this.treeOperationSequencer.queue(() => this.updateTreeSelection()); })); + + // Add/Remove event handlers + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.visibilityDisposables); + this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.visibilityDisposables); + for (const repository of this.scmService.repositories) { + this.onDidAddRepository(repository); + } }); }, this, this._store); } @@ -438,6 +421,29 @@ export class SCMRepositoriesViewPane extends ViewPane { this._register(this.tree.onDidChangeContentHeight(this.onTreeContentHeightChange, this)); } + private async onDidAddRepository(repository: ISCMRepository): Promise { + const disposables = new DisposableStore(); + + disposables.add(autorun(async reader => { + const artifactsProvider = repository.provider.artifactProvider.read(reader); + if (!artifactsProvider) { + return; + } + + reader.store.add(artifactsProvider.onDidChangeArtifacts(async groups => { + await this.updateRepository(repository); + })); + })); + + await this.updateRepository(repository); + this.repositoryDisposables.set(repository, disposables); + } + + private async onDidRemoveRepository(repository: ISCMRepository): Promise { + await this.updateRepository(repository); + this.repositoryDisposables.deleteAndDispose(repository); + } + private onTreeContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { return; diff --git a/src/vs/workbench/contrib/scm/common/artifact.ts b/src/vs/workbench/contrib/scm/common/artifact.ts index de2b5ec2f60..579a3240b8d 100644 --- a/src/vs/workbench/contrib/scm/common/artifact.ts +++ b/src/vs/workbench/contrib/scm/common/artifact.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from '../../../../base/common/uri.js'; +import { Event } from '../../../../base/common/event.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { ISCMRepository } from './scm.js'; export interface ISCMArtifactProvider { + readonly onDidChangeArtifacts: Event; provideArtifactGroups(): Promise; provideArtifacts(group: string): Promise; } diff --git a/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts index fcef4a44c43..279a3aa91f7 100644 --- a/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts @@ -11,7 +11,7 @@ declare module 'vscode' { } export interface SourceControlArtifactProvider { - readonly onDidChangeArtifacts: Event; + readonly onDidChangeArtifacts: Event; provideArtifactGroups(token: CancellationToken): ProviderResult; provideArtifacts(group: string, token: CancellationToken): ProviderResult; From 4249ae4bc05d29b72a38bb467dc0ca97e73bcd93 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 09:50:47 +0100 Subject: [PATCH 1918/4355] Chat: "Pick Model" appears with empty picker if aborting chat setup (fix #272829) (#274272) * Chat: "Pick Model" appears with empty picker if aborting chat setup (fix #272829) * Update src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../browser/modelPicker/modelPickerActionItem.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts index 2d9bc62efb0..109ea2d9aae 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts @@ -84,7 +84,7 @@ function getModelPickerActionBarActionProvider(commandService: ICommandService, id: 'manageModels', label: localize('chat.manageModels', "Manage Models..."), enabled: true, - tooltip: localize('chat.manageModels.tooltip', "Manage language models"), + tooltip: localize('chat.manageModels.tooltip', "Manage Language Models"), class: undefined, run: () => { const commandId = ManageModelsAction.ID; @@ -93,16 +93,17 @@ function getModelPickerActionBarActionProvider(commandService: ICommandService, }); } - // Add sign-in / upgrade option if entitlement is anonymous / free - if (chatEntitlementService.anonymous || chatEntitlementService.entitlement === ChatEntitlement.Free) { + // Add sign-in / upgrade option if entitlement is anonymous / free / new user + const isNewOrAnonymousUser = !chatEntitlementService.sentiment.installed || chatEntitlementService.entitlement === ChatEntitlement.Available || chatEntitlementService.anonymous; + if (isNewOrAnonymousUser || chatEntitlementService.entitlement === ChatEntitlement.Free) { additionalActions.push({ id: 'moreModels', - label: localize('chat.moreModels', "Add Premium Models"), + label: isNewOrAnonymousUser ? localize('chat.moreModels', "Add Language Models") : localize('chat.morePremiumModels', "Add Premium Models"), enabled: true, - tooltip: localize('chat.moreModels.tooltip', "Add premium models"), + tooltip: isNewOrAnonymousUser ? localize('chat.moreModels.tooltip', "Add Language Models") : localize('chat.morePremiumModels.tooltip', "Add Premium Models"), class: undefined, run: () => { - const commandId = chatEntitlementService.anonymous ? 'workbench.action.chat.triggerSetup' : 'workbench.action.chat.upgradePlan'; + const commandId = isNewOrAnonymousUser ? 'workbench.action.chat.triggerSetup' : 'workbench.action.chat.upgradePlan'; commandService.executeCommand(commandId); } }); From cd23f513b2dfcd33db78995e399c23785fbef00e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 09:56:54 +0100 Subject: [PATCH 1919/4355] docs - update tools to use `github/*` prefixes (#274278) --- .github/prompts/fixIssueNo.prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/prompts/fixIssueNo.prompt.md b/.github/prompts/fixIssueNo.prompt.md index 0184204d3b4..22130f14047 100644 --- a/.github/prompts/fixIssueNo.prompt.md +++ b/.github/prompts/fixIssueNo.prompt.md @@ -1,6 +1,6 @@ --- agent: Plan -tools: ['runCommands', 'runTasks', 'runNotebooks', 'search', 'new', 'usages', 'vscodeAPI', 'problems', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests', 'get_issue', 'get_issue_comments', 'get_me', 'get_pull_request', 'get_pull_request_diff', 'get_pull_request_files'] +tools: ['runCommands', 'runTasks', 'runNotebooks', 'search', 'new', 'usages', 'vscodeAPI', 'problems', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests', 'github/get_issue', 'github/get_issue_comments', 'github/get_me', 'github/get_pull_request', 'github/get_pull_request_diff', 'github/get_pull_request_files'] --- The user has given you a Github issue number. Use the `get_issue` to retrieve its details. Understand the issue and propose a solution to solve it. From fdee3172367b92693b4fb4012e1c71f1288647d6 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 31 Oct 2025 02:13:32 -0700 Subject: [PATCH 1920/4355] Use case-insensitive glob in extension recommendations (#274230) --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index cf50a4f8c55..fa687116c37 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -187,9 +187,9 @@ export class FileBasedRecommendations extends ExtensionRecommendations { } } - if ((condition).pathGlob) { - const pathGlob = (condition).pathGlob; - if (processedPathGlobs.get(pathGlob) ?? match((condition).pathGlob, uri.with({ fragment: '' }).toString())) { + const pathGlob = (condition).pathGlob; + if (pathGlob) { + if (processedPathGlobs.get(pathGlob) ?? match(pathGlob, uri.with({ fragment: '' }).toString(), { ignoreCase: true })) { pathGlobMatched = true; } processedPathGlobs.set(pathGlob, pathGlobMatched); From 5b7222a09ec50a3329eb95dadfb52d727ed9a49a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 31 Oct 2025 10:17:54 +0100 Subject: [PATCH 1921/4355] add codenotify --- .github/CODENOTIFY | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index efcf992f7cc..2c0c8a18907 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -70,6 +70,7 @@ src/vs/workbench/services/chat/** @bpasero src/vs/workbench/services/contextmenu/** @bpasero src/vs/workbench/services/dialogs/** @alexr00 @bpasero src/vs/workbench/services/editor/** @bpasero +src/vs/workbench/services/editor/common/customEditorLabelService.ts @benibenj src/vs/workbench/services/environment/** @bpasero src/vs/workbench/services/files/** @bpasero src/vs/workbench/services/filesConfiguration/** @bpasero From f37adabc1d5b4cda51b65dcc4a6b4cc2372e5701 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 31 Oct 2025 02:23:50 -0700 Subject: [PATCH 1922/4355] Make `workbench.editor.customLabels.patterns` setting case-insensitive (#274280) --- .../services/editor/common/customEditorLabelService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/editor/common/customEditorLabelService.ts b/src/vs/workbench/services/editor/common/customEditorLabelService.ts index bf7692f7589..2cf1daee3c8 100644 --- a/src/vs/workbench/services/editor/common/customEditorLabelService.ts +++ b/src/vs/workbench/services/editor/common/customEditorLabelService.ts @@ -90,7 +90,7 @@ export class CustomEditorLabelService extends Disposable implements ICustomEdito } const isAbsolutePath = isAbsolute(pattern); - const parsedPattern = parseGlob(pattern); + const parsedPattern = parseGlob(pattern, { ignoreCase: true }); this.patterns.push({ pattern, template, isAbsolutePath, parsedPattern }); } From 06b63c6ff9493ed5a2acf55d2d5206a3a8b22dd0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 31 Oct 2025 10:36:26 +0100 Subject: [PATCH 1923/4355] fix #274281 (#274295) --- .../chat/browser/chatManagement/chatManagement.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts index 381f84e8424..54adc68fa4d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts @@ -102,7 +102,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: MANAGE_CHAT_COMMAND_ID, - title: localize2('openAiManagement', "Manage Language Models"), + title: localize2('openAiManagement', "Manage Language Models (Preview)"), category: CHAT_CATEGORY, precondition: ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ChatContextKeys.enabled, ContextKeyExpr.or( ChatContextKeys.Entitlement.planFree, From 06f00fada273786210879715fd9e47e899b05251 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:48:18 +0000 Subject: [PATCH 1924/4355] Add progress indication when loading language models (#274289) * Initial plan * Add progress indication when loading language models Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> * remove progress service from editor * remove trailing comma --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> Co-authored-by: Sandeep Somavarapu --- .../browser/chatManagement/chatModelsWidget.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index ac98be013be..5ffa1511561 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -36,6 +36,7 @@ import { AnchorAlignment } from '../../../../../base/browser/ui/contextview/cont import { ToolBar } from '../../../../../base/browser/ui/toolbar/toolbar.js'; import { preferencesClearInputIcon } from '../../../preferences/browser/preferencesIcons.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IEditorProgressService } from '../../../../../platform/progress/common/progress.js'; const $ = DOM.$; @@ -673,9 +674,10 @@ export class ChatModelsWidget extends Disposable { constructor( @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, + @IExtensionService private readonly extensionService: IExtensionService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + @IEditorProgressService private readonly editorProgressService: IEditorProgressService, ) { super(); @@ -684,10 +686,14 @@ export class ChatModelsWidget extends Disposable { this.element = DOM.$('.models-widget'); this.create(this.element); - extensionService.whenInstalledExtensionsRegistered().then(async () => { + const loadingPromise = this.extensionService.whenInstalledExtensionsRegistered().then(async () => { await this.viewModel.resolve(); this.refreshTable(); }); + + // Show progress indicator while loading models + this.editorProgressService.showWhile(loadingPromise, 300); + this._register(this.viewModel.onDidChangeModelEntries(() => this.refreshTable())); } @@ -966,7 +972,7 @@ export class ChatModelsWidget extends Disposable { } public async refresh(): Promise { - await this.viewModel.resolve(); - this.refreshTable(); + const refreshPromise = this.viewModel.resolve().then(() => this.refreshTable()); + await this.editorProgressService.showWhile(refreshPromise, 300); } } From 9e00493a2d2a2188f79ebb14c134e7076d5c1657 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Oct 2025 02:49:40 -0700 Subject: [PATCH 1925/4355] Remove finalized terminal completions proposal from package.json --- extensions/terminal-suggest/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index cbb5f54de6b..583fd20098e 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -14,7 +14,6 @@ "Other" ], "enabledApiProposals": [ - "terminalCompletionProvider", "terminalShellEnv" ], "contributes": { From a2235b918540b553b72af286da5875e6edbedf11 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 11:30:29 +0100 Subject: [PATCH 1926/4355] chat - fix input border when working sets or todo are visible (#274304) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 56f43e39750..d5842fd663d 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -756,6 +756,13 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } +.interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container, +.interactive-input-part:has(.chat-todo-list-widget-container > .chat-todo-list-widget.has-todos) .chat-input-container { + /* Remove top border radius when editing session or todo list is present (TODO@bpasero rewrite chat input widget and then do this properly without :has() */ + border-top-left-radius: 0; + border-top-right-radius: 0; +} + .interactive-session .chat-editing-session { margin-bottom: -4px; /* Counter the flex gap */ /* reset the 4px gap of the container for editing sessions */ @@ -1020,7 +1027,6 @@ have to be updated for changes to the rules above, or to support more deeply nes border-color: transparent; color: var(--vscode-foreground); cursor: pointer; - height: 16px; padding: 0px; border-radius: 2px; display: inline-flex; From cdd355cc35450d477ccb3820d42d24e97acf6fc6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 11:38:24 +0100 Subject: [PATCH 1927/4355] Chat: Title of desktop notification should be static: "Copilot" or "Visual Studio Code" (fix #274298) (#274305) --- .../chat/browser/chatAccessibilityService.ts | 16 ++++++-------- .../chatConfirmationWidget.ts | 21 ++++++++----------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 18bb6b2583a..50a15709b2d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -108,16 +108,12 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi this.notifications.delete(ds); } - const chatTitle = widget.viewModel?.model.title || localize('chat.untitledChat', "Untitled Chat"); - const maxDetailLength = 100; - const truncatedResponse = responseContent.length > maxDetailLength - ? responseContent.substring(0, maxDetailLength) + '...' - : responseContent; - const notification = await dom.triggerNotification(chatTitle, { - detail: truncatedResponse, - sticky: false, - }); - + const title = widget?.viewModel?.model.title ? localize('chatTitle', "Chat: {0}", widget.viewModel.model.title) : localize('chat.untitledChat', "Untitled Chat"); + const notification = await dom.triggerNotification(title, + { + detail: localize('notificationDetail', "New chat response.") + } + ); if (!notification) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts index 725ff39e21d..4bad64ddf13 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts @@ -5,7 +5,7 @@ import './media/chatConfirmationWidget.css'; import * as dom from '../../../../../base/browser/dom.js'; -import { IRenderedMarkdown, renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js'; +import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { Button, ButtonWithDropdown, IButton, IButtonOptions } from '../../../../../base/browser/ui/button/button.js'; import { Action, Separator } from '../../../../../base/common/actions.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; @@ -118,16 +118,17 @@ class ChatConfirmationNotifier extends Disposable { super(); } - async notify(targetWindow: Window, title: string | IMarkdownString, sessionId: string): Promise { + async notify(targetWindow: Window, sessionId: string): Promise { // Focus Window this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); // Notify - const titleText = renderAsPlaintext(title); - const notification = await dom.triggerNotification(titleText ? localize('notificationTitle', "Chat: {0}", titleText) : localize('defaultTitle', "Chat: Confirmation Required"), + const widget = this._chatWidgetService.getWidgetBySessionId(sessionId); + const title = widget?.viewModel?.model.title ? localize('chatTitle', "Chat: {0}", widget.viewModel.model.title) : localize('chat.untitledChat', "Untitled Chat"); + const notification = await dom.triggerNotification(title, { - detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed.") + detail: localize('notificationDetail', "Approval needed to continue.") } ); if (notification) { @@ -136,7 +137,7 @@ class ChatConfirmationNotifier extends Disposable { disposables.add(Event.once(notification.onClick)(async () => { await this._hostService.focus(targetWindow, { mode: FocusMode.Force }); - const widget = this._chatWidgetService.getWidgetBySessionId(sessionId); + if (widget) { await this._instantiationService.invokeFunction(showChatWidgetInViewOrEditor, widget); widget.focusInput(); @@ -174,7 +175,6 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { } private readonly messageElement: HTMLElement; - private readonly title: string | IMarkdownString; private readonly silent: boolean; private readonly notificationManager: ChatConfirmationNotifier; @@ -191,7 +191,6 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { super(); const { title, subtitle, message, buttons, silent } = options; - this.title = title; this.silent = !!silent; this.notificationManager = this._register(instantiationService.createInstance(ChatConfirmationNotifier)); @@ -286,7 +285,7 @@ abstract class BaseSimpleChatConfirmationWidget extends Disposable { if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation') && !this.silent) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { - this.notificationManager.notify(targetWindow, this.title, this.context.element.sessionId); + this.notificationManager.notify(targetWindow, this.context.element.sessionId); } } } @@ -352,7 +351,6 @@ abstract class BaseChatConfirmationWidget extends Disposable { } private readonly messageElement: HTMLElement; - private readonly title: string | IMarkdownString; private readonly notificationManager: ChatConfirmationNotifier; @@ -368,7 +366,6 @@ abstract class BaseChatConfirmationWidget extends Disposable { super(); const { title, subtitle, message, buttons, icon } = options; - this.title = title; this.notificationManager = this._register(instantiationService.createInstance(ChatConfirmationNotifier)); @@ -484,7 +481,7 @@ abstract class BaseChatConfirmationWidget extends Disposable { if (this.showingButtons && this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { const targetWindow = dom.getWindow(listContainer); if (!targetWindow.document.hasFocus()) { - this.notificationManager.notify(targetWindow, this.title, this._context.element.sessionId); + this.notificationManager.notify(targetWindow, this._context.element.sessionId); } } } From ad9005ae1d3330fc8fe3a4a0cc790254d6f70a1d Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 31 Oct 2025 10:43:02 +0000 Subject: [PATCH 1928/4355] codicons: refresh codicon.ttf Refresh the codicon TrueType font binary to include recent glyph updates and icon renames. --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 121056 -> 121072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4bd23249453c03ff5acd050027898932837b0149..cb1bfd2f44e4c8c3ac1d241420e5d116c4adcbd5 100644 GIT binary patch delta 2141 zcma)43rtg48a}t}?Y+H}+Q;n$+ESr~wv<|GX-h?Mu=Rn64gwZLsK`r1zz!4#8EjEt z2a!b{S{-FZL`3Z13<^R;bWlfzn8}Rm64p#Mh9zdg8Zv9#iJ35Fv-g5-%p@k8oO|wn zPX5PtzVDm4hP=3jw8pa|u8z+Epa}y|i>@unNVhgzZ>yK{(g1*da~!B}p5&)S0cb3# z&8K7Rb38kHZT?548w+vOG&F_Dwq@89Q;CRkv?AT3dsn?LZ@Y3c|sT? z?ECN>1zVA<7*f1ahA7R-?^RsYctldf6SY*Gscu#esUNH7Bk@RmWKradrdcz#g1w@1 z#ez0jdnZa4RTb42^+Ko6rRjQfKd)4+tXkQtPtXs$qiNBF(LK?x3M`FTO+5+RgvQPBo1Y|BCC+Xk zww&4Go=M70dc4)Xbzxicw*P$C`r+&3s^pj3ZQFe*q7+}MCbcAuomRgC-BGq<+D5ZE zZ3F3&^v?8OGTJll?d0q%-Z`FW$UK=jze~EyzN&GGJ0?`hvNpNr?l=9cA7=F##J^J?4)kP2YY4$mbHx}0xkM1Y-x9=Y<;g)2T^p~Qg38nR=-qJ^9XqlzVb3l5a z_`sxt;V?VO9A7zJf0XjkM0r^GK>0#NLPd|$E1w^H__6up;VQJM@DO&$ z-C7N*i>f=TXFkz<;;muUBvoY09V18i~g4W3pqOCT3G@)6?Uz$34eqn`4{D|J>Mu zwm4hFV5g@@gm?t|S^7vnEJ z=~4H1E-5Y*UYhO=?CriBa=EXM*;m--yJET0>qWiEUaxoI%hrCp|7ib{t07mbt}cA# zF8iAHb>p?hLBrsg!LcFLQ0>s<4cCp)8!v}bh8u_HMruc9eZVL2nSC~2m2c{%?q>IB z*y!{v)h*vx_ifAV(K}6droJ(MGddnPUOhg0S9N#b?yvVu_kOr9z2AC&^4szU>Id}` zV4`~B@ppj$00#L1NCb%h8w3I6pg|L@rjy5xV%v5%2MUI8;-*SPgHgIlMMq(3e;X%- z2DZU`%psKF*gDTLf>`F^5=5^D_47kLD2DxIv4R#6@rP=0TVMza!(~DT#=lN*-&P8! zDnAILb^uYZ24mB+nKB9+7f65xtN|cgh%+N0DYP2WuZ7|ug90J{9XGRsbCaSlWU%4oeQ4hkTI%F|r~ zkkG9sQ=;|6;-h4W#WLcb$Vvp;>W}08l^j7Y3)xDi>*yF#iLfU5%lWA+F2f-VW}#^? zJqSkNAUga`V5Lsvmg>gHE&|SFs?B8tBRkOD2#Zx<=S%r4M2a865I-(M^oQ~wIGt>O zz%a=@+cU_1!#%}(()+moM>o!(r0BStEqr5`_lXcr(yO3^P>>|A`+vmvUv@Zy3Bio; z_b5Z7+(O!9@YqGHjB@X34-bmp{6HX)2% z41wfF9-GBt^Sbu#Wpla3_5dY;v3OE`9TOi}&WC=NxxoVX9q35S4QnaY0^~C0lH`;T zDFbLBxr_thQp%h$)RmA!lW8Fo z8vO_h#n5F4W^Lf$IH#bTg9%uIJ)xm`tCh<`FjxR191ICXFmhoC7elNF>&ABfAOQwJ zbQ+ybHbFT;2ZGfKIAjY4{gQ^x5B NJSc1A_7zAT^>o~a_>zpG2~qmAV3lW0zxDSj}VX&M2eUqAR z{?6x|@AtQI4SRA8>quZW|M0UNfKWMrHvi5v4OP_A*4ZrMBmn^5m=9GQ>E>pJ1307W zoKn)pTE{1@E&V0XN*7g%?BXzSyLY|!ijU3bM_;CIoA0tDLGsYA&ObYV9`H!&kR{70 z^7-{f1+FkCMis9EC4rHF^Fgej*$t^1ehT&p&IxV{9u0mN{4#_aq6{exc^ujnI=vC! zXy5oI%n&vc9u{5`emQ(uDOYAF`<2f&DK^z?x)_lh;k2rN%B1R7y;eu4E7Y$wYE83d zInogMN#v_2M|4f}Xbe9lA?9Mt3$0q)8k-tB9Q$0Cp_`2hj%$qjSG*;DWpi!oAp^*aoSJY3%0KrI^L(e-<=N9>(YPSk-K9iLy|F*smwHIv9nrtGIv()T+BwZ zE!oap{=52iy~^pyxwo6UyJGj9-00jdb656A_Y~|I%=5}C$xqF9<}VqA#xmpLUUYBG z-s!z-`{MTv?+@GGvwx+)t02ChvS6VQEi@F?7Y7j~43ud}mXRb7l9tMZA56@QyRXQuzs?w?)N3=&A)spJ;>f!3;ng<{1KAg5NEu|l^ zKC*V#(rU|U`)Z$kto(Sm4zDxSIqQBsnsBu7=-e^%nDN+SE7NMI2lb`(Qw`dNmyNYe z+@_?a#-{0JOY>}txaDliqn4#ll0VJ<^hK+<_3~$m&+Kh@TXWmfsak9=v@9H_luvcUD_^3H^19*QhCyL@_tWHPgc+3m&UIs zUp=*_*k?~EPMJ?lpISYgcY5he!I_1#S?46@($6*bmGuqxJvkqAzU2I^etCaI|H6gn z3lA?!FWN3iE*UR9xXiw6zv6YJ@5-9P=olH$4xAkXgDHdkgRj1B9byjE4n4f;b=7?J z)v&dAc=en5YxQHHV;y5or@zzUoExthA02-=kvdU7@nq66xj6Y|ia({CGEAAL7On?f zw@>>_Ke*w4V`Rqut@hi|*~Zz0@08z-&avkz=N51J-*ntux)pZo-fiLS*4uM;O7BYV z*4=$`ukzmf_iO+Fjd%eRLor|i5eOVphpK|99?+w!G<(NpDe4RP!g0Brf}m8njEW&t zxtxk%RGFNLB6?pz1LDaras9XqZ3~OfXSJ|+ysj1u$1!r4@A^lUU_F9)H3)p^3_@jM zc%I=ABDa$5R*_OLrruSEN{MX&8~A`wpaUREz(+%%1jfRMco+xO3M?X0-E}?R*XX7q z)+05me?rKlA_z5;h9I;u0>iyc&mVjI?N1bm2!;?(xto}jN`HgK#-fdXjm#0ljerAW zAO>s&MFfl@1WHj%;KGR{XaX^kPO2cLkpzh!6beXSOrs8mVK4;7liWgL`K`{SA~d}t zBT&#)(Mz}9XLuZ73m_D*o7h6ShX=ih91hSZa%wbAXUQ=R!-Q}=^y|DBa$Y`4+eV&@ z7$8dXLFlLt`RRkw5n{5&5VCKug+g{8lkOy{U^8?Ns+>xLbvOq(&11xC{JIi8k6Z#?-nkAPq?SFBT9tp;0SH4`fo5FN}o(zA%EglCGQi0_*>>CV;r{ z0Fs)VX#XFJepd&O)&~4Xy8y)S!oRO{1bHhH|4Ma-B@3 zOV$#d&_j5j^p>dORDKc>1);kwLTDSj#4=rq4wd;wNonY0uLhQhty~}nslWg-2p$O} zI*UaiBNV72F#^d95o^DvN+tO1GBxh-_(GSE2pD03t2xN{5v!U&fl31*g5W&SI-H;oQJQgdg~0w z)jE3eXFZNpVKg4X!59pTW76wQ9M0t5(_vc?0Xvq;5#a97GEl0(AnOe v)hH)G#Hr?Zc@cwX$`fZWa8Fxa9`5NmFOc%Ha1L7S>CL7yCVv=!>%;#I)hJEZ From 881d3b8d62f9b619495ca0f98611fb418cd64249 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:58:50 +0000 Subject: [PATCH 1929/4355] Git - add create branch/tag action to repository explorer (#274307) --- extensions/git/package.json | 14 ++++++ src/vs/workbench/contrib/scm/browser/menus.ts | 50 +++++++++++++++++++ .../scm/browser/scmRepositoriesViewPane.ts | 43 +++++++++------- src/vs/workbench/contrib/scm/browser/util.ts | 4 +- src/vs/workbench/contrib/scm/common/scm.ts | 4 +- 5 files changed, 94 insertions(+), 21 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index dc5ff357642..76843999334 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -496,6 +496,7 @@ { "command": "git.branch", "title": "%command.branch%", + "icon": "$(plus)", "category": "Git", "enablement": "!operationInProgress" }, @@ -550,6 +551,7 @@ { "command": "git.createTag", "title": "%command.createTag%", + "icon": "$(plus)", "category": "Git", "enablement": "!operationInProgress" }, @@ -1843,6 +1845,18 @@ "when": "scmProvider == git && scmProviderContext == worktree" } ], + "scm/artifactGroup/context": [ + { + "command": "git.branch", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == branches" + }, + { + "command": "git.createTag", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == tags" + } + ], "scm/artifact/context": [ { "command": "git.repositories.checkout", diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index f4732f57be1..6fda3db9b1b 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -15,6 +15,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { ISCMMenus, ISCMProvider, ISCMRepository, ISCMRepositoryMenus, ISCMResource, ISCMResourceGroup, ISCMService } from '../common/scm.js'; +import { ISCMArtifactGroup } from '../common/artifact.js'; function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; @@ -183,6 +184,9 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { private genericRepositoryContextMenu: IMenu | undefined; private contextualRepositoryContextMenus: Map | undefined; + private artifactGroupMenus: Map | undefined; + private artifactMenus: Map | undefined; + private readonly resourceGroupMenusItems = new Map(); private readonly disposables = new DisposableStore(); @@ -208,6 +212,52 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { this.onDidChangeResourceGroups(); } + getArtifactGroupMenu(artifactGroup: ISCMArtifactGroup): IMenu { + if (!this.artifactGroupMenus) { + this.artifactGroupMenus = new Map(); + } + + let item = this.artifactGroupMenus.get(artifactGroup.id); + + if (!item) { + const contextKeyService = this.contextKeyService.createOverlay([['scmArtifactGroup', artifactGroup.id]]); + const menu = this.menuService.createMenu(MenuId.SCMArtifactGroupContext, contextKeyService); + + item = { + menu, dispose() { + menu.dispose(); + } + }; + + this.artifactGroupMenus.set(artifactGroup.id, item); + } + + return item.menu; + } + + getArtifactMenu(artifactGroup: ISCMArtifactGroup): IMenu { + if (!this.artifactMenus) { + this.artifactMenus = new Map(); + } + + let item = this.artifactMenus.get(artifactGroup.id); + + if (!item) { + const contextKeyService = this.contextKeyService.createOverlay([['scmArtifactGroupId', artifactGroup.id]]); + const menu = this.menuService.createMenu(MenuId.SCMArtifactContext, contextKeyService); + + item = { + menu, dispose() { + menu.dispose(); + } + }; + + this.artifactMenus.set(artifactGroup.id, item); + } + + return item.menu; + } + getRepositoryMenu(repository: ISCMRepository): IMenu { const contextValue = repository.provider.contextValue.get(); if (typeof contextValue === 'undefined') { diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index b34fc848e14..307ae6c5579 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -8,7 +8,7 @@ import { localize } from '../../../../nls.js'; import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browser/ui/list/list.js'; -import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js'; +import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent, ITreeNode, ITreeRenderer, ITreeElementRenderDetails } from '../../../../base/browser/ui/tree/tree.js'; import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -21,7 +21,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IViewDescriptorService } from '../../../common/views.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRenderer.js'; -import { collectContextMenuActions, getActionViewItemProvider, isSCMArtifactGroupTreeElement, isSCMArtifactTreeElement, isSCMRepository } from './util.js'; +import { collectContextMenuActions, connectPrimaryMenu, getActionViewItemProvider, isSCMArtifactGroupTreeElement, isSCMArtifactTreeElement, isSCMRepository } from './util.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; @@ -37,7 +37,6 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement; @@ -63,6 +62,7 @@ class ListDelegate implements IListVirtualDelegate { interface ArtifactGroupTemplate { readonly label: IconLabel; readonly actionBar: WorkbenchToolBar; + readonly elementDisposables: DisposableStore; readonly templateDisposable: IDisposable; } @@ -77,6 +77,7 @@ class ArtifactGroupRenderer implements ITreeRenderer, index: number, templateData: ArtifactGroupTemplate): void { @@ -98,13 +99,15 @@ class ArtifactGroupRenderer implements ITreeRenderer { + templateData.actionBar.setActions(primary); + }, 'inline', provider)); + templateData.actionBar.context = artifactGroup; + } - templateData.actionBar.context = node.element.artifactGroup; - templateData.actionBar.setActions(getActionBarActions(actions, 'inline').primary); + disposeElement(element: ITreeNode, index: number, templateData: ArtifactGroupTemplate, details?: ITreeElementRenderDetails): void { + templateData.elementDisposables.clear(); } disposeTemplate(templateData: ArtifactGroupTemplate): void { @@ -115,6 +118,7 @@ class ArtifactGroupRenderer implements ITreeRenderer, index: number, templateData: ArtifactTemplate): void { @@ -152,13 +157,15 @@ class ArtifactRenderer implements ITreeRenderer { + templateData.actionBar.setActions(primary); + }, 'inline', provider)); + templateData.actionBar.context = artifact; + } - templateData.actionBar.context = node.element.artifact; - templateData.actionBar.setActions(getActionBarActions(actions, 'inline').primary); + disposeElement(element: ITreeNode, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void { + templateData.elementDisposables.clear(); } disposeTemplate(templateData: ArtifactTemplate): void { @@ -387,7 +394,7 @@ export class SCMRepositoriesViewPane extends ViewPane { compressionEnabled: compressionEnabled.get(), overrideStyles: this.getLocationBasedColors().listOverrideStyles, multipleSelectionSupport: this.scmViewService.selectionModeConfig.get() === 'multiple', - expandOnDoubleClick: false, + expandOnDoubleClick: true, expandOnlyOnTwistieClick: true, accessibilityProvider: { getAriaLabel(element: TreeElement): string { diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 7510400c264..1783c012ef4 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -82,12 +82,12 @@ const compareActions = (a: IAction, b: IAction) => { return a.id === b.id && a.enabled === b.enabled; }; -export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, primaryGroup?: string): IDisposable { +export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, primaryGroup?: string, arg?: unknown): IDisposable { let cachedPrimary: IAction[] = []; let cachedSecondary: IAction[] = []; const updateActions = () => { - const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true }), primaryGroup); + const { primary, secondary } = getActionBarActions(menu.getActions({ arg, shouldForwardArgs: true }), primaryGroup); if (equals(cachedPrimary, primary, compareActions) && equals(cachedSecondary, secondary, compareActions)) { return; diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index eb0ce02c857..91b578ed33b 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -16,7 +16,7 @@ import { ResourceTree } from '../../../../base/common/resourceTree.js'; import { ISCMHistoryProvider } from './history.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IObservable } from '../../../../base/common/observable.js'; -import { ISCMArtifactProvider } from './artifact.js'; +import { ISCMArtifact, ISCMArtifactGroup, ISCMArtifactProvider } from './artifact.js'; export const VIEWLET_ID = 'workbench.view.scm'; export const VIEW_PANE_ID = 'workbench.scm'; @@ -200,6 +200,8 @@ export interface ISCMRepositoryMenus { getResourceGroupMenu(group: ISCMResourceGroup): IMenu; getResourceMenu(resource: ISCMResource): IMenu; getResourceFolderMenu(group: ISCMResourceGroup): IMenu; + getArtifactGroupMenu(artifactGroup: ISCMArtifactGroup): IMenu; + getArtifactMenu(artifact: ISCMArtifact): IMenu; } export interface ISCMMenus { From 39da82e39f74283c1b2ef27f5b6c71236ead3715 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:59:35 -0700 Subject: [PATCH 1930/4355] fix todolist padding (#274312) --- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d5842fd663d..1f6a6654801 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -1093,7 +1093,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget { - padding: 6px 3px 6px 3px; + padding: 4px 3px 4px 3px; box-sizing: border-box; border: 1px solid var(--vscode-input-border, transparent); background-color: var(--vscode-editor-background); From 5301f61b4b9789ba51db5d2c2bd4423a186ee434 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Fri, 31 Oct 2025 11:09:44 +0000 Subject: [PATCH 1931/4355] extensions: standardize header metadata font-size to 11px and tighten codicon spacing --- .../contrib/extensions/browser/media/extension.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index 0b4d2dd3aa0..9bda6c319fc 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -92,17 +92,17 @@ } .extension-list-item > .details > .header-container > .header .extension-kind-indicator { - font-size: 80%; + font-size: 11px; margin-left: 4px; } .extension-list-item > .details > .header-container > .header > .install-count:not(:empty) { - font-size: 80%; + font-size: 11px; margin: 0 6px; } .extension-list-item > .details > .header-container > .header > .activation-status:not(:empty) { - font-size: 80%; + font-size: 11px; margin-left: 2px; } @@ -112,8 +112,7 @@ } .extension-list-item > .details > .header-container > .header .codicon { - font-size: 120%; - margin-right: 3px; + margin-right: 2px; -webkit-mask: inherit; } From 942fcbaa9fc511993534850bb4d2408a7c392c44 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Oct 2025 12:38:54 +0100 Subject: [PATCH 1932/4355] Remove chat.emptyChatState.enabled setting (fix #266074) (#274321) * Remove chat.emptyChatState.enabled setting (fix #266074) * . * . --- .../chat/browser/actions/chatActions.ts | 51 -------------- .../contrib/chat/browser/chat.contribution.ts | 9 --- .../chat/browser/chatSessions.contribution.ts | 7 -- .../contrib/chat/browser/chatWidget.ts | 66 ++++--------------- .../chat/browser/media/chatViewWelcome.css | 44 +------------ .../viewsWelcome/chatViewWelcomeController.ts | 36 ++-------- .../chat/common/chatSessionsService.ts | 1 - .../test/common/mockChatSessionsService.ts | 4 -- 8 files changed, 19 insertions(+), 199 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 47331dcd9f9..7db81bf62b2 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -23,10 +23,8 @@ import { hasKey } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; -import { Position } from '../../../../../editor/common/core/position.js'; import { IRange } from '../../../../../editor/common/core/range.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; -import { SuggestController } from '../../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../../nls.js'; import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; @@ -64,7 +62,6 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { ChatMode, IChatMode, IChatModeService } from '../../common/chatModes.js'; -import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; import { ISCMHistoryItemChangeRangeVariableEntry, ISCMHistoryItemChangeVariableEntry } from '../../common/chatVariableEntries.js'; @@ -1209,54 +1206,6 @@ export function registerChatActions() { } }); - registerAction2(class ChatAddAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.addParticipant', - title: localize2('chatWith', "Chat with Extension"), - icon: Codicon.mention, - f1: false, - category: CHAT_CATEGORY, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and( - ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask), - ContextKeyExpr.not('config.chat.emptyChatState.enabled'), - ChatContextKeys.lockedToCodingAgent.negate() - ), - group: 'navigation', - order: 1 - }] - }); - } - - override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { - const widgetService = accessor.get(IChatWidgetService); - const context = args[0] as { widget?: IChatWidget } | undefined; - const widget = context?.widget ?? widgetService.lastFocusedWidget; - if (!widget) { - return; - } - - const hasAgentOrCommand = extractAgentAndCommand(widget.parsedInput); - if (hasAgentOrCommand?.agentPart || hasAgentOrCommand?.commandPart) { - return; - } - - const suggestCtrl = SuggestController.get(widget.inputEditor); - if (suggestCtrl) { - const curText = widget.inputEditor.getValue(); - const newValue = curText ? `@ ${curText}` : '@'; - if (!curText.startsWith('@')) { - widget.inputEditor.setValue(newValue); - } - - widget.inputEditor.setPosition(new Position(1, 2)); - suggestCtrl.triggerSuggest(undefined, true); - } - } - }); - registerAction2(class ClearChatInputHistoryAction extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index adc852a5175..27cf748ced0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -304,15 +304,6 @@ configurationRegistry.registerConfiguration({ enum: ['inline', 'hover', 'input', 'none'], default: 'inline', }, - 'chat.emptyChatState.enabled': { - type: 'boolean', - default: true, - description: nls.localize('chat.emptyChatState', "Shows a modified empty chat state with hints in the input placeholder text."), - tags: ['experimental'], - experiment: { - mode: 'startup' - } - }, [ChatConfiguration.EmptyStateHistoryEnabled]: { type: 'boolean', default: product.quality === 'insiders', diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 75c238c4f26..4d4a5eea6dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -929,13 +929,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ return contribution?.capabilities; } - /** - * Get the welcome tips for a specific session type - */ - public getWelcomeTipsForSessionType(chatSessionType: string): string | undefined { - return this._sessionTypeWelcomeTips.get(chatSessionType); - } - public getContentProviderSchemes(): string[] { return Array.from(this._contentProviders.keys()); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 9773ce7ee33..1c04692fb2f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -1067,9 +1067,6 @@ export class ChatWidget extends Disposable implements IChatWidget { const numItems = this.viewModel?.getItems().length ?? 0; if (!numItems) { - const expEmptyState = this.configurationService.getValue('chat.emptyChatState.enabled'); - - let welcomeContent: IChatViewWelcomeContent; const defaultAgent = this.chatAgentService.getDefaultAgent(this.location, this.input.currentModeKind); let additionalMessage: string | IMarkdownString | undefined; if (this.chatEntitlementService.anonymous && !this.chatEntitlementService.sentiment.installed) { @@ -1080,19 +1077,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!additionalMessage && !this._lockedAgent) { additionalMessage = this._getGenerateInstructionsMessage(); } - if (expEmptyState) { - welcomeContent = this.getWelcomeViewContent(additionalMessage, expEmptyState); - } else { - const defaultTips = this.input.currentModeKind === ChatModeKind.Ask - ? new MarkdownString(localize('chatWidget.tips', "{0} or type {1} to attach context\n\n{2} to chat with extensions\n\nType {3} to use commands", '$(attach)', '#', '$(mention)', '/'), { supportThemeIcons: true }) - : new MarkdownString(localize('chatWidget.tips.withoutParticipants', "{0} or type {1} to attach context", '$(attach)', '#'), { supportThemeIcons: true }); - const contributedTips = this._lockedAgent?.id ? this.chatSessionsService.getWelcomeTipsForSessionType(this._lockedAgent.id) : undefined; - const tips = contributedTips - ? new MarkdownString(contributedTips, { supportThemeIcons: true }) - : (!this._lockedAgent ? defaultTips : undefined); - welcomeContent = this.getWelcomeViewContent(additionalMessage); - welcomeContent.tips = tips; - } + const welcomeContent = this.getWelcomeViewContent(additionalMessage); if (!this.welcomePart.value || this.welcomePart.value.needsRerender(welcomeContent)) { this.historyViewStore.clear(); dom.clearNode(this.welcomeMessageContainer); @@ -1355,13 +1340,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private getWelcomeViewContent(additionalMessage: string | IMarkdownString | undefined, expEmptyState?: boolean): IChatViewWelcomeContent { - const disclaimerMessage = expEmptyState - ? this.chatDisclaimer - : localize('chatMessage', "Chat is powered by AI, so mistakes are possible. Review output carefully before use."); - const icon = Codicon.chatSparkle; - - + private getWelcomeViewContent(additionalMessage: string | IMarkdownString | undefined): IChatViewWelcomeContent { if (this.isLockedToCodingAgent) { // Check for provider-specific customizations from chat sessions service const providerIcon = this._lockedAgent ? this.chatSessionsService.getIconForSessionType(this._lockedAgent.id) : undefined; @@ -1384,39 +1363,22 @@ export class ChatWidget extends Disposable implements IChatWidget { }; } - const suggestedPrompts = this.getPromptFileSuggestions(); - + let title: string; if (this.input.currentModeKind === ChatModeKind.Ask) { - return { - title: localize('chatDescription', "Ask about your code"), - message: new MarkdownString(disclaimerMessage), - icon, - additionalMessage, - suggestedPrompts - }; + title = localize('chatDescription', "Ask about your code"); } else if (this.input.currentModeKind === ChatModeKind.Edit) { - const editsHelpMessage = localize('editsHelp', "Start your editing session by defining a set of files that you want to work with. Then ask for the changes you want to make."); - const message = expEmptyState ? disclaimerMessage : `${editsHelpMessage}\n\n${disclaimerMessage}`; - - return { - title: localize('editsTitle', "Edit in context"), - message: new MarkdownString(message), - icon, - additionalMessage, - suggestedPrompts - }; + title = localize('editsTitle', "Edit in context"); } else { - const agentHelpMessage = localize('agentMessage', "Ask to edit your files using [Agent]({0}). Agent will automatically use multiple requests to pick files to edit, run terminal commands, and iterate on errors.", 'https://aka.ms/vscode-copilot-agent'); - const message = expEmptyState ? disclaimerMessage : `${agentHelpMessage}\n\n${disclaimerMessage}`; - - return { - title: localize('agentTitle', "Build with Agent"), - message: new MarkdownString(message), - icon, - additionalMessage, - suggestedPrompts - }; + title = localize('agentTitle', "Build with Agent"); } + + return { + title, + message: new MarkdownString(this.chatDisclaimer), + icon: Codicon.chatSparkle, + additionalMessage, + suggestedPrompts: this.getPromptFileSuggestions() + }; } private getPromptFileSuggestions(): IChatSuggestedPrompts[] { diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index d5be607b666..74cceb59ff5 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -101,29 +101,11 @@ div.chat-welcome-view { padding: 0 8px; } - & > .chat-welcome-view-indicator-container { - display: flex; - flex-wrap: wrap; - margin-top: 5px; - gap: 9px; - justify-content: center; - } - & > .chat-welcome-view-message { - text-align: center; - max-width: 350px; - padding: 0 20px; - margin-top: 10px; - - a { - color: var(--vscode-textLink-foreground); - } - } - - & > .chat-welcome-view-message.empty-state { position: relative; text-align: center; max-width: 100%; + padding: 0 20px; margin: 0 auto; color: var(--vscode-input-placeholderForeground); @@ -165,30 +147,6 @@ div.chat-welcome-view { } } - & > .chat-welcome-new-view-message { - text-align: center; - max-width: 350px; - padding: 0 20px 32px; - font-size: 16px; - - a { - color: var(--vscode-input-placeholderForeground); - } - } - - & > .chat-welcome-view-additional-message { - color: var(--vscode-input-placeholderForeground); - text-align: center; - max-width: 400px; - margin-top: 8px; - padding: 0 12px; - font-size: 12px; - - a { - color: var(--vscode-textLink-foreground); - } - } - & > .chat-welcome-view-disclaimer { color: var(--vscode-input-placeholderForeground); text-align: center; diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index 569034f55d1..c4bfbe3c142 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -21,7 +21,6 @@ import { ThemeIcon } from '../../../../../base/common/themables.js'; import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { IRenderedMarkdown } from '../../../../../base/browser/markdownRenderer.js'; import { localize } from '../../../../../nls.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; @@ -119,7 +118,6 @@ export interface IChatViewWelcomeContent { readonly additionalMessage?: string | IMarkdownString; tips?: IMarkdownString; readonly inputPart?: HTMLElement; - readonly isNew?: boolean; readonly suggestedPrompts?: readonly IChatSuggestedPrompts[]; readonly useLargeIcon?: boolean; } @@ -148,7 +146,6 @@ export class ChatViewWelcomePart extends Disposable { @ILogService private logService: ILogService, @IChatWidgetService private chatWidgetService: IChatWidgetService, @ITelemetryService private telemetryService: ITelemetryService, - @IConfigurationService private configurationService: IConfigurationService, @IMarkdownRendererService private readonly markdownRendererService: IMarkdownRendererService, @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { @@ -185,26 +182,13 @@ export class ChatViewWelcomePart extends Disposable { const title = dom.append(this.element, $('.chat-welcome-view-title')); title.textContent = content.title; - // Preview indicator (no experimental variants) - const expEmptyState = this.configurationService.getValue('chat.emptyChatState.enabled'); - if (typeof content.message !== 'function' && options?.isWidgetAgentWelcomeViewContent && !expEmptyState) { - const container = dom.append(this.element, $('.chat-welcome-view-indicator-container')); - dom.append(container, $('.chat-welcome-view-subtitle', undefined, localize('agentModeSubtitle', "Agent"))); - } - - const message = dom.append(this.element, content.isNew ? $('.chat-welcome-new-view-message') : $('.chat-welcome-view-message')); - message.classList.toggle('empty-state', expEmptyState); + const message = dom.append(this.element, $('.chat-welcome-view-message')); const messageResult = this.renderMarkdownMessageContent(content.message, options); dom.append(message, messageResult.element); - if (content.isNew && content.inputPart) { - content.inputPart.querySelector('.chat-attachments-container')?.remove(); - dom.append(this.element, content.inputPart); - } - - // Additional message (new user mode) - if (!content.isNew && content.additionalMessage) { + // Additional message + if (content.additionalMessage) { const disclaimers = dom.append(this.element, $('.chat-welcome-view-disclaimer')); if (typeof content.additionalMessage === 'string') { disclaimers.textContent = content.additionalMessage; @@ -302,17 +286,6 @@ export class ChatViewWelcomePart extends Disposable { const tipsResult = this._register(this.markdownRendererService.render(content.tips)); tips.appendChild(tipsResult.element); } - - // In new user mode, render the additional message after suggested prompts (deferred) - if (content.isNew && content.additionalMessage) { - const additionalMsg = dom.append(this.element, $('.chat-welcome-view-additional-message')); - if (typeof content.additionalMessage === 'string') { - additionalMsg.textContent = content.additionalMessage; - } else { - const additionalMessageResult = this.renderMarkdownMessageContent(content.additionalMessage, options); - additionalMsg.appendChild(additionalMessageResult.element); - } - } } catch (err) { this.logService.error('Failed to render chat view welcome content', err); } @@ -341,9 +314,8 @@ export class ChatViewWelcomePart extends Disposable { public needsRerender(content: IChatViewWelcomeContent): boolean { // Heuristic based on content that changes between states - return !!(content.isNew || + return !!( this.content.title !== content.title || - this.content.isNew !== content.isNew || this.content.message.value !== content.message.value || this.content.additionalMessage !== content.additionalMessage || this.content.tips?.value !== content.tips?.value || diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 153b85b6d74..fa252786e73 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -157,7 +157,6 @@ export interface IChatSessionsService { getWelcomeTitleForSessionType(chatSessionType: string): string | undefined; getWelcomeMessageForSessionType(chatSessionType: string): string | undefined; getInputPlaceholderForSessionType(chatSessionType: string): string | undefined; - getWelcomeTipsForSessionType(chatSessionType: string): string | undefined; /** * Get the list of chat session items grouped by session type. diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts index e110f4bde9f..fa3ad78161a 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts @@ -98,10 +98,6 @@ export class MockChatSessionsService implements IChatSessionsService { return this.contributions.find(c => c.type === chatSessionType)?.inputPlaceholder; } - getWelcomeTipsForSessionType(chatSessionType: string): string | undefined { - return this.contributions.find(c => c.type === chatSessionType)?.welcomeTips; - } - async getNewChatSessionItem(chatSessionType: string, options: { request: IChatAgentRequest; metadata?: unknown }, token: CancellationToken): Promise { const provider = this.sessionItemProviders.get(chatSessionType); if (!provider?.provideNewChatSessionItem) { From b72b61e07abdda9c2b1ef6ba549c40f24e53c39b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:53:46 +0000 Subject: [PATCH 1933/4355] Hide vendor header when only one model provider exists (#274288) * Initial plan * Hide vendor header when only one model provider exists Modified groupByVendor to skip vendor header entries when only one vendor is present. Also ensures models are always visible even when single vendor is "collapsed" since there's no header to collapse. Added comprehensive tests to verify: - No vendor header shown for single vendor - Vendor headers shown for multiple vendors - Models remain visible when single vendor is collapsed - Filtering works correctly with single vendor Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> * Refactor tests to reduce code duplication Extracted a helper function createSingleVendorViewModel to reduce duplication in single vendor test cases. This improves maintainability and makes tests more concise. Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> --- .../chatManagement/chatModelsViewModel.ts | 31 +++-- .../test/browser/chatModelsViewModel.test.ts | 113 ++++++++++++++++++ 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts index a6fb278fb7b..531a9c8a311 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts @@ -309,23 +309,28 @@ export class ChatModelsViewModel extends EditorModel { vendorMap.set(modelEntry.vendor, models); } + const showVendorHeaders = vendorMap.size > 1; + for (const [vendor, models] of vendorMap) { const firstModel = models[0]; const isCollapsed = this.collapsedVendors.has(vendor); const vendorInfo = this.languageModelsService.getVendors().find(v => v.vendor === vendor); - result.push({ - type: 'vendor', - id: `vendor-${vendor}`, - vendorEntry: { - vendor: firstModel.vendor, - vendorDisplayName: firstModel.vendorDisplayName, - managementCommand: vendorInfo?.managementCommand - }, - templateId: VENDOR_ENTRY_TEMPLATE_ID, - collapsed: isCollapsed - }); - - if (!isCollapsed) { + + if (showVendorHeaders) { + result.push({ + type: 'vendor', + id: `vendor-${vendor}`, + vendorEntry: { + vendor: firstModel.vendor, + vendorDisplayName: firstModel.vendorDisplayName, + managementCommand: vendorInfo?.managementCommand + }, + templateId: VENDOR_ENTRY_TEMPLATE_ID, + collapsed: isCollapsed + }); + } + + if (!isCollapsed || !showVendorHeaders) { for (const modelEntry of models) { const modelId = ChatModelsViewModel.getId(modelEntry); result.push({ diff --git a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts index d241e421127..257e6b0ed34 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts @@ -530,4 +530,117 @@ suite('ChatModelsViewModel', () => { assert.ok(model.capabilityMatches.some(c => c === 'toolCalling' || c === 'vision')); } }); + + // Helper function to create a single vendor test environment + function createSingleVendorViewModel(store: DisposableStore, chatEntitlementService: IChatEntitlementService, includeSecondModel: boolean = true): { service: MockLanguageModelsService; viewModel: ChatModelsViewModel } { + const service = new MockLanguageModelsService(); + service.addVendor({ + vendor: 'copilot', + displayName: 'GitHub Copilot', + managementCommand: undefined, + when: undefined + }); + + service.addModel('copilot', 'copilot-gpt-4', { + extension: new ExtensionIdentifier('github.copilot'), + id: 'gpt-4', + name: 'GPT-4', + family: 'gpt-4', + version: '1.0', + vendor: 'copilot', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Copilot', order: 1 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: true, + agentMode: false + } + }); + + if (includeSecondModel) { + service.addModel('copilot', 'copilot-gpt-4o', { + extension: new ExtensionIdentifier('github.copilot'), + id: 'gpt-4o', + name: 'GPT-4o', + family: 'gpt-4', + version: '1.0', + vendor: 'copilot', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Copilot', order: 1 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: true, + agentMode: true + } + }); + } + + const viewModel = store.add(new ChatModelsViewModel(service, chatEntitlementService)); + return { service, viewModel }; + } + + test('should not show vendor header when only one vendor exists', async () => { + const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService); + await singleVendorViewModel.resolve(); + + const results = singleVendorViewModel.fetch(''); + + // Should have only model entries, no vendor entry + const vendors = results.filter(isVendorEntry); + assert.strictEqual(vendors.length, 0, 'Should not show vendor header when only one vendor exists'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 2, 'Should show all models'); + assert.ok(models.every(m => m.modelEntry.vendor === 'copilot')); + }); + + test('should show vendor headers when multiple vendors exist', () => { + // This is the existing behavior test + const results = viewModel.fetch(''); + + // Should have 2 vendor entries and 4 model entries (grouped by vendor) + const vendors = results.filter(isVendorEntry); + assert.strictEqual(vendors.length, 2, 'Should show vendor headers when multiple vendors exist'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 4); + }); + + test('should show models even when single vendor is collapsed', async () => { + const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService, false); + await singleVendorViewModel.resolve(); + + // Try to collapse the single vendor + singleVendorViewModel.toggleVendorCollapsed('copilot'); + + const results = singleVendorViewModel.fetch(''); + + // Should still show models even though vendor is "collapsed" + // because there's no vendor header to collapse + const vendors = results.filter(isVendorEntry); + assert.strictEqual(vendors.length, 0, 'Should not show vendor header'); + + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1, 'Should still show models even when single vendor is collapsed'); + }); + + test('should filter single vendor models by capability', async () => { + const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService); + await singleVendorViewModel.resolve(); + + const results = singleVendorViewModel.fetch('@capability:agent'); + + // Should not show vendor header + const vendors = results.filter(isVendorEntry); + assert.strictEqual(vendors.length, 0, 'Should not show vendor header'); + + // Should only show the model with agent capability + const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[]; + assert.strictEqual(models.length, 1); + assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4o'); + }); }); From 8fa2689d9f542497508aaec2bad025c81e04deae Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:04:32 +0000 Subject: [PATCH 1934/4355] Use mouse-positioned compact hovers for language models table (#274293) * Initial plan * Update hover options for language models table - Change ModelNameColumnRenderer to use setupDelayedHoverAtMouse - Change TokenLimitsColumnRenderer to use setupDelayedHoverAtMouse - Remove showPointer and HoverPosition.BELOW options - Add compact: true for both hovers - Remove unused HoverPosition import Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sandy081 <10746682+sandy081@users.noreply.github.com> --- .../browser/chatManagement/chatModelsWidget.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index 5ffa1511561..e3f610f2f2e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -18,7 +18,6 @@ import { WorkbenchTable } from '../../../../../platform/list/browser/listService import { ITableVirtualDelegate, ITableRenderer } from '../../../../../base/browser/ui/table/table.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { IAction, toAction, Action, Separator } from '../../../../../base/common/actions.js'; @@ -392,14 +391,11 @@ class ModelNameColumnRenderer extends ModelsTableColumnRenderer ({ + templateData.elementDisposables.add(this.hoverService.setupDelayedHoverAtMouse(templateData.container!, () => ({ content: markdown, appearance: { - showPointer: true, + compact: true, skipFadeInAnimation: true, - }, - position: { - hoverPosition: HoverPosition.BELOW } }))); } @@ -497,14 +493,11 @@ class TokenLimitsColumnRenderer extends ModelsTableColumnRenderer ({ + templateData.elementDisposables.add(this.hoverService.setupDelayedHoverAtMouse(templateData.container, () => ({ content: markdown, appearance: { - showPointer: true, + compact: true, skipFadeInAnimation: true, - }, - position: { - hoverPosition: HoverPosition.BELOW } }))); } From e6d31b33fd609c293fbd8dea9c6f1dbd004c64f8 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 31 Oct 2025 05:20:28 -0700 Subject: [PATCH 1935/4355] Use target OS case-sensitivity when matching terminal completion resource glob pattern (#274299) --- .../suggest/browser/terminalCompletionService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index cb86a71e169..4885f2f1650 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -9,7 +9,7 @@ import { basename } from '../../../../../base/common/path.js'; import { URI, UriComponents } from '../../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IFileService } from '../../../../../platform/files/common/files.js'; +import { FileSystemProviderCapabilities, IFileService } from '../../../../../platform/files/common/files.js'; import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; import { TerminalCapability, type ITerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { GeneralShellType, ITerminalLogService, TerminalShellType, WindowsShellType } from '../../../../../platform/terminal/common/terminal.js'; @@ -345,7 +345,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } // Assemble completions based on the resource of lastWordFolder. Note that on Windows the - // path seprators are normalized to `\`. + // path separators are normalized to `\`. if (!lastWordFolderResource) { return undefined; } @@ -450,7 +450,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (child.isFile && globPattern) { const filePath = child.resource.fsPath; - const matches = match(globPattern, filePath); + const ignoreCase = !this._fileService.hasCapability(child.resource, FileSystemProviderCapabilities.PathCaseSensitive); + const matches = match(globPattern, filePath, { ignoreCase }); if (!matches) { return; } From 37f883f2df6d1a76e8919ee07406aff58927285c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 31 Oct 2025 13:37:48 +0100 Subject: [PATCH 1936/4355] feedback (#274335) --- src/vs/workbench/browser/media/style.css | 1 + .../browser/actions/manageModelsActions.ts | 3 +- .../chatManagement.contribution.ts | 2 +- .../chatManagement/chatManagementEditor.ts | 2 +- .../chatManagement/chatModelsViewModel.ts | 24 ++-- .../chatManagement/chatModelsWidget.ts | 62 ++++++----- .../media/chatManagementEditor.css | 2 +- .../chatManagement/media/chatModelsWidget.css | 26 ++++- .../modelPicker/modelPickerActionItem.ts | 9 +- .../test/browser/chatModelsViewModel.test.ts | 105 ++++++++++++++++++ 10 files changed, 189 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 7f9dc676745..ba3e164a18a 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -280,6 +280,7 @@ body { outline-color: var(--vscode-focusBorder); } +.monaco-workbench .monaco-list:not(:focus) .monaco-list-row.focused .monaco-highlighted-label .highlight, .monaco-workbench .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: var(--vscode-list-highlightForeground); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts b/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts index 7ec0500de58..4bf148b6da9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/manageModelsActions.ts @@ -11,6 +11,7 @@ import { localize2 } from '../../../../../nls.js'; import { Action2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ProductQualityContext } from '../../../../../platform/contextkey/common/contextkeys.js'; import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; @@ -35,7 +36,7 @@ export class ManageModelsAction extends Action2 { id: ManageModelsAction.ID, title: localize2('manageLanguageModels', 'Manage Language Models...'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.or( + precondition: ContextKeyExpr.and(ProductQualityContext.isEqualTo('stable'), ChatContextKeys.enabled, ContextKeyExpr.or( ChatContextKeys.Entitlement.planFree, ChatContextKeys.Entitlement.planPro, ChatContextKeys.Entitlement.planProPlus, diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts index 54adc68fa4d..381f84e8424 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts @@ -102,7 +102,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: MANAGE_CHAT_COMMAND_ID, - title: localize2('openAiManagement', "Manage Language Models (Preview)"), + title: localize2('openAiManagement', "Manage Language Models"), category: CHAT_CATEGORY, precondition: ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ChatContextKeys.enabled, ContextKeyExpr.or( ChatContextKeys.Entitlement.planFree, diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts index 58996bac09a..8a21cc38c8f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatManagementEditor.ts @@ -69,7 +69,7 @@ export class ModelsManagementEditor extends EditorPane { override layout(dimension: Dimension): void { this.dimension = dimension; if (this.bodyContainer) { - this.modelsWidget?.layout(dimension.height - 30, this.bodyContainer!.clientWidth - 30); + this.modelsWidget?.layout(dimension.height - 15, this.bodyContainer!.clientWidth - 24); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts index 531a9c8a311..07aa011cf75 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsViewModel.ts @@ -7,7 +7,7 @@ import { distinct, coalesce } from '../../../../../base/common/arrays.js'; import { IMatch, IFilter, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from '../../../../../base/common/filters.js'; import { Emitter } from '../../../../../base/common/event.js'; import { EditorModel } from '../../../../common/editor/editorModel.js'; -import { ILanguageModelsService, ILanguageModelChatMetadata } from '../../../chat/common/languageModels.js'; +import { ILanguageModelsService, ILanguageModelChatMetadata, IUserFriendlyLanguageModel } from '../../../chat/common/languageModels.js'; import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; export const MODEL_ENTRY_TEMPLATE_ID = 'model.entry.template'; @@ -136,7 +136,7 @@ export class ChatModelsViewModel extends EditorModel { searchValue = searchValue.trim(); if (!searchValue) { - return this.groupByVendor(modelEntries, capabilityMatchesMap); + return this.toEntries(modelEntries, capabilityMatchesMap); } return this.filterByText(modelEntries, searchValue, capabilityMatchesMap); @@ -255,12 +255,18 @@ export class ChatModelsViewModel extends EditorModel { return result; } + getVendors(): IUserFriendlyLanguageModel[] { + return [...this.languageModelsService.getVendors()].sort((a, b) => { + if (a.vendor === 'copilot') { return -1; } + if (b.vendor === 'copilot') { return 1; } + return a.displayName.localeCompare(b.displayName); + }); + } + override async resolve(): Promise { this.modelEntries = []; - const vendors = this.languageModelsService.getVendors(); - - for (const vendor of vendors) { + for (const vendor of this.getVendors()) { const modelIdentifiers = await this.languageModelsService.selectLanguageModels({ vendor: vendor.vendor }, vendor.vendor === 'copilot'); const models = coalesce(modelIdentifiers.map(identifier => { const metadata = this.languageModelsService.lookupLanguageModel(identifier); @@ -299,7 +305,11 @@ export class ChatModelsViewModel extends EditorModel { this._onDidChangeModelEntries.fire(); } - private groupByVendor(modelEntries: IModelEntry[], capabilityMatchesMap: Map): (IVendorItemEntry | IModelItemEntry)[] { + getConfiguredVendors(): IVendorItemEntry[] { + return this.toEntries(this.modelEntries, new Map(), true) as IVendorItemEntry[]; + } + + private toEntries(modelEntries: IModelEntry[], capabilityMatchesMap: Map, excludeModels?: boolean): (IVendorItemEntry | IModelItemEntry)[] { const result: (IVendorItemEntry | IModelItemEntry)[] = []; const vendorMap = new Map(); @@ -330,7 +340,7 @@ export class ChatModelsViewModel extends EditorModel { }); } - if (!isCollapsed || !showVendorHeaders) { + if (!excludeModels && (!isCollapsed || !showVendorHeaders)) { for (const modelEntry of models) { const modelId = ChatModelsViewModel.getId(modelEntry); result.push({ diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index e3f610f2f2e..d0dd7169607 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -135,7 +135,7 @@ class ModelsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionVie action: IAction, options: IActionViewItemOptions, private readonly searchWidget: SuggestEnabledInput, - @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, + private readonly viewModel: ChatModelsViewModel, @IContextMenuService contextMenuService: IContextMenuService ) { super(action, @@ -207,26 +207,27 @@ class ModelsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionVie } private getActions(): IAction[] { - const vendors = this.languageModelsService.getVendors(); const actions: IAction[] = []; + // Visibility filters actions.push(this.createVisibleAction(true, localize('filter.visible', 'Visible'))); + actions.push(this.createVisibleAction(false, localize('filter.hidden', 'Hidden'))); - if (vendors.length > 0) { - actions.push(new Separator()); - actions.push(...vendors.map(vendor => this.createProviderAction(vendor.vendor, vendor.displayName))); - } - - if (actions.length > 1) { - actions.push(new Separator()); - } - + // Capability filters + actions.push(new Separator()); actions.push( this.createCapabilityAction('tools', localize('capability.tools', 'Tools')), this.createCapabilityAction('vision', localize('capability.vision', 'Vision')), this.createCapabilityAction('agent', localize('capability.agent', 'Agent Mode')) ); + // Provider filters - only show providers with configured models + const configuredVendors = this.viewModel.getConfiguredVendors(); + if (configuredVendors.length > 1) { + actions.push(new Separator()); + actions.push(...configuredVendors.map(vendor => this.createProviderAction(vendor.vendorEntry.vendor, vendor.vendorEntry.vendorDisplayName))); + } + return actions; } } @@ -269,6 +270,7 @@ abstract class ModelsTableColumnRenderer + this._onDidToggleCollapse.fire(entry.vendorEntry.vendor))); + } } private createToggleCollapseAction(entry: IVendorItemEntry): IAction { @@ -630,7 +638,7 @@ class ActionsColumnRenderer extends ModelsTableColumnRenderer { const newVisibility = !isVisible; @@ -653,6 +661,8 @@ function formatTokenCount(count: number): string { export class ChatModelsWidget extends Disposable { + private static NUM_INSTANCES: number = 0; + readonly element: HTMLElement; private searchWidget!: SuggestEnabledInput; private searchActionsContainer!: HTMLElement; @@ -705,7 +715,7 @@ export class ChatModelsWidget extends Disposable { const queryParts = query.split(/\s/g); const lastPart = queryParts[queryParts.length - 1]; if (lastPart.startsWith('@provider:')) { - const vendors = this.languageModelsService.getVendors(); + const vendors = this.viewModel.getVendors(); return vendors.map(v => `@provider:"${v.displayName}"`); } else if (lastPart.startsWith('@capability:')) { return SEARCH_SUGGESTIONS.CAPABILITIES; @@ -718,7 +728,7 @@ export class ChatModelsWidget extends Disposable { } }, placeholder, - 'chatModelsWidget:searchinput', + `chatModelsWidget:searchinput:${ChatModelsWidget.NUM_INSTANCES++}`, { placeholderText: placeholder, styleOverrides: { @@ -728,14 +738,7 @@ export class ChatModelsWidget extends Disposable { )); this._register(this.searchWidget.onInputDidChange(() => this.filterModels())); - const filterAction = new ModelsFilterAction(); - const refreshAction = this._register(new Action( - 'workbench.models.refresh', - localize('refresh', "Refresh"), - ThemeIcon.asClassName(Codicon.refresh), - true, - () => this.refresh() - )); + const filterAction = this._register(new ModelsFilterAction()); const clearSearchAction = this._register(new Action( 'workbench.models.clearSearch', localize('clearSearch', "Clear Search"), @@ -763,11 +766,11 @@ export class ChatModelsWidget extends Disposable { })); this.searchActionsContainer = DOM.append(searchContainer, $('.models-search-actions')); - const actions = [clearSearchAction, refreshAction, filterAction]; + const actions = [clearSearchAction, filterAction]; const toolBar = this._register(new ToolBar(this.searchActionsContainer, this.contextMenuService, { actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action.id === filterAction.id) { - return this.instantiationService.createInstance(ModelsSearchFilterDropdownMenuActionViewItem, action, options, this.searchWidget); + return this.instantiationService.createInstance(ModelsSearchFilterDropdownMenuActionViewItem, action, options, this.searchWidget, this.viewModel); } return undefined; }, @@ -834,7 +837,7 @@ export class ChatModelsWidget extends Disposable { { label: '', tooltip: '', - weight: 0, + weight: 0.12, minimumWidth: 40, maximumWidth: 40, templateId: ToggleCollapseColumnRenderer.TEMPLATE_ID, @@ -905,7 +908,7 @@ export class ChatModelsWidget extends Disposable { multipleSelectionSupport: false, setRowLineHeight: false, openOnSingleClick: false, - alwaysConsumeMouseWheel: false + alwaysConsumeMouseWheel: false, } )) as WorkbenchTable; @@ -919,7 +922,7 @@ export class ChatModelsWidget extends Disposable { const searchValue = this.searchWidget.getValue(); const modelItems = this.viewModel.fetch(searchValue); - const vendors = this.languageModelsService.getVendors(); + const vendors = this.viewModel.getVendors(); const vendorsWithModels = new Set(modelItems .filter((item): item is IModelItemEntry => !isVendorEntry(item)) .map(item => item.modelEntry.vendor) @@ -947,8 +950,11 @@ export class ChatModelsWidget extends Disposable { } public layout(height: number, width: number): void { + width = width - 24; this.searchWidget.layout(new DOM.Dimension(width - this.searchActionsContainer.clientWidth - this.addButtonContainer.clientWidth - 8, 22)); - this.table.layout(height - 40, width); + const tableHeight = height - 40; + this.tableContainer.style.height = `${tableHeight}px`; + this.table.layout(tableHeight, width); } public focusSearch(): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css index e5e4be4fd5d..ee88946369f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatManagementEditor.css @@ -17,7 +17,7 @@ overflow: hidden; max-width: 1200px; margin: 0 auto; - padding: 15px; + padding: 15px 0px 0px 24px; } /* Header - spans full width */ 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 c961b39171a..d48424ace93 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/media/chatModelsWidget.css @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.models-widget { + padding-right: 24px; +} + /** Search and button container styling **/ .models-widget .models-search-and-button-container { @@ -17,10 +21,6 @@ position: relative; } -.models-widget .models-search-container .settings-header-widget { - width: 100%; -} - .models-widget .models-search-container .models-search-actions { position: absolute; top: 0; @@ -41,6 +41,10 @@ box-sizing: content-box; } +.models-widget .models-search-and-button-container .section-title-actions .models-add-model-button { + white-space: nowrap; +} + /** Table styling **/ .models-widget .models-table-container { @@ -161,6 +165,7 @@ } .models-widget .models-table-container .monaco-table-td .model-capability { + width: fit-content; padding: 0 6px; border-radius: 3px; font-size: 11px; @@ -170,15 +175,26 @@ } .models-widget .models-table-container .monaco-table-td .model-capability.active { + border-color: var(--vscode-radio-activeBorder, transparent); +} + +/** Visibility styling **/ + +.models-widget .models-table-container .monaco-table-td .model-visible { color: var(--vscode-radio-activeForeground); background-color: var(--vscode-radio-activeBackground); - border-color: var(--vscode-radio-activeBorder, transparent); + border: 1px solid var(--vscode-radio-activeBorder, transparent); +} + +.models-widget .models-table-container .monaco-table-td .model-visible:hover { + background-color: var(--vscode-radio-activeBackground) !important; } /** Vendor row styling **/ .models-widget .models-table-container .models-vendor-row { background-color: var(--vscode-keybindingTable-headerBackground); + cursor: pointer; } .models-widget .models-table-container .monaco-table-tr:hover .models-vendor-row { diff --git a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts index 109ea2d9aae..c6dbd3bcf3f 100644 --- a/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/modelPicker/modelPickerActionItem.ts @@ -21,6 +21,8 @@ import { DEFAULT_MODEL_PICKER_CATEGORY } from '../../common/modelPicker/modelPic import { ManageModelsAction } from '../actions/manageModelsActions.js'; import { IActionProvider } from '../../../../../base/browser/ui/dropdown/dropdown.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { IProductService } from '../../../../../platform/product/common/productService.js'; +import { MANAGE_CHAT_COMMAND_ID } from '../../common/constants.js'; export interface IModelPickerDelegate { readonly onDidChangeModel: Event; @@ -69,7 +71,7 @@ function modelDelegateToWidgetActionsProvider(delegate: IModelPickerDelegate, te }; } -function getModelPickerActionBarActionProvider(commandService: ICommandService, chatEntitlementService: IChatEntitlementService): IActionProvider { +function getModelPickerActionBarActionProvider(commandService: ICommandService, chatEntitlementService: IChatEntitlementService, productService: IProductService): IActionProvider { const actionProvider: IActionProvider = { getActions: () => { @@ -88,7 +90,7 @@ function getModelPickerActionBarActionProvider(commandService: ICommandService, class: undefined, run: () => { const commandId = ManageModelsAction.ID; - commandService.executeCommand(commandId); + commandService.executeCommand(productService.quality === 'stable' ? commandId : MANAGE_CHAT_COMMAND_ID); } }); } @@ -130,6 +132,7 @@ export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { @IChatEntitlementService chatEntitlementService: IChatEntitlementService, @IKeybindingService keybindingService: IKeybindingService, @ITelemetryService telemetryService: ITelemetryService, + @IProductService productService: IProductService, ) { // Modify the original action with a different label and make it show the current model const actionWithLabel: IAction = { @@ -141,7 +144,7 @@ export class ModelPickerActionItem extends ActionWidgetDropdownActionViewItem { const modelPickerActionWidgetOptions: Omit = { actionProvider: modelDelegateToWidgetActionsProvider(delegate, telemetryService), - actionBarActionProvider: getModelPickerActionBarActionProvider(commandService, chatEntitlementService) + actionBarActionProvider: getModelPickerActionBarActionProvider(commandService, chatEntitlementService, productService) }; super(actionWithLabel, widgetOptions ?? modelPickerActionWidgetOptions, actionWidgetService, keybindingService, contextKeyService); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts index 257e6b0ed34..767f51a82db 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatModelsViewModel.test.ts @@ -643,4 +643,109 @@ suite('ChatModelsViewModel', () => { assert.strictEqual(models.length, 1); assert.strictEqual(models[0].modelEntry.metadata.id, 'gpt-4o'); }); + + test('should always place copilot vendor at the top', () => { + const results = viewModel.fetch(''); + + const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; + assert.ok(vendors.length >= 2); + + // First vendor should always be copilot + assert.strictEqual(vendors[0].vendorEntry.vendor, 'copilot'); + }); + + test('should maintain copilot at top with multiple vendors', async () => { + // Add more vendors to ensure sorting works correctly + languageModelsService.addVendor({ + vendor: 'anthropic', + displayName: 'Anthropic', + managementCommand: undefined, + when: undefined + }); + + languageModelsService.addModel('anthropic', 'anthropic-claude', { + extension: new ExtensionIdentifier('anthropic.api'), + id: 'claude-3', + name: 'Claude 3', + family: 'claude', + version: '1.0', + vendor: 'anthropic', + maxInputTokens: 100000, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Anthropic', order: 3 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: false, + agentMode: false + } + }); + + languageModelsService.addVendor({ + vendor: 'azure', + displayName: 'Azure OpenAI', + managementCommand: undefined, + when: undefined + }); + + languageModelsService.addModel('azure', 'azure-gpt-4', { + extension: new ExtensionIdentifier('microsoft.azure'), + id: 'azure-gpt-4', + name: 'Azure GPT-4', + family: 'gpt-4', + version: '1.0', + vendor: 'azure', + maxInputTokens: 8192, + maxOutputTokens: 4096, + modelPickerCategory: { label: 'Azure', order: 4 }, + isUserSelectable: true, + capabilities: { + toolCalling: true, + vision: false, + agentMode: false + } + }); + + await viewModel.resolve(); + + const results = viewModel.fetch(''); + const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; + + // Should have 4 vendors: copilot, openai, anthropic, azure + assert.strictEqual(vendors.length, 4); + + // First vendor should always be copilot + assert.strictEqual(vendors[0].vendorEntry.vendor, 'copilot'); + + // Other vendors should be alphabetically sorted: anthropic, azure, openai + assert.strictEqual(vendors[1].vendorEntry.vendor, 'anthropic'); + assert.strictEqual(vendors[2].vendorEntry.vendor, 'azure'); + assert.strictEqual(vendors[3].vendorEntry.vendor, 'openai'); + }); + + test('should keep copilot at top even with text search', () => { + // Even when searching, if results include multiple vendors, copilot should be first + const results = viewModel.fetch('GPT'); + + const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; + + if (vendors.length > 1) { + // If multiple vendors match, copilot should be first + const copilotVendor = vendors.find(v => v.vendorEntry.vendor === 'copilot'); + if (copilotVendor) { + assert.strictEqual(vendors[0].vendorEntry.vendor, 'copilot'); + } + } + }); + + test('should keep copilot at top when filtering by capability', () => { + const results = viewModel.fetch('@capability:tools'); + + const vendors = results.filter(isVendorEntry) as IVendorItemEntry[]; + + // Both copilot and openai have models with tools capability + if (vendors.length > 1) { + assert.strictEqual(vendors[0].vendorEntry.vendor, 'copilot'); + } + }); }); From 3628ca55162af9f8ad490a0ec64f778b40277d00 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 31 Oct 2025 13:54:32 +0100 Subject: [PATCH 1937/4355] Inline chat v2 UI work (#274338) * disable auto-accept for inline chat * hide inline chat via ESC more * inline - render picker below input * remove unneccessary v2 actions * fix `Enter` to only accept from input when there is no new text typed * custom placeholder, use prompt as placeholder while request is on * no min width for inline chat widget --- src/vs/workbench/contrib/chat/browser/chat.ts | 1 + .../contrib/chat/browser/chatInputPart.ts | 8 +- .../contrib/chat/browser/chatWidget.ts | 10 ++- .../browser/inlineChat.contribution.ts | 2 - .../inlineChat/browser/inlineChatActions.ts | 74 ++----------------- .../browser/inlineChatController.ts | 23 +++++- .../inlineChat/browser/media/inlineChat.css | 18 +++++ 7 files changed, 55 insertions(+), 81 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index b2a3b75dd3b..9c56ba31904 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -155,6 +155,7 @@ export interface IChatWidgetViewOptions { renderInputOnTop?: boolean; renderFollowups?: boolean; renderStyle?: 'compact' | 'minimal'; + renderInputToolbarBelowInput?: boolean; supportsFileReferences?: boolean; filter?: (item: ChatTreeItem) => boolean; rendererOptions?: IChatListItemRendererOptions; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 899f91f2eb8..f0e872d7d7f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -122,6 +122,7 @@ export interface IChatInputPartOptions { defaultMode?: IChatMode; renderFollowups: boolean; renderStyle?: 'compact'; + renderInputToolbarBelowInput: boolean; menus: { executeToolbar: MenuId; telemetrySource: string; @@ -1422,7 +1423,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(dom.addStandardDisposableListener(toolbarsContainer, dom.EventType.CLICK, e => this.inputEditor.focus())); this._register(dom.addStandardDisposableListener(this.attachmentsContainer, dom.EventType.CLICK, e => this.inputEditor.focus())); - this.inputActionsToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, MenuId.ChatInput, { + this.inputActionsToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, this.options.renderInputToolbarBelowInput ? this.attachmentsContainer : toolbarsContainer, MenuId.ChatInput, { telemetrySource: this.options.menus.telemetrySource, menuOptions: { shouldForwardArgs: true }, hiddenItemStrategy: HiddenItemStrategy.NoHide, @@ -2143,6 +2144,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const executeToolbarWidth = this.cachedExecuteToolbarWidth = this.executeToolbar.getItemsWidth(); const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth(); + const inputSideToolbarWidth = this.inputSideToolbarContainer ? dom.getTotalWidth(this.inputSideToolbarContainer) : 0; const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4; const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 : 0; return { @@ -2154,10 +2156,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge attachmentsHeight: this.attachmentsHeight, editorBorder: 2, inputPartHorizontalPaddingInside: 12, - toolbarsWidth: this.options.renderStyle === 'compact' ? executeToolbarWidth + executeToolbarPadding + inputToolbarWidth + inputToolbarPadding : 0, + toolbarsWidth: this.options.renderStyle === 'compact' ? executeToolbarWidth + executeToolbarPadding + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding) : 0, toolbarsHeight: this.options.renderStyle === 'compact' ? 0 : 22, chatEditingStateHeight: this.chatEditingSessionWidgetContainer.offsetHeight, - sideToolbarWidth: this.inputSideToolbarContainer ? dom.getTotalWidth(this.inputSideToolbarContainer) + 4 /*gap*/ : 0, + sideToolbarWidth: inputSideToolbarWidth > 0 ? inputSideToolbarWidth + 4 /*gap*/ : 0, todoListWidgetContainerHeight: this.chatInputTodoListWidgetContainer.offsetHeight, }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 1c04692fb2f..6e7c2c1b208 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -800,6 +800,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const renderInputOnTop = this.viewOptions.renderInputOnTop ?? false; const renderFollowups = this.viewOptions.renderFollowups ?? !renderInputOnTop; const renderStyle = this.viewOptions.renderStyle; + const renderInputToolbarBelowInput = this.viewOptions.renderInputToolbarBelowInput ?? false; this.container = dom.append(parent, $('.interactive-session')); this.welcomeMessageContainer = dom.append(this.container, $('.chat-welcome-view-container', { style: 'display: none' })); @@ -815,12 +816,12 @@ export class ChatWidget extends Disposable implements IChatWidget { })); if (renderInputOnTop) { - this.createInput(this.container, { renderFollowups, renderStyle }); + this.createInput(this.container, { renderFollowups, renderStyle, renderInputToolbarBelowInput }); this.listContainer = dom.append(this.container, $(`.interactive-list`)); } else { this.listContainer = dom.append(this.container, $(`.interactive-list`)); dom.append(this.container, this.chatSuggestNextWidget.domNode); - this.createInput(this.container, { renderFollowups, renderStyle }); + this.createInput(this.container, { renderFollowups, renderStyle, renderInputToolbarBelowInput }); } this._welcomeRenderScheduler.schedule(); @@ -2076,10 +2077,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'compact' | 'minimal' }): void { + private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'compact' | 'minimal'; renderInputToolbarBelowInput?: boolean }): void { const commonConfig: IChatInputPartOptions = { renderFollowups: options?.renderFollowups ?? true, renderStyle: options?.renderStyle === 'minimal' ? 'compact' : options?.renderStyle, + renderInputToolbarBelowInput: options?.renderInputToolbarBelowInput ?? false, menus: { executeToolbar: MenuId.ChatExecute, telemetrySource: 'chatWidget', @@ -2669,7 +2671,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } layout(height: number, width: number): void { - width = Math.min(width, 950); + width = Math.min(width, this.viewOptions.renderStyle === 'minimal' ? width : 950); // no min width of inline chat const heightUpdated = this.bodyDimension && this.bodyDimension.height !== height; this.bodyDimension = new dom.Dimension(width, height); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 48e9e7ea6dd..27b562a58a0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -30,8 +30,6 @@ registerEditorContribution(InlineChatController.ID, InlineChatController, Editor registerAction2(InlineChatActions.KeepSessionAction2); registerAction2(InlineChatActions.UndoAndCloseSessionAction2); -registerAction2(InlineChatActions.RevealWidget); -registerAction2(InlineChatActions.CancelRequestAction); // --- browser diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index e2587225229..3c04092f4ae 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -12,7 +12,7 @@ import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/code import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { InlineChatController, InlineChatController1, InlineChatController2, InlineChatRunOptions } from './inlineChatController.js'; import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, MENU_INLINE_CHAT_SIDE, CTX_INLINE_CHAT_V2_ENABLED, CTX_INLINE_CHAT_V1_ENABLED } from '../common/inlineChat.js'; -import { ctxHasEditorModification, ctxHasRequestInProgress, ctxIsGlobalEditingSession, ctxRequestCount } from '../../chat/browser/chatEditing/chatEditingEditorContextKeys.js'; +import { ctxHasEditorModification, ctxHasRequestInProgress, ctxRequestCount } from '../../chat/browser/chatEditing/chatEditingEditorContextKeys.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -655,7 +655,7 @@ export class KeepSessionAction2 extends KeepOrUndoSessionAction { ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline) ), keybinding: [{ - when: ChatContextKeys.inputHasFocus, + when: ContextKeyExpr.and(ChatContextKeys.inputHasFocus, ChatContextKeys.inputHasText.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, { @@ -687,7 +687,10 @@ export class UndoAndCloseSessionAction2 extends KeepOrUndoSessionAction { icon: Codicon.close, precondition: CTX_INLINE_CHAT_VISIBLE, keybinding: [{ - when: ChatContextKeys.inputHasFocus, + when: ContextKeyExpr.or( + ContextKeyExpr.and(EditorContextKeys.focus, ctxHasEditorModification.negate()), + ChatContextKeys.inputHasFocus, + ), weight: KeybindingWeight.WorkbenchContrib + 1, primary: KeyCode.Escape, }, { @@ -703,68 +706,3 @@ export class UndoAndCloseSessionAction2 extends KeepOrUndoSessionAction { }); } } - -// TODO@jrieken REMOVE this -export class RevealWidget extends AbstractInline2ChatAction { - constructor() { - super({ - id: 'inlineChat2.reveal', - title: localize2('reveal', "Toggle Inline Chat"), - f1: true, - icon: Codicon.chatSparkle, - precondition: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ContextKeyExpr.greaterEquals(ctxRequestCount.key, 1)), - toggled: { - condition: CTX_INLINE_CHAT_VISIBLE, - }, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI - }, - menu: { - id: MenuId.ChatEditingEditorContent, - when: ContextKeyExpr.and( - ContextKeyExpr.greaterEquals(ctxRequestCount.key, 1), - ctxIsGlobalEditingSession.negate(), - ), - group: 'navigate', - order: 4, - } - }); - } - - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void { - ctrl.toggleWidgetUntilNextRequest(); - ctrl.markActiveController(); - } -} - -// TODO@jrieken REMOVE this -export class CancelRequestAction extends AbstractInline2ChatAction { - constructor() { - super({ - id: 'inlineChat2.cancelRequest', - title: localize2('cancel', "Cancel Request"), - f1: true, - icon: Codicon.stopCircle, - precondition: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ctxHasRequestInProgress), - toggled: CTX_INLINE_CHAT_VISIBLE, - menu: { - id: MenuId.ChatEditingEditorContent, - when: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ctxHasRequestInProgress), - group: 'a_request', - order: 1, - } - }); - } - - runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void { - const chatService = accessor.get(IChatService); - - const { viewModel } = ctrl.widget.chatWidget; - if (viewModel) { - ctrl.toggleWidgetUntilNextRequest(); - ctrl.markActiveController(); - chatService.cancelCurrentRequestForSession(viewModel.sessionResource); - } - } -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index cbe8705b987..c3bf78e028f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1324,7 +1324,7 @@ export class InlineChatController2 implements IEditorContribution { enableWorkingSet: 'implicit', enableImplicitContext: false, renderInputOnTop: false, - renderStyle: 'compact', + renderInputToolbarBelowInput: true, filter: _item => false, // filter ALL items menus: { telemetrySource: 'inlineChatWidget', @@ -1398,8 +1398,8 @@ export class InlineChatController2 implements IEditorContribution { this._store.add(autorun(r => { + // HIDE/SHOW const session = visibleSessionObs.read(r); - if (!session) { this._zone.rawValue?.hide(); _editor.focus(); @@ -1413,10 +1413,25 @@ export class InlineChatController2 implements IEditorContribution { } this._zone.value.reveal(this._zone.value.position!); this._zone.value.widget.focus(); - const entry = session.editingSession.getEntry(session.uri); + } + })); + + this._store.add(autorun(r => { + const session = visibleSessionObs.read(r); + if (!session) { + return; + } - entry?.autoAcceptController.read(undefined)?.cancel(); + const entry = session.editingSession.readEntry(session.uri, r); + entry?.enableReviewModeUntilSettled(); + const inProgress = session.chatModel.requestInProgressObs.read(r); + this._zone.value.widget.domNode.classList.toggle('request-in-progress', inProgress); + if (!inProgress) { + this._zone.value.widget.chatWidget.setInputPlaceholder(localize('placeholder', "Edit, refactor, and generate code")); + } else { + const prompt = session.chatModel.getRequests().at(-1)?.message.text; + this._zone.value.widget.chatWidget.setInputPlaceholder(prompt || localize('loading', "Working...")); } })); diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index a566e1e09b9..08d377f2a17 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -84,6 +84,14 @@ } +@keyframes pulse-opacity { + 0%, 100% { + opacity: 1; + } + 33% { + opacity: .6; + } +} .monaco-workbench .zone-widget.inline-chat-widget.inline-chat-2 { @@ -98,6 +106,16 @@ .interactive-session .chat-input-container:focus-within { border-color: var(--vscode-input-border); } + + .chat-attachments-container > .chat-input-toolbar { + margin-left: auto; + margin-right: 16px; + } + + /* TODO@jrieken this isn't the nicest selector... */ + .request-in-progress .monaco-editor [class^="ced-chat-session-detail"]::after { + animation: pulse-opacity 2.5s ease-in-out infinite; + } } From e82ab3b36635fa28c7cea5d5f10215c2bd59aab3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:44:15 +0000 Subject: [PATCH 1938/4355] SCM - add more commands to the repositories view (#274352) * SCM - artifact tree improvements * Add support for compression * Add more commands --- extensions/git/package.json | 35 ++++ extensions/git/src/commands.ts | 94 ++++++--- extensions/git/src/util.ts | 8 +- .../scm/browser/scmRepositoriesViewPane.ts | 193 ++++++++++++------ src/vs/workbench/contrib/scm/browser/util.ts | 8 +- 5 files changed, 251 insertions(+), 87 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 76843999334..87e7dcae439 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1028,6 +1028,18 @@ "icon": "$(target)", "category": "Git", "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.checkoutDetached", + "title": "%command.graphCheckoutDetached%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.compareRef", + "title": "%command.graphCompareRef%", + "category": "Git", + "enablement": "!operationInProgress" } ], "continueEditSession": [ @@ -1655,6 +1667,14 @@ { "command": "git.repositories.checkout", "when": "false" + }, + { + "command": "git.repositories.checkoutDetached", + "when": "false" + }, + { + "command": "git.repositories.compareRef", + "when": "false" } ], "scm/title": [ @@ -1862,6 +1882,21 @@ "command": "git.repositories.checkout", "group": "inline@1", "when": "scmProvider == git" + }, + { + "command": "git.repositories.checkout", + "group": "1_checkout@1", + "when": "scmProvider == git" + }, + { + "command": "git.repositories.checkoutDetached", + "group": "1_checkout@2", + "when": "scmProvider == git" + }, + { + "command": "git.repositories.compareRef", + "group": "2_compare@1", + "when": "scmProvider == git" } ], "scm/resourceGroup/context": [ diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5e60b8de373..7054410ee05 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; -import { DiagnosticSeverityConfig, dispose, fromNow, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; +import { DiagnosticSeverityConfig, dispose, fromNow, getHistoryItemDisplayName, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -124,6 +124,7 @@ class RefItem implements QuickPickItem { get refName(): string | undefined { return this.ref.name; } get refRemote(): string | undefined { return this.ref.remote; } get shortCommit(): string { return (this.ref.commit || '').substring(0, this.shortCommitLength); } + get commitMessage(): string | undefined { return this.ref.commitDetails?.message; } private _buttons?: QuickInputButton[]; get buttons(): QuickInputButton[] | undefined { return this._buttons; } @@ -3115,10 +3116,13 @@ export class CommandCenter { return; } + const title = `${repository.historyProvider.currentHistoryItemRemoteRef.name} ↔ ${getHistoryItemDisplayName(historyItem)}`; + await this._openChangesBetweenRefs( repository, - repository.historyProvider.currentHistoryItemRemoteRef.name, - historyItem); + repository.historyProvider.currentHistoryItemRemoteRef.revision, + historyItem.id, + title); } @command('git.graph.compareWithMergeBase', { repository: true }) @@ -3127,14 +3131,17 @@ export class CommandCenter { return; } + const title = `${repository.historyProvider.currentHistoryItemBaseRef.name} ↔ ${getHistoryItemDisplayName(historyItem)}`; + await this._openChangesBetweenRefs( repository, repository.historyProvider.currentHistoryItemBaseRef.name, - historyItem); + historyItem.id, + title); } @command('git.graph.compareRef', { repository: true }) - async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + async compareRef(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { if (!repository || !historyItem) { return; } @@ -3161,39 +3168,30 @@ export class CommandCenter { return; } + const title = `${sourceRef.ref.name} ↔ ${getHistoryItemDisplayName(historyItem)}`; + await this._openChangesBetweenRefs( repository, - sourceRef.ref.name, - historyItem); + sourceRef.ref.commit, + historyItem.id, + title); } - private async _openChangesBetweenRefs(repository: Repository, ref: string | undefined, historyItem: SourceControlHistoryItem | undefined): Promise { - if (!repository || !ref || !historyItem) { + private async _openChangesBetweenRefs(repository: Repository, ref1: string | undefined, ref2: string | undefined, title: string): Promise { + if (!repository || !ref1 || !ref2) { return; } - const ref2 = historyItem.references?.length - ? historyItem.references[0].name - : historyItem.id; - try { - const changes = await repository.diffBetween2(ref, historyItem.id); + const changes = await repository.diffBetween2(ref1, ref2); if (changes.length === 0) { - window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref, ref2)); + window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref1, ref2)); return; } - const refDisplayName = historyItem.references?.length - ? historyItem.references[0].name - : `${historyItem.displayId || historyItem.id} - ${historyItem.subject}`; - - const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref, ref2)); - const title = `${ref} ↔ ${refDisplayName}`; - const multiDiffSourceUri = Uri.from({ - scheme: 'git-ref-compare', - path: `${repository.root}/${ref}..${ref2}` - }); + const multiDiffSourceUri = Uri.from({ scheme: 'git-ref-compare', path: `${repository.root}/${ref1}..${ref2}` }); + const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref1, ref2)); await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, @@ -3201,7 +3199,7 @@ export class CommandCenter { resources }); } catch (err) { - window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref, ref2, err.message)); + window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref1, ref2, err.message)); } } @@ -5191,6 +5189,50 @@ export class CommandCenter { await this._checkout(repository, { treeish: artifact.name }); } + @command('git.repositories.checkoutDetached', { repository: true }) + async artifactCheckoutDetached(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await this._checkout(repository, { treeish: artifact.name, detached: true }); + } + + @command('git.repositories.compareRef', { repository: true }) + async artifactCompareWith(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a reference to compare with'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem) || !sourceRef.ref.commit) { + return; + } + + await this._openChangesBetweenRefs( + repository, + sourceRef.ref.commit, + artifact.id, + `${sourceRef.ref.name} ↔ ${artifact.name}`); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 7600a2e46ac..fcc820c8cd4 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri, DiagnosticSeverity, env } from 'vscode'; +import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri, DiagnosticSeverity, env, SourceControlHistoryItem } from 'vscode'; import { dirname, normalize, sep, relative } from 'path'; import { Readable } from 'stream'; import { promises as fs, createReadStream } from 'fs'; @@ -797,6 +797,12 @@ export function getCommitShortHash(scope: Uri, hash: string): string { return hash.substring(0, shortHashLength); } +export function getHistoryItemDisplayName(historyItem: SourceControlHistoryItem): string { + return historyItem.references?.length + ? historyItem.references[0].name + : historyItem.displayId ?? historyItem.id; +} + export type DiagnosticSeverityConfig = 'error' | 'warning' | 'information' | 'hint' | 'none'; export function toDiagnosticSeverity(value: DiagnosticSeverityConfig): DiagnosticSeverity { diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 307ae6c5579..55a39c09022 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -8,7 +8,7 @@ import { localize } from '../../../../nls.js'; import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { append, $ } from '../../../../base/browser/dom.js'; import { IListVirtualDelegate, IIdentityProvider } from '../../../../base/browser/ui/list/list.js'; -import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent, ITreeNode, ITreeRenderer, ITreeElementRenderDetails } from '../../../../base/browser/ui/tree/tree.js'; +import { IAsyncDataSource, ITreeEvent, ITreeContextMenuEvent, ITreeNode, ITreeElementRenderDetails } from '../../../../base/browser/ui/tree/tree.js'; import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; import { ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -21,7 +21,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IViewDescriptorService } from '../../../common/views.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { RepositoryActionRunner, RepositoryRenderer } from './scmRepositoryRenderer.js'; -import { collectContextMenuActions, connectPrimaryMenu, getActionViewItemProvider, isSCMArtifactGroupTreeElement, isSCMArtifactTreeElement, isSCMRepository } from './util.js'; +import { collectContextMenuActions, connectPrimaryMenu, getActionViewItemProvider, isSCMArtifactGroupTreeElement, isSCMArtifactNode, isSCMArtifactTreeElement, isSCMRepository } from './util.js'; import { Orientation } from '../../../../base/browser/ui/sash/sash.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; @@ -37,8 +37,14 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IResourceNode, ResourceTree } from '../../../../base/common/resourceTree.js'; +import { URI } from '../../../../base/common/uri.js'; +import { basename } from '../../../../base/common/resources.js'; +import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; +import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/asyncDataTree.js'; -type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement; +type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode; class ListDelegate implements IListVirtualDelegate { @@ -51,7 +57,7 @@ class ListDelegate implements IListVirtualDelegate { return RepositoryRenderer.TEMPLATE_ID; } else if (isSCMArtifactGroupTreeElement(element)) { return ArtifactGroupRenderer.TEMPLATE_ID; - } else if (isSCMArtifactTreeElement(element)) { + } else if (isSCMArtifactTreeElement(element) || isSCMArtifactNode(element)) { return ArtifactRenderer.TEMPLATE_ID; } else { throw new Error('Invalid tree element'); @@ -66,7 +72,7 @@ interface ArtifactGroupTemplate { readonly templateDisposable: IDisposable; } -class ArtifactGroupRenderer implements ITreeRenderer { +class ArtifactGroupRenderer implements ICompressibleTreeRenderer { static readonly TEMPLATE_ID = 'artifactGroup'; get templateId(): string { return ArtifactGroupRenderer.TEMPLATE_ID; } @@ -106,11 +112,16 @@ class ArtifactGroupRenderer implements ITreeRenderer, FuzzyScore>, index: number, templateData: ArtifactGroupTemplate, details?: ITreeElementRenderDetails): void { + throw new Error('Should never happen since node is incompressible'); + } + disposeElement(element: ITreeNode, index: number, templateData: ArtifactGroupTemplate, details?: ITreeElementRenderDetails): void { templateData.elementDisposables.clear(); } disposeTemplate(templateData: ArtifactGroupTemplate): void { + templateData.elementDisposables.dispose(); templateData.templateDisposable.dispose(); } } @@ -122,7 +133,7 @@ interface ArtifactTemplate { readonly templateDisposable: IDisposable; } -class ArtifactRenderer implements ITreeRenderer { +class ArtifactRenderer implements ICompressibleTreeRenderer, FuzzyScore, ArtifactTemplate> { static readonly TEMPLATE_ID = 'artifact'; get templateId(): string { return ArtifactRenderer.TEMPLATE_ID; } @@ -147,28 +158,49 @@ class ArtifactRenderer implements ITreeRenderer, index: number, templateData: ArtifactTemplate): void { - const provider = node.element.repository.provider; - const artifact = node.element.artifact; + renderElement(nodeOrElement: ITreeNode, FuzzyScore>, index: number, templateData: ArtifactTemplate): void { + const artifactOrFolder = nodeOrElement.element; - const artifactGroup = node.element.group; - const artifactGroupIcon = ThemeIcon.isThemeIcon(artifactGroup.icon) - ? `$(${artifactGroup.icon.id}) ` : ''; + if (isSCMArtifactNode(artifactOrFolder)) { + // Folder + templateData.label.setLabel(`$(folder) ${basename(artifactOrFolder.uri)}`); - templateData.label.setLabel(`${artifactGroupIcon}${artifact.name}`, artifact.description); + templateData.actionBar.setActions([]); + templateData.actionBar.context = undefined; + } else { + // Artifact + const artifact = artifactOrFolder.artifact; + const artifactIcon = ThemeIcon.isThemeIcon(artifactOrFolder.group.icon) + ? `$(${artifactOrFolder.group.icon.id}) ` + : ''; + + const artifactLabel = artifact.name.split('/').pop() ?? artifact.name; + templateData.label.setLabel(`${artifactIcon}${artifactLabel}`, artifact.description); + + const provider = artifactOrFolder.repository.provider; + const repositoryMenus = this._scmViewService.menus.getRepositoryMenus(provider); + templateData.elementDisposables.add(connectPrimaryMenu(repositoryMenus.getArtifactMenu(artifactOrFolder.group), primary => { + templateData.actionBar.setActions(primary); + }, 'inline', provider)); + templateData.actionBar.context = artifact; + } + } - const repositoryMenus = this._scmViewService.menus.getRepositoryMenus(provider); - templateData.elementDisposables.add(connectPrimaryMenu(repositoryMenus.getArtifactMenu(artifactGroup), primary => { - templateData.actionBar.setActions(primary); - }, 'inline', provider)); - templateData.actionBar.context = artifact; + renderCompressedElements(node: ITreeNode>, FuzzyScore>, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void { + const compressed = node.element as ICompressedTreeNode>; + const folder = compressed.elements[compressed.elements.length - 1]; + templateData.label.setLabel(`$(folder) ${folder.uri.fsPath.substring(1)}`); + + templateData.actionBar.setActions([]); + templateData.actionBar.context = undefined; } - disposeElement(element: ITreeNode, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void { + disposeElement(element: ITreeNode, FuzzyScore>, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void { templateData.elementDisposables.clear(); } disposeTemplate(templateData: ArtifactTemplate): void { + templateData.elementDisposables.dispose(); templateData.templateDisposable.dispose(); } } @@ -204,17 +236,26 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource ({ - repository, - group: inputOrElement.artifactGroup, - artifact, - type: 'artifact' - })); - } else if (isSCMArtifactTreeElement(inputOrElement)) { - return []; - } else { - return []; - } + // Create resource tree for artifacts + const artifactsTree = new ResourceTree(inputOrElement); + for (const artifact of artifacts) { + artifactsTree.add(URI.from({ + scheme: 'scm-artifact', path: artifact.name + }), { + repository, + group: inputOrElement.artifactGroup, + artifact, + type: 'artifact' + }); + } + + return Iterable.map(artifactsTree.root.children, node => node.element ?? node); + } else if (isSCMArtifactNode(inputOrElement)) { + return Iterable.map(inputOrElement.children, + node => node.element && node.childrenCount === 0 ? node.element : node); + } else if (isSCMArtifactTreeElement(inputOrElement)) { } + + return []; } hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { @@ -238,6 +279,8 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource 0; } else { return false; } @@ -252,12 +295,24 @@ class RepositoryTreeIdentityProvider implements IIdentityProvider { return `artifactGroup:${element.repository.provider.id}/${element.artifactGroup.id}`; } else if (isSCMArtifactTreeElement(element)) { return `artifact:${element.repository.provider.id}/${element.group.id}/${element.artifact.id}`; + } else if (isSCMArtifactNode(element)) { + return `artifactFolder:${element.context.repository.provider.id}/${element.context.artifactGroup.id}/${element.uri.fsPath}`; } else { throw new Error('Invalid tree element'); } } } +class RepositoriesTreeCompressionDelegate implements ITreeCompressionDelegate { + isIncompressible(element: TreeElement): boolean { + if (ResourceTree.isResourceNode(element)) { + return element.childrenCount === 0 || !element.parent || !element.parent.parent; + } + + return true; + } +} + export class SCMRepositoriesViewPane extends ViewPane { private tree!: WorkbenchCompressibleAsyncDataTree; @@ -361,16 +416,12 @@ export class SCMRepositoriesViewPane extends ViewPane { this.treeDataSource = this.instantiationService.createInstance(RepositoryTreeDataSource); this._register(this.treeDataSource); - const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); - this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, 'SCM Repositories', container, new ListDelegate(), - { - isIncompressible: () => true - }, + new RepositoriesTreeCompressionDelegate(), [ this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMSourceControlInline, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ArtifactGroupRenderer), @@ -389,9 +440,20 @@ export class SCMRepositoriesViewPane extends ViewPane { } // Explorer mode + // Expand artifact folders with one child only + if (isSCMArtifactNode(e)) { + if (e.childrenCount !== 1) { + return true; + } + + // Check if the only child is a leaf node + const firstChild = Iterable.first(e.children); + return firstChild?.element !== undefined; + } + return true; }, - compressionEnabled: compressionEnabled.get(), + compressionEnabled: true, overrideStyles: this.getLocationBasedColors().listOverrideStyles, multipleSelectionSupport: this.scmViewService.selectionModeConfig.get() === 'multiple', expandOnDoubleClick: true, @@ -456,29 +518,42 @@ export class SCMRepositoriesViewPane extends ViewPane { return; } - if (!isSCMRepository(e.element)) { - return; - } - - const provider = e.element.provider; - const menus = this.scmViewService.menus.getRepositoryMenus(provider); - const menu = menus.getRepositoryContextMenu(e.element); - const actions = collectContextMenuActions(menu); + if (isSCMRepository(e.element)) { + // Repository + const provider = e.element.provider; + const menus = this.scmViewService.menus.getRepositoryMenus(provider); + const menu = menus.getRepositoryContextMenu(e.element); + const actions = collectContextMenuActions(menu); - const disposables = new DisposableStore(); - const actionRunner = new RepositoryActionRunner(() => { - return this.getTreeSelection(); - }); - disposables.add(actionRunner); - disposables.add(actionRunner.onWillRun(() => this.tree.domFocus())); - - this.contextMenuService.showContextMenu({ - actionRunner, - getAnchor: () => e.anchor, - getActions: () => actions, - getActionsContext: () => provider, - onHide: () => disposables.dispose() - }); + const disposables = new DisposableStore(); + const actionRunner = new RepositoryActionRunner(() => { + return this.getTreeSelection(); + }); + disposables.add(actionRunner); + disposables.add(actionRunner.onWillRun(() => this.tree.domFocus())); + + this.contextMenuService.showContextMenu({ + actionRunner, + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => provider, + onHide: () => disposables.dispose() + }); + } else if (isSCMArtifactTreeElement(e.element)) { + // Artifact + const provider = e.element.repository.provider; + const artifact = e.element.artifact; + + const menus = this.scmViewService.menus.getRepositoryMenus(provider); + const menu = menus.getArtifactMenu(e.element.group); + const actions = collectContextMenuActions(menu, provider); + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => artifact + }); + } } private onTreeSelectionChange(e: ITreeEvent): void { @@ -615,6 +690,8 @@ export class SCMRepositoriesViewPane extends ViewPane { return e; } else if (isSCMArtifactGroupTreeElement(e) || isSCMArtifactTreeElement(e)) { return e.repository; + } else if (isSCMArtifactNode(e)) { + return e.context.repository; } else { throw new Error('Invalid tree element'); } diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 1783c012ef4..50670663afc 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -70,6 +70,10 @@ export function isSCMArtifactGroupTreeElement(element: unknown): element is SCMA return (element as SCMArtifactGroupTreeElement).type === 'artifactGroup'; } +export function isSCMArtifactNode(element: unknown): element is IResourceNode { + return ResourceTree.isResourceNode(element) && isSCMArtifactGroupTreeElement(element.context); +} + export function isSCMArtifactTreeElement(element: unknown): element is SCMArtifactTreeElement { return (element as SCMArtifactTreeElement).type === 'artifact'; } @@ -104,8 +108,8 @@ export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], s return menu.onDidChange(updateActions); } -export function collectContextMenuActions(menu: IMenu): IAction[] { - return getContextMenuActions(menu.getActions({ shouldForwardArgs: true }), 'inline').secondary; +export function collectContextMenuActions(menu: IMenu, arg?: unknown): IAction[] { + return getContextMenuActions(menu.getActions({ arg, shouldForwardArgs: true }), 'inline').secondary; } export class StatusBarAction extends Action { From e69a45d855ca6e10f6dd7b5dbdb0ef1ab529fa07 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:44:45 +0000 Subject: [PATCH 1939/4355] SCM - disable context menu on incoming/outgoing changes nodes (#274355) --- src/vs/workbench/contrib/scm/browser/scmHistory.ts | 2 +- src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index 1a5926690ce..d1ac36b056a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -240,7 +240,7 @@ export function renderSCMHistoryItemGraph(historyItemViewModel: ISCMHistoryItemV const innerCircle = drawCircle(circleIndex, CIRCLE_STROKE_WIDTH, CIRCLE_RADIUS + 5); svg.append(innerCircle); - const dashedCircle = drawDashedCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH, circleColor); + const dashedCircle = drawDashedCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH - 1, circleColor); svg.append(dashedCircle); } else { if (historyItem.parentIds.length > 1) { diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index d67ea2f1331..012800e0453 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -2033,6 +2033,11 @@ export class SCMHistoryViewPane extends ViewPane { if (isSCMHistoryItemViewModelTreeElement(element)) { // HistoryItem + if (element.historyItemViewModel.kind === 'incoming-changes' || element.historyItemViewModel.kind === 'outgoing-changes') { + // Incoming/Outgoing changes node does not support any context menu actions + return; + } + this._contextMenuDisposables.value = new DisposableStore(); const historyProvider = element.repository.provider.historyProvider.get(); From 3b48ce40609207200c76d73cb860efc666037041 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 31 Oct 2025 16:12:49 +0100 Subject: [PATCH 1940/4355] only enable undo/keep keybindings when cursor is inside the respective hunks (#274361) https://github.com/microsoft/vscode/issues/258281 --- .../chatEditingCodeEditorIntegration.ts | 39 +++++++++++++++---- .../chatEditing/chatEditingEditorActions.ts | 4 +- .../chatEditingEditorContextKeys.ts | 1 + 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index e769749a75b..a1ec84829ab 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -40,6 +40,8 @@ import { IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEdi import { isTextDiffEditorForEntry } from './chatEditing.js'; import { ActionViewItem } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { AcceptHunkAction, RejectHunkAction } from './chatEditingEditorActions.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ctxCursorInChangeRange } from './chatEditingEditorContextKeys.js'; export interface IDocumentDiff2 extends IDocumentDiff { @@ -73,6 +75,7 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito renderDiffImmediately: boolean, @IEditorService private readonly _editorService: IEditorService, @IAccessibilitySignalService private readonly _accessibilitySignalsService: IAccessibilitySignalService, + @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, ) { this._diffLineDecorations = _editor.createDecorationsCollection(); @@ -159,22 +162,32 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito } })); + const _ctxCursorInChangeRange = ctxCursorInChangeRange.bindTo(contextKeyService); // accessibility: signals while cursor changes + // ctx: cursor in change range this._store.add(autorun(r => { const position = codeEditorObs.positions.read(r)?.at(0); if (!position || !enabledObs.read(r)) { + _ctxCursorInChangeRange.reset(); return; } const diff = documentDiffInfo.read(r); - const mapping = diff.changes.find(m => m.modified.contains(position.lineNumber) || m.modified.isEmpty && m.modified.startLineNumber === position.lineNumber); - if (mapping?.modified.isEmpty) { - this._accessibilitySignalsService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'chatEditingEditor.cursorPositionChanged' }); - } else if (mapping?.original.isEmpty) { - this._accessibilitySignalsService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'chatEditingEditor.cursorPositionChanged' }); - } else if (mapping) { - this._accessibilitySignalsService.playSignal(AccessibilitySignal.diffLineModified, { source: 'chatEditingEditor.cursorPositionChanged' }); + const changeAtCursor = diff.changes.find(m => m.modified.contains(position.lineNumber) || m.modified.isEmpty && m.modified.startLineNumber === position.lineNumber); + + _ctxCursorInChangeRange.set(!!changeAtCursor); + + if (changeAtCursor) { + let signal: AccessibilitySignal; + if (changeAtCursor.modified.isEmpty) { + signal = AccessibilitySignal.diffLineDeleted; + } else if (changeAtCursor.original.isEmpty) { + signal = AccessibilitySignal.diffLineInserted; + } else { + signal = AccessibilitySignal.diffLineModified; + } + this._accessibilitySignalsService.playSignal(signal, { source: 'chatEditingEditor.cursorPositionChanged' }); } })); @@ -442,6 +455,18 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito })); + this._diffHunksRenderStore.add(this._editor.onMouseUp(e => { + // set approximate position when clicking on view zone + if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + const zone = e.target.detail; + const idx = this._viewZones.findIndex(id => id === zone.viewZoneId); + if (idx >= 0) { + this._editor.setPosition(e.target.position); + this._editor.focus(); + } + } + })); + this._diffHunksRenderStore.add(this._editor.onMouseMove(e => { // reveal when hovering over diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 14f8d78514d..ff27554ae55 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -23,7 +23,7 @@ import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState } from '../../common/chatEditingService.js'; import { LocalChatSessionUri } from '../../common/chatUri.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; -import { ctxHasEditorModification, ctxIsCurrentlyBeingModified, ctxIsGlobalEditingSession, ctxReviewModeEnabled } from './chatEditingEditorContextKeys.js'; +import { ctxCursorInChangeRange, ctxHasEditorModification, ctxIsCurrentlyBeingModified, ctxIsGlobalEditingSession, ctxReviewModeEnabled } from './chatEditingEditorContextKeys.js'; abstract class ChatEditingEditorAction extends Action2 { @@ -237,7 +237,7 @@ abstract class AcceptRejectHunkAction extends ChatEditingEditorAction { precondition: ContextKeyExpr.and(ctxHasEditorModification, ctxIsCurrentlyBeingModified.negate()), f1: true, keybinding: { - when: ContextKeyExpr.or(EditorContextKeys.focus, NOTEBOOK_CELL_LIST_FOCUSED), + when: ContextKeyExpr.and(ctxCursorInChangeRange, ContextKeyExpr.or(EditorContextKeys.focus, NOTEBOOK_CELL_LIST_FOCUSED)), weight: KeybindingWeight.WorkbenchContrib + 1, primary: _accept ? KeyMod.CtrlCmd | KeyCode.KeyY diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts index 39275b3d998..7e39ffbe7e3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorContextKeys.ts @@ -22,6 +22,7 @@ export const ctxIsCurrentlyBeingModified = new RawContextKey('chatEdits export const ctxReviewModeEnabled = new RawContextKey('chatEdits.isReviewModeEnabled', true, localize('chat.ctxReviewModeEnabled', "Review mode for chat changes is enabled")); export const ctxHasRequestInProgress = new RawContextKey('chatEdits.isRequestInProgress', false, localize('chat.ctxHasRequestInProgress', "The current editor shows a file from an edit session which is still in progress")); export const ctxRequestCount = new RawContextKey('chatEdits.requestCount', 0, localize('chatEdits.requestCount', "The number of turns the editing session in this editor has")); +export const ctxCursorInChangeRange = new RawContextKey('chatEdits.cursorInChangeRange', false, localize('chat.ctxCursorInChangeRange', "The cursor is inside a change range made by chat editing.")); export class ChatEditingEditorContextKeys implements IWorkbenchContribution { From f6a89979f1271c309d7676a56674dfdc2cd23fc8 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:28:49 +0100 Subject: [PATCH 1941/4355] Fix hover display for nested targets (#274363) fix showing hover for nested hover targets --- src/vs/platform/hover/browser/hoverService.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/hover/browser/hoverService.ts b/src/vs/platform/hover/browser/hoverService.ts index 6fff21328a9..a3de2f5745a 100644 --- a/src/vs/platform/hover/browser/hoverService.ts +++ b/src/vs/platform/hover/browser/hoverService.ts @@ -444,7 +444,7 @@ export class HoverService extends Disposable implements IHoverService { // track the mouse position const onMouseMove = (e: MouseEvent) => { target.x = e.x + 10; - if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, targetElement) !== targetElement) { + if (!eventIsRelatedToTarget(e, targetElement)) { hideHover(true, true); } }; @@ -453,17 +453,21 @@ export class HoverService extends Disposable implements IHoverService { hoverPreparation = mouseOverStore; - if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, targetElement) !== targetElement) { + if (!eventIsRelatedToTarget(e, targetElement)) { return; // Do not show hover when the mouse is over another hover target } mouseOverStore.add(triggerShowHover(typeof hoverDelegate.delay === 'function' ? hoverDelegate.delay(content) : hoverDelegate.delay, false, target)); }, true)); - const onFocus = () => { + const onFocus = (e: FocusEvent) => { if (isMouseDown || hoverPreparation) { return; } + if (!eventIsRelatedToTarget(e, targetElement)) { + return; // Do not show hover when the focus is on another hover target + } + const target: IHoverDelegateTarget = { targetElements: [targetElement], dispose: () => { } @@ -597,6 +601,10 @@ class HoverContextViewDelegate implements IDelegate { } } +function eventIsRelatedToTarget(event: UIEvent, target: HTMLElement): boolean { + return isHTMLElement(event.target) && getHoverTargetElement(event.target as HTMLElement, target) === target; +} + function getHoverTargetElement(element: HTMLElement, stopElement?: HTMLElement): HTMLElement { stopElement = stopElement ?? getWindow(element).document.body; while (!element.hasAttribute('custom-hover') && element !== stopElement) { From d9476794e7ab451979dfcce478cd7249a770e105 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 31 Oct 2025 16:36:51 +0100 Subject: [PATCH 1942/4355] more feedback (#274366) --- .../chatManagement/chatModelsWidget.ts | 111 +++++++++++++----- .../chatManagement/media/chatModelsWidget.css | 42 ++++--- 2 files changed, 102 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts index d0dd7169607..adb355c52a0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatManagement/chatModelsWidget.ts @@ -252,7 +252,10 @@ abstract class ModelsTableColumnRenderer { +class GutterColumnRenderer extends ModelsTableColumnRenderer { - static readonly TEMPLATE_ID = 'toggleCollapse'; + static readonly TEMPLATE_ID = 'gutter'; - readonly templateId: string = ToggleCollapseColumnRenderer.TEMPLATE_ID; + readonly templateId: string = GutterColumnRenderer.TEMPLATE_ID; private readonly _onDidToggleCollapse = new Emitter(); readonly onDidToggleCollapse = this._onDidToggleCollapse.event; + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor( + @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService + ) { + super(); + } + renderTemplate(container: HTMLElement): IToggleCollapseColumnTemplateData { const disposables = new DisposableStore(); const elementDisposables = new DisposableStore(); - const actionBar = disposables.add(new ActionBar(DOM.append(container, $('.collapse-actions-column')))); + container.classList.add('models-gutter-column'); + const actionBar = disposables.add(new ActionBar(container)); return { rowContainer: container.parentElement, container, @@ -326,12 +339,28 @@ class ToggleCollapseColumnRenderer extends ModelsTableColumnRenderer { + const newVisibility = !isVisible; + this.languageModelsService.updateModelPickerPreference(modelEntry.identifier, newVisibility); + this._onDidChange.fire(); + } + }); + templateData.actionBar.push(toggleVisibilityAction, { icon: true, label: false }); } } interface IModelNameColumnTemplateData extends IModelTableColumnTemplateData { readonly statusIcon: HTMLElement; readonly nameLabel: HighlightedLabel; + readonly actionBar: ActionBar; } class ModelNameColumnRenderer extends ModelsTableColumnRenderer { @@ -351,10 +380,12 @@ class ModelNameColumnRenderer extends ModelsTableColumnRenderer